From cea2397e06c29614551f71d9934788169cc1fc20 Mon Sep 17 00:00:00 2001 From: Nick Rayburn Date: Thu, 27 May 2021 23:13:03 -0500 Subject: [PATCH 0001/1897] npm check for root package.json before findFiles --- extensions/npm/src/tasks.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index 911d66fbe3b..533ec5c56b6 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -365,12 +365,16 @@ export function getPackageJsonUriFromTask(task: Task): Uri | null { } export async function hasPackageJson(): Promise { + // Faster than `findFiles` for workspaces with a root package.json. + if (await hasRootPackageJson()) { + return true; + } const token = new CancellationTokenSource(); // Search for files for max 1 second. const timeout = setTimeout(() => token.cancel(), 1000); const files = await workspace.findFiles('**/package.json', undefined, 1, token.token); clearTimeout(timeout); - return files.length > 0 || await hasRootPackageJson(); + return files.length > 0; } async function hasRootPackageJson(): Promise { From 9a61ab7c32c4ee45191f551d26dea971bf1f610e Mon Sep 17 00:00:00 2001 From: hsfzxjy Date: Tue, 28 Nov 2023 22:10:35 +0800 Subject: [PATCH 0002/1897] Add duration to the terminal command SI tooltip --- .../common/capabilities/capabilities.ts | 1 + .../commandDetection/terminalCommand.ts | 21 +++++++++++++++++++ .../commandDetectionCapability.ts | 2 ++ .../browser/xterm/decorationStyles.ts | 6 +++--- .../test/browser/terminalLinkOpeners.test.ts | 3 +++ 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/capabilities.ts b/src/vs/platform/terminal/common/capabilities/capabilities.ts index cf9e74a75d2..debdaf7a926 100644 --- a/src/vs/platform/terminal/common/capabilities/capabilities.ts +++ b/src/vs/platform/terminal/common/capabilities/capabilities.ts @@ -242,6 +242,7 @@ interface IBaseTerminalCommand { command: string; isTrusted: boolean; timestamp: number; + duration: number; // Optional serializable cwd: string | undefined; diff --git a/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts b/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts index f792df9a3d2..1fffd61bfe0 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts @@ -14,6 +14,7 @@ export interface ITerminalCommandProperties { command: string; isTrusted: boolean; timestamp: number; + duration: number; marker: IXtermMarker | undefined; cwd: string | undefined; exitCode: number | undefined; @@ -34,6 +35,7 @@ export class TerminalCommand implements ITerminalCommand { get command() { return this._properties.command; } get isTrusted() { return this._properties.isTrusted; } get timestamp() { return this._properties.timestamp; } + get duration() { return this._properties.duration; } get promptStartMarker() { return this._properties.promptStartMarker; } get marker() { return this._properties.marker; } get endMarker() { return this._properties.endMarker; } @@ -77,6 +79,7 @@ export class TerminalCommand implements ITerminalCommand { executedMarker, executedX: serialized.executedX, timestamp: serialized.timestamp, + duration: serialized.duration, cwd: serialized.cwd, commandStartLineContent: serialized.commandStartLineContent, exitCode: serialized.exitCode, @@ -101,6 +104,7 @@ export class TerminalCommand implements ITerminalCommand { exitCode: this.exitCode, commandStartLineContent: this.commandStartLineContent, timestamp: this.timestamp, + duration: this.duration, markProperties: this.markProperties, }; } @@ -251,6 +255,9 @@ export class PartialTerminalCommand implements ICurrentPartialCommand { commandExecutedMarker?: IMarker; commandExecutedX?: number; + private commandExecutedTimestamp?: number; + private commandDuration?: number; + commandFinishedMarker?: IMarker; currentContinuationMarker?: IMarker; @@ -284,6 +291,7 @@ export class PartialTerminalCommand implements ICurrentPartialCommand { exitCode: undefined, commandStartLineContent: undefined, timestamp: 0, + duration: 0, markProperties: undefined }; } @@ -305,6 +313,7 @@ export class PartialTerminalCommand implements ICurrentPartialCommand { executedMarker: this.commandExecutedMarker, executedX: this.commandExecutedX, timestamp: Date.now(), + duration: this.commandDuration || 0, cwd, exitCode, commandStartLineContent: this.commandStartLineContent, @@ -315,6 +324,18 @@ export class PartialTerminalCommand implements ICurrentPartialCommand { return undefined; } + markExecutedTime() { + if (this.commandExecutedTimestamp === undefined) { + this.commandExecutedTimestamp = Date.now(); + } + } + + markFinishedTime() { + if (this.commandDuration === undefined && this.commandExecutedTimestamp !== undefined) { + this.commandDuration = Date.now() - this.commandExecutedTimestamp; + } + } + getPromptRowCount(): number { return getPromptRowCount(this, this._xterm.buffer.active); } diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index da16580b4b4..5ba9a37177d 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -303,9 +303,11 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe handleCommandExecuted(options?: IHandleCommandOptions): void { this._ptyHeuristics.value.handleCommandExecuted(options); + this._currentCommand.markExecutedTime(); } handleCommandFinished(exitCode: number | undefined, options?: IHandleCommandOptions): void { + this._currentCommand.markFinishedTime(); this._ptyHeuristics.value.preHandleCommandFinished?.(); this._logService.debug('CommandDetectionCapability#handleCommandFinished', this._terminal.buffer.active.cursorX, options?.marker?.line, this._currentCommand.command, this._currentCommand); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationStyles.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationStyles.ts index 4d475759e0b..bc8b3843f54 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationStyles.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationStyles.ts @@ -73,12 +73,12 @@ export class TerminalDecorationHoverManager extends Disposable { } } else if (command.exitCode) { if (command.exitCode === -1) { - hoverContent += localize('terminalPromptCommandFailed', 'Command executed {0} and failed', fromNow(command.timestamp, true)); + hoverContent += localize('terminalPromptCommandFailed', 'Command executed {0} for {1} ms and failed', fromNow(command.timestamp, true), command.duration); } else { - hoverContent += localize('terminalPromptCommandFailedWithExitCode', 'Command executed {0} and failed (Exit Code {1})', fromNow(command.timestamp, true), command.exitCode); + hoverContent += localize('terminalPromptCommandFailedWithExitCode', 'Command executed {0} for {2} ms and failed (Exit Code {1})', fromNow(command.timestamp, true), command.exitCode, command.duration); } } else { - hoverContent += localize('terminalPromptCommandSuccess', 'Command executed {0}', fromNow(command.timestamp, true)); + hoverContent += localize('terminalPromptCommandSuccess', 'Command executed {0} for {1} ms', fromNow(command.timestamp, true), command.duration); } this._hoverService.showHover({ content: new MarkdownString(hoverContent), target: element }); }); diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts index 10eca24459c..a6c9139fc2a 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts @@ -142,6 +142,7 @@ suite('Workbench - TerminalLinkOpeners', () => { isTrusted: true, cwd: '/initial/cwd', timestamp: 0, + duration: 0, executedX: undefined, startX: undefined, marker: { @@ -279,6 +280,7 @@ suite('Workbench - TerminalLinkOpeners', () => { isTrusted: true, cwd, timestamp: 0, + duration: 0, executedX: undefined, startX: undefined, marker: { @@ -374,6 +376,7 @@ suite('Workbench - TerminalLinkOpeners', () => { executedX: undefined, startX: undefined, timestamp: 0, + duration: 0, marker: { line: 0 } as Partial as any, From 74c419b5bd8c18e45ec8a962a2b01a17bd17e7c7 Mon Sep 17 00:00:00 2001 From: samhanic <55490861+samhanic@users.noreply.github.com> Date: Sun, 3 Dec 2023 14:25:40 +0100 Subject: [PATCH 0003/1897] add cli update-extensions prototype --- cli/src/bin/code/legacy_args.rs | 9 +++++++++ cli/src/commands/args.rs | 5 +++++ cli/src/tunnels/code_server.rs | 4 ++++ resources/completions/bash/code | 2 +- resources/completions/zsh/_code | 1 + src/server-main.js | 4 ++-- src/vs/code/node/cli.ts | 1 + src/vs/code/node/cliProcessMain.ts | 4 ++++ src/vs/platform/environment/common/argv.ts | 1 + src/vs/platform/environment/node/argv.ts | 1 + .../extensionManagement/common/extensionManagementCLI.ts | 4 ++++ src/vs/server/node/remoteExtensionHostAgentCli.ts | 5 +++++ src/vs/server/node/server.cli.ts | 8 ++++++-- src/vs/server/node/serverEnvironmentService.ts | 2 ++ 14 files changed, 46 insertions(+), 5 deletions(-) diff --git a/cli/src/bin/code/legacy_args.rs b/cli/src/bin/code/legacy_args.rs index 6a533426608..3f134443641 100644 --- a/cli/src/bin/code/legacy_args.rs +++ b/cli/src/bin/code/legacy_args.rs @@ -60,6 +60,7 @@ pub fn try_parse_legacy( // Now translate them to subcommands. // --list-extensions -> ext list + // --update-extensions -> update // --install-extension=id -> ext install // --uninstall-extension=id -> ext uninstall // --status -> status @@ -87,6 +88,14 @@ pub fn try_parse_legacy( })), ..Default::default() }) + } else if let Some(_exts) = args.remove("update-extensions") { + Some(CliCore { + subcommand: Some(Commands::Extension(ExtensionArgs { + subcommand: ExtensionSubcommand::Update, + desktop_code_options, + })), + ..Default::default() + }) } else if let Some(exts) = args.remove("uninstall-extension") { Some(CliCore { subcommand: Some(Commands::Extension(ExtensionArgs { diff --git a/cli/src/commands/args.rs b/cli/src/commands/args.rs index 7863635ae99..6b0017a3488 100644 --- a/cli/src/commands/args.rs +++ b/cli/src/commands/args.rs @@ -254,6 +254,8 @@ pub enum ExtensionSubcommand { Install(InstallExtensionArgs), /// Uninstall an extension. Uninstall(UninstallExtensionArgs), + /// Update the marketplace-installed extensions. + Update, } impl ExtensionSubcommand { @@ -284,6 +286,9 @@ impl ExtensionSubcommand { target.push(format!("--uninstall-extension={}", id)); } } + ExtensionSubcommand::Update => { + target.push("--update-extensions".to_string()); + } } } } diff --git a/cli/src/tunnels/code_server.rs b/cli/src/tunnels/code_server.rs index 06e42681799..27d8bed1845 100644 --- a/cli/src/tunnels/code_server.rs +++ b/cli/src/tunnels/code_server.rs @@ -58,6 +58,7 @@ pub struct CodeServerArgs { // extension management pub install_extensions: Vec, pub uninstall_extensions: Vec, + pub update_extensions: bool, pub list_extensions: bool, pub show_versions: bool, pub category: Option, @@ -129,6 +130,9 @@ impl CodeServerArgs { for extension in &self.uninstall_extensions { args.push(format!("--uninstall-extension={}", extension)); } + if self.update_extensions { + args.push(String::from("--update-extensions")); + } if self.list_extensions { args.push(String::from("--list-extensions")); if self.show_versions { diff --git a/resources/completions/bash/code b/resources/completions/bash/code index 9f1b3d682d3..d141c297b96 100644 --- a/resources/completions/bash/code +++ b/resources/completions/bash/code @@ -49,7 +49,7 @@ _@@APPNAME@@() --list-extensions --show-versions --install-extension --uninstall-extension --enable-proposed-api --verbose --log -s --status -p --performance --prof-startup --disable-extensions - --disable-extension --inspect-extensions + --disable-extension --inspect-extensions --update-extensions --inspect-brk-extensions --disable-gpu' -- "$cur") ) [[ $COMPREPLY == *= ]] && compopt -o nospace return diff --git a/resources/completions/zsh/_code b/resources/completions/zsh/_code index 97d163c13c3..46a0e019680 100644 --- a/resources/completions/zsh/_code +++ b/resources/completions/zsh/_code @@ -21,6 +21,7 @@ arguments=( '--show-versions[show versions of installed extensions, when using --list-extensions]' '--install-extension[install an extension]:id or path:_files -g "*.vsix(-.)"' '--uninstall-extension[uninstall an extension]:id or path:_files -g "*.vsix(-.)"' + '--update-extensions[update the marketplace-installed extensions]' '--enable-proposed-api[enables proposed API features for extensions]::extension id: ' '--verbose[print verbose output (implies --wait)]' '--log[log level to use]:level [info]:(critical error warn info debug trace off)' diff --git a/src/server-main.js b/src/server-main.js index 5167527baed..81e88e118f7 100644 --- a/src/server-main.js +++ b/src/server-main.js @@ -20,7 +20,7 @@ async function start() { // Do a quick parse to determine if a server or the cli needs to be started const parsedArgs = minimist(process.argv.slice(2), { - boolean: ['start-server', 'list-extensions', 'print-ip-address', 'help', 'version', 'accept-server-license-terms'], + boolean: ['start-server', 'list-extensions', 'print-ip-address', 'help', 'version', 'accept-server-license-terms', 'update-extensions'], string: ['install-extension', 'install-builtin-extension', 'uninstall-extension', 'locate-extension', 'socket-path', 'host', 'port', 'compatibility'], alias: { help: 'h', version: 'v' } }); @@ -34,7 +34,7 @@ async function start() { }); const extensionLookupArgs = ['list-extensions', 'locate-extension']; - const extensionInstallArgs = ['install-extension', 'install-builtin-extension', 'uninstall-extension']; + const extensionInstallArgs = ['install-extension', 'install-builtin-extension', 'uninstall-extension', 'update-extensions']; const shouldSpawnCli = parsedArgs.help || parsedArgs.version || extensionLookupArgs.some(a => !!parsedArgs[a]) || (extensionInstallArgs.some(a => !!parsedArgs[a]) && !parsedArgs['start-server']); diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 4770e9ef0bd..89265911347 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -33,6 +33,7 @@ function shouldSpawnCliProcess(argv: NativeParsedArgs): boolean { || !!argv['list-extensions'] || !!argv['install-extension'] || !!argv['uninstall-extension'] + || !!argv['update-extensions'] || !!argv['locate-extension'] || !!argv['telemetry']; } diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index b2861976c11..b91367f1fc2 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -280,6 +280,10 @@ class CliMain extends Disposable { return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).uninstallExtensions(this.asExtensionIdOrVSIX(this.argv['uninstall-extension']), !!this.argv['force'], profileLocation); } + else if (this.argv['update-extensions']) { + return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).updateExtensions(profileLocation); + } + // Locate Extension else if (this.argv['locate-extension']) { return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).locateExtension(this.argv['locate-extension']); diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index f205cc30439..8b9f9100b05 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -82,6 +82,7 @@ export interface NativeParsedArgs { 'pre-release'?: boolean; 'install-builtin-extension'?: string[]; // undefined or array of 1 or more 'uninstall-extension'?: string[]; // undefined or array of 1 or more + 'update-extensions'?: boolean; 'locate-extension'?: string[]; // undefined or array of 1 or more 'enable-proposed-api'?: string[]; // undefined or array of 1 or more 'open-url'?: boolean; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 79046c7fa20..8ffc1c4a775 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -100,6 +100,7 @@ export const OPTIONS: OptionDescriptions> = { 'install-extension': { type: 'string[]', cat: 'e', args: 'ext-id | path', description: localize('installExtension', "Installs or updates an extension. The argument is either an extension id or a path to a VSIX. The identifier of an extension is '${publisher}.${name}'. Use '--force' argument to update to latest version. To install a specific version provide '@${version}'. For example: 'vscode.csharp@1.2.3'.") }, 'pre-release': { type: 'boolean', cat: 'e', description: localize('install prerelease', "Installs the pre-release version of the extension, when using --install-extension") }, 'uninstall-extension': { type: 'string[]', cat: 'e', args: 'ext-id', description: localize('uninstallExtension', "Uninstalls an extension.") }, + 'update-extensions': { type: 'boolean', cat: 'e', description: localize('updateExtensions', "Update the marketplace-installed extensions.") }, 'enable-proposed-api': { type: 'string[]', allowEmptyValue: true, cat: 'e', args: 'ext-id', description: localize('experimentalApis', "Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.") }, 'version': { type: 'boolean', cat: 't', alias: 'v', description: localize('version', "Print version.") }, diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts index 1515c45cca2..046ba0df740 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts @@ -172,6 +172,10 @@ export class ExtensionManagementCLI { } } + public async updateExtensions(profileLocation?: URI): Promise { + this.logger.info("Update extensions: TODO"); + } + private async installVSIX(vsix: URI, installOptions: InstallOptions, force: boolean, installedExtensions: ILocalExtension[]): Promise { const manifest = await this.extensionManagementService.getManifest(vsix); diff --git a/src/vs/server/node/remoteExtensionHostAgentCli.ts b/src/vs/server/node/remoteExtensionHostAgentCli.ts index 5f6c64c3a1d..6960a2d4424 100644 --- a/src/vs/server/node/remoteExtensionHostAgentCli.ts +++ b/src/vs/server/node/remoteExtensionHostAgentCli.ts @@ -159,6 +159,11 @@ class CliMain extends Disposable { return extensionManagementCLI.uninstallExtensions(this.asExtensionIdOrVSIX(this.args['uninstall-extension']), !!this.args['force']); } + // Update the marketplace-installed extensions + else if (this.args['update-extensions']) { + return extensionManagementCLI.updateExtensions(); + } + // Locate Extension else if (this.args['locate-extension']) { return extensionManagementCLI.locateExtension(this.args['locate-extension']); diff --git a/src/vs/server/node/server.cli.ts b/src/vs/server/node/server.cli.ts index 10e1acbb917..22e77753062 100644 --- a/src/vs/server/node/server.cli.ts +++ b/src/vs/server/node/server.cli.ts @@ -67,6 +67,7 @@ const isSupportedForPipe = (optionId: keyof RemoteParsedArgs) => { case 'status': case 'install-extension': case 'uninstall-extension': + case 'update-extensions': case 'list-extensions': case 'force': case 'show-versions': @@ -217,7 +218,7 @@ export async function main(desc: ProductDescription, args: string[]): Promise cmdLine.push('--install-extension', id)); parsedArgs['uninstall-extension']?.forEach(id => cmdLine.push('--uninstall-extension', id)); @@ -227,6 +228,9 @@ export async function main(desc: ProductDescription, args: string[]): Promise console.log(err)); @@ -293,7 +297,7 @@ export async function main(desc: ProductDescription, args: string[]): Promise> = { 'builtin-extensions-dir': OPTIONS['builtin-extensions-dir'], 'install-extension': OPTIONS['install-extension'], 'install-builtin-extension': OPTIONS['install-builtin-extension'], + 'update-extensions': OPTIONS['update-extensions'], 'uninstall-extension': OPTIONS['uninstall-extension'], 'list-extensions': OPTIONS['list-extensions'], 'locate-extension': OPTIONS['locate-extension'], @@ -177,6 +178,7 @@ export interface ServerParsedArgs { 'builtin-extensions-dir'?: string; 'install-extension'?: string[]; 'install-builtin-extension'?: string[]; + 'update-extensions'?: boolean; 'uninstall-extension'?: string[]; 'list-extensions'?: boolean; 'locate-extension'?: string[]; From 0148753af20e6b0fdb1888b21679cceb9f7d9c15 Mon Sep 17 00:00:00 2001 From: samhanic <55490861+samhanic@users.noreply.github.com> Date: Sun, 3 Dec 2023 15:58:37 +0100 Subject: [PATCH 0004/1897] add updateExtensions option to cli --- .../common/extensionManagementCLI.ts | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts index 046ba0df740..e41b3a2e3d9 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts @@ -173,7 +173,54 @@ export class ExtensionManagementCLI { } public async updateExtensions(profileLocation?: URI): Promise { - this.logger.info("Update extensions: TODO"); + const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User, profileLocation); + + const installedExtensionsQuery: IExtensionInfo[] = []; + for (const extension of installedExtensions) { + if (!!extension.identifier.uuid) { // No need to check new version for an unpublished extension + installedExtensionsQuery.push({ + id: `${extension.manifest.publisher}.${extension.manifest.name}`, + preRelease: extension.isPreReleaseVersion + }); + } + } + + this.logger.info(localize('updateExtensionsQuery', "Update extensions: querying version for {0} extensions", installedExtensionsQuery.length)); + const availableVersions = await this.extensionGalleryService.getExtensions(installedExtensionsQuery, { compatible: true }, CancellationToken.None); + + const newVersionAvailable: [IGalleryExtension, boolean, string][] = []; + for (const newVersion of availableVersions) { + for (const oldVersion of installedExtensions) { + if (areSameExtensions(oldVersion.identifier, newVersion.identifier) && gt(newVersion.version, oldVersion.manifest.version)) { + newVersionAvailable.push([newVersion, oldVersion.isPreReleaseVersion, oldVersion.manifest.version]); + } + } + } + + if (newVersionAvailable.length) { + this.logger.info(localize('updateExtensionsNewVersionsAvailable', "Update extensions: {0} extensions have new version available", newVersionAvailable.length)); + const failed: string[] = []; + await Promise.all(newVersionAvailable.map(async extensionInfo => { + try { + if (extensionInfo[1]) { + this.logger.info(localize('updateExtensionsPrereleaseVersion', "Updating extension '{0}' from prerelease version {1} to version {2}", extensionInfo[0].identifier.id, extensionInfo[2], extensionInfo[0].version)); + } + else { + this.logger.info(localize('updateExtensionsStandardVersion', "Updating extension '{0}' from version {1} to version {2}", extensionInfo[0].identifier.id, extensionInfo[2], extensionInfo[0].version)); + } + await this.extensionManagementService.installFromGallery(extensionInfo[0]); + } catch (err) { + this.logger.error(err.message || err.stack || err); + failed.push(extensionInfo[0].identifier.id); + } + })); + if (failed.length) { + throw new Error(localize('installation failed', "Failed Installing Extensions: {0}", failed.join(', '))); + } + } + else { + this.logger.info(localize('updateExtensionsNoExtensions', "No extension to update")); + } } private async installVSIX(vsix: URI, installOptions: InstallOptions, force: boolean, installedExtensions: ILocalExtension[]): Promise { From 3ff7d4545b3804e4fe4130bfeb1c5c063c85dd6b Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 4 Dec 2023 08:12:05 -0800 Subject: [PATCH 0005/1897] Update cli/src/commands/args.rs Co-authored-by: Sandeep Somavarapu --- cli/src/commands/args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/commands/args.rs b/cli/src/commands/args.rs index 6b0017a3488..ce1bf513e90 100644 --- a/cli/src/commands/args.rs +++ b/cli/src/commands/args.rs @@ -254,7 +254,7 @@ pub enum ExtensionSubcommand { Install(InstallExtensionArgs), /// Uninstall an extension. Uninstall(UninstallExtensionArgs), - /// Update the marketplace-installed extensions. + /// Update the installed extensions. Update, } From c2104486017f839e19cc9b157dff754a6c8943bb Mon Sep 17 00:00:00 2001 From: samhanic <55490861+samhanic@users.noreply.github.com> Date: Wed, 6 Dec 2023 13:54:02 +0100 Subject: [PATCH 0006/1897] CLI update extension review fixes --- resources/completions/zsh/_code | 2 +- src/vs/platform/environment/node/argv.ts | 2 +- .../common/extensionManagementCLI.ts | 48 +++++++------------ .../node/remoteExtensionHostAgentCli.ts | 2 +- 4 files changed, 19 insertions(+), 35 deletions(-) diff --git a/resources/completions/zsh/_code b/resources/completions/zsh/_code index 46a0e019680..eafa37c81c7 100644 --- a/resources/completions/zsh/_code +++ b/resources/completions/zsh/_code @@ -21,7 +21,7 @@ arguments=( '--show-versions[show versions of installed extensions, when using --list-extensions]' '--install-extension[install an extension]:id or path:_files -g "*.vsix(-.)"' '--uninstall-extension[uninstall an extension]:id or path:_files -g "*.vsix(-.)"' - '--update-extensions[update the marketplace-installed extensions]' + '--update-extensions[update the installed extensions]' '--enable-proposed-api[enables proposed API features for extensions]::extension id: ' '--verbose[print verbose output (implies --wait)]' '--log[log level to use]:level [info]:(critical error warn info debug trace off)' diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 8ffc1c4a775..691dd284960 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -100,7 +100,7 @@ export const OPTIONS: OptionDescriptions> = { 'install-extension': { type: 'string[]', cat: 'e', args: 'ext-id | path', description: localize('installExtension', "Installs or updates an extension. The argument is either an extension id or a path to a VSIX. The identifier of an extension is '${publisher}.${name}'. Use '--force' argument to update to latest version. To install a specific version provide '@${version}'. For example: 'vscode.csharp@1.2.3'.") }, 'pre-release': { type: 'boolean', cat: 'e', description: localize('install prerelease', "Installs the pre-release version of the extension, when using --install-extension") }, 'uninstall-extension': { type: 'string[]', cat: 'e', args: 'ext-id', description: localize('uninstallExtension', "Uninstalls an extension.") }, - 'update-extensions': { type: 'boolean', cat: 'e', description: localize('updateExtensions', "Update the marketplace-installed extensions.") }, + 'update-extensions': { type: 'boolean', cat: 'e', description: localize('updateExtensions', "Update the installed extensions.") }, 'enable-proposed-api': { type: 'string[]', allowEmptyValue: true, cat: 'e', args: 'ext-id', description: localize('experimentalApis', "Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.") }, 'version': { type: 'boolean', cat: 't', alias: 'v', description: localize('version', "Print version.") }, diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts index e41b3a2e3d9..6a29ab91f67 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts @@ -10,7 +10,7 @@ import { basename } from 'vs/base/common/resources'; import { gt } from 'vs/base/common/semver/semver'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { EXTENSION_IDENTIFIER_REGEX, IExtensionGalleryService, IExtensionInfo, IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { EXTENSION_IDENTIFIER_REGEX, IExtensionGalleryService, IExtensionInfo, IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOptions, InstallExtensionInfo, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, getGalleryExtensionId, getIdAndVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, EXTENSION_CATEGORIES, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { ILogger } from 'vs/platform/log/common/log'; @@ -29,7 +29,7 @@ function getId(manifest: IExtensionManifest, withVersion?: boolean): string { } type InstallVSIXInfo = { vsix: URI; installOptions: InstallOptions }; -type InstallExtensionInfo = { id: string; version?: string; installOptions: InstallOptions }; +type CLIInstallExtensionInfo = { id: string; version?: string; installOptions: InstallOptions }; export class ExtensionManagementCLI { @@ -89,7 +89,7 @@ export class ExtensionManagementCLI { } const installVSIXInfos: InstallVSIXInfo[] = []; - let installExtensionInfos: InstallExtensionInfo[] = []; + let installExtensionInfos: CLIInstallExtensionInfo[] = []; const addInstallExtensionInfo = (id: string, version: string | undefined, isBuiltin: boolean) => { installExtensionInfos.push({ id, version: version !== 'prerelease' ? version : undefined, installOptions: { ...installOptions, isBuiltin, installPreReleaseVersion: version === 'prerelease' || installOptions.installPreReleaseVersion } }); }; @@ -178,45 +178,29 @@ export class ExtensionManagementCLI { const installedExtensionsQuery: IExtensionInfo[] = []; for (const extension of installedExtensions) { if (!!extension.identifier.uuid) { // No need to check new version for an unpublished extension - installedExtensionsQuery.push({ - id: `${extension.manifest.publisher}.${extension.manifest.name}`, - preRelease: extension.isPreReleaseVersion - }); + installedExtensionsQuery.push({ ...extension.identifier, preRelease: extension.preRelease }); } } - this.logger.info(localize('updateExtensionsQuery', "Update extensions: querying version for {0} extensions", installedExtensionsQuery.length)); + this.logger.trace(localize('updateExtensionsQuery', "Fetching latest versions for {0} extensions", installedExtensionsQuery.length)); const availableVersions = await this.extensionGalleryService.getExtensions(installedExtensionsQuery, { compatible: true }, CancellationToken.None); - const newVersionAvailable: [IGalleryExtension, boolean, string][] = []; + const extensionsToUpdate: InstallExtensionInfo[] = []; for (const newVersion of availableVersions) { for (const oldVersion of installedExtensions) { if (areSameExtensions(oldVersion.identifier, newVersion.identifier) && gt(newVersion.version, oldVersion.manifest.version)) { - newVersionAvailable.push([newVersion, oldVersion.isPreReleaseVersion, oldVersion.manifest.version]); + extensionsToUpdate.push({ + extension: newVersion, + options: { operation: InstallOperation.Update, installPreReleaseVersion: oldVersion.isPreReleaseVersion } + }); } } } - if (newVersionAvailable.length) { - this.logger.info(localize('updateExtensionsNewVersionsAvailable', "Update extensions: {0} extensions have new version available", newVersionAvailable.length)); - const failed: string[] = []; - await Promise.all(newVersionAvailable.map(async extensionInfo => { - try { - if (extensionInfo[1]) { - this.logger.info(localize('updateExtensionsPrereleaseVersion', "Updating extension '{0}' from prerelease version {1} to version {2}", extensionInfo[0].identifier.id, extensionInfo[2], extensionInfo[0].version)); - } - else { - this.logger.info(localize('updateExtensionsStandardVersion', "Updating extension '{0}' from version {1} to version {2}", extensionInfo[0].identifier.id, extensionInfo[2], extensionInfo[0].version)); - } - await this.extensionManagementService.installFromGallery(extensionInfo[0]); - } catch (err) { - this.logger.error(err.message || err.stack || err); - failed.push(extensionInfo[0].identifier.id); - } - })); - if (failed.length) { - throw new Error(localize('installation failed', "Failed Installing Extensions: {0}", failed.join(', '))); - } + if (extensionsToUpdate.length) { + this.logger.info(localize('updateExtensionsNewVersionsAvailable', "Updating extensions: {0}", extensionsToUpdate.map(ext => ext.extension.identifier.id).join(', '))); + await this.extensionManagementService.installGalleryExtensions(extensionsToUpdate); + this.logger.info(localize('updateExtensionsDone', "Updated {0} extensions", extensionsToUpdate.length)); } else { this.logger.info(localize('updateExtensionsNoExtensions', "No extension to update")); @@ -248,7 +232,7 @@ export class ExtensionManagementCLI { return null; } - private async getGalleryExtensions(extensions: InstallExtensionInfo[]): Promise> { + private async getGalleryExtensions(extensions: CLIInstallExtensionInfo[]): Promise> { const galleryExtensions = new Map(); const preRelease = extensions.some(e => e.installOptions.installPreReleaseVersion); const targetPlatform = await this.extensionManagementService.getTargetPlatform(); @@ -267,7 +251,7 @@ export class ExtensionManagementCLI { return galleryExtensions; } - private async installFromGallery({ id, version, installOptions }: InstallExtensionInfo, galleryExtension: IGalleryExtension, installed: ILocalExtension[]): Promise { + private async installFromGallery({ id, version, installOptions }: CLIInstallExtensionInfo, galleryExtension: IGalleryExtension, installed: ILocalExtension[]): Promise { const manifest = await this.extensionGalleryService.getManifest(galleryExtension, CancellationToken.None); if (manifest && !this.validateExtensionKind(manifest)) { return null; diff --git a/src/vs/server/node/remoteExtensionHostAgentCli.ts b/src/vs/server/node/remoteExtensionHostAgentCli.ts index 6960a2d4424..b489ca72bcb 100644 --- a/src/vs/server/node/remoteExtensionHostAgentCli.ts +++ b/src/vs/server/node/remoteExtensionHostAgentCli.ts @@ -159,7 +159,7 @@ class CliMain extends Disposable { return extensionManagementCLI.uninstallExtensions(this.asExtensionIdOrVSIX(this.args['uninstall-extension']), !!this.args['force']); } - // Update the marketplace-installed extensions + // Update the installed extensions else if (this.args['update-extensions']) { return extensionManagementCLI.updateExtensions(); } From 9824af2c7b3833689965064039871c74224d6500 Mon Sep 17 00:00:00 2001 From: gjsjohnmurray Date: Mon, 11 Dec 2023 17:52:06 +0000 Subject: [PATCH 0007/1897] Fix `Customize Layout` bugs related to Activity Bar (fix #200571) --- src/vs/workbench/browser/actions/layoutActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 4a2a2ad5dc6..1d54794a87b 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -1360,7 +1360,7 @@ if (!isMacintosh || !isNative) { } ToggleVisibilityActions.push(...[ - CreateToggleLayoutItem(ToggleActivityBarVisibilityActionId, ContextKeyExpr.equals('config.workbench.activityBar.visible', true), localize('activityBar', "Activity Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: activityBarLeftIcon, iconB: activityBarRightIcon }), + CreateToggleLayoutItem(ToggleActivityBarVisibilityActionId, ContextKeyExpr.notEquals('config.workbench.activityBar.location', 'hidden'), localize('activityBar', "Activity Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: activityBarLeftIcon, iconB: activityBarRightIcon }), CreateToggleLayoutItem(ToggleSidebarVisibilityAction.ID, SideBarVisibleContext, localize('sideBar', "Primary Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelLeftIcon, iconB: panelRightIcon }), CreateToggleLayoutItem(ToggleAuxiliaryBarAction.ID, AuxiliaryBarVisibleContext, localize('secondarySideBar', "Secondary Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelRightIcon, iconB: panelLeftIcon }), CreateToggleLayoutItem(TogglePanelAction.ID, PanelVisibleContext, localize('panel', "Panel"), panelIcon), @@ -1548,7 +1548,7 @@ registerAction2(class CustomizeLayoutAction extends Action2 { }; // Reset all layout options - resetSetting('workbench.activityBar.visible'); + resetSetting('workbench.activityBar.location'); resetSetting('workbench.sideBar.location'); resetSetting('workbench.statusBar.visible'); resetSetting('workbench.panel.defaultLocation'); From 4ba8b88c464d886219614e1c39ddc72915ae2e12 Mon Sep 17 00:00:00 2001 From: Gayan Perera Date: Sat, 22 Oct 2022 21:12:58 +0200 Subject: [PATCH 0008/1897] Add support for breakpoint dependencies --- .../contrib/debug/browser/breakpointWidget.ts | 82 +++++++++++++++---- .../contrib/debug/browser/breakpointsView.ts | 14 +++- .../debug/browser/media/breakpointWidget.css | 18 ++++ .../workbench/contrib/debug/common/debug.ts | 14 +++- .../contrib/debug/common/debugModel.ts | 40 ++++++++- .../contrib/debug/common/debugStorage.ts | 14 +++- 6 files changed, 157 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 8cfe6b3730c..683bb7a0236 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -78,6 +78,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi private selectContainer!: HTMLElement; private inputContainer!: HTMLElement; + private selectBreakpointContainer!: HTMLElement; private input!: IActiveCodeEditor; private toDispose: lifecycle.IDisposable[]; private conditionInput = ''; @@ -86,6 +87,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi private breakpoint: IBreakpoint | undefined; private context: Context; private heightInPx: number | undefined; + private waitForBreakpointInput: IBreakpoint | undefined; constructor(editor: ICodeEditor, private lineNumber: number, private column: number | undefined, context: Context | undefined, @IContextViewService private readonly contextViewService: IContextViewService, @@ -114,6 +116,8 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.context = Context.LOG_MESSAGE; } else if (this.breakpoint && !this.breakpoint.condition && this.breakpoint.hitCondition) { this.context = Context.HIT_COUNT; + } else if (this.breakpoint && this.breakpoint.waitFor) { + this.context = Context.WAIT_FOR_BREAKPOINT; } else { this.context = Context.CONDITION; } @@ -156,16 +160,18 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi } private rememberInput(): void { - const value = this.input.getModel().getValue(); - switch (this.context) { - case Context.LOG_MESSAGE: - this.logMessageInput = value; - break; - case Context.HIT_COUNT: - this.hitCountInput = value; - break; - default: - this.conditionInput = value; + if (this.context !== Context.WAIT_FOR_BREAKPOINT) { + const value = this.input.getModel().getValue(); + switch (this.context) { + case Context.LOG_MESSAGE: + this.logMessageInput = value; + break; + case Context.HIT_COUNT: + this.hitCountInput = value; + break; + default: + this.conditionInput = value; + } } } @@ -189,17 +195,13 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi protected _fillContainer(container: HTMLElement): void { this.setCssClass('breakpoint-widget'); - const selectBox = new SelectBox([{ text: nls.localize('expression', "Expression") }, { text: nls.localize('hitCount', "Hit Count") }, { text: nls.localize('logMessage', "Log Message") }], this.context, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('breakpointType', 'Breakpoint Type') }); + const selectBox = new SelectBox([{ text: nls.localize('expression', "Expression") }, { text: nls.localize('hitCount', "Hit Count") }, { text: nls.localize('logMessage', "Log Message") }, { text: nls.localize('waitBreakpoint', "Wait for breakpoint") }], this.context, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('breakpointType', 'Breakpoint Type') }); this.selectContainer = $('.breakpoint-select-container'); selectBox.render(dom.append(container, this.selectContainer)); selectBox.onDidSelect(e => { this.rememberInput(); this.context = e.index; - this.setInputMode(); - - const value = this.getInputValue(this.breakpoint); - this.input.getModel().setValue(value); - this.input.focus(); + this.updateContextInput(); }); this.inputContainer = $('.inputContainer'); @@ -210,10 +212,49 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.fitHeightToContent(); })); this.input.setPosition({ lineNumber: 1, column: this.input.getModel().getLineMaxColumn(1) }); + + const breakpoints = this.debugService.getModel().getBreakpoints().filter(bp => bp !== this.breakpoint); + const index = breakpoints.findIndex((bp) => { return this.breakpoint?.waitFor?.matches(bp); }); + let select = 0; + if (index > -1) { + select = index + 1; + } + const items: ISelectOptionItem[] = [{ text: nls.localize('noBreakpointDependency', 'None') }]; + breakpoints.map(bp => { text: `${bp.uri.path} : ${bp.lineNumber}` }) + .forEach(i => items.push(i)); + + const selectBreakpointBox = new SelectBox(items, select, this.contextViewService, undefined, { ariaLabel: nls.localize('selectBreakpoint', 'Select breakpoint') }); + selectBreakpointBox.onDidSelect(e => { + if (e.index === 0) { + this.waitForBreakpointInput = undefined; + } else { + this.waitForBreakpointInput = breakpoints[e.index - 1]; + } + this.close(true); + }); + this.toDispose.push(attachSelectBoxStyler(selectBreakpointBox, this.themeService)); + this.selectBreakpointContainer = $('.select-breakpoint-container'); + selectBreakpointBox.render(dom.append(container, this.selectBreakpointContainer)); + + this.updateContextInput(); // Due to an electron bug we have to do the timeout, otherwise we do not get focus setTimeout(() => this.input.focus(), 150); } + private updateContextInput() { + if (this.context === Context.WAIT_FOR_BREAKPOINT) { + this.inputContainer.hidden = true; + this.selectBreakpointContainer.hidden = false; + } else { + this.inputContainer.hidden = false; + this.selectBreakpointContainer.hidden = true; + this.setInputMode(); + const value = this.getInputValue(this.breakpoint); + this.input.getModel().setValue(value); + this.input.focus(); + } + } + protected override _doLayout(heightInPixel: number, widthInPixel: number): void { this.heightInPx = heightInPixel; this.input.layout({ height: heightInPixel, width: widthInPixel - 113 }); @@ -321,6 +362,9 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi let condition = this.breakpoint && this.breakpoint.condition; let hitCondition = this.breakpoint && this.breakpoint.hitCondition; let logMessage = this.breakpoint && this.breakpoint.logMessage; + let waitFor = this.breakpoint && this.breakpoint.waitFor && + this.debugService.getModel().getBreakpoints().filter(b => { return this.breakpoint?.waitFor?.matches(b); })[0]; + this.rememberInput(); if (this.conditionInput || this.context === Context.CONDITION) { @@ -332,13 +376,17 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi if (this.logMessageInput || this.context === Context.LOG_MESSAGE) { logMessage = this.logMessageInput; } + if (this.context === Context.WAIT_FOR_BREAKPOINT) { + waitFor = this.waitForBreakpointInput; + } if (this.breakpoint) { const data = new Map(); data.set(this.breakpoint.getId(), { condition, hitCondition, - logMessage + logMessage, + waitFor }); this.debugService.updateBreakpoints(this.breakpoint.originalUri, data, false).then(undefined, onUnexpectedError); } else { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 7e7379706dc..1f656c3f065 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -48,7 +48,7 @@ import { IViewDescriptorService } from 'vs/workbench/common/views'; import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; import { DisassemblyView } from 'vs/workbench/contrib/debug/browser/disassemblyView'; import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_BREAKPOINT_ITEM_TYPE, CONTEXT_BREAKPOINT_SUPPORTS_CONDITION, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_IN_DEBUG_MODE, DebuggerString, DEBUG_SCHEME, IBaseBreakpoint, IBreakpoint, IBreakpointEditorContribution, IDataBreakpoint, IDebugModel, IDebugService, IEnablement, IExceptionBreakpoint, IFunctionBreakpoint, IInstructionBreakpoint, State } from 'vs/workbench/contrib/debug/common/debug'; -import { Breakpoint, DataBreakpoint, ExceptionBreakpoint, FunctionBreakpoint, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Breakpoint, BreakpointReference, DataBreakpoint, ExceptionBreakpoint, FunctionBreakpoint, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; @@ -1291,7 +1291,13 @@ export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: }; } - if (breakpoint.logMessage || breakpoint.condition || breakpoint.hitCondition) { + // can change this when all breakpoint supports dependent breakpoint condition + let waitForBreakpoint: BreakpointReference | undefined; + if (breakpoint instanceof Breakpoint) { + waitForBreakpoint = breakpoint.waitFor; + } + + if (breakpoint.logMessage || breakpoint.condition || breakpoint.hitCondition || waitForBreakpoint) { const messages: string[] = []; let icon = breakpoint.logMessage ? icons.logBreakpoint.regular : icons.conditionalBreakpoint.regular; if (!breakpoint.supported) { @@ -1309,6 +1315,10 @@ export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: messages.push(localize('hitCount', "Hit Count: {0}", breakpoint.hitCondition)); } + if (waitForBreakpoint) { + messages.push(localize('waitFor', "Hit after breakpoint: {0}:{1}", waitForBreakpoint.uri.toString(), waitForBreakpoint.lineNumber)); + } + return { icon, message: appendMessage(messages.join('\n')) diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css index 89915ecb95f..71f25c6641a 100644 --- a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css @@ -29,3 +29,21 @@ .monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .inputContainer { flex: 1; } + +.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container { + display: flex; + justify-content: center; + flex-direction: column; + padding: 0 10px; + flex-shrink: 0; +} + +.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container .monaco-select-box { + min-width: 100px; + min-height: 18px; + padding: 2px 20px 2px 8px; +} + +.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container:after { + right: 14px; +} diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 11658d55c94..1c9cd2a3e6b 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -539,6 +539,7 @@ export interface IBreakpointUpdateData { readonly logMessage?: string; readonly lineNumber?: number; readonly column?: number; + readonly waitFor?: IBreakpoint; } export interface IBaseBreakpoint extends IEnablement { @@ -550,6 +551,15 @@ export interface IBaseBreakpoint extends IEnablement { readonly message?: string; readonly sessionsThatVerified: string[]; getIdFromAdapter(sessionId: string): number | undefined; + isHit(sessionId: string): boolean; + hit(sessionId: string): void; +} + +export interface IBreakpointReference { + readonly lineNumber: number; + readonly column?: number; + readonly uri: uri; + matches(bp: IBreakpoint): boolean; } export interface IBreakpoint extends IBaseBreakpoint { @@ -563,6 +573,7 @@ export interface IBreakpoint extends IBaseBreakpoint { readonly endColumn?: number; readonly adapterData: any; readonly sessionAgnosticData: { lineNumber: number; column: number | undefined }; + readonly waitFor?: IBreakpointReference; } export interface IFunctionBreakpoint extends IBaseBreakpoint { @@ -1203,7 +1214,8 @@ export interface IDebugService { export const enum BreakpointWidgetContext { CONDITION = 0, HIT_COUNT = 1, - LOG_MESSAGE = 2 + LOG_MESSAGE = 2, + WAIT_FOR_BREAKPOINT = 3 } export interface IDebugEditorContribution extends editorCommon.IEditorContribution { diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 4636296b973..c4ec2d0bb8e 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -19,7 +19,7 @@ import { IRange, Range } from 'vs/editor/common/core/range'; import * as nls from 'vs/nls'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IEditorPane } from 'vs/workbench/common/editor'; -import { DEBUG_MEMORY_SCHEME, IBaseBreakpoint, IBreakpoint, IBreakpointData, IBreakpointsChangeEvent, IBreakpointUpdateData, IDataBreakpoint, IDebugModel, IDebugSession, IEnablement, IExceptionBreakpoint, IExceptionInfo, IExpression, IExpressionContainer, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryInvalidationEvent, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IScope, IStackFrame, IThread, ITreeElement, MemoryRange, MemoryRangeType, State } from 'vs/workbench/contrib/debug/common/debug'; +import { DEBUG_MEMORY_SCHEME, IBaseBreakpoint, IBreakpoint, IBreakpointData, IBreakpointReference, IBreakpointsChangeEvent, IBreakpointUpdateData, IDataBreakpoint, IDebugModel, IDebugSession, IEnablement, IExceptionBreakpoint, IExceptionInfo, IExpression, IExpressionContainer, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryInvalidationEvent, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IScope, IStackFrame, IThread, ITreeElement, MemoryRange, MemoryRangeType, State } from 'vs/workbench/contrib/debug/common/debug'; import { getUriFromSource, Source, UNKNOWN_SOURCE_LABEL } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; @@ -769,6 +769,7 @@ function toBreakpointSessionData(data: DebugProtocol.Breakpoint, capabilities: D export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoint { private sessionData = new Map(); + private sessionHitData = new Map(); protected data: IBreakpointSessionData | undefined; constructor( @@ -787,6 +788,7 @@ export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoi setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void { if (!data) { this.sessionData.delete(sessionId); + this.sessionHitData.delete(sessionId); } else { data.sessionId = sessionId; this.sessionData.set(sessionId, data); @@ -863,6 +865,18 @@ export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoi return result; } + + isHit(sessionId: string): boolean { + const data = this.sessionHitData.get(sessionId); + if (data) { + return data; + } + return false; + } + + hit(sessionId: string): void { + this.sessionHitData.set(sessionId, true); + } } export class Breakpoint extends BaseBreakpoint implements IBreakpoint { @@ -879,7 +893,8 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { private readonly textFileService: ITextFileService, private readonly uriIdentityService: IUriIdentityService, private readonly logService: ILogService, - id = generateUuid() + id = generateUuid(), + public waitFor: IBreakpointReference | undefined = undefined ) { super(enabled, hitCondition, condition, logMessage, id); } @@ -966,6 +981,14 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { result.column = this._column; result.adapterData = this.adapterData; + if (this.waitFor) { + const wf = Object.create(null); + wf.uri = this.waitFor?.uri.toString(); + wf.lineNumber = this.waitFor?.lineNumber; + wf.column = this.waitFor?.column; + result.waitFor = wf; + } + return result; } @@ -989,6 +1012,19 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { if (!isUndefinedOrNull(data.logMessage)) { this.logMessage = data.logMessage; } + if (!isUndefinedOrNull(data.waitFor)) { + this.waitFor = new BreakpointReference(data.waitFor.uri, data.waitFor.lineNumber, data.waitFor.column); + } else { + this.waitFor = undefined; + } + } +} + +export class BreakpointReference implements IBreakpointReference { + constructor(public uri: uri, public lineNumber: number, public column?: number) { } + + matches(bp: IBreakpoint): boolean { + return bp.uri.toString() === this.uri?.toString() && bp.lineNumber === this.lineNumber && bp.column === this.column; } } diff --git a/src/vs/workbench/contrib/debug/common/debugStorage.ts b/src/vs/workbench/contrib/debug/common/debugStorage.ts index efcd601c246..f5c72a26101 100644 --- a/src/vs/workbench/contrib/debug/common/debugStorage.ts +++ b/src/vs/workbench/contrib/debug/common/debugStorage.ts @@ -5,8 +5,8 @@ import { URI } from 'vs/base/common/uri'; import { StorageScope, IStorageService, StorageTarget } from 'vs/platform/storage/common/storage'; -import { ExceptionBreakpoint, Expression, Breakpoint, FunctionBreakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; -import { IEvaluate, IExpression, IDebugModel } from 'vs/workbench/contrib/debug/common/debug'; +import { ExceptionBreakpoint, Expression, Breakpoint, FunctionBreakpoint, DataBreakpoint, BreakpointReference } from 'vs/workbench/contrib/debug/common/debugModel'; +import { IEvaluate, IExpression, IDebugModel, IBreakpointReference } from 'vs/workbench/contrib/debug/common/debug'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { ILogService } from 'vs/platform/log/common/log'; @@ -66,13 +66,21 @@ export class DebugStorage extends Disposable { let result: Breakpoint[] | undefined; try { result = JSON.parse(this.storageService.get(DEBUG_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((breakpoint: any) => { - return new Breakpoint(URI.parse(breakpoint.uri.external || breakpoint.source.uri.external), breakpoint.lineNumber, breakpoint.column, breakpoint.enabled, breakpoint.condition, breakpoint.hitCondition, breakpoint.logMessage, breakpoint.adapterData, this.textFileService, this.uriIdentityService, this.logService, breakpoint.id); + return new Breakpoint(URI.parse(breakpoint.uri.external || breakpoint.source.uri.external), breakpoint.lineNumber, breakpoint.column, breakpoint.enabled, breakpoint.condition, breakpoint.hitCondition, breakpoint.logMessage, breakpoint.adapterData, this.textFileService, this.uriIdentityService, this.logService, breakpoint.id, + this.parseBreakpointRef(breakpoint)); }); } catch (e) { } return result || []; } + private parseBreakpointRef(breakpoint: any): IBreakpointReference | undefined { + if (breakpoint.waitFor) { + return new BreakpointReference(breakpoint.waitFor.uri, breakpoint.waitFor.lineNumber, breakpoint.waitFor.column); + } + return undefined; + } + private loadFunctionBreakpoints(): FunctionBreakpoint[] { let result: FunctionBreakpoint[] | undefined; try { From 8c0522560fda7400418aec1392438f5447ed7298 Mon Sep 17 00:00:00 2001 From: Gayan Perera Date: Sun, 17 Dec 2023 08:53:47 +0100 Subject: [PATCH 0009/1897] update the PR according to feedback --- .../contrib/debug/browser/breakpointWidget.ts | 4 +- .../contrib/debug/browser/debugService.ts | 2 +- .../contrib/debug/browser/debugSession.ts | 45 +++++++++++++++++++ .../workbench/contrib/debug/common/debug.ts | 2 - .../contrib/debug/common/debugModel.ts | 22 ++++----- 5 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 683bb7a0236..e9826b29948 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -223,7 +223,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi breakpoints.map(bp => { text: `${bp.uri.path} : ${bp.lineNumber}` }) .forEach(i => items.push(i)); - const selectBreakpointBox = new SelectBox(items, select, this.contextViewService, undefined, { ariaLabel: nls.localize('selectBreakpoint', 'Select breakpoint') }); + const selectBreakpointBox = new SelectBox(items, select, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('selectBreakpoint', 'Select breakpoint') }); selectBreakpointBox.onDidSelect(e => { if (e.index === 0) { this.waitForBreakpointInput = undefined; @@ -232,7 +232,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi } this.close(true); }); - this.toDispose.push(attachSelectBoxStyler(selectBreakpointBox, this.themeService)); + this.toDispose.push(selectBreakpointBox); this.selectBreakpointContainer = $('.select-breakpoint-container'); selectBreakpointBox.render(dom.append(container, this.selectBreakpointContainer)); diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index a7ff2816f8a..86272f84a84 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -1153,7 +1153,7 @@ export class DebugService implements IDebugService { } private async sendBreakpoints(modelUri: uri, sourceModified = false, session?: IDebugSession): Promise { - const breakpointsToSend = this.model.getBreakpoints({ originalUri: modelUri, enabledOnly: true }); + const breakpointsToSend = this.model.getBreakpoints({ originalUri: modelUri, enabledOnly: true, excludeDependent: true }); await sendToOneOrAllSessions(this.model, session, async s => { if (!s.configuration.noDebug) { await s.sendBreakpoints(modelUri, breakpointsToSend, sourceModified); diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 71966c239a3..4c2d0489127 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -1273,6 +1273,8 @@ export class DebugSession implements IDebugSession, IDisposable { return; } + this.enableDependentBreakpoints(thread); + focus(); await promises.wholeCallStack; @@ -1291,6 +1293,49 @@ export class DebugSession implements IDebugSession, IDisposable { ); } + private enableDependentBreakpoints(thread: Thread) { + const frame = thread.getTopStackFrame(); + if (frame === undefined) { + return; + } + + if (thread.stoppedDetails && thread.stoppedDetails.reason !== 'breakpoint') { + return; + } + + // find the current breakpoints + const breakpoints = this.model.getBreakpoints({ uri: frame?.source.uri }).filter(bp => { + if (bp.lineNumber < frame.range.startLineNumber || bp.lineNumber > frame.range.endLineNumber) { + return false; + } + + if (bp.column && (bp.column < frame.range.startColumn || bp.column > frame.range.endColumn)) { + return false; + } + return true; + }); + + // check if the current breakpoints are dependencies, and if so collect and send the dependents to DA + const uriBreakpoints = new Map(); + this.model.getBreakpoints({ dependentOnly: true }).forEach(bp => { + breakpoints.forEach(cbp => { + if (bp.waitFor?.matches(cbp)) { + const uri = bp.uri; + if (!uriBreakpoints.has(uri)) { + uriBreakpoints.set(uri, []); + } + uriBreakpoints.get(uri)?.push(bp); + } + }); + }); + + if (uriBreakpoints.size > 0) { + uriBreakpoints.forEach((bps, uri, _) => { + this.sendBreakpoints(uri, bps, false); + }); + } + } + private onDidExitAdapter(event?: AdapterEndEvent): void { this.initialized = true; this.model.setBreakpointSessionData(this.getId(), this.capabilities, undefined); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 1c9cd2a3e6b..9e2f53c862b 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -551,8 +551,6 @@ export interface IBaseBreakpoint extends IEnablement { readonly message?: string; readonly sessionsThatVerified: string[]; getIdFromAdapter(sessionId: string): number | undefined; - isHit(sessionId: string): boolean; - hit(sessionId: string): void; } export interface IBreakpointReference { diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index c4ec2d0bb8e..4e4206960ff 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -865,18 +865,6 @@ export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoi return result; } - - isHit(sessionId: string): boolean { - const data = this.sessionHitData.get(sessionId); - if (data) { - return data; - } - return false; - } - - hit(sessionId: string): void { - this.sessionHitData.set(sessionId, true); - } } export class Breakpoint extends BaseBreakpoint implements IBreakpoint { @@ -912,7 +900,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { return this.data.verified && !this.textFileService.isDirty(this._uri); } - return true; + return !this.waitFor; } get uri(): uri { @@ -1404,7 +1392,7 @@ export class DebugModel extends Disposable implements IDebugModel { return { wholeCallStack, topCallStack: wholeCallStack }; } - getBreakpoints(filter?: { uri?: uri; originalUri?: uri; lineNumber?: number; column?: number; enabledOnly?: boolean }): IBreakpoint[] { + getBreakpoints(filter?: { uri?: uri; originalUri?: uri; lineNumber?: number; column?: number; enabledOnly?: boolean; dependentOnly?: boolean; excludeDependent?: boolean }): IBreakpoint[] { if (filter) { const uriStr = filter.uri?.toString(); const originalUriStr = filter.originalUri?.toString(); @@ -1424,6 +1412,12 @@ export class DebugModel extends Disposable implements IDebugModel { if (filter.enabledOnly && (!this.breakpointsActivated || !bp.enabled)) { return false; } + if (filter.dependentOnly && bp.waitFor === undefined) { + return false; + } + if (filter.excludeDependent && bp.waitFor !== undefined) { + return false; + } return true; }); From 12751887c61fca13b79669f6c169397f03cd0bd5 Mon Sep 17 00:00:00 2001 From: Gayan Perera Date: Mon, 18 Dec 2023 18:57:00 +0100 Subject: [PATCH 0010/1897] Update src/vs/workbench/contrib/debug/browser/breakpointWidget.ts Co-authored-by: Connor Peet --- src/vs/workbench/contrib/debug/browser/breakpointWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index e9826b29948..dc0049e6485 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -363,7 +363,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi let hitCondition = this.breakpoint && this.breakpoint.hitCondition; let logMessage = this.breakpoint && this.breakpoint.logMessage; let waitFor = this.breakpoint && this.breakpoint.waitFor && - this.debugService.getModel().getBreakpoints().filter(b => { return this.breakpoint?.waitFor?.matches(b); })[0]; + this.debugService.getModel().getBreakpoints().find(b => this.breakpoint!.waitFor!.matches(b)); this.rememberInput(); From f38f64ac0fdac8e653899ffe6e7604cdeabb4352 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Tue, 19 Dec 2023 18:09:41 +0100 Subject: [PATCH 0011/1897] Added sticky scroll focus functionality to tree --- src/vs/base/browser/ui/tree/abstractTree.ts | 277 +++++++++++++++++- src/vs/base/browser/ui/tree/asyncDataTree.ts | 9 +- src/vs/base/browser/ui/tree/tree.ts | 1 + src/vs/platform/list/browser/listService.ts | 6 +- .../workbench/browser/actions/listCommands.ts | 71 ++++- 5 files changed, 354 insertions(+), 10 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 179b84fab2b..72827d37528 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDragAndDropData } from 'vs/base/browser/dnd'; -import { $, append, clearNode, createStyleSheet, getWindow, h, hasParentWithClass, isActiveElement } from 'vs/base/browser/dom'; +import { $, append, clearNode, createStyleSheet, getWindow, h, hasParentWithClass, isActiveElement, isPointerEvent, asCssValueWithDefault } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -1202,6 +1202,20 @@ class StickyScrollState extends Disposable { return equals(this.stickyNodes, state.stickyNodes, stickyScrollNodeEquals); } + lastNodePartiallyVisible(): boolean { + if (this.count === 0) { + return false; + } + + const lastStickyNode = this.stickyNodes[this.count - 1]; + if (this.count === 1) { + return lastStickyNode.position !== 0; + } + + const secondLastStickyNode = this.stickyNodes[this.count - 2]; + return secondLastStickyNode.position + secondLastStickyNode.height !== lastStickyNode.position; + } + addDisposable(disposable: IDisposable): void { this._register(disposable); } @@ -1209,6 +1223,9 @@ class StickyScrollState extends Disposable { class StickyScrollController extends Disposable { + readonly onDidChangeHasFocus: Event; + readonly onContextMenu: Event>; + private stickyScrollMaxItemCount: number; private readonly maxWidgetViewRatio = 0.4; @@ -1238,6 +1255,8 @@ class StickyScrollController extends Disposable { this.stickyScrollMaxItemCount = stickyScrollOptions.stickyScrollMaxItemCount; this._widget = this._register(new StickyScrollWidget(view.getScrollableElement(), view, model, renderers, treeDelegate)); + this.onDidChangeHasFocus = this._widget.onDidChangeHasFocus; + this.onContextMenu = this._widget.onContextMenu; this._register(view.onDidScroll(() => this.update())); this._register(view.onDidChangeContentHeight(() => this.update())); @@ -1436,6 +1455,10 @@ class StickyScrollController extends Disposable { return widgetHeight; } + getFocus(): T | undefined { + return this._widget.getFocus(); + } + updateOptions(optionsUpdate: IAbstractTreeOptionsUpdate = {}): void { const validatedOptions = this.validateStickySettings(optionsUpdate); if (this.stickyScrollMaxItemCount !== validatedOptions.stickyScrollMaxItemCount) { @@ -1458,6 +1481,10 @@ class StickyScrollWidget implements IDisposable { private readonly _rootDomNode: HTMLElement; private _previousState: StickyScrollState | undefined; + private stickyScrollFocus: StickyScrollFocus; + readonly onDidChangeHasFocus: Event; + readonly onContextMenu: Event>; + constructor( container: HTMLElement, private readonly view: List>, @@ -1469,6 +1496,10 @@ class StickyScrollWidget implements IDisposable { this._rootDomNode = document.createElement('div'); this._rootDomNode.classList.add('monaco-tree-sticky-container'); container.appendChild(this._rootDomNode); + + this.stickyScrollFocus = new StickyScrollFocus(this._rootDomNode, view); + this.onDidChangeHasFocus = this.stickyScrollFocus.onDidChangeHasFocus; + this.onContextMenu = this.stickyScrollFocus.onContextMenu; } get height(): number { @@ -1510,17 +1541,19 @@ class StickyScrollWidget implements IDisposable { return; } + const elements = Array(state.count); for (let stickyIndex = state.count - 1; stickyIndex >= 0; stickyIndex--) { const stickyNode = state.stickyNodes[stickyIndex]; - const previousStickyNode = stickyIndex ? state.stickyNodes[stickyIndex - 1] : undefined; - const currentWidgetHieght = previousStickyNode ? previousStickyNode.position + previousStickyNode.height : 0; - const { element, disposable } = this.createElement(stickyNode, currentWidgetHieght); + const { element, disposable } = this.createElement(stickyNode); + elements[stickyIndex] = element; this._rootDomNode.appendChild(element); state.addDisposable(disposable); } + this.stickyScrollFocus.updateElements(elements, state); + // Add shadow element to the end of the widget const shadow = $('.monaco-tree-sticky-container-shadow'); this._rootDomNode.appendChild(shadow); @@ -1531,7 +1564,7 @@ class StickyScrollWidget implements IDisposable { this._rootDomNode.style.height = `${lastStickyNode.position + lastStickyNode.height}px`; } - private createElement(stickyNode: StickyScrollNode, currentWidgetHeight: number): { element: HTMLElement; disposable: IDisposable } { + private createElement(stickyNode: StickyScrollNode): { element: HTMLElement; disposable: IDisposable } { const nodeLocation = this.model.getNodeLocation(stickyNode.node); const nodeIndex = this.model.getListIndex(nodeLocation); @@ -1574,14 +1607,208 @@ class StickyScrollWidget implements IDisposable { private setVisible(visible: boolean): void { this._rootDomNode.style.display = visible ? 'block' : 'none'; + + if (!visible) { + this.stickyScrollFocus.updateElements([], undefined); + } + } + + getFocus(): T | undefined { + return this.stickyScrollFocus.getFocus(); } dispose(): void { + this.stickyScrollFocus.dispose(); this._previousState?.dispose(); this._rootDomNode.remove(); } } +class StickyScrollFocus extends Disposable { + + private elements: HTMLElement[] = []; + private state: StickyScrollState | undefined; + + private _onDidChangeState = new Emitter(); + readonly onDidChangeHasFocus = this._onDidChangeState.event; + + private _domHasFocus: boolean = false; + private get domHasFocus(): boolean { return this._domHasFocus; } + private set domHasFocus(hasFocus: boolean) { + this._domHasFocus = hasFocus; + this._onDidChangeState.fire(hasFocus); + } + + private focusedIndex: number = -1; + + private _onContextMenu = new Emitter>(); + readonly onContextMenu: Event> = this._onContextMenu.event; + + constructor( + private readonly container: HTMLElement, + private readonly view: List> + ) { + super(); + + this.container.addEventListener('focus', () => this.onFocus()); + this.container.addEventListener('blur', () => this.onBlur()); + + this._register(this.view.onKeyDown((e) => this.onKeyDown(e))); + this._register(this.view.onContextMenu((e) => this.handleContextMenu(e))); + } + + private handleContextMenu(e: IListContextMenuEvent>): void { + // We only handle context menu triggers by a keyboardevent here + // Context menu triggers by mouse are handled by the list itself + + const target = e.browserEvent.target as HTMLElement; + const isStickyScroll = target.classList.contains('monaco-tree-sticky-container') && !isPointerEvent(e); + + if (!this.domHasFocus && !isStickyScroll) { + return; + } + if (!this.state || this.focusedIndex < 0) { + throw new Error('Context menu key should not be triggered when focus is not in sticky scroll widget'); + } + + const stickyNode = this.state.stickyNodes[this.focusedIndex]; + const element = stickyNode.node.element; + const anchor = this.elements[this.focusedIndex]; + this._onContextMenu.fire({ element, anchor, browserEvent: e.browserEvent, isStickyScroll: true }); + } + + private onKeyDown(e: KeyboardEvent): void { + // Sticky Scroll Navigation + if (this.domHasFocus && this.state) { + // Move up + if (e.key === 'ArrowUp') { + this.setElementFocused(Math.max(0, this.focusedIndex - 1)); + e.preventDefault(); + e.stopPropagation(); + } + // Move down, if last sticky node is focused, move focus into first child of last sticky node + else if (e.key === 'ArrowDown' || e.key === 'ArrowRight') { + if (this.focusedIndex >= this.state.count - 1) { + const nodeIndexToFocus = this.state.stickyNodes[this.state.count - 1].startIndex + 1; + this.view.domFocus(); + this.view.setFocus([nodeIndexToFocus]); + this.scrollNodeUnderWidget(nodeIndexToFocus, this.state); + } else { + this.setElementFocused(this.focusedIndex + 1); + } + e.preventDefault(); + e.stopPropagation(); + } + } + } + + updateElements(elements: HTMLElement[], state: StickyScrollState | undefined): void { + const previousFocusedIndex = this.focusedIndex; + this.removeFocus(); + + this.elements = elements; + this.state = state; + + this.container.tabIndex = state ? 0 : -1; + + if (this.domHasFocus && state) { + const newFocusedIndex = clamp(previousFocusedIndex, 0, state.count - 1); + this.setFocus(newFocusedIndex); + } else { + this.removeFocus(); + this.view.domFocus(); + } + } + + setElementFocused(stickyIndex: number): void { + const state = this.state; + if (!state) { + throw new Error('Cannot set focus when state is undefined'); + } + + this.setFocus(stickyIndex); + + if (stickyIndex < state.count - 1) { + return; + } + + // If the last sticky node is not fully visible, scroll it into view + if (state.lastNodePartiallyVisible()) { + const lastStickyNode = state.stickyNodes[stickyIndex]; + this.scrollNodeUnderWidget(lastStickyNode.endIndex + 1, state); + } + } + + private scrollNodeUnderWidget(nodeIndex: number, state: StickyScrollState) { + const lastStickyNode = state.stickyNodes[state.count - 1]; + const secondLastStickyNode = state.count > 1 ? state.stickyNodes[state.count - 2] : undefined; + + const elementScrollTop = this.view.getElementTop(nodeIndex); + const elementTargetViewTop = secondLastStickyNode ? secondLastStickyNode.position + secondLastStickyNode.height + lastStickyNode.height : lastStickyNode.height; + this.view.scrollTop = elementScrollTop - elementTargetViewTop; + } + + getFocus(): T | undefined { + if (!this.state || this.focusedIndex === -1) { + return undefined; + } + return this.state.stickyNodes[this.focusedIndex].node.element; + } + + private removeFocus(): void { + if (this.focusedIndex === -1) { + return; + } + this.toggleElementFocus(this.elements[this.focusedIndex], false); + this.focusedIndex = -1; + } + + private setFocus(newFocusIndex: number): void { + if (0 > newFocusIndex) { + throw new Error('addFocus() can not remove focus'); + } + if (!this.state && newFocusIndex >= 0) { + throw new Error('Cannot set focus index when state is undefined'); + } + if (this.state && newFocusIndex >= this.state.count) { + throw new Error('Cannot set focus index to an index that does not exist'); + } + + const oldIndex = this.focusedIndex; + if (oldIndex >= 0) { + this.toggleElementFocus(this.elements[oldIndex], false); + } + if (newFocusIndex >= 0) { + this.toggleElementFocus(this.elements[newFocusIndex], true); + } + this.focusedIndex = newFocusIndex; + } + + private toggleElementFocus(element: HTMLElement, focused: boolean): void { + element.classList.toggle('focused', focused); + } + + private onFocus(): void { + if (!this.state || this.elements.length === 0) { + throw new Error('Cannot focus when state is undefined or elements are empty'); + } + + this.domHasFocus = true; + if (this.focusedIndex === -1) { + this.setFocus(0); + } + } + + private onBlur(): void { + this.domHasFocus = false; + } + + override dispose(): void { + this._onDidChangeState.fire(false); + super.dispose(); + } +} + function asTreeMouseEvent(event: IListMouseEvent>): ITreeMouseEvent { let target: TreeMouseEventTarget = TreeMouseEventTarget.Unknown; @@ -1601,10 +1828,14 @@ function asTreeMouseEvent(event: IListMouseEvent>): ITreeMo } function asTreeContextMenuEvent(event: IListContextMenuEvent>): ITreeContextMenuEvent { + const target = event.browserEvent.target as HTMLElement; + const isStickyScroll = target.classList.contains('monaco-tree-sticky-container'); + return { element: event.element ? event.element.element : null, browserEvent: event.browserEvent, - anchor: event.anchor + anchor: event.anchor, + isStickyScroll }; } @@ -1850,6 +2081,7 @@ class TreeNodeListMouseController extends MouseController< const elementScrollTop = this.list.getElementTop(nodeIndex); const elementTargetViewTop = stickyScrollController.nodePositionTopBelowWidget(node); this.tree.scrollTop = elementScrollTop - elementTargetViewTop; + this.list.domFocus(); this.list.setFocus([nodeIndex]); this.list.setSelection([nodeIndex]); } @@ -1976,6 +2208,8 @@ export abstract class AbstractTree implements IDisposable private eventBufferer = new EventBufferer(); private findController?: FindController; readonly onDidChangeFindOpenState: Event = Event.None; + onDidChangeStickyScrollFocused: Event = Event.None; + onStickyScrollContextMenu: Event> = Event.None; private focusNavigationFilter: ((node: ITreeNode) => boolean) | undefined; private stickyScrollController?: StickyScrollController; private styleElement: HTMLStyleElement; @@ -1988,7 +2222,7 @@ export abstract class AbstractTree implements IDisposable get onMouseClick(): Event> { return Event.map(this.view.onMouseClick, asTreeMouseEvent); } get onMouseDblClick(): Event> { return Event.filter(Event.map(this.view.onMouseDblClick, asTreeMouseEvent), e => e.target !== TreeMouseEventTarget.Filter); } - get onContextMenu(): Event> { return Event.map(this.view.onContextMenu, asTreeContextMenuEvent); } + get onContextMenu(): Event> { return Event.any(Event.filter(Event.map(this.view.onContextMenu, asTreeContextMenuEvent), e => !e.isStickyScroll), this.onStickyScrollContextMenu); } get onTap(): Event> { return Event.map(this.view.onTap, asTreeMouseEvent); } get onPointer(): Event> { return Event.map(this.view.onPointer, asTreeMouseEvent); } @@ -2117,6 +2351,8 @@ export abstract class AbstractTree implements IDisposable if (_options.enableStickyScroll) { this.stickyScrollController = new StickyScrollController(this, this.model, this.view, this.renderers, this.treeDelegate, _options); + this.onDidChangeStickyScrollFocused = this.stickyScrollController.onDidChangeHasFocus; + this.onStickyScrollContextMenu = this.stickyScrollController.onContextMenu; } this.styleElement = createStyleSheet(this.view.getHTMLElement()); @@ -2146,7 +2382,11 @@ export abstract class AbstractTree implements IDisposable private updateStickyScroll(optionsUpdate: IAbstractTreeOptionsUpdate) { if (!this.stickyScrollController && this._options.enableStickyScroll) { this.stickyScrollController = new StickyScrollController(this, this.model, this.view, this.renderers, this.treeDelegate, this._options); + this.onDidChangeStickyScrollFocused = this.stickyScrollController.onDidChangeHasFocus; + this.onStickyScrollContextMenu = this.stickyScrollController.onContextMenu; } else if (this.stickyScrollController && !this._options.enableStickyScroll) { + this.onDidChangeStickyScrollFocused = Event.None; + this.onStickyScrollContextMenu = Event.None; this.stickyScrollController.dispose(); this.stickyScrollController = undefined; } @@ -2267,11 +2507,30 @@ export abstract class AbstractTree implements IDisposable content.push(`.monaco-list${suffix} .monaco-tl-indent > .indent-guide.active { border-color: ${styles.treeIndentGuidesStroke}; }`); } + // Sticky Scroll Background if (styles.listBackground) { content.push(`.monaco-list${suffix} .monaco-scrollable-element .monaco-tree-sticky-container { background-color: ${styles.listBackground}; }`); content.push(`.monaco-list${suffix} .monaco-scrollable-element .monaco-tree-sticky-container .monaco-tree-sticky-row { background-color: ${styles.listBackground}; }`); } + // Sticky Scroll Focus + if (styles.listFocusForeground) { + content.push(`.monaco-list${suffix} .monaco-scrollable-element .monaco-tree-sticky-container:focus .monaco-list-row.focused { color: ${styles.listFocusForeground}; }`); + content.push(`.monaco-list${suffix}:focus .monaco-scrollable-element .monaco-tree-sticky-container .monaco-list-row.focused { color: inherit; }`); + } + + // Sticky Scroll Focus Outlines + const focusAndSelectionOutline = asCssValueWithDefault(styles.listFocusAndSelectionOutline, asCssValueWithDefault(styles.listSelectionOutline, styles.listFocusOutline ?? '')); + if (focusAndSelectionOutline) { // default: listFocusOutline + content.push(`.monaco-list${suffix} .monaco-scrollable-element .monaco-tree-sticky-container:focus .monaco-list-row.focused.selected { outline: 1px solid ${focusAndSelectionOutline}; outline-offset: -1px;}`); + content.push(`.monaco-list${suffix}:focus .monaco-scrollable-element .monaco-tree-sticky-container .monaco-list-row.focused.selected { outline: inherit;}`); + } + + if (styles.listFocusOutline) { // default: set + content.push(`.monaco-list${suffix} .monaco-scrollable-element .monaco-tree-sticky-container:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`); + content.push(`.monaco-list${suffix}:focus .monaco-scrollable-element .monaco-tree-sticky-container .monaco-list-row.focused { outline: inherit; }`); + } + this.styleElement.textContent = content.join('\n'); this.view.style(styles); @@ -2419,6 +2678,10 @@ export abstract class AbstractTree implements IDisposable return this.focus.get(); } + getStickyScrollFocus(): T | undefined { + return this.stickyScrollController?.getFocus(); + } + reveal(location: TRef, relativeTop?: number): void { this.model.expandTo(location); diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index dbd603b052d..f6f41a85a97 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -152,7 +152,8 @@ function asTreeContextMenuEvent(e: ITreeContextMenuEvent implements IDisposable get onDidUpdateOptions(): Event { return this.tree.onDidUpdateOptions; } get onDidChangeFindOpenState(): Event { return this.tree.onDidChangeFindOpenState; } + get onDidChangeStickyScrollFocused(): Event { return this.tree.onDidChangeStickyScrollFocused; } get findMode(): TreeFindMode { return this.tree.findMode; } set findMode(mode: TreeFindMode) { this.tree.findMode = mode; } @@ -751,6 +753,11 @@ export class AsyncDataTree implements IDisposable return nodes.map(n => n!.element as T); } + getStickyScrollFocus(): T | undefined { + const node = this.tree.getStickyScrollFocus(); + return node?.element as T; + } + reveal(element: T, relativeTop?: number): void { this.tree.reveal(this.getDataNode(element), relativeTop); } diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index e929dc7de09..d5a5040f5c9 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -178,6 +178,7 @@ export interface ITreeContextMenuEvent { readonly browserEvent: UIEvent; readonly element: T | null; readonly anchor: HTMLElement | IMouseEvent; + readonly isStickyScroll: boolean; } export interface ITreeNavigator { diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 20da83bf254..b87a1604e7b 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -123,8 +123,9 @@ export const WorkbenchListScrollAtBottomContextKey = ContextKeyExpr.or( RawWorkbenchListScrollAtBoundaryContextKey.isEqualTo('both')); export const RawWorkbenchListFocusContextKey = new RawContextKey('listFocus', true); +export const WorkbenchTreeStickyScrollFocused = new RawContextKey('treestickyScrollFocused', false); export const WorkbenchListSupportsMultiSelectContextKey = new RawContextKey('listSupportsMultiselect', true); -export const WorkbenchListFocusContextKey = ContextKeyExpr.and(RawWorkbenchListFocusContextKey, ContextKeyExpr.not(InputFocusedContextKey)); +export const WorkbenchListFocusContextKey = ContextKeyExpr.and(RawWorkbenchListFocusContextKey, ContextKeyExpr.not(InputFocusedContextKey), WorkbenchTreeStickyScrollFocused.negate()); export const WorkbenchListHasSelectionOrFocus = new RawContextKey('listHasSelectionOrFocus', false); export const WorkbenchListDoubleSelection = new RawContextKey('listDoubleSelection', false); export const WorkbenchListMultiSelection = new RawContextKey('listMultiSelection', false); @@ -1193,6 +1194,7 @@ class WorkbenchTreeInternals { private treeElementCanExpand: IContextKey; private treeElementHasChild: IContextKey; private treeFindOpen: IContextKey; + private treeStickyScrollFocused: IContextKey; private _useAltAsMultipleSelectionModifier: boolean; private disposables: IDisposable[] = []; @@ -1232,6 +1234,7 @@ class WorkbenchTreeInternals { this.treeElementHasChild = WorkbenchTreeElementHasChild.bindTo(this.contextKeyService); this.treeFindOpen = WorkbenchTreeFindOpen.bindTo(this.contextKeyService); + this.treeStickyScrollFocused = WorkbenchTreeStickyScrollFocused.bindTo(this.contextKeyService); this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); @@ -1278,6 +1281,7 @@ class WorkbenchTreeInternals { tree.onDidChangeCollapseState(updateCollapseContextKeys), tree.onDidChangeModel(updateCollapseContextKeys), tree.onDidChangeFindOpenState(enabled => this.treeFindOpen.set(enabled)), + tree.onDidChangeStickyScrollFocused(focused => this.treeStickyScrollFocused.set(focused)), configurationService.onDidChangeConfiguration(e => { let newOptions: IAbstractTreeOptionsUpdate = {}; if (e.affectsConfiguration(multiSelectModifierSettingKey)) { diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index 0585cdfaec4..7f119dab2e2 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -7,7 +7,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { List } from 'vs/base/browser/ui/list/listWidget'; -import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey, ListWidget, WorkbenchListHasSelectionOrFocus, getSelectionKeyboardEvent, WorkbenchListWidget, WorkbenchListSelectionNavigation, WorkbenchTreeElementCanCollapse, WorkbenchTreeElementHasParent, WorkbenchTreeElementHasChild, WorkbenchTreeElementCanExpand, RawWorkbenchListFocusContextKey, WorkbenchTreeFindOpen, WorkbenchListSupportsFind, WorkbenchListScrollAtBottomContextKey, WorkbenchListScrollAtTopContextKey } from 'vs/platform/list/browser/listService'; +import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey, ListWidget, WorkbenchListHasSelectionOrFocus, getSelectionKeyboardEvent, WorkbenchListWidget, WorkbenchListSelectionNavigation, WorkbenchTreeElementCanCollapse, WorkbenchTreeElementHasParent, WorkbenchTreeElementHasChild, WorkbenchTreeElementCanExpand, RawWorkbenchListFocusContextKey, WorkbenchTreeFindOpen, WorkbenchListSupportsFind, WorkbenchListScrollAtBottomContextKey, WorkbenchListScrollAtTopContextKey, WorkbenchTreeStickyScrollFocused } from 'vs/platform/list/browser/listService'; import { PagedList } from 'vs/base/browser/ui/list/listPaging'; import { equals, range } from 'vs/base/common/arrays'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -192,6 +192,19 @@ function expandMultiSelection(focused: WorkbenchListWidget, previousFocus: unkno } } +function revealFocusedStickyScroll(tree: ObjectTree | DataTree | AsyncDataTree, postRevealAction?: (focus: any) => void): void { + const focus = tree.getStickyScrollFocus(); + + if (!focus) { + throw new Error(`StickyScroll has no focus`); + } + + tree.reveal(focus); + tree.domFocus(); + tree.setFocus([focus]); + postRevealAction?.(focus); +} + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'list.expandSelectionDown', weight: KeybindingWeight.WorkbenchContrib, @@ -290,6 +303,26 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'list.stickyScroll.collapse', + weight: KeybindingWeight.WorkbenchContrib, + when: WorkbenchTreeStickyScrollFocused, + primary: KeyCode.LeftArrow, + mac: { + primary: KeyCode.LeftArrow, + secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow] + }, + handler: (accessor) => { + const widget = accessor.get(IListService).lastFocusedList; + + if (!widget || !(widget instanceof ObjectTree || widget instanceof DataTree || widget instanceof AsyncDataTree)) { + return; + } + + revealFocusedStickyScroll(widget, focus => widget.collapse(focus)); + } +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'list.collapseAll', weight: KeybindingWeight.WorkbenchContrib, @@ -469,6 +502,26 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'list.stickyScrollselect', + weight: KeybindingWeight.WorkbenchContrib, + when: WorkbenchTreeStickyScrollFocused, + primary: KeyCode.Enter, + mac: { + primary: KeyCode.Enter, + secondary: [KeyMod.CtrlCmd | KeyCode.DownArrow] + }, + handler: (accessor) => { + const widget = accessor.get(IListService).lastFocusedList; + + if (!widget || !(widget instanceof ObjectTree || widget instanceof DataTree || widget instanceof AsyncDataTree)) { + return; + } + + revealFocusedStickyScroll(widget, focus => widget.setSelection([focus])); + } +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'list.selectAndPreserveFocus', weight: KeybindingWeight.WorkbenchContrib, @@ -598,6 +651,22 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'list.toggleExpand', + weight: KeybindingWeight.WorkbenchContrib, + when: WorkbenchTreeStickyScrollFocused, + primary: KeyCode.Space, + handler: (accessor) => { + const widget = accessor.get(IListService).lastFocusedList; + + if (!widget || !(widget instanceof ObjectTree || widget instanceof DataTree || widget instanceof AsyncDataTree)) { + return; + } + + revealFocusedStickyScroll(widget, focus => widget.setSelection([focus])); + } +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'list.clear', weight: KeybindingWeight.WorkbenchContrib, From 37070e90c94ac2436c03979a185dbd757301efc0 Mon Sep 17 00:00:00 2001 From: Gayan Perera Date: Mon, 18 Dec 2023 18:58:26 +0100 Subject: [PATCH 0012/1897] change breakpoint lookup at current frame and exclude disable dependent breakpoints --- .../contrib/debug/browser/debugSession.ts | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 4c2d0489127..c6cdf36a14a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -1269,12 +1269,13 @@ export class DebugSession implements IDebugSession, IDisposable { }; await promises.topCallStack; + + this.enableDependentBreakpoints(thread); + if (token.isCancellationRequested) { return; } - this.enableDependentBreakpoints(thread); - focus(); await promises.wholeCallStack; @@ -1304,20 +1305,12 @@ export class DebugSession implements IDebugSession, IDisposable { } // find the current breakpoints - const breakpoints = this.model.getBreakpoints({ uri: frame?.source.uri }).filter(bp => { - if (bp.lineNumber < frame.range.startLineNumber || bp.lineNumber > frame.range.endLineNumber) { - return false; - } - - if (bp.column && (bp.column < frame.range.startColumn || bp.column > frame.range.endColumn)) { - return false; - } - return true; - }); + const hitBreakpointIds = thread.stoppedDetails?.hitBreakpointIds?.map(id => id.toString()); + const breakpoints = this.getBreakpoints(hitBreakpointIds) || this.getBreakpointsAtPosition(frame.source.uri, frame.range.startLineNumber, frame.range.endLineNumber, frame.range.startColumn, frame.range.endColumn); // check if the current breakpoints are dependencies, and if so collect and send the dependents to DA const uriBreakpoints = new Map(); - this.model.getBreakpoints({ dependentOnly: true }).forEach(bp => { + this.model.getBreakpoints({ dependentOnly: true, enabledOnly: true }).forEach(bp => { breakpoints.forEach(cbp => { if (bp.waitFor?.matches(cbp)) { const uri = bp.uri; @@ -1336,6 +1329,26 @@ export class DebugSession implements IDebugSession, IDisposable { } } + private getBreakpoints(ids?: string[]): IBreakpoint[] | undefined { + if (ids) { + return this.model.getBreakpoints().filter(bp => ids.includes(bp.getId())); + } + return undefined; + } + + private getBreakpointsAtPosition(uri: URI, startLineNumber: number, endLineNumber: number, startColumn: number, endColumn: number): IBreakpoint[] { + return this.model.getBreakpoints({ uri: uri }).filter(bp => { + if (bp.lineNumber < startLineNumber || bp.lineNumber > endLineNumber) { + return false; + } + + if (bp.column && (bp.column < startColumn || bp.column > endColumn)) { + return false; + } + return true; + }); + } + private onDidExitAdapter(event?: AdapterEndEvent): void { this.initialized = true; this.model.setBreakpointSessionData(this.getId(), this.capabilities, undefined); From b45752d859fef768177155955bd5274aeb1d4845 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Wed, 20 Dec 2023 12:17:45 +0100 Subject: [PATCH 0013/1897] Add accessibility labels #199376 --- src/vs/base/browser/ui/tree/abstractTree.ts | 41 ++++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index ed6e082ad9a..711f0777d45 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -13,7 +13,7 @@ import { FindInput } from 'vs/base/browser/ui/findinput/findInput'; import { IInputBoxStyles, IMessage, MessageType, unthemedInboxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; -import { IListOptions, IListStyles, isActionItem, isButton, isInputElement, isMonacoCustomToggle, isMonacoEditor, isStickyScrollElement, List, MouseController, TypeNavigationMode } from 'vs/base/browser/ui/list/listWidget'; +import { IListAccessibilityProvider, IListOptions, IListStyles, isActionItem, isButton, isInputElement, isMonacoCustomToggle, isMonacoEditor, isStickyScrollElement, List, MouseController, TypeNavigationMode } from 'vs/base/browser/ui/list/listWidget'; import { IToggleStyles, Toggle, unthemedToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel'; import { ICollapseStateChangeEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeEvent, ITreeFilter, ITreeModel, ITreeModelSpliceEvent, ITreeMouseEvent, ITreeNavigator, ITreeNode, ITreeRenderer, TreeDragOverBubble, TreeError, TreeFilterResult, TreeMouseEventTarget, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; @@ -1254,7 +1254,7 @@ class StickyScrollController extends Disposable { const stickyScrollOptions = this.validateStickySettings(options); this.stickyScrollMaxItemCount = stickyScrollOptions.stickyScrollMaxItemCount; - this._widget = this._register(new StickyScrollWidget(view.getScrollableElement(), view, model, renderers, treeDelegate)); + this._widget = this._register(new StickyScrollWidget(view.getScrollableElement(), view, model, renderers, treeDelegate, options.accessibilityProvider)); this.onDidChangeHasFocus = this._widget.onDidChangeHasFocus; this.onContextMenu = this._widget.onContextMenu; @@ -1490,7 +1490,8 @@ class StickyScrollWidget implements IDisposable { private readonly view: List>, private readonly model: ITreeModel, private readonly treeRenderers: TreeRenderer[], - private readonly treeDelegate: IListVirtualDelegate> + private readonly treeDelegate: IListVirtualDelegate>, + private readonly accessibilityProvider: IListAccessibilityProvider | undefined, ) { this._rootDomNode = document.createElement('div'); @@ -1545,7 +1546,7 @@ class StickyScrollWidget implements IDisposable { for (let stickyIndex = state.count - 1; stickyIndex >= 0; stickyIndex--) { const stickyNode = state.stickyNodes[stickyIndex]; - const { element, disposable } = this.createElement(stickyNode); + const { element, disposable } = this.createElement(stickyNode, stickyIndex, state.count); elements[stickyIndex] = element; this._rootDomNode.appendChild(element); @@ -1564,7 +1565,7 @@ class StickyScrollWidget implements IDisposable { this._rootDomNode.style.height = `${lastStickyNode.position + lastStickyNode.height}px`; } - private createElement(stickyNode: StickyScrollNode): { element: HTMLElement; disposable: IDisposable } { + private createElement(stickyNode: StickyScrollNode, stickyIndex: number, stickyNodesTotal: number): { element: HTMLElement; disposable: IDisposable } { const nodeLocation = this.model.getNodeLocation(stickyNode.node); const nodeIndex = this.model.getListIndex(nodeLocation); @@ -1581,6 +1582,7 @@ class StickyScrollWidget implements IDisposable { stickyElement.setAttribute('data-index', `${nodeIndex}`); stickyElement.setAttribute('data-parity', nodeIndex % 2 === 0 ? 'even' : 'odd'); stickyElement.setAttribute('id', this.view.getElementID(nodeIndex)); + this.setAccessibilityAttributes(stickyElement, stickyNode.node.element, stickyIndex, stickyNodesTotal); // Get the renderer for the node const nodeTemplateId = this.treeDelegate.getTemplateId(stickyNode.node); @@ -1605,6 +1607,35 @@ class StickyScrollWidget implements IDisposable { return { element: stickyElement, disposable }; } + private setAccessibilityAttributes(container: HTMLElement, element: T, stickyIndex: number, stickyNodesTotal: number): void { + if (!this.accessibilityProvider) { + return; + } + + if (this.accessibilityProvider.getSetSize) { + container.setAttribute('aria-setsize', String(this.accessibilityProvider.getSetSize(element, stickyIndex, stickyNodesTotal))); + } + if (this.accessibilityProvider.getPosInSet) { + container.setAttribute('aria-posinset', String(this.accessibilityProvider.getPosInSet(element, stickyIndex))); + } + if (this.accessibilityProvider.getRole) { + container.setAttribute('role', this.accessibilityProvider.getRole(element) ?? 'treeitem'); + } + + const ariaLabel = this.accessibilityProvider.getAriaLabel(element); + if (ariaLabel) { + container.setAttribute('aria-label', ariaLabel); + } + + const ariaLevel = this.accessibilityProvider.getAriaLevel && this.accessibilityProvider.getAriaLevel(element); + if (typeof ariaLevel === 'number') { + container.setAttribute('aria-level', `${ariaLevel}`); + } + + // Sticky Scroll elements can not be selected + container.setAttribute('aria-selected', String(false)); + } + private setVisible(visible: boolean): void { this._rootDomNode.style.display = visible ? 'block' : 'none'; From 31541c6b187386ac281eaaff43024a623931f357 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Tue, 19 Dec 2023 16:55:52 +0500 Subject: [PATCH 0014/1897] inline chat: display detect intent for the duration of the request & allow re-running the request without intent detection --- .../workbench/api/common/extHostInlineChat.ts | 1 + .../contrib/inlineChat/browser/inlineChat.css | 20 +++++++++++ .../browser/inlineChatController.ts | 30 ++++++++++++++-- .../inlineChat/browser/inlineChatWidget.ts | 35 ++++++++++++++++--- .../contrib/inlineChat/common/inlineChat.ts | 1 + .../view/cellParts/chat/cellChatController.ts | 3 +- .../vscode.proposed.interactive.d.ts | 1 + 7 files changed, 83 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/common/extHostInlineChat.ts b/src/vs/workbench/api/common/extHostInlineChat.ts index 360b6444332..7baad711305 100644 --- a/src/vs/workbench/api/common/extHostInlineChat.ts +++ b/src/vs/workbench/api/common/extHostInlineChat.ts @@ -151,6 +151,7 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { wholeRange: typeConvert.Range.to(request.wholeRange), attempt: request.attempt, live: request.live, + withIntentDetection: request.withIntentDetection, }; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css index 7aaf6275dc7..9f50942dafc 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css @@ -154,6 +154,26 @@ color: var(--vscode-chat-slashCommandForeground); } +.monaco-editor .inline-chat .detectedIntent { + color: var(--vscode-descriptionForeground); + padding: 5px 0px 5px 5px; +} + +.monaco-editor .inline-chat .detectedIntent.hidden { + display: none; +} + +.monaco-editor .inline-chat .detectedIntent .slash-command-pill CODE { + border-radius: 3px; + padding: 0 1px; + background-color: var(--vscode-chat-slashCommandBackground); + color: var(--vscode-chat-slashCommandForeground); +} + +.monaco-editor .inline-chat .detectedIntent .slash-command-pill a { + color: var(--vscode-textLink-foreground); + cursor: pointer; +} /* .monaco-editor .inline-chat .markdownMessage .message * { margin: unset; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 0916d9b82a9..e5960753a60 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -75,6 +75,7 @@ export abstract class InlineChatRunOptions { existingSession?: Session; isUnstashed?: boolean; position?: IPosition; + withIntentDetection?: boolean; static isInteractiveEditorOptions(options: any): options is InlineChatRunOptions { const { initialSelection, initialRange, message, autoSend, position } = options; @@ -375,6 +376,25 @@ export class InlineChatController implements IEditorContribution { this._sessionStore.clear(); + this._sessionStore.add(this._zone.value.widget.onRequestWithoutIntentDetection(async () => { + options.withIntentDetection = false; + + // undo changes + if (this._activeSession && this._activeSession.lastExchange && this._strategy) { + const { lastExchange } = this._activeSession; + if (lastExchange.response instanceof ReplyResponse) { + try { + this._ignoreModelContentChanged = true; + await this._strategy.undoChanges(lastExchange.response.modelAltVersionId); + } finally { + this._ignoreModelContentChanged = false; + } + } + } + + this.acceptInput(); + })); + const wholeRangeDecoration = this._editor.createDecorationsCollection(); const updateWholeRangeDecoration = () => { const newDecorations = this._strategy?.getWholeRangeDecoration() ?? []; @@ -563,7 +583,7 @@ export class InlineChatController implements IEditorContribution { return State.MAKE_REQUEST; } - private async [State.MAKE_REQUEST](): Promise { + private async [State.MAKE_REQUEST](options: InlineChatRunOptions): Promise { assertType(this._editor.hasModel()); assertType(this._activeSession); assertType(this._strategy); @@ -587,9 +607,13 @@ export class InlineChatController implements IEditorContribution { attempt: this._activeSession.lastInput.attempt, selection: this._editor.getSelection(), wholeRange: this._activeSession.wholeRange.trackedInitialRange, - live: this._activeSession.editMode !== EditMode.Preview // TODO@jrieken let extension know what document is used for previewing + live: this._activeSession.editMode !== EditMode.Preview, // TODO@jrieken let extension know what document is used for previewing + withIntentDetection: options.withIntentDetection ?? true /* use intent detection by default */, }; + // re-enable intent detection + delete options.withIntentDetection; + const modelAltVersionIdNow = this._activeSession.textModelN.getAlternativeVersionId(); const progressEdits: TextEdit[][] = []; @@ -736,6 +760,8 @@ export class InlineChatController implements IEditorContribution { return State.PAUSE; } else if (message & Message.ACCEPT_SESSION) { return State.ACCEPT; + } else if (message & Message.ACCEPT_INPUT) { + return State.MAKE_REQUEST; } else { return State.APPLY_RESPONSE; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index beacd0fdcf4..20469180e6c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -172,6 +172,7 @@ export class InlineChatWidget { h('div.widget-toolbar@widgetToolbar') ]), h('div.progress@progress'), + h('div.detectedIntent.hidden@detectedIntent'), h('div.previewDiff.hidden@previewDiff'), h('div.previewCreateTitle.show-file-icons@previewCreateTitle'), h('div.previewCreate.hidden@previewCreate'), @@ -220,6 +221,9 @@ export class InlineChatWidget { private readonly _onDidChangeInput = this._store.add(new Emitter()); readonly onDidChangeInput: Event = this._onDidChangeInput.event; + private readonly _onRequestWithoutIntentDetection = this._store.add(new Emitter()); + readonly onRequestWithoutIntentDetection: Event = this._onRequestWithoutIntentDetection.event; + private _lastDim: Dimension | undefined; private _isLayouting: boolean = false; private _preferredExpansionState: ExpansionState | undefined; @@ -231,6 +235,7 @@ export class InlineChatWidget { private readonly _editorOptions: ChatEditorOptions; private _chatMessageDisposables = this._store.add(new DisposableStore()); private _followUpDisposables = this._store.add(new DisposableStore()); + private _slashCommandUsedDisposables = this._store.add(new DisposableStore()); private _chatMessage: MarkdownString | undefined; @@ -506,12 +511,13 @@ export class InlineChatWidget { getHeight(): number { const base = getTotalHeight(this._elements.progress) + getTotalHeight(this._elements.status); const editorHeight = this._inputEditor.getContentHeight() + 12 /* padding and border */; + const detectedIntentHeight = getTotalHeight(this._elements.detectedIntent); const followUpsHeight = getTotalHeight(this._elements.followUps); const chatResponseHeight = getTotalHeight(this._elements.chatMessage); const previewDiffHeight = this._previewDiffEditor.hasValue && this._previewDiffEditor.value.getModel() ? 12 + Math.min(300, Math.max(0, this._previewDiffEditor.value.getContentHeight())) : 0; const previewCreateTitleHeight = getTotalHeight(this._elements.previewCreateTitle); const previewCreateHeight = this._previewCreateEditor.hasValue && this._previewCreateEditor.value.getModel() ? 18 + Math.min(300, Math.max(0, this._previewCreateEditor.value.getContentHeight())) : 0; - return base + editorHeight + followUpsHeight + chatResponseHeight + previewDiffHeight + previewCreateTitleHeight + previewCreateHeight + 18 /* padding */ + 8 /*shadow*/; + return base + editorHeight + detectedIntentHeight + followUpsHeight + chatResponseHeight + previewDiffHeight + previewCreateTitleHeight + previewCreateHeight + 18 /* padding */ + 8 /*shadow*/; } updateProgress(show: boolean) { @@ -672,11 +678,28 @@ export class InlineChatWidget { return; } - this._elements.infoLabel.classList.toggle('hidden', false); - const label = localize('slashCommandUsed', "Using {0} to generate response...", `\`\`/${details.command}\`\``); + this._elements.detectedIntent.classList.toggle('hidden', false); - const e = renderFormattedText(label, { inline: true, renderCodeSegments: true, className: 'slash-command-pill' }); - reset(this._elements.infoLabel, e); + this._slashCommandUsedDisposables.clear(); + + const label = localize('slashCommandUsed', "Using {0} to generate response. [[Re-run without]]", `\`\`/${details.command}\`\``); + const usingSlashCommandText = renderFormattedText(label, { + inline: true, + renderCodeSegments: true, + className: 'slash-command-pill', + actionHandler: { + callback: (content) => { + if (content !== '0') { + return; + } + this._elements.detectedIntent.classList.toggle('hidden', true); + this._onRequestWithoutIntentDetection.fire(); + }, + disposables: this._slashCommandUsedDisposables, + } + }); + + reset(this._elements.detectedIntent, usingSlashCommandText); this._onDidChangeHeight.fire(); } @@ -718,6 +741,7 @@ export class InlineChatWidget { this.updateFollowUps(undefined); reset(this._elements.statusLabel); + this._elements.detectedIntent.classList.toggle('hidden', true); this._elements.statusLabel.classList.toggle('hidden', true); this._elements.extraToolbar.classList.add('hidden'); this._elements.statusToolbar.classList.add('hidden'); @@ -864,6 +888,7 @@ export class InlineChatWidget { const updateSlashDecorations = () => { this._slashCommandContentWidget.hide(); + this._elements.detectedIntent.classList.toggle('hidden', true); const newDecorations: IModelDeltaDecoration[] = []; for (const command of commands) { diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 5c29afd09f1..edf44ad1194 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -45,6 +45,7 @@ export interface IInlineChatRequest { attempt: number; requestId: string; live: boolean; + withIntentDetection: boolean; } export type IInlineChatResponse = IInlineChatEditResponse | IInlineChatBulkEditResponse; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts index 7dee82705f5..54e4d7b0b0c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts @@ -288,7 +288,8 @@ export class NotebookCellChatController extends Disposable { attempt: 0, selection: { selectionStartLineNumber: 1, selectionStartColumn: 1, positionLineNumber: 1, positionColumn: 1 }, wholeRange: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, - live: true + live: true, + withIntentDetection: true, // TODO: don't hard code but allow in corresponding UI to run without intent detection? }; const requestCts = new CancellationTokenSource(); diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index 47ccf2834ee..d24f451699a 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -33,6 +33,7 @@ declare module 'vscode' { wholeRange: Range; attempt: number; live: boolean; + withIntentDetection: boolean; } // todo@API make classes From 2f40bba1a297da6c6e0881b856a9719e391620ad Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 20 Dec 2023 16:54:43 +0100 Subject: [PATCH 0015/1897] first version od quick voice that starts inline chat with what you said --- .../inlineChat/browser/inlineChatActions.ts | 2 +- .../inlineChat.contribution.ts | 13 + .../electron-sandbox/inlineChatQuickVoice.css | 39 +++ .../electron-sandbox/inlineChatQuickVoice.ts | 226 ++++++++++++++++++ src/vs/workbench/workbench.desktop.main.ts | 2 + 5 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts create mode 100644 src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.css create mode 100644 src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 337fcaaf551..bdcb583a399 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -93,7 +93,7 @@ export class UnstashSessionAction extends EditorAction2 { } } -abstract class AbstractInlineChatAction extends EditorAction2 { +export abstract class AbstractInlineChatAction extends EditorAction2 { static readonly category = { value: localize('cat', 'Inline Chat'), original: 'Inline Chat' }; diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts new file mode 100644 index 00000000000..d60b9420644 --- /dev/null +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { CancelAction, InlineChatQuickVoice, StartAction, StopAction } from 'vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice'; + +registerEditorContribution(InlineChatQuickVoice.ID, InlineChatQuickVoice, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors +registerAction2(StartAction); +registerAction2(StopAction); +registerAction2(CancelAction); diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.css b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.css new file mode 100644 index 00000000000..784d716d80f --- /dev/null +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.css @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .inline-chat-quick-voice { + background-color: var(--vscode-editor-background); + padding: 2px; + border-radius: 8px; + display: flex; + align-items: center; + box-shadow: 0 4px 8px var(--vscode-inlineChat-shadow); + white-space: nowrap; + z-index: 1000; +} + +.monaco-editor .inline-chat-quick-voice .codicon.codicon-mic-filled { + display: flex; + align-items: center; + width: 16px; + height: 16px; +} + +.monaco-editor .inline-chat-quick-voice.recording .codicon.codicon-mic-filled { + color: var(--vscode-activityBarBadge-background); + animation: ani-inline-chat 1s infinite; +} + +@keyframes ani-inline-chat { + 0% { + color: var(--vscode-editorCursor-background); + } + 50% { + color: var(--vscode-activityBarBadge-background); + } + 100% { + color: var(--vscode-editorCursor-background); + } +} diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts new file mode 100644 index 00000000000..88333cf1215 --- /dev/null +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -0,0 +1,226 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./inlineChatQuickVoice'; +import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { Codicon } from 'vs/base/common/codicons'; +import { KeyChord, KeyCode } from 'vs/base/common/keyCodes'; +import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; +import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { localize, localize2 } from 'vs/nls'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import { h, reset } from 'vs/base/browser/dom'; +import { IDimension } from 'vs/editor/common/core/dimension'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { AbstractInlineChatAction } from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; + +const CTX_QUICK_CHAT_IN_PROGRESS = new RawContextKey('inlineChat.quickChatInProgress', false); + +export class StartAction extends EditorAction2 { + + constructor() { + super({ + id: 'inlineChat.quickVoice.start', + title: localize2('start', "Start Inline Voice Chat"), + category: AbstractInlineChatAction.category, + precondition: ContextKeyExpr.and(HasSpeechProvider, CTX_QUICK_CHAT_IN_PROGRESS.toNegated()), + keybinding: { + primary: KeyChord(KeyCode.F12, KeyCode.F12), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) { + InlineChatQuickVoice.get(editor)?.start(); + } +} + +export class StopAction extends EditorAction2 { + + constructor() { + super({ + id: 'inlineChat.quickVoice.stop', + title: localize2('stop', "Stop Inline Voice Chat"), + category: AbstractInlineChatAction.category, + precondition: ContextKeyExpr.and(HasSpeechProvider, CTX_QUICK_CHAT_IN_PROGRESS), + keybinding: { + primary: KeyChord(KeyCode.F12, KeyCode.F12), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) { + InlineChatQuickVoice.get(editor)?.stop(); + } +} + +export class CancelAction extends EditorAction2 { + + constructor() { + super({ + id: 'inlineChat.quickVoice.Cancel', + title: localize('Cancel', "Cancel Inline Voice Chat"), + category: AbstractInlineChatAction.category, + precondition: ContextKeyExpr.and(HasSpeechProvider, CTX_QUICK_CHAT_IN_PROGRESS), + keybinding: { + primary: KeyCode.Escape, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) { + InlineChatQuickVoice.get(editor)?.cancel(); + } +} + +class QuickVoiceWidget implements IContentWidget { + + readonly suppressMouseDown = true; + + private readonly _domNode = document.createElement('div'); + private readonly _elements = h('.inline-chat-quick-voice@main', [ + h('span@mic'), + h('span@message'), + ]); + + constructor(private readonly _editor: ICodeEditor) { + this._domNode.appendChild(this._elements.root); + this._domNode.style.zIndex = '1000'; + reset(this._elements.mic, renderIcon(Codicon.micFilled)); + } + + getId(): string { + return 'inlineChatQuickVoice'; + } + + getDomNode(): HTMLElement { + return this._domNode; + } + + getPosition(): IContentWidgetPosition | null { + if (!this._editor.hasModel()) { + return null; + } + const selection = this._editor.getSelection(); + // const position = this._editor.getPosition(); + return { + position: selection.getPosition(), + preference: [ + selection.getPosition().equals(selection.getStartPosition()) ? ContentWidgetPositionPreference.ABOVE : ContentWidgetPositionPreference.BELOW, + ContentWidgetPositionPreference.EXACT + ] + }; + } + + beforeRender(): IDimension | null { + const lineHeight = this._editor.getOption(EditorOption.lineHeight); + this._elements.main.style.lineHeight = `${lineHeight}px`; + this._elements.main.style.height = `${lineHeight}px`; + return null; + } + + // --- + + updateInput(input: string | undefined): void { + this._elements.message.textContent = input ?? ''; + } + + active(): void { + this._elements.main.classList.add('recording'); + } + + reset(): void { + this._elements.main.classList.remove('recording'); + this.updateInput(undefined); + } +} + +export class InlineChatQuickVoice implements IEditorContribution { + + static readonly ID = 'inlineChatQuickVoice'; + + static get(editor: ICodeEditor): InlineChatQuickVoice | null { + return editor.getContribution(InlineChatQuickVoice.ID); + } + + private readonly _ctxQuickChatInProgress: IContextKey; + private readonly _widget: QuickVoiceWidget; + private _finishCallback?: (abort: boolean) => void; + + constructor( + private readonly _editor: ICodeEditor, + @ISpeechService private readonly _speechService: ISpeechService, + @IContextKeyService contextKeyService: IContextKeyService, + ) { + this._widget = new QuickVoiceWidget(this._editor); + this._ctxQuickChatInProgress = CTX_QUICK_CHAT_IN_PROGRESS.bindTo(contextKeyService); + } + + dispose(): void { + this._finishCallback?.(true); + } + + start() { + + const cts = new CancellationTokenSource(); + this._editor.addContentWidget(this._widget); + this._ctxQuickChatInProgress.set(true); + + let message: string | undefined; + + const session = this._speechService.createSpeechToTextSession(cts.token); + const listener = session.onDidChange(e => { + + if (cts.token.isCancellationRequested) { + return; + } + + switch (e.status) { + case SpeechToTextStatus.Started: + this._widget.active(); + break; + case SpeechToTextStatus.Stopped: + break; + case SpeechToTextStatus.Recognizing: + case SpeechToTextStatus.Recognized: + message = e.text; + this._widget.updateInput(message); + break; + } + }); + + const done = (abort: boolean) => { + cts.dispose(true); + listener.dispose(); + this._widget.reset(); + this._editor.removeContentWidget(this._widget); + this._ctxQuickChatInProgress.reset(); + + if (!abort && message) { + InlineChatController.get(this._editor)?.run({ message, autoSend: true }); + } + }; + + this._finishCallback = done; + } + + stop(): void { + this._finishCallback?.(false); + } + + cancel(): void { + this._finishCallback?.(true); + } + +} diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 7f311e34818..e9005c84d11 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -174,6 +174,8 @@ import 'vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribu // Chat import 'vs/workbench/contrib/chat/electron-sandbox/chat.contribution'; +import 'vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution'; + //#endregion From 1197bf0fe2bb44eb8a6870b1a0f2ba44c03f976f Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Wed, 20 Dec 2023 17:34:46 +0100 Subject: [PATCH 0016/1897] stickyscroll commands have heigher prio --- src/vs/workbench/browser/actions/listCommands.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index 7ced87996c4..7c29e925cf3 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -365,7 +365,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'list.stickyScroll.collapse', - weight: KeybindingWeight.WorkbenchContrib, + weight: KeybindingWeight.WorkbenchContrib + 50, when: WorkbenchTreeStickyScrollFocused, primary: KeyCode.LeftArrow, mac: { @@ -564,7 +564,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'list.stickyScrollselect', - weight: KeybindingWeight.WorkbenchContrib, + weight: KeybindingWeight.WorkbenchContrib + 50, // priorities over file explorer when: WorkbenchTreeStickyScrollFocused, primary: KeyCode.Enter, mac: { @@ -713,7 +713,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'list.toggleExpand', - weight: KeybindingWeight.WorkbenchContrib, + weight: KeybindingWeight.WorkbenchContrib + 50, // priorities over file explorer when: WorkbenchTreeStickyScrollFocused, primary: KeyCode.Space, handler: (accessor) => { From ab4852b729440982b8ccdc50870ead19b176298d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 20 Dec 2023 13:27:36 -0600 Subject: [PATCH 0017/1897] work on #195282 --- .../accessibility/common/accessibility.ts | 7 +++- .../browser/accessibilityConfiguration.ts | 30 +++++++++++++- .../browser/accessibleNotificationService.ts | 41 ++++++++++++++++--- .../audioCueLineFeatureContribution.ts | 17 ++++---- 4 files changed, 80 insertions(+), 15 deletions(-) diff --git a/src/vs/platform/accessibility/common/accessibility.ts b/src/vs/platform/accessibility/common/accessibility.ts index 55b6f1ba0c1..112c92b72ba 100644 --- a/src/vs/platform/accessibility/common/accessibility.ts +++ b/src/vs/platform/accessibility/common/accessibility.ts @@ -56,10 +56,15 @@ export const IAccessibleNotificationService = createDecorator = new Map(); + private _events: Map = new Map(); constructor( @IAudioCueService private readonly _audioCueService: IAudioCueService, @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -26,17 +30,24 @@ export class AccessibleNotificationService extends Disposable implements IAccess this._events.set(AccessibleNotificationEvent.Clear, { audioCue: AudioCue.clear, alertMessage: localize('cleared', "Cleared") }); this._events.set(AccessibleNotificationEvent.Save, { audioCue: AudioCue.save, alertMessage: localize('saved', "Saved"), alertSetting: AccessibilityAlertSettingId.Save }); this._events.set(AccessibleNotificationEvent.Format, { audioCue: AudioCue.format, alertMessage: localize('formatted', "Formatted"), alertSetting: AccessibilityAlertSettingId.Format }); + this._events.set(AccessibleNotificationEvent.Breakpoint, { audioCue: AudioCue.break, alertMessage: localize('breakpoint', "Breakpoint"), alertSetting: AccessibilityAlertSettingId.Breakpoint }); + this._events.set(AccessibleNotificationEvent.Error, { audioCue: AudioCue.error, alertMessage: localize('error', "Error"), alertSetting: AccessibilityAlertSettingId.Error }); + this._events.set(AccessibleNotificationEvent.Warning, { audioCue: AudioCue.warning, alertMessage: localize('warning', "Warning"), alertSetting: AccessibilityAlertSettingId.Warning }); + this._events.set(AccessibleNotificationEvent.Folded, { audioCue: AudioCue.foldedArea, alertMessage: localize('foldedArea', "Folded Area"), alertSetting: AccessibilityAlertSettingId.FoldedArea }); - this._register(this._workingCopyService.onDidSave((e) => this._notify(AccessibleNotificationEvent.Save, e.reason === SaveReason.EXPLICIT))); + this._register(this._workingCopyService.onDidSave((e) => this._notifyBasedOnUserGesture(AccessibleNotificationEvent.Save, e.reason === SaveReason.EXPLICIT))); } + /** + * Notify on clear, save, format. Alerts and audio cues are mutually exclusive. + */ notify(event: AccessibleNotificationEvent, userGesture?: boolean): void { if (event === AccessibleNotificationEvent.Format) { - return this._notify(event, userGesture); + return this._notifyBasedOnUserGesture(AccessibleNotificationEvent.Format, userGesture); } const { audioCue, alertMessage } = this._events.get(event)!; - const audioCueValue = this._configurationService.getValue(audioCue.settingsKey); - if (audioCueValue === 'on' || audioCueValue === 'auto' && this._accessibilityService.isScreenReaderOptimized()) { + const audioCueSetting = this._configurationService.getValue(audioCue.settingsKey); + if (audioCueSetting === 'on' || audioCueSetting === 'auto' && this._accessibilityService.isScreenReaderOptimized()) { this._logService.debug('AccessibleNotificationService playing sound: ', audioCue.name); this._audioCueService.playAudioCue(audioCue); } else { @@ -45,7 +56,24 @@ export class AccessibleNotificationService extends Disposable implements IAccess } } - private _notify(event: AccessibleNotificationEvent, userGesture?: boolean): void { + /** + * Line feature contributions can use this to notify the user of changes to the line. + */ + notifyLineChanges(events: AccessibleNotificationEvent[]): void { + const audioCues = events.map(e => this._events.get(e)!.audioCue); + if (audioCues.length) { + this._logService.debug('AccessibleNotificationService playing sounds if enabled: ', events.map(e => this._events.get(e)!.audioCue.name).join(', ')); + this._audioCueService.playAudioCues(audioCues); + } + + const alerts = events.filter(e => this._configurationService.getValue(this._events.get(e)!.alertSetting!) === true).map(e => this._events.get(e)?.alertMessage); + if (alerts.length) { + this._logService.debug('AccessibleNotificationService alerting: ', alerts.join(', ')); + this._accessibilityService.alert(alerts.join(', ')); + } + } + + private _notifyBasedOnUserGesture(event: AccessibleNotificationEvent, userGesture?: boolean): void { const { audioCue, alertMessage, alertSetting } = this._events.get(event)!; if (!alertSetting) { return; @@ -79,4 +107,5 @@ export class TestAccessibleNotificationService extends Disposable implements IAc declare readonly _serviceBrand: undefined; notify(event: AccessibleNotificationEvent, userGesture?: boolean): void { } + notifyLineChanges(event: AccessibleNotificationEvent[]): void { } } diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts index c9421fd053d..bd42e67e35d 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts @@ -12,6 +12,7 @@ import { Position } from 'vs/editor/common/core/position'; import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; import { ITextModel } from 'vs/editor/common/model'; import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; +import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -26,8 +27,8 @@ export class AudioCueLineFeatureContribution private readonly store = this._register(new DisposableStore()); private readonly features: LineFeature[] = [ - this.instantiationService.createInstance(MarkerLineFeature, AudioCue.error, MarkerSeverity.Error), - this.instantiationService.createInstance(MarkerLineFeature, AudioCue.warning, MarkerSeverity.Warning), + this.instantiationService.createInstance(MarkerLineFeature, AccessibleNotificationEvent.Error, AudioCue.error, MarkerSeverity.Error), + this.instantiationService.createInstance(MarkerLineFeature, AccessibleNotificationEvent.Warning, AudioCue.warning, MarkerSeverity.Warning), this.instantiationService.createInstance(FoldedAreaLineFeature), this.instantiationService.createInstance(BreakpointLineFeature), ]; @@ -41,7 +42,8 @@ export class AudioCueLineFeatureContribution @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IAudioCueService private readonly audioCueService: IAudioCueService, - @IConfigurationService private readonly _configurationService: IConfigurationService + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService ) { super(); @@ -155,8 +157,7 @@ export class AudioCueLineFeatureContribution newValue?.featureStates.get(feature) && (!lastValue?.featureStates?.get(feature) || newValue.lineNumber !== lastValue.lineNumber) ); - - this.audioCueService.playAudioCues(newFeatures.map(f => f.audioCue)); + this._accessibleNotificationService.notifyLineChanges(newFeatures.map(feature => feature.event)); }) ); } @@ -164,6 +165,7 @@ export class AudioCueLineFeatureContribution interface LineFeature { audioCue: AudioCue; + event: AccessibleNotificationEvent; debounceWhileTyping?: boolean; getObservableState( editor: ICodeEditor, @@ -179,6 +181,7 @@ class MarkerLineFeature implements LineFeature { public readonly debounceWhileTyping = true; private _previousLine: number = 0; constructor( + public readonly event: AccessibleNotificationEvent, public readonly audioCue: AudioCue, private readonly severity: MarkerSeverity, @IMarkerService private readonly markerService: IMarkerService, @@ -210,7 +213,7 @@ class MarkerLineFeature implements LineFeature { class FoldedAreaLineFeature implements LineFeature { public readonly audioCue = AudioCue.foldedArea; - + public readonly event = AccessibleNotificationEvent.Folded; getObservableState(editor: ICodeEditor, model: ITextModel): IObservable { const foldingController = FoldingController.get(editor); if (!foldingController) { @@ -237,7 +240,7 @@ class FoldedAreaLineFeature implements LineFeature { class BreakpointLineFeature implements LineFeature { public readonly audioCue = AudioCue.break; - + public readonly event = AccessibleNotificationEvent.Breakpoint; constructor(@IDebugService private readonly debugService: IDebugService) { } getObservableState(editor: ICodeEditor, model: ITextModel): IObservable { From 91a18e15bbc8e958871ef32b06c5933cb809cb39 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 20 Dec 2023 13:39:12 -0600 Subject: [PATCH 0018/1897] fix issue --- src/vs/editor/standalone/browser/standaloneServices.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 25b1325d3c1..a0cdce16e4b 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -1076,9 +1076,8 @@ class StandaloneAudioService implements IAudioCueService { class StandaloneAccessibleNotificationService implements IAccessibleNotificationService { _serviceBrand: undefined; - notify(event: AccessibleNotificationEvent, userGesture?: boolean | undefined): void { - // NOOP - } + notify(event: AccessibleNotificationEvent, userGesture?: boolean | undefined): void { } + notifyLineChanges(event: AccessibleNotificationEvent[]): void { } } export interface IEditorOverrideServices { From 8b30e7b2c7c51053f07fd047dd2cf2a2ebcb522b Mon Sep 17 00:00:00 2001 From: Gayan Perera Date: Wed, 20 Dec 2023 15:21:38 +0100 Subject: [PATCH 0019/1897] feedback changes - change feature name to triggeredBy - add close button - add actions - add confirmation on toggle --- .../browser/breakpointEditorContribution.ts | 9 +- .../contrib/debug/browser/breakpointWidget.ts | 85 +++++++++++-------- .../contrib/debug/browser/breakpointsView.ts | 10 +-- .../debug/browser/debugEditorActions.ts | 31 +++++++ .../contrib/debug/browser/debugSession.ts | 2 +- .../debug/browser/media/breakpointWidget.css | 18 +++- .../workbench/contrib/debug/common/debug.ts | 7 +- .../contrib/debug/common/debugModel.ts | 30 ++++--- 8 files changed, 133 insertions(+), 59 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 2948b28ab89..ce8593f6705 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -284,7 +284,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi if (isShiftPressed) { breakpoints.forEach(bp => this.debugService.enableOrDisableBreakpoints(!enabled, bp)); - } else if (!env.isLinux && breakpoints.some(bp => !!bp.condition || !!bp.logMessage || !!bp.hitCondition)) { + } else if (!env.isLinux && breakpoints.some(bp => !!bp.condition || !!bp.logMessage || !!bp.hitCondition || !!bp.triggeredBy)) { // Show the dialog if there is a potential condition to be accidently lost. // Do not show dialog on linux due to electron issue freezing the mouse #50026 const logPoint = breakpoints.every(bp => !!bp.logMessage); @@ -454,6 +454,13 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi true, () => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.LOG_MESSAGE)) )); + actions.push(new Action( + 'addTriggerByBreakpoint', + nls.localize('addTriggerByBreakpoint', "Add Wait for breakpoint.."), + undefined, + true, + () => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.TRIGGER_POINT)) + )); } if (this.debugService.state === State.Stopped) { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index dc0049e6485..fafb2f1c52d 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -40,6 +40,7 @@ import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry' import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { Button } from 'vs/base/browser/ui/button/button'; const $ = dom.$; const IPrivateBreakpointWidgetService = createDecorator('privateBreakpointWidgetService'); @@ -87,7 +88,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi private breakpoint: IBreakpoint | undefined; private context: Context; private heightInPx: number | undefined; - private waitForBreakpointInput: IBreakpoint | undefined; + private triggeredByBreakpointInput: IBreakpoint | undefined; constructor(editor: ICodeEditor, private lineNumber: number, private column: number | undefined, context: Context | undefined, @IContextViewService private readonly contextViewService: IContextViewService, @@ -116,8 +117,8 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.context = Context.LOG_MESSAGE; } else if (this.breakpoint && !this.breakpoint.condition && this.breakpoint.hitCondition) { this.context = Context.HIT_COUNT; - } else if (this.breakpoint && this.breakpoint.waitFor) { - this.context = Context.WAIT_FOR_BREAKPOINT; + } else if (this.breakpoint && this.breakpoint.triggeredBy) { + this.context = Context.TRIGGER_POINT; } else { this.context = Context.CONDITION; } @@ -160,7 +161,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi } private rememberInput(): void { - if (this.context !== Context.WAIT_FOR_BREAKPOINT) { + if (this.context !== Context.TRIGGER_POINT) { const value = this.input.getModel().getValue(); switch (this.context) { case Context.LOG_MESSAGE: @@ -195,7 +196,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi protected _fillContainer(container: HTMLElement): void { this.setCssClass('breakpoint-widget'); - const selectBox = new SelectBox([{ text: nls.localize('expression', "Expression") }, { text: nls.localize('hitCount', "Hit Count") }, { text: nls.localize('logMessage', "Log Message") }, { text: nls.localize('waitBreakpoint', "Wait for breakpoint") }], this.context, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('breakpointType', 'Breakpoint Type') }); + const selectBox = new SelectBox([{ text: nls.localize('expression', "Expression") }, { text: nls.localize('hitCount', "Hit Count") }, { text: nls.localize('logMessage', "Log Message") }, { text: nls.localize('triggeredBy', "Wait for breakpoint") }], this.context, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('breakpointType', 'Breakpoint Type') }); this.selectContainer = $('.breakpoint-select-container'); selectBox.render(dom.append(container, this.selectContainer)); selectBox.onDidSelect(e => { @@ -213,36 +214,51 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi })); this.input.setPosition({ lineNumber: 1, column: this.input.getModel().getLineMaxColumn(1) }); - const breakpoints = this.debugService.getModel().getBreakpoints().filter(bp => bp !== this.breakpoint); - const index = breakpoints.findIndex((bp) => { return this.breakpoint?.waitFor?.matches(bp); }); - let select = 0; - if (index > -1) { - select = index + 1; - } - const items: ISelectOptionItem[] = [{ text: nls.localize('noBreakpointDependency', 'None') }]; - breakpoints.map(bp => { text: `${bp.uri.path} : ${bp.lineNumber}` }) - .forEach(i => items.push(i)); - - const selectBreakpointBox = new SelectBox(items, select, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('selectBreakpoint', 'Select breakpoint') }); - selectBreakpointBox.onDidSelect(e => { - if (e.index === 0) { - this.waitForBreakpointInput = undefined; - } else { - this.waitForBreakpointInput = breakpoints[e.index - 1]; - } - this.close(true); - }); - this.toDispose.push(selectBreakpointBox); - this.selectBreakpointContainer = $('.select-breakpoint-container'); - selectBreakpointBox.render(dom.append(container, this.selectBreakpointContainer)); + this.createTriggerBreakpointInput(container); this.updateContextInput(); // Due to an electron bug we have to do the timeout, otherwise we do not get focus setTimeout(() => this.input.focus(), 150); } + private createTriggerBreakpointInput(container: HTMLElement) { + const breakpoints = this.debugService.getModel().getBreakpoints().filter(bp => bp !== this.breakpoint); + + const index = breakpoints.findIndex((bp) => { return this.breakpoint?.triggeredBy?.matches(bp); }); + let select = 0; + if (index > -1) { + select = index + 1; + } + const items: ISelectOptionItem[] = [{ text: nls.localize('noTriggerByBreakpoint', 'None') }]; + breakpoints.map(bp => { text: `${bp.uri.path} : ${bp.lineNumber}` }) + .forEach(i => items.push(i)); + + const selectBreakpointBox = new SelectBox(items, select, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('selectBreakpoint', 'Select breakpoint') }); + selectBreakpointBox.onDidSelect(e => { + if (e.index === 0) { + this.triggeredByBreakpointInput = undefined; + } else { + this.triggeredByBreakpointInput = breakpoints[e.index - 1]; + } + this.close(true); + }); + this.toDispose.push(selectBreakpointBox); + this.selectBreakpointContainer = $('.select-breakpoint-container'); + + const selectionWrapper = $('.select-box-container'); + dom.append(this.selectBreakpointContainer, selectionWrapper); + selectBreakpointBox.render(selectionWrapper); + + dom.append(container, this.selectBreakpointContainer); + + const closeButton = new Button(this.selectBreakpointContainer, {}); + closeButton.label = nls.localize('close', "Close"); + closeButton.onDidClick(() => this.close(false)); + this.toDispose.push(closeButton); + } + private updateContextInput() { - if (this.context === Context.WAIT_FOR_BREAKPOINT) { + if (this.context === Context.TRIGGER_POINT) { this.inputContainer.hidden = true; this.selectBreakpointContainer.hidden = false; } else { @@ -362,8 +378,8 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi let condition = this.breakpoint && this.breakpoint.condition; let hitCondition = this.breakpoint && this.breakpoint.hitCondition; let logMessage = this.breakpoint && this.breakpoint.logMessage; - let waitFor = this.breakpoint && this.breakpoint.waitFor && - this.debugService.getModel().getBreakpoints().find(b => this.breakpoint!.waitFor!.matches(b)); + let triggeredBy = this.breakpoint && this.breakpoint.triggeredBy && + this.debugService.getModel().getBreakpoints().find(b => this.breakpoint!.triggeredBy!.matches(b)); this.rememberInput(); @@ -376,8 +392,8 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi if (this.logMessageInput || this.context === Context.LOG_MESSAGE) { logMessage = this.logMessageInput; } - if (this.context === Context.WAIT_FOR_BREAKPOINT) { - waitFor = this.waitForBreakpointInput; + if (this.context === Context.TRIGGER_POINT) { + triggeredBy = this.triggeredByBreakpointInput; } if (this.breakpoint) { @@ -386,7 +402,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi condition, hitCondition, logMessage, - waitFor + triggeredBy }); this.debugService.updateBreakpoints(this.breakpoint.originalUri, data, false).then(undefined, onUnexpectedError); } else { @@ -398,7 +414,8 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi enabled: true, condition, hitCondition, - logMessage + logMessage, + triggeredBy }]); } } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 1f656c3f065..5a3820b3355 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -1292,12 +1292,12 @@ export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: } // can change this when all breakpoint supports dependent breakpoint condition - let waitForBreakpoint: BreakpointReference | undefined; + let triggeredByBreakpoint: BreakpointReference | undefined; if (breakpoint instanceof Breakpoint) { - waitForBreakpoint = breakpoint.waitFor; + triggeredByBreakpoint = breakpoint.triggeredBy; } - if (breakpoint.logMessage || breakpoint.condition || breakpoint.hitCondition || waitForBreakpoint) { + if (breakpoint.logMessage || breakpoint.condition || breakpoint.hitCondition || triggeredByBreakpoint) { const messages: string[] = []; let icon = breakpoint.logMessage ? icons.logBreakpoint.regular : icons.conditionalBreakpoint.regular; if (!breakpoint.supported) { @@ -1315,8 +1315,8 @@ export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: messages.push(localize('hitCount', "Hit Count: {0}", breakpoint.hitCondition)); } - if (waitForBreakpoint) { - messages.push(localize('waitFor', "Hit after breakpoint: {0}:{1}", waitForBreakpoint.uri.toString(), waitForBreakpoint.lineNumber)); + if (triggeredByBreakpoint) { + messages.push(localize('triggeredBy', "Hit after breakpoint: {0}:{1}", triggeredByBreakpoint.uri.toString(), triggeredByBreakpoint.lineNumber)); } return { diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 415a2df0083..aebf6c90d3d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -151,6 +151,36 @@ class LogPointAction extends EditorAction { } } +class TriggerByBreakpointAction extends EditorAction { + + constructor() { + super({ + id: 'editor.debug.action.triggerByBreakpoint', + label: nls.localize('triggerByBreakpointEditorAction', "Debug: Wait for breakpoint..."), + precondition: CONTEXT_DEBUGGERS_AVAILABLE, + alias: 'Debug: Wait for breakpoint...', + menuOpts: [ + { + menuId: MenuId.MenubarNewBreakpointMenu, + title: nls.localize({ key: 'miTriggerByBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Wait for breakpoint..."), + group: '1_breakpoints', + order: 4, + when: CONTEXT_DEBUGGERS_AVAILABLE, + } + ] + }); + } + + async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + const debugService = accessor.get(IDebugService); + + const position = editor.getPosition(); + if (position && editor.hasModel() && debugService.canSetBreakpointsIn(editor.getModel())) { + editor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID)?.showBreakpointWidget(position.lineNumber, position.column, BreakpointWidgetContext.TRIGGER_POINT); + } + } +} + class EditBreakpointAction extends EditorAction { constructor() { super({ @@ -596,6 +626,7 @@ registerAction2(ToggleDisassemblyViewSourceCodeAction); registerAction2(ToggleBreakpointAction); registerEditorAction(ConditionalBreakpointAction); registerEditorAction(LogPointAction); +registerEditorAction(TriggerByBreakpointAction); registerEditorAction(EditBreakpointAction); registerEditorAction(RunToCursorAction); registerEditorAction(StepIntoTargetsAction); diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index c6cdf36a14a..20dd6e53c4b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -1312,7 +1312,7 @@ export class DebugSession implements IDebugSession, IDisposable { const uriBreakpoints = new Map(); this.model.getBreakpoints({ dependentOnly: true, enabledOnly: true }).forEach(bp => { breakpoints.forEach(cbp => { - if (bp.waitFor?.matches(cbp)) { + if (bp.triggeredBy?.matches(cbp)) { const uri = bp.uri; if (!uriBreakpoints.has(uri)) { uriBreakpoints.set(uri, []); diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css index 71f25c6641a..0af62bf27b9 100644 --- a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css @@ -31,17 +31,29 @@ } .monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container { + display: flex; + flex-direction: row; + padding: 0 10px; + flex-shrink: 1; +} + +.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container .monaco-button { + min-width: 100px; + min-height: 18px; + padding: 2px 20px 2px 1px; +} + +.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container .select-box-container { display: flex; justify-content: center; flex-direction: column; - padding: 0 10px; flex-shrink: 0; } -.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container .monaco-select-box { +.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container .select-box-container .monaco-select-box { min-width: 100px; min-height: 18px; - padding: 2px 20px 2px 8px; + padding: 2px 1px 2px 0px; } .monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container:after { diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 9e2f53c862b..2eb4610ee0e 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -531,6 +531,7 @@ export interface IBreakpointData { readonly condition?: string; readonly logMessage?: string; readonly hitCondition?: string; + readonly triggeredBy?: IBreakpoint; } export interface IBreakpointUpdateData { @@ -539,7 +540,7 @@ export interface IBreakpointUpdateData { readonly logMessage?: string; readonly lineNumber?: number; readonly column?: number; - readonly waitFor?: IBreakpoint; + readonly triggeredBy?: IBreakpoint; } export interface IBaseBreakpoint extends IEnablement { @@ -571,7 +572,7 @@ export interface IBreakpoint extends IBaseBreakpoint { readonly endColumn?: number; readonly adapterData: any; readonly sessionAgnosticData: { lineNumber: number; column: number | undefined }; - readonly waitFor?: IBreakpointReference; + readonly triggeredBy?: IBreakpointReference; } export interface IFunctionBreakpoint extends IBaseBreakpoint { @@ -1213,7 +1214,7 @@ export const enum BreakpointWidgetContext { CONDITION = 0, HIT_COUNT = 1, LOG_MESSAGE = 2, - WAIT_FOR_BREAKPOINT = 3 + TRIGGER_POINT = 3 } export interface IDebugEditorContribution extends editorCommon.IEditorContribution { diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 4e4206960ff..91f5b27ff89 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -882,7 +882,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { private readonly uriIdentityService: IUriIdentityService, private readonly logService: ILogService, id = generateUuid(), - public waitFor: IBreakpointReference | undefined = undefined + public triggeredBy: IBreakpointReference | undefined = undefined ) { super(enabled, hitCondition, condition, logMessage, id); } @@ -900,7 +900,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { return this.data.verified && !this.textFileService.isDirty(this._uri); } - return !this.waitFor; + return !this.triggeredBy; } get uri(): uri { @@ -969,11 +969,11 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { result.column = this._column; result.adapterData = this.adapterData; - if (this.waitFor) { + if (this.triggeredBy) { const wf = Object.create(null); - wf.uri = this.waitFor?.uri.toString(); - wf.lineNumber = this.waitFor?.lineNumber; - wf.column = this.waitFor?.column; + wf.uri = this.triggeredBy?.uri.toString(); + wf.lineNumber = this.triggeredBy?.lineNumber; + wf.column = this.triggeredBy?.column; result.waitFor = wf; } @@ -1000,10 +1000,10 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { if (!isUndefinedOrNull(data.logMessage)) { this.logMessage = data.logMessage; } - if (!isUndefinedOrNull(data.waitFor)) { - this.waitFor = new BreakpointReference(data.waitFor.uri, data.waitFor.lineNumber, data.waitFor.column); + if (!isUndefinedOrNull(data.triggeredBy)) { + this.triggeredBy = new BreakpointReference(data.triggeredBy.uri, data.triggeredBy.lineNumber, data.triggeredBy.column); } else { - this.waitFor = undefined; + this.triggeredBy = undefined; } } } @@ -1412,10 +1412,10 @@ export class DebugModel extends Disposable implements IDebugModel { if (filter.enabledOnly && (!this.breakpointsActivated || !bp.enabled)) { return false; } - if (filter.dependentOnly && bp.waitFor === undefined) { + if (filter.dependentOnly && bp.triggeredBy === undefined) { return false; } - if (filter.excludeDependent && bp.waitFor !== undefined) { + if (filter.excludeDependent && bp.triggeredBy !== undefined) { return false; } @@ -1492,7 +1492,13 @@ export class DebugModel extends Disposable implements IDebugModel { } addBreakpoints(uri: uri, rawData: IBreakpointData[], fireEvent = true): IBreakpoint[] { - const newBreakpoints = rawData.map(rawBp => new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled === false ? false : true, rawBp.condition, rawBp.hitCondition, rawBp.logMessage, undefined, this.textFileService, this.uriIdentityService, this.logService, rawBp.id)); + const newBreakpoints = rawData.map(rawBp => { + let triggeredBy = undefined; + if (rawBp!.triggeredBy) { + triggeredBy = new BreakpointReference(rawBp.triggeredBy.uri, rawBp.triggeredBy.lineNumber, rawBp.triggeredBy.column); + } + return new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled === false ? false : true, rawBp.condition, rawBp.hitCondition, rawBp.logMessage, undefined, this.textFileService, this.uriIdentityService, this.logService, rawBp.id, triggeredBy); + }); this.breakpoints = this.breakpoints.concat(newBreakpoints); this.breakpointsActivated = true; this.sortAndDeDup(); From 203dd25083ef94032d7f266d4f4e1ae4924f50e9 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 20 Dec 2023 12:36:35 -0800 Subject: [PATCH 0020/1897] re-apply changes with css fixups --- .../contrib/debug/browser/breakpointWidget.ts | 91 ++++++++++--------- .../contrib/debug/browser/debugSession.ts | 44 +++++---- .../debug/browser/media/breakpointWidget.css | 18 ++-- 3 files changed, 82 insertions(+), 71 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index fafb2f1c52d..1723e2f36f5 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -3,44 +3,46 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/breakpointWidget'; -import * as nls from 'vs/nls'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { SelectBox, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; -import * as lifecycle from 'vs/base/common/lifecycle'; import * as dom from 'vs/base/browser/dom'; -import { Position, IPosition } from 'vs/editor/common/core/position'; -import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { IDebugService, IBreakpoint, BreakpointWidgetContext as Context, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, DEBUG_SCHEME, CONTEXT_IN_BREAKPOINT_WIDGET, IBreakpointUpdateData, IBreakpointEditorContribution, BREAKPOINT_EDITOR_CONTRIBUTION_ID } from 'vs/workbench/contrib/debug/common/debug'; -import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; -import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ServicesAccessor, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { IModelService } from 'vs/editor/common/services/model'; -import { URI as uri } from 'vs/base/common/uri'; -import { CompletionList, CompletionContext, CompletionItemKind } from 'vs/editor/common/languages'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { ITextModel } from 'vs/editor/common/model'; -import { provideSuggestionItems, CompletionOptions } from 'vs/editor/contrib/suggest/browser/suggest'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { editorForeground } from 'vs/platform/theme/common/colorRegistry'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IDecorationOptions } from 'vs/editor/common/editorCommon'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { getSimpleEditorOptions, getSimpleCodeEditorWidgetOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; -import { IRange, Range } from 'vs/editor/common/core/range'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; -import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Button } from 'vs/base/browser/ui/button/button'; +import { ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import * as lifecycle from 'vs/base/common/lifecycle'; +import { URI as uri } from 'vs/base/common/uri'; +import 'vs/css!./media/breakpointWidget'; +import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorCommand, ServicesAccessor, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { EditorOption, IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { IPosition, Position } from 'vs/editor/common/core/position'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { IDecorationOptions } from 'vs/editor/common/editorCommon'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { CompletionContext, CompletionItemKind, CompletionList } from 'vs/editor/common/languages'; +import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; +import { ITextModel } from 'vs/editor/common/model'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { IModelService } from 'vs/editor/common/services/model'; +import { CompletionOptions, provideSuggestionItems } from 'vs/editor/contrib/suggest/browser/suggest'; +import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; +import * as nls from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { defaultButtonStyles, defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { editorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; +import { BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, CONTEXT_IN_BREAKPOINT_WIDGET, BreakpointWidgetContext as Context, DEBUG_SCHEME, IBreakpoint, IBreakpointEditorContribution, IBreakpointUpdateData, IDebugService } from 'vs/workbench/contrib/debug/common/debug'; const $ = dom.$; const IPrivateBreakpointWidgetService = createDecorator('privateBreakpointWidgetService'); @@ -101,6 +103,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi @IConfigurationService private readonly _configurationService: IConfigurationService, @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @IKeybindingService private readonly keybindingService: IKeybindingService, + @ILabelService private readonly labelService: ILabelService, ) { super(editor, { showFrame: true, showArrow: false, frameWidth: 1, isAccessible: true }); @@ -230,7 +233,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi select = index + 1; } const items: ISelectOptionItem[] = [{ text: nls.localize('noTriggerByBreakpoint', 'None') }]; - breakpoints.map(bp => { text: `${bp.uri.path} : ${bp.lineNumber}` }) + breakpoints.map(bp => { text: `${this.labelService.getUriLabel(bp.uri, { relative: true })}: ${bp.lineNumber}` }) .forEach(i => items.push(i)); const selectBreakpointBox = new SelectBox(items, select, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('selectBreakpoint', 'Select breakpoint') }); @@ -239,11 +242,17 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.triggeredByBreakpointInput = undefined; } else { this.triggeredByBreakpointInput = breakpoints[e.index - 1]; + this.close(true); } - this.close(true); }); this.toDispose.push(selectBreakpointBox); this.selectBreakpointContainer = $('.select-breakpoint-container'); + this.toDispose.push(dom.addDisposableListener(this.selectBreakpointContainer, dom.EventType.KEY_DOWN, e => { + const event = new StandardKeyboardEvent(e); + if (event.equals(KeyCode.Escape)) { + this.close(false); + } + })); const selectionWrapper = $('.select-box-container'); dom.append(this.selectBreakpointContainer, selectionWrapper); @@ -251,9 +260,9 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi dom.append(container, this.selectBreakpointContainer); - const closeButton = new Button(this.selectBreakpointContainer, {}); - closeButton.label = nls.localize('close', "Close"); - closeButton.onDidClick(() => this.close(false)); + const closeButton = new Button(this.selectBreakpointContainer, defaultButtonStyles); + closeButton.label = nls.localize('ok', "Ok"); + this.toDispose.push(closeButton.onDidClick(() => this.close(false))); this.toDispose.push(closeButton); } diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 20dd6e53c4b..321abdb0f5c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -1227,6 +1227,13 @@ export class DebugSession implements IDebugSession, IDisposable { this.passFocusScheduler.cancel(); this.stoppedDetails.push(event); + // do this very eagerly if we have hitBreakpointIds, since it may take a + // moment for breakpoints to set and we want to do our best to not miss + // anything + if (event.hitBreakpointIds) { + this.enableDependentBreakpoints(event.hitBreakpointIds); + } + this.statusQueue.run( this.fetchThreads(event).then(() => event.threadId === undefined ? this.threadIds : [event.threadId]), async (threadId, token) => { @@ -1238,6 +1245,7 @@ export class DebugSession implements IDebugSession, IDisposable { if (focusedThreadDoesNotExist) { this.debugService.focusStackFrame(undefined, undefined); } + const thread = typeof threadId === 'number' ? this.getThread(threadId) : undefined; if (thread) { // Call fetch call stack twice, the first only return the top stack frame. @@ -1270,7 +1278,9 @@ export class DebugSession implements IDebugSession, IDisposable { await promises.topCallStack; - this.enableDependentBreakpoints(thread); + if (!event.hitBreakpointIds) { // if hitBreakpointIds are present, this is handled earlier on + this.enableDependentBreakpoints(thread); + } if (token.isCancellationRequested) { return; @@ -1294,25 +1304,30 @@ export class DebugSession implements IDebugSession, IDisposable { ); } - private enableDependentBreakpoints(thread: Thread) { - const frame = thread.getTopStackFrame(); - if (frame === undefined) { - return; - } + private enableDependentBreakpoints(hitBreakpointIdsOrThread: Thread | number[]) { + let breakpoints: IBreakpoint[]; + if (Array.isArray(hitBreakpointIdsOrThread)) { + breakpoints = this.model.getBreakpoints().filter(bp => hitBreakpointIdsOrThread.includes(bp.getIdFromAdapter(this.id)!)); + } else { + const frame = hitBreakpointIdsOrThread.getTopStackFrame(); + if (frame === undefined) { + return; + } - if (thread.stoppedDetails && thread.stoppedDetails.reason !== 'breakpoint') { - return; + if (hitBreakpointIdsOrThread.stoppedDetails && hitBreakpointIdsOrThread.stoppedDetails.reason !== 'breakpoint') { + return; + } + + breakpoints = this.getBreakpointsAtPosition(frame.source.uri, frame.range.startLineNumber, frame.range.endLineNumber, frame.range.startColumn, frame.range.endColumn); } // find the current breakpoints - const hitBreakpointIds = thread.stoppedDetails?.hitBreakpointIds?.map(id => id.toString()); - const breakpoints = this.getBreakpoints(hitBreakpointIds) || this.getBreakpointsAtPosition(frame.source.uri, frame.range.startLineNumber, frame.range.endLineNumber, frame.range.startColumn, frame.range.endColumn); // check if the current breakpoints are dependencies, and if so collect and send the dependents to DA const uriBreakpoints = new Map(); this.model.getBreakpoints({ dependentOnly: true, enabledOnly: true }).forEach(bp => { breakpoints.forEach(cbp => { - if (bp.triggeredBy?.matches(cbp)) { + if (bp.enabled && bp.triggeredBy?.matches(cbp)) { const uri = bp.uri; if (!uriBreakpoints.has(uri)) { uriBreakpoints.set(uri, []); @@ -1329,13 +1344,6 @@ export class DebugSession implements IDebugSession, IDisposable { } } - private getBreakpoints(ids?: string[]): IBreakpoint[] | undefined { - if (ids) { - return this.model.getBreakpoints().filter(bp => ids.includes(bp.getId())); - } - return undefined; - } - private getBreakpointsAtPosition(uri: URI, startLineNumber: number, endLineNumber: number, startColumn: number, endColumn: number): IBreakpoint[] { return this.model.getBreakpoints({ uri: uri }).filter(bp => { if (bp.lineNumber < startLineNumber || bp.lineNumber > endLineNumber) { diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css index 0af62bf27b9..88728c85b20 100644 --- a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css @@ -16,7 +16,7 @@ flex-shrink: 0; } -.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .breakpoint-select-container .monaco-select-box { +.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .monaco-select-box { min-width: 100px; min-height: 18px; padding: 2px 20px 2px 8px; @@ -32,15 +32,15 @@ .monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container { display: flex; + align-items: center; flex-direction: row; - padding: 0 10px; - flex-shrink: 1; + gap: 10px; } .monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container .monaco-button { - min-width: 100px; - min-height: 18px; - padding: 2px 20px 2px 1px; + padding-left: 8px; + padding-right: 8px; + line-height: 14px; } .monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container .select-box-container { @@ -50,12 +50,6 @@ flex-shrink: 0; } -.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container .select-box-container .monaco-select-box { - min-width: 100px; - min-height: 18px; - padding: 2px 1px 2px 0px; -} - .monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container:after { right: 14px; } From 05e23a3d8ef7cea9e624c7275215e76b24429d7d Mon Sep 17 00:00:00 2001 From: Gayan Perera Date: Thu, 21 Dec 2023 20:39:23 +0100 Subject: [PATCH 0021/1897] add icon support for better diff look of pending breakpoints --- src/vs/base/common/codicons.ts | 1 + .../debug/browser/breakpointEditorContribution.ts | 7 +++++++ .../workbench/contrib/debug/browser/breakpointsView.ts | 7 +++++++ src/vs/workbench/contrib/debug/browser/debugIcons.ts | 3 ++- src/vs/workbench/contrib/debug/common/debug.ts | 2 ++ src/vs/workbench/contrib/debug/common/debugModel.ts | 9 ++++++++- 6 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index dcaf8a435aa..37343ad65c6 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -93,6 +93,7 @@ export const Codicon = { closeDirty: register('close-dirty', 0xea71), debugBreakpoint: register('debug-breakpoint', 0xea71), debugBreakpointDisabled: register('debug-breakpoint-disabled', 0xea71), + debugBreakpointPending: register('debug-breakpoint-pending', 0xebd9), debugHint: register('debug-hint', 0xea71), primitiveSquare: register('primitive-square', 0xea72), edit: register('edit', 0xea73), diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index ce8593f6705..949a858acbe 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -799,6 +799,13 @@ registerThemingParticipant((theme, collector) => { color: ${debugIconBreakpointColor} !important; } `); + + collector.addRule(` + .monaco-workbench ${ThemeIcon.asCSSSelector(icons.breakpoint.pending)} { + color: ${debugIconBreakpointColor} !important; + font-size: 12px !important; + } + `); } const debugIconBreakpointDisabledColor = theme.getColor(debugIconBreakpointDisabledForeground); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 5a3820b3355..427ad74fbe4 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -1221,6 +1221,13 @@ export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: const appendMessage = (text: string): string => { return ('message' in breakpoint && breakpoint.message) ? text.concat(', ' + breakpoint.message) : text; }; + + if (debugActive && breakpoint instanceof Breakpoint && breakpoint.pending) { + return { + icon: icons.breakpoint.pending + }; + } + if (debugActive && !breakpoint.verified) { return { icon: breakpointIcon.unverified, diff --git a/src/vs/workbench/contrib/debug/browser/debugIcons.ts b/src/vs/workbench/contrib/debug/browser/debugIcons.ts index 4da189b0aff..b1a9a4a0789 100644 --- a/src/vs/workbench/contrib/debug/browser/debugIcons.ts +++ b/src/vs/workbench/contrib/debug/browser/debugIcons.ts @@ -18,7 +18,8 @@ export const loadedScriptsViewIcon = registerIcon('loaded-scripts-view-icon', Co export const breakpoint = { regular: registerIcon('debug-breakpoint', Codicon.debugBreakpoint, localize('debugBreakpoint', 'Icon for breakpoints.')), disabled: registerIcon('debug-breakpoint-disabled', Codicon.debugBreakpointDisabled, localize('debugBreakpointDisabled', 'Icon for disabled breakpoints.')), - unverified: registerIcon('debug-breakpoint-unverified', Codicon.debugBreakpointUnverified, localize('debugBreakpointUnverified', 'Icon for unverified breakpoints.')) + unverified: registerIcon('debug-breakpoint-unverified', Codicon.debugBreakpointUnverified, localize('debugBreakpointUnverified', 'Icon for unverified breakpoints.')), + pending: registerIcon('debug-breakpoint-pending', Codicon.debugBreakpointPending, localize('debugBreakpointPendingOnTrigger', 'Icon for breakpoints waiting on another breakpoint.')), }; export const functionBreakpoint = { regular: registerIcon('debug-breakpoint-function', Codicon.debugBreakpointFunction, localize('debugBreakpointFunction', 'Icon for function breakpoints.')), diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 2eb4610ee0e..87628924bd6 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -573,6 +573,8 @@ export interface IBreakpoint extends IBaseBreakpoint { readonly adapterData: any; readonly sessionAgnosticData: { lineNumber: number; column: number | undefined }; readonly triggeredBy?: IBreakpointReference; + /** Pending on the trigger breakpoint, which means this breakpoint is not yet sent to DA */ + readonly pending: boolean; } export interface IFunctionBreakpoint extends IBaseBreakpoint { diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 91f5b27ff89..1d88c4b270f 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -900,7 +900,14 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { return this.data.verified && !this.textFileService.isDirty(this._uri); } - return !this.triggeredBy; + return true; + } + + get pending(): boolean { + if (this.data) { + return false; + } + return this.triggeredBy !== undefined; } get uri(): uri { From 676d7ba4857f3781af8b58ea6da34bc2500706ba Mon Sep 17 00:00:00 2001 From: Justin Carrus Date: Sun, 24 Dec 2023 01:22:12 +0000 Subject: [PATCH 0022/1897] Increase editor.stickyScroll.maxLineCount from 10 to 20 --- src/vs/editor/common/config/editorOptions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 764e94ed31e..823718b890e 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2822,7 +2822,7 @@ class EditorStickyScroll extends BaseEditorOption(input.defaultModel, this.defaultValue.defaultModel, ['outlineModel', 'foldingProviderModel', 'indentationModel']), scrollWithEditor: boolean(input.scrollWithEditor, this.defaultValue.scrollWithEditor) }; From 01b8428485c31b6f5a899a54a3196bf64335cd81 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 29 Dec 2023 08:39:35 +0100 Subject: [PATCH 0023/1897] VSCode always restores the panel size if toggling primary side bar. (fix #195119) (#201567) --- src/vs/workbench/browser/layout.ts | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 0efb79865fe..a4c1c34b1a4 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -1274,6 +1274,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.focusPart(Parts.EDITOR_PART, getWindow(this.activeContainer)); } + private focusPanelOrEditor(): void { + const activePanel = this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel); + if ((this.hasFocus(Parts.PANEL_PART) || !this.isVisible(Parts.EDITOR_PART)) && activePanel) { + activePanel.focus(); // prefer panel if it has focus or editor is hidden + } else { + this.focus(); // otherwise focus editor + } + } + getMaximumEditorDimensions(container: HTMLElement): IDimension { const targetWindow = getWindow(container); const containerDimension = this.getContainerDimension(container); @@ -1746,14 +1755,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // If sidebar becomes hidden, also hide the current active Viewlet if any if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar)) { this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.Sidebar); - - // Pass Focus to Editor or Panel if Sidebar is now hidden - const activePanel = this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel); - if (this.hasFocus(Parts.PANEL_PART) && activePanel) { - activePanel.focus(); - } else { - this.focus(); - } + this.focusPanelOrEditor(); } // If sidebar becomes visible, show last active Viewlet or default viewlet @@ -1986,14 +1988,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // If auxiliary bar becomes hidden, also hide the current active pane composite if any if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.AuxiliaryBar)) { this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.AuxiliaryBar); - - // Pass Focus to Editor or Panel if Auxiliary Bar is now hidden - const activePanel = this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel); - if (this.hasFocus(Parts.PANEL_PART) && activePanel) { - activePanel.focus(); - } else { - this.focus(); - } + this.focusPanelOrEditor(); } // If auxiliary bar becomes visible, show last active pane composite or default pane composite From 14d01569e5907e43d0966806af8c4c593a109f5c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 29 Dec 2023 08:46:38 +0100 Subject: [PATCH 0024/1897] Aux window: support to restore aux windows and editors (fix #195887) (#201291) --- .../electron-main/auxiliaryWindow.ts | 7 +- .../electron-main/lifecycleMainService.ts | 34 +++++ .../electron-main/workbenchTestServices.ts | 2 + .../api/browser/mainThreadEditorTabs.ts | 2 +- .../browser/actions/developerActions.ts | 4 +- src/vs/workbench/browser/contextkeys.ts | 2 +- src/vs/workbench/browser/layout.ts | 10 +- src/vs/workbench/browser/part.ts | 4 +- .../parts/editor/auxiliaryEditorPart.ts | 27 +++- .../browser/parts/editor/editorGroupView.ts | 8 +- .../browser/parts/editor/editorPart.ts | 31 ++-- .../browser/parts/editor/editorParts.ts | 136 ++++++++++++++++-- .../browser/parts/editor/editorsObserver.ts | 2 +- .../browser/parts/statusbar/statusbarPart.ts | 6 +- .../browser/parts/titlebar/titlebarPart.ts | 6 +- .../services/notebookEditorServiceImpl.ts | 2 +- .../browser/webviewPanel.contribution.ts | 2 +- .../browser/auxiliaryWindowService.ts | 33 +++-- .../services/editor/browser/editorService.ts | 2 +- .../editor/common/editorGroupsService.ts | 72 +++++----- .../history/browser/historyService.ts | 4 +- 21 files changed, 304 insertions(+), 92 deletions(-) diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts index 8317fe7bcd2..cf36f0950b9 100644 --- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts +++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts @@ -6,6 +6,7 @@ import { BrowserWindow, WebContents } from 'electron'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; +import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { ILogService } from 'vs/platform/log/common/log'; import { IStateService } from 'vs/platform/state/node/state'; import { IBaseWindow } from 'vs/platform/window/electron-main/window'; @@ -33,7 +34,8 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { @IEnvironmentMainService environmentMainService: IEnvironmentMainService, @ILogService private readonly logService: ILogService, @IConfigurationService configurationService: IConfigurationService, - @IStateService stateService: IStateService + @IStateService stateService: IStateService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService ) { super(configurationService, stateService, environmentMainService); @@ -61,6 +63,9 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { // Disable Menu window.setMenu(null); + + // Lifecycle + this.lifecycleMainService.registerAuxWindow(this); } } } diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts index 82640175b1c..ed098115159 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts @@ -18,6 +18,7 @@ import { IStateService } from 'vs/platform/state/node/state'; import { ICodeWindow, LoadReason, UnloadReason } from 'vs/platform/window/electron-main/window'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; +import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; export const ILifecycleMainService = createDecorator('lifecycleMainService'); @@ -131,6 +132,11 @@ export interface ILifecycleMainService { */ registerWindow(window: ICodeWindow): void; + /** + * Make a `IAuxiliaryWindow` known to the lifecycle main service. + */ + registerAuxWindow(auxWindow: IAuxiliaryWindow): void; + /** * Reload a window. All lifecycle event handlers are triggered. */ @@ -472,6 +478,34 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe }); } + registerAuxWindow(auxWindow: IAuxiliaryWindow): void { + const win = assertIsDefined(auxWindow.win); + + win.on('close', e => { + this.trace(`Lifecycle#auxWindow.on('close') - window ID ${auxWindow.id}`); + + if (this._quitRequested) { + this.trace(`Lifecycle#auxWindow.on('close') - preventDefault() because quit requested`); + + // When quit is requested, Electron will close all + // auxiliary windows before closing the main windows. + // This prevents us from storing the auxiliary window + // state on shutdown and thus we prevent closing if + // quit is requested. + // + // Interestingly, this will not prevent the application + // from quitting because the auxiliary windows will still + // close once the owning window closes. + + e.preventDefault(); + } + }); + + win.on('closed', () => { + this.trace(`Lifecycle#auxWindow.on('closed') - window ID ${auxWindow.id}`); + }); + } + async reload(window: ICodeWindow, cli?: NativeParsedArgs): Promise { // Only reload when the window has not vetoed this diff --git a/src/vs/platform/test/electron-main/workbenchTestServices.ts b/src/vs/platform/test/electron-main/workbenchTestServices.ts index 639f1fab2bc..65beabd3b2a 100644 --- a/src/vs/platform/test/electron-main/workbenchTestServices.ts +++ b/src/vs/platform/test/electron-main/workbenchTestServices.ts @@ -5,6 +5,7 @@ import { Promises } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; +import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { ILifecycleMainService, IRelaunchHandler, LifecycleMainPhase, ShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IStateService } from 'vs/platform/state/node/state'; @@ -41,6 +42,7 @@ export class TestLifecycleMainService implements ILifecycleMainService { phase = LifecycleMainPhase.Ready; registerWindow(window: ICodeWindow): void { } + registerAuxWindow(auxWindow: IAuxiliaryWindow): void { } async reload(window: ICodeWindow, cli?: NativeParsedArgs): Promise { } async unload(window: ICodeWindow, reason: UnloadReason): Promise { return true; } setRelaunchHandler(handler: IRelaunchHandler): void { } diff --git a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts index 5f2f3610314..d23f8e91fd5 100644 --- a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts +++ b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts @@ -70,7 +70,7 @@ export class MainThreadEditorTabs implements MainThreadEditorTabsShape { this._dispoables.add(this._editorGroupsService.onDidRemoveGroup(() => this._createTabsModel())); // Once everything is read go ahead and initialize the model - this._editorGroupsService.mainPart.whenReady.then(() => this._createTabsModel()); + this._editorGroupsService.whenReady.then(() => this._createTabsModel()); } dispose(): void { diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index 14430b7c668..3d3208a6162 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -39,6 +39,7 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import product from 'vs/platform/product/common/product'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; class InspectContextKeysAction extends Action2 { @@ -467,6 +468,7 @@ class RemoveLargeStorageEntriesAction extends Action2 { const quickInputService = accessor.get(IQuickInputService); const userDataProfileService = accessor.get(IUserDataProfileService); const dialogService = accessor.get(IDialogService); + const environmentService = accessor.get(IEnvironmentService); interface IStorageItem extends IQuickPickItem { readonly key: string; @@ -485,7 +487,7 @@ class RemoveLargeStorageEntriesAction extends Action2 { for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) { for (const key of storageService.keys(scope, target)) { const value = storageService.get(key, scope); - if (value && value.length > RemoveLargeStorageEntriesAction.SIZE_THRESHOLD) { + if (value && (!environmentService.isBuilt /* show all keys in dev */ || value.length > RemoveLargeStorageEntriesAction.SIZE_THRESHOLD)) { items.push({ key, scope, diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 5c5f96531a9..4c142fb8246 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -227,7 +227,7 @@ export class WorkbenchContextKeysHandler extends Disposable { } private registerListeners(): void { - this.editorGroupService.mainPart.whenReady.then(() => { + this.editorGroupService.whenReady.then(() => { this.updateEditorAreaContextKeys(); this.updateEditorContextKeys(); }); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index a4c1c34b1a4..4d64eeb391c 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -333,7 +333,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Wait to register these listeners after the editor group service // is ready to avoid conflicts on startup - this.editorGroupService.mainPart.whenRestored.then(() => { + this.editorGroupService.whenRestored.then(() => { // Restore main editor part on any editor change in main part this._register(this.mainPartEditorService.onDidVisibleEditorsChange(showEditorIfHidden)); @@ -494,7 +494,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.updateMenubarVisibility(!!skipLayout); // Centered Layout - this.editorGroupService.mainPart.whenRestored.then(() => { + this.editorGroupService.whenRestored.then(() => { this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED), skipLayout); }); } @@ -781,7 +781,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Empty workbench configured to open untitled file if empty else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && this.configurationService.getValue('workbench.startupEditor') === 'newUntitledFile') { - if (this.editorGroupService.mainPart.hasRestorableState) { + if (this.editorGroupService.hasRestorableState) { return []; // do not open any empty untitled file if we restored groups/editors from previous session } @@ -854,7 +854,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi mark('code/willRestoreEditors'); // first ensure the editor part is ready - await this.editorGroupService.mainPart.whenReady; + await this.editorGroupService.whenReady; mark('code/restoreEditors/editorGroupsReady'); // apply editor layout if any @@ -910,7 +910,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi layoutRestoredPromises.push( Promise.all([ openEditorsPromise?.finally(() => mark('code/restoreEditors/editorsOpened')), - this.editorGroupService.mainPart.whenRestored.finally(() => mark('code/restoreEditors/editorGroupsRestored')) + this.editorGroupService.whenRestored.finally(() => mark('code/restoreEditors/editorGroupsRestored')) ]).finally(() => { // the `code/didRestoreEditors` perf mark is specifically // for when visible editors have resolved, so we only mark diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index b9e07cb213a..7ae271c65cc 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -12,7 +12,7 @@ import { ISerializableView, IViewSize } from 'vs/base/browser/ui/grid/grid'; import { Event, Emitter } from 'vs/base/common/event'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { assertIsDefined } from 'vs/base/common/types'; -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; export interface IPartOptions { readonly hasTitle?: boolean; @@ -187,7 +187,7 @@ export interface IMultiWindowPart { readonly element: HTMLElement; } -export abstract class MultiWindowParts extends Disposable { +export abstract class MultiWindowParts extends Component { protected readonly _parts = new Set(); get parts() { return Array.from(this._parts); } diff --git a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts index 8d1ea43513e..e6322146585 100644 --- a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts @@ -15,7 +15,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { getTitleBarStyle } from 'vs/platform/window/common/window'; import { IEditorGroupView, IEditorPartsView } from 'vs/workbench/browser/parts/editor/editor'; -import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { EditorPart, IEditorPartUIState } from 'vs/workbench/browser/parts/editor/editorPart'; import { IAuxiliaryTitlebarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; import { WindowTitle } from 'vs/workbench/browser/parts/titlebar/windowTitle'; import { IAuxiliaryWindowOpenOptions, IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService'; @@ -27,6 +27,16 @@ import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecy import { IStatusbarService } from 'vs/workbench/services/statusbar/browser/statusbar'; import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; +export interface IAuxiliaryEditorPartOpenOptions extends IAuxiliaryWindowOpenOptions { + readonly state?: IEditorPartUIState; +} + +export interface ICreateAuxiliaryEditorPartResult { + readonly part: AuxiliaryEditorPartImpl; + readonly instantiationService: IInstantiationService; + readonly disposables: DisposableStore; +} + export class AuxiliaryEditorPart { private static STATUS_BAR_VISIBILITY = 'workbench.statusBar.visible'; @@ -44,7 +54,7 @@ export class AuxiliaryEditorPart { ) { } - async create(label: string, options?: IAuxiliaryWindowOpenOptions): Promise<{ readonly part: AuxiliaryEditorPartImpl; readonly instantiationService: IInstantiationService; readonly disposables: DisposableStore }> { + async create(label: string, options?: IAuxiliaryEditorPartOpenOptions): Promise { function computeEditorPartHeightOffset(): number { let editorPartHeightOffset = 0; @@ -90,9 +100,9 @@ export class AuxiliaryEditorPart { editorPartContainer.style.position = 'relative'; auxiliaryWindow.container.appendChild(editorPartContainer); - const editorPart = disposables.add(this.instantiationService.createInstance(AuxiliaryEditorPartImpl, auxiliaryWindow.window.vscodeWindowId, this.editorPartsView, label)); + const editorPart = disposables.add(this.instantiationService.createInstance(AuxiliaryEditorPartImpl, auxiliaryWindow.window.vscodeWindowId, this.editorPartsView, options?.state, label)); disposables.add(this.editorPartsView.registerPart(editorPart)); - editorPart.create(editorPartContainer, { restorePreviousState: false }); + editorPart.create(editorPartContainer); // Titlebar let titlebarPart: IAuxiliaryTitlebarPart | undefined = undefined; @@ -140,7 +150,7 @@ export class AuxiliaryEditorPart { // Lifecycle const editorCloseListener = disposables.add(Event.once(editorPart.onWillClose)(() => auxiliaryWindow.window.close())); - disposables.add(Event.once(auxiliaryWindow.onWillClose)(() => { + disposables.add(Event.once(auxiliaryWindow.onUnload)(() => { if (disposables.isDisposed) { return; // the close happened as part of an earlier dispose call } @@ -187,6 +197,7 @@ class AuxiliaryEditorPartImpl extends EditorPart implements IAuxiliaryEditorPart constructor( readonly windowId: number, editorPartsView: IEditorPartsView, + private readonly state: IEditorPartUIState | undefined, groupsLabel: string, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @@ -231,8 +242,12 @@ class AuxiliaryEditorPartImpl extends EditorPart implements IAuxiliaryEditorPart this.doClose(false /* do not merge any groups to main part */); } + protected override loadState(): IEditorPartUIState | undefined { + return this.state; + } + protected override saveState(): void { - return; // TODO support auxiliary editor state + return; // disabled, auxiliary editor part state is tracked outside } close(): void { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 64a236f7be4..bb23015b994 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -507,13 +507,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView { options.pinned = this.model.isPinned(activeEditor); // preserve pinned state options.sticky = this.model.isSticky(activeEditor); // preserve sticky state - options.preserveFocus = true; // handle focus after editor is opened + options.preserveFocus = true; // handle focus after editor is restored + + const internalOptions: IInternalEditorOpenOptions = { + preserveWindowOrder: true // handle window order after editor is restored + }; const activeElement = getActiveElement(); // Show active editor (intentionally not using async to keep // `restoreEditors` from executing in same stack) - return this.doShowEditor(activeEditor, { active: true, isNew: false /* restored */ }, options).then(() => { + return this.doShowEditor(activeEditor, { active: true, isNew: false /* restored */ }, options, internalOptions).then(() => { // Set focused now if this is the active group and focus has // not changed meanwhile. This prevents focus from being diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 1590335d17d..8aac8aa2023 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -36,7 +36,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { EditorPartMaximizedEditorGroupContext, EditorPartMultipleEditorGroupsContext, IsAuxiliaryEditorPartContext } from 'vs/workbench/common/contextkeys'; -interface IEditorPartUIState { +export interface IEditorPartUIState { readonly serializedGrid: ISerializedGrid; readonly activeGroup: GroupIdentifier; readonly mostRecentActiveGroups: GroupIdentifier[]; @@ -251,6 +251,9 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { return !!this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY]; } + private _didRestoreState = false; + get didRestoreState(): boolean { return this._didRestoreState; } + getGroups(order = GroupsOrder.CREATION_TIME): IEditorGroupView[] { switch (order) { case GroupsOrder.CREATION_TIME: @@ -1167,7 +1170,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { } private doCreateGridControlWithPreviousState(): boolean { - const uiState: IEditorPartUIState = this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY]; + const uiState: IEditorPartUIState | undefined = this.loadState(); if (uiState?.serializedGrid) { try { @@ -1177,8 +1180,8 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { // Grid Widget this.doCreateGridControlWithState(uiState.serializedGrid, uiState.activeGroup); - // Ensure last active group has focus - this._activeGroup.focus(); + // Remember that we did restore previous state + this._didRestoreState = true; } catch (error) { // Log error @@ -1312,16 +1315,10 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { // Persist grid UI state if (this.gridWidget) { - const uiState: IEditorPartUIState = { - serializedGrid: this.gridWidget.serialize(), - activeGroup: this._activeGroup.id, - mostRecentActiveGroups: this.mostRecentActiveGroups - }; - if (this.isEmpty) { delete this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY]; } else { - this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] = uiState; + this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] = this.createState(); } } @@ -1338,6 +1335,18 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { super.saveState(); } + protected loadState(): IEditorPartUIState | undefined { + return this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY]; + } + + createState(): IEditorPartUIState { + return { + serializedGrid: this.gridWidget.serialize(), + activeGroup: this._activeGroup.id, + mostRecentActiveGroups: this.mostRecentActiveGroups + }; + } + toJSON(): object { return { type: Parts.EDITOR_PART diff --git a/src/vs/workbench/browser/parts/editor/editorParts.ts b/src/vs/workbench/browser/parts/editor/editorParts.ts index 155d6d153d2..7f36225bf2c 100644 --- a/src/vs/workbench/browser/parts/editor/editorParts.ts +++ b/src/vs/workbench/browser/parts/editor/editorParts.ts @@ -8,14 +8,28 @@ import { EditorGroupLayout, GroupDirection, GroupLocation, GroupOrientation, Gro import { Emitter } from 'vs/base/common/event'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { GroupIdentifier } from 'vs/workbench/common/editor'; -import { EditorPart, MainEditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { EditorPart, IEditorPartUIState, MainEditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { IEditorGroupView, IEditorPartsView } from 'vs/workbench/browser/parts/editor/editor'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IAuxiliaryWindowOpenOptions } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService'; -import { distinct } from 'vs/base/common/arrays'; -import { AuxiliaryEditorPart } from 'vs/workbench/browser/parts/editor/auxiliaryEditorPart'; +import { distinct, firstOrDefault } from 'vs/base/common/arrays'; +import { AuxiliaryEditorPart, IAuxiliaryEditorPartOpenOptions } from 'vs/workbench/browser/parts/editor/auxiliaryEditorPart'; import { MultiWindowParts } from 'vs/workbench/browser/part'; +import { DeferredPromise } from 'vs/base/common/async'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IRectangle } from 'vs/platform/window/common/window'; +import { getWindow } from 'vs/base/browser/dom'; + +interface IEditorPartsUIState { + readonly auxiliary: IAuxiliaryEditorPartState[]; + readonly mru: number[]; +} + +interface IAuxiliaryEditorPartState { + readonly state: IEditorPartUIState; + readonly bounds?: IRectangle; +} export class EditorParts extends MultiWindowParts implements IEditorGroupsService, IEditorPartsView { @@ -23,14 +37,18 @@ export class EditorParts extends MultiWindowParts implements IEditor readonly mainPart = this._register(this.createMainEditorPart()); - private readonly mostRecentActiveParts = [this.mainPart]; + private mostRecentActiveParts = [this.mainPart]; constructor( - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IStorageService storageService: IStorageService, + @IThemeService themeService: IThemeService ) { - super(); + super('workbench.editorParts', themeService, storageService); this._register(this.registerPart(this.mainPart)); + + this.restoreParts(); } protected createMainEditorPart(): MainEditorPart { @@ -42,7 +60,7 @@ export class EditorParts extends MultiWindowParts implements IEditor private readonly _onDidCreateAuxiliaryEditorPart = this._register(new Emitter()); readonly onDidCreateAuxiliaryEditorPart = this._onDidCreateAuxiliaryEditorPart.event; - async createAuxiliaryEditorPart(options?: IAuxiliaryWindowOpenOptions): Promise { + async createAuxiliaryEditorPart(options?: IAuxiliaryEditorPartOpenOptions): Promise { const { part, instantiationService, disposables } = await this.instantiationService.createInstance(AuxiliaryEditorPart, this).create(this.getGroupsLabel(this._parts.size), options); // Events @@ -156,6 +174,108 @@ export class EditorParts extends MultiWindowParts implements IEditor //#endregion + //#region Lifecycle / State + + private static readonly EDITOR_PARTS_UI_STATE_STORAGE_KEY = 'editorparts.state'; + + private readonly workspaceMemento = this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); + + private _isReady = false; + get isReady(): boolean { return this._isReady; } + + private readonly whenReadyPromise = new DeferredPromise(); + readonly whenReady = this.whenReadyPromise.p; + + private readonly whenRestoredPromise = new DeferredPromise(); + readonly whenRestored = this.whenRestoredPromise.p; + + private async restoreParts(): Promise { + + // Join on the main part being ready to pick + // the right moment to begin restoring. + // The main part is automatically being created + // as part of the overall startup process. + await this.mainPart.whenReady; + + // Only attempt to restore auxiliary editor parts + // when the main part did restore. It is possible + // that restoring was not attempted because specific + // editors were opened. + if (this.mainPart.didRestoreState) { + const uiState: IEditorPartsUIState | undefined = this.workspaceMemento[EditorParts.EDITOR_PARTS_UI_STATE_STORAGE_KEY]; + if (uiState?.auxiliary.length) { + const auxiliaryEditorPartPromises: Promise[] = []; + + // Create auxiliary editor parts + for (const auxiliaryEditorPartState of uiState.auxiliary) { + auxiliaryEditorPartPromises.push(this.createAuxiliaryEditorPart({ + bounds: auxiliaryEditorPartState.bounds, + state: auxiliaryEditorPartState.state + })); + } + + // Await creation + await Promise.allSettled(auxiliaryEditorPartPromises); + + // Update MRU list + if (uiState.mru.length === this.parts.length) { + this.mostRecentActiveParts = uiState.mru.map(index => this.parts[index]); + } else { + this.mostRecentActiveParts = [...this.parts]; + } + } + } + + // Await ready + await Promise.allSettled(this.parts.map(part => part.whenReady)); + + const mostRecentActivePart = firstOrDefault(this.mostRecentActiveParts); + mostRecentActivePart?.activeGroup.focus(); + + this._isReady = true; + this.whenReadyPromise.complete(); + + // Await restored + await Promise.allSettled(this.parts.map(part => part.whenRestored)); + this.whenRestoredPromise.complete(); + } + + protected override saveState(): void { + const uiState: IEditorPartsUIState = { + auxiliary: this.parts.filter(part => part !== this.mainPart).map(part => { + return { + state: part.createState(), + bounds: (() => { + const auxiliaryWindow = getWindow(part.getContainer()); + if (auxiliaryWindow) { + return { + x: auxiliaryWindow.screenX, + y: auxiliaryWindow.screenY, + width: auxiliaryWindow.outerWidth, + height: auxiliaryWindow.outerHeight + }; + } + + return undefined; + })() + }; + }), + mru: this.mostRecentActiveParts.map(part => this.parts.indexOf(part)) + }; + + if (uiState.auxiliary.length === 0) { + delete this.workspaceMemento[EditorParts.EDITOR_PARTS_UI_STATE_STORAGE_KEY]; + } else { + this.workspaceMemento[EditorParts.EDITOR_PARTS_UI_STATE_STORAGE_KEY] = uiState; + } + } + + get hasRestorableState(): boolean { + return this.parts.some(part => part.hasRestorableState); + } + + //#endregion + //#region Events private readonly _onDidActiveGroupChange = this._register(new Emitter()); diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index e8168535369..c6a09b4fcfd 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -460,7 +460,7 @@ export class EditorsObserver extends Disposable { private async loadState(): Promise { if (this.editorGroupsContainer === this.editorGroupService.mainPart || this.editorGroupsContainer === this.editorGroupService) { - await this.editorGroupService.mainPart.whenReady; + await this.editorGroupService.whenReady; } // Previous state: Load editors map from persisted state diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 98f233b324c..568c01bf5a1 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -708,9 +708,11 @@ export class StatusbarService extends MultiWindowParts implements readonly mainPart = this._register(this.instantiationService.createInstance(MainStatusbarPart)); constructor( - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IStorageService storageService: IStorageService, + @IThemeService themeService: IThemeService ) { - super(); + super('workbench.statusBarService', themeService, storageService); this._register(this.registerPart(this.mainPart)); } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index df69f28b7c5..1f98cb50ef7 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -81,9 +81,11 @@ export class BrowserTitleService extends MultiWindowParts i readonly mainPart = this._register(this.createMainTitlebarPart()); constructor( - @IInstantiationService protected readonly instantiationService: IInstantiationService + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IStorageService storageService: IStorageService, + @IThemeService themeService: IThemeService ) { - super(); + super('workbench.titleService', themeService, storageService); this._register(this.registerPart(this.mainPart)); } diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts index b45c469242f..e5baf7c6af3 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts @@ -77,7 +77,7 @@ export class NotebookEditorWidgetService implements INotebookEditorService { groupListener.set(id, listeners); }; this._disposables.add(editorGroupService.onDidAddGroup(onNewGroup)); - editorGroupService.mainPart.whenReady.then(() => editorGroupService.groups.forEach(onNewGroup)); + editorGroupService.whenReady.then(() => editorGroupService.groups.forEach(onNewGroup)); // group removed -> clean up listeners, clean up widgets this._disposables.add(editorGroupService.onDidRemoveGroup(group => { diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts index cc01ea4ed52..8d49c0221a1 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts @@ -36,7 +36,7 @@ class WebviewPanelContribution extends Disposable implements IWorkbenchContribut super(); // Add all the initial groups to be listened to - this.editorGroupService.mainPart.whenReady.then(() => this.editorGroupService.groups.forEach(group => { + this.editorGroupService.whenReady.then(() => this.editorGroupService.groups.forEach(group => { this.registerGroupListener(group); })); diff --git a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts index 3bb124ffa6f..bcb5c79f51b 100644 --- a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts @@ -45,7 +45,10 @@ export interface IAuxiliaryWindowService { export interface IAuxiliaryWindow extends IDisposable { readonly onDidLayout: Event; - readonly onWillClose: Event; + + readonly onBeforeUnload: Event; + readonly onUnload: Event; + readonly whenStylesHaveLoaded: Promise; readonly window: CodeWindow; @@ -59,8 +62,11 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { private readonly _onDidLayout = this._register(new Emitter()); readonly onDidLayout = this._onDidLayout.event; - private readonly _onWillClose = this._register(new Emitter()); - readonly onWillClose = this._onWillClose.event; + private readonly _onBeforeUnload = this._register(new Emitter()); + readonly onBeforeUnload = this._onBeforeUnload.event; + + private readonly _onUnload = this._register(new Emitter()); + readonly onUnload = this._onUnload.event; private readonly _onWillDispose = this._register(new Emitter()); readonly onWillDispose = this._onWillDispose.event; @@ -80,8 +86,8 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { } private registerListeners(): void { - this._register(addDisposableListener(this.window, EventType.BEFORE_UNLOAD, (e: BeforeUnloadEvent) => this.onBeforeUnload(e))); - this._register(addDisposableListener(this.window, EventType.UNLOAD, () => this._onWillClose.fire())); + this._register(addDisposableListener(this.window, EventType.BEFORE_UNLOAD, (e: BeforeUnloadEvent) => this.handleBeforeUnload(e))); + this._register(addDisposableListener(this.window, EventType.UNLOAD, () => this.handleUnload())); this._register(addDisposableListener(this.window, 'unhandledrejection', e => { onUnexpectedError(e.reason); @@ -108,7 +114,12 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { } } - private onBeforeUnload(e: BeforeUnloadEvent): void { + private handleBeforeUnload(e: BeforeUnloadEvent): void { + + // Event + this._onBeforeUnload.fire(); + + // Check for confirm before close setting const confirmBeforeCloseSetting = this.configurationService.getValue<'always' | 'never' | 'keyboardOnly'>('window.confirmBeforeClose'); const confirmBeforeClose = confirmBeforeCloseSetting === 'always' || (confirmBeforeCloseSetting === 'keyboardOnly' && ModifierKeyEmitter.getInstance().isModifierPressed); if (confirmBeforeClose) { @@ -121,6 +132,12 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { e.returnValue = localize('lifecycleVeto', "Changes that you made may not be saved. Please check press 'Cancel' and try again."); } + private handleUnload(): void { + + // Event + this._onUnload.fire(); + } + layout(): void { this._onDidLayout.fire(getClientArea(this.window.document.body, this.container)); } @@ -224,8 +241,8 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili const height = Math.max(options?.bounds?.height ?? BrowserAuxiliaryWindowService.DEFAULT_SIZE.height, WindowMinimumSize.HEIGHT); let newWindowBounds: IRectangle = { - x: options?.bounds?.x ?? (activeWindowBounds.x + activeWindowBounds.width / 2 - width / 2), - y: options?.bounds?.y ?? (activeWindowBounds.y + activeWindowBounds.height / 2 - height / 2), + x: options?.bounds?.x ?? Math.max(activeWindowBounds.x + activeWindowBounds.width / 2 - width / 2, 0), + y: options?.bounds?.y ?? Math.max(activeWindowBounds.y + activeWindowBounds.height / 2 - height / 2, 0), width, height }; diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 854441c1122..06675aace81 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -94,7 +94,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Editor & group changes if (this.editorGroupsContainer === this.editorGroupService.mainPart || this.editorGroupsContainer === this.editorGroupService) { - this.editorGroupService.mainPart.whenReady.then(() => this.onEditorGroupsReady()); + this.editorGroupService.whenReady.then(() => this.onEditorGroupsReady()); } else { this.onEditorGroupsReady(); } diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index a0d5a1bafb1..3ab10751b59 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -221,6 +221,42 @@ export interface IEditorGroupsContainer { */ readonly onDidChangeGroupMaximized: Event; + /** + * A property that indicates when groups have been created + * and are ready to be used in the editor part. + */ + readonly isReady: boolean; + + /** + * A promise that resolves when groups have been created + * and are ready to be used in the editor part. + * + * Await this promise to safely work on the editor groups model + * (for example, install editor group listeners). + * + * Use the `whenRestored` property to await visible editors + * having fully resolved. + */ + readonly whenReady: Promise; + + /** + * A promise that resolves when groups have been restored in + * the editor part. + * + * For groups with active editor, the promise will resolve + * when the visible editor has finished to resolve. + * + * Use the `whenReady` property to not await editors to + * resolve. + */ + readonly whenRestored: Promise; + + /** + * Find out if the editor part has UI state to restore + * from a previous session. + */ + readonly hasRestorableState: boolean; + /** * An active group is the default location for new editors to open. */ @@ -412,42 +448,6 @@ export interface IEditorPart extends IEditorGroupsContainer { */ readonly contentDimension: IDimension; - /** - * A property that indicates when groups have been created - * and are ready to be used in the editor part. - */ - readonly isReady: boolean; - - /** - * A promise that resolves when groups have been created - * and are ready to be used in the editor part. - * - * Await this promise to safely work on the editor groups model - * (for example, install editor group listeners). - * - * Use the `whenRestored` property to await visible editors - * having fully resolved. - */ - readonly whenReady: Promise; - - /** - * A promise that resolves when groups have been restored in - * the editor part. - * - * For groups with active editor, the promise will resolve - * when the visible editor has finished to resolve. - * - * Use the `whenReady` property to not await editors to - * resolve. - */ - readonly whenRestored: Promise; - - /** - * Find out if the editor part has UI state to restore - * from a previous session. - */ - readonly hasRestorableState: boolean; - /** * Find out if an editor group is currently maximized. */ diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts index d417eacada2..18062624a78 100644 --- a/src/vs/workbench/services/history/browser/historyService.ts +++ b/src/vs/workbench/services/history/browser/historyService.ts @@ -939,11 +939,11 @@ export class HistoryService extends Disposable implements IHistoryService { // We want to seed history from opened editors // too as well as previous stored state, so we // need to wait for the editor groups being ready - if (this.editorGroupService.mainPart.isReady) { + if (this.editorGroupService.isReady) { this.loadHistory(); } else { (async () => { - await this.editorGroupService.mainPart.whenReady; + await this.editorGroupService.whenReady; this.loadHistory(); })(); From 6bda74d83aaea9ae13c737629e4d23fd34a0e0ac Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 22 Dec 2023 15:40:32 +0100 Subject: [PATCH 0025/1897] aux window - track fullscreen state per window (#197325) --- src/vs/base/browser/browser.ts | 57 +++++++++++-------- src/vs/base/browser/ui/menu/menubar.ts | 3 +- .../browser/actions/layoutActions.ts | 4 +- .../browser/actions/windowActions.ts | 4 +- src/vs/workbench/browser/contextkeys.ts | 13 +++-- src/vs/workbench/browser/layout.ts | 55 +++++++++--------- .../parts/editor/auxiliaryEditorPart.ts | 6 +- .../browser/parts/titlebar/menubarControl.ts | 12 ++-- src/vs/workbench/browser/web.main.ts | 2 +- src/vs/workbench/browser/window.ts | 33 ++++++----- src/vs/workbench/common/contextkeys.ts | 2 +- .../contrib/splash/browser/partsSplash.ts | 4 +- .../electron-sandbox/desktop.main.ts | 2 +- src/vs/workbench/electron-sandbox/window.ts | 10 ++-- .../browser/auxiliaryWindowService.ts | 9 ++- .../auxiliaryWindowService.ts | 13 +++-- .../keybinding/browser/keybindingService.ts | 10 +++- .../services/layout/browser/layoutService.ts | 5 -- src/vs/workbench/test/browser/window.test.ts | 3 +- .../test/browser/workbenchTestServices.ts | 1 - 20 files changed, 139 insertions(+), 109 deletions(-) diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index de37d415e03..64b0984693f 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -3,21 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $window } from 'vs/base/browser/window'; +import { $window, CodeWindow } from 'vs/base/browser/window'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, markAsSingleton } from 'vs/base/common/lifecycle'; class WindowManager { - public static readonly INSTANCE = new WindowManager(); + static readonly INSTANCE = new WindowManager(); // --- Zoom Level + private _zoomLevel: number = 0; - public getZoomLevel(): number { + getZoomLevel(): number { return this._zoomLevel; } - public setZoomLevel(zoomLevel: number): void { + setZoomLevel(zoomLevel: number): void { if (this._zoomLevel === zoomLevel) { return; } @@ -25,30 +26,38 @@ class WindowManager { } // --- Zoom Factor + private _zoomFactor: number = 1; - public getZoomFactor(): number { + getZoomFactor(): number { return this._zoomFactor; } - public setZoomFactor(zoomFactor: number): void { + setZoomFactor(zoomFactor: number): void { this._zoomFactor = zoomFactor; } // --- Fullscreen - private _fullscreen: boolean = false; - private readonly _onDidChangeFullscreen = new Emitter(); - public readonly onDidChangeFullscreen: Event = this._onDidChangeFullscreen.event; - public setFullscreen(fullscreen: boolean): void { - if (this._fullscreen === fullscreen) { + private readonly _onDidChangeFullscreen = new Emitter(); + readonly onDidChangeFullscreen = this._onDidChangeFullscreen.event; + + private readonly mapWindowIdToFullScreen = new Map(); + + setFullscreen(fullscreen: boolean, targetWindow: Window): void { + if (this.isFullscreen(targetWindow) === fullscreen) { return; } - this._fullscreen = fullscreen; - this._onDidChangeFullscreen.fire(); + const windowId = this.getWindowId(targetWindow); + this.mapWindowIdToFullScreen.set(windowId, fullscreen); + this._onDidChangeFullscreen.fire(windowId); } - public isFullscreen(): boolean { - return this._fullscreen; + isFullscreen(targetWindow: Window): boolean { + return !!this.mapWindowIdToFullScreen.get(this.getWindowId(targetWindow)); + } + + private getWindowId(targetWindow: Window): number { + return (targetWindow as CodeWindow).vscodeWindowId; } } @@ -58,7 +67,7 @@ class WindowManager { class DevicePixelRatioMonitor extends Disposable { private readonly _onDidChange = this._register(new Emitter()); - public readonly onDidChange = this._onDidChange.event; + readonly onDidChange = this._onDidChange.event; private readonly _listener: () => void; private _mediaQueryList: MediaQueryList | null; @@ -86,11 +95,11 @@ class DevicePixelRatioMonitor extends Disposable { class PixelRatioImpl extends Disposable { private readonly _onDidChange = this._register(new Emitter()); - public readonly onDidChange = this._onDidChange.event; + readonly onDidChange = this._onDidChange.event; private _value: number; - public get value(): number { + get value(): number { return this._value; } @@ -131,14 +140,14 @@ class PixelRatioFacade { /** * Get the current value. */ - public get value(): number { + get value(): number { return this._getOrCreatePixelRatioMonitor().value; } /** * Listen for changes. */ - public get onDidChange(): Event { + get onDidChange(): Event { return this._getOrCreatePixelRatioMonitor().onDidChange; } } @@ -175,11 +184,11 @@ export function setZoomFactor(zoomFactor: number): void { WindowManager.INSTANCE.setZoomFactor(zoomFactor); } -export function setFullscreen(fullscreen: boolean): void { - WindowManager.INSTANCE.setFullscreen(fullscreen); +export function setFullscreen(fullscreen: boolean, targetWindow: Window): void { + WindowManager.INSTANCE.setFullscreen(fullscreen, targetWindow); } -export function isFullscreen(): boolean { - return WindowManager.INSTANCE.isFullscreen(); +export function isFullscreen(targetWindow: Window): boolean { + return WindowManager.INSTANCE.isFullscreen(targetWindow); } export const onDidChangeFullscreen = WindowManager.INSTANCE.onDidChangeFullscreen; diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 2e58738bdab..feadeb47651 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -22,6 +22,7 @@ import { isMacintosh } from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import 'vs/css!./menubar'; import * as nls from 'vs/nls'; +import { mainWindow } from 'vs/base/browser/window'; const $ = DOM.$; @@ -786,7 +787,7 @@ export class MenuBar extends Disposable { private setUnfocusedState(): void { if (this.options.visibility === 'toggle' || this.options.visibility === 'hidden') { this.focusState = MenubarState.HIDDEN; - } else if (this.options.visibility === 'classic' && browser.isFullscreen()) { + } else if (this.options.visibility === 'classic' && browser.isFullscreen(mainWindow)) { this.focusState = MenubarState.HIDDEN; } else { this.focusState = MenubarState.VISIBLE; diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index a3bacf4ad5c..a8df6a29409 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -21,7 +21,7 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { ToggleAuxiliaryBarAction } from 'vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions'; import { TogglePanelAction } from 'vs/workbench/browser/parts/panel/panelActions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { AuxiliaryBarVisibleContext, PanelAlignmentContext, PanelVisibleContext, SideBarVisibleContext, FocusedViewContext, InEditorZenModeContext, IsCenteredLayoutContext, MainEditorAreaVisibleContext, IsFullscreenContext, PanelPositionContext, IsAuxiliaryWindowFocusedContext } from 'vs/workbench/common/contextkeys'; +import { AuxiliaryBarVisibleContext, PanelAlignmentContext, PanelVisibleContext, SideBarVisibleContext, FocusedViewContext, InEditorZenModeContext, IsCenteredLayoutContext, MainEditorAreaVisibleContext, IsMainWindowFullscreenContext, PanelPositionContext, IsAuxiliaryWindowFocusedContext } from 'vs/workbench/common/contextkeys'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -1387,7 +1387,7 @@ const AlignPanelActions: CustomizeLayoutItem[] = [ ]; const MiscLayoutOptions: CustomizeLayoutItem[] = [ - CreateOptionLayoutItem('workbench.action.toggleFullScreen', IsFullscreenContext, localize('fullscreen', "Full Screen"), fullscreenIcon), + CreateOptionLayoutItem('workbench.action.toggleFullScreen', IsMainWindowFullscreenContext, localize('fullscreen', "Full Screen"), fullscreenIcon), CreateOptionLayoutItem('workbench.action.toggleZenMode', InEditorZenModeContext, localize('zenMode', "Zen Mode"), zenModeIcon), CreateOptionLayoutItem('workbench.action.toggleCenteredLayout', IsCenteredLayoutContext, localize('centeredLayout', "Centered Layout"), centerLayoutIcon), ]; diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 14c7b8aa01f..24aefca0450 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -8,7 +8,7 @@ import { IWindowOpenable } from 'vs/platform/window/common/window'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { MenuRegistry, MenuId, Action2, registerAction2, IAction2Options } from 'vs/platform/actions/common/actions'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IsAuxiliaryWindowFocusedContext, IsFullscreenContext } from 'vs/workbench/common/contextkeys'; +import { IsAuxiliaryWindowFocusedContext, IsMainWindowFullscreenContext } from 'vs/workbench/common/contextkeys'; import { IsMacNativeContext, IsDevelopmentContext, IsWebContext, IsIOSContext } from 'vs/platform/contextkey/common/contextkeys'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -298,7 +298,7 @@ class ToggleFullScreenAction extends Action2 { } }, precondition: IsIOSContext.toNegated(), - toggled: IsFullscreenContext, + toggled: IsMainWindowFullscreenContext, menu: [{ id: MenuId.MenubarAppearanceMenu, group: '1_toggle_view', diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 4c142fb8246..c10da3b722c 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from 'vs/platform/contextkey/common/contextkeys'; -import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, MainEditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, TitleBarVisibleContext, TitleBarStyleContext, MultipleEditorGroupsContext, IsAuxiliaryWindowFocusedContext, ActiveCompareEditorOriginalWriteableContext } from 'vs/workbench/common/contextkeys'; +import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, MainEditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, TitleBarVisibleContext, TitleBarStyleContext, MultipleEditorGroupsContext, IsAuxiliaryWindowFocusedContext, ActiveCompareEditorOriginalWriteableContext } from 'vs/workbench/common/contextkeys'; import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType, onDidRegisterWindow, getActiveWindow } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -28,6 +28,7 @@ import { FileSystemProviderCapabilities, IFileService } from 'vs/platform/files/ import { getTitleBarStyle } from 'vs/platform/window/common/window'; import { mainWindow } from 'vs/base/browser/window'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { isFullscreen, onDidChangeFullscreen } from 'vs/base/browser/browser'; export class WorkbenchContextKeysHandler extends Disposable { private inputFocusedContext: IContextKey; @@ -68,7 +69,7 @@ export class WorkbenchContextKeysHandler extends Disposable { private temporaryWorkspaceContext: IContextKey; private inZenModeContext: IContextKey; - private isFullscreenContext: IContextKey; + private isMainWindowFullscreenContext: IContextKey; private isAuxiliaryWindowFocusedContext: IContextKey; private isCenteredLayoutContext: IContextKey; private sideBarVisibleContext: IContextKey; @@ -188,7 +189,7 @@ export class WorkbenchContextKeysHandler extends Disposable { this.updateSplitEditorsVerticallyContext(); // Window - this.isFullscreenContext = IsFullscreenContext.bindTo(this.contextKeyService); + this.isMainWindowFullscreenContext = IsMainWindowFullscreenContext.bindTo(this.contextKeyService); this.isAuxiliaryWindowFocusedContext = IsAuxiliaryWindowFocusedContext.bindTo(this.contextKeyService); // Zen Mode @@ -260,7 +261,11 @@ export class WorkbenchContextKeysHandler extends Disposable { this._register(this.layoutService.onDidChangeZenMode(enabled => this.inZenModeContext.set(enabled))); this._register(this.layoutService.onDidChangeActiveContainer(() => this.isAuxiliaryWindowFocusedContext.set(this.layoutService.activeContainer !== this.layoutService.mainContainer))); - this._register(this.layoutService.onDidChangeFullscreen(fullscreen => this.isFullscreenContext.set(fullscreen))); + this._register(onDidChangeFullscreen(windowId => { + if (windowId === mainWindow.vscodeWindowId) { + this.isMainWindowFullscreenContext.set(isFullscreen(mainWindow)); + } + })); this._register(this.layoutService.onDidChangeCenteredLayout(centered => this.isCenteredLayoutContext.set(centered))); this._register(this.layoutService.onDidChangePanelPosition(position => this.panelPositionContext.set(position))); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 4d64eeb391c..bef47d88deb 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -53,7 +53,7 @@ import { mainWindow } from 'vs/base/browser/window'; interface ILayoutRuntimeState { activeContainerId: number; - fullscreen: boolean; + mainWindowFullscreen: boolean; readonly maximized: Set; hasFocus: boolean; mainWindowBorder: boolean; @@ -125,9 +125,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private readonly _onDidChangeZenMode = this._register(new Emitter()); readonly onDidChangeZenMode = this._onDidChangeZenMode.event; - private readonly _onDidChangeFullscreen = this._register(new Emitter()); - readonly onDidChangeFullscreen = this._onDidChangeFullscreen.event; - private readonly _onDidChangeCenteredLayout = this._register(new Emitter()); readonly onDidChangeCenteredLayout = this._onDidChangeCenteredLayout.event; @@ -358,7 +355,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi })); // Fullscreen changes - this._register(onDidChangeFullscreen(() => this.onFullscreenChanged())); + this._register(onDidChangeFullscreen(windowId => this.onFullscreenChanged(windowId))); // Group changes this._register(this.editorGroupService.mainPart.onDidAddGroup(() => this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED)))); @@ -408,7 +405,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // The menu bar toggles the title bar in full screen for toggle and classic settings - else if (this.state.runtime.fullscreen && (menuBarVisibility === 'toggle' || menuBarVisibility === 'classic')) { + else if (this.state.runtime.mainWindowFullscreen && (menuBarVisibility === 'toggle' || menuBarVisibility === 'classic')) { this.workbenchGrid.setViewVisible(this.titleBarPartView, this.shouldShowTitleBar()); } @@ -431,11 +428,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._onDidLayoutContainer.fire({ container, dimension }); } - private onFullscreenChanged(): void { - this.state.runtime.fullscreen = isFullscreen(); + private onFullscreenChanged(windowId: number): void { + if (windowId !== mainWindow.vscodeWindowId) { + return; // ignore all but main window + } + + this.state.runtime.mainWindowFullscreen = isFullscreen(mainWindow); // Apply as CSS class - if (this.state.runtime.fullscreen) { + if (this.state.runtime.mainWindowFullscreen) { this.mainContainer.classList.add(LayoutClasses.FULLSCREEN); } else { this.mainContainer.classList.remove(LayoutClasses.FULLSCREEN); @@ -448,9 +449,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Change edge snapping accordingly - this.workbenchGrid.edgeSnapping = this.state.runtime.fullscreen; + this.workbenchGrid.edgeSnapping = this.state.runtime.mainWindowFullscreen; - // Changing fullscreen state of the window has an impact + // Changing fullscreen state of the main window has an impact // on custom title bar visibility, so we need to update if (getTitleBarStyle(this.configurationService) === 'custom') { @@ -459,8 +460,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.updateWindowsBorder(true); } - - this._onDidChangeFullscreen.fire(this.state.runtime.fullscreen); } private onActiveWindowChanged(): void { @@ -554,7 +553,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const containerWindowId = getWindowId(getWindow(container)); let windowBorder = false; - if (!this.state.runtime.fullscreen && !this.state.runtime.maximized.has(containerWindowId) && (activeBorder || inactiveBorder)) { + if (!this.state.runtime.mainWindowFullscreen && !this.state.runtime.maximized.has(containerWindowId) && (activeBorder || inactiveBorder)) { windowBorder = true; // If the inactive color is missing, fallback to the active one @@ -629,7 +628,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout Runtime State const layoutRuntimeState: ILayoutRuntimeState = { activeContainerId: this.getActiveContainerId(), - fullscreen: isFullscreen(), + mainWindowFullscreen: isFullscreen(mainWindow), hasFocus: this.hostService.hasFocus, maximized: new Set(), mainWindowBorder: false, @@ -1237,23 +1236,23 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // macOS desktop does not need a title bar when full screen if (isMacintosh && isNative) { - return !this.state.runtime.fullscreen; + return !this.state.runtime.mainWindowFullscreen; } // non-fullscreen native must show the title bar - if (isNative && !this.state.runtime.fullscreen) { + if (isNative && !this.state.runtime.mainWindowFullscreen) { return true; } // if WCO is visible, we have to show the title bar - if (isWCOEnabled() && !this.state.runtime.fullscreen) { + if (isWCOEnabled() && !this.state.runtime.mainWindowFullscreen) { return true; } // remaining behavior is based on menubar visibility switch (getMenuBarVisibility(this.configurationService)) { case 'classic': - return !this.state.runtime.fullscreen || this.state.runtime.menuBar.toggled; + return !this.state.runtime.mainWindowFullscreen || this.state.runtime.menuBar.toggled; case 'compact': case 'hidden': return false; @@ -1262,7 +1261,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi case 'visible': return true; default: - return isWeb ? false : !this.state.runtime.fullscreen || this.state.runtime.menuBar.toggled; + return isWeb ? false : !this.state.runtime.mainWindowFullscreen || this.state.runtime.menuBar.toggled; } } @@ -1336,17 +1335,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Check if zen mode transitioned to full screen and if now we are out of zen mode // -> we need to go out of full screen (same goes for the centered editor layout) - let toggleFullScreen = false; + let toggleMainWindowFullScreen = false; const config = getZenModeConfiguration(this.configurationService); const zenModeExitInfo = this.stateModel.getRuntimeValue(LayoutStateKeys.ZEN_MODE_EXIT_INFO); // Zen Mode Active if (this.stateModel.getRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE)) { - toggleFullScreen = !this.state.runtime.fullscreen && config.fullScreen && !isIOS; + toggleMainWindowFullScreen = !this.state.runtime.mainWindowFullscreen && config.fullScreen && !isIOS; if (!restoring) { - zenModeExitInfo.transitionedToFullScreen = toggleFullScreen; + zenModeExitInfo.transitionedToFullScreen = toggleMainWindowFullScreen; zenModeExitInfo.transitionedToCenteredEditorLayout = !this.isMainEditorLayoutCentered() && config.centerLayout; zenModeExitInfo.handleNotificationsDoNotDisturbMode = this.notificationService.getFilter() === NotificationsFilter.OFF; zenModeExitInfo.wasVisible.sideBar = this.isVisible(Parts.SIDEBAR_PART); @@ -1461,15 +1460,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.focus(); - toggleFullScreen = zenModeExitInfo.transitionedToFullScreen && this.state.runtime.fullscreen; + toggleMainWindowFullScreen = zenModeExitInfo.transitionedToFullScreen && this.state.runtime.mainWindowFullscreen; } if (!skipLayout) { this.layout(); } - if (toggleFullScreen) { - this.hostService.toggleFullScreen(getActiveWindow()); + if (toggleMainWindowFullScreen) { + this.hostService.toggleFullScreen(mainWindow); } // Event @@ -1531,7 +1530,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.mainContainer.prepend(workbenchGrid.element); this.mainContainer.setAttribute('role', 'application'); this.workbenchGrid = workbenchGrid; - this.workbenchGrid.edgeSnapping = this.state.runtime.fullscreen; + this.workbenchGrid.edgeSnapping = this.state.runtime.mainWindowFullscreen; for (const part of [titleBar, editorPart, activityBar, panelPart, sideBar, statusBar, auxiliaryBarPart, bannerPart]) { this._register(part.onDidVisibilityChange((visible) => { @@ -1738,7 +1737,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi !this.isVisible(Parts.PANEL_PART) ? LayoutClasses.PANEL_HIDDEN : undefined, !this.isVisible(Parts.AUXILIARYBAR_PART) ? LayoutClasses.AUXILIARYBAR_HIDDEN : undefined, !this.isVisible(Parts.STATUSBAR_PART) ? LayoutClasses.STATUSBAR_HIDDEN : undefined, - this.state.runtime.fullscreen ? LayoutClasses.FULLSCREEN : undefined + this.state.runtime.mainWindowFullscreen ? LayoutClasses.FULLSCREEN : undefined ]); } diff --git a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts index e6322146585..272979d0555 100644 --- a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { onDidChangeFullscreen } from 'vs/base/browser/browser'; import { detectFullscreen, hide, show } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -49,8 +50,7 @@ export class AuxiliaryEditorPart { @IConfigurationService private readonly configurationService: IConfigurationService, @IStatusbarService private readonly statusbarService: IStatusbarService, @ITitleService private readonly titleService: ITitleService, - @IEditorService private readonly editorService: IEditorService, - @IHostService private readonly hostService: IHostService + @IEditorService private readonly editorService: IEditorService ) { } @@ -114,7 +114,7 @@ export class AuxiliaryEditorPart { disposables.add(titlebarPart.onDidChange(() => updateEditorPartHeight(true))); - disposables.add(this.hostService.onDidChangeFullScreen(windowId => { + disposables.add(onDidChangeFullscreen(windowId => { if (windowId !== auxiliaryWindow.window.vscodeWindowId) { return; // ignore all but our window } diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 46c8de7dbd7..61bd34e2889 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -28,8 +28,7 @@ import { MenuBar, IMenuBarOptions } from 'vs/base/browser/ui/menu/menubar'; import { Direction } from 'vs/base/browser/ui/menu/menu'; import { mnemonicMenuLabel, unmnemonicLabel } from 'vs/base/common/labels'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { isFullscreen } from 'vs/base/browser/browser'; +import { isFullscreen, onDidChangeFullscreen } from 'vs/base/browser/browser'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -398,7 +397,6 @@ export class CustomMenubarControl extends MenubarControl { @IPreferencesService preferencesService: IPreferencesService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IAccessibilityService accessibilityService: IAccessibilityService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IHostService hostService: IHostService, @ICommandService commandService: ICommandService @@ -535,7 +533,7 @@ export class CustomMenubarControl extends MenubarControl { enableMenuBarMnemonics = true; } - return enableMenuBarMnemonics && (!isWeb || isFullscreen()); + return enableMenuBarMnemonics && (!isWeb || isFullscreen(mainWindow)); } private get currentCompactMenuMode(): Direction | undefined { @@ -798,7 +796,11 @@ export class CustomMenubarControl extends MenubarControl { // Mnemonics require fullscreen in web if (isWeb) { - this._register(this.layoutService.onDidChangeFullscreen(e => this.updateMenubar())); + this._register(onDidChangeFullscreen(windowId => { + if (windowId === mainWindow.vscodeWindowId) { + this.updateMenubar(); + } + })); this._register(this.webNavigationMenu.onDidChange(() => this.updateMenubar())); } } diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 83800a7036d..c49eec92da6 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -113,7 +113,7 @@ export class BrowserMain extends Disposable { private init(): void { // Browser config - setFullscreen(!!detectFullscreen(mainWindow)); + setFullscreen(!!detectFullscreen(mainWindow), mainWindow); } async open(): Promise { diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index 56278f29851..f0db3ec3276 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isSafari, setFullscreen } from 'vs/base/browser/browser'; -import { addDisposableListener, detectFullscreen, EventHelper, EventType, getActiveWindow, getWindow, getWindows, getWindowsCount, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom'; +import { addDisposableListener, detectFullscreen, EventHelper, EventType, getActiveWindow, getWindow, getWindowById, getWindows, getWindowsCount, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { HidDeviceData, requestHidDevice, requestSerialPort, requestUsbDevice, SerialPortData, UsbDeviceData } from 'vs/base/browser/deviceAccess'; import { timeout } from 'vs/base/common/async'; @@ -35,11 +35,17 @@ export abstract class BaseWindow extends Disposable { private static TIMEOUT_HANDLES = Number.MIN_SAFE_INTEGER; // try to not compete with the IDs of native `setTimeout` private static readonly TIMEOUT_DISPOSABLES = new Map>(); - constructor(targetWindow: CodeWindow, dom = { getWindowsCount, getWindows } /* for testing */) { + constructor( + targetWindow: CodeWindow, + dom = { getWindowsCount, getWindows }, /* for testing */ + @IHostService protected readonly hostService: IHostService + ) { super(); this.enableWindowFocusOnElementFocus(targetWindow); this.enableMultiWindowAwareTimeout(targetWindow, dom); + + this.registerFullScreenListeners(targetWindow.vscodeWindowId); } //#region focus handling in multi-window applications @@ -128,6 +134,16 @@ export abstract class BaseWindow extends Disposable { //#endregion + private registerFullScreenListeners(targetWindowId: number): void { + this._register(this.hostService.onDidChangeFullScreen(windowId => { + if (windowId === targetWindowId) { + const targetWindow = getWindowById(targetWindowId); + if (targetWindow) { + setFullscreen(!!detectFullscreen(targetWindow.window), targetWindow.window); + } + } + })); + } } export class BrowserWindow extends BaseWindow { @@ -141,9 +157,9 @@ export class BrowserWindow extends BaseWindow { @IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IHostService private readonly hostService: IHostService + @IHostService hostService: IHostService ) { - super(mainWindow); + super(mainWindow, undefined, hostService); this.registerListeners(); this.create(); @@ -173,15 +189,6 @@ export class BrowserWindow extends BaseWindow { // Prevent default navigation on drop this._register(addDisposableListener(this.layoutService.mainContainer, EventType.DROP, e => EventHelper.stop(e, true))); - - // Fullscreen - this._register(this.hostService.onDidChangeFullScreen(windowId => { - if (windowId !== mainWindow.vscodeWindowId) { - return; // ignore all but main window - } - - setFullscreen(!!detectFullscreen(mainWindow)); - })); } private onWillShutdown(): void { diff --git a/src/vs/workbench/common/contextkeys.ts b/src/vs/workbench/common/contextkeys.ts index fc4a59cb05e..b778b1689df 100644 --- a/src/vs/workbench/common/contextkeys.ts +++ b/src/vs/workbench/common/contextkeys.ts @@ -33,7 +33,7 @@ export const RemoteNameContext = new RawContextKey('remoteName', '', loc export const VirtualWorkspaceContext = new RawContextKey('virtualWorkspace', '', localize('virtualWorkspace', "The scheme of the current workspace is from a virtual file system or an empty string.")); export const TemporaryWorkspaceContext = new RawContextKey('temporaryWorkspace', false, localize('temporaryWorkspace', "The scheme of the current workspace is from a temporary file system.")); -export const IsFullscreenContext = new RawContextKey('isFullscreen', false, localize('isFullscreen', "Whether the window is in fullscreen mode")); +export const IsMainWindowFullscreenContext = new RawContextKey('isFullscreen', false, localize('isFullscreen', "Whether the main window is in fullscreen mode")); export const IsAuxiliaryWindowFocusedContext = new RawContextKey('isAuxiliaryWindowFocusedContext', false, localize('isAuxiliaryWindowFocusedContext', "Whether an auxiliary window is focused")); export const HasWebFileSystemAccess = new RawContextKey('hasWebFileSystemAccess', false, true); // Support for FileSystemAccess web APIs (https://wicg.github.io/file-system-access) diff --git a/src/vs/workbench/contrib/splash/browser/partsSplash.ts b/src/vs/workbench/contrib/splash/browser/partsSplash.ts index 1007270a967..63f2d79012d 100644 --- a/src/vs/workbench/contrib/splash/browser/partsSplash.ts +++ b/src/vs/workbench/contrib/splash/browser/partsSplash.ts @@ -49,7 +49,7 @@ export class PartsSplash { lastIdleSchedule.value = dom.runWhenWindowIdle(mainWindow, () => this._savePartsSplash(), 2500); }; lifecycleService.when(LifecyclePhase.Restored).then(() => { - Event.any(onDidChangeFullscreen, editorGroupsService.mainPart.onDidLayout, _themeService.onDidColorThemeChange)(savePartsSplashSoon, undefined, this._disposables); + Event.any(Event.filter(onDidChangeFullscreen, windowId => windowId === mainWindow.vscodeWindowId), editorGroupsService.mainPart.onDidLayout, _themeService.onDidColorThemeChange)(savePartsSplashSoon, undefined, this._disposables); savePartsSplashSoon(); }); @@ -96,7 +96,7 @@ export class PartsSplash { } private _shouldSaveLayoutInfo(): boolean { - return !isFullscreen() && !this._environmentService.isExtensionDevelopment && !this._didChangeTitleBarStyle; + return !isFullscreen(mainWindow) && !this._environmentService.isExtensionDevelopment && !this._didChangeTitleBarStyle; } private _removePartsSplash(): void { diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index b285e2ed236..0acf57e3448 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -78,7 +78,7 @@ export class DesktopMain extends Disposable { this.reviveUris(); // Apply fullscreen early if configured - setFullscreen(!!this.configuration.fullscreen); + setFullscreen(!!this.configuration.fullscreen, mainWindow); } private reviveUris() { diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index b1388d0b6a6..a0df8c476f2 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -72,6 +72,7 @@ import { IUtilityProcessWorkerWorkbenchService } from 'vs/workbench/services/uti import { registerWindowDriver } from 'vs/workbench/services/driver/electron-sandbox/driver'; import { mainWindow } from 'vs/base/browser/window'; import { BaseWindow } from 'vs/workbench/browser/window'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class NativeWindow extends BaseWindow { @@ -124,9 +125,10 @@ export class NativeWindow extends BaseWindow { @IBannerService private readonly bannerService: IBannerService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @IPreferencesService private readonly preferencesService: IPreferencesService, - @IUtilityProcessWorkerWorkbenchService private readonly utilityProcessWorkerWorkbenchService: IUtilityProcessWorkerWorkbenchService + @IUtilityProcessWorkerWorkbenchService private readonly utilityProcessWorkerWorkbenchService: IUtilityProcessWorkerWorkbenchService, + @IHostService hostService: IHostService ) { - super(mainWindow); + super(mainWindow, undefined, hostService); this.mainPartEditorService = editorService.createScoped('main', this._store); @@ -248,8 +250,8 @@ export class NativeWindow extends BaseWindow { }); // Fullscreen Events - ipcRenderer.on('vscode:enterFullScreen', async () => { setFullscreen(true); }); - ipcRenderer.on('vscode:leaveFullScreen', async () => { setFullscreen(false); }); + ipcRenderer.on('vscode:enterFullScreen', async () => { setFullscreen(true, mainWindow); }); + ipcRenderer.on('vscode:leaveFullScreen', async () => { setFullscreen(false, mainWindow); }); // Proxy Login Dialog ipcRenderer.on('vscode:openProxyAuthenticationDialog', async (event: unknown, payload: { authInfo: AuthInfo; username?: string; password?: string; replyChannel: string }) => { diff --git a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts index bcb5c79f51b..5af8ccad75a 100644 --- a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts @@ -21,6 +21,7 @@ import { BaseWindow } from 'vs/workbench/browser/window'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Barrier } from 'vs/base/common/async'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export const IAuxiliaryWindowService = createDecorator('auxiliaryWindowService'); @@ -78,8 +79,9 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { readonly container: HTMLElement, stylesHaveLoaded: Barrier, @IConfigurationService private readonly configurationService: IConfigurationService, + @IHostService hostService: IHostService ) { - super(window); + super(window, undefined, hostService); this.whenStylesHaveLoaded = stylesHaveLoaded.wait().then(() => { }); this.registerListeners(); @@ -170,7 +172,8 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IDialogService private readonly dialogService: IDialogService, @IConfigurationService protected readonly configurationService: IConfigurationService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IHostService protected readonly hostService: IHostService ) { super(); } @@ -225,7 +228,7 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili } protected createAuxiliaryWindow(targetWindow: CodeWindow, container: HTMLElement, stylesLoaded: Barrier): AuxiliaryWindow { - return new AuxiliaryWindow(targetWindow, container, stylesLoaded, this.configurationService); + return new AuxiliaryWindow(targetWindow, container, stylesLoaded, this.configurationService, this.hostService); } private async openWindow(options?: IAuxiliaryWindowOpenOptions): Promise { diff --git a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts index 525eb74fd2a..3ac7684e596 100644 --- a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts @@ -20,6 +20,7 @@ import { ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Barrier } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; type NativeCodeWindow = CodeWindow & { readonly vscode: ISandboxGlobals; @@ -35,9 +36,10 @@ export class NativeAuxiliaryWindow extends AuxiliaryWindow { stylesHaveLoaded: Barrier, @IConfigurationService configurationService: IConfigurationService, @INativeHostService private readonly nativeHostService: INativeHostService, - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IHostService hostService: IHostService ) { - super(window, container, stylesHaveLoaded, configurationService); + super(window, container, stylesHaveLoaded, configurationService, hostService); } protected override async confirmBeforeClose(e: BeforeUnloadEvent): Promise { @@ -65,9 +67,10 @@ export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService @IDialogService dialogService: IDialogService, @IInstantiationService private readonly instantiationService: IInstantiationService, @ITelemetryService telemetryService: ITelemetryService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IHostService hostService: IHostService ) { - super(layoutService, dialogService, configurationService, telemetryService); + super(layoutService, dialogService, configurationService, telemetryService, hostService); } protected override async resolveWindowId(auxiliaryWindow: NativeCodeWindow): Promise { @@ -110,7 +113,7 @@ export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService } protected override createAuxiliaryWindow(targetWindow: CodeWindow, container: HTMLElement, stylesHaveLoaded: Barrier,): AuxiliaryWindow { - return new NativeAuxiliaryWindow(targetWindow, container, stylesHaveLoaded, this.configurationService, this.nativeHostService, this.instantiationService); + return new NativeAuxiliaryWindow(targetWindow, container, stylesHaveLoaded, this.configurationService, this.nativeHostService, this.instantiationService, this.hostService); } } diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index fb13ac6c764..ad218733589 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -241,14 +241,18 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { this._register(Event.runAndSubscribe(dom.onDidRegisterWindow, ({ window, disposables }) => disposables.add(this._registerKeyListeners(window)), { window: mainWindow, disposables: this._store })); - this._register(browser.onDidChangeFullscreen(() => { + this._register(browser.onDidChangeFullscreen(windowId => { + if (windowId !== mainWindow.vscodeWindowId) { + return; + } + const keyboard: IKeyboard | null = (navigator).keyboard; if (BrowserFeatures.keyboard === KeyboardSupport.None) { return; } - if (browser.isFullscreen()) { + if (browser.isFullscreen(mainWindow)) { keyboard?.lock(['Escape']); } else { keyboard?.unlock(); @@ -461,7 +465,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { return false; } - if (BrowserFeatures.keyboard === KeyboardSupport.FullScreen && browser.isFullscreen()) { + if (BrowserFeatures.keyboard === KeyboardSupport.FullScreen && browser.isFullscreen(mainWindow)) { return false; } diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index aa61afdf61c..10ee5560632 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -116,11 +116,6 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ readonly onDidChangeZenMode: Event; - /** - * Emits when fullscreen is enabled or disabled. - */ - readonly onDidChangeFullscreen: Event; - /** * Emits when the target window is maximized or unmaximized. */ diff --git a/src/vs/workbench/test/browser/window.test.ts b/src/vs/workbench/test/browser/window.test.ts index eabf5661cfc..6d9b702cea6 100644 --- a/src/vs/workbench/test/browser/window.test.ts +++ b/src/vs/workbench/test/browser/window.test.ts @@ -10,6 +10,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { BaseWindow } from 'vs/workbench/browser/window'; +import { TestHostService } from 'vs/workbench/test/browser/workbenchTestServices'; suite('Window', () => { @@ -18,7 +19,7 @@ suite('Window', () => { class TestWindow extends BaseWindow { constructor(window: CodeWindow, dom: { getWindowsCount: () => number; getWindows: () => Iterable }) { - super(window, dom); + super(window, dom, new TestHostService()); } protected override enableWindowFocusOnElementFocus(): void { } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index ceabefed273..a80fd170b01 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -605,7 +605,6 @@ export class TestLayoutService implements IWorkbenchLayoutService { onDidChangeZenMode: Event = Event.None; onDidChangeCenteredLayout: Event = Event.None; - onDidChangeFullscreen: Event = Event.None; onDidChangeWindowMaximized: Event<{ windowId: number; maximized: boolean }> = Event.None; onDidChangePanelPosition: Event = Event.None; onDidChangePanelAlignment: Event = Event.None; From 37544870745f1d17ecaefecb4feaf4a7eb2448d4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 22 Dec 2023 15:51:21 +0100 Subject: [PATCH 0026/1897] aux window - enable reload window --- .../auxiliaryWindow/electron-main/auxiliaryWindow.ts | 2 -- src/vs/workbench/browser/actions/windowActions.ts | 8 ++------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts index cf36f0950b9..1730e9a0817 100644 --- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts +++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts @@ -39,8 +39,6 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { ) { super(configurationService, stateService, environmentMainService); - contents.removeAllListeners('devtools-reload-page'); // remove built in listener as aux windows have no reload - // Try to claim window this.tryClaimWindow(); } diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 24aefca0450..bf3fc0d3b1b 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -8,7 +8,7 @@ import { IWindowOpenable } from 'vs/platform/window/common/window'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { MenuRegistry, MenuId, Action2, registerAction2, IAction2Options } from 'vs/platform/actions/common/actions'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IsAuxiliaryWindowFocusedContext, IsMainWindowFullscreenContext } from 'vs/workbench/common/contextkeys'; +import { IsMainWindowFullscreenContext } from 'vs/workbench/common/contextkeys'; import { IsMacNativeContext, IsDevelopmentContext, IsWebContext, IsIOSContext } from 'vs/platform/contextkey/common/contextkeys'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -35,7 +35,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { isFolderBackupInfo, isWorkspaceBackupInfo } from 'vs/platform/backup/common/backup'; import { getActiveElement, getActiveWindow } from 'vs/base/browser/dom'; -import { mainWindow } from 'vs/base/browser/window'; export const inRecentFilesPickerContextKey = 'inRecentFilesPicker'; @@ -323,7 +322,6 @@ export class ReloadWindowAction extends Action2 { id: ReloadWindowAction.ID, title: { value: localize('reloadWindow', "Reload Window"), original: 'Reload Window' }, category: Categories.Developer, - precondition: IsAuxiliaryWindowFocusedContext.toNegated(), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib + 50, @@ -336,9 +334,7 @@ export class ReloadWindowAction extends Action2 { override async run(accessor: ServicesAccessor): Promise { const hostService = accessor.get(IHostService); - if (getActiveWindow() === mainWindow) { - return hostService.reload(); // only supported for main window - } + return hostService.reload(); } } From c29a6e10b9f3db1bfd548f1fbc766ebaf5ac5796 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 24 Dec 2023 09:27:33 +0100 Subject: [PATCH 0027/1897] aux window - allow a per-window zoom level (#185836) --- src/vs/base/browser/browser.ts | 45 +++++---- .../browser/ui/scrollbar/scrollableElement.ts | 3 +- src/vs/code/electron-main/app.ts | 7 ++ .../issue/issueReporterService.ts | 6 +- .../processExplorer/processExplorerMain.ts | 6 +- src/vs/platform/window/common/window.ts | 1 + .../platform/window/electron-main/window.ts | 3 + .../window/electron-sandbox/window.ts | 34 +++++-- .../windows/electron-main/windowImpl.ts | 34 ++++++- .../platform/windows/electron-main/windows.ts | 2 +- .../electron-main/windowsMainService.ts | 1 - .../test/electron-main/windowsFinder.test.ts | 1 + .../browser/parts/editor/editorParts.ts | 13 ++- .../browser/parts/titlebar/titlebarPart.ts | 8 +- .../electron-sandbox/actions/windowActions.ts | 91 +++++++++++++++---- .../electron-sandbox/desktop.contribution.ts | 12 ++- .../electron-sandbox/desktop.main.ts | 17 +++- .../parts/titlebar/titlebarPart.ts | 6 +- src/vs/workbench/electron-sandbox/window.ts | 36 ++++++-- .../browser/auxiliaryWindowService.ts | 5 +- .../auxiliaryWindowService.ts | 17 +++- .../electron-sandbox/contextmenuService.ts | 2 +- .../issue/electron-sandbox/issueService.ts | 5 +- 23 files changed, 264 insertions(+), 91 deletions(-) diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index 64b0984693f..d8bd1f089ce 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -13,27 +13,33 @@ class WindowManager { // --- Zoom Level - private _zoomLevel: number = 0; + private readonly mapWindowIdToZoomLevel = new Map(); - getZoomLevel(): number { - return this._zoomLevel; + private readonly _onDidChangeZoomLevel = new Emitter(); + readonly onDidChangeZoomLevel = this._onDidChangeZoomLevel.event; + + getZoomLevel(targetWindow: Window): number { + return this.mapWindowIdToZoomLevel.get(this.getWindowId(targetWindow)) ?? 0; } - setZoomLevel(zoomLevel: number): void { - if (this._zoomLevel === zoomLevel) { + setZoomLevel(zoomLevel: number, targetWindow: Window): void { + if (this.getZoomLevel(targetWindow) === zoomLevel) { return; } - this._zoomLevel = zoomLevel; + + const targetWindowId = this.getWindowId(targetWindow); + this.mapWindowIdToZoomLevel.set(targetWindowId, zoomLevel); + this._onDidChangeZoomLevel.fire(targetWindowId); } // --- Zoom Factor - private _zoomFactor: number = 1; + private readonly mapWindowIdToZoomFactor = new Map(); - getZoomFactor(): number { - return this._zoomFactor; + getZoomFactor(targetWindow: Window): number { + return this.mapWindowIdToZoomFactor.get(this.getWindowId(targetWindow)) ?? 1; } - setZoomFactor(zoomFactor: number): void { - this._zoomFactor = zoomFactor; + setZoomFactor(zoomFactor: number, targetWindow: Window): void { + this.mapWindowIdToZoomFactor.set(this.getWindowId(targetWindow), zoomFactor); } // --- Fullscreen @@ -169,19 +175,20 @@ export function addMatchMediaChangeListener(query: string | MediaQueryList, call export const PixelRatio = new PixelRatioFacade(); /** A zoom index, e.g. 1, 2, 3 */ -export function setZoomLevel(zoomLevel: number): void { - WindowManager.INSTANCE.setZoomLevel(zoomLevel); +export function setZoomLevel(zoomLevel: number, targetWindow: Window): void { + WindowManager.INSTANCE.setZoomLevel(zoomLevel, targetWindow); } -export function getZoomLevel(): number { - return WindowManager.INSTANCE.getZoomLevel(); +export function getZoomLevel(targetWindow: Window): number { + return WindowManager.INSTANCE.getZoomLevel(targetWindow); } +export const onDidChangeZoomLevel = WindowManager.INSTANCE.onDidChangeZoomLevel; /** The zoom scale for an index, e.g. 1, 1.2, 1.4 */ -export function getZoomFactor(): number { - return WindowManager.INSTANCE.getZoomFactor(); +export function getZoomFactor(targetWindow: Window): number { + return WindowManager.INSTANCE.getZoomFactor(targetWindow); } -export function setZoomFactor(zoomFactor: number): void { - WindowManager.INSTANCE.setZoomFactor(zoomFactor); +export function setZoomFactor(zoomFactor: number, targetWindow: Window): void { + WindowManager.INSTANCE.setZoomFactor(zoomFactor, targetWindow); } export function setFullscreen(fullscreen: boolean, targetWindow: Window): void { diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index ea8a90ea6c0..85253acf938 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -87,7 +87,8 @@ export class MouseWheelClassifier { } public acceptStandardWheelEvent(e: StandardWheelEvent): void { - const osZoomFactor = dom.getWindow(e.browserEvent).devicePixelRatio / getZoomFactor(); + const targetWindow = dom.getWindow(e.browserEvent); + const osZoomFactor = targetWindow.devicePixelRatio / getZoomFactor(targetWindow); if (platform.isWindows || platform.isLinux) { // On Windows and Linux, the incoming delta events are multiplied with the OS zoom factor. // The OS zoom factor can be reverse engineered by using the device pixel ratio and the configured zoom factor into account. diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 641ee04aa63..6f02aa6fc22 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -516,6 +516,13 @@ export class CodeApplication extends Disposable { validatedIpcMain.on('vscode:reloadWindow', event => event.sender.reload()); + validatedIpcMain.handle('vscode:notifyZoomLevel', async (event, zoomLevel: number | undefined) => { + const window = this.windowsMainService?.getWindowById(event.sender.id); + if (window) { + window.notifyZoomLevel(zoomLevel); + } + }); + //#endregion } diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index d7f98f666b9..417b40f216f 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -127,7 +127,7 @@ export class IssueReporter extends Disposable { this.setUpTypes(); this.setEventHandlers(); - applyZoom(configuration.data.zoomLevel); + applyZoom(configuration.data.zoomLevel, mainWindow); this.applyStyles(configuration.data.styles); this.handleExtensionData(configuration.data.enabledExtensions); this.updateExperimentsInfo(configuration.data.experiments); @@ -435,12 +435,12 @@ export class IssueReporter extends Disposable { // Cmd/Ctrl + zooms in if (cmdOrCtrlKey && e.keyCode === 187) { - zoomIn(); + zoomIn(mainWindow); } // Cmd/Ctrl - zooms out if (cmdOrCtrlKey && e.keyCode === 189) { - zoomOut(); + zoomOut(mainWindow); } // With latest electron upgrade, cmd+a is no longer propagating correctly for inputs in this window on mac diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index 1d2089e1586..91bb7854340 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -288,12 +288,12 @@ class ProcessExplorer { // Cmd/Ctrl + zooms in if (cmdOrCtrlKey && e.keyCode === 187) { - zoomIn(); + zoomIn(mainWindow); } // Cmd/Ctrl - zooms out if (cmdOrCtrlKey && e.keyCode === 189) { - zoomOut(); + zoomOut(mainWindow); } }; } @@ -595,7 +595,7 @@ export function startup(configuration: ProcessExplorerWindowConfiguration): void const platformClass = configuration.data.platform === 'win32' ? 'windows' : configuration.data.platform === 'linux' ? 'linux' : 'mac'; mainWindow.document.body.classList.add(platformClass); // used by our fonts createCodiconStyleSheet(); - applyZoom(configuration.data.zoomLevel); + applyZoom(configuration.data.zoomLevel, mainWindow); new ProcessExplorer(configuration.windowId, configuration.data); } diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index 38ad7ca52eb..25eb202ca4e 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -347,6 +347,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native colorScheme: IColorScheme; autoDetectHighContrast?: boolean; autoDetectColorScheme?: boolean; + isCustomZoomLevel?: boolean; perfMarks: PerformanceMark[]; diff --git a/src/vs/platform/window/electron-main/window.ts b/src/vs/platform/window/electron-main/window.ts index dbcef949cd8..6f744cf6454 100644 --- a/src/vs/platform/window/electron-main/window.ts +++ b/src/vs/platform/window/electron-main/window.ts @@ -81,6 +81,8 @@ export interface ICodeWindow extends IBaseWindow { updateTouchBar(items: ISerializableCommandAction[][]): void; + notifyZoomLevel(zoomLevel: number | undefined): void; + serializeWindowState(): IWindowState; } @@ -131,6 +133,7 @@ export interface IWindowState { x?: number; y?: number; mode?: WindowMode; + zoomLevel?: number; readonly display?: number; } diff --git a/src/vs/platform/window/electron-sandbox/window.ts b/src/vs/platform/window/electron-sandbox/window.ts index 55c34942677..d8a3301ff3c 100644 --- a/src/vs/platform/window/electron-sandbox/window.ts +++ b/src/vs/platform/window/electron-sandbox/window.ts @@ -4,21 +4,35 @@ *--------------------------------------------------------------------------------------------*/ import { getZoomLevel, setZoomFactor, setZoomLevel } from 'vs/base/browser/browser'; -import { getWindows } from 'vs/base/browser/dom'; +import { getActiveWindow, getWindows } from 'vs/base/browser/dom'; import { mainWindow } from 'vs/base/browser/window'; import { ISandboxGlobals, ipcRenderer, webFrame } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; +export enum ApplyZoomTarget { + ACTIVE_WINDOW = 1, + ALL_WINDOWS +} + /** * Apply a zoom level to the window. Also sets it in our in-memory * browser helper so that it can be accessed in non-electron layers. */ -export function applyZoom(zoomLevel: number): void { - for (const { window } of getWindows()) { - getGlobals(window)?.webFrame?.setZoomLevel(zoomLevel); +export function applyZoom(zoomLevel: number, target: ApplyZoomTarget | Window): void { + const targetWindows: Window[] = []; + if (target === ApplyZoomTarget.ACTIVE_WINDOW) { + targetWindows.push(getActiveWindow()); + } else if (target === ApplyZoomTarget.ALL_WINDOWS) { + targetWindows.push(...Array.from(getWindows()).map(({ window }) => window)); + } else { + targetWindows.push(target); + } + + for (const targetWindow of targetWindows) { + getGlobals(targetWindow)?.webFrame?.setZoomLevel(zoomLevel); + setZoomFactor(zoomLevelToZoomFactor(zoomLevel), targetWindow); + setZoomLevel(zoomLevel, targetWindow); } - setZoomFactor(zoomLevelToZoomFactor(zoomLevel)); - setZoomLevel(zoomLevel); } function getGlobals(win: Window): ISandboxGlobals | undefined { @@ -36,10 +50,10 @@ function getGlobals(win: Window): ISandboxGlobals | undefined { return undefined; } -export function zoomIn(): void { - applyZoom(getZoomLevel() + 1); +export function zoomIn(target: ApplyZoomTarget | Window): void { + applyZoom(getZoomLevel(typeof target === 'number' ? getActiveWindow() : target) + 1, target); } -export function zoomOut(): void { - applyZoom(getZoomLevel() - 1); +export function zoomOut(target: ApplyZoomTarget | Window): void { + applyZoom(getZoomLevel(typeof target === 'number' ? getActiveWindow() : target) - 1, target); } diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 4972933c581..4f2bb8182de 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -478,6 +478,8 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { private currentHttpProxy: string | undefined = undefined; private currentNoProxy: string | undefined = undefined; + private customZoomLevel: number | undefined = undefined; + private readonly configObjectUrl = this._register(this.protocolMainService.createIPCObjectUrl()); private pendingLoadConfig: INativeWindowConfiguration | undefined; private wasLoaded = false; @@ -1050,6 +1052,11 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { configuration.fullscreen = this.isFullScreen; configuration.maximized = this._win.isMaximized(); configuration.partsSplash = this.themeMainService.getWindowSplash(); + configuration.zoomLevel = this.getZoomLevel(); + configuration.isCustomZoomLevel = typeof this.customZoomLevel === 'number'; + if (configuration.isCustomZoomLevel && configuration.partsSplash) { + configuration.partsSplash.zoomLevel = configuration.zoomLevel; + } // Update with latest perf marks mark('code/willOpenNewWindow'); @@ -1149,7 +1156,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { const defaultState = defaultWindowState(); - const res = { + return { mode: WindowMode.Fullscreen, display: display ? display.id : undefined, @@ -1161,10 +1168,9 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { width: this.windowState.width || defaultState.width, height: this.windowState.height || defaultState.height, x: this.windowState.x || 0, - y: this.windowState.y || 0 + y: this.windowState.y || 0, + zoomLevel: this.customZoomLevel }; - - return res; } const state: IWindowState = Object.create(null); @@ -1199,6 +1205,8 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { state.height = bounds.height; } + state.zoomLevel = this.customZoomLevel; + return state; } @@ -1207,6 +1215,11 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { let hasMultipleDisplays = false; if (state) { + + // Window zoom + this.customZoomLevel = state.zoomLevel; + + // Window dimensions try { const displays = screen.getAllDisplays(); hasMultipleDisplays = displays.length > 1; @@ -1443,6 +1456,19 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { } } + notifyZoomLevel(zoomLevel: number | undefined): void { + this.customZoomLevel = zoomLevel; + } + + private getZoomLevel(): number | undefined { + if (typeof this.customZoomLevel === 'number') { + return this.customZoomLevel; + } + + const windowSettings = this.configurationService.getValue('window'); + return windowSettings?.zoomLevel; + } + close(): void { this._win?.close(); } diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 1bfd4d3631f..a658f3fa5ba 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -132,7 +132,7 @@ export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowSt webPreferences: { enableWebSQL: false, spellcheck: false, - zoomFactor: zoomLevelToZoomFactor(windowSettings?.zoomLevel), + zoomFactor: zoomLevelToZoomFactor(windowState?.zoomLevel ?? windowSettings?.zoomLevel), autoplayPolicy: 'user-gesture-required', // Enable experimental css highlight api https://chromestatus.com/feature/5436441440026624 // Refs https://github.com/microsoft/vscode/issues/140098 diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 5a1473a0844..32019b8b42c 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -1456,7 +1456,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic isInitialStartup: options.initialStartup, perfMarks: getMarks(), os: { release: release(), hostname: hostname(), arch: arch() }, - zoomLevel: typeof windowConfig?.zoomLevel === 'number' ? windowConfig.zoomLevel : undefined, autoDetectHighContrast: windowConfig?.autoDetectHighContrast ?? true, autoDetectColorScheme: windowConfig?.autoDetectColorScheme ?? false, diff --git a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts index d395dc44ab1..dbba00ae8f0 100644 --- a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts +++ b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts @@ -74,6 +74,7 @@ suite('WindowsFinder', () => { updateTouchBar(items: UriDto[][]): void { throw new Error('Method not implemented.'); } serializeWindowState(): IWindowState { throw new Error('Method not implemented'); } updateWindowControls(options: { height?: number | undefined; backgroundColor?: string | undefined; foregroundColor?: string | undefined }): void { throw new Error('Method not implemented.'); } + notifyZoomLevel(level: number): void { throw new Error('Method not implemented.'); } dispose(): void { } }; } diff --git a/src/vs/workbench/browser/parts/editor/editorParts.ts b/src/vs/workbench/browser/parts/editor/editorParts.ts index 7f36225bf2c..f99c1230bd6 100644 --- a/src/vs/workbench/browser/parts/editor/editorParts.ts +++ b/src/vs/workbench/browser/parts/editor/editorParts.ts @@ -20,6 +20,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IRectangle } from 'vs/platform/window/common/window'; import { getWindow } from 'vs/base/browser/dom'; +import { getZoomLevel } from 'vs/base/browser/browser'; interface IEditorPartsUIState { readonly auxiliary: IAuxiliaryEditorPartState[]; @@ -29,6 +30,7 @@ interface IEditorPartsUIState { interface IAuxiliaryEditorPartState { readonly state: IEditorPartUIState; readonly bounds?: IRectangle; + readonly zoomLevel?: number; } export class EditorParts extends MultiWindowParts implements IEditorGroupsService, IEditorPartsView { @@ -210,7 +212,8 @@ export class EditorParts extends MultiWindowParts implements IEditor for (const auxiliaryEditorPartState of uiState.auxiliary) { auxiliaryEditorPartPromises.push(this.createAuxiliaryEditorPart({ bounds: auxiliaryEditorPartState.bounds, - state: auxiliaryEditorPartState.state + state: auxiliaryEditorPartState.state, + zoomLevel: auxiliaryEditorPartState.zoomLevel })); } @@ -256,6 +259,14 @@ export class EditorParts extends MultiWindowParts implements IEditor }; } + return undefined; + })(), + zoomLevel: (() => { + const auxiliaryWindow = getWindow(part.getContainer()); + if (auxiliaryWindow) { + return getZoomLevel(auxiliaryWindow); + } + return undefined; })() }; diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 1f98cb50ef7..2e69d9da37c 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -170,7 +170,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { get minimumHeight(): number { const value = this.isCommandCenterVisible || (isWeb && isWCOEnabled()) ? 35 : 30; - return value / (this.useCounterZoom ? getZoomFactor() : 1); + return value / (this.useCounterZoom ? getZoomFactor(getWindow(this.element)) : 1); } get maximumHeight(): number { return this.minimumHeight; } @@ -674,7 +674,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } protected onContextMenu(e: MouseEvent, menuId: MenuId): void { - const event = new StandardMouseEvent(getWindow(this.rootContainer), e); + const event = new StandardMouseEvent(getWindow(this.element), e); // Show it this.contextMenuService.showContextMenu({ @@ -719,7 +719,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { // 1. Shrinking below the window control size (zoom < 1) // 2. No custom items are present in the title bar - const zoomFactor = getZoomFactor(); + const zoomFactor = getZoomFactor(getWindow(this.element)); const noMenubar = this.currentMenubarVisibility === 'hidden' || this.currentMenubarVisibility === 'compact' || (!isWeb && isMacintosh); const noCommandCenter = !this.isCommandCenterVisible; @@ -738,7 +738,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.lastLayoutDimensions = dimension; if (getTitleBarStyle(this.configurationService) === 'custom') { - const zoomFactor = getZoomFactor(); + const zoomFactor = getZoomFactor(getWindow(this.element)); this.element.style.setProperty('--zoom-factor', zoomFactor.toString()); this.rootContainer.classList.toggle('counter-zoom', this.useCounterZoom); diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index 1e855db5d64..bee086ad56e 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/actions'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { applyZoom } from 'vs/platform/window/electron-sandbox/window'; +import { ApplyZoomTarget, applyZoom } from 'vs/platform/window/electron-sandbox/window'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { getZoomLevel } from 'vs/base/browser/browser'; import { FileKind } from 'vs/platform/files/common/files'; @@ -74,7 +74,7 @@ abstract class BaseZoomAction extends Action2 { super(desc); } - protected async setConfiguredZoomLevel(accessor: ServicesAccessor, level: number): Promise { + protected async setZoomLevel(accessor: ServicesAccessor, level: number, target: ApplyZoomTarget): Promise { const configurationService = accessor.get(IConfigurationService); level = Math.round(level); // when reaching smallest zoom, prevent fractional zoom levels @@ -83,21 +83,23 @@ abstract class BaseZoomAction extends Action2 { return; // https://github.com/microsoft/vscode/issues/48357 } - await configurationService.updateValue(BaseZoomAction.SETTING_KEY, level); + if (target === ApplyZoomTarget.ALL_WINDOWS) { + await configurationService.updateValue(BaseZoomAction.SETTING_KEY, level); + } - applyZoom(level); + applyZoom(level, target); } } -export class ZoomInAction extends BaseZoomAction { +export class ZoomInAllWindowsAction extends BaseZoomAction { constructor() { super({ id: 'workbench.action.zoomIn', title: { - value: localize('zoomIn', "Zoom In"), + value: localize('zoomIn', "Zoom In (All Windows)"), mnemonicTitle: localize({ key: 'miZoomIn', comment: ['&& denotes a mnemonic'] }, "&&Zoom In"), - original: 'Zoom In' + original: 'Zoom In (All Windows)' }, category: Categories.View, f1: true, @@ -115,19 +117,19 @@ export class ZoomInAction extends BaseZoomAction { } override run(accessor: ServicesAccessor): Promise { - return super.setConfiguredZoomLevel(accessor, getZoomLevel() + 1); + return super.setZoomLevel(accessor, getZoomLevel(getActiveWindow()) + 1, ApplyZoomTarget.ALL_WINDOWS); } } -export class ZoomOutAction extends BaseZoomAction { +export class ZoomOutAllWindowsAction extends BaseZoomAction { constructor() { super({ id: 'workbench.action.zoomOut', title: { - value: localize('zoomOut', "Zoom Out"), + value: localize('zoomOut', "Zoom Out (All Windows)"), mnemonicTitle: localize({ key: 'miZoomOut', comment: ['&& denotes a mnemonic'] }, "&&Zoom Out"), - original: 'Zoom Out' + original: 'Zoom Out (All Windows)' }, category: Categories.View, f1: true, @@ -149,19 +151,19 @@ export class ZoomOutAction extends BaseZoomAction { } override run(accessor: ServicesAccessor): Promise { - return super.setConfiguredZoomLevel(accessor, getZoomLevel() - 1); + return super.setZoomLevel(accessor, getZoomLevel(getActiveWindow()) - 1, ApplyZoomTarget.ALL_WINDOWS); } } -export class ZoomResetAction extends BaseZoomAction { +export class ZoomResetAllWindowsAction extends BaseZoomAction { constructor() { super({ id: 'workbench.action.zoomReset', title: { - value: localize('zoomReset', "Reset Zoom"), + value: localize('zoomReset', "Reset Zoom (All Windows)"), mnemonicTitle: localize({ key: 'miZoomReset', comment: ['&& denotes a mnemonic'] }, "&&Reset Zoom"), - original: 'Reset Zoom' + original: 'Reset Zoom (All Windows)' }, category: Categories.View, f1: true, @@ -178,7 +180,64 @@ export class ZoomResetAction extends BaseZoomAction { } override run(accessor: ServicesAccessor): Promise { - return super.setConfiguredZoomLevel(accessor, 0); + return super.setZoomLevel(accessor, 0, ApplyZoomTarget.ALL_WINDOWS); + } +} + +export class ZoomInActiveWindowAction extends BaseZoomAction { + + constructor() { + super({ + id: 'workbench.action.zoomInActiveWindow', + title: { + value: localize('zoomInActiveWindow', "Zoom In (Active Window)"), + original: 'Zoom In (Active Window)' + }, + category: Categories.View, + f1: true + }); + } + + override run(accessor: ServicesAccessor): Promise { + return super.setZoomLevel(accessor, getZoomLevel(getActiveWindow()) + 1, ApplyZoomTarget.ACTIVE_WINDOW); + } +} + +export class ZoomOutActiveWindowAction extends BaseZoomAction { + + constructor() { + super({ + id: 'workbench.action.zoomOutActiveWindow', + title: { + value: localize('zoomOutActiveWindow', "Zoom Out (Active Window)"), + original: 'Zoom Out (Active Window)' + }, + category: Categories.View, + f1: true + }); + } + + override run(accessor: ServicesAccessor): Promise { + return super.setZoomLevel(accessor, getZoomLevel(getActiveWindow()) - 1, ApplyZoomTarget.ACTIVE_WINDOW); + } +} + +export class ZoomResetActiveWindowAction extends BaseZoomAction { + + constructor() { + super({ + id: 'workbench.action.zoomResetActiveWindow', + title: { + value: localize('zoomResetActiveWindow', "Reset Zoom (Active Window)"), + original: 'Reset Zoom (Active Window)' + }, + category: Categories.View, + f1: true + }); + } + + override run(accessor: ServicesAccessor): Promise { + return super.setZoomLevel(accessor, 0, ApplyZoomTarget.ACTIVE_WINDOW); } } diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 843fbd70fbf..78dd92aa508 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -10,7 +10,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { ConfigureRuntimeArgumentsAction, ToggleDevToolsAction, ReloadWindowWithExtensionsDisabledAction, OpenUserDataFolderAction } from 'vs/workbench/electron-sandbox/actions/developerActions'; -import { ZoomResetAction, ZoomOutAction, ZoomInAction, CloseWindowAction, SwitchWindowAction, QuickSwitchWindowAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-sandbox/actions/windowActions'; +import { ZoomResetAllWindowsAction, ZoomOutAllWindowsAction, ZoomInAllWindowsAction, CloseWindowAction, SwitchWindowAction, QuickSwitchWindowAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler, ZoomInActiveWindowAction, ZoomOutActiveWindowAction, ZoomResetActiveWindowAction } from 'vs/workbench/electron-sandbox/actions/windowActions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -32,9 +32,13 @@ import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from (function registerActions(): void { // Actions: Zoom - registerAction2(ZoomInAction); - registerAction2(ZoomOutAction); - registerAction2(ZoomResetAction); + registerAction2(ZoomInAllWindowsAction); + registerAction2(ZoomOutAllWindowsAction); + registerAction2(ZoomResetAllWindowsAction); + + registerAction2(ZoomInActiveWindowAction); + registerAction2(ZoomOutActiveWindowAction); + registerAction2(ZoomResetActiveWindowAction); // Actions: Window registerAction2(SwitchWindowAction); diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 0acf57e3448..1118dd5d38f 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -116,8 +116,10 @@ export class DesktopMain extends Disposable { // and before the workbench is created to prevent flickering. // We also need to respect that zoom level can be configured per // workspace, so we need the resolved configuration service. + // Finally, it is possible for the window to have a custom + // zoom level that is not derived from settings. // (fixes https://github.com/microsoft/vscode/issues/187982) - this.applyConfiguredWindowZoomLevel(services.configurationService); + this.applyWindowZoomLevel(services.configurationService); // Create Workbench const workbench = new Workbench(mainWindow.document.body, { extraClasses: this.getExtraClasses() }, services.serviceCollection, services.logService); @@ -132,11 +134,16 @@ export class DesktopMain extends Disposable { this._register(instantiationService.createInstance(NativeWindow)); } - private applyConfiguredWindowZoomLevel(configurationService: IConfigurationService) { - const windowConfig = configurationService.getValue(); - const windowZoomLevel = typeof windowConfig.window?.zoomLevel === 'number' ? windowConfig.window.zoomLevel : 0; + private applyWindowZoomLevel(configurationService: IConfigurationService) { + let zoomLevel: number | undefined = undefined; + if (this.configuration.isCustomZoomLevel && typeof this.configuration.zoomLevel === 'number') { + zoomLevel = this.configuration.zoomLevel; + } else { + const windowConfig = configurationService.getValue(); + zoomLevel = typeof windowConfig.window?.zoomLevel === 'number' ? windowConfig.window.zoomLevel : 0; + } - applyZoom(windowZoomLevel); + applyZoom(zoomLevel, mainWindow); } private getExtraClasses(): string[] { diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index 161395b56f6..e3854eefde7 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -38,7 +38,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { return super.minimumHeight; } - return (this.isCommandCenterVisible ? 35 : this.macTitlebarSize) / (this.useCounterZoom ? getZoomFactor() : 1); + return (this.isCommandCenterVisible ? 35 : this.macTitlebarSize) / (this.useCounterZoom ? getZoomFactor(getWindow(this.element)) : 1); } override get maximumHeight(): number { return this.minimumHeight; } @@ -201,7 +201,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { return; } - const zoomFactor = getZoomFactor(); + const zoomFactor = getZoomFactor(getWindow(this.element)); this.onContextMenu(new MouseEvent('mouseup', { clientX: x / zoomFactor, clientY: y / zoomFactor }), MenuId.TitleBarContext); })); } @@ -261,7 +261,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { // so that they can have the traffic lights rendered at the proper offset. // Ref https://github.com/microsoft/vscode/issues/159862 - const newHeight = (height > 0 || this.bigSurOrNewer) ? Math.round(height * getZoomFactor()) : this.macTitlebarSize; + const newHeight = (height > 0 || this.bigSurOrNewer) ? Math.round(height * getZoomFactor(getWindow(this.element))) : this.macTitlebarSize; if (newHeight !== this.cachedWindowControlHeight) { this.cachedWindowControlHeight = newHeight; this.nativeHostService.updateWindowControls({ diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index a0df8c476f2..9a52c01defa 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -7,17 +7,17 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { onUnexpectedError } from 'vs/base/common/errors'; import { equals } from 'vs/base/common/objects'; -import { EventType, EventHelper, addDisposableListener, ModifierKeyEmitter, getActiveElement, hasWindow, getWindow, getWindowById, getWindowId } from 'vs/base/browser/dom'; +import { EventType, EventHelper, addDisposableListener, ModifierKeyEmitter, getActiveElement, hasWindow, getWindow, getWindowById, getWindowId, getWindows } from 'vs/base/browser/dom'; import { Separator, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; import { EditorResourceAccessor, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors, IResourceDiffEditorInput, IUntypedEditorInput, IEditorPane, isResourceEditorInput, IResourceMergeEditorInput } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { WindowMinimumSize, IOpenFileRequest, IWindowsConfiguration, getTitleBarStyle, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest } from 'vs/platform/window/common/window'; +import { WindowMinimumSize, IOpenFileRequest, IWindowsConfiguration, getTitleBarStyle, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest, IWindowSettings } from 'vs/platform/window/common/window'; import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { applyZoom } from 'vs/platform/window/electron-sandbox/window'; -import { setFullscreen, getZoomLevel } from 'vs/base/browser/browser'; +import { ApplyZoomTarget, applyZoom } from 'vs/platform/window/electron-sandbox/window'; +import { setFullscreen, getZoomLevel, onDidChangeZoomLevel } from 'vs/base/browser/browser'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { ipcRenderer, process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; @@ -339,6 +339,22 @@ export class NativeWindow extends BaseWindow { } })); + this._register(onDidChangeZoomLevel(targetWindowId => { + if (targetWindowId !== mainWindow.vscodeWindowId) { + return; // only update our own window + } + + const configuredWindowZoomLevel = this.configurationService.getValue('window')?.zoomLevel; + const currentWindowZoomLevel = getZoomLevel(mainWindow); + + let notifyZoomLevel: number | undefined = undefined; + if (configuredWindowZoomLevel !== currentWindowZoomLevel) { + notifyZoomLevel = currentWindowZoomLevel; + } + + ipcRenderer.invoke('vscode:notifyZoomLevel', notifyZoomLevel); + })); + // Listen to visible editor changes (debounced in case a new editor opens immediately after) this._register(Event.debounce(this.editorService.onDidVisibleEditorsChange, () => undefined, 0, undefined, undefined, undefined, this._store)(() => this.maybeCloseWindow())); @@ -626,8 +642,16 @@ export class NativeWindow extends BaseWindow { const windowConfig = this.configurationService.getValue(); const windowZoomLevel = typeof windowConfig.window?.zoomLevel === 'number' ? windowConfig.window.zoomLevel : 0; - if (getZoomLevel() !== windowZoomLevel) { - applyZoom(windowZoomLevel); + let applyZoomLevel = false; + for (const { window } of getWindows()) { + if (getZoomLevel(window) !== windowZoomLevel) { + applyZoomLevel = true; + break; + } + } + + if (applyZoomLevel) { + applyZoom(windowZoomLevel, ApplyZoomTarget.ALL_WINDOWS); } } diff --git a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts index 5af8ccad75a..53803bb6316 100644 --- a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts @@ -32,6 +32,7 @@ export interface IAuxiliaryWindowOpenEvent { export interface IAuxiliaryWindowOpenOptions { readonly bounds?: Partial; + readonly zoomLevel?: number; } export interface IAuxiliaryWindowService { @@ -191,7 +192,7 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili ensureCodeWindow(targetWindow, resolvedWindowId); const containerDisposables = new DisposableStore(); - const { container, stylesLoaded } = this.createContainer(targetWindow, containerDisposables); + const { container, stylesLoaded } = this.createContainer(targetWindow, containerDisposables, options); const auxiliaryWindow = this.createAuxiliaryWindow(targetWindow, container, stylesLoaded); @@ -283,7 +284,7 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili return BrowserAuxiliaryWindowService.WINDOW_IDS++; } - protected createContainer(auxiliaryWindow: CodeWindow, disposables: DisposableStore): { stylesLoaded: Barrier; container: HTMLElement } { + protected createContainer(auxiliaryWindow: CodeWindow, disposables: DisposableStore, options?: IAuxiliaryWindowOpenOptions): { stylesLoaded: Barrier; container: HTMLElement } { this.patchMethods(auxiliaryWindow); this.applyMeta(auxiliaryWindow); diff --git a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts index 3ac7684e596..04f04560893 100644 --- a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts @@ -5,7 +5,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { AuxiliaryWindow, BrowserAuxiliaryWindowService, IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService'; +import { AuxiliaryWindow, BrowserAuxiliaryWindowService, IAuxiliaryWindowOpenOptions, IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService'; import { ISandboxGlobals } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWindowsConfiguration } from 'vs/platform/window/common/window'; @@ -21,6 +21,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Barrier } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { applyZoom } from 'vs/platform/window/electron-sandbox/window'; type NativeCodeWindow = CodeWindow & { readonly vscode: ISandboxGlobals; @@ -81,12 +82,18 @@ export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService return windowId; } - protected override createContainer(auxiliaryWindow: NativeCodeWindow, disposables: DisposableStore) { + protected override createContainer(auxiliaryWindow: NativeCodeWindow, disposables: DisposableStore, options?: IAuxiliaryWindowOpenOptions) { // Zoom level - const windowConfig = this.configurationService.getValue(); - const windowZoomLevel = typeof windowConfig.window?.zoomLevel === 'number' ? windowConfig.window.zoomLevel : 0; - auxiliaryWindow.vscode.webFrame.setZoomLevel(windowZoomLevel); + let windowZoomLevel: number; + if (typeof options?.zoomLevel === 'number') { + windowZoomLevel = options.zoomLevel; + } else { + const windowConfig = this.configurationService.getValue(); + windowZoomLevel = typeof windowConfig.window?.zoomLevel === 'number' ? windowConfig.window.zoomLevel : 0; + } + + applyZoom(windowZoomLevel, auxiliaryWindow); return super.createContainer(auxiliaryWindow, disposables); } diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts index 49955b1c058..381c8be72ec 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts @@ -106,7 +106,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService let x: number | undefined; let y: number | undefined; - let zoom = getZoomFactor(); + let zoom = getZoomFactor(anchor instanceof HTMLElement ? dom.getWindow(anchor) : dom.getActiveWindow()); if (anchor instanceof HTMLElement) { const elementPosition = dom.getDomNodePagePosition(anchor); diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts index b3b906f09ed..f5e7db23472 100644 --- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts @@ -27,6 +27,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; import { ILogService } from 'vs/platform/log/common/log'; import { IIssueDataProvider, IIssueUriRequestHandler, IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; +import { mainWindow } from 'vs/base/browser/window'; export class NativeIssueService implements IWorkbenchIssueService { declare readonly _serviceBrand: undefined; @@ -144,7 +145,7 @@ export class NativeIssueService implements IWorkbenchIssueService { const theme = this.themeService.getColorTheme(); const issueReporterData: IssueReporterData = Object.assign({ styles: getIssueReporterStyles(theme), - zoomLevel: getZoomLevel(), + zoomLevel: getZoomLevel(mainWindow), enabledExtensions: extensionData, experiments: experiments?.join('\n'), restrictedMode: !this.workspaceTrustManagementService.isWorkspaceTrusted(), @@ -158,7 +159,7 @@ export class NativeIssueService implements IWorkbenchIssueService { const theme = this.themeService.getColorTheme(); const data: ProcessExplorerData = { pid: this.environmentService.mainPid, - zoomLevel: getZoomLevel(), + zoomLevel: getZoomLevel(mainWindow), styles: { backgroundColor: getColor(theme, editorBackground), color: getColor(theme, editorForeground), From ea076f5bdff3496e36a1c18b73612a1c6487c8b0 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 25 Dec 2023 10:18:35 +0100 Subject: [PATCH 0028/1897] debt - tweak some keybindings --- .../browser/parts/editor/editorActions.ts | 6 ++++- .../contrib/files/browser/fileActions.ts | 1 - .../electron-sandbox/actions/windowActions.ts | 26 ++++++++++++++++--- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 351cff2e7b0..74f3f40069c 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -30,7 +30,7 @@ import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/act import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IKeybindingRule, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ILogService } from 'vs/platform/log/common/log'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { ActiveEditorAvailableEditorIdsContext, ActiveEditorContext, ActiveEditorGroupEmptyContext, AuxiliaryBarVisibleContext, EditorPartMaximizedEditorGroupContext, EditorPartMultipleEditorGroupsContext, IsAuxiliaryWindowFocusedContext, MultipleEditorGroupsContext, SideBarVisibleContext } from 'vs/workbench/common/contextkeys'; @@ -2493,6 +2493,7 @@ abstract class BaseMoveCopyEditorToNewWindowAction extends Action2 { constructor( id: string, title: ICommandActionTitle, + keybinding: Omit | undefined, private readonly move: boolean ) { super({ @@ -2500,6 +2501,7 @@ abstract class BaseMoveCopyEditorToNewWindowAction extends Action2 { title, category: Categories.View, precondition: ActiveEditorContext, + keybinding, f1: true }); } @@ -2532,6 +2534,7 @@ export class MoveEditorToNewWindowAction extends BaseMoveCopyEditorToNewWindowAc mnemonicTitle: localize({ key: 'miMoveEditorToNewWindow', comment: ['&& denotes a mnemonic'] }, "&&Move Editor into New Window"), original: 'Move Editor into New Window' }, + undefined, true ); } @@ -2547,6 +2550,7 @@ export class CopyEditorToNewindowAction extends BaseMoveCopyEditorToNewWindowAct mnemonicTitle: localize({ key: 'miCopyEditorToNewWindow', comment: ['&& denotes a mnemonic'] }, "&&Copy Editor into New Window"), original: 'Copy Editor into New Window' }, + { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyO), weight: KeybindingWeight.WorkbenchContrib }, false ); } diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index da784179f5f..69959a9df8d 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -661,7 +661,6 @@ export class OpenActiveFileInEmptyWorkspace extends Action2 { title: { value: OpenActiveFileInEmptyWorkspace.LABEL, original: 'Open Active File in New Empty Workspace' }, f1: true, category: Categories.File, - keybinding: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyO), weight: KeybindingWeight.WorkbenchContrib }, precondition: EmptyWorkspaceSupportContext }); } diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index bee086ad56e..3d69577332d 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -23,7 +23,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/actions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { isMacintosh } from 'vs/base/common/platform'; import { getActiveWindow } from 'vs/base/browser/dom'; @@ -194,7 +194,12 @@ export class ZoomInActiveWindowAction extends BaseZoomAction { original: 'Zoom In (Active Window)' }, category: Categories.View, - f1: true + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Equal), + secondary: [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Equal), KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.NumpadAdd)] + } }); } @@ -213,7 +218,16 @@ export class ZoomOutActiveWindowAction extends BaseZoomAction { original: 'Zoom Out (Active Window)' }, category: Categories.View, - f1: true + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Minus), + secondary: [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Minus), KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.NumpadSubtract)], + linux: { + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Minus), + secondary: [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.NumpadSubtract)] + } + } }); } @@ -232,7 +246,11 @@ export class ZoomResetActiveWindowAction extends BaseZoomAction { original: 'Reset Zoom (Active Window)' }, category: Categories.View, - f1: true + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Numpad0) + } }); } From 35c44eeb2790d76fb335974e1a0130d45e658808 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 25 Dec 2023 13:12:36 +0100 Subject: [PATCH 0029/1897] Aux window: reduce $window usage (#195888) --- src/vs/base/browser/browser.ts | 14 ++++----- src/vs/base/browser/dom.ts | 2 +- .../browser/standaloneThemeService.ts | 2 +- .../browser/inlineChatController.ts | 5 ++-- .../browser/inlineChatStrategies.ts | 29 +++++++++---------- .../view/cellParts/chat/cellChatController.ts | 4 +-- .../browser/browserHostColorSchemeService.ts | 4 +-- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index d8bd1f089ce..5020e44a36b 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $window, CodeWindow } from 'vs/base/browser/window'; +import { $window, CodeWindow, mainWindow } from 'vs/base/browser/window'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, markAsSingleton } from 'vs/base/common/lifecycle'; @@ -158,9 +158,9 @@ class PixelRatioFacade { } } -export function addMatchMediaChangeListener(query: string | MediaQueryList, callback: (this: MediaQueryList, ev: MediaQueryListEvent) => any): void { +export function addMatchMediaChangeListener(targetWindow: Window, query: string | MediaQueryList, callback: (this: MediaQueryList, ev: MediaQueryListEvent) => any): void { if (typeof query === 'string') { - query = $window.matchMedia(query); + query = targetWindow.matchMedia(query); } query.addEventListener('change', callback); } @@ -210,11 +210,11 @@ export const isElectron = (userAgent.indexOf('Electron/') >= 0); export const isAndroid = (userAgent.indexOf('Android') >= 0); let standalone = false; -if ($window.matchMedia) { - const standaloneMatchMedia = $window.matchMedia('(display-mode: standalone) or (display-mode: window-controls-overlay)'); - const fullScreenMatchMedia = $window.matchMedia('(display-mode: fullscreen)'); +if (typeof mainWindow.matchMedia === 'function') { + const standaloneMatchMedia = mainWindow.matchMedia('(display-mode: standalone) or (display-mode: window-controls-overlay)'); + const fullScreenMatchMedia = mainWindow.matchMedia('(display-mode: fullscreen)'); standalone = standaloneMatchMedia.matches; - addMatchMediaChangeListener(standaloneMatchMedia, ({ matches }) => { + addMatchMediaChangeListener(mainWindow, standaloneMatchMedia, ({ matches }) => { // entering fullscreen would change standaloneMatchMedia.matches to false // if standalone is true (running as PWA) and entering fullscreen, skip this change if (standalone && fullScreenMatchMedia.matches) { diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 5379bd3814c..112ed1854d2 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -261,7 +261,7 @@ export let runAtThisOrScheduleAtNextAnimationFrame: (targetWindow: Window, runne */ export let scheduleAtNextAnimationFrame: (targetWindow: Window, runner: () => void, priority?: number) => IDisposable; -export function disposableWindowInterval(targetWindow: Window & typeof globalThis, handler: () => void | boolean /* stop interval */ | Promise, interval: number, iterations?: number): IDisposable { +export function disposableWindowInterval(targetWindow: Window, handler: () => void | boolean /* stop interval */ | Promise, interval: number, iterations?: number): IDisposable { let iteration = 0; const timer = targetWindow.setInterval(() => { iteration++; diff --git a/src/vs/editor/standalone/browser/standaloneThemeService.ts b/src/vs/editor/standalone/browser/standaloneThemeService.ts index 5fc765ed2d1..959ca90487a 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeService.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeService.ts @@ -261,7 +261,7 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe this._updateCSS(); })); - addMatchMediaChangeListener('(forced-colors: active)', () => { + addMatchMediaChangeListener(mainWindow, '(forced-colors: active)', () => { this._onOSSchemeChanged(); }); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 0916d9b82a9..31cfb0aa6a9 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { getWindow } from 'vs/base/browser/dom'; import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { Barrier, Queue, raceCancellation, raceCancellationError } from 'vs/base/common/async'; @@ -773,9 +774,9 @@ export class InlineChatController implements IEditorContribution { this._ignoreModelContentChanged = true; this._activeSession.wholeRange.trackEdits(editOperations); if (opts) { - await this._strategy.makeProgressiveChanges(editOperations, opts); + await this._strategy.makeProgressiveChanges(getWindow(this._editor.getContainerDomNode()), editOperations, opts); } else { - await this._strategy.makeChanges(editOperations); + await this._strategy.makeChanges(getWindow(this._editor.getContainerDomNode()), editOperations); } this._ctxDidEdit.set(this._activeSession.hasChangedText); } finally { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 83b5c32a982..56fe6fced08 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { disposableWindowInterval } from 'vs/base/browser/dom'; -import { $window } from 'vs/base/browser/window'; import { IAction, toAction } from 'vs/base/common/actions'; import { coalesceInPlace, equals, tail } from 'vs/base/common/arrays'; import { AsyncIterableObject, AsyncIterableSource } from 'vs/base/common/async'; @@ -77,9 +76,9 @@ export abstract class EditModeStrategy { abstract cancel(): Promise; - abstract makeProgressiveChanges(edits: ISingleEditOperation[], timings: ProgressingEditsOptions): Promise; + abstract makeProgressiveChanges(targetWindow: Window, edits: ISingleEditOperation[], timings: ProgressingEditsOptions): Promise; - abstract makeChanges(edits: ISingleEditOperation[]): Promise; + abstract makeChanges(targetWindow: Window, edits: ISingleEditOperation[]): Promise; abstract undoChanges(altVersionId: number): Promise; @@ -151,7 +150,7 @@ export class PreviewStrategy extends EditModeStrategy { // nothing to do } - override async makeChanges(_edits: ISingleEditOperation[]): Promise { + override async makeChanges(_targetWindow: Window, _edits: ISingleEditOperation[]): Promise { // nothing to do } @@ -244,7 +243,7 @@ export class LivePreviewStrategy extends EditModeStrategy { const targetAltVersion = textModelNSnapshotAltVersion ?? textModelNAltVersion; await undoModelUntil(modelN, targetAltVersion); } - override async makeChanges(edits: ISingleEditOperation[]): Promise { + override async makeChanges(_targetWindow: Window, edits: ISingleEditOperation[]): Promise { const cursorStateComputerAndInlineDiffCollection: ICursorStateComputer = (undoEdits) => { let last: Position | null = null; for (const edit of undoEdits) { @@ -266,7 +265,7 @@ export class LivePreviewStrategy extends EditModeStrategy { await this._updateDiffZones(); } - override async makeProgressiveChanges(edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise { + override async makeProgressiveChanges(targetWindow: Window, edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise { // push undo stop before first edit if (++this._editCount === 1) { @@ -285,7 +284,7 @@ export class LivePreviewStrategy extends EditModeStrategy { const wordCount = countWords(edit.text ?? ''); const speed = wordCount / durationInSec; // console.log({ durationInSec, wordCount, speed: wordCount / durationInSec }); - await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(edit, speed, opts.token)); + await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(targetWindow, edit, speed, opts.token)); } await renderTask; @@ -444,7 +443,7 @@ export function asAsyncEdit(edit: IIdentifiedSingleEditOperation): AsyncTextEdit } satisfies AsyncTextEdit; } -export function asProgressiveEdit(edit: IIdentifiedSingleEditOperation, wordsPerSec: number, token: CancellationToken): AsyncTextEdit { +export function asProgressiveEdit(targetWindow: Window, edit: IIdentifiedSingleEditOperation, wordsPerSec: number, token: CancellationToken): AsyncTextEdit { wordsPerSec = Math.max(10, wordsPerSec); @@ -452,7 +451,7 @@ export function asProgressiveEdit(edit: IIdentifiedSingleEditOperation, wordsPer let newText = edit.text ?? ''; // const wordCount = countWords(newText); - const handle = disposableWindowInterval($window, () => { + const handle = disposableWindowInterval(targetWindow, () => { const r = getNWords(newText, 1); stream.emitOne(r.value); @@ -584,15 +583,15 @@ export class LiveStrategy extends EditModeStrategy { await undoModelUntil(textModelN, altVersionId); } - override async makeChanges(edits: ISingleEditOperation[]): Promise { - return this._makeChanges(edits, undefined); + override async makeChanges(targetWindow: Window, edits: ISingleEditOperation[]): Promise { + return this._makeChanges(targetWindow, edits, undefined); } - override async makeProgressiveChanges(edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise { - return this._makeChanges(edits, opts); + override async makeProgressiveChanges(targetWindow: Window, edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise { + return this._makeChanges(targetWindow, edits, opts); } - private async _makeChanges(edits: ISingleEditOperation[], opts: ProgressingEditsOptions | undefined): Promise { + private async _makeChanges(targetWindow: Window, edits: ISingleEditOperation[], opts: ProgressingEditsOptions | undefined): Promise { // push undo stop before first edit if (++this._editCount === 1) { @@ -625,7 +624,7 @@ export class LiveStrategy extends EditModeStrategy { const wordCount = countWords(edit.text ?? ''); const speed = wordCount / durationInSec; // console.log({ durationInSec, wordCount, speed: wordCount / durationInSec }); - await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(edit, speed, opts.token), progress); + await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(targetWindow, edit, speed, opts.token), progress); } } else { diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts index 7dee82705f5..f02c0943fc7 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Dimension, h } from 'vs/base/browser/dom'; +import { Dimension, getWindow, h } from 'vs/base/browser/dom'; import { CancelablePromise, Queue, createCancelablePromise, raceCancellationError } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; @@ -468,7 +468,7 @@ class EditStrategy { const wordCount = countWords(edit.text ?? ''); const speed = wordCount / durationInSec; // console.log({ durationInSec, wordCount, speed: wordCount / durationInSec }); - await performAsyncTextEdit(editor.getModel(), asProgressiveEdit(edit, speed, opts.token)); + await performAsyncTextEdit(editor.getModel(), asProgressiveEdit(getWindow(editor.getContainerDomNode()), edit, speed, opts.token)); } } diff --git a/src/vs/workbench/services/themes/browser/browserHostColorSchemeService.ts b/src/vs/workbench/services/themes/browser/browserHostColorSchemeService.ts index ac32b57b136..ca31f33374f 100644 --- a/src/vs/workbench/services/themes/browser/browserHostColorSchemeService.ts +++ b/src/vs/workbench/services/themes/browser/browserHostColorSchemeService.ts @@ -25,10 +25,10 @@ export class BrowserHostColorSchemeService extends Disposable implements IHostCo private registerListeners(): void { - addMatchMediaChangeListener('(prefers-color-scheme: dark)', () => { + addMatchMediaChangeListener(mainWindow, '(prefers-color-scheme: dark)', () => { this._onDidSchemeChangeEvent.fire(); }); - addMatchMediaChangeListener('(forced-colors: active)', () => { + addMatchMediaChangeListener(mainWindow, '(forced-colors: active)', () => { this._onDidSchemeChangeEvent.fire(); }); } From ce283e0bc15bb106c42262cc45fa9c2702d58849 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 28 Dec 2023 06:31:09 +0100 Subject: [PATCH 0030/1897] editors - separate split in editor action --- .../parts/editor/editor.contribution.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 146e91e1b59..fe3238769b3 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -10,7 +10,8 @@ import { IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/ed import { TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, EditorPartMultipleEditorGroupsContext, ActiveEditorDirtyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, - EditorTabsVisibleContext, ActiveEditorLastInGroupContext, EditorPartMaximizedEditorGroupContext, MultipleEditorGroupsContext, InEditorZenModeContext, IsAuxiliaryEditorPartContext, ActiveCompareEditorOriginalWriteableContext + EditorTabsVisibleContext, ActiveEditorLastInGroupContext, EditorPartMaximizedEditorGroupContext, MultipleEditorGroupsContext, InEditorZenModeContext, + IsAuxiliaryEditorPartContext, ActiveCompareEditorOriginalWriteableContext } from 'vs/workbench/common/contextkeys'; import { SideBySideEditorInput, SideBySideEditorInputSerializer } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; @@ -41,13 +42,16 @@ import { ReOpenInTextEditorAction, DuplicateGroupDownAction, DuplicateGroupLeftAction, DuplicateGroupRightAction, DuplicateGroupUpAction, ToggleEditorTypeAction, SplitEditorToAboveGroupAction, SplitEditorToBelowGroupAction, SplitEditorToFirstGroupAction, SplitEditorToLastGroupAction, SplitEditorToLeftGroupAction, SplitEditorToNextGroupAction, SplitEditorToPreviousGroupAction, SplitEditorToRightGroupAction, NavigateForwardInEditsAction, NavigateBackwardsInEditsAction, NavigateForwardInNavigationsAction, NavigateBackwardsInNavigationsAction, NavigatePreviousInNavigationsAction, NavigatePreviousInEditsAction, NavigateToLastNavigationLocationAction, - MaximizeGroupHideSidebarAction, MoveEditorToNewWindowAction, CopyEditorToNewindowAction, RestoreEditorsToMainWindowAction, ToggleMaximizeEditorGroupAction, MinimizeOtherGroupsHideSidebarAction, CopyEditorGroupToNewWindowAction, MoveEditorGroupToNewWindowAction, NewEmptyEditorWindowAction + MaximizeGroupHideSidebarAction, MoveEditorToNewWindowAction, CopyEditorToNewindowAction, RestoreEditorsToMainWindowAction, ToggleMaximizeEditorGroupAction, MinimizeOtherGroupsHideSidebarAction, CopyEditorGroupToNewWindowAction, + MoveEditorGroupToNewWindowAction, NewEmptyEditorWindowAction } from 'vs/workbench/browser/parts/editor/editorActions'; import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_EDITOR_GROUP_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_PINNED_EDITOR_COMMAND_ID, CLOSE_SAVED_EDITORS_COMMAND_ID, GOTO_NEXT_CHANGE, GOTO_PREVIOUS_CHANGE, KEEP_EDITOR_COMMAND_ID, PIN_EDITOR_COMMAND_ID, SHOW_EDITORS_IN_GROUP, SPLIT_EDITOR_DOWN, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, TOGGLE_DIFF_SIDE_BY_SIDE, TOGGLE_KEEP_EDITORS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, setup as registerEditorCommands, REOPEN_WITH_COMMAND_ID, - TOGGLE_LOCK_GROUP_COMMAND_ID, UNLOCK_GROUP_COMMAND_ID, SPLIT_EDITOR_IN_GROUP, JOIN_EDITOR_IN_GROUP, FOCUS_FIRST_SIDE_EDITOR, FOCUS_SECOND_SIDE_EDITOR, TOGGLE_SPLIT_EDITOR_IN_GROUP_LAYOUT, LOCK_GROUP_COMMAND_ID, SPLIT_EDITOR, TOGGLE_MAXIMIZE_EDITOR_GROUP, MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, MOVE_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID, DIFF_SWAP_SIDES + TOGGLE_LOCK_GROUP_COMMAND_ID, UNLOCK_GROUP_COMMAND_ID, SPLIT_EDITOR_IN_GROUP, JOIN_EDITOR_IN_GROUP, FOCUS_FIRST_SIDE_EDITOR, FOCUS_SECOND_SIDE_EDITOR, TOGGLE_SPLIT_EDITOR_IN_GROUP_LAYOUT, LOCK_GROUP_COMMAND_ID, + SPLIT_EDITOR, TOGGLE_MAXIMIZE_EDITOR_GROUP, MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, MOVE_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, + NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID, DIFF_SWAP_SIDES } from 'vs/workbench/browser/parts/editor/editorCommands'; import { inQuickPickContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -394,10 +398,10 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_ED MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_DOWN, title: localize('splitDown', "Split Down") }, group: '5_split', order: 20 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_LEFT, title: localize('splitLeft', "Split Left") }, group: '5_split', order: 30 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_RIGHT, title: localize('splitRight', "Split Right") }, group: '5_split', order: 40 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_IN_GROUP, title: localize('splitInGroup', "Split in Group") }, group: '5_split', order: 50, when: ActiveEditorCanSplitInGroupContext }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: JOIN_EDITOR_IN_GROUP, title: localize('joinInGroup', "Join in Group") }, group: '5_split', order: 50, when: SideBySideEditorActiveContext }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, title: localize('moveToNewWindow', "Move into New Window") }, group: '6_new_window', order: 10 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, title: localize('copyToNewWindow', "Copy into New Window") }, group: '6_new_window', order: 20 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_IN_GROUP, title: localize('splitInGroup', "Split in Group") }, group: '6_split_in_group', order: 10, when: ActiveEditorCanSplitInGroupContext }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: JOIN_EDITOR_IN_GROUP, title: localize('joinInGroup', "Join in Group") }, group: '6_split_in_group', order: 10, when: SideBySideEditorActiveContext }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, title: localize('moveToNewWindow', "Move into New Window") }, group: '7_new_window', order: 10 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, title: localize('copyToNewWindow', "Copy into New Window") }, group: '7_new_window', order: 20 }); // Editor Title Menu MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_DIFF_SIDE_BY_SIDE, title: localize('inlineView', "Inline View"), toggled: ContextKeyExpr.equals('config.diffEditor.renderSideBySide', false) }, group: '1_diff', order: 10, when: ContextKeyExpr.has('isInDiffEditor') }); From 83b909c39f0ce5368d3a41a30c609de86a2e106e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 28 Dec 2023 08:04:55 +0100 Subject: [PATCH 0031/1897] aux window - ensure focus window before showing editor close confirmation --- src/vs/workbench/browser/parts/editor/editorGroupView.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index bb23015b994..a51faaefc23 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -55,6 +55,7 @@ import { EditorGroupWatermark } from 'vs/workbench/browser/parts/editor/editorGr import { EditorTitleControl } from 'vs/workbench/browser/parts/editor/editorTitleControl'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class EditorGroupView extends Themable implements IEditorGroupView { @@ -154,7 +155,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @ILogService private readonly logService: ILogService, - @IEditorResolverService private readonly editorResolverService: IEditorResolverService + @IEditorResolverService private readonly editorResolverService: IEditorResolverService, + @IHostService private readonly hostService: IHostService ) { super(themeService); @@ -1605,6 +1607,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { await this.doOpenEditor(editor); } + // Ensure our window has focus since we are about to show a dialog + await this.hostService.focus(getWindow(this.element)); + // Let editor handle confirmation if implemented if (typeof editor.closeHandler?.confirm === 'function') { confirmation = await editor.closeHandler.confirm([{ editor, groupId: this.id }]); From df8db3a75a49b85c9636530a3557cdbc639f7bdc Mon Sep 17 00:00:00 2001 From: Mahmoud Salah Date: Sun, 31 Dec 2023 08:28:35 +0100 Subject: [PATCH 0032/1897] =?UTF-8?q?For=20open=20diff=20editors,=20resolv?= =?UTF-8?q?e=20the=20underlying=20original=20editor=20to=20set=20=E2=80=A6?= =?UTF-8?q?=20(#201597)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * For open diff editors, resolve the underlying original editor to set the appropriate language debugger. * Get modified editor rather than original. --------- Co-authored-by: Mahmoud Khalil --- .../workbench/contrib/debug/browser/welcomeView.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/welcomeView.ts b/src/vs/workbench/contrib/debug/browser/welcomeView.ts index cdff715b9bf..b0c09a24c52 100644 --- a/src/vs/workbench/contrib/debug/browser/welcomeView.ts +++ b/src/vs/workbench/contrib/debug/browser/welcomeView.ts @@ -20,7 +20,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { WorkbenchStateContext } from 'vs/workbench/common/contextkeys'; import { OpenFolderAction, OpenFileAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; -import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -62,7 +62,11 @@ export class WelcomeView extends ViewPane { this.debugStartLanguageContext.set(lastSetLanguage); const setContextKey = () => { - const editorControl = this.editorService.activeTextEditorControl; + let editorControl = this.editorService.activeTextEditorControl; + if (isDiffEditor(editorControl)) { + editorControl = editorControl.getModifiedEditor(); + } + if (isCodeEditor(editorControl)) { const model = editorControl.getModel(); const language = model ? model.getLanguageId() : undefined; @@ -82,7 +86,11 @@ export class WelcomeView extends ViewPane { this._register(editorService.onDidActiveEditorChange(() => { disposables.clear(); - const editorControl = this.editorService.activeTextEditorControl; + let editorControl = this.editorService.activeTextEditorControl; + if (isDiffEditor(editorControl)) { + editorControl = editorControl.getModifiedEditor(); + } + if (isCodeEditor(editorControl)) { disposables.add(editorControl.onDidChangeModelLanguage(setContextKey)); } From 275819ac9d6d5a049994103a97648eddc988d1f1 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Tue, 2 Jan 2024 21:27:19 +0500 Subject: [PATCH 0033/1897] inline chat: refactor: use RERUN_INPUT instead of ACCEPT_INPUT for re-running without intent detection --- .../browser/inlineChatController.ts | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index e5960753a60..5bf9e74ce43 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -379,20 +379,7 @@ export class InlineChatController implements IEditorContribution { this._sessionStore.add(this._zone.value.widget.onRequestWithoutIntentDetection(async () => { options.withIntentDetection = false; - // undo changes - if (this._activeSession && this._activeSession.lastExchange && this._strategy) { - const { lastExchange } = this._activeSession; - if (lastExchange.response instanceof ReplyResponse) { - try { - this._ignoreModelContentChanged = true; - await this._strategy.undoChanges(lastExchange.response.modelAltVersionId); - } finally { - this._ignoreModelContentChanged = false; - } - } - } - - this.acceptInput(); + this.regenerate(); })); const wholeRangeDecoration = this._editor.createDecorationsCollection(); @@ -531,7 +518,9 @@ export class InlineChatController implements IEditorContribution { if (message & Message.RERUN_INPUT && this._activeSession.lastExchange) { const { lastExchange } = this._activeSession; - this._activeSession.addInput(lastExchange.prompt.retry()); + if (options.withIntentDetection === undefined) { // @ulugbekna: if we're re-running with intent detection turned off, no need to update `attempt` # + this._activeSession.addInput(lastExchange.prompt.retry()); + } if (lastExchange.response instanceof ReplyResponse) { try { this._ignoreModelContentChanged = true; From 2db6dc982aca50da4ff353d74b063909b41e1c8c Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Tue, 2 Jan 2024 21:29:19 +0500 Subject: [PATCH 0034/1897] inline chat: better wording for re-running without intent detection --- src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 20469180e6c..6678164b6f5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -682,7 +682,7 @@ export class InlineChatWidget { this._slashCommandUsedDisposables.clear(); - const label = localize('slashCommandUsed', "Using {0} to generate response. [[Re-run without]]", `\`\`/${details.command}\`\``); + const label = localize('slashCommandUsed', "Using {0} to generate response ([[re-run without]])", `\`\`/${details.command}\`\``); const usingSlashCommandText = renderFormattedText(label, { inline: true, renderCodeSegments: true, From 12656dc79dc91f1e782e1b85b549fc85cb7886c2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 3 Jan 2024 08:40:05 +0100 Subject: [PATCH 0035/1897] aux window - fix issue with restoring when main window is empty (#201698) --- .../workbench/browser/parts/editor/editorPart.ts | 14 ++++++-------- .../workbench/browser/parts/editor/editorParts.ts | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 8aac8aa2023..be83c793d58 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -251,8 +251,8 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { return !!this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY]; } - private _didRestoreState = false; - get didRestoreState(): boolean { return this._didRestoreState; } + private _willRestoreState = false; + get willRestoreState(): boolean { return this._willRestoreState; } getGroups(order = GroupsOrder.CREATION_TIME): IEditorGroupView[] { switch (order) { @@ -986,7 +986,8 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { )); // Grid control - this.doCreateGridControl(options); + this._willRestoreState = !options || options.restorePreviousState; + this.doCreateGridControl(); // Centered layout widget this.centeredLayoutWidget = this._register(new CenteredViewLayout(this.container, this.gridWidgetView, this.profileMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY], this._partOptions.centeredLayoutFixedWidth)); @@ -1145,11 +1146,11 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { return false; } - private doCreateGridControl(options?: IEditorPartCreationOptions): void { + private doCreateGridControl(): void { // Grid Widget (with previous UI state) let restoreError = false; - if (!options || options.restorePreviousState) { + if (this._willRestoreState) { restoreError = !this.doCreateGridControlWithPreviousState(); } @@ -1179,9 +1180,6 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { // Grid Widget this.doCreateGridControlWithState(uiState.serializedGrid, uiState.activeGroup); - - // Remember that we did restore previous state - this._didRestoreState = true; } catch (error) { // Log error diff --git a/src/vs/workbench/browser/parts/editor/editorParts.ts b/src/vs/workbench/browser/parts/editor/editorParts.ts index f99c1230bd6..5ec469b889d 100644 --- a/src/vs/workbench/browser/parts/editor/editorParts.ts +++ b/src/vs/workbench/browser/parts/editor/editorParts.ts @@ -203,7 +203,7 @@ export class EditorParts extends MultiWindowParts implements IEditor // when the main part did restore. It is possible // that restoring was not attempted because specific // editors were opened. - if (this.mainPart.didRestoreState) { + if (this.mainPart.willRestoreState) { const uiState: IEditorPartsUIState | undefined = this.workspaceMemento[EditorParts.EDITOR_PARTS_UI_STATE_STORAGE_KEY]; if (uiState?.auxiliary.length) { const auxiliaryEditorPartPromises: Promise[] = []; From 93a6a2a12574e98ecaf5a47e991a8c0db660462a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 3 Jan 2024 12:25:14 +0100 Subject: [PATCH 0036/1897] untitled - encapsulate and update model within input (#201712) --- .../parts/editor/textResourceEditor.ts | 4 +- .../test/browser/textEditorService.test.ts | 6 +-- .../common/untitledTextEditorHandler.ts | 4 +- .../common/untitledTextEditorInput.ts | 30 ++++++++++--- .../common/untitledTextEditorService.ts | 11 +++++ .../test/browser/untitledTextEditor.test.ts | 43 +++++++++++++------ 6 files changed, 72 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 2da5fb20fa3..34762256f40 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -156,7 +156,7 @@ export class TextResourceEditor extends AbstractTextResourceEditor { } private onDidEditorPaste(e: IPasteEvent, codeEditor: ICodeEditor): void { - if (this.input instanceof UntitledTextEditorInput && this.input.model.hasLanguageSetExplicitly) { + if (this.input instanceof UntitledTextEditorInput && this.input.hasLanguageSetExplicitly) { return; // do not override language if it was set explicitly } @@ -205,7 +205,7 @@ export class TextResourceEditor extends AbstractTextResourceEditor { if (candidateLanguage && candidateLanguage.id !== PLAINTEXT_LANGUAGE_ID) { if (this.input instanceof UntitledTextEditorInput && candidateLanguage.source === 'event') { // High confidence, set language id at TextEditorModel level to block future auto-detection - this.input.model.setLanguageId(candidateLanguage.id); + this.input.setLanguageId(candidateLanguage.id); } else { textModel.setLanguage(this.languageService.createById(candidateLanguage.id)); } diff --git a/src/vs/workbench/services/textfile/test/browser/textEditorService.test.ts b/src/vs/workbench/services/textfile/test/browser/textEditorService.test.ts index 4e1ea75097f..749f3cc6dc5 100644 --- a/src/vs/workbench/services/textfile/test/browser/textEditorService.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textEditorService.test.ts @@ -129,14 +129,14 @@ suite('TextEditorService', () => { // Untyped Input (untitled with file path) input = disposables.add(service.createTextEditor({ resource: URI.file('/some/path.txt'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } })); assert(input instanceof UntitledTextEditorInput); - assert.ok((input as UntitledTextEditorInput).model.hasAssociatedFilePath); + assert.ok((input as UntitledTextEditorInput).hasAssociatedFilePath); // Untyped Input (untitled with untitled resource) untypedInput = { resource: URI.parse('untitled://Untitled-1'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }; assert.ok(isUntitledResourceEditorInput(untypedInput)); input = disposables.add(service.createTextEditor(untypedInput)); assert(input instanceof UntitledTextEditorInput); - assert.ok(!(input as UntitledTextEditorInput).model.hasAssociatedFilePath); + assert.ok(!(input as UntitledTextEditorInput).hasAssociatedFilePath); // Untyped input (untitled with custom resource, but forceUntitled) untypedInput = { resource: URI.file('/fake'), forceUntitled: true }; @@ -149,7 +149,7 @@ suite('TextEditorService', () => { input = disposables.add(service.createTextEditor({ resource: URI.parse('untitled-custom://some/path'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } })); assert(input instanceof UntitledTextEditorInput); - assert.ok((input as UntitledTextEditorInput).model.hasAssociatedFilePath); + assert.ok((input as UntitledTextEditorInput).hasAssociatedFilePath); provider.dispose(); diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts index 919261dd687..3674546202b 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts @@ -47,7 +47,7 @@ export class UntitledTextEditorInputSerializer implements IEditorSerializer { const untitledTextEditorInput = editorInput as UntitledTextEditorInput; let resource = untitledTextEditorInput.resource; - if (untitledTextEditorInput.model.hasAssociatedFilePath) { + if (untitledTextEditorInput.hasAssociatedFilePath) { resource = toLocalResource(resource, this.environmentService.remoteAuthority, this.pathService.defaultUriScheme); // untitled with associated file path use the local schema } @@ -59,7 +59,7 @@ export class UntitledTextEditorInputSerializer implements IEditorSerializer { const languageIdCandidate = untitledTextEditorInput.getLanguageId(); if (languageIdCandidate !== PLAINTEXT_LANGUAGE_ID) { languageId = languageIdCandidate; - } else if (untitledTextEditorInput.model.hasLanguageSetExplicitly) { + } else if (untitledTextEditorInput.hasLanguageSetExplicitly) { languageId = languageIdCandidate; } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts index 766081132c1..1d67b04088d 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts @@ -18,7 +18,7 @@ import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { dispose, IReference } from 'vs/base/common/lifecycle'; +import { DisposableStore, dispose, IReference } from 'vs/base/common/lifecycle'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; /** @@ -37,10 +37,11 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp } private modelResolve: Promise | undefined = undefined; + private readonly modelDisposables = this._register(new DisposableStore()); private cachedUntitledTextEditorModelReference: IReference | undefined = undefined; constructor( - readonly model: IUntitledTextEditorModel, + protected model: IUntitledTextEditorModel, @ITextFileService textFileService: ITextFileService, @ILabelService labelService: ILabelService, @IEditorService editorService: IEditorService, @@ -54,16 +55,31 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp super(model.resource, undefined, editorService, textFileService, labelService, fileService, filesConfigurationService, textResourceConfigurationService); this.registerModelListeners(model); + + this._register(this.textFileService.untitled.onDidCreate(model => this.onDidCreateUntitledModel(model))); } private registerModelListeners(model: IUntitledTextEditorModel): void { + this.modelDisposables.clear(); // re-emit some events from the model - this._register(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); - this._register(model.onDidChangeName(() => this._onDidChangeLabel.fire())); + this.modelDisposables.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + this.modelDisposables.add(model.onDidChangeName(() => this._onDidChangeLabel.fire())); // a reverted untitled text editor model renders this input disposed - this._register(model.onDidRevert(() => this.dispose())); + this.modelDisposables.add(model.onDidRevert(() => this.dispose())); + } + + private onDidCreateUntitledModel(model: IUntitledTextEditorModel): void { + if (isEqual(model.resource, this.model.resource) && model !== this.model) { + + // Ensure that we keep our model up to date with + // the actual model from the service so that we + // never get out of sync with the truth. + + this.model = model; + this.registerModelListeners(model); + } } override getName(): string { @@ -116,6 +132,10 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp return this.model.setEncoding(encoding); } + get hasLanguageSetExplicitly() { return this.model.hasLanguageSetExplicitly; } + + get hasAssociatedFilePath() { return this.model.hasAssociatedFilePath; } + setLanguageId(languageId: string, source?: string): void { this.model.setLanguageId(languageId, source); } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index 8b4d6a5919e..dc465ef62fb 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -78,6 +78,11 @@ export interface IUntitledTextEditorModelManager { */ readonly onDidChangeLabel: Event; + /** + * Events for when untitled text editor models are created. + */ + readonly onDidCreate: Event; + /** * Events for when untitled text editors are about to be disposed. */ @@ -143,6 +148,9 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe private readonly _onDidChangeEncoding = this._register(new Emitter()); readonly onDidChangeEncoding = this._onDidChangeEncoding.event; + private readonly _onDidCreate = this._register(new Emitter()); + readonly onDidCreate = this._onDidCreate.event; + private readonly _onWillDispose = this._register(new Emitter()); readonly onWillDispose = this._onWillDispose.event; @@ -267,6 +275,9 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe // Add to cache this.mapResourceToModel.set(model.resource, model); + // Emit as event + this._onDidCreate.fire(model); + // If the model is dirty right from the beginning, // make sure to emit this as an event if (model.isDirty()) { diff --git a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts index f65c4f2c15d..133ee3f3298 100644 --- a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts @@ -26,6 +26,10 @@ import { timeout } from 'vs/base/common/async'; suite('Untitled text editors', () => { + class TestUntitledTextEditorInput extends UntitledTextEditorInput { + getModel() { return this.model; } + } + const disposables = new DisposableStore(); let instantiationService: IInstantiationService; let accessor: TestServiceAccessor; @@ -44,11 +48,19 @@ suite('Untitled text editors', () => { const service = accessor.untitledTextEditorService; const workingCopyService = accessor.workingCopyService; - const input1 = instantiationService.createInstance(UntitledTextEditorInput, service.create()); + const events: IUntitledTextEditorModel[] = []; + disposables.add(service.onDidCreate(model => { + events.push(model); + })); + + const input1 = instantiationService.createInstance(TestUntitledTextEditorInput, service.create()); await input1.resolve(); - assert.strictEqual(service.get(input1.resource), input1.model); + assert.strictEqual(service.get(input1.resource), input1.getModel()); assert.ok(!accessor.untitledTextEditorService.isUntitledWithAssociatedResource(input1.resource)); + assert.strictEqual(events.length, 1); + assert.strictEqual(events[0].resource.toString(), input1.getModel().resource.toString()); + assert.ok(service.get(input1.resource)); assert.ok(!service.get(URI.file('testing'))); @@ -59,16 +71,16 @@ suite('Untitled text editors', () => { assert.ok(!input1.hasCapability(EditorInputCapabilities.RequiresTrust)); assert.ok(!input1.hasCapability(EditorInputCapabilities.Scratchpad)); - const input2 = instantiationService.createInstance(UntitledTextEditorInput, service.create()); - assert.strictEqual(service.get(input2.resource), input2.model); + const input2 = instantiationService.createInstance(TestUntitledTextEditorInput, service.create()); + assert.strictEqual(service.get(input2.resource), input2.getModel()); // toUntyped() const untypedInput = input1.toUntyped({ preserveViewState: 0 }); assert.strictEqual(untypedInput.forceUntitled, true); // get() - assert.strictEqual(service.get(input1.resource), input1.model); - assert.strictEqual(service.get(input2.resource), input2.model); + assert.strictEqual(service.get(input1.resource), input1.getModel()); + assert.strictEqual(service.get(input2.resource), input2.getModel()); // revert() await input1.revert(0); @@ -80,6 +92,9 @@ suite('Untitled text editors', () => { assert.strictEqual(await service.resolve({ untitledResource: input2.resource }), model); assert.ok(service.get(model.resource)); + assert.strictEqual(events.length, 2); + assert.strictEqual(events[1].resource.toString(), input2.resource.toString()); + assert.ok(!input2.isDirty()); const resourcePromise = awaitDidChangeDirty(accessor.untitledTextEditorService); @@ -214,10 +229,10 @@ suite('Untitled text editors', () => { const service = accessor.untitledTextEditorService; const workingCopyService = accessor.workingCopyService; - const untitled = disposables.add(instantiationService.createInstance(UntitledTextEditorInput, service.create({ initialValue: 'Hello World' }))); + const untitled = disposables.add(instantiationService.createInstance(TestUntitledTextEditorInput, service.create({ initialValue: 'Hello World' }))); assert.ok(untitled.isDirty()); - const backup = (await untitled.model.backup(CancellationToken.None)).content; + const backup = (await untitled.getModel().backup(CancellationToken.None)).content; if (isReadableStream(backup)) { const value = await streamToBuffer(backup as VSBufferReadableStream); assert.strictEqual(value.toString(), 'Hello World'); @@ -307,9 +322,9 @@ suite('Untitled text editors', () => { const model = disposables.add(service.create()); const input = disposables.add(instantiationService.createInstance(UntitledTextEditorInput, model)); - assert.ok(!input.model.hasLanguageSetExplicitly); + assert.ok(!input.hasLanguageSetExplicitly); input.setLanguageId(PLAINTEXT_LANGUAGE_ID); - assert.ok(input.model.hasLanguageSetExplicitly); + assert.ok(input.hasLanguageSetExplicitly); assert.strictEqual(input.getLanguageId(), PLAINTEXT_LANGUAGE_ID); }); @@ -327,9 +342,9 @@ suite('Untitled text editors', () => { const input = disposables.add(instantiationService.createInstance(UntitledTextEditorInput, model)); disposables.add(await input.resolve()); - assert.ok(!input.model.hasLanguageSetExplicitly); + assert.ok(!input.hasLanguageSetExplicitly); model.textEditorModel!.setLanguage(accessor.languageService.createById(language)); - assert.ok(input.model.hasLanguageSetExplicitly); + assert.ok(input.hasLanguageSetExplicitly); assert.strictEqual(model.getLanguageId(), language); }); @@ -346,12 +361,12 @@ suite('Untitled text editors', () => { const input = disposables.add(instantiationService.createInstance(UntitledTextEditorInput, model)); await input.resolve(); - assert.ok(!input.model.hasLanguageSetExplicitly); + assert.ok(!input.hasLanguageSetExplicitly); model.textEditorModel!.setLanguage( accessor.languageService.createById(language), // This is really what this is testing LanguageDetectionLanguageEventSource); - assert.ok(!input.model.hasLanguageSetExplicitly); + assert.ok(!input.hasLanguageSetExplicitly); assert.strictEqual(model.getLanguageId(), language); }); From 17e0dec8cf3a60d75e5517093752af6f8e8e1978 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 3 Jan 2024 12:29:18 +0100 Subject: [PATCH 0037/1897] Fixes #198471 --- .../diffEditorViewZones.ts | 23 ++++++++++++++++--- .../common/services/editorSimpleWorker.ts | 3 ++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts index 480280da8f1..ac5b8886ac3 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts @@ -159,10 +159,17 @@ export class DiffEditorViewZones extends Disposable { const deletedCodeLineBreaksComputer = !renderSideBySide ? this._editors.modified._getViewModel()?.createLineBreaksComputer() : undefined; if (deletedCodeLineBreaksComputer) { + const originalModel = this._editors.original.getModel()!; for (const a of alignmentsVal) { if (a.diff) { for (let i = a.originalRange.startLineNumber; i < a.originalRange.endLineNumberExclusive; i++) { - deletedCodeLineBreaksComputer?.addRequest(this._editors.original.getModel()!.getLineContent(i), null, null); + // `i` can be out of bound when the diff has not been updated yet. + // In this case, we do an early return. + // TODO@hediet: Fix this by applying the edit directly to the diff model, so that the diff is always valid. + if (i > originalModel.getLineCount()) { + return { orig: origViewZones, mod: modViewZones }; + } + deletedCodeLineBreaksComputer?.addRequest(originalModel.getLineContent(i), null, null); } } } @@ -186,8 +193,15 @@ export class DiffEditorViewZones extends Disposable { const deletedCodeDomNode = document.createElement('div'); deletedCodeDomNode.classList.add('view-lines', 'line-delete', 'monaco-mouse-cursor-text'); + const originalModel = this._editors.original.getModel()!; + // `a.originalRange` can be out of bound when the diff has not been updated yet. + // In this case, we do an early return. + // TODO@hediet: Fix this by applying the edit directly to the diff model, so that the diff is always valid. + if (a.originalRange.endLineNumberExclusive - 1 > originalModel.getLineCount()) { + return { orig: origViewZones, mod: modViewZones }; + } const source = new LineSource( - a.originalRange.mapToLineArray(l => this._editors.original.getModel()!.tokenization.getLineTokens(l)), + a.originalRange.mapToLineArray(l => originalModel.tokenization.getLineTokens(l)), a.originalRange.mapToLineArray(_ => lineBreakData[lineBreakDataIdx++]), mightContainNonBasicASCII, mightContainRTL, @@ -551,7 +565,10 @@ function computeRangeAlignment( // There is some unmodified text on this line before the diff emitAlignment(i.originalRange.startLineNumber, i.modifiedRange.startLineNumber); } - if (i.originalRange.endColumn < originalEditor.getModel()!.getLineMaxColumn(i.originalRange.endLineNumber)) { + const originalModel = originalEditor.getModel()!; + // When the diff is invalid, the ranges might be out of bounds (this should be fixed in the diff model by applying edits directly). + const maxColumn = i.originalRange.endLineNumber <= originalModel.getLineCount() ? originalModel.getLineMaxColumn(i.originalRange.endLineNumber) : Number.MAX_SAFE_INTEGER; + if (i.originalRange.endColumn < maxColumn) { // // There is some unmodified text on this line after the diff emitAlignment(i.originalRange.endLineNumber, i.modifiedRange.endLineNumber); } diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index 12b28f16713..f03e018cac0 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -410,7 +410,8 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { return null; } - return EditorSimpleWorker.computeDiff(original, modified, options, algorithm); + const result = EditorSimpleWorker.computeDiff(original, modified, options, algorithm); + return result; } private static computeDiff(originalTextModel: ICommonModel | ITextModel, modifiedTextModel: ICommonModel | ITextModel, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): IDiffComputationResult { From b69fb3a61bf0c51cb3210f1da71c0f4d3d0e509b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 3 Jan 2024 20:26:57 +0530 Subject: [PATCH 0038/1897] fix #201707 (#201722) --- .../contrib/extensions/browser/extensionsWorkbenchService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 92097d03ccc..34e22edab47 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -1102,7 +1102,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (isUninstalled) { const canRemoveRunningExtension = runningExtension && this.extensionService.canRemoveExtension(runningExtension); const isSameExtensionRunning = runningExtension && (!extension.server || extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension))); - if (!canRemoveRunningExtension && isSameExtensionRunning) { + if (!canRemoveRunningExtension && isSameExtensionRunning && !runningExtension.isUnderDevelopment) { return nls.localize('postUninstallTooltip', "Please reload Visual Studio Code to complete the uninstallation of this extension."); } return undefined; From a0b228b51c9c7f8224dd8873927016cfb6797a80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jan 2024 17:41:14 +0100 Subject: [PATCH 0039/1897] Bump actions/setup-node from 3 to 4 (#196967) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/basic.yml | 6 +++--- .github/workflows/ci.yml | 8 ++++---- .github/workflows/monaco-editor.yml | 2 +- .github/workflows/telemetry.yml | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index 3fd6daf0048..92c75ebdd9a 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -29,7 +29,7 @@ jobs: sudo update-rc.d xvfb defaults sudo service xvfb start - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc @@ -81,7 +81,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc @@ -143,7 +143,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a598efa9f37..b75e45b49c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc @@ -113,7 +113,7 @@ jobs: sudo update-rc.d xvfb defaults sudo service xvfb start - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc @@ -184,7 +184,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc @@ -256,7 +256,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc diff --git a/.github/workflows/monaco-editor.yml b/.github/workflows/monaco-editor.yml index 155e18b38cf..c62fd2b2697 100644 --- a/.github/workflows/monaco-editor.yml +++ b/.github/workflows/monaco-editor.yml @@ -20,7 +20,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc diff --git a/.github/workflows/telemetry.yml b/.github/workflows/telemetry.yml index ab1559d8fa6..d463a0e2eca 100644 --- a/.github/workflows/telemetry.yml +++ b/.github/workflows/telemetry.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: 'actions/checkout@v4' - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: node-version: 'lts/*' From 0a08e6fdcd514741ceb679fb38c746550b5aabf3 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 3 Jan 2024 11:02:52 -0700 Subject: [PATCH 0040/1897] Don't dispose the CancellationTokenSource until all onCancellationRequested handlers have run (#199067) --- src/vs/base/common/async.ts | 2 +- src/vs/base/test/common/async.test.ts | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index e2ea4a6fb69..a997633ffe2 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -28,7 +28,6 @@ export function createCancelablePromise(callback: (token: CancellationToken) const promise = new Promise((resolve, reject) => { const subscription = source.token.onCancellationRequested(() => { subscription.dispose(); - source.dispose(); reject(new CancellationError()); }); Promise.resolve(thenable).then(value => { @@ -45,6 +44,7 @@ export function createCancelablePromise(callback: (token: CancellationToken) return >new class { cancel() { source.cancel(); + source.dispose(); } then(resolve?: ((value: T) => TResult1 | Promise) | undefined | null, reject?: ((reason: any) => TResult2 | Promise) | undefined | null): Promise { return promise.then(resolve, reject); diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index bd6ec6a4bcc..69f1ab5f56f 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -93,6 +93,27 @@ suite('Async', () => { return promise.then(() => assert.deepStrictEqual(order, ['in callback', 'afterCreate', 'cancelled', 'afterCancel', 'finally'])); }); + test('execution order (async with late listener)', async function () { + const order: string[] = []; + + const cancellablePromise = async.createCancelablePromise(async token => { + order.push('in callback'); + + await async.timeout(0); + store.add(token.onCancellationRequested(_ => order.push('cancelled'))); + cancellablePromise.cancel(); + order.push('afterCancel'); + }); + + order.push('afterCreate'); + + const promise = cancellablePromise + .then(undefined, err => null) + .then(() => order.push('finally')); + + return promise.then(() => assert.deepStrictEqual(order, ['in callback', 'afterCreate', 'cancelled', 'afterCancel', 'finally'])); + }); + test('get inner result', async function () { const promise = async.createCancelablePromise(token => { return async.timeout(12).then(_ => 1234); From a1cfe91f9c2235ceee493c933bbce5d46f0117c6 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 3 Jan 2024 12:55:21 -0600 Subject: [PATCH 0041/1897] Fix preferTypeOnlyAutoImports preference getter (#201376) --- .../src/languageFeatures/fileConfigurationManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts index 3162410275e..42e7f9f7461 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts @@ -192,7 +192,7 @@ export default class FileConfigurationManager extends Disposable { includeCompletionsWithObjectLiteralMethodSnippets: config.get('suggest.objectLiteralMethodSnippets.enabled', true), autoImportFileExcludePatterns: this.getAutoImportFileExcludePatternsPreference(preferencesConfig, vscode.workspace.getWorkspaceFolder(document.uri)?.uri), // @ts-expect-error until 5.3 #56090 - preferTypeOnlyAutoImports: config.get('preferTypeOnlyAutoImports', false), + preferTypeOnlyAutoImports: preferencesConfig.get('preferTypeOnlyAutoImports', false), useLabelDetailsInCompletionEntries: true, allowIncompleteCompletions: true, displayPartsForJSDoc: true, From edbf6bc17c743f3bf10c023c13979ef2e8c2fafd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Wed, 3 Jan 2024 20:10:31 +0100 Subject: [PATCH 0042/1897] fixes #201683 (#201737) --- .../electron-main/nativeHostMainService.ts | 33 +++++++++++-------- .../actions/installActions.ts | 9 +++++ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index 28e8d65665a..5d73705288b 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -42,6 +42,7 @@ import { WindowProfiler } from 'vs/platform/profiling/electron-main/windowProfil import { IV8Profile } from 'vs/platform/profiling/common/profiling'; import { IAuxiliaryWindowsMainService, isAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; +import { CancellationError } from 'vs/base/common/errors'; export interface INativeHostMainService extends AddFirstParameterToFunctions /* only methods, not events */, number | undefined /* window ID */> { } @@ -340,13 +341,15 @@ export class NativeHostMainService extends Disposable implements INativeHostMain ] }); - if (response === 0 /* OK */) { - try { - const command = `osascript -e "do shell script \\"mkdir -p /usr/local/bin && ln -sf \'${target}\' \'${source}\'\\" with administrator privileges"`; - await promisify(exec)(command); - } catch (error) { - throw new Error(localize('cantCreateBinFolder', "Unable to install the shell command '{0}'.", source)); - } + if (response === 1 /* Cancel */) { + throw new CancellationError(); + } + + try { + const command = `osascript -e "do shell script \\"mkdir -p /usr/local/bin && ln -sf \'${target}\' \'${source}\'\\" with administrator privileges"`; + await promisify(exec)(command); + } catch (error) { + throw new Error(localize('cantCreateBinFolder', "Unable to install the shell command '{0}'.", source)); } } } @@ -368,13 +371,15 @@ export class NativeHostMainService extends Disposable implements INativeHostMain ] }); - if (response === 0 /* OK */) { - try { - const command = `osascript -e "do shell script \\"rm \'${source}\'\\" with administrator privileges"`; - await promisify(exec)(command); - } catch (error) { - throw new Error(localize('cantUninstall', "Unable to uninstall the shell command '{0}'.", source)); - } + if (response === 1 /* Cancel */) { + throw new CancellationError(); + } + + try { + const command = `osascript -e "do shell script \\"rm \'${source}\'\\" with administrator privileges"`; + await promisify(exec)(command); + } catch (error) { + throw new Error(localize('cantUninstall', "Unable to uninstall the shell command '{0}'.", source)); } break; } diff --git a/src/vs/workbench/electron-sandbox/actions/installActions.ts b/src/vs/workbench/electron-sandbox/actions/installActions.ts index 5e397b97309..be65b67af45 100644 --- a/src/vs/workbench/electron-sandbox/actions/installActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/installActions.ts @@ -12,6 +12,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { INativeHostService } from 'vs/platform/native/common/native'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { IProductService } from 'vs/platform/product/common/productService'; +import { isCancellationError } from 'vs/base/common/errors'; const shellCommandCategory: ILocalizedString = { value: localize('shellCommand', "Shell Command"), original: 'Shell Command' }; @@ -39,6 +40,10 @@ export class InstallShellScriptAction extends Action2 { dialogService.info(localize('successIn', "Shell command '{0}' successfully installed in PATH.", productService.applicationName)); } catch (error) { + if (isCancellationError(error)) { + return; + } + dialogService.error(toErrorMessage(error)); } } @@ -68,6 +73,10 @@ export class UninstallShellScriptAction extends Action2 { dialogService.info(localize('successFrom', "Shell command '{0}' successfully uninstalled from PATH.", productService.applicationName)); } catch (error) { + if (isCancellationError(error)) { + return; + } + dialogService.error(toErrorMessage(error)); } } From 510df986c10eb12a19842546d2baeff774b4b387 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 3 Jan 2024 13:20:12 -0600 Subject: [PATCH 0043/1897] allow format, clear, save to have both audio cue and alert --- .../browser/accessibilityConfiguration.ts | 19 +++++++++++++------ .../browser/accessibleNotificationService.ts | 16 +++++----------- .../browser/audioCues.contribution.ts | 2 +- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 11b731840dd..06ae9408207 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -55,6 +55,7 @@ export const enum AccessibilityVerbositySettingId { } export const enum AccessibilityAlertSettingId { + Clear = 'accessibility.alert.clear', Save = 'accessibility.alert.save', Format = 'accessibility.alert.format', Breakpoint = 'accessibility.alert.breakpoint', @@ -135,7 +136,7 @@ const configuration: IConfigurationNode = { ...baseProperty }, [AccessibilityAlertSettingId.Save]: { - 'markdownDescription': localize('alert.save', "When in screen reader mode, alerts when a file is saved. Note that this will be ignored when {0} is enabled.", '`#audioCues.save#`'), + 'markdownDescription': localize('alert.save', "Alerts when a file is saved. Also see {0}.", '`#audioCues.save#`'), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'always', @@ -146,8 +147,14 @@ const configuration: IConfigurationNode = { ], tags: ['accessibility'] }, + [AccessibilityAlertSettingId.Clear]: { + 'markdownDescription': localize('alert.clear', "Alerts when a feature is cleared (for example, the terminal, Debug Console, or Output channel). Also see {0}.", '`#audioCues.clear#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, [AccessibilityAlertSettingId.Format]: { - 'markdownDescription': localize('alert.format', "When in screen reader mode, alerts when a file or notebook cell is formatted. Note that this will be ignored when {0} is enabled.", '`#audioCues.format#`'), + 'markdownDescription': localize('alert.format', "Alerts when a file or notebook cell is formatted. Also see {0}.", '`#audioCues.format#`'), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'always', @@ -159,25 +166,25 @@ const configuration: IConfigurationNode = { tags: ['accessibility'] }, [AccessibilityAlertSettingId.Breakpoint]: { - 'markdownDescription': localize('alert.breakpoint', "When in screen reader mode, alerts when the active line has a breakpoint. Also see {0}.", '`#audioCues.breakpoint#`'), + 'markdownDescription': localize('alert.breakpoint', "Alerts when the active line has a breakpoint. Also see {0}.", '`#audioCues.breakpoint#`'), 'type': 'boolean', 'default': true, tags: ['accessibility'] }, [AccessibilityAlertSettingId.Error]: { - 'markdownDescription': localize('alert.error', "When in screen reader mode, alerts when the active line has an error. Also see {0}.", '`#audioCues.error#`'), + 'markdownDescription': localize('alert.error', "Alerts when the active line has an error. Also see {0}.", '`#audioCues.error#`'), 'type': 'boolean', 'default': true, tags: ['accessibility'] }, [AccessibilityAlertSettingId.Warning]: { - 'markdownDescription': localize('alert.warning', "When in screen reader mode, alerts when the active line has a warning. Also see {0}.", '`#audioCues.warning#`'), + 'markdownDescription': localize('alert.warning', "Alerts when the active line has a warning. Also see {0}.", '`#audioCues.warning#`'), 'type': 'boolean', 'default': true, tags: ['accessibility'] }, [AccessibilityAlertSettingId.FoldedArea]: { - 'markdownDescription': localize('alert.foldedArea', "When in screen reader mode, alerts when the active line has a folded area that can be unfolded. Also see {0}.", '`#audioCues.foldedArea#`'), + 'markdownDescription': localize('alert.foldedArea', "Alerts when the active line has a folded area that can be unfolded. Also see {0}.", '`#audioCues.foldedArea#`'), 'type': 'boolean', 'default': true, tags: ['accessibility'] diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts index 4e70120525c..c6adb8e5ab1 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts @@ -27,7 +27,7 @@ export class AccessibleNotificationService extends Disposable implements IAccess @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, @ILogService private readonly _logService: ILogService) { super(); - this._events.set(AccessibleNotificationEvent.Clear, { audioCue: AudioCue.clear, alertMessage: localize('cleared', "Cleared") }); + this._events.set(AccessibleNotificationEvent.Clear, { audioCue: AudioCue.clear, alertMessage: localize('cleared', "Cleared"), alertSetting: AccessibilityAlertSettingId.Clear }); this._events.set(AccessibleNotificationEvent.Save, { audioCue: AudioCue.save, alertMessage: localize('saved', "Saved"), alertSetting: AccessibilityAlertSettingId.Save }); this._events.set(AccessibleNotificationEvent.Format, { audioCue: AudioCue.format, alertMessage: localize('formatted', "Formatted"), alertSetting: AccessibilityAlertSettingId.Format }); this._events.set(AccessibleNotificationEvent.Breakpoint, { audioCue: AudioCue.break, alertMessage: localize('breakpoint', "Breakpoint"), alertSetting: AccessibilityAlertSettingId.Breakpoint }); @@ -38,19 +38,18 @@ export class AccessibleNotificationService extends Disposable implements IAccess this._register(this._workingCopyService.onDidSave((e) => this._notifyBasedOnUserGesture(AccessibleNotificationEvent.Save, e.reason === SaveReason.EXPLICIT))); } - /** - * Notify on clear, save, format. Alerts and audio cues are mutually exclusive. - */ notify(event: AccessibleNotificationEvent, userGesture?: boolean): void { if (event === AccessibleNotificationEvent.Format) { return this._notifyBasedOnUserGesture(AccessibleNotificationEvent.Format, userGesture); } - const { audioCue, alertMessage } = this._events.get(event)!; + const { audioCue, alertMessage, alertSetting } = this._events.get(event)!; const audioCueSetting = this._configurationService.getValue(audioCue.settingsKey); if (audioCueSetting === 'on' || audioCueSetting === 'auto' && this._accessibilityService.isScreenReaderOptimized()) { this._logService.debug('AccessibleNotificationService playing sound: ', audioCue.name); this._audioCueService.playAudioCue(audioCue); - } else { + } + + if (alertSetting && this._configurationService.getValue(alertSetting) === true) { this._logService.debug('AccessibleNotificationService alerting: ', alertMessage); this._accessibilityService.alert(alertMessage); } @@ -83,11 +82,6 @@ export class AccessibleNotificationService extends Disposable implements IAccess this._logService.debug('AccessibleNotificationService playing sound: ', audioCue.name); // Play sound bypasses the usual audio cue checks IE screen reader optimized, auto, etc. this._audioCueService.playSound(audioCue.sound.getSound(), true); - return; - } - if (audioCueSetting !== 'never') { - // Never do both sound and alert - return; } const alertSettingValue: NotificationSetting = this._configurationService.getValue(alertSetting); if (this._shouldNotify(alertSettingValue, userGesture)) { diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index efc297855f2..fef18d00448 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -133,7 +133,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis default: 'off' }, 'audioCues.clear': { - 'description': localize('audioCues.clear', "Plays a sound when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), + 'description': localize('audioCues.clear', "Plays a sound when a feature is cleared (for example, the terminal, Debug Console, or Output channel)."), ...audioCueFeatureBase, default: 'off' }, From 8b471ca1dc75b538f80213e4836539e7c7b21ed8 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 3 Jan 2024 14:04:06 -0600 Subject: [PATCH 0044/1897] adopt in many more places --- .../accessibility/common/accessibility.ts | 10 ++++- .../browser/accessibilityConfiguration.ts | 44 ++++++++++++++++++- .../browser/accessibleNotificationService.ts | 12 +++-- .../chat/browser/chatAccessibilityService.ts | 5 ++- .../tasks/browser/taskTerminalStatus.ts | 12 ++--- .../terminal/browser/terminalInstance.ts | 7 ++- .../terminal/browser/xterm/decorationAddon.ts | 6 +-- .../quickFix/browser/quickFixAddon.ts | 7 ++- 8 files changed, 78 insertions(+), 25 deletions(-) diff --git a/src/vs/platform/accessibility/common/accessibility.ts b/src/vs/platform/accessibility/common/accessibility.ts index 112c92b72ba..e7a2dd24ff8 100644 --- a/src/vs/platform/accessibility/common/accessibility.ts +++ b/src/vs/platform/accessibility/common/accessibility.ts @@ -55,7 +55,7 @@ export const IAccessibleNotificationService = createDecorator this._notifyBasedOnUserGesture(AccessibleNotificationEvent.Save, e.reason === SaveReason.EXPLICIT))); } - notify(event: AccessibleNotificationEvent, userGesture?: boolean): void { + notify(event: AccessibleNotificationEvent, userGesture?: boolean, forceSound?: boolean, allowManyInParallel?: boolean): void { if (event === AccessibleNotificationEvent.Format) { return this._notifyBasedOnUserGesture(AccessibleNotificationEvent.Format, userGesture); } const { audioCue, alertMessage, alertSetting } = this._events.get(event)!; const audioCueSetting = this._configurationService.getValue(audioCue.settingsKey); - if (audioCueSetting === 'on' || audioCueSetting === 'auto' && this._accessibilityService.isScreenReaderOptimized()) { + if (audioCueSetting === 'on' || audioCueSetting === 'auto' && this._accessibilityService.isScreenReaderOptimized() || forceSound) { this._logService.debug('AccessibleNotificationService playing sound: ', audioCue.name); - this._audioCueService.playAudioCue(audioCue); + this._audioCueService.playSound(audioCue.sound.getSound(), allowManyInParallel); } if (alertSetting && this._configurationService.getValue(alertSetting) === true) { diff --git a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts index fa522294c26..5e81f842aa3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts @@ -6,6 +6,7 @@ import { status } from 'vs/base/browser/ui/aria/aria'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Disposable, DisposableMap, IDisposable } from 'vs/base/common/lifecycle'; +import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; @@ -19,12 +20,12 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi private _requestId: number = 0; - constructor(@IAudioCueService private readonly _audioCueService: IAudioCueService, @IInstantiationService private readonly _instantiationService: IInstantiationService) { + constructor(@IAudioCueService private readonly _audioCueService: IAudioCueService, @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService, @IInstantiationService private readonly _instantiationService: IInstantiationService) { super(); } acceptRequest(): number { this._requestId++; - this._audioCueService.playAudioCue(AudioCue.chatRequestSent, { allowManyInParallel: true }); + this._accessibleNotificationService.notify(AccessibleNotificationEvent.ChatRequestSent, undefined, undefined, true); this._pendingCueMap.set(this._requestId, this._instantiationService.createInstance(AudioCueScheduler)); return this._requestId; } diff --git a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts index df714767124..ac6f1d174e9 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts @@ -14,8 +14,8 @@ import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/termina import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { spinningLoading } from 'vs/platform/theme/common/iconRegistry'; import { IMarker } from 'vs/platform/terminal/common/capabilities/capabilities'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { ITerminalStatus } from 'vs/workbench/contrib/terminal/common/terminal'; +import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; interface ITerminalData { terminal: ITerminalInstance; @@ -40,7 +40,7 @@ const INFO_INACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID export class TaskTerminalStatus extends Disposable { private terminalMap: Map = new Map(); private _marker: IMarker | undefined; - constructor(@ITaskService taskService: ITaskService, @IAudioCueService private readonly _audioCueService: IAudioCueService) { + constructor(@ITaskService taskService: ITaskService, @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService) { super(); this._register(taskService.onDidStateChange((event) => { switch (event.kind) { @@ -95,7 +95,7 @@ export class TaskTerminalStatus extends Disposable { terminalData.taskRunEnded = true; terminalData.terminal.statusList.remove(terminalData.status); if ((event.exitCode === 0) && (terminalData.problemMatcher.numberOfMatches === 0)) { - this._audioCueService.playAudioCue(AudioCue.taskCompleted); + this._accessibleNotificationService.notify(AccessibleNotificationEvent.TaskCompleted); if (terminalData.task.configurationProperties.isBackground) { for (const status of terminalData.terminal.statusList.statuses) { terminalData.terminal.statusList.remove(status); @@ -104,7 +104,7 @@ export class TaskTerminalStatus extends Disposable { terminalData.terminal.statusList.add(SUCCEEDED_TASK_STATUS); } } else if (event.exitCode || terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Error) { - this._audioCueService.playAudioCue(AudioCue.taskFailed); + this._accessibleNotificationService.notify(AccessibleNotificationEvent.TaskFailed); terminalData.terminal.statusList.add(FAILED_TASK_STATUS); } else if (terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Warning) { terminalData.terminal.statusList.add(WARNING_TASK_STATUS); @@ -120,10 +120,10 @@ export class TaskTerminalStatus extends Disposable { } terminalData.terminal.statusList.remove(terminalData.status); if (terminalData.problemMatcher.numberOfMatches === 0) { - this._audioCueService.playAudioCue(AudioCue.taskCompleted); + this._accessibleNotificationService.notify(AccessibleNotificationEvent.TaskCompleted); terminalData.terminal.statusList.add(SUCCEEDED_INACTIVE_TASK_STATUS); } else if (terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Error) { - this._audioCueService.playAudioCue(AudioCue.taskFailed); + this._accessibleNotificationService.notify(AccessibleNotificationEvent.TaskFailed); terminalData.terminal.statusList.add(FAILED_INACTIVE_TASK_STATUS); } else if (terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Warning) { terminalData.terminal.statusList.add(WARNING_INACTIVE_TASK_STATUS); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 4aefa9ab3ff..c8c7226475f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -25,8 +25,7 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { URI } from 'vs/base/common/uri'; import { TabFocus } from 'vs/editor/browser/config/tabFocus'; import * as nls from 'vs/nls'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibleNotificationEvent, IAccessibilityService, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -362,7 +361,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { @ITelemetryService private readonly _telemetryService: ITelemetryService, @IOpenerService private readonly _openerService: IOpenerService, @ICommandService private readonly _commandService: ICommandService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService, @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService ) { super(); @@ -759,7 +758,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { icon: Codicon.bell, tooltip: nls.localize('bellStatus', "Bell") }, this._configHelper.config.bellDuration); - this._audioCueService.playSound(AudioCue.terminalBell.sound.getSound()); + this._accessibleNotificationService.notify(AccessibleNotificationEvent.TerminalBell, undefined, true); } })); }, 1000, this._store); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index da0a7fc9d0c..41c7f59ac4a 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -9,7 +9,6 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -26,6 +25,7 @@ import { DecorationSelector, TerminalDecorationHoverManager, updateLayout } from import { TERMINAL_COMMAND_DECORATION_DEFAULT_BACKGROUND_COLOR, TERMINAL_COMMAND_DECORATION_ERROR_BACKGROUND_COLOR, TERMINAL_COMMAND_DECORATION_SUCCESS_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import type { IDecoration, ITerminalAddon, Terminal } from '@xterm/xterm'; +import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; interface IDisposableDecoration { decoration: IDecoration; disposables: IDisposable[]; exitCode?: number; markProperties?: IMarkProperties } @@ -52,7 +52,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { @ILifecycleService lifecycleService: ILifecycleService, @ICommandService private readonly _commandService: ICommandService, @IInstantiationService instantiationService: IInstantiationService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService, @INotificationService private readonly _notificationService: INotificationService ) { super(); @@ -219,7 +219,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { commandDetectionListeners.push(capability.onCommandFinished(command => { this.registerCommandDecoration(command); if (command.exitCode) { - this._audioCueService.playAudioCue(AudioCue.terminalCommandFailed); + this._accessibleNotificationService.notify(AccessibleNotificationEvent.TerminalCommandFailed); } })); // Command invalidated diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts index ea488b6fe0e..55327132258 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts @@ -20,7 +20,6 @@ import type { IDecoration, Terminal } from '@xterm/xterm'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IActionWidgetService } from 'vs/platform/actionWidget/browser/actionWidget'; import { ActionSet } from 'vs/platform/actionWidget/common/actionWidget'; import { getLinesForCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; @@ -35,6 +34,7 @@ import { CodeActionKind } from 'vs/editor/contrib/codeAction/common/types'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; const quickFixClasses = [ DecorationSelector.QuickFix, @@ -77,7 +77,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, @ITerminalQuickFixService private readonly _quickFixService: ITerminalQuickFixService, @ICommandService private readonly _commandService: ICommandService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService, @IOpenerService private readonly _openerService: IOpenerService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IExtensionService private readonly _extensionService: IExtensionService, @@ -284,8 +284,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, e.classList.add(...ThemeIcon.asClassNameArray(isExplainOnly ? Codicon.sparkle : Codicon.lightBulb)); updateLayout(this._configurationService, e); - this._audioCueService.playAudioCue(AudioCue.terminalQuickFix); - + this._accessibleNotificationService.notify(AccessibleNotificationEvent.TerminalQuickFix); const parentElement = (e.closest('.xterm') as HTMLElement).parentElement; if (!parentElement) { return; From 12603ced19378c6fca857f045edabe787307bcb4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 3 Jan 2024 14:12:59 -0600 Subject: [PATCH 0045/1897] add for notebooks --- .../accessibility/common/accessibility.ts | 4 +++- .../browser/accessibilityConfiguration.ts | 16 +++++++++++++++- .../browser/accessibleNotificationService.ts | 2 ++ .../notebookExecutionStateServiceImpl.ts | 8 ++++---- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/accessibility/common/accessibility.ts b/src/vs/platform/accessibility/common/accessibility.ts index e7a2dd24ff8..a6801186c95 100644 --- a/src/vs/platform/accessibility/common/accessibility.ts +++ b/src/vs/platform/accessibility/common/accessibility.ts @@ -72,5 +72,7 @@ export const enum AccessibleNotificationEvent { TerminalCommandFailed = 'terminalCommandFailed', TaskCompleted = 'taskCompleted', TaskFailed = 'taskFailed', - ChatRequestSent = 'chatRequestSent' + ChatRequestSent = 'chatRequestSent', + NotebookCellCompleted = 'notebookCellCompleted', + NotebookCellFailed = 'notebookCellFailed' } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 11ccfe1571a..770630f3993 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -67,7 +67,9 @@ export const enum AccessibilityAlertSettingId { TerminalCommandFailed = 'accessibility.alert.terminalCommandFailed', TaskCompleted = 'accessibility.alert.taskCompleted', TaskFailed = 'accessibility.alert.taskFailed', - ChatRequestSent = 'accessibility.alert.chatRequestSent' + ChatRequestSent = 'accessibility.alert.chatRequestSent', + NotebookCellCompleted = 'accessibility.alert.notebookCellCompleted', + NotebookCellFailed = 'accessibility.alert.notebookCellFailed' } export const enum AccessibleViewProviderId { @@ -231,6 +233,18 @@ const configuration: IConfigurationNode = { 'default': true, tags: ['accessibility'] }, + [AccessibilityAlertSettingId.NotebookCellCompleted]: { + 'markdownDescription': localize('alert.notebookCellCompleted', "Alerts when a notebook cell completes successfully. Also see {0}.", '`#audioCues.notebookCellCompleted#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, + [AccessibilityAlertSettingId.NotebookCellFailed]: { + 'markdownDescription': localize('alert.notebookCellFailed', "Alerts when a notebook cell fails. Also see {0}.", '`#audioCues.notebookCellFailed#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, [AccessibilityVoiceSettingId.SpeechTimeout]: { 'markdownDescription': localize('voice.speechTimeout', "The duration in milliseconds that voice speech recognition remains active after you stop speaking. For example in a chat session, the transcribed text is submitted automatically after the timeout is met. Set to `0` to disable this feature."), 'type': 'number', diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts index f45ac699ee4..fc693042325 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts @@ -40,6 +40,8 @@ export class AccessibleNotificationService extends Disposable implements IAccess this._events.set(AccessibleNotificationEvent.TaskFailed, { audioCue: AudioCue.taskFailed, alertMessage: localize('taskFailed', "Task Failed"), alertSetting: AccessibilityAlertSettingId.TaskFailed }); this._events.set(AccessibleNotificationEvent.TaskCompleted, { audioCue: AudioCue.taskCompleted, alertMessage: localize('taskCompleted', "Task Completed"), alertSetting: AccessibilityAlertSettingId.TaskCompleted }); this._events.set(AccessibleNotificationEvent.ChatRequestSent, { audioCue: AudioCue.chatRequestSent, alertMessage: localize('chatRequestSent', "Chat Request Sent"), alertSetting: AccessibilityAlertSettingId.ChatRequestSent }); + this._events.set(AccessibleNotificationEvent.NotebookCellCompleted, { audioCue: AudioCue.notebookCellCompleted, alertMessage: localize('notebookCellCompleted', "Notebook Cell Completed"), alertSetting: AccessibilityAlertSettingId.NotebookCellCompleted }); + this._events.set(AccessibleNotificationEvent.NotebookCellFailed, { audioCue: AudioCue.notebookCellFailed, alertMessage: localize('notebookCellFailed', "Notebook Cell Failed"), alertSetting: AccessibilityAlertSettingId.NotebookCellFailed }); this._register(this._workingCopyService.onDidSave((e) => this._notifyBasedOnUserGesture(AccessibleNotificationEvent.Save, e.reason === SaveReason.EXPLICIT))); } diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts index a5736966b8a..b040c9a74ea 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts @@ -9,7 +9,7 @@ import { ResourceMap } from 'vs/base/common/map'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; @@ -38,7 +38,7 @@ export class NotebookExecutionStateService extends Disposable implements INotebo @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILogService private readonly _logService: ILogService, @INotebookService private readonly _notebookService: INotebookService, - @IAudioCueService private readonly _audioCueService: IAudioCueService + @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService ) { super(); } @@ -112,11 +112,11 @@ export class NotebookExecutionStateService extends Disposable implements INotebo if (lastRunSuccess !== undefined) { if (lastRunSuccess) { if (this._executions.size === 0) { - this._audioCueService.playAudioCue(AudioCue.notebookCellCompleted); + this._accessibleNotificationService.notify(AccessibleNotificationEvent.NotebookCellCompleted); } this._clearLastFailedCell(notebookUri); } else { - this._audioCueService.playAudioCue(AudioCue.notebookCellFailed); + this._accessibleNotificationService.notify(AccessibleNotificationEvent.NotebookCellFailed); this._setLastFailedCell(notebookUri, cellHandle); } } From 25974b8afc4a98f81f9c92910cdca680746c474b Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 3 Jan 2024 12:54:48 -0800 Subject: [PATCH 0046/1897] Update cell/inline widget conflict (#201736) --- .../view/cellParts/chat/cellChatActions.ts | 8 ++++++++ .../notebook/browser/view/cellParts/codeCell.ts | 15 ++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts index cea9f976596..ce98030fd02 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts @@ -297,6 +297,14 @@ registerAction2(class extends NotebookCellAction { if (!ctrl) { return; } + + context.notebookEditor.getCellsInRange().forEach(cell => { + const cellCtrl = NotebookCellChatController.get(cell); + if (cellCtrl) { + cellCtrl.dismiss(false); + } + }); + ctrl.show(); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts index 5c38ae568dd..cdfd236913a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts @@ -99,7 +99,7 @@ export class CodeCell extends Disposable { } if (e.focusModeChanged) { - this.updateEditorForFocusModeChange(); + this.updateEditorForFocusModeChange(true); } })); @@ -110,7 +110,7 @@ export class CodeCell extends Disposable { })); this.updateEditorOptions(); - this.updateEditorForFocusModeChange(); + this.updateEditorForFocusModeChange(false); this.updateForOutputHover(); this.updateForOutputFocus(); @@ -341,15 +341,20 @@ export class CodeCell extends Disposable { && (this.notebookEditor.hasEditorFocus() || this.notebookEditor.getDomNode().ownerDocument.activeElement === this.notebookEditor.getDomNode().ownerDocument.body); } - private updateEditorForFocusModeChange() { + private updateEditorForFocusModeChange(sync: boolean) { if (this.shouldUpdateDOMFocus()) { - this.templateData.editor?.focus(); + if (sync) { + this.templateData.editor?.focus(); + } else { + this._register(DOM.runAtThisOrScheduleAtNextAnimationFrame(DOM.getWindow(this.templateData.container), () => { + this.templateData.editor?.focus(); + })); + } } this.templateData.container.classList.toggle('cell-editor-focus', this.viewCell.focusMode === CellFocusMode.Editor); this.templateData.container.classList.toggle('cell-output-focus', this.viewCell.focusMode === CellFocusMode.Output); } - private updateForCollapseState(): boolean { if (this.viewCell.isOutputCollapsed === this._renderedOutputCollapseState && this.viewCell.isInputCollapsed === this._renderedInputCollapseState) { From eb55c74a90679ba9471ea942b7cb8e6df2b24fad Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 3 Jan 2024 13:57:54 -0800 Subject: [PATCH 0047/1897] Allow Core to contribute auth providers (#201741) * Allow Core to contribute auth providers This props us up to allow embedders to contribute their own auth providers that override. This is moving in the direction to resolve this one: https://github.com/microsoft/vscode/issues/197389 * handle the change to AuthenticationSessionsChangeEvent to align with vscode API --- .../api/browser/mainThreadAuthentication.ts | 113 +++------------- .../api/common/extHostAuthentication.ts | 8 +- .../browser/parts/globalCompositeBar.ts | 8 +- .../accountsEntitlements.contribution.ts | 4 +- .../browser/editSessionsStorageService.ts | 2 +- .../browser/authenticationService.ts | 128 ++++++++++++++---- .../authentication/common/authentication.ts | 53 +++++++- .../browser/userDataSyncWorkbenchService.ts | 2 +- 8 files changed, 177 insertions(+), 141 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index ec4ae3964cb..43aaf2c863c 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -3,116 +3,36 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { AllowedExtension, readAllowedExtensions, getAuthenticationProviderActivationEvent, addAccountUsage, readAccountUsages, removeAccountUsage } from 'vs/workbench/services/authentication/browser/authenticationService'; +import { getAuthenticationProviderActivationEvent, addAccountUsage } from 'vs/workbench/services/authentication/browser/authenticationService'; import { IAuthenticationCreateSessionOptions, AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; import { ExtHostAuthenticationShape, ExtHostContext, MainContext, MainThreadAuthenticationShape } from '../common/extHost.protocol'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import Severity from 'vs/base/common/severity'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { fromNow } from 'vs/base/common/date'; import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import type { AuthenticationGetSessionOptions } from 'vscode'; +import { Emitter, Event } from 'vs/base/common/event'; -interface TrustedExtensionsQuickPickItem { - label: string; - description: string; - extension: AllowedExtension; -} export class MainThreadAuthenticationProvider extends Disposable implements IAuthenticationProvider { + + readonly onDidChangeSessions: Event; + constructor( private readonly _proxy: ExtHostAuthenticationShape, public readonly id: string, public readonly label: string, public readonly supportsMultipleAccounts: boolean, private readonly notificationService: INotificationService, - private readonly storageService: IStorageService, - private readonly quickInputService: IQuickInputService, - private readonly dialogService: IDialogService + onDidChangeSessionsEmitter: Emitter, ) { super(); - } - public manageTrustedExtensions(accountName: string) { - const allowedExtensions = readAllowedExtensions(this.storageService, this.id, accountName); - - if (!allowedExtensions.length) { - this.dialogService.info(nls.localize('noTrustedExtensions', "This account has not been used by any extensions.")); - return; - } - - const quickPick = this.quickInputService.createQuickPick(); - quickPick.canSelectMany = true; - quickPick.customButton = true; - quickPick.customLabel = nls.localize('manageTrustedExtensions.cancel', 'Cancel'); - const usages = readAccountUsages(this.storageService, this.id, accountName); - const items = allowedExtensions.map(extension => { - const usage = usages.find(usage => extension.id === usage.extensionId); - return { - label: extension.name, - description: usage - ? nls.localize({ key: 'accountLastUsedDate', comment: ['The placeholder {0} is a string with time information, such as "3 days ago"'] }, "Last used this account {0}", fromNow(usage.lastUsed, true)) - : nls.localize('notUsed', "Has not used this account"), - extension - }; - }); - - quickPick.items = items; - quickPick.selectedItems = items.filter(item => item.extension.allowed === undefined || item.extension.allowed); - quickPick.title = nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"); - quickPick.placeholder = nls.localize('manageExtensions', "Choose which extensions can access this account"); - - quickPick.onDidAccept(() => { - const updatedAllowedList = quickPick.items - .map(i => (i as TrustedExtensionsQuickPickItem).extension); - this.storageService.store(`${this.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.APPLICATION, StorageTarget.USER); - - quickPick.dispose(); - }); - - quickPick.onDidChangeSelection((changed) => { - quickPick.items.forEach(item => { - if ((item as TrustedExtensionsQuickPickItem).extension) { - (item as TrustedExtensionsQuickPickItem).extension.allowed = false; - } - }); - - changed.forEach((item) => item.extension.allowed = true); - }); - - quickPick.onDidHide(() => { - quickPick.dispose(); - }); - - quickPick.onDidCustom(() => { - quickPick.hide(); - }); - - quickPick.show(); - } - - async removeAccountSessions(accountName: string, sessions: AuthenticationSession[]): Promise { - const accountUsages = readAccountUsages(this.storageService, this.id, accountName); - - const { confirmed } = await this.dialogService.confirm({ - type: Severity.Info, - message: accountUsages.length - ? nls.localize('signOutMessage', "The account '{0}' has been used by: \n\n{1}\n\n Sign out from these extensions?", accountName, accountUsages.map(usage => usage.extensionName).join('\n')) - : nls.localize('signOutMessageSimple', "Sign out of '{0}'?", accountName), - primaryButton: nls.localize({ key: 'signOut', comment: ['&& denotes a mnemonic'] }, "&&Sign Out") - }); - - if (confirmed) { - const removeSessionPromises = sessions.map(session => this.removeSession(session.id)); - await Promise.all(removeSessionPromises); - removeAccountUsage(this.storageService, this.id, accountName); - this.storageService.remove(`${this.id}-${accountName}`, StorageScope.APPLICATION); - } + this.onDidChangeSessions = onDidChangeSessionsEmitter.event; } async getSessions(scopes?: string[]) { @@ -133,13 +53,14 @@ export class MainThreadAuthenticationProvider extends Disposable implements IAut export class MainThreadAuthentication extends Disposable implements MainThreadAuthenticationShape { private readonly _proxy: ExtHostAuthenticationShape; + private readonly _registrations = this._register(new DisposableMap()); + constructor( extHostContext: IExtHostContext, @IAuthenticationService private readonly authenticationService: IAuthenticationService, @IDialogService private readonly dialogService: IDialogService, @IStorageService private readonly storageService: IStorageService, @INotificationService private readonly notificationService: INotificationService, - @IQuickInputService private readonly quickInputService: IQuickInputService, @IExtensionService private readonly extensionService: IExtensionService, @ITelemetryService private readonly telemetryService: ITelemetryService ) { @@ -158,11 +79,14 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu } async $registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean): Promise { - const provider = new MainThreadAuthenticationProvider(this._proxy, id, label, supportsMultipleAccounts, this.notificationService, this.storageService, this.quickInputService, this.dialogService); + const emitter = new Emitter(); + this._registrations.set(id, emitter); + const provider = new MainThreadAuthenticationProvider(this._proxy, id, label, supportsMultipleAccounts, this.notificationService, emitter); this.authenticationService.registerAuthenticationProvider(id, provider); } $unregisterAuthenticationProvider(id: string): void { + this._registrations.deleteAndDispose(id); this.authenticationService.unregisterAuthenticationProvider(id); } @@ -170,8 +94,11 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu return this.extensionService.activateByEvent(getAuthenticationProviderActivationEvent(id), ActivationKind.Immediate); } - $sendDidChangeSessions(id: string, event: AuthenticationSessionsChangeEvent): void { - this.authenticationService.sessionsUpdate(id, event); + $sendDidChangeSessions(providerId: string, event: AuthenticationSessionsChangeEvent): void { + const obj = this._registrations.get(providerId); + if (obj instanceof Emitter) { + obj.fire(event); + } } $removeSession(providerId: string, sessionId: string): Promise { diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index 7a4f6dde67f..3c49ad4a7b4 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -83,13 +83,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { }); } - const listener = provider.onDidChangeSessions(e => { - this._proxy.$sendDidChangeSessions(id, { - added: e.added ?? [], - changed: e.changed ?? [], - removed: e.removed ?? [] - }); - }); + const listener = provider.onDidChangeSessions(e => this._proxy.$sendDidChangeSessions(id, e)); this._proxy.$registerAuthenticationProvider(id, label, options?.supportsMultipleAccounts ?? false); diff --git a/src/vs/workbench/browser/parts/globalCompositeBar.ts b/src/vs/workbench/browser/parts/globalCompositeBar.ts index 13432c87811..04d417fb635 100644 --- a/src/vs/workbench/browser/parts/globalCompositeBar.ts +++ b/src/vs/workbench/browser/parts/globalCompositeBar.ts @@ -331,15 +331,17 @@ export class AccountsActivityActionViewItem extends AbstractGlobalActivityAction })); this._register(this.authenticationService.onDidChangeSessions(async e => { - for (const changed of [...e.event.changed, ...e.event.added]) { + for (const changed of [...(e.event.changed ?? []), ...(e.event.added ?? [])]) { try { await this.addOrUpdateAccount(e.providerId, changed.account); } catch (e) { this.logService.error(e); } } - for (const removed of e.event.removed) { - this.removeAccount(e.providerId, removed.account); + if (e.event.removed) { + for (const removed of e.event.removed) { + this.removeAccount(e.providerId, removed.account); + } } })); } diff --git a/src/vs/workbench/contrib/accountEntitlements/browser/accountsEntitlements.contribution.ts b/src/vs/workbench/contrib/accountEntitlements/browser/accountsEntitlements.contribution.ts index 44169a00242..8ce4726c5f3 100644 --- a/src/vs/workbench/contrib/accountEntitlements/browser/accountsEntitlements.contribution.ts +++ b/src/vs/workbench/contrib/accountEntitlements/browser/accountsEntitlements.contribution.ts @@ -100,9 +100,9 @@ class AccountsEntitlement extends Disposable implements IWorkbenchContribution { })); this._register(this.authenticationService.onDidChangeSessions(async (e) => { - if (e.providerId === this.productService.gitHubEntitlement!.providerId && e.event.added.length > 0 && !this.isInitialized) { + if (e.providerId === this.productService.gitHubEntitlement!.providerId && e.event.added?.length && !this.isInitialized) { this.onSessionChange(e.event.added[0]); - } else if (e.providerId === this.productService.gitHubEntitlement!.providerId && e.event.removed.length > 0) { + } else if (e.providerId === this.productService.gitHubEntitlement!.providerId && e.event.removed?.length) { this.contextKey.set(false); } })); diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts b/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts index 00914aa8eff..e8585ae96e5 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts @@ -448,7 +448,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes } private onDidChangeSessions(e: AuthenticationSessionsChangeEvent): void { - if (this.authenticationInfo?.sessionId && e.removed.find(session => session.id === this.authenticationInfo?.sessionId)) { + if (this.authenticationInfo?.sessionId && e.removed?.find(session => session.id === this.authenticationInfo?.sessionId)) { this.clearAuthenticationPreference(); } } diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 7ad844439c5..6e296726ec2 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { flatten } from 'vs/base/common/arrays'; +import { fromNow } from 'vs/base/common/date'; import { Emitter, Event } from 'vs/base/common/event'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { Disposable, dispose, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap, DisposableStore, dispose, IDisposable, isDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { isString } from 'vs/base/common/types'; import * as nls from 'vs/nls'; @@ -34,7 +34,9 @@ interface IAccountUsage { lastUsed: number; } -export function readAccountUsages(storageService: IStorageService, providerId: string, accountName: string,): IAccountUsage[] { +// TODO: make this account usage stuff a service + +function readAccountUsages(storageService: IStorageService, providerId: string, accountName: string,): IAccountUsage[] { const accountKey = `${providerId}-${accountName}-usages`; const storedUsages = storageService.get(accountKey, StorageScope.APPLICATION); let usages: IAccountUsage[] = []; @@ -49,7 +51,7 @@ export function readAccountUsages(storageService: IStorageService, providerId: s return usages; } -export function removeAccountUsage(storageService: IStorageService, providerId: string, accountName: string): void { +function removeAccountUsage(storageService: IStorageService, providerId: string, accountName: string): void { const accountKey = `${providerId}-${accountName}-usages`; storageService.remove(accountKey, StorageScope.APPLICATION); } @@ -107,7 +109,7 @@ export interface AllowedExtension { allowed?: boolean; } -export function readAllowedExtensions(storageService: IStorageService, providerId: string, accountName: string): AllowedExtension[] { +function readAllowedExtensions(storageService: IStorageService, providerId: string, accountName: string): AllowedExtension[] { let trustedExtensions: AllowedExtension[] = []; try { const trustedExtensionSrc = storageService.get(`${providerId}-${accountName}`, StorageScope.APPLICATION); @@ -182,6 +184,7 @@ export class AuthenticationService extends Disposable implements IAuthentication private _accountBadgeDisposable = this._register(new MutableDisposable()); private _authenticationProviders: Map = new Map(); + private _authenticationProviderDisposables: DisposableMap = this._register(new DisposableMap()); /** * All providers that have been statically declared by extensions. These may not be registered. @@ -231,7 +234,7 @@ export class AuthenticationService extends Disposable implements IAuthentication } }); - const removedExtPoints = flatten(removed.map(r => r.value)); + const removedExtPoints = removed.flatMap(r => r.value); removedExtPoints.forEach(point => { const index = this.declaredProviders.findIndex(provider => provider.id === point.id); if (index > -1) { @@ -257,6 +260,12 @@ export class AuthenticationService extends Disposable implements IAuthentication registerAuthenticationProvider(id: string, authenticationProvider: IAuthenticationProvider): void { this._authenticationProviders.set(id, authenticationProvider); + const disposableStore = new DisposableStore(); + disposableStore.add(authenticationProvider.onDidChangeSessions(e => this.sessionsUpdate(authenticationProvider, e))); + if (isDisposable(authenticationProvider)) { + disposableStore.add(authenticationProvider); + } + this._authenticationProviderDisposables.set(id, disposableStore); this._onDidRegisterAuthenticationProvider.fire({ id, label: authenticationProvider.label }); if (placeholderMenuItem) { @@ -268,7 +277,6 @@ export class AuthenticationService extends Disposable implements IAuthentication unregisterAuthenticationProvider(id: string): void { const provider = this._authenticationProviders.get(id); if (provider) { - provider.dispose(); this._authenticationProviders.delete(id); this._onDidUnregisterAuthenticationProvider.fire({ id, label: provider.label }); @@ -277,6 +285,7 @@ export class AuthenticationService extends Disposable implements IAuthentication this.removeAccessRequest(id, extensionId); }); } + this._authenticationProviderDisposables.deleteAndDispose(id); if (!this._authenticationProviders.size) { placeholderMenuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { @@ -289,21 +298,15 @@ export class AuthenticationService extends Disposable implements IAuthentication } } - async sessionsUpdate(id: string, event: AuthenticationSessionsChangeEvent): Promise { - const provider = this._authenticationProviders.get(id); - if (provider) { - this._onDidChangeSessions.fire({ providerId: id, label: provider.label, event: event }); - - if (event.added) { - await this.updateNewSessionRequests(provider, event.added); - } - - if (event.removed) { - await this.updateAccessRequests(id, event.removed); - } - - this.updateBadgeCount(); + private async sessionsUpdate(provider: IAuthenticationProvider, event: AuthenticationSessionsChangeEvent): Promise { + this._onDidChangeSessions.fire({ providerId: provider.id, label: provider.label, event }); + if (event.added?.length) { + await this.updateNewSessionRequests(provider, event.added); } + if (event.removed?.length) { + await this.updateAccessRequests(provider.id, event.removed); + } + this.updateBadgeCount(); } private async updateNewSessionRequests(provider: IAuthenticationProvider, addedSessions: readonly AuthenticationSession[]): Promise { @@ -769,20 +772,91 @@ export class AuthenticationService extends Disposable implements IAuthentication async manageTrustedExtensionsForAccount(id: string, accountName: string): Promise { const authProvider = this._authenticationProviders.get(id); - if (authProvider) { - return authProvider.manageTrustedExtensions(accountName); - } else { + if (!authProvider) { throw new Error(`No authentication provider '${id}' is currently registered.`); } + const allowedExtensions = readAllowedExtensions(this.storageService, authProvider.id, accountName); + + if (!allowedExtensions.length) { + this.dialogService.info(nls.localize('noTrustedExtensions', "This account has not been used by any extensions.")); + return; + } + + type TrustedExtensionsQuickPickItem = { + label: string; + description: string; + extension: AllowedExtension; + }; + const quickPick = this.quickInputService.createQuickPick(); + quickPick.canSelectMany = true; + quickPick.customButton = true; + quickPick.customLabel = nls.localize('manageTrustedExtensions.cancel', 'Cancel'); + const usages = readAccountUsages(this.storageService, authProvider.id, accountName); + const items = allowedExtensions.map(extension => { + const usage = usages.find(usage => extension.id === usage.extensionId); + return { + label: extension.name, + description: usage + ? nls.localize({ key: 'accountLastUsedDate', comment: ['The placeholder {0} is a string with time information, such as "3 days ago"'] }, "Last used this account {0}", fromNow(usage.lastUsed, true)) + : nls.localize('notUsed', "Has not used this account"), + extension + }; + }); + + quickPick.items = items; + quickPick.selectedItems = items.filter(item => item.extension.allowed === undefined || item.extension.allowed); + quickPick.title = nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"); + quickPick.placeholder = nls.localize('manageExtensions', "Choose which extensions can access this account"); + + quickPick.onDidAccept(() => { + const updatedAllowedList = quickPick.items.map(i => (i as TrustedExtensionsQuickPickItem).extension); + this.storageService.store(`${authProvider.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.APPLICATION, StorageTarget.USER); + quickPick.dispose(); + }); + + quickPick.onDidChangeSelection((changed) => { + quickPick.items.forEach(item => { + if ((item as TrustedExtensionsQuickPickItem).extension) { + (item as TrustedExtensionsQuickPickItem).extension.allowed = false; + } + }); + + changed.forEach((item) => item.extension.allowed = true); + }); + + quickPick.onDidHide(() => { + quickPick.dispose(); + }); + + quickPick.onDidCustom(() => { + quickPick.hide(); + }); + + quickPick.show(); } async removeAccountSessions(id: string, accountName: string, sessions: AuthenticationSession[]): Promise { const authProvider = this._authenticationProviders.get(id); - if (authProvider) { - return authProvider.removeAccountSessions(accountName, sessions); - } else { + if (!authProvider) { throw new Error(`No authentication provider '${id}' is currently registered.`); } + + const accountUsages = readAccountUsages(this.storageService, authProvider.id, accountName); + + const { confirmed } = await this.dialogService.confirm({ + type: Severity.Info, + message: accountUsages.length + ? nls.localize('signOutMessage', "The account '{0}' has been used by: \n\n{1}\n\n Sign out from these extensions?", accountName, accountUsages.map(usage => usage.extensionName).join('\n')) + : nls.localize('signOutMessageSimple', "Sign out of '{0}'?", accountName), + primaryButton: nls.localize({ key: 'signOut', comment: ['&& denotes a mnemonic'] }, "&&Sign Out") + }); + + if (confirmed) { + const removeSessionPromises = sessions.map(session => authProvider.removeSession(session.id)); + await Promise.all(removeSessionPromises); + removeAccountUsage(this.storageService, authProvider.id, accountName); + this.storageService.remove(`${authProvider.id}-${accountName}`, StorageScope.APPLICATION); + } } } diff --git a/src/vs/workbench/services/authentication/common/authentication.ts b/src/vs/workbench/services/authentication/common/authentication.ts index 0581a7308c0..0ff2179bfe2 100644 --- a/src/vs/workbench/services/authentication/common/authentication.ts +++ b/src/vs/workbench/services/authentication/common/authentication.ts @@ -19,9 +19,9 @@ export interface AuthenticationSession { } export interface AuthenticationSessionsChangeEvent { - added: ReadonlyArray; - removed: ReadonlyArray; - changed: ReadonlyArray; + added: ReadonlyArray | undefined; + removed: ReadonlyArray | undefined; + changed: ReadonlyArray | undefined; } export interface AuthenticationProviderInformation { @@ -53,7 +53,6 @@ export interface IAuthenticationService { requestSessionAccess(providerId: string, extensionId: string, extensionName: string, scopes: string[], possibleSessions: readonly AuthenticationSession[]): void; completeSessionAccessRequest(providerId: string, extensionId: string, extensionName: string, scopes: string[]): Promise; requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise; - sessionsUpdate(providerId: string, event: AuthenticationSessionsChangeEvent): void; readonly onDidRegisterAuthenticationProvider: Event; readonly onDidUnregisterAuthenticationProvider: Event; @@ -78,14 +77,54 @@ export interface IAuthenticationProviderCreateSessionOptions { sessionToRecreate?: AuthenticationSession; } +/** + * Represents an authentication provider. + */ export interface IAuthenticationProvider { + /** + * The unique identifier of the authentication provider. + */ readonly id: string; + + /** + * The display label of the authentication provider. + */ readonly label: string; + + /** + * Indicates whether the authentication provider supports multiple accounts. + */ readonly supportsMultipleAccounts: boolean; - dispose(): void; - manageTrustedExtensions(accountName: string): void; - removeAccountSessions(accountName: string, sessions: AuthenticationSession[]): Promise; + + /** + * An {@link Event} which fires when the array of sessions has changed, or data + * within a session has changed. + */ + readonly onDidChangeSessions: Event; + + /** + * Retrieves a list of authentication sessions. + * @param scopes - An optional list of scopes. If provided, the sessions returned should match these permissions, otherwise all sessions should be returned. + * @returns A promise that resolves to an array of authentication sessions. + */ getSessions(scopes?: string[]): Promise; + + /** + * Prompts the user to log in. + * If login is successful, the `onDidChangeSessions` event should be fired. + * If login fails, a rejected promise should be returned. + * If the provider does not support multiple accounts, this method should not be called if there is already an existing session matching the provided scopes. + * @param scopes - A list of scopes that the new session should be created with. + * @param options - Additional options for creating the session. + * @returns A promise that resolves to an authentication session. + */ createSession(scopes: string[], options: IAuthenticationProviderCreateSessionOptions): Promise; + + /** + * Removes the session corresponding to the specified session ID. + * If the removal is successful, the `onDidChangeSessions` event should be fired. + * If a session cannot be removed, the provider should reject with an error message. + * @param sessionId - The ID of the session to remove. + */ removeSession(sessionId: string): Promise; } diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index aba2a967b4b..befb6177c14 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -692,7 +692,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } private onDidChangeSessions(e: AuthenticationSessionsChangeEvent): void { - if (this.currentSessionId && e.removed.find(session => session.id === this.currentSessionId)) { + if (this.currentSessionId && e.removed?.find(session => session.id === this.currentSessionId)) { this.currentSessionId = undefined; } this.update('change in sessions'); From f39061c133000b29a8cc6803a4723206cd5dc64f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 3 Jan 2024 16:17:21 -0600 Subject: [PATCH 0048/1897] more instances --- src/vs/platform/accessibility/common/accessibility.ts | 4 +++- .../accessibility/browser/accessibilityConfiguration.ts | 9 ++++++++- .../browser/accessibleNotificationService.ts | 1 + .../audioCues/browser/audioCueDebuggerContribution.ts | 6 ++++-- .../contrib/inlayHints/browser/inlayHintsAccessibilty.ts | 6 +++--- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/vs/platform/accessibility/common/accessibility.ts b/src/vs/platform/accessibility/common/accessibility.ts index a6801186c95..9a353cb0044 100644 --- a/src/vs/platform/accessibility/common/accessibility.ts +++ b/src/vs/platform/accessibility/common/accessibility.ts @@ -74,5 +74,7 @@ export const enum AccessibleNotificationEvent { TaskFailed = 'taskFailed', ChatRequestSent = 'chatRequestSent', NotebookCellCompleted = 'notebookCellCompleted', - NotebookCellFailed = 'notebookCellFailed' + NotebookCellFailed = 'notebookCellFailed', + OnDebugBreak = 'onDebugBreak', + NoInlayHints = 'noInlayHints' } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 770630f3993..0f3c4296bc9 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -69,7 +69,8 @@ export const enum AccessibilityAlertSettingId { TaskFailed = 'accessibility.alert.taskFailed', ChatRequestSent = 'accessibility.alert.chatRequestSent', NotebookCellCompleted = 'accessibility.alert.notebookCellCompleted', - NotebookCellFailed = 'accessibility.alert.notebookCellFailed' + NotebookCellFailed = 'accessibility.alert.notebookCellFailed', + OnDebugBreak = 'accessibility.alert.onDebugBreak' } export const enum AccessibleViewProviderId { @@ -245,6 +246,12 @@ const configuration: IConfigurationNode = { 'default': true, tags: ['accessibility'] }, + [AccessibilityAlertSettingId.OnDebugBreak]: { + 'markdownDescription': localize('alert.onDebugBreak', "Alerts when the debugger breaks. Also see {0}.", '`#audioCues.onDebugBreak#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, [AccessibilityVoiceSettingId.SpeechTimeout]: { 'markdownDescription': localize('voice.speechTimeout', "The duration in milliseconds that voice speech recognition remains active after you stop speaking. For example in a chat session, the transcribed text is submitted automatically after the timeout is met. Set to `0` to disable this feature."), 'type': 'number', diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts index fc693042325..a3eff41e1bf 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts @@ -42,6 +42,7 @@ export class AccessibleNotificationService extends Disposable implements IAccess this._events.set(AccessibleNotificationEvent.ChatRequestSent, { audioCue: AudioCue.chatRequestSent, alertMessage: localize('chatRequestSent', "Chat Request Sent"), alertSetting: AccessibilityAlertSettingId.ChatRequestSent }); this._events.set(AccessibleNotificationEvent.NotebookCellCompleted, { audioCue: AudioCue.notebookCellCompleted, alertMessage: localize('notebookCellCompleted', "Notebook Cell Completed"), alertSetting: AccessibilityAlertSettingId.NotebookCellCompleted }); this._events.set(AccessibleNotificationEvent.NotebookCellFailed, { audioCue: AudioCue.notebookCellFailed, alertMessage: localize('notebookCellFailed', "Notebook Cell Failed"), alertSetting: AccessibilityAlertSettingId.NotebookCellFailed }); + this._events.set(AccessibleNotificationEvent.OnDebugBreak, { audioCue: AudioCue.onDebugBreak, alertMessage: localize('onDebugBreak', "On Debug Break"), alertSetting: AccessibilityAlertSettingId.OnDebugBreak }); this._register(this._workingCopyService.onDidSave((e) => this._notifyBasedOnUserGesture(AccessibleNotificationEvent.Save, e.reason === SaveReason.EXPLICIT))); } diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts index e5051feb3a4..140e1f673dc 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts @@ -5,6 +5,7 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { autorunWithStore, observableFromEvent } from 'vs/base/common/observable'; +import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { IAudioCueService, AudioCue, AudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; @@ -15,7 +16,8 @@ export class AudioCueLineDebuggerContribution constructor( @IDebugService debugService: IDebugService, - @IAudioCueService private readonly audioCueService: AudioCueService, + @IAudioCueService audioCueService: AudioCueService, + @IAccessibleNotificationService private readonly accessibleNotificationService: IAccessibleNotificationService ) { super(); @@ -60,7 +62,7 @@ export class AudioCueLineDebuggerContribution const stoppedDetails = session.getStoppedDetails(); const BREAKPOINT_STOP_REASON = 'breakpoint'; if (stoppedDetails && stoppedDetails.reason === BREAKPOINT_STOP_REASON) { - this.audioCueService.playAudioCue(AudioCue.onDebugBreak); + this.accessibleNotificationService.notify(AccessibleNotificationEvent.OnDebugBreak); } }); diff --git a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts index 3f5e22cb5cc..9061c940cdb 100644 --- a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts +++ b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts @@ -14,8 +14,8 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InlayHintItem, asCommandLink } from 'vs/editor/contrib/inlayHints/browser/inlayHints'; import { InlayHintsController } from 'vs/editor/contrib/inlayHints/browser/inlayHintsController'; import { localize } from 'vs/nls'; +import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { registerAction2 } from 'vs/platform/actions/common/actions'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -40,7 +40,7 @@ export class InlayHintsAccessibility implements IEditorContribution { constructor( private readonly _editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService, @IInstantiationService private readonly _instaService: IInstantiationService, ) { this._ariaElement = document.createElement('span'); @@ -156,7 +156,7 @@ export class InlayHintsAccessibility implements IEditorContribution { const line = this._editor.getPosition().lineNumber; const hints = InlayHintsController.get(this._editor)?.getInlayHintsForLine(line); if (!hints || hints.length === 0) { - this._audioCueService.playAudioCue(AudioCue.noInlayHints); + this._accessibleNotificationService.notify(AccessibleNotificationEvent.NoInlayHints); } else { this._read(line, hints); } From 40c0f939e543a90e69eae60b4eaef8fa23f25fdb Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 4 Jan 2024 06:40:11 +0800 Subject: [PATCH 0049/1897] Fix optionalReplacementSpan not being applied to completion entries (#200945) * Fix optionalReplacementSpan not being applied to completion entries * Format completions.ts --- .../src/languageFeatures/completions.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/extensions/typescript-language-features/src/languageFeatures/completions.ts b/extensions/typescript-language-features/src/languageFeatures/completions.ts index b97c9b8b3fb..943a2437449 100644 --- a/extensions/typescript-language-features/src/languageFeatures/completions.ts +++ b/extensions/typescript-language-features/src/languageFeatures/completions.ts @@ -760,6 +760,12 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< isIncomplete = !!response.body.isIncomplete || (response as any).metadata && (response as any).metadata.isIncomplete; entries = response.body.entries; metadata = response.metadata; + + if (response.body.optionalReplacementSpan) { + for (const entry of entries) { + entry.replacementSpan ??= response.body.optionalReplacementSpan; + } + } } else { const response = await this.client.interruptGetErr(() => this.client.execute('completions', args, token)); if (response.type !== 'response' || !response.body) { From 95c1e5236adc94b942dd752516a182baff3f0879 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 4 Jan 2024 13:11:59 +0530 Subject: [PATCH 0050/1897] fix #199946 (#201761) --- .../output/browser/output.contribution.ts | 84 ++++++++++++------- 1 file changed, 56 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 71b27b54876..00448ae5ec4 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -15,12 +15,12 @@ import { OutputViewPane } from 'vs/workbench/contrib/output/browser/outputView'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ViewContainer, IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions, IViewsRegistry, IViewsService } from 'vs/workbench/common/views'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IQuickPickItem, IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { AUX_WINDOW_GROUP, AUX_WINDOW_GROUP_TYPE, IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { assertIsDefined } from 'vs/base/common/types'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Codicon } from 'vs/base/common/codicons'; @@ -81,8 +81,9 @@ Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews class OutputContribution extends Disposable implements IWorkbenchContribution { constructor( - @IInstantiationService instantiationService: IInstantiationService, @IOutputService private readonly outputService: IOutputService, + @IEditorService private readonly editorService: IEditorService, + @IFilesConfigurationService private readonly fileConfigurationService: IFilesConfigurationService, ) { super(); this.registerActions(); @@ -94,6 +95,7 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { this.registerClearOutputAction(); this.registerToggleAutoScrollAction(); this.registerOpenActiveOutputFileAction(); + this.registerOpenActiveOutputFileInAuxWindowAction(); this.registerShowLogsAction(); this.registerOpenLogFileAction(); } @@ -260,11 +262,12 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { } private registerOpenActiveOutputFileAction(): void { + const that = this; this._register(registerAction2(class extends Action2 { constructor() { super({ id: `workbench.action.openActiveLogOutputFile`, - title: nls.localize2('openActiveOutputFile', "Open Output as Editor"), + title: nls.localize2('openActiveOutputFile', "Open Output in Editor"), menu: [{ id: MenuId.ViewTitle, when: ContextKeyExpr.equals('view', OUTPUT_VIEW_ID), @@ -275,34 +278,59 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { precondition: CONTEXT_ACTIVE_FILE_OUTPUT }); } - async run(accessor: ServicesAccessor): Promise { - const outputService = accessor.get(IOutputService); - const editorService = accessor.get(IEditorService); - const fileConfigurationService = accessor.get(IFilesConfigurationService); - const fileOutputChannelDescriptor = this.getFileOutputChannelDescriptor(outputService); - if (fileOutputChannelDescriptor) { - await fileConfigurationService.updateReadonly(fileOutputChannelDescriptor.file, true); - await editorService.openEditor({ - resource: fileOutputChannelDescriptor.file, - options: { - pinned: true, - } - }); - } - } - private getFileOutputChannelDescriptor(outputService: IOutputService): IFileOutputChannelDescriptor | null { - const channel = outputService.getActiveChannel(); - if (channel) { - const descriptor = outputService.getChannelDescriptors().filter(c => c.id === channel.id)[0]; - if (descriptor?.file) { - return descriptor; - } - } - return null; + async run(): Promise { + that.openActiveOutoutFile(); } })); } + private registerOpenActiveOutputFileInAuxWindowAction(): void { + const that = this; + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.action.openActiveLogOutputFileInNewWindow`, + title: nls.localize2('openActiveOutputFileInNewWindow', "Open Output in New Window"), + menu: [{ + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', OUTPUT_VIEW_ID), + group: 'navigation', + order: 5 + }], + icon: Codicon.window, + precondition: CONTEXT_ACTIVE_FILE_OUTPUT + }); + } + async run(): Promise { + that.openActiveOutoutFile(AUX_WINDOW_GROUP); + } + })); + } + + private async openActiveOutoutFile(group?: AUX_WINDOW_GROUP_TYPE): Promise { + const fileOutputChannelDescriptor = this.getFileOutputChannelDescriptor(); + if (fileOutputChannelDescriptor) { + await this.fileConfigurationService.updateReadonly(fileOutputChannelDescriptor.file, true); + await this.editorService.openEditor({ + resource: fileOutputChannelDescriptor.file, + options: { + pinned: true, + }, + }, group); + } + } + + private getFileOutputChannelDescriptor(): IFileOutputChannelDescriptor | null { + const channel = this.outputService.getActiveChannel(); + if (channel) { + const descriptor = this.outputService.getChannelDescriptors().filter(c => c.id === channel.id)[0]; + if (descriptor?.file) { + return descriptor; + } + } + return null; + } + private registerShowLogsAction(): void { this._register(registerAction2(class extends Action2 { constructor() { From c016ce64fba0b8ab1dfb7eb7b7e29f07448dd208 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 3 Jan 2024 23:42:22 -0800 Subject: [PATCH 0051/1897] testing: misc work on test coverage (#201758) - Allow coverage bar color thresholds to be configurable as the Java folks requested. - Update some of our scripts for integration into the selfhost test runner. - Initial parts of showing function coverage in the Test Coverage view. (Still a work in progress, more tomorrow) --- package.json | 2 +- src/vs/workbench/api/common/extHostTypes.ts | 15 +- .../contrib/testing/browser/media/testing.css | 1 - .../testing/browser/testCoverageBars.ts | 80 +++-- .../testing/browser/testCoverageView.ts | 302 ++++++++++++++---- .../contrib/testing/common/configuration.ts | 21 ++ .../contrib/testing/common/testCoverage.ts | 42 ++- .../contrib/testing/common/testTypes.ts | 4 +- test/unit/coverage.js | 7 +- test/unit/electron/index.js | 2 +- test/unit/electron/renderer.js | 2 +- test/unit/node/index.js | 5 +- yarn.lock | 10 +- 13 files changed, 362 insertions(+), 131 deletions(-) diff --git a/package.json b/package.json index e1d479a26eb..fa7242f618b 100644 --- a/package.json +++ b/package.json @@ -180,7 +180,7 @@ "husky": "^0.13.1", "innosetup": "6.0.5", "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-instrument": "^5.2.0", + "istanbul-lib-instrument": "^6.0.1", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.1", "istanbul-reports": "^3.1.5", diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index de1b35423e8..fe574fb9c67 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3948,9 +3948,16 @@ export class TestTag implements vscode.TestTag { //#region Test Coverage export class CoveredCount implements vscode.CoveredCount { - constructor(public covered: number, public total: number) { } + constructor(public covered: number, public total: number) { + } } +const validateCC = (cc?: vscode.CoveredCount) => { + if (cc && cc.covered > cc.total) { + throw new Error(`The total number of covered items (${cc.covered}) cannot be greater than the total (${cc.total})`); + } +}; + export class FileCoverage implements vscode.FileCoverage { public static fromDetails(uri: vscode.Uri, details: vscode.DetailedCoverage[]): vscode.FileCoverage { const statements = new CoveredCount(0, 0); @@ -3991,7 +3998,11 @@ export class FileCoverage implements vscode.FileCoverage { public statementCoverage: vscode.CoveredCount, public branchCoverage?: vscode.CoveredCount, public functionCoverage?: vscode.CoveredCount, - ) { } + ) { + validateCC(statementCoverage); + validateCC(branchCoverage); + validateCC(functionCoverage); + } } export class StatementCoverage implements vscode.StatementCoverage { diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index 384e427d1a3..20c87255f36 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -376,7 +376,6 @@ } .test-coverage-bars .bar { - width: 16px; height: 8px; border: 1px solid currentColor; border-radius: 2px; diff --git a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts index 576294a7ffd..54272d86756 100644 --- a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts +++ b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts @@ -8,15 +8,16 @@ import { assertNever } from 'vs/base/common/assert'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; -import { ITransaction, autorun, observableFromEvent, observableValue } from 'vs/base/common/observable'; +import { clamp } from 'vs/base/common/numbers'; +import { ITransaction, autorun, observableValue } from 'vs/base/common/observable'; import { isDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { asCssVariableName, chartsGreen, chartsRed, chartsYellow } from 'vs/platform/theme/common/colorRegistry'; import { IExplorerFileContribution } from 'vs/workbench/contrib/files/browser/explorerFileContrib'; -import { TestingConfigKeys, TestingDisplayedCoveragePercent, getTestingConfiguration } from 'vs/workbench/contrib/testing/common/configuration'; -import { AbstractFileCoverage } from 'vs/workbench/contrib/testing/common/testCoverage'; +import { ITestingCoverageBarThresholds, TestingConfigKeys, TestingDisplayedCoveragePercent, getTestingConfiguration, observeTestingConfiguration } from 'vs/workbench/contrib/testing/common/configuration'; +import { AbstractFileCoverage, getTotalCoveragePercent } from 'vs/workbench/contrib/testing/common/testCoverage'; import { ITestCoverageService } from 'vs/workbench/contrib/testing/common/testCoverageService'; import { ICoveredCount } from 'vs/workbench/contrib/testing/common/testTypes'; import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; @@ -33,14 +34,11 @@ export interface TestCoverageBarsOptions { container: HTMLElement; } -const colorThresholds = [ - { color: asCssVariableName(chartsGreen), threshold: 0.9 }, - { color: asCssVariableName(chartsYellow), threshold: 0.6 }, - { color: asCssVariableName(chartsRed), threshold: -Infinity }, -]; +/** Type that can be used to render coverage bars */ +export type CoverageBarSource = Pick; export class ManagedTestCoverageBars extends Disposable { - private _coverage?: AbstractFileCoverage; + private _coverage?: CoverageBarSource; private readonly el = new Lazy(() => { if (this.options.compact) { const el = h('.test-coverage-bars.compact', [ @@ -78,7 +76,7 @@ export class ManagedTestCoverageBars extends Disposable { super(); } - private attachHover(target: HTMLElement, factory: (coverage: AbstractFileCoverage) => string | IMarkdownString | undefined) { + private attachHover(target: HTMLElement, factory: (coverage: CoverageBarSource) => string | IMarkdownString | undefined) { target.onmouseenter = () => { if (!this._coverage) { return; @@ -104,7 +102,7 @@ export class ManagedTestCoverageBars extends Disposable { }; } - public setCoverageInfo(coverage: AbstractFileCoverage | undefined) { + public setCoverageInfo(coverage: CoverageBarSource | undefined) { const ds = this.visibleStore; if (!coverage) { if (this._coverage) { @@ -119,7 +117,11 @@ export class ManagedTestCoverageBars extends Disposable { ds.add(toDisposable(() => this.options.container.removeChild(root))); this.options.container.appendChild(root); ds.add(this.configurationService.onDidChangeConfiguration(c => { - if (c.affectsConfiguration(TestingConfigKeys.CoveragePercent) && this._coverage) { + if (!this._coverage) { + return; + } + + if (c.affectsConfiguration(TestingConfigKeys.CoveragePercent) || c.affectsConfiguration(TestingConfigKeys.CoverageBarThresholds)) { this.doRender(this._coverage); } })); @@ -129,36 +131,57 @@ export class ManagedTestCoverageBars extends Disposable { this.doRender(coverage); } - private doRender(coverage: AbstractFileCoverage) { + private doRender(coverage: CoverageBarSource) { const el = this.el.value; const precision = this.options.compact ? 0 : 2; + const thresholds = getTestingConfiguration(this.configurationService, TestingConfigKeys.CoverageBarThresholds); const overallStat = calculateDisplayedStat(coverage, getTestingConfiguration(this.configurationService, TestingConfigKeys.CoveragePercent)); el.overall.textContent = displayPercent(overallStat, precision); if ('tpcBar' in el) { // compact mode - renderBar(el.tpcBar, overallStat); + renderBar(el.tpcBar, overallStat, thresholds); } else { - renderBar(el.statement, percent(coverage.statement)); - renderBar(el.function, coverage.function && percent(coverage.function)); - renderBar(el.branch, coverage.branch && percent(coverage.branch)); + renderBar(el.statement, percent(coverage.statement), thresholds); + renderBar(el.function, coverage.function && percent(coverage.function), thresholds); + renderBar(el.branch, coverage.branch && percent(coverage.branch), thresholds); } } } -const percent = (cc: ICoveredCount) => cc.total === 0 ? 1 : cc.covered / cc.total; +const percent = (cc: ICoveredCount) => clamp(cc.total === 0 ? 1 : cc.covered / cc.total, 0, 1); const epsilon = 10e-8; +const barWidth = 16; -const renderBar = (bar: HTMLElement, pct: number | undefined) => { +const renderBar = (bar: HTMLElement, pct: number | undefined, thresholds: ITestingCoverageBarThresholds) => { if (pct === undefined) { bar.style.display = 'none'; } else { bar.style.display = 'block'; - bar.style.setProperty('--test-bar-width', `${pct * 100}%`); - bar.style.color = `var(${colorThresholds.find(t => pct >= t.threshold)!.color})`; + bar.style.width = `${barWidth}px`; + // this is floored so the bar is only completely filled at 100% and not 99.9% + bar.style.setProperty('--test-bar-width', `${Math.floor(pct * 16)}px`); + + let best = colorThresholds[0].color; // red + let distance = pct; + for (const { key, color } of colorThresholds) { + const t = thresholds[key] / 100; + if (t && pct >= t && pct - t < distance) { + best = color; + distance = pct - t; + } + } + + bar.style.color = best; } }; -const calculateDisplayedStat = (coverage: AbstractFileCoverage, method: TestingDisplayedCoveragePercent) => { +const colorThresholds = [ + { color: `var(${asCssVariableName(chartsRed)})`, key: 'red' }, + { color: `var(${asCssVariableName(chartsYellow)})`, key: 'yellow' }, + { color: `var(${asCssVariableName(chartsGreen)})`, key: 'green' }, +] as const; + +const calculateDisplayedStat = (coverage: CoverageBarSource, method: TestingDisplayedCoveragePercent) => { switch (method) { case TestingDisplayedCoveragePercent.Statement: return percent(coverage.statement); @@ -169,7 +192,7 @@ const calculateDisplayedStat = (coverage: AbstractFileCoverage, method: TestingD return value; } case TestingDisplayedCoveragePercent.TotalCoverage: - return coverage.tpc; + return getTotalCoveragePercent(coverage.statement, coverage.branch, coverage.function); default: assertNever(method); } @@ -187,11 +210,11 @@ const displayPercent = (value: number, precision = 2) => { return `${display}%`; }; -const stmtCoverageText = (coverage: AbstractFileCoverage) => localize('statementCoverage', '{0}/{1} statements covered ({2})', coverage.statement.covered, coverage.statement.total, displayPercent(percent(coverage.statement))); -const fnCoverageText = (coverage: AbstractFileCoverage) => coverage.function && localize('functionCoverage', '{0}/{1} functions covered ({2})', coverage.function.covered, coverage.function.total, displayPercent(percent(coverage.function))); -const branchCoverageText = (coverage: AbstractFileCoverage) => coverage.branch && localize('branchCoverage', '{0}/{1} branches covered ({2})', coverage.branch.covered, coverage.branch.total, displayPercent(percent(coverage.branch))); +const stmtCoverageText = (coverage: CoverageBarSource) => localize('statementCoverage', '{0}/{1} statements covered ({2})', coverage.statement.covered, coverage.statement.total, displayPercent(percent(coverage.statement))); +const fnCoverageText = (coverage: CoverageBarSource) => coverage.function && localize('functionCoverage', '{0}/{1} functions covered ({2})', coverage.function.covered, coverage.function.total, displayPercent(percent(coverage.function))); +const branchCoverageText = (coverage: CoverageBarSource) => coverage.branch && localize('branchCoverage', '{0}/{1} branches covered ({2})', coverage.branch.covered, coverage.branch.total, displayPercent(percent(coverage.branch))); -const getOverallHoverText = (coverage: AbstractFileCoverage) => new MarkdownString([ +const getOverallHoverText = (coverage: CoverageBarSource) => new MarkdownString([ stmtCoverageText(coverage), fnCoverageText(coverage), branchCoverageText(coverage), @@ -212,8 +235,7 @@ export class ExplorerTestCoverageBars extends ManagedTestCoverageBars implements ) { super(options, hoverService, configurationService); - const isEnabled = observableFromEvent(configurationService.onDidChangeConfiguration, () => - getTestingConfiguration(configurationService, TestingConfigKeys.ShowCoverageInExplorer)); + const isEnabled = observeTestingConfiguration(configurationService, TestingConfigKeys.ShowCoverageInExplorer); this._register(autorun(async reader => { let info: AbstractFileCoverage | undefined; diff --git a/src/vs/workbench/contrib/testing/browser/testCoverageView.ts b/src/vs/workbench/contrib/testing/browser/testCoverageView.ts index e20f3d18fbd..90d6231894a 100644 --- a/src/vs/workbench/contrib/testing/browser/testCoverageView.ts +++ b/src/vs/workbench/contrib/testing/browser/testCoverageView.ts @@ -4,17 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree'; -import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; +import { ICompressedTreeElement, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; -import { IAsyncDataSource, ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { assertNever } from 'vs/base/common/assert'; +import { Codicon } from 'vs/base/common/codicons'; +import { memoize } from 'vs/base/common/decorators'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { Iterable } from 'vs/base/common/iterator'; import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { autorun } from 'vs/base/common/observable'; -import { IPrefixTreeNode, WellDefinedPrefixTree } from 'vs/base/common/prefixTree'; +import { IPrefixTreeNode } from 'vs/base/common/prefixTree'; import { basenameOrAuthority } from 'vs/base/common/resources'; +import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; +import { Range } from 'vs/editor/common/core/range'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -24,17 +28,17 @@ import { FileKind } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ILabelService } from 'vs/platform/label/common/label'; -import { WorkbenchCompressibleAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewDescriptorService } from 'vs/workbench/common/views'; -import { ManagedTestCoverageBars } from 'vs/workbench/contrib/testing/browser/testCoverageBars'; +import { CoverageBarSource, ManagedTestCoverageBars } from 'vs/workbench/contrib/testing/browser/testCoverageBars'; import { ComputedFileCoverage, FileCoverage, TestCoverage } from 'vs/workbench/contrib/testing/common/testCoverage'; import { ITestCoverageService } from 'vs/workbench/contrib/testing/common/testCoverageService'; -import { DetailType, IFunctionCoverage } from 'vs/workbench/contrib/testing/common/testTypes'; +import { CoverageDetails, DetailType, ICoveredCount, IFunctionCoverage } from 'vs/workbench/contrib/testing/common/testTypes'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; export class TestCoverageView extends ViewPane { @@ -78,25 +82,75 @@ export class TestCoverageView extends ViewPane { } } -class TestCoverageInput { - public readonly tree: WellDefinedPrefixTree; +let fnNodeId = 0; - constructor(coverage: TestCoverage) { - this.tree = coverage.tree; +class FunctionCoverageNode { + public readonly id = String(fnNodeId++); + + public get hits() { + return this.data.count; + } + + public get name() { + return this.data.name; + } + + constructor( + private readonly data: IFunctionCoverage, + private readonly details: CoverageDetails[], + ) { } + + /** + * If the function defines a range, we can look at statements within the + * function to get total coverage for the function, rather than a boolean + * yes/no. + */ + @memoize + public attributableCoverage() { + const { location, count } = this.data; + if (!(location instanceof Range) || !count) { + return; + } + + const statement: ICoveredCount = { covered: 0, total: 0 }; + const branch: ICoveredCount = { covered: 0, total: 0 }; + for (const detail of this.details) { + if (detail.type !== DetailType.Statement) { + continue; + } + const withinFn = detail.location instanceof Range ? location.containsRange(detail.location) : location.containsPosition(detail.location); + if (!withinFn) { + continue; + } + + statement.covered += detail.count > 0 ? 0 : 1; + statement.total++; + + if (detail.branches) { + for (const { count } of detail.branches) { + branch.covered += count > 0 ? 0 : 1; + branch.total++; + } + } + } + + return { statement, branch } satisfies CoverageBarSource; } } - +const LoadingDetails = Symbol(); +const loadingDetailsLabel = localize('loadingCoverageDetails', "Loading Coverage Details..."); /** Type of nodes returned from {@link TestCoverage}. Note: value is *always* defined. */ type TestCoverageFileNode = IPrefixTreeNode; +type CoverageTreeElement = TestCoverageFileNode | FunctionCoverageNode | typeof LoadingDetails; -type CoverageTreeElement = TestCoverageFileNode | IFunctionCoverage; - -const isFileCoverage = (c: CoverageTreeElement): c is TestCoverageFileNode => 'value' in c; -const isFunctionCoverage = (c: CoverageTreeElement): c is IFunctionCoverage => 'type' in c && c.type === DetailType.Function; +const isFileCoverage = (c: CoverageTreeElement): c is TestCoverageFileNode => typeof c === 'object' && 'value' in c; +const isFunctionCoverage = (c: CoverageTreeElement): c is FunctionCoverageNode => c instanceof FunctionCoverageNode; +const shouldShowFunctionDetailsOnExpand = (c: CoverageTreeElement): c is IPrefixTreeNode => + isFileCoverage(c) && c.value instanceof FileCoverage && !!c.value.function?.total; class TestCoverageTree extends Disposable { - private readonly tree: WorkbenchCompressibleAsyncDataTree; + private readonly tree: WorkbenchCompressibleObjectTree; constructor( container: HTMLElement, @@ -106,34 +160,60 @@ class TestCoverageTree extends Disposable { ) { super(); - this.tree = >instantiationService.createInstance( - WorkbenchCompressibleAsyncDataTree, + this.tree = >instantiationService.createInstance( + WorkbenchCompressibleObjectTree, 'TestCoverageView', container, new TestCoverageTreeListDelegate(), - new TestCoverageCompressionDelegate(), - [instantiationService.createInstance(FileCoverageRenderer, labels)], - instantiationService.createInstance(TestCoverageDataSource), + [ + instantiationService.createInstance(FileCoverageRenderer, labels), + instantiationService.createInstance(FunctionCoverageRenderer, labels), + instantiationService.createInstance(LoadingDetailsRenderer), + ], { + expandOnlyOnTwistieClick: true, accessibilityProvider: { getAriaLabel(element: CoverageTreeElement) { if (isFileCoverage(element)) { const name = basenameOrAuthority(element.value!.uri); return localize('testCoverageItemLabel', "{0} coverage: {0}%", name, (element.value!.tpc * 100).toFixed(2)); } + if (isFunctionCoverage(element)) { + return element.name; + } + if (element === LoadingDetails) { + return loadingDetailsLabel; + } - return element.name; + assertNever(element); }, getWidgetAriaLabel() { return localize('testCoverageTreeLabel', "Test Coverage Explorer"); } }, - autoExpandSingleChildren: true, identityProvider: new TestCoverageIdentityProvider(), } ); this._register(this.tree); + this._register(this.tree.onDidChangeCollapseState(e => { + const el = e.node.element; + if (!e.node.collapsed && !e.node.children.length && el && shouldShowFunctionDetailsOnExpand(el)) { + if (el.value!.hasSynchronousDetails) { + this.tree.setChildren(el, [{ element: LoadingDetails, incompressible: true }]); + } + + el.value!.details().then(details => { + if (!this.tree.hasElement(el)) { + return; // avoid any issues if the tree changes in the meanwhile + } + + this.tree.setChildren(el, details + .filter((d): d is IFunctionCoverage => d.type === DetailType.Function) + .map(fn => ({ element: new FunctionCoverageNode(fn, details), incompressible: true }))); + }); + } + })); this._register(this.tree.onDidOpen(e => { let resource: URI | undefined; if (e.element && isFileCoverage(e.element) && !e.element.children?.size) { @@ -151,7 +231,28 @@ class TestCoverageTree extends Disposable { } public setInput(coverage: TestCoverage) { - this.tree.setInput(new TestCoverageInput(coverage)); + const files = []; + for (let node of coverage.tree.nodes) { + // when showing initial children, only show from the first file or tee + while (!(node.value instanceof FileCoverage) && node.children?.size === 1) { + node = Iterable.first(node.children.values())!; + } + files.push(node); + } + + const toChild = (file: TestCoverageFileNode): ICompressedTreeElement => { + const isFile = !file.children?.size; + return { + element: file, + incompressible: isFile, + collapsed: isFile, + // directories can be expanded, and items with function info can be expanded + collapsible: !isFile || !!file.value?.function?.total, + children: file.children && Iterable.map(file.children?.values(), toChild) + }; + }; + + this.tree.setChildren(null, Iterable.map(files, toChild)); } public layout(height: number, width: number) { @@ -159,57 +260,33 @@ class TestCoverageTree extends Disposable { } } -class TestCoverageDataSource implements IAsyncDataSource { - - public hasChildren(element: CoverageTreeElement | TestCoverageInput): boolean { - return element instanceof TestCoverageInput || (isFileCoverage(element) && !!element.children?.size); - } - - public async getChildren(element: CoverageTreeElement | TestCoverageInput): Promise> { - if (element instanceof TestCoverageInput) { - const files = []; - for (let node of element.tree.nodes) { - // when showing initial children, only show from the first file or tee - while (!(node.value instanceof FileCoverage) && node.children?.size === 1) { - node = Iterable.first(node.children.values())!; - } - files.push(node); - } - return files; - } - - if (isFileCoverage(element) && element.children) { - return element.children.values(); - } - - return Iterable.empty(); - } -} - -class TestCoverageCompressionDelegate implements ITreeCompressionDelegate { - isIncompressible(element: CoverageTreeElement): boolean { - return isFunctionCoverage(element) || !element.children?.size; - } -} - class TestCoverageTreeListDelegate implements IListVirtualDelegate { getHeight(element: CoverageTreeElement): number { return 22; } - getTemplateId(_element: CoverageTreeElement): string { - return FileCoverageRenderer.ID; + getTemplateId(element: CoverageTreeElement): string { + if (isFileCoverage(element)) { + return FileCoverageRenderer.ID; + } + if (isFunctionCoverage(element)) { + return FunctionCoverageRenderer.ID; + } + if (element === LoadingDetails) { + return LoadingDetailsRenderer.ID; + } + assertNever(element); } } -interface TemplateData { +interface FileTemplateData { container: HTMLElement; bars: ManagedTestCoverageBars; templateDisposables: DisposableStore; label: IResourceLabel; } -class FileCoverageRenderer implements ICompressibleTreeRenderer { +class FileCoverageRenderer implements ICompressibleTreeRenderer { public static readonly ID = 'F'; public readonly templateId = FileCoverageRenderer.ID; @@ -220,7 +297,7 @@ class FileCoverageRenderer implements ICompressibleTreeRenderer, _index: number, templateData: TemplateData): void { + public renderElement(node: ITreeNode, _index: number, templateData: FileTemplateData): void { this.doRender(node.element as TestCoverageFileNode, templateData, node.filterData); } /** @inheritdoc */ - public renderCompressedElements(node: ITreeNode, FuzzyScore>, _index: number, templateData: TemplateData): void { + public renderCompressedElements(node: ITreeNode, FuzzyScore>, _index: number, templateData: FileTemplateData): void { this.doRender(node.element.elements, templateData, node.filterData); } - public disposeTemplate(templateData: TemplateData) { + public disposeTemplate(templateData: FileTemplateData) { templateData.templateDisposables.dispose(); } /** @inheritdoc */ - private doRender(element: CoverageTreeElement | CoverageTreeElement[], templateData: TemplateData, filterData: FuzzyScore | undefined) { + private doRender(element: CoverageTreeElement | CoverageTreeElement[], templateData: FileTemplateData, filterData: FuzzyScore | undefined) { const stat = (element instanceof Array ? element[element.length - 1] : element) as TestCoverageFileNode; const file = stat.value!; const name = element instanceof Array ? element.map(e => basenameOrAuthority((e as TestCoverageFileNode).value!.uri)) : basenameOrAuthority(file.uri); @@ -264,8 +341,93 @@ class FileCoverageRenderer implements ICompressibleTreeRenderer { - public getId(element: CoverageTreeElement) { - return isFileCoverage(element) ? element.value!.uri.toString() : element.name; +interface FunctionTemplateData { + container: HTMLElement; + bars: ManagedTestCoverageBars; + templateDisposables: DisposableStore; + label: IResourceLabel; +} + +class FunctionCoverageRenderer implements ICompressibleTreeRenderer { + public static readonly ID = 'N'; + public readonly templateId = FunctionCoverageRenderer.ID; + + constructor( + private readonly labels: ResourceLabels, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { } + + /** @inheritdoc */ + public renderTemplate(container: HTMLElement): FileTemplateData { + const templateDisposables = new DisposableStore(); + container.classList.add('test-coverage-list-item'); + + return { + container, + bars: templateDisposables.add(this.instantiationService.createInstance(ManagedTestCoverageBars, { compact: false, container })), + label: templateDisposables.add(this.labels.create(container, { + supportHighlights: true, + })), + templateDisposables, + }; + } + + /** @inheritdoc */ + public renderElement(node: ITreeNode, _index: number, templateData: FileTemplateData): void { + this.doRender(node.element as FunctionCoverageNode, templateData, node.filterData); + } + + /** @inheritdoc */ + public renderCompressedElements(node: ITreeNode, FuzzyScore>, _index: number, templateData: FileTemplateData): void { + this.doRender(node.element.elements[node.element.elements.length - 1] as FunctionCoverageNode, templateData, node.filterData); + } + + public disposeTemplate(templateData: FileTemplateData) { + templateData.templateDisposables.dispose(); + } + + /** @inheritdoc */ + private doRender(element: FunctionCoverageNode, templateData: FileTemplateData, filterData: FuzzyScore | undefined) { + const classes = ['test-coverage-list-item-label']; + if (element.hits > 0) { + classes.push(...ThemeIcon.asClassNameArray(Codicon.pass)); + } + + templateData.bars.setCoverageInfo(element.attributableCoverage()); + templateData.label.setLabel(element.name, undefined, { + matches: createMatches(filterData), + extraClasses: ['test-coverage-list-item-label'], + }); + } +} + +class LoadingDetailsRenderer implements ICompressibleTreeRenderer { + public static readonly ID = 'L'; + public readonly templateId = LoadingDetailsRenderer.ID; + + renderCompressedElements(): void { + // no-op + } + + renderTemplate(container: HTMLElement): void { + container.innerText = loadingDetailsLabel; + } + + renderElement(): void { + // no-op + } + + disposeTemplate(): void { + // no-op + } +} + +class TestCoverageIdentityProvider implements IIdentityProvider { + public getId(element: CoverageTreeElement) { + return isFileCoverage(element) + ? element.value!.uri.toString() + : isFunctionCoverage(element) + ? element.id + : element.toString(); } } diff --git a/src/vs/workbench/contrib/testing/common/configuration.ts b/src/vs/workbench/contrib/testing/common/configuration.ts index e17fa6662ca..a10867e48b4 100644 --- a/src/vs/workbench/contrib/testing/common/configuration.ts +++ b/src/vs/workbench/contrib/testing/common/configuration.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { observableFromEvent } from 'vs/base/common/observable'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; @@ -21,6 +22,7 @@ export const enum TestingConfigKeys { ShowAllMessages = 'testing.showAllMessages', CoveragePercent = 'testing.displayedCoveragePercent', ShowCoverageInExplorer = 'testing.showCoverageInExplorer', + CoverageBarThresholds = 'testing.coverageBarThresholds', } export const enum AutoOpenTesting { @@ -176,9 +178,24 @@ export const testingConfiguration: IConfigurationNode = { localize('testing.displayedCoveragePercent.minimum', 'The minimum of statement, function, and branch coverage.'), ], }, + [TestingConfigKeys.CoverageBarThresholds]: { + markdownDescription: localize('testing.coverageBarThresholds', "Configures the colors used for percentages in test coverage bars."), + default: { red: 0, yellow: 60, green: 90 }, + properties: { + red: { type: 'number', minimum: 0, maximum: 100, default: 0 }, + yellow: { type: 'number', minimum: 0, maximum: 100, default: 60 }, + green: { type: 'number', minimum: 0, maximum: 100, default: 90 }, + }, + }, } }; +export interface ITestingCoverageBarThresholds { + red: number; + green: number; + yellow: number; +} + export interface ITestingConfiguration { [TestingConfigKeys.AutoRunDelay]: number; [TestingConfigKeys.AutoOpenPeekView]: AutoOpenPeekViewWhen; @@ -193,6 +210,10 @@ export interface ITestingConfiguration { [TestingConfigKeys.ShowAllMessages]: boolean; [TestingConfigKeys.CoveragePercent]: TestingDisplayedCoveragePercent; [TestingConfigKeys.ShowCoverageInExplorer]: boolean; + [TestingConfigKeys.CoverageBarThresholds]: ITestingCoverageBarThresholds; } export const getTestingConfiguration = (config: IConfigurationService, key: K) => config.getValue(key); + +export const observeTestingConfiguration = (config: IConfigurationService, key: K) => observableFromEvent(config.onDidChangeConfiguration, () => + getTestingConfiguration(config, key)); diff --git a/src/vs/workbench/contrib/testing/common/testCoverage.ts b/src/vs/workbench/contrib/testing/common/testCoverage.ts index 2ec588a0a88..5dad3a5fa58 100644 --- a/src/vs/workbench/contrib/testing/common/testCoverage.ts +++ b/src/vs/workbench/contrib/testing/common/testCoverage.ts @@ -123,6 +123,23 @@ export class TestCoverage { } } +export const getTotalCoveragePercent = (statement: ICoveredCount, branch: ICoveredCount | undefined, function_: ICoveredCount | undefined) => { + let numerator = statement.covered; + let denominator = statement.total; + + if (branch) { + numerator += branch.covered; + denominator += branch.total; + } + + if (function_) { + numerator += function_.covered; + denominator += function_.total; + } + + return denominator === 0 ? 1 : numerator / denominator; +}; + export abstract class AbstractFileCoverage { public readonly uri: URI; public readonly statement: ICoveredCount; @@ -134,20 +151,7 @@ export abstract class AbstractFileCoverage { * This is based on the Clover total coverage formula */ public get tpc() { - let numerator = this.statement.covered; - let denominator = this.statement.total; - - if (this.branch) { - numerator += this.branch.covered; - denominator += this.branch.total; - } - - if (this.function) { - numerator += this.function.covered; - denominator += this.function.total; - } - - return denominator === 0 ? 1 : numerator / denominator; + return getTotalCoveragePercent(this.statement, this.branch, this.function); } constructor(coverage: IFileCoverage) { @@ -166,6 +170,12 @@ export class ComputedFileCoverage extends AbstractFileCoverage { } export class FileCoverage extends AbstractFileCoverage { private _details?: CoverageDetails[] | Promise; + private resolved?: boolean; + + /** Gets whether details are synchronously available */ + public get hasSynchronousDetails() { + return this._details instanceof Array || this.resolved; + } constructor(coverage: IFileCoverage, private readonly index: number, private readonly accessor: ICoverageAccessor) { super(coverage); @@ -179,7 +189,9 @@ export class FileCoverage extends AbstractFileCoverage { this._details ??= this.accessor.resolveFileCoverage(this.index, token); try { - return await this._details; + const d = await this._details; + this.resolved = true; + return d; } catch (e) { this._details = undefined; throw e; diff --git a/src/vs/workbench/contrib/testing/common/testTypes.ts b/src/vs/workbench/contrib/testing/common/testTypes.ts index 3c57f2b0a53..e242bf4de05 100644 --- a/src/vs/workbench/contrib/testing/common/testTypes.ts +++ b/src/vs/workbench/contrib/testing/common/testTypes.ts @@ -631,7 +631,7 @@ export interface IFunctionCoverage { type: DetailType.Function; name: string; count: number; - location?: Range | Position; + location: Range | Position; } export namespace IFunctionCoverage { @@ -639,7 +639,7 @@ export namespace IFunctionCoverage { type: DetailType.Function; name: string; count: number; - location?: IRange | IPosition; + location: IRange | IPosition; } export const serialize: (original: IFunctionCoverage) => Serialized = serializeThingWithLocation; diff --git a/test/unit/coverage.js b/test/unit/coverage.js index ac0c1440f4a..5134343c74b 100644 --- a/test/unit/coverage.js +++ b/test/unit/coverage.js @@ -37,7 +37,7 @@ exports.initialize = function (loaderConfig) { }; }; -exports.createReport = function (isSingle) { +exports.createReport = function (isSingle, coveragePath) { const mapStore = iLibSourceMaps.createSourceMapStore(); const coverageMap = iLibCoverage.createCoverageMap(global.__coverage__); return mapStore.transformCoverage(coverageMap).then((transformed) => { @@ -52,7 +52,7 @@ exports.createReport = function (isSingle) { transformed.data = newData; const context = iLibReport.createContext({ - dir: path.join(REPO_PATH, `.build/coverage${isSingle ? '-single' : ''}`), + dir: coveragePath || path.join(REPO_PATH, `.build/coverage${isSingle ? '-single' : ''}`), coverageMap: transformed }); const tree = context.getTree('flat'); @@ -60,6 +60,9 @@ exports.createReport = function (isSingle) { const reports = []; if (isSingle) { reports.push(iReports.create('lcovonly')); + if (coveragePath) { + reports.push(iReports.create('json')); + } } else { reports.push(iReports.create('json')); reports.push(iReports.create('lcov')); diff --git a/test/unit/electron/index.js b/test/unit/electron/index.js index f61514f72f1..d02921fb483 100644 --- a/test/unit/electron/index.js +++ b/test/unit/electron/index.js @@ -43,7 +43,7 @@ const minimist = require('minimist'); * }} */ const args = minimist(process.argv.slice(2), { - string: ['grep', 'run', 'runGlob', 'reporter', 'reporter-options', 'waitServer', 'timeout', 'crash-reporter-directory', 'tfs'], + string: ['grep', 'run', 'runGlob', 'reporter', 'reporter-options', 'waitServer', 'timeout', 'crash-reporter-directory', 'tfs', 'coveragePath'], boolean: ['build', 'coverage', 'help', 'dev'], alias: { 'grep': ['g', 'f'], diff --git a/test/unit/electron/renderer.js b/test/unit/electron/renderer.js index 61237ebd8e4..1488dfe85f5 100644 --- a/test/unit/electron/renderer.js +++ b/test/unit/electron/renderer.js @@ -123,7 +123,7 @@ function initLoader(opts) { function createCoverageReport(opts) { if (opts.coverage) { - return coverage.createReport(opts.run || opts.runGlob); + return coverage.createReport(opts.run || opts.runGlob, opts.coveragePath); } return Promise.resolve(undefined); } diff --git a/test/unit/node/index.js b/test/unit/node/index.js index c1b24ad362f..d1a787e4419 100644 --- a/test/unit/node/index.js +++ b/test/unit/node/index.js @@ -23,7 +23,7 @@ const { takeSnapshotAndCountClasses } = require('../analyzeSnapshot'); */ const args = minimist(process.argv.slice(2), { boolean: ['build', 'coverage', 'help'], - string: ['run'], + string: ['run', 'coveragePath'], alias: { h: 'help' }, @@ -36,6 +36,7 @@ const args = minimist(process.argv.slice(2), { build: 'Run from out-build', run: 'Run a single file', coverage: 'Generate a coverage report', + coveragePath: 'Path to coverage report to generate', help: 'Show help' } }); @@ -141,7 +142,7 @@ function main() { if (code !== 0) { return; } - coverage.createReport(args.run || args.runGlob); + coverage.createReport(args.run || args.runGlob, args.coveragePath); }); } diff --git a/yarn.lock b/yarn.lock index 060fe354a56..28a8dd70157 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5836,16 +5836,16 @@ istanbul-lib-coverage@^3.2.0: resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== -istanbul-lib-instrument@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f" - integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== +istanbul-lib-instrument@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz#71e87707e8041428732518c6fb5211761753fbdf" + integrity sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA== dependencies: "@babel/core" "^7.12.3" "@babel/parser" "^7.14.7" "@istanbuljs/schema" "^0.1.2" istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" + semver "^7.5.4" istanbul-lib-report@^3.0.0: version "3.0.0" From c18ce2ce9521cef51e98325bb3fbddf24abb8ecd Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 4 Jan 2024 15:12:48 +0530 Subject: [PATCH 0052/1897] hide open actions by default (#201769) --- .../workbench/contrib/output/browser/output.contribution.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 00448ae5ec4..ea455fe91a6 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -272,7 +272,8 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { id: MenuId.ViewTitle, when: ContextKeyExpr.equals('view', OUTPUT_VIEW_ID), group: 'navigation', - order: 4 + order: 4, + isHiddenByDefault: true }], icon: Codicon.goToFile, precondition: CONTEXT_ACTIVE_FILE_OUTPUT @@ -295,7 +296,8 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { id: MenuId.ViewTitle, when: ContextKeyExpr.equals('view', OUTPUT_VIEW_ID), group: 'navigation', - order: 5 + order: 5, + isHiddenByDefault: true }], icon: Codicon.window, precondition: CONTEXT_ACTIVE_FILE_OUTPUT From 9aac4edf575892e9a0c0101a1166378e801ef4e2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 4 Jan 2024 15:41:38 +0530 Subject: [PATCH 0053/1897] fix #201730 (#201771) --- .../abstractExtensionManagementService.ts | 2 +- .../extensions/browser/extensionsActions.ts | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index c452ce1e2ee..c733a5984f3 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -528,7 +528,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl if (!compatibleExtension) { /** If no compatible release version is found, check if the extension has a release version or not and throw relevant error */ if (!installPreRelease && extension.properties.isPreReleaseVersion && (await this.galleryService.getExtensions([extension.identifier], CancellationToken.None))[0]) { - throw new ExtensionManagementError(nls.localize('notFoundReleaseExtension', "Can't install release version of '{0}' extension because it has no release version.", extension.identifier.id), ExtensionManagementErrorCode.ReleaseVersionNotFound); + throw new ExtensionManagementError(nls.localize('notFoundReleaseExtension', "Can't install release version of '{0}' extension because it has no release version.", extension.displayName ?? extension.identifier.id), ExtensionManagementErrorCode.ReleaseVersionNotFound); } throw new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of {1} (version {2}).", extension.identifier.id, this.productService.nameLong, this.productService.version), ExtensionManagementErrorCode.Incompatible); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 908f2949a9e..bef42b4c7b9 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -115,7 +115,24 @@ export class PromptExtensionInstallFailureAction extends Action { return; } - if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleTargetPlatform, ExtensionManagementErrorCode.Malicious, ExtensionManagementErrorCode.ReleaseVersionNotFound, ExtensionManagementErrorCode.Deprecated].includes(this.error.name)) { + if (ExtensionManagementErrorCode.ReleaseVersionNotFound === (this.error.name)) { + await this.dialogService.prompt({ + type: 'error', + message: getErrorMessage(this.error), + buttons: [{ + label: localize('install prerelease', "Install Pre-Release"), + run: () => { + const installAction = this.instantiationService.createInstance(InstallAction, { installPreReleaseVersion: true }); + installAction.extension = this.extension; + return installAction.run(); + } + }], + cancelButton: localize('cancel', "Cancel") + }); + return; + } + + if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleTargetPlatform, ExtensionManagementErrorCode.Malicious, ExtensionManagementErrorCode.Deprecated].includes(this.error.name)) { await this.dialogService.info(getErrorMessage(this.error)); return; } From f405cbff7496f1a262888e6012c75028a4ec07cb Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 4 Jan 2024 15:55:25 +0530 Subject: [PATCH 0054/1897] change icon (#201773) --- src/vs/workbench/contrib/output/browser/output.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index ea455fe91a6..4aecf41e33f 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -299,7 +299,7 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { order: 5, isHiddenByDefault: true }], - icon: Codicon.window, + icon: Codicon.emptyWindow, precondition: CONTEXT_ACTIVE_FILE_OUTPUT }); } From 901ba0737af2aea7dcbadbb9f47c6790989bd09a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 4 Jan 2024 11:28:43 +0100 Subject: [PATCH 0055/1897] Allow to store/resume open editors across Continue On transitions (fix #193704) (#201631) * Allow to store/resume open editors across Continue On transitions (fix #193704) * address feedback --- .../browser/parts/editor/editorPart.ts | 100 ++++++++++----- .../browser/parts/editor/editorParts.ts | 117 +++++++++++++----- src/vs/workbench/common/component.ts | 12 +- src/vs/workbench/common/memento.ts | 69 +++++++---- .../test/browser/notebookServiceImpl.test.ts | 1 + 5 files changed, 217 insertions(+), 82 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index be83c793d58..48818240c36 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -18,7 +18,7 @@ import { IEditorGroupView, getEditorPartOptions, impactsEditorPartOptions, IEdit import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ISerializedEditorGroupModel, isSerializedEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; import { EditorDropTarget } from 'vs/workbench/browser/parts/editor/editorDropTarget'; import { Color } from 'vs/base/common/color'; @@ -135,7 +135,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { //#endregion - private readonly workspaceMemento = this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); + private readonly workspaceMemento = this.getMemento(StorageScope.WORKSPACE, StorageTarget.USER); private readonly profileMemento = this.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE); private readonly groupViews = new Map(); @@ -172,6 +172,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { private registerListeners(): void { this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); this._register(this.themeService.onDidFileIconThemeChange(() => this.handleChangedPartOptions())); + this._register(this.onDidChangeMementoValue(StorageScope.WORKSPACE, this._store)(e => this.onDidChangeMementoState(e))); } private onConfigurationUpdated(event: IConfigurationChangeEvent): void { @@ -493,23 +494,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { }); // Recreate gridwidget with descriptor - this.doCreateGridControlWithState(gridDescriptor, activeGroup.id, currentGroupViews); - - // Layout - this.doLayout(this._contentDimension); - - // Update container - this.updateContainer(); - - // Events for groups that got added - for (const groupView of this.getGroups(GroupsOrder.GRID_APPEARANCE)) { - if (!currentGroupViews.includes(groupView)) { - this._onDidAddGroup.fire(groupView); - } - } - - // Notify group index change given layout has changed - this.notifyGroupIndexChange(); + this.doApplyGridState(gridDescriptor, activeGroup.id, currentGroupViews); // Restore focus as needed if (restoreFocus) { @@ -712,15 +697,21 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { } private doRestoreGroup(group: IEditorGroupView): void { - if (this.gridWidget) { - if (this.hasMaximizedGroup() && !this.isGroupMaximized(group)) { - this.unmaximizeGroup(); - } + if (!this.gridWidget) { + return; // method is called as part of state restore very early + } + if (this.hasMaximizedGroup() && !this.isGroupMaximized(group)) { + this.unmaximizeGroup(); + } + + try { const viewSize = this.gridWidget.getViewSize(group); if (viewSize.width === group.minimumWidth || viewSize.height === group.minimumHeight) { this.arrangeGroups(GroupsArrangement.EXPAND, group); } + } catch (error) { + // ignore: method might be called too early before view is known to grid } } @@ -1171,19 +1162,19 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { } private doCreateGridControlWithPreviousState(): boolean { - const uiState: IEditorPartUIState | undefined = this.loadState(); - if (uiState?.serializedGrid) { + const state: IEditorPartUIState | undefined = this.loadState(); + if (state?.serializedGrid) { try { // MRU - this.mostRecentActiveGroups = uiState.mostRecentActiveGroups; + this.mostRecentActiveGroups = state.mostRecentActiveGroups; // Grid Widget - this.doCreateGridControlWithState(uiState.serializedGrid, uiState.activeGroup); + this.doCreateGridControlWithState(state.serializedGrid, state.activeGroup); } catch (error) { // Log error - onUnexpectedError(new Error(`Error restoring editor grid widget: ${error} (with state: ${JSON.stringify(uiState)})`)); + onUnexpectedError(new Error(`Error restoring editor grid widget: ${error} (with state: ${JSON.stringify(state)})`)); // Clear any state we have from the failing restore this.disposeGroups(); @@ -1345,6 +1336,59 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { }; } + async applyState(state: IEditorPartUIState): Promise { + + // Close all opened editors and dispose groups + for (const group of this.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) { + const closed = await group.closeAllEditors(); + if (!closed) { + return false; + } + } + this.disposeGroups(); + + // MRU + this.mostRecentActiveGroups = state.mostRecentActiveGroups; + + // Grid Widget + this.doApplyGridState(state.serializedGrid, state.activeGroup); + + return true; + } + + private doApplyGridState(gridState: ISerializedGrid, activeGroupId: GroupIdentifier, editorGroupViewsToReuse?: IEditorGroupView[]): void { + + // Recreate grid widget from state + this.doCreateGridControlWithState(gridState, activeGroupId, editorGroupViewsToReuse); + + // Layout + this.doLayout(this._contentDimension); + + // Update container + this.updateContainer(); + + // Events for groups that got added + for (const groupView of this.getGroups(GroupsOrder.GRID_APPEARANCE)) { + if (!editorGroupViewsToReuse?.includes(groupView)) { + this._onDidAddGroup.fire(groupView); + } + } + + // Notify group index change given layout has changed + this.notifyGroupIndexChange(); + } + + private onDidChangeMementoState(e: IStorageValueChangeEvent): void { + if (e.external && e.scope === StorageScope.WORKSPACE) { + this.reloadMemento(e.scope); + + const state = this.loadState(); + if (state) { + this.applyState(state); + } + } + } + toJSON(): object { return { type: Parts.EDITOR_PART diff --git a/src/vs/workbench/browser/parts/editor/editorParts.ts b/src/vs/workbench/browser/parts/editor/editorParts.ts index 5ec469b889d..35b9750480d 100644 --- a/src/vs/workbench/browser/parts/editor/editorParts.ts +++ b/src/vs/workbench/browser/parts/editor/editorParts.ts @@ -16,7 +16,7 @@ import { distinct, firstOrDefault } from 'vs/base/common/arrays'; import { AuxiliaryEditorPart, IAuxiliaryEditorPartOpenOptions } from 'vs/workbench/browser/parts/editor/auxiliaryEditorPart'; import { MultiWindowParts } from 'vs/workbench/browser/part'; import { DeferredPromise } from 'vs/base/common/async'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IRectangle } from 'vs/platform/window/common/window'; import { getWindow } from 'vs/base/browser/dom'; @@ -51,6 +51,11 @@ export class EditorParts extends MultiWindowParts implements IEditor this._register(this.registerPart(this.mainPart)); this.restoreParts(); + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.onDidChangeMementoValue(StorageScope.WORKSPACE, this._store)(e => this.onDidChangeMementoState(e))); } protected createMainEditorPart(): MainEditorPart { @@ -180,7 +185,7 @@ export class EditorParts extends MultiWindowParts implements IEditor private static readonly EDITOR_PARTS_UI_STATE_STORAGE_KEY = 'editorparts.state'; - private readonly workspaceMemento = this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); + private readonly workspaceMemento = this.getMemento(StorageScope.WORKSPACE, StorageTarget.USER); private _isReady = false; get isReady(): boolean { return this._isReady; } @@ -204,34 +209,12 @@ export class EditorParts extends MultiWindowParts implements IEditor // that restoring was not attempted because specific // editors were opened. if (this.mainPart.willRestoreState) { - const uiState: IEditorPartsUIState | undefined = this.workspaceMemento[EditorParts.EDITOR_PARTS_UI_STATE_STORAGE_KEY]; - if (uiState?.auxiliary.length) { - const auxiliaryEditorPartPromises: Promise[] = []; - - // Create auxiliary editor parts - for (const auxiliaryEditorPartState of uiState.auxiliary) { - auxiliaryEditorPartPromises.push(this.createAuxiliaryEditorPart({ - bounds: auxiliaryEditorPartState.bounds, - state: auxiliaryEditorPartState.state, - zoomLevel: auxiliaryEditorPartState.zoomLevel - })); - } - - // Await creation - await Promise.allSettled(auxiliaryEditorPartPromises); - - // Update MRU list - if (uiState.mru.length === this.parts.length) { - this.mostRecentActiveParts = uiState.mru.map(index => this.parts[index]); - } else { - this.mostRecentActiveParts = [...this.parts]; - } + const state = this.loadState(); + if (state) { + await this.restoreState(state); } } - // Await ready - await Promise.allSettled(this.parts.map(part => part.whenReady)); - const mostRecentActivePart = firstOrDefault(this.mostRecentActiveParts); mostRecentActivePart?.activeGroup.focus(); @@ -243,8 +226,21 @@ export class EditorParts extends MultiWindowParts implements IEditor this.whenRestoredPromise.complete(); } + private loadState(): IEditorPartsUIState | undefined { + return this.workspaceMemento[EditorParts.EDITOR_PARTS_UI_STATE_STORAGE_KEY]; + } + protected override saveState(): void { - const uiState: IEditorPartsUIState = { + const state = this.createState(); + if (state.auxiliary.length === 0) { + delete this.workspaceMemento[EditorParts.EDITOR_PARTS_UI_STATE_STORAGE_KEY]; + } else { + this.workspaceMemento[EditorParts.EDITOR_PARTS_UI_STATE_STORAGE_KEY] = state; + } + } + + private createState(): IEditorPartsUIState { + return { auxiliary: this.parts.filter(part => part !== this.mainPart).map(part => { return { state: part.createState(), @@ -273,11 +269,33 @@ export class EditorParts extends MultiWindowParts implements IEditor }), mru: this.mostRecentActiveParts.map(part => this.parts.indexOf(part)) }; + } - if (uiState.auxiliary.length === 0) { - delete this.workspaceMemento[EditorParts.EDITOR_PARTS_UI_STATE_STORAGE_KEY]; - } else { - this.workspaceMemento[EditorParts.EDITOR_PARTS_UI_STATE_STORAGE_KEY] = uiState; + private async restoreState(state: IEditorPartsUIState): Promise { + if (state.auxiliary.length) { + const auxiliaryEditorPartPromises: Promise[] = []; + + // Create auxiliary editor parts + for (const auxiliaryEditorPartState of state.auxiliary) { + auxiliaryEditorPartPromises.push(this.createAuxiliaryEditorPart({ + bounds: auxiliaryEditorPartState.bounds, + state: auxiliaryEditorPartState.state, + zoomLevel: auxiliaryEditorPartState.zoomLevel + })); + } + + // Await creation + await Promise.allSettled(auxiliaryEditorPartPromises); + + // Update MRU list + if (state.mru.length === this.parts.length) { + this.mostRecentActiveParts = state.mru.map(index => this.parts[index]); + } else { + this.mostRecentActiveParts = [...this.parts]; + } + + // Await ready + await Promise.allSettled(this.parts.map(part => part.whenReady)); } } @@ -285,6 +303,41 @@ export class EditorParts extends MultiWindowParts implements IEditor return this.parts.some(part => part.hasRestorableState); } + private onDidChangeMementoState(e: IStorageValueChangeEvent): void { + if (e.external && e.scope === StorageScope.WORKSPACE) { + this.reloadMemento(e.scope); + + const state = this.loadState(); + if (state) { + this.applyState(state); + } + } + } + + private async applyState(state: IEditorPartsUIState): Promise { + + // Close all editors and auxiliary parts first + for (const part of this.parts) { + if (part === this.mainPart) { + continue; // main part takes care on its own + } + + for (const group of part.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) { + const closed = await group.closeAllEditors(); + if (!closed) { + return false; + } + } + + (part as unknown as IAuxiliaryEditorPart).close(); + } + + // Restore auxiliary state + await this.restoreState(state); + + return true; + } + //#endregion //#region Events diff --git a/src/vs/workbench/common/component.ts b/src/vs/workbench/common/component.ts index f4454acc56f..6c25dc9d977 100644 --- a/src/vs/workbench/common/component.ts +++ b/src/vs/workbench/common/component.ts @@ -5,7 +5,9 @@ import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; export class Component extends Themable { @@ -39,6 +41,14 @@ export class Component extends Themable { return this.memento.getMemento(scope, target); } + protected reloadMemento(scope: StorageScope): void { + return this.memento.reloadMemento(scope); + } + + protected onDidChangeMementoValue(scope: StorageScope, disposables: DisposableStore): Event { + return this.memento.onDidChangeValue(scope, disposables); + } + protected saveState(): void { // Subclasses to implement for storing state } diff --git a/src/vs/workbench/common/memento.ts b/src/vs/workbench/common/memento.ts index 4c6cea09123..ad0e9e50fe3 100644 --- a/src/vs/workbench/common/memento.ts +++ b/src/vs/workbench/common/memento.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { isEmptyObject } from 'vs/base/common/types'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; export type MementoObject = { [key: string]: any }; @@ -25,8 +27,6 @@ export class Memento { getMemento(scope: StorageScope, target: StorageTarget): MementoObject { switch (scope) { - - // Scope by Workspace case StorageScope.WORKSPACE: { let workspaceMemento = Memento.workspaceMementos.get(this.id); if (!workspaceMemento) { @@ -37,7 +37,6 @@ export class Memento { return workspaceMemento.getMemento(); } - // Scope Profile case StorageScope.PROFILE: { let profileMemento = Memento.profileMementos.get(this.id); if (!profileMemento) { @@ -48,7 +47,6 @@ export class Memento { return profileMemento.getMemento(); } - // Scope Application case StorageScope.APPLICATION: { let applicationMemento = Memento.applicationMementos.get(this.id); if (!applicationMemento) { @@ -61,12 +59,33 @@ export class Memento { } } + onDidChangeValue(scope: StorageScope, disposables: DisposableStore): Event { + return this.storageService.onDidChangeValue(scope, this.id, disposables); + } + saveMemento(): void { Memento.workspaceMementos.get(this.id)?.save(); Memento.profileMementos.get(this.id)?.save(); Memento.applicationMementos.get(this.id)?.save(); } + reloadMemento(scope: StorageScope): void { + let memento: ScopedMemento | undefined; + switch (scope) { + case StorageScope.APPLICATION: + memento = Memento.applicationMementos.get(this.id); + break; + case StorageScope.PROFILE: + memento = Memento.profileMementos.get(this.id); + break; + case StorageScope.WORKSPACE: + memento = Memento.workspaceMementos.get(this.id); + break; + } + + memento?.reload(); + } + static clear(scope: StorageScope): void { switch (scope) { case StorageScope.WORKSPACE: @@ -84,36 +103,44 @@ export class Memento { class ScopedMemento { - private readonly mementoObj: MementoObject; + private mementoObj: MementoObject; constructor(private id: string, private scope: StorageScope, private target: StorageTarget, private storageService: IStorageService) { - this.mementoObj = this.load(); + this.mementoObj = this.doLoad(); + } + + private doLoad(): MementoObject { + try { + return this.storageService.getObject(this.id, this.scope, {}); + } catch (error) { + // Seeing reports from users unable to open editors + // from memento parsing exceptions. Log the contents + // to diagnose further + // https://github.com/microsoft/vscode/issues/102251 + onUnexpectedError(`[memento]: failed to parse contents: ${error} (id: ${this.id}, scope: ${this.scope}, contents: ${this.storageService.get(this.id, this.scope)})`); + } + + return {}; } getMemento(): MementoObject { return this.mementoObj; } - private load(): MementoObject { - const memento = this.storageService.get(this.id, this.scope); - if (memento) { - try { - return JSON.parse(memento); - } catch (error) { - // Seeing reports from users unable to open editors - // from memento parsing exceptions. Log the contents - // to diagnose further - // https://github.com/microsoft/vscode/issues/102251 - onUnexpectedError(`[memento]: failed to parse contents: ${error} (id: ${this.id}, scope: ${this.scope}, contents: ${memento})`); - } + reload(): void { + + // Clear old + for (const name of Object.getOwnPropertyNames(this.mementoObj)) { + delete this.mementoObj[name]; } - return {}; + // Assign new + Object.assign(this.mementoObj, this.doLoad()); } save(): void { if (!isEmptyObject(this.mementoObj)) { - this.storageService.store(this.id, JSON.stringify(this.mementoObj), this.scope, this.target); + this.storageService.store(this.id, this.mementoObj, this.scope, this.target); } else { this.storageService.remove(this.id, this.scope); } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookServiceImpl.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookServiceImpl.test.ts index c204630f49d..647c96305ce 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookServiceImpl.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookServiceImpl.test.ts @@ -31,6 +31,7 @@ suite('NotebookProviderInfoStore', function () { new class extends mock() { override get() { return ''; } override store() { } + override getObject() { return {}; } }, new class extends mock() { override onDidRegisterExtensions = Event.None; From 750117cf1d6d9b225288db735daf7a04ab8fce42 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 4 Jan 2024 16:25:17 +0530 Subject: [PATCH 0056/1897] fix #201688 (#201779) --- .../browser/parts/globalCompositeBar.ts | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/browser/parts/globalCompositeBar.ts b/src/vs/workbench/browser/parts/globalCompositeBar.ts index 04d417fb635..117ac45f02a 100644 --- a/src/vs/workbench/browser/parts/globalCompositeBar.ts +++ b/src/vs/workbench/browser/parts/globalCompositeBar.ts @@ -66,12 +66,14 @@ export class GlobalCompositeBar extends Disposable { super(); this.element = document.createElement('div'); - const anchorAlignment = configurationService.getValue('workbench.sideBar.location') === 'left' ? AnchorAlignment.RIGHT : AnchorAlignment.LEFT; - const anchorAxisAlignment = AnchorAxisAlignment.HORIZONTAL; + const contextMenuAlignmentOptions = () => ({ + anchorAlignment: configurationService.getValue('workbench.sideBar.location') === 'left' ? AnchorAlignment.RIGHT : AnchorAlignment.LEFT, + anchorAxisAlignment: AnchorAxisAlignment.HORIZONTAL + }); this.globalActivityActionBar = this._register(new ActionBar(this.element, { actionViewItemProvider: action => { if (action.id === GLOBAL_ACTIVITY_ID) { - return this.instantiationService.createInstance(GlobalActivityActionViewItem, this.contextMenuActionsProvider, { colors: this.colors, hoverOptions: this.activityHoverOptions }, anchorAlignment, anchorAxisAlignment); + return this.instantiationService.createInstance(GlobalActivityActionViewItem, this.contextMenuActionsProvider, { colors: this.colors, hoverOptions: this.activityHoverOptions }, contextMenuAlignmentOptions); } if (action.id === ACCOUNTS_ACTIVITY_ID) { @@ -81,8 +83,7 @@ export class GlobalCompositeBar extends Disposable { colors: this.colors, hoverOptions: this.activityHoverOptions }, - anchorAlignment, - anchorAxisAlignment, + contextMenuAlignmentOptions, (actions: IAction[]) => { actions.unshift(...[ toAction({ id: 'hideAccounts', label: localize('hideAccounts', "Hide Accounts"), run: () => this.storageService.store(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, false, StorageScope.PROFILE, StorageTarget.USER) }), @@ -159,8 +160,7 @@ abstract class AbstractGlobalActivityActionViewItem extends CompoisteBarActionVi action: CompositeBarAction, options: ICompositeBarActionViewItemOptions, private readonly contextMenuActionsProvider: () => IAction[], - private readonly anchorAlignment: AnchorAlignment | undefined, - private readonly anchorAxisAlignment: AnchorAxisAlignment | undefined, + private readonly contextMenuAlignmentOptions: () => Readonly<{ anchorAlignment: AnchorAlignment; anchorAxisAlignment: AnchorAxisAlignment }> | undefined, @IThemeService themeService: IThemeService, @IHoverService hoverService: IHoverService, @IMenuService private readonly menuService: IMenuService, @@ -258,11 +258,12 @@ abstract class AbstractGlobalActivityActionViewItem extends CompoisteBarActionVi const disposables = new DisposableStore(); const menu = disposables.add(this.menuService.createMenu(this.menuId, this.contextKeyService)); const actions = await this.resolveMainMenuActions(menu, disposables); + const { anchorAlignment, anchorAxisAlignment } = this.contextMenuAlignmentOptions() ?? { anchorAlignment: undefined, anchorAxisAlignment: undefined }; this.contextMenuService.showContextMenu({ getAnchor: () => this.label, - anchorAlignment: this.anchorAlignment, - anchorAxisAlignment: this.anchorAxisAlignment, + anchorAlignment, + anchorAxisAlignment, getActions: () => actions, onHide: () => disposables.dispose(), menuActionOptions: { renderShortTitle: true }, @@ -290,8 +291,7 @@ export class AccountsActivityActionViewItem extends AbstractGlobalActivityAction constructor( contextMenuActionsProvider: () => IAction[], options: ICompositeBarActionViewItemOptions, - anchorAlignment: AnchorAlignment | undefined, - anchorAxisAlignment: AnchorAxisAlignment | undefined, + contextMenuAlignmentOptions: () => Readonly<{ anchorAlignment: AnchorAlignment; anchorAxisAlignment: AnchorAxisAlignment }> | undefined, private readonly fillContextMenuActions: (actions: IAction[]) => void, @IThemeService themeService: IThemeService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @@ -314,7 +314,7 @@ export class AccountsActivityActionViewItem extends AbstractGlobalActivityAction name: localize('accounts', "Accounts"), classNames: ThemeIcon.asClassNameArray(GlobalCompositeBar.ACCOUNTS_ICON) }); - super(MenuId.AccountsContext, action, options, contextMenuActionsProvider, anchorAlignment, anchorAxisAlignment, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, keybindingService, activityService); + super(MenuId.AccountsContext, action, options, contextMenuActionsProvider, contextMenuAlignmentOptions, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, keybindingService, activityService); this._register(action); this.registerListeners(); this.initialize(); @@ -536,8 +536,7 @@ export class GlobalActivityActionViewItem extends AbstractGlobalActivityActionVi constructor( contextMenuActionsProvider: () => IAction[], options: ICompositeBarActionViewItemOptions, - anchorAlignment: AnchorAlignment | undefined, - anchorAxisAlignment: AnchorAxisAlignment | undefined, + contextMenuAlignmentOptions: () => Readonly<{ anchorAlignment: AnchorAlignment; anchorAxisAlignment: AnchorAxisAlignment }> | undefined, @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @IThemeService themeService: IThemeService, @IHoverService hoverService: IHoverService, @@ -555,7 +554,7 @@ export class GlobalActivityActionViewItem extends AbstractGlobalActivityActionVi name: localize('manage', "Manage"), classNames: ThemeIcon.asClassNameArray(userDataProfileService.currentProfile.icon ? ThemeIcon.fromId(userDataProfileService.currentProfile.icon) : DEFAULT_ICON) }); - super(MenuId.GlobalActivity, action, options, contextMenuActionsProvider, anchorAlignment, anchorAxisAlignment, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, keybindingService, activityService); + super(MenuId.GlobalActivity, action, options, contextMenuActionsProvider, contextMenuAlignmentOptions, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, keybindingService, activityService); this._register(action); this._register(this.userDataProfileService.onDidChangeCurrentProfile(e => { action.compositeBarActionItem = { @@ -635,7 +634,7 @@ export class SimpleAccountActivityActionViewItem extends AccountsActivityActionV }), hoverOptions, compact: true, - }, undefined, undefined, actions => actions, themeService, lifecycleService, hoverService, contextMenuService, menuService, contextKeyService, authenticationService, environmentService, productService, configurationService, keybindingService, secretStorageService, logService, activityService, instantiationService); + }, () => undefined, actions => actions, themeService, lifecycleService, hoverService, contextMenuService, menuService, contextKeyService, authenticationService, environmentService, productService, configurationService, keybindingService, secretStorageService, logService, activityService, instantiationService); } } @@ -662,6 +661,6 @@ export class SimpleGlobalActivityActionViewItem extends GlobalActivityActionView }), hoverOptions, compact: true, - }, undefined, undefined, userDataProfileService, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, environmentService, keybindingService, instantiationService, activityService); + }, () => undefined, userDataProfileService, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, environmentService, keybindingService, instantiationService, activityService); } } From e2eb6c042b4946ecc9b5a10f86331b8691828ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 4 Jan 2024 12:02:02 +0100 Subject: [PATCH 0057/1897] use sha256 in git (#201781) --- extensions/git/src/ipc/ipcServer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/git/src/ipc/ipcServer.ts b/extensions/git/src/ipc/ipcServer.ts index 8481aa4a35d..a7142fe22e1 100644 --- a/extensions/git/src/ipc/ipcServer.ts +++ b/extensions/git/src/ipc/ipcServer.ts @@ -30,7 +30,7 @@ export interface IIPCHandler { export async function createIPCServer(context?: string): Promise { const server = http.createServer(); - const hash = crypto.createHash('sha1'); + const hash = crypto.createHash('sha256'); if (!context) { const buffer = await new Promise((c, e) => crypto.randomBytes(20, (err, buf) => err ? e(err) : c(buf))); @@ -39,7 +39,7 @@ export async function createIPCServer(context?: string): Promise { hash.update(context); } - const ipcHandlePath = getIPCHandlePath(hash.digest('hex').substr(0, 10)); + const ipcHandlePath = getIPCHandlePath(hash.digest('hex').substring(0, 10)); if (process.platform !== 'win32') { try { From 907fef5b79099bdad68dd9c0b72056e6e4355d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 4 Jan 2024 14:53:57 +0100 Subject: [PATCH 0058/1897] revert index tree twistie changes (#201789) * Revert "Tree - revert options propagation (#199755)" This reverts commit 5dab5012d8e1cada5f1c9fffdc6e2525e44745ff. * Revert "update collapsible when children change" This reverts commit a8dd7f60a6204353e8d6969c8bc39f0476d46e2a. --- src/vs/base/browser/ui/tree/asyncDataTree.ts | 4 +-- src/vs/base/browser/ui/tree/indexTreeModel.ts | 6 ---- .../browser/ui/tree/indexTreeModel.test.ts | 30 ------------------- 3 files changed, 2 insertions(+), 38 deletions(-) diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 345963b1083..6bfcfaffc2e 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -1286,7 +1286,7 @@ export class CompressibleAsyncDataTree extends As return { focus, selection, expanded, scrollTop: this.scrollTop }; } - protected override render(node: IAsyncDataTreeNode, viewStateContext?: IAsyncDataTreeViewStateContext): void { + protected override render(node: IAsyncDataTreeNode, viewStateContext?: IAsyncDataTreeViewStateContext, options?: IAsyncDataTreeUpdateChildrenOptions): void { if (!this.identityProvider) { return super.render(node, viewStateContext); } @@ -1316,7 +1316,7 @@ export class CompressibleAsyncDataTree extends As const oldSelection = getUncompressedIds(this.tree.getSelection() as IAsyncDataTreeNode[]); const oldFocus = getUncompressedIds(this.tree.getFocus() as IAsyncDataTreeNode[]); - super.render(node, viewStateContext); + super.render(node, viewStateContext, options); const selection = this.getSelection(); let didChangeSelection = false; diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index ba745b069cb..4e83338804b 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -238,7 +238,6 @@ export class IndexTreeModel, TFilterData = voi const nodesToInsertIterator = Iterable.map(toInsert, el => this.createTreeNode(el, parentNode, parentNode.visible ? TreeVisibility.Visible : TreeVisibility.Hidden, revealed, treeListElementsToInsert, onDidCreateNode)); const lastIndex = location[location.length - 1]; - const lastHadChildren = parentNode.children.length > 0; // figure out what's the visible child start index right before the // splice point @@ -317,11 +316,6 @@ export class IndexTreeModel, TFilterData = voi this._onDidSplice.fire({ insertedNodes: nodesToInsert, deletedNodes }); - const currentlyHasChildren = parentNode.children.length > 0; - if (lastHadChildren !== currentlyHasChildren) { - this.setCollapsible(location.slice(0, -1), currentlyHasChildren); - } - let node: IIndexTreeNode | undefined = parentNode; while (node) { diff --git a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts index 008da2a3ea0..5657e458849 100644 --- a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts @@ -313,36 +313,6 @@ suite('IndexTreeModel', () => { assert.deepStrictEqual(list[2].depth, 1); })); - test('updates collapsible', () => withSmartSplice(options => { - const list: ITreeNode[] = []; - const model = new IndexTreeModel('test', toList(list), -1); - - model.splice([0], 0, [ - { - element: 0, children: [ - { element: 1 }, - ] - }, - ], options); - - assert.strictEqual(list[0].collapsible, true); - assert.strictEqual(list[1].collapsible, false); - - model.splice([0, 0], 1, [], options); - { - const [first, second] = list; - assert.strictEqual(first.collapsible, false); - assert.strictEqual(second, undefined); - } - - model.splice([0, 0], 0, [{ element: 1 }], options); - { - const [first, second] = list; - assert.strictEqual(first.collapsible, true); - assert.strictEqual(second.collapsible, false); - } - })); - test('expand', () => withSmartSplice(options => { const list: ITreeNode[] = []; const model = new IndexTreeModel('test', toList(list), -1); From f873eb102260dcbdd0c4068a59f21c1ef91697bd Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 4 Jan 2024 15:05:50 +0100 Subject: [PATCH 0059/1897] Polish per-window zoom (#201791) * rename editor actions * introduce `zoomPerWindow` setting * handle reset properly * inherit zoom from main window * . --- .../contrib/fontZoom/browser/fontZoom.ts | 12 +- .../workbench/browser/parts/editor/editor.ts | 3 +- .../electron-sandbox/actions/windowActions.ts | 136 ++++++------------ .../electron-sandbox/desktop.contribution.ts | 24 ++-- .../auxiliaryWindowService.ts | 9 +- 5 files changed, 70 insertions(+), 114 deletions(-) diff --git a/src/vs/editor/contrib/fontZoom/browser/fontZoom.ts b/src/vs/editor/contrib/fontZoom/browser/fontZoom.ts index 9fdab6ce5ac..bdbf9804563 100644 --- a/src/vs/editor/contrib/fontZoom/browser/fontZoom.ts +++ b/src/vs/editor/contrib/fontZoom/browser/fontZoom.ts @@ -13,8 +13,8 @@ class EditorFontZoomIn extends EditorAction { constructor() { super({ id: 'editor.action.fontZoomIn', - label: nls.localize('EditorFontZoomIn.label', "Editor Font Zoom In"), - alias: 'Editor Font Zoom In', + label: nls.localize('EditorFontZoomIn.label', "Increase Editor Font Size"), + alias: 'Increase Editor Font Size', precondition: undefined }); } @@ -29,8 +29,8 @@ class EditorFontZoomOut extends EditorAction { constructor() { super({ id: 'editor.action.fontZoomOut', - label: nls.localize('EditorFontZoomOut.label', "Editor Font Zoom Out"), - alias: 'Editor Font Zoom Out', + label: nls.localize('EditorFontZoomOut.label', "Decrease Editor Font Size"), + alias: 'Decrease Editor Font Size', precondition: undefined }); } @@ -45,8 +45,8 @@ class EditorFontZoomReset extends EditorAction { constructor() { super({ id: 'editor.action.fontZoomReset', - label: nls.localize('EditorFontZoomReset.label', "Editor Font Zoom Reset"), - alias: 'Editor Font Zoom Reset', + label: nls.localize('EditorFontZoomReset.label', "Reset Editor Font Size"), + alias: 'Reset Editor Font Size', precondition: undefined }); } diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 79daee52bef..b8c41c45d72 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -17,7 +17,6 @@ import { isObject } from 'vs/base/common/types'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IWindowsConfiguration } from 'vs/platform/window/common/window'; import { BooleanVerifier, EnumVerifier, NumberVerifier, ObjectVerifier, SetVerifier, verifyObject } from 'vs/base/common/verifier'; -import product from 'vs/platform/product/common/product'; import { IAuxiliaryWindowOpenOptions } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService'; export interface IEditorPartCreationOptions { @@ -51,7 +50,7 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { labelFormat: 'default', splitSizing: 'auto', splitOnDragAndDrop: true, - dragToOpenWindow: product.quality !== 'stable', + dragToOpenWindow: true, centeredLayoutFixedWidth: false, doubleClickTabToToggleEditorGroupSizes: 'expand', editorActionsLocation: 'default', diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index 3d69577332d..ba0f0c06364 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -23,7 +23,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/actions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { isMacintosh } from 'vs/base/common/platform'; import { getActiveWindow } from 'vs/base/browser/dom'; @@ -65,7 +65,8 @@ export class CloseWindowAction extends Action2 { abstract class BaseZoomAction extends Action2 { - private static readonly SETTING_KEY = 'window.zoomLevel'; + private static readonly ZOOM_LEVEL_SETTING_KEY = 'window.zoomLevel'; + private static readonly ZOOM_PER_WINDOW_SETTING_KEY = 'window.zoomPerWindow'; private static readonly MAX_ZOOM_LEVEL = 8; private static readonly MIN_ZOOM_LEVEL = -8; @@ -74,9 +75,37 @@ abstract class BaseZoomAction extends Action2 { super(desc); } - protected async setZoomLevel(accessor: ServicesAccessor, level: number, target: ApplyZoomTarget): Promise { + protected async setZoomLevel(accessor: ServicesAccessor, levelOrReset: number | true): Promise { const configurationService = accessor.get(IConfigurationService); + let target: ApplyZoomTarget; + if (configurationService.getValue(BaseZoomAction.ZOOM_PER_WINDOW_SETTING_KEY) !== false) { + target = ApplyZoomTarget.ACTIVE_WINDOW; + } else { + target = ApplyZoomTarget.ALL_WINDOWS; + } + + let level: number; + if (typeof levelOrReset === 'number') { + level = levelOrReset; + } else { + + // reset to 0 when we apply to all windows + if (target === ApplyZoomTarget.ALL_WINDOWS) { + level = 0; + } + + // otherwise, reset to the default zoom level + else { + const defaultLevel = configurationService.getValue(BaseZoomAction.ZOOM_LEVEL_SETTING_KEY); + if (typeof defaultLevel === 'number') { + level = defaultLevel; + } else { + level = 0; + } + } + } + level = Math.round(level); // when reaching smallest zoom, prevent fractional zoom levels if (level > BaseZoomAction.MAX_ZOOM_LEVEL || level < BaseZoomAction.MIN_ZOOM_LEVEL) { @@ -84,22 +113,22 @@ abstract class BaseZoomAction extends Action2 { } if (target === ApplyZoomTarget.ALL_WINDOWS) { - await configurationService.updateValue(BaseZoomAction.SETTING_KEY, level); + await configurationService.updateValue(BaseZoomAction.ZOOM_LEVEL_SETTING_KEY, level); } applyZoom(level, target); } } -export class ZoomInAllWindowsAction extends BaseZoomAction { +export class ZoomInAction extends BaseZoomAction { constructor() { super({ id: 'workbench.action.zoomIn', title: { - value: localize('zoomIn', "Zoom In (All Windows)"), + value: localize('zoomIn', "Zoom In"), mnemonicTitle: localize({ key: 'miZoomIn', comment: ['&& denotes a mnemonic'] }, "&&Zoom In"), - original: 'Zoom In (All Windows)' + original: 'Zoom In' }, category: Categories.View, f1: true, @@ -117,19 +146,19 @@ export class ZoomInAllWindowsAction extends BaseZoomAction { } override run(accessor: ServicesAccessor): Promise { - return super.setZoomLevel(accessor, getZoomLevel(getActiveWindow()) + 1, ApplyZoomTarget.ALL_WINDOWS); + return super.setZoomLevel(accessor, getZoomLevel(getActiveWindow()) + 1); } } -export class ZoomOutAllWindowsAction extends BaseZoomAction { +export class ZoomOutAction extends BaseZoomAction { constructor() { super({ id: 'workbench.action.zoomOut', title: { - value: localize('zoomOut', "Zoom Out (All Windows)"), + value: localize('zoomOut', "Zoom Out"), mnemonicTitle: localize({ key: 'miZoomOut', comment: ['&& denotes a mnemonic'] }, "&&Zoom Out"), - original: 'Zoom Out (All Windows)' + original: 'Zoom Out' }, category: Categories.View, f1: true, @@ -151,19 +180,19 @@ export class ZoomOutAllWindowsAction extends BaseZoomAction { } override run(accessor: ServicesAccessor): Promise { - return super.setZoomLevel(accessor, getZoomLevel(getActiveWindow()) - 1, ApplyZoomTarget.ALL_WINDOWS); + return super.setZoomLevel(accessor, getZoomLevel(getActiveWindow()) - 1); } } -export class ZoomResetAllWindowsAction extends BaseZoomAction { +export class ZoomResetAction extends BaseZoomAction { constructor() { super({ id: 'workbench.action.zoomReset', title: { - value: localize('zoomReset', "Reset Zoom (All Windows)"), + value: localize('zoomReset', "Reset Zoom"), mnemonicTitle: localize({ key: 'miZoomReset', comment: ['&& denotes a mnemonic'] }, "&&Reset Zoom"), - original: 'Reset Zoom (All Windows)' + original: 'Reset Zoom' }, category: Categories.View, f1: true, @@ -180,82 +209,7 @@ export class ZoomResetAllWindowsAction extends BaseZoomAction { } override run(accessor: ServicesAccessor): Promise { - return super.setZoomLevel(accessor, 0, ApplyZoomTarget.ALL_WINDOWS); - } -} - -export class ZoomInActiveWindowAction extends BaseZoomAction { - - constructor() { - super({ - id: 'workbench.action.zoomInActiveWindow', - title: { - value: localize('zoomInActiveWindow', "Zoom In (Active Window)"), - original: 'Zoom In (Active Window)' - }, - category: Categories.View, - f1: true, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Equal), - secondary: [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Equal), KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.NumpadAdd)] - } - }); - } - - override run(accessor: ServicesAccessor): Promise { - return super.setZoomLevel(accessor, getZoomLevel(getActiveWindow()) + 1, ApplyZoomTarget.ACTIVE_WINDOW); - } -} - -export class ZoomOutActiveWindowAction extends BaseZoomAction { - - constructor() { - super({ - id: 'workbench.action.zoomOutActiveWindow', - title: { - value: localize('zoomOutActiveWindow', "Zoom Out (Active Window)"), - original: 'Zoom Out (Active Window)' - }, - category: Categories.View, - f1: true, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Minus), - secondary: [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Minus), KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.NumpadSubtract)], - linux: { - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Minus), - secondary: [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.NumpadSubtract)] - } - } - }); - } - - override run(accessor: ServicesAccessor): Promise { - return super.setZoomLevel(accessor, getZoomLevel(getActiveWindow()) - 1, ApplyZoomTarget.ACTIVE_WINDOW); - } -} - -export class ZoomResetActiveWindowAction extends BaseZoomAction { - - constructor() { - super({ - id: 'workbench.action.zoomResetActiveWindow', - title: { - value: localize('zoomResetActiveWindow', "Reset Zoom (Active Window)"), - original: 'Reset Zoom (Active Window)' - }, - category: Categories.View, - f1: true, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Numpad0) - } - }); - } - - override run(accessor: ServicesAccessor): Promise { - return super.setZoomLevel(accessor, 0, ApplyZoomTarget.ACTIVE_WINDOW); + return super.setZoomLevel(accessor, true); } } diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 78dd92aa508..9d4ff738a27 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -10,7 +10,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { ConfigureRuntimeArgumentsAction, ToggleDevToolsAction, ReloadWindowWithExtensionsDisabledAction, OpenUserDataFolderAction } from 'vs/workbench/electron-sandbox/actions/developerActions'; -import { ZoomResetAllWindowsAction, ZoomOutAllWindowsAction, ZoomInAllWindowsAction, CloseWindowAction, SwitchWindowAction, QuickSwitchWindowAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler, ZoomInActiveWindowAction, ZoomOutActiveWindowAction, ZoomResetActiveWindowAction } from 'vs/workbench/electron-sandbox/actions/windowActions'; +import { ZoomResetAction, ZoomOutAction, ZoomInAction, CloseWindowAction, SwitchWindowAction, QuickSwitchWindowAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-sandbox/actions/windowActions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -27,18 +27,15 @@ import { ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; import { ModifierKeyEmitter } from 'vs/base/browser/dom'; import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from 'vs/workbench/common/configuration'; +import product from 'vs/platform/product/common/product'; // Actions (function registerActions(): void { // Actions: Zoom - registerAction2(ZoomInAllWindowsAction); - registerAction2(ZoomOutAllWindowsAction); - registerAction2(ZoomResetAllWindowsAction); - - registerAction2(ZoomInActiveWindowAction); - registerAction2(ZoomOutActiveWindowAction); - registerAction2(ZoomResetActiveWindowAction); + registerAction2(ZoomInAction); + registerAction2(ZoomOutAction); + registerAction2(ZoomResetAction); // Actions: Window registerAction2(SwitchWindowAction); @@ -194,11 +191,18 @@ import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from 'window.zoomLevel': { 'type': 'number', 'default': 0, - 'minimum': -5, - 'description': localize('zoomLevel', "Adjust the zoom level of the window. The original size is 0 and each increment above (e.g. 1) or below (e.g. -1) represents zooming 20% larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity."), + 'minimum': -8, + 'maximum': 8, + 'markdownDescription': localize({ comment: ['{0} will be a setting name rendered as a link'], key: 'zoomLevel' }, "Adjust the default zoom level for all windows. Each increment above `0` (e.g. `1`) or below (e.g. `-1`) represents zooming `20%` larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity. See {0} for configuring if the 'Zoom In' and 'Zoom Out' commands apply the zoom level to all windows or only the active window.", '`#window.zoomPerWindow#`'), ignoreSync: true, tags: ['accessibility'] }, + 'window.zoomPerWindow': { + 'type': 'boolean', + 'default': product.quality !== 'stable', // TODO@bpasero flip the default eventually + 'markdownDescription': localize({ comment: ['{0} will be a setting name rendered as a link'], key: 'zoomPerWindow' }, "Controls if the 'Zoom In' and 'Zoom Out' commands apply the zoom level to all windows or only the active window. See {0} for configuring a default zoom level for all windows.", '`#window.zoomLevel#`'), + tags: ['accessibility'] + }, 'window.newWindowDimensions': { 'type': 'string', 'enum': ['default', 'inherit', 'offset', 'maximized', 'fullscreen'], diff --git a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts index 04f04560893..a9ae966b65b 100644 --- a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts @@ -8,11 +8,10 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { AuxiliaryWindow, BrowserAuxiliaryWindowService, IAuxiliaryWindowOpenOptions, IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService'; import { ISandboxGlobals } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWindowsConfiguration } from 'vs/platform/window/common/window'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { INativeHostService } from 'vs/platform/native/common/native'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { CodeWindow } from 'vs/base/browser/window'; +import { CodeWindow, mainWindow } from 'vs/base/browser/window'; import { mark } from 'vs/base/common/performance'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; @@ -22,6 +21,7 @@ import { Barrier } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { applyZoom } from 'vs/platform/window/electron-sandbox/window'; +import { getZoomLevel } from 'vs/base/browser/browser'; type NativeCodeWindow = CodeWindow & { readonly vscode: ISandboxGlobals; @@ -84,13 +84,12 @@ export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService protected override createContainer(auxiliaryWindow: NativeCodeWindow, disposables: DisposableStore, options?: IAuxiliaryWindowOpenOptions) { - // Zoom level + // Zoom level (either explicitly provided or inherited from main window) let windowZoomLevel: number; if (typeof options?.zoomLevel === 'number') { windowZoomLevel = options.zoomLevel; } else { - const windowConfig = this.configurationService.getValue(); - windowZoomLevel = typeof windowConfig.window?.zoomLevel === 'number' ? windowConfig.window.zoomLevel : 0; + windowZoomLevel = getZoomLevel(mainWindow); } applyZoom(windowZoomLevel, auxiliaryWindow); From b2046c1a97cb51b5f1d03650d09ca9df1a407992 Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 4 Jan 2024 15:56:40 +0100 Subject: [PATCH 0060/1897] Support for dragging between list items (#201786) * support dragging between list items * add color to unthemed * Renamed ListDragAndDropPosition to ListViewItemDragAndDropSector in various files * rename * watch expressions * targetSector --- src/vs/base/browser/ui/list/list.ts | 19 +++++-- src/vs/base/browser/ui/list/listView.ts | 53 ++++++++++++++----- src/vs/base/browser/ui/list/listWidget.ts | 36 +++++++++---- src/vs/base/browser/ui/tree/abstractTree.ts | 12 ++--- src/vs/base/browser/ui/tree/asyncDataTree.ts | 10 ++-- src/vs/base/browser/ui/tree/tree.ts | 9 ++-- .../platform/theme/browser/defaultStyles.ts | 8 +-- src/vs/platform/theme/common/colorRegistry.ts | 3 +- src/vs/workbench/browser/dnd.ts | 6 +-- .../workbench/browser/parts/views/treeView.ts | 6 +-- .../debug/browser/watchExpressionsView.ts | 51 +++++++++++++++--- .../contrib/files/browser/views/emptyView.ts | 4 +- .../files/browser/views/explorerViewer.ts | 11 ++-- .../files/browser/views/openEditorsView.ts | 45 ++++++++++++---- .../notebook/browser/diff/notebookDiffList.ts | 4 +- .../notebook/browser/view/notebookCellList.ts | 4 +- .../contrib/scm/browser/scmViewPane.ts | 6 +-- .../terminal/browser/terminalTabsList.ts | 10 ++-- 18 files changed, 211 insertions(+), 86 deletions(-) diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index a12d3fb4515..707d4b6199c 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -7,6 +7,7 @@ import { IDragAndDropData } from 'vs/base/browser/dnd'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { GestureEvent } from 'vs/base/browser/touch'; +import { ListViewTargetSector } from 'vs/base/browser/ui/list/listView'; import { IDisposable } from 'vs/base/common/lifecycle'; export interface IListVirtualDelegate { @@ -57,6 +58,7 @@ export interface IListDragEvent { readonly browserEvent: DragEvent; readonly element: T | undefined; readonly index: number | undefined; + readonly sector: ListViewTargetSector | undefined; } export interface IListContextMenuEvent { @@ -84,11 +86,22 @@ export interface IKeyboardNavigationDelegate { mightProducePrintableCharacter(event: IKeyboardEvent): boolean; } -export const enum ListDragOverEffect { +export const enum ListDragOverEffectType { Copy, Move } +export const enum ListDragOverEffectPosition { + Over = 'drop-target', + Before = 'drop-target-before', + After = 'drop-target-after' +} + +export interface ListDragOverEffect { + type: ListDragOverEffectType; + position?: ListDragOverEffectPosition; +} + export interface IListDragOverReaction { accept: boolean; effect?: ListDragOverEffect; @@ -108,9 +121,9 @@ export interface IListDragAndDrop extends IDisposable { getDragURI(element: T): string | null; getDragLabel?(elements: T[], originalEvent: DragEvent): string | undefined; onDragStart?(data: IDragAndDropData, originalEvent: DragEvent): void; - onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | IListDragOverReaction; + onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | IListDragOverReaction; onDragLeave?(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void; - drop(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void; + drop(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void; onDragEnd?(originalEvent: DragEvent): void; } diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 9729d8ab286..a3a5fbe93f0 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -17,7 +17,7 @@ import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/ import { IRange, Range } from 'vs/base/common/range'; import { INewScrollDimensions, Scrollable, ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; import { ISpliceable } from 'vs/base/common/sequence'; -import { IListDragAndDrop, IListDragEvent, IListGestureEvent, IListMouseEvent, IListRenderer, IListTouchEvent, IListVirtualDelegate, ListDragOverEffect } from 'vs/base/browser/ui/list/list'; +import { IListDragAndDrop, IListDragEvent, IListGestureEvent, IListMouseEvent, IListRenderer, IListTouchEvent, IListVirtualDelegate, ListDragOverEffectPosition, ListDragOverEffectType } from 'vs/base/browser/ui/list/list'; import { RangeMap, shift } from 'vs/base/browser/ui/list/rangeMap'; import { IRow, RowCache } from 'vs/base/browser/ui/list/rowCache'; import { IObservableValue } from 'vs/base/common/observableValue'; @@ -48,6 +48,14 @@ export interface IListViewDragAndDrop extends IListDragAndDrop { getDragElements(element: T): T[]; } +export const enum ListViewTargetSector { + // drop position relative to the top of the item + TOP = 0, // [0%-25%) + CENTER_TOP = 1, // [25%-50%) + CENTER_BOTTOM = 2, // [50%-75%) + BOTTOM = 3 // [75%-100%) +} + export interface IListViewAccessibilityProvider { getSetSize?(element: T, index: number, listLength: number): number; getPosInSet?(element: T, index: number): number; @@ -309,6 +317,7 @@ export class ListView implements IListView { private canDrop: boolean = false; private currentDragData: IDragAndDropData | undefined; private currentDragFeedback: number[] | undefined; + private currentDragFeedbackPosition: ListDragOverEffectPosition | undefined; private currentDragFeedbackDisposable: IDisposable = Disposable.None; private onDragLeaveTimeout: IDisposable = Disposable.None; @@ -1083,7 +1092,8 @@ export class ListView implements IListView { const index = this.getItemIndexFromEventTarget(browserEvent.target || null); const item = typeof index === 'undefined' ? undefined : this.items[index]; const element = item && item.element; - return { browserEvent, index, element }; + const sector = this.getTargetSector(browserEvent, index); + return { browserEvent, index, element, sector }; } private onScroll(e: ScrollEvent): void { @@ -1184,7 +1194,7 @@ export class ListView implements IListView { } } - const result = this.dnd.onDragOver(this.currentDragData, event.element, event.index, event.browserEvent); + const result = this.dnd.onDragOver(this.currentDragData, event.element, event.index, event.sector, event.browserEvent); this.canDrop = typeof result === 'boolean' ? result : result.accept; if (!this.canDrop) { @@ -1193,7 +1203,7 @@ export class ListView implements IListView { return false; } - event.browserEvent.dataTransfer.dropEffect = (typeof result !== 'boolean' && result.effect === ListDragOverEffect.Copy) ? 'copy' : 'move'; + event.browserEvent.dataTransfer.dropEffect = (typeof result !== 'boolean' && result.effect?.type === ListDragOverEffectType.Copy) ? 'copy' : 'move'; let feedback: number[]; @@ -1211,26 +1221,35 @@ export class ListView implements IListView { feedback = distinct(feedback).filter(i => i >= -1 && i < this.length).sort((a, b) => a - b); feedback = feedback[0] === -1 ? [-1] : feedback; - if (equalsDragFeedback(this.currentDragFeedback, feedback)) { + const dragOverEffctPosition = typeof result !== 'boolean' && result.effect && result.effect.position ? result.effect.position : ListDragOverEffectPosition.Over; + + if (equalsDragFeedback(this.currentDragFeedback, feedback) && this.currentDragFeedbackPosition === dragOverEffctPosition) { return true; } this.currentDragFeedback = feedback; + this.currentDragFeedbackPosition = dragOverEffctPosition; this.currentDragFeedbackDisposable.dispose(); if (feedback[0] === -1) { // entire list feedback - this.domNode.classList.add('drop-target'); - this.rowsContainer.classList.add('drop-target'); + console.log('entire list feedback', dragOverEffctPosition); + this.domNode.classList.add(dragOverEffctPosition); + this.rowsContainer.classList.add(dragOverEffctPosition); this.currentDragFeedbackDisposable = toDisposable(() => { - this.domNode.classList.remove('drop-target'); - this.rowsContainer.classList.remove('drop-target'); + this.domNode.classList.remove(dragOverEffctPosition); + this.rowsContainer.classList.remove(dragOverEffctPosition); }); } else { + + if (feedback.length > 1 && dragOverEffctPosition !== ListDragOverEffectPosition.Over) { + throw new Error('Can\'t use multiple feedbacks with position different than \'over\''); + } + for (const index of feedback) { const item = this.items[index]!; item.dropTarget = true; - item.row?.domNode.classList.add('drop-target'); + item.row?.domNode.classList.add(dragOverEffctPosition); } this.currentDragFeedbackDisposable = toDisposable(() => { @@ -1238,7 +1257,7 @@ export class ListView implements IListView { const item = this.items[index]!; item.dropTarget = false; - item.row?.domNode.classList.remove('drop-target'); + item.row?.domNode.classList.remove(dragOverEffctPosition); } }); } @@ -1272,7 +1291,7 @@ export class ListView implements IListView { event.browserEvent.preventDefault(); dragData.update(event.browserEvent.dataTransfer); - this.dnd.drop(dragData, event.element, event.index, event.browserEvent); + this.dnd.drop(dragData, event.element, event.index, event.sector, event.browserEvent); } private onDragEnd(event: DragEvent): void { @@ -1288,6 +1307,7 @@ export class ListView implements IListView { private clearDragOverFeedback(): void { this.currentDragFeedback = undefined; + this.currentDragFeedbackPosition = undefined; this.currentDragFeedbackDisposable.dispose(); this.currentDragFeedbackDisposable = Disposable.None; } @@ -1337,6 +1357,15 @@ export class ListView implements IListView { // Util + private getTargetSector(browserEvent: DragEvent, targetIndex: number | undefined): ListViewTargetSector | undefined { + if (targetIndex === undefined) { + return undefined; + } + + const relativePosition = browserEvent.offsetY / this.items[targetIndex].size; + return Math.floor(relativePosition / 0.25); + } + private getItemIndexFromEventTarget(target: EventTarget | null): number | undefined { const scrollableElement = this.scrollableElement.getDomNode(); let element: HTMLElement | null = target as (HTMLElement | null); diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 25bf405993a..46321069256 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -26,7 +26,7 @@ import { ISpliceable } from 'vs/base/common/sequence'; import { isNumber } from 'vs/base/common/types'; import 'vs/css!./list'; import { IIdentityProvider, IKeyboardNavigationDelegate, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListEvent, IListGestureEvent, IListMouseEvent, IListRenderer, IListTouchEvent, IListVirtualDelegate, ListError } from './list'; -import { IListView, IListViewAccessibilityProvider, IListViewDragAndDrop, IListViewOptions, IListViewOptionsUpdate, ListView } from './listView'; +import { IListView, IListViewAccessibilityProvider, IListViewDragAndDrop, IListViewOptions, IListViewOptionsUpdate, ListViewTargetSector, ListView } from './listView'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; interface ITraitChangeEvent { @@ -965,14 +965,30 @@ export class DefaultStyleController implements IStyleController { content.push(`.monaco-list${suffix} .monaco-list-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`); } - if (styles.listDropBackground) { + if (styles.listDropOverBackground) { content.push(` .monaco-list${suffix}.drop-target, .monaco-list${suffix} .monaco-list-rows.drop-target, - .monaco-list${suffix} .monaco-list-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; } + .monaco-list${suffix} .monaco-list-row.drop-target { background-color: ${styles.listDropOverBackground} !important; color: inherit !important; } `); } + if (styles.listDropBetweenBackground) { + content.push(` + .monaco-list${suffix} .monaco-list-rows.drop-target-before .monaco-list-row:first-child::before, + .monaco-list${suffix} .monaco-list-row.drop-target-after + .monaco-list-row::before, + .monaco-list${suffix} .monaco-list-row.drop-target-before::before { + content: ""; position: absolute; top: 0px; left: 0px; width: 100%; height: 1px; + background-color: ${styles.listDropBetweenBackground}; + }`); + content.push(` + .monaco-list${suffix} .monaco-list-rows.drop-target-after .monaco-list-row:last-child::after, + .monaco-list${suffix} .monaco-list-row:last-child.drop-target-after::after { + content: ""; position: absolute; bottom: 0px; left: 0px; width: 100%; height: 1px; + background-color: ${styles.listDropBetweenBackground}; + }`); + } + if (styles.tableColumnsBorder) { content.push(` .monaco-table > .monaco-split-view2, @@ -1059,7 +1075,8 @@ export interface IListStyles { listInactiveFocusBackground: string | undefined; listHoverBackground: string | undefined; listHoverForeground: string | undefined; - listDropBackground: string | undefined; + listDropOverBackground: string | undefined; + listDropBetweenBackground: string | undefined; listFocusOutline: string | undefined; listInactiveFocusOutline: string | undefined; listSelectionOutline: string | undefined; @@ -1081,7 +1098,8 @@ export const unthemedListStyles: IListStyles = { listInactiveSelectionBackground: '#3F3F46', listInactiveSelectionIconForeground: '#FFFFFF', listHoverBackground: '#2A2D2E', - listDropBackground: '#383B3D', + listDropOverBackground: '#383B3D', + listDropBetweenBackground: '#EEEEEE', treeIndentGuidesStroke: '#a9a9a9', treeInactiveIndentGuidesStroke: Color.fromHex('#a9a9a9').transparent(0.4).toString(), tableColumnsBorder: Color.fromHex('#cccccc').transparent(0.2).toString(), @@ -1293,8 +1311,8 @@ class ListViewDragAndDrop implements IListViewDragAndDrop { this.dnd.onDragStart?.(data, originalEvent); } - onDragOver(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): boolean | IListDragOverReaction { - return this.dnd.onDragOver(data, targetElement, targetIndex, originalEvent); + onDragOver(data: IDragAndDropData, targetElement: T, targetIndex: number, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | IListDragOverReaction { + return this.dnd.onDragOver(data, targetElement, targetIndex, targetSector, originalEvent); } onDragLeave(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): void { @@ -1305,8 +1323,8 @@ class ListViewDragAndDrop implements IListViewDragAndDrop { this.dnd.onDragEnd?.(originalEvent); } - drop(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): void { - this.dnd.drop(data, targetElement, targetIndex, originalEvent); + drop(data: IDragAndDropData, targetElement: T, targetIndex: number, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void { + this.dnd.drop(data, targetElement, targetIndex, targetSector, originalEvent); } dispose(): void { diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index a9a30782b08..8bfae8ea917 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -12,7 +12,7 @@ import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview import { FindInput } from 'vs/base/browser/ui/findinput/findInput'; import { IInputBoxStyles, IMessage, MessageType, unthemedInboxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; +import { ElementsDragAndDropData, ListViewTargetSector } from 'vs/base/browser/ui/list/listView'; import { IListOptions, IListStyles, isActionItem, isButton, isInputElement, isMonacoCustomToggle, isMonacoEditor, isStickyScrollElement, List, MouseController, TypeNavigationMode } from 'vs/base/browser/ui/list/listWidget'; import { IToggleStyles, Toggle, unthemedToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel'; @@ -81,8 +81,8 @@ class TreeNodeListDragAndDrop implements IListDragAndDrop< this.dnd.onDragStart?.(asTreeDragAndDropData(data), originalEvent); } - onDragOver(data: IDragAndDropData, targetNode: ITreeNode | undefined, targetIndex: number | undefined, originalEvent: DragEvent, raw = true): boolean | IListDragOverReaction { - const result = this.dnd.onDragOver(asTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, originalEvent); + onDragOver(data: IDragAndDropData, targetNode: ITreeNode | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent, raw = true): boolean | IListDragOverReaction { + const result = this.dnd.onDragOver(asTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, targetSector, originalEvent); const didChangeAutoExpandNode = this.autoExpandNode !== targetNode; if (didChangeAutoExpandNode) { @@ -124,7 +124,7 @@ class TreeNodeListDragAndDrop implements IListDragAndDrop< const parentNode = model.getNode(parentRef); const parentIndex = parentRef && model.getListIndex(parentRef); - return this.onDragOver(data, parentNode, parentIndex, originalEvent, false); + return this.onDragOver(data, parentNode, parentIndex, targetSector, originalEvent, false); } const model = this.modelProvider(); @@ -135,11 +135,11 @@ class TreeNodeListDragAndDrop implements IListDragAndDrop< return { ...result, feedback: range(start, start + length) }; } - drop(data: IDragAndDropData, targetNode: ITreeNode | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void { + drop(data: IDragAndDropData, targetNode: ITreeNode | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void { this.autoExpandDisposable.dispose(); this.autoExpandNode = undefined; - this.dnd.drop(asTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, originalEvent); + this.dnd.drop(asTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, targetSector, originalEvent); } onDragEnd(originalEvent: DragEvent): void { diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 6bfcfaffc2e..bd86417690c 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -5,7 +5,7 @@ import { IDragAndDropData } from 'vs/base/browser/dnd'; import { IIdentityProvider, IListDragAndDrop, IListDragOverReaction, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; +import { ElementsDragAndDropData, ListViewTargetSector } from 'vs/base/browser/ui/list/listView'; import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { ComposedTreeDelegate, TreeFindMode as TreeFindMode, IAbstractTreeOptions, IAbstractTreeOptionsUpdate, TreeFindMatchType } from 'vs/base/browser/ui/tree/abstractTree'; import { ICompressedTreeElement, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; @@ -199,12 +199,12 @@ class AsyncDataTreeNodeListDragAndDrop implements IListDragAndDrop | undefined, targetIndex: number | undefined, originalEvent: DragEvent, raw = true): boolean | IListDragOverReaction { - return this.dnd.onDragOver(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element as T, targetIndex, originalEvent); + onDragOver(data: IDragAndDropData, targetNode: IAsyncDataTreeNode | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent, raw = true): boolean | IListDragOverReaction { + return this.dnd.onDragOver(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element as T, targetIndex, targetSector, originalEvent); } - drop(data: IDragAndDropData, targetNode: IAsyncDataTreeNode | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void { - this.dnd.drop(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element as T, targetIndex, originalEvent); + drop(data: IDragAndDropData, targetNode: IAsyncDataTreeNode | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void { + this.dnd.drop(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element as T, targetIndex, targetSector, originalEvent); } onDragEnd(originalEvent: DragEvent): void { diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index e929dc7de09..0a073ad57fd 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -5,7 +5,8 @@ import { IDragAndDropData } from 'vs/base/browser/dnd'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; -import { IListDragAndDrop, IListDragOverReaction, IListRenderer, ListDragOverEffect } from 'vs/base/browser/ui/list/list'; +import { IListDragAndDrop, IListDragOverReaction, IListRenderer, ListDragOverEffectPosition, ListDragOverEffectType } from 'vs/base/browser/ui/list/list'; +import { ListViewTargetSector } from 'vs/base/browser/ui/list/listView'; import { Event } from 'vs/base/common/event'; export const enum TreeVisibility { @@ -212,12 +213,12 @@ export interface ITreeDragOverReaction extends IListDragOverReaction { export const TreeDragOverReactions = { acceptBubbleUp(): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Up }; }, acceptBubbleDown(autoExpand = false): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Down, autoExpand }; }, - acceptCopyBubbleUp(): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Up, effect: ListDragOverEffect.Copy }; }, - acceptCopyBubbleDown(autoExpand = false): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Down, effect: ListDragOverEffect.Copy, autoExpand }; } + acceptCopyBubbleUp(): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Up, effect: { type: ListDragOverEffectType.Copy, position: ListDragOverEffectPosition.Over } }; }, + acceptCopyBubbleDown(autoExpand = false): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Down, effect: { type: ListDragOverEffectType.Copy, position: ListDragOverEffectPosition.Over }, autoExpand }; } }; export interface ITreeDragAndDrop extends IListDragAndDrop { - onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction; + onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction; } export class TreeError extends Error { diff --git a/src/vs/platform/theme/browser/defaultStyles.ts b/src/vs/platform/theme/browser/defaultStyles.ts index 5a631749859..a455c0ec614 100644 --- a/src/vs/platform/theme/browser/defaultStyles.ts +++ b/src/vs/platform/theme/browser/defaultStyles.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IButtonStyles } from 'vs/base/browser/ui/button/button'; import { IKeybindingLabelStyles } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; -import { ColorIdentifier, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, asCssVariable, widgetShadow, buttonForeground, buttonSeparator, buttonBackground, buttonHoverBackground, buttonSecondaryForeground, buttonSecondaryBackground, buttonSecondaryHoverBackground, buttonBorder, progressBarBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputActiveOptionBackground, editorWidgetBackground, editorWidgetForeground, contrastBorder, checkboxBorder, checkboxBackground, checkboxForeground, problemsErrorIconForeground, problemsWarningIconForeground, problemsInfoIconForeground, inputBackground, inputForeground, inputBorder, textLinkForeground, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationErrorBorder, inputValidationErrorBackground, inputValidationErrorForeground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFilterWidgetShadow, badgeBackground, badgeForeground, breadcrumbsBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, activeContrastBorder, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropBackground, listFocusAndSelectionOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, tableColumnsBorder, tableOddRowsBackgroundColor, treeIndentGuidesStroke, asCssVariableWithDefault, editorWidgetBorder, focusBorder, pickerGroupForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, selectBackground, selectBorder, selectForeground, selectListBackground, treeInactiveIndentGuidesStroke, menuBorder, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuSeparatorBackground, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; +import { ColorIdentifier, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, asCssVariable, widgetShadow, buttonForeground, buttonSeparator, buttonBackground, buttonHoverBackground, buttonSecondaryForeground, buttonSecondaryBackground, buttonSecondaryHoverBackground, buttonBorder, progressBarBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputActiveOptionBackground, editorWidgetBackground, editorWidgetForeground, contrastBorder, checkboxBorder, checkboxBackground, checkboxForeground, problemsErrorIconForeground, problemsWarningIconForeground, problemsInfoIconForeground, inputBackground, inputForeground, inputBorder, textLinkForeground, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationErrorBorder, inputValidationErrorBackground, inputValidationErrorForeground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFilterWidgetShadow, badgeBackground, badgeForeground, breadcrumbsBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, activeContrastBorder, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropOverBackground, listFocusAndSelectionOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, tableColumnsBorder, tableOddRowsBackgroundColor, treeIndentGuidesStroke, asCssVariableWithDefault, editorWidgetBorder, focusBorder, pickerGroupForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, selectBackground, selectBorder, selectForeground, selectListBackground, treeInactiveIndentGuidesStroke, menuBorder, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuSeparatorBackground, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listDropBetweenBackground } from 'vs/platform/theme/common/colorRegistry'; import { IProgressBarStyles } from 'vs/base/browser/ui/progressbar/progressbar'; import { ICheckboxStyles, IToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; import { IDialogStyles } from 'vs/base/browser/ui/dialog/dialog'; @@ -168,7 +168,8 @@ export const defaultListStyles: IListStyles = { listInactiveFocusOutline: asCssVariable(listInactiveFocusOutline), listHoverBackground: asCssVariable(listHoverBackground), listHoverForeground: asCssVariable(listHoverForeground), - listDropBackground: asCssVariable(listDropBackground), + listDropOverBackground: asCssVariable(listDropOverBackground), + listDropBetweenBackground: asCssVariable(listDropBetweenBackground), listSelectionOutline: asCssVariable(activeContrastBorder), listHoverOutline: asCssVariable(activeContrastBorder), treeIndentGuidesStroke: asCssVariable(treeIndentGuidesStroke), @@ -201,7 +202,8 @@ export const defaultSelectBoxStyles: ISelectBoxStyles = { listActiveSelectionForeground: undefined, listActiveSelectionIconForeground: undefined, listFocusAndSelectionBackground: undefined, - listDropBackground: undefined, + listDropOverBackground: undefined, + listDropBetweenBackground: undefined, listInactiveSelectionBackground: undefined, listInactiveSelectionForeground: undefined, listInactiveFocusBackground: undefined, diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 7df2d5b85b0..05989bbbb4d 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -454,7 +454,8 @@ export const listInactiveFocusBackground = registerColor('list.inactiveFocusBack export const listInactiveFocusOutline = registerColor('list.inactiveFocusOutline', { dark: null, light: null, hcDark: null, hcLight: null }, nls.localize('listInactiveFocusOutline', "List/Tree outline color for the focused item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); export const listHoverBackground = registerColor('list.hoverBackground', { dark: '#2A2D2E', light: '#F0F0F0', hcDark: Color.white.transparent(0.1), hcLight: Color.fromHex('#0F4A85').transparent(0.1) }, nls.localize('listHoverBackground', "List/Tree background when hovering over items using the mouse.")); export const listHoverForeground = registerColor('list.hoverForeground', { dark: null, light: null, hcDark: null, hcLight: null }, nls.localize('listHoverForeground', "List/Tree foreground when hovering over items using the mouse.")); -export const listDropBackground = registerColor('list.dropBackground', { dark: '#062F4A', light: '#D6EBFF', hcDark: null, hcLight: null }, nls.localize('listDropBackground', "List/Tree drag and drop background when moving items around using the mouse.")); +export const listDropOverBackground = registerColor('list.dropBackground', { dark: '#062F4A', light: '#D6EBFF', hcDark: null, hcLight: null }, nls.localize('listDropBackground', "List/Tree drag and drop background when moving items over other items when using the mouse.")); +export const listDropBetweenBackground = registerColor('list.dropBetweenBackground', { dark: iconForeground, light: iconForeground, hcDark: null, hcLight: null }, nls.localize('listDropBetweenBackground', "List/Tree drag and drop border color when moving items between items when using the mouse.")); export const listHighlightForeground = registerColor('list.highlightForeground', { dark: '#2AAAFF', light: '#0066BF', hcDark: focusBorder, hcLight: focusBorder }, nls.localize('highlight', 'List/Tree foreground color of the match highlights when searching inside the list/tree.')); export const listFocusHighlightForeground = registerColor('list.focusHighlightForeground', { dark: listHighlightForeground, light: ifDefinedThenElse(listActiveSelectionBackground, listHighlightForeground, '#BBE7FF'), hcDark: listHighlightForeground, hcLight: listHighlightForeground }, nls.localize('listFocusHighlightForeground', 'List/Tree foreground color of the match highlights on actively focused items when searching inside the list/tree.')); export const listInvalidItemForeground = registerColor('list.invalidItemForeground', { dark: '#B89500', light: '#B89500', hcDark: '#B89500', hcLight: '#B5200D' }, nls.localize('invalidItemForeground', 'List/Tree foreground color for invalid items, for example an unresolved root in explorer.')); diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index b130fffd421..80e00159bbc 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -7,7 +7,7 @@ import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd'; import { DragAndDropObserver, EventType, addDisposableListener, onDidRegisterWindow } from 'vs/base/browser/dom'; import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; import { IListDragAndDrop } from 'vs/base/browser/ui/list/list'; -import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; +import { ElementsDragAndDropData, ListViewTargetSector } from 'vs/base/browser/ui/list/listView'; import { ITreeDragOverReaction } from 'vs/base/browser/ui/tree/tree'; import { coalesce } from 'vs/base/common/arrays'; import { UriList, VSDataTransfer } from 'vs/base/common/dataTransfer'; @@ -653,11 +653,11 @@ export class ResourceListDnDHandler implements IListDragAndDrop { } } - onDragOver(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction { + onDragOver(data: IDragAndDropData, targetElement: T, targetIndex: number, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { return false; } - drop(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): void { } + drop(data: IDragAndDropData, targetElement: T, targetIndex: number, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void { } dispose(): void { } } diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index f44b0bb362e..71e907b8760 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -11,7 +11,7 @@ import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { ITooltipMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; +import { ElementsDragAndDropData, ListViewTargetSector } from 'vs/base/browser/ui/list/listView'; import { IAsyncDataSource, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, ITreeNode, ITreeRenderer, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree'; import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; import { ActionRunner, IAction } from 'vs/base/common/actions'; @@ -1740,7 +1740,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { } } - onDragOver(data: IDragAndDropData, targetElement: ITreeItem, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction { + onDragOver(data: IDragAndDropData, targetElement: ITreeItem, targetIndex: number, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { const dataTransfer = toExternalVSDataTransfer(originalEvent.dataTransfer!); const types = new Set(Array.from(dataTransfer, x => x[0])); @@ -1792,7 +1792,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { return element.label ? element.label.label : (element.resourceUri ? this.labelService.getUriLabel(URI.revive(element.resourceUri)) : undefined); } - async drop(data: IDragAndDropData, targetNode: ITreeItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): Promise { + async drop(data: IDragAndDropData, targetNode: ITreeItem | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): Promise { const dndController = this.dndController; if (!originalEvent.dataTransfer || !dndController) { return; diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index d51ba1e6f48..24bcb37bc4a 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -15,12 +15,12 @@ import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpres import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ViewPane, ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, ListDragOverEffectPosition, ListDragOverEffectType } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { IAsyncDataSource, ITreeMouseEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { IDragAndDropData } from 'vs/base/browser/dnd'; -import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; +import { ElementsDragAndDropData, ListViewTargetSector } from 'vs/base/browser/ui/list/listView'; import { FuzzyScore } from 'vs/base/common/filters'; import { IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; @@ -383,13 +383,34 @@ class WatchExpressionsDragAndDrop implements ITreeDragAndDrop { constructor(private debugService: IDebugService) { } - onDragOver(data: IDragAndDropData): boolean | ITreeDragOverReaction { + onDragOver(data: IDragAndDropData, targetElement: IExpression | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { if (!(data instanceof ElementsDragAndDropData)) { return false; } const expressions = (data as ElementsDragAndDropData).elements; - return expressions.length > 0 && expressions[0] instanceof Expression; + if (!(expressions.length > 0 && expressions[0] instanceof Expression)) { + return false; + } + + let dropEffectPosition: ListDragOverEffectPosition | undefined = undefined; + if (targetIndex === undefined) { + // Hovering over the list + dropEffectPosition = ListDragOverEffectPosition.After; + targetIndex = -1; + } else { + // Hovering over an element + switch (targetSector) { + case ListViewTargetSector.TOP: + case ListViewTargetSector.CENTER_TOP: + dropEffectPosition = ListDragOverEffectPosition.Before; break; + case ListViewTargetSector.CENTER_BOTTOM: + case ListViewTargetSector.BOTTOM: + dropEffectPosition = ListDragOverEffectPosition.After; break; + } + } + + return { accept: true, effect: { type: ListDragOverEffectType.Move, position: dropEffectPosition }, feedback: [targetIndex] } as ITreeDragOverReaction; } getDragURI(element: IExpression): string | null { @@ -408,15 +429,31 @@ class WatchExpressionsDragAndDrop implements ITreeDragAndDrop { return undefined; } - drop(data: IDragAndDropData, targetElement: IExpression): void { + drop(data: IDragAndDropData, targetElement: IExpression, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void { if (!(data instanceof ElementsDragAndDropData)) { return; } const draggedElement = (data as ElementsDragAndDropData).elements[0]; + if (!(draggedElement instanceof Expression)) { + throw new Error('Invalid dragged element'); + } + const watches = this.debugService.getModel().getWatchExpressions(); - const position = targetElement instanceof Expression ? watches.indexOf(targetElement) : watches.length - 1; - this.debugService.moveWatchExpression(draggedElement.getId(), position); + const sourcePosition = watches.indexOf(draggedElement); + let targetPosition = targetElement instanceof Expression ? watches.indexOf(targetElement) : watches.length - 1; + + switch (targetSector) { + case ListViewTargetSector.TOP: + case ListViewTargetSector.CENTER_TOP: + targetPosition -= 1; break; + } + + if (sourcePosition >= targetPosition) { + targetPosition += 1; + } + + this.debugService.moveWatchExpression(draggedElement.getId(), targetPosition); } dispose(): void { } diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index aba7df4c942..d994c7b120d 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -13,7 +13,7 @@ import { isTemporaryWorkspace, IWorkspaceContextService, WorkbenchState } from ' import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { ResourcesDropHandler } from 'vs/workbench/browser/dnd'; -import { listDropBackground } from 'vs/platform/theme/common/colorRegistry'; +import { listDropOverBackground } from 'vs/platform/theme/common/colorRegistry'; import { ILabelService } from 'vs/platform/label/common/label'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IViewDescriptorService } from 'vs/workbench/common/views'; @@ -63,7 +63,7 @@ export class EmptyView extends ViewPane { dropHandler.handleDrop(e, getWindow(container)); }, onDragEnter: () => { - const color = this.themeService.getColorTheme().getColor(listDropBackground); + const color = this.themeService.getColorTheme().getColor(listDropOverBackground); container.style.backgroundColor = color ? color.toString() : ''; }, onDragEnd: () => { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 464a8061f6f..6ad0f0cfaf2 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -6,7 +6,7 @@ import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import * as DOM from 'vs/base/browser/dom'; import * as glob from 'vs/base/common/glob'; -import { IListVirtualDelegate, ListDragOverEffect } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, ListDragOverEffectPosition, ListDragOverEffectType } from 'vs/base/browser/ui/list/list'; import { IProgressService, ProgressLocation, } from 'vs/platform/progress/common/progress'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IFileService, FileKind, FileOperationError, FileOperationResult, FileChangeType } from 'vs/platform/files/common/files'; @@ -34,7 +34,7 @@ import { fillEditorsDragData } from 'vs/workbench/browser/dnd'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; import { Schemas } from 'vs/base/common/network'; -import { NativeDragAndDropData, ExternalElementsDragAndDropData, ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; +import { NativeDragAndDropData, ExternalElementsDragAndDropData, ElementsDragAndDropData, ListViewTargetSector } from 'vs/base/browser/ui/list/listView'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { IDialogService, getFileNamesMessage } from 'vs/platform/dialogs/common/dialogs'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; @@ -1090,7 +1090,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { this.disposables.add(this.configurationService.onDidChangeConfiguration(e => updateDropEnablement(e))); } - onDragOver(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { + onDragOver(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { if (!this.dropEnabled) { return false; } @@ -1133,7 +1133,8 @@ export class FileDragAndDrop implements ITreeDragAndDrop { private handleDragOver(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { const isCopy = originalEvent && ((originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh)); const isNative = data instanceof NativeDragAndDropData; - const effect = (isNative || isCopy) ? ListDragOverEffect.Copy : ListDragOverEffect.Move; + const effectType = (isNative || isCopy) ? ListDragOverEffectType.Copy : ListDragOverEffectType.Move; + const effect = { type: effectType, position: ListDragOverEffectPosition.Over }; // Native DND if (isNative) { @@ -1252,7 +1253,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } } - async drop(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): Promise { + async drop(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): Promise { this.compressedDropTargetDisposable.dispose(); // Find compressed target diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index b2d1f3df98a..27eff7350dc 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -22,7 +22,7 @@ import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/con import { IThemeService } from 'vs/platform/theme/common/themeService'; import { asCssVariable, badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; -import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, ListDragOverEffectPosition, ListDragOverEffectType } from 'vs/base/browser/ui/list/list'; import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -36,7 +36,7 @@ import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; 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 { ElementsDragAndDropData, ListViewTargetSector, NativeDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; @@ -716,26 +716,49 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop { - oe.group.moveEditor(oe.editor, group, { index: index + offset, preserveFocus: true }); + let offset = 0; + data.elements.forEach((oe: OpenEditor) => { + // Moving an editor which is located before the target location does not change the index of the target + if (oe.group.getIndexOfEditor(oe.editor) >= targetEditorIndex) { + offset += 1; + } + oe.group.moveEditor(oe.editor, group, { index: targetIndex + offset, preserveFocus: true }); }); this.editorGroupService.activateGroup(group); } else { - this.dropHandler.handleDrop(originalEvent, mainWindow, () => group, () => group.focus(), { index }); + this.dropHandler.handleDrop(originalEvent, mainWindow, () => group, () => group.focus(), { index: targetIndex + 1 }); } } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts index ad6676cab7e..bfa29e624ee 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts @@ -443,11 +443,11 @@ export class NotebookTextDiffList extends WorkbenchList div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`); } - if (styles.listDropBackground) { + if (styles.listDropOverBackground) { content.push(` .monaco-list${suffix}.drop-target, .monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows.drop-target, - .monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; } + .monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-row.drop-target { background-color: ${styles.listDropOverBackground} !important; color: inherit !important; } `); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 7abcb85f422..e0bdf102eee 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -1330,11 +1330,11 @@ export class NotebookCellList extends WorkbenchList implements ID content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`); } - if (styles.listDropBackground) { + if (styles.listDropOverBackground) { content.push(` .monaco-list${suffix}.drop-target, .monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows.drop-target, - .monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; } + .monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-row.drop-target { background-color: ${styles.listDropOverBackground} !important; color: inherit !important; } `); } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 4870c836ac3..f4a5e2bea81 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -90,7 +90,7 @@ import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService import { Schemas } from 'vs/base/common/network'; import { IDragAndDropData } from 'vs/base/browser/dnd'; import { fillEditorsDragData } from 'vs/workbench/browser/dnd'; -import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; +import { ElementsDragAndDropData, ListViewTargetSector } from 'vs/base/browser/ui/list/listView'; import { CodeDataTransfers } from 'vs/platform/dnd/browser/dnd'; import { FormatOnType } from 'vs/editor/contrib/format/browser/formatActions'; import { EditorOption, EditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; @@ -272,11 +272,11 @@ class SCMTreeDragAndDrop implements ITreeDragAndDrop { return String(elements.length); } - onDragOver(data: IDragAndDropData, targetElement: TreeElement | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { + onDragOver(data: IDragAndDropData, targetElement: TreeElement | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { return true; } - drop(data: IDragAndDropData, targetElement: TreeElement | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void { } + drop(data: IDragAndDropData, targetElement: TreeElement | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void { } private static getResourcesFromDragAndDropData(data: ElementsDragAndDropData): URI[] { const uris: URI[] = []; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 1224faa40a8..82736672af8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -26,10 +26,10 @@ import { IDecorationData, IDecorationsProvider, IDecorationsService } from 'vs/w import { IHoverAction, IHoverService } from 'vs/workbench/services/hover/browser/hover'; import Severity from 'vs/base/common/severity'; import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IListDragAndDrop, IListDragOverReaction, IListRenderer, ListDragOverEffect } from 'vs/base/browser/ui/list/list'; +import { IListDragAndDrop, IListDragOverReaction, IListRenderer, ListDragOverEffectPosition, ListDragOverEffectType } from 'vs/base/browser/ui/list/list'; import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd'; import { disposableTimeout } from 'vs/base/common/async'; -import { ElementsDragAndDropData, NativeDragAndDropData } from 'vs/base/browser/ui/list/listView'; +import { ElementsDragAndDropData, ListViewTargetSector, NativeDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { URI } from 'vs/base/common/uri'; import { getColorClass, getIconId, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; import { IEditableData } from 'vs/workbench/common/views'; @@ -609,7 +609,7 @@ class TerminalTabsDragAndDrop extends Disposable implements IListDragAndDrop { + async drop(data: IDragAndDropData, targetInstance: ITerminalInstance | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): Promise { this._autoFocusDisposable.dispose(); this._autoFocusInstance = undefined; From 90f37e5ee25a2d16cad446b38766609aa74b244f Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 4 Jan 2024 16:49:15 +0100 Subject: [PATCH 0061/1897] Adopts localize2 (see #155983) (#201803) --- .../diffEditor/diffEditor.contribution.ts | 20 +- src/vs/nls.ts | 2 +- .../action/common/actionCommonCategories.ts | 12 +- .../common/extensionManagement.ts | 4 +- .../browser/actions/developerActions.ts | 14 +- .../workbench/browser/actions/helpActions.ts | 4 +- .../browser/actions/layoutActions.ts | 32 +-- .../browser/actions/navigationActions.ts | 14 +- .../browser/actions/quickAccessActions.ts | 10 +- .../browser/actions/windowActions.ts | 8 +- .../browser/actions/workspaceActions.ts | 24 +-- .../browser/actions/workspaceCommands.ts | 4 +- .../parts/activitybar/activitybarPart.ts | 8 +- .../parts/auxiliarybar/auxiliaryBarActions.ts | 4 +- .../parts/editor/editor.contribution.ts | 24 +-- .../browser/parts/editor/editorActions.ts | 202 +++++++++--------- .../browser/parts/editor/editorCommands.ts | 26 +-- .../browser/parts/editor/editorStatus.ts | 8 +- .../notifications/notificationsCommands.ts | 18 +- .../browser/parts/panel/panelActions.ts | 12 +- .../browser/parts/sidebar/sidebarActions.ts | 4 +- .../parts/statusbar/statusbarActions.ts | 4 +- .../browser/parts/titlebar/menubarControl.ts | 4 +- .../browser/parts/titlebar/titlebarPart.ts | 4 +- .../browser/callHierarchy.contribution.ts | 10 +- .../chat/browser/actions/chatActions.ts | 4 +- .../browser/actions/chatQuickInputActions.ts | 4 +- .../codeEditor/browser/inspectKeybindings.ts | 6 +- .../quickaccess/gotoLineQuickAccess.ts | 4 +- .../browser/toggleMultiCursorModifier.ts | 4 +- .../contrib/debug/browser/breakpointsView.ts | 6 +- .../workbench/contrib/debug/browser/repl.ts | 4 +- .../browser/editSessions.contribution.ts | 14 +- .../browser/extensions.contribution.ts | 66 +++--- .../extensions/browser/extensionsActions.ts | 14 +- .../extensions/browser/extensionsViewlet.ts | 2 +- .../electron-sandbox/extensionsActions.ts | 6 +- .../browser/interactive.contribution.ts | 18 +- .../electron-sandbox/issue.contribution.ts | 6 +- .../browser/localHistoryCommands.ts | 28 +-- .../markers/browser/markers.contribution.ts | 8 +- .../mergeEditor/browser/commands/commands.ts | 6 +- .../browser/commands/devCommands.ts | 4 +- .../electron-sandbox/devCommands.ts | 4 +- .../browser/multiDiffEditor.contribution.ts | 8 +- .../contrib/clipboard/notebookClipboard.ts | 4 +- .../browser/contrib/find/notebookFind.ts | 6 +- .../browser/contrib/format/formatting.ts | 4 +- .../browser/contrib/layout/layoutActions.ts | 4 +- .../browser/controller/coreActions.ts | 4 +- .../browser/controller/foldingController.ts | 4 +- .../browser/controller/layoutActions.ts | 8 +- .../browser/diff/notebookDiffActions.ts | 8 +- .../view/cellParts/cellEditorOptions.ts | 4 +- .../browser/viewParts/notebookKernelView.ts | 4 +- .../browser/performance.contribution.ts | 10 +- .../browser/commandsQuickAccess.ts | 6 +- .../quickaccess/browser/viewQuickAccess.ts | 6 +- .../remote/common/remote.contribution.ts | 6 +- .../contrib/scm/browser/scm.contribution.ts | 4 +- .../browser/searchEditor.contribution.ts | 26 +-- .../terminal/browser/terminalActions.ts | 108 +++++----- .../contrib/terminal/browser/terminalMenus.ts | 6 +- .../electron-sandbox/terminalRemote.ts | 4 +- .../terminal.accessibility.contribution.ts | 6 +- .../terminal.developer.contribution.ts | 8 +- ...erminal.environmentChanges.contribution.ts | 4 +- .../browser/terminal.find.contribution.ts | 18 +- .../browser/terminal.links.contribution.ts | 8 +- .../browser/terminal.quickFix.contribution.ts | 4 +- .../browser/terminal.suggest.contribution.ts | 14 +- .../testing/browser/testExplorerActions.ts | 52 ++--- .../testing/browser/testing.contribution.ts | 2 +- .../testing/browser/testingOutputPeek.ts | 10 +- .../themes/browser/themes.contribution.ts | 16 +- .../contrib/timeline/browser/timelinePane.ts | 12 +- .../browser/typeHierarchy.contribution.ts | 10 +- .../update/browser/update.contribution.ts | 12 +- .../contrib/url/browser/url.contribution.ts | 4 +- .../browser/userDataProfile.ts | 4 +- .../userDataSync/browser/userDataSync.ts | 14 +- .../browser/gettingStarted.contribution.ts | 10 +- .../common/newFile.contribution.ts | 6 +- .../browser/editor/editorWalkThrough.ts | 4 +- .../browser/workspace.contribution.ts | 4 +- .../actions/developerActions.ts | 10 +- .../actions/installActions.ts | 4 +- .../electron-sandbox/actions/windowActions.ts | 6 +- .../electron-sandbox/desktop.contribution.ts | 14 +- .../browser/extensionBisect.ts | 8 +- .../browser/webExtensionsScannerService.ts | 4 +- .../extensions/browser/extensionUrlHandler.ts | 6 +- .../issue/browser/issueTroubleshoot.ts | 6 +- .../userDataProfile/common/userDataProfile.ts | 4 +- 94 files changed, 601 insertions(+), 601 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts b/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts index 554c842d24d..b6bbd656c3f 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts @@ -11,7 +11,7 @@ import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensi import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -24,7 +24,7 @@ export class ToggleCollapseUnchangedRegions extends Action2 { constructor() { super({ id: 'diffEditor.toggleCollapseUnchangedRegions', - title: { value: localize('toggleCollapseUnchangedRegions', "Toggle Collapse Unchanged Regions"), original: 'Toggle Collapse Unchanged Regions' }, + title: localize2('toggleCollapseUnchangedRegions', 'Toggle Collapse Unchanged Regions'), icon: Codicon.map, toggled: ContextKeyExpr.has('config.diffEditor.hideUnchangedRegions.enabled'), precondition: ContextKeyExpr.has('isInDiffEditor'), @@ -50,7 +50,7 @@ export class ToggleShowMovedCodeBlocks extends Action2 { constructor() { super({ id: 'diffEditor.toggleShowMovedCodeBlocks', - title: { value: localize('toggleShowMovedCodeBlocks', "Toggle Show Moved Code Blocks"), original: 'Toggle Show Moved Code Blocks' }, + title: localize2('toggleShowMovedCodeBlocks', 'Toggle Show Moved Code Blocks'), precondition: ContextKeyExpr.has('isInDiffEditor'), }); } @@ -68,7 +68,7 @@ export class ToggleUseInlineViewWhenSpaceIsLimited extends Action2 { constructor() { super({ id: 'diffEditor.toggleUseInlineViewWhenSpaceIsLimited', - title: { value: localize('toggleUseInlineViewWhenSpaceIsLimited', "Toggle Use Inline View When Space Is Limited"), original: 'Toggle Use Inline View When Space Is Limited' }, + title: localize2('toggleUseInlineViewWhenSpaceIsLimited', 'Toggle Use Inline View When Space Is Limited'), precondition: ContextKeyExpr.has('isInDiffEditor'), }); } @@ -119,7 +119,7 @@ export class SwitchSide extends EditorAction2 { constructor() { super({ id: 'diffEditor.switchSide', - title: { value: localize('switchSide', "Switch Side"), original: 'Switch Side' }, + title: localize2('switchSide', 'Switch Side'), icon: Codicon.arrowSwap, precondition: ContextKeyExpr.has('isInDiffEditor'), f1: true, @@ -146,7 +146,7 @@ export class ExitCompareMove extends EditorAction2 { constructor() { super({ id: 'diffEditor.exitCompareMove', - title: { value: localize('exitCompareMove', "Exit Compare Move"), original: 'Exit Compare Move' }, + title: localize2('exitCompareMove', 'Exit Compare Move'), icon: Codicon.close, precondition: EditorContextKeys.comparingMovedCode, f1: false, @@ -172,7 +172,7 @@ export class CollapseAllUnchangedRegions extends EditorAction2 { constructor() { super({ id: 'diffEditor.collapseAllUnchangedRegions', - title: { value: localize('collapseAllUnchangedRegions', "Collapse All Unchanged Regions"), original: 'Collapse All Unchanged Regions' }, + title: localize2('collapseAllUnchangedRegions', 'Collapse All Unchanged Regions'), icon: Codicon.fold, precondition: ContextKeyExpr.has('isInDiffEditor'), f1: true, @@ -194,7 +194,7 @@ export class ShowAllUnchangedRegions extends EditorAction2 { constructor() { super({ id: 'diffEditor.showAllUnchangedRegions', - title: { value: localize('showAllUnchangedRegions', "Show All Unchanged Regions"), original: 'Show All Unchanged Regions' }, + title: localize2('showAllUnchangedRegions', 'Show All Unchanged Regions'), icon: Codicon.unfold, precondition: ContextKeyExpr.has('isInDiffEditor'), f1: true, @@ -223,7 +223,7 @@ export class AccessibleDiffViewerNext extends Action2 { constructor() { super({ id: AccessibleDiffViewerNext.id, - title: { value: localize('editor.action.accessibleDiffViewer.next', "Go to Next Difference"), original: 'Go to Next Difference' }, + title: localize2('editor.action.accessibleDiffViewer.next', 'Go to Next Difference'), category: accessibleDiffViewerCategory, precondition: ContextKeyExpr.has('isInDiffEditor'), keybinding: { @@ -260,7 +260,7 @@ export class AccessibleDiffViewerPrev extends Action2 { constructor() { super({ id: AccessibleDiffViewerPrev.id, - title: { value: localize('editor.action.accessibleDiffViewer.prev', "Go to Previous Difference"), original: 'Go to Previous Difference' }, + title: localize2('editor.action.accessibleDiffViewer.prev', 'Go to Previous Difference'), category: accessibleDiffViewerCategory, precondition: ContextKeyExpr.has('isInDiffEditor'), keybinding: { diff --git a/src/vs/nls.ts b/src/vs/nls.ts index af0a0b96071..233840e65ab 100644 --- a/src/vs/nls.ts +++ b/src/vs/nls.ts @@ -30,7 +30,7 @@ export interface ILocalizeInfo { comment: string[]; } -interface ILocalizedString { +export interface ILocalizedString { original: string; value: string; } diff --git a/src/vs/platform/action/common/actionCommonCategories.ts b/src/vs/platform/action/common/actionCommonCategories.ts index 40b375dd7fb..943b9c65149 100644 --- a/src/vs/platform/action/common/actionCommonCategories.ts +++ b/src/vs/platform/action/common/actionCommonCategories.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; export const Categories = Object.freeze({ - View: { value: localize('view', "View"), original: 'View' }, - Help: { value: localize('help', "Help"), original: 'Help' }, - Test: { value: localize('test', "Test"), original: 'Test' }, - File: { value: localize('file', "File"), original: 'File' }, - Preferences: { value: localize('preferences', "Preferences"), original: 'Preferences' }, + View: localize2('view', 'View'), + Help: localize2('help', 'Help'), + Test: localize2('test', 'Test'), + File: localize2('file', 'File'), + Preferences: localize2('preferences', 'Preferences'), Developer: { value: localize({ key: 'developer', comment: ['A developer on Code itself or someone diagnosing issues in Code'] }, "Developer"), original: 'Developer' } }); diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index b545fceeed3..be1cfcd113f 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -9,7 +9,7 @@ import { Event } from 'vs/base/common/event'; import { IPager } from 'vs/base/common/paging'; import { Platform } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ExtensionType, IExtension, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -524,4 +524,4 @@ export interface IExtensionTipsService { export const ExtensionsLabel = localize('extensions', "Extensions"); export const ExtensionsLocalizedLabel = { value: ExtensionsLabel, original: 'Extensions' }; -export const PreferencesLocalizedLabel = { value: localize('preferences', "Preferences"), original: 'Preferences' }; +export const PreferencesLocalizedLabel = localize2('preferences', 'Preferences'); diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index 3d3208a6162..7b40f8df1cf 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/actions'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { DomEmitter } from 'vs/base/browser/event'; import { Color } from 'vs/base/common/color'; @@ -46,7 +46,7 @@ class InspectContextKeysAction extends Action2 { constructor() { super({ id: 'workbench.action.inspectContextKeys', - title: { value: localize('inspect context keys', "Inspect Context Keys"), original: 'Inspect Context Keys' }, + title: localize2('inspect context keys', 'Inspect Context Keys'), category: Categories.Developer, f1: true }); @@ -112,7 +112,7 @@ class ToggleScreencastModeAction extends Action2 { constructor() { super({ id: 'workbench.action.toggleScreencastMode', - title: { value: localize('toggle screencast mode', "Toggle Screencast Mode"), original: 'Toggle Screencast Mode' }, + title: localize2('toggle screencast mode', 'Toggle Screencast Mode'), category: Categories.Developer, f1: true }); @@ -457,7 +457,7 @@ class RemoveLargeStorageEntriesAction extends Action2 { constructor() { super({ id: 'workbench.action.removeLargeStorageDatabaseEntries', - title: { value: localize('removeLargeStorageDatabaseEntries', "Remove Large Storage Database Entries..."), original: 'Remove Large Storage Database Entries...' }, + title: localize2('removeLargeStorageDatabaseEntries', 'Remove Large Storage Database Entries...'), category: Categories.Developer, f1: true }); @@ -567,7 +567,7 @@ class StartTrackDisposables extends Action2 { constructor() { super({ id: 'workbench.action.startTrackDisposables', - title: { value: localize('startTrackDisposables', "Start Tracking Disposables"), original: 'Start Tracking Disposables' }, + title: localize2('startTrackDisposables', 'Start Tracking Disposables'), category: Categories.Developer, f1: true, precondition: ContextKeyExpr.and(DisposablesSnapshotStateContext.isEqualTo('pending').negate(), DisposablesSnapshotStateContext.isEqualTo('started').negate()) @@ -590,7 +590,7 @@ class SnapshotTrackedDisposables extends Action2 { constructor() { super({ id: 'workbench.action.snapshotTrackedDisposables', - title: { value: localize('snapshotTrackedDisposables', "Snapshot Tracked Disposables"), original: 'Snapshot Tracked Disposables' }, + title: localize2('snapshotTrackedDisposables', 'Snapshot Tracked Disposables'), category: Categories.Developer, f1: true, precondition: DisposablesSnapshotStateContext.isEqualTo('started') @@ -610,7 +610,7 @@ class StopTrackDisposables extends Action2 { constructor() { super({ id: 'workbench.action.stopTrackDisposables', - title: { value: localize('stopTrackDisposables', "Stop Tracking Disposables"), original: 'Stop Tracking Disposables' }, + title: localize2('stopTrackDisposables', 'Stop Tracking Disposables'), category: Categories.Developer, f1: true, precondition: DisposablesSnapshotStateContext.isEqualTo('pending') diff --git a/src/vs/workbench/browser/actions/helpActions.ts b/src/vs/workbench/browser/actions/helpActions.ts index 7b857877002..cc993982dc8 100644 --- a/src/vs/workbench/browser/actions/helpActions.ts +++ b/src/vs/workbench/browser/actions/helpActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import product from 'vs/platform/product/common/product'; import { isMacintosh, isLinux, language, isWeb } from 'vs/base/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -163,7 +163,7 @@ class OpenNewsletterSignupUrlAction extends Action2 { constructor() { super({ id: OpenNewsletterSignupUrlAction.ID, - title: { value: localize('newsletterSignup', "Signup for the VS Code Newsletter"), original: 'Signup for the VS Code Newsletter' }, + title: localize2('newsletterSignup', 'Signup for the VS Code Newsletter'), category: Categories.Help, f1: true }); diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 4c3eca3262c..4a24aee2acb 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -58,7 +58,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.closeSidebar', - title: { value: localize('closeSidebar', "Close Primary Side Bar"), original: 'Close Primary Side Bar' }, + title: localize2('closeSidebar', 'Close Primary Side Bar'), category: Categories.View, f1: true }); @@ -164,7 +164,7 @@ export class ToggleSidebarPositionAction extends Action2 { constructor() { super({ id: ToggleSidebarPositionAction.ID, - title: { value: localize('toggleSidebarPosition', "Toggle Primary Side Bar Position"), original: 'Toggle Primary Side Bar Position' }, + title: localize2('toggleSidebarPosition', "Toggle Primary Side Bar Position"), category: Categories.View, f1: true }); @@ -322,7 +322,7 @@ class ToggleSidebarVisibilityAction extends Action2 { constructor() { super({ id: ToggleSidebarVisibilityAction.ID, - title: { value: localize('toggleSidebar', "Toggle Primary Side Bar Visibility"), original: 'Toggle Primary Side Bar Visibility' }, + title: localize2('toggleSidebar', 'Toggle Primary Side Bar Visibility'), toggled: { condition: SideBarVisibleContext, title: localize('primary sidebar', "Primary Side Bar"), @@ -478,7 +478,7 @@ export class HideEditorTabsAction extends AbstractSetShowTabsAction { constructor() { const precondition = ContextKeyExpr.and(ContextKeyExpr.equals(`config.${LayoutSettings.EDITOR_TABS_MODE}`, EditorTabsMode.NONE).negate(), InEditorZenModeContext.negate())!; - const title = { value: localize('hideEditorTabs', "Hide Editor Tabs"), original: 'Hide Editor Tabs' }; + const title = localize2('hideEditorTabs', 'Hide Editor Tabs'); super(LayoutSettings.EDITOR_TABS_MODE, EditorTabsMode.NONE, title, HideEditorTabsAction.ID, precondition); } } @@ -489,7 +489,7 @@ export class ZenHideEditorTabsAction extends AbstractSetShowTabsAction { constructor() { const precondition = ContextKeyExpr.and(ContextKeyExpr.equals(`config.${ZenModeSettings.SHOW_TABS}`, EditorTabsMode.NONE).negate(), InEditorZenModeContext)!; - const title = { value: localize('hideEditorTabsZenMode', "Hide Editor Tabs in Zen Mode"), original: 'Hide Editor Tabs in Zen Mode' }; + const title = localize2('hideEditorTabsZenMode', 'Hide Editor Tabs in Zen Mode'); super(ZenModeSettings.SHOW_TABS, EditorTabsMode.NONE, title, ZenHideEditorTabsAction.ID, precondition); } } @@ -502,7 +502,7 @@ export class ShowMultipleEditorTabsAction extends AbstractSetShowTabsAction { constructor() { const precondition = ContextKeyExpr.and(ContextKeyExpr.equals(`config.${LayoutSettings.EDITOR_TABS_MODE}`, EditorTabsMode.MULTIPLE).negate(), InEditorZenModeContext.negate())!; - const title = { value: localize('showMultipleEditorTabs', "Show Multiple Editor Tabs"), original: 'Show Multiple Editor Tabs' }; + const title = localize2('showMultipleEditorTabs', 'Show Multiple Editor Tabs'); super(LayoutSettings.EDITOR_TABS_MODE, EditorTabsMode.MULTIPLE, title, ShowMultipleEditorTabsAction.ID, precondition); } @@ -514,7 +514,7 @@ export class ZenShowMultipleEditorTabsAction extends AbstractSetShowTabsAction { constructor() { const precondition = ContextKeyExpr.and(ContextKeyExpr.equals(`config.${ZenModeSettings.SHOW_TABS}`, EditorTabsMode.MULTIPLE).negate(), InEditorZenModeContext)!; - const title = { value: localize('showMultipleEditorTabsZenMode', "Show Multiple Editor Tabs in Zen Mode"), original: 'Show Multiple Editor Tabs in Zen Mode' }; + const title = localize2('showMultipleEditorTabsZenMode', 'Show Multiple Editor Tabs in Zen Mode'); super(ZenModeSettings.SHOW_TABS, EditorTabsMode.MULTIPLE, title, ZenShowMultipleEditorTabsAction.ID, precondition); } @@ -528,7 +528,7 @@ export class ShowSingleEditorTabAction extends AbstractSetShowTabsAction { constructor() { const precondition = ContextKeyExpr.and(ContextKeyExpr.equals(`config.${LayoutSettings.EDITOR_TABS_MODE}`, EditorTabsMode.SINGLE).negate(), InEditorZenModeContext.negate())!; - const title = { value: localize('showSingleEditorTab', "Show Single Editor Tab"), original: 'Show Single Editor Tab' }; + const title = localize2('showSingleEditorTab', 'Show Single Editor Tab'); super(LayoutSettings.EDITOR_TABS_MODE, EditorTabsMode.SINGLE, title, ShowSingleEditorTabAction.ID, precondition); } @@ -540,7 +540,7 @@ export class ZenShowSingleEditorTabAction extends AbstractSetShowTabsAction { constructor() { const precondition = ContextKeyExpr.and(ContextKeyExpr.equals(`config.${ZenModeSettings.SHOW_TABS}`, EditorTabsMode.SINGLE).negate(), InEditorZenModeContext)!; - const title = { value: localize('showSingleEditorTabZenMode', "Show Single Editor Tab in Zen Mode"), original: 'Show Single Editor Tab in Zen Mode' }; + const title = localize2('showSingleEditorTabZenMode', 'Show Single Editor Tab in Zen Mode'); super(ZenModeSettings.SHOW_TABS, EditorTabsMode.SINGLE, title, ZenShowSingleEditorTabAction.ID, precondition); } @@ -1217,7 +1217,7 @@ class IncreaseViewSizeAction extends BaseResizeViewAction { constructor() { super({ id: 'workbench.action.increaseViewSize', - title: { value: localize('increaseViewSize', "Increase Current View Size"), original: 'Increase Current View Size' }, + title: localize2('increaseViewSize', 'Increase Current View Size'), f1: true, precondition: IsAuxiliaryWindowFocusedContext.toNegated() }); @@ -1233,7 +1233,7 @@ class IncreaseViewWidthAction extends BaseResizeViewAction { constructor() { super({ id: 'workbench.action.increaseViewWidth', - title: { value: localize('increaseEditorWidth', "Increase Editor Width"), original: 'Increase Editor Width' }, + title: localize2('increaseEditorWidth', 'Increase Editor Width'), f1: true, precondition: IsAuxiliaryWindowFocusedContext.toNegated() }); @@ -1249,7 +1249,7 @@ class IncreaseViewHeightAction extends BaseResizeViewAction { constructor() { super({ id: 'workbench.action.increaseViewHeight', - title: { value: localize('increaseEditorHeight', "Increase Editor Height"), original: 'Increase Editor Height' }, + title: localize2('increaseEditorHeight', 'Increase Editor Height'), f1: true, precondition: IsAuxiliaryWindowFocusedContext.toNegated() }); @@ -1265,7 +1265,7 @@ class DecreaseViewSizeAction extends BaseResizeViewAction { constructor() { super({ id: 'workbench.action.decreaseViewSize', - title: { value: localize('decreaseViewSize', "Decrease Current View Size"), original: 'Decrease Current View Size' }, + title: localize2('decreaseViewSize', 'Decrease Current View Size'), f1: true, precondition: IsAuxiliaryWindowFocusedContext.toNegated() }); @@ -1280,7 +1280,7 @@ class DecreaseViewWidthAction extends BaseResizeViewAction { constructor() { super({ id: 'workbench.action.decreaseViewWidth', - title: { value: localize('decreaseEditorWidth', "Decrease Editor Width"), original: 'Decrease Editor Width' }, + title: localize2('decreaseEditorWidth', 'Decrease Editor Width'), f1: true, precondition: IsAuxiliaryWindowFocusedContext.toNegated() }); @@ -1296,7 +1296,7 @@ class DecreaseViewHeightAction extends BaseResizeViewAction { constructor() { super({ id: 'workbench.action.decreaseViewHeight', - title: { value: localize('decreaseEditorHeight', "Decrease Editor Height"), original: 'Decrease Editor Height' }, + title: localize2('decreaseEditorHeight', 'Decrease Editor Height'), f1: true, precondition: IsAuxiliaryWindowFocusedContext.toNegated() }); diff --git a/src/vs/workbench/browser/actions/navigationActions.ts b/src/vs/workbench/browser/actions/navigationActions.ts index 0dfba758d5a..e0e838c85bf 100644 --- a/src/vs/workbench/browser/actions/navigationActions.ts +++ b/src/vs/workbench/browser/actions/navigationActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { IEditorGroupsService, GroupDirection, GroupLocation, IFindGroupScope } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { Action2, IAction2Options, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -195,7 +195,7 @@ registerAction2(class extends BaseNavigationAction { constructor() { super({ id: 'workbench.action.navigateLeft', - title: { value: localize('navigateLeft', "Navigate to the View on the Left"), original: 'Navigate to the View on the Left' }, + title: localize2('navigateLeft', 'Navigate to the View on the Left'), category: Categories.View, f1: true }, Direction.Left); @@ -207,7 +207,7 @@ registerAction2(class extends BaseNavigationAction { constructor() { super({ id: 'workbench.action.navigateRight', - title: { value: localize('navigateRight', "Navigate to the View on the Right"), original: 'Navigate to the View on the Right' }, + title: localize2('navigateRight', 'Navigate to the View on the Right'), category: Categories.View, f1: true }, Direction.Right); @@ -219,7 +219,7 @@ registerAction2(class extends BaseNavigationAction { constructor() { super({ id: 'workbench.action.navigateUp', - title: { value: localize('navigateUp', "Navigate to the View Above"), original: 'Navigate to the View Above' }, + title: localize2('navigateUp', 'Navigate to the View Above'), category: Categories.View, f1: true }, Direction.Up); @@ -231,7 +231,7 @@ registerAction2(class extends BaseNavigationAction { constructor() { super({ id: 'workbench.action.navigateDown', - title: { value: localize('navigateDown', "Navigate to the View Below"), original: 'Navigate to the View Below' }, + title: localize2('navigateDown', 'Navigate to the View Below'), category: Categories.View, f1: true }, Direction.Down); @@ -319,7 +319,7 @@ registerAction2(class extends BaseFocusAction { constructor() { super({ id: 'workbench.action.focusNextPart', - title: { value: localize('focusNextPart', "Focus Next Part"), original: 'Focus Next Part' }, + title: localize2('focusNextPart', 'Focus Next Part'), category: Categories.View, f1: true, keybinding: { @@ -335,7 +335,7 @@ registerAction2(class extends BaseFocusAction { constructor() { super({ id: 'workbench.action.focusPreviousPart', - title: { value: localize('focusPreviousPart', "Focus Previous Part"), original: 'Focus Previous Part' }, + title: localize2('focusPreviousPart', 'Focus Previous Part'), category: Categories.View, f1: true, keybinding: { diff --git a/src/vs/workbench/browser/actions/quickAccessActions.ts b/src/vs/workbench/browser/actions/quickAccessActions.ts index c562935d24f..ce41b3e787f 100644 --- a/src/vs/workbench/browser/actions/quickAccessActions.ts +++ b/src/vs/workbench/browser/actions/quickAccessActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { MenuId, Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { KeybindingsRegistry, KeybindingWeight, IKeybindingRule } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -212,14 +212,14 @@ class BaseQuickAccessNavigateAction extends Action2 { class QuickAccessNavigateNextAction extends BaseQuickAccessNavigateAction { constructor() { - super('workbench.action.quickOpenNavigateNext', { value: localize('quickNavigateNext', "Navigate Next in Quick Open"), original: 'Navigate Next in Quick Open' }, true, true); + super('workbench.action.quickOpenNavigateNext', localize2('quickNavigateNext', 'Navigate Next in Quick Open'), true, true); } } class QuickAccessNavigatePreviousAction extends BaseQuickAccessNavigateAction { constructor() { - super('workbench.action.quickOpenNavigatePrevious', { value: localize('quickNavigatePrevious', "Navigate Previous in Quick Open"), original: 'Navigate Previous in Quick Open' }, false, true); + super('workbench.action.quickOpenNavigatePrevious', localize2('quickNavigatePrevious', 'Navigate Previous in Quick Open'), false, true); } } @@ -228,7 +228,7 @@ class QuickAccessSelectNextAction extends BaseQuickAccessNavigateAction { constructor() { super( 'workbench.action.quickOpenSelectNext', - { value: localize('quickSelectNext', "Select Next in Quick Open"), original: 'Select Next in Quick Open' }, + localize2('quickSelectNext', 'Select Next in Quick Open'), true, false, { @@ -246,7 +246,7 @@ class QuickAccessSelectPreviousAction extends BaseQuickAccessNavigateAction { constructor() { super( 'workbench.action.quickOpenSelectPrevious', - { value: localize('quickSelectPrevious', "Select Previous in Quick Open"), original: 'Select Previous in Quick Open' }, + localize2('quickSelectPrevious', 'Select Previous in Quick Open'), false, false, { diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index bf3fc0d3b1b..1fe675afa5e 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IWindowOpenable } from 'vs/platform/window/common/window'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { MenuRegistry, MenuId, Action2, registerAction2, IAction2Options } from 'vs/platform/actions/common/actions'; @@ -266,7 +266,7 @@ class QuickPickRecentAction extends BaseOpenRecentAction { constructor() { super({ id: 'workbench.action.quickOpenRecent', - title: { value: localize('quickOpenRecent', "Quick Open Recent..."), original: 'Quick Open Recent...' }, + title: localize2('quickOpenRecent', 'Quick Open Recent...'), category: Categories.File, f1: false // hide quick pickers from command palette to not confuse with the other entry that shows a input field }); @@ -320,7 +320,7 @@ export class ReloadWindowAction extends Action2 { constructor() { super({ id: ReloadWindowAction.ID, - title: { value: localize('reloadWindow', "Reload Window"), original: 'Reload Window' }, + title: localize2('reloadWindow', 'Reload Window'), category: Categories.Developer, f1: true, keybinding: { @@ -402,7 +402,7 @@ class BlurAction extends Action2 { constructor() { super({ id: 'workbench.action.blur', - title: { value: localize('blur', "Remove keyboard focus from focused element"), original: 'Remove keyboard focus from focused element' } + title: localize2('blur', 'Remove keyboard focus from focused element') }); } diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 3f2e2799fa5..d3bb8546062 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder, hasWorkspaceFileExtension } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; @@ -24,7 +24,7 @@ import { IsMacNativeContext } from 'vs/platform/contextkey/common/contextkeys'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -const workspacesCategory: ILocalizedString = { value: localize('workspaces', "Workspaces"), original: 'Workspaces' }; +const workspacesCategory: ILocalizedString = localize2('workspaces', 'Workspaces'); export class OpenFileAction extends Action2 { @@ -33,7 +33,7 @@ export class OpenFileAction extends Action2 { constructor() { super({ id: OpenFileAction.ID, - title: { value: localize('openFile', "Open File..."), original: 'Open File...' }, + title: localize2('openFile', 'Open File...'), category: Categories.File, f1: true, keybinding: { @@ -58,7 +58,7 @@ export class OpenFolderAction extends Action2 { constructor() { super({ id: OpenFolderAction.ID, - title: { value: localize('openFolder', "Open Folder..."), original: 'Open Folder...' }, + title: localize2('openFolder', 'Open Folder...'), category: Categories.File, f1: true, precondition: OpenFolderWorkspaceSupportContext, @@ -94,7 +94,7 @@ export class OpenFolderViaWorkspaceAction extends Action2 { constructor() { super({ id: OpenFolderViaWorkspaceAction.ID, - title: { value: localize('openFolder', "Open Folder..."), original: 'Open Folder...' }, + title: localize2('openFolder', 'Open Folder...'), category: Categories.File, f1: true, precondition: ContextKeyExpr.and(OpenFolderWorkspaceSupportContext.toNegated(), WorkbenchStateContext.isEqualTo('workspace')), @@ -115,7 +115,7 @@ export class OpenFolderViaWorkspaceAction extends Action2 { export class OpenFileFolderAction extends Action2 { static readonly ID = 'workbench.action.files.openFileFolder'; - static readonly LABEL: ILocalizedString = { value: localize('openFileFolder', "Open..."), original: 'Open...' }; + static readonly LABEL: ILocalizedString = localize2('openFileFolder', 'Open...'); constructor() { super({ @@ -145,7 +145,7 @@ class OpenWorkspaceAction extends Action2 { constructor() { super({ id: OpenWorkspaceAction.ID, - title: { value: localize('openWorkspaceAction', "Open Workspace from File..."), original: 'Open Workspace from File...' }, + title: localize2('openWorkspaceAction', 'Open Workspace from File...'), category: Categories.File, f1: true, precondition: EnterMultiRootWorkspaceSupportContext @@ -166,7 +166,7 @@ class CloseWorkspaceAction extends Action2 { constructor() { super({ id: CloseWorkspaceAction.ID, - title: { value: localize('closeWorkspace', "Close Workspace"), original: 'Close Workspace' }, + title: localize2('closeWorkspace', 'Close Workspace'), category: workspacesCategory, f1: true, precondition: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('empty'), EmptyWorkspaceSupportContext), @@ -192,7 +192,7 @@ class OpenWorkspaceConfigFileAction extends Action2 { constructor() { super({ id: OpenWorkspaceConfigFileAction.ID, - title: { value: localize('openWorkspaceConfigFile', "Open Workspace Configuration File"), original: 'Open Workspace Configuration File' }, + title: localize2('openWorkspaceConfigFile', 'Open Workspace Configuration File'), category: workspacesCategory, f1: true, precondition: WorkbenchStateContext.isEqualTo('workspace') @@ -238,7 +238,7 @@ export class RemoveRootFolderAction extends Action2 { constructor() { super({ id: RemoveRootFolderAction.ID, - title: { value: localize('globalRemoveFolderFromWorkspace', "Remove Folder from Workspace..."), original: 'Remove Folder from Workspace...' }, + title: localize2('globalRemoveFolderFromWorkspace', 'Remove Folder from Workspace...'), category: workspacesCategory, f1: true, precondition: ContextKeyExpr.and(WorkspaceFolderCountContext.notEqualsTo('0'), ContextKeyExpr.or(EnterMultiRootWorkspaceSupportContext, WorkbenchStateContext.isEqualTo('workspace'))) @@ -263,7 +263,7 @@ class SaveWorkspaceAsAction extends Action2 { constructor() { super({ id: SaveWorkspaceAsAction.ID, - title: { value: localize('saveWorkspaceAsAction', "Save Workspace As..."), original: 'Save Workspace As...' }, + title: localize2('saveWorkspaceAsAction', 'Save Workspace As...'), category: workspacesCategory, f1: true, precondition: EnterMultiRootWorkspaceSupportContext @@ -296,7 +296,7 @@ class DuplicateWorkspaceInNewWindowAction extends Action2 { constructor() { super({ id: DuplicateWorkspaceInNewWindowAction.ID, - title: { value: localize('duplicateWorkspaceInNewWindow', "Duplicate As Workspace in New Window"), original: 'Duplicate As Workspace in New Window' }, + title: localize2('duplicateWorkspaceInNewWindow', 'Duplicate As Workspace in New Window'), category: workspacesCategory, f1: true, precondition: EnterMultiRootWorkspaceSupportContext diff --git a/src/vs/workbench/browser/actions/workspaceCommands.ts b/src/vs/workbench/browser/actions/workspaceCommands.ts index ad3124c4276..2ce4f6003b9 100644 --- a/src/vs/workbench/browser/actions/workspaceCommands.ts +++ b/src/vs/workbench/browser/actions/workspaceCommands.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { hasWorkspaceFileExtension, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { dirname } from 'vs/base/common/resources'; @@ -26,7 +26,7 @@ import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { ILocalizedString } from 'vs/platform/action/common/action'; export const ADD_ROOT_FOLDER_COMMAND_ID = 'addRootFolder'; -export const ADD_ROOT_FOLDER_LABEL: ILocalizedString = { value: localize('addFolderToWorkspace', "Add Folder to Workspace..."), original: 'Add Folder to Workspace...' }; +export const ADD_ROOT_FOLDER_LABEL: ILocalizedString = localize2('addFolderToWorkspace', 'Add Folder to Workspace...'); export const SET_ROOT_FOLDER_COMMAND_ID = 'setRootFolder'; diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index b4b247137f4..f3c9c66960a 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/activitybarpart'; import 'vs/css!./media/activityaction'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { Part } from 'vs/workbench/browser/part'; import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; @@ -516,7 +516,7 @@ registerAction2( constructor() { super({ id: 'workbench.action.previousSideBarView', - title: { value: localize('previousSideBarView', "Previous Primary Side Bar View"), original: 'Previous Primary Side Bar View' }, + title: localize2('previousSideBarView', 'Previous Primary Side Bar View'), category: Categories.View, f1: true }, -1); @@ -529,7 +529,7 @@ registerAction2( constructor() { super({ id: 'workbench.action.nextSideBarView', - title: { value: localize('nextSideBarView', "Next Primary Side Bar View"), original: 'Next Primary Side Bar View' }, + title: localize2('nextSideBarView', 'Next Primary Side Bar View'), category: Categories.View, f1: true }, 1); @@ -542,7 +542,7 @@ registerAction2( constructor() { super({ id: 'workbench.action.focusActivityBar', - title: { value: localize('focusActivityBar', "Focus Activity Bar"), original: 'Focus Activity Bar' }, + title: localize2('focusActivityBar', 'Focus Activity Bar'), category: Categories.View, f1: true }); diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts index 17600e72b25..ff3f99716c8 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Codicon } from 'vs/base/common/codicons'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; @@ -130,7 +130,7 @@ MenuRegistry.appendMenuItems([ group: '3_workbench_layout_move', command: { id: ToggleAuxiliaryBarAction.ID, - title: { value: localize('hideAuxiliaryBar', "Hide Secondary Side Bar"), original: 'Hide Secondary Side Bar' }, + title: localize2('hideAuxiliaryBar', 'Hide Secondary Side Bar'), }, when: ContextKeyExpr.and(AuxiliaryBarVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))), order: 2 diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index fe3238769b3..7081081e504 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Registry } from 'vs/platform/registry/common/platform'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IEditorPaneRegistry, EditorPaneDescriptor } from 'vs/workbench/browser/editor'; import { IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { @@ -627,17 +627,17 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { }); // Editor Commands for Command Palette -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: KEEP_EDITOR_COMMAND_ID, title: { value: localize('keepEditor', "Keep Editor"), original: 'Keep Editor' }, category: Categories.View }, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: PIN_EDITOR_COMMAND_ID, title: { value: localize('pinEditor', "Pin Editor"), original: 'Pin Editor' }, category: Categories.View } }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: UNPIN_EDITOR_COMMAND_ID, title: { value: localize('unpinEditor', "Unpin Editor"), original: 'Unpin Editor' }, category: Categories.View } }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_EDITOR_COMMAND_ID, title: { value: localize('closeEditor', "Close Editor"), original: 'Close Editor' }, category: Categories.View } }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_PINNED_EDITOR_COMMAND_ID, title: { value: localize('closePinnedEditor', "Close Pinned Editor"), original: 'Close Pinned Editor' }, category: Categories.View } }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: { value: localize('closeEditorsInGroup', "Close All Editors in Group"), original: 'Close All Editors in Group' }, category: Categories.View } }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_SAVED_EDITORS_COMMAND_ID, title: { value: localize('closeSavedEditors', "Close Saved Editors in Group"), original: 'Close Saved Editors in Group' }, category: Categories.View } }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: { value: localize('closeOtherEditors', "Close Other Editors in Group"), original: 'Close Other Editors in Group' }, category: Categories.View } }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: { value: localize('closeRightEditors', "Close Editors to the Right in Group"), original: 'Close Editors to the Right in Group' }, category: Categories.View }, when: ActiveEditorLastInGroupContext.toNegated() }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_EDITORS_AND_GROUP_COMMAND_ID, title: { value: localize('closeEditorGroup', "Close Editor Group"), original: 'Close Editor Group' }, category: Categories.View }, when: MultipleEditorGroupsContext }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: REOPEN_WITH_COMMAND_ID, title: { value: localize('reopenWith', "Reopen Editor With..."), original: 'Reopen Editor With...' }, category: Categories.View }, when: ActiveEditorAvailableEditorIdsContext }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: KEEP_EDITOR_COMMAND_ID, title: localize2('keepEditor', 'Keep Editor'), category: Categories.View }, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: PIN_EDITOR_COMMAND_ID, title: localize2('pinEditor', 'Pin Editor'), category: Categories.View } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: UNPIN_EDITOR_COMMAND_ID, title: localize2('unpinEditor', 'Unpin Editor'), category: Categories.View } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_EDITOR_COMMAND_ID, title: localize2('closeEditor', 'Close Editor'), category: Categories.View } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_PINNED_EDITOR_COMMAND_ID, title: localize2('closePinnedEditor', 'Close Pinned Editor'), category: Categories.View } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: localize2('closeEditorsInGroup', 'Close All Editors in Group'), category: Categories.View } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_SAVED_EDITORS_COMMAND_ID, title: localize2('closeSavedEditors', 'Close Saved Editors in Group'), category: Categories.View } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: localize2('closeOtherEditors', 'Close Other Editors in Group'), category: Categories.View } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: localize2('closeRightEditors', 'Close Editors to the Right in Group'), category: Categories.View }, when: ActiveEditorLastInGroupContext.toNegated() }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_EDITORS_AND_GROUP_COMMAND_ID, title: localize2('closeEditorGroup', 'Close Editor Group'), category: Categories.View }, when: MultipleEditorGroupsContext }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: REOPEN_WITH_COMMAND_ID, title: localize2('reopenWith', "Reopen Editor With..."), category: Categories.View }, when: ActiveEditorAvailableEditorIdsContext }); // File menu MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 74f3f40069c..ca749d9e59b 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { firstOrDefault } from 'vs/base/common/arrays'; import { IEditorIdentifier, IEditorCommandsContext, CloseDirection, SaveReason, EditorsOrder, EditorInputCapabilities, DEFAULT_EDITOR_ASSOCIATION, GroupIdentifier, EditorResourceAccessor } from 'vs/workbench/common/editor'; @@ -76,7 +76,7 @@ export class SplitEditorAction extends AbstractSplitEditorAction { constructor() { super({ id: SplitEditorAction.ID, - title: { value: localize('splitEditor', "Split Editor"), original: 'Split Editor' }, + title: localize2('splitEditor', 'Split Editor'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -92,7 +92,7 @@ export class SplitEditorOrthogonalAction extends AbstractSplitEditorAction { constructor() { super({ id: 'workbench.action.splitEditorOrthogonal', - title: { value: localize('splitEditorOrthogonal', "Split Editor Orthogonal"), original: 'Split Editor Orthogonal' }, + title: localize2('splitEditorOrthogonal', 'Split Editor Orthogonal'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -114,7 +114,7 @@ export class SplitEditorLeftAction extends ExecuteCommandAction { constructor() { super({ id: SPLIT_EDITOR_LEFT, - title: { value: localize('splitEditorGroupLeft', "Split Editor Left"), original: 'Split Editor Left' }, + title: localize2('splitEditorGroupLeft', 'Split Editor Left'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -130,7 +130,7 @@ export class SplitEditorRightAction extends ExecuteCommandAction { constructor() { super({ id: SPLIT_EDITOR_RIGHT, - title: { value: localize('splitEditorGroupRight', "Split Editor Right"), original: 'Split Editor Right' }, + title: localize2('splitEditorGroupRight', 'Split Editor Right'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -148,7 +148,7 @@ export class SplitEditorUpAction extends ExecuteCommandAction { constructor() { super({ id: SPLIT_EDITOR_UP, - title: { value: localize('splitEditorGroupUp', "Split Editor Up"), original: 'Split Editor Up' }, + title: localize2('splitEditorGroupUp', "Split Editor Up"), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -166,7 +166,7 @@ export class SplitEditorDownAction extends ExecuteCommandAction { constructor() { super({ id: SPLIT_EDITOR_DOWN, - title: { value: localize('splitEditorGroupDown', "Split Editor Down"), original: 'Split Editor Down' }, + title: localize2('splitEditorGroupDown', "Split Editor Down"), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -182,7 +182,7 @@ export class JoinTwoGroupsAction extends Action2 { constructor() { super({ id: 'workbench.action.joinTwoGroups', - title: { value: localize('joinTwoGroups', "Join Editor Group with Next Group"), original: 'Join Editor Group with Next Group' }, + title: localize2('joinTwoGroups', 'Join Editor Group with Next Group'), f1: true, category: Categories.View }); @@ -217,7 +217,7 @@ export class JoinAllGroupsAction extends Action2 { constructor() { super({ id: 'workbench.action.joinAllGroups', - title: { value: localize('joinAllGroups', "Join All Editor Groups"), original: 'Join All Editor Groups' }, + title: localize2('joinAllGroups', 'Join All Editor Groups'), f1: true, category: Categories.View }); @@ -235,7 +235,7 @@ export class NavigateBetweenGroupsAction extends Action2 { constructor() { super({ id: 'workbench.action.navigateEditorGroups', - title: { value: localize('navigateEditorGroups', "Navigate Between Editor Groups"), original: 'Navigate Between Editor Groups' }, + title: localize2('navigateEditorGroups', 'Navigate Between Editor Groups'), f1: true, category: Categories.View }); @@ -254,7 +254,7 @@ export class FocusActiveGroupAction extends Action2 { constructor() { super({ id: 'workbench.action.focusActiveEditorGroup', - title: { value: localize('focusActiveEditorGroup', "Focus Active Editor Group"), original: 'Focus Active Editor Group' }, + title: localize2('focusActiveEditorGroup', 'Focus Active Editor Group'), f1: true, category: Categories.View }); @@ -289,7 +289,7 @@ export class FocusFirstGroupAction extends AbstractFocusGroupAction { constructor() { super({ id: 'workbench.action.focusFirstEditorGroup', - title: { value: localize('focusFirstEditorGroup', "Focus First Editor Group"), original: 'Focus First Editor Group' }, + title: localize2('focusFirstEditorGroup', 'Focus First Editor Group'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -305,7 +305,7 @@ export class FocusLastGroupAction extends AbstractFocusGroupAction { constructor() { super({ id: 'workbench.action.focusLastEditorGroup', - title: { value: localize('focusLastEditorGroup', "Focus Last Editor Group"), original: 'Focus Last Editor Group' }, + title: localize2('focusLastEditorGroup', 'Focus Last Editor Group'), f1: true, category: Categories.View }, { location: GroupLocation.LAST }); @@ -317,7 +317,7 @@ export class FocusNextGroup extends AbstractFocusGroupAction { constructor() { super({ id: 'workbench.action.focusNextGroup', - title: { value: localize('focusNextGroup', "Focus Next Editor Group"), original: 'Focus Next Editor Group' }, + title: localize2('focusNextGroup', 'Focus Next Editor Group'), f1: true, category: Categories.View }, { location: GroupLocation.NEXT }); @@ -329,7 +329,7 @@ export class FocusPreviousGroup extends AbstractFocusGroupAction { constructor() { super({ id: 'workbench.action.focusPreviousGroup', - title: { value: localize('focusPreviousGroup', "Focus Previous Editor Group"), original: 'Focus Previous Editor Group' }, + title: localize2('focusPreviousGroup', 'Focus Previous Editor Group'), f1: true, category: Categories.View }, { location: GroupLocation.PREVIOUS }); @@ -341,7 +341,7 @@ export class FocusLeftGroup extends AbstractFocusGroupAction { constructor() { super({ id: 'workbench.action.focusLeftGroup', - title: { value: localize('focusLeftGroup', "Focus Left Editor Group"), original: 'Focus Left Editor Group' }, + title: localize2('focusLeftGroup', 'Focus Left Editor Group'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -357,7 +357,7 @@ export class FocusRightGroup extends AbstractFocusGroupAction { constructor() { super({ id: 'workbench.action.focusRightGroup', - title: { value: localize('focusRightGroup', "Focus Right Editor Group"), original: 'Focus Right Editor Group' }, + title: localize2('focusRightGroup', 'Focus Right Editor Group'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -373,7 +373,7 @@ export class FocusAboveGroup extends AbstractFocusGroupAction { constructor() { super({ id: 'workbench.action.focusAboveGroup', - title: { value: localize('focusAboveGroup', "Focus Editor Group Above"), original: 'Focus Editor Group Above' }, + title: localize2('focusAboveGroup', 'Focus Editor Group Above'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -389,7 +389,7 @@ export class FocusBelowGroup extends AbstractFocusGroupAction { constructor() { super({ id: 'workbench.action.focusBelowGroup', - title: { value: localize('focusBelowGroup', "Focus Editor Group Below"), original: 'Focus Editor Group Below' }, + title: localize2('focusBelowGroup', 'Focus Editor Group Below'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -486,7 +486,7 @@ export class RevertAndCloseEditorAction extends Action2 { constructor() { super({ id: 'workbench.action.revertAndCloseActiveEditor', - title: { value: localize('revertAndCloseActiveEditor', "Revert and Close Editor"), original: 'Revert and Close Editor' }, + title: localize2('revertAndCloseActiveEditor', 'Revert and Close Editor'), f1: true, category: Categories.View }); @@ -525,7 +525,7 @@ export class CloseLeftEditorsInGroupAction extends Action2 { constructor() { super({ id: 'workbench.action.closeEditorsToTheLeft', - title: { value: localize('closeEditorsToTheLeft', "Close Editors to the Left in Group"), original: 'Close Editors to the Left in Group' }, + title: localize2('closeEditorsToTheLeft', 'Close Editors to the Left in Group'), f1: true, category: Categories.View }); @@ -718,7 +718,7 @@ abstract class AbstractCloseAllAction extends Action2 { export class CloseAllEditorsAction extends AbstractCloseAllAction { static readonly ID = 'workbench.action.closeAllEditors'; - static readonly LABEL = { value: localize('closeAllEditors', "Close All Editors"), original: 'Close All Editors' }; + static readonly LABEL = localize2('closeAllEditors', 'Close All Editors'); constructor() { super({ @@ -744,7 +744,7 @@ export class CloseAllEditorGroupsAction extends AbstractCloseAllAction { constructor() { super({ id: 'workbench.action.closeAllGroups', - title: { value: localize('closeAllGroups', "Close All Editor Groups"), original: 'Close All Editor Groups' }, + title: localize2('closeAllGroups', 'Close All Editor Groups'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -772,7 +772,7 @@ export class CloseEditorsInOtherGroupsAction extends Action2 { constructor() { super({ id: 'workbench.action.closeEditorsInOtherGroups', - title: { value: localize('closeEditorsInOtherGroups', "Close Editors in Other Groups"), original: 'Close Editors in Other Groups' }, + title: localize2('closeEditorsInOtherGroups', 'Close Editors in Other Groups'), f1: true, category: Categories.View }); @@ -797,7 +797,7 @@ export class CloseEditorInAllGroupsAction extends Action2 { constructor() { super({ id: 'workbench.action.closeEditorInAllGroups', - title: { value: localize('closeEditorInAllGroups', "Close Editor in All Groups"), original: 'Close Editor in All Groups' }, + title: localize2('closeEditorInAllGroups', 'Close Editor in All Groups'), f1: true, category: Categories.View }); @@ -894,7 +894,7 @@ export class MoveGroupLeftAction extends AbstractMoveGroupAction { constructor() { super({ id: 'workbench.action.moveActiveEditorGroupLeft', - title: { value: localize('moveActiveGroupLeft', "Move Editor Group Left"), original: 'Move Editor Group Left' }, + title: localize2('moveActiveGroupLeft', 'Move Editor Group Left'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -910,7 +910,7 @@ export class MoveGroupRightAction extends AbstractMoveGroupAction { constructor() { super({ id: 'workbench.action.moveActiveEditorGroupRight', - title: { value: localize('moveActiveGroupRight', "Move Editor Group Right"), original: 'Move Editor Group Right' }, + title: localize2('moveActiveGroupRight', 'Move Editor Group Right'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -926,7 +926,7 @@ export class MoveGroupUpAction extends AbstractMoveGroupAction { constructor() { super({ id: 'workbench.action.moveActiveEditorGroupUp', - title: { value: localize('moveActiveGroupUp', "Move Editor Group Up"), original: 'Move Editor Group Up' }, + title: localize2('moveActiveGroupUp', 'Move Editor Group Up'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -942,7 +942,7 @@ export class MoveGroupDownAction extends AbstractMoveGroupAction { constructor() { super({ id: 'workbench.action.moveActiveEditorGroupDown', - title: { value: localize('moveActiveGroupDown', "Move Editor Group Down"), original: 'Move Editor Group Down' }, + title: localize2('moveActiveGroupDown', 'Move Editor Group Down'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -968,7 +968,7 @@ export class DuplicateGroupLeftAction extends AbstractDuplicateGroupAction { constructor() { super({ id: 'workbench.action.duplicateActiveEditorGroupLeft', - title: { value: localize('duplicateActiveGroupLeft', "Duplicate Editor Group Left"), original: 'Duplicate Editor Group Left' }, + title: localize2('duplicateActiveGroupLeft', 'Duplicate Editor Group Left'), f1: true, category: Categories.View }, GroupDirection.LEFT); @@ -980,7 +980,7 @@ export class DuplicateGroupRightAction extends AbstractDuplicateGroupAction { constructor() { super({ id: 'workbench.action.duplicateActiveEditorGroupRight', - title: { value: localize('duplicateActiveGroupRight', "Duplicate Editor Group Right"), original: 'Duplicate Editor Group Right' }, + title: localize2('duplicateActiveGroupRight', 'Duplicate Editor Group Right'), f1: true, category: Categories.View }, GroupDirection.RIGHT); @@ -992,7 +992,7 @@ export class DuplicateGroupUpAction extends AbstractDuplicateGroupAction { constructor() { super({ id: 'workbench.action.duplicateActiveEditorGroupUp', - title: { value: localize('duplicateActiveGroupUp', "Duplicate Editor Group Up"), original: 'Duplicate Editor Group Up' }, + title: localize2('duplicateActiveGroupUp', 'Duplicate Editor Group Up'), f1: true, category: Categories.View }, GroupDirection.UP); @@ -1004,7 +1004,7 @@ export class DuplicateGroupDownAction extends AbstractDuplicateGroupAction { constructor() { super({ id: 'workbench.action.duplicateActiveEditorGroupDown', - title: { value: localize('duplicateActiveGroupDown', "Duplicate Editor Group Down"), original: 'Duplicate Editor Group Down' }, + title: localize2('duplicateActiveGroupDown', 'Duplicate Editor Group Down'), f1: true, category: Categories.View }, GroupDirection.DOWN); @@ -1016,7 +1016,7 @@ export class MinimizeOtherGroupsAction extends Action2 { constructor() { super({ id: 'workbench.action.minimizeOtherEditors', - title: { value: localize('minimizeOtherEditorGroups', "Expand Editor Group"), original: 'Expand Editor Group' }, + title: localize2('minimizeOtherEditorGroups', 'Expand Editor Group'), f1: true, category: Categories.View, precondition: MultipleEditorGroupsContext @@ -1035,7 +1035,7 @@ export class MinimizeOtherGroupsHideSidebarAction extends Action2 { constructor() { super({ id: 'workbench.action.minimizeOtherEditorsHideSidebar', - title: { value: localize('minimizeOtherEditorGroupsHideSidebar', "Expand Editor Group and Hide Side Bars"), original: 'Expand Editor Group and Hide Side Bars' }, + title: localize2('minimizeOtherEditorGroupsHideSidebar', 'Expand Editor Group and Hide Side Bars'), f1: true, category: Categories.View, precondition: ContextKeyExpr.or(MultipleEditorGroupsContext, SideBarVisibleContext, AuxiliaryBarVisibleContext) @@ -1057,7 +1057,7 @@ export class ResetGroupSizesAction extends Action2 { constructor() { super({ id: 'workbench.action.evenEditorWidths', - title: { value: localize('evenEditorGroups', "Reset Editor Group Sizes"), original: 'Reset Editor Group Sizes' }, + title: localize2('evenEditorGroups', 'Reset Editor Group Sizes'), f1: true, category: Categories.View }); @@ -1075,7 +1075,7 @@ export class ToggleGroupSizesAction extends Action2 { constructor() { super({ id: 'workbench.action.toggleEditorWidths', - title: { value: localize('toggleEditorWidths', "Toggle Editor Group Sizes"), original: 'Toggle Editor Group Sizes' }, + title: localize2('toggleEditorWidths', 'Toggle Editor Group Sizes'), f1: true, category: Categories.View }); @@ -1093,7 +1093,7 @@ export class MaximizeGroupHideSidebarAction extends Action2 { constructor() { super({ id: 'workbench.action.maximizeEditorHideSidebar', - title: { value: localize('maximizeEditorHideSidebar', "Maximize Editor Group and Hide Side Bars"), original: 'Maximize Editor Group and Hide Side Bars' }, + title: localize2('maximizeEditorHideSidebar', 'Maximize Editor Group and Hide Side Bars'), f1: true, category: Categories.View, precondition: ContextKeyExpr.or(ContextKeyExpr.and(EditorPartMaximizedEditorGroupContext.negate(), EditorPartMultipleEditorGroupsContext), SideBarVisibleContext, AuxiliaryBarVisibleContext) @@ -1118,7 +1118,7 @@ export class ToggleMaximizeEditorGroupAction extends Action2 { constructor() { super({ id: TOGGLE_MAXIMIZE_EDITOR_GROUP, - title: { value: localize('toggleMaximizeEditorGroup', "Toggle Maximize Editor Group"), original: 'Toggle Maximize Editor Group' }, + title: localize2('toggleMaximizeEditorGroup', 'Toggle Maximize Editor Group'), f1: true, category: Categories.View, precondition: ContextKeyExpr.or(EditorPartMultipleEditorGroupsContext, EditorPartMaximizedEditorGroupContext), @@ -1180,7 +1180,7 @@ export class OpenNextEditor extends AbstractNavigateEditorAction { constructor() { super({ id: 'workbench.action.nextEditor', - title: { value: localize('openNextEditor', "Open Next Editor"), original: 'Open Next Editor' }, + title: localize2('openNextEditor', 'Open Next Editor'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1228,7 +1228,7 @@ export class OpenPreviousEditor extends AbstractNavigateEditorAction { constructor() { super({ id: 'workbench.action.previousEditor', - title: { value: localize('openPreviousEditor', "Open Previous Editor"), original: 'Open Previous Editor' }, + title: localize2('openPreviousEditor', 'Open Previous Editor'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1276,7 +1276,7 @@ export class OpenNextEditorInGroup extends AbstractNavigateEditorAction { constructor() { super({ id: 'workbench.action.nextEditorInGroup', - title: { value: localize('nextEditorInGroup', "Open Next Editor in Group"), original: 'Open Next Editor in Group' }, + title: localize2('nextEditorInGroup', 'Open Next Editor in Group'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1303,7 +1303,7 @@ export class OpenPreviousEditorInGroup extends AbstractNavigateEditorAction { constructor() { super({ id: 'workbench.action.previousEditorInGroup', - title: { value: localize('openPreviousEditorInGroup', "Open Previous Editor in Group"), original: 'Open Previous Editor in Group' }, + title: localize2('openPreviousEditorInGroup', 'Open Previous Editor in Group'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1330,7 +1330,7 @@ export class OpenFirstEditorInGroup extends AbstractNavigateEditorAction { constructor() { super({ id: 'workbench.action.firstEditorInGroup', - title: { value: localize('firstEditorInGroup', "Open First Editor in Group"), original: 'Open First Editor in Group' }, + title: localize2('firstEditorInGroup', 'Open First Editor in Group'), f1: true, category: Categories.View }); @@ -1349,7 +1349,7 @@ export class OpenLastEditorInGroup extends AbstractNavigateEditorAction { constructor() { super({ id: 'workbench.action.lastEditorInGroup', - title: { value: localize('lastEditorInGroup', "Open Last Editor in Group"), original: 'Open Last Editor in Group' }, + title: localize2('lastEditorInGroup', 'Open Last Editor in Group'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1441,7 +1441,7 @@ export class NavigatePreviousAction extends Action2 { constructor() { super({ id: 'workbench.action.navigateLast', - title: { value: localize('navigatePrevious', "Go Previous"), original: 'Go Previous' }, + title: localize2('navigatePrevious', 'Go Previous'), f1: true }); } @@ -1458,7 +1458,7 @@ export class NavigateForwardInEditsAction extends Action2 { constructor() { super({ id: 'workbench.action.navigateForwardInEditLocations', - title: { value: localize('navigateForwardInEdits', "Go Forward in Edit Locations"), original: 'Go Forward in Edit Locations' }, + title: localize2('navigateForwardInEdits', 'Go Forward in Edit Locations'), f1: true }); } @@ -1475,7 +1475,7 @@ export class NavigateBackwardsInEditsAction extends Action2 { constructor() { super({ id: 'workbench.action.navigateBackInEditLocations', - title: { value: localize('navigateBackInEdits', "Go Back in Edit Locations"), original: 'Go Back in Edit Locations' }, + title: localize2('navigateBackInEdits', 'Go Back in Edit Locations'), f1: true }); } @@ -1492,7 +1492,7 @@ export class NavigatePreviousInEditsAction extends Action2 { constructor() { super({ id: 'workbench.action.navigatePreviousInEditLocations', - title: { value: localize('navigatePreviousInEdits', "Go Previous in Edit Locations"), original: 'Go Previous in Edit Locations' }, + title: localize2('navigatePreviousInEdits', 'Go Previous in Edit Locations'), f1: true }); } @@ -1509,7 +1509,7 @@ export class NavigateToLastEditLocationAction extends Action2 { constructor() { super({ id: 'workbench.action.navigateToLastEditLocation', - title: { value: localize('navigateToLastEditLocation', "Go to Last Edit Location"), original: 'Go to Last Edit Location' }, + title: localize2('navigateToLastEditLocation', 'Go to Last Edit Location'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1530,7 +1530,7 @@ export class NavigateForwardInNavigationsAction extends Action2 { constructor() { super({ id: 'workbench.action.navigateForwardInNavigationLocations', - title: { value: localize('navigateForwardInNavigations', "Go Forward in Navigation Locations"), original: 'Go Forward in Navigation Locations' }, + title: localize2('navigateForwardInNavigations', 'Go Forward in Navigation Locations'), f1: true }); } @@ -1547,7 +1547,7 @@ export class NavigateBackwardsInNavigationsAction extends Action2 { constructor() { super({ id: 'workbench.action.navigateBackInNavigationLocations', - title: { value: localize('navigateBackInNavigations', "Go Back in Navigation Locations"), original: 'Go Back in Navigation Locations' }, + title: localize2('navigateBackInNavigations', 'Go Back in Navigation Locations'), f1: true }); } @@ -1564,7 +1564,7 @@ export class NavigatePreviousInNavigationsAction extends Action2 { constructor() { super({ id: 'workbench.action.navigatePreviousInNavigationLocations', - title: { value: localize('navigatePreviousInNavigationLocations', "Go Previous in Navigation Locations"), original: 'Go Previous in Navigation Locations' }, + title: localize2('navigatePreviousInNavigationLocations', 'Go Previous in Navigation Locations'), f1: true }); } @@ -1581,7 +1581,7 @@ export class NavigateToLastNavigationLocationAction extends Action2 { constructor() { super({ id: 'workbench.action.navigateToLastNavigationLocation', - title: { value: localize('navigateToLastNavigationLocation', "Go to Last Navigation Location"), original: 'Go to Last Navigation Location' }, + title: localize2('navigateToLastNavigationLocation', 'Go to Last Navigation Location'), f1: true }); } @@ -1600,7 +1600,7 @@ export class ReopenClosedEditorAction extends Action2 { constructor() { super({ id: ReopenClosedEditorAction.ID, - title: { value: localize('reopenClosedEditor', "Reopen Closed Editor"), original: 'Reopen Closed Editor' }, + title: localize2('reopenClosedEditor', 'Reopen Closed Editor'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1624,7 +1624,7 @@ export class ClearRecentFilesAction extends Action2 { constructor() { super({ id: ClearRecentFilesAction.ID, - title: { value: localize('clearRecentFiles', "Clear Recently Opened..."), original: 'Clear Recently Opened...' }, + title: localize2('clearRecentFiles', 'Clear Recently Opened...'), f1: true, category: Categories.File }); @@ -1662,7 +1662,7 @@ export class ShowEditorsInActiveGroupByMostRecentlyUsedAction extends Action2 { constructor() { super({ id: ShowEditorsInActiveGroupByMostRecentlyUsedAction.ID, - title: { value: localize('showEditorsInActiveGroup', "Show Editors in Active Group By Most Recently Used"), original: 'Show Editors in Active Group By Most Recently Used' }, + title: localize2('showEditorsInActiveGroup', 'Show Editors in Active Group By Most Recently Used'), f1: true, category: Categories.View }); @@ -1682,7 +1682,7 @@ export class ShowAllEditorsByAppearanceAction extends Action2 { constructor() { super({ id: ShowAllEditorsByAppearanceAction.ID, - title: { value: localize('showAllEditors', "Show All Editors By Appearance"), original: 'Show All Editors By Appearance' }, + title: localize2('showAllEditors', 'Show All Editors By Appearance'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1709,7 +1709,7 @@ export class ShowAllEditorsByMostRecentlyUsedAction extends Action2 { constructor() { super({ id: ShowAllEditorsByMostRecentlyUsedAction.ID, - title: { value: localize('showAllEditorsByMostRecentlyUsed', "Show All Editors By Most Recently Used"), original: 'Show All Editors By Most Recently Used' }, + title: localize2('showAllEditorsByMostRecentlyUsed', 'Show All Editors By Most Recently Used'), f1: true, category: Categories.View }); @@ -1750,7 +1750,7 @@ export class QuickAccessPreviousRecentlyUsedEditorAction extends AbstractQuickAc constructor() { super({ id: 'workbench.action.quickOpenPreviousRecentlyUsedEditor', - title: { value: localize('quickOpenPreviousRecentlyUsedEditor', "Quick Open Previous Recently Used Editor"), original: 'Quick Open Previous Recently Used Editor' }, + title: localize2('quickOpenPreviousRecentlyUsedEditor', 'Quick Open Previous Recently Used Editor'), f1: true, category: Categories.View }, AllEditorsByMostRecentlyUsedQuickAccess.PREFIX, undefined); @@ -1762,7 +1762,7 @@ export class QuickAccessLeastRecentlyUsedEditorAction extends AbstractQuickAcces constructor() { super({ id: 'workbench.action.quickOpenLeastRecentlyUsedEditor', - title: { value: localize('quickOpenLeastRecentlyUsedEditor', "Quick Open Least Recently Used Editor"), original: 'Quick Open Least Recently Used Editor' }, + title: localize2('quickOpenLeastRecentlyUsedEditor', 'Quick Open Least Recently Used Editor'), f1: true, category: Categories.View }, AllEditorsByMostRecentlyUsedQuickAccess.PREFIX, undefined); @@ -1774,7 +1774,7 @@ export class QuickAccessPreviousRecentlyUsedEditorInGroupAction extends Abstract constructor() { super({ id: 'workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup', - title: { value: localize('quickOpenPreviousRecentlyUsedEditorInGroup', "Quick Open Previous Recently Used Editor in Group"), original: 'Quick Open Previous Recently Used Editor in Group' }, + title: localize2('quickOpenPreviousRecentlyUsedEditorInGroup', 'Quick Open Previous Recently Used Editor in Group'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1794,7 +1794,7 @@ export class QuickAccessLeastRecentlyUsedEditorInGroupAction extends AbstractQui constructor() { super({ id: 'workbench.action.quickOpenLeastRecentlyUsedEditorInGroup', - title: { value: localize('quickOpenLeastRecentlyUsedEditorInGroup', "Quick Open Least Recently Used Editor in Group"), original: 'Quick Open Least Recently Used Editor in Group' }, + title: localize2('quickOpenLeastRecentlyUsedEditorInGroup', 'Quick Open Least Recently Used Editor in Group'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1816,7 +1816,7 @@ export class QuickAccessPreviousEditorFromHistoryAction extends Action2 { constructor() { super({ id: QuickAccessPreviousEditorFromHistoryAction.ID, - title: { value: localize('navigateEditorHistoryByInput', "Quick Open Previous Editor from History"), original: 'Quick Open Previous Editor from History' }, + title: localize2('navigateEditorHistoryByInput', 'Quick Open Previous Editor from History'), f1: true }); } @@ -1844,7 +1844,7 @@ export class OpenNextRecentlyUsedEditorAction extends Action2 { constructor() { super({ id: 'workbench.action.openNextRecentlyUsedEditor', - title: { value: localize('openNextRecentlyUsedEditor', "Open Next Recently Used Editor"), original: 'Open Next Recently Used Editor' }, + title: localize2('openNextRecentlyUsedEditor', 'Open Next Recently Used Editor'), f1: true, category: Categories.View }); @@ -1862,7 +1862,7 @@ export class OpenPreviousRecentlyUsedEditorAction extends Action2 { constructor() { super({ id: 'workbench.action.openPreviousRecentlyUsedEditor', - title: { value: localize('openPreviousRecentlyUsedEditor', "Open Previous Recently Used Editor"), original: 'Open Previous Recently Used Editor' }, + title: localize2('openPreviousRecentlyUsedEditor', 'Open Previous Recently Used Editor'), f1: true, category: Categories.View }); @@ -1880,7 +1880,7 @@ export class OpenNextRecentlyUsedEditorInGroupAction extends Action2 { constructor() { super({ id: 'workbench.action.openNextRecentlyUsedEditorInGroup', - title: { value: localize('openNextRecentlyUsedEditorInGroup', "Open Next Recently Used Editor In Group"), original: 'Open Next Recently Used Editor In Group' }, + title: localize2('openNextRecentlyUsedEditorInGroup', 'Open Next Recently Used Editor In Group'), f1: true, category: Categories.View }); @@ -1899,7 +1899,7 @@ export class OpenPreviousRecentlyUsedEditorInGroupAction extends Action2 { constructor() { super({ id: 'workbench.action.openPreviousRecentlyUsedEditorInGroup', - title: { value: localize('openPreviousRecentlyUsedEditorInGroup', "Open Previous Recently Used Editor In Group"), original: 'Open Previous Recently Used Editor In Group' }, + title: localize2('openPreviousRecentlyUsedEditorInGroup', 'Open Previous Recently Used Editor In Group'), f1: true, category: Categories.View }); @@ -1918,7 +1918,7 @@ export class ClearEditorHistoryAction extends Action2 { constructor() { super({ id: 'workbench.action.clearEditorHistory', - title: { value: localize('clearEditorHistory', "Clear Editor History"), original: 'Clear Editor History' }, + title: localize2('clearEditorHistory', 'Clear Editor History'), f1: true }); } @@ -1949,7 +1949,7 @@ export class MoveEditorLeftInGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.moveEditorLeftInGroup', - title: { value: localize('moveEditorLeft', "Move Editor Left"), original: 'Move Editor Left' }, + title: localize2('moveEditorLeft', 'Move Editor Left'), keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageUp, @@ -1968,7 +1968,7 @@ export class MoveEditorRightInGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.moveEditorRightInGroup', - title: { value: localize('moveEditorRight', "Move Editor Right"), original: 'Move Editor Right' }, + title: localize2('moveEditorRight', 'Move Editor Right'), keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageDown, @@ -1987,7 +1987,7 @@ export class MoveEditorToPreviousGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.moveEditorToPreviousGroup', - title: { value: localize('moveEditorToPreviousGroup', "Move Editor into Previous Group"), original: 'Move Editor into Previous Group' }, + title: localize2('moveEditorToPreviousGroup', 'Move Editor into Previous Group'), keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, @@ -2006,7 +2006,7 @@ export class MoveEditorToNextGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.moveEditorToNextGroup', - title: { value: localize('moveEditorToNextGroup', "Move Editor into Next Group"), original: 'Move Editor into Next Group' }, + title: localize2('moveEditorToNextGroup', 'Move Editor into Next Group'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -2025,7 +2025,7 @@ export class MoveEditorToAboveGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.moveEditorToAboveGroup', - title: { value: localize('moveEditorToAboveGroup', "Move Editor into Group Above"), original: 'Move Editor into Group Above' }, + title: localize2('moveEditorToAboveGroup', 'Move Editor into Group Above'), f1: true, category: Categories.View }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'up', by: 'group' } as ActiveEditorMoveCopyArguments); @@ -2037,7 +2037,7 @@ export class MoveEditorToBelowGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.moveEditorToBelowGroup', - title: { value: localize('moveEditorToBelowGroup', "Move Editor into Group Below"), original: 'Move Editor into Group Below' }, + title: localize2('moveEditorToBelowGroup', 'Move Editor into Group Below'), f1: true, category: Categories.View }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'down', by: 'group' } as ActiveEditorMoveCopyArguments); @@ -2049,7 +2049,7 @@ export class MoveEditorToLeftGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.moveEditorToLeftGroup', - title: { value: localize('moveEditorToLeftGroup', "Move Editor into Left Group"), original: 'Move Editor into Left Group' }, + title: localize2('moveEditorToLeftGroup', 'Move Editor into Left Group'), f1: true, category: Categories.View }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'left', by: 'group' } as ActiveEditorMoveCopyArguments); @@ -2061,7 +2061,7 @@ export class MoveEditorToRightGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.moveEditorToRightGroup', - title: { value: localize('moveEditorToRightGroup', "Move Editor into Right Group"), original: 'Move Editor into Right Group' }, + title: localize2('moveEditorToRightGroup', 'Move Editor into Right Group'), f1: true, category: Categories.View }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'right', by: 'group' } as ActiveEditorMoveCopyArguments); @@ -2073,7 +2073,7 @@ export class MoveEditorToFirstGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.moveEditorToFirstGroup', - title: { value: localize('moveEditorToFirstGroup', "Move Editor into First Group"), original: 'Move Editor into First Group' }, + title: localize2('moveEditorToFirstGroup', 'Move Editor into First Group'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -2092,7 +2092,7 @@ export class MoveEditorToLastGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.moveEditorToLastGroup', - title: { value: localize('moveEditorToLastGroup', "Move Editor into Last Group"), original: 'Move Editor into Last Group' }, + title: localize2('moveEditorToLastGroup', 'Move Editor into Last Group'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -2111,7 +2111,7 @@ export class SplitEditorToPreviousGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.splitEditorToPreviousGroup', - title: { value: localize('splitEditorToPreviousGroup', "Split Editor into Previous Group"), original: 'Split Editor into Previous Group' }, + title: localize2('splitEditorToPreviousGroup', 'Split Editor into Previous Group'), f1: true, category: Categories.View }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'previous', by: 'group' } as ActiveEditorMoveCopyArguments); @@ -2123,7 +2123,7 @@ export class SplitEditorToNextGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.splitEditorToNextGroup', - title: { value: localize('splitEditorToNextGroup', "Split Editor into Next Group"), original: 'Split Editor into Next Group' }, + title: localize2('splitEditorToNextGroup', 'Split Editor into Next Group'), f1: true, category: Categories.View }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'next', by: 'group' } as ActiveEditorMoveCopyArguments); @@ -2135,7 +2135,7 @@ export class SplitEditorToAboveGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.splitEditorToAboveGroup', - title: { value: localize('splitEditorToAboveGroup', "Split Editor into Group Above"), original: 'Split Editor into Group Above' }, + title: localize2('splitEditorToAboveGroup', 'Split Editor into Group Above'), f1: true, category: Categories.View }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'up', by: 'group' } as ActiveEditorMoveCopyArguments); @@ -2147,7 +2147,7 @@ export class SplitEditorToBelowGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.splitEditorToBelowGroup', - title: { value: localize('splitEditorToBelowGroup', "Split Editor into Group Below"), original: 'Split Editor into Group Below' }, + title: localize2('splitEditorToBelowGroup', 'Split Editor into Group Below'), f1: true, category: Categories.View }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'down', by: 'group' } as ActiveEditorMoveCopyArguments); @@ -2162,7 +2162,7 @@ export class SplitEditorToLeftGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.splitEditorToLeftGroup', - title: { value: localize('splitEditorToLeftGroup', "Split Editor into Left Group"), original: 'Split Editor into Left Group' }, + title: localize2('splitEditorToLeftGroup', "Split Editor into Left Group"), f1: true, category: Categories.View }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'left', by: 'group' } as ActiveEditorMoveCopyArguments); @@ -2174,7 +2174,7 @@ export class SplitEditorToRightGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.splitEditorToRightGroup', - title: { value: localize('splitEditorToRightGroup', "Split Editor into Right Group"), original: 'Split Editor into Right Group' }, + title: localize2('splitEditorToRightGroup', 'Split Editor into Right Group'), f1: true, category: Categories.View }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'right', by: 'group' } as ActiveEditorMoveCopyArguments); @@ -2186,7 +2186,7 @@ export class SplitEditorToFirstGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.splitEditorToFirstGroup', - title: { value: localize('splitEditorToFirstGroup', "Split Editor into First Group"), original: 'Split Editor into First Group' }, + title: localize2('splitEditorToFirstGroup', 'Split Editor into First Group'), f1: true, category: Categories.View }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'first', by: 'group' } as ActiveEditorMoveCopyArguments); @@ -2198,7 +2198,7 @@ export class SplitEditorToLastGroupAction extends ExecuteCommandAction { constructor() { super({ id: 'workbench.action.splitEditorToLastGroup', - title: { value: localize('splitEditorToLastGroup', "Split Editor into Last Group"), original: 'Split Editor into Last Group' }, + title: localize2('splitEditorToLastGroup', 'Split Editor into Last Group'), f1: true, category: Categories.View }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'last', by: 'group' } as ActiveEditorMoveCopyArguments); @@ -2212,7 +2212,7 @@ export class EditorLayoutSingleAction extends ExecuteCommandAction { constructor() { super({ id: EditorLayoutSingleAction.ID, - title: { value: localize('editorLayoutSingle', "Single Column Editor Layout"), original: 'Single Column Editor Layout' }, + title: localize2('editorLayoutSingle', 'Single Column Editor Layout'), f1: true, category: Categories.View }, LAYOUT_EDITOR_GROUPS_COMMAND_ID, { groups: [{}] } as EditorGroupLayout); @@ -2226,7 +2226,7 @@ export class EditorLayoutTwoColumnsAction extends ExecuteCommandAction { constructor() { super({ id: EditorLayoutTwoColumnsAction.ID, - title: { value: localize('editorLayoutTwoColumns', "Two Columns Editor Layout"), original: 'Two Columns Editor Layout' }, + title: localize2('editorLayoutTwoColumns', 'Two Columns Editor Layout'), f1: true, category: Categories.View }, LAYOUT_EDITOR_GROUPS_COMMAND_ID, { groups: [{}, {}], orientation: GroupOrientation.HORIZONTAL } as EditorGroupLayout); @@ -2240,7 +2240,7 @@ export class EditorLayoutThreeColumnsAction extends ExecuteCommandAction { constructor() { super({ id: EditorLayoutThreeColumnsAction.ID, - title: { value: localize('editorLayoutThreeColumns', "Three Columns Editor Layout"), original: 'Three Columns Editor Layout' }, + title: localize2('editorLayoutThreeColumns', 'Three Columns Editor Layout'), f1: true, category: Categories.View }, LAYOUT_EDITOR_GROUPS_COMMAND_ID, { groups: [{}, {}, {}], orientation: GroupOrientation.HORIZONTAL } as EditorGroupLayout); @@ -2254,7 +2254,7 @@ export class EditorLayoutTwoRowsAction extends ExecuteCommandAction { constructor() { super({ id: EditorLayoutTwoRowsAction.ID, - title: { value: localize('editorLayoutTwoRows', "Two Rows Editor Layout"), original: 'Two Rows Editor Layout' }, + title: localize2('editorLayoutTwoRows', 'Two Rows Editor Layout'), f1: true, category: Categories.View }, LAYOUT_EDITOR_GROUPS_COMMAND_ID, { groups: [{}, {}], orientation: GroupOrientation.VERTICAL } as EditorGroupLayout); @@ -2268,7 +2268,7 @@ export class EditorLayoutThreeRowsAction extends ExecuteCommandAction { constructor() { super({ id: EditorLayoutThreeRowsAction.ID, - title: { value: localize('editorLayoutThreeRows', "Three Rows Editor Layout"), original: 'Three Rows Editor Layout' }, + title: localize2('editorLayoutThreeRows', 'Three Rows Editor Layout'), f1: true, category: Categories.View }, LAYOUT_EDITOR_GROUPS_COMMAND_ID, { groups: [{}, {}, {}], orientation: GroupOrientation.VERTICAL } as EditorGroupLayout); @@ -2282,7 +2282,7 @@ export class EditorLayoutTwoByTwoGridAction extends ExecuteCommandAction { constructor() { super({ id: EditorLayoutTwoByTwoGridAction.ID, - title: { value: localize('editorLayoutTwoByTwoGrid', "Grid Editor Layout (2x2)"), original: 'Grid Editor Layout (2x2)' }, + title: localize2('editorLayoutTwoByTwoGrid', 'Grid Editor Layout (2x2)'), f1: true, category: Categories.View }, LAYOUT_EDITOR_GROUPS_COMMAND_ID, { groups: [{ groups: [{}, {}] }, { groups: [{}, {}] }] } as EditorGroupLayout); @@ -2296,7 +2296,7 @@ export class EditorLayoutTwoColumnsBottomAction extends ExecuteCommandAction { constructor() { super({ id: EditorLayoutTwoColumnsBottomAction.ID, - title: { value: localize('editorLayoutTwoColumnsBottom', "Two Columns Bottom Editor Layout"), original: 'Two Columns Bottom Editor Layout' }, + title: localize2('editorLayoutTwoColumnsBottom', 'Two Columns Bottom Editor Layout'), f1: true, category: Categories.View }, LAYOUT_EDITOR_GROUPS_COMMAND_ID, { groups: [{}, { groups: [{}, {}] }], orientation: GroupOrientation.VERTICAL } as EditorGroupLayout); @@ -2310,7 +2310,7 @@ export class EditorLayoutTwoRowsRightAction extends ExecuteCommandAction { constructor() { super({ id: EditorLayoutTwoRowsRightAction.ID, - title: { value: localize('editorLayoutTwoRowsRight', "Two Rows Right Editor Layout"), original: 'Two Rows Right Editor Layout' }, + title: localize2('editorLayoutTwoRowsRight', 'Two Rows Right Editor Layout'), f1: true, category: Categories.View }, LAYOUT_EDITOR_GROUPS_COMMAND_ID, { groups: [{}, { groups: [{}, {}] }], orientation: GroupOrientation.HORIZONTAL } as EditorGroupLayout); @@ -2358,7 +2358,7 @@ export class NewEditorGroupLeftAction extends AbstractCreateEditorGroupAction { constructor() { super({ id: 'workbench.action.newGroupLeft', - title: { value: localize('newGroupLeft', "New Editor Group to the Left"), original: 'New Editor Group to the Left' }, + title: localize2('newGroupLeft', 'New Editor Group to the Left'), f1: true, category: Categories.View }, GroupDirection.LEFT); @@ -2370,7 +2370,7 @@ export class NewEditorGroupRightAction extends AbstractCreateEditorGroupAction { constructor() { super({ id: 'workbench.action.newGroupRight', - title: { value: localize('newGroupRight', "New Editor Group to the Right"), original: 'New Editor Group to the Right' }, + title: localize2('newGroupRight', 'New Editor Group to the Right'), f1: true, category: Categories.View }, GroupDirection.RIGHT); @@ -2382,7 +2382,7 @@ export class NewEditorGroupAboveAction extends AbstractCreateEditorGroupAction { constructor() { super({ id: 'workbench.action.newGroupAbove', - title: { value: localize('newGroupAbove', "New Editor Group Above"), original: 'New Editor Group Above' }, + title: localize2('newGroupAbove', 'New Editor Group Above'), f1: true, category: Categories.View }, GroupDirection.UP); @@ -2394,7 +2394,7 @@ export class NewEditorGroupBelowAction extends AbstractCreateEditorGroupAction { constructor() { super({ id: 'workbench.action.newGroupBelow', - title: { value: localize('newGroupBelow', "New Editor Group Below"), original: 'New Editor Group Below' }, + title: localize2('newGroupBelow', 'New Editor Group Below'), f1: true, category: Categories.View }, GroupDirection.DOWN); @@ -2406,7 +2406,7 @@ export class ToggleEditorTypeAction extends Action2 { constructor() { super({ id: 'workbench.action.toggleEditorType', - title: { value: localize('toggleEditorType', "Toggle Editor Type"), original: 'Toggle Editor Type' }, + title: localize2('toggleEditorType', 'Toggle Editor Type'), f1: true, category: Categories.View, precondition: ActiveEditorAvailableEditorIdsContext @@ -2452,7 +2452,7 @@ export class ReOpenInTextEditorAction extends Action2 { constructor() { super({ id: 'workbench.action.reopenTextEditor', - title: { value: localize('reopenTextEditor', "Reopen Editor With Text Editor"), original: 'Reopen Editor With Text Editor' }, + title: localize2('reopenTextEditor', 'Reopen Editor With Text Editor'), f1: true, category: Categories.View, precondition: ActiveEditorAvailableEditorIdsContext diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 19e63fbccfd..9ef96ab29ad 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -14,7 +14,7 @@ import { isNumber, isObject, isString, isUndefined } from 'vs/base/common/types' import { URI, UriComponents } from 'vs/base/common/uri'; import { isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; @@ -385,7 +385,7 @@ function registerDiffEditorCommands(): void { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: GOTO_NEXT_CHANGE, - title: { value: localize('compare.nextChange', "Go to Next Change"), original: 'Go to Next Change' }, + title: localize2('compare.nextChange', 'Go to Next Change'), } }); @@ -400,7 +400,7 @@ function registerDiffEditorCommands(): void { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: GOTO_PREVIOUS_CHANGE, - title: { value: localize('compare.previousChange', "Go to Previous Change"), original: 'Go to Previous Change' }, + title: localize2('compare.previousChange', 'Go to Previous Change'), } }); @@ -1208,7 +1208,7 @@ function registerSplitEditorInGroupCommands(): void { constructor() { super({ id: SPLIT_EDITOR_IN_GROUP, - title: { value: localize('splitEditorInGroup', "Split Editor in Group"), original: 'Split Editor in Group' }, + title: localize2('splitEditorInGroup', 'Split Editor in Group'), category: Categories.View, precondition: ActiveEditorCanSplitInGroupContext, f1: true, @@ -1254,7 +1254,7 @@ function registerSplitEditorInGroupCommands(): void { constructor() { super({ id: JOIN_EDITOR_IN_GROUP, - title: { value: localize('joinEditorInGroup', "Join Editor in Group"), original: 'Join Editor in Group' }, + title: localize2('joinEditorInGroup', 'Join Editor in Group'), category: Categories.View, precondition: SideBySideEditorActiveContext, f1: true, @@ -1274,7 +1274,7 @@ function registerSplitEditorInGroupCommands(): void { constructor() { super({ id: TOGGLE_SPLIT_EDITOR_IN_GROUP, - title: { value: localize('toggleJoinEditorInGroup', "Toggle Split Editor in Group"), original: 'Toggle Split Editor in Group' }, + title: localize2('toggleJoinEditorInGroup', 'Toggle Split Editor in Group'), category: Categories.View, precondition: ContextKeyExpr.or(ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext), f1: true @@ -1296,7 +1296,7 @@ function registerSplitEditorInGroupCommands(): void { constructor() { super({ id: TOGGLE_SPLIT_EDITOR_IN_GROUP_LAYOUT, - title: { value: localize('toggleSplitEditorInGroupLayout', "Toggle Layout of Split Editor in Group"), original: 'Toggle Layout of Split Editor in Group' }, + title: localize2('toggleSplitEditorInGroupLayout', 'Toggle Layout of Split Editor in Group'), category: Categories.View, precondition: SideBySideEditorActiveContext, f1: true @@ -1324,7 +1324,7 @@ function registerFocusSideEditorsCommands(): void { constructor() { super({ id: FOCUS_FIRST_SIDE_EDITOR, - title: { value: localize('focusLeftSideEditor', "Focus First Side in Active Editor"), original: 'Focus First Side in Active Editor' }, + title: localize2('focusLeftSideEditor', 'Focus First Side in Active Editor'), category: Categories.View, precondition: ContextKeyExpr.or(SideBySideEditorActiveContext, TextCompareEditorActiveContext), f1: true @@ -1347,7 +1347,7 @@ function registerFocusSideEditorsCommands(): void { constructor() { super({ id: FOCUS_SECOND_SIDE_EDITOR, - title: { value: localize('focusRightSideEditor', "Focus Second Side in Active Editor"), original: 'Focus Second Side in Active Editor' }, + title: localize2('focusRightSideEditor', 'Focus Second Side in Active Editor'), category: Categories.View, precondition: ContextKeyExpr.or(SideBySideEditorActiveContext, TextCompareEditorActiveContext), f1: true @@ -1370,7 +1370,7 @@ function registerFocusSideEditorsCommands(): void { constructor() { super({ id: FOCUS_OTHER_SIDE_EDITOR, - title: { value: localize('focusOtherSideEditor', "Focus Other Side in Active Editor"), original: 'Focus Other Side in Active Editor' }, + title: localize2('focusOtherSideEditor', 'Focus Other Side in Active Editor'), category: Categories.View, precondition: ContextKeyExpr.or(SideBySideEditorActiveContext, TextCompareEditorActiveContext), f1: true @@ -1433,7 +1433,7 @@ function registerOtherEditorCommands(): void { constructor() { super({ id: TOGGLE_LOCK_GROUP_COMMAND_ID, - title: { value: localize('toggleEditorGroupLock', "Toggle Editor Group Lock"), original: 'Toggle Editor Group Lock' }, + title: localize2('toggleEditorGroupLock', 'Toggle Editor Group Lock'), category: Categories.View, f1: true }); @@ -1447,7 +1447,7 @@ function registerOtherEditorCommands(): void { constructor() { super({ id: LOCK_GROUP_COMMAND_ID, - title: { value: localize('lockEditorGroup', "Lock Editor Group"), original: 'Lock Editor Group' }, + title: localize2('lockEditorGroup', 'Lock Editor Group'), category: Categories.View, precondition: ActiveEditorGroupLockedContext.toNegated(), f1: true @@ -1462,7 +1462,7 @@ function registerOtherEditorCommands(): void { constructor() { super({ id: UNLOCK_GROUP_COMMAND_ID, - title: { value: localize('unlockEditorGroup', "Unlock Editor Group"), original: 'Unlock Editor Group' }, + title: localize2('unlockEditorGroup', 'Unlock Editor Group'), precondition: ActiveEditorGroupLockedContext, category: Categories.View, f1: true diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 49a12d29655..0a8d16e996b 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/editorstatus'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { getWindowById, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { format, compare, splitLines } from 'vs/base/common/strings'; import { extname, basename, isEqual } from 'vs/base/common/resources'; @@ -1057,7 +1057,7 @@ export class ChangeLanguageAction extends Action2 { constructor() { super({ id: ChangeLanguageAction.ID, - title: { value: localize('changeMode', "Change Language Mode"), original: 'Change Language Mode' }, + title: localize2('changeMode', 'Change Language Mode'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1312,7 +1312,7 @@ export class ChangeEOLAction extends Action2 { constructor() { super({ id: 'workbench.action.editor.changeEOL', - title: { value: localize('changeEndOfLine', "Change End of Line Sequence"), original: 'Change End of Line Sequence' }, + title: localize2('changeEndOfLine', 'Change End of Line Sequence'), f1: true }); } @@ -1361,7 +1361,7 @@ export class ChangeEncodingAction extends Action2 { constructor() { super({ id: 'workbench.action.editor.changeEncoding', - title: { value: localize('changeEncoding', "Change File Encoding"), original: 'Change File Encoding' }, + title: localize2('changeEncoding', 'Change File Encoding'), f1: true }); } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index 2e5ce4abbd2..0816033b762 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -9,7 +9,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { INotificationViewItem, isNotificationViewItem, NotificationsModel } from 'vs/workbench/common/notifications'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IListService, WorkbenchList } from 'vs/platform/list/browser/listService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NotificationMetrics, NotificationMetricsClassification, notificationToMetrics } from 'vs/workbench/browser/parts/notifications/notificationsTelemetry'; @@ -332,14 +332,14 @@ export function registerNotificationCommands(center: INotificationsCenterControl }); // Commands for Command Palette - const category = { value: localize('notifications', "Notifications"), original: 'Notifications' }; - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: SHOW_NOTIFICATIONS_CENTER, title: { value: localize('showNotifications', "Show Notifications"), original: 'Show Notifications' }, category } }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: HIDE_NOTIFICATIONS_CENTER, title: { value: localize('hideNotifications', "Hide Notifications"), original: 'Hide Notifications' }, category }, when: NotificationsCenterVisibleContext }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLEAR_ALL_NOTIFICATIONS, title: { value: localize('clearAllNotifications', "Clear All Notifications"), original: 'Clear All Notifications' }, category } }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: ACCEPT_PRIMARY_ACTION_NOTIFICATION, title: { value: localize('acceptNotificationPrimaryAction', "Accept Notification Primary Action"), original: 'Accept Notification Primary Action' }, category } }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: TOGGLE_DO_NOT_DISTURB_MODE, title: { value: localize('toggleDoNotDisturbMode', "Toggle Do Not Disturb Mode"), original: 'Toggle Do Not Disturb Mode' }, category } }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: TOGGLE_DO_NOT_DISTURB_MODE_BY_SOURCE, title: { value: localize('toggleDoNotDisturbModeBySource', "Toggle Do Not Disturb Mode By Source..."), original: 'Toggle Do Not Disturb Mode By Source...' }, category } }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: FOCUS_NOTIFICATION_TOAST, title: { value: localize('focusNotificationToasts', "Focus Notification Toast"), original: 'Focus Notification Toast' }, category }, when: NotificationsToastsVisibleContext }); + const category = localize2('notifications', 'Notifications'); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: SHOW_NOTIFICATIONS_CENTER, title: localize2('showNotifications', 'Show Notifications'), category } }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: HIDE_NOTIFICATIONS_CENTER, title: localize2('hideNotifications', 'Hide Notifications'), category }, when: NotificationsCenterVisibleContext }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLEAR_ALL_NOTIFICATIONS, title: localize2('clearAllNotifications', 'Clear All Notifications'), category } }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: ACCEPT_PRIMARY_ACTION_NOTIFICATION, title: localize2('acceptNotificationPrimaryAction', 'Accept Notification Primary Action'), category } }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: TOGGLE_DO_NOT_DISTURB_MODE, title: localize2('toggleDoNotDisturbMode', 'Toggle Do Not Disturb Mode'), category } }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: TOGGLE_DO_NOT_DISTURB_MODE_BY_SOURCE, title: localize2('toggleDoNotDisturbModeBySource', 'Toggle Do Not Disturb Mode By Source...'), category } }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: FOCUS_NOTIFICATION_TOAST, title: localize2('focusNotificationToasts', 'Focus Notification Toast'), category }, when: NotificationsToastsVisibleContext }); } diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index b8bd616597f..f846bbefe78 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/panelpart'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { MenuId, MenuRegistry, registerAction2, Action2, IAction2Options } from 'vs/platform/actions/common/actions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; @@ -73,7 +73,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.focusPanel', - title: { value: localize('focusPanel', "Focus into Panel"), original: 'Focus into Panel' }, + title: localize2('focusPanel', "Focus into Panel"), category: Categories.View, f1: true, }); @@ -281,7 +281,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.toggleMaximizedPanel', - title: { value: localize('toggleMaximizedPanel', "Toggle Maximized Panel"), original: 'Toggle Maximized Panel' }, + title: localize2('toggleMaximizedPanel', 'Toggle Maximized Panel'), tooltip: localize('maximizePanel', "Maximize Panel Size"), category: Categories.View, f1: true, @@ -323,7 +323,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.closePanel', - title: { value: localize('closePanel', "Close Panel"), original: 'Close Panel' }, + title: localize2('closePanel', 'Close Panel'), category: Categories.View, icon: closeIcon, menu: [{ @@ -345,7 +345,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.closeAuxiliaryBar', - title: { value: localize('closeSecondarySideBar', "Close Secondary Side Bar"), original: 'Close Secondary Side Bar' }, + title: localize2('closeSecondarySideBar', 'Close Secondary Side Bar'), category: Categories.View, icon: closeIcon, menu: [{ @@ -383,7 +383,7 @@ MenuRegistry.appendMenuItems([ group: '3_workbench_layout_move', command: { id: TogglePanelAction.ID, - title: { value: localize('hidePanel', "Hide Panel"), original: 'Hide Panel' }, + title: localize2('hidePanel', 'Hide Panel'), }, when: ContextKeyExpr.and(PanelVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Panel))), order: 2 diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarActions.ts b/src/vs/workbench/browser/parts/sidebar/sidebarActions.ts index 974349fe5b3..8cafc0dbbcd 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarActions.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/sidebarpart'; -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; @@ -19,7 +19,7 @@ export class FocusSideBarAction extends Action2 { constructor() { super({ id: 'workbench.action.focusSideBar', - title: { value: localize('focusSideBar', "Focus into Primary Side Bar"), original: 'Focus into Primary Side Bar' }, + title: localize2('focusSideBar', 'Focus into Primary Side Bar'), category: Categories.View, f1: true, keybinding: { diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarActions.ts b/src/vs/workbench/browser/parts/statusbar/statusbarActions.ts index 017fa732122..831d600e896 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarActions.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IStatusbarService } from 'vs/workbench/services/statusbar/browser/statusbar'; import { Action } from 'vs/base/common/actions'; import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -114,7 +114,7 @@ class FocusStatusBarAction extends Action2 { constructor() { super({ id: 'workbench.action.focusStatusBar', - title: { value: localize('focusStatusBar', "Focus Status Bar"), original: 'Focus Status Bar' }, + title: localize2('focusStatusBar', 'Focus Status Bar'), category: Categories.View, f1: true }); diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 61bd34e2889..80a172c0d15 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/menubarControl'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action2, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable, getMenuBarVisibility } from 'vs/platform/window/common/window'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -438,7 +438,7 @@ export class CustomMenubarControl extends MenubarControl { constructor() { super({ id: `workbench.actions.menubar.focus`, - title: { value: localize('focusMenu', "Focus Application Menu"), original: 'Focus Application Menu' }, + title: localize2('focusMenu', 'Focus Application Menu'), keybinding: { primary: KeyMod.Alt | KeyCode.F10, weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 2e69d9da37c..06c7a85a31d 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/titlebarpart'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { MultiWindowParts, Part } from 'vs/workbench/browser/part'; import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; import { getZoomFactor, isWCOEnabled } from 'vs/base/browser/browser'; @@ -461,7 +461,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { constructor() { super({ id: `workbench.action.focusTitleBar`, - title: { value: localize('focusTitleBar', "Focus Title Bar"), original: 'Focus Title Bar' }, + title: localize2('focusTitleBar', 'Focus Title Bar'), category: Categories.View, f1: true, }); diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts index cab296d9d30..8dcb48da8a2 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { CallHierarchyProviderRegistry, CallHierarchyDirection, CallHierarchyModel } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -179,7 +179,7 @@ registerAction2(class PeekCallHierarchyAction extends EditorAction2 { constructor() { super({ id: 'editor.showCallHierarchy', - title: { value: localize('title', "Peek Call Hierarchy"), original: 'Peek Call Hierarchy' }, + title: localize2('title', 'Peek Call Hierarchy'), menu: { id: MenuId.EditorContextPeek, group: 'navigation', @@ -212,7 +212,7 @@ registerAction2(class extends EditorAction2 { constructor() { super({ id: 'editor.showIncomingCalls', - title: { value: localize('title.incoming', "Show Incoming Calls"), original: 'Show Incoming Calls' }, + title: localize2('title.incoming', 'Show Incoming Calls'), icon: registerIcon('callhierarchy-incoming', Codicon.callIncoming, localize('showIncomingCallsIcons', 'Icon for incoming calls in the call hierarchy view.')), precondition: ContextKeyExpr.and(_ctxCallHierarchyVisible, _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsFrom)), keybinding: { @@ -237,7 +237,7 @@ registerAction2(class extends EditorAction2 { constructor() { super({ id: 'editor.showOutgoingCalls', - title: { value: localize('title.outgoing', "Show Outgoing Calls"), original: 'Show Outgoing Calls' }, + title: localize2('title.outgoing', 'Show Outgoing Calls'), icon: registerIcon('callhierarchy-outgoing', Codicon.callOutgoing, localize('showOutgoingCallsIcon', 'Icon for outgoing calls in the call hierarchy view.')), precondition: ContextKeyExpr.and(_ctxCallHierarchyVisible, _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsTo)), keybinding: { @@ -263,7 +263,7 @@ registerAction2(class extends EditorAction2 { constructor() { super({ id: 'editor.refocusCallHierarchy', - title: { value: localize('title.refocus', "Refocus Call Hierarchy"), original: 'Refocus Call Hierarchy' }, + title: localize2('title.refocus', 'Refocus Call Hierarchy'), precondition: _ctxCallHierarchyVisible, keybinding: { weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index e776928802f..70758d60fa8 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -35,7 +35,7 @@ import { IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chat import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -export const CHAT_CATEGORY = { value: localize('chat.category', "Chat"), original: 'Chat' }; +export const CHAT_CATEGORY = localize2('chat.category', 'Chat'); export const CHAT_OPEN_ACTION_ID = 'workbench.action.chat.open'; export interface IChatViewOpenOptions { @@ -178,7 +178,7 @@ export function registerChatActions() { constructor() { super({ id: 'chat.action.focus', - title: { value: localize('actions.interactiveSession.focus', "Focus Chat List"), original: 'Focus Chat List' }, + title: localize2('actions.interactiveSession.focus', 'Focus Chat List'), precondition: ContextKeyExpr.and(CONTEXT_IN_CHAT_INPUT, CONTEXT_CHAT_INPUT_CURSOR_AT_TOP), category: CHAT_CATEGORY, keybinding: { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts index 6007992437b..dfa463316a9 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts @@ -7,7 +7,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { Selection } from 'vs/editor/common/core/selection'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -109,7 +109,7 @@ class QuickChatGlobalAction extends Action2 { constructor() { super({ id: ASK_QUICK_QUESTION_ACTION_ID, - title: { value: localize('quickChat', "Quick Chat"), original: 'Quick Chat' }, + title: localize2('quickChat', 'Quick Chat'), precondition: CONTEXT_PROVIDER_EXISTS, icon: Codicon.commentDiscussion, f1: false, diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts index dc493bf295c..af947509e81 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -16,7 +16,7 @@ class InspectKeyMap extends Action2 { constructor() { super({ id: 'workbench.action.inspectKeyMappings', - title: { value: localize('workbench.action.inspectKeyMap', "Inspect Key Mappings"), original: 'Inspect Key Mappings' }, + title: localize2('workbench.action.inspectKeyMap', 'Inspect Key Mappings'), category: Categories.Developer, f1: true }); @@ -37,7 +37,7 @@ class InspectKeyMapJSON extends Action2 { constructor() { super({ id: 'workbench.action.inspectKeyMappingsJSON', - title: { value: localize('workbench.action.inspectKeyMapJSON', "Inspect Key Mappings (JSON)"), original: 'Inspect Key Mappings (JSON)' }, + title: localize2('workbench.action.inspectKeyMapJSON', 'Inspect Key Mappings (JSON)'), category: Categories.Developer, f1: true }); diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts index 2f406a83c22..14499cff388 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IKeyMods, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IRange } from 'vs/editor/common/core/range'; @@ -73,7 +73,7 @@ class GotoLineAction extends Action2 { constructor() { super({ id: GotoLineAction.ID, - title: { value: localize('gotoLine', "Go to Line/Column..."), original: 'Go to Line/Column...' }, + title: localize2('gotoLine', 'Go to Line/Column...'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts index fea60d0515a..f5b2c84ebcb 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { isMacintosh } from 'vs/base/common/platform'; import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -22,7 +22,7 @@ export class ToggleMultiCursorModifierAction extends Action2 { constructor() { super({ id: ToggleMultiCursorModifierAction.ID, - title: { value: localize('toggleLocation', "Toggle Multi-Cursor Modifier"), original: 'Toggle Multi-Cursor Modifier' }, + title: localize2('toggleLocation', 'Toggle Multi-Cursor Modifier'), f1: true }); } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 7e7379706dc..556878a5844 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -25,7 +25,7 @@ import { Constants } from 'vs/base/common/uint'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -1357,7 +1357,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.debug.viewlet.action.toggleBreakpointsActivatedAction', - title: { value: localize('activateBreakpoints', "Toggle Activate Breakpoints"), original: 'Toggle Activate Breakpoints' }, + title: localize2('activateBreakpoints', 'Toggle Activate Breakpoints'), f1: true, icon: icons.breakpointsActivate, menu: { @@ -1514,7 +1514,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.debug.viewlet.action.reapplyBreakpointsAction', - title: { value: localize('reapplyAllBreakpoints', "Reapply All Breakpoints"), original: 'Reapply All Breakpoints' }, + title: localize2('reapplyAllBreakpoints', 'Reapply All Breakpoints'), f1: true, precondition: CONTEXT_IN_DEBUG_MODE, menu: [{ diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 3ca7c13cffd..2aa639163de 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -37,7 +37,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { IModelService } from 'vs/editor/common/services/model'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -971,7 +971,7 @@ registerAction2(class extends ViewAction { super({ id: 'workbench.debug.panel.action.clearReplAction', viewId: REPL_VIEW_ID, - title: { value: localize('clearRepl', "Clear Console"), original: 'Clear Console' }, + title: localize2('clearRepl', 'Clear Console'), f1: true, icon: debugConsoleClearAll, menu: [{ diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index 7ba7a009540..88202895d9e 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -9,7 +9,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { ILifecycleService, LifecyclePhase, ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Action2, IAction2Options, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IEditSessionsStorageService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_CATEGORY, EDIT_SESSIONS_CONTAINER_ID, EditSessionSchemaVersion, IEditSessionsLogService, EDIT_SESSIONS_VIEW_ICON, EDIT_SESSIONS_TITLE, EDIT_SESSIONS_SHOW_VIEW, EDIT_SESSIONS_DATA_VIEW_ID, decodeEditSessionFileContent, hashedEditSessionId, editSessionsLogId, EDIT_SESSIONS_PENDING } from 'vs/workbench/contrib/editSessions/common/editSessions'; import { ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; import { IFileService } from 'vs/platform/files/common/files'; @@ -75,13 +75,13 @@ registerSingleton(IEditSessionsStorageService, EditSessionsWorkbenchService, Ins const continueWorkingOnCommand: IAction2Options = { id: '_workbench.editSessions.actions.continueEditSession', - title: { value: localize('continue working on', "Continue Working On..."), original: 'Continue Working On...' }, + title: localize2('continue working on', 'Continue Working On...'), precondition: WorkspaceFolderCountContext.notEqualsTo('0'), f1: true }; const openLocalFolderCommand: IAction2Options = { id: '_workbench.editSessions.actions.continueEditSession.openLocalFolder', - title: { value: localize('continue edit session in local folder', "Open In Local Folder"), original: 'Open In Local Folder' }, + title: localize2('continue edit session in local folder', 'Open In Local Folder'), category: EDIT_SESSION_SYNC_CATEGORY, precondition: ContextKeyExpr.and(IsWebContext.toNegated(), VirtualWorkspaceContext) }; @@ -317,7 +317,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo constructor() { super({ id: 'workbench.editSessions.actions.showEditSessions', - title: { value: localize('show cloud changes', "Show Cloud Changes"), original: 'Show Cloud Changes' }, + title: localize2('show cloud changes', 'Show Cloud Changes'), category: EDIT_SESSION_SYNC_CATEGORY, f1: true }); @@ -426,7 +426,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo constructor() { super({ id: 'workbench.editSessions.actions.resumeLatest', - title: { value: localize('resume latest cloud changes', "Resume Latest Changes from Cloud"), original: 'Resume Latest Changes from Cloud' }, + title: localize2('resume latest cloud changes', 'Resume Latest Changes from Cloud'), category: EDIT_SESSION_SYNC_CATEGORY, f1: true, }); @@ -440,7 +440,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo constructor() { super({ id: 'workbench.editSessions.actions.resumeFromSerializedPayload', - title: { value: localize('resume cloud changes', "Resume Changes from Serialized Data"), original: 'Resume Changes from Serialized Data' }, + title: localize2('resume cloud changes', 'Resume Changes from Serialized Data'), category: 'Developer', f1: true, }); @@ -462,7 +462,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo constructor() { super({ id: 'workbench.editSessions.actions.storeCurrent', - title: { value: localize('store working changes in cloud', "Store Working Changes in Cloud"), original: 'Store Working Changes in Cloud' }, + title: localize2('store working changes in cloud', 'Store Working Changes in Cloud'), category: EDIT_SESSION_SYNC_CATEGORY, f1: true, }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 4797dcb84c3..f802e17f7c3 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; import { MenuRegistry, MenuId, registerAction2, Action2, ISubmenuItem, IMenuItem, IAction2Options } from 'vs/platform/actions/common/actions'; @@ -107,7 +107,7 @@ Registry.as(EditorExtensions.EditorPane).registerEditorPane Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer( { id: VIEWLET_ID, - title: { value: localize('extensions', "Extensions"), original: 'Extensions' }, + title: localize2('extensions', "Extensions"), openCommandActionDescriptor: { id: VIEWLET_ID, mnemonicTitle: localize({ key: 'miViewExtensions', comment: ['&& denotes a mnemonic'] }, "E&&xtensions"), @@ -536,7 +536,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.focusExtensionsView', - title: { value: localize('focusExtensions', "Focus on Extensions View"), original: 'Focus on Extensions View' }, + title: localize2('focusExtensions', 'Focus on Extensions View'), category: ExtensionsLocalizedLabel, f1: true, run: async (accessor: ServicesAccessor) => { @@ -546,7 +546,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.installExtensions', - title: { value: localize('installExtensions', "Install Extensions"), original: 'Install Extensions' }, + title: localize2('installExtensions', 'Install Extensions'), category: ExtensionsLocalizedLabel, menu: { id: MenuId.CommandPalette, @@ -559,7 +559,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.showRecommendedKeymapExtensions', - title: { value: localize('showRecommendedKeymapExtensionsShort', "Keymaps"), original: 'Keymaps' }, + title: localize2('showRecommendedKeymapExtensionsShort', 'Keymaps'), category: PreferencesLocalizedLabel, menu: [{ id: MenuId.CommandPalette, @@ -577,7 +577,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.showLanguageExtensions', - title: { value: localize('showLanguageExtensionsShort', "Language Extensions"), original: 'Language Extensions' }, + title: localize2('showLanguageExtensionsShort', 'Language Extensions'), category: PreferencesLocalizedLabel, menu: { id: MenuId.CommandPalette, @@ -588,7 +588,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.checkForUpdates', - title: { value: localize('checkForUpdates', "Check for Extension Updates"), original: 'Check for Extension Updates' }, + title: localize2('checkForUpdates', 'Check for Extension Updates'), category: ExtensionsLocalizedLabel, menu: [{ id: MenuId.CommandPalette, @@ -665,7 +665,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.updateAllExtensions', - title: { value: localize('updateAll', "Update All Extensions"), original: 'Update All Extensions' }, + title: localize2('updateAll', 'Update All Extensions'), category: ExtensionsLocalizedLabel, precondition: HasOutdatedExtensionsContext, menu: [ @@ -701,7 +701,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.disableAutoUpdate', - title: { value: localize('disableAutoUpdate', "Disable Auto Update for All Extensions"), original: 'Disable Auto Update for All Extensions' }, + title: localize2('disableAutoUpdate', 'Disable Auto Update for All Extensions'), category: ExtensionsLocalizedLabel, f1: true, precondition: CONTEXT_HAS_GALLERY, @@ -710,7 +710,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.enableAutoUpdate', - title: { value: localize('enableAutoUpdate', "Enable Auto Update for All Extensions"), original: 'Enable Auto Update for All Extensions' }, + title: localize2('enableAutoUpdate', 'Enable Auto Update for All Extensions'), category: ExtensionsLocalizedLabel, f1: true, precondition: CONTEXT_HAS_GALLERY, @@ -719,7 +719,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.enableAll', - title: { value: localize('enableAll', "Enable All Extensions"), original: 'Enable All Extensions' }, + title: localize2('enableAll', 'Enable All Extensions'), category: ExtensionsLocalizedLabel, menu: [{ id: MenuId.CommandPalette, @@ -740,7 +740,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.enableAllWorkspace', - title: { value: localize('enableAllWorkspace', "Enable All Extensions for this Workspace"), original: 'Enable All Extensions for this Workspace' }, + title: localize2('enableAllWorkspace', 'Enable All Extensions for this Workspace'), category: ExtensionsLocalizedLabel, menu: { id: MenuId.CommandPalette, @@ -756,7 +756,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.disableAll', - title: { value: localize('disableAll', "Disable All Installed Extensions"), original: 'Disable All Installed Extensions' }, + title: localize2('disableAll', 'Disable All Installed Extensions'), category: ExtensionsLocalizedLabel, menu: [{ id: MenuId.CommandPalette, @@ -777,7 +777,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.disableAllWorkspace', - title: { value: localize('disableAllWorkspace', "Disable All Installed Extensions for this Workspace"), original: 'Disable All Installed Extensions for this Workspace' }, + title: localize2('disableAllWorkspace', 'Disable All Installed Extensions for this Workspace'), category: ExtensionsLocalizedLabel, menu: { id: MenuId.CommandPalette, @@ -793,7 +793,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, - title: { value: localize('InstallFromVSIX', "Install from VSIX..."), original: 'Install from VSIX...' }, + title: localize2('InstallFromVSIX', 'Install from VSIX...'), category: ExtensionsLocalizedLabel, menu: [{ id: MenuId.CommandPalette, @@ -857,7 +857,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.installExtensionFromLocation', - title: { value: localize('installExtensionFromLocation', "Install Extension from Location..."), original: 'Install Extension from Location...' }, + title: localize2('installExtensionFromLocation', 'Install Extension from Location...'), category: Categories.Developer, menu: [{ id: MenuId.CommandPalette, @@ -909,7 +909,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi const showFeaturedExtensionsId = 'extensions.filter.featured'; this.registerExtensionAction({ id: showFeaturedExtensionsId, - title: { value: localize('showFeaturedExtensions', "Show Featured Extensions"), original: 'Show Featured Extensions' }, + title: localize2('showFeaturedExtensions', 'Show Featured Extensions'), category: ExtensionsLocalizedLabel, menu: [{ id: MenuId.CommandPalette, @@ -928,7 +928,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.showPopularExtensions', - title: { value: localize('showPopularExtensions', "Show Popular Extensions"), original: 'Show Popular Extensions' }, + title: localize2('showPopularExtensions', 'Show Popular Extensions'), category: ExtensionsLocalizedLabel, menu: [{ id: MenuId.CommandPalette, @@ -947,7 +947,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.showRecommendedExtensions', - title: { value: localize('showRecommendedExtensions', "Show Recommended Extensions"), original: 'Show Recommended Extensions' }, + title: localize2('showRecommendedExtensions', 'Show Recommended Extensions'), category: ExtensionsLocalizedLabel, menu: [{ id: MenuId.CommandPalette, @@ -966,7 +966,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.recentlyPublishedExtensions', - title: { value: localize('recentlyPublishedExtensions', "Show Recently Published Extensions"), original: 'Show Recently Published Extensions' }, + title: localize2('recentlyPublishedExtensions', 'Show Recently Published Extensions'), category: ExtensionsLocalizedLabel, menu: [{ id: MenuId.CommandPalette, @@ -1007,7 +1007,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.listBuiltInExtensions', - title: { value: localize('showBuiltInExtensions', "Show Built-in Extensions"), original: 'Show Built-in Extensions' }, + title: localize2('showBuiltInExtensions', 'Show Built-in Extensions'), category: ExtensionsLocalizedLabel, menu: [{ id: MenuId.CommandPalette, @@ -1025,7 +1025,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.extensionUpdates', - title: { value: localize('extensionUpdates', "Show Extension Updates"), original: 'Show Extension Updates' }, + title: localize2('extensionUpdates', 'Show Extension Updates'), category: ExtensionsLocalizedLabel, precondition: CONTEXT_HAS_GALLERY, f1: true, @@ -1043,7 +1043,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, - title: { value: localize('showWorkspaceUnsupportedExtensions', "Show Extensions Unsupported By Workspace"), original: 'Show Extensions Unsupported By Workspace' }, + title: localize2('showWorkspaceUnsupportedExtensions', 'Show Extensions Unsupported By Workspace'), category: ExtensionsLocalizedLabel, menu: [{ id: MenuId.CommandPalette, @@ -1062,7 +1062,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.showEnabledExtensions', - title: { value: localize('showEnabledExtensions', "Show Enabled Extensions"), original: 'Show Enabled Extensions' }, + title: localize2('showEnabledExtensions', 'Show Enabled Extensions'), category: ExtensionsLocalizedLabel, menu: [{ id: MenuId.CommandPalette, @@ -1080,7 +1080,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.showDisabledExtensions', - title: { value: localize('showDisabledExtensions', "Show Disabled Extensions"), original: 'Show Disabled Extensions' }, + title: localize2('showDisabledExtensions', 'Show Disabled Extensions'), category: ExtensionsLocalizedLabel, menu: [{ id: MenuId.CommandPalette, @@ -1134,7 +1134,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.clearExtensionsSearchResults', - title: { value: localize('clearExtensionsSearchResults', "Clear Extensions Search Results"), original: 'Clear Extensions Search Results' }, + title: localize2('clearExtensionsSearchResults', 'Clear Extensions Search Results'), category: ExtensionsLocalizedLabel, icon: clearSearchResultsIcon, f1: true, @@ -1156,7 +1156,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.refreshExtension', - title: { value: localize('refreshExtension', "Refresh"), original: 'Refresh' }, + title: localize2('refreshExtension', 'Refresh'), category: ExtensionsLocalizedLabel, icon: refreshIcon, f1: true, @@ -1297,7 +1297,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.showPreReleaseVersion', - title: { value: localize('show pre-release version', "Show Pre-Release Version"), original: 'Show Pre-Release Version' }, + title: localize2('show pre-release version', 'Show Pre-Release Version'), menu: { id: MenuId.ExtensionContext, group: INSTALL_ACTIONS_GROUP, @@ -1313,7 +1313,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.showReleasedVersion', - title: { value: localize('show released version', "Show Release Version"), original: 'Show Release Version' }, + title: localize2('show released version', 'Show Release Version'), menu: { id: MenuId.ExtensionContext, group: INSTALL_ACTIONS_GROUP, @@ -1430,7 +1430,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.copyExtension', - title: { value: localize('workbench.extensions.action.copyExtension', "Copy"), original: 'Copy' }, + title: localize2('workbench.extensions.action.copyExtension', 'Copy'), menu: { id: MenuId.ExtensionContext, group: '1_copy' @@ -1454,7 +1454,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.copyExtensionId', - title: { value: localize('workbench.extensions.action.copyExtensionId', "Copy Extension ID"), original: 'Copy Extension ID' }, + title: localize2('workbench.extensions.action.copyExtensionId', 'Copy Extension ID'), menu: { id: MenuId.ExtensionContext, group: '1_copy' @@ -1464,7 +1464,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.configure', - title: { value: localize('workbench.extensions.action.configure', "Extension Settings"), original: 'Extension Settings' }, + title: localize2('workbench.extensions.action.configure', 'Extension Settings'), menu: { id: MenuId.ExtensionContext, group: '2_configure', @@ -1476,7 +1476,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.configureKeybindings', - title: { value: localize('workbench.extensions.action.configureKeybindings', "Extension Keyboard Shortcuts"), original: 'Extension Keyboard Shortcuts' }, + title: localize2('workbench.extensions.action.configureKeybindings', 'Extension Keyboard Shortcuts'), menu: { id: MenuId.ExtensionContext, group: '2_configure', diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index bef42b4c7b9..c4bc8542d3d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/extensionActions'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IAction, Action, Separator, SubmenuAction } from 'vs/base/common/actions'; import { Delayer, Promises, Throttler } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; @@ -1292,7 +1292,7 @@ export class SwitchToPreReleaseVersionAction extends ExtensionAction { export class SwitchToReleasedVersionAction extends ExtensionAction { static readonly ID = 'workbench.extensions.action.switchToReleaseVersion'; - static readonly TITLE = { value: localize('switch to release version', "Switch to Release Version"), original: 'Switch to Release Version' }; + static readonly TITLE = localize2('switch to release version', 'Switch to Release Version'); constructor( icon: boolean, @@ -1601,7 +1601,7 @@ function getQuickPickEntries(themes: IWorkbenchTheme[], currentTheme: IWorkbench export class SetColorThemeAction extends ExtensionAction { static readonly ID = 'workbench.extensions.action.setColorTheme'; - static readonly TITLE = { value: localize('workbench.extensions.action.setColorTheme', "Set Color Theme"), original: 'Set Color Theme' }; + static readonly TITLE = localize2('workbench.extensions.action.setColorTheme', 'Set Color Theme'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; private static readonly DisabledClass = `${SetColorThemeAction.EnabledClass} disabled`; @@ -1652,7 +1652,7 @@ export class SetColorThemeAction extends ExtensionAction { export class SetFileIconThemeAction extends ExtensionAction { static readonly ID = 'workbench.extensions.action.setFileIconTheme'; - static readonly TITLE = { value: localize('workbench.extensions.action.setFileIconTheme', "Set File Icon Theme"), original: 'Set File Icon Theme' }; + static readonly TITLE = localize2('workbench.extensions.action.setFileIconTheme', 'Set File Icon Theme'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; private static readonly DisabledClass = `${SetFileIconThemeAction.EnabledClass} disabled`; @@ -1702,7 +1702,7 @@ export class SetFileIconThemeAction extends ExtensionAction { export class SetProductIconThemeAction extends ExtensionAction { static readonly ID = 'workbench.extensions.action.setProductIconTheme'; - static readonly TITLE = { value: localize('workbench.extensions.action.setProductIconTheme', "Set Product Icon Theme"), original: 'Set Product Icon Theme' }; + static readonly TITLE = localize2('workbench.extensions.action.setProductIconTheme', 'Set Product Icon Theme'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; private static readonly DisabledClass = `${SetProductIconThemeAction.EnabledClass} disabled`; @@ -1753,7 +1753,7 @@ export class SetProductIconThemeAction extends ExtensionAction { export class SetLanguageAction extends ExtensionAction { static readonly ID = 'workbench.extensions.action.setDisplayLanguage'; - static readonly TITLE = { value: localize('workbench.extensions.action.setDisplayLanguage', "Set Display Language"), original: 'Set Display Language' }; + static readonly TITLE = localize2('workbench.extensions.action.setDisplayLanguage', 'Set Display Language'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} language`; private static readonly DisabledClass = `${SetLanguageAction.EnabledClass} disabled`; @@ -1789,7 +1789,7 @@ export class SetLanguageAction extends ExtensionAction { export class ClearLanguageAction extends ExtensionAction { static readonly ID = 'workbench.extensions.action.clearLanguage'; - static readonly TITLE = { value: localize('workbench.extensions.action.clearLanguage', "Clear Display Language"), original: 'Clear Display Language' }; + static readonly TITLE = localize2('workbench.extensions.action.clearLanguage', 'Clear Display Language'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} language`; private static readonly DisabledClass = `${ClearLanguageAction.EnabledClass} disabled`; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 87ca44f407d..63d722516ec 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -203,7 +203,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio constructor() { super({ id: 'workbench.extensions.actions.installLocalExtensionsInRemote', - title: { value: localize('install remote in local', "Install Remote Extensions Locally..."), original: 'Install Remote Extensions Locally...' }, + title: localize2('install remote in local', 'Install Remote Extensions Locally...'), category: REMOTE_CATEGORY, f1: true }); diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts index b8bbdd0e6e9..0b76db20375 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; @@ -19,7 +19,7 @@ export class OpenExtensionsFolderAction extends Action2 { constructor() { super({ id: 'workbench.extensions.action.openExtensionsFolder', - title: { value: localize('openExtensionsFolder', "Open Extensions Folder"), original: 'Open Extensions Folder' }, + title: localize2('openExtensionsFolder', 'Open Extensions Folder'), category: ExtensionsLocalizedLabel, f1: true }); @@ -51,7 +51,7 @@ export class CleanUpExtensionsFolderAction extends Action2 { constructor() { super({ id: '_workbench.extensions.action.cleanUpExtensionsFolder', - title: { value: localize('cleanUpExtensionsFolder', "Cleanup Extensions Folder"), original: 'Cleanup Extensions Folder' }, + title: localize2('cleanUpExtensionsFolder', 'Cleanup Extensions Folder'), category: Categories.Developer, f1: true }); diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index 24ca46450ed..5f28a60360b 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -21,7 +21,7 @@ import { IModelService } from 'vs/editor/common/services/model'; import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import { peekViewBorder } from 'vs/editor/contrib/peekView/browser/peekView'; import { Context as SuggestContext } from 'vs/editor/contrib/suggest/browser/suggest'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -302,11 +302,11 @@ registerAction2(class extends Action2 { constructor() { super({ id: '_interactive.open', - title: { value: localize('interactive.open', "Open Interactive Window"), original: 'Open Interactive Window' }, + title: localize2('interactive.open', 'Open Interactive Window'), f1: false, category: interactiveWindowCategory, metadata: { - description: localize('interactive.open', "Open Interactive Window"), + description: localize('interactive.open', 'Open Interactive Window'), args: [ { name: 'showOptions', @@ -420,7 +420,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'interactive.execute', - title: { value: localize('interactive.execute', "Execute Code"), original: 'Execute Code' }, + title: localize2('interactive.execute', 'Execute Code'), category: interactiveWindowCategory, keybinding: { // when: NOTEBOOK_CELL_LIST_FOCUSED, @@ -535,7 +535,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'interactive.input.clear', - title: { value: localize('interactive.input.clear', "Clear the interactive window input editor contents"), original: 'Clear the interactive window input editor contents' }, + title: localize2('interactive.input.clear', 'Clear the interactive window input editor contents'), category: interactiveWindowCategory, f1: false }); @@ -561,7 +561,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'interactive.history.previous', - title: { value: localize('interactive.history.previous', "Previous value in history"), original: 'Previous value in history' }, + title: localize2('interactive.history.previous', 'Previous value in history'), category: interactiveWindowCategory, f1: false, keybinding: { @@ -600,7 +600,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'interactive.history.next', - title: { value: localize('interactive.history.next', "Next value in history"), original: 'Next value in history' }, + title: localize2('interactive.history.next', 'Next value in history'), category: interactiveWindowCategory, f1: false, keybinding: { @@ -699,7 +699,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'interactive.input.focus', - title: { value: localize('interactive.input.focus', "Focus Input Editor"), original: 'Focus Input Editor' }, + title: localize2('interactive.input.focus', 'Focus Input Editor'), category: interactiveWindowCategory, menu: { id: MenuId.CommandPalette, @@ -738,7 +738,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'interactive.history.focus', - title: { value: localize('interactive.history.focus', "Focus History"), original: 'Focus History' }, + title: localize2('interactive.history.focus', 'Focus History'), category: interactiveWindowCategory, menu: { id: MenuId.CommandPalette, diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts index ccaafef7cdd..3f446e23dba 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { MenuRegistry, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -67,7 +67,7 @@ class OpenProcessExplorer extends Action2 { constructor() { super({ id: OpenProcessExplorer.ID, - title: { value: localize('openProcessExplorer', "Open Process Explorer"), original: 'Open Process Explorer' }, + title: localize2('openProcessExplorer', 'Open Process Explorer'), category: Categories.Developer, f1: true }); @@ -96,7 +96,7 @@ class StopTracing extends Action2 { constructor() { super({ id: StopTracing.ID, - title: { value: localize('stopTracing', "Stop Tracing"), original: 'Stop Tracing' }, + title: localize2('stopTracing', 'Stop Tracing'), category: Categories.Developer, f1: true }); diff --git a/src/vs/workbench/contrib/localHistory/browser/localHistoryCommands.ts b/src/vs/workbench/contrib/localHistory/browser/localHistoryCommands.ts index 7523573ec2e..c768ab602f0 100644 --- a/src/vs/workbench/contrib/localHistory/browser/localHistoryCommands.ts +++ b/src/vs/workbench/contrib/localHistory/browser/localHistoryCommands.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; @@ -32,7 +32,7 @@ import { firstOrDefault } from 'vs/base/common/arrays'; import { getLocalHistoryDateFormatter, LOCAL_HISTORY_ICON_RESTORE, LOCAL_HISTORY_MENU_CONTEXT_KEY } from 'vs/workbench/contrib/localHistory/browser/localHistory'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; -const LOCAL_HISTORY_CATEGORY = { value: localize('localHistory.category', "Local History"), original: 'Local History' }; +const LOCAL_HISTORY_CATEGORY = localize2('localHistory.category', 'Local History'); export interface ITimelineCommandArgument { uri: URI; @@ -41,7 +41,7 @@ export interface ITimelineCommandArgument { //#region Compare with File -export const COMPARE_WITH_FILE_LABEL = { value: localize('localHistory.compareWithFile', "Compare with File"), original: 'Compare with File' }; +export const COMPARE_WITH_FILE_LABEL = localize2('localHistory.compareWithFile', 'Compare with File'); registerAction2(class extends Action2 { constructor() { @@ -75,7 +75,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.localHistory.compareWithPrevious', - title: { value: localize('localHistory.compareWithPrevious', "Compare with Previous"), original: 'Compare with Previous' }, + title: localize2('localHistory.compareWithPrevious', 'Compare with Previous'), menu: { id: MenuId.TimelineItemContext, group: '1_compare', @@ -115,7 +115,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.localHistory.selectForCompare', - title: { value: localize('localHistory.selectForCompare', "Select for Compare"), original: 'Select for Compare' }, + title: localize2('localHistory.selectForCompare', 'Select for Compare'), menu: { id: MenuId.TimelineItemContext, group: '2_compare_with', @@ -140,7 +140,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.localHistory.compareWithSelected', - title: { value: localize('localHistory.compareWithSelected', "Compare with Selected"), original: 'Compare with Selected' }, + title: localize2('localHistory.compareWithSelected', 'Compare with Selected'), menu: { id: MenuId.TimelineItemContext, group: '2_compare_with', @@ -177,7 +177,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.localHistory.open', - title: { value: localize('localHistory.open', "Show Contents"), original: 'Show Contents' }, + title: localize2('localHistory.open', 'Show Contents'), menu: { id: MenuId.TimelineItemContext, group: '3_contents', @@ -199,7 +199,7 @@ registerAction2(class extends Action2 { //#region Restore Contents -const RESTORE_CONTENTS_LABEL = { value: localize('localHistory.restore', "Restore Contents"), original: 'Restore Contents' }; +const RESTORE_CONTENTS_LABEL = localize2('localHistory.restore', 'Restore Contents'); registerAction2(class extends Action2 { constructor() { @@ -314,7 +314,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.localHistory.restoreViaPicker', - title: { value: localize('localHistory.restoreViaPicker', "Find Entry to Restore"), original: 'Find Entry to Restore' }, + title: localize2('localHistory.restoreViaPicker', 'Find Entry to Restore'), f1: true, category: LOCAL_HISTORY_CATEGORY }); @@ -402,7 +402,7 @@ registerAction2(class extends Action2 { } }); -MenuRegistry.appendMenuItem(MenuId.TimelineTitle, { command: { id: 'workbench.action.localHistory.restoreViaPicker', title: { value: localize('localHistory.restoreViaPickerMenu', "Local History: Find Entry to Restore..."), original: 'Local History: Find Entry to Restore...' } }, group: 'submenu', order: 1 }); +MenuRegistry.appendMenuItem(MenuId.TimelineTitle, { command: { id: 'workbench.action.localHistory.restoreViaPicker', title: localize2('localHistory.restoreViaPickerMenu', 'Local History: Find Entry to Restore...') }, group: 'submenu', order: 1 }); //#endregion @@ -412,7 +412,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.localHistory.rename', - title: { value: localize('localHistory.rename', "Rename"), original: 'Rename' }, + title: localize2('localHistory.rename', 'Rename'), menu: { id: MenuId.TimelineItemContext, group: '5_edit', @@ -451,7 +451,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.localHistory.delete', - title: { value: localize('localHistory.delete', "Delete"), original: 'Delete' }, + title: localize2('localHistory.delete', 'Delete'), menu: { id: MenuId.TimelineItemContext, group: '5_edit', @@ -497,7 +497,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.localHistory.deleteAll', - title: { value: localize('localHistory.deleteAll', "Delete All"), original: 'Delete All' }, + title: localize2('localHistory.deleteAll', 'Delete All'), f1: true, category: LOCAL_HISTORY_CATEGORY }); @@ -531,7 +531,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.localHistory.create', - title: { value: localize('localHistory.create', "Create Entry"), original: 'Create Entry' }, + title: localize2('localHistory.create', 'Create Entry'), f1: true, category: LOCAL_HISTORY_CATEGORY, precondition: ActiveEditorContext diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index d4c8ef2398c..5d03f79aa92 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -9,7 +9,7 @@ import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/co import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Marker, RelatedInformation, ResourceMarkers } from 'vs/workbench/contrib/markers/browser/markersModel'; import { MarkersView } from 'vs/workbench/contrib/markers/browser/markersView'; import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; @@ -348,7 +348,7 @@ registerAction2(class extends ViewAction { const when = ContextKeyExpr.and(FocusedViewContext.isEqualTo(Markers.MARKERS_VIEW_ID), MarkersContextKeys.MarkersTreeVisibilityContextKey, MarkersContextKeys.RelatedInformationFocusContextKey.toNegated()); super({ id: Markers.MARKER_COPY_ACTION_ID, - title: { value: localize('copyMarker', "Copy"), original: 'Copy' }, + title: localize2('copyMarker', 'Copy'), menu: { id: MenuId.ProblemsPanelContext, when, @@ -388,7 +388,7 @@ registerAction2(class extends ViewAction { constructor() { super({ id: Markers.MARKER_COPY_MESSAGE_ACTION_ID, - title: { value: localize('copyMessage', "Copy Message"), original: 'Copy Message' }, + title: localize2('copyMessage', 'Copy Message'), menu: { id: MenuId.ProblemsPanelContext, when: MarkersContextKeys.MarkerFocusContextKey, @@ -410,7 +410,7 @@ registerAction2(class extends ViewAction { constructor() { super({ id: Markers.RELATED_INFORMATION_COPY_MESSAGE_ACTION_ID, - title: { value: localize('copyMessage', "Copy Message"), original: 'Copy Message' }, + title: localize2('copyMessage', 'Copy Message'), menu: { id: MenuId.ProblemsPanelContext, when: MarkersContextKeys.RelatedInformationFocusContextKey, diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index 1fbeb6a6355..90d23b25a11 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -6,7 +6,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { basename } from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -81,7 +81,7 @@ export class OpenMergeEditor extends Action2 { constructor() { super({ id: '_open.mergeEditor', - title: { value: localize('title', "Open Merge Editor"), original: 'Open Merge Editor' }, + title: localize2('title', 'Open Merge Editor'), }); } run(accessor: ServicesAccessor, ...args: unknown[]): void { @@ -201,7 +201,7 @@ export class SetColumnLayout extends Action2 { constructor() { super({ id: 'merge.columnLayout', - title: { value: localize('layout.column', "Column Layout"), original: 'Column Layout' }, + title: localize2('layout.column', 'Column Layout'), toggled: ctxMergeEditorLayout.isEqualTo('columns'), menu: [{ id: MenuId.EditorTitle, diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts index 0f2b6b56183..8ddbfaf4d96 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts @@ -7,7 +7,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { Codicon } from 'vs/base/common/codicons'; import { URI } from 'vs/base/common/uri'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { Action2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -21,7 +21,7 @@ import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/merge import { ctxIsMergeEditor, MergeEditorContents } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -const MERGE_EDITOR_CATEGORY: ILocalizedString = { value: localize('mergeEditor', "Merge Editor (Dev)"), original: 'Merge Editor (Dev)' }; +const MERGE_EDITOR_CATEGORY: ILocalizedString = localize2('mergeEditor', 'Merge Editor (Dev)'); export class MergeEditorCopyContentsToJSON extends Action2 { constructor() { diff --git a/src/vs/workbench/contrib/mergeEditor/electron-sandbox/devCommands.ts b/src/vs/workbench/contrib/mergeEditor/electron-sandbox/devCommands.ts index 63049439462..e302a2e5a2c 100644 --- a/src/vs/workbench/contrib/mergeEditor/electron-sandbox/devCommands.ts +++ b/src/vs/workbench/contrib/mergeEditor/electron-sandbox/devCommands.ts @@ -8,7 +8,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { randomPath } from 'vs/base/common/extpath'; import { URI } from 'vs/base/common/uri'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { Action2, IAction2Options } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -22,7 +22,7 @@ import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/v import { MergeEditorContents } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -const MERGE_EDITOR_CATEGORY: ILocalizedString = { value: localize('mergeEditor', "Merge Editor (Dev)"), original: 'Merge Editor (Dev)' }; +const MERGE_EDITOR_CATEGORY: ILocalizedString = localize2('mergeEditor', 'Merge Editor (Dev)'); export class MergeEditorOpenContentsFromJSON extends Action2 { constructor() { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts index 1d771edff73..eebfbc32e6e 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -110,7 +110,7 @@ export class GoToFileAction extends Action2 { constructor() { super({ id: 'multiDiffEditor.goToFile', - title: { value: localize('goToFile', "Open File"), original: 'Open File' }, + title: localize2('goToFile', 'Open File'), icon: Codicon.goToFile, precondition: EditorContextKeys.inMultiDiffEditor, menu: { @@ -133,7 +133,7 @@ export class CollapseAllAction extends Action2 { constructor() { super({ id: 'multiDiffEditor.collapseAll', - title: { value: localize('collapseAllDiffs', "Collapse All Diffs"), original: 'Collapse All Diffs' }, + title: localize2('collapseAllDiffs', 'Collapse All Diffs'), icon: Codicon.collapseAll, precondition: ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), ContextKeyExpr.not('multiDiffEditorAllCollapsed')), menu: { @@ -160,7 +160,7 @@ export class ExpandAllAction extends Action2 { constructor() { super({ id: 'multiDiffEditor.expandAll', - title: { value: localize('ExpandAllDiffs', "Expand All Diffs"), original: 'Expand All Diffs' }, + title: localize2('ExpandAllDiffs', 'Expand All Diffs'), icon: Codicon.expandAll, precondition: ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), ContextKeyExpr.has('multiDiffEditorAllCollapsed')), menu: { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts index a80df1911de..694b84d87ee 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -550,7 +550,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.toggleNotebookClipboardLog', - title: { value: localize('toggleNotebookClipboardLog', "Toggle Notebook Clipboard Troubleshooting"), original: 'Toggle Notebook Clipboard Troubleshooting' }, + title: localize2('toggleNotebookClipboardLog', 'Toggle Notebook Clipboard Troubleshooting'), category: Categories.Developer, f1: true }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts index 1e11992065a..f91d2ed8863 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts @@ -13,7 +13,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITextModel } from 'vs/editor/common/model'; import { FindStartFocusAction, getSelectionSearchString, IFindStartOptions, StartFindAction, StartFindReplaceAction } from 'vs/editor/contrib/find/browser/findController'; -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -32,7 +32,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.hideFind', - title: { value: localize('notebookActions.hideFind', "Hide Find in Notebook"), original: 'Hide Find in Notebook' }, + title: localize2('notebookActions.hideFind', 'Hide Find in Notebook'), keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED), primary: KeyCode.Escape, @@ -59,7 +59,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.find', - title: { value: localize('notebookActions.findInNotebook', "Find in Notebook"), original: 'Find in Notebook' }, + title: localize2('notebookActions.findInNotebook', 'Find in Notebook'), keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.or(NOTEBOOK_IS_ACTIVE_EDITOR, INTERACTIVE_WINDOW_IS_ACTIVE_EDITOR), EditorContextKeys.focus.toNegated()), primary: KeyCode.KeyF | KeyMod.CtrlCmd, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/format/formatting.ts b/src/vs/workbench/contrib/notebook/browser/contrib/format/formatting.ts index cfb37c00a5f..7cb9a68437a 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/format/formatting.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/format/formatting.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { CancellationToken } from 'vs/base/common/cancellation'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -38,7 +38,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.format', - title: { value: localize('format.title', "Format Notebook"), original: 'Format Notebook' }, + title: localize2('format.title', 'Format Notebook'), category: NOTEBOOK_ACTIONS_CATEGORY, precondition: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_EDITOR_EDITABLE), keybinding: { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts index a476da25bba..f051ee94662 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -16,7 +16,7 @@ export class ToggleCellToolbarPositionAction extends Action2 { constructor() { super({ id: TOGGLE_CELL_TOOLBAR_POSITION, - title: { value: localize('notebook.toggleCellToolbarPosition', "Toggle Cell Toolbar Position"), original: 'Toggle Cell Toolbar Position' }, + title: localize2('notebook.toggleCellToolbarPosition', 'Toggle Cell Toolbar Position'), menu: [{ id: MenuId.NotebookCellTitle, group: 'View', diff --git a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts index 3b3ed7da385..087c143716c 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI, UriComponents } from 'vs/base/common/uri'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, IAction2Options, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -25,7 +25,7 @@ import { isEqual } from 'vs/base/common/resources'; // Kernel Command export const SELECT_KERNEL_ID = '_notebook.selectKernel'; -export const NOTEBOOK_ACTIONS_CATEGORY = { value: localize('notebookActions.category', "Notebook"), original: 'Notebook' }; +export const NOTEBOOK_ACTIONS_CATEGORY = localize2('notebookActions.category', 'Notebook'); export const CELL_TITLE_CELL_GROUP_ID = 'inline/cell'; export const CELL_TITLE_OUTPUT_GROUP_ID = 'inline/output'; diff --git a/src/vs/workbench/contrib/notebook/browser/controller/foldingController.ts b/src/vs/workbench/contrib/notebook/browser/controller/foldingController.ts index 292b2d77744..2d7c6dee573 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/foldingController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/foldingController.ts @@ -17,7 +17,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { NOTEBOOK_ACTIONS_CATEGORY } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { FoldingRegion } from 'vs/editor/contrib/folding/browser/foldingRanges'; import { ICommandMetadata } from 'vs/platform/commands/common/commands'; import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; @@ -183,7 +183,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.fold', - title: { value: localize('fold.cell', "Fold Cell"), original: 'Fold Cell' }, + title: localize2('fold.cell', "Fold Cell"), category: NOTEBOOK_ACTIONS_CATEGORY, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), diff --git a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts index 8a168cc34e1..cbe53c348cf 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts @@ -5,7 +5,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; @@ -124,7 +124,7 @@ registerAction2(class ToggleLineNumberFromEditorTitle extends Action2 { constructor() { super({ id: 'notebook.toggleLineNumbersFromEditorTitle', - title: { value: localize('notebook.toggleLineNumbers', "Toggle Notebook Line Numbers"), original: 'Toggle Notebook Line Numbers' }, + title: localize2('notebook.toggleLineNumbers', 'Toggle Notebook Line Numbers'), precondition: NOTEBOOK_EDITOR_FOCUSED, menu: [ { @@ -151,7 +151,7 @@ registerAction2(class ToggleCellToolbarPositionFromEditorTitle extends Action2 { constructor() { super({ id: 'notebook.toggleCellToolbarPositionFromEditorTitle', - title: { value: localize('notebook.toggleCellToolbarPosition', "Toggle Cell Toolbar Position"), original: 'Toggle Cell Toolbar Position' }, + title: localize2('notebook.toggleCellToolbarPosition', 'Toggle Cell Toolbar Position'), menu: [{ id: MenuId.NotebookEditorLayoutConfigure, group: 'notebookLayoutDetails', @@ -171,7 +171,7 @@ registerAction2(class ToggleBreadcrumbFromEditorTitle extends Action2 { constructor() { super({ id: 'breadcrumbs.toggleFromEditorTitle', - title: { value: localize('notebook.toggleBreadcrumb', "Toggle Breadcrumbs"), original: 'Toggle Breadcrumbs' }, + title: localize2('notebook.toggleBreadcrumb', 'Toggle Breadcrumbs'), menu: [{ id: MenuId.NotebookEditorLayoutConfigure, group: 'notebookLayoutDetails', diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts index 74573468268..f7cfcf23fec 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; @@ -31,7 +31,7 @@ registerAction2(class extends Action2 { super({ id: 'notebook.diff.switchToText', icon: openAsTextIcon, - title: { value: localize('notebook.diff.switchToText', "Open Text Diff Editor"), original: 'Open Text Diff Editor' }, + title: localize2('notebook.diff.switchToText', 'Open Text Diff Editor'), precondition: ActiveEditorContext.isEqualTo(NotebookTextDiffEditor.ID), menu: [{ id: MenuId.EditorTitle, @@ -252,7 +252,7 @@ class ToggleRenderAction extends Action2 { registerAction2(class extends ToggleRenderAction { constructor() { super('notebook.diff.showOutputs', - { value: localize('notebook.diff.showOutputs', "Show Outputs Differences"), original: 'Show Outputs Differences' }, + localize2('notebook.diff.showOutputs', 'Show Outputs Differences'), ActiveEditorContext.isEqualTo(NotebookTextDiffEditor.ID), ContextKeyExpr.notEquals('config.notebook.diff.ignoreOutputs', true), 2, @@ -265,7 +265,7 @@ registerAction2(class extends ToggleRenderAction { registerAction2(class extends ToggleRenderAction { constructor() { super('notebook.diff.showMetadata', - { value: localize('notebook.diff.showMetadata', "Show Metadata Differences"), original: 'Show Metadata Differences' }, + localize2('notebook.diff.showMetadata', 'Show Metadata Differences'), ActiveEditorContext.isEqualTo(NotebookTextDiffEditor.ID), ContextKeyExpr.notEquals('config.notebook.diff.ignoreMetadata', true), 1, diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts index 5a708473622..0804f29c3ac 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts @@ -6,7 +6,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; @@ -134,7 +134,7 @@ registerAction2(class ToggleLineNumberAction extends Action2 { constructor() { super({ id: 'notebook.toggleLineNumbers', - title: { value: localize('notebook.toggleLineNumbers', "Toggle Notebook Line Numbers"), original: 'Toggle Notebook Line Numbers' }, + title: localize2('notebook.toggleLineNumbers', 'Toggle Notebook Line Numbers'), precondition: NOTEBOOK_EDITOR_FOCUSED, menu: [ { diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts index f0831e1bc10..64bd903fae6 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts @@ -6,7 +6,7 @@ import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { Action, IAction } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -44,7 +44,7 @@ registerAction2(class extends Action2 { super({ id: SELECT_KERNEL_ID, category: NOTEBOOK_ACTIONS_CATEGORY, - title: { value: localize('notebookActions.selectKernel', "Select Notebook Kernel"), original: 'Select Notebook Kernel' }, + title: localize2('notebookActions.selectKernel', 'Select Notebook Kernel'), icon: selectKernelIcon, f1: true, precondition: NOTEBOOK_IS_ACTIVE_EDITOR, diff --git a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts index 5c2fc5fd2ef..1a32b7dc306 100644 --- a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts +++ b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -45,7 +45,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'perfview.show', - title: { value: localize('show.label', "Startup Performance"), original: 'Startup Performance' }, + title: localize2('show.label', 'Startup Performance'), category: Categories.Developer, f1: true }); @@ -64,7 +64,7 @@ registerAction2(class PrintServiceCycles extends Action2 { constructor() { super({ id: 'perf.insta.printAsyncCycles', - title: { value: localize('cycles', "Print Service Cycles"), original: 'Print Service Cycles' }, + title: localize2('cycles', 'Print Service Cycles'), category: Categories.Developer, f1: true }); @@ -88,7 +88,7 @@ registerAction2(class PrintServiceTraces extends Action2 { constructor() { super({ id: 'perf.insta.printTraces', - title: { value: localize('insta.trace', "Print Service Traces"), original: 'Print Service Traces' }, + title: localize2('insta.trace', 'Print Service Traces'), category: Categories.Developer, f1: true }); @@ -112,7 +112,7 @@ registerAction2(class PrintEventProfiling extends Action2 { constructor() { super({ id: 'perf.event.profiling', - title: { value: localize('emitter', "Print Emitter Profiles"), original: 'Print Emitter Profiles' }, + title: localize2('emitter', 'Print Emitter Profiles'), category: Categories.Developer, f1: true }); diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index 5c37664c654..ac98c4c386f 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ICommandQuickPick, CommandsHistory } from 'vs/platform/quickinput/browser/commandsQuickAccess'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IMenuService, MenuId, MenuItemAction, SubmenuItemAction, Action2 } from 'vs/platform/actions/common/actions'; @@ -265,7 +265,7 @@ export class ShowAllCommandsAction extends Action2 { constructor() { super({ id: ShowAllCommandsAction.ID, - title: { value: localize('showTriggerActions', "Show All Commands"), original: 'Show All Commands' }, + title: localize2('showTriggerActions', 'Show All Commands'), keybinding: { weight: KeybindingWeight.WorkbenchContrib, when: undefined, @@ -286,7 +286,7 @@ export class ClearCommandHistoryAction extends Action2 { constructor() { super({ id: 'workbench.action.clearCommandHistory', - title: { value: localize('clearCommandHistory', "Clear Command History"), original: 'Clear Command History' }, + title: localize2('clearCommandHistory', 'Clear Command History'), f1: true }); } diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index 3f7e8e9147c..77bc27042a5 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IQuickPickSeparator, IQuickInputService, ItemActivation } from 'vs/platform/quickinput/common/quickInput'; import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { IViewDescriptorService, IViewsService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; @@ -232,7 +232,7 @@ export class OpenViewPickerAction extends Action2 { constructor() { super({ id: OpenViewPickerAction.ID, - title: { value: localize('openView', "Open View"), original: 'Open View' }, + title: localize2('openView', 'Open View'), category: Categories.View, f1: true }); @@ -255,7 +255,7 @@ export class QuickAccessViewPickerAction extends Action2 { constructor() { super({ id: QuickAccessViewPickerAction.ID, - title: { value: localize('quickOpenView', "Quick Open View"), original: 'Quick Open View' }, + title: localize2('quickOpenView', 'Quick Open View'), category: Categories.View, f1: false, // hide quick pickers from command palette to not confuse with the other entry that shows a input field keybinding: { diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index b52ace6c365..6108eeacda4 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -11,7 +11,7 @@ import { OperatingSystem, isWeb, OS } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { IRemoteAgentService, remoteConnectionLatencyMeasurer } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ILoggerService } from 'vs/platform/log/common/log'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; @@ -241,7 +241,7 @@ if (enableDiagnostics) { constructor() { super({ id: 'workbench.action.triggerReconnect', - title: { value: localize('triggerReconnect', "Connection: Trigger Reconnect"), original: 'Connection: Trigger Reconnect' }, + title: localize2('triggerReconnect', 'Connection: Trigger Reconnect'), category: Categories.Developer, f1: true, }); @@ -256,7 +256,7 @@ if (enableDiagnostics) { constructor() { super({ id: 'workbench.action.pauseSocketWriting', - title: { value: localize('pauseSocketWriting', "Connection: Pause socket writing"), original: 'Connection: Pause socket writing' }, + title: localize2('pauseSocketWriting', 'Connection: Pause socket writing'), category: Categories.Developer, f1: true, }); diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 2cdc8781436..f5cf500d5be 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -48,7 +48,7 @@ const sourceControlViewIcon = registerIcon('source-control-view-icon', Codicon.s const viewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, - title: { value: localize('source control', "Source Control"), original: 'Source Control' }, + title: localize2('source control', 'Source Control'), ctorDescriptor: new SyncDescriptor(SCMViewPaneContainer), storageId: 'workbench.scm.views.state', icon: sourceControlViewIcon, @@ -76,7 +76,7 @@ viewsRegistry.registerViewWelcomeContent(VIEW_PANE_ID, { viewsRegistry.registerViews([{ id: VIEW_PANE_ID, - name: localize2('source control', "Source Control"), + name: localize2('source control', 'Source Control'), ctorDescriptor: new SyncDescriptor(SCMViewPane), canToggleVisibility: true, canMoveView: true, diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 1457698d8a4..0317a3588ef 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { Range } from 'vs/editor/common/core/range'; import { ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/browser/findModel'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -169,7 +169,7 @@ CommandsRegistry.registerCommand( //#endregion //#region Actions -const category = { value: localize('search', "Search Editor"), original: 'Search Editor' }; +const category = localize2('search', 'Search Editor'); export type LegacySearchEditorArgs = Partial<{ query: string; @@ -230,7 +230,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'search.searchEditor.action.deleteFileResults', - title: { value: localize('searchEditor.deleteResultBlock', "Delete File Results"), original: 'Delete File Results' }, + title: localize2('searchEditor.deleteResultBlock', 'Delete File Results'), keybinding: { weight: KeybindingWeight.EditorContrib, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Backspace, @@ -253,7 +253,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: SearchEditorConstants.OpenNewEditorCommandId, - title: { value: localize('search.openNewSearchEditor', "New Search Editor"), original: 'New Search Editor' }, + title: localize2('search.openNewSearchEditor', 'New Search Editor'), category, f1: true, metadata: openArgMetadata @@ -268,7 +268,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: SearchEditorConstants.OpenEditorCommandId, - title: { value: localize('search.openSearchEditor', "Open Search Editor"), original: 'Open Search Editor' }, + title: localize2('search.openSearchEditor', 'Open Search Editor'), category, f1: true, metadata: openArgMetadata @@ -283,7 +283,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: OpenNewEditorToSideCommandId, - title: { value: localize('search.openNewEditorToSide', "Open New Search Editor to the Side"), original: 'Open new Search Editor to the Side' }, + title: localize2('search.openNewEditorToSide', 'Open New Search Editor to the Side'), category, f1: true, metadata: openArgMetadata @@ -298,7 +298,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: OpenInEditorCommandId, - title: { value: localize('search.openResultsInEditor', "Open Results in Editor"), original: 'Open Results in Editor' }, + title: localize2('search.openResultsInEditor', 'Open Results in Editor'), category, f1: true, keybinding: { @@ -325,7 +325,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: RerunSearchEditorSearchCommandId, - title: { value: localize('search.rerunSearchInEditor', "Search Again"), original: 'Search Again' }, + title: localize2('search.rerunSearchInEditor', 'Search Again'), category, keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyR, @@ -357,7 +357,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: FocusQueryEditorWidgetCommandId, - title: { value: localize('search.action.focusQueryEditorWidget', "Focus Search Editor Input"), original: 'Focus Search Editor Input' }, + title: localize2('search.action.focusQueryEditorWidget', 'Focus Search Editor Input'), category, f1: true, precondition: SearchEditorConstants.InSearchEditor, @@ -380,7 +380,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: FocusQueryEditorFilesToIncludeCommandId, - title: { value: localize('search.action.focusFilesToInclude', "Focus Search Editor Files to Include"), original: 'Focus Search Editor Files to Include' }, + title: localize2('search.action.focusFilesToInclude', 'Focus Search Editor Files to Include'), category, f1: true, precondition: SearchEditorConstants.InSearchEditor, @@ -399,7 +399,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: FocusQueryEditorFilesToExcludeCommandId, - title: { value: localize('search.action.focusFilesToExclude', "Focus Search Editor Files to Exclude"), original: 'Focus Search Editor Files to Exclude' }, + title: localize2('search.action.focusFilesToExclude', 'Focus Search Editor Files to Exclude'), category, f1: true, precondition: SearchEditorConstants.InSearchEditor, @@ -418,7 +418,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: ToggleSearchEditorCaseSensitiveCommandId, - title: { value: localize('searchEditor.action.toggleSearchEditorCaseSensitive', "Toggle Match Case"), original: 'Toggle Match Case' }, + title: localize2('searchEditor.action.toggleSearchEditorCaseSensitive', 'Toggle Match Case'), category, f1: true, precondition: SearchEditorConstants.InSearchEditor, @@ -437,7 +437,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: ToggleSearchEditorWholeWordCommandId, - title: { value: localize('searchEditor.action.toggleSearchEditorWholeWord', "Toggle Match Whole Word"), original: 'Toggle Match Whole Word' }, + title: localize2('searchEditor.action.toggleSearchEditorWholeWord', 'Toggle Match Whole Word'), category, f1: true, precondition: SearchEditorConstants.InSearchEditor, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index a169a4b3154..5febebadd1b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -14,7 +14,7 @@ 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'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { Action2, registerAction2, IAction2Options, MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -305,7 +305,7 @@ function getTerminalServices(accessor: ServicesAccessor): ITerminalServicesColle export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.NewInActiveWorkspace, - title: { value: localize('workbench.action.terminal.newInActiveWorkspace', "Create New Terminal (In Active Workspace)"), original: 'Create New Terminal (In Active Workspace)' }, + title: localize2('workbench.action.terminal.newInActiveWorkspace', 'Create New Terminal (In Active Workspace)'), run: async (c) => { if (c.service.isProcessSupportRegistered) { const instance = await c.service.createTerminal({ location: c.service.defaultLocation }); @@ -323,7 +323,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.CreateTerminalEditor, - title: { value: localize('workbench.action.terminal.createTerminalEditor', "Create New Terminal in Editor Area"), original: 'Create New Terminal in Editor Area' }, + title: localize2('workbench.action.terminal.createTerminalEditor', 'Create New Terminal in Editor Area'), run: async (c, _, args) => { const options = (isObject(args) && 'location' in args) ? args as ICreateTerminalOptions : { location: TerminalLocation.Editor }; const instance = await c.service.createTerminal(options); @@ -333,7 +333,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.CreateTerminalEditorSameGroup, - title: { value: localize('workbench.action.terminal.createTerminalEditor', "Create New Terminal in Editor Area"), original: 'Create New Terminal in Editor Area' }, + title: localize2('workbench.action.terminal.createTerminalEditor', 'Create New Terminal in Editor Area'), f1: false, run: async (c, accessor, args) => { // Force the editor into the same editor group if it's locked. This command is only ever @@ -348,7 +348,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.CreateTerminalEditorSide, - title: { value: localize('workbench.action.terminal.createTerminalEditorSide', "Create New Terminal in Editor Area to the Side"), original: 'Create New Terminal in Editor Area to the Side' }, + title: localize2('workbench.action.terminal.createTerminalEditorSide', 'Create New Terminal in Editor Area to the Side'), run: async (c) => { const instance = await c.service.createTerminal({ location: { viewColumn: SIDE_GROUP } @@ -388,7 +388,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.FocusPreviousPane, - title: { value: localize('workbench.action.terminal.focusPreviousPane', "Focus Previous Terminal in Terminal Group"), original: 'Focus Previous Terminal in Terminal Group' }, + title: localize2('workbench.action.terminal.focusPreviousPane', 'Focus Previous Terminal in Terminal Group'), keybinding: { primary: KeyMod.Alt | KeyCode.LeftArrow, secondary: [KeyMod.Alt | KeyCode.UpArrow], @@ -408,7 +408,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.FocusNextPane, - title: { value: localize('workbench.action.terminal.focusNextPane', "Focus Next Terminal in Terminal Group"), original: 'Focus Next Terminal in Terminal Group' }, + title: localize2('workbench.action.terminal.focusNextPane', 'Focus Next Terminal in Terminal Group'), keybinding: { primary: KeyMod.Alt | KeyCode.RightArrow, secondary: [KeyMod.Alt | KeyCode.DownArrow], @@ -428,7 +428,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.RunRecentCommand, - title: { value: localize('workbench.action.terminal.runRecentCommand', "Run Recent Command..."), original: 'Run Recent Command...' }, + title: localize2('workbench.action.terminal.runRecentCommand', 'Run Recent Command...'), precondition: sharedWhenClause.terminalAvailable, keybinding: [ { @@ -516,7 +516,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.GoToRecentDirectory, - title: { value: localize('workbench.action.terminal.goToRecentDirectory', "Go to Recent Directory..."), original: 'Go to Recent Directory...' }, + title: localize2('workbench.action.terminal.goToRecentDirectory', 'Go to Recent Directory...'), precondition: sharedWhenClause.terminalAvailable, keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyG, @@ -535,7 +535,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.ResizePaneLeft, - title: { value: localize('workbench.action.terminal.resizePaneLeft', "Resize Terminal Left"), original: 'Resize Terminal Left' }, + title: localize2('workbench.action.terminal.resizePaneLeft', 'Resize Terminal Left'), keybinding: { linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow }, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow }, @@ -548,7 +548,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.ResizePaneRight, - title: { value: localize('workbench.action.terminal.resizePaneRight', "Resize Terminal Right"), original: 'Resize Terminal Right' }, + title: localize2('workbench.action.terminal.resizePaneRight', 'Resize Terminal Right'), keybinding: { linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow }, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow }, @@ -561,7 +561,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.ResizePaneUp, - title: { value: localize('workbench.action.terminal.resizePaneUp', "Resize Terminal Up"), original: 'Resize Terminal Up' }, + title: localize2('workbench.action.terminal.resizePaneUp', 'Resize Terminal Up'), keybinding: { mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.UpArrow }, when: TerminalContextKeys.focus, @@ -573,7 +573,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.ResizePaneDown, - title: { value: localize('workbench.action.terminal.resizePaneDown', "Resize Terminal Down"), original: 'Resize Terminal Down' }, + title: localize2('workbench.action.terminal.resizePaneDown', 'Resize Terminal Down'), keybinding: { mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.DownArrow }, when: TerminalContextKeys.focus, @@ -604,7 +604,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.FocusTabs, - title: { value: localize('workbench.action.terminal.focus.tabsView', "Focus Terminal Tabs View"), original: 'Focus Terminal Tabs View' }, + title: localize2('workbench.action.terminal.focus.tabsView', 'Focus Terminal Tabs View'), keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Backslash, weight: KeybindingWeight.WorkbenchContrib, @@ -616,7 +616,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.FocusNext, - title: { value: localize('workbench.action.terminal.focusNext', "Focus Next Terminal Group"), original: 'Focus Next Terminal Group' }, + title: localize2('workbench.action.terminal.focusNext', 'Focus Next Terminal Group'), precondition: sharedWhenClause.terminalAvailable, keybinding: { primary: KeyMod.CtrlCmd | KeyCode.PageDown, @@ -634,7 +634,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.FocusPrevious, - title: { value: localize('workbench.action.terminal.focusPrevious', "Focus Previous Terminal Group"), original: 'Focus Previous Terminal Group' }, + title: localize2('workbench.action.terminal.focusPrevious', 'Focus Previous Terminal Group'), precondition: sharedWhenClause.terminalAvailable, keybinding: { primary: KeyMod.CtrlCmd | KeyCode.PageUp, @@ -652,7 +652,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.RunSelectedText, - title: { value: localize('workbench.action.terminal.runSelectedText', "Run Selected Text In Active Terminal"), original: 'Run Selected Text In Active Terminal' }, + title: localize2('workbench.action.terminal.runSelectedText', 'Run Selected Text In Active Terminal'), run: async (c, accessor) => { const codeEditorService = accessor.get(ICodeEditorService); const editor = codeEditorService.getActiveCodeEditor(); @@ -675,7 +675,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.RunActiveFile, - title: { value: localize('workbench.action.terminal.runActiveFile', "Run Active File In Active Terminal"), original: 'Run Active File In Active Terminal' }, + title: localize2('workbench.action.terminal.runActiveFile', 'Run Active File In Active Terminal'), precondition: sharedWhenClause.terminalAvailable, run: async (c, accessor) => { const codeEditorService = accessor.get(ICodeEditorService); @@ -703,7 +703,7 @@ export function registerTerminalActions() { registerActiveXtermAction({ id: TerminalCommandId.ScrollDownLine, - title: { value: localize('workbench.action.terminal.scrollDown', "Scroll Down (Line)"), original: 'Scroll Down (Line)' }, + title: localize2('workbench.action.terminal.scrollDown', 'Scroll Down (Line)'), keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow }, @@ -716,7 +716,7 @@ export function registerTerminalActions() { registerActiveXtermAction({ id: TerminalCommandId.ScrollDownPage, - title: { value: localize('workbench.action.terminal.scrollDownPage', "Scroll Down (Page)"), original: 'Scroll Down (Page)' }, + title: localize2('workbench.action.terminal.scrollDownPage', 'Scroll Down (Page)'), keybinding: { primary: KeyMod.Shift | KeyCode.PageDown, mac: { primary: KeyCode.PageDown }, @@ -729,7 +729,7 @@ export function registerTerminalActions() { registerActiveXtermAction({ id: TerminalCommandId.ScrollToBottom, - title: { value: localize('workbench.action.terminal.scrollToBottom', "Scroll to Bottom"), original: 'Scroll to Bottom' }, + title: localize2('workbench.action.terminal.scrollToBottom', 'Scroll to Bottom'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.End, linux: { primary: KeyMod.Shift | KeyCode.End }, @@ -742,7 +742,7 @@ export function registerTerminalActions() { registerActiveXtermAction({ id: TerminalCommandId.ScrollUpLine, - title: { value: localize('workbench.action.terminal.scrollUp', "Scroll Up (Line)"), original: 'Scroll Up (Line)' }, + title: localize2('workbench.action.terminal.scrollUp', 'Scroll Up (Line)'), keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow }, @@ -755,7 +755,7 @@ export function registerTerminalActions() { registerActiveXtermAction({ id: TerminalCommandId.ScrollUpPage, - title: { value: localize('workbench.action.terminal.scrollUpPage', "Scroll Up (Page)"), original: 'Scroll Up (Page)' }, + title: localize2('workbench.action.terminal.scrollUpPage', 'Scroll Up (Page)'), f1: true, category, keybinding: { @@ -770,7 +770,7 @@ export function registerTerminalActions() { registerActiveXtermAction({ id: TerminalCommandId.ScrollToTop, - title: { value: localize('workbench.action.terminal.scrollToTop', "Scroll to Top"), original: 'Scroll to Top' }, + title: localize2('workbench.action.terminal.scrollToTop', 'Scroll to Top'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.Home, linux: { primary: KeyMod.Shift | KeyCode.Home }, @@ -783,7 +783,7 @@ export function registerTerminalActions() { registerActiveXtermAction({ id: TerminalCommandId.ClearSelection, - title: { value: localize('workbench.action.terminal.clearSelection', "Clear Selection"), original: 'Clear Selection' }, + title: localize2('workbench.action.terminal.clearSelection', 'Clear Selection'), keybinding: { primary: KeyCode.Escape, when: ContextKeyExpr.and(TerminalContextKeys.focusInAny, TerminalContextKeys.textSelected, TerminalContextKeys.notFindVisible), @@ -901,13 +901,13 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.DetachSession, - title: { value: localize('workbench.action.terminal.detachSession', "Detach Session"), original: 'Detach Session' }, + title: localize2('workbench.action.terminal.detachSession', 'Detach Session'), run: (activeInstance) => activeInstance.detachProcessAndDispose(TerminalExitReason.User) }); registerTerminalAction({ id: TerminalCommandId.AttachToSession, - title: { value: localize('workbench.action.terminal.attachToSession', "Attach to Session"), original: 'Attach to Session' }, + title: localize2('workbench.action.terminal.attachToSession', 'Attach to Session'), run: async (c, accessor) => { const quickInputService = accessor.get(IQuickInputService); const labelService = accessor.get(ILabelService); @@ -952,7 +952,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.QuickOpenTerm, - title: { value: localize('quickAccessTerminal', "Switch Active Terminal"), original: 'Switch Active Terminal' }, + title: localize2('quickAccessTerminal', 'Switch Active Terminal'), precondition: sharedWhenClause.terminalAvailable, run: (c, accessor) => accessor.get(IQuickInputService).quickAccess.show(TerminalQuickAccessProvider.PREFIX) }); @@ -1006,7 +1006,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.SelectToPreviousCommand, - title: { value: localize('workbench.action.terminal.selectToPreviousCommand', "Select To Previous Command"), original: 'Select To Previous Command' }, + title: localize2('workbench.action.terminal.selectToPreviousCommand', 'Select To Previous Command'), keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow, when: TerminalContextKeys.focus, @@ -1021,7 +1021,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.SelectToNextCommand, - title: { value: localize('workbench.action.terminal.selectToNextCommand', "Select To Next Command"), original: 'Select To Next Command' }, + title: localize2('workbench.action.terminal.selectToNextCommand', 'Select To Next Command'), keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow, when: TerminalContextKeys.focus, @@ -1036,7 +1036,7 @@ export function registerTerminalActions() { registerActiveXtermAction({ id: TerminalCommandId.SelectToPreviousLine, - title: { value: localize('workbench.action.terminal.selectToPreviousLine', "Select To Previous Line"), original: 'Select To Previous Line' }, + title: localize2('workbench.action.terminal.selectToPreviousLine', 'Select To Previous Line'), precondition: sharedWhenClause.terminalAvailable, run: async (xterm, _, instance) => { xterm.markTracker.selectToPreviousLine(); @@ -1047,7 +1047,7 @@ export function registerTerminalActions() { registerActiveXtermAction({ id: TerminalCommandId.SelectToNextLine, - title: { value: localize('workbench.action.terminal.selectToNextLine', "Select To Next Line"), original: 'Select To Next Line' }, + title: localize2('workbench.action.terminal.selectToNextLine', 'Select To Next Line'), precondition: sharedWhenClause.terminalAvailable, run: async (xterm, _, instance) => { xterm.markTracker.selectToNextLine(); @@ -1143,7 +1143,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.Relaunch, - title: { value: localize('workbench.action.terminal.relaunch', "Relaunch Active Terminal"), original: 'Relaunch Active Terminal' }, + title: localize2('workbench.action.terminal.relaunch', 'Relaunch Active Terminal'), run: (activeInstance) => activeInstance.relaunch() }); @@ -1221,7 +1221,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.JoinActiveTab, - title: { value: localize('workbench.action.terminal.joinInstance', "Join Terminals"), original: 'Join Terminals' }, + title: localize2('workbench.action.terminal.joinInstance', 'Join Terminals'), precondition: ContextKeyExpr.and(sharedWhenClause.terminalAvailable, TerminalContextKeys.tabsSingularSelection.toNegated()), run: async (c, accessor) => { const instances = getSelectedInstances(accessor); @@ -1233,7 +1233,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.Join, - title: { value: localize('workbench.action.terminal.join', "Join Terminals"), original: 'Join Terminals' }, + title: localize2('workbench.action.terminal.join', 'Join Terminals'), precondition: sharedWhenClause.terminalAvailable, run: async (c, accessor) => { const themeService = accessor.get(IThemeService); @@ -1280,7 +1280,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.SplitInActiveWorkspace, - title: { value: localize('workbench.action.terminal.splitInActiveWorkspace', "Split Terminal (In Active Workspace)"), original: 'Split Terminal (In Active Workspace)' }, + title: localize2('workbench.action.terminal.splitInActiveWorkspace', 'Split Terminal (In Active Workspace)'), run: async (instance, c) => { const newInstance = await c.service.createTerminal({ location: { parentTerminal: instance } }); if (newInstance?.target !== TerminalLocation.Editor) { @@ -1291,7 +1291,7 @@ export function registerTerminalActions() { registerActiveXtermAction({ id: TerminalCommandId.SelectAll, - title: { value: localize('workbench.action.terminal.selectAll', "Select All"), original: 'Select All' }, + title: localize2('workbench.action.terminal.selectAll', 'Select All'), precondition: sharedWhenClause.terminalAvailable, keybinding: [{ // Don't use ctrl+a by default as that would override the common go to start @@ -1309,7 +1309,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.New, - title: { value: localize('workbench.action.terminal.new', "Create New Terminal"), original: 'Create New Terminal' }, + title: localize2('workbench.action.terminal.new', 'Create New Terminal'), precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.webExtensionContributedProfile), icon: newTerminalIcon, keybinding: { @@ -1367,7 +1367,7 @@ export function registerTerminalActions() { } registerTerminalAction({ id: TerminalCommandId.Kill, - title: { value: localize('workbench.action.terminal.kill', "Kill the Active Terminal Instance"), original: 'Kill the Active Terminal Instance' }, + title: localize2('workbench.action.terminal.kill', 'Kill the Active Terminal Instance'), precondition: ContextKeyExpr.or(sharedWhenClause.terminalAvailable, TerminalContextKeys.isOpen), icon: killTerminalIcon, run: async (c) => killInstance(c, c.groupService.activeInstance) @@ -1382,7 +1382,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.KillAll, - title: { value: localize('workbench.action.terminal.killAll', "Kill All Terminals"), original: 'Kill All Terminals' }, + title: localize2('workbench.action.terminal.killAll', 'Kill All Terminals'), precondition: ContextKeyExpr.or(sharedWhenClause.terminalAvailable, TerminalContextKeys.isOpen), icon: Codicon.trash, run: async (c) => { @@ -1396,7 +1396,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.KillEditor, - title: { value: localize('workbench.action.terminal.killEditor', "Kill the Active Terminal in Editor Area"), original: 'Kill the Active Terminal in Editor Area' }, + title: localize2('workbench.action.terminal.killEditor', 'Kill the Active Terminal in Editor Area'), precondition: sharedWhenClause.terminalAvailable, keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyW, @@ -1445,7 +1445,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.Clear, - title: { value: localize('workbench.action.terminal.clear', "Clear"), original: 'Clear' }, + title: localize2('workbench.action.terminal.clear', 'Clear'), precondition: sharedWhenClause.terminalAvailable, keybinding: [{ primary: 0, @@ -1462,20 +1462,20 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.SelectDefaultProfile, - title: { value: localize('workbench.action.terminal.selectDefaultShell', "Select Default Profile"), original: 'Select Default Profile' }, + title: localize2('workbench.action.terminal.selectDefaultShell', 'Select Default Profile'), run: (c) => c.service.showProfileQuickPick('setDefault') }); registerTerminalAction({ id: TerminalCommandId.ConfigureTerminalSettings, - title: { value: localize('workbench.action.terminal.openSettings', "Configure Terminal Settings"), original: 'Configure Terminal Settings' }, + title: localize2('workbench.action.terminal.openSettings', 'Configure Terminal Settings'), precondition: sharedWhenClause.terminalAvailable, run: (c, accessor) => accessor.get(IPreferencesService).openSettings({ jsonEditor: false, query: '@feature:terminal' }) }); registerActiveInstanceAction({ id: TerminalCommandId.SetDimensions, - title: { value: localize('workbench.action.terminal.setFixedDimensions', "Set Fixed Dimensions"), original: 'Set Fixed Dimensions' }, + title: localize2('workbench.action.terminal.setFixedDimensions', 'Set Fixed Dimensions'), precondition: sharedWhenClause.terminalAvailable_and_opened, run: (activeInstance) => activeInstance.setFixedDimensions() }); @@ -1494,7 +1494,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.ClearPreviousSessionHistory, - title: { value: localize('workbench.action.terminal.clearPreviousSessionHistory', "Clear Previous Session History"), original: 'Clear Previous Session History' }, + title: localize2('workbench.action.terminal.clearPreviousSessionHistory', 'Clear Previous Session History'), precondition: sharedWhenClause.terminalAvailable, run: async (c, accessor) => { getCommandHistory(accessor).clear(); @@ -1504,7 +1504,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.ToggleStickyScroll, - title: { value: localize('workbench.action.terminal.toggleStickyScroll', "Toggle Sticky Scroll"), original: 'Toggle Sticky Scroll' }, + title: localize2('workbench.action.terminal.toggleStickyScroll', 'Toggle Sticky Scroll'), toggled: { condition: ContextKeyExpr.equals('config.terminal.integrated.stickyScroll.enabled', true), title: localize('stickyScroll', "Sticky Scroll"), @@ -1524,7 +1524,7 @@ export function registerTerminalActions() { if (BrowserFeatures.clipboard.writeText) { registerActiveXtermAction({ id: TerminalCommandId.CopySelection, - title: { value: localize('workbench.action.terminal.copySelection', "Copy Selection"), original: 'Copy Selection' }, + title: localize2('workbench.action.terminal.copySelection', 'Copy Selection'), // TODO: Why is copy still showing up when text isn't selected? precondition: ContextKeyExpr.or(TerminalContextKeys.textSelectedInFocused, ContextKeyExpr.and(sharedWhenClause.terminalAvailable, TerminalContextKeys.textSelected)), keybinding: [{ @@ -1541,7 +1541,7 @@ export function registerTerminalActions() { registerActiveXtermAction({ id: TerminalCommandId.CopyAndClearSelection, - title: { value: localize('workbench.action.terminal.copyAndClearSelection', "Copy and Clear Selection"), original: 'Copy and Clear Selection' }, + title: localize2('workbench.action.terminal.copyAndClearSelection', 'Copy and Clear Selection'), precondition: ContextKeyExpr.or(TerminalContextKeys.textSelectedInFocused, ContextKeyExpr.and(sharedWhenClause.terminalAvailable, TerminalContextKeys.textSelected)), keybinding: [{ win: { primary: KeyMod.CtrlCmd | KeyCode.KeyC }, @@ -1559,7 +1559,7 @@ export function registerTerminalActions() { registerActiveXtermAction({ id: TerminalCommandId.CopySelectionAsHtml, - title: { value: localize('workbench.action.terminal.copySelectionAsHtml', "Copy Selection as HTML"), original: 'Copy Selection as HTML' }, + title: localize2('workbench.action.terminal.copySelectionAsHtml', 'Copy Selection as HTML'), f1: true, category, precondition: ContextKeyExpr.or(TerminalContextKeys.textSelectedInFocused, ContextKeyExpr.and(sharedWhenClause.terminalAvailable, TerminalContextKeys.textSelected)), @@ -1570,7 +1570,7 @@ export function registerTerminalActions() { if (BrowserFeatures.clipboard.readText) { registerActiveInstanceAction({ id: TerminalCommandId.Paste, - title: { value: localize('workbench.action.terminal.paste', "Paste into Active Terminal"), original: 'Paste into Active Terminal' }, + title: localize2('workbench.action.terminal.paste', 'Paste into Active Terminal'), precondition: sharedWhenClause.terminalAvailable, keybinding: [{ primary: KeyMod.CtrlCmd | KeyCode.KeyV, @@ -1586,7 +1586,7 @@ export function registerTerminalActions() { if (BrowserFeatures.clipboard.readText && isLinux) { registerActiveInstanceAction({ id: TerminalCommandId.PasteSelection, - title: { value: localize('workbench.action.terminal.pasteSelection', "Paste Selection into Active Terminal"), original: 'Paste Selection into Active Terminal' }, + title: localize2('workbench.action.terminal.pasteSelection', 'Paste Selection into Active Terminal'), precondition: sharedWhenClause.terminalAvailable, keybinding: [{ linux: { primary: KeyMod.Shift | KeyCode.Insert }, @@ -1599,7 +1599,7 @@ export function registerTerminalActions() { registerTerminalAction({ id: TerminalCommandId.SwitchTerminal, - title: { value: localize('workbench.action.terminal.switchTerminal', "Switch Terminal"), original: 'Switch Terminal' }, + title: localize2('workbench.action.terminal.switchTerminal', 'Switch Terminal'), precondition: sharedWhenClause.terminalAvailable, run: async (c, accessor, args) => { const item = toOptionalString(args); @@ -1727,7 +1727,7 @@ export function refreshTerminalActions(detectedProfiles: ITerminalProfile[]) { constructor() { super({ id: TerminalCommandId.NewWithProfile, - title: { value: localize('workbench.action.terminal.newWithProfile', "Create New Terminal (With Profile)"), original: 'Create New Terminal (With Profile)' }, + title: localize2('workbench.action.terminal.newWithProfile', 'Create New Terminal (With Profile)'), f1: true, category, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.webExtensionContributedProfile), diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index c9097e71561..c7ba657cb79 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -6,7 +6,7 @@ import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { Schemas } from 'vs/base/common/network'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IMenu, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IExtensionTerminalProfile, ITerminalProfile, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; @@ -318,7 +318,7 @@ export function setupTerminalMenus(): void { item: { command: { id: TerminalCommandId.SelectDefaultProfile, - title: { value: localize('workbench.action.terminal.selectDefaultProfile', "Select Default Profile"), original: 'Select Default Profile' }, + title: localize2('workbench.action.terminal.selectDefaultProfile', 'Select Default Profile'), }, group: '3_configure' } @@ -367,7 +367,7 @@ export function setupTerminalMenus(): void { item: { command: { id: TerminalCommandId.SwitchTerminal, - title: { value: localize('workbench.action.terminal.switchTerminal', "Switch Terminal"), original: 'Switch Terminal' } + title: localize2('workbench.action.terminal.switchTerminal', 'Switch Terminal') }, group: 'navigation', order: 0, diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/terminalRemote.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/terminalRemote.ts index 12bffc2dfc0..43edcf91365 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/terminalRemote.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/terminalRemote.ts @@ -5,7 +5,7 @@ import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { registerTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; @@ -15,7 +15,7 @@ import { IHistoryService } from 'vs/workbench/services/history/common/history'; export function registerRemoteContributions() { registerTerminalAction({ id: TerminalCommandId.NewLocal, - title: { value: localize('workbench.action.terminal.newLocal', "Create New Integrated Terminal (Local)"), original: 'Create New Integrated Terminal (Local)' }, + title: localize2('workbench.action.terminal.newLocal', 'Create New Integrated Terminal (Local)'), run: async (c, accessor) => { const historyService = accessor.get(IHistoryService); const remoteAuthorityResolverService = accessor.get(IRemoteAuthorityResolverService); 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 7790ed30a3c..650ae6d9f35 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 @@ -5,7 +5,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -325,7 +325,7 @@ registerTerminalAction({ registerTerminalAction({ id: TerminalCommandId.ScrollToBottom, - title: { value: localize('workbench.action.terminal.scrollToBottom', "Scroll to Bottom"), original: 'Scroll to Bottom' }, + title: localize2('workbench.action.terminal.scrollToBottom', 'Scroll to Bottom'), precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.End, @@ -345,7 +345,7 @@ registerTerminalAction({ registerTerminalAction({ id: TerminalCommandId.ScrollToTop, - title: { value: localize('workbench.action.terminal.scrollToTop', "Scroll to Top"), original: 'Scroll to Top' }, + title: localize2('workbench.action.terminal.scrollToTop', 'Scroll to Top'), precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.Home, diff --git a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts index 66bc030e476..e7af8e061a7 100644 --- a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/developer'; import { VSBuffer } from 'vs/base/common/buffer'; import { Disposable, IDisposable, MutableDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -28,7 +28,7 @@ import { getWindow } from 'vs/base/browser/dom'; registerTerminalAction({ id: TerminalCommandId.ShowTextureAtlas, - title: { value: localize('workbench.action.terminal.showTextureAtlas', "Show Terminal Texture Atlas"), original: 'Show Terminal Texture Atlas' }, + title: localize2('workbench.action.terminal.showTextureAtlas', 'Show Terminal Texture Atlas'), category: Categories.Developer, precondition: ContextKeyExpr.or(TerminalContextKeys.isOpen), run: async (c, accessor) => { @@ -60,7 +60,7 @@ registerTerminalAction({ registerTerminalAction({ id: TerminalCommandId.WriteDataToTerminal, - title: { value: localize('workbench.action.terminal.writeDataToTerminal', "Write Data to Terminal"), original: 'Write Data to Terminal' }, + title: localize2('workbench.action.terminal.writeDataToTerminal', 'Write Data to Terminal'), category: Categories.Developer, run: async (c, accessor) => { const quickInputService = accessor.get(IQuickInputService); @@ -96,7 +96,7 @@ registerTerminalAction({ registerTerminalAction({ id: TerminalCommandId.RestartPtyHost, - title: { value: localize('workbench.action.terminal.restartPtyHost', "Restart Pty Host"), original: 'Restart Pty Host' }, + title: localize2('workbench.action.terminal.restartPtyHost', 'Restart Pty Host'), category: Categories.Developer, run: async (c, accessor) => { const logService = accessor.get(ITerminalLogService); diff --git a/src/vs/workbench/contrib/terminalContrib/environmentChanges/browser/terminal.environmentChanges.contribution.ts b/src/vs/workbench/contrib/terminalContrib/environmentChanges/browser/terminal.environmentChanges.contribution.ts index e4bcaf9b0c0..e12eb04ebd5 100644 --- a/src/vs/workbench/contrib/terminalContrib/environmentChanges/browser/terminal.environmentChanges.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/environmentChanges/browser/terminal.environmentChanges.contribution.ts @@ -8,7 +8,7 @@ import { Event } from 'vs/base/common/event'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EnvironmentVariableMutatorType, EnvironmentVariableScope, IEnvironmentVariableMutator, IMergedEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; import { registerActiveInstanceAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; @@ -19,7 +19,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic registerActiveInstanceAction({ id: TerminalCommandId.ShowEnvironmentContributions, - title: { value: localize('workbench.action.terminal.showEnvironmentContributions', "Show Environment Contributions"), original: 'Show Environment Contributions' }, + title: localize2('workbench.action.terminal.showEnvironmentContributions', 'Show Environment Contributions'), run: async (activeInstance, c, accessor, arg) => { const collection = activeInstance.extEnvironmentVariableCollection; if (collection) { diff --git a/src/vs/workbench/contrib/terminalContrib/find/browser/terminal.find.contribution.ts b/src/vs/workbench/contrib/terminalContrib/find/browser/terminal.find.contribution.ts index cf9c16b27f6..3b7cae8c426 100644 --- a/src/vs/workbench/contrib/terminalContrib/find/browser/terminal.find.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/find/browser/terminal.find.contribution.ts @@ -7,7 +7,7 @@ import { IDimension } from 'vs/base/browser/dom'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -99,7 +99,7 @@ registerTerminalContribution(TerminalFindContribution.ID, TerminalFindContributi registerActiveXtermAction({ id: TerminalCommandId.FindFocus, - title: { value: localize('workbench.action.terminal.focusFind', "Focus Find"), original: 'Focus Find' }, + title: localize2('workbench.action.terminal.focusFind', 'Focus Find'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyF, when: ContextKeyExpr.or(TerminalContextKeys.findFocus, TerminalContextKeys.focusInAny), @@ -114,7 +114,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalCommandId.FindHide, - title: { value: localize('workbench.action.terminal.hideFind', "Hide Find"), original: 'Hide Find' }, + title: localize2('workbench.action.terminal.hideFind', 'Hide Find'), keybinding: { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], @@ -130,7 +130,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalCommandId.ToggleFindRegex, - title: { value: localize('workbench.action.terminal.toggleFindRegex', "Toggle Find Using Regex"), original: 'Toggle Find Using Regex' }, + title: localize2('workbench.action.terminal.toggleFindRegex', 'Toggle Find Using Regex'), keybinding: { primary: KeyMod.Alt | KeyCode.KeyR, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyR }, @@ -147,7 +147,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalCommandId.ToggleFindWholeWord, - title: { value: localize('workbench.action.terminal.toggleFindWholeWord', "Toggle Find Using Whole Word"), original: 'Toggle Find Using Whole Word' }, + title: localize2('workbench.action.terminal.toggleFindWholeWord', 'Toggle Find Using Whole Word'), keybinding: { primary: KeyMod.Alt | KeyCode.KeyW, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyW }, @@ -164,7 +164,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalCommandId.ToggleFindCaseSensitive, - title: { value: localize('workbench.action.terminal.toggleFindCaseSensitive', "Toggle Find Using Case Sensitive"), original: 'Toggle Find Using Case Sensitive' }, + title: localize2('workbench.action.terminal.toggleFindCaseSensitive', 'Toggle Find Using Case Sensitive'), keybinding: { primary: KeyMod.Alt | KeyCode.KeyC, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyC }, @@ -181,7 +181,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalCommandId.FindNext, - title: { value: localize('workbench.action.terminal.findNext', "Find Next"), original: 'Find Next' }, + title: localize2('workbench.action.terminal.findNext', 'Find Next'), keybinding: [ { primary: KeyCode.F3, @@ -208,7 +208,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalCommandId.FindPrevious, - title: { value: localize('workbench.action.terminal.findPrevious', "Find Previous"), original: 'Find Previous' }, + title: localize2('workbench.action.terminal.findPrevious', 'Find Previous'), keybinding: [ { primary: KeyMod.Shift | KeyCode.F3, @@ -236,7 +236,7 @@ registerActiveXtermAction({ // Global workspace file search registerActiveInstanceAction({ id: TerminalCommandId.SearchWorkspace, - title: { value: localize('workbench.action.terminal.searchWorkspace', "Search Workspace"), original: 'Search Workspace' }, + title: localize2('workbench.action.terminal.searchWorkspace', 'Search Workspace'), keybinding: [ { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyF, diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts index 77c2abed382..bc8bb51aeac 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts @@ -5,7 +5,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -108,7 +108,7 @@ const category = terminalStrings.actionCategory; registerActiveInstanceAction({ id: TerminalCommandId.OpenDetectedLink, - title: { value: localize('workbench.action.terminal.openDetectedLink', "Open Detected Link..."), original: 'Open Detected Link...' }, + title: localize2('workbench.action.terminal.openDetectedLink', 'Open Detected Link...'), f1: true, category, precondition: TerminalContextKeys.terminalHasBeenCreated, @@ -126,7 +126,7 @@ registerActiveInstanceAction({ }); registerActiveInstanceAction({ id: TerminalCommandId.OpenWebLink, - title: { value: localize('workbench.action.terminal.openLastUrlLink', "Open Last URL Link"), original: 'Open Last URL Link' }, + title: localize2('workbench.action.terminal.openLastUrlLink', 'Open Last URL Link'), f1: true, category, precondition: TerminalContextKeys.terminalHasBeenCreated, @@ -134,7 +134,7 @@ registerActiveInstanceAction({ }); registerActiveInstanceAction({ id: TerminalCommandId.OpenFileLink, - title: { value: localize('workbench.action.terminal.openLastLocalFileLink', "Open Last Local File Link"), original: 'Open Last Local File Link' }, + title: localize2('workbench.action.terminal.openLastLocalFileLink', 'Open Last Local File Link'), f1: true, category, precondition: TerminalContextKeys.terminalHasBeenCreated, diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminal.quickFix.contribution.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminal.quickFix.contribution.ts index 6d23bfadaed..faa206bdcc2 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminal.quickFix.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminal.quickFix.contribution.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/terminalQuickFix'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -73,7 +73,7 @@ registerTerminalContribution(TerminalQuickFixContribution.ID, TerminalQuickFixCo // Actions registerActiveInstanceAction({ id: TerminalCommandId.ShowQuickFixes, - title: { value: localize('workbench.action.terminal.showQuickFixes', "Show Terminal Quick Fixes"), original: 'Show Terminal Quick Fixes' }, + title: localize2('workbench.action.terminal.showQuickFixes', 'Show Terminal Quick Fixes'), precondition: TerminalContextKeys.focus, keybinding: { primary: KeyMod.CtrlCmd | KeyCode.Period, diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts index be107cefd80..0cf5fabbe4e 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts @@ -15,7 +15,7 @@ import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; import { ContextKeyExpr, IContextKey, IContextKeyService, IReadableSet } from 'vs/platform/contextkey/common/contextkey'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { registerActiveInstanceAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -76,7 +76,7 @@ registerTerminalContribution(TerminalSuggestContribution.ID, TerminalSuggestCont // Actions registerActiveInstanceAction({ id: TerminalCommandId.SelectPrevSuggestion, - title: { value: localize('workbench.action.terminal.selectPrevSuggestion', "Select the Previous Suggestion"), original: 'Select the Previous Suggestion' }, + title: localize2('workbench.action.terminal.selectPrevSuggestion', 'Select the Previous Suggestion'), f1: false, precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.focus, TerminalContextKeys.isOpen, TerminalContextKeys.suggestWidgetVisible), keybinding: { @@ -89,7 +89,7 @@ registerActiveInstanceAction({ registerActiveInstanceAction({ id: TerminalCommandId.SelectPrevPageSuggestion, - title: { value: localize('workbench.action.terminal.selectPrevPageSuggestion', "Select the Previous Page Suggestion"), original: 'Select the Previous Page Suggestion' }, + title: localize2('workbench.action.terminal.selectPrevPageSuggestion', 'Select the Previous Page Suggestion'), f1: false, precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.focus, TerminalContextKeys.isOpen, TerminalContextKeys.suggestWidgetVisible), keybinding: { @@ -102,7 +102,7 @@ registerActiveInstanceAction({ registerActiveInstanceAction({ id: TerminalCommandId.SelectNextSuggestion, - title: { value: localize('workbench.action.terminal.selectNextSuggestion', "Select the Next Suggestion"), original: 'Select the Next Suggestion' }, + title: localize2('workbench.action.terminal.selectNextSuggestion', 'Select the Next Suggestion'), f1: false, precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.focus, TerminalContextKeys.isOpen, TerminalContextKeys.suggestWidgetVisible), keybinding: { @@ -115,7 +115,7 @@ registerActiveInstanceAction({ registerActiveInstanceAction({ id: TerminalCommandId.SelectNextPageSuggestion, - title: { value: localize('workbench.action.terminal.selectNextPageSuggestion', "Select the Next Page Suggestion"), original: 'Select the Next Page Suggestion' }, + title: localize2('workbench.action.terminal.selectNextPageSuggestion', 'Select the Next Page Suggestion'), f1: false, precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.focus, TerminalContextKeys.isOpen, TerminalContextKeys.suggestWidgetVisible), keybinding: { @@ -128,7 +128,7 @@ registerActiveInstanceAction({ registerActiveInstanceAction({ id: TerminalCommandId.AcceptSelectedSuggestion, - title: { value: localize('workbench.action.terminal.acceptSelectedSuggestion', "Accept Selected Suggestion"), original: 'Accept Selected Suggestion' }, + title: localize2('workbench.action.terminal.acceptSelectedSuggestion', 'Accept Selected Suggestion'), f1: false, precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.focus, TerminalContextKeys.isOpen, TerminalContextKeys.suggestWidgetVisible), keybinding: { @@ -142,7 +142,7 @@ registerActiveInstanceAction({ registerActiveInstanceAction({ id: TerminalCommandId.HideSuggestWidget, - title: { value: localize('workbench.action.terminal.hideSuggestWidget', "Hide Suggest Widget"), original: 'Hide Suggest Widget' }, + title: localize2('workbench.action.terminal.hideSuggestWidget', 'Hide Suggest Widget'), f1: false, precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.focus, TerminalContextKeys.isOpen, TerminalContextKeys.suggestWidgetVisible), keybinding: { diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index 550d40acc80..5b7abc3825e 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -14,7 +14,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -390,7 +390,7 @@ class StopContinuousRunAction extends Action2 { constructor() { super({ id: TestCommandId.StopContinousRun, - title: { value: localize('testing.stopContinuous', "Stop Continuous Run"), original: 'Stop Continuous Run' }, + title: localize2('testing.stopContinuous', 'Stop Continuous Run'), category, icon: icons.testingTurnContinuousRunOff, menu: continuousMenus(true), @@ -663,7 +663,7 @@ export class CancelTestRunAction extends Action2 { constructor() { super({ id: TestCommandId.CancelTestRunAction, - title: { value: localize('testing.cancelRun', "Cancel Test Run"), original: 'Cancel Test Run' }, + title: localize2('testing.cancelRun', 'Cancel Test Run'), icon: icons.testingCancelIcon, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -700,7 +700,7 @@ export class TestingViewAsListAction extends ViewAction { super({ id: TestCommandId.TestingViewAsListAction, viewId: Testing.ExplorerViewId, - title: { value: localize('testing.viewAsList', "View as List"), original: 'View as List' }, + title: localize2('testing.viewAsList', 'View as List'), toggled: TestingContextKeys.viewMode.isEqualTo(TestExplorerViewMode.List), menu: { id: MenuId.ViewTitle, @@ -724,7 +724,7 @@ export class TestingViewAsTreeAction extends ViewAction { super({ id: TestCommandId.TestingViewAsTreeAction, viewId: Testing.ExplorerViewId, - title: { value: localize('testing.viewAsTree', "View as Tree"), original: 'View as Tree' }, + title: localize2('testing.viewAsTree', 'View as Tree'), toggled: TestingContextKeys.viewMode.isEqualTo(TestExplorerViewMode.Tree), menu: { id: MenuId.ViewTitle, @@ -749,7 +749,7 @@ export class TestingSortByStatusAction extends ViewAction { super({ id: TestCommandId.TestingSortByStatusAction, viewId: Testing.ExplorerViewId, - title: { value: localize('testing.sortByStatus', "Sort by Status"), original: 'Sort by Status' }, + title: localize2('testing.sortByStatus', 'Sort by Status'), toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByStatus), menu: { id: MenuId.ViewTitle, @@ -773,7 +773,7 @@ export class TestingSortByLocationAction extends ViewAction super({ id: TestCommandId.TestingSortByLocationAction, viewId: Testing.ExplorerViewId, - title: { value: localize('testing.sortByLocation', "Sort by Location"), original: 'Sort by Location' }, + title: localize2('testing.sortByLocation', 'Sort by Location'), toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByLocation), menu: { id: MenuId.ViewTitle, @@ -797,7 +797,7 @@ export class TestingSortByDurationAction extends ViewAction super({ id: TestCommandId.TestingSortByDurationAction, viewId: Testing.ExplorerViewId, - title: { value: localize('testing.sortByDuration', "Sort by Duration"), original: 'Sort by Duration' }, + title: localize2('testing.sortByDuration', 'Sort by Duration'), toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByDuration), menu: { id: MenuId.ViewTitle, @@ -820,7 +820,7 @@ export class ShowMostRecentOutputAction extends Action2 { constructor() { super({ id: TestCommandId.ShowMostRecentOutputAction, - title: { value: localize('testing.showMostRecentOutput', "Show Output"), original: 'Show Output' }, + title: localize2('testing.showMostRecentOutput', 'Show Output'), category, icon: Codicon.terminal, keybinding: { @@ -852,7 +852,7 @@ export class CollapseAllAction extends ViewAction { super({ id: TestCommandId.CollapseAllAction, viewId: Testing.ExplorerViewId, - title: { value: localize('testing.collapseAll', "Collapse All Tests"), original: 'Collapse All Tests' }, + title: localize2('testing.collapseAll', 'Collapse All Tests'), icon: Codicon.collapseAll, menu: { id: MenuId.ViewTitle, @@ -875,7 +875,7 @@ export class ClearTestResultsAction extends Action2 { constructor() { super({ id: TestCommandId.ClearTestResultsAction, - title: { value: localize('testing.clearResults', "Clear All Results"), original: 'Clear All Results' }, + title: localize2('testing.clearResults', 'Clear All Results'), category, icon: Codicon.trash, menu: [{ @@ -909,7 +909,7 @@ export class GoToTest extends Action2 { constructor() { super({ id: TestCommandId.GoToTest, - title: { value: localize('testing.editFocusedTest', "Go to Test"), original: 'Go to Test' }, + title: localize2('testing.editFocusedTest', 'Go to Test'), icon: Codicon.goToFile, menu: testItemInlineAndInContext(ActionOrder.GoToTest, TestingContextKeys.testItemHasUri.isEqualTo(true)), keybinding: { @@ -1034,7 +1034,7 @@ export class RunAtCursor extends ExecuteTestAtCursor { constructor() { super({ id: TestCommandId.RunAtCursor, - title: { value: localize('testing.runAtCursor', "Run Test at Cursor"), original: 'Run Test at Cursor' }, + title: localize2('testing.runAtCursor', 'Run Test at Cursor'), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1049,7 +1049,7 @@ export class DebugAtCursor extends ExecuteTestAtCursor { constructor() { super({ id: TestCommandId.DebugAtCursor, - title: { value: localize('testing.debugAtCursor', "Debug Test at Cursor"), original: 'Debug Test at Cursor' }, + title: localize2('testing.debugAtCursor', 'Debug Test at Cursor'), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1177,7 +1177,7 @@ export class RunCurrentFile extends ExecuteTestsInCurrentFile { constructor() { super({ id: TestCommandId.RunCurrentFile, - title: { value: localize('testing.runCurrentFile', "Run Tests in Current File"), original: 'Run Tests in Current File' }, + title: localize2('testing.runCurrentFile', 'Run Tests in Current File'), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1193,7 +1193,7 @@ export class DebugCurrentFile extends ExecuteTestsInCurrentFile { constructor() { super({ id: TestCommandId.DebugCurrentFile, - title: { value: localize('testing.debugCurrentFile', "Debug Tests in Current File"), original: 'Debug Tests in Current File' }, + title: localize2('testing.debugCurrentFile', 'Debug Tests in Current File'), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1301,7 +1301,7 @@ export class ReRunFailedTests extends RunOrDebugFailedTests { constructor() { super({ id: TestCommandId.ReRunFailedTests, - title: { value: localize('testing.reRunFailTests', "Rerun Failed Tests"), original: 'Rerun Failed Tests' }, + title: localize2('testing.reRunFailTests', 'Rerun Failed Tests'), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1322,7 +1322,7 @@ export class DebugFailedTests extends RunOrDebugFailedTests { constructor() { super({ id: TestCommandId.DebugFailedTests, - title: { value: localize('testing.debugFailTests', "Debug Failed Tests"), original: 'Debug Failed Tests' }, + title: localize2('testing.debugFailTests', 'Debug Failed Tests'), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1343,7 +1343,7 @@ export class ReRunLastRun extends RunOrDebugLastRun { constructor() { super({ id: TestCommandId.ReRunLastRun, - title: { value: localize('testing.reRunLastRun', "Rerun Last Run"), original: 'Rerun Last Run' }, + title: localize2('testing.reRunLastRun', 'Rerun Last Run'), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1364,7 +1364,7 @@ export class DebugLastRun extends RunOrDebugLastRun { constructor() { super({ id: TestCommandId.DebugLastRun, - title: { value: localize('testing.debugLastRun', "Debug Last Run"), original: 'Debug Last Run' }, + title: localize2('testing.debugLastRun', 'Debug Last Run'), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1385,7 +1385,7 @@ export class SearchForTestExtension extends Action2 { constructor() { super({ id: TestCommandId.SearchForTestExtension, - title: { value: localize('testing.searchForTestExtension', "Search for Test Extension"), original: 'Search for Test Extension' }, + title: localize2('testing.searchForTestExtension', 'Search for Test Extension'), }); } @@ -1401,7 +1401,7 @@ export class OpenOutputPeek extends Action2 { constructor() { super({ id: TestCommandId.OpenOutputPeek, - title: { value: localize('testing.openOutputPeek', "Peek Output"), original: 'Peek Output' }, + title: localize2('testing.openOutputPeek', 'Peek Output'), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1423,7 +1423,7 @@ export class ToggleInlineTestOutput extends Action2 { constructor() { super({ id: TestCommandId.ToggleInlineTestOutput, - title: { value: localize('testing.toggleInlineTestOutput', "Toggle Inline Test Output"), original: 'Toggle Inline Test Output' }, + title: localize2('testing.toggleInlineTestOutput', 'Toggle Inline Test Output'), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1472,7 +1472,7 @@ export class RefreshTestsAction extends Action2 { constructor() { super({ id: TestCommandId.RefreshTestsAction, - title: { value: localize('testing.refreshTests', "Refresh Tests"), original: 'Refresh Tests' }, + title: localize2('testing.refreshTests', 'Refresh Tests'), category, icon: icons.testingRefreshTests, keybinding: { @@ -1503,7 +1503,7 @@ export class CancelTestRefreshAction extends Action2 { constructor() { super({ id: TestCommandId.CancelTestRefreshAction, - title: { value: localize('testing.cancelTestRefresh', "Cancel Test Refresh"), original: 'Cancel Test Refresh' }, + title: localize2('testing.cancelTestRefresh', 'Cancel Test Refresh'), category, icon: icons.testingCancelRefreshTests, menu: refreshMenus(true), @@ -1519,7 +1519,7 @@ export class TestCoverageClose extends Action2 { constructor() { super({ id: TestCommandId.CoverageClose, - title: { value: localize('testing.closeCoverage', "Close Coverage"), original: 'Close Coverage' }, + title: localize2('testing.closeCoverage', 'Close Coverage'), icon: widgetClose, menu: { id: MenuId.ViewTitle, diff --git a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts index 37ca58ee5f6..f28190a627d 100644 --- a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts +++ b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts @@ -61,7 +61,7 @@ registerSingleton(ITestingDecorationsService, TestingDecorationService, Instanti const viewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: Testing.ViewletId, - title: { value: localize('test', "Testing"), original: 'Testing' }, + title: localize2('test', 'Testing'), ctorDescriptor: new SyncDescriptor(TestingViewPaneContainer), icon: testingViewIcon, alwaysUseContainerInfo: true, diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index bc67a56f964..f0bc2c768e1 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -48,7 +48,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; import { IPeekViewService, PeekViewWidget, peekViewResultsBackground, peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/peekView/browser/peekView'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { FloatingClickMenu } from 'vs/platform/actions/browser/floatingMenu'; import { MenuEntryActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -2474,7 +2474,7 @@ export class GoToNextMessageAction extends Action2 { super({ id: GoToNextMessageAction.ID, f1: true, - title: { value: localize('testing.goToNextMessage', "Go to Next Test Failure"), original: 'Go to Next Test Failure' }, + title: localize2('testing.goToNextMessage', 'Go to Next Test Failure'), icon: Codicon.arrowDown, category: Categories.Test, keybinding: { @@ -2507,7 +2507,7 @@ export class GoToPreviousMessageAction extends Action2 { super({ id: GoToPreviousMessageAction.ID, f1: true, - title: { value: localize('testing.goToPreviousMessage', "Go to Previous Test Failure"), original: 'Go to Previous Test Failure' }, + title: localize2('testing.goToPreviousMessage', 'Go to Previous Test Failure'), icon: Codicon.arrowUp, category: Categories.Test, keybinding: { @@ -2540,7 +2540,7 @@ export class OpenMessageInEditorAction extends Action2 { super({ id: OpenMessageInEditorAction.ID, f1: false, - title: { value: localize('testing.openMessageInEditor', "Open in Editor"), original: 'Open in Editor' }, + title: localize2('testing.openMessageInEditor', 'Open in Editor'), icon: Codicon.goToFile, category: Categories.Test, menu: [{ id: MenuId.TestPeekTitle }], @@ -2558,7 +2558,7 @@ export class ToggleTestingPeekHistory extends Action2 { super({ id: ToggleTestingPeekHistory.ID, f1: true, - title: { value: localize('testing.toggleTestingPeekHistory', "Toggle Test History in Peek"), original: 'Toggle Test History in Peek' }, + title: localize2('testing.toggleTestingPeekHistory', 'Toggle Test History in Peek'), icon: Codicon.history, category: Categories.Test, menu: [{ diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 60a131aa7a5..2117ec5c728 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { MenuRegistry, MenuId, Action2, registerAction2, ISubmenuItem } from 'vs/platform/actions/common/actions'; import { equalsIgnoreCase } from 'vs/base/common/strings'; @@ -384,7 +384,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: SelectColorThemeCommandId, - title: { value: localize('selectTheme.label', "Color Theme"), original: 'Color Theme' }, + title: localize2('selectTheme.label', 'Color Theme'), category: Categories.Preferences, f1: true, keybinding: { @@ -426,7 +426,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: SelectFileIconThemeCommandId, - title: { value: localize('selectIconTheme.label', "File Icon Theme"), original: 'File Icon Theme' }, + title: localize2('selectIconTheme.label', 'File Icon Theme'), category: Categories.Preferences, f1: true }); @@ -461,7 +461,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: SelectProductIconThemeCommandId, - title: { value: localize('selectProductIconTheme.label', "Product Icon Theme"), original: 'Product Icon Theme' }, + title: localize2('selectProductIconTheme.label', 'Product Icon Theme'), category: Categories.Preferences, f1: true }); @@ -578,7 +578,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.generateColorTheme', - title: { value: localize('generateColorTheme.label', "Generate Color Theme From Current Settings"), original: 'Generate Color Theme From Current Settings' }, + title: localize2('generateColorTheme.label', 'Generate Color Theme From Current Settings'), category: Categories.Developer, f1: true }); @@ -632,7 +632,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: toggleLightDarkThemesCommandId, - title: { value: localize('toggleLightDarkThemes.label', "Toggle between Light/Dark Themes"), original: 'Toggle between Light/Dark Themes' }, + title: localize2('toggleLightDarkThemes.label', 'Toggle between Light/Dark Themes'), category: Categories.Preferences, f1: true, }); @@ -677,7 +677,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: browseColorThemesInMarketplaceCommandId, - title: { value: localize('browseColorThemeInMarketPlace.label', "Browse Color Themes in Marketplace"), original: 'Browse Color Themes in Marketplace' }, + title: localize2('browseColorThemeInMarketPlace.label', 'Browse Color Themes in Marketplace'), category: Categories.Preferences, f1: true, }); @@ -736,7 +736,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { MenuRegistry.appendMenuItem(ThemesSubMenu, { command: { id: SelectColorThemeCommandId, - title: localize('selectTheme.label', "Color Theme") + title: localize('selectTheme.label', 'Color Theme') }, order: 1 }); diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 28036f14af7..bf5a8baf6ad 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -1250,9 +1250,9 @@ class TimelinePaneCommands extends Disposable { constructor() { super({ id: 'timeline.refresh', - title: { value: localize('refresh', "Refresh"), original: 'Refresh' }, + title: localize2('refresh', "Refresh"), icon: timelineRefresh, - category: { value: localize('timeline', "Timeline"), original: 'Timeline' }, + category: localize2('timeline', "Timeline"), menu: { id: MenuId.TimelineTitle, group: 'navigation', @@ -1272,9 +1272,9 @@ class TimelinePaneCommands extends Disposable { this._register(MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ command: { id: 'timeline.toggleFollowActiveEditor', - title: { value: localize('timeline.toggleFollowActiveEditorCommand.follow', "Pin the Current Timeline"), original: 'Pin the Current Timeline' }, + title: localize2('timeline.toggleFollowActiveEditorCommand.follow', 'Pin the Current Timeline'), icon: timelinePin, - category: { value: localize('timeline', "Timeline"), original: 'Timeline' }, + category: localize2('timeline', "Timeline"), }, group: 'navigation', order: 98, @@ -1284,9 +1284,9 @@ class TimelinePaneCommands extends Disposable { this._register(MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ command: { id: 'timeline.toggleFollowActiveEditor', - title: { value: localize('timeline.toggleFollowActiveEditorCommand.unfollow', "Unpin the Current Timeline"), original: 'Unpin the Current Timeline' }, + title: localize2('timeline.toggleFollowActiveEditorCommand.unfollow', 'Unpin the Current Timeline'), icon: timelineUnpin, - category: { value: localize('timeline', "Timeline"), original: 'Timeline' }, + category: localize2('timeline', "Timeline"), }, group: 'navigation', order: 98, diff --git a/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution.ts b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution.ts index 0c62e8d9b8b..df24b2949de 100644 --- a/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution.ts @@ -16,7 +16,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { PeekContext } from 'vs/editor/contrib/peekView/browser/peekView'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -177,7 +177,7 @@ registerAction2(class PeekTypeHierarchyAction extends EditorAction2 { constructor() { super({ id: 'editor.showTypeHierarchy', - title: { value: localize('title', "Peek Type Hierarchy"), original: 'Peek Type Hierarchy' }, + title: localize2('title', 'Peek Type Hierarchy'), menu: { id: MenuId.EditorContextPeek, group: 'navigation', @@ -206,7 +206,7 @@ registerAction2(class extends EditorAction2 { constructor() { super({ id: 'editor.showSupertypes', - title: { value: localize('title.supertypes', "Show Supertypes"), original: 'Show Supertypes' }, + title: localize2('title.supertypes', 'Show Supertypes'), icon: Codicon.typeHierarchySuper, precondition: ContextKeyExpr.and(_ctxTypeHierarchyVisible, _ctxTypeHierarchyDirection.isEqualTo(TypeHierarchyDirection.Subtypes)), keybinding: { @@ -231,7 +231,7 @@ registerAction2(class extends EditorAction2 { constructor() { super({ id: 'editor.showSubtypes', - title: { value: localize('title.subtypes', "Show Subtypes"), original: 'Show Subtypes' }, + title: localize2('title.subtypes', 'Show Subtypes'), icon: Codicon.typeHierarchySub, precondition: ContextKeyExpr.and(_ctxTypeHierarchyVisible, _ctxTypeHierarchyDirection.isEqualTo(TypeHierarchyDirection.Supertypes)), keybinding: { @@ -256,7 +256,7 @@ registerAction2(class extends EditorAction2 { constructor() { super({ id: 'editor.refocusTypeHierarchy', - title: { value: localize('title.refocusTypeHierarchy', "Refocus Type Hierarchy"), original: 'Refocus Type Hierarchy' }, + title: localize2('title.refocusTypeHierarchy', 'Refocus Type Hierarchy'), precondition: _ctxTypeHierarchyVisible, keybinding: { weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/contrib/update/browser/update.contribution.ts b/src/vs/workbench/contrib/update/browser/update.contribution.ts index 1f131713ae4..0885b877466 100644 --- a/src/vs/workbench/contrib/update/browser/update.contribution.ts +++ b/src/vs/workbench/contrib/update/browser/update.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/platform/update/common/update.config.contribution'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; @@ -80,7 +80,7 @@ export class CheckForUpdateAction extends Action2 { constructor() { super({ id: 'update.checkForUpdate', - title: { value: localize('checkForUpdates', "Check for Updates..."), original: 'Check for Updates...' }, + title: localize2('checkForUpdates', 'Check for Updates...'), category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Idle), @@ -97,7 +97,7 @@ class DownloadUpdateAction extends Action2 { constructor() { super({ id: 'update.downloadUpdate', - title: { value: localize('downloadUpdate', "Download Update"), original: 'Download Update' }, + title: localize2('downloadUpdate', 'Download Update'), category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.AvailableForDownload) @@ -113,7 +113,7 @@ class InstallUpdateAction extends Action2 { constructor() { super({ id: 'update.installUpdate', - title: { value: localize('installUpdate', "Install Update"), original: 'Install Update' }, + title: localize2('installUpdate', 'Install Update'), category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloaded) @@ -129,7 +129,7 @@ class RestartToUpdateAction extends Action2 { constructor() { super({ id: 'update.restartToUpdate', - title: { value: localize('restartToUpdate', "Restart to Update"), original: 'Restart to Update' }, + title: localize2('restartToUpdate', 'Restart to Update'), category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready) @@ -182,7 +182,7 @@ if (isWindows) { constructor() { super({ id: '_update.applyupdate', - title: { value: localize('applyUpdate', "Apply Update..."), original: 'Apply Update...' }, + title: localize2('applyUpdate', 'Apply Update...'), category: Categories.Developer, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Idle) diff --git a/src/vs/workbench/contrib/url/browser/url.contribution.ts b/src/vs/workbench/contrib/url/browser/url.contribution.ts index 994320e7863..26d0c77eade 100644 --- a/src/vs/workbench/contrib/url/browser/url.contribution.ts +++ b/src/vs/workbench/contrib/url/browser/url.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { MenuId, MenuRegistry, Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -26,7 +26,7 @@ class OpenUrlAction extends Action2 { constructor() { super({ id: 'workbench.action.url.openUrl', - title: { value: localize('openUrl', "Open URL"), original: 'Open URL' }, + title: localize2('openUrl', 'Open URL'), category: Categories.Developer, f1: true }); diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index bf6cc44b591..fc86e344d3e 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -6,7 +6,7 @@ import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, IMenuService, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; @@ -147,7 +147,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements constructor() { super({ id: `workbench.profiles.actions.switchProfile`, - title: { value: localize('switchProfile', "Switch Profile..."), original: 'Switch Profile...' }, + title: localize2('switchProfile', 'Switch Profile...'), category: PROFILES_CATEGORY, f1: true, precondition: PROFILES_ENABLEMENT_CONTEXT, diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 1a0b1b736e5..ce24dc14b3e 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -14,7 +14,7 @@ import type { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, ContextKeyTrueExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -64,12 +64,12 @@ type SyncConflictsClassification = { action?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'action taken while resolving conflicts. Eg: acceptLocal, acceptRemote' }; }; -const turnOffSyncCommand = { id: 'workbench.userDataSync.actions.turnOff', title: { value: localize('stop sync', "Turn Off"), original: 'Turn Off' } }; -const configureSyncCommand = { id: CONFIGURE_SYNC_COMMAND_ID, title: { value: localize('configure sync', "Configure..."), original: 'Configure...' } }; +const turnOffSyncCommand = { id: 'workbench.userDataSync.actions.turnOff', title: localize2('stop sync', 'Turn Off') }; +const configureSyncCommand = { id: CONFIGURE_SYNC_COMMAND_ID, title: localize2('configure sync', 'Configure...') }; const showConflictsCommandId = 'workbench.userDataSync.actions.showConflicts'; const syncNowCommand = { id: 'workbench.userDataSync.actions.syncNow', - title: { value: localize('sync now', "Sync Now"), original: 'Sync Now' }, + title: localize2('sync now', 'Sync Now'), description(userDataSyncService: IUserDataSyncService): string | undefined { if (userDataSyncService.status === SyncStatus.Syncing) { return localize('syncing', "syncing"); @@ -80,8 +80,8 @@ const syncNowCommand = { return undefined; } }; -const showSyncSettingsCommand = { id: 'workbench.userDataSync.actions.settings', title: { value: localize('sync settings', "Show Settings"), original: 'Show Settings' }, }; -const showSyncedDataCommand = { id: 'workbench.userDataSync.actions.showSyncedData', title: { value: localize('show synced data', "Show Synced Data"), original: 'Show Synced Data' }, }; +const showSyncSettingsCommand = { id: 'workbench.userDataSync.actions.settings', title: localize2('sync settings', 'Show Settings'), }; +const showSyncedDataCommand = { id: 'workbench.userDataSync.actions.showSyncedData', title: localize2('show synced data', 'Show Synced Data'), }; const CONTEXT_TURNING_ON_STATE = new RawContextKey('userDataSyncTurningOn', false); @@ -725,7 +725,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo constructor() { super({ id: 'workbench.userDataSync.actions.turnOn', - title: { value: localize('global activity turn on sync', "Backup and Sync Settings..."), original: 'Backup and Sync Settings...' }, + title: localize2('global activity turn on sync', 'Backup and Sync Settings...'), category: SYNC_TITLE, f1: true, precondition: when, diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts index 1e57d47d68a..3d0ae3e607d 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { GettingStartedInputSerializer, GettingStartedPage, inWelcomeContext } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; @@ -38,7 +38,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.openWalkthrough', - title: { value: localize('miWelcome', "Welcome"), original: 'Welcome' }, + title: localize2('miWelcome', 'Welcome'), category: Categories.Help, f1: true, menu: { @@ -127,13 +127,13 @@ Registry.as(EditorExtensions.EditorPane).registerEditorPane ] ); -const category = { value: localize('welcome', "Welcome"), original: 'Welcome' }; +const category = localize2('welcome', "Welcome"); registerAction2(class extends Action2 { constructor() { super({ id: 'welcome.goBack', - title: { value: localize('welcome.goBack', "Go Back"), original: 'Go Back' }, + title: localize2('welcome.goBack', 'Go Back'), category, keybinding: { weight: KeybindingWeight.EditorContrib, @@ -203,7 +203,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'welcome.showAllWalkthroughs', - title: { value: localize('welcome.showAllWalkthroughs', "Open Walkthrough..."), original: 'Open Walkthrough...' }, + title: localize2('welcome.showAllWalkthroughs', 'Open Walkthrough...'), category, f1: true, }); diff --git a/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts b/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts index 7a50bf9a74b..beaa0363420 100644 --- a/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts +++ b/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts @@ -6,7 +6,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { assertIsDefined } from 'vs/base/common/types'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { Action2, IMenuService, MenuId, registerAction2, IMenu, MenuRegistry, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -20,13 +20,13 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } fr import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; const builtInSource = localize('Built-In', "Built-In"); -const category: ILocalizedString = { value: localize('Create', "Create"), original: 'Create' }; +const category: ILocalizedString = localize2('Create', 'Create'); registerAction2(class extends Action2 { constructor() { super({ id: 'welcome.showNewFileEntries', - title: { value: localize('welcome.newFile', "New File..."), original: 'New File...' }, + title: localize2('welcome.newFile', 'New File...'), category, f1: true, keybinding: { diff --git a/src/vs/workbench/contrib/welcomeWalkthrough/browser/editor/editorWalkThrough.ts b/src/vs/workbench/contrib/welcomeWalkthrough/browser/editor/editorWalkThrough.ts index 46030249ab8..7db257327e4 100644 --- a/src/vs/workbench/contrib/welcomeWalkthrough/browser/editor/editorWalkThrough.ts +++ b/src/vs/workbench/contrib/welcomeWalkthrough/browser/editor/editorWalkThrough.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/workbench/contrib/welcomeWalkthrough/browser/editor/vs_code_editor_walkthrough'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { WalkThroughInput, WalkThroughInputOptions } from 'vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughInput'; @@ -29,7 +29,7 @@ const inputOptions: WalkThroughInputOptions = { export class EditorWalkThroughAction extends Action2 { public static readonly ID = 'workbench.action.showInteractivePlayground'; - public static readonly LABEL = { value: localize('editorWalkThrough', "Interactive Editor Playground"), original: 'Interactive Editor Playground' }; + public static readonly LABEL = localize2('editorWalkThrough', 'Interactive Editor Playground'); constructor() { super({ diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index 864a19e1ce4..dbb1822d053 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/workspaceTrustEditor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -676,7 +676,7 @@ Registry.as(EditorExtensions.EditorPane).registerEditorPane // Configure Workspace Trust Settings const CONFIGURE_TRUST_COMMAND_ID = 'workbench.trust.configure'; -const WORKSPACES_CATEGORY = { value: localize('workspacesCategory', "Workspaces"), original: 'Workspaces' }; +const WORKSPACES_CATEGORY = localize2('workspacesCategory', 'Workspaces'); registerAction2(class extends Action2 { constructor() { diff --git a/src/vs/workbench/electron-sandbox/actions/developerActions.ts b/src/vs/workbench/electron-sandbox/actions/developerActions.ts index f3cf27295b2..25df5605d17 100644 --- a/src/vs/workbench/electron-sandbox/actions/developerActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/developerActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { INativeHostService } from 'vs/platform/native/common/native'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; @@ -22,7 +22,7 @@ export class ToggleDevToolsAction extends Action2 { constructor() { super({ id: 'workbench.action.toggleDevTools', - title: { value: localize('toggleDevTools', "Toggle Developer Tools"), original: 'Toggle Developer Tools' }, + title: localize2('toggleDevTools', 'Toggle Developer Tools'), category: Categories.Developer, f1: true, keybinding: { @@ -51,7 +51,7 @@ export class ConfigureRuntimeArgumentsAction extends Action2 { constructor() { super({ id: 'workbench.action.configureRuntimeArguments', - title: { value: localize('configureRuntimeArguments', "Configure Runtime Arguments"), original: 'Configure Runtime Arguments' }, + title: localize2('configureRuntimeArguments', 'Configure Runtime Arguments'), category: Categories.Preferences, f1: true }); @@ -73,7 +73,7 @@ export class ReloadWindowWithExtensionsDisabledAction extends Action2 { constructor() { super({ id: 'workbench.action.reloadWindowWithExtensionsDisabled', - title: { value: localize('reloadWindowWithExtensionsDisabled', "Reload With Extensions Disabled"), original: 'Reload With Extensions Disabled' }, + title: localize2('reloadWindowWithExtensionsDisabled', 'Reload With Extensions Disabled'), category: Categories.Developer, f1: true }); @@ -89,7 +89,7 @@ export class OpenUserDataFolderAction extends Action2 { constructor() { super({ id: 'workbench.action.openUserDataFolder', - title: { value: localize('openUserDataFolder', "Open User Data Folder"), original: 'Open User Data Folder' }, + title: localize2('openUserDataFolder', 'Open User Data Folder'), category: Categories.Developer, f1: true }); diff --git a/src/vs/workbench/electron-sandbox/actions/installActions.ts b/src/vs/workbench/electron-sandbox/actions/installActions.ts index be65b67af45..952883aec23 100644 --- a/src/vs/workbench/electron-sandbox/actions/installActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/installActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2 } from 'vs/platform/actions/common/actions'; import { ILocalizedString } from 'vs/platform/action/common/action'; import product from 'vs/platform/product/common/product'; @@ -14,7 +14,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { IProductService } from 'vs/platform/product/common/productService'; import { isCancellationError } from 'vs/base/common/errors'; -const shellCommandCategory: ILocalizedString = { value: localize('shellCommand', "Shell Command"), original: 'Shell Command' }; +const shellCommandCategory: ILocalizedString = localize2('shellCommand', 'Shell Command'); export class InstallShellScriptAction extends Action2 { diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index ba0f0c06364..8f5a4a5f0f0 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/actions'; import { URI } from 'vs/base/common/uri'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ApplyZoomTarget, applyZoom } from 'vs/platform/window/electron-sandbox/window'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { getZoomLevel } from 'vs/base/browser/browser'; @@ -321,7 +321,7 @@ export class SwitchWindowAction extends BaseSwitchWindow { constructor() { super({ id: 'workbench.action.switchWindow', - title: { value: localize('switchWindow', "Switch Window..."), original: 'Switch Window...' }, + title: localize2('switchWindow', 'Switch Window...'), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -341,7 +341,7 @@ export class QuickSwitchWindowAction extends BaseSwitchWindow { constructor() { super({ id: 'workbench.action.quickSwitchWindow', - title: { value: localize('quickSwitchWindow', "Quick Switch Window..."), original: 'Quick Switch Window...' }, + title: localize2('quickSwitchWindow', 'Quick Switch Window...'), f1: false // hide quick pickers from command palette to not confuse with the other entry that shows a input field }); } diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 9d4ff738a27..d2690d052f3 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Registry } from 'vs/platform/registry/common/platform'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { MenuRegistry, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; @@ -87,12 +87,12 @@ import product from 'vs/platform/product/common/product'; // Actions: macOS Native Tabs if (isMacintosh) { for (const command of [ - { handler: NewWindowTabHandler, id: 'workbench.action.newWindowTab', title: { value: localize('newTab', "New Window Tab"), original: 'New Window Tab' } }, - { handler: ShowPreviousWindowTabHandler, id: 'workbench.action.showPreviousWindowTab', title: { value: localize('showPreviousTab', "Show Previous Window Tab"), original: 'Show Previous Window Tab' } }, - { handler: ShowNextWindowTabHandler, id: 'workbench.action.showNextWindowTab', title: { value: localize('showNextWindowTab', "Show Next Window Tab"), original: 'Show Next Window Tab' } }, - { handler: MoveWindowTabToNewWindowHandler, id: 'workbench.action.moveWindowTabToNewWindow', title: { value: localize('moveWindowTabToNewWindow', "Move Window Tab to New Window"), original: 'Move Window Tab to New Window' } }, - { handler: MergeWindowTabsHandlerHandler, id: 'workbench.action.mergeAllWindowTabs', title: { value: localize('mergeAllWindowTabs', "Merge All Windows"), original: 'Merge All Windows' } }, - { handler: ToggleWindowTabsBarHandler, id: 'workbench.action.toggleWindowTabsBar', title: { value: localize('toggleWindowTabsBar', "Toggle Window Tabs Bar"), original: 'Toggle Window Tabs Bar' } } + { handler: NewWindowTabHandler, id: 'workbench.action.newWindowTab', title: localize2('newTab', 'New Window Tab') }, + { handler: ShowPreviousWindowTabHandler, id: 'workbench.action.showPreviousWindowTab', title: localize2('showPreviousTab', 'Show Previous Window Tab') }, + { handler: ShowNextWindowTabHandler, id: 'workbench.action.showNextWindowTab', title: localize2('showNextWindowTab', 'Show Next Window Tab') }, + { handler: MoveWindowTabToNewWindowHandler, id: 'workbench.action.moveWindowTabToNewWindow', title: localize2('moveWindowTabToNewWindow', 'Move Window Tab to New Window') }, + { handler: MergeWindowTabsHandlerHandler, id: 'workbench.action.mergeAllWindowTabs', title: localize2('mergeAllWindowTabs', 'Merge All Windows') }, + { handler: ToggleWindowTabsBarHandler, id: 'workbench.action.toggleWindowTabsBar', title: localize2('toggleWindowTabsBar', 'Toggle Window Tabs Bar') } ]) { CommandsRegistry.registerCommand(command.id, command.handler); diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionBisect.ts b/src/vs/workbench/services/extensionManagement/browser/extensionBisect.ts index 1029b67197d..caf3def4700 100644 --- a/src/vs/workbench/services/extensionManagement/browser/extensionBisect.ts +++ b/src/vs/workbench/services/extensionManagement/browser/extensionBisect.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ExtensionType, IExtension, isResolverExtension } from 'vs/platform/extensions/common/extensions'; @@ -219,7 +219,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'extension.bisect.start', - title: { value: localize('title.start', "Start Extension Bisect"), original: 'Start Extension Bisect' }, + title: localize2('title.start', 'Start Extension Bisect'), category: Categories.Help, f1: true, precondition: ExtensionBisectUi.ctxIsBisectActive.negate(), @@ -258,7 +258,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'extension.bisect.next', - title: { value: localize('title.isBad', "Continue Extension Bisect"), original: 'Continue Extension Bisect' }, + title: localize2('title.isBad', 'Continue Extension Bisect'), category: Categories.Help, f1: true, precondition: ExtensionBisectUi.ctxIsBisectActive @@ -354,7 +354,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'extension.bisect.stop', - title: { value: localize('title.stop', "Stop Extension Bisect"), original: 'Stop Extension Bisect' }, + title: localize2('title.stop', 'Stop Extension Bisect'), category: Categories.Help, f1: true, precondition: ExtensionBisectUi.ctxIsBisectActive diff --git a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts index 8cc185f5da7..6d129f0c111 100644 --- a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts @@ -19,7 +19,7 @@ import { IExtensionGalleryService, IExtensionInfo, IGalleryExtension, IGalleryMe import { areSameExtensions, getGalleryExtensionId, getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { Disposable } from 'vs/base/common/lifecycle'; import { ITranslations, localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import * as semver from 'vs/base/common/semver/semver'; import { isString, isUndefined } from 'vs/base/common/types'; import { getErrorMessage } from 'vs/base/common/errors'; @@ -983,7 +983,7 @@ if (isWeb) { constructor() { super({ id: 'workbench.extensions.action.openInstalledWebExtensionsResource', - title: { value: localize('openInstalledWebExtensionsResource', "Open Installed Web Extensions Resource"), original: 'Open Installed Web Extensions Resource' }, + title: localize2('openInstalledWebExtensionsResource', 'Open Installed Web Extensions Resource'), category: Categories.Developer, f1: true, precondition: IsWebContext diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index db455b09007..ecc45f88c83 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -433,8 +433,8 @@ class ManageAuthorizedExtensionURIsAction extends Action2 { constructor() { super({ id: 'workbench.extensions.action.manageAuthorizedExtensionURIs', - title: { value: localize('manage', "Manage Authorized Extension URIs..."), original: 'Manage Authorized Extension URIs...' }, - category: { value: localize('extensions', "Extensions"), original: 'Extensions' }, + title: localize2('manage', 'Manage Authorized Extension URIs...'), + category: localize2('extensions', 'Extensions'), menu: { id: MenuId.CommandPalette, when: IsWebContext.toNegated() diff --git a/src/vs/workbench/services/issue/browser/issueTroubleshoot.ts b/src/vs/workbench/services/issue/browser/issueTroubleshoot.ts index 8e154c5f967..86f84f2af22 100644 --- a/src/vs/workbench/services/issue/browser/issueTroubleshoot.ts +++ b/src/vs/workbench/services/issue/browser/issueTroubleshoot.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -349,7 +349,7 @@ registerAction2(class TroubleshootIssueAction extends Action2 { constructor() { super({ id: 'workbench.action.troubleshootIssue.start', - title: { value: localize('troubleshootIssue', "Troubleshoot Issue..."), original: 'Troubleshoot Issue...' }, + title: localize2('troubleshootIssue', 'Troubleshoot Issue...'), category: Categories.Help, f1: true, precondition: ContextKeyExpr.and(IssueTroubleshootUi.ctxIsTroubleshootActive.negate(), RemoteNameContext.isEqualTo(''), IsWebContext.negate()), @@ -364,7 +364,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.troubleshootIssue.stop', - title: { value: localize('title.stop', "Stop Troubleshoot Issue"), original: 'Stop Troubleshoot Issue' }, + title: localize2('title.stop', 'Stop Troubleshoot Issue'), category: Categories.Help, f1: true, precondition: IssueTroubleshootUi.ctxIsTroubleshootActive diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index 95b19167137..7f0b3b60418 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -5,7 +5,7 @@ import { isUndefined } from 'vs/base/common/types'; import { Event } from 'vs/base/common/event'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IUserDataProfile, IUserDataProfileOptions, IUserDataProfileUpdateOptions, ProfileResourceType } from 'vs/platform/userDataProfile/common/userDataProfile'; @@ -138,7 +138,7 @@ export const defaultUserDataProfileIcon = registerIcon('defaultProfile-icon', Co export const ProfilesMenu = new MenuId('Profiles'); export const MANAGE_PROFILES_ACTION_ID = 'workbench.profiles.actions.manage'; -export const PROFILES_TITLE = { value: localize('profiles', "Profiles"), original: 'Profiles' }; +export const PROFILES_TITLE = localize2('profiles', 'Profiles'); export const PROFILES_CATEGORY = { ...PROFILES_TITLE }; export const PROFILE_EXTENSION = 'code-profile'; export const PROFILE_FILTER = [{ name: localize('profile', "Profile"), extensions: [PROFILE_EXTENSION] }]; From 44ffc28fea5da56f1e447939da3621d644fe86ce Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 4 Jan 2024 07:50:30 -0800 Subject: [PATCH 0062/1897] Move IHoverService to `vs/editor` (#201035) --- .../browser/services}/hoverService.ts | 6 +- .../browser/widget/hoverWidget}/hover.css | 0 .../widget/hoverWidget}/hoverWidget.ts | 8 +- .../browser/markdownRenderer.ts | 0 .../browser/renderedMarkdown.css | 0 .../contrib/hover/browser/marginHover.ts | 2 +- .../hover/browser/markdownHoverParticipant.ts | 2 +- .../browser/hoverParticipant.ts | 2 +- .../message/browser/messageController.ts | 2 +- .../browser/parameterHintsWidget.ts | 2 +- .../suggest/browser/suggestWidgetDetails.ts | 2 +- .../browser/bannerController.ts | 2 +- .../quickInput/standaloneQuickInputService.ts | 15 +++- .../standalone/browser/standaloneServices.ts | 1 + .../hover/browser/hover.ts | 5 +- .../platform/quickinput/browser/quickInput.ts | 53 +++++++++++- .../browser/quickInputController.ts | 5 +- .../quickinput/browser/quickInputList.ts | 85 +++++++++---------- .../quickinput/browser/quickInputService.ts | 11 ++- .../api/browser/viewsExtensionPoint.ts | 2 +- .../browser/parts/banner/bannerPart.ts | 2 +- .../browser/parts/compositeBarActions.ts | 3 +- .../browser/parts/dialogs/dialogHandler.ts | 2 +- .../browser/parts/globalCompositeBar.ts | 2 +- .../browser/parts/statusbar/statusbarPart.ts | 2 +- .../browser/parts/titlebar/titlebarPart.ts | 2 +- .../workbench/browser/parts/views/treeView.ts | 4 +- .../browser/accessibilityContributions.ts | 2 +- .../contrib/chat/browser/chatListRenderer.ts | 2 +- .../contrib/comments/browser/commentNode.ts | 2 +- .../comments/browser/commentThreadBody.ts | 2 +- .../comments/browser/commentThreadWidget.ts | 2 +- .../comments/browser/commentsTreeViewer.ts | 2 +- .../contrib/debug/browser/breakpointsView.ts | 2 +- .../extensions/browser/extensionsWidgets.ts | 2 +- .../files/browser/views/explorerViewer.ts | 2 +- .../browser/view/cellParts/cellStatusPart.ts | 2 +- .../settingsEditorSettingIndicators.ts | 3 +- .../preferences/browser/settingsTree.ts | 2 +- .../contrib/remote/browser/tunnelView.ts | 2 +- .../contrib/scm/browser/scmViewPane.ts | 2 +- .../terminal/browser/terminalTabbedView.ts | 2 +- .../terminal/browser/terminalTabsList.ts | 2 +- .../terminal/browser/terminalTooltip.ts | 2 +- .../contrib/terminal/browser/terminalView.ts | 2 +- .../browser/widgets/terminalHoverWidget.ts | 2 +- .../browser/xterm/decorationStyles.ts | 2 +- .../terminalContrib/links/browser/links.ts | 2 +- .../links/browser/terminalLink.ts | 2 +- .../links/browser/terminalLinkManager.ts | 2 +- .../testing/browser/testCoverageBars.ts | 2 +- .../testing/browser/testingExplorerView.ts | 2 +- .../testing/browser/testingOutputPeek.ts | 2 +- .../contrib/timeline/browser/timelinePane.ts | 2 +- .../welcomeDialog/browser/welcomeWidget.ts | 2 +- .../browser/gettingStarted.ts | 2 +- .../parts/titlebar/titlebarPart.ts | 2 +- .../quickinput/browser/quickInputService.ts | 54 +----------- .../userDataProfileImportExportService.ts | 2 +- .../test/browser/workbenchTestServices.ts | 3 +- src/vs/workbench/workbench.common.main.ts | 2 +- 61 files changed, 180 insertions(+), 164 deletions(-) rename src/vs/{workbench/services/hover/browser => editor/browser/services}/hoverService.ts (97%) rename src/vs/{workbench/services/hover/browser/media => editor/browser/widget/hoverWidget}/hover.css (100%) rename src/vs/{workbench/services/hover/browser => editor/browser/widget/hoverWidget}/hoverWidget.ts (98%) rename src/vs/editor/{contrib => browser/widget}/markdownRenderer/browser/markdownRenderer.ts (100%) rename src/vs/editor/{contrib => browser/widget}/markdownRenderer/browser/renderedMarkdown.css (100%) rename src/vs/{workbench/services => platform}/hover/browser/hover.ts (98%) diff --git a/src/vs/workbench/services/hover/browser/hoverService.ts b/src/vs/editor/browser/services/hoverService.ts similarity index 97% rename from src/vs/workbench/services/hover/browser/hoverService.ts rename to src/vs/editor/browser/services/hoverService.ts index a3c4dfd71f8..73e3a819587 100644 --- a/src/vs/workbench/services/hover/browser/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService.ts @@ -3,14 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/hover'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { editorHoverBorder } from 'vs/platform/theme/common/colorRegistry'; -import { IHoverService, IHoverOptions, IHoverWidget } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService, IHoverOptions } from 'vs/platform/hover/browser/hover'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { HoverWidget } from 'vs/workbench/services/hover/browser/hoverWidget'; +import { HoverWidget } from 'vs/editor/browser/widget/hoverWidget/hoverWidget'; import { IContextViewProvider, IDelegate } from 'vs/base/browser/ui/contextview/contextview'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { addDisposableListener, EventType, getActiveElement, isAncestorOfActiveElement, isAncestor, getWindow } from 'vs/base/browser/dom'; @@ -20,6 +19,7 @@ import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { mainWindow } from 'vs/base/browser/window'; +import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; export class HoverService implements IHoverService { declare readonly _serviceBrand: undefined; diff --git a/src/vs/workbench/services/hover/browser/media/hover.css b/src/vs/editor/browser/widget/hoverWidget/hover.css similarity index 100% rename from src/vs/workbench/services/hover/browser/media/hover.css rename to src/vs/editor/browser/widget/hoverWidget/hover.css diff --git a/src/vs/workbench/services/hover/browser/hoverWidget.ts b/src/vs/editor/browser/widget/hoverWidget/hoverWidget.ts similarity index 98% rename from src/vs/workbench/services/hover/browser/hoverWidget.ts rename to src/vs/editor/browser/widget/hoverWidget/hoverWidget.ts index a29612cb2d7..d6f6249675c 100644 --- a/src/vs/workbench/services/hover/browser/hoverWidget.ts +++ b/src/vs/editor/browser/widget/hoverWidget/hoverWidget.ts @@ -3,11 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./hover'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import * as dom from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IHoverTarget, IHoverOptions } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverTarget, IHoverOptions } from 'vs/platform/hover/browser/hover'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; @@ -16,12 +17,13 @@ import { Widget } from 'vs/base/browser/ui/widget'; import { AnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MarkdownRenderer, openLinkFromMarkdown } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { MarkdownRenderer, openLinkFromMarkdown } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { isMarkdownString } from 'vs/base/common/htmlContent'; import { localize } from 'vs/nls'; import { isMacintosh } from 'vs/base/common/platform'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { status } from 'vs/base/browser/ui/aria/aria'; +import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; const $ = dom.$; type TargetRect = { @@ -40,7 +42,7 @@ const enum Constants { HoverWindowEdgeMargin = 2, } -export class HoverWidget extends Widget { +export class HoverWidget extends Widget implements IHoverWidget { private readonly _messageListeners = new DisposableStore(); private readonly _lockMouseTracker: CompositeMouseTracker; diff --git a/src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts similarity index 100% rename from src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts rename to src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts diff --git a/src/vs/editor/contrib/markdownRenderer/browser/renderedMarkdown.css b/src/vs/editor/browser/widget/markdownRenderer/browser/renderedMarkdown.css similarity index 100% rename from src/vs/editor/contrib/markdownRenderer/browser/renderedMarkdown.css rename to src/vs/editor/browser/widget/markdownRenderer/browser/renderedMarkdown.css diff --git a/src/vs/editor/contrib/hover/browser/marginHover.ts b/src/vs/editor/contrib/hover/browser/marginHover.ts index deac3bff8e9..803fc30fd39 100644 --- a/src/vs/editor/contrib/hover/browser/marginHover.ts +++ b/src/vs/editor/contrib/hover/browser/marginHover.ts @@ -7,7 +7,7 @@ import * as dom from 'vs/base/browser/dom'; import { asArray } from 'vs/base/common/arrays'; import { IMarkdownString, isEmptyMarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { ILanguageService } from 'vs/editor/common/languages/language'; diff --git a/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts b/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts index 93f4631209b..2227602d0d9 100644 --- a/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts @@ -9,7 +9,7 @@ import { AsyncIterableObject } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IMarkdownString, isEmptyMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts b/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts index 21e40220440..373eece44c2 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts @@ -15,7 +15,7 @@ import { IModelDecoration } from 'vs/editor/common/model'; import { HoverAnchor, HoverAnchorType, HoverForeignElementAnchor, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes'; import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; import { InlineSuggestionHintsContentWidget } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import * as nls from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/editor/contrib/message/browser/messageController.ts b/src/vs/editor/contrib/message/browser/messageController.ts index 6e13775d651..6c27301f8b3 100644 --- a/src/vs/editor/contrib/message/browser/messageController.ts +++ b/src/vs/editor/contrib/message/browser/messageController.ts @@ -16,7 +16,7 @@ import { IPosition } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; import { PositionAffinity } from 'vs/editor/common/model'; -import { openLinkFromMarkdown } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { openLinkFromMarkdown } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import * as nls from 'vs/nls'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; diff --git a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts index c595faccc73..46d92a532e9 100644 --- a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts @@ -17,7 +17,7 @@ import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentW import { EditorOption } from 'vs/editor/common/config/editorOptions'; import * as languages from 'vs/editor/common/languages'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { ParameterHintsModel } from 'vs/editor/contrib/parameterHints/browser/parameterHintsModel'; import { Context } from 'vs/editor/contrib/parameterHints/browser/provideSignatureHelp'; import * as nls from 'vs/nls'; diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts index 8b8dc97ec6c..5d2a5fbf282 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts @@ -10,7 +10,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { Emitter, Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { ResizableHTMLElement } from 'vs/base/browser/ui/resizable/resizable'; diff --git a/src/vs/editor/contrib/unicodeHighlighter/browser/bannerController.ts b/src/vs/editor/contrib/unicodeHighlighter/browser/bannerController.ts index 42edc0d56bd..b5a54c49f20 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/browser/bannerController.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/browser/bannerController.ts @@ -8,7 +8,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable } from 'vs/base/common/lifecycle'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILinkDescriptor, Link } from 'vs/platform/opener/browser/link'; diff --git a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts index 9897d433286..75a3520379b 100644 --- a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts +++ b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts @@ -19,6 +19,8 @@ import { QuickInputController, IQuickInputControllerHost } from 'vs/platform/qui import { QuickInputService } from 'vs/platform/quickinput/browser/quickInputService'; import { createSingleCallFunction } from 'vs/base/common/functional'; import { IQuickAccessController } from 'vs/platform/quickinput/common/quickAccess'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; class EditorScopedQuickInputService extends QuickInputService { @@ -29,9 +31,18 @@ class EditorScopedQuickInputService extends QuickInputService { @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, - @ICodeEditorService codeEditorService: ICodeEditorService + @ICodeEditorService codeEditorService: ICodeEditorService, + @IConfigurationService configurationService: IConfigurationService, + @IHoverService hoverService: IHoverService, ) { - super(instantiationService, contextKeyService, themeService, new EditorScopedLayoutService(editor.getContainerDomNode(), codeEditorService)); + super( + instantiationService, + contextKeyService, + themeService, + new EditorScopedLayoutService(editor.getContainerDomNode(), codeEditorService), + configurationService, + hoverService + ); // Use the passed in code editor as host for the quick input widget const contribution = QuickInputEditorContribution.get(editor); diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index a0cdce16e4b..221d66ba4a3 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -10,6 +10,7 @@ import 'vs/platform/undoRedo/common/undoRedoService'; import 'vs/editor/common/services/languageFeatureDebounce'; import 'vs/editor/common/services/semanticTokensStylingService'; import 'vs/editor/common/services/languageFeaturesService'; +import 'vs/editor/browser/services/hoverService'; import * as strings from 'vs/base/common/strings'; import * as dom from 'vs/base/browser/dom'; diff --git a/src/vs/workbench/services/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts similarity index 98% rename from src/vs/workbench/services/hover/browser/hover.ts rename to src/vs/platform/hover/browser/hover.ts index 324a69173f4..5f3bb1e3317 100644 --- a/src/vs/workbench/services/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -7,6 +7,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IDisposable } from 'vs/base/common/lifecycle'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; +import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; export const IHoverService = createDecorator('hoverService'); @@ -45,10 +46,6 @@ export interface IHoverService { showAndFocusLastHover(): void; } -export interface IHoverWidget extends IDisposable { - readonly isDisposed: boolean; -} - export interface IHoverOptions { /** * The content to display in the primary section of the hover. The type of text determines the diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 3584e6e6d7b..16b52782c26 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -8,7 +8,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button, IButtonStyles } from 'vs/base/browser/ui/button/button'; import { CountBadge, ICountBadgeStyles } from 'vs/base/browser/ui/countBadge/countBadge'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate, IHoverDelegateOptions, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; import { IKeybindingLabelStyles } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; @@ -30,6 +30,8 @@ import { IInputBox, IKeyMods, IQuickInput, IQuickInputButton, IQuickInputHideEve import { QuickInputBox } from './quickInputBox'; import { QuickInputList, QuickInputListFocus } from './quickInputList'; import { quickInputButtonToAction, renderQuickInputDescription } from './quickInputUtils'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; export interface IQuickInputOptions { idPrefix: string; @@ -46,7 +48,11 @@ export interface IQuickInputOptions { renderers: IListRenderer[], options: IListOptions, ): List; - hoverDelegate?: IHoverDelegate; + /** + * @todo With IHover in vs/editor, can we depend on the service directly + * instead of passing it through a hover delegate? + */ + hoverDelegate: IHoverDelegate; styles: IQuickInputStyles; } @@ -1251,3 +1257,46 @@ export class QuickWidget extends QuickInput implements IQuickWidget { super.update(); } } + +export class QuickInputHoverDelegate implements IHoverDelegate { + private lastHoverHideTime = 0; + readonly placement = 'element'; + + get delay() { + if (Date.now() - this.lastHoverHideTime < 200) { + return 0; // show instantly when a hover was recently shown + } + + return this.configurationService.getValue('workbench.hover.delay'); + } + + constructor( + private readonly configurationService: IConfigurationService, + private readonly hoverService: IHoverService + ) { } + + showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined { + // Only show the hover hint if the content is of a decent size + const showHoverHint = ( + options.content instanceof HTMLElement + ? options.content.textContent ?? '' + : typeof options.content === 'string' + ? options.content + : options.content.value + ).length > 20; + return this.hoverService.showHover({ + ...options, + persistence: { + hideOnKeyDown: false, + }, + appearance: { + showHoverHint, + skipFadeInAnimation: true, + }, + }, focus); + } + + onDidHideHover(): void { + this.lastHoverHideTime = Date.now(); + } +} diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index d66057170f7..db5440e3d84 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -103,13 +103,12 @@ export class QuickInputController extends Disposable { const titleBar = dom.append(container, $('.quick-input-titlebar')); - const actionBarOption = this.options.hoverDelegate ? { hoverDelegate: this.options.hoverDelegate } : undefined; - const leftActionBar = this._register(new ActionBar(titleBar, actionBarOption)); + const leftActionBar = this._register(new ActionBar(titleBar, { hoverDelegate: this.options.hoverDelegate })); leftActionBar.domNode.classList.add('quick-input-left-action-bar'); const title = dom.append(titleBar, $('.quick-input-title')); - const rightActionBar = this._register(new ActionBar(titleBar, actionBarOption)); + const rightActionBar = this._register(new ActionBar(titleBar, { hoverDelegate: this.options.hoverDelegate })); rightActionBar.domNode.classList.add('quick-input-right-action-bar'); const headerContainer = dom.append(container, $('.quick-input-header')); diff --git a/src/vs/platform/quickinput/browser/quickInputList.ts b/src/vs/platform/quickinput/browser/quickInputList.ts index a1ad53c81c9..f5da014dabb 100644 --- a/src/vs/platform/quickinput/browser/quickInputList.ts +++ b/src/vs/platform/quickinput/browser/quickInputList.ts @@ -530,49 +530,47 @@ export class QuickInputList { } })); - if (options.hoverDelegate) { - const delayer = new ThrottledDelayer(options.hoverDelegate.delay); - // onMouseOver triggers every time a new element has been moused over - // even if it's on the same list item. - this.disposables.push(this.list.onMouseOver(async e => { - // If we hover over an anchor element, we don't want to show the hover because - // the anchor may have a tooltip that we want to show instead. - if (e.browserEvent.target instanceof HTMLAnchorElement) { - delayer.cancel(); - return; - } - if ( - // anchors are an exception as called out above so we skip them here - !(e.browserEvent.relatedTarget instanceof HTMLAnchorElement) && - // check if the mouse is still over the same element - dom.isAncestor(e.browserEvent.relatedTarget as Node, e.element?.element as Node) - ) { - return; - } - try { - await delayer.trigger(async () => { - if (e.element) { - this.showHover(e.element); - } - }); - } catch (e) { - // Ignore cancellation errors due to mouse out - if (!isCancellationError(e)) { - throw e; - } - } - })); - this.disposables.push(this.list.onMouseOut(e => { - // onMouseOut triggers every time a new element has been moused over - // even if it's on the same list item. We only want one event, so we - // check if the mouse is still over the same element. - if (dom.isAncestor(e.browserEvent.relatedTarget as Node, e.element?.element as Node)) { - return; - } + const delayer = new ThrottledDelayer(options.hoverDelegate.delay); + // onMouseOver triggers every time a new element has been moused over + // even if it's on the same list item. + this.disposables.push(this.list.onMouseOver(async e => { + // If we hover over an anchor element, we don't want to show the hover because + // the anchor may have a tooltip that we want to show instead. + if (e.browserEvent.target instanceof HTMLAnchorElement) { delayer.cancel(); - })); - this.disposables.push(delayer); - } + return; + } + if ( + // anchors are an exception as called out above so we skip them here + !(e.browserEvent.relatedTarget instanceof HTMLAnchorElement) && + // check if the mouse is still over the same element + dom.isAncestor(e.browserEvent.relatedTarget as Node, e.element?.element as Node) + ) { + return; + } + try { + await delayer.trigger(async () => { + if (e.element) { + this.showHover(e.element); + } + }); + } catch (e) { + // Ignore cancellation errors due to mouse out + if (!isCancellationError(e)) { + throw e; + } + } + })); + this.disposables.push(this.list.onMouseOut(e => { + // onMouseOut triggers every time a new element has been moused over + // even if it's on the same list item. We only want one event, so we + // check if the mouse is still over the same element. + if (dom.isAncestor(e.browserEvent.relatedTarget as Node, e.element?.element as Node)) { + return; + } + delayer.cancel(); + })); + this.disposables.push(delayer); this.disposables.push(this._listElementChecked.event(_ => this.fireCheckedEvents())); this.disposables.push( this._onChangedAllVisibleChecked, @@ -830,9 +828,6 @@ export class QuickInputList { * @param element The element to show the hover for */ private showHover(element: IListElement): void { - if (this.options.hoverDelegate === undefined) { - return; - } if (this._lastHover && !this._lastHover.isDisposed) { this.options.hoverDelegate.onDidHideHover?.(); this._lastHover?.dispose(); diff --git a/src/vs/platform/quickinput/browser/quickInputService.ts b/src/vs/platform/quickinput/browser/quickInputService.ts index 0d6884730e1..db4284cba8c 100644 --- a/src/vs/platform/quickinput/browser/quickInputService.ts +++ b/src/vs/platform/quickinput/browser/quickInputService.ts @@ -18,8 +18,10 @@ import { IInputBox, IInputOptions, IKeyMods, IPickOptions, IQuickInputButton, IQ import { defaultButtonStyles, defaultCountBadgeStyles, defaultInputBoxStyles, defaultKeybindingLabelStyles, defaultProgressBarStyles, defaultToggleStyles, getListStyles } from 'vs/platform/theme/browser/defaultStyles'; import { activeContrastBorder, asCssVariable, pickerGroupBorder, pickerGroupForeground, quickInputBackground, quickInputForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, quickInputTitleBackground, widgetBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; -import { IQuickInputOptions, IQuickInputStyles } from './quickInput'; +import { IQuickInputOptions, IQuickInputStyles, QuickInputHoverDelegate } from './quickInput'; import { QuickInputController, IQuickInputControllerHost } from 'vs/platform/quickinput/browser/quickInputController'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; export class QuickInputService extends Themable implements IQuickInputService { @@ -59,7 +61,9 @@ export class QuickInputService extends Themable implements IQuickInputService { @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService protected readonly contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, - @ILayoutService protected readonly layoutService: ILayoutService + @ILayoutService protected readonly layoutService: ILayoutService, + @IConfigurationService protected readonly configurationService: IConfigurationService, + @IHoverService private readonly hoverService: IHoverService ) { super(themeService); } @@ -86,7 +90,8 @@ export class QuickInputService extends Themable implements IQuickInputService { renderers: IListRenderer[], options: IWorkbenchListOptions ) => this.instantiationService.createInstance(WorkbenchList, user, container, delegate, renderers, options) as List, - styles: this.computeStyles() + styles: this.computeStyles(), + hoverDelegate: new QuickInputHoverDelegate(this.configurationService, this.hoverService) }; const controller = this._register(new QuickInputController({ diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index b50d4ee084c..15bca66e58e 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -30,7 +30,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService'; diff --git a/src/vs/workbench/browser/parts/banner/bannerPart.ts b/src/vs/workbench/browser/parts/banner/bannerPart.ts index b120c945d32..29fb5f7f53a 100644 --- a/src/vs/workbench/browser/parts/banner/bannerPart.ts +++ b/src/vs/workbench/browser/parts/banner/bannerPart.ts @@ -19,7 +19,7 @@ import { Link } from 'vs/platform/opener/browser/link'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Emitter } from 'vs/base/common/event'; import { IBannerItem, IBannerService } from 'vs/workbench/services/banner/browser/bannerService'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 8abd6ec0387..2c8cb8f9840 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -20,12 +20,13 @@ import { Color } from 'vs/base/common/color'; import { BaseActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; -import { IHoverService, IHoverWidget } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { URI } from 'vs/base/common/uri'; import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; +import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; export interface ICompositeBar { diff --git a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts index 4bbbec9d22a..c064ebced0c 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts @@ -17,7 +17,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { fromNow } from 'vs/base/common/date'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { defaultButtonStyles, defaultCheckboxStyles, defaultDialogStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; diff --git a/src/vs/workbench/browser/parts/globalCompositeBar.ts b/src/vs/workbench/browser/parts/globalCompositeBar.ts index 117ac45f02a..d65d239c959 100644 --- a/src/vs/workbench/browser/parts/globalCompositeBar.ts +++ b/src/vs/workbench/browser/parts/globalCompositeBar.ts @@ -35,7 +35,7 @@ import { ISecretStorageService } from 'vs/platform/secrets/common/secrets'; import { AuthenticationSessionInfo, getCurrentAuthenticationSessionInfo } from 'vs/workbench/services/authentication/browser/authenticationService'; import { AuthenticationSessionAccount, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { DEFAULT_ICON } from 'vs/workbench/services/userDataProfile/common/userDataProfileIcons'; diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 568c01bf5a1..e3b6e8d4bed 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -27,7 +27,7 @@ import { assertIsDefined } from 'vs/base/common/types'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { isHighContrast } from 'vs/platform/theme/common/theme'; import { hash } from 'vs/base/common/hash'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IHoverDelegate, IHoverDelegateOptions, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { HideStatusbarEntryAction, ToggleStatusbarEntryVisibilityAction } from 'vs/workbench/browser/parts/statusbar/statusbarActions'; diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 06c7a85a31d..b85b76a8051 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -34,7 +34,7 @@ import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; import { WindowTitle } from 'vs/workbench/browser/parts/titlebar/windowTitle'; import { CommandCenterControl } from 'vs/workbench/browser/parts/titlebar/commandCenterControl'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { ACCOUNTS_ACTIVITY_ID, GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 71e907b8760..bd54589ef44 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -62,7 +62,7 @@ import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme import { Extensions, ITreeItem, ITreeItemLabel, ITreeView, ITreeViewDataProvider, ITreeViewDescriptor, ITreeViewDragAndDropController, IViewBadge, IViewDescriptorService, IViewsRegistry, ResolvableTreeItem, TreeCommand, TreeItemCollapsibleState, TreeViewItemHandleArg, TreeViewPaneHandleArg, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService'; import { CodeDataTransfers, LocalSelectionTransfer } from 'vs/platform/dnd/browser/dnd'; import { toExternalVSDataTransfer } from 'vs/editor/browser/dnd'; @@ -72,7 +72,7 @@ import { AriaRole } from 'vs/base/browser/ui/aria/aria'; import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUtils'; import { ITreeViewsDnDService } from 'vs/editor/common/services/treeViewsDndService'; import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd'; -import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; export class TreeViewPane extends ViewPane { diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts index f0766ee07d3..73833a9fad3 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts @@ -22,7 +22,7 @@ import { getNotificationFromContext } from 'vs/workbench/browser/parts/notificat import { IListService, WorkbenchList } from 'vs/platform/list/browser/listService'; import { NotificationFocusedContext } from 'vs/workbench/common/contextkeys'; import { IAccessibleViewService, IAccessibleViewOptions, AccessibleViewType } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { AccessibilityHelpAction, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { IAction } from 'vs/base/common/actions'; diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 66afb5bfef3..93a4d7599b7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -28,7 +28,7 @@ import { FileAccess } from 'vs/base/common/network'; import { clamp } from 'vs/base/common/numbers'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; -import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { localize } from 'vs/nls'; import { IMenuEntryActionViewItemOptions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 2b244af9e62..e5b1da295f4 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; import { LayoutableEditor, MIN_EDITOR_HEIGHT, SimpleCommentEditor, calculateEditorHeight } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts index bf82a652239..10c45a6f9df 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts @@ -15,7 +15,7 @@ import { CommentNode } from 'vs/workbench/contrib/comments/browser/commentNode'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; -import { IMarkdownRendererOptions, MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { IMarkdownRendererOptions, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index de118bffe70..548157717f3 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -9,7 +9,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import * as languages from 'vs/editor/common/languages'; -import { IMarkdownRendererOptions } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { IMarkdownRendererOptions } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 33e67c5b230..69ffc3d3f6d 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -27,7 +27,7 @@ import { Color } from 'vs/base/common/color'; import { IMatch } from 'vs/base/common/filters'; import { FilterOptions } from 'vs/workbench/contrib/comments/browser/commentsFilterOptions'; import { basename } from 'vs/base/common/resources'; -import { openLinkFromMarkdown } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { openLinkFromMarkdown } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { IStyleOverride } from 'vs/platform/theme/browser/defaultStyles'; import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { ILocalizedString } from 'vs/platform/action/common/action'; diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 556878a5844..6ad048c6170 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -51,7 +51,7 @@ import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPO import { Breakpoint, DataBreakpoint, ExceptionBreakpoint, FunctionBreakpoint, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; const $ = dom.$; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 325b43be484..2145b7dfbee 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -24,7 +24,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { activationTimeIcon, errorIcon, infoIcon, installCountIcon, preReleaseIcon, ratingIcon, remoteIcon, sponsorIcon, starEmptyIcon, starFullIcon, starHalfIcon, syncIgnoredIcon, verifiedPublisherIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { registerColor, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { URI } from 'vs/base/common/uri'; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 6ad0f0cfaf2..907fc93ce83 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -63,7 +63,7 @@ import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { timeout } from 'vs/base/common/async'; import { IHoverDelegate, IHoverDelegateOptions, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { mainWindow } from 'vs/base/browser/window'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts index e30213af3c1..52e87ad8086 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts @@ -29,7 +29,7 @@ import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod import { CellStatusbarAlignment, INotebookCellStatusBarItem } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ITooltipMarkdownString, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index 5899fb3b141..2f32b9c5932 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -21,7 +21,8 @@ import { IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/ import { SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { POLICY_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; -import { IHoverOptions, IHoverService, IHoverWidget } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverOptions, IHoverService } from 'vs/platform/hover/browser/hover'; +import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; const $ = DOM.$; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 5510f254bf8..8a24222eac3 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -34,7 +34,7 @@ import { isIOS } from 'vs/base/common/platform'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { isDefined, isUndefinedOrNull } from 'vs/base/common/types'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { localize } from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index a7f32f80b22..bed24198261 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -51,7 +51,7 @@ import { Button } from 'vs/base/browser/ui/button/button'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { STATUS_BAR_REMOTE_ITEM_BACKGROUND } from 'vs/workbench/common/theme'; import { Codicon } from 'vs/base/common/codicons'; import { defaultButtonStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index f4a5e2bea81..90810f73477 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -76,7 +76,7 @@ import { LabelFuzzyScore } from 'vs/base/browser/ui/tree/abstractTree'; import { Selection } from 'vs/editor/common/core/selection'; import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { createActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { MarkdownRenderer, openLinkFromMarkdown } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { MarkdownRenderer, openLinkFromMarkdown } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { Button, ButtonWithDescription, ButtonWithDropdown } from 'vs/base/browser/ui/button/button'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { RepositoryContextKeys } from 'vs/workbench/contrib/scm/browser/scmViewService'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts index 7022de5de53..45ce76d7b7e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts @@ -24,7 +24,7 @@ import { openContextMenu } from 'vs/workbench/contrib/terminal/browser/terminalC import { TerminalStorageKeys } from 'vs/workbench/contrib/terminal/common/terminalStorageKeys'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { getInstanceHoverInfo } from 'vs/workbench/contrib/terminal/browser/terminalTooltip'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; const $ = dom.$; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 82736672af8..88b4f4e11ec 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -23,7 +23,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { Action } from 'vs/base/common/actions'; import { DEFAULT_LABELS_CONTAINER, IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; import { IDecorationData, IDecorationsProvider, IDecorationsService } from 'vs/workbench/services/decorations/common/decorations'; -import { IHoverAction, IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverAction, IHoverService } from 'vs/platform/hover/browser/hover'; import Severity from 'vs/base/common/severity'; import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IListDragAndDrop, IListDragOverReaction, IListRenderer, ListDragOverEffectPosition, ListDragOverEffectType } from 'vs/base/browser/ui/list/list'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts b/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts index 9121569cb13..ebd4abf5e19 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { asArray } from 'vs/base/common/arrays'; -import { IHoverAction } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverAction } from 'vs/platform/hover/browser/hover'; import { MarkdownString } from 'vs/base/common/htmlContent'; export function getInstanceHoverInfo(instance: ITerminalInstance): { content: MarkdownString; actions: IHoverAction[] } { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index cedb103f398..c5d7591819e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -45,7 +45,7 @@ import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/cap import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Event } from 'vs/base/common/event'; import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { InstanceContext, TerminalContextActionRunner } from 'vs/workbench/contrib/terminal/browser/terminalContextMenu'; diff --git a/src/vs/workbench/contrib/terminal/browser/widgets/terminalHoverWidget.ts b/src/vs/workbench/contrib/terminal/browser/widgets/terminalHoverWidget.ts index f6be9eefb63..7d3cd512684 100644 --- a/src/vs/workbench/contrib/terminal/browser/widgets/terminalHoverWidget.ts +++ b/src/vs/workbench/contrib/terminal/browser/widgets/terminalHoverWidget.ts @@ -9,7 +9,7 @@ import { Widget } from 'vs/base/browser/ui/widget'; import { ITerminalWidget } from 'vs/workbench/contrib/terminal/browser/widgets/widgets'; import * as dom from 'vs/base/browser/dom'; import type { IViewportRange } from '@xterm/xterm'; -import { IHoverTarget, IHoverService, IHoverAction } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverTarget, IHoverService, IHoverAction } from 'vs/platform/hover/browser/hover'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationStyles.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationStyles.ts index 4d475759e0b..2c110b884f0 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationStyles.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationStyles.ts @@ -13,7 +13,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; const enum DecorationStyles { DefaultDimension = 16, diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/links.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/links.ts index 37ba3865fba..b680864e23d 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/links.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/links.ts @@ -5,7 +5,7 @@ import type { IBufferLine, IBufferRange, Terminal } from '@xterm/xterm'; import { URI } from 'vs/base/common/uri'; -import { IHoverAction } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverAction } from 'vs/platform/hover/browser/hover'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; import { IParsedLink } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing'; diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLink.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLink.ts index ad896eb9477..4654d703f1d 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLink.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLink.ts @@ -12,7 +12,7 @@ import { isMacintosh } from 'vs/base/common/platform'; import { Emitter, Event } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TerminalLinkType } from 'vs/workbench/contrib/terminalContrib/links/browser/links'; -import { IHoverAction } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverAction } from 'vs/platform/hover/browser/hover'; export class TerminalLink extends DisposableStore implements ILink { decorations: ILinkDecorations; diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts index aa273465a87..88b07ac643e 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts @@ -26,7 +26,7 @@ import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/wid import { IXtermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private'; import { ITerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/capabilities'; import { ITerminalConfiguration, ITerminalProcessInfo, TERMINAL_CONFIG_SECTION } from 'vs/workbench/contrib/terminal/common/terminal'; -import { IHoverAction } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverAction } from 'vs/platform/hover/browser/hover'; import type { ILink, ILinkProvider, IViewportRange, Terminal } from '@xterm/xterm'; import { convertBufferRangeToViewport } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLinkHelpers'; import { RunOnceScheduler } from 'vs/base/common/async'; diff --git a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts index 54272d86756..b6fe5ee6be8 100644 --- a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts +++ b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts @@ -20,7 +20,7 @@ import { ITestingCoverageBarThresholds, TestingConfigKeys, TestingDisplayedCover import { AbstractFileCoverage, getTotalCoveragePercent } from 'vs/workbench/contrib/testing/common/testCoverage'; import { ITestCoverageService } from 'vs/workbench/contrib/testing/common/testCoverageService'; import { ICoveredCount } from 'vs/workbench/contrib/testing/common/testTypes'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; export interface TestCoverageBarsOptions { /** diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index da72f80aa1d..4110388332e 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -24,7 +24,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { isDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/testing'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { localize } from 'vs/nls'; import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem'; import { MenuEntryActionViewItem, createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index f0bc2c768e1..39e673936b6 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -46,7 +46,7 @@ import { Range } from 'vs/editor/common/core/range'; import { IEditor, IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { IPeekViewService, PeekViewWidget, peekViewResultsBackground, peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/peekView/browser/peekView'; import { localize, localize2 } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index bf5a8baf6ad..b01a40523e8 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -49,7 +49,7 @@ import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from 'vs/ import { MarshalledId } from 'vs/base/common/marshallingIds'; import { isString } from 'vs/base/common/types'; import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; diff --git a/src/vs/workbench/contrib/welcomeDialog/browser/welcomeWidget.ts b/src/vs/workbench/contrib/welcomeDialog/browser/welcomeWidget.ts index 38ef2e32529..4d9159848bd 100644 --- a/src/vs/workbench/contrib/welcomeDialog/browser/welcomeWidget.ts +++ b/src/vs/workbench/contrib/welcomeDialog/browser/welcomeWidget.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; import { $, append, hide } from 'vs/base/browser/dom'; import { MarkdownString } from 'vs/base/common/htmlContent'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 04ddd988865..85a3eebf4c2 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -29,7 +29,7 @@ import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import 'vs/css!./media/gettingStarted'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { localize } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ICommandService } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index e3854eefde7..8ebc1bf861c 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -23,7 +23,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { NativeMenubarControl } from 'vs/workbench/electron-sandbox/parts/titlebar/menubarControl'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IEditorGroupsContainer, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; diff --git a/src/vs/workbench/services/quickinput/browser/quickInputService.ts b/src/vs/workbench/services/quickinput/browser/quickInputService.ts index 793f3bffe37..463bea822b2 100644 --- a/src/vs/workbench/services/quickinput/browser/quickInputService.ts +++ b/src/vs/workbench/services/quickinput/browser/quickInputService.ts @@ -14,24 +14,22 @@ import { QuickInputService as BaseQuickInputService } from 'vs/platform/quickinp import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { InQuickPickContextKey } from 'vs/workbench/browser/quickaccess'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; -import { IHoverDelegate, IHoverDelegateOptions, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; export class QuickInputService extends BaseQuickInputService { - private readonly hoverDelegate = new QuickInputHoverDelegate(this.configurationService, this.hoverService); private readonly inQuickInputContext = InQuickPickContextKey.bindTo(this.contextKeyService); constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, + @IConfigurationService configurationService: IConfigurationService, @IInstantiationService instantiationService: IInstantiationService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, @ILayoutService layoutService: ILayoutService, - @IHoverService private readonly hoverService: IHoverService + @IHoverService hoverService: IHoverService ) { - super(instantiationService, contextKeyService, themeService, layoutService); + super(instantiationService, contextKeyService, themeService, layoutService, configurationService, hoverService); this.registerListeners(); } @@ -45,52 +43,8 @@ export class QuickInputService extends BaseQuickInputService { return super.createController(this.layoutService, { ignoreFocusOut: () => !this.configurationService.getValue('workbench.quickOpen.closeOnFocusLost'), backKeybindingLabel: () => this.keybindingService.lookupKeybinding('workbench.action.quickInputBack')?.getLabel() || undefined, - hoverDelegate: this.hoverDelegate }); } } -class QuickInputHoverDelegate implements IHoverDelegate { - private lastHoverHideTime = 0; - readonly placement = 'element'; - - get delay() { - if (Date.now() - this.lastHoverHideTime < 200) { - return 0; // show instantly when a hover was recently shown - } - - return this.configurationService.getValue('workbench.hover.delay'); - } - - constructor( - private readonly configurationService: IConfigurationService, - private readonly hoverService: IHoverService - ) { } - - showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined { - // Only show the hover hint if the content is of a decent size - const showHoverHint = ( - options.content instanceof HTMLElement - ? options.content.textContent ?? '' - : typeof options.content === 'string' - ? options.content - : options.content.value - ).length > 20; - return this.hoverService.showHover({ - ...options, - persistence: { - hideOnKeyDown: false, - }, - appearance: { - showHoverHint, - skipFadeInAnimation: true, - }, - }, focus); - } - - onDidHideHover(): void { - this.lastHoverHideTime = Date.now(); - } -} - registerSingleton(IQuickInputService, QuickInputService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index 6f09bcb2cfe..d16d9793244 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -72,7 +72,7 @@ import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { showWindowLogActionId } from 'vs/workbench/services/log/common/logConstants'; import { ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; import { ThemeIcon } from 'vs/base/common/themables'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { DEFAULT_ICON, ICONS } from 'vs/workbench/services/userDataProfile/common/userDataProfileIcons'; import { WorkbenchIconSelectBox } from 'vs/workbench/services/userDataProfile/browser/iconSelectBox'; diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index a80fd170b01..b7f8f32f918 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -162,13 +162,14 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { EnablementState, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { InstallVSIXOptions, ILocalExtension, IGalleryExtension, InstallOptions, IExtensionIdentifier, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant, Metadata, InstallExtensionResult, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Codicon } from 'vs/base/common/codicons'; -import { IHoverOptions, IHoverService, IHoverWidget } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverOptions, IHoverService } from 'vs/platform/hover/browser/hover'; import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; import { IRemoteSocketFactoryService, RemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; import { EditorParts } from 'vs/workbench/browser/parts/editor/editorParts'; import { TestAccessibleNotificationService } from 'vs/workbench/contrib/accessibility/browser/accessibleNotificationService'; import { mainWindow } from 'vs/base/browser/window'; import { IMarkerService } from 'vs/platform/markers/common/markers'; +import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined, undefined); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 7e1f993f344..1694e0f75e7 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -102,7 +102,7 @@ import 'vs/workbench/services/views/browser/viewDescriptorService'; import 'vs/workbench/services/quickinput/browser/quickInputService'; import 'vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService'; import 'vs/workbench/services/authentication/browser/authenticationService'; -import 'vs/workbench/services/hover/browser/hoverService'; +import 'vs/editor/browser/services/hoverService'; import 'vs/workbench/services/assignment/common/assignmentService'; import 'vs/workbench/services/outline/browser/outlineService'; import 'vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl'; From 4aa96c1c34725d5bb2b3f56326448ed15ecd69bf Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 4 Jan 2024 11:12:28 -0600 Subject: [PATCH 0063/1897] Revert "add `alert` so braille users can detect events " --- .../standalone/browser/standaloneServices.ts | 5 +- .../accessibility/common/accessibility.ts | 19 +--- .../browser/accessibilityConfiguration.ts | 104 +----------------- .../browser/accessibleNotificationService.ts | 64 +++-------- .../browser/audioCueDebuggerContribution.ts | 6 +- .../audioCueLineFeatureContribution.ts | 17 ++- .../browser/audioCues.contribution.ts | 2 +- .../chat/browser/chatAccessibilityService.ts | 5 +- .../browser/inlayHintsAccessibilty.ts | 6 +- .../notebookExecutionStateServiceImpl.ts | 8 +- .../tasks/browser/taskTerminalStatus.ts | 12 +- .../terminal/browser/terminalInstance.ts | 7 +- .../terminal/browser/xterm/decorationAddon.ts | 6 +- .../quickFix/browser/quickFixAddon.ts | 7 +- 14 files changed, 60 insertions(+), 208 deletions(-) diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 221d66ba4a3..61e84831376 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -1077,8 +1077,9 @@ class StandaloneAudioService implements IAudioCueService { class StandaloneAccessibleNotificationService implements IAccessibleNotificationService { _serviceBrand: undefined; - notify(event: AccessibleNotificationEvent, userGesture?: boolean | undefined): void { } - notifyLineChanges(event: AccessibleNotificationEvent[]): void { } + notify(event: AccessibleNotificationEvent, userGesture?: boolean | undefined): void { + // NOOP + } } export interface IEditorOverrideServices { diff --git a/src/vs/platform/accessibility/common/accessibility.ts b/src/vs/platform/accessibility/common/accessibility.ts index 9a353cb0044..55b6f1ba0c1 100644 --- a/src/vs/platform/accessibility/common/accessibility.ts +++ b/src/vs/platform/accessibility/common/accessibility.ts @@ -55,26 +55,11 @@ export const IAccessibleNotificationService = createDecorator = new Map(); + private _events: Map = new Map(); constructor( @IAudioCueService private readonly _audioCueService: IAudioCueService, @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -27,61 +23,29 @@ export class AccessibleNotificationService extends Disposable implements IAccess @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, @ILogService private readonly _logService: ILogService) { super(); - this._events.set(AccessibleNotificationEvent.Clear, { audioCue: AudioCue.clear, alertMessage: localize('cleared', "Cleared"), alertSetting: AccessibilityAlertSettingId.Clear }); + this._events.set(AccessibleNotificationEvent.Clear, { audioCue: AudioCue.clear, alertMessage: localize('cleared', "Cleared") }); this._events.set(AccessibleNotificationEvent.Save, { audioCue: AudioCue.save, alertMessage: localize('saved', "Saved"), alertSetting: AccessibilityAlertSettingId.Save }); this._events.set(AccessibleNotificationEvent.Format, { audioCue: AudioCue.format, alertMessage: localize('formatted', "Formatted"), alertSetting: AccessibilityAlertSettingId.Format }); - this._events.set(AccessibleNotificationEvent.Breakpoint, { audioCue: AudioCue.break, alertMessage: localize('breakpoint', "Breakpoint"), alertSetting: AccessibilityAlertSettingId.Breakpoint }); - this._events.set(AccessibleNotificationEvent.Error, { audioCue: AudioCue.error, alertMessage: localize('error', "Error"), alertSetting: AccessibilityAlertSettingId.Error }); - this._events.set(AccessibleNotificationEvent.Warning, { audioCue: AudioCue.warning, alertMessage: localize('warning', "Warning"), alertSetting: AccessibilityAlertSettingId.Warning }); - this._events.set(AccessibleNotificationEvent.Folded, { audioCue: AudioCue.foldedArea, alertMessage: localize('foldedArea', "Folded Area"), alertSetting: AccessibilityAlertSettingId.FoldedArea }); - this._events.set(AccessibleNotificationEvent.TerminalQuickFix, { audioCue: AudioCue.terminalQuickFix, alertMessage: localize('terminalQuickFix', "Quick Fix"), alertSetting: AccessibilityAlertSettingId.TerminalQuickFix }); - this._events.set(AccessibleNotificationEvent.TerminalBell, { audioCue: AudioCue.terminalBell, alertMessage: localize('terminalBell', "Terminal Bell"), alertSetting: AccessibilityAlertSettingId.TerminalBell }); - this._events.set(AccessibleNotificationEvent.TerminalCommandFailed, { audioCue: AudioCue.terminalCommandFailed, alertMessage: localize('terminalCommandFailed', "Terminal Command Failed"), alertSetting: AccessibilityAlertSettingId.TerminalCommandFailed }); - this._events.set(AccessibleNotificationEvent.TaskFailed, { audioCue: AudioCue.taskFailed, alertMessage: localize('taskFailed', "Task Failed"), alertSetting: AccessibilityAlertSettingId.TaskFailed }); - this._events.set(AccessibleNotificationEvent.TaskCompleted, { audioCue: AudioCue.taskCompleted, alertMessage: localize('taskCompleted', "Task Completed"), alertSetting: AccessibilityAlertSettingId.TaskCompleted }); - this._events.set(AccessibleNotificationEvent.ChatRequestSent, { audioCue: AudioCue.chatRequestSent, alertMessage: localize('chatRequestSent', "Chat Request Sent"), alertSetting: AccessibilityAlertSettingId.ChatRequestSent }); - this._events.set(AccessibleNotificationEvent.NotebookCellCompleted, { audioCue: AudioCue.notebookCellCompleted, alertMessage: localize('notebookCellCompleted', "Notebook Cell Completed"), alertSetting: AccessibilityAlertSettingId.NotebookCellCompleted }); - this._events.set(AccessibleNotificationEvent.NotebookCellFailed, { audioCue: AudioCue.notebookCellFailed, alertMessage: localize('notebookCellFailed', "Notebook Cell Failed"), alertSetting: AccessibilityAlertSettingId.NotebookCellFailed }); - this._events.set(AccessibleNotificationEvent.OnDebugBreak, { audioCue: AudioCue.onDebugBreak, alertMessage: localize('onDebugBreak', "On Debug Break"), alertSetting: AccessibilityAlertSettingId.OnDebugBreak }); - this._register(this._workingCopyService.onDidSave((e) => this._notifyBasedOnUserGesture(AccessibleNotificationEvent.Save, e.reason === SaveReason.EXPLICIT))); + this._register(this._workingCopyService.onDidSave((e) => this._notify(AccessibleNotificationEvent.Save, e.reason === SaveReason.EXPLICIT))); } - notify(event: AccessibleNotificationEvent, userGesture?: boolean, forceSound?: boolean, allowManyInParallel?: boolean): void { + notify(event: AccessibleNotificationEvent, userGesture?: boolean): void { if (event === AccessibleNotificationEvent.Format) { - return this._notifyBasedOnUserGesture(AccessibleNotificationEvent.Format, userGesture); + return this._notify(event, userGesture); } - const { audioCue, alertMessage, alertSetting } = this._events.get(event)!; - const audioCueSetting = this._configurationService.getValue(audioCue.settingsKey); - if (audioCueSetting === 'on' || audioCueSetting === 'auto' && this._accessibilityService.isScreenReaderOptimized() || forceSound) { + const { audioCue, alertMessage } = this._events.get(event)!; + const audioCueValue = this._configurationService.getValue(audioCue.settingsKey); + if (audioCueValue === 'on' || audioCueValue === 'auto' && this._accessibilityService.isScreenReaderOptimized()) { this._logService.debug('AccessibleNotificationService playing sound: ', audioCue.name); - this._audioCueService.playSound(audioCue.sound.getSound(), allowManyInParallel); - } - - if (alertSetting && this._configurationService.getValue(alertSetting) === true) { + this._audioCueService.playAudioCue(audioCue); + } else { this._logService.debug('AccessibleNotificationService alerting: ', alertMessage); this._accessibilityService.alert(alertMessage); } } - /** - * Line feature contributions can use this to notify the user of changes to the line. - */ - notifyLineChanges(events: AccessibleNotificationEvent[]): void { - const audioCues = events.map(e => this._events.get(e)!.audioCue); - if (audioCues.length) { - this._logService.debug('AccessibleNotificationService playing sounds if enabled: ', events.map(e => this._events.get(e)!.audioCue.name).join(', ')); - this._audioCueService.playAudioCues(audioCues); - } - - const alerts = events.filter(e => this._configurationService.getValue(this._events.get(e)!.alertSetting!) === true).map(e => this._events.get(e)?.alertMessage); - if (alerts.length) { - this._logService.debug('AccessibleNotificationService alerting: ', alerts.join(', ')); - this._accessibilityService.alert(alerts.join(', ')); - } - } - - private _notifyBasedOnUserGesture(event: AccessibleNotificationEvent, userGesture?: boolean): void { + private _notify(event: AccessibleNotificationEvent, userGesture?: boolean): void { const { audioCue, alertMessage, alertSetting } = this._events.get(event)!; if (!alertSetting) { return; @@ -91,6 +55,11 @@ export class AccessibleNotificationService extends Disposable implements IAccess this._logService.debug('AccessibleNotificationService playing sound: ', audioCue.name); // Play sound bypasses the usual audio cue checks IE screen reader optimized, auto, etc. this._audioCueService.playSound(audioCue.sound.getSound(), true); + return; + } + if (audioCueSetting !== 'never') { + // Never do both sound and alert + return; } const alertSettingValue: NotificationSetting = this._configurationService.getValue(alertSetting); if (this._shouldNotify(alertSettingValue, userGesture)) { @@ -110,5 +79,4 @@ export class TestAccessibleNotificationService extends Disposable implements IAc declare readonly _serviceBrand: undefined; notify(event: AccessibleNotificationEvent, userGesture?: boolean): void { } - notifyLineChanges(event: AccessibleNotificationEvent[]): void { } } diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts index 140e1f673dc..e5051feb3a4 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts @@ -5,7 +5,6 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { autorunWithStore, observableFromEvent } from 'vs/base/common/observable'; -import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { IAudioCueService, AudioCue, AudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; @@ -16,8 +15,7 @@ export class AudioCueLineDebuggerContribution constructor( @IDebugService debugService: IDebugService, - @IAudioCueService audioCueService: AudioCueService, - @IAccessibleNotificationService private readonly accessibleNotificationService: IAccessibleNotificationService + @IAudioCueService private readonly audioCueService: AudioCueService, ) { super(); @@ -62,7 +60,7 @@ export class AudioCueLineDebuggerContribution const stoppedDetails = session.getStoppedDetails(); const BREAKPOINT_STOP_REASON = 'breakpoint'; if (stoppedDetails && stoppedDetails.reason === BREAKPOINT_STOP_REASON) { - this.accessibleNotificationService.notify(AccessibleNotificationEvent.OnDebugBreak); + this.audioCueService.playAudioCue(AudioCue.onDebugBreak); } }); diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts index bd42e67e35d..c9421fd053d 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts @@ -12,7 +12,6 @@ import { Position } from 'vs/editor/common/core/position'; import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; import { ITextModel } from 'vs/editor/common/model'; import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; -import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -27,8 +26,8 @@ export class AudioCueLineFeatureContribution private readonly store = this._register(new DisposableStore()); private readonly features: LineFeature[] = [ - this.instantiationService.createInstance(MarkerLineFeature, AccessibleNotificationEvent.Error, AudioCue.error, MarkerSeverity.Error), - this.instantiationService.createInstance(MarkerLineFeature, AccessibleNotificationEvent.Warning, AudioCue.warning, MarkerSeverity.Warning), + this.instantiationService.createInstance(MarkerLineFeature, AudioCue.error, MarkerSeverity.Error), + this.instantiationService.createInstance(MarkerLineFeature, AudioCue.warning, MarkerSeverity.Warning), this.instantiationService.createInstance(FoldedAreaLineFeature), this.instantiationService.createInstance(BreakpointLineFeature), ]; @@ -42,8 +41,7 @@ export class AudioCueLineFeatureContribution @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IAudioCueService private readonly audioCueService: IAudioCueService, - @IConfigurationService private readonly _configurationService: IConfigurationService, - @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService + @IConfigurationService private readonly _configurationService: IConfigurationService ) { super(); @@ -157,7 +155,8 @@ export class AudioCueLineFeatureContribution newValue?.featureStates.get(feature) && (!lastValue?.featureStates?.get(feature) || newValue.lineNumber !== lastValue.lineNumber) ); - this._accessibleNotificationService.notifyLineChanges(newFeatures.map(feature => feature.event)); + + this.audioCueService.playAudioCues(newFeatures.map(f => f.audioCue)); }) ); } @@ -165,7 +164,6 @@ export class AudioCueLineFeatureContribution interface LineFeature { audioCue: AudioCue; - event: AccessibleNotificationEvent; debounceWhileTyping?: boolean; getObservableState( editor: ICodeEditor, @@ -181,7 +179,6 @@ class MarkerLineFeature implements LineFeature { public readonly debounceWhileTyping = true; private _previousLine: number = 0; constructor( - public readonly event: AccessibleNotificationEvent, public readonly audioCue: AudioCue, private readonly severity: MarkerSeverity, @IMarkerService private readonly markerService: IMarkerService, @@ -213,7 +210,7 @@ class MarkerLineFeature implements LineFeature { class FoldedAreaLineFeature implements LineFeature { public readonly audioCue = AudioCue.foldedArea; - public readonly event = AccessibleNotificationEvent.Folded; + getObservableState(editor: ICodeEditor, model: ITextModel): IObservable { const foldingController = FoldingController.get(editor); if (!foldingController) { @@ -240,7 +237,7 @@ class FoldedAreaLineFeature implements LineFeature { class BreakpointLineFeature implements LineFeature { public readonly audioCue = AudioCue.break; - public readonly event = AccessibleNotificationEvent.Breakpoint; + constructor(@IDebugService private readonly debugService: IDebugService) { } getObservableState(editor: ICodeEditor, model: ITextModel): IObservable { diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index fef18d00448..efc297855f2 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -133,7 +133,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis default: 'off' }, 'audioCues.clear': { - 'description': localize('audioCues.clear', "Plays a sound when a feature is cleared (for example, the terminal, Debug Console, or Output channel)."), + 'description': localize('audioCues.clear', "Plays a sound when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), ...audioCueFeatureBase, default: 'off' }, diff --git a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts index 5e81f842aa3..fa522294c26 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts @@ -6,7 +6,6 @@ import { status } from 'vs/base/browser/ui/aria/aria'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Disposable, DisposableMap, IDisposable } from 'vs/base/common/lifecycle'; -import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; @@ -20,12 +19,12 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi private _requestId: number = 0; - constructor(@IAudioCueService private readonly _audioCueService: IAudioCueService, @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService, @IInstantiationService private readonly _instantiationService: IInstantiationService) { + constructor(@IAudioCueService private readonly _audioCueService: IAudioCueService, @IInstantiationService private readonly _instantiationService: IInstantiationService) { super(); } acceptRequest(): number { this._requestId++; - this._accessibleNotificationService.notify(AccessibleNotificationEvent.ChatRequestSent, undefined, undefined, true); + this._audioCueService.playAudioCue(AudioCue.chatRequestSent, { allowManyInParallel: true }); this._pendingCueMap.set(this._requestId, this._instantiationService.createInstance(AudioCueScheduler)); return this._requestId; } diff --git a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts index 9061c940cdb..3f5e22cb5cc 100644 --- a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts +++ b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts @@ -14,8 +14,8 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InlayHintItem, asCommandLink } from 'vs/editor/contrib/inlayHints/browser/inlayHints'; import { InlayHintsController } from 'vs/editor/contrib/inlayHints/browser/inlayHintsController'; import { localize } from 'vs/nls'; -import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -40,7 +40,7 @@ export class InlayHintsAccessibility implements IEditorContribution { constructor( private readonly _editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, - @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService, + @IAudioCueService private readonly _audioCueService: IAudioCueService, @IInstantiationService private readonly _instaService: IInstantiationService, ) { this._ariaElement = document.createElement('span'); @@ -156,7 +156,7 @@ export class InlayHintsAccessibility implements IEditorContribution { const line = this._editor.getPosition().lineNumber; const hints = InlayHintsController.get(this._editor)?.getInlayHintsForLine(line); if (!hints || hints.length === 0) { - this._accessibleNotificationService.notify(AccessibleNotificationEvent.NoInlayHints); + this._audioCueService.playAudioCue(AudioCue.noInlayHints); } else { this._read(line, hints); } diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts index b040c9a74ea..a5736966b8a 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts @@ -9,7 +9,7 @@ import { ResourceMap } from 'vs/base/common/map'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; @@ -38,7 +38,7 @@ export class NotebookExecutionStateService extends Disposable implements INotebo @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILogService private readonly _logService: ILogService, @INotebookService private readonly _notebookService: INotebookService, - @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService + @IAudioCueService private readonly _audioCueService: IAudioCueService ) { super(); } @@ -112,11 +112,11 @@ export class NotebookExecutionStateService extends Disposable implements INotebo if (lastRunSuccess !== undefined) { if (lastRunSuccess) { if (this._executions.size === 0) { - this._accessibleNotificationService.notify(AccessibleNotificationEvent.NotebookCellCompleted); + this._audioCueService.playAudioCue(AudioCue.notebookCellCompleted); } this._clearLastFailedCell(notebookUri); } else { - this._accessibleNotificationService.notify(AccessibleNotificationEvent.NotebookCellFailed); + this._audioCueService.playAudioCue(AudioCue.notebookCellFailed); this._setLastFailedCell(notebookUri, cellHandle); } } diff --git a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts index ac6f1d174e9..df714767124 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts @@ -14,8 +14,8 @@ import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/termina import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { spinningLoading } from 'vs/platform/theme/common/iconRegistry'; import { IMarker } from 'vs/platform/terminal/common/capabilities/capabilities'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { ITerminalStatus } from 'vs/workbench/contrib/terminal/common/terminal'; -import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; interface ITerminalData { terminal: ITerminalInstance; @@ -40,7 +40,7 @@ const INFO_INACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID export class TaskTerminalStatus extends Disposable { private terminalMap: Map = new Map(); private _marker: IMarker | undefined; - constructor(@ITaskService taskService: ITaskService, @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService) { + constructor(@ITaskService taskService: ITaskService, @IAudioCueService private readonly _audioCueService: IAudioCueService) { super(); this._register(taskService.onDidStateChange((event) => { switch (event.kind) { @@ -95,7 +95,7 @@ export class TaskTerminalStatus extends Disposable { terminalData.taskRunEnded = true; terminalData.terminal.statusList.remove(terminalData.status); if ((event.exitCode === 0) && (terminalData.problemMatcher.numberOfMatches === 0)) { - this._accessibleNotificationService.notify(AccessibleNotificationEvent.TaskCompleted); + this._audioCueService.playAudioCue(AudioCue.taskCompleted); if (terminalData.task.configurationProperties.isBackground) { for (const status of terminalData.terminal.statusList.statuses) { terminalData.terminal.statusList.remove(status); @@ -104,7 +104,7 @@ export class TaskTerminalStatus extends Disposable { terminalData.terminal.statusList.add(SUCCEEDED_TASK_STATUS); } } else if (event.exitCode || terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Error) { - this._accessibleNotificationService.notify(AccessibleNotificationEvent.TaskFailed); + this._audioCueService.playAudioCue(AudioCue.taskFailed); terminalData.terminal.statusList.add(FAILED_TASK_STATUS); } else if (terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Warning) { terminalData.terminal.statusList.add(WARNING_TASK_STATUS); @@ -120,10 +120,10 @@ export class TaskTerminalStatus extends Disposable { } terminalData.terminal.statusList.remove(terminalData.status); if (terminalData.problemMatcher.numberOfMatches === 0) { - this._accessibleNotificationService.notify(AccessibleNotificationEvent.TaskCompleted); + this._audioCueService.playAudioCue(AudioCue.taskCompleted); terminalData.terminal.statusList.add(SUCCEEDED_INACTIVE_TASK_STATUS); } else if (terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Error) { - this._accessibleNotificationService.notify(AccessibleNotificationEvent.TaskFailed); + this._audioCueService.playAudioCue(AudioCue.taskFailed); terminalData.terminal.statusList.add(FAILED_INACTIVE_TASK_STATUS); } else if (terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Warning) { terminalData.terminal.statusList.add(WARNING_INACTIVE_TASK_STATUS); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index c8c7226475f..4aefa9ab3ff 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -25,7 +25,8 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { URI } from 'vs/base/common/uri'; import { TabFocus } from 'vs/editor/browser/config/tabFocus'; import * as nls from 'vs/nls'; -import { AccessibleNotificationEvent, IAccessibilityService, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -361,7 +362,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { @ITelemetryService private readonly _telemetryService: ITelemetryService, @IOpenerService private readonly _openerService: IOpenerService, @ICommandService private readonly _commandService: ICommandService, - @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService, + @IAudioCueService private readonly _audioCueService: IAudioCueService, @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService ) { super(); @@ -758,7 +759,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { icon: Codicon.bell, tooltip: nls.localize('bellStatus', "Bell") }, this._configHelper.config.bellDuration); - this._accessibleNotificationService.notify(AccessibleNotificationEvent.TerminalBell, undefined, true); + this._audioCueService.playSound(AudioCue.terminalBell.sound.getSound()); } })); }, 1000, this._store); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index 41c7f59ac4a..da0a7fc9d0c 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -9,6 +9,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -25,7 +26,6 @@ import { DecorationSelector, TerminalDecorationHoverManager, updateLayout } from import { TERMINAL_COMMAND_DECORATION_DEFAULT_BACKGROUND_COLOR, TERMINAL_COMMAND_DECORATION_ERROR_BACKGROUND_COLOR, TERMINAL_COMMAND_DECORATION_SUCCESS_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import type { IDecoration, ITerminalAddon, Terminal } from '@xterm/xterm'; -import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; interface IDisposableDecoration { decoration: IDecoration; disposables: IDisposable[]; exitCode?: number; markProperties?: IMarkProperties } @@ -52,7 +52,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { @ILifecycleService lifecycleService: ILifecycleService, @ICommandService private readonly _commandService: ICommandService, @IInstantiationService instantiationService: IInstantiationService, - @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService, + @IAudioCueService private readonly _audioCueService: IAudioCueService, @INotificationService private readonly _notificationService: INotificationService ) { super(); @@ -219,7 +219,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { commandDetectionListeners.push(capability.onCommandFinished(command => { this.registerCommandDecoration(command); if (command.exitCode) { - this._accessibleNotificationService.notify(AccessibleNotificationEvent.TerminalCommandFailed); + this._audioCueService.playAudioCue(AudioCue.terminalCommandFailed); } })); // Command invalidated diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts index 55327132258..ea488b6fe0e 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts @@ -20,6 +20,7 @@ import type { IDecoration, Terminal } from '@xterm/xterm'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IActionWidgetService } from 'vs/platform/actionWidget/browser/actionWidget'; import { ActionSet } from 'vs/platform/actionWidget/common/actionWidget'; import { getLinesForCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; @@ -34,7 +35,6 @@ import { CodeActionKind } from 'vs/editor/contrib/codeAction/common/types'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; const quickFixClasses = [ DecorationSelector.QuickFix, @@ -77,7 +77,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, @ITerminalQuickFixService private readonly _quickFixService: ITerminalQuickFixService, @ICommandService private readonly _commandService: ICommandService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService, + @IAudioCueService private readonly _audioCueService: IAudioCueService, @IOpenerService private readonly _openerService: IOpenerService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IExtensionService private readonly _extensionService: IExtensionService, @@ -284,7 +284,8 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, e.classList.add(...ThemeIcon.asClassNameArray(isExplainOnly ? Codicon.sparkle : Codicon.lightBulb)); updateLayout(this._configurationService, e); - this._accessibleNotificationService.notify(AccessibleNotificationEvent.TerminalQuickFix); + this._audioCueService.playAudioCue(AudioCue.terminalQuickFix); + const parentElement = (e.closest('.xterm') as HTMLElement).parentElement; if (!parentElement) { return; From 3adde6e20b630e8c5ed468971690328fb6249a30 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 4 Jan 2024 11:00:50 -0800 Subject: [PATCH 0064/1897] Supporting aux window for notebook editors (#201738) * Supporting aux window for notebook editors * fix interactive window * better handling of active container --- .../interactive/browser/interactiveEditor.ts | 3 ++- .../notebook/browser/notebookBrowser.ts | 2 ++ .../notebook/browser/notebookEditor.ts | 3 ++- .../notebook/browser/notebookEditorWidget.ts | 7 ++++++- .../browser/services/notebookEditorService.ts | 3 ++- .../services/notebookEditorServiceImpl.ts | 20 +++++++++++++++---- .../notebook/common/notebookEditorInput.ts | 2 +- .../contrib/webview/browser/webviewElement.ts | 4 ++-- 8 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index a1708b5113b..b15b5a16a66 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -63,6 +63,7 @@ import { INTERACTIVE_WINDOW_EDITOR_ID } from 'vs/workbench/contrib/notebook/comm import 'vs/css!./interactiveEditor'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { deepClone } from 'vs/base/common/objects'; +import { mainWindow } from 'vs/base/browser/window'; const DECORATION_KEY = 'interactiveInputDecoration'; const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState'; @@ -385,7 +386,7 @@ export class InteractiveEditor extends EditorPane { MarkerController.ID ]), options: this._notebookOptions - }); + }, undefined, this._rootElement ? DOM.getWindow(this._rootElement) : mainWindow); this._codeEditorWidget = this._instantiationService.createInstance(CodeEditorWidget, this._inputEditorContainer, this._editorOptions, { ...{ diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 8fa9bf480d8..d1841f036fe 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CodeWindow } from 'vs/base/browser/window'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -377,6 +378,7 @@ export interface INotebookEditorCreationOptions { cellExecutePrimary?: MenuId; }; readonly options?: NotebookOptions; + readonly codeWindow?: CodeWindow; } export interface INotebookWebviewMessage { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 2370b8a2972..fdc168b7d89 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -5,6 +5,7 @@ import * as DOM from 'vs/base/browser/dom'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { mainWindow } from 'vs/base/browser/window'; import { IAction, toAction } from 'vs/base/common/actions'; import { timeout } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -205,7 +206,7 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { // we need to hide it before getting a new widget this._widget.value?.onWillHide(); - this._widget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, group, input, undefined, this._pagePosition?.dimension); + this._widget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, group, input, undefined, this._pagePosition?.dimension, this._rootElement ? DOM.getWindow(this._rootElement) : mainWindow); if (this._rootElement && this._widget.value!.getDomNode()) { this._rootElement.setAttribute('aria-flowto', this._widget.value!.getDomNode().id || ''); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 424862aeca3..6195bc90217 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -404,7 +404,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._overlayContainer.classList.add('notebook-editor'); this._overlayContainer.style.visibility = 'hidden'; - this.layoutService.mainContainer.appendChild(this._overlayContainer); + if (creationOptions.codeWindow) { + this.layoutService.getContainer(creationOptions.codeWindow).appendChild(this._overlayContainer); + } else { + this.layoutService.mainContainer.appendChild(this._overlayContainer); + } + this._createBody(this._overlayContainer); this._generateFontInfo(); this._isVisible = true; diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts index c9f22f1f832..ec20db8d5cf 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CodeWindow } from 'vs/base/browser/window'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; @@ -21,7 +22,7 @@ export interface IBorrowValue { export interface INotebookEditorService { _serviceBrand: undefined; - retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput, creationOptions?: INotebookEditorCreationOptions, dimension?: Dimension): IBorrowValue; + retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput, creationOptions?: INotebookEditorCreationOptions, dimension?: Dimension, codeWindow?: CodeWindow): IBorrowValue; retrieveExistingWidgetFromURI(resource: URI): IBorrowValue | undefined; retrieveAllExistingWidgets(): IBorrowValue[]; diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts index e5baf7c6af3..46f2bd5e3bc 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CodeWindow } from 'vs/base/browser/window'; import { ResourceMap } from 'vs/base/common/map'; import { getDefaultNotebookCreationOptions, NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, IAuxiliaryEditorPart } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { isCompositeNotebookEditorInput, NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; @@ -36,7 +37,7 @@ export class NotebookEditorWidgetService implements INotebookEditorService { private readonly _borrowableEditors = new Map>(); constructor( - @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorGroupsService readonly editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IContextKeyService contextKeyService: IContextKeyService ) { @@ -126,6 +127,13 @@ export class NotebookEditorWidgetService implements INotebookEditorService { } private _allowWidgetMove(input: NotebookEditorInput, sourceID: GroupIdentifier, targetID: GroupIdentifier): void { + const sourcePart = this.editorGroupService.getPart(sourceID); + const targetPart = this.editorGroupService.getPart(targetID); + + if ((sourcePart as IAuxiliaryEditorPart).windowId !== (targetPart as IAuxiliaryEditorPart).windowId) { + return; + } + const targetWidget = this._borrowableEditors.get(targetID)?.get(input.resource); if (targetWidget) { // not needed @@ -168,14 +176,18 @@ export class NotebookEditorWidgetService implements INotebookEditorService { return ret; } - retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput, creationOptions?: INotebookEditorCreationOptions, initialDimension?: Dimension): IBorrowValue { + retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput, creationOptions?: INotebookEditorCreationOptions, initialDimension?: Dimension, codeWindow?: CodeWindow): IBorrowValue { let value = this._borrowableEditors.get(group.id)?.get(input.resource); if (!value) { // NEW widget const instantiationService = accessor.get(IInstantiationService); - const widget = instantiationService.createInstance(NotebookEditorWidget, creationOptions ?? getDefaultNotebookCreationOptions(), initialDimension); + const ctorOptions = creationOptions ?? getDefaultNotebookCreationOptions(); + const widget = instantiationService.createInstance(NotebookEditorWidget, { + ...ctorOptions, + codeWindow: codeWindow ?? ctorOptions.codeWindow, + }, initialDimension); const token = this._tokenPool++; value = { widget, token }; diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts index 7add5409fe2..6053741b7a8 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts @@ -129,7 +129,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { } override get capabilities(): EditorInputCapabilities { - let capabilities = EditorInputCapabilities.AuxWindowUnsupported; + let capabilities = EditorInputCapabilities.None; if (this.resource.scheme === Schemas.untitled) { capabilities |= EditorInputCapabilities.Untitled; diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index b442277e920..a3f4989a1da 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isFirefox } from 'vs/base/browser/browser'; -import { addDisposableListener, EventType } from 'vs/base/browser/dom'; +import { addDisposableListener, EventType, getActiveWindow } from 'vs/base/browser/dom'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { ThrottledDelayer } from 'vs/base/common/async'; import { streamToBuffer, VSBufferReadableStream } from 'vs/base/common/buffer'; @@ -181,7 +181,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD this._element = this._createElement(initInfo.options, initInfo.contentOptions); - const subscription = this._register(addDisposableListener($window, 'message', (e: MessageEvent) => { + const subscription = this._register(addDisposableListener(getActiveWindow(), 'message', (e: MessageEvent) => { if (!this._encodedWebviewOrigin || e?.data?.target !== this.id) { return; } From 4a6b8d9bd912c1aa79161bb811dc9599c55431a9 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Thu, 4 Jan 2024 11:37:39 -0800 Subject: [PATCH 0065/1897] Updated CSS selector in titlebarpart.css for hover effect on window icon (#201829) fixes #196080 --- src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index e71fb9d7eb5..9b713be146e 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -349,7 +349,7 @@ background-color: rgba(255, 255, 255, 0.1); } -.monaco-workbench .part.titlebar.light > .window-controls-container > .window-icon:hover { +.monaco-workbench .part.titlebar.light .window-controls-container > .window-icon:hover { background-color: rgba(0, 0, 0, 0.1); } From 3b234eab7272990d49703e21a045c2a9eb8427a3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 4 Jan 2024 20:38:27 +0100 Subject: [PATCH 0066/1897] zoom - show a status entry to reset custom zoom (#201830) * zoom - show a status entry to reset custom zoom * . --- src/vs/workbench/electron-sandbox/window.ts | 160 +++++++++++++----- .../auxiliaryWindowService.ts | 5 +- 2 files changed, 125 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 9a52c01defa..8239e05c46e 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -13,7 +13,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { EditorResourceAccessor, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors, IResourceDiffEditorInput, IUntypedEditorInput, IEditorPane, isResourceEditorInput, IResourceMergeEditorInput } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { WindowMinimumSize, IOpenFileRequest, IWindowsConfiguration, getTitleBarStyle, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest, IWindowSettings } from 'vs/platform/window/common/window'; +import { WindowMinimumSize, IOpenFileRequest, getTitleBarStyle, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest } from 'vs/platform/window/common/window'; import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ApplyZoomTarget, applyZoom } from 'vs/platform/window/electron-sandbox/window'; @@ -26,7 +26,7 @@ import { IMenuService, MenuId, IMenu, MenuItemAction, MenuRegistry } from 'vs/pl import { ICommandAction } from 'vs/platform/action/common/action'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { LifecyclePhase, ILifecycleService, WillShutdownEvent, ShutdownReason, BeforeShutdownErrorEvent, BeforeShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; @@ -73,13 +73,10 @@ import { registerWindowDriver } from 'vs/workbench/services/driver/electron-sand import { mainWindow } from 'vs/base/browser/window'; import { BaseWindow } from 'vs/workbench/browser/window'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; export class NativeWindow extends BaseWindow { - private touchBarMenu: IMenu | undefined; - private readonly touchBarDisposables = this._register(new DisposableStore()); - private lastInstalledTouchedBar: ICommandAction[][] | undefined; - private readonly customTitleContextMenuDisposable = this._register(new DisposableStore()); private readonly addFoldersScheduler = this._register(new RunOnceScheduler(() => this.doAddFolders(), 100)); @@ -330,29 +327,19 @@ export class NativeWindow extends BaseWindow { this.configurationService.updateValue(setting, false, ConfigurationTarget.USER_LOCAL); }); - // Zoom level changes + // Window Zoom this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('window.zoomLevel')) { - this.updateWindowZoomLevel(); + this.onDidChangeConfiguredWindowZoomLevel(); } else if (e.affectsConfiguration('keyboard.touchbar.enabled') || e.affectsConfiguration('keyboard.touchbar.ignored')) { this.updateTouchbarMenu(); } })); - this._register(onDidChangeZoomLevel(targetWindowId => { - if (targetWindowId !== mainWindow.vscodeWindowId) { - return; // only update our own window - } + this._register(onDidChangeZoomLevel(targetWindowId => this.handleOnDidChangeZoomLevel(targetWindowId))); - const configuredWindowZoomLevel = this.configurationService.getValue('window')?.zoomLevel; - const currentWindowZoomLevel = getZoomLevel(mainWindow); - - let notifyZoomLevel: number | undefined = undefined; - if (configuredWindowZoomLevel !== currentWindowZoomLevel) { - notifyZoomLevel = currentWindowZoomLevel; - } - - ipcRenderer.invoke('vscode:notifyZoomLevel', notifyZoomLevel); + this._register(this.editorGroupService.onDidCreateAuxiliaryEditorPart(({ instantiationService, disposables, part }) => { + this.createResetWindowZoomStatusEntry(instantiationService, part.windowId, disposables); })); // Listen to visible editor changes (debounced in case a new editor opens immediately after) @@ -430,6 +417,8 @@ export class NativeWindow extends BaseWindow { this._register(this.lifecycleService.onWillShutdown(e => this.onWillShutdown(e))); } + //#region Window Lifecycle + private onBeforeShutdown({ veto, reason }: BeforeShutdownEvent): void { if (reason === ShutdownReason.CLOSE) { const confirmBeforeCloseSetting = this.configurationService.getValue<'always' | 'never' | 'keyboardOnly'>('window.confirmBeforeClose'); @@ -578,6 +567,8 @@ export class NativeWindow extends BaseWindow { } } + //#endregion + private updateDocumentEdited(documentEdited: true | undefined): void { let setDocumentEdited: boolean; if (typeof documentEdited === 'boolean') { @@ -638,23 +629,6 @@ export class NativeWindow extends BaseWindow { } } - private updateWindowZoomLevel(): void { - const windowConfig = this.configurationService.getValue(); - const windowZoomLevel = typeof windowConfig.window?.zoomLevel === 'number' ? windowConfig.window.zoomLevel : 0; - - let applyZoomLevel = false; - for (const { window } of getWindows()) { - if (getZoomLevel(window) !== windowZoomLevel) { - applyZoomLevel = true; - break; - } - } - - if (applyZoomLevel) { - applyZoom(windowZoomLevel, ApplyZoomTarget.ALL_WINDOWS); - } - } - private provideCustomTitleContextMenu(filePath: string | undefined): void { // Clear old menu @@ -708,6 +682,11 @@ export class NativeWindow extends BaseWindow { // Touchbar menu (if enabled) this.updateTouchbarMenu(); + // Zoom status + for (const { window, disposables } of getWindows()) { + this.createResetWindowZoomStatusEntry(this.instantiationService, window.vscodeWindowId, disposables); + } + // Smoke Test Driver if (this.environmentService.enableSmokeTestDriver) { this.setupDriver(); @@ -913,6 +892,12 @@ export class NativeWindow extends BaseWindow { }); } + //#region Touchbar + + private touchBarMenu: IMenu | undefined; + private readonly touchBarDisposables = this._register(new DisposableStore()); + private lastInstalledTouchedBar: ICommandAction[][] | undefined; + private updateTouchbarMenu(): void { if (!isMacintosh) { return; // macOS only @@ -981,6 +966,8 @@ export class NativeWindow extends BaseWindow { } } + //#endregion + private onAddFoldersRequest(request: IAddFoldersRequest): void { // Buffer all pending requests @@ -1064,4 +1051,101 @@ export class NativeWindow extends BaseWindow { return this.editorService.openEditors(editors, undefined, { validateTrust: true }); } + + //#region Window Zoom + + private readonly mapWindowIdToResetZoomStatusEntry = new Map(); + + private configuredWindowZoomLevel = this.resolveConfiguredWindowZoomLevel(); + + private resolveConfiguredWindowZoomLevel(): number { + const windowZoomLevel = this.configurationService.getValue('window.zoomLevel'); + + return typeof windowZoomLevel === 'number' ? windowZoomLevel : 0; + } + + private handleOnDidChangeZoomLevel(targetWindowId: number): void { + + // Zoom status entry + this.updateResetWindowZoomStatusEntry(targetWindowId); + + // Notify main process about a custom zoom level + if (targetWindowId === mainWindow.vscodeWindowId) { + const currentWindowZoomLevel = getZoomLevel(mainWindow); + + let notifyZoomLevel: number | undefined = undefined; + if (this.configuredWindowZoomLevel !== currentWindowZoomLevel) { + notifyZoomLevel = currentWindowZoomLevel; + } + + ipcRenderer.invoke('vscode:notifyZoomLevel', notifyZoomLevel); + } + } + + private createResetWindowZoomStatusEntry(instantiationService: IInstantiationService, targetWindowId: number, disposables: DisposableStore): void { + this.mapWindowIdToResetZoomStatusEntry.set(targetWindowId, disposables.add(instantiationService.createInstance(ResetZoomStatusEntry))); + disposables.add(toDisposable(() => this.mapWindowIdToResetZoomStatusEntry.delete(targetWindowId))); + + this.updateResetWindowZoomStatusEntry(targetWindowId); + } + + private updateResetWindowZoomStatusEntry(targetWindowId: number): void { + const targetWindow = getWindowById(targetWindowId); + const entry = this.mapWindowIdToResetZoomStatusEntry.get(targetWindowId); + if (entry && targetWindow) { + entry.updateResetZoomEntry(getZoomLevel(targetWindow.window) !== this.configuredWindowZoomLevel); + } + } + + private onDidChangeConfiguredWindowZoomLevel(): void { + this.configuredWindowZoomLevel = this.resolveConfiguredWindowZoomLevel(); + + let applyZoomLevel = false; + for (const { window } of getWindows()) { + if (getZoomLevel(window) !== this.configuredWindowZoomLevel) { + applyZoomLevel = true; + break; + } + } + + if (applyZoomLevel) { + applyZoom(this.configuredWindowZoomLevel, ApplyZoomTarget.ALL_WINDOWS); + } + } + + //#endregion + + override dispose(): void { + super.dispose(); + + for (const [, entry] of this.mapWindowIdToResetZoomStatusEntry) { + entry.dispose(); + } + } +} + +class ResetZoomStatusEntry extends Disposable { + + private readonly resetZoomStatusEntry = this._register(new MutableDisposable()); + + constructor(@IStatusbarService private readonly statusbarService: IStatusbarService) { + super(); + } + + updateResetZoomEntry(visible: boolean): void { + if (visible) { + if (!this.resetZoomStatusEntry.value) { + const text = localize('resetZoom', "Reset Zoom"); + this.resetZoomStatusEntry.value = this.statusbarService.addEntry({ + name: localize('status.resetWindowZoom', "Reset Window Zoom"), + text, + ariaLabel: text, + command: 'workbench.action.zoomReset', + kind: 'prominent' + }, 'status.resetWindowZoom', StatusbarAlignment.RIGHT, 102); + } + } else { + this.resetZoomStatusEntry.clear(); + } + } } diff --git a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts index a9ae966b65b..b7e1b0ef394 100644 --- a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts @@ -11,7 +11,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { DisposableStore } from 'vs/base/common/lifecycle'; import { INativeHostService } from 'vs/platform/native/common/native'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { CodeWindow, mainWindow } from 'vs/base/browser/window'; +import { CodeWindow } from 'vs/base/browser/window'; import { mark } from 'vs/base/common/performance'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; @@ -22,6 +22,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IHostService } from 'vs/workbench/services/host/browser/host'; import { applyZoom } from 'vs/platform/window/electron-sandbox/window'; import { getZoomLevel } from 'vs/base/browser/browser'; +import { getActiveWindow } from 'vs/base/browser/dom'; type NativeCodeWindow = CodeWindow & { readonly vscode: ISandboxGlobals; @@ -89,7 +90,7 @@ export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService if (typeof options?.zoomLevel === 'number') { windowZoomLevel = options.zoomLevel; } else { - windowZoomLevel = getZoomLevel(mainWindow); + windowZoomLevel = getZoomLevel(getActiveWindow()); } applyZoom(windowZoomLevel, auxiliaryWindow); From 03853823823340ed3f5834cb8fa6c65e84d9d923 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 4 Jan 2024 12:22:46 -0800 Subject: [PATCH 0067/1897] testing: more out-of-editor refinements (#201834) - Som more tweaks to our own runner scripts to allow asking for the generated coverage formats. - Add actions alongside debug/run for executing coverage profiles - Finish with displaying function coverage stats in Coverage view, allow changing its sort order. Fixes #200529 Fixes #199380 --- package.json | 2 +- .../contrib/testing/browser/icons.ts | 7 +- .../contrib/testing/browser/media/testing.css | 10 + .../testing/browser/testCoverageBars.ts | 52 +-- .../testing/browser/testCoverageView.ts | 332 +++++++++++++----- .../testing/browser/testExplorerActions.ts | 148 ++++++-- .../testing/browser/testingOutputPeek.ts | 2 +- .../contrib/testing/common/constants.ts | 8 + test/unit/coverage.js | 14 +- test/unit/electron/index.js | 4 +- test/unit/electron/renderer.js | 2 +- test/unit/node/index.js | 7 +- 12 files changed, 450 insertions(+), 138 deletions(-) diff --git a/package.json b/package.json index fa7242f618b..57a6dd672af 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.86.0", - "distro": "536a5224f3131ed95718b23b3f82d75485d89721", + "distro": "31d3f5c070f8d16d2907b37f44f2b7e3f595f28c", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/workbench/contrib/testing/browser/icons.ts b/src/vs/workbench/contrib/testing/browser/icons.ts index 6b65cba6ca2..455f07ec53a 100644 --- a/src/vs/workbench/contrib/testing/browser/icons.ts +++ b/src/vs/workbench/contrib/testing/browser/icons.ts @@ -19,6 +19,10 @@ export const testingRunAllIcon = registerIcon('testing-run-all-icon', Codicon.ru // todo: https://github.com/microsoft/vscode-codicons/issues/72 export const testingDebugAllIcon = registerIcon('testing-debug-all-icon', Codicon.debugAltSmall, localize('testingDebugAllIcon', 'Icon of the "debug all tests" action.')); export const testingDebugIcon = registerIcon('testing-debug-icon', Codicon.debugAltSmall, localize('testingDebugIcon', 'Icon of the "debug test" action.')); +// todo: https://github.com/microsoft/vscode-codicons/issues/205 +export const testingCoverageIcon = registerIcon('testing-coverage-icon', Codicon.heartFilled, localize('testingCoverageIcon', 'Icon of the "run test with coverage" action.')); +// todo: https://github.com/microsoft/vscode-codicons/issues/205 +export const testingCoverageAllIcon = registerIcon('testing-coverage-all-icon', Codicon.heartFilled, localize('testingRunAllWithCoverageIcon', 'Icon of the "run all tests with coverage" action.')); export const testingCancelIcon = registerIcon('testing-cancel-icon', Codicon.debugStop, localize('testingCancelIcon', 'Icon to cancel ongoing test runs.')); export const testingFilterIcon = registerIcon('testing-filter', Codicon.filter, localize('filterIcon', 'Icon for the \'Filter\' action in the testing view.')); export const testingHiddenIcon = registerIcon('testing-hidden', Codicon.eyeClosed, localize('hiddenIcon', 'Icon shown beside hidden tests, when they\'ve been shown.')); @@ -33,7 +37,8 @@ export const testingTurnContinuousRunOff = registerIcon('testing-turn-continuous export const testingContinuousIsOn = registerIcon('testing-continuous-is-on', Codicon.eye, localize('testingTurnContinuousRunIsOn', 'Icon when continuous run is on for a test ite,.')); export const testingCancelRefreshTests = registerIcon('testing-cancel-refresh-tests', Codicon.stop, localize('testingCancelRefreshTests', 'Icon on the button to cancel refreshing tests.')); -export const testingCoverage = registerIcon('testing-coverage', Codicon.lightBulb, localize('testingCoverage', 'Icon representing test coverage')); +export const testingCoverageReport = registerIcon('testing-coverage', Codicon.lightBulb, localize('testingCoverage', 'Icon representing test coverage')); +export const testingWasCovered = registerIcon('testing-was-covered', Codicon.check, localize('testingWasCovered', 'Icon representing that an element was covered')); export const testingStatesToIcons = new Map([ [TestResultState.Errored, registerIcon('testing-error-icon', Codicon.issues, localize('testingErrorIcon', 'Icon shown for tests that have an error.'))], diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index 20c87255f36..a523e914d59 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -43,6 +43,7 @@ .test-explorer .test-item .label, .test-output-peek-tree .test-peek-item .name, +.test-coverage-list-item .name, .test-coverage-list-item-label { flex-grow: 1; width: 0; @@ -365,6 +366,7 @@ .test-coverage-list-item { display: flex; + align-items: center; } .test-coverage-bars { @@ -392,6 +394,14 @@ opacity: 0.7; } +.test-coverage-list-item .icon { + margin-right: 0.2em; +} + +.test-coverage-list-item.not-covered .name { + opacity: 0.7; +} + /** -- coverage in the explorer */ .explorer-item-with-test-coverage { diff --git a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts index b6fe5ee6be8..9a1c2987b9d 100644 --- a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts +++ b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts @@ -139,11 +139,11 @@ export class ManagedTestCoverageBars extends Disposable { const overallStat = calculateDisplayedStat(coverage, getTestingConfiguration(this.configurationService, TestingConfigKeys.CoveragePercent)); el.overall.textContent = displayPercent(overallStat, precision); if ('tpcBar' in el) { // compact mode - renderBar(el.tpcBar, overallStat, thresholds); + renderBar(el.tpcBar, overallStat, false, thresholds); } else { - renderBar(el.statement, percent(coverage.statement), thresholds); - renderBar(el.function, coverage.function && percent(coverage.function), thresholds); - renderBar(el.branch, coverage.branch && percent(coverage.branch), thresholds); + renderBar(el.statement, percent(coverage.statement), coverage.statement.total === 0, thresholds); + renderBar(el.function, coverage.function && percent(coverage.function), coverage.function?.total === 0, thresholds); + renderBar(el.branch, coverage.branch && percent(coverage.branch), coverage.branch?.total === 0, thresholds); } } } @@ -152,27 +152,35 @@ const percent = (cc: ICoveredCount) => clamp(cc.total === 0 ? 1 : cc.covered / c const epsilon = 10e-8; const barWidth = 16; -const renderBar = (bar: HTMLElement, pct: number | undefined, thresholds: ITestingCoverageBarThresholds) => { +const renderBar = (bar: HTMLElement, pct: number | undefined, isZero: boolean, thresholds: ITestingCoverageBarThresholds) => { if (pct === undefined) { bar.style.display = 'none'; - } else { - bar.style.display = 'block'; - bar.style.width = `${barWidth}px`; - // this is floored so the bar is only completely filled at 100% and not 99.9% - bar.style.setProperty('--test-bar-width', `${Math.floor(pct * 16)}px`); - - let best = colorThresholds[0].color; // red - let distance = pct; - for (const { key, color } of colorThresholds) { - const t = thresholds[key] / 100; - if (t && pct >= t && pct - t < distance) { - best = color; - distance = pct - t; - } - } - - bar.style.color = best; + return; } + + bar.style.display = 'block'; + bar.style.width = `${barWidth}px`; + // this is floored so the bar is only completely filled at 100% and not 99.9% + bar.style.setProperty('--test-bar-width', `${Math.floor(pct * 16)}px`); + + if (isZero) { + bar.style.color = 'currentColor'; + bar.style.opacity = '0.5'; + return; + } + + let best = colorThresholds[0].color; // red + let distance = pct; + for (const { key, color } of colorThresholds) { + const t = thresholds[key] / 100; + if (t && pct >= t && pct - t < distance) { + best = color; + distance = pct - t; + } + } + + bar.style.color = best; + bar.style.opacity = '1'; }; const colorThresholds = [ diff --git a/src/vs/workbench/contrib/testing/browser/testCoverageView.ts b/src/vs/workbench/contrib/testing/browser/testCoverageView.ts index 90d6231894a..ecf22d64e7e 100644 --- a/src/vs/workbench/contrib/testing/browser/testCoverageView.ts +++ b/src/vs/workbench/contrib/testing/browser/testCoverageView.ts @@ -3,46 +3,59 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as dom from 'vs/base/browser/dom'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ICompressedTreeElement, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; -import { ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { ITreeNode, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; import { assertNever } from 'vs/base/common/assert'; import { Codicon } from 'vs/base/common/codicons'; import { memoize } from 'vs/base/common/decorators'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { Iterable } from 'vs/base/common/iterator'; import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; -import { autorun } from 'vs/base/common/observable'; +import { IObservable, autorun, observableValue } from 'vs/base/common/observable'; import { IPrefixTreeNode } from 'vs/base/common/prefixTree'; import { basenameOrAuthority } from 'vs/base/common/resources'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; +import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; +import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { EditorOpenSource } from 'vs/platform/editor/common/editor'; +import { EditorOpenSource, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; import { FileKind } from 'vs/platform/files/common/files'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ILabelService } from 'vs/platform/label/common/label'; import { WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; +import { IViewPaneOptions, ViewAction, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { testingStatesToIcons, testingWasCovered } from 'vs/workbench/contrib/testing/browser/icons'; import { CoverageBarSource, ManagedTestCoverageBars } from 'vs/workbench/contrib/testing/browser/testCoverageBars'; -import { ComputedFileCoverage, FileCoverage, TestCoverage } from 'vs/workbench/contrib/testing/common/testCoverage'; +import { TestCommandId, Testing } from 'vs/workbench/contrib/testing/common/constants'; +import { ComputedFileCoverage, FileCoverage, TestCoverage, getTotalCoveragePercent } from 'vs/workbench/contrib/testing/common/testCoverage'; import { ITestCoverageService } from 'vs/workbench/contrib/testing/common/testCoverageService'; -import { CoverageDetails, DetailType, ICoveredCount, IFunctionCoverage } from 'vs/workbench/contrib/testing/common/testTypes'; +import { CoverageDetails, DetailType, ICoveredCount, IFunctionCoverage, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +const enum CoverageSortOrder { + Coverage, + Location, + Name, +} + export class TestCoverageView extends ViewPane { private readonly tree = new MutableDisposable(); + public readonly sortOrder = observableValue('sortOrder', CoverageSortOrder.Location); constructor( options: IViewPaneOptions, @@ -68,7 +81,7 @@ export class TestCoverageView extends ViewPane { this._register(autorun(reader => { const coverage = this.coverageService.selected.read(reader); if (coverage) { - const t = (this.tree.value ??= this.instantiationService.createInstance(TestCoverageTree, container, labels)); + const t = (this.tree.value ??= this.instantiationService.createInstance(TestCoverageTree, container, labels, this.sortOrder)); t.setInput(coverage); } else { this.tree.clear(); @@ -86,19 +99,45 @@ let fnNodeId = 0; class FunctionCoverageNode { public readonly id = String(fnNodeId++); + public readonly containedDetails = new Set(); + public readonly children: FunctionCoverageNode[] = []; public get hits() { return this.data.count; } - public get name() { + public get label() { return this.data.name; } + public get location() { + return this.data.location; + } + + public get tpc() { + const attr = this.attributableCoverage(); + return attr && getTotalCoveragePercent(attr.statement, attr.branch, undefined); + } + constructor( + public readonly uri: URI, private readonly data: IFunctionCoverage, - private readonly details: CoverageDetails[], - ) { } + details: readonly CoverageDetails[], + ) { + if (data.location instanceof Range) { + for (const detail of details) { + if (this.contains(detail.location)) { + this.containedDetails.add(detail); + } + } + } + } + + /** Gets whether this function has a defined range and contains the given range. */ + public contains(location: Range | Position) { + const own = this.data.location; + return own instanceof Range && (location instanceof Range ? own.containsRange(location) : own.containsPosition(location)); + } /** * If the function defines a range, we can look at statements within the @@ -114,21 +153,16 @@ class FunctionCoverageNode { const statement: ICoveredCount = { covered: 0, total: 0 }; const branch: ICoveredCount = { covered: 0, total: 0 }; - for (const detail of this.details) { + for (const detail of this.containedDetails) { if (detail.type !== DetailType.Statement) { continue; } - const withinFn = detail.location instanceof Range ? location.containsRange(detail.location) : location.containsPosition(detail.location); - if (!withinFn) { - continue; - } - statement.covered += detail.count > 0 ? 0 : 1; + statement.covered += detail.count > 0 ? 1 : 0; statement.total++; - if (detail.branches) { for (const { count } of detail.branches) { - branch.covered += count > 0 ? 0 : 1; + branch.covered += count > 0 ? 1 : 0; branch.total++; } } @@ -138,11 +172,24 @@ class FunctionCoverageNode { } } -const LoadingDetails = Symbol(); -const loadingDetailsLabel = localize('loadingCoverageDetails', "Loading Coverage Details..."); +class RevealUncoveredFunctions { + public readonly id = String(fnNodeId++); + + public get label() { + return localize('functionsWithoutCoverage', "{0} functions without coverage...", this.n); + } + + constructor(public readonly n: number) { } +} + +class LoadingDetails { + public readonly id = String(fnNodeId++); + public readonly label = localize('loadingCoverageDetails', "Loading Coverage Details..."); +} + /** Type of nodes returned from {@link TestCoverage}. Note: value is *always* defined. */ type TestCoverageFileNode = IPrefixTreeNode; -type CoverageTreeElement = TestCoverageFileNode | FunctionCoverageNode | typeof LoadingDetails; +type CoverageTreeElement = TestCoverageFileNode | FunctionCoverageNode | LoadingDetails | RevealUncoveredFunctions; const isFileCoverage = (c: CoverageTreeElement): c is TestCoverageFileNode => typeof c === 'object' && 'value' in c; const isFunctionCoverage = (c: CoverageTreeElement): c is FunctionCoverageNode => c instanceof FunctionCoverageNode; @@ -155,6 +202,7 @@ class TestCoverageTree extends Disposable { constructor( container: HTMLElement, labels: ResourceLabels, + sortOrder: IObservable, @IInstantiationService instantiationService: IInstantiationService, @IEditorService editorService: IEditorService, ) { @@ -167,25 +215,30 @@ class TestCoverageTree extends Disposable { new TestCoverageTreeListDelegate(), [ instantiationService.createInstance(FileCoverageRenderer, labels), - instantiationService.createInstance(FunctionCoverageRenderer, labels), - instantiationService.createInstance(LoadingDetailsRenderer), + instantiationService.createInstance(FunctionCoverageRenderer), + instantiationService.createInstance(BasicRenderer), ], { expandOnlyOnTwistieClick: true, + sorter: new Sorter(sortOrder), + keyboardNavigationLabelProvider: { + getCompressedNodeKeyboardNavigationLabel(elements: CoverageTreeElement[]) { + return elements.map(e => this.getKeyboardNavigationLabel(e)).join('/'); + }, + getKeyboardNavigationLabel(e: CoverageTreeElement) { + return isFileCoverage(e) + ? basenameOrAuthority(e.value!.uri) + : e.label; + }, + }, accessibilityProvider: { getAriaLabel(element: CoverageTreeElement) { if (isFileCoverage(element)) { const name = basenameOrAuthority(element.value!.uri); return localize('testCoverageItemLabel', "{0} coverage: {0}%", name, (element.value!.tpc * 100).toFixed(2)); + } else { + return element.label; } - if (isFunctionCoverage(element)) { - return element.name; - } - if (element === LoadingDetails) { - return loadingDetailsLabel; - } - - assertNever(element); }, getWidgetAriaLabel() { return localize('testCoverageTreeLabel', "Test Coverage Explorer"); @@ -195,29 +248,32 @@ class TestCoverageTree extends Disposable { } ); + this._register(autorun(reader => { + sortOrder.read(reader); + this.tree.resort(null, true); + })); + this._register(this.tree); this._register(this.tree.onDidChangeCollapseState(e => { const el = e.node.element; if (!e.node.collapsed && !e.node.children.length && el && shouldShowFunctionDetailsOnExpand(el)) { if (el.value!.hasSynchronousDetails) { - this.tree.setChildren(el, [{ element: LoadingDetails, incompressible: true }]); + this.tree.setChildren(el, [{ element: new LoadingDetails(), incompressible: true }]); } - el.value!.details().then(details => { - if (!this.tree.hasElement(el)) { - return; // avoid any issues if the tree changes in the meanwhile - } - - this.tree.setChildren(el, details - .filter((d): d is IFunctionCoverage => d.type === DetailType.Function) - .map(fn => ({ element: new FunctionCoverageNode(fn, details), incompressible: true }))); - }); + el.value!.details().then(details => this.updateWithDetails(el, details)); } })); this._register(this.tree.onDidOpen(e => { let resource: URI | undefined; - if (e.element && isFileCoverage(e.element) && !e.element.children?.size) { - resource = e.element.value!.uri; + let selection: Range | Position | undefined; + if (e.element) { + if (isFileCoverage(e.element) && !e.element.children?.size) { + resource = e.element.value!.uri; + } else if (isFunctionCoverage(e.element)) { + resource = e.element.uri; + selection = e.element.location; + } } if (!resource) { return; @@ -225,7 +281,14 @@ class TestCoverageTree extends Disposable { editorService.openEditor({ resource, - options: { preserveFocus: e.editorOptions.preserveFocus, pinned: e.editorOptions.pinned, source: EditorOpenSource.USER } + options: { + selection: selection instanceof Position ? Range.fromPositions(selection, selection) : selection, + revealIfOpened: true, + selectionRevealType: TextEditorSelectionRevealType.NearTopIfOutsideViewport, + preserveFocus: e.editorOptions.preserveFocus, + pinned: e.editorOptions.pinned, + source: EditorOpenSource.USER, + }, }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); })); } @@ -258,6 +321,41 @@ class TestCoverageTree extends Disposable { public layout(height: number, width: number) { this.tree.layout(height, width); } + + private updateWithDetails(el: IPrefixTreeNode, details: readonly CoverageDetails[]) { + if (!this.tree.hasElement(el)) { + return; // avoid any issues if the tree changes in the meanwhile + } + + const functions: FunctionCoverageNode[] = []; + for (const fn of details) { + if (fn.type !== DetailType.Function) { + continue; + } + + let arr = functions; + while (true) { + const parent = arr.find(p => p.containedDetails.has(fn)); + if (parent) { + arr = parent.children; + } else { + break; + } + } + + arr.push(new FunctionCoverageNode(el.value!.uri, fn, details)); + } + + const makeChild = (fn: FunctionCoverageNode): ICompressedTreeElement => ({ + element: fn, + incompressible: true, + collapsed: true, + collapsible: fn.children.length > 0, + children: fn.children.map(makeChild) + }); + + this.tree.setChildren(el, functions.map(makeChild)); + } } class TestCoverageTreeListDelegate implements IListVirtualDelegate { @@ -272,13 +370,48 @@ class TestCoverageTreeListDelegate implements IListVirtualDelegate { + constructor(private readonly order: IObservable) { } + compare(a: CoverageTreeElement, b: CoverageTreeElement): number { + const order = this.order.get(); + if (isFileCoverage(a) && isFileCoverage(b)) { + switch (order) { + case CoverageSortOrder.Location: + case CoverageSortOrder.Name: + return a.value!.uri.toString().localeCompare(b.value!.uri.toString()); + case CoverageSortOrder.Coverage: + return b.value!.tpc - a.value!.tpc; + } + } else if (isFunctionCoverage(a) && isFunctionCoverage(b)) { + switch (order) { + case CoverageSortOrder.Location: + return Position.compare( + a.location instanceof Range ? a.location.getStartPosition() : a.location, + b.location instanceof Range ? b.location.getStartPosition() : b.location, + ); + case CoverageSortOrder.Name: + return a.label.localeCompare(b.label); + case CoverageSortOrder.Coverage: { + const attrA = a.tpc; + const attrB = b.tpc; + return (attrA !== undefined && attrB !== undefined && attrB - attrA) + || (b.hits - a.hits) + || a.label.localeCompare(b.label); + } + } + } else { + return 0; + } + } +} + interface FileTemplateData { container: HTMLElement; bars: ManagedTestCoverageBars; @@ -345,7 +478,8 @@ interface FunctionTemplateData { container: HTMLElement; bars: ManagedTestCoverageBars; templateDisposables: DisposableStore; - label: IResourceLabel; + icon: HTMLElement; + label: HTMLElement; } class FunctionCoverageRenderer implements ICompressibleTreeRenderer { @@ -353,81 +487,119 @@ class FunctionCoverageRenderer implements ICompressibleTreeRenderer, _index: number, templateData: FileTemplateData): void { + public renderElement(node: ITreeNode, _index: number, templateData: FunctionTemplateData): void { this.doRender(node.element as FunctionCoverageNode, templateData, node.filterData); } /** @inheritdoc */ - public renderCompressedElements(node: ITreeNode, FuzzyScore>, _index: number, templateData: FileTemplateData): void { + public renderCompressedElements(node: ITreeNode, FuzzyScore>, _index: number, templateData: FunctionTemplateData): void { this.doRender(node.element.elements[node.element.elements.length - 1] as FunctionCoverageNode, templateData, node.filterData); } - public disposeTemplate(templateData: FileTemplateData) { + public disposeTemplate(templateData: FunctionTemplateData) { templateData.templateDisposables.dispose(); } /** @inheritdoc */ - private doRender(element: FunctionCoverageNode, templateData: FileTemplateData, filterData: FuzzyScore | undefined) { - const classes = ['test-coverage-list-item-label']; - if (element.hits > 0) { - classes.push(...ThemeIcon.asClassNameArray(Codicon.pass)); - } - + private doRender(element: FunctionCoverageNode, templateData: FunctionTemplateData, _filterData: FuzzyScore | undefined) { + const covered = element.hits > 0; + const icon = covered ? testingWasCovered : testingStatesToIcons.get(TestResultState.Unset); + templateData.container.classList.toggle('not-covered', !covered); + templateData.icon.className = `computed-state ${ThemeIcon.asClassName(icon!)}`; + templateData.label.innerText = element.label; templateData.bars.setCoverageInfo(element.attributableCoverage()); - templateData.label.setLabel(element.name, undefined, { - matches: createMatches(filterData), - extraClasses: ['test-coverage-list-item-label'], - }); } } -class LoadingDetailsRenderer implements ICompressibleTreeRenderer { - public static readonly ID = 'L'; - public readonly templateId = LoadingDetailsRenderer.ID; +class BasicRenderer implements ICompressibleTreeRenderer { + public static readonly ID = 'B'; + public readonly templateId = BasicRenderer.ID; - renderCompressedElements(): void { - // no-op + renderCompressedElements(node: ITreeNode, FuzzyScore>, _index: number, container: HTMLElement): void { + this.renderInner(node.element.elements[node.element.elements.length - 1], container); } - renderTemplate(container: HTMLElement): void { - container.innerText = loadingDetailsLabel; + renderTemplate(container: HTMLElement): HTMLElement { + return container; } - renderElement(): void { - // no-op + renderElement(node: ITreeNode, index: number, container: HTMLElement): void { + this.renderInner(node.element, container); } disposeTemplate(): void { // no-op } + + private renderInner(element: CoverageTreeElement, container: HTMLElement) { + container.innerText = (element as RevealUncoveredFunctions | LoadingDetails).label; + } } class TestCoverageIdentityProvider implements IIdentityProvider { public getId(element: CoverageTreeElement) { return isFileCoverage(element) ? element.value!.uri.toString() - : isFunctionCoverage(element) - ? element.id - : element.toString(); + : element.id; } } + +registerAction2(class TestCoverageChangeSortingAction extends ViewAction { + constructor() { + super({ + id: TestCommandId.CoverageViewChangeSorting, + viewId: Testing.CoverageViewId, + title: localize2('testing.changeCoverageSort', 'Change Sort Order'), + icon: Codicon.sortPrecedence, + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', Testing.CoverageViewId), + group: 'navigation', + } + }); + } + + override runInView(accessor: ServicesAccessor, view: TestCoverageView) { + type Item = IQuickPickItem & { value: CoverageSortOrder }; + + const disposables = new DisposableStore(); + const quickInput = disposables.add(accessor.get(IQuickInputService).createQuickPick()); + const items: Item[] = [ + { label: localize('testing.coverageSortByLocation', 'Sort by Location'), value: CoverageSortOrder.Location, description: localize('testing.coverageSortByLocationDescription', 'Files are sorted alphabetically, functions are sorted by position') }, + { label: localize('testing.coverageSortByCoverage', 'Sort by Coverage'), value: CoverageSortOrder.Coverage, description: localize('testing.coverageSortByCoverageDescription', 'Files and functions are sorted by total coverage') }, + { label: localize('testing.coverageSortByName', 'Sort by Name'), value: CoverageSortOrder.Name, description: localize('testing.coverageSortByNameDescription', 'Files and functions are sorted alphabetically') }, + ]; + + quickInput.placeholder = localize('testing.coverageSortPlaceholder', 'Sort the Test Coverage view...'); + quickInput.items = items; + quickInput.show(); + quickInput.onDidHide(() => quickInput.dispose()); + quickInput.onDidAccept(() => { + const picked = quickInput.selectedItems[0]?.value; + if (picked !== undefined) { + view.sortOrder.set(picked, undefined); + quickInput.dispose(); + } + }); + } +}); diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index 5b7abc3825e..1716ae12a8a 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -76,6 +76,7 @@ const hasAnyTestProvider = ContextKeyGreaterExpr.create(TestingContextKeys.provi const LABEL_RUN_TESTS = { value: localize('runSelectedTests', 'Run Tests'), original: 'Run Tests' }; const LABEL_DEBUG_TESTS = { value: localize('debugSelectedTests', 'Debug Tests'), original: 'Debug Tests' }; +const LABEL_COVERAGE_TESTS = { value: localize('coverageSelectedTests', 'Run Tests with Coverage'), original: 'Run Tests withCoverage' }; export class HideTestAction extends Action2 { constructor() { @@ -152,13 +153,10 @@ const testItemInlineAndInContext = (order: ActionOrder, when?: ContextKeyExpress } ]; -export class DebugAction extends ViewAction { - constructor() { +abstract class RunVisibleAction extends ViewAction { + constructor(private readonly bitset: TestRunProfileBitset, desc: Readonly) { super({ - id: TestCommandId.DebugAction, - title: localize('debug test', 'Debug Test'), - icon: icons.testingDebugIcon, - menu: testItemInlineAndInContext(ActionOrder.Debug, TestingContextKeys.hasDebuggableTests.isEqualTo(true)), + ...desc, viewId: Testing.ExplorerViewId, }); } @@ -171,7 +169,29 @@ export class DebugAction extends ViewAction { return accessor.get(ITestService).runTests({ tests: include, exclude, - group: TestRunProfileBitset.Debug, + group: this.bitset, + }); + } +} + +export class DebugAction extends RunVisibleAction { + constructor() { + super(TestRunProfileBitset.Debug, { + id: TestCommandId.DebugAction, + title: localize('debug test', 'Debug Test'), + icon: icons.testingDebugIcon, + menu: testItemInlineAndInContext(ActionOrder.Debug, TestingContextKeys.hasDebuggableTests.isEqualTo(true)), + }); + } +} + +export class CoverageAction extends RunVisibleAction { + constructor() { + super(TestRunProfileBitset.Coverage, { + id: TestCommandId.RunWithCoverageAction, + title: localize('run with cover test', 'Run Test with Coverage'), + icon: icons.testingCoverageIcon, + menu: testItemInlineAndInContext(ActionOrder.Coverage, TestingContextKeys.hasCoverableTests.isEqualTo(true)), }); } } @@ -212,26 +232,13 @@ export class RunUsingProfileAction extends Action2 { } } -export class RunAction extends ViewAction { +export class RunAction extends RunVisibleAction { constructor() { - super({ + super(TestRunProfileBitset.Run, { id: TestCommandId.RunAction, title: localize('run test', 'Run Test'), icon: icons.testingRunIcon, menu: testItemInlineAndInContext(ActionOrder.Run, TestingContextKeys.hasRunnableTests.isEqualTo(true)), - viewId: Testing.ExplorerViewId, - }); - } - - /** - * @override - */ - public runInView(accessor: ServicesAccessor, view: TestingExplorerView, ...elements: TestItemTreeElement[]): Promise { - const { include, exclude } = view.getTreeIncludeExclude(elements.map(e => e.test)); - return accessor.get(ITestService).runTests({ - tests: include, - exclude, - group: TestRunProfileBitset.Run, }); } } @@ -587,6 +594,16 @@ export class DebugSelectedAction extends ExecuteSelectedAction { } } +export class CoverageSelectedAction extends ExecuteSelectedAction { + constructor() { + super({ + id: TestCommandId.CoverageSelectedAction, + title: LABEL_COVERAGE_TESTS, + icon: icons.testingCoverageAllIcon, + }, TestRunProfileBitset.Coverage); + } +} + const showDiscoveringWhile = (progress: IProgressService, task: Promise): Promise => { return progress.withProgress( { @@ -659,6 +676,24 @@ export class DebugAllAction extends RunOrDebugAllTestsAction { } } +export class CoverageAllAction extends RunOrDebugAllTestsAction { + constructor() { + super( + { + id: TestCommandId.RunAllWithCoverageAction, + title: localize('runAllWithCoverage', 'Run All Tests with Coverage'), + icon: icons.testingCoverageIcon, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.Semicolon, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyA), + }, + }, + TestRunProfileBitset.Coverage, + localize('noCoverageTestProvider', 'No tests with coverage runners found in this workspace. You may need to install a test provider extension'), + ); + } +} + export class CancelTestRunAction extends Action2 { constructor() { super({ @@ -1060,6 +1095,21 @@ export class DebugAtCursor extends ExecuteTestAtCursor { } } +export class CoverageAtCursor extends ExecuteTestAtCursor { + constructor() { + super({ + id: TestCommandId.CoverageAtCursor, + title: localize2('testing.coverageAtCursor', 'Run Test at Cursor with Coverage'), + category, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: EditorContextKeys.editorTextFocus, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.Semicolon, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyC), + }, + }, TestRunProfileBitset.Coverage); + } +} + abstract class ExecuteTestsUnderUriAction extends Action2 { constructor(options: IAction2Options, protected readonly group: TestRunProfileBitset) { super({ @@ -1111,6 +1161,16 @@ class DebugTestsUnderUri extends ExecuteTestsUnderUriAction { } } +class CoverageTestsUnderUri extends ExecuteTestsUnderUriAction { + constructor() { + super({ + id: TestCommandId.CoverageByUri, + title: LABEL_COVERAGE_TESTS, + category, + }, TestRunProfileBitset.Coverage); + } +} + abstract class ExecuteTestsInCurrentFile extends Action2 { constructor(options: IAction2Options, protected readonly group: TestRunProfileBitset) { super({ @@ -1189,7 +1249,6 @@ export class RunCurrentFile extends ExecuteTestsInCurrentFile { } export class DebugCurrentFile extends ExecuteTestsInCurrentFile { - constructor() { super({ id: TestCommandId.DebugCurrentFile, @@ -1204,6 +1263,21 @@ export class DebugCurrentFile extends ExecuteTestsInCurrentFile { } } +export class CoverageCurrentFile extends ExecuteTestsInCurrentFile { + constructor() { + super({ + id: TestCommandId.CoverageCurrentFile, + title: localize2('testing.coverageCurrentFile', 'Run Tests with Coverage in Current File'), + category, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: EditorContextKeys.editorTextFocus, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.Semicolon, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyF), + }, + }, TestRunProfileBitset.Coverage); + } +} + export const discoverAndRunTests = async ( collection: IMainThreadTestCollection, progress: IProgressService, @@ -1381,6 +1455,27 @@ export class DebugLastRun extends RunOrDebugLastRun { } } +export class CoverageLastRun extends RunOrDebugLastRun { + constructor() { + super({ + id: TestCommandId.CoverageLastRun, + title: localize2('testing.coverageLastRun', 'Rerun Last Run with Coverage'), + category, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.Semicolon, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyL), + }, + }); + } + + protected runTest(service: ITestService, internalTests: InternalTestItem[]): Promise { + return service.runTests({ + group: TestRunProfileBitset.Coverage, + tests: internalTests, + }); + } +} + export class SearchForTestExtension extends Action2 { constructor() { super({ @@ -1543,6 +1638,13 @@ export const allTestActions = [ ConfigureTestProfilesAction, ContinuousRunTestAction, ContinuousRunUsingProfileTestAction, + CoverageAction, + CoverageAllAction, + CoverageAtCursor, + CoverageCurrentFile, + CoverageLastRun, + CoverageSelectedAction, + CoverageTestsUnderUri, DebugAction, DebugAllAction, DebugAtCursor, diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 39e673936b6..776340abfca 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -1717,7 +1717,7 @@ class CoverageElement implements ITreeElement { } public get icon() { - return this.isOpen ? widgetClose : icons.testingCoverage; + return this.isOpen ? widgetClose : icons.testingCoverageReport; } public get isOpen() { diff --git a/src/vs/workbench/contrib/testing/common/constants.ts b/src/vs/workbench/contrib/testing/common/constants.ts index e8a22d62340..e3fff330e6a 100644 --- a/src/vs/workbench/contrib/testing/common/constants.ts +++ b/src/vs/workbench/contrib/testing/common/constants.ts @@ -58,7 +58,13 @@ export const enum TestCommandId { CollapseAllAction = 'testing.collapseAll', ConfigureTestProfilesAction = 'testing.configureProfile', ContinousRunUsingForTest = 'testing.continuousRunUsingForTest', + CoverageAtCursor = 'testing.coverageAtCursor', + CoverageByUri = 'testing.coverage.uri', + CoverageViewChangeSorting = 'testing.coverageViewChangeSorting', CoverageClose = 'testing.coverage.close', + CoverageCurrentFile = 'testing.coverageCurrentFile', + CoverageLastRun = 'testing.coverageLastRun', + CoverageSelectedAction = 'testing.coverageSelected', DebugAction = 'testing.debug', DebugAllAction = 'testing.debugAll', DebugAtCursor = 'testing.debugAtCursor', @@ -78,11 +84,13 @@ export const enum TestCommandId { ReRunLastRun = 'testing.reRunLastRun', RunAction = 'testing.run', RunAllAction = 'testing.runAll', + RunAllWithCoverageAction = 'testing.coverageAll', RunAtCursor = 'testing.runAtCursor', RunByUri = 'testing.run.uri', RunCurrentFile = 'testing.runCurrentFile', RunSelectedAction = 'testing.runSelected', RunUsingProfileAction = 'testing.runUsing', + RunWithCoverageAction = 'testing.coverage', SearchForTestExtension = 'testing.searchForTestExtension', SelectDefaultTestProfiles = 'testing.selectDefaultTestProfiles', ShowMostRecentOutputAction = 'testing.showMostRecentOutput', diff --git a/test/unit/coverage.js b/test/unit/coverage.js index 5134343c74b..13712241b4c 100644 --- a/test/unit/coverage.js +++ b/test/unit/coverage.js @@ -37,7 +37,7 @@ exports.initialize = function (loaderConfig) { }; }; -exports.createReport = function (isSingle, coveragePath) { +exports.createReport = function (isSingle, coveragePath, formats) { const mapStore = iLibSourceMaps.createSourceMapStore(); const coverageMap = iLibCoverage.createCoverageMap(global.__coverage__); return mapStore.transformCoverage(coverageMap).then((transformed) => { @@ -58,11 +58,15 @@ exports.createReport = function (isSingle, coveragePath) { const tree = context.getTree('flat'); const reports = []; - if (isSingle) { - reports.push(iReports.create('lcovonly')); - if (coveragePath) { - reports.push(iReports.create('json')); + if (formats) { + if (typeof formats === 'string') { + formats = [formats]; } + formats.forEach(format => { + reports.push(iReports.create(format)); + }); + } else if (isSingle) { + reports.push(iReports.create('lcovonly')); } else { reports.push(iReports.create('json')); reports.push(iReports.create('lcov')); diff --git a/test/unit/electron/index.js b/test/unit/electron/index.js index d02921fb483..cfc2a5a9890 100644 --- a/test/unit/electron/index.js +++ b/test/unit/electron/index.js @@ -39,11 +39,13 @@ const minimist = require('minimist'); * tfs: string; * build: boolean; * coverage: boolean; + * coveragePath: string; + * coverageFormats: string | string[]; * help: boolean; * }} */ const args = minimist(process.argv.slice(2), { - string: ['grep', 'run', 'runGlob', 'reporter', 'reporter-options', 'waitServer', 'timeout', 'crash-reporter-directory', 'tfs', 'coveragePath'], + string: ['grep', 'run', 'runGlob', 'reporter', 'reporter-options', 'waitServer', 'timeout', 'crash-reporter-directory', 'tfs', 'coveragePath', 'coverageFormats'], boolean: ['build', 'coverage', 'help', 'dev'], alias: { 'grep': ['g', 'f'], diff --git a/test/unit/electron/renderer.js b/test/unit/electron/renderer.js index 1488dfe85f5..243d5a5b142 100644 --- a/test/unit/electron/renderer.js +++ b/test/unit/electron/renderer.js @@ -123,7 +123,7 @@ function initLoader(opts) { function createCoverageReport(opts) { if (opts.coverage) { - return coverage.createReport(opts.run || opts.runGlob, opts.coveragePath); + return coverage.createReport(opts.run || opts.runGlob, opts.coveragePath, opts.coverageFormats); } return Promise.resolve(undefined); } diff --git a/test/unit/node/index.js b/test/unit/node/index.js index d1a787e4419..14ceed7177c 100644 --- a/test/unit/node/index.js +++ b/test/unit/node/index.js @@ -19,11 +19,11 @@ const minimist = require('minimist'); const { takeSnapshotAndCountClasses } = require('../analyzeSnapshot'); /** - * @type {{ build: boolean; run: string; runGlob: string; coverage: boolean; help: boolean; }} + * @type {{ build: boolean; run: string; runGlob: string; coverage: boolean; help: boolean; coverageFormats: string | string[]; coveragePath: string; }} */ const args = minimist(process.argv.slice(2), { boolean: ['build', 'coverage', 'help'], - string: ['run', 'coveragePath'], + string: ['run', 'coveragePath', 'coverageFormats'], alias: { h: 'help' }, @@ -37,6 +37,7 @@ const args = minimist(process.argv.slice(2), { run: 'Run a single file', coverage: 'Generate a coverage report', coveragePath: 'Path to coverage report to generate', + coverageFormats: 'Coverage formats to generate', help: 'Show help' } }); @@ -142,7 +143,7 @@ function main() { if (code !== 0) { return; } - coverage.createReport(args.run || args.runGlob, args.coveragePath); + coverage.createReport(args.run || args.runGlob, args.coveragePath, args.coverageFormats); }); } From d2005b6706931608b7426c61bdc037e205904a76 Mon Sep 17 00:00:00 2001 From: samhanic <55490861+samhanic@users.noreply.github.com> Date: Thu, 4 Jan 2024 21:46:28 +0100 Subject: [PATCH 0068/1897] CLI update extension: add some logs --- .../common/extensionManagementCLI.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts index 6a29ab91f67..08dd0fb2761 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts @@ -197,14 +197,20 @@ export class ExtensionManagementCLI { } } - if (extensionsToUpdate.length) { - this.logger.info(localize('updateExtensionsNewVersionsAvailable', "Updating extensions: {0}", extensionsToUpdate.map(ext => ext.extension.identifier.id).join(', '))); - await this.extensionManagementService.installGalleryExtensions(extensionsToUpdate); - this.logger.info(localize('updateExtensionsDone', "Updated {0} extensions", extensionsToUpdate.length)); - } - else { + if (!extensionsToUpdate.length) { this.logger.info(localize('updateExtensionsNoExtensions', "No extension to update")); + return; } + + this.logger.info(localize('updateExtensionsNewVersionsAvailable', "Updating extensions: {0}", extensionsToUpdate.map(ext => ext.extension.identifier.id).join(', '))); + const installationResult = await this.extensionManagementService.installGalleryExtensions(extensionsToUpdate); + + const failed: string[] = installationResult.filter(ext => ext.error).map(ext => ext.identifier.id); + if (failed.length) { + throw new Error(localize('updateExtensionsFailed', "Failed updating extensions: {0}", failed.join(', '))); + } + + this.logger.info(localize('updateExtensionsDone', "Successfully updated {0} extensions", extensionsToUpdate.length)); } private async installVSIX(vsix: URI, installOptions: InstallOptions, force: boolean, installedExtensions: ILocalExtension[]): Promise { From 06f41fda5dca4cbde66ff9053b81cc0f8382ee4a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 4 Jan 2024 15:53:31 -0600 Subject: [PATCH 0069/1897] add initial voice actions in terminal and editor area --- .../electron-sandbox/actions/voiceActions.ts | 121 ++++++++++++++++++ .../electron-sandbox/chat.contribution.ts | 3 + 2 files changed, 124 insertions(+) create mode 100644 src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts new file mode 100644 index 00000000000..b8160eaa173 --- /dev/null +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts @@ -0,0 +1,121 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { localize } from 'vs/nls'; +import { Action2 } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + + +export class StartEditorSpeechToTextAction extends Action2 { + + static readonly ID = 'workbench.action.startEditorSpeechToText'; + + constructor() { + super({ + id: 'workbench.action.startEditorSpeechToText', + title: { + value: localize('workbench.action.startEditorSpeechToText', "Start Editor Speech To Text"), + original: 'Start Editor Speech To Text' + }, + precondition: ContextKeyExpr.and(HasSpeechProvider, EditorContextKeys.focus), + f1: true + }); + } + + async run(accessor: ServicesAccessor): Promise { + const instantiationService = accessor.get(IInstantiationService); + VoiceSession.getInstance(instantiationService).start('editor'); + } +} + + +export class StartTerminalSpeechToTextAction extends Action2 { + + static readonly ID = 'workbench.action.startTerminalSpeechToText'; + + constructor() { + super({ + id: 'workbench.action.startTerminalSpeechToText', + title: { + value: localize('workbench.action.startTerminalSpeechToText', "Start Terminal Speech To Text"), + original: 'Start Terminal Speech To Text' + }, + precondition: ContextKeyExpr.and(HasSpeechProvider, TerminalContextKeys.focus), + f1: true + }); + } + + async run(accessor: ServicesAccessor): Promise { + const instantiationService = accessor.get(IInstantiationService); + VoiceSession.getInstance(instantiationService).start('terminal'); + } +} + +class VoiceSession extends Disposable { + private static instance: VoiceSession | undefined = undefined; + static getInstance(instantiationService: IInstantiationService): VoiceSession { + if (!VoiceSession.instance) { + VoiceSession.instance = instantiationService.createInstance(VoiceSession); + } + + return VoiceSession.instance; + } + private _cancellationTokenSource: CancellationTokenSource | undefined; + private _disposables = new DisposableStore(); + constructor( + private readonly _speechService: ISpeechService, + private readonly _editorService: IEditorService, + private readonly _terminalService: ITerminalService + ) { + super(); + } + start(type: 'terminal' | 'editor'): void { + this.stop(); + if (this._cancellationTokenSource) { + this._cancellationTokenSource.cancel(); + } else { + this._cancellationTokenSource = new CancellationTokenSource(); + } + const session = this._disposables.add(this._speechService.createSpeechToTextSession(this._cancellationTokenSource!.token)); + this._disposables.add(session.onDidChange((e) => { + switch (e.status) { + case SpeechToTextStatus.Started: + console.log('started', e.text); + break; + case SpeechToTextStatus.Recognizing: + console.log('recognizing', e.text); + if (type === 'terminal') { + this._terminalService.activeInstance?.sendText(e.text!, true); + } else { + this._editorService.activeTextEditorControl?.trigger?.('type', 'type', { text: e.text }); + } + break; + case SpeechToTextStatus.Recognized: + console.log('recognized', e.text); + if (type === 'terminal') { + this._terminalService.activeInstance?.sendText(e.text!, true); + } else { + this._editorService.activeTextEditorControl?.trigger?.('type', 'type', { text: e.text }); + } + break; + case SpeechToTextStatus.Stopped: + console.log('stopped', e.text); + break; + } + })); + } + stop(): void { + this._cancellationTokenSource?.cancel(); + this._disposables.dispose(); + } +} diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts index 9ba5430737e..fb529a7342c 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts @@ -5,6 +5,7 @@ import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { StartEditorSpeechToTextAction, StartTerminalSpeechToTextAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions'; registerAction2(StartVoiceChatAction); @@ -20,3 +21,5 @@ registerAction2(StopListeningInChatEditorAction); registerAction2(StopListeningInQuickChatAction); registerAction2(StopListeningInInlineChatAction); +registerAction2(StartTerminalSpeechToTextAction); +registerAction2(StartEditorSpeechToTextAction); From 5fcae79502f84526fdf5aa8ca60206bc645be5c3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 4 Jan 2024 15:58:10 -0600 Subject: [PATCH 0070/1897] stop on stop --- .../contrib/chat/electron-sandbox/actions/voiceActions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts index b8160eaa173..0f460badb75 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts @@ -110,6 +110,7 @@ class VoiceSession extends Disposable { break; case SpeechToTextStatus.Stopped: console.log('stopped', e.text); + this.stop(); break; } })); From bf9609642d52dbadd99ef6501baa6b323717dd1f Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Thu, 4 Jan 2024 16:15:29 -0600 Subject: [PATCH 0071/1897] Add better support for navigating to search viewlet (#201839) --- .../browser/quickTextSearch/textSearchQuickAccess.ts | 12 ++++++++++-- .../workbench/contrib/search/browser/searchIcons.ts | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index 119051f27d0..1a851121bdd 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -15,13 +15,13 @@ import { ITextEditorSelection } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { WorkbenchCompressibleObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService'; -import { FastAndSlowPicks, IPickerQuickAccessItem, PickerQuickAccessProvider, Picks } from 'vs/platform/quickinput/browser/pickerQuickAccess'; +import { FastAndSlowPicks, IPickerQuickAccessItem, PickerQuickAccessProvider, Picks, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { DefaultQuickAccessFilterValue, IQuickAccessProviderRunOptions } from 'vs/platform/quickinput/common/quickAccess'; import { IKeyMods, IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; import { IViewsService } from 'vs/workbench/common/views'; -import { searchDetailsIcon, searchOpenInFileIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; +import { searchDetailsIcon, searchOpenInFileIcon, searchSeeMoreIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import { FileMatch, Match, RenderableMatch, SearchModel, searchComparer } from 'vs/workbench/contrib/search/browser/searchModel'; import { SearchView, getEditorSelectionFromMatch } from 'vs/workbench/contrib/search/browser/searchView'; import { IWorkbenchSearchConfiguration, getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; @@ -220,6 +220,10 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { await this.handleAccept(fileMatch, { @@ -228,6 +232,10 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { + this.moveToSearchViewlet(this.searchModel, element); + return TriggerAction.CLOSE_PICKER; } }); } diff --git a/src/vs/workbench/contrib/search/browser/searchIcons.ts b/src/vs/workbench/contrib/search/browser/searchIcons.ts index 34ab891e7ad..c1cd71a8bca 100644 --- a/src/vs/workbench/contrib/search/browser/searchIcons.ts +++ b/src/vs/workbench/contrib/search/browser/searchIcons.ts @@ -8,6 +8,7 @@ import { localize } from 'vs/nls'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; export const searchDetailsIcon = registerIcon('search-details', Codicon.ellipsis, localize('searchDetailsIcon', 'Icon to make search details visible.')); +export const searchSeeMoreIcon = registerIcon('search-see-more', Codicon.arrowRight, localize('searchSeeMoreIcon', 'Icon to view more context in the search view.')); export const searchShowContextIcon = registerIcon('search-show-context', Codicon.listSelection, localize('searchShowContextIcon', 'Icon for toggle the context in the search editor.')); export const searchHideReplaceIcon = registerIcon('search-hide-replace', Codicon.chevronRight, localize('searchHideReplaceIcon', 'Icon to collapse the replace section in the search view.')); From db577c2430d2ea71fbdf41c58f0b0c388e04a718 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 4 Jan 2024 17:22:58 -0600 Subject: [PATCH 0072/1897] service injection --- .../contrib/chat/electron-sandbox/actions/voiceActions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts index 0f460badb75..2ffc602203a 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts @@ -73,9 +73,9 @@ class VoiceSession extends Disposable { private _cancellationTokenSource: CancellationTokenSource | undefined; private _disposables = new DisposableStore(); constructor( - private readonly _speechService: ISpeechService, - private readonly _editorService: IEditorService, - private readonly _terminalService: ITerminalService + @ISpeechService private readonly _speechService: ISpeechService, + @IEditorService readonly _editorService: IEditorService, + @ITerminalService readonly _terminalService: ITerminalService ) { super(); } From ee91ce84bba9db64fcaaa9ed1e98e5dcda5e8e30 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 4 Jan 2024 15:54:50 -0800 Subject: [PATCH 0073/1897] Show TS Server commands in diff editors and notebook cells (#201847) Fixes #201734 Switches to use tabs so that we can enable TS commands if you're in a JS/TS editor in a diff editor or notebook cell --- .../src/ui/activeJsTsEditorTracker.ts | 87 ++++++++++++++----- 1 file changed, 64 insertions(+), 23 deletions(-) diff --git a/extensions/typescript-language-features/src/ui/activeJsTsEditorTracker.ts b/extensions/typescript-language-features/src/ui/activeJsTsEditorTracker.ts index e7ad8d7ea09..3da1f5a8570 100644 --- a/extensions/typescript-language-features/src/ui/activeJsTsEditorTracker.ts +++ b/extensions/typescript-language-features/src/ui/activeJsTsEditorTracker.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import { isJsConfigOrTsConfigFileName } from '../configuration/languageDescription'; import { isSupportedLanguageMode } from '../configuration/languageIds'; import { Disposable } from '../utils/dispose'; +import { coalesce } from '../utils/arrays'; /** * Tracks the active JS/TS editor. @@ -24,41 +25,81 @@ export class ActiveJsTsEditorTracker extends Disposable { public constructor() { super(); - vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, this._disposables); - vscode.window.onDidChangeVisibleTextEditors(() => { - // Make sure the active editor is still in the visible set. - // This can happen if the output view is focused and the last active TS file is closed - if (this._activeJsTsEditor) { - if (!vscode.window.visibleTextEditors.some(visibleEditor => visibleEditor === this._activeJsTsEditor)) { - this.onDidChangeActiveTextEditor(undefined); - } - } - }, this, this._disposables); - this.onDidChangeActiveTextEditor(vscode.window.activeTextEditor); + this._register(vscode.window.onDidChangeActiveTextEditor(_ => this.update())); + this._register(vscode.window.onDidChangeVisibleTextEditors(_ => this.update())); + + this.update(); } public get activeJsTsEditor(): vscode.TextEditor | undefined { return this._activeJsTsEditor; } - private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined): any { - if (editor === this._activeJsTsEditor) { - return; + + private update() { + // Use tabs to find the active editor. + // This correctly handles switching to the output view / debug console, which changes the activeEditor but not + // the active tab. + const editorCandidates = this.getEditorCandidatesForActiveTab(); + const managedEditors = editorCandidates.filter(editor => this.isManagedFile(editor)); + const newActiveJsTsEditor = managedEditors.at(0); + if (this._activeJsTsEditor !== newActiveJsTsEditor) { + this._activeJsTsEditor = newActiveJsTsEditor; + this._onDidChangeActiveJsTsEditor.fire(this._activeJsTsEditor); + } + } + + private getEditorCandidatesForActiveTab(): vscode.TextEditor[] { + const tab = vscode.window.tabGroups.activeTabGroup.activeTab; + if (!tab) { + return []; } - if (editor && !editor.viewColumn) { - // viewColumn is undefined for the debug/output panel, but we still want - // to show the version info for the previous editor - return; + // Basic text editor tab + if (tab.input instanceof vscode.TabInputText) { + const inputUri = tab.input.uri; + const editor = vscode.window.visibleTextEditors.find(editor => { + return editor.document.uri.toString() === inputUri.toString() + && editor.viewColumn === tab.group.viewColumn; + }); + return editor ? [editor] : []; } - if (editor && this.isManagedFile(editor)) { - this._activeJsTsEditor = editor; - } else { - this._activeJsTsEditor = undefined; + // Diff editor tab. We could be focused on either side of the editor. + if (tab.input instanceof vscode.TabInputTextDiff) { + const original = tab.input.original; + const modified = tab.input.modified; + // Check the active editor first. However if a non tab editor like the output view is focused, + // we still need to check the visible text editors. + // TODO: This may return incorrect editors incorrect as there does not seem to be a reliable way to map from an editor to the + // view column of its parent diff editor. See https://github.com/microsoft/vscode/issues/201845 + return coalesce([vscode.window.activeTextEditor, ...vscode.window.visibleTextEditors]).filter(editor => { + return (editor.document.uri.toString() === original.toString() || editor.document.uri.toString() === modified.toString()) + && editor.viewColumn === undefined; // Editors in diff views have undefined view columns + }); } - this._onDidChangeActiveJsTsEditor.fire(this._activeJsTsEditor); + + // Notebook editor. Find editor for notebook cell. + if (tab.input instanceof vscode.TabInputNotebook) { + const activeEditor = vscode.window.activeTextEditor; + if (!activeEditor) { + return []; + } + + // Notebooks cell editors have undefined view columns. + if (activeEditor.viewColumn !== undefined) { + return []; + } + + const notebook = vscode.window.visibleNotebookEditors.find(editor => + editor.notebook.uri.toString() === (tab.input as vscode.TabInputNotebook).uri.toString() + && editor.viewColumn === tab.group.viewColumn); + + return notebook?.notebook.getCells().some(cell => cell.document.uri.toString() === activeEditor.document.uri.toString()) ? [activeEditor] : []; + } + + return []; } private isManagedFile(editor: vscode.TextEditor): boolean { From 26ef59c6a7136b3d7e44fc9015fa3dc5d28ec88a Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 4 Jan 2024 15:59:14 -0800 Subject: [PATCH 0074/1897] Rework markdown paste resource (#201838) Fixes #184980 This refactors much of the logic around markdown paste/drop. PR got a little large but the main highlights are: - Allow using a custom snippet for inserted audio/video - Merge the drop/paste resource provider classes since these are so similar - Enable smart pasting of url text by default - Refactor url paste logic - For now, disable the behavior where url paste could paste a combination of markdown and plain uris. In practice this is confusing, especially because our labels for this were wrong. We can always reintroduce this later if multicursor users find it useful --- .../markdown-language-features/package.json | 12 +- .../package.nls.json | 10 +- .../src/commands/insertResource.ts | 29 +- .../src/extension.shared.ts | 6 +- .../languageFeatures/copyFiles/copyFiles.ts | 82 +---- .../copyFiles/dropOrPasteResource.ts | 237 +++++++++++++ .../copyFiles/dropResourceProvider.ts | 86 ----- .../copyFiles/newFilePathGenerator.ts | 77 +++++ .../copyFiles/pasteResourceProvider.ts | 91 ----- .../copyFiles/pasteUrlProvider.ts | 118 ++++++- .../src/languageFeatures/copyFiles/shared.ts | 327 +++++------------- .../languageFeatures/copyFiles/snippets.ts | 32 ++ .../src/test/markdownLink.test.ts | 47 +-- .../src/util/uriList.ts | 14 + 14 files changed, 622 insertions(+), 546 deletions(-) create mode 100644 extensions/markdown-language-features/src/languageFeatures/copyFiles/dropOrPasteResource.ts delete mode 100644 extensions/markdown-language-features/src/languageFeatures/copyFiles/dropResourceProvider.ts create mode 100644 extensions/markdown-language-features/src/languageFeatures/copyFiles/newFilePathGenerator.ts delete mode 100644 extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteResourceProvider.ts create mode 100644 extensions/markdown-language-features/src/languageFeatures/copyFiles/snippets.ts create mode 100644 extensions/markdown-language-features/src/util/uriList.ts diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index dbbf937e6c6..5dcce219bfa 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -503,11 +503,21 @@ "%configuration.copyIntoWorkspace.never%" ] }, + "markdown.editor.filePaste.videoSnippet": { + "type": "string", + "markdownDescription": "%configuration.markdown.editor.filePaste.videoSnippet%", + "default": "" + }, + "markdown.editor.filePaste.audioSnippet": { + "type": "string", + "markdownDescription": "%configuration.markdown.editor.filePaste.audioSnippet%", + "default": "" + }, "markdown.editor.pasteUrlAsFormattedLink.enabled": { "type": "string", "scope": "resource", "markdownDescription": "%configuration.markdown.editor.pasteUrlAsFormattedLink.enabled%", - "default": "never", + "default": "smart", "enum": [ "always", "smart", diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index 9054456fed9..02d80098b9a 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -43,10 +43,10 @@ "configuration.markdown.editor.filePaste.copyIntoWorkspace": "Controls if files outside of the workspace that are pasted into a Markdown editor should be copied into the workspace.\n\nUse `#markdown.copyFiles.destination#` to configure where copied files should be created.", "configuration.copyIntoWorkspace.mediaFiles": "Try to copy external image and video files into the workspace.", "configuration.copyIntoWorkspace.never": "Do not copy external files into the workspace.", - "configuration.markdown.editor.pasteUrlAsFormattedLink.enabled": "Controls how a Markdown link is created when a URL is pasted into the Markdown editor. Requires enabling `#editor.pasteAs.enabled#`.", - "configuration.pasteUrlAsFormattedLink.always": "Always creates a Markdown link when a URL is pasted into the Markdown editor.", - "configuration.pasteUrlAsFormattedLink.smart": "Smartly avoids creating a Markdown link in specific cases, such as within code brackets or inside an existing Markdown link.", - "configuration.pasteUrlAsFormattedLink.never": "Never creates a Markdown link when a URL is pasted into the Markdown editor.", + "configuration.markdown.editor.pasteUrlAsFormattedLink.enabled": "Controls if Markdown links are created when URLs are pasted into a Markdown editor. Requires enabling `#editor.pasteAs.enabled#`.", + "configuration.pasteUrlAsFormattedLink.always": "Always insert Markdown links.", + "configuration.pasteUrlAsFormattedLink.smart": "Smartly create Markdown links by default when you have selected text and are not pasting into a code block or other special element. Use the paste widget to switch between pasting as plain text or as Markdown links.", + "configuration.pasteUrlAsFormattedLink.never": "Never create Markdown links.", "configuration.markdown.validate.enabled.description": "Enable all error reporting in Markdown files.", "configuration.markdown.validate.referenceLinks.enabled.description": "Validate reference links in Markdown files, for example: `[link][ref]`. Requires enabling `#markdown.validate.enabled#`.", "configuration.markdown.validate.fragmentLinks.enabled.description": "Validate fragment links to headers in the current Markdown file, for example: `[link](#header)`. Requires enabling `#markdown.validate.enabled#`.", @@ -71,5 +71,7 @@ "configuration.markdown.preferredMdPathExtensionStyle.auto": "For existing paths, try to maintain the file extension style. For new paths, add file extensions.", "configuration.markdown.preferredMdPathExtensionStyle.includeExtension": "Prefer including the file extension. For example, path completions to a file named `file.md` will insert `file.md`.", "configuration.markdown.preferredMdPathExtensionStyle.removeExtension": "Prefer removing the file extension. For example, path completions to a file named `file.md` will insert `file` without the `.md`.", + "configuration.markdown.editor.filePaste.videoSnippet": "Snippet used when adding videos to Markdown. This snippet can use the following variables:\n- `${src}` — The resolved path of the video file.\n- `${title}` — The title used for the video. A snippet placeholder will automatically be created for this variable.", + "configuration.markdown.editor.filePaste.audioSnippet": "Snippet used when adding audio to Markdown. This snippet can use the following variables:\n- `${src}` — The resolved path of the audio file.\n- `${title}` — The title used for the audio. A snippet placeholder will automatically be created for this variable.", "workspaceTrust": "Required for loading styles configured in the workspace." } diff --git a/extensions/markdown-language-features/src/commands/insertResource.ts b/extensions/markdown-language-features/src/commands/insertResource.ts index 598afb9bcdf..9369141465e 100644 --- a/extensions/markdown-language-features/src/commands/insertResource.ts +++ b/extensions/markdown-language-features/src/commands/insertResource.ts @@ -29,8 +29,11 @@ export class InsertLinkFromWorkspace implements Command { title: vscode.l10n.t("Insert link"), defaultUri: getDefaultUri(activeEditor.document), }); + if (!resources) { + return; + } - return insertLink(activeEditor, resources ?? [], false); + return insertLink(activeEditor, resources, false); } } @@ -54,8 +57,11 @@ export class InsertImageFromWorkspace implements Command { title: vscode.l10n.t("Insert image"), defaultUri: getDefaultUri(activeEditor.document), }); + if (!resources) { + return; + } - return insertLink(activeEditor, resources ?? [], true); + return insertLink(activeEditor, resources, true); } } @@ -67,20 +73,18 @@ function getDefaultUri(document: vscode.TextDocument) { return Utils.dirname(docUri); } -async function insertLink(activeEditor: vscode.TextEditor, selectedFiles: vscode.Uri[], insertAsImage: boolean): Promise { - if (!selectedFiles.length) { - return; +async function insertLink(activeEditor: vscode.TextEditor, selectedFiles: readonly vscode.Uri[], insertAsMedia: boolean): Promise { + const edit = createInsertLinkEdit(activeEditor, selectedFiles, insertAsMedia); + if (edit) { + await vscode.workspace.applyEdit(edit); } - - const edit = createInsertLinkEdit(activeEditor, selectedFiles, insertAsImage); - await vscode.workspace.applyEdit(edit); } -function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: vscode.Uri[], insertAsMedia: boolean, title = '', placeholderValue = 0, pasteAsMarkdownLink = true, isExternalLink = false) { +function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: readonly vscode.Uri[], insertAsMedia: boolean) { const snippetEdits = coalesce(activeEditor.selections.map((selection, i): vscode.SnippetTextEdit | undefined => { const selectionText = activeEditor.document.getText(selection); - const snippet = createUriListSnippet(activeEditor.document, selectedFiles.map(uri => ({ uri })), title, placeholderValue, pasteAsMarkdownLink, isExternalLink, { - insertAsMedia, + const snippet = createUriListSnippet(activeEditor.document.uri, selectedFiles.map(uri => ({ uri })), { + insertAsMedia: insertAsMedia, placeholderText: selectionText, placeholderStartIndex: (i + 1) * selectedFiles.length, separator: insertAsMedia ? '\n' : ' ', @@ -88,6 +92,9 @@ function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: vs return snippet ? new vscode.SnippetTextEdit(selection, snippet.snippet) : undefined; })); + if (!snippetEdits.length) { + return; + } const edit = new vscode.WorkspaceEdit(); edit.set(activeEditor.document.uri, snippetEdits); diff --git a/extensions/markdown-language-features/src/extension.shared.ts b/extensions/markdown-language-features/src/extension.shared.ts index 0d6c160bffe..aceb8f8b5ae 100644 --- a/extensions/markdown-language-features/src/extension.shared.ts +++ b/extensions/markdown-language-features/src/extension.shared.ts @@ -7,10 +7,9 @@ import * as vscode from 'vscode'; import { MdLanguageClient } from './client/client'; import { CommandManager } from './commandManager'; import { registerMarkdownCommands } from './commands/index'; -import { registerPasteSupport } from './languageFeatures/copyFiles/pasteResourceProvider'; import { registerLinkPasteSupport } from './languageFeatures/copyFiles/pasteUrlProvider'; +import { registerResourceDropOrPasteSupport } from './languageFeatures/copyFiles/dropOrPasteResource'; import { registerDiagnosticSupport } from './languageFeatures/diagnostics'; -import { registerDropIntoEditorSupport } from './languageFeatures/copyFiles/dropResourceProvider'; import { registerFindFileReferenceSupport } from './languageFeatures/fileReferences'; import { registerUpdateLinksOnRename } from './languageFeatures/linkUpdater'; import { ILogger } from './logging'; @@ -57,9 +56,8 @@ function registerMarkdownLanguageFeatures( return vscode.Disposable.from( // Language features registerDiagnosticSupport(selector, commandManager), - registerDropIntoEditorSupport(selector), registerFindFileReferenceSupport(commandManager, client), - registerPasteSupport(selector), + registerResourceDropOrPasteSupport(selector), registerLinkPasteSupport(selector), registerUpdateLinksOnRename(client), ); diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts index 276aacbf310..299a8347b1c 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts @@ -3,19 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as path from 'path'; -import * as picomatch from 'picomatch'; import * as vscode from 'vscode'; import { Utils } from 'vscode-uri'; -import { getParentDocumentUri } from '../../util/document'; type OverwriteBehavior = 'overwrite' | 'nameIncrementally'; -interface CopyFileConfiguration { +export interface CopyFileConfiguration { readonly destination: Record; readonly overwriteBehavior: OverwriteBehavior; } -function getCopyFileConfiguration(document: vscode.TextDocument): CopyFileConfiguration { +export function getCopyFileConfiguration(document: vscode.TextDocument): CopyFileConfiguration { const config = vscode.workspace.getConfiguration('markdown', document); return { destination: config.get>('copyFiles.destination') ?? {}, @@ -30,72 +28,7 @@ function readOverwriteBehavior(config: vscode.WorkspaceConfiguration): Overwrite } } -export class NewFilePathGenerator { - - private readonly _usedPaths = new Set(); - - async getNewFilePath( - document: vscode.TextDocument, - file: vscode.DataTransferFile, - token: vscode.CancellationToken, - ): Promise<{ readonly uri: vscode.Uri; readonly overwrite: boolean } | undefined> { - const config = getCopyFileConfiguration(document); - const desiredPath = getDesiredNewFilePath(config, document, file); - - const root = Utils.dirname(desiredPath); - const ext = Utils.extname(desiredPath); - let baseName = Utils.basename(desiredPath); - baseName = baseName.slice(0, baseName.length - ext.length); - for (let i = 0; ; ++i) { - if (token.isCancellationRequested) { - return undefined; - } - - const name = i === 0 ? baseName : `${baseName}-${i}`; - const uri = vscode.Uri.joinPath(root, name + ext); - if (this._wasPathAlreadyUsed(uri)) { - continue; - } - - // Try overwriting if it already exists - if (config.overwriteBehavior === 'overwrite') { - this._usedPaths.add(uri.toString()); - return { uri, overwrite: true }; - } - - // Otherwise we need to check the fs to see if it exists - try { - await vscode.workspace.fs.stat(uri); - } catch { - if (!this._wasPathAlreadyUsed(uri)) { - // Does not exist - this._usedPaths.add(uri.toString()); - return { uri, overwrite: false }; - } - } - } - } - - private _wasPathAlreadyUsed(uri: vscode.Uri) { - return this._usedPaths.has(uri.toString()); - } -} - -function getDesiredNewFilePath(config: CopyFileConfiguration, document: vscode.TextDocument, file: vscode.DataTransferFile): vscode.Uri { - const docUri = getParentDocumentUri(document.uri); - for (const [rawGlob, rawDest] of Object.entries(config.destination)) { - for (const glob of parseGlob(rawGlob)) { - if (picomatch.isMatch(docUri.path, glob, { dot: true })) { - return resolveCopyDestination(docUri, file.name, rawDest, uri => vscode.workspace.getWorkspaceFolder(uri)?.uri); - } - } - } - - // Default to next to current file - return vscode.Uri.joinPath(Utils.dirname(docUri), file.name); -} - -function parseGlob(rawGlob: string): Iterable { +export function parseGlob(rawGlob: string): Iterable { if (rawGlob.startsWith('/')) { // Anchor to workspace folders return (vscode.workspace.workspaceFolders ?? []).map(folder => vscode.Uri.joinPath(folder.uri, rawGlob).path); @@ -165,7 +98,7 @@ function resolveCopyDestinationSetting(documentUri: vscode.Uri, fileName: string ['fileExtName', path.extname(fileName).replace('.', '')], // File extension (without dot): png ]); - return outDest.replaceAll(/(?\\\$)|(?\w+)(?:\/(?(?:\\\/|[^\}])+?)\/(?(?:\\\/|[^\}])+?)\/)?\}/g, (match, _escape, name, pattern, replacement, _offset, _str, groups) => { + return outDest.replaceAll(/(?\\\$)|(?\w+)(?:\/(?(?:\\\/|[^\}\/])+)\/(?(?:\\\/|[^\}\/])*)\/)?\}/g, (match, _escape, name, pattern, replacement, _offset, _str, groups) => { if (groups?.['escape']) { return '$'; } @@ -176,7 +109,11 @@ function resolveCopyDestinationSetting(documentUri: vscode.Uri, fileName: string } if (pattern && replacement) { - return entry.replace(new RegExp(replaceTransformEscapes(pattern)), replaceTransformEscapes(replacement)); + try { + return entry.replace(new RegExp(replaceTransformEscapes(pattern)), replaceTransformEscapes(replacement)); + } catch (e) { + console.log(`Error applying 'resolveCopyDestinationSetting' transform: ${pattern} -> ${replacement}`); + } } return entry; @@ -186,4 +123,3 @@ function resolveCopyDestinationSetting(documentUri: vscode.Uri, fileName: string function replaceTransformEscapes(str: string): string { return str.replaceAll(/\\\//g, '/'); } - diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropOrPasteResource.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropOrPasteResource.ts new file mode 100644 index 00000000000..41c8bd13146 --- /dev/null +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropOrPasteResource.ts @@ -0,0 +1,237 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { coalesce } from '../../util/arrays'; +import { getParentDocumentUri } from '../../util/document'; +import { Mime, mediaMimes } from '../../util/mimes'; +import { Schemes } from '../../util/schemes'; +import { NewFilePathGenerator } from './newFilePathGenerator'; +import { createInsertUriListEdit, createUriListSnippet, getSnippetLabel } from './shared'; + +/** + * Provides support for pasting or dropping resources into markdown documents. + * + * This includes: + * + * - `text/uri-list` data in the data transfer. + * - File object in the data transfer. + * - Media data in the data transfer, such as `image/png`. + */ +class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, vscode.DocumentDropEditProvider { + + public static readonly id = 'insertResource'; + + public static readonly mimeTypes = [ + Mime.textUriList, + 'files', + ...mediaMimes, + ]; + + private readonly _yieldTo = [ + { mimeType: 'text/plain' }, + { extensionId: 'vscode.ipynb', providerId: 'insertAttachment' }, + ]; + + public async provideDocumentDropEdits( + document: vscode.TextDocument, + position: vscode.Position, + dataTransfer: vscode.DataTransfer, + token: vscode.CancellationToken, + ): Promise { + const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.drop.enabled', true); + if (!enabled) { + return; + } + + const filesEdit = await this._getMediaFilesDropEdit(document, dataTransfer, token); + if (filesEdit) { + return filesEdit; + } + + if (token.isCancellationRequested) { + return; + } + + return this._createEditFromUriListData(document, [new vscode.Range(position, position)], dataTransfer, token); + } + + public async provideDocumentPasteEdits( + document: vscode.TextDocument, + ranges: readonly vscode.Range[], + dataTransfer: vscode.DataTransfer, + token: vscode.CancellationToken, + ): Promise { + const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.filePaste.enabled', true); + if (!enabled) { + return; + } + + const createEdit = await this._getMediaFilesPasteEdit(document, dataTransfer, token); + if (createEdit) { + return createEdit; + } + + if (token.isCancellationRequested) { + return; + } + + return this._createEditFromUriListData(document, ranges, dataTransfer, token); + } + + private async _createEditFromUriListData( + 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 pasteEdit = createInsertUriListEdit(document, ranges, uriList); + if (!pasteEdit) { + return; + } + + const uriEdit = new vscode.DocumentPasteEdit('', pasteEdit.label); + const edit = new vscode.WorkspaceEdit(); + edit.set(document.uri, pasteEdit.edits); + uriEdit.additionalEdit = edit; + uriEdit.yieldTo = this._yieldTo; + return uriEdit; + } + + private async _getMediaFilesPasteEdit( + document: vscode.TextDocument, + dataTransfer: vscode.DataTransfer, + token: vscode.CancellationToken, + ): Promise { + if (getParentDocumentUri(document.uri).scheme === Schemes.untitled) { + return; + } + + const copyFilesIntoWorkspace = vscode.workspace.getConfiguration('markdown', document).get<'mediaFiles' | 'never'>('editor.filePaste.copyIntoWorkspace', 'mediaFiles'); + if (copyFilesIntoWorkspace !== 'mediaFiles') { + return; + } + + const edit = await this._createEditForMediaFiles(document, dataTransfer, token); + if (!edit) { + return; + } + + const pasteEdit = new vscode.DocumentPasteEdit(edit.snippet, edit.label); + pasteEdit.additionalEdit = edit.additionalEdits; + pasteEdit.yieldTo = this._yieldTo; + return pasteEdit; + } + + private async _getMediaFilesDropEdit( + document: vscode.TextDocument, + dataTransfer: vscode.DataTransfer, + token: vscode.CancellationToken, + ): Promise { + if (getParentDocumentUri(document.uri).scheme === Schemes.untitled) { + return; + } + + const copyIntoWorkspace = vscode.workspace.getConfiguration('markdown', document).get<'mediaFiles' | 'never'>('editor.drop.copyIntoWorkspace', 'mediaFiles'); + if (copyIntoWorkspace !== 'mediaFiles') { + return; + } + + const edit = await this._createEditForMediaFiles(document, dataTransfer, token); + if (!edit) { + return; + } + + const dropEdit = new vscode.DocumentDropEdit(edit.snippet); + dropEdit.label = edit.label; + dropEdit.additionalEdit = edit.additionalEdits; + dropEdit.yieldTo = this._yieldTo; + return dropEdit; + } + + /** + * Create a new edit for media files in a data transfer. + * + * This tries copying files outside of the workspace into the workspace. + */ + private async _createEditForMediaFiles( + document: vscode.TextDocument, + dataTransfer: vscode.DataTransfer, + token: vscode.CancellationToken, + ): Promise<{ snippet: vscode.SnippetString; label: string; additionalEdits: vscode.WorkspaceEdit } | undefined> { + interface FileEntry { + readonly uri: vscode.Uri; + readonly newFile?: { readonly contents: vscode.DataTransferFile; readonly overwrite: boolean }; + } + + const pathGenerator = new NewFilePathGenerator(); + const fileEntries = coalesce(await Promise.all(Array.from(dataTransfer, async ([mime, item]): Promise => { + if (!mediaMimes.has(mime)) { + return; + } + + const file = item?.asFile(); + if (!file) { + return; + } + + if (file.uri) { + // If the file is already in a workspace, we don't want to create a copy of it + const workspaceFolder = vscode.workspace.getWorkspaceFolder(file.uri); + if (workspaceFolder) { + return { uri: file.uri }; + } + } + + const newFile = await pathGenerator.getNewFilePath(document, file, token); + if (!newFile) { + return; + } + return { uri: newFile.uri, newFile: { contents: file, overwrite: newFile.overwrite } }; + }))); + if (!fileEntries.length) { + return; + } + + const workspaceEdit = new vscode.WorkspaceEdit(); + for (const entry of fileEntries) { + if (entry.newFile) { + workspaceEdit.createFile(entry.uri, { + contents: entry.newFile.contents, + overwrite: entry.newFile.overwrite, + }); + } + } + + const snippet = createUriListSnippet(document.uri, fileEntries); + if (!snippet) { + return; + } + + return { + snippet: snippet.snippet, + label: getSnippetLabel(snippet), + additionalEdits: workspaceEdit, + }; + } +} + +export function registerResourceDropOrPasteSupport(selector: vscode.DocumentSelector): vscode.Disposable { + return vscode.Disposable.from( + vscode.languages.registerDocumentPasteEditProvider(selector, new ResourcePasteOrDropProvider(), { + id: ResourcePasteOrDropProvider.id, + pasteMimeTypes: ResourcePasteOrDropProvider.mimeTypes, + }), + vscode.languages.registerDocumentDropEditProvider(selector, new ResourcePasteOrDropProvider(), { + id: ResourcePasteOrDropProvider.id, + dropMimeTypes: ResourcePasteOrDropProvider.mimeTypes, + }), + ); +} diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropResourceProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropResourceProvider.ts deleted file mode 100644 index 6a8bdf88609..00000000000 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropResourceProvider.ts +++ /dev/null @@ -1,86 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; -import { Mime, mediaMimes } from '../../util/mimes'; -import { Schemes } from '../../util/schemes'; -import { createEditForMediaFiles, tryGetUriListSnippet } from './shared'; -import { getParentDocumentUri } from '../../util/document'; - -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' }, - ]; - - async provideDocumentDropEdits(document: vscode.TextDocument, _position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { - const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.drop.enabled', true); - if (!enabled) { - return; - } - - const filesEdit = await this._getMediaFilesEdit(document, dataTransfer, token); - if (filesEdit) { - return filesEdit; - } - - if (token.isCancellationRequested) { - return; - } - - return this._getUriListEdit(document, dataTransfer, token); - } - - private async _getUriListEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { - const urlList = await dataTransfer.get(Mime.textUriList)?.asString(); - if (!urlList || token.isCancellationRequested) { - return; - } - - const snippet = tryGetUriListSnippet(document, urlList); - if (!snippet) { - return; - } - - const edit = new vscode.DocumentDropEdit(snippet.snippet); - edit.label = snippet.label; - edit.yieldTo = this._yieldTo; - return edit; - } - - private async _getMediaFilesEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { - if (getParentDocumentUri(document.uri).scheme === Schemes.untitled) { - return; - } - - const copyIntoWorkspace = vscode.workspace.getConfiguration('markdown', document).get<'mediaFiles' | 'never'>('editor.drop.copyIntoWorkspace', 'mediaFiles'); - if (copyIntoWorkspace !== 'mediaFiles') { - return; - } - - const edit = await createEditForMediaFiles(document, dataTransfer, token); - if (!edit) { - return; - } - - 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 ResourceDropProvider(), ResourceDropProvider); -} diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/newFilePathGenerator.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/newFilePathGenerator.ts new file mode 100644 index 00000000000..1625977a72c --- /dev/null +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/newFilePathGenerator.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as picomatch from 'picomatch'; +import * as vscode from 'vscode'; +import { Utils } from 'vscode-uri'; +import { getParentDocumentUri } from '../../util/document'; +import { CopyFileConfiguration, getCopyFileConfiguration, parseGlob, resolveCopyDestination } from './copyFiles'; + + +export class NewFilePathGenerator { + + private readonly _usedPaths = new Set(); + + async getNewFilePath( + document: vscode.TextDocument, + file: vscode.DataTransferFile, + token: vscode.CancellationToken + ): Promise<{ readonly uri: vscode.Uri; readonly overwrite: boolean } | undefined> { + const config = getCopyFileConfiguration(document); + const desiredPath = getDesiredNewFilePath(config, document, file); + + const root = Utils.dirname(desiredPath); + const ext = Utils.extname(desiredPath); + let baseName = Utils.basename(desiredPath); + baseName = baseName.slice(0, baseName.length - ext.length); + for (let i = 0; ; ++i) { + if (token.isCancellationRequested) { + return undefined; + } + + const name = i === 0 ? baseName : `${baseName}-${i}`; + const uri = vscode.Uri.joinPath(root, name + ext); + if (this._wasPathAlreadyUsed(uri)) { + continue; + } + + // Try overwriting if it already exists + if (config.overwriteBehavior === 'overwrite') { + this._usedPaths.add(uri.toString()); + return { uri, overwrite: true }; + } + + // Otherwise we need to check the fs to see if it exists + try { + await vscode.workspace.fs.stat(uri); + } catch { + if (!this._wasPathAlreadyUsed(uri)) { + // Does not exist + this._usedPaths.add(uri.toString()); + return { uri, overwrite: false }; + } + } + } + } + + private _wasPathAlreadyUsed(uri: vscode.Uri) { + return this._usedPaths.has(uri.toString()); + } +} + +export function getDesiredNewFilePath(config: CopyFileConfiguration, document: vscode.TextDocument, file: vscode.DataTransferFile): vscode.Uri { + const docUri = getParentDocumentUri(document.uri); + for (const [rawGlob, rawDest] of Object.entries(config.destination)) { + for (const glob of parseGlob(rawGlob)) { + if (picomatch.isMatch(docUri.path, glob, { dot: true })) { + return resolveCopyDestination(docUri, file.name, rawDest, uri => vscode.workspace.getWorkspaceFolder(uri)?.uri); + } + } + } + + // Default to next to current file + return vscode.Uri.joinPath(Utils.dirname(docUri), file.name); +} + diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteResourceProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteResourceProvider.ts deleted file mode 100644 index 46b47e5198e..00000000000 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteResourceProvider.ts +++ /dev/null @@ -1,91 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; -import { Mime, mediaMimes } from '../../util/mimes'; -import { Schemes } from '../../util/schemes'; -import { PasteUrlAsFormattedLink, createEditAddingLinksForUriList, createEditForMediaFiles, getPasteUrlAsFormattedLinkSetting } from './shared'; -import { getParentDocumentUri } from '../../util/document'; - -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' }, - ]; - - async provideDocumentPasteEdits( - document: vscode.TextDocument, - ranges: readonly vscode.Range[], - dataTransfer: vscode.DataTransfer, - token: vscode.CancellationToken, - ): Promise { - const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.filePaste.enabled', true); - if (!enabled) { - return; - } - - const createEdit = await this._getMediaFilesEdit(document, dataTransfer, token); - if (createEdit) { - return createEdit; - } - - 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 = createEditAddingLinksForUriList(document, ranges, uriList, false, pasteUrlSetting === PasteUrlAsFormattedLink.Smart); - if (!pasteEdit) { - return; - } - - const uriEdit = new vscode.DocumentPasteEdit('', pasteEdit.label); - uriEdit.additionalEdit = pasteEdit.additionalEdits; - uriEdit.yieldTo = this._yieldTo; - return uriEdit; - } - - private async _getMediaFilesEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { - if (getParentDocumentUri(document.uri).scheme === Schemes.untitled) { - return; - } - - const copyFilesIntoWorkspace = vscode.workspace.getConfiguration('markdown', document).get<'mediaFiles' | 'never'>('editor.filePaste.copyIntoWorkspace', 'mediaFiles'); - if (copyFilesIntoWorkspace === 'never') { - return; - } - - const edit = await createEditForMediaFiles(document, dataTransfer, token); - if (!edit) { - return; - } - - const pasteEdit = new vscode.DocumentPasteEdit(edit.snippet, edit.label); - pasteEdit.additionalEdit = edit.additionalEdits; - pasteEdit.yieldTo = this._yieldTo; - return pasteEdit; - } -} - -export function registerPasteSupport(selector: vscode.DocumentSelector,) { - return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteResourceEditProvider(), PasteResourceEditProvider); -} diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts index 814f5e5d9f3..e87c63f3d50 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts @@ -4,16 +4,31 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { ITextDocument } from '../../types/textDocument'; import { Mime } from '../../util/mimes'; -import { createEditAddingLinksForUriList, findValidUriInText, getPasteUrlAsFormattedLinkSetting, PasteUrlAsFormattedLink } from './shared'; +import { createInsertUriListEdit, externalUriSchemes } from './shared'; +enum PasteUrlAsFormattedLink { + Always = 'always', + Smart = 'smart', + Never = 'never' +} + +function getPasteUrlAsFormattedLinkSetting(document: vscode.TextDocument): PasteUrlAsFormattedLink { + return vscode.workspace.getConfiguration('markdown', document) + .get('editor.pasteUrlAsFormattedLink.enabled', PasteUrlAsFormattedLink.Smart); +} + +/** + * Adds support for pasting text uris to create markdown links. + * + * This only applies to `text/plain`. Other mimes like `text/uri-list` are handled by ResourcePasteOrDropProvider. + */ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { public static readonly id = 'insertMarkdownLink'; - public static readonly pasteMimeTypes = [ - Mime.textPlain, - ]; + public static readonly pasteMimeTypes = [Mime.textPlain]; async provideDocumentPasteEdits( document: vscode.TextDocument, @@ -37,18 +52,99 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { return; } - const pasteEdit = createEditAddingLinksForUriList(document, ranges, uriText, true, pasteUrlSetting === PasteUrlAsFormattedLink.Smart); - if (!pasteEdit) { + const edit = createInsertUriListEdit(document, ranges, uriText); + if (!edit) { return; } - const edit = new vscode.DocumentPasteEdit('', pasteEdit.label); - edit.additionalEdit = pasteEdit.additionalEdits; - edit.yieldTo = pasteEdit.markdownLink ? undefined : [{ mimeType: Mime.textPlain }]; - return edit; + const pasteEdit = new vscode.DocumentPasteEdit('', edit.label); + const workspaceEdit = new vscode.WorkspaceEdit(); + workspaceEdit.set(document.uri, edit.edits); + pasteEdit.additionalEdit = workspaceEdit; + + // If smart pasting is enabled, deprioritize this provider when: + // - The user has no selection + // - At least one of the ranges occurs in a context where smart pasting is disabled (such as a fenced code block) + if (pasteUrlSetting === PasteUrlAsFormattedLink.Smart) { + if (!ranges.every(range => shouldSmartPaste(document, range))) { + pasteEdit.yieldTo = [{ mimeType: Mime.textPlain }]; + } + } + return pasteEdit; } } export function registerLinkPasteSupport(selector: vscode.DocumentSelector,) { - return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteUrlEditProvider(), PasteUrlEditProvider); + return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteUrlEditProvider(), { + id: PasteUrlEditProvider.id, + pasteMimeTypes: PasteUrlEditProvider.pasteMimeTypes, + }); +} + +const smartPasteRegexes = [ + { regex: /(\[[^\[\]]*](?:\([^\(\)]*\)|\[[^\[\]]*]))/g }, // In a Markdown link + { regex: /^```[\s\S]*?```$/gm }, // In a backtick fenced code block + { regex: /^~~~[\s\S]*?~~~$/gm }, // In a tildefenced code block + { regex: /^\$\$[\s\S]*?\$\$$/gm }, // In a fenced math block + { regex: /`[^`]*`/g }, // In inline code + { regex: /\$[^$]*\$/g }, // In inline math +]; + +export function shouldSmartPaste(document: ITextDocument, selectedRange: vscode.Range): boolean { + // Disable for empty selections and multi-line selections + if (selectedRange.isEmpty || selectedRange.start.line !== selectedRange.end.line) { + return false; + } + + const rangeText = document.getText(selectedRange); + // Disable for whitespace only selections + if (rangeText.trim().length === 0) { + return false; + } + + // Disable when the selection is already a link + if (findValidUriInText(rangeText)) { + return false; + } + + if (/\[.*\]\(.*\)/.test(rangeText) || /!\[.*\]\(.*\)/.test(rangeText)) { + return false; + } + + for (const regex of smartPasteRegexes) { + const matches = [...document.getText().matchAll(regex.regex)]; + for (const match of matches) { + if (match.index !== undefined) { + const useDefaultPaste = selectedRange.start.character > match.index && selectedRange.end.character < match.index + match[0].length; + if (useDefaultPaste) { + return false; + } + } + } + } + + return true; +} + +export function findValidUriInText(text: string): string | undefined { + const trimmedUrlList = text.trim(); + + // Uri must consist of a single sequence of characters without spaces + if (!/^\S+$/.test(trimmedUrlList)) { + return; + } + + let uri: vscode.Uri; + try { + uri = vscode.Uri.parse(trimmedUrlList); + } catch { + // Could not parse + return; + } + + if (!externalUriSchemes.includes(uri.scheme.toLowerCase()) || uri.authority.length <= 1) { + return; + } + + return trimmedUrlList; } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts index e269ff45c9d..479c8eac956 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts @@ -9,9 +9,9 @@ import * as URI from 'vscode-uri'; import { ITextDocument } from '../../types/textDocument'; import { coalesce } from '../../util/arrays'; import { getDocumentDir } from '../../util/document'; -import { mediaMimes } from '../../util/mimes'; import { Schemes } from '../../util/schemes'; -import { NewFilePathGenerator } from './copyFiles'; +import { resolveSnippet } from './snippets'; +import { parseUriList } from '../../util/uriList'; enum MediaKind { Image, @@ -19,7 +19,7 @@ enum MediaKind { Audio, } -const externalUriSchemes = [ +export const externalUriSchemes = [ 'http', 'https', 'mailto', @@ -51,114 +51,36 @@ export const mediaFileExtensions = new Map([ ['wav', MediaKind.Audio], ]); -export enum PasteUrlAsFormattedLink { - Always = 'always', - Smart = 'smart', - Never = 'never' +export function getSnippetLabel(counter: { insertedAudioVideoCount: number; insertedImageCount: number; insertedLinkCount: number }) { + if (counter.insertedAudioVideoCount > 0) { + if (counter.insertedLinkCount > 0) { + return vscode.l10n.t('Insert Markdown Media and Links'); + } else { + return vscode.l10n.t('Insert Markdown Media'); + } + } else if (counter.insertedImageCount > 0 && counter.insertedLinkCount > 0) { + return vscode.l10n.t('Insert Markdown Images and Links'); + } else if (counter.insertedImageCount > 0) { + return counter.insertedImageCount > 1 + ? vscode.l10n.t('Insert Markdown Images') + : vscode.l10n.t('Insert Markdown Image'); + } else { + return counter.insertedLinkCount > 1 + ? vscode.l10n.t('Insert Markdown Links') + : vscode.l10n.t('Insert Markdown Link'); + } } -export function getPasteUrlAsFormattedLinkSetting(document: vscode.TextDocument): PasteUrlAsFormattedLink { - return vscode.workspace.getConfiguration('markdown', document).get('editor.pasteUrlAsFormattedLink.enabled', PasteUrlAsFormattedLink.Smart); -} - -export function createEditAddingLinksForUriList( +export function createInsertUriListEdit( document: ITextDocument, ranges: readonly vscode.Range[], urlList: string, - isExternalLink: boolean, - useSmartPaste: boolean, -): { additionalEdits: vscode.WorkspaceEdit; label: string; markdownLink: boolean } | undefined { +): { edits: vscode.SnippetTextEdit[]; label: string } | undefined { if (!ranges.length) { return; } - const edits: vscode.SnippetTextEdit[] = []; - let placeHolderValue: number = ranges.length; - let label: string = ''; - let pasteAsMarkdownLink: boolean = true; - let markdownLink: boolean = true; - - for (const range of ranges) { - if (useSmartPaste) { - pasteAsMarkdownLink = shouldSmartPaste(document, range); - markdownLink = pasteAsMarkdownLink; // FIX: this will only match the last range - } - - const snippet = tryGetUriListSnippet(document, urlList, document.getText(range), placeHolderValue, pasteAsMarkdownLink, isExternalLink); - if (!snippet) { - return; - } - - pasteAsMarkdownLink = true; - placeHolderValue--; - edits.push(new vscode.SnippetTextEdit(range, snippet.snippet)); - label = snippet.label; - } - - const additionalEdits = new vscode.WorkspaceEdit(); - additionalEdits.set(document.uri, edits); - - return { additionalEdits, label, markdownLink }; -} - -export function findValidUriInText(text: string): string | undefined { - const trimmedUrlList = text.trim(); - - // Uri must consist of a single sequence of characters without spaces - if (!/^\S+$/.test(trimmedUrlList)) { - return; - } - - let uri: vscode.Uri; - try { - uri = vscode.Uri.parse(trimmedUrlList); - } catch { - // Could not parse - return; - } - - if (!externalUriSchemes.includes(uri.scheme.toLowerCase()) || uri.authority.length <= 1) { - return; - } - - return trimmedUrlList; -} - -const smartPasteRegexes = [ - { regex: /(\[[^\[\]]*](?:\([^\(\)]*\)|\[[^\[\]]*]))/g }, // In a Markdown link - { regex: /^```[\s\S]*?```$/gm }, // In a backtick fenced code block - { regex: /^~~~[\s\S]*?~~~$/gm }, // In a tildefenced code block - { regex: /^\$\$[\s\S]*?\$\$$/gm }, // In a fenced math block - { regex: /`[^`]*`/g }, // In inline code - { regex: /\$[^$]*\$/g }, // In inline math -]; - -export function shouldSmartPaste(document: ITextDocument, selectedRange: vscode.Range): boolean { - if (selectedRange.isEmpty || /^[\s\n]*$/.test(document.getText(selectedRange)) || findValidUriInText(document.getText(selectedRange))) { - return false; - } - - if (/\[.*\]\(.*\)/.test(document.getText(selectedRange)) || /!\[.*\]\(.*\)/.test(document.getText(selectedRange))) { - return false; - } - - for (const regex of smartPasteRegexes) { - const matches = [...document.getText().matchAll(regex.regex)]; - for (const match of matches) { - if (match.index !== undefined) { - const useDefaultPaste = selectedRange.start.character > match.index && selectedRange.end.character < match.index + match[0].length; - if (useDefaultPaste) { - return false; - } - } - } - } - - return true; -} - -export function tryGetUriListSnippet(document: ITextDocument, urlList: String, title = '', placeHolderValue = 0, pasteAsMarkdownLink = true, isExternalLink = false): { snippet: vscode.SnippetString; label: string } | undefined { - const entries = coalesce(urlList.split(/\r?\n/g).map(line => { + const entries = coalesce(parseUriList(urlList).map(line => { try { return { uri: vscode.Uri.parse(line), str: line }; } catch { @@ -166,7 +88,36 @@ export function tryGetUriListSnippet(document: ITextDocument, urlList: String, t return undefined; } })); - return createUriListSnippet(document, entries, title, placeHolderValue, pasteAsMarkdownLink, isExternalLink); + if (!entries.length) { + return; + } + + const edits: vscode.SnippetTextEdit[] = []; + let placeHolderValue = ranges.length; + + let insertedLinkCount = 0; + let insertedImageCount = 0; + let insertedAudioVideoCount = 0; + + for (const range of ranges) { + const snippet = createUriListSnippet(document.uri, entries, { + placeholderText: !range.isEmpty ? document.getText(range) : undefined, + placeholderStartIndex: placeHolderValue, + }); + if (!snippet) { + continue; + } + + insertedLinkCount += snippet.insertedLinkCount; + insertedImageCount += snippet.insertedImageCount; + insertedAudioVideoCount += snippet.insertedAudioVideoCount; + + placeHolderValue--; + edits.push(new vscode.SnippetTextEdit(range, snippet.snippet)); + } + + const label = getSnippetLabel({ insertedAudioVideoCount, insertedImageCount, insertedLinkCount }); + return { edits, label }; } interface UriListSnippetOptions { @@ -175,82 +126,76 @@ interface UriListSnippetOptions { readonly placeholderStartIndex?: number; /** - * Should the snippet be for an image link or video? + * Controls if a media link (`![](...)`) is inserted instead of a normal markdown link. * - * If `undefined`, tries to infer this from the uri. + * By default tries to infer this from the uri. */ readonly insertAsMedia?: boolean; readonly separator?: string; } -export function appendToLinkSnippet( - snippet: vscode.SnippetString, - title: string, - link: string, - placeholderValue: number, - _isExternalLink: boolean, -): void { - snippet.appendText('['); - snippet.appendPlaceholder(escapeBrackets(title) || 'Title', placeholderValue); - snippet.appendText(`](${escapeMarkdownLinkPath(link)})`); + +interface UriSnippet { + snippet: vscode.SnippetString; + insertedLinkCount: number; + insertedImageCount: number; + insertedAudioVideoCount: number; } export function createUriListSnippet( - document: ITextDocument, + document: vscode.Uri, uris: ReadonlyArray<{ readonly uri: vscode.Uri; readonly str?: string; }>, - title = '', - placeholderValue = 0, - pasteAsMarkdownLink = true, - isExternalLink = false, options?: UriListSnippetOptions, -): { snippet: vscode.SnippetString; label: string } | undefined { +): UriSnippet | undefined { if (!uris.length) { return; } - const documentDir = getDocumentDir(document.uri); + const documentDir = getDocumentDir(document); + const config = vscode.workspace.getConfiguration('markdown', document); + const title = options?.placeholderText || 'Title'; - const snippet = new vscode.SnippetString(); let insertedLinkCount = 0; let insertedImageCount = 0; let insertedAudioVideoCount = 0; + const snippet = new vscode.SnippetString(); + let placeholderIndex = options?.placeholderStartIndex ?? 1; + uris.forEach((uri, i) => { const mdPath = getRelativeMdPath(documentDir, uri.uri) ?? uri.str ?? uri.uri.toString(); const ext = URI.Utils.extname(uri.uri).toLowerCase().replace('.', ''); - const insertAsMedia = typeof options?.insertAsMedia === 'undefined' ? mediaFileExtensions.has(ext) : !!options.insertAsMedia; - const insertAsVideo = mediaFileExtensions.get(ext) === MediaKind.Video; - const insertAsAudio = mediaFileExtensions.get(ext) === MediaKind.Audio; + const insertAsMedia = options?.insertAsMedia || (typeof options?.insertAsMedia === 'undefined' && mediaFileExtensions.has(ext)); - if (insertAsVideo) { - insertedAudioVideoCount++; - snippet.appendText(`'); - } else if (insertAsAudio) { - insertedAudioVideoCount++; - snippet.appendText(`'); - } else if (insertAsMedia) { - insertedImageCount++; - if (pasteAsMarkdownLink) { + if (insertAsMedia) { + const insertAsVideo = mediaFileExtensions.get(ext) === MediaKind.Video; + const insertAsAudio = mediaFileExtensions.get(ext) === MediaKind.Audio; + if (insertAsVideo || insertAsAudio) { + insertedAudioVideoCount++; + const mediaSnippet = insertAsVideo + ? config.get('editor.filePaste.videoSnippet', '') + : config.get('editor.filePaste.audioSnippet', ''); + snippet.value += resolveSnippet(mediaSnippet, new Map([ + ['src', mdPath], + ['title', `\${${placeholderIndex++}:${title}}`], + ])); + } else { + insertedImageCount++; snippet.appendText('!['); - const placeholderText = escapeBrackets(title) || options?.placeholderText || 'Alt text'; - const placeholderIndex = typeof options?.placeholderStartIndex !== 'undefined' ? options?.placeholderStartIndex + i : (placeholderValue === 0 ? undefined : placeholderValue); + const placeholderText = escapeBrackets(options?.placeholderText || 'alt text'); snippet.appendPlaceholder(placeholderText, placeholderIndex); snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`); - } else { - snippet.appendText(escapeMarkdownLinkPath(mdPath)); } } else { insertedLinkCount++; - appendToLinkSnippet(snippet, title, mdPath, placeholderValue, isExternalLink); + snippet.appendText('['); + snippet.appendPlaceholder(escapeBrackets(options?.placeholderText ?? 'text'), placeholderIndex); + snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`); } if (i < uris.length - 1 && uris.length > 1) { @@ -258,97 +203,9 @@ export function createUriListSnippet( } }); - let label: string; - if (insertedAudioVideoCount > 0) { - if (insertedLinkCount > 0) { - label = vscode.l10n.t('Insert Markdown Media and Links'); - } else { - label = vscode.l10n.t('Insert Markdown Media'); - } - } else if (insertedImageCount > 0 && insertedLinkCount > 0) { - label = vscode.l10n.t('Insert Markdown Images and Links'); - } else if (insertedImageCount > 0) { - label = insertedImageCount > 1 - ? vscode.l10n.t('Insert Markdown Images') - : vscode.l10n.t('Insert Markdown Image'); - } else { - label = insertedLinkCount > 1 - ? vscode.l10n.t('Insert Markdown Links') - : vscode.l10n.t('Insert Markdown Link'); - } - - return { snippet, label }; + return { snippet, insertedAudioVideoCount, insertedImageCount, insertedLinkCount }; } -/** - * Create a new edit from the image files in a data transfer. - * - * This tries copying files outside of the workspace into the workspace. - */ -export async function createEditForMediaFiles( - document: vscode.TextDocument, - dataTransfer: vscode.DataTransfer, - token: vscode.CancellationToken -): Promise<{ snippet: vscode.SnippetString; label: string; additionalEdits: vscode.WorkspaceEdit } | undefined> { - if (document.uri.scheme === Schemes.untitled) { - return; - } - - interface FileEntry { - readonly uri: vscode.Uri; - readonly newFile?: { readonly contents: vscode.DataTransferFile; readonly overwrite: boolean }; - } - - const pathGenerator = new NewFilePathGenerator(); - const fileEntries = coalesce(await Promise.all(Array.from(dataTransfer, async ([mime, item]): Promise => { - if (!mediaMimes.has(mime)) { - return; - } - - const file = item?.asFile(); - if (!file) { - return; - } - - if (file.uri) { - // If the file is already in a workspace, we don't want to create a copy of it - const workspaceFolder = vscode.workspace.getWorkspaceFolder(file.uri); - if (workspaceFolder) { - return { uri: file.uri }; - } - } - - const newFile = await pathGenerator.getNewFilePath(document, file, token); - if (!newFile) { - return; - } - return { uri: newFile.uri, newFile: { contents: file, overwrite: newFile.overwrite } }; - }))); - if (!fileEntries.length) { - return; - } - - const workspaceEdit = new vscode.WorkspaceEdit(); - for (const entry of fileEntries) { - if (entry.newFile) { - workspaceEdit.createFile(entry.uri, { - contents: entry.newFile.contents, - overwrite: entry.newFile.overwrite, - }); - } - } - - const snippet = createUriListSnippet(document, fileEntries); - if (!snippet) { - return; - } - - return { - snippet: snippet.snippet, - label: snippet.label, - additionalEdits: workspaceEdit, - }; -} function getRelativeMdPath(dir: vscode.Uri | undefined, file: vscode.Uri): string | undefined { if (dir && dir.scheme === file.scheme && dir.authority === file.authority) { @@ -365,10 +222,6 @@ function getRelativeMdPath(dir: vscode.Uri | undefined, file: vscode.Uri): strin return undefined; } -function escapeHtmlAttribute(attr: string): string { - return encodeURI(attr).replaceAll('"', '"'); -} - function escapeMarkdownLinkPath(mdPath: string): string { if (needsBracketLink(mdPath)) { return '<' + mdPath.replaceAll('<', '\\<').replaceAll('>', '\\>') + '>'; diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/snippets.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/snippets.ts new file mode 100644 index 00000000000..854cf2175d0 --- /dev/null +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/snippets.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Resolves variables in a VS Code snippet style string + */ +export function resolveSnippet(snippetString: string, vars: ReadonlyMap): string { + return snippetString.replaceAll(/(?\\\$)|(?\w+)(?:\/(?(?:\\\/|[^\}])+?)\/(?(?:\\\/|[^\}])+?)\/)?\}/g, (match, _escape, name, pattern, replacement, _offset, _str, groups) => { + if (groups?.['escape']) { + return '$'; + } + + const entry = vars.get(name); + if (typeof entry !== 'string') { + return match; + } + + if (pattern && replacement) { + return entry.replace(new RegExp(replaceTransformEscapes(pattern)), replaceTransformEscapes(replacement)); + } + + return entry; + }); +} + + +function replaceTransformEscapes(str: string): string { + return str.replaceAll(/\\\//g, '/'); +} + diff --git a/extensions/markdown-language-features/src/test/markdownLink.test.ts b/extensions/markdown-language-features/src/test/markdownLink.test.ts index 4d0fffd0585..0d30ec948d5 100644 --- a/extensions/markdown-language-features/src/test/markdownLink.test.ts +++ b/extensions/markdown-language-features/src/test/markdownLink.test.ts @@ -6,15 +6,20 @@ import * as assert from 'assert'; import 'mocha'; import * as vscode from 'vscode'; import { InMemoryDocument } from '../client/inMemoryDocument'; -import { appendToLinkSnippet, createEditAddingLinksForUriList, findValidUriInText, shouldSmartPaste } from '../languageFeatures/copyFiles/shared'; +import { findValidUriInText, shouldSmartPaste } from '../languageFeatures/copyFiles/pasteUrlProvider'; +import { createInsertUriListEdit } from '../languageFeatures/copyFiles/shared'; + +function makeTestDoc(contents: string) { + return new InMemoryDocument(vscode.Uri.file('test.md'), contents); +} suite('createEditAddingLinksForUriList', () => { test('Markdown Link Pasting should occur for a valid link (end to end)', async () => { // createEditAddingLinksForUriList -> checkSmartPaste -> tryGetUriListSnippet -> createUriListSnippet -> createLinkSnippet - const result = createEditAddingLinksForUriList( - new InMemoryDocument(vscode.Uri.file('test.md'), 'hello world!'), [new vscode.Range(0, 0, 0, 12)], 'https://www.microsoft.com/', true, true); + const result = createInsertUriListEdit( + new InMemoryDocument(vscode.Uri.file('test.md'), 'hello world!'), [new vscode.Range(0, 0, 0, 12)], 'https://www.microsoft.com/'); // need to check the actual result -> snippet value assert.strictEqual(result?.label, 'Insert Markdown Link'); }); @@ -100,41 +105,31 @@ suite('createEditAddingLinksForUriList', () => { }); }); - suite('appendToLinkSnippet', () => { + suite('createInsertUriListEdit', () => { test('Should create snippet with < > when pasted link has an mismatched parentheses', () => { - const uriString = 'https://www.mic(rosoft.com'; - const snippet = new vscode.SnippetString(''); - appendToLinkSnippet(snippet, 'abc', uriString, 0, true); - assert.strictEqual(snippet?.value, '[${0:abc}]()'); + const edit = createInsertUriListEdit(makeTestDoc(''), [new vscode.Range(0, 0, 0, 0)], 'https://www.mic(rosoft.com'); + assert.strictEqual(edit?.edits?.[0].snippet.value, '[${1:text}]()'); }); test('Should create Markdown link snippet when pasteAsMarkdownLink is true', () => { - const uriString = 'https://www.microsoft.com'; - const snippet = new vscode.SnippetString(''); - appendToLinkSnippet(snippet, '', uriString, 0, true); - assert.strictEqual(snippet?.value, '[${0:Title}](https://www.microsoft.com)'); + const edit = createInsertUriListEdit(makeTestDoc(''), [new vscode.Range(0, 0, 0, 0)], 'https://www.microsoft.com'); + assert.strictEqual(edit?.edits?.[0].snippet.value, '[${1:text}](https://www.microsoft.com)'); }); test('Should use an unencoded URI string in Markdown link when passing in an external browser link', () => { - const uriString = 'https://www.microsoft.com'; - const snippet = new vscode.SnippetString(''); - appendToLinkSnippet(snippet, '', uriString, 0, true); - assert.strictEqual(snippet?.value, '[${0:Title}](https://www.microsoft.com)'); + const edit = createInsertUriListEdit(makeTestDoc(''), [new vscode.Range(0, 0, 0, 0)], 'https://www.microsoft.com'); + assert.strictEqual(edit?.edits?.[0].snippet.value, '[${1:text}](https://www.microsoft.com)'); }); test('Should not decode an encoded URI string when passing in an external browser link', () => { - const uriString = 'https://www.microsoft.com/%20'; - const snippet = new vscode.SnippetString(''); - appendToLinkSnippet(snippet, '', uriString, 0, true); - assert.strictEqual(snippet?.value, '[${0:Title}](https://www.microsoft.com/%20)'); + const edit = createInsertUriListEdit(makeTestDoc(''), [new vscode.Range(0, 0, 0, 0)], 'https://www.microsoft.com/%20'); + assert.strictEqual(edit?.edits?.[0].snippet.value, '[${1:text}](https://www.microsoft.com/%20)'); }); test('Should not encode an unencoded URI string when passing in an external browser link', () => { - const uriString = 'https://www.example.com/path?query=value&another=value#fragment'; - const snippet = new vscode.SnippetString(''); - appendToLinkSnippet(snippet, '', uriString, 0, true); - assert.strictEqual(snippet?.value, '[${0:Title}](https://www.example.com/path?query=value&another=value#fragment)'); + const edit = createInsertUriListEdit(makeTestDoc(''), [new vscode.Range(0, 0, 0, 0)], 'https://www.example.com/path?query=value&another=value#fragment'); + assert.strictEqual(edit?.edits?.[0].snippet.value, '[${1:text}](https://www.example.com/path?query=value&another=value#fragment)'); }); }); @@ -220,7 +215,3 @@ suite('createEditAddingLinksForUriList', () => { }); }); }); - -function makeTestDoc(contents: string) { - return new InMemoryDocument(vscode.Uri.file('test.md'), contents); -} diff --git a/extensions/markdown-language-features/src/util/uriList.ts b/extensions/markdown-language-features/src/util/uriList.ts new file mode 100644 index 00000000000..04897af453e --- /dev/null +++ b/extensions/markdown-language-features/src/util/uriList.ts @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +function splitUriList(str: string): string[] { + return str.split('\r\n'); +} + +export function parseUriList(str: string): string[] { + return splitUriList(str) + .filter(value => !value.startsWith('#')) // Remove comments + .map(value => value.trim()); +} From 6079e24015ed3594788b1e68f1d5af764cdd9003 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 4 Jan 2024 16:37:14 -0800 Subject: [PATCH 0075/1897] Allow optionally pasting html content in clipboard (#200912) Fixes #57577 This adds a new, non-default paste provider that inserts the `text/html` content from the clipboard Originally added this just for markdown and html, but I think it's likely useful in other languages too. Adding to core to keep implementation consistent --- .../browser/defaultProviders.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts index 69ff4dc3181..7ba4cd9949e 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts @@ -152,6 +152,30 @@ class RelativePathProvider extends SimplePasteAndDropProvider { } } +class PasteHtmlProvider implements DocumentPasteEditProvider { + + public readonly id = 'html'; + + public readonly pasteMimeTypes = ['text/html']; + + private readonly _yieldTo = [{ mimeType: Mimes.text }]; + + async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise { + const entry = dataTransfer.get('text/html'); + const htmlText = await entry?.asString(); + if (!htmlText || token.isCancellationRequested) { + return; + } + + return { + insertText: htmlText, + yieldTo: this._yieldTo, + label: localize('pasteHtmlLabel', 'Insert HTML'), + detail: builtInLabel, + }; + } +} + async function extractUriList(dataTransfer: IReadonlyVSDataTransfer): Promise<{ readonly uri: URI; readonly originalText: string }[]> { const urlListEntry = dataTransfer.get(Mimes.uriList); if (!urlListEntry) { @@ -193,5 +217,6 @@ export class DefaultPasteProvidersFeature extends Disposable { this._register(languageFeaturesService.documentPasteEditProvider.register('*', new DefaultTextProvider())); this._register(languageFeaturesService.documentPasteEditProvider.register('*', new PathProvider())); this._register(languageFeaturesService.documentPasteEditProvider.register('*', new RelativePathProvider(workspaceContextService))); + this._register(languageFeaturesService.documentPasteEditProvider.register('*', new PasteHtmlProvider())); } } From 14b09484ffb970df1f9dd5e0bf21bff88b1e8f52 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 4 Jan 2024 17:21:09 -0800 Subject: [PATCH 0076/1897] Change how placeholders in parsed markdown links are created (#201851) Fixes #186284 See https://github.com/microsoft/vscode/issues/186284#issuecomment-1877968127 for details Also fixes the tab order to always start from the top most range in the document --- .../src/languageFeatures/copyFiles/shared.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts index 479c8eac956..bd2bf0b8053 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts @@ -93,16 +93,22 @@ export function createInsertUriListEdit( } const edits: vscode.SnippetTextEdit[] = []; - let placeHolderValue = ranges.length; let insertedLinkCount = 0; let insertedImageCount = 0; let insertedAudioVideoCount = 0; - for (const range of ranges) { + // Use 1 for all empty ranges but give non-empty range unique indices starting after 1 + let placeHolderStartIndex = 1 + entries.length; + + // Sort ranges by start position + const orderedRanges = [...ranges].sort((a, b) => a.start.compareTo(b.start)); + const allRangesAreEmpty = orderedRanges.every(range => range.isEmpty); + + for (const range of orderedRanges) { const snippet = createUriListSnippet(document.uri, entries, { - placeholderText: !range.isEmpty ? document.getText(range) : undefined, - placeholderStartIndex: placeHolderValue, + placeholderText: range.isEmpty ? undefined : document.getText(range), + placeholderStartIndex: allRangesAreEmpty ? 1 : placeHolderStartIndex, }); if (!snippet) { continue; @@ -112,7 +118,8 @@ export function createInsertUriListEdit( insertedImageCount += snippet.insertedImageCount; insertedAudioVideoCount += snippet.insertedAudioVideoCount; - placeHolderValue--; + placeHolderStartIndex += entries.length; + edits.push(new vscode.SnippetTextEdit(range, snippet.snippet)); } From d2028ff9c7678834f810cad53f10a35718f3fcba Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Thu, 4 Jan 2024 18:40:31 -0800 Subject: [PATCH 0077/1897] start to deprecate boolean for codeActionsOnSave (#201808) * deprecate boolean in setting, shows warning, still allows true for now * change default value --- .../codeActions/browser/codeActionsContribution.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts b/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts index 70ca709c5be..4225ffa7cd7 100644 --- a/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts +++ b/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts @@ -19,7 +19,7 @@ import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensi const createCodeActionsAutoSave = (description: string): IJSONSchema => { return { - type: ['string', 'boolean'], + type: 'string', enum: ['always', 'explicit', 'never', true, false], enumDescriptions: [ nls.localize('alwaysSave', 'Triggers Code Actions on explicit saves and auto saves triggered by window or focus changes.'), @@ -28,7 +28,7 @@ const createCodeActionsAutoSave = (description: string): IJSONSchema => { nls.localize('explicitSaveBoolean', 'Triggers Code Actions only when explicitly saved. This value will be deprecated in favor of "explicit".'), nls.localize('neverSaveBoolean', 'Never triggers Code Actions on save. This value will be deprecated in favor of "never".') ], - default: true, + default: 'explicit', description: description }; }; @@ -43,7 +43,7 @@ const codeActionsOnSaveSchema: IConfigurationPropertySchema = { type: 'object', properties: codeActionsOnSaveDefaultProperties, additionalProperties: { - type: ['string', 'boolean'] + type: 'string' }, }, { @@ -54,7 +54,7 @@ const codeActionsOnSaveSchema: IConfigurationPropertySchema = { markdownDescription: nls.localize('editor.codeActionsOnSave', 'Run Code Actions for the editor on save. Code Actions must be specified and the editor must not be shutting down. Example: `"source.organizeImports": "explicit" `'), type: ['object', 'array'], additionalProperties: { - type: ['string', 'boolean'], + type: 'string', enum: ['always', 'explicit', 'never', true, false], }, default: {}, From bd1fa85f89e0596e83354c56e06afa90a4f35163 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 5 Jan 2024 13:38:34 +0530 Subject: [PATCH 0078/1897] Revert "debug: fix toolbar min y position (#200090)" (#201863) This reverts commit 6605df0f116425196f2ebfb77bdb6557bd9fa946. --- src/vs/workbench/contrib/debug/browser/debugToolBar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index ce66d512dc2..209acbc3cd8 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -281,7 +281,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { private get yRange(): [number, number] { if (!this._yRange) { const isTitleBarVisible = this.layoutService.isVisible(Parts.TITLEBAR_PART, dom.getWindow(this.layoutService.activeContainer)); - const yMin = isTitleBarVisible ? this.layoutService.mainContainerOffset.top : 0; + const yMin = isTitleBarVisible ? 0 : this.layoutService.mainContainerOffset.top; let yMax = 0; if (isTitleBarVisible) { From c563e053bacb8dbaae5eff970d94cc9258f693f6 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 5 Jan 2024 13:42:08 +0530 Subject: [PATCH 0079/1897] fix problems view filters names (#201868) --- .../markers/browser/markers.contribution.ts | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 5d03f79aa92..dffcbdff240 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -208,12 +208,9 @@ registerAction2(class extends ViewAction { constructor() { super({ id: `workbench.actions.${Markers.MARKERS_VIEW_ID}.toggleErrors`, - title: localize('toggle errors', "Toggle Errors"), + title: localize('show errors', "Show Errors"), category: localize('problems', "Problems"), - toggled: { - condition: MarkersContextKeys.ShowErrorsFilterContextKey, - title: localize('errors', "Show Errors") - }, + toggled: MarkersContextKeys.ShowErrorsFilterContextKey, menu: { id: viewFilterSubmenu, group: '1_filter', @@ -233,12 +230,9 @@ registerAction2(class extends ViewAction { constructor() { super({ id: `workbench.actions.${Markers.MARKERS_VIEW_ID}.toggleWarnings`, - title: localize('toggle warnings', "Toggle Warnings"), + title: localize('show warnings', "Show Warnings"), category: localize('problems', "Problems"), - toggled: { - condition: MarkersContextKeys.ShowWarningsFilterContextKey, - title: localize('warnings', "Show Warnings") - }, + toggled: MarkersContextKeys.ShowWarningsFilterContextKey, menu: { id: viewFilterSubmenu, group: '1_filter', @@ -258,12 +252,9 @@ registerAction2(class extends ViewAction { constructor() { super({ id: `workbench.actions.${Markers.MARKERS_VIEW_ID}.toggleInfos`, - title: localize('toggle infos', "Toggle Infos"), + title: localize('show infos', "Show Infos"), category: localize('problems', "Problems"), - toggled: { - condition: MarkersContextKeys.ShowInfoFilterContextKey, - title: localize('Infos', "Show Infos") - }, + toggled: MarkersContextKeys.ShowInfoFilterContextKey, menu: { id: viewFilterSubmenu, group: '1_filter', @@ -283,12 +274,9 @@ registerAction2(class extends ViewAction { constructor() { super({ id: `workbench.actions.${Markers.MARKERS_VIEW_ID}.toggleActiveFile`, - title: localize('toggle active file', "Toggle Active File"), + title: localize('show active file', "Show Active File Only"), category: localize('problems', "Problems"), - toggled: { - condition: MarkersContextKeys.ShowActiveFileFilterContextKey, - title: localize('Active File', "Show Active File Only") - }, + toggled: MarkersContextKeys.ShowActiveFileFilterContextKey, menu: { id: viewFilterSubmenu, group: '2_filter', @@ -308,12 +296,9 @@ registerAction2(class extends ViewAction { constructor() { super({ id: `workbench.actions.${Markers.MARKERS_VIEW_ID}.toggleExcludedFiles`, - title: localize('toggle Excluded Files', "Toggle Excluded Files"), + title: localize('show excluded files', "Show Excluded Files"), category: localize('problems', "Problems"), - toggled: { - condition: MarkersContextKeys.ShowExcludedFilesFilterContextKey, - title: localize('Excluded Files', "Hide Excluded Files") - }, + toggled: MarkersContextKeys.ShowExcludedFilesFilterContextKey.negate(), menu: { id: viewFilterSubmenu, group: '2_filter', From cbbef4dc01814fe7ba6bd6490ffae4f29a2367e2 Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 5 Jan 2024 18:03:05 +0900 Subject: [PATCH 0080/1897] chore: address codeql warnings (#201776) --- build/linux/debian/install-sysroot.js | 1 + build/linux/debian/install-sysroot.ts | 1 + src/vs/base/parts/ipc/node/ipc.net.ts | 6 +++--- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/build/linux/debian/install-sysroot.js b/build/linux/debian/install-sysroot.js index de6c1656411..40ca42efe78 100644 --- a/build/linux/debian/install-sysroot.js +++ b/build/linux/debian/install-sysroot.js @@ -34,6 +34,7 @@ function getElectronVersion() { return { electronVersion, msBuildId }; } function getSha(filename) { + // CodeQL [SM04514] Hash logic cannot be changed due to external dependency, also the code is only used during build. const hash = (0, crypto_1.createHash)('sha1'); // Read file 1 MB at a time const fd = fs.openSync(filename, 'r'); diff --git a/build/linux/debian/install-sysroot.ts b/build/linux/debian/install-sysroot.ts index 9de84d60624..37ea883d9bc 100644 --- a/build/linux/debian/install-sysroot.ts +++ b/build/linux/debian/install-sysroot.ts @@ -45,6 +45,7 @@ function getElectronVersion(): Record { } function getSha(filename: fs.PathLike): string { + // CodeQL [SM04514] Hash logic cannot be changed due to external dependency, also the code is only used during build. const hash = createHash('sha1'); // Read file 1 MB at a time const fd = fs.openSync(filename, 'r'); diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index e47b6c70b23..7d57ca6cb47 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -785,11 +785,12 @@ export function createRandomIPCHandle(): string { } export function createStaticIPCHandle(directoryPath: string, type: string, version: string): string { - const scope = createHash('md5').update(directoryPath).digest('hex'); + const scope = createHash('sha256').update(directoryPath).digest('hex'); + const scopeForSocket = scope.substr(0, 8); // Windows: use named pipe if (process.platform === 'win32') { - return `\\\\.\\pipe\\${scope}-${version}-${type}-sock`; + return `\\\\.\\pipe\\${scopeForSocket}-${version}-${type}-sock`; } // Mac & Unix: Use socket file @@ -799,7 +800,6 @@ export function createStaticIPCHandle(directoryPath: string, type: string, versi const versionForSocket = version.substr(0, 4); const typeForSocket = type.substr(0, 6); - const scopeForSocket = scope.substr(0, 8); let result: string; if (process.platform !== 'darwin' && XDG_RUNTIME_DIR && !process.env['VSCODE_PORTABLE']) { From 4500bff058ad93a1901521e7a345b9abe24ca2a4 Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 5 Jan 2024 10:08:39 +0100 Subject: [PATCH 0081/1897] Drag Between Cleanup :lipstick: (#201870) Drag Between Cleanup --- src/vs/base/browser/ui/list/listView.ts | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index a3a5fbe93f0..4f8ab395ce6 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -1221,27 +1221,26 @@ export class ListView implements IListView { feedback = distinct(feedback).filter(i => i >= -1 && i < this.length).sort((a, b) => a - b); feedback = feedback[0] === -1 ? [-1] : feedback; - const dragOverEffctPosition = typeof result !== 'boolean' && result.effect && result.effect.position ? result.effect.position : ListDragOverEffectPosition.Over; + const dragOverEffectPosition = typeof result !== 'boolean' && result.effect && result.effect.position ? result.effect.position : ListDragOverEffectPosition.Over; - if (equalsDragFeedback(this.currentDragFeedback, feedback) && this.currentDragFeedbackPosition === dragOverEffctPosition) { + if (equalsDragFeedback(this.currentDragFeedback, feedback) && this.currentDragFeedbackPosition === dragOverEffectPosition) { return true; } this.currentDragFeedback = feedback; - this.currentDragFeedbackPosition = dragOverEffctPosition; + this.currentDragFeedbackPosition = dragOverEffectPosition; this.currentDragFeedbackDisposable.dispose(); if (feedback[0] === -1) { // entire list feedback - console.log('entire list feedback', dragOverEffctPosition); - this.domNode.classList.add(dragOverEffctPosition); - this.rowsContainer.classList.add(dragOverEffctPosition); + this.domNode.classList.add(dragOverEffectPosition); + this.rowsContainer.classList.add(dragOverEffectPosition); this.currentDragFeedbackDisposable = toDisposable(() => { - this.domNode.classList.remove(dragOverEffctPosition); - this.rowsContainer.classList.remove(dragOverEffctPosition); + this.domNode.classList.remove(dragOverEffectPosition); + this.rowsContainer.classList.remove(dragOverEffectPosition); }); } else { - if (feedback.length > 1 && dragOverEffctPosition !== ListDragOverEffectPosition.Over) { + if (feedback.length > 1 && dragOverEffectPosition !== ListDragOverEffectPosition.Over) { throw new Error('Can\'t use multiple feedbacks with position different than \'over\''); } @@ -1249,7 +1248,7 @@ export class ListView implements IListView { const item = this.items[index]!; item.dropTarget = true; - item.row?.domNode.classList.add(dragOverEffctPosition); + item.row?.domNode.classList.add(dragOverEffectPosition); } this.currentDragFeedbackDisposable = toDisposable(() => { @@ -1257,7 +1256,7 @@ export class ListView implements IListView { const item = this.items[index]!; item.dropTarget = false; - item.row?.domNode.classList.remove(dragOverEffctPosition); + item.row?.domNode.classList.remove(dragOverEffectPosition); } }); } From d57d309cdc9998259988af66084efa586deb9c6e Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 5 Jan 2024 11:03:39 +0100 Subject: [PATCH 0082/1897] Fix drag and drop target position calculation (#201872) fix corner cases --- .../debug/browser/watchExpressionsView.ts | 22 ++++++++++------ .../files/browser/views/openEditorsView.ts | 25 +++++++++---------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 24bcb37bc4a..991c7712217 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -441,16 +441,22 @@ class WatchExpressionsDragAndDrop implements ITreeDragAndDrop { const watches = this.debugService.getModel().getWatchExpressions(); const sourcePosition = watches.indexOf(draggedElement); - let targetPosition = targetElement instanceof Expression ? watches.indexOf(targetElement) : watches.length - 1; - switch (targetSector) { - case ListViewTargetSector.TOP: - case ListViewTargetSector.CENTER_TOP: - targetPosition -= 1; break; - } + let targetPosition; + if (targetElement instanceof Expression) { + targetPosition = watches.indexOf(targetElement); - if (sourcePosition >= targetPosition) { - targetPosition += 1; + switch (targetSector) { + case ListViewTargetSector.BOTTOM: + case ListViewTargetSector.CENTER_BOTTOM: + targetPosition++; break; + } + + if (sourcePosition < targetPosition) { + targetPosition--; + } + } else { + targetPosition = watches.length - 1; } this.debugService.moveWatchExpression(draggedElement.getId(), targetPosition); diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 27eff7350dc..a6428c1317e 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -738,27 +738,26 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop { - // Moving an editor which is located before the target location does not change the index of the target - if (oe.group.getIndexOfEditor(oe.editor) >= targetEditorIndex) { - offset += 1; + for (const oe of data.elements) { + const sourceEditorIndex = oe.group.getIndexOfEditor(oe.editor); + if (sourceEditorIndex < targetEditorIndex) { + targetEditorIndex--; } - oe.group.moveEditor(oe.editor, group, { index: targetIndex + offset, preserveFocus: true }); - }); + oe.group.moveEditor(oe.editor, group, { index: targetEditorIndex, preserveFocus: true }); + targetEditorIndex++; + } this.editorGroupService.activateGroup(group); } else { - this.dropHandler.handleDrop(originalEvent, mainWindow, () => group, () => group.focus(), { index: targetIndex + 1 }); + this.dropHandler.handleDrop(originalEvent, mainWindow, () => group, () => group.focus(), { index: targetEditorIndex }); } } From 3bdc234232d6e795badbd0be81b86ceea9f30cd2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 5 Jan 2024 15:46:21 +0530 Subject: [PATCH 0083/1897] fix #201198 (#201875) --- src/vs/workbench/browser/parts/views/media/views.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index 05fb4fc8c93..15c4a1aae00 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -223,6 +223,10 @@ /* filter view pane */ +.monaco-workbench .auxiliarybar.pane-composite-part > .title.has-composite-bar > .title-actions .monaco-action-bar .action-item.viewpane-filter-container { + max-width: inherit; +} + .viewpane-filter-container { cursor: default; display: flex; From 0dc196fc51ba1eed615f6dbfb3aac501f975b78b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 5 Jan 2024 11:22:15 +0100 Subject: [PATCH 0084/1897] use sha256 for validating windows updates (#201777) * use sha256 for validating windows updates * fix test --- src/vs/base/node/crypto.ts | 6 +++--- src/vs/base/test/node/crypto.test.ts | 2 +- src/vs/platform/update/common/update.ts | 2 +- src/vs/platform/update/electron-main/updateService.win32.ts | 6 ++---- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/vs/base/node/crypto.ts b/src/vs/base/node/crypto.ts index 7631aac0524..910d492e8ff 100644 --- a/src/vs/base/node/crypto.ts +++ b/src/vs/base/node/crypto.ts @@ -7,10 +7,10 @@ import * as crypto from 'crypto'; import * as fs from 'fs'; import { createSingleCallFunction } from 'vs/base/common/functional'; -export async function checksum(path: string, sha1hash: string | undefined): Promise { +export async function checksum(path: string, sha256hash: string | undefined): Promise { const checksumPromise = new Promise((resolve, reject) => { const input = fs.createReadStream(path); - const hash = crypto.createHash('sha1'); + const hash = crypto.createHash('sha256'); input.pipe(hash); const done = createSingleCallFunction((err?: Error, result?: string) => { @@ -32,7 +32,7 @@ export async function checksum(path: string, sha1hash: string | undefined): Prom const hash = await checksumPromise; - if (hash !== sha1hash) { + if (hash !== sha256hash) { throw new Error('Hash mismatch'); } } diff --git a/src/vs/base/test/node/crypto.test.ts b/src/vs/base/test/node/crypto.test.ts index 95a04d6a113..f61fe475b7a 100644 --- a/src/vs/base/test/node/crypto.test.ts +++ b/src/vs/base/test/node/crypto.test.ts @@ -27,6 +27,6 @@ flakySuite('Crypto', () => { const testFile = join(testDir, 'checksum.txt'); await Promises.writeFile(testFile, 'Hello World'); - await checksum(testFile, '0a4d55a8d778e5022fab701977c5d840bbc486d0'); + await checksum(testFile, 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e'); }); }); diff --git a/src/vs/platform/update/common/update.ts b/src/vs/platform/update/common/update.ts index 0f79e179b66..4cc8994bd01 100644 --- a/src/vs/platform/update/common/update.ts +++ b/src/vs/platform/update/common/update.ts @@ -10,7 +10,7 @@ export interface IUpdate { version: string; productVersion: string; url?: string; - hash?: string; + sha256hash?: string; } /** diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index a67e140d797..ff8bbb0e559 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -144,13 +144,11 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun return Promise.resolve(updatePackagePath); } - const url = update.url; - const hash = update.hash; const downloadPath = `${updatePackagePath}.tmp`; - return this.requestService.request({ url }, CancellationToken.None) + return this.requestService.request({ url: update.url }, CancellationToken.None) .then(context => this.fileService.writeFile(URI.file(downloadPath), context.stream)) - .then(hash ? () => checksum(downloadPath, update.hash) : () => undefined) + .then(update.sha256hash ? () => checksum(downloadPath, update.sha256hash) : () => undefined) .then(() => pfs.Promises.rename(downloadPath, updatePackagePath, false /* no retry */)) .then(() => updatePackagePath); }); From 91e69a338368a8dfd6270f7e7822833d9ae73737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 5 Jan 2024 11:24:24 +0100 Subject: [PATCH 0085/1897] codeql exemption (#201780) * codeql exemption * add missing compilation --- build/azure-pipelines/common/publish.js | 2 +- build/azure-pipelines/common/publish.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/common/publish.js b/build/azure-pipelines/common/publish.js index 5835dff9fbc..153343e1d5f 100644 --- a/build/azure-pipelines/common/publish.js +++ b/build/azure-pipelines/common/publish.js @@ -506,7 +506,7 @@ async function processArtifact(artifact, artifactFilePath) { const type = getRealType(unprocessedType); const size = fs.statSync(artifactFilePath).size; const stream = fs.createReadStream(artifactFilePath); - const [sha1hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); + const [sha1hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); // CodeQL [SM04514] Using SHA1 only for legacy reasons, we are actually only respecting SHA256 const [assetUrl, prssUrl] = await Promise.all([ uploadAssetLegacy(log, quality, commit, artifactFilePath), releaseAndProvision(log, e('RELEASE_TENANT_ID'), e('RELEASE_CLIENT_ID'), e('RELEASE_AUTH_CERT_SUBJECT_NAME'), e('RELEASE_REQUEST_SIGNING_CERT_SUBJECT_NAME'), e('PROVISION_TENANT_ID'), e('PROVISION_AAD_USERNAME'), e('PROVISION_AAD_PASSWORD'), commit, quality, artifactFilePath) diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index 08ef204fa37..5c4753728cc 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -673,7 +673,7 @@ async function processArtifact(artifact: Artifact, artifactFilePath: string): Pr const type = getRealType(unprocessedType); const size = fs.statSync(artifactFilePath).size; const stream = fs.createReadStream(artifactFilePath); - const [sha1hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); + const [sha1hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); // CodeQL [SM04514] Using SHA1 only for legacy reasons, we are actually only respecting SHA256 const [assetUrl, prssUrl] = await Promise.all([ uploadAssetLegacy(log, quality, commit, artifactFilePath), From 6f96811659efb2b014086b293531f9034390281e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 5 Jan 2024 11:45:57 +0100 Subject: [PATCH 0086/1897] stop uploading to legacy CDN (#201792) * stop uploading to legacy CDN * add missing compilation --- build/azure-pipelines/common/publish.js | 36 ++------------ build/azure-pipelines/common/publish.ts | 65 ++++++------------------- build/azure-pipelines/product-build.yml | 2 - 3 files changed, 18 insertions(+), 85 deletions(-) diff --git a/build/azure-pipelines/common/publish.js b/build/azure-pipelines/common/publish.js index 153343e1d5f..d035de8bb84 100644 --- a/build/azure-pipelines/common/publish.js +++ b/build/azure-pipelines/common/publish.js @@ -11,8 +11,6 @@ const promises_1 = require("node:stream/promises"); const yauzl = require("yauzl"); const crypto = require("crypto"); const retry_1 = require("./retry"); -const storage_blob_1 = require("@azure/storage-blob"); -const mime = require("mime"); const cosmos_1 = require("@azure/cosmos"); const identity_1 = require("@azure/identity"); const cp = require("child_process"); @@ -467,31 +465,6 @@ function getRealType(type) { return type; } } -async function uploadAssetLegacy(log, quality, commit, filePath) { - const fileName = path.basename(filePath); - const blobName = commit + '/' + fileName; - const credential = new identity_1.ClientSecretCredential(e('AZURE_TENANT_ID'), e('AZURE_CLIENT_ID'), e('AZURE_CLIENT_SECRET')); - const blobServiceClient = new storage_blob_1.BlobServiceClient(`https://vscode.blob.core.windows.net`, credential, { retryOptions: { retryPolicyType: storage_blob_1.StorageRetryPolicyType.FIXED, tryTimeoutInMs: 2 * 60 * 1000 } }); - const containerClient = blobServiceClient.getContainerClient(quality); - const blobClient = containerClient.getBlockBlobClient(blobName); - const blobOptions = { - blobHTTPHeaders: { - blobContentType: mime.lookup(filePath), - blobContentDisposition: `attachment; filename="${fileName}"`, - blobCacheControl: 'max-age=31536000, public' - } - }; - log(`Checking for blob in Azure...`); - if (await blobClient.exists()) { - log(`Blob ${quality}, ${blobName} already exists, not publishing again.`); - } - else { - log(`Uploading blobs to Azure storage...`); - await blobClient.uploadFile(filePath, blobOptions); - log('Blob successfully uploaded to Azure storage.'); - } - return `${e('AZURE_CDN_URL')}/${quality}/${blobName}`; -} async function processArtifact(artifact, artifactFilePath) { const log = (...args) => console.log(`[${artifact.name}]`, ...args); const match = /^vscode_(?[^_]+)_(?[^_]+)_(?[^_]+)_(?[^_]+)$/.exec(artifact.name); @@ -506,12 +479,9 @@ async function processArtifact(artifact, artifactFilePath) { const type = getRealType(unprocessedType); const size = fs.statSync(artifactFilePath).size; const stream = fs.createReadStream(artifactFilePath); - const [sha1hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); // CodeQL [SM04514] Using SHA1 only for legacy reasons, we are actually only respecting SHA256 - const [assetUrl, prssUrl] = await Promise.all([ - uploadAssetLegacy(log, quality, commit, artifactFilePath), - releaseAndProvision(log, e('RELEASE_TENANT_ID'), e('RELEASE_CLIENT_ID'), e('RELEASE_AUTH_CERT_SUBJECT_NAME'), e('RELEASE_REQUEST_SIGNING_CERT_SUBJECT_NAME'), e('PROVISION_TENANT_ID'), e('PROVISION_AAD_USERNAME'), e('PROVISION_AAD_PASSWORD'), commit, quality, artifactFilePath) - ]); - const asset = { platform, type, url: assetUrl, hash: sha1hash, mooncakeUrl: prssUrl, prssUrl, sha256hash, size, supportsFastUpdate: true }; + const [hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); // CodeQL [SM04514] Using SHA1 only for legacy reasons, we are actually only respecting SHA256 + const url = await releaseAndProvision(log, e('RELEASE_TENANT_ID'), e('RELEASE_CLIENT_ID'), e('RELEASE_AUTH_CERT_SUBJECT_NAME'), e('RELEASE_REQUEST_SIGNING_CERT_SUBJECT_NAME'), e('PROVISION_TENANT_ID'), e('PROVISION_AAD_USERNAME'), e('PROVISION_AAD_PASSWORD'), commit, quality, artifactFilePath); + const asset = { platform, type, url, hash, sha256hash, size, supportsFastUpdate: true }; log('Creating asset...', JSON.stringify(asset)); await (0, retry_1.retry)(async (attempt) => { log(`Creating asset in Cosmos DB (attempt ${attempt})...`); diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index 5c4753728cc..6b20492c87c 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -11,8 +11,6 @@ import { pipeline } from 'node:stream/promises'; import * as yauzl from 'yauzl'; import * as crypto from 'crypto'; import { retry } from './retry'; -import { BlobServiceClient, BlockBlobParallelUploadOptions, StorageRetryPolicyType } from '@azure/storage-blob'; -import * as mime from 'mime'; import { CosmosClient } from '@azure/cosmos'; import { ClientSecretCredential } from '@azure/identity'; import * as cp from 'child_process'; @@ -627,36 +625,6 @@ function getRealType(type: string) { } } -async function uploadAssetLegacy(log: (...args: any[]) => void, quality: string, commit: string, filePath: string): Promise { - const fileName = path.basename(filePath); - const blobName = commit + '/' + fileName; - - const credential = new ClientSecretCredential(e('AZURE_TENANT_ID'), e('AZURE_CLIENT_ID'), e('AZURE_CLIENT_SECRET')); - const blobServiceClient = new BlobServiceClient(`https://vscode.blob.core.windows.net`, credential, { retryOptions: { retryPolicyType: StorageRetryPolicyType.FIXED, tryTimeoutInMs: 2 * 60 * 1000 } }); - const containerClient = blobServiceClient.getContainerClient(quality); - const blobClient = containerClient.getBlockBlobClient(blobName); - - const blobOptions: BlockBlobParallelUploadOptions = { - blobHTTPHeaders: { - blobContentType: mime.lookup(filePath), - blobContentDisposition: `attachment; filename="${fileName}"`, - blobCacheControl: 'max-age=31536000, public' - } - }; - - log(`Checking for blob in Azure...`); - - if (await blobClient.exists()) { - log(`Blob ${quality}, ${blobName} already exists, not publishing again.`); - } else { - log(`Uploading blobs to Azure storage...`); - await blobClient.uploadFile(filePath, blobOptions); - log('Blob successfully uploaded to Azure storage.'); - } - - return `${e('AZURE_CDN_URL')}/${quality}/${blobName}`; -} - async function processArtifact(artifact: Artifact, artifactFilePath: string): Promise { const log = (...args: any[]) => console.log(`[${artifact.name}]`, ...args); const match = /^vscode_(?[^_]+)_(?[^_]+)_(?[^_]+)_(?[^_]+)$/.exec(artifact.name); @@ -673,26 +641,23 @@ async function processArtifact(artifact: Artifact, artifactFilePath: string): Pr const type = getRealType(unprocessedType); const size = fs.statSync(artifactFilePath).size; const stream = fs.createReadStream(artifactFilePath); - const [sha1hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); // CodeQL [SM04514] Using SHA1 only for legacy reasons, we are actually only respecting SHA256 + const [hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); // CodeQL [SM04514] Using SHA1 only for legacy reasons, we are actually only respecting SHA256 - const [assetUrl, prssUrl] = await Promise.all([ - uploadAssetLegacy(log, quality, commit, artifactFilePath), - releaseAndProvision( - log, - e('RELEASE_TENANT_ID'), - e('RELEASE_CLIENT_ID'), - e('RELEASE_AUTH_CERT_SUBJECT_NAME'), - e('RELEASE_REQUEST_SIGNING_CERT_SUBJECT_NAME'), - e('PROVISION_TENANT_ID'), - e('PROVISION_AAD_USERNAME'), - e('PROVISION_AAD_PASSWORD'), - commit, - quality, - artifactFilePath - ) - ]); + const url = await releaseAndProvision( + log, + e('RELEASE_TENANT_ID'), + e('RELEASE_CLIENT_ID'), + e('RELEASE_AUTH_CERT_SUBJECT_NAME'), + e('RELEASE_REQUEST_SIGNING_CERT_SUBJECT_NAME'), + e('PROVISION_TENANT_ID'), + e('PROVISION_AAD_USERNAME'), + e('PROVISION_AAD_PASSWORD'), + commit, + quality, + artifactFilePath + ); - const asset: Asset = { platform, type, url: assetUrl, hash: sha1hash, mooncakeUrl: prssUrl, prssUrl, sha256hash, size, supportsFastUpdate: true }; + const asset: Asset = { platform, type, url, hash, sha256hash, size, supportsFastUpdate: true }; log('Creating asset...', JSON.stringify(asset)); await retry(async (attempt) => { diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index a46a61be86e..54a37e9b1fa 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -128,8 +128,6 @@ variables: value: c24324f7-e65f-4c45-8702-ed2d4c35df99 - name: PRSS_PROVISION_TENANT_ID value: 72f988bf-86f1-41af-91ab-2d7cd011db47 - - name: AZURE_CDN_URL - value: https://az764295.vo.msecnd.net - name: AZURE_DOCUMENTDB_ENDPOINT value: https://vscode.documents.azure.com:443/ - name: VSCODE_MIXIN_REPO From 78fee308ca3d0e8a7ebb21ddc52b8a3e2b31e21f Mon Sep 17 00:00:00 2001 From: samhanic <55490861+samhanic@users.noreply.github.com> Date: Fri, 5 Jan 2024 12:28:17 +0100 Subject: [PATCH 0087/1897] review fix --- .../common/extensionManagementCLI.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts index 08dd0fb2761..cd67533d2c8 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts @@ -205,12 +205,13 @@ export class ExtensionManagementCLI { this.logger.info(localize('updateExtensionsNewVersionsAvailable', "Updating extensions: {0}", extensionsToUpdate.map(ext => ext.extension.identifier.id).join(', '))); const installationResult = await this.extensionManagementService.installGalleryExtensions(extensionsToUpdate); - const failed: string[] = installationResult.filter(ext => ext.error).map(ext => ext.identifier.id); - if (failed.length) { - throw new Error(localize('updateExtensionsFailed', "Failed updating extensions: {0}", failed.join(', '))); + for (const extensionResult of installationResult) { + if (extensionResult.error) { + this.logger.error(localize('errorUpdatingExtension', "Error while updating extension {0}: {1}", extensionResult.identifier.id, getErrorMessage(extensionResult.error))); + } else { + this.logger.info(localize('successUpdate', "Extension '{0}' v{1} was successfully updated.", extensionResult.identifier.id, extensionResult.local?.manifest.version)); + } } - - this.logger.info(localize('updateExtensionsDone', "Successfully updated {0} extensions", extensionsToUpdate.length)); } private async installVSIX(vsix: URI, installOptions: InstallOptions, force: boolean, installedExtensions: ILocalExtension[]): Promise { From 9688d25cecff6023b75d2b875f8143fcea4deaa5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 5 Jan 2024 17:23:37 +0530 Subject: [PATCH 0088/1897] cleanup (#201879) --- .../common/extensionManagementCLI.ts | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts index 1515c45cca2..f78a198efe1 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts @@ -11,7 +11,7 @@ import { gt } from 'vs/base/common/semver/semver'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { EXTENSION_IDENTIFIER_REGEX, IExtensionGalleryService, IExtensionInfo, IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { areSameExtensions, getGalleryExtensionId, getIdAndVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { areSameExtensions, getExtensionId, getGalleryExtensionId, getIdAndVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, EXTENSION_CATEGORIES, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { ILogger } from 'vs/platform/log/common/log'; @@ -19,15 +19,6 @@ import { ILogger } from 'vs/platform/log/common/log'; const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id); const useId = localize('useId', "Make sure you use the full extension ID, including the publisher, e.g.: {0}", 'ms-dotnettools.csharp'); - -function getId(manifest: IExtensionManifest, withVersion?: boolean): string { - if (withVersion) { - return `${manifest.publisher}.${manifest.name}@${manifest.version}`; - } else { - return `${manifest.publisher}.${manifest.name}`; - } -} - type InstallVSIXInfo = { vsix: URI; installOptions: InstallOptions }; type InstallExtensionInfo = { id: string; version?: string; installOptions: InstallOptions }; @@ -74,7 +65,7 @@ export class ExtensionManagementCLI { for (const extension of extensions) { if (lastId !== extension.identifier.id) { lastId = extension.identifier.id; - this.logger.info(getId(extension.manifest, showVersions)); + this.logger.info(showVersions ? `${lastId}@${extension.manifest.version}` : lastId); } } } @@ -269,17 +260,17 @@ export class ExtensionManagementCLI { } public async uninstallExtensions(extensions: (string | URI)[], force: boolean, profileLocation?: URI): Promise { - const getExtensionId = async (extensionDescription: string | URI): Promise => { + const getId = async (extensionDescription: string | URI): Promise => { if (extensionDescription instanceof URI) { const manifest = await this.extensionManagementService.getManifest(extensionDescription); - return getId(manifest); + return getExtensionId(manifest.publisher, manifest.name); } return extensionDescription; }; const uninstalledExtensions: ILocalExtension[] = []; for (const extension of extensions) { - const id = await getExtensionId(extension); + const id = await getId(extension); const installed = await this.extensionManagementService.getInstalled(undefined, profileLocation); const extensionsToUninstall = installed.filter(e => areSameExtensions(e.identifier, { id })); if (!extensionsToUninstall.length) { From 9a621245247fb2abd9b693bb6009b9e26691f062 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 5 Jan 2024 15:05:57 +0100 Subject: [PATCH 0089/1897] SCM - only save tree state if tree exists (#201883) --- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 90810f73477..75d5748e909 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -3085,7 +3085,9 @@ export class SCMViewPane extends ViewPane { } private storeTreeViewState() { - this.storageService.store('scm.viewState2', JSON.stringify(this.tree.getViewState()), StorageScope.WORKSPACE, StorageTarget.MACHINE); + if (this.tree) { + this.storageService.store('scm.viewState2', JSON.stringify(this.tree.getViewState()), StorageScope.WORKSPACE, StorageTarget.MACHINE); + } } private updateChildren(element?: ISCMRepository) { From 1cfc62d46e6b9dd217cd485d7d4591136f421d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 5 Jan 2024 16:45:11 +0100 Subject: [PATCH 0090/1897] fixes #201890 (#201891) --- src/vs/workbench/contrib/files/browser/views/explorerView.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index f8d2a9ddcfa..03dca3b5a66 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -693,9 +693,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { } const toRefresh = item || this.tree.getInput(); - return this.tree.updateChildren(toRefresh, recursive, !!item, { - diffIdentityProvider: identityProvider - }); + return this.tree.updateChildren(toRefresh, recursive, !!item); } override getOptimalWidth(): number { From fe249aa1349cda9d39de9fdf41cef2e5febc1d89 Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Fri, 5 Jan 2024 17:01:12 +0100 Subject: [PATCH 0091/1897] Remove bigNumbersDelta (#194712) If I understand correctly, this is just a hack to support IE. However, IE is no longer supported, so this is no longer necessary. --- src/vs/editor/browser/view/renderingContext.ts | 2 -- .../viewParts/contentWidgets/contentWidgets.ts | 2 +- src/vs/editor/browser/viewParts/lines/viewLines.ts | 2 +- src/vs/editor/browser/viewParts/margin/margin.ts | 2 +- .../browser/viewParts/viewCursors/viewCursor.ts | 4 ++-- .../editor/browser/viewParts/viewZones/viewZones.ts | 2 +- src/vs/editor/common/viewLayout/linesLayout.ts | 12 ------------ .../common/viewLayout/viewLinesViewportData.ts | 6 ------ src/vs/editor/common/viewModel.ts | 4 ---- 9 files changed, 6 insertions(+), 30 deletions(-) diff --git a/src/vs/editor/browser/view/renderingContext.ts b/src/vs/editor/browser/view/renderingContext.ts index 3396fb74b78..86c489e60cb 100644 --- a/src/vs/editor/browser/view/renderingContext.ts +++ b/src/vs/editor/browser/view/renderingContext.ts @@ -22,7 +22,6 @@ export abstract class RestrictedRenderingContext { public readonly scrollHeight: number; public readonly visibleRange: Range; - public readonly bigNumbersDelta: number; public readonly scrollTop: number; public readonly scrollLeft: number; @@ -40,7 +39,6 @@ export abstract class RestrictedRenderingContext { this.scrollHeight = this._viewLayout.getScrollHeight(); this.visibleRange = this.viewportData.visibleRange; - this.bigNumbersDelta = this.viewportData.bigNumbersDelta; const vInfo = this._viewLayout.getCurrentViewport(); this.scrollTop = vInfo.top; diff --git a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts index 3112c19c324..6190b81be51 100644 --- a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts +++ b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts @@ -537,7 +537,7 @@ class Widget { this.domNode.setTop(this._renderData.coordinate.top); this.domNode.setLeft(this._renderData.coordinate.left); } else { - this.domNode.setTop(this._renderData.coordinate.top + ctx.scrollTop - ctx.bigNumbersDelta); + this.domNode.setTop(this._renderData.coordinate.top + ctx.scrollTop); this.domNode.setLeft(this._renderData.coordinate.left); } diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index 220ef987255..e85a47ebf2e 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -662,7 +662,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, // (3) handle scrolling this._linesContent.setLayerHinting(this._canUseLayerHinting); this._linesContent.setContain('strict'); - const adjustedScrollTop = this._context.viewLayout.getCurrentScrollTop() - viewportData.bigNumbersDelta; + const adjustedScrollTop = this._context.viewLayout.getCurrentScrollTop(); this._linesContent.setTop(-adjustedScrollTop); this._linesContent.setLeft(-this._context.viewLayout.getCurrentScrollLeft()); } diff --git a/src/vs/editor/browser/viewParts/margin/margin.ts b/src/vs/editor/browser/viewParts/margin/margin.ts index 5748c0564dd..1de685e5ad8 100644 --- a/src/vs/editor/browser/viewParts/margin/margin.ts +++ b/src/vs/editor/browser/viewParts/margin/margin.ts @@ -80,7 +80,7 @@ export class Margin extends ViewPart { public render(ctx: RestrictedRenderingContext): void { this._domNode.setLayerHinting(this._canUseLayerHinting); this._domNode.setContain('strict'); - const adjustedScrollTop = ctx.scrollTop - ctx.bigNumbersDelta; + const adjustedScrollTop = ctx.scrollTop; this._domNode.setTop(-adjustedScrollTop); const height = Math.min(ctx.scrollHeight, 1000000); diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts index 36506e9fed0..ed9c39bf373 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts @@ -166,7 +166,7 @@ export class ViewCursor { left -= paddingLeft; } - const top = ctx.getVerticalOffsetForLineNumber(position.lineNumber) - ctx.bigNumbersDelta; + const top = ctx.getVerticalOffsetForLineNumber(position.lineNumber); return new ViewCursorRenderData(top, left, paddingLeft, width, this._lineHeight, textContent, textContentClassName); } @@ -196,7 +196,7 @@ export class ViewCursor { textContentClassName = this._getTokenClassName(position); } - let top = ctx.getVerticalOffsetForLineNumber(position.lineNumber) - ctx.bigNumbersDelta; + let top = ctx.getVerticalOffsetForLineNumber(position.lineNumber); let height = this._lineHeight; // Underline might interfere with clicking diff --git a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts index 37914a70335..d328eab467c 100644 --- a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts +++ b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts @@ -374,7 +374,7 @@ export class ViewZones extends ViewPart { let newHeight = 0; let newDisplay = 'none'; if (visibleZones.hasOwnProperty(id)) { - newTop = visibleZones[id].verticalOffset - ctx.bigNumbersDelta; + newTop = visibleZones[id].verticalOffset; newHeight = visibleZones[id].height; newDisplay = 'block'; // zone is visible diff --git a/src/vs/editor/common/viewLayout/linesLayout.ts b/src/vs/editor/common/viewLayout/linesLayout.ts index 7bb55aeef6e..d80f1495d97 100644 --- a/src/vs/editor/common/viewLayout/linesLayout.ts +++ b/src/vs/editor/common/viewLayout/linesLayout.ts @@ -644,17 +644,6 @@ export class LinesLayout { let currentVerticalOffset = startLineNumberVerticalOffset; let currentLineRelativeOffset = currentVerticalOffset; - // IE (all versions) cannot handle units above about 1,533,908 px, so every 500k pixels bring numbers down - const STEP_SIZE = 500000; - let bigNumbersDelta = 0; - if (startLineNumberVerticalOffset >= STEP_SIZE) { - // Compute a delta that guarantees that lines are positioned at `lineHeight` increments - bigNumbersDelta = Math.floor(startLineNumberVerticalOffset / STEP_SIZE) * STEP_SIZE; - bigNumbersDelta = Math.floor(bigNumbersDelta / lineHeight) * lineHeight; - - currentLineRelativeOffset -= bigNumbersDelta; - } - const linesOffsets: number[] = []; const verticalCenter = verticalOffset1 + (verticalOffset2 - verticalOffset1) / 2; @@ -721,7 +710,6 @@ export class LinesLayout { } return { - bigNumbersDelta: bigNumbersDelta, startLineNumber: startLineNumber, endLineNumber: endLineNumber, relativeVerticalOffset: linesOffsets, diff --git a/src/vs/editor/common/viewLayout/viewLinesViewportData.ts b/src/vs/editor/common/viewLayout/viewLinesViewportData.ts index 8ddcfddb99d..91c46eeedd6 100644 --- a/src/vs/editor/common/viewLayout/viewLinesViewportData.ts +++ b/src/vs/editor/common/viewLayout/viewLinesViewportData.ts @@ -34,11 +34,6 @@ export class ViewportData { */ public readonly visibleRange: Range; - /** - * Value to be substracted from `scrollTop` (in order to vertical offset numbers < 1MM) - */ - public readonly bigNumbersDelta: number; - /** * Positioning information about gaps whitespace. */ @@ -56,7 +51,6 @@ export class ViewportData { this.startLineNumber = partialData.startLineNumber | 0; this.endLineNumber = partialData.endLineNumber | 0; this.relativeVerticalOffset = partialData.relativeVerticalOffset; - this.bigNumbersDelta = partialData.bigNumbersDelta | 0; this.whitespaceViewportData = whitespaceViewportData; this._model = model; diff --git a/src/vs/editor/common/viewModel.ts b/src/vs/editor/common/viewModel.ts index 335816d0dcc..3c3f6af54ad 100644 --- a/src/vs/editor/common/viewModel.ts +++ b/src/vs/editor/common/viewModel.ts @@ -151,10 +151,6 @@ export interface IWhitespaceChangeAccessor { } export interface IPartialViewLinesViewportData { - /** - * Value to be substracted from `scrollTop` (in order to vertical offset numbers < 1MM) - */ - readonly bigNumbersDelta: number; /** * The first (partially) visible line number. */ From 4abe42fa454c4d0937cc4c4aa219cee8828a7d86 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 5 Jan 2024 09:50:00 -0800 Subject: [PATCH 0092/1897] bump distro for proposed api (#201895) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 57a6dd672af..fec8a4166e0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.86.0", - "distro": "31d3f5c070f8d16d2907b37f44f2b7e3f595f28c", + "distro": "9d1271fd1c2cf545a209fb8aaf409f75da726c4c", "author": { "name": "Microsoft Corporation" }, From dd5be7a8e524cdc950c9dd023c5692824516f113 Mon Sep 17 00:00:00 2001 From: effectivecui Date: Sat, 6 Jan 2024 02:03:38 +0800 Subject: [PATCH 0093/1897] avoid to enter the infinite loop when item.children is empty. (#201701) avoid while loop when item.children is empty. Co-authored-by: Logan Ramos --- .../contrib/files/browser/views/explorerView.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 03dca3b5a66..d4d37818cd7 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -805,13 +805,16 @@ export class ExplorerView extends ViewPane implements IExplorerView { } catch (e) { return this.selectResource(resource, reveal, retry + 1); } - - for (const child of item.children.values()) { - if (this.uriIdentityService.extUri.isEqualOrParent(resource, child.resource)) { - item = child; - break; - } + if (!item.children.size) { item = null; + } else { + for (const child of item.children.values()) { + if (this.uriIdentityService.extUri.isEqualOrParent(resource, child.resource)) { + item = child; + break; + } + item = null; + } } } From 0d735d01abd17a5edc7bed45fdd7f15d4902954a Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Fri, 5 Jan 2024 10:16:49 -0800 Subject: [PATCH 0094/1897] Change Welcome page contribution activation to LifecyclePhase.Starting (#201844) Change up starup activation for welcome pages --- .../browser/gettingStarted.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts index 3d0ae3e607d..6e052c1019c 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts @@ -327,4 +327,4 @@ configurationRegistry.registerConfiguration({ }); Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(StartupPageContribution, LifecyclePhase.Restored); + .registerWorkbenchContribution(StartupPageContribution, LifecyclePhase.Starting); From c37b435618f84a885e531bbb94593c0cd8e9f6f4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 5 Jan 2024 12:19:56 -0600 Subject: [PATCH 0095/1897] move things around --- .../actions/voice.contribution.ts | 10 +++ .../electron-sandbox/actions/voiceActions.ts | 79 ++++++++----------- .../electron-sandbox/chat.contribution.ts | 3 - 3 files changed, 43 insertions(+), 49 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/electron-sandbox/actions/voice.contribution.ts diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voice.contribution.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voice.contribution.ts new file mode 100644 index 00000000000..9cd18e8c82f --- /dev/null +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voice.contribution.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { StartTerminalSpeechToTextAction, StopTerminalSpeechToTextAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions'; + +registerAction2(StartTerminalSpeechToTextAction); +registerAction2(StopTerminalSpeechToTextAction); diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts index 2ffc602203a..a32b3f54eab 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts @@ -5,7 +5,6 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { localize } from 'vs/nls'; import { Action2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -13,30 +12,6 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; - - -export class StartEditorSpeechToTextAction extends Action2 { - - static readonly ID = 'workbench.action.startEditorSpeechToText'; - - constructor() { - super({ - id: 'workbench.action.startEditorSpeechToText', - title: { - value: localize('workbench.action.startEditorSpeechToText', "Start Editor Speech To Text"), - original: 'Start Editor Speech To Text' - }, - precondition: ContextKeyExpr.and(HasSpeechProvider, EditorContextKeys.focus), - f1: true - }); - } - - async run(accessor: ServicesAccessor): Promise { - const instantiationService = accessor.get(IInstantiationService); - VoiceSession.getInstance(instantiationService).start('editor'); - } -} export class StartTerminalSpeechToTextAction extends Action2 { @@ -57,7 +32,30 @@ export class StartTerminalSpeechToTextAction extends Action2 { async run(accessor: ServicesAccessor): Promise { const instantiationService = accessor.get(IInstantiationService); - VoiceSession.getInstance(instantiationService).start('terminal'); + VoiceSession.getInstance(instantiationService).start(); + } +} + + +export class StopTerminalSpeechToTextAction extends Action2 { + + static readonly ID = 'workbench.action.stopTerminalSpeechToText'; + + constructor() { + super({ + id: 'workbench.action.stopTerminalSpeechToText', + title: { + value: localize('workbench.action.stopTerminalSpeechToText', "Stop Terminal Speech To Text"), + original: 'Stop Terminal Speech To Text' + }, + precondition: ContextKeyExpr.and(HasSpeechProvider, TerminalContextKeys.focus), + f1: true + }); + } + + async run(accessor: ServicesAccessor): Promise { + const instantiationService = accessor.get(IInstantiationService); + VoiceSession.getInstance(instantiationService).stop(); } } @@ -74,42 +72,31 @@ class VoiceSession extends Disposable { private _disposables = new DisposableStore(); constructor( @ISpeechService private readonly _speechService: ISpeechService, - @IEditorService readonly _editorService: IEditorService, @ITerminalService readonly _terminalService: ITerminalService ) { super(); } - start(type: 'terminal' | 'editor'): void { + start(): void { this.stop(); - if (this._cancellationTokenSource) { - this._cancellationTokenSource.cancel(); - } else { - this._cancellationTokenSource = new CancellationTokenSource(); - } + this._cancellationTokenSource = new CancellationTokenSource(); const session = this._disposables.add(this._speechService.createSpeechToTextSession(this._cancellationTokenSource!.token)); this._disposables.add(session.onDidChange((e) => { + if (this._cancellationTokenSource?.token.isCancellationRequested) { + return; + } switch (e.status) { case SpeechToTextStatus.Started: - console.log('started', e.text); break; case SpeechToTextStatus.Recognizing: - console.log('recognizing', e.text); - if (type === 'terminal') { - this._terminalService.activeInstance?.sendText(e.text!, true); - } else { - this._editorService.activeTextEditorControl?.trigger?.('type', 'type', { text: e.text }); - } + // TODO: start audio cue, show in status bar break; case SpeechToTextStatus.Recognized: - console.log('recognized', e.text); - if (type === 'terminal') { - this._terminalService.activeInstance?.sendText(e.text!, true); - } else { - this._editorService.activeTextEditorControl?.trigger?.('type', 'type', { text: e.text }); + if (e.text) { + this._terminalService.activeInstance?.sendText(e.text, false); } break; case SpeechToTextStatus.Stopped: - console.log('stopped', e.text); + // TODO: stop audio cue, hide in status bar this.stop(); break; } diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts index fb529a7342c..9ba5430737e 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts @@ -5,7 +5,6 @@ import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; import { registerAction2 } from 'vs/platform/actions/common/actions'; -import { StartEditorSpeechToTextAction, StartTerminalSpeechToTextAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions'; registerAction2(StartVoiceChatAction); @@ -21,5 +20,3 @@ registerAction2(StopListeningInChatEditorAction); registerAction2(StopListeningInQuickChatAction); registerAction2(StopListeningInInlineChatAction); -registerAction2(StartTerminalSpeechToTextAction); -registerAction2(StartEditorSpeechToTextAction); From 965919002645fb9d79844e4cbd04827602342d93 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Fri, 5 Jan 2024 12:36:21 -0600 Subject: [PATCH 0096/1897] quick text search -> quick search (#201900) quich text search -> quick search --- .../contrib/search/browser/searchActionsTextQuickAccess.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts b/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts index 977c05f532e..a5e68918206 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts @@ -22,8 +22,8 @@ registerAction2(class TextSearchQuickAccessAction extends Action2 { super({ id: Constants.QuickTextSearchActionId, title: { - value: nls.localize('quickTextSearch', "Quick Text Search (Experimental)"), - original: 'Quick Text Search (Experimental)' + value: nls.localize('quickTextSearch', "Quick Search (Experimental)"), + original: 'Quick Search (Experimental)' }, category, f1: true From 13fcd1a8c3591fcb167f502ace45771862bf5015 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 5 Jan 2024 12:41:54 -0600 Subject: [PATCH 0097/1897] move around --- .../electron-sandbox/actions/voice.contribution.ts | 10 ---------- .../contrib/chat/electron-sandbox/chat.contribution.ts | 3 +++ 2 files changed, 3 insertions(+), 10 deletions(-) delete mode 100644 src/vs/workbench/contrib/chat/electron-sandbox/actions/voice.contribution.ts diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voice.contribution.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voice.contribution.ts deleted file mode 100644 index 9cd18e8c82f..00000000000 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voice.contribution.ts +++ /dev/null @@ -1,10 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { registerAction2 } from 'vs/platform/actions/common/actions'; -import { StartTerminalSpeechToTextAction, StopTerminalSpeechToTextAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions'; - -registerAction2(StartTerminalSpeechToTextAction); -registerAction2(StopTerminalSpeechToTextAction); diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts index 9ba5430737e..68c167ab1c7 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts @@ -5,6 +5,7 @@ import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { StartTerminalSpeechToTextAction, StopTerminalSpeechToTextAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions'; registerAction2(StartVoiceChatAction); @@ -20,3 +21,5 @@ registerAction2(StopListeningInChatEditorAction); registerAction2(StopListeningInQuickChatAction); registerAction2(StopListeningInInlineChatAction); +registerAction2(StartTerminalSpeechToTextAction); +registerAction2(StopTerminalSpeechToTextAction); From 7156038cfc6d17eb2b4b4ab41b5000faa7869d90 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 5 Jan 2024 12:46:28 -0600 Subject: [PATCH 0098/1897] rename things --- .../actions/{voiceActions.ts => voiceTerminalActions.ts} | 1 - .../{chat.contribution.ts => voice.contribution.ts} | 2 +- src/vs/workbench/workbench.desktop.main.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) rename src/vs/workbench/contrib/chat/electron-sandbox/actions/{voiceActions.ts => voiceTerminalActions.ts} (99%) rename src/vs/workbench/contrib/chat/electron-sandbox/{chat.contribution.ts => voice.contribution.ts} (98%) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceTerminalActions.ts similarity index 99% rename from src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts rename to src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceTerminalActions.ts index a32b3f54eab..c91b3d2dd10 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceTerminalActions.ts @@ -13,7 +13,6 @@ import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbe import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; - export class StartTerminalSpeechToTextAction extends Action2 { static readonly ID = 'workbench.action.startTerminalSpeechToText'; diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-sandbox/voice.contribution.ts similarity index 98% rename from src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts rename to src/vs/workbench/contrib/chat/electron-sandbox/voice.contribution.ts index 68c167ab1c7..2eb682451ef 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/voice.contribution.ts @@ -5,7 +5,7 @@ import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; import { registerAction2 } from 'vs/platform/actions/common/actions'; -import { StartTerminalSpeechToTextAction, StopTerminalSpeechToTextAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceActions'; +import { StartTerminalSpeechToTextAction, StopTerminalSpeechToTextAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceTerminalActions'; registerAction2(StartVoiceChatAction); diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 7f311e34818..ad942cb8973 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -173,7 +173,7 @@ import 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contributio import 'vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution'; // Chat -import 'vs/workbench/contrib/chat/electron-sandbox/chat.contribution'; +import 'vs/workbench/contrib/chat/electron-sandbox/voice.contribution'; //#endregion From ccb11dc2b902773681be3f9fa20d4478c41c6798 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 5 Jan 2024 11:15:36 -0800 Subject: [PATCH 0099/1897] Re #201832. Fetch correct codewindow. (#201904) * Re #201832. Fetch correct codewindow. * remove console --- .../notebook/browser/notebookEditor.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index fdc168b7d89..d041b44578a 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -36,7 +36,7 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { NOTEBOOK_EDITOR_ID, NotebookWorkingCopyTypeIdentifier } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { NotebookPerfMarks } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; -import { GroupsOrder, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { GroupsOrder, IAuxiliaryEditorPart, IEditorGroup, IEditorGroupsService, IEditorPart } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; @@ -48,6 +48,7 @@ import { streamToBuffer } from 'vs/base/common/buffer'; import { ILogService } from 'vs/platform/log/common/log'; import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState'; @@ -92,7 +93,7 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { @IWorkingCopyBackupService private readonly _workingCopyBackupService: IWorkingCopyBackupService, @ILogService private readonly logService: ILogService, @INotebookEditorWorkerService private readonly _notebookEditorWorkerService: INotebookEditorWorkerService, - @IPreferencesService private readonly _preferencesService: IPreferencesService, + @IPreferencesService private readonly _preferencesService: IPreferencesService ) { super(NotebookEditor.ID, telemetryService, themeService, storageService); this._editorMemento = this.getEditorMemento(_editorGroupService, configurationService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY); @@ -206,7 +207,20 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { // we need to hide it before getting a new widget this._widget.value?.onWillHide(); - this._widget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, group, input, undefined, this._pagePosition?.dimension, this._rootElement ? DOM.getWindow(this._rootElement) : mainWindow); + let codeWindow = this._rootElement ? DOM.getWindow(this._rootElement) : undefined; + if (this.group) { + // get matching part + // TODO this._editorGroupService.getPart(this.group.id) is always returning main part on window reload + const groupView = (this.group as IEditorGroupView).groupsView as unknown as (IEditorPart | unknown); + const currentPart = this._editorGroupService.parts.find(part => part === groupView); + + // get window id + if (currentPart && (currentPart as IAuxiliaryEditorPart).windowId) { + codeWindow = DOM.getWindowById((currentPart as IAuxiliaryEditorPart).windowId)?.window; + } + } + + this._widget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, group, input, undefined, this._pagePosition?.dimension, codeWindow ?? mainWindow); if (this._rootElement && this._widget.value!.getDomNode()) { this._rootElement.setAttribute('aria-flowto', this._widget.value!.getDomNode().id || ''); From b33222d206ef6b864ffe8ca65055b26cc1fe4874 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 5 Jan 2024 11:42:36 -0800 Subject: [PATCH 0100/1897] Allow Embedders to contribute auth providers (#201897) * Allow Embedders to contribute auth providers * update integration tests --- .../api/browser/mainThreadAuthentication.ts | 12 ++++------ .../workbench/api/common/extHost.protocol.ts | 3 +-- .../api/common/extHostAuthentication.ts | 22 ------------------- .../extHostAuthentication.integrationTest.ts | 4 +++- src/vs/workbench/browser/web.api.ts | 10 +++++++++ .../browser/authenticationService.ts | 2 ++ 6 files changed, 20 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 43aaf2c863c..9bc8b7b1685 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -70,12 +70,6 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu this._register(this.authenticationService.onDidChangeSessions(e => { this._proxy.$onDidChangeAuthenticationSessions(e.providerId, e.label); })); - - this._proxy.$setProviders(this.authenticationService.declaredProviders); - - this._register(this.authenticationService.onDidChangeDeclaredProviders(e => { - this._proxy.$setProviders(e); - })); } async $registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean): Promise { @@ -90,8 +84,10 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu this.authenticationService.unregisterAuthenticationProvider(id); } - $ensureProvider(id: string): Promise { - return this.extensionService.activateByEvent(getAuthenticationProviderActivationEvent(id), ActivationKind.Immediate); + async $ensureProvider(id: string): Promise { + if (!this.authenticationService.isAuthenticationProviderRegistered(id)) { + return await this.extensionService.activateByEvent(getAuthenticationProviderActivationEvent(id), ActivationKind.Immediate); + } } $sendDidChangeSessions(providerId: string, event: AuthenticationSessionsChangeEvent): void { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 70f3fc2abac..69b3e3d0e32 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -68,7 +68,7 @@ import { CoverageDetails, ExtensionRunTestsRequest, ICallProfileRunHandler, IFil import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; import { TypeHierarchyItem } from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; import { RelatedInformationResult, RelatedInformationType } from 'vs/workbench/services/aiRelatedInformation/common/aiRelatedInformation'; -import { AuthenticationProviderInformation, AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationCreateSessionOptions } from 'vs/workbench/services/authentication/common/authentication'; +import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationCreateSessionOptions } from 'vs/workbench/services/authentication/common/authentication'; import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { IExtensionDescriptionDelta, IStaticWorkspaceData } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { IResolveAuthorityResult } from 'vs/workbench/services/extensions/common/extensionHostProxy'; @@ -1752,7 +1752,6 @@ export interface ExtHostAuthenticationShape { $createSession(id: string, scopes: string[], options: IAuthenticationCreateSessionOptions): Promise; $removeSession(id: string, sessionId: string): Promise; $onDidChangeAuthenticationSessions(id: string, label: string): Promise; - $setProviders(providers: AuthenticationProviderInformation[]): Promise; } export interface ExtHostAiRelatedInformationShape { diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index 3c49ad4a7b4..ca07cbfef64 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -19,8 +19,6 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { private _proxy: MainThreadAuthenticationShape; private _authenticationProviders: Map = new Map(); - private _providers: vscode.AuthenticationProviderInformation[] = []; - private _onDidChangeSessions = new Emitter(); readonly onDidChangeSessions: Event = this._onDidChangeSessions.event; @@ -31,11 +29,6 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication); } - $setProviders(providers: vscode.AuthenticationProviderInformation[]): Promise { - this._providers = providers; - return Promise.resolve(); - } - async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: readonly string[], options: vscode.AuthenticationGetSessionOptions & ({ createIfNone: true } | { forceNewSession: true } | { forceNewSession: vscode.AuthenticationForceNewSessionOptions })): Promise; async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: readonly string[], options: vscode.AuthenticationGetSessionOptions & { forceNewSession: true }): Promise; async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: readonly string[], options: vscode.AuthenticationGetSessionOptions & { forceNewSession: vscode.AuthenticationForceNewSessionOptions }): Promise; @@ -75,27 +68,12 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { } this._authenticationProviders.set(id, { label, provider, options: options ?? { supportsMultipleAccounts: false } }); - - if (!this._providers.find(p => p.id === id)) { - this._providers.push({ - id: id, - label: label - }); - } - const listener = provider.onDidChangeSessions(e => this._proxy.$sendDidChangeSessions(id, e)); - this._proxy.$registerAuthenticationProvider(id, label, options?.supportsMultipleAccounts ?? false); return new Disposable(() => { listener.dispose(); this._authenticationProviders.delete(id); - - const i = this._providers.findIndex(p => p.id === id); - if (i > -1) { - this._providers.splice(i); - } - this._proxy.$unregisterAuthenticationProvider(id); }); } diff --git a/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts b/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts index 87266ef8b62..25455816095 100644 --- a/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts +++ b/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts @@ -23,9 +23,10 @@ import { IAuthenticationService } from 'vs/workbench/services/authentication/com import { IExtensionService, nullExtensionDescription as extensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { TestRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; -import { TestQuickInputService, TestRemoteAgentService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEnvironmentService, TestQuickInputService, TestRemoteAgentService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestActivityService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; import type { AuthenticationProvider, AuthenticationSession } from 'vscode'; +import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; class AuthQuickPick { private listener: ((e: IQuickPickDidAcceptEvent) => any) | undefined; @@ -109,6 +110,7 @@ suite('ExtHostAuthentication', () => { instantiationService.stub(IRemoteAgentService, new TestRemoteAgentService()); instantiationService.stub(INotificationService, new TestNotificationService()); instantiationService.stub(ITelemetryService, NullTelemetryService); + instantiationService.stub(IBrowserWorkbenchEnvironmentService, TestEnvironmentService); const rpcProtocol = new TestRPCProtocol(); instantiationService.stub(IAuthenticationService, instantiationService.createInstance(AuthenticationService)); diff --git a/src/vs/workbench/browser/web.api.ts b/src/vs/workbench/browser/web.api.ts index 826919fc5ee..50d76a2213c 100644 --- a/src/vs/workbench/browser/web.api.ts +++ b/src/vs/workbench/browser/web.api.ts @@ -18,6 +18,7 @@ import type { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import type { IFolderToOpen, IWorkspaceToOpen } from 'vs/platform/window/common/window'; import type { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService'; import type { IEmbedderTerminalOptions } from 'vs/workbench/services/terminal/common/embedderTerminalService'; +import type { IAuthenticationProvider } from 'vs/workbench/services/authentication/common/authentication'; /** * The `IWorkbench` interface is the API facade for web embedders @@ -354,6 +355,15 @@ export interface IWorkbenchConstructionOptions { //#endregion + //#region Authentication Providers + + /** + * Optional authentication provider contributions. These take precedence over + * any authentication providers contributed via extensions. + */ + readonly authenticationProviders?: readonly IAuthenticationProvider[]; + + //#endregion //#region Development options diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 6e296726ec2..ab9c5697cfb 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -210,9 +210,11 @@ export class AuthenticationService extends Disposable implements IAuthentication @IDialogService private readonly dialogService: IDialogService, @IQuickInputService private readonly quickInputService: IQuickInputService, @IProductService private readonly productService: IProductService, + @IBrowserWorkbenchEnvironmentService environmentService: IBrowserWorkbenchEnvironmentService, ) { super(); + environmentService.options?.authenticationProviders?.forEach(provider => this.registerAuthenticationProvider(provider.id, provider)); authenticationExtPoint.setHandler((extensions, { added, removed }) => { added.forEach(point => { for (const provider of point.value) { From b2809a8d3e82be2378dbba7e64b7ec3eb96ccaa0 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Fri, 5 Jan 2024 12:20:16 -0800 Subject: [PATCH 0101/1897] allow issue reporter command to activate API (#201905) * added groundwork * removed extra button stuff * removed commented out code --- .../issue/issueReporterService.ts | 127 ++++++++++++------ src/vs/platform/issue/common/issue.ts | 2 + .../api/browser/mainThreadIssueReporter.ts | 11 +- .../services/issue/browser/issueService.ts | 2 +- .../issue/electron-sandbox/issueService.ts | 1 + 5 files changed, 93 insertions(+), 50 deletions(-) diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index 417b40f216f..896747af279 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -71,7 +71,6 @@ export class IssueReporter extends Disposable { }); //TODO: Handle case where extension is not activated - const issueReporterElement = this.getElementById('issue-reporter'); if (issueReporterElement) { this.previewButton = new Button(issueReporterElement, unthemedButtonStyles); @@ -133,6 +132,11 @@ export class IssueReporter extends Disposable { this.updateExperimentsInfo(configuration.data.experiments); this.updateRestrictedMode(configuration.data.restrictedMode); this.updateUnsupportedMode(configuration.data.isUnsupported); + + // Handle case where extension is pre-selected through the command + if (configuration.data.command && targetExtension) { + this.updateExtensionStatus(targetExtension); + } } render(): void { @@ -772,7 +776,6 @@ export class IssueReporter extends Disposable { hide(workspaceBlock); hide(extensionsBlock); hide(experimentsBlock); - hide(problemSource); hide(extensionSelector); hide(extensionDataTextArea); hide(extensionDataBlock); @@ -785,7 +788,7 @@ export class IssueReporter extends Disposable { show(extensionSelector); } - if (fileOnExtension && selectedExtension?.hasIssueUriRequestHandler) { + if (fileOnExtension && selectedExtension?.hasIssueUriRequestHandler && !selectedExtension.hasIssueDataProviders) { hide(titleTextArea); hide(descriptionTextArea); reset(descriptionTitle, localize('handlesIssuesElsewhere', "This extension handles issues outside of VS Code")); @@ -890,8 +893,10 @@ export class IssueReporter extends Disposable { } private async createIssue(): Promise { + const hasUri = this.issueReporterModel.getData().selectedExtension?.hasIssueUriRequestHandler; + const hasData = this.issueReporterModel.getData().selectedExtension?.hasIssueDataProviders; // Short circuit if the extension provides a custom issue handler - if (this.issueReporterModel.getData().selectedExtension?.hasIssueUriRequestHandler) { + if (hasUri && !hasData) { const url = this.getExtensionBugsUrl(); if (url) { this.hasBeenSubmitted = true; @@ -934,7 +939,10 @@ export class IssueReporter extends Disposable { const issueTitle = (this.getElementById('issue-title')).value; const issueBody = this.issueReporterModel.serialize(); - const issueUrl = this.getIssueUrl(); + const issueUrl = hasUri ? this.getExtensionBugsUrl() : this.getIssueUrl(); + if (!issueUrl) { + return false; + } const gitHubDetails = this.parseGitHubUrl(issueUrl); if (this.configuration.data.githubAccessToken && gitHubDetails) { return this.submitToGitHub(issueTitle, issueBody, gitHubDetails); @@ -1141,49 +1149,13 @@ export class IssueReporter extends Disposable { const extensions = this.issueReporterModel.getData().allExtensions; const matches = extensions.filter(extension => extension.id === selectedExtensionId); if (matches.length) { - this.issueReporterModel.update({ selectedExtension: matches[0] }); - - // if extension does not have provider/handles, will check for either. If extension is already active, IPC will return [false, false] and will proceed as normal. - if (!matches[0].hasIssueDataProviders && !matches[0].hasIssueUriRequestHandler) { - const toActivate = await this.getReporterStatus(matches[0]); - matches[0].hasIssueDataProviders = toActivate[0]; - matches[0].hasIssueUriRequestHandler = toActivate[1]; - this.renderBlocks(); - } - - if (matches[0].hasIssueUriRequestHandler) { - this.updateIssueReporterUri(matches[0]); - } else if (matches[0].hasIssueDataProviders) { - const template = await this.getIssueTemplateFromExtension(matches[0]); - const descriptionTextArea = this.getElementById('description')!; - const descriptionText = (descriptionTextArea as HTMLTextAreaElement).value; - if (descriptionText === '' || !descriptionText.includes(template)) { - const fullTextArea = descriptionText + (descriptionText === '' ? '' : '\n') + template; - (descriptionTextArea as HTMLTextAreaElement).value = fullTextArea; - this.issueReporterModel.update({ issueDescription: fullTextArea }); - } - const extensionDataBlock = mainWindow.document.querySelector('.block-extension-data')!; - show(extensionDataBlock); - - // Start loading for extension data. - const iconElement = document.createElement('span'); - iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin'); - this.setLoading(iconElement); - await this.getIssueDataFromExtension(matches[0]); - this.removeLoading(iconElement); - } else { - this.validateSelectedExtension(); - this.issueReporterModel.update({ extensionData: undefined }); - const title = (this.getElementById('issue-title')).value; - this.searchExtensionIssues(title); - } + this.updateExtensionStatus(matches[0]); } else { this.issueReporterModel.update({ selectedExtension: undefined }); this.clearSearchResults(); this.validateSelectedExtension(); } - this.updatePreviewButtonState(); - this.renderBlocks(); + }); } @@ -1192,6 +1164,73 @@ export class IssueReporter extends Disposable { }); } + private async updateExtensionStatus(extension: IssueReporterExtensionData) { + this.issueReporterModel.update({ selectedExtension: extension }); + + // if extension does not have provider/handles, will check for either. If extension is already active, IPC will return [false, false] and will proceed as normal. + if (!extension.hasIssueDataProviders && !extension.hasIssueUriRequestHandler) { + const toActivate = await this.getReporterStatus(extension); + extension.hasIssueDataProviders = toActivate[0]; + extension.hasIssueUriRequestHandler = toActivate[1]; + this.renderBlocks(); + } + + if (extension.hasIssueUriRequestHandler && extension.hasIssueDataProviders) { + // update this first + const template = await this.getIssueTemplateFromExtension(extension); + const descriptionTextArea = this.getElementById('description')!; + const descriptionText = (descriptionTextArea as HTMLTextAreaElement).value; + if (descriptionText === '' || !descriptionText.includes(template)) { + const fullTextArea = descriptionText + (descriptionText === '' ? '' : '\n') + template; + (descriptionTextArea as HTMLTextAreaElement).value = fullTextArea; + this.issueReporterModel.update({ issueDescription: fullTextArea }); + } + const extensionDataBlock = mainWindow.document.querySelector('.block-extension-data')!; + show(extensionDataBlock); + + // Start loading for extension data. + const iconElement = document.createElement('span'); + iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin'); + this.setLoading(iconElement); + await this.getIssueDataFromExtension(extension); + this.removeLoading(iconElement); + + // then update this + this.updateIssueReporterUri(extension); + + // reset to false so issue url is updated, but won't be affected later. + // extension.hasIssueUriRequestHandler = false; + } else if (extension.hasIssueUriRequestHandler) { + this.updateIssueReporterUri(extension); + } else if (extension.hasIssueDataProviders) { + const template = await this.getIssueTemplateFromExtension(extension); + const descriptionTextArea = this.getElementById('description')!; + const descriptionText = (descriptionTextArea as HTMLTextAreaElement).value; + if (descriptionText === '' || !descriptionText.includes(template)) { + const fullTextArea = descriptionText + (descriptionText === '' ? '' : '\n') + template; + (descriptionTextArea as HTMLTextAreaElement).value = fullTextArea; + this.issueReporterModel.update({ issueDescription: fullTextArea }); + } + const extensionDataBlock = mainWindow.document.querySelector('.block-extension-data')!; + show(extensionDataBlock); + + // Start loading for extension data. + const iconElement = document.createElement('span'); + iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin'); + this.setLoading(iconElement); + await this.getIssueDataFromExtension(extension); + this.removeLoading(iconElement); + } else { + this.validateSelectedExtension(); + this.issueReporterModel.update({ extensionData: undefined }); + const title = (this.getElementById('issue-title')).value; + this.searchExtensionIssues(title); + } + + this.updatePreviewButtonState(); + this.renderBlocks(); + } + private validateSelectedExtension(): void { const extensionValidationMessage = this.getElementById('extension-selection-validation-error')!; const extensionValidationNoUrlsMessage = this.getElementById('extension-selection-validation-error-no-url')!; @@ -1205,7 +1244,7 @@ export class IssueReporter extends Disposable { } const hasValidGitHubUrl = this.getExtensionGitHubUrl(); - if (hasValidGitHubUrl || extension.hasIssueUriRequestHandler) { + if (hasValidGitHubUrl || (extension.hasIssueUriRequestHandler && !extension.hasIssueDataProviders)) { this.previewButton.enabled = true; } else { this.setExtensionValidationMessage(); diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index 6af67b552df..93a1f412623 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -57,6 +57,7 @@ export interface IssueReporterExtensionData { extensionTemplate?: string; hasIssueUriRequestHandler?: boolean; hasIssueDataProviders?: boolean; + command?: boolean; } export interface IssueReporterData extends WindowData { @@ -70,6 +71,7 @@ export interface IssueReporterData extends WindowData { githubAccessToken: string; readonly issueTitle?: string; readonly issueBody?: string; + readonly command?: boolean; } export interface ISettingSearchResult { diff --git a/src/vs/workbench/api/browser/mainThreadIssueReporter.ts b/src/vs/workbench/api/browser/mainThreadIssueReporter.ts index 4bcc6c8bb63..c3afade0cbb 100644 --- a/src/vs/workbench/api/browser/mainThreadIssueReporter.ts +++ b/src/vs/workbench/api/browser/mainThreadIssueReporter.ts @@ -13,7 +13,8 @@ import { IIssueDataProvider, IIssueUriRequestHandler, IWorkbenchIssueService } f @extHostNamedCustomer(MainContext.MainThreadIssueReporter) export class MainThreadIssueReporter extends Disposable implements MainThreadIssueReporterShape { private readonly _proxy: ExtHostIssueReporterShape; - private readonly _registrations = this._register(new DisposableMap()); + private readonly _registrationsUri = this._register(new DisposableMap()); + private readonly _registrationsData = this._register(new DisposableMap()); constructor( context: IExtHostContext, @@ -30,11 +31,11 @@ export class MainThreadIssueReporter extends Disposable implements MainThreadIss return URI.from(parts); } }; - this._registrations.set(extensionId, this._issueService.registerIssueUriRequestHandler(extensionId, handler)); + this._registrationsUri.set(extensionId, this._issueService.registerIssueUriRequestHandler(extensionId, handler)); } $unregisterIssueUriRequestHandler(extensionId: string): void { - this._registrations.deleteAndDispose(extensionId); + this._registrationsUri.deleteAndDispose(extensionId); } $registerIssueDataProvider(extensionId: string): void { @@ -48,10 +49,10 @@ export class MainThreadIssueReporter extends Disposable implements MainThreadIss return parts; } }; - this._registrations.set(extensionId, this._issueService.registerIssueDataProvider(extensionId, provider)); + this._registrationsData.set(extensionId, this._issueService.registerIssueDataProvider(extensionId, provider)); } $unregisterIssueDataProvider(extensionId: string): void { - this._registrations.deleteAndDispose(extensionId); + this._registrationsData.deleteAndDispose(extensionId); } } diff --git a/src/vs/workbench/services/issue/browser/issueService.ts b/src/vs/workbench/services/issue/browser/issueService.ts index 2d840cf2220..469ae81b74b 100644 --- a/src/vs/workbench/services/issue/browser/issueService.ts +++ b/src/vs/workbench/services/issue/browser/issueService.ts @@ -73,7 +73,7 @@ export class WebIssueService implements IWorkbenchIssueService { registerIssueDataProvider(extensionId: string, handler: IIssueDataProvider): IDisposable { this._providers.set(extensionId, handler); - return toDisposable(() => this._handlers.delete(extensionId)); + return toDisposable(() => this._providers.delete(extensionId)); } private async getIssueUriFromHandler(extensionId: string, token: CancellationToken): Promise { diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts index f5e7db23472..ebc4b66e1dc 100644 --- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts @@ -104,6 +104,7 @@ export class NativeIssueService implements IWorkbenchIssueService { hasIssueDataProviders: this._providers.has(extension.identifier.id.toLowerCase()), displayName: manifest.displayName, id: extension.identifier.id, + command: dataOverrides.command, isTheme, isBuiltin, extensionData: 'Extensions data loading', From bff3ef7651851953433ca21298aba7bbc2575155 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 5 Jan 2024 21:55:05 +0100 Subject: [PATCH 0102/1897] Git - improve code that gets commit count (#201907) --- extensions/git/src/git.ts | 8 +++++++- extensions/git/src/historyProvider.ts | 10 ++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 7a8c891cf06..6f768d36ea0 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -2622,7 +2622,13 @@ export class Repository { } async getCommitCount(range: string): Promise<{ ahead: number; behind: number }> { - const result = await this.exec(['rev-list', '--count', '--left-right', range]); + const args = ['rev-list', '--count', '--left-right', range]; + + if (isWindows) { + args.splice(0, 0, '-c', 'core.longpaths=true'); + } + + const result = await this.exec(args); const [ahead, behind] = result.stdout.trim().split('\t'); return { ahead: Number(ahead) || 0, behind: Number(behind) || 0 }; diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index a58b491ce62..a6e97c63763 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -186,8 +186,14 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return undefined; } - const commitCount = await this.repository.getCommitCount(`${refId1}...${refId2}`); - return { id: ancestor, ahead: commitCount.ahead, behind: commitCount.behind }; + try { + const commitCount = await this.repository.getCommitCount(`${refId1}...${refId2}`); + return { id: ancestor, ahead: commitCount.ahead, behind: commitCount.behind }; + } catch (err) { + this.logger.error(`Failed to get ahead/behind for '${refId1}...${refId2}': ${err.message}`); + } + + return undefined; } provideFileDecoration(uri: Uri): FileDecoration | undefined { From 41c99fd2c560f3ae5e7b2639995f237fa46f720d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 5 Jan 2024 15:10:50 -0600 Subject: [PATCH 0103/1897] on dispose/change of active instance, stop --- .../chat/electron-sandbox/actions/voiceTerminalActions.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceTerminalActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceTerminalActions.ts index c91b3d2dd10..ec695b30894 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceTerminalActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceTerminalActions.ts @@ -74,6 +74,8 @@ class VoiceSession extends Disposable { @ITerminalService readonly _terminalService: ITerminalService ) { super(); + this._register(this._terminalService.onDidChangeActiveInstance(() => this.stop())); + this._register(this._terminalService.onDidDisposeInstance(() => this.stop())); } start(): void { this.stop(); From 87d7b79bca92def979dc02f457e055f462aac47b Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 5 Jan 2024 14:59:16 -0800 Subject: [PATCH 0104/1897] improve labels, enable/disable/remove state, hydration error --- .../browser/breakpointEditorContribution.ts | 3 +- .../contrib/debug/browser/breakpointsView.ts | 5 ++-- .../contrib/debug/browser/debugService.ts | 20 +++++++++++++ .../contrib/debug/common/debugStorage.ts | 2 +- .../debug/test/browser/breakpoints.test.ts | 28 +++++++++++-------- 5 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 949a858acbe..88755e694d5 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -83,7 +83,8 @@ export function createBreakpointDecorations(accessor: ServicesAccessor, model: I function getBreakpointDecorationOptions(accessor: ServicesAccessor, model: ITextModel, breakpoint: IBreakpoint, state: State, breakpointsActivated: boolean, showBreakpointsInOverviewRuler: boolean, hasOtherBreakpointsOnLine: boolean): IModelDecorationOptions { const debugService = accessor.get(IDebugService); const languageService = accessor.get(ILanguageService); - const { icon, message, showAdapterUnverifiedMessage } = getBreakpointMessageAndIcon(state, breakpointsActivated, breakpoint, undefined); + const labelService = accessor.get(ILabelService); + const { icon, message, showAdapterUnverifiedMessage } = getBreakpointMessageAndIcon(state, breakpointsActivated, breakpoint, labelService); let glyphMarginHoverMessage: MarkdownString | undefined; let unverifiedMessage: string | undefined; diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 31aa96989ab..5ba9b4a670f 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -1206,7 +1206,7 @@ export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolea }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } -export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: boolean, breakpoint: BreakpointItem, labelService?: ILabelService): { message?: string; icon: ThemeIcon; showAdapterUnverifiedMessage?: boolean } { +export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: boolean, breakpoint: BreakpointItem, labelService: ILabelService): { message?: string; icon: ThemeIcon; showAdapterUnverifiedMessage?: boolean } { const debugActive = state === State.Running || state === State.Stopped; const breakpointIcon = breakpoint instanceof DataBreakpoint ? icons.dataBreakpoint : breakpoint instanceof FunctionBreakpoint ? icons.functionBreakpoint : breakpoint.logMessage ? icons.logBreakpoint : icons.breakpoint; @@ -1321,9 +1321,8 @@ export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: if (breakpoint.hitCondition) { messages.push(localize('hitCount', "Hit Count: {0}", breakpoint.hitCondition)); } - if (triggeredByBreakpoint) { - messages.push(localize('triggeredBy', "Hit after breakpoint: {0}:{1}", triggeredByBreakpoint.uri.toString(), triggeredByBreakpoint.lineNumber)); + messages.push(localize('triggeredBy', "Hit after breakpoint: {0}", `${labelService.getUriLabel(triggeredByBreakpoint.uri, { relative: true })}: ${triggeredByBreakpoint.lineNumber}`)); } return { diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 86272f84a84..7ccc96a2c27 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -1004,6 +1004,7 @@ export class DebugService implements IDebugService { this.model.setEnablement(breakpoint, enable); this.debugStorage.storeBreakpoints(this.model); if (breakpoint instanceof Breakpoint) { + await this.makeDependentBreakpointsMatchEnablement(enable, breakpoint); await this.sendBreakpoints(breakpoint.originalUri); } else if (breakpoint instanceof FunctionBreakpoint) { await this.sendFunctionBreakpoints(); @@ -1152,6 +1153,25 @@ export class DebugService implements IDebugService { } } + private async makeDependentBreakpointsMatchEnablement(enable: boolean, breakpoint: Breakpoint) { + if (enable) { + /** If the breakpoint is being enabled, also ensure its triggerer is enabled */ + if (breakpoint.triggeredBy) { + const trigger = this.model.getBreakpoints().find(bp => breakpoint.triggeredBy!.matches(bp)); + if (trigger && !trigger.enabled) { + await this.enableOrDisableBreakpoints(enable, trigger); + } + } + } + + + /** Makes its triggeree states match the state of this breakpoint */ + await Promise.all(this.model.getBreakpoints() + .filter(bp => bp.triggeredBy?.matches(breakpoint) && bp.enabled !== enable) + .map(bp => this.enableOrDisableBreakpoints(enable, bp)) + ); + } + private async sendBreakpoints(modelUri: uri, sourceModified = false, session?: IDebugSession): Promise { const breakpointsToSend = this.model.getBreakpoints({ originalUri: modelUri, enabledOnly: true, excludeDependent: true }); await sendToOneOrAllSessions(this.model, session, async s => { diff --git a/src/vs/workbench/contrib/debug/common/debugStorage.ts b/src/vs/workbench/contrib/debug/common/debugStorage.ts index f5c72a26101..fbc318d522a 100644 --- a/src/vs/workbench/contrib/debug/common/debugStorage.ts +++ b/src/vs/workbench/contrib/debug/common/debugStorage.ts @@ -76,7 +76,7 @@ export class DebugStorage extends Disposable { private parseBreakpointRef(breakpoint: any): IBreakpointReference | undefined { if (breakpoint.waitFor) { - return new BreakpointReference(breakpoint.waitFor.uri, breakpoint.waitFor.lineNumber, breakpoint.waitFor.column); + return new BreakpointReference(URI.parse(breakpoint.waitFor.uri), breakpoint.waitFor.lineNumber, breakpoint.waitFor.column); } return undefined; } diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index 72df37df6ba..0aff04fe30a 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -14,6 +14,7 @@ import { OverviewRulerLane } from 'vs/editor/common/model'; import { LanguageService } from 'vs/editor/common/services/languageService'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { ILabelService } from 'vs/platform/label/common/label'; import { NullLogService } from 'vs/platform/log/common/log'; import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { createBreakpointDecorations } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution'; @@ -23,6 +24,7 @@ import { Breakpoint, DebugModel } from 'vs/workbench/contrib/debug/common/debugM import { createTestSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test'; import { createMockDebugModel, mockUriIdentityService } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; import { MockDebugService, MockDebugStorage } from 'vs/workbench/contrib/debug/test/common/mockDebug'; +import { MockLabelService } from 'vs/workbench/services/label/test/common/mockLabelService'; import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; function addBreakpointsAndCheckEvents(model: DebugModel, uri: uri, data: IBreakpointData[]) { @@ -346,39 +348,40 @@ suite('Debug - Breakpoints', () => { { lineNumber: 500, enabled: true }, ]); const breakpoints = model.getBreakpoints(); + const ls = new MockLabelService(); - let result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[0]); + let result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[0], ls); assert.strictEqual(result.message, 'Condition: x > 5'); assert.strictEqual(result.icon.id, 'debug-breakpoint-conditional'); - result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[1]); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[1], ls); assert.strictEqual(result.message, 'Disabled Breakpoint'); assert.strictEqual(result.icon.id, 'debug-breakpoint-disabled'); - result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[2]); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[2], ls); assert.strictEqual(result.message, 'Log Message: hello'); assert.strictEqual(result.icon.id, 'debug-breakpoint-log'); - result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[3]); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[3], ls); assert.strictEqual(result.message, 'Hit Count: 12'); assert.strictEqual(result.icon.id, 'debug-breakpoint-conditional'); - result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[4]); - assert.strictEqual(result.message, 'Breakpoint'); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[4], ls); + assert.strictEqual(result.message, ls.getUriLabel(breakpoints[4].uri)); assert.strictEqual(result.icon.id, 'debug-breakpoint'); - result = getBreakpointMessageAndIcon(State.Stopped, false, breakpoints[2]); + result = getBreakpointMessageAndIcon(State.Stopped, false, breakpoints[2], ls); assert.strictEqual(result.message, 'Disabled Logpoint'); assert.strictEqual(result.icon.id, 'debug-breakpoint-log-disabled'); model.addDataBreakpoint('label', 'id', true, ['read'], 'read'); const dataBreakpoints = model.getDataBreakpoints(); - result = getBreakpointMessageAndIcon(State.Stopped, true, dataBreakpoints[0]); + result = getBreakpointMessageAndIcon(State.Stopped, true, dataBreakpoints[0], ls); assert.strictEqual(result.message, 'Data Breakpoint'); assert.strictEqual(result.icon.id, 'debug-breakpoint-data'); const functionBreakpoint = model.addFunctionBreakpoint('foo', '1'); - result = getBreakpointMessageAndIcon(State.Stopped, true, functionBreakpoint); + result = getBreakpointMessageAndIcon(State.Stopped, true, functionBreakpoint, ls); assert.strictEqual(result.message, 'Function Breakpoint'); assert.strictEqual(result.icon.id, 'debug-breakpoint-function'); @@ -389,15 +392,15 @@ suite('Debug - Breakpoints', () => { data.set(functionBreakpoint.getId(), { verified: true }); model.setBreakpointSessionData('mocksessionid', { supportsFunctionBreakpoints: false, supportsDataBreakpoints: true, supportsLogPoints: true }, data); - result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[0]); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[0], ls); assert.strictEqual(result.message, 'Unverified Breakpoint'); assert.strictEqual(result.icon.id, 'debug-breakpoint-unverified'); - result = getBreakpointMessageAndIcon(State.Stopped, true, functionBreakpoint); + result = getBreakpointMessageAndIcon(State.Stopped, true, functionBreakpoint, ls); assert.strictEqual(result.message, 'Function breakpoints not supported by this debug type'); assert.strictEqual(result.icon.id, 'debug-breakpoint-function-unverified'); - result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[2]); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[2], ls); assert.strictEqual(result.message, 'Log Message: hello, world'); assert.strictEqual(result.icon.id, 'debug-breakpoint-log'); }); @@ -419,6 +422,7 @@ suite('Debug - Breakpoints', () => { const instantiationService = new TestInstantiationService(); instantiationService.stub(IDebugService, new MockDebugService()); + instantiationService.stub(ILabelService, new MockLabelService()); instantiationService.stub(ILanguageService, disposables.add(new LanguageService())); let decorations = instantiationService.invokeFunction(accessor => createBreakpointDecorations(accessor, textModel, breakpoints, State.Running, true, true)); assert.strictEqual(decorations.length, 3); // last breakpoint filtered out since it has a large line number From cce898337c776ee9a052dec842c3c8b1c7f9626b Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 5 Jan 2024 15:35:59 -0800 Subject: [PATCH 0105/1897] unlink dependent breakpoints when parent is removed, fixed column matching --- .../contrib/debug/browser/debugService.ts | 31 ++++++++++++++++--- .../workbench/contrib/debug/common/debug.ts | 2 ++ .../contrib/debug/common/debugModel.ts | 13 ++++++-- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 7ccc96a2c27..477494e0df7 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -40,7 +40,7 @@ import { ConfigurationManager } from 'vs/workbench/contrib/debug/browser/debugCo import { DebugMemoryFileSystemProvider } from 'vs/workbench/contrib/debug/browser/debugMemory'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; import { DebugTaskRunner, TaskRunResult } from 'vs/workbench/contrib/debug/browser/debugTaskRunner'; -import { CALLSTACK_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_HAS_DEBUGGED, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_UX, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_IN_DEBUG_MODE, debuggerDisabledMessage, DEBUG_MEMORY_SCHEME, getStateLabel, IAdapterManager, IBreakpoint, IBreakpointData, ICompound, IConfig, IConfigurationManager, IDebugConfiguration, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IEnablement, IExceptionBreakpoint, IGlobalConfig, ILaunch, IStackFrame, IThread, IViewModel, REPL_VIEW_ID, State, VIEWLET_ID, DEBUG_SCHEME } from 'vs/workbench/contrib/debug/common/debug'; +import { CALLSTACK_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_HAS_DEBUGGED, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_UX, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_IN_DEBUG_MODE, debuggerDisabledMessage, DEBUG_MEMORY_SCHEME, getStateLabel, IAdapterManager, IBreakpoint, IBreakpointData, ICompound, IConfig, IConfigurationManager, IDebugConfiguration, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IEnablement, IExceptionBreakpoint, IGlobalConfig, ILaunch, IStackFrame, IThread, IViewModel, REPL_VIEW_ID, State, VIEWLET_ID, DEBUG_SCHEME, IBreakpointUpdateData } from 'vs/workbench/contrib/debug/common/debug'; import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; import { Breakpoint, DataBreakpoint, DebugModel, FunctionBreakpoint, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; @@ -1037,7 +1037,7 @@ export class DebugService implements IDebugService { return breakpoints; } - async updateBreakpoints(uri: uri, data: Map, sendOnResourceSaved: boolean): Promise { + async updateBreakpoints(uri: uri, data: Map, sendOnResourceSaved: boolean): Promise { this.model.updateBreakpoints(data); this.debugStorage.storeBreakpoints(this.model); if (sendOnResourceSaved) { @@ -1049,15 +1049,17 @@ export class DebugService implements IDebugService { } async removeBreakpoints(id?: string): Promise { - const toRemove = this.model.getBreakpoints().filter(bp => !id || bp.getId() === id); + const breakpoints = this.model.getBreakpoints(); + const toRemove = breakpoints.filter(bp => !id || bp.getId() === id); // note: using the debugger-resolved uri for aria to reflect UI state toRemove.forEach(bp => aria.status(nls.localize('breakpointRemoved', "Removed breakpoint, line {0}, file {1}", bp.lineNumber, bp.uri.fsPath))); - const urisToClear = distinct(toRemove, bp => bp.originalUri.toString()).map(bp => bp.originalUri); + const urisToClear = new Set(toRemove.map(bp => bp.originalUri.toString())); this.model.removeBreakpoints(toRemove); + this.unlinkDependentBreakpoints(breakpoints, toRemove).forEach(uri => urisToClear.add(uri.toString())); this.debugStorage.storeBreakpoints(this.model); - await Promise.all(urisToClear.map(uri => this.sendBreakpoints(uri))); + await Promise.all([...urisToClear].map(uri => this.sendBreakpoints(URI.parse(uri)))); } setBreakpointsActivated(activated: boolean): Promise { @@ -1153,6 +1155,25 @@ export class DebugService implements IDebugService { } } + /** + * Removes the condition of triggered breakpoints that depended on + * breakpoints in `removedBreakpoints`. Returns the URIs of resources that + * had their breakpoints changed in this way. + */ + private unlinkDependentBreakpoints(allBreakpoints: readonly IBreakpoint[], removedBreakpoints: readonly IBreakpoint[]): uri[] { + const affectedUris: uri[] = []; + for (const removed of removedBreakpoints) { + for (const existing of allBreakpoints) { + if (!removedBreakpoints.includes(existing) && existing.triggeredBy?.matches(removed)) { + this.model.updateBreakpoints(new Map([[existing.getId(), { triggeredBy: undefined }]])); + affectedUris.push(existing.originalUri); + } + } + } + + return affectedUris; + } + private async makeDependentBreakpointsMatchEnablement(enable: boolean, breakpoint: Breakpoint) { if (enable) { /** If the breakpoint is being enabled, also ensure its triggerer is enabled */ diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 87628924bd6..a9c4f198265 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -562,6 +562,8 @@ export interface IBreakpointReference { } export interface IBreakpoint extends IBaseBreakpoint { + /** Colum number where the breakpoint was first set by the user. */ + readonly originalColumn?: number; /** URI where the breakpoint was first set by the user. */ readonly originalUri: uri; /** URI where the breakpoint is currently shown; may be moved by debugger */ diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 1d88c4b270f..bcc5090d082 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -891,6 +891,10 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { return this._uri; } + get originalColumn() { + return this._column; + } + get lineNumber(): number { return this.verified && this.data && typeof this.data.line === 'number' ? this.data.line : this._lineNumber; } @@ -1008,7 +1012,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { this.logMessage = data.logMessage; } if (!isUndefinedOrNull(data.triggeredBy)) { - this.triggeredBy = new BreakpointReference(data.triggeredBy.uri, data.triggeredBy.lineNumber, data.triggeredBy.column); + this.triggeredBy = new BreakpointReference(data.triggeredBy.uri, data.triggeredBy.lineNumber, data.triggeredBy.originalColumn); } else { this.triggeredBy = undefined; } @@ -1019,7 +1023,12 @@ export class BreakpointReference implements IBreakpointReference { constructor(public uri: uri, public lineNumber: number, public column?: number) { } matches(bp: IBreakpoint): boolean { - return bp.uri.toString() === this.uri?.toString() && bp.lineNumber === this.lineNumber && bp.column === this.column; + // prefer to store and match the original column as the reference, since + // start-of-line breakpoints can be moved to a more specific location + // within the line, which could ordinarily prevent matching. + return bp.uri.toString() === this.uri?.toString() + && bp.lineNumber === this.lineNumber + && (bp.originalColumn === this.column); } } From f88bce8fe6a6d2ccd27cbd64bb26853cd8779afa Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 5 Jan 2024 15:44:43 -0800 Subject: [PATCH 0106/1897] get children when node expands (#201909) * get children when node expands * switch to asynchronous tree for more natural lazy loading * store variables by ID so we can look up the correct instance --- .../api/browser/mainThreadNotebookKernels.ts | 6 +- .../workbench/api/common/extHost.protocol.ts | 11 ++- .../api/common/extHostNotebookKernels.ts | 32 ++++++- .../notebookVariables/notebookVariables.ts | 3 +- .../notebookVariablesDataSource.ts | 94 +++++++++++++++++++ .../notebookVariablesTree.ts | 13 +-- .../notebookVariablesView.ts | 78 +++------------ .../notebook/common/notebookKernelService.ts | 11 +-- .../browser/notebookExecutionService.test.ts | 2 +- .../notebookExecutionStateService.test.ts | 2 +- .../browser/notebookKernelHistory.test.ts | 2 +- .../browser/notebookKernelService.test.ts | 2 +- ...ode.proposed.notebookVariableProvider.d.ts | 2 +- 13 files changed, 164 insertions(+), 94 deletions(-) create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index e1190a65660..678825ac6a5 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -101,7 +101,7 @@ abstract class MainThreadKernel implements INotebookKernel { abstract executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise; abstract cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise; - abstract provideVariables(notebookUri: URI, variableName: string | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject; + abstract provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject; } class MainThreadKernelDetectionTask implements INotebookKernelDetectionTask { @@ -242,7 +242,7 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape async cancelNotebookCellExecution(uri: URI, handles: number[]): Promise { await that._proxy.$cancelCells(handle, uri, handles); } - provideVariables(notebookUri: URI, parentName: string | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject { + provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject { const requestId = `${handle}variables${that.variableRequestIndex++}`; if (that.variableRequestMap.has(requestId)) { return that.variableRequestMap.get(requestId)!.asyncIterable; @@ -250,7 +250,7 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape const source = new AsyncIterableSource(); that.variableRequestMap.set(requestId, source); - that._proxy.$provideVariables(handle, requestId, notebookUri, parentName, kind, start, token).then(() => { + that._proxy.$provideVariables(handle, requestId, notebookUri, parentId, kind, start, token).then(() => { source.resolve(); that.variableRequestMap.delete(requestId); }).catch((err) => { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 69b3e3d0e32..712156eff9e 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -80,7 +80,6 @@ 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'; -import { VariablesResult } from 'vscode'; export interface IWorkspaceData extends IStaticWorkspaceData { folders: { uri: UriComponents; name: string; index: number }[]; @@ -1114,6 +1113,14 @@ export interface ICellExecutionCompleteDto extends ICellExecutionComplete { export type ICellExecuteUpdateDto = ICellExecuteOutputEditDto | ICellExecuteOutputItemEditDto | ICellExecutionStateUpdateDto; +export interface VariablesResult { + id: number; + name: string; + value: string; + hasNamedChildren: boolean; + indexedChildrenCount: number; +} + export interface MainThreadNotebookKernelsShape extends IDisposable { $postMessage(handle: number, editorId: string | undefined, message: any): Promise; $addKernel(handle: number, data: INotebookKernelDto2): Promise; @@ -2544,7 +2551,7 @@ export interface ExtHostNotebookKernelsShape { $acceptKernelMessageFromRenderer(handle: number, editorId: string, message: any): void; $cellExecutionChanged(uri: UriComponents, cellHandle: number, state: notebookCommon.NotebookCellExecutionState | undefined): void; $provideKernelSourceActions(handle: number, token: CancellationToken): Promise; - $provideVariables(handle: number, requestId: string, notebookUri: UriComponents, variableName: string | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): Promise; + $provideVariables(handle: number, requestId: string, notebookUri: UriComponents, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): Promise; } export interface ExtHostInteractiveShape { diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index 164369d1212..a83caeb5c84 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -415,7 +415,10 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { } } - async $provideVariables(handle: number, requestId: string, notebookUri: UriComponents, parentName: string | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): Promise { + private id = 0; + private variableStore: Record = {}; + + async $provideVariables(handle: number, requestId: string, notebookUri: UriComponents, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): Promise { const obj = this._kernelData.get(handle); if (!obj) { return; @@ -427,13 +430,34 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { return; } - const parent = parentName ? { name: parentName, value: '' } : undefined; + let parent: vscode.Variable | undefined = undefined; + if (parentId) { + parent = this.variableStore[parentId]; + if (!parent) { + // request for unknown parent + return; + } + } else { + // root request, clear store + this.variableStore = {}; + } + + const requestKind = kind === 'named' ? NotebookVariablesRequestKind.Named : NotebookVariablesRequestKind.Indexed; - const variables = variableProvider.provideVariables(document.apiNotebook, parent, requestKind, start, token); - for await (const variable of variables) { + const variableResults = variableProvider.provideVariables(document.apiNotebook, parent, requestKind, start, token); + + for await (const result of variableResults) { if (token.isCancellationRequested) { return; } + const variable = { + id: this.id++, + name: result.variable.name, + value: result.variable.value, + hasNamedChildren: result.hasNamedChildren, + indexedChildrenCount: result.indexedChildrenCount + }; + this.variableStore[variable.id] = result.variable; this._proxy.$receiveVariable(requestId, variable); } } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts index be6c2da14e1..03c9c7b819e 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts @@ -43,7 +43,8 @@ export class NotebookVariables extends Disposable implements IWorkbenchContribut if (debugViewContainer) { const viewsRegistry = Registry.as(Extensions.ViewsRegistry); const viewDescriptor = { - id: 'NOTEBOOK_VARIABLES', name: nls.localize2('notebookVariables', "Notebook Variables"), containerIcon: variablesViewIcon, ctorDescriptor: new SyncDescriptor(NotebookVariablesView), + id: 'NOTEBOOK_VARIABLES', name: nls.localize2('notebookVariables', "Notebook Variables"), + containerIcon: variablesViewIcon, ctorDescriptor: new SyncDescriptor(NotebookVariablesView), order: 50, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: ContextKeyExpr.notEquals(NOTEBOOK_KERNEL.key, ''), }; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts new file mode 100644 index 00000000000..bc64f09d6e7 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { INotebookKernelService, VariablesResult } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; + +export interface INotebookScope { + type: 'root'; + readonly notebook: NotebookTextModel | undefined; +} + +export interface INotebookVariableElement { + type: 'variable'; + readonly id: number; + readonly name: string; + readonly value: string; + readonly indexedChildrenCount: number; + readonly hasNamedChildren: boolean; +} + +export class NotebookVariableDataSource implements IAsyncDataSource { + + private notebook: NotebookTextModel | undefined = undefined; + + constructor(private readonly notebookKernelService: INotebookKernelService) { } + + hasChildren(element: INotebookScope | INotebookVariableElement): boolean { + return element.type === 'root' || element.hasNamedChildren || element.indexedChildrenCount > 0; + } + + async getChildren(element: INotebookScope | INotebookVariableElement): Promise> { + if (element.type === 'root') { + this.notebook = element.notebook; + return this.getRootVariables(); + } else { + return this.getVariables(element); + } + } + + async getVariables(parent: INotebookVariableElement): Promise { + if (!this.notebook) { + return []; + } + const selectedKernel = this.notebookKernelService.getMatchingKernel(this.notebook).selected; + if (selectedKernel && selectedKernel.hasVariableProvider) { + + let children: INotebookVariableElement[] = []; + if (parent.hasNamedChildren) { + const variables = selectedKernel.provideVariables(this.notebook.uri, parent.id, 'named', 0, CancellationToken.None); + const childNodes = await variables + .map(variable => { return this.createVariableElement(variable); }) + .toPromise(); + children = children.concat(childNodes); + } + if (parent.indexedChildrenCount > 0) { + const variables = selectedKernel.provideVariables(this.notebook.uri, parent.id, 'indexed', 0, CancellationToken.None); + const childNodes = await variables + .map(variable => { return this.createVariableElement(variable); }) + .toPromise(); + children = children.concat(childNodes); + } + + return children; + } + return []; + } + + async getRootVariables(): Promise { + if (!this.notebook) { + return []; + } + + const selectedKernel = this.notebookKernelService.getMatchingKernel(this.notebook).selected; + if (selectedKernel && selectedKernel.hasVariableProvider) { + const variables = selectedKernel.provideVariables(this.notebook.uri, undefined, 'named', 0, CancellationToken.None); + return await variables + .map(variable => { return this.createVariableElement(variable); }) + .toPromise(); + } + + return []; + } + + private createVariableElement(variable: VariablesResult): INotebookVariableElement { + return { + type: 'variable', + ...variable + }; + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts index 1fbda38b993..2fa75095b6e 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts @@ -10,12 +10,7 @@ import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { FuzzyScore } from 'vs/base/common/filters'; import { localize } from 'vs/nls'; import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; - -export interface INotebookVariableElement { - readonly id: string; - readonly label: string; - readonly value: string; -} +import { INotebookVariableElement } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource'; export class NotebookVariablesTree extends WorkbenchObjectTree { } @@ -43,8 +38,8 @@ export class NotebookVariableRenderer implements ITreeRenderer, index: number, templateData: { wrapper: HTMLElement }, height: number | undefined): void { - templateData.wrapper.innerText = `${element.element.label} - ${element.element.value}`; + renderElement(element: ITreeNode, _index: number, templateData: { wrapper: HTMLElement }): void { + templateData.wrapper.innerText = `${element.element.name}: ${element.element.value}`; } disposeTemplate(): void { @@ -59,6 +54,6 @@ export class NotebookVariableAccessibilityProvider implements IListAccessibility } getAriaLabel(element: INotebookVariableElement): string { - return localize('notebookVariableAriaLabel', "Variable {0}, value {1}", element.label, element.value); + return localize('notebookVariableAriaLabel', "Variable {0}, value {1}", element.name, element.value); } } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index eba684c4c9f..c2b00fd9f17 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IObjectTreeElement } from 'vs/base/browser/ui/tree/tree'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { ILocalizedString } from 'vs/platform/action/common/action'; @@ -14,18 +12,19 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; +import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewDescriptorService } from 'vs/workbench/common/views'; -import { NotebookVariableAccessibilityProvider, NotebookVariableRenderer, INotebookVariableElement, NotebookVariablesDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree'; +import { INotebookScope, INotebookVariableElement, NotebookVariableDataSource } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource'; +import { NotebookVariableAccessibilityProvider, NotebookVariableRenderer, NotebookVariablesDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree'; import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { ICellExecutionStateChangedEvent, IExecutionStateChangedEvent, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernelService, VariablesResult } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class NotebookVariablesView extends ViewPane { @@ -33,7 +32,7 @@ export class NotebookVariablesView extends ViewPane { static readonly ID = 'notebookVariablesView'; static readonly TITLE: ILocalizedString = nls.localize2('notebook.notebookVariables', "Notebook Variables"); - private tree: WorkbenchObjectTree | undefined; + private tree: WorkbenchAsyncDataTree | undefined; private activeNotebook: NotebookTextModel | undefined; constructor( @@ -63,23 +62,20 @@ export class NotebookVariablesView extends ViewPane { protected override renderBody(container: HTMLElement): void { super.renderBody(container); - this.tree = >this.instantiationService.createInstance( - WorkbenchObjectTree, + this.tree = >this.instantiationService.createInstance( + WorkbenchAsyncDataTree, 'notebookVariablesTree', container, new NotebookVariablesDelegate(), [new NotebookVariableRenderer()], + new NotebookVariableDataSource(this.notebookKernelService), { - identityProvider: { getId: (e: INotebookVariableElement) => e.id }, - horizontalScrolling: false, - supportDynamicHeights: true, - hideTwistiesOfChildlessElements: true, accessibilityProvider: new NotebookVariableAccessibilityProvider(), - setRowLineHeight: false, + identityProvider: { getId: (e: INotebookVariableElement) => e.id }, }); this.tree.layout(); - this.tree?.setChildren(null, []); + this.tree.setInput({ type: 'root', notebook: this.activeNotebook }); } protected override layoutBody(height: number, width: number): void { @@ -93,7 +89,8 @@ export class NotebookVariablesView extends ViewPane { const notebookDocument = getNotebookEditorFromEditorPane(activeEditorPane)?.getViewModel()?.notebookDocument; if (notebookDocument && notebookDocument !== this.activeNotebook) { this.activeNotebook = notebookDocument; - this.updateVariables(this.activeNotebook); + this.tree?.setInput({ type: 'root', notebook: this.activeNotebook }); + this.tree?.updateChildren(); } } } @@ -102,60 +99,15 @@ export class NotebookVariablesView extends ViewPane { if (this.activeNotebook) { // changed === undefined -> excecution ended if (event.changed === undefined && event.affectsNotebook(this.activeNotebook?.uri)) { - this.updateVariables(this.activeNotebook); + this.tree?.updateChildren(); } } } private handleVariablesChanged(notebookUri: URI) { if (this.activeNotebook && notebookUri.toString() === this.activeNotebook.uri.toString()) { - this.updateVariables(this.activeNotebook); + this.tree?.setInput({ type: 'root', notebook: this.activeNotebook }); + this.tree?.updateChildren(); } } - - private async updateVariables(notebook: NotebookTextModel) { - const selectedKernel = this.notebookKernelService.getMatchingKernel(notebook).selected; - if (selectedKernel && selectedKernel.hasVariableProvider) { - - const variables = selectedKernel.provideVariables(notebook.uri, undefined, 'named', 0, CancellationToken.None); - const treeData = await variables - .map(variable => { return this.createTreeItem(variable); }) - .toPromise(); - - this.tree?.setChildren(null, treeData); - } - } - - private index = 0; - - private createTreeItem(variable: VariablesResult): IObjectTreeElement { - let collapsed: boolean | undefined = undefined; - let children: IObjectTreeElement[] | undefined = undefined; - if (variable.namedChildrenCount > 0 || variable.indexedChildrenCount > 0) { - collapsed = true; - children = [ - { - element: { - id: `${this.index + 1}-placeholder`, - label: ' ', - value: 'loading...', - } - } - ]; - } - - const element = { - element: { - id: `${this.index++}`, - label: variable.variable.name, - value: variable.variable.value, - }, - children, - collapsed - }; - - - - return element; - } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index 6b1e5ca8c9b..212fe0ef0ba 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -38,14 +38,11 @@ export interface INotebookKernelChangeEvent { hasVariableProvider?: true; } -export interface Variable { +export interface VariablesResult { + id: number; name: string; value: string; -} - -export interface VariablesResult { - variable: Variable; - namedChildrenCount: number; + hasNamedChildren: boolean; indexedChildrenCount: number; } @@ -70,7 +67,7 @@ export interface INotebookKernel { executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise; cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise; - provideVariables(notebookUri: URI, variableName: string | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject; + provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject; } export const enum ProxyKernelState { diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts index 59ac82b1abb..c4b00528450 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts @@ -174,7 +174,7 @@ class TestNotebookKernel implements INotebookKernel { preloadUris: URI[] = []; preloadProvides: string[] = []; supportedLanguages: string[] = []; - provideVariables(notebookUri: URI, variableName: string | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject { + provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject { return AsyncIterableObject.EMPTY; } executeNotebookCellsRequest(): Promise { diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts index 269f4c10ab4..7087aa9ec1a 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts @@ -362,7 +362,7 @@ class TestNotebookKernel implements INotebookKernel { supportedLanguages: string[] = []; async executeNotebookCellsRequest(): Promise { } async cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise { } - provideVariables(notebookUri: URI, variableName: string | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject { + provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject { return AsyncIterableObject.EMPTY; } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts index 084f7e5f0b0..a476f5caead 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts @@ -186,7 +186,7 @@ class TestNotebookKernel implements INotebookKernel { cancelNotebookCellExecution(): Promise { throw new Error('Method not implemented.'); } - provideVariables(notebookUri: URI, variableName: string | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject { + provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject { return AsyncIterableObject.EMPTY; } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts index f82e97ae7fa..1ed657a1972 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts @@ -199,7 +199,7 @@ class TestNotebookKernel implements INotebookKernel { cancelNotebookCellExecution(): Promise { throw new Error('Method not implemented.'); } - provideVariables(notebookUri: URI, variableName: string | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject { + provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject { return AsyncIterableObject.EMPTY; } diff --git a/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts b/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts index 0071af59ff4..ce3de6ebbfc 100644 --- a/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts @@ -16,7 +16,7 @@ declare module 'vscode' { interface VariablesResult { variable: Variable; - namedChildrenCount: number; + hasNamedChildren: boolean; indexedChildrenCount: number; } From 10458b49e4cd497f033eb1a903a8aba9b03a7558 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 5 Jan 2024 16:29:01 -0800 Subject: [PATCH 0107/1897] use ID-based enablement and track hit state on the breakpoint - Referencing the breakpoint location got tricky when breakpoints resolve to different locations than they were initially set at, and also as breakpoints change when files change. Instead, use the breakpoint UUID. - Newly-enabled breakpoints were sent for a URI when hit, but in DAP sending breakpoints for a URI replaces all the breakpoints in it, so this was problematic. Instead track on the breakpoint whether it's been triggered for a session. --- .../browser/breakpointEditorContribution.ts | 4 +- .../contrib/debug/browser/breakpointWidget.ts | 11 ++- .../contrib/debug/browser/breakpointsView.ts | 32 ++++---- .../contrib/debug/browser/debugService.ts | 13 ++-- .../contrib/debug/browser/debugSession.ts | 17 ++-- .../workbench/contrib/debug/common/debug.ts | 28 +++---- .../contrib/debug/common/debugModel.ts | 78 +++++++------------ .../contrib/debug/common/debugStorage.ts | 26 +++---- .../debug/test/browser/breakpoints.test.ts | 26 ++++--- .../contrib/debug/test/common/mockDebug.ts | 4 + 10 files changed, 107 insertions(+), 132 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 88755e694d5..19a1f87ae07 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -84,7 +84,7 @@ function getBreakpointDecorationOptions(accessor: ServicesAccessor, model: IText const debugService = accessor.get(IDebugService); const languageService = accessor.get(ILanguageService); const labelService = accessor.get(ILabelService); - const { icon, message, showAdapterUnverifiedMessage } = getBreakpointMessageAndIcon(state, breakpointsActivated, breakpoint, labelService); + const { icon, message, showAdapterUnverifiedMessage } = getBreakpointMessageAndIcon(state, breakpointsActivated, breakpoint, labelService, debugService.getModel()); let glyphMarginHoverMessage: MarkdownString | undefined; let unverifiedMessage: string | undefined; @@ -526,7 +526,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi // Candidate decoration has a breakpoint attached when a breakpoint is already at that location and we did not yet set a decoration there // In practice this happens for the first breakpoint that was set on a line // We could have also rendered this first decoration as part of desiredBreakpointDecorations however at that moment we have no location information - const icon = candidate.breakpoint ? getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint, this.labelService).icon : icons.breakpoint.disabled; + const icon = candidate.breakpoint ? getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint, this.labelService, this.debugService.getModel()).icon : icons.breakpoint.disabled; const contextMenuActions = () => this.getContextMenuActions(candidate.breakpoint ? [candidate.breakpoint] : [], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn); const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, ThemeIcon.asClassName(icon), candidate.breakpoint, this.debugService, this.contextMenuService, contextMenuActions); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 1723e2f36f5..06993e38dbd 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -227,7 +227,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi private createTriggerBreakpointInput(container: HTMLElement) { const breakpoints = this.debugService.getModel().getBreakpoints().filter(bp => bp !== this.breakpoint); - const index = breakpoints.findIndex((bp) => { return this.breakpoint?.triggeredBy?.matches(bp); }); + const index = breakpoints.findIndex((bp) => this.breakpoint?.triggeredBy === bp.getId()); let select = 0; if (index > -1) { select = index + 1; @@ -387,8 +387,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi let condition = this.breakpoint && this.breakpoint.condition; let hitCondition = this.breakpoint && this.breakpoint.hitCondition; let logMessage = this.breakpoint && this.breakpoint.logMessage; - let triggeredBy = this.breakpoint && this.breakpoint.triggeredBy && - this.debugService.getModel().getBreakpoints().find(b => this.breakpoint!.triggeredBy!.matches(b)); + let triggeredBy = this.breakpoint && this.breakpoint.triggeredBy; this.rememberInput(); @@ -402,7 +401,11 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi logMessage = this.logMessageInput; } if (this.context === Context.TRIGGER_POINT) { - triggeredBy = this.triggeredByBreakpointInput; + // currently, trigger points don't support additional conditions: + condition = undefined; + hitCondition = undefined; + logMessage = undefined; + triggeredBy = this.triggeredByBreakpointInput?.getId(); } if (this.breakpoint) { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 5ba9b4a670f..4c94fc5d41c 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -19,8 +19,9 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import * as resources from 'vs/base/common/resources'; +import { ThemeIcon } from 'vs/base/common/themables'; import { Constants } from 'vs/base/common/uint'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; @@ -32,6 +33,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -40,18 +42,16 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ThemeIcon } from 'vs/base/common/themables'; import { ViewAction, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IEditorPane } from 'vs/workbench/common/editor'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; import { DisassemblyView } from 'vs/workbench/contrib/debug/browser/disassemblyView'; -import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_BREAKPOINT_ITEM_TYPE, CONTEXT_BREAKPOINT_SUPPORTS_CONDITION, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_IN_DEBUG_MODE, DebuggerString, DEBUG_SCHEME, IBaseBreakpoint, IBreakpoint, IBreakpointEditorContribution, IDataBreakpoint, IDebugModel, IDebugService, IEnablement, IExceptionBreakpoint, IFunctionBreakpoint, IInstructionBreakpoint, State } from 'vs/workbench/contrib/debug/common/debug'; -import { Breakpoint, BreakpointReference, DataBreakpoint, ExceptionBreakpoint, FunctionBreakpoint, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; +import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_BREAKPOINT_ITEM_TYPE, CONTEXT_BREAKPOINT_SUPPORTS_CONDITION, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_IN_DEBUG_MODE, DEBUG_SCHEME, DebuggerString, IBaseBreakpoint, IBreakpoint, IBreakpointEditorContribution, IDataBreakpoint, IDebugModel, IDebugService, IEnablement, IExceptionBreakpoint, IFunctionBreakpoint, IInstructionBreakpoint, State } from 'vs/workbench/contrib/debug/common/debug'; +import { Breakpoint, DataBreakpoint, ExceptionBreakpoint, FunctionBreakpoint, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; const $ = dom.$; @@ -539,7 +539,7 @@ class BreakpointsRenderer implements IListRenderer bp.getId() === breakpoint.triggeredBy); } if (breakpoint.logMessage || breakpoint.condition || breakpoint.hitCondition || triggeredByBreakpoint) { diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 477494e0df7..16989fb9747 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -1164,7 +1164,7 @@ export class DebugService implements IDebugService { const affectedUris: uri[] = []; for (const removed of removedBreakpoints) { for (const existing of allBreakpoints) { - if (!removedBreakpoints.includes(existing) && existing.triggeredBy?.matches(removed)) { + if (!removedBreakpoints.includes(existing) && existing.triggeredBy === removed.getId()) { this.model.updateBreakpoints(new Map([[existing.getId(), { triggeredBy: undefined }]])); affectedUris.push(existing.originalUri); } @@ -1178,7 +1178,7 @@ export class DebugService implements IDebugService { if (enable) { /** If the breakpoint is being enabled, also ensure its triggerer is enabled */ if (breakpoint.triggeredBy) { - const trigger = this.model.getBreakpoints().find(bp => breakpoint.triggeredBy!.matches(bp)); + const trigger = this.model.getBreakpoints().find(bp => breakpoint.triggeredBy === bp.getId()); if (trigger && !trigger.enabled) { await this.enableOrDisableBreakpoints(enable, trigger); } @@ -1188,16 +1188,17 @@ export class DebugService implements IDebugService { /** Makes its triggeree states match the state of this breakpoint */ await Promise.all(this.model.getBreakpoints() - .filter(bp => bp.triggeredBy?.matches(breakpoint) && bp.enabled !== enable) + .filter(bp => bp.triggeredBy === breakpoint.getId() && bp.enabled !== enable) .map(bp => this.enableOrDisableBreakpoints(enable, bp)) ); } - private async sendBreakpoints(modelUri: uri, sourceModified = false, session?: IDebugSession): Promise { - const breakpointsToSend = this.model.getBreakpoints({ originalUri: modelUri, enabledOnly: true, excludeDependent: true }); + public async sendBreakpoints(modelUri: uri, sourceModified = false, session?: IDebugSession): Promise { + const breakpointsToSend = this.model.getBreakpoints({ originalUri: modelUri, enabledOnly: true }); await sendToOneOrAllSessions(this.model, session, async s => { if (!s.configuration.noDebug) { - await s.sendBreakpoints(modelUri, breakpointsToSend, sourceModified); + const sessionBps = breakpointsToSend.filter(bp => !bp.triggeredBy || bp.getSessionDidTrigger(s.getId())); + await s.sendBreakpoints(modelUri, sessionBps, sourceModified); } }); } diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 321abdb0f5c..2d73bfc55c6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -1324,24 +1324,17 @@ export class DebugSession implements IDebugSession, IDisposable { // find the current breakpoints // check if the current breakpoints are dependencies, and if so collect and send the dependents to DA - const uriBreakpoints = new Map(); + const urisToResend = new Set(); this.model.getBreakpoints({ dependentOnly: true, enabledOnly: true }).forEach(bp => { breakpoints.forEach(cbp => { - if (bp.enabled && bp.triggeredBy?.matches(cbp)) { - const uri = bp.uri; - if (!uriBreakpoints.has(uri)) { - uriBreakpoints.set(uri, []); - } - uriBreakpoints.get(uri)?.push(bp); + if (bp.enabled && bp.triggeredBy === cbp.getId()) { + bp.setSessionDidTrigger(this.getId()); + urisToResend.add(bp.uri.toString()); } }); }); - if (uriBreakpoints.size > 0) { - uriBreakpoints.forEach((bps, uri, _) => { - this.sendBreakpoints(uri, bps, false); - }); - } + urisToResend.forEach((uri) => this.debugService.sendBreakpoints(URI.parse(uri), undefined, this)); } private getBreakpointsAtPosition(uri: URI, startLineNumber: number, endLineNumber: number, startColumn: number, endColumn: number): IBreakpoint[] { diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index a9c4f198265..8f443c2d307 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -531,7 +531,7 @@ export interface IBreakpointData { readonly condition?: string; readonly logMessage?: string; readonly hitCondition?: string; - readonly triggeredBy?: IBreakpoint; + readonly triggeredBy?: string; } export interface IBreakpointUpdateData { @@ -540,7 +540,7 @@ export interface IBreakpointUpdateData { readonly logMessage?: string; readonly lineNumber?: number; readonly column?: number; - readonly triggeredBy?: IBreakpoint; + readonly triggeredBy?: string; } export interface IBaseBreakpoint extends IEnablement { @@ -554,16 +554,7 @@ export interface IBaseBreakpoint extends IEnablement { getIdFromAdapter(sessionId: string): number | undefined; } -export interface IBreakpointReference { - readonly lineNumber: number; - readonly column?: number; - readonly uri: uri; - matches(bp: IBreakpoint): boolean; -} - export interface IBreakpoint extends IBaseBreakpoint { - /** Colum number where the breakpoint was first set by the user. */ - readonly originalColumn?: number; /** URI where the breakpoint was first set by the user. */ readonly originalUri: uri; /** URI where the breakpoint is currently shown; may be moved by debugger */ @@ -574,9 +565,15 @@ export interface IBreakpoint extends IBaseBreakpoint { readonly endColumn?: number; readonly adapterData: any; readonly sessionAgnosticData: { lineNumber: number; column: number | undefined }; - readonly triggeredBy?: IBreakpointReference; + /** An ID of the breakpoint that triggers this breakpoint. */ + readonly triggeredBy?: string; /** Pending on the trigger breakpoint, which means this breakpoint is not yet sent to DA */ readonly pending: boolean; + + /** Marks that a session did trigger the breakpoint. */ + setSessionDidTrigger(sessionId: string): void; + /** Gets whether the `triggeredBy` condition has been met in the given sesison ID. */ + getSessionDidTrigger(sessionId: string): boolean; } export interface IFunctionBreakpoint extends IBaseBreakpoint { @@ -652,7 +649,7 @@ export interface IEvaluate { export interface IDebugModel extends ITreeElement { getSession(sessionId: string | undefined, includeInactive?: boolean): IDebugSession | undefined; getSessions(includeInactive?: boolean): IDebugSession[]; - getBreakpoints(filter?: { uri?: uri; originalUri?: uri; lineNumber?: number; column?: number; enabledOnly?: boolean }): ReadonlyArray; + getBreakpoints(filter?: { uri?: uri; originalUri?: uri; lineNumber?: number; column?: number; enabledOnly?: boolean; dependentOnly?: boolean }): ReadonlyArray; areBreakpointsActivated(): boolean; getFunctionBreakpoints(): ReadonlyArray; getDataBreakpoints(): ReadonlyArray; @@ -1152,6 +1149,11 @@ export interface IDebugService { */ sendAllBreakpoints(session?: IDebugSession): Promise; + /** + * Sends breakpoints of the given source to the passed session. + */ + sendBreakpoints(modelUri: uri, sourceModified?: boolean, session?: IDebugSession): Promise; + /** * Adds a new watch expression and evaluates it against the debug adapter. */ diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index bcc5090d082..0f5ff8361ff 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -4,30 +4,30 @@ *--------------------------------------------------------------------------------------------*/ import { distinct } from 'vs/base/common/arrays'; +import { findLastIdx } from 'vs/base/common/arraysFind'; import { DeferredPromise, RunOnceScheduler } from 'vs/base/common/async'; -import { decodeBase64, encodeBase64, VSBuffer } from 'vs/base/common/buffer'; +import { VSBuffer, decodeBase64, encodeBase64 } from 'vs/base/common/buffer'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { stringHash } from 'vs/base/common/hash'; import { Emitter, Event } from 'vs/base/common/event'; +import { stringHash } from 'vs/base/common/hash'; import { Disposable } from 'vs/base/common/lifecycle'; import { mixin } from 'vs/base/common/objects'; +import { autorun } from 'vs/base/common/observable'; import * as resources from 'vs/base/common/resources'; import { isString, isUndefinedOrNull } from 'vs/base/common/types'; import { URI, URI as uri } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IRange, Range } from 'vs/editor/common/core/range'; import * as nls from 'vs/nls'; +import { ILogService } from 'vs/platform/log/common/log'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IEditorPane } from 'vs/workbench/common/editor'; -import { DEBUG_MEMORY_SCHEME, IBaseBreakpoint, IBreakpoint, IBreakpointData, IBreakpointReference, IBreakpointsChangeEvent, IBreakpointUpdateData, IDataBreakpoint, IDebugModel, IDebugSession, IEnablement, IExceptionBreakpoint, IExceptionInfo, IExpression, IExpressionContainer, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryInvalidationEvent, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IScope, IStackFrame, IThread, ITreeElement, MemoryRange, MemoryRangeType, State } from 'vs/workbench/contrib/debug/common/debug'; -import { getUriFromSource, Source, UNKNOWN_SOURCE_LABEL } from 'vs/workbench/contrib/debug/common/debugSource'; +import { DEBUG_MEMORY_SCHEME, IBaseBreakpoint, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IBreakpointsChangeEvent, IDataBreakpoint, IDebugModel, IDebugSession, IEnablement, IExceptionBreakpoint, IExceptionInfo, IExpression, IExpressionContainer, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryInvalidationEvent, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IScope, IStackFrame, IThread, ITreeElement, MemoryRange, MemoryRangeType, State } from 'vs/workbench/contrib/debug/common/debug'; +import { Source, UNKNOWN_SOURCE_LABEL, getUriFromSource } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { ILogService } from 'vs/platform/log/common/log'; -import { autorun } from 'vs/base/common/observable'; -import { findLastIdx } from 'vs/base/common/arraysFind'; interface IDebugProtocolVariableWithContext extends DebugProtocol.Variable { __vscodeVariableMenuContext?: string; @@ -769,7 +769,6 @@ function toBreakpointSessionData(data: DebugProtocol.Breakpoint, capabilities: D export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoint { private sessionData = new Map(); - private sessionHitData = new Map(); protected data: IBreakpointSessionData | undefined; constructor( @@ -788,7 +787,6 @@ export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoi setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void { if (!data) { this.sessionData.delete(sessionId); - this.sessionHitData.delete(sessionId); } else { data.sessionId = sessionId; this.sessionData.set(sessionId, data); @@ -868,6 +866,7 @@ export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoi } export class Breakpoint extends BaseBreakpoint implements IBreakpoint { + private sessionsDidTrigger?: Set; constructor( private readonly _uri: uri, @@ -882,7 +881,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { private readonly uriIdentityService: IUriIdentityService, private readonly logService: ILogService, id = generateUuid(), - public triggeredBy: IBreakpointReference | undefined = undefined + public triggeredBy: string | undefined = undefined ) { super(enabled, hitCondition, condition, logMessage, id); } @@ -891,10 +890,6 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { return this._uri; } - get originalColumn() { - return this._column; - } - get lineNumber(): number { return this.verified && this.data && typeof this.data.line === 'number' ? this.data.line : this._lineNumber; } @@ -979,15 +974,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { result.lineNumber = this._lineNumber; result.column = this._column; result.adapterData = this.adapterData; - - if (this.triggeredBy) { - const wf = Object.create(null); - wf.uri = this.triggeredBy?.uri.toString(); - wf.lineNumber = this.triggeredBy?.lineNumber; - wf.column = this.triggeredBy?.column; - result.waitFor = wf; - } - + result.triggeredBy = this.triggeredBy; return result; } @@ -995,43 +982,38 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { return `${resources.basenameOrAuthority(this.uri)} ${this.lineNumber}`; } + public setSessionDidTrigger(sessionId: string): void { + this.sessionsDidTrigger ??= new Set(); + this.sessionsDidTrigger.add(sessionId); + } + + public getSessionDidTrigger(sessionId: string): boolean { + return !!this.sessionsDidTrigger?.has(sessionId); + } + update(data: IBreakpointUpdateData): void { - if (!isUndefinedOrNull(data.lineNumber)) { + if (data.hasOwnProperty('lineNumber') && !isUndefinedOrNull(data.lineNumber)) { this._lineNumber = data.lineNumber; } - if (!isUndefinedOrNull(data.column)) { + if (data.hasOwnProperty('column')) { this._column = data.column; } - if (!isUndefinedOrNull(data.condition)) { + if (data.hasOwnProperty('condition')) { this.condition = data.condition; } - if (!isUndefinedOrNull(data.hitCondition)) { + if (data.hasOwnProperty('hitCondition')) { this.hitCondition = data.hitCondition; } - if (!isUndefinedOrNull(data.logMessage)) { + if (data.hasOwnProperty('logMessage')) { this.logMessage = data.logMessage; } - if (!isUndefinedOrNull(data.triggeredBy)) { - this.triggeredBy = new BreakpointReference(data.triggeredBy.uri, data.triggeredBy.lineNumber, data.triggeredBy.originalColumn); - } else { - this.triggeredBy = undefined; + if (data.hasOwnProperty('triggeredBy')) { + this.triggeredBy = data.triggeredBy; + this.sessionsDidTrigger = undefined; } } } -export class BreakpointReference implements IBreakpointReference { - constructor(public uri: uri, public lineNumber: number, public column?: number) { } - - matches(bp: IBreakpoint): boolean { - // prefer to store and match the original column as the reference, since - // start-of-line breakpoints can be moved to a more specific location - // within the line, which could ordinarily prevent matching. - return bp.uri.toString() === this.uri?.toString() - && bp.lineNumber === this.lineNumber - && (bp.originalColumn === this.column); - } -} - export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreakpoint { constructor( @@ -1509,11 +1491,7 @@ export class DebugModel extends Disposable implements IDebugModel { addBreakpoints(uri: uri, rawData: IBreakpointData[], fireEvent = true): IBreakpoint[] { const newBreakpoints = rawData.map(rawBp => { - let triggeredBy = undefined; - if (rawBp!.triggeredBy) { - triggeredBy = new BreakpointReference(rawBp.triggeredBy.uri, rawBp.triggeredBy.lineNumber, rawBp.triggeredBy.column); - } - return new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled === false ? false : true, rawBp.condition, rawBp.hitCondition, rawBp.logMessage, undefined, this.textFileService, this.uriIdentityService, this.logService, rawBp.id, triggeredBy); + return new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled === false ? false : true, rawBp.condition, rawBp.hitCondition, rawBp.logMessage, undefined, this.textFileService, this.uriIdentityService, this.logService, rawBp.id, rawBp.triggeredBy); }); this.breakpoints = this.breakpoints.concat(newBreakpoints); this.breakpointsActivated = true; diff --git a/src/vs/workbench/contrib/debug/common/debugStorage.ts b/src/vs/workbench/contrib/debug/common/debugStorage.ts index fbc318d522a..dd267f90358 100644 --- a/src/vs/workbench/contrib/debug/common/debugStorage.ts +++ b/src/vs/workbench/contrib/debug/common/debugStorage.ts @@ -3,15 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from 'vs/base/common/uri'; -import { StorageScope, IStorageService, StorageTarget } from 'vs/platform/storage/common/storage'; -import { ExceptionBreakpoint, Expression, Breakpoint, FunctionBreakpoint, DataBreakpoint, BreakpointReference } from 'vs/workbench/contrib/debug/common/debugModel'; -import { IEvaluate, IExpression, IDebugModel, IBreakpointReference } from 'vs/workbench/contrib/debug/common/debug'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { ILogService } from 'vs/platform/log/common/log'; -import { observableValue } from 'vs/base/common/observable'; import { Disposable } from 'vs/base/common/lifecycle'; +import { observableValue } from 'vs/base/common/observable'; +import { URI } from 'vs/base/common/uri'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IDebugModel, IEvaluate, IExpression } from 'vs/workbench/contrib/debug/common/debug'; +import { Breakpoint, DataBreakpoint, ExceptionBreakpoint, Expression, FunctionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint'; const DEBUG_FUNCTION_BREAKPOINTS_KEY = 'debug.functionbreakpoint'; @@ -66,21 +66,13 @@ export class DebugStorage extends Disposable { let result: Breakpoint[] | undefined; try { result = JSON.parse(this.storageService.get(DEBUG_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((breakpoint: any) => { - return new Breakpoint(URI.parse(breakpoint.uri.external || breakpoint.source.uri.external), breakpoint.lineNumber, breakpoint.column, breakpoint.enabled, breakpoint.condition, breakpoint.hitCondition, breakpoint.logMessage, breakpoint.adapterData, this.textFileService, this.uriIdentityService, this.logService, breakpoint.id, - this.parseBreakpointRef(breakpoint)); + return new Breakpoint(URI.parse(breakpoint.uri.external || breakpoint.source.uri.external), breakpoint.lineNumber, breakpoint.column, breakpoint.enabled, breakpoint.condition, breakpoint.hitCondition, breakpoint.logMessage, breakpoint.adapterData, this.textFileService, this.uriIdentityService, this.logService, breakpoint.id, breakpoint.triggeredBy); }); } catch (e) { } return result || []; } - private parseBreakpointRef(breakpoint: any): IBreakpointReference | undefined { - if (breakpoint.waitFor) { - return new BreakpointReference(URI.parse(breakpoint.waitFor.uri), breakpoint.waitFor.lineNumber, breakpoint.waitFor.column); - } - return undefined; - } - private loadFunctionBreakpoints(): FunctionBreakpoint[] { let result: FunctionBreakpoint[] | undefined; try { diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index 0aff04fe30a..b655b4b6603 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -350,38 +350,38 @@ suite('Debug - Breakpoints', () => { const breakpoints = model.getBreakpoints(); const ls = new MockLabelService(); - let result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[0], ls); + let result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[0], ls, model); assert.strictEqual(result.message, 'Condition: x > 5'); assert.strictEqual(result.icon.id, 'debug-breakpoint-conditional'); - result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[1], ls); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[1], ls, model); assert.strictEqual(result.message, 'Disabled Breakpoint'); assert.strictEqual(result.icon.id, 'debug-breakpoint-disabled'); - result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[2], ls); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[2], ls, model); assert.strictEqual(result.message, 'Log Message: hello'); assert.strictEqual(result.icon.id, 'debug-breakpoint-log'); - result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[3], ls); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[3], ls, model); assert.strictEqual(result.message, 'Hit Count: 12'); assert.strictEqual(result.icon.id, 'debug-breakpoint-conditional'); - result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[4], ls); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[4], ls, model); assert.strictEqual(result.message, ls.getUriLabel(breakpoints[4].uri)); assert.strictEqual(result.icon.id, 'debug-breakpoint'); - result = getBreakpointMessageAndIcon(State.Stopped, false, breakpoints[2], ls); + result = getBreakpointMessageAndIcon(State.Stopped, false, breakpoints[2], ls, model); assert.strictEqual(result.message, 'Disabled Logpoint'); assert.strictEqual(result.icon.id, 'debug-breakpoint-log-disabled'); model.addDataBreakpoint('label', 'id', true, ['read'], 'read'); const dataBreakpoints = model.getDataBreakpoints(); - result = getBreakpointMessageAndIcon(State.Stopped, true, dataBreakpoints[0], ls); + result = getBreakpointMessageAndIcon(State.Stopped, true, dataBreakpoints[0], ls, model); assert.strictEqual(result.message, 'Data Breakpoint'); assert.strictEqual(result.icon.id, 'debug-breakpoint-data'); const functionBreakpoint = model.addFunctionBreakpoint('foo', '1'); - result = getBreakpointMessageAndIcon(State.Stopped, true, functionBreakpoint, ls); + result = getBreakpointMessageAndIcon(State.Stopped, true, functionBreakpoint, ls, model); assert.strictEqual(result.message, 'Function Breakpoint'); assert.strictEqual(result.icon.id, 'debug-breakpoint-function'); @@ -392,15 +392,15 @@ suite('Debug - Breakpoints', () => { data.set(functionBreakpoint.getId(), { verified: true }); model.setBreakpointSessionData('mocksessionid', { supportsFunctionBreakpoints: false, supportsDataBreakpoints: true, supportsLogPoints: true }, data); - result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[0], ls); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[0], ls, model); assert.strictEqual(result.message, 'Unverified Breakpoint'); assert.strictEqual(result.icon.id, 'debug-breakpoint-unverified'); - result = getBreakpointMessageAndIcon(State.Stopped, true, functionBreakpoint, ls); + result = getBreakpointMessageAndIcon(State.Stopped, true, functionBreakpoint, ls, model); assert.strictEqual(result.message, 'Function breakpoints not supported by this debug type'); assert.strictEqual(result.icon.id, 'debug-breakpoint-function-unverified'); - result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[2], ls); + result = getBreakpointMessageAndIcon(State.Stopped, true, breakpoints[2], ls, model); assert.strictEqual(result.message, 'Log Message: hello, world'); assert.strictEqual(result.icon.id, 'debug-breakpoint-log'); }); @@ -421,7 +421,9 @@ suite('Debug - Breakpoints', () => { const breakpoints = model.getBreakpoints(); const instantiationService = new TestInstantiationService(); - instantiationService.stub(IDebugService, new MockDebugService()); + const debugService = new MockDebugService(); + debugService.getModel = () => model; + instantiationService.stub(IDebugService, debugService); instantiationService.stub(ILabelService, new MockLabelService()); instantiationService.stub(ILanguageService, disposables.add(new LanguageService())); let decorations = instantiationService.invokeFunction(accessor => createBreakpointDecorations(accessor, textModel, breakpoints, State.Running, true, true)); diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index da4b7f0dceb..5dca0f132e5 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -61,6 +61,10 @@ export class MockDebugService implements IDebugService { throw new Error('not implemented'); } + sendBreakpoints(modelUri: uri, sourceModified?: boolean | undefined, session?: IDebugSession | undefined): Promise { + throw new Error('not implemented'); + } + addBreakpoints(uri: uri, rawBreakpoints: IBreakpointData[]): Promise { throw new Error('not implemented'); } From 8f6498a2d81c914bea171a93eb8e5fed5a135606 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 8 Jan 2024 06:58:58 +0530 Subject: [PATCH 0108/1897] fix #174883 (#201959) --- .../workbench/contrib/extensions/browser/extensionEditor.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index a3b0c2646ce..c0002fd0df1 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -604,9 +604,7 @@ export class ExtensionEditor extends EditorPane { this.currentIdentifier = extension.identifier.id; } - if (extension.hasReadme()) { - template.navbar.push(ExtensionEditorTab.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file")); - } + template.navbar.push(ExtensionEditorTab.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file")); if (manifest && manifest.contributes) { template.navbar.push(ExtensionEditorTab.Contributions, localize('contributions', "Feature Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension")); } From 4caba0473af34a2b128cedc46f6b13316aae9226 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 8 Jan 2024 06:59:19 +0530 Subject: [PATCH 0109/1897] fix #147668 (#201961) --- .../contrib/extensions/browser/extensionsActions.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index c4bc8542d3d..853426bcdd4 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -719,7 +719,8 @@ export class UninstallAction extends ExtensionAction { private static readonly UnInstallingClass = `${ExtensionAction.LABEL_ACTION_CLASS} uninstall uninstalling`; constructor( - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IDialogService private readonly dialogService: IDialogService ) { super('extensions.uninstall', UninstallAction.UninstallLabel, UninstallAction.UninstallClass, false); this.update(); @@ -763,9 +764,12 @@ export class UninstallAction extends ExtensionAction { } alert(localize('uninstallExtensionStart', "Uninstalling extension {0} started.", this.extension.displayName)); - return this.extensionsWorkbenchService.uninstall(this.extension).then(() => { + try { + await this.extensionsWorkbenchService.uninstall(this.extension); alert(localize('uninstallExtensionComplete', "Please reload Visual Studio Code to complete the uninstallation of the extension {0}.", this.extension!.displayName)); - }); + } catch (error) { + this.dialogService.error(getErrorMessage(error)); + } } } From 0a90cb8a06af8472673147bd90ec7edd76aa63a9 Mon Sep 17 00:00:00 2001 From: Anthony Stewart <150152+a-stewart@users.noreply.github.com> Date: Mon, 8 Jan 2024 09:14:01 +0100 Subject: [PATCH 0110/1897] Log `extensionLocation` not `location` in error message of `getCustomBuiltinExtensionsFromLocations` (#200748) Log extensionLocation not location in error message of getCustomBuiltinExtensionsFromLocations --- .../extensionManagement/browser/webExtensionsScannerService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts index 6d129f0c111..de40f04aeaf 100644 --- a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts @@ -239,7 +239,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten this.logService.info(`Skipping invalid additional builtin extension ${webExtension.identifier.id}`); } } catch (error) { - this.logService.info(`Error while fetching the additional builtin extension ${location.toString()}.`, getErrorMessage(error)); + this.logService.info(`Error while fetching the additional builtin extension ${extensionLocation.toString()}.`, getErrorMessage(error)); } })); return result; From cfebdd863a219cdaa571684aae229dc9d7f88ea9 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 8 Jan 2024 09:48:51 +0100 Subject: [PATCH 0111/1897] Git - add open stash command (#201970) * Initial implementation * Add button to quick pick * Improve stash picker * Remove quick pick buttons --- extensions/git/package.json | 10 ++++++ extensions/git/package.nls.json | 1 + extensions/git/src/commands.ts | 30 ++++++++++++++-- extensions/git/src/git.ts | 60 +++++++++++++++++++++++++++----- extensions/git/src/repository.ts | 6 +++- 5 files changed, 96 insertions(+), 11 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 8744035090e..fbdcb00b462 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -674,6 +674,12 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.stashOpen", + "title": "%command.stashOpen%", + "category": "Git", + "enablement": "!operationInProgress" + }, { "command": "git.timeline.openDiff", "title": "%command.timelineOpenDiff%", @@ -1257,6 +1263,10 @@ "command": "git.openRepositoriesInParentFolders", "when": "config.git.enabled && !git.missing && git.parentRepositoryCount != 0" }, + { + "command": "git.openStash", + "when": "config.git.enabled && !git.missing && config.multiDiffEditor.experimental.enabled" + }, { "command": "git.viewChanges", "when": "false" diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 9b053671d0f..3c6a721b804 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -104,6 +104,7 @@ "command.stashApplyLatest": "Apply Latest Stash", "command.stashDrop": "Drop Stash...", "command.stashDropAll": "Drop All Stashes...", + "command.stashOpen": "Open Stash...", "command.timelineOpenDiff": "Open Changes", "command.timelineOpenCommit": "Open Commit", "command.timelineCopyCommitId": "Copy Commit ID", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 50a9fbbb22f..78b52187a50 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3535,6 +3535,31 @@ export class CommandCenter { await repository.dropStash(); } + @command('git.stashOpen', { repository: true }) + async stashOpen(repository: Repository): Promise { + const placeHolder = l10n.t('Pick a stash to open'); + const stash = await this.pickStash(repository, placeHolder); + + if (!stash) { + return; + } + + const stashFiles = await repository.showStash(stash.index); + + if (!stashFiles || stashFiles.length === 0) { + return; + } + + const args: [Uri, Uri | undefined, Uri | undefined][] = []; + + for (const file of stashFiles) { + const fileUri = Uri.file(path.join(repository.root, file)); + args.push([fileUri, toGitUri(fileUri, `stash@{${stash.index}}`), fileUri]); + } + + commands.executeCommand('vscode.changes', `Git Stash #${stash.index}: ${stash.description}`, args); + } + private async pickStash(repository: Repository, placeHolder: string): Promise { const stashes = await repository.getStashes(); @@ -3543,9 +3568,10 @@ export class CommandCenter { return; } - const picks = stashes.map(stash => ({ label: `#${stash.index}: ${stash.description}`, description: '', details: '', stash })); + const picks = stashes.map(stash => ({ label: `#${stash.index}: ${stash.description}`, description: stash.branchName, stash })); const result = await window.showQuickPick(picks, { placeHolder }); - return result && result.stash; + + return result?.stash; } @command('git.timeline.openDiff', { repository: false }) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 6f768d36ea0..2c8be161784 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -37,6 +37,7 @@ export interface IFileStatus { export interface Stash { index: number; description: string; + branchName?: string; } interface MutableRemote extends Remote { @@ -964,6 +965,38 @@ export function parseLsFiles(raw: string): LsFilesElement[] { .map(([, mode, object, stage, file]) => ({ mode, object, stage, file })); } +function parseGitStashes(raw: string): Stash[] { + const result: Stash[] = []; + const regex = /^stash@{(\d+)}:(.+)$/; + const descriptionRegex = /(WIP\s)*on([^:]+):(.*)$/i; + + for (const stash of raw.split('\n').filter(s => !!s)) { + // Extract index and description + const match = regex.exec(stash); + if (!match) { + continue; + } + + const [, index, description] = match; + + // Extract branch name from description + const descriptionMatch = descriptionRegex.exec(description); + if (!descriptionMatch) { + result.push({ index: parseInt(index), description: description.trim() }); + continue; + } + + const [, wip, branchName, message] = descriptionMatch; + result.push({ + index: parseInt(index), + description: wip ? `WIP (${message.trim()})` : message.trim(), + branchName: branchName.trim() + }); + } + + return result; +} + export interface PullOptions { unshallow?: boolean; tags?: boolean; @@ -2114,6 +2147,24 @@ export class Repository { } } + async showStash(index: number): Promise { + const args = ['stash', 'show', `stash@{${index}}`, '--name-only']; + + try { + const result = await this.exec(args); + + return result.stdout.trim() + .split('\n') + .filter(line => !!line); + } catch (err) { + if (/No stash found/.test(err.stderr || '')) { + return undefined; + } + + throw err; + } + } + async getStatus(opts?: { limit?: number; ignoreSubmodules?: boolean; similarityThreshold?: number; untrackedChanges?: 'mixed' | 'separate' | 'hidden'; cancellationToken?: CancellationToken }): Promise<{ status: IFileStatus[]; statusLength: number; didHitLimit: boolean }> { if (opts?.cancellationToken && opts?.cancellationToken.isCancellationRequested) { throw new CancellationError(); @@ -2385,14 +2436,7 @@ export class Repository { async getStashes(): Promise { const result = await this.exec(['stash', 'list']); - const regex = /^stash@{(\d+)}:(.+)$/; - const rawStashes = result.stdout.trim().split('\n') - .filter(b => !!b) - .map(line => regex.exec(line) as RegExpExecArray) - .filter(g => !!g) - .map(([, index, description]: RegExpExecArray) => ({ index: parseInt(index), description })); - - return rawStashes; + return parseGitStashes(result.stdout.trim()); } async getRemotes(): Promise { diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 4855d754240..42f90428a2c 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1931,7 +1931,7 @@ export class Repository implements Disposable { } async getStashes(): Promise { - return await this.repository.getStashes(); + return this.run(Operation.Stash, () => this.repository.getStashes()); } async createStash(message?: string, includeUntracked?: boolean, staged?: boolean): Promise { @@ -1958,6 +1958,10 @@ export class Repository implements Disposable { return await this.run(Operation.Stash, () => this.repository.applyStash(index)); } + async showStash(index: number): Promise { + return await this.run(Operation.Stash, () => this.repository.showStash(index)); + } + async getCommitTemplate(): Promise { return await this.run(Operation.GetCommitTemplate, async () => this.repository.getCommitTemplate()); } From f8616b9a2bb54bba3a9de9ebd480601a50f83155 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 8 Jan 2024 10:59:49 +0100 Subject: [PATCH 0112/1897] Git - rename "Open Stash" to "Preview Stash" (#201973) * Git - rename "Open Stash" to "Preview Stash" * Add Preview Stash command to the "..." menu --- extensions/git/package.json | 11 ++++++++--- extensions/git/package.nls.json | 2 +- extensions/git/src/commands.ts | 6 +++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index fbdcb00b462..3e4137d36e4 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -675,8 +675,8 @@ "enablement": "!operationInProgress" }, { - "command": "git.stashOpen", - "title": "%command.stashOpen%", + "command": "git.stashPreview", + "title": "%command.stashPreview%", "category": "Git", "enablement": "!operationInProgress" }, @@ -1264,7 +1264,7 @@ "when": "config.git.enabled && !git.missing && git.parentRepositoryCount != 0" }, { - "command": "git.openStash", + "command": "git.stashPreview", "when": "config.git.enabled && !git.missing && config.multiDiffEditor.experimental.enabled" }, { @@ -2103,6 +2103,11 @@ { "command": "git.stashDropAll", "group": "4_drop@2" + }, + { + "command": "git.stashPreview", + "when": "config.multiDiffEditor.experimental.enabled", + "group": "5_preview@1" } ], "git.tags": [ diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 3c6a721b804..627b6195828 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -104,7 +104,7 @@ "command.stashApplyLatest": "Apply Latest Stash", "command.stashDrop": "Drop Stash...", "command.stashDropAll": "Drop All Stashes...", - "command.stashOpen": "Open Stash...", + "command.stashPreview": "Preview Stash...", "command.timelineOpenDiff": "Open Changes", "command.timelineOpenCommit": "Open Commit", "command.timelineCopyCommitId": "Copy Commit ID", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 78b52187a50..892e3698e56 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3535,9 +3535,9 @@ export class CommandCenter { await repository.dropStash(); } - @command('git.stashOpen', { repository: true }) - async stashOpen(repository: Repository): Promise { - const placeHolder = l10n.t('Pick a stash to open'); + @command('git.stashPreview', { repository: true }) + async stashPreview(repository: Repository): Promise { + const placeHolder = l10n.t('Pick a stash to preview'); const stash = await this.pickStash(repository, placeHolder); if (!stash) { From 48a49a3b23c251e21ed7cea9e676b5e31983c4db Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 8 Jan 2024 15:31:08 +0530 Subject: [PATCH 0113/1897] log error message (#201974) --- .../common/abstractExtensionManagementService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index c733a5984f3..097f3c50cd1 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -359,7 +359,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl error }); } - this.logService.error('Error while installing the extension:', task.identifier.id); + this.logService.error('Error while installing the extension', task.identifier.id, getErrorMessage(error)); throw error; } finally { extensionsToInstallMap.delete(task.identifier.id.toLowerCase()); } })); From 2443fd16132eb1611873d8e0abeae7671bb20d07 Mon Sep 17 00:00:00 2001 From: zWing <371657110@qq.com> Date: Mon, 8 Jan 2024 18:06:09 +0800 Subject: [PATCH 0114/1897] fix registerProfileContentHandler typos (#183197) --- src/vs/workbench/api/common/extHost.api.impl.ts | 2 +- src/vs/workbench/api/common/extHostProfileContentHandler.ts | 2 +- 2 files 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 f39b6920640..82fe905ff78 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -877,7 +877,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, registerProfileContentHandler(id: string, handler: vscode.ProfileContentHandler) { checkProposedApiEnabled(extension, 'profileContentHandlers'); - return extHostProfileContentHandlers.registrProfileContentHandler(extension, id, handler); + return extHostProfileContentHandlers.registerProfileContentHandler(extension, id, handler); }, registerQuickDiffProvider(selector: vscode.DocumentSelector, quickDiffProvider: vscode.QuickDiffProvider, label: string, rootUri?: vscode.Uri): vscode.Disposable { checkProposedApiEnabled(extension, 'quickDiffProvider'); diff --git a/src/vs/workbench/api/common/extHostProfileContentHandler.ts b/src/vs/workbench/api/common/extHostProfileContentHandler.ts index 4ebc0833f2d..19c2fb0d9b3 100644 --- a/src/vs/workbench/api/common/extHostProfileContentHandler.ts +++ b/src/vs/workbench/api/common/extHostProfileContentHandler.ts @@ -26,7 +26,7 @@ export class ExtHostProfileContentHandlers implements ExtHostProfileContentHandl this.proxy = mainContext.getProxy(MainContext.MainThreadProfileContentHandlers); } - registrProfileContentHandler( + registerProfileContentHandler( extension: IExtensionDescription, id: string, handler: vscode.ProfileContentHandler, From 6476d4b87f5426e2e1773cc4149791f825350478 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Mon, 8 Jan 2024 11:34:05 +0100 Subject: [PATCH 0115/1897] Document reason for using sha1 (resolves CodeQL warning) (#201979) --- src/vs/server/node/remoteExtensionHostAgentServer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/server/node/remoteExtensionHostAgentServer.ts b/src/vs/server/node/remoteExtensionHostAgentServer.ts index d1a0f51e783..a90d28e82cf 100644 --- a/src/vs/server/node/remoteExtensionHostAgentServer.ts +++ b/src/vs/server/node/remoteExtensionHostAgentServer.ts @@ -204,7 +204,7 @@ class RemoteExtensionHostAgentServer extends Disposable implements IServerAPI { // https://tools.ietf.org/html/rfc6455#section-4 const requestNonce = req.headers['sec-websocket-key']; - const hash = crypto.createHash('sha1'); + const hash = crypto.createHash('sha1');// CodeQL [SM04514] SHA1 must be used here to respect the WebSocket protocol specification hash.update(requestNonce + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'); const responseNonce = hash.digest('base64'); From 9bf447edf420a9d8e1d4670f865c2be1c2f7ba99 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 8 Jan 2024 11:38:47 +0100 Subject: [PATCH 0116/1897] Explorer Context menu fix --- src/vs/base/browser/ui/list/listWidget.ts | 9 +- src/vs/base/browser/ui/tree/abstractTree.ts | 163 +++++++++++++++--- src/vs/base/browser/ui/tree/asyncDataTree.ts | 12 +- .../files/browser/views/explorerView.ts | 6 +- 4 files changed, 157 insertions(+), 33 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 02db4869cfe..a2cf3493c34 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -292,6 +292,10 @@ export function isStickyScrollElement(e: HTMLElement): boolean { return isListElementDescendantOfClass(e, 'monaco-tree-sticky-row'); } +export function isStickyScrollContainer(e: HTMLElement): boolean { + return e.classList.contains('monaco-tree-sticky-container'); +} + export function isButton(e: HTMLElement): boolean { if ((e.tagName === 'A' && e.classList.contains('monaco-button')) || (e.tagName === 'DIV' && e.classList.contains('monaco-button-dropdown'))) { @@ -727,7 +731,7 @@ export class MouseController implements IDisposable { return this.isSelectionSingleChangeEvent(event) || this.isSelectionRangeChangeEvent(event); } - private onMouseDown(e: IListMouseEvent | IListTouchEvent): void { + protected onMouseDown(e: IListMouseEvent | IListTouchEvent): void { if (isMonacoEditor(e.browserEvent.target as HTMLElement)) { return; } @@ -737,11 +741,10 @@ export class MouseController implements IDisposable { } } - private onContextMenu(e: IListContextMenuEvent): void { + protected onContextMenu(e: IListContextMenuEvent): void { if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) { return; } - const focus = typeof e.index === 'undefined' ? [] : [e.index]; this.list.setFocus(focus, e.browserEvent); } diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 711f0777d45..c3d88d446f8 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -4,16 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { IDragAndDropData } from 'vs/base/browser/dnd'; -import { $, append, clearNode, createStyleSheet, getWindow, h, hasParentWithClass, isActiveElement, isPointerEvent, asCssValueWithDefault, isKeyboardEvent } from 'vs/base/browser/dom'; +import { $, append, clearNode, createStyleSheet, getWindow, h, hasParentWithClass, isActiveElement, asCssValueWithDefault, isKeyboardEvent } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { FindInput } from 'vs/base/browser/ui/findinput/findInput'; import { IInputBoxStyles, IMessage, MessageType, unthemedInboxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; -import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListTouchEvent, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; -import { IListAccessibilityProvider, IListOptions, IListStyles, isActionItem, isButton, isInputElement, isMonacoCustomToggle, isMonacoEditor, isStickyScrollElement, List, MouseController, TypeNavigationMode } from 'vs/base/browser/ui/list/listWidget'; +import { IListAccessibilityProvider, IListOptions, IListStyles, isActionItem, isButton, isInputElement, isMonacoCustomToggle, isMonacoEditor, isStickyScrollContainer, isStickyScrollElement, List, MouseController, TypeNavigationMode } from 'vs/base/browser/ui/list/listWidget'; import { IToggleStyles, Toggle, unthemedToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel'; import { ICollapseStateChangeEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeEvent, ITreeFilter, ITreeModel, ITreeModelSpliceEvent, ITreeMouseEvent, ITreeNavigator, ITreeNode, ITreeRenderer, TreeDragOverBubble, TreeError, TreeFilterResult, TreeMouseEventTarget, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; @@ -1459,6 +1459,15 @@ class StickyScrollController extends Disposable { return this._widget.getFocus(); } + domFocus(): void { + this._widget.domFocus(); + } + + // If sticky scroll was the last focused part in the tree + focusedLast(): boolean { + return this._widget.focusedLast(); + } + updateOptions(optionsUpdate: IAbstractTreeOptionsUpdate = {}): void { const validatedOptions = this.validateStickySettings(optionsUpdate); if (this.stickyScrollMaxItemCount !== validatedOptions.stickyScrollMaxItemCount) { @@ -1648,6 +1657,14 @@ class StickyScrollWidget implements IDisposable { return this.stickyScrollFocus.getFocus(); } + domFocus(): void { + this.stickyScrollFocus.domFocus(); + } + + focusedLast(): boolean { + return this.stickyScrollFocus.focusedLast(); + } + dispose(): void { this.stickyScrollFocus.dispose(); this._previousState?.dispose(); @@ -1684,20 +1701,38 @@ class StickyScrollFocus extends Disposable { this.container.addEventListener('focus', () => this.onFocus()); this.container.addEventListener('blur', () => this.onBlur()); + this._register(this.view.onDidFocus(() => this.toggleStickyScrollFocused(false))); this._register(this.view.onKeyDown((e) => this.onKeyDown(e))); + this._register(this.view.onMouseDown((e) => this.onMouseDown(e))); this._register(this.view.onContextMenu((e) => this.handleContextMenu(e))); } private handleContextMenu(e: IListContextMenuEvent>): void { - // We only handle context menu triggers by a keyboardevent here - // Context menu triggers by mouse are handled by the list itself - const target = e.browserEvent.target as HTMLElement; - const isStickyScroll = target.classList.contains('monaco-tree-sticky-container') && !isPointerEvent(e); - - if (!this.domHasFocus && !isStickyScroll) { + if (!isStickyScrollContainer(target) && !isStickyScrollElement(target)) { + if (this.focusedLast()) { + this.view.domFocus(); + } return; } + + // The list handles the context menu triggered by a mouse event + // In that case only set the focus of the element clicked and leave the rest to the list to handle + if (!isKeyboardEvent(e.browserEvent)) { + if (!this.state) { + throw new Error('Context menu should not be triggered when state is undefined'); + } + + const stickyIndex = this.state.stickyNodes.findIndex(stickyNode => stickyNode.node.element === e.element?.element); + + if (stickyIndex === -1) { + throw new Error('Context menu should not be triggered when element is not in sticky scroll widget'); + } + this.toggleStickyScrollFocused(true); + this.setFocus(stickyIndex); + return; + } + if (!this.state || this.focusedIndex < 0) { throw new Error('Context menu key should not be triggered when focus is not in sticky scroll widget'); } @@ -1713,7 +1748,7 @@ class StickyScrollFocus extends Disposable { if (this.domHasFocus && this.state) { // Move up if (e.key === 'ArrowUp') { - this.setElementFocused(Math.max(0, this.focusedIndex - 1)); + this.setFocusedElement(Math.max(0, this.focusedIndex - 1)); e.preventDefault(); e.stopPropagation(); } @@ -1725,7 +1760,7 @@ class StickyScrollFocus extends Disposable { this.view.setFocus([nodeIndexToFocus]); this.scrollNodeUnderWidget(nodeIndexToFocus, this.state); } else { - this.setElementFocused(this.focusedIndex + 1); + this.setFocusedElement(this.focusedIndex + 1); } e.preventDefault(); e.stopPropagation(); @@ -1733,7 +1768,26 @@ class StickyScrollFocus extends Disposable { } } + private onMouseDown(e: IListMouseEvent>): void { + const target = e.browserEvent.target as HTMLElement; + if (!isStickyScrollContainer(target) && !isStickyScrollElement(target)) { + return; + } + + e.browserEvent.preventDefault(); + e.browserEvent.stopPropagation(); + + this.container.focus(); + } + updateElements(elements: HTMLElement[], state: StickyScrollState | undefined): void { + if (state && state.count === 0) { + throw new Error('Sticky scroll state must be undefined when there are no sticky nodes'); + } + if (state && state.count !== elements.length) { + throw new Error('Sticky scroll focus received illigel state'); + } + const previousFocusedIndex = this.focusedIndex; this.removeFocus(); @@ -1747,11 +1801,16 @@ class StickyScrollFocus extends Disposable { this.setFocus(newFocusedIndex); } else { this.removeFocus(); - this.view.domFocus(); + this.toggleStickyScrollFocused(false); + if (this.domHasFocus) { + this.view.domFocus(); + } } } - setElementFocused(stickyIndex: number): void { + private setFocusedElement(stickyIndex: number): void { + // doesn't imply that the widget has (or will have) focus + const state = this.state; if (!state) { throw new Error('Cannot set focus when state is undefined'); @@ -1786,6 +1845,21 @@ class StickyScrollFocus extends Disposable { return this.state.stickyNodes[this.focusedIndex].node.element; } + domFocus(): void { + if (!this.state) { + throw new Error('Cannot focus when state is undefined'); + } + + this.container.focus(); + } + + focusedLast(): boolean { + if (!this.state) { + return false; + } + return this.view.getHTMLElement().classList.contains('sticky-scroll-focused'); + } + private removeFocus(): void { if (this.focusedIndex === -1) { return; @@ -1819,12 +1893,17 @@ class StickyScrollFocus extends Disposable { element.classList.toggle('focused', focused); } + private toggleStickyScrollFocused(focused: boolean) { + // Weather the last focus in the view was sticky scroll and not the list + this.view.getHTMLElement().classList.toggle('sticky-scroll-focused', focused); + } + private onFocus(): void { if (!this.state || this.elements.length === 0) { throw new Error('Cannot focus when state is undefined or elements are empty'); } - this.domHasFocus = true; + this.toggleStickyScrollFocused(true); if (this.focusedIndex === -1) { this.setFocus(0); } @@ -1835,6 +1914,7 @@ class StickyScrollFocus extends Disposable { } override dispose(): void { + this.toggleStickyScrollFocused(false); this._onDidChangeState.fire(false); super.dispose(); } @@ -1859,8 +1939,7 @@ function asTreeMouseEvent(event: IListMouseEvent>): ITreeMo } function asTreeContextMenuEvent(event: IListContextMenuEvent>): ITreeContextMenuEvent { - const target = event.browserEvent.target as HTMLElement; - const isStickyScroll = target.classList.contains('monaco-tree-sticky-container'); + const isStickyScroll = isStickyScrollContainer(event.browserEvent.target as HTMLElement); return { element: event.element ? event.element.element : null, @@ -2130,6 +2209,23 @@ class TreeNodeListMouseController extends MouseController< super.onDoubleClick(e); } + + // to make sure dom focus is not stolen (for example with context menu) + protected override onMouseDown(e: IListMouseEvent> | IListTouchEvent>): void { + const target = e.browserEvent.target as HTMLElement; + if (!isStickyScrollContainer(target) && !isStickyScrollElement(target)) { + super.onMouseDown(e); + return; + } + } + + protected override onContextMenu(e: IListContextMenuEvent>): void { + const target = e.browserEvent.target as HTMLElement; + if (!isStickyScrollContainer(target) && !isStickyScrollElement(target)) { + super.onContextMenu(e); + return; + } + } } interface ITreeNodeListOptions extends IListOptions> { @@ -2227,6 +2323,11 @@ class TreeNodeList extends List> } } +export const enum AbstractTreePart { + Tree, + StickyScroll, +} + export abstract class AbstractTree implements IDisposable { protected view: TreeNodeList; @@ -2514,7 +2615,11 @@ export abstract class AbstractTree implements IDisposable } domFocus(): void { - this.view.domFocus(); + if (this.stickyScrollController?.focusedLast()) { + this.stickyScrollController.domFocus(); + } else { + this.view.domFocus(); + } } isDOMFocused(): boolean { @@ -2546,20 +2651,23 @@ export abstract class AbstractTree implements IDisposable // Sticky Scroll Focus if (styles.listFocusForeground) { - content.push(`.monaco-list${suffix} .monaco-scrollable-element .monaco-tree-sticky-container:focus .monaco-list-row.focused { color: ${styles.listFocusForeground}; }`); - content.push(`.monaco-list${suffix}:focus .monaco-scrollable-element .monaco-tree-sticky-container .monaco-list-row.focused { color: inherit; }`); + content.push(`.monaco-list${suffix}.sticky-scroll-focused .monaco-scrollable-element .monaco-tree-sticky-container:focus .monaco-list-row.focused { color: ${styles.listFocusForeground}; }`); + content.push(`.monaco-list${suffix}:not(.sticky-scroll-focused) .monaco-scrollable-element .monaco-tree-sticky-container .monaco-list-row.focused { color: inherit; }`); } // Sticky Scroll Focus Outlines const focusAndSelectionOutline = asCssValueWithDefault(styles.listFocusAndSelectionOutline, asCssValueWithDefault(styles.listSelectionOutline, styles.listFocusOutline ?? '')); if (focusAndSelectionOutline) { // default: listFocusOutline - content.push(`.monaco-list${suffix} .monaco-scrollable-element .monaco-tree-sticky-container:focus .monaco-list-row.focused.selected { outline: 1px solid ${focusAndSelectionOutline}; outline-offset: -1px;}`); - content.push(`.monaco-list${suffix}:focus .monaco-scrollable-element .monaco-tree-sticky-container .monaco-list-row.focused.selected { outline: inherit;}`); + content.push(`.monaco-list${suffix}.sticky-scroll-focused .monaco-scrollable-element .monaco-tree-sticky-container:focus .monaco-list-row.focused.selected { outline: 1px solid ${focusAndSelectionOutline}; outline-offset: -1px;}`); + content.push(`.monaco-list${suffix}:not(.sticky-scroll-focused) .monaco-scrollable-element .monaco-tree-sticky-container .monaco-list-row.focused.selected { outline: inherit;}`); } if (styles.listFocusOutline) { // default: set - content.push(`.monaco-list${suffix} .monaco-scrollable-element .monaco-tree-sticky-container:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`); - content.push(`.monaco-list${suffix}:focus .monaco-scrollable-element .monaco-tree-sticky-container .monaco-list-row.focused { outline: inherit; }`); + content.push(`.monaco-list${suffix}.sticky-scroll-focused .monaco-scrollable-element .monaco-tree-sticky-container:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`); + content.push(`.monaco-list${suffix}:not(.sticky-scroll-focused) .monaco-scrollable-element .monaco-tree-sticky-container .monaco-list-row.focused { outline: inherit; }`); + + content.push(`.monaco-workbench.context-menu-visible .monaco-list${suffix}.last-focused.sticky-scroll-focused .monaco-list-rows .monaco-list-row.focused { outline: inherit; }`); + content.push(`.monaco-workbench.context-menu-visible .monaco-list${suffix}.last-focused:not(.sticky-scroll-focused) .monaco-tree-sticky-container .monaco-list-rows .monaco-list-row.focused { outline: inherit; }`); } this.styleElement.textContent = content.join('\n'); @@ -2709,8 +2817,13 @@ export abstract class AbstractTree implements IDisposable return this.focus.get(); } - getStickyScrollFocus(): T | undefined { - return this.stickyScrollController?.getFocus(); + getStickyScrollFocus(): T[] { + const focus = this.stickyScrollController?.getFocus(); + return focus ? [focus] : []; + } + + getFocusedPart(): AbstractTreePart { + return this.stickyScrollController?.focusedLast() ? AbstractTreePart.StickyScroll : AbstractTreePart.Tree; } reveal(location: TRef, relativeTop?: number): void { diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 97ad4fd0853..e629b4697bd 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -7,7 +7,7 @@ import { IDragAndDropData } from 'vs/base/browser/dnd'; import { IIdentityProvider, IListDragAndDrop, IListDragOverReaction, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; -import { ComposedTreeDelegate, TreeFindMode as TreeFindMode, IAbstractTreeOptions, IAbstractTreeOptionsUpdate, TreeFindMatchType } from 'vs/base/browser/ui/tree/abstractTree'; +import { ComposedTreeDelegate, TreeFindMode as TreeFindMode, IAbstractTreeOptions, IAbstractTreeOptionsUpdate, TreeFindMatchType, AbstractTreePart } from 'vs/base/browser/ui/tree/abstractTree'; import { ICompressedTreeElement, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel'; import { CompressibleObjectTree, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions, ICompressibleTreeRenderer, IObjectTreeOptions, IObjectTreeSetChildrenOptions, ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; @@ -758,9 +758,13 @@ export class AsyncDataTree implements IDisposable return nodes.map(n => n!.element as T); } - getStickyScrollFocus(): T | undefined { - const node = this.tree.getStickyScrollFocus(); - return node?.element as T; + getStickyScrollFocus(): T[] { + const nodes = this.tree.getStickyScrollFocus(); + return nodes.map(n => n!.element as T); + } + + getFocusedPart(): AbstractTreePart { + return this.tree.getFocusedPart(); } reveal(element: T, relativeTop?: number): void { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 815c79a9c67..1e65133adcd 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -54,6 +54,7 @@ import { IEditorResolverService } from 'vs/workbench/services/editor/common/edit import { EditorOpenSource } from 'vs/platform/editor/common/editor'; import { ResourceMap } from 'vs/base/common/map'; import { isInputElement } from 'vs/base/browser/ui/list/listWidget'; +import { AbstractTreePart } from 'vs/base/browser/ui/tree/abstractTree'; function hasExpandedRootChild(tree: WorkbenchCompressibleAsyncDataTree, treeInput: ExplorerItem[]): boolean { @@ -349,7 +350,10 @@ export class ExplorerView extends ViewPane implements IExplorerView { } getContext(respectMultiSelection: boolean): ExplorerItem[] { - return getContext(this.tree.getFocus(), this.tree.getSelection(), respectMultiSelection, this.renderer); + const focusedItems = this.tree.getFocusedPart() === AbstractTreePart.StickyScroll ? + this.tree.getStickyScrollFocus() : + this.tree.getFocus(); + return getContext(focusedItems, this.tree.getSelection(), respectMultiSelection, this.renderer); } isItemVisible(item: ExplorerItem): boolean { From 1350b6eca0c46484fd489e9cdad83ec146c15ef2 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 8 Jan 2024 12:02:42 +0100 Subject: [PATCH 0117/1897] fix https://github.com/microsoft/vscode/issues/201340 (#201983) --- .../workbench/browser/parts/titlebar/commandCenterControl.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts index 6a50aff59a5..3843068e941 100644 --- a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts @@ -182,7 +182,6 @@ class CommandCenterCenterViewItem extends BaseActionViewItem { } else if (that._editorGroupService.partOptions.showTabs === 'none') { label = that._windowTitle.fileName ?? label; } - if (!label) { label = localize('label.dfl', "Search"); } @@ -192,7 +191,8 @@ class CommandCenterCenterViewItem extends BaseActionViewItem { if (suffix) { label = localize('label2', "{0} {1}", label, suffix); } - return label; + + return label.replaceAll(/\r\n|\r|\n/g, '\u23CE'); } }); } From 731da6474016f36e9989eb0eb0bdf56510d49c89 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 8 Jan 2024 17:09:20 +0530 Subject: [PATCH 0118/1897] fix #201813 (#201985) --- .../contrib/extensions/browser/fileBasedRecommendations.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index 47de97ba803..c043223dc06 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -136,6 +136,10 @@ export class FileBasedRecommendations extends ExtensionRecommendations { * or prompt to search the marketplace if it has extensions that can support the file type */ private promptImportantRecommendations(uri: URI, model: ITextModel, extensionRecommendations?: IStringDictionary): void { + if (model.isDisposed()) { + return; + } + const pattern = extname(uri).toLowerCase(); extensionRecommendations = extensionRecommendations ?? this.recommendationsByPattern.get(pattern) ?? this.fileOpenRecommendations; const extensionRecommendationEntries = Object.entries(extensionRecommendations); From 03e191825d23faae8b1311597571f884ca543def Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 8 Jan 2024 13:02:13 +0100 Subject: [PATCH 0119/1897] Git - fix context menu order (#201986) --- extensions/git/package.json | 42 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 3e4137d36e4..9ac30f041e3 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1545,24 +1545,24 @@ "when": "scmProvider == git && scmResourceGroup == merge", "group": "inline@2" }, - { - "command": "git.revealInExplorer", - "when": "scmProvider == git && scmResourceGroup == merge", - "group": "2_view@1" - }, { "command": "git.revealFileInOS.linux", "when": "scmProvider == git && scmResourceGroup == merge && remoteName == '' && isLinux", - "group": "2_view@2" + "group": "2_view@1" }, { "command": "git.revealFileInOS.mac", "when": "scmProvider == git && scmResourceGroup == merge && remoteName == '' && isMac", - "group": "2_view@2" + "group": "2_view@1" }, { "command": "git.revealFileInOS.windows", "when": "scmProvider == git && scmResourceGroup == merge && remoteName == '' && isWindows", + "group": "2_view@1" + }, + { + "command": "git.revealInExplorer", + "when": "scmProvider == git && scmResourceGroup == merge", "group": "2_view@2" }, { @@ -1600,24 +1600,24 @@ "when": "scmProvider == git && scmResourceGroup == index", "group": "inline@2" }, - { - "command": "git.revealInExplorer", - "when": "scmProvider == git && scmResourceGroup == index", - "group": "2_view@1" - }, { "command": "git.revealFileInOS.linux", "when": "scmProvider == git && scmResourceGroup == index && remoteName == '' && isLinux", - "group": "2_view@2" + "group": "2_view@1" }, { "command": "git.revealFileInOS.mac", "when": "scmProvider == git && scmResourceGroup == index && remoteName == '' && isMac", - "group": "2_view@2" + "group": "2_view@1" }, { "command": "git.revealFileInOS.windows", "when": "scmProvider == git && scmResourceGroup == index && remoteName == '' && isWindows", + "group": "2_view@1" + }, + { + "command": "git.revealInExplorer", + "when": "scmProvider == git && scmResourceGroup == index", "group": "2_view@2" }, { @@ -1680,24 +1680,24 @@ "when": "scmProvider == git && scmResourceGroup == workingTree", "group": "1_modification@3" }, - { - "command": "git.revealInExplorer", - "when": "scmProvider == git && scmResourceGroup == workingTree", - "group": "2_view@1" - }, { "command": "git.revealFileInOS.linux", "when": "scmProvider == git && scmResourceGroup == workingTree && remoteName == '' && isLinux", - "group": "2_view@2" + "group": "2_view@1" }, { "command": "git.revealFileInOS.mac", "when": "scmProvider == git && scmResourceGroup == workingTree && remoteName == '' && isMac", - "group": "2_view@2" + "group": "2_view@1" }, { "command": "git.revealFileInOS.windows", "when": "scmProvider == git && scmResourceGroup == workingTree && remoteName == '' && isWindows", + "group": "2_view@1" + }, + { + "command": "git.revealInExplorer", + "when": "scmProvider == git && scmResourceGroup == workingTree", "group": "2_view@2" }, { From 3beaab3f7c14ede7dcda54aa0344b3a0f8a7f57a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 8 Jan 2024 13:57:24 +0100 Subject: [PATCH 0120/1897] Joh/organic-armadillo (#201988) * insta - keep proxied events alive when service got instantiated in the meantime * insta - make sure to not leak listener of early, proxied events --- .../common/instantiationService.ts | 26 +++- .../test/common/instantiationService.test.ts | 129 ++++++++++++++++++ 2 files changed, 149 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index dee82758c0a..cd9c15da0df 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -6,7 +6,7 @@ import { GlobalIdleValue } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import { illegalState } from 'vs/base/common/errors'; -import { toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { SyncDescriptor, SyncDescriptor0 } from 'vs/platform/instantiation/common/descriptors'; import { Graph } from 'vs/platform/instantiation/common/graph'; import { GetLeadingNonServiceArgs, IInstantiationService, ServiceIdentifier, ServicesAccessor, _util } from 'vs/platform/instantiation/common/instantiation'; @@ -248,12 +248,17 @@ export class InstantiationService implements IInstantiationService { const child = new InstantiationService(undefined, this._strict, this, this._enableTracing); child._globalGraphImplicitDependency = String(id); + type EaryListenerData = { + listener: Parameters>; + disposable?: IDisposable; + }; + // Return a proxy object that's backed by an idle value. That // strategy is to instantiate services in our idle time or when actually // needed but not when injected into a consumer // return "empty events" when the service isn't instantiated yet - const earlyListeners = new Map>>>(); + const earlyListeners = new Map>(); const idle = new GlobalIdleValue(() => { const result = child._createInstance(ctor, args, _trace); @@ -263,8 +268,8 @@ export class InstantiationService implements IInstantiationService { for (const [key, values] of earlyListeners) { const candidate = >(result)[key]; if (typeof candidate === 'function') { - for (const listener of values) { - candidate.apply(result, listener); + for (const value of values) { + value.disposable = candidate.apply(result, value.listener); } } } @@ -284,8 +289,17 @@ export class InstantiationService implements IInstantiationService { earlyListeners.set(key, list); } const event: Event = (callback, thisArg, disposables) => { - const rm = list!.push([callback, thisArg, disposables]); - return toDisposable(rm); + if (idle.isInitialized) { + return idle.value[key](callback, thisArg, disposables); + } else { + const entry: EaryListenerData = { listener: [callback, thisArg, disposables], disposable: undefined }; + const rm = list!.push(entry); + const result = toDisposable(() => { + rm(); + entry.disposable?.dispose(); + }); + return result; + } }; return event; } diff --git a/src/vs/platform/instantiation/test/common/instantiationService.test.ts b/src/vs/platform/instantiation/test/common/instantiationService.test.ts index 4a7d45c5989..349e31056ff 100644 --- a/src/vs/platform/instantiation/test/common/instantiationService.test.ts +++ b/src/vs/platform/instantiation/test/common/instantiationService.test.ts @@ -524,4 +524,133 @@ suite('Instantiation Service', () => { dispose([d1, d3]); }); + + + test('Capture event before init, use after init', function () { + const A = createDecorator('A'); + interface A { + _serviceBrand: undefined; + onDidDoIt: Event; + doIt(): void; + noop(): void; + } + + let created = false; + class AImpl implements A { + _serviceBrand: undefined; + _doIt = 0; + + _onDidDoIt = new Emitter(); + onDidDoIt: Event = this._onDidDoIt.event; + + constructor() { + created = true; + } + + doIt(): void { + this._doIt += 1; + this._onDidDoIt.fire(this); + } + + noop(): void { + } + } + + const insta = new InstantiationService(new ServiceCollection( + [A, new SyncDescriptor(AImpl, undefined, true)], + ), true, undefined, true); + + class Consumer { + constructor(@A public readonly a: A) { + // eager subscribe -> NO service instance + } + } + + const c: Consumer = insta.createInstance(Consumer); + let eventCount = 0; + + // subscribing to event doesn't trigger instantiation + const listener = (e: any) => { + assert.ok(e instanceof AImpl); + eventCount++; + }; + + const event = c.a.onDidDoIt; + + // const d1 = c.a.onDidDoIt(listener); + assert.strictEqual(created, false); + + c.a.noop(); + assert.strictEqual(created, true); + + const d1 = event(listener); + + c.a.doIt(); + + + // instantiation happens on first call + assert.strictEqual(eventCount, 1); + + dispose(d1); + }); + + test('Dispose early event listener', function () { + const A = createDecorator('A'); + interface A { + _serviceBrand: undefined; + onDidDoIt: Event; + doIt(): void; + } + let created = false; + class AImpl implements A { + _serviceBrand: undefined; + _doIt = 0; + + _onDidDoIt = new Emitter(); + onDidDoIt: Event = this._onDidDoIt.event; + + constructor() { + created = true; + } + + doIt(): void { + this._doIt += 1; + this._onDidDoIt.fire(this); + } + } + + const insta = new InstantiationService(new ServiceCollection( + [A, new SyncDescriptor(AImpl, undefined, true)], + ), true, undefined, true); + + class Consumer { + constructor(@A public readonly a: A) { + // eager subscribe -> NO service instance + } + } + + const c: Consumer = insta.createInstance(Consumer); + let eventCount = 0; + + // subscribing to event doesn't trigger instantiation + const listener = (e: any) => { + assert.ok(e instanceof AImpl); + eventCount++; + }; + + const d1 = c.a.onDidDoIt(listener); + assert.strictEqual(created, false); + assert.strictEqual(eventCount, 0); + + c.a.doIt(); + + // instantiation happens on first call + assert.strictEqual(created, true); + assert.strictEqual(eventCount, 1); + + dispose(d1); + + c.a.doIt(); + assert.strictEqual(eventCount, 1); + }); }); From b0b59b4c5aff7dd5a0fbbfc4cece67ef1b124cf5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 8 Jan 2024 18:27:52 +0530 Subject: [PATCH 0121/1897] fix #200026 (#201990) --- .../common/extensionGalleryService.ts | 10 ++++++++-- .../extensionManagement/common/extensionManagement.ts | 1 + .../contrib/extensions/browser/extensionEditor.ts | 5 +++++ .../extensions/browser/extensionsWorkbenchService.ts | 4 ++++ .../workbench/contrib/extensions/common/extensions.ts | 1 + 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 434839f3a88..6602f172d96 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -212,7 +212,8 @@ const PropertyType = { PreRelease: 'Microsoft.VisualStudio.Code.PreRelease', LocalizedLanguages: 'Microsoft.VisualStudio.Code.LocalizedLanguages', WebExtension: 'Microsoft.VisualStudio.Code.WebExtension', - SponsorLink: 'Microsoft.VisualStudio.Code.SponsorLink' + SponsorLink: 'Microsoft.VisualStudio.Code.SponsorLink', + SupportLink: 'Microsoft.VisualStudio.Services.Links.Support', }; interface ICriterium { @@ -439,6 +440,10 @@ function getSponsorLink(version: IRawGalleryExtensionVersion): string | undefine return version.properties?.find(p => p.key === PropertyType.SponsorLink)?.value; } +function getSupportLink(version: IRawGalleryExtensionVersion): string | undefined { + return version.properties?.find(p => p.key === PropertyType.SupportLink)?.value; +} + function getIsPreview(flags: string): boolean { return flags.indexOf('preview') !== -1; } @@ -550,7 +555,8 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller hasReleaseVersion: true, preview: getIsPreview(galleryExtension.flags), isSigned: !!assets.signature, - queryContext + queryContext, + supportLink: getSupportLink(latestVersion) }; } diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index be1cfcd113f..7b1397ca29c 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -219,6 +219,7 @@ export interface IGalleryExtension { properties: IGalleryExtensionProperties; telemetryData?: any; queryContext?: IStringDictionary; + supportLink?: string; } export interface IGalleryMetadata { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index c0002fd0df1..e991e2d5f8d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -940,6 +940,11 @@ export class ExtensionEditor extends EditorPane { if (extension.url) { resources.push([localize('Marketplace', "Marketplace"), URI.parse(extension.url)]); } + if (extension.url && extension.supportUrl) { + try { + resources.push([localize('issues', "Issues"), URI.parse(extension.supportUrl)]); + } catch (error) {/* Ignore */ } + } if (extension.repository) { try { resources.push([localize('repository', "Repository"), URI.parse(extension.repository)]); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 34e22edab47..889b216554b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -217,6 +217,10 @@ export class Extension implements IExtension { return this.gallery && this.gallery.assets.license ? this.gallery.assets.license.uri : undefined; } + get supportUrl(): string | undefined { + return this.gallery && this.gallery.supportLink ? this.gallery.supportLink : undefined; + } + get state(): ExtensionState { return this.stateProvider(this); } diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 77fdf576c77..97ee589ea19 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -58,6 +58,7 @@ export interface IExtension { readonly description: string; readonly url?: string; readonly repository?: string; + readonly supportUrl?: string; readonly iconUrl: string; readonly iconUrlFallback: string; readonly licenseUrl?: string; From 51e490e8a2bc28c7ed97d7893c7f9f3cdd4f97c0 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:00:57 +0100 Subject: [PATCH 0122/1897] =?UTF-8?q?Git=20-=20=F0=9F=92=84=20add=20histor?= =?UTF-8?q?yItemParentId=20to=20provideHistoryItemChanges=20(#201991)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/git/src/historyProvider.ts | 35 ++++++++----------- src/vs/workbench/api/browser/mainThreadSCM.ts | 4 +-- .../workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostSCM.ts | 4 +-- .../contrib/scm/browser/scmViewPane.ts | 15 +++++--- .../workbench/contrib/scm/common/history.ts | 2 +- .../vscode.proposed.scmHistoryProvider.d.ts | 2 +- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index a6e97c63763..9e8dfc2bc7e 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -78,12 +78,12 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec throw new Error('Unsupported options.'); } - const optionsRef = options.limit.id; - const historyItemGroupIdRef = await this.repository.revParse(historyItemGroupId) ?? ''; + const refParentId = options.limit.id; + const refId = await this.repository.revParse(historyItemGroupId) ?? ''; - const [commits, summary] = await Promise.all([ - this.repository.log({ range: `${optionsRef}..${historyItemGroupIdRef}`, shortStats: true, sortByAuthorDate: true }), - this.getSummaryHistoryItem(optionsRef, historyItemGroupIdRef) + const [summary, commits] = await Promise.all([ + this.getSummaryHistoryItem(refId, refParentId), + this.repository.log({ range: `${refParentId}..${refId}`, shortStats: true, sortByAuthorDate: true }) ]); await ensureEmojis(); @@ -107,20 +107,15 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return historyItems; } - async provideHistoryItemChanges(historyItemId: string): Promise { - // The "All Changes" history item uses a special id - // which is a commit range instead of a single commit id - let [originalRef, modifiedRef] = historyItemId.includes('..') - ? historyItemId.split('..') : [undefined, historyItemId]; - - if (!originalRef) { - const commit = await this.repository.getCommit(modifiedRef); - originalRef = commit.parents.length > 0 ? commit.parents[0] : `${modifiedRef}^`; + async provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise { + if (!historyItemParentId) { + const commit = await this.repository.getCommit(historyItemId); + historyItemParentId = commit.parents.length > 0 ? commit.parents[0] : `${historyItemId}^`; } const historyItemChangesUri: Uri[] = []; const historyItemChanges: SourceControlHistoryItemChange[] = []; - const changes = await this.repository.diffBetween(originalRef, modifiedRef); + const changes = await this.repository.diffBetween(historyItemParentId, historyItemId); for (const change of changes) { const historyItemUri = change.uri.with({ @@ -130,8 +125,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec // History item change historyItemChanges.push({ uri: historyItemUri, - originalUri: toGitUri(change.originalUri, originalRef), - modifiedUri: toGitUri(change.originalUri, modifiedRef), + originalUri: toGitUri(change.originalUri, historyItemParentId), + modifiedUri: toGitUri(change.originalUri, historyItemId), renameUri: change.renameUri, }); @@ -208,9 +203,9 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return new FileDecoration(letter, tooltip, color); } - private async getSummaryHistoryItem(ref1: string, ref2: string): Promise { - const statistics = await this.repository.diffBetweenShortStat(ref1, ref2); - return { id: `${ref1}..${ref2}`, parentIds: [], icon: new ThemeIcon('files'), label: l10n.t('All Changes'), statistics }; + private async getSummaryHistoryItem(refId: string, refParentId: string): Promise { + const statistics = await this.repository.diffBetweenShortStat(refParentId, refId); + return { id: refId, parentIds: [refParentId], icon: new ThemeIcon('files'), label: l10n.t('All Changes'), statistics }; } dispose(): void { diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 345a52efcdf..f42a0b5d9f4 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -187,8 +187,8 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider { return historyItems?.map(historyItem => ({ ...historyItem, icon: getIconFromIconDto(historyItem.icon) })); } - async provideHistoryItemChanges(historyItemId: string): Promise { - const changes = await this.proxy.$provideHistoryItemChanges(this.handle, historyItemId, CancellationToken.None); + async provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise { + const changes = await this.proxy.$provideHistoryItemChanges(this.handle, historyItemId, historyItemParentId, CancellationToken.None); return changes?.map(change => ({ uri: URI.revive(change.uri), originalUri: change.originalUri && URI.revive(change.originalUri), diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 712156eff9e..c6dbe8b3a75 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2220,7 +2220,7 @@ export interface ExtHostSCMShape { $validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string | IMarkdownString, number] | undefined>; $setSelectedSourceControl(selectedSourceControlHandle: number | undefined): Promise; $provideHistoryItems(sourceControlHandle: number, historyItemGroupId: string, options: any, token: CancellationToken): Promise; - $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise; + $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise; $resolveHistoryItemGroupBase(sourceControlHandle: number, historyItemGroupId: string, token: CancellationToken): Promise; $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined>; } diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 74372613449..2c7d375a7a7 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -955,8 +955,8 @@ export class ExtHostSCM implements ExtHostSCMShape { return historyItems?.map(item => ({ ...item, icon: getHistoryItemIconDto(item) })) ?? undefined; } - async $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise { + async $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise { const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; - return await historyProvider?.provideHistoryItemChanges(historyItemId, token) ?? undefined; + return await historyProvider?.provideHistoryItemChanges(historyItemId, historyItemParentId, token) ?? undefined; } } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 75d5748e909..f2bd08bc1b6 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1257,7 +1257,7 @@ function getSCMResourceId(element: TreeElement): string { } else if (isSCMHistoryItemTreeElement(element)) { const historyItemGroup = element.historyItemGroup; const provider = historyItemGroup.repository.provider; - return `historyItem:${provider.id}/${historyItemGroup.id}/${element.id}`; + return `historyItem:${provider.id}/${historyItemGroup.id}/${element.id}/${element.parentIds.join(',')}`; } else if (isSCMHistoryItemChangeTreeElement(element)) { const historyItem = element.historyItem; const historyItemGroup = historyItem.historyItemGroup; @@ -1713,7 +1713,8 @@ class HistoryItemViewChangesAction extends Action2 { return; } - const historyItemChanges = await historyProvider.provideHistoryItemChanges(historyItem.id); + const historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined; + const historyItemChanges = await historyProvider.provideHistoryItemChanges(historyItem.id, historyItemParentId); if (!historyItemChanges || historyItemChanges.length === 0) { return; } @@ -3417,13 +3418,17 @@ class SCMTreeDataSource implements IAsyncDataSource 0 ? element.parentIds[0] : undefined; + let historyItemChanges = historyItemChangesMap.get(`${element.id}/${historyItemParentId}`); if (!historyItemChanges) { - historyItemChanges = await historyProvider.provideHistoryItemChanges(element.id) ?? []; + const historyItemParentId = element.parentIds.length > 0 ? element.parentIds[0] : undefined; + historyItemChanges = await historyProvider.provideHistoryItemChanges(element.id, historyItemParentId) ?? []; + this.historyProviderCache.set(repository, { ...historyProviderCacheEntry, - historyItemChanges: historyItemChangesMap.set(element.id, historyItemChanges) + historyItemChanges: historyItemChangesMap.set(`${element.id}/${historyItemParentId}`, historyItemChanges) }); } diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index 5ccbce67e26..d1e61ec676c 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -21,7 +21,7 @@ export interface ISCMHistoryProvider { set currentHistoryItemGroup(historyItemGroup: ISCMHistoryItemGroup | undefined); provideHistoryItems(historyItemGroupId: string, options: ISCMHistoryOptions): Promise; - provideHistoryItemChanges(historyItemId: string): Promise; + provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise; resolveHistoryItemGroupBase(historyItemGroupId: string): Promise; resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined>; resolveHistoryItemGroupDetails(historyItemGroup: ISCMHistoryItemGroup): Promise; diff --git a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts index 22ec4683f3b..8dd3dd899df 100644 --- a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts @@ -25,7 +25,7 @@ declare module 'vscode' { // onDidChangeHistoryItemGroups: Event; provideHistoryItems(historyItemGroupId: string, options: SourceControlHistoryOptions, token: CancellationToken): ProviderResult; - provideHistoryItemChanges(historyItemId: string, token: CancellationToken): ProviderResult; + provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): ProviderResult; resolveHistoryItemGroupBase(historyItemGroupId: string, token: CancellationToken): ProviderResult; resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId: string, token: CancellationToken): ProviderResult<{ id: string; ahead: number; behind: number }>; From deae3ca4b18f7b1ad27d7e282bba48350ecbed6c Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Mon, 8 Jan 2024 14:11:19 +0100 Subject: [PATCH 0123/1897] Switch to using sha256 for checksums (fixes CodeQL warning) (#201977) * Switch to using sha256 for checksums (fixes CodeQL warning) * Fix unit test --- build/gulpfile.vscode.js | 2 +- src/vs/platform/checksum/node/checksumService.ts | 2 +- src/vs/platform/checksum/test/node/checksumService.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 857114fea15..bfd5c896e2f 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -184,7 +184,7 @@ function computeChecksum(filename) { const contents = fs.readFileSync(filename); const hash = crypto - .createHash('md5') + .createHash('sha256') .update(contents) .digest('base64') .replace(/=+$/, ''); diff --git a/src/vs/platform/checksum/node/checksumService.ts b/src/vs/platform/checksum/node/checksumService.ts index e4214019ff1..707095fde87 100644 --- a/src/vs/platform/checksum/node/checksumService.ts +++ b/src/vs/platform/checksum/node/checksumService.ts @@ -18,7 +18,7 @@ export class ChecksumService implements IChecksumService { async checksum(resource: URI): Promise { const stream = (await this.fileService.readFileStream(resource)).value; return new Promise((resolve, reject) => { - const hash = createHash('md5'); + const hash = createHash('sha256'); listenStream(stream, { onData: data => hash.update(data.buffer), diff --git a/src/vs/platform/checksum/test/node/checksumService.test.ts b/src/vs/platform/checksum/test/node/checksumService.test.ts index 3e6a29fb573..3e56af64720 100644 --- a/src/vs/platform/checksum/test/node/checksumService.test.ts +++ b/src/vs/platform/checksum/test/node/checksumService.test.ts @@ -35,7 +35,7 @@ suite('Checksum Service', () => { const checksumService = new ChecksumService(fileService); const checksum = await checksumService.checksum(URI.file(FileAccess.asFileUri('vs/platform/checksum/test/node/fixtures/lorem.txt').fsPath)); - assert.ok(checksum === '8mi5KF8kcb817zmlal1kZA' || checksum === 'DnUKbJ1bHPPNZoHgHV25sg'); // depends on line endings git config + assert.ok(checksum === 'd/9bMU0ydNCmc/hg8ItWeiLT/ePnf7gyPRQVGpd6tRI' || checksum === 'eJeeTIS0dzi8MZY+nHhjPBVtNbmGqxfVvgEOB4sqVIc'); // depends on line endings git config }); ensureNoDisposablesAreLeakedInTestSuite(); From 4e278ecca798d62f7414103b88090d26b0fd1872 Mon Sep 17 00:00:00 2001 From: VDisawal Date: Mon, 8 Jan 2024 19:18:18 +0530 Subject: [PATCH 0124/1897] #198975: saveValue => historyNavigator.add (#199142) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Vishwas Co-authored-by: João Moreno --- src/vs/workbench/contrib/scm/common/scmService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/common/scmService.ts b/src/vs/workbench/contrib/scm/common/scmService.ts index 2bc030d479c..7e85cdaa4c1 100644 --- a/src/vs/workbench/contrib/scm/common/scmService.ts +++ b/src/vs/workbench/contrib/scm/common/scmService.ts @@ -143,7 +143,7 @@ class SCMInput implements ISCMInput { } if (!transient) { - this.saveValue(); + this.historyNavigator.add(this._value); this.historyNavigator.add(value); this.didChangeHistory = true; } From c84e38d44bf2a0ce2780b9f100ffee7d09a65cca Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 8 Jan 2024 14:49:15 +0100 Subject: [PATCH 0125/1897] small fixes --- src/vs/base/browser/ui/tree/abstractTree.ts | 10 ++++------ src/vs/base/browser/ui/tree/media/tree.css | 4 ++++ .../contrib/files/browser/views/explorerView.ts | 8 +++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 65a775ffd38..326d42778c5 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1788,7 +1788,7 @@ class StickyScrollFocus extends Disposable { throw new Error('Sticky scroll focus received illigel state'); } - const previousFocusedIndex = this.focusedIndex; + const previousIndex = this.focusedIndex; this.removeFocus(); this.elements = elements; @@ -1796,12 +1796,10 @@ class StickyScrollFocus extends Disposable { this.container.tabIndex = state ? 0 : -1; - if (this.domHasFocus && state) { - const newFocusedIndex = clamp(previousFocusedIndex, 0, state.count - 1); + if (state) { + const newFocusedIndex = clamp(previousIndex, 0, state.count - 1); this.setFocus(newFocusedIndex); } else { - this.removeFocus(); - this.toggleStickyScrollFocused(false); if (this.domHasFocus) { this.view.domFocus(); } @@ -2825,7 +2823,7 @@ export abstract class AbstractTree implements IDisposable getStickyScrollFocus(): T[] { const focus = this.stickyScrollController?.getFocus(); - return focus ? [focus] : []; + return focus !== undefined ? [focus] : []; } getFocusedPart(): AbstractTreePart { diff --git a/src/vs/base/browser/ui/tree/media/tree.css b/src/vs/base/browser/ui/tree/media/tree.css index 1c9aa292e0f..123ca640fcd 100644 --- a/src/vs/base/browser/ui/tree/media/tree.css +++ b/src/vs/base/browser/ui/tree/media/tree.css @@ -164,3 +164,7 @@ width: 100%; box-shadow: var(--vscode-scrollbar-shadow) 0 6px 6px -6px inset; } + +.monaco-list .monaco-scrollable-element .monaco-tree-sticky-container[tabindex="0"]:focus{ + outline: none; +} diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 50fb3e0c119..ade3a51da93 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -339,9 +339,11 @@ export class ExplorerView extends ViewPane implements IExplorerView { super.focus(); this.tree.domFocus(); - const focused = this.tree.getFocus(); - if (focused.length === 1 && this._autoReveal) { - this.tree.reveal(focused[0], 0.5); + if (this.tree.getFocusedPart() === AbstractTreePart.Tree) { + const focused = this.tree.getFocus(); + if (focused.length === 1 && this._autoReveal) { + this.tree.reveal(focused[0], 0.5); + } } } From f14bc31be88e2dff0e6481305bc9c5d0bc57b66e Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 8 Jan 2024 14:54:17 +0100 Subject: [PATCH 0126/1897] :lipstick: --- src/vs/base/browser/ui/list/listWidget.ts | 1 + src/vs/base/browser/ui/tree/abstractTree.ts | 25 +++++++++------------ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 7838dce3095..5ed28547f6a 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -734,6 +734,7 @@ export class MouseController implements IDisposable { if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) { return; } + const focus = typeof e.index === 'undefined' ? [] : [e.index]; this.list.setFocus(focus, e.browserEvent); } diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 326d42778c5..e3afcdf75f6 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1463,7 +1463,7 @@ class StickyScrollController extends Disposable { this._widget.domFocus(); } - // If sticky scroll was the last focused part in the tree + // Whether sticky scroll was the last focused part in the tree or not focusedLast(): boolean { return this._widget.focusedLast(); } @@ -1674,24 +1674,23 @@ class StickyScrollWidget implements IDisposable { class StickyScrollFocus extends Disposable { + private focusedIndex: number = -1; private elements: HTMLElement[] = []; private state: StickyScrollState | undefined; - private _onDidChangeState = new Emitter(); - readonly onDidChangeHasFocus = this._onDidChangeState.event; + private _onDidChangeHasFocus = new Emitter(); + readonly onDidChangeHasFocus = this._onDidChangeHasFocus.event; + + private _onContextMenu = new Emitter>(); + readonly onContextMenu: Event> = this._onContextMenu.event; private _domHasFocus: boolean = false; private get domHasFocus(): boolean { return this._domHasFocus; } private set domHasFocus(hasFocus: boolean) { this._domHasFocus = hasFocus; - this._onDidChangeState.fire(hasFocus); + this._onDidChangeHasFocus.fire(hasFocus); } - private focusedIndex: number = -1; - - private _onContextMenu = new Emitter>(); - readonly onContextMenu: Event> = this._onContextMenu.event; - constructor( private readonly container: HTMLElement, private readonly view: List> @@ -1913,7 +1912,7 @@ class StickyScrollFocus extends Disposable { override dispose(): void { this.toggleStickyScrollFocused(false); - this._onDidChangeState.fire(false); + this._onDidChangeHasFocus.fire(false); super.dispose(); } } @@ -2339,7 +2338,6 @@ export abstract class AbstractTree implements IDisposable private findController?: FindController; readonly onDidChangeFindOpenState: Event = Event.None; onDidChangeStickyScrollFocused: Event = Event.None; - onStickyScrollContextMenu: Event> = Event.None; private focusNavigationFilter: ((node: ITreeNode) => boolean) | undefined; private stickyScrollController?: StickyScrollController; private styleElement: HTMLStyleElement; @@ -2352,7 +2350,7 @@ export abstract class AbstractTree implements IDisposable get onMouseClick(): Event> { return Event.map(this.view.onMouseClick, asTreeMouseEvent); } get onMouseDblClick(): Event> { return Event.filter(Event.map(this.view.onMouseDblClick, asTreeMouseEvent), e => e.target !== TreeMouseEventTarget.Filter); } - get onContextMenu(): Event> { return Event.any(Event.filter(Event.map(this.view.onContextMenu, asTreeContextMenuEvent), e => !e.isStickyScroll), this.onStickyScrollContextMenu); } + get onContextMenu(): Event> { return Event.any(Event.filter(Event.map(this.view.onContextMenu, asTreeContextMenuEvent), e => !e.isStickyScroll), this.stickyScrollController?.onContextMenu ?? Event.None); } get onTap(): Event> { return Event.map(this.view.onTap, asTreeMouseEvent); } get onPointer(): Event> { return Event.map(this.view.onPointer, asTreeMouseEvent); } @@ -2482,7 +2480,6 @@ export abstract class AbstractTree implements IDisposable if (_options.enableStickyScroll) { this.stickyScrollController = new StickyScrollController(this, this.model, this.view, this.renderers, this.treeDelegate, _options); this.onDidChangeStickyScrollFocused = this.stickyScrollController.onDidChangeHasFocus; - this.onStickyScrollContextMenu = this.stickyScrollController.onContextMenu; } this.styleElement = createStyleSheet(this.view.getHTMLElement()); @@ -2513,10 +2510,8 @@ export abstract class AbstractTree implements IDisposable if (!this.stickyScrollController && this._options.enableStickyScroll) { this.stickyScrollController = new StickyScrollController(this, this.model, this.view, this.renderers, this.treeDelegate, this._options); this.onDidChangeStickyScrollFocused = this.stickyScrollController.onDidChangeHasFocus; - this.onStickyScrollContextMenu = this.stickyScrollController.onContextMenu; } else if (this.stickyScrollController && !this._options.enableStickyScroll) { this.onDidChangeStickyScrollFocused = Event.None; - this.onStickyScrollContextMenu = Event.None; this.stickyScrollController.dispose(); this.stickyScrollController = undefined; } From cc7a0865e407e8b5eeaaf0b5cbda6f06f8a99d8b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 8 Jan 2024 14:57:05 +0100 Subject: [PATCH 0127/1897] update notebook (#201998) --- .vscode/notebooks/api.github-issues | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index e2d8331cc77..f990cff7af1 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"November 2023\"\n" + "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"December / January 2024\"\n" }, { "kind": 1, @@ -17,7 +17,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repo $milestone label:api-finalization\n" + "value": "$REPO $MILESTONE label:api-finalization\n" }, { "kind": 1, @@ -27,6 +27,6 @@ { "kind": 2, "language": "github-issues", - "value": "$repo $milestone is:open label:api-proposal sort:created-asc\n" + "value": "$REPO $MILESTONE is:open label:api-proposal sort:created-asc\n" } ] \ No newline at end of file From 9d161efa98a1e856eb9c33940b485435b81c2900 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 8 Jan 2024 15:37:51 +0100 Subject: [PATCH 0128/1897] tabindex calls blur() to early --- src/vs/base/browser/ui/tree/abstractTree.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index e3afcdf75f6..2a956d84150 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1793,8 +1793,6 @@ class StickyScrollFocus extends Disposable { this.elements = elements; this.state = state; - this.container.tabIndex = state ? 0 : -1; - if (state) { const newFocusedIndex = clamp(previousIndex, 0, state.count - 1); this.setFocus(newFocusedIndex); @@ -1803,6 +1801,9 @@ class StickyScrollFocus extends Disposable { this.view.domFocus(); } } + + // must come last as it calls blur() + this.container.tabIndex = state ? 0 : -1; } private setFocusedElement(stickyIndex: number): void { From 683c7f51722408d9f7b8bc750691779949b58afd Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 8 Jan 2024 16:04:57 +0100 Subject: [PATCH 0129/1897] fix https://github.com/microsoft/vscode/issues/201825 (#202012) --- .../workbench/contrib/inlineChat/browser/inlineChatController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 7e0d8fc469b..424cc9c7217 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -876,6 +876,7 @@ export class InlineChatController implements IEditorContribution { private async[State.PAUSE]() { + this._sessionStore.clear(); this._ctxDidEdit.reset(); this._ctxUserDidEdit.reset(); this._ctxLastFeedbackKind.reset(); From 6eed4af2d4cf22b9cac5b4710a6b19ec3b7d44b2 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 8 Jan 2024 16:11:02 +0100 Subject: [PATCH 0130/1897] Add colors to the output editor in the sidebar with CSS (#201292) * removing the registration of theming participant and instead adding the css rules into output.css * removing the imports which we no longer need --- .../contrib/output/browser/output.css | 4 ++++ .../contrib/output/browser/outputView.ts | 18 +----------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/output/browser/output.css b/src/vs/workbench/contrib/output/browser/output.css index 89848f4f595..5235694247c 100644 --- a/src/vs/workbench/contrib/output/browser/output.css +++ b/src/vs/workbench/contrib/output/browser/output.css @@ -3,12 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +.monaco-workbench .part.sidebar .output-view .monaco-editor, +.monaco-workbench .part.sidebar .output-view .monaco-editor .margin, +.monaco-workbench .part.sidebar .output-view .monaco-editor .monaco-editor-background, .monaco-workbench .part.panel > .content .pane-body.output-view .monaco-editor, .monaco-workbench .part.panel > .content .pane-body.output-view .monaco-editor .margin, .monaco-workbench .part.panel > .content .pane-body.output-view .monaco-editor .monaco-editor-background { background-color: var(--vscode-outputView-background); } +.monaco-workbench .part.sidebar .output-view .sticky-widget, .monaco-workbench .part.panel > .content .pane-body.output-view .sticky-widget { background-color: var(--vscode-outputViewStickyScroll-background, var(--vscode-panel-background)); } diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index b72381a8a26..b7484919efa 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -14,7 +14,7 @@ import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/c import { IEditorOpenContext } from 'vs/workbench/common/editor'; import { AbstractTextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; import { OUTPUT_VIEW_ID, CONTEXT_IN_OUTPUT, IOutputChannel, CONTEXT_OUTPUT_SCROLL_LOCK } from 'vs/workbench/services/output/common/output'; -import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -26,8 +26,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IViewDescriptorService } from 'vs/workbench/common/views'; import { TextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { Dimension } from 'vs/base/browser/dom'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; @@ -257,17 +255,3 @@ class OutputEditor extends AbstractTextResourceEditor { } } } - -registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { - // Sidebar background for the output view - const sidebarBackground = theme.getColor(SIDE_BAR_BACKGROUND); - if (sidebarBackground && sidebarBackground !== theme.getColor(editorBackground)) { - collector.addRule(` - .monaco-workbench .part.sidebar .output-view .monaco-editor, - .monaco-workbench .part.sidebar .output-view .monaco-editor .margin, - .monaco-workbench .part.sidebar .output-view .monaco-editor .monaco-editor-background { - background-color: ${sidebarBackground}; - } - `); - } -}); From 880eee6a96a3b2ff46111ae5e5d9d6cc5558f388 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 8 Jan 2024 16:27:32 +0100 Subject: [PATCH 0131/1897] print warning about reused action ids, fix view action duplication (#202000) * print warning about reused action ids, fix view action duplication * remove debug code * make tests happy-ish --- src/vs/platform/actions/common/actions.ts | 5 +++++ .../services/views/browser/viewDescriptorService.ts | 6 +++++- test/unit/electron/renderer.js | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index d43a2a4cb5f..5b48ab3dfd6 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -577,6 +577,11 @@ export function registerAction2(ctor: { new(): Action2 }): IDisposable { const { f1, menu, keybinding, ...command } = action.desc; + if (CommandsRegistry.getCommand(command.id)) { + // throw new Error(`Cannot register two commands with the same id: ${command.id}`); + console.warn(`Cannot register two commands with the same id: ${command.id}`); + } + // command disposables.add(CommandsRegistry.registerCommand({ id: command.id, diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index edadb1ab37d..d841d165c09 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -756,13 +756,17 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } private registerViewsVisibilityActions(viewContainer: ViewContainer, { viewContainerModel, disposables }: { viewContainerModel: ViewContainerModel; disposables: DisposableStore }): void { + this.viewsVisibilityActionDisposables.deleteAndDispose(viewContainer); this.viewsVisibilityActionDisposables.set(viewContainer, this.registerViewsVisibilityActionsForContainer(viewContainerModel)); disposables.add(Event.any( viewContainerModel.onDidChangeActiveViewDescriptors, viewContainerModel.onDidAddVisibleViewDescriptors, viewContainerModel.onDidRemoveVisibleViewDescriptors, viewContainerModel.onDidMoveVisibleViewDescriptors - )(e => this.viewsVisibilityActionDisposables.set(viewContainer, this.registerViewsVisibilityActionsForContainer(viewContainerModel)))); + )(e => { + this.viewsVisibilityActionDisposables.deleteAndDispose(viewContainer); + this.viewsVisibilityActionDisposables.set(viewContainer, this.registerViewsVisibilityActionsForContainer(viewContainerModel)); + })); } private registerViewsVisibilityActionsForContainer(viewContainerModel: ViewContainerModel): IDisposable { diff --git a/test/unit/electron/renderer.js b/test/unit/electron/renderer.js index 243d5a5b142..fd22a6e9512 100644 --- a/test/unit/electron/renderer.js +++ b/test/unit/electron/renderer.js @@ -195,6 +195,7 @@ function loadTests(opts) { 'issue #149130: vscode freezes because of Bracket Pair Colorization', // https://github.com/microsoft/vscode/issues/192440 'property limits', // https://github.com/microsoft/vscode/issues/192443 'Error events', // https://github.com/microsoft/vscode/issues/192443 + 'fetch returns keybinding with user first if title and id matches' // ]); let _testsWithUnexpectedOutput = false; From 545eb1dabec2ccca84500f47ebb740c4c7170d95 Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 8 Jan 2024 16:29:36 +0100 Subject: [PATCH 0132/1897] Fix for displaying icons when icon theme is disabled (#201709) * fix #201700 * predefined-file-icon --- src/vs/editor/common/services/getIconClasses.ts | 2 +- src/vs/workbench/browser/media/style.css | 6 +++++- .../contrib/terminal/browser/terminalEditorInput.ts | 2 +- src/vs/workbench/contrib/terminal/browser/terminalIcon.ts | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/common/services/getIconClasses.ts b/src/vs/editor/common/services/getIconClasses.ts index 0df1d17f6ee..45678eeab0f 100644 --- a/src/vs/editor/common/services/getIconClasses.ts +++ b/src/vs/editor/common/services/getIconClasses.ts @@ -16,7 +16,7 @@ const fileIconDirectoryRegex = /(?:\/|^)(?:([^\/]+)\/)?([^\/]+)$/; export function getIconClasses(modelService: IModelService, languageService: ILanguageService, resource: uri | undefined, fileKind?: FileKind, icon?: ThemeIcon): string[] { if (icon) { - return [`codicon-${icon.id}`, 'product-icon']; + return [`codicon-${icon.id}`, 'predefined-file-icon']; } // we always set these base classes even if we do not have a path diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index 6c52fb2c369..262745f0ed2 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -150,13 +150,17 @@ body.web { font-size: 16px; /* sets font-size for codicons in workbench (https://github.com/microsoft/vscode/issues/98495) */ } -.monaco-workbench .product-icon[class*='codicon-']::before { +.monaco-workbench .predefined-file-icon[class*='codicon-']::before { font-family: 'codicon'; width: 16px; padding-left: 3px; /* width (16px) - font-size (13px) = padding-left (3px) */ padding-right: 3px; } +.monaco-workbench:not(.file-icons-enabled) .predefined-file-icon[class*='codicon-']::before { + content: unset !important; +} + .monaco-workbench.modal-dialog-visible .monaco-progress-container.infinite .progress-bit { display: none; /* stop progress animations when dialogs are visible (https://github.com/microsoft/vscode/issues/138396) */ } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts index c8399fc4cb8..1203a986668 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts @@ -205,7 +205,7 @@ export class TerminalEditorInput extends EditorInput implements IEditorCloseHand if (!this._terminalInstance) { return []; } - const extraClasses: string[] = ['terminal-tab', 'product-icon']; + const extraClasses: string[] = ['terminal-tab', 'predefined-file-icon']; const colorClass = getColorClass(this._terminalInstance); if (colorClass) { extraClasses.push(colorClass); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts index 22015db095a..ea676544830 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts @@ -77,7 +77,7 @@ export function getColorStyleContent(colorTheme: IColorTheme, editor?: boolean): if (color) { if (editor) { css += ( - `.monaco-workbench .show-file-icons .product-icon.terminal-tab.${colorClass}::before,` + + `.monaco-workbench .show-file-icons .predefined-file-icon.terminal-tab.${colorClass}::before,` + `.monaco-workbench .show-file-icons .file-icon.terminal-tab.${colorClass}::before` + `{ color: ${color} !important; }` ); From cea4eee18aa4525b9ce6977c69aba91d7772ae87 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Mon, 8 Jan 2024 17:03:22 +0100 Subject: [PATCH 0133/1897] Use sha256 (resolves CodeQL warning) (#201978) * Use sha256 (resolves CodeQL warning) * check in JS file too --- build/azure-pipelines/common/computeNodeModulesCacheKey.js | 2 +- build/azure-pipelines/common/computeNodeModulesCacheKey.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/common/computeNodeModulesCacheKey.js b/build/azure-pipelines/common/computeNodeModulesCacheKey.js index 535cb7efd98..fe84d03e1d7 100644 --- a/build/azure-pipelines/common/computeNodeModulesCacheKey.js +++ b/build/azure-pipelines/common/computeNodeModulesCacheKey.js @@ -9,7 +9,7 @@ const path = require("path"); const crypto = require("crypto"); const { dirs } = require('../../npm/dirs'); const ROOT = path.join(__dirname, '../../../'); -const shasum = crypto.createHash('sha1'); +const shasum = crypto.createHash('sha256'); shasum.update(fs.readFileSync(path.join(ROOT, 'build/.cachesalt'))); shasum.update(fs.readFileSync(path.join(ROOT, '.yarnrc'))); shasum.update(fs.readFileSync(path.join(ROOT, 'remote/.yarnrc'))); diff --git a/build/azure-pipelines/common/computeNodeModulesCacheKey.ts b/build/azure-pipelines/common/computeNodeModulesCacheKey.ts index f9d70503685..16bf76796f9 100644 --- a/build/azure-pipelines/common/computeNodeModulesCacheKey.ts +++ b/build/azure-pipelines/common/computeNodeModulesCacheKey.ts @@ -10,7 +10,7 @@ const { dirs } = require('../../npm/dirs'); const ROOT = path.join(__dirname, '../../../'); -const shasum = crypto.createHash('sha1'); +const shasum = crypto.createHash('sha256'); shasum.update(fs.readFileSync(path.join(ROOT, 'build/.cachesalt'))); shasum.update(fs.readFileSync(path.join(ROOT, '.yarnrc'))); From 1a7ec6aea22745fd0dc9a4aa8e09c047d41441b9 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 8 Jan 2024 08:33:55 -0800 Subject: [PATCH 0134/1897] Fix code block in notebook markdown rendering (#201748) Fixes #201731 --- extensions/markdown-language-features/notebook/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index 22fe3119ad1..e8c30c9b1eb 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -135,9 +135,9 @@ export const activate: ActivationFunction = (ctx) => { linkify: true, highlight: (str: string, lang?: string) => { if (lang) { - return `${markdownIt.utils.escapeHtml(str)}`; + return `
${markdownIt.utils.escapeHtml(str)}
`; } - return `${markdownIt.utils.escapeHtml(str)}`; + return markdownIt.utils.escapeHtml(str); } }); markdownIt.linkify.set({ fuzzyLink: false }); @@ -281,6 +281,7 @@ export const activate: ActivationFunction = (ctx) => { pre code { line-height: 1.357em; white-space: pre-wrap; + padding: 0; } li p { From dc029abccd7f7a1bcf67f569a1cde21db08c36f3 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 8 Jan 2024 17:40:19 +0100 Subject: [PATCH 0135/1897] focus navigation fix --- src/vs/workbench/browser/actions/listCommands.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index a6c625d68d3..ff6e3cbf3d4 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -255,14 +255,17 @@ function expandMultiSelection(focused: WorkbenchListWidget, previousFocus: unkno function revealFocusedStickyScroll(tree: ObjectTree | DataTree | AsyncDataTree, postRevealAction?: (focus: any) => void): void { const focus = tree.getStickyScrollFocus(); - if (!focus) { + if (focus.length === 0) { throw new Error(`StickyScroll has no focus`); } + if (focus.length > 1) { + throw new Error(`StickyScroll can only have a single focused item`); + } - tree.reveal(focus); + tree.reveal(focus[0]); tree.domFocus(); - tree.setFocus([focus]); - postRevealAction?.(focus); + tree.setFocus(focus); + postRevealAction?.(focus[0]); } KeybindingsRegistry.registerCommandAndKeybindingRule({ From 322931b0fcc5acc222c95f0fa61e65b41c23dee3 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:45:44 +0100 Subject: [PATCH 0136/1897] Git - Only show "All Changes" node if there is more than one incoming/outgoing commit (#202021) --- extensions/git/src/historyProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 9e8dfc2bc7e..0e882103140 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -88,7 +88,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec await ensureEmojis(); - const historyItems = commits.length === 0 ? [] : [summary]; + const historyItems = commits.length < 2 ? [] : [summary]; historyItems.push(...commits.map(commit => { const newLineIndex = commit.message.indexOf('\n'); const subject = newLineIndex !== -1 ? commit.message.substring(0, newLineIndex) : commit.message; From affed727e36b81ff784b48db470fb2845e850587 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 8 Jan 2024 22:52:14 +0530 Subject: [PATCH 0137/1897] fix #176433 (#202022) --- .../keybinding/common/keybindingEditing.ts | 117 +++++++++--------- 1 file changed, 58 insertions(+), 59 deletions(-) diff --git a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts index 4ddcceda908..25231d5ad4e 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts @@ -15,7 +15,6 @@ import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel } from 'vs/editor/common/model'; import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IFileService } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -49,8 +48,7 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding @ITextModelService private readonly textModelResolverService: ITextModelService, @ITextFileService private readonly textFileService: ITextFileService, @IFileService private readonly fileService: IFileService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, ) { super(); this.queue = new Queue(); @@ -92,29 +90,33 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding } } - private doRemoveKeybinding(keybindingItem: ResolvedKeybindingItem): Promise { - return this.resolveAndValidate() - .then(reference => { - const model = reference.object.textEditorModel; - if (keybindingItem.isDefault) { - this.removeDefaultKeybinding(keybindingItem, model); - } else { - this.removeUserKeybinding(keybindingItem, model); - } - return this.save().finally(() => reference.dispose()); - }); + private async doRemoveKeybinding(keybindingItem: ResolvedKeybindingItem): Promise { + const reference = await this.resolveAndValidate(); + const model = reference.object.textEditorModel; + if (keybindingItem.isDefault) { + this.removeDefaultKeybinding(keybindingItem, model); + } else { + this.removeUserKeybinding(keybindingItem, model); + } + try { + return await this.save(); + } finally { + reference.dispose(); + } } - private doResetKeybinding(keybindingItem: ResolvedKeybindingItem): Promise { - return this.resolveAndValidate() - .then(reference => { - const model = reference.object.textEditorModel; - if (!keybindingItem.isDefault) { - this.removeUserKeybinding(keybindingItem, model); - this.removeUnassignedDefaultKeybinding(keybindingItem, model); - } - return this.save().finally(() => reference.dispose()); - }); + private async doResetKeybinding(keybindingItem: ResolvedKeybindingItem): Promise { + const reference = await this.resolveAndValidate(); + const model = reference.object.textEditorModel; + if (!keybindingItem.isDefault) { + this.removeUserKeybinding(keybindingItem, model); + this.removeUnassignedDefaultKeybinding(keybindingItem, model); + } + try { + return await this.save(); + } finally { + reference.dispose(); + } } private save(): Promise { @@ -239,47 +241,44 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding model.pushEditOperations([new Selection(startPosition.lineNumber, startPosition.column, startPosition.lineNumber, startPosition.column)], [editOperation], () => []); } - private resolveModelReference(): Promise> { - return this.fileService.exists(this.userDataProfileService.currentProfile.keybindingsResource) - .then(exists => { - const EOL = this.configurationService.getValue<{ eol: string }>('files', { overrideIdentifier: 'json' })['eol']; - const result: Promise = exists ? Promise.resolve(null) : this.textFileService.write(this.userDataProfileService.currentProfile.keybindingsResource, this.getEmptyContent(EOL), { encoding: 'utf8' }); - return result.then(() => this.textModelResolverService.createModelReference(this.userDataProfileService.currentProfile.keybindingsResource)); - }); + private async resolveModelReference(): Promise> { + const exists = await this.fileService.exists(this.userDataProfileService.currentProfile.keybindingsResource); + if (!exists) { + await this.textFileService.write(this.userDataProfileService.currentProfile.keybindingsResource, this.getEmptyContent(), { encoding: 'utf8' }); + } + return this.textModelResolverService.createModelReference(this.userDataProfileService.currentProfile.keybindingsResource); } - private resolveAndValidate(): Promise> { + private async resolveAndValidate(): Promise> { // Target cannot be dirty if not writing into buffer if (this.textFileService.isDirty(this.userDataProfileService.currentProfile.keybindingsResource)) { - return Promise.reject(new Error(localize('errorKeybindingsFileDirty', "Unable to write because the keybindings configuration file has unsaved changes. Please save it first and then try again."))); + throw new Error(localize('errorKeybindingsFileDirty', "Unable to write because the keybindings configuration file has unsaved changes. Please save it first and then try again.")); } - return this.resolveModelReference() - .then(reference => { - const model = reference.object.textEditorModel; - const EOL = model.getEOL(); - if (model.getValue()) { - const parsed = this.parse(model); - if (parsed.parseErrors.length) { - reference.dispose(); - return Promise.reject(new Error(localize('parseErrors', "Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again."))); - } - if (parsed.result) { - if (!Array.isArray(parsed.result)) { - reference.dispose(); - return Promise.reject(new Error(localize('errorInvalidConfiguration', "Unable to write to the keybindings configuration file. It has an object which is not of type Array. Please open the file to clean up and try again."))); - } - } else { - const content = EOL + '[]'; - this.applyEditsToBuffer({ content, length: content.length, offset: model.getValue().length }, model); - } - } else { - const content = this.getEmptyContent(EOL); - this.applyEditsToBuffer({ content, length: content.length, offset: 0 }, model); + const reference = await this.resolveModelReference(); + const model = reference.object.textEditorModel; + const EOL = model.getEOL(); + if (model.getValue()) { + const parsed = this.parse(model); + if (parsed.parseErrors.length) { + reference.dispose(); + throw new Error(localize('parseErrors', "Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again.")); + } + if (parsed.result) { + if (!Array.isArray(parsed.result)) { + reference.dispose(); + throw new Error(localize('errorInvalidConfiguration', "Unable to write to the keybindings configuration file. It has an object which is not of type Array. Please open the file to clean up and try again.")); } - return reference; - }); + } else { + const content = EOL + '[]'; + this.applyEditsToBuffer({ content, length: content.length, offset: model.getValue().length }, model); + } + } else { + const content = this.getEmptyContent(); + this.applyEditsToBuffer({ content, length: content.length, offset: 0 }, model); + } + return reference; } private parse(model: ITextModel): { result: IUserFriendlyKeybinding[]; parseErrors: json.ParseError[] } { @@ -288,8 +287,8 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding return { result, parseErrors }; } - private getEmptyContent(EOL: string): string { - return '// ' + localize('emptyKeybindingsHeader', "Place your key bindings in this file to override the defaults") + EOL + '[]'; + private getEmptyContent(): string { + return '// ' + localize('emptyKeybindingsHeader', "Place your key bindings in this file to override the defaults") + '\n[\n]'; } } From b5d2084dfbc68836bf0aae82748d250205aef785 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Mon, 8 Jan 2024 10:54:25 -0700 Subject: [PATCH 0138/1897] Fix reveal in explorer (#202026) Fix reveal in exporer --- src/vs/workbench/contrib/files/browser/fileCommands.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index aeea27bed6e..d991754b47f 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -328,13 +328,12 @@ CommandsRegistry.registerCommand({ const uri = getResourceForCommand(resource, accessor.get(IListService), accessor.get(IEditorService)); if (uri && contextService.isInsideWorkspace(uri)) { - const explorerView = viewService.getViewWithId(VIEW_ID); + const explorerView = await viewService.openView(VIEW_ID, false); if (explorerView) { const oldAutoReveal = explorerView.autoReveal; // Disable autoreveal before revealing the explorer to prevent a race betwene auto reveal + selection // Fixes #197268 explorerView.autoReveal = false; - await viewService.openView(VIEW_ID, false); explorerView.setExpanded(true); await explorerService.select(uri, 'force'); explorerView.focus(); From ceec92c7fa65c09a4b42dd6155ee52f5c95eac8d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 8 Jan 2024 12:03:11 -0600 Subject: [PATCH 0139/1897] fix #200083 --- .../contrib/chat/browser/actions/chatAccessibilityHelp.ts | 1 + src/vs/workbench/contrib/chat/browser/chatFollowups.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index c787fbdaa98..65e9587e792 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -22,6 +22,7 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'pane content.push(localize('chat.overview', 'The chat view is comprised of an input box and a request/response list. The input box is used to make requests and the list is used to display responses.')); content.push(localize('chat.requestHistory', 'In the input box, use up and down arrows to navigate your request history. Edit input and use enter or the submit button to run a new request.')); content.push(openAccessibleViewKeybinding ? localize('chat.inspectResponse', 'In the input box, inspect the last response in the accessible view via {0}', openAccessibleViewKeybinding) : localize('chat.inspectResponseNoKb', 'With the input box focused, inspect the last response in the accessible view via the Open Accessible View command, which is currently not triggerable by a keybinding.')); + content.push(localize('chat.followUp', 'In the input box, navigate to the suggested follow up question (Shift+Tab) and press Enter to run it. Such follow ups also exist within the list of responses.')); content.push(localize('chat.announcement', 'Chat responses will be announced as they come in. A response will indicate the number of code blocks, if any, and then the rest of the response.')); content.push(descriptionForCommand('chat.action.focus', localize('workbench.action.chat.focus', 'To focus the chat request/response list, which can be navigated with up and down arrows, invoke the Focus Chat command ({0}).',), localize('workbench.action.chat.focusNoKb', 'To focus the chat request/response list, which can be navigated with up and down arrows, invoke The Focus Chat List command, which is currently not triggerable by a keybinding.'), keybindingService)); content.push(descriptionForCommand('workbench.action.chat.focusInput', localize('workbench.action.chat.focusInput', 'To focus the input box for chat requests, invoke the Focus Chat Input command ({0})'), localize('workbench.action.interactiveSession.focusInputNoKb', 'To focus the input box for chat requests, invoke the Focus Chat Input command, which is currently not triggerable by a keybinding.'), keybindingService)); diff --git a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts index 409e4452098..aed879a9c23 100644 --- a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts +++ b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts @@ -39,7 +39,7 @@ export class ChatFollowups extends Disposable { } else if (followup.kind === 'command') { button.element.classList.add('interactive-followup-command'); } - + button.element.ariaLabel = `Follow up question: ${followup.title}`; const label = followup.kind === 'reply' ? '$(sparkle) ' + (followup.title || followup.message) : followup.title; From 1fb00310ef3cc74d632cb9feca13059d529572cb Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 8 Jan 2024 12:05:08 -0600 Subject: [PATCH 0140/1897] tweak --- .../contrib/chat/browser/actions/chatAccessibilityHelp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 65e9587e792..681d0e9b07a 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -22,7 +22,7 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'pane content.push(localize('chat.overview', 'The chat view is comprised of an input box and a request/response list. The input box is used to make requests and the list is used to display responses.')); content.push(localize('chat.requestHistory', 'In the input box, use up and down arrows to navigate your request history. Edit input and use enter or the submit button to run a new request.')); content.push(openAccessibleViewKeybinding ? localize('chat.inspectResponse', 'In the input box, inspect the last response in the accessible view via {0}', openAccessibleViewKeybinding) : localize('chat.inspectResponseNoKb', 'With the input box focused, inspect the last response in the accessible view via the Open Accessible View command, which is currently not triggerable by a keybinding.')); - content.push(localize('chat.followUp', 'In the input box, navigate to the suggested follow up question (Shift+Tab) and press Enter to run it. Such follow ups also exist within the list of responses.')); + content.push(localize('chat.followUp', 'In the input box, navigate to the suggested follow up question (Shift+Tab) and press Enter to run it. Such follow ups also may exist within the list of responses.')); content.push(localize('chat.announcement', 'Chat responses will be announced as they come in. A response will indicate the number of code blocks, if any, and then the rest of the response.')); content.push(descriptionForCommand('chat.action.focus', localize('workbench.action.chat.focus', 'To focus the chat request/response list, which can be navigated with up and down arrows, invoke the Focus Chat command ({0}).',), localize('workbench.action.chat.focusNoKb', 'To focus the chat request/response list, which can be navigated with up and down arrows, invoke The Focus Chat List command, which is currently not triggerable by a keybinding.'), keybindingService)); content.push(descriptionForCommand('workbench.action.chat.focusInput', localize('workbench.action.chat.focusInput', 'To focus the input box for chat requests, invoke the Focus Chat Input command ({0})'), localize('workbench.action.interactiveSession.focusInputNoKb', 'To focus the input box for chat requests, invoke the Focus Chat Input command, which is currently not triggerable by a keybinding.'), keybindingService)); From 53c8f849111dd6c4e06d9bb3b8fe6141e63e7f90 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 8 Jan 2024 10:16:27 -0800 Subject: [PATCH 0141/1897] debug: fixup debug aux window position properly (#202027) fixup debug toolbar properly --- src/vs/workbench/contrib/debug/browser/debugToolBar.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 209acbc3cd8..e769411e5bb 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -158,7 +158,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { if (mouseClickEvent.detail === 2) { // double click on debug bar centers it again #8250 const widgetWidth = this.$el.clientWidth; - this.setCoordinates(0.5 * activeWindow.innerWidth - 0.5 * widgetWidth, 0); + this.setCoordinates(0.5 * activeWindow.innerWidth - 0.5 * widgetWidth, this.yDefault); this.storePosition(); } })); @@ -267,7 +267,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { : this.auxWindowCoordinates.get(currentWindow)?.y; } - this.setYCoordinate(y || 0); + this.setYCoordinate(y ?? this.yDefault); } private setYCoordinate(y = this.yCoordinate): void { @@ -277,6 +277,10 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this.yCoordinate = y; } + private get yDefault() { + return this.layoutService.mainContainerOffset.top; + } + private _yRange: [number, number] | undefined; private get yRange(): [number, number] { if (!this._yRange) { From 0d856184a3e74ce7761b41cdf4f43c10e9158627 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 8 Jan 2024 15:16:45 -0300 Subject: [PATCH 0142/1897] Capitalize labels --- .../contrib/debug/browser/breakpointEditorContribution.ts | 2 +- src/vs/workbench/contrib/debug/browser/breakpointWidget.ts | 2 +- .../workbench/contrib/debug/browser/debugEditorActions.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 19a1f87ae07..7eeac6c7506 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -457,7 +457,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi )); actions.push(new Action( 'addTriggerByBreakpoint', - nls.localize('addTriggerByBreakpoint', "Add Wait for breakpoint.."), + nls.localize('addTriggerByBreakpoint', "Add Wait For Breakpoint.."), undefined, true, () => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.TRIGGER_POINT)) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 06993e38dbd..3fdef928009 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -199,7 +199,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi protected _fillContainer(container: HTMLElement): void { this.setCssClass('breakpoint-widget'); - const selectBox = new SelectBox([{ text: nls.localize('expression', "Expression") }, { text: nls.localize('hitCount', "Hit Count") }, { text: nls.localize('logMessage', "Log Message") }, { text: nls.localize('triggeredBy', "Wait for breakpoint") }], this.context, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('breakpointType', 'Breakpoint Type') }); + const selectBox = new SelectBox([{ text: nls.localize('expression', "Expression") }, { text: nls.localize('hitCount', "Hit Count") }, { text: nls.localize('logMessage', "Log Message") }, { text: nls.localize('triggeredBy', "Wait For Breakpoint") }], this.context, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('breakpointType', 'Breakpoint Type') }); this.selectContainer = $('.breakpoint-select-container'); selectBox.render(dom.append(container, this.selectContainer)); selectBox.onDidSelect(e => { diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index aebf6c90d3d..5f25982d58d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -156,13 +156,13 @@ class TriggerByBreakpointAction extends EditorAction { constructor() { super({ id: 'editor.debug.action.triggerByBreakpoint', - label: nls.localize('triggerByBreakpointEditorAction', "Debug: Wait for breakpoint..."), + label: nls.localize('triggerByBreakpointEditorAction', "Debug: Add Wait For Breakpoint..."), precondition: CONTEXT_DEBUGGERS_AVAILABLE, - alias: 'Debug: Wait for breakpoint...', + alias: 'Debug: Wait For Breakpoint...', menuOpts: [ { menuId: MenuId.MenubarNewBreakpointMenu, - title: nls.localize({ key: 'miTriggerByBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Wait for breakpoint..."), + title: nls.localize({ key: 'miTriggerByBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Wait For Breakpoint..."), group: '1_breakpoints', order: 4, when: CONTEXT_DEBUGGERS_AVAILABLE, From 83f9d6b3a2d425b4b1617dc6142538151caa7866 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 8 Jan 2024 19:30:05 +0100 Subject: [PATCH 0143/1897] =?UTF-8?q?SCM=20-=20=F0=9F=92=84=20make=20sure?= =?UTF-8?q?=20scm=20action=20ids=20are=20unique=20(#202030)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contrib/scm/browser/scmViewPane.ts | 57 +++++++++++-------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index f2bd08bc1b6..f2aa3236d63 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1490,9 +1490,11 @@ class RepositoryVisibilityActionController { } class SetListViewModeAction extends ViewAction { - constructor(menu: Partial = {}) { + constructor( + id = 'workbench.scm.action.setListViewMode', + menu: Partial = {}) { super({ - id: 'workbench.scm.action.setListViewMode', + id, title: localize('setListViewMode', "View as List"), viewId: VIEW_PANE_ID, f1: false, @@ -1509,26 +1511,31 @@ class SetListViewModeAction extends ViewAction { class SetListViewModeNavigationAction extends SetListViewModeAction { constructor() { - super({ - id: MenuId.SCMTitle, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0), ContextKeys.SCMViewMode.isEqualTo(ViewMode.Tree)), - group: 'navigation', - order: -1000 - }); + super( + 'workbench.scm.action.setListViewModeNavigation', + { + id: MenuId.SCMTitle, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0), ContextKeys.SCMViewMode.isEqualTo(ViewMode.Tree)), + group: 'navigation', + order: -1000 + }); } } class SetTreeViewModeAction extends ViewAction { - constructor(menu: Partial = {}) { - super({ - id: 'workbench.scm.action.setTreeViewMode', - title: localize('setTreeViewMode', "View as Tree"), - viewId: VIEW_PANE_ID, - f1: false, - icon: Codicon.listFlat, - toggled: ContextKeys.SCMViewMode.isEqualTo(ViewMode.Tree), - menu: { id: Menus.ViewSort, group: '1_viewmode', ...menu } - }); + constructor( + id = 'workbench.scm.action.setTreeViewMode', + menu: Partial = {}) { + super( + { + id, + title: localize('setTreeViewMode', "View as Tree"), + viewId: VIEW_PANE_ID, + f1: false, + icon: Codicon.listFlat, + toggled: ContextKeys.SCMViewMode.isEqualTo(ViewMode.Tree), + menu: { id: Menus.ViewSort, group: '1_viewmode', ...menu } + }); } async runInView(_: ServicesAccessor, view: SCMViewPane): Promise { @@ -1538,12 +1545,14 @@ class SetTreeViewModeAction extends ViewAction { class SetTreeViewModeNavigationAction extends SetTreeViewModeAction { constructor() { - super({ - id: MenuId.SCMTitle, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0), ContextKeys.SCMViewMode.isEqualTo(ViewMode.List)), - group: 'navigation', - order: -1000 - }); + super( + 'workbench.scm.action.setTreeViewModeNavigation', + { + id: MenuId.SCMTitle, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0), ContextKeys.SCMViewMode.isEqualTo(ViewMode.List)), + group: 'navigation', + order: -1000 + }); } } From 7eea54545e84167a48f7b840cb581c6819e32d2c Mon Sep 17 00:00:00 2001 From: Gayan Perera Date: Mon, 8 Jan 2024 19:44:37 +0100 Subject: [PATCH 0144/1897] Only update breakpoint when OK is clicked. --- src/vs/workbench/contrib/debug/browser/breakpointWidget.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 3fdef928009..205c8c80e2f 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -242,7 +242,6 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.triggeredByBreakpointInput = undefined; } else { this.triggeredByBreakpointInput = breakpoints[e.index - 1]; - this.close(true); } }); this.toDispose.push(selectBreakpointBox); @@ -262,7 +261,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi const closeButton = new Button(this.selectBreakpointContainer, defaultButtonStyles); closeButton.label = nls.localize('ok', "Ok"); - this.toDispose.push(closeButton.onDidClick(() => this.close(false))); + this.toDispose.push(closeButton.onDidClick(() => this.close(true))); this.toDispose.push(closeButton); } From 4ceb4ff491f82ca82f3197f48a79f21ca3797aad Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 8 Jan 2024 13:12:28 -0600 Subject: [PATCH 0145/1897] Update src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts --- .../contrib/chat/browser/actions/chatAccessibilityHelp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 681d0e9b07a..302c4d0c61b 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -22,7 +22,7 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'pane content.push(localize('chat.overview', 'The chat view is comprised of an input box and a request/response list. The input box is used to make requests and the list is used to display responses.')); content.push(localize('chat.requestHistory', 'In the input box, use up and down arrows to navigate your request history. Edit input and use enter or the submit button to run a new request.')); content.push(openAccessibleViewKeybinding ? localize('chat.inspectResponse', 'In the input box, inspect the last response in the accessible view via {0}', openAccessibleViewKeybinding) : localize('chat.inspectResponseNoKb', 'With the input box focused, inspect the last response in the accessible view via the Open Accessible View command, which is currently not triggerable by a keybinding.')); - content.push(localize('chat.followUp', 'In the input box, navigate to the suggested follow up question (Shift+Tab) and press Enter to run it. Such follow ups also may exist within the list of responses.')); + content.push(localize('chat.followUp', 'In the input box, navigate to the suggested follow up question (Shift+Tab) and press Enter to run it.')); content.push(localize('chat.announcement', 'Chat responses will be announced as they come in. A response will indicate the number of code blocks, if any, and then the rest of the response.')); content.push(descriptionForCommand('chat.action.focus', localize('workbench.action.chat.focus', 'To focus the chat request/response list, which can be navigated with up and down arrows, invoke the Focus Chat command ({0}).',), localize('workbench.action.chat.focusNoKb', 'To focus the chat request/response list, which can be navigated with up and down arrows, invoke The Focus Chat List command, which is currently not triggerable by a keybinding.'), keybindingService)); content.push(descriptionForCommand('workbench.action.chat.focusInput', localize('workbench.action.chat.focusInput', 'To focus the input box for chat requests, invoke the Focus Chat Input command ({0})'), localize('workbench.action.interactiveSession.focusInputNoKb', 'To focus the input box for chat requests, invoke the Focus Chat Input command, which is currently not triggerable by a keybinding.'), keybindingService)); From 217d2acf638c717420f58942fdba3831bd580d15 Mon Sep 17 00:00:00 2001 From: Gayan Perera Date: Mon, 8 Jan 2024 20:12:56 +0100 Subject: [PATCH 0146/1897] Make sure dependent breakpoints updates are sent to DA before continue --- .../workbench/contrib/debug/browser/debugSession.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 2d73bfc55c6..6691af66b16 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -1231,9 +1231,13 @@ export class DebugSession implements IDebugSession, IDisposable { // moment for breakpoints to set and we want to do our best to not miss // anything if (event.hitBreakpointIds) { - this.enableDependentBreakpoints(event.hitBreakpointIds); + this.enableDependentBreakpoints(event.hitBreakpointIds).then(() => this.runInStatusQueue(event)); + } else { + this.runInStatusQueue(event); } + } + private async runInStatusQueue(event: IRawStoppedDetails) { this.statusQueue.run( this.fetchThreads(event).then(() => event.threadId === undefined ? this.threadIds : [event.threadId]), async (threadId, token) => { @@ -1304,7 +1308,7 @@ export class DebugSession implements IDebugSession, IDisposable { ); } - private enableDependentBreakpoints(hitBreakpointIdsOrThread: Thread | number[]) { + private async enableDependentBreakpoints(hitBreakpointIdsOrThread: Thread | number[]) { let breakpoints: IBreakpoint[]; if (Array.isArray(hitBreakpointIdsOrThread)) { breakpoints = this.model.getBreakpoints().filter(bp => hitBreakpointIdsOrThread.includes(bp.getIdFromAdapter(this.id)!)); @@ -1334,7 +1338,9 @@ export class DebugSession implements IDebugSession, IDisposable { }); }); - urisToResend.forEach((uri) => this.debugService.sendBreakpoints(URI.parse(uri), undefined, this)); + const results: Promise[] = []; + urisToResend.forEach((uri) => results.push(this.debugService.sendBreakpoints(URI.parse(uri), undefined, this))); + return Promise.all(results); } private getBreakpointsAtPosition(uri: URI, startLineNumber: number, endLineNumber: number, startColumn: number, endColumn: number): IBreakpoint[] { From fbb656b4d2c987c58899d6e2d12727841295428e Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 8 Jan 2024 11:14:50 -0800 Subject: [PATCH 0147/1897] Add new pasteAsText command (#202033) Fixes #187507 --- .../browser/copyPasteContribution.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts index 5acc38f2dc4..fbdd84d88d4 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts @@ -6,6 +6,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorCommand, EditorContributionInstantiation, ServicesAccessor, registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { registerEditorFeature } from 'vs/editor/common/editorFeatures'; import { CopyPasteController, changePasteTypeCommandId, pasteWidgetVisibleCtx } from 'vs/editor/contrib/dropOrPasteInto/browser/copyPasteController'; import { DefaultPasteProvidersFeature } from 'vs/editor/contrib/dropOrPasteInto/browser/defaultProviders'; @@ -39,7 +40,7 @@ registerEditorAction(class extends EditorAction { id: 'editor.action.pasteAs', label: nls.localize('pasteAs', "Paste As..."), alias: 'Paste As...', - precondition: undefined, + precondition: EditorContextKeys.writable, metadata: { description: 'Paste as', args: [{ @@ -63,3 +64,18 @@ registerEditorAction(class extends EditorAction { return CopyPasteController.get(editor)?.pasteAs(id); } }); + +registerEditorAction(class extends EditorAction { + constructor() { + super({ + id: 'editor.action.pasteAsText', + label: nls.localize('pasteAsText', "Paste as Text"), + alias: 'Paste as Text', + precondition: EditorContextKeys.writable, + }); + } + + public override run(_accessor: ServicesAccessor, editor: ICodeEditor, args: any) { + return CopyPasteController.get(editor)?.pasteAs('text'); + } +}); From 06f465d7ae96e87341c9b6df97fb85774b41e9fc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 8 Jan 2024 13:17:48 -0600 Subject: [PATCH 0148/1897] localize --- src/vs/workbench/contrib/chat/browser/chatFollowups.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts index aed879a9c23..6c6e0d8f6d7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts +++ b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts @@ -7,6 +7,7 @@ import * as dom from 'vs/base/browser/dom'; import { Button, IButtonStyles } from 'vs/base/browser/ui/button/button'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; @@ -39,7 +40,7 @@ export class ChatFollowups extends Disposable { } else if (followup.kind === 'command') { button.element.classList.add('interactive-followup-command'); } - button.element.ariaLabel = `Follow up question: ${followup.title}`; + button.element.ariaLabel = localize('followUpAriaLabel', "Follow up question: {0}", followup.title); const label = followup.kind === 'reply' ? '$(sparkle) ' + (followup.title || followup.message) : followup.title; From e1f8dddce9f083e170d0cc13f634b551ccf72105 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Mon, 8 Jan 2024 12:48:16 -0700 Subject: [PATCH 0149/1897] Handle empty paste array (#202038) --- src/vs/workbench/contrib/files/browser/fileActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 69959a9df8d..5ac909eb123 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -1097,7 +1097,7 @@ export const pasteFileHandler = async (accessor: ServicesAccessor, fileList?: Fi const toPaste = await getFilesToPaste(fileList, clipboardService); - if (confirmPasteNative) { + if (confirmPasteNative && toPaste?.length >= 1) { const message = toPaste.length > 1 ? nls.localize('confirmMultiPasteNative', "Are you sure you want to paste the following {0} items?", toPaste.length) : nls.localize('confirmPasteNative', "Are you sure you want to paste '{0}'?", basename(toPaste[0].fsPath)); From 66023e11e65760d18069ffe6d3d715752a24f8a2 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 8 Jan 2024 20:50:42 +0100 Subject: [PATCH 0150/1897] =?UTF-8?q?Git=20-=20=F0=9F=92=84=20clean-up=20s?= =?UTF-8?q?ummary=20generation=20(#202036)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/git/src/historyProvider.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 0e882103140..cd32a38c1aa 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -81,14 +81,16 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec const refParentId = options.limit.id; const refId = await this.repository.revParse(historyItemGroupId) ?? ''; - const [summary, commits] = await Promise.all([ - this.getSummaryHistoryItem(refId, refParentId), - this.repository.log({ range: `${refParentId}..${refId}`, shortStats: true, sortByAuthorDate: true }) - ]); + const historyItems: SourceControlHistoryItem[] = []; + const commits = await this.repository.log({ range: `${refParentId}..${refId}`, shortStats: true, sortByAuthorDate: true }); + + if (commits.length >= 2) { + const allChanges = await this.repository.diffBetweenShortStat(refParentId, refId); + historyItems.push({ id: refId, parentIds: [refParentId], icon: new ThemeIcon('files'), label: l10n.t('All Changes'), statistics: allChanges }); + } await ensureEmojis(); - const historyItems = commits.length < 2 ? [] : [summary]; historyItems.push(...commits.map(commit => { const newLineIndex = commit.message.indexOf('\n'); const subject = newLineIndex !== -1 ? commit.message.substring(0, newLineIndex) : commit.message; @@ -203,11 +205,6 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return new FileDecoration(letter, tooltip, color); } - private async getSummaryHistoryItem(refId: string, refParentId: string): Promise { - const statistics = await this.repository.diffBetweenShortStat(refParentId, refId); - return { id: refId, parentIds: [refParentId], icon: new ThemeIcon('files'), label: l10n.t('All Changes'), statistics }; - } - dispose(): void { this.disposables.forEach(d => d.dispose()); } From 88a6ed6d9b81017207d03e537b4fb39648808faf Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 8 Jan 2024 12:32:02 -0800 Subject: [PATCH 0151/1897] Only show paste as html when it is explicitly requested (#202039) Fixes #202013 --- src/vs/editor/common/languages.ts | 10 +++++++++- .../browser/copyPasteController.ts | 18 +++++++++--------- .../browser/defaultProviders.ts | 10 +++++++--- .../api/browser/mainThreadLanguageFeatures.ts | 2 +- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 7a362c9d9d2..67d9d60a925 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -812,6 +812,14 @@ export interface DocumentPasteEdit { additionalEdit?: WorkspaceEdit; } +/** + * @internal + */ +export interface DocumentPasteContext { + readonly only?: string; + readonly trigger: 'explicit' | 'implicit'; +} + /** * @internal */ @@ -824,7 +832,7 @@ export interface DocumentPasteEditProvider { prepareDocumentPaste?(model: model.ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise; - provideDocumentPasteEdits?(model: model.ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise; + provideDocumentPasteEdits?(model: model.ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise; } /** diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts index 59281b6154e..acffc02602d 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts @@ -20,7 +20,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IRange, Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { Handler, IEditorContribution, PastePayload } from 'vs/editor/common/editorCommon'; -import { DocumentPasteEdit, DocumentPasteEditProvider } from 'vs/editor/common/languages'; +import { DocumentPasteContext, DocumentPasteEdit, DocumentPasteEditProvider } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { createCombinedWorkspaceEdit, sortEditsByYieldTo } from 'vs/editor/contrib/dropOrPasteInto/browser/edit'; @@ -252,13 +252,13 @@ export class CopyPasteController extends Disposable implements IEditorContributi e.stopImmediatePropagation(); if (this._pasteAsActionContext) { - this.showPasteAsPick(this._pasteAsActionContext.preferredId, allProviders, selections, dataTransfer, metadata); + this.showPasteAsPick(this._pasteAsActionContext.preferredId, allProviders, selections, dataTransfer, metadata, { trigger: 'explicit', only: this._pasteAsActionContext.preferredId }); } else { - this.doPasteInline(allProviders, selections, dataTransfer, metadata); + this.doPasteInline(allProviders, selections, dataTransfer, metadata, { trigger: 'implicit' }); } } - private doPasteInline(allProviders: readonly DocumentPasteEditProvider[], selections: readonly Selection[], dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined): void { + private doPasteInline(allProviders: readonly DocumentPasteEditProvider[], selections: readonly Selection[], dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, context: DocumentPasteContext): void { const p = createCancelablePromise(async (token) => { const editor = this._editor; if (!editor.hasModel()) { @@ -282,7 +282,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi return; } - const providerEdits = await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, tokenSource.token); + const providerEdits = await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, tokenSource.token); if (tokenSource.token.isCancellationRequested) { return; } @@ -311,7 +311,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi this._currentPasteOperation = p; } - private showPasteAsPick(preferredId: string | undefined, allProviders: readonly DocumentPasteEditProvider[], selections: readonly Selection[], dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined): void { + private showPasteAsPick(preferredId: string | undefined, allProviders: readonly DocumentPasteEditProvider[], selections: readonly Selection[], dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, context: DocumentPasteContext): void { const p = createCancelablePromise(async (token) => { const editor = this._editor; if (!editor.hasModel()) { @@ -333,7 +333,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi supportedProviders = supportedProviders.filter(edit => edit.id === preferredId); } - const providerEdits = await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, tokenSource.token); + const providerEdits = await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, tokenSource.token); if (tokenSource.token.isCancellationRequested) { return; } @@ -437,11 +437,11 @@ 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[], context: DocumentPasteContext, token: CancellationToken): Promise> { const results = await raceCancellation( Promise.all(providers.map(async provider => { try { - const edit = await provider.provideDocumentPasteEdits?.(model, selections, dataTransfer, token); + const edit = await provider.provideDocumentPasteEdits?.(model, selections, dataTransfer, context, token); if (edit) { return { ...edit, providerId: provider.id }; } diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts index 7ba4cd9949e..27812236c50 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts @@ -13,7 +13,7 @@ import { relativePath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange } from 'vs/editor/common/core/range'; -import { DocumentOnDropEdit, DocumentOnDropEditProvider, DocumentPasteEdit, DocumentPasteEditProvider } from 'vs/editor/common/languages'; +import { DocumentOnDropEdit, DocumentOnDropEditProvider, DocumentPasteContext, DocumentPasteEdit, DocumentPasteEditProvider } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { localize } from 'vs/nls'; @@ -27,7 +27,7 @@ abstract class SimplePasteAndDropProvider implements DocumentOnDropEditProvider, abstract readonly dropMimeTypes: readonly string[] | undefined; abstract readonly pasteMimeTypes: readonly string[]; - async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise { + async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise { const edit = await this.getEdit(dataTransfer, token); return edit ? { insertText: edit.insertText, label: edit.label, detail: edit.detail, handledMimeType: edit.handledMimeType, yieldTo: edit.yieldTo } : undefined; } @@ -160,7 +160,11 @@ class PasteHtmlProvider implements DocumentPasteEditProvider { private readonly _yieldTo = [{ mimeType: Mimes.text }]; - async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise { + async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise { + if (context.trigger !== 'explicit' && context.only !== this.id) { + return; + } + const entry = dataTransfer.get('text/html'); const htmlText = await entry?.asString(); if (!htmlText || token.isCancellationRequested) { diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index a9fb937ed58..1db2d6cd5bf 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -1009,7 +1009,7 @@ class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider } if (metadata.supportsPaste) { - this.provideDocumentPasteEdits = async (model: ITextModel, selections: Selection[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken) => { + this.provideDocumentPasteEdits = async (model: ITextModel, selections: Selection[], dataTransfer: IReadonlyVSDataTransfer, context: languages.DocumentPasteContext, token: CancellationToken) => { const request = this.dataTransfers.add(dataTransfer); try { const dataTransferDto = await typeConvert.DataTransfer.from(dataTransfer); From 891a17ab5736b827acf162a2b7b1e85517f0e642 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 8 Jan 2024 21:39:07 +0100 Subject: [PATCH 0152/1897] Git - move "Open Commit" command from core to the git extension (#202042) * Initial implementation * Move the command to the git extension * Add missing enablement property --- extensions/git/package.json | 36 ++++++- extensions/git/package.nls.json | 2 +- extensions/git/src/commands.ts | 26 ++++- extensions/git/src/repository.ts | 9 +- src/vs/platform/actions/common/actions.ts | 3 +- src/vs/workbench/api/browser/mainThreadSCM.ts | 2 + src/vs/workbench/contrib/scm/browser/menus.ts | 17 ++-- .../contrib/scm/browser/scmViewPane.ts | 94 ++++++++----------- .../workbench/contrib/scm/common/history.ts | 3 +- .../actions/common/menusExtensionPoint.ts | 12 +++ .../common/extensionsApiProposals.ts | 1 + ...d.contribSourceControlHistoryItemMenu.d.ts | 7 ++ 12 files changed, 137 insertions(+), 75 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemMenu.d.ts diff --git a/extensions/git/package.json b/extensions/git/package.json index 9ac30f041e3..c41d7ceb386 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -24,7 +24,8 @@ "tabInputTextMerge", "timeline", "contribMergeEditorMenus", - "contribSourceControlInputBoxMenu" + "contribSourceControlInputBoxMenu", + "contribSourceControlHistoryItemMenu" ], "categories": [ "Other" @@ -708,7 +709,7 @@ }, { "command": "git.timeline.openCommit", - "title": "%command.timelineOpenCommit%", + "title": "%command.openCommit%", "icon": "$(diff-multiple)", "category": "Git" }, @@ -776,13 +777,22 @@ "command": "git.viewChanges", "title": "%command.viewChanges%", "icon": "$(diff-multiple)", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.viewStagedChanges", "title": "%command.viewStagedChanges%", "icon": "$(diff-multiple)", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.openCommit", + "title": "%command.openCommit%", + "icon": "$(diff-multiple)", + "category": "Git", + "enablement": "!operationInProgress" } ], "continueEditSession": [ @@ -1274,6 +1284,10 @@ { "command": "git.viewStagedChanges", "when": "false" + }, + { + "command": "git.openCommit", + "when": "false" } ], "scm/title": [ @@ -1751,6 +1765,20 @@ "group": "1_modification@3" } ], + "scm/incoming/historyItem/context": [ + { + "command": "git.openCommit", + "when": "scmProvider == git && config.multiDiffEditor.experimental.enabled", + "group": "inline@1" + } + ], + "scm/outgoing/historyItem/context": [ + { + "command": "git.openCommit", + "when": "scmProvider == git && config.multiDiffEditor.experimental.enabled", + "group": "inline@1" + } + ], "editor/title": [ { "command": "git.openFile", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 627b6195828..8903480d364 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -106,7 +106,6 @@ "command.stashDropAll": "Drop All Stashes...", "command.stashPreview": "Preview Stash...", "command.timelineOpenDiff": "Open Changes", - "command.timelineOpenCommit": "Open Commit", "command.timelineCopyCommitId": "Copy Commit ID", "command.timelineCopyCommitMessage": "Copy Commit Message", "command.timelineSelectForCompare": "Select for Compare", @@ -115,6 +114,7 @@ "command.openRepositoriesInParentFolders": "Open Repositories In Parent Folders", "command.viewChanges": "View Changes", "command.viewStagedChanges": "View Staged Changes", + "command.openCommit": "Open Commit", "command.api.getRepositories": "Get Repositories", "command.api.getRepositoryState": "Get Repository State", "command.api.getRemoteSources": "Get Remote Sources", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 892e3698e56..ad4e32920b9 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ import * as os from 'os'; import * as path from 'path'; -import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon } from 'vscode'; +import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import { ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git'; @@ -3617,7 +3617,6 @@ export class CommandCenter { @command('git.timeline.openCommit', { repository: false }) async timelineOpenCommit(item: TimelineItem, uri: Uri | undefined, _source: string) { - console.log('timelineOpenCommit', item); if (!GitTimelineItem.is(item)) { return; } @@ -3840,6 +3839,29 @@ export class CommandCenter { commands.executeCommand('vscode.changes', title, args); } + @command('git.openCommit', { repository: true }) + async openCommit(repository: Repository, historyItem: SourceControlHistoryItem): Promise { + if (!repository || !historyItem) { + return; + } + + const historyProvider = repository.historyProvider; + const historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined; + const historyItemChanges = await historyProvider.provideHistoryItemChanges(historyItem.id, historyItemParentId); + + if (historyItemChanges.length === 0) { + return; + } + + const modifiedShortRef = historyItem.id.substring(0, 8); + const originalShortRef = historyItem.parentIds.length > 0 ? historyItem.parentIds[0].substring(0, 8) : `${modifiedShortRef}^`; + + const title = l10n.t('Changes ({0} ↔ {1})', originalShortRef, modifiedShortRef); + const args = historyItemChanges.map(change => [change.uri, change.originalUri, change.modifiedUri]); + + commands.executeCommand('vscode.changes', title, args); + } + private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any { const result = (...args: any[]) => { let result: Promise; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 42f90428a2c..e512d5f0bed 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -796,6 +796,9 @@ export class Repository implements Disposable { return this.repository.dotGit; } + private _historyProvider: GitHistoryProvider; + get historyProvider(): GitHistoryProvider { return this._historyProvider; } + private isRepositoryHuge: false | { limit: number } = false; private didWarnAboutLimit = false; @@ -850,9 +853,9 @@ export class Repository implements Disposable { this._sourceControl.quickDiffProvider = this; - const historyProvider = new GitHistoryProvider(this, logger); - this._sourceControl.historyProvider = historyProvider; - this.disposables.push(historyProvider); + this._historyProvider = new GitHistoryProvider(this, logger); + this._sourceControl.historyProvider = this._historyProvider; + this.disposables.push(this._historyProvider); this._sourceControl.acceptInputCommand = { command: 'git.commit', title: l10n.t('Commit'), arguments: [this._sourceControl] }; this._sourceControl.inputBox.validateInput = this.validateInput.bind(this); diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 5b48ab3dfd6..9346da55bbf 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -107,7 +107,8 @@ export class MenuId { static readonly OpenEditorsContextShare = new MenuId('OpenEditorsContextShare'); static readonly ProblemsPanelContext = new MenuId('ProblemsPanelContext'); static readonly SCMInputBox = new MenuId('SCMInputBox'); - static readonly SCMHistoryItem = new MenuId('SCMHistoryItem'); + static readonly SCMIncomingHistoryItemContext = new MenuId('SCMIncomingHistoryItemContext'); + static readonly SCMOutgoingHistoryItemContext = new MenuId('SCMOutgoingHistoryItemContext'); static readonly SCMChangeContext = new MenuId('SCMChangeContext'); static readonly SCMResourceContext = new MenuId('SCMResourceContext'); static readonly SCMResourceContextShare = new MenuId('SCMResourceContextShare'); diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index f42a0b5d9f4..e0afc9bed49 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -158,6 +158,7 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider { id: historyItemGroupBase.id, label: historyItemGroupBase.label, icon: Codicon.arrowCircleDown, + direction: 'incoming', ancestor: ancestor.id, count: ancestor.behind, }; @@ -167,6 +168,7 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider { id: historyItemGroup.id, label: historyItemGroup.label, icon: Codicon.arrowCircleUp, + direction: 'outgoing', ancestor: ancestor.id, count: ancestor.ahead, }; diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index 7a2dce84f38..fecb988c5ad 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -15,7 +15,7 @@ import { equals } from 'vs/base/common/arrays'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { localize } from 'vs/nls'; -import { ISCMHistoryItem, ISCMHistoryProviderMenus } from 'vs/workbench/contrib/scm/common/history'; +import { ISCMHistoryItem, ISCMHistoryItemGroupEntry, ISCMHistoryProviderMenus } from 'vs/workbench/contrib/scm/common/history'; function actionEquals(a: IAction, b: IAction): boolean { return a.id === b.id; @@ -177,7 +177,7 @@ export class SCMRepositoryMenus implements ISCMRepositoryMenus, IDisposable { private _historyProviderMenu: SCMHistoryProviderMenus | undefined; get historyProviderMenu(): SCMHistoryProviderMenus | undefined { if (this.provider.historyProvider && !this._historyProviderMenu) { - this._historyProviderMenu = this.instantiationService.createInstance(SCMHistoryProviderMenus); + this._historyProviderMenu = new SCMHistoryProviderMenus(this.contextKeyService, this.menuService); this.disposables.add(this._historyProviderMenu); } @@ -189,7 +189,7 @@ export class SCMRepositoryMenus implements ISCMRepositoryMenus, IDisposable { constructor( private readonly provider: ISCMProvider, @IContextKeyService contextKeyService: IContextKeyService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IMenuService private readonly menuService: IMenuService ) { this.contextKeyService = contextKeyService.createOverlay([ @@ -261,11 +261,11 @@ export class SCMHistoryProviderMenus implements ISCMHistoryProviderMenus, IDispo @IContextKeyService private readonly contextKeyService: IContextKeyService, @IMenuService private readonly menuService: IMenuService) { } - getHistoryItemMenu(historyItem: ISCMHistoryItem): IMenu { - return this.getOrCreateHistoryItemMenu(historyItem); + getHistoryItemMenu(historyItemGroup: ISCMHistoryItemGroupEntry, historyItem: ISCMHistoryItem): IMenu { + return this.getOrCreateHistoryItemMenu(historyItemGroup, historyItem); } - private getOrCreateHistoryItemMenu(historyItem: ISCMHistoryItem): IMenu { + private getOrCreateHistoryItemMenu(historyItemGroup: ISCMHistoryItemGroupEntry, historyItem: ISCMHistoryItem): IMenu { let result = this.historyItemMenus.get(historyItem); if (!result) { @@ -273,7 +273,10 @@ export class SCMHistoryProviderMenus implements ISCMHistoryProviderMenus, IDispo ['scmHistoryItem', historyItem.id], ]); - result = this.menuService.createMenu(MenuId.SCMHistoryItem, contextKeyService); + const menuId = historyItemGroup.direction === 'incoming' ? + MenuId.SCMIncomingHistoryItemContext : MenuId.SCMOutgoingHistoryItemContext; + + result = this.menuService.createMenu(menuId, contextKeyService); this.historyItemMenus.set(historyItem, result); } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index f2aa3236d63..ca3168ffc3f 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -11,7 +11,7 @@ import { ViewPane, IViewPaneOptions, ViewAction } from 'vs/workbench/browser/par import { append, $, Dimension, asCSSUrl, trackFocus, clearNode, prepend } from 'vs/base/browser/dom'; import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryProviderCacheEntry, SCMHistoryItemChangeTreeElement, SCMHistoryItemGroupTreeElement, SCMHistoryItemTreeElement, SCMViewSeparatorElement } from 'vs/workbench/contrib/scm/common/history'; -import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, ISCMService, SCMInputChangeReason, VIEW_PANE_ID, ISCMActionButton, ISCMActionButtonDescriptor, ISCMRepositorySortKey, REPOSITORIES_VIEW_PANE_ID, ISCMInputValueProviderContext } from 'vs/workbench/contrib/scm/common/scm'; +import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, ISCMService, SCMInputChangeReason, VIEW_PANE_ID, ISCMActionButton, ISCMActionButtonDescriptor, ISCMRepositorySortKey, REPOSITORIES_VIEW_PANE_ID, ISCMInputValueProviderContext, ISCMProvider } from 'vs/workbench/contrib/scm/common/scm'; import { ResourceLabels, IResourceLabel, IFileLabelOptions } from 'vs/workbench/browser/labels'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -21,7 +21,7 @@ import { IContextKeyService, IContextKey, ContextKeyExpr, RawContextKey } from ' import { ICommandService } from 'vs/platform/commands/common/commands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { MenuItemAction, IMenuService, registerAction2, MenuId, IAction2Options, MenuRegistry, Action2, IMenu } from 'vs/platform/actions/common/actions'; -import { IAction, ActionRunner, Action, Separator } from 'vs/base/common/actions'; +import { IAction, ActionRunner, Action, Separator, IActionRunner } from 'vs/base/common/actions'; import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService'; import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, getActionViewItemProvider, isSCMActionButton, isSCMViewService, isSCMHistoryItemGroupTreeElement, isSCMHistoryItemTreeElement, isSCMHistoryItemChangeTreeElement, toDiffEditorArguments, isSCMResourceNode, isSCMHistoryItemChangeNode, isSCMViewSeparator } from './util'; @@ -808,6 +808,29 @@ class HistoryItemGroupRenderer implements ICompressibleTreeRenderer { + if (!(action instanceof MenuItemAction)) { + return super.runAction(action, context); + } + + const args: (ISCMProvider | ISCMHistoryItem)[] = []; + args.push(context.historyItemGroup.repository.provider); + args.push({ + id: context.id, + parentIds: context.parentIds, + label: context.label, + description: context.description, + icon: context.icon, + timestamp: context.timestamp, + statistics: context.statistics, + } satisfies ISCMHistoryItem); + + await action.run(...args); + } +} + interface HistoryItemTemplate { readonly iconContainer: HTMLElement; readonly label: IconLabel; @@ -826,6 +849,7 @@ class HistoryItemRenderer implements ICompressibleTreeRenderer { registerAction2(CollapseAllRepositoriesAction); registerAction2(ExpandAllRepositoriesAction); -class HistoryItemViewChangesAction extends Action2 { - - constructor() { - super({ - id: `workbench.scm.action.historyItemViewChanges`, - title: localize('historyItemViewChanges', "View Changes"), - icon: Codicon.diffMultiple, - f1: false, - menu: { - id: MenuId.SCMHistoryItem, - group: 'inline', - when: ContextKeyExpr.has('config.multiDiffEditor.experimental.enabled'), - } - }); - } - - async run(accessor: ServicesAccessor, historyItem: SCMHistoryItemTreeElement): Promise { - const commandService = accessor.get(ICommandService); - - const historyProvider = historyItem.historyItemGroup.repository.provider.historyProvider; - if (!historyProvider) { - return; - } - - const historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined; - const historyItemChanges = await historyProvider.provideHistoryItemChanges(historyItem.id, historyItemParentId); - if (!historyItemChanges || historyItemChanges.length === 0) { - return; - } - - let [originalRef, modifiedRef] = historyItem.id.includes('..') - ? historyItem.id.split('..').map(id => id.substring(0, 8)) : [undefined, historyItem.id.substring(0, 8)]; - - if (!originalRef) { - originalRef = historyItem.parentIds.length > 0 ? historyItem.parentIds[0].substring(0, 8) : `${modifiedRef}^`; - } - - const title = localize('historyItemChangesTitle', "Changes ({0} ↔ {1})", originalRef, modifiedRef); - const args = historyItemChanges.map(change => [change.uri, change.originalUri, change.modifiedUri]); - - return commandService.executeCommand('_workbench.changes', title, args); - } - -} - -registerAction2(HistoryItemViewChangesAction); - const enum SCMInputWidgetCommandId { CancelAction = 'scm.input.cancelAction' } @@ -2742,9 +2720,13 @@ export class SCMViewPane extends ViewPane { this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this.disposables.add(this.listLabels); - const actionRunner = new RepositoryPaneActionRunner(() => this.getSelectedResources()); - actionRunner.onWillRun(() => this.tree.domFocus(), this, this.disposables); - this.disposables.add(actionRunner); + const resourceActionRunner = new RepositoryPaneActionRunner(() => this.getSelectedResources()); + resourceActionRunner.onWillRun(() => this.tree.domFocus(), this, this.disposables); + this.disposables.add(resourceActionRunner); + + const historyItemActionRunner = new HistoryItemActionRunner(); + historyItemActionRunner.onWillRun(() => this.tree.domFocus(), this, this.disposables); + this.disposables.add(historyItemActionRunner); const treeDataSource = this.instantiationService.createInstance(SCMTreeDataSource, () => this.viewMode, () => this.alwaysShowRepositories, () => this.showActionButton, () => this.showIncomingChanges, () => this.showOutgoingChanges); this.disposables.add(treeDataSource); @@ -2760,9 +2742,9 @@ export class SCMViewPane extends ViewPane { this.actionButtonRenderer, this.instantiationService.createInstance(RepositoryRenderer, MenuId.SCMTitle, getActionViewItemProvider(this.instantiationService)), this.instantiationService.createInstance(ResourceGroupRenderer, getActionViewItemProvider(this.instantiationService)), - this.instantiationService.createInstance(ResourceRenderer, () => this.viewMode, this.listLabels, getActionViewItemProvider(this.instantiationService), actionRunner), + this.instantiationService.createInstance(ResourceRenderer, () => this.viewMode, this.listLabels, getActionViewItemProvider(this.instantiationService), resourceActionRunner), this.instantiationService.createInstance(HistoryItemGroupRenderer), - this.instantiationService.createInstance(HistoryItemRenderer, getActionViewItemProvider(this.instantiationService)), + this.instantiationService.createInstance(HistoryItemRenderer, historyItemActionRunner, getActionViewItemProvider(this.instantiationService)), this.instantiationService.createInstance(HistoryItemChangeRenderer, () => this.viewMode, this.listLabels), this.instantiationService.createInstance(SeparatorRenderer) ], diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index d1e61ec676c..db2e8ed4bf7 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -10,7 +10,7 @@ import { IMenu } from 'vs/platform/actions/common/actions'; import { ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; export interface ISCMHistoryProviderMenus { - getHistoryItemMenu(historyItem: ISCMHistoryItem): IMenu; + getHistoryItemMenu(historyItemGroup: ISCMHistoryItemGroupEntry, historyItem: ISCMHistoryItem): IMenu; } export interface ISCMHistoryProvider { @@ -57,6 +57,7 @@ export interface ISCMHistoryItemGroupDetails { export interface ISCMHistoryItemGroupEntry { readonly id: string; readonly label: string; + readonly direction: 'incoming' | 'outgoing'; readonly icon?: URI | { light: URI; dark: URI } | ThemeIcon; readonly description?: string; readonly ancestor?: string; diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 8bf3f2c149f..3d62dc3f751 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -150,6 +150,18 @@ const apiMenus: IAPIMenu[] = [ description: localize('menus.input', "The Source Control input box menu"), proposed: 'contribSourceControlInputBoxMenu' }, + { + key: 'scm/incoming/historyItem/context', + id: MenuId.SCMIncomingHistoryItemContext, + description: localize('menus.incomingHistoryItemContext', "The Source Control incoming history item context menu"), + proposed: 'contribSourceControlHistoryItemMenu' + }, + { + key: 'scm/outgoing/historyItem/context', + id: MenuId.SCMOutgoingHistoryItemContext, + description: localize('menus.outgoingHistoryItemContext', "The Source Control outgoing history item context menu"), + proposed: 'contribSourceControlHistoryItemMenu' + }, { key: 'statusBar/remoteIndicator', id: MenuId.StatusBarRemoteIndicatorMenu, diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 3ebb6ae371d..0d20d085e99 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -32,6 +32,7 @@ export const allApiProposals = Object.freeze({ contribNotebookStaticPreloads: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribNotebookStaticPreloads.d.ts', contribRemoteHelp: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribRemoteHelp.d.ts', contribShareMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts', + contribSourceControlHistoryItemMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemMenu.d.ts', contribSourceControlInputBoxMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlInputBoxMenu.d.ts', contribStatusBarItems: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribStatusBarItems.d.ts', contribViewsRemote: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts', diff --git a/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemMenu.d.ts b/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemMenu.d.ts new file mode 100644 index 00000000000..44bfaf17b01 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemMenu.d.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `scm/historyItem/context`-menu contribution point +// https://github.com/microsoft/vscode/issues/201997 From 851b39876e521ee04a037bf4ec279524faff4667 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 8 Jan 2024 21:50:59 +0100 Subject: [PATCH 0153/1897] [json] don't use md5 (#201971) --- .../json-language-features/client/src/node/schemaCache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/json-language-features/client/src/node/schemaCache.ts b/extensions/json-language-features/client/src/node/schemaCache.ts index ad14e3228a9..ad373657591 100644 --- a/extensions/json-language-features/client/src/node/schemaCache.ts +++ b/extensions/json-language-features/client/src/node/schemaCache.ts @@ -143,5 +143,5 @@ export class JSONSchemaCache { } } function getCacheFileName(uri: string): string { - return `${createHash('MD5').update(uri).digest('hex')}.schema.json`; + return `${createHash('sha256').update(uri).digest('hex')}.schema.json`; } From 0a71ec927ce51ef5cc43d3c7af1da4961c9371e0 Mon Sep 17 00:00:00 2001 From: RedCMD <33529441+RedCMD@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:10:59 +1300 Subject: [PATCH 0154/1897] Change lowercase `l` to uppercase `L`. `asp-vb-net.tmLanguage.json` (#201981) * Change lowercase `l` to uppercase `L`. `asp-vb-net.tmLanguage.json` `.tmLanguage.json` is the common file extension for JSON TextMate while `vb` is currently using `.tmlanguage.json` * Rename file. * Remove EOF newline >:( --------- Co-authored-by: Martin Aeschlimann --- extensions/vb/package.json | 4 ++-- ...{asp-vb-net.tmlanguage.json => asp-vb-net.tmLanguage.json} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename extensions/vb/syntaxes/{asp-vb-net.tmlanguage.json => asp-vb-net.tmLanguage.json} (99%) diff --git a/extensions/vb/package.json b/extensions/vb/package.json index 801ef7180da..6abf5ca5cc1 100644 --- a/extensions/vb/package.json +++ b/extensions/vb/package.json @@ -9,7 +9,7 @@ "vscode": "*" }, "scripts": { - "update-grammar": "node ../node_modules/vscode-grammar-updater/bin textmate/asp.vb.net.tmbundle Syntaxes/ASP%20VB.net.plist ./syntaxes/asp-vb-net.tmlanguage.json" + "update-grammar": "node ../node_modules/vscode-grammar-updater/bin textmate/asp.vb.net.tmbundle Syntaxes/ASP%20VB.net.plist ./syntaxes/asp-vb-net.tmLanguage.json" }, "contributes": { "languages": [ @@ -33,7 +33,7 @@ { "language": "vb", "scopeName": "source.asp.vb.net", - "path": "./syntaxes/asp-vb-net.tmlanguage.json" + "path": "./syntaxes/asp-vb-net.tmLanguage.json" } ], "snippets": [ diff --git a/extensions/vb/syntaxes/asp-vb-net.tmlanguage.json b/extensions/vb/syntaxes/asp-vb-net.tmLanguage.json similarity index 99% rename from extensions/vb/syntaxes/asp-vb-net.tmlanguage.json rename to extensions/vb/syntaxes/asp-vb-net.tmLanguage.json index 262f9c9750b..b4aae18e4b1 100644 --- a/extensions/vb/syntaxes/asp-vb-net.tmlanguage.json +++ b/extensions/vb/syntaxes/asp-vb-net.tmLanguage.json @@ -235,4 +235,4 @@ ] } } -} \ No newline at end of file +} From d2a02721bda2db82641c143b5deea699a4ef005c Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Mon, 8 Jan 2024 16:25:55 -0600 Subject: [PATCH 0155/1897] Add icon support for quickpick custom button (#201913) --- .../browser/quickInputController.ts | 2 +- .../quickTextSearch/textSearchQuickAccess.ts | 23 +++++++++++++------ .../contrib/search/browser/searchIcons.ts | 2 +- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index db5440e3d84..f5c2d37135e 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -150,7 +150,7 @@ export class QuickInputController extends Disposable { })); const customButtonContainer = dom.append(headerContainer, $('.quick-input-action')); - const customButton = this._register(new Button(customButtonContainer, this.styles.button)); + const customButton = this._register(new Button(customButtonContainer, { ...this.styles.button, supportIcons: true })); customButton.label = localize('custom', "Custom"); this._register(customButton.onDidClick(e => { this.onDidCustomEmitter.fire(); diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index 1a851121bdd..6c141676972 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -21,7 +21,7 @@ import { IKeyMods, IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/pl import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; import { IViewsService } from 'vs/workbench/common/views'; -import { searchDetailsIcon, searchOpenInFileIcon, searchSeeMoreIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; +import { searchDetailsIcon, searchOpenInFileIcon, searchActivityBarIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import { FileMatch, Match, RenderableMatch, SearchModel, searchComparer } from 'vs/workbench/contrib/search/browser/searchModel'; import { SearchView, getEditorSelectionFromMatch } from 'vs/workbench/contrib/search/browser/searchView'; import { IWorkbenchSearchConfiguration, getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; @@ -86,6 +86,12 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { + this.moveToSearchViewlet(this.searchModel, undefined); + picker.hide(); + }); disposables.add(super.provide(picker, token, runOptions)); disposables.add(picker.onDidHide(() => this.searchModel.searchResult.toggleHighlights(false))); disposables.add(picker.onDidAccept(() => this.searchModel.searchResult.toggleHighlights(false))); @@ -141,7 +147,7 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider | undefined = viewlet?.getControl(); - - viewer.setFocus([currentElem], getSelectionKeyboardEvent()); - viewer.setSelection([currentElem], getSelectionKeyboardEvent()); - viewer.reveal(currentElem); + if (currentElem) { + viewer.setFocus([currentElem], getSelectionKeyboardEvent()); + viewer.setSelection([currentElem], getSelectionKeyboardEvent()); + viewer.reveal(currentElem); + } else { + viewlet.searchAndReplaceWidget.focus(); + } } private _getPicksFromMatches(matches: FileMatch[], limit: number): (IQuickPickSeparator | IPickerQuickAccessItem)[] { @@ -221,7 +230,7 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider Date: Mon, 8 Jan 2024 14:31:37 -0800 Subject: [PATCH 0156/1897] Don't enable notebook copy if active element is not in the notebook dom (#202035) Fixes #201178 Fixes copying text from the chat window when a notebook editor is active --- .../browser/contrib/clipboard/notebookClipboard.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts index 694b84d87ee..47b68f9f10b 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts @@ -30,7 +30,7 @@ import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { ILogService } from 'vs/platform/log/common/log'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { showWindowLogActionId } from 'vs/workbench/services/log/common/logConstants'; -import { getActiveElement, getWindow } from 'vs/base/browser/dom'; +import { getActiveElement, getWindow, isAncestor } from 'vs/base/browser/dom'; let _logging: boolean = false; function toggleLogging() { @@ -343,8 +343,8 @@ export class NotebookClipboardContribution extends Disposable { runCopyAction(accessor: ServicesAccessor) { const loggerService = accessor.get(ILogService); - const activeElement = getActiveElement(); - if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { + const activeElement = getActiveElement(); + if (activeElement instanceof HTMLElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { _log(loggerService, '[NotebookEditor] focus is on input or textarea element, bypass'); return false; } @@ -355,6 +355,11 @@ export class NotebookClipboardContribution extends Disposable { return false; } + if (!isAncestor(activeElement, editor.getDomNode())) { + _log(loggerService, '[NotebookEditor] focus is outside of the notebook editor, bypass'); + return false; + } + if (this._focusInsideEmebedMonaco(editor)) { _log(loggerService, '[NotebookEditor] focus is on embed monaco editor, bypass'); return false; From 0e743a2d912fc41ec1326e98c9b9c722392ee782 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 8 Jan 2024 16:04:32 -0800 Subject: [PATCH 0157/1897] testing: add initial editor decorations This is the first pass at decorations in-editor. This PR doesn't actually register the contribution, as it's not ready for selfhosting yet. This PR creates decorations that look like this. The idea is that coverage decorations in the glyph margin will always be visibile when there's coverage, and users can get coverage in their code via hover or shortcut, with the intention of making coverage unobtrusive and easy to run all the time. ![](https://memes.peet.io/img/24-01-8e61f4db-f115-4732-affe-59dea879a335.png) The notable thing is that there is now a third glyph margin row. I reworked some of the editor code to handle this. ![](https://memes.peet.io/img/24-01-f400369f-650c-4303-be65-e65903f8ad17.png) Some open questions: - The glyph margin coverage wants doesn't need to be full-width, should we add a new 'leftmost' glyph lane instead that's thinner? - Adding breakpoints in files with coverage is a little annoying since the breakpoint hint widget can expand the glyph margin on lines with coverage, and jump back over otherwise. Probably we should never decrease the number of lanes shown whenever the cursor is over the glyph margin. ![](https://memes.peet.io/img/24-01-79b53dd9-6fca-41dd-87b5-a113f9c25efb.gif) --- .../lib/stylelint/vscode-known-variables.json | 4 + src/vs/editor/browser/view.ts | 44 +++---- src/vs/editor/common/model.ts | 3 +- src/vs/editor/common/model/textModel.ts | 2 +- .../common/standalone/standaloneEnums.ts | 3 +- src/vs/monaco.d.ts | 3 +- .../browser/breakpointEditorContribution.ts | 6 +- .../browser/codeCoverageDecorations.ts | 123 ++++++++++++++++++ .../contrib/testing/browser/media/testing.css | 40 ++++++ .../testing/browser/testingDecorations.ts | 62 +++++---- .../contrib/testing/browser/theme.ts | 30 ++++- .../contrib/testing/common/configuration.ts | 3 + .../contrib/testing/common/constants.ts | 1 + 13 files changed, 261 insertions(+), 63 deletions(-) create mode 100644 src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 0d6805f2788..6d0e83e9743 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -679,6 +679,8 @@ "--vscode-terminalOverviewRuler-findMatchForeground", "--vscode-terminalStickyScroll-background", "--vscode-terminalStickyScrollHover-background", + "--vscode-testing-coveredBackground", + "--vscode-testing-coveredGutterBackground", "--vscode-testing-iconErrored", "--vscode-testing-iconFailed", "--vscode-testing-iconPassed", @@ -692,6 +694,8 @@ "--vscode-testing-peekBorder", "--vscode-testing-peekHeaderBackground", "--vscode-testing-runAction", + "--vscode-testing-uncoveredBackground", + "--vscode-testing-uncoveredGutterBackground", "--vscode-textBlockQuote-background", "--vscode-textBlockQuote-border", "--vscode-textCodeBlock-background", diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index 5bc11e49309..04defbed5db 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -250,7 +250,7 @@ export class View extends ViewEventHandler { // Add all margin decorations glyphs = glyphs.concat(model.getAllMarginDecorations().map((decoration) => { - const lane = decoration.options.glyphMargin?.position ?? GlyphMarginLane.Left; + const lane = decoration.options.glyphMargin?.position ?? GlyphMarginLane.Center; return { range: decoration.range, lane }; })); @@ -263,40 +263,34 @@ export class View extends ViewEventHandler { // Sorted by their start position glyphs.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); - let leftDecRange: Range | null = null; - let rightDecRange: Range | null = null; + const maxLane = GlyphMarginLane.Right; + const lanes: (Range | undefined)[] = Array.from({ length: maxLane + 1 }); + let requiredLanes = 1; + for (const decoration of glyphs) { - - if (decoration.lane === GlyphMarginLane.Left && (!leftDecRange || Range.compareRangesUsingEnds(leftDecRange, decoration.range) < 0)) { - // assign only if the range of `decoration` ends after, which means it has a higher chance to overlap with the other lane - leftDecRange = decoration.range; + // assign only if the range of `decoration` ends after, which means it has a higher chance to overlap with the other lane + if (!lanes[decoration.lane] || Range.compareRangesUsingEnds(lanes[decoration.lane]!, decoration.range) < 0) { + lanes[decoration.lane] = decoration.range; } - if (decoration.lane === GlyphMarginLane.Right && (!rightDecRange || Range.compareRangesUsingEnds(rightDecRange, decoration.range) < 0)) { - // assign only if the range of `decoration` ends after, which means it has a higher chance to overlap with the other lane - rightDecRange = decoration.range; - } - - if (leftDecRange && rightDecRange) { - - if (leftDecRange.endLineNumber < rightDecRange.startLineNumber) { - // there's no chance for `leftDecRange` to ever intersect something going further - leftDecRange = null; + let requiredLanesHere = 0; + for (let i = 1; i <= maxLane; i++) { + const lane = lanes[i]; + if (!lane || lane.endLineNumber < decoration.range.startLineNumber) { + lanes[i] = undefined; continue; } - if (rightDecRange.endLineNumber < leftDecRange.startLineNumber) { - // there's no chance for `rightDecRange` to ever intersect something going further - rightDecRange = null; - continue; - } + requiredLanesHere++; + } - // leftDecRange and rightDecRange are intersecting or touching => we need two lanes - return 2; + requiredLanes = Math.max(requiredLanes, requiredLanesHere); + if (requiredLanes === maxLane) { + return requiredLanes; } } - return 1; + return requiredLanes; } private _createPointerHandlerHelper(): IPointerHandlerHelper { diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 7677c2b5002..07bbe0e53e4 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -39,7 +39,8 @@ export enum OverviewRulerLane { */ export enum GlyphMarginLane { Left = 1, - Right = 2 + Center = 2, + Right = 3, } /** diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index ab1ca0cda21..a9abc6174d5 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -2206,7 +2206,7 @@ export class ModelDecorationGlyphMarginOptions { readonly position: model.GlyphMarginLane; constructor(options: model.IModelDecorationGlyphMarginOptions | null | undefined) { - this.position = options?.position ?? model.GlyphMarginLane.Left; + this.position = options?.position ?? model.GlyphMarginLane.Center; } } diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index dc5c995558d..bbdeb0560ff 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -360,7 +360,8 @@ export enum EndOfLineSequence { */ export enum GlyphMarginLane { Left = 1, - Right = 2 + Center = 2, + Right = 3 } /** diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 226226fc52c..9055bf39700 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1580,7 +1580,8 @@ declare namespace monaco.editor { */ export enum GlyphMarginLane { Left = 1, - Right = 2 + Center = 2, + Right = 3 } /** diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 2948b28ab89..bbeb7e5ecbf 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -475,7 +475,11 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi if (decorations) { for (const { options } of decorations) { const clz = options.glyphMarginClassName; - if (clz && (!clz.includes('codicon-') || clz.includes('codicon-testing-') || clz.includes('codicon-merge-') || clz.includes('codicon-arrow-') || clz.includes('codicon-loading') || clz.includes('codicon-fold') || clz.includes('codicon-inline-chat'))) { + if (!clz) { + continue; + } + const hasSomeActionableCodicon = !(clz.includes('codicon-') || clz.startsWith('coverage-deco-')) || clz.includes('codicon-testing-') || clz.includes('codicon-merge-') || clz.includes('codicon-arrow-') || clz.includes('codicon-loading') || clz.includes('codicon-fold') || clz.includes('codicon-inline-chat'); + if (hasSomeActionableCodicon) { return false; } } diff --git a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts new file mode 100644 index 00000000000..bb5d32c6f57 --- /dev/null +++ b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts @@ -0,0 +1,123 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { autorun, derived, observableFromEvent } from 'vs/base/common/observable'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Range } from 'vs/editor/common/core/range'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { GlyphMarginLane, ITextModel } from 'vs/editor/common/model'; +import { localize } from 'vs/nls'; +import { ILogService } from 'vs/platform/log/common/log'; +import { FileCoverage } from 'vs/workbench/contrib/testing/common/testCoverage'; +import { ITestCoverageService } from 'vs/workbench/contrib/testing/common/testCoverageService'; +import { DetailType } from 'vs/workbench/contrib/testing/common/testTypes'; + +export class CodeCoverageDecorations extends Disposable implements IEditorContribution { + private loadingCancellation?: CancellationTokenSource; + private readonly displayedStore = this._register(new DisposableStore()); + + constructor( + editor: ICodeEditor, + @ITestCoverageService coverage: ITestCoverageService, + @ILogService private readonly log: ILogService, + ) { + super(); + + const modelObs = observableFromEvent(editor.onDidChangeModel, () => editor.getModel()); + + const fileCoverage = derived(reader => { + const report = coverage.selected.read(reader); + if (!report) { + return; + } + + const model = modelObs.read(reader); + if (!model) { + return; + } + + return report.getUri(model.uri); + }); + + this._register(autorun(reader => { + const c = fileCoverage.read(reader); + if (c) { + this.apply(editor.getModel()!, c); + } else { + this.clear(); + } + })); + } + + private async apply(model: ITextModel, coverage: FileCoverage) { + const details = await this.loadDetails(coverage); + if (!details) { + return this.clear(); + } + + const decorations: string[] = []; + model.changeDecorations(e => { + for (const detail of details) { + const range = detail.location instanceof Range ? detail.location : Range.fromPositions(detail.location); + if (detail.type === DetailType.Statement) { + const cls = detail.count > 0 ? 'coverage-deco-hit' : 'coverage-deco-miss'; + decorations.push(e.addDecoration(range, { + showIfCollapsed: false, + glyphMargin: { position: GlyphMarginLane.Left }, + description: localize('testing.hitCount', 'Hit count: {0}', detail.count), + glyphMarginClassName: `coverage-deco-gutter ${cls}`, + className: `coverage-deco-inline ${cls}`, + })); + + if (detail.branches) { + for (const branch of detail.branches) { + const location = branch.location || range.getEndPosition(); + const branchRange = location instanceof Range ? location : Range.fromPositions(location); + decorations.push(e.addDecoration(branchRange, { + showIfCollapsed: false, + glyphMargin: { position: GlyphMarginLane.Left }, + description: localize('testing.hitCount', 'Hit count: {0}', detail.count), + glyphMarginClassName: `coverage-deco-gutter ${cls}`, + className: `coverage-deco-inline ${cls}`, + })); + } + } + } + } + }); + + this.displayedStore.add(toDisposable(() => { + model.changeDecorations(e => { + for (const decoration of decorations) { + e.removeDecoration(decoration); + } + }); + })); + } + + private clear() { + this.loadingCancellation?.cancel(); + this.loadingCancellation = undefined; + this.displayedStore.clear(); + } + + private async loadDetails(coverage: FileCoverage) { + const cts = this.loadingCancellation = new CancellationTokenSource(); + this.displayedStore.add(this.loadingCancellation); + + try { + const details = await coverage.details(this.loadingCancellation.token); + if (!cts.token.isCancellationRequested) { + return details; + } + } catch (e) { + this.log.error('Error loading coverage details', e); + } + + return undefined; + } +} diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index a523e914d59..a599b48c632 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -414,3 +414,43 @@ .explorer-item-with-test-coverage .monaco-icon-label::after { margin-right: 12px; /* slightly reduce because the bars handle the scrollbar margin */ } + +/** -- coverage decorations */ + +.coverage-deco-gutter::before { + content: ''; + position: absolute; + inset: 0; + right: 25%; + left: 25%; +} + +.coverage-deco-gutter.coverage-deco-hit::before { + background: var(--vscode-testing-coveredGutterBackground); +} + +.coverage-deco-gutter.coverage-deco-miss::before { + background: var(--vscode-testing-uncoveredGutterBackground); +} + +.coverage-deco-gutter.coverage-deco-miss.coverage-deco-hit::before { + background-image: linear-gradient(45deg, + var(--vscode-testing-coveredGutterBackground) 25%, + var(--vscode-testing-uncoveredGutterBackground) 25%, + var(--vscode-testing-uncoveredGutterBackground) 50%, + var(--vscode-testing-coveredGutterBackground) 50%, + 75%, + var(--vscode-testing-uncoveredGutterBackground) 75%, + var(--vscode-testing-uncoveredGutterBackground) 100% + ); + background-size: 6px 6px; + background-color: transparent; +} + +.coverage-deco-inline.coverage-deco-hit { + background: var(--vscode-testing-coveredBackground); +} + +.coverage-deco-inline.coverage-deco-miss { + background: var(--vscode-testing-uncoveredBackground); +} diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index 540c1961a72..a062c3d79d2 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -754,11 +754,14 @@ abstract class RunTestDecoration { this.showContextMenu(e); break; case DefaultGutterClickAction.Debug: - (alternateAction ? this.defaultRun() : this.defaultDebug()); + this.runWith(alternateAction ? TestRunProfileBitset.Run : TestRunProfileBitset.Debug); + break; + case DefaultGutterClickAction.Coverage: + this.runWith(alternateAction ? TestRunProfileBitset.Debug : TestRunProfileBitset.Coverage); break; case DefaultGutterClickAction.Run: default: - (alternateAction ? this.defaultDebug() : this.defaultRun()); + this.runWith(alternateAction ? TestRunProfileBitset.Debug : TestRunProfileBitset.Run); break; } @@ -798,17 +801,10 @@ abstract class RunTestDecoration { */ abstract getContextMenuActions(): IReference; - protected defaultRun() { + protected runWith(profile: TestRunProfileBitset) { return this.testService.runTests({ tests: this.tests.map(({ test }) => test), - group: TestRunProfileBitset.Run, - }); - } - - protected defaultDebug() { - return this.testService.runTests({ - tests: this.tests.map(({ test }) => test), - group: TestRunProfileBitset.Debug, + group: profile, }); } @@ -823,6 +819,8 @@ abstract class RunTestDecoration { return localize('testing.gutterMsg.contextMenu', 'Click for test options'); case DefaultGutterClickAction.Debug: return localize('testing.gutterMsg.debug', 'Click to debug tests, right click for more options'); + case DefaultGutterClickAction.Coverage: + return localize('testing.gutterMsg.coverage', 'Click to run tests with coverage, right click for more options'); case DefaultGutterClickAction.Run: default: return localize('testing.gutterMsg.run', 'Click to run tests, right click for more options'); @@ -835,19 +833,17 @@ abstract class RunTestDecoration { protected getTestContextMenuActions(test: InternalTestItem, resultItem?: TestResultItem): IReference { const testActions: IAction[] = []; const capabilities = this.testProfileService.capabilitiesForTest(test); - if (capabilities & TestRunProfileBitset.Run) { - testActions.push(new Action('testing.gutter.run', localize('run test', 'Run Test'), undefined, undefined, () => this.testService.runTests({ - group: TestRunProfileBitset.Run, - tests: [test], - }))); - } - if (capabilities & TestRunProfileBitset.Debug) { - testActions.push(new Action('testing.gutter.debug', localize('debug test', 'Debug Test'), undefined, undefined, () => this.testService.runTests({ - group: TestRunProfileBitset.Debug, - tests: [test], - }))); - } + [ + { bitset: TestRunProfileBitset.Run, label: localize('run test', 'Run Test') }, + { bitset: TestRunProfileBitset.Debug, label: localize('debug test', 'Debug Test') }, + { bitset: TestRunProfileBitset.Coverage, label: localize('coverage test', 'Run with Coverage') }, + ].forEach(({ bitset, label }) => { + if (capabilities & bitset) { + testActions.push(new Action(`testing.gutter.${bitset}`, label, undefined, undefined, + () => this.testService.runTests({ group: bitset, tests: [test] }))); + } + }); if (capabilities & TestRunProfileBitset.HasNonDefaultProfile) { testActions.push(new Action('testing.runUsing', localize('testing.runUsing', 'Execute Using Profile...'), undefined, undefined, async () => { @@ -924,17 +920,19 @@ class MultiRunTestDecoration extends RunTestDecoration implements ITestDecoratio super(tests, visible, model, codeEditorService, testService, contextMenuService, commandService, configurationService, testProfileService, contextKeyService, menuService); } - override getContextMenuActions() { + public override getContextMenuActions() { const allActions: IAction[] = []; - const canRun = this.tests.some(({ test }) => this.testProfileService.capabilitiesForTest(test) & TestRunProfileBitset.Run); - if (canRun) { - allActions.push(new Action('testing.gutter.runAll', localize('run all test', 'Run All Tests'), undefined, undefined, () => this.defaultRun())); - } - const canDebug = this.tests.some(({ test }) => this.testProfileService.capabilitiesForTest(test) & TestRunProfileBitset.Debug); - if (canDebug) { - allActions.push(new Action('testing.gutter.debugAll', localize('debug all test', 'Debug All Tests'), undefined, undefined, () => this.defaultDebug())); - } + [ + { bitset: TestRunProfileBitset.Run, label: localize('run all test', 'Run All Tests') }, + { bitset: TestRunProfileBitset.Coverage, label: localize('run all test with coverage', 'Run All Tests with Coverage') }, + { bitset: TestRunProfileBitset.Debug, label: localize('debug all test', 'Debug All Tests') }, + ].forEach(({ bitset, label }, i) => { + const canRun = this.tests.some(({ test }) => this.testProfileService.capabilitiesForTest(test) & bitset); + if (canRun) { + allActions.push(new Action(`testing.gutter.run${i}`, label, undefined, undefined, () => this.runWith(bitset))); + } + }); const testItems = this.tests.map((testItem): IMultiRunTest => ({ currentLabel: testItem.test.item.label, diff --git a/src/vs/workbench/contrib/testing/browser/theme.ts b/src/vs/workbench/contrib/testing/browser/theme.ts index ce17f7ffc27..440bff5b8be 100644 --- a/src/vs/workbench/contrib/testing/browser/theme.ts +++ b/src/vs/workbench/contrib/testing/browser/theme.ts @@ -5,7 +5,7 @@ import { Color, RGBA } from 'vs/base/common/color'; import { localize } from 'vs/nls'; -import { contrastBorder, editorErrorForeground, editorForeground, editorInfoForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; +import { contrastBorder, diffInserted, diffRemoved, editorErrorForeground, editorForeground, editorInfoForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; import { TestMessageType, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes'; export const testingColorIconFailed = registerColor('testing.iconFailed', { @@ -85,6 +85,34 @@ export const testingPeekMessageHeaderBackground = registerColor('testing.message hcLight: null }, localize('testing.messagePeekHeaderBackground', 'Color of the peek view borders and arrow when peeking a logged message.')); +export const testingCoveredBackground = registerColor('testing.coveredBackground', { + dark: diffInserted, + light: diffInserted, + hcDark: null, + hcLight: null +}, localize('testing.coveredBackground', 'Background color of text that was covered.')); + +export const testingCoveredGutterBackground = registerColor('testing.coveredGutterBackground', { + dark: diffInserted, + light: diffInserted, + hcDark: null, + hcLight: null +}, localize('testing.coveredGutterBackground', 'Gutter color of regions where code was covered.')); + +export const testingUncoveredBackground = registerColor('testing.uncoveredBackground', { + dark: diffRemoved, + light: diffRemoved, + hcDark: null, + hcLight: null +}, localize('testing.uncoveredBackground', 'Background color of text that was not covered.')); + +export const testingUncoveredGutterBackground = registerColor('testing.uncoveredGutterBackground', { + dark: diffRemoved, + light: diffRemoved, + hcDark: null, + hcLight: null +}, localize('testing.uncoveredGutterBackground', 'Gutter color of regions where code not covered.')); + export const testMessageSeverityColors: { [K in TestMessageType]: { decorationForeground: string; diff --git a/src/vs/workbench/contrib/testing/common/configuration.ts b/src/vs/workbench/contrib/testing/common/configuration.ts index a10867e48b4..1bbc290e9cd 100644 --- a/src/vs/workbench/contrib/testing/common/configuration.ts +++ b/src/vs/workbench/contrib/testing/common/configuration.ts @@ -41,6 +41,7 @@ export const enum AutoOpenPeekViewWhen { export const enum DefaultGutterClickAction { Run = 'run', Debug = 'debug', + Coverage = 'runWithCoverage', ContextMenu = 'contextMenu', } @@ -119,11 +120,13 @@ export const testingConfiguration: IConfigurationNode = { enum: [ DefaultGutterClickAction.Run, DefaultGutterClickAction.Debug, + DefaultGutterClickAction.Coverage, DefaultGutterClickAction.ContextMenu, ], enumDescriptions: [ localize('testing.defaultGutterClickAction.run', 'Run the test.'), localize('testing.defaultGutterClickAction.debug', 'Debug the test.'), + localize('testing.defaultGutterClickAction.coverage', 'Run the test with coverage.'), localize('testing.defaultGutterClickAction.contextMenu', 'Open the context menu for more options.'), ], default: DefaultGutterClickAction.Run, diff --git a/src/vs/workbench/contrib/testing/common/constants.ts b/src/vs/workbench/contrib/testing/common/constants.ts index e3fff330e6a..08875ef9b98 100644 --- a/src/vs/workbench/contrib/testing/common/constants.ts +++ b/src/vs/workbench/contrib/testing/common/constants.ts @@ -13,6 +13,7 @@ export const enum Testing { ExplorerViewId = 'workbench.view.testing', OutputPeekContributionId = 'editor.contrib.testingOutputPeek', DecorationsContributionId = 'editor.contrib.testingDecorations', + CoverageDecorationsContributionId = 'editor.contrib.coverageDecorations', CoverageViewId = 'workbench.view.testCoverage', ResultsPanelId = 'workbench.panel.testResults', From 284e949daca9b5a7ee6f46f7711e717873e54729 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 8 Jan 2024 22:48:18 -0800 Subject: [PATCH 0158/1897] Pick up latest TS for building VS Code (#202047) --- package.json | 2 +- src/vs/base/browser/ui/list/listWidget.ts | 3 +-- src/vs/base/common/observableInternal/base.ts | 3 +-- src/vs/base/common/prefixTree.ts | 2 +- src/vs/base/test/common/observable.test.ts | 3 +-- .../processExplorer/processExplorerMain.ts | 2 +- src/vs/platform/markers/common/markerService.ts | 2 +- .../contrib/callHierarchy/browser/callHierarchyPeek.ts | 2 +- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 10 +++++----- .../tasks/test/browser/taskTerminalStatus.test.ts | 2 +- src/vs/workbench/contrib/testing/common/testTypes.ts | 2 +- .../contrib/typeHierarchy/browser/typeHierarchyPeek.ts | 2 +- .../workingCopy/common/storedFileWorkingCopy.ts | 2 +- .../workingCopy/common/untitledFileWorkingCopy.ts | 2 +- yarn.lock | 8 ++++---- 15 files changed, 22 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index fec8a4166e0..6b19116b084 100644 --- a/package.json +++ b/package.json @@ -208,7 +208,7 @@ "ts-loader": "^9.4.2", "ts-node": "^10.9.1", "tsec": "0.2.7", - "typescript": "^5.4.0-dev.20231116", + "typescript": "^5.4.0-dev.20240108", "typescript-formatter": "7.1.0", "util": "^0.12.4", "vscode-nls-dev": "^3.3.1", diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 5ed28547f6a..1f9a9b0aa72 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -41,8 +41,7 @@ interface IRenderedContainer { index: number; } -class TraitRenderer implements IListRenderer -{ +class TraitRenderer implements IListRenderer { private renderedElements: IRenderedContainer[] = []; constructor(private trait: Trait) { } diff --git a/src/vs/base/common/observableInternal/base.ts b/src/vs/base/common/observableInternal/base.ts index d925197b3f8..c7ff44dad68 100644 --- a/src/vs/base/common/observableInternal/base.ts +++ b/src/vs/base/common/observableInternal/base.ts @@ -452,8 +452,7 @@ export function observableValue(nameOrOwner: string | object, export class ObservableValue extends BaseObservable - implements ISettableObservable -{ + implements ISettableObservable { protected _value: T; get debugName() { diff --git a/src/vs/base/common/prefixTree.ts b/src/vs/base/common/prefixTree.ts index 69ba2d82597..00fa5ac99b8 100644 --- a/src/vs/base/common/prefixTree.ts +++ b/src/vs/base/common/prefixTree.ts @@ -182,7 +182,7 @@ export class WellDefinedPrefixTree { } } -class Node implements IPrefixTreeNode { +class Node implements IPrefixTreeNode { public children?: Map>; public get value() { diff --git a/src/vs/base/test/common/observable.test.ts b/src/vs/base/test/common/observable.test.ts index 5b1a273f767..d7586f4c27c 100644 --- a/src/vs/base/test/common/observable.test.ts +++ b/src/vs/base/test/common/observable.test.ts @@ -1083,8 +1083,7 @@ export class LoggingObserver implements IObserver { export class LoggingObservableValue extends BaseObservable - implements ISettableObservable -{ + implements ISettableObservable { private value: T; constructor(public readonly debugName: string, initialValue: T, private readonly log: Log) { diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index 91bb7854340..6acf0415bb8 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -66,7 +66,7 @@ interface IProcessRowTemplateData { readonly name: HTMLElement; } -class ProcessTreeDataSource implements IDataSource { +class ProcessTreeDataSource implements IDataSource { hasChildren(element: ProcessTree | ProcessInformation | MachineProcessInformation | ProcessItem | IRemoteDiagnosticError): boolean { if (isRemoteDiagnosticError(element)) { return false; diff --git a/src/vs/platform/markers/common/markerService.ts b/src/vs/platform/markers/common/markerService.ts index 2e524bb3aec..0f2ef2566fc 100644 --- a/src/vs/platform/markers/common/markerService.ts +++ b/src/vs/platform/markers/common/markerService.ts @@ -14,7 +14,7 @@ import { IMarker, IMarkerData, IMarkerService, IResourceMarker, MarkerSeverity, export const unsupportedSchemas = new Set([Schemas.inMemory, Schemas.vscodeSourceControl, Schemas.walkThrough, Schemas.walkThroughSnippet]); -class DoubleResourceMap{ +class DoubleResourceMap { private _byResource = new ResourceMap>(); private _byOwner = new Map>(); diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index c6398302aef..6eb9f63cba9 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -63,7 +63,7 @@ class LayoutInfo { ) { } } -class CallHierarchyTree extends WorkbenchAsyncDataTree{ } +class CallHierarchyTree extends WorkbenchAsyncDataTree { } export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index ca3168ffc3f..058a7572889 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1514,7 +1514,7 @@ class RepositoryVisibilityActionController { } } -class SetListViewModeAction extends ViewAction { +class SetListViewModeAction extends ViewAction { constructor( id = 'workbench.scm.action.setListViewMode', menu: Partial = {}) { @@ -1547,7 +1547,7 @@ class SetListViewModeNavigationAction extends SetListViewModeAction { } } -class SetTreeViewModeAction extends ViewAction { +class SetTreeViewModeAction extends ViewAction { constructor( id = 'workbench.scm.action.setTreeViewMode', menu: Partial = {}) { @@ -1636,7 +1636,7 @@ registerAction2(RepositorySortByDiscoveryTimeAction); registerAction2(RepositorySortByNameAction); registerAction2(RepositorySortByPathAction); -abstract class SetSortKeyAction extends ViewAction { +abstract class SetSortKeyAction extends ViewAction { constructor(private sortKey: ViewSortKey, title: string) { super({ id: `workbench.scm.action.setSortKey.${sortKey}`, @@ -1676,7 +1676,7 @@ registerAction2(SetSortByNameAction); registerAction2(SetSortByPathAction); registerAction2(SetSortByStatusAction); -class CollapseAllRepositoriesAction extends ViewAction { +class CollapseAllRepositoriesAction extends ViewAction { constructor() { super({ @@ -1698,7 +1698,7 @@ class CollapseAllRepositoriesAction extends ViewAction { } } -class ExpandAllRepositoriesAction extends ViewAction { +class ExpandAllRepositoriesAction extends ViewAction { constructor() { super({ diff --git a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts index faaa27efe17..5e413afc9c1 100644 --- a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts +++ b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts @@ -58,7 +58,7 @@ class TestTask extends CommonTask { } } -class TestProblemCollector extends Disposable implements Partial { +class TestProblemCollector extends Disposable implements Partial { protected readonly _onDidFindFirstMatch = new Emitter(); readonly onDidFindFirstMatch = this._onDidFindFirstMatch.event; protected readonly _onDidFindErrors = new Emitter(); diff --git a/src/vs/workbench/contrib/testing/common/testTypes.ts b/src/vs/workbench/contrib/testing/common/testTypes.ts index e242bf4de05..e85bdc9aa2e 100644 --- a/src/vs/workbench/contrib/testing/common/testTypes.ts +++ b/src/vs/workbench/contrib/testing/common/testTypes.ts @@ -802,7 +802,7 @@ export interface IncrementalChangeCollector { /** * Maintains tests in this extension host sent from the main thread. */ -export abstract class AbstractIncrementalTestCollection { +export abstract class AbstractIncrementalTestCollection { private readonly _tags = new Map(); /** diff --git a/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts index 8d8fb299078..8753d7436f5 100644 --- a/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts +++ b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts @@ -64,7 +64,7 @@ class LayoutInfo { ) { } } -class TypeHierarchyTree extends WorkbenchAsyncDataTree{ } +class TypeHierarchyTree extends WorkbenchAsyncDataTree { } export class TypeHierarchyTreePeekWidget extends peekView.PeekViewWidget { diff --git a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts index d4707a2ec50..04ae5cdb552 100644 --- a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts +++ b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts @@ -295,7 +295,7 @@ export function isStoredFileWorkingCopySaveEvent(e: IWorkingCopySaveEvent): e is return !!candidate.stat; } -export class StoredFileWorkingCopy extends ResourceWorkingCopy implements IStoredFileWorkingCopy { +export class StoredFileWorkingCopy extends ResourceWorkingCopy implements IStoredFileWorkingCopy { readonly capabilities: WorkingCopyCapabilities = WorkingCopyCapabilities.None; diff --git a/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopy.ts b/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopy.ts index 6638739535d..7bdafa266dc 100644 --- a/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopy.ts +++ b/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopy.ts @@ -90,7 +90,7 @@ export interface IUntitledFileWorkingCopyInitialContents { readonly markModified?: boolean; } -export class UntitledFileWorkingCopy extends Disposable implements IUntitledFileWorkingCopy { +export class UntitledFileWorkingCopy extends Disposable implements IUntitledFileWorkingCopy { readonly capabilities = this.isScratchpad ? WorkingCopyCapabilities.Untitled | WorkingCopyCapabilities.Scratchpad : WorkingCopyCapabilities.Untitled; diff --git a/yarn.lock b/yarn.lock index 28a8dd70157..4be700f1be3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9770,10 +9770,10 @@ typescript@^4.7.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== -typescript@^5.4.0-dev.20231116: - version "5.4.0-dev.20231116" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.0-dev.20231116.tgz#540e12c5cd5bea457adf2c19edb0fa396fb757c5" - integrity sha512-arT/MwHnosyqo1MQ2xN9VzwvWo1jxrhUpa1iQX1yZACfBRJb1dQ1D2MU/Ijotjr6eXw8BZ3RFdj9X/9O56iOgw== +typescript@^5.4.0-dev.20240108: + version "5.4.0-dev.20240108" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.0-dev.20240108.tgz#2b5d1a1aa0e7fc91f739cc190f4cc73944463af6" + integrity sha512-flXeU+FYwW3mL6zcOz1lNX0juSolUIeIRs4nO8j77jB+N/3lrqWNOSz05dzRC4eYJcjFIIOvv5y9u8MQ5nTtzg== typical@^4.0.0: version "4.0.0" From 06bad2c05f955cabcd39bccfe47113822180fa3a Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 8 Jan 2024 22:51:28 -0800 Subject: [PATCH 0159/1897] Clear selection on paste edit apply (#202049) Switches to use a snippet to ensure that existing editor selections are cleared when paste edits are applied --- .../browser/copyPasteController.ts | 1 - .../contrib/dropOrPasteInto/browser/edit.ts | 16 ++++++++++--- .../dropOrPasteInto/browser/postEditWidget.ts | 23 +++---------------- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts index acffc02602d..4a5da94b99f 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts @@ -378,7 +378,6 @@ export class CopyPasteController extends Disposable implements IEditorContributi }, () => p); } - private setCopyMetadata(dataTransfer: DataTransfer, metadata: CopyMetadata) { dataTransfer.setData(vscodeClipboardMime, JSON.stringify(metadata)); } diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/edit.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/edit.ts index e7b42fe7843..55d8dff9e7b 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/edit.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/edit.ts @@ -7,6 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; import { DropYieldTo, WorkspaceEdit } from 'vs/editor/common/languages'; import { Range } from 'vs/editor/common/core/range'; +import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; export interface DropOrPasteEdit { readonly label: string; @@ -14,14 +15,23 @@ export interface DropOrPasteEdit { readonly additionalEdit?: WorkspaceEdit; } +/** + * Given a {@link DropOrPasteEdit} and set of ranges, creates a {@link WorkspaceEdit} that applies the insert text from + * the {@link DropOrPasteEdit} at each range plus any additional edits. + */ export function createCombinedWorkspaceEdit(uri: URI, ranges: readonly Range[], edit: DropOrPasteEdit): WorkspaceEdit { + // If the edit insert text is empty, skip applying at each range + if (typeof edit.insertText === 'string' ? edit.insertText === '' : edit.insertText.snippet === '') { + return { + edits: edit.additionalEdit?.edits ?? [] + }; + } + return { edits: [ ...ranges.map(range => new ResourceTextEdit(uri, - typeof edit.insertText === 'string' - ? { range, text: edit.insertText, insertAsSnippet: false } - : { range, text: edit.insertText.snippet, insertAsSnippet: true } + { range, text: typeof edit.insertText === 'string' ? SnippetParser.escape(edit.insertText) + '$0' : edit.insertText.snippet, insertAsSnippet: true } )), ...(edit.additionalEdit?.edits ?? []) ] diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/postEditWidget.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/postEditWidget.ts index af54bdbf846..97287e46c5f 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/postEditWidget.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/postEditWidget.ts @@ -11,10 +11,11 @@ import { Event } from 'vs/base/common/event'; import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import 'vs/css!./postEditWidget'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; -import { IBulkEditResult, IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; +import { IBulkEditResult, IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { Range } from 'vs/editor/common/core/range'; import { WorkspaceEdit } from 'vs/editor/common/languages'; import { TrackedRangeStickiness } from 'vs/editor/common/model'; +import { createCombinedWorkspaceEdit } from 'vs/editor/contrib/dropOrPasteInto/browser/edit'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -166,25 +167,7 @@ export class PostEditWidgetManager extends Disposable { return; } - let insertTextEdit: ResourceTextEdit[] = []; - if (typeof edit.insertText === 'string' ? edit.insertText === '' : edit.insertText.snippet === '') { - insertTextEdit = []; - } else { - insertTextEdit = ranges.map(range => new ResourceTextEdit(model.uri, - typeof edit.insertText === 'string' - ? { range, text: edit.insertText, insertAsSnippet: false } - : { range, text: edit.insertText.snippet, insertAsSnippet: true } - )); - } - - const allEdits = [ - ...insertTextEdit, - ...(edit.additionalEdit?.edits ?? []) - ]; - - const combinedWorkspaceEdit: WorkspaceEdit = { - edits: allEdits - }; + const combinedWorkspaceEdit = createCombinedWorkspaceEdit(model.uri, ranges, edit); // Use a decoration to track edits around the trigger range const primaryRange = ranges[0]; From fb769554405bee9be16e21ceb0a496bd29126941 Mon Sep 17 00:00:00 2001 From: Robo Date: Mon, 8 Jan 2024 23:33:34 -0800 Subject: [PATCH 0160/1897] chore: update electron@27.2.1 (#202053) * chore: bump electron@27.2.1 * chore: enable code cache support for custom standard schemes Refs https://github.com/electron/electron/commit/267cbc841eb97c1c768ea116352f7430968cb05d * chore: remove --ms-enable-electron-run-as-node Removed in favor of https://github.com/electron/electron/commit/f842ead6bc627afdf5b945305abb3e1872b558ef * chore: update distro --- .yarnrc | 4 +- build/checksums/electron.txt | 150 ++++++++++----------- cgmanifest.json | 4 +- extensions/git/src/askpass.ts | 2 +- extensions/git/src/gitEditor.ts | 2 +- package.json | 4 +- resources/darwin/bin/code.sh | 2 +- resources/linux/bin/code.sh | 2 +- resources/win32/bin/code.cmd | 2 +- resources/win32/bin/code.sh | 4 +- scripts/code-cli.bat | 2 +- scripts/code-cli.sh | 2 +- scripts/code.sh | 2 +- scripts/node-electron.bat | 2 +- scripts/node-electron.sh | 6 +- src/main.js | 4 +- src/vs/base/node/ps.ts | 6 - src/vs/platform/environment/common/argv.ts | 3 - src/vs/platform/environment/node/argv.ts | 1 - src/vs/platform/shell/node/shellEnv.ts | 2 +- src/vs/server/node/server.cli.ts | 1 - yarn.lock | 8 +- 22 files changed, 101 insertions(+), 114 deletions(-) diff --git a/.yarnrc b/.yarnrc index c7befdb5fa1..1614ee06ded 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,5 +1,5 @@ disturl "https://electronjs.org/headers" -target "27.1.3" -ms_build_id "25612240" +target "27.2.1" +ms_build_id "26149897" runtime "electron" build_from_source "true" diff --git a/build/checksums/electron.txt b/build/checksums/electron.txt index 4726faa51f4..d5d99bdf924 100644 --- a/build/checksums/electron.txt +++ b/build/checksums/electron.txt @@ -1,75 +1,75 @@ -fb6f9b62f3f031106dbaba2538bd850c6c18206353a85552f66e41bea36cb4c0 *chromedriver-v27.1.3-darwin-arm64.zip -ad6fb1824df1ae040aaa0ea280b37b175458517f6023414ce11418a0787e7bc8 *chromedriver-v27.1.3-darwin-x64.zip -559c07c4392640478702818076414a53e32845ea10647b354f9d5cbd45eabea2 *chromedriver-v27.1.3-linux-arm64.zip -82a17100d8443b10763e66194960830236a1d6a8a931db7b75beac8d5fa929c4 *chromedriver-v27.1.3-linux-armv7l.zip -fea8713f19cafb4eb0591074d42a52ca6d1394a9da05d8c73ed2590f1bbd6056 *chromedriver-v27.1.3-linux-x64.zip -6202162efb2d4479a826dc0d6090c1ffa3c90efa245f6c66cfd6d71169f52cd8 *chromedriver-v27.1.3-mas-arm64.zip -dd966c0dd992b63616e0ec811f27367232c40b1ae12aca80467c7c3b3bf52da5 *chromedriver-v27.1.3-mas-x64.zip -a7c800241316318e74cdefd8c311c0aa80c84263471bc6f20738d6be1ede9f76 *chromedriver-v27.1.3-win32-arm64.zip -95722b9b0915e8e883de762ceea7e3e859d4b0c8ea05e1ec78273707bbae95be *chromedriver-v27.1.3-win32-ia32.zip -73d0d834812c51b408dd863588686e21013b076eb5af43a19a9b5bdb11b035cf *chromedriver-v27.1.3-win32-x64.zip -977a4b72c9f4bc83256b46bc477d8f99957e9db2d543a99e10bc72667431823c *electron-api.json -dee2d7db6aeaf9a0a4c67c6ad4242d162f5573f96198c8af7d3c4888090e7c3e *electron-v27.1.3-darwin-arm64-dsym-snapshot.zip -cf8bf2d3d907e0860b28fbe13d41889161fab11578d991251344820e2d9a0370 *electron-v27.1.3-darwin-arm64-dsym.zip -a98e58cc6b840dd079b42224ebb612e4286ee2dabc0fb0b3338d71da65b388c9 *electron-v27.1.3-darwin-arm64-symbols.zip -ae44b0d094c53e233f5f69db13fe83b1db5e6434a7688fe72550f30f00bde40c *electron-v27.1.3-darwin-arm64.zip -a262a0c9baa3c4296f0e82498714c84189ee4474a099bf3ed3f7ab532c42d8f7 *electron-v27.1.3-darwin-x64-dsym-snapshot.zip -ca6fced7f9c356bf85ca512721a929df6b8c72fffdd18de49e475140038aae99 *electron-v27.1.3-darwin-x64-dsym.zip -1f29c88e7a170ec4c0d28feff70b117d7dd9c32758f12e99e38cf7178d710c7d *electron-v27.1.3-darwin-x64-symbols.zip -1f390ed58536a6ca35a65f2d89dabfbec7710a63c0037e7b6b5b93ddec830e4a *electron-v27.1.3-darwin-x64.zip -20ec3f863b6af6dbf16e71a73f813dce72aa3ebe24e45a9aafeec95d0e50e0b1 *electron-v27.1.3-linux-arm64-debug.zip -9c61c8eccad95493e5b26d390d76054cc193ef6969ea09f9487d5fe7dddc74ed *electron-v27.1.3-linux-arm64-symbols.zip -dc40729e50501923fb6c05f96179404fb7cb66abffd4c541ba48a98dd99eb1ed *electron-v27.1.3-linux-arm64.zip -20ec3f863b6af6dbf16e71a73f813dce72aa3ebe24e45a9aafeec95d0e50e0b1 *electron-v27.1.3-linux-armv7l-debug.zip -070f1eb4f748ed7f2fcd7251eb1cc6a60dd673b875b5ccdde725a6d81d63aa93 *electron-v27.1.3-linux-armv7l-symbols.zip -b7722854c6cdc32748341a42a8031c6fb39a9c7bb4b14a5df2bede8b6c34a51d *electron-v27.1.3-linux-armv7l.zip -6a5b0a98a784761ad526b4d3c3f6ee9bb1b080fec033a6707f31562e21f78e21 *electron-v27.1.3-linux-x64-debug.zip -47cd6f88991657387ede77513fda4bee4af8774cda70370aee10efe20daf1e3d *electron-v27.1.3-linux-x64-symbols.zip -5c567315071b2c69e5b3834c8098270bc8be2625242709a82404ca16e66fae14 *electron-v27.1.3-linux-x64.zip -0e86b47e647bfa05638341f36405cd313827c35be4dc39e007150c5fed559d43 *electron-v27.1.3-mas-arm64-dsym-snapshot.zip -f353c28728ad2c18c8f394adca6cf579c7a6031d87192e4ac21c131aa15d73d1 *electron-v27.1.3-mas-arm64-dsym.zip -8a69b87569291270e647aeb45c4ba0c16b5282c6c23c60aafb067b9485908b2a *electron-v27.1.3-mas-arm64-symbols.zip -78f5a61e5d828fd85a25cad0f8bd97144fcb59e79f6be8ab9a6f5b92410bf8d0 *electron-v27.1.3-mas-arm64.zip -71d2a5843fab0b1e6fbbf31788f5ce262d26bead43974e4559b502e608c5caa0 *electron-v27.1.3-mas-x64-dsym-snapshot.zip -31ba66bc89ff0ca11d82971e5a1846ef3426bdd1aa22675fc5e5a9ecd545a419 *electron-v27.1.3-mas-x64-dsym.zip -3eb43d0058e88a0b45845f2e78d270325c52ae32b552e94b5da38d3a0187d3c8 *electron-v27.1.3-mas-x64-symbols.zip -bc7ff5f8938d24390a1305f13ed29760fb7c841b533fad090682024bdf93b304 *electron-v27.1.3-mas-x64.zip -e98c36b90984f7307463f815a7d593b41d373e24e6949e8dad28ea84b581c615 *electron-v27.1.3-win32-arm64-pdb.zip -c71180638cc29d888f6bdbbf92476dd4aa443c934ee3e9c588a898fbe0aaa70b *electron-v27.1.3-win32-arm64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.1.3-win32-arm64-toolchain-profile.zip -4b8cbea485aff9325f3e41b842d4714900299667255179b2c7ac653b56d4b0c8 *electron-v27.1.3-win32-arm64.zip -d5be66a6f7416d4bf506c950e31c907604bbfc3c4c417c9545df81070cb669dd *electron-v27.1.3-win32-ia32-pdb.zip -e5ab5957302d1d839e620e803de810de9c476147383a05af41e08ba9add74445 *electron-v27.1.3-win32-ia32-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.1.3-win32-ia32-toolchain-profile.zip -c06c9ee185276e56dfb62ca7ea742f8442b8d573b430bb9d6ce03024fe4be670 *electron-v27.1.3-win32-ia32.zip -82a8dfb06126e25672f793a87fa6183c62badeefc4d6372043f038f3347c3880 *electron-v27.1.3-win32-x64-pdb.zip -441c05b66b2584421683771cd428be78d59e37359cee88d01e8a1201e8c95206 *electron-v27.1.3-win32-x64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.1.3-win32-x64-toolchain-profile.zip -ed04476baf0d1c363f91c6cc457e448eaee2efd2464449f5bd1b3ec94f5a6b81 *electron-v27.1.3-win32-x64.zip -f58626c26c10446993521695da1fbe22162285dc4a49691de455153a6eb7ad4f *electron.d.ts -178f0822f1ac3c3dd936a05e7691eccbce7dc091fb23399eeb6c8c2d19d35309 *ffmpeg-v27.1.3-darwin-arm64.zip -8756fbaeffb0f076e9c03b03fbc0828c1d2af1fa5fafd892dbb35dbcab39606a *ffmpeg-v27.1.3-darwin-x64.zip -be517ba93c5b5f36d2e55af148213f4c2fc43177563825601bee6f88dd4f7b05 *ffmpeg-v27.1.3-linux-arm64.zip -926d0da25ffcea3d05a6cbcae15e5d7729d93bc43394ae4439747669d2210e1d *ffmpeg-v27.1.3-linux-armv7l.zip -6f9c0ef52af14828ad547a80b17f8c63cac51a18b8d5769a2f33e4fa6cccfc7e *ffmpeg-v27.1.3-linux-x64.zip -a8820f2d8133ac554edac5aaf5926f7b4d46e793c2315a1729099836dbe0674f *ffmpeg-v27.1.3-mas-arm64.zip -28cd237b7a4d04020ce262ab3e7bab66cb7921a94bb75f49d95eee0953624021 *ffmpeg-v27.1.3-mas-x64.zip -9949f98c8c07d03892a2b4940a4620a1ab2bc9501a1ab46d1941b4c2a3966beb *ffmpeg-v27.1.3-win32-arm64.zip -5cfe4c2b731c3db36338d01a88e146dc8cf7b83ede3f2090396f803f5095e7c2 *ffmpeg-v27.1.3-win32-ia32.zip -c175b1ef006ff337754bb9d9dd5b86e11273b981602d01a83bbc5eff09d583cb *ffmpeg-v27.1.3-win32-x64.zip -a37cecf4b0b2ffef035ed1916cc747037d42bd8e083c5b901decff3bfeb55a4d *hunspell_dictionaries.zip -3433837c4fdeaec616527ae03d433ae6f515a75b3855dfe750dc22b1622d67f0 *libcxx-objects-v27.1.3-linux-arm64.zip -60141a1fac588aed7320a53b355262b98dd3e67a6ad94a93f44ff240b34e69e0 *libcxx-objects-v27.1.3-linux-armv7l.zip -14d048f8375c27adc28006c9cdf73b4f9dcebb18f089b21cd2e35a17c9c89c21 *libcxx-objects-v27.1.3-linux-x64.zip -9983ad6c098939d91bc67b5a8d9ebd2e72685000310c712876533ce31e71d129 *libcxx_headers.zip -8052c0a22a9ad673d32c1ae157df9638838fbd017a030263d4cce21aeed5c824 *libcxxabi_headers.zip -734eef5f5c8ca1d9feb107c786d85a9560a2f8009c32cc6f8cda04d24b825d21 *mksnapshot-v27.1.3-darwin-arm64.zip -ec489cbe9634d799c14ec9897973a7924126404570a049a16eb0a2a771d728ed *mksnapshot-v27.1.3-darwin-x64.zip -b4f952c96d369b566e85f7b1d67c6a1553158f041c944a53e9bbd324a4a8699b *mksnapshot-v27.1.3-linux-arm64-x64.zip -c705cd0f7301fd7b8b282ffe51efdce3c7305a828c21015640c5091e6f9b71ed *mksnapshot-v27.1.3-linux-armv7l-x64.zip -bf5e7c0aa8000e6da7f8d00acdf7bb93474af8c2eb684177fb6fd0b2d2ae72d8 *mksnapshot-v27.1.3-linux-x64.zip -dd9ff64c44563d5713ae516dd05db950a34e9b271bc3df5ffed20e7eda1ae50b *mksnapshot-v27.1.3-mas-arm64.zip -31558d25b4051cbce2e5f98f58cc860b14fbbe92d727e2696f3f36ed266aed9e *mksnapshot-v27.1.3-mas-x64.zip -0b8f3b6702aa81adff35ddaedf44623ab91eda203aecde5e15759ff8ae95558f *mksnapshot-v27.1.3-win32-arm64-x64.zip -ad95d9708c7a94b396714956b7407c9a7283ca54b26bcf0034f81764d9285037 *mksnapshot-v27.1.3-win32-ia32.zip -98e7e1fd5580e8c626f519eb619ff8bf16c3d909212cd5260cebb8a155d2ddce *mksnapshot-v27.1.3-win32-x64.zip +fa257474711c65fe0f929299fc21a7171a548ef655e393246a48319ee53f702e *chromedriver-v27.2.1-darwin-arm64.zip +d3f279d281221ca87510716afda22be95f8ad66641b3d46943885de0ae94b046 *chromedriver-v27.2.1-darwin-x64.zip +fc0c512cb87c82b1d5327bb04ef7dfb2046ea884d00896bab89652d607ce1c97 *chromedriver-v27.2.1-linux-arm64.zip +4c33d1a323bc59d776a14b2ebb40229dbf1b8de30d51e5643e6584bd63cd7593 *chromedriver-v27.2.1-linux-armv7l.zip +a60c7fc2f278b7a177eb1ec3dccff6fc9c1c8d120596deb595b544a2b8018a79 *chromedriver-v27.2.1-linux-x64.zip +80bc0e0ce66028436853bb5a625f8a1c6c8456c1a8435070a6e30d0e06b9312b *chromedriver-v27.2.1-mas-arm64.zip +67fcfafbc24902d1dff455527115f4453d49308d95010214dda7a1e46a96e6e7 *chromedriver-v27.2.1-mas-x64.zip +1c33c6cadb07c62e7a80ae64de520cb3122cc8496ee73533da67770d0ab1bf5c *chromedriver-v27.2.1-win32-arm64.zip +666042a40a30ab322a66bbcf8552f777c0564bb4a7647122eb7bd260269cd1c3 *chromedriver-v27.2.1-win32-ia32.zip +af92d72b04760be18ac748c5a0072751b497cff4ebabb4bfeecc23538ea9de8e *chromedriver-v27.2.1-win32-x64.zip +9f626747797f005cfe41076bc905a11ab17ac380a2d7865c3cfba05c63ea6514 *electron-api.json +a1ea4e720b5ce964bb4aa6f723f0305ec81a0024f8e9fe474fb1b05b79402e51 *electron-v27.2.1-darwin-arm64-dsym-snapshot.zip +60f69390ca803845992dae4aecb0d360c8c3da62c842523f307a8d495bec84e7 *electron-v27.2.1-darwin-arm64-dsym.zip +33ed9025a4ac39ec597c6cdcc5f7c425a34aca4e34713732de159685a7884d52 *electron-v27.2.1-darwin-arm64-symbols.zip +a8d12908b5aad53083e1b257ba8028ea55ac099cab859260c2a8b5ce512597ca *electron-v27.2.1-darwin-arm64.zip +68b5e5a021239fc7849986a24dddb54d21f4cc9ffbceb8777b7bfd696c940c50 *electron-v27.2.1-darwin-x64-dsym-snapshot.zip +ed33f31e69925642c3b09b2c318ec66be8877bdfb4984c91ee3b90052fb1bcac *electron-v27.2.1-darwin-x64-dsym.zip +88f7b0fec77ee4e0ae9659cfee4d9cbe3a26b378cc92878236b928254568fb07 *electron-v27.2.1-darwin-x64-symbols.zip +7ea6d061520afa77483b891bb8623574d87458deb54841f740c51a1adfa88fea *electron-v27.2.1-darwin-x64.zip +e4af2089beecdcdb7625918ce1dfd6b3401b10239c1c0435951a8642ff17568f *electron-v27.2.1-linux-arm64-debug.zip +c9b009ce848f02fc31c072b546758fbccb39a6ff2779dfea794920c1a83f8f8d *electron-v27.2.1-linux-arm64-symbols.zip +32c86c89a4fdc55aac107c8807bf3181fc31cf5c6dc4e0ca00319bb7837ed695 *electron-v27.2.1-linux-arm64.zip +e4af2089beecdcdb7625918ce1dfd6b3401b10239c1c0435951a8642ff17568f *electron-v27.2.1-linux-armv7l-debug.zip +ceb3f9e7b19f8e1ca0e94858492b1b5eb8bb3c7e91ebde230bdbe463838f2a0a *electron-v27.2.1-linux-armv7l-symbols.zip +b4496ae943fa8ee19d418f9f871ce05ad2ffab35b5dfc79052c842478464b5d6 *electron-v27.2.1-linux-armv7l.zip +35bdcb99f7f63a6892dde033046b4eb315ca71a459124461b43f9ffcf9cae579 *electron-v27.2.1-linux-x64-debug.zip +5271540e93fbdb1f69f21a37a3deb51886afaf7e1b9200ed0cbb6dc91f83854c *electron-v27.2.1-linux-x64-symbols.zip +b4be96cd85797d23afd422dc7cc6bf321234001810d08b52e32b055a7e8afbd7 *electron-v27.2.1-linux-x64.zip +dc7feb71d7a65bde3ddbcc43e514940a070af9fd8b385f819ac012b9d694ce64 *electron-v27.2.1-mas-arm64-dsym-snapshot.zip +98317e6af5ddb3db98511958a9ad46a54de00c897b67c6d1e3dc3baaf3e3878a *electron-v27.2.1-mas-arm64-dsym.zip +91a78763ac1959c3b4865902e378830c7304917dc20877db7c12283c1d34e510 *electron-v27.2.1-mas-arm64-symbols.zip +01414a8e5ef3c1f7efb888d32bdfc85ba0ea8b42d4e8093ca7cf0e7a8a728415 *electron-v27.2.1-mas-arm64.zip +e082d37c20b1af792307fa2db9ff576e4345256d26dd0e0f5f2d807a45673991 *electron-v27.2.1-mas-x64-dsym-snapshot.zip +3a93a95bf05ec4d01268a64db2d58a07bff90463f3059a3dd4a30968297aab36 *electron-v27.2.1-mas-x64-dsym.zip +138ac2d1fea73d0f677fa5218a0ba2d5422d22cb6090d865c7ddce99383aa415 *electron-v27.2.1-mas-x64-symbols.zip +87457c8938858570b20a122c1cdb23c0a41374b7010253626d49e9b6b127a50a *electron-v27.2.1-mas-x64.zip +ce3638d29dfb46fc5b375ead5c0bbb98b50eec2c9e42c5b4a0a948a60ef25439 *electron-v27.2.1-win32-arm64-pdb.zip +f24636b71937e9b4ca46d046e6131a3acb87edca93be0b26d500d93c6a78abeb *electron-v27.2.1-win32-arm64-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.2.1-win32-arm64-toolchain-profile.zip +5b4fd051b04de97ef14a711533ecd44c99d31ce6fc83f2593c4e041e9550eeb1 *electron-v27.2.1-win32-arm64.zip +c8cd49e0ba8a87666a50d358dc3a2810d0d5f2c13e345c48febf488aed513856 *electron-v27.2.1-win32-ia32-pdb.zip +7d28badd01d7f357f0cbbff12df55b1816f2dd08bcceea00fa03b027d7c8bb9b *electron-v27.2.1-win32-ia32-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.2.1-win32-ia32-toolchain-profile.zip +508f29dd2d07c19b1ae6d7546772103230d601d44f33443a79a3b42a5fa9d293 *electron-v27.2.1-win32-ia32.zip +1b40186cd080f9d45bb03b1d7f6e4f3c4fc6332bbbe36453f99bd8643545f384 *electron-v27.2.1-win32-x64-pdb.zip +e2bc044c06a463d29699b5a0bedecb68714cacfdb5661a70db4047cb211a8246 *electron-v27.2.1-win32-x64-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.2.1-win32-x64-toolchain-profile.zip +4d0810c1bc9985e7e471efb06380bd190b5d07f46ef1938c1d3ee496e0cdcccf *electron-v27.2.1-win32-x64.zip +f48477c175fe9bc34c1541cb75bd9d3eda2c540a799e2f3b715adcdd626c3dc1 *electron.d.ts +c951898cbf3c65013a21b5b4581d24e38f568183893f785f7d3bab0aedf49d6f *ffmpeg-v27.2.1-darwin-arm64.zip +5306f41915ced8307c44858a91250a228819304c82dfdd80c9f584fe092436ad *ffmpeg-v27.2.1-darwin-x64.zip +be517ba93c5b5f36d2e55af148213f4c2fc43177563825601bee6f88dd4f7b05 *ffmpeg-v27.2.1-linux-arm64.zip +926d0da25ffcea3d05a6cbcae15e5d7729d93bc43394ae4439747669d2210e1d *ffmpeg-v27.2.1-linux-armv7l.zip +6f9c0ef52af14828ad547a80b17f8c63cac51a18b8d5769a2f33e4fa6cccfc7e *ffmpeg-v27.2.1-linux-x64.zip +bb0036c363cdc2558d9feec90cb896c66133400b499b14fa099421e1d0117bc6 *ffmpeg-v27.2.1-mas-arm64.zip +33203048fa78450cac7ae1988b708ad51d1a49933d212b505fcf272da201e6d6 *ffmpeg-v27.2.1-mas-x64.zip +954870aaa5ffa56bbe06b04308bd6a4fc937b82cc11d8780542e4a9f25790b37 *ffmpeg-v27.2.1-win32-arm64.zip +a46eab4db71ff41a0a1056b8777ec2d01f83f4b7a85687514562045f1f2b4820 *ffmpeg-v27.2.1-win32-ia32.zip +74841b03157199f2439d2182cf4da66dfdc4014d4e2d76a043055b4dc5d33a9b *ffmpeg-v27.2.1-win32-x64.zip +7f15f0f64083f093a98f9dc002d6256a3a56cc7d720cd861d820a7223d46be83 *hunspell_dictionaries.zip +6cc8cbde27c4e0ed7689f87a4c444aa50a1bee9bfec35289e26e9854864c1d69 *libcxx-objects-v27.2.1-linux-arm64.zip +ce935631fa896af9d34e8ed01a431c7f4b289d9cc872cede5bfd9e4663ac4d6e *libcxx-objects-v27.2.1-linux-armv7l.zip +979a65ea5cd9c8c44e6d9760c2cb79bec0914396b68be9270b52d09c1b1a72ee *libcxx-objects-v27.2.1-linux-x64.zip +86f961adc7015bbf8762453bc5148d381ff7c7c4580ceed9189c93fd01139fcf *libcxx_headers.zip +acb2c0d967dc14fe63784c79c58648193348252efb25a691b61d3c19142d67e9 *libcxxabi_headers.zip +07bc91f769fb7320bfb331d7e40d963e787960dec0f404b165c7df9ef49f531b *mksnapshot-v27.2.1-darwin-arm64.zip +c9127c412b6f10a706c406865a72119abf34692efd19e58231dec631af91f3c5 *mksnapshot-v27.2.1-darwin-x64.zip +d52e6deec09571b4618dab3ee1c615f2996d3d52e97f0df4c8a7ecddc443d847 *mksnapshot-v27.2.1-linux-arm64-x64.zip +23aa985b9d4431d9a92eefe8f635bcd54a50a1b02d008d9883dce1ca90670d9b *mksnapshot-v27.2.1-linux-armv7l-x64.zip +9aac8616fcfaeda69945742f4fe99309cabfcc355f5a6edae5ac6bdb5de78552 *mksnapshot-v27.2.1-linux-x64.zip +f53004a849ea0d5e19569f48561fc79b95015c3395405ef28800fa34e5a6b37c *mksnapshot-v27.2.1-mas-arm64.zip +8b7458854d6e3339220623a53927eeaac2d97e02133172f2615ee976e32a1337 *mksnapshot-v27.2.1-mas-x64.zip +5314c938447f1f24aee9007719d8d65ef4f68a3fac0e103897bc53f26685ed6a *mksnapshot-v27.2.1-win32-arm64-x64.zip +fbae6d4323d20af0a7c5d5c044e9927b6f8396e0d9c8a45c1f6945ce32250715 *mksnapshot-v27.2.1-win32-ia32.zip +08556f224fcb5dee0928dce8beda23f058258cc85e85da3fe651bf0e84469f5d *mksnapshot-v27.2.1-win32-x64.zip diff --git a/cgmanifest.json b/cgmanifest.json index 505453b1a6d..139424f2613 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -528,12 +528,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "d449c890159beae4d5176138dc65ab22d37e0fee" + "commitHash": "8471e4750734ea33a85720ce20bca0d8d9d56985" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "27.1.3" + "version": "27.2.1" }, { "component": { diff --git a/extensions/git/src/askpass.ts b/extensions/git/src/askpass.ts index 6b27d876185..5d99534d55a 100644 --- a/extensions/git/src/askpass.ts +++ b/extensions/git/src/askpass.ts @@ -30,7 +30,7 @@ export class Askpass implements IIPCHandler, ITerminalEnvironmentProvider { GIT_ASKPASS: path.join(__dirname, this.ipc ? 'askpass.sh' : 'askpass-empty.sh'), // VSCODE_GIT_ASKPASS VSCODE_GIT_ASKPASS_NODE: process.execPath, - VSCODE_GIT_ASKPASS_EXTRA_ARGS: (process.versions['electron'] && process.versions['microsoft-build']) ? '--ms-enable-electron-run-as-node' : '', + VSCODE_GIT_ASKPASS_EXTRA_ARGS: '', VSCODE_GIT_ASKPASS_MAIN: path.join(__dirname, 'askpass-main.js'), }; diff --git a/extensions/git/src/gitEditor.ts b/extensions/git/src/gitEditor.ts index f43e6f68209..f5701642084 100644 --- a/extensions/git/src/gitEditor.ts +++ b/extensions/git/src/gitEditor.ts @@ -27,7 +27,7 @@ export class GitEditor implements IIPCHandler, ITerminalEnvironmentProvider { this.env = { GIT_EDITOR: `"${path.join(__dirname, ipc ? 'git-editor.sh' : 'git-editor-empty.sh')}"`, VSCODE_GIT_EDITOR_NODE: process.execPath, - VSCODE_GIT_EDITOR_EXTRA_ARGS: (process.versions['electron'] && process.versions['microsoft-build']) ? '--ms-enable-electron-run-as-node' : '', + VSCODE_GIT_EDITOR_EXTRA_ARGS: '', VSCODE_GIT_EDITOR_MAIN: path.join(__dirname, 'git-editor-main.js') }; } diff --git a/package.json b/package.json index 6b19116b084..59ecb4c4c67 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.86.0", - "distro": "9d1271fd1c2cf545a209fb8aaf409f75da726c4c", + "distro": "4665912689c3b96279fd50cc65117ab6969e7132", "author": { "name": "Microsoft Corporation" }, @@ -150,7 +150,7 @@ "cssnano": "^4.1.11", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "27.1.3", + "electron": "27.2.1", "eslint": "8.36.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^46.5.0", diff --git a/resources/darwin/bin/code.sh b/resources/darwin/bin/code.sh index 8c058727071..1d66515c2c7 100755 --- a/resources/darwin/bin/code.sh +++ b/resources/darwin/bin/code.sh @@ -31,5 +31,5 @@ fi CONTENTS="$APP_PATH/Contents" ELECTRON="$CONTENTS/MacOS/Electron" CLI="$CONTENTS/Resources/app/out/cli.js" -ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --ms-enable-electron-run-as-node "$@" +ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" "$@" exit $? diff --git a/resources/linux/bin/code.sh b/resources/linux/bin/code.sh index eb48bf8fcfc..4f11f5b82e0 100755 --- a/resources/linux/bin/code.sh +++ b/resources/linux/bin/code.sh @@ -59,5 +59,5 @@ fi ELECTRON="$VSCODE_PATH/@@APPNAME@@" CLI="$VSCODE_PATH/resources/app/out/cli.js" -ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --ms-enable-electron-run-as-node "$@" +ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" "$@" exit $? diff --git a/resources/win32/bin/code.cmd b/resources/win32/bin/code.cmd index c72e9e28333..9da8ab4f7b8 100644 --- a/resources/win32/bin/code.cmd +++ b/resources/win32/bin/code.cmd @@ -2,5 +2,5 @@ setlocal set VSCODE_DEV= set ELECTRON_RUN_AS_NODE=1 -"%~dp0..\@@NAME@@.exe" "%~dp0..\resources\app\out\cli.js" --ms-enable-electron-run-as-node %* +"%~dp0..\@@NAME@@.exe" "%~dp0..\resources\app\out\cli.js" %* endlocal diff --git a/resources/win32/bin/code.sh b/resources/win32/bin/code.sh index a56c068f4ac..bcf31892c23 100644 --- a/resources/win32/bin/code.sh +++ b/resources/win32/bin/code.sh @@ -43,7 +43,7 @@ if [ $IN_WSL = true ]; then # use the Remote WSL extension if installed WSL_EXT_ID="ms-vscode-remote.remote-wsl" - ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --ms-enable-electron-run-as-node --locate-extension $WSL_EXT_ID >/tmp/remote-wsl-loc.txt 2>/dev/null /tmp/remote-wsl-loc.txt 2>/dev/null { function findName(cmd: string): string { const UTILITY_NETWORK_HINT = /--utility-sub-type=network/i; - const NODEJS_PROCESS_HINT = /--ms-enable-electron-run-as-node/i; const WINDOWS_CRASH_REPORTER = /--crashes-directory/i; const WINPTY = /\\pipe\\winpty-control/i; const CONPTY = /conhost\.exe.+--headless/i; @@ -103,11 +102,6 @@ export function listProcesses(rootPid: number): Promise { } } - // find Electron node.js processes - if (NODEJS_PROCESS_HINT.exec(cmd)) { - return `electron-nodejs (${cmd})`; - } - return cmd; } diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index 0ec11b21b51..7acd17b994a 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -139,7 +139,4 @@ export interface NativeParsedArgs { 'log-net-log'?: string; 'vmodule'?: string; 'disable-dev-shm-usage'?: boolean; - - // MS Build command line arg - 'ms-enable-electron-run-as-node'?: boolean; } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 60016db3d2c..3bb02bcc7f1 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -123,7 +123,6 @@ export const OPTIONS: OptionDescriptions> = { 'disable-gpu': { type: 'boolean', cat: 't', description: localize('disableGPU', "Disable GPU hardware acceleration.") }, 'disable-chromium-sandbox': { type: 'boolean', cat: 't', description: localize('disableChromiumSandbox', "Use this option only when there is requirement to launch the application as sudo user on Linux or when running as an elevated user in an applocker environment on Windows.") }, 'sandbox': { type: 'boolean' }, - 'ms-enable-electron-run-as-node': { type: 'boolean', global: true }, 'telemetry': { type: 'boolean', cat: 't', description: localize('telemetry', "Shows all telemetry events which VS code collects.") }, 'remote': { type: 'string', allowEmptyValue: true }, diff --git a/src/vs/platform/shell/node/shellEnv.ts b/src/vs/platform/shell/node/shellEnv.ts index 7c033e073bf..b956c37aa2a 100644 --- a/src/vs/platform/shell/node/shellEnv.ts +++ b/src/vs/platform/shell/node/shellEnv.ts @@ -128,7 +128,7 @@ async function doResolveUnixShellEnv(logService: ILogService, token: Cancellatio // handle popular non-POSIX shells const name = basename(systemShellUnix); let command: string, shellArgs: Array; - const extraArgs = (process.versions['electron'] && process.versions['microsoft-build']) ? '--ms-enable-electron-run-as-node' : ''; + const extraArgs = ''; if (/^pwsh(-preview)?$/.test(name)) { // Older versions of PowerShell removes double quotes sometimes so we use "double single quotes" which is how // you escape single quotes inside of a single quoted string. diff --git a/src/vs/server/node/server.cli.ts b/src/vs/server/node/server.cli.ts index 22e77753062..6695c4b5a84 100644 --- a/src/vs/server/node/server.cli.ts +++ b/src/vs/server/node/server.cli.ts @@ -269,7 +269,6 @@ export async function main(desc: ProductDescription, args: string[]): Promise Date: Tue, 9 Jan 2024 11:19:35 +0100 Subject: [PATCH 0161/1897] fix https://github.com/microsoft/vscode/issues/193124 (#202061) --- src/vs/editor/contrib/inlayHints/browser/inlayHints.ts | 1 + src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlayHints/browser/inlayHints.ts b/src/vs/editor/contrib/inlayHints/browser/inlayHints.ts index f8ed0407c06..c5905ab7cf4 100644 --- a/src/vs/editor/contrib/inlayHints/browser/inlayHints.ts +++ b/src/vs/editor/contrib/inlayHints/browser/inlayHints.ts @@ -57,6 +57,7 @@ export class InlayHintItem { const newHint = await Promise.resolve(this.provider.resolveInlayHint!(this.hint, token)); this.hint.tooltip = newHint?.tooltip ?? this.hint.tooltip; this.hint.label = newHint?.label ?? this.hint.label; + this.hint.textEdits = newHint?.textEdits ?? this.hint.textEdits; this._isResolved = true; } catch (err) { onUnexpectedExternalError(err); diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 1db2d6cd5bf..497fc46663b 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -669,7 +669,8 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread return { ...hint, tooltip: result.tooltip, - label: revive(result.label) + label: revive(result.label), + textEdits: result.textEdits }; }; } From 4759837ec993f7a687b17195554f7a54ccd88d21 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 9 Jan 2024 11:36:47 +0100 Subject: [PATCH 0162/1897] chore - rearrange bits (#202062) --- .../browser/inlineChatController.ts | 184 +++++++++--------- 1 file changed, 93 insertions(+), 91 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 424cc9c7217..5057e127ba5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -247,49 +247,8 @@ export class InlineChatController implements IEditorContribution { } } - joinCurrentRun(): Promise | undefined { - return this._currentRun; - } - // ---- state machine - private _showWidget(initialRender: boolean = false, position?: Position) { - assertType(this._editor.hasModel()); - - let widgetPosition: Position; - if (position) { - // explicit position wins - widgetPosition = position; - } else if (this._zone.value.position) { - // already showing - special case of line 1 - if (this._zone.value.position.lineNumber === 1) { - widgetPosition = this._zone.value.position.delta(-1); - } else { - widgetPosition = this._zone.value.position; - } - } else { - // default to ABOVE the selection - widgetPosition = this._editor.getSelection().getStartPosition().delta(-1); - } - - if (initialRender) { - this._zone.value.setContainerMargins(); - } - - if (this._activeSession && !position && (this._activeSession.hasChangedText || this._activeSession.lastExchange)) { - widgetPosition = this._activeSession.wholeRange.value.getStartPosition().delta(-1); - } - if (this._activeSession) { - this._zone.value.updateBackgroundColor(widgetPosition, this._activeSession.wholeRange.value); - } - if (!this._zone.value.position) { - this._zone.value.setWidgetMargins(widgetPosition); - this._zone.value.show(widgetPosition); - } else { - this._zone.value.updatePositionAndHeight(widgetPosition); - } - } - protected async _nextState(state: State, options: InlineChatRunOptions): Promise { let nextState: State | void = state; while (nextState) { @@ -452,26 +411,6 @@ export class InlineChatController implements IEditorContribution { } } - private _forcedPlaceholder: string | undefined = undefined; - setPlaceholder(text: string): void { - this._forcedPlaceholder = text; - this._updatePlaceholder(); - } - - resetPlaceholder(): void { - this._forcedPlaceholder = undefined; - this._updatePlaceholder(); - } - - private _updatePlaceholder(): void { - this._zone.value.widget.placeholder = this._getPlaceholderText(); - } - - private _getPlaceholderText(): string { - return this._forcedPlaceholder ?? this._activeSession?.session.placeholder ?? ''; - } - - private async [State.WAIT_FOR_INPUT](options: InlineChatRunOptions): Promise { assertType(this._activeSession); assertType(this._strategy); @@ -770,35 +709,6 @@ export class InlineChatController implements IEditorContribution { return State.SHOW_RESPONSE; } - private async _makeChanges(edits: TextEdit[], opts: ProgressingEditsOptions | undefined) { - assertType(this._activeSession); - assertType(this._strategy); - - const moreMinimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(this._activeSession.textModelN.uri, edits); - this._log('edits from PROVIDER and after making them MORE MINIMAL', this._activeSession.provider.debugName, edits, moreMinimalEdits); - - if (moreMinimalEdits?.length === 0) { - // nothing left to do - return; - } - - const actualEdits = !opts && moreMinimalEdits ? moreMinimalEdits : edits; - const editOperations = actualEdits.map(TextEdit.asEditOperation); - - try { - this._ignoreModelContentChanged = true; - this._activeSession.wholeRange.trackEdits(editOperations); - if (opts) { - await this._strategy.makeProgressiveChanges(getWindow(this._editor.getContainerDomNode()), editOperations, opts); - } else { - await this._strategy.makeChanges(getWindow(this._editor.getContainerDomNode()), editOperations); - } - this._ctxDidEdit.set(this._activeSession.hasChangedText); - } finally { - this._ignoreModelContentChanged = false; - } - } - private async[State.SHOW_RESPONSE](): Promise { assertType(this._activeSession); assertType(this._strategy); @@ -939,8 +849,97 @@ export class InlineChatController implements IEditorContribution { } } + // ---- + + private _showWidget(initialRender: boolean = false, position?: Position) { + assertType(this._editor.hasModel()); + + let widgetPosition: Position; + if (position) { + // explicit position wins + widgetPosition = position; + } else if (this._zone.value.position) { + // already showing - special case of line 1 + if (this._zone.value.position.lineNumber === 1) { + widgetPosition = this._zone.value.position.delta(-1); + } else { + widgetPosition = this._zone.value.position; + } + } else { + // default to ABOVE the selection + widgetPosition = this._editor.getSelection().getStartPosition().delta(-1); + } + + if (initialRender) { + this._zone.value.setContainerMargins(); + } + + if (this._activeSession && !position && (this._activeSession.hasChangedText || this._activeSession.lastExchange)) { + widgetPosition = this._activeSession.wholeRange.value.getStartPosition().delta(-1); + } + if (this._activeSession) { + this._zone.value.updateBackgroundColor(widgetPosition, this._activeSession.wholeRange.value); + } + if (!this._zone.value.position) { + this._zone.value.setWidgetMargins(widgetPosition); + this._zone.value.show(widgetPosition); + } else { + this._zone.value.updatePositionAndHeight(widgetPosition); + } + } + + private async _makeChanges(edits: TextEdit[], opts: ProgressingEditsOptions | undefined) { + assertType(this._activeSession); + assertType(this._strategy); + + const moreMinimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(this._activeSession.textModelN.uri, edits); + this._log('edits from PROVIDER and after making them MORE MINIMAL', this._activeSession.provider.debugName, edits, moreMinimalEdits); + + if (moreMinimalEdits?.length === 0) { + // nothing left to do + return; + } + + const actualEdits = !opts && moreMinimalEdits ? moreMinimalEdits : edits; + const editOperations = actualEdits.map(TextEdit.asEditOperation); + + try { + this._ignoreModelContentChanged = true; + this._activeSession.wholeRange.trackEdits(editOperations); + if (opts) { + await this._strategy.makeProgressiveChanges(getWindow(this._editor.getContainerDomNode()), editOperations, opts); + } else { + await this._strategy.makeChanges(getWindow(this._editor.getContainerDomNode()), editOperations); + } + this._ctxDidEdit.set(this._activeSession.hasChangedText); + } finally { + this._ignoreModelContentChanged = false; + } + } + + private _forcedPlaceholder: string | undefined = undefined; + + private _updatePlaceholder(): void { + this._zone.value.widget.placeholder = this._getPlaceholderText(); + } + + private _getPlaceholderText(): string { + return this._forcedPlaceholder ?? this._activeSession?.session.placeholder ?? ''; + } + // ---- controller API + + setPlaceholder(text: string): void { + this._forcedPlaceholder = text; + this._updatePlaceholder(); + } + + resetPlaceholder(): void { + this._forcedPlaceholder = undefined; + this._updatePlaceholder(); + } + acceptInput(): void { this._messages.fire(Message.ACCEPT_INPUT); } @@ -1075,8 +1074,11 @@ export class InlineChatController implements IEditorContribution { unstashLastSession(): Session | undefined { return this._stashedSession.value?.unstash(); } -} + joinCurrentRun(): Promise | undefined { + return this._currentRun; + } +} class StashedSession { From b87ba0192633b7f453ed5f038ce9ca7334e791da Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:55:16 +0100 Subject: [PATCH 0163/1897] Git - swap file sides when previewing a stash (#202063) --- extensions/git/src/commands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index ad4e32920b9..21c1b2e3e0b 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3554,7 +3554,7 @@ export class CommandCenter { for (const file of stashFiles) { const fileUri = Uri.file(path.join(repository.root, file)); - args.push([fileUri, toGitUri(fileUri, `stash@{${stash.index}}`), fileUri]); + args.push([fileUri, fileUri, toGitUri(fileUri, `stash@{${stash.index}}`)]); } commands.executeCommand('vscode.changes', `Git Stash #${stash.index}: ${stash.description}`, args); From 429c6895457af8aff5475860e94f1e15016a9e83 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 9 Jan 2024 12:28:19 +0100 Subject: [PATCH 0164/1897] Allow tunnel factory providers to opt out of protocol support (#202067) Part of #201465 --- .../remote/common/remoteAuthorityResolver.ts | 1 + src/vs/platform/tunnel/common/tunnel.ts | 8 ++++++++ .../api/common/extHostExtensionService.ts | 6 +++++- .../workbench/api/common/extHostTunnelService.ts | 6 ++++-- .../contrib/remote/browser/tunnelFactory.ts | 3 ++- .../workbench/contrib/remote/browser/tunnelView.ts | 14 +++++++++++++- src/vscode-dts/vscode.proposed.resolvers.d.ts | 4 ++++ src/vscode-dts/vscode.proposed.tunnelFactory.d.ts | 4 ++++ 8 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index e3c17feb4c9..14aa2b1a256 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -73,6 +73,7 @@ export interface TunnelInformation { elevation: boolean; public?: boolean; privacyOptions: TunnelPrivacy[]; + protocol: boolean; }; } diff --git a/src/vs/platform/tunnel/common/tunnel.ts b/src/vs/platform/tunnel/common/tunnel.ts index 0622e624c15..1038d0056fb 100644 --- a/src/vs/platform/tunnel/common/tunnel.ts +++ b/src/vs/platform/tunnel/common/tunnel.ts @@ -58,6 +58,7 @@ export interface TunnelProviderFeatures { */ public?: boolean; privacyOptions: TunnelPrivacy[]; + protocol: boolean; } export interface ITunnelProvider { @@ -126,6 +127,7 @@ export interface ITunnelService { readonly onTunnelOpened: Event; readonly onTunnelClosed: Event<{ host: string; port: number }>; readonly canElevate: boolean; + readonly canChangeProtocol: boolean; readonly hasTunnelProvider: boolean; readonly onAddedTunnelProvider: Event; @@ -208,6 +210,7 @@ export abstract class AbstractTunnelService implements ITunnelService { protected readonly _tunnels = new Map }>>(); protected _tunnelProvider: ITunnelProvider | undefined; protected _canElevate: boolean = false; + private _canChangeProtocol: boolean = true; private _privacyOptions: TunnelPrivacy[] = []; private _factoryInProgress: Set = new Set(); @@ -250,6 +253,11 @@ export abstract class AbstractTunnelService implements ITunnelService { setTunnelFeatures(features: TunnelProviderFeatures): void { this._canElevate = features.elevation; this._privacyOptions = features.privacyOptions; + this._canChangeProtocol = features.protocol; + } + + public get canChangeProtocol(): boolean { + return this._canChangeProtocol; } public get canElevate(): boolean { diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 0f16b2d3261..91f910aa4c8 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -909,7 +909,11 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme const tunnelInformation: TunnelInformation = { environmentTunnels: result.environmentTunnels, - features: result.tunnelFeatures + features: result.tunnelFeatures ? { + elevation: result.tunnelFeatures.elevation, + privacyOptions: result.tunnelFeatures.privacyOptions, + protocol: result.tunnelFeatures.protocol === undefined ? true : result.tunnelFeatures.protocol, + } : undefined }; // Split merged API result into separate authority/options diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index 7040aca80d9..ccf5700c83b 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -155,7 +155,8 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe const tunnelFeatures = information.tunnelFeatures ? { elevation: !!information.tunnelFeatures?.elevation, - privacyOptions: information.tunnelFeatures?.privacyOptions + privacyOptions: information.tunnelFeatures?.privacyOptions, + protocol: information.tunnelFeatures.protocol === undefined ? true : information.tunnelFeatures.protocol, } : undefined; this._proxy.$setTunnelProvider(tunnelFeatures); @@ -206,7 +207,8 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe const tunnelFeatures = provider.tunnelFeatures ? { elevation: !!provider.tunnelFeatures?.elevation, public: !!provider.tunnelFeatures?.public, - privacyOptions + privacyOptions, + protocol: true } : undefined; this._proxy.$setTunnelProvider(tunnelFeatures); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelFactory.ts b/src/vs/workbench/contrib/remote/browser/tunnelFactory.ts index ce1c98ab11f..74a94d7c21b 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelFactory.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelFactory.ts @@ -88,7 +88,8 @@ export class TunnelFactoryContribution extends Disposable implements IWorkbenchC features: { elevation: !!environmentService.options?.tunnelProvider?.features?.elevation, public: !!environmentService.options?.tunnelProvider?.features?.public, - privacyOptions + privacyOptions, + protocol: environmentService.options?.tunnelProvider?.features?.protocol === undefined ? true : !!environmentService.options?.tunnelProvider?.features?.protocol } } : undefined; remoteExplorerService.setTunnelInformation(tunnelInformation); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index bed24198261..f8b17eacd62 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -738,6 +738,7 @@ const TunnelViewMultiSelectionKeyName = 'tunnelViewMultiSelection'; // host:port[] const TunnelViewMultiSelectionContextKey = new RawContextKey(TunnelViewMultiSelectionKeyName, undefined, true); const PortChangableContextKey = new RawContextKey('portChangable', false, true); +const ProtocolChangeableContextKey = new RawContextKey('protocolChangable', true, true); export class TunnelPanel extends ViewPane { @@ -756,6 +757,7 @@ export class TunnelPanel extends ViewPane { private tunnelViewSelectionContext: IContextKey; private tunnelViewMultiSelectionContext: IContextKey; private portChangableContextKey: IContextKey; + private protocolChangableContextKey: IContextKey; private isEditing: boolean = false; private titleActions: IAction[] = []; private lastFocus: number[] = []; @@ -786,6 +788,8 @@ export class TunnelPanel extends ViewPane { this.tunnelPrivacyContext = TunnelPrivacyContextKey.bindTo(contextKeyService); this.tunnelPrivacyEnabledContext = TunnelPrivacyEnabledContextKey.bindTo(contextKeyService); this.tunnelPrivacyEnabledContext.set(tunnelService.canChangePrivacy); + this.protocolChangableContextKey = ProtocolChangeableContextKey.bindTo(contextKeyService); + this.protocolChangableContextKey.set(tunnelService.canChangeProtocol); this.tunnelProtocolContext = TunnelProtocolContextKey.bindTo(contextKeyService); this.tunnelViewFocusContext = TunnelViewFocusContextKey.bindTo(contextKeyService); this.tunnelViewSelectionContext = TunnelViewSelectionContextKey.bindTo(contextKeyService); @@ -809,8 +813,16 @@ export class TunnelPanel extends ViewPane { this.registerPrivacyActions(); this._register(Event.once(this.tunnelService.onAddedTunnelProvider)(() => { + let updated = false; if (this.tunnelPrivacyEnabledContext.get() === false) { this.tunnelPrivacyEnabledContext.set(tunnelService.canChangePrivacy); + updated = true; + } + if (this.protocolChangableContextKey.get() === true) { + this.protocolChangableContextKey.set(tunnelService.canChangeProtocol); + updated = true; + } + if (updated) { updateActions(); this.registerPrivacyActions(); this.createTable(); @@ -1704,7 +1716,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ order: 3, submenu: MenuId.TunnelProtocol, title: nls.localize('tunnelContext.protocolMenu', "Change Port Protocol"), - when: ContextKeyExpr.and(isForwardedExpr, isNotMultiSelectionExpr) + when: ContextKeyExpr.and(isForwardedExpr, isNotMultiSelectionExpr, ProtocolChangeableContextKey) })); MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ group: '3_forward', diff --git a/src/vscode-dts/vscode.proposed.resolvers.d.ts b/src/vscode-dts/vscode.proposed.resolvers.d.ts index eaef16c501a..3bf1f93eb7d 100644 --- a/src/vscode-dts/vscode.proposed.resolvers.d.ts +++ b/src/vscode-dts/vscode.proposed.resolvers.d.ts @@ -124,6 +124,10 @@ declare module 'vscode' { * One of the the options must have the ID "private". */ privacyOptions: TunnelPrivacy[]; + /** + * Defaults to true for backwards compatibility. + */ + protocol?: boolean; }; } diff --git a/src/vscode-dts/vscode.proposed.tunnelFactory.d.ts b/src/vscode-dts/vscode.proposed.tunnelFactory.d.ts index 5a2192df3c8..828a948b80f 100644 --- a/src/vscode-dts/vscode.proposed.tunnelFactory.d.ts +++ b/src/vscode-dts/vscode.proposed.tunnelFactory.d.ts @@ -22,6 +22,10 @@ declare module 'vscode' { * One of the the options must have the ID "private". */ privacyOptions: TunnelPrivacy[]; + /** + * Defaults to true for backwards compatibility. + */ + protocol?: boolean; }; } From 9808d4e655214cbf4407f3b03026b3a1d710dcb2 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 9 Jan 2024 12:34:27 +0100 Subject: [PATCH 0165/1897] SCM - rename incoming/outgoing menu contribution (#202068) --- extensions/git/package.json | 4 ++-- src/vs/platform/actions/common/actions.ts | 4 ++-- src/vs/workbench/contrib/scm/browser/menus.ts | 2 +- .../services/actions/common/menusExtensionPoint.ts | 12 ++++++------ ...proposed.contribSourceControlHistoryItemMenu.d.ts | 3 ++- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index c41d7ceb386..0dbe6d739e8 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1765,14 +1765,14 @@ "group": "1_modification@3" } ], - "scm/incoming/historyItem/context": [ + "scm/incomingChanges/historyItem/context": [ { "command": "git.openCommit", "when": "scmProvider == git && config.multiDiffEditor.experimental.enabled", "group": "inline@1" } ], - "scm/outgoing/historyItem/context": [ + "scm/outgoingChanges/historyItem/context": [ { "command": "git.openCommit", "when": "scmProvider == git && config.multiDiffEditor.experimental.enabled", diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 9346da55bbf..ba21daa8d9d 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -107,8 +107,8 @@ export class MenuId { static readonly OpenEditorsContextShare = new MenuId('OpenEditorsContextShare'); static readonly ProblemsPanelContext = new MenuId('ProblemsPanelContext'); static readonly SCMInputBox = new MenuId('SCMInputBox'); - static readonly SCMIncomingHistoryItemContext = new MenuId('SCMIncomingHistoryItemContext'); - static readonly SCMOutgoingHistoryItemContext = new MenuId('SCMOutgoingHistoryItemContext'); + static readonly SCMIncomingChangesHistoryItemContext = new MenuId('SCMIncomingChangesHistoryItemContext'); + static readonly SCMOutgoingChangesHistoryItemContext = new MenuId('SCMOutgoingChangesHistoryItemContext'); static readonly SCMChangeContext = new MenuId('SCMChangeContext'); static readonly SCMResourceContext = new MenuId('SCMResourceContext'); static readonly SCMResourceContextShare = new MenuId('SCMResourceContextShare'); diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index fecb988c5ad..7b1b601cea2 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -274,7 +274,7 @@ export class SCMHistoryProviderMenus implements ISCMHistoryProviderMenus, IDispo ]); const menuId = historyItemGroup.direction === 'incoming' ? - MenuId.SCMIncomingHistoryItemContext : MenuId.SCMOutgoingHistoryItemContext; + MenuId.SCMIncomingChangesHistoryItemContext : MenuId.SCMOutgoingChangesHistoryItemContext; result = this.menuService.createMenu(menuId, contextKeyService); this.historyItemMenus.set(historyItem, result); diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 3d62dc3f751..51b6081d9eb 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -151,15 +151,15 @@ const apiMenus: IAPIMenu[] = [ proposed: 'contribSourceControlInputBoxMenu' }, { - key: 'scm/incoming/historyItem/context', - id: MenuId.SCMIncomingHistoryItemContext, - description: localize('menus.incomingHistoryItemContext', "The Source Control incoming history item context menu"), + key: 'scm/incomingChanges/historyItem/context', + id: MenuId.SCMIncomingChangesHistoryItemContext, + description: localize('menus.incomingChangesHistoryItemContext', "The Source Control incoming changes history item context menu"), proposed: 'contribSourceControlHistoryItemMenu' }, { - key: 'scm/outgoing/historyItem/context', - id: MenuId.SCMOutgoingHistoryItemContext, - description: localize('menus.outgoingHistoryItemContext', "The Source Control outgoing history item context menu"), + key: 'scm/outgoingChanges/historyItem/context', + id: MenuId.SCMOutgoingChangesHistoryItemContext, + description: localize('menus.outgoingChangesHistoryItemContext', "The Source Control outgoing changes history item context menu"), proposed: 'contribSourceControlHistoryItemMenu' }, { diff --git a/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemMenu.d.ts b/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemMenu.d.ts index 44bfaf17b01..fc6181be77f 100644 --- a/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemMenu.d.ts +++ b/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemMenu.d.ts @@ -3,5 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// empty placeholder declaration for the `scm/historyItem/context`-menu contribution point +// empty placeholder declaration for the `scm/incomingChanges/historyItem/context`-menu contribution point +// empty placeholder declaration for the `scm/outgoingChanges/historyItem/context`-menu contribution point // https://github.com/microsoft/vscode/issues/201997 From 26a486aee8775246acb7afa38f1c11c1e00cae09 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 9 Jan 2024 17:18:55 +0530 Subject: [PATCH 0166/1897] fix #195436 (#202069) --- .../common/configurationService.ts | 140 +++++++++++++++-- .../test/common/configurationService.test.ts | 147 ++++++++++++++++++ 2 files changed, 278 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index 7eee99fd90e..a985ac1bf14 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -3,15 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { RunOnceScheduler } from 'vs/base/common/async'; +import { distinct, equals as arrayEquals } from 'vs/base/common/arrays'; +import { Queue, RunOnceScheduler } from 'vs/base/common/async'; +import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter, Event } from 'vs/base/common/event'; +import { JSONPath, ParseError, parse } from 'vs/base/common/json'; +import { applyEdits, setProperty } from 'vs/base/common/jsonEdit'; +import { Edit, FormattingOptions } from 'vs/base/common/jsonFormatter'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { equals } from 'vs/base/common/objects'; +import { OS, OperatingSystem } from 'vs/base/common/platform'; import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { ConfigurationTarget, IConfigurationChange, IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationChange, IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationUpdateOptions, IConfigurationUpdateOverrides, IConfigurationValue, isConfigurationOverrides, isConfigurationUpdateOverrides } from 'vs/platform/configuration/common/configuration'; import { Configuration, ConfigurationChangeEvent, ConfigurationModel, UserSettings } from 'vs/platform/configuration/common/configurationModels'; +import { keyFromOverrideIdentifiers } from 'vs/platform/configuration/common/configurationRegistry'; import { DefaultConfiguration, IPolicyConfiguration, NullPolicyConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; -import { IFileService } from 'vs/platform/files/common/files'; +import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; @@ -28,6 +36,8 @@ export class ConfigurationService extends Disposable implements IConfigurationSe private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; + private readonly configurationEditing: ConfigurationEditing; + constructor( private readonly settingsResource: URI, fileService: IFileService, @@ -39,6 +49,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe this.policyConfiguration = policyService instanceof NullPolicyService ? new NullPolicyConfiguration() : this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService)); this.userConfiguration = this._register(new UserSettings(this.settingsResource, {}, extUriBiasedIgnorePathCase, fileService)); this.configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel(), new ConfigurationModel()); + this.configurationEditing = new ConfigurationEditing(settingsResource, fileService, this); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reloadConfiguration(), 50)); this._register(this.defaultConfiguration.onDidChangeConfiguration(({ defaults, properties }) => this.onDidDefaultConfigurationChange(defaults, properties))); @@ -66,15 +77,51 @@ export class ConfigurationService extends Disposable implements IConfigurationSe } updateValue(key: string, value: any): Promise; - updateValue(key: string, value: any, overrides: IConfigurationOverrides): Promise; + updateValue(key: string, value: any, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides): Promise; updateValue(key: string, value: any, target: ConfigurationTarget): Promise; - updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): Promise; - updateValue(key: string, value: any, arg3?: any, arg4?: any): Promise { - return Promise.reject(new Error('not supported')); + updateValue(key: string, value: any, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides, target: ConfigurationTarget, options?: IConfigurationUpdateOptions): Promise; + async updateValue(key: string, value: any, arg3?: any, arg4?: any, options?: any): Promise { + const overrides: IConfigurationUpdateOverrides | undefined = isConfigurationUpdateOverrides(arg3) ? arg3 + : isConfigurationOverrides(arg3) ? { resource: arg3.resource, overrideIdentifiers: arg3.overrideIdentifier ? [arg3.overrideIdentifier] : undefined } : undefined; + + const target: ConfigurationTarget | undefined = overrides ? arg4 : arg3; + if (target !== undefined) { + if (target !== ConfigurationTarget.USER_LOCAL && target !== ConfigurationTarget.USER) { + throw new Error(`Unable to write ${key} to target ${target}.`); + } + } + + if (overrides?.overrideIdentifiers) { + overrides.overrideIdentifiers = distinct(overrides.overrideIdentifiers); + overrides.overrideIdentifiers = overrides.overrideIdentifiers.length ? overrides.overrideIdentifiers : undefined; + } + + const inspect = this.inspect(key, { resource: overrides?.resource, overrideIdentifier: overrides?.overrideIdentifiers ? overrides.overrideIdentifiers[0] : undefined }); + if (inspect.policyValue !== undefined) { + throw new Error(`Unable to write ${key} because it is configured in system policy.`); + } + + // Remove the setting, if the value is same as default value + if (equals(value, inspect.defaultValue)) { + value = undefined; + } + + if (overrides?.overrideIdentifiers?.length && overrides.overrideIdentifiers.length > 1) { + const overrideIdentifiers = overrides.overrideIdentifiers.sort(); + const existingOverrides = this.configuration.localUserConfiguration.overrides.find(override => arrayEquals([...override.identifiers].sort(), overrideIdentifiers)); + if (existingOverrides) { + overrides.overrideIdentifiers = existingOverrides.identifiers; + } + } + + const path = overrides?.overrideIdentifiers?.length ? [keyFromOverrideIdentifiers(overrides.overrideIdentifiers), key] : [key]; + + await this.configurationEditing.write(path, value); + await this.reloadConfiguration(); } - inspect(key: string): IConfigurationValue { - return this.configuration.inspect(key, {}, undefined); + inspect(key: string, overrides: IConfigurationOverrides = {}): IConfigurationValue { + return this.configuration.inspect(key, overrides, undefined); } keys(): { @@ -126,3 +173,78 @@ export class ConfigurationService extends Disposable implements IConfigurationSe return {}; } } + +class ConfigurationEditing { + + private readonly queue: Queue; + + constructor( + private readonly settingsResource: URI, + private readonly fileService: IFileService, + private readonly configurationService: IConfigurationService, + ) { + this.queue = new Queue(); + } + + write(path: JSONPath, value: any): Promise { + return this.queue.queue(() => this.doWriteConfiguration(path, value)); // queue up writes to prevent race conditions + } + + private async doWriteConfiguration(path: JSONPath, value: any): Promise { + let content: string; + try { + const fileContent = await this.fileService.readFile(this.settingsResource); + content = fileContent.value.toString(); + } catch (error) { + if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { + content = '{}'; + } else { + throw error; + } + } + + const parseErrors: ParseError[] = []; + parse(content, parseErrors, { allowTrailingComma: true, allowEmptyContent: true }); + if (parseErrors.length > 0) { + throw new Error('Unable to write into the settings file. Please open the file to correct errors/warnings in the file and try again.'); + } + + const edits = this.getEdits(content, path, value); + content = applyEdits(content, edits); + + await this.fileService.writeFile(this.settingsResource, VSBuffer.fromString(content)); + } + + private getEdits(content: string, path: JSONPath, value: any): Edit[] { + const { tabSize, insertSpaces, eol } = this.formattingOptions; + + // With empty path the entire file is being replaced, so we just use JSON.stringify + if (!path.length) { + const content = JSON.stringify(value, null, insertSpaces ? ' '.repeat(tabSize) : '\t'); + return [{ + content, + length: content.length, + offset: 0 + }]; + } + + return setProperty(content, path, value, { tabSize, insertSpaces, eol }); + } + + private _formattingOptions: Required | undefined; + private get formattingOptions(): Required { + if (!this._formattingOptions) { + let eol = OS === OperatingSystem.Linux || OS === OperatingSystem.Macintosh ? '\n' : '\r\n'; + const configuredEol = this.configurationService.getValue('files.eol', { overrideIdentifier: 'jsonc' }); + if (configuredEol && typeof configuredEol === 'string' && configuredEol !== 'auto') { + eol = configuredEol; + } + this._formattingOptions = { + eol, + insertSpaces: !!this.configurationService.getValue('editor.insertSpaces', { overrideIdentifier: 'jsonc' }), + tabSize: this.configurationService.getValue('editor.tabSize', { overrideIdentifier: 'jsonc' }) + }; + } + return this._formattingOptions!; + } +} diff --git a/src/vs/platform/configuration/test/common/configurationService.test.ts b/src/vs/platform/configuration/test/common/configurationService.test.ts index afa34cc695d..880e49e8e48 100644 --- a/src/vs/platform/configuration/test/common/configurationService.test.ts +++ b/src/vs/platform/configuration/test/common/configurationService.test.ts @@ -17,6 +17,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { NullLogService } from 'vs/platform/log/common/log'; +import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; import { NullPolicyService } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -247,4 +248,150 @@ suite('ConfigurationService.test.ts', () => { assert.strictEqual(res.value, null); assert.strictEqual(res.userValue, null); })); + + test('update configuration', async () => { + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + configurationRegistry.registerConfiguration({ + 'id': '_test', + 'type': 'object', + 'properties': { + 'configurationService.testSetting': { + 'type': 'string', + 'default': 'isSet' + } + } + }); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); + await testObject.initialize(); + + await testObject.updateValue('configurationService.testSetting', 'value'); + assert.strictEqual(testObject.getValue('configurationService.testSetting'), 'value'); + }); + + test('update configuration when exist', async () => { + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + configurationRegistry.registerConfiguration({ + 'id': '_test', + 'type': 'object', + 'properties': { + 'configurationService.testSetting': { + 'type': 'string', + 'default': 'isSet' + } + } + }); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); + await testObject.initialize(); + + await testObject.updateValue('configurationService.testSetting', 'value'); + await testObject.updateValue('configurationService.testSetting', 'updatedValue'); + assert.strictEqual(testObject.getValue('configurationService.testSetting'), 'updatedValue'); + }); + + test('update configuration to default value should remove', async () => { + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + configurationRegistry.registerConfiguration({ + 'id': '_test', + 'type': 'object', + 'properties': { + 'configurationService.testSetting': { + 'type': 'string', + 'default': 'isSet' + } + } + }); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); + await testObject.initialize(); + + await testObject.updateValue('configurationService.testSetting', 'value'); + await testObject.updateValue('configurationService.testSetting', 'isSet'); + const inspect = testObject.inspect('configurationService.testSetting'); + + assert.strictEqual(inspect.userValue, undefined); + }); + + test('update configuration should remove when undefined is passed', async () => { + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + configurationRegistry.registerConfiguration({ + 'id': '_test', + 'type': 'object', + 'properties': { + 'configurationService.testSetting': { + 'type': 'string', + 'default': 'isSet' + } + } + }); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); + await testObject.initialize(); + + await testObject.updateValue('configurationService.testSetting', 'value'); + await testObject.updateValue('configurationService.testSetting', undefined); + const inspect = testObject.inspect('configurationService.testSetting'); + + assert.strictEqual(inspect.userValue, undefined); + }); + + test('update unknown configuration', async () => { + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); + await testObject.initialize(); + + await testObject.updateValue('configurationService.unknownSetting', 'value'); + assert.strictEqual(testObject.getValue('configurationService.unknownSetting'), 'value'); + }); + + test('update configuration in non user target throws error', async () => { + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + configurationRegistry.registerConfiguration({ + 'id': '_test', + 'type': 'object', + 'properties': { + 'configurationService.testSetting': { + 'type': 'string', + 'default': 'isSet' + } + } + }); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); + await testObject.initialize(); + + try { + await testObject.updateValue('configurationService.testSetting', 'value', ConfigurationTarget.WORKSPACE); + assert.fail('Should fail with error'); + } catch (e) { + // succeess + } + }); + + test('update configuration throws error for policy setting', async () => { + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + configurationRegistry.registerConfiguration({ + 'id': '_test', + 'type': 'object', + 'properties': { + 'configurationService.policySetting': { + 'type': 'string', + 'default': 'isSet', + policy: { + name: 'configurationService.policySetting', + minimumVersion: '1.0.0', + } + } + } + }); + + const logService = new NullLogService(); + const policyFile = URI.file('policies.json'); + await fileService.writeFile(policyFile, VSBuffer.fromString('{ "configurationService.policySetting": "policyValue" }')); + const policyService = disposables.add(new FilePolicyService(policyFile, fileService, logService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, policyService, logService)); + await testObject.initialize(); + + try { + await testObject.updateValue('configurationService.policySetting', 'value'); + assert.fail('Should throw error'); + } catch (error) { + // succeess + } + }); }); From 6d9541fff6fb49325e06ecb01f95e56f93963723 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 9 Jan 2024 14:11:19 +0100 Subject: [PATCH 0167/1897] Try out a new Go grammar (#202070) --- extensions/go/cgmanifest.json | 10 +- extensions/go/package.json | 2 +- extensions/go/syntaxes/go.tmLanguage.json | 2456 ++++++++++++++--- .../test/colorize-results/test-13777_go.json | 66 +- .../test/colorize-results/test_go.json | 316 ++- 5 files changed, 2349 insertions(+), 501 deletions(-) diff --git a/extensions/go/cgmanifest.json b/extensions/go/cgmanifest.json index f47768ba861..90df81c75ec 100644 --- a/extensions/go/cgmanifest.json +++ b/extensions/go/cgmanifest.json @@ -4,14 +4,14 @@ "component": { "type": "git", "git": { - "name": "better-go-syntax", - "repositoryUrl": "https://github.com/jeff-hykin/better-go-syntax/ ", - "commitHash": "54ff898316f8647d77ffcf83880a9556445326f1" + "name": "go-syntax", + "repositoryUrl": "https://github.com/worlpaker/go-syntax", + "commitHash": "4014e9376f32e5317eb0c6be286db7ffcba838f9" } }, "license": "MIT", - "description": "The file syntaxes/go.tmLanguage.json is from https://github.com/jeff-hykin/better-go-syntax/ .", - "version": "1.0.0" + "description": "The file syntaxes/go.tmLanguage.json is from https://github.com/worlpaker/go-syntax, which in turn was derived from https://github.com/jeff-hykin/better-go-syntax.", + "version": "0.5.1" } ], "version": 1 diff --git a/extensions/go/package.json b/extensions/go/package.json index 7b3bc14971a..02a6e32df76 100644 --- a/extensions/go/package.json +++ b/extensions/go/package.json @@ -9,7 +9,7 @@ "vscode": "*" }, "scripts": { - "update-grammar": "node ../node_modules/vscode-grammar-updater/bin jeff-hykin/better-go-syntax export/generated.tmLanguage.json ./syntaxes/go.tmLanguage.json" + "update-grammar": "node ../node_modules/vscode-grammar-updater/bin worlpaker/go-syntax syntaxes/go.tmLanguage.json ./syntaxes/go.tmLanguage.json" }, "contributes": { "languages": [ diff --git a/extensions/go/syntaxes/go.tmLanguage.json b/extensions/go/syntaxes/go.tmLanguage.json index 9aa700887ea..85e2bbfe78e 100644 --- a/extensions/go/syntaxes/go.tmLanguage.json +++ b/extensions/go/syntaxes/go.tmLanguage.json @@ -1,311 +1,365 @@ { "information_for_contributors": [ - "This file has been converted from https://github.com/jeff-hykin/better-go-syntax/blob/master/export/generated.tmLanguage.json", + "This file has been converted from https://github.com/worlpaker/go-syntax/blob/master/syntaxes/go.tmLanguage.json", "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jeff-hykin/better-go-syntax/commit/6175663a7a0e23d58ccf9aab95054cb6e5c92aff", + "version": "https://github.com/worlpaker/go-syntax/commit/4014e9376f32e5317eb0c6be286db7ffcba838f9", "name": "Go", "scopeName": "source.go", "patterns": [ { - "include": "#comments" - }, - { - "include": "#comments" - }, - { - "comment": "Interpreted string literals", - "begin": "\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.go" - } - }, - "end": "\"", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.go" - } - }, - "name": "string.quoted.double.go", + "include": "#statements" + } + ], + "repository": { + "statements": { "patterns": [ { - "include": "#string_escaped_char" + "include": "#package_name" }, { - "include": "#string_placeholder" + "include": "#import" + }, + { + "include": "#syntax_errors" + }, + { + "include": "#group-functions" + }, + { + "include": "#group-types" + }, + { + "include": "#group-variables" } ] }, - { - "comment": "Raw string literals", - "begin": "`", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.go" - } - }, - "end": "`", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.go" - } - }, - "name": "string.quoted.raw.go", + "group-functions": { + "comment": "all statements related to functions", "patterns": [ { - "include": "#string_placeholder" + "include": "#function_declaration" + }, + { + "include": "#functions_inline" + }, + { + "include": "#functions" + }, + { + "include": "#built_in_functions" + }, + { + "include": "#support_functions" } ] }, - { - "comment": "Syntax error receiving channels", - "match": "<\\-([\\t ]+)chan\\b", - "captures": { - "1": { - "name": "invalid.illegal.receive-channel.go" - } - } - }, - { - "comment": "Syntax error sending channels", - "match": "\\bchan([\\t ]+)<-", - "captures": { - "1": { - "name": "invalid.illegal.send-channel.go" - } - } - }, - { - "comment": "Syntax error using slices", - "match": "\\[\\](\\s+)", - "captures": { - "1": { - "name": "invalid.illegal.slice.go" - } - } - }, - { - "comment": "Syntax error numeric literals", - "match": "\\b0[0-7]*[89]\\d*\\b", - "name": "invalid.illegal.numeric.go" - }, - { - "comment": "Built-in functions", - "match": "\\b(append|cap|close|complex|copy|delete|imag|len|make|new|panic|print|println|real|recover)\\b(?=\\()", - "name": "support.function.builtin.go" - }, - { - "comment": "Function declarations", - "match": "^(\\bfunc\\b)(?:\\s+(\\([^\\)]+\\)\\s+)?(\\w+)(?=\\())?", - "captures": { - "1": { - "name": "keyword.function.go" + "group-types": { + "comment": "all statements related to types", + "patterns": [ + { + "include": "#other_struct_interface_expressions" }, - "2": { + { + "include": "#struct_variables_types" + }, + { + "include": "#interface_variables_types" + }, + { + "include": "#single_type" + }, + { + "include": "#multi_types" + }, + { + "include": "#struct_interface_declaration" + }, + { + "include": "#double_parentheses_types" + }, + { + "include": "#switch_types" + }, + { + "include": "#type-declarations" + } + ] + }, + "group-variables": { + "comment": "all statements related to variables", + "patterns": [ + { + "include": "#after_control_variables" + }, + { + "include": "#var_const_single_assignment" + }, + { + "include": "#var_assignment" + }, + { + "include": "#var_other_assignment" + }, + { + "include": "#var_const_multi_assignment" + }, + { + "include": "#other_variables" + } + ] + }, + "type-declarations": { + "comment": "includes all type declarations", + "patterns": [ + { + "include": "#language_constants" + }, + { + "include": "#comments" + }, + { + "include": "#map_other_types" + }, + { + "include": "#brackets" + }, + { + "include": "#delimiters" + }, + { + "include": "#keywords" + }, + { + "include": "#operators" + }, + { + "include": "#runes" + }, + { + "include": "#storage_types" + }, + { + "include": "#raw_strings_literals" + }, + { + "include": "#string_literals" + }, + { + "include": "#numeric_literals" + }, + { + "include": "#terminators" + } + ] + }, + "type-declarations-without-brackets": { + "comment": "includes all type declarations without brackets (in some cases, brackets need to be captured manually)", + "patterns": [ + { + "include": "#language_constants" + }, + { + "include": "#comments" + }, + { + "include": "#map_other_types" + }, + { + "include": "#delimiters" + }, + { + "include": "#keywords" + }, + { + "include": "#operators" + }, + { + "include": "#runes" + }, + { + "include": "#storage_types" + }, + { + "include": "#raw_strings_literals" + }, + { + "include": "#string_literals" + }, + { + "include": "#numeric_literals" + }, + { + "include": "#terminators" + } + ] + }, + "parameter-variable-types": { + "comment": "function and generic parameter types", + "patterns": [ + { + "match": "\\{", + "name": "punctuation.definition.begin.bracket.curly.go" + }, + { + "match": "\\}", + "name": "punctuation.definition.end.bracket.curly.go" + }, + { + "begin": "(?:([\\w\\.\\*]+)?(\\[))", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + }, + "2": { + "name": "punctuation.definition.begin.bracket.square.go" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.square.go" + } + }, "patterns": [ { - "include": "#brackets" - }, - { - "include": "#operators" + "include": "#generic_param_types" } ] }, - "3": { + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.begin.bracket.round.go" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.round.go" + } + }, "patterns": [ { - "match": "\\d\\w*", - "name": "invalid.illegal.identifier.go" - }, - { - "match": "\\w+", - "name": "entity.name.function.go" + "include": "#function_param_types" } ] } - } + ] }, - { - "comment": "Functions", - "match": "(\\bfunc\\b)|(\\w+)(?=\\()", - "captures": { - "1": { - "name": "keyword.function.go" - }, - "2": { - "patterns": [ - { - "match": "\\d\\w*", - "name": "invalid.illegal.identifier.go" - }, - { - "match": "\\w+", - "name": "support.function.go" - } - ] - } - } - }, - { - "include": "#numeric_literals" - }, - { + "language_constants": { "comment": "Language constants", "match": "\\b(true|false|nil|iota)\\b", "name": "constant.language.go" }, - { - "begin": "\\b(package)\\s+", - "beginCaptures": { - "1": { - "name": "keyword.package.go" - } - }, - "end": "(?!\\G)", + "comments": { "patterns": [ { - "match": "\\d\\w*", - "name": "invalid.illegal.identifier.go" + "name": "comment.block.go", + "begin": "(\\/\\*)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.comment.go" + } + }, + "end": "(\\*\\/)", + "endCaptures": { + "1": { + "name": "punctuation.definition.comment.go" + } + } }, { - "match": "\\w+", - "name": "entity.name.package.go" + "name": "comment.line.double-slash.go", + "begin": "(\\/\\/)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.comment.go" + } + }, + "end": "(?:\\n|$)" } ] }, - { - "begin": "\\b(type)\\s+", - "beginCaptures": { - "1": { - "name": "keyword.type.go" - } - }, - "end": "(?!\\G)", + "map_other_types": { "patterns": [ { - "match": "\\d\\w*", - "name": "invalid.illegal.identifier.go" - }, - { - "match": "\\w+", - "name": "entity.name.type.go" - } - ] - }, - { - "begin": "\\b(import)\\s+", - "beginCaptures": { - "1": { - "name": "keyword.import.go" - } - }, - "end": "(?!\\G)", - "patterns": [ - { - "include": "#imports" - } - ] - }, - { - "begin": "\\b(var)\\s+", - "beginCaptures": { - "1": { - "name": "keyword.var.go" - } - }, - "end": "(?!\\G)", - "patterns": [ - { - "include": "#variables" - } - ] - }, - { - "match": "(?,\\s*\\w+(?:\\.\\w+)*)*)(?=\\s*=(?!=))", - "captures": { - "1": { - "patterns": [ - { - "match": "\\d\\w*", - "name": "invalid.illegal.identifier.go" + "comment": "Other map type names (struct/interface)", + "match": "(?:(\\bmap\\b)(\\[(?:.*)\\])?((?!\\bfunc\\b|\\bstruct\\b)[\\w\\.\\*]+)?)", + "captures": { + "1": { + "name": "keyword.map.go" }, - { - "match": "\\w+(?:\\.\\w+)*", - "name": "variable.other.assignment.go", - "captures": { - "0": { - "patterns": [ - { - "include": "#delimiters" - } - ] + "2": { + "patterns": [ + { + "include": "#type-declarations-without-brackets" + }, + { + "match": "\\[", + "name": "punctuation.definition.begin.bracket.square.go" + }, + { + "match": "\\]", + "name": "punctuation.definition.end.bracket.square.go" + }, + { + "match": "\\{", + "name": "punctuation.definition.begin.bracket.curly.go" + }, + { + "match": "\\}", + "name": "punctuation.definition.end.bracket.curly.go" + }, + { + "match": "\\(", + "name": "punctuation.definition.begin.bracket.round.go" + }, + { + "match": "\\)", + "name": "punctuation.definition.end.bracket.round.go" + }, + { + "match": "\\w+", + "name": "entity.name.type.go" } - } + ] }, - { - "include": "#delimiters" + "3": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "entity.name.type.go" + } + ] } - ] + } } - } + ] }, - { - "match": "\\b\\w+(?:,\\s*\\w+)*(?=\\s*:=)", - "captures": { - "0": { - "patterns": [ - { - "match": "\\d\\w*", - "name": "invalid.illegal.identifier.go" - }, - { - "match": "\\w+", - "name": "variable.other.assignment.go" - }, - { - "include": "#delimiters" - } - ] - } - } - }, - { - "comment": "Terminators", - "match": ";", - "name": "punctuation.terminator.go" - }, - { - "include": "#brackets" - }, - { - "include": "#delimiters" - }, - { - "include": "#keywords" - }, - { - "include": "#operators" - }, - { - "include": "#runes" - }, - { - "include": "#storage_types" - } - ], - "repository": { "brackets": { "patterns": [ { - "begin": "{", + "begin": "\\{", "beginCaptures": { "0": { "name": "punctuation.definition.begin.bracket.curly.go" } }, - "end": "}", + "end": "\\}", "endCaptures": { "0": { "name": "punctuation.definition.end.bracket.curly.go" @@ -337,44 +391,30 @@ ] }, { - "match": "\\[|\\]", - "name": "punctuation.definition.bracket.square.go" - } - ] - }, - "comments": { - "patterns": [ - { - "name": "comment.block.go", - "begin": "(\\/\\*)", + "begin": "\\[", "beginCaptures": { - "1": { - "name": "punctuation.definition.comment.go" + "0": { + "name": "punctuation.definition.begin.bracket.square.go" } }, - "end": "(\\*\\/)", + "end": "\\]", "endCaptures": { - "1": { - "name": "punctuation.definition.comment.go" - } - } - }, - { - "name": "comment.line.double-slash.go", - "begin": "(\\/\\/)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.comment.go" + "0": { + "name": "punctuation.definition.end.bracket.square.go" } }, - "end": "(?:\\n|$)" + "patterns": [ + { + "include": "$self" + } + ] } ] }, "delimiters": { "patterns": [ { - "match": ",", + "match": "\\,", "name": "punctuation.other.comma.go" }, { @@ -387,52 +427,6 @@ } ] }, - "imports": { - "patterns": [ - { - "match": "((?!\\s+\")[^\\s]*)?\\s*((\")([^\"]*)(\"))", - "captures": { - "1": { - "name": "entity.alias.import.go" - }, - "2": { - "name": "string.quoted.double.go" - }, - "3": { - "name": "punctuation.definition.string.begin.go" - }, - "4": { - "name": "entity.name.import.go" - }, - "5": { - "name": "punctuation.definition.string.end.go" - } - } - }, - { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "punctuation.definition.imports.begin.bracket.round.go" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.definition.imports.end.bracket.round.go" - } - }, - "patterns": [ - { - "include": "#comments" - }, - { - "include": "#imports" - } - ] - } - ] - }, "keywords": { "patterns": [ { @@ -448,6 +442,10 @@ "match": "\\bconst\\b", "name": "keyword.const.go" }, + { + "match": "\\bvar\\b", + "name": "keyword.var.go" + }, { "match": "\\bfunc\\b", "name": "keyword.function.go" @@ -463,6 +461,14 @@ { "match": "\\bstruct\\b", "name": "keyword.struct.go" + }, + { + "match": "\\bimport\\b", + "name": "keyword.control.import.go" + }, + { + "match": "\\btype\\b", + "name": "keyword.type.go" } ] }, @@ -470,7 +476,7 @@ "comment": "Note that the order here is very important!", "patterns": [ { - "match": "(\\*|&)(?=\\w)", + "match": "((?:\\*|&)+)(?:(?!\\d)(?=(?:[\\w\\[\\]])|(?:\\<\\-)))", "name": "keyword.operator.address.go" }, { @@ -502,7 +508,7 @@ "name": "keyword.operator.arithmetic.go" }, { - "match": "(&(?!\\^)|\\||\\^|&\\^|<<|>>)", + "match": "(&(?!\\^)|\\||\\^|&\\^|<<|>>|\\~)", "name": "keyword.operator.arithmetic.bitwise.go" }, { @@ -569,6 +575,59 @@ { "match": "\\buintptr\\b", "name": "storage.type.uintptr.go" + }, + { + "match": "\\bany\\b", + "name": "entity.name.type.any.go" + } + ] + }, + "raw_strings_literals": { + "comment": "Raw string literals", + "begin": "`", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.go" + } + }, + "end": "`", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.go" + } + }, + "name": "string.quoted.raw.go", + "patterns": [ + { + "include": "#string_placeholder" + } + ] + }, + "string_literals": { + "patterns": [ + { + "comment": "Interpreted string literals", + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.go" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.go" + } + }, + "name": "string.quoted.double.go", + "patterns": [ + { + "include": "#string_escaped_char" + }, + { + "include": "#string_placeholder" + } + ] } ] }, @@ -592,86 +651,6 @@ } ] }, - "variables": { - "patterns": [ - { - "match": "(\\w+(?:,\\s*\\w+)*)(\\s+\\*?\\w+(?:\\.\\w+)?\\s*)?(?=\\s*=)", - "captures": { - "1": { - "patterns": [ - { - "match": "\\d\\w*", - "name": "invalid.illegal.identifier.go" - }, - { - "match": "\\w+", - "name": "variable.other.assignment.go" - }, - { - "include": "#delimiters" - } - ] - }, - "2": { - "patterns": [ - { - "include": "$self" - } - ] - } - } - }, - { - "match": "(\\w+(?:,\\s*\\w+)*)(\\s+(\\[(\\d*|\\.\\.\\.)\\])*\\*?(<-)?\\w+(?:\\.\\w+)?\\s*[^=].*)", - "captures": { - "1": { - "patterns": [ - { - "match": "\\d\\w*", - "name": "invalid.illegal.identifier.go" - }, - { - "match": "\\w+", - "name": "variable.other.declaration.go" - }, - { - "include": "#delimiters" - } - ] - }, - "2": { - "patterns": [ - { - "include": "$self" - } - ] - } - } - }, - { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "punctuation.definition.variables.begin.bracket.round.go" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.definition.variables.end.bracket.round.go" - } - }, - "patterns": [ - { - "include": "$self" - }, - { - "include": "#variables" - } - ] - } - ] - }, "numeric_literals": { "match": "(?\\-]+(?:\\s*)(?:\\/(?:\\/|\\*).*)?)$)", + "captures": { + "1": { + "patterns": [ + { + "include": "#type-declarations-without-brackets" + }, + { + "include": "#parameter-variable-types" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + } + } + }, + { + "include": "$self" + } + ] + }, + "function_param_types": { + "comment": "function parameter variables and types", + "patterns": [ + { + "include": "#struct_variables_types" + }, + { + "include": "#type-declarations-without-brackets" + }, + { + "comment": "struct type declaration", + "match": "((?:(?:\\w+\\,\\s*)+)?\\w+)\\s+(?=(?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:[\\[\\]\\*]+)?\\bstruct\\b\\s*\\{)", + "captures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "variable.parameter.go" + } + ] + } + } + }, + { + "comment": "multiple parameters one type -with multilines", + "match": "(?:(?:(?<=\\()|^\\s*)((?:(?:\\w+\\,\\s*)+)(?:/(?:/|\\*).*)?)$)", + "captures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "variable.parameter.go" + } + ] + } + } + }, + { + "comment": "multiple params and types | multiple params one type | one param one type", + "match": "(?:((?:(?:\\w+\\,\\s*)+)?\\w+)(?:\\s+)((?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:(?:(?:[\\w\\[\\]\\.\\*]+)?(?:(?:\\bfunc\\b\\((?:[^\\)]+)?\\))(?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:\\s*))+(?:(?:(?:[\\w\\*\\.\\[\\]]+)|(?:\\((?:[^\\)]+)?\\))))?)|(?:(?:[\\[\\]\\*]+)?[\\w\\*\\.]+(?:\\[(?:[^\\]]+)\\])?(?:[\\w\\.\\*]+)?)+)))", + "captures": { + "1": { + "patterns": [ + { + "include": "#delimiters" + }, + { + "match": "\\w+", + "name": "variable.parameter.go" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#type-declarations-without-brackets" + }, + { + "include": "#parameter-variable-types" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + } + } + }, + { + "include": "#parameter-variable-types" + }, + { + "comment": "other types", + "match": "([\\w\\.]+)", + "captures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + } + } + }, + { + "include": "$self" + } + ] + }, + "generic_param_types": { + "comment": "generic parameter variables and types", + "patterns": [ + { + "include": "#type-declarations-without-brackets" + }, + { + "comment": "multiple parameters one type -with multilines", + "match": "(?:(?:(?<=\\()|^\\s*)((?:(?:\\w+\\,\\s*)+)(?:/(?:/|\\*).*)?)$)", + "captures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "variable.parameter.go" + } + ] + } + } + }, + { + "comment": "multiple params and types | multiple types one param", + "match": "(?:((?:(?:\\w+\\,\\s*)+)?\\w+)(?:\\s+)((?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:(?:(?:[\\w\\[\\]\\.\\*]+)?(?:(?:\\bfunc\\b\\((?:[^\\)]+)?\\))(?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:\\s*))+(?:(?:(?:[\\w\\*\\.]+)|(?:\\((?:[^\\)]+)?\\))))?)|(?:(?:(?:[\\w\\*\\.\\~]+)|(?:\\[(?:(?:[\\w\\.\\*]+)?(?:\\[(?:[^\\]]+)?\\])?(?:\\,\\s+)?)+\\]))(?:[\\w\\.\\*]+)?)+)))", + "captures": { + "1": { + "patterns": [ + { + "include": "#delimiters" + }, + { + "match": "\\w+", + "name": "variable.parameter.go" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#type-declarations-without-brackets" + }, + { + "include": "#parameter-variable-types" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + }, + "3": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + } + } + }, + { + "include": "#parameter-variable-types" + }, + { + "comment": "other types", + "match": "([\\w\\.]+)", + "captures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + } + } + }, + { + "include": "$self" + } + ] + }, + "functions": { + "comment": "Functions", + "begin": "(?:(\\bfunc\\b)(?=\\())", + "beginCaptures": { + "1": { + "name": "keyword.function.go" + } + }, + "end": "(?:(?<=\\))(\\s*(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?((?:(?:\\s*(?:(?:[\\[\\]\\*]+)?[\\w\\.\\*]+)?(?:(?:\\[(?:(?:[\\w\\.\\*]+)?(?:\\[(?:[^\\]]+)?\\])?(?:\\,\\s+)?)+\\])|(?:\\((?:[^\\)]+)?\\)))?(?:[\\w\\.\\*]+)?)(?:\\s*)(?=\\{))|(?:\\s*(?:(?:(?:[\\[\\]\\*]+)?(?!\\bfunc\\b)(?:[\\w\\.\\*]+)(?:\\[(?:(?:[\\w\\.\\*]+)?(?:\\[(?:[^\\]]+)?\\])?(?:\\,\\s+)?)+\\])?(?:[\\w\\.\\*]+)?)|(?:\\((?:[^\\)]+)?\\)))))?)", + "endCaptures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#type-declarations-without-brackets" + }, + { + "include": "#parameter-variable-types" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + } + }, + "patterns": [ + { + "include": "#parameter-variable-types" + } + ] + }, + "functions_inline": { + "comment": "functions in-line with multi return types", + "match": "(?:(\\bfunc\\b)((?:\\((?:.*)\\))(?:\\s+)(?:\\((?:.*)\\)))(?:\\s+)(?=\\{))", + "captures": { + "1": { + "name": "keyword.function.go" + }, + "2": { + "patterns": [ + { + "include": "#type-declarations-without-brackets" + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.begin.bracket.round.go" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.round.go" + } + }, + "patterns": [ + { + "include": "#function_param_types" + }, + { + "include": "$self" + } + ] + }, + { + "match": "\\[", + "name": "punctuation.definition.begin.bracket.square.go" + }, + { + "match": "\\]", + "name": "punctuation.definition.end.bracket.square.go" + }, + { + "match": "\\{", + "name": "punctuation.definition.begin.bracket.curly.go" + }, + { + "match": "\\}", + "name": "punctuation.definition.end.bracket.curly.go" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + } + } + }, + "support_functions": { + "comment": "Support Functions", + "match": "(?:(?:((?<=\\.)\\w+)|(\\w+))(\\[(?:.*)?\\])?(?=\\())", + "captures": { + "1": { + "name": "entity.name.function.support.go" + }, + "2": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\d\\w*", + "name": "invalid.illegal.identifier.go" + }, + { + "match": "\\w+", + "name": "entity.name.function.support.go" + } + ] + }, + "3": { + "patterns": [ + { + "include": "#type-declarations-without-brackets" + }, + { + "match": "\\[", + "name": "punctuation.definition.begin.bracket.square.go" + }, + { + "match": "\\]", + "name": "punctuation.definition.end.bracket.square.go" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + } + } + }, + "other_struct_interface_expressions": { + "patterns": [ + { + "include": "#storage_types" + }, + { + "include": "#label_loop_variable" + }, + { + "include": "#property_variables" + }, + { + "include": "#switch_select_case_variables" + }, + { + "include": "#after_control_variables" + }, + { + "comment": "struct expression before curly bracket", + "match": "((?:(?:\\w+\\.)+)?\\w+)(\\[(?:[^\\]]+)?\\])?(?=\\{)(?|\\<\\=|\\>\\=|\\=\\=|\\!\\=|\\w(?:\\+|/|\\-|\\*|\\%)(?:\\=)?|\\|\\||\\&\\&)(?:\\s*)([[:alnum:]\\-\\_\\!\\.\\[\\]\\<\\>\\=\\*/\\+\\%\\:]+)(?:\\s*)(?=\\{))", + "captures": { + "1": { + "patterns": [ + { + "include": "#type-declarations-without-brackets" + }, + { + "match": "\\[", + "name": "punctuation.definition.begin.bracket.square.go" + }, + { + "match": "\\]", + "name": "punctuation.definition.end.bracket.square.go" + }, + { + "match": "(?:\\w+)", + "name": "variable.other.go" + } + ] + } + } + } + ] + }, + "syntax_errors": { + "patterns": [ + { + "comment": "Syntax error using slices", + "match": "\\[\\](\\s+)", + "captures": { + "1": { + "name": "invalid.illegal.slice.go" + } + } + }, + { + "comment": "Syntax error numeric literals", + "match": "\\b0[0-7]*[89]\\d*\\b", + "name": "invalid.illegal.numeric.go" + } + ] + }, + "built_in_functions": { + "comment": "Built-in functions", + "patterns": [ + { + "match": "\\b(append|cap|close|complex|copy|delete|imag|len|panic|print|println|real|recover|min|max|clear)\\b(?=\\()", + "name": "entity.name.function.support.builtin.go" + }, + { + "comment": "new keyword", + "begin": "(?:(\\bnew\\b)(\\())", + "beginCaptures": { + "1": { + "name": "entity.name.function.support.builtin.go" + }, + "2": { + "name": "punctuation.definition.begin.bracket.round.go" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.round.go" + } + }, + "patterns": [ + { + "include": "#functions" + }, + { + "include": "#struct_variables_types" + }, + { + "include": "#type-declarations" + }, + { + "include": "#generic_types" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + }, + { + "include": "$self" + } + ] + }, + { + "comment": "make keyword", + "match": "(?:(\\bmake\\b)(?:(\\()((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:[\\w\\.\\*\\[\\]\\{\\}]+)(?:\\[(?:[^\\]]+)?\\])?(?:[\\w\\.\\*\\[\\]\\{\\}]+)?)?((?:\\,\\s*[\\w\\.\\(\\)]+)+)?(\\))))", + "captures": { + "1": { + "name": "entity.name.function.support.builtin.go" + }, + "2": { + "name": "punctuation.definition.begin.bracket.round.go" + }, + "3": { + "patterns": [ + { + "include": "#type-declarations-without-brackets" + }, + { + "include": "#parameter-variable-types" + }, + { + "match": "\\w+", + "name": "entity.name.type.go" + } + ] + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + }, + "5": { + "name": "punctuation.definition.end.bracket.round.go" + } + } + } + ] + }, + "struct_interface_declaration": { + "comment": "struct, interface type declarations", + "match": "(?:(?:^|\\s+)(\\btype\\b)(?:\\s+)([\\w\\.]+)(\\[(?:.*)\\])?)", + "captures": { + "1": { + "name": "keyword.type.go" + }, + "2": { + "patterns": [ + { + "match": "\\w+", + "name": "entity.name.type.go" + } + ] + }, + "3": { + "patterns": [ + { + "begin": "\\[", + "beginCaptures": { + "0": { + "name": "punctuation.definition.begin.bracket.square.go" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.square.go" + } + }, + "patterns": [ + { + "include": "#generic_param_types" + }, + { + "include": "$self" + } + ] + } + ] + } + } + }, + "switch_types": { + "begin": "(?<=\\bswitch\\b)(?:\\s*)(?:(\\w+\\s*\\:\\=)?\\s*([\\w\\.\\*\\(\\)\\[\\]]+))(\\.\\(\\btype\\b\\)\\s*)(\\{)", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#operators" + }, + { + "match": "(?:\\w+)", + "name": "variable.other.assignment.go" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#support_functions" + }, + { + "include": "#type-declarations" + }, + { + "match": "(?:\\w+)", + "name": "variable.other.go" + } + ] + }, + "3": { + "patterns": [ + { + "include": "#delimiters" + }, + { + "include": "#brackets" + }, + { + "match": "\\btype\\b", + "name": "keyword.type.go" + } + ] + }, + "4": { + "name": "punctuation.definition.begin.bracket.curly.go" + } + }, + "end": "(?:\\})", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.curly.go" + } + }, + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "(?:^\\s*(\\bcase\\b))(?:\\s+)([\\w\\.\\,\\*\\=\\<\\>\\!\\s]+)(:)(\\s*/(?:/|\\*)\\s*.*)?$", + "captures": { + "1": { + "name": "keyword.control.go" + }, + "2": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "entity.name.type.go" + } + ] + }, + "3": { + "name": "punctuation.other.colon.go" + }, + "4": { + "patterns": [ + { + "include": "#comments" + } + ] + } + } + }, + { + "include": "$self" + } + ] + }, + "var_const_single_assignment": { + "comment": "var and const with single type assignment", + "match": "(?:(?<=\\bvar\\b|\\bconst\\b)(?:\\s*)([\\w\\.]+(?:(?:\\,\\s*[\\w\\.]+)+)?)(?:\\s*)(?:(?!(?:(?:[\\[\\]\\*]+)?\\bstruct\\b\\s*\\{)|(?:(?:[\\[\\]\\*]+)?\\bfunc\\b))((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:(?:(?:[\\w\\.\\*]+)?(?:\\[(?:.*)\\])?(?:[\\w\\.\\*]+)?))?\\s*(?:\\=)?))?)", + "captures": { + "1": { + "patterns": [ + { + "include": "#delimiters" + }, + { + "match": "(?:\\w+)", + "name": "variable.other.assignment.go" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "include": "#generic_types" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + } + } + }, + "var_assignment": { + "comment": "variable assignment", + "match": "(?,\\s*\\w+(?:\\.\\w+)*)*)(?=\\s*=(?!=))", + "captures": { + "1": { + "patterns": [ + { + "match": "\\d\\w*", + "name": "invalid.illegal.identifier.go" + }, + { + "match": "\\w+(?:\\.\\w+)*", + "name": "variable.other.assignment.go", + "captures": { + "0": { + "patterns": [ + { + "include": "#delimiters" + } + ] + } + } + }, + { + "include": "#delimiters" + } + ] + } + } + }, + "var_other_assignment": { + "comment": "variable other assignment", + "match": "\\b\\w+(?:,\\s*\\w+)*(?=\\s*:=)", + "captures": { + "0": { + "patterns": [ + { + "match": "\\d\\w*", + "name": "invalid.illegal.identifier.go" + }, + { + "match": "\\w+", + "name": "variable.other.assignment.go" + }, + { + "include": "#delimiters" + } + ] + } + } + }, + "var_const_multi_assignment": { + "patterns": [ + { + "comment": "var and const with multi type assignment", + "begin": "(?:(?<=\\bvar\\b|\\bconst\\b)(?:\\s*)(\\())", + "beginCaptures": { + "1": { + "name": "punctuation.definition.begin.bracket.round.go" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.round.go" + } + }, + "patterns": [ + { + "match": "(?:(?:^\\s+)([\\w\\.]+(?:(?:\\,\\s*[\\w\\.]+)+)?)(?:\\s*)((?:(?!(?:(?:[\\[\\]\\*]+)?\\bstruct\\b\\s*\\{)|(?:(?:[\\[\\]\\*]+)?\\bfunc\\b))((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:(?:(?:[\\w\\.\\*]+)?(?:\\[(?:.*)\\])?(?:[\\w\\.\\*]+)?))?\\s*(?:\\=)?))?))", + "captures": { + "1": { + "patterns": [ + { + "include": "#delimiters" + }, + { + "match": "(?:\\w+)", + "name": "variable.other.assignment.go" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "include": "#generic_types" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + } + } + }, + { + "include": "$self" + } + ] + } + ] + }, + "generic_types": { + "comment": "Generic support for all types", + "match": "(?:([\\w\\.\\*]+)(\\[(?:[^\\]]+)?\\]))", + "captures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "entity.name.type.go" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#parameter-variable-types" + } + ] + } + } + }, + "switch_select_case_variables": { + "comment": "variables after case control keyword in switch/select expression", + "match": "(?:(?:^\\s*(\\bcase\\b))(?:\\s+)([\\s\\S]+(?:\\:)\\s*(?:/(?:/|\\*).*)?)$)", + "captures": { + "1": { + "name": "keyword.control.go" + }, + "2": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "include": "#support_functions" + }, + { + "match": "\\w+", + "name": "variable.other.go" + } + ] + } + } + }, + "property_variables": { + "patterns": [ + { + "comment": "Property variables in struct", + "match": "(?:((?:[\\w\\.]+)(?:\\:))(?!\\=))", + "captures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "variable.other.property.go" + } + ] + } + } + }, + { + "comment": "property variables as parameter field in struct initialization", + "match": "(?<=[\\w\\.]\\:)(?:\\s*)([\\w\\.\\*\\&\\[\\]]+)(\\.\\w+)(?![\\w\\.\\*\\&\\[\\]]*(?:\\{|\\())", + "captures": { + "1": { + "patterns": [ + { + "include": "$self" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "(?:\\w+)", + "name": "variable.other.property.field.go" + } + ] + } + } + } + ] + }, + "label_loop_variable": { + "comment": "labeled loop variable name", + "match": "((?:^\\s*\\w+:\\s*$)|(?:^\\s*(?:\\bbreak\\b|\\bgoto\\b|\\bcontinue\\b)\\s+\\w+(?:\\s*/(?:/|\\*)\\s*.*)?$))", + "captures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "variable.other.label.go" + } + ] + } + } + }, + "double_parentheses_types": { + "comment": "double parentheses types", + "match": "(?:(?:(\\()([^\\)]+)(\\)))(?=\\((?:[\\w\\.\\*\\&]+)\\)))", + "captures": { + "1": { + "name": "punctuation.definition.begin.bracket.round.go" + }, + "2": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "entity.name.type.go" + } + ] + }, + "3": { + "name": "punctuation.definition.end.bracket.round.go" + } + } + }, + "other_variables": { + "match": "(?:\\w+)", + "name": "variable.other.go", + "patterns": [ + { + "include": "#storage_types" + }, + { + "include": "$self" + } + ] } } } \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-13777_go.json b/extensions/vscode-colorize-tests/test/colorize-results/test-13777_go.json index dbbcbe73add..2309da55308 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-13777_go.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-13777_go.json @@ -29,7 +29,7 @@ }, { "c": "e", - "t": "source.go variable.other.declaration.go", + "t": "source.go variable.other.assignment.go", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -56,8 +56,50 @@ } }, { - "c": "[][]", - "t": "source.go punctuation.definition.bracket.square.go", + "c": "[", + "t": "source.go punctuation.definition.begin.bracket.square.go", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "]", + "t": "source.go punctuation.definition.end.bracket.square.go", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "[", + "t": "source.go punctuation.definition.begin.bracket.square.go", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "]", + "t": "source.go punctuation.definition.end.bracket.square.go", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -84,7 +126,21 @@ } }, { - "c": "aType ", + "c": "aType", + "t": "source.go entity.name.type.go", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0", + "dark_modern": "entity.name.type: #4EC9B0", + "hc_light": "entity.name.type: #185E73", + "light_modern": "entity.name.type: #267F99" + } + }, + { + "c": " ", "t": "source.go", "r": { "dark_plus": "default: #D4D4D4", @@ -125,4 +181,4 @@ "light_modern": "comment: #008000" } } -] \ No newline at end of file +] diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_go.json b/extensions/vscode-colorize-tests/test/colorize-results/test_go.json index 5258e4f205c..4d399b0c0ec 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_go.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_go.json @@ -29,30 +29,30 @@ }, { "c": "main", - "t": "source.go entity.name.package.go", + "t": "source.go entity.name.type.package.go", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" + "hc_black": "entity.name.type: #4EC9B0", + "dark_modern": "entity.name.type: #4EC9B0", + "hc_light": "entity.name.type: #185E73", + "light_modern": "entity.name.type: #267F99" } }, { "c": "import", - "t": "source.go keyword.import.go", + "t": "source.go keyword.control.import.go", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6", - "dark_modern": "keyword: #569CD6", - "hc_light": "keyword: #0F4A85", - "light_modern": "keyword: #0000FF" + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #C586C0", + "hc_light": "keyword.control: #B5200D", + "light_modern": "keyword.control: #AF00DB" } }, { @@ -603,16 +603,16 @@ }, { "c": "make", - "t": "source.go support.function.builtin.go", + "t": "source.go entity.name.function.support.builtin.go", "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "dark_modern": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC", - "light_modern": "support.function: #795E26" + "hc_black": "entity.name.function: #DCDCAA", + "dark_modern": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_modern": "entity.name.function: #795E26" } }, { @@ -784,7 +784,7 @@ } }, { - "c": " management", + "c": " ", "t": "source.go", "r": { "dark_plus": "default: #D4D4D4", @@ -797,6 +797,20 @@ "light_modern": "default: #3B3B3B" } }, + { + "c": "management", + "t": "source.go variable.other.go", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, { "c": ".", "t": "source.go punctuation.other.period.go", @@ -813,16 +827,16 @@ }, { "c": "ClientFromPublishSettingsFile", - "t": "source.go support.function.go", + "t": "source.go entity.name.function.support.go", "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "dark_modern": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC", - "light_modern": "support.function: #795E26" + "hc_black": "entity.name.function: #DCDCAA", + "dark_modern": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_modern": "entity.name.function: #795E26" } }, { @@ -980,7 +994,35 @@ } }, { - "c": " err ", + "c": " ", + "t": "source.go", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "err", + "t": "source.go variable.other.go", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": " ", "t": "source.go", "r": { "dark_plus": "default: #D4D4D4", @@ -1079,16 +1121,16 @@ }, { "c": "panic", - "t": "source.go support.function.builtin.go", + "t": "source.go entity.name.function.support.builtin.go", "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "dark_modern": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC", - "light_modern": "support.function: #795E26" + "hc_black": "entity.name.function: #DCDCAA", + "dark_modern": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_modern": "entity.name.function: #795E26" } }, { @@ -1107,16 +1149,16 @@ }, { "c": "err", - "t": "source.go", + "t": "source.go variable.other.go", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" } }, { @@ -1260,7 +1302,7 @@ } }, { - "c": " vmutils", + "c": " ", "t": "source.go", "r": { "dark_plus": "default: #D4D4D4", @@ -1273,6 +1315,20 @@ "light_modern": "default: #3B3B3B" } }, + { + "c": "vmutils", + "t": "source.go variable.other.go", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, { "c": ".", "t": "source.go punctuation.other.period.go", @@ -1289,16 +1345,16 @@ }, { "c": "NewVMConfiguration", - "t": "source.go support.function.go", + "t": "source.go entity.name.function.support.go", "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "dark_modern": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC", - "light_modern": "support.function: #795E26" + "hc_black": "entity.name.function: #DCDCAA", + "dark_modern": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_modern": "entity.name.function: #795E26" } }, { @@ -1317,16 +1373,16 @@ }, { "c": "dnsName", - "t": "source.go", + "t": "source.go variable.other.go", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" } }, { @@ -1344,7 +1400,7 @@ } }, { - "c": " vmSize", + "c": " ", "t": "source.go", "r": { "dark_plus": "default: #D4D4D4", @@ -1357,6 +1413,20 @@ "light_modern": "default: #3B3B3B" } }, + { + "c": "vmSize", + "t": "source.go variable.other.go", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, { "c": ")", "t": "source.go punctuation.definition.end.bracket.round.go", @@ -1372,7 +1442,7 @@ } }, { - "c": " vmutils", + "c": " ", "t": "source.go", "r": { "dark_plus": "default: #D4D4D4", @@ -1385,6 +1455,20 @@ "light_modern": "default: #3B3B3B" } }, + { + "c": "vmutils", + "t": "source.go variable.other.go", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, { "c": ".", "t": "source.go punctuation.other.period.go", @@ -1401,16 +1485,16 @@ }, { "c": "ConfigureDeploymentFromPlatformImage", - "t": "source.go support.function.go", + "t": "source.go entity.name.function.support.go", "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "dark_modern": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC", - "light_modern": "support.function: #795E26" + "hc_black": "entity.name.function: #DCDCAA", + "dark_modern": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_modern": "entity.name.function: #795E26" } }, { @@ -1457,16 +1541,16 @@ }, { "c": "role", - "t": "source.go", + "t": "source.go variable.other.go", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" } }, { @@ -1484,7 +1568,7 @@ } }, { - "c": " vmImage", + "c": " ", "t": "source.go", "r": { "dark_plus": "default: #D4D4D4", @@ -1497,6 +1581,20 @@ "light_modern": "default: #3B3B3B" } }, + { + "c": "vmImage", + "t": "source.go variable.other.go", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, { "c": ",", "t": "source.go punctuation.other.comma.go", @@ -1512,7 +1610,7 @@ } }, { - "c": " fmt", + "c": " ", "t": "source.go", "r": { "dark_plus": "default: #D4D4D4", @@ -1525,6 +1623,20 @@ "light_modern": "default: #3B3B3B" } }, + { + "c": "fmt", + "t": "source.go variable.other.go", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, { "c": ".", "t": "source.go punctuation.other.period.go", @@ -1541,16 +1653,16 @@ }, { "c": "Sprintf", - "t": "source.go support.function.go", + "t": "source.go entity.name.function.support.go", "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "dark_modern": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC", - "light_modern": "support.function: #795E26" + "hc_black": "entity.name.function: #DCDCAA", + "dark_modern": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_modern": "entity.name.function: #795E26" } }, { @@ -1680,7 +1792,7 @@ } }, { - "c": " storageAccount", + "c": " ", "t": "source.go", "r": { "dark_plus": "default: #D4D4D4", @@ -1693,6 +1805,20 @@ "light_modern": "default: #3B3B3B" } }, + { + "c": "storageAccount", + "t": "source.go variable.other.go", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, { "c": ",", "t": "source.go punctuation.other.comma.go", @@ -1708,7 +1834,7 @@ } }, { - "c": " dnsName", + "c": " ", "t": "source.go", "r": { "dark_plus": "default: #D4D4D4", @@ -1721,6 +1847,20 @@ "light_modern": "default: #3B3B3B" } }, + { + "c": "dnsName", + "t": "source.go variable.other.go", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, { "c": ")", "t": "source.go punctuation.definition.end.bracket.round.go", From 70f5e0883a3e5d445006faba45115b9c70bd8753 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 9 Jan 2024 14:39:28 +0100 Subject: [PATCH 0168/1897] Implements #199291 - Multi Diff Editor: Identify Input With Source, Reload File List Dynamically --- .../base/common/observableInternal/derived.ts | 3 +- .../base/common/observableInternal/utils.ts | 67 +++- .../widget/multiDiffEditorWidget/model.ts | 3 +- .../multiDiffEditorViewModel.ts | 8 +- .../multiDiffEditorWidgetImpl.ts | 28 +- src/vs/workbench/common/editor.ts | 19 +- .../multiDiffEditor/browser/actions.ts | 92 ++++++ .../browser/icons.contribution.ts | 10 + .../browser/multiDiffEditor.contribution.ts | 219 +++---------- .../browser/multiDiffEditorInput.ts | 290 +++++++++++++----- .../browser/multiDiffSourceResolverService.ts | 75 +++++ 11 files changed, 534 insertions(+), 280 deletions(-) create mode 100644 src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts create mode 100644 src/vs/workbench/contrib/multiDiffEditor/browser/icons.contribution.ts create mode 100644 src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts diff --git a/src/vs/base/common/observableInternal/derived.ts b/src/vs/base/common/observableInternal/derived.ts index bcfe729ec37..653c6f39c2d 100644 --- a/src/vs/base/common/observableInternal/derived.ts +++ b/src/vs/base/common/observableInternal/derived.ts @@ -31,10 +31,11 @@ export function derivedOpts( owner?: object; debugName?: string | (() => string | undefined); equalityComparer?: EqualityComparer; + onLastObserverRemoved?: (() => void); }, computeFn: (reader: IReader) => T ): IObservable { - return new Derived(options.owner, options.debugName, computeFn, undefined, undefined, undefined, options.equalityComparer ?? defaultEqualityComparer); + return new Derived(options.owner, options.debugName, computeFn, undefined, undefined, options.onLastObserverRemoved, options.equalityComparer ?? defaultEqualityComparer); } /** diff --git a/src/vs/base/common/observableInternal/utils.ts b/src/vs/base/common/observableInternal/utils.ts index 35b7c695f5a..35b01114b45 100644 --- a/src/vs/base/common/observableInternal/utils.ts +++ b/src/vs/base/common/observableInternal/utils.ts @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { autorun } from 'vs/base/common/observableInternal/autorun'; import { BaseObservable, ConvenientObservable, IObservable, IObserver, IReader, ITransaction, _setRecomputeInitiallyAndOnChange, getDebugName, getFunctionName, observableValue, subtransaction, transaction } from 'vs/base/common/observableInternal/base'; -import { derived } from 'vs/base/common/observableInternal/derived'; +import { derived, derivedOpts } from 'vs/base/common/observableInternal/derived'; import { getLogger } from 'vs/base/common/observableInternal/logging'; /** @@ -406,3 +406,68 @@ export function derivedObservableWithWritableCache(owner: object, computeFn: }, }); } + +/** + * When the items array changes, referential equal items are not mapped again. + */ +export function mapObservableArrayCached(items: IObservable, ownerOrName: object | string, map: (input: TIn, store: DisposableStore) => TOut, keySelector?: (input: TIn) => TKey): IObservable { + let m = new ArrayMap(map, keySelector); + return derivedOpts({ + debugName: typeof ownerOrName === 'string' ? ownerOrName : undefined, + owner: typeof ownerOrName === 'string' ? undefined : ownerOrName, + onLastObserverRemoved: () => { + m.dispose(); + m = new ArrayMap(map); + } + }, (reader) => { + m.setItems(items.read(reader)); + return m.getItems(); + }); +} + +class ArrayMap implements IDisposable { + private readonly _cache = new Map(); + private _items: TOut[] = []; + constructor( + private readonly _map: (input: TIn, store: DisposableStore) => TOut, + private readonly _keySelector?: (input: TIn) => TKey, + ) { + } + + public dispose(): void { + this._cache.forEach(entry => entry.store.dispose()); + this._cache.clear(); + } + + public setItems(items: readonly TIn[]): void { + const newItems: TOut[] = []; + const itemsToRemove = new Set(this._cache.keys()); + + for (const item of items) { + const key = this._keySelector ? this._keySelector(item) : item as unknown as TKey; + + let entry = this._cache.get(key); + if (!entry) { + const store = new DisposableStore(); + const out = this._map(item, store); + entry = { out, store }; + this._cache.set(key, entry); + } else { + itemsToRemove.delete(key); + } + newItems.push(entry.out); + } + + for (const item of itemsToRemove) { + const entry = this._cache.get(item)!; + entry.store.dispose(); + this._cache.delete(item); + } + + this._items = newItems; + } + + public getItems(): TOut[] { + return this._items; + } +} diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts index 376a4557308..70e32ff56bb 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts @@ -8,7 +8,7 @@ import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextModel } from 'vs/editor/common/model'; export interface IMultiDiffEditorModel { - readonly documents: LazyPromise[]; + readonly documents: readonly LazyPromise[]; readonly onDidChange: Event; } @@ -35,7 +35,6 @@ export class ConstLazyPromise implements LazyPromise { } export interface IDocumentDiffItem { - readonly title: string; readonly original: ITextModel | undefined; // undefined if the file was created. readonly modified: ITextModel | undefined; // undefined if the file was deleted. readonly options?: IDiffEditorOptions; diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts index 786c8e7e579..f459fb6db98 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { derivedWithStore, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable'; +import { observableFromEvent, observableValue, transaction } from 'vs/base/common/observable'; +import { mapObservableArrayCached } from 'vs/base/common/observableInternal/utils'; import { DiffEditorOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorOptions'; import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; import { IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; @@ -15,9 +16,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti export class MultiDiffEditorViewModel extends Disposable { private readonly _documents = observableFromEvent(this._model.onDidChange, /** @description MultiDiffEditorViewModel.documents */() => this._model.documents); - public readonly items = derivedWithStore(this, - (reader, store) => this._documents.read(reader).map(d => store.add(new DocumentDiffItemViewModel(d, this._instantiationService))) - ).recomputeInitiallyAndOnChange(this._store); + public readonly items = mapObservableArrayCached(this._documents, this, (d, store) => store.add(new DocumentDiffItemViewModel(d, this._instantiationService))) + .recomputeInitiallyAndOnChange(this._store); public readonly activeDiffItem = observableValue(this, undefined); diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index f5b162613ba..4a1baec1796 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -239,25 +239,31 @@ class VirtualizedViewItem extends Disposable { const isFocused = ref.object.isFocused.read(reader); if (isFocused) { return; } - transaction(tx => { - this._lastTemplateData.set({ - contentHeight: ref.object.height.get(), - maxScroll: { maxScroll: 0, width: 0, } // Reset max scroll - }, tx); - ref.object.hide(); - - this._templateRef.set(undefined, tx); - }); + this._clear(); })); } override dispose(): void { - this.hide(); + this._clear(); super.dispose(); } public override toString(): string { - return `VirtualViewItem(${this.viewModel.entry.value!.title})`; + return `VirtualViewItem(${this.viewModel.entry.value!.modified?.uri.toString()})`; + } + + private _clear(): void { + const ref = this._templateRef.get(); + if (!ref) { return; } + transaction(tx => { + this._lastTemplateData.set({ + contentHeight: ref.object.height.get(), + maxScroll: { maxScroll: 0, width: 0, } // Reset max scroll + }, tx); + ref.object.hide(); + + this._templateRef.set(undefined, tx); + }); } public hide(): void { diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index ee6a8e501c8..b90fb940fe6 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -495,9 +495,16 @@ export interface IResourceDiffEditorInput extends IBaseUntypedEditorInput { */ export interface IResourceMultiDiffEditorInput extends IBaseUntypedEditorInput { /** - * The list of resources to compare. + * A unique identifier of this multi diff editor input. + * If a second multi diff editor with the same uri is opened, the existing one is revealed instead (even if the resources list is different!). */ - readonly resources: (IResourceDiffEditorInput & { readonly resource: URI })[]; + readonly multiDiffSource?: URI; + + /** + * The list of resources to compare. + * If not set, the resources are dynamically derived from the {@link multiDiffSource}. + */ + readonly resources?: IResourceDiffEditorInput[]; } export type IResourceMergeEditorInputSide = (IResourceEditorInput | ITextResourceEditorInput) & { detail?: string }; @@ -558,8 +565,14 @@ export function isResourceDiffListEditorInput(editor: unknown): editor is IResou } const candidate = editor as IResourceMultiDiffEditorInput | undefined; + if (!candidate) { + return false; + } + if (candidate.resources && !Array.isArray(candidate.resources)) { + return false; + } - return Array.isArray(candidate?.resources); + return !!candidate.resources || !!candidate.multiDiffSource; } export function isResourceSideBySideEditorInput(editor: unknown): editor is IResourceSideBySideEditorInput { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts new file mode 100644 index 00000000000..296861e895e --- /dev/null +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { URI } from 'vs/base/common/uri'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { localize2 } from 'vs/nls'; +import { Action2, MenuId } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { MultiDiffEditor } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor'; +import { MultiDiffEditorInput } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +export class GoToFileAction extends Action2 { + constructor() { + super({ + id: 'multiDiffEditor.goToFile', + title: localize2('goToFile', 'Open File'), + icon: Codicon.goToFile, + precondition: EditorContextKeys.inMultiDiffEditor, + menu: { + when: EditorContextKeys.inMultiDiffEditor, + id: MenuId.MultiDiffEditorFileToolbar, + order: 22, + group: 'navigation', + }, + }); + } + + run(accessor: ServicesAccessor, ...args: any[]): void { + const uri = args[0] as URI; + const editorService = accessor.get(IEditorService); + editorService.openEditor({ resource: uri }); + } +} + +export class CollapseAllAction extends Action2 { + constructor() { + super({ + id: 'multiDiffEditor.collapseAll', + title: localize2('collapseAllDiffs', 'Collapse All Diffs'), + icon: Codicon.collapseAll, + precondition: ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), ContextKeyExpr.not('multiDiffEditorAllCollapsed')), + menu: { + when: ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), ContextKeyExpr.not('multiDiffEditorAllCollapsed')), + id: MenuId.EditorTitle, + group: 'navigation', + }, + f1: true, + }); + } + + async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const editorService = accessor.get(IEditorService); + const activeEditor = editorService.activeEditor; + + if (activeEditor instanceof MultiDiffEditorInput) { + const viewModel = await activeEditor.getViewModel(); + viewModel.collapseAll(); + } + } +} + +export class ExpandAllAction extends Action2 { + constructor() { + super({ + id: 'multiDiffEditor.expandAll', + title: localize2('ExpandAllDiffs', 'Expand All Diffs'), + icon: Codicon.expandAll, + precondition: ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), ContextKeyExpr.has('multiDiffEditorAllCollapsed')), + menu: { + when: ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), ContextKeyExpr.has('multiDiffEditorAllCollapsed')), + id: MenuId.EditorTitle, + group: 'navigation', + }, + f1: true, + }); + } + + async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const editorService = accessor.get(IEditorService); + const activeEditor = editorService.activeEditor; + + if (activeEditor instanceof MultiDiffEditorInput) { + const viewModel = await activeEditor.getViewModel(); + viewModel.expandAll(); + } + } +} diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/icons.contribution.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/icons.contribution.ts new file mode 100644 index 00000000000..40c9b0ae568 --- /dev/null +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/icons.contribution.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { localize } from 'vs/nls'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; + +export const MultiDiffEditorIcon = registerIcon('multi-diff-editor-label-icon', Codicon.diffMultiple, localize('multiDiffEditorLabelIcon', 'Icon of the multi diff editor label.')); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts index eebfbc32e6e..c34ddd0318e 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts @@ -3,196 +3,47 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize, localize2 } from 'vs/nls'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { DEFAULT_EDITOR_ASSOCIATION, EditorExtensions, EditorInputWithOptions, IEditorFactoryRegistry, IEditorSerializer, IResourceMultiDiffEditorInput } from 'vs/workbench/common/editor'; -import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { parse } from 'vs/base/common/marshalling'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { MultiDiffEditorInput, MultiDiffEditorInputData } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { MultiDiffEditor } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor'; -import { Codicon } from 'vs/base/common/codicons'; -import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; -import { URI } from 'vs/base/common/uri'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; - -class MultiDiffEditorResolverContribution extends Disposable { - - constructor( - @IEditorResolverService editorResolverService: IEditorResolverService, - @IInstantiationService instantiationService: IInstantiationService, - ) { - super(); - - this._register(editorResolverService.registerEditor( - `*`, - { - id: DEFAULT_EDITOR_ASSOCIATION.id, - label: DEFAULT_EDITOR_ASSOCIATION.displayName, - detail: DEFAULT_EDITOR_ASSOCIATION.providerDisplayName, - priority: RegisteredEditorPriority.builtin - }, - {}, - { - createMultiDiffEditorInput: (diffListEditor: IResourceMultiDiffEditorInput): EditorInputWithOptions => { - return { - editor: instantiationService.createInstance( - MultiDiffEditorInput, - diffListEditor.label, - diffListEditor.resources.map(resource => { - return new MultiDiffEditorInputData( - resource.resource, - resource.original.resource, - resource.modified.resource, - ); - }), - undefined, - ), - }; - } - } - )); - } -} - -Registry - .as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(MultiDiffEditorResolverContribution, LifecyclePhase.Starting); - -class MultiDiffEditorSerializer implements IEditorSerializer { - - canSerialize(editor: EditorInput): boolean { - return true; - } - - serialize(editor: MultiDiffEditorInput): string | undefined { - return JSON.stringify({ label: editor.label, resources: editor.resources, id: editor.id }); - } - - deserialize(instantiationService: IInstantiationService, serializedEditor: string): EditorInput | undefined { - try { - const data = parse(serializedEditor) as { label: string | undefined; resources: MultiDiffEditorInputData[]; id: string }; - return instantiationService.createInstance(MultiDiffEditorInput, data.label, data.resources, data.id); - } catch (err) { - onUnexpectedError(err); - return undefined; - } - } -} - -Registry.as(EditorExtensions.EditorPane).registerEditorPane( - EditorPaneDescriptor.create( - MultiDiffEditor, - MultiDiffEditor.ID, - localize('name', "Multi Diff Editor") - ), - [ - new SyncDescriptor(MultiDiffEditorInput) - ] -); - -Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer( - MultiDiffEditorInput.ID, - MultiDiffEditorSerializer -); - -export class GoToFileAction extends Action2 { - constructor() { - super({ - id: 'multiDiffEditor.goToFile', - title: localize2('goToFile', 'Open File'), - icon: Codicon.goToFile, - precondition: EditorContextKeys.inMultiDiffEditor, - menu: { - when: EditorContextKeys.inMultiDiffEditor, - id: MenuId.MultiDiffEditorFileToolbar, - order: 22, - group: 'navigation', - }, - }); - } - - run(accessor: ServicesAccessor, ...args: any[]): void { - const uri = args[0] as URI; - const editorService = accessor.get(IEditorService); - editorService.openEditor({ resource: uri }); - } -} - -export class CollapseAllAction extends Action2 { - constructor() { - super({ - id: 'multiDiffEditor.collapseAll', - title: localize2('collapseAllDiffs', 'Collapse All Diffs'), - icon: Codicon.collapseAll, - precondition: ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), ContextKeyExpr.not('multiDiffEditorAllCollapsed')), - menu: { - when: ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), ContextKeyExpr.not('multiDiffEditorAllCollapsed')), - id: MenuId.EditorTitle, - group: 'navigation', - }, - f1: true, - }); - } - - async run(accessor: ServicesAccessor, ...args: any[]): Promise { - const editorService = accessor.get(IEditorService); - const activeEditor = editorService.activeEditor; - - if (activeEditor instanceof MultiDiffEditorInput) { - const viewModel = await activeEditor.getViewModel(); - viewModel.collapseAll(); - } - } -} - -export class ExpandAllAction extends Action2 { - constructor() { - super({ - id: 'multiDiffEditor.expandAll', - title: localize2('ExpandAllDiffs', 'Expand All Diffs'), - icon: Codicon.expandAll, - precondition: ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), ContextKeyExpr.has('multiDiffEditorAllCollapsed')), - menu: { - when: ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), ContextKeyExpr.has('multiDiffEditorAllCollapsed')), - id: MenuId.EditorTitle, - group: 'navigation', - }, - f1: true, - }); - } - - async run(accessor: ServicesAccessor, ...args: any[]): Promise { - const editorService = accessor.get(IEditorService); - const activeEditor = editorService.activeEditor; - - if (activeEditor instanceof MultiDiffEditorInput) { - const viewModel = await activeEditor.getViewModel(); - viewModel.expandAll(); - } - } -} +import { MultiDiffEditorInput, MultiDiffEditorResolverContribution, MultiDiffEditorSerializer } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { CollapseAllAction, ExpandAllAction, GoToFileAction } from './actions'; +import { IMultiDiffSourceResolverService, MultiDiffSourceResolverService } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; registerAction2(GoToFileAction); registerAction2(CollapseAllAction); registerAction2(ExpandAllAction); -Registry.as(Extensions.Configuration).registerConfiguration({ - properties: { - 'multiDiffEditor.experimental.enabled': { - type: 'boolean', - default: false, - description: 'Enable experimental multi diff editor.', - }, - } -}); +Registry.as(Extensions.Configuration) + .registerConfiguration({ + properties: { + 'multiDiffEditor.experimental.enabled': { + type: 'boolean', + default: false, + description: 'Enable experimental multi diff editor.', + }, + } + }); + +registerSingleton(IMultiDiffSourceResolverService, MultiDiffSourceResolverService, InstantiationType.Delayed); + +// Editor Integration +Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(MultiDiffEditorResolverContribution, LifecyclePhase.Starting); + +Registry.as(EditorExtensions.EditorPane) + .registerEditorPane( + EditorPaneDescriptor.create(MultiDiffEditor, MultiDiffEditor.ID, localize('name', "Multi Diff Editor")), + [new SyncDescriptor(MultiDiffEditorInput)] + ); + +Registry.as(EditorExtensions.EditorFactory) + .registerEditorSerializer(MultiDiffEditorInput.ID, MultiDiffEditorSerializer); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 51616103eb7..fdd6e11453a 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -4,71 +4,116 @@ *--------------------------------------------------------------------------------------------*/ import { LazyStatefulPromise, raceTimeout } from 'vs/base/common/async'; -import { Codicon } from 'vs/base/common/codicons'; -import { onUnexpectedError } from 'vs/base/common/errors'; +import { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors'; +import { Emitter, Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { IDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, IReference, toDisposable } from 'vs/base/common/lifecycle'; +import { parse } from 'vs/base/common/marshalling'; import { deepClone } from 'vs/base/common/objects'; +import { autorun, derived, observableFromEvent, observableValue } from 'vs/base/common/observable'; +import { mapObservableArrayCached } from 'vs/base/common/observableInternal/utils'; import { ThemeIcon } from 'vs/base/common/themables'; import { isDefined, isObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { ConstLazyPromise, IDocumentDiffItem, IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; +import { ConstLazyPromise, IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; -import { DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities } from 'vs/workbench/common/editor'; +import { DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, EditorInputWithOptions, IEditorSerializer, IResourceMultiDiffEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { MultiDiffEditorIcon } from 'vs/workbench/contrib/multiDiffEditor/browser/icons.contribution'; +import { ConstResolvedMultiDiffSource, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService'; +import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { ILanguageSupport } from 'vs/workbench/services/textfile/common/textfiles'; -const MultiDiffEditorIcon = registerIcon('multi-diff-editor-label-icon', Codicon.diffMultiple, localize('multiDiffEditorLabelIcon', 'Icon of the multi diff editor label.')); - /* hot-reload:patch-prototype-methods */ export class MultiDiffEditorInput extends EditorInput implements ILanguageSupport { + public static fromResourceMultiDiffEditorInput(input: IResourceMultiDiffEditorInput, instantiationService: IInstantiationService): MultiDiffEditorInput { + if (!input.multiDiffSource && !input.resources) { + throw new BugIndicatingError('MultiDiffEditorInput requires either multiDiffSource or resources'); + } + const multiDiffSource = input.multiDiffSource ?? URI.parse(`multi-diff-editor:${new Date().getMilliseconds().toString() + Math.random().toString()}`); + return instantiationService.createInstance( + MultiDiffEditorInput, + multiDiffSource, + input.label, + input.resources?.map(resource => { + return new MultiDiffEditorItem( + resource.original.resource, + resource.modified.resource, + ); + }), + ); + } + + public static fromSerialized(data: ISerializedMultiDiffEditorInput, instantiationService: IInstantiationService): MultiDiffEditorInput { + return instantiationService.createInstance( + MultiDiffEditorInput, + URI.parse(data.multiDiffSourceUri), + data.label, + data.resources?.map(resource => new MultiDiffEditorItem( + resource.originalUri ? URI.parse(resource.originalUri) : undefined, + resource.modifiedUri ? URI.parse(resource.modifiedUri) : undefined, + )) + ); + } + static readonly ID: string = 'workbench.input.multiDiffEditor'; - public readonly id: string; + get resource(): URI | undefined { return this.multiDiffSource; } - get resource(): URI | undefined { - return URI.parse(`multi-diff-editor:${this.id}`); - } + override get capabilities(): EditorInputCapabilities { return EditorInputCapabilities.Readonly; } + override get typeId(): string { return MultiDiffEditorInput.ID; } - override get capabilities(): EditorInputCapabilities { - return EditorInputCapabilities.Readonly; - } + private _name: string = ''; + override getName(): string { return this._name; } - override get typeId(): string { - return MultiDiffEditorInput.ID; - } - - override getName(): string { - return (this.label ?? localize('name', "Multi Diff Editor")) + ` (${this.resources.length} files)`; - } - - override get editorId(): string { - return DEFAULT_EDITOR_ASSOCIATION.id; - } - - override getIcon(): ThemeIcon { - return MultiDiffEditorIcon; - } + override get editorId(): string { return DEFAULT_EDITOR_ASSOCIATION.id; } + override getIcon(): ThemeIcon { return MultiDiffEditorIcon; } constructor( - readonly label: string | undefined, - readonly resources: readonly MultiDiffEditorInputData[], - id: string | undefined, + public readonly multiDiffSource: URI, + public readonly label: string | undefined, + public readonly initialResources: readonly MultiDiffEditorItem[] | undefined, @ITextModelService private readonly _textModelService: ITextModelService, @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IModelService private readonly _modelService: IModelService, + @IMultiDiffSourceResolverService private readonly _multiDiffSourceResolverService: IMultiDiffSourceResolverService, ) { super(); - this.id = id || new Date().getMilliseconds().toString() + Math.random().toString(); + + this._register(autorun((reader) => { + /** @description Updates name */ + const resources = this._resources.read(reader)?.read(reader); + const label = this.label ?? localize('name', "Multi Diff Editor"); + this._name = label + localize('files', " ({0} files)", resources?.length ?? 0); + this._onDidChangeLabel.fire(); + })); + } + + private readonly _resolvedMultiDiffSource = observableValue(this, undefined); + private readonly _resources = derived(this, reader => { + const s = this._resolvedMultiDiffSource.read(reader); + if (!s) { return undefined; } + return observableFromEvent(s.onDidChange, () => s.resources); + }); + + public serialize(): ISerializedMultiDiffEditorInput { + return { + label: this.label, + multiDiffSourceUri: this.multiDiffSource.toString(), + resources: this.initialResources?.map(resource => ({ + originalUri: resource.original?.toString(), + modifiedUri: resource.modified?.toString(), + })), + }; } setLanguageId(languageId: string, source?: string | undefined): void { @@ -85,6 +130,13 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor } private readonly _viewModel = new LazyStatefulPromise(async () => { + this._resolvedMultiDiffSource.set( + this.initialResources + ? new ConstResolvedMultiDiffSource(this.initialResources) + : await this._multiDiffSourceResolverService.resolve(this.multiDiffSource), + undefined + ); + const model = await this._createModel(); this._register(model); const vm = new MultiDiffEditorViewModel(model, this._instantiationService); @@ -94,52 +146,94 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor }); private async _createModel(): Promise { - const store = new DisposableStore(); - const rs = (await Promise.all(this.resources.map(async r => { + const resources = this._resources.get()!; + const textResourceConfigurationService = this._textResourceConfigurationService; + + // Enables delayed disposing + const garbage = new DisposableStore(); + + const documentsWithPromises = mapObservableArrayCached(resources, 'documentsWithPromises', async (r, store) => { + let originalTextModel: ITextModel; + let modifiedTextModel: ITextModel; + let modifiedRef: IReference | undefined; + const store2 = new DisposableStore(); + store.add(toDisposable(() => { + // Mark the text model references as garbage when they get stale (don't dispose them yet) + garbage.add(store2); + })); + try { - return { - originalRef: r.original ? store.add(await this._textModelService.createModelReference(r.original)) : undefined, - originalModel: !r.original ? store.add(this._modelService.createModel('', null)) : undefined, - modifiedRef: r.modified ? store.add(await this._textModelService.createModelReference(r.modified)) : undefined, - modifiedModel: !r.modified ? store.add(this._modelService.createModel('', null)) : undefined, - title: r.resource.fsPath, - }; + [originalTextModel, modifiedTextModel] = await Promise.all([ + r.original + ? store2.add(await this._textModelService.createModelReference(r.original)).object.textEditorModel + : store2.add(this._modelService.createModel('', null)), + r.modified + ? store2.add(modifiedRef = await this._textModelService.createModelReference(r.modified)).object.textEditorModel + : store2.add(this._modelService.createModel('', null)), + ]); } catch (e) { // e.g. "File seems to be binary and cannot be opened as text" console.error(e); onUnexpectedError(e); return undefined; } - }))).filter(isDefined); - const textResourceConfigurationService = this._textResourceConfigurationService; + const uri = (r.modified ?? r.original)!; + return new ConstLazyPromise({ + original: originalTextModel, + modified: modifiedTextModel, + get options() { + return { + ...getReadonlyConfiguration(modifiedRef?.object.isReadonly() ?? true), + ...computeOptions(textResourceConfigurationService.getValue(uri)), + } satisfies IDiffEditorOptions; + }, + onOptionsDidChange: h => this._textResourceConfigurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(uri, 'editor') || e.affectsConfiguration(uri, 'diffEditor')) { + h(); + } + }), + }); + }, i => JSON.stringify([i.modified?.toString(), i.original?.toString()])); + + let documents: readonly LazyPromise[] = []; + const documentChangeEmitter = new Emitter(); + + const p = Event.toPromise(documentChangeEmitter.event); + + const a = autorun(async reader => { + /** @description Update documents */ + const docsPromises = documentsWithPromises.read(reader); + const docs = await Promise.all(docsPromises); + documents = docs.filter(isDefined); + documentChangeEmitter.fire(); + + garbage.clear(); // Only dispose text models after the documents have been updated + }); + + await p; return { - dispose: () => store.dispose(), - onDidChange: () => toDisposable(() => { }), - documents: rs.map(r => { - const uri = (r.originalRef ?? r.modifiedRef!).object.textEditorModel.uri; - return new ConstLazyPromise({ - original: r.originalRef ? r.originalRef.object.textEditorModel : r.originalModel!, - modified: r.modifiedRef ? r.modifiedRef.object.textEditorModel : r.modifiedModel!, - title: r.title, - get options() { - r.modifiedRef?.object.isReadonly; - - return { - ...getReadonlyConfiguration(r.modifiedRef?.object.isReadonly() ?? true), - ...computeOptions(textResourceConfigurationService.getValue(uri)), - } satisfies IDiffEditorOptions; - }, - onOptionsDidChange: h => this._textResourceConfigurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(uri, 'editor') || e.affectsConfiguration(uri, 'diffEditor')) { - h(); - } - }), - }); - }), + dispose: () => { + a.dispose(); + garbage.dispose(); + }, + onDidChange: documentChangeEmitter.event, + get documents() { return documents; }, }; } + + override matches(otherInput: EditorInput | IUntypedEditorInput): boolean { + if (super.matches(otherInput)) { + return true; + } + + if (otherInput instanceof MultiDiffEditorInput) { + return this.multiDiffSource.toString() === otherInput.multiDiffSource.toString(); + } + + return false; + } } function getReadonlyConfiguration(isReadonly: boolean | IMarkdownString | undefined): { readOnly: boolean; readOnlyMessage: IMarkdownString | undefined } { @@ -169,10 +263,58 @@ function computeOptions(configuration: IEditorConfiguration): IDiffEditorOptions return editorConfiguration; } -export class MultiDiffEditorInputData { +export class MultiDiffEditorResolverContribution extends Disposable { constructor( - readonly resource: URI, - readonly original: URI | undefined, - readonly modified: URI | undefined - ) { } + @IEditorResolverService editorResolverService: IEditorResolverService, + @IInstantiationService instantiationService: IInstantiationService, + ) { + super(); + + this._register(editorResolverService.registerEditor( + `*`, + { + id: DEFAULT_EDITOR_ASSOCIATION.id, + label: DEFAULT_EDITOR_ASSOCIATION.displayName, + detail: DEFAULT_EDITOR_ASSOCIATION.providerDisplayName, + priority: RegisteredEditorPriority.builtin + }, + {}, + { + createMultiDiffEditorInput: (diffListEditor: IResourceMultiDiffEditorInput): EditorInputWithOptions => { + return { + editor: MultiDiffEditorInput.fromResourceMultiDiffEditorInput(diffListEditor, instantiationService), + }; + }, + } + )); + } +} + +interface ISerializedMultiDiffEditorInput { + multiDiffSourceUri: string; + label: string | undefined; + resources: { + originalUri: string | undefined; + modifiedUri: string | undefined; + }[] | undefined; +} + +export class MultiDiffEditorSerializer implements IEditorSerializer { + canSerialize(editor: EditorInput): boolean { + return editor instanceof MultiDiffEditorInput; + } + + serialize(editor: MultiDiffEditorInput): string | undefined { + return JSON.stringify(editor.serialize()); + } + + deserialize(instantiationService: IInstantiationService, serializedEditor: string): EditorInput | undefined { + try { + const data = parse(serializedEditor) as ISerializedMultiDiffEditorInput; + return MultiDiffEditorInput.fromSerialized(data, instantiationService); + } catch (err) { + onUnexpectedError(err); + return undefined; + } + } } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts new file mode 100644 index 00000000000..78f9ea7deab --- /dev/null +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts @@ -0,0 +1,75 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; +import { Event } from 'vs/base/common/event'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { BugIndicatingError } from 'vs/base/common/errors'; + +export const IMultiDiffSourceResolverService = createDecorator('multiDiffSourceResolverService'); + +export interface IMultiDiffSourceResolverService { + readonly _serviceBrand: undefined; + + registerResolver(resolver: IMultiDiffSourceResolver): IDisposable; + + resolve(uri: URI): Promise; +} + +export interface IMultiDiffSourceResolver { + canHandleUri(uri: URI): boolean; + + resolveDiffSource(uri: URI): Promise; +} + +export interface IResolvedMultiDiffSource { + readonly resources: readonly MultiDiffEditorItem[]; + + readonly onDidChange: Event; +} + +export class ConstResolvedMultiDiffSource implements IResolvedMultiDiffSource { + public readonly onDidChange = Event.None; + + constructor( + public readonly resources: readonly MultiDiffEditorItem[] + ) { } +} + +export class MultiDiffEditorItem { + constructor( + readonly original: URI | undefined, + readonly modified: URI | undefined, + ) { + if (!original && !modified) { + throw new BugIndicatingError('Invalid arguments'); + } + } +} + +export class MultiDiffSourceResolverService implements IMultiDiffSourceResolverService { + public readonly _serviceBrand: undefined; + + private readonly _resolvers = new Set(); + + registerResolver(resolver: IMultiDiffSourceResolver): IDisposable { + // throw on duplicate + if (this._resolvers.has(resolver)) { + throw new BugIndicatingError('Duplicate resolver'); + } + this._resolvers.add(resolver); + return toDisposable(() => this._resolvers.delete(resolver)); + } + + resolve(uri: URI): Promise { + for (const resolver of this._resolvers) { + if (resolver.canHandleUri(uri)) { + return resolver.resolveDiffSource(uri); + } + } + return Promise.resolve(undefined); + } +} From 77ca6ccd41400699307cbbc05ba02fec124e9019 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 9 Jan 2024 15:43:26 +0100 Subject: [PATCH 0169/1897] Joh/mysterious-octopus (#202079) * debt - don't use extra buttons for accept/discard hunk * - inline chat history shouldn't loop - preserve the initial value and restore it fixes https://github.com/microsoft/vscode-copilot-release/issues/671 --- .../browser/inlineChat.contribution.ts | 1 + .../inlineChat/browser/inlineChatActions.ts | 28 ++++++- .../browser/inlineChatController.ts | 34 +++++++- .../browser/inlineChatStrategies.ts | 79 +++++++++---------- .../inlineChat/browser/inlineChatWidget.ts | 14 +--- 5 files changed, 97 insertions(+), 59 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index b896a99bf9b..973c1072b1a 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -30,6 +30,7 @@ registerAction2(InlineChatActions.UnstashSessionAction); registerAction2(InlineChatActions.MakeRequestAction); registerAction2(InlineChatActions.StopRequestAction); registerAction2(InlineChatActions.ReRunRequestAction); +registerAction2(InlineChatActions.DiscardHunkAction); registerAction2(InlineChatActions.DiscardAction); registerAction2(InlineChatActions.DiscardToClipboardAction); registerAction2(InlineChatActions.DiscardUndoToNewFileAction); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 337fcaaf551..ecca297e868 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -303,6 +303,29 @@ export class NextFromHistory extends AbstractInlineChatAction { } } +export class DiscardHunkAction extends AbstractInlineChatAction { + + constructor() { + super({ + id: 'inlineChat.discardHunkChange', + title: localize('discard', 'Discard'), + icon: Codicon.clearAll, + precondition: CTX_INLINE_CHAT_VISIBLE, + menu: { + id: MENU_INLINE_CHAT_WIDGET_STATUS, + when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.OnlyMessages), CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live)), + group: '0_main', + order: 3 + } + }); + } + + async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): Promise { + return ctrl.discardHunk(); + } +} + + MenuRegistry.appendMenuItem(MENU_INLINE_CHAT_WIDGET_STATUS, { submenu: MENU_INLINE_CHAT_WIDGET_DISCARD, title: localize('discardMenu', "Discard..."), @@ -510,7 +533,7 @@ export class AcceptChanges extends AbstractInlineChatAction { when: CTX_INLINE_CHAT_USER_DID_EDIT }], menu: { - when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.OnlyMessages), CTX_INLINE_CHAT_EDIT_MODE.notEqualsTo(EditMode.Live)), + when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.OnlyMessages)), id: MENU_INLINE_CHAT_WIDGET_STATUS, group: '0_main', order: 0 @@ -519,7 +542,7 @@ export class AcceptChanges extends AbstractInlineChatAction { } override async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController): Promise { - ctrl.acceptSession(); + ctrl.acceptHunk(); } } @@ -549,6 +572,7 @@ export class CancelSessionAction extends AbstractInlineChatAction { } } + export class CloseAction extends AbstractInlineChatAction { constructor() { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 5057e127ba5..951ddfbbefb 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -101,6 +101,7 @@ export class InlineChatController implements IEditorContribution { private static _storageKey = 'inline-chat-history'; private static _promptHistory: string[] = []; private _historyOffset: number = -1; + private _historyCandidate: string = ''; private _historyUpdate: (prompt: string) => void; private readonly _store = new DisposableStore(); @@ -173,6 +174,7 @@ export class InlineChatController implements IEditorContribution { } InlineChatController._promptHistory.unshift(prompt); this._historyOffset = -1; + this._historyCandidate = ''; this._storageService.store(InlineChatController._storageKey, JSON.stringify(InlineChatController._promptHistory), StorageScope.PROFILE, StorageTarget.USER); }; } @@ -230,6 +232,7 @@ export class InlineChatController implements IEditorContribution { this._editor.setSelection(options.initialSelection); } this._historyOffset = -1; + this._historyCandidate = ''; this._onWillStartSession.fire(); this._currentRun = this._nextState(State.CREATE_SESSION, options); await this._currentRun; @@ -986,12 +989,29 @@ export class InlineChatController implements IEditorContribution { if (len === 0) { return; } - const pos = (len + this._historyOffset + (up ? 1 : -1)) % len; - const entry = InlineChatController._promptHistory[pos]; + + if (this._historyOffset === -1) { + // remember the current value + this._historyCandidate = this._zone.value.widget.value; + } + + const newIdx = this._historyOffset + (up ? 1 : -1); + if (newIdx >= len) { + // reached the end + return; + } + + let entry: string; + if (newIdx < 0) { + entry = this._historyCandidate; + this._historyOffset = -1; + } else { + entry = InlineChatController._promptHistory[newIdx]; + this._historyOffset = newIdx; + } this._zone.value.widget.value = entry; this._zone.value.widget.selectAll(); - this._historyOffset = pos; } viewInChat() { @@ -1042,6 +1062,14 @@ export class InlineChatController implements IEditorContribution { this._messages.fire(Message.ACCEPT_SESSION); } + acceptHunk() { + return this._strategy?.acceptHunk(); + } + + discardHunk() { + return this._strategy?.discardHunk(); + } + async cancelSession() { let result: string | undefined; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 56fe6fced08..12f26c6878d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -4,18 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import { disposableWindowInterval } from 'vs/base/browser/dom'; -import { IAction, toAction } from 'vs/base/common/actions'; import { coalesceInPlace, equals, tail } from 'vs/base/common/arrays'; import { AsyncIterableObject, AsyncIterableSource } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { Lazy } from 'vs/base/common/lazy'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ThemeIcon, themeColorFromId } from 'vs/base/common/themables'; +import { themeColorFromId } from 'vs/base/common/themables'; import { ICodeEditor, IViewZone, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; -import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; import { LineSource, RenderOptions, renderLines } from 'vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines'; import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation'; @@ -34,7 +31,6 @@ import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgress, Progress } from 'vs/platform/progress/common/progress'; -import { IStorageService } from 'vs/platform/storage/common/storage'; import { SaveReason } from 'vs/workbench/common/editor'; import { countWords, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { InlineChatFileCreatePreviewWidget, InlineChatLivePreviewWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget'; @@ -76,6 +72,14 @@ export abstract class EditModeStrategy { abstract cancel(): Promise; + async acceptHunk(): Promise { + this._onDidAccept.fire(); + } + + async discardHunk(): Promise { + this._onDidDiscard.fire(); + } + abstract makeProgressiveChanges(targetWindow: Window, edits: ISingleEditOperation[], timings: ProgressingEditsOptions): Promise; abstract makeChanges(targetWindow: Window, edits: ISingleEditOperation[]): Promise; @@ -199,8 +203,6 @@ export class LivePreviewStrategy extends EditModeStrategy { session: Session, private readonly _editor: ICodeEditor, zone: InlineChatZoneWidget, - @IStorageService storageService: IStorageService, - @IBulkEditService bulkEditService: IBulkEditService, @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, @IInstantiationService private readonly _instaService: IInstantiationService, ) { @@ -243,6 +245,7 @@ export class LivePreviewStrategy extends EditModeStrategy { const targetAltVersion = textModelNSnapshotAltVersion ?? textModelNAltVersion; await undoModelUntil(modelN, targetAltVersion); } + override async makeChanges(_targetWindow: Window, edits: ISingleEditOperation[]): Promise { const cursorStateComputerAndInlineDiffCollection: ICursorStateComputer = (undoEdits) => { let last: Position | null = null; @@ -516,6 +519,9 @@ export class LiveStrategy extends EditModeStrategy { private _editCount: number = 0; + override acceptHunk: () => Promise = () => super.acceptHunk(); + override discardHunk: () => Promise = () => super.discardHunk(); + constructor( session: Session, protected readonly _editor: ICodeEditor, @@ -668,7 +674,8 @@ export class LiveStrategy extends EditModeStrategy { distance: number; position: Position; - actions: IAction[]; + acceptHunk: () => void; + discardHunk: () => void; toggleDiff?: () => any; }; @@ -695,36 +702,25 @@ export class LiveStrategy extends EditModeStrategy { decorationIds.push(decorationsAccessor.addDecoration(change.modifiedRange, this._decoInsertedTextRange)); } - const actions = [ - toAction({ - id: 'accept', - label: localize('accept', "Accept"), - class: ThemeIcon.asClassName(Codicon.check), - run: () => { - // ACCEPT: stop rendering this as inserted - hunkDisplayData.get(hunk)!.acceptedOrRejected = HunkState.Accepted; - renderHunks(); - } - }), - toAction({ - id: 'discard', - label: localize('discard', "Discard"), - class: ThemeIcon.asClassName(Codicon.discard), - run: () => { - const edits: ISingleEditOperation[] = []; - for (let i = 1; i < decorationIds.length; i++) { - // DISCARD: replace modified range with original value. The modified range is retrieved from a decoration - // which was created above so that typing in the editor keeps discard working. - const modifiedRange = this._session.textModelN.getDecorationRange(decorationIds[i])!; - const originalValue = this._session.textModel0.getValueInRange(hunk.changes[i - 1].originalRange); - edits.push(EditOperation.replace(modifiedRange, originalValue)); - } - this._session.textModelN.pushEditOperations(null, edits, () => null); - hunkDisplayData.get(hunk)!.acceptedOrRejected = HunkState.Rejected; - renderHunks(); - } - }), - ]; + const acceptHunk = () => { + // ACCEPT: stop rendering this as inserted + hunkDisplayData.get(hunk)!.acceptedOrRejected = HunkState.Accepted; + renderHunks(); + }; + + const discardHunk = () => { + const edits: ISingleEditOperation[] = []; + for (let i = 1; i < decorationIds.length; i++) { + // DISCARD: replace modified range with original value. The modified range is retrieved from a decoration + // which was created above so that typing in the editor keeps discard working. + const modifiedRange = this._session.textModelN.getDecorationRange(decorationIds[i])!; + const originalValue = this._session.textModel0.getValueInRange(hunk.changes[i - 1].originalRange); + edits.push(EditOperation.replace(modifiedRange, originalValue)); + } + this._session.textModelN.pushEditOperations(null, edits, () => null); + hunkDisplayData.get(hunk)!.acceptedOrRejected = HunkState.Rejected; + renderHunks(); + }; // original view zone const mightContainNonBasicASCII = this._session.textModel0.mightContainNonBasicASCII() ?? false; @@ -776,8 +772,9 @@ export class LiveStrategy extends EditModeStrategy { viewZone: viewZoneData, distance: myDistance, position: modifiedRange.getStartPosition().delta(-1), + acceptHunk, + discardHunk, toggleDiff: !hunk.original.isEmpty ? toggleDiff : undefined, - actions }; hunkDisplayData.set(hunk, data); @@ -813,7 +810,6 @@ export class LiveStrategy extends EditModeStrategy { }); if (widgetData) { - this._zone.widget.setExtraButtons(widgetData.actions); this._zone.updatePositionAndHeight(widgetData.position); this._editor.revealPositionInCenterIfOutsideViewport(widgetData.position); @@ -822,6 +818,8 @@ export class LiveStrategy extends EditModeStrategy { this._ctxCurrentChangeHasDiff.set(Boolean(widgetData.toggleDiff)); this.toggleDiff = widgetData.toggleDiff; + this.acceptHunk = async () => widgetData!.acceptHunk(); + this.discardHunk = async () => widgetData!.discardHunk(); } else if (hunkDisplayData.size > 0) { // everything accepted or rejected @@ -843,7 +841,6 @@ export class LiveStrategy extends EditModeStrategy { renderHunks(); this._renderStore.add(toDisposable(() => { - this._zone.widget.setExtraButtons([]); changeDecorationsAndViewZones(this._editor, (decorationsAccessor, viewZoneAccessor) => { for (const data of hunkDisplayData.values()) { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 6678164b6f5..669caac3ae4 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -46,7 +46,7 @@ import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibil import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { ExpansionState } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import * as aria from 'vs/base/browser/ui/aria/aria'; -import { IWorkbenchButtonBarOptions, MenuWorkbenchButtonBar, WorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; +import { IWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; @@ -67,7 +67,6 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IChatReplyFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; -import { IAction } from 'vs/base/common/actions'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -183,7 +182,6 @@ export class InlineChatWidget { h('div.followUps.hidden@followUps'), h('div.status@status', [ h('div.label.info.hidden@infoLabel'), - h('div.actions.hidden@extraToolbar'), h('div.actions.hidden@statusToolbar'), h('div.label.status.hidden@statusLabel'), h('div.actions.hidden@feedbackToolbar'), @@ -571,15 +569,6 @@ export class InlineChatWidget { this._onDidChangeHeight.fire(); } - private _extraButtonsCleanup = this._store.add(new MutableDisposable()); - - setExtraButtons(buttons: IAction[]) { - const bar = this._instantiationService.createInstance(WorkbenchButtonBar, this._elements.extraToolbar, { telemetrySource: 'inlineChat' }); - bar.update(buttons); - this._elements.extraToolbar.classList.toggle('hidden', buttons.length === 0); - this._extraButtonsCleanup.value = bar; - } - get expansionState(): ExpansionState { return this._expansionState; } @@ -743,7 +732,6 @@ export class InlineChatWidget { reset(this._elements.statusLabel); this._elements.detectedIntent.classList.toggle('hidden', true); this._elements.statusLabel.classList.toggle('hidden', true); - this._elements.extraToolbar.classList.add('hidden'); this._elements.statusToolbar.classList.add('hidden'); this._elements.feedbackToolbar.classList.add('hidden'); this.updateInfo(''); From bbec320f5dc1e50dd38dff6d4615d69ca027b508 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 9 Jan 2024 15:48:10 +0100 Subject: [PATCH 0170/1897] Fix memory leak in tree (#202080) Part of #200015 --- .../api/browser/mainThreadTreeViews.ts | 3 +++ .../api/browser/viewsExtensionPoint.ts | 2 +- .../workbench/browser/parts/views/treeView.ts | 4 ++-- .../services/views/browser/treeViewsService.ts | 3 +-- .../services/views/common/treeViewsService.ts | 18 +++++++++--------- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index a19161c1221..270a4cc9f3d 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -270,6 +270,9 @@ class TreeViewDataProvider implements ITreeViewDataProvider { } getChildren(treeItem?: ITreeItem): Promise { + if (!treeItem) { + this.itemsMap.clear(); + } return this._proxy.$getChildren(this.treeViewId, treeItem ? treeItem.handle : undefined) .then( children => this.postGetChildren(children), diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 15bca66e58e..c51db51e1b5 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -307,7 +307,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { if (!treeItem.tooltip) { return; } - const element = treeViewsService.getRenderedTreeElement(treeItem); + const element = treeViewsService.getRenderedTreeElement(('handle' in treeItem) ? treeItem.handle : treeItem); if (!element) { return; } diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index bd54589ef44..19bfe50a6e2 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -1266,7 +1266,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer { } +export interface ITreeViewsService extends ITreeViewsServiceCommon { } export const ITreeViewsService = createDecorator('treeViewsService'); registerSingleton(ITreeViewsService, TreeviewsService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/views/common/treeViewsService.ts b/src/vs/workbench/services/views/common/treeViewsService.ts index ac1631c2d5f..4785e5fd362 100644 --- a/src/vs/workbench/services/views/common/treeViewsService.ts +++ b/src/vs/workbench/services/views/common/treeViewsService.ts @@ -3,30 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export interface ITreeViewsService { +export interface ITreeViewsService { readonly _serviceBrand: undefined; - getRenderedTreeElement(node: U): V | undefined; - addRenderedTreeItemElement(node: U, element: V): void; - removeRenderedTreeItemElement(node: U): void; + getRenderedTreeElement(node: string): V | undefined; + addRenderedTreeItemElement(node: string, element: V): void; + removeRenderedTreeItemElement(node: string): void; } -export class TreeviewsService implements ITreeViewsService { +export class TreeviewsService implements ITreeViewsService { _serviceBrand: undefined; - private _renderedElements: Map = new Map(); + private _renderedElements: Map = new Map(); - getRenderedTreeElement(node: U): V | undefined { + getRenderedTreeElement(node: string): V | undefined { if (this._renderedElements.has(node)) { return this._renderedElements.get(node); } return undefined; } - addRenderedTreeItemElement(node: U, element: V): void { + addRenderedTreeItemElement(node: string, element: V): void { this._renderedElements.set(node, element); } - removeRenderedTreeItemElement(node: U): void { + removeRenderedTreeItemElement(node: string): void { if (this._renderedElements.has(node)) { this._renderedElements.delete(node); } From 6d9bf2113d0bee0620160e1cfbfd81591f7c94e1 Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 9 Jan 2024 16:11:38 +0100 Subject: [PATCH 0171/1897] Fix click focus glitch (#202084) fix click focus glitch --- src/vs/base/browser/ui/tree/abstractTree.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 2a956d84150..848a195256f 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1727,7 +1727,7 @@ class StickyScrollFocus extends Disposable { if (stickyIndex === -1) { throw new Error('Context menu should not be triggered when element is not in sticky scroll widget'); } - this.toggleStickyScrollFocused(true); + this.container.focus(); this.setFocus(stickyIndex); return; } @@ -1775,8 +1775,6 @@ class StickyScrollFocus extends Disposable { e.browserEvent.preventDefault(); e.browserEvent.stopPropagation(); - - this.container.focus(); } updateElements(elements: HTMLElement[], state: StickyScrollState | undefined): void { From 1e9f54e20208f4a04b0b25019a32ff771c2a0f82 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 9 Jan 2024 16:21:10 +0100 Subject: [PATCH 0172/1897] Linked editing causing uncaught exception (#202085) --- .../linkedEditing/browser/linkedEditing.ts | 111 +++++++++--------- 1 file changed, 55 insertions(+), 56 deletions(-) diff --git a/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts b/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts index f862405cd96..491c00b67d4 100644 --- a/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts +++ b/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as arrays from 'vs/base/common/arrays'; -import { CancelablePromise, createCancelablePromise, Delayer, first } from 'vs/base/common/async'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { Delayer, first } from 'vs/base/common/async'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Color } from 'vs/base/common/color'; import { isCancellationError, onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; @@ -66,7 +66,7 @@ export class LinkedEditingContribution extends Disposable implements IEditorCont private _rangeUpdateTriggerPromise: Promise | null; private _rangeSyncTriggerPromise: Promise | null; - private _currentRequest: CancelablePromise | null; + private _currentRequestCts: CancellationTokenSource | null; private _currentRequestPosition: Position | null; private _currentRequestModelVersion: number | null; @@ -102,7 +102,7 @@ export class LinkedEditingContribution extends Disposable implements IEditorCont this._rangeUpdateTriggerPromise = null; this._rangeSyncTriggerPromise = null; - this._currentRequest = null; + this._currentRequestCts = null; this._currentRequestPosition = null; this._currentRequestModelVersion = null; @@ -258,9 +258,9 @@ export class LinkedEditingContribution extends Disposable implements IEditorCont public clearRanges(): void { this._visibleContextKey.set(false); this._currentDecorations.clear(); - if (this._currentRequest) { - this._currentRequest.cancel(); - this._currentRequest = null; + if (this._currentRequestCts) { + this._currentRequestCts.cancel(); + this._currentRequestCts = null; this._currentRequestPosition = null; } } @@ -305,61 +305,60 @@ export class LinkedEditingContribution extends Disposable implements IEditorCont this._currentRequestPosition = position; this._currentRequestModelVersion = modelVersionId; - const request = createCancelablePromise(async token => { - try { - const sw = new StopWatch(false); - const response = await getLinkedEditingRanges(this._providers, model, position, token); - this._debounceInformation.update(model, sw.elapsed()); - if (request !== this._currentRequest) { - return; - } - this._currentRequest = null; - if (modelVersionId !== model.getVersionId()) { - return; - } - let ranges: IRange[] = []; - if (response?.ranges) { - ranges = response.ranges; - } + const currentRequestCts = this._currentRequestCts = new CancellationTokenSource(); + try { + const sw = new StopWatch(false); + const response = await getLinkedEditingRanges(this._providers, model, position, currentRequestCts.token); + this._debounceInformation.update(model, sw.elapsed()); + if (currentRequestCts !== this._currentRequestCts) { + return; + } + this._currentRequestCts = null; + if (modelVersionId !== model.getVersionId()) { + return; + } - this._currentWordPattern = response?.wordPattern || this._languageWordPattern; + let ranges: IRange[] = []; + if (response?.ranges) { + ranges = response.ranges; + } - let foundReferenceRange = false; - for (let i = 0, len = ranges.length; i < len; i++) { - if (Range.containsPosition(ranges[i], position)) { - foundReferenceRange = true; - if (i !== 0) { - const referenceRange = ranges[i]; - ranges.splice(i, 1); - ranges.unshift(referenceRange); - } - break; + this._currentWordPattern = response?.wordPattern || this._languageWordPattern; + + let foundReferenceRange = false; + for (let i = 0, len = ranges.length; i < len; i++) { + if (Range.containsPosition(ranges[i], position)) { + foundReferenceRange = true; + if (i !== 0) { + const referenceRange = ranges[i]; + ranges.splice(i, 1); + ranges.unshift(referenceRange); } - } - - if (!foundReferenceRange) { - // Cannot do linked editing if the ranges are not where the cursor is... - this.clearRanges(); - return; - } - - const decorations: IModelDeltaDecoration[] = ranges.map(range => ({ range: range, options: LinkedEditingContribution.DECORATION })); - this._visibleContextKey.set(true); - this._currentDecorations.set(decorations); - this._syncRangesToken++; // cancel any pending syncRanges call - } catch (err) { - if (!isCancellationError(err)) { - onUnexpectedError(err); - } - if (this._currentRequest === request || !this._currentRequest) { - // stop if we are still the latest request - this.clearRanges(); + break; } } - }); - this._currentRequest = request; - return request; + + if (!foundReferenceRange) { + // Cannot do linked editing if the ranges are not where the cursor is... + this.clearRanges(); + return; + } + + const decorations: IModelDeltaDecoration[] = ranges.map(range => ({ range: range, options: LinkedEditingContribution.DECORATION })); + this._visibleContextKey.set(true); + this._currentDecorations.set(decorations); + this._syncRangesToken++; // cancel any pending syncRanges call + } catch (err) { + if (!isCancellationError(err)) { + onUnexpectedError(err); + } + if (this._currentRequestCts === currentRequestCts || !this._currentRequestCts) { + // stop if we are still the latest request + this.clearRanges(); + } + } + } // for testing From 6bd246028a486f1b54ea563718ea29c3a7512d46 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 9 Jan 2024 16:41:03 +0100 Subject: [PATCH 0173/1897] some API notes (#202087) --- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 0800dbe77c5..41b4d727ee6 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -102,11 +102,13 @@ declare module 'vscode' { * slash command is prepended to the chat input. */ readonly shouldRepopulate?: boolean; + /** * Placeholder text to render in the chat input * when the slash command has been repopulated. * Has no effect if `shouldRepopulate` is `false`. */ + // TODO@API merge this with shouldRepopulate? so that invalid state cannot be represented? readonly followupPlaceholder?: string; } @@ -224,7 +226,6 @@ declare module 'vscode' { onDidReceiveFeedback: Event; /** - * TODO@API explain what happens wrt to history, in-flight requests etc... * Dispose this agent and free resources */ dispose(): void; @@ -249,6 +250,7 @@ declare module 'vscode' { variables: Record; } + // TODO@API we need to arrive at a state where we can put things into buckets-by-name of (1) rendered data, (2) metadata, etc pp export type ChatAgentProgress = | ChatAgentContent | ChatAgentTask @@ -313,6 +315,8 @@ declare module 'vscode' { /** * A Thenable resolving to the real content. The placeholder will be replaced with this content once it's available. */ + // TODO@API Should this be an async iterable or progress instance instead + // TODO@API Should this include more inline-renderable items like `ChatAgentInlineContentReference` resolvedContent: Thenable; } @@ -338,11 +342,15 @@ declare module 'vscode' { /** * A Uri for this node, opened when it's clicked. */ + // TODO@API why label and uri. Can the former be derived from the latter? + // TODO@API don't use uri but just names? This API allows to to build nonsense trees where the data structure doesn't match the uris + // path-structure. uri: Uri; /** * The type of this node. Defaults to {@link FileType.Directory} if it has {@link ChatAgentFileTreeData.children children}. */ + // TODO@API cross API usage type?: FileType; /** From 2d01e64a05218c6482b2c2b0c7ecbd5d40aad4b5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 9 Jan 2024 21:26:29 +0530 Subject: [PATCH 0174/1897] do not register command twice (#202088) #199744 do not register command twice --- .../preferences/test/browser/keybindingsEditorModel.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts b/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts index 58c5d5169c3..ca279aaea69 100644 --- a/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts +++ b/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts @@ -140,7 +140,6 @@ suite('KeybindingsEditorModel', () => { ); registerCommandWithTitle(keybindings[1].command!, 'Same Title'); - registerCommandWithTitle(keybindings[3].command!, 'Same Title'); const expected = [keybindings[3], keybindings[1], keybindings[0], keybindings[2]]; await testObject.resolve(new Map()); From 67e16ed45acfd32ac6e764a029c2aba9ccfee6e7 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 9 Jan 2024 17:00:55 +0100 Subject: [PATCH 0175/1897] Enable lightbulb setting to `on` (includes empty lines) (#202089) Enable lightbulb for empty lines (for #200538) --- src/vs/editor/common/config/editorOptions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 764e94ed31e..2890c08e763 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2748,7 +2748,7 @@ export type EditorLightbulbOptions = Readonly> class EditorLightbulb extends BaseEditorOption { constructor() { - const defaults: EditorLightbulbOptions = { enabled: ShowLightbulbIconMode.OnCode }; + const defaults: EditorLightbulbOptions = { enabled: ShowLightbulbIconMode.On }; super( EditorOption.lightbulb, 'lightbulb', defaults, { From 4ec14ee32603940ee4c525ca6d42d5a2d07706fe Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 9 Jan 2024 17:48:53 +0100 Subject: [PATCH 0176/1897] Fix constant expression (#202092) --- src/vs/base/common/worker/simpleWorker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/common/worker/simpleWorker.ts b/src/vs/base/common/worker/simpleWorker.ts index 924dbf3ad7d..57cc59ee770 100644 --- a/src/vs/base/common/worker/simpleWorker.ts +++ b/src/vs/base/common/worker/simpleWorker.ts @@ -522,7 +522,7 @@ export class SimpleWorkerServer { delete loaderConfig.paths['vs']; } } - if (typeof loaderConfig.trustedTypesPolicy !== undefined) { + if (typeof loaderConfig.trustedTypesPolicy !== 'undefined') { // don't use, it has been destroyed during serialize delete loaderConfig['trustedTypesPolicy']; } From 9c2e81f9f63ccd59cbcd3eb8c5d9a4e496623c8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jan 2024 08:53:01 -0800 Subject: [PATCH 0177/1897] Bump follow-redirects from 1.15.3 to 1.15.4 in /extensions/github-authentication (#202055) Bump follow-redirects in /extensions/github-authentication Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.4. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.4) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- extensions/github-authentication/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/github-authentication/yarn.lock b/extensions/github-authentication/yarn.lock index 6ca3b99e669..a1c68b8d5a6 100644 --- a/extensions/github-authentication/yarn.lock +++ b/extensions/github-authentication/yarn.lock @@ -154,9 +154,9 @@ delayed-stream@~1.0.0: integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= follow-redirects@^1.15.0: - version "1.15.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" - integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== + version "1.15.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" + integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== form-data@^3.0.0: version "3.0.0" From 5b1893b24bebb16f61bec9aa761976b971e58d00 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 9 Jan 2024 22:25:55 +0530 Subject: [PATCH 0178/1897] fix #195150 (#202093) --- src/vs/workbench/contrib/markers/browser/markersView.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index ad7f369ae90..82c27df0f16 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -112,6 +112,7 @@ export class MarkersView extends FilterViewPane implements IMarkersView { private currentHeight = 0; private currentWidth = 0; + private readonly memento: Memento; private readonly panelState: MementoObject; private cachedFilterStats: { total: number; filtered: number } | undefined = undefined; @@ -138,7 +139,8 @@ export class MarkersView extends FilterViewPane implements IMarkersView { @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, ) { - const panelState = new Memento(Markers.MARKERS_VIEW_STORAGE_ID, storageService).getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); + const memento = new Memento(Markers.MARKERS_VIEW_STORAGE_ID, storageService); + const panelState = memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); super({ ...options, filterOptions: { @@ -149,6 +151,7 @@ export class MarkersView extends FilterViewPane implements IMarkersView { history: panelState['filterHistory'] || [] } }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + this.memento = memento; this.panelState = panelState; this.markersModel = this._register(instantiationService.createInstance(MarkersModel)); @@ -894,6 +897,7 @@ export class MarkersView extends FilterViewPane implements IMarkersView { this.panelState['multiline'] = this.markersViewModel.multiline; this.panelState['viewMode'] = this.markersViewModel.viewMode; + this.memento.saveMemento(); super.saveState(); } From fb84f3bcfb9ab61a8b79a695069186e344391d29 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Tue, 9 Jan 2024 09:16:42 -0800 Subject: [PATCH 0179/1897] test: skip flaky Settings editor smoke test (#202097) --- test/smoke/src/areas/preferences/preferences.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/smoke/src/areas/preferences/preferences.test.ts b/test/smoke/src/areas/preferences/preferences.test.ts index ed5726c3f20..14f618ef30a 100644 --- a/test/smoke/src/areas/preferences/preferences.test.ts +++ b/test/smoke/src/areas/preferences/preferences.test.ts @@ -48,7 +48,8 @@ export function setup(logger: Logger) { await app.code.waitForSetValue('.settings-editor .setting-item-contents .setting-item-control input', '4'); }); - it('turns off editor line numbers and verifies the live change', async function () { + // Skipping test due to it being flaky. + it.skip('turns off editor line numbers and verifies the live change', async function () { const app = this.app as Application; await app.workbench.editors.newUntitledFile(); From a6659cb883bbf7a4fdd448991555abb52f284b7f Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 9 Jan 2024 18:41:54 +0100 Subject: [PATCH 0180/1897] API feedback for comment reactor (#202098) Part of #201131 --- src/vs/editor/common/languages.ts | 6 ++++++ src/vs/monaco.d.ts | 5 +++++ src/vs/workbench/api/common/extHostComments.ts | 4 ++-- src/vscode-dts/vscode.proposed.commentReactor.d.ts | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 67d9d60a925..9b3613efc66 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1777,6 +1777,12 @@ export interface CommentingRanges { fileComments: boolean; } +export interface CommentAuthorInformation { + name: string; + iconPath?: UriComponents; + +} + /** * @internal */ diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 226226fc52c..bd130e5d03d 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7726,6 +7726,11 @@ declare namespace monaco.languages { arguments?: any[]; } + export interface CommentAuthorInformation { + name: string; + iconPath?: UriComponents; + } + export interface PendingCommentThread { body: string; range: IRange | undefined; diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 04541a1decb..91e14ee9a07 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -697,7 +697,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo iconPath: reaction.iconPath ? extHostTypeConverter.pathOrURIToURI(reaction.iconPath) : undefined, count: reaction.count, hasReacted: reaction.authorHasReacted, - reactors: reaction.reactors + reactors: ((reaction.reactors && (reaction.reactors.length > 0) && (typeof reaction.reactors[0] !== 'string')) ? (reaction.reactors as languages.CommentAuthorInformation[]).map(reactor => reactor.name) : reaction.reactors) as string[] }; } @@ -707,7 +707,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo count: reaction.count || 0, iconPath: reaction.iconPath ? URI.revive(reaction.iconPath) : '', authorHasReacted: reaction.hasReacted || false, - reactors: reaction.reactors + reactors: reaction.reactors?.map(reactor => ({ name: reactor })) }; } diff --git a/src/vscode-dts/vscode.proposed.commentReactor.d.ts b/src/vscode-dts/vscode.proposed.commentReactor.d.ts index 13cad5f50a2..25a433cd8cf 100644 --- a/src/vscode-dts/vscode.proposed.commentReactor.d.ts +++ b/src/vscode-dts/vscode.proposed.commentReactor.d.ts @@ -8,6 +8,6 @@ declare module 'vscode' { // @alexr00 https://github.com/microsoft/vscode/issues/201131 export interface CommentReaction { - readonly reactors?: readonly string[]; + readonly reactors?: readonly CommentAuthorInformation[]; } } From eae814bef6fa7ab37607c9cf213a58a94a2ef2dc Mon Sep 17 00:00:00 2001 From: Aleksandr Kondrashov <116561995+aramikuto@users.noreply.github.com> Date: Wed, 10 Jan 2024 03:06:59 +0900 Subject: [PATCH 0181/1897] Update IExplorerView interface (#201992) --- src/vs/workbench/contrib/files/browser/files.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index 0d6f0b454cd..b3e8260dde4 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -52,8 +52,8 @@ export const IExplorerService = createDecorator('explorerServi export interface IExplorerView { autoReveal: boolean | 'force' | 'focusNoScroll'; getContext(respectMultiSelection: boolean): ExplorerItem[]; - refresh(recursive: boolean, item?: ExplorerItem): Promise; - selectResource(resource: URI | undefined, reveal?: boolean | string): Promise; + refresh(recursive: boolean, item?: ExplorerItem, cancelEditing?: boolean): Promise; + selectResource(resource: URI | undefined, reveal?: boolean | string, retry?: number): Promise; setTreeInput(): Promise; itemsCopied(tats: ExplorerItem[], cut: boolean, previousCut: ExplorerItem[] | undefined): void; setEditable(stat: ExplorerItem, isEditing: boolean): Promise; From c1bc5415f87d10f18474d842587c7b8e85c07ab2 Mon Sep 17 00:00:00 2001 From: Ronak Jain Date: Wed, 10 Jan 2024 00:32:53 +0530 Subject: [PATCH 0182/1897] Fix tsserver crashing when using custom node path (#201966) * use quotes in spawned process name * Use JSON stringify to handle quotes as well --- .../src/tsServer/serverProcess.electron.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts b/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts index 8b0ec2fb7b7..d03c7ea5df0 100644 --- a/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts +++ b/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts @@ -278,7 +278,7 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory { } const childProcess = execPath ? - child_process.spawn(execPath, [...execArgv, tsServerPath, ...runtimeArgs], { + child_process.spawn(JSON.stringify(execPath), [...execArgv, tsServerPath, ...runtimeArgs], { shell: true, windowsHide: true, cwd: undefined, From a3445c68a08d30734c8b3fa45d09c73723cb393a Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 9 Jan 2024 14:03:19 -0600 Subject: [PATCH 0183/1897] handle alerts in audio cue service, add `List Alerts` command and settings (#201833) * massive refactor --- src/vs/editor/common/standaloneStrings.ts | 8 +- .../editor/contrib/format/browser/format.ts | 10 +- .../contrib/format/browser/formatActions.ts | 6 +- .../standalone/browser/standaloneServices.ts | 17 +- .../accessibility/common/accessibility.ts | 16 -- .../audioCues/browser/audioCueService.ts | 171 ++++++++++++++---- .../notifications/notificationsCenter.ts | 6 +- .../notifications/notificationsCommands.ts | 6 +- .../browser/accessibility.contribution.ts | 5 +- .../browser/accessibilityConfiguration.ts | 113 +++++++++++- .../browser/accessibilityContributions.ts | 11 +- .../browser/accessibleNotificationService.ts | 82 --------- .../browser/editorAccessibilityHelp.ts | 32 +--- .../accessibility/browser/saveAudioCue.ts | 22 +++ .../browser/audioCueDebuggerContribution.ts | 2 +- .../audioCueLineFeatureContribution.ts | 2 +- .../browser/audioCues.contribution.ts | 3 +- .../contrib/audioCues/browser/commands.ts | 50 ++++- .../chat/browser/actions/chatClearActions.ts | 4 +- .../workbench/contrib/debug/browser/repl.ts | 6 +- .../files/test/browser/editorAutoSave.test.ts | 5 +- .../output/browser/output.contribution.ts | 6 +- .../contrib/search/browser/searchView.ts | 7 +- .../terminal/browser/terminalInstance.ts | 2 +- .../terminal/browser/xterm/xtermTerminal.ts | 6 +- .../test/browser/bufferContentTracker.test.ts | 6 +- .../test/browser/workbenchTestServices.ts | 7 +- 27 files changed, 377 insertions(+), 234 deletions(-) delete mode 100644 src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts create mode 100644 src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts diff --git a/src/vs/editor/common/standaloneStrings.ts b/src/vs/editor/common/standaloneStrings.ts index 5c084d90156..112c5ff1caf 100644 --- a/src/vs/editor/common/standaloneStrings.ts +++ b/src/vs/editor/common/standaloneStrings.ts @@ -25,12 +25,8 @@ export namespace AccessibilityHelpNLS { export const tabFocusModeOffMsg = nls.localize("tabFocusModeOffMsg", "Pressing Tab in the current editor will insert the tab character. Toggle this behavior {0}."); export const tabFocusModeOffMsgNoKb = nls.localize("tabFocusModeOffMsgNoKb", "Pressing Tab in the current editor will insert the tab character. The command {0} is currently not triggerable by a keybinding."); export const showAccessibilityHelpAction = nls.localize("showAccessibilityHelpAction", "Show Accessibility Help"); - export const saveAudioCueDisabled = nls.localize("saveAudioCueDisabled", "`audioCues.save` is disabled, so an alert will occur when a file is saved."); - export const saveAudioCueAlways = nls.localize("saveAudioCueAlways", "`audioCues.save` is enabled, so will play whenever a file is saved."); - export const saveAudioCueUserGesture = nls.localize("saveAudioCueUserGesture", "`audioCues.save` is enabled, so will play when a file is saved via user gesture."); - export const formatAudioCueDisabled = nls.localize("formatAudioCueDisabled", "`audioCues.format` is disabled, so an alert will occur when a file is formatted."); - export const formatAudioCueAlways = nls.localize("formatAudioCueAlways", "`audioCues.format` is enabled, so will play whenever a file is formatted."); - export const formatAudioCueUserGesture = nls.localize("formatAudioCueUserGesture", "`audioCues.format` is enabled, so will play when a file is formatted via user gesture."); + export const listAudioCues = nls.localize("listAudioCuesCommand", "Run the command: List Audio Cues for an overview of all audio cues and their current status."); + export const listAlerts = nls.localize("listAlertsCommand", "Run the command: List Alerts for an overview of alerts and their current status."); } export namespace InspectTokensNLS { diff --git a/src/vs/editor/contrib/format/browser/format.ts b/src/vs/editor/contrib/format/browser/format.ts index 402d15f5643..742518daae3 100644 --- a/src/vs/editor/contrib/format/browser/format.ts +++ b/src/vs/editor/contrib/format/browser/format.ts @@ -30,7 +30,7 @@ import { IProgress } from 'vs/platform/progress/common/progress'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; import { ILogService } from 'vs/platform/log/common/log'; -import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; export function getRealAndSyntheticDocumentFormattersOrdered( documentFormattingEditProvider: LanguageFeatureRegistry, @@ -135,7 +135,7 @@ export async function formatDocumentRangesWithProvider( ): Promise { const workerService = accessor.get(IEditorWorkerService); const logService = accessor.get(ILogService); - const accessibleNotificationService = accessor.get(IAccessibleNotificationService); + const audioCueService = accessor.get(IAudioCueService); let model: ITextModel; let cts: CancellationTokenSource; @@ -279,7 +279,7 @@ export async function formatDocumentRangesWithProvider( return null; }); } - accessibleNotificationService.notify(AccessibleNotificationEvent.Format, userGesture); + audioCueService.playAudioCue(AudioCue.format, { userGesture }); return true; } @@ -312,7 +312,7 @@ export async function formatDocumentWithProvider( userGesture?: boolean ): Promise { const workerService = accessor.get(IEditorWorkerService); - const accessibleNotificationService = accessor.get(IAccessibleNotificationService); + const audioCueService = accessor.get(IAudioCueService); let model: ITextModel; let cts: CancellationTokenSource; @@ -373,7 +373,7 @@ export async function formatDocumentWithProvider( return null; }); } - accessibleNotificationService.notify(AccessibleNotificationEvent.Format, userGesture); + audioCueService.playAudioCue(AudioCue.format, { userGesture }); return true; } diff --git a/src/vs/editor/contrib/format/browser/formatActions.ts b/src/vs/editor/contrib/format/browser/formatActions.ts index 1282572b361..a9c4984e4c1 100644 --- a/src/vs/editor/contrib/format/browser/formatActions.ts +++ b/src/vs/editor/contrib/format/browser/formatActions.ts @@ -21,7 +21,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { formatDocumentRangesWithSelectedProvider, formatDocumentWithSelectedProvider, FormattingMode, getOnTypeFormattingEdits } from 'vs/editor/contrib/format/browser/format'; import { FormattingEdit } from 'vs/editor/contrib/format/browser/formattingEdit'; import * as nls from 'vs/nls'; -import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -40,7 +40,7 @@ export class FormatOnType implements IEditorContribution { private readonly _editor: ICodeEditor, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @IEditorWorkerService private readonly _workerService: IEditorWorkerService, - @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService + @IAudioCueService private readonly _audioCueService: IAudioCueService ) { this._disposables.add(_languageFeaturesService.onTypeFormattingEditProvider.onDidChange(this._update, this)); this._disposables.add(_editor.onDidChangeModel(() => this._update())); @@ -143,7 +143,7 @@ export class FormatOnType implements IEditorContribution { return; } if (isNonEmptyArray(edits)) { - this._accessibleNotificationService.notify(AccessibleNotificationEvent.Format, false); + this._audioCueService.playAudioCue(AudioCue.format, { userGesture: false }); FormattingEdit.execute(this._editor, edits, true); } }).finally(() => { diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 61e84831376..1e915d95a25 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -71,7 +71,7 @@ import { StandaloneQuickInputService } from 'vs/editor/standalone/browser/quickI import { StandaloneThemeService } from 'vs/editor/standalone/browser/standaloneThemeService'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneTheme'; import { AccessibilityService } from 'vs/platform/accessibility/browser/accessibilityService'; -import { AccessibleNotificationEvent, IAccessibilityService, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IMenuService } from 'vs/platform/actions/common/actions'; import { MenuService } from 'vs/platform/actions/common/menuService'; import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService'; @@ -1059,7 +1059,11 @@ class StandaloneAudioService implements IAudioCueService { async playAudioCues(cues: AudioCue[]): Promise { } - isEnabled(cue: AudioCue): boolean { + isCueEnabled(cue: AudioCue): boolean { + return false; + } + + isAlertEnabled(cue: AudioCue): boolean { return false; } @@ -1074,14 +1078,6 @@ class StandaloneAudioService implements IAudioCueService { } } -class StandaloneAccessibleNotificationService implements IAccessibleNotificationService { - _serviceBrand: undefined; - - notify(event: AccessibleNotificationEvent, userGesture?: boolean | undefined): void { - // NOOP - } -} - export interface IEditorOverrideServices { [index: string]: any; } @@ -1120,7 +1116,6 @@ registerSingleton(IClipboardService, BrowserClipboardService, InstantiationType. registerSingleton(IContextMenuService, StandaloneContextMenuService, InstantiationType.Eager); registerSingleton(IMenuService, MenuService, InstantiationType.Eager); registerSingleton(IAudioCueService, StandaloneAudioService, InstantiationType.Eager); -registerSingleton(IAccessibleNotificationService, StandaloneAccessibleNotificationService, InstantiationType.Eager); /** * We don't want to eagerly instantiate services because embedders get a one time chance diff --git a/src/vs/platform/accessibility/common/accessibility.ts b/src/vs/platform/accessibility/common/accessibility.ts index 55b6f1ba0c1..b370b1e0267 100644 --- a/src/vs/platform/accessibility/common/accessibility.ts +++ b/src/vs/platform/accessibility/common/accessibility.ts @@ -47,19 +47,3 @@ export function isAccessibilityInformation(obj: any): obj is IAccessibilityInfor && (typeof obj.role === 'undefined' || typeof obj.role === 'string'); } -export const IAccessibleNotificationService = createDecorator('accessibleNotificationService'); -/** - * Manages whether an audio cue or an aria alert will be used - * in response to actions taken around the workbench. - * Targets screen reader and braille users. - */ -export interface IAccessibleNotificationService { - readonly _serviceBrand: undefined; - notify(event: AccessibleNotificationEvent, userGesture?: boolean): void; -} - -export const enum AccessibleNotificationEvent { - Clear = 'clear', - Save = 'save', - Format = 'format' -} diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index a97297094c1..62fd6b5042d 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -19,7 +19,8 @@ export interface IAudioCueService { readonly _serviceBrand: undefined; playAudioCue(cue: AudioCue, options?: IAudioCueOptions): Promise; playAudioCues(cues: (AudioCue | { cue: AudioCue; source: string })[]): Promise; - isEnabled(cue: AudioCue): boolean; + isCueEnabled(cue: AudioCue): boolean; + isAlertEnabled(cue: AudioCue): boolean; onEnabledChanged(cue: AudioCue): Event; playSound(cue: Sound, allowManyInParallel?: boolean): Promise; @@ -29,6 +30,12 @@ export interface IAudioCueService { export interface IAudioCueOptions { allowManyInParallel?: boolean; source?: string; + /** + * For actions like save or format, depending on the + * configured value, we will only + * play the sound if the user triggered the action. + */ + userGesture?: boolean; } export class AudioCueService extends Disposable implements IAudioCueService { @@ -49,7 +56,12 @@ export class AudioCueService extends Disposable implements IAudioCueService { } public async playAudioCue(cue: AudioCue, options: IAudioCueOptions = {}): Promise { - if (this.isEnabled(cue)) { + const alertMessage = cue.alertMessage; + if (this.isAlertEnabled(cue, options.userGesture) && alertMessage) { + this.accessibilityService.alert(alertMessage); + } + + if (this.isCueEnabled(cue, options.userGesture)) { this.sendAudioCueTelemetry(cue, options.source); await this.playSound(cue.sound.getSound(), options.allowManyInParallel); } @@ -59,12 +71,19 @@ export class AudioCueService extends Disposable implements IAudioCueService { for (const cue of cues) { this.sendAudioCueTelemetry('cue' in cue ? cue.cue : cue, 'source' in cue ? cue.source : undefined); } + const cueArray = cues.map(c => 'cue' in c ? c.cue : c); + const alerts = cueArray.filter(cue => this.isAlertEnabled(cue)).map(c => c.alertMessage); + if (alerts.length) { + this.accessibilityService.alert(alerts.join(', ')); + } // Some audio cues might reuse sounds. Don't play the same sound twice. - const sounds = new Set(cues.map(c => 'cue' in c ? c.cue : c).filter(cue => this.isEnabled(cue)).map(cue => cue.sound.getSound())); + const sounds = new Set(cueArray.filter(cue => this.isCueEnabled(cue)).map(cue => cue.sound.getSound())); await Promise.all(Array.from(sounds).map(sound => this.playSound(sound, true))); + } + private sendAudioCueTelemetry(cue: AudioCue, source: string | undefined): void { const isScreenReaderOptimized = this.accessibilityService.isScreenReaderOptimized(); const key = cue.name + (source ? `::${source}` : '') + (isScreenReaderOptimized ? '{screenReaderOptimized}' : ''); @@ -152,15 +171,15 @@ export class AudioCueService extends Disposable implements IAudioCueService { Event.filter(this.configurationService.onDidChangeConfiguration, (e) => e.affectsConfiguration('audioCues.enabled') ), - () => /** @description config: audioCues.enabled */ this.configurationService.getValue<'on' | 'off' | 'auto'>('audioCues.enabled') + () => /** @description config: audioCues.enabled */ this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>('audioCues.enabled') ); - private readonly isEnabledCache = new Cache((cue: AudioCue) => { + private readonly isCueEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(cue.settingsKey) + e.affectsConfiguration(event.cue.settingsKey) ), - () => this.configurationService.getValue<'on' | 'off' | 'auto'>(cue.settingsKey) + () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.cue.settingsKey) ); return derived(reader => { /** @description audio cue enabled */ @@ -170,6 +189,8 @@ export class AudioCueService extends Disposable implements IAudioCueService { (setting === 'auto' && this.screenReaderAttached.read(reader)) ) { return true; + } else if (setting === 'always' || setting === 'userGesture' && event.userGesture) { + return true; } const obsoleteSetting = this.obsoleteAudioCuesEnabled.read(reader); @@ -182,17 +203,41 @@ export class AudioCueService extends Disposable implements IAudioCueService { return false; }); - }); + }, JSON.stringify); - public isEnabled(cue: AudioCue): boolean { - return this.isEnabledCache.get(cue).get(); + private readonly isAlertEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { + const settingObservable = observableFromEvent( + Event.filter(this.configurationService.onDidChangeConfiguration, (e) => + e.affectsConfiguration(event.cue.settingsKey) + ), + () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.alertSettingsKey) : false + ); + return derived(reader => { + /** @description audio cue enabled */ + const setting = settingObservable.read(reader); + if ( + !this.screenReaderAttached.read(reader) + ) { + return false; + } + return setting === true || setting === 'always' || setting === 'userGesture' && event.userGesture; + }); + }, JSON.stringify); + + public isAlertEnabled(cue: AudioCue, userGesture?: boolean): boolean { + return this.isAlertEnabledCache.get({ cue, userGesture }).get() ?? false; + } + + public isCueEnabled(cue: AudioCue, userGesture?: boolean): boolean { + return this.isCueEnabledCache.get({ cue, userGesture }).get() ?? false; } public onEnabledChanged(cue: AudioCue): Event { - return Event.fromObservableLight(this.isEnabledCache.get(cue)); + return Event.fromObservableLight(this.isCueEnabledCache.get({ cue })); } } + /** * Play the given audio url. * @volume value between 0 and 1 @@ -216,8 +261,8 @@ function playAudio(url: string, volume: number): Promise { } class Cache { - private readonly map = new Map(); - constructor(private readonly getValue: (value: TArg) => TValue) { + private readonly map = new Map(); + constructor(private readonly getValue: (value: TArg) => TValue, private readonly getKey: (value: TArg) => unknown) { } public get(arg: TArg): TValue { @@ -226,7 +271,8 @@ class Cache { } const value = this.getValue(arg); - this.map.set(arg, value); + const key = this.getKey(arg); + this.map.set(key, value); return value; } } @@ -279,6 +325,29 @@ export class SoundSource { } } +export const enum AccessibilityAlertSettingId { + Save = 'accessibility.alert.save', + Format = 'accessibility.alert.format', + Clear = 'accessibility.alert.clear', + Breakpoint = 'accessibility.alert.breakpoint', + Error = 'accessibility.alert.error', + Warning = 'accessibility.alert.warning', + FoldedArea = 'accessibility.alert.foldedArea', + TerminalQuickFix = 'accessibility.alert.terminalQuickFix', + TerminalBell = 'accessibility.alert.terminalBell', + TerminalCommandFailed = 'accessibility.alert.terminalCommandFailed', + TaskCompleted = 'accessibility.alert.taskCompleted', + TaskFailed = 'accessibility.alert.taskFailed', + ChatRequestSent = 'accessibility.alert.chatRequestSent', + NotebookCellCompleted = 'accessibility.alert.notebookCellCompleted', + NotebookCellFailed = 'accessibility.alert.notebookCellFailed', + OnDebugBreak = 'accessibility.alert.onDebugBreak', + NoInlayHints = 'accessibility.alert.noInlayHints', + LineHasBreakpoint = 'accessibility.alert.lineHasBreakpoint', + ChatResponsePending = 'accessibility.alert.chatResponsePending' +} + + export class AudioCue { private static _audioCues = new Set(); private static register(options: { @@ -291,9 +360,11 @@ export class AudioCue { randomOneOf: Sound[]; }; settingsKey: string; + alertSettingsKey?: AccessibilityAlertSettingId; + alertMessage?: string; }): AudioCue { const soundSource = new SoundSource('randomOneOf' in options.sound ? options.sound.randomOneOf : [options.sound]); - const audioCue = new AudioCue(soundSource, options.name, options.settingsKey); + const audioCue = new AudioCue(soundSource, options.name, options.settingsKey, options.alertSettingsKey, options.alertMessage); AudioCue._audioCues.add(audioCue); return audioCue; } @@ -306,21 +377,29 @@ export class AudioCue { name: localize('audioCues.lineHasError.name', 'Error on Line'), sound: Sound.error, settingsKey: 'audioCues.lineHasError', + alertSettingsKey: AccessibilityAlertSettingId.Error, + alertMessage: localize('audioCues.lineHasError.alertMessage', 'Error') }); public static readonly warning = AudioCue.register({ name: localize('audioCues.lineHasWarning.name', 'Warning on Line'), sound: Sound.warning, settingsKey: 'audioCues.lineHasWarning', + alertSettingsKey: AccessibilityAlertSettingId.Warning, + alertMessage: localize('audioCues.lineHasWarning.alertMessage', 'Warning') }); public static readonly foldedArea = AudioCue.register({ name: localize('audioCues.lineHasFoldedArea.name', 'Folded Area on Line'), sound: Sound.foldedArea, settingsKey: 'audioCues.lineHasFoldedArea', + alertSettingsKey: AccessibilityAlertSettingId.FoldedArea, + alertMessage: localize('audioCues.lineHasFoldedArea.alertMessage', 'Folded') }); public static readonly break = AudioCue.register({ name: localize('audioCues.lineHasBreakpoint.name', 'Breakpoint on Line'), sound: Sound.break, settingsKey: 'audioCues.lineHasBreakpoint', + alertSettingsKey: AccessibilityAlertSettingId.Breakpoint, + alertMessage: localize('audioCues.lineHasBreakpoint.alertMessage', 'Breakpoint') }); public static readonly inlineSuggestion = AudioCue.register({ name: localize('audioCues.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), @@ -332,78 +411,98 @@ export class AudioCue { name: localize('audioCues.terminalQuickFix.name', 'Terminal Quick Fix'), sound: Sound.quickFixes, settingsKey: 'audioCues.terminalQuickFix', + alertSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, + alertMessage: localize('audioCues.terminalQuickFix.alertMessage', 'Quick Fix') }); public static readonly onDebugBreak = AudioCue.register({ name: localize('audioCues.onDebugBreak.name', 'Debugger Stopped on Breakpoint'), sound: Sound.break, settingsKey: 'audioCues.onDebugBreak', + alertSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, + alertMessage: localize('audioCues.onDebugBreak.alertMessage', 'Breakpoint') }); public static readonly noInlayHints = AudioCue.register({ name: localize('audioCues.noInlayHints', 'No Inlay Hints on Line'), sound: Sound.error, - settingsKey: 'audioCues.noInlayHints' + settingsKey: 'audioCues.noInlayHints', + alertSettingsKey: AccessibilityAlertSettingId.NoInlayHints, + alertMessage: localize('audioCues.noInlayHints.alertMessage', 'No Inlay Hints') }); public static readonly taskCompleted = AudioCue.register({ name: localize('audioCues.taskCompleted', 'Task Completed'), sound: Sound.taskCompleted, - settingsKey: 'audioCues.taskCompleted' + settingsKey: 'audioCues.taskCompleted', + alertSettingsKey: AccessibilityAlertSettingId.TaskCompleted, + alertMessage: localize('audioCues.taskCompleted.alertMessage', 'Task Completed') }); public static readonly taskFailed = AudioCue.register({ name: localize('audioCues.taskFailed', 'Task Failed'), sound: Sound.taskFailed, - settingsKey: 'audioCues.taskFailed' + settingsKey: 'audioCues.taskFailed', + alertSettingsKey: AccessibilityAlertSettingId.TaskFailed, + alertMessage: localize('audioCues.taskFailed.alertMessage', 'Task Failed') }); public static readonly terminalCommandFailed = AudioCue.register({ name: localize('audioCues.terminalCommandFailed', 'Terminal Command Failed'), sound: Sound.error, - settingsKey: 'audioCues.terminalCommandFailed' + settingsKey: 'audioCues.terminalCommandFailed', + alertSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, + alertMessage: localize('audioCues.terminalCommandFailed.alertMessage', 'Command Failed') }); public static readonly terminalBell = AudioCue.register({ name: localize('audioCues.terminalBell', 'Terminal Bell'), sound: Sound.terminalBell, - settingsKey: 'audioCues.terminalBell' + settingsKey: 'audioCues.terminalBell', + alertSettingsKey: AccessibilityAlertSettingId.TerminalBell, + alertMessage: localize('audioCues.terminalBell.alertMessage', 'Terminal Bell') }); public static readonly notebookCellCompleted = AudioCue.register({ name: localize('audioCues.notebookCellCompleted', 'Notebook Cell Completed'), sound: Sound.taskCompleted, - settingsKey: 'audioCues.notebookCellCompleted' + settingsKey: 'audioCues.notebookCellCompleted', + alertSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, + alertMessage: localize('audioCues.notebookCellCompleted.alertMessage', 'Notebook Cell Completed') }); public static readonly notebookCellFailed = AudioCue.register({ name: localize('audioCues.notebookCellFailed', 'Notebook Cell Failed'), sound: Sound.taskFailed, - settingsKey: 'audioCues.notebookCellFailed' + settingsKey: 'audioCues.notebookCellFailed', + alertSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, + alertMessage: localize('audioCues.notebookCellFailed.alertMessage', 'Notebook Cell Failed') }); public static readonly diffLineInserted = AudioCue.register({ name: localize('audioCues.diffLineInserted', 'Diff Line Inserted'), sound: Sound.diffLineInserted, - settingsKey: 'audioCues.diffLineInserted' + settingsKey: 'audioCues.diffLineInserted', }); public static readonly diffLineDeleted = AudioCue.register({ name: localize('audioCues.diffLineDeleted', 'Diff Line Deleted'), sound: Sound.diffLineDeleted, - settingsKey: 'audioCues.diffLineDeleted' + settingsKey: 'audioCues.diffLineDeleted', }); public static readonly diffLineModified = AudioCue.register({ name: localize('audioCues.diffLineModified', 'Diff Line Modified'), sound: Sound.diffLineModified, - settingsKey: 'audioCues.diffLineModified' + settingsKey: 'audioCues.diffLineModified', }); public static readonly chatRequestSent = AudioCue.register({ name: localize('audioCues.chatRequestSent', 'Chat Request Sent'), sound: Sound.chatRequestSent, - settingsKey: 'audioCues.chatRequestSent' + settingsKey: 'audioCues.chatRequestSent', + alertSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, + alertMessage: localize('audioCues.chatRequestSent.alertMessage', 'Chat Request Sent') }); public static readonly chatResponseReceived = AudioCue.register({ @@ -416,36 +515,46 @@ export class AudioCue { Sound.chatResponseReceived3, Sound.chatResponseReceived4 ] - } + }, }); public static readonly chatResponsePending = AudioCue.register({ name: localize('audioCues.chatResponsePending', 'Chat Response Pending'), sound: Sound.chatResponsePending, - settingsKey: 'audioCues.chatResponsePending' + settingsKey: 'audioCues.chatResponsePending', + alertSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, + alertMessage: localize('audioCues.chatResponsePending.alertMessage', 'Chat Response Pending') }); public static readonly clear = AudioCue.register({ name: localize('audioCues.clear', 'Clear'), sound: Sound.clear, - settingsKey: 'audioCues.clear' + settingsKey: 'audioCues.clear', + alertSettingsKey: AccessibilityAlertSettingId.Clear, + alertMessage: localize('audioCues.clear.alertMessage', 'Clear') }); public static readonly save = AudioCue.register({ name: localize('audioCues.save', 'Save'), sound: Sound.save, - settingsKey: 'audioCues.save' + settingsKey: 'audioCues.save', + alertSettingsKey: AccessibilityAlertSettingId.Save, + alertMessage: localize('audioCues.save.alertMessage', 'Save') }); public static readonly format = AudioCue.register({ name: localize('audioCues.format', 'Format'), sound: Sound.format, - settingsKey: 'audioCues.format' + settingsKey: 'audioCues.format', + alertSettingsKey: AccessibilityAlertSettingId.Format, + alertMessage: localize('audioCues.format.alertMessage', 'Format') }); private constructor( public readonly sound: SoundSource, public readonly name: string, public readonly settingsKey: string, + public readonly alertSettingsKey?: string, + public readonly alertMessage?: string ) { } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index 568e4fb5ac3..8a88a9e5f4a 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -25,10 +25,10 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { assertAllDefined, assertIsDefined } from 'vs/base/common/types'; import { NotificationsCenterVisibleContext } from 'vs/workbench/common/contextkeys'; import { INotificationService, NotificationsFilter } from 'vs/platform/notification/common/notification'; -import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { mainWindow } from 'vs/base/browser/window'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; export class NotificationsCenter extends Themable implements INotificationsCenterController { @@ -59,7 +59,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IKeybindingService private readonly keybindingService: IKeybindingService, @INotificationService private readonly notificationService: INotificationService, - @IAccessibleNotificationService private readonly accessibleNotificationService: IAccessibleNotificationService, + @IAudioCueService private readonly audioCueService: IAudioCueService, @IContextMenuService private readonly contextMenuService: IContextMenuService ) { super(themeService); @@ -383,7 +383,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente if (!notification.hasProgress) { notification.close(); } - this.accessibleNotificationService.notify(AccessibleNotificationEvent.Clear); + this.audioCueService.playAudioCue(AudioCue.clear); } } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index 0816033b762..5e8135b4fcc 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -19,9 +19,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ActionRunner, IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { hash } from 'vs/base/common/hash'; import { firstOrDefault } from 'vs/base/common/arrays'; -import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; // Center export const SHOW_NOTIFICATIONS_CENTER = 'notifications.showList'; @@ -142,11 +142,11 @@ export function registerNotificationCommands(center: INotificationsCenterControl primary: KeyMod.CtrlCmd | KeyCode.Backspace }, handler: (accessor, args?) => { - const accessibleNotificationService = accessor.get(IAccessibleNotificationService); + const audioCueService = accessor.get(IAudioCueService); const notification = getNotificationFromContext(accessor.get(IListService), args); if (notification && !notification.hasProgress) { notification.close(); - accessibleNotificationService.notify(AccessibleNotificationEvent.Clear); + audioCueService.playAudioCue(AudioCue.clear); } } }); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index c08a26dccc9..5df70bb0761 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -13,13 +13,11 @@ import { UnfocusedViewDimmingContribution } from 'vs/workbench/contrib/accessibi import { HoverAccessibleViewContribution, InlineCompletionsAccessibleViewContribution, NotificationAccessibleViewContribution } from 'vs/workbench/contrib/accessibility/browser/accessibilityContributions'; import { AccessibilityStatus } from 'vs/workbench/contrib/accessibility/browser/accessibilityStatus'; import { CommentsAccessibilityHelpContribution } from 'vs/workbench/contrib/comments/browser/comments.contribution'; -import { IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; -import { AccessibleNotificationService } from 'vs/workbench/contrib/accessibility/browser/accessibleNotificationService'; import { EditorAccessibilityHelpContribution } from 'vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp'; +import { SaveAudioCueContribution } from 'vs/workbench/contrib/accessibility/browser/saveAudioCue'; registerAccessibilityConfiguration(); registerSingleton(IAccessibleViewService, AccessibleViewService, InstantiationType.Delayed); -registerSingleton(IAccessibleNotificationService, AccessibleNotificationService, InstantiationType.Delayed); const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(EditorAccessibilityHelpContribution, LifecyclePhase.Eventually); @@ -31,3 +29,4 @@ workbenchContributionsRegistry.registerWorkbenchContribution(HoverAccessibleView workbenchContributionsRegistry.registerWorkbenchContribution(NotificationAccessibleViewContribution, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(InlineCompletionsAccessibleViewContribution, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(AccessibilityStatus, LifecyclePhase.Ready); +workbenchContributionsRegistry.registerWorkbenchContribution(SaveAudioCueContribution, LifecyclePhase.Ready); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index e75de941aa5..16b03b216cd 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -8,6 +8,7 @@ import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegis import { Registry } from 'vs/platform/registry/common/platform'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; +import { AccessibilityAlertSettingId } from 'vs/platform/audioCues/browser/audioCueService'; export const accessibilityHelpIsShown = new RawContextKey('accessibilityHelpIsShown', false, true); export const accessibleViewIsShown = new RawContextKey('accessibleViewIsShown', false, true); @@ -54,11 +55,6 @@ export const enum AccessibilityVerbositySettingId { Comments = 'accessibility.verbosity.comments' } -export const enum AccessibilityAlertSettingId { - Save = 'accessibility.alert.save', - Format = 'accessibility.alert.format' -} - export const enum AccessibleViewProviderId { Terminal = 'terminal', TerminalHelp = 'terminal-help', @@ -131,8 +127,7 @@ const configuration: IConfigurationNode = { ...baseProperty }, [AccessibilityAlertSettingId.Save]: { - 'markdownDescription': localize('alert.save', "When in screen reader mode, alerts when a file is saved. Note that this will be ignored when {0} is enabled.", '`#audioCues.save#`'), - 'type': 'string', + 'markdownDescription': localize('alert.save', "Alerts when a file is saved. Also see {0}.", '`#audioCues.save#`'), 'enum': ['userGesture', 'always', 'never'], 'default': 'always', 'enumDescriptions': [ @@ -142,8 +137,14 @@ const configuration: IConfigurationNode = { ], tags: ['accessibility'] }, + [AccessibilityAlertSettingId.Clear]: { + 'markdownDescription': localize('alert.clear', "Alerts when a feature is cleared (for example, the terminal, Debug Console, or Output channel). Also see {0}.", '`#audioCues.clear#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, [AccessibilityAlertSettingId.Format]: { - 'markdownDescription': localize('alert.format', "When in screen reader mode, alerts when a file or notebook cell is formatted. Note that this will be ignored when {0} is enabled.", '`#audioCues.format#`'), + 'markdownDescription': localize('alert.format', "Alerts when a file or notebook cell is formatted. Also see {0}.", '`#audioCues.format#`'), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'always', @@ -154,6 +155,102 @@ const configuration: IConfigurationNode = { ], tags: ['accessibility'] }, + [AccessibilityAlertSettingId.Breakpoint]: { + 'markdownDescription': localize('alert.breakpoint', "Alerts when the active line has a breakpoint. Also see {0}.", '`#audioCues.breakpoint#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, + [AccessibilityAlertSettingId.Error]: { + 'markdownDescription': localize('alert.error', "Alerts when the active line has an error. Also see {0}.", '`#audioCues.error#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, + [AccessibilityAlertSettingId.Warning]: { + 'markdownDescription': localize('alert.warning', "Alerts when the active line has a warning. Also see {0}.", '`#audioCues.warning#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, + [AccessibilityAlertSettingId.FoldedArea]: { + 'markdownDescription': localize('alert.foldedArea', "Alerts when the active line has a folded area that can be unfolded. Also see {0}.", '`#audioCues.foldedArea#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, + [AccessibilityAlertSettingId.TerminalQuickFix]: { + 'markdownDescription': localize('alert.terminalQuickFix', "Alerts when there is an available terminal quick fix. Also see {0}.", '`#audioCues.terminalQuickFix#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, + [AccessibilityAlertSettingId.TerminalBell]: { + 'markdownDescription': localize('alert.terminalBell', "Alerts when the terminal bell is activated. Also see {0}.", '`#audioCues.terminalBell#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, + [AccessibilityAlertSettingId.TerminalCommandFailed]: { + 'markdownDescription': localize('alert.terminalCommandFailed', "Alerts when a terminal command fails (non-zero exit code). Also see {0}.", '`#audioCues.terminalCommandFailed#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, + [AccessibilityAlertSettingId.TaskFailed]: { + 'markdownDescription': localize('alert.taskFailed', "Alerts when a task fails (non-zero exit code). Also see {0}.", '`#audioCues.taskFailed#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, + [AccessibilityAlertSettingId.TaskCompleted]: { + 'markdownDescription': localize('alert.taskCompleted', "Alerts when a task completes successfully (zero exit code). Also see {0}.", '`#audioCues.taskCompleted#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, + [AccessibilityAlertSettingId.ChatRequestSent]: { + 'markdownDescription': localize('alert.chatRequestSent', "Alerts when a chat request is sent. Also see {0}.", '`#audioCues.chatRequestSent#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, + [AccessibilityAlertSettingId.ChatResponsePending]: { + 'markdownDescription': localize('alert.chatResponsePending', "Alerts when a chat response is pending. Also see {0}.", '`#audioCues.chatResponsePending#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, + [AccessibilityAlertSettingId.NoInlayHints]: { + 'markdownDescription': localize('alert.noInlayHints', "Alerts when there are no inlay hints. Also see {0}.", '`#audioCues.noInlayHints#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, + [AccessibilityAlertSettingId.LineHasBreakpoint]: { + 'markdownDescription': localize('alert.lineHasBreakpoint', "Alerts when on a line with a breakpoint. Also see {0}.", '`#audioCues.lineHasBreakpoint#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, + [AccessibilityAlertSettingId.NotebookCellCompleted]: { + 'markdownDescription': localize('alert.notebookCellCompleted', "Alerts when a notebook cell completes successfully. Also see {0}.", '`#audioCues.notebookCellCompleted#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, + [AccessibilityAlertSettingId.NotebookCellFailed]: { + 'markdownDescription': localize('alert.notebookCellFailed', "Alerts when a notebook cell fails. Also see {0}.", '`#audioCues.notebookCellFailed#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, + [AccessibilityAlertSettingId.OnDebugBreak]: { + 'markdownDescription': localize('alert.onDebugBreak', "Alerts when the debugger breaks. Also see {0}.", '`#audioCues.onDebugBreak#`'), + 'type': 'boolean', + 'default': true, + tags: ['accessibility'] + }, [AccessibilityVoiceSettingId.SpeechTimeout]: { 'markdownDescription': localize('voice.speechTimeout', "The duration in milliseconds that voice speech recognition remains active after you stop speaking. For example in a chat session, the transcribed text is submitted automatically after the timeout is met. Set to `0` to disable this feature."), 'type': 'number', diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts index 73833a9fad3..815ac45fe1d 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts @@ -32,7 +32,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; export function descriptionForCommand(commandId: string, msg: string, noKbMsg: string, keybindingService: IKeybindingService): string { const kb = keybindingService.lookupKeybinding(commandId); @@ -104,7 +104,7 @@ export class NotificationAccessibleViewContribution extends Disposable { const accessibleViewService = accessor.get(IAccessibleViewService); const listService = accessor.get(IListService); const commandService = accessor.get(ICommandService); - const accessibleNotificationService = accessor.get(IAccessibleNotificationService); + const audioCueService = accessor.get(IAudioCueService); function renderAccessibleView(): boolean { const notification = getNotificationFromContext(listService); @@ -165,7 +165,7 @@ export class NotificationAccessibleViewContribution extends Disposable { }, verbositySettingKey: AccessibilityVerbositySettingId.Notification, options: { type: AccessibleViewType.View }, - actions: getActionsFromNotification(notification, accessibleNotificationService) + actions: getActionsFromNotification(notification, audioCueService) }); return true; } @@ -174,7 +174,7 @@ export class NotificationAccessibleViewContribution extends Disposable { } } -function getActionsFromNotification(notification: INotificationViewItem, accessibleNotificationService: IAccessibleNotificationService): IAction[] | undefined { +function getActionsFromNotification(notification: INotificationViewItem, audioCueService: IAudioCueService): IAction[] | undefined { let actions = undefined; if (notification.actions) { actions = []; @@ -203,7 +203,7 @@ function getActionsFromNotification(notification: INotificationViewItem, accessi actions.push({ id: 'clearNotification', label: localize('clearNotification', "Clear Notification"), tooltip: localize('clearNotification', "Clear Notification"), run: () => { notification.close(); - accessibleNotificationService.notify(AccessibleNotificationEvent.Clear); + audioCueService.playAudioCue(AudioCue.clear); }, enabled: true, class: ThemeIcon.asClassName(Codicon.clearAll) }); } @@ -272,3 +272,4 @@ export class InlineCompletionsAccessibleViewContribution extends Disposable { })); } } + diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts deleted file mode 100644 index 5afd59cd2e3..00000000000 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts +++ /dev/null @@ -1,82 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable } from 'vs/base/common/lifecycle'; -import { localize } from 'vs/nls'; -import { AccessibleNotificationEvent, IAccessibilityService, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ILogService } from 'vs/platform/log/common/log'; -import { SaveReason } from 'vs/workbench/common/editor'; -import { AccessibilityAlertSettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; - -export class AccessibleNotificationService extends Disposable implements IAccessibleNotificationService { - declare readonly _serviceBrand: undefined; - private _events: Map = new Map(); - constructor( - @IAudioCueService private readonly _audioCueService: IAudioCueService, - @IConfigurationService private readonly _configurationService: IConfigurationService, - @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, - @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, - @ILogService private readonly _logService: ILogService) { - super(); - this._events.set(AccessibleNotificationEvent.Clear, { audioCue: AudioCue.clear, alertMessage: localize('cleared', "Cleared") }); - this._events.set(AccessibleNotificationEvent.Save, { audioCue: AudioCue.save, alertMessage: localize('saved', "Saved"), alertSetting: AccessibilityAlertSettingId.Save }); - this._events.set(AccessibleNotificationEvent.Format, { audioCue: AudioCue.format, alertMessage: localize('formatted', "Formatted"), alertSetting: AccessibilityAlertSettingId.Format }); - - this._register(this._workingCopyService.onDidSave((e) => this._notify(AccessibleNotificationEvent.Save, e.reason === SaveReason.EXPLICIT))); - } - - notify(event: AccessibleNotificationEvent, userGesture?: boolean): void { - if (event === AccessibleNotificationEvent.Format) { - return this._notify(event, userGesture); - } - const { audioCue, alertMessage } = this._events.get(event)!; - const audioCueValue = this._configurationService.getValue(audioCue.settingsKey); - if (audioCueValue === 'on' || audioCueValue === 'auto' && this._accessibilityService.isScreenReaderOptimized()) { - this._logService.debug('AccessibleNotificationService playing sound: ', audioCue.name); - this._audioCueService.playAudioCue(audioCue); - } else { - this._logService.debug('AccessibleNotificationService alerting: ', alertMessage); - this._accessibilityService.alert(alertMessage); - } - } - - private _notify(event: AccessibleNotificationEvent, userGesture?: boolean): void { - const { audioCue, alertMessage, alertSetting } = this._events.get(event)!; - if (!alertSetting) { - return; - } - const audioCueSetting: NotificationSetting = this._configurationService.getValue(audioCue.settingsKey); - if (this._shouldNotify(audioCueSetting, userGesture)) { - this._logService.debug('AccessibleNotificationService playing sound: ', audioCue.name); - // Play sound bypasses the usual audio cue checks IE screen reader optimized, auto, etc. - this._audioCueService.playSound(audioCue.sound.getSound(), true); - return; - } - if (audioCueSetting !== 'never') { - // Never do both sound and alert - return; - } - const alertSettingValue: NotificationSetting = this._configurationService.getValue(alertSetting); - if (this._shouldNotify(alertSettingValue, userGesture)) { - this._logService.debug('AccessibleNotificationService alerting: ', alertMessage); - this._accessibilityService.alert(alertMessage); - } - } - - private _shouldNotify(settingValue: NotificationSetting, userGesture?: boolean): boolean { - return settingValue === 'always' || settingValue === 'userGesture' && userGesture === true; - } -} -type NotificationSetting = 'never' | 'always' | 'userGesture'; - -export class TestAccessibleNotificationService extends Disposable implements IAccessibleNotificationService { - - declare readonly _serviceBrand: undefined; - - notify(event: AccessibleNotificationEvent, userGesture?: boolean): void { } -} diff --git a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts index 1bd79ceb9a3..d4bde61062f 100644 --- a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts @@ -10,9 +10,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { AccessibilityHelpNLS } from 'vs/editor/common/standaloneStrings'; import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode'; -import { AudioCue } from 'vs/platform/audioCues/browser/audioCueService'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -54,8 +52,7 @@ class EditorAccessibilityHelpProvider implements IAccessibleContentProvider { constructor( private readonly _editor: ICodeEditor, @IKeybindingService private readonly _keybindingService: IKeybindingService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IConfigurationService private readonly _configurationService: IConfigurationService + @IContextKeyService private readonly _contextKeyService: IContextKeyService ) { } @@ -76,30 +73,9 @@ class EditorAccessibilityHelpProvider implements IAccessibleContentProvider { content.push(AccessibilityHelpNLS.editableEditor); } } - const saveAudioCue = this._configurationService.getValue(AudioCue.save.settingsKey); - switch (saveAudioCue) { - case 'never': - content.push(AccessibilityHelpNLS.saveAudioCueDisabled); - break; - case 'always': - content.push(AccessibilityHelpNLS.saveAudioCueAlways); - break; - case 'userGesture': - content.push(AccessibilityHelpNLS.saveAudioCueUserGesture); - break; - } - const formatAudioCue = this._configurationService.getValue(AudioCue.format.settingsKey); - switch (formatAudioCue) { - case 'never': - content.push(AccessibilityHelpNLS.formatAudioCueDisabled); - break; - case 'always': - content.push(AccessibilityHelpNLS.formatAudioCueAlways); - break; - case 'userGesture': - content.push(AccessibilityHelpNLS.formatAudioCueUserGesture); - break; - } + + content.push(AccessibilityHelpNLS.listAudioCues); + content.push(AccessibilityHelpNLS.listAlerts); const commentCommandInfo = getCommentCommandInfo(this._keybindingService, this._contextKeyService, this._editor); if (commentCommandInfo) { diff --git a/src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts b/src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts new file mode 100644 index 00000000000..45a0c582aa9 --- /dev/null +++ b/src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { SaveReason } from 'vs/workbench/common/editor'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; + +export class SaveAudioCueContribution extends Disposable implements IWorkbenchContribution { + constructor( + @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, + ) { + super(); + this._register(this._workingCopyService.onDidSave((e) => { + this._audioCueService.playAudioCue(AudioCue.save, { userGesture: e.reason === SaveReason.EXPLICIT }); + })); + } +} diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts index e5051feb3a4..6150308ccfa 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts @@ -21,7 +21,7 @@ export class AudioCueLineDebuggerContribution const isEnabled = observableFromEvent( audioCueService.onEnabledChanged(AudioCue.onDebugBreak), - () => audioCueService.isEnabled(AudioCue.onDebugBreak) + () => audioCueService.isCueEnabled(AudioCue.onDebugBreak) ); this._register(autorunWithStore((reader, store) => { /** @description subscribe to debug sessions */ diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts index c9421fd053d..06a9a8645ed 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts @@ -34,7 +34,7 @@ export class AudioCueLineFeatureContribution private readonly isEnabledCache = new CachedFunction>((cue) => observableFromEvent( this.audioCueService.onEnabledChanged(cue), - () => this.audioCueService.isEnabled(cue) + () => this.audioCueService.isCueEnabled(cue) )); constructor( diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index efc297855f2..dcb9c506701 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ShowAudioCueHelp } from 'vs/workbench/contrib/audioCues/browser/commands'; +import { ShowAccessibilityAlertHelp, ShowAudioCueHelp } from 'vs/workbench/contrib/audioCues/browser/commands'; import { localize } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; @@ -165,3 +165,4 @@ Registry.as(ConfigurationExtensions.Configuration).regis }); registerAction2(ShowAudioCueHelp); +registerAction2(ShowAccessibilityAlertHelp); diff --git a/src/vs/workbench/contrib/audioCues/browser/commands.ts b/src/vs/workbench/contrib/audioCues/browser/commands.ts index f5d295f7c4c..bda17b0a3e9 100644 --- a/src/vs/workbench/contrib/audioCues/browser/commands.ts +++ b/src/vs/workbench/contrib/audioCues/browser/commands.ts @@ -35,8 +35,8 @@ export class ShowAudioCueHelp extends Action2 { const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.map((cue, idx) => ({ label: accessibilityService.isScreenReaderOptimized() ? - `${cue.name}${audioCueService.isEnabled(cue) ? '' : ' (' + localize('disabled', "Disabled") + ')'}` - : `${audioCueService.isEnabled(cue) ? '$(check)' : ' '} ${cue.name}`, + `${cue.name}${audioCueService.isCueEnabled(cue) ? '' : ' (' + localize('disabled', "Disabled") + ')'}` + : `${audioCueService.isCueEnabled(cue) ? '$(check)' : ' '} ${cue.name}`, audioCue: cue, buttons: [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), @@ -61,3 +61,49 @@ export class ShowAudioCueHelp extends Action2 { await quickPick; } } + +export class ShowAccessibilityAlertHelp extends Action2 { + static readonly ID = 'accessibility.alert.help'; + + constructor() { + super({ + id: ShowAccessibilityAlertHelp.ID, + title: { + value: localize('accessibility.alert.help', "Help: List Alerts"), + original: 'Help: List Alerts' + }, + f1: true, + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const audioCueService = accessor.get(IAudioCueService); + const quickPickService = accessor.get(IQuickInputService); + const preferencesService = accessor.get(IPreferencesService); + const accessibilityService = accessor.get(IAccessibilityService); + + const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.filter(c => !!c.alertMessage).map((cue, idx) => ({ + label: accessibilityService.isScreenReaderOptimized() ? + `${cue.name}${audioCueService.isAlertEnabled(cue) ? '' : ' (' + localize('disabled', "Disabled") + ')'}` + : `${audioCueService.isAlertEnabled(cue) ? '$(check)' : ' '} ${cue.name}`, + audioCue: cue, + buttons: [{ + iconClass: ThemeIcon.asClassName(Codicon.settingsGear), + tooltip: localize('alerts.help.settings', 'Enable/Disable Audio Cue'), + }], + })); + + const quickPick = quickPickService.pick( + items, + { + activeItem: items[0], + onDidTriggerItemButton: (context) => { + preferencesService.openSettings({ query: context.item.audioCue.alertSettingsKey }); + }, + placeHolder: localize('alerts.help.placeholder', 'Inspect and configure the status of an alert'), + } + ); + + await quickPick; + } +} diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index dd15cd78e37..9e3a418ab6c 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -7,8 +7,8 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize2 } from 'vs/nls'; -import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { Action2, IAction2Options, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; @@ -117,5 +117,5 @@ export function getClearAction(viewId: string, providerId: string) { } function announceChatCleared(accessor: ServicesAccessor): void { - accessor.get(IAccessibleNotificationService).notify(AccessibleNotificationEvent.Clear); + accessor.get(IAudioCueService).playAudioCue(AudioCue.clear); } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 2aa639163de..4f35acf8ab1 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -69,7 +69,7 @@ import { Variable } from 'vs/workbench/contrib/debug/common/debugModel'; import { ReplEvaluationResult, ReplGroup } from 'vs/workbench/contrib/debug/common/replModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands'; -import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; const $ = dom.$; @@ -988,9 +988,9 @@ registerAction2(class extends ViewAction { } runInView(_accessor: ServicesAccessor, view: Repl): void { - const accessibleNotificationService = _accessor.get(IAccessibleNotificationService); + const audioCueService = _accessor.get(IAudioCueService); view.clearRepl(); - accessibleNotificationService.notify(AccessibleNotificationEvent.Clear); + audioCueService.playAudioCue(AudioCue.clear); } }); diff --git a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts index 05f9e919889..27594f7cd7c 100644 --- a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts @@ -23,8 +23,7 @@ import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { TestContextService, TestMarkerService } from 'vs/workbench/test/common/workbenchTestServices'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; -import { IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; -import { TestAccessibleNotificationService } from 'vs/workbench/contrib/accessibility/browser/accessibleNotificationService'; +import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; suite('EditorAutoSave', () => { @@ -44,7 +43,7 @@ suite('EditorAutoSave', () => { const configurationService = new TestConfigurationService(); configurationService.setUserConfiguration('files', autoSaveConfig); instantiationService.stub(IConfigurationService, configurationService); - instantiationService.stub(IAccessibleNotificationService, disposables.add(new TestAccessibleNotificationService())); + instantiationService.stub(IAudioCueService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); instantiationService.stub(IFilesConfigurationService, disposables.add(new TestFilesConfigurationService( instantiationService.createInstance(MockContextKeyService), configurationService, diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 4aecf41e33f..eaad7257dae 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -28,7 +28,7 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; // Register Service registerSingleton(IOutputService, OutputService, InstantiationType.Delayed); @@ -223,11 +223,11 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { } async run(accessor: ServicesAccessor): Promise { const outputService = accessor.get(IOutputService); - const accessibleNotificationService = accessor.get(IAccessibleNotificationService); + const audioCueService = accessor.get(IAudioCueService); const activeChannel = outputService.getActiveChannel(); if (activeChannel) { activeChannel.clear(); - accessibleNotificationService.notify(AccessibleNotificationEvent.Clear); + audioCueService.playAudioCue(AudioCue.clear); } } })); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index f37b2f4e202..423e7aa80af 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -30,7 +30,7 @@ import { IEditor } from 'vs/editor/common/editorCommon'; import { CommonFindController } from 'vs/editor/contrib/find/browser/findController'; import { MultiCursorSelectionController } from 'vs/editor/contrib/multicursor/browser/multicursor'; import * as nls from 'vs/nls'; -import { AccessibleNotificationEvent, IAccessibilityService, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -80,6 +80,7 @@ import { TextSearchCompleteMessage } from 'vs/workbench/services/search/common/s import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { ILogService } from 'vs/platform/log/common/log'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; const $ = dom.$; @@ -189,7 +190,7 @@ export class SearchView extends ViewPane { @ITelemetryService telemetryService: ITelemetryService, @INotebookService private readonly notebookService: INotebookService, @ILogService private readonly logService: ILogService, - @IAccessibleNotificationService private readonly accessibleNotificationService: IAccessibleNotificationService + @IAudioCueService private readonly audioCueService: IAudioCueService ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); @@ -1246,7 +1247,7 @@ export class SearchView extends ViewPane { this.viewModel.cancelSearch(); this.tree.ariaLabel = nls.localize('emptySearch', "Empty Search"); - this.accessibleNotificationService.notify(AccessibleNotificationEvent.Clear); + this.audioCueService.playAudioCue(AudioCue.clear); this.reLayout(); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 4aefa9ab3ff..bec7a9a3502 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -759,8 +759,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { icon: Codicon.bell, tooltip: nls.localize('bellStatus', "Bell") }, this._configHelper.config.bellDuration); - this._audioCueService.playSound(AudioCue.terminalBell.sound.getSound()); } + this._audioCueService.playAudioCue(AudioCue.terminalBell); })); }, 1000, this._store); this._register(xterm.raw.onSelectionChange(async () => this._onSelectionChange())); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 97bf4a1ff78..529b33ddef5 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -42,8 +42,8 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { debounce } from 'vs/base/common/decorators'; import { MouseWheelClassifier } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { IMouseWheelEvent, StandardWheelEvent } from 'vs/base/browser/mouseEvent'; -import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; const enum RenderConstants { /** @@ -205,7 +205,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach @ITelemetryService private readonly _telemetryService: ITelemetryService, @IClipboardService private readonly _clipboardService: IClipboardService, @IContextKeyService contextKeyService: IContextKeyService, - @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService, + @IAudioCueService private readonly _audioCueService: IAudioCueService, @ILayoutService layoutService: ILayoutService ) { super(); @@ -584,7 +584,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach // the prompt being written this._capabilities.get(TerminalCapability.CommandDetection)?.handlePromptStart(); this._capabilities.get(TerminalCapability.CommandDetection)?.handleCommandStart(); - this._accessibleNotificationService.notify(AccessibleNotificationEvent.Clear); + this._audioCueService.playAudioCue(AudioCue.clear); } hasSelection(): boolean { diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts index 75812ea4a31..833db09d807 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts @@ -7,7 +7,6 @@ import * as assert from 'assert'; import { importAMDNodeModule } from 'vs/amdX'; import { isWindows } from 'vs/base/common/platform'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -22,7 +21,6 @@ import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilitie import { ITerminalLogService } from 'vs/platform/terminal/common/terminal'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; -import { TestAccessibleNotificationService } from 'vs/workbench/contrib/accessibility/browser/accessibleNotificationService'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { writeP } from 'vs/workbench/contrib/terminal/browser/terminalTestHelpers'; import { XtermTerminal } from 'vs/workbench/contrib/terminal/browser/xterm/xtermTerminal'; @@ -32,6 +30,7 @@ import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecy import { TestLayoutService, TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestLoggerService } from 'vs/workbench/test/common/workbenchTestServices'; import type { Terminal } from '@xterm/xterm'; +import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; const defaultTerminalConfig: Partial = { fontFamily: 'monospace', @@ -68,7 +67,8 @@ suite('Buffer Content Tracker', () => { instantiationService.stub(IContextMenuService, store.add(instantiationService.createInstance(ContextMenuService))); instantiationService.stub(ILifecycleService, store.add(new TestLifecycleService())); instantiationService.stub(IContextKeyService, store.add(new MockContextKeyService())); - instantiationService.stub(IAccessibleNotificationService, store.add(new TestAccessibleNotificationService())); + instantiationService.stub(IAudioCueService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); + instantiationService.stub(ILayoutService, new TestLayoutService()); configHelper = store.add(instantiationService.createInstance(TerminalConfigHelper)); capabilities = store.add(new TerminalCapabilityStore()); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index b7f8f32f918..e3980962458 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -73,7 +73,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkingCopyService, WorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IWorkingCopy, IWorkingCopyBackupMeta, IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { IAccessibilityService, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { BrowserTextFileService } from 'vs/workbench/services/textfile/browser/browserTextFileService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -166,10 +166,10 @@ import { IHoverOptions, IHoverService } from 'vs/platform/hover/browser/hover'; import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; import { IRemoteSocketFactoryService, RemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; import { EditorParts } from 'vs/workbench/browser/parts/editor/editorParts'; -import { TestAccessibleNotificationService } from 'vs/workbench/contrib/accessibility/browser/accessibleNotificationService'; import { mainWindow } from 'vs/base/browser/window'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined, undefined); @@ -279,8 +279,7 @@ export function workbenchInstantiationService( instantiationService.stub(IDialogService, new TestDialogService()); const accessibilityService = new TestAccessibilityService(); instantiationService.stub(IAccessibilityService, accessibilityService); - const accessibleNotificationService = disposables.add(new TestAccessibleNotificationService()); - instantiationService.stub(IAccessibleNotificationService, accessibleNotificationService); + instantiationService.stub(IAudioCueService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); instantiationService.stub(IFileDialogService, instantiationService.createInstance(TestFileDialogService)); instantiationService.stub(ILanguageService, disposables.add(instantiationService.createInstance(LanguageService))); instantiationService.stub(ILanguageFeaturesService, new LanguageFeaturesService()); From 0a284dde183e8f15b1938714e18615b1ec41831d Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 9 Jan 2024 21:57:53 +0100 Subject: [PATCH 0184/1897] Introduces _workbench.openMultiDiffEditor --- .../browser/parts/editor/editorCommands.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 9ef96ab29ad..4a924f46cc5 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -739,6 +739,21 @@ function registerOpenEditorAPICommands(): void { await editorService.openEditor({ resources: editor, label: title }); }); + + CommandsRegistry.registerCommand('_workbench.openMultiDiffEditor', async (accessor: ServicesAccessor, options: OpenMultiFileDiffEditorOptions) => { + const editorService = accessor.get(IEditorService); + await editorService.openEditor({ + multiDiffSource: options.multiDiffSourceUri ? URI.revive(options.multiDiffSourceUri) : undefined, + resources: options.resources?.map(r => ({ original: { resource: URI.revive(r.originalUri) }, modified: { resource: URI.revive(r.modifiedUri) } })), + label: options.title, + }); + }); +} + +interface OpenMultiFileDiffEditorOptions { + title: string; + multiDiffSourceUri?: UriComponents; + resources?: { originalUri: UriComponents; modifiedUri: UriComponents }[]; } function registerOpenEditorAtIndexCommands(): void { From 05dfba857ab3d1007f51e2dd757038c5000bb1d3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 9 Jan 2024 22:28:08 +0100 Subject: [PATCH 0185/1897] Adopts #199291 for SCM view changes. (#202077) --- extensions/git/package.json | 33 +--- extensions/git/src/commands.ts | 20 --- extensions/git/src/repository.ts | 14 +- extensions/git/tsconfig.json | 1 + src/vs/workbench/api/browser/mainThreadSCM.ts | 18 ++- .../workbench/api/common/extHost.protocol.ts | 6 +- src/vs/workbench/api/common/extHostSCM.ts | 32 +++- .../browser/multiDiffEditor.contribution.ts | 6 + .../browser/scmMultiDiffSourceResolver.ts | 153 ++++++++++++++++++ src/vs/workbench/contrib/scm/browser/menus.ts | 17 +- src/vs/workbench/contrib/scm/common/scm.ts | 4 + .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.scmMultiDiffEditor.d.ts | 34 ++++ 13 files changed, 263 insertions(+), 76 deletions(-) create mode 100644 src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts create mode 100644 src/vscode-dts/vscode.proposed.scmMultiDiffEditor.d.ts diff --git a/extensions/git/package.json b/extensions/git/package.json index 0dbe6d739e8..b730372960b 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -21,6 +21,7 @@ "scmHistoryProvider", "scmSelectedProvider", "scmValidation", + "scmMultiDiffEditor", "tabInputTextMerge", "timeline", "contribMergeEditorMenus", @@ -773,20 +774,6 @@ "title": "%command.openRepositoriesInParentFolders%", "category": "Git" }, - { - "command": "git.viewChanges", - "title": "%command.viewChanges%", - "icon": "$(diff-multiple)", - "category": "Git", - "enablement": "!operationInProgress" - }, - { - "command": "git.viewStagedChanges", - "title": "%command.viewStagedChanges%", - "icon": "$(diff-multiple)", - "category": "Git", - "enablement": "!operationInProgress" - }, { "command": "git.openCommit", "title": "%command.openCommit%", @@ -1277,14 +1264,6 @@ "command": "git.stashPreview", "when": "config.git.enabled && !git.missing && config.multiDiffEditor.experimental.enabled" }, - { - "command": "git.viewChanges", - "when": "false" - }, - { - "command": "git.viewStagedChanges", - "when": "false" - }, { "command": "git.openCommit", "when": "false" @@ -1459,16 +1438,6 @@ "command": "git.stageAllUntracked", "when": "scmProvider == git && scmResourceGroup == untracked", "group": "inline@2" - }, - { - "command": "git.viewStagedChanges", - "when": "scmProvider == git && scmResourceGroup == index && config.multiDiffEditor.experimental.enabled", - "group": "inline@1" - }, - { - "command": "git.viewChanges", - "when": "scmProvider == git && scmResourceGroup == workingTree && config.multiDiffEditor.experimental.enabled", - "group": "inline@1" } ], "scm/resourceFolder/context": [ diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 21c1b2e3e0b..ccc68cf65a2 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3819,26 +3819,6 @@ export class CommandCenter { } } - @command('git.viewChanges', { repository: true }) - viewChanges(repository: Repository): void { - this._viewChanges('Git: Changes', repository.workingTreeGroup.resourceStates); - } - - @command('git.viewStagedChanges', { repository: true }) - viewStagedChanges(repository: Repository): void { - this._viewChanges('Git: Staged Changes', repository.indexGroup.resourceStates); - } - - private _viewChanges(title: string, resources: Resource[]): void { - const args: [Uri, Uri | undefined, Uri | undefined][] = []; - - for (const resource of resources) { - args.push([resource.resourceUri, resource.leftUri, resource.rightUri]); - } - - commands.executeCommand('vscode.changes', title, args); - } - @command('git.openCommit', { repository: true }) async openCommit(repository: Repository, historyItem: SourceControlHistoryItem): Promise { if (!repository || !historyItem) { diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index e512d5f0bed..94ce234bb01 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -161,6 +161,14 @@ export class Resource implements SourceControlResourceState { return this.resources[1]; } + get multiDiffEditorOriginalUri(): Uri | undefined { + return this.leftUri; + } + + get multiFileDiffEditorModifiedUri(): Uri | undefined { + return this.rightUri; + } + @memoize get command(): Command { return this._commandResolver.resolveDefaultCommand(this); @@ -866,9 +874,9 @@ export class Repository implements Disposable { this.disposables.push(this.onDidRunGitStatus(() => this.updateInputBoxPlaceholder())); this._mergeGroup = this._sourceControl.createResourceGroup('merge', l10n.t('Merge Changes')); - this._indexGroup = this._sourceControl.createResourceGroup('index', l10n.t('Staged Changes')); - this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', l10n.t('Changes')); - this._untrackedGroup = this._sourceControl.createResourceGroup('untracked', l10n.t('Untracked Changes')); + this._indexGroup = this._sourceControl.createResourceGroup('index', l10n.t('Staged Changes'), { multiDiffEditorEnableViewChanges: true }); + this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', l10n.t('Changes'), { multiDiffEditorEnableViewChanges: true }); + this._untrackedGroup = this._sourceControl.createResourceGroup('untracked', l10n.t('Untracked Changes'), { multiDiffEditorEnableViewChanges: true }); const updateIndexGroupVisibility = () => { const config = workspace.getConfiguration('git', root); diff --git a/extensions/git/tsconfig.json b/extensions/git/tsconfig.json index d5fdbd539da..b9658b04666 100644 --- a/extensions/git/tsconfig.json +++ b/extensions/git/tsconfig.json @@ -15,6 +15,7 @@ "../../src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts", "../../src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts", "../../src/vscode-dts/vscode.proposed.scmValidation.d.ts", + "../../src/vscode-dts/vscode.proposed.scmMultiDiffEditor.d.ts", "../../src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts", "../../src/vscode-dts/vscode.proposed.timeline.d.ts", "../types/lib.textEncoder.d.ts" diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index e0afc9bed49..6e4d467c126 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -67,6 +67,7 @@ class MainThreadSCMResourceGroup implements ISCMResourceGroup { public features: SCMGroupFeatures, public label: string, public id: string, + public readonly multiDiffEditorEnableViewChanges: boolean, private readonly _uriIdentService: IUriIdentityService ) { } @@ -107,7 +108,9 @@ class MainThreadSCMResource implements ISCMResource { readonly resourceGroup: ISCMResourceGroup, readonly decorations: ISCMResourceDecorations, readonly contextValue: string | undefined, - readonly command: Command | undefined + readonly command: Command | undefined, + readonly multiFileDiffEditorOriginalUri: URI | undefined, + readonly multiFileDiffEditorModifiedUri: URI | undefined, ) { } open(preserveFocus: boolean): Promise { @@ -316,8 +319,8 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { } } - $registerGroups(_groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures][]): void { - const groups = _groups.map(([handle, id, label, features]) => { + $registerGroups(_groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures, /* multiDiffEditorEnableViewChanges */ boolean][]): void { + const groups = _groups.map(([handle, id, label, features, multiDiffEditorEnableViewChanges]) => { const group = new MainThreadSCMResourceGroup( this.handle, handle, @@ -325,6 +328,7 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { features, label, id, + multiDiffEditorEnableViewChanges, this._uriIdentService ); @@ -370,7 +374,7 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { for (const [start, deleteCount, rawResources] of groupSlices) { const resources = rawResources.map(rawResource => { - const [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue, command] = rawResource; + const [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue, command, multiFileDiffEditorOriginalUri, multiFileDiffEditorModifiedUri] = rawResource; const [light, dark] = icons; const icon = ThemeIcon.isThemeIcon(light) ? light : URI.revive(light); @@ -393,7 +397,9 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { group, decorations, contextValue || undefined, - command + command, + URI.revive(multiFileDiffEditorOriginalUri), + URI.revive(multiFileDiffEditorModifiedUri), ); }); @@ -520,7 +526,7 @@ export class MainThreadSCM implements MainThreadSCMShape { this._repositories.delete(handle); } - $registerGroups(sourceControlHandle: number, groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures][], splices: SCMRawResourceSplices[]): void { + $registerGroups(sourceControlHandle: number, groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures, /* multiDiffEditorEnableViewChanges */ boolean][], splices: SCMRawResourceSplices[]): void { const repository = this._repositories.get(sourceControlHandle); if (!repository) { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c6dbe8b3a75..646e6791659 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1441,7 +1441,9 @@ export type SCMRawResource = [ boolean /*strike through*/, boolean /*faded*/, string /*context value*/, - ICommandDto | undefined /*command*/ + ICommandDto | undefined /*command*/, + UriComponents | undefined /* multiFileDiffEditorOriginalUri */, + UriComponents | undefined /* multiFileDiffEditorModifiedUri */, ]; export type SCMRawResourceSplice = [ @@ -1487,7 +1489,7 @@ export interface MainThreadSCMShape extends IDisposable { $updateSourceControl(handle: number, features: SCMProviderFeatures): void; $unregisterSourceControl(handle: number): void; - $registerGroups(sourceControlHandle: number, groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures][], splices: SCMRawResourceSplices[]): void; + $registerGroups(sourceControlHandle: number, groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures, /* multiDiffEditorEnableViewChanges */ boolean][], splices: SCMRawResourceSplices[]): void; $updateGroup(sourceControlHandle: number, handle: number, features: SCMGroupFeatures): void; $updateGroupLabel(sourceControlHandle: number, handle: number, label: string): void; $unregisterGroup(sourceControlHandle: number, handle: number): void; diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 2c7d375a7a7..5576c0be0cf 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -203,6 +203,21 @@ function compareResourceStates(a: vscode.SourceControlResourceState, b: vscode.S return -1; } + if (a.multiFileDiffEditorModifiedUri && b.multiFileDiffEditorModifiedUri) { + result = comparePaths(a.multiFileDiffEditorModifiedUri.fsPath, b.multiFileDiffEditorModifiedUri.fsPath, true); + } else if (a.multiFileDiffEditorModifiedUri) { + return 1; + } else if (b.multiFileDiffEditorModifiedUri) { + return -1; + } + if (a.multiDiffEditorOriginalUri && b.multiDiffEditorOriginalUri) { + result = comparePaths(a.multiDiffEditorOriginalUri.fsPath, b.multiDiffEditorOriginalUri.fsPath, true); + } else if (a.multiDiffEditorOriginalUri) { + return 1; + } else if (b.multiDiffEditorOriginalUri) { + return -1; + } + return result; } @@ -401,6 +416,8 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG private _sourceControlHandle: number, private _id: string, private _label: string, + public readonly multiDiffEditorEnableViewChanges: boolean, + private readonly _extension: IExtensionDescription, ) { } getResourceState(handle: number): vscode.SourceControlResourceState | undefined { @@ -439,6 +456,10 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG } } + const hasScmMultiDiffEditorProposalEnabled = isProposedApiEnabled(this._extension, 'scmMultiDiffEditor'); + const multiFileDiffEditorOriginalUri = hasScmMultiDiffEditorProposalEnabled ? r.multiDiffEditorOriginalUri : undefined; + const multiFileDiffEditorModifiedUri = hasScmMultiDiffEditorProposalEnabled ? r.multiFileDiffEditorModifiedUri : undefined; + const icon = getIconResource(r.decorations); const lightIcon = r.decorations && getIconResource(r.decorations.light) || icon; const darkIcon = r.decorations && getIconResource(r.decorations.dark) || icon; @@ -449,7 +470,7 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG const faded = r.decorations && !!r.decorations.faded; const contextValue = r.contextValue || ''; - const rawResource = [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue, command] as SCMRawResource; + const rawResource = [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue, command, multiFileDiffEditorOriginalUri, multiFileDiffEditorModifiedUri] as SCMRawResource; return { rawResource, handle }; }); @@ -673,8 +694,9 @@ class ExtHostSourceControl implements vscode.SourceControl { private createdResourceGroups = new Map(); private updatedResourceGroups = new Set(); - createResourceGroup(id: string, label: string): ExtHostSourceControlResourceGroup { - const group = new ExtHostSourceControlResourceGroup(this.#proxy, this._commands, this.handle, id, label); + createResourceGroup(id: string, label: string, options?: { multiDiffEditorEnableViewChanges?: boolean }): ExtHostSourceControlResourceGroup { + const multiDiffEditorEnableViewChanges = isProposedApiEnabled(this._extension, 'scmMultiDiffEditor') && options?.multiDiffEditorEnableViewChanges === true; + const group = new ExtHostSourceControlResourceGroup(this.#proxy, this._commands, this.handle, id, label, multiDiffEditorEnableViewChanges, this._extension); const disposable = Event.once(group.onDidDispose)(() => this.createdResourceGroups.delete(group)); this.createdResourceGroups.set(group, disposable); this.eventuallyAddResourceGroups(); @@ -683,7 +705,7 @@ class ExtHostSourceControl implements vscode.SourceControl { @debounce(100) eventuallyAddResourceGroups(): void { - const groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures][] = []; + const groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures, /*multiDiffEditorEnableViewChanges*/ boolean][] = []; const splices: SCMRawResourceSplices[] = []; for (const [group, disposable] of this.createdResourceGroups) { @@ -701,7 +723,7 @@ class ExtHostSourceControl implements vscode.SourceControl { this.#proxy.$unregisterGroup(this.handle, group.handle); }); - groups.push([group.handle, group.id, group.label, group.features]); + groups.push([group.handle, group.id, group.label, group.features, group.multiDiffEditorEnableViewChanges]); const snapshot = group._takeResourceStateSnapshot(); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts index c34ddd0318e..19631c2f3b7 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts @@ -17,6 +17,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { CollapseAllAction, ExpandAllAction, GoToFileAction } from './actions'; import { IMultiDiffSourceResolverService, MultiDiffSourceResolverService } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { OpenScmGroupAction, ScmMultiDiffSourceResolverContribution } from 'vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver'; registerAction2(GoToFileAction); registerAction2(CollapseAllAction); @@ -47,3 +48,8 @@ Registry.as(EditorExtensions.EditorPane) Registry.as(EditorExtensions.EditorFactory) .registerEditorSerializer(MultiDiffEditorInput.ID, MultiDiffEditorSerializer); + +// SCM integration +registerAction2(OpenScmGroupAction); +Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(ScmMultiDiffSourceResolverContribution, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts new file mode 100644 index 00000000000..2081d3b74ec --- /dev/null +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts @@ -0,0 +1,153 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { localize, localize2 } from 'vs/nls'; +import { Action2, MenuId } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IMultiDiffSourceResolver, IMultiDiffSourceResolverService, IResolvedMultiDiffSource } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService'; +import { ISCMResourceGroup, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +export class ScmMultiDiffSourceResolver implements IMultiDiffSourceResolver { + private static readonly _scheme = 'scm-multi-diff-source'; + + public static getMultiDiffSourceUri(repositoryUri: string, groupId: string): URI { + return URI.from({ + scheme: ScmMultiDiffSourceResolver._scheme, + query: JSON.stringify({ repositoryUri, groupId } satisfies UriFields), + }); + } + + private static parseUri(uri: URI): { repositoryUri: URI; groupId: string } | undefined { + if (uri.scheme !== ScmMultiDiffSourceResolver._scheme) { + return undefined; + } + + let query: any; + try { + query = JSON.parse(uri.query) as UriFields; + } catch (e) { + return undefined; + } + + if (typeof query !== 'object' || query === null) { + return undefined; + } + + const { repositoryUri, groupId } = query; + if (typeof repositoryUri !== 'string' || typeof groupId !== 'string') { + return undefined; + } + + return { repositoryUri: URI.parse(repositoryUri), groupId }; + } + + constructor( + @ISCMService private readonly _scmService: ISCMService, + ) { + } + + canHandleUri(uri: URI): boolean { + return ScmMultiDiffSourceResolver.parseUri(uri) !== undefined; + } + + async resolveDiffSource(uri: URI): Promise { + const { repositoryUri, groupId } = ScmMultiDiffSourceResolver.parseUri(uri)!; + + const repository = await promiseFromEventState( + this._scmService.onDidAddRepository, + () => { + const repository = [...this._scmService.repositories].find(r => r.provider.rootUri?.toString() === repositoryUri.toString()); + return repository ?? false; + } + ); + + const group = await promiseFromEventState( + repository.provider.onDidChangeResourceGroups, + () => { + const group = repository.provider.groups.find(g => g.id === groupId); + return group ?? false; + } + ); + + return { + get resources() { + return group.resources.map(e => ({ + original: e.multiFileDiffEditorOriginalUri, + modified: e.multiFileDiffEditorModifiedUri + })); + }, + onDidChange: e => group.onDidChangeResources(() => e()), + }; + } +} + +interface UriFields { + repositoryUri: string; + groupId: string; +} + +function promiseFromEventState(event: Event, checkState: () => T | false): Promise { + const state = checkState(); + if (state) { + return Promise.resolve(state); + } + + return new Promise(resolve => { + const listener = event(() => { + const state = checkState(); + if (state) { + listener.dispose(); + resolve(state); + } + }); + }); +} + +export class ScmMultiDiffSourceResolverContribution extends Disposable { + constructor( + @IInstantiationService instantiationService: IInstantiationService, + @IMultiDiffSourceResolverService multiDiffSourceResolverService: IMultiDiffSourceResolverService, + ) { + super(); + + this._register(multiDiffSourceResolverService.registerResolver(instantiationService.createInstance(ScmMultiDiffSourceResolver))); + } +} + +export class OpenScmGroupAction extends Action2 { + constructor() { + super({ + id: 'multiDiffEditor.openScmDiff', + title: localize2('viewChanges', 'View Changes'), + icon: Codicon.diffMultiple, + menu: { + when: ContextKeyExpr.and( + ContextKeyExpr.has('config.multiDiffEditor.experimental.enabled'), + ContextKeyExpr.has('multiDiffEditorEnableViewChanges'), + ), + id: MenuId.SCMResourceGroupContext, + group: 'inline', + }, + f1: false, + }); + } + + async run(accessor: ServicesAccessor, group: ISCMResourceGroup): Promise { + const editorService = accessor.get(IEditorService); + if (!group.provider.rootUri) { + return; + } + + const multiDiffSource = ScmMultiDiffSourceResolver.getMultiDiffSourceUri(group.provider.rootUri.toString(), group.id); + const label = localize('scmDiffLabel', '{0}: {1}', group.provider.label, group.label); + await editorService.openEditor({ label, multiDiffSource }); + } +} diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index 7b1b601cea2..fa07957da0a 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -3,19 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/scm'; -import { Emitter } from 'vs/base/common/event'; -import { IDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IMenuService, MenuId, IMenu, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IAction } from 'vs/base/common/actions'; -import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { ISCMResource, ISCMResourceGroup, ISCMProvider, ISCMRepository, ISCMService, ISCMMenus, ISCMRepositoryMenus } from 'vs/workbench/contrib/scm/common/scm'; import { equals } from 'vs/base/common/arrays'; +import { Emitter } from 'vs/base/common/event'; +import { DisposableStore, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import 'vs/css!./media/scm'; +import { localize } from 'vs/nls'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenu, IMenuService, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { localize } from 'vs/nls'; import { ISCMHistoryItem, ISCMHistoryItemGroupEntry, ISCMHistoryProviderMenus } from 'vs/workbench/contrib/scm/common/history'; +import { ISCMMenus, ISCMProvider, ISCMRepository, ISCMRepositoryMenus, ISCMResource, ISCMResourceGroup, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; function actionEquals(a: IAction, b: IAction): boolean { return a.id === b.id; @@ -228,6 +228,7 @@ export class SCMRepositoryMenus implements ISCMRepositoryMenus, IDisposable { if (!result) { const contextKeyService = this.contextKeyService.createOverlay([ ['scmResourceGroup', group.id], + ['multiDiffEditorEnableViewChanges', group.multiDiffEditorEnableViewChanges], ]); result = new SCMMenusItem(contextKeyService, this.menuService); diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 5cf8d9bf716..1ff1a8c59fd 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -39,6 +39,8 @@ export interface ISCMResource { readonly decorations: ISCMResourceDecorations; readonly contextValue: string | undefined; readonly command: Command | undefined; + readonly multiFileDiffEditorOriginalUri: URI | undefined; + readonly multiFileDiffEditorModifiedUri: URI | undefined; open(preserveFocus: boolean): Promise; } @@ -53,6 +55,8 @@ export interface ISCMResourceGroup { readonly label: string; readonly hideWhenEmpty: boolean; readonly onDidChange: Event; + + readonly multiDiffEditorEnableViewChanges: boolean; } export interface ISCMProvider extends IDisposable { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 0d20d085e99..6bfddc2c6e5 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -82,6 +82,7 @@ export const allApiProposals = Object.freeze({ resolvers: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.resolvers.d.ts', scmActionButton: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmActionButton.d.ts', scmHistoryProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts', + scmMultiDiffEditor: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmMultiDiffEditor.d.ts', scmSelectedProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts', scmTextDocument: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmTextDocument.d.ts', scmValidation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmValidation.d.ts', diff --git a/src/vscode-dts/vscode.proposed.scmMultiDiffEditor.d.ts b/src/vscode-dts/vscode.proposed.scmMultiDiffEditor.d.ts new file mode 100644 index 00000000000..49fdd4a3993 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.scmMultiDiffEditor.d.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * 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/199291 + + export interface SourceControlResourceState { + /** + * The uri that resolves to the original document of this resource (before the change). + * Used for the multi diff editor exclusively. + */ + readonly multiDiffEditorOriginalUri?: Uri; + + /** + * The uri that resolves to the modified document of this resource (after the change). + * Used for the multi diff editor exclusively. + */ + readonly multiFileDiffEditorModifiedUri?: Uri; + } + + export interface SourceControl { + /** + * Create a new {@link SourceControlResourceGroup resource group}. + * @param id An `id` for the {@link SourceControlResourceGroup resource group}. + * @param label A human-readable string for the {@link SourceControlResourceGroup resource group}. + * @param options Options for the {@link SourceControlResourceGroup resource group}. + * Set `multiDiffEditorEnableViewChanges` to `true` to enable the "View Changes" option which opens the multi file diff editor. + * @return An instance of {@link SourceControlResourceGroup resource group}. + */ + createResourceGroup(id: string, label: string, options: { multiDiffEditorEnableViewChanges?: boolean }): SourceControlResourceGroup; + } +} From 35579c0b5700e43b3f1ff4062bd1498c78a736d2 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 9 Jan 2024 14:27:28 -0800 Subject: [PATCH 0186/1897] De-prioritize markdown link paste in code blocks (#202109) --- .../src/client/inMemoryDocument.ts | 5 +++++ .../src/languageFeatures/copyFiles/pasteUrlProvider.ts | 8 ++++++-- .../markdown-language-features/src/types/textDocument.ts | 2 ++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/extensions/markdown-language-features/src/client/inMemoryDocument.ts b/extensions/markdown-language-features/src/client/inMemoryDocument.ts index adf7a143c1d..953f0da7c89 100644 --- a/extensions/markdown-language-features/src/client/inMemoryDocument.ts +++ b/extensions/markdown-language-features/src/client/inMemoryDocument.ts @@ -27,4 +27,9 @@ export class InMemoryDocument implements ITextDocument { getText(range?: vscode.Range): string { return this._doc.getText(range); } + + positionAt(offset: number): vscode.Position { + const pos = this._doc.positionAt(offset); + return new vscode.Position(pos.line, pos.character); + } } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts index e87c63f3d50..1515dbdee85 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts @@ -111,12 +111,16 @@ export function shouldSmartPaste(document: ITextDocument, selectedRange: vscode. return false; } + // TODO: use proper parsing instead of regexes for (const regex of smartPasteRegexes) { const matches = [...document.getText().matchAll(regex.regex)]; for (const match of matches) { if (match.index !== undefined) { - const useDefaultPaste = selectedRange.start.character > match.index && selectedRange.end.character < match.index + match[0].length; - if (useDefaultPaste) { + const matchRange = new vscode.Range( + document.positionAt(match.index), + document.positionAt(match.index + match[0].length) + ); + if (matchRange.intersection(selectedRange)) { return false; } } diff --git a/extensions/markdown-language-features/src/types/textDocument.ts b/extensions/markdown-language-features/src/types/textDocument.ts index e57299671f6..d8809315c2e 100644 --- a/extensions/markdown-language-features/src/types/textDocument.ts +++ b/extensions/markdown-language-features/src/types/textDocument.ts @@ -13,5 +13,7 @@ export interface ITextDocument { readonly version: number; getText(range?: vscode.Range): string; + + positionAt(offset: number): vscode.Position; } From ad18bb00ce2447914f85f03c600bbd79a5b9dca6 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 9 Jan 2024 23:00:52 +0100 Subject: [PATCH 0187/1897] Fixes #199351 --- .../lib/stylelint/vscode-known-variables.json | 4 +- .../theme-defaults/themes/light_vs.json | 3 +- .../widget/multiDiffEditorWidget/colors.ts | 13 +++++++ .../diffEditorItemTemplate.ts | 39 ++++++------------- .../multiDiffEditorWidgetImpl.ts | 17 ++++---- .../widget/multiDiffEditorWidget/style.css | 38 ++++++++++-------- 6 files changed, 58 insertions(+), 56 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 6d0e83e9743..c15c90a21d9 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -727,7 +727,9 @@ "--vscode-widget-border", "--vscode-widget-shadow", "--vscode-window-activeBorder", - "--vscode-window-inactiveBorder" + "--vscode-window-inactiveBorder", + "--vscode-multiDiffEditor-background", + "--vscode-multiDiffEditor-border" ], "others": [ "--background-dark", diff --git a/extensions/theme-defaults/themes/light_vs.json b/extensions/theme-defaults/themes/light_vs.json index c67d2932a16..bdd063fbc56 100644 --- a/extensions/theme-defaults/themes/light_vs.json +++ b/extensions/theme-defaults/themes/light_vs.json @@ -31,7 +31,8 @@ "list.focusAndSelectionOutline": "#90C2F9", "terminal.inactiveSelectionBackground": "#E5EBF1", "widget.border": "#d4d4d4", - "actionBar.toggledBackground": "#dddddd" + "actionBar.toggledBackground": "#dddddd", + "diffEditor.unchangedRegionBackground": "#f8f8f8" }, "tokenColors": [ { diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/colors.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/colors.ts index 5b69dd6c1df..b07e5338e80 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/colors.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/colors.ts @@ -11,3 +11,16 @@ export const multiDiffEditorHeaderBackground = registerColor( { dark: '#808080', light: '#b4b4b4', hcDark: '#808080', hcLight: '#b4b4b4', }, localize('multiDiffEditor.headerBackground', 'The background color of the diff editor\'s header') ); + +export const multiDiffEditorBackground = registerColor( + 'multiDiffEditor.background', + { dark: '#000000', light: '#e5e5e5', hcDark: '#000000', hcLight: '#e5e5e5', }, + localize('multiDiffEditor.background', 'The background color of the multi file diff editor') +); + +export const multiDiffEditorBorder = registerColor( + 'multiDiffEditor.border', + { dark: 'sideBarSectionHeader.border', light: '#cccccc', hcDark: 'sideBarSectionHeader.border', hcLight: '#cccccc', }, + localize('multiDiffEditor.border', 'The border color of the multi file diff editor') +); + diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts index 2218b53f5ed..e81aa6f51b9 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts @@ -36,9 +36,9 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< private readonly _collapsed = derived(this, reader => this._viewModel.read(reader)?.collapsed.read(reader)); - private readonly _contentHeight = observableValue(this, 500); - public readonly height = derived(this, reader => { - const h = this._collapsed.read(reader) ? 0 : this._contentHeight.read(reader); + private readonly _editorContentHeight = observableValue(this, 500); + public readonly contentHeight = derived(this, reader => { + const h = this._collapsed.read(reader) ? 0 : this._editorContentHeight.read(reader); return h + this._outerEditorHeight; }); @@ -58,29 +58,14 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< }); private readonly _elements = h('div.multiDiffEntry', [ - h('div.content', { - style: { - display: 'flex', - flexDirection: 'column', - flex: '1', - overflow: 'hidden', - } - }, [ - h('div.header@header', [ - h('div.collapse-button@collapseButton'), - h('div.title.show-file-icons@title', [] as any), - h('div.actions@actions'), - ]), + h('div.header@header', [ + h('div.collapse-button@collapseButton'), + h('div.title.show-file-icons@title', [] as any), + h('div.actions@actions'), + ]), - h('div.editorParent', { - style: { - flex: '1', - display: 'flex', - flexDirection: 'column', - } - }, [ - h('div.editorContainer@editor', { style: { flex: '1' } }), - ]) + h('div.editorParent', [ + h('div.editorContainer@editor'), ]) ]); @@ -132,7 +117,7 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< this._register(this.editor.onDidContentSizeChange(e => { globalTransaction(tx => { - this._contentHeight.set(e.contentHeight, tx); + this._editorContentHeight.set(e.contentHeight, tx); this._modifiedContentWidth.set(this.editor.getModifiedEditor().getContentWidth(), tx); this._originalContentWidth.set(this.editor.getOriginalEditor().getContentWidth(), tx); }); @@ -199,7 +184,7 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< }); } - private readonly _headerHeight = this._elements.header.clientHeight; + private readonly _headerHeight = /*this._elements.header.clientHeight*/ 38; public render(verticalRange: OffsetRange, width: number, editorScroll: number, viewPort: OffsetRange): void { this._elements.root.style.visibility = 'visible'; diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 4a1baec1796..cd1fbf9cb81 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -23,11 +23,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; export class MultiDiffEditorWidgetImpl extends Disposable { - private readonly _elements = h('div', { - style: { - overflowY: 'hidden', - } - }, [ + private readonly _elements = h('div.monaco-component.multiDiffEditor', [ h('div@content', { style: { overflow: 'hidden', @@ -59,7 +55,6 @@ export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _scrollableElement = this._register(new SmoothScrollableElement(this._elements.root, { vertical: ScrollbarVisibility.Auto, horizontal: ScrollbarVisibility.Auto, - className: 'monaco-component', useShadows: false, }, this._scrollable)); @@ -193,8 +188,10 @@ export class MultiDiffEditorWidgetImpl extends Disposable { v.render(itemRange, scroll, width, viewPort); } - itemHeightSumBefore += itemHeight; - itemContentHeightSumBefore += itemContentHeight; + const spaceBetween = 10; + + itemHeightSumBefore += itemHeight + spaceBetween; + itemContentHeightSumBefore += itemContentHeight + spaceBetween; } this._elements.content.style.transform = `translateY(${-(scrollTop + contentScrollOffsetToScrollOffset)}px)`; @@ -210,7 +207,7 @@ class VirtualizedViewItem extends Disposable { private readonly _templateRef = this._register(disposableObservableValue | undefined>(this, undefined)); public readonly contentHeight = derived(this, reader => - this._templateRef.read(reader)?.object.height?.read(reader) ?? this._lastTemplateData.read(reader).contentHeight + this._templateRef.read(reader)?.object.contentHeight?.read(reader) ?? this._lastTemplateData.read(reader).contentHeight ); public readonly maxScroll = derived(this, reader => this._templateRef.read(reader)?.object.maxScroll.read(reader) ?? this._lastTemplateData.read(reader).maxScroll); @@ -257,7 +254,7 @@ class VirtualizedViewItem extends Disposable { if (!ref) { return; } transaction(tx => { this._lastTemplateData.set({ - contentHeight: ref.object.height.get(), + contentHeight: ref.object.contentHeight.get(), maxScroll: { maxScroll: 0, width: 0, } // Reset max scroll }, tx); ref.object.hide(); diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css b/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css index 8674fdfb791..f1ce31f7837 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css @@ -3,21 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +.monaco-component.multiDiffEditor { + background: var(--vscode-multiDiffEditor-background); + overflow-y: hidden; +} + .monaco-component .multiDiffEntry { display: flex; flex-direction: column; -} + flex: 1; + overflow: hidden; -.monaco-component .multiDiffEntry .editorParent { - border-left: 2px var(--vscode-tab-inactiveBackground) solid; -} - -.monaco-component .multiDiffEntry.focused .editorParent { - border-left: 2px var(--vscode-notebook-focusedCellBorder) solid; -} - -.monaco-component .multiDiffEntry .editorParent .editorContainer { - border-left: 17px var(--vscode-tab-inactiveBackground) solid; + border-bottom: 1px solid var(--vscode-multiDiffEditor-border); } .monaco-component .multiDiffEntry .collapse-button { @@ -35,15 +32,12 @@ padding: 8px 5px; color: var(--vscode-foreground); background: var(--vscode-editor-background); + z-index: 1000; - border-bottom: 1px var(--vscode-sideBarSectionHeader-border) solid; - border-top: 1px var(--vscode-sideBarSectionHeader-border) solid; + border-top: 1px solid var(--vscode-multiDiffEditor-border); - border-left: 2px var(--vscode-editor-background) solid; -} -.monaco-component .multiDiffEntry.focused .header { - border-left: 2px var(--vscode-notebook-focusedCellBorder) solid; + border-bottom: 1px solid var(--vscode-sideBarSectionHeader-border); } .monaco-component .multiDiffEntry .header.shadow { @@ -59,3 +53,13 @@ .monaco-component .multiDiffEntry .header .actions { padding: 0 8px; } + +.monaco-component .multiDiffEntry .editorParent { + flex: 1; + display: flex; + flex-direction: column; +} + +.monaco-component .multiDiffEntry .editorContainer { + flex: 1; +} From c973ec767338f7846710cc2ee46e38b4504a044c Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 9 Jan 2024 23:40:39 +0100 Subject: [PATCH 0188/1897] SCM - fix border radius for checkout/sync actions (#202106) --- src/vs/workbench/contrib/scm/browser/media/scm.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index e5bc7bb4496..af1ddef0015 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -85,6 +85,7 @@ .scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .monaco-dropdown > .dropdown-label > .action-label { display: flex; align-items: center; + line-height: 16px; overflow: hidden; } From a308dde7c6b759cfb742fd164f4dacd160380e83 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 9 Jan 2024 16:51:19 -0800 Subject: [PATCH 0189/1897] wait to resume until breakpoint is set, align vocabulary --- .../browser/breakpointEditorContribution.ts | 4 +- .../contrib/debug/browser/breakpointsView.ts | 10 ++--- .../debug/browser/debugEditorActions.ts | 6 +-- .../contrib/debug/browser/debugService.ts | 8 ++-- .../contrib/debug/browser/debugSession.ts | 38 +++++++++++++++---- .../workbench/contrib/debug/common/debug.ts | 2 +- .../contrib/debug/common/debugModel.ts | 7 +--- 7 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index f74ceec1dfb..4667e3f3201 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -456,8 +456,8 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi () => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.LOG_MESSAGE)) )); actions.push(new Action( - 'addTriggerByBreakpoint', - nls.localize('addTriggerByBreakpoint', "Add Wait For Breakpoint.."), + 'addTriggeredBreakpoint', + nls.localize('addTriggeredBreakpoint', "Add Wait For Breakpoint.."), undefined, true, () => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.TRIGGER_POINT)) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 4c94fc5d41c..11e9a439150 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -1299,12 +1299,12 @@ export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: } // can change this when all breakpoint supports dependent breakpoint condition - let triggeredByBreakpoint: IBreakpoint | undefined; + let triggeringBreakpoint: IBreakpoint | undefined; if (breakpoint instanceof Breakpoint && breakpoint.triggeredBy) { - triggeredByBreakpoint = debugModel.getBreakpoints().find(bp => bp.getId() === breakpoint.triggeredBy); + triggeringBreakpoint = debugModel.getBreakpoints().find(bp => bp.getId() === breakpoint.triggeredBy); } - if (breakpoint.logMessage || breakpoint.condition || breakpoint.hitCondition || triggeredByBreakpoint) { + if (breakpoint.logMessage || breakpoint.condition || breakpoint.hitCondition || triggeringBreakpoint) { const messages: string[] = []; let icon = breakpoint.logMessage ? icons.logBreakpoint.regular : icons.conditionalBreakpoint.regular; if (!breakpoint.supported) { @@ -1321,8 +1321,8 @@ export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: if (breakpoint.hitCondition) { messages.push(localize('hitCount', "Hit Count: {0}", breakpoint.hitCondition)); } - if (triggeredByBreakpoint) { - messages.push(localize('triggeredBy', "Hit after breakpoint: {0}", `${labelService.getUriLabel(triggeredByBreakpoint.uri, { relative: true })}: ${triggeredByBreakpoint.lineNumber}`)); + if (triggeringBreakpoint) { + messages.push(localize('triggeredBy', "Hit after breakpoint: {0}", `${labelService.getUriLabel(triggeringBreakpoint.uri, { relative: true })}: ${triggeringBreakpoint.lineNumber}`)); } return { diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 5f25982d58d..2540e40a1fe 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -156,13 +156,13 @@ class TriggerByBreakpointAction extends EditorAction { constructor() { super({ id: 'editor.debug.action.triggerByBreakpoint', - label: nls.localize('triggerByBreakpointEditorAction', "Debug: Add Wait For Breakpoint..."), + label: nls.localize('triggerByBreakpointEditorAction', "Debug: Add Triggered Breakpoint..."), precondition: CONTEXT_DEBUGGERS_AVAILABLE, - alias: 'Debug: Wait For Breakpoint...', + alias: 'Debug: Triggered Breakpoint...', menuOpts: [ { menuId: MenuId.MenubarNewBreakpointMenu, - title: nls.localize({ key: 'miTriggerByBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Wait For Breakpoint..."), + title: nls.localize({ key: 'miTriggerByBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Triggered Breakpoint..."), group: '1_breakpoints', order: 4, when: CONTEXT_DEBUGGERS_AVAILABLE, diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 16989fb9747..b1c48f1cad5 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -1004,7 +1004,7 @@ export class DebugService implements IDebugService { this.model.setEnablement(breakpoint, enable); this.debugStorage.storeBreakpoints(this.model); if (breakpoint instanceof Breakpoint) { - await this.makeDependentBreakpointsMatchEnablement(enable, breakpoint); + await this.makeTriggeredBreakpointsMatchEnablement(enable, breakpoint); await this.sendBreakpoints(breakpoint.originalUri); } else if (breakpoint instanceof FunctionBreakpoint) { await this.sendFunctionBreakpoints(); @@ -1056,7 +1056,7 @@ export class DebugService implements IDebugService { const urisToClear = new Set(toRemove.map(bp => bp.originalUri.toString())); this.model.removeBreakpoints(toRemove); - this.unlinkDependentBreakpoints(breakpoints, toRemove).forEach(uri => urisToClear.add(uri.toString())); + this.unlinkTriggeredBreakpoints(breakpoints, toRemove).forEach(uri => urisToClear.add(uri.toString())); this.debugStorage.storeBreakpoints(this.model); await Promise.all([...urisToClear].map(uri => this.sendBreakpoints(URI.parse(uri)))); @@ -1160,7 +1160,7 @@ export class DebugService implements IDebugService { * breakpoints in `removedBreakpoints`. Returns the URIs of resources that * had their breakpoints changed in this way. */ - private unlinkDependentBreakpoints(allBreakpoints: readonly IBreakpoint[], removedBreakpoints: readonly IBreakpoint[]): uri[] { + private unlinkTriggeredBreakpoints(allBreakpoints: readonly IBreakpoint[], removedBreakpoints: readonly IBreakpoint[]): uri[] { const affectedUris: uri[] = []; for (const removed of removedBreakpoints) { for (const existing of allBreakpoints) { @@ -1174,7 +1174,7 @@ export class DebugService implements IDebugService { return affectedUris; } - private async makeDependentBreakpointsMatchEnablement(enable: boolean, breakpoint: Breakpoint) { + private async makeTriggeredBreakpointsMatchEnablement(enable: boolean, breakpoint: Breakpoint) { if (enable) { /** If the breakpoint is being enabled, also ensure its triggerer is enabled */ if (breakpoint.triggeredBy) { diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 6691af66b16..847075cce33 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -5,7 +5,7 @@ import * as aria from 'vs/base/browser/ui/aria/aria'; import { distinct } from 'vs/base/common/arrays'; -import { Queue, RunOnceScheduler } from 'vs/base/common/async'; +import { Queue, RunOnceScheduler, raceTimeout } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { canceled } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; @@ -42,6 +42,8 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { getActiveWindow } from 'vs/base/browser/dom'; import { mainWindow } from 'vs/base/browser/window'; +const TRIGGERED_BREAKPOINT_MAX_DELAY = 1500; + export class DebugSession implements IDebugSession, IDisposable { parentSession: IDebugSession | undefined; @@ -78,6 +80,12 @@ export class DebugSession implements IDebugSession, IDisposable { private _name: string | undefined; private readonly _onDidChangeName = new Emitter(); + /** + * Promise set while enabling dependent breakpoints to block the debugger + * from continuing from a stopped state. + */ + private _waitToResume?: Promise; + constructor( private id: string, private _configuration: { resolved: IConfig; unresolved: IConfig | undefined }, @@ -636,6 +644,7 @@ export class DebugSession implements IDebugSession, IDisposable { } async restartFrame(frameId: number, threadId: number): Promise { + await this.waitForTriggeredBreakpoints(); if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'restartFrame')); } @@ -651,6 +660,7 @@ export class DebugSession implements IDebugSession, IDisposable { } async next(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise { + await this.waitForTriggeredBreakpoints(); if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'next')); } @@ -660,6 +670,7 @@ export class DebugSession implements IDebugSession, IDisposable { } async stepIn(threadId: number, targetId?: number, granularity?: DebugProtocol.SteppingGranularity): Promise { + await this.waitForTriggeredBreakpoints(); if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepIn')); } @@ -669,6 +680,7 @@ export class DebugSession implements IDebugSession, IDisposable { } async stepOut(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise { + await this.waitForTriggeredBreakpoints(); if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepOut')); } @@ -678,6 +690,7 @@ export class DebugSession implements IDebugSession, IDisposable { } async stepBack(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise { + await this.waitForTriggeredBreakpoints(); if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepBack')); } @@ -687,6 +700,7 @@ export class DebugSession implements IDebugSession, IDisposable { } async continue(threadId: number): Promise { + await this.waitForTriggeredBreakpoints(); if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'continue')); } @@ -695,6 +709,7 @@ export class DebugSession implements IDebugSession, IDisposable { } async reverseContinue(threadId: number): Promise { + await this.waitForTriggeredBreakpoints(); if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'reverse continue')); } @@ -929,6 +944,17 @@ export class DebugSession implements IDebugSession, IDisposable { } } + private waitForTriggeredBreakpoints() { + if (!this._waitToResume) { + return; + } + + return raceTimeout( + this._waitToResume, + TRIGGERED_BREAKPOINT_MAX_DELAY + ); + } + private async fetchThreads(stoppedDetails?: IRawStoppedDetails): Promise { if (this.raw) { const response = await this.raw.threads(); @@ -1231,13 +1257,9 @@ export class DebugSession implements IDebugSession, IDisposable { // moment for breakpoints to set and we want to do our best to not miss // anything if (event.hitBreakpointIds) { - this.enableDependentBreakpoints(event.hitBreakpointIds).then(() => this.runInStatusQueue(event)); - } else { - this.runInStatusQueue(event); + this._waitToResume = this.enableDependentBreakpoints(event.hitBreakpointIds); } - } - private async runInStatusQueue(event: IRawStoppedDetails) { this.statusQueue.run( this.fetchThreads(event).then(() => event.threadId === undefined ? this.threadIds : [event.threadId]), async (threadId, token) => { @@ -1283,7 +1305,7 @@ export class DebugSession implements IDebugSession, IDisposable { await promises.topCallStack; if (!event.hitBreakpointIds) { // if hitBreakpointIds are present, this is handled earlier on - this.enableDependentBreakpoints(thread); + this._waitToResume = this.enableDependentBreakpoints(thread); } if (token.isCancellationRequested) { @@ -1329,7 +1351,7 @@ export class DebugSession implements IDebugSession, IDisposable { // check if the current breakpoints are dependencies, and if so collect and send the dependents to DA const urisToResend = new Set(); - this.model.getBreakpoints({ dependentOnly: true, enabledOnly: true }).forEach(bp => { + this.model.getBreakpoints({ triggeredOnly: true, enabledOnly: true }).forEach(bp => { breakpoints.forEach(cbp => { if (bp.enabled && bp.triggeredBy === cbp.getId()) { bp.setSessionDidTrigger(this.getId()); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 8f443c2d307..308b0ac9bc0 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -649,7 +649,7 @@ export interface IEvaluate { export interface IDebugModel extends ITreeElement { getSession(sessionId: string | undefined, includeInactive?: boolean): IDebugSession | undefined; getSessions(includeInactive?: boolean): IDebugSession[]; - getBreakpoints(filter?: { uri?: uri; originalUri?: uri; lineNumber?: number; column?: number; enabledOnly?: boolean; dependentOnly?: boolean }): ReadonlyArray; + getBreakpoints(filter?: { uri?: uri; originalUri?: uri; lineNumber?: number; column?: number; enabledOnly?: boolean; triggeredOnly?: boolean }): ReadonlyArray; areBreakpointsActivated(): boolean; getFunctionBreakpoints(): ReadonlyArray; getDataBreakpoints(): ReadonlyArray; diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 0f5ff8361ff..c4f5c811018 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -1390,7 +1390,7 @@ export class DebugModel extends Disposable implements IDebugModel { return { wholeCallStack, topCallStack: wholeCallStack }; } - getBreakpoints(filter?: { uri?: uri; originalUri?: uri; lineNumber?: number; column?: number; enabledOnly?: boolean; dependentOnly?: boolean; excludeDependent?: boolean }): IBreakpoint[] { + getBreakpoints(filter?: { uri?: uri; originalUri?: uri; lineNumber?: number; column?: number; enabledOnly?: boolean; triggeredOnly?: boolean }): IBreakpoint[] { if (filter) { const uriStr = filter.uri?.toString(); const originalUriStr = filter.originalUri?.toString(); @@ -1410,10 +1410,7 @@ export class DebugModel extends Disposable implements IDebugModel { if (filter.enabledOnly && (!this.breakpointsActivated || !bp.enabled)) { return false; } - if (filter.dependentOnly && bp.triggeredBy === undefined) { - return false; - } - if (filter.excludeDependent && bp.triggeredBy !== undefined) { + if (filter.triggeredOnly && bp.triggeredBy === undefined) { return false; } From 207197a5e5a795d80726f4380a57912674717cce Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 9 Jan 2024 16:54:55 -0800 Subject: [PATCH 0190/1897] one more place --- .../contrib/debug/browser/breakpointEditorContribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 4667e3f3201..67df7ad254b 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -457,7 +457,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi )); actions.push(new Action( 'addTriggeredBreakpoint', - nls.localize('addTriggeredBreakpoint', "Add Wait For Breakpoint.."), + nls.localize('addTriggeredBreakpoint', "Add Triggered Breakpoint.."), undefined, true, () => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.TRIGGER_POINT)) From 6bf996da2b916f4412e716f282ee9c6049d5d068 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 10 Jan 2024 12:02:20 +0530 Subject: [PATCH 0191/1897] adopt ensureNoDisposablesAreLeakedInTestSuite (#202121) #200091 adopt ensureNoDisposablesAreLeakedInTestSuite --- .eslintrc.json | 7 ------- .../test/common/configurationModels.test.ts | 11 +++++++++++ .../test/common/configurationRegistry.test.ts | 3 +++ .../userDataSync/test/common/extensionsMerge.test.ts | 3 +++ .../userDataSync/test/common/globalStateMerge.test.ts | 3 +++ .../userDataSync/test/common/settingsMerge.test.ts | 7 +++++++ .../test/common/userDataProfilesManifestMerge.test.ts | 3 +++ .../test/common/configurationModels.test.ts | 7 +++++++ 8 files changed, 37 insertions(+), 7 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 1235b1328e4..0bd15f4a019 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -214,8 +214,6 @@ "src/vs/editor/test/node/diffing/defaultLinesDiffComputer.test.ts", "src/vs/editor/test/node/diffing/fixtures.test.ts", "src/vs/platform/configuration/test/common/configuration.test.ts", - "src/vs/platform/configuration/test/common/configurationModels.test.ts", - "src/vs/platform/configuration/test/common/configurationRegistry.test.ts", "src/vs/platform/contextkey/test/common/contextkey.test.ts", "src/vs/platform/contextkey/test/common/parser.test.ts", "src/vs/platform/contextkey/test/common/scanner.test.ts", @@ -232,10 +230,6 @@ "src/vs/platform/registry/test/common/platform.test.ts", "src/vs/platform/remote/test/common/remoteHosts.test.ts", "src/vs/platform/telemetry/test/browser/1dsAppender.test.ts", - "src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts", - "src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts", - "src/vs/platform/userDataSync/test/common/settingsMerge.test.ts", - "src/vs/platform/userDataSync/test/common/userDataProfilesManifestMerge.test.ts", "src/vs/platform/workspace/test/common/workspace.test.ts", "src/vs/platform/workspaces/test/electron-main/workspaces.test.ts", "src/vs/server/test/node/serverConnectionToken.test.ts", @@ -262,7 +256,6 @@ "src/vs/workbench/contrib/tasks/test/common/problemMatcher.test.ts", "src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts", "src/vs/workbench/services/commands/test/common/commandService.test.ts", - "src/vs/workbench/services/configuration/test/common/configurationModels.test.ts", "src/vs/workbench/services/extensions/test/common/extensionDescriptionRegistry.test.ts", "src/vs/workbench/services/keybinding/test/browser/keybindingIO.test.ts", "src/vs/workbench/services/keybinding/test/node/fallbackKeyboardMapper.test.ts", diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index db22861ee4a..41bae16625f 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { join } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Configuration, ConfigurationChangeEvent, ConfigurationModel, ConfigurationModelParser, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationRegistry, Extensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -13,6 +14,8 @@ import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; suite('ConfigurationModelParser', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + suiteSetup(() => { Registry.as(Extensions.Configuration).registerConfiguration({ 'id': 'ConfigurationModelParserTest', @@ -88,6 +91,8 @@ suite('ConfigurationModelParser', () => { suite('ConfigurationModel', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('setValue for a key that has no sections and not defined', () => { const testObject = new ConfigurationModel({ 'a': { 'b': 1 } }, ['a.b']); @@ -403,6 +408,8 @@ suite('ConfigurationModel', () => { suite('CustomConfigurationModel', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('simple merge using models', () => { const base = new ConfigurationModelParser('base'); base.parse(JSON.stringify({ 'a': 1, 'b': 2 })); @@ -511,6 +518,8 @@ suite('CustomConfigurationModel', () => { suite('Configuration', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('Test inspect for overrideIdentifiers', () => { const defaultConfigurationModel = parseConfigurationModel({ '[l1]': { 'a': 1 }, '[l2]': { 'b': 1 } }); const userConfigurationModel = parseConfigurationModel({ '[l3]': { 'a': 2 } }); @@ -672,6 +681,8 @@ suite('Configuration', () => { suite('ConfigurationChangeEvent', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('changeEvent affecting keys with new configuration', () => { const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ diff --git a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts index 8e836674dae..9fc9e709322 100644 --- a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts +++ b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts @@ -4,11 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; suite('ConfigurationRegistry', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); test('configuration override', async () => { diff --git a/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts b/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts index 1abd8f73766..3436a31fb07 100644 --- a/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts @@ -4,11 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { merge } from 'vs/platform/userDataSync/common/extensionsMerge'; import { ILocalSyncExtension, ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync'; suite('ExtensionsMerge', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('merge returns local extension if remote does not exist', () => { const localExtensions = [ aLocalSyncExtension({ identifier: { id: 'a', uuid: 'a' } }), diff --git a/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts b/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts index be518dba470..3c61cd98aca 100644 --- a/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts @@ -4,11 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { NullLogService } from 'vs/platform/log/common/log'; import { merge } from 'vs/platform/userDataSync/common/globalStateMerge'; suite('GlobalStateMerge', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('merge when local and remote are same with one value and local is not synced yet', async () => { const local = { 'a': { version: 1, value: 'a' } }; const remote = { 'a': { version: 1, value: 'a' } }; diff --git a/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts index 9f603062d8a..274ac5ee696 100644 --- a/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { addSetting, merge, updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; import type { IConflictSetting } from 'vs/platform/userDataSync/common/userDataSync'; @@ -11,6 +12,8 @@ const formattingOptions = { eol: '\n', insertSpaces: false, tabSize: 4 }; suite('SettingsMerge - Merge', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('merge when local and remote are same with one entry', async () => { const localContent = stringify({ 'a': 1 }); const remoteContent = stringify({ 'a': 1 }); @@ -744,6 +747,8 @@ suite('SettingsMerge - Merge', () => { suite('SettingsMerge - Compute Remote Content', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('local content is returned when there are no ignored settings', async () => { const localContent = stringify({ 'a': 1, @@ -805,6 +810,8 @@ suite('SettingsMerge - Compute Remote Content', () => { suite('SettingsMerge - Add Setting', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('Insert after a setting without comments', () => { const sourceContent = ` diff --git a/src/vs/platform/userDataSync/test/common/userDataProfilesManifestMerge.test.ts b/src/vs/platform/userDataSync/test/common/userDataProfilesManifestMerge.test.ts index b0036c3869b..40cc6f3623d 100644 --- a/src/vs/platform/userDataSync/test/common/userDataProfilesManifestMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataProfilesManifestMerge.test.ts @@ -5,12 +5,15 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IUserDataProfile, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { merge } from 'vs/platform/userDataSync/common/userDataProfilesManifestMerge'; import { ISyncUserDataProfile } from 'vs/platform/userDataSync/common/userDataSync'; suite('UserDataProfilesManifestMerge', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('merge returns local profiles if remote does not exist', () => { const localProfiles: IUserDataProfile[] = [ toUserDataProfile('1', '1', URI.file('1'), URI.file('cache')), diff --git a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts index 72486f21370..b99556e5d30 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -11,9 +11,12 @@ import { ResourceMap } from 'vs/base/common/map'; import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { URI } from 'vs/base/common/uri'; import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('FolderSettingsModelParser', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + suiteSetup(() => { const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ @@ -125,6 +128,8 @@ suite('FolderSettingsModelParser', () => { suite('StandaloneConfigurationModelParser', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('parse tasks stand alone configuration model', () => { const testObject = new StandaloneConfigurationModelParser('tasks', 'tasks'); @@ -141,6 +146,8 @@ suite('StandaloneConfigurationModelParser', () => { suite('Workspace Configuration', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const defaultConfigurationModel = toConfigurationModel({ 'editor.lineNumbers': 'on', 'editor.fontSize': 12, From dd7a20db89f1a546c3e0fc587dff037fe3348fb8 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 10 Jan 2024 13:09:00 +0530 Subject: [PATCH 0192/1897] fix #142478 (#202126) --- .../api/browser/mainThreadComments.ts | 3 +- .../api/browser/mainThreadOutputService.ts | 2 +- .../api/browser/mainThreadTreeViews.ts | 3 +- .../browser/actions/layoutActions.ts | 3 +- .../browser/parts/panel/panelActions.ts | 3 +- .../browser/parts/titlebar/windowTitle.ts | 2 +- .../workbench/browser/parts/views/viewPane.ts | 3 +- .../browser/parts/views/viewPaneContainer.ts | 3 +- src/vs/workbench/common/views.ts | 26 -------------- .../browser/preview/bulkEdit.contribution.ts | 3 +- .../chat/browser/actions/chatActions.ts | 2 +- .../chat/browser/actions/chatMoveActions.ts | 2 +- .../contrib/chat/browser/chatWidget.ts | 2 +- .../actions/voiceChatActions.ts | 2 +- .../chat/test/common/chatService.test.ts | 2 +- .../comments/browser/commentsController.ts | 2 +- .../contrib/comments/browser/commentsView.ts | 3 +- .../contrib/debug/browser/debugCommands.ts | 3 +- .../debug/browser/debugConsoleQuickAccess.ts | 2 +- .../debug/browser/debugEditorActions.ts | 2 +- .../contrib/debug/browser/debugProgress.ts | 2 +- .../contrib/debug/browser/debugService.ts | 3 +- .../debug/browser/debugSessionPicker.ts | 2 +- .../contrib/debug/browser/debugTaskRunner.ts | 2 +- .../contrib/debug/browser/debugViewlet.ts | 3 +- .../workbench/contrib/debug/browser/repl.ts | 3 +- .../browser/editSessions.contribution.ts | 3 +- .../browser/extensions.contribution.ts | 3 +- .../contrib/files/browser/fileActions.ts | 3 +- .../contrib/files/browser/fileCommands.ts | 3 +- .../files/browser/views/explorerView.ts | 3 +- .../markers/browser/markers.contribution.ts | 3 +- .../output/browser/output.contribution.ts | 3 +- .../contrib/output/browser/outputServices.ts | 2 +- .../quickaccess/browser/viewQuickAccess.ts | 3 +- .../contrib/remote/browser/tunnelView.ts | 3 +- .../quickTextSearch/textSearchQuickAccess.ts | 2 +- .../search/browser/searchActionsBase.ts | 2 +- .../search/browser/searchActionsCopy.ts | 2 +- .../search/browser/searchActionsFind.ts | 3 +- .../search/browser/searchActionsNav.ts | 2 +- .../browser/searchActionsRemoveReplace.ts | 2 +- .../search/browser/searchActionsTopBar.ts | 2 +- .../contrib/search/browser/searchWidget.ts | 2 +- .../browser/searchEditor.contribution.ts | 2 +- .../browser/searchEditorActions.ts | 2 +- .../tasks/browser/abstractTaskService.ts | 3 +- .../tasks/browser/terminalTaskSystem.ts | 3 +- .../tasks/electron-sandbox/taskService.ts | 3 +- .../contrib/terminal/browser/terminalGroup.ts | 2 +- .../terminal/browser/terminalGroupService.ts | 3 +- .../terminal/browser/terminalInstance.ts | 3 +- .../terminal/browser/terminalService.ts | 3 +- .../testing/browser/testExplorerActions.ts | 3 +- .../testing/browser/testing.contribution.ts | 3 +- .../testing/browser/testingOutputPeek.ts | 3 +- .../browser/testingProgressUiService.ts | 2 +- .../testing/common/testCoverageService.ts | 2 +- .../timeline/common/timelineService.ts | 2 +- .../browser/userDataSyncTrigger.ts | 2 +- .../webviewView/browser/webviewViewPane.ts | 3 +- .../browser/gettingStartedService.ts | 2 +- .../progress/browser/progressService.ts | 3 +- .../userDataProfileImportExportService.ts | 3 +- .../browser/userDataSyncWorkbenchService.ts | 3 +- .../views/browser}/viewsService.ts | 3 +- .../services/views/common/viewsService.ts | 35 +++++++++++++++++++ .../test/browser/workbenchTestServices.ts | 3 +- src/vs/workbench/workbench.common.main.ts | 2 +- 69 files changed, 139 insertions(+), 93 deletions(-) rename src/vs/workbench/{browser/parts/views => services/views/browser}/viewsService.ts (99%) create mode 100644 src/vs/workbench/services/views/common/viewsService.ts diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 7685218a12d..98a479f4b9a 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -16,7 +16,7 @@ import { ICommentController, ICommentInfo, ICommentService, INotebookCommentInfo import { CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsView'; import { CommentProviderFeatures, ExtHostCommentsShape, ExtHostContext, MainContext, MainThreadCommentsShape, CommentThreadChanges } from '../common/extHost.protocol'; import { COMMENTS_VIEW_ID, COMMENTS_VIEW_STORAGE_ID, COMMENTS_VIEW_TITLE } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; -import { ViewContainer, IViewContainersRegistry, Extensions as ViewExtensions, ViewContainerLocation, IViewsRegistry, IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewExtensions, ViewContainerLocation, IViewsRegistry, IViewDescriptorService } from 'vs/workbench/common/views'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { Codicon } from 'vs/base/common/codicons'; @@ -25,6 +25,7 @@ import { localize } from 'vs/nls'; import { MarshalledId } from 'vs/base/common/marshallingIds'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { Schemas } from 'vs/base/common/network'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; export class MainThreadCommentThread implements languages.CommentThread { private _input?: languages.CommentInput; diff --git a/src/vs/workbench/api/browser/mainThreadOutputService.ts b/src/vs/workbench/api/browser/mainThreadOutputService.ts index 4f5c2c9a984..077f0958325 100644 --- a/src/vs/workbench/api/browser/mainThreadOutputService.ts +++ b/src/vs/workbench/api/browser/mainThreadOutputService.ts @@ -10,7 +10,7 @@ import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/ext import { UriComponents, URI } from 'vs/base/common/uri'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { isNumber } from 'vs/base/common/types'; @extHostNamedCustomer(MainContext.MainThreadOutputService) diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 270a4cc9f3d..eefa3c78a7f 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, CheckboxUpdate } from 'vs/workbench/api/common/extHost.protocol'; -import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeView, IViewsRegistry, ITreeViewDescriptor, IRevealOptions, Extensions, ResolvableTreeItem, ITreeViewDragAndDropController, IViewBadge, NoTreeViewError } from 'vs/workbench/common/views'; +import { ITreeViewDataProvider, ITreeItem, ITreeView, IViewsRegistry, ITreeViewDescriptor, IRevealOptions, Extensions, ResolvableTreeItem, ITreeViewDragAndDropController, IViewBadge, NoTreeViewError } from 'vs/workbench/common/views'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { distinct } from 'vs/base/common/arrays'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -19,6 +19,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { DataTransferFileCache } from 'vs/workbench/api/common/shared/dataTransferCache'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; @extHostNamedCustomer(MainContext.MainThreadTreeViews) export class MainThreadTreeViews extends Disposable implements MainThreadTreeViewsShape { diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 4a24aee2acb..0727a5a0889 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -14,7 +14,8 @@ import { isWindows, isLinux, isWeb, isMacintosh, isNative } from 'vs/base/common import { IsMacNativeContext } from 'vs/platform/contextkey/common/contextkeys'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IViewDescriptorService, IViewsService, ViewContainerLocation, IViewDescriptor, ViewContainerLocationToString } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainerLocation, IViewDescriptor, ViewContainerLocationToString } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { QuickPickItem, IQuickInputService, IQuickPickItem, IQuickPickSeparator, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index f846bbefe78..a872e4a89d4 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -14,7 +14,8 @@ import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/com import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { ViewContainerLocationToString, ViewContainerLocation, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { ViewContainerLocationToString, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ICommandActionTitle } from 'vs/platform/action/common/action'; diff --git a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts index d329d6f71ec..fffceb67baf 100644 --- a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts +++ b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts @@ -24,7 +24,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { Schemas } from 'vs/base/common/network'; import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtualWorkspace'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; const enum WindowSettingNames { diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index 9124a2a46b9..aa66323770b 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -20,7 +20,8 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ThemeIcon } from 'vs/base/common/themables'; import { IPaneOptions, Pane, IPaneStyles } from 'vs/base/browser/ui/splitview/paneview'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Extensions as ViewContainerExtensions, IView, IViewDescriptorService, ViewContainerLocation, IViewsRegistry, IViewContentDescriptor, defaultViewIcon, IViewsService, ViewContainerLocationToString } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, IView, IViewDescriptorService, ViewContainerLocation, IViewsRegistry, IViewContentDescriptor, defaultViewIcon, ViewContainerLocationToString } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { assertIsDefined } from 'vs/base/common/types'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 67b09829a33..44b825998ab 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -35,7 +35,8 @@ import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { Component } from 'vs/workbench/common/component'; import { PANEL_SECTION_BORDER, PANEL_SECTION_DRAG_AND_DROP_BACKGROUND, PANEL_SECTION_HEADER_BACKGROUND, PANEL_SECTION_HEADER_BORDER, PANEL_SECTION_HEADER_FOREGROUND, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, SIDE_BAR_SECTION_HEADER_FOREGROUND } from 'vs/workbench/common/theme'; -import { IAddedViewDescriptorRef, ICustomViewDescriptor, IView, IViewContainerModel, IViewDescriptor, IViewDescriptorRef, IViewDescriptorService, IViewPaneContainer, IViewsService, ViewContainer, ViewContainerLocation, ViewContainerLocationToString, ViewVisibilityState } from 'vs/workbench/common/views'; +import { IAddedViewDescriptorRef, ICustomViewDescriptor, IView, IViewContainerModel, IViewDescriptor, IViewDescriptorRef, IViewDescriptorService, IViewPaneContainer, ViewContainer, ViewContainerLocation, ViewContainerLocationToString, ViewVisibilityState } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { FocusedViewContext } from 'vs/workbench/common/contextkeys'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 9c886825943..7288219657e 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -19,7 +19,6 @@ import { flatten } from 'vs/base/common/arrays'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IProgressIndicator } from 'vs/platform/progress/common/progress'; import Severity from 'vs/base/common/severity'; -import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { mixin } from 'vs/base/common/objects'; @@ -567,31 +566,6 @@ export interface IView { getProgressIndicator(): IProgressIndicator | undefined; } -export const IViewsService = createDecorator('viewsService'); -export interface IViewsService { - - readonly _serviceBrand: undefined; - - // View Container APIs - readonly onDidChangeViewContainerVisibility: Event<{ id: string; visible: boolean; location: ViewContainerLocation }>; - isViewContainerVisible(id: string): boolean; - openViewContainer(id: string, focus?: boolean): Promise; - closeViewContainer(id: string): void; - getVisibleViewContainer(location: ViewContainerLocation): ViewContainer | null; - getActiveViewPaneContainerWithId(viewContainerId: string): IViewPaneContainer | null; - getFocusedViewName(): string; - - // View APIs - readonly onDidChangeViewVisibility: Event<{ id: string; visible: boolean }>; - readonly onDidChangeFocusedView: Event; - isViewVisible(id: string): boolean; - openView(id: string, focus?: boolean): Promise; - closeView(id: string): void; - getActiveViewWithId(id: string): T | null; - getViewWithId(id: string): T | null; - getViewProgressIndicator(id: string): IProgressIndicator | undefined; -} - export const IViewDescriptorService = createDecorator('viewDescriptorService'); export enum ViewVisibilityState { diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts index a00a3f22ef9..070f87e2feb 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts @@ -8,7 +8,8 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IBulkEditService, ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { BulkEditPane } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane'; -import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry, IViewsService } from 'vs/workbench/common/views'; +import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { FocusedViewContext } from 'vs/workbench/common/contextkeys'; import { localize, localize2 } from 'vs/nls'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 70758d60fa8..f38834e7e49 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -19,7 +19,7 @@ import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/commo import { Registry } from 'vs/platform/registry/common/platform'; import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { runAccessibilityHelpAction } from 'vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts index 834c5eca7e1..b2ab5388bab 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts @@ -9,7 +9,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 443afc6be81..b4c02dc0b01 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -21,7 +21,7 @@ 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 { ILogService } from 'vs/platform/log/common/log'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ChatTreeItem, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatWidget, IChatWidgetService, IChatWidgetViewContext, IChatWidgetViewOptions } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { ChatAccessibilityProvider, ChatListDelegate, ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index cce939e31a6..2a684fff64b 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -25,7 +25,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode } from 'vs/base/common/keyCodes'; 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 aa805cbe725..a69148ce360 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -20,7 +20,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ChatAgentService, IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index d1c3f79d3c7..66ed2edee17 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -27,7 +27,7 @@ import { isMouseUpEventDragFromMouseDown, parseMouseDownInfoFromEvent, ReviewZon import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { COMMENTS_VIEW_ID } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { COMMENTS_SECTION, ICommentsConfiguration } from 'vs/workbench/contrib/comments/common/commentsConfiguration'; diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index 6ac8e09a5d2..598f2501d3f 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -18,7 +18,8 @@ import { textLinkForeground, textLinkActiveForeground, focusBorder, textPreforma import { ResourceLabels } from 'vs/workbench/browser/labels'; import { CommentsList, COMMENTS_VIEW_ID, COMMENTS_VIEW_TITLE, Filter } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; import { IViewPaneOptions, ViewAction, FilterViewPane } from 'vs/workbench/browser/parts/views/viewPane'; -import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 0a74560e1b4..30272d26dcc 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -26,7 +26,8 @@ import { ITextResourcePropertiesService } from 'vs/editor/common/services/textRe import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { deepClone } from 'vs/base/common/objects'; import { isWeb, isWindows } from 'vs/base/common/platform'; import { saveAllBeforeDebugStart } from 'vs/workbench/contrib/debug/common/debugUtils'; diff --git a/src/vs/workbench/contrib/debug/browser/debugConsoleQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugConsoleQuickAccess.ts index 5479a5a393c..68936e1018f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConsoleQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConsoleQuickAccess.ts @@ -9,7 +9,7 @@ import { localize } from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { FastAndSlowPicks, IPickerQuickAccessItem, PickerQuickAccessProvider, Picks } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, SELECT_AND_START_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { IDebugService, IDebugSession, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 2540e40a1fe..c3dea1cac40 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -22,7 +22,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { PanelFocusContext } from 'vs/workbench/common/contextkeys'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { openBreakpointSource } from 'vs/workbench/contrib/debug/browser/breakpointsView'; import { DisassemblyView } from 'vs/workbench/contrib/debug/browser/disassemblyView'; import { BREAKPOINT_EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE, CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_EXCEPTION_WIDGET_VISIBLE, CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE, CONTEXT_IN_DEBUG_MODE, CONTEXT_LANGUAGE_SUPPORTS_DISASSEMBLE_REQUEST, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IDebugConfiguration, IDebugEditorContribution, IDebugService, REPL_VIEW_ID, WATCH_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; diff --git a/src/vs/workbench/contrib/debug/browser/debugProgress.ts b/src/vs/workbench/contrib/debug/browser/debugProgress.ts index 6bda803c6db..acf23c97080 100644 --- a/src/vs/workbench/contrib/debug/browser/debugProgress.ts +++ b/src/vs/workbench/contrib/debug/browser/debugProgress.ts @@ -8,7 +8,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IDebugService, VIEWLET_ID, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { NotificationPriority } from 'vs/platform/notification/common/notification'; export class DebugProgressContribution implements IWorkbenchContribution { diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index b1c48f1cad5..805156e1210 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -33,7 +33,8 @@ import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/p import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { EditorsOrder } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { AdapterManager } from 'vs/workbench/contrib/debug/browser/debugAdapterManager'; import { DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { ConfigurationManager } from 'vs/workbench/contrib/debug/browser/debugConfigurationManager'; diff --git a/src/vs/workbench/contrib/debug/browser/debugSessionPicker.ts b/src/vs/workbench/contrib/debug/browser/debugSessionPicker.ts index 9e3b5b7efff..74ca56b6439 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSessionPicker.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSessionPicker.ts @@ -10,7 +10,7 @@ import { IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IPickerDebugItem } from 'vs/workbench/contrib/debug/common/loadedScriptsPicker'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ICommandService } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts index cbd1f4a9613..15de8bc4e40 100644 --- a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts +++ b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts @@ -14,7 +14,7 @@ import { ITaskEvent, TaskEventKind, ITaskIdentifier, Task } from 'vs/workbench/c import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; 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'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { createErrorWithActions } from 'vs/base/common/errorMessage'; import { Action } from 'vs/base/common/actions'; diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 7db229d65c2..4a8f0e2dc99 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -23,7 +23,8 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { ViewPaneContainer, ViewsSubMenu } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { WorkbenchStateContext } from 'vs/workbench/common/contextkeys'; -import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { FocusSessionActionViewItem, StartDebugActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; import { DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL, DEBUG_START_COMMAND_ID, DEBUG_START_LABEL, DISCONNECT_ID, FOCUS_SESSION_ID, SELECT_AND_START_ID, STOP_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { debugConfigure } from 'vs/workbench/contrib/debug/browser/debugIcons'; diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 4f35acf8ab1..0c50dcaed7d 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -57,7 +57,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { editorForeground, resolveColorValue } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { FilterViewPane, IViewPaneOptions, ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; -import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; import { debugConsoleClearAll, debugConsoleEvaluationPrompt } from 'vs/workbench/contrib/debug/browser/debugIcons'; diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index 88202895d9e..6dd73fa5326 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -39,7 +39,8 @@ import { Schemas } from 'vs/base/common/network'; import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { IExtensionService, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { EditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessionsLogService'; -import { IViewContainersRegistry, Extensions as ViewExtensions, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views'; +import { IViewContainersRegistry, Extensions as ViewExtensions, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index f802e17f7c3..bbcba568c68 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -32,7 +32,8 @@ import { ExtensionActivationProgress } from 'vs/workbench/contrib/extensions/bro import { onUnexpectedError } from 'vs/base/common/errors'; import { ExtensionDependencyChecker } from 'vs/workbench/contrib/extensions/browser/extensionsDependencyChecker'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions, IViewsService } from 'vs/workbench/common/views'; +import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 5ac909eb123..fba86d0dbbd 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -43,7 +43,8 @@ import { timeout } from 'vs/base/common/async'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; -import { IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { trim, rtrim } from 'vs/base/common/strings'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService'; diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index d991754b47f..434d8623aca 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -46,7 +46,8 @@ import { EditorOpenSource, EditorResolution } from 'vs/platform/editor/common/ed import { hash } from 'vs/base/common/hash'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; -import { IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { OPEN_TO_SIDE_COMMAND_ID, COMPARE_WITH_SAVED_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, COMPARE_SELECTED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, COPY_PATH_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_WITH_EXPLORER_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_AS_COMMAND_ID, SAVE_ALL_COMMAND_ID, SAVE_ALL_IN_GROUP_COMMAND_ID, SAVE_FILES_COMMAND_ID, REVERT_FILE_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, PREVIOUS_COMPRESSED_FOLDER, NEXT_COMPRESSED_FOLDER, FIRST_COMPRESSED_FOLDER, LAST_COMPRESSED_FOLDER, NEW_UNTITLED_FILE_COMMAND_ID, NEW_UNTITLED_FILE_LABEL, NEW_FILE_COMMAND_ID } from './fileConstants'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { RemoveRootFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index ade3a51da93..739d6d19ce2 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -43,7 +43,8 @@ import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/ import { IDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index dffcbdff240..cf53ba18848 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -23,7 +23,8 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/workbench/services/statusbar/browser/statusbar'; import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers'; -import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry, IViewsService } from 'vs/workbench/common/views'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { getVisbileViewContextKey, FocusedViewContext } from 'vs/workbench/common/contextkeys'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index eaad7257dae..5d31bf65850 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -16,7 +16,8 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ViewContainer, IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions, IViewsRegistry, IViewsService } from 'vs/workbench/common/views'; +import { ViewContainer, IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions, IViewsRegistry } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IQuickPickItem, IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index 27f04dabcc0..26fed6ffc4e 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -16,7 +16,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { ILogService } from 'vs/platform/log/common/log'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IOutputChannelModel } from 'vs/workbench/contrib/output/common/outputChannelModel'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { OutputViewPane } from 'vs/workbench/contrib/output/browser/outputView'; import { IOutputChannelModelService } from 'vs/workbench/contrib/output/common/outputChannelModelService'; import { ILanguageService } from 'vs/editor/common/languages/language'; diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index 77bc27042a5..e234858bf6f 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -6,7 +6,8 @@ import { localize, localize2 } from 'vs/nls'; import { IQuickPickSeparator, IQuickInputService, ItemActivation } from 'vs/platform/quickinput/common/quickInput'; import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/browser/pickerQuickAccess'; -import { IViewDescriptorService, IViewsService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IOutputService } from 'vs/workbench/services/output/common/output'; import { ITerminalGroupService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index f8b17eacd62..b96a7ee7e78 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -6,7 +6,8 @@ import 'vs/css!./media/tunnelView'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; -import { IViewDescriptor, IEditableData, IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewDescriptor, IEditableData, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IContextKeyService, IContextKey, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index 6c141676972..1f248e45437 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -20,7 +20,7 @@ import { DefaultQuickAccessFilterValue, IQuickAccessProviderRunOptions } from 'v import { IKeyMods, IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { searchDetailsIcon, searchOpenInFileIcon, searchActivityBarIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import { FileMatch, Match, RenderableMatch, SearchModel, searchComparer } from 'vs/workbench/contrib/search/browser/searchModel'; import { SearchView, getEditorSelectionFromMatch } from 'vs/workbench/contrib/search/browser/searchView'; diff --git a/src/vs/workbench/contrib/search/browser/searchActionsBase.ts b/src/vs/workbench/contrib/search/browser/searchActionsBase.ts index 537f3f9614d..6fb0525ecf4 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsBase.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsBase.ts @@ -7,7 +7,7 @@ import * as DOM from 'vs/base/browser/dom'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import * as nls from 'vs/nls'; import { WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import { FileMatch, FolderMatch, Match, RenderableMatch, searchComparer } from 'vs/workbench/contrib/search/browser/searchModel'; import { ISearchConfigurationProperties, VIEW_ID } from 'vs/workbench/services/search/common/search'; diff --git a/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts b/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts index 28d437a82f8..c48d4ce4e63 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { FileMatch, FolderMatch, FolderMatchWithResource, Match, RenderableMatch, searchMatchComparer } from 'vs/workbench/contrib/search/browser/searchModel'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; diff --git a/src/vs/workbench/contrib/search/browser/searchActionsFind.ts b/src/vs/workbench/contrib/search/browser/searchActionsFind.ts index ef0c7e3ac22..a4fe70aa949 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsFind.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsFind.ts @@ -8,7 +8,8 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IListService, WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; -import { IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; import { FileMatch, FolderMatchWithResource, Match, RenderableMatch } from 'vs/workbench/contrib/search/browser/searchModel'; diff --git a/src/vs/workbench/contrib/search/browser/searchActionsNav.ts b/src/vs/workbench/contrib/search/browser/searchActionsNav.ts index b90ae42c06f..c9ec0a91ae0 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsNav.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsNav.ts @@ -9,7 +9,7 @@ import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; import { FileMatchOrMatch, FolderMatch, RenderableMatch } from 'vs/workbench/contrib/search/browser/searchModel'; diff --git a/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts b/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts index 82c0a9b1a8e..d116782fd0f 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { getSelectionKeyboardEvent, WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { searchRemoveIcon, searchReplaceIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; diff --git a/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts b/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts index 5db628b4174..f7e0dae7b12 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { searchClearIcon, searchCollapseAllIcon, searchExpandAllIcon, searchRefreshIcon, searchShowAsList, searchShowAsTree, searchStopIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { ISearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 6062d87937d..0b38cd404bb 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -31,7 +31,7 @@ import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { isMacintosh } from 'vs/base/common/platform'; import { IToggleStyles, Toggle } from 'vs/base/browser/ui/toggle/toggle'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { searchReplaceAllIcon, searchHideReplaceIcon, searchShowContextIcon, searchShowReplaceIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import { ToggleSearchEditorContextLinesCommandId } from 'vs/workbench/contrib/searchEditor/browser/constants'; import { showHistoryKeybindingHint } from 'vs/platform/history/browser/historyWidgetKeybindingHint'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 0317a3588ef..db9f8621b93 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -22,7 +22,7 @@ import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/ import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IEditorSerializer, IEditorFactoryRegistry, EditorExtensions, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActionsBase'; import { searchNewEditorIcon, searchRefreshIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import * as SearchConstants from 'vs/workbench/contrib/search/common/constants'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index 787d4cd991e..5ab5da3f9e6 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -14,7 +14,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { EditorsOrder } from 'vs/workbench/common/editor'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActionsBase'; import { SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 64496475b1a..d96c5abe1e6 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -74,7 +74,8 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { VirtualWorkspaceContext } from 'vs/workbench/common/contextkeys'; import { EditorResourceAccessor, SaveReason } from 'vs/workbench/common/editor'; -import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { configureTaskIcon, isWorkspaceFolder, ITaskQuickPickEntry, QUICKOPEN_DETAIL_CONFIG, QUICKOPEN_SKIP_CONFIG, TaskQuickPick } from 'vs/workbench/contrib/tasks/browser/taskQuickPick'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ILifecycleService, ShutdownReason, StartupKind } from 'vs/workbench/services/lifecycle/common/lifecycle'; diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 24a03eb1660..a35f507a53f 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -34,7 +34,8 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IShellLaunchConfig, WaitOnExitValue } from 'vs/platform/terminal/common/terminal'; import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings'; -import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { TaskTerminalStatus } from 'vs/workbench/contrib/tasks/browser/taskTerminalStatus'; import { ProblemCollectorEventKind, ProblemHandlingStrategy, StartStopProblemCollector, WatchingProblemCollector } from 'vs/workbench/contrib/tasks/common/problemCollectors'; import { GroupKind } from 'vs/workbench/contrib/tasks/common/taskConfiguration'; diff --git a/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts b/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts index 34fb9cc4cd3..5eeee32c264 100644 --- a/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts +++ b/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts @@ -29,7 +29,8 @@ import { IProgressService } from 'vs/platform/progress/common/progress'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IOutputService } from 'vs/workbench/services/output/common/output'; import { ITerminalGroupService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index e0b14265066..d50bd624688 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -13,8 +13,8 @@ import { ITerminalInstance, Direction, ITerminalGroup, ITerminalService, ITermin import { ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; import { IShellLaunchConfig, ITerminalTabLayoutInfoById, TerminalLocation } from 'vs/platform/terminal/common/terminal'; import { TerminalStatus } from 'vs/workbench/contrib/terminal/browser/terminalStatusList'; -import { getPartByLocation } from 'vs/workbench/browser/parts/views/viewsService'; import { getWindow } from 'vs/base/browser/dom'; +import { getPartByLocation } from 'vs/workbench/services/views/browser/viewsService'; const enum Constants { /** diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts index d5e1af67916..b177b4fad02 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts @@ -11,7 +11,8 @@ import { URI } from 'vs/base/common/uri'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IShellLaunchConfig } from 'vs/platform/terminal/common/terminal'; -import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ITerminalGroup, ITerminalGroupService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalGroup } from 'vs/workbench/contrib/terminal/browser/terminalGroup'; import { getInstanceFromResource } from 'vs/workbench/contrib/terminal/browser/terminalUri'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index bec7a9a3502..5ac44dc2718 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -55,7 +55,8 @@ import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeServic import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IRequestAddInstanceToGroupEvent, ITerminalContribution, ITerminalInstance, IXtermColorProvider, TerminalDataTransfers } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalLaunchHelpAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index bff4a07ecab..f78008e14db 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -28,7 +28,8 @@ import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { ThemeIcon } from 'vs/base/common/themables'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { VirtualWorkspaceContext } from 'vs/workbench/common/contextkeys'; -import { IEditableData, IViewsService } from 'vs/workbench/common/views'; +import { IEditableData } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ICreateTerminalOptions, IDetachedTerminalInstance, IDetachedXTermOptions, IRequestAddInstanceToGroupEvent, ITerminalConfigHelper, ITerminalEditorService, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceHost, ITerminalInstanceService, ITerminalLocationOptions, ITerminalService, ITerminalServiceNativeDelegate, TerminalConnectionState, TerminalEditorLocation } from 'vs/workbench/contrib/terminal/browser/terminal'; import { getCwdForSplit } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index 1716ae12a8a..5a886c371ca 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -29,7 +29,8 @@ import { widgetClose } from 'vs/platform/theme/common/iconRegistry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { FocusedViewContext } from 'vs/workbench/common/contextkeys'; -import { IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { VIEWLET_ID as EXTENSIONS_VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions'; import { TestExplorerTreeElement, TestItemTreeElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/index'; import * as icons from 'vs/workbench/contrib/testing/browser/icons'; diff --git a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts index f28190a627d..ff0608bddd5 100644 --- a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts +++ b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts @@ -18,7 +18,8 @@ import { IProgressService } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { IViewContainersRegistry, IViewsRegistry, IViewsService, Extensions as ViewContainerExtensions, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewContainersRegistry, IViewsRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { REVEAL_IN_EXPLORER_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileConstants'; import { testingResultsIcon, testingViewIcon } from 'vs/workbench/contrib/testing/browser/icons'; import { TestingDecorationService, TestingDecorations } from 'vs/workbench/contrib/testing/browser/testingDecorations'; diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 776340abfca..ffe03ea5df3 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -78,7 +78,8 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { DetachedProcessInfo } from 'vs/workbench/contrib/terminal/browser/detachedTerminal'; import { IDetachedTerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { getXtermScaledDimensions } from 'vs/workbench/contrib/terminal/browser/xterm/xtermTerminal'; diff --git a/src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts b/src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts index f916c0df5ab..fbabc8903f8 100644 --- a/src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts +++ b/src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts @@ -6,7 +6,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { AutoOpenTesting, getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration'; import { Testing } from 'vs/workbench/contrib/testing/common/constants'; import { isFailedState } from 'vs/workbench/contrib/testing/common/testingStates'; diff --git a/src/vs/workbench/contrib/testing/common/testCoverageService.ts b/src/vs/workbench/contrib/testing/common/testCoverageService.ts index 9e5b175eb7e..e9278f7739f 100644 --- a/src/vs/workbench/contrib/testing/common/testCoverageService.ts +++ b/src/vs/workbench/contrib/testing/common/testCoverageService.ts @@ -10,7 +10,7 @@ import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { Testing } from 'vs/workbench/contrib/testing/common/constants'; import { TestCoverage } from 'vs/workbench/contrib/testing/common/testCoverage'; import { ITestRunTaskResults } from 'vs/workbench/contrib/testing/common/testResult'; diff --git a/src/vs/workbench/contrib/timeline/common/timelineService.ts b/src/vs/workbench/contrib/timeline/common/timelineService.ts index 190dfa2501c..5cc26501d0d 100644 --- a/src/vs/workbench/contrib/timeline/common/timelineService.ts +++ b/src/vs/workbench/contrib/timeline/common/timelineService.ts @@ -9,7 +9,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { ITimelineService, TimelineChangeEvent, TimelineOptions, TimelineProvidersChangeEvent, TimelineProvider, TimelinePaneId } from './timeline'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts index 2fd68ddc29c..89a1bbbbd77 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts @@ -11,7 +11,7 @@ import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/use import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts index b56367f4cb6..e217b68758b 100644 --- a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts +++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts @@ -22,7 +22,8 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ViewPane, ViewPaneShowActions } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { Memento, MementoObject } from 'vs/workbench/common/memento'; -import { IViewBadge, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { IViewBadge, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ExtensionKeyedWebviewOriginStore, IOverlayWebview, IWebviewService, WebviewContentPurpose } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewWindowDragMonitor } from 'vs/workbench/contrib/webview/browser/webviewWindowDragMonitor'; import { IWebviewViewService, WebviewView } from 'vs/workbench/contrib/webviewView/browser/webviewViewService'; diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts index f0c08e1b826..b6683748bc8 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts @@ -27,7 +27,7 @@ import { walkthroughsExtensionPoint } from 'vs/workbench/contrib/welcomeGettingS import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { dirname } from 'vs/base/common/path'; import { coalesce, flatten } from 'vs/base/common/arrays'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { localize } from 'vs/nls'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { checkGlobFileExists } from 'vs/workbench/services/extensions/common/workspaceContains'; diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 4cdef572cc0..440aaa1e544 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -21,7 +21,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventHelper } from 'vs/base/browser/dom'; import { parseLinkedText } from 'vs/base/common/linkedText'; -import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { stripIcons } from 'vs/base/common/iconLabels'; import { defaultButtonStyles, defaultCheckboxStyles, defaultDialogStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index d16d9793244..7850000eec8 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -17,7 +17,8 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; -import { Extensions, ITreeItem, ITreeViewDataProvider, ITreeViewDescriptor, IViewContainersRegistry, IViewDescriptorService, IViewsRegistry, IViewsService, TreeItemCollapsibleState, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; +import { Extensions, ITreeItem, ITreeViewDataProvider, ITreeViewDescriptor, IViewContainersRegistry, IViewDescriptorService, IViewsRegistry, TreeItemCollapsibleState, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IUserDataProfile, IUserDataProfileOptions, IUserDataProfilesService, ProfileResourceType, UseDefaultProfileFlags, isUserDataProfile, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index befb6177c14..8b833a0ce2a 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -23,7 +23,8 @@ import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/d import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { URI } from 'vs/base/common/uri'; -import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/workbench/browser/parts/views/viewsService.ts b/src/vs/workbench/services/views/browser/viewsService.ts similarity index 99% rename from src/vs/workbench/browser/parts/views/viewsService.ts rename to src/vs/workbench/services/views/browser/viewsService.ts index 0d7354463d2..8e7a0e6b753 100644 --- a/src/vs/workbench/browser/parts/views/viewsService.ts +++ b/src/vs/workbench/services/views/browser/viewsService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IViewDescriptorService, ViewContainer, IViewDescriptor, IView, ViewContainerLocation, IViewsService, IViewPaneContainer } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainer, IViewDescriptor, IView, ViewContainerLocation, IViewPaneContainer } from 'vs/workbench/common/views'; import { FocusedViewContext, getVisbileViewContextKey } from 'vs/workbench/common/contextkeys'; import { Registry } from 'vs/platform/registry/common/platform'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -33,6 +33,7 @@ import { FilterViewPaneContainer } from 'vs/workbench/browser/parts/views/viewsV import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ICommandActionTitle, ILocalizedString } from 'vs/platform/action/common/action'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; export class ViewsService extends Disposable implements IViewsService { diff --git a/src/vs/workbench/services/views/common/viewsService.ts b/src/vs/workbench/services/views/common/viewsService.ts new file mode 100644 index 00000000000..de2cab29744 --- /dev/null +++ b/src/vs/workbench/services/views/common/viewsService.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IProgressIndicator } from 'vs/platform/progress/common/progress'; +import { IPaneComposite } from 'vs/workbench/common/panecomposite'; +import { IView, IViewPaneContainer, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; + +export const IViewsService = createDecorator('viewsService'); +export interface IViewsService { + + readonly _serviceBrand: undefined; + + // View Container APIs + readonly onDidChangeViewContainerVisibility: Event<{ id: string; visible: boolean; location: ViewContainerLocation }>; + isViewContainerVisible(id: string): boolean; + openViewContainer(id: string, focus?: boolean): Promise; + closeViewContainer(id: string): void; + getVisibleViewContainer(location: ViewContainerLocation): ViewContainer | null; + getActiveViewPaneContainerWithId(viewContainerId: string): IViewPaneContainer | null; + getFocusedViewName(): string; + + // View APIs + readonly onDidChangeViewVisibility: Event<{ id: string; visible: boolean }>; + readonly onDidChangeFocusedView: Event; + isViewVisible(id: string): boolean; + openView(id: string, focus?: boolean): Promise; + closeView(id: string): void; + getActiveViewWithId(id: string): T | null; + getViewWithId(id: string): T | null; + getViewProgressIndicator(id: string): IProgressIndicator | undefined; +} diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index e3980962458..bc429501b51 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -99,7 +99,8 @@ import { QuickInputService } from 'vs/workbench/services/quickinput/browser/quic import { IListService } from 'vs/platform/list/browser/listService'; import { win32, posix } from 'vs/base/common/path'; import { TestContextService, TestStorageService, TestTextResourcePropertiesService, TestExtensionService, TestProductService, createFileStat, TestLoggerService, TestWorkspaceTrustManagementService, TestWorkspaceTrustRequestService, TestMarkerService } from 'vs/workbench/test/common/workbenchTestServices'; -import { IViewsService, IView, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IView, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 1694e0f75e7..bcad16fc802 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -46,7 +46,6 @@ import 'vs/workbench/browser/parts/editor/editorParts'; import 'vs/workbench/browser/parts/paneCompositePartService'; import 'vs/workbench/browser/parts/banner/bannerPart'; import 'vs/workbench/browser/parts/statusbar/statusbarPart'; -import 'vs/workbench/browser/parts/views/viewsService'; //#endregion @@ -99,6 +98,7 @@ import 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; import 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import 'vs/workbench/services/views/browser/viewDescriptorService'; +import 'vs/workbench/services/views/browser/viewsService'; import 'vs/workbench/services/quickinput/browser/quickInputService'; import 'vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService'; import 'vs/workbench/services/authentication/browser/authenticationService'; From ad68ddc7aaea0d0cae7c5ebede6af169c03c4350 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 10 Jan 2024 08:49:44 +0100 Subject: [PATCH 0193/1897] allow to set/configure ariaLabel for IButton (#202127) fixes https://github.com/microsoft/vscode/issues/201527 --- src/vs/base/browser/ui/button/button.ts | 10 ++++++++++ src/vs/platform/actions/browser/buttonbar.ts | 2 ++ 2 files changed, 12 insertions(+) diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index 5550ff19c50..2e845b684f6 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -23,6 +23,7 @@ import { localize } from 'vs/nls'; export interface IButtonOptions extends Partial { readonly title?: boolean | string; + readonly ariaLabel?: boolean | string; readonly supportIcons?: boolean; readonly supportShortLabel?: boolean; readonly secondary?: boolean; @@ -108,6 +109,9 @@ export class Button extends Disposable implements IButton { this._element.classList.add('monaco-text-button-with-short-label'); } + if (typeof options.ariaLabel === 'string') { + this._element.setAttribute('aria-label', options.ariaLabel); + } container.appendChild(this._element); this._register(Gesture.addTarget(this._element)); @@ -238,6 +242,12 @@ export class Button extends Disposable implements IButton { this._element.title = renderStringAsPlaintext(value); } + if (typeof this.options.ariaLabel === 'string') { + this._element.setAttribute('aria-label', this.options.ariaLabel); + } else if (this.options.ariaLabel) { + this._element.setAttribute('aria-label', this._element.title); + } + this._label = value; } diff --git a/src/vs/platform/actions/browser/buttonbar.ts b/src/vs/platform/actions/browser/buttonbar.ts index d8f57e29ebf..21d4c4c5fc6 100644 --- a/src/vs/platform/actions/browser/buttonbar.ts +++ b/src/vs/platform/actions/browser/buttonbar.ts @@ -82,11 +82,13 @@ export class WorkbenchButtonBar extends ButtonBar { actionRunner: this._actionRunner, actions: rest, contextMenuProvider: this._contextMenuService, + ariaLabel: action.label }); } else { action = actionOrSubmenu; btn = this.addButton({ secondary: conifgProvider(action)?.isSecondary ?? secondary, + ariaLabel: action.label }); } From ba2f50ca50a8e07a8f7d70ae1d74ea05ec019c58 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 10 Jan 2024 13:57:19 +0530 Subject: [PATCH 0194/1897] fix #71846 (#202132) --- .../semanticTokensProviderStyling.test.ts | 14 ++-- .../textResourceConfigurationService.test.ts | 4 +- .../node/installGalleryExtensionTask.test.ts | 4 +- .../test/common/userDataSyncClient.ts | 6 +- .../test/browser/editSessions.test.ts | 2 +- .../test/electron-sandbox/extension.test.ts | 4 +- .../extensionRecommendationsService.test.ts | 34 +++----- .../extensionsActions.test.ts | 82 +++++++++---------- .../electron-sandbox/extensionsViews.test.ts | 31 +++---- .../extensionsWorkbenchService.test.ts | 30 ++++--- .../extensionEnablementService.test.ts | 24 +++--- .../browser/extensionStorageMigration.test.ts | 2 +- ...extensionManifestPropertiesService.test.ts | 26 +++--- .../browser/keybindingsEditorModel.test.ts | 4 +- 14 files changed, 124 insertions(+), 143 deletions(-) diff --git a/src/vs/editor/test/common/services/semanticTokensProviderStyling.test.ts b/src/vs/editor/test/common/services/semanticTokensProviderStyling.test.ts index 83d60991849..bfec2c00b1a 100644 --- a/src/vs/editor/test/common/services/semanticTokensProviderStyling.test.ts +++ b/src/vs/editor/test/common/services/semanticTokensProviderStyling.test.ts @@ -10,7 +10,7 @@ import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { SemanticTokensProviderStyling, toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling'; import { createModelServices } from 'vs/editor/test/common/testTextModel'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { IThemeService, ITokenStyle } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService, ITokenStyle } from 'vs/platform/theme/common/themeService'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; @@ -38,9 +38,9 @@ suite('ModelService', () => { tokenTypes: ['st0', 'st1', 'st2', 'st3', 'st4', 'st5', 'st6', 'st7', 'st8', 'st9', 'st10'], tokenModifiers: [] }; - instantiationService.stub(IThemeService, >{ + instantiationService.stub(IThemeService, { getColorTheme() { - return { + return { getTokenStyleMetadata: (tokenType, tokenModifiers, languageId): ITokenStyle => { return { foreground: parseInt(tokenType.substr(2), 10), @@ -90,9 +90,9 @@ suite('ModelService', () => { tokenTypes: ['st0', 'st1', 'st2', 'st3', 'st4', 'st5', 'st6', 'st7', 'st8', 'st9'], tokenModifiers: ['stm0', 'stm1', 'stm2'] }; - instantiationService.stub(IThemeService, >{ + instantiationService.stub(IThemeService, { getColorTheme() { - return { + return { getTokenStyleMetadata: (tokenType, tokenModifiers, languageId): ITokenStyle => { return { foreground: parseInt(tokenType.substr(2), 10), @@ -142,9 +142,9 @@ suite('ModelService', () => { tokenTypes: ['st0', 'st1', 'st2', 'st3', 'st4', 'st5'], tokenModifiers: ['stm0', 'stm1', 'stm2'] }; - instantiationService.stub(IThemeService, >{ + instantiationService.stub(IThemeService, { getColorTheme() { - return { + return { getTokenStyleMetadata: (tokenType, tokenModifiers, languageId): ITokenStyle => { return { foreground: parseInt(tokenType.substr(2), 10), diff --git a/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts b/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts index 561d6cb23fe..a40b185fd28 100644 --- a/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts +++ b/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts @@ -35,8 +35,8 @@ suite('TextResourceConfigurationService - Update', () => { setup(() => { instantiationService = disposables.add(new TestInstantiationService()); - instantiationService.stub(IModelService, >{ getModel() { return null; } }); - instantiationService.stub(ILanguageService, >{ guessLanguageIdByFilepathOrFirstLine() { return language; } }); + instantiationService.stub(IModelService, { getModel() { return null; } }); + instantiationService.stub(ILanguageService, { guessLanguageIdByFilepathOrFirstLine() { return language; } }); instantiationService.stub(IConfigurationService, configurationService); testObject = disposables.add(instantiationService.createInstance(TextResourceConfigurationService)); }); diff --git a/src/vs/platform/extensionManagement/test/node/installGalleryExtensionTask.test.ts b/src/vs/platform/extensionManagement/test/node/installGalleryExtensionTask.test.ts index 51395a86737..1767e8931cb 100644 --- a/src/vs/platform/extensionManagement/test/node/installGalleryExtensionTask.test.ts +++ b/src/vs/platform/extensionManagement/test/node/installGalleryExtensionTask.test.ts @@ -224,8 +224,8 @@ suite('InstallGalleryExtensionTask Tests', () => { instantiationService.stub(IProductService, { quality: options.quality ?? 'insiders' }); instantiationService.stub(IFileService, fileService); instantiationService.stub(ILogService, logService); - instantiationService.stub(INativeEnvironmentService, >{ extensionsDownloadLocation: joinPath(ROOT, 'CachedExtensionVSIXs') }); - instantiationService.stub(IExtensionGalleryService, >{ + instantiationService.stub(INativeEnvironmentService, { extensionsDownloadLocation: joinPath(ROOT, 'CachedExtensionVSIXs') }); + instantiationService.stub(IExtensionGalleryService, { async download(extension, location, operation) { await fileService.writeFile(location, VSBuffer.fromString('extension vsix')); }, diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 4d1eaa4afb2..b622a3efe44 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -62,7 +62,7 @@ export class UserDataSyncClient extends Disposable { const userRoamingDataHome = URI.file('userdata').with({ scheme: Schemas.inMemory }); const userDataSyncHome = joinPath(userRoamingDataHome, '.sync'); - const environmentService = this.instantiationService.stub(IEnvironmentService, >{ + const environmentService = this.instantiationService.stub(IEnvironmentService, { userDataSyncHome, userRoamingDataHome, cacheHome: joinPath(userRoamingDataHome, 'cache'), @@ -117,7 +117,7 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IUserDataSyncUtilService, new TestUserDataSyncUtilService()); this.instantiationService.stub(IUserDataSyncEnablementService, this._register(this.instantiationService.createInstance(UserDataSyncEnablementService))); - this.instantiationService.stub(IExtensionManagementService, >{ + this.instantiationService.stub(IExtensionManagementService, { async getInstalled() { return []; }, onDidInstallExtensions: new Emitter().event, onDidUninstallExtension: new Emitter().event, @@ -125,7 +125,7 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IGlobalExtensionEnablementService, this._register(this.instantiationService.createInstance(GlobalExtensionEnablementService))); this.instantiationService.stub(IExtensionStorageService, this._register(this.instantiationService.createInstance(ExtensionStorageService))); this.instantiationService.stub(IIgnoredExtensionsManagementService, this.instantiationService.createInstance(IgnoredExtensionsManagementService)); - this.instantiationService.stub(IExtensionGalleryService, >{ + this.instantiationService.stub(IExtensionGalleryService, { isEnabled() { return true; }, async getCompatibleExtension() { return null; } }); diff --git a/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts index c4f3e61cfd5..4ad6b2fe900 100644 --- a/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts +++ b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts @@ -80,7 +80,7 @@ suite('Edit session sync', () => { override onWillShutdown = Event.None; }); instantiationService.stub(INotificationService, new TestNotificationService()); - instantiationService.stub(IProductService, >{ 'editSessions.store': { url: 'https://test.com', canSwitch: true, authenticationProviders: {} } }); + instantiationService.stub(IProductService, { 'editSessions.store': { url: 'https://test.com', canSwitch: true, authenticationProviders: {} } }); instantiationService.stub(IStorageService, new TestStorageService()); instantiationService.stub(IUriIdentityService, new UriIdentityService(fileService)); instantiationService.stub(IEditSessionsStorageService, new class extends mock() { diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts index 583c9f99be2..0ef0e1d50c0 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts @@ -20,7 +20,7 @@ suite('Extension Test', () => { setup(() => { instantiationService = new TestInstantiationService(); - instantiationService.stub(IProductService, >{ quality: 'insiders' }); + instantiationService.stub(IProductService, { quality: 'insiders' }); }); teardown(() => { @@ -58,7 +58,7 @@ suite('Extension Test', () => { }); test('extension is not outdated when local is built in and older than gallery but product quality is stable', () => { - instantiationService.stub(IProductService, >{ quality: 'stable' }); + instantiationService.stub(IProductService, { quality: 'stable' }); const extension = instantiationService.createInstance(Extension, () => ExtensionState.Installed, () => undefined, undefined, aLocalExtension('somext', { version: '1.0.0' }, { type: ExtensionType.System }), aGalleryExtension('somext', { version: '1.0.1' })); assert.strictEqual(extension.outdated, false); }); diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts index c644f0acb11..1e9e11baa19 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts @@ -7,10 +7,9 @@ import * as sinon from 'sinon'; import * as assert from 'assert'; import * as uuid from 'vs/base/common/uuid'; import { - IExtensionGalleryService, IGalleryExtensionAssets, IGalleryExtension, IExtensionManagementService, - DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionTipsService, InstallExtensionResult, getTargetPlatform, UninstallExtensionEvent + IExtensionGalleryService, IGalleryExtensionAssets, IGalleryExtension, IExtensionManagementService, IExtensionTipsService, getTargetPlatform, } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { Emitter, Event } from 'vs/base/common/event'; @@ -188,10 +187,6 @@ suite('ExtensionRecommendationsService Test', () => { let instantiationService: TestInstantiationService; let testConfigurationService: TestConfigurationService; let testObject: ExtensionRecommendationsService; - let installEvent: Emitter, - didInstallEvent: Emitter, - uninstallEvent: Emitter, - didUninstallEvent: Emitter; let prompted: boolean; let promptedEmitter: Emitter; let onModelAddedEvent: Emitter; @@ -207,10 +202,6 @@ suite('ExtensionRecommendationsService Test', () => { disposableStore = new DisposableStore(); instantiationService = disposableStore.add(new TestInstantiationService()); promptedEmitter = disposableStore.add(new Emitter()); - installEvent = disposableStore.add(new Emitter()); - didInstallEvent = disposableStore.add(new Emitter()); - uninstallEvent = disposableStore.add(new Emitter()); - didUninstallEvent = disposableStore.add(new Emitter()); instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); instantiationService.stub(ISharedProcessService, TestSharedProcessService); instantiationService.stub(ILifecycleService, disposableStore.add(new TestLifecycleService())); @@ -218,11 +209,11 @@ suite('ExtensionRecommendationsService Test', () => { instantiationService.stub(IConfigurationService, testConfigurationService); instantiationService.stub(INotificationService, new TestNotificationService()); instantiationService.stub(IContextKeyService, new MockContextKeyService()); - instantiationService.stub(IExtensionManagementService, >{ - onInstallExtension: installEvent.event, - onDidInstallExtensions: didInstallEvent.event, - onUninstallExtension: uninstallEvent.event, - onDidUninstallExtension: didUninstallEvent.event, + instantiationService.stub(IWorkbenchExtensionManagementService, { + onInstallExtension: Event.None, + onDidInstallExtensions: Event.None, + onUninstallExtension: Event.None, + onDidUninstallExtension: Event.None, onDidUpdateExtensionMetadata: Event.None, onDidChangeProfile: Event.None, async getInstalled() { return []; }, @@ -230,7 +221,7 @@ suite('ExtensionRecommendationsService Test', () => { async getExtensionsControlManifest() { return { malicious: [], deprecated: {}, search: [] }; }, async getTargetPlatform() { return getTargetPlatform(platform, arch); } }); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { onDidChangeExtensions: Event.None, extensions: [], async whenInstalledExtensionsRegistered() { return true; } @@ -241,12 +232,7 @@ suite('ExtensionRecommendationsService Test', () => { instantiationService.stub(IWorkspaceTagsService, new NoOpWorkspaceTagsService()); instantiationService.stub(IStorageService, disposableStore.add(new TestStorageService())); instantiationService.stub(ILogService, new NullLogService()); - instantiationService.stub(IProductService, >{ - extensionTips: { - 'ms-dotnettools.csharp': '{**/*.cs,**/project.json,**/global.json,**/*.csproj,**/*.sln,**/appsettings.json}', - 'msjsdiag.debugger-for-chrome': '{**/*.ts,**/*.tsx,**/*.js,**/*.jsx,**/*.es6,**/*.mjs,**/*.cjs,**/.babelrc}', - 'lukehoban.Go': '**/*.go' - }, + instantiationService.stub(IProductService, { extensionRecommendations: { 'ms-python.python': { onFileOpen: [ @@ -293,7 +279,7 @@ suite('ExtensionRecommendationsService Test', () => { onModelAddedEvent = new Emitter(); - instantiationService.stub(IEnvironmentService, >{}); + instantiationService.stub(IEnvironmentService, {}); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); instantiationService.stub(IExtensionGalleryService, 'isEnabled', true); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...mockExtensionGallery)); diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts index c811b48b7bf..0762678249d 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts @@ -12,7 +12,7 @@ import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, InstallOperation, IExtensionTipsService, InstallExtensionResult, getTargetPlatform, IExtensionsControlManifest, UninstallExtensionEvent, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, ExtensionInstallLocation, IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, ExtensionInstallLocation, IProfileAwareExtensionManagementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test'; @@ -86,13 +86,13 @@ function setupTest(disposables: Pick) { instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); instantiationService.stub(ISharedProcessService, TestSharedProcessService); - instantiationService.stub(IExtensionManagementService, >{ - onInstallExtension: installEvent.event, + instantiationService.stub(IWorkbenchExtensionManagementService, { onDidInstallExtensions: didInstallEvent.event, - onUninstallExtension: uninstallEvent.event, - onDidUninstallExtension: didUninstallEvent.event, - onDidChangeProfile: Event.None, + onInstallExtension: installEvent.event as any, + onUninstallExtension: uninstallEvent.event as any, + onDidUninstallExtension: didUninstallEvent.event as any, onDidUpdateExtensionMetadata: Event.None, + onDidChangeProfile: Event.None, async getInstalled() { return []; }, async getExtensionsControlManifest() { return { malicious: [], deprecated: {}, search: [] }; }, async updateMetadata(local: ILocalExtension, metadata: Partial) { @@ -108,7 +108,7 @@ function setupTest(disposables: Pick) { instantiationService.stub(IRemoteAgentService, RemoteAgentService); const localExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService) as IProfileAwareExtensionManagementService, label: 'local', id: 'vscode-local' }; - instantiationService.stub(IExtensionManagementServerService, >{ + instantiationService.stub(IExtensionManagementServerService, { get localExtensionManagementServer(): IExtensionManagementServer { return localExtensionManagementServer; }, @@ -131,7 +131,7 @@ function setupTest(disposables: Pick) { instantiationService.stub(IExtensionGalleryService, 'isEnabled', true); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', []); - instantiationService.stub(IExtensionService, >{ extensions: [], onDidChangeExtensions: Event.None, canAddExtension: (extension: IExtensionDescription) => false, canRemoveExtension: (extension: IExtensionDescription) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); + instantiationService.stub(IExtensionService, { extensions: [], onDidChangeExtensions: Event.None, canAddExtension: (extension: IExtensionDescription) => false, canRemoveExtension: (extension: IExtensionDescription) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); (instantiationService.get(IWorkbenchExtensionEnablementService)).reset(); instantiationService.stub(IUserDataSyncEnablementService, disposables.add(instantiationService.createInstance(UserDataSyncEnablementService))); @@ -784,7 +784,7 @@ suite('ExtensionsActions', () => { test('Test DisableForWorkspaceAction when extension is enabled', () => { const local = aLocalExtension('a'); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(local)], onDidChangeExtensions: Event.None, whenInstalledExtensionsRegistered: () => Promise.resolve(true) @@ -839,7 +839,7 @@ suite('ExtensionsActions', () => { test('Test DisableGloballyAction when the extension is enabled', () => { const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(local)], onDidChangeExtensions: Event.None, whenInstalledExtensionsRegistered: () => Promise.resolve(true) @@ -856,7 +856,7 @@ suite('ExtensionsActions', () => { test('Test DisableGloballyAction when extension is installed and enabled', () => { const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(local)], onDidChangeExtensions: Event.None, whenInstalledExtensionsRegistered: () => Promise.resolve(true) @@ -872,7 +872,7 @@ suite('ExtensionsActions', () => { test('Test DisableGloballyAction when extension is installed and disabled globally', () => { const local = aLocalExtension('a'); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(local)], onDidChangeExtensions: Event.None, whenInstalledExtensionsRegistered: () => Promise.resolve(true) @@ -894,7 +894,7 @@ suite('ExtensionsActions', () => { test('Test DisableGloballyAction when extension is uninstalled', () => { const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('a'))], onDidChangeExtensions: Event.None, whenInstalledExtensionsRegistered: () => Promise.resolve(true) @@ -911,7 +911,7 @@ suite('ExtensionsActions', () => { test('Test DisableGloballyAction when extension is installing', () => { const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('a'))], onDidChangeExtensions: Event.None, whenInstalledExtensionsRegistered: () => Promise.resolve(true) @@ -930,7 +930,7 @@ suite('ExtensionsActions', () => { test('Test DisableGloballyAction when extension is uninstalling', () => { const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(local)], onDidChangeExtensions: Event.None, whenInstalledExtensionsRegistered: () => Promise.resolve(true) @@ -988,7 +988,7 @@ suite('ReloadAction', () => { test('Test ReloadAction when extension is newly installed', async () => { const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('b'))], onDidChangeExtensions: onDidChangeExtensionsEmitter.event, canAddExtension: (extension) => false, @@ -1013,7 +1013,7 @@ suite('ReloadAction', () => { test('Test ReloadAction when extension is newly installed and reload is not required', async () => { const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('b'))], onDidChangeExtensions: onDidChangeExtensionsEmitter.event, canAddExtension: (extension) => true, @@ -1034,7 +1034,7 @@ suite('ReloadAction', () => { }); test('Test ReloadAction when extension is installed and uninstalled', async () => { - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('b'))], onDidChangeExtensions: Event.None, canRemoveExtension: (extension) => false, @@ -1058,7 +1058,7 @@ suite('ReloadAction', () => { }); test('Test ReloadAction when extension is uninstalled', async () => { - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('a', { version: '1.0.0' }))], onDidChangeExtensions: Event.None, canRemoveExtension: (extension) => false, @@ -1081,7 +1081,7 @@ suite('ReloadAction', () => { test('Test ReloadAction when extension is uninstalled and can be removed', async () => { const local = aLocalExtension('a'); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(local)], onDidChangeExtensions: Event.None, canRemoveExtension: (extension) => true, @@ -1100,7 +1100,7 @@ suite('ReloadAction', () => { }); test('Test ReloadAction when extension is uninstalled and installed', async () => { - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('a', { version: '1.0.0' }))], onDidChangeExtensions: Event.None, canRemoveExtension: (extension) => false, @@ -1126,7 +1126,7 @@ suite('ReloadAction', () => { }); test('Test ReloadAction when extension is updated while running', async () => { - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('a', { version: '1.0.1' }))], onDidChangeExtensions: Event.None, canRemoveExtension: (extension) => true, @@ -1155,7 +1155,7 @@ suite('ReloadAction', () => { }); test('Test ReloadAction when extension is updated when not running', async () => { - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('b'))], onDidChangeExtensions: Event.None, canRemoveExtension: (extension) => false, @@ -1179,7 +1179,7 @@ suite('ReloadAction', () => { }); test('Test ReloadAction when extension is disabled when running', async () => { - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('a'))], onDidChangeExtensions: Event.None, canRemoveExtension: (extension) => false, @@ -1202,7 +1202,7 @@ suite('ReloadAction', () => { }); test('Test ReloadAction when extension enablement is toggled when running', async () => { - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('a', { version: '1.0.0' }))], onDidChangeExtensions: Event.None, canRemoveExtension: (extension) => false, @@ -1223,7 +1223,7 @@ suite('ReloadAction', () => { }); test('Test ReloadAction when extension is enabled when not running', async () => { - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('b'))], onDidChangeExtensions: Event.None, canRemoveExtension: (extension) => false, @@ -1245,7 +1245,7 @@ suite('ReloadAction', () => { }); test('Test ReloadAction when extension enablement is toggled when not running', async () => { - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('b'))], onDidChangeExtensions: Event.None, canRemoveExtension: (extension) => false, @@ -1266,7 +1266,7 @@ suite('ReloadAction', () => { }); test('Test ReloadAction when extension is updated when not running and enabled', async () => { - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('a'))], onDidChangeExtensions: Event.None, canRemoveExtension: (extension) => false, @@ -1292,7 +1292,7 @@ suite('ReloadAction', () => { }); test('Test ReloadAction when a localization extension is newly installed', async () => { - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('b'))], onDidChangeExtensions: Event.None, canRemoveExtension: (extension) => false, @@ -1314,7 +1314,7 @@ suite('ReloadAction', () => { }); test('Test ReloadAction when a localization extension is updated while running', async () => { - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('a', { version: '1.0.1' }))], onDidChangeExtensions: Event.None, canRemoveExtension: (extension) => false, @@ -1344,7 +1344,7 @@ suite('ReloadAction', () => { instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(remoteExtension)], onDidChangeExtensions: onDidChangeExtensionsEmitter.event, canAddExtension: (extension) => false, @@ -1378,7 +1378,7 @@ suite('ReloadAction', () => { instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(remoteExtension)], onDidChangeExtensions: onDidChangeExtensionsEmitter.event, canAddExtension: (extension) => false, @@ -1417,7 +1417,7 @@ suite('ReloadAction', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [], onDidChangeExtensions: onDidChangeExtensionsEmitter.event, canAddExtension: (extension) => false, @@ -1456,7 +1456,7 @@ suite('ReloadAction', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [], onDidChangeExtensions: onDidChangeExtensionsEmitter.event, canAddExtension: (extension) => false, @@ -1493,7 +1493,7 @@ suite('ReloadAction', () => { instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(localExtension)], onDidChangeExtensions: onDidChangeExtensionsEmitter.event, canAddExtension: (extension) => false, @@ -1528,7 +1528,7 @@ suite('ReloadAction', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(localExtension)], onDidChangeExtensions: onDidChangeExtensionsEmitter.event, canAddExtension: (extension) => false, @@ -1560,7 +1560,7 @@ suite('ReloadAction', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(remoteExtension)], onDidChangeExtensions: onDidChangeExtensionsEmitter.event, canAddExtension: (extension) => false, @@ -1592,7 +1592,7 @@ suite('ReloadAction', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(localExtension)], onDidChangeExtensions: onDidChangeExtensionsEmitter.event, canAddExtension: (extension) => false, @@ -1624,7 +1624,7 @@ suite('ReloadAction', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(remoteExtension)], onDidChangeExtensions: onDidChangeExtensionsEmitter.event, canAddExtension: (extension) => false, @@ -1649,7 +1649,7 @@ suite('ReloadAction', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, null, createExtensionManagementService([remoteExtension]), createExtensionManagementService([webExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(remoteExtension)], onDidChangeExtensions: Event.None, canAddExtension: (extension) => false, @@ -1677,7 +1677,7 @@ suite('ReloadAction', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), null, createExtensionManagementService([webExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(localExtension)], onDidChangeExtensions: Event.None, canAddExtension: (extension) => false, diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts index 440111628df..1d07a03c712 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts @@ -11,7 +11,7 @@ import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/com import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, - DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, getTargetPlatform, IExtensionInfo, UninstallExtensionEvent, SortBy + getTargetPlatform, IExtensionInfo, SortBy } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IProfileAwareExtensionManagementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IExtensionRecommendationsService, ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; @@ -19,7 +19,7 @@ import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/ex import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { IPager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; @@ -55,10 +55,6 @@ suite('ExtensionsViews Tests', () => { let instantiationService: TestInstantiationService; let testableView: ExtensionsListView; - let installEvent: Emitter, - didInstallEvent: Emitter, - uninstallEvent: Emitter, - didUninstallEvent: Emitter; const localEnabledTheme = aLocalExtension('first-enabled-extension', { categories: ['Themes', 'random'] }, { installedTimestamp: 123456 }); const localEnabledLanguage = aLocalExtension('second-enabled-extension', { categories: ['Programming languages'], version: '1.0.0' }, { installedTimestamp: Date.now(), updated: false }); @@ -79,11 +75,6 @@ suite('ExtensionsViews Tests', () => { const otherRecommendationA = aGalleryExtension('other-recommendation-A'); setup(async () => { - installEvent = disposableStore.add(new Emitter()); - didInstallEvent = disposableStore.add(new Emitter()); - uninstallEvent = disposableStore.add(new Emitter()); - didUninstallEvent = disposableStore.add(new Emitter()); - instantiationService = disposableStore.add(new TestInstantiationService()); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(ILogService, NullLogService); @@ -95,13 +86,13 @@ suite('ExtensionsViews Tests', () => { instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); instantiationService.stub(ISharedProcessService, TestSharedProcessService); - instantiationService.stub(IExtensionManagementService, >{ - onInstallExtension: installEvent.event, - onDidInstallExtensions: didInstallEvent.event, - onUninstallExtension: uninstallEvent.event, - onDidUninstallExtension: didUninstallEvent.event, - onDidChangeProfile: Event.None, + instantiationService.stub(IWorkbenchExtensionManagementService, { + onInstallExtension: Event.None, + onDidInstallExtensions: Event.None, + onUninstallExtension: Event.None, + onDidUninstallExtension: Event.None, onDidUpdateExtensionMetadata: Event.None, + onDidChangeProfile: Event.None, async getInstalled() { return []; }, async canInstall() { return true; }, async getExtensionsControlManifest() { return { malicious: [], deprecated: {}, search: [] }; }, @@ -113,7 +104,7 @@ suite('ExtensionsViews Tests', () => { instantiationService.stub(IMenuService, new TestMenuService()); const localExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService) as IProfileAwareExtensionManagementService, label: 'local', id: 'vscode-local' }; - instantiationService.stub(IExtensionManagementServerService, >{ + instantiationService.stub(IExtensionManagementServerService, { get localExtensionManagementServer(): IExtensionManagementServer { return localExtensionManagementServer; }, @@ -134,7 +125,7 @@ suite('ExtensionsViews Tests', () => { reasons[fileBasedRecommendationB.identifier.id] = { reasonId: ExtensionRecommendationReason.File }; reasons[otherRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.Executable }; reasons[configBasedRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.WorkspaceConfig }; - instantiationService.stub(IExtensionRecommendationsService, >{ + instantiationService.stub(IExtensionRecommendationsService, { getWorkspaceRecommendations() { return Promise.resolve([ workspaceRecommendationA.identifier.id, @@ -181,7 +172,7 @@ suite('ExtensionsViews Tests', () => { onDidChangeLocation: Event.None }); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { onDidChangeExtensions: Event.None, extensions: [ toExtensionDescription(localEnabledTheme), diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts index 44b5ff85e3e..ab080153b5e 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts @@ -12,7 +12,7 @@ import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, InstallOperation, IExtensionTipsService, InstallExtensionResult, getTargetPlatform, IExtensionsControlManifest, UninstallExtensionEvent, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IProfileAwareExtensionManagementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { anExtensionManagementServerService, TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test'; @@ -86,13 +86,13 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stub(IRemoteAgentService, RemoteAgentService); - instantiationService.stub(IExtensionManagementService, >{ - onInstallExtension: installEvent.event, + instantiationService.stub(IWorkbenchExtensionManagementService, { onDidInstallExtensions: didInstallEvent.event, - onUninstallExtension: uninstallEvent.event, - onDidUninstallExtension: didUninstallEvent.event, - onDidChangeProfile: Event.None, + onInstallExtension: installEvent.event as any, + onUninstallExtension: uninstallEvent.event as any, + onDidUninstallExtension: didUninstallEvent.event as any, onDidUpdateExtensionMetadata: Event.None, + onDidChangeProfile: Event.None, async getInstalled() { return []; }, async getExtensionsControlManifest() { return { malicious: [], deprecated: {}, search: [] }; }, async updateMetadata(local: ILocalExtension, metadata: Partial) { @@ -119,7 +119,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stub(INotificationService, { prompt: () => null! }); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { onDidChangeExtensions: Event.None, extensions: [], async whenInstalledExtensionsRegistered() { return true; } @@ -695,9 +695,10 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionB = aLocalExtension('b'); const extensionC = aLocalExtension('c'); - instantiationService.stub(INotificationService, >{ + instantiationService.stub(INotificationService, { prompt(severity, message, choices, options) { options!.onCancel!(); + return null!; } }); return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionA], EnablementState.EnabledGlobally) @@ -715,9 +716,10 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionB = aLocalExtension('b'); const extensionC = aLocalExtension('c'); - instantiationService.stub(INotificationService, >{ + instantiationService.stub(INotificationService, { prompt(severity, message, choices, options) { choices[0].run(); + return null!; } }); return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionA], EnablementState.EnabledGlobally) @@ -837,9 +839,10 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionB = aLocalExtension('b', { extensionDependencies: ['pub.a'] }); const extensionC = aLocalExtension('c'); - instantiationService.stub(INotificationService, >{ + instantiationService.stub(INotificationService, { prompt(severity, message, choices, options) { options!.onCancel!(); + return null!; } }); return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionA], EnablementState.EnabledGlobally) @@ -877,9 +880,10 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionB = aLocalExtension('b', { extensionDependencies: ['pub.c'] }); const extensionC = aLocalExtension('c', { extensionDependencies: ['pub.a'] }); - instantiationService.stub(INotificationService, >{ + instantiationService.stub(INotificationService, { prompt(severity, message, choices, options) { options!.onCancel!(); + return null!; } }); return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionA], EnablementState.EnabledGlobally) @@ -1662,9 +1666,9 @@ suite('ExtensionsWorkbenchServiceTest', () => { [AutoUpdateConfigurationKey]: autoUpdateValue ?? true, [AutoCheckUpdatesConfigurationKey]: autoCheckUpdatesValue ?? true }; - instantiationService.stub(IConfigurationService, >{ + instantiationService.stub(IConfigurationService, { onDidChangeConfiguration: () => { return undefined!; }, - getValue: (key?: string) => { + getValue: (key?: any) => { return key ? values[key] : undefined; }, updateValue: async (key: string, value: any) => { diff --git a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts index fa8aff5fde3..24908288aa9 100644 --- a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts @@ -77,7 +77,7 @@ export class TestExtensionEnablementService extends ExtensionEnablementService { storageService, disposables.add(new GlobalExtensionEnablementService(storageService, extensionManagementService)), instantiationService.get(IWorkspaceContextService) || new TestContextService(), - instantiationService.get(IWorkbenchEnvironmentService) || instantiationService.stub(IWorkbenchEnvironmentService, {} as IWorkbenchEnvironmentService), + instantiationService.get(IWorkbenchEnvironmentService) || instantiationService.stub(IWorkbenchEnvironmentService, {}), workbenchExtensionManagementService, instantiationService.get(IConfigurationService), extensionManagementServerService, @@ -518,26 +518,26 @@ suite('ExtensionEnablementService Test', () => { }); test('test canChangeEnablement return false when extensions are disabled in environment', () => { - instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService); + instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true }); testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.canChangeEnablement(aLocalExtension('pub.a')), false); }); test('test canChangeEnablement return false when the extension is disabled in environment', () => { - instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); + instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: ['pub.a'] }); testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.canChangeEnablement(aLocalExtension('pub.a')), false); }); test('test canChangeEnablement return true for system extensions when extensions are disabled in environment', () => { - instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService); + instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true }); testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); const extension = aLocalExtension('pub.a', undefined, ExtensionType.System); assert.strictEqual(testObject.canChangeEnablement(extension), true); }); test('test canChangeEnablement return false for system extension when extension is disabled in environment', () => { - instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); + instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: ['pub.a'] }); testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); const extension = aLocalExtension('pub.a', undefined, ExtensionType.System); assert.ok(!testObject.canChangeEnablement(extension)); @@ -547,7 +547,7 @@ suite('ExtensionEnablementService Test', () => { const extension = aLocalExtension('pub.a'); installed.push(extension); - instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); + instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: ['pub.a'] }); testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(!testObject.isEnabled(extension)); @@ -558,7 +558,7 @@ suite('ExtensionEnablementService Test', () => { const extension = aLocalExtension('pub.a'); installed.push(extension); - instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); + instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: ['pub.a'] }); testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(testObject.isEnabled(extension)); @@ -570,7 +570,7 @@ suite('ExtensionEnablementService Test', () => { installed.push(extension); await testObject.setEnablement([extension], EnablementState.EnabledWorkspace); - instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); + instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: ['pub.a'] }); testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(testObject.isEnabled(extension)); @@ -582,7 +582,7 @@ suite('ExtensionEnablementService Test', () => { installed.push(extension); await testObject.setEnablement([extension], EnablementState.DisabledGlobally); - instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); + instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: ['pub.a'] }); testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(testObject.isEnabled(extension)); @@ -594,7 +594,7 @@ suite('ExtensionEnablementService Test', () => { installed.push(extension); await testObject.setEnablement([extension], EnablementState.DisabledWorkspace); - instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); + instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: ['pub.a'] }); testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(testObject.isEnabled(extension)); @@ -606,7 +606,7 @@ suite('ExtensionEnablementService Test', () => { installed.push(extension); testObject.setEnablement([extension], EnablementState.DisabledWorkspace); - instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true, enableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); + instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true, enableExtensions: ['pub.a'] }); testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(!testObject.isEnabled(extension)); @@ -614,7 +614,7 @@ suite('ExtensionEnablementService Test', () => { }); test('test canChangeEnablement return false when the extension is enabled in environment', () => { - instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); + instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: ['pub.a'] }); testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.canChangeEnablement(aLocalExtension('pub.a')), false); }); diff --git a/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts b/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts index 52dc0e6997f..f1795ab4908 100644 --- a/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts +++ b/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts @@ -38,7 +38,7 @@ suite('ExtensionStorageMigration', () => { const fileService = disposables.add(new FileService(new NullLogService())); disposables.add(fileService.registerProvider(ROOT.scheme, disposables.add(new InMemoryFileSystemProvider()))); instantiationService.stub(IFileService, fileService); - const environmentService = instantiationService.stub(IEnvironmentService, >{ userRoamingDataHome: ROOT, workspaceStorageHome, cacheHome: ROOT }); + const environmentService = instantiationService.stub(IEnvironmentService, { userRoamingDataHome: ROOT, workspaceStorageHome, cacheHome: ROOT }); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, disposables.add(new UserDataProfilesService(environmentService, fileService, disposables.add(new UriIdentityService(fileService)), new NullLogService()))); instantiationService.stub(IUserDataProfileService, disposables.add(new UserDataProfileService(userDataProfilesService.defaultProfile))); diff --git a/src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts b/src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts index 9cde0946333..ab5a069fe14 100644 --- a/src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts +++ b/src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts @@ -136,7 +136,7 @@ if (!isWeb) { } test('test extension workspace trust request when main entry point is missing', () => { - instantiationService.stub(IProductService, >{}); + instantiationService.stub(IProductService, {}); instantiationService.stub(IWorkspaceTrustEnablementService, new TestWorkspaceTrustEnablementService()); const extensionManifest = getExtensionManifest(); @@ -144,7 +144,7 @@ if (!isWeb) { }); test('test extension workspace trust request when workspace trust is disabled', async () => { - instantiationService.stub(IProductService, >{}); + instantiationService.stub(IProductService, {}); instantiationService.stub(IWorkspaceTrustEnablementService, new TestWorkspaceTrustEnablementService(false)); const extensionManifest = getExtensionManifest({ main: './out/extension.js' }); @@ -152,7 +152,7 @@ if (!isWeb) { }); test('test extension workspace trust request when "true" override exists in settings.json', async () => { - instantiationService.stub(IProductService, >{}); + instantiationService.stub(IProductService, {}); instantiationService.stub(IWorkspaceTrustEnablementService, new TestWorkspaceTrustEnablementService()); await testConfigurationService.setUserConfiguration('extensions', { supportUntrustedWorkspaces: { 'pub.a': { supported: true } } }); @@ -161,7 +161,7 @@ if (!isWeb) { }); test('test extension workspace trust request when override (false) exists in settings.json', async () => { - instantiationService.stub(IProductService, >{}); + instantiationService.stub(IProductService, {}); instantiationService.stub(IWorkspaceTrustEnablementService, new TestWorkspaceTrustEnablementService()); await testConfigurationService.setUserConfiguration('extensions', { supportUntrustedWorkspaces: { 'pub.a': { supported: false } } }); @@ -170,7 +170,7 @@ if (!isWeb) { }); test('test extension workspace trust request when override (true) for the version exists in settings.json', async () => { - instantiationService.stub(IProductService, >{}); + instantiationService.stub(IProductService, {}); instantiationService.stub(IWorkspaceTrustEnablementService, new TestWorkspaceTrustEnablementService()); await testConfigurationService.setUserConfiguration('extensions', { supportUntrustedWorkspaces: { 'pub.a': { supported: true, version: '1.0.0' } } }); @@ -179,7 +179,7 @@ if (!isWeb) { }); test('test extension workspace trust request when override (false) for the version exists in settings.json', async () => { - instantiationService.stub(IProductService, >{}); + instantiationService.stub(IProductService, {}); instantiationService.stub(IWorkspaceTrustEnablementService, new TestWorkspaceTrustEnablementService()); await testConfigurationService.setUserConfiguration('extensions', { supportUntrustedWorkspaces: { 'pub.a': { supported: false, version: '1.0.0' } } }); @@ -188,7 +188,7 @@ if (!isWeb) { }); test('test extension workspace trust request when override for a different version exists in settings.json', async () => { - instantiationService.stub(IProductService, >{}); + instantiationService.stub(IProductService, {}); instantiationService.stub(IWorkspaceTrustEnablementService, new TestWorkspaceTrustEnablementService()); await testConfigurationService.setUserConfiguration('extensions', { supportUntrustedWorkspaces: { 'pub.a': { supported: true, version: '2.0.0' } } }); @@ -197,7 +197,7 @@ if (!isWeb) { }); test('test extension workspace trust request when default (true) exists in product.json', () => { - instantiationService.stub(IProductService, >{ extensionUntrustedWorkspaceSupport: { 'pub.a': { default: true } } }); + instantiationService.stub(IProductService, { extensionUntrustedWorkspaceSupport: { 'pub.a': { default: true } } }); instantiationService.stub(IWorkspaceTrustEnablementService, new TestWorkspaceTrustEnablementService()); const extensionManifest = getExtensionManifest({ main: './out/extension.js' }); @@ -205,7 +205,7 @@ if (!isWeb) { }); test('test extension workspace trust request when default (false) exists in product.json', () => { - instantiationService.stub(IProductService, >{ extensionUntrustedWorkspaceSupport: { 'pub.a': { default: false } } }); + instantiationService.stub(IProductService, { extensionUntrustedWorkspaceSupport: { 'pub.a': { default: false } } }); instantiationService.stub(IWorkspaceTrustEnablementService, new TestWorkspaceTrustEnablementService()); const extensionManifest = getExtensionManifest({ main: './out/extension.js' }); @@ -213,7 +213,7 @@ if (!isWeb) { }); test('test extension workspace trust request when override (limited) exists in product.json', () => { - instantiationService.stub(IProductService, >{ extensionUntrustedWorkspaceSupport: { 'pub.a': { override: 'limited' } } }); + instantiationService.stub(IProductService, { extensionUntrustedWorkspaceSupport: { 'pub.a': { override: 'limited' } } }); instantiationService.stub(IWorkspaceTrustEnablementService, new TestWorkspaceTrustEnablementService()); const extensionManifest = getExtensionManifest({ main: './out/extension.js', capabilities: { untrustedWorkspaces: { supported: true } } }); @@ -221,7 +221,7 @@ if (!isWeb) { }); test('test extension workspace trust request when override (false) exists in product.json', () => { - instantiationService.stub(IProductService, >{ extensionUntrustedWorkspaceSupport: { 'pub.a': { override: false } } }); + instantiationService.stub(IProductService, { extensionUntrustedWorkspaceSupport: { 'pub.a': { override: false } } }); instantiationService.stub(IWorkspaceTrustEnablementService, new TestWorkspaceTrustEnablementService()); const extensionManifest = getExtensionManifest({ main: './out/extension.js', capabilities: { untrustedWorkspaces: { supported: true } } }); @@ -229,7 +229,7 @@ if (!isWeb) { }); test('test extension workspace trust request when value exists in package.json', () => { - instantiationService.stub(IProductService, >{}); + instantiationService.stub(IProductService, {}); instantiationService.stub(IWorkspaceTrustEnablementService, new TestWorkspaceTrustEnablementService()); const extensionManifest = getExtensionManifest({ main: './out/extension.js', capabilities: { untrustedWorkspaces: { supported: 'limited' } } }); @@ -237,7 +237,7 @@ if (!isWeb) { }); test('test extension workspace trust request when no value exists in package.json', () => { - instantiationService.stub(IProductService, >{}); + instantiationService.stub(IProductService, {}); instantiationService.stub(IWorkspaceTrustEnablementService, new TestWorkspaceTrustEnablementService()); const extensionManifest = getExtensionManifest({ main: './out/extension.js' }); diff --git a/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts b/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts index ca279aaea69..b7b974d2d49 100644 --- a/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts +++ b/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts @@ -41,9 +41,9 @@ suite('KeybindingsEditorModel', () => { instantiationService = disposables.add(new TestInstantiationService()); instantiationService.stub(IKeybindingService, {}); - instantiationService.stub(IExtensionService, >{ + instantiationService.stub(IExtensionService, { whenInstalledExtensionsRegistered: () => Promise.resolve(true), - get extensions() { return extensions; } + get extensions() { return extensions as IExtensionDescription[]; } }); testObject = disposables.add(instantiationService.createInstance(KeybindingsEditorModel, OS)); From 572198c07d3021fad00be382c1042ab652b0be36 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 10 Jan 2024 15:03:49 +0530 Subject: [PATCH 0195/1897] add more logging (#202139) --- .../extensionManagement/node/extensionDownloader.ts | 1 + .../node/extensionSignatureVerificationService.ts | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/vs/platform/extensionManagement/node/extensionDownloader.ts b/src/vs/platform/extensionManagement/node/extensionDownloader.ts index b71f24d33d3..cc9a0089434 100644 --- a/src/vs/platform/extensionManagement/node/extensionDownloader.ts +++ b/src/vs/platform/extensionManagement/node/extensionDownloader.ts @@ -99,6 +99,7 @@ export class ExtensionsDownloader extends Disposable { private shouldVerifySignature(extension: IGalleryExtension): boolean { if (!extension.isSigned) { + this.logService.info(`Extension is not signed: ${extension.identifier.id}`); return false; } diff --git a/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts b/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts index 05c9ad01cd2..4f33aa1f48e 100644 --- a/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts +++ b/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { getErrorMessage } from 'vs/base/common/errors'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; export const IExtensionSignatureVerificationService = createDecorator('IExtensionSignatureVerificationService'); @@ -44,6 +46,10 @@ export class ExtensionSignatureVerificationService implements IExtensionSignatur private moduleLoadingPromise: Promise | undefined; + constructor( + @ILogService private readonly logService: ILogService + ) { } + private vsceSign(): Promise { if (!this.moduleLoadingPromise) { this.moduleLoadingPromise = new Promise( @@ -65,6 +71,7 @@ export class ExtensionSignatureVerificationService implements IExtensionSignatur try { module = await this.vsceSign(); } catch (error) { + this.logService.error('Could not load vsce-sign module', getErrorMessage(error)); return false; } From 314c9891c3b676cf476a3f95b01492c1931b4ca7 Mon Sep 17 00:00:00 2001 From: Louis Lazaris Date: Wed, 10 Jan 2024 07:44:25 -0500 Subject: [PATCH 0196/1897] Fixed some CSS terminology (#202125) --- extensions/css-language-features/package.nls.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/css-language-features/package.nls.json b/extensions/css-language-features/package.nls.json index 784eaa531b7..6a6adb16ddc 100644 --- a/extensions/css-language-features/package.nls.json +++ b/extensions/css-language-features/package.nls.json @@ -2,7 +2,7 @@ "displayName": "CSS Language Features", "description": "Provides rich language support for CSS, LESS and SCSS files.", "css.title": "CSS", - "css.customData.desc": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-css-languageservice/blob/master/docs/customData.md).\n\nVS Code loads custom data on startup to enhance its CSS support for the custom CSS properties, at directives, pseudo classes and pseudo elements you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", + "css.customData.desc": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-css-languageservice/blob/master/docs/customData.md).\n\nVS Code loads custom data on startup to enhance its CSS support for CSS custom properties (variables), at-rules, pseudo-classes, and pseudo-elements you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", "css.completion.triggerPropertyValueCompletion.desc": "By default, VS Code triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", "css.completion.completePropertyWithSemicolon.desc": "Insert semicolon at end of line when completing CSS properties.", "css.lint.argumentsInColorFunction.desc": "Invalid number of parameters.", From 89eb2de09370a3c72598192fbfedaf929fb1a938 Mon Sep 17 00:00:00 2001 From: Louis Lazaris Date: Wed, 10 Jan 2024 08:42:16 -0500 Subject: [PATCH 0197/1897] Corrected CSS Hover Documentation text (#202142) --- extensions/css-language-features/package.nls.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/css-language-features/package.nls.json b/extensions/css-language-features/package.nls.json index 6a6adb16ddc..5e9129c84e1 100644 --- a/extensions/css-language-features/package.nls.json +++ b/extensions/css-language-features/package.nls.json @@ -28,7 +28,7 @@ "css.trace.server.desc": "Traces the communication between VS Code and the CSS language server.", "css.validate.title": "Controls CSS validation and problem severities.", "css.validate.desc": "Enables or disables all validations.", - "css.hover.documentation": "Show tag and attribute documentation in CSS hovers.", + "css.hover.documentation": "Show property and value documentation in CSS hovers.", "css.hover.references": "Show references to MDN in CSS hovers.", "css.format.enable.desc": "Enable/disable default CSS formatter.", "css.format.newlineBetweenSelectors.desc": "Separate selectors with a new line.", @@ -62,7 +62,7 @@ "less.lint.zeroUnits.desc": "No unit for zero needed.", "less.validate.title": "Controls LESS validation and problem severities.", "less.validate.desc": "Enables or disables all validations.", - "less.hover.documentation": "Show tag and attribute documentation in LESS hovers.", + "less.hover.documentation": "Show property and value documentation in LESS hovers.", "less.hover.references": "Show references to MDN in LESS hovers.", "less.format.enable.desc": "Enable/disable default LESS formatter.", "less.format.newlineBetweenSelectors.desc": "Separate selectors with a new line.", @@ -96,7 +96,7 @@ "scss.lint.zeroUnits.desc": "No unit for zero needed.", "scss.validate.title": "Controls SCSS validation and problem severities.", "scss.validate.desc": "Enables or disables all validations.", - "scss.hover.documentation": "Show tag and attribute documentation in SCSS hovers.", + "scss.hover.documentation": "Show property and value documentation in SCSS hovers.", "scss.hover.references": "Show references to MDN in SCSS hovers.", "scss.format.enable.desc": "Enable/disable default SCSS formatter.", "scss.format.newlineBetweenSelectors.desc": "Separate selectors with a new line.", From fc3df618cb854a5e977b31ab72570b9933500ce1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 10 Jan 2024 15:22:40 +0100 Subject: [PATCH 0198/1897] workspace.fs.delete useTrash option is not respected (fix #201742) (#202156) --- src/vs/workbench/api/common/extHostFileSystemConsumer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostFileSystemConsumer.ts b/src/vs/workbench/api/common/extHostFileSystemConsumer.ts index ee009c2ab7e..264a37f5856 100644 --- a/src/vs/workbench/api/common/extHostFileSystemConsumer.ts +++ b/src/vs/workbench/api/common/extHostFileSystemConsumer.ts @@ -121,7 +121,7 @@ export class ExtHostConsumerFileSystem { async delete(uri: vscode.Uri, options?: { recursive?: boolean; useTrash?: boolean }): Promise { try { const provider = that._fileSystemProvider.get(uri.scheme); - if (provider && !provider.isReadonly) { + if (provider && !provider.isReadonly && !options?.useTrash /* no shortcut: use trash */) { // use shortcut await that._proxy.$ensureActivation(uri.scheme); return await provider.impl.delete(uri, { recursive: false, ...options }); From ff7dcf29742486f5fb68ce15e25555c91bfe8e28 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 10 Jan 2024 15:25:42 +0100 Subject: [PATCH 0199/1897] zoom - fix some issues (#202158) --- src/vs/platform/windows/electron-main/windowsStateHandler.ts | 5 ++++- src/vs/workbench/electron-sandbox/window.ts | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/windows/electron-main/windowsStateHandler.ts b/src/vs/platform/windows/electron-main/windowsStateHandler.ts index a020ac049ae..df866ea3556 100644 --- a/src/vs/platform/windows/electron-main/windowsStateHandler.ts +++ b/src/vs/platform/windows/electron-main/windowsStateHandler.ts @@ -389,7 +389,10 @@ export class WindowsStateHandler extends Disposable { if (lastActiveState.mode === WindowMode.Fullscreen) { state.mode = WindowMode.Fullscreen; // only take mode (fixes https://github.com/microsoft/vscode/issues/19331) } else { - state = lastActiveState; + state = { + ...lastActiveState, + zoomLevel: undefined // do not inherit zoom level + }; } ensureNoOverlap = state.mode !== WindowMode.Fullscreen && windowConfig.newWindowDimensions === 'offset'; diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 8239e05c46e..1a014842112 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -1111,6 +1111,10 @@ export class NativeWindow extends BaseWindow { if (applyZoomLevel) { applyZoom(this.configuredWindowZoomLevel, ApplyZoomTarget.ALL_WINDOWS); } + + for (const [windowId] of this.mapWindowIdToResetZoomStatusEntry) { + this.updateResetWindowZoomStatusEntry(windowId); + } } //#endregion From 8ffbf09e3114b624cdd77b568a1a406ad3e0ab28 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 10 Jan 2024 15:27:09 +0100 Subject: [PATCH 0200/1897] Clear edit queue when request errors, tweak Queue/Limiter, tests (#202157) * don't drag targetWindow all the way across all the layers * add `ILimiter#clear`, add tests and define onDrain and clear behaviour * fix compilo * Queue/Limit - clarify onDrain timing, make sure it fires only once, add whenIdle * chat - clear edit queue on error, adopt whenIdle * add test to ensure progress edit stream stop on being cancelled --- src/vs/base/browser/dom.ts | 15 +- src/vs/base/common/async.ts | 40 ++++- src/vs/base/test/common/async.test.ts | 142 ++++++++++++++++++ .../browser/inlineChatController.ts | 13 +- .../browser/inlineChatStrategies.ts | 36 ++--- .../test/browser/inlineChatStrategies.test.ts | 75 +++++++++ .../view/cellParts/chat/cellChatController.ts | 4 +- 7 files changed, 288 insertions(+), 37 deletions(-) create mode 100644 src/vs/workbench/contrib/inlineChat/test/browser/inlineChatStrategies.test.ts diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 112ed1854d2..18e56736dd2 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -277,8 +277,19 @@ export function disposableWindowInterval(targetWindow: Window, handler: () => vo export class WindowIntervalTimer extends IntervalTimer { - override cancelAndSet(runner: () => void, interval: number, targetWindow: Window & typeof globalThis): void { - return super.cancelAndSet(runner, interval, targetWindow); + private readonly defaultTarget?: Window & typeof globalThis; + + /** + * + * @param node The optional node from which the target window is determined + */ + constructor(node?: Node) { + super(); + this.defaultTarget = node && getWindow(node); + } + + override cancelAndSet(runner: () => void, interval: number, targetWindow?: Window & typeof globalThis): void { + return super.cancelAndSet(runner, interval, targetWindow ?? this.defaultTarget); } } diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index a997633ffe2..54fb9b0bd47 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -626,6 +626,8 @@ export interface ILimiter { readonly size: number; queue(factory: ITask>): Promise; + + clear(): void; } /** @@ -635,6 +637,7 @@ export interface ILimiter { export class Limiter implements ILimiter { private _size = 0; + private _isDisposed = false; private runningPromises: number; private readonly maxDegreeOfParalellism: number; private readonly outstandingPromises: ILimitedTaskFactory[]; @@ -648,13 +651,16 @@ export class Limiter implements ILimiter { } /** - * An event that fires when every promise in the queue - * has started to execute. In other words: no work is - * pending to be scheduled. * - * This is NOT an event that signals when all promises - * have finished though. + * @returns A promise that resolved when all work is done (onDrained) or when + * there is nothing to do */ + whenIdle(): Promise { + return this.size > 0 + ? Event.toPromise(this.onDrained) + : Promise.resolve(); + } + get onDrained(): Event { return this._onDrained.event; } @@ -664,6 +670,9 @@ export class Limiter implements ILimiter { } queue(factory: ITask>): Promise { + if (this._isDisposed) { + throw new Error('Object has been disposed'); + } this._size++; return new Promise((c, e) => { @@ -684,17 +693,32 @@ export class Limiter implements ILimiter { } private consumed(): void { - this._size--; + if (this._isDisposed) { + return; + } this.runningPromises--; + if (--this._size === 0) { + this._onDrained.fire(); + } if (this.outstandingPromises.length > 0) { this.consume(); - } else { - this._onDrained.fire(); } } + clear(): void { + if (this._isDisposed) { + throw new Error('Object has been disposed'); + } + this.outstandingPromises.length = 0; + this._size = this.runningPromises; + } + dispose(): void { + // SEE https://github.com/microsoft/vscode/issues/202136 + // this._isDisposed = true; + // this.outstandingPromises.length = 0; // stop further processing + // this._size = 0; this._onDrained.dispose(); } } diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index 69f1ab5f56f..ebec06f453d 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -485,6 +485,7 @@ suite('Async', () => { }); }); + suite('Queue', () => { test('simple', function () { const queue = new async.Queue(); @@ -509,6 +510,147 @@ suite('Async', () => { }); }); + // skipped because of https://github.com/microsoft/vscode/issues/202136 + test.skip('stop processing on dispose', async function () { + const queue = new async.Queue(); + + let workCounter = 0; + const task = async () => { + await async.timeout(0); + workCounter++; + queue.dispose(); // DISPOSE HERE + }; + + const p1 = queue.queue(task); + queue.queue(task); + queue.queue(task); + assert.strictEqual(queue.size, 3); + + + await p1; + + assert.strictEqual(workCounter, 1); + }); + + test('stop on clear', async function () { + const queue = new async.Queue(); + + let workCounter = 0; + const task = async () => { + await async.timeout(0); + workCounter++; + queue.clear(); // CLEAR HERE + assert.strictEqual(queue.size, 1); // THIS task is still running + }; + + const p1 = queue.queue(task); + queue.queue(task); + queue.queue(task); + assert.strictEqual(queue.size, 3); + + await p1; + assert.strictEqual(workCounter, 1); + assert.strictEqual(queue.size, 0); // has been cleared + + + const p2 = queue.queue(task); + await p2; + assert.strictEqual(workCounter, 2); + }); + + test('clear and drain (1)', async function () { + const queue = new async.Queue(); + + let workCounter = 0; + const task = async () => { + await async.timeout(0); + workCounter++; + queue.clear(); // CLEAR HERE + }; + + const p0 = Event.toPromise(queue.onDrained); + const p1 = queue.queue(task); + + await p1; + await p0; // expect drain to fire because a task was running + assert.strictEqual(workCounter, 1); + queue.dispose(); + }); + + test('clear and drain (2)', async function () { + const queue = new async.Queue(); + + let didFire = false; + const d = queue.onDrained(() => { + didFire = true; + }); + + queue.clear(); + + assert.strictEqual(didFire, false); // no work, no drain! + d.dispose(); + queue.dispose(); + }); + + test('drain timing', async function () { + const queue = new async.Queue(); + + const logicClock = new class { + private time = 0; + tick() { + return this.time++; + } + }; + + let didDrainTime = 0; + let didFinishTime1 = 0; + let didFinishTime2 = 0; + const d = queue.onDrained(() => { + didDrainTime = logicClock.tick(); + }); + + const p1 = queue.queue(() => { + // await async.timeout(10); + didFinishTime1 = logicClock.tick(); + return Promise.resolve(); + }); + + const p2 = queue.queue(async () => { + await async.timeout(10); + didFinishTime2 = logicClock.tick(); + }); + + + await Promise.all([p1, p2]); + + assert.strictEqual(didFinishTime1, 0); + assert.strictEqual(didFinishTime2, 1); + assert.strictEqual(didDrainTime, 2); + + d.dispose(); + queue.dispose(); + }); + + test('drain event is send only once', async function () { + const queue = new async.Queue(); + + let drainCount = 0; + const d = queue.onDrained(() => { drainCount++; }); + queue.queue(async () => { }); + queue.queue(async () => { }); + queue.queue(async () => { }); + queue.queue(async () => { }); + assert.strictEqual(drainCount, 0); + assert.strictEqual(queue.size, 4); + + await queue.whenIdle(); + + assert.strictEqual(drainCount, 1); + + d.dispose(); + queue.dispose(); + }); + test('order is kept', function () { const queue = new async.Queue(); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 951ddfbbefb..f9c6a7c2c20 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getWindow } from 'vs/base/browser/dom'; import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { Barrier, Queue, raceCancellation, raceCancellationError } from 'vs/base/common/async'; @@ -632,10 +631,9 @@ export class InlineChatController implements IEditorContribution { this._ctxHasActiveRequest.set(true); reply = await raceCancellationError(Promise.resolve(task), requestCts.token); - if (progressiveEditsQueue.size > 0) { - // we must wait for all edits that came in via progress to complete - await Event.toPromise(progressiveEditsQueue.onDrained); - } + // we must wait for all edits that came in via progress to complete + await progressiveEditsQueue.whenIdle(); + if (progressiveChatResponse) { progressiveChatResponse.cancel(); } @@ -659,6 +657,7 @@ export class InlineChatController implements IEditorContribution { } } catch (e) { + progressiveEditsQueue.clear(); response = new ErrorResponse(e); a11yResponse = (response).message; @@ -910,9 +909,9 @@ export class InlineChatController implements IEditorContribution { this._ignoreModelContentChanged = true; this._activeSession.wholeRange.trackEdits(editOperations); if (opts) { - await this._strategy.makeProgressiveChanges(getWindow(this._editor.getContainerDomNode()), editOperations, opts); + await this._strategy.makeProgressiveChanges(editOperations, opts); } else { - await this._strategy.makeChanges(getWindow(this._editor.getContainerDomNode()), editOperations); + await this._strategy.makeChanges(editOperations); } this._ctxDidEdit.set(this._activeSession.hasChangedText); } finally { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 12f26c6878d..7c483f8ea98 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { disposableWindowInterval } from 'vs/base/browser/dom'; +import { WindowIntervalTimer } from 'vs/base/browser/dom'; import { coalesceInPlace, equals, tail } from 'vs/base/common/arrays'; -import { AsyncIterableObject, AsyncIterableSource } from 'vs/base/common/async'; +import { AsyncIterableObject, AsyncIterableSource, IntervalTimer } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; @@ -80,9 +80,9 @@ export abstract class EditModeStrategy { this._onDidDiscard.fire(); } - abstract makeProgressiveChanges(targetWindow: Window, edits: ISingleEditOperation[], timings: ProgressingEditsOptions): Promise; + abstract makeProgressiveChanges(edits: ISingleEditOperation[], timings: ProgressingEditsOptions): Promise; - abstract makeChanges(targetWindow: Window, edits: ISingleEditOperation[]): Promise; + abstract makeChanges(edits: ISingleEditOperation[]): Promise; abstract undoChanges(altVersionId: number): Promise; @@ -154,7 +154,7 @@ export class PreviewStrategy extends EditModeStrategy { // nothing to do } - override async makeChanges(_targetWindow: Window, _edits: ISingleEditOperation[]): Promise { + override async makeChanges(_edits: ISingleEditOperation[]): Promise { // nothing to do } @@ -246,7 +246,7 @@ export class LivePreviewStrategy extends EditModeStrategy { await undoModelUntil(modelN, targetAltVersion); } - override async makeChanges(_targetWindow: Window, edits: ISingleEditOperation[]): Promise { + override async makeChanges(edits: ISingleEditOperation[]): Promise { const cursorStateComputerAndInlineDiffCollection: ICursorStateComputer = (undoEdits) => { let last: Position | null = null; for (const edit of undoEdits) { @@ -268,7 +268,7 @@ export class LivePreviewStrategy extends EditModeStrategy { await this._updateDiffZones(); } - override async makeProgressiveChanges(targetWindow: Window, edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise { + override async makeProgressiveChanges(edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise { // push undo stop before first edit if (++this._editCount === 1) { @@ -287,7 +287,7 @@ export class LivePreviewStrategy extends EditModeStrategy { const wordCount = countWords(edit.text ?? ''); const speed = wordCount / durationInSec; // console.log({ durationInSec, wordCount, speed: wordCount / durationInSec }); - await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(targetWindow, edit, speed, opts.token)); + await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(new WindowIntervalTimer(this._zone.domNode), edit, speed, opts.token)); } await renderTask; @@ -446,7 +446,7 @@ export function asAsyncEdit(edit: IIdentifiedSingleEditOperation): AsyncTextEdit } satisfies AsyncTextEdit; } -export function asProgressiveEdit(targetWindow: Window, edit: IIdentifiedSingleEditOperation, wordsPerSec: number, token: CancellationToken): AsyncTextEdit { +export function asProgressiveEdit(interval: IntervalTimer, edit: IIdentifiedSingleEditOperation, wordsPerSec: number, token: CancellationToken): AsyncTextEdit { wordsPerSec = Math.max(10, wordsPerSec); @@ -454,13 +454,13 @@ export function asProgressiveEdit(targetWindow: Window, edit: IIdentifiedSingleE let newText = edit.text ?? ''; // const wordCount = countWords(newText); - const handle = disposableWindowInterval(targetWindow, () => { + interval.cancelAndSet(() => { const r = getNWords(newText, 1); stream.emitOne(r.value); newText = newText.substring(r.value.length); if (r.isFullString) { - handle.dispose(); + interval.cancel(); stream.resolve(); d.dispose(); } @@ -469,7 +469,7 @@ export function asProgressiveEdit(targetWindow: Window, edit: IIdentifiedSingleE // cancel ASAP const d = token.onCancellationRequested(() => { - handle.dispose(); + interval.cancel(); stream.resolve(); d.dispose(); }); @@ -589,15 +589,15 @@ export class LiveStrategy extends EditModeStrategy { await undoModelUntil(textModelN, altVersionId); } - override async makeChanges(targetWindow: Window, edits: ISingleEditOperation[]): Promise { - return this._makeChanges(targetWindow, edits, undefined); + override async makeChanges(edits: ISingleEditOperation[]): Promise { + return this._makeChanges(edits, undefined); } - override async makeProgressiveChanges(targetWindow: Window, edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise { - return this._makeChanges(targetWindow, edits, opts); + override async makeProgressiveChanges(edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise { + return this._makeChanges(edits, opts); } - private async _makeChanges(targetWindow: Window, edits: ISingleEditOperation[], opts: ProgressingEditsOptions | undefined): Promise { + private async _makeChanges(edits: ISingleEditOperation[], opts: ProgressingEditsOptions | undefined): Promise { // push undo stop before first edit if (++this._editCount === 1) { @@ -630,7 +630,7 @@ export class LiveStrategy extends EditModeStrategy { const wordCount = countWords(edit.text ?? ''); const speed = wordCount / durationInSec; // console.log({ durationInSec, wordCount, speed: wordCount / durationInSec }); - await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(targetWindow, edit, speed, opts.token), progress); + await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(new WindowIntervalTimer(this._zone.domNode), edit, speed, opts.token), progress); } } else { diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatStrategies.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatStrategies.test.ts new file mode 100644 index 00000000000..d91aa020190 --- /dev/null +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatStrategies.test.ts @@ -0,0 +1,75 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IntervalTimer } from 'vs/base/common/async'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { asProgressiveEdit } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; +import * as assert from 'assert'; + + +suite('AsyncEdit', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('asProgressiveEdit', async () => { + const interval = new IntervalTimer(); + const edit = { + range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, + text: 'Hello, world!' + }; + + const cts = new CancellationTokenSource(); + const result = asProgressiveEdit(interval, edit, 5, cts.token); + + // Verify the range + assert.deepStrictEqual(result.range, edit.range); + + const iter = result.newText[Symbol.asyncIterator](); + + // Verify the newText + const a = await iter.next(); + assert.strictEqual(a.value, 'Hello,'); + assert.strictEqual(a.done, false); + + // Verify the next word + const b = await iter.next(); + assert.strictEqual(b.value, ' world!'); + assert.strictEqual(b.done, false); + + const c = await iter.next(); + assert.strictEqual(c.value, undefined); + assert.strictEqual(c.done, true); + + cts.dispose(); + }); + + test('asProgressiveEdit - cancellation', async () => { + const interval = new IntervalTimer(); + const edit = { + range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, + text: 'Hello, world!' + }; + + const cts = new CancellationTokenSource(); + const result = asProgressiveEdit(interval, edit, 5, cts.token); + + // Verify the range + assert.deepStrictEqual(result.range, edit.range); + + const iter = result.newText[Symbol.asyncIterator](); + + // Verify the newText + const a = await iter.next(); + assert.strictEqual(a.value, 'Hello,'); + assert.strictEqual(a.done, false); + + cts.dispose(true); + + const c = await iter.next(); + assert.strictEqual(c.value, undefined); + assert.strictEqual(c.done, true); + }); +}); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts index c9412d493ed..c12e5ceb2e1 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Dimension, getWindow, h } from 'vs/base/browser/dom'; +import { Dimension, WindowIntervalTimer, h } from 'vs/base/browser/dom'; import { CancelablePromise, Queue, createCancelablePromise, raceCancellationError } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; @@ -469,7 +469,7 @@ class EditStrategy { const wordCount = countWords(edit.text ?? ''); const speed = wordCount / durationInSec; // console.log({ durationInSec, wordCount, speed: wordCount / durationInSec }); - await performAsyncTextEdit(editor.getModel(), asProgressiveEdit(getWindow(editor.getContainerDomNode()), edit, speed, opts.token)); + await performAsyncTextEdit(editor.getModel(), asProgressiveEdit(new WindowIntervalTimer(), edit, speed, opts.token)); } } From b6d977789e3697ba2bcb1afedfc57949bb8704af Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 10 Jan 2024 15:48:23 +0100 Subject: [PATCH 0201/1897] Screen cheese when reloading with a notebook in a floating window (fix #201832) (#202159) --- .../parts/editor/auxiliaryEditorPart.ts | 4 ++-- .../workbench/browser/parts/editor/editor.ts | 2 +- .../browser/parts/editor/editorGroupView.ts | 6 +++++- .../browser/parts/editor/editorPart.ts | 9 +++++---- .../contrib/notebook/browser/notebookEditor.ts | 18 ++---------------- .../editor/common/editorGroupsService.ts | 5 +++++ .../test/browser/workbenchTestServices.ts | 3 ++- 7 files changed, 22 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts index 272979d0555..6e7b8c352b7 100644 --- a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts @@ -195,7 +195,7 @@ class AuxiliaryEditorPartImpl extends EditorPart implements IAuxiliaryEditorPart readonly onWillClose = this._onWillClose.event; constructor( - readonly windowId: number, + windowId: number, editorPartsView: IEditorPartsView, private readonly state: IEditorPartUIState | undefined, groupsLabel: string, @@ -208,7 +208,7 @@ class AuxiliaryEditorPartImpl extends EditorPart implements IAuxiliaryEditorPart @IContextKeyService contextKeyService: IContextKeyService ) { const id = AuxiliaryEditorPartImpl.COUNTER++; - super(editorPartsView, `workbench.parts.auxiliaryEditor.${id}`, groupsLabel, true, instantiationService, themeService, configurationService, storageService, layoutService, hostService, contextKeyService); + super(editorPartsView, `workbench.parts.auxiliaryEditor.${id}`, groupsLabel, windowId, instantiationService, themeService, configurationService, storageService, layoutService, hostService, contextKeyService); } override removeGroup(group: number | IEditorGroupView, preserveFocus?: boolean): void { diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index b8c41c45d72..8accd2f29dc 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -192,7 +192,7 @@ export interface IEditorPartsView { */ export interface IEditorGroupsView { - readonly isAuxiliary: boolean; + readonly windowId: number; readonly groups: IEditorGroupView[]; readonly activeGroup: IEditorGroupView; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index a51faaefc23..acd91eb9c33 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -140,7 +140,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { constructor( from: IEditorGroupView | ISerializedEditorGroupModel | null, private readonly editorPartsView: IEditorPartsView, - public readonly groupsView: IEditorGroupsView, + readonly groupsView: IEditorGroupsView, private groupsLabel: string, private _index: number, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -850,6 +850,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this.model.id; } + get windowId(): number { + return this.groupsView.windowId; + } + get editors(): EditorInput[] { return this.model.getEditors(EditorsOrder.SEQUENTIAL); } diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 48818240c36..c50d1aa9b75 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -35,6 +35,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { EditorPartMaximizedEditorGroupContext, EditorPartMultipleEditorGroupsContext, IsAuxiliaryEditorPartContext } from 'vs/workbench/common/contextkeys'; +import { mainWindow } from 'vs/base/browser/window'; export interface IEditorPartUIState { readonly serializedGrid: ISerializedGrid; @@ -155,7 +156,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { protected readonly editorPartsView: IEditorPartsView, id: string, private readonly groupsLabel: string, - public readonly isAuxiliary: boolean, + readonly windowId: number, @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -965,7 +966,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { this.element = parent; this.container = document.createElement('div'); this.container.classList.add('content'); - if (this.isAuxiliary) { + if (this.windowId !== mainWindow.vscodeWindowId) { this.container.classList.add('auxiliary'); } parent.appendChild(this.container); @@ -1004,7 +1005,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { private handleContextKeys(contextKeyService: IContextKeyService): void { const isAuxiliaryEditorPartContext = IsAuxiliaryEditorPartContext.bindTo(contextKeyService); - isAuxiliaryEditorPartContext.set(this.isAuxiliary); + isAuxiliaryEditorPartContext.set(this.windowId !== mainWindow.vscodeWindowId); const multipleEditorGroupsContext = EditorPartMultipleEditorGroupsContext.bindTo(contextKeyService); const maximizedEditorGroupContext = EditorPartMaximizedEditorGroupContext.bindTo(contextKeyService); @@ -1432,6 +1433,6 @@ export class MainEditorPart extends EditorPart { @IHostService hostService: IHostService, @IContextKeyService contextKeyService: IContextKeyService ) { - super(editorPartsView, Parts.EDITOR_PART, '', false, instantiationService, themeService, configurationService, storageService, layoutService, hostService, contextKeyService); + super(editorPartsView, Parts.EDITOR_PART, '', mainWindow.vscodeWindowId, instantiationService, themeService, configurationService, storageService, layoutService, hostService, contextKeyService); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index d041b44578a..694914c9946 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -36,7 +36,7 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { NOTEBOOK_EDITOR_ID, NotebookWorkingCopyTypeIdentifier } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { NotebookPerfMarks } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; -import { GroupsOrder, IAuxiliaryEditorPart, IEditorGroup, IEditorGroupsService, IEditorPart } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { GroupsOrder, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; @@ -48,7 +48,6 @@ import { streamToBuffer } from 'vs/base/common/buffer'; import { ILogService } from 'vs/platform/log/common/log'; import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState'; @@ -207,20 +206,7 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { // we need to hide it before getting a new widget this._widget.value?.onWillHide(); - let codeWindow = this._rootElement ? DOM.getWindow(this._rootElement) : undefined; - if (this.group) { - // get matching part - // TODO this._editorGroupService.getPart(this.group.id) is always returning main part on window reload - const groupView = (this.group as IEditorGroupView).groupsView as unknown as (IEditorPart | unknown); - const currentPart = this._editorGroupService.parts.find(part => part === groupView); - - // get window id - if (currentPart && (currentPart as IAuxiliaryEditorPart).windowId) { - codeWindow = DOM.getWindowById((currentPart as IAuxiliaryEditorPart).windowId)?.window; - } - } - - this._widget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, group, input, undefined, this._pagePosition?.dimension, codeWindow ?? mainWindow); + this._widget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, group, input, undefined, this._pagePosition?.dimension, DOM.getWindowById(group.windowId)?.window ?? mainWindow); if (this._rootElement && this._widget.value!.getDomNode()) { this._rootElement.setAttribute('aria-flowto', this._widget.value!.getDomNode().id || ''); diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 3ab10751b59..f2d29edc549 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -589,6 +589,11 @@ export interface IEditorGroup { */ readonly id: GroupIdentifier; + /** + * The identifier of the `CodeWindow` this editor group is part of. + */ + readonly windowId: number; + /** * A number that indicates the position of this group in the visual * order of groups from left to right and top to bottom. The lowest diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index bc429501b51..4ba2d0fb3ab 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -893,6 +893,7 @@ export class TestEditorGroupView implements IEditorGroupView { constructor(public id: number) { } + windowId = mainWindow.vscodeWindowId; groupsView: IEditorGroupsView = undefined!; activeEditorPane!: IVisibleEditorPane; activeEditor!: EditorInput; @@ -966,7 +967,7 @@ export class TestEditorGroupView implements IEditorGroupView { export class TestEditorGroupAccessor implements IEditorGroupsView { label: string = ''; - isAuxiliary: boolean = false; + windowId = mainWindow.vscodeWindowId; groups: IEditorGroupView[] = []; activeGroup!: IEditorGroupView; From 74da22bb03e6945bd785582dfd58bbeac166dd0d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 10 Jan 2024 15:49:48 +0100 Subject: [PATCH 0202/1897] ENOSPC though no workspace (fix #201923) (#202161) --- src/vs/workbench/contrib/files/browser/workspaceWatcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts b/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts index f9100a0eb4d..0ac5922236c 100644 --- a/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts +++ b/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts @@ -73,7 +73,7 @@ export class WorkspaceWatcher extends Disposable { if (msg.indexOf('ENOSPC') >= 0) { this.notificationService.prompt( Severity.Warning, - localize('enospcError', "Unable to watch for file changes in this large workspace folder. Please follow the instructions link to resolve this issue."), + localize('enospcError', "Unable to watch for file changes in this workspace. Please follow the instructions link to resolve this issue."), [{ label: localize('learnMore', "Instructions"), run: () => this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?linkid=867693')) From 1e9f0887fb24c03b3f7ac959301ed6aacd2d88b4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 10 Jan 2024 16:03:25 +0100 Subject: [PATCH 0203/1897] window - push down static method to base (#202162) --- src/vs/workbench/browser/window.ts | 48 +++++++++++++++---- src/vs/workbench/electron-sandbox/window.ts | 29 +---------- .../auxiliaryWindowService.ts | 3 +- 3 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index f0db3ec3276..9d5416d007c 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -11,7 +11,7 @@ import { timeout } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { matchesScheme, Schemas } from 'vs/base/common/network'; -import { isIOS } from 'vs/base/common/platform'; +import { isIOS, isMacintosh } from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; @@ -24,11 +24,12 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { BrowserLifecycleService } from 'vs/workbench/services/lifecycle/browser/lifecycleService'; -import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { ILifecycleService, ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { registerWindowDriver } from 'vs/workbench/services/driver/browser/driver'; import { CodeWindow, isAuxiliaryWindow, mainWindow } from 'vs/base/browser/window'; import { createSingleCallFunction } from 'vs/base/common/functional'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export abstract class BaseWindow extends Disposable { @@ -75,13 +76,13 @@ export abstract class BaseWindow extends Disposable { //#region timeout handling in multi-window applications - /** - * Override `setTimeout` and `clearTimeout` on the provided window to make - * sure timeouts are dispatched to all opened windows. Some browsers may decide - * to throttle timeouts in minimized windows, so with this we can ensure the - * timeout is scheduled without being throttled (unless all windows are minimized). - */ private enableMultiWindowAwareTimeout(targetWindow: Window, dom = { getWindowsCount, getWindows }): void { + + // Override `setTimeout` and `clearTimeout` on the provided window to make + // sure timeouts are dispatched to all opened windows. Some browsers may decide + // to throttle timeouts in minimized windows, so with this we can ensure the + // timeout is scheduled without being throttled (unless all windows are minimized). + const originalSetTimeout = targetWindow.setTimeout; Object.defineProperty(targetWindow, 'vscodeOriginalSetTimeout', { get: () => originalSetTimeout }); @@ -144,6 +145,37 @@ export abstract class BaseWindow extends Disposable { } })); } + + //#region Confirm on Shutdown + + static async confirmOnShutdown(accessor: ServicesAccessor, reason: ShutdownReason): Promise { + const dialogService = accessor.get(IDialogService); + const configurationService = accessor.get(IConfigurationService); + + const message = reason === ShutdownReason.QUIT ? + (isMacintosh ? localize('quitMessageMac', "Are you sure you want to quit?") : localize('quitMessage', "Are you sure you want to exit?")) : + localize('closeWindowMessage', "Are you sure you want to close the window?"); + const primaryButton = reason === ShutdownReason.QUIT ? + (isMacintosh ? localize({ key: 'quitButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Quit") : localize({ key: 'exitButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Exit")) : + localize({ key: 'closeWindowButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Close Window"); + + const res = await dialogService.confirm({ + message, + primaryButton, + checkbox: { + label: localize('doNotAskAgain', "Do not ask me again") + } + }); + + // Update setting if checkbox checked + if (res.confirmed && res.checkboxChecked) { + await configurationService.updateValue('window.confirmBeforeClose', 'never'); + } + + return res.confirmed; + } + + //#endregion } export class BrowserWindow extends BaseWindow { diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 1a014842112..1e9ca9d0cd7 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -57,7 +57,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { AuthInfo } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes'; import { ILogService } from 'vs/platform/log/common/log'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { whenEditorClosed } from 'vs/workbench/browser/editor'; import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; @@ -472,33 +472,6 @@ export class NativeWindow extends BaseWindow { }); } - static async confirmOnShutdown(accessor: ServicesAccessor, reason: ShutdownReason): Promise { - const dialogService = accessor.get(IDialogService); - const configurationService = accessor.get(IConfigurationService); - - const message = reason === ShutdownReason.QUIT ? - (isMacintosh ? localize('quitMessageMac', "Are you sure you want to quit?") : localize('quitMessage', "Are you sure you want to exit?")) : - localize('closeWindowMessage', "Are you sure you want to close the window?"); - const primaryButton = reason === ShutdownReason.QUIT ? - (isMacintosh ? localize({ key: 'quitButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Quit") : localize({ key: 'exitButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Exit")) : - localize({ key: 'closeWindowButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Close Window"); - - const res = await dialogService.confirm({ - message, - primaryButton, - checkbox: { - label: localize('doNotAskAgain', "Do not ask me again") - } - }); - - // Update setting if checkbox checked - if (res.confirmed && res.checkboxChecked) { - await configurationService.updateValue('window.confirmBeforeClose', 'never'); - } - - return res.confirmed; - } - private onBeforeShutdownError({ error, reason }: BeforeShutdownErrorEvent): void { this.dialogService.error(this.toShutdownLabel(reason, true), localize('shutdownErrorDetail', "Error: {0}", toErrorMessage(error))); } diff --git a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts index b7e1b0ef394..d07c6d8addf 100644 --- a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts @@ -14,7 +14,6 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { CodeWindow } from 'vs/base/browser/window'; import { mark } from 'vs/base/common/performance'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; import { ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Barrier } from 'vs/base/common/async'; @@ -52,7 +51,7 @@ export class NativeAuxiliaryWindow extends AuxiliaryWindow { e.preventDefault(); e.returnValue = true; - const confirmed = await this.instantiationService.invokeFunction(accessor => NativeWindow.confirmOnShutdown(accessor, ShutdownReason.CLOSE)); + const confirmed = await this.instantiationService.invokeFunction(accessor => NativeAuxiliaryWindow.confirmOnShutdown(accessor, ShutdownReason.CLOSE)); if (confirmed) { this.skipUnloadConfirmation = true; this.nativeHostService.closeWindow({ targetWindowId: this.window.vscodeWindowId }); From 6fced49205b07265deb4c582887a276379e02ced Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 10 Jan 2024 09:07:15 -0800 Subject: [PATCH 0204/1897] testing: update testingActiveProfile proposal to final implementation (#202116) * testing: update testingActiveProfile proposal to final implementation * update tests --- .../api/browser/mainThreadTesting.ts | 12 +- .../workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostTesting.ts | 83 ++++++---- .../api/test/browser/extHostTesting.test.ts | 28 ++-- .../testing/common/testProfileService.ts | 145 +++++++++++------- .../test/common/testProfileService.test.ts | 36 +++-- src/vscode-dts/vscode.d.ts | 3 + .../vscode.proposed.testingActiveProfile.d.ts | 11 +- 8 files changed, 185 insertions(+), 135 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadTesting.ts b/src/vs/workbench/api/browser/mainThreadTesting.ts index e61a62a53b8..fa5376ca4b1 100644 --- a/src/vs/workbench/api/browser/mainThreadTesting.ts +++ b/src/vs/workbench/api/browser/mainThreadTesting.ts @@ -19,7 +19,7 @@ import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testPro import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult'; import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; import { IMainThreadTestController, ITestRootProvider, ITestService } from 'vs/workbench/contrib/testing/common/testService'; -import { CoverageDetails, ExtensionRunTestsRequest, IFileCoverage, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, TestResultState, TestRunProfileBitset, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes'; +import { CoverageDetails, ExtensionRunTestsRequest, IFileCoverage, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, TestResultState, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { ExtHostContext, ExtHostTestingShape, ILocationDto, ITestControllerPatch, MainContext, MainThreadTestingShape } from '../common/extHost.protocol'; @@ -49,18 +49,12 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh })); this._register(Event.debounce(testProfiles.onDidChange, (_last, e) => e)(() => { - const defaults = new Set([ - ...testProfiles.getGroupDefaultProfiles(TestRunProfileBitset.Run), - ...testProfiles.getGroupDefaultProfiles(TestRunProfileBitset.Debug), - ...testProfiles.getGroupDefaultProfiles(TestRunProfileBitset.Coverage), - ]); - const obj: Record = {}; for (const { controller, profiles } of this.testProfiles.all()) { - obj[controller.id] = profiles.filter(p => defaults.has(p)).map(p => p.profileId); + obj[controller.id] = profiles.filter(p => p.isDefault).map(p => p.profileId); } - this.proxy.$setActiveRunProfiles(obj); + this.proxy.$setDefaultRunProfiles(obj); })); this._register(resultService.onResultsChanged(evt => { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 646e6791659..22dcca3d955 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2641,7 +2641,7 @@ export interface ExtHostTestingShape { /** Ensures any pending test diffs are flushed */ $syncTests(): Promise; /** Sets the active test run profiles */ - $setActiveRunProfiles(profiles: Record): void; + $setDefaultRunProfiles(profiles: Record): void; } export interface ExtHostLocalizationShape { diff --git a/src/vs/workbench/api/common/extHostTesting.ts b/src/vs/workbench/api/common/extHostTesting.ts index 22c3ec80d4f..fa062b02547 100644 --- a/src/vs/workbench/api/common/extHostTesting.ts +++ b/src/vs/workbench/api/common/extHostTesting.ts @@ -28,7 +28,7 @@ import { TestRunProfileKind, TestRunRequest } from 'vs/workbench/api/common/extH import { TestCommandId } from 'vs/workbench/contrib/testing/common/constants'; import { TestId, TestIdPathParts, TestPosition } from 'vs/workbench/contrib/testing/common/testId'; import { InvalidTestItemError } from 'vs/workbench/contrib/testing/common/testItemCollection'; -import { AbstractIncrementalTestCollection, CoverageDetails, ICallProfileRunHandler, IFileCoverage, ISerializedTestResults, IStartControllerTests, IStartControllerTestsResult, ITestErrorMessage, ITestItem, ITestItemContext, ITestMessageMenuArgs, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, KEEP_N_LAST_COVERAGE_REPORTS, TestResultState, TestRunProfileBitset, TestsDiff, TestsDiffOp, isStartControllerTests } from 'vs/workbench/contrib/testing/common/testTypes'; +import { AbstractIncrementalTestCollection, CoverageDetails, ICallProfileRunHandler, IFileCoverage, ISerializedTestResults, IStartControllerTests, IStartControllerTestsResult, ITestErrorMessage, ITestItem, ITestItemContext, ITestMessageMenuArgs, ITestRunProfile, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, KEEP_N_LAST_COVERAGE_REPORTS, TestResultState, TestRunProfileBitset, TestsDiff, TestsDiffOp, isStartControllerTests } from 'vs/workbench/contrib/testing/common/testTypes'; import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import type * as vscode from 'vscode'; @@ -40,7 +40,7 @@ interface ControllerInfo { activeProfiles: Set; } -type ActiveProfileChangeEvent = Map>; +type DefaultProfileChangeEvent = Map>; export class ExtHostTesting extends Disposable implements ExtHostTestingShape { private readonly resultsChangedEmitter = this._register(new Emitter()); @@ -48,7 +48,7 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { private readonly proxy: MainThreadTestingShape; private readonly runTracker: TestRunCoordinator; private readonly observer: TestObservers; - private readonly activeProfilesChangedEmitter = this._register(new Emitter()); + private readonly defaultProfilesChangedEmitter = this._register(new Emitter()); public onResultsChanged = this.resultsChangedEmitter.event; public results: ReadonlyArray = []; @@ -148,7 +148,7 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { profileId++; } - return new TestRunProfileImpl(this.proxy, profiles, extension, activeProfiles, this.activeProfilesChangedEmitter.event, controllerId, profileId, label, group, runHandler, isDefault, tag, supportsContinuousRun); + return new TestRunProfileImpl(this.proxy, profiles, extension, activeProfiles, this.defaultProfilesChangedEmitter.event, controllerId, profileId, label, group, runHandler, isDefault, tag, supportsContinuousRun); }, createTestItem(id, label, uri) { return new TestItemImpl(controllerId, id, label, uri); @@ -256,8 +256,8 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { } /** @inheritdoc */ - $setActiveRunProfiles(profiles: Record): void { - const evt: ActiveProfileChangeEvent = new Map(); + $setDefaultRunProfiles(profiles: Record): void { + const evt: DefaultProfileChangeEvent = new Map(); for (const [controllerId, profileIds] of Object.entries(profiles)) { const ctrl = this.controllers.get(controllerId); if (!ctrl) { @@ -279,7 +279,7 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { } } - this.activeProfilesChangedEmitter.fire(evt); + this.defaultProfilesChangedEmitter.fire(evt); } /** @inheritdoc */ @@ -1058,11 +1058,20 @@ class TestObservers { } } +const updateProfile = (impl: TestRunProfileImpl, proxy: MainThreadTestingShape, initial: ITestRunProfile | undefined, update: Partial) => { + if (initial) { + Object.assign(initial, update); + } else { + proxy.$updateTestRunConfig(impl.controllerId, impl.profileId, update); + } +}; + export class TestRunProfileImpl implements vscode.TestRunProfile { readonly #proxy: MainThreadTestingShape; readonly #extension: IRelaxedExtensionDescription; - readonly #activeProfiles: ReadonlySet; - readonly #onDidChangeActiveProfiles: Event; + readonly #activeProfiles: Set; + readonly #onDidChangeDefaultProfiles: Event; + #initialPublish?: ITestRunProfile; #profiles?: Map; private _configureHandler?: (() => void); @@ -1073,7 +1082,7 @@ export class TestRunProfileImpl implements vscode.TestRunProfile { public set label(label: string) { if (label !== this._label) { this._label = label; - this.#proxy.$updateTestRunConfig(this.controllerId, this.profileId, { label }); + updateProfile(this, this.#proxy, this.#initialPublish, { label }); } } @@ -1084,18 +1093,25 @@ export class TestRunProfileImpl implements vscode.TestRunProfile { public set supportsContinuousRun(supports: boolean) { if (supports !== this._supportsContinuousRun) { this._supportsContinuousRun = supports; - this.#proxy.$updateTestRunConfig(this.controllerId, this.profileId, { supportsContinuousRun: supports }); + updateProfile(this, this.#proxy, this.#initialPublish, { supportsContinuousRun: supports }); } } public get isDefault() { - return this._isDefault; + return this.#activeProfiles.has(this.profileId); } public set isDefault(isDefault: boolean) { - if (isDefault !== this._isDefault) { - this._isDefault = isDefault; - this.#proxy.$updateTestRunConfig(this.controllerId, this.profileId, { isDefault }); + if (isDefault !== this.isDefault) { + // #activeProfiles is synced from the main thread, so we can make + // provisional changes here that will get confirmed momentarily + if (isDefault) { + this.#activeProfiles.add(this.profileId); + } else { + this.#activeProfiles.delete(this.profileId); + } + + updateProfile(this, this.#proxy, this.#initialPublish, { isDefault }); } } @@ -1106,7 +1122,7 @@ export class TestRunProfileImpl implements vscode.TestRunProfile { public set tag(tag: vscode.TestTag | undefined) { if (tag?.id !== this._tag?.id) { this._tag = tag; - this.#proxy.$updateTestRunConfig(this.controllerId, this.profileId, { + updateProfile(this, this.#proxy, this.#initialPublish, { tag: tag ? Convert.TestTag.namespace(this.controllerId, tag.id) : null, }); } @@ -1119,18 +1135,13 @@ export class TestRunProfileImpl implements vscode.TestRunProfile { public set configureHandler(handler: undefined | (() => void)) { if (handler !== this._configureHandler) { this._configureHandler = handler; - this.#proxy.$updateTestRunConfig(this.controllerId, this.profileId, { hasConfigurationHandler: !!handler }); + updateProfile(this, this.#proxy, this.#initialPublish, { hasConfigurationHandler: !!handler }); } } - public get isSelected() { + public get onDidChangeDefault() { checkProposedApiEnabled(this.#extension, 'testingActiveProfile'); - return this.#activeProfiles.has(this.profileId); - } - - public get onDidChangeSelected() { - checkProposedApiEnabled(this.#extension, 'testingActiveProfile'); - return Event.chain(this.#onDidChangeActiveProfiles, $ => $ + return Event.chain(this.#onDidChangeDefaultProfiles, $ => $ .map(ev => ev.get(this.controllerId)?.get(this.profileId)) .filter(isDefined) ); @@ -1140,14 +1151,14 @@ export class TestRunProfileImpl implements vscode.TestRunProfile { proxy: MainThreadTestingShape, profiles: Map, extension: IRelaxedExtensionDescription, - activeProfiles: ReadonlySet, - onDidChangeActiveProfiles: Event, + activeProfiles: Set, + onDidChangeActiveProfiles: Event, public readonly controllerId: string, public readonly profileId: number, private _label: string, public readonly kind: vscode.TestRunProfileKind, public runHandler: (request: vscode.TestRunRequest, token: vscode.CancellationToken) => Thenable | void, - private _isDefault = false, + _isDefault = false, public _tag: vscode.TestTag | undefined = undefined, private _supportsContinuousRun = false, ) { @@ -1155,7 +1166,7 @@ export class TestRunProfileImpl implements vscode.TestRunProfile { this.#profiles = profiles; this.#extension = extension; this.#activeProfiles = activeProfiles; - this.#onDidChangeActiveProfiles = onDidChangeActiveProfiles; + this.#onDidChangeDefaultProfiles = onDidChangeActiveProfiles; profiles.set(profileId, this); const groupBitset = profileGroupToBitset[kind]; @@ -1163,7 +1174,11 @@ export class TestRunProfileImpl implements vscode.TestRunProfile { throw new Error(`Unknown TestRunProfile.group ${kind}`); } - this.#proxy.$publishTestRunProfile({ + if (_isDefault) { + activeProfiles.add(profileId); + } + + this.#initialPublish = { profileId: profileId, controllerId, tag: _tag ? Convert.TestTag.namespace(this.controllerId, _tag.id) : null, @@ -1172,6 +1187,15 @@ export class TestRunProfileImpl implements vscode.TestRunProfile { isDefault: _isDefault, hasConfigurationHandler: false, supportsContinuousRun: _supportsContinuousRun, + }; + + // we send the initial profile publish out on the next microtask so that + // initially setting the isDefault value doesn't overwrite a user-configured value + queueMicrotask(() => { + if (this.#initialPublish) { + this.#proxy.$publishTestRunProfile(this.#initialPublish); + this.#initialPublish = undefined; + } }); } @@ -1180,6 +1204,7 @@ export class TestRunProfileImpl implements vscode.TestRunProfile { this.#profiles = undefined; this.#proxy.$removeTestProfile(this.controllerId, this.profileId); } + this.#initialPublish = undefined; } } diff --git a/src/vs/workbench/api/test/browser/extHostTesting.test.ts b/src/vs/workbench/api/test/browser/extHostTesting.test.ts index c788ae54073..62df7ae029f 100644 --- a/src/vs/workbench/api/test/browser/extHostTesting.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTesting.test.ts @@ -917,19 +917,19 @@ suite('ExtHost Testing', () => { const neverCalled = sinon.stub(); // empty default state: - assert.deepStrictEqual(profAA.isSelected, false); - assert.deepStrictEqual(profBA.isSelected, false); - assert.deepStrictEqual(profBB.isSelected, false); + assert.deepStrictEqual(profAA.isDefault, false); + assert.deepStrictEqual(profBA.isDefault, false); + assert.deepStrictEqual(profBB.isDefault, false); // fires a change event: - const changeA = Event.toPromise(profAA.onDidChangeSelected as Event); - const changeBA = Event.toPromise(profBA.onDidChangeSelected as Event); - const changeBB = Event.toPromise(profBB.onDidChangeSelected as Event); + const changeA = Event.toPromise(profAA.onDidChangeDefault as Event); + const changeBA = Event.toPromise(profBA.onDidChangeDefault as Event); + const changeBB = Event.toPromise(profBB.onDidChangeDefault as Event); - ds.add(profAB.onDidChangeSelected(neverCalled)); + ds.add(profAB.onDidChangeDefault(neverCalled)); assert.strictEqual(neverCalled.called, false); - ctrl.$setActiveRunProfiles({ + ctrl.$setDefaultRunProfiles({ a: [ctrl.getProfileInternalId(ctrlA, profAA)], b: [ctrl.getProfileInternalId(ctrlB, profBA), ctrl.getProfileInternalId(ctrlB, profBB)] }); @@ -939,14 +939,14 @@ suite('ExtHost Testing', () => { assert.deepStrictEqual(await changeBB, true); // updates internal state: - assert.deepStrictEqual(profAA.isSelected, true); - assert.deepStrictEqual(profBA.isSelected, true); - assert.deepStrictEqual(profBB.isSelected, true); - assert.deepStrictEqual(profAB.isSelected, false); + assert.deepStrictEqual(profAA.isDefault, true); + assert.deepStrictEqual(profBA.isDefault, true); + assert.deepStrictEqual(profBB.isDefault, true); + assert.deepStrictEqual(profAB.isDefault, false); // no-ops if equal - ds.add(profAA.onDidChangeSelected(neverCalled)); - ctrl.$setActiveRunProfiles({ + ds.add(profAA.onDidChangeDefault(neverCalled)); + ctrl.$setDefaultRunProfiles({ a: [ctrl.getProfileInternalId(ctrlA, profAA)], }); assert.strictEqual(neverCalled.called, false); diff --git a/src/vs/workbench/contrib/testing/common/testProfileService.ts b/src/vs/workbench/contrib/testing/common/testProfileService.ts index 4c0d99bdf8c..adf73855e6c 100644 --- a/src/vs/workbench/contrib/testing/common/testProfileService.ts +++ b/src/vs/workbench/contrib/testing/common/testProfileService.ts @@ -4,16 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { isDefined } from 'vs/base/common/types'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { deepClone } from 'vs/base/common/objects'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue'; -import { InternalTestItem, ITestRunProfile, TestRunProfileBitset, testRunProfileBitsetList } from 'vs/workbench/contrib/testing/common/testTypes'; import { TestId } from 'vs/workbench/contrib/testing/common/testId'; -import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; import { IMainThreadTestController } from 'vs/workbench/contrib/testing/common/testService'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { ITestRunProfile, InternalTestItem, TestRunProfileBitset, testRunProfileBitsetList } from 'vs/workbench/contrib/testing/common/testTypes'; +import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; export const ITestProfileService = createDecorator('testProfileService'); @@ -91,6 +91,10 @@ const sorter = (a: ITestRunProfile, b: ITestRunProfile) => { return a.label.localeCompare(b.label); }; +interface IExtendedTestRunProfile extends ITestRunProfile { + wasInitiallyDefault: boolean; +} + /** * Given a capabilities bitset, returns a map of context keys representing * them. @@ -101,13 +105,15 @@ export const capabilityContextKeys = (capabilities: number): [key: string, value [TestingContextKeys.hasCoverableTests.key, (capabilities & TestRunProfileBitset.Coverage) !== 0], ]; +type DefaultsMap = { [controllerId: string]: { [profileId: number]: /* isDefault */ boolean } }; + export class TestProfileService extends Disposable implements ITestProfileService { declare readonly _serviceBrand: undefined; - private readonly preferredDefaults: StoredValue<{ [K in TestRunProfileBitset]?: { controllerId: string; profileId: number }[] }>; + private readonly userDefaults: StoredValue; private readonly capabilitiesContexts: { [K in TestRunProfileBitset]: IContextKey }; private readonly changeEmitter = this._register(new Emitter()); private readonly controllerProfiles = new Map(); @@ -120,8 +126,9 @@ export class TestProfileService extends Disposable implements ITestProfileServic ) { super(); - this.preferredDefaults = this._register(new StoredValue({ - key: 'testingPreferredProfiles', + storageService.remove('testingPreferredProfiles', StorageScope.WORKSPACE); // cleanup old format + this.userDefaults = this._register(new StoredValue({ + key: 'testingPreferredProfiles2', scope: StorageScope.WORKSPACE, target: StorageTarget.MACHINE, }, storageService)); @@ -140,13 +147,20 @@ export class TestProfileService extends Disposable implements ITestProfileServic /** @inheritdoc */ public addProfile(controller: IMainThreadTestController, profile: ITestRunProfile): void { + const previousExplicitDefaultValue = this.userDefaults.get()?.[controller.id]?.[profile.profileId]; + const extended: IExtendedTestRunProfile = { + ...profile, + isDefault: previousExplicitDefaultValue ?? profile.isDefault, + wasInitiallyDefault: profile.isDefault, + }; + let record = this.controllerProfiles.get(profile.controllerId); if (record) { - record.profiles.push(profile); + record.profiles.push(extended); record.profiles.sort(sorter); } else { record = { - profiles: [profile], + profiles: [extended], controller, }; this.controllerProfiles.set(profile.controllerId, record); @@ -170,6 +184,15 @@ export class TestProfileService extends Disposable implements ITestProfileServic Object.assign(profile, update); ctrl.profiles.sort(sorter); + + // store updates is isDefault as if the user changed it (which they might + // have through some extension-contributed UI) + if (update.isDefault !== undefined) { + const map = deepClone(this.userDefaults.get({})); + setIsDefault(map, profile, update.isDefault); + this.userDefaults.store(map); + } + this.changeEmitter.fire(); } @@ -230,61 +253,57 @@ export class TestProfileService extends Disposable implements ITestProfileServic /** @inheritdoc */ public getGroupDefaultProfiles(group: TestRunProfileBitset) { - const preferred = this.preferredDefaults.get(); - if (!preferred) { - return this.getBaseDefaults(group); - } - - const profiles = preferred[group] - ?.map(p => this.controllerProfiles.get(p.controllerId)?.profiles.find( - c => c.profileId === p.profileId && c.group === group)) - .filter(isDefined); - - return profiles?.length ? profiles : this.getBaseDefaults(group); - } - - /** @inheritdoc */ - public setGroupDefaultProfiles(group: TestRunProfileBitset, profiles: ITestRunProfile[]) { - const next = { - ...this.preferredDefaults.get(), - [group]: profiles.map(c => ({ profileId: c.profileId, controllerId: c.controllerId })), - }; - - // When switching a run/debug profile, if the controller has a same-named - // profile in the other group, use that instead of anything else that was selected. - if (group === TestRunProfileBitset.Run || group === TestRunProfileBitset.Debug) { - const otherGroup = group === TestRunProfileBitset.Run ? TestRunProfileBitset.Debug : TestRunProfileBitset.Run; - - const previousDefaults = next[otherGroup] || []; - let newDefaults = previousDefaults.slice(); - for (const [ctrlId, { profiles: ctrlProfiles }] of this.controllerProfiles) { - const labels = new Set(profiles.filter(p => p.controllerId === ctrlId).map(p => p.label)); - const nextByLabels = ctrlProfiles.filter(p => labels.has(p.label) && p.group === otherGroup); - if (nextByLabels.length) { - newDefaults = newDefaults.filter(p => p.controllerId !== ctrlId); - newDefaults.push(...nextByLabels.map(p => ({ profileId: p.profileId, controllerId: p.controllerId }))); - } - } - - next[otherGroup] = newDefaults; - } - - this.preferredDefaults.store(next); - this.changeEmitter.fire(); - } - - private getBaseDefaults(group: TestRunProfileBitset) { - const defaults: ITestRunProfile[] = []; + let defaults: ITestRunProfile[] = []; for (const { profiles } of this.controllerProfiles.values()) { - const profile = profiles.find(c => c.group === group); - if (profile) { - defaults.push(profile); + defaults = defaults.concat(profiles.filter(c => c.group === group && c.isDefault)); + } + + // have *some* default profile to run if none are set otherwise + if (defaults.length === 0) { + for (const { profiles } of this.controllerProfiles.values()) { + const first = profiles.find(p => p.group === group); + if (first) { + defaults.push(first); + break; + } } } return defaults; } + /** @inheritdoc */ + public setGroupDefaultProfiles(group: TestRunProfileBitset, profiles: ITestRunProfile[]) { + const next: DefaultsMap = {}; + for (const ctrl of this.controllerProfiles.values()) { + next[ctrl.controller.id] = {}; + for (const profile of ctrl.profiles) { + if (profile.group !== group) { + continue; + } + + setIsDefault(next, profile, profiles.some(p => p.profileId === profile.profileId)); + } + + // When switching a profile, if the controller has a same-named profile in + // other groups, update those to match the enablement state as well. + for (const profile of ctrl.profiles) { + if (profile.group === group) { + continue; + } + const matching = ctrl.profiles.find(p => p.group === group && p.label === profile.label); + if (matching) { + setIsDefault(next, profile, matching.isDefault); + } + } + + ctrl.profiles.sort(sorter); + } + + this.userDefaults.store(next); + this.changeEmitter.fire(); + } + private refreshContextKeys() { let allCapabilities = 0; for (const { profiles } of this.controllerProfiles.values()) { @@ -299,3 +318,13 @@ export class TestProfileService extends Disposable implements ITestProfileServic } } } + +const setIsDefault = (map: DefaultsMap, profile: IExtendedTestRunProfile, isDefault: boolean) => { + profile.isDefault = isDefault; + map[profile.controllerId] ??= {}; + if (profile.isDefault !== profile.wasInitiallyDefault) { + map[profile.controllerId][profile.profileId] = profile.isDefault; + } else { + delete map[profile.controllerId][profile.profileId]; + } +}; diff --git a/src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts b/src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts index bbb315cfa2f..1d5771ae4bb 100644 --- a/src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts +++ b/src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts @@ -46,10 +46,14 @@ suite('Workbench - TestProfileService', () => { ...profile, }; - t.addProfile(null as any, p); + t.addProfile({ id: 'ctrlId' } as any, p); return p; }; + const assertGroupDefaults = (group: TestRunProfileBitset, expected: ITestRunProfile[]) => { + assert.deepStrictEqual(t.getGroupDefaultProfiles(group).map(p => p.label), expected.map(e => e.label)); + }; + const expectProfiles = (expected: ITestRunProfile[], actual: string[]) => { const e = expected.map(e => e.label).sort(); const a = actual.sort(); @@ -74,8 +78,8 @@ suite('Workbench - TestProfileService', () => { addProfile({ isDefault: false, group: TestRunProfileBitset.Run, label: 'd' }); t.setGroupDefaultProfiles(TestRunProfileBitset.Run, [p3]); - assert.deepStrictEqual(t.getGroupDefaultProfiles(TestRunProfileBitset.Run), [p3]); - assert.deepStrictEqual(t.getGroupDefaultProfiles(TestRunProfileBitset.Debug), [p1]); + assertGroupDefaults(TestRunProfileBitset.Run, [p3]); + assertGroupDefaults(TestRunProfileBitset.Debug, [p1]); }); test('syncs labels if same', () => { @@ -85,12 +89,12 @@ suite('Workbench - TestProfileService', () => { const p4 = addProfile({ isDefault: false, group: TestRunProfileBitset.Run, label: 'b' }); t.setGroupDefaultProfiles(TestRunProfileBitset.Run, [p3]); - assert.deepStrictEqual(t.getGroupDefaultProfiles(TestRunProfileBitset.Run), [p3]); - assert.deepStrictEqual(t.getGroupDefaultProfiles(TestRunProfileBitset.Debug), [p1]); + assertGroupDefaults(TestRunProfileBitset.Run, [p3]); + assertGroupDefaults(TestRunProfileBitset.Debug, [p1]); t.setGroupDefaultProfiles(TestRunProfileBitset.Debug, [p2]); - assert.deepStrictEqual(t.getGroupDefaultProfiles(TestRunProfileBitset.Run), [p4]); - assert.deepStrictEqual(t.getGroupDefaultProfiles(TestRunProfileBitset.Debug), [p2]); + assertGroupDefaults(TestRunProfileBitset.Run, [p4]); + assertGroupDefaults(TestRunProfileBitset.Debug, [p2]); }); test('does not mess up sync for multiple controllers', () => { @@ -107,23 +111,23 @@ suite('Workbench - TestProfileService', () => { // same profile on both t.setGroupDefaultProfiles(TestRunProfileBitset.Debug, [p3]); - assert.deepStrictEqual(t.getGroupDefaultProfiles(TestRunProfileBitset.Run), [p7]); - assert.deepStrictEqual(t.getGroupDefaultProfiles(TestRunProfileBitset.Debug), [p3]); + assertGroupDefaults(TestRunProfileBitset.Run, [p7]); + assertGroupDefaults(TestRunProfileBitset.Debug, [p3]); // different profile, other should be unaffected t.setGroupDefaultProfiles(TestRunProfileBitset.Run, [p8]); - assert.deepStrictEqual(t.getGroupDefaultProfiles(TestRunProfileBitset.Run), [p8]); - assert.deepStrictEqual(t.getGroupDefaultProfiles(TestRunProfileBitset.Debug), [p3]); + assertGroupDefaults(TestRunProfileBitset.Run, [p8]); + assertGroupDefaults(TestRunProfileBitset.Debug, [p5]); // multiple changes in one go, with unmatched c t.setGroupDefaultProfiles(TestRunProfileBitset.Debug, [p1, p2, p4]); - assert.deepStrictEqual(t.getGroupDefaultProfiles(TestRunProfileBitset.Run), [p5, p6]); - assert.deepStrictEqual(t.getGroupDefaultProfiles(TestRunProfileBitset.Debug), [p1, p2, p4]); + assertGroupDefaults(TestRunProfileBitset.Run, [p5, p6, p8]); + assertGroupDefaults(TestRunProfileBitset.Debug, [p1, p2, p4]); // identity - t.setGroupDefaultProfiles(TestRunProfileBitset.Run, [p5, p8]); - assert.deepStrictEqual(t.getGroupDefaultProfiles(TestRunProfileBitset.Run), [p5, p8]); - assert.deepStrictEqual(t.getGroupDefaultProfiles(TestRunProfileBitset.Debug), [p2, p4, p1]); + t.setGroupDefaultProfiles(TestRunProfileBitset.Run, [p5, p6, p8]); + assertGroupDefaults(TestRunProfileBitset.Run, [p5, p6, p8]); + assertGroupDefaults(TestRunProfileBitset.Debug, [p1, p2, p4]); }); }); }); diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 5fd543fde25..0bd6a16bf80 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -17142,6 +17142,9 @@ declare module 'vscode' { * the generic "run all" button, then the default profile for * {@link TestRunProfileKind.Run} will be executed, although the * user can configure this. + * + * Changes the user makes in their default profiles will be reflected + * in this property after a {@link onDidChangeDefault} event. */ isDefault: boolean; diff --git a/src/vscode-dts/vscode.proposed.testingActiveProfile.d.ts b/src/vscode-dts/vscode.proposed.testingActiveProfile.d.ts index 8bce86f3347..474489f45a6 100644 --- a/src/vscode-dts/vscode.proposed.testingActiveProfile.d.ts +++ b/src/vscode-dts/vscode.proposed.testingActiveProfile.d.ts @@ -8,14 +8,9 @@ declare module 'vscode' { export interface TestRunProfile { /** - * Whether this profile is currently selected as a default by the user + * Fired when a user has changed whether this is a default profile. The + * event contains the new value of {@link isDefault} */ - readonly isSelected: boolean; - - /** - * Fired when a user has changed whether this is a selected profile. The - * event contains the new value of {@link isSelected} - */ - onDidChangeSelected: Event; + onDidChangeDefault: Event; } } From f5952f92c0fa2c41fed4f333c26d4539d9d0a9bc Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 10 Jan 2024 09:07:40 -0800 Subject: [PATCH 0205/1897] monaco: improve handling of glyph margins (#202102) * monaco: improve handling of glyph margins In my previous PR #202048, I added another glyph margin lane. However I didn't realize in some cases the support I added was incomplete since the rendering code did not fully handle an unbounded number of lanes. Also, I noticed in existing issues with hover and click handling because some parts of VS Code still assume there's at most a single glyph widget visible. It is hard to determine which glyph widget is interacted with without checking what classes are applied to the glyph element, which is what most existing code does. And we probably shouldn't do that in e.g. the MouseHandler to determine what glyph is being clicked on. So in this PR I added a `GlyphMarginLanesModel` that contains the information necessary for rendering. I also want to expose it in a place that the MouseHandler or MouseTarget can get at, but I wasn't sure the best way to do that, any suggestions @alexdima? Implementation-wise I opted for a flag Uint8Array. A 1024 line file with decorations on the last line, it would take 384 bytes to hold decoration positions. I also tried out a Range array based implementation, but I think this is faster (for cases where there are lots of decorations) and much simpler, in exchange for higher memory usage in edge cases (very long files with decorations near the end.) * allow lanes to be persisted to avoid jumping bp icons --- src/vs/editor/browser/view.ts | 45 ++++------- .../viewParts/glyphMargin/glyphLanesModel.ts | 61 +++++++++++++++ .../viewParts/glyphMargin/glyphMargin.ts | 39 ++++++---- src/vs/editor/common/model.ts | 18 +++++ src/vs/editor/common/model/textModel.ts | 2 + .../test/browser/view/glyphLanesModel.test.ts | 75 +++++++++++++++++++ src/vs/monaco.d.ts | 16 ++++ .../browser/codeCoverageDecorations.ts | 4 +- .../testing/browser/testing.contribution.ts | 2 + 9 files changed, 215 insertions(+), 47 deletions(-) create mode 100644 src/vs/editor/browser/viewParts/glyphMargin/glyphLanesModel.ts create mode 100644 src/vs/editor/test/browser/view/glyphLanesModel.test.ts diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index 04defbed5db..c8dc8cefa27 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -56,6 +56,7 @@ import { GlyphMarginWidgets } from 'vs/editor/browser/viewParts/glyphMargin/glyp import { GlyphMarginLane } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { CodeWindow } from 'vs/base/browser/window'; +import { GlyphMarginLanesModel } from 'vs/editor/browser/viewParts/glyphMargin/glyphLanesModel'; export interface IContentWidgetData { @@ -243,54 +244,37 @@ export class View extends ViewEventHandler { this._pointerHandler = this._register(new PointerHandler(this._context, viewController, this._createPointerHandlerHelper())); } - private _computeGlyphMarginLaneCount(): number { + private _computeGlyphMarginLanes(): GlyphMarginLanesModel { const model = this._context.viewModel.model; - type Glyph = { range: Range; lane: GlyphMarginLane }; + type Glyph = { range: Range; lane: GlyphMarginLane; persist?: boolean }; let glyphs: Glyph[] = []; + let maxLineNumber = 0; // Add all margin decorations glyphs = glyphs.concat(model.getAllMarginDecorations().map((decoration) => { const lane = decoration.options.glyphMargin?.position ?? GlyphMarginLane.Center; - return { range: decoration.range, lane }; + maxLineNumber = Math.max(maxLineNumber, decoration.range.endLineNumber); + return { range: decoration.range, lane, persist: decoration.options.glyphMargin?.persistLane }; })); // Add all glyph margin widgets glyphs = glyphs.concat(this._glyphMarginWidgets.getWidgets().map((widget) => { const range = model.validateRange(widget.preference.range); + maxLineNumber = Math.max(maxLineNumber, range.endLineNumber); return { range, lane: widget.preference.lane }; })); // Sorted by their start position glyphs.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); - const maxLane = GlyphMarginLane.Right; - const lanes: (Range | undefined)[] = Array.from({ length: maxLane + 1 }); - let requiredLanes = 1; - - for (const decoration of glyphs) { - // assign only if the range of `decoration` ends after, which means it has a higher chance to overlap with the other lane - if (!lanes[decoration.lane] || Range.compareRangesUsingEnds(lanes[decoration.lane]!, decoration.range) < 0) { - lanes[decoration.lane] = decoration.range; - } - - let requiredLanesHere = 0; - for (let i = 1; i <= maxLane; i++) { - const lane = lanes[i]; - if (!lane || lane.endLineNumber < decoration.range.startLineNumber) { - lanes[i] = undefined; - continue; - } - - requiredLanesHere++; - } - - requiredLanes = Math.max(requiredLanes, requiredLanesHere); - if (requiredLanes === maxLane) { - return requiredLanes; - } + const lanes = new GlyphMarginLanesModel(maxLineNumber); + for (const glyph of glyphs) { + lanes.push(glyph.lane, glyph.range, glyph.persist); } - return requiredLanes; + this._glyphMarginWidgets.updateLanesModel(lanes); + + return lanes; } private _createPointerHandlerHelper(): IPointerHandlerHelper { @@ -485,7 +469,8 @@ export class View extends ViewEventHandler { prepareRenderText: () => { if (this._shouldRecomputeGlyphMarginLanes) { this._shouldRecomputeGlyphMarginLanes = false; - this._context.configuration.setGlyphMarginDecorationLaneCount(this._computeGlyphMarginLaneCount()); + const model = this._computeGlyphMarginLanes(); + this._context.configuration.setGlyphMarginDecorationLaneCount(model.requiredLanes); } inputLatency.onRenderStart(); }, diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphLanesModel.ts b/src/vs/editor/browser/viewParts/glyphMargin/glyphLanesModel.ts new file mode 100644 index 00000000000..2b680684c82 --- /dev/null +++ b/src/vs/editor/browser/viewParts/glyphMargin/glyphLanesModel.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Range } from 'vs/editor/common/core/range'; +import { GlyphMarginLane, IGlyphMarginLanesModel } from 'vs/editor/common/model'; + + +const MAX_LANE = GlyphMarginLane.Right; + +export class GlyphMarginLanesModel implements IGlyphMarginLanesModel { + private readonly lanes: Uint8Array; + private persist = 0; + private _requiredLanes = 1; // always render at least one lane + + constructor(maxLine: number) { + this.lanes = new Uint8Array(Math.ceil((maxLine * MAX_LANE) / 8)); + } + + public get requiredLanes() { + return this._requiredLanes; + } + + /** Adds a new range to the model. Assumes ranges are added in ascending order of line number. */ + public push(lane: GlyphMarginLane, range: Range, persist?: boolean): void { + if (persist) { + this.persist |= (1 << (lane - 1)); + } + for (let i = range.startLineNumber; i <= range.endLineNumber; i++) { + const bit = (MAX_LANE * i) + (lane - 1); + this.lanes[bit >>> 3] |= (1 << (bit % 8)); + this._requiredLanes = Math.max(this._requiredLanes, this.countAtLine(i)); + } + } + + public getLanesAtLine(lineNumber: number): GlyphMarginLane[] { + const lanes: GlyphMarginLane[] = []; + let bit = MAX_LANE * lineNumber; + for (let i = 0; i < MAX_LANE; i++) { + if (this.persist & (1 << i) || this.lanes[bit >>> 3] & (1 << (bit % 8))) { + lanes.push(i + 1); + } + bit++; + } + + return lanes.length ? lanes : [GlyphMarginLane.Center]; + } + + private countAtLine(lineNumber: number): number { + let bit = MAX_LANE * lineNumber; + let count = 0; + for (let i = 0; i < MAX_LANE; i++) { + if (this.persist & (1 << i) || this.lanes[bit >>> 3] & (1 << (bit % 8))) { + count++; + } + bit++; + } + return count; + } +} diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts index ff4739bb506..ed5393fa9b3 100644 --- a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts +++ b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts @@ -10,8 +10,10 @@ import { IGlyphMarginWidget, IGlyphMarginWidgetPosition } from 'vs/editor/browse import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay'; import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext'; import { ViewPart } from 'vs/editor/browser/view/viewPart'; +import { GlyphMarginLanesModel } from 'vs/editor/browser/viewParts/glyphMargin/glyphLanesModel'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; +import { GlyphMarginLane, IGlyphMarginLanesModel } from 'vs/editor/common/model'; import * as viewEvents from 'vs/editor/common/viewEvents'; import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; @@ -121,6 +123,7 @@ export class GlyphMarginWidgets extends ViewPart { public domNode: FastDomNode; + private _lanesModel: IGlyphMarginLanesModel; private _lineHeight: number; private _glyphMargin: boolean; private _glyphMarginLeft: number; @@ -135,6 +138,7 @@ export class GlyphMarginWidgets extends ViewPart { constructor(context: ViewContext) { super(context); this._context = context; + this._lanesModel = new GlyphMarginLanesModel(0); const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); @@ -246,6 +250,10 @@ export class GlyphMarginWidgets extends ViewPart { } } + public updateLanesModel(model: GlyphMarginLanesModel) { + this._lanesModel = model; + } + // --- end widget management private _collectDecorationBasedGlyphRenderRequest(ctx: RenderingContext, requests: GlyphRenderRequest[]): void { @@ -261,11 +269,12 @@ export class GlyphMarginWidgets extends ViewPart { const startLineNumber = Math.max(d.range.startLineNumber, visibleStartLineNumber); const endLineNumber = Math.min(d.range.endLineNumber, visibleEndLineNumber); - const lane = Math.min(d.options.glyphMargin?.position ?? 1, this._glyphMarginDecorationLaneCount); + const lane = d.options.glyphMargin?.position ?? GlyphMarginLane.Center; const zIndex = d.options.zIndex ?? 0; for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) { - requests.push(new DecorationBasedGlyphRenderRequest(lineNumber, lane, zIndex, glyphMarginClassName)); + const laneIndex = this._lanesModel.getLanesAtLine(lineNumber).indexOf(lane); + requests.push(new DecorationBasedGlyphRenderRequest(lineNumber, laneIndex, zIndex, glyphMarginClassName)); } } } @@ -284,8 +293,8 @@ export class GlyphMarginWidgets extends ViewPart { // The widget is in the viewport, find a good line for it const widgetLineNumber = Math.max(startLineNumber, visibleStartLineNumber); - const lane = Math.min(widget.preference.lane, this._glyphMarginDecorationLaneCount); - requests.push(new WidgetBasedGlyphRenderRequest(widgetLineNumber, lane, widget.preference.zIndex, widget)); + const laneIndex = this._lanesModel.getLanesAtLine(widgetLineNumber).indexOf(widget.preference.lane); + requests.push(new WidgetBasedGlyphRenderRequest(widgetLineNumber, laneIndex, widget.preference.zIndex, widget)); } } @@ -300,7 +309,7 @@ export class GlyphMarginWidgets extends ViewPart { // don't change this sort unless you understand `prepareRender` below. requests.sort((a, b) => { if (a.lineNumber === b.lineNumber) { - if (a.lane === b.lane) { + if (a.laneIndex === b.laneIndex) { if (a.zIndex === b.zIndex) { if (b.type === a.type) { if (a.type === GlyphRenderRequestType.Decoration && b.type === GlyphRenderRequestType.Decoration) { @@ -312,7 +321,7 @@ export class GlyphMarginWidgets extends ViewPart { } return b.zIndex - a.zIndex; } - return a.lane - b.lane; + return a.laneIndex - b.laneIndex; } return a.lineNumber - b.lineNumber; }); @@ -343,7 +352,7 @@ export class GlyphMarginWidgets extends ViewPart { } // Requests are sorted by lineNumber and lane, so we read all requests for this particular location - const requestsAtLocation = requests.takeWhile((el) => el.lineNumber === first.lineNumber && el.lane === first.lane); + const requestsAtLocation = requests.takeWhile((el) => el.lineNumber === first.lineNumber && el.laneIndex === first.laneIndex); if (!requestsAtLocation || requestsAtLocation.length === 0) { // not possible break; @@ -369,7 +378,7 @@ export class GlyphMarginWidgets extends ViewPart { // widgets cannot be combined winner.widget.renderInfo = { lineNumber: winner.lineNumber, - lane: winner.lane, + laneIndex: winner.laneIndex, }; } } @@ -397,7 +406,7 @@ export class GlyphMarginWidgets extends ViewPart { widget.domNode.setDisplay('none'); } else { const top = ctx.viewportData.relativeVerticalOffset[widget.renderInfo.lineNumber - ctx.viewportData.startLineNumber]; - const left = this._glyphMarginLeft + (widget.renderInfo.lane - 1) * this._lineHeight; + const left = this._glyphMarginLeft + widget.renderInfo.laneIndex * this._lineHeight; widget.domNode.setDisplay('block'); widget.domNode.setTop(top); @@ -411,7 +420,7 @@ export class GlyphMarginWidgets extends ViewPart { for (let i = 0; i < this._decorationGlyphsToRender.length; i++) { const dec = this._decorationGlyphsToRender[i]; const top = ctx.viewportData.relativeVerticalOffset[dec.lineNumber - ctx.viewportData.startLineNumber]; - const left = this._glyphMarginLeft + (dec.lane - 1) * this._lineHeight; + const left = this._glyphMarginLeft + dec.laneIndex * this._lineHeight; let domNode: FastDomNode; if (i < this._managedDomNodes.length) { @@ -451,7 +460,7 @@ export interface IWidgetData { export interface IRenderInfo { lineNumber: number; - lane: number; + laneIndex: number; } const enum GlyphRenderRequestType { @@ -467,13 +476,13 @@ class DecorationBasedGlyphRenderRequest { constructor( public readonly lineNumber: number, - public readonly lane: number, + public readonly laneIndex: number, public readonly zIndex: number, public readonly className: string, ) { } accept(combinedClassName: string): DecorationBasedGlyph { - return new DecorationBasedGlyph(this.lineNumber, this.lane, combinedClassName); + return new DecorationBasedGlyph(this.lineNumber, this.laneIndex, combinedClassName); } } @@ -485,7 +494,7 @@ class WidgetBasedGlyphRenderRequest { constructor( public readonly lineNumber: number, - public readonly lane: number, + public readonly laneIndex: number, public readonly zIndex: number, public readonly widget: IWidgetData, ) { } @@ -496,7 +505,7 @@ type GlyphRenderRequest = DecorationBasedGlyphRenderRequest | WidgetBasedGlyphRe class DecorationBasedGlyph { constructor( public readonly lineNumber: number, - public readonly lane: number, + public readonly laneIndex: number, public readonly combinedClassName: string ) { } } diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 07bbe0e53e4..dabe64051a3 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -43,6 +43,18 @@ export enum GlyphMarginLane { Right = 3, } +export interface IGlyphMarginLanesModel { + /** + * The number of lanes that should be rendered in the editor. + */ + readonly requiredLanes: number; + + /** + * Gets the lanes that should be rendered starting at a given line number. + */ + getLanesAtLine(lineNumber: number): GlyphMarginLane[]; +} + /** * Position in the minimap to render the decoration. */ @@ -69,6 +81,12 @@ export interface IModelDecorationGlyphMarginOptions { * The position in the glyph margin. */ position: GlyphMarginLane; + + /** + * Whether the glyph margin lane in {@link position} should be rendered even + * outside of this decoration's range. + */ + persistLane?: boolean; } /** diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index a9abc6174d5..8b91f6ae75e 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -2204,9 +2204,11 @@ export class ModelDecorationOverviewRulerOptions extends DecorationOptions { export class ModelDecorationGlyphMarginOptions { readonly position: model.GlyphMarginLane; + readonly persistLane: boolean | undefined; constructor(options: model.IModelDecorationGlyphMarginOptions | null | undefined) { this.position = options?.position ?? model.GlyphMarginLane.Center; + this.persistLane = options?.persistLane; } } diff --git a/src/vs/editor/test/browser/view/glyphLanesModel.test.ts b/src/vs/editor/test/browser/view/glyphLanesModel.test.ts new file mode 100644 index 00000000000..1055c5f1a5f --- /dev/null +++ b/src/vs/editor/test/browser/view/glyphLanesModel.test.ts @@ -0,0 +1,75 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { GlyphMarginLanesModel, } from 'vs/editor/browser/viewParts/glyphMargin/glyphLanesModel'; +import { Range } from 'vs/editor/common/core/range'; +import { GlyphMarginLane } from 'vs/editor/common/model'; + +suite('GlyphLanesModel', () => { + let model: GlyphMarginLanesModel; + + ensureNoDisposablesAreLeakedInTestSuite(); + + const lineRange = (startLineNumber: number, endLineNumber: number) => new Range(startLineNumber, 1, endLineNumber, 1); + const assertLines = (fromLine: number, n: number, expected: GlyphMarginLane[][]) => { + const result: GlyphMarginLane[][] = []; + for (let i = 0; i < n; i++) { + result.push(model.getLanesAtLine(fromLine + i)); + } + assert.deepStrictEqual(result, expected, `fromLine: ${fromLine}, n: ${n}`); + }; + + setup(() => { + model = new GlyphMarginLanesModel(10); + }); + + test('handles empty', () => { + assert.equal(model.requiredLanes, 1); + assertLines(1, 1, [ + [GlyphMarginLane.Center], + ]); + }); + + test('works with a single line range', () => { + model.push(GlyphMarginLane.Left, lineRange(2, 3)); + assert.equal(model.requiredLanes, 1); + assertLines(1, 5, [ + [GlyphMarginLane.Center], // 1 + [GlyphMarginLane.Left], // 2 + [GlyphMarginLane.Left], // 3 + [GlyphMarginLane.Center], // 4 + [GlyphMarginLane.Center], // 5 + ]); + }); + + test('persists ranges', () => { + model.push(GlyphMarginLane.Left, lineRange(2, 3), true); + assert.equal(model.requiredLanes, 1); + assertLines(1, 5, [ + [GlyphMarginLane.Left], // 1 + [GlyphMarginLane.Left], // 2 + [GlyphMarginLane.Left], // 3 + [GlyphMarginLane.Left], // 4 + [GlyphMarginLane.Left], // 5 + ]); + }); + + test('handles overlaps', () => { + model.push(GlyphMarginLane.Left, lineRange(6, 9)); + model.push(GlyphMarginLane.Right, lineRange(5, 7)); + model.push(GlyphMarginLane.Center, lineRange(7, 8)); + assert.equal(model.requiredLanes, 3); + assertLines(5, 6, [ + [GlyphMarginLane.Right], // 5 + [GlyphMarginLane.Left, GlyphMarginLane.Right], // 6 + [GlyphMarginLane.Left, GlyphMarginLane.Center, GlyphMarginLane.Right], // 7 + [GlyphMarginLane.Left, GlyphMarginLane.Center], // 8 + [GlyphMarginLane.Left], // 9 + [GlyphMarginLane.Center], // 10 + ]); + }); +}); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 8c76883ec1f..ef0d28b496e 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1584,6 +1584,17 @@ declare namespace monaco.editor { Right = 3 } + export interface IGlyphMarginLanesModel { + /** + * The number of lanes that should be rendered in the editor. + */ + readonly requiredLanes: number; + /** + * Gets the lanes that should be rendered starting at a given line number. + */ + getLanesAtLine(lineNumber: number): GlyphMarginLane[]; + } + /** * Position in the minimap to render the decoration. */ @@ -1610,6 +1621,11 @@ declare namespace monaco.editor { * The position in the glyph margin. */ position: GlyphMarginLane; + /** + * Whether the glyph margin lane in {@link position} should be rendered even + * outside of this decoration's range. + */ + persistLane?: boolean; } /** diff --git a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts index bb5d32c6f57..c2b80080edc 100644 --- a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts @@ -67,7 +67,7 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri const cls = detail.count > 0 ? 'coverage-deco-hit' : 'coverage-deco-miss'; decorations.push(e.addDecoration(range, { showIfCollapsed: false, - glyphMargin: { position: GlyphMarginLane.Left }, + glyphMargin: { position: GlyphMarginLane.Left, persistLane: true }, description: localize('testing.hitCount', 'Hit count: {0}', detail.count), glyphMarginClassName: `coverage-deco-gutter ${cls}`, className: `coverage-deco-inline ${cls}`, @@ -79,7 +79,7 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri const branchRange = location instanceof Range ? location : Range.fromPositions(location); decorations.push(e.addDecoration(branchRange, { showIfCollapsed: false, - glyphMargin: { position: GlyphMarginLane.Left }, + glyphMargin: { position: GlyphMarginLane.Left, persistLane: true }, description: localize('testing.hitCount', 'Hit count: {0}', detail.count), glyphMarginClassName: `coverage-deco-gutter ${cls}`, className: `coverage-deco-inline ${cls}`, diff --git a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts index ff0608bddd5..c1f5a100d3d 100644 --- a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts +++ b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts @@ -49,6 +49,7 @@ import { ITestCoverageService, TestCoverageService } from 'vs/workbench/contrib/ import { TestCoverageView } from 'vs/workbench/contrib/testing/browser/testCoverageView'; import { ExplorerExtensions, IExplorerFileContributionRegistry } from 'vs/workbench/contrib/files/browser/explorerFileContrib'; import { ExplorerTestCoverageBars } from 'vs/workbench/contrib/testing/browser/testCoverageBars'; +// import { CodeCoverageDecorations } from 'vs/workbench/contrib/testing/browser/codeCoverageDecorations'; registerSingleton(ITestService, TestService, InstantiationType.Delayed); registerSingleton(ITestResultStorage, TestResultStorage, InstantiationType.Delayed); @@ -144,6 +145,7 @@ Registry.as(WorkbenchExtensions.Workbench).regi registerEditorContribution(Testing.OutputPeekContributionId, TestingOutputPeekController, EditorContributionInstantiation.AfterFirstRender); registerEditorContribution(Testing.DecorationsContributionId, TestingDecorations, EditorContributionInstantiation.AfterFirstRender); +// registerEditorContribution(Testing.CoverageDecorationsContributionId, CodeCoverageDecorations, EditorContributionInstantiation.Eventually); CommandsRegistry.registerCommand({ id: '_revealTestInExplorer', From 39e866b96474f6ff0d22c2196c03e56d819ca409 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 10 Jan 2024 10:04:33 -0800 Subject: [PATCH 0206/1897] Pick up latest markdown-it-katex (#202178) --- extensions/markdown-math/package.json | 2 +- extensions/markdown-math/yarn.lock | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/extensions/markdown-math/package.json b/extensions/markdown-math/package.json index 71cb1028e6f..ea2761b901d 100644 --- a/extensions/markdown-math/package.json +++ b/extensions/markdown-math/package.json @@ -98,7 +98,7 @@ "build-notebook": "node ./esbuild" }, "dependencies": { - "@vscode/markdown-it-katex": "^1.0.0" + "@vscode/markdown-it-katex": "^1.0.1" }, "devDependencies": { "@types/markdown-it": "^0.0.0", diff --git a/extensions/markdown-math/yarn.lock b/extensions/markdown-math/yarn.lock index 03ebbb2cf83..3b6786c1c1d 100644 --- a/extensions/markdown-math/yarn.lock +++ b/extensions/markdown-math/yarn.lock @@ -12,21 +12,21 @@ resolved "https://registry.yarnpkg.com/@types/vscode-notebook-renderer/-/vscode-notebook-renderer-1.72.0.tgz#8943dc3cef0ced2dfb1e04c0a933bd289e7d5199" integrity sha512-5iTjb39DpLn03ULUwrDR3L2Dy59RV4blSUHy0oLdQuIY11PhgWO4mXIcoFS0VxY1GZQ4IcjSf3ooT2Jrrcahnw== -"@vscode/markdown-it-katex@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@vscode/markdown-it-katex/-/markdown-it-katex-1.0.0.tgz#c0b35df95be90f79ed92c8bae77a764e96dde5c4" - integrity sha512-J0jtR3iI1VTSX1fadhNRB6vQUh792+5D7TW6XhJuktgG9+4ZXtWHwFOttvQVw7XKMB7RiPBdGV4cLzEuYV6bSg== +"@vscode/markdown-it-katex@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@vscode/markdown-it-katex/-/markdown-it-katex-1.0.1.tgz#79c6e7312074e1f897cc22c42ce078d1e72003b0" + integrity sha512-O/HiT5Uc6rN6rSx8tDdgwO1tLSn/lrNeikTzYw1EBG6B2IGLKw4I4e/GBh9DRNSdE9PajCA0tsVBz86qyA7B3A== dependencies: - katex "^0.16.2" + katex "^0.16.4" -commander@^8.0.0: +commander@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== -katex@^0.16.2: - version "0.16.4" - resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.4.tgz#87021bc3bbd80586ef715aeb476794cba6a49ad4" - integrity sha512-WudRKUj8yyBeVDI4aYMNxhx5Vhh2PjpzQw1GRu/LVGqL4m1AxwD1GcUp0IMbdJaf5zsjtj8ghP0DOQRYhroNkw== +katex@^0.16.4: + version "0.16.9" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.9.tgz#bc62d8f7abfea6e181250f85a56e4ef292dcb1fa" + integrity sha512-fsSYjWS0EEOwvy81j3vRA8TEAhQhKiqO+FQaKWp0m39qwOzHVBgAUBIXWj1pB+O2W3fIpNa6Y9KSKCVbfPhyAQ== dependencies: - commander "^8.0.0" + commander "^8.3.0" From 7dab57d216ff2f4e31a1716ccc80967f4227ff7b Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 10 Jan 2024 10:28:26 -0800 Subject: [PATCH 0207/1897] ensureNoDisposables leaked in base/common code (#202175) * ensureNoDisposables leaked in base/common code * more --- .eslintrc.json | 39 ----------- src/vs/base/test/common/arrays.test.ts | 3 + src/vs/base/test/common/arraysFind.test.ts | 3 + src/vs/base/test/common/cache.test.ts | 3 + src/vs/base/test/common/charCode.test.ts | 3 + src/vs/base/test/common/collections.test.ts | 3 + src/vs/base/test/common/color.test.ts | 3 + src/vs/base/test/common/decorators.test.ts | 3 + src/vs/base/test/common/diff/diff.test.ts | 5 ++ src/vs/base/test/common/errors.test.ts | 3 + src/vs/base/test/common/filters.test.ts | 3 + src/vs/base/test/common/iconLabels.test.ts | 1 - src/vs/base/test/common/iterator.test.ts | 3 + src/vs/base/test/common/json.test.ts | 4 ++ src/vs/base/test/common/jsonEdit.test.ts | 3 + src/vs/base/test/common/jsonFormatter.test.ts | 3 + src/vs/base/test/common/keyCodes.test.ts | 3 + src/vs/base/test/common/keybindings.test.ts | 3 + src/vs/base/test/common/linkedList.test.ts | 3 + src/vs/base/test/common/linkedText.test.ts | 3 + src/vs/base/test/common/map.test.ts | 7 ++ .../base/test/common/markdownString.test.ts | 3 + src/vs/base/test/common/marshalling.test.ts | 3 + src/vs/base/test/common/network.test.ts | 3 + src/vs/base/test/common/observable.test.ts | 67 ++++++++++--------- src/vs/base/test/common/path.test.ts | 2 + src/vs/base/test/common/resourceTree.test.ts | 3 + src/vs/base/test/common/resources.test.ts | 3 + src/vs/base/test/common/scrollable.test.ts | 3 + src/vs/base/test/common/skipList.test.ts | 3 + src/vs/base/test/common/stripComments.test.ts | 3 + .../test/common/ternarySearchtree.test.ts | 3 + src/vs/base/test/common/types.test.ts | 3 + src/vs/base/test/common/uri.test.ts | 3 + src/vs/base/test/common/uuid.test.ts | 3 + src/vs/base/test/node/crypto.test.ts | 3 + src/vs/base/test/node/css.build.test.ts | 3 + src/vs/base/test/node/snapshot.test.ts | 4 +- 38 files changed, 146 insertions(+), 73 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 0bd15f4a019..f325a998034 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -156,45 +156,6 @@ "src/vs/base/test/browser/ui/menu/menubar.test.ts", "src/vs/base/test/browser/ui/scrollbar/scrollableElement.test.ts", "src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts", - "src/vs/base/test/common/arrays.test.ts", - "src/vs/base/test/common/arraysFind.test.ts", - "src/vs/base/test/common/cache.test.ts", - "src/vs/base/test/common/charCode.test.ts", - "src/vs/base/test/common/collections.test.ts", - "src/vs/base/test/common/color.test.ts", - "src/vs/base/test/common/decorators.test.ts", - "src/vs/base/test/common/diff/diff.test.ts", - "src/vs/base/test/common/errors.test.ts", - "src/vs/base/test/common/filters.perf.test.ts", - "src/vs/base/test/common/filters.test.ts", - "src/vs/base/test/common/iconLabels.test.ts", - "src/vs/base/test/common/iterator.test.ts", - "src/vs/base/test/common/json.test.ts", - "src/vs/base/test/common/jsonEdit.test.ts", - "src/vs/base/test/common/jsonFormatter.test.ts", - "src/vs/base/test/common/keybindings.test.ts", - "src/vs/base/test/common/keyCodes.test.ts", - "src/vs/base/test/common/linkedList.test.ts", - "src/vs/base/test/common/linkedText.test.ts", - "src/vs/base/test/common/map.test.ts", - "src/vs/base/test/common/markdownString.test.ts", - "src/vs/base/test/common/marshalling.test.ts", - "src/vs/base/test/common/network.test.ts", - "src/vs/base/test/common/observable.test.ts", - "src/vs/base/test/common/path.test.ts", - "src/vs/base/test/common/resources.test.ts", - "src/vs/base/test/common/resourceTree.test.ts", - "src/vs/base/test/common/scrollable.test.ts", - "src/vs/base/test/common/skipList.test.ts", - "src/vs/base/test/common/stripComments.test.ts", - "src/vs/base/test/common/ternarySearchtree.test.ts", - "src/vs/base/test/common/types.test.ts", - "src/vs/base/test/common/uri.test.ts", - "src/vs/base/test/common/uuid.test.ts", - "src/vs/base/test/node/crypto.test.ts", - "src/vs/base/test/node/css.build.test.ts", - "src/vs/base/test/node/id.test.ts", - "src/vs/base/test/node/snapshot.test.ts", "src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts", "src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts", "src/vs/editor/contrib/dropOrPasteInto/test/browser/editSort.test.ts", diff --git a/src/vs/base/test/common/arrays.test.ts b/src/vs/base/test/common/arrays.test.ts index d4bd7850703..276e7071d15 100644 --- a/src/vs/base/test/common/arrays.test.ts +++ b/src/vs/base/test/common/arrays.test.ts @@ -5,9 +5,12 @@ import * as assert from 'assert'; import * as arrays from 'vs/base/common/arrays'; import * as arraysFind from 'vs/base/common/arraysFind'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Arrays', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('removeFastWithoutKeepingOrder', () => { const array = [1, 4, 5, 7, 55, 59, 60, 61, 64, 69]; arrays.removeFastWithoutKeepingOrder(array, 1); diff --git a/src/vs/base/test/common/arraysFind.test.ts b/src/vs/base/test/common/arraysFind.test.ts index 00ef5b6112a..d932b68cbba 100644 --- a/src/vs/base/test/common/arraysFind.test.ts +++ b/src/vs/base/test/common/arraysFind.test.ts @@ -5,8 +5,11 @@ import * as assert from 'assert'; import { MonotonousArray, findFirstMonotonous, findLastMonotonous } from 'vs/base/common/arraysFind'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Arrays', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('findLastMonotonous', () => { const array = [1, 4, 5, 7, 55, 59, 60, 61, 64, 69]; diff --git a/src/vs/base/test/common/cache.test.ts b/src/vs/base/test/common/cache.test.ts index 4394bbe81b0..b1946ed354a 100644 --- a/src/vs/base/test/common/cache.test.ts +++ b/src/vs/base/test/common/cache.test.ts @@ -6,9 +6,12 @@ import * as assert from 'assert'; import { timeout } from 'vs/base/common/async'; import { Cache } from 'vs/base/common/cache'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Cache', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('simple value', () => { let counter = 0; const cache = new Cache(_ => Promise.resolve(counter++)); diff --git a/src/vs/base/test/common/charCode.test.ts b/src/vs/base/test/common/charCode.test.ts index bf877878e1b..49be69d8578 100644 --- a/src/vs/base/test/common/charCode.test.ts +++ b/src/vs/base/test/common/charCode.test.ts @@ -5,8 +5,11 @@ import * as assert from 'assert'; import { CharCode } from 'vs/base/common/charCode'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('CharCode', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('has good values', () => { function assertValue(actual: CharCode, expected: string): void { diff --git a/src/vs/base/test/common/collections.test.ts b/src/vs/base/test/common/collections.test.ts index 9dfe59a58fe..c2616cc377e 100644 --- a/src/vs/base/test/common/collections.test.ts +++ b/src/vs/base/test/common/collections.test.ts @@ -5,9 +5,12 @@ import * as assert from 'assert'; import * as collections from 'vs/base/common/collections'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Collections', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('groupBy', () => { const group1 = 'a', group2 = 'b'; diff --git a/src/vs/base/test/common/color.test.ts b/src/vs/base/test/common/color.test.ts index be9dd23d614..857c394d322 100644 --- a/src/vs/base/test/common/color.test.ts +++ b/src/vs/base/test/common/color.test.ts @@ -5,9 +5,12 @@ import * as assert from 'assert'; import { Color, HSLA, HSVA, RGBA } from 'vs/base/common/color'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Color', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('isLighterColor', () => { const color1 = new Color(new HSLA(60, 1, 0.5, 1)), color2 = new Color(new HSLA(0, 0, 0.753, 1)); diff --git a/src/vs/base/test/common/decorators.test.ts b/src/vs/base/test/common/decorators.test.ts index 8b0544dd559..92bce8c605b 100644 --- a/src/vs/base/test/common/decorators.test.ts +++ b/src/vs/base/test/common/decorators.test.ts @@ -6,8 +6,11 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { memoize, throttle } from 'vs/base/common/decorators'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Decorators', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('memoize should memoize methods', () => { class Foo { count = 0; diff --git a/src/vs/base/test/common/diff/diff.test.ts b/src/vs/base/test/common/diff/diff.test.ts index 0ccb5b55535..353b68ebd7e 100644 --- a/src/vs/base/test/common/diff/diff.test.ts +++ b/src/vs/base/test/common/diff/diff.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { IDiffChange, LcsDiff, StringDiffSequence } from 'vs/base/common/diff/diff'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; function createArray(length: number, value: T): T[] { const r: T[] = []; @@ -79,6 +80,8 @@ function lcsTest(originalStr: string, modifiedStr: string, answerStr: string) { } suite('Diff', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('LcsDiff - different strings tests', function () { this.timeout(10000); lcsTest('heLLo world', 'hello orlando', 'heo orld'); @@ -97,6 +100,8 @@ suite('Diff', () => { }); suite('Diff - Ported from VS', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('using continue processing predicate to quit early', function () { const left = 'abcdef'; const right = 'abxxcyyydzzzzezzzzzzzzzzzzzzzzzzzzf'; diff --git a/src/vs/base/test/common/errors.test.ts b/src/vs/base/test/common/errors.test.ts index a5e79162361..96210c55314 100644 --- a/src/vs/base/test/common/errors.test.ts +++ b/src/vs/base/test/common/errors.test.ts @@ -5,8 +5,11 @@ import * as assert from 'assert'; import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Errors', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('Get Error Message', function () { assert.strictEqual(toErrorMessage('Foo Bar'), 'Foo Bar'); assert.strictEqual(toErrorMessage(new Error('Foo Bar')), 'Foo Bar'); diff --git a/src/vs/base/test/common/filters.test.ts b/src/vs/base/test/common/filters.test.ts index df8d25f3dab..135330f7044 100644 --- a/src/vs/base/test/common/filters.test.ts +++ b/src/vs/base/test/common/filters.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { anyScore, createMatches, fuzzyScore, fuzzyScoreGraceful, fuzzyScoreGracefulAggressive, FuzzyScorer, IFilter, IMatch, matchesCamelCase, matchesContiguousSubString, matchesPrefix, matchesStrictPrefix, matchesSubString, matchesWords, or } from 'vs/base/common/filters'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; function filterOk(filter: IFilter, word: string, wordToMatchAgainst: string, highlights?: { start: number; end: number }[]) { const r = filter(word, wordToMatchAgainst); @@ -18,6 +19,8 @@ function filterNotOk(filter: IFilter, word: string, wordToMatchAgainst: string) } suite('Filters', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('or', () => { let filter: IFilter; let counters: number[]; diff --git a/src/vs/base/test/common/iconLabels.test.ts b/src/vs/base/test/common/iconLabels.test.ts index b8352069e1c..4f2cd802c64 100644 --- a/src/vs/base/test/common/iconLabels.test.ts +++ b/src/vs/base/test/common/iconLabels.test.ts @@ -22,7 +22,6 @@ function filterOk(filter: IIconFilter, word: string, target: IParsedLabelWithIco } suite('Icon Labels', () => { - test('Can get proper aria labels', () => { // note, the spaces in the results are important const testCases = new Map([ diff --git a/src/vs/base/test/common/iterator.test.ts b/src/vs/base/test/common/iterator.test.ts index f43908c415b..ce69814ad43 100644 --- a/src/vs/base/test/common/iterator.test.ts +++ b/src/vs/base/test/common/iterator.test.ts @@ -5,9 +5,12 @@ import * as assert from 'assert'; import { Iterable } from 'vs/base/common/iterator'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Iterable', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + const customIterable = new class { *[Symbol.iterator]() { diff --git a/src/vs/base/test/common/json.test.ts b/src/vs/base/test/common/json.test.ts index 5accb16227c..4ad121151b4 100644 --- a/src/vs/base/test/common/json.test.ts +++ b/src/vs/base/test/common/json.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { createScanner, Node, parse, ParseError, ParseErrorCode, ParseOptions, parseTree, ScanError, SyntaxKind } from 'vs/base/common/json'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; function assertKinds(text: string, ...kinds: SyntaxKind[]): void { const scanner = createScanner(text); @@ -59,6 +60,9 @@ function assertTree(input: string, expected: any, expectedErrors: number[] = [], } suite('JSON', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('tokens', () => { assertKinds('{', SyntaxKind.OpenBraceToken); assertKinds('}', SyntaxKind.CloseBraceToken); diff --git a/src/vs/base/test/common/jsonEdit.test.ts b/src/vs/base/test/common/jsonEdit.test.ts index 228db5b15df..fa5d41b481a 100644 --- a/src/vs/base/test/common/jsonEdit.test.ts +++ b/src/vs/base/test/common/jsonEdit.test.ts @@ -5,9 +5,12 @@ import * as assert from 'assert'; import { removeProperty, setProperty } from 'vs/base/common/jsonEdit'; import { Edit, FormattingOptions } from 'vs/base/common/jsonFormatter'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('JSON - edits', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function assertEdit(content: string, edits: Edit[], expected: string) { assert(edits); let lastEditOffset = content.length; diff --git a/src/vs/base/test/common/jsonFormatter.test.ts b/src/vs/base/test/common/jsonFormatter.test.ts index 3ecfe41c7d0..636d987c54a 100644 --- a/src/vs/base/test/common/jsonFormatter.test.ts +++ b/src/vs/base/test/common/jsonFormatter.test.ts @@ -4,9 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import * as Formatter from 'vs/base/common/jsonFormatter'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('JSON - formatter', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function format(content: string, expected: string, insertSpaces = true) { let range: Formatter.Range | undefined = undefined; const rangeStart = content.indexOf('|'); diff --git a/src/vs/base/test/common/keyCodes.test.ts b/src/vs/base/test/common/keyCodes.test.ts index e0b6bda7b9e..e3c73412eab 100644 --- a/src/vs/base/test/common/keyCodes.test.ts +++ b/src/vs/base/test/common/keyCodes.test.ts @@ -7,9 +7,12 @@ import * as assert from 'assert'; import { EVENT_KEY_CODE_MAP, IMMUTABLE_CODE_TO_KEY_CODE, IMMUTABLE_KEY_CODE_TO_CODE, KeyChord, KeyCode, KeyCodeUtils, KeyMod, NATIVE_WINDOWS_KEY_CODE_TO_KEY_CODE, ScanCode, ScanCodeUtils } from 'vs/base/common/keyCodes'; import { decodeKeybinding, KeyCodeChord, Keybinding } from 'vs/base/common/keybindings'; import { OperatingSystem } from 'vs/base/common/platform'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('keyCodes', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function testBinaryEncoding(expected: Keybinding | null, k: number, OS: OperatingSystem): void { assert.deepStrictEqual(decodeKeybinding(k, OS), expected); } diff --git a/src/vs/base/test/common/keybindings.test.ts b/src/vs/base/test/common/keybindings.test.ts index c690efa61cd..ab26cf24864 100644 --- a/src/vs/base/test/common/keybindings.test.ts +++ b/src/vs/base/test/common/keybindings.test.ts @@ -6,9 +6,12 @@ import * as assert from 'assert'; import { KeyCode, ScanCode } from 'vs/base/common/keyCodes'; import { KeyCodeChord, ScanCodeChord } from 'vs/base/common/keybindings'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('keyCodes', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('issue #173325: wrong interpretations of special keys (e.g. [Equal] is mistaken for V)', () => { const a = new KeyCodeChord(true, false, false, false, KeyCode.KeyV); const b = new ScanCodeChord(true, false, false, false, ScanCode.Equal); diff --git a/src/vs/base/test/common/linkedList.test.ts b/src/vs/base/test/common/linkedList.test.ts index 46b39b03055..181916d9625 100644 --- a/src/vs/base/test/common/linkedList.test.ts +++ b/src/vs/base/test/common/linkedList.test.ts @@ -5,9 +5,12 @@ import * as assert from 'assert'; import { LinkedList } from 'vs/base/common/linkedList'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('LinkedList', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + function assertElements(list: LinkedList, ...elements: E[]) { // check size diff --git a/src/vs/base/test/common/linkedText.test.ts b/src/vs/base/test/common/linkedText.test.ts index 903bb48c7f5..dc3bdbd0609 100644 --- a/src/vs/base/test/common/linkedText.test.ts +++ b/src/vs/base/test/common/linkedText.test.ts @@ -5,8 +5,11 @@ import * as assert from 'assert'; import { parseLinkedText } from 'vs/base/common/linkedText'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('LinkedText', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('parses correctly', () => { assert.deepStrictEqual(parseLinkedText('').nodes, []); assert.deepStrictEqual(parseLinkedText('hello').nodes, ['hello']); diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index 399307b69bf..60aee7cd1dc 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -7,9 +7,12 @@ import * as assert from 'assert'; import { BidirectionalMap, LinkedMap, LRUCache, mapsStrictEqualIgnoreOrder, ResourceMap, SetMap, Touch } from 'vs/base/common/map'; import { extUriIgnorePathCase } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Map', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('LinkedMap - Simple', () => { const map = new LinkedMap(); map.set('ak', 'av'); @@ -505,6 +508,8 @@ suite('Map', () => { }); suite('BidirectionalMap', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('should set and get values correctly', () => { const map = new BidirectionalMap(); map.set('one', 1); @@ -593,6 +598,8 @@ suite('BidirectionalMap', () => { suite('SetMap', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('add and get', () => { const setMap = new SetMap(); setMap.add('a', 1); diff --git a/src/vs/base/test/common/markdownString.test.ts b/src/vs/base/test/common/markdownString.test.ts index a23573f610a..cb7df1696b3 100644 --- a/src/vs/base/test/common/markdownString.test.ts +++ b/src/vs/base/test/common/markdownString.test.ts @@ -5,9 +5,12 @@ import * as assert from 'assert'; import { MarkdownString } from 'vs/base/common/htmlContent'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('MarkdownString', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('Escape leading whitespace', function () { const mds = new MarkdownString(); mds.appendText('Hello\n Not a code block'); diff --git a/src/vs/base/test/common/marshalling.test.ts b/src/vs/base/test/common/marshalling.test.ts index 62210356720..7d3bb66cec7 100644 --- a/src/vs/base/test/common/marshalling.test.ts +++ b/src/vs/base/test/common/marshalling.test.ts @@ -5,9 +5,12 @@ import * as assert from 'assert'; import { parse, stringify } from 'vs/base/common/marshalling'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Marshalling', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('RegExp', () => { const value = /foo/img; const raw = stringify(value); diff --git a/src/vs/base/test/common/network.test.ts b/src/vs/base/test/common/network.test.ts index 9c6938967d9..a85b2b392a8 100644 --- a/src/vs/base/test/common/network.test.ts +++ b/src/vs/base/test/common/network.test.ts @@ -8,9 +8,12 @@ import { FileAccess, Schemas } from 'vs/base/common/network'; import { isWeb } from 'vs/base/common/platform'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('network', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + (isWeb ? test.skip : test)('FileAccess: URI (native)', () => { // asCodeUri() & asFileUri(): simple, without authority diff --git a/src/vs/base/test/common/observable.test.ts b/src/vs/base/test/common/observable.test.ts index d7586f4c27c..5f33dcbfc4c 100644 --- a/src/vs/base/test/common/observable.test.ts +++ b/src/vs/base/test/common/observable.test.ts @@ -7,8 +7,11 @@ import * as assert from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; import { ISettableObservable, autorun, derived, ITransaction, observableFromEvent, observableValue, transaction, keepObserved } from 'vs/base/common/observable'; import { BaseObservable, IObservable, IObserver } from 'vs/base/common/observableInternal/base'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('observables', () => { + const ds = ensureNoDisposablesAreLeakedInTestSuite(); + /** * Reads these tests to understand how to use observables. */ @@ -17,10 +20,10 @@ suite('observables', () => { const log = new Log(); const myObservable = observableValue('myObservable', 0); - autorun(reader => { + ds.add(autorun(reader => { /** @description myAutorun */ log.log(`myAutorun.run(myObservable: ${myObservable.read(reader)})`); - }); + })); // The autorun runs immediately assert.deepStrictEqual(log.getAndClearEntries(), ['myAutorun.run(myObservable: 0)']); @@ -59,10 +62,10 @@ suite('observables', () => { return sum; }); - autorun(reader => { + ds.add(autorun(reader => { /** @description myAutorun */ log.log(`myAutorun(myDerived: ${myDerived.read(reader)})`); - }); + })); // autorun runs immediately assert.deepStrictEqual(log.getAndClearEntries(), [ "myDerived.recompute: 0 + 0 = 0", @@ -122,10 +125,10 @@ suite('observables', () => { return sum; }); - autorun(reader => { + ds.add(autorun(reader => { /** @description myAutorun */ log.log(`myAutorun(myDerived: ${myDerived.read(reader)})`); - }); + })); // autorun runs immediately assert.deepStrictEqual(log.getAndClearEntries(), [ "myDerived.recompute: 0 + 0 = 0", @@ -294,10 +297,10 @@ suite('observables', () => { return sum; }); - autorun(reader => { + ds.add(autorun(reader => { /** @description myAutorun */ log.log(`myAutorun.run(myComputed3: ${myComputed3.read(reader)})`); - }); + })); assert.deepStrictEqual(log.getAndClearEntries(), [ "myComputed1.recompute(myObservable1: 0 + myObservable2: 0 = 0)", "myComputed2.recompute(myComputed1: 0 + myObservable1: 0 + myObservable2: 0 = 0)", @@ -491,11 +494,11 @@ suite('observables', () => { } return 1; }); - autorun(reader => { + ds.add(autorun(reader => { /** @description myAutorun */ const value = myComputed.read(reader); log.log(`myAutorun: ${value}`); - }); + })); assert.deepStrictEqual(log.getAndClearEntries(), [ "myComputed.recompute", "myObs1.firstObserverAdded", @@ -533,7 +536,7 @@ suite('observables', () => { return myObs1Val; }); - autorun(reader => { + ds.add(autorun(reader => { /** @description myAutorun */ const shouldRead = myObsShouldRead.read(reader); if (shouldRead) { @@ -542,7 +545,7 @@ suite('observables', () => { } else { log.log(`myAutorun(shouldRead: false): run`); } - }); + })); assert.deepStrictEqual(log.getAndClearEntries(), [ "myObsShouldRead.firstObserverAdded", "myObsShouldRead.get", @@ -587,10 +590,10 @@ suite('observables', () => { const log = new Log(); const myObservable = observableValue('myObservable', 0); - autorun(reader => { + ds.add(autorun(reader => { /** @description myAutorun */ log.log(`myAutorun.run(myObservable: ${myObservable.read(reader)})`); - }); + })); assert.deepStrictEqual(log.getAndClearEntries(), ['myAutorun.run(myObservable: 0)']); @@ -614,10 +617,10 @@ suite('observables', () => { return val; }); - autorun(reader => { + ds.add(autorun(reader => { /** @description myAutorun */ log.log(`myAutorun.run(myDerived: ${myDerived.read(reader)})`); - }); + })); assert.deepStrictEqual(log.getAndClearEntries(), [ "myDerived.read(myObservable: 0)", "myAutorun.run(myDerived: 0)" @@ -645,10 +648,10 @@ suite('observables', () => { return val; }); - autorun(reader => { + ds.add(autorun(reader => { /** @description myAutorun */ log.log(`myAutorun.run(myDerived: ${myDerived.read(reader)})`); - }); + })); assert.deepStrictEqual(log.getAndClearEntries(), [ "myDerived.read(myObservable: 0)", "myAutorun.run(myDerived: 0)" @@ -748,12 +751,12 @@ suite('observables', () => { return val; }); - autorun(reader => { + ds.add(autorun(reader => { /** @description myAutorun */ const myDerived1Val = myDerived1.read(reader); const myDerived2Val = myDerived2.read(reader); log.log(`myAutorun.run(myDerived1: ${myDerived1Val}, myDerived2: ${myDerived2Val})`); - }); + })); transaction(tx => { myObservable2.set(1, tx); @@ -780,11 +783,11 @@ suite('observables', () => { return value; }); - autorun(reader => { + ds.add(autorun(reader => { /** @description myAutorun */ const value = myComputed.read(reader); log.log(`myAutorun(myComputed: ${value})`); - }); + })); assert.deepStrictEqual(log.getAndClearEntries(), [ "myObservable.firstObserverAdded", "myObservable.get", @@ -815,7 +818,7 @@ suite('observables', () => { const log = new Log(); const myObservable = new LoggingObservableValue('myObservable', 0, log); - autorun(reader => { + ds.add(autorun(reader => { /** @description myAutorun */ const value = myObservable.read(reader); log.log(`myAutorun(myObservable: ${value}): start`); @@ -823,7 +826,7 @@ suite('observables', () => { myObservable.set(value + 1, undefined); } log.log(`myAutorun(myObservable: ${value}): end`); - }); + })); assert.deepStrictEqual(log.getAndClearEntries(), [ "myObservable.firstObserverAdded", "myObservable.get", @@ -870,11 +873,11 @@ suite('observables', () => { return value; }); - autorun(reader => { + ds.add(autorun(reader => { /** @description myAutorun */ const value = myDerived2.read(reader); log.log(`myAutorun(myDerived2: ${value})`); - }); + })); assert.deepStrictEqual(log.getAndClearEntries(), [ "myObservable.firstObserverAdded", "myObservable.get", @@ -929,11 +932,11 @@ suite('observables', () => { return `${val1} + ${val2}`; }); - autorun(reader => { + ds.add(autorun(reader => { /** @description myAutorun */ const val = myDerived3.read(reader); log.log(`myAutorun(myDerived3: ${val})`); - }); + })); assert.deepStrictEqual(log.getAndClearEntries(), [ "myObservable1.firstObserverAdded", "myObservable1.get", @@ -988,15 +991,15 @@ suite('observables', () => { } }); - autorun(reader => { + ds.add(autorun(reader => { /** @description myAutorun1 */ myDerivedA1.read(reader); - }); + })); - autorun(reader => { + ds.add(autorun(reader => { /** @description myAutorun2 */ myDerived2.read(reader); - }); + })); transaction(tx => { myObservable1.set(1, tx); diff --git a/src/vs/base/test/common/path.test.ts b/src/vs/base/test/common/path.test.ts index 4deaf085ffc..62b43c91d0b 100644 --- a/src/vs/base/test/common/path.test.ts +++ b/src/vs/base/test/common/path.test.ts @@ -31,9 +31,11 @@ import * as assert from 'assert'; import * as path from 'vs/base/common/path'; import { isWeb, isWindows } from 'vs/base/common/platform'; import * as process from 'vs/base/common/process'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Paths (Node Implementation)', () => { const __filename = 'path.test.js'; + ensureNoDisposablesAreLeakedInTestSuite(); test('join', () => { const failures = [] as string[]; const backslashRE = /\\/g; diff --git a/src/vs/base/test/common/resourceTree.test.ts b/src/vs/base/test/common/resourceTree.test.ts index 7e3cefa4250..9024269a1ef 100644 --- a/src/vs/base/test/common/resourceTree.test.ts +++ b/src/vs/base/test/common/resourceTree.test.ts @@ -6,8 +6,11 @@ import * as assert from 'assert'; import { ResourceTree } from 'vs/base/common/resourceTree'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ResourceTree', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + test('ctor', function () { const tree = new ResourceTree(null); assert.strictEqual(tree.root.childrenCount, 0); diff --git a/src/vs/base/test/common/resources.test.ts b/src/vs/base/test/common/resources.test.ts index a7b13f12516..d1e4e1025ae 100644 --- a/src/vs/base/test/common/resources.test.ts +++ b/src/vs/base/test/common/resources.test.ts @@ -8,10 +8,13 @@ import { posix, win32 } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; import { addTrailingPathSeparator, basename, dirname, distinctParents, extUri, extUriIgnorePathCase, hasTrailingPathSeparator, isAbsolutePath, joinPath, normalizePath, relativePath, removeTrailingPathSeparator, resolvePath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Resources', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('distinctParents', () => { // Basic diff --git a/src/vs/base/test/common/scrollable.test.ts b/src/vs/base/test/common/scrollable.test.ts index 17c9eacce57..7059d813daa 100644 --- a/src/vs/base/test/common/scrollable.test.ts +++ b/src/vs/base/test/common/scrollable.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { SmoothScrollingOperation, SmoothScrollingUpdate } from 'vs/base/common/scrollable'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; class TestSmoothScrollingOperation extends SmoothScrollingOperation { @@ -32,6 +33,8 @@ suite('SmoothScrollingOperation', () => { const ANIMATION_DURATION = 125; const LINE_HEIGHT = 20; + ensureNoDisposablesAreLeakedInTestSuite(); + function extractLines(scrollable: TestSmoothScrollingOperation, now: number): [number, number] { const scrollTop = scrollable.testTick(now).scrollTop; const scrollBottom = scrollTop + VIEWPORT_HEIGHT; diff --git a/src/vs/base/test/common/skipList.test.ts b/src/vs/base/test/common/skipList.test.ts index bbfb8a021a0..8dadb0e34dd 100644 --- a/src/vs/base/test/common/skipList.test.ts +++ b/src/vs/base/test/common/skipList.test.ts @@ -7,10 +7,13 @@ import * as assert from 'assert'; import { binarySearch } from 'vs/base/common/arrays'; import { SkipList } from 'vs/base/common/skipList'; import { StopWatch } from 'vs/base/common/stopwatch'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('SkipList', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + function assertValues(list: SkipList, expected: V[]) { assert.strictEqual(list.size, expected.length); assert.deepStrictEqual([...list.values()], expected); diff --git a/src/vs/base/test/common/stripComments.test.ts b/src/vs/base/test/common/stripComments.test.ts index d3bd74e71f5..4aac8cdcb9d 100644 --- a/src/vs/base/test/common/stripComments.test.ts +++ b/src/vs/base/test/common/stripComments.test.ts @@ -5,10 +5,13 @@ import * as assert from 'assert'; import { stripComments } from 'vs/base/common/stripComments'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; // We use this regular expression quite often to strip comments in JSON files. suite('Strip Comments', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('Line comment', () => { const content: string = [ "{", diff --git a/src/vs/base/test/common/ternarySearchtree.test.ts b/src/vs/base/test/common/ternarySearchtree.test.ts index c2f27122730..c1a8cc771a1 100644 --- a/src/vs/base/test/common/ternarySearchtree.test.ts +++ b/src/vs/base/test/common/ternarySearchtree.test.ts @@ -9,9 +9,12 @@ import { randomPath } from 'vs/base/common/extpath'; import { StopWatch } from 'vs/base/common/stopwatch'; import { ConfigKeysIterator, PathIterator, StringIterator, TernarySearchTree, UriIterator } from 'vs/base/common/ternarySearchTree'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Ternary Search Tree', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('PathIterator', () => { const iter = new PathIterator(); iter.reset('file:///usr/bin/file.txt'); diff --git a/src/vs/base/test/common/types.test.ts b/src/vs/base/test/common/types.test.ts index 1f5e7d0b812..93e2464925a 100644 --- a/src/vs/base/test/common/types.test.ts +++ b/src/vs/base/test/common/types.test.ts @@ -5,9 +5,12 @@ import * as assert from 'assert'; import * as types from 'vs/base/common/types'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Types', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('isFunction', () => { assert(!types.isFunction(undefined)); assert(!types.isFunction(null)); diff --git a/src/vs/base/test/common/uri.test.ts b/src/vs/base/test/common/uri.test.ts index 8002409cef3..69e2abe720d 100644 --- a/src/vs/base/test/common/uri.test.ts +++ b/src/vs/base/test/common/uri.test.ts @@ -5,9 +5,12 @@ import * as assert from 'assert'; import { isWindows } from 'vs/base/common/platform'; import { URI, UriComponents, isUriComponents } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('URI', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('file#toString', () => { assert.strictEqual(URI.file('c:/win/path').toString(), 'file:///c%3A/win/path'); assert.strictEqual(URI.file('C:/win/path').toString(), 'file:///c%3A/win/path'); diff --git a/src/vs/base/test/common/uuid.test.ts b/src/vs/base/test/common/uuid.test.ts index 632366bc260..a8defebb567 100644 --- a/src/vs/base/test/common/uuid.test.ts +++ b/src/vs/base/test/common/uuid.test.ts @@ -4,8 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import * as uuid from 'vs/base/common/uuid'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('UUID', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('generation', () => { const asHex = uuid.generateUuid(); assert.strictEqual(asHex.length, 36); diff --git a/src/vs/base/test/node/crypto.test.ts b/src/vs/base/test/node/crypto.test.ts index f61fe475b7a..ff929aaa21c 100644 --- a/src/vs/base/test/node/crypto.test.ts +++ b/src/vs/base/test/node/crypto.test.ts @@ -7,12 +7,15 @@ import { tmpdir } from 'os'; import { join } from 'vs/base/common/path'; import { checksum } from 'vs/base/node/crypto'; import { Promises } from 'vs/base/node/pfs'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; flakySuite('Crypto', () => { let testDir: string; + ensureNoDisposablesAreLeakedInTestSuite(); + setup(function () { testDir = getRandomTestPath(tmpdir(), 'vsctests', 'crypto'); diff --git a/src/vs/base/test/node/css.build.test.ts b/src/vs/base/test/node/css.build.test.ts index 9e7323b7f7a..16c79d41225 100644 --- a/src/vs/base/test/node/css.build.test.ts +++ b/src/vs/base/test/node/css.build.test.ts @@ -4,10 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CSSPluginUtilities, rewriteUrls } from 'vs/css.build'; suite('CSSPlugin', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('Utilities.pathOf', () => { assert.strictEqual(CSSPluginUtilities.pathOf(''), ''); assert.strictEqual(CSSPluginUtilities.pathOf('/a'), '/'); diff --git a/src/vs/base/test/node/snapshot.test.ts b/src/vs/base/test/node/snapshot.test.ts index b7edc05d34c..48621bed95f 100644 --- a/src/vs/base/test/node/snapshot.test.ts +++ b/src/vs/base/test/node/snapshot.test.ts @@ -9,7 +9,7 @@ import { Promises } from 'vs/base/node/pfs'; import { SnapshotContext, assertSnapshot } from 'vs/base/test/common/snapshot'; import { URI } from 'vs/base/common/uri'; import * as path from 'path'; -import { assertThrowsAsync } from 'vs/base/test/common/utils'; +import { assertThrowsAsync, ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; // tests for snapshot are in Node so that we can use native FS operations to // set up and validate things. @@ -19,6 +19,8 @@ import { assertThrowsAsync } from 'vs/base/test/common/utils'; suite('snapshot', () => { let testDir: string; + ensureNoDisposablesAreLeakedInTestSuite(); + setup(function () { testDir = getRandomTestPath(tmpdir(), 'vsctests', 'snapshot'); return Promises.mkdir(testDir, { recursive: true }); From 53d7995f47ddf70bc209f4a74e1c410a2f8b4779 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 10 Jan 2024 19:46:33 +0100 Subject: [PATCH 0208/1897] ENOSPC though no workspace (fix #201923) (#202180) --- src/vs/workbench/contrib/files/browser/workspaceWatcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts b/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts index 0ac5922236c..6872bfabc67 100644 --- a/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts +++ b/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts @@ -73,7 +73,7 @@ export class WorkspaceWatcher extends Disposable { if (msg.indexOf('ENOSPC') >= 0) { this.notificationService.prompt( Severity.Warning, - localize('enospcError', "Unable to watch for file changes in this workspace. Please follow the instructions link to resolve this issue."), + localize('enospcError', "Unable to watch for file changes. Please follow the instructions link to resolve this issue."), [{ label: localize('learnMore', "Instructions"), run: () => this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?linkid=867693')) From 115155df62db44ab81c30b8ad20cabb67aaba6f1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 10 Jan 2024 19:48:10 +0100 Subject: [PATCH 0209/1897] Add test that asserts stream is done when the request is done (#202184) test that asserts stream is done when the request is done manifests fixing https://github.com/microsoft/vscode-copilot/issues/3345 --- .../browser/inlineChatStrategies.ts | 11 +--- .../test/browser/inlineChatController.test.ts | 56 ++++++++++++++++++- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 7c483f8ea98..7c1699450b6 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -5,7 +5,7 @@ import { WindowIntervalTimer } from 'vs/base/browser/dom'; import { coalesceInPlace, equals, tail } from 'vs/base/common/arrays'; -import { AsyncIterableObject, AsyncIterableSource, IntervalTimer } from 'vs/base/common/async'; +import { AsyncIterableSource, IntervalTimer } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; @@ -439,21 +439,12 @@ export async function performAsyncTextEdit(model: ITextModel, edit: AsyncTextEdi } } -export function asAsyncEdit(edit: IIdentifiedSingleEditOperation): AsyncTextEdit { - return { - range: edit.range, - newText: AsyncIterableObject.fromArray([edit.text ?? '']) - } satisfies AsyncTextEdit; -} - export function asProgressiveEdit(interval: IntervalTimer, edit: IIdentifiedSingleEditOperation, wordsPerSec: number, token: CancellationToken): AsyncTextEdit { wordsPerSec = Math.max(10, wordsPerSec); const stream = new AsyncIterableSource(); let newText = edit.text ?? ''; - // const wordCount = countWords(newText); - interval.cancelAndSet(() => { const r = getNWords(newText, 1); diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 5a730ef8b65..356055c7420 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -5,9 +5,11 @@ import * as assert from 'assert'; import { equals } from 'vs/base/common/arrays'; +import { timeout } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { mock } from 'vs/base/test/common/mock'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { TestDiffProviderFactoryService } from 'vs/editor/browser/diff/testDiffProviderFactoryService'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -31,7 +33,7 @@ import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/cha import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { InlineChatController, InlineChatRunOptions, State } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { IInlineChatSessionService, InlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { IInlineChatService, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { EditMode, IInlineChatService, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; @@ -371,4 +373,56 @@ suite('InteractiveChatController', function () { editor.getModel().undo(); assert.strictEqual(editor.getModel().getValue(), valueThen); }); + + test('UI is streaming edits minutes after the response is finished #3345', async function () { + + configurationService.setUserConfiguration(InlineChatConfigKeys.Mode, EditMode.Live); + + return runWithFakedTimers({ maxTaskCount: Number.MAX_SAFE_INTEGER }, async () => { + + const d = inlineChatService.addProvider({ + debugName: 'Unit Test', + label: 'Unit Test', + prepareInlineChatSession() { + return { + id: Math.random(), + }; + }, + async provideResponse(session, request, progress) { + + const text = '${CSI}#a\n${CSI}#b\n${CSI}#c\n'; + + await timeout(10); + progress.report({ edits: [{ range: new Range(1, 1, 1, 1), text: text }] }); + + await timeout(10); + progress.report({ edits: [{ range: new Range(1, 1, 1, 1), text: text.repeat(1000) + 'DONE' }] }); + + throw new Error('Too long'); + } + }); + + + let modelChangeCounter = 0; + store.add(editor.getModel().onDidChangeContent(() => { modelChangeCounter++; })); + + store.add(d); + ctrl = instaService.createInstance(TestController, editor); + const p = ctrl.waitFor([...TestController.INIT_SEQUENCE, State.MAKE_REQUEST, State.APPLY_RESPONSE, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + const r = ctrl.run({ message: 'Hello', autoSend: true }); + await p; + + assert.ok(modelChangeCounter > 0); // some changes have been made + + const modelChangeCounterNow = modelChangeCounter; + + await timeout(10); + + assert.strictEqual(modelChangeCounterNow, modelChangeCounter); + assert.ok(!editor.getModel().getValue().includes('DONE')); + + await ctrl.cancelSession(); + await r; + }); + }); }); From 0bb69da87ca19412a768d5966fcb0e7b36b2dd3f Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 10 Jan 2024 11:01:52 -0800 Subject: [PATCH 0210/1897] Add new `smartWithSelection` option for markdown links (#202183) This becomes the new default while `smart` always smartly pastes, even with no selection --- .../markdown-language-features/package.json | 4 +- .../package.nls.json | 3 +- .../copyFiles/pasteUrlProvider.ts | 58 ++++++++++++------- .../src/test/markdownLink.test.ts | 52 +++++++++-------- 4 files changed, 70 insertions(+), 47 deletions(-) diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 5dcce219bfa..160ddbb3a7b 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -517,15 +517,17 @@ "type": "string", "scope": "resource", "markdownDescription": "%configuration.markdown.editor.pasteUrlAsFormattedLink.enabled%", - "default": "smart", + "default": "smartWithSelection", "enum": [ "always", "smart", + "smartWithSelection", "never" ], "markdownEnumDescriptions": [ "%configuration.pasteUrlAsFormattedLink.always%", "%configuration.pasteUrlAsFormattedLink.smart%", + "%configuration.pasteUrlAsFormattedLink.smartWithSelection%", "%configuration.pasteUrlAsFormattedLink.never%" ] }, diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index 02d80098b9a..b99593a6301 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -45,7 +45,8 @@ "configuration.copyIntoWorkspace.never": "Do not copy external files into the workspace.", "configuration.markdown.editor.pasteUrlAsFormattedLink.enabled": "Controls if Markdown links are created when URLs are pasted into a Markdown editor. Requires enabling `#editor.pasteAs.enabled#`.", "configuration.pasteUrlAsFormattedLink.always": "Always insert Markdown links.", - "configuration.pasteUrlAsFormattedLink.smart": "Smartly create Markdown links by default when you have selected text and are not pasting into a code block or other special element. Use the paste widget to switch between pasting as plain text or as Markdown links.", + "configuration.pasteUrlAsFormattedLink.smart": "Smartly create Markdown links by default when not pasting into a code block or other special element. Use the paste widget to switch between pasting as plain text or as Markdown links.", + "configuration.pasteUrlAsFormattedLink.smartWithSelection": "Smartly create Markdown links by default when you have selected text and are not pasting into a code block or other special element. Use the paste widget to switch between pasting as plain text or as Markdown links.", "configuration.pasteUrlAsFormattedLink.never": "Never create Markdown links.", "configuration.markdown.validate.enabled.description": "Enable all error reporting in Markdown files.", "configuration.markdown.validate.referenceLinks.enabled.description": "Validate reference links in Markdown files, for example: `[link][ref]`. Requires enabling `#markdown.validate.enabled#`.", diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts index 1515dbdee85..a4998e787f8 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts @@ -8,15 +8,16 @@ import { ITextDocument } from '../../types/textDocument'; import { Mime } from '../../util/mimes'; import { createInsertUriListEdit, externalUriSchemes } from './shared'; -enum PasteUrlAsFormattedLink { +export enum PasteUrlAsMarkdownLink { Always = 'always', + SmartWithSelection = 'smartWithSelection', Smart = 'smart', Never = 'never' } -function getPasteUrlAsFormattedLinkSetting(document: vscode.TextDocument): PasteUrlAsFormattedLink { +function getPasteUrlAsFormattedLinkSetting(document: vscode.TextDocument): PasteUrlAsMarkdownLink { return vscode.workspace.getConfiguration('markdown', document) - .get('editor.pasteUrlAsFormattedLink.enabled', PasteUrlAsFormattedLink.Smart); + .get('editor.pasteUrlAsFormattedLink.enabled', PasteUrlAsMarkdownLink.SmartWithSelection); } /** @@ -37,17 +38,17 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { token: vscode.CancellationToken, ): Promise { const pasteUrlSetting = getPasteUrlAsFormattedLinkSetting(document); - if (pasteUrlSetting === PasteUrlAsFormattedLink.Never) { + if (pasteUrlSetting === PasteUrlAsMarkdownLink.Never) { return; } const item = dataTransfer.get(Mime.textPlain); - const urlList = await item?.asString(); - if (token.isCancellationRequested || !urlList) { + const text = await item?.asString(); + if (token.isCancellationRequested || !text) { return; } - const uriText = findValidUriInText(urlList); + const uriText = findValidUriInText(text); if (!uriText) { return; } @@ -62,14 +63,10 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { workspaceEdit.set(document.uri, edit.edits); pasteEdit.additionalEdit = workspaceEdit; - // If smart pasting is enabled, deprioritize this provider when: - // - The user has no selection - // - At least one of the ranges occurs in a context where smart pasting is disabled (such as a fenced code block) - if (pasteUrlSetting === PasteUrlAsFormattedLink.Smart) { - if (!ranges.every(range => shouldSmartPaste(document, range))) { - pasteEdit.yieldTo = [{ mimeType: Mime.textPlain }]; - } + if (!shouldInsertMarkdownLinkByDefault(document, pasteUrlSetting, ranges)) { + pasteEdit.yieldTo = [{ mimeType: Mime.textPlain }]; } + return pasteEdit; } } @@ -90,18 +87,35 @@ const smartPasteRegexes = [ { regex: /\$[^$]*\$/g }, // In inline math ]; -export function shouldSmartPaste(document: ITextDocument, selectedRange: vscode.Range): boolean { - // Disable for empty selections and multi-line selections - if (selectedRange.isEmpty || selectedRange.start.line !== selectedRange.end.line) { +export function shouldInsertMarkdownLinkByDefault(document: ITextDocument, pasteUrlSetting: PasteUrlAsMarkdownLink, ranges: readonly vscode.Range[]): boolean { + switch (pasteUrlSetting) { + case PasteUrlAsMarkdownLink.Always: { + return true; + } + case PasteUrlAsMarkdownLink.Smart: { + return ranges.every(range => shouldSmartPasteForSelection(document, range)); + } + case PasteUrlAsMarkdownLink.SmartWithSelection: { + return ( + // At least one range must not be empty + ranges.some(range => document.getText(range).trim().length > 0) + // And all ranges must be smart + && ranges.every(range => shouldSmartPasteForSelection(document, range)) + ); + } + default: { + return false; + } + } +} + +function shouldSmartPasteForSelection(document: ITextDocument, selectedRange: vscode.Range): boolean { + // Disable for multi-line selections + if (selectedRange.start.line !== selectedRange.end.line) { return false; } const rangeText = document.getText(selectedRange); - // Disable for whitespace only selections - if (rangeText.trim().length === 0) { - return false; - } - // Disable when the selection is already a link if (findValidUriInText(rangeText)) { return false; diff --git a/extensions/markdown-language-features/src/test/markdownLink.test.ts b/extensions/markdown-language-features/src/test/markdownLink.test.ts index 0d30ec948d5..62cfa9e9c8a 100644 --- a/extensions/markdown-language-features/src/test/markdownLink.test.ts +++ b/extensions/markdown-language-features/src/test/markdownLink.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import 'mocha'; import * as vscode from 'vscode'; import { InMemoryDocument } from '../client/inMemoryDocument'; -import { findValidUriInText, shouldSmartPaste } from '../languageFeatures/copyFiles/pasteUrlProvider'; +import { PasteUrlAsMarkdownLink, findValidUriInText, shouldInsertMarkdownLinkByDefault } from '../languageFeatures/copyFiles/pasteUrlProvider'; import { createInsertUriListEdit } from '../languageFeatures/copyFiles/shared'; function makeTestDoc(contents: string) { @@ -134,83 +134,89 @@ suite('createEditAddingLinksForUriList', () => { }); - suite('checkSmartPaste', () => { + suite('shouldInsertMarkdownLinkByDefault', () => { - test('Should evaluate pasteAsMarkdownLink as true for selected plain text', () => { + test('Smart should enabled for selected plain text', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('hello world'), new vscode.Range(0, 0, 0, 12)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('hello world'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 12)]), true); }); - test('Should evaluate pasteAsMarkdownLink as false for a valid selected link', () => { + test('Smart should enabled for empty selection', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('https://www.microsoft.com'), new vscode.Range(0, 0, 0, 25)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('xyz'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 0, 0, 0)]), + true); + }); + + test('SmartWithSelection should disable for empty selection', () => { + assert.strictEqual( + shouldInsertMarkdownLinkByDefault(makeTestDoc('xyz'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 0)]), false); }); - test('Should evaluate pasteAsMarkdownLink as false for a valid selected link with trailing whitespace', () => { + test('Smart should disable for selected link', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc(' https://www.microsoft.com '), new vscode.Range(0, 0, 0, 30)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('https://www.microsoft.com'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 25)]), + false); + }); + + test('Smart should disable for selected link with trailing whitespace', () => { + assert.strictEqual( + shouldInsertMarkdownLinkByDefault(makeTestDoc(' https://www.microsoft.com '), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 30)]), false); }); test('Should evaluate pasteAsMarkdownLink as true for a link pasted in square brackets', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('[abc]'), new vscode.Range(0, 1, 0, 4)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('[abc]'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 1, 0, 4)]), true); }); - test('Should evaluate pasteAsMarkdownLink as false for no selection', () => { - assert.strictEqual( - shouldSmartPaste(makeTestDoc('xyz'), new vscode.Range(0, 0, 0, 0)), - false); - }); - test('Should evaluate pasteAsMarkdownLink as false for selected whitespace and new lines', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc(' \r\n\r\n'), new vscode.Range(0, 0, 0, 7)), + shouldInsertMarkdownLinkByDefault(makeTestDoc(' \r\n\r\n'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 7)]), false); }); test('Should evaluate pasteAsMarkdownLink as false for pasting within a backtick code block', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('```\r\n\r\n```'), new vscode.Range(0, 5, 0, 5)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('```\r\n\r\n```'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 5, 0, 5)]), false); }); test('Should evaluate pasteAsMarkdownLink as false for pasting within a tilde code block', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('~~~\r\n\r\n~~~'), new vscode.Range(0, 5, 0, 5)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('~~~\r\n\r\n~~~'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 5, 0, 5)]), false); }); test('Should evaluate pasteAsMarkdownLink as false for pasting within a math block', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('$$$\r\n\r\n$$$'), new vscode.Range(0, 5, 0, 5)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('$$$\r\n\r\n$$$'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 5, 0, 5)]), false); }); test('Should evaluate pasteAsMarkdownLink as false for pasting within a Markdown link', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('[a](bcdef)'), new vscode.Range(0, 4, 0, 6)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('[a](bcdef)'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 4, 0, 6)]), false); }); test('Should evaluate pasteAsMarkdownLink as false for pasting within a Markdown image link', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('![a](bcdef)'), new vscode.Range(0, 5, 0, 10)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('![a](bcdef)'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 5, 0, 10)]), false); }); test('Should evaluate pasteAsMarkdownLink as false for pasting within inline code', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('``'), new vscode.Range(0, 1, 0, 1)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('``'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 1, 0, 1)]), false); }); test('Should evaluate pasteAsMarkdownLink as false for pasting within inline math', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('$$'), new vscode.Range(0, 1, 0, 1)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('$$'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 1, 0, 1)]), false); }); }); From 89ccca8cfc3273968b2cb913c6a46ddca633a9f0 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 10 Jan 2024 11:41:22 -0800 Subject: [PATCH 0211/1897] Allow notebook customize widget toolbar --- .../workbench/contrib/inlineChat/browser/inlineChatWidget.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 6678164b6f5..0f264006e9f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -138,6 +138,7 @@ export interface InlineChatWidgetViewState { export interface IInlineChatWidgetConstructionOptions { menuId: MenuId; + widgetMenuId: MenuId; statusMenuId: MenuId; feedbackMenuId: MenuId; } @@ -384,7 +385,7 @@ export class InlineChatWidget { this._store.add(this._progressBar); - this._store.add(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.widgetToolbar, MENU_INLINE_CHAT_WIDGET, { + this._store.add(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.widgetToolbar, _options.widgetMenuId, { telemetrySource: 'interactiveEditorWidget-toolbar', toolbarOptions: { primaryGroup: 'main' } })); @@ -960,6 +961,7 @@ export class InlineChatZoneWidget extends ZoneWidget { this.widget = this._instaService.createInstance(InlineChatWidget, this.editor, { menuId: MENU_INLINE_CHAT_INPUT, + widgetMenuId: MENU_INLINE_CHAT_WIDGET, statusMenuId: MENU_INLINE_CHAT_WIDGET_STATUS, feedbackMenuId: MENU_INLINE_CHAT_WIDGET_FEEDBACK }); From 518fd1e0de66a354a29bc75b12fb2a379ec4d917 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 10 Jan 2024 16:42:29 -0300 Subject: [PATCH 0212/1897] Merge chat variables proposal into chatAgents2 proposal --- .../common/extensionsApiProposals.ts | 1 - .../vscode.proposed.chatAgents2.d.ts | 53 +++++++++++++++++++ .../vscode.proposed.chatVariables.d.ts | 39 -------------- 3 files changed, 53 insertions(+), 40 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.chatVariables.d.ts diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 6bfddc2c6e5..0e67b531270 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -16,7 +16,6 @@ export const allApiProposals = Object.freeze({ 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', chatTab: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatTab.d.ts', - chatVariables: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatVariables.d.ts', codeActionAI: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionAI.d.ts', codiconDecoration: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts', commentReactor: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentReactor.d.ts', diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 41b4d727ee6..3f379bf95bc 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -384,5 +384,58 @@ declare module 'vscode' { * @returns A new chat agent */ export function createChatAgent(name: string, handler: ChatAgentHandler): ChatAgent2; + + /** + * Register a variable which can be used in a chat request to any agent. + * @param name The name of the variable, to be used in the chat input as `#name`. + * @param description A description of the variable for the chat input suggest widget. + * @param resolver Will be called to provide the chat variable's value when it is used. + */ + export function registerVariable(name: string, description: string, resolver: ChatVariableResolver): Disposable; + } + + /** + * The detail level of this chat variable value. + */ + export enum ChatVariableLevel { + Short = 1, + Medium = 2, + Full = 3 + } + + export interface ChatVariableValue { + /** + * The detail level of this chat variable value. If possible, variable resolvers should try to offer shorter values that will consume fewer tokens in an LLM prompt. + */ + level: ChatVariableLevel; + + /** + * An optional type tag for extensions to communicate the kind of the variable. An extension might use it to interpret the shape of `value`. + */ + kind?: string; + + /** + * The variable's value, which can be included in an LLM prompt as-is, or the chat agent may decide to read the value and do something else with it. + */ + value: string | Uri; + + description?: string; + } + + export interface ChatVariableContext { + /** + * The message entered by the user, which includes this variable. + */ + prompt: string; + } + + export interface ChatVariableResolver { + /** + * A callback to resolve the value of a chat variable. + * @param name The name of the variable. + * @param context Contextual information about this chat request. + * @param token A cancellation token. + */ + resolve(name: string, context: ChatVariableContext, token: CancellationToken): ProviderResult; } } diff --git a/src/vscode-dts/vscode.proposed.chatVariables.d.ts b/src/vscode-dts/vscode.proposed.chatVariables.d.ts deleted file mode 100644 index 7d06baa51f7..00000000000 --- a/src/vscode-dts/vscode.proposed.chatVariables.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 InteractiveRequest { - variables: Record; - } - - export enum ChatVariableLevel { - Short = 1, - Medium = 2, - Full = 3 - } - - export interface ChatVariableValue { - level: ChatVariableLevel; - /** - * An optional type tag for extensions to communicate the kind of the variable. An extension might use it to interpret the shape of `value`. - */ - kind?: string; - value: string | Uri | any; - description?: string; - } - - export interface ChatVariableContext { - message: string; - } - - export interface ChatVariableResolver { - resolve(name: string, context: ChatVariableContext, token: CancellationToken): ProviderResult; - } - - export namespace chat { - export function registerVariable(name: string, description: string, resolver: ChatVariableResolver): Disposable; - } -} From 4bac474bf611a6bcbb38935a1eda91444d6c2ff5 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 10 Jan 2024 11:42:44 -0800 Subject: [PATCH 0213/1897] Update widget layout --- .../browser/media/notebookCellChat.css | 5 ----- .../view/cellParts/chat/cellChatActions.ts | 10 +++++----- .../view/cellParts/chat/cellChatController.ts | 18 ++++++------------ 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookCellChat.css b/src/vs/workbench/contrib/notebook/browser/media/notebookCellChat.css index fda2877f566..abf309564a2 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebookCellChat.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookCellChat.css @@ -15,11 +15,6 @@ padding: 8px 8px 0px 8px; } -.monaco-workbench .notebookOverlay .cell-chat-part > .toolbar { - height: 22px; - margin: 10px 2px; -} - .monaco-workbench .notebookOverlay .cell-chat-part .inline-chat { color: inherit; margin-top: 6px; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts index ce98030fd02..6353f3a614b 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts @@ -6,14 +6,14 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { localize } from 'vs/nls'; -import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { INotebookCellActionContext, NotebookAction, NotebookCellAction } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { insertNewCell } from 'vs/workbench/contrib/notebook/browser/controller/insertCellActions'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS, MENU_CELL_CHAT_WIDGET_TOOLBAR, NotebookCellChatController } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS, NotebookCellChatController } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_EDITABLE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; @@ -34,7 +34,7 @@ registerAction2(class extends NotebookCellAction { primary: KeyCode.Enter }, menu: { - id: MENU_CELL_CHAT_WIDGET, + id: MENU_CELL_CHAT_INPUT, group: 'main', order: 1, when: CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST.negate() @@ -63,7 +63,7 @@ registerAction2(class extends NotebookCellAction { }, icon: Codicon.debugStop, menu: { - id: MENU_CELL_CHAT_WIDGET, + id: MENU_CELL_CHAT_INPUT, group: 'main', order: 1, when: CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST @@ -92,7 +92,7 @@ registerAction2(class extends NotebookCellAction { }, icon: Codicon.close, menu: { - id: MENU_CELL_CHAT_WIDGET_TOOLBAR, + id: MENU_CELL_CHAT_WIDGET, group: 'main', order: 2 } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts index c9412d493ed..189f77cf449 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Dimension, getWindow, h } from 'vs/base/browser/dom'; +import { Dimension, getWindow } from 'vs/base/browser/dom'; import { CancelablePromise, Queue, createCancelablePromise, raceCancellationError } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; @@ -38,6 +38,7 @@ import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbe export const CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused")); export const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request")); +export const MENU_CELL_CHAT_INPUT = MenuId.for('cellChatInput'); export const MENU_CELL_CHAT_WIDGET = MenuId.for('cellChatWidget'); export const MENU_CELL_CHAT_WIDGET_STATUS = MenuId.for('cellChatWidget.status'); export const MENU_CELL_CHAT_WIDGET_FEEDBACK = MenuId.for('cellChatWidget.feedback'); @@ -62,7 +63,6 @@ export class NotebookCellChatController extends Disposable { private _inlineChatListener: IDisposable | undefined; private _widget: InlineChatWidget | undefined; - private readonly _toolbarDOM = h('div.toolbar@editorToolbar'); private _toolbar: MenuWorkbenchToolBar | undefined; private readonly _ctxVisible: IContextKey; private readonly _ctxCellWidgetFocused: IContextKey; @@ -110,7 +110,8 @@ export class NotebookCellChatController extends Disposable { private _initialize(editor: IActiveCodeEditor) { this._widget = this._instantiationService.createInstance(InlineChatWidget, editor, { - menuId: MENU_CELL_CHAT_WIDGET, + menuId: MENU_CELL_CHAT_INPUT, + widgetMenuId: MENU_CELL_CHAT_WIDGET, statusMenuId: MENU_CELL_CHAT_WIDGET_STATUS, feedbackMenuId: MENU_CELL_CHAT_WIDGET_FEEDBACK }); @@ -135,12 +136,6 @@ export class NotebookCellChatController extends Disposable { this._partContainer.appendChild(this._widget.domNode); - this._partContainer.appendChild(this._toolbarDOM.editorToolbar); - - this._toolbar = this._register(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._toolbarDOM.editorToolbar, MENU_CELL_CHAT_WIDGET_TOOLBAR, { - telemetrySource: 'interactiveEditorWidget-toolbar', - toolbarOptions: { primaryGroup: 'main' } - })); } public override dispose(): void { @@ -154,7 +149,6 @@ export class NotebookCellChatController extends Disposable { try { if (this._widget) { this._partContainer.removeChild(this._widget.domNode); - this._partContainer.removeChild(this._toolbarDOM.editorToolbar); } } catch (_ex) { @@ -173,9 +167,9 @@ export class NotebookCellChatController extends Disposable { layout() { if (this._isVisible && this._widget) { - const innerEditorWidth = this._cell.layoutInfo.editorWidth; + const width = this._notebookEditor.getLayoutInfo().width - (/** margin */ 16 + 6) - (/** padding */ 6 * 2); const height = this._widget.getHeight(); - this._widget.layout(new Dimension(innerEditorWidth, height)); + this._widget.layout(new Dimension(width, height)); } } From c83486e790eb99268c729f21d1d65656462503a0 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 10 Jan 2024 11:44:53 -0800 Subject: [PATCH 0214/1897] Add generate button to notebook toolbar --- .../view/cellParts/chat/cellChatActions.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts index 6353f3a614b..efa0cc043d3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts @@ -308,3 +308,21 @@ registerAction2(class extends NotebookCellAction { ctrl.show(); } }); + +MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, { + command: { + id: 'notebook.cell.insertCodeCellWithChat', + icon: Codicon.sparkle, + title: localize('notebookActions.menu.insertCode.ontoolbar', "Generate"), + tooltip: localize('notebookActions.menu.insertCode.tooltip', "Generate Code Cell with Chat") + }, + order: -10, + group: 'navigation/add', + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true), + ContextKeyExpr.notEquals('config.notebook.insertToolbarLocation', 'betweenCells'), + ContextKeyExpr.notEquals('config.notebook.insertToolbarLocation', 'hidden'), + CTX_INLINE_CHAT_HAS_PROVIDER, + ContextKeyExpr.equals(`config.${NotebookSetting.cellChat}`, true) + ) +}); From 7ca79f837f1236b2e8f5333f67aa74eda79f43ab Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 10 Jan 2024 11:50:24 -0800 Subject: [PATCH 0215/1897] Restore focus back to cell when chat widget is dismissed --- .../notebook/browser/view/cellParts/chat/cellChatActions.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts index efa0cc043d3..f8a9e4488b6 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts @@ -172,6 +172,8 @@ registerAction2(class extends NotebookCellAction { // todo discard ctrl.dismiss(true); + // focus on the cell editor container + context.notebookEditor.focusNotebookCell(context.cell, 'container'); } }); From 4aca61d43b8ee38f06107bf730cbd8d0d9263857 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 10 Jan 2024 19:20:29 -0300 Subject: [PATCH 0216/1897] Fix --- src/vs/workbench/api/common/extHost.api.impl.ts | 2 +- src/vs/workbench/api/common/extHostChatVariables.ts | 2 +- src/vs/workbench/contrib/chat/common/chatVariables.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 82fe905ff78..ee65e81f97f 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1384,7 +1384,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostChatProvider.requestChatResponseProvider(extension.identifier, id); }, registerVariable(name: string, description: string, resolver: vscode.ChatVariableResolver) { - checkProposedApiEnabled(extension, 'chatVariables'); + checkProposedApiEnabled(extension, 'chatAgents2'); return extHostChatVariables.registerVariableResolver(extension, name, description, resolver); }, registerMappedEditsProvider(selector: vscode.DocumentSelector, provider: vscode.MappedEditsProvider) { diff --git a/src/vs/workbench/api/common/extHostChatVariables.ts b/src/vs/workbench/api/common/extHostChatVariables.ts index 8f142a13a99..a151a67432f 100644 --- a/src/vs/workbench/api/common/extHostChatVariables.ts +++ b/src/vs/workbench/api/common/extHostChatVariables.ts @@ -29,7 +29,7 @@ export class ExtHostChatVariables implements ExtHostChatVariablesShape { return undefined; } try { - const value = await item.resolver.resolve(item.data.name, { message: messageText }, token); + const value = await item.resolver.resolve(item.data.name, { prompt: messageText }, token); if (value) { return value.map(ChatVariable.from); } diff --git a/src/vs/workbench/contrib/chat/common/chatVariables.ts b/src/vs/workbench/contrib/chat/common/chatVariables.ts index 9aea4d07820..951e5bcf161 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariables.ts @@ -21,7 +21,7 @@ export interface IChatVariableData { export interface IChatRequestVariableValue { level: 'short' | 'medium' | 'full'; kind?: string; - value: string | URI | any; + value: string | URI; description?: string; } From 05548e8124ca91252de8bd3dfab12bfaadb1bce7 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 10 Jan 2024 19:28:57 -0300 Subject: [PATCH 0217/1897] More type fixes, remove unneeded 'kind' --- .../api/browser/mainThreadChatAgents2.ts | 2 +- src/vs/workbench/api/common/extHost.protocol.ts | 6 ++++-- .../api/common/extHostTypeConverters.ts | 16 +++++++--------- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 5 ----- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index 08320bb64d3..a1c61dfc505 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -196,7 +196,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA kind: CompletionItemKind.Text, detail: v.detail, documentation: v.documentation, - command: { id: AddDynamicVariableAction.ID, title: '', arguments: [{ widget, range: rangeAfterInsert, variableData: v.values } satisfies IAddDynamicVariableContext] } + command: { id: AddDynamicVariableAction.ID, title: '', arguments: [{ widget, range: rangeAfterInsert, variableData: revive(v.values) } satisfies IAddDynamicVariableContext] } } satisfies CompletionItem; }); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 646e6791659..4a6368e101c 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1196,7 +1196,7 @@ export interface MainThreadChatAgentsShape2 extends IDisposable { export interface IChatAgentCompletionItem { insertText?: string; label: string | languages.CompletionItemLabel; - values: IChatRequestVariableValue[]; + values: IChatRequestVariableValueDto[]; detail?: string; documentation?: string | IMarkdownString; } @@ -1216,8 +1216,10 @@ export interface MainThreadChatVariablesShape extends IDisposable { $unregisterVariable(handle: number): void; } +export type IChatRequestVariableValueDto = Dto; + export interface ExtHostChatVariablesShape { - $resolveVariable(handle: number, messageText: string, token: CancellationToken): Promise; + $resolveVariable(handle: number, messageText: string, token: CancellationToken): Promise; } export interface MainThreadInlineChatShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 4a02b6ab3ff..bd2120b89ab 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -11,11 +11,11 @@ import * as htmlContent from 'vs/base/common/htmlContent'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ResourceMap, ResourceSet } from 'vs/base/common/map'; import { marked } from 'vs/base/common/marked/marked'; -import { parse, revive } from 'vs/base/common/marshalling'; +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 } from 'vs/base/common/types'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI, UriComponents, isUriComponents } from 'vs/base/common/uri'; import { IURITransformer } from 'vs/base/common/uriIpc'; import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; import { IPosition } from 'vs/editor/common/core/position'; @@ -34,7 +34,10 @@ import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; import { getPrivateApiFor } from 'vs/workbench/api/common/extHostTestingPrivateApi'; import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/editor'; import { IViewBadge } from 'vs/workbench/common/views'; +import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; import { IChatFollowup, IChatReplyFollowup, IChatResponseCommandFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import * as search from 'vs/workbench/contrib/search/common/search'; @@ -42,12 +45,9 @@ import { TestId, TestPosition } from 'vs/workbench/contrib/testing/common/testId import { CoverageDetails, DetailType, ICoveredCount, IFileCoverage, ISerializedTestResults, ITestErrorMessage, ITestItem, ITestTag, TestMessageType, TestResultItem, denamespaceTestTag, namespaceTestTag } from 'vs/workbench/contrib/testing/common/testTypes'; import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import type * as vscode from 'vscode'; import * as types from './extHostTypes'; -import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; -import { InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; export namespace Command { @@ -2253,8 +2253,7 @@ export namespace ChatVariable { export function to(variable: IChatRequestVariableValue): vscode.ChatVariableValue { return { level: ChatVariableLevel.to(variable.level), - kind: variable.kind, - value: revive(variable.value), + value: isUriComponents(variable.value) ? URI.revive(variable.value) : variable.value, description: variable.description }; } @@ -2262,7 +2261,6 @@ export namespace ChatVariable { export function from(variable: vscode.ChatVariableValue): IChatRequestVariableValue { return { level: ChatVariableLevel.from(variable.level), - kind: variable.kind, value: variable.value, description: variable.description }; diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 3f379bf95bc..54a7bf71c06 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -409,11 +409,6 @@ declare module 'vscode' { */ level: ChatVariableLevel; - /** - * An optional type tag for extensions to communicate the kind of the variable. An extension might use it to interpret the shape of `value`. - */ - kind?: string; - /** * The variable's value, which can be included in an LLM prompt as-is, or the chat agent may decide to read the value and do something else with it. */ From 8f328853591a35138fce9b2c561bff0ffc09bff6 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 10 Jan 2024 14:32:58 -0800 Subject: [PATCH 0218/1897] Use markdown engine to enable/disable smart paste (#202192) Fixes #188863 Fixes #188958 Fixes #188868 This is more reliable than using the regular expressions. However the regular expressions are still needed for inline elements --- .../markdown-language-features/package.json | 1 + .../src/extension.shared.ts | 10 +- .../copyFiles/pasteUrlProvider.ts | 82 +++++++---- .../src/markdownEngine.ts | 6 + .../src/test/markdownLink.test.ts | 129 ++++++++++++------ .../src/typings/markdown-it-katex.d.ts | 6 + .../markdown-language-features/yarn.lock | 19 +++ 7 files changed, 180 insertions(+), 73 deletions(-) create mode 100644 extensions/markdown-language-features/src/typings/markdown-it-katex.d.ts diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 160ddbb3a7b..839219f5a94 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -756,6 +756,7 @@ "@types/picomatch": "^2.3.0", "@types/vscode-notebook-renderer": "^1.60.0", "@types/vscode-webview": "^1.57.0", + "@vscode/markdown-it-katex": "^1.0.1", "lodash.throttle": "^4.1.1", "vscode-languageserver-types": "^3.17.2", "vscode-markdown-languageservice": "^0.3.0-alpha.3" diff --git a/extensions/markdown-language-features/src/extension.shared.ts b/extensions/markdown-language-features/src/extension.shared.ts index aceb8f8b5ae..d6499591039 100644 --- a/extensions/markdown-language-features/src/extension.shared.ts +++ b/extensions/markdown-language-features/src/extension.shared.ts @@ -7,13 +7,13 @@ import * as vscode from 'vscode'; import { MdLanguageClient } from './client/client'; import { CommandManager } from './commandManager'; import { registerMarkdownCommands } from './commands/index'; -import { registerLinkPasteSupport } from './languageFeatures/copyFiles/pasteUrlProvider'; +import { registerPasteUrlSupport } from './languageFeatures/copyFiles/pasteUrlProvider'; import { registerResourceDropOrPasteSupport } from './languageFeatures/copyFiles/dropOrPasteResource'; import { registerDiagnosticSupport } from './languageFeatures/diagnostics'; import { registerFindFileReferenceSupport } from './languageFeatures/fileReferences'; import { registerUpdateLinksOnRename } from './languageFeatures/linkUpdater'; import { ILogger } from './logging'; -import { MarkdownItEngine } from './markdownEngine'; +import { IMdParser, MarkdownItEngine } from './markdownEngine'; import { MarkdownContributionProvider } from './markdownExtensions'; import { MdDocumentRenderer } from './preview/documentRenderer'; import { MarkdownPreviewManager } from './preview/previewManager'; @@ -40,7 +40,7 @@ export function activateShared( const previewManager = new MarkdownPreviewManager(contentProvider, logger, contributions, opener); context.subscriptions.push(previewManager); - context.subscriptions.push(registerMarkdownLanguageFeatures(client, commandManager)); + context.subscriptions.push(registerMarkdownLanguageFeatures(client, commandManager, engine)); context.subscriptions.push(registerMarkdownCommands(commandManager, previewManager, telemetryReporter, cspArbiter, engine)); context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => { @@ -51,6 +51,7 @@ export function activateShared( function registerMarkdownLanguageFeatures( client: MdLanguageClient, commandManager: CommandManager, + parser: IMdParser, ): vscode.Disposable { const selector: vscode.DocumentSelector = { language: 'markdown', scheme: '*' }; return vscode.Disposable.from( @@ -58,8 +59,7 @@ function registerMarkdownLanguageFeatures( registerDiagnosticSupport(selector, commandManager), registerFindFileReferenceSupport(commandManager, client), registerResourceDropOrPasteSupport(selector), - registerLinkPasteSupport(selector), + registerPasteUrlSupport(selector, parser), registerUpdateLinksOnRename(client), ); } - diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts index a4998e787f8..c051683ce96 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { IMdParser } from '../../markdownEngine'; import { ITextDocument } from '../../types/textDocument'; import { Mime } from '../../util/mimes'; import { createInsertUriListEdit, externalUriSchemes } from './shared'; @@ -31,6 +32,10 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { public static readonly pasteMimeTypes = [Mime.textPlain]; + constructor( + private readonly _parser: IMdParser, + ) { } + async provideDocumentPasteEdits( document: vscode.TextDocument, ranges: readonly vscode.Range[], @@ -63,7 +68,7 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { workspaceEdit.set(document.uri, edit.edits); pasteEdit.additionalEdit = workspaceEdit; - if (!shouldInsertMarkdownLinkByDefault(document, pasteUrlSetting, ranges)) { + if (!shouldInsertMarkdownLinkByDefault(this._parser, document, pasteUrlSetting, ranges, token)) { pasteEdit.yieldTo = [{ mimeType: Mime.textPlain }]; } @@ -71,45 +76,59 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { } } -export function registerLinkPasteSupport(selector: vscode.DocumentSelector,) { - return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteUrlEditProvider(), { +export function registerPasteUrlSupport(selector: vscode.DocumentSelector, parser: IMdParser) { + return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteUrlEditProvider(parser), { id: PasteUrlEditProvider.id, pasteMimeTypes: PasteUrlEditProvider.pasteMimeTypes, }); } -const smartPasteRegexes = [ +const smartPasteLineRegexes = [ { regex: /(\[[^\[\]]*](?:\([^\(\)]*\)|\[[^\[\]]*]))/g }, // In a Markdown link - { regex: /^```[\s\S]*?```$/gm }, // In a backtick fenced code block - { regex: /^~~~[\s\S]*?~~~$/gm }, // In a tildefenced code block - { regex: /^\$\$[\s\S]*?\$\$$/gm }, // In a fenced math block + { regex: /\$\$[\s\S]*?\$\$/gm }, // In a fenced math block { regex: /`[^`]*`/g }, // In inline code { regex: /\$[^$]*\$/g }, // In inline math + { regex: /^[ ]{0,3}\[\w+\]:\s.*$/g }, // Block link definition (needed as tokens are not generated for these) ]; -export function shouldInsertMarkdownLinkByDefault(document: ITextDocument, pasteUrlSetting: PasteUrlAsMarkdownLink, ranges: readonly vscode.Range[]): boolean { +export async function shouldInsertMarkdownLinkByDefault( + parser: IMdParser, + document: ITextDocument, + pasteUrlSetting: PasteUrlAsMarkdownLink, + ranges: readonly vscode.Range[], + token: vscode.CancellationToken, +): Promise { switch (pasteUrlSetting) { case PasteUrlAsMarkdownLink.Always: { return true; } case PasteUrlAsMarkdownLink.Smart: { - return ranges.every(range => shouldSmartPasteForSelection(document, range)); + return checkSmart(); } case PasteUrlAsMarkdownLink.SmartWithSelection: { - return ( - // At least one range must not be empty - ranges.some(range => document.getText(range).trim().length > 0) - // And all ranges must be smart - && ranges.every(range => shouldSmartPasteForSelection(document, range)) - ); + // At least one range must not be empty + if (!ranges.some(range => document.getText(range).trim().length > 0)) { + return false; + } + // And all ranges must be smart + return checkSmart(); } default: { return false; } } + + async function checkSmart(): Promise { + return (await Promise.all(ranges.map(range => shouldSmartPasteForSelection(parser, document, range, token)))).every(x => x); + } } -function shouldSmartPasteForSelection(document: ITextDocument, selectedRange: vscode.Range): boolean { +async function shouldSmartPasteForSelection( + parser: IMdParser, + document: ITextDocument, + selectedRange: vscode.Range, + token: vscode.CancellationToken, +): Promise { // Disable for multi-line selections if (selectedRange.start.line !== selectedRange.end.line) { return false; @@ -125,18 +144,25 @@ function shouldSmartPasteForSelection(document: ITextDocument, selectedRange: vs return false; } - // TODO: use proper parsing instead of regexes - for (const regex of smartPasteRegexes) { - const matches = [...document.getText().matchAll(regex.regex)]; - for (const match of matches) { - if (match.index !== undefined) { - const matchRange = new vscode.Range( - document.positionAt(match.index), - document.positionAt(match.index + match[0].length) - ); - if (matchRange.intersection(selectedRange)) { - return false; - } + // Check if selection is inside a special block level element using markdown engine + const tokens = await parser.tokenize(document); + if (token.isCancellationRequested) { + return false; + } + for (const token of tokens) { + if (token.map && token.map[0] <= selectedRange.start.line && token.map[1] > selectedRange.start.line) { + if (!['paragraph_open', 'inline', 'heading_open', 'ordered_list_open', 'bullet_list_open', 'list_item_open', 'blockquote_open'].includes(token.type)) { + return false; + } + } + } + + // Run additional regex checks on the current line to check if we are inside an inline element + const line = document.getText(new vscode.Range(selectedRange.start.line, 0, selectedRange.start.line, Number.MAX_SAFE_INTEGER)); + for (const regex of smartPasteLineRegexes) { + for (const match of line.matchAll(regex.regex)) { + if (match.index !== undefined && selectedRange.start.character >= match.index && selectedRange.start.character <= match.index + match[0].length) { + return false; } } } diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts index 2a2e5896dbd..8d67bf6f9c0 100644 --- a/extensions/markdown-language-features/src/markdownEngine.ts +++ b/extensions/markdown-language-features/src/markdownEngine.ts @@ -118,6 +118,12 @@ export class MarkdownItEngine implements IMdParser { }); } + + public async getEngine(resource: vscode.Uri | undefined): Promise { + const config = this._getConfig(resource); + return this._getEngine(config); + } + private async _getEngine(config: MarkdownItConfig): Promise { if (!this._md) { this._md = (async () => { diff --git a/extensions/markdown-language-features/src/test/markdownLink.test.ts b/extensions/markdown-language-features/src/test/markdownLink.test.ts index 62cfa9e9c8a..7b5849e47e2 100644 --- a/extensions/markdown-language-features/src/test/markdownLink.test.ts +++ b/extensions/markdown-language-features/src/test/markdownLink.test.ts @@ -8,6 +8,8 @@ import * as vscode from 'vscode'; import { InMemoryDocument } from '../client/inMemoryDocument'; import { PasteUrlAsMarkdownLink, findValidUriInText, shouldInsertMarkdownLinkByDefault } from '../languageFeatures/copyFiles/pasteUrlProvider'; import { createInsertUriListEdit } from '../languageFeatures/copyFiles/shared'; +import { createNewMarkdownEngine } from './engine'; +import { noopToken } from '../util/cancellation'; function makeTestDoc(contents: string) { return new InMemoryDocument(vscode.Uri.file('test.md'), contents); @@ -136,88 +138,135 @@ suite('createEditAddingLinksForUriList', () => { suite('shouldInsertMarkdownLinkByDefault', () => { - test('Smart should enabled for selected plain text', () => { + test('Smart should be enabled for selected plain text', async () => { assert.strictEqual( - shouldInsertMarkdownLinkByDefault(makeTestDoc('hello world'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 12)]), + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('hello world'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 12)], noopToken), true); }); - test('Smart should enabled for empty selection', () => { + test('Smart should be enabled in headers', async () => { assert.strictEqual( - shouldInsertMarkdownLinkByDefault(makeTestDoc('xyz'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 0, 0, 0)]), + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('# title'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 2, 0, 2)], noopToken), true); }); - test('SmartWithSelection should disable for empty selection', () => { + test('Smart should be enabled in lists', async () => { assert.strictEqual( - shouldInsertMarkdownLinkByDefault(makeTestDoc('xyz'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 0)]), - false); - }); - - test('Smart should disable for selected link', () => { - assert.strictEqual( - shouldInsertMarkdownLinkByDefault(makeTestDoc('https://www.microsoft.com'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 25)]), - false); - }); - - test('Smart should disable for selected link with trailing whitespace', () => { - assert.strictEqual( - shouldInsertMarkdownLinkByDefault(makeTestDoc(' https://www.microsoft.com '), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 30)]), - false); - }); - - test('Should evaluate pasteAsMarkdownLink as true for a link pasted in square brackets', () => { - assert.strictEqual( - shouldInsertMarkdownLinkByDefault(makeTestDoc('[abc]'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 1, 0, 4)]), + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('1. text'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 3, 0, 3)], noopToken), true); }); - test('Should evaluate pasteAsMarkdownLink as false for selected whitespace and new lines', () => { + test('Smart should be enabled in blockquotes', async () => { assert.strictEqual( - shouldInsertMarkdownLinkByDefault(makeTestDoc(' \r\n\r\n'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 7)]), + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('> text'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 3, 0, 3)], noopToken), + true); + }); + + test('Smart should be disabled in indented code blocks', async () => { + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc(' code'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 4, 0, 4)], noopToken), false); }); - test('Should evaluate pasteAsMarkdownLink as false for pasting within a backtick code block', () => { + test('Smart should be disabled in fenced code blocks', async () => { assert.strictEqual( - shouldInsertMarkdownLinkByDefault(makeTestDoc('```\r\n\r\n```'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 5, 0, 5)]), + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('```\r\n\r\n```'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 5, 0, 5)], noopToken), + false); + + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('~~~\r\n\r\n~~~'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 5, 0, 5)], noopToken), false); }); - test('Should evaluate pasteAsMarkdownLink as false for pasting within a tilde code block', () => { + test('Smart should be disabled in math blocks', async () => { + const katex = (await import('@vscode/markdown-it-katex')).default; + const engine = createNewMarkdownEngine(); + (await engine.getEngine(undefined)).use(katex); assert.strictEqual( - shouldInsertMarkdownLinkByDefault(makeTestDoc('~~~\r\n\r\n~~~'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 5, 0, 5)]), + await shouldInsertMarkdownLinkByDefault(engine, makeTestDoc('$$\r\n\r\n$$'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 5, 0, 5)], noopToken), false); }); - test('Should evaluate pasteAsMarkdownLink as false for pasting within a math block', () => { + test('Smart should be disabled in link definitions', async () => { assert.strictEqual( - shouldInsertMarkdownLinkByDefault(makeTestDoc('$$$\r\n\r\n$$$'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 5, 0, 5)]), + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('[ref]: http://example.com'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 4, 0, 6)], noopToken), + false); + + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('[ref]: '), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 7, 0, 7)], noopToken), false); }); - test('Should evaluate pasteAsMarkdownLink as false for pasting within a Markdown link', () => { + test('Smart should be disabled in html blocks', async () => { assert.strictEqual( - shouldInsertMarkdownLinkByDefault(makeTestDoc('[a](bcdef)'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 4, 0, 6)]), + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('

\na\n

'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(1, 0, 1, 0)], noopToken), false); }); - test('Should evaluate pasteAsMarkdownLink as false for pasting within a Markdown image link', () => { + test('Smart should be disabled in Markdown links', async () => { assert.strictEqual( - shouldInsertMarkdownLinkByDefault(makeTestDoc('![a](bcdef)'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 5, 0, 10)]), + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('[a](bcdef)'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 4, 0, 6)], noopToken), false); }); - test('Should evaluate pasteAsMarkdownLink as false for pasting within inline code', () => { + test('Smart should be disabled in Markdown images', async () => { assert.strictEqual( - shouldInsertMarkdownLinkByDefault(makeTestDoc('``'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 1, 0, 1)]), + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('![a](bcdef)'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 5, 0, 10)], noopToken), false); }); - test('Should evaluate pasteAsMarkdownLink as false for pasting within inline math', () => { + test('Smart should be disabled in inline code', async () => { assert.strictEqual( - shouldInsertMarkdownLinkByDefault(makeTestDoc('$$'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 1, 0, 1)]), + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('``'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 1, 0, 1)], noopToken), + false); + + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('``'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 0, 0, 0)], noopToken), false); }); + + test('Smart should be disabled in inline math', async () => { + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('$$'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 1, 0, 1)], noopToken), + false); + }); + + test('Smart should be enabled for empty selection', async () => { + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('xyz'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 0, 0, 0)], noopToken), + true); + }); + + test('SmartWithSelection should disable for empty selection', async () => { + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('xyz'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 0)], noopToken), + false); + }); + + test('Smart should disable for selected link', async () => { + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('https://www.microsoft.com'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 25)], noopToken), + false); + }); + + test('Smart should disable for selected link with trailing whitespace', async () => { + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc(' https://www.microsoft.com '), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 30)], noopToken), + false); + }); + + test('Should evaluate pasteAsMarkdownLink as true for a link pasted in square brackets', async () => { + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('[abc]'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 1, 0, 4)], noopToken), + true); + }); + + test('Should evaluate pasteAsMarkdownLink as false for selected whitespace and new lines', async () => { + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc(' \r\n\r\n'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 7)], noopToken), + false); + }); + + }); }); diff --git a/extensions/markdown-language-features/src/typings/markdown-it-katex.d.ts b/extensions/markdown-language-features/src/typings/markdown-it-katex.d.ts new file mode 100644 index 00000000000..7e9ed78cf5e --- /dev/null +++ b/extensions/markdown-language-features/src/typings/markdown-it-katex.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * 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/markdown-it-katex'; diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index 5e3d1826af5..c3022ef86f7 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -166,6 +166,13 @@ resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.10.tgz#9c513107c690c0dd16e3ec61e453743de15ebdb0" integrity sha512-E1OCmDcDWa0Ya7vtSjp/XfHFGqYJfh+YPC1RkATU71fTac+j1JjCcB3qwSzmlKAighx2WxhLlfhS0RwAN++PFQ== +"@vscode/markdown-it-katex@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@vscode/markdown-it-katex/-/markdown-it-katex-1.0.1.tgz#79c6e7312074e1f897cc22c42ce078d1e72003b0" + integrity sha512-O/HiT5Uc6rN6rSx8tDdgwO1tLSn/lrNeikTzYw1EBG6B2IGLKw4I4e/GBh9DRNSdE9PajCA0tsVBz86qyA7B3A== + dependencies: + katex "^0.16.4" + argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -184,6 +191,11 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +commander@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -204,6 +216,13 @@ highlight.js@^11.8.0: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.8.0.tgz#966518ea83257bae2e7c9a48596231856555bb65" integrity sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg== +katex@^0.16.4: + version "0.16.9" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.9.tgz#bc62d8f7abfea6e181250f85a56e4ef292dcb1fa" + integrity sha512-fsSYjWS0EEOwvy81j3vRA8TEAhQhKiqO+FQaKWp0m39qwOzHVBgAUBIXWj1pB+O2W3fIpNa6Y9KSKCVbfPhyAQ== + dependencies: + commander "^8.3.0" + linkify-it@^3.0.1: version "3.0.3" resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" From 24963b381277459dbaf3ea89c0176706f7061396 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 10 Jan 2024 10:46:33 -0800 Subject: [PATCH 0219/1897] editor: fix decoration lane assignment with word wrap --- .../browser/viewParts/glyphMargin/glyphLanesModel.ts | 2 +- src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphLanesModel.ts b/src/vs/editor/browser/viewParts/glyphMargin/glyphLanesModel.ts index 2b680684c82..177391a85b8 100644 --- a/src/vs/editor/browser/viewParts/glyphMargin/glyphLanesModel.ts +++ b/src/vs/editor/browser/viewParts/glyphMargin/glyphLanesModel.ts @@ -15,7 +15,7 @@ export class GlyphMarginLanesModel implements IGlyphMarginLanesModel { private _requiredLanes = 1; // always render at least one lane constructor(maxLine: number) { - this.lanes = new Uint8Array(Math.ceil((maxLine * MAX_LANE) / 8)); + this.lanes = new Uint8Array(Math.ceil(((maxLine + 1) * MAX_LANE) / 8)); } public get requiredLanes() { diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts index ed5393fa9b3..5625e837871 100644 --- a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts +++ b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts @@ -12,6 +12,7 @@ import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/ import { ViewPart } from 'vs/editor/browser/view/viewPart'; import { GlyphMarginLanesModel } from 'vs/editor/browser/viewParts/glyphMargin/glyphLanesModel'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { GlyphMarginLane, IGlyphMarginLanesModel } from 'vs/editor/common/model'; import * as viewEvents from 'vs/editor/common/viewEvents'; @@ -273,7 +274,8 @@ export class GlyphMarginWidgets extends ViewPart { const zIndex = d.options.zIndex ?? 0; for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) { - const laneIndex = this._lanesModel.getLanesAtLine(lineNumber).indexOf(lane); + const modelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber, 0)); + const laneIndex = this._lanesModel.getLanesAtLine(modelPosition.lineNumber).indexOf(lane); requests.push(new DecorationBasedGlyphRenderRequest(lineNumber, laneIndex, zIndex, glyphMarginClassName)); } } @@ -293,7 +295,8 @@ export class GlyphMarginWidgets extends ViewPart { // The widget is in the viewport, find a good line for it const widgetLineNumber = Math.max(startLineNumber, visibleStartLineNumber); - const laneIndex = this._lanesModel.getLanesAtLine(widgetLineNumber).indexOf(widget.preference.lane); + const modelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(widgetLineNumber, 0)); + const laneIndex = this._lanesModel.getLanesAtLine(modelPosition.lineNumber).indexOf(widget.preference.lane); requests.push(new WidgetBasedGlyphRenderRequest(widgetLineNumber, laneIndex, widget.preference.zIndex, widget)); } } From 709db7827fe89642ecc02c596f7831ce3887d931 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 10 Jan 2024 12:28:03 -0800 Subject: [PATCH 0220/1897] editor: include lane in GUTTER_GLYPH_MARGIN hover events Also fixes handling of testing clicks and margin hovers which were not aware of multiple lanes. Hover messages are now shown correctly and tests no longer run if e.g. a breakpoint decoration on the same line is clicked. Note sure if this was the best way to do this, let me know. --- .../editor/browser/controller/mouseTarget.ts | 6 ++- src/vs/editor/browser/editorBrowser.ts | 1 + src/vs/editor/browser/view.ts | 50 +++++++++---------- .../viewParts/glyphMargin/glyphMargin.ts | 13 ++--- src/vs/editor/common/model.ts | 12 +++++ src/vs/editor/common/viewModel.ts | 4 +- .../viewModel}/glyphLanesModel.ts | 13 ++++- .../editor/common/viewModel/viewModelImpl.ts | 5 +- src/vs/editor/contrib/hover/browser/hover.ts | 4 +- .../contrib/hover/browser/marginHover.ts | 20 +++++++- .../viewModel}/glyphLanesModel.test.ts | 2 +- src/vs/monaco.d.ts | 11 ++++ .../testing/browser/testingDecorations.ts | 5 +- 13 files changed, 99 insertions(+), 47 deletions(-) rename src/vs/editor/{browser/viewParts/glyphMargin => common/viewModel}/glyphLanesModel.ts (87%) rename src/vs/editor/test/{browser/view => common/viewModel}/glyphLanesModel.test.ts (96%) diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index b6bb7881850..8598d9140b9 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -20,6 +20,7 @@ import * as dom from 'vs/base/browser/dom'; import { AtomicTabMoveOperations, Direction } from 'vs/editor/common/cursor/cursorAtomicMoveOperations'; import { PositionAffinity } from 'vs/editor/common/model'; import { InjectedText } from 'vs/editor/common/modelLineProjectionData'; +import { Mutable } from 'vs/base/common/types'; const enum HitTestResultType { Unknown, @@ -672,7 +673,7 @@ export class MouseTargetFactory { const res = ctx.getFullLineRangeAtCoord(request.mouseVerticalOffset); const pos = res.range.getStartPosition(); let offset = Math.abs(request.relativePos.x); - const detail: IMouseTargetMarginData = { + const detail: Mutable = { isAfterLines: res.isAfterLines, glyphMarginLeft: ctx.layoutInfo.glyphMarginLeft, glyphMarginWidth: ctx.layoutInfo.glyphMarginWidth, @@ -684,6 +685,9 @@ export class MouseTargetFactory { if (offset <= ctx.layoutInfo.glyphMarginWidth) { // On the glyph margin + const modelCoordinate = ctx.viewModel.coordinatesConverter.convertViewPositionToModelPosition(res.range.getStartPosition()); + const lanes = ctx.viewModel.glyphLanes.getLanesAtLine(modelCoordinate.lineNumber); + detail.glyphMarginLane = lanes[Math.floor(offset / ctx.lineHeight)]; return request.fulfillMargin(MouseTargetType.GUTTER_GLYPH_MARGIN, pos, res.range, detail); } offset -= ctx.layoutInfo.glyphMarginWidth; diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 0b8fc09755a..9fe0301b8ce 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -405,6 +405,7 @@ export interface IMouseTargetMarginData { readonly isAfterLines: boolean; readonly glyphMarginLeft: number; readonly glyphMarginWidth: number; + readonly glyphMarginLane?: GlyphMarginLane; readonly lineNumbersWidth: number; readonly offsetX: number; } diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index c8dc8cefa27..ba0b5008760 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -4,23 +4,28 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { Selection } from 'vs/editor/common/core/selection'; -import { Range } from 'vs/editor/common/core/range'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; +import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; +import { inputLatency } from 'vs/base/browser/performance'; +import { CodeWindow } from 'vs/base/browser/window'; import { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IPointerHandlerHelper } from 'vs/editor/browser/controller/mouseHandler'; +import { PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget'; import { PointerHandler } from 'vs/editor/browser/controller/pointerHandler'; import { IVisibleRangeProvider, TextAreaHandler } from 'vs/editor/browser/controller/textAreaHandler'; -import { IContentWidget, IContentWidgetPosition, IOverlayWidget, IOverlayWidgetPosition, IMouseTarget, IViewZoneChangeAccessor, IEditorAriaOptions, IGlyphMarginWidget, IGlyphMarginWidgetPosition } from 'vs/editor/browser/editorBrowser'; +import { IContentWidget, IContentWidgetPosition, IEditorAriaOptions, IGlyphMarginWidget, IGlyphMarginWidgetPosition, IMouseTarget, IOverlayWidget, IOverlayWidgetPosition, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; +import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext'; import { ICommandDelegate, ViewController } from 'vs/editor/browser/view/viewController'; -import { ViewUserInputEvents } from 'vs/editor/browser/view/viewUserInputEvents'; import { ContentViewOverlays, MarginViewOverlays } from 'vs/editor/browser/view/viewOverlays'; import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart'; +import { ViewUserInputEvents } from 'vs/editor/browser/view/viewUserInputEvents'; +import { BlockDecorations } from 'vs/editor/browser/viewParts/blockDecorations/blockDecorations'; import { ViewContentWidgets } from 'vs/editor/browser/viewParts/contentWidgets/contentWidgets'; import { CurrentLineHighlightOverlay, CurrentLineMarginHighlightOverlay } from 'vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight'; import { DecorationsOverlay } from 'vs/editor/browser/viewParts/decorations/decorations'; import { EditorScrollbar } from 'vs/editor/browser/viewParts/editorScrollbar/editorScrollbar'; +import { GlyphMarginWidgets } from 'vs/editor/browser/viewParts/glyphMargin/glyphMargin'; import { IndentGuidesOverlay } from 'vs/editor/browser/viewParts/indentGuides/indentGuides'; import { LineNumbersOverlay } from 'vs/editor/browser/viewParts/lineNumbers/lineNumbers'; import { ViewLines } from 'vs/editor/browser/viewParts/lines/viewLines'; @@ -36,27 +41,21 @@ import { ScrollDecorationViewPart } from 'vs/editor/browser/viewParts/scrollDeco import { SelectionsOverlay } from 'vs/editor/browser/viewParts/selections/selections'; import { ViewCursors } from 'vs/editor/browser/viewParts/viewCursors/viewCursors'; import { ViewZones } from 'vs/editor/browser/viewParts/viewZones/viewZones'; -import { Position } from 'vs/editor/common/core/position'; -import { ScrollType } from 'vs/editor/common/editorCommon'; +import { WhitespaceOverlay } from 'vs/editor/browser/viewParts/whitespace/whitespace'; import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration'; -import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext'; -import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; +import { ScrollType } from 'vs/editor/common/editorCommon'; +import { GlyphMarginLane, IGlyphMarginLanesModel } from 'vs/editor/common/model'; +import { ViewEventHandler } from 'vs/editor/common/viewEventHandler'; import * as viewEvents from 'vs/editor/common/viewEvents'; import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; -import { ViewEventHandler } from 'vs/editor/common/viewEventHandler'; import { IViewModel } from 'vs/editor/common/viewModel'; -import { getThemeTypeSelector, IColorTheme } from 'vs/platform/theme/common/themeService'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget'; -import { BlockDecorations } from 'vs/editor/browser/viewParts/blockDecorations/blockDecorations'; -import { inputLatency } from 'vs/base/browser/performance'; -import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; -import { WhitespaceOverlay } from 'vs/editor/browser/viewParts/whitespace/whitespace'; -import { GlyphMarginWidgets } from 'vs/editor/browser/viewParts/glyphMargin/glyphMargin'; -import { GlyphMarginLane } from 'vs/editor/common/model'; +import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { CodeWindow } from 'vs/base/browser/window'; -import { GlyphMarginLanesModel } from 'vs/editor/browser/viewParts/glyphMargin/glyphLanesModel'; +import { IColorTheme, getThemeTypeSelector } from 'vs/platform/theme/common/themeService'; export interface IContentWidgetData { @@ -244,8 +243,9 @@ export class View extends ViewEventHandler { this._pointerHandler = this._register(new PointerHandler(this._context, viewController, this._createPointerHandlerHelper())); } - private _computeGlyphMarginLanes(): GlyphMarginLanesModel { + private _computeGlyphMarginLanes(): IGlyphMarginLanesModel { const model = this._context.viewModel.model; + const laneModel = this._context.viewModel.glyphLanes; type Glyph = { range: Range; lane: GlyphMarginLane; persist?: boolean }; let glyphs: Glyph[] = []; let maxLineNumber = 0; @@ -267,14 +267,12 @@ export class View extends ViewEventHandler { // Sorted by their start position glyphs.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); - const lanes = new GlyphMarginLanesModel(maxLineNumber); + laneModel.reset(maxLineNumber); for (const glyph of glyphs) { - lanes.push(glyph.lane, glyph.range, glyph.persist); + laneModel.push(glyph.lane, glyph.range, glyph.persist); } - this._glyphMarginWidgets.updateLanesModel(lanes); - - return lanes; + return laneModel; } private _createPointerHandlerHelper(): IPointerHandlerHelper { diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts index 5625e837871..48c79a783ea 100644 --- a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts +++ b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts @@ -10,11 +10,10 @@ import { IGlyphMarginWidget, IGlyphMarginWidgetPosition } from 'vs/editor/browse import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay'; import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext'; import { ViewPart } from 'vs/editor/browser/view/viewPart'; -import { GlyphMarginLanesModel } from 'vs/editor/browser/viewParts/glyphMargin/glyphLanesModel'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { GlyphMarginLane, IGlyphMarginLanesModel } from 'vs/editor/common/model'; +import { GlyphMarginLane } from 'vs/editor/common/model'; import * as viewEvents from 'vs/editor/common/viewEvents'; import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; @@ -124,7 +123,6 @@ export class GlyphMarginWidgets extends ViewPart { public domNode: FastDomNode; - private _lanesModel: IGlyphMarginLanesModel; private _lineHeight: number; private _glyphMargin: boolean; private _glyphMarginLeft: number; @@ -139,7 +137,6 @@ export class GlyphMarginWidgets extends ViewPart { constructor(context: ViewContext) { super(context); this._context = context; - this._lanesModel = new GlyphMarginLanesModel(0); const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); @@ -251,10 +248,6 @@ export class GlyphMarginWidgets extends ViewPart { } } - public updateLanesModel(model: GlyphMarginLanesModel) { - this._lanesModel = model; - } - // --- end widget management private _collectDecorationBasedGlyphRenderRequest(ctx: RenderingContext, requests: GlyphRenderRequest[]): void { @@ -275,7 +268,7 @@ export class GlyphMarginWidgets extends ViewPart { for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) { const modelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber, 0)); - const laneIndex = this._lanesModel.getLanesAtLine(modelPosition.lineNumber).indexOf(lane); + const laneIndex = this._context.viewModel.glyphLanes.getLanesAtLine(modelPosition.lineNumber).indexOf(lane); requests.push(new DecorationBasedGlyphRenderRequest(lineNumber, laneIndex, zIndex, glyphMarginClassName)); } } @@ -296,7 +289,7 @@ export class GlyphMarginWidgets extends ViewPart { // The widget is in the viewport, find a good line for it const widgetLineNumber = Math.max(startLineNumber, visibleStartLineNumber); const modelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(widgetLineNumber, 0)); - const laneIndex = this._lanesModel.getLanesAtLine(modelPosition.lineNumber).indexOf(widget.preference.lane); + const laneIndex = this._context.viewModel.glyphLanes.getLanesAtLine(modelPosition.lineNumber).indexOf(widget.preference.lane); requests.push(new WidgetBasedGlyphRenderRequest(widgetLineNumber, laneIndex, widget.preference.zIndex, widget)); } } diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index dabe64051a3..3d84860632f 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -53,6 +53,18 @@ export interface IGlyphMarginLanesModel { * Gets the lanes that should be rendered starting at a given line number. */ getLanesAtLine(lineNumber: number): GlyphMarginLane[]; + + /** + * Resets the model and ensures it can contain at least `maxLine` lines. + */ + reset(maxLine: number): void; + + /** + * Registers that a lane should be visible at the Range in the model. + * @param persist - if true, notes that the lane should always be visible, + * even on lines where there's no specific request for that lane. + */ + push(lane: GlyphMarginLane, range: Range, persist?: boolean): void; } /** diff --git a/src/vs/editor/common/viewModel.ts b/src/vs/editor/common/viewModel.ts index 3c3f6af54ad..adf6cd307c6 100644 --- a/src/vs/editor/common/viewModel.ts +++ b/src/vs/editor/common/viewModel.ts @@ -12,7 +12,7 @@ import { CursorConfiguration, CursorState, EditOperationType, IColumnSelectData, import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; import { INewScrollPosition, ScrollType } from 'vs/editor/common/editorCommon'; import { EditorTheme } from 'vs/editor/common/editorTheme'; -import { EndOfLinePreference, IModelDecorationOptions, ITextModel, PositionAffinity } from 'vs/editor/common/model'; +import { EndOfLinePreference, IGlyphMarginLanesModel, IModelDecorationOptions, ITextModel, PositionAffinity } from 'vs/editor/common/model'; import { ILineBreaksComputer, InjectedText } from 'vs/editor/common/modelLineProjectionData'; import { BracketGuideOptions, IActiveIndentGuideInfo, IndentGuide } from 'vs/editor/common/textModelGuides'; import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens'; @@ -29,6 +29,8 @@ export interface IViewModel extends ICursorSimpleModel { readonly cursorConfig: CursorConfiguration; + readonly glyphLanes: IGlyphMarginLanesModel; + addViewEventHandler(eventHandler: ViewEventHandler): void; removeViewEventHandler(eventHandler: ViewEventHandler): void; diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphLanesModel.ts b/src/vs/editor/common/viewModel/glyphLanesModel.ts similarity index 87% rename from src/vs/editor/browser/viewParts/glyphMargin/glyphLanesModel.ts rename to src/vs/editor/common/viewModel/glyphLanesModel.ts index 177391a85b8..c63c9b24f8d 100644 --- a/src/vs/editor/browser/viewParts/glyphMargin/glyphLanesModel.ts +++ b/src/vs/editor/common/viewModel/glyphLanesModel.ts @@ -10,7 +10,7 @@ import { GlyphMarginLane, IGlyphMarginLanesModel } from 'vs/editor/common/model' const MAX_LANE = GlyphMarginLane.Right; export class GlyphMarginLanesModel implements IGlyphMarginLanesModel { - private readonly lanes: Uint8Array; + private lanes: Uint8Array; private persist = 0; private _requiredLanes = 1; // always render at least one lane @@ -18,11 +18,20 @@ export class GlyphMarginLanesModel implements IGlyphMarginLanesModel { this.lanes = new Uint8Array(Math.ceil(((maxLine + 1) * MAX_LANE) / 8)); } + public reset(maxLine: number) { + const bytes = Math.ceil(((maxLine + 1) * MAX_LANE) / 8); + if (this.lanes.length < bytes) { + this.lanes = new Uint8Array(bytes); + } else { + this.lanes.fill(0); + } + this._requiredLanes = 1; + } + public get requiredLanes() { return this._requiredLanes; } - /** Adds a new range to the model. Assumes ranges are added in ascending order of line number. */ public push(lane: GlyphMarginLane, range: Range, persist?: boolean): void { if (persist) { this.persist |= (1 << (lane - 1)); diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index ff5f4eef6f6..bf152209d83 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -19,7 +19,7 @@ import { Range } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { ICommand, ICursorState, IViewState, ScrollType } from 'vs/editor/common/editorCommon'; import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration'; -import { EndOfLinePreference, IAttachedView, ICursorStateComputer, IIdentifiedSingleEditOperation, ITextModel, PositionAffinity, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { EndOfLinePreference, IAttachedView, ICursorStateComputer, IGlyphMarginLanesModel, IIdentifiedSingleEditOperation, ITextModel, PositionAffinity, TrackedRangeStickiness } from 'vs/editor/common/model'; import { IActiveIndentGuideInfo, BracketGuideOptions, IndentGuide } from 'vs/editor/common/textModelGuides'; import { ModelDecorationMinimapOptions, ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel'; import * as textModelEvents from 'vs/editor/common/textModelEvents'; @@ -39,6 +39,7 @@ import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecora import { FocusChangedEvent, HiddenAreasChangedEvent, ModelContentChangedEvent, ModelDecorationsChangedEvent, ModelLanguageChangedEvent, ModelLanguageConfigurationChangedEvent, ModelOptionsChangedEvent, ModelTokensChangedEvent, OutgoingViewModelEvent, ReadOnlyEditAttemptEvent, ScrollChangedEvent, ViewModelEventDispatcher, ViewModelEventsCollector, ViewZonesChangedEvent } from 'vs/editor/common/viewModelEventDispatcher'; import { IViewModelLines, ViewModelLinesFromModelAsIs, ViewModelLinesFromProjectedModel } from 'vs/editor/common/viewModel/viewModelLines'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { GlyphMarginLanesModel } from 'vs/editor/common/viewModel/glyphLanesModel'; const USE_IDENTITY_LINES_COLLECTION = true; @@ -58,6 +59,7 @@ export class ViewModel extends Disposable implements IViewModel { public readonly viewLayout: ViewLayout; private readonly _cursor: CursorsController; private readonly _decorations: ViewModelDecorations; + public readonly glyphLanes: IGlyphMarginLanesModel; constructor( editorId: number, @@ -81,6 +83,7 @@ export class ViewModel extends Disposable implements IViewModel { this._updateConfigurationViewLineCount = this._register(new RunOnceScheduler(() => this._updateConfigurationViewLineCountNow(), 0)); this._hasFocus = false; this._viewportStart = ViewportStart.create(this.model); + this.glyphLanes = new GlyphMarginLanesModel(0); if (USE_IDENTITY_LINES_COLLECTION && this.model.isTooLargeForTokenization()) { diff --git a/src/vs/editor/contrib/hover/browser/hover.ts b/src/vs/editor/contrib/hover/browser/hover.ts index 06964b7da09..08b2a7bb68d 100644 --- a/src/vs/editor/contrib/hover/browser/hover.ts +++ b/src/vs/editor/contrib/hover/browser/hover.ts @@ -294,10 +294,10 @@ export class HoverController extends Disposable implements IEditorContribution { return; } - if (target.type === MouseTargetType.GUTTER_GLYPH_MARGIN && target.position) { + if (target.type === MouseTargetType.GUTTER_GLYPH_MARGIN && target.position && target.detail.glyphMarginLane) { this._contentWidget?.hide(); const glyphWidget = this._getOrCreateGlyphWidget(); - glyphWidget.startShowingAt(target.position.lineNumber); + glyphWidget.startShowingAt(target.position.lineNumber, target.detail.glyphMarginLane); return; } if (_sticky) { diff --git a/src/vs/editor/contrib/hover/browser/marginHover.ts b/src/vs/editor/contrib/hover/browser/marginHover.ts index 803fc30fd39..98af0a06cda 100644 --- a/src/vs/editor/contrib/hover/browser/marginHover.ts +++ b/src/vs/editor/contrib/hover/browser/marginHover.ts @@ -14,6 +14,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/browser/hoverOperation'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { HoverWidget } from 'vs/base/browser/ui/hover/hoverWidget'; +import { GlyphMarginLane } from 'vs/editor/common/model'; const $ = dom.$; @@ -98,8 +99,8 @@ export class MarginHoverWidget extends Disposable implements IOverlayWidget { } } - public startShowingAt(lineNumber: number): void { - if (this._computer.lineNumber === lineNumber) { + public startShowingAt(lineNumber: number, lane: GlyphMarginLane): void { + if (this._computer.lineNumber === lineNumber && this._computer.lane === lane) { // We have to show the widget at the exact same line number as before, so no work is needed return; } @@ -109,6 +110,7 @@ export class MarginHoverWidget extends Disposable implements IOverlayWidget { this.hide(); this._computer.lineNumber = lineNumber; + this._computer.lane = lane; this._hoverOperation.start(HoverStartMode.Delayed); } @@ -176,6 +178,7 @@ export class MarginHoverWidget extends Disposable implements IOverlayWidget { class MarginHoverComputer implements IHoverComputer { private _lineNumber: number = -1; + private _lane = GlyphMarginLane.Center; public get lineNumber(): number { return this._lineNumber; @@ -185,6 +188,14 @@ class MarginHoverComputer implements IHoverComputer { this._lineNumber = value; } + public get lane(): number { + return this._lane; + } + + public set lane(value: GlyphMarginLane) { + this._lane = value; + } + constructor( private readonly _editor: ICodeEditor ) { @@ -210,6 +221,11 @@ class MarginHoverComputer implements IHoverComputer { continue; } + const lane = d.options.glyphMargin?.position ?? GlyphMarginLane.Center; + if (lane !== this._lane) { + continue; + } + const hoverMessage = d.options.glyphMarginHoverMessage; if (!hoverMessage || isEmptyMarkdownString(hoverMessage)) { continue; diff --git a/src/vs/editor/test/browser/view/glyphLanesModel.test.ts b/src/vs/editor/test/common/viewModel/glyphLanesModel.test.ts similarity index 96% rename from src/vs/editor/test/browser/view/glyphLanesModel.test.ts rename to src/vs/editor/test/common/viewModel/glyphLanesModel.test.ts index 1055c5f1a5f..7c0522a84f0 100644 --- a/src/vs/editor/test/browser/view/glyphLanesModel.test.ts +++ b/src/vs/editor/test/common/viewModel/glyphLanesModel.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { GlyphMarginLanesModel, } from 'vs/editor/browser/viewParts/glyphMargin/glyphLanesModel'; +import { GlyphMarginLanesModel, } from 'vs/editor/common/viewModel/glyphLanesModel'; import { Range } from 'vs/editor/common/core/range'; import { GlyphMarginLane } from 'vs/editor/common/model'; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index ef0d28b496e..ac4a31bd48e 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1593,6 +1593,16 @@ declare namespace monaco.editor { * Gets the lanes that should be rendered starting at a given line number. */ getLanesAtLine(lineNumber: number): GlyphMarginLane[]; + /** + * Resets the model and ensures it can contain at least `maxLine` lines. + */ + reset(maxLine: number): void; + /** + * Registers that a lane should be visible at the Range in the model. + * @param persist - if true, notes that the lane should always be visible, + * even on lines where there's no specific request for that lane. + */ + push(lane: GlyphMarginLane, range: Range, persist?: boolean): void; } /** @@ -5464,6 +5474,7 @@ declare namespace monaco.editor { readonly isAfterLines: boolean; readonly glyphMarginLeft: number; readonly glyphMarginWidth: number; + readonly glyphMarginLane?: GlyphMarginLane; readonly lineNumbersWidth: number; readonly offsetX: number; } diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index a062c3d79d2..6083c9af896 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -25,7 +25,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { overviewRulerError, overviewRulerInfo } from 'vs/editor/common/core/editorColorRegistry'; import { IRange } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { IModelDeltaDecoration, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { GlyphMarginLane, IModelDeltaDecoration, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -56,6 +56,7 @@ import { TestUriType, buildTestUri, parseTestUri } from 'vs/workbench/contrib/te const MAX_INLINE_MESSAGE_LENGTH = 128; const MAX_TESTS_IN_SUBMENU = 30; +const GLYPH_MARGIN_LANE = GlyphMarginLane.Center; function isOriginalInDiffEditor(codeEditorService: ICodeEditorService, codeEditor: ICodeEditor): boolean { const diffEditors = codeEditorService.listDiffEditors(); @@ -586,6 +587,7 @@ const createRunTestDecoration = (tests: readonly IncrementalTestCollectionItem[] return hoverMessage; }, + glyphMargin: { position: GLYPH_MARGIN_LANE }, glyphMarginClassName, stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, zIndex: 10000, @@ -741,6 +743,7 @@ abstract class RunTestDecoration { /** @inheritdoc */ public click(e: IEditorMouseEvent): boolean { if (e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN + || e.target.detail.glyphMarginLane !== GLYPH_MARGIN_LANE // handled by editor gutter context menu || e.event.rightButton || isMacintosh && e.event.leftButton && e.event.ctrlKey From a45edfc415d19852fa3c3e1cd255a02de9e016d2 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Wed, 10 Jan 2024 16:28:08 -0800 Subject: [PATCH 0221/1897] Change font family for nb sticky scroll (#202200) change font family for nb sticky scroll --- .../notebook/browser/media/notebookEditorStickyScroll.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css b/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css index 5750b20cd65..ce91cb50910 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css @@ -9,8 +9,8 @@ background-color: var(--vscode-notebook-editorBackground); z-index: var(--z-index-notebook-sticky-scroll); width: 100%; - font-family: var(--notebook-cell-input-preview-font-family); } + .monaco-workbench .notebookOverlay .notebook-sticky-scroll-container From c6f71bab2c97f3fcedd7330325626e8cf9c5f1df Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 10 Jan 2024 22:31:52 -0800 Subject: [PATCH 0222/1897] testing: initial visible in-editor coverage decorations (#202205) Still needs polishing, but works and is unobtrusive --- .../browser/codeCoverageDecorations.ts | 233 +++++++++++++++--- .../contrib/testing/browser/media/testing.css | 4 + .../testing/browser/testing.contribution.ts | 14 +- .../contrib/testing/browser/theme.ts | 13 + .../contrib/testing/common/testTypes.ts | 1 + ..._Decorations_CoverageDetailsModel_1.0.snap | 30 +++ ..._Decorations_CoverageDetailsModel_2.0.snap | 22 ++ ..._Decorations_CoverageDetailsModel_3.0.snap | 22 ++ ..._Decorations_CoverageDetailsModel_4.0.snap | 22 ++ .../browser/codeCoverageDecorations.test.ts | 89 +++++++ 10 files changed, 413 insertions(+), 37 deletions(-) create mode 100644 src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_1.0.snap create mode 100644 src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_2.0.snap create mode 100644 src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_3.0.snap create mode 100644 src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_4.0.snap create mode 100644 src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts diff --git a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts index c2b80080edc..856c7d4e6ae 100644 --- a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts @@ -4,24 +4,37 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { autorun, derived, observableFromEvent } from 'vs/base/common/observable'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { GlyphMarginLane, ITextModel } from 'vs/editor/common/model'; +import { GlyphMarginLane, IModelDecorationOptions, ITextModel } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; import { ILogService } from 'vs/platform/log/common/log'; import { FileCoverage } from 'vs/workbench/contrib/testing/common/testCoverage'; import { ITestCoverageService } from 'vs/workbench/contrib/testing/common/testCoverageService'; -import { DetailType } from 'vs/workbench/contrib/testing/common/testTypes'; +import { CoverageDetails, DetailType, IStatementCoverage } from 'vs/workbench/contrib/testing/common/testTypes'; + +const GLYPH_LANE = GlyphMarginLane.Left; +const MAX_HOVERED_LINES = 30; +const CLASS_HIT = 'coverage-deco-hit'; +const CLASS_MISS = 'coverage-deco-miss'; export class CodeCoverageDecorations extends Disposable implements IEditorContribution { private loadingCancellation?: CancellationTokenSource; private readonly displayedStore = this._register(new DisposableStore()); + private readonly hoveredStore = this._register(new DisposableStore()); + private decorationIds = new Map; + }>(); + private hoveredLineNumber?: number; constructor( - editor: ICodeEditor, + private readonly editor: ICodeEditor, @ITestCoverageService coverage: ITestCoverageService, @ILogService private readonly log: ILogService, ) { @@ -51,6 +64,67 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri this.clear(); } })); + + this._register(editor.onMouseMove(e => { + if (e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN + && e.target.detail.glyphMarginLane === GLYPH_LANE) { + this.hoverLineNumber(editor.getModel()!, e.target.position.lineNumber); + } else { + this.hoveredStore.clear(); + } + })); + } + + private hoverLineNumber(model: ITextModel, lineNumber: number) { + if (lineNumber === this.hoveredLineNumber) { + return; + } + + this.hoveredLineNumber = lineNumber; + this.hoveredStore.clear(); + + const todo = [{ line: lineNumber, dir: 0 }]; + const toEnable = new Set(); + for (let i = 0; i < todo.length && i < MAX_HOVERED_LINES; i++) { + const { line, dir } = todo[i]; + let found = false; + for (const decoration of model.getLineDecorations(line)) { + if (this.decorationIds.has(decoration.id)) { + toEnable.add(decoration.id); + found = true; + } + } + if (found) { + if (dir <= 0) { + todo.push({ line: line - 1, dir: -1 }); + } + if (dir >= 0) { + todo.push({ line: line + 1, dir: 1 }); + } + } + } + + model.changeDecorations(e => { + for (const id of toEnable) { + const { hoverOptions, options } = this.decorationIds.get(id)!; + e.changeDecorationOptions(id, { ...options, ...hoverOptions }); + } + }); + + this.hoveredStore.add(this.editor.onMouseLeave(e => { + this.hoveredStore.clear(); + })); + + this.hoveredStore.add(toDisposable(() => { + model.changeDecorations(e => { + for (const id of toEnable) { + const deco = this.decorationIds.get(id); + if (deco) { + e.changeDecorationOptions(id, deco.options); + } + } + }); + })); } private async apply(model: ITextModel, coverage: FileCoverage) { @@ -59,42 +133,57 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri return this.clear(); } - const decorations: string[] = []; - model.changeDecorations(e => { - for (const detail of details) { - const range = detail.location instanceof Range ? detail.location : Range.fromPositions(detail.location); - if (detail.type === DetailType.Statement) { - const cls = detail.count > 0 ? 'coverage-deco-hit' : 'coverage-deco-miss'; - decorations.push(e.addDecoration(range, { - showIfCollapsed: false, - glyphMargin: { position: GlyphMarginLane.Left, persistLane: true }, - description: localize('testing.hitCount', 'Hit count: {0}', detail.count), - glyphMarginClassName: `coverage-deco-gutter ${cls}`, - className: `coverage-deco-inline ${cls}`, - })); + const detailModel = new CoverageDetailsModel(details); - if (detail.branches) { - for (const branch of detail.branches) { - const location = branch.location || range.getEndPosition(); - const branchRange = location instanceof Range ? location : Range.fromPositions(location); - decorations.push(e.addDecoration(branchRange, { - showIfCollapsed: false, - glyphMargin: { position: GlyphMarginLane.Left, persistLane: true }, - description: localize('testing.hitCount', 'Hit count: {0}', detail.count), - glyphMarginClassName: `coverage-deco-gutter ${cls}`, - className: `coverage-deco-inline ${cls}`, - })); + model.changeDecorations(e => { + for (const { metadata: detail, range } of detailModel.ranges) { + if (detail.type === DetailType.Branch) { + const hits = detail.detail.branches![detail.branch].count; + const cls = hits ? CLASS_HIT : CLASS_MISS; + const opts: IModelDecorationOptions = { + showIfCollapsed: false, + description: 'coverage-gutter', + glyphMargin: { position: GlyphMarginLane.Left, persistLane: true }, + glyphMarginHoverMessage: new MarkdownString() + .appendCodeblock(model.getLanguageId(), model.getValueInRange(range)) + .appendText(localize('testing.branchHitCount', 'Branch hit count: {0}', hits)), + glyphMarginClassName: `coverage-deco-gutter ${cls}`, + }; + + this.decorationIds.set(e.addDecoration(range, opts), { + options: opts, + hoverOptions: { + className: `coverage-deco-inline ${cls}`, } - } + }); + } else if (detail.type === DetailType.Statement) { + const cls = detail.count ? CLASS_HIT : CLASS_MISS; + const opts: IModelDecorationOptions = { + showIfCollapsed: false, + description: 'coverage-inline', + glyphMargin: { position: GlyphMarginLane.Left, persistLane: true }, + glyphMarginHoverMessage: new MarkdownString() + .appendCodeblock(model.getLanguageId(), model.getValueInRange(range)) + .appendText(localize('testing.hitCount', 'Hit count: {0}', detail.count)), + glyphMarginClassName: `coverage-deco-gutter ${cls}`, + }; + + this.decorationIds.set(e.addDecoration(range, opts), { + options: opts, + hoverOptions: { + className: `coverage-deco-inline ${cls}`, + } + }); } } }); this.displayedStore.add(toDisposable(() => { model.changeDecorations(e => { - for (const decoration of decorations) { + for (const decoration of this.decorationIds.keys()) { e.removeDecoration(decoration); } + this.decorationIds.clear(); }); })); } @@ -103,6 +192,7 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri this.loadingCancellation?.cancel(); this.loadingCancellation = undefined; this.displayedStore.clear(); + this.hoveredStore.clear(); } private async loadDetails(coverage: FileCoverage) { @@ -121,3 +211,86 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri return undefined; } } + +type CoverageDetailsWithBranch = CoverageDetails | { type: DetailType.Branch; branch: number; detail: IStatementCoverage }; +type DetailRange = { range: Range; metadata: CoverageDetailsWithBranch }; + +export class CoverageDetailsModel { + public ranges: DetailRange[] = []; + + constructor(public readonly details: CoverageDetails[]) { + + //#region decoration generation + // Coverage from a provider can have a range that contains smaller ranges, + // such as a function declarationt that has nested statements. In this we + // make sequential, non-overlapping ranges for each detail for display in + // the editor without ugly overlaps. + const detailRanges: DetailRange[] = details.map(d => ({ range: tidyLocation(d.location), metadata: d })); + + for (const { range, metadata: detail } of detailRanges) { + if (detail.type === DetailType.Statement && detail.branches) { + for (let i = 0; i < detail.branches.length; i++) { + detailRanges.push({ + range: tidyLocation(detail.branches[i].location || Range.fromPositions(range.getEndPosition())), + metadata: { type: DetailType.Branch, branch: i, detail }, + }); + } + } + } + + detailRanges.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); + + const stack: DetailRange[] = []; + const result: DetailRange[] = this.ranges = []; + const pop = () => { + const next = stack.pop()!; + const prev = stack[stack.length - 1]; + if (prev) { + prev.range = prev.range.setStartPosition(next.range.endLineNumber, next.range.endColumn); + } + + result.push(next); + }; + + for (const item of detailRanges) { + // 1. Ensure that any ranges in the stack that ended before this are flushed + const start = item.range.getStartPosition(); + while (stack[stack.length - 1]?.range.containsPosition(start) === false) { + pop(); + } + + // Empty ranges (usually representing missing branches) can be added + // without worry about overlay. + if (item.range.isEmpty()) { + result.push(item); + continue; + } + + // 2. Take the last (overlapping) item in the stack, push range before + // the `item.range` into the result and modify its stack to push the start + // until after the `item.range` ends. + const prev = stack[stack.length - 1]; + if (prev) { + const si = prev.range.setEndPosition(start.lineNumber, start.column); + prev.range = prev.range.setStartPosition(item.range.endLineNumber, item.range.endColumn); + result.push({ range: si, metadata: prev.metadata }); + } + + stack.push(item); + } + while (stack.length) { + pop(); + } + //#endregion + } +} + +// 'tidies' the range by normalizing it into a range and removing leading +// and trailing whitespace. +function tidyLocation(location: Range | Position): Range { + if (location instanceof Position) { + return Range.fromPositions(location); + } + + return location; +} diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index a599b48c632..6168c064211 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -447,6 +447,10 @@ background-color: transparent; } +.coverage-deco-inline { + outline-offset: -1px; +} + .coverage-deco-inline.coverage-deco-hit { background: var(--vscode-testing-coveredBackground); } diff --git a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts index c1f5a100d3d..5d18aac55d9 100644 --- a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts +++ b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts @@ -19,9 +19,12 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IViewContainersRegistry, IViewsRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation } from 'vs/workbench/common/views'; -import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { ExplorerExtensions, IExplorerFileContributionRegistry } from 'vs/workbench/contrib/files/browser/explorerFileContrib'; import { REVEAL_IN_EXPLORER_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileConstants'; +import { CodeCoverageDecorations } from 'vs/workbench/contrib/testing/browser/codeCoverageDecorations'; import { testingResultsIcon, testingViewIcon } from 'vs/workbench/contrib/testing/browser/icons'; +import { ExplorerTestCoverageBars } from 'vs/workbench/contrib/testing/browser/testCoverageBars'; +import { TestCoverageView } from 'vs/workbench/contrib/testing/browser/testCoverageView'; import { TestingDecorationService, TestingDecorations } from 'vs/workbench/contrib/testing/browser/testingDecorations'; import { TestingExplorerView } from 'vs/workbench/contrib/testing/browser/testingExplorerView'; import { CloseTestPeek, GoToNextMessageAction, GoToPreviousMessageAction, OpenMessageInEditorAction, TestResultsView, TestingOutputPeekController, TestingPeekOpener, ToggleTestingPeekHistory } from 'vs/workbench/contrib/testing/browser/testingOutputPeek'; @@ -29,6 +32,7 @@ import { TestingProgressTrigger } from 'vs/workbench/contrib/testing/browser/tes import { TestingViewPaneContainer } from 'vs/workbench/contrib/testing/browser/testingViewPaneContainer'; import { testingConfiguration } from 'vs/workbench/contrib/testing/common/configuration'; import { TestCommandId, Testing } from 'vs/workbench/contrib/testing/common/constants'; +import { ITestCoverageService, TestCoverageService } from 'vs/workbench/contrib/testing/common/testCoverageService'; import { ITestExplorerFilterState, TestExplorerFilterState } from 'vs/workbench/contrib/testing/common/testExplorerFilterState'; import { TestId, TestPosition } from 'vs/workbench/contrib/testing/common/testId'; import { ITestProfileService, TestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService'; @@ -43,13 +47,9 @@ import { ITestingContinuousRunService, TestingContinuousRunService } from 'vs/wo import { ITestingDecorationsService } from 'vs/workbench/contrib/testing/common/testingDecorations'; import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { allTestActions, discoverAndRunTests } from './testExplorerActions'; import './testingConfigurationUi'; -import { ITestCoverageService, TestCoverageService } from 'vs/workbench/contrib/testing/common/testCoverageService'; -import { TestCoverageView } from 'vs/workbench/contrib/testing/browser/testCoverageView'; -import { ExplorerExtensions, IExplorerFileContributionRegistry } from 'vs/workbench/contrib/files/browser/explorerFileContrib'; -import { ExplorerTestCoverageBars } from 'vs/workbench/contrib/testing/browser/testCoverageBars'; -// import { CodeCoverageDecorations } from 'vs/workbench/contrib/testing/browser/codeCoverageDecorations'; registerSingleton(ITestService, TestService, InstantiationType.Delayed); registerSingleton(ITestResultStorage, TestResultStorage, InstantiationType.Delayed); @@ -145,7 +145,7 @@ Registry.as(WorkbenchExtensions.Workbench).regi registerEditorContribution(Testing.OutputPeekContributionId, TestingOutputPeekController, EditorContributionInstantiation.AfterFirstRender); registerEditorContribution(Testing.DecorationsContributionId, TestingDecorations, EditorContributionInstantiation.AfterFirstRender); -// registerEditorContribution(Testing.CoverageDecorationsContributionId, CodeCoverageDecorations, EditorContributionInstantiation.Eventually); +registerEditorContribution(Testing.CoverageDecorationsContributionId, CodeCoverageDecorations, EditorContributionInstantiation.Eventually); CommandsRegistry.registerCommand({ id: '_revealTestInExplorer', diff --git a/src/vs/workbench/contrib/testing/browser/theme.ts b/src/vs/workbench/contrib/testing/browser/theme.ts index 440bff5b8be..e73428ef908 100644 --- a/src/vs/workbench/contrib/testing/browser/theme.ts +++ b/src/vs/workbench/contrib/testing/browser/theme.ts @@ -6,6 +6,7 @@ import { Color, RGBA } from 'vs/base/common/color'; import { localize } from 'vs/nls'; import { contrastBorder, diffInserted, diffRemoved, editorErrorForeground, editorForeground, editorInfoForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { TestMessageType, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes'; export const testingColorIconFailed = registerColor('testing.iconFailed', { @@ -153,3 +154,15 @@ export const testStatesToIconColors: { [K in TestResultState]?: string } = { [TestResultState.Unset]: testingColorIconUnset, [TestResultState.Skipped]: testingColorIconSkipped, }; + +registerThemingParticipant((theme, collector) => { + + collector.addRule(` + .coverage-deco-inline.coverage-deco-hit { + outline: 1px solid ${theme.getColor(testingCoveredBackground)?.transparent(0.75)}; + } + .coverage-deco-inline.coverage-deco-miss { + outline: 1px solid ${theme.getColor(testingUncoveredBackground)?.transparent(0.75)}; + } + `); +}); diff --git a/src/vs/workbench/contrib/testing/common/testTypes.ts b/src/vs/workbench/contrib/testing/common/testTypes.ts index e85bdc9aa2e..11ea5b4d9a9 100644 --- a/src/vs/workbench/contrib/testing/common/testTypes.ts +++ b/src/vs/workbench/contrib/testing/common/testTypes.ts @@ -598,6 +598,7 @@ export const KEEP_N_LAST_COVERAGE_REPORTS = 3; export const enum DetailType { Function, Statement, + Branch, } export type CoverageDetails = IFunctionCoverage | IStatementCoverage; diff --git a/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_1.0.snap b/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_1.0.snap new file mode 100644 index 00000000000..711f0048dad --- /dev/null +++ b/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_1.0.snap @@ -0,0 +1,30 @@ +[ + { + range: "[1,0 -> 2,0]", + count: 1 + }, + { + range: "[2,0 -> 3,0]", + count: 2 + }, + { + range: "[3,0 -> 4,0]", + count: 3 + }, + { + range: "[4,0 -> 6,0]", + count: 4 + }, + { + range: "[6,0 -> 7,0]", + count: 3 + }, + { + range: "[7,0 -> 7,0]", + count: 2 + }, + { + range: "[5,0 -> 7,0]", + count: 1 + } +] \ No newline at end of file diff --git a/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_2.0.snap b/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_2.0.snap new file mode 100644 index 00000000000..b5f83de23d8 --- /dev/null +++ b/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_2.0.snap @@ -0,0 +1,22 @@ +[ + { + range: "[1,0 -> 2,0]", + count: 1 + }, + { + range: "[2,0 -> 3,0]", + count: 2 + }, + { + range: "[3,0 -> 3,5]", + count: 3 + }, + { + range: "[3,5 -> 4,0]", + count: 2 + }, + { + range: "[4,0 -> 5,0]", + count: 1 + } +] \ No newline at end of file diff --git a/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_3.0.snap b/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_3.0.snap new file mode 100644 index 00000000000..9cfb71a8f91 --- /dev/null +++ b/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_3.0.snap @@ -0,0 +1,22 @@ +[ + { + range: "[1,0 -> 2,0]", + count: 1 + }, + { + range: "[2,0 -> 3,0]", + count: 2 + }, + { + range: "[3,0 -> 4,0]", + count: 1 + }, + { + range: "[4,0 -> 5,0]", + count: 3 + }, + { + range: "[5,0 -> 5,0]", + count: 1 + } +] \ No newline at end of file diff --git a/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_4.0.snap b/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_4.0.snap new file mode 100644 index 00000000000..a11f50d03c1 --- /dev/null +++ b/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_4.0.snap @@ -0,0 +1,22 @@ +[ + { + range: "[2,0 -> 2,0]", + count: 2 + }, + { + range: "[1,0 -> 4,0]", + count: 1 + }, + { + range: "[4,3 -> 4,3]", + count: 4 + }, + { + range: "[4,0 -> 5,0]", + count: 3 + }, + { + range: "[5,0 -> 5,0]", + count: 1 + } +] \ No newline at end of file diff --git a/src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts b/src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts new file mode 100644 index 00000000000..10c2f7bb901 --- /dev/null +++ b/src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +import { assertSnapshot } from 'vs/base/test/common/snapshot'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { CoverageDetailsModel } from 'vs/workbench/contrib/testing/browser/codeCoverageDecorations'; +import { CoverageDetails, DetailType } from 'vs/workbench/contrib/testing/common/testTypes'; + +suite('Code Coverage Decorations', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('CoverageDetailsModel#1', async () => { + // Create some sample coverage details + const details: CoverageDetails[] = [ + { location: new Range(1, 0, 5, 0), type: DetailType.Statement, count: 1 }, + { location: new Range(2, 0, 3, 0), type: DetailType.Statement, count: 2 }, + { location: new Range(4, 0, 6, 0), type: DetailType.Statement, branches: [{ location: new Range(3, 0, 7, 0), count: 3 }], count: 4 }, + ]; + + // Create a new CoverageDetailsModel instance + const model = new CoverageDetailsModel(details); + + // Verify that the ranges are generated correctly + await assertSnapshot(model.ranges.map(r => ({ + range: r.range.toString(), + count: r.metadata.type === DetailType.Branch ? r.metadata.detail.branches![r.metadata.branch].count : r.metadata.count, + }))); + }); + + test('CoverageDetailsModel#2', async () => { + // Create some sample coverage details + const details: CoverageDetails[] = [ + { location: new Range(1, 0, 5, 0), type: DetailType.Statement, count: 1 }, + { location: new Range(2, 0, 4, 0), type: DetailType.Statement, count: 2 }, + { location: new Range(3, 0, 3, 5), type: DetailType.Statement, count: 3 }, + ]; + + // Create a new CoverageDetailsModel instance + const model = new CoverageDetailsModel(details); + + // Verify that the ranges are generated correctly + await assertSnapshot(model.ranges.map(r => ({ + range: r.range.toString(), + count: r.metadata.type === DetailType.Branch ? r.metadata.detail.branches![r.metadata.branch].count : r.metadata.count, + }))); + }); + + test('CoverageDetailsModel#3', async () => { + // Create some sample coverage details + const details: CoverageDetails[] = [ + { location: new Range(1, 0, 5, 0), type: DetailType.Statement, count: 1 }, + { location: new Range(2, 0, 3, 0), type: DetailType.Statement, count: 2 }, + { location: new Range(4, 0, 5, 0), type: DetailType.Statement, count: 3 }, + ]; + + // Create a new CoverageDetailsModel instance + const model = new CoverageDetailsModel(details); + + // Verify that the ranges are generated correctly + await assertSnapshot(model.ranges.map(r => ({ + range: r.range.toString(), + count: r.metadata.type === DetailType.Branch ? r.metadata.detail.branches![r.metadata.branch].count : r.metadata.count, + }))); + }); + + test('CoverageDetailsModel#4', async () => { + // Create some sample coverage details + const details: CoverageDetails[] = [ + { location: new Range(1, 0, 5, 0), type: DetailType.Statement, count: 1 }, + { location: new Position(2, 0), type: DetailType.Statement, count: 2 }, + { location: new Range(4, 0, 5, 0), type: DetailType.Statement, count: 3 }, + { location: new Position(4, 3), type: DetailType.Statement, count: 4 }, + ]; + + // Create a new CoverageDetailsModel instance + const model = new CoverageDetailsModel(details); + + // Verify that the ranges are generated correctly + await assertSnapshot(model.ranges.map(r => ({ + range: r.range.toString(), + count: r.metadata.type === DetailType.Branch ? r.metadata.detail.branches![r.metadata.branch].count : r.metadata.count, + }))); + }); +}); From e34dc5f8a44495a110f1aad7b83fefe090b7cc79 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 11 Jan 2024 09:37:19 +0100 Subject: [PATCH 0223/1897] Queue and ResourceQueue issues (fix #202136) (#202190) --- src/vs/base/common/async.ts | 17 +++++++---- src/vs/base/node/pfs.ts | 4 +-- src/vs/base/test/common/async.test.ts | 30 ++++++++----------- src/vs/platform/files/common/fileService.ts | 24 +++++++-------- .../api/common/extHostFileSystemConsumer.ts | 2 +- .../common/textFileEditorModelManager.ts | 6 ++-- .../common/storedFileWorkingCopyManager.ts | 6 ++-- .../common/workingCopyBackupService.ts | 8 ++--- 8 files changed, 47 insertions(+), 50 deletions(-) diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 54fb9b0bd47..8be1eb8a7c4 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -715,10 +715,9 @@ export class Limiter implements ILimiter { } dispose(): void { - // SEE https://github.com/microsoft/vscode/issues/202136 - // this._isDisposed = true; - // this.outstandingPromises.length = 0; // stop further processing - // this._size = 0; + this._isDisposed = true; + this.outstandingPromises.length = 0; // stop further processing + this._size = 0; this._onDrained.dispose(); } } @@ -792,7 +791,13 @@ export class ResourceQueue implements IDisposable { return true; } - queueFor(resource: URI, extUri: IExtUri = defaultExtUri): ILimiter { + queueSize(resource: URI, extUri: IExtUri = defaultExtUri): number { + const key = extUri.getComparisonKey(resource); + + return this.queues.get(key)?.size ?? 0; + } + + queueFor(resource: URI, factory: ITask>, extUri: IExtUri = defaultExtUri): Promise { const key = extUri.getComparisonKey(resource); let queue = this.queues.get(key); @@ -820,7 +825,7 @@ export class ResourceQueue implements IDisposable { this.queues.set(key, queue); } - return queue; + return queue.queue(factory); } private onDidQueueDrain(): void { diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index e41bcab40bb..025df98b423 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -380,11 +380,11 @@ function writeFile(path: string, data: Buffer, options?: IWriteFileOptions): Pro function writeFile(path: string, data: Uint8Array, options?: IWriteFileOptions): Promise; function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise; function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise { - return writeQueues.queueFor(URI.file(path), extUriBiasedIgnorePathCase).queue(() => { + return writeQueues.queueFor(URI.file(path), () => { const ensuredOptions = ensureWriteOptions(options); return new Promise((resolve, reject) => doWriteFileAndFlush(path, data, ensuredOptions, error => error ? reject(error) : resolve())); - }); + }, extUriBiasedIgnorePathCase); } interface IWriteFileOptions { diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index ebec06f453d..4c2e74ce39b 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -510,8 +510,7 @@ suite('Async', () => { }); }); - // skipped because of https://github.com/microsoft/vscode/issues/202136 - test.skip('stop processing on dispose', async function () { + test('stop processing on dispose', async function () { const queue = new async.Queue(); let workCounter = 0; @@ -762,21 +761,19 @@ suite('Async', () => { await queue.whenDrained(); // returns immediately since empty - const r1Queue = queue.queueFor(URI.file('/some/path')); + let done1 = false; + queue.queueFor(URI.file('/some/path'), async () => { done1 = true; }); + await queue.whenDrained(); // returns immediately since no work scheduled + assert.strictEqual(done1, true); - await queue.whenDrained(); // returns immediately since empty - - const r2Queue = queue.queueFor(URI.file('/some/other/path')); - - await queue.whenDrained(); // returns immediately since empty - - assert.ok(r1Queue); - assert.ok(r2Queue); - assert.strictEqual(r1Queue, queue.queueFor(URI.file('/some/path'))); // same queue returned + let done2 = false; + queue.queueFor(URI.file('/some/other/path'), async () => { done2 = true; }); + await queue.whenDrained(); // returns immediately since no work scheduled + assert.strictEqual(done2, true); // schedule some work const w1 = new async.DeferredPromise(); - r1Queue.queue(() => w1.p); + queue.queueFor(URI.file('/some/path'), () => w1.p); let drained = false; queue.whenDrained().then(() => drained = true); @@ -785,14 +782,11 @@ suite('Async', () => { await async.timeout(0); assert.strictEqual(drained, true); - const r1Queue2 = queue.queueFor(URI.file('/some/path')); - assert.notStrictEqual(r1Queue, r1Queue2); // previous one got disposed after finishing - // schedule some work const w2 = new async.DeferredPromise(); const w3 = new async.DeferredPromise(); - r1Queue.queue(() => w2.p); - r2Queue.queue(() => w3.p); + queue.queueFor(URI.file('/some/path'), () => w2.p); + queue.queueFor(URI.file('/some/other/path'), () => w3.p); drained = false; queue.whenDrained().then(() => drained = true); diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index 791185e9a88..b353968540e 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -505,14 +505,14 @@ export class FileService extends Disposable implements IFileService { private async doReadFileAtomic(provider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability | IFileSystemProviderWithFileReadStreamCapability, resource: URI, options?: IReadFileOptions, token?: CancellationToken): Promise { return new Promise((resolve, reject) => { - this.writeQueue.queueFor(resource, this.getExtUri(provider).providerExtUri).queue(async () => { + this.writeQueue.queueFor(resource, async () => { try { const content = await this.doReadFile(provider, resource, options, token); resolve(content); } catch (error) { reject(error); } - }); + }, this.getExtUri(provider).providerExtUri); }); } @@ -1087,17 +1087,15 @@ export class FileService extends Disposable implements IFileService { // create parent folders await this.mkdirp(targetProvider, this.getExtUri(targetProvider).providerExtUri.dirname(target)); - // queue on the source to ensure atomic read - const sourceWriteQueue = this.writeQueue.queueFor(source, this.getExtUri(sourceProvider).providerExtUri); - // leverage `copy` method if provided and providers are identical + // queue on the source to ensure atomic read if (sourceProvider === targetProvider && hasFileFolderCopyCapability(sourceProvider)) { - return sourceWriteQueue.queue(() => sourceProvider.copy(source, target, { overwrite: true })); + return this.writeQueue.queueFor(source, () => sourceProvider.copy(source, target, { overwrite: true }), this.getExtUri(sourceProvider).providerExtUri); } // otherwise copy via buffer/unbuffered and use a write queue // on the source to ensure atomic operation as much as possible - return sourceWriteQueue.queue(() => this.doCopyFile(sourceProvider, source, targetProvider, target)); + return this.writeQueue.queueFor(source, () => this.doCopyFile(sourceProvider, source, targetProvider, target), this.getExtUri(sourceProvider).providerExtUri); } //#endregion @@ -1223,7 +1221,7 @@ export class FileService extends Disposable implements IFileService { private readonly writeQueue = this._register(new ResourceQueue()); private async doWriteBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, options: IWriteFileOptions | undefined, readableOrStreamOrBufferedStream: VSBufferReadable | VSBufferReadableStream | VSBufferReadableBufferedStream): Promise { - return this.writeQueue.queueFor(resource, this.getExtUri(provider).providerExtUri).queue(async () => { + return this.writeQueue.queueFor(resource, async () => { // open handle const handle = await provider.open(resource, { create: true, unlock: options?.unlock ?? false }); @@ -1242,7 +1240,7 @@ export class FileService extends Disposable implements IFileService { // close handle always await provider.close(handle); } - }); + }, this.getExtUri(provider).providerExtUri); } private async doWriteStreamBufferedQueued(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, handle: number, streamOrBufferedStream: VSBufferReadableStream | VSBufferReadableBufferedStream): Promise { @@ -1321,7 +1319,7 @@ export class FileService extends Disposable implements IFileService { } private async doWriteUnbuffered(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, options: IWriteFileOptions | undefined, bufferOrReadableOrStreamOrBufferedStream: VSBuffer | VSBufferReadable | VSBufferReadableStream | VSBufferReadableBufferedStream): Promise { - return this.writeQueue.queueFor(resource, this.getExtUri(provider).providerExtUri).queue(() => this.doWriteUnbufferedQueued(provider, resource, options, bufferOrReadableOrStreamOrBufferedStream)); + return this.writeQueue.queueFor(resource, () => this.doWriteUnbufferedQueued(provider, resource, options, bufferOrReadableOrStreamOrBufferedStream), this.getExtUri(provider).providerExtUri); } private async doWriteUnbufferedQueued(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, options: IWriteFileOptions | undefined, bufferOrReadableOrStreamOrBufferedStream: VSBuffer | VSBufferReadable | VSBufferReadableStream | VSBufferReadableBufferedStream): Promise { @@ -1341,7 +1339,7 @@ export class FileService extends Disposable implements IFileService { } private async doPipeBuffered(sourceProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI): Promise { - return this.writeQueue.queueFor(target, this.getExtUri(targetProvider).providerExtUri).queue(() => this.doPipeBufferedQueued(sourceProvider, source, targetProvider, target)); + return this.writeQueue.queueFor(target, () => this.doPipeBufferedQueued(sourceProvider, source, targetProvider, target), this.getExtUri(targetProvider).providerExtUri); } private async doPipeBufferedQueued(sourceProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI): Promise { @@ -1387,7 +1385,7 @@ export class FileService extends Disposable implements IFileService { } private async doPipeUnbuffered(sourceProvider: IFileSystemProviderWithFileReadWriteCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability, target: URI): Promise { - return this.writeQueue.queueFor(target, this.getExtUri(targetProvider).providerExtUri).queue(() => this.doPipeUnbufferedQueued(sourceProvider, source, targetProvider, target)); + return this.writeQueue.queueFor(target, () => this.doPipeUnbufferedQueued(sourceProvider, source, targetProvider, target), this.getExtUri(targetProvider).providerExtUri); } private async doPipeUnbufferedQueued(sourceProvider: IFileSystemProviderWithFileReadWriteCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability, target: URI): Promise { @@ -1395,7 +1393,7 @@ export class FileService extends Disposable implements IFileService { } private async doPipeUnbufferedToBuffered(sourceProvider: IFileSystemProviderWithFileReadWriteCapability, source: URI, targetProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI): Promise { - return this.writeQueue.queueFor(target, this.getExtUri(targetProvider).providerExtUri).queue(() => this.doPipeUnbufferedToBufferedQueued(sourceProvider, source, targetProvider, target)); + return this.writeQueue.queueFor(target, () => this.doPipeUnbufferedToBufferedQueued(sourceProvider, source, targetProvider, target), this.getExtUri(targetProvider).providerExtUri); } private async doPipeUnbufferedToBufferedQueued(sourceProvider: IFileSystemProviderWithFileReadWriteCapability, source: URI, targetProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI): Promise { diff --git a/src/vs/workbench/api/common/extHostFileSystemConsumer.ts b/src/vs/workbench/api/common/extHostFileSystemConsumer.ts index 264a37f5856..fb325813d25 100644 --- a/src/vs/workbench/api/common/extHostFileSystemConsumer.ts +++ b/src/vs/workbench/api/common/extHostFileSystemConsumer.ts @@ -110,7 +110,7 @@ export class ExtHostConsumerFileSystem { // use shortcut await that._proxy.$ensureActivation(uri.scheme); await that.mkdirp(provider.impl, provider.extUri, provider.extUri.dirname(uri)); - return await that._writeQueue.queueFor(uri).queue(() => Promise.resolve(provider.impl.writeFile(uri, content, { create: true, overwrite: true }))); + return await that._writeQueue.queueFor(uri, () => Promise.resolve(provider.impl.writeFile(uri, content, { create: true, overwrite: true }))); } else { return await that._proxy.$writeFile(uri, VSBuffer.wrap(content)); } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 062308a9a65..7328c060936 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -159,9 +159,9 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // Resolve model to update (use a queue to prevent accumulation of resolves // when the resolve actually takes long. At most we only want the queue // to have a size of 2 (1 running resolve and 1 queued resolve). - const queue = this.modelResolveQueue.queueFor(model.resource); - if (queue.size <= 1) { - queue.queue(async () => { + const queueSize = this.modelResolveQueue.queueSize(model.resource); + if (queueSize <= 1) { + this.modelResolveQueue.queueFor(model.resource, async () => { try { await this.reload(model); } catch (error) { diff --git a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopyManager.ts b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopyManager.ts index f76fdd404c7..9798a9ad662 100644 --- a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopyManager.ts +++ b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopyManager.ts @@ -293,9 +293,9 @@ export class StoredFileWorkingCopyManager // Resolves a working copy to update (use a queue to prevent accumulation of // resolve when the resolving actually takes long. At most we only want the // queue to have a size of 2 (1 running resolve and 1 queued resolve). - const queue = this.workingCopyResolveQueue.queueFor(workingCopy.resource); - if (queue.size <= 1) { - queue.queue(async () => { + const queueSize = this.workingCopyResolveQueue.queueSize(workingCopy.resource); + if (queueSize <= 1) { + this.workingCopyResolveQueue.queueFor(workingCopy.resource, async () => { try { await this.reload(workingCopy); } catch (error) { diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts index 16715e8b21d..c360206fd3a 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts @@ -258,7 +258,7 @@ class WorkingCopyBackupServiceImpl extends Disposable implements IWorkingCopyBac return; } - return this.ioOperationQueues.queueFor(backupResource).queue(async () => { + return this.ioOperationQueues.queueFor(backupResource, async () => { if (token?.isCancellationRequested) { return; } @@ -344,7 +344,7 @@ class WorkingCopyBackupServiceImpl extends Disposable implements IWorkingCopyBac return; } - return this.ioOperationQueues.queueFor(backupResource).queue(async () => { + return this.ioOperationQueues.queueFor(backupResource, async () => { if (token?.isCancellationRequested) { return; } @@ -386,7 +386,7 @@ class WorkingCopyBackupServiceImpl extends Disposable implements IWorkingCopyBac private async resolveIdentifier(backupResource: URI, model: WorkingCopyBackupsModel): Promise { let res: IWorkingCopyIdentifier | undefined = undefined; - await this.ioOperationQueues.queueFor(backupResource).queue(async () => { + await this.ioOperationQueues.queueFor(backupResource, async () => { if (!model.has(backupResource)) { return; // require backup to be present } @@ -450,7 +450,7 @@ class WorkingCopyBackupServiceImpl extends Disposable implements IWorkingCopyBac let res: IResolvedWorkingCopyBackup | undefined = undefined; - await this.ioOperationQueues.queueFor(backupResource).queue(async () => { + await this.ioOperationQueues.queueFor(backupResource, async () => { if (!model.has(backupResource)) { return; // require backup to be present } From 74f8c5cd89dd4b5b0df5463ef4dfe7e1395dfcca Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 11 Jan 2024 10:16:00 +0100 Subject: [PATCH 0224/1897] Make the watermark editor labels themable (#202228) --- .../lib/stylelint/vscode-known-variables.json | 1 + .../parts/editor/editorGroupWatermark.ts | 3 +++ .../parts/editor/media/editorgroupview.css | 22 +------------------ 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index c15c90a21d9..e537aed9b1c 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -296,6 +296,7 @@ "--vscode-editorWarning-background", "--vscode-editorWarning-border", "--vscode-editorWarning-foreground", + "--vscode-editorWatermark-foreground", "--vscode-editorWhitespace-foreground", "--vscode-editorWidget-background", "--vscode-editorWidget-border", diff --git a/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts b/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts index 3c4ac7d594b..f2f8c9704f0 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts @@ -14,6 +14,9 @@ import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLa import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { defaultKeybindingLabelStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { editorForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; + +registerColor('editorWatermark.foreground', { dark: transparent(editorForeground, 0.6), light: transparent(editorForeground, 0.68), hcDark: editorForeground, hcLight: editorForeground }, localize('editorLineHighlight', 'Foreground color for the labels in the editor watermark.')); interface WatermarkEntry { readonly text: string; diff --git a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css index a2d1841fa74..76c8a561b4e 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css +++ b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css @@ -87,6 +87,7 @@ display: table-row; opacity: .8; cursor: default; + color: var(--vscode-editorWatermark-foreground); } .monaco-workbench .part.editor > .content .editor-group-container > .editor-group-watermark > .shortcuts dt { @@ -104,27 +105,6 @@ vertical-align: middle; } -.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-watermark > .shortcuts dt, -.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-watermark > .shortcuts dl { - color: rgba(0,0,0,.68); -} - -.monaco-workbench.vs-dark .part.editor > .content .editor-group-container .editor-group-watermark > .shortcuts dt, -.monaco-workbench.vs-dark .part.editor > .content .editor-group-container .editor-group-watermark > .shortcuts dl { - color: rgba(255,255,255,.6); -} - -.monaco-workbench.hc-black .part.editor > .content .editor-group-container .editor-group-watermark > .shortcuts dt, -.monaco-workbench.hc-light .part.editor > .content .editor-group-container .editor-group-watermark > .shortcuts dt { - color: var(--vscode-editor-foreground); -} -.monaco-workbench.hc-black .part.editor > .content .editor-group-container .editor-group-watermark > .shortcuts dl, -.monaco-workbench.hc-light .part.editor > .content .editor-group-container .editor-group-watermark > .shortcuts dl { - color: var(--vscode-editor-foreground); - opacity: 1; -} - - /* Title */ .monaco-workbench .part.editor > .content .editor-group-container > .title { From c30c5b3e55cacd767806358ddc0ba33afeaa3a01 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 11 Jan 2024 11:13:26 +0100 Subject: [PATCH 0225/1897] Adopt ensureNoDisposablesAreLeakedInTestSuite round 2 (#202230) --- .eslintrc.json | 7 ------- src/vs/base/test/browser/ui/menu/menubar.test.ts | 2 ++ .../contrib/folding/test/browser/foldingModel.test.ts | 2 ++ .../contrib/folding/test/browser/foldingRanges.test.ts | 3 ++- .../contrib/folding/test/browser/hiddenRangeModel.test.ts | 4 ++-- .../editor/contrib/folding/test/browser/indentFold.test.ts | 3 +++ .../folding/test/browser/indentRangeProvider.test.ts | 3 +++ .../editor/contrib/folding/test/browser/syntaxFold.test.ts | 3 ++- src/vs/server/test/node/serverConnectionToken.test.ts | 2 ++ .../services/themes/test/node/tokenStyleResolving.test.ts | 3 +++ 10 files changed, 21 insertions(+), 11 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index f325a998034..fb829ab320d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -153,16 +153,11 @@ "src/vs/base/test/browser/comparers.test.ts", "src/vs/base/test/browser/hash.test.ts", "src/vs/base/test/browser/indexedDB.test.ts", - "src/vs/base/test/browser/ui/menu/menubar.test.ts", "src/vs/base/test/browser/ui/scrollbar/scrollableElement.test.ts", "src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts", "src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts", "src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts", "src/vs/editor/contrib/dropOrPasteInto/test/browser/editSort.test.ts", - "src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts", - "src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts", - "src/vs/editor/contrib/folding/test/browser/indentFold.test.ts", - "src/vs/editor/contrib/folding/test/browser/indentRangeProvider.test.ts", "src/vs/editor/contrib/gotoSymbol/test/browser/referencesModel.test.ts", "src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts", "src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts", @@ -193,7 +188,6 @@ "src/vs/platform/telemetry/test/browser/1dsAppender.test.ts", "src/vs/platform/workspace/test/common/workspace.test.ts", "src/vs/platform/workspaces/test/electron-main/workspaces.test.ts", - "src/vs/server/test/node/serverConnectionToken.test.ts", "src/vs/workbench/api/test/browser/extHostApiCommands.test.ts", "src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts", "src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts", @@ -223,7 +217,6 @@ "src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts", "src/vs/workbench/services/keybinding/test/node/windowsKeyboardMapper.test.ts", "src/vs/workbench/services/telemetry/test/browser/commonProperties.test.ts", - "src/vs/workbench/services/themes/test/node/tokenStyleResolving.test.ts", "src/vs/workbench/services/userActivity/test/browser/domActivityTracker.test.ts", "src/vs/workbench/test/browser/quickAccess.test.ts" ] diff --git a/src/vs/base/test/browser/ui/menu/menubar.test.ts b/src/vs/base/test/browser/ui/menu/menubar.test.ts index 4a3c939c64b..97eaaa49373 100644 --- a/src/vs/base/test/browser/ui/menu/menubar.test.ts +++ b/src/vs/base/test/browser/ui/menu/menubar.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { $ } from 'vs/base/browser/dom'; import { unthemedMenuStyles } from 'vs/base/browser/ui/menu/menu'; import { MenuBar } from 'vs/base/browser/ui/menu/menubar'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; function getButtonElementByAriaLabel(menubarElement: HTMLElement, ariaLabel: string): HTMLElement | null { let i; @@ -61,6 +62,7 @@ function validateMenuBarItem(menubar: MenuBar, menubarContainer: HTMLElement, la } suite('Menubar', () => { + ensureNoDisposablesAreLeakedInTestSuite(); const container = $('.container'); const menubar = new MenuBar(container, { diff --git a/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts b/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts index f663759531d..3cef0c2a56e 100644 --- a/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts +++ b/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -86,6 +87,7 @@ export class TestDecorationProvider { } suite('Folding Model', () => { + ensureNoDisposablesAreLeakedInTestSuite(); function r(startLineNumber: number, endLineNumber: number, isCollapsed: boolean = false): ExpectedRegion { return { startLineNumber, endLineNumber, isCollapsed }; } diff --git a/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts b/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts index 2a063882078..912cf515d1a 100644 --- a/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts +++ b/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { FoldingMarkers } from 'vs/editor/common/languages/languageConfiguration'; import { MAX_FOLDING_REGIONS, FoldRange, FoldingRegions, FoldSource } from 'vs/editor/contrib/folding/browser/foldingRanges'; import { RangesCollector, computeRanges } from 'vs/editor/contrib/folding/browser/indentRangeProvider'; @@ -15,7 +16,7 @@ const markers: FoldingMarkers = { }; suite('FoldingRanges', () => { - + ensureNoDisposablesAreLeakedInTestSuite(); const foldRange = (from: number, to: number, collapsed: boolean | undefined = undefined, source: FoldSource = FoldSource.provider, type: string | undefined = undefined) => { startLineNumber: from, diff --git a/src/vs/editor/contrib/folding/test/browser/hiddenRangeModel.test.ts b/src/vs/editor/contrib/folding/test/browser/hiddenRangeModel.test.ts index 8a22c84ed88..71281efa963 100644 --- a/src/vs/editor/contrib/folding/test/browser/hiddenRangeModel.test.ts +++ b/src/vs/editor/contrib/folding/test/browser/hiddenRangeModel.test.ts @@ -18,6 +18,8 @@ interface ExpectedRange { } suite('Hidden Range Model', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function r(startLineNumber: number, endLineNumber: number): ExpectedRange { return { startLineNumber, endLineNumber }; } @@ -96,6 +98,4 @@ suite('Hidden Range Model', () => { hiddenRangeModel.dispose(); } }); - - ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/editor/contrib/folding/test/browser/indentFold.test.ts b/src/vs/editor/contrib/folding/test/browser/indentFold.test.ts index 3145d1ab5aa..afee83dff29 100644 --- a/src/vs/editor/contrib/folding/test/browser/indentFold.test.ts +++ b/src/vs/editor/contrib/folding/test/browser/indentFold.test.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { computeRanges } from 'vs/editor/contrib/folding/browser/indentRangeProvider'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; @@ -12,6 +13,8 @@ interface IndentRange { } suite('Indentation Folding', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function r(start: number, end: number): IndentRange { return { start, end }; } diff --git a/src/vs/editor/contrib/folding/test/browser/indentRangeProvider.test.ts b/src/vs/editor/contrib/folding/test/browser/indentRangeProvider.test.ts index f632abf2fbc..0a1c3220e64 100644 --- a/src/vs/editor/contrib/folding/test/browser/indentRangeProvider.test.ts +++ b/src/vs/editor/contrib/folding/test/browser/indentRangeProvider.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { FoldingMarkers } from 'vs/editor/common/languages/languageConfiguration'; import { computeRanges } from 'vs/editor/contrib/folding/browser/indentRangeProvider'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; @@ -31,6 +32,7 @@ function r(startLineNumber: number, endLineNumber: number, parentIndex: number, } suite('Indentation Folding', () => { + ensureNoDisposablesAreLeakedInTestSuite(); test('Fold one level', () => { const range = [ 'A', @@ -151,6 +153,7 @@ const markers: FoldingMarkers = { }; suite('Folding with regions', () => { + ensureNoDisposablesAreLeakedInTestSuite(); test('Inside region, indented', () => { assertRanges([ /* 1*/ 'class A {', diff --git a/src/vs/editor/contrib/folding/test/browser/syntaxFold.test.ts b/src/vs/editor/contrib/folding/test/browser/syntaxFold.test.ts index 59e4e0723dd..3551f446606 100644 --- a/src/vs/editor/contrib/folding/test/browser/syntaxFold.test.ts +++ b/src/vs/editor/contrib/folding/test/browser/syntaxFold.test.ts @@ -29,6 +29,8 @@ class TestFoldingRangeProvider implements FoldingRangeProvider { } suite('Syntax folding', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function r(start: number, end: number): IndentRange { return { start, end }; } @@ -109,5 +111,4 @@ suite('Syntax folding', () => { model.dispose(); }); - ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/server/test/node/serverConnectionToken.test.ts b/src/vs/server/test/node/serverConnectionToken.test.ts index 0b955bbe206..b624dd6d676 100644 --- a/src/vs/server/test/node/serverConnectionToken.test.ts +++ b/src/vs/server/test/node/serverConnectionToken.test.ts @@ -7,11 +7,13 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { parseServerConnectionToken, ServerConnectionToken, ServerConnectionTokenParseError, ServerConnectionTokenType } from 'vs/server/node/serverConnectionToken'; import { ServerParsedArgs } from 'vs/server/node/serverEnvironmentService'; suite('parseServerConnectionToken', () => { + ensureNoDisposablesAreLeakedInTestSuite(); function isError(r: ServerConnectionToken | ServerConnectionTokenParseError): r is ServerConnectionTokenParseError { return (r instanceof ServerConnectionTokenParseError); diff --git a/src/vs/workbench/services/themes/test/node/tokenStyleResolving.test.ts b/src/vs/workbench/services/themes/test/node/tokenStyleResolving.test.ts index 9d4f7f963b1..98701b3dada 100644 --- a/src/vs/workbench/services/themes/test/node/tokenStyleResolving.test.ts +++ b/src/vs/workbench/services/themes/test/node/tokenStyleResolving.test.ts @@ -20,6 +20,7 @@ import { IRequestService } from 'vs/platform/request/common/request'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; const undefinedStyle = { bold: undefined, underline: undefined, italic: undefined }; const unsetStyle = { bold: false, underline: false, italic: false }; @@ -97,6 +98,8 @@ suite('Themes - TokenStyleResolving', () => { diskFileSystemProvider.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('color defaults', async () => { const themeData = ColorThemeData.createUnloadedTheme('foo'); themeData.location = FileAccess.asFileUri('vs/workbench/services/themes/test/node/color-theme.json'); From ad245888bef0a75b409f17c3d4be08dd25d72d4c Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 11 Jan 2024 11:01:46 +0100 Subject: [PATCH 0226/1897] Sets scmResourceGroup and scmProvider context keys on multi diff editor when opened from scm view --- .../browser/widget/multiDiffEditorWidget/model.ts | 2 ++ .../multiDiffEditorViewModel.ts | 5 +++++ .../multiDiffEditorWidgetImpl.ts | 15 +++++++++++++-- .../browser/multiDiffEditorInput.ts | 11 +++++------ .../browser/multiDiffSourceResolverService.ts | 3 +++ .../browser/scmMultiDiffSourceResolver.ts | 4 ++++ 6 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts index 70e32ff56bb..c6973f50cdf 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts @@ -6,10 +6,12 @@ import { Event } from 'vs/base/common/event'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextModel } from 'vs/editor/common/model'; +import { ContextKeyValue } from 'vs/platform/contextkey/common/contextkey'; export interface IMultiDiffEditorModel { readonly documents: readonly LazyPromise[]; readonly onDidChange: Event; + readonly contextKeys?: Record; } export interface LazyPromise { diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts index f459fb6db98..be3dcec1286 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts @@ -11,6 +11,7 @@ import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditor/diffEdi import { IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IDiffEditorViewModel } from 'vs/editor/common/editorCommon'; +import { ContextKeyValue } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class MultiDiffEditorViewModel extends Disposable { @@ -43,6 +44,10 @@ export class MultiDiffEditorViewModel extends Disposable { }); } + public get contextKeys(): Record | undefined { + return this._model.contextKeys; + } + constructor( private readonly _model: IMultiDiffEditorModel, private readonly _instantiationService: IInstantiationService, diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index cd1fbf9cb81..2c4299af5be 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -7,7 +7,7 @@ import { Dimension, getWindow, h, scheduleAtNextAnimationFrame } from 'vs/base/b import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { findFirstMaxBy } from 'vs/base/common/arraysFind'; import { Disposable, IReference, toDisposable } from 'vs/base/common/lifecycle'; -import { IObservable, IReader, autorun, derived, derivedObservableWithCache, derivedWithStore, observableFromEvent, observableValue } from 'vs/base/common/observable'; +import { IObservable, IReader, autorun, autorunWithStore, derived, derivedObservableWithCache, derivedWithStore, observableFromEvent, observableValue } from 'vs/base/common/observable'; import { disposableObservableValue, globalTransaction, transaction } from 'vs/base/common/observableInternal/base'; import { Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable'; import 'vs/css!./style'; @@ -18,7 +18,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { DiffEditorItemTemplate, TemplateData } from './diffEditorItemTemplate'; import { DocumentDiffItemViewModel, MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { ObjectPool } from './objectPool'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyValue, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -94,6 +94,17 @@ export class MultiDiffEditorWidgetImpl extends Disposable { this._contextKeyService.createKey(EditorContextKeys.inMultiDiffEditor.key, true); + this._register(autorunWithStore((reader, store) => { + const viewModel = this._viewModel.read(reader); + if (viewModel && viewModel.contextKeys) { + for (const [key, value] of Object.entries(viewModel.contextKeys)) { + const contextKey = this._contextKeyService.createKey(key, undefined); + contextKey.set(value); + store.add(toDisposable(() => contextKey.reset())); + } + } + })); + const ctxAllCollapsed = this._parentContextKeyService.createKey(EditorContextKeys.multiDiffEditorAllCollapsed.key, false); this._register(autorun((reader) => { const viewModel = this._viewModel.read(reader); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index fdd6e11453a..c93fcdb1774 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -130,12 +130,10 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor } private readonly _viewModel = new LazyStatefulPromise(async () => { - this._resolvedMultiDiffSource.set( - this.initialResources - ? new ConstResolvedMultiDiffSource(this.initialResources) - : await this._multiDiffSourceResolverService.resolve(this.multiDiffSource), - undefined - ); + const source = this.initialResources + ? new ConstResolvedMultiDiffSource(this.initialResources) + : await this._multiDiffSourceResolverService.resolve(this.multiDiffSource); + this._resolvedMultiDiffSource.set(source, undefined); const model = await this._createModel(); this._register(model); @@ -220,6 +218,7 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor }, onDidChange: documentChangeEmitter.event, get documents() { return documents; }, + contextKeys: this._resolvedMultiDiffSource.get()!.contextKeys, }; } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts index 78f9ea7deab..e78363dc229 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts @@ -8,6 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { BugIndicatingError } from 'vs/base/common/errors'; +import { ContextKeyValue } from 'vs/platform/contextkey/common/contextkey'; export const IMultiDiffSourceResolverService = createDecorator('multiDiffSourceResolverService'); @@ -29,6 +30,8 @@ export interface IResolvedMultiDiffSource { readonly resources: readonly MultiDiffEditorItem[]; readonly onDidChange: Event; + + readonly contextKeys?: Record; } export class ConstResolvedMultiDiffSource implements IResolvedMultiDiffSource { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts index 2081d3b74ec..1c1a4c8fc56 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts @@ -85,6 +85,10 @@ export class ScmMultiDiffSourceResolver implements IMultiDiffSourceResolver { })); }, onDidChange: e => group.onDidChangeResources(() => e()), + contextKeys: { + scmResourceGroup: groupId, + scmProvider: repository.provider.contextValue, + }, }; } } From effbda690cca43397f1335d086406f8a85029502 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 11 Jan 2024 11:33:11 +0100 Subject: [PATCH 0227/1897] Fixes #187844 --- .../widget/diffEditor/diffEditorViewModel.ts | 184 ++++++++++++++---- .../features/hideUnchangedRegionsFeature.ts | 8 +- .../editor/browser/widget/diffEditor/utils.ts | 9 + src/vs/editor/common/diff/rangeMapping.ts | 10 +- 4 files changed, 168 insertions(+), 43 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts index c53dad65d37..ab4d3ab9eea 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts @@ -6,20 +6,22 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IObservable, IReader, ISettableObservable, ITransaction, autorunWithStore, derived, observableSignal, observableSignalFromEvent, observableValue, transaction, waitForState } from 'vs/base/common/observable'; +import { IObservable, IReader, ISettableObservable, ITransaction, autorun, autorunWithStore, derived, observableSignal, observableSignalFromEvent, observableValue, transaction, waitForState } from 'vs/base/common/observable'; import { IDiffProviderFactoryService } from 'vs/editor/browser/widget/diffEditor/diffProviderFactoryService'; -import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; -import { ISerializedLineRange, LineRange } from 'vs/editor/common/core/lineRange'; +import { filterWithPrevious, readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; +import { ISerializedLineRange, LineRange, LineRangeSet } from 'vs/editor/common/core/lineRange'; import { DefaultLinesDiffComputer } from 'vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer'; import { IDocumentDiff } from 'vs/editor/common/diff/documentDiffProvider'; import { MovedText } from 'vs/editor/common/diff/linesDiffComputer'; -import { DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; +import { DetailedLineRangeMapping, LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { IDiffEditorModel, IDiffEditorViewModel } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { TextEditInfo } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper'; import { combineTextEditInfos } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/combineTextEditInfos'; import { DiffEditorOptions } from './diffEditorOptions'; import { optimizeSequenceDiffs } from 'vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations'; +import { isDefined } from 'vs/base/common/types'; +import { groupAdjacentBy } from 'vs/base/common/arrays'; export class DiffEditorViewModel extends Disposable implements IDiffEditorViewModel { private readonly _isDiffUpToDate = observableValue(this, false); @@ -29,17 +31,14 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo private readonly _diff = observableValue(this, undefined); public readonly diff: IObservable = this._diff; - private readonly _unchangedRegions = observableValue<{ regions: UnchangedRegion[]; originalDecorationIds: string[]; modifiedDecorationIds: string[] }>( - this, - { regions: [], originalDecorationIds: [], modifiedDecorationIds: [] } - ); + private readonly _unchangedRegions = observableValue<{ regions: UnchangedRegion[]; originalDecorationIds: string[]; modifiedDecorationIds: string[] } | undefined>(this, undefined); public readonly unchangedRegions: IObservable = derived(this, r => { if (this._options.hideUnchangedRegions.read(r)) { - return this._unchangedRegions.read(r).regions; + return this._unchangedRegions.read(r)?.regions ?? []; } else { // Reset state transaction(tx => { - for (const r of this._unchangedRegions.get().regions) { + for (const r of this._unchangedRegions.get()?.regions || []) { r.collapseAll(tx); } }); @@ -89,6 +88,66 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo const contentChangedSignal = observableSignal('contentChangedSignal'); const debouncer = this._register(new RunOnceScheduler(() => contentChangedSignal.trigger(undefined), 200)); + this._register(autorun(reader => { + /** @description collapse touching unchanged ranges */ + + const lastUnchangedRegions = this._unchangedRegions.read(reader); + if (!lastUnchangedRegions || lastUnchangedRegions.regions.some(r => r.isDragged.read(reader))) { + return; + } + + const lastUnchangedRegionsOrigRanges = lastUnchangedRegions.originalDecorationIds + .map(id => model.original.getDecorationRange(id)) + .map(r => r ? LineRange.fromRangeInclusive(r) : undefined); + const lastUnchangedRegionsModRanges = lastUnchangedRegions.modifiedDecorationIds + .map(id => model.modified.getDecorationRange(id)) + .map(r => r ? LineRange.fromRangeInclusive(r) : undefined); + const updatedLastUnchangedRegions = lastUnchangedRegions.regions.map((r, idx) => + (!lastUnchangedRegionsOrigRanges[idx] || !lastUnchangedRegionsModRanges[idx]) ? undefined : + new UnchangedRegion( + lastUnchangedRegionsOrigRanges[idx]!.startLineNumber, + lastUnchangedRegionsModRanges[idx]!.startLineNumber, + lastUnchangedRegionsOrigRanges[idx]!.length, + r.visibleLineCountTop.read(reader), + r.visibleLineCountBottom.read(reader), + )).filter(isDefined); + + const newRanges: UnchangedRegion[] = []; + + let didChange = false; + for (const touching of groupAdjacentBy(updatedLastUnchangedRegions, (a, b) => a.getHiddenModifiedRange(reader).endLineNumberExclusive === b.getHiddenModifiedRange(reader).startLineNumber)) { + if (touching.length > 1) { + didChange = true; + const sumLineCount = touching.reduce((sum, r) => sum + r.lineCount, 0); + const r = new UnchangedRegion(touching[0].originalLineNumber, touching[0].modifiedLineNumber, sumLineCount, touching[0].visibleLineCountTop.get(), touching[touching.length - 1].visibleLineCountBottom.get()); + newRanges.push(r); + } else { + newRanges.push(touching[0]); + } + } + if (didChange) { + const originalDecorationIds = model.original.deltaDecorations( + lastUnchangedRegions.originalDecorationIds, + newRanges.map(r => ({ range: r.originalUnchangedRange.toInclusiveRange()!, options: { description: 'unchanged' } })) + ); + const modifiedDecorationIds = model.modified.deltaDecorations( + lastUnchangedRegions.modifiedDecorationIds, + newRanges.map(r => ({ range: r.modifiedUnchangedRange.toInclusiveRange()!, options: { description: 'unchanged' } })) + ); + + transaction(tx => { + this._unchangedRegions.set( + { + regions: newRanges, + originalDecorationIds, + modifiedDecorationIds + }, + tx + ); + }); + } + })); + const updateUnchangedRegions = (result: IDocumentDiff, tx: ITransaction, reader?: IReader) => { const newUnchangedRegions = UnchangedRegion.fromDiffs( result.changes, @@ -99,36 +158,55 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo ); // Transfer state from cur state + let visibleRegions: LineRangeMapping[] | undefined = undefined; + const lastUnchangedRegions = this._unchangedRegions.get(); - const lastUnchangedRegionsOrigRanges = lastUnchangedRegions.originalDecorationIds - .map(id => model.original.getDecorationRange(id)) - .map(r => r ? LineRange.fromRange(r) : undefined); - const lastUnchangedRegionsModRanges = lastUnchangedRegions.modifiedDecorationIds - .map(id => model.modified.getDecorationRange(id)) - .map(r => r ? LineRange.fromRange(r) : undefined); + if (lastUnchangedRegions) { + const lastUnchangedRegionsOrigRanges = lastUnchangedRegions.originalDecorationIds + .map(id => model.original.getDecorationRange(id)) + .map(r => r ? LineRange.fromRangeInclusive(r) : undefined); + const lastUnchangedRegionsModRanges = lastUnchangedRegions.modifiedDecorationIds + .map(id => model.modified.getDecorationRange(id)) + .map(r => r ? LineRange.fromRangeInclusive(r) : undefined); + const updatedLastUnchangedRegions = filterWithPrevious( + lastUnchangedRegions.regions.map((r, idx) => + (!lastUnchangedRegionsOrigRanges[idx] || !lastUnchangedRegionsModRanges[idx]) ? undefined : + new UnchangedRegion( + lastUnchangedRegionsOrigRanges[idx]!.startLineNumber, + lastUnchangedRegionsModRanges[idx]!.startLineNumber, + lastUnchangedRegionsOrigRanges[idx]!.length, + r.visibleLineCountTop.get(), + r.visibleLineCountBottom.get(), + )).filter(isDefined), + (cur, prev) => !prev || (cur.modifiedLineNumber >= prev.modifiedLineNumber + prev.lineCount && cur.originalLineNumber >= prev.originalLineNumber + prev.lineCount) + ); + + const hiddenRegions = updatedLastUnchangedRegions.map(r => new LineRangeMapping(r.getHiddenOriginalRange(reader), r.getHiddenModifiedRange(reader))); + visibleRegions = LineRangeMapping.inverse(hiddenRegions, model.original.getLineCount(), model.modified.getLineCount()); + } + + const newUnchangedRegions2 = []; + if (visibleRegions) { + for (const r of newUnchangedRegions) { + const intersecting = visibleRegions.filter(f => f.original.intersectsStrict(r.originalUnchangedRange) && f.modified.intersectsStrict(r.modifiedUnchangedRange)); + newUnchangedRegions2.push(...r.setVisibleRanges(intersecting, tx)); + } + } else { + newUnchangedRegions2.push(...newUnchangedRegions); + } const originalDecorationIds = model.original.deltaDecorations( - lastUnchangedRegions.originalDecorationIds, - newUnchangedRegions.map(r => ({ range: r.originalUnchangedRange.toInclusiveRange()!, options: { description: 'unchanged' } })) + lastUnchangedRegions?.originalDecorationIds || [], + newUnchangedRegions2.map(r => ({ range: r.originalUnchangedRange.toInclusiveRange()!, options: { description: 'unchanged' } })) ); const modifiedDecorationIds = model.modified.deltaDecorations( - lastUnchangedRegions.modifiedDecorationIds, - newUnchangedRegions.map(r => ({ range: r.modifiedUnchangedRange.toInclusiveRange()!, options: { description: 'unchanged' } })) + lastUnchangedRegions?.modifiedDecorationIds || [], + newUnchangedRegions2.map(r => ({ range: r.modifiedUnchangedRange.toInclusiveRange()!, options: { description: 'unchanged' } })) ); - - for (const r of newUnchangedRegions) { - for (let i = 0; i < lastUnchangedRegions.regions.length; i++) { - if (lastUnchangedRegionsOrigRanges[i] && r.originalUnchangedRange.intersectsStrict(lastUnchangedRegionsOrigRanges[i]!) - && lastUnchangedRegionsModRanges[i] && r.modifiedUnchangedRange.intersectsStrict(lastUnchangedRegionsModRanges[i]!)) { - r.setHiddenModifiedRange(lastUnchangedRegions.regions[i].getHiddenModifiedRange(undefined), tx); - break; - } - } - } this._unchangedRegions.set( { - regions: newUnchangedRegions, + regions: newUnchangedRegions2, originalDecorationIds, modifiedDecorationIds }, @@ -236,7 +314,7 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo if (this.diff.get()?.mappings.length === 0) { return; } - const unchangedRegions = this._unchangedRegions.get().regions; + const unchangedRegions = this._unchangedRegions.get()?.regions || []; for (const r of unchangedRegions) { if (r.getHiddenModifiedRange(undefined).contains(lineNumber)) { r.showModifiedLine(lineNumber, tx); @@ -249,7 +327,7 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo if (this.diff.get()?.mappings.length === 0) { return; } - const unchangedRegions = this._unchangedRegions.get().regions; + const unchangedRegions = this._unchangedRegions.get()?.regions || []; for (const r of unchangedRegions) { if (r.getHiddenOriginalRange(undefined).contains(lineNumber)) { r.showOriginalLine(lineNumber, tx); @@ -265,13 +343,16 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo public serializeState(): SerializedState { const regions = this._unchangedRegions.get(); return { - collapsedRegions: regions.regions.map(r => ({ range: r.getHiddenModifiedRange(undefined).serialize() })) + collapsedRegions: regions?.regions.map(r => ({ range: r.getHiddenModifiedRange(undefined).serialize() })) }; } public restoreSerializedState(state: SerializedState): void { - const ranges = state.collapsedRegions.map(r => LineRange.deserialize(r.range)); + const ranges = state.collapsedRegions?.map(r => LineRange.deserialize(r.range)); const regions = this._unchangedRegions.get(); + if (!regions || !ranges) { + return; + } transaction(tx => { for (const r of regions.regions) { for (const range of ranges) { @@ -315,7 +396,7 @@ function normalizeRangeMapping(rangeMapping: RangeMapping, original: ITextModel, } interface SerializedState { - collapsedRegions: { range: ISerializedLineRange }[]; + collapsedRegions: { range: ISerializedLineRange }[] | undefined; } export class DiffState { @@ -431,6 +512,37 @@ export class UnchangedRegion { this._visibleLineCountBottom.set(visibleLineCountBottom, undefined); } + public setVisibleRanges(visibleRanges: LineRangeMapping[], tx: ITransaction): UnchangedRegion[] { + const result: UnchangedRegion[] = []; + + const hiddenModified = new LineRangeSet(visibleRanges.map(r => r.modified)).subtractFrom(this.modifiedUnchangedRange); + + let originalStartLineNumber = this.originalLineNumber; + let modifiedStartLineNumber = this.modifiedLineNumber; + const modifiedEndLineNumberEx = this.modifiedLineNumber + this.lineCount; + if (hiddenModified.ranges.length === 0) { + this.showAll(tx); + result.push(this); + } else { + let i = 0; + for (const r of hiddenModified.ranges) { + const isLast = i === hiddenModified.ranges.length - 1; + i++; + + const length = (isLast ? modifiedEndLineNumberEx : r.endLineNumberExclusive) - modifiedStartLineNumber; + + const newR = new UnchangedRegion(originalStartLineNumber, modifiedStartLineNumber, length, 0, 0); + newR.setHiddenModifiedRange(r, tx); + result.push(newR); + + originalStartLineNumber = newR.originalUnchangedRange.endLineNumberExclusive; + modifiedStartLineNumber = newR.modifiedUnchangedRange.endLineNumberExclusive; + } + } + + return result; + } + public shouldHideControls(reader: IReader | undefined): boolean { return this._shouldHideControls.read(reader); } diff --git a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts index e71379433c8..dd83299f65d 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts @@ -82,7 +82,13 @@ export class HideUnchangedRegionsFeature extends Disposable { } })); - const unchangedRegions = this._diffModel.map((m, reader) => m?.diff.read(reader)?.mappings.length === 0 ? [] : m?.unchangedRegions.read(reader) ?? []); + const unchangedRegions = this._diffModel.map((m, reader) => { + const regions = m?.unchangedRegions.read(reader) ?? []; + if (regions.length === 1 && regions[0].modifiedLineNumber === 1 && regions[0].lineCount === this._editors.modifiedModel.read(reader)?.getLineCount()) { + return []; + } + return regions; + }); this.viewZones = derivedWithStore(this, (reader, store) => { /** @description view Zones */ diff --git a/src/vs/editor/browser/widget/diffEditor/utils.ts b/src/vs/editor/browser/widget/diffEditor/utils.ts index 17e5630a7b6..0109a630f20 100644 --- a/src/vs/editor/browser/widget/diffEditor/utils.ts +++ b/src/vs/editor/browser/widget/diffEditor/utils.ts @@ -447,3 +447,12 @@ export function bindContextKey(key: RawContextKey, boundKey.set(computeValue(reader)); }); } + +export function filterWithPrevious(arr: T[], filter: (cur: T, prev: T | undefined) => boolean): T[] { + let prev: T | undefined; + return arr.filter(cur => { + const result = filter(cur, prev); + prev = cur; + return result; + }); +} diff --git a/src/vs/editor/common/diff/rangeMapping.ts b/src/vs/editor/common/diff/rangeMapping.ts index 9b69d90fbad..5dc60d70294 100644 --- a/src/vs/editor/common/diff/rangeMapping.ts +++ b/src/vs/editor/common/diff/rangeMapping.ts @@ -10,16 +10,15 @@ import { Range } from 'vs/editor/common/core/range'; * Maps a line range in the original text model to a line range in the modified text model. */ export class LineRangeMapping { - public static inverse(mapping: readonly DetailedLineRangeMapping[], originalLineCount: number, modifiedLineCount: number): DetailedLineRangeMapping[] { - const result: DetailedLineRangeMapping[] = []; + public static inverse(mapping: readonly LineRangeMapping[], originalLineCount: number, modifiedLineCount: number): LineRangeMapping[] { + const result: LineRangeMapping[] = []; let lastOriginalEndLineNumber = 1; let lastModifiedEndLineNumber = 1; for (const m of mapping) { - const r = new DetailedLineRangeMapping( + const r = new LineRangeMapping( new LineRange(lastOriginalEndLineNumber, m.original.startLineNumber), new LineRange(lastModifiedEndLineNumber, m.modified.startLineNumber), - undefined ); if (!r.modified.isEmpty) { result.push(r); @@ -27,10 +26,9 @@ export class LineRangeMapping { lastOriginalEndLineNumber = m.original.endLineNumberExclusive; lastModifiedEndLineNumber = m.modified.endLineNumberExclusive; } - const r = new DetailedLineRangeMapping( + const r = new LineRangeMapping( new LineRange(lastOriginalEndLineNumber, originalLineCount + 1), new LineRange(lastModifiedEndLineNumber, modifiedLineCount + 1), - undefined ); if (!r.modified.isEmpty) { result.push(r); From 8720ead44d75fab3187a098f8b9ce9cf6834aaca Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 11 Jan 2024 12:21:17 +0100 Subject: [PATCH 0228/1897] Git - adopt _workbench.openMultiDiffEditor command (#202236) --- extensions/git/src/commands.ts | 28 +++++++++++++++++----------- extensions/git/src/uri.ts | 7 ++----- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index ccc68cf65a2..7c335f990ed 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3550,14 +3550,16 @@ export class CommandCenter { return; } - const args: [Uri, Uri | undefined, Uri | undefined][] = []; + const title = `Git Stash #${stash.index}: ${stash.description}`; + const multiDiffSourceUri = toGitUri(Uri.file(repository.root), `stash@{${stash.index}}`, { scheme: 'git-stash' }); + const resources: { originalUri: Uri; modifiedUri: Uri }[] = []; for (const file of stashFiles) { const fileUri = Uri.file(path.join(repository.root, file)); - args.push([fileUri, fileUri, toGitUri(fileUri, `stash@{${stash.index}}`)]); + resources.push({ originalUri: fileUri, modifiedUri: toGitUri(fileUri, `stash@{${stash.index}}`) }); } - commands.executeCommand('vscode.changes', `Git Stash #${stash.index}: ${stash.description}`, args); + commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); } private async pickStash(repository: Repository, placeHolder: string): Promise { @@ -3649,16 +3651,19 @@ export class CommandCenter { const commit = await repository.getCommit(item.ref); const commitFiles = await repository.getCommitFiles(item.ref); - const args: [Uri, Uri | undefined, Uri | undefined][] = []; + const title = `${item.shortRef} - ${commit.message}`; + const multiDiffSourceUri = toGitUri(Uri.file(repository.root), item.ref, { scheme: 'git-commit' }); + + const resources: { originalUri: Uri; modifiedUri: Uri }[] = []; for (const commitFile of commitFiles) { const commitFileUri = Uri.file(path.join(repository.root, commitFile)); - args.push([commitFileUri, toGitUri(commitFileUri, item.previousRef), toGitUri(commitFileUri, item.ref)]); + resources.push({ originalUri: toGitUri(commitFileUri, item.previousRef), modifiedUri: toGitUri(commitFileUri, item.ref) }); } return { - command: 'vscode.changes', + command: '_workbench.openMultiDiffEditor', title: l10n.t('Open Commit'), - arguments: [`${item.shortRef} - ${commit.message}`, args, options] + arguments: [{ multiDiffSourceUri, title, resources }, options] }; } @@ -3827,9 +3832,9 @@ export class CommandCenter { const historyProvider = repository.historyProvider; const historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined; - const historyItemChanges = await historyProvider.provideHistoryItemChanges(historyItem.id, historyItemParentId); + const commitFiles = await historyProvider.provideHistoryItemChanges(historyItem.id, historyItemParentId); - if (historyItemChanges.length === 0) { + if (commitFiles.length === 0) { return; } @@ -3837,9 +3842,10 @@ export class CommandCenter { const originalShortRef = historyItem.parentIds.length > 0 ? historyItem.parentIds[0].substring(0, 8) : `${modifiedShortRef}^`; const title = l10n.t('Changes ({0} ↔ {1})', originalShortRef, modifiedShortRef); - const args = historyItemChanges.map(change => [change.uri, change.originalUri, change.modifiedUri]); + const multiDiffSourceUri = toGitUri(Uri.file(repository.root), historyItem.id, { scheme: 'git-commit' }); + const resources = commitFiles.map(change => ({ originalUri: change.originalUri, modifiedUri: change.modifiedUri })); - commands.executeCommand('vscode.changes', title, args); + commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); } private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any { diff --git a/extensions/git/src/uri.ts b/extensions/git/src/uri.ts index 5694c920d6b..3a39bfeaa64 100644 --- a/extensions/git/src/uri.ts +++ b/extensions/git/src/uri.ts @@ -20,6 +20,7 @@ export function fromGitUri(uri: Uri): GitUriParams { } export interface GitUriOptions { + scheme?: string; replaceFileExtension?: boolean; submoduleOf?: string; } @@ -45,11 +46,7 @@ export function toGitUri(uri: Uri, ref: string, options: GitUriOptions = {}): Ur path = `${path}.diff`; } - return uri.with({ - scheme: 'git', - path, - query: JSON.stringify(params) - }); + return uri.with({ scheme: options.scheme ?? 'git', path, query: JSON.stringify(params) }); } /** From 573c43c196814c25eb98028ce7d59e495a39f6b0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 11 Jan 2024 18:43:57 +0530 Subject: [PATCH 0229/1897] #200091 adopt ensureNoDisposablesAreLeakedInTestSuite (#202241) --- .eslintrc.json | 2 -- .../test/common/configRemotes.test.ts | 3 ++- .../test/common/extensionManagement.test.ts | 3 +++ .../extensions/test/electron-sandbox/extension.test.ts | 9 ++++----- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index fb829ab320d..532cb095cf8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -173,7 +173,6 @@ "src/vs/platform/contextkey/test/common/contextkey.test.ts", "src/vs/platform/contextkey/test/common/parser.test.ts", "src/vs/platform/contextkey/test/common/scanner.test.ts", - "src/vs/platform/extensionManagement/test/common/extensionManagement.test.ts", "src/vs/platform/extensions/test/common/extensionValidator.test.ts", "src/vs/platform/instantiation/test/common/graph.test.ts", "src/vs/platform/instantiation/test/common/instantiationService.test.ts", @@ -202,7 +201,6 @@ "src/vs/workbench/contrib/chat/test/common/chatWordCounter.test.ts", "src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts", "src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts", - "src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts", "src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts", "src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts", "src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts", diff --git a/src/vs/platform/extensionManagement/test/common/configRemotes.test.ts b/src/vs/platform/extensionManagement/test/common/configRemotes.test.ts index e27bb5a488a..ce93c6e73d7 100644 --- a/src/vs/platform/extensionManagement/test/common/configRemotes.test.ts +++ b/src/vs/platform/extensionManagement/test/common/configRemotes.test.ts @@ -9,6 +9,8 @@ import { getDomainsOfRemotes, getRemotes } from 'vs/platform/extensionManagement suite('Config Remotes', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const allowedDomains = [ 'github.com', 'github2.com', @@ -129,5 +131,4 @@ suite('Config Remotes', () => { `; } - ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/platform/extensionManagement/test/common/extensionManagement.test.ts b/src/vs/platform/extensionManagement/test/common/extensionManagement.test.ts index 291bb1767e9..d0813771c18 100644 --- a/src/vs/platform/extensionManagement/test/common/extensionManagement.test.ts +++ b/src/vs/platform/extensionManagement/test/common/extensionManagement.test.ts @@ -3,12 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionKey } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { TargetPlatform } from 'vs/platform/extensions/common/extensions'; suite('Extension Identifier Pattern', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('extension identifier pattern', () => { const regEx = new RegExp(EXTENSION_IDENTIFIER_PATTERN); assert.strictEqual(true, regEx.test('publisher.name')); diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts index 0ef0e1d50c0..1a32f81a7db 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts @@ -13,20 +13,19 @@ import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/ex import { generateUuid } from 'vs/base/common/uuid'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IProductService } from 'vs/platform/product/common/productService'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Extension Test', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + let instantiationService: TestInstantiationService; setup(() => { - instantiationService = new TestInstantiationService(); + instantiationService = disposables.add(new TestInstantiationService()); instantiationService.stub(IProductService, { quality: 'insiders' }); }); - teardown(() => { - instantiationService.dispose(); - }); - test('extension is not outdated when there is no local and gallery', () => { const extension = instantiationService.createInstance(Extension, () => ExtensionState.Installed, () => undefined, undefined, undefined, undefined); assert.strictEqual(extension.outdated, false); From 753024143d14c59e196df0aa8e652a433d6f25db Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 11 Jan 2024 07:14:42 -0700 Subject: [PATCH 0230/1897] Adopt ensureNoDisposablesAreLeakedInTestSuite (#202245) --- .eslintrc.json | 1 - .../telemetry/test/browser/commonProperties.test.ts | 11 +++++++++-- .../telemetry/test/node/commonProperties.test.ts | 12 ++++++++---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 532cb095cf8..196a7dcf334 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -214,7 +214,6 @@ "src/vs/workbench/services/keybinding/test/node/fallbackKeyboardMapper.test.ts", "src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts", "src/vs/workbench/services/keybinding/test/node/windowsKeyboardMapper.test.ts", - "src/vs/workbench/services/telemetry/test/browser/commonProperties.test.ts", "src/vs/workbench/services/userActivity/test/browser/domActivityTracker.test.ts", "src/vs/workbench/test/browser/quickAccess.test.ts" ] diff --git a/src/vs/workbench/services/telemetry/test/browser/commonProperties.test.ts b/src/vs/workbench/services/telemetry/test/browser/commonProperties.test.ts index e8eb559e97e..950605ae825 100644 --- a/src/vs/workbench/services/telemetry/test/browser/commonProperties.test.ts +++ b/src/vs/workbench/services/telemetry/test/browser/commonProperties.test.ts @@ -4,13 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { resolveWorkbenchCommonProperties } from 'vs/workbench/services/telemetry/browser/workbenchCommonProperties'; -import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; +import { InMemoryStorageService } from 'vs/platform/storage/common/storage'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Browser Telemetry - common properties', function () { const commit: string = (undefined)!; const version: string = (undefined)!; - let testStorageService: IStorageService; + let testStorageService: InMemoryStorageService; + + teardown(() => { + testStorageService.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); setup(() => { testStorageService = new InMemoryStorageService(); diff --git a/src/vs/workbench/services/telemetry/test/node/commonProperties.test.ts b/src/vs/workbench/services/telemetry/test/node/commonProperties.test.ts index 48159a6c21c..5f47fd16202 100644 --- a/src/vs/workbench/services/telemetry/test/node/commonProperties.test.ts +++ b/src/vs/workbench/services/telemetry/test/node/commonProperties.test.ts @@ -6,21 +6,25 @@ import * as assert from 'assert'; import { release, hostname } from 'os'; import { resolveWorkbenchCommonProperties } from 'vs/workbench/services/telemetry/common/workbenchCommonProperties'; -import { IStorageService, StorageScope, InMemoryStorageService, StorageTarget } from 'vs/platform/storage/common/storage'; +import { StorageScope, InMemoryStorageService, StorageTarget } from 'vs/platform/storage/common/storage'; import { timeout } from 'vs/base/common/async'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Telemetry - common properties', function () { const commit: string = (undefined)!; const version: string = (undefined)!; - let testStorageService: IStorageService; + let testStorageService: InMemoryStorageService; + + teardown(() => { + testStorageService.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); setup(() => { testStorageService = new InMemoryStorageService(); }); - ensureNoDisposablesAreLeakedInTestSuite(); - test('default', function () { const props = resolveWorkbenchCommonProperties(testStorageService, release(), hostname(), commit, version, 'someMachineId', 'someSqmId', false, process); assert.ok('commitHash' in props); From b94a564c8a97abdfcd6f3021133e237361339fdb Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 11 Jan 2024 15:29:52 +0100 Subject: [PATCH 0231/1897] Allow FileSystemProviders to set the read-only file message (#202244) Finalize read-only message file system provider API Part of #166971 --- src/vs/workbench/api/common/extHostFileSystem.ts | 1 - .../extensions/common/extensionsApiProposals.ts | 1 - src/vscode-dts/vscode.d.ts | 3 ++- .../vscode.proposed.readonlyMessage.d.ts | 14 -------------- 4 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.readonlyMessage.d.ts diff --git a/src/vs/workbench/api/common/extHostFileSystem.ts b/src/vs/workbench/api/common/extHostFileSystem.ts index 6530636e45f..8d06a8dd025 100644 --- a/src/vs/workbench/api/common/extHostFileSystem.ts +++ b/src/vs/workbench/api/common/extHostFileSystem.ts @@ -167,7 +167,6 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { let readOnlyMessage: IMarkdownString | undefined; if (options.isReadonly && isMarkdownString(options.isReadonly)) { - checkProposedApiEnabled(extension, 'readonlyMessage'); readOnlyMessage = { value: options.isReadonly.value, isTrusted: options.isReadonly.isTrusted, diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 0e67b531270..ad592f53082 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -77,7 +77,6 @@ export const allApiProposals = Object.freeze({ quickDiffProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts', quickPickItemTooltip: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickItemTooltip.d.ts', quickPickSortByLabel: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts', - readonlyMessage: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.readonlyMessage.d.ts', resolvers: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.resolvers.d.ts', scmActionButton: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmActionButton.d.ts', scmHistoryProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts', diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 0bd6a16bf80..36f2cd8ca2c 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -13635,8 +13635,9 @@ declare module 'vscode' { readonly isCaseSensitive?: boolean; /** * Whether the file system provider is readonly, no modifications like write, delete, create are possible. + * If a {@link MarkdownString} is given, it will be shown as the reason why the file system is readonly. */ - readonly isReadonly?: boolean; + readonly isReadonly?: boolean | MarkdownString; }): Disposable; /** diff --git a/src/vscode-dts/vscode.proposed.readonlyMessage.d.ts b/src/vscode-dts/vscode.proposed.readonlyMessage.d.ts deleted file mode 100644 index 03bad3a664b..00000000000 --- a/src/vscode-dts/vscode.proposed.readonlyMessage.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// https://github.com/microsoft/vscode/issues/166971 - -declare module 'vscode' { - - export namespace workspace { - - export function registerFileSystemProvider(scheme: string, provider: FileSystemProvider, options?: { readonly isCaseSensitive?: boolean; readonly isReadonly?: boolean | MarkdownString }): Disposable; - } -} From 2c2cc18333c007068067be7eecc235bdf83fc476 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 11 Jan 2024 15:30:18 +0100 Subject: [PATCH 0232/1897] Plus button to add a comment doesn't disappear when leaving the editor area (#202248) Fixes #199342 --- .../workbench/contrib/comments/browser/commentsController.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index 66ed2edee17..88c3daef30e 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -516,6 +516,7 @@ export class CommentController implements IEditorContribution { return; } this._editorDisposables.push(this.editor.onMouseMove(e => this.onEditorMouseMove(e))); + this._editorDisposables.push(this.editor.onMouseLeave(() => this.onEditorMouseLeave())); this._editorDisposables.push(this.editor.onDidChangeCursorPosition(e => this.onEditorChangeCursorPosition(e.position))); this._editorDisposables.push(this.editor.onDidFocusEditorWidget(() => this.onEditorChangeCursorPosition(this.editor?.getPosition() ?? null))); this._editorDisposables.push(this.editor.onDidChangeCursorSelection(e => this.onEditorChangeCursorSelection(e))); @@ -527,6 +528,10 @@ export class CommentController implements IEditorContribution { this._editorDisposables = []; } + private onEditorMouseLeave() { + this._commentingRangeDecorator.updateHover(); + } + private onEditorMouseMove(e: IEditorMouseEvent): void { const position = e.target.position?.lineNumber; if (e.event.leftButton.valueOf() && position && this.mouseDownInfo) { From 4c92920ffed6787ee71301a22aeede7959bad4ed Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 11 Jan 2024 20:10:10 +0530 Subject: [PATCH 0233/1897] fix #116191 (#202249) --- .../node/sharedProcess/sharedProcessMain.ts | 33 ++-- .../userDataSync/common/userDataSyncIpc.ts | 141 ++++++------------ .../common/userDataSyncServiceIpc.ts | 4 +- .../userDataSync.contribution.ts | 8 +- .../userDataSyncAccountService.ts | 47 ------ .../userDataSyncMachinesService.ts | 50 ------- .../electron-sandbox/userDataSyncService.ts | 12 +- .../userDataSyncStoreManagementService.ts | 40 ----- src/vs/workbench/workbench.desktop.main.ts | 3 - 9 files changed, 77 insertions(+), 261 deletions(-) delete mode 100644 src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncAccountService.ts delete mode 100644 src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService.ts delete mode 100644 src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncStoreManagementService.ts diff --git a/src/vs/code/node/sharedProcess/sharedProcessMain.ts b/src/vs/code/node/sharedProcess/sharedProcessMain.ts index 3c5650cb69d..82b79418af0 100644 --- a/src/vs/code/node/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/node/sharedProcess/sharedProcessMain.ts @@ -7,7 +7,7 @@ import { hostname, release } from 'os'; import { MessagePortMain, MessageEvent } from 'vs/base/parts/sandbox/node/electronTypes'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; -import { combinedDisposable, Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { combinedDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { firstOrDefault } from 'vs/base/common/arrays'; @@ -63,12 +63,12 @@ import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService import { IUserDataSyncLocalStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, IUserDataSyncUtilService, registerConfiguration as registerUserDataSyncConfiguration, IUserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; import { UserDataSyncLocalStoreService } from 'vs/platform/userDataSync/common/userDataSyncLocalStoreService'; -import { UserDataAutoSyncChannel, UserDataSyncAccountServiceChannel, UserDataSyncMachinesServiceChannel, UserDataSyncStoreManagementServiceChannel, UserDataSyncUtilServiceClient } from 'vs/platform/userDataSync/common/userDataSyncIpc'; +import { UserDataSyncAccountServiceChannel, UserDataSyncStoreManagementServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { IUserDataSyncMachinesService, UserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; -import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncServiceIpc'; +import { UserDataSyncServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncServiceIpc'; import { UserDataSyncStoreManagementService, UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; import { NativeUserDataProfileStorageService } from 'vs/platform/userDataProfile/node/userDataProfileStorageService'; @@ -339,7 +339,7 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { // Settings Sync services.set(IUserDataSyncAccountService, new SyncDescriptor(UserDataSyncAccountService, undefined, true)); services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService, undefined, true)); - services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(this.server.getChannel('userDataSyncUtil', client => client.ctx !== 'main'))); + services.set(IUserDataSyncUtilService, ProxyChannel.toService(this.server.getChannel('userDataSyncUtil', client => client.ctx !== 'main'))); services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService, undefined, false /* Eagerly resets installed extensions */)); services.set(IIgnoredExtensionsManagementService, new SyncDescriptor(IgnoredExtensionsManagementService, undefined, true)); services.set(IExtensionStorageService, new SyncDescriptor(ExtensionStorageService)); @@ -370,18 +370,18 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { private initChannels(accessor: ServicesAccessor): void { - const disposables = this._register(new DisposableStore()); + // const disposables = this._register(new DisposableStore()); // Extensions Management const channel = new ExtensionManagementChannel(accessor.get(IExtensionManagementService), () => null); this.server.registerChannel('extensions', channel); // Language Packs - const languagePacksChannel = ProxyChannel.fromService(accessor.get(ILanguagePackService), disposables); + const languagePacksChannel = ProxyChannel.fromService(accessor.get(ILanguagePackService), this._store); this.server.registerChannel('languagePacks', languagePacksChannel); // Diagnostics - const diagnosticsChannel = ProxyChannel.fromService(accessor.get(IDiagnosticsService), disposables); + const diagnosticsChannel = ProxyChannel.fromService(accessor.get(IDiagnosticsService), this._store); this.server.registerChannel('diagnostics', diagnosticsChannel); // Extension Tips @@ -389,19 +389,19 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { this.server.registerChannel('extensionTipsService', extensionTipsChannel); // Checksum - const checksumChannel = ProxyChannel.fromService(accessor.get(IChecksumService), disposables); + const checksumChannel = ProxyChannel.fromService(accessor.get(IChecksumService), this._store); this.server.registerChannel('checksum', checksumChannel); // Profiling - const profilingChannel = ProxyChannel.fromService(accessor.get(IV8InspectProfilingService), disposables); + const profilingChannel = ProxyChannel.fromService(accessor.get(IV8InspectProfilingService), this._store); this.server.registerChannel('v8InspectProfiling', profilingChannel); // Settings Sync - const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(accessor.get(IUserDataSyncMachinesService)); + const userDataSyncMachineChannel = ProxyChannel.fromService(accessor.get(IUserDataSyncMachinesService), this._store); this.server.registerChannel('userDataSyncMachines', userDataSyncMachineChannel); // Custom Endpoint Telemetry - const customEndpointTelemetryChannel = ProxyChannel.fromService(accessor.get(ICustomEndpointTelemetryService), disposables); + const customEndpointTelemetryChannel = ProxyChannel.fromService(accessor.get(ICustomEndpointTelemetryService), this._store); this.server.registerChannel('customEndpointTelemetry', customEndpointTelemetryChannel); const userDataSyncAccountChannel = new UserDataSyncAccountServiceChannel(accessor.get(IUserDataSyncAccountService)); @@ -410,21 +410,20 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { const userDataSyncStoreManagementChannel = new UserDataSyncStoreManagementServiceChannel(accessor.get(IUserDataSyncStoreManagementService)); this.server.registerChannel('userDataSyncStoreManagement', userDataSyncStoreManagementChannel); - const userDataSyncChannel = new UserDataSyncChannel(accessor.get(IUserDataSyncService), accessor.get(IUserDataProfilesService), accessor.get(ILogService)); + const userDataSyncChannel = new UserDataSyncServiceChannel(accessor.get(IUserDataSyncService), accessor.get(IUserDataProfilesService), accessor.get(ILogService)); this.server.registerChannel('userDataSync', userDataSyncChannel); const userDataAutoSync = this._register(accessor.get(IInstantiationService).createInstance(UserDataAutoSyncService)); - const userDataAutoSyncChannel = new UserDataAutoSyncChannel(userDataAutoSync); - this.server.registerChannel('userDataAutoSync', userDataAutoSyncChannel); + this.server.registerChannel('userDataAutoSync', ProxyChannel.fromService(userDataAutoSync, this._store)); - this.server.registerChannel('IUserDataSyncResourceProviderService', ProxyChannel.fromService(accessor.get(IUserDataSyncResourceProviderService), disposables)); + this.server.registerChannel('IUserDataSyncResourceProviderService', ProxyChannel.fromService(accessor.get(IUserDataSyncResourceProviderService), this._store)); // Tunnel - const sharedProcessTunnelChannel = ProxyChannel.fromService(accessor.get(ISharedProcessTunnelService), disposables); + const sharedProcessTunnelChannel = ProxyChannel.fromService(accessor.get(ISharedProcessTunnelService), this._store); this.server.registerChannel(ipcSharedProcessTunnelChannelName, sharedProcessTunnelChannel); // Remote Tunnel - const remoteTunnelChannel = ProxyChannel.fromService(accessor.get(IRemoteTunnelService), disposables); + const remoteTunnelChannel = ProxyChannel.fromService(accessor.get(IRemoteTunnelService), this._store); this.server.registerChannel('remoteTunnel', remoteTunnelChannel); } diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 9ee868cf0fe..74725542b10 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -3,99 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IStringDictionary } from 'vs/base/common/collections'; -import { Event } from 'vs/base/common/event'; -import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IUserDataAutoSyncService, IUserDataSyncStore, IUserDataSyncStoreManagementService, IUserDataSyncUtilService, UserDataSyncStoreType } from 'vs/platform/userDataSync/common/userDataSync'; -import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; -import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; - -export class UserDataAutoSyncChannel implements IServerChannel { - - constructor(private readonly service: IUserDataAutoSyncService) { } - - listen(_: unknown, event: string): Event { - switch (event) { - case 'onError': return this.service.onError; - } - throw new Error(`Event not found: ${event}`); - } - - call(context: any, command: string, args?: any): Promise { - switch (command) { - case 'triggerSync': return this.service.triggerSync(args[0], args[1], args[2]); - case 'turnOn': return this.service.turnOn(); - case 'turnOff': return this.service.turnOff(args[0]); - } - throw new Error('Invalid call'); - } -} - -export class UserDataSycnUtilServiceChannel implements IServerChannel { - - constructor(private readonly service: IUserDataSyncUtilService) { } - - listen(_: unknown, event: string): Event { - throw new Error(`Event not found: ${event}`); - } - - call(context: any, command: string, args?: any): Promise { - switch (command) { - case 'resolveDefaultIgnoredSettings': return this.service.resolveDefaultIgnoredSettings(); - case 'resolveUserKeybindings': return this.service.resolveUserBindings(args[0]); - case 'resolveFormattingOptions': return this.service.resolveFormattingOptions(URI.revive(args[0])); - } - throw new Error('Invalid call'); - } -} - -export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService { - - declare readonly _serviceBrand: undefined; - - constructor(private readonly channel: IChannel) { - } - - async resolveDefaultIgnoredSettings(): Promise { - return this.channel.call('resolveDefaultIgnoredSettings'); - } - - async resolveUserBindings(userbindings: string[]): Promise> { - return this.channel.call('resolveUserKeybindings', [userbindings]); - } - - async resolveFormattingOptions(file: URI): Promise { - return this.channel.call('resolveFormattingOptions', [file]); - } - -} - -export class UserDataSyncMachinesServiceChannel implements IServerChannel { - - constructor(private readonly service: IUserDataSyncMachinesService) { } - - listen(_: unknown, event: string): Event { - switch (event) { - case 'onDidChange': return this.service.onDidChange; - } - throw new Error(`Event not found: ${event}`); - } - - async call(context: any, command: string, args?: any): Promise { - switch (command) { - case 'getMachines': return this.service.getMachines(); - case 'addCurrentMachine': return this.service.addCurrentMachine(); - case 'removeCurrentMachine': return this.service.removeCurrentMachine(); - case 'renameMachine': return this.service.renameMachine(args[0], args[1]); - case 'setEnablements': return this.service.setEnablements(args); - } - throw new Error('Invalid call'); - } - -} +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IUserDataSyncStore, IUserDataSyncStoreManagementService, UserDataSyncStoreType } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncAccount, IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; +import { AbstractUserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; export class UserDataSyncAccountServiceChannel implements IServerChannel { constructor(private readonly service: IUserDataSyncAccountService) { } @@ -117,6 +34,35 @@ export class UserDataSyncAccountServiceChannel implements IServerChannel { } } +export class UserDataSyncAccountServiceChannelClient extends Disposable implements IUserDataSyncAccountService { + + declare readonly _serviceBrand: undefined; + + private _account: IUserDataSyncAccount | undefined; + get account(): IUserDataSyncAccount | undefined { return this._account; } + + get onTokenFailed(): Event { return this.channel.listen('onTokenFailed'); } + + private _onDidChangeAccount = this._register(new Emitter()); + readonly onDidChangeAccount = this._onDidChangeAccount.event; + + constructor(private readonly channel: IChannel) { + super(); + this.channel.call('_getInitialData').then(account => { + this._account = account; + this._register(this.channel.listen('onDidChangeAccount')(account => { + this._account = account; + this._onDidChangeAccount.fire(account); + })); + }); + } + + updateAccount(account: IUserDataSyncAccount | undefined): Promise { + return this.channel.call('updateAccount', account); + } + +} + export class UserDataSyncStoreManagementServiceChannel implements IServerChannel { constructor(private readonly service: IUserDataSyncStoreManagementService) { } @@ -136,13 +82,16 @@ export class UserDataSyncStoreManagementServiceChannel implements IServerChannel } } -export class UserDataSyncStoreManagementServiceChannelClient extends Disposable { +export class UserDataSyncStoreManagementServiceChannelClient extends AbstractUserDataSyncStoreManagementService implements IUserDataSyncStoreManagementService { - readonly onDidChangeUserDataSyncStore: Event; - - constructor(private readonly channel: IChannel) { - super(); - this.onDidChangeUserDataSyncStore = this.channel.listen('onDidChangeUserDataSyncStore'); + constructor( + private readonly channel: IChannel, + @IProductService productService: IProductService, + @IConfigurationService configurationService: IConfigurationService, + @IStorageService storageService: IStorageService, + ) { + super(productService, configurationService, storageService); + this._register(this.channel.listen('onDidChangeUserDataSyncStore')(() => this.updateUserDataSyncStore())); } async switch(type: UserDataSyncStoreType): Promise { diff --git a/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts index 85ac679bfa2..9619fae438e 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts @@ -25,7 +25,7 @@ function reviewSyncResourceHandle(syncResourceHandle: ISyncResourceHandle): ISyn return { created: syncResourceHandle.created, uri: URI.revive(syncResourceHandle.uri) }; } -export class UserDataSyncChannel implements IServerChannel { +export class UserDataSyncServiceChannel implements IServerChannel { private readonly manualSyncTasks = new Map(); private readonly onManualSynchronizeResources = new Emitter>(); @@ -119,7 +119,7 @@ export class UserDataSyncChannel implements IServerChannel { } -export class UserDataSyncChannelClient extends Disposable implements IUserDataSyncService { +export class UserDataSyncServiceChannelClient extends Disposable implements IUserDataSyncService { declare readonly _serviceBrand: undefined; diff --git a/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts index 4f3ae606740..dd126556bca 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts @@ -8,7 +8,6 @@ import { IUserDataSyncUtilService, SyncStatus } from 'vs/platform/userDataSync/c import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; -import { UserDataSycnUtilServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; import { localize, localize2 } from 'vs/nls'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -18,14 +17,17 @@ import { INativeHostService } from 'vs/platform/native/common/native'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { CONTEXT_SYNC_STATE, DOWNLOAD_ACTIVITY_ACTION_DESCRIPTOR, IUserDataSyncWorkbenchService, SYNC_TITLE } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { Schemas } from 'vs/base/common/network'; +import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; +import { Disposable } from 'vs/base/common/lifecycle'; -class UserDataSyncServicesContribution implements IWorkbenchContribution { +class UserDataSyncServicesContribution extends Disposable implements IWorkbenchContribution { constructor( @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @ISharedProcessService sharedProcessService: ISharedProcessService, ) { - sharedProcessService.registerChannel('userDataSyncUtil', new UserDataSycnUtilServiceChannel(userDataSyncUtilService)); + super(); + sharedProcessService.registerChannel('userDataSyncUtil', ProxyChannel.fromService(userDataSyncUtilService, this._store)); } } diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncAccountService.ts b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncAccountService.ts deleted file mode 100644 index 134525e6d86..00000000000 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncAccountService.ts +++ /dev/null @@ -1,47 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { Event, Emitter } from 'vs/base/common/event'; -import { IUserDataSyncAccountService, IUserDataSyncAccount } from 'vs/platform/userDataSync/common/userDataSyncAccount'; - -export class UserDataSyncAccountService extends Disposable implements IUserDataSyncAccountService { - - declare readonly _serviceBrand: undefined; - - private readonly channel: IChannel; - - private _account: IUserDataSyncAccount | undefined; - get account(): IUserDataSyncAccount | undefined { return this._account; } - - get onTokenFailed(): Event { return this.channel.listen('onTokenFailed'); } - - private _onDidChangeAccount: Emitter = this._register(new Emitter()); - readonly onDidChangeAccount: Event = this._onDidChangeAccount.event; - - constructor( - @ISharedProcessService sharedProcessService: ISharedProcessService, - ) { - super(); - this.channel = sharedProcessService.getChannel('userDataSyncAccount'); - this.channel.call('_getInitialData').then(account => { - this._account = account; - this._register(this.channel.listen('onDidChangeAccount')(account => { - this._account = account; - this._onDidChangeAccount.fire(account); - })); - }); - } - - updateAccount(account: IUserDataSyncAccount | undefined): Promise { - return this.channel.call('updateAccount', account); - } - -} - -registerSingleton(IUserDataSyncAccountService, UserDataSyncAccountService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService.ts b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService.ts deleted file mode 100644 index 766386ba6a9..00000000000 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IUserDataSyncMachinesService, IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines'; -import { Event } from 'vs/base/common/event'; - -class UserDataSyncMachinesService extends Disposable implements IUserDataSyncMachinesService { - - declare readonly _serviceBrand: undefined; - - private readonly channel: IChannel; - - get onDidChange(): Event { return this.channel.listen('onDidChange'); } - - constructor( - @ISharedProcessService sharedProcessService: ISharedProcessService - ) { - super(); - this.channel = sharedProcessService.getChannel('userDataSyncMachines'); - } - - getMachines(): Promise { - return this.channel.call('getMachines'); - } - - addCurrentMachine(): Promise { - return this.channel.call('addCurrentMachine'); - } - - removeCurrentMachine(): Promise { - return this.channel.call('removeCurrentMachine'); - } - - renameMachine(machineId: string, name: string): Promise { - return this.channel.call('renameMachine', [machineId, name]); - } - - setEnablements(enablements: [string, boolean][]): Promise { - return this.channel.call('setEnablements', enablements); - } - -} - -registerSingleton(IUserDataSyncMachinesService, UserDataSyncMachinesService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts index 4825bb8d580..8d9b97c943f 100644 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts @@ -3,9 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncResourceProviderService, IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncResourceProviderService, IUserDataSyncService, IUserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSync'; import { registerSharedProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; -import { UserDataSyncChannelClient } from 'vs/platform/userDataSync/common/userDataSyncServiceIpc'; +import { UserDataSyncServiceChannelClient } from 'vs/platform/userDataSync/common/userDataSyncServiceIpc'; +import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; +import { UserDataSyncAccountServiceChannelClient, UserDataSyncStoreManagementServiceChannelClient } from 'vs/platform/userDataSync/common/userDataSyncIpc'; +import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; -registerSharedProcessRemoteService(IUserDataSyncService, 'userDataSync', { channelClientCtor: UserDataSyncChannelClient }); +registerSharedProcessRemoteService(IUserDataSyncService, 'userDataSync', { channelClientCtor: UserDataSyncServiceChannelClient }); registerSharedProcessRemoteService(IUserDataSyncResourceProviderService, 'IUserDataSyncResourceProviderService'); +registerSharedProcessRemoteService(IUserDataSyncMachinesService, 'userDataSyncMachines'); +registerSharedProcessRemoteService(IUserDataSyncAccountService, 'userDataSyncAccount', { channelClientCtor: UserDataSyncAccountServiceChannelClient }); +registerSharedProcessRemoteService(IUserDataSyncStoreManagementService, 'userDataSyncStoreManagement', { channelClientCtor: UserDataSyncStoreManagementServiceChannelClient }); diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncStoreManagementService.ts b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncStoreManagementService.ts deleted file mode 100644 index a85e432cd86..00000000000 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncStoreManagementService.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IUserDataSyncStoreManagementService, UserDataSyncStoreType, IUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { AbstractUserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { UserDataSyncStoreManagementServiceChannelClient } from 'vs/platform/userDataSync/common/userDataSyncIpc'; - -class UserDataSyncStoreManagementService extends AbstractUserDataSyncStoreManagementService implements IUserDataSyncStoreManagementService { - - private readonly channelClient: UserDataSyncStoreManagementServiceChannelClient; - - constructor( - @IProductService productService: IProductService, - @IConfigurationService configurationService: IConfigurationService, - @IStorageService storageService: IStorageService, - @ISharedProcessService sharedProcessService: ISharedProcessService, - ) { - super(productService, configurationService, storageService); - this.channelClient = this._register(new UserDataSyncStoreManagementServiceChannelClient(sharedProcessService.getChannel('userDataSyncStoreManagement'))); - this._register(this.channelClient.onDidChangeUserDataSyncStore(() => this.updateUserDataSyncStore())); - } - - async switch(type: UserDataSyncStoreType): Promise { - return this.channelClient.switch(type); - } - - async getPreviousUserDataSyncStore(): Promise { - return this.channelClient.getPreviousUserDataSyncStore(); - } - -} - -registerSingleton(IUserDataSyncStoreManagementService, UserDataSyncStoreManagementService, InstantiationType.Delayed); diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 7f311e34818..28715bf1613 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -65,10 +65,7 @@ import 'vs/workbench/services/localization/electron-sandbox/localeService'; import 'vs/workbench/services/extensions/electron-sandbox/extensionsScannerService'; import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService'; import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService'; -import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService'; import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService'; -import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncAccountService'; -import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncStoreManagementService'; import 'vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService'; import 'vs/workbench/services/timer/electron-sandbox/timerService'; import 'vs/workbench/services/environment/electron-sandbox/shellEnvironmentService'; From 24899b4de9f69639680dc53333a1d42a0bf5c338 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 11 Jan 2024 16:03:56 +0100 Subject: [PATCH 0234/1897] zoom - use codicon to convey zoom status (#202239) --- src/vs/workbench/electron-sandbox/window.ts | 24 +++++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 1e9ca9d0cd7..40705a7d9aa 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -1066,7 +1066,16 @@ export class NativeWindow extends BaseWindow { const targetWindow = getWindowById(targetWindowId); const entry = this.mapWindowIdToResetZoomStatusEntry.get(targetWindowId); if (entry && targetWindow) { - entry.updateResetZoomEntry(getZoomLevel(targetWindow.window) !== this.configuredWindowZoomLevel); + const currentZoomLevel = getZoomLevel(targetWindow.window); + + let text: string | undefined = undefined; + if (currentZoomLevel < this.configuredWindowZoomLevel) { + text = localize('resetZoomOut', "$(zoom-out)"); + } else if (currentZoomLevel > this.configuredWindowZoomLevel) { + text = localize('resetZoomIn', "$(zoom-in)"); + } + + entry.updateResetZoomEntry(text ?? false); } } @@ -1109,14 +1118,15 @@ class ResetZoomStatusEntry extends Disposable { super(); } - updateResetZoomEntry(visible: boolean): void { - if (visible) { + updateResetZoomEntry(visibleOrText: false | string): void { + if (typeof visibleOrText === 'string') { if (!this.resetZoomStatusEntry.value) { - const text = localize('resetZoom', "Reset Zoom"); + const name = localize('status.resetWindowZoom', "Reset Window Zoom"); this.resetZoomStatusEntry.value = this.statusbarService.addEntry({ - name: localize('status.resetWindowZoom', "Reset Window Zoom"), - text, - ariaLabel: text, + name, + text: visibleOrText, + tooltip: name, + ariaLabel: name, command: 'workbench.action.zoomReset', kind: 'prominent' }, 'status.resetWindowZoom', StatusbarAlignment.RIGHT, 102); From e6e8f301ba1e02614a3d5d9f3df52360d94a0d1a Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 11 Jan 2024 16:38:33 +0100 Subject: [PATCH 0235/1897] Allows navigation_{number} for the multi diff context menu --- .../widget/multiDiffEditorWidget/diffEditorItemTemplate.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts index e81aa6f51b9..451b5bbe270 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts @@ -135,7 +135,8 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< actionRunner: this._register(new ActionRunnerWithContext(() => (this._viewModel.get()?.diffEditorViewModel?.model.modified.uri))), menuOptions: { shouldForwardArgs: true, - } + }, + toolbarOptions: { primaryGroup: g => g.startsWith('navigation') }, })); } From a9d3d00283f6aba6a220c4c0ed8078a2ff13c388 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 11 Jan 2024 08:03:07 -0800 Subject: [PATCH 0236/1897] add speech timeout --- .../actions/voiceTerminalActions.ts | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceTerminalActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceTerminalActions.ts index ec695b30894..58404b0ff15 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceTerminalActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceTerminalActions.ts @@ -3,15 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { Action2 } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { AccessibilityVoiceSettingId, SpeechTimeoutDefault } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { isNumber } from 'vs/base/common/types'; export class StartTerminalSpeechToTextAction extends Action2 { @@ -59,6 +63,7 @@ export class StopTerminalSpeechToTextAction extends Action2 { } class VoiceSession extends Disposable { + private _input: string = ''; private static instance: VoiceSession | undefined = undefined; static getInstance(instantiationService: IInstantiationService): VoiceSession { if (!VoiceSession.instance) { @@ -71,7 +76,8 @@ class VoiceSession extends Disposable { private _disposables = new DisposableStore(); constructor( @ISpeechService private readonly _speechService: ISpeechService, - @ITerminalService readonly _terminalService: ITerminalService + @ITerminalService readonly _terminalService: ITerminalService, + @IConfigurationService readonly configurationService: IConfigurationService ) { super(); this._register(this._terminalService.onDidChangeActiveInstance(() => this.stop())); @@ -79,6 +85,14 @@ class VoiceSession extends Disposable { } start(): void { this.stop(); + let voiceTimeout = this.configurationService.getValue(AccessibilityVoiceSettingId.SpeechTimeout); + if (!isNumber(voiceTimeout) || voiceTimeout < 0) { + voiceTimeout = SpeechTimeoutDefault; + } + const acceptTranscriptionScheduler = this._disposables.add(new RunOnceScheduler(() => { + this._terminalService.activeInstance?.sendText(this._input, false); + this.stop(); + }, voiceTimeout)); this._cancellationTokenSource = new CancellationTokenSource(); const session = this._disposables.add(this._speechService.createSpeechToTextSession(this._cancellationTokenSource!.token)); this._disposables.add(session.onDidChange((e) => { @@ -89,15 +103,21 @@ class VoiceSession extends Disposable { case SpeechToTextStatus.Started: break; case SpeechToTextStatus.Recognizing: - // TODO: start audio cue, show in status bar + // TODO: start audio cue, show in terminal by the cursor + if (voiceTimeout > 0) { + acceptTranscriptionScheduler.cancel(); + } break; case SpeechToTextStatus.Recognized: if (e.text) { - this._terminalService.activeInstance?.sendText(e.text, false); + this._input = [this._input, e.text].join(' '); + } + if (voiceTimeout > 0) { + acceptTranscriptionScheduler.schedule(); } break; case SpeechToTextStatus.Stopped: - // TODO: stop audio cue, hide in status bar + // TODO: stop audio cue, hide in terminal by the cursor this.stop(); break; } @@ -106,5 +126,6 @@ class VoiceSession extends Disposable { stop(): void { this._cancellationTokenSource?.cancel(); this._disposables.dispose(); + this._input = ''; } } From 273e4b0d7bd19bf8b9383d8de2e6fd01a3883852 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 11 Jan 2024 17:21:05 +0100 Subject: [PATCH 0237/1897] SCM - Add scm.showChangesSummary setting (#202256) * SCM - make "All Changes" node part of the API and add setting * Cleanup configuration change listeners * More settings cleanup --- extensions/git/src/historyProvider.ts | 17 ++- src/vs/workbench/api/browser/mainThreadSCM.ts | 5 + .../workbench/api/common/extHost.protocol.ts | 1 + src/vs/workbench/api/common/extHostSCM.ts | 10 ++ .../contrib/scm/browser/scm.contribution.ts | 5 + .../contrib/scm/browser/scmViewPane.ts | 115 ++++++++++-------- .../workbench/contrib/scm/common/history.ts | 1 + .../vscode.proposed.scmHistoryProvider.d.ts | 1 + 8 files changed, 100 insertions(+), 55 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index cd32a38c1aa..b5c735586cc 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ -import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, l10n, LogOutputChannel } from 'vscode'; +import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel } from 'vscode'; import { Repository, Resource } from './repository'; import { IDisposable, filterEvent } from './util'; import { toGitUri } from './uri'; @@ -84,11 +84,6 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec const historyItems: SourceControlHistoryItem[] = []; const commits = await this.repository.log({ range: `${refParentId}..${refId}`, shortStats: true, sortByAuthorDate: true }); - if (commits.length >= 2) { - const allChanges = await this.repository.diffBetweenShortStat(refParentId, refId); - historyItems.push({ id: refId, parentIds: [refParentId], icon: new ThemeIcon('files'), label: l10n.t('All Changes'), statistics: allChanges }); - } - await ensureEmojis(); historyItems.push(...commits.map(commit => { @@ -109,6 +104,16 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return historyItems; } + async provideHistoryItemSummary(historyItemId: string, historyItemParentId: string | undefined): Promise { + if (!historyItemParentId) { + const commit = await this.repository.getCommit(historyItemId); + historyItemParentId = commit.parents.length > 0 ? commit.parents[0] : `${historyItemId}^`; + } + + const allChanges = await this.repository.diffBetweenShortStat(historyItemParentId, historyItemId); + return { id: historyItemId, parentIds: [historyItemParentId], label: '', statistics: allChanges }; + } + async provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise { if (!historyItemParentId) { const commit = await this.repository.getCommit(historyItemId); diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 6e4d467c126..81a43a35387 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -192,6 +192,11 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider { return historyItems?.map(historyItem => ({ ...historyItem, icon: getIconFromIconDto(historyItem.icon) })); } + async provideHistoryItemSummary(historyItemId: string, historyItemParentId: string | undefined): Promise { + const historyItem = await this.proxy.$provideHistoryItemSummary(this.handle, historyItemId, historyItemParentId, CancellationToken.None); + return historyItem ? { ...historyItem, icon: getIconFromIconDto(historyItem.icon) } : undefined; + } + async provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise { const changes = await this.proxy.$provideHistoryItemChanges(this.handle, historyItemId, historyItemParentId, CancellationToken.None); return changes?.map(change => ({ diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 1520b5afe25..346c1ddd264 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2224,6 +2224,7 @@ export interface ExtHostSCMShape { $validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string | IMarkdownString, number] | undefined>; $setSelectedSourceControl(selectedSourceControlHandle: number | undefined): Promise; $provideHistoryItems(sourceControlHandle: number, historyItemGroupId: string, options: any, token: CancellationToken): Promise; + $provideHistoryItemSummary(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise; $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise; $resolveHistoryItemGroupBase(sourceControlHandle: number, historyItemGroupId: string, token: CancellationToken): Promise; $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined>; diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 5576c0be0cf..3e40a0917ae 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -977,6 +977,16 @@ export class ExtHostSCM implements ExtHostSCMShape { return historyItems?.map(item => ({ ...item, icon: getHistoryItemIconDto(item) })) ?? undefined; } + async $provideHistoryItemSummary(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise { + const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; + if (typeof historyProvider?.provideHistoryItemSummary !== 'function') { + return undefined; + } + + const historyItem = await historyProvider.provideHistoryItemSummary(historyItemId, historyItemParentId, token); + return historyItem ? { ...historyItem, icon: getHistoryItemIconDto(historyItem) } : undefined; + } + async $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise { const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; return await historyProvider?.provideHistoryItemChanges(historyItemId, historyItemParentId, token) ?? undefined; diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index f5cf500d5be..8fae0d843b8 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -321,6 +321,11 @@ Registry.as(ConfigurationExtensions.Configuration).regis ], description: localize('scm.showOutgoingChanges', "Controls whether outgoing changes are shown in the Source Control view."), default: 'auto' + }, + 'scm.showChangesSummary': { + type: 'boolean', + description: localize('scm.showChangesSummary', "Controls whether the All Changes entry is shown for incoming/outgoing changes in the Source Control view."), + default: true } } }); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 058a7572889..73ea9f1095f 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -26,7 +26,7 @@ import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar import { IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService'; import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, getActionViewItemProvider, isSCMActionButton, isSCMViewService, isSCMHistoryItemGroupTreeElement, isSCMHistoryItemTreeElement, isSCMHistoryItemChangeTreeElement, toDiffEditorArguments, isSCMResourceNode, isSCMHistoryItemChangeNode, isSCMViewSeparator } from './util'; import { WorkbenchCompressibleAsyncDataTree, IOpenEvent } from 'vs/platform/list/browser/listService'; -import { IConfigurationService, ConfigurationTarget, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { disposableTimeout, Sequencer, ThrottledDelayer, Throttler } from 'vs/base/common/async'; import { ITreeNode, ITreeFilter, ITreeSorter, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { ResourceTree, IResourceNode } from 'vs/base/common/resourceTree'; @@ -2525,18 +2525,6 @@ export class SCMViewPane extends ViewPane { private readonly _onDidChangeViewSortKey = new Emitter(); readonly onDidChangeViewSortKey = this._onDidChangeViewSortKey.event; - private _showActionButton = false; - get showActionButton(): boolean { return this._showActionButton; } - - private _alwaysShowRepositories = false; - get alwaysShowRepositories(): boolean { return this._alwaysShowRepositories; } - - private _showIncomingChanges: ShowChangesSetting | undefined; - get showIncomingChanges(): ShowChangesSetting { return this._showIncomingChanges ?? 'never'; } - - private _showOutgoingChanges: ShowChangesSetting | undefined; - get showOutgoingChanges(): ShowChangesSetting { return this._showOutgoingChanges ?? 'never'; } - private readonly items = new DisposableMap(); private readonly visibilityDisposables = new DisposableStore(); @@ -2663,28 +2651,25 @@ export class SCMViewPane extends ViewPane { if (visible) { await this.tree.setInput(this.scmViewService, viewState); - const onDidChangeConfiguration = (e?: IConfigurationChangeEvent) => { - if (!e || - e.affectsConfiguration('scm.showActionButton') || - e.affectsConfiguration('scm.alwaysShowRepositories') || - e.affectsConfiguration('scm.showIncomingChanges') || - e.affectsConfiguration('scm.showOutgoingChanges') || - e.affectsConfiguration('scm.inputMinLineCount') || - e.affectsConfiguration('scm.inputMaxLineCount')) { - this._showActionButton = this.configurationService.getValue('scm.showActionButton'); - this._alwaysShowRepositories = this.configurationService.getValue('scm.alwaysShowRepositories'); - this._showIncomingChanges = this.configurationService.getValue('scm.showIncomingChanges'); - this._showOutgoingChanges = this.configurationService.getValue('scm.showOutgoingChanges'); - - if (e?.affectsConfiguration('scm.alwaysShowRepositories')) { - this.updateActions(); - } - + Event.filter(this.configurationService.onDidChangeConfiguration, + e => + e.affectsConfiguration('scm.alwaysShowRepositories'), + this.visibilityDisposables) + (() => { + this.updateActions(); this.updateChildren(); - } - }; - this.configurationService.onDidChangeConfiguration(onDidChangeConfiguration, this, this.visibilityDisposables); - onDidChangeConfiguration(); + }, this, this.visibilityDisposables); + + Event.filter(this.configurationService.onDidChangeConfiguration, + e => + e.affectsConfiguration('scm.inputMinLineCount') || + e.affectsConfiguration('scm.inputMaxLineCount') || + e.affectsConfiguration('scm.showActionButton') || + e.affectsConfiguration('scm.showChangesSummary') || + e.affectsConfiguration('scm.showIncomingChanges') || + e.affectsConfiguration('scm.showOutgoingChanges'), + this.visibilityDisposables) + (() => this.updateChildren(), this, this.visibilityDisposables); // Add visible repositories this.editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.visibilityDisposables); @@ -2728,7 +2713,7 @@ export class SCMViewPane extends ViewPane { historyItemActionRunner.onWillRun(() => this.tree.domFocus(), this, this.disposables); this.disposables.add(historyItemActionRunner); - const treeDataSource = this.instantiationService.createInstance(SCMTreeDataSource, () => this.viewMode, () => this.alwaysShowRepositories, () => this.showActionButton, () => this.showIncomingChanges, () => this.showOutgoingChanges); + const treeDataSource = this.instantiationService.createInstance(SCMTreeDataSource, () => this.viewMode); this.disposables.add(treeDataSource); this.tree = this.instantiationService.createInstance( @@ -3113,7 +3098,9 @@ export class SCMViewPane extends ViewPane { } private updateScmProviderContextKeys(): void { - if (!this.alwaysShowRepositories && this.items.size === 1) { + const alwaysShowRepositories = this.configurationService.getValue('scm.alwaysShowRepositories'); + + if (!alwaysShowRepositories && this.items.size === 1) { const provider = Iterable.first(this.items.keys())!.provider; this.scmProviderContextKey.set(provider.contextValue); this.scmProviderRootUriContextKey.set(provider.rootUri?.toString()); @@ -3197,13 +3184,16 @@ class SCMTreeDataSource implements IAsyncDataSource ViewMode, - private readonly alwaysShowRepositories: () => boolean, - private readonly showActionButton: () => boolean, - private readonly showIncomingChanges: () => ShowChangesSetting, - private readonly showOutgoingChanges: () => ShowChangesSetting, + @IConfigurationService private readonly configurationService: IConfigurationService, @ISCMViewService private readonly scmViewService: ISCMViewService, @IUriIdentityService private uriIdentityService: IUriIdentityService, ) { + const onDidChangeConfiguration = Event.filter( + this.configurationService.onDidChangeConfiguration, + e => e.affectsConfiguration('scm.showChangesSummary'), + this.disposables); + this.disposables.add(onDidChangeConfiguration(() => this.historyProviderCache.clear())); + this.scmViewService.onDidChangeVisibleRepositories(this.onDidChangeVisibleRepositories, this, this.disposables); this.onDidChangeVisibleRepositories({ added: this.scmViewService.visibleRepositories, removed: Iterable.empty() }); } @@ -3237,8 +3227,8 @@ class SCMTreeDataSource implements IAsyncDataSource> { + const { alwaysShowRepositories, showActionButton, showIncomingChanges, showOutgoingChanges } = this.getConfiguration(); const repositoryCount = this.scmViewService.visibleRepositories.length; - const alwaysShowRepositories = this.alwaysShowRepositories(); if (isSCMViewService(inputOrElement) && (repositoryCount > 1 || alwaysShowRepositories)) { return this.scmViewService.visibleRepositories; @@ -3248,7 +3238,6 @@ class SCMTreeDataSource implements IAsyncDataSource { + const { showIncomingChanges, showOutgoingChanges } = this.getConfiguration(); + const scmProvider = element.provider; const historyProvider = scmProvider.historyProvider; const currentHistoryItemGroup = historyProvider?.currentHistoryItemGroup; - if (!historyProvider || !currentHistoryItemGroup || (this.showIncomingChanges() === 'never' && this.showOutgoingChanges() === 'never')) { + if (!historyProvider || !currentHistoryItemGroup || (showIncomingChanges === 'never' && showOutgoingChanges === 'never')) { return []; } @@ -3347,8 +3338,8 @@ class SCMTreeDataSource implements IAsyncDataSource 0))) { + (showIncomingChanges === 'always' || + (showIncomingChanges === 'auto' && (historyItemGroupDetails.incoming.count ?? 0) > 0))) { children.push({ ...historyItemGroupDetails.incoming, ariaLabel: localize('incomingChangesAriaLabel', "Incoming changes from {0}", historyItemGroupDetails.incoming.label), @@ -3359,8 +3350,8 @@ class SCMTreeDataSource implements IAsyncDataSource 0))) { + (showOutgoingChanges === 'always' || + (showOutgoingChanges === 'auto' && (historyItemGroupDetails.outgoing.count ?? 0) > 0))) { children.push({ ...historyItemGroupDetails.outgoing, ariaLabel: localize('outgoingChangesAriaLabel', "Outgoing changes from {0}", historyItemGroupDetails.outgoing.label), @@ -3386,6 +3377,16 @@ class SCMTreeDataSource implements IAsyncDataSource= 2) { + const allChanges = await historyProvider.provideHistoryItemSummary(element.id, element.ancestor); + if (allChanges) { + historyItems.splice(0, 0, { ...allChanges, icon: allChanges.icon ?? Codicon.files, label: localize('allChanges', "All Changes") }); + } + } + this.historyProviderCache.set(repository, { ...historyProviderCacheEntry, historyItems: historyItemsMap.set(element.id, historyItems) @@ -3481,6 +3482,22 @@ class SCMTreeDataSource implements IAsyncDataSource('scm.alwaysShowRepositories'), + showActionButton: this.configurationService.getValue('scm.showActionButton'), + showChangesSummary: this.configurationService.getValue('scm.showChangesSummary'), + showIncomingChanges: this.configurationService.getValue('scm.showIncomingChanges'), + showOutgoingChanges: this.configurationService.getValue('scm.showOutgoingChanges') + }; + } + private onDidChangeVisibleRepositories({ added, removed }: ISCMViewVisibleRepositoryChangeEvent): void { // Added repositories for (const repository of added) { diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index db2e8ed4bf7..eaf365e1972 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -21,6 +21,7 @@ export interface ISCMHistoryProvider { set currentHistoryItemGroup(historyItemGroup: ISCMHistoryItemGroup | undefined); provideHistoryItems(historyItemGroupId: string, options: ISCMHistoryOptions): Promise; + provideHistoryItemSummary(historyItemId: string, historyItemParentId: string | undefined): Promise; provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise; resolveHistoryItemGroupBase(historyItemGroupId: string): Promise; resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined>; diff --git a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts index 8dd3dd899df..b030e44ee98 100644 --- a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts @@ -25,6 +25,7 @@ declare module 'vscode' { // onDidChangeHistoryItemGroups: Event; provideHistoryItems(historyItemGroupId: string, options: SourceControlHistoryOptions, token: CancellationToken): ProviderResult; + provideHistoryItemSummary?(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): ProviderResult; provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): ProviderResult; resolveHistoryItemGroupBase(historyItemGroupId: string, token: CancellationToken): ProviderResult; From 97f6e8e25579d57acbe4316deefa998dee1cf8b6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 11 Jan 2024 09:30:11 -0800 Subject: [PATCH 0238/1897] rearrange, work on decoration --- .../electron-sandbox/voice.contribution.ts | 2 +- .../contrib/terminal/browser/terminal.ts | 5 +- .../terminal/browser/terminalInstance.ts | 6 +- .../browser}/voiceTerminalActions.ts | 63 +++++++++++++++---- 4 files changed, 61 insertions(+), 15 deletions(-) rename src/vs/workbench/contrib/{chat/electron-sandbox/actions => terminal/browser}/voiceTerminalActions.ts (72%) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/voice.contribution.ts b/src/vs/workbench/contrib/chat/electron-sandbox/voice.contribution.ts index 2eb682451ef..89f8f793d48 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/voice.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/voice.contribution.ts @@ -5,7 +5,7 @@ import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; import { registerAction2 } from 'vs/platform/actions/common/actions'; -import { StartTerminalSpeechToTextAction, StopTerminalSpeechToTextAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceTerminalActions'; +import { StartTerminalSpeechToTextAction, StopTerminalSpeechToTextAction } from 'vs/workbench/contrib/terminal/browser/voiceTerminalActions'; registerAction2(StartVoiceChatAction); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index f5dfc7d63a0..540bf204153 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -23,7 +23,7 @@ import { ITerminalStatusList } from 'vs/workbench/contrib/terminal/browser/termi import { XtermTerminal } from 'vs/workbench/contrib/terminal/browser/xterm/xtermTerminal'; import { IRegisterContributedProfileArgs, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfiguration, ITerminalFont, ITerminalProcessExtHostProxy, ITerminalProcessInfo } from 'vs/workbench/contrib/terminal/common/terminal'; import { ISimpleSelectedSuggestion } from 'vs/workbench/services/suggest/browser/simpleSuggestWidget'; -import type { IMarker, ITheme, Terminal as RawXtermTerminal } from '@xterm/xterm'; +import type { IMarker, ITheme, IDecoration, IDecorationOptions, Terminal as RawXtermTerminal } from '@xterm/xterm'; import { ScrollPosition } from 'vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { GroupIdentifier } from 'vs/workbench/common/editor'; @@ -792,6 +792,9 @@ export interface ITerminalInstance extends IBaseTerminalInstance { */ registerMarker(): IMarker | undefined; + + registerDecoration(options: IDecorationOptions): IDecoration | undefined; + /** * Adds a marker to the buffer, mapping it to an ID if provided. */ diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 5ac44dc2718..02bcb839769 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -84,7 +84,7 @@ import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/ import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { importAMDNodeModule } from 'vs/amdX'; -import type { IMarker, Terminal as XTermTerminal } from '@xterm/xterm'; +import type { IMarker, IDecorationOptions, IDecoration, Terminal as XTermTerminal } from '@xterm/xterm'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; import { shouldPasteTerminalText } from 'vs/workbench/contrib/terminal/common/terminalClipboard'; @@ -1444,6 +1444,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return this.xterm?.raw.registerMarker(); } + public registerDecoration(options: IDecorationOptions): IDecoration | undefined { + return this.xterm?.raw.registerDecoration(options); + } + public addBufferMarker(properties: IMarkProperties): void { this.capabilities.get(TerminalCapability.BufferMarkDetection)?.addMark(properties); } diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceTerminalActions.ts b/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts similarity index 72% rename from src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceTerminalActions.ts rename to src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts index 58404b0ff15..e728bd61eba 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceTerminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts @@ -16,6 +16,10 @@ import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbe import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { isNumber } from 'vs/base/common/types'; +import type { IDecoration, Terminal } from '@xterm/xterm'; +import { IXtermMarker } from 'vs/platform/terminal/common/capabilities/capabilities'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { Codicon } from 'vs/base/common/codicons'; export class StartTerminalSpeechToTextAction extends Action2 { @@ -35,7 +39,7 @@ export class StartTerminalSpeechToTextAction extends Action2 { async run(accessor: ServicesAccessor): Promise { const instantiationService = accessor.get(IInstantiationService); - VoiceSession.getInstance(instantiationService).start(); + TerminalVoiceSession.getInstance(instantiationService).start(); } } @@ -58,31 +62,40 @@ export class StopTerminalSpeechToTextAction extends Action2 { async run(accessor: ServicesAccessor): Promise { const instantiationService = accessor.get(IInstantiationService); - VoiceSession.getInstance(instantiationService).stop(); + TerminalVoiceSession.getInstance(instantiationService).stop(); } } -class VoiceSession extends Disposable { +export class TerminalVoiceSession extends Disposable { private _input: string = ''; - private static instance: VoiceSession | undefined = undefined; - static getInstance(instantiationService: IInstantiationService): VoiceSession { - if (!VoiceSession.instance) { - VoiceSession.instance = instantiationService.createInstance(VoiceSession); + private _xterm: Terminal | undefined; + private _decoration: IDecoration | undefined; + private _marker: IXtermMarker | undefined; + private static _instance: TerminalVoiceSession | undefined = undefined; + static getInstance(instantiationService: IInstantiationService): TerminalVoiceSession { + if (!TerminalVoiceSession._instance) { + TerminalVoiceSession._instance = instantiationService.createInstance(TerminalVoiceSession); } - return VoiceSession.instance; + return TerminalVoiceSession._instance; } private _cancellationTokenSource: CancellationTokenSource | undefined; private _disposables = new DisposableStore(); constructor( @ISpeechService private readonly _speechService: ISpeechService, @ITerminalService readonly _terminalService: ITerminalService, - @IConfigurationService readonly configurationService: IConfigurationService + @IConfigurationService readonly configurationService: IConfigurationService, + @IInstantiationService readonly _instantationService: IInstantiationService ) { super(); this._register(this._terminalService.onDidChangeActiveInstance(() => this.stop())); this._register(this._terminalService.onDidDisposeInstance(() => this.stop())); } + + activate(terminal: Terminal): void { + this._xterm = terminal; + } + start(): void { this.stop(); let voiceTimeout = this.configurationService.getValue(AccessibilityVoiceSettingId.SpeechTimeout); @@ -102,12 +115,18 @@ class VoiceSession extends Disposable { switch (e.status) { case SpeechToTextStatus.Started: break; - case SpeechToTextStatus.Recognizing: - // TODO: start audio cue, show in terminal by the cursor + case SpeechToTextStatus.Recognizing: { + if (!this._decoration) { + console.log('terminal', this._xterm); + // TODO: start audio cue, show in terminal by the cursor + this._createDecoration(); + } + if (voiceTimeout > 0) { acceptTranscriptionScheduler.cancel(); } break; + } case SpeechToTextStatus.Recognized: if (e.text) { this._input = [this._input, e.text].join(' '); @@ -124,8 +143,28 @@ class VoiceSession extends Disposable { })); } stop(): void { + this._marker?.dispose(); + this._decoration?.dispose(); this._cancellationTokenSource?.cancel(); - this._disposables.dispose(); + this._disposables.clear(); this._input = ''; } + + private _createDecoration(): void { + this._marker = this._terminalService.activeInstance?.registerMarker(); + if (!this._marker) { + return; + } + this._decoration = this._terminalService.activeInstance?.registerDecoration({ + marker: this._marker, + layer: 'top' + }); + this._decoration?.onRender((e: HTMLElement) => { + e.classList.add(...ThemeIcon.asClassNameArray(Codicon.mic)); + e.classList.add('quick-fix'); + }); + console.log(this._decoration?.element); + } } + + From 0c27dae0e63533c070ce862ff706e9ada94fb5a5 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 11 Jan 2024 18:50:25 +0100 Subject: [PATCH 0239/1897] Fixes #202147 --- .../algorithms/diffAlgorithm.ts | 4 +++ .../heuristicSequenceOptimizations.ts | 6 +++- .../advanced.expected.diff.json | 8 ++--- .../fixtures/issue-202147-trimws/1.tst | 11 +++++++ .../fixtures/issue-202147-trimws/2.tst | 13 ++++++++ .../advanced.expected.diff.json | 32 +++++++++++++++++++ .../legacy.expected.diff.json | 27 ++++++++++++++++ .../noisy-move1/advanced.expected.diff.json | 6 +--- 8 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 src/vs/editor/test/node/diffing/fixtures/issue-202147-trimws/1.tst create mode 100644 src/vs/editor/test/node/diffing/fixtures/issue-202147-trimws/2.tst create mode 100644 src/vs/editor/test/node/diffing/fixtures/issue-202147-trimws/advanced.expected.diff.json create mode 100644 src/vs/editor/test/node/diffing/fixtures/issue-202147-trimws/legacy.expected.diff.json diff --git a/src/vs/editor/common/diff/defaultLinesDiffComputer/algorithms/diffAlgorithm.ts b/src/vs/editor/common/diff/defaultLinesDiffComputer/algorithms/diffAlgorithm.ts index 2e0840de625..50fbf66deba 100644 --- a/src/vs/editor/common/diff/defaultLinesDiffComputer/algorithms/diffAlgorithm.ts +++ b/src/vs/editor/common/diff/defaultLinesDiffComputer/algorithms/diffAlgorithm.ts @@ -132,6 +132,10 @@ export class OffsetPair { } return new OffsetPair(this.offset1 + offset, this.offset2 + offset); } + + public equals(other: OffsetPair): boolean { + return this.offset1 === other.offset1 && this.offset2 === other.offset2; + } } export interface ISequence { diff --git a/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts b/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts index 468789d20d0..08ead77d7c6 100644 --- a/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts +++ b/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts @@ -456,7 +456,11 @@ export function removeVeryShortMatchingTextBetweenLongDiffs(sequence1: LinesSlic next ? next.getStarts() : OffsetPair.max, ); const result = newDiff.intersect(availableSpace)!; - newDiffs.push(result); + if (newDiffs.length > 0 && result.getStarts().equals(newDiffs[newDiffs.length - 1].getEndExclusives())) { + newDiffs[newDiffs.length - 1] = newDiffs[newDiffs.length - 1].join(result); + } else { + newDiffs.push(result); + } }); return newDiffs; diff --git a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/advanced.expected.diff.json index 131a397b9a5..f9f33e2481d 100644 --- a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/advanced.expected.diff.json @@ -35,12 +35,8 @@ "modifiedRange": "[22,30 -> 22,38]" }, { - "originalRange": "[26,17 -> 27,9]", - "modifiedRange": "[22,42 -> 22,47]" - }, - { - "originalRange": "[27,9 -> 33,1 EOL]", - "modifiedRange": "[22,47 -> 23,1 EOL]" + "originalRange": "[26,17 -> 33,1 EOL]", + "modifiedRange": "[22,42 -> 23,1 EOL]" } ] }, diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-202147-trimws/1.tst b/src/vs/editor/test/node/diffing/fixtures/issue-202147-trimws/1.tst new file mode 100644 index 00000000000..f2775d89e8e --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-202147-trimws/1.tst @@ -0,0 +1,11 @@ + + + item.row!.domNode.classList.toggle('drop-target', item.dropTarget); + } + + +a +x +a +a +a \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-202147-trimws/2.tst b/src/vs/editor/test/node/diffing/fixtures/issue-202147-trimws/2.tst new file mode 100644 index 00000000000..e8b1fa6b546 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-202147-trimws/2.tst @@ -0,0 +1,13 @@ + + + item.row!.domNode.classList.toggle('drop-target', item.dropTarget !== false); + if (typeof item.dropTarget !== 'boolean') { + item.row!.domNode.classList.toggle('drop-target-start', item.dropTarget.first); + } + } + + +a +x +a +a diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-202147-trimws/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/issue-202147-trimws/advanced.expected.diff.json new file mode 100644 index 00000000000..849deabac0c --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-202147-trimws/advanced.expected.diff.json @@ -0,0 +1,32 @@ +{ + "original": { + "content": "\n\n\t\titem.row!.domNode.classList.toggle('drop-target', item.dropTarget);\n\t}\n\n\na\nx\na\na\na", + "fileName": "./1.tst" + }, + "modified": { + "content": "\n\n\t\titem.row!.domNode.classList.toggle('drop-target', item.dropTarget !== false);\n\t\tif (typeof item.dropTarget !== 'boolean') {\n\t\t\titem.row!.domNode.classList.toggle('drop-target-start', item.dropTarget.first);\n\t\t}\n\t}\n\n\na\nx\na\na\n", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[3,4)", + "modifiedRange": "[3,7)", + "innerChanges": [ + { + "originalRange": "[3,68 -> 4,1]", + "modifiedRange": "[3,68 -> 7,1]" + } + ] + }, + { + "originalRange": "[11,12)", + "modifiedRange": "[14,15)", + "innerChanges": [ + { + "originalRange": "[11,1 -> 11,2 EOL]", + "modifiedRange": "[14,1 -> 14,1 EOL]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-202147-trimws/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/issue-202147-trimws/legacy.expected.diff.json new file mode 100644 index 00000000000..740c1c6c472 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-202147-trimws/legacy.expected.diff.json @@ -0,0 +1,27 @@ +{ + "original": { + "content": "\n\n\t\titem.row!.domNode.classList.toggle('drop-target', item.dropTarget);\n\t}\n\n\na\nx\na\na\na", + "fileName": "./1.tst" + }, + "modified": { + "content": "\n\n\t\titem.row!.domNode.classList.toggle('drop-target', item.dropTarget !== false);\n\t\tif (typeof item.dropTarget !== 'boolean') {\n\t\t\titem.row!.domNode.classList.toggle('drop-target-start', item.dropTarget.first);\n\t\t}\n\t}\n\n\na\nx\na\na\n", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[3,4)", + "modifiedRange": "[3,7)", + "innerChanges": [ + { + "originalRange": "[3,68 -> 3,70 EOL]", + "modifiedRange": "[3,68 -> 6,4 EOL]" + } + ] + }, + { + "originalRange": "[11,12)", + "modifiedRange": "[14,15)", + "innerChanges": null + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/noisy-move1/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/noisy-move1/advanced.expected.diff.json index 6fd1598ffe3..79396a409c3 100644 --- a/src/vs/editor/test/node/diffing/fixtures/noisy-move1/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/noisy-move1/advanced.expected.diff.json @@ -149,12 +149,8 @@ "modifiedRange": "[454,30 -> 454,37]" }, { - "originalRange": "[346,54 -> 350,1]", + "originalRange": "[346,54 -> 351,1]", "modifiedRange": "[454,43 -> 455,1]" - }, - { - "originalRange": "[350,1 -> 351,1]", - "modifiedRange": "[455,1 -> 455,1]" } ] }, From 4d2d0582c600c81a6afec20f9d0605c62f2115a3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 11 Jan 2024 19:12:09 +0100 Subject: [PATCH 0240/1897] Fixes #199328 --- .../widget/diffEditor/diffEditorViewModel.ts | 22 ++++++++++++------- .../features/hideUnchangedRegionsFeature.ts | 14 ++++++------ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts index ab4d3ab9eea..ac9d816bc1d 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts @@ -310,27 +310,27 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo })); } - public ensureModifiedLineIsVisible(lineNumber: number, tx: ITransaction | undefined): void { + public ensureModifiedLineIsVisible(lineNumber: number, preference: RevealPreference, tx: ITransaction | undefined): void { if (this.diff.get()?.mappings.length === 0) { return; } const unchangedRegions = this._unchangedRegions.get()?.regions || []; for (const r of unchangedRegions) { if (r.getHiddenModifiedRange(undefined).contains(lineNumber)) { - r.showModifiedLine(lineNumber, tx); + r.showModifiedLine(lineNumber, preference, tx); return; } } } - public ensureOriginalLineIsVisible(lineNumber: number, tx: ITransaction | undefined): void { + public ensureOriginalLineIsVisible(lineNumber: number, preference: RevealPreference, tx: ITransaction | undefined): void { if (this.diff.get()?.mappings.length === 0) { return; } const unchangedRegions = this._unchangedRegions.get()?.regions || []; for (const r of unchangedRegions) { if (r.getHiddenOriginalRange(undefined).contains(lineNumber)) { - r.showOriginalLine(lineNumber, tx); + r.showOriginalLine(lineNumber, preference, tx); return; } } @@ -589,20 +589,20 @@ export class UnchangedRegion { this._visibleLineCountBottom.set(this.lineCount - this._visibleLineCountTop.get(), tx); } - public showModifiedLine(lineNumber: number, tx: ITransaction | undefined): void { + public showModifiedLine(lineNumber: number, preference: RevealPreference, tx: ITransaction | undefined): void { const top = lineNumber + 1 - (this.modifiedLineNumber + this._visibleLineCountTop.get()); const bottom = (this.modifiedLineNumber - this._visibleLineCountBottom.get() + this.lineCount) - lineNumber; - if (top < bottom) { + if (preference === RevealPreference.FromCloserSide && top < bottom || preference === RevealPreference.FromTop) { this._visibleLineCountTop.set(this._visibleLineCountTop.get() + top, tx); } else { this._visibleLineCountBottom.set(this._visibleLineCountBottom.get() + bottom, tx); } } - public showOriginalLine(lineNumber: number, tx: ITransaction | undefined): void { + public showOriginalLine(lineNumber: number, preference: RevealPreference, tx: ITransaction | undefined): void { const top = lineNumber - this.originalLineNumber; const bottom = (this.originalLineNumber + this.lineCount) - lineNumber; - if (top < bottom) { + if (preference === RevealPreference.FromCloserSide && top < bottom || preference === RevealPreference.FromTop) { this._visibleLineCountTop.set(Math.min(this._visibleLineCountTop.get() + bottom - top, this.getMaxVisibleLineCountTop()), tx); } else { this._visibleLineCountBottom.set(Math.min(this._visibleLineCountBottom.get() + top - bottom, this.getMaxVisibleLineCountBottom()), tx); @@ -623,6 +623,12 @@ export class UnchangedRegion { } } +export const enum RevealPreference { + FromCloserSide, + FromTop, + FromBottom, +} + function applyOriginalEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], originalTextModel: ITextModel, modifiedTextModel: ITextModel): IDocumentDiff | undefined { return undefined; /* diff --git a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts index dd83299f65d..fb45a24f3c0 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts @@ -15,7 +15,7 @@ import { isDefined } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/components/diffEditorEditors'; import { DiffEditorOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorOptions'; -import { DiffEditorViewModel, UnchangedRegion } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; +import { DiffEditorViewModel, RevealPreference, UnchangedRegion } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; import { IObservableViewZone, PlaceholderViewZone, ViewZoneOverlayWidget, applyObservableDecorations, applyStyle } from 'vs/editor/browser/widget/diffEditor/utils'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { LineRange } from 'vs/editor/common/core/lineRange'; @@ -63,8 +63,8 @@ export class HideUnchangedRegionsFeature extends Disposable { const m = this._diffModel.get(); transaction(tx => { for (const s of this._editors.original.getSelections() || []) { - m?.ensureOriginalLineIsVisible(s.getStartPosition().lineNumber, tx); - m?.ensureOriginalLineIsVisible(s.getEndPosition().lineNumber, tx); + m?.ensureOriginalLineIsVisible(s.getStartPosition().lineNumber, RevealPreference.FromCloserSide, tx); + m?.ensureOriginalLineIsVisible(s.getEndPosition().lineNumber, RevealPreference.FromCloserSide, tx); } }); } @@ -75,8 +75,8 @@ export class HideUnchangedRegionsFeature extends Disposable { const m = this._diffModel.get(); transaction(tx => { for (const s of this._editors.modified.getSelections() || []) { - m?.ensureModifiedLineIsVisible(s.getStartPosition().lineNumber, tx); - m?.ensureModifiedLineIsVisible(s.getEndPosition().lineNumber, tx); + m?.ensureModifiedLineIsVisible(s.getStartPosition().lineNumber, RevealPreference.FromCloserSide, tx); + m?.ensureModifiedLineIsVisible(s.getEndPosition().lineNumber, RevealPreference.FromCloserSide, tx); } }); } @@ -116,7 +116,7 @@ export class HideUnchangedRegionsFeature extends Disposable { r.originalUnchangedRange, !sideBySide, modifiedOutlineSource, - l => this._diffModel.get()!.ensureModifiedLineIsVisible(l, undefined), + l => this._diffModel.get()!.ensureModifiedLineIsVisible(l, RevealPreference.FromBottom, undefined), this._options, )); } @@ -131,7 +131,7 @@ export class HideUnchangedRegionsFeature extends Disposable { r.modifiedUnchangedRange, false, modifiedOutlineSource, - l => this._diffModel.get()!.ensureModifiedLineIsVisible(l, undefined), + l => this._diffModel.get()!.ensureModifiedLineIsVisible(l, RevealPreference.FromBottom, undefined), this._options, )); } From e9a5fab224539010fdcdbce881d00f221d314322 Mon Sep 17 00:00:00 2001 From: Ajay Kumbhare Date: Fri, 12 Jan 2024 00:01:19 +0530 Subject: [PATCH 0241/1897] fix: terminal renaming not functioning as expected in editor area #202267 --- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 5febebadd1b..3517feca516 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -1902,7 +1902,9 @@ async function focusActiveTerminal(instance: ITerminalInstance, c: ITerminalServ async function renameWithQuickPick(c: ITerminalServicesCollection, accessor: ServicesAccessor, resource?: unknown) { let instance: ITerminalInstance | undefined = resource as ITerminalInstance; - if (!instance) { + // Check if the 'instance' does not exist or if 'instance.rename' is not defined + if (!instance || !instance?.rename) { + // If not, obtain the resource instance using 'getResourceOrActiveInstance' instance = getResourceOrActiveInstance(c, resource); } From 5b684a116229f10acc75bd39bb4bbdd51decb74a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 11 Jan 2024 20:30:41 +0100 Subject: [PATCH 0242/1897] auto save - allow per-resource or editor override (microsoft/vscode-copilot#3414) (#202276) --- .../browser/parts/editor/editorAutoSave.ts | 46 +++++++++--------- .../common/filesConfigurationService.ts | 48 +++++++++++++++++-- .../test/common/workbenchTestServices.ts | 2 + 3 files changed, 71 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts index 89d1bdb1c16..26f11a6a224 100644 --- a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts +++ b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts @@ -29,9 +29,9 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution private lastActiveGroupId: GroupIdentifier | undefined = undefined; private lastActiveEditorControlDisposable = this._register(new DisposableStore()); - // Auto save: waiting on errors to clear - private readonly waitingOnErrorAutoSaveWorkingCopies = new ResourceMap<{ readonly workingCopy: IWorkingCopy; readonly reason: SaveReason }>(resource => this.uriIdentityService.extUri.getComparisonKey(resource)); - private readonly waitingOnErrorAutoSaveEditors = new ResourceMap<{ readonly editor: IEditorIdentifier; readonly reason: SaveReason }>(resource => this.uriIdentityService.extUri.getComparisonKey(resource)); + // Auto save: waiting on specific condition + private readonly waitingOnConditionAutoSaveWorkingCopies = new ResourceMap<{ readonly workingCopy: IWorkingCopy; readonly reason: SaveReason; condition: AutoSaveDisabledReason }>(resource => this.uriIdentityService.extUri.getComparisonKey(resource)); + private readonly waitingOnConditionAutoSaveEditors = new ResourceMap<{ readonly editor: IEditorIdentifier; readonly reason: SaveReason; condition: AutoSaveDisabledReason }>(resource => this.uriIdentityService.extUri.getComparisonKey(resource)); constructor( @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @@ -65,38 +65,40 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution this._register(this.workingCopyService.onDidChangeDirty(workingCopy => this.onDidChangeDirty(workingCopy))); this._register(this.workingCopyService.onDidChangeContent(workingCopy => this.onDidChangeContent(workingCopy))); - // Marker events - this._register(this.markerService.onMarkerChanged(e => this.onMarkerChanged(e))); + // Condition changes + this._register(this.markerService.onMarkerChanged(e => this.onConditionChanged(e, AutoSaveDisabledReason.ERRORS))); + this._register(this.filesConfigurationService.onDidChangeAutoSaveDisabled(resource => this.onConditionChanged([resource], AutoSaveDisabledReason.DISABLED))); } - private onMarkerChanged(resources: readonly URI[]): void { + private onConditionChanged(resources: readonly URI[], condition: AutoSaveDisabledReason.ERRORS | AutoSaveDisabledReason.DISABLED): void { for (const resource of resources) { // Waiting working copies - const workingCopyResult = this.waitingOnErrorAutoSaveWorkingCopies.get(resource); - if (workingCopyResult) { + const workingCopyResult = this.waitingOnConditionAutoSaveWorkingCopies.get(resource); + if (workingCopyResult?.condition === condition) { if ( workingCopyResult.workingCopy.isDirty() && this.filesConfigurationService.getAutoSaveMode(workingCopyResult.workingCopy.resource).mode !== AutoSaveMode.OFF ) { this.discardAutoSave(workingCopyResult.workingCopy); - this.logService.info(`[editor auto save] running auto save from marker change event`, workingCopyResult.workingCopy.resource.toString(), workingCopyResult.workingCopy.typeId); + this.logService.info(`[editor auto save] running auto save from condition change event`, workingCopyResult.workingCopy.resource.toString(), workingCopyResult.workingCopy.typeId); workingCopyResult.workingCopy.save({ reason: workingCopyResult.reason }); } } // Waiting editors else { - const editorResult = this.waitingOnErrorAutoSaveEditors.get(resource); + const editorResult = this.waitingOnConditionAutoSaveEditors.get(resource); if ( - !editorResult?.editor.editor.isDisposed() && - editorResult?.editor.editor.isDirty() && + editorResult?.condition === condition && + !editorResult.editor.editor.isDisposed() && + editorResult.editor.editor.isDirty() && this.filesConfigurationService.getAutoSaveMode(editorResult.editor.editor).mode !== AutoSaveMode.OFF ) { - this.waitingOnErrorAutoSaveEditors.delete(resource); + this.waitingOnConditionAutoSaveEditors.delete(resource); - this.logService.info(`[editor auto save] running auto save from marker change event with reason ${editorResult.reason}`); + this.logService.info(`[editor auto save] running auto save from condition change event with reason ${editorResult.reason}`); this.editorService.save(editorResult.editor, { reason: editorResult.reason }); } } @@ -158,8 +160,8 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution this.logService.trace(`[editor auto save] triggering auto save with reason ${reason}`); this.editorService.save(editorIdentifier, { reason }); } - } else if (autoSaveMode.reason === AutoSaveDisabledReason.ERRORS && editorIdentifier.editor.resource) { - this.waitingOnErrorAutoSaveEditors.set(editorIdentifier.editor.resource, { editor: editorIdentifier, reason }); + } else if (editorIdentifier.editor.resource && (autoSaveMode.reason === AutoSaveDisabledReason.ERRORS || autoSaveMode.reason === AutoSaveDisabledReason.DISABLED)) { + this.waitingOnConditionAutoSaveEditors.set(editorIdentifier.editor.resource, { editor: editorIdentifier, reason, condition: autoSaveMode.reason }); } } else { this.saveAllDirtyAutoSaveables(reason); @@ -197,8 +199,8 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution const autoSaveMode = this.filesConfigurationService.getAutoSaveMode(workingCopy.resource); if (autoSaveMode.mode !== AutoSaveMode.OFF) { workingCopy.save({ reason }); - } else if (autoSaveMode.reason === AutoSaveDisabledReason.ERRORS) { - this.waitingOnErrorAutoSaveWorkingCopies.set(workingCopy.resource, { workingCopy, reason }); + } else if (autoSaveMode.reason === AutoSaveDisabledReason.ERRORS || autoSaveMode.reason === AutoSaveDisabledReason.DISABLED) { + this.waitingOnConditionAutoSaveWorkingCopies.set(workingCopy.resource, { workingCopy, reason, condition: autoSaveMode.reason }); } } } @@ -257,8 +259,8 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution if (autoSaveMode.mode !== AutoSaveMode.OFF) { this.logService.trace(`[editor auto save] running auto save`, workingCopy.resource.toString(), workingCopy.typeId); workingCopy.save({ reason: SaveReason.AUTO }); - } else if (autoSaveMode.reason === AutoSaveDisabledReason.ERRORS) { - this.waitingOnErrorAutoSaveWorkingCopies.set(workingCopy.resource, { workingCopy, reason: SaveReason.AUTO }); + } else if (autoSaveMode.reason === AutoSaveDisabledReason.ERRORS || autoSaveMode.reason === AutoSaveDisabledReason.DISABLED) { + this.waitingOnConditionAutoSaveWorkingCopies.set(workingCopy.resource, { workingCopy, reason: SaveReason.AUTO, condition: autoSaveMode.reason }); } } }, autoSaveAfterDelay); @@ -275,7 +277,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution dispose(this.scheduledAutoSavesAfterDelay.get(workingCopy)); this.scheduledAutoSavesAfterDelay.delete(workingCopy); - this.waitingOnErrorAutoSaveWorkingCopies.delete(workingCopy.resource); - this.waitingOnErrorAutoSaveEditors.delete(workingCopy.resource); + this.waitingOnConditionAutoSaveWorkingCopies.delete(workingCopy.resource); + this.waitingOnConditionAutoSaveEditors.delete(workingCopy.resource); } } diff --git a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts index aef821fa2dd..ced867e820b 100644 --- a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts +++ b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFilesConfiguration, AutoSaveConfiguration, HotExitConfiguration, FILES_READONLY_INCLUDE_CONFIG, FILES_READONLY_EXCLUDE_CONFIG, IFileStatWithMetadata, IFileService, IBaseFileStat, hasReadonlyCapability, IFilesConfigurationNode } from 'vs/platform/files/common/files'; @@ -57,7 +57,8 @@ export const enum AutoSaveMode { export const enum AutoSaveDisabledReason { SETTINGS = 1, OUT_OF_WORKSPACE, - ERRORS + ERRORS, + DISABLED } export type IAutoSaveMode = IEnabledAutoSaveMode | IDisabledAutoSaveMode; @@ -81,6 +82,8 @@ export interface IFilesConfigurationService { readonly onDidChangeAutoSaveConfiguration: Event; + readonly onDidChangeAutoSaveDisabled: Event; + getAutoSaveConfiguration(resourceOrEditor: EditorInput | URI | undefined): IAutoSaveConfiguration; isShortAutoSaveDelayConfigured(resourceOrEditor: EditorInput | URI | undefined): boolean; @@ -89,6 +92,8 @@ export interface IFilesConfigurationService { toggleAutoSave(): Promise; + disableAutoSave(resourceOrEditor: EditorInput | URI): IDisposable; + //#endregion //#region Configured Readonly @@ -128,6 +133,9 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi private readonly _onDidChangeAutoSaveConfiguration = this._register(new Emitter()); readonly onDidChangeAutoSaveConfiguration = this._onDidChangeAutoSaveConfiguration.event; + private readonly _onDidChangeAutoSaveDisabled = this._register(new Emitter()); + readonly onDidChangeAutoSaveDisabled = this._onDidChangeAutoSaveDisabled.event; + private readonly _onDidChangeFilesAssociation = this._register(new Emitter()); readonly onDidChangeFilesAssociation = this._onDidChangeFilesAssociation.event; @@ -139,6 +147,7 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi private currentHotExitConfiguration: string; private readonly autoSaveConfigurationCache = new LRUCache(1000); + private readonly autoSaveDisabledOverrides = new ResourceMap(); private readonly autoSaveAfterShortDelayContext = AutoSaveAfterShortDelayContext.bindTo(this.contextKeyService); @@ -367,11 +376,20 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi } isShortAutoSaveDelayConfigured(resourceOrEditor: EditorInput | URI | undefined): boolean { - return this.getAutoSaveConfiguration(resourceOrEditor).isShortAutoSaveDelay === true; + const resource = this.toResource(resourceOrEditor); + if (this.getAutoSaveConfiguration(resource).isShortAutoSaveDelay) { + return !resource || !this.autoSaveDisabledOverrides.has(resource); + } + + return false; } getAutoSaveMode(resourceOrEditor: EditorInput | URI | undefined): IAutoSaveMode { const resource = this.toResource(resourceOrEditor); + if (resource && this.autoSaveDisabledOverrides.has(resource)) { + return { mode: AutoSaveMode.OFF, reason: AutoSaveDisabledReason.DISABLED }; + } + const autoSaveConfiguration = this.getAutoSaveConfiguration(resource); if (typeof autoSaveConfiguration.autoSave === 'undefined') { return { mode: AutoSaveMode.OFF, reason: AutoSaveDisabledReason.SETTINGS }; @@ -418,6 +436,30 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi return this.configurationService.updateValue('files.autoSave', newAutoSaveValue); } + disableAutoSave(resourceOrEditor: EditorInput | URI): IDisposable { + const resource = this.toResource(resourceOrEditor); + if (!resource) { + return Disposable.None; + } + + const counter = this.autoSaveDisabledOverrides.get(resource) ?? 0; + this.autoSaveDisabledOverrides.set(resource, counter + 1); + + if (counter === 0) { + this._onDidChangeAutoSaveDisabled.fire(resource); + } + + return toDisposable(() => { + const counter = this.autoSaveDisabledOverrides.get(resource) ?? 0; + if (counter <= 1) { + this.autoSaveDisabledOverrides.delete(resource); + this._onDidChangeAutoSaveDisabled.fire(resource); + } else { + this.autoSaveDisabledOverrides.set(resource, counter - 1); + } + }); + } + get isHotExitEnabled(): boolean { if (this.contextService.getWorkspace().transient) { // Transient workspace: hot exit is disabled because diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index a6693a82cd4..b41ae15d043 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -311,6 +311,7 @@ export const NullFilesConfigurationService = new class implements IFilesConfigur _serviceBrand: undefined; readonly onDidChangeAutoSaveConfiguration = Event.None; + readonly onDidChangeAutoSaveDisabled = Event.None; readonly onDidChangeReadonly = Event.None; readonly onDidChangeFilesAssociation = Event.None; @@ -321,6 +322,7 @@ export const NullFilesConfigurationService = new class implements IFilesConfigur getAutoSaveMode(): IAutoSaveMode { throw new Error('Method not implemented.'); } isShortAutoSaveDelayConfigured(): boolean { throw new Error('Method not implemented.'); } toggleAutoSave(): Promise { throw new Error('Method not implemented.'); } + disableAutoSave(resourceOrEditor: URI | EditorInput): IDisposable { throw new Error('Method not implemented.'); } isReadonly(resource: URI, stat?: IBaseFileStat | undefined): boolean { return false; } async updateReadonly(resource: URI, readonly: boolean | 'toggle' | 'reset'): Promise { } preventSaveConflicts(resource: URI, language?: string | undefined): boolean { throw new Error('Method not implemented.'); } From aa8e5c8de98e658060d977edc2438138cd06e6aa Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 11 Jan 2024 21:20:01 +0100 Subject: [PATCH 0243/1897] :lipstick: rename method (#202277) --- .../contrib/files/browser/editors/fileEditorInput.ts | 2 +- .../contrib/files/browser/editors/textFileEditorTracker.ts | 2 +- .../workbench/contrib/files/browser/views/openEditorsView.ts | 2 +- src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts | 2 +- .../workbench/contrib/notebook/common/notebookEditorInput.ts | 2 +- .../workbench/contrib/search/browser/anythingQuickAccess.ts | 2 +- src/vs/workbench/electron-sandbox/window.ts | 2 +- .../filesConfiguration/common/filesConfigurationService.ts | 4 ++-- .../services/workingCopy/common/workingCopyBackupTracker.ts | 2 +- src/vs/workbench/test/common/workbenchTestServices.ts | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts index 210542f1d71..51684e02b56 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts @@ -318,7 +318,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements // and it could result in bad UX where an editor can be closed even though // it shows up as dirty and has not finished saving yet. - if (this.filesConfigurationService.isShortAutoSaveDelayConfigured(this)) { + if (this.filesConfigurationService.hasShortAutoSaveDelay(this)) { return true; // a short auto save is configured, treat this as being saved } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts index 89bc7247633..fa8593ad23e 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts @@ -69,7 +69,7 @@ export class TextFileEditorTracker extends Disposable implements IWorkbenchContr return false; // resource must not be pending to save } - if (resource.scheme !== Schemas.untitled && !fileModel?.hasState(TextFileEditorModelState.ERROR) && this.filesConfigurationService.isShortAutoSaveDelayConfigured(resource)) { + if (resource.scheme !== Schemas.untitled && !fileModel?.hasState(TextFileEditorModelState.ERROR) && this.filesConfigurationService.hasShortAutoSaveDelay(resource)) { // leave models auto saved after short delay unless // the save resulted in an error and not for untitled // that are not auto-saved anyway diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index a6428c1317e..d38538f3e97 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -460,7 +460,7 @@ export class OpenEditorsView extends ViewPane { private updateDirtyIndicator(workingCopy?: IWorkingCopy): void { if (workingCopy) { const gotDirty = workingCopy.isDirty(); - if (gotDirty && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled) && this.filesConfigurationService.isShortAutoSaveDelayConfigured(workingCopy.resource)) { + if (gotDirty && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled) && this.filesConfigurationService.hasShortAutoSaveDelay(workingCopy.resource)) { return; // do not indicate dirty of working copies that are auto saved after short delay } } diff --git a/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts b/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts index dc28ee8d9ed..c17726dec0f 100644 --- a/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts +++ b/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts @@ -42,7 +42,7 @@ export class DirtyFilesIndicator extends Disposable implements IWorkbenchContrib private onWorkingCopyDidChangeDirty(workingCopy: IWorkingCopy): void { const gotDirty = workingCopy.isDirty(); - if (gotDirty && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled) && this.filesConfigurationService.isShortAutoSaveDelayConfigured(workingCopy.resource)) { + if (gotDirty && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled) && this.filesConfigurationService.hasShortAutoSaveDelay(workingCopy.resource)) { return; // do not indicate dirty of working copies that are auto saved after short delay } diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts index 6053741b7a8..db98d0bbb96 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts @@ -181,7 +181,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { } // if a short auto save is configured, treat this as being saved - return this.filesConfigurationService.isShortAutoSaveDelayConfigured(this); + return this.filesConfigurationService.hasShortAutoSaveDelay(this); } override async save(group: GroupIdentifier, options?: ISaveOptions): Promise { diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index 95387f053bd..00c01996488 100644 --- a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -963,7 +963,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider { const gotDirty = workingCopy.isDirty(); - if (gotDirty && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled) && this.filesConfigurationService.isShortAutoSaveDelayConfigured(workingCopy.resource)) { + if (gotDirty && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled) && this.filesConfigurationService.hasShortAutoSaveDelay(workingCopy.resource)) { return; // do not indicate dirty of working copies that are auto saved after short delay } diff --git a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts index ced867e820b..a2c2076994b 100644 --- a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts +++ b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts @@ -86,7 +86,7 @@ export interface IFilesConfigurationService { getAutoSaveConfiguration(resourceOrEditor: EditorInput | URI | undefined): IAutoSaveConfiguration; - isShortAutoSaveDelayConfigured(resourceOrEditor: EditorInput | URI | undefined): boolean; + hasShortAutoSaveDelay(resourceOrEditor: EditorInput | URI | undefined): boolean; getAutoSaveMode(resourceOrEditor: EditorInput | URI | undefined): IAutoSaveMode; @@ -375,7 +375,7 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi return resourceOrEditor; } - isShortAutoSaveDelayConfigured(resourceOrEditor: EditorInput | URI | undefined): boolean { + hasShortAutoSaveDelay(resourceOrEditor: EditorInput | URI | undefined): boolean { const resource = this.toResource(resourceOrEditor); if (this.getAutoSaveConfiguration(resource).isShortAutoSaveDelay) { return !resource || !this.autoSaveDisabledOverrides.has(resource); diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts index 1eeb70a86e8..3f75e1e0bfd 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts @@ -231,7 +231,7 @@ export abstract class WorkingCopyBackupTracker extends Disposable { if (workingCopy.capabilities & WorkingCopyCapabilities.Untitled) { backupScheduleDelay = 'default'; // auto-save is never on for untitled working copies } else { - backupScheduleDelay = this.filesConfigurationService.isShortAutoSaveDelayConfigured(workingCopy.resource) ? 'delayed' : 'default'; + backupScheduleDelay = this.filesConfigurationService.hasShortAutoSaveDelay(workingCopy.resource) ? 'delayed' : 'default'; } return WorkingCopyBackupTracker.DEFAULT_BACKUP_SCHEDULE_DELAYS[backupScheduleDelay]; diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index b41ae15d043..864c6e9ed8f 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -320,7 +320,7 @@ export const NullFilesConfigurationService = new class implements IFilesConfigur getAutoSaveConfiguration(): IAutoSaveConfiguration { throw new Error('Method not implemented.'); } getAutoSaveMode(): IAutoSaveMode { throw new Error('Method not implemented.'); } - isShortAutoSaveDelayConfigured(): boolean { throw new Error('Method not implemented.'); } + hasShortAutoSaveDelay(): boolean { throw new Error('Method not implemented.'); } toggleAutoSave(): Promise { throw new Error('Method not implemented.'); } disableAutoSave(resourceOrEditor: URI | EditorInput): IDisposable { throw new Error('Method not implemented.'); } isReadonly(resource: URI, stat?: IBaseFileStat | undefined): boolean { return false; } From ccb9f5490a5b6caa318e346523169edd3ce1c353 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Thu, 11 Jan 2024 12:39:45 -0800 Subject: [PATCH 0244/1897] move `problems.visibility` to workbench (#202187) * first pass at moving away from contribs * moved to workbench * clean and fix configuration affects * revert and remove space * moved meta data, used in markers and workbench contribs --- .../workbench/browser/parts/editor/textEditor.ts | 2 +- .../workbench/browser/workbench.contribution.ts | 16 +++++++++++++++- src/vs/workbench/common/configuration.ts | 7 +++++++ .../markers/browser/markers.contribution.ts | 12 ++---------- .../markers/browser/markersFileDecorations.ts | 2 +- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 0fc1efdc5de..563f12a1be7 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -105,7 +105,7 @@ export abstract class AbstractTextEditor extends Abs } protected shouldHandleConfigurationChangeEvent(e: ITextResourceConfigurationChangeEvent, resource: URI | undefined): boolean { - return e.affectsConfiguration(resource, 'editor') || e.affectsConfiguration(resource, 'problems'); + return e.affectsConfiguration(resource, 'editor') || e.affectsConfiguration(resource, 'problems.visibility'); } private consumePendingConfigurationChangeEvent(): void { diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index f757ec7fdce..9fa670968d9 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -7,7 +7,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { localize } from 'vs/nls'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform'; -import { ConfigurationMigrationWorkbenchContribution, DynamicWorkbenchConfigurationWorkbenchContribution, IConfigurationMigrationRegistry, workbenchConfigurationNodeBase, Extensions, ConfigurationKeyValuePairs } from 'vs/workbench/common/configuration'; +import { ConfigurationMigrationWorkbenchContribution, DynamicWorkbenchConfigurationWorkbenchContribution, IConfigurationMigrationRegistry, workbenchConfigurationNodeBase, Extensions, ConfigurationKeyValuePairs, problemsConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { isStandalone } from 'vs/base/browser/browser'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -736,6 +736,20 @@ const registry = Registry.as(ConfigurationExtensions.Con } }); + // Problems + registry.registerConfiguration({ + ...problemsConfigurationNodeBase, + 'properties': { + 'problems.visibility': { + 'type': 'boolean', + 'default': true, + 'tags': ['experimental'], + 'description': localize('problems.visibility', "Controls whether the problems are visible throughout the editor and workbench."), + }, + // TODO: Add additional properties for problems (fine tuning in outline, marker file decorations, etc) + } + }); + // Zen Mode registry.registerConfiguration({ 'id': 'zenMode', diff --git a/src/vs/workbench/common/configuration.ts b/src/vs/workbench/common/configuration.ts index 07e1bd3afa5..e0db1ec2745 100644 --- a/src/vs/workbench/common/configuration.ts +++ b/src/vs/workbench/common/configuration.ts @@ -38,6 +38,13 @@ export const securityConfigurationNodeBase = Object.freeze({ 'order': 7 }); +export const problemsConfigurationNodeBase = Object.freeze({ + 'id': 'problems', + 'title': localize('problemsConfigurationTitle', "Problems"), + 'type': 'object', + 'order': 101 +}); + export const Extensions = { ConfigurationMigration: 'base.contributions.configuration.migration' }; diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index cf53ba18848..2466217b62c 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -35,6 +35,7 @@ import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { viewFilterSubmenu } from 'vs/workbench/browser/parts/views/viewFilter'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { problemsConfigurationNodeBase } from 'vs/workbench/common/configuration'; KeybindingsRegistry.registerCommandAndKeybindingRule({ id: Markers.MARKER_OPEN_ACTION_ID, @@ -91,10 +92,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // configuration Registry.as(Extensions.Configuration).registerConfiguration({ - 'id': 'problems', - 'order': 101, - 'title': Messages.PROBLEMS_PANEL_CONFIGURATION_TITLE, - 'type': 'object', + ...problemsConfigurationNodeBase, 'properties': { 'problems.autoReveal': { 'description': Messages.PROBLEMS_PANEL_CONFIGURATION_AUTO_REVEAL, @@ -122,12 +120,6 @@ Registry.as(Extensions.Configuration).registerConfigurat Messages.PROBLEMS_PANEL_CONFIGURATION_COMPARE_ORDER_POSITION, ], }, - 'problems.visibility': { - type: 'boolean', - default: true, - tags: ['experimental'], - description: localize('problems.visibility', "Controls whether the problems are visible throughout the editor and workbench."), - } } }); diff --git a/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts b/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts index 53954c20c67..e7355a0dd3d 100644 --- a/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts +++ b/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts @@ -66,7 +66,7 @@ class MarkersFileDecorations implements IWorkbenchContribution { ) { this._disposables = [ this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('problems')) { + if (e.affectsConfiguration('problems.visibility')) { this._updateEnablement(); } }), From 7f9b823b36dbbe76c39d15a251aacc2cabc334fb Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 11 Jan 2024 12:59:53 -0800 Subject: [PATCH 0245/1897] Polish cell chat widget layout (#202283) --- .../notebook/browser/media/notebookCellChat.css | 7 +++++-- .../contrib/notebook/browser/notebookEditorWidget.ts | 2 +- .../view/cellParts/chat/cellChatController.ts | 12 ++++++------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookCellChat.css b/src/vs/workbench/contrib/notebook/browser/media/notebookCellChat.css index abf309564a2..4577677c02a 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebookCellChat.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookCellChat.css @@ -6,7 +6,7 @@ .monaco-workbench .notebookOverlay .cell-chat-part { display: none; color: inherit; - padding: 0 6px; + padding: 6px; border-radius: 6px; border: 1px solid var(--vscode-inlineChat-border); background: var(--vscode-inlineChat-background); @@ -17,7 +17,6 @@ .monaco-workbench .notebookOverlay .cell-chat-part .inline-chat { color: inherit; - margin-top: 6px; } /* body */ @@ -83,6 +82,10 @@ gap: 4px; } +.monaco-workbench .notebookOverlay .cell-chat-part .inline-chat .body > .widget-toolbar { + padding-left: 4px; +} + /* progress bit */ .monaco-workbench .notebookOverlay .cell-chat-part .inline-chat .progress { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 6195bc90217..4484d005a1c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -869,7 +869,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD // chat styleSheets.push(` .monaco-workbench .notebookOverlay .cell-chat-part { - margin: 0 ${cellRightMargin}px 6px 6px; + margin: 0 ${cellRightMargin}px 6px 4px; } `); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts index fd32446deed..f2f620ce9d9 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts @@ -174,17 +174,17 @@ export class NotebookCellChatController extends Disposable { } private _updateHeight() { - const margin = 6; - const heightWithMargin = this._isVisible && this._widget - ? (this._widget.getHeight() - 8 /** shadow */ - 18 /** padding */ + margin /** bottom margin for the cell part */) + const surrounding = 6 * 2 /** padding */ + 6 /** cell chat widget margin bottom */ + 2 /** border */; + const heightWithPadding = this._isVisible && this._widget + ? (this._widget.getHeight() - 8 /** shadow */ - 18 /** padding */ - 6 /** widget's internal margin top */ + surrounding) : 0; - if (this._cell.chatHeight === heightWithMargin) { + if (this._cell.chatHeight === heightWithPadding) { return; } - this._cell.chatHeight = heightWithMargin; - this._partContainer.style.height = `${heightWithMargin - margin}px`; + this._cell.chatHeight = heightWithPadding; + this._partContainer.style.height = `${heightWithPadding - surrounding}px`; } async show() { From 2828de7e302e0a3cf7d67e6d870d4fec4976dce9 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 11 Jan 2024 13:07:23 -0800 Subject: [PATCH 0246/1897] use symbol map --- .../contrib/terminal/browser/terminal.ts | 5 +-- .../terminal/browser/terminalInstance.ts | 6 +-- .../terminal/browser/voiceTerminalActions.ts | 45 +++++++++++++------ 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 540bf204153..f5dfc7d63a0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -23,7 +23,7 @@ import { ITerminalStatusList } from 'vs/workbench/contrib/terminal/browser/termi import { XtermTerminal } from 'vs/workbench/contrib/terminal/browser/xterm/xtermTerminal'; import { IRegisterContributedProfileArgs, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfiguration, ITerminalFont, ITerminalProcessExtHostProxy, ITerminalProcessInfo } from 'vs/workbench/contrib/terminal/common/terminal'; import { ISimpleSelectedSuggestion } from 'vs/workbench/services/suggest/browser/simpleSuggestWidget'; -import type { IMarker, ITheme, IDecoration, IDecorationOptions, Terminal as RawXtermTerminal } from '@xterm/xterm'; +import type { IMarker, ITheme, Terminal as RawXtermTerminal } from '@xterm/xterm'; import { ScrollPosition } from 'vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { GroupIdentifier } from 'vs/workbench/common/editor'; @@ -792,9 +792,6 @@ export interface ITerminalInstance extends IBaseTerminalInstance { */ registerMarker(): IMarker | undefined; - - registerDecoration(options: IDecorationOptions): IDecoration | undefined; - /** * Adds a marker to the buffer, mapping it to an ID if provided. */ diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 02bcb839769..5ac44dc2718 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -84,7 +84,7 @@ import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/ import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { importAMDNodeModule } from 'vs/amdX'; -import type { IMarker, IDecorationOptions, IDecoration, Terminal as XTermTerminal } from '@xterm/xterm'; +import type { IMarker, Terminal as XTermTerminal } from '@xterm/xterm'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; import { shouldPasteTerminalText } from 'vs/workbench/contrib/terminal/common/terminalClipboard'; @@ -1444,10 +1444,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return this.xterm?.raw.registerMarker(); } - public registerDecoration(options: IDecorationOptions): IDecoration | undefined { - return this.xterm?.raw.registerDecoration(options); - } - public addBufferMarker(properties: IMarkProperties): void { this.capabilities.get(TerminalCapability.BufferMarkDetection)?.addMark(properties); } diff --git a/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts b/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts index e728bd61eba..c9947a3f2d2 100644 --- a/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts @@ -16,7 +16,7 @@ import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbe import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { isNumber } from 'vs/base/common/types'; -import type { IDecoration, Terminal } from '@xterm/xterm'; +import type { IDecoration } from '@xterm/xterm'; import { IXtermMarker } from 'vs/platform/terminal/common/capabilities/capabilities'; import { ThemeIcon } from 'vs/base/common/themables'; import { Codicon } from 'vs/base/common/codicons'; @@ -68,7 +68,6 @@ export class StopTerminalSpeechToTextAction extends Action2 { export class TerminalVoiceSession extends Disposable { private _input: string = ''; - private _xterm: Terminal | undefined; private _decoration: IDecoration | undefined; private _marker: IXtermMarker | undefined; private static _instance: TerminalVoiceSession | undefined = undefined; @@ -92,10 +91,6 @@ export class TerminalVoiceSession extends Disposable { this._register(this._terminalService.onDidDisposeInstance(() => this.stop())); } - activate(terminal: Terminal): void { - this._xterm = terminal; - } - start(): void { this.stop(); let voiceTimeout = this.configurationService.getValue(AccessibilityVoiceSettingId.SpeechTimeout); @@ -108,19 +103,19 @@ export class TerminalVoiceSession extends Disposable { }, voiceTimeout)); this._cancellationTokenSource = new CancellationTokenSource(); const session = this._disposables.add(this._speechService.createSpeechToTextSession(this._cancellationTokenSource!.token)); + this._disposables.add(session.onDidChange((e) => { if (this._cancellationTokenSource?.token.isCancellationRequested) { return; } switch (e.status) { case SpeechToTextStatus.Started: - break; - case SpeechToTextStatus.Recognizing: { if (!this._decoration) { - console.log('terminal', this._xterm); // TODO: start audio cue, show in terminal by the cursor this._createDecoration(); } + break; + case SpeechToTextStatus.Recognizing: { if (voiceTimeout > 0) { acceptTranscriptionScheduler.cancel(); @@ -129,7 +124,26 @@ export class TerminalVoiceSession extends Disposable { } case SpeechToTextStatus.Recognized: if (e.text) { - this._input = [this._input, e.text].join(' '); + const str = [this._input, e.text].join(' ').replaceAll(/[.,?;!]/g, ''); + const symbolMap: { [key: string]: string } = { + 'ampersand': '&', + 'dollar': '$', + 'at': '@', + 'percent': '%', + 'asterisk': '*', + 'plus': '+', + 'equals': '=', + 'exclamation': '!', + 'slash': '/', + 'backslash': '\\', + 'dot': '.', + 'period': '.', + }; + + for (const symbol in symbolMap) { + const regex: RegExp = new RegExp(symbol); + this._input = str.replace(regex, symbolMap[symbol]); + } } if (voiceTimeout > 0) { acceptTranscriptionScheduler.schedule(); @@ -140,11 +154,13 @@ export class TerminalVoiceSession extends Disposable { this.stop(); break; } + console.log(e.status); })); } stop(): void { this._marker?.dispose(); this._decoration?.dispose(); + this._decoration = undefined; this._cancellationTokenSource?.cancel(); this._disposables.clear(); this._input = ''; @@ -155,15 +171,18 @@ export class TerminalVoiceSession extends Disposable { if (!this._marker) { return; } - this._decoration = this._terminalService.activeInstance?.registerDecoration({ + this._decoration = this._terminalService.activeInstance?.xterm?.raw.registerDecoration({ marker: this._marker, - layer: 'top' + layer: 'top', + x: this._terminalService.activeInstance.xterm?.raw?.buffer.active.cursorX ?? this._terminalService.activeInstance.xterm?.raw?.buffer.active.cursorX! ?? 0, }); + console.log(this._terminalService.activeInstance?.xterm?.raw?.buffer.active.cursorX); this._decoration?.onRender((e: HTMLElement) => { e.classList.add(...ThemeIcon.asClassNameArray(Codicon.mic)); e.classList.add('quick-fix'); + e.style.zIndex = '1000'; + e.style.paddingLeft = '5px'; }); - console.log(this._decoration?.element); } } From c72ffc8cd8fe11f6708f34129741d5fecf6dee5a Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 11 Jan 2024 13:42:24 -0800 Subject: [PATCH 0247/1897] Use published markdown-katex typings (#202284) --- extensions/markdown-language-features/package.json | 2 +- .../src/typings/markdown-it-katex.d.ts | 6 ------ extensions/markdown-language-features/yarn.lock | 8 ++++---- 3 files changed, 5 insertions(+), 11 deletions(-) delete mode 100644 extensions/markdown-language-features/src/typings/markdown-it-katex.d.ts diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 839219f5a94..8d81b3fcc03 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -756,7 +756,7 @@ "@types/picomatch": "^2.3.0", "@types/vscode-notebook-renderer": "^1.60.0", "@types/vscode-webview": "^1.57.0", - "@vscode/markdown-it-katex": "^1.0.1", + "@vscode/markdown-it-katex": "^1.0.2", "lodash.throttle": "^4.1.1", "vscode-languageserver-types": "^3.17.2", "vscode-markdown-languageservice": "^0.3.0-alpha.3" diff --git a/extensions/markdown-language-features/src/typings/markdown-it-katex.d.ts b/extensions/markdown-language-features/src/typings/markdown-it-katex.d.ts deleted file mode 100644 index 7e9ed78cf5e..00000000000 --- a/extensions/markdown-language-features/src/typings/markdown-it-katex.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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/markdown-it-katex'; diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index c3022ef86f7..7ff0968e5af 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -166,10 +166,10 @@ resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.10.tgz#9c513107c690c0dd16e3ec61e453743de15ebdb0" integrity sha512-E1OCmDcDWa0Ya7vtSjp/XfHFGqYJfh+YPC1RkATU71fTac+j1JjCcB3qwSzmlKAighx2WxhLlfhS0RwAN++PFQ== -"@vscode/markdown-it-katex@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@vscode/markdown-it-katex/-/markdown-it-katex-1.0.1.tgz#79c6e7312074e1f897cc22c42ce078d1e72003b0" - integrity sha512-O/HiT5Uc6rN6rSx8tDdgwO1tLSn/lrNeikTzYw1EBG6B2IGLKw4I4e/GBh9DRNSdE9PajCA0tsVBz86qyA7B3A== +"@vscode/markdown-it-katex@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@vscode/markdown-it-katex/-/markdown-it-katex-1.0.2.tgz#27ba579fa3896b2944b71209dd30d0f983983f11" + integrity sha512-QY/OnOHPTqc8tQoCoAjVblILX4yE6xGZHKODtiTKqA328OXra+lSpeJO5Ouo9AAvrs9AwcCLz6xvW3zwcsPBQg== dependencies: katex "^0.16.4" From 8a44dee10866f478c4e6347d4372dd67b28b3b19 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 11 Jan 2024 12:58:12 -0800 Subject: [PATCH 0248/1897] editor: support line number decorations Supports setting a class name and hover message for line numbers --- .../viewParts/lineNumbers/lineNumbers.ts | 36 ++++++++++++++----- src/vs/editor/common/model.ts | 8 +++++ src/vs/editor/common/model/textModel.ts | 22 ++++++------ src/vs/editor/common/textModelEvents.ts | 1 + src/vs/editor/common/viewEvents.ts | 3 ++ src/vs/editor/contrib/hover/browser/hover.ts | 6 ++++ .../contrib/hover/browser/marginHover.ts | 31 ++++++++-------- src/vs/monaco.d.ts | 9 +++++ .../browser/codeCoverageDecorations.ts | 16 ++++----- .../contrib/testing/browser/media/testing.css | 6 ++-- 10 files changed, 91 insertions(+), 47 deletions(-) diff --git a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts index 92537ee51db..03336d34278 100644 --- a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts +++ b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts @@ -8,6 +8,7 @@ import * as platform from 'vs/base/common/platform'; import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay'; import { RenderLineNumbersType, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; import { RenderingContext } from 'vs/editor/browser/view/renderingContext'; import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; import * as viewEvents from 'vs/editor/common/viewEvents'; @@ -98,6 +99,9 @@ export class LineNumbersOverlay extends DynamicViewOverlay { public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return true; } + public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + return e.affectsLineNumber; + } // --- end event handlers @@ -143,36 +147,50 @@ export class LineNumbersOverlay extends DynamicViewOverlay { const visibleStartLineNumber = ctx.visibleRange.startLineNumber; const visibleEndLineNumber = ctx.visibleRange.endLineNumber; + const lineNoDecorations = this._context.viewModel.getDecorationsInViewport(ctx.visibleRange).filter(d => !!d.options.lineNumberClassName); + lineNoDecorations.sort((a, b) => Range.compareRangesUsingEnds(a.range, b.range)); + let decorationStartIndex = 0; + const lineCount = this._context.viewModel.getLineCount(); const output: string[] = []; for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { const lineIndex = lineNumber - visibleStartLineNumber; - const renderLineNumber = this._getLineRenderLineNumber(lineNumber); + let renderLineNumber = this._getLineRenderLineNumber(lineNumber); + let extraClassNames = ''; - if (!renderLineNumber) { + // skip decorations whose end positions we've already passed + while (decorationStartIndex < lineNoDecorations.length && lineNoDecorations[decorationStartIndex].range.endLineNumber < lineNumber) { + decorationStartIndex++; + } + for (let i = decorationStartIndex; i < lineNoDecorations.length; i++) { + const { range, options } = lineNoDecorations[i]; + if (range.startLineNumber <= lineNumber) { + extraClassNames += ' ' + options.lineNumberClassName; + } + } + + if (!renderLineNumber && !extraClassNames) { output[lineIndex] = ''; continue; } - let extraClassName = ''; - if (lineNumber === lineCount && this._context.viewModel.getLineLength(lineNumber) === 0) { // this is the last line if (this._renderFinalNewline === 'off') { - output[lineIndex] = ''; - continue; + renderLineNumber = ''; } if (this._renderFinalNewline === 'dimmed') { - extraClassName = ' dimmed-line-number'; + extraClassNames += ' dimmed-line-number'; } } if (lineNumber === this._activeLineNumber) { - extraClassName = ' active-line-number'; + extraClassNames += ' active-line-number'; } + output[lineIndex] = ( - `
${renderLineNumber}
` + `
${renderLineNumber}
` ); } diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 3d84860632f..ad0d31765ff 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -160,6 +160,10 @@ export interface IModelDecorationOptions { * Array of MarkdownString to render as the decoration message. */ hoverMessage?: IMarkdownString | IMarkdownString[] | null; + /** + * Array of MarkdownString to render as the line number message. + */ + lineNumberHoverMessage?: IMarkdownString | IMarkdownString[] | null; /** * Should the decoration expand to encompass a whole line. */ @@ -204,6 +208,10 @@ export interface IModelDecorationOptions { * Controls the tooltip text of the line decoration. */ linesDecorationsTooltip?: string | null; + /** + * If set, the decoration will be rendered on the line number. + */ + lineNumberClassName?: string | null; /** * If set, the decoration will be rendered in the lines decorations with this CSS class name, but only for the first line in case of line wrapping. */ diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 8b91f6ae75e..e3e8239cb55 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -2299,6 +2299,8 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions { readonly glyphMargin?: model.IModelDecorationGlyphMarginOptions | null | undefined; readonly glyphMarginClassName: string | null; readonly linesDecorationsClassName: string | null; + readonly lineNumberClassName: string | null; + readonly lineNumberHoverMessage: IMarkdownString | IMarkdownString[] | null; readonly linesDecorationsTooltip: string | null; readonly firstLineDecorationClassName: string | null; readonly marginClassName: string | null; @@ -2323,6 +2325,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions { this.shouldFillLineOnLineBreak = options.shouldFillLineOnLineBreak ?? null; this.hoverMessage = options.hoverMessage || null; this.glyphMarginHoverMessage = options.glyphMarginHoverMessage || null; + this.lineNumberHoverMessage = options.lineNumberHoverMessage || null; this.isWholeLine = options.isWholeLine || false; this.showIfCollapsed = options.showIfCollapsed || false; this.collapseOnReplaceEdit = options.collapseOnReplaceEdit || false; @@ -2331,6 +2334,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions { this.glyphMargin = options.glyphMarginClassName ? new ModelDecorationGlyphMarginOptions(options.glyphMargin) : null; this.glyphMarginClassName = options.glyphMarginClassName ? cleanClassName(options.glyphMarginClassName) : null; this.linesDecorationsClassName = options.linesDecorationsClassName ? cleanClassName(options.linesDecorationsClassName) : null; + this.lineNumberClassName = options.lineNumberClassName ? cleanClassName(options.lineNumberClassName) : null; this.linesDecorationsTooltip = options.linesDecorationsTooltip ? strings.htmlAttributeEncodeValue(options.linesDecorationsTooltip) : null; this.firstLineDecorationClassName = options.firstLineDecorationClassName ? cleanClassName(options.firstLineDecorationClassName) : null; this.marginClassName = options.marginClassName ? cleanClassName(options.marginClassName) : null; @@ -2374,6 +2378,7 @@ class DidChangeDecorationsEmitter extends Disposable { private _affectsOverviewRuler: boolean; private _affectedInjectedTextLines: Set | null = null; private _affectsGlyphMargin: boolean; + private _affectsLineNumber: boolean; constructor(private readonly handleBeforeFire: (affectedInjectedTextLines: Set | null) => void) { super(); @@ -2382,6 +2387,7 @@ class DidChangeDecorationsEmitter extends Disposable { this._affectsMinimap = false; this._affectsOverviewRuler = false; this._affectsGlyphMargin = false; + this._affectsLineNumber = false; } hasListeners(): boolean { @@ -2412,15 +2418,10 @@ class DidChangeDecorationsEmitter extends Disposable { } public checkAffectedAndFire(options: ModelDecorationOptions): void { - if (!this._affectsMinimap) { - this._affectsMinimap = options.minimap && options.minimap.position ? true : false; - } - if (!this._affectsOverviewRuler) { - this._affectsOverviewRuler = options.overviewRuler && options.overviewRuler.color ? true : false; - } - if (!this._affectsGlyphMargin) { - this._affectsGlyphMargin = options.glyphMarginClassName ? true : false; - } + this._affectsMinimap ||= !!options.minimap?.position; + this._affectsOverviewRuler ||= !!options.overviewRuler?.color; + this._affectsGlyphMargin ||= !!options.glyphMarginClassName; + this._affectsLineNumber ||= !!options.lineNumberClassName; this.tryFire(); } @@ -2445,7 +2446,8 @@ class DidChangeDecorationsEmitter extends Disposable { const event: IModelDecorationsChangedEvent = { affectsMinimap: this._affectsMinimap, affectsOverviewRuler: this._affectsOverviewRuler, - affectsGlyphMargin: this._affectsGlyphMargin + affectsGlyphMargin: this._affectsGlyphMargin, + affectsLineNumber: this._affectsLineNumber, }; this._shouldFireDeferred = false; this._affectsMinimap = false; diff --git a/src/vs/editor/common/textModelEvents.ts b/src/vs/editor/common/textModelEvents.ts index 8c25e06776d..58c720ac87c 100644 --- a/src/vs/editor/common/textModelEvents.ts +++ b/src/vs/editor/common/textModelEvents.ts @@ -91,6 +91,7 @@ export interface IModelDecorationsChangedEvent { readonly affectsMinimap: boolean; readonly affectsOverviewRuler: boolean; readonly affectsGlyphMargin: boolean; + readonly affectsLineNumber: boolean; } /** diff --git a/src/vs/editor/common/viewEvents.ts b/src/vs/editor/common/viewEvents.ts index 0e6cb0fa166..f607afe73e9 100644 --- a/src/vs/editor/common/viewEvents.ts +++ b/src/vs/editor/common/viewEvents.ts @@ -76,16 +76,19 @@ export class ViewDecorationsChangedEvent { readonly affectsMinimap: boolean; readonly affectsOverviewRuler: boolean; readonly affectsGlyphMargin: boolean; + readonly affectsLineNumber: boolean; constructor(source: IModelDecorationsChangedEvent | null) { if (source) { this.affectsMinimap = source.affectsMinimap; this.affectsOverviewRuler = source.affectsOverviewRuler; this.affectsGlyphMargin = source.affectsGlyphMargin; + this.affectsLineNumber = source.affectsLineNumber; } else { this.affectsMinimap = true; this.affectsOverviewRuler = true; this.affectsGlyphMargin = true; + this.affectsLineNumber = true; } } } diff --git a/src/vs/editor/contrib/hover/browser/hover.ts b/src/vs/editor/contrib/hover/browser/hover.ts index 08b2a7bb68d..509eb67e324 100644 --- a/src/vs/editor/contrib/hover/browser/hover.ts +++ b/src/vs/editor/contrib/hover/browser/hover.ts @@ -300,6 +300,12 @@ export class HoverController extends Disposable implements IEditorContribution { glyphWidget.startShowingAt(target.position.lineNumber, target.detail.glyphMarginLane); return; } + if (target.type === MouseTargetType.GUTTER_LINE_NUMBERS && target.position) { + this._contentWidget?.hide(); + const glyphWidget = this._getOrCreateGlyphWidget(); + glyphWidget.startShowingAt(target.position.lineNumber, 'lineNo'); + return; + } if (_sticky) { return; } diff --git a/src/vs/editor/contrib/hover/browser/marginHover.ts b/src/vs/editor/contrib/hover/browser/marginHover.ts index 98af0a06cda..e9eb33604c1 100644 --- a/src/vs/editor/contrib/hover/browser/marginHover.ts +++ b/src/vs/editor/contrib/hover/browser/marginHover.ts @@ -22,6 +22,8 @@ export interface IHoverMessage { value: IMarkdownString; } +type LaneOrLineNumber = GlyphMarginLane | 'lineNo'; + export class MarginHoverWidget extends Disposable implements IOverlayWidget { public static readonly ID = 'editor.contrib.modesGlyphHoverWidget'; @@ -99,8 +101,8 @@ export class MarginHoverWidget extends Disposable implements IOverlayWidget { } } - public startShowingAt(lineNumber: number, lane: GlyphMarginLane): void { - if (this._computer.lineNumber === lineNumber && this._computer.lane === lane) { + public startShowingAt(lineNumber: number, laneOrLine: LaneOrLineNumber): void { + if (this._computer.lineNumber === lineNumber && this._computer.lane === laneOrLine) { // We have to show the widget at the exact same line number as before, so no work is needed return; } @@ -110,7 +112,7 @@ export class MarginHoverWidget extends Disposable implements IOverlayWidget { this.hide(); this._computer.lineNumber = lineNumber; - this._computer.lane = lane; + this._computer.lane = laneOrLine; this._hoverOperation.start(HoverStartMode.Delayed); } @@ -169,8 +171,8 @@ export class MarginHoverWidget extends Disposable implements IOverlayWidget { const lineHeight = this._editor.getOption(EditorOption.lineHeight); const nodeHeight = this._hover.containerDomNode.clientHeight; const top = topForLineNumber - editorScrollTop - ((nodeHeight - lineHeight) / 2); - - this._hover.containerDomNode.style.left = `${editorLayout.glyphMarginLeft + editorLayout.glyphMarginWidth}px`; + const left = editorLayout.glyphMarginLeft + editorLayout.glyphMarginWidth + (this._computer.lane === 'lineNo' ? editorLayout.lineNumbersWidth : 0); + this._hover.containerDomNode.style.left = `${left}px`; this._hover.containerDomNode.style.top = `${Math.max(Math.round(top), 0)}px`; } } @@ -178,7 +180,7 @@ export class MarginHoverWidget extends Disposable implements IOverlayWidget { class MarginHoverComputer implements IHoverComputer { private _lineNumber: number = -1; - private _lane = GlyphMarginLane.Center; + private _laneOrLine: LaneOrLineNumber = GlyphMarginLane.Center; public get lineNumber(): number { return this._lineNumber; @@ -188,12 +190,12 @@ class MarginHoverComputer implements IHoverComputer { this._lineNumber = value; } - public get lane(): number { - return this._lane; + public get lane(): LaneOrLineNumber { + return this._laneOrLine; } - public set lane(value: GlyphMarginLane) { - this._lane = value; + public set lane(value: LaneOrLineNumber) { + this._laneOrLine = value; } constructor( @@ -212,21 +214,18 @@ class MarginHoverComputer implements IHoverComputer { const lineDecorations = this._editor.getLineDecorations(this._lineNumber); const result: IHoverMessage[] = []; + const isLineHover = this._laneOrLine === 'lineNo'; if (!lineDecorations) { return result; } for (const d of lineDecorations) { - if (!d.options.glyphMarginClassName) { - continue; - } - const lane = d.options.glyphMargin?.position ?? GlyphMarginLane.Center; - if (lane !== this._lane) { + if (!isLineHover && lane !== this._laneOrLine) { continue; } - const hoverMessage = d.options.glyphMarginHoverMessage; + const hoverMessage = isLineHover ? d.options.lineNumberHoverMessage : d.options.glyphMarginHoverMessage; if (!hoverMessage || isEmptyMarkdownString(hoverMessage)) { continue; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index ac4a31bd48e..f0028ac35dc 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1691,6 +1691,10 @@ declare namespace monaco.editor { * Array of MarkdownString to render as the decoration message. */ hoverMessage?: IMarkdownString | IMarkdownString[] | null; + /** + * Array of MarkdownString to render as the line number message. + */ + lineNumberHoverMessage?: IMarkdownString | IMarkdownString[] | null; /** * Should the decoration expand to encompass a whole line. */ @@ -1730,6 +1734,10 @@ declare namespace monaco.editor { * Controls the tooltip text of the line decoration. */ linesDecorationsTooltip?: string | null; + /** + * If set, the decoration will be rendered on the line number. + */ + lineNumberClassName?: string | null; /** * If set, the decoration will be rendered in the lines decorations with this CSS class name, but only for the first line in case of line wrapping. */ @@ -2930,6 +2938,7 @@ declare namespace monaco.editor { readonly affectsMinimap: boolean; readonly affectsOverviewRuler: boolean; readonly affectsGlyphMargin: boolean; + readonly affectsLineNumber: boolean; } export interface IModelOptionsChangedEvent { diff --git a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts index 856c7d4e6ae..7fb11557806 100644 --- a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts @@ -11,14 +11,13 @@ import { ICodeEditor, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { GlyphMarginLane, IModelDecorationOptions, ITextModel } from 'vs/editor/common/model'; +import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; import { ILogService } from 'vs/platform/log/common/log'; import { FileCoverage } from 'vs/workbench/contrib/testing/common/testCoverage'; import { ITestCoverageService } from 'vs/workbench/contrib/testing/common/testCoverageService'; import { CoverageDetails, DetailType, IStatementCoverage } from 'vs/workbench/contrib/testing/common/testTypes'; -const GLYPH_LANE = GlyphMarginLane.Left; const MAX_HOVERED_LINES = 30; const CLASS_HIT = 'coverage-deco-hit'; const CLASS_MISS = 'coverage-deco-miss'; @@ -66,8 +65,7 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri })); this._register(editor.onMouseMove(e => { - if (e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN - && e.target.detail.glyphMarginLane === GLYPH_LANE) { + if (e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS) { this.hoverLineNumber(editor.getModel()!, e.target.position.lineNumber); } else { this.hoveredStore.clear(); @@ -143,11 +141,10 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri const opts: IModelDecorationOptions = { showIfCollapsed: false, description: 'coverage-gutter', - glyphMargin: { position: GlyphMarginLane.Left, persistLane: true }, - glyphMarginHoverMessage: new MarkdownString() + lineNumberHoverMessage: new MarkdownString() .appendCodeblock(model.getLanguageId(), model.getValueInRange(range)) .appendText(localize('testing.branchHitCount', 'Branch hit count: {0}', hits)), - glyphMarginClassName: `coverage-deco-gutter ${cls}`, + lineNumberClassName: `coverage-deco-gutter ${cls}`, }; this.decorationIds.set(e.addDecoration(range, opts), { @@ -161,11 +158,10 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri const opts: IModelDecorationOptions = { showIfCollapsed: false, description: 'coverage-inline', - glyphMargin: { position: GlyphMarginLane.Left, persistLane: true }, - glyphMarginHoverMessage: new MarkdownString() + lineNumberHoverMessage: new MarkdownString() .appendCodeblock(model.getLanguageId(), model.getValueInRange(range)) .appendText(localize('testing.hitCount', 'Hit count: {0}', detail.count)), - glyphMarginClassName: `coverage-deco-gutter ${cls}`, + lineNumberClassName: `coverage-deco-gutter ${cls}`, }; this.decorationIds.set(e.addDecoration(range, opts), { diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index 6168c064211..d40e482a1c7 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -417,12 +417,14 @@ /** -- coverage decorations */ +.coverage-deco-gutter { + z-index: 0; +} .coverage-deco-gutter::before { content: ''; position: absolute; inset: 0; - right: 25%; - left: 25%; + z-index: -1; } .coverage-deco-gutter.coverage-deco-hit::before { From 34b030128751b9caefb2e5b62c583dde516a78d6 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 11 Jan 2024 14:27:22 -0800 Subject: [PATCH 0249/1897] Fix insert range of ts entries with replacement spans (#202287) Fix #202153 --- .../src/languageFeatures/completions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/completions.ts b/extensions/typescript-language-features/src/languageFeatures/completions.ts index 943a2437449..817f614a6c5 100644 --- a/extensions/typescript-language-features/src/languageFeatures/completions.ts +++ b/extensions/typescript-language-features/src/languageFeatures/completions.ts @@ -374,7 +374,7 @@ class MyCompletionItem extends vscode.CompletionItem { // If TS returns an explicit replacement range, we should use it for both types of completion return { - inserting: replaceRange, + inserting: new vscode.Range(replaceRange.start, this.position), replacing: replaceRange, }; } From eee233f1ed71b24e83acdb6ca499fc0d728d5aea Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 11 Jan 2024 14:35:42 -0800 Subject: [PATCH 0250/1897] get it to work --- .../terminal/browser/voiceTerminalActions.ts | 83 ++++++++++++------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts b/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts index c9947a3f2d2..404068b4f59 100644 --- a/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts @@ -12,7 +12,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { AccessibilityVoiceSettingId, SpeechTimeoutDefault } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { HasSpeechProvider, ISpeechService, ISpeechToTextEvent, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { isNumber } from 'vs/base/common/types'; @@ -62,7 +62,7 @@ export class StopTerminalSpeechToTextAction extends Action2 { async run(accessor: ServicesAccessor): Promise { const instantiationService = accessor.get(IInstantiationService); - TerminalVoiceSession.getInstance(instantiationService).stop(); + TerminalVoiceSession.getInstance(instantiationService).stop(true); } } @@ -71,6 +71,7 @@ export class TerminalVoiceSession extends Disposable { private _decoration: IDecoration | undefined; private _marker: IXtermMarker | undefined; private static _instance: TerminalVoiceSession | undefined = undefined; + private _acceptTranscriptionScheduler: RunOnceScheduler | undefined; static getInstance(instantiationService: IInstantiationService): TerminalVoiceSession { if (!TerminalVoiceSession._instance) { TerminalVoiceSession._instance = instantiationService.createInstance(TerminalVoiceSession); @@ -97,7 +98,7 @@ export class TerminalVoiceSession extends Disposable { if (!isNumber(voiceTimeout) || voiceTimeout < 0) { voiceTimeout = SpeechTimeoutDefault; } - const acceptTranscriptionScheduler = this._disposables.add(new RunOnceScheduler(() => { + this._acceptTranscriptionScheduler = this._disposables.add(new RunOnceScheduler(() => { this._terminalService.activeInstance?.sendText(this._input, false); this.stop(); }, voiceTimeout)); @@ -110,54 +111,36 @@ export class TerminalVoiceSession extends Disposable { } switch (e.status) { case SpeechToTextStatus.Started: + // TODO: play start audio cue if (!this._decoration) { - // TODO: start audio cue, show in terminal by the cursor this._createDecoration(); } break; case SpeechToTextStatus.Recognizing: { - + this._updateInput(e); if (voiceTimeout > 0) { - acceptTranscriptionScheduler.cancel(); + this._acceptTranscriptionScheduler!.cancel(); } break; } case SpeechToTextStatus.Recognized: - if (e.text) { - const str = [this._input, e.text].join(' ').replaceAll(/[.,?;!]/g, ''); - const symbolMap: { [key: string]: string } = { - 'ampersand': '&', - 'dollar': '$', - 'at': '@', - 'percent': '%', - 'asterisk': '*', - 'plus': '+', - 'equals': '=', - 'exclamation': '!', - 'slash': '/', - 'backslash': '\\', - 'dot': '.', - 'period': '.', - }; - - for (const symbol in symbolMap) { - const regex: RegExp = new RegExp(symbol); - this._input = str.replace(regex, symbolMap[symbol]); - } - } + this._updateInput(e); if (voiceTimeout > 0) { - acceptTranscriptionScheduler.schedule(); + this._acceptTranscriptionScheduler!.schedule(); } break; case SpeechToTextStatus.Stopped: - // TODO: stop audio cue, hide in terminal by the cursor + // TODO: play stop audio cue this.stop(); break; } - console.log(e.status); })); } - stop(): void { + stop(send?: boolean): void { + if (send) { + this._acceptTranscriptionScheduler!.cancel(); + this._terminalService.activeInstance?.sendText(this._input, false); + } this._marker?.dispose(); this._decoration?.dispose(); this._decoration = undefined; @@ -166,6 +149,42 @@ export class TerminalVoiceSession extends Disposable { this._input = ''; } + private _updateInput(e: ISpeechToTextEvent): void { + if (e.text) { + let input = e.text.replaceAll(/[.,?;!]/g, ''); + const symbolMap: { [key: string]: string } = { + 'Ampersand': '&', + 'ampersand': '&', + 'Dollar': '$', + 'dollar': '$', + 'Percent': '%', + 'percent': '%', + 'Asterisk': '*', + 'asterisk': '*', + 'Plus': '+', + 'plus': '+', + 'Equals': '=', + 'equals': '=', + 'Exclamation': '!', + 'exclamation': '!', + 'Slash': '/', + 'slash': '/', + 'Backslash': '\\', + 'backslash': '\\', + 'Dot': '.', + 'dot': '.', + 'Period': '.', + 'period': '.' + }; + + for (const symbol in symbolMap) { + const regex: RegExp = new RegExp(symbol); + input = input.replace(regex, symbolMap[symbol]); + } + this._input = ' ' + input; + } + } + private _createDecoration(): void { this._marker = this._terminalService.activeInstance?.registerMarker(); if (!this._marker) { From 80bb443f663a70c45ef87ce44f3c68b3dee2b58e Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 11 Jan 2024 14:55:14 -0800 Subject: [PATCH 0251/1897] debug: add a line preview in the trigger point select box (#202289) --- .../contrib/debug/browser/breakpointWidget.ts | 30 +++++++++++++++---- .../debug/browser/media/breakpointWidget.css | 7 +++-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 205c8c80e2f..14ea1c09176 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -27,6 +27,7 @@ import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry' import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { IModelService } from 'vs/editor/common/services/model'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { CompletionOptions, provideSuggestionItems } from 'vs/editor/contrib/suggest/browser/suggest'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; import * as nls from 'vs/nls'; @@ -104,6 +105,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @IKeybindingService private readonly keybindingService: IKeybindingService, @ILabelService private readonly labelService: ILabelService, + @ITextModelService private readonly textModelService: ITextModelService, ) { super(editor, { showFrame: true, showArrow: false, frameWidth: 1, isAccessible: true }); @@ -199,7 +201,12 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi protected _fillContainer(container: HTMLElement): void { this.setCssClass('breakpoint-widget'); - const selectBox = new SelectBox([{ text: nls.localize('expression', "Expression") }, { text: nls.localize('hitCount', "Hit Count") }, { text: nls.localize('logMessage', "Log Message") }, { text: nls.localize('triggeredBy', "Wait For Breakpoint") }], this.context, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('breakpointType', 'Breakpoint Type') }); + const selectBox = new SelectBox([ + { text: nls.localize('expression', "Expression") }, + { text: nls.localize('hitCount', "Hit Count") }, + { text: nls.localize('logMessage', "Log Message") }, + { text: nls.localize('triggeredBy', "Wait For Breakpoint") }, + ], this.context, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('breakpointType', 'Breakpoint Type') }); this.selectContainer = $('.breakpoint-select-container'); selectBox.render(dom.append(container, this.selectContainer)); selectBox.onDidSelect(e => { @@ -232,11 +239,24 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi if (index > -1) { select = index + 1; } - const items: ISelectOptionItem[] = [{ text: nls.localize('noTriggerByBreakpoint', 'None') }]; - breakpoints.map(bp => { text: `${this.labelService.getUriLabel(bp.uri, { relative: true })}: ${bp.lineNumber}` }) - .forEach(i => items.push(i)); - const selectBreakpointBox = new SelectBox(items, select, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('selectBreakpoint', 'Select breakpoint') }); + Promise.all(breakpoints.map(async (bp): Promise => ({ + text: `${this.labelService.getUriLabel(bp.uri, { relative: true })}: ${bp.lineNumber}`, + description: await this.textModelService.createModelReference(bp.uri).then(ref => { + try { + return ref.object.textEditorModel.getLineContent(bp.lineNumber).trim(); + } finally { + ref.dispose(); + } + }, () => undefined), + }))).then(breakpoints => { + selectBreakpointBox.setOptions([ + { text: nls.localize('noTriggerByBreakpoint', 'None') }, + ...breakpoints + ], select); + }); + + const selectBreakpointBox = new SelectBox([{ text: nls.localize('triggerByLoading', 'Loading...'), isDisabled: true }], 0, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('selectBreakpoint', 'Select breakpoint') }); selectBreakpointBox.onDidSelect(e => { if (e.index === 0) { this.triggeredByBreakpointInput = undefined; diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css index 88728c85b20..413342607d6 100644 --- a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css @@ -33,21 +33,24 @@ .monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container { display: flex; align-items: center; - flex-direction: row; gap: 10px; + flex-grow: 1; + width: 0; } .monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container .monaco-button { padding-left: 8px; padding-right: 8px; line-height: 14px; + flex-grow: 0; + width: initial; } .monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container .select-box-container { display: flex; justify-content: center; flex-direction: column; - flex-shrink: 0; + width: 300px; } .monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .select-breakpoint-container:after { From 6a4796ab12b3a19cb7314c5f7dac5cfab042848a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 11 Jan 2024 15:01:26 -0800 Subject: [PATCH 0252/1897] fix part of #199744 --- src/vs/workbench/contrib/terminal/common/terminal.ts | 2 ++ .../browser/terminal.accessibility.contribution.ts | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 51525b5ca39..1063933ba91 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -451,9 +451,11 @@ export const enum TerminalCommandId { ScrollDownLine = 'workbench.action.terminal.scrollDown', ScrollDownPage = 'workbench.action.terminal.scrollDownPage', ScrollToBottom = 'workbench.action.terminal.scrollToBottom', + ScrollToBottomAccessibleView = 'workbench.action.terminal.scrollToBottomAccessibleView', ScrollUpLine = 'workbench.action.terminal.scrollUp', ScrollUpPage = 'workbench.action.terminal.scrollUpPage', ScrollToTop = 'workbench.action.terminal.scrollToTop', + ScrollToTopAccessibleView = 'workbench.action.terminal.scrollToTopAccessibleView', Clear = 'workbench.action.terminal.clear', ClearSelection = 'workbench.action.terminal.clearSelection', ChangeIcon = 'workbench.action.terminal.changeIcon', 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 650ae6d9f35..b280dfd54c8 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 @@ -324,8 +324,8 @@ registerTerminalAction({ }); registerTerminalAction({ - id: TerminalCommandId.ScrollToBottom, - title: localize2('workbench.action.terminal.scrollToBottom', 'Scroll to Bottom'), + id: TerminalCommandId.ScrollToBottomAccessibleView, + title: localize2('workbench.action.terminal.scrollToBottomAccessibleView', 'Scroll to Accessible View Bottom'), precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.End, @@ -344,8 +344,8 @@ registerTerminalAction({ }); registerTerminalAction({ - id: TerminalCommandId.ScrollToTop, - title: localize2('workbench.action.terminal.scrollToTop', 'Scroll to Top'), + id: TerminalCommandId.ScrollToTopAccessibleView, + title: localize2('workbench.action.terminal.scrollToTopAccessibleView', 'Scroll to Accessible View Top'), precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.Home, From db58cfdb063c44d7c621b2941793025335d2bebf Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 11 Jan 2024 15:12:27 -0800 Subject: [PATCH 0253/1897] cleanup --- .../terminal/browser/voiceTerminalActions.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts b/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts index 404068b4f59..e55f9c4f4cf 100644 --- a/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts @@ -186,20 +186,23 @@ export class TerminalVoiceSession extends Disposable { } private _createDecoration(): void { - this._marker = this._terminalService.activeInstance?.registerMarker(); + const activeInstance = this._terminalService.activeInstance; + const xterm = activeInstance?.xterm?.raw; + if (!xterm) { + return; + } + this._marker = activeInstance.registerMarker(); if (!this._marker) { return; } - this._decoration = this._terminalService.activeInstance?.xterm?.raw.registerDecoration({ + this._decoration = xterm.registerDecoration({ marker: this._marker, layer: 'top', - x: this._terminalService.activeInstance.xterm?.raw?.buffer.active.cursorX ?? this._terminalService.activeInstance.xterm?.raw?.buffer.active.cursorX! ?? 0, + x: xterm.buffer.active.cursorX ?? 0, }); - console.log(this._terminalService.activeInstance?.xterm?.raw?.buffer.active.cursorX); this._decoration?.onRender((e: HTMLElement) => { e.classList.add(...ThemeIcon.asClassNameArray(Codicon.mic)); e.classList.add('quick-fix'); - e.style.zIndex = '1000'; e.style.paddingLeft = '5px'; }); } From 34fcd8bebc9f4fd5821ca92acf6dfce40849f23a Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 11 Jan 2024 15:29:01 -0800 Subject: [PATCH 0254/1897] Revert autoclosing of ```` ``` ```` in md (#202290) Fixes #192676 Turns out to be more annoying than the value it provides --- extensions/markdown-basics/language-configuration.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/extensions/markdown-basics/language-configuration.json b/extensions/markdown-basics/language-configuration.json index 40f4be77c1d..0d36cf29d7a 100644 --- a/extensions/markdown-basics/language-configuration.json +++ b/extensions/markdown-basics/language-configuration.json @@ -46,10 +46,6 @@ "open": "`", "close": "`" }, - { - "open": "```", - "close": "```" - }, ], "surroundingPairs": [ [ From 3822928d403f8fc095f04ec7028a09f1d37db8e9 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Thu, 11 Jan 2024 17:30:55 -0600 Subject: [PATCH 0255/1897] Change icon for quick search individual result (#202113) --- src/vs/workbench/contrib/search/browser/searchIcons.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/searchIcons.ts b/src/vs/workbench/contrib/search/browser/searchIcons.ts index e06535f6e6b..f81dc87d394 100644 --- a/src/vs/workbench/contrib/search/browser/searchIcons.ts +++ b/src/vs/workbench/contrib/search/browser/searchIcons.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; export const searchDetailsIcon = registerIcon('search-details', Codicon.ellipsis, localize('searchDetailsIcon', 'Icon to make search details visible.')); -export const searchActivityBarIcon = registerIcon('search-see-more', Codicon.arrowRight, localize('searchSeeMoreIcon', 'Icon to view more context in the search view.')); +export const searchActivityBarIcon = registerIcon('search-see-more', Codicon.linkExternal, localize('searchSeeMoreIcon', 'Icon to view more context in the search view.')); export const searchShowContextIcon = registerIcon('search-show-context', Codicon.listSelection, localize('searchShowContextIcon', 'Icon for toggle the context in the search editor.')); export const searchHideReplaceIcon = registerIcon('search-hide-replace', Codicon.chevronRight, localize('searchHideReplaceIcon', 'Icon to collapse the replace section in the search view.')); From 6e9b9a45433997677d27fc2cedf4f87748ef7844 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 11 Jan 2024 16:50:47 -0800 Subject: [PATCH 0256/1897] Show warning when pasteAs fails (#202300) For #188736 Show an inline message when a requested pasteAs keybinding fails --- .../browser/copyPasteController.ts | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts index 4a5da94b99f..10654d61a52 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts @@ -33,6 +33,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { PostEditWidgetManager } from './postEditWidget'; +import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; export const changePasteTypeCommandId = 'editor.changePasteType'; @@ -212,6 +213,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi return; } + MessageController.get(this._editor)?.closeMessage(); this._currentPasteOperation?.cancel(); this._currentPasteOperation = undefined; @@ -221,7 +223,10 @@ export class CopyPasteController extends Disposable implements IEditorContributi return; } - if (!this.isPasteAsEnabled()) { + if ( + !this.isPasteAsEnabled() + && !this._pasteAsActionContext // Still enable if paste as was explicitly requested + ) { return; } @@ -240,8 +245,19 @@ export class CopyPasteController extends Disposable implements IEditorContributi const allProviders = this._languageFeaturesService.documentPasteEditProvider .ordered(model) - .filter(provider => provider.pasteMimeTypes?.some(type => matchesMimeType(type, allPotentialMimeTypes))); + .filter(provider => { + if (this._pasteAsActionContext?.preferredId) { + if (this._pasteAsActionContext.preferredId !== provider.id) { + return false; + } + } + + return provider.pasteMimeTypes?.some(type => matchesMimeType(type, allPotentialMimeTypes)); + }); if (!allProviders.length) { + if (this._pasteAsActionContext?.preferredId) { + this.showPasteAsNoEditMessage(selections, this._pasteAsActionContext?.preferredId); + } return; } @@ -258,6 +274,10 @@ export class CopyPasteController extends Disposable implements IEditorContributi } } + private showPasteAsNoEditMessage(selections: readonly Selection[], editId: string) { + MessageController.get(this._editor)?.showMessage(localize('pasteAsError', "No paste edits for '{0}' found", editId), selections[0].getStartPosition()); + } + private doPasteInline(allProviders: readonly DocumentPasteEditProvider[], selections: readonly Selection[], dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, context: DocumentPasteContext): void { const p = createCancelablePromise(async (token) => { const editor = this._editor; @@ -339,6 +359,9 @@ export class CopyPasteController extends Disposable implements IEditorContributi } if (!providerEdits.length) { + if (context.only) { + this.showPasteAsNoEditMessage(selections, context.only); + } return; } From 58171d859b07ed8b8d1cfc0fed7f070bfdb8f160 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 11 Jan 2024 17:22:26 -0800 Subject: [PATCH 0257/1897] Reduce delays in md test (#202295) Fixes #149712 --- src/vs/base/test/browser/markdownRenderer.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts index a394e18e001..0c02a319432 100644 --- a/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -97,7 +97,7 @@ suite('MarkdownRenderer', () => { codeBlockRenderer: simpleCodeBlockRenderer }); result.dispose(); - setTimeout(resolve, 50); + setTimeout(resolve, 10); }); }); @@ -116,8 +116,8 @@ suite('MarkdownRenderer', () => { setTimeout(() => { result.dispose(); resolveCodeBlockRendering(document.createElement('code')); - setTimeout(resolve, 50); - }, 50); + setTimeout(resolve, 10); + }, 10); }); }); From d81d32b9408cd50f2d99c4ea7b3fdbcbed5bec00 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 11 Jan 2024 17:23:13 -0800 Subject: [PATCH 0258/1897] Re-render markdown preview when linkify setting changes (#200599) --- .../src/markdownEngine.ts | 9 +++++---- .../src/preview/previewConfig.ts | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts index 8d67bf6f9c0..0ef05452e4f 100644 --- a/extensions/markdown-language-features/src/markdownEngine.ts +++ b/extensions/markdown-language-features/src/markdownEngine.ts @@ -8,6 +8,7 @@ import type Token = require('markdown-it/lib/token'); import * as vscode from 'vscode'; import { ILogger } from './logging'; import { MarkdownContributionProvider } from './markdownExtensions'; +import { MarkdownPreviewConfiguration } from './preview/previewConfig'; import { Slugifier } from './slugify'; import { ITextDocument } from './types/textDocument'; import { WebviewResourceProvider } from './util/resources'; @@ -237,11 +238,11 @@ export class MarkdownItEngine implements IMdParser { } private _getConfig(resource?: vscode.Uri): MarkdownItConfig { - const config = vscode.workspace.getConfiguration('markdown', resource ?? null); + const config = MarkdownPreviewConfiguration.getForResource(resource ?? null); return { - breaks: config.get('preview.breaks', false), - linkify: config.get('preview.linkify', true), - typographer: config.get('preview.typographer', false) + breaks: config.previewLineBreaks, + linkify: config.previewLinkify, + typographer: config.previewTypographer, }; } diff --git a/extensions/markdown-language-features/src/preview/previewConfig.ts b/extensions/markdown-language-features/src/preview/previewConfig.ts index 22191ba8945..01c9a66c32c 100644 --- a/extensions/markdown-language-features/src/preview/previewConfig.ts +++ b/extensions/markdown-language-features/src/preview/previewConfig.ts @@ -7,13 +7,17 @@ import * as vscode from 'vscode'; import { equals } from '../util/arrays'; export class MarkdownPreviewConfiguration { - public static getForResource(resource: vscode.Uri) { + public static getForResource(resource: vscode.Uri | null) { return new MarkdownPreviewConfiguration(resource); } public readonly scrollBeyondLastLine: boolean; public readonly wordWrap: boolean; - public readonly lineBreaks: boolean; + + public readonly previewLineBreaks: boolean; + public readonly previewLinkify: boolean; + public readonly previewTypographer: boolean; + public readonly doubleClickToSwitchToEditor: boolean; public readonly scrollEditorWithPreview: boolean; public readonly scrollPreviewWithEditor: boolean; @@ -24,7 +28,7 @@ export class MarkdownPreviewConfiguration { public readonly fontFamily: string | undefined; public readonly styles: readonly string[]; - private constructor(resource: vscode.Uri) { + private constructor(resource: vscode.Uri | null) { const editorConfig = vscode.workspace.getConfiguration('editor', resource); const markdownConfig = vscode.workspace.getConfiguration('markdown', resource); const markdownEditorConfig = vscode.workspace.getConfiguration('[markdown]', resource); @@ -38,7 +42,11 @@ export class MarkdownPreviewConfiguration { this.scrollPreviewWithEditor = !!markdownConfig.get('preview.scrollPreviewWithEditor', true); this.scrollEditorWithPreview = !!markdownConfig.get('preview.scrollEditorWithPreview', true); - this.lineBreaks = !!markdownConfig.get('preview.breaks', false); + + this.previewLineBreaks = !!markdownConfig.get('preview.breaks', false); + this.previewLinkify = !!markdownConfig.get('preview.linkify', true); + this.previewTypographer = !!markdownConfig.get('preview.typographer', false); + this.doubleClickToSwitchToEditor = !!markdownConfig.get('preview.doubleClickToSwitchToEditor', true); this.markEditorSelection = !!markdownConfig.get('preview.markEditorSelection', true); @@ -61,7 +69,7 @@ export class MarkdownPreviewConfiguration { return equals(this.styles, otherConfig.styles); } - [key: string]: any; + readonly [key: string]: any; } export class MarkdownPreviewConfigurationManager { From 33acc14ecefd4fd06345ef1aa9b7f1d20d79102a Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 11 Jan 2024 17:36:53 -0800 Subject: [PATCH 0259/1897] Fix generate cell at the top of the document. (#202302) --- .../view/cellParts/chat/cellChatActions.ts | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts index f8a9e4488b6..378668f09dc 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts @@ -5,12 +5,14 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ILanguageService } from 'vs/editor/common/languages/language'; import { localize } from 'vs/nls'; import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; import { INotebookCellActionContext, NotebookAction, NotebookCellAction } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { insertNewCell } from 'vs/workbench/contrib/notebook/browser/controller/insertCellActions'; import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS, NotebookCellChatController } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; @@ -273,7 +275,45 @@ registerAction2(class extends NotebookCellAction { CTX_INLINE_CHAT_HAS_PROVIDER, ContextKeyExpr.equals(`config.${NotebookSetting.cellChat}`, true) ) - }, + } + ] + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + const newCell = await insertNewCell(accessor, context, CellKind.Code, 'below', true); + + if (!newCell) { + return; + } + await context.notebookEditor.focusNotebookCell(newCell, 'container'); + const ctrl = NotebookCellChatController.get(newCell); + if (!ctrl) { + return; + } + + context.notebookEditor.getCellsInRange().forEach(cell => { + const cellCtrl = NotebookCellChatController.get(cell); + if (cellCtrl) { + cellCtrl.dismiss(false); + } + }); + + ctrl.show(); + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super( + { + id: 'notebook.cell.insertCodeCellWithChatAtTop', + title: { + value: '$(sparkle) ' + localize('notebookActions.menu.insertCodeCellWithChat', "Generate"), + original: '$(sparkle) Generate', + }, + tooltip: localize('notebookActions.menu.insertCodeCellWithChat.tooltip', "Generate Code Cell with Chat"), + menu: [ { id: MenuId.NotebookCellListTop, group: 'inline', @@ -289,7 +329,8 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const newCell = await insertNewCell(accessor, context, CellKind.Code, 'below', true); + const languageService = accessor.get(ILanguageService); + const newCell = insertCell(languageService, context.notebookEditor, 0, CellKind.Code, 'above', undefined, true); if (!newCell) { return; From d1a524e864b80294ff13c29afc76255bf1868e5b Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 11 Jan 2024 22:56:56 -0300 Subject: [PATCH 0260/1897] Restore ChatVariableValue#kind --- src/vs/workbench/api/common/extHostTypeConverters.ts | 2 ++ src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index bd2120b89ab..5e2fcc991a8 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2253,6 +2253,7 @@ export namespace ChatVariable { export function to(variable: IChatRequestVariableValue): vscode.ChatVariableValue { return { level: ChatVariableLevel.to(variable.level), + kind: variable.kind, value: isUriComponents(variable.value) ? URI.revive(variable.value) : variable.value, description: variable.description }; @@ -2261,6 +2262,7 @@ export namespace ChatVariable { export function from(variable: vscode.ChatVariableValue): IChatRequestVariableValue { return { level: ChatVariableLevel.from(variable.level), + kind: variable.kind, value: variable.value, description: variable.description }; diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index 2ad0993986f..51fd7baf32d 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -129,4 +129,11 @@ declare module 'vscode' { readonly result: ChatAgentResult2; readonly action: ChatAgentCopyAction | ChatAgentInsertAction | ChatAgentTerminalAction | ChatAgentCommandAction | ChatAgentSessionFollowupAction | ChatAgentBugReportAction; } + + export interface ChatVariableValue { + /** + * An optional type tag for extensions to communicate the kind of the variable. An extension might use it to interpret the shape of `value`. + */ + kind?: string; + } } From a1a9707400d8e2502418b4ed5aa9676a06db6211 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jan 2024 19:09:15 -0800 Subject: [PATCH 0261/1897] Bump follow-redirects from 1.15.3 to 1.15.4 in /extensions/typescript-language-features (#202211) Bump follow-redirects in /extensions/typescript-language-features Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.4. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.4) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- extensions/typescript-language-features/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index e825c13b1ca..482239a3cbb 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -167,9 +167,9 @@ delayed-stream@~1.0.0: integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== follow-redirects@^1.15.0: - version "1.15.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" - integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== + version "1.15.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" + integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== form-data@^4.0.0: version "4.0.0" From 29bef5673ebb1fc3a59b90c9d8bfd2bc61c3f011 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 11 Jan 2024 20:57:50 -0800 Subject: [PATCH 0262/1897] testing: get coverage hovers working better (#202307) Move away from using decoration-provided hovers, because I want to control more exactly what hover information is shown for a line versus just concatenating all hover information from each statement/branch/fn on that line. Does that, and also adds a toggle to show persistent inline coverage: ![](https://memes.peet.io/img/24-01-8ef2413d-5fe2-4ee1-ba1d-8572ff4fe0f1.png) --- .../browser/codeCoverageDecorations.ts | 252 ++++++++++++++++-- .../contrib/testing/browser/media/testing.css | 1 + .../contrib/testing/browser/theme.ts | 8 +- 3 files changed, 230 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts index 7fb11557806..b69910cd580 100644 --- a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts @@ -3,42 +3,65 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as dom from 'vs/base/browser/dom'; +import { HoverWidget } from 'vs/base/browser/ui/hover/hoverWidget'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { MarkdownString } from 'vs/base/common/htmlContent'; +import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { Lazy } from 'vs/base/common/lazy'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; -import { autorun, derived, observableFromEvent } from 'vs/base/common/observable'; -import { ICodeEditor, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { autorun, derived, observableFromEvent, observableValue } from 'vs/base/common/observable'; +import { isDefined } from 'vs/base/common/types'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model'; +import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/browser/hoverOperation'; import { localize } from 'vs/nls'; +import { Categories } from 'vs/platform/action/common/actionCommonCategories'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ILogService } from 'vs/platform/log/common/log'; import { FileCoverage } from 'vs/workbench/contrib/testing/common/testCoverage'; import { ITestCoverageService } from 'vs/workbench/contrib/testing/common/testCoverageService'; import { CoverageDetails, DetailType, IStatementCoverage } from 'vs/workbench/contrib/testing/common/testTypes'; +import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; const MAX_HOVERED_LINES = 30; const CLASS_HIT = 'coverage-deco-hit'; const CLASS_MISS = 'coverage-deco-miss'; +const TOGGLE_INLINE_COMMAND_TEXT = localize('testing.toggleInlineCoverage', 'Toggle Inline Coverage'); +const TOGGLE_INLINE_COMMAND_ID = 'testing.toggleInlineCoverage'; export class CodeCoverageDecorations extends Disposable implements IEditorContribution { + public static showInline = observableValue('inlineCoverage', false); + private loadingCancellation?: CancellationTokenSource; private readonly displayedStore = this._register(new DisposableStore()); private readonly hoveredStore = this._register(new DisposableStore()); + private readonly lineHoverWidget: Lazy; private decorationIds = new Map; }>(); private hoveredLineNumber?: number; + private details?: CoverageDetailsModel; constructor( private readonly editor: ICodeEditor, + @IInstantiationService instantiationService: IInstantiationService, @ITestCoverageService coverage: ITestCoverageService, @ILogService private readonly log: ILogService, ) { super(); + this.lineHoverWidget = new Lazy(() => this._register(instantiationService.createInstance(LineHoverWidget, this.editor))); + const modelObs = observableFromEvent(editor.onDidChangeModel, () => editor.getModel()); const fileCoverage = derived(reader => { @@ -58,7 +81,7 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri this._register(autorun(reader => { const c = fileCoverage.read(reader); if (c) { - this.apply(editor.getModel()!, c); + this.apply(editor.getModel()!, c, CodeCoverageDecorations.showInline.read(reader)); } else { this.clear(); } @@ -67,6 +90,8 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri this._register(editor.onMouseMove(e => { if (e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS) { this.hoverLineNumber(editor.getModel()!, e.target.position.lineNumber); + } else if (this.lineHoverWidget.hasValue && this.lineHoverWidget.value.getDomNode().contains(e.target.element)) { + // don't dismiss the hover } else { this.hoveredStore.clear(); } @@ -109,11 +134,16 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri } }); - this.hoveredStore.add(this.editor.onMouseLeave(e => { + this.lineHoverWidget.value.startShowingAt(lineNumber, this.details!); + + this.hoveredStore.add(this.editor.onMouseLeave(() => { this.hoveredStore.clear(); })); this.hoveredStore.add(toDisposable(() => { + this.lineHoverWidget.value.hide(); + this.hoveredLineNumber = -1; + model.changeDecorations(e => { for (const id of toEnable) { const deco = this.decorationIds.get(id); @@ -125,51 +155,46 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri })); } - private async apply(model: ITextModel, coverage: FileCoverage) { + private async apply(model: ITextModel, coverage: FileCoverage, showInlineByDefault: boolean) { const details = await this.loadDetails(coverage); if (!details) { return this.clear(); } - const detailModel = new CoverageDetailsModel(details); + this.displayedStore.clear(); + const detailModel = this.details = new CoverageDetailsModel(details); model.changeDecorations(e => { for (const { metadata: detail, range } of detailModel.ranges) { if (detail.type === DetailType.Branch) { const hits = detail.detail.branches![detail.branch].count; const cls = hits ? CLASS_HIT : CLASS_MISS; - const opts: IModelDecorationOptions = { + const options: IModelDecorationOptions = { showIfCollapsed: false, description: 'coverage-gutter', - lineNumberHoverMessage: new MarkdownString() - .appendCodeblock(model.getLanguageId(), model.getValueInRange(range)) - .appendText(localize('testing.branchHitCount', 'Branch hit count: {0}', hits)), lineNumberClassName: `coverage-deco-gutter ${cls}`, }; - this.decorationIds.set(e.addDecoration(range, opts), { - options: opts, - hoverOptions: { - className: `coverage-deco-inline ${cls}`, - } - }); + const hoverOptions: Partial = { className: `coverage-deco-inline ${cls}` }; + if (showInlineByDefault) { + Object.assign(options, hoverOptions); + } + + this.decorationIds.set(e.addDecoration(range, options), { options, hoverOptions }); } else if (detail.type === DetailType.Statement) { const cls = detail.count ? CLASS_HIT : CLASS_MISS; - const opts: IModelDecorationOptions = { + const options: IModelDecorationOptions = { showIfCollapsed: false, description: 'coverage-inline', - lineNumberHoverMessage: new MarkdownString() - .appendCodeblock(model.getLanguageId(), model.getValueInRange(range)) - .appendText(localize('testing.hitCount', 'Hit count: {0}', detail.count)), lineNumberClassName: `coverage-deco-gutter ${cls}`, }; - this.decorationIds.set(e.addDecoration(range, opts), { - options: opts, - hoverOptions: { - className: `coverage-deco-inline ${cls}`, - } - }); + const hoverOptions: Partial = { className: `coverage-deco-inline ${cls}` }; + if (showInlineByDefault) { + Object.assign(options, hoverOptions); + } + + this.decorationIds.set(e.addDecoration(range, options), { options, hoverOptions }); } } }); @@ -290,3 +315,176 @@ function tidyLocation(location: Range | Position): Range { return location; } + +class LineHoverComputer implements IHoverComputer { + public line = -1; + public lineContents = ''; + public details!: CoverageDetailsModel; + + constructor(@IKeybindingService private readonly keybindingService: IKeybindingService) { } + + /** @inheritdoc */ + public computeSync(): IMarkdownString[] { + const bestDetails: DetailRange[] = []; + let bestLine = -1; + for (const detail of this.details.ranges) { + if (detail.range.startLineNumber > this.line) { + break; + } + if (detail.range.endLineNumber < this.line) { + continue; + } + if (detail.range.startLineNumber !== bestLine) { + bestDetails.length = 0; + } + bestLine = detail.range.startLineNumber; + bestDetails.push(detail); + } + + const strs = bestDetails.map(({ range, metadata: detail }) => { + if (detail.type === DetailType.Function) { + return new MarkdownString().appendMarkdown(localize('coverage.fnExecutedCount', 'Function `{0}` was executed {1} time(s).', detail.name, detail.count)); + } else if (detail.type === DetailType.Statement) { + const text = normalizeName(this.lineContents.slice(range.startColumn - 1, range.endLineNumber === range.startLineNumber ? range.endColumn + 1 : undefined) || ``); + const str = new MarkdownString(); + if (detail.branches?.length) { + const covered = detail.branches.filter(b => b.count > 0).length; + str.appendMarkdown(localize('coverage.branches', '{0} of {1} of branches in `{2}` were covered.', covered, detail.branches.length, text)); + } else { + str.appendMarkdown(localize('coverage.codeExecutedCount', '`{0}` was executed {1} time(s).', text, detail.count)); + } + return str; + } else { + return undefined; + } + }).filter(isDefined); + + if (strs.length) { + const s = new MarkdownString().appendMarkdown(`[${TOGGLE_INLINE_COMMAND_TEXT}](command:${TOGGLE_INLINE_COMMAND_ID})`); + s.isTrusted = true; + const binding = this.keybindingService.lookupKeybinding(TOGGLE_INLINE_COMMAND_ID); + if (binding) { + s.appendText(` (${binding.getLabel()})`); + } + strs.push(s); + } + + return strs; + } +} + +function normalizeName(functionNameOrCode: string) { + functionNameOrCode = functionNameOrCode.replace(/[\n\r`]/g, ''); + if (functionNameOrCode.length > 50) { + functionNameOrCode = functionNameOrCode.slice(0, 40) + '...'; + } + return functionNameOrCode; +} + +class LineHoverWidget extends Disposable implements IOverlayWidget { + public static readonly ID = 'editor.contrib.testingCoverageLineHoverWidget'; + + private readonly computer: LineHoverComputer; + private readonly hoverOperation: HoverOperation; + private readonly hover = this._register(new HoverWidget()); + private readonly renderDisposables = this._register(new DisposableStore()); + private readonly markdownRenderer: MarkdownRenderer; + + constructor(private readonly editor: ICodeEditor, @IInstantiationService instantiationService: IInstantiationService) { + super(); + this.computer = instantiationService.createInstance(LineHoverComputer); + this.markdownRenderer = this._register(instantiationService.createInstance(MarkdownRenderer, { editor: this.editor })); + this.hoverOperation = this._register(new HoverOperation(this.editor, this.computer)); + this.hover.containerDomNode.classList.add('hidden'); + this.hoverOperation.onResult(result => { + if (result.value.length) { + this.render(result.value); + } else { + this.hide(); + } + }); + this.editor.addOverlayWidget(this); + } + + /** @inheritdoc */ + getId(): string { + return LineHoverWidget.ID; + } + + /** @inheritdoc */ + public getDomNode(): HTMLElement { + return this.hover.containerDomNode; + } + + /** @inheritdoc */ + public getPosition(): IOverlayWidgetPosition | null { + return null; + } + + /** @inheritdoc */ + public override dispose(): void { + this.editor.removeOverlayWidget(this); + super.dispose(); + } + + /** Shows the hover widget at the given line */ + public startShowingAt(lineNumber: number, details: CoverageDetailsModel) { + this.hide(); + this.computer.line = lineNumber; + this.computer.lineContents = this.editor.getModel()?.getLineContent(lineNumber) || ''; + this.computer.details = details; + this.hoverOperation.start(HoverStartMode.Delayed); + } + + /** Hides the hover widget */ + public hide() { + this.hoverOperation.cancel(); + this.hover.containerDomNode.classList.add('hidden'); + } + + private render(elements: IMarkdownString[]) { + const { hover: h, editor: editor } = this; + const fragment = document.createDocumentFragment(); + + for (const msg of elements) { + const markdownHoverElement = dom.$('div.hover-row.markdown-hover'); + const hoverContentsElement = dom.append(markdownHoverElement, dom.$('div.hover-contents')); + const renderedContents = this.renderDisposables.add(this.markdownRenderer.render(msg)); + hoverContentsElement.appendChild(renderedContents.element); + fragment.appendChild(markdownHoverElement); + } + + dom.clearNode(h.contentsDomNode); + h.contentsDomNode.appendChild(fragment); + + h.containerDomNode.classList.remove('hidden'); + const editorLayout = editor.getLayoutInfo(); + const topForLineNumber = editor.getTopForLineNumber(this.computer.line); + const editorScrollTop = editor.getScrollTop(); + const lineHeight = editor.getOption(EditorOption.lineHeight); + const nodeHeight = h.containerDomNode.clientHeight; + const top = topForLineNumber - editorScrollTop - ((nodeHeight - lineHeight) / 2); + const left = editorLayout.lineNumbersLeft + editorLayout.lineNumbersWidth; + h.containerDomNode.style.left = `${left}px`; + h.containerDomNode.style.top = `${Math.max(Math.round(top), 0)}px`; + } +} + +registerAction2(class ToggleInlineCoverage extends Action2 { + constructor() { + super({ + id: TOGGLE_INLINE_COMMAND_ID, + title: { value: localize('coverage.toggleInline', "Toggle Inline Coverage"), original: 'Toggle Inline Coverage' }, + category: Categories.Test, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.Semicolon, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI), + }, + precondition: TestingContextKeys.isTestCoverageOpen, + }); + } + + public run() { + CodeCoverageDecorations.showInline.set(!CodeCoverageDecorations.showInline.get(), undefined); + } +}); diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index d40e482a1c7..f2c0c7d15e7 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -420,6 +420,7 @@ .coverage-deco-gutter { z-index: 0; } + .coverage-deco-gutter::before { content: ''; position: absolute; diff --git a/src/vs/workbench/contrib/testing/browser/theme.ts b/src/vs/workbench/contrib/testing/browser/theme.ts index e73428ef908..24654b3523f 100644 --- a/src/vs/workbench/contrib/testing/browser/theme.ts +++ b/src/vs/workbench/contrib/testing/browser/theme.ts @@ -94,8 +94,8 @@ export const testingCoveredBackground = registerColor('testing.coveredBackground }, localize('testing.coveredBackground', 'Background color of text that was covered.')); export const testingCoveredGutterBackground = registerColor('testing.coveredGutterBackground', { - dark: diffInserted, - light: diffInserted, + dark: transparent(diffInserted, 0.8), + light: transparent(diffInserted, 0.8), hcDark: null, hcLight: null }, localize('testing.coveredGutterBackground', 'Gutter color of regions where code was covered.')); @@ -108,8 +108,8 @@ export const testingUncoveredBackground = registerColor('testing.uncoveredBackgr }, localize('testing.uncoveredBackground', 'Background color of text that was not covered.')); export const testingUncoveredGutterBackground = registerColor('testing.uncoveredGutterBackground', { - dark: diffRemoved, - light: diffRemoved, + dark: transparent(diffRemoved, 1.5), + light: transparent(diffRemoved, 1.5), hcDark: null, hcLight: null }, localize('testing.uncoveredGutterBackground', 'Gutter color of regions where code not covered.')); From 87b02e9c4ee557af5d89e17b5ed7302fc338c133 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Thu, 11 Jan 2024 22:21:29 -0800 Subject: [PATCH 0263/1897] align auxiliary window zooming to main titlebar behavior (#201903) * align auxiliary window zooming to main titlebar behavior fixes #200435 * address feedback and make methods more sane --- .../browser/parts/titlebar/titlebarPart.ts | 36 +++++++++++-------- .../parts/titlebar/titlebarPart.ts | 15 ++++++-- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index b85b76a8051..816fdf355fd 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -117,7 +117,7 @@ export class BrowserTitleService extends MultiWindowParts i } protected doCreateAuxiliaryTitlebarPart(container: HTMLElement, editorGroupsContainer: IEditorGroupsContainer): BrowserTitlebarPart & IAuxiliaryTitlebarPart { - return this.instantiationService.createInstance(AuxiliaryBrowserTitlebarPart, container, editorGroupsContainer); + return this.instantiationService.createInstance(AuxiliaryBrowserTitlebarPart, container, editorGroupsContainer, this.mainPart); } //#endregion @@ -159,9 +159,6 @@ class TitlebarPartHoverDelegate implements IHoverDelegate { } export class BrowserTitlebarPart extends Part implements ITitlebarPart { - - declare readonly _serviceBrand: undefined; - //#region IView readonly minimumWidth: number = 0; @@ -170,7 +167,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { get minimumHeight(): number { const value = this.isCommandCenterVisible || (isWeb && isWCOEnabled()) ? 35 : 30; - return value / (this.useCounterZoom ? getZoomFactor(getWindow(this.element)) : 1); + return value / (this.preventZoom ? getZoomFactor(getWindow(this.element)) : 1); } get maximumHeight(): number { return this.minimumHeight; } @@ -713,19 +710,19 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { return !this.isAuxiliary && this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP; } - protected get useCounterZoom(): boolean { + get hasZoomableElements(): boolean { + const hasMenubar = !(this.currentMenubarVisibility === 'hidden' || this.currentMenubarVisibility === 'compact' || (!isWeb && isMacintosh)); + const hasCommandCenter = this.isCommandCenterVisible; + const hasToolBarActions = this.layoutControlEnabled || this.editorActionsEnabled || this.activityActionsEnabled; + return hasMenubar || hasCommandCenter || hasToolBarActions; + } + get preventZoom(): boolean { // Prevent zooming behavior if any of the following conditions are met: // 1. Shrinking below the window control size (zoom < 1) // 2. No custom items are present in the title bar - const zoomFactor = getZoomFactor(getWindow(this.element)); - - const noMenubar = this.currentMenubarVisibility === 'hidden' || this.currentMenubarVisibility === 'compact' || (!isWeb && isMacintosh); - const noCommandCenter = !this.isCommandCenterVisible; - const noToolBarActions = !this.layoutControlEnabled && !this.editorActionsEnabled && !this.activityActionsEnabled; - - return zoomFactor < 1 || (noMenubar && noCommandCenter && noToolBarActions); + return getZoomFactor(getWindow(this.element)) < 1 || !this.hasZoomableElements; } override layout(width: number, height: number): void { @@ -741,7 +738,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { const zoomFactor = getZoomFactor(getWindow(this.element)); this.element.style.setProperty('--zoom-factor', zoomFactor.toString()); - this.rootContainer.classList.toggle('counter-zoom', this.useCounterZoom); + this.rootContainer.classList.toggle('counter-zoom', this.preventZoom); if (this.customMenubar) { const menubarDimension = new Dimension(0, dimension.height); @@ -799,6 +796,7 @@ export class AuxiliaryBrowserTitlebarPart extends BrowserTitlebarPart implements constructor( readonly container: HTMLElement, editorGroupsContainer: IEditorGroupsContainer, + private readonly mainTitlebar: BrowserTitlebarPart, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @IBrowserWorkbenchEnvironmentService environmentService: IBrowserWorkbenchEnvironmentService, @@ -817,4 +815,14 @@ export class AuxiliaryBrowserTitlebarPart extends BrowserTitlebarPart implements const id = AuxiliaryBrowserTitlebarPart.COUNTER++; super(`workbench.parts.auxiliaryTitle.${id}`, getWindow(container), editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, hoverService, editorGroupService, editorService, menuService, keybindingService); } + + override get preventZoom(): boolean { + // Prevent zooming behavior if any of the following conditions are met: + // 1. Shrinking below the window control size (zoom < 1) + // 2. No custom items are present in the main title bar + // The auxiliary title bar never contains any zoomable items itself, + // but we want to match the behavior of the main title bar. + + return getZoomFactor(getWindow(this.element)) < 1 || !this.mainTitlebar.hasZoomableElements; + } } diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index 8ebc1bf861c..af61278c198 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -38,7 +38,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { return super.minimumHeight; } - return (this.isCommandCenterVisible ? 35 : this.macTitlebarSize) / (this.useCounterZoom ? getZoomFactor(getWindow(this.element)) : 1); + return (this.isCommandCenterVisible ? 35 : this.macTitlebarSize) / (this.preventZoom ? getZoomFactor(getWindow(this.element)) : 1); } override get maximumHeight(): number { return this.minimumHeight; } @@ -305,6 +305,7 @@ export class AuxiliaryNativeTitlebarPart extends NativeTitlebarPart implements I constructor( readonly container: HTMLElement, editorGroupsContainer: IEditorGroupsContainer, + private readonly mainTitlebar: BrowserTitlebarPart, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @@ -324,6 +325,16 @@ export class AuxiliaryNativeTitlebarPart extends NativeTitlebarPart implements I const id = AuxiliaryNativeTitlebarPart.COUNTER++; super(`workbench.parts.auxiliaryTitle.${id}`, getWindow(container), editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, nativeHostService, hoverService, editorGroupService, editorService, menuService, keybindingService); } + + override get preventZoom(): boolean { + // Prevent zooming behavior if any of the following conditions are met: + // 1. Shrinking below the window control size (zoom < 1) + // 2. No custom items are present in the main title bar + // The auxiliary title bar never contains any zoomable items itself, + // but we want to match the behavior of the main title bar. + + return getZoomFactor(getWindow(this.element)) < 1 || !this.mainTitlebar.hasZoomableElements; + } } export class NativeTitleService extends BrowserTitleService { @@ -333,6 +344,6 @@ export class NativeTitleService extends BrowserTitleService { } protected override doCreateAuxiliaryTitlebarPart(container: HTMLElement, editorGroupsContainer: IEditorGroupsContainer): AuxiliaryNativeTitlebarPart { - return this.instantiationService.createInstance(AuxiliaryNativeTitlebarPart, container, editorGroupsContainer); + return this.instantiationService.createInstance(AuxiliaryNativeTitlebarPart, container, editorGroupsContainer, this.mainPart); } } From b0abf6638b6dde2c0458dd8d24c53ee574d7df88 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 12 Jan 2024 09:11:05 +0100 Subject: [PATCH 0264/1897] fix https://github.com/microsoft/vscode/issues/202164 (#202317) * remove start-method from strategies * rename - activeSession to session * keep strategy alive when pausing session, let strategy participate in pause, allow to call LiveStrategy#renderChanges repeatedly without recomputing hunks * reset diff toggled state * make sure to reset hunk display on done (dipose, accept, discard) * reset diff state in pause, not hide function --- .../emptyTextEditorHint.ts | 4 +- .../browser/inlineChatController.ts | 277 ++++++----- .../inlineChat/browser/inlineChatSession.ts | 8 +- .../browser/inlineChatStrategies.ts | 440 ++++++++++-------- 4 files changed, 399 insertions(+), 330 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts index 76c1a353154..ba47bfed2e7 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts @@ -93,8 +93,8 @@ export class EmptyTextEditorHintContribution implements IEditorContribution { this.textHintContentWidget?.dispose(); } })); - this.toDispose.push(inlineChatSessionService.onDidEndSession(editor => { - if (this.editor === editor) { + this.toDispose.push(inlineChatSessionService.onDidEndSession(e => { + if (this.editor === e.editor) { this.update(); } })); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index f9c6a7c2c20..158e709bc01 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -122,7 +122,8 @@ export class InlineChatController implements IEditorContribution { private readonly _sessionStore: DisposableStore = this._store.add(new DisposableStore()); private readonly _stashedSession: MutableDisposable = this._store.add(new MutableDisposable()); - private _activeSession?: Session; + private readonly _pausedStrategies = new Map(); + private _session?: Session; private _strategy?: EditModeStrategy; private _ignoreModelContentChanged = false; @@ -150,7 +151,7 @@ export class InlineChatController implements IEditorContribution { this._zone = new Lazy(() => this._store.add(_instaService.createInstance(InlineChatZoneWidget, this._editor))); this._store.add(this._editor.onDidChangeModel(async e => { - if (this._activeSession || !e.newModelUrl) { + if (this._session || !e.newModelUrl) { return; } @@ -165,6 +166,11 @@ export class InlineChatController implements IEditorContribution { })); this._log('NEW controller'); + this._store.add(this._inlineChatSessionService.onDidEndSession(e => { + this._pausedStrategies.get(e.session)?.dispose(); + this._pausedStrategies.delete(e.session); + })); + InlineChatController._promptHistory = JSON.parse(_storageService.get(InlineChatController._storageKey, StorageScope.PROFILE, '[]')); this._historyUpdate = (prompt: string) => { const idx = InlineChatController._promptHistory.indexOf(prompt); @@ -181,8 +187,8 @@ export class InlineChatController implements IEditorContribution { dispose(): void { this._strategy?.dispose(); this._stashedSession.clear(); - if (this._activeSession) { - this._inlineChatSessionService.releaseSession(this._activeSession); + if (this._session) { + this._inlineChatSessionService.releaseSession(this._session); } this._store.dispose(); this._log('controller disposed'); @@ -239,8 +245,8 @@ export class InlineChatController implements IEditorContribution { } catch (error) { // this should not happen but when it does make sure to tear down the UI and everything onUnexpectedError(error); - if (this._activeSession) { - this._inlineChatSessionService.releaseSession(this._activeSession); + if (this._session) { + this._inlineChatSessionService.releaseSession(this._session); } this[State.PAUSE](); @@ -260,7 +266,7 @@ export class InlineChatController implements IEditorContribution { } private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { - assertType(this._activeSession === undefined); + assertType(this._session === undefined); assertType(this._editor.hasModel()); let session: Session | undefined = options.existingSession; @@ -312,25 +318,32 @@ export class InlineChatController implements IEditorContribution { return State.CANCEL; } - switch (session.editMode) { - case EditMode.Live: - this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._zone.value); - break; - case EditMode.Preview: - this._strategy = this._instaService.createInstance(PreviewStrategy, session, this._zone.value); - break; - case EditMode.LivePreview: - default: - this._strategy = this._instaService.createInstance(LivePreviewStrategy, session, this._editor, this._zone.value); - break; + if (this._pausedStrategies.has(session)) { + // maybe a strategy was previously paused, use it + this._strategy = this._pausedStrategies.get(session)!; + this._pausedStrategies.delete(session); + } else { + // create a new strategy + switch (session.editMode) { + case EditMode.Live: + this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._zone.value); + break; + case EditMode.Preview: + this._strategy = this._instaService.createInstance(PreviewStrategy, session, this._zone.value); + break; + case EditMode.LivePreview: + default: + this._strategy = this._instaService.createInstance(LivePreviewStrategy, session, this._editor, this._zone.value); + break; + } } - this._activeSession = session; + this._session = session; return State.INIT_UI; } private async [State.INIT_UI](options: InlineChatRunOptions): Promise { - assertType(this._activeSession); + assertType(this._session); assertType(this._strategy); // hide/cancel inline completions when invoking IE @@ -350,22 +363,22 @@ export class InlineChatController implements IEditorContribution { wholeRangeDecoration.set(newDecorations); }; this._sessionStore.add(toDisposable(() => wholeRangeDecoration.clear())); - this._sessionStore.add(this._activeSession.wholeRange.onDidChange(updateWholeRangeDecoration)); + this._sessionStore.add(this._session.wholeRange.onDidChange(updateWholeRangeDecoration)); updateWholeRangeDecoration(); - this._zone.value.widget.updateSlashCommands(this._activeSession.session.slashCommands ?? []); + this._zone.value.widget.updateSlashCommands(this._session.session.slashCommands ?? []); this._updatePlaceholder(); - this._zone.value.widget.updateInfo(this._activeSession.session.message ?? localize('welcome.1', "AI-generated code may be incorrect")); - this._zone.value.widget.preferredExpansionState = this._activeSession.lastExpansionState; - this._zone.value.widget.value = this._activeSession.session.input ?? this._activeSession.lastInput?.value ?? this._zone.value.widget.value; - if (this._activeSession.session.input) { + this._zone.value.widget.updateInfo(this._session.session.message ?? localize('welcome.1', "AI-generated code may be incorrect")); + this._zone.value.widget.preferredExpansionState = this._session.lastExpansionState; + this._zone.value.widget.value = this._session.session.input ?? this._session.lastInput?.value ?? this._zone.value.widget.value; + if (this._session.session.input) { this._zone.value.widget.selectAll(); } this._showWidget(true); this._sessionStore.add(this._editor.onDidChangeModel((e) => { - const msg = this._activeSession?.lastExchange + const msg = this._session?.lastExchange ? Message.PAUSE_SESSION // pause when switching models/tabs and when having a previous exchange : Message.CANCEL_SESSION; this._log('model changed, pause or cancel session', msg, e); @@ -384,7 +397,7 @@ export class InlineChatController implements IEditorContribution { return; } - const wholeRange = this._activeSession!.wholeRange; + const wholeRange = this._session!.wholeRange; let shouldFinishSession = false; if (this._configurationService.getValue(InlineChatConfigKeys.FinishOnType)) { for (const { range } of e.changes) { @@ -392,7 +405,7 @@ export class InlineChatController implements IEditorContribution { } } - this._activeSession!.recordExternalEditOccurred(shouldFinishSession); + this._session!.recordExternalEditOccurred(shouldFinishSession); if (shouldFinishSession) { this._log('text changed outside of whole range, FINISH session'); @@ -401,9 +414,9 @@ export class InlineChatController implements IEditorContribution { })); // Update context key - this._ctxSupportIssueReporting.set(this._activeSession.provider.supportIssueReporting ?? false); + this._ctxSupportIssueReporting.set(this._session.provider.supportIssueReporting ?? false); - if (!this._activeSession.lastExchange) { + if (!this._session.lastExchange) { return State.WAIT_FOR_INPUT; } else if (options.isUnstashed) { delete options.isUnstashed; @@ -414,7 +427,7 @@ export class InlineChatController implements IEditorContribution { } private async [State.WAIT_FOR_INPUT](options: InlineChatRunOptions): Promise { - assertType(this._activeSession); + assertType(this._session); assertType(this._strategy); this._updatePlaceholder(); @@ -458,10 +471,10 @@ export class InlineChatController implements IEditorContribution { return State.PAUSE; } - if (message & Message.RERUN_INPUT && this._activeSession.lastExchange) { - const { lastExchange } = this._activeSession; + if (message & Message.RERUN_INPUT && this._session.lastExchange) { + const { lastExchange } = this._session; if (options.withIntentDetection === undefined) { // @ulugbekna: if we're re-running with intent detection turned off, no need to update `attempt` # - this._activeSession.addInput(lastExchange.prompt.retry()); + this._session.addInput(lastExchange.prompt.retry()); } if (lastExchange.response instanceof ReplyResponse) { try { @@ -483,10 +496,10 @@ export class InlineChatController implements IEditorContribution { this._historyUpdate(input); - const refer = this._activeSession.session.slashCommands?.some(value => value.refer && input!.startsWith(`/${value.command}`)); + const refer = this._session.session.slashCommands?.some(value => value.refer && input!.startsWith(`/${value.command}`)); if (refer) { - this._log('[IE] seeing refer command, continuing outside editor', this._activeSession.provider.debugName); - this._editor.setSelection(this._activeSession.wholeRange.value); + this._log('[IE] seeing refer command, continuing outside editor', this._session.provider.debugName); + this._editor.setSelection(this._session.wholeRange.value); let massagedInput = input; if (input.startsWith(chatSubcommandLeader)) { const withoutSubCommandLeader = input.slice(1); @@ -503,22 +516,22 @@ export class InlineChatController implements IEditorContribution { // if agent has a refer command, massage the input to include the agent name this._instaService.invokeFunction(sendRequest, massagedInput); - if (!this._activeSession.lastExchange) { + if (!this._session.lastExchange) { // DONE when there wasn't any exchange yet. We used the inline chat only as trampoline return State.ACCEPT; } return State.WAIT_FOR_INPUT; } - this._activeSession.addInput(new SessionPrompt(input)); + this._session.addInput(new SessionPrompt(input)); return State.MAKE_REQUEST; } private async [State.MAKE_REQUEST](options: InlineChatRunOptions): Promise { assertType(this._editor.hasModel()); - assertType(this._activeSession); + assertType(this._session); assertType(this._strategy); - assertType(this._activeSession.lastInput); + assertType(this._session.lastInput); const requestCts = new CancellationTokenSource(); @@ -534,18 +547,18 @@ export class InlineChatController implements IEditorContribution { const requestClock = StopWatch.create(); const request: IInlineChatRequest = { requestId: generateUuid(), - prompt: this._activeSession.lastInput.value, - attempt: this._activeSession.lastInput.attempt, + prompt: this._session.lastInput.value, + attempt: this._session.lastInput.attempt, selection: this._editor.getSelection(), - wholeRange: this._activeSession.wholeRange.trackedInitialRange, - live: this._activeSession.editMode !== EditMode.Preview, // TODO@jrieken let extension know what document is used for previewing + wholeRange: this._session.wholeRange.trackedInitialRange, + live: this._session.editMode !== EditMode.Preview, // TODO@jrieken let extension know what document is used for previewing withIntentDetection: options.withIntentDetection ?? true /* use intent detection by default */, }; // re-enable intent detection delete options.withIntentDetection; - const modelAltVersionIdNow = this._activeSession.textModelN.getAlternativeVersionId(); + const modelAltVersionIdNow = this._session.textModelN.getAlternativeVersionId(); const progressEdits: TextEdit[][] = []; const progressiveEditsAvgDuration = new MovingAverage(); @@ -582,7 +595,7 @@ export class InlineChatController implements IEditorContribution { progressiveEditsQueue.queue(async () => { - const startThen = this._activeSession!.wholeRange.value.getStartPosition(); + const startThen = this._session!.wholeRange.value.getStartPosition(); // making changes goes into a queue because otherwise the async-progress time will // influence the time it takes to receive the changes and progressive typing will @@ -593,7 +606,7 @@ export class InlineChatController implements IEditorContribution { ); // reshow the widget if the start position changed or shows at the wrong position - const startNow = this._activeSession!.wholeRange.value.getStartPosition(); + const startNow = this._session!.wholeRange.value.getStartPosition(); if (!startNow.equals(startThen) || !this._zone.value.position?.equals(startNow)) { this._showWidget(false, startNow.delta(-1)); } @@ -603,7 +616,7 @@ export class InlineChatController implements IEditorContribution { if (!progressiveChatResponse) { const message = { message: new MarkdownString(data.markdownFragment, { supportThemeIcons: true, supportHtml: true, isTrusted: false }), - providerId: this._activeSession!.provider.debugName, + providerId: this._session!.provider.debugName, requestId: request.requestId, }; progressiveChatResponse = this._zone.value.widget.updateChatMessage(message, true); @@ -617,8 +630,8 @@ export class InlineChatController implements IEditorContribution { const a11yVerboseInlineChat = this._configurationService.getValue('accessibility.verbosity.inlineChat') === true; const requestId = this._chatAccessibilityService.acceptRequest(); - const task = this._activeSession.provider.provideResponse(this._activeSession.session, request, progress, requestCts.token); - this._log('request started', this._activeSession.provider.debugName, this._activeSession.session, request); + const task = this._session.provider.provideResponse(this._session.session, request, progress, requestCts.token); + this._log('request started', this._session.provider.debugName, this._session.session, request); let response: ReplyResponse | ErrorResponse | EmptyResponse; let reply: IInlineChatResponse | null | undefined; @@ -626,8 +639,7 @@ export class InlineChatController implements IEditorContribution { this._zone.value.widget.updateChatMessage(undefined); this._zone.value.widget.updateFollowUps(undefined); this._zone.value.widget.updateProgress(true); - this._zone.value.widget.updateInfo(!this._activeSession.lastExchange ? localize('thinking', "Thinking\u2026") : ''); - await this._strategy.start(); + this._zone.value.widget.updateInfo(!this._session.lastExchange ? localize('thinking', "Thinking\u2026") : ''); this._ctxHasActiveRequest.set(true); reply = await raceCancellationError(Promise.resolve(task), requestCts.token); @@ -643,7 +655,7 @@ export class InlineChatController implements IEditorContribution { a11yResponse = localize('empty', "No results, please refine your input and try again"); } else { const markdownContents = reply.message ?? new MarkdownString('', { supportThemeIcons: true, supportHtml: true, isTrusted: false }); - const replyResponse = response = this._instaService.createInstance(ReplyResponse, reply, markdownContents, this._activeSession.textModelN.uri, modelAltVersionIdNow, progressEdits, request.requestId); + const replyResponse = response = this._instaService.createInstance(ReplyResponse, reply, markdownContents, this._session.textModelN.uri, modelAltVersionIdNow, progressEdits, request.requestId); for (let i = progressEdits.length; i < replyResponse.allLocalEdits.length; i++) { await this._makeChanges(replyResponse.allLocalEdits[i], undefined); @@ -666,13 +678,13 @@ export class InlineChatController implements IEditorContribution { this._zone.value.widget.updateProgress(false); this._zone.value.widget.updateInfo(''); this._zone.value.widget.updateToolbar(true); - this._log('request took', requestClock.elapsed(), this._activeSession.provider.debugName); + this._log('request took', requestClock.elapsed(), this._session.provider.debugName); this._chatAccessibilityService.acceptResponse(a11yResponse, requestId); } // todo@jrieken we can likely remove 'trackEdit' - const diff = await this._editorWorkerService.computeDiff(this._activeSession.textModel0.uri, this._activeSession.textModelN.uri, { computeMoves: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, ignoreTrimWhitespace: false }, 'advanced'); - this._activeSession.wholeRange.fixup(diff?.changes ?? []); + const diff = await this._editorWorkerService.computeDiff(this._session.textModel0.uri, this._session.textModelN.uri, { computeMoves: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, ignoreTrimWhitespace: false }, 'advanced'); + this._session.wholeRange.fixup(diff?.changes ?? []); progressiveEditsCts.dispose(true); requestCts.dispose(); @@ -683,7 +695,7 @@ export class InlineChatController implements IEditorContribution { this._strategy?.undoChanges(modelAltVersionIdNow); } - this._activeSession.addExchange(new SessionExchange(this._activeSession.lastInput, response)); + this._session.addExchange(new SessionExchange(this._session.lastInput, response)); if (message & Message.CANCEL_SESSION) { return State.CANCEL; @@ -699,10 +711,10 @@ export class InlineChatController implements IEditorContribution { } private async[State.APPLY_RESPONSE](): Promise { - assertType(this._activeSession); + assertType(this._session); assertType(this._strategy); - const { response } = this._activeSession.lastExchange!; + const { response } = this._session.lastExchange!; if (response instanceof ReplyResponse && response.workspaceEdit) { // this reply cannot be applied in the normal inline chat UI and needs to be handled off to workspace edit this._bulkEditService.apply(response.workspaceEdit, { showPreview: true }); @@ -712,13 +724,13 @@ export class InlineChatController implements IEditorContribution { } private async[State.SHOW_RESPONSE](): Promise { - assertType(this._activeSession); + assertType(this._session); assertType(this._strategy); - const { response } = this._activeSession.lastExchange!; + const { response } = this._session.lastExchange!; let responseTypes: InlineChatResponseTypes | undefined; - for (const { response } of this._activeSession.exchanges) { + for (const { response } of this._session.exchanges) { const thisType = response instanceof ReplyResponse ? response.responseType @@ -732,7 +744,7 @@ export class InlineChatController implements IEditorContribution { } } this._ctxResponseTypes.set(responseTypes); - this._ctxDidEdit.set(this._activeSession.hasChangedText); + this._ctxDidEdit.set(this._session.hasChangedText); let newPosition: Position | undefined; @@ -751,25 +763,25 @@ export class InlineChatController implements IEditorContribution { } else if (response instanceof ReplyResponse) { // real response -> complex... this._zone.value.widget.updateStatus(''); - const message = { message: response.mdContent, providerId: this._activeSession.provider.debugName, requestId: response.requestId }; + const message = { message: response.mdContent, providerId: this._session.provider.debugName, requestId: response.requestId }; this._zone.value.widget.updateChatMessage(message); //this._zone.value.widget.updateMarkdownMessage(response.mdContent); - this._activeSession.lastExpansionState = this._zone.value.widget.expansionState; + this._session.lastExpansionState = this._zone.value.widget.expansionState; this._zone.value.widget.updateToolbar(true); newPosition = await this._strategy.renderChanges(response); - if (this._activeSession.provider.provideFollowups) { + if (this._session.provider.provideFollowups) { const followupCts = new CancellationTokenSource(); const msgListener = Event.once(this._messages.event)(() => { followupCts.cancel(); }); - const followupTask = this._activeSession.provider.provideFollowups(this._activeSession.session, response.raw, followupCts.token); - this._log('followup request started', this._activeSession.provider.debugName, this._activeSession.session, response.raw); + const followupTask = this._session.provider.provideFollowups(this._session.session, response.raw, followupCts.token); + this._log('followup request started', this._session.provider.debugName, this._session.session, response.raw); raceCancellation(Promise.resolve(followupTask), followupCts.token).then(followupReply => { - if (followupReply && this._activeSession) { - this._log('followup request received', this._activeSession.provider.debugName, this._activeSession.session, followupReply); + if (followupReply && this._session) { + this._log('followup request received', this._session.provider.debugName, this._session.session, followupReply); this._zone.value.widget.updateFollowUps(followupReply, followup => { this.updateInput(followup.message); this.acceptInput(); @@ -787,28 +799,18 @@ export class InlineChatController implements IEditorContribution { } private async[State.PAUSE]() { + assertType(this._session); + assertType(this._strategy); - this._sessionStore.clear(); - this._ctxDidEdit.reset(); - this._ctxUserDidEdit.reset(); - this._ctxLastFeedbackKind.reset(); - this._ctxSupportIssueReporting.reset(); + this._resetWidget(); - this._zone.value.hide(); - - // Return focus to the editor only if the current focus is within the editor widget - if (this._editor.hasWidgetFocus()) { - this._editor.focus(); - } - - - this._strategy?.dispose(); - this._strategy = undefined; - this._activeSession = undefined; + this._pausedStrategies.set(this._session, this._strategy); + this._strategy.pause?.(); + this._session = undefined; } private async[State.ACCEPT]() { - assertType(this._activeSession); + assertType(this._session); assertType(this._strategy); this._sessionStore.clear(); @@ -820,18 +822,20 @@ export class InlineChatController implements IEditorContribution { this._log(err); } - this._inlineChatSessionService.releaseSession(this._activeSession); + this._inlineChatSessionService.releaseSession(this._session); - this[State.PAUSE](); + this._resetWidget(); + + this._strategy?.dispose(); + this._strategy = undefined; + this._session = undefined; } private async[State.CANCEL]() { - assertType(this._activeSession); + assertType(this._session); assertType(this._strategy); this._sessionStore.clear(); - const mySession = this._activeSession; - try { await this._strategy.cancel(); } catch (err) { @@ -840,15 +844,19 @@ export class InlineChatController implements IEditorContribution { this._log(err); } - this[State.PAUSE](); + this._resetWidget(); this._stashedSession.clear(); - if (!mySession.isUnstashed && mySession.lastExchange) { + if (!this._session.isUnstashed && this._session.lastExchange) { // only stash sessions that had edits - this._stashedSession.value = this._instaService.createInstance(StashedSession, this._editor, mySession); + this._stashedSession.value = this._instaService.createInstance(StashedSession, this._editor, this._session); } else { - this._inlineChatSessionService.releaseSession(mySession); + this._inlineChatSessionService.releaseSession(this._session); } + + this._strategy?.dispose(); + this._strategy = undefined; + this._session = undefined; } // ---- @@ -876,11 +884,11 @@ export class InlineChatController implements IEditorContribution { this._zone.value.setContainerMargins(); } - if (this._activeSession && !position && (this._activeSession.hasChangedText || this._activeSession.lastExchange)) { - widgetPosition = this._activeSession.wholeRange.value.getStartPosition().delta(-1); + if (this._session && !position && (this._session.hasChangedText || this._session.lastExchange)) { + widgetPosition = this._session.wholeRange.value.getStartPosition().delta(-1); } - if (this._activeSession) { - this._zone.value.updateBackgroundColor(widgetPosition, this._activeSession.wholeRange.value); + if (this._session) { + this._zone.value.updateBackgroundColor(widgetPosition, this._session.wholeRange.value); } if (!this._zone.value.position) { this._zone.value.setWidgetMargins(widgetPosition); @@ -890,12 +898,27 @@ export class InlineChatController implements IEditorContribution { } } + private _resetWidget() { + this._sessionStore.clear(); + this._ctxDidEdit.reset(); + this._ctxUserDidEdit.reset(); + this._ctxLastFeedbackKind.reset(); + this._ctxSupportIssueReporting.reset(); + + this._zone.value.hide(); + + // Return focus to the editor only if the current focus is within the editor widget + if (this._editor.hasWidgetFocus()) { + this._editor.focus(); + } + } + private async _makeChanges(edits: TextEdit[], opts: ProgressingEditsOptions | undefined) { - assertType(this._activeSession); + assertType(this._session); assertType(this._strategy); - const moreMinimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(this._activeSession.textModelN.uri, edits); - this._log('edits from PROVIDER and after making them MORE MINIMAL', this._activeSession.provider.debugName, edits, moreMinimalEdits); + const moreMinimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(this._session.textModelN.uri, edits); + this._log('edits from PROVIDER and after making them MORE MINIMAL', this._session.provider.debugName, edits, moreMinimalEdits); if (moreMinimalEdits?.length === 0) { // nothing left to do @@ -907,13 +930,13 @@ export class InlineChatController implements IEditorContribution { try { this._ignoreModelContentChanged = true; - this._activeSession.wholeRange.trackEdits(editOperations); + this._session.wholeRange.trackEdits(editOperations); if (opts) { await this._strategy.makeProgressiveChanges(editOperations, opts); } else { await this._strategy.makeChanges(editOperations); } - this._ctxDidEdit.set(this._activeSession.hasChangedText); + this._ctxDidEdit.set(this._session.hasChangedText); } finally { this._ignoreModelContentChanged = false; } @@ -926,7 +949,7 @@ export class InlineChatController implements IEditorContribution { } private _getPlaceholderText(): string { - return this._forcedPlaceholder ?? this._activeSession?.session.placeholder ?? ''; + return this._forcedPlaceholder ?? this._session?.session.placeholder ?? ''; } // ---- controller API @@ -1014,16 +1037,16 @@ export class InlineChatController implements IEditorContribution { } viewInChat() { - if (this._activeSession?.lastExchange?.response instanceof ReplyResponse) { - this._instaService.invokeFunction(showMessageResponse, this._activeSession.lastExchange.prompt.value, this._activeSession.lastExchange.response.mdContent.value); + if (this._session?.lastExchange?.response instanceof ReplyResponse) { + this._instaService.invokeFunction(showMessageResponse, this._session.lastExchange.prompt.value, this._session.lastExchange.response.mdContent.value); } } updateExpansionState(expand: boolean) { - if (this._activeSession) { + if (this._session) { const expansionState = expand ? ExpansionState.EXPANDED : ExpansionState.CROPPED; this._zone.value.widget.updateChatMessageExpansionState(expansionState); - this._activeSession.lastExpansionState = expansionState; + this._session.lastExpansionState = expansionState; } } @@ -1032,8 +1055,8 @@ export class InlineChatController implements IEditorContribution { } feedbackLast(kind: InlineChatResponseFeedbackKind) { - if (this._activeSession?.lastExchange && this._activeSession.lastExchange.response instanceof ReplyResponse) { - this._activeSession.provider.handleInlineChatResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, kind); + if (this._session?.lastExchange && this._session.lastExchange.response instanceof ReplyResponse) { + this._session.provider.handleInlineChatResponseFeedback?.(this._session.session, this._session.lastExchange.response.raw, kind); switch (kind) { case InlineChatResponseFeedbackKind.Helpful: this._ctxLastFeedbackKind.set('helpful'); @@ -1049,14 +1072,14 @@ export class InlineChatController implements IEditorContribution { } createSnapshot(): void { - if (this._activeSession && !this._activeSession.textModel0.equalsTextBuffer(this._activeSession.textModelN.getTextBuffer())) { - this._activeSession.createSnapshot(); + if (this._session && !this._session.textModel0.equalsTextBuffer(this._session.textModelN.getTextBuffer())) { + this._session.createSnapshot(); } } acceptSession(): void { - if (this._activeSession?.lastExchange && this._activeSession.lastExchange.response instanceof ReplyResponse) { - this._activeSession.provider.handleInlineChatResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, InlineChatResponseFeedbackKind.Accepted); + if (this._session?.lastExchange && this._session.lastExchange.response instanceof ReplyResponse) { + this._session.provider.handleInlineChatResponseFeedback?.(this._session.session, this._session.lastExchange.response.raw, InlineChatResponseFeedbackKind.Accepted); } this._messages.fire(Message.ACCEPT_SESSION); } @@ -1072,13 +1095,13 @@ export class InlineChatController implements IEditorContribution { async cancelSession() { let result: string | undefined; - if (this._activeSession) { + if (this._session) { - const diff = await this._editorWorkerService.computeDiff(this._activeSession.textModel0.uri, this._activeSession.textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000, computeMoves: false }, 'advanced'); - result = this._activeSession.asChangedText(diff?.changes ?? []); + const diff = await this._editorWorkerService.computeDiff(this._session.textModel0.uri, this._session.textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000, computeMoves: false }, 'advanced'); + result = this._session.asChangedText(diff?.changes ?? []); - if (this._activeSession.lastExchange && this._activeSession.lastExchange.response instanceof ReplyResponse) { - this._activeSession.provider.handleInlineChatResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, InlineChatResponseFeedbackKind.Undone); + if (this._session.lastExchange && this._session.lastExchange.response instanceof ReplyResponse) { + this._session.provider.handleInlineChatResponseFeedback?.(this._session.session, this._session.lastExchange.response.raw, InlineChatResponseFeedbackKind.Undone); } } @@ -1087,12 +1110,12 @@ export class InlineChatController implements IEditorContribution { } finishExistingSession(): void { - if (this._activeSession) { - if (this._activeSession.editMode === EditMode.Preview) { - this._log('finishing existing session, using CANCEL', this._activeSession.editMode); + if (this._session) { + if (this._session.editMode === EditMode.Preview) { + this._log('finishing existing session, using CANCEL', this._session.editMode); this.cancelSession(); } else { - this._log('finishing existing session, using APPLY', this._activeSession.editMode); + this._log('finishing existing session, using APPLY', this._session.editMode); this.acceptSession(); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 057367761db..4ece029e74b 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -423,7 +423,7 @@ export interface IInlineChatSessionService { onWillStartSession: Event; - onDidEndSession: Event; + onDidEndSession: Event<{ editor: ICodeEditor; session: Session }>; createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: IRange }, token: CancellationToken): Promise; @@ -452,8 +452,8 @@ export class InlineChatSessionService implements IInlineChatSessionService { private readonly _onWillStartSession = new Emitter(); readonly onWillStartSession: Event = this._onWillStartSession.event; - private readonly _onDidEndSession = new Emitter(); - readonly onDidEndSession: Event = this._onDidEndSession.event; + private readonly _onDidEndSession = new Emitter<{ editor: ICodeEditor; session: Session }>(); + readonly onDidEndSession: Event<{ editor: ICodeEditor; session: Session }> = this._onDidEndSession.event; private readonly _sessions = new Map(); private readonly _keyComputers = new Map(); @@ -563,7 +563,7 @@ export class InlineChatSessionService implements IInlineChatSessionService { // send telemetry this._telemetryService.publicLog2('interactiveEditor/session', session.asTelemetryData()); - this._onDidEndSession.fire(editor); + this._onDidEndSession.fire({ editor, session }); } getSession(editor: ICodeEditor, uri: URI): Session | undefined { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 7c1699450b6..b8c107d602f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -55,6 +55,7 @@ export abstract class EditModeStrategy { readonly onDidDiscard: Event = this._onDidDiscard.event; toggleDiff?: () => any; + pause?: () => any; constructor( protected readonly _session: Session, @@ -66,8 +67,6 @@ export abstract class EditModeStrategy { this._onDidDiscard.dispose(); } - abstract start(): Promise; - abstract apply(): Promise; abstract cancel(): Promise; @@ -124,10 +123,6 @@ export class PreviewStrategy extends EditModeStrategy { super.dispose(); } - async start() { - // nothing to do - } - async apply() { if (!(this._session.lastExchange?.response instanceof ReplyResponse)) { @@ -220,9 +215,6 @@ export class LivePreviewStrategy extends EditModeStrategy { this._previewZone.rawValue?.dispose(); super.dispose(); } - async start() { - // nothing to do - } async apply() { if (this._editCount > 0) { @@ -482,8 +474,44 @@ class Hunk { ) { } } +type HunkTrackedRange = { + /** + * The first element [0] is the whole modified range and subsequent elements are word-level changes + */ + getRanges(): Range[]; +}; + +const enum HunkState { + Accepted = 1, + Rejected = 2, +} + +type HunkDisplayData = { + acceptedOrRejected: HunkState | undefined; + decorationIds: string[]; + + viewZoneId: string | undefined; + viewZone: IViewZone; + + distance: number; + position: Position; + acceptHunk: () => void; + discardHunk: () => void; + toggleDiff?: () => any; +}; + +interface HunkDisplay { + renderHunks(): HunkDisplayData | undefined; + hideHunks(): void; +} + export class LiveStrategy extends EditModeStrategy { + private readonly _decoTrackedRange = ModelDecorationOptions.register({ + description: 'inline-tracked-range', + className: 'inline-chat-tracked-range' + }); + private readonly _decoInsertedText = ModelDecorationOptions.register({ description: 'inline-modified-line', className: 'inline-chat-inserted-range-linehighlight', @@ -499,8 +527,8 @@ export class LiveStrategy extends EditModeStrategy { className: 'inline-chat-inserted-range', }); - private readonly _store: DisposableStore = new DisposableStore(); - private readonly _renderStore: DisposableStore = new DisposableStore(); + private readonly _store = new DisposableStore(); + private readonly _renderStore = new DisposableStore(); private readonly _previewZone: Lazy; private readonly _ctxCurrentChangeHasDiff: IContextKey; @@ -545,9 +573,10 @@ export class LiveStrategy extends EditModeStrategy { this._progressiveEditingDecorations.clear(); } - async start() { - this._resetDiff(); - } + override pause = () => { + this._hunkDisplay?.hideHunks(); + this._ctxCurrentChangeShowsDiff.reset(); + }; async apply() { this._resetDiff(); @@ -574,7 +603,6 @@ export class LiveStrategy extends EditModeStrategy { } override async undoChanges(altVersionId: number): Promise { - this._renderStore.clear(); const { textModelN } = this._session; await undoModelUntil(textModelN, altVersionId); @@ -633,6 +661,8 @@ export class LiveStrategy extends EditModeStrategy { } } + private _hunkDisplay?: HunkDisplay; + override async renderChanges(response: ReplyResponse) { if (response.untitledTextModel && !response.untitledTextModel.isDisposed()) { @@ -643,213 +673,229 @@ export class LiveStrategy extends EditModeStrategy { this._progressiveEditingDecorations.clear(); - const hunks = await this._computeHunks(); + if (!this._hunkDisplay) { - this._renderStore.clear(); + this._renderStore.add(toDisposable(() => { + this._hunkDisplay?.hideHunks(); + this._hunkDisplay = undefined; + })); - if (hunks.length === 0) { - return undefined; - } + const hunkTrackedRanges = new Map(); + const hunkDisplayData = new Map(); - const enum HunkState { - Accepted = 1, - Rejected = 2, - } - - type HunkDisplayData = { - acceptedOrRejected: HunkState | undefined; - decorationIds: string[]; - - viewZoneId: string | undefined; - viewZone: IViewZone; - - distance: number; - position: Position; - acceptHunk: () => void; - discardHunk: () => void; - toggleDiff?: () => any; - }; - - let widgetData: HunkDisplayData | undefined; - const hunkDisplayData = new Map(); - - const renderHunks = () => { - - changeDecorationsAndViewZones(this._editor, (decorationsAccessor, viewZoneAccessor) => { - - widgetData = undefined; + // (INIT) compute hunks + const hunks = await this._computeHunks(); + if (hunks.length === 0) { + this._hunkDisplay = { renderHunks() { return undefined; }, hideHunks() { } }; + return undefined; + } + // (INIT) add tracked ranges per hunk + const model = this._editor.getModel()!; + model.changeDecorations(accessor => { for (const hunk of hunks) { + const decorationIds: string[] = []; + const modifiedRange = asRange(hunk.modified, this._session.textModelN); + decorationIds.push(accessor.addDecoration(modifiedRange, this._decoTrackedRange)); + for (const change of hunk.changes) { + decorationIds.push(accessor.addDecoration(change.modifiedRange, this._decoTrackedRange)); + } + hunkTrackedRanges.set(hunk, { + getRanges: () => { + const ranges = decorationIds.map(id => model.getDecorationRange(id)); + coalesceInPlace(ranges); + return ranges; + } + }); + this._renderStore.add(toDisposable(() => { + model.deltaDecorations(decorationIds, []); + })); + } + }); - const { modified } = hunk; + let widgetData: HunkDisplayData | undefined; - let data = hunkDisplayData.get(hunk); - if (!data) { - // first time -> create decoration - const decorationIds: string[] = []; - const modifiedRange = asRange(modified, this._session.textModelN); - decorationIds.push(decorationsAccessor.addDecoration(modifiedRange, this._decoInsertedText)); - for (const change of hunk.changes) { - decorationIds.push(decorationsAccessor.addDecoration(change.modifiedRange, this._decoInsertedTextRange)); + const renderHunks = () => { + + changeDecorationsAndViewZones(this._editor, (decorationsAccessor, viewZoneAccessor) => { + + widgetData = undefined; + + for (const hunk of hunks) { + + const hunkRanges = hunkTrackedRanges.get(hunk)!.getRanges(); + let data = hunkDisplayData.get(hunk); + if (!data) { + // first time -> create decoration + const decorationIds: string[] = []; + for (let i = 0; i < hunkRanges.length; i++) { + decorationIds.push(decorationsAccessor.addDecoration(hunkRanges[i], i === 0 + ? this._decoInsertedText + : this._decoInsertedTextRange) + ); + } + + const acceptHunk = () => { + // ACCEPT: stop rendering this as inserted + hunkDisplayData.get(hunk)!.acceptedOrRejected = HunkState.Accepted; + renderHunks(); + }; + + const discardHunk = () => { + const edits: ISingleEditOperation[] = []; + const hunkRanges = hunkTrackedRanges.get(hunk)!.getRanges(); + for (let i = 1; i < hunkRanges.length; i++) { + // DISCARD: replace modified range with original value. The modified range is retrieved from a decoration + // which was created above so that typing in the editor keeps discard working. + const modifiedRange = hunkRanges[i]; + const originalValue = this._session.textModel0.getValueInRange(hunk.changes[i - 1].originalRange); + edits.push(EditOperation.replace(modifiedRange, originalValue)); + } + this._session.textModelN.pushEditOperations(null, edits, () => null); + hunkDisplayData.get(hunk)!.acceptedOrRejected = HunkState.Rejected; + renderHunks(); + }; + + // original view zone + const mightContainNonBasicASCII = this._session.textModel0.mightContainNonBasicASCII() ?? false; + const mightContainRTL = this._session.textModel0.mightContainRTL() ?? false; + const renderOptions = RenderOptions.fromEditor(this._editor); + const source = new LineSource( + hunk.original.mapToLineArray(l => this._session.textModel0.tokenization.getLineTokens(l)), + [], + mightContainNonBasicASCII, + mightContainRTL, + ); + const domNode = document.createElement('div'); + domNode.className = 'inline-chat-original-zone2'; + const result = renderLines(source, renderOptions, [new InlineDecoration(new Range(hunk.original.startLineNumber, 1, hunk.original.startLineNumber, 1), '', InlineDecorationType.Regular)], domNode); + const viewZoneData: IViewZone = { + afterLineNumber: -1, + heightInLines: result.heightInLines, + domNode, + }; + + const toggleDiff = () => { + const scrollState = StableEditorScrollState.capture(this._editor); + if (!data!.viewZoneId) { + + this._editor.changeViewZones(accessor => { + const [hunkRange] = hunkTrackedRanges.get(hunk)!.getRanges(); + viewZoneData.afterLineNumber = hunkRange.startLineNumber - 1; + data!.viewZoneId = accessor.addZone(viewZoneData); + }); + this._ctxCurrentChangeShowsDiff.set(true); + } else { + this._editor.changeViewZones(accessor => { + accessor.removeZone(data!.viewZoneId!); + data!.viewZoneId = undefined; + }); + this._ctxCurrentChangeShowsDiff.set(false); + } + scrollState.restore(this._editor); + }; + + const zoneLineNumber = this._zone.position!.lineNumber; + const myDistance = zoneLineNumber <= hunkRanges[0].startLineNumber + ? hunkRanges[0].startLineNumber - zoneLineNumber + : zoneLineNumber - hunkRanges[0].endLineNumber; + + data = { + acceptedOrRejected: undefined, + decorationIds, + viewZoneId: '', + viewZone: viewZoneData, + distance: myDistance, + position: hunkRanges[0].getStartPosition().delta(-1), + acceptHunk, + discardHunk, + toggleDiff: !hunk.original.isEmpty ? toggleDiff : undefined, + }; + + hunkDisplayData.set(hunk, data); + + } else if (data.acceptedOrRejected !== undefined) { + // accepted or rejected -> remove decoration + for (const decorationId of data.decorationIds) { + decorationsAccessor.removeDecoration(decorationId); + } + if (data.viewZoneId) { + viewZoneAccessor.removeZone(data.viewZoneId); + } + + data.decorationIds = []; + data.viewZoneId = undefined; + + } else { + // update distance and position based on modifiedRange-decoration + const zoneLineNumber = this._zone.position!.lineNumber; + const modifiedRangeNow = hunkRanges[0]; + data.position = modifiedRangeNow.getStartPosition().delta(-1); + data.distance = zoneLineNumber <= modifiedRangeNow.startLineNumber + ? modifiedRangeNow.startLineNumber - zoneLineNumber + : zoneLineNumber - modifiedRangeNow.endLineNumber; } - const acceptHunk = () => { - // ACCEPT: stop rendering this as inserted - hunkDisplayData.get(hunk)!.acceptedOrRejected = HunkState.Accepted; - renderHunks(); - }; - - const discardHunk = () => { - const edits: ISingleEditOperation[] = []; - for (let i = 1; i < decorationIds.length; i++) { - // DISCARD: replace modified range with original value. The modified range is retrieved from a decoration - // which was created above so that typing in the editor keeps discard working. - const modifiedRange = this._session.textModelN.getDecorationRange(decorationIds[i])!; - const originalValue = this._session.textModel0.getValueInRange(hunk.changes[i - 1].originalRange); - edits.push(EditOperation.replace(modifiedRange, originalValue)); + if (!data.acceptedOrRejected) { + if (!widgetData || data.distance < widgetData.distance) { + widgetData = data; } - this._session.textModelN.pushEditOperations(null, edits, () => null); - hunkDisplayData.get(hunk)!.acceptedOrRejected = HunkState.Rejected; - renderHunks(); - }; + } + } + }); - // original view zone - const mightContainNonBasicASCII = this._session.textModel0.mightContainNonBasicASCII() ?? false; - const mightContainRTL = this._session.textModel0.mightContainRTL() ?? false; - const renderOptions = RenderOptions.fromEditor(this._editor); - const source = new LineSource( - hunk.original.mapToLineArray(l => this._session.textModel0.tokenization.getLineTokens(l)), - [], - mightContainNonBasicASCII, - mightContainRTL, - ); - const domNode = document.createElement('div'); - domNode.className = 'inline-chat-original-zone2'; - const result = renderLines(source, renderOptions, [new InlineDecoration(new Range(hunk.original.startLineNumber, 1, hunk.original.startLineNumber, 1), '', InlineDecorationType.Regular)], domNode); - const viewZoneData: IViewZone = { - afterLineNumber: -1, - heightInLines: result.heightInLines, - domNode, - }; + if (widgetData) { + this._zone.updatePositionAndHeight(widgetData.position); + this._editor.revealPositionInCenterIfOutsideViewport(widgetData.position); - const toggleDiff = () => { - const scrollState = StableEditorScrollState.capture(this._editor); - if (!data!.viewZoneId) { + const remainingHunks = Iterable.reduce(hunkDisplayData.values(), (p, c) => { return p + (c.acceptedOrRejected ? 0 : 1); }, 0); + this._updateSummaryMessage(remainingHunks); - this._editor.changeViewZones(accessor => { - viewZoneData.afterLineNumber = this._session.textModelN.getDecorationRange(decorationIds[0])!.startLineNumber - 1; - data!.viewZoneId = accessor.addZone(viewZoneData); - }); - this._ctxCurrentChangeShowsDiff.set(true); - } else { - this._editor.changeViewZones(accessor => { - accessor.removeZone(data!.viewZoneId!); - data!.viewZoneId = undefined; - }); - this._ctxCurrentChangeShowsDiff.set(false); - } - scrollState.restore(this._editor); - }; + this._ctxCurrentChangeHasDiff.set(Boolean(widgetData.toggleDiff)); + this.toggleDiff = widgetData.toggleDiff; + this.acceptHunk = async () => widgetData!.acceptHunk(); + this.discardHunk = async () => widgetData!.discardHunk(); - const zoneLineNumber = this._zone.position!.lineNumber; - const myDistance = zoneLineNumber <= modifiedRange.startLineNumber - ? modifiedRange.startLineNumber - zoneLineNumber - : zoneLineNumber - modifiedRange.endLineNumber; + } else if (hunkDisplayData.size > 0) { + // everything accepted or rejected + let oneAccepted = false; + for (const data of hunkDisplayData.values()) { + if (data.acceptedOrRejected === HunkState.Accepted) { + oneAccepted = true; + break; + } + } + if (oneAccepted) { + this._onDidAccept.fire(); + } else { + this._onDidDiscard.fire(); + } + } - data = { - acceptedOrRejected: undefined, - decorationIds, - viewZoneId: '', - viewZone: viewZoneData, - distance: myDistance, - position: modifiedRange.getStartPosition().delta(-1), - acceptHunk, - discardHunk, - toggleDiff: !hunk.original.isEmpty ? toggleDiff : undefined, - }; + return widgetData; + }; - hunkDisplayData.set(hunk, data); - - } else if (data.acceptedOrRejected !== undefined) { - // accepted or rejected -> remove decoration + const hideHunks = () => { + changeDecorationsAndViewZones(this._editor, (decorationsAccessor, viewZoneAccessor) => { + for (const data of hunkDisplayData.values()) { + // remove decorations for (const decorationId of data.decorationIds) { decorationsAccessor.removeDecoration(decorationId); } + // remove view zone if (data.viewZoneId) { viewZoneAccessor.removeZone(data.viewZoneId); } - - data.decorationIds = []; - data.viewZoneId = undefined; - - } else { - // update distance and position based on modifiedRange-decoration - const zoneLineNumber = this._zone.position!.lineNumber; - const modifiedRangeNow = this._session.textModelN.getDecorationRange(data.decorationIds[0])!; - data.position = modifiedRangeNow.getStartPosition().delta(-1); - data.distance = zoneLineNumber <= modifiedRangeNow.startLineNumber - ? modifiedRangeNow.startLineNumber - zoneLineNumber - : zoneLineNumber - modifiedRangeNow.endLineNumber; + data.viewZone.domNode.remove(); } + }); + hunkDisplayData.clear(); + }; - if (!data.acceptedOrRejected) { - if (!widgetData || data.distance < widgetData.distance) { - widgetData = data; - } - } - } - }); + this._hunkDisplay = { renderHunks, hideHunks }; + } - if (widgetData) { - this._zone.updatePositionAndHeight(widgetData.position); - this._editor.revealPositionInCenterIfOutsideViewport(widgetData.position); - - const remainingHunks = Iterable.reduce(hunkDisplayData.values(), (p, c) => { return p + (c.acceptedOrRejected ? 0 : 1); }, 0); - this._updateSummaryMessage(remainingHunks); - - this._ctxCurrentChangeHasDiff.set(Boolean(widgetData.toggleDiff)); - this.toggleDiff = widgetData.toggleDiff; - this.acceptHunk = async () => widgetData!.acceptHunk(); - this.discardHunk = async () => widgetData!.discardHunk(); - - } else if (hunkDisplayData.size > 0) { - // everything accepted or rejected - let oneAccepted = false; - for (const data of hunkDisplayData.values()) { - if (data.acceptedOrRejected === HunkState.Accepted) { - oneAccepted = true; - break; - } - } - if (oneAccepted) { - this._onDidAccept.fire(); - } else { - this._onDidDiscard.fire(); - } - } - }; - - renderHunks(); - - this._renderStore.add(toDisposable(() => { - - changeDecorationsAndViewZones(this._editor, (decorationsAccessor, viewZoneAccessor) => { - for (const data of hunkDisplayData.values()) { - // remove decorations - for (const decorationId of data.decorationIds) { - decorationsAccessor.removeDecoration(decorationId); - } - // remove view zone - if (data.viewZoneId) { - viewZoneAccessor.removeZone(data.viewZoneId); - } - data.viewZone.domNode.remove(); - } - }); - })); - - - return widgetData?.position; + return this._hunkDisplay?.renderHunks()?.position; } private static readonly HUNK_THRESHOLD = 8; From 18b692070530e2992f02a507d28248e5fa8c0d9b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 12 Jan 2024 09:16:32 +0100 Subject: [PATCH 0265/1897] throw when seeing Action2 with re-used id (#202318) re https://github.com/microsoft/vscode/issues/199744 --- src/vs/platform/actions/common/actions.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index ba21daa8d9d..07859fa9cc4 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -579,8 +579,7 @@ export function registerAction2(ctor: { new(): Action2 }): IDisposable { const { f1, menu, keybinding, ...command } = action.desc; if (CommandsRegistry.getCommand(command.id)) { - // throw new Error(`Cannot register two commands with the same id: ${command.id}`); - console.warn(`Cannot register two commands with the same id: ${command.id}`); + throw new Error(`Cannot register two commands with the same id: ${command.id}`); } // command From 4f338c87ab68833847df18ddcb8a5f5d7ee667fc Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 12 Jan 2024 17:48:45 +0900 Subject: [PATCH 0266/1897] fix: account for DSF when using wheelDeltaX and wheelDeltaY values (#200776) * fix: account for DSF when using wheelDeltaX and wheelDeltaY values * chore: address review feedback * fix: address review feedback --- src/vs/base/browser/mouseEvent.ts | 18 ++++++++++++++++-- .../browser/view/renderers/webviewPreloads.ts | 8 +++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/mouseEvent.ts b/src/vs/base/browser/mouseEvent.ts index b4ae34ad5ec..6e414822d73 100644 --- a/src/vs/base/browser/mouseEvent.ts +++ b/src/vs/base/browser/mouseEvent.ts @@ -140,10 +140,16 @@ export class StandardWheelEvent { // Old (deprecated) wheel events const e1 = e; const e2 = e; + const devicePixelRatio = e.view?.devicePixelRatio || 1; // vertical delta scroll if (typeof e1.wheelDeltaY !== 'undefined') { - this.deltaY = e1.wheelDeltaY / 120; + if (browser.isChrome) { + // Refs https://github.com/microsoft/vscode/issues/146403#issuecomment-1854538928 + this.deltaY = e1.wheelDeltaY / (120 * devicePixelRatio); + } else { + this.deltaY = e1.wheelDeltaY / 120; + } } else if (typeof e2.VERTICAL_AXIS !== 'undefined' && e2.axis === e2.VERTICAL_AXIS) { this.deltaY = -e2.detail / 3; } else if (e.type === 'wheel') { @@ -167,6 +173,9 @@ export class StandardWheelEvent { if (typeof e1.wheelDeltaX !== 'undefined') { if (browser.isSafari && platform.isWindows) { this.deltaX = - (e1.wheelDeltaX / 120); + } else if (browser.isChrome) { + // Refs https://github.com/microsoft/vscode/issues/146403#issuecomment-1854538928 + this.deltaX = e1.wheelDeltaX / (120 * devicePixelRatio); } else { this.deltaX = e1.wheelDeltaX / 120; } @@ -191,7 +200,12 @@ export class StandardWheelEvent { // Assume a vertical scroll if nothing else worked if (this.deltaY === 0 && this.deltaX === 0 && e.wheelDelta) { - this.deltaY = e.wheelDelta / 120; + if (browser.isChrome) { + // Refs https://github.com/microsoft/vscode/issues/146403#issuecomment-1854538928 + this.deltaY = e.wheelDelta / (120 * devicePixelRatio); + } else { + this.deltaY = e.wheelDelta / 120; + } } } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 6a0eb8e644b..32082d471ab 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -9,6 +9,7 @@ import type { IDisposable } from 'vs/base/common/lifecycle'; import type * as webviewMessages from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages'; import type { NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import type * as rendererApi from 'vscode-notebook-renderer'; +import * as browser from 'vs/base/browser/browser'; // !! IMPORTANT !! ---------------------------------------------------------------------------------- // import { RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -482,9 +483,10 @@ async function webviewPreloads(ctx: PreloadContext) { deltaX: event.deltaX, deltaY: event.deltaY, deltaZ: event.deltaZ, - wheelDelta: event.wheelDelta, - wheelDeltaX: event.wheelDeltaX, - wheelDeltaY: event.wheelDeltaY, + // Refs https://github.com/microsoft/vscode/issues/146403#issuecomment-1854538928 + wheelDelta: event.wheelDelta && browser.isChrome ? (event.wheelDelta / $window.devicePixelRatio) : event.wheelDelta, + wheelDeltaX: event.wheelDeltaX && browser.isChrome ? (event.wheelDeltaX / $window.devicePixelRatio) : event.wheelDeltaX, + wheelDeltaY: event.wheelDeltaY && browser.isChrome ? (event.wheelDeltaY / $window.devicePixelRatio) : event.wheelDeltaY, detail: event.detail, shiftKey: event.shiftKey, type: event.type From 5260b203172155949040d89e7de897ddb5975937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 12 Jan 2024 10:41:42 +0100 Subject: [PATCH 0267/1897] ensure that the second window always gets the same environment as the first one (#202326) fixes #194736 Co-authored-by: @bpasero --- src/vs/platform/environment/common/argv.ts | 1 + src/vs/platform/environment/node/argv.ts | 1 + .../launch/electron-main/launchMainService.ts | 13 ++++++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index 7acd17b994a..cc157af7ab3 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -105,6 +105,7 @@ export interface NativeParsedArgs { 'remote'?: string; 'force'?: boolean; 'do-not-sync'?: boolean; + 'preserve-env'?: boolean; 'force-user-env'?: boolean; 'force-disable-user-env'?: boolean; 'sync'?: 'on' | 'off'; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 3bb02bcc7f1..a5d96361f08 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -167,6 +167,7 @@ export const OPTIONS: OptionDescriptions> = { 'trace': { type: 'boolean' }, 'trace-category-filter': { type: 'string' }, 'trace-options': { type: 'string' }, + 'preserve-env': { type: 'boolean' }, 'force-user-env': { type: 'boolean' }, 'force-disable-user-env': { type: 'boolean' }, 'open-devtools': { type: 'boolean' }, diff --git a/src/vs/platform/launch/electron-main/launchMainService.ts b/src/vs/platform/launch/electron-main/launchMainService.ts index 3375a4d37e2..8cc5076eea2 100644 --- a/src/vs/platform/launch/electron-main/launchMainService.ts +++ b/src/vs/platform/launch/electron-main/launchMainService.ts @@ -119,7 +119,18 @@ export class LaunchMainService implements ILaunchMainService { const baseConfig: IOpenConfiguration = { context, cli: args, - userEnv, + /** + * When opening a new window from a second instance that sent args and env + * over to this instance, we want to preserve the environment only if that second + * instance was spawned from the CLI or used the `--preserve-env` flag (example: + * when using `open -n "VSCode.app" --args --preserve-env WORKSPACE_FOLDER`). + * + * This is done to ensure that the second window gets treated exactly the same + * as the first window, for example, it gets the same resolved user shell environment. + * + * https://github.com/microsoft/vscode/issues/194736 + */ + userEnv: (args['preserve-env'] || context === OpenContext.CLI) ? userEnv : undefined, waitMarkerFileURI, remoteAuthority, forceProfile: args.profile, From 2434b7da03ff51e5a56c4f4a4674167e49470419 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 12 Jan 2024 10:46:52 +0100 Subject: [PATCH 0268/1897] Git - Add "View All Changes" action + polish (#202327) * Make allChanges a distinct historyItem element * Add allChanges menu contribution * Add View All Changes command * Rename "Open Commit" to "View Commit" * Pass the correct argument when getting "All Changes" node --- extensions/git/package.json | 45 ++++++++++++++---- extensions/git/package.nls.json | 5 +- extensions/git/src/commands.ts | 43 ++++++++++++----- src/vs/platform/actions/common/actions.ts | 2 + src/vs/workbench/contrib/scm/browser/menus.ts | 28 ++++++----- .../contrib/scm/browser/scmViewPane.ts | 46 ++++++++++++------- src/vs/workbench/contrib/scm/browser/util.ts | 3 +- .../workbench/contrib/scm/common/history.ts | 6 +-- .../actions/common/menusExtensionPoint.ts | 12 +++++ 9 files changed, 132 insertions(+), 58 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index b730372960b..c7901c9ee14 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -709,8 +709,8 @@ "category": "Git" }, { - "command": "git.timeline.openCommit", - "title": "%command.openCommit%", + "command": "git.timeline.viewCommit", + "title": "%command.viewCommit%", "icon": "$(diff-multiple)", "category": "Git" }, @@ -775,8 +775,15 @@ "category": "Git" }, { - "command": "git.openCommit", - "title": "%command.openCommit%", + "command": "git.viewCommit", + "title": "%command.viewCommit%", + "icon": "$(diff-multiple)", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.viewAllChanges", + "title": "%command.viewAllChanges%", "icon": "$(diff-multiple)", "category": "Git", "enablement": "!operationInProgress" @@ -1229,7 +1236,7 @@ "when": "false" }, { - "command": "git.timeline.openCommit", + "command": "git.timeline.viewCommit", "when": "false" }, { @@ -1265,7 +1272,11 @@ "when": "config.git.enabled && !git.missing && config.multiDiffEditor.experimental.enabled" }, { - "command": "git.openCommit", + "command": "git.viewCommit", + "when": "false" + }, + { + "command": "git.viewAllChanges", "when": "false" } ], @@ -1734,16 +1745,30 @@ "group": "1_modification@3" } ], + "scm/incomingChanges/allChanges/context": [ + { + "command": "git.viewAllChanges", + "when": "scmProvider == git && config.multiDiffEditor.experimental.enabled", + "group": "inline@1" + } + ], "scm/incomingChanges/historyItem/context": [ { - "command": "git.openCommit", + "command": "git.viewCommit", + "when": "scmProvider == git && config.multiDiffEditor.experimental.enabled", + "group": "inline@1" + } + ], + "scm/outgoingChanges/allChanges/context": [ + { + "command": "git.viewAllChanges", "when": "scmProvider == git && config.multiDiffEditor.experimental.enabled", "group": "inline@1" } ], "scm/outgoingChanges/historyItem/context": [ { - "command": "git.openCommit", + "command": "git.viewCommit", "when": "scmProvider == git && config.multiDiffEditor.experimental.enabled", "group": "inline@1" } @@ -1830,7 +1855,7 @@ ], "timeline/item/context": [ { - "command": "git.timeline.openCommit", + "command": "git.timeline.viewCommit", "group": "inline", "when": "config.git.enabled && !git.missing && timelineItem =~ /git:file:commit\\b/ && !listMultiSelection && config.multiDiffEditor.experimental.enabled" }, @@ -1840,7 +1865,7 @@ "when": "config.git.enabled && !git.missing && timelineItem =~ /git:file\\b/ && !listMultiSelection" }, { - "command": "git.timeline.openCommit", + "command": "git.timeline.viewCommit", "group": "1_actions@2", "when": "config.git.enabled && !git.missing && timelineItem =~ /git:file:commit\\b/ && !listMultiSelection && config.multiDiffEditor.experimental.enabled" }, diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 8903480d364..b4db8a31ca8 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -112,9 +112,8 @@ "command.timelineCompareWithSelected": "Compare with Selected", "command.manageUnsafeRepositories": "Manage Unsafe Repositories", "command.openRepositoriesInParentFolders": "Open Repositories In Parent Folders", - "command.viewChanges": "View Changes", - "command.viewStagedChanges": "View Staged Changes", - "command.openCommit": "Open Commit", + "command.viewAllChanges": "View All Changes", + "command.viewCommit": "View Commit", "command.api.getRepositories": "Get Repositories", "command.api.getRepositoryState": "Get Repository State", "command.api.getRemoteSources": "Get Remote Sources", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 7c335f990ed..73a14a42893 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3617,8 +3617,8 @@ export class CommandCenter { }; } - @command('git.timeline.openCommit', { repository: false }) - async timelineOpenCommit(item: TimelineItem, uri: Uri | undefined, _source: string) { + @command('git.timeline.viewCommit', { repository: false }) + async timelineViewCommit(item: TimelineItem, uri: Uri | undefined, _source: string) { if (!GitTimelineItem.is(item)) { return; } @@ -3824,28 +3824,47 @@ export class CommandCenter { } } - @command('git.openCommit', { repository: true }) - async openCommit(repository: Repository, historyItem: SourceControlHistoryItem): Promise { + @command('git.viewCommit', { repository: true }) + async viewCommit(repository: Repository, historyItem: SourceControlHistoryItem): Promise { if (!repository || !historyItem) { return; } - const historyProvider = repository.historyProvider; - const historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined; - const commitFiles = await historyProvider.provideHistoryItemChanges(historyItem.id, historyItemParentId); + const commit = await repository.getCommit(historyItem.id); + const title = `${historyItem.id.substring(0, 8)} - ${commit.message}`; - if (commitFiles.length === 0) { + const multiDiffSourceUri = toGitUri(Uri.file(repository.root), historyItem.id, { scheme: 'git-commit' }); + + await this._viewChanges(repository, historyItem, multiDiffSourceUri, title); + } + + @command('git.viewAllChanges', { repository: true }) + async viewAllChanges(repository: Repository, historyItem: SourceControlHistoryItem): Promise { + if (!repository || !historyItem) { return; } const modifiedShortRef = historyItem.id.substring(0, 8); const originalShortRef = historyItem.parentIds.length > 0 ? historyItem.parentIds[0].substring(0, 8) : `${modifiedShortRef}^`; + const title = l10n.t('All Changes ({0} ↔ {1})', originalShortRef, modifiedShortRef); - const title = l10n.t('Changes ({0} ↔ {1})', originalShortRef, modifiedShortRef); - const multiDiffSourceUri = toGitUri(Uri.file(repository.root), historyItem.id, { scheme: 'git-commit' }); - const resources = commitFiles.map(change => ({ originalUri: change.originalUri, modifiedUri: change.modifiedUri })); + const multiDiffSourceUri = toGitUri(Uri.file(repository.root), historyItem.id, { scheme: 'git-changes' }); - commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); + await this._viewChanges(repository, historyItem, multiDiffSourceUri, title); + } + + async _viewChanges(repository: Repository, historyItem: SourceControlHistoryItem, multiDiffSourceUri: Uri, title: string): Promise { + const historyProvider = repository.historyProvider; + const historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined; + const files = await historyProvider.provideHistoryItemChanges(historyItem.id, historyItemParentId); + + if (files.length === 0) { + return; + } + + const resources = files.map(f => ({ originalUri: f.originalUri, modifiedUri: f.modifiedUri })); + + await commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); } private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any { diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 07859fa9cc4..5af2f709c13 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -107,7 +107,9 @@ export class MenuId { static readonly OpenEditorsContextShare = new MenuId('OpenEditorsContextShare'); static readonly ProblemsPanelContext = new MenuId('ProblemsPanelContext'); static readonly SCMInputBox = new MenuId('SCMInputBox'); + static readonly SCMIncomingChangesAllChangesContext = new MenuId('SCMIncomingChangesAllChangesContext'); static readonly SCMIncomingChangesHistoryItemContext = new MenuId('SCMIncomingChangesHistoryItemContext'); + static readonly SCMOutgoingChangesAllChangesContext = new MenuId('SCMOutgoingChangesAllChangesContext'); static readonly SCMOutgoingChangesHistoryItemContext = new MenuId('SCMOutgoingChangesHistoryItemContext'); static readonly SCMChangeContext = new MenuId('SCMChangeContext'); static readonly SCMResourceContext = new MenuId('SCMResourceContext'); diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index fa07957da0a..1cf5d9b4174 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -14,7 +14,7 @@ import { IMenu, IMenuService, MenuId, MenuRegistry } from 'vs/platform/actions/c import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { ISCMHistoryItem, ISCMHistoryItemGroupEntry, ISCMHistoryProviderMenus } from 'vs/workbench/contrib/scm/common/history'; +import { ISCMHistoryProviderMenus, SCMHistoryItemTreeElement } from 'vs/workbench/contrib/scm/common/history'; import { ISCMMenus, ISCMProvider, ISCMRepository, ISCMRepositoryMenus, ISCMResource, ISCMResourceGroup, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; function actionEquals(a: IAction, b: IAction): boolean { @@ -255,29 +255,33 @@ export class SCMRepositoryMenus implements ISCMRepositoryMenus, IDisposable { export class SCMHistoryProviderMenus implements ISCMHistoryProviderMenus, IDisposable { - private readonly historyItemMenus = new Map(); + private readonly historyItemMenus = new Map(); private readonly disposables = new DisposableStore(); constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, @IMenuService private readonly menuService: IMenuService) { } - getHistoryItemMenu(historyItemGroup: ISCMHistoryItemGroupEntry, historyItem: ISCMHistoryItem): IMenu { - return this.getOrCreateHistoryItemMenu(historyItemGroup, historyItem); + getHistoryItemMenu(historyItem: SCMHistoryItemTreeElement): IMenu { + return this.getOrCreateHistoryItemMenu(historyItem); } - private getOrCreateHistoryItemMenu(historyItemGroup: ISCMHistoryItemGroupEntry, historyItem: ISCMHistoryItem): IMenu { + private getOrCreateHistoryItemMenu(historyItem: SCMHistoryItemTreeElement): IMenu { let result = this.historyItemMenus.get(historyItem); if (!result) { - const contextKeyService = this.contextKeyService.createOverlay([ - ['scmHistoryItem', historyItem.id], - ]); + let menuId: MenuId; + if (historyItem.historyItemGroup.direction === 'incoming') { + menuId = historyItem.type === 'allChanges' ? + MenuId.SCMIncomingChangesAllChangesContext : + MenuId.SCMIncomingChangesHistoryItemContext; + } else { + menuId = historyItem.type === 'allChanges' ? + MenuId.SCMOutgoingChangesAllChangesContext : + MenuId.SCMOutgoingChangesHistoryItemContext; + } - const menuId = historyItemGroup.direction === 'incoming' ? - MenuId.SCMIncomingChangesHistoryItemContext : MenuId.SCMOutgoingChangesHistoryItemContext; - - result = this.menuService.createMenu(menuId, contextKeyService); + result = this.menuService.createMenu(menuId, this.contextKeyService); this.historyItemMenus.set(historyItem, result); } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 73ea9f1095f..9236047335e 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -890,7 +890,7 @@ class HistoryItemRenderer implements ICompressibleTreeRenderer= 2) { - const allChanges = await historyProvider.provideHistoryItemSummary(element.id, element.ancestor); - if (allChanges) { - historyItems.splice(0, 0, { ...allChanges, icon: allChanges.icon ?? Codicon.files, label: localize('allChanges', "All Changes") }); - } - } + const allChanges = showChangesSummary && historyItems.length >= 2 ? + await historyProvider.provideHistoryItemSummary(historyItems[0].id, element.ancestor) : undefined; + + historyItemsElement = [allChanges, historyItems]; this.historyProviderCache.set(repository, { ...historyProviderCacheEntry, - historyItems: historyItemsMap.set(element.id, historyItems) + historyItems: historyItemsMap.set(element.id, historyItemsElement) }); } - return historyItems.map(historyItem => ({ - ...historyItem, - historyItemGroup: element, - type: 'historyItem' - })); + const children: SCMHistoryItemTreeElement[] = []; + if (historyItemsElement[0]) { + children.push({ + ...historyItemsElement[0], + icon: historyItemsElement[0].icon ?? Codicon.files, + label: localize('allChanges', "All Changes"), + historyItemGroup: element, + type: 'allChanges' + } satisfies SCMHistoryItemTreeElement); + } + + children.push(...historyItemsElement[1] + .map(historyItem => ({ + ...historyItem, + historyItemGroup: element, + type: 'historyItem' + } satisfies SCMHistoryItemTreeElement))); + + return children; } private async getHistoryItemChanges(element: SCMHistoryItemTreeElement): Promise<(SCMHistoryItemChangeTreeElement | IResourceNode)[]> { @@ -3520,7 +3532,7 @@ class SCMTreeDataSource implements IAsyncDataSource(), + historyItems: new Map(), historyItemChanges: new Map() }; } diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index c21291a5816..be5afbb292f 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -58,7 +58,8 @@ export function isSCMHistoryItemGroupTreeElement(element: any): element is SCMHi } export function isSCMHistoryItemTreeElement(element: any): element is SCMHistoryItemTreeElement { - return (element as SCMHistoryItemTreeElement).type === 'historyItem'; + return (element as SCMHistoryItemTreeElement).type === 'allChanges' || + (element as SCMHistoryItemTreeElement).type === 'historyItem'; } export function isSCMHistoryItemChangeTreeElement(element: any): element is SCMHistoryItemChangeTreeElement { diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index eaf365e1972..385019d9ede 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -10,7 +10,7 @@ import { IMenu } from 'vs/platform/actions/common/actions'; import { ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; export interface ISCMHistoryProviderMenus { - getHistoryItemMenu(historyItemGroup: ISCMHistoryItemGroupEntry, historyItem: ISCMHistoryItem): IMenu; + getHistoryItemMenu(historyItem: SCMHistoryItemTreeElement): IMenu; } export interface ISCMHistoryProvider { @@ -30,7 +30,7 @@ export interface ISCMHistoryProvider { export interface ISCMHistoryProviderCacheEntry { readonly historyItemGroupDetails?: ISCMHistoryItemGroupDetails; - readonly historyItems: Map; + readonly historyItems: Map; readonly historyItemChanges: Map; } @@ -89,7 +89,7 @@ export interface ISCMHistoryItem { export interface SCMHistoryItemTreeElement extends ISCMHistoryItem { readonly historyItemGroup: SCMHistoryItemGroupTreeElement; - readonly type: 'historyItem'; + readonly type: 'allChanges' | 'historyItem'; } export interface ISCMHistoryItemChange { diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 51b6081d9eb..122b88c8016 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -150,12 +150,24 @@ const apiMenus: IAPIMenu[] = [ description: localize('menus.input', "The Source Control input box menu"), proposed: 'contribSourceControlInputBoxMenu' }, + { + key: 'scm/incomingChanges/allChanges/context', + id: MenuId.SCMIncomingChangesAllChangesContext, + description: localize('menus.incomingChangesAllChangesContext', "The Source Control all incoming changes context menu"), + proposed: 'contribSourceControlHistoryItemMenu' + }, { key: 'scm/incomingChanges/historyItem/context', id: MenuId.SCMIncomingChangesHistoryItemContext, description: localize('menus.incomingChangesHistoryItemContext', "The Source Control incoming changes history item context menu"), proposed: 'contribSourceControlHistoryItemMenu' }, + { + key: 'scm/outgoingChanges/allChanges/context', + id: MenuId.SCMOutgoingChangesAllChangesContext, + description: localize('menus.outgoingChangesAllChangesContext', "The Source Control all outgoing changes context menu"), + proposed: 'contribSourceControlHistoryItemMenu' + }, { key: 'scm/outgoingChanges/historyItem/context', id: MenuId.SCMOutgoingChangesHistoryItemContext, From a00c9c385f2ab66a31c80197a9505b5e0e10ebe5 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 12 Jan 2024 13:22:24 +0100 Subject: [PATCH 0269/1897] Fixes diff editor bug --- .../browser/widget/diffEditor/diffEditorViewModel.ts | 3 ++- src/vs/editor/common/diff/rangeMapping.ts | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts index ac9d816bc1d..ab824ae4c1e 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts @@ -181,7 +181,8 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo (cur, prev) => !prev || (cur.modifiedLineNumber >= prev.modifiedLineNumber + prev.lineCount && cur.originalLineNumber >= prev.originalLineNumber + prev.lineCount) ); - const hiddenRegions = updatedLastUnchangedRegions.map(r => new LineRangeMapping(r.getHiddenOriginalRange(reader), r.getHiddenModifiedRange(reader))); + let hiddenRegions = updatedLastUnchangedRegions.map(r => new LineRangeMapping(r.getHiddenOriginalRange(reader), r.getHiddenModifiedRange(reader))); + hiddenRegions = LineRangeMapping.clip(hiddenRegions, LineRange.ofLength(1, model.original.getLineCount()), LineRange.ofLength(1, model.modified.getLineCount())); visibleRegions = LineRangeMapping.inverse(hiddenRegions, model.original.getLineCount(), model.modified.getLineCount()); } diff --git a/src/vs/editor/common/diff/rangeMapping.ts b/src/vs/editor/common/diff/rangeMapping.ts index 5dc60d70294..d00f0061698 100644 --- a/src/vs/editor/common/diff/rangeMapping.ts +++ b/src/vs/editor/common/diff/rangeMapping.ts @@ -36,6 +36,18 @@ export class LineRangeMapping { return result; } + public static clip(mapping: readonly LineRangeMapping[], originalRange: LineRange, modifiedRange: LineRange): LineRangeMapping[] { + const result: LineRangeMapping[] = []; + for (const m of mapping) { + const original = m.original.intersect(originalRange); + const modified = m.modified.intersect(modifiedRange); + if (original && !original.isEmpty && modified && !modified.isEmpty) { + result.push(new LineRangeMapping(original, modified)); + } + } + return result; + } + /** * The line range in the original text model. */ From 550ea737b22da2bc83fada7000926d019145589d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 12 Jan 2024 10:57:55 -0300 Subject: [PATCH 0270/1897] Chat Agent API updates #199908 --- .../api/common/extHostChatAgents2.ts | 20 ++++++------- .../api/common/extHostTypeConverters.ts | 2 +- .../vscode.proposed.chatAgents2.d.ts | 30 +++++++++++-------- .../vscode.proposed.chatAgents2Additions.d.ts | 10 +++---- .../vscode.proposed.defaultChatAgent.d.ts | 2 +- .../vscode.proposed.interactive.d.ts | 17 ++--------- 6 files changed, 37 insertions(+), 44 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index dd59212cbb0..fe236452814 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -28,7 +28,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { private static _idPool = 0; - private readonly _agents = new Map(); + private readonly _agents = new Map>(); private readonly _proxy: MainThreadChatAgentsShape2; private readonly _previousResultMap: Map = new Map(); @@ -42,9 +42,9 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); } - createChatAgent(extension: IExtensionDescription, name: string, handler: vscode.ChatAgentExtendedHandler): vscode.ChatAgent2 { + createChatAgent(extension: IExtensionDescription, name: string, handler: vscode.ChatAgentExtendedHandler): vscode.ChatAgent2 { const handle = ExtHostChatAgents2._idPool++; - const agent = new ExtHostChatAgent(extension, name, this._proxy, handle, handler); + const agent = new ExtHostChatAgent(extension, name, this._proxy, handle, handler); this._agents.set(handle, agent); this._proxy.$registerAgent(handle, name, {}); @@ -228,11 +228,11 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } } -class ExtHostChatAgent { +class ExtHostChatAgent { private _slashCommandProvider: vscode.ChatAgentSlashCommandProvider | undefined; private _lastSlashCommands: vscode.ChatAgentSlashCommand[] | undefined; - private _followupProvider: vscode.FollowupProvider | undefined; + private _followupProvider: vscode.FollowupProvider | undefined; private _description: string | undefined; private _fullName: string | undefined; private _iconPath: vscode.Uri | { light: vscode.Uri; dark: vscode.Uri } | vscode.ThemeIcon | undefined; @@ -241,7 +241,7 @@ class ExtHostChatAgent { private _helpTextPostfix: string | vscode.MarkdownString | undefined; private _sampleRequest?: string; private _isSecondary: boolean | undefined; - private _onDidReceiveFeedback = new Emitter(); + private _onDidReceiveFeedback = new Emitter>(); private _onDidPerformAction = new Emitter(); private _supportIssueReporting: boolean | undefined; private _agentVariableProvider?: { provider: vscode.ChatAgentCompletionItemProvider; triggerCharacters: string[] }; @@ -254,7 +254,7 @@ class ExtHostChatAgent { private readonly _callback: vscode.ChatAgentExtendedHandler, ) { } - acceptFeedback(feedback: vscode.ChatAgentResult2Feedback) { + acceptFeedback(feedback: vscode.ChatAgentResult2Feedback) { this._onDidReceiveFeedback.fire(feedback); } @@ -302,7 +302,7 @@ class ExtHostChatAgent { })); } - async provideFollowups(result: vscode.ChatAgentResult2, token: CancellationToken): Promise { + async provideFollowups(result: TResult, token: CancellationToken): Promise { if (!this._followupProvider) { return []; } @@ -313,7 +313,7 @@ class ExtHostChatAgent { return followups.map(f => typeConvert.ChatFollowup.from(f)); } - get apiAgent(): vscode.ChatAgent2 { + get apiAgent(): vscode.ChatAgent2 { let disposed = false; let updateScheduled = false; const updateMetadataSoon = () => { @@ -478,7 +478,7 @@ class ExtHostChatAgent { that._onDidReceiveFeedback.dispose(); that._proxy.$unregisterAgent(that._handle); }, - } satisfies vscode.ChatAgent2; + } satisfies vscode.ChatAgent2; } invoke(request: vscode.ChatAgentRequest, context: vscode.ChatAgentContext, progress: Progress, token: CancellationToken): vscode.ProviderResult { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 5e2fcc991a8..17e1ef13113 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2171,7 +2171,7 @@ export namespace DataTransfer { } export namespace ChatReplyFollowup { - export function from(followup: vscode.InteractiveSessionReplyFollowup | vscode.InteractiveEditorReplyFollowup): IChatReplyFollowup { + export function from(followup: vscode.ChatAgentReplyFollowup | vscode.InteractiveEditorReplyFollowup): IChatReplyFollowup { return { kind: 'reply', message: followup.message, diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 54a7bf71c06..172f4df6d67 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -64,12 +64,12 @@ declare module 'vscode' { /** * Represents user feedback for a result. */ - export interface ChatAgentResult2Feedback { + export interface ChatAgentResult2Feedback { /** * This instance of ChatAgentResult2 is the same instance that was returned from the chat agent, * and it can be extended with arbitrary properties if needed. */ - readonly result: ChatAgentResult2; + readonly result: TResult; /** * The kind of feedback that was received. @@ -161,16 +161,16 @@ declare module 'vscode' { /** * Will be invoked once after each request to get suggested followup questions to show the user. The user can click the followup to send it to the chat. */ - export interface FollowupProvider { + export interface FollowupProvider { /** * * @param result The same instance of the result object that was returned by the chat agent, and it can be extended with arbitrary properties if needed. * @param token A cancellation token. */ - provideFollowups(result: ChatAgentResult2, token: CancellationToken): ProviderResult; + provideFollowups(result: TResult, token: CancellationToken): ProviderResult; } - export interface ChatAgent2 { + export interface ChatAgent2 { /** * The short name by which this agent is referred to in the UI, e.g `workspace`. @@ -209,7 +209,7 @@ declare module 'vscode' { /** * This provider will be called once after each request to retrieve suggested followup questions. */ - followupProvider?: FollowupProvider; + followupProvider?: FollowupProvider; /** * When the user clicks this agent in `/help`, this text will be submitted to this slash command @@ -223,7 +223,7 @@ declare module 'vscode' { * The passed {@link ChatAgentResult2Feedback.result result} is guaranteed to be the same instance that was * previously returned from this chat agent. */ - onDidReceiveFeedback: Event; + onDidReceiveFeedback: Event>; /** * Dispose this agent and free resources @@ -250,16 +250,19 @@ declare module 'vscode' { variables: Record; } - // TODO@API we need to arrive at a state where we can put things into buckets-by-name of (1) rendered data, (2) metadata, etc pp - export type ChatAgentProgress = + export type ChatAgentContentProgress = | ChatAgentContent - | ChatAgentTask | ChatAgentFileTree + | ChatAgentInlineContentReference + | ChatAgentTask; + + export type ChatAgentMetadataProgress = | ChatAgentUsedContext | ChatAgentContentReference - | ChatAgentInlineContentReference | ChatAgentProgressMessage; + export type ChatAgentProgress = ChatAgentContentProgress | ChatAgentMetadataProgress; + /** * Is displayed in the UI to communicate steps of progress to the user. Should be used when the agent may be slow to respond, e.g. due to doing extra work before sending the actual request to the LLM. */ @@ -383,7 +386,7 @@ declare module 'vscode' { * @param handler The reply-handler of the agent. * @returns A new chat agent */ - export function createChatAgent(name: string, handler: ChatAgentHandler): ChatAgent2; + export function createChatAgent(name: string, handler: ChatAgentHandler): ChatAgent2; /** * Register a variable which can be used in a chat request to any agent. @@ -414,6 +417,9 @@ declare module 'vscode' { */ value: string | Uri; + /** + * A description of this value, which could be provided to the LLM as a hint. + */ description?: string; } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index 51fd7baf32d..146ba3bbe92 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -5,7 +5,7 @@ declare module 'vscode' { - export interface ChatAgent2 { + export interface ChatAgent2 { onDidPerformAction: Event; supportIssueReporting?: boolean; } @@ -43,7 +43,7 @@ declare module 'vscode' { | ChatAgentMarkdownContent | ChatAgentDetectedAgent; - export interface ChatAgent2 { + export interface ChatAgent2 { /** * Provide a set of variables that can only be used with this agent. */ @@ -70,7 +70,7 @@ declare module 'vscode' { /** * Create a chat agent with the extended progress type */ - export function createChatAgent(name: string, handler: ChatAgentExtendedHandler): ChatAgent2; + export function createChatAgent(name: string, handler: ChatAgentExtendedHandler): ChatAgent2; } /* @@ -111,13 +111,13 @@ declare module 'vscode' { export interface ChatAgentCommandAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'command'; - command: ChatAgentCommandFollowup; + command: any; // ChatAgentCommandButton; } export interface ChatAgentSessionFollowupAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'followUp'; - followup: ChatAgentReplyFollowup; + followup: ChatAgentFollowup; } export interface ChatAgentBugReportAction { diff --git a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts b/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts index e4c4ba15c9a..cc671da4e69 100644 --- a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts +++ b/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts @@ -5,7 +5,7 @@ declare module 'vscode' { - export interface ChatAgent2 { + export interface ChatAgent2 { /** * When true, this agent is invoked by default when no other agent is being invoked */ diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index d24f451699a..98955ea4dd2 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -111,24 +111,11 @@ declare module 'vscode' { inputPlaceholder?: string; } - export interface InteractiveResponseCommand { - commandId: string; - args?: any[]; - title: string; // supports codicon strings - when?: string; - } - - export interface InteractiveSessionReplyFollowup { - message: string; - tooltip?: string; - title?: string; - } - - export type InteractiveWelcomeMessageContent = string | MarkdownString | InteractiveSessionReplyFollowup[]; + export type InteractiveWelcomeMessageContent = string | MarkdownString | ChatAgentReplyFollowup[]; export interface InteractiveSessionProvider { provideWelcomeMessage?(token: CancellationToken): ProviderResult; - provideSampleQuestions?(token: CancellationToken): ProviderResult; + provideSampleQuestions?(token: CancellationToken): ProviderResult; prepareSession(token: CancellationToken): ProviderResult; } From 37b173d55970e5777db965809b2c5b983bc814b7 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 12 Jan 2024 15:16:53 +0100 Subject: [PATCH 0271/1897] Add stage/unstage actions to the multi-diff editor (#202297) --- extensions/git/package.json | 40 ++++++++++++++++ extensions/git/src/commands.ts | 47 +++++++++++++++++++ .../actions/common/menusExtensionPoint.ts | 6 +++ .../common/extensionsApiProposals.ts | 1 + ....proposed.contribMultiDiffEditorMenus.d.ts | 6 +++ 5 files changed, 100 insertions(+) create mode 100644 src/vscode-dts/vscode.proposed.contribMultiDiffEditorMenus.d.ts diff --git a/extensions/git/package.json b/extensions/git/package.json index c7901c9ee14..913656a1121 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -25,6 +25,7 @@ "tabInputTextMerge", "timeline", "contribMergeEditorMenus", + "contribMultiDiffEditorMenus", "contribSourceControlInputBoxMenu", "contribSourceControlHistoryItemMenu" ], @@ -195,6 +196,13 @@ "icon": "$(add)", "enablement": "!operationInProgress" }, + { + "command": "git.stageFile", + "title": "%command.stage%", + "category": "Git", + "icon": "$(add)", + "enablement": "!operationInProgress" + }, { "command": "git.revertChange", "title": "%command.revertChange%", @@ -222,6 +230,13 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.unstageFile", + "title": "%command.unstage%", + "category": "Git", + "icon": "$(remove)", + "enablement": "!operationInProgress" + }, { "command": "git.clean", "title": "%command.clean%", @@ -1278,6 +1293,14 @@ { "command": "git.viewAllChanges", "when": "false" + }, + { + "command": "git.stageFile", + "when": "false" + }, + { + "command": "git.unstageFile", + "when": "false" } ], "scm/title": [ @@ -1843,6 +1866,23 @@ "when": "config.git.enabled && !git.missing && !isInDiffEditor && !isMergeEditor && resource in git.mergeChanges" } ], + "multiDiffEditor/resource/title": [ + { + "command": "git.stageFile", + "group": "navigation", + "when": "scmProvider == git && scmResourceGroup == workingTree" + }, + { + "command": "git.stageFile", + "group": "navigation", + "when": "scmProvider == git && scmResourceGroup == untracked" + }, + { + "command": "git.unstageFile", + "group": "navigation", + "when": "scmProvider == git && scmResourceGroup == index" + } + ], "scm/change/title": [ { "command": "git.stageChange", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 73a14a42893..18c982d3b94 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1509,6 +1509,30 @@ export class CommandCenter { await this._stageChanges(textEditor, selectedChanges); } + @command('git.stageFile') + async stageFile(uri: Uri): Promise { + if (!uri) { + return; + } + + const repository = this.model.getRepository(uri); + if (!repository) { + return; + } + + const resources = [ + ...repository.workingTreeGroup.resourceStates, + ...repository.untrackedGroup.resourceStates] + .filter(r => r.multiFileDiffEditorModifiedUri?.toString() === uri.toString()) + .map(r => r.resourceUri); + + if (resources.length === 0) { + return; + } + + await repository.add(resources); + } + @command('git.acceptMerge') async acceptMerge(_uri: Uri | unknown): Promise { const { activeTab } = window.tabGroups.activeTabGroup; @@ -1761,6 +1785,29 @@ export class CommandCenter { await this.runByRepository(modifiedUri, async (repository, resource) => await repository.stage(resource, result)); } + @command('git.unstageFile') + async unstageFile(uri: Uri): Promise { + if (!uri) { + return; + } + + const repository = this.model.getRepository(uri); + if (!repository) { + return; + } + + const resources = repository.indexGroup.resourceStates + .filter(r => r.multiFileDiffEditorModifiedUri?.toString() === uri.toString()) + .map(r => r.resourceUri); + + if (resources.length === 0) { + return; + } + + await repository.revert(resources); + } + + @command('git.clean') async clean(...resourceStates: SourceControlResourceState[]): Promise { // Remove duplicate resources diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 122b88c8016..df25f3c2008 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -369,6 +369,12 @@ const apiMenus: IAPIMenu[] = [ description: localize('menus.mergeEditorResult', "The result toolbar of the merge editor"), proposed: 'contribMergeEditorMenus' }, + { + key: 'multiDiffEditor/resource/title', + id: MenuId.MultiDiffEditorFileToolbar, + description: localize('menus.multiDiffEditorResource', "The resource toolbar in the multi diff editor"), + proposed: 'contribMultiDiffEditorMenus' + } ]; namespace schema { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index ad592f53082..225fd886589 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -28,6 +28,7 @@ export const allApiProposals = Object.freeze({ contribLabelFormatterWorkspaceTooltip: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts', contribMenuBarHome: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMenuBarHome.d.ts', contribMergeEditorMenus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMergeEditorMenus.d.ts', + contribMultiDiffEditorMenus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMultiDiffEditorMenus.d.ts', contribNotebookStaticPreloads: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribNotebookStaticPreloads.d.ts', contribRemoteHelp: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribRemoteHelp.d.ts', contribShareMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts', diff --git a/src/vscode-dts/vscode.proposed.contribMultiDiffEditorMenus.d.ts b/src/vscode-dts/vscode.proposed.contribMultiDiffEditorMenus.d.ts new file mode 100644 index 00000000000..6641a0064f5 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribMultiDiffEditorMenus.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for `multiDiffEditor/*` menus From cc5e1c7ee76c1ed7038e91cc0bb69bddaf700893 Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 12 Jan 2024 15:36:01 +0100 Subject: [PATCH 0272/1897] Added sticky scroll compression (#202345) * Added sticky scroll compression * remove first visible index under widget --- src/vs/base/browser/ui/tree/abstractTree.ts | 99 ++++++++++++++++----- src/vs/base/browser/ui/tree/objectTree.ts | 93 +++++++++++++++++-- 2 files changed, 166 insertions(+), 26 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 848a195256f..7a48963048b 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1172,7 +1172,7 @@ class FindController implements IDisposable { } } -interface StickyScrollNode { +export interface StickyScrollNode { readonly node: ITreeNode; readonly startIndex: number; readonly endIndex: number; @@ -1221,11 +1221,33 @@ class StickyScrollState extends Disposable { } } +export interface IStickyScrollDelegate { + constrainStickyScrollNodes(stickyNodes: StickyScrollNode[], stickyScrollMaxItemCount: number, maxWidgetHeight: number): StickyScrollNode[]; +} + +class DefaultStickyScrollDelegate implements IStickyScrollDelegate { + + constrainStickyScrollNodes(stickyNodes: StickyScrollNode[], stickyScrollMaxItemCount: number, maxWidgetHeight: number): StickyScrollNode[] { + + for (let i = 0; i < stickyNodes.length; i++) { + const stickyNode = stickyNodes[i]; + const stickyNodeBottom = stickyNode.position + stickyNode.height; + if (stickyNodeBottom > maxWidgetHeight || i >= stickyScrollMaxItemCount) { + return stickyNodes.slice(0, i); + } + } + + return stickyNodes; + } +} + class StickyScrollController extends Disposable { readonly onDidChangeHasFocus: Event; readonly onContextMenu: Event>; + private readonly stickyScrollDelegate: IStickyScrollDelegate; + private stickyScrollMaxItemCount: number; private readonly maxWidgetViewRatio = 0.4; @@ -1254,7 +1276,9 @@ class StickyScrollController extends Disposable { const stickyScrollOptions = this.validateStickySettings(options); this.stickyScrollMaxItemCount = stickyScrollOptions.stickyScrollMaxItemCount; - this._widget = this._register(new StickyScrollWidget(view.getScrollableElement(), view, model, renderers, treeDelegate, options.accessibilityProvider)); + this.stickyScrollDelegate = options.stickyScrollDelegate ?? new DefaultStickyScrollDelegate(); + + this._widget = this._register(new StickyScrollWidget(view.getScrollableElement(), view, tree, renderers, treeDelegate, options.accessibilityProvider)); this.onDidChangeHasFocus = this._widget.onDidChangeHasFocus; this.onContextMenu = this._widget.onContextMenu; @@ -1292,29 +1316,27 @@ class StickyScrollController extends Disposable { private findStickyState(firstVisibleNode: ITreeNode): StickyScrollState | undefined { const stickyNodes: StickyScrollNode[] = []; - const maximumStickyWidgetHeight = this.view.renderHeight * this.maxWidgetViewRatio; let firstVisibleNodeUnderWidget: ITreeNode | undefined = firstVisibleNode; let stickyNodesHeight = 0; let nextStickyNode = this.getNextStickyNode(firstVisibleNodeUnderWidget, undefined, stickyNodesHeight); - while (nextStickyNode && stickyNodesHeight + nextStickyNode.height < maximumStickyWidgetHeight) { + while (nextStickyNode) { stickyNodes.push(nextStickyNode); stickyNodesHeight += nextStickyNode.height; - if (stickyNodes.length >= this.stickyScrollMaxItemCount) { - break; - } - - firstVisibleNodeUnderWidget = this.getNextVisibleNode(firstVisibleNodeUnderWidget); - if (!firstVisibleNodeUnderWidget) { - break; + if (stickyNodes.length <= this.stickyScrollMaxItemCount) { + firstVisibleNodeUnderWidget = this.getNextVisibleNode(firstVisibleNodeUnderWidget); + if (!firstVisibleNodeUnderWidget) { + break; + } } nextStickyNode = this.getNextStickyNode(firstVisibleNodeUnderWidget, nextStickyNode.node, stickyNodesHeight); } - return stickyNodes.length ? new StickyScrollState(stickyNodes) : undefined; + const contrainedStickyNodes = this.constrainStickyNodes(stickyNodes); + return contrainedStickyNodes.length ? new StickyScrollState(contrainedStickyNodes) : undefined; } private getNextVisibleNode(node: ITreeNode): ITreeNode | undefined { @@ -1407,6 +1429,34 @@ class StickyScrollController extends Disposable { return stickyRowPositionTop; } + private constrainStickyNodes(stickyNodes: StickyScrollNode[]): StickyScrollNode[] { + if (stickyNodes.length === 0) { + return []; + } + + // Check if sticky nodes need to be constrained + const maximumStickyWidgetHeight = this.view.renderHeight * this.maxWidgetViewRatio; + const lastStickyNode = stickyNodes[stickyNodes.length - 1]; + if (stickyNodes.length <= this.stickyScrollMaxItemCount && lastStickyNode.position + lastStickyNode.height <= maximumStickyWidgetHeight) { + return stickyNodes; + } + + // constrain sticky nodes + const constrainedStickyNodes = this.stickyScrollDelegate.constrainStickyScrollNodes(stickyNodes, this.stickyScrollMaxItemCount, maximumStickyWidgetHeight); + + if (!constrainedStickyNodes.length) { + return []; + } + + // Validate constraints + const lastConstrainedStickyNode = constrainedStickyNodes[constrainedStickyNodes.length - 1]; + if (constrainedStickyNodes.length > this.stickyScrollMaxItemCount || lastConstrainedStickyNode.position + lastConstrainedStickyNode.height > maximumStickyWidgetHeight) { + throw new Error('stickyScrollDelegate violates constraints'); + } + + return constrainedStickyNodes; + } + private getParentNode(node: ITreeNode): ITreeNode | undefined { const nodeLocation = this.model.getNodeLocation(node); const parentLocation = this.model.getParentNodeLocation(nodeLocation); @@ -1418,10 +1468,8 @@ class StickyScrollController extends Disposable { return this.model.getListRenderCount(nodeLocation) > 1; } - private getNodeIndex(node: ITreeNode, nodeLocation?: TRef): number { - if (nodeLocation === undefined) { - nodeLocation = this.model.getNodeLocation(node); - } + private getNodeIndex(node: ITreeNode): number { + const nodeLocation = this.model.getNodeLocation(node); const nodeIndex = this.model.getListIndex(nodeLocation); return nodeIndex; } @@ -1469,6 +1517,10 @@ class StickyScrollController extends Disposable { } updateOptions(optionsUpdate: IAbstractTreeOptionsUpdate = {}): void { + if (!optionsUpdate.stickyScrollMaxItemCount) { + return; + } + const validatedOptions = this.validateStickySettings(optionsUpdate); if (this.stickyScrollMaxItemCount !== validatedOptions.stickyScrollMaxItemCount) { this.stickyScrollMaxItemCount = validatedOptions.stickyScrollMaxItemCount; @@ -1477,7 +1529,7 @@ class StickyScrollController extends Disposable { } validateStickySettings(options: IAbstractTreeOptionsUpdate): { stickyScrollMaxItemCount: number } { - let stickyScrollMaxItemCount = 5; + let stickyScrollMaxItemCount = 7; if (typeof options.stickyScrollMaxItemCount === 'number') { stickyScrollMaxItemCount = Math.max(options.stickyScrollMaxItemCount, 1); } @@ -1497,7 +1549,7 @@ class StickyScrollWidget implements IDisposable { constructor( container: HTMLElement, private readonly view: List>, - private readonly model: ITreeModel, + private readonly tree: AbstractTree, private readonly treeRenderers: TreeRenderer[], private readonly treeDelegate: IListVirtualDelegate>, private readonly accessibilityProvider: IListAccessibilityProvider | undefined, @@ -1576,8 +1628,7 @@ class StickyScrollWidget implements IDisposable { private createElement(stickyNode: StickyScrollNode, stickyIndex: number, stickyNodesTotal: number): { element: HTMLElement; disposable: IDisposable } { - const nodeLocation = this.model.getNodeLocation(stickyNode.node); - const nodeIndex = this.model.getListIndex(nodeLocation); + const nodeIndex = stickyNode.startIndex; // Sticky element container const stickyElement = document.createElement('div'); @@ -1600,7 +1651,12 @@ class StickyScrollWidget implements IDisposable { throw new Error(`No renderer found for template id ${nodeTemplateId}`); } - const nodeCopy = new Proxy(stickyNode.node, {}); + // To make sure we do not influence the original node, we create a copy of the node + // We need to check if it is already a unique instance of the node by the delegate + let nodeCopy = stickyNode.node; + if (nodeCopy === this.tree.getNode(this.tree.getNodeLocation(stickyNode.node))) { + nodeCopy = new Proxy(stickyNode.node, {}); + } // Render the element const templateData = renderer.renderTemplate(stickyElement); @@ -1972,6 +2028,7 @@ export interface IAbstractTreeOptions extends IAbstractTr readonly findWidgetEnabled?: boolean; readonly findWidgetStyles?: IFindWidgetStyles; readonly defaultFindVisibility?: TreeVisibility | ((e: T) => TreeVisibility); + readonly stickyScrollDelegate?: IStickyScrollDelegate; } function dfs(node: ITreeNode, fn: (node: ITreeNode) => void): void { diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts index 5880b0bb25d..41903e32813 100644 --- a/src/vs/base/browser/ui/tree/objectTree.ts +++ b/src/vs/base/browser/ui/tree/objectTree.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { AbstractTree, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree'; +import { AbstractTree, IAbstractTreeOptions, IAbstractTreeOptionsUpdate, IStickyScrollDelegate, StickyScrollNode } from 'vs/base/browser/ui/tree/abstractTree'; import { CompressibleObjectTreeModel, ElementMapper, ICompressedTreeElement, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { IList } from 'vs/base/browser/ui/tree/indexTreeModel'; import { IObjectTreeModel, ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; @@ -106,7 +106,7 @@ class CompressibleRenderer, TFilterData, TTemplateDat return this._compressedTreeNodeProvider(); } - constructor(private _compressedTreeNodeProvider: () => ICompressedTreeNodeProvider, private renderer: ICompressibleTreeRenderer) { + constructor(private _compressedTreeNodeProvider: () => ICompressedTreeNodeProvider, private stickyScrollDelegate: CompressibleStickyScrollDelegate, private renderer: ICompressibleTreeRenderer) { this.templateId = renderer.templateId; if (renderer.onDidChangeTwistieState) { @@ -120,7 +120,10 @@ class CompressibleRenderer, TFilterData, TTemplateDat } renderElement(node: ITreeNode, index: number, templateData: CompressibleTemplateData, height: number | undefined): void { - const compressedTreeNode = this.compressedTreeNodeProvider.getCompressedTreeNode(node.element) as ITreeNode, TFilterData>; + let compressedTreeNode = this.stickyScrollDelegate.getCompressedNode(node); + if (!compressedTreeNode) { + compressedTreeNode = this.compressedTreeNodeProvider.getCompressedTreeNode(node.element) as ITreeNode, TFilterData>; + } if (compressedTreeNode.element.elements.length === 1) { templateData.compressedTreeNode = undefined; @@ -151,6 +154,84 @@ class CompressibleRenderer, TFilterData, TTemplateDat } } +class CompressibleStickyScrollDelegate implements IStickyScrollDelegate { + + private readonly compressedStickyNodes = new Map, ITreeNode, TFilterData>>(); + + constructor(private readonly modelProvider: () => CompressibleObjectTreeModel) { } + + getCompressedNode(node: ITreeNode): ITreeNode, TFilterData> | undefined { + return this.compressedStickyNodes.get(node); + } + + constrainStickyScrollNodes(stickyNodes: StickyScrollNode[], stickyScrollMaxItemCount: number, maxWidgetHeight: number): StickyScrollNode[] { + this.compressedStickyNodes.clear(); + if (stickyNodes.length === 0) { + return []; + } + + for (let i = 0; i < stickyNodes.length; i++) { + const stickyNode = stickyNodes[i]; + const stickyNodeBottom = stickyNode.position + stickyNode.height; + const followingReachesMaxHeight = i + 1 < stickyNodes.length && stickyNodeBottom + stickyNodes[i + 1].height > maxWidgetHeight; + + if (followingReachesMaxHeight || i >= stickyScrollMaxItemCount - 1 && stickyScrollMaxItemCount < stickyNodes.length) { + const uncompressedStickyNodes = stickyNodes.slice(0, i); + const overflowingStickyNodes = stickyNodes.slice(i); + const compressedStickyNode = this.compressStickyNodes(overflowingStickyNodes); + return [...uncompressedStickyNodes, compressedStickyNode]; + } + + } + + return stickyNodes; + } + + private compressStickyNodes(stickyNodes: StickyScrollNode[]): StickyScrollNode { + + if (!this.modelProvider().isCompressionEnabled()) { + return stickyNodes[0]; + } + + // Collect all elements to be compressed + const elements: T[] = []; + for (const stickyNode of stickyNodes) { + const compressedNode = this.modelProvider().getCompressedTreeNode(stickyNode.node.element); + + if (compressedNode.element) { + elements.push(...compressedNode.element.elements); + } + } + + if (elements.length === 0) { + throw new Error('Can\'t compress when there are no elements to compress'); + } + + if (elements.length === 1) { + return stickyNodes[0]; + } + + // Compress the elements + const lastStickyNode = stickyNodes[stickyNodes.length - 1]; + const compressedElement: ICompressedTreeNode = { elements, incompressible: false }; + const compressedNode = { ...lastStickyNode.node, children: [], element: compressedElement } as ITreeNode, TFilterData>; + + const stickyTreeNode = new Proxy(stickyNodes[0].node, {}); + + const compressedStickyNode: StickyScrollNode = { + node: stickyTreeNode, + startIndex: stickyNodes[0].startIndex, + endIndex: lastStickyNode.endIndex, + position: stickyNodes[0].position, + height: stickyNodes[0].height, + }; + + this.compressedStickyNodes.set(stickyTreeNode, compressedNode); + + return compressedStickyNode; + } +} + export interface ICompressibleKeyboardNavigationLabelProvider extends IKeyboardNavigationLabelProvider { getCompressedNodeKeyboardNavigationLabel(elements: T[]): { toString(): string | undefined } | undefined; } @@ -200,8 +281,10 @@ export class CompressibleObjectTree, TFilterData = vo options: ICompressibleObjectTreeOptions = {} ) { const compressedTreeNodeProvider = () => this; - const compressibleRenderers = renderers.map(r => new CompressibleRenderer(compressedTreeNodeProvider, r)); - super(user, container, delegate, compressibleRenderers, asObjectTreeOptions(compressedTreeNodeProvider, options)); + const stickyScrollDelegate = new CompressibleStickyScrollDelegate(() => this.model); + const compressibleRenderers = renderers.map(r => new CompressibleRenderer(compressedTreeNodeProvider, stickyScrollDelegate, r)); + + super(user, container, delegate, compressibleRenderers, { ...asObjectTreeOptions(compressedTreeNodeProvider, options), stickyScrollDelegate }); } override setChildren(element: T | null, children: Iterable> = Iterable.empty(), options?: IObjectTreeSetChildrenOptions): void { From 448fab1a7390fdb8edd61d2f864db36f4eed5525 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 12 Jan 2024 15:59:44 +0100 Subject: [PATCH 0273/1897] handle Recognized and Recognizing differently --- .../inlineChat/electron-sandbox/inlineChatQuickVoice.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index 88333cf1215..b666c9bbbc3 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -193,8 +193,11 @@ export class InlineChatQuickVoice implements IEditorContribution { case SpeechToTextStatus.Stopped: break; case SpeechToTextStatus.Recognizing: + // TODO@jrieken special rendering for "in-flight" message? + this._widget.updateInput(!message ? e.text : `${message} ${e.text}`); + break; case SpeechToTextStatus.Recognized: - message = e.text; + message = !message ? e.text : `${message} ${e.text}`; this._widget.updateInput(message); break; } From e40021eca093b18c05faf76da0537e44ff237db0 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 12 Jan 2024 15:53:50 +0100 Subject: [PATCH 0274/1897] Fixes #200336 --- src/vs/base/common/async.ts | 15 ++- src/vs/base/common/observableInternal/base.ts | 24 ++++- .../base/common/observableInternal/utils.ts | 4 +- .../multiDiffEditorViewModel.ts | 6 +- .../browser/multiDiffEditorInput.ts | 78 ++++++++++----- .../contrib/multiDiffEditor/browser/utils.ts | 99 +++++++++++++++++++ 6 files changed, 196 insertions(+), 30 deletions(-) create mode 100644 src/vs/workbench/contrib/multiDiffEditor/browser/utils.ts diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 8be1eb8a7c4..2b12a493357 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -1680,6 +1680,10 @@ export class StatefulPromise { ); } + /** + * Returns the resolved value. + * Throws if the promise is not resolved yet. + */ public requireValue(): T { if (!this._isResolved) { throw new BugIndicatingError('Promise is not resolved yet'); @@ -1692,7 +1696,7 @@ export class StatefulPromise { } export class LazyStatefulPromise { - private _promise = new Lazy(() => new StatefulPromise(this._compute())); + private readonly _promise = new Lazy(() => new StatefulPromise(this._compute())); constructor( private readonly _compute: () => Promise, @@ -1700,7 +1704,7 @@ export class LazyStatefulPromise { /** * Returns the resolved value. - * Crashes if the promise is not resolved yet. + * Throws if the promise is not resolved yet. */ public requireValue(): T { return this._promise.value.requireValue(); @@ -1712,6 +1716,13 @@ export class LazyStatefulPromise { public getPromise(): Promise { return this._promise.value.promise; } + + /** + * Reads the current value without triggering a computation of the promise. + */ + public get currentValue(): T | undefined { + return this._promise.rawValue?.value; + } } //#endregion diff --git a/src/vs/base/common/observableInternal/base.ts b/src/vs/base/common/observableInternal/base.ts index c7ff44dad68..6b487100d86 100644 --- a/src/vs/base/common/observableInternal/base.ts +++ b/src/vs/base/common/observableInternal/base.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; +import { keepObserved, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; import type { derivedOpts } from 'vs/base/common/observableInternal/derived'; import { getLogger } from 'vs/base/common/observableInternal/logging'; @@ -69,6 +69,11 @@ export interface IObservable { */ recomputeInitiallyAndOnChange(store: DisposableStore, handleValue?: (value: T) => void): IObservable; + /** + * Makes sure this value is cached. + */ + keepObserved(store: DisposableStore): IObservable; + /** * A human-readable name for debugging purposes. */ @@ -152,11 +157,16 @@ export interface ITransaction { } let _recomputeInitiallyAndOnChange: typeof recomputeInitiallyAndOnChange; - export function _setRecomputeInitiallyAndOnChange(recomputeInitiallyAndOnChange: typeof _recomputeInitiallyAndOnChange) { _recomputeInitiallyAndOnChange = recomputeInitiallyAndOnChange; } +let _keepObserved: typeof keepObserved; +export function _setKeepObserved(keepObserved: typeof _keepObserved) { + _keepObserved = keepObserved; +} + + let _derived: typeof derivedOpts; /** * @internal @@ -224,6 +234,16 @@ export abstract class ConvenientObservable implements IObservable { + store.add(_keepObserved!(this)); + return this; + } + public abstract get debugName(): string; } diff --git a/src/vs/base/common/observableInternal/utils.ts b/src/vs/base/common/observableInternal/utils.ts index 35b01114b45..018045f1282 100644 --- a/src/vs/base/common/observableInternal/utils.ts +++ b/src/vs/base/common/observableInternal/utils.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { autorun } from 'vs/base/common/observableInternal/autorun'; -import { BaseObservable, ConvenientObservable, IObservable, IObserver, IReader, ITransaction, _setRecomputeInitiallyAndOnChange, getDebugName, getFunctionName, observableValue, subtransaction, transaction } from 'vs/base/common/observableInternal/base'; +import { BaseObservable, ConvenientObservable, IObservable, IObserver, IReader, ITransaction, _setKeepObserved, _setRecomputeInitiallyAndOnChange, getDebugName, getFunctionName, observableValue, subtransaction, transaction } from 'vs/base/common/observableInternal/base'; import { derived, derivedOpts } from 'vs/base/common/observableInternal/derived'; import { getLogger } from 'vs/base/common/observableInternal/logging'; @@ -331,6 +331,8 @@ export function keepObserved(observable: IObservable): IDisposable { }); } +_setKeepObserved(keepObserved); + /** * This converts the given observable into an autorun. */ diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts index be3dcec1286..c1382def822 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts @@ -15,7 +15,7 @@ import { ContextKeyValue } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class MultiDiffEditorViewModel extends Disposable { - private readonly _documents = observableFromEvent(this._model.onDidChange, /** @description MultiDiffEditorViewModel.documents */() => this._model.documents); + private readonly _documents = observableFromEvent(this.model.onDidChange, /** @description MultiDiffEditorViewModel.documents */() => this.model.documents); public readonly items = mapObservableArrayCached(this._documents, this, (d, store) => store.add(new DocumentDiffItemViewModel(d, this._instantiationService))) .recomputeInitiallyAndOnChange(this._store); @@ -45,11 +45,11 @@ export class MultiDiffEditorViewModel extends Disposable { } public get contextKeys(): Record | undefined { - return this._model.contextKeys; + return this.model.contextKeys; } constructor( - private readonly _model: IMultiDiffEditorModel, + public readonly model: IMultiDiffEditorModel, private readonly _instantiationService: IInstantiationService, ) { super(); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index c93fcdb1774..0799ccfc045 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -10,8 +10,8 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableStore, IDisposable, IReference, toDisposable } from 'vs/base/common/lifecycle'; import { parse } from 'vs/base/common/marshalling'; import { deepClone } from 'vs/base/common/objects'; -import { autorun, derived, observableFromEvent, observableValue } from 'vs/base/common/observable'; -import { mapObservableArrayCached } from 'vs/base/common/observableInternal/utils'; +import { autorun, derived, observableFromEvent } from 'vs/base/common/observable'; +import { constObservable, mapObservableArrayCached } from 'vs/base/common/observableInternal/utils'; import { ThemeIcon } from 'vs/base/common/themables'; import { isDefined, isObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -25,12 +25,13 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/tex import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; -import { DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, EditorInputWithOptions, IEditorSerializer, IResourceMultiDiffEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor'; +import { DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, EditorInputWithOptions, IEditorSerializer, IResourceMultiDiffEditorInput, ISaveOptions, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { MultiDiffEditorIcon } from 'vs/workbench/contrib/multiDiffEditor/browser/icons.contribution'; import { ConstResolvedMultiDiffSource, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService'; +import { ObservableLazyStatefulPromise } from 'vs/workbench/contrib/multiDiffEditor/browser/utils'; import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; -import { ILanguageSupport } from 'vs/workbench/services/textfile/common/textfiles'; +import { ILanguageSupport, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; /* hot-reload:patch-prototype-methods */ export class MultiDiffEditorInput extends EditorInput implements ILanguageSupport { @@ -86,25 +87,19 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor @IInstantiationService private readonly _instantiationService: IInstantiationService, @IModelService private readonly _modelService: IModelService, @IMultiDiffSourceResolverService private readonly _multiDiffSourceResolverService: IMultiDiffSourceResolverService, + @ITextFileService private readonly _textFileService: ITextFileService, ) { super(); this._register(autorun((reader) => { /** @description Updates name */ - const resources = this._resources.read(reader)?.read(reader); + const resources = this._resources.read(reader) ?? []; const label = this.label ?? localize('name', "Multi Diff Editor"); this._name = label + localize('files', " ({0} files)", resources?.length ?? 0); this._onDidChangeLabel.fire(); })); } - private readonly _resolvedMultiDiffSource = observableValue(this, undefined); - private readonly _resources = derived(this, reader => { - const s = this._resolvedMultiDiffSource.read(reader); - if (!s) { return undefined; } - return observableFromEvent(s.onDidChange, () => s.resources); - }); - public serialize(): ISerializedMultiDiffEditorInput { return { label: this.label, @@ -116,7 +111,7 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor }; } - setLanguageId(languageId: string, source?: string | undefined): void { + public setLanguageId(languageId: string, source?: string | undefined): void { const activeDiffItem = this._viewModel.requireValue().activeDiffItem.get(); const value = activeDiffItem?.entry?.value; if (!value) { return; } @@ -125,16 +120,11 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor target.setLanguage(languageId, source); } - async getViewModel(): Promise { + public async getViewModel(): Promise { return this._viewModel.getPromise(); } private readonly _viewModel = new LazyStatefulPromise(async () => { - const source = this.initialResources - ? new ConstResolvedMultiDiffSource(this.initialResources) - : await this._multiDiffSourceResolverService.resolve(this.multiDiffSource); - this._resolvedMultiDiffSource.set(source, undefined); - const model = await this._createModel(); this._register(model); const vm = new MultiDiffEditorViewModel(model, this._instantiationService); @@ -144,13 +134,13 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor }); private async _createModel(): Promise { - const resources = this._resources.get()!; + const source = await this._resolvedSource.getValue(); const textResourceConfigurationService = this._textResourceConfigurationService; // Enables delayed disposing const garbage = new DisposableStore(); - const documentsWithPromises = mapObservableArrayCached(resources, 'documentsWithPromises', async (r, store) => { + const documentsWithPromises = mapObservableArrayCached(source.resources, 'documentsWithPromises', async (r, store) => { let originalTextModel: ITextModel; let modifiedTextModel: ITextModel; let modifiedRef: IReference | undefined; @@ -218,10 +208,20 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor }, onDidChange: documentChangeEmitter.event, get documents() { return documents; }, - contextKeys: this._resolvedMultiDiffSource.get()!.contextKeys, + contextKeys: source.source?.contextKeys, }; } + private readonly _resolvedSource = new ObservableLazyStatefulPromise(async () => { + const source: IResolvedMultiDiffSource | undefined = this.initialResources + ? new ConstResolvedMultiDiffSource(this.initialResources) + : await this._multiDiffSourceResolverService.resolve(this.multiDiffSource); + return { + source, + resources: source ? observableFromEvent(source.onDidChange, () => source.resources) : constObservable([]), + }; + }); + override matches(otherInput: EditorInput | IUntypedEditorInput): boolean { if (super.matches(otherInput)) { return true; @@ -233,6 +233,40 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor return false; } + + private readonly _resources = derived(this, reader => this._resolvedSource.cachedValue.read(reader)?.value?.resources.read(reader)); + private readonly _isDirtyObservables = mapObservableArrayCached(this._resources.map(r => r || []), this, res => { + const isModifiedDirty = res.modified ? isUriDirty(this._textFileService, res.modified) : constObservable(false); + const isOriginalDirty = res.original ? isUriDirty(this._textFileService, res.original) : constObservable(false); + return derived(reader => /** @description modifiedDirty||originalDirty */ isModifiedDirty.read(reader) || isOriginalDirty.read(reader)); + }, i => JSON.stringify([i.modified?.toString(), i.original?.toString()])); + private readonly _isDirtyObservable = derived(this, reader => this._isDirtyObservables.read(reader).some(isDirty => isDirty.read(reader))) + .keepObserved(this._store); + + override readonly onDidChangeDirty = Event.fromObservableLight(this._isDirtyObservable); + override isDirty() { return this._isDirtyObservable.get(); } + + override async save(group: number, options?: ISaveOptions | undefined): Promise { + const items = this._viewModel.currentValue?.items.get(); + if (items) { + await Promise.all(items.map(async item => { + const model = item.diffEditorViewModel.model; + + await Promise.all([ + this._textFileService.save(model.original.uri, options), + this._textFileService.save(model.modified.uri, options), + ]); + })); + } + return undefined; + } +} + +function isUriDirty(textFileService: ITextFileService, uri: URI) { + return observableFromEvent( + Event.filter(textFileService.files.onDidChangeDirty, e => e.resource.toString() === uri.toString()), + () => textFileService.isDirty(uri) + ); } function getReadonlyConfiguration(isReadonly: boolean | IMarkdownString | undefined): { readOnly: boolean; readOnlyMessage: IMarkdownString | undefined } { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/utils.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/utils.ts new file mode 100644 index 00000000000..26383b333ea --- /dev/null +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/utils.ts @@ -0,0 +1,99 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IObservable, derived, observableValue } from 'vs/base/common/observable'; + +export class ObservableLazy { + private readonly _value = observableValue(this, undefined); + + /** + * The cached value. + * Does not force a computation of the value. + */ + public get cachedValue(): IObservable { return this._value; } + + constructor(private readonly _computeValue: () => T) { + } + + /** + * Returns the cached value. + * Computes the value if the value has not been cached yet. + */ + public getValue() { + let v = this._value.get(); + if (!v) { + v = this._computeValue(); + this._value.set(v, undefined); + } + return v; + } +} + +/** + * A promise whose state is observable. + */ +export class ObservablePromise { + private readonly _value = observableValue | undefined>(this, undefined); + + public readonly promise: Promise; + public readonly value: IObservable | undefined> = this._value; + + constructor(promise: Promise) { + this.promise = promise.then(value => { + this._value.set(new PromiseResult(value, undefined), undefined); + return value; + }, error => { + this._value.set(new PromiseResult(undefined, error), undefined); + throw error; + }); + } +} + +export class PromiseResult { + constructor( + /** + * The value of the resolved promise. + * Undefined if the promise rejected. + */ + public readonly value: T | undefined, + + /** + * The error in case of a rejected promise. + * Undefined if the promise resolved. + */ + public readonly error: unknown | undefined, + ) { + } + + /** + * Returns the value if the promise resolved, otherwise throws the error. + */ + public getValue(): T { + if (this.error) { + throw this.error; + } + return this.value!; + } +} + +/** + * A lazy promise whose state is observable. + */ +export class ObservableLazyStatefulPromise { + private readonly _lazyValue = new ObservableLazy(() => new ObservablePromise(this._computeValue())); + + /** + * Does not enforce evaluation of the promise compute function. + * Is undefined if the promise has not been computed yet. + */ + public readonly cachedValue = derived(this, reader => this._lazyValue.cachedValue.read(reader)?.value.read(reader)); + + constructor(private readonly _computeValue: () => Promise) { + } + + public getValue(): Promise { + return this._lazyValue.getValue().promise; + } +} From e0e2a8176572e351b19286c400b912bf0fa5e791 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 12 Jan 2024 16:12:19 +0100 Subject: [PATCH 0275/1897] Cleans up observable owners --- src/vs/base/common/observableInternal/base.ts | 27 +++++++++++-------- .../base/common/observableInternal/derived.ts | 16 +++++------ .../base/common/observableInternal/utils.ts | 13 ++++----- .../multiDiffEditorViewModel.ts | 2 +- .../browser/multiDiffEditorInput.ts | 5 ++-- 5 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/vs/base/common/observableInternal/base.ts b/src/vs/base/common/observableInternal/base.ts index 6b487100d86..45e6f0ef201 100644 --- a/src/vs/base/common/observableInternal/base.ts +++ b/src/vs/base/common/observableInternal/base.ts @@ -199,9 +199,9 @@ export abstract class ConvenientObservable implements IObservable(fn: (value: T, reader: IReader) => TNew): IObservable; - public map(owner: object, fn: (value: T, reader: IReader) => TNew): IObservable; - public map(fnOrOwner: object | ((value: T, reader: IReader) => TNew), fnOrUndefined?: (value: T, reader: IReader) => TNew): IObservable { - const owner = fnOrUndefined === undefined ? undefined : fnOrOwner as object; + public map(owner: Owner, fn: (value: T, reader: IReader) => TNew): IObservable; + public map(fnOrOwner: Owner | ((value: T, reader: IReader) => TNew), fnOrUndefined?: (value: T, reader: IReader) => TNew): IObservable { + const owner = fnOrUndefined === undefined ? undefined : fnOrOwner as Owner; const fn = fnOrUndefined === undefined ? fnOrOwner as (value: T, reader: IReader) => TNew : fnOrUndefined; return _derived( @@ -354,31 +354,36 @@ export class TransactionImpl implements ITransaction { } } +/** + * The owner object of an observable. + * Is only used for debugging purposes, such as computing a name for the observable by iterating over the fields of the owner. + */ +export type Owner = object | undefined; export type DebugNameFn = string | (() => string | undefined); const countPerName = new Map(); const cachedDebugName = new WeakMap(); -export function getDebugName(obj: object, debugNameFn: DebugNameFn | undefined, fn: Function | undefined, owner: object | undefined, self: object): string | undefined { - const cached = cachedDebugName.get(obj); +export function getDebugName(self: object, debugNameFn: DebugNameFn | undefined, fn: Function | undefined, owner: Owner): string | undefined { + const cached = cachedDebugName.get(self); if (cached) { return cached; } - const dbgName = computeDebugName(obj, debugNameFn, fn, owner, self); + const dbgName = computeDebugName(self, debugNameFn, fn, owner); if (dbgName) { let count = countPerName.get(dbgName) ?? 0; count++; countPerName.set(dbgName, count); const result = count === 1 ? dbgName : `${dbgName}#${count}`; - cachedDebugName.set(obj, result); + cachedDebugName.set(self, result); return result; } return undefined; } -function computeDebugName(obj: object, debugNameFn: DebugNameFn | undefined, fn: Function | undefined, owner: object | undefined, self: object): string | undefined { - const cached = cachedDebugName.get(obj); +function computeDebugName(self: object, debugNameFn: DebugNameFn | undefined, fn: Function | undefined, owner: Owner): string | undefined { + const cached = cachedDebugName.get(self); if (cached) { return cached; } @@ -476,11 +481,11 @@ export class ObservableValue protected _value: T; get debugName() { - return getDebugName(this, this._debugName, undefined, this._owner, this) ?? 'ObservableValue'; + return getDebugName(this, this._debugName, undefined, this._owner) ?? 'ObservableValue'; } constructor( - private readonly _owner: object | undefined, + private readonly _owner: Owner, private readonly _debugName: string | undefined, initialValue: T ) { diff --git a/src/vs/base/common/observableInternal/derived.ts b/src/vs/base/common/observableInternal/derived.ts index 653c6f39c2d..83abf2444d3 100644 --- a/src/vs/base/common/observableInternal/derived.ts +++ b/src/vs/base/common/observableInternal/derived.ts @@ -5,7 +5,7 @@ import { BugIndicatingError } from 'vs/base/common/errors'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IReader, IObservable, BaseObservable, IObserver, _setDerivedOpts, IChangeContext, getFunctionName, DebugNameFn, getDebugName } from 'vs/base/common/observableInternal/base'; +import { IReader, IObservable, BaseObservable, IObserver, _setDerivedOpts, IChangeContext, getFunctionName, DebugNameFn, getDebugName, Owner } from 'vs/base/common/observableInternal/base'; import { getLogger } from 'vs/base/common/observableInternal/logging'; export type EqualityComparer = (a: T, b: T) => boolean; @@ -29,7 +29,7 @@ export function derived(computeFnOrOwner: ((reader: IReader) => T) | object, export function derivedOpts( options: { owner?: object; - debugName?: string | (() => string | undefined); + debugName?: DebugNameFn; equalityComparer?: EqualityComparer; onLastObserverRemoved?: (() => void); }, @@ -68,7 +68,7 @@ export function derivedWithStore(computeFn: (reader: IReader, store: Disposab export function derivedWithStore(owner: object, computeFn: (reader: IReader, store: DisposableStore) => T): IObservable; export function derivedWithStore(computeFnOrOwner: ((reader: IReader, store: DisposableStore) => T) | object, computeFnOrUndefined?: ((reader: IReader, store: DisposableStore) => T)): IObservable { let computeFn: (reader: IReader, store: DisposableStore) => T; - let owner: object | undefined; + let owner: Owner; if (computeFnOrUndefined === undefined) { computeFn = computeFnOrOwner as any; owner = undefined; @@ -92,10 +92,10 @@ export function derivedWithStore(computeFnOrOwner: ((reader: IReader, store: } export function derivedDisposable(computeFn: (reader: IReader) => T): IObservable; -export function derivedDisposable(owner: object, computeFn: (reader: IReader) => T): IObservable; -export function derivedDisposable(computeFnOrOwner: ((reader: IReader) => T) | object, computeFnOrUndefined?: ((reader: IReader) => T)): IObservable { +export function derivedDisposable(owner: Owner, computeFn: (reader: IReader) => T): IObservable; +export function derivedDisposable(computeFnOrOwner: ((reader: IReader) => T) | Owner, computeFnOrUndefined?: ((reader: IReader) => T)): IObservable { let computeFn: (reader: IReader) => T; - let owner: object | undefined; + let owner: Owner; if (computeFnOrUndefined === undefined) { computeFn = computeFnOrOwner as any; owner = undefined; @@ -155,11 +155,11 @@ export class Derived extends BaseObservable im private changeSummary: TChangeSummary | undefined = undefined; public override get debugName(): string { - return getDebugName(this, this._debugName, this._computeFn, this._owner, this) ?? '(anonymous)'; + return getDebugName(this, this._debugName, this._computeFn, this._owner) ?? '(anonymous)'; } constructor( - private readonly _owner: object | undefined, + private readonly _owner: Owner, private readonly _debugName: DebugNameFn | undefined, public readonly _computeFn: (reader: IReader, changeSummary: TChangeSummary) => T, private readonly createChangeSummary: (() => TChangeSummary) | undefined, diff --git a/src/vs/base/common/observableInternal/utils.ts b/src/vs/base/common/observableInternal/utils.ts index 018045f1282..830785aab40 100644 --- a/src/vs/base/common/observableInternal/utils.ts +++ b/src/vs/base/common/observableInternal/utils.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { autorun } from 'vs/base/common/observableInternal/autorun'; -import { BaseObservable, ConvenientObservable, IObservable, IObserver, IReader, ITransaction, _setKeepObserved, _setRecomputeInitiallyAndOnChange, getDebugName, getFunctionName, observableValue, subtransaction, transaction } from 'vs/base/common/observableInternal/base'; +import { BaseObservable, ConvenientObservable, IObservable, IObserver, IReader, ITransaction, Owner, _setKeepObserved, _setRecomputeInitiallyAndOnChange, getDebugName, getFunctionName, observableValue, subtransaction, transaction } from 'vs/base/common/observableInternal/base'; import { derived, derivedOpts } from 'vs/base/common/observableInternal/derived'; import { getLogger } from 'vs/base/common/observableInternal/logging'; @@ -249,7 +249,7 @@ export interface IObservableSignal extends IObservable { class ObservableSignal extends BaseObservable implements IObservableSignal { public get debugName() { - return getDebugName(this, this._debugName, undefined, this._owner, this) ?? 'Observable Signal'; + return getDebugName(this, this._debugName, undefined, this._owner) ?? 'Observable Signal'; } constructor( @@ -412,11 +412,11 @@ export function derivedObservableWithWritableCache(owner: object, computeFn: /** * When the items array changes, referential equal items are not mapped again. */ -export function mapObservableArrayCached(items: IObservable, ownerOrName: object | string, map: (input: TIn, store: DisposableStore) => TOut, keySelector?: (input: TIn) => TKey): IObservable { +export function mapObservableArrayCached(owner: Owner, items: IObservable, map: (input: TIn, store: DisposableStore) => TOut, keySelector?: (input: TIn) => TKey): IObservable { let m = new ArrayMap(map, keySelector); - return derivedOpts({ - debugName: typeof ownerOrName === 'string' ? ownerOrName : undefined, - owner: typeof ownerOrName === 'string' ? undefined : ownerOrName, + const self = derivedOpts({ + debugName: () => getDebugName(m, undefined, map, owner), + owner, onLastObserverRemoved: () => { m.dispose(); m = new ArrayMap(map); @@ -425,6 +425,7 @@ export function mapObservableArrayCached(items: IObservab m.setItems(items.read(reader)); return m.getItems(); }); + return self; } class ArrayMap implements IDisposable { diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts index c1382def822..45b94c052e3 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts @@ -17,7 +17,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti export class MultiDiffEditorViewModel extends Disposable { private readonly _documents = observableFromEvent(this.model.onDidChange, /** @description MultiDiffEditorViewModel.documents */() => this.model.documents); - public readonly items = mapObservableArrayCached(this._documents, this, (d, store) => store.add(new DocumentDiffItemViewModel(d, this._instantiationService))) + public readonly items = mapObservableArrayCached(this, this._documents, (d, store) => store.add(new DocumentDiffItemViewModel(d, this._instantiationService))) .recomputeInitiallyAndOnChange(this._store); public readonly activeDiffItem = observableValue(this, undefined); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 0799ccfc045..0fa31d32b33 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -140,7 +140,8 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor // Enables delayed disposing const garbage = new DisposableStore(); - const documentsWithPromises = mapObservableArrayCached(source.resources, 'documentsWithPromises', async (r, store) => { + const documentsWithPromises = mapObservableArrayCached(undefined, source.resources, async (r, store) => { + /** @description documentsWithPromises */ let originalTextModel: ITextModel; let modifiedTextModel: ITextModel; let modifiedRef: IReference | undefined; @@ -235,7 +236,7 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor } private readonly _resources = derived(this, reader => this._resolvedSource.cachedValue.read(reader)?.value?.resources.read(reader)); - private readonly _isDirtyObservables = mapObservableArrayCached(this._resources.map(r => r || []), this, res => { + private readonly _isDirtyObservables = mapObservableArrayCached(this, this._resources.map(r => r || []), res => { const isModifiedDirty = res.modified ? isUriDirty(this._textFileService, res.modified) : constObservable(false); const isOriginalDirty = res.original ? isUriDirty(this._textFileService, res.original) : constObservable(false); return derived(reader => /** @description modifiedDirty||originalDirty */ isModifiedDirty.read(reader) || isOriginalDirty.read(reader)); From 02580fd00c94f8c72fd1fd00f5b39b21957c1217 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 12 Jan 2024 16:29:12 +0100 Subject: [PATCH 0276/1897] Make sure AI generate code gets accepted/discarded before saving (#202347) * add inline chat save logic that ensures AI generated changes are only saved after consent * live - when cancelling don't undo until start version but revert changes * add setting to control save participation * fix typo * stub new services, fixes tests --- .../browser/inlineChat.contribution.ts | 4 +- .../browser/inlineChatController.ts | 102 +++++------ .../inlineChat/browser/inlineChatSaving.ts | 173 ++++++++++++++++++ .../browser/inlineChatStrategies.ts | 49 +++-- .../contrib/inlineChat/common/inlineChat.ts | 6 + .../test/browser/inlineChatController.test.ts | 8 +- 6 files changed, 266 insertions(+), 76 deletions(-) create mode 100644 src/vs/workbench/contrib/inlineChat/browser/inlineChatSaving.ts diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index 973c1072b1a..bf3ff77ac18 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -16,9 +16,11 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { InlineChatNotebookContribution } from 'vs/workbench/contrib/inlineChat/browser/inlineChatNotebook'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { InlineChatAccessibleViewContribution } from './inlineChatAccessibleView'; +import { IInlineChatSavingService, InlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSaving'; registerSingleton(IInlineChatService, InlineChatServiceImpl, InstantiationType.Delayed); registerSingleton(IInlineChatSessionService, InlineChatSessionService, InstantiationType.Delayed); +registerSingleton(IInlineChatSavingService, InlineChatSavingService, InstantiationType.Delayed); registerEditorContribution(INLINE_CHAT_ID, InlineChatController, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors registerEditorContribution(INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID, InlineChatActions.InlineAccessibilityHelpContribution, EditorContributionInstantiation.Eventually); @@ -26,7 +28,7 @@ registerEditorContribution(INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID, InlineChatA registerAction2(InlineChatActions.StartSessionAction); registerAction2(InlineChatActions.CloseAction); registerAction2(InlineChatActions.ConfigureInlineChatAction); -registerAction2(InlineChatActions.UnstashSessionAction); +// registerAction2(InlineChatActions.UnstashSessionAction); registerAction2(InlineChatActions.MakeRequestAction); registerAction2(InlineChatActions.StopRequestAction); registerAction2(InlineChatActions.ReRunRequestAction); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 158e709bc01..a6f3a10de37 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -12,7 +12,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Lazy } from 'vs/base/common/lazy'; -import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { MovingAverage } from 'vs/base/common/numbers'; import { StopWatch } from 'vs/base/common/stopwatch'; import { assertType } from 'vs/base/common/types'; @@ -39,10 +39,11 @@ import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/cont import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSaving'; import { EmptyResponse, ErrorResponse, ExpansionState, IInlineChatSessionService, ReplyResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { EditModeStrategy, LivePreviewStrategy, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; import { IInlineChatMessageAppender, InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; -import { CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_HAS_STASHED_SESSION, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatProgressItem, IInlineChatRequest, IInlineChatResponse, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatProgressItem, IInlineChatRequest, IInlineChatResponse, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; export const enum State { CREATE_SESSION = 'CREATE_SESSION', @@ -121,7 +122,6 @@ export class InlineChatController implements IEditorContribution { readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store); private readonly _sessionStore: DisposableStore = this._store.add(new DisposableStore()); - private readonly _stashedSession: MutableDisposable = this._store.add(new MutableDisposable()); private readonly _pausedStrategies = new Map(); private _session?: Session; private _strategy?: EditModeStrategy; @@ -131,6 +131,7 @@ export class InlineChatController implements IEditorContribution { private readonly _editor: ICodeEditor, @IInstantiationService private readonly _instaService: IInstantiationService, @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, + @IInlineChatSavingService private readonly _inlineChatSavingService: IInlineChatSavingService, @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, @ILogService private readonly _logService: ILogService, @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -185,11 +186,10 @@ export class InlineChatController implements IEditorContribution { } dispose(): void { - this._strategy?.dispose(); - this._stashedSession.clear(); if (this._session) { this._inlineChatSessionService.releaseSession(this._session); } + this._strategy?.dispose(); this._store.dispose(); this._log('controller disposed'); } @@ -232,7 +232,6 @@ export class InlineChatController implements IEditorContribution { if (this._currentRun) { await this._currentRun; } - this._stashedSession.clear(); if (options.initialSelection) { this._editor.setSelection(options.initialSelection); } @@ -845,14 +844,8 @@ export class InlineChatController implements IEditorContribution { } this._resetWidget(); + this._inlineChatSessionService.releaseSession(this._session); - this._stashedSession.clear(); - if (!this._session.isUnstashed && this._session.lastExchange) { - // only stash sessions that had edits - this._stashedSession.value = this._instaService.createInstance(StashedSession, this._editor, this._session); - } else { - this._inlineChatSessionService.releaseSession(this._session); - } this._strategy?.dispose(); this._strategy = undefined; @@ -930,6 +923,7 @@ export class InlineChatController implements IEditorContribution { try { this._ignoreModelContentChanged = true; + this._inlineChatSavingService.markChanged(this._session); this._session.wholeRange.trackEdits(editOperations); if (opts) { await this._strategy.makeProgressiveChanges(editOperations, opts); @@ -1122,7 +1116,7 @@ export class InlineChatController implements IEditorContribution { } unstashLastSession(): Session | undefined { - return this._stashedSession.value?.unstash(); + return undefined; } joinCurrentRun(): Promise | undefined { @@ -1130,52 +1124,52 @@ export class InlineChatController implements IEditorContribution { } } -class StashedSession { +// class StashedSession { - private readonly _listener: IDisposable; - private readonly _ctxHasStashedSession: IContextKey; - private _session: Session | undefined; +// private readonly _listener: IDisposable; +// private readonly _ctxHasStashedSession: IContextKey; +// private _session: Session | undefined; - constructor( - editor: ICodeEditor, - session: Session, - @IContextKeyService contextKeyService: IContextKeyService, - @IInlineChatSessionService private readonly _sessionService: IInlineChatSessionService, - @ILogService private readonly _logService: ILogService, - ) { - this._ctxHasStashedSession = CTX_INLINE_CHAT_HAS_STASHED_SESSION.bindTo(contextKeyService); +// constructor( +// editor: ICodeEditor, +// session: Session, +// @IContextKeyService contextKeyService: IContextKeyService, +// @IInlineChatSessionService private readonly _sessionService: IInlineChatSessionService, +// @ILogService private readonly _logService: ILogService, +// ) { +// this._ctxHasStashedSession = CTX_INLINE_CHAT_HAS_STASHED_SESSION.bindTo(contextKeyService); - // keep session for a little bit, only release when user continues to work (type, move cursor, etc.) - this._session = session; - this._ctxHasStashedSession.set(true); - this._listener = Event.once(Event.any(editor.onDidChangeCursorSelection, editor.onDidChangeModelContent, editor.onDidChangeModel))(() => { - this._session = undefined; - this._sessionService.releaseSession(session); - this._ctxHasStashedSession.reset(); - }); - } +// // keep session for a little bit, only release when user continues to work (type, move cursor, etc.) +// this._session = session; +// this._ctxHasStashedSession.set(true); +// this._listener = Event.once(Event.any(editor.onDidChangeCursorSelection, editor.onDidChangeModelContent, editor.onDidChangeModel))(() => { +// this._session = undefined; +// this._sessionService.releaseSession(session); +// this._ctxHasStashedSession.reset(); +// }); +// } - dispose() { - this._listener.dispose(); - this._ctxHasStashedSession.reset(); - if (this._session) { - this._sessionService.releaseSession(this._session); - } - } +// dispose() { +// this._listener.dispose(); +// this._ctxHasStashedSession.reset(); +// if (this._session) { +// this._sessionService.releaseSession(this._session); +// } +// } - unstash(): Session | undefined { - if (!this._session) { - return undefined; - } - this._listener.dispose(); - const result = this._session; - result.markUnstashed(); - this._session = undefined; - this._logService.debug('[IE] Unstashed session'); - return result; - } +// unstash(): Session | undefined { +// if (!this._session) { +// return undefined; +// } +// this._listener.dispose(); +// const result = this._session; +// result.markUnstashed(); +// this._session = undefined; +// this._logService.debug('[IE] Unstashed session'); +// return result; +// } -} +// } async function showMessageResponse(accessor: ServicesAccessor, query: string, response: string) { const chatService = accessor.get(IChatService); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSaving.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSaving.ts new file mode 100644 index 00000000000..c6f7dba516b --- /dev/null +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSaving.ts @@ -0,0 +1,173 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { raceCancellation } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { DisposableStore, MutableDisposable, dispose } from 'vs/base/common/lifecycle'; +import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ITextModel } from 'vs/editor/common/model'; +import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; +import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/editor'; +import { IInlineChatSessionService, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { InlineChatConfigKeys } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { GroupsOrder, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; + +export const IInlineChatSavingService = createDecorator('IInlineChatSavingService '); + +export interface IInlineChatSavingService { + _serviceBrand: undefined; + + markChanged(session: Session): void; + +} + +interface SessionData { + readonly dispose: () => void; + readonly session: Session; + readonly group: IEditorGroup; +} + +export class InlineChatSavingService implements IInlineChatSavingService { + + declare readonly _serviceBrand: undefined; + + private readonly _store = new DisposableStore(); + private readonly _saveParticipant = this._store.add(new MutableDisposable()); + private readonly _sessionData = new Map(); + + constructor( + @IFilesConfigurationService private readonly _fileConfigService: IFilesConfigurationService, + @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, + @ITextFileService private readonly _textFileService: ITextFileService, + @IEditorService private readonly _editorService: IEditorService, + @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, + @IConfigurationService private readonly _configService: IConfigurationService, + ) { + this._store.add(_inlineChatSessionService.onDidEndSession(e => { + this._sessionData.get(e.session)?.dispose(); + })); + } + + dispose(): void { + this._store.dispose(); + dispose(this._sessionData.values()); + } + + markChanged(session: Session): void { + if (!this._sessionData.has(session)) { + + if (this._sessionData.size === 0) { + this._installSaveParticpant(); + } + + const disposable = this._fileConfigService.disableAutoSave(session.textModelN.uri); + const group = this._getEditorGroup(session); + this._sessionData.set(session, { + session, + group, + dispose: () => { + disposable.dispose(); + this._sessionData.delete(session); + if (this._sessionData.size === 0) { + this._saveParticipant.clear(); + } + } + }); + } + } + + private _installSaveParticpant(): void { + this._saveParticipant.value = this._textFileService.files.addSaveParticipant({ + participate: (model, context, progress, token) => this._participate(model.textEditorModel, context.reason, progress, token) + }); + } + + private async _participate(model: ITextModel | null, reason: SaveReason, progress: IProgress, token: CancellationToken): Promise { + + if (reason !== SaveReason.EXPLICIT) { + // all saves that we are concerned about are explicit + // because we have disabled auto-save for them + return; + } + + if (!this._configService.getValue(InlineChatConfigKeys.AcceptedOrDiscardBeforeSave)) { + // disabled + return; + } + + const sessions = new Map(); + for (const [session, data] of this._sessionData) { + if (model === session.textModelN) { + sessions.set(session, data); + } + } + + if (sessions.size === 0) { + return; + } + + const store = new DisposableStore(); + + const allDone = new Promise(resolve => { + store.add(this._inlineChatSessionService.onDidEndSession(e => { + + const data = sessions.get(e.session); + if (!data) { + return; + } + + data.dispose(); + sessions.delete(e.session); + + if (sessions.size === 0) { + resolve(); // DONE, release save block! + } + })); + }); + + progress.report({ + message: sessions.size === 1 + ? localize('inlineChat', "Waiting for Inline Chat changes to be Accepted or Discarded...") + : localize('inlineChat.N', "Waiting for Inline Chat changes in {0} editors to be Accepted or Discarded...", sessions.size) + }); + + await this._revealInlineChatSessions(sessions.values()); + + try { + await raceCancellation(allDone, token); + } finally { + store.dispose(); + } + } + + private _getEditorGroup(session: Session): IEditorGroup { + const candidate = this._editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(group => { + return getCodeEditor(group.activeEditorPane?.getControl()) === session.editor; + }); + return candidate ?? this._editorGroupService.activeGroup; + } + + private async _revealInlineChatSessions(sessions: Iterable): Promise { + + for (const data of sessions) { + + const inputs = data.group + .findEditors(data.session.textModelN.uri) + .filter(input => input.editorId === DEFAULT_EDITOR_ASSOCIATION.id); + + if (inputs.length === 0) { + await this._editorService.openEditor({ resource: data.session.textModelN.uri }, data.group); + } else { + await data.group.openEditor(inputs[0]); + } + } + } +} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index b8c107d602f..7fe41b4a244 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -479,6 +479,8 @@ type HunkTrackedRange = { * The first element [0] is the whole modified range and subsequent elements are word-level changes */ getRanges(): Range[]; + + discardChanges(): void; }; const enum HunkState { @@ -503,6 +505,7 @@ type HunkDisplayData = { interface HunkDisplay { renderHunks(): HunkDisplayData | undefined; hideHunks(): void; + discardHunks(): void; } export class LiveStrategy extends EditModeStrategy { @@ -593,13 +596,8 @@ export class LiveStrategy extends EditModeStrategy { } async cancel() { + this._hunkDisplay?.discardHunks(); this._resetDiff(); - const { textModelN: modelN, textModelNAltVersion, textModelNSnapshotAltVersion } = this._session; - if (modelN.isDisposed()) { - return; - } - const targetAltVersion = textModelNSnapshotAltVersion ?? textModelNAltVersion; - await undoModelUntil(modelN, targetAltVersion); } override async undoChanges(altVersionId: number): Promise { @@ -686,7 +684,7 @@ export class LiveStrategy extends EditModeStrategy { // (INIT) compute hunks const hunks = await this._computeHunks(); if (hunks.length === 0) { - this._hunkDisplay = { renderHunks() { return undefined; }, hideHunks() { } }; + this._hunkDisplay = { renderHunks() { return undefined; }, hideHunks() { }, discardHunks() { } }; return undefined; } @@ -700,13 +698,26 @@ export class LiveStrategy extends EditModeStrategy { for (const change of hunk.changes) { decorationIds.push(accessor.addDecoration(change.modifiedRange, this._decoTrackedRange)); } - hunkTrackedRanges.set(hunk, { + const hunkLiveInfo: HunkTrackedRange = { getRanges: () => { const ranges = decorationIds.map(id => model.getDecorationRange(id)); coalesceInPlace(ranges); return ranges; + }, + discardChanges: () => { + const edits: ISingleEditOperation[] = []; + const ranges = hunkLiveInfo.getRanges(); + for (let i = 1; i < ranges.length; i++) { + // DISCARD: replace modified range with original value. The modified range is retrieved from a decoration + // which was created above so that typing in the editor keeps discard working. + const modifiedRange = ranges[i]; + const originalValue = this._session.textModel0.getValueInRange(hunk.changes[i - 1].originalRange); + edits.push(EditOperation.replace(modifiedRange, originalValue)); + } + this._session.textModelN.pushEditOperations(null, edits, () => null); } - }); + }; + hunkTrackedRanges.set(hunk, hunkLiveInfo); this._renderStore.add(toDisposable(() => { model.deltaDecorations(decorationIds, []); })); @@ -742,16 +753,8 @@ export class LiveStrategy extends EditModeStrategy { }; const discardHunk = () => { - const edits: ISingleEditOperation[] = []; - const hunkRanges = hunkTrackedRanges.get(hunk)!.getRanges(); - for (let i = 1; i < hunkRanges.length; i++) { - // DISCARD: replace modified range with original value. The modified range is retrieved from a decoration - // which was created above so that typing in the editor keeps discard working. - const modifiedRange = hunkRanges[i]; - const originalValue = this._session.textModel0.getValueInRange(hunk.changes[i - 1].originalRange); - edits.push(EditOperation.replace(modifiedRange, originalValue)); - } - this._session.textModelN.pushEditOperations(null, edits, () => null); + const info = hunkTrackedRanges.get(hunk)!; + info.discardChanges(); hunkDisplayData.get(hunk)!.acceptedOrRejected = HunkState.Rejected; renderHunks(); }; @@ -892,7 +895,13 @@ export class LiveStrategy extends EditModeStrategy { hunkDisplayData.clear(); }; - this._hunkDisplay = { renderHunks, hideHunks }; + const discardHunks = () => { + for (const data of hunkTrackedRanges.values()) { + data.discardChanges(); + } + }; + + this._hunkDisplay = { renderHunks, hideHunks, discardHunks }; } return this._hunkDisplay?.renderHunks()?.position; diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index edf44ad1194..cef2b7500ca 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -201,6 +201,7 @@ Registry.as(ExtensionsMigration.ConfigurationMi export const enum InlineChatConfigKeys { Mode = 'inlineChat.mode', FinishOnType = 'inlineChat.finishOnType', + AcceptedOrDiscardBeforeSave = 'inlineChat.acceptedOrDiscardBeforeSave', } Registry.as(Extensions.Configuration).registerConfiguration({ @@ -222,5 +223,10 @@ Registry.as(Extensions.Configuration).registerConfigurat default: false, type: 'boolean' }, + [InlineChatConfigKeys.AcceptedOrDiscardBeforeSave]: { + description: localize('acceptedOrDiscardBeforeSave', "Whether pending inline chat sessions prevent saving."), + default: true, + type: 'boolean' + } } }); diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 356055c7420..4219dd38c12 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -32,7 +32,8 @@ import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/brows import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { InlineChatController, InlineChatRunOptions, State } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { IInlineChatSessionService, InlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSaving'; +import { IInlineChatSessionService, InlineChatSessionService, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { EditMode, IInlineChatService, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; @@ -106,6 +107,11 @@ suite('InteractiveChatController', function () { [IInlineChatService, inlineChatService], [IDiffProviderFactoryService, new SyncDescriptor(TestDiffProviderFactoryService)], [IInlineChatSessionService, new SyncDescriptor(InlineChatSessionService)], + [IInlineChatSavingService, new class extends mock() { + override markChanged(session: Session): void { + // noop + } + }], [IEditorProgressService, new class extends mock() { override show(total: unknown, delay?: unknown): IProgressRunner { return { From c096f73022a1c42705d7d7aa29262be0cd05acab Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 12 Jan 2024 16:31:30 +0100 Subject: [PATCH 0277/1897] zoom - show rich controls in status entry (#202352) --- .../browser/ui/iconLabel/iconLabelHover.ts | 5 +- .../browser/parts/statusbar/statusbarItem.ts | 9 +- .../electron-sandbox/media/window.css | 20 ++++ src/vs/workbench/electron-sandbox/window.ts | 94 +++++++++++++------ 4 files changed, 95 insertions(+), 33 deletions(-) create mode 100644 src/vs/workbench/electron-sandbox/media/window.css diff --git a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts index 23878f78ea8..9baf56005ee 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts @@ -47,6 +47,7 @@ interface IHoverAction { export interface IUpdatableHoverOptions { actions?: IHoverAction[]; linkHandler?(url: string): void; + disableHideOnMouseDown?: boolean; } export interface ICustomHover extends IDisposable { @@ -194,7 +195,9 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM let isMouseDown = false; const mouseDownEmitter = dom.addDisposableListener(htmlElement, dom.EventType.MOUSE_DOWN, () => { isMouseDown = true; - hideHover(true, true); + if (!options?.disableHideOnMouseDown) { + hideHover(true, true); + } }, true); const mouseUpEmitter = dom.addDisposableListener(htmlElement, dom.EventType.MOUSE_UP, () => { isMouseDown = false; diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts index 88792e4d1c0..4b771c3f7e1 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts @@ -117,10 +117,11 @@ export class StatusbarEntryItem extends Disposable { // Update: Hover if (!this.entry || !this.isEqualTooltip(this.entry, entry)) { const hoverContents = isMarkdownString(entry.tooltip) ? { markdown: entry.tooltip, markdownNotSupportedFallback: undefined } : entry.tooltip; + const entryOpensTooltip = entry.command === ShowTooltipCommand; if (this.hover) { - this.hover.update(hoverContents); + this.hover.update(hoverContents, { disableHideOnMouseDown: entryOpensTooltip }); } else { - this.hover = this._register(setupCustomHover(this.hoverDelegate, this.container, hoverContents)); + this.hover = this._register(setupCustomHover(this.hoverDelegate, this.container, hoverContents, { disableHideOnMouseDown: entryOpensTooltip })); } this.focusListener.value = addDisposableListener(this.labelContainer, EventType.FOCUS, (e) => { EventHelper.stop(e); @@ -128,7 +129,9 @@ export class StatusbarEntryItem extends Disposable { }); this.focusOutListener.value = addDisposableListener(this.labelContainer, EventType.FOCUS_OUT, (e) => { EventHelper.stop(e); - this.hover?.hide(); + if (!entryOpensTooltip) { + this.hover?.hide(); + } }); } diff --git a/src/vs/workbench/electron-sandbox/media/window.css b/src/vs/workbench/electron-sandbox/media/window.css new file mode 100644 index 00000000000..fbfcd3c97bf --- /dev/null +++ b/src/vs/workbench/electron-sandbox/media/window.css @@ -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. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .zoom-status { + display: flex; + padding: 2px 8px; + vertical-align: middle; +} + +.monaco-workbench .zoom-status > .left { + margin-top: auto; + margin-bottom: auto; + margin-right: 20px; +} + +.monaco-workbench .zoom-status > .right { + margin: auto 0; +} diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index c0aba33c59e..df5b21dae6d 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/window'; import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { onUnexpectedError } from 'vs/base/common/errors'; import { equals } from 'vs/base/common/objects'; import { EventType, EventHelper, addDisposableListener, ModifierKeyEmitter, getActiveElement, hasWindow, getWindow, getWindowById, getWindowId, getWindows } from 'vs/base/browser/dom'; -import { Separator, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; +import { Action, Separator, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; import { EditorResourceAccessor, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors, IResourceDiffEditorInput, IUntypedEditorInput, IEditorPane, isResourceEditorInput, IResourceMergeEditorInput } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -73,7 +74,9 @@ import { registerWindowDriver } from 'vs/workbench/services/driver/electron-sand import { mainWindow } from 'vs/base/browser/window'; import { BaseWindow } from 'vs/workbench/browser/window'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; +import { IStatusbarService, ShowTooltipCommand, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ThemeIcon } from 'vs/base/common/themables'; export class NativeWindow extends BaseWindow { @@ -339,7 +342,7 @@ export class NativeWindow extends BaseWindow { this._register(onDidChangeZoomLevel(targetWindowId => this.handleOnDidChangeZoomLevel(targetWindowId))); this._register(this.editorGroupService.onDidCreateAuxiliaryEditorPart(({ instantiationService, disposables, part }) => { - this.createResetWindowZoomStatusEntry(instantiationService, part.windowId, disposables); + this.createWindowZoomStatusEntry(instantiationService, part.windowId, disposables); })); // Listen to visible editor changes (debounced in case a new editor opens immediately after) @@ -657,7 +660,7 @@ export class NativeWindow extends BaseWindow { // Zoom status for (const { window, disposables } of getWindows()) { - this.createResetWindowZoomStatusEntry(this.instantiationService, window.vscodeWindowId, disposables); + this.createWindowZoomStatusEntry(this.instantiationService, window.vscodeWindowId, disposables); } // Smoke Test Driver @@ -1027,7 +1030,7 @@ export class NativeWindow extends BaseWindow { //#region Window Zoom - private readonly mapWindowIdToResetZoomStatusEntry = new Map(); + private readonly mapWindowIdToZoomStatusEntry = new Map(); private configuredWindowZoomLevel = this.resolveConfiguredWindowZoomLevel(); @@ -1040,7 +1043,7 @@ export class NativeWindow extends BaseWindow { private handleOnDidChangeZoomLevel(targetWindowId: number): void { // Zoom status entry - this.updateResetWindowZoomStatusEntry(targetWindowId); + this.updateWindowZoomStatusEntry(targetWindowId); // Notify main process about a custom zoom level if (targetWindowId === mainWindow.vscodeWindowId) { @@ -1055,27 +1058,27 @@ export class NativeWindow extends BaseWindow { } } - private createResetWindowZoomStatusEntry(instantiationService: IInstantiationService, targetWindowId: number, disposables: DisposableStore): void { - this.mapWindowIdToResetZoomStatusEntry.set(targetWindowId, disposables.add(instantiationService.createInstance(ResetZoomStatusEntry))); - disposables.add(toDisposable(() => this.mapWindowIdToResetZoomStatusEntry.delete(targetWindowId))); + private createWindowZoomStatusEntry(instantiationService: IInstantiationService, targetWindowId: number, disposables: DisposableStore): void { + this.mapWindowIdToZoomStatusEntry.set(targetWindowId, disposables.add(instantiationService.createInstance(ZoomStatusEntry))); + disposables.add(toDisposable(() => this.mapWindowIdToZoomStatusEntry.delete(targetWindowId))); - this.updateResetWindowZoomStatusEntry(targetWindowId); + this.updateWindowZoomStatusEntry(targetWindowId); } - private updateResetWindowZoomStatusEntry(targetWindowId: number): void { + private updateWindowZoomStatusEntry(targetWindowId: number): void { const targetWindow = getWindowById(targetWindowId); - const entry = this.mapWindowIdToResetZoomStatusEntry.get(targetWindowId); + const entry = this.mapWindowIdToZoomStatusEntry.get(targetWindowId); if (entry && targetWindow) { const currentZoomLevel = getZoomLevel(targetWindow.window); let text: string | undefined = undefined; if (currentZoomLevel < this.configuredWindowZoomLevel) { - text = localize('resetZoomOut', "$(zoom-out)"); + text = localize('zoomedOut', "$(zoom-out)"); } else if (currentZoomLevel > this.configuredWindowZoomLevel) { - text = localize('resetZoomIn', "$(zoom-in)"); + text = localize('zoomedIn', "$(zoom-in)"); } - entry.updateResetZoomEntry(text ?? false); + entry.updateZoomEntry(text ?? false, targetWindowId); } } @@ -1094,8 +1097,8 @@ export class NativeWindow extends BaseWindow { applyZoom(this.configuredWindowZoomLevel, ApplyZoomTarget.ALL_WINDOWS); } - for (const [windowId] of this.mapWindowIdToResetZoomStatusEntry) { - this.updateResetWindowZoomStatusEntry(windowId); + for (const [windowId] of this.mapWindowIdToZoomStatusEntry) { + this.updateWindowZoomStatusEntry(windowId); } } @@ -1104,35 +1107,68 @@ export class NativeWindow extends BaseWindow { override dispose(): void { super.dispose(); - for (const [, entry] of this.mapWindowIdToResetZoomStatusEntry) { + for (const [, entry] of this.mapWindowIdToZoomStatusEntry) { entry.dispose(); } } } -class ResetZoomStatusEntry extends Disposable { +class ZoomStatusEntry extends Disposable { - private readonly resetZoomStatusEntry = this._register(new MutableDisposable()); + private readonly zoomStatusEntry = this._register(new MutableDisposable()); - constructor(@IStatusbarService private readonly statusbarService: IStatusbarService) { + constructor( + @IStatusbarService private readonly statusbarService: IStatusbarService, + @ICommandService private readonly commandService: ICommandService + ) { super(); } - updateResetZoomEntry(visibleOrText: false | string): void { + updateZoomEntry(visibleOrText: false | string, targetWindowId: number): void { if (typeof visibleOrText === 'string') { - if (!this.resetZoomStatusEntry.value) { - const name = localize('status.resetWindowZoom', "Reset Window Zoom"); - this.resetZoomStatusEntry.value = this.statusbarService.addEntry({ + if (!this.zoomStatusEntry.value) { + const disposables = new DisposableStore(); + this.zoomStatusEntry.value = disposables; + + const element = document.createElement('div'); + element.classList.add('zoom-status'); + + const left = document.createElement('div'); + left.classList.add('left'); + element.appendChild(left); + + const zoomValue = document.createElement('span'); + zoomValue.textContent = `Zoom Level: ${getZoomLevel(getWindowById(targetWindowId)?.window ?? mainWindow)}`; + left.appendChild(zoomValue); + + const right = document.createElement('div'); + right.classList.add('right'); + element.appendChild(right); + + const actionBar = disposables.add(new ActionBar(right, {})); + + actionBar.push([ + disposables.add(new Action('zoomOut', localize('zoomOut', "Zoom Out"), ThemeIcon.asClassName(Codicon.zoomOut), true, () => this.commandService.executeCommand('workbench.action.zoomOut'))), + disposables.add(new Action('zoomIn', localize('zoomIn', "Zoom In"), ThemeIcon.asClassName(Codicon.zoomIn), true, () => this.commandService.executeCommand('workbench.action.zoomIn'))) + ], { icon: true, label: false }); + + actionBar.push( + disposables.add(new Action('zoomReset', localize('zoomReset', "Reset"), undefined, true, async () => this.commandService.executeCommand('workbench.action.zoomReset'))), + { icon: false, label: true } + ); + + const name = localize('status.windowZoom', "Window Zoom"); + disposables.add(this.statusbarService.addEntry({ name, text: visibleOrText, - tooltip: name, + tooltip: element, ariaLabel: name, - command: 'workbench.action.zoomReset', + command: ShowTooltipCommand, kind: 'prominent' - }, 'status.resetWindowZoom', StatusbarAlignment.RIGHT, 102); + }, 'status.windowZoom', StatusbarAlignment.RIGHT, 102)); } } else { - this.resetZoomStatusEntry.clear(); + this.zoomStatusEntry.clear(); } } } From 6950062688611b234d247be644ee69564fca827c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 12 Jan 2024 17:23:07 +0100 Subject: [PATCH 0278/1897] zoom - update level properly (#202355) --- src/vs/workbench/electron-sandbox/window.ts | 94 ++++++++++++--------- 1 file changed, 52 insertions(+), 42 deletions(-) diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index df5b21dae6d..68a0a37c7ef 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -1115,7 +1115,8 @@ export class NativeWindow extends BaseWindow { class ZoomStatusEntry extends Disposable { - private readonly zoomStatusEntry = this._register(new MutableDisposable()); + private readonly disposable = this._register(new MutableDisposable()); + private labelContainer: HTMLElement | undefined = undefined; constructor( @IStatusbarService private readonly statusbarService: IStatusbarService, @@ -1126,49 +1127,58 @@ class ZoomStatusEntry extends Disposable { updateZoomEntry(visibleOrText: false | string, targetWindowId: number): void { if (typeof visibleOrText === 'string') { - if (!this.zoomStatusEntry.value) { - const disposables = new DisposableStore(); - this.zoomStatusEntry.value = disposables; - - const element = document.createElement('div'); - element.classList.add('zoom-status'); - - const left = document.createElement('div'); - left.classList.add('left'); - element.appendChild(left); - - const zoomValue = document.createElement('span'); - zoomValue.textContent = `Zoom Level: ${getZoomLevel(getWindowById(targetWindowId)?.window ?? mainWindow)}`; - left.appendChild(zoomValue); - - const right = document.createElement('div'); - right.classList.add('right'); - element.appendChild(right); - - const actionBar = disposables.add(new ActionBar(right, {})); - - actionBar.push([ - disposables.add(new Action('zoomOut', localize('zoomOut', "Zoom Out"), ThemeIcon.asClassName(Codicon.zoomOut), true, () => this.commandService.executeCommand('workbench.action.zoomOut'))), - disposables.add(new Action('zoomIn', localize('zoomIn', "Zoom In"), ThemeIcon.asClassName(Codicon.zoomIn), true, () => this.commandService.executeCommand('workbench.action.zoomIn'))) - ], { icon: true, label: false }); - - actionBar.push( - disposables.add(new Action('zoomReset', localize('zoomReset', "Reset"), undefined, true, async () => this.commandService.executeCommand('workbench.action.zoomReset'))), - { icon: false, label: true } - ); - - const name = localize('status.windowZoom', "Window Zoom"); - disposables.add(this.statusbarService.addEntry({ - name, - text: visibleOrText, - tooltip: element, - ariaLabel: name, - command: ShowTooltipCommand, - kind: 'prominent' - }, 'status.windowZoom', StatusbarAlignment.RIGHT, 102)); + if (!this.disposable.value) { + this.createZoomEntry(targetWindowId, visibleOrText); + } else { + this.updateZoomEntryLabel(targetWindowId); } } else { - this.zoomStatusEntry.clear(); + this.disposable.clear(); + } + } + private createZoomEntry(targetWindowId: number, visibleOrText: string) { + const disposables = new DisposableStore(); + this.disposable.value = disposables; + + const element = document.createElement('div'); + element.classList.add('zoom-status'); + + this.labelContainer = document.createElement('div'); + element.appendChild(this.labelContainer); + disposables.add(toDisposable(() => this.labelContainer = undefined)); + this.labelContainer.classList.add('left'); + this.updateZoomEntryLabel(targetWindowId); + + const actionsContainer = document.createElement('div'); + actionsContainer.classList.add('right'); + element.appendChild(actionsContainer); + + const actionBar = disposables.add(new ActionBar(actionsContainer, {})); + + actionBar.push([ + disposables.add(new Action('zoomOut', localize('zoomOut', "Zoom Out"), ThemeIcon.asClassName(Codicon.zoomOut), true, () => this.commandService.executeCommand('workbench.action.zoomOut'))), + disposables.add(new Action('zoomIn', localize('zoomIn', "Zoom In"), ThemeIcon.asClassName(Codicon.zoomIn), true, () => this.commandService.executeCommand('workbench.action.zoomIn'))) + ], { icon: true, label: false }); + + actionBar.push( + disposables.add(new Action('zoomReset', localize('zoomReset', "Reset"), undefined, true, async () => this.commandService.executeCommand('workbench.action.zoomReset'))), + { icon: false, label: true } + ); + + const name = localize('status.windowZoom', "Window Zoom"); + disposables.add(this.statusbarService.addEntry({ + name, + text: visibleOrText, + tooltip: element, + ariaLabel: name, + command: ShowTooltipCommand, + kind: 'prominent' + }, 'status.windowZoom', StatusbarAlignment.RIGHT, 102)); + } + + private updateZoomEntryLabel(targetWindowId: number): void { + if (this.labelContainer) { + this.labelContainer.textContent = localize('zoomLevel', "Zoom Level: {0}", getZoomLevel(getWindowById(targetWindowId)?.window ?? mainWindow)); } } } From 85afd448bf37e818047c6cb51bb593b3d93e34b4 Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 12 Jan 2024 17:30:25 +0100 Subject: [PATCH 0279/1897] Improved Sticky Scroll Rendering (#202354) Sticky Scroll improved rendering --- src/vs/base/browser/ui/tree/abstractTree.ts | 85 +++++++++++++-------- 1 file changed, 54 insertions(+), 31 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 7a48963048b..8d8b317d2ae 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1180,26 +1180,27 @@ export interface StickyScrollNode { readonly position: number; } +function stickyScrollNodeStateEquals(node1: StickyScrollNode, node2: StickyScrollNode) { + return node1.position === node2.position && stickyScrollNodeEquals(node1, node2); +} + function stickyScrollNodeEquals(node1: StickyScrollNode, node2: StickyScrollNode) { - return node1.position === node2.position && - node1.node.element === node2.node.element && + return node1.node.element === node2.node.element && node1.startIndex === node2.startIndex && node1.height === node2.height && node1.endIndex === node2.endIndex; } -class StickyScrollState extends Disposable { +class StickyScrollState { constructor( readonly stickyNodes: StickyScrollNode[] = [] - ) { - super(); - } + ) { } get count(): number { return this.stickyNodes.length; } equal(state: StickyScrollState): boolean { - return equals(this.stickyNodes, state.stickyNodes, stickyScrollNodeEquals); + return equals(this.stickyNodes, state.stickyNodes, stickyScrollNodeStateEquals); } lastNodePartiallyVisible(): boolean { @@ -1216,8 +1217,19 @@ class StickyScrollState extends Disposable { return secondLastStickyNode.position + secondLastStickyNode.height !== lastStickyNode.position; } - addDisposable(disposable: IDisposable): void { - this._register(disposable); + animationStateChanged(previousState: StickyScrollState): boolean { + if (!equals(this.stickyNodes, previousState.stickyNodes, stickyScrollNodeEquals)) { + return false; + } + + if (this.count === 0) { + return false; + } + + const lastStickyNode = this.stickyNodes[this.count - 1]; + const previousLastStickyNode = previousState.stickyNodes[previousState.count - 1]; + + return lastStickyNode.position !== previousLastStickyNode.position; } } @@ -1541,6 +1553,8 @@ class StickyScrollWidget implements IDisposable { private readonly _rootDomNode: HTMLElement; private _previousState: StickyScrollState | undefined; + private _previousElements: HTMLElement[] = []; + private _previousStateDisposables: DisposableStore = new DisposableStore(); private stickyScrollFocus: StickyScrollFocus; readonly onDidChangeHasFocus: Event; @@ -1555,10 +1569,12 @@ class StickyScrollWidget implements IDisposable { private readonly accessibilityProvider: IListAccessibilityProvider | undefined, ) { - this._rootDomNode = document.createElement('div'); - this._rootDomNode.classList.add('monaco-tree-sticky-container'); + this._rootDomNode = $('.monaco-tree-sticky-container'); container.appendChild(this._rootDomNode); + const shadow = $('.monaco-tree-sticky-container-shadow'); + this._rootDomNode.appendChild(shadow); + this.stickyScrollFocus = new StickyScrollFocus(this._rootDomNode, view); this.onDidChangeHasFocus = this.stickyScrollFocus.onDidChangeHasFocus; this.onContextMenu = this.stickyScrollFocus.onContextMenu; @@ -1595,34 +1611,41 @@ class StickyScrollWidget implements IDisposable { this.setVisible(isVisible); } - // Remove previous state - this._previousState?.dispose(); - this._previousState = state; - if (!isVisible) { + this._previousState = undefined; + this._previousElements = []; + this._previousStateDisposables.clear(); return; } - const elements = Array(state.count); - for (let stickyIndex = state.count - 1; stickyIndex >= 0; stickyIndex--) { - const stickyNode = state.stickyNodes[stickyIndex]; + const lastStickyNode = state.stickyNodes[state.count - 1]; - const { element, disposable } = this.createElement(stickyNode, stickyIndex, state.count); - elements[stickyIndex] = element; + // If the new state is only a change in the last node's position, update the position of the last element + if (this._previousState && state.animationStateChanged(this._previousState)) { + this._previousElements[this._previousState.count - 1].style.top = `${lastStickyNode.position}px`; + } + // create new dom elements + else { + this._previousStateDisposables.clear(); - this._rootDomNode.appendChild(element); - state.addDisposable(disposable); + const elements = Array(state.count); + for (let stickyIndex = state.count - 1; stickyIndex >= 0; stickyIndex--) { + const stickyNode = state.stickyNodes[stickyIndex]; + + const { element, disposable } = this.createElement(stickyNode, stickyIndex, state.count); + elements[stickyIndex] = element; + + this._rootDomNode.appendChild(element); + this._previousStateDisposables.add(disposable); + } + + this.stickyScrollFocus.updateElements(elements, state); + + this._previousState = state; + this._previousElements = elements; } - this.stickyScrollFocus.updateElements(elements, state); - - // Add shadow element to the end of the widget - const shadow = $('.monaco-tree-sticky-container-shadow'); - this._rootDomNode.appendChild(shadow); - state.addDisposable(toDisposable(() => shadow.remove())); - // Set the height of the widget to the bottom of the last sticky node - const lastStickyNode = state.stickyNodes[state.count - 1]; this._rootDomNode.style.height = `${lastStickyNode.position + lastStickyNode.height}px`; } @@ -1723,7 +1746,7 @@ class StickyScrollWidget implements IDisposable { dispose(): void { this.stickyScrollFocus.dispose(); - this._previousState?.dispose(); + this._previousStateDisposables.dispose(); this._rootDomNode.remove(); } } From ab2a8a09d800bfddc92af72c643231a70a447bea Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 12 Jan 2024 17:36:32 +0100 Subject: [PATCH 0280/1897] aux window - fix double action registration in title bar part (#202358) cc @jrieken @sbatten --- .../browser/parts/titlebar/titlebarPart.ts | 55 +++++++++++-------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 816fdf355fd..4a00d9df336 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -19,7 +19,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; import { isMacintosh, isWindows, isLinux, isWeb, isNative, platformLocale } from 'vs/base/common/platform'; import { Color } from 'vs/base/common/color'; -import { EventType, EventHelper, Dimension, append, $, addDisposableListener, prepend, reset, getWindow, getWindowId, isAncestor } from 'vs/base/browser/dom'; +import { EventType, EventHelper, Dimension, append, $, addDisposableListener, prepend, reset, getWindow, getWindowId, isAncestor, getActiveDocument } from 'vs/base/browser/dom'; import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; @@ -88,12 +88,35 @@ export class BrowserTitleService extends MultiWindowParts i super('workbench.titleService', themeService, storageService); this._register(this.registerPart(this.mainPart)); + + this.registerActions(); } protected createMainTitlebarPart(): BrowserTitlebarPart { return this.instantiationService.createInstance(MainBrowserTitlebarPart); } + private registerActions(): void { + + // Focus action + const that = this; + registerAction2(class FocusTitleBar extends Action2 { + + constructor() { + super({ + id: `workbench.action.focusTitleBar`, + title: localize2('focusTitleBar', 'Focus Title Bar'), + category: Categories.View, + f1: true, + }); + } + + run(): void { + that.getPartByDocument(getActiveDocument()).focus(); + } + }); + } + //#region Auxiliary Titlebar Parts createAuxiliaryTitlebarPart(container: HTMLElement, editorGroupsContainer: IEditorGroupsContainer): IAuxiliaryTitlebarPart { @@ -451,28 +474,6 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } } - // Focus action - const that = this; - registerAction2(class FocusTitleBar extends Action2 { - - constructor() { - super({ - id: `workbench.action.focusTitleBar`, - title: localize2('focusTitleBar', 'Focus Title Bar'), - category: Categories.View, - f1: true, - }); - } - - run(): void { - if (that.customMenubar) { - that.customMenubar.toggleFocus(); - } else { - (that.element.querySelector('[tabindex]:not([tabindex="-1"])') as HTMLElement).focus(); - } - } - }); - this.updateStyles(); return this.element; @@ -747,6 +748,14 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } } + focus(): void { + if (this.customMenubar) { + this.customMenubar.toggleFocus(); + } else { + (this.element.querySelector('[tabindex]:not([tabindex="-1"])') as HTMLElement).focus(); + } + } + toJSON(): object { return { type: Parts.TITLEBAR_PART From ce962df66ea04314d1d8dba2eea29f5dbffbdf17 Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 12 Jan 2024 19:07:26 +0100 Subject: [PATCH 0281/1897] Fix `list.focusPageUp` movement with `workbench.tree.enableStickyScroll` (#202361) fix #202288 --- src/vs/base/browser/ui/list/listWidget.ts | 11 ++++++----- src/vs/base/browser/ui/tree/abstractTree.ts | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 1f9a9b0aa72..4e497b12fbd 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -1762,9 +1762,10 @@ export class List implements ISpliceable, IDisposable { } } - async focusPreviousPage(browserEvent?: UIEvent, filter?: (element: T) => boolean): Promise { + async focusPreviousPage(browserEvent?: UIEvent, filter?: (element: T) => boolean, getPaddingTop: () => number = () => 0): Promise { let firstPageIndex: number; - const scrollTop = this.view.getScrollTop(); + const paddingTop = getPaddingTop(); + const scrollTop = this.view.getScrollTop() + paddingTop; if (scrollTop === 0) { firstPageIndex = this.view.indexAt(scrollTop); @@ -1784,14 +1785,14 @@ export class List implements ISpliceable, IDisposable { } } else { const previousScrollTop = scrollTop; - this.view.setScrollTop(scrollTop - this.view.renderHeight); + this.view.setScrollTop(scrollTop - this.view.renderHeight - paddingTop); - if (this.view.getScrollTop() !== previousScrollTop) { + if (this.view.getScrollTop() + getPaddingTop() !== previousScrollTop) { this.setFocus([]); // Let the scroll event listener run await timeout(0); - await this.focusPreviousPage(browserEvent, filter); + await this.focusPreviousPage(browserEvent, filter, getPaddingTop); } } } diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 8d8b317d2ae..d3ce16ad04d 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -2880,7 +2880,7 @@ export abstract class AbstractTree implements IDisposable } focusPreviousPage(browserEvent?: UIEvent, filter = (isKeyboardEvent(browserEvent) && browserEvent.altKey) ? undefined : this.focusNavigationFilter): Promise { - return this.view.focusPreviousPage(browserEvent, filter); + return this.view.focusPreviousPage(browserEvent, filter, () => this.stickyScrollController?.height ?? 0); } focusLast(browserEvent?: UIEvent, filter = (isKeyboardEvent(browserEvent) && browserEvent.altKey) ? undefined : this.focusNavigationFilter): void { From f0aff164822f5e248655eaeb87b70a0cd80860b7 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 12 Jan 2024 10:22:30 -0800 Subject: [PATCH 0282/1897] Dismiss the chat widget on dispose (#202363) --- .../notebook/browser/view/cellParts/chat/cellChatController.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts index f2f620ce9d9..1406eab2b18 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts @@ -155,6 +155,8 @@ export class NotebookCellChatController extends Disposable { // might not be attached } + // dismiss since we can't restore the widget properly now + this.dismiss(false); this._widget?.dispose(); this._inlineChatListener?.dispose(); this._toolbar?.dispose(); From 8c3320b33d533ac944b43453f285b1ebf87d143d Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 12 Jan 2024 19:16:20 +0100 Subject: [PATCH 0283/1897] Fixes #199295 --- .../multiDiffEditorViewModel.ts | 13 ++ .../multiDiffEditorWidget.ts | 20 +-- .../multiDiffEditorWidgetImpl.ts | 116 +++++++++++++++--- .../browser/multiDiffEditor.ts | 11 +- 4 files changed, 123 insertions(+), 37 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts index 45b94c052e3..a2d272329e3 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts @@ -10,6 +10,7 @@ import { DiffEditorOptions } from 'vs/editor/browser/widget/diffEditor/diffEdito import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; import { IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { Selection } from 'vs/editor/common/core/selection'; import { IDiffEditorViewModel } from 'vs/editor/common/editorCommon'; import { ContextKeyValue } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -60,6 +61,11 @@ export class DocumentDiffItemViewModel extends Disposable { public readonly diffEditorViewModel: IDiffEditorViewModel; public readonly collapsed = observableValue(this, false); + public readonly lastTemplateData = observableValue<{ contentHeight: number; selections: Selection[] | undefined }>( + this, + { contentHeight: 500, selections: undefined, } + ); + constructor( public readonly entry: LazyPromise, private readonly _instantiationService: IInstantiationService, @@ -87,4 +93,11 @@ export class DocumentDiffItemViewModel extends Disposable { modified: entry.value!.modified!, }, options)); } + + public getKey(): string { + return JSON.stringify([ + this.diffEditorViewModel.model.original.uri.toString(), + this.diffEditorViewModel.model.modified.uri.toString() + ]); + } } diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index c68d16c1302..418e2c3b81d 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { derived, derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; -import { MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorViewState, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import './colors'; @@ -61,21 +61,11 @@ export class MultiDiffEditorWidget extends Disposable { public readonly onDidChangeActiveControl = Event.fromObservableLight(this._activeControl); - private readonly _scrollState = derived(this, (reader) => { - const w = this._widgetImpl.read(reader); - const top = w.scrollTop.read(reader); - const left = w.scrollLeft.read(reader); - return { top, left }; - }); - - public getScrollState(): { top: number; left: number } { - return this._scrollState.get(); + public getViewState(): IMultiDiffEditorViewState { + return this._widgetImpl.get().getViewState(); } - public setScrollState(scrollState: { top?: number; left?: number }): void { - const w = this._widgetImpl.get(); - w.setScrollState(scrollState); + public setViewState(viewState: IMultiDiffEditorViewState): void { + this._widgetImpl.get().setViewState(viewState); } - - public readonly onDidChangeScrollState = Event.fromObservableLight(this._scrollState); } diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 2c4299af5be..a63e5338ff5 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -8,7 +8,7 @@ import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollable import { findFirstMaxBy } from 'vs/base/common/arraysFind'; import { Disposable, IReference, toDisposable } from 'vs/base/common/lifecycle'; import { IObservable, IReader, autorun, autorunWithStore, derived, derivedObservableWithCache, derivedWithStore, observableFromEvent, observableValue } from 'vs/base/common/observable'; -import { disposableObservableValue, globalTransaction, transaction } from 'vs/base/common/observableInternal/base'; +import { ITransaction, disposableObservableValue, globalTransaction, transaction } from 'vs/base/common/observableInternal/base'; import { Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable'; import 'vs/css!./style'; import { ObservableElementSizeObserver } from 'vs/editor/browser/widget/diffEditor/utils'; @@ -21,6 +21,7 @@ import { ObjectPool } from './objectPool'; import { ContextKeyValue, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ISelection, Selection } from 'vs/editor/common/core/selection'; export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _elements = h('div.monaco-component.multiDiffEditor', [ @@ -68,7 +69,16 @@ export class MultiDiffEditorWidgetImpl extends Disposable { return []; } const items = vm.items.read(reader); - return items.map(d => store.add(new VirtualizedViewItem(d, this._objectPool, this.scrollLeft))); + return items.map(d => { + const item = store.add(new VirtualizedViewItem(d, this._objectPool, this.scrollLeft)); + const data = this._lastDocStates?.[item.getKey()]; + if (data) { + transaction(tx => { + item.setViewState(data, tx); + }); + } + return item; + }); } ); @@ -171,6 +181,37 @@ export class MultiDiffEditorWidgetImpl extends Disposable { this._scrollableElement.setScrollPosition({ scrollLeft: scrollState.left, scrollTop: scrollState.top }); } + public getViewState(): IMultiDiffEditorViewState { + return { + scrollState: { + top: this.scrollTop.get(), + left: this.scrollLeft.get(), + }, + docStates: Object.fromEntries(this._viewItems.get().map(i => [i.getKey(), i.getViewState()])), + }; + } + + /** This accounts for documents that are not loaded yet. */ + private _lastDocStates: IMultiDiffEditorViewState['docStates'] = {}; + + public setViewState(viewState: IMultiDiffEditorViewState): void { + this.setScrollState(viewState.scrollState); + + this._lastDocStates = viewState.docStates; + + transaction(tx => { + /** setViewState */ + if (viewState.docStates) { + for (const i of this._viewItems.get()) { + const state = viewState.docStates[i.getKey()]; + if (state) { + i.setViewState(state, tx); + } + } + } + }); + } + private render(reader: IReader | undefined) { const scrollTop = this.scrollTop.read(reader); let contentScrollOffsetToScrollOffset = 0; @@ -209,19 +250,24 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } } +export interface IMultiDiffEditorViewState { + scrollState: { top: number; left: number }; + docStates?: Record; +} + +interface IMultiDiffDocState { + collapsed: boolean; + selections?: ISelection[]; +} + class VirtualizedViewItem extends Disposable { - // TODO this should be in the view model - private readonly _lastTemplateData = observableValue<{ contentHeight: number; maxScroll: { maxScroll: number; width: number } }>( - this, - { contentHeight: 500, maxScroll: { maxScroll: 0, width: 0 }, } - ); private readonly _templateRef = this._register(disposableObservableValue | undefined>(this, undefined)); public readonly contentHeight = derived(this, reader => - this._templateRef.read(reader)?.object.contentHeight?.read(reader) ?? this._lastTemplateData.read(reader).contentHeight + this._templateRef.read(reader)?.object.contentHeight?.read(reader) ?? this.viewModel.lastTemplateData.read(reader).contentHeight ); - public readonly maxScroll = derived(this, reader => this._templateRef.read(reader)?.object.maxScroll.read(reader) ?? this._lastTemplateData.read(reader).maxScroll); + public readonly maxScroll = derived(this, reader => this._templateRef.read(reader)?.object.maxScroll.read(reader) ?? { maxScroll: 0, scrollWidth: 0 }); public readonly template = derived(this, reader => this._templateRef.read(reader)?.object); private _isHidden = observableValue(this, false); @@ -260,16 +306,53 @@ class VirtualizedViewItem extends Disposable { return `VirtualViewItem(${this.viewModel.entry.value!.modified?.uri.toString()})`; } + public getKey(): string { + return this.viewModel.getKey(); + } + + public getViewState(): IMultiDiffDocState { + transaction(tx => { + this._updateTemplateData(tx); + }); + return { + collapsed: this.viewModel.collapsed.get(), + selections: this.viewModel.lastTemplateData.get().selections, + }; + } + + public setViewState(viewState: IMultiDiffDocState, tx: ITransaction): void { + this.viewModel.collapsed.set(viewState.collapsed, tx); + + this._updateTemplateData(tx); + const data = this.viewModel.lastTemplateData.get(); + const selections = viewState.selections?.map(Selection.liftSelection); + this.viewModel.lastTemplateData.set({ + ...data, + selections, + }, tx); + const ref = this._templateRef.get(); + if (ref) { + if (selections) { + ref.object.editor.setSelections(selections); + } + } + } + + private _updateTemplateData(tx: ITransaction): void { + const ref = this._templateRef.get(); + if (!ref) { return; } + this.viewModel.lastTemplateData.set({ + contentHeight: ref.object.contentHeight.get(), + selections: ref.object.editor.getSelections() ?? undefined, + }, tx); + } + private _clear(): void { const ref = this._templateRef.get(); if (!ref) { return; } transaction(tx => { - this._lastTemplateData.set({ - contentHeight: ref.object.contentHeight.get(), - maxScroll: { maxScroll: 0, width: 0, } // Reset max scroll - }, tx); + this._updateTemplateData(tx); ref.object.hide(); - this._templateRef.set(undefined, tx); }); } @@ -285,6 +368,11 @@ class VirtualizedViewItem extends Disposable { if (!ref) { ref = this._objectPool.getUnusedObj(new TemplateData(this.viewModel)); this._templateRef.set(ref, undefined); + + const selections = this.viewModel.lastTemplateData.get().selections; + if (selections) { + ref.object.editor.setSelections(selections); + } } ref.object.render(verticalSpace, width, offset, viewPort); } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 8ab8baf9c8d..1fd67736d98 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -24,6 +24,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { URI } from 'vs/base/common/uri'; import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; +import { IMultiDiffEditorViewState } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; export class MultiDiffEditor extends AbstractEditorWithViewState { static readonly ID = 'multiDiffEditor'; @@ -35,7 +36,6 @@ export class MultiDiffEditor extends AbstractEditorWithViewState Date: Fri, 12 Jan 2024 10:33:41 -0800 Subject: [PATCH 0284/1897] highlight fixable errors when hovering/selecting code action (#202294) * highlight for fixall * removed duplicatd code * use const --- .../browser/codeActionController.ts | 5 +++-- .../codeAction/browser/codeActionModel.ts | 19 ++++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index 12cac46395c..81b765253ab 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -279,8 +279,9 @@ export class CodeActionController extends Disposable implements IEditorContribut return { canPreview: !!action.action.edit?.edits.length }; }, onFocus: (action: CodeActionItem | undefined) => { - if (action && action.highlightRange && action.action.diagnostics) { - const decorations: IModelDeltaDecoration[] = [{ range: action.action.diagnostics[0], options: CodeActionController.DECORATION }]; + if (action && action.action.diagnostics) { + currentDecorations.clear(); + const decorations: IModelDeltaDecoration[] = action.action.diagnostics.map(diagnostic => ({ range: diagnostic, options: CodeActionController.DECORATION })); currentDecorations.set(decorations); const diagnostic = action.action.diagnostics[0]; const selectionText = this._editor.getModel()?.getWordAtPosition({ lineNumber: diagnostic.startLineNumber, column: diagnostic.startColumn })?.word; diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts b/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts index 658c35288ad..547b6c43108 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts @@ -24,6 +24,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur export const SUPPORTED_CODE_ACTIONS = new RawContextKey('supportedCodeAction', ''); +export const APPLY_FIX_ALL_COMMAND_ID = '_typescript.applyFixAllCodeAction'; + type TriggeredCodeAction = { readonly selection: Selection; readonly trigger: CodeActionTrigger; @@ -234,10 +236,15 @@ export class CodeActionModel extends Disposable { // Search for quickfixes in the curret code action set. const foundQuickfix = codeActionSet.validActions?.some(action => action.action.kind ? CodeActionKind.QuickFix.contains(new CodeActionKind(action.action.kind)) : false); - - if (!foundQuickfix) { - const allMarkers = this._markerService.read({ resource: model.uri }); - + const allMarkers = this._markerService.read({ resource: model.uri }); + if (foundQuickfix) { + for (const action of codeActionSet.validActions) { + if (action.action.command?.arguments?.some(arg => typeof arg === 'string' && arg.includes(APPLY_FIX_ALL_COMMAND_ID))) { + action.action.diagnostics = [...allMarkers.filter(marker => marker.relatedInformation)]; + } + } + return { validActions: codeActionSet.validActions, allActions: allCodeActions, documentation: codeActionSet.documentation, hasAutoFix: codeActionSet.hasAutoFix, hasAIFix: codeActionSet.hasAIFix, allAIFixes: codeActionSet.allAIFixes, dispose: () => { codeActionSet.dispose(); } }; + } else if (!foundQuickfix) { // If markers exists, and there are no quickfixes found or length is zero, check for quickfixes on that line. if (allMarkers.length > 0) { const currPosition = trigger.selection.getPosition(); @@ -266,7 +273,9 @@ export class CodeActionModel extends Disposable { if (actionsAtMarker.validActions.length !== 0) { for (const action of actionsAtMarker.validActions) { - action.highlightRange = action.action.isPreferred; + if (action.action.command?.arguments?.some(arg => typeof arg === 'string' && arg.includes(APPLY_FIX_ALL_COMMAND_ID))) { + action.action.diagnostics = [...allMarkers.filter(marker => marker.relatedInformation)]; + } } if (codeActionSet.allActions.length === 0) { From a15d1916bb2f0644feb591f71976ce1180d33801 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 12 Jan 2024 17:08:21 -0300 Subject: [PATCH 0285/1897] Fix left over : after inserting #file --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 1 + .../contrib/chat/browser/contrib/chatInputEditorContrib.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 68d027868f2..dec5a7301e2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -251,6 +251,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge showSnippets: false, showWords: true, showStatusBar: false, + insertMode: 'replace', }; options.scrollbar = { ...(options.scrollbar ?? {}), vertical: 'hidden' }; diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 928c561c4df..76a17b298bd 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -534,7 +534,7 @@ class BuiltinDynamicCompletions extends Disposable { return null; } - const afterRange = new Range(position.lineNumber, range.replace.startColumn, position.lineNumber, range.replace.endColumn + 'file:'.length); + const afterRange = new Range(position.lineNumber, range.replace.startColumn, position.lineNumber, range.replace.startColumn + '#file:'.length); return { suggestions: [ { From 738d3cc29bb537a2e43ce76fa5a76ed52e23a4cf Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 12 Jan 2024 18:00:05 -0300 Subject: [PATCH 0286/1897] Fix backspace-token-delete for variables --- .../contrib/chat/browser/contrib/chatDynamicVariables.ts | 8 +++++++- .../chat/browser/contrib/chatInputEditorContrib.ts | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts index a2e9446011e..7b6bb6c1c2b 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts @@ -44,8 +44,14 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC e.changes.forEach(c => { // Don't mutate entries in _variables, since they will be returned from the getter this._variables = coalesce(this._variables.map(ref => { - if (Range.areIntersecting(ref.range, c.range)) { + const intersection = Range.intersectRanges(ref.range, c.range); + if (intersection && !intersection.isEmpty()) { // The reference text was changed, it's broken + const rangeToDelete = new Range(ref.range.startLineNumber, ref.range.startColumn, ref.range.endLineNumber, ref.range.endColumn - 1); + this.widget.inputEditor.executeEdits(this.id, [{ + range: rangeToDelete, + text: '', + }]); return null; } else if (Range.compareRangesUsingStarts(ref.range, c.range) > 0) { const delta = c.text.length - c.rangeLength; diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 76a17b298bd..a5f4d01a6c3 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -662,7 +662,8 @@ class ChatTokenDeleter extends Disposable { // If this was a simple delete, try to find out whether it was inside a token if (!change.text) { parser.parseChatRequest(this.widget.viewModel!.sessionId, previousInputValue).then(previousParsedValue => { - const deletableTokens = previousParsedValue.parts.filter(p => p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart || p instanceof ChatRequestSlashCommandPart); + // For dynamic variables, this has to happen in ChatDynamicVariableModel with the other bookkeeping + const deletableTokens = previousParsedValue.parts.filter(p => p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart || p instanceof ChatRequestSlashCommandPart || p instanceof ChatRequestVariablePart); deletableTokens.forEach(token => { const deletedRangeOfToken = Range.intersectRanges(token.editorRange, change.range); // Part of this token was deleted, and the deletion range doesn't go off the front of the token, for simpler math From 25d12715f05fa20935cd4fcb2d89a5937c4f538e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 12 Jan 2024 22:18:32 +0100 Subject: [PATCH 0287/1897] aux window - fix zoom when title bar prevents it (#202378) --- .../browser/parts/editor/auxiliaryEditorPart.ts | 14 +++++++++++++- .../browser/parts/titlebar/titlebarPart.ts | 2 ++ .../parts/titlebar/titlebarPart.ts | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts index 6e7b8c352b7..d50e62f6095 100644 --- a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { onDidChangeFullscreen } from 'vs/base/browser/browser'; +import { onDidChangeFullscreen, onDidChangeZoomLevel } from 'vs/base/browser/browser'; import { detectFullscreen, hide, show } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -113,6 +113,18 @@ export class AuxiliaryEditorPart { titlebarPartVisible = true; disposables.add(titlebarPart.onDidChange(() => updateEditorPartHeight(true))); + disposables.add(onDidChangeZoomLevel(targetWindowId => { + if (auxiliaryWindow.window.vscodeWindowId === targetWindowId && titlebarPartVisible) { + + // This is a workaround for https://github.com/microsoft/vscode/issues/202377 + // The title bar part prevents zooming in certain cases and when doing so, + // adjusts its size accordingly. This is however not reported from the + // `onDidchange` event that we listen to above, so we manually update the + // editor part height here. + + updateEditorPartHeight(true); + } + })); disposables.add(onDidChangeFullscreen(windowId => { if (windowId !== auxiliaryWindow.window.vscodeWindowId) { diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 4a00d9df336..c66a4dabd81 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -182,6 +182,7 @@ class TitlebarPartHoverDelegate implements IHoverDelegate { } export class BrowserTitlebarPart extends Part implements ITitlebarPart { + //#region IView readonly minimumWidth: number = 0; @@ -826,6 +827,7 @@ export class AuxiliaryBrowserTitlebarPart extends BrowserTitlebarPart implements } override get preventZoom(): boolean { + // Prevent zooming behavior if any of the following conditions are met: // 1. Shrinking below the window control size (zoom < 1) // 2. No custom items are present in the main title bar diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index af61278c198..7b95b0c342c 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -327,6 +327,7 @@ export class AuxiliaryNativeTitlebarPart extends NativeTitlebarPart implements I } override get preventZoom(): boolean { + // Prevent zooming behavior if any of the following conditions are met: // 1. Shrinking below the window control size (zoom < 1) // 2. No custom items are present in the main title bar From b1722705504c2776ad6196154712936a2d944bfa Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 12 Jan 2024 22:36:03 +0100 Subject: [PATCH 0288/1897] Adopt ensureNoDisposablesAreLeakedInTestSuite round 2 (#200091) (#202379) --- .../base/parts/sandbox/test/electron-sandbox/globals.test.ts | 4 ++++ src/vs/base/test/browser/indexedDB.test.ts | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts b/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts index 16320fe3789..491f2209c6d 100644 --- a/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts +++ b/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts @@ -5,8 +5,10 @@ import * as assert from 'assert'; import { context, ipcRenderer, process, webFrame } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Sandbox', () => { + test('globals', async () => { assert.ok(typeof ipcRenderer.send === 'function'); assert.ok(typeof webFrame.setZoomLevel === 'function'); @@ -16,4 +18,6 @@ suite('Sandbox', () => { assert.ok(config); assert.ok(context.configuration()); }); + + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/base/test/browser/indexedDB.test.ts b/src/vs/base/test/browser/indexedDB.test.ts index ba9dcd0f51c..a6266231292 100644 --- a/src/vs/base/test/browser/indexedDB.test.ts +++ b/src/vs/base/test/browser/indexedDB.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { IndexedDB } from 'vs/base/browser/indexedDB'; import { flakySuite } from 'vs/base/test/common/testUtils'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; flakySuite('IndexedDB', () => { @@ -60,4 +61,5 @@ flakySuite('IndexedDB', () => { } catch (error) { } }); + ensureNoDisposablesAreLeakedInTestSuite(); }); From abdeb03dc000d8ca0b9d9b522d7260a0279c76fe Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 12 Jan 2024 22:50:36 +0100 Subject: [PATCH 0289/1897] SCM - shrink the checkout action so that other actions are visible (#202383) --- src/vs/workbench/contrib/scm/browser/media/scm.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index af1ddef0015..335b67c2cad 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -81,6 +81,10 @@ align-items: center; } +.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(1) { + min-width: 20px; +} + .scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label, .scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .monaco-dropdown > .dropdown-label > .action-label { display: flex; From bb866cd34524c7aaa1ea51e5176654e83b47e556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20K=C3=B6ck?= Date: Sat, 13 Jan 2024 01:38:29 +0100 Subject: [PATCH 0290/1897] Oh, it's already 2024 (#202344) chore: update copyright year Co-authored-by: deepak1556 --- build/lib/electron.js | 2 +- build/lib/electron.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/lib/electron.js b/build/lib/electron.js index df6473c32f8..8524b18850c 100644 --- a/build/lib/electron.js +++ b/build/lib/electron.js @@ -87,7 +87,7 @@ exports.config = { tag: product.electronRepository ? `v${electronVersion}-${msBuildId}` : undefined, productAppName: product.nameLong, companyName: 'Microsoft Corporation', - copyright: 'Copyright (C) 2023 Microsoft. All rights reserved', + copyright: 'Copyright (C) 2024 Microsoft. All rights reserved', darwinIcon: 'resources/darwin/code.icns', darwinBundleIdentifier: product.darwinBundleIdentifier, darwinApplicationCategoryType: 'public.app-category.developer-tools', diff --git a/build/lib/electron.ts b/build/lib/electron.ts index 33b407398da..ba93c3a2af3 100644 --- a/build/lib/electron.ts +++ b/build/lib/electron.ts @@ -104,7 +104,7 @@ export const config = { tag: product.electronRepository ? `v${electronVersion}-${msBuildId}` : undefined, productAppName: product.nameLong, companyName: 'Microsoft Corporation', - copyright: 'Copyright (C) 2023 Microsoft. All rights reserved', + copyright: 'Copyright (C) 2024 Microsoft. All rights reserved', darwinIcon: 'resources/darwin/code.icns', darwinBundleIdentifier: product.darwinBundleIdentifier, darwinApplicationCategoryType: 'public.app-category.developer-tools', From afbec2027b8792a461b58e33697536b622f6636b Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 12 Jan 2024 18:46:08 -0800 Subject: [PATCH 0291/1897] Fix markdown-it-katex error (#202393) --- extensions/markdown-language-features/notebook/index.ts | 6 +++++- extensions/markdown-math/notebook/katex.ts | 2 +- extensions/markdown-math/package.json | 2 +- extensions/markdown-math/src/extension.ts | 2 +- extensions/markdown-math/yarn.lock | 8 ++++---- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index e8c30c9b1eb..d89f9b15d80 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -342,7 +342,11 @@ export const activate: ActivationFunction = (ctx) => { } }, extendMarkdownIt: (f: (md: typeof markdownIt) => void) => { - f(markdownIt); + try { + f(markdownIt); + } catch (err) { + console.error('Error extending markdown-it', err); + } } }; }; diff --git a/extensions/markdown-math/notebook/katex.ts b/extensions/markdown-math/notebook/katex.ts index 92b2e5a56a7..94aad4f3c3b 100644 --- a/extensions/markdown-math/notebook/katex.ts +++ b/extensions/markdown-math/notebook/katex.ts @@ -45,7 +45,7 @@ export async function activate(ctx: RendererContext) { styleTemplate.content.appendChild(link); document.head.appendChild(styleTemplate); - const katex = require('@vscode/markdown-it-katex'); + const katex = require('@vscode/markdown-it-katex').default; const macros = {}; markdownItRenderer.extendMarkdownIt((md: markdownIt.MarkdownIt) => { return md.use(katex, { diff --git a/extensions/markdown-math/package.json b/extensions/markdown-math/package.json index ea2761b901d..8947e89ed2f 100644 --- a/extensions/markdown-math/package.json +++ b/extensions/markdown-math/package.json @@ -98,7 +98,7 @@ "build-notebook": "node ./esbuild" }, "dependencies": { - "@vscode/markdown-it-katex": "^1.0.1" + "@vscode/markdown-it-katex": "^1.0.2" }, "devDependencies": { "@types/markdown-it": "^0.0.0", diff --git a/extensions/markdown-math/src/extension.ts b/extensions/markdown-math/src/extension.ts index 38fe52b3203..1c27036b2fc 100644 --- a/extensions/markdown-math/src/extension.ts +++ b/extensions/markdown-math/src/extension.ts @@ -28,7 +28,7 @@ export function activate(context: vscode.ExtensionContext) { return { extendMarkdownIt(md: any) { if (isEnabled()) { - const katex = require('@vscode/markdown-it-katex'); + const katex = require('@vscode/markdown-it-katex').default; const settingsMacros = getMacros(); const options = { globalGroup: true, macros: { ...settingsMacros } }; md.core.ruler.push('reset-katex-macros', () => { diff --git a/extensions/markdown-math/yarn.lock b/extensions/markdown-math/yarn.lock index 3b6786c1c1d..e49c27e7bf8 100644 --- a/extensions/markdown-math/yarn.lock +++ b/extensions/markdown-math/yarn.lock @@ -12,10 +12,10 @@ resolved "https://registry.yarnpkg.com/@types/vscode-notebook-renderer/-/vscode-notebook-renderer-1.72.0.tgz#8943dc3cef0ced2dfb1e04c0a933bd289e7d5199" integrity sha512-5iTjb39DpLn03ULUwrDR3L2Dy59RV4blSUHy0oLdQuIY11PhgWO4mXIcoFS0VxY1GZQ4IcjSf3ooT2Jrrcahnw== -"@vscode/markdown-it-katex@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@vscode/markdown-it-katex/-/markdown-it-katex-1.0.1.tgz#79c6e7312074e1f897cc22c42ce078d1e72003b0" - integrity sha512-O/HiT5Uc6rN6rSx8tDdgwO1tLSn/lrNeikTzYw1EBG6B2IGLKw4I4e/GBh9DRNSdE9PajCA0tsVBz86qyA7B3A== +"@vscode/markdown-it-katex@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@vscode/markdown-it-katex/-/markdown-it-katex-1.0.2.tgz#27ba579fa3896b2944b71209dd30d0f983983f11" + integrity sha512-QY/OnOHPTqc8tQoCoAjVblILX4yE6xGZHKODtiTKqA328OXra+lSpeJO5Ouo9AAvrs9AwcCLz6xvW3zwcsPBQg== dependencies: katex "^0.16.4" From 1c0c4726d30a0574739b35cc6d7a7dea9b3decb2 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 12 Jan 2024 22:35:11 -0800 Subject: [PATCH 0292/1897] testing: finish up coverage decorations (#202391) * testing: finish up coverage decorations - Adds an inline decorator for empty-range branches - Adds hover information for inline coverage hovers - Fixes the editor not handling decorations that get after/before content in `updateDecorationOptions` - Adds an option `label` for branches - A bunch of other misc tweaks to get coverage looking nice - Keep decorations in sync if a user makes changes in editor * update tests --- .../lib/stylelint/vscode-known-variables.json | 1 + src/vs/editor/browser/editorBrowser.ts | 5 + .../editor/browser/widget/codeEditorWidget.ts | 15 +- src/vs/editor/common/model/textModel.ts | 9 +- src/vs/monaco.d.ts | 5 + .../api/common/extHostTypeConverters.ts | 2 +- src/vs/workbench/api/common/extHostTypes.ts | 1 + .../browser/codeCoverageDecorations.ts | 284 +++++++++++++----- .../contrib/testing/browser/icons.ts | 1 + .../contrib/testing/browser/media/testing.css | 20 ++ .../testing/browser/testExplorerActions.ts | 41 ++- .../contrib/testing/browser/theme.ts | 28 +- .../contrib/testing/common/constants.ts | 1 + .../contrib/testing/common/testCoverage.ts | 2 + .../contrib/testing/common/testTypes.ts | 2 + ..._Decorations_CoverageDetailsModel_3.0.snap | 4 - ..._Decorations_CoverageDetailsModel_4.0.snap | 4 - .../browser/codeCoverageDecorations.test.ts | 35 +-- .../vscode.proposed.testCoverage.d.ts | 8 +- 19 files changed, 347 insertions(+), 121 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index e537aed9b1c..e0fcdff3c6d 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -681,6 +681,7 @@ "--vscode-terminalStickyScroll-background", "--vscode-terminalStickyScrollHover-background", "--vscode-testing-coveredBackground", + "--vscode-testing-coverage-lineHeight", "--vscode-testing-coveredGutterBackground", "--vscode-testing-iconErrored", "--vscode-testing-iconFailed", diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 9fe0301b8ce..ecedcdb42a9 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -599,6 +599,11 @@ export interface ICodeEditor extends editorCommon.IEditor { * @event */ readonly onDidChangeCursorSelection: Event; + /** + * An event emitted when the model of this editor is about to change (e.g. from `editor.setModel()`). + * @event + */ + readonly onWillChangeModel: Event; /** * An event emitted when the model of this editor has changed (e.g. `editor.setModel()`). * @event diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index b94abf06b8f..9e1a7f0df17 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -142,6 +142,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter({ deliveryQueue: this._deliveryQueue })); public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; + protected readonly _onWillChangeModel: Emitter = this._register(new Emitter({ deliveryQueue: this._deliveryQueue })); + public readonly onWillChangeModel: Event = this._onWillChangeModel.event; + protected readonly _onDidChangeModel: Emitter = this._register(new Emitter({ deliveryQueue: this._deliveryQueue })); public readonly onDidChangeModel: Event = this._onDidChangeModel.event; @@ -502,6 +505,13 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE // Current model is the new model return; } + + const e: editorCommon.IModelChangedEvent = { + oldModelUrl: this._modelData?.model.uri || null, + newModelUrl: model?.uri || null + }; + this._onWillChangeModel.fire(e); + const hasTextFocus = this.hasTextFocus(); const detachedModel = this._detachModel(); this._attachModel(model); @@ -509,11 +519,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this.focus(); } - const e: editorCommon.IModelChangedEvent = { - oldModelUrl: detachedModel ? detachedModel.uri : null, - newModelUrl: model ? model.uri : null - }; - this._removeDecorationTypes(); this._onDidChangeModel.fire(e); this._postDetachModelCleanup(detachedModel); diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index e3e8239cb55..7117b8240af 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -1832,8 +1832,9 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati this._onDidChangeDecorations.recordLineAffectedByInjectedText(nodeRange.startLineNumber); } - if (nodeWasInOverviewRuler !== nodeIsInOverviewRuler) { - // Delete + Insert due to an overview ruler status change + const movedInOverviewRuler = nodeWasInOverviewRuler !== nodeIsInOverviewRuler; + const changedWhetherInjectedText = isOptionsInjectedText(options) !== isNodeInjectedText(node); + if (movedInOverviewRuler || changedWhetherInjectedText) { this._decorationsTree.delete(node); node.setOptions(options); this._decorationsTree.insert(node); @@ -2001,6 +2002,10 @@ function isNodeInOverviewRuler(node: IntervalNode): boolean { return (node.options.overviewRuler && node.options.overviewRuler.color ? true : false); } +function isOptionsInjectedText(options: ModelDecorationOptions): boolean { + return !!options.after || !!options.before; +} + function isNodeInjectedText(node: IntervalNode): boolean { return !!node.options.after || !!node.options.before; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index f0028ac35dc..51a4fa5d458 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5644,6 +5644,11 @@ declare namespace monaco.editor { * @event */ readonly onDidChangeCursorSelection: IEvent; + /** + * An event emitted when the model of this editor is about to change (e.g. from `editor.setModel()`). + * @event + */ + readonly onWillChangeModel: IEvent; /** * An event emitted when the model of this editor has changed (e.g. `editor.setModel()`). * @event diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 17e1ef13113..143ae656c2d 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1995,7 +1995,7 @@ export namespace TestCoverage { location: fromLocation(coverage.location), type: DetailType.Statement, branches: coverage.branches.length - ? coverage.branches.map(b => ({ count: b.executionCount, location: b.location && fromLocation(b.location) })) + ? coverage.branches.map(b => ({ count: b.executionCount, location: b.location && fromLocation(b.location), label: b.label })) : undefined, }; } else { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index fe574fb9c67..cd84f1b1675 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4017,6 +4017,7 @@ export class BranchCoverage implements vscode.BranchCoverage { constructor( public executionCount: number, public location: Position | Range, + public label?: string, ) { } } diff --git a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts index b69910cd580..4c7a6caa211 100644 --- a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts @@ -5,12 +5,14 @@ import * as dom from 'vs/base/browser/dom'; import { HoverWidget } from 'vs/base/browser/ui/hover/hoverWidget'; +import { mapFindFirst } from 'vs/base/common/arraysFind'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { autorun, derived, observableFromEvent, observableValue } from 'vs/base/common/observable'; +import { ThemeIcon } from 'vs/base/common/themables'; import { isDefined } from 'vs/base/common/types'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; @@ -18,7 +20,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model'; +import { IModelDecorationOptions, ITextModel, InjectedTextCursorStops } from 'vs/editor/common/model'; import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/browser/hoverOperation'; import { localize } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; @@ -27,6 +29,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ILogService } from 'vs/platform/log/common/log'; +import { testingCoverageMissingBranch } from 'vs/workbench/contrib/testing/browser/icons'; import { FileCoverage } from 'vs/workbench/contrib/testing/common/testCoverage'; import { ITestCoverageService } from 'vs/workbench/contrib/testing/common/testCoverageService'; import { CoverageDetails, DetailType, IStatementCoverage } from 'vs/workbench/contrib/testing/common/testTypes'; @@ -37,19 +40,22 @@ const CLASS_HIT = 'coverage-deco-hit'; const CLASS_MISS = 'coverage-deco-miss'; const TOGGLE_INLINE_COMMAND_TEXT = localize('testing.toggleInlineCoverage', 'Toggle Inline Coverage'); const TOGGLE_INLINE_COMMAND_ID = 'testing.toggleInlineCoverage'; +const BRANCH_MISS_INDICATOR_CHARS = 4; export class CodeCoverageDecorations extends Disposable implements IEditorContribution { public static showInline = observableValue('inlineCoverage', false); + private static readonly fileCoverageDecorations = new WeakMap(); private loadingCancellation?: CancellationTokenSource; private readonly displayedStore = this._register(new DisposableStore()); private readonly hoveredStore = this._register(new DisposableStore()); private readonly lineHoverWidget: Lazy; private decorationIds = new Map; + applyHoverOptions(target: IModelDecorationOptions): void; }>(); - private hoveredLineNumber?: number; + private hoveredSubject?: unknown; private details?: CoverageDetailsModel; constructor( @@ -63,6 +69,7 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri this.lineHoverWidget = new Lazy(() => this._register(instantiationService.createInstance(LineHoverWidget, this.editor))); const modelObs = observableFromEvent(editor.onDidChangeModel, () => editor.getModel()); + const configObs = observableFromEvent(editor.onDidChangeConfiguration, i => i); const fileCoverage = derived(reader => { const report = coverage.selected.read(reader); @@ -87,54 +94,121 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri } })); + this._register(autorun(reader => { + const c = fileCoverage.read(reader); + if (c) { + const evt = configObs.read(reader); + if (evt?.hasChanged(EditorOption.lineHeight) !== false) { + this.updateEditorStyles(); + } + } + })); + this._register(editor.onMouseMove(e => { if (e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS) { this.hoverLineNumber(editor.getModel()!, e.target.position.lineNumber); } else if (this.lineHoverWidget.hasValue && this.lineHoverWidget.value.getDomNode().contains(e.target.element)) { // don't dismiss the hover + } else if (CodeCoverageDecorations.showInline.get() && e.target.type === MouseTargetType.CONTENT_TEXT) { + this.hoverInlineDecoration(editor.getModel()!, e.target.position); } else { this.hoveredStore.clear(); } })); + + this._register(editor.onWillChangeModel(() => { + const model = editor.getModel(); + if (!this.details || !model) { + return; + } + + // Decorations adjust to local changes made in-editor, keep them synced in case the file is reopened: + for (const decoration of model.getAllDecorations()) { + const own = this.decorationIds.get(decoration.id); + if (own) { + own.detail.range = decoration.range; + } + } + })); } - private hoverLineNumber(model: ITextModel, lineNumber: number) { - if (lineNumber === this.hoveredLineNumber) { + private updateEditorStyles() { + const lineHeight = this.editor.getOption(EditorOption.lineHeight); + const { style } = this.editor.getContainerDomNode(); + style.setProperty('--vscode-testing-coverage-lineHeight', `${lineHeight}px`); + } + + private hoverInlineDecoration(model: ITextModel, position: Position) { + const allDecorations = model.getDecorationsInRange(Range.fromPositions(position)); + const decoration = mapFindFirst(allDecorations, ({ id }) => this.decorationIds.has(id) ? { id, deco: this.decorationIds.get(id)! } : undefined); + if (decoration === this.hoveredSubject) { return; } - this.hoveredLineNumber = lineNumber; this.hoveredStore.clear(); + this.hoveredSubject = decoration; - const todo = [{ line: lineNumber, dir: 0 }]; - const toEnable = new Set(); - for (let i = 0; i < todo.length && i < MAX_HOVERED_LINES; i++) { - const { line, dir } = todo[i]; - let found = false; - for (const decoration of model.getLineDecorations(line)) { - if (this.decorationIds.has(decoration.id)) { - toEnable.add(decoration.id); - found = true; - } - } - if (found) { - if (dir <= 0) { - todo.push({ line: line - 1, dir: -1 }); - } - if (dir >= 0) { - todo.push({ line: line + 1, dir: 1 }); - } - } + if (!decoration) { + return; } model.changeDecorations(e => { - for (const id of toEnable) { - const { hoverOptions, options } = this.decorationIds.get(id)!; - e.changeDecorationOptions(id, { ...options, ...hoverOptions }); - } + e.changeDecorationOptions(decoration.id, { + ...decoration.deco.options, + className: `${decoration.deco.options.className} coverage-deco-hovered`, + }); }); - this.lineHoverWidget.value.startShowingAt(lineNumber, this.details!); + this.hoveredStore.add(toDisposable(() => { + this.hoveredSubject = undefined; + model.changeDecorations(e => { + e.changeDecorationOptions(decoration!.id, decoration!.deco.options); + }); + })); + } + + private hoverLineNumber(model: ITextModel, lineNumber: number) { + if (lineNumber === this.hoveredSubject) { + return; + } + + const wasPreviouslyHovering = typeof this.hoveredSubject === 'number'; + this.hoveredStore.clear(); + this.hoveredSubject = lineNumber; + + const todo = [{ line: lineNumber, dir: 0 }]; + const toEnable = new Set(); + if (!CodeCoverageDecorations.showInline.get()) { + for (let i = 0; i < todo.length && i < MAX_HOVERED_LINES; i++) { + const { line, dir } = todo[i]; + let found = false; + for (const decoration of model.getLineDecorations(line)) { + if (this.decorationIds.has(decoration.id)) { + toEnable.add(decoration.id); + found = true; + } + } + if (found) { + if (dir <= 0) { + todo.push({ line: line - 1, dir: -1 }); + } + if (dir >= 0) { + todo.push({ line: line + 1, dir: 1 }); + } + } + } + + model.changeDecorations(e => { + for (const id of toEnable) { + const { applyHoverOptions, options } = this.decorationIds.get(id)!; + const dup = { ...options }; + applyHoverOptions(dup); + e.changeDecorationOptions(id, dup); + } + }); + } + + this.lineHoverWidget.value.startShowingAt(lineNumber, this.details!, wasPreviouslyHovering); this.hoveredStore.add(this.editor.onMouseLeave(() => { this.hoveredStore.clear(); @@ -142,7 +216,7 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri this.hoveredStore.add(toDisposable(() => { this.lineHoverWidget.value.hide(); - this.hoveredLineNumber = -1; + this.hoveredSubject = undefined; model.changeDecorations(e => { for (const id of toEnable) { @@ -156,31 +230,46 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri } private async apply(model: ITextModel, coverage: FileCoverage, showInlineByDefault: boolean) { - const details = await this.loadDetails(coverage); + const details = this.details = await this.loadDetails(coverage, model); if (!details) { return this.clear(); } this.displayedStore.clear(); - const detailModel = this.details = new CoverageDetailsModel(details); model.changeDecorations(e => { - for (const { metadata: detail, range } of detailModel.ranges) { + for (const detailRange of details.ranges) { + const { metadata: { detail, description }, range } = detailRange; if (detail.type === DetailType.Branch) { const hits = detail.detail.branches![detail.branch].count; const cls = hits ? CLASS_HIT : CLASS_MISS; + // don't bother showing the miss indicator if the condition wasn't executed at all: + const showMissIndicator = !hits && range.isEmpty() && detail.detail.branches!.some(b => b.count); const options: IModelDecorationOptions = { - showIfCollapsed: false, + showIfCollapsed: showMissIndicator, // only avoid collapsing if we want to show the miss indicator description: 'coverage-gutter', lineNumberClassName: `coverage-deco-gutter ${cls}`, }; - const hoverOptions: Partial = { className: `coverage-deco-inline ${cls}` }; + const applyHoverOptions = (target: IModelDecorationOptions) => { + target.hoverMessage = description; + if (showMissIndicator) { + target.after = { + content: '\xa0'.repeat(BRANCH_MISS_INDICATOR_CHARS), // nbsp + inlineClassName: `coverage-deco-branch-miss-indicator ${ThemeIcon.asClassName(testingCoverageMissingBranch)}`, + inlineClassNameAffectsLetterSpacing: true, + cursorStops: InjectedTextCursorStops.None, + }; + } else { + target.className = `coverage-deco-inline ${cls}`; + } + }; + if (showInlineByDefault) { - Object.assign(options, hoverOptions); + applyHoverOptions(options); } - this.decorationIds.set(e.addDecoration(range, options), { options, hoverOptions }); + this.decorationIds.set(e.addDecoration(range, options), { options, applyHoverOptions, detail: detailRange }); } else if (detail.type === DetailType.Statement) { const cls = detail.count ? CLASS_HIT : CLASS_MISS; const options: IModelDecorationOptions = { @@ -189,12 +278,16 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri lineNumberClassName: `coverage-deco-gutter ${cls}`, }; - const hoverOptions: Partial = { className: `coverage-deco-inline ${cls}` }; + const applyHoverOptions = (target: IModelDecorationOptions) => { + target.className = `coverage-deco-inline ${cls}`; + target.hoverMessage = description; + }; + if (showInlineByDefault) { - Object.assign(options, hoverOptions); + applyHoverOptions(options); } - this.decorationIds.set(e.addDecoration(range, options), { options, hoverOptions }); + this.decorationIds.set(e.addDecoration(range, options), { options, applyHoverOptions, detail: detailRange }); } } }); @@ -216,15 +309,24 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri this.hoveredStore.clear(); } - private async loadDetails(coverage: FileCoverage) { + private async loadDetails(coverage: FileCoverage, textModel: ITextModel) { + const existing = CodeCoverageDecorations.fileCoverageDecorations.get(coverage); + if (existing) { + return existing; + } + const cts = this.loadingCancellation = new CancellationTokenSource(); this.displayedStore.add(this.loadingCancellation); try { const details = await coverage.details(this.loadingCancellation.token); - if (!cts.token.isCancellationRequested) { - return details; + if (cts.token.isCancellationRequested) { + return; } + const model = CodeCoverageDecorations.fileCoverageDecorations.get(coverage) + || new CoverageDetailsModel(details, textModel); + CodeCoverageDecorations.fileCoverageDecorations.set(coverage, model); + return model; } catch (e) { this.log.error('Error loading coverage details', e); } @@ -234,32 +336,41 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri } type CoverageDetailsWithBranch = CoverageDetails | { type: DetailType.Branch; branch: number; detail: IStatementCoverage }; -type DetailRange = { range: Range; metadata: CoverageDetailsWithBranch }; +type DetailRange = { range: Range; metadata: { detail: CoverageDetailsWithBranch; description: IMarkdownString | undefined } }; export class CoverageDetailsModel { - public ranges: DetailRange[] = []; + public readonly ranges: DetailRange[] = []; - constructor(public readonly details: CoverageDetails[]) { + constructor(public readonly details: CoverageDetails[], textModel: ITextModel) { //#region decoration generation // Coverage from a provider can have a range that contains smaller ranges, // such as a function declarationt that has nested statements. In this we // make sequential, non-overlapping ranges for each detail for display in // the editor without ugly overlaps. - const detailRanges: DetailRange[] = details.map(d => ({ range: tidyLocation(d.location), metadata: d })); + const detailRanges: DetailRange[] = details.map(detail => ({ + range: tidyLocation(detail.location), + metadata: { detail, description: this.describe(detail, textModel) } + })); - for (const { range, metadata: detail } of detailRanges) { + for (const { range, metadata: { detail } } of detailRanges) { if (detail.type === DetailType.Statement && detail.branches) { for (let i = 0; i < detail.branches.length; i++) { + const branch: CoverageDetailsWithBranch = { type: DetailType.Branch, branch: i, detail }; detailRanges.push({ range: tidyLocation(detail.branches[i].location || Range.fromPositions(range.getEndPosition())), - metadata: { type: DetailType.Branch, branch: i, detail }, + metadata: { + detail: branch, + description: this.describe(branch, textModel), + }, }); } } } - detailRanges.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); + // type ordering is done so that function declarations come first on a tie so that + // single-statement functions (`() => foo()` for example) get inline decorations. + detailRanges.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range) || a.metadata.detail.type - b.metadata.detail.type); const stack: DetailRange[] = []; const result: DetailRange[] = this.ranges = []; @@ -294,6 +405,8 @@ export class CoverageDetailsModel { if (prev) { const si = prev.range.setEndPosition(start.lineNumber, start.column); prev.range = prev.range.setStartPosition(item.range.endLineNumber, item.range.endColumn); + // discard the previous range if it became empty, e.g. a nested statement + if (prev.range.isEmpty()) { stack.pop(); } result.push({ range: si, metadata: prev.metadata }); } @@ -304,6 +417,34 @@ export class CoverageDetailsModel { } //#endregion } + + /** Gets the markdown description for the given detail */ + public describe(detail: CoverageDetailsWithBranch, model: ITextModel): IMarkdownString | undefined { + if (detail.type === DetailType.Function) { + return new MarkdownString().appendMarkdown(localize('coverage.fnExecutedCount', 'Function `{0}` was executed {1} time(s).', detail.name, detail.count)); + } else if (detail.type === DetailType.Statement) { + const text = wrapName(model.getValueInRange(tidyLocation(detail.location)).trim() || ``); + const str = new MarkdownString(); + if (detail.branches?.length) { + const covered = detail.branches.filter(b => b.count > 0).length; + str.appendMarkdown(localize('coverage.branches', '{0} of {1} of branches in {2} were covered.', covered, detail.branches.length, text)); + } else { + str.appendMarkdown(localize('coverage.codeExecutedCount', '{0} was executed {1} time(s).', text, detail.count)); + } + return str; + } else if (detail.type === DetailType.Branch) { + const text = wrapName(model.getValueInRange(tidyLocation(detail.detail.location)).trim() || ``); + const { count, label } = detail.detail.branches![detail.branch]; + const label2 = label ? wrapInBackticks(label) : `#${detail.branch + 1}`; + if (count === 0) { + return new MarkdownString().appendMarkdown(localize('coverage.branchNotCovered', 'Branch {0} in {1} was not covered.', label2, text)); + } else { + return new MarkdownString().appendMarkdown(localize('coverage.branchCovered', 'Branch {0} in {1} was executed {2} time(s).', label2, text, count)); + } + } + + return undefined; + } } // 'tidies' the range by normalizing it into a range and removing leading @@ -318,7 +459,7 @@ function tidyLocation(location: Range | Position): Range { class LineHoverComputer implements IHoverComputer { public line = -1; - public lineContents = ''; + public textModel!: ITextModel; public details!: CoverageDetailsModel; constructor(@IKeybindingService private readonly keybindingService: IKeybindingService) { } @@ -341,24 +482,7 @@ class LineHoverComputer implements IHoverComputer { bestDetails.push(detail); } - const strs = bestDetails.map(({ range, metadata: detail }) => { - if (detail.type === DetailType.Function) { - return new MarkdownString().appendMarkdown(localize('coverage.fnExecutedCount', 'Function `{0}` was executed {1} time(s).', detail.name, detail.count)); - } else if (detail.type === DetailType.Statement) { - const text = normalizeName(this.lineContents.slice(range.startColumn - 1, range.endLineNumber === range.startLineNumber ? range.endColumn + 1 : undefined) || ``); - const str = new MarkdownString(); - if (detail.branches?.length) { - const covered = detail.branches.filter(b => b.count > 0).length; - str.appendMarkdown(localize('coverage.branches', '{0} of {1} of branches in `{2}` were covered.', covered, detail.branches.length, text)); - } else { - str.appendMarkdown(localize('coverage.codeExecutedCount', '`{0}` was executed {1} time(s).', text, detail.count)); - } - return str; - } else { - return undefined; - } - }).filter(isDefined); - + const strs = bestDetails.map(d => d.metadata.detail.type === DetailType.Branch ? undefined : d.metadata.description).filter(isDefined); if (strs.length) { const s = new MarkdownString().appendMarkdown(`[${TOGGLE_INLINE_COMMAND_TEXT}](command:${TOGGLE_INLINE_COMMAND_ID})`); s.isTrusted = true; @@ -373,12 +497,15 @@ class LineHoverComputer implements IHoverComputer { } } -function normalizeName(functionNameOrCode: string) { - functionNameOrCode = functionNameOrCode.replace(/[\n\r`]/g, ''); +function wrapInBackticks(str: string) { + return '`' + str.replace(/[\n\r`]/g, '') + '`'; +} + +function wrapName(functionNameOrCode: string) { if (functionNameOrCode.length > 50) { functionNameOrCode = functionNameOrCode.slice(0, 40) + '...'; } - return functionNameOrCode; + return wrapInBackticks(functionNameOrCode); } class LineHoverWidget extends Disposable implements IOverlayWidget { @@ -428,12 +555,17 @@ class LineHoverWidget extends Disposable implements IOverlayWidget { } /** Shows the hover widget at the given line */ - public startShowingAt(lineNumber: number, details: CoverageDetailsModel) { + public startShowingAt(lineNumber: number, details: CoverageDetailsModel, showImmediate: boolean) { this.hide(); + const textModel = this.editor.getModel(); + if (!textModel) { + return; + } + this.computer.line = lineNumber; - this.computer.lineContents = this.editor.getModel()?.getLineContent(lineNumber) || ''; + this.computer.textModel = textModel; this.computer.details = details; - this.hoverOperation.start(HoverStartMode.Delayed); + this.hoverOperation.start(showImmediate ? HoverStartMode.Immediate : HoverStartMode.Delayed); } /** Hides the hover widget */ diff --git a/src/vs/workbench/contrib/testing/browser/icons.ts b/src/vs/workbench/contrib/testing/browser/icons.ts index 455f07ec53a..e237f5afbaa 100644 --- a/src/vs/workbench/contrib/testing/browser/icons.ts +++ b/src/vs/workbench/contrib/testing/browser/icons.ts @@ -39,6 +39,7 @@ export const testingCancelRefreshTests = registerIcon('testing-cancel-refresh-te export const testingCoverageReport = registerIcon('testing-coverage', Codicon.lightBulb, localize('testingCoverage', 'Icon representing test coverage')); export const testingWasCovered = registerIcon('testing-was-covered', Codicon.check, localize('testingWasCovered', 'Icon representing that an element was covered')); +export const testingCoverageMissingBranch = registerIcon('testing-missing-branch', Codicon.question, localize('testingMissingBranch', 'Icon representing a uncovered block without a range')); export const testingStatesToIcons = new Map([ [TestResultState.Errored, registerIcon('testing-error-icon', Codicon.issues, localize('testingErrorIcon', 'Icon shown for tests that have an error.'))], diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index f2c0c7d15e7..f0cf2ea8a49 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -461,3 +461,23 @@ .coverage-deco-inline.coverage-deco-miss { background: var(--vscode-testing-uncoveredBackground); } + +.coverage-deco-branch-miss-indicator { + height: 100%; + width: 4ch; + position: relative; + display: inline-block; + font: inherit !important; +} + +.coverage-deco-branch-miss-indicator::before { + position: absolute; + top: 50%; + left: 50%; + text-align: center; + transform: translate(-50%, -50%); + padding: calc(var(--vscode-testing-coverage-lineHeight) / 10); + border-radius: 2px; + font: normal normal normal calc(var(--vscode-testing-coverage-lineHeight) / 2)/1 codicon; + border: 1px solid; +} diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index 5a886c371ca..b23a81c7b97 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -30,7 +30,6 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { FocusedViewContext } from 'vs/workbench/common/contextkeys'; import { ViewContainerLocation } from 'vs/workbench/common/views'; -import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { VIEWLET_ID as EXTENSIONS_VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions'; import { TestExplorerTreeElement, TestItemTreeElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/index'; import * as icons from 'vs/workbench/contrib/testing/browser/icons'; @@ -51,6 +50,7 @@ import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingP import { isFailedState } from 'vs/workbench/contrib/testing/common/testingStates'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; const category = Categories.Test; @@ -1611,18 +1611,22 @@ export class CancelTestRefreshAction extends Action2 { } } -export class TestCoverageClose extends Action2 { +export class CloseCoverage extends Action2 { constructor() { super({ id: TestCommandId.CoverageClose, title: localize2('testing.closeCoverage', 'Close Coverage'), icon: widgetClose, - menu: { + category, + menu: [{ id: MenuId.ViewTitle, group: 'navigation', order: ActionOrder.Refresh, when: ContextKeyExpr.equals('view', Testing.CoverageViewId) - } + }, { + id: MenuId.CommandPalette, + when: TestingContextKeys.isTestCoverageOpen.isEqualTo(true), + }] }); } @@ -1631,10 +1635,37 @@ export class TestCoverageClose extends Action2 { } } +export class OpenCoverage extends Action2 { + constructor() { + super({ + id: TestCommandId.OpenCoverage, + title: localize2('testing.openCoverage', 'Open Coverage'), + category, + menu: [{ + id: MenuId.CommandPalette, + when: TestingContextKeys.hasAnyResults.isEqualTo(true), + }] + }); + } + + public override run(accessor: ServicesAccessor) { + const results = accessor.get(ITestResultService).results; + const task = results.length && results[0].tasks.find(r => r.coverage); + if (!task) { + const notificationService = accessor.get(INotificationService); + notificationService.info(localize('testing.noCoverage', 'No coverage information available on the last test run.')); + return; + } + + accessor.get(ITestCoverageService).openCoverage(task, true); + } +} + export const allTestActions = [ CancelTestRefreshAction, CancelTestRunAction, ClearTestResultsAction, + CloseCoverage, CollapseAllAction, ConfigureTestProfilesAction, ContinuousRunTestAction, @@ -1658,6 +1689,7 @@ export const allTestActions = [ GetSelectedProfiles, GoToTest, HideTestAction, + OpenCoverage, OpenOutputPeek, RefreshTestsAction, ReRunFailedTests, @@ -1674,7 +1706,6 @@ export const allTestActions = [ ShowMostRecentOutputAction, StartContinuousRunAction, StopContinuousRunAction, - TestCoverageClose, TestingSortByDurationAction, TestingSortByLocationAction, TestingSortByStatusAction, diff --git a/src/vs/workbench/contrib/testing/browser/theme.ts b/src/vs/workbench/contrib/testing/browser/theme.ts index 24654b3523f..a7c8cd7c553 100644 --- a/src/vs/workbench/contrib/testing/browser/theme.ts +++ b/src/vs/workbench/contrib/testing/browser/theme.ts @@ -5,7 +5,7 @@ import { Color, RGBA } from 'vs/base/common/color'; import { localize } from 'vs/nls'; -import { contrastBorder, diffInserted, diffRemoved, editorErrorForeground, editorForeground, editorInfoForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; +import { contrastBorder, diffInserted, diffRemoved, editorBackground, editorErrorForeground, editorForeground, editorInfoForeground, opaque, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { TestMessageType, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes'; @@ -94,12 +94,19 @@ export const testingCoveredBackground = registerColor('testing.coveredBackground }, localize('testing.coveredBackground', 'Background color of text that was covered.')); export const testingCoveredGutterBackground = registerColor('testing.coveredGutterBackground', { - dark: transparent(diffInserted, 0.8), - light: transparent(diffInserted, 0.8), + dark: transparent(diffInserted, 0.6), + light: transparent(diffInserted, 0.6), hcDark: null, hcLight: null }, localize('testing.coveredGutterBackground', 'Gutter color of regions where code was covered.')); +export const testingUncoveredBranchBackground = registerColor('testing.uncoveredBranchBackground', { + dark: opaque(transparent(diffRemoved, 2), editorBackground), + light: opaque(transparent(diffRemoved, 2), editorBackground), + hcDark: null, + hcLight: null +}, localize('testing.uncoveredBranchBackground', 'Background of the widget shown for an uncovered branch.')); + export const testingUncoveredBackground = registerColor('testing.uncoveredBackground', { dark: diffRemoved, light: diffRemoved, @@ -157,12 +164,27 @@ export const testStatesToIconColors: { [K in TestResultState]?: string } = { registerThemingParticipant((theme, collector) => { + const editorBg = theme.getColor(editorBackground); + const missBadgeBackground = editorBg && theme.getColor(testingUncoveredBackground)?.transparent(2).makeOpaque(editorBg); + collector.addRule(` .coverage-deco-inline.coverage-deco-hit { outline: 1px solid ${theme.getColor(testingCoveredBackground)?.transparent(0.75)}; } + .coverage-deco-inline.coverage-deco-hit.coverage-deco-hovered { + background: ${theme.getColor(testingCoveredBackground)?.transparent(1.3)}; + outline: 1px solid ${theme.getColor(testingCoveredBackground)?.transparent(2)}; + } .coverage-deco-inline.coverage-deco-miss { outline: 1px solid ${theme.getColor(testingUncoveredBackground)?.transparent(0.75)}; } + .coverage-deco-inline.coverage-deco-miss.coverage-deco-hovered { + background: ${theme.getColor(testingUncoveredBackground)?.transparent(1.3)}; + outline: 1px solid ${theme.getColor(testingUncoveredBackground)?.transparent(2)}; + } + .coverage-deco-branch-miss-indicator::before { + border-color: ${missBadgeBackground?.transparent(1.3)}; + background-color: ${missBadgeBackground}; + } `); }); diff --git a/src/vs/workbench/contrib/testing/common/constants.ts b/src/vs/workbench/contrib/testing/common/constants.ts index 08875ef9b98..d05d75c4d65 100644 --- a/src/vs/workbench/contrib/testing/common/constants.ts +++ b/src/vs/workbench/contrib/testing/common/constants.ts @@ -80,6 +80,7 @@ export const enum TestCommandId { GoToTest = 'testing.editFocusedTest', HideTestAction = 'testing.hideTest', OpenOutputPeek = 'testing.openOutputPeek', + OpenCoverage = 'testing.openCoverage', RefreshTestsAction = 'testing.refreshTests', ReRunFailedTests = 'testing.reRunFailTests', ReRunLastRun = 'testing.reRunLastRun', diff --git a/src/vs/workbench/contrib/testing/common/testCoverage.ts b/src/vs/workbench/contrib/testing/common/testCoverage.ts index 5dad3a5fa58..7545c7c3da6 100644 --- a/src/vs/workbench/contrib/testing/common/testCoverage.ts +++ b/src/vs/workbench/contrib/testing/common/testCoverage.ts @@ -34,6 +34,8 @@ export class TestCoverage { return this._tree ??= this.buildCoverageTree(); } + public readonly associatedData = new Map(); + constructor( public readonly fromTaskId: string, private readonly fileCoverage: ResourceMap, diff --git a/src/vs/workbench/contrib/testing/common/testTypes.ts b/src/vs/workbench/contrib/testing/common/testTypes.ts index 11ea5b4d9a9..31eaf041f11 100644 --- a/src/vs/workbench/contrib/testing/common/testTypes.ts +++ b/src/vs/workbench/contrib/testing/common/testTypes.ts @@ -615,12 +615,14 @@ export namespace CoverageDetails { export interface IBranchCoverage { count: number; + label?: string; location?: Range | Position; } export namespace IBranchCoverage { export interface Serialized { count: number; + label?: string; location?: IRange | IPosition; } diff --git a/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_3.0.snap b/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_3.0.snap index 9cfb71a8f91..48ea7252e5a 100644 --- a/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_3.0.snap +++ b/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_3.0.snap @@ -14,9 +14,5 @@ { range: "[4,0 -> 5,0]", count: 3 - }, - { - range: "[5,0 -> 5,0]", - count: 1 } ] \ No newline at end of file diff --git a/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_4.0.snap b/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_4.0.snap index a11f50d03c1..a7523744be8 100644 --- a/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_4.0.snap +++ b/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_4.0.snap @@ -14,9 +14,5 @@ { range: "[4,0 -> 5,0]", count: 3 - }, - { - range: "[5,0 -> 5,0]", - count: 1 } ] \ No newline at end of file diff --git a/src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts b/src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts index 10c2f7bb901..38c00c2fb3b 100644 --- a/src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts +++ b/src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts @@ -8,12 +8,19 @@ import { assertSnapshot } from 'vs/base/test/common/snapshot'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { ITextModel } from 'vs/editor/common/model'; import { CoverageDetailsModel } from 'vs/workbench/contrib/testing/browser/codeCoverageDecorations'; import { CoverageDetails, DetailType } from 'vs/workbench/contrib/testing/common/testTypes'; suite('Code Coverage Decorations', () => { ensureNoDisposablesAreLeakedInTestSuite(); + const textModel = { getValueInRange: () => '' } as any as ITextModel; + const assertRanges = async (model: CoverageDetailsModel) => await assertSnapshot(model.ranges.map(r => ({ + range: r.range.toString(), + count: r.metadata.detail.type === DetailType.Branch ? r.metadata.detail.detail.branches![r.metadata.detail.branch].count : r.metadata.detail.count, + }))); + test('CoverageDetailsModel#1', async () => { // Create some sample coverage details const details: CoverageDetails[] = [ @@ -23,13 +30,10 @@ suite('Code Coverage Decorations', () => { ]; // Create a new CoverageDetailsModel instance - const model = new CoverageDetailsModel(details); + const model = new CoverageDetailsModel(details, textModel); // Verify that the ranges are generated correctly - await assertSnapshot(model.ranges.map(r => ({ - range: r.range.toString(), - count: r.metadata.type === DetailType.Branch ? r.metadata.detail.branches![r.metadata.branch].count : r.metadata.count, - }))); + await assertRanges(model); }); test('CoverageDetailsModel#2', async () => { @@ -41,13 +45,10 @@ suite('Code Coverage Decorations', () => { ]; // Create a new CoverageDetailsModel instance - const model = new CoverageDetailsModel(details); + const model = new CoverageDetailsModel(details, textModel); // Verify that the ranges are generated correctly - await assertSnapshot(model.ranges.map(r => ({ - range: r.range.toString(), - count: r.metadata.type === DetailType.Branch ? r.metadata.detail.branches![r.metadata.branch].count : r.metadata.count, - }))); + await assertRanges(model); }); test('CoverageDetailsModel#3', async () => { @@ -59,13 +60,10 @@ suite('Code Coverage Decorations', () => { ]; // Create a new CoverageDetailsModel instance - const model = new CoverageDetailsModel(details); + const model = new CoverageDetailsModel(details, textModel); // Verify that the ranges are generated correctly - await assertSnapshot(model.ranges.map(r => ({ - range: r.range.toString(), - count: r.metadata.type === DetailType.Branch ? r.metadata.detail.branches![r.metadata.branch].count : r.metadata.count, - }))); + await assertRanges(model); }); test('CoverageDetailsModel#4', async () => { @@ -78,12 +76,9 @@ suite('Code Coverage Decorations', () => { ]; // Create a new CoverageDetailsModel instance - const model = new CoverageDetailsModel(details); + const model = new CoverageDetailsModel(details, textModel); // Verify that the ranges are generated correctly - await assertSnapshot(model.ranges.map(r => ({ - range: r.range.toString(), - count: r.metadata.type === DetailType.Branch ? r.metadata.detail.branches![r.metadata.branch].count : r.metadata.count, - }))); + await assertRanges(model); }); }); diff --git a/src/vscode-dts/vscode.proposed.testCoverage.d.ts b/src/vscode-dts/vscode.proposed.testCoverage.d.ts index ec855c7dbdd..db0b52c4731 100644 --- a/src/vscode-dts/vscode.proposed.testCoverage.d.ts +++ b/src/vscode-dts/vscode.proposed.testCoverage.d.ts @@ -164,11 +164,17 @@ declare module 'vscode' { */ location?: Position | Range; + /** + * Label for the branch, used in the context of "the ${label} branch was + * not taken," for example. + */ + label?: string; + /** * @param executionCount The number of times this branch was executed. * @param location The branch position. */ - constructor(executionCount: number, location?: Position | Range); + constructor(executionCount: number, location?: Position | Range, label?: string); } /** From bc94d88d66b650b98cdf837cb721df2b9059fd21 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 13 Jan 2024 21:20:02 +0100 Subject: [PATCH 0293/1897] Adopt ensureNoDisposablesAreLeakedInTestSuite round 2 (#200091) (#202399) --- .eslintrc.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 196a7dcf334..3b616d8a44e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -148,11 +148,9 @@ { // Files should (only) be removed from the list they adopt the leak detector "exclude": [ - "src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts", "src/vs/base/test/browser/browser.test.ts", "src/vs/base/test/browser/comparers.test.ts", "src/vs/base/test/browser/hash.test.ts", - "src/vs/base/test/browser/indexedDB.test.ts", "src/vs/base/test/browser/ui/scrollbar/scrollableElement.test.ts", "src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts", "src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts", From 12443ca030f93843ca5e9dfb5682ad92d2c6400c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 13 Jan 2024 21:20:40 +0100 Subject: [PATCH 0294/1897] hover - reduce flicker when hover appears on element click (#202401) * hover - reduce flicker when hover appears on element click * fix it --- src/vs/base/browser/ui/iconLabel/iconLabelHover.ts | 4 ++-- src/vs/editor/browser/services/hoverService.ts | 12 +++++++----- src/vs/platform/hover/browser/hover.ts | 7 +++++++ .../browser/parts/statusbar/statusbarItem.ts | 4 ++-- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts index 9baf56005ee..5f1947e1410 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts @@ -47,7 +47,7 @@ interface IHoverAction { export interface IUpdatableHoverOptions { actions?: IHoverAction[]; linkHandler?(url: string): void; - disableHideOnMouseDown?: boolean; + disableHideOnClick?: boolean; } export interface ICustomHover extends IDisposable { @@ -195,7 +195,7 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM let isMouseDown = false; const mouseDownEmitter = dom.addDisposableListener(htmlElement, dom.EventType.MOUSE_DOWN, () => { isMouseDown = true; - if (!options?.disableHideOnMouseDown) { + if (!options?.disableHideOnClick) { hideHover(true, true); } }, true); diff --git a/src/vs/editor/browser/services/hoverService.ts b/src/vs/editor/browser/services/hoverService.ts index 73e3a819587..3551e5205bd 100644 --- a/src/vs/editor/browser/services/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService.ts @@ -97,12 +97,14 @@ export class HoverService implements IHoverService { } })); } else { - if ('targetElements' in options.target) { - for (const element of options.target.targetElements) { - hoverDisposables.add(addDisposableListener(element, EventType.CLICK, () => this.hideHover())); + if (!options.disableHideOnClick) { + if ('targetElements' in options.target) { + for (const element of options.target.targetElements) { + hoverDisposables.add(addDisposableListener(element, EventType.CLICK, () => this.hideHover())); + } + } else { + hoverDisposables.add(addDisposableListener(options.target, EventType.CLICK, () => this.hideHover())); } - } else { - hoverDisposables.add(addDisposableListener(options.target, EventType.CLICK, () => this.hideHover())); } const focusedElement = getActiveElement(); if (focusedElement) { diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 5f3bb1e3317..7bec87eb176 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -114,6 +114,13 @@ export interface IHoverOptions { * Options that define how the hover looks. */ appearance?: IHoverAppearanceOptions; + + /** + * Option to disable automated hiding of the hover when clicking its + * target element. This should be set when clicking the target element + * shows the hover to prevent flicker. + */ + disableHideOnClick?: boolean; } export interface IHoverPositionOptions { diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts index 4b771c3f7e1..a0388947c25 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts @@ -119,9 +119,9 @@ export class StatusbarEntryItem extends Disposable { const hoverContents = isMarkdownString(entry.tooltip) ? { markdown: entry.tooltip, markdownNotSupportedFallback: undefined } : entry.tooltip; const entryOpensTooltip = entry.command === ShowTooltipCommand; if (this.hover) { - this.hover.update(hoverContents, { disableHideOnMouseDown: entryOpensTooltip }); + this.hover.update(hoverContents, { disableHideOnClick: entryOpensTooltip }); } else { - this.hover = this._register(setupCustomHover(this.hoverDelegate, this.container, hoverContents, { disableHideOnMouseDown: entryOpensTooltip })); + this.hover = this._register(setupCustomHover(this.hoverDelegate, this.container, hoverContents, { disableHideOnClick: entryOpensTooltip })); } this.focusListener.value = addDisposableListener(this.labelContainer, EventType.FOCUS, (e) => { EventHelper.stop(e); From 9595f43c80bfac511c809400f55ea4a43c2bfb94 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 14 Jan 2024 12:53:45 -0300 Subject: [PATCH 0295/1897] Fix chat input history navigation Fix microsoft/vscode-copilot-release#661 --- .../contrib/chat/browser/chatInputPart.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index dec5a7301e2..6261ecd5bdb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -86,6 +86,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private historyStates: Map = new Map(); private historyNavigationBackwardsEnablement!: IContextKey; private historyNavigationForewardsEnablement!: IContextKey; + private onHistoryEntry = false; private inputModel: ITextModel | undefined; private inputEditorHasText: IContextKey; private chatCursorAtTop: IContextKey; @@ -161,6 +162,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge (this.history.previous() ?? this.history.first()) : this.history.next()) ?? ''; + this.onHistoryEntry = previous || this.history.current() !== null; + aria.status(historyInput); this.setValue(historyInput); this._onDidLoadInputState.fire(this.historyStates.get(historyInput)); @@ -272,6 +275,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const model = this._inputEditor.getModel(); const inputHasText = !!model && model.getValueLength() > 0; this.inputEditorHasText.set(inputHasText); + if (!this.onHistoryEntry) { + this.historyNavigationForewardsEnablement.set(!inputHasText); + this.historyNavigationBackwardsEnablement.set(!inputHasText); + } })); this._register(this._inputEditor.onDidFocusEditorText(() => { this._onDidFocus.fire(); @@ -289,9 +296,12 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } const atTop = e.position.column === 1 && e.position.lineNumber === 1; - this.historyNavigationBackwardsEnablement.set(atTop); this.chatCursorAtTop.set(atTop); - this.historyNavigationForewardsEnablement.set(e.position.equals(getLastPosition(model))); + + if (this.onHistoryEntry) { + this.historyNavigationBackwardsEnablement.set(atTop); + this.historyNavigationForewardsEnablement.set(e.position.equals(getLastPosition(model))); + } })); this.toolbar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, inputContainer, MenuId.ChatExecute, { From eadc9145e995a8b1f4ae73b33001aac8407f8ce9 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 14 Jan 2024 12:59:59 -0300 Subject: [PATCH 0296/1897] Use full history entry object in history navigator --- .../contrib/chat/browser/chatInputPart.ts | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 6261ecd5bdb..ce8a6081bea 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -39,7 +39,7 @@ import { CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_ import { chatAgentLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatReplyFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; -import { IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService'; +import { IChatHistoryEntry, IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService'; import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; import { ChatSubmitEditorAction, ChatSubmitSecondaryAgentEditorAction } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IPosition } from 'vs/editor/common/core/position'; @@ -82,8 +82,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return this._inputEditor; } - private history: HistoryNavigator; - private historyStates: Map = new Map(); + private history: HistoryNavigator; private historyNavigationBackwardsEnablement!: IContextKey; private historyNavigationForewardsEnablement!: IContextKey; private onHistoryEntry = false; @@ -134,11 +133,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge setState(providerId: string, inputValue: string | undefined): void { this.providerId = providerId; const history = this.historyService.getHistory(providerId); - this.historyStates = new Map(history.map(h => [h.text, h.state])); - const historyTexts: string[] = []; - this.historyStates.forEach((_, str) => historyTexts.push(str)); - - this.history = new HistoryNavigator(historyTexts, 50); + this.history = new HistoryNavigator(history, 50); if (typeof inputValue === 'string') { this.setValue(inputValue); @@ -158,15 +153,15 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } private navigateHistory(previous: boolean): void { - const historyInput = (previous ? + const historyEntry = (previous ? (this.history.previous() ?? this.history.first()) : this.history.next()) - ?? ''; + ?? { text: '' }; this.onHistoryEntry = previous || this.history.current() !== null; - aria.status(historyInput); - this.setValue(historyInput); - this._onDidLoadInputState.fire(this.historyStates.get(historyInput)); + aria.status(historyEntry.text); + this.setValue(historyEntry.text); + this._onDidLoadInputState.fire(historyEntry.state); if (previous) { this._inputEditor.setPosition({ lineNumber: 1, column: 1 }); } else { @@ -199,8 +194,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge */ async acceptInput(userQuery?: string, inputState?: any): Promise { if (userQuery) { - this.history.add(userQuery); - this.historyStates.set(userQuery, inputState); + this.history.add({ text: userQuery, state: inputState }); } if (this.accessibilityService.isScreenReaderOptimized() && isMacintosh) { @@ -391,8 +385,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge saveState(): void { const inputHistory = this.history.getHistory(); - const historyEntries = inputHistory.map(entry => ({ text: entry, state: this.historyStates.get(entry) })); - this.historyService.saveHistory(this.providerId!, historyEntries); + this.historyService.saveHistory(this.providerId!, inputHistory); } } From b8683a4512407e26475160f4aed78dc335a4fdea Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 14 Jan 2024 17:26:25 +0100 Subject: [PATCH 0297/1897] zoom - update status entry controls visuals (#202440) --- .../electron-sandbox/media/window.css | 33 ++++++++-- src/vs/workbench/electron-sandbox/window.ts | 66 ++++++++++++------- 2 files changed, 68 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/electron-sandbox/media/window.css b/src/vs/workbench/electron-sandbox/media/window.css index fbfcd3c97bf..4baf8a5f2a4 100644 --- a/src/vs/workbench/electron-sandbox/media/window.css +++ b/src/vs/workbench/electron-sandbox/media/window.css @@ -5,16 +5,37 @@ .monaco-workbench .zoom-status { display: flex; - padding: 2px 8px; - vertical-align: middle; + padding-top: 2px; + padding-bottom: 2px; + padding-left: 8px; + padding-right: 2px; } -.monaco-workbench .zoom-status > .left { +.monaco-workbench .zoom-status .action-label.codicon::before { + /* + * the hover sets font-size: inherit which makes actions + * have a size of 13px. but codicons have a size of 16px + * so we want to ensure the icon is still centered + */ + margin: auto; +} + +.monaco-workbench .zoom-status > .zoom-status-left { + display: flex; margin-top: auto; margin-bottom: auto; - margin-right: 20px; + margin-right: 8px; } -.monaco-workbench .zoom-status > .right { - margin: auto 0; +.monaco-workbench .zoom-status > .zoom-status-center { + display: flex; + margin-top: auto; + margin-bottom: auto; +} + +.monaco-workbench .zoom-status > .zoom-status-right { + display: flex; + margin-top: auto; + margin-bottom: auto; + margin-left: 8px; } diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 68a0a37c7ef..dc20ed5fd16 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -18,7 +18,7 @@ import { WindowMinimumSize, IOpenFileRequest, getTitleBarStyle, IAddFoldersReque import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ApplyZoomTarget, applyZoom } from 'vs/platform/window/electron-sandbox/window'; -import { setFullscreen, getZoomLevel, onDidChangeZoomLevel } from 'vs/base/browser/browser'; +import { setFullscreen, getZoomLevel, onDidChangeZoomLevel, getZoomFactor } from 'vs/base/browser/browser'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { ipcRenderer, process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; @@ -1116,7 +1116,7 @@ export class NativeWindow extends BaseWindow { class ZoomStatusEntry extends Disposable { private readonly disposable = this._register(new MutableDisposable()); - private labelContainer: HTMLElement | undefined = undefined; + private resetZoomAction: Action | undefined = undefined; constructor( @IStatusbarService private readonly statusbarService: IStatusbarService, @@ -1130,7 +1130,7 @@ class ZoomStatusEntry extends Disposable { if (!this.disposable.value) { this.createZoomEntry(targetWindowId, visibleOrText); } else { - this.updateZoomEntryLabel(targetWindowId); + this.updateZoomResetAction(targetWindowId); } } else { this.disposable.clear(); @@ -1140,45 +1140,61 @@ class ZoomStatusEntry extends Disposable { const disposables = new DisposableStore(); this.disposable.value = disposables; - const element = document.createElement('div'); - element.classList.add('zoom-status'); + const container = document.createElement('div'); + container.classList.add('zoom-status'); - this.labelContainer = document.createElement('div'); - element.appendChild(this.labelContainer); - disposables.add(toDisposable(() => this.labelContainer = undefined)); - this.labelContainer.classList.add('left'); - this.updateZoomEntryLabel(targetWindowId); + const left = document.createElement('div'); + left.classList.add('zoom-status-left'); + left.textContent = localize('zoomLabel', "Zoom"); + container.appendChild(left); - const actionsContainer = document.createElement('div'); - actionsContainer.classList.add('right'); - element.appendChild(actionsContainer); + const center = document.createElement('div'); + center.classList.add('zoom-status-center'); + container.appendChild(center); - const actionBar = disposables.add(new ActionBar(actionsContainer, {})); + const actionBarZoom = disposables.add(new ActionBar(center)); + actionBarZoom.push( + disposables.add(new Action('zoomOut', localize('zoomOut', "Zoom Out"), ThemeIcon.asClassName(Codicon.remove), true, () => this.commandService.executeCommand('workbench.action.zoomOut'))), + { icon: true, label: false } + ); - actionBar.push([ - disposables.add(new Action('zoomOut', localize('zoomOut', "Zoom Out"), ThemeIcon.asClassName(Codicon.zoomOut), true, () => this.commandService.executeCommand('workbench.action.zoomOut'))), - disposables.add(new Action('zoomIn', localize('zoomIn', "Zoom In"), ThemeIcon.asClassName(Codicon.zoomIn), true, () => this.commandService.executeCommand('workbench.action.zoomIn'))) - ], { icon: true, label: false }); + this.resetZoomAction = disposables.add(new Action('zoomReset', '', undefined, true, async () => this.commandService.executeCommand('workbench.action.zoomReset'))); + this.updateZoomResetAction(targetWindowId); + disposables.add(toDisposable(() => this.resetZoomAction = undefined)); + this.resetZoomAction.tooltip = localize('resetZoom', "Reset Zoom"); + actionBarZoom.push(this.resetZoomAction, { icon: false, label: true }); - actionBar.push( - disposables.add(new Action('zoomReset', localize('zoomReset', "Reset"), undefined, true, async () => this.commandService.executeCommand('workbench.action.zoomReset'))), - { icon: false, label: true } + actionBarZoom.push( + disposables.add(new Action('zoomIn', localize('zoomIn', "Zoom In"), ThemeIcon.asClassName(Codicon.plus), true, () => this.commandService.executeCommand('workbench.action.zoomIn'))), + { icon: true, label: false } + ); + + const right = document.createElement('div'); + right.classList.add('zoom-status-right'); + container.appendChild(right); + + const actionBarSettings = disposables.add(new ActionBar(right)); + actionBarSettings.push( + disposables.add(new Action('zoomSettings', localize('zoomSettings', "Settings"), ThemeIcon.asClassName(Codicon.settingsGear), true, () => this.commandService.executeCommand('workbench.action.openSettings', 'window.zoom'))), + { icon: true, label: false } ); const name = localize('status.windowZoom', "Window Zoom"); disposables.add(this.statusbarService.addEntry({ name, text: visibleOrText, - tooltip: element, + tooltip: container, ariaLabel: name, command: ShowTooltipCommand, kind: 'prominent' }, 'status.windowZoom', StatusbarAlignment.RIGHT, 102)); + + console.log(container); } - private updateZoomEntryLabel(targetWindowId: number): void { - if (this.labelContainer) { - this.labelContainer.textContent = localize('zoomLevel', "Zoom Level: {0}", getZoomLevel(getWindowById(targetWindowId)?.window ?? mainWindow)); + private updateZoomResetAction(targetWindowId: number): void { + if (this.resetZoomAction) { + this.resetZoomAction.label = localize('zoomLevel', "{0}%", Math.round(getZoomFactor(getWindowById(targetWindowId)?.window ?? mainWindow) * 100)); } } } From 0a85581f1dabc99322696fb59ec7020c759e177b Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 14 Jan 2024 13:57:48 -0300 Subject: [PATCH 0298/1897] Some chat session improvements Use "chat" word instead of "session", and use "new chat" instead of "clear". Add "Clear all chats" command to delete all history microsoft/vscode-copilot-release#725 --- .../chat/browser/actions/chatActions.ts | 32 ++++++++++++------- .../chat/browser/actions/chatClearActions.ts | 28 ++++++++-------- .../chat/browser/actions/chatMoveActions.ts | 16 +++++----- .../contrib/chat/browser/chat.contribution.ts | 12 +++---- .../browser/chatContributionServiceImpl.ts | 4 +-- .../contrib/chat/common/chatService.ts | 1 + .../contrib/chat/common/chatServiceImpl.ts | 8 ++++- 7 files changed, 59 insertions(+), 42 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index f38834e7e49..6da0b90162a 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -155,14 +155,11 @@ export function registerChatActions() { registerAction2(ChatSubmitSecondaryAgentEditorAction); - registerAction2(class ClearChatHistoryAction extends Action2 { + registerAction2(class ClearChatInputHistoryAction extends Action2 { constructor() { super({ - id: 'workbench.action.chatEditor.clearHistory', - title: { - value: localize('interactiveSession.clearHistory.label', "Clear Input History"), - original: 'Clear Input History' - }, + id: 'workbench.action.chat.clearInputHistory', + title: localize2('interactiveSession.clearHistory.label', "Clear Input History"), precondition: CONTEXT_PROVIDER_EXISTS, category: CHAT_CATEGORY, f1: true, @@ -174,6 +171,22 @@ export function registerChatActions() { } }); + registerAction2(class ClearChatHistoryAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.chat.clearHistory', + title: localize2('chat.clear.label', "Clear All Workspace Chats"), + precondition: CONTEXT_PROVIDER_EXISTS, + category: CHAT_CATEGORY, + f1: true, + }); + } + async run(accessor: ServicesAccessor, ...args: any[]) { + const chatService = accessor.get(IChatService); + chatService.clearAllHistoryEntries(); + } + }); + registerAction2(class FocusChatAction extends EditorAction2 { constructor() { super({ @@ -257,10 +270,7 @@ export function getOpenChatEditorAction(id: string, label: string, when?: string const getHistoryChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly & { viewId: string } => ({ viewId, id: `workbench.action.chat.${providerId}.history`, - title: { - value: localize('interactiveSession.history.label', "Show History"), - original: 'Show History' - }, + title: localize2('chat.history.label', "Show Chats"), menu: { id: MenuId.ViewTitle, when: ContextKeyExpr.equals('view', viewId), @@ -295,7 +305,7 @@ export function getHistoryAction(viewId: string, providerId: string) { })); const selection = await quickInputService.pick(picks, { - placeHolder: localize('interactiveSession.history.pick', "Switch to chat session"), + placeHolder: localize('interactiveSession.history.pick', "Switch to chat"), onDidTriggerItemButton: context => { chatService.removeHistoryEntry(context.item.chat.sessionId); context.removeItem(); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index 9e3a418ab6c..c3c1f31f1f1 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -20,15 +20,15 @@ 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 const ACTION_ID_NEW_CHAT = `workbench.action.chat.newChat`; -export function registerClearActions() { +export function registerNewChatActions() { - registerAction2(class ClearEditorAction extends Action2 { + registerAction2(class NewChatEditorAction extends Action2 { constructor() { super({ - id: 'workbench.action.chatEditor.clear', - title: localize2('chat.newSession.label', "New Session"), + id: 'workbench.action.chatEditor.newChat', + title: localize2('chat.newChat.label', "New Chat"), icon: Codicon.plus, f1: false, precondition: CONTEXT_PROVIDER_EXISTS, @@ -50,10 +50,10 @@ export function registerClearActions() { registerAction2(class GlobalClearChatAction extends Action2 { constructor() { super({ - id: ACTION_ID_CLEAR_CHAT, - title: localize2('chat.newSession.label', "New Session"), + id: ACTION_ID_NEW_CHAT, + title: localize2('chat.newChat.label', "New Chat"), category: CHAT_CATEGORY, - icon: Codicon.clearAll, + icon: Codicon.plus, precondition: CONTEXT_PROVIDER_EXISTS, f1: true, keybinding: { @@ -86,10 +86,10 @@ export function registerClearActions() { }); } -const getClearChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly & { viewId: string } => ({ +const getNewChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly & { viewId: string } => ({ viewId, - id: `workbench.action.chat.${providerId}.clear`, - title: localize2('chat.newSession.label', "New Session"), + id: `workbench.action.chat.${providerId}.newChat`, + title: localize2('chat.newChat.label', "New Chat"), menu: { id: MenuId.ViewTitle, when: ContextKeyExpr.equals('view', viewId), @@ -102,10 +102,10 @@ const getClearChatActionDescriptorForViewTitle = (viewId: string, providerId: st f1: false }); -export function getClearAction(viewId: string, providerId: string) { - return class ClearAction extends ViewAction { +export function getNewChatAction(viewId: string, providerId: string) { + return class NewChatAction extends ViewAction { constructor() { - super(getClearChatActionDescriptorForViewTitle(viewId, providerId)); + super(getNewChatActionDescriptorForViewTitle(viewId, providerId)); } async runInView(accessor: ServicesAccessor, view: ChatViewPane) { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts index b2ab5388bab..a9742a70414 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts @@ -29,8 +29,8 @@ enum MoveToNewLocation { const getMoveToChatActionDescriptorForViewTitle = (viewId: string, providerId: string, moveTo: MoveToNewLocation): Readonly & { viewId: string } => ({ id: `workbench.action.chat.${providerId}.openIn${moveTo}`, title: { - value: moveTo === MoveToNewLocation.Editor ? localize('chat.openInEditor.label', "Open Session in Editor") : localize('chat.openInNewWindow.label', "Open Session in New Window"), - original: moveTo === MoveToNewLocation.Editor ? 'Open Session in Editor' : 'Open Session in New Window', + value: moveTo === MoveToNewLocation.Editor ? localize('chat.openInEditor.label', "Open Chat in Editor") : localize('chat.openInNewWindow.label', "Open Chat in New Window"), + original: moveTo === MoveToNewLocation.Editor ? 'Open Chat in Editor' : 'Open Chat in New Window', }, category: CHAT_CATEGORY, precondition: CONTEXT_PROVIDER_EXISTS, @@ -78,8 +78,8 @@ export function registerMoveActions() { super({ id: `workbench.action.chat.openInEditor`, title: { - value: localize('interactiveSession.openInEditor.label', "Open Session in Editor"), - original: 'Open Session in Editor' + value: localize('interactiveSession.openInEditor.label', "Open Chat in Editor"), + original: 'Open Chat in Editor' }, category: CHAT_CATEGORY, precondition: CONTEXT_PROVIDER_EXISTS, @@ -98,8 +98,8 @@ export function registerMoveActions() { super({ id: `workbench.action.chat.openInNewWindow`, title: { - value: localize('interactiveSession.openInNewWindow.label', "Open Session in New Window"), - original: 'Open Session In New Window' + value: localize('interactiveSession.openInNewWindow.label', "Open Chat in New Window"), + original: 'Open Chat In New Window' }, category: CHAT_CATEGORY, precondition: CONTEXT_PROVIDER_EXISTS, @@ -117,8 +117,8 @@ export function registerMoveActions() { super({ id: `workbench.action.chat.openInSidebar`, title: { - value: localize('interactiveSession.openInSidebar.label', "Open Session in Side Bar"), - original: 'Open Session in Side Bar' + value: localize('interactiveSession.openInSidebar.label', "Open Chat in Side Bar"), + original: 'Open Chat in Side Bar' }, category: CHAT_CATEGORY, precondition: CONTEXT_PROVIDER_EXISTS, diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index b316f8d5dfc..8a2486b786e 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 { 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 { ACTION_ID_CLEAR_CHAT, registerClearActions } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; +import { ACTION_ID_NEW_CHAT, registerNewChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; 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'; @@ -227,12 +227,12 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { ) { super(); this._store.add(slashCommandService.registerSlashCommand({ - command: 'clear', - detail: nls.localize('clear', "Clear the session"), - sortText: 'z2_clear', + command: 'newChat', + detail: nls.localize('newChat', "Start a new chat"), + sortText: 'z2_newChat', executeImmediately: true }, async () => { - commandService.executeCommand(ACTION_ID_CLEAR_CHAT); + commandService.executeCommand(ACTION_ID_NEW_CHAT); })); this._store.add(slashCommandService.registerSlashCommand({ command: 'help', @@ -295,7 +295,7 @@ registerChatExecuteActions(); registerQuickChatActions(); registerChatExportActions(); registerMoveActions(); -registerClearActions(); +registerNewChatActions(); registerSingleton(IChatService, ChatService, InstantiationType.Delayed); registerSingleton(IChatContributionService, ChatContributionService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts index 78ff2e9fa61..dd167ee1175 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts @@ -14,7 +14,7 @@ import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneCont import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation, Extensions as ViewExtensions } from 'vs/workbench/common/views'; import { getHistoryAction, getOpenChatEditorAction } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; -import { getClearAction } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; +import { getNewChatAction } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; import { getMoveToEditorAction, getMoveToNewWindowAction } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions'; import { getQuickChatActionForProvider } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions'; import { CHAT_SIDEBAR_PANEL_ID, ChatViewPane, IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane'; @@ -135,7 +135,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { // Actions in view title const disposables = new DisposableStore(); disposables.add(registerAction2(getHistoryAction(viewId, providerDescriptor.id))); - disposables.add(registerAction2(getClearAction(viewId, providerDescriptor.id))); + disposables.add(registerAction2(getNewChatAction(viewId, providerDescriptor.id))); disposables.add(registerAction2(getMoveToEditorAction(viewId, providerDescriptor.id))); disposables.add(registerAction2(getMoveToNewWindowAction(viewId, providerDescriptor.id))); diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 8b2d49825af..4184155f86b 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -314,6 +314,7 @@ export interface IChatService { addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, response: IChatCompleteResponse): void; sendRequestToProvider(sessionId: string, message: IChatDynamicRequest): void; getHistory(): IChatDetail[]; + clearAllHistoryEntries(): void; removeHistoryEntry(sessionId: string): void; onDidPerformUserAction: Event; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 6c873f5a4f5..1de767f97a0 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -135,7 +135,7 @@ export class ChatService extends Disposable implements IChatService { private readonly _sessionModels = this._register(new DisposableMap()); private readonly _pendingRequests = this._register(new DisposableMap()); - private readonly _persistedSessions: ISerializableChatsData; + private _persistedSessions: ISerializableChatsData; private readonly _hasProvider: IContextKey; private _transferredSessionData: IChatTransferredSessionData | undefined; @@ -331,6 +331,12 @@ export class ChatService extends Disposable implements IChatService { removeHistoryEntry(sessionId: string): void { delete this._persistedSessions[sessionId]; + this.saveState(); + } + + clearAllHistoryEntries(): void { + this._persistedSessions = {}; + this.saveState(); } startSession(providerId: string, token: CancellationToken): ChatModel { From 1e0580ec231c33f4867bb4b00694121e64f20052 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 14 Jan 2024 22:49:29 +0530 Subject: [PATCH 0299/1897] Use categories for builtin extensions groups (#202453) * support grouping of extensions * remove grouping * reuse categories parsing * cleanup * fix tests --- extensions/bat/package.json | 1 + extensions/clojure/package.json | 1 + extensions/coffeescript/package.json | 1 + extensions/cpp/package.json | 1 + extensions/csharp/package.json | 1 + extensions/css/package.json | 1 + extensions/dart/package.json | 15 +- extensions/diff/package.json | 1 + extensions/docker/package.json | 1 + extensions/fsharp/package.json | 1 + extensions/go/package.json | 1 + extensions/groovy/package.json | 1 + extensions/handlebars/package.json | 1 + extensions/hlsl/package.json | 1 + extensions/html/package.json | 1 + extensions/ini/package.json | 1 + extensions/java/package.json | 1 + extensions/javascript/package.json | 1 + extensions/json/package.json | 1 + extensions/julia/package.json | 1 + extensions/latex/package.json | 1 + extensions/less/package.json | 1 + extensions/log/package.json | 1 + extensions/lua/package.json | 1 + extensions/make/package.json | 1 + extensions/markdown-basics/package.json | 1 + extensions/markdown-math/package.json | 3 +- extensions/objective-c/package.json | 1 + extensions/perl/package.json | 1 + extensions/php/package.json | 1 + extensions/powershell/package.json | 1 + extensions/pug/package.json | 1 + extensions/python/package.json | 1 + extensions/r/package.json | 1 + extensions/razor/package.json | 1 + extensions/references-view/package.json | 3 - extensions/restructuredtext/package.json | 1 + extensions/ruby/package.json | 1 + extensions/rust/package.json | 1 + extensions/scss/package.json | 1 + extensions/search-result/package.json | 3 - extensions/shaderlab/package.json | 1 + extensions/shellscript/package.json | 1 + extensions/sql/package.json | 1 + extensions/swift/package.json | 1 + extensions/theme-abyss/package.json | 1 + extensions/theme-kimbie-dark/package.json | 1 + extensions/theme-monokai-dimmed/package.json | 1 + extensions/theme-monokai/package.json | 1 + extensions/theme-quietlight/package.json | 1 + extensions/theme-red/package.json | 1 + extensions/theme-seti/package.json | 1 + extensions/theme-solarized-dark/package.json | 1 + extensions/theme-solarized-light/package.json | 1 + .../theme-tomorrow-night-blue/package.json | 1 + extensions/typescript-basics/package.json | 1 + extensions/vb/package.json | 1 + extensions/xml/package.json | 1 + extensions/yaml/package.json | 1 + .../browser/extensions.contribution.ts | 3 +- .../extensions/browser/extensionsViewlet.ts | 22 ++- .../extensions/browser/extensionsViews.ts | 160 ++++++++++-------- .../extensions/common/extensionQuery.ts | 16 +- .../test/common/extensionQuery.test.ts | 44 ++--- .../electron-sandbox/extensionsViews.test.ts | 10 +- 65 files changed, 198 insertions(+), 136 deletions(-) diff --git a/extensions/bat/package.json b/extensions/bat/package.json index f01e5d35ab2..2e654fe10a5 100644 --- a/extensions/bat/package.json +++ b/extensions/bat/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin mmims/language-batchfile grammars/batchfile.cson ./syntaxes/batchfile.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/clojure/package.json b/extensions/clojure/package.json index 4c1dc3db8e9..730594c2ac5 100644 --- a/extensions/clojure/package.json +++ b/extensions/clojure/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin atom/language-clojure grammars/clojure.cson ./syntaxes/clojure.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/coffeescript/package.json b/extensions/coffeescript/package.json index 61f89ecfee1..44f423c7dfc 100644 --- a/extensions/coffeescript/package.json +++ b/extensions/coffeescript/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin atom/language-coffee-script grammars/coffeescript.cson ./syntaxes/coffeescript.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/cpp/package.json b/extensions/cpp/package.json index be5c8902ce5..c1d3f4882f6 100644 --- a/extensions/cpp/package.json +++ b/extensions/cpp/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ./build/update-grammars.js" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/csharp/package.json b/extensions/csharp/package.json index 01d2de34e3e..c8af5a25f1f 100644 --- a/extensions/csharp/package.json +++ b/extensions/csharp/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin dotnet/csharp-tmLanguage grammars/csharp.tmLanguage ./syntaxes/csharp.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "configurationDefaults": { "[csharp]": { diff --git a/extensions/css/package.json b/extensions/css/package.json index 3f2c9f95fcb..711c6f4a41a 100644 --- a/extensions/css/package.json +++ b/extensions/css/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin microsoft/vscode-css grammars/css.cson ./syntaxes/css.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/dart/package.json b/extensions/dart/package.json index 92863ffb2b7..4aea7f98f50 100644 --- a/extensions/dart/package.json +++ b/extensions/dart/package.json @@ -8,9 +8,12 @@ "engines": { "vscode": "0.10.x" }, - "scripts": { - "update-grammar": "node ../node_modules/vscode-grammar-updater/bin dart-lang/dart-syntax-highlight grammars/dart.json ./syntaxes/dart.tmLanguage.json" - }, + "scripts": { + "update-grammar": "node ../node_modules/vscode-grammar-updater/bin dart-lang/dart-syntax-highlight grammars/dart.json ./syntaxes/dart.tmLanguage.json" + }, + "categories": [ + "Programming Languages" + ], "contributes": { "languages": [ { @@ -26,9 +29,9 @@ ], "grammars": [ { - "language": "dart", - "scopeName": "source.dart", - "path": "./syntaxes/dart.tmLanguage.json" + "language": "dart", + "scopeName": "source.dart", + "path": "./syntaxes/dart.tmLanguage.json" } ] } diff --git a/extensions/diff/package.json b/extensions/diff/package.json index 7d23e24ac6b..23047e3a534 100644 --- a/extensions/diff/package.json +++ b/extensions/diff/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin textmate/diff.tmbundle Syntaxes/Diff.plist ./syntaxes/diff.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/docker/package.json b/extensions/docker/package.json index cdda622a22f..9309bd51f9f 100644 --- a/extensions/docker/package.json +++ b/extensions/docker/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin moby/moby contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage ./syntaxes/docker.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/fsharp/package.json b/extensions/fsharp/package.json index c88c1a7f194..1d05eb83de3 100644 --- a/extensions/fsharp/package.json +++ b/extensions/fsharp/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin ionide/ionide-fsgrammar grammars/fsharp.json ./syntaxes/fsharp.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/go/package.json b/extensions/go/package.json index 02a6e32df76..ea0ca792148 100644 --- a/extensions/go/package.json +++ b/extensions/go/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin worlpaker/go-syntax syntaxes/go.tmLanguage.json ./syntaxes/go.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/groovy/package.json b/extensions/groovy/package.json index 1f50ff6caf7..02e1138256f 100644 --- a/extensions/groovy/package.json +++ b/extensions/groovy/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin textmate/groovy.tmbundle Syntaxes/Groovy.tmLanguage ./syntaxes/groovy.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/handlebars/package.json b/extensions/handlebars/package.json index 8e70493f3ca..7e6ecc1b113 100644 --- a/extensions/handlebars/package.json +++ b/extensions/handlebars/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin daaain/Handlebars grammars/Handlebars.json ./syntaxes/Handlebars.tmLanguage.json" }, + "categories": ["Programming Languages"], "extensionKind": [ "ui", "workspace" diff --git a/extensions/hlsl/package.json b/extensions/hlsl/package.json index 046ff9b31c8..ea60ab16483 100644 --- a/extensions/hlsl/package.json +++ b/extensions/hlsl/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin tgjones/shaders-tmLanguage grammars/hlsl.json ./syntaxes/hlsl.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/html/package.json b/extensions/html/package.json index 238f1348955..098f9eae994 100644 --- a/extensions/html/package.json +++ b/extensions/html/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ./build/update-grammar.mjs" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/ini/package.json b/extensions/ini/package.json index a8b332a74ec..3a594569546 100644 --- a/extensions/ini/package.json +++ b/extensions/ini/package.json @@ -12,6 +12,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin textmate/ini.tmbundle Syntaxes/Ini.plist ./syntaxes/ini.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/java/package.json b/extensions/java/package.json index 6788ddd2133..2403b88bf9a 100644 --- a/extensions/java/package.json +++ b/extensions/java/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin redhat-developer/vscode-java language-support/java/java.tmLanguage.json ./syntaxes/java.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/javascript/package.json b/extensions/javascript/package.json index 26e71b46d64..0b6b1c3cf76 100644 --- a/extensions/javascript/package.json +++ b/extensions/javascript/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "0.10.x" }, + "categories": ["Programming Languages"], "contributes": { "configurationDefaults": { "[javascript]": { diff --git a/extensions/json/package.json b/extensions/json/package.json index 7834eb38d75..8844345d672 100644 --- a/extensions/json/package.json +++ b/extensions/json/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ./build/update-grammars.js" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/julia/package.json b/extensions/julia/package.json index 3c30069dce6..31cdd4ce71a 100644 --- a/extensions/julia/package.json +++ b/extensions/julia/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin JuliaEditorSupport/atom-language-julia grammars/julia_vscode.json ./syntaxes/julia.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/latex/package.json b/extensions/latex/package.json index f7bacdbcd5b..73e1829f73f 100644 --- a/extensions/latex/package.json +++ b/extensions/latex/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ./build/update-grammars.js" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/less/package.json b/extensions/less/package.json index 96301ba0fd1..5bd376312fa 100644 --- a/extensions/less/package.json +++ b/extensions/less/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ./build/update-grammar.js" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/log/package.json b/extensions/log/package.json index 73093e1179a..d52c43afea7 100644 --- a/extensions/log/package.json +++ b/extensions/log/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin emilast/vscode-logfile-highlighter syntaxes/log.tmLanguage ./syntaxes/log.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/lua/package.json b/extensions/lua/package.json index 20dba16c918..72c0840a9ea 100644 --- a/extensions/lua/package.json +++ b/extensions/lua/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin sumneko/lua.tmbundle Syntaxes/Lua.plist ./syntaxes/lua.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/make/package.json b/extensions/make/package.json index 89a5134c64d..49e96cab1eb 100644 --- a/extensions/make/package.json +++ b/extensions/make/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin fadeevab/make.tmbundle Syntaxes/Makefile.plist ./syntaxes/make.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/markdown-basics/package.json b/extensions/markdown-basics/package.json index 52588bbd362..6eadec35592 100644 --- a/extensions/markdown-basics/package.json +++ b/extensions/markdown-basics/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "^1.20.0" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/markdown-math/package.json b/extensions/markdown-math/package.json index 8947e89ed2f..85d09cbc9dc 100644 --- a/extensions/markdown-math/package.json +++ b/extensions/markdown-math/package.json @@ -11,7 +11,8 @@ "vscode": "^1.54.0" }, "categories": [ - "Other" + "Other", + "Programming Languages" ], "capabilities": { "virtualWorkspaces": true, diff --git a/extensions/objective-c/package.json b/extensions/objective-c/package.json index 91cc25be774..5339f7c42f9 100644 --- a/extensions/objective-c/package.json +++ b/extensions/objective-c/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ./build/update-grammars.js" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/perl/package.json b/extensions/perl/package.json index 003ec922c32..1569e556146 100644 --- a/extensions/perl/package.json +++ b/extensions/perl/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin textmate/perl.tmbundle Syntaxes/Perl.plist ./syntaxes/perl.tmLanguage.json Syntaxes/Perl%206.tmLanguage ./syntaxes/perl6.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/php/package.json b/extensions/php/package.json index 145431f9318..e38b844be65 100644 --- a/extensions/php/package.json +++ b/extensions/php/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "0.10.x" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/powershell/package.json b/extensions/powershell/package.json index 1097b2511ba..0fd7abd4149 100644 --- a/extensions/powershell/package.json +++ b/extensions/powershell/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "*" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/pug/package.json b/extensions/pug/package.json index 0d3c6b6eed8..cb77f60ba88 100644 --- a/extensions/pug/package.json +++ b/extensions/pug/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin davidrios/pug-tmbundle Syntaxes/Pug.JSON-tmLanguage ./syntaxes/pug.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/python/package.json b/extensions/python/package.json index 332ff083c2b..543268de715 100644 --- a/extensions/python/package.json +++ b/extensions/python/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "*" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/r/package.json b/extensions/r/package.json index b78c30ce95e..fd1505e7b00 100644 --- a/extensions/r/package.json +++ b/extensions/r/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin REditorSupport/vscode-R syntax/r.json ./syntaxes/r.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/razor/package.json b/extensions/razor/package.json index 2312ce86c48..06551edc8b9 100644 --- a/extensions/razor/package.json +++ b/extensions/razor/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ./build/update-grammar.mjs" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/references-view/package.json b/extensions/references-view/package.json index a14e5a60ec0..228332773c6 100644 --- a/extensions/references-view/package.json +++ b/extensions/references-view/package.json @@ -22,9 +22,6 @@ "bugs": { "url": "https://github.com/Microsoft/vscode-references-view/issues" }, - "categories": [ - "Programming Languages" - ], "activationEvents": [ "onCommand:references-view.find", "onCommand:editor.action.showReferences" diff --git a/extensions/restructuredtext/package.json b/extensions/restructuredtext/package.json index 0fc722ed88c..4e74e6611ed 100644 --- a/extensions/restructuredtext/package.json +++ b/extensions/restructuredtext/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin trond-snekvik/vscode-rst syntaxes/rst.tmLanguage.json ./syntaxes/rst.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/ruby/package.json b/extensions/ruby/package.json index 1ce625ef7c8..70dd99f26b5 100644 --- a/extensions/ruby/package.json +++ b/extensions/ruby/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin textmate/ruby.tmbundle Syntaxes/Ruby.plist ./syntaxes/ruby.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/rust/package.json b/extensions/rust/package.json index a5485e3d473..34132cd15df 100644 --- a/extensions/rust/package.json +++ b/extensions/rust/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ./build/update-grammar.mjs" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/scss/package.json b/extensions/scss/package.json index 967b790d9e5..19eed5e43c6 100644 --- a/extensions/scss/package.json +++ b/extensions/scss/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin atom/language-sass grammars/scss.cson ./syntaxes/scss.tmLanguage.json grammars/sassdoc.cson ./syntaxes/sassdoc.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/search-result/package.json b/extensions/search-result/package.json index 747cde8e648..6582db3e782 100644 --- a/extensions/search-result/package.json +++ b/extensions/search-result/package.json @@ -9,9 +9,6 @@ "engines": { "vscode": "^1.39.0" }, - "categories": [ - "Programming Languages" - ], "main": "./out/extension.js", "browser": "./dist/extension.js", "activationEvents": [ diff --git a/extensions/shaderlab/package.json b/extensions/shaderlab/package.json index 7ffb311bea1..df6282b6fa9 100644 --- a/extensions/shaderlab/package.json +++ b/extensions/shaderlab/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin tgjones/shaders-tmLanguage grammars/shaderlab.json ./syntaxes/shaderlab.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/shellscript/package.json b/extensions/shellscript/package.json index 72224868c2d..6f9e7072ab8 100644 --- a/extensions/shellscript/package.json +++ b/extensions/shellscript/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin jeff-hykin/better-shell-syntax autogenerated/shell.tmLanguage.json ./syntaxes/shell-unix-bash.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/sql/package.json b/extensions/sql/package.json index 20e19ad8dd0..c048da78d1c 100644 --- a/extensions/sql/package.json +++ b/extensions/sql/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ./build/update-grammar.mjs" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/swift/package.json b/extensions/swift/package.json index fd1a76cbadd..5effcf0c6fd 100644 --- a/extensions/swift/package.json +++ b/extensions/swift/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin jtbandes/swift-tmlanguage Swift.tmLanguage.json ./syntaxes/swift.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/theme-abyss/package.json b/extensions/theme-abyss/package.json index 57183bf3d5e..3d3cf45c82a 100644 --- a/extensions/theme-abyss/package.json +++ b/extensions/theme-abyss/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "*" }, + "categories": ["Themes"], "contributes": { "themes": [ { diff --git a/extensions/theme-kimbie-dark/package.json b/extensions/theme-kimbie-dark/package.json index 7c186403adc..5c3ec5f7ad0 100644 --- a/extensions/theme-kimbie-dark/package.json +++ b/extensions/theme-kimbie-dark/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "*" }, + "categories": ["Themes"], "contributes": { "themes": [ { diff --git a/extensions/theme-monokai-dimmed/package.json b/extensions/theme-monokai-dimmed/package.json index 0c5b4f5fef8..bd6055e6b28 100644 --- a/extensions/theme-monokai-dimmed/package.json +++ b/extensions/theme-monokai-dimmed/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "*" }, + "categories": ["Themes"], "contributes": { "themes": [ { diff --git a/extensions/theme-monokai/package.json b/extensions/theme-monokai/package.json index b7cffe26a62..f2e63cd3494 100644 --- a/extensions/theme-monokai/package.json +++ b/extensions/theme-monokai/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "*" }, + "categories": ["Themes"], "contributes": { "themes": [ { diff --git a/extensions/theme-quietlight/package.json b/extensions/theme-quietlight/package.json index ea5387ba751..e27a8d30d65 100644 --- a/extensions/theme-quietlight/package.json +++ b/extensions/theme-quietlight/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "*" }, + "categories": ["Themes"], "contributes": { "themes": [ { diff --git a/extensions/theme-red/package.json b/extensions/theme-red/package.json index 176c78c22fb..c2273a0046d 100644 --- a/extensions/theme-red/package.json +++ b/extensions/theme-red/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "*" }, + "categories": ["Themes"], "contributes": { "themes": [ { diff --git a/extensions/theme-seti/package.json b/extensions/theme-seti/package.json index fd0dec2c8bf..fbc23ec6ee4 100644 --- a/extensions/theme-seti/package.json +++ b/extensions/theme-seti/package.json @@ -13,6 +13,7 @@ "engines": { "vscode": "*" }, + "categories": ["Themes"], "contributes": { "iconThemes": [ { diff --git a/extensions/theme-solarized-dark/package.json b/extensions/theme-solarized-dark/package.json index fee98f59466..c3eed2573d8 100644 --- a/extensions/theme-solarized-dark/package.json +++ b/extensions/theme-solarized-dark/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "*" }, + "categories": ["Themes"], "contributes": { "themes": [ { diff --git a/extensions/theme-solarized-light/package.json b/extensions/theme-solarized-light/package.json index 7efd642d58b..0d016de93dc 100644 --- a/extensions/theme-solarized-light/package.json +++ b/extensions/theme-solarized-light/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "*" }, + "categories": ["Themes"], "contributes": { "themes": [ { diff --git a/extensions/theme-tomorrow-night-blue/package.json b/extensions/theme-tomorrow-night-blue/package.json index a9a6405a947..8739e70c13f 100644 --- a/extensions/theme-tomorrow-night-blue/package.json +++ b/extensions/theme-tomorrow-night-blue/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "*" }, + "categories": ["Themes"], "contributes": { "themes": [ { diff --git a/extensions/typescript-basics/package.json b/extensions/typescript-basics/package.json index 63c07e75b6d..d765f6116f8 100644 --- a/extensions/typescript-basics/package.json +++ b/extensions/typescript-basics/package.json @@ -12,6 +12,7 @@ "scripts": { "update-grammar": "node ./build/update-grammars.mjs" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/vb/package.json b/extensions/vb/package.json index 6abf5ca5cc1..00665b071c3 100644 --- a/extensions/vb/package.json +++ b/extensions/vb/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin textmate/asp.vb.net.tmbundle Syntaxes/ASP%20VB.net.plist ./syntaxes/asp-vb-net.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/xml/package.json b/extensions/xml/package.json index 36ccc2afc6e..60b0df42f65 100644 --- a/extensions/xml/package.json +++ b/extensions/xml/package.json @@ -8,6 +8,7 @@ "engines": { "vscode": "*" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/extensions/yaml/package.json b/extensions/yaml/package.json index 15739573f41..96e02e37da6 100644 --- a/extensions/yaml/package.json +++ b/extensions/yaml/package.json @@ -11,6 +11,7 @@ "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin textmate/yaml.tmbundle Syntaxes/YAML.tmLanguage ./syntaxes/yaml.tmLanguage.json" }, + "categories": ["Programming Languages"], "contributes": { "languages": [ { diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index bbcba568c68..b8f44f8df23 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -104,7 +104,6 @@ Registry.as(EditorExtensions.EditorPane).registerEditorPane new SyncDescriptor(ExtensionsInput) ]); - Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer( { id: VIEWLET_ID, @@ -1127,7 +1126,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi const viewlet = await this.paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar, true); const extensionsViewPaneContainer = viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer; const currentQuery = Query.parse(extensionsViewPaneContainer.searchValue || ''); - extensionsViewPaneContainer.search(new Query(currentQuery.value, id, currentQuery.groupBy).toString()); + extensionsViewPaneContainer.search(new Query(currentQuery.value, id).toString()); extensionsViewPaneContainer.focus(); } }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 63d722516ec..55b42b4583c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -21,7 +21,7 @@ import { InstallLocalExtensionsInRemoteAction, InstallRemoteExtensionsInLocalAct import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; -import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInFeatureExtensionsView, BuiltInThemesExtensionsView, BuiltInProgrammingLanguageExtensionsView, ServerInstalledExtensionsView, DefaultRecommendedExtensionsView, UntrustedWorkspaceUnsupportedExtensionsView, UntrustedWorkspacePartiallySupportedExtensionsView, VirtualWorkspaceUnsupportedExtensionsView, VirtualWorkspacePartiallySupportedExtensionsView, DefaultPopularExtensionsView, DeprecatedExtensionsView, SearchMarketplaceExtensionsView, RecentlyUpdatedExtensionsView, OutdatedExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; +import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, ServerInstalledExtensionsView, DefaultRecommendedExtensionsView, UntrustedWorkspaceUnsupportedExtensionsView, UntrustedWorkspacePartiallySupportedExtensionsView, VirtualWorkspaceUnsupportedExtensionsView, VirtualWorkspacePartiallySupportedExtensionsView, DefaultPopularExtensionsView, DeprecatedExtensionsView, SearchMarketplaceExtensionsView, RecentlyUpdatedExtensionsView, OutdatedExtensionsView, StaticQueryExtensionsView, NONE_CATEGORY } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import Severity from 'vs/base/common/severity'; @@ -42,7 +42,7 @@ import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { SuggestEnabledInput } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput'; import { alert } from 'vs/base/browser/ui/aria/aria'; -import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { EXTENSION_CATEGORIES, ExtensionType } from 'vs/platform/extensions/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { ILabelService } from 'vs/platform/label/common/label'; import { MementoObject } from 'vs/workbench/common/memento'; @@ -69,6 +69,7 @@ export const DefaultViewsContext = new RawContextKey('defaultExtensionV export const ExtensionsSortByContext = new RawContextKey('extensionsSortByValue', ''); export const SearchMarketplaceExtensionsContext = new RawContextKey('searchMarketplaceExtensions', false); export const SearchHasTextContext = new RawContextKey('extensionSearchHasText', false); +const InstalledExtensionsContext = new RawContextKey('installedExtensions', false); const SearchInstalledExtensionsContext = new RawContextKey('searchInstalledExtensions', false); const SearchRecentlyUpdatedExtensionsContext = new RawContextKey('searchRecentlyUpdatedExtensions', false); const SearchExtensionUpdatesContext = new RawContextKey('searchExtensionUpdates', false); @@ -301,7 +302,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio id: 'workbench.views.extensions.searchInstalled', name: localize2('installed', "Installed"), ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), - when: ContextKeyExpr.and(ContextKeyExpr.has('searchInstalledExtensions')), + when: ContextKeyExpr.or(ContextKeyExpr.has('searchInstalledExtensions'), ContextKeyExpr.has('installedExtensions')), }); /* @@ -394,24 +395,28 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio private createBuiltinExtensionsViewDescriptors(): IViewDescriptor[] { const viewDescriptors: IViewDescriptor[] = []; + const configuredCategories = ['themes', 'programming languages']; + const otherCategories = EXTENSION_CATEGORIES.filter(c => !configuredCategories.includes(c.toLowerCase())); + otherCategories.push(NONE_CATEGORY); + const otherCategoriesQuery = `${otherCategories.map(c => `category:"${c}"`).join(' ')} ${configuredCategories.map(c => `category:"-${c}"`).join(' ')}`; viewDescriptors.push({ id: 'workbench.views.extensions.builtinFeatureExtensions', name: localize2('builtinFeatureExtensions', "Features"), - ctorDescriptor: new SyncDescriptor(BuiltInFeatureExtensionsView, [{}]), + ctorDescriptor: new SyncDescriptor(StaticQueryExtensionsView, [{ query: `@builtin ${otherCategoriesQuery}` }]), when: ContextKeyExpr.has('builtInExtensions'), }); viewDescriptors.push({ id: 'workbench.views.extensions.builtinThemeExtensions', name: localize2('builtInThemesExtensions', "Themes"), - ctorDescriptor: new SyncDescriptor(BuiltInThemesExtensionsView, [{}]), + ctorDescriptor: new SyncDescriptor(StaticQueryExtensionsView, [{ query: `@builtin category:themes` }]), when: ContextKeyExpr.has('builtInExtensions'), }); viewDescriptors.push({ id: 'workbench.views.extensions.builtinProgrammingLanguageExtensions', name: localize2('builtinProgrammingLanguageExtensions', "Programming Languages"), - ctorDescriptor: new SyncDescriptor(BuiltInProgrammingLanguageExtensionsView, [{}]), + ctorDescriptor: new SyncDescriptor(StaticQueryExtensionsView, [{ query: `@builtin category:"programming languages"` }]), when: ContextKeyExpr.has('builtInExtensions'), }); @@ -474,6 +479,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE private searchMarketplaceExtensionsContextKey: IContextKey; private searchHasTextContextKey: IContextKey; private sortByUpdateDateContextKey: IContextKey; + private installedExtensionsContextKey: IContextKey; private searchInstalledExtensionsContextKey: IContextKey; private searchRecentlyUpdatedExtensionsContextKey: IContextKey; private searchExtensionUpdatesContextKey: IContextKey; @@ -521,6 +527,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this.searchMarketplaceExtensionsContextKey = SearchMarketplaceExtensionsContext.bindTo(contextKeyService); this.searchHasTextContextKey = SearchHasTextContext.bindTo(contextKeyService); this.sortByUpdateDateContextKey = SortByUpdateDateContext.bindTo(contextKeyService); + this.installedExtensionsContextKey = InstalledExtensionsContext.bindTo(contextKeyService); this.searchInstalledExtensionsContextKey = SearchInstalledExtensionsContext.bindTo(contextKeyService); this.searchRecentlyUpdatedExtensionsContextKey = SearchRecentlyUpdatedExtensionsContext.bindTo(contextKeyService); this.searchExtensionUpdatesContextKey = SearchExtensionUpdatesContext.bindTo(contextKeyService); @@ -713,7 +720,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this.contextKeyService.bufferChangeEvents(() => { const isRecommendedExtensionsQuery = ExtensionsListView.isRecommendedExtensionsQuery(value); this.searchHasTextContextKey.set(value.trim() !== ''); - this.searchInstalledExtensionsContextKey.set(ExtensionsListView.isInstalledExtensionsQuery(value)); + this.installedExtensionsContextKey.set(ExtensionsListView.isInstalledExtensionsQuery(value)); + this.searchInstalledExtensionsContextKey.set(ExtensionsListView.isSearchInstalledExtensionsQuery(value)); this.searchRecentlyUpdatedExtensionsContextKey.set(ExtensionsListView.isSearchRecentlyUpdatedQuery(value) && !ExtensionsListView.isSearchExtensionUpdatesQuery(value)); this.searchOutdatedExtensionsContextKey.set(ExtensionsListView.isOutdatedExtensionsQuery(value) && !ExtensionsListView.isSearchExtensionUpdatesQuery(value)); this.searchExtensionUpdatesContextKey.set(ExtensionsListView.isSearchExtensionUpdatesQuery(value)); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 64d33c31756..db5a0672ed9 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -56,8 +56,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { isOfflineError } from 'vs/base/parts/request/common/request'; import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; -// Extensions that are automatically classified as Programming Language extensions, but should be Feature extensions -const FORCE_FEATURE_EXTENSIONS = ['vscode.git', 'vscode.git-base', 'vscode.search-result']; +export const NONE_CATEGORY = 'none'; class ExtensionsViewState extends Disposable implements IExtensionsViewState { @@ -447,74 +446,56 @@ export class ExtensionsListView extends ViewPane { } private filterBuiltinExtensions(local: IExtension[], query: Query, options: IQueryOptions): IExtension[] { - let value = query.value; - const showThemesOnly = /@builtin:themes/i.test(value); - if (showThemesOnly) { - value = value.replace(/@builtin:themes/g, ''); - } - const showBasicsOnly = /@builtin:basics/i.test(value); - if (showBasicsOnly) { - value = value.replace(/@builtin:basics/g, ''); - } - const showFeaturesOnly = /@builtin:features/i.test(value); - if (showFeaturesOnly) { - value = value.replace(/@builtin:features/g, ''); - } - + let { value, includedCategories, excludedCategories } = this.parseCategories(query.value); value = value.replace(/@builtin/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase(); const result = local - .filter(e => e.isBuiltin && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1)); - - const isThemeExtension = (e: IExtension): boolean => { - return (Array.isArray(e.local?.manifest?.contributes?.themes) && e.local!.manifest!.contributes!.themes.length > 0) - || (Array.isArray(e.local?.manifest?.contributes?.iconThemes) && e.local!.manifest!.contributes!.iconThemes.length > 0); - }; - if (showThemesOnly) { - const themesExtensions = result.filter(isThemeExtension); - return this.sortExtensions(themesExtensions, options); - } - - const isLanguageBasicExtension = (e: IExtension): boolean => { - return FORCE_FEATURE_EXTENSIONS.indexOf(e.identifier.id) === -1 - && (Array.isArray(e.local?.manifest?.contributes?.grammars) && e.local!.manifest!.contributes!.grammars.length > 0); - }; - if (showBasicsOnly) { - const basics = result.filter(isLanguageBasicExtension); - return this.sortExtensions(basics, options); - } - if (showFeaturesOnly) { - const others = result.filter(e => { - return e.local - && e.local.manifest - && !isThemeExtension(e) - && !isLanguageBasicExtension(e); - }); - return this.sortExtensions(others, options); - } + .filter(e => e.isBuiltin && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1) + && this.filterExtensionByCategory(e, includedCategories, excludedCategories)); return this.sortExtensions(result, options); } - private parseCategories(value: string): { value: string; categories: string[] } { - const categories: string[] = []; + private filterExtensionByCategory(e: IExtension, includedCategories: string[], excludedCategories: string[]): boolean { + if (!includedCategories.length && !excludedCategories.length) { + return true; + } + if (e.categories.length) { + if (excludedCategories.length && e.categories.some(category => excludedCategories.includes(category.toLowerCase()))) { + return false; + } + return e.categories.some(category => includedCategories.includes(category.toLowerCase())); + } else { + return includedCategories.includes(NONE_CATEGORY); + } + } + + private parseCategories(value: string): { value: string; includedCategories: string[]; excludedCategories: string[] } { + const includedCategories: string[] = []; + const excludedCategories: string[] = []; value = value.replace(/\bcategory:("([^"]*)"|([^"]\S*))(\s+|\b|$)/g, (_, quotedCategory, category) => { const entry = (category || quotedCategory || '').toLowerCase(); - if (categories.indexOf(entry) === -1) { - categories.push(entry); + if (entry.startsWith('-')) { + if (excludedCategories.indexOf(entry) === -1) { + excludedCategories.push(entry); + } + } else { + if (includedCategories.indexOf(entry) === -1) { + includedCategories.push(entry); + } } return ''; }); - return { value, categories }; + return { value, includedCategories, excludedCategories }; } private filterInstalledExtensions(local: IExtension[], runningExtensions: readonly IExtensionDescription[], query: Query, options: IQueryOptions): IExtension[] { - let { value, categories } = this.parseCategories(query.value); + let { value, includedCategories, excludedCategories } = this.parseCategories(query.value); value = value.replace(/@installed/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase(); const matchingText = (e: IExtension) => (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1 || e.description.toLowerCase().indexOf(value) > -1) - && (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category))); + && this.filterExtensionByCategory(e, includedCategories, excludedCategories); let result; if (options.sortBy !== undefined) { @@ -570,7 +551,7 @@ export class ExtensionsListView extends ViewPane { } private filterOutdatedExtensions(local: IExtension[], query: Query, options: IQueryOptions): IExtension[] { - let { value, categories } = this.parseCategories(query.value); + let { value, includedCategories, excludedCategories } = this.parseCategories(query.value); value = value.replace(/@outdated/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase(); @@ -578,13 +559,13 @@ export class ExtensionsListView extends ViewPane { .sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)) .filter(extension => extension.outdated && (extension.name.toLowerCase().indexOf(value) > -1 || extension.displayName.toLowerCase().indexOf(value) > -1) - && (!categories.length || categories.some(category => !!extension.local && extension.local.manifest.categories!.some(c => c.toLowerCase() === category)))); + && this.filterExtensionByCategory(extension, includedCategories, excludedCategories)); return this.sortExtensions(result, options); } private filterDisabledExtensions(local: IExtension[], runningExtensions: readonly IExtensionDescription[], query: Query, options: IQueryOptions): IExtension[] { - let { value, categories } = this.parseCategories(query.value); + let { value, includedCategories, excludedCategories } = this.parseCategories(query.value); value = value.replace(/@disabled/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase(); @@ -592,13 +573,13 @@ export class ExtensionsListView extends ViewPane { .sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)) .filter(e => runningExtensions.every(r => !areSameExtensions({ id: r.identifier.value, uuid: r.uuid }, e.identifier)) && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1) - && (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category)))); + && this.filterExtensionByCategory(e, includedCategories, excludedCategories)); return this.sortExtensions(result, options); } private filterEnabledExtensions(local: IExtension[], runningExtensions: readonly IExtensionDescription[], query: Query, options: IQueryOptions): IExtension[] { - let { value, categories } = this.parseCategories(query.value); + let { value, includedCategories, excludedCategories } = this.parseCategories(query.value); value = value ? value.replace(/@enabled/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase() : ''; @@ -607,7 +588,7 @@ export class ExtensionsListView extends ViewPane { .sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)) .filter(e => runningExtensions.some(r => areSameExtensions({ id: r.identifier.value, uuid: r.uuid }, e.identifier)) && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1) - && (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category)))); + && this.filterExtensionByCategory(e, includedCategories, excludedCategories)); return this.sortExtensions(result, options); } @@ -681,15 +662,15 @@ export class ExtensionsListView extends ViewPane { } private filterRecentlyUpdatedExtensions(local: IExtension[], query: Query, options: IQueryOptions): IExtension[] { - let { value, categories } = this.parseCategories(query.value); + let { value, includedCategories, excludedCategories } = this.parseCategories(query.value); const currentTime = Date.now(); local = local.filter(e => !e.isBuiltin && !e.outdated && e.local?.updated && e.local?.installedTimestamp !== undefined && currentTime - e.local.installedTimestamp < ExtensionsListView.RECENT_UPDATE_DURATION); value = value.replace(/@recentlyUpdated/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase(); const result = local.filter(e => - (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1) && - (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category)))); + (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1) + && this.filterExtensionByCategory(e, includedCategories, excludedCategories)); options.sortBy = options.sortBy ?? LocalSortBy.UpdateDate; @@ -1082,6 +1063,7 @@ export class ExtensionsListView extends ViewPane { static isLocalExtensionsQuery(query: string, sortBy?: string): boolean { return this.isInstalledExtensionsQuery(query) + || this.isSearchInstalledExtensionsQuery(query) || this.isOutdatedExtensionsQuery(query) || this.isEnabledExtensionsQuery(query) || this.isDisabledExtensionsQuery(query) @@ -1112,7 +1094,11 @@ export class ExtensionsListView extends ViewPane { } static isInstalledExtensionsQuery(query: string): boolean { - return /@installed/i.test(query); + return /@installed$/i.test(query); + } + + static isSearchInstalledExtensionsQuery(query: string): boolean { + return /@installed\s./i.test(query); } static isOutdatedExtensionsQuery(query: string): boolean { @@ -1262,21 +1248,49 @@ export class RecentlyUpdatedExtensionsView extends ExtensionsListView { } -export class BuiltInFeatureExtensionsView extends ExtensionsListView { - override async show(query: string): Promise> { - return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:features'); - } +export interface StaticQueryExtensionsViewOptions extends ExtensionsListViewOptions { + readonly query: string; } -export class BuiltInThemesExtensionsView extends ExtensionsListView { - override async show(query: string): Promise> { - return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:themes'); - } -} +export class StaticQueryExtensionsView extends ExtensionsListView { -export class BuiltInProgrammingLanguageExtensionsView extends ExtensionsListView { - override async show(query: string): Promise> { - return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:basics'); + constructor( + protected override readonly options: StaticQueryExtensionsViewOptions, + viewletViewOptions: IViewletViewOptions, + @INotificationService notificationService: INotificationService, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IExtensionService extensionService: IExtensionService, + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionRecommendationsService extensionRecommendationsService: IExtensionRecommendationsService, + @ITelemetryService telemetryService: ITelemetryService, + @IConfigurationService configurationService: IConfigurationService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, + @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IWorkbenchExtensionManagementService extensionManagementService: IWorkbenchExtensionManagementService, + @IWorkspaceContextService workspaceService: IWorkspaceContextService, + @IProductService productService: IProductService, + @IContextKeyService contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IOpenerService openerService: IOpenerService, + @IPreferencesService preferencesService: IPreferencesService, + @IStorageService storageService: IStorageService, + @IWorkspaceTrustManagementService workspaceTrustManagementService: IWorkspaceTrustManagementService, + @IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILogService logService: ILogService + ) { + super(options, viewletViewOptions, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, + extensionsWorkbenchService, extensionRecommendationsService, telemetryService, configurationService, contextService, extensionManagementServerService, + extensionManifestPropertiesService, extensionManagementService, workspaceService, productService, contextKeyService, viewDescriptorService, openerService, + preferencesService, storageService, workspaceTrustManagementService, extensionEnablementService, layoutService, logService); + } + + override show(): Promise> { + return super.show(this.options.query); } } diff --git a/src/vs/workbench/contrib/extensions/common/extensionQuery.ts b/src/vs/workbench/contrib/extensions/common/extensionQuery.ts index d8e5ef9385b..b74676a210f 100644 --- a/src/vs/workbench/contrib/extensions/common/extensionQuery.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionQuery.ts @@ -8,7 +8,7 @@ import { EXTENSION_CATEGORIES } from 'vs/platform/extensions/common/extensions'; export class Query { - constructor(public value: string, public sortBy: string, public groupBy: string) { + constructor(public value: string, public sortBy: string) { this.value = value.trim(); } @@ -48,15 +48,7 @@ export class Query { return ''; }); - - let groupBy = ''; - value = value.replace(/@group:(\w+)(-\w*)?/g, (match, by: string, order: string) => { - groupBy = by; - - return ''; - }); - - return new Query(value, sortBy, groupBy); + return new Query(value, sortBy); } toString(): string { @@ -65,10 +57,6 @@ export class Query { if (this.sortBy) { result = `${result}${result ? ' ' : ''}@sort:${this.sortBy}`; } - if (this.groupBy) { - result = `${result}${result ? ' ' : ''}@group:${this.groupBy}`; - } - return result; } diff --git a/src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts b/src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts index 53a83c27df2..a743e9e184e 100644 --- a/src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts +++ b/src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts @@ -70,72 +70,72 @@ suite('Extension query', () => { }); test('toString', () => { - let query = new Query('hello', '', ''); + let query = new Query('hello', ''); assert.strictEqual(query.toString(), 'hello'); - query = new Query('hello world', '', ''); + query = new Query('hello world', ''); assert.strictEqual(query.toString(), 'hello world'); - query = new Query(' hello ', '', ''); + query = new Query(' hello ', ''); assert.strictEqual(query.toString(), 'hello'); - query = new Query('', 'installs', ''); + query = new Query('', 'installs'); assert.strictEqual(query.toString(), '@sort:installs'); - query = new Query('', 'installs', ''); + query = new Query('', 'installs'); assert.strictEqual(query.toString(), '@sort:installs'); - query = new Query('', 'installs', ''); + query = new Query('', 'installs'); assert.strictEqual(query.toString(), '@sort:installs'); - query = new Query('hello', 'installs', ''); + query = new Query('hello', 'installs'); assert.strictEqual(query.toString(), 'hello @sort:installs'); - query = new Query(' hello ', 'installs', ''); + query = new Query(' hello ', 'installs'); assert.strictEqual(query.toString(), 'hello @sort:installs'); }); test('isValid', () => { - let query = new Query('hello', '', ''); + let query = new Query('hello', ''); assert(query.isValid()); - query = new Query('hello world', '', ''); + query = new Query('hello world', ''); assert(query.isValid()); - query = new Query(' hello ', '', ''); + query = new Query(' hello ', ''); assert(query.isValid()); - query = new Query('', 'installs', ''); + query = new Query('', 'installs'); assert(query.isValid()); - query = new Query('', 'installs', ''); + query = new Query('', 'installs'); assert(query.isValid()); - query = new Query('', 'installs', ''); + query = new Query('', 'installs'); assert(query.isValid()); - query = new Query('', 'installs', ''); + query = new Query('', 'installs'); assert(query.isValid()); - query = new Query('hello', 'installs', ''); + query = new Query('hello', 'installs'); assert(query.isValid()); - query = new Query(' hello ', 'installs', ''); + query = new Query(' hello ', 'installs'); assert(query.isValid()); }); test('equals', () => { - const query1 = new Query('hello', '', ''); - let query2 = new Query('hello', '', ''); + const query1 = new Query('hello', ''); + let query2 = new Query('hello', ''); assert(query1.equals(query2)); - query2 = new Query('hello world', '', ''); + query2 = new Query('hello world', ''); assert(!query1.equals(query2)); - query2 = new Query('hello', 'installs', ''); + query2 = new Query('hello', 'installs'); assert(!query1.equals(query2)); - query2 = new Query('hello', 'installs', ''); + query2 = new Query('hello', 'installs'); assert(!query1.equals(query2)); }); diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts index 1d07a03c712..4a06d92c0e8 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts @@ -61,8 +61,8 @@ suite('ExtensionsViews Tests', () => { const localDisabledTheme = aLocalExtension('first-disabled-extension', { categories: ['themes'] }, { installedTimestamp: 234567 }); const localDisabledLanguage = aLocalExtension('second-disabled-extension', { categories: ['programming languages'] }, { installedTimestamp: Date.now() - 50000, updated: true }); const localRandom = aLocalExtension('random-enabled-extension', { categories: ['random'] }, { installedTimestamp: 345678 }); - const builtInTheme = aLocalExtension('my-theme', { contributes: { themes: ['my-theme'] } }, { type: ExtensionType.System, installedTimestamp: 222 }); - const builtInBasic = aLocalExtension('my-lang', { contributes: { grammars: [{ language: 'my-language' }] } }, { type: ExtensionType.System, installedTimestamp: 666666 }); + const builtInTheme = aLocalExtension('my-theme', { categories: ['Themes'], contributes: { themes: ['my-theme'] } }, { type: ExtensionType.System, installedTimestamp: 222 }); + const builtInBasic = aLocalExtension('my-lang', { categories: ['Programming Languages'], contributes: { grammars: [{ language: 'my-language' }] } }, { type: ExtensionType.System, installedTimestamp: 666666 }); const galleryEnabledLanguage = aGalleryExtension(localEnabledLanguage.manifest.name, { ...localEnabledLanguage.manifest, version: '1.0.1', identifier: localDisabledLanguage.identifier }); @@ -286,12 +286,12 @@ suite('ExtensionsViews Tests', () => { assert.strictEqual(result.get(2).name, localEnabledLanguage.manifest.name, 'Unexpected extension for @enabled query.'); }); - await testableView.show('@builtin:themes').then(result => { - assert.strictEqual(result.length, 1, 'Unexpected number of results for @builtin:themes query'); + await testableView.show('@builtin category:themes').then(result => { + assert.strictEqual(result.length, 1, 'Unexpected number of results for @builtin category:themes query'); assert.strictEqual(result.get(0).name, builtInTheme.manifest.name, 'Unexpected extension for @builtin:themes query.'); }); - await testableView.show('@builtin:basics').then(result => { + await testableView.show('@builtin category:"programming languages"').then(result => { assert.strictEqual(result.length, 1, 'Unexpected number of results for @builtin:basics query'); assert.strictEqual(result.get(0).name, builtInBasic.manifest.name, 'Unexpected extension for @builtin:basics query.'); }); From d8bb2b7956cd3d35ebb29220de4a5b2a82ecf139 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 14 Jan 2024 14:55:14 -0300 Subject: [PATCH 0300/1897] Patch incomplete markdown for links that contain a title/argument --- src/vs/base/browser/markdownRenderer.ts | 14 +++++++++++++- .../base/test/browser/markdownRenderer.test.ts | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index fa4352fd344..951a60ff113 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -581,7 +581,8 @@ function mergeRawTokenText(tokens: marked.Token[]): string { } function completeSingleLinePattern(token: marked.Tokens.ListItem | marked.Tokens.Paragraph): marked.Token | undefined { - for (const subtoken of token.tokens) { + for (let i = 0; i < token.tokens.length; i++) { + const subtoken = token.tokens[i]; if (subtoken.type === 'text') { const lines = subtoken.raw.split('\n'); const lastLine = lines[lines.length - 1]; @@ -596,6 +597,13 @@ function completeSingleLinePattern(token: marked.Tokens.ListItem | marked.Tokens } else if (lastLine.match(/(^|\s)_\w/)) { return completeUnderscore(token); } else if (lastLine.match(/(^|\s)\[.*\]\(\w*/)) { + const nextTwoSubTokens = token.tokens.slice(i + 1); + if (nextTwoSubTokens[0]?.type === 'link' && nextTwoSubTokens[1]?.type === 'text' && nextTwoSubTokens[1].raw.match(/^ *"[^"]*$/)) { + // A markdown link can look like + // [link text](https://microsoft.com "more text") + // Where "more text" is a title for the link or an argument to a vscode command link + return completeLinkTargetArg(token); + } return completeLinkTarget(token); } else if (lastLine.match(/(^|\s)\[\w/)) { return completeLinkText(token); @@ -693,6 +701,10 @@ function completeLinkTarget(tokens: marked.Token): marked.Token { return completeWithString(tokens, ')'); } +function completeLinkTargetArg(tokens: marked.Token): marked.Token { + return completeWithString(tokens, '")'); +} + function completeLinkText(tokens: marked.Token): marked.Token { return completeWithString(tokens, '](about:blank)'); } diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts index 0c02a319432..01803b7434d 100644 --- a/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -678,6 +678,24 @@ suite('MarkdownRenderer', () => { assert.deepStrictEqual(newTokens, completeTokens); }); + test('incomplete link target 2', () => { + const incomplete = 'foo [text](http://microsoft.com'; + const tokens = marked.lexer(incomplete); + const newTokens = fillInIncompleteTokens(tokens); + + const completeTokens = marked.lexer(incomplete + ')'); + assert.deepStrictEqual(newTokens, completeTokens); + }); + + test('incomplete link target with arg', () => { + const incomplete = 'foo [text](http://microsoft.com "more text here '; + const tokens = marked.lexer(incomplete); + const newTokens = fillInIncompleteTokens(tokens); + + const completeTokens = marked.lexer(incomplete + '")'); + assert.deepStrictEqual(newTokens, completeTokens); + }); + test.skip('incomplete link in list', () => { const incomplete = '- [text'; const tokens = marked.lexer(incomplete); From 3ddf196d27d71d2f5539db607b1a10adb3d0800c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 14 Jan 2024 20:15:29 +0100 Subject: [PATCH 0301/1897] zoom - further update status entry controls visuals (#202459) --- .../browser/parts/statusbar/statusbarItem.ts | 18 +++---- .../electron-sandbox/media/window.css | 16 ++++-- src/vs/workbench/electron-sandbox/window.ts | 52 ++++++++++++------- 3 files changed, 55 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts index a0388947c25..2186eb86511 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts @@ -123,16 +123,16 @@ export class StatusbarEntryItem extends Disposable { } else { this.hover = this._register(setupCustomHover(this.hoverDelegate, this.container, hoverContents, { disableHideOnClick: entryOpensTooltip })); } - this.focusListener.value = addDisposableListener(this.labelContainer, EventType.FOCUS, (e) => { - EventHelper.stop(e); - this.hover?.show(false); - }); - this.focusOutListener.value = addDisposableListener(this.labelContainer, EventType.FOCUS_OUT, (e) => { - EventHelper.stop(e); - if (!entryOpensTooltip) { + if (!entryOpensTooltip) { + this.focusListener.value = addDisposableListener(this.labelContainer, EventType.FOCUS, e => { + EventHelper.stop(e); + this.hover?.show(false); + }); + this.focusOutListener.value = addDisposableListener(this.labelContainer, EventType.FOCUS_OUT, e => { + EventHelper.stop(e); this.hover?.hide(); - } - }); + }); + } } // Update: Command diff --git a/src/vs/workbench/electron-sandbox/media/window.css b/src/vs/workbench/electron-sandbox/media/window.css index 4baf8a5f2a4..e9fd9826a62 100644 --- a/src/vs/workbench/electron-sandbox/media/window.css +++ b/src/vs/workbench/electron-sandbox/media/window.css @@ -11,7 +11,7 @@ padding-right: 2px; } -.monaco-workbench .zoom-status .action-label.codicon::before { +.monaco-workbench .zoom-status .monaco-action-bar .action-label.codicon::before { /* * the hover sets font-size: inherit which makes actions * have a size of 13px. but codicons have a size of 16px @@ -24,7 +24,7 @@ display: flex; margin-top: auto; margin-bottom: auto; - margin-right: 8px; + margin-right: 16px; } .monaco-workbench .zoom-status > .zoom-status-center { @@ -33,9 +33,19 @@ margin-bottom: auto; } +.monaco-workbench .zoom-status > .zoom-status-center .monaco-action-bar .action-label.disabled, +.monaco-workbench .zoom-status > .zoom-status-center .monaco-action-bar .action-label.disabled:hover { + /* + * we use a disabled action as label for the zoom level + * so we override the style to not show it disabled + */ + opacity: 1; + color: unset; +} + .monaco-workbench .zoom-status > .zoom-status-right { display: flex; margin-top: auto; margin-bottom: auto; - margin-left: 8px; + margin-left: 16px; } diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index dc20ed5fd16..dd01d61f1b0 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -1116,7 +1116,9 @@ export class NativeWindow extends BaseWindow { class ZoomStatusEntry extends Disposable { private readonly disposable = this._register(new MutableDisposable()); - private resetZoomAction: Action | undefined = undefined; + + private zoomLabel: Action | undefined = undefined; + private zoomReset: Action | undefined = undefined; constructor( @IStatusbarService private readonly statusbarService: IStatusbarService, @@ -1129,9 +1131,9 @@ class ZoomStatusEntry extends Disposable { if (typeof visibleOrText === 'string') { if (!this.disposable.value) { this.createZoomEntry(targetWindowId, visibleOrText); - } else { - this.updateZoomResetAction(targetWindowId); } + + this.updateZoomActions(targetWindowId); } else { this.disposable.clear(); } @@ -1152,19 +1154,17 @@ class ZoomStatusEntry extends Disposable { center.classList.add('zoom-status-center'); container.appendChild(center); - const actionBarZoom = disposables.add(new ActionBar(center)); - actionBarZoom.push( + const actionBarCenter = disposables.add(new ActionBar(center)); + actionBarCenter.push( disposables.add(new Action('zoomOut', localize('zoomOut', "Zoom Out"), ThemeIcon.asClassName(Codicon.remove), true, () => this.commandService.executeCommand('workbench.action.zoomOut'))), { icon: true, label: false } ); - this.resetZoomAction = disposables.add(new Action('zoomReset', '', undefined, true, async () => this.commandService.executeCommand('workbench.action.zoomReset'))); - this.updateZoomResetAction(targetWindowId); - disposables.add(toDisposable(() => this.resetZoomAction = undefined)); - this.resetZoomAction.tooltip = localize('resetZoom', "Reset Zoom"); - actionBarZoom.push(this.resetZoomAction, { icon: false, label: true }); + this.zoomLabel = disposables.add(new Action('zoomLabel', '', undefined, false)); + disposables.add(toDisposable(() => this.zoomLabel = undefined)); + actionBarCenter.push(this.zoomLabel, { icon: false, label: true }); - actionBarZoom.push( + actionBarCenter.push( disposables.add(new Action('zoomIn', localize('zoomIn', "Zoom In"), ThemeIcon.asClassName(Codicon.plus), true, () => this.commandService.executeCommand('workbench.action.zoomIn'))), { icon: true, label: false } ); @@ -1173,9 +1173,16 @@ class ZoomStatusEntry extends Disposable { right.classList.add('zoom-status-right'); container.appendChild(right); - const actionBarSettings = disposables.add(new ActionBar(right)); - actionBarSettings.push( - disposables.add(new Action('zoomSettings', localize('zoomSettings', "Settings"), ThemeIcon.asClassName(Codicon.settingsGear), true, () => this.commandService.executeCommand('workbench.action.openSettings', 'window.zoom'))), + const actionBarRight = disposables.add(new ActionBar(right)); + + this.zoomReset = disposables.add(new Action('zoomReset', localize('zoomReset', "Reset Zoom"), undefined, true, () => this.commandService.executeCommand('workbench.action.zoomReset'))); + disposables.add(toDisposable(() => this.zoomReset = undefined)); + + actionBarRight.push( + [ + this.zoomReset, + disposables.add(new Action('zoomSettings', localize('zoomSettings', "Settings"), ThemeIcon.asClassName(Codicon.settingsGear), true, () => this.commandService.executeCommand('workbench.action.openSettings', 'window.zoom'))) + ], { icon: true, label: false } ); @@ -1188,13 +1195,20 @@ class ZoomStatusEntry extends Disposable { command: ShowTooltipCommand, kind: 'prominent' }, 'status.windowZoom', StatusbarAlignment.RIGHT, 102)); - - console.log(container); } - private updateZoomResetAction(targetWindowId: number): void { - if (this.resetZoomAction) { - this.resetZoomAction.label = localize('zoomLevel', "{0}%", Math.round(getZoomFactor(getWindowById(targetWindowId)?.window ?? mainWindow) * 100)); + private updateZoomActions(targetWindowId: number): void { + const targetWindow = getWindowById(targetWindowId)?.window ?? mainWindow; + const zoomFactor = Math.round(getZoomFactor(targetWindow) * 100); + const zoomLevel = getZoomLevel(targetWindow); + + if (this.zoomLabel) { + this.zoomLabel.label = localize('zoomLevel', "{0}%", zoomFactor); + this.zoomLabel.tooltip = localize('zoomNumber', "Zoom Level: {0}", zoomLevel); + } + + if (this.zoomReset) { + this.zoomReset.class = zoomLevel > 0 ? ThemeIcon.asClassName(Codicon.screenNormal) : ThemeIcon.asClassName(Codicon.screenFull); } } } From 56846b7595e83b115dadd06b6b69e06539efd6e7 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 14 Jan 2024 16:20:16 -0300 Subject: [PATCH 0302/1897] Allow chat progress messages in the middle of the rendered content. This will enable us to replace the "Task" progress type. See #200598 --- .../api/browser/mainThreadChatAgents2.ts | 5 - .../api/common/extHostTypeConverters.ts | 2 +- .../contrib/chat/browser/chatListRenderer.ts | 110 +++++++++++++----- .../browser/contrib/chatInputEditorContrib.ts | 2 + .../contrib/chat/browser/media/chat.css | 11 +- .../contrib/chat/common/chatModel.ts | 23 ++-- .../contrib/chat/common/chatService.ts | 2 +- .../contrib/chat/common/chatViewModel.ts | 20 +++- 8 files changed, 118 insertions(+), 57 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index a1c61dfc505..14f12dc5719 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -146,11 +146,6 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA return responsePartHandle; } - // No need to support standalone tree data that's not attached to a placeholder in API - if (progress.kind === 'treeData') { - return; - } - const revivedProgress = revive(progress); this._pendingProgress.get(requestId)?.(revivedProgress as IChatProgress); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 143ae656c2d..9a12f53fb53 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2363,7 +2363,7 @@ export namespace ChatResponseProgress { } else if ('treeData' in progress) { return { treeData: progress.treeData, kind: 'treeData' }; } else if ('message' in progress) { - return { content: progress.message, kind: 'progressMessage' }; + return { content: MarkdownString.from(progress.message), kind: 'progressMessage' }; } else { return undefined; } diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 93a4d7599b7..3377ae546d0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -59,8 +59,8 @@ import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents' import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatContentReference, IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; -import { IChatResponseMarkdownRenderData, IChatResponseRenderData, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { IChatContentReference, IChatProgressMessage, IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatProgressMessageRenderData, IChatRenderData, IChatResponseMarkdownRenderData, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView'; import { IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; @@ -310,7 +310,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - const last = index === element.progressMessages.length - 1; - const icon = last ? ThemeIcon.modify(Codicon.sync, 'spin') : Codicon.check; - const step = dom.$('.progress-step', undefined, renderIcon(icon), dom.$('span.progress-step-message', undefined, msg.content)); - templateData.progressSteps.appendChild(step); - }); - } - private renderAvatar(element: ChatTreeItem, templateData: IChatListItemTemplate): void { if (element.avatarIconUri) { const avatarImgIcon = dom.$('img.icon'); @@ -451,15 +437,19 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { const result = data.kind === 'treeData' ? this.renderTreeData(data.treeData, element, templateData, fileTreeIndex++) : data.kind === 'markdownContent' ? this.renderMarkdown(data.content, element, templateData, fillInIncompleteTokens) - : this.renderPlaceholder(new MarkdownString(data.content), templateData); - templateData.value.appendChild(result.element); - templateData.elementDisposables.add(result); - } + : data.kind === 'asyncContent' ? this.renderPlaceholder(new MarkdownString(data.content), templateData) + : onlyProgressMessagesAfterI(value, index) ? this.renderProgressMessage(data, false) + : undefined; + if (result) { + templateData.value.appendChild(result.element); + templateData.elementDisposables.add(result); + } + }); if (isResponseVM(element) && element.errorDetails?.message) { const icon = element.errorDetails.responseIsFiltered ? Codicon.info : Codicon.error; @@ -541,8 +531,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { @@ -563,6 +552,12 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer don't do anything. null => remove the rendered element + let result: { element: HTMLElement } & IDisposable | undefined | null; if (isInteractiveProgressTreeData(partToRender)) { result = this.renderTreeData(partToRender, element, templateData, index); + } else if (isProgressMessageRenderData(partToRender)) { + if (onlyProgressMessageRenderDatasAfterI(partsToRender, index)) { + result = this.renderProgressMessage(partToRender.progressMessage, index === partsToRender.length - 1); + } else { + result = null; + } } // Avoid doing progressive rendering for multiple markdown parts simultaneously - else if (!hasRenderedOneMarkdownBlock) { + else if (!hasRenderedOneMarkdownBlock && wordCountResults[index]) { const { value } = wordCountResults[index]; result = renderableResponse[index].kind === 'asyncContent' ? this.renderPlaceholder(new MarkdownString(value), templateData) @@ -631,7 +644,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer content.dispose() }; } + private renderProgressMessage(progress: IChatProgressMessage, showSpinner: boolean): IMarkdownRenderResult { + const codicon = showSpinner ? ThemeIcon.modify(Codicon.sync, 'spin').id : Codicon.check.id; + const markdown = new MarkdownString(`$(${codicon}) ${progress.content.value}`, { + supportThemeIcons: true + }); + const result = this.renderer.render(markdown); + result.element.classList.add('progress-step'); + return result; + } + private renderMarkdown(markdown: IMarkdownString, element: ChatTreeItem, templateData: IChatListItemTemplate, fillInIncompleteTokens = false): IMarkdownRenderResult { const disposables = new DisposableStore(); let codeBlockIndex = 0; @@ -1324,10 +1354,26 @@ class ChatListTreeDataSource implements IAsyncDataSource, i: number): boolean { + return items.slice(i).every(isProgressMessage); +} + +function onlyProgressMessageRenderDatasAfterI(items: ReadonlyArray, i: number): boolean { + return items.slice(i).every(isProgressMessageRenderData); +} diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index a5f4d01a6c3..ccfa0c7a23d 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -60,6 +60,7 @@ class InputEditorDecorations extends Disposable { @ICodeEditorService private readonly codeEditorService: ICodeEditorService, @IThemeService private readonly themeService: IThemeService, @IChatService private readonly chatService: IChatService, + @IChatAgentService private readonly chatAgentService: IChatAgentService, ) { super(); @@ -80,6 +81,7 @@ class InputEditorDecorations extends Disposable { this.previouslyUsedAgents.add(agentAndCommandToKey(e.agent.id, e.slashCommand.name)); } })); + this._register(this.chatAgentService.onDidChangeAgents(() => this.updateInputEditorDecorations())); this.registerViewModelListeners(); } diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index de89b0c9c7c..009e5b35f49 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -602,27 +602,26 @@ margin: 0 0 0 4px; } -.interactive-item-container .progress-steps { +.interactive-item-container .rendered-markdown.progress-step { display: flex; - flex-direction: column; - gap: 4px; margin-left: 4px; white-space: normal; } -.interactive-item-container .progress-steps .progress-step { +.interactive-item-container .rendered-markdown.progress-step > p { display: flex; gap: 5px; align-items: center; opacity: 0.7; + margin-bottom: 4px; } -.interactive-item-container .progress-steps .progress-step .codicon { +.interactive-item-container .rendered-markdown.progress-step > p .codicon { /* Very aggressive list styles try to apply focus colors to every codicon in a list row. */ color: var(--vscode-icon-foreground) !important; } -.interactive-item-container .progress-steps .progress-step .codicon.codicon-check { +.interactive-item-container .rendered-markdown.progress-step > p .codicon.codicon-check { font-size: 14px; color: var(--vscode-debugIcon-startForeground) !important; } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 1e5fcfc1b0f..503cdb3b762 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -32,7 +32,8 @@ export type IChatProgressResponseContent = | IChatAgentMarkdownContentWithVulnerability | IChatTreeData | IChatAsyncContent - | IChatContentInlineReference; + | IChatContentInlineReference + | IChatProgressMessage; export type IChatProgressRenderableResponseContent = Exclude; @@ -163,7 +164,7 @@ export class Response implements IResponse { } this._updateRepr(quiet); }); - } else if (progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln') { + } else if (progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage') { this._responseParts.push(progress); this._updateRepr(quiet); } @@ -292,15 +293,15 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel /** * Apply one of the progress updates that are not part of the actual response content. */ - applyProgress(progress: IChatUsedContext | IChatContentReference | IChatProgressMessage) { + applyReference(progress: IChatUsedContext | IChatContentReference | IChatProgressMessage) { if (progress.kind === 'usedContext') { this._usedContext = progress; } else if (progress.kind === 'reference') { this._contentReferences.push(progress); this._onDidChange.fire(); } else if (progress.kind === 'progressMessage') { - this._progressMessages.push(progress); - this._onDidChange.fire(); + // this._progressMessages.push(progress); + // this._onDidChange.fire(); } } @@ -554,11 +555,11 @@ export class ChatModel extends Disposable implements IChatModel { revive(raw.agent) : undefined; request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, request.id, true, raw.isCanceled, raw.vote, raw.responseErrorDetails, raw.followups); if (raw.usedContext) { // @ulugbekna: if this's a new vscode sessions, doc versions are incorrect anyway? - request.response.applyProgress(raw.usedContext); + request.response.applyReference(raw.usedContext); } if (raw.contentReferences) { - raw.contentReferences.forEach(r => request.response!.applyProgress(r)); + raw.contentReferences.forEach(r => request.response!.applyReference(r)); } } return request; @@ -655,10 +656,10 @@ export class ChatModel extends Disposable implements IChatModel { if (progress.kind === 'vulnerability') { // TODO@roblourens ChatModel should just work with strings request.response.updateContent({ kind: 'markdownVuln', content: { value: progress.content }, vulnerabilities: progress.vulnerabilities }, quiet); - } else if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'asyncContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln') { + } else if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'asyncContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage') { request.response.updateContent(progress, quiet); - } else if (progress.kind === 'usedContext' || progress.kind === 'reference' || progress.kind === 'progressMessage') { - request.response.applyProgress(progress); + } else if (progress.kind === 'usedContext' || progress.kind === 'reference') { + request.response.applyReference(progress); } else if (progress.kind === 'agentDetection') { const agent = this.chatAgentService.getAgent(progress.agentName); if (agent) { @@ -746,7 +747,7 @@ export class ChatModel extends Disposable implements IChatModel { } else if (item.kind === 'asyncContent') { return new MarkdownString(item.content); } else { - return item; + return item as any; // TODO } }) : undefined, diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 4184155f86b..8239ec32294 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -129,7 +129,7 @@ export interface IChatAsyncContent { } export interface IChatProgressMessage { - content: string; + content: IMarkdownString; kind: 'progressMessage'; } diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index 2ed73840d90..1abbb3d22b9 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -71,8 +71,26 @@ export interface IChatResponseMarkdownRenderData { isFullyRendered: boolean; } +export interface IChatProgressMessageRenderData { + progressMessage: IChatProgressMessage; + + /** + * Indicates whether this is part of a group of progress messages that are at the end of the response. + * (Not whether this particular item is the very last one in the response). + * Need to re-render and add to partsToRender when this changes. + */ + isAtEndOfResponse: boolean; + + /** + * Whether this progress message the very last item in the response. + * Need to re-render to update spinner vs check when this changes. + */ + isLast: boolean; +} + +export type IChatRenderData = IChatResponseProgressFileTreeData | IChatResponseMarkdownRenderData | IChatProgressMessageRenderData; export interface IChatResponseRenderData { - renderedParts: (IChatResponseProgressFileTreeData | IChatResponseMarkdownRenderData)[]; + renderedParts: IChatRenderData[]; } export interface IChatLiveUpdateData { From a5cf5c405e0b880dbba5d3b4dbc998a070239908 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 15 Jan 2024 12:31:01 +0530 Subject: [PATCH 0303/1897] reporting more error codes (#202474) --- .../abstractExtensionManagementService.ts | 23 ++++++++----------- .../common/extensionGalleryService.ts | 13 +++++------ .../common/extensionManagement.ts | 19 +++++++++++++++ .../node/extensionDownloader.ts | 6 ++++- .../node/extensionManagementService.ts | 17 ++++++++++---- 5 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 097f3c50cd1..6c3b902e362 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -16,7 +16,7 @@ import * as nls from 'vs/nls'; import { ExtensionManagementError, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementParticipant, IGalleryExtension, ILocalExtension, InstallOperation, IExtensionsControlManifest, StatisticType, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode, - InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService, InstallExtensionInfo, EXTENSION_INSTALL_DEP_PACK_CONTEXT + InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService, InstallExtensionInfo, EXTENSION_INSTALL_DEP_PACK_CONTEXT, ExtensionGalleryError } from 'vs/platform/extensionManagement/common/extensionManagement'; import { adoptToGalleryExtensionId, areSameExtensions, ExtensionKey, getGalleryExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, IExtensionManifest, isApplicationScopedExtension, TargetPlatform } from 'vs/platform/extensions/common/extensions'; @@ -108,7 +108,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl if (result?.error) { throw result.error; } - throw toExtensionManagementError(new Error(`Unknown error while installing extension ${extension.identifier.id}`)); + throw new ExtensionManagementError(`Unknown error while installing extension ${extension.identifier.id}`, ExtensionManagementErrorCode.Unknown); } catch (error) { throw toExtensionManagementError(error); } @@ -116,7 +116,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl async installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise { if (!this.galleryService.isEnabled()) { - throw new ExtensionManagementError(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"), ExtensionManagementErrorCode.Internal); + throw new ExtensionManagementError(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"), ExtensionManagementErrorCode.NotAllowed); } const results: InstallExtensionResult[] = []; @@ -351,14 +351,6 @@ export abstract class AbstractExtensionManagementService extends Disposable impl installResults.push({ local, identifier: task.identifier, operation: task.operation, source: task.source, context: task.options.context, profileLocation: task.profileLocation, applicationScoped: local.isApplicationScoped }); } catch (error) { - if (!URI.isUri(task.source)) { - reportTelemetry(this.telemetryService, task.operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install', { - extensionData: getGalleryExtensionTelemetryData(task.source), - verificationStatus: task.verificationStatus, - duration: new Date().getTime() - startTime, - error - }); - } this.logService.error('Error while installing the extension', task.identifier.id, getErrorMessage(error)); throw error; } finally { extensionsToInstallMap.delete(task.identifier.id.toLowerCase()); } @@ -787,13 +779,18 @@ export function toExtensionManagementError(error: Error): ExtensionManagementErr if (error instanceof ExtensionManagementError) { return error; } + if (error instanceof ExtensionGalleryError) { + const e = new ExtensionManagementError(error.message, ExtensionManagementErrorCode.Gallery); + e.stack = error.stack; + return e; + } const e = new ExtensionManagementError(error.message, ExtensionManagementErrorCode.Internal); e.stack = error.stack; return e; } function reportTelemetry(telemetryService: ITelemetryService, eventName: string, { extensionData, verificationStatus, duration, error, durationSinceUpdate }: { extensionData: any; verificationStatus?: ExtensionVerificationStatus; duration?: number; durationSinceUpdate?: number; error?: Error }): void { - let errorcode: ExtensionManagementErrorCode | undefined; + let errorcode: string | undefined; let errorcodeDetail: string | undefined; if (isDefined(verificationStatus)) { @@ -809,7 +806,7 @@ function reportTelemetry(telemetryService: ITelemetryService, eventName: string, } if (error) { - if (error instanceof ExtensionManagementError) { + if (error instanceof ExtensionManagementError || error instanceof ExtensionGalleryError) { errorcode = error.code; if (error.code === ExtensionManagementErrorCode.Signature) { errorcodeDetail = error.message; diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 6602f172d96..6afa575b1a0 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -15,7 +15,7 @@ import { URI } from 'vs/base/common/uri'; import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions, IDeprecationInfo, ISearchPrefferedResults } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions, IDeprecationInfo, ISearchPrefferedResults, ExtensionGalleryError, ExtensionGalleryErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; @@ -956,7 +956,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi }; const stopWatch = new StopWatch(); - let context: IRequestContext | undefined, error: any, total: number = 0; + let context: IRequestContext | undefined, error: ExtensionGalleryError | undefined, total: number = 0; try { context = await this.requestService.request({ @@ -988,8 +988,9 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi return { galleryExtensions: [], total }; } catch (e) { - error = e; - throw e; + const errorCode = isCancellationError(e) ? ExtensionGalleryErrorCode.Cancelled : getErrorMessage(e).startsWith('XHR timeout') ? ExtensionGalleryErrorCode.Timeout : ExtensionGalleryErrorCode.Failed; + error = new ExtensionGalleryError(getErrorMessage(e), errorCode); + throw error; } finally { this.telemetryService.publicLog2('galleryService:query', { ...query.telemetryData, @@ -998,9 +999,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi success: !!context && isSuccess(context), responseBodySize: context?.res.headers['Content-Length'], statusCode: context ? String(context.res.statusCode) : undefined, - errorCode: error - ? isCancellationError(error) ? 'canceled' : getErrorMessage(error).startsWith('XHR timeout') ? 'timeout' : 'failed' - : undefined, + errorCode: error?.code, count: String(total) }); } diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 7b1397ca29c..853b9c182ba 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -398,12 +398,18 @@ export enum ExtensionManagementErrorCode { ReleaseVersionNotFound = 'ReleaseVersionNotFound', Invalid = 'Invalid', Download = 'Download', + DownloadSignature = 'DownloadSignature', + UpdateMetadata = 'UpdateMetadata', Extract = 'Extract', + Scanning = 'Scanning', Delete = 'Delete', Rename = 'Rename', CorruptZip = 'CorruptZip', IncompleteZip = 'IncompleteZip', Signature = 'Signature', + NotAllowed = 'NotAllowed', + Gallery = 'Gallery', + Unknown = 'Unknown', Internal = 'Internal', } @@ -420,6 +426,19 @@ export class ExtensionManagementError extends Error { } } +export enum ExtensionGalleryErrorCode { + Timeout = 'Timeout', + Cancelled = 'Cancelled', + Failed = 'Failed' +} + +export class ExtensionGalleryError extends Error { + constructor(message: string, readonly code: ExtensionGalleryErrorCode) { + super(message); + this.name = code; + } +} + export type InstallOptions = { isBuiltin?: boolean; isMachineScoped?: boolean; diff --git a/src/vs/platform/extensionManagement/node/extensionDownloader.ts b/src/vs/platform/extensionManagement/node/extensionDownloader.ts index cc9a0089434..65cd11430ef 100644 --- a/src/vs/platform/extensionManagement/node/extensionDownloader.ts +++ b/src/vs/platform/extensionManagement/node/extensionDownloader.ts @@ -111,7 +111,11 @@ export class ExtensionsDownloader extends Disposable { await this.cleanUpPromise; const location = joinPath(this.extensionsDownloadDir, `${this.getName(extension)}${ExtensionsDownloader.SignatureArchiveExtension}`); - await this.downloadFile(extension, location, location => this.extensionGalleryService.downloadSignatureArchive(extension, location)); + try { + await this.downloadFile(extension, location, location => this.extensionGalleryService.downloadSignatureArchive(extension, location)); + } catch (error) { + throw new ExtensionManagementError(error.message, ExtensionManagementErrorCode.DownloadSignature); + } return location; } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 9578d20bd34..8bcf57b346a 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -865,9 +865,14 @@ export class InstallGalleryExtensionTask extends InstallExtensionTask { } protected async install(token: CancellationToken): Promise<[ILocalExtension, Metadata]> { - const installed = await this.extensionsScanner.scanExtensions(null, this.options.profileLocation); - const existingExtension = installed.find(i => areSameExtensions(i.identifier, this.gallery.identifier)); + let installed; + try { + installed = await this.extensionsScanner.scanExtensions(null, this.options.profileLocation); + } catch (error) { + throw new ExtensionManagementError(error, ExtensionManagementErrorCode.Scanning); + } + const existingExtension = installed.find(i => areSameExtensions(i.identifier, this.gallery.identifier)); if (existingExtension) { this._operation = InstallOperation.Update; } @@ -892,8 +897,12 @@ export class InstallGalleryExtensionTask extends InstallExtensionTask { }; if (existingExtension?.manifest.version === this.gallery.version) { - const local = await this.extensionsScanner.updateMetadata(existingExtension, metadata); - return [local, metadata]; + try { + const local = await this.extensionsScanner.updateMetadata(existingExtension, metadata); + return [local, metadata]; + } catch (error) { + throw new ExtensionManagementError(getErrorMessage(error), ExtensionManagementErrorCode.UpdateMetadata); + } } const { location, verificationStatus } = await this.extensionsDownloader.download(this.gallery, this._operation, !this.options.donotVerifySignature); From 1339f075b16afb7e6111f8fd1a201b156c57c503 Mon Sep 17 00:00:00 2001 From: Robo Date: Mon, 15 Jan 2024 17:43:42 +0900 Subject: [PATCH 0304/1897] fix: add linux library dependency check for remote server (#202210) * fix: add linux library dependency check for remote server * chore: add faq link * chore: move to separate file for reuse * chore: add option to skip check * fix: check * fix: package path * fix: don't forget to exit main script --- build/gulpfile.reh.js | 8 ++ resources/server/bin/code-server-linux.sh | 9 ++ .../bin/helpers/check-requirements-linux.sh | 100 ++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 resources/server/bin/helpers/check-requirements-linux.sh diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index 7f485a072c0..df18cf06aa9 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -372,6 +372,14 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa ); } + if (platform === 'linux' || platform === 'alpine') { + result = es.merge(result, + gulp.src(`resources/server/bin/helpers/check-requirements-linux.sh`, { base: '.' }) + .pipe(rename(`bin/helpers/check-requirements.sh`)) + .pipe(util.setExecutableBit()) + ); + } + return result.pipe(vfs.dest(destination)); }; } diff --git a/resources/server/bin/code-server-linux.sh b/resources/server/bin/code-server-linux.sh index 3df32dfd43c..c0d6b29f194 100644 --- a/resources/server/bin/code-server-linux.sh +++ b/resources/server/bin/code-server-linux.sh @@ -9,4 +9,13 @@ esac ROOT="$(dirname "$(dirname "$(readlink -f "$0")")")" +# Check platform requirements +if [ "$(echo "$@" | grep -c -- "--skip-requirements-check")" -eq 0 ]; then + $ROOT/bin/helpers/check-requirements.sh + exit_code=$? + if [ $exit_code -ne 0 ]; then + exit $exit_code + fi +fi + "$ROOT/node" ${INSPECT:-} "$ROOT/out/server-main.js" "$@" diff --git a/resources/server/bin/helpers/check-requirements-linux.sh b/resources/server/bin/helpers/check-requirements-linux.sh new file mode 100644 index 00000000000..625b569602a --- /dev/null +++ b/resources/server/bin/helpers/check-requirements-linux.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env sh +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# + +set -e + +BITNESS=$(getconf LONG_BIT) +ARCH=$(uname -m) +found_required_glibc=0 +found_required_glibcxx=0 + +# Based on https://github.com/bminor/glibc/blob/520b1df08de68a3de328b65a25b86300a7ddf512/elf/cache.c#L162-L245 +case $ARCH in + x86_64) LDCONFIG_ARCH="x86-64";; + armv7l | armv8l) LDCONFIG_ARCH="hard-float";; + arm64 | aarch64) + if [ "$BITNESS" = "32" ]; then + # Can have 32-bit userland on 64-bit kernel + LDCONFIG_ARCH="hard-float" + else + LDCONFIG_ARCH="AArch64" + fi + ;; +esac + +if [ -f /usr/lib64/libstdc++.so.6 ]; then + # Typical path + libstdcpp_path='/usr/lib64/libstdc++.so.6' +elif [ -f /usr/lib/libstdc++.so.6 ]; then + # Typical path + libstdcpp_path='/usr/lib/libstdc++.so.6' +elif [ -f /sbin/ldconfig ]; then + # Look up path + libstdcpp_paths=$(ldconfig -p | grep 'libstdc++.so.6') + + if [ "$(echo "$libstdcpp_paths" | wc -l)" -gt 1 ]; then + libstdcpp_path=$(echo "$libstdcpp_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}') + else + libstdcpp_path=$(echo "$libstdcpp_paths" | awk '{print $NF}') + fi +else + echo "Warning: Can't find libstdc++.so or ldconfig, can't verify libstdc++ version" +fi + +if [ -n "$libstdcpp_path" ]; then + # Extracts the version number from the path, e.g. libstdc++.so.6.0.22 -> 6.0.22 + # which is then compared based on the fact that release versioning and symbol versioning + # are aligned for libstdc++. Refs https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html + # (i-e) GLIBCXX_3.4. is provided by libstdc++.so.6.y. + libstdcpp_real_path=$(readlink -f "$libstdcpp_path") + libstdcpp_version=$(echo "$libstdcpp_real_path" | awk -F'\\.so\\.' '{print $NF}') + if [ "$(printf '%s\n' "6.0.25" "$libstdcpp_version" | sort -V | head -n1)" = "6.0.25" ]; then + found_required_glibcxx=1 + else + echo "Warning: Missing GLIBCXX >= 3.4.25! from $libstdcpp_real_path" + fi +fi + +if [ -n "$(ldd --version | grep -v musl)" ]; then + if [ -f /usr/lib64/libc.so.6 ]; then + # Typical path + libc_path='/usr/lib64/libc.so.6' + elif [ -f /usr/lib/libc.so.6 ]; then + # Typical path + libc_path='/usr/lib/libc.so.6' + elif [ -f /sbin/ldconfig ]; then + # Look up path + libc_paths=$(ldconfig -p | grep 'libc.so.6') + + if [ "$(echo "$libc_paths" | wc -l)" -gt 1 ]; then + libc_path=$(echo "$libc_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}') + else + libc_path=$(echo "$libc_paths" | awk '{print $NF}') + fi + else + echo "Warning: Can't find libc.so or ldconfig, can't verify libc version" + fi + + if [ -n "$libc_path" ]; then + # Rather than trusting the output of ldd --version (which is not always accurate) + # we instead use the version of the cached libc.so.6 file itself. + libc_real_path=$(readlink -f "$libc_path") + libc_version=$($libc_real_path --version | sed -n 's/.*stable release version \([0-9]\+\.[0-9]\+\).*/\1/p') + if [ "$(printf '%s\n' "2.28" "$libc_version" | sort -V | head -n1)" = "2.28" ]; then + found_required_glibc=1 + else + echo "Warning: Missing GLIBC >= 2.28! from $libc_real_path" + fi + fi +else + echo "Warning: musl detected, skipping GLIBC check" + found_required_glibc=1 +fi + +if [ "$found_required_glibc" = "0" ] || [ "$found_required_glibcxx" = "0" ]; then + echo "Error: Missing required dependencies. Please refer to our FAQ https://aka.ms/vscode-remote/faq/old-linux for additional information." + # Custom exit code based on https://tldp.org/LDP/abs/html/exitcodes.html + exit 99 +fi From 83127004a87124b1b8b976b9f9022da41814dfd0 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 15 Jan 2024 09:51:51 +0100 Subject: [PATCH 0305/1897] zoom - more polish (#202484) --- .../electron-sandbox/media/window.css | 25 ++++----- src/vs/workbench/electron-sandbox/window.ts | 52 +++++++------------ 2 files changed, 29 insertions(+), 48 deletions(-) diff --git a/src/vs/workbench/electron-sandbox/media/window.css b/src/vs/workbench/electron-sandbox/media/window.css index e9fd9826a62..8bf36659380 100644 --- a/src/vs/workbench/electron-sandbox/media/window.css +++ b/src/vs/workbench/electron-sandbox/media/window.css @@ -7,8 +7,8 @@ display: flex; padding-top: 2px; padding-bottom: 2px; - padding-left: 8px; - padding-right: 2px; + padding-left: 5px; + padding-right: 5px; } .monaco-workbench .zoom-status .monaco-action-bar .action-label.codicon::before { @@ -22,19 +22,10 @@ .monaco-workbench .zoom-status > .zoom-status-left { display: flex; - margin-top: auto; - margin-bottom: auto; - margin-right: 16px; } -.monaco-workbench .zoom-status > .zoom-status-center { - display: flex; - margin-top: auto; - margin-bottom: auto; -} - -.monaco-workbench .zoom-status > .zoom-status-center .monaco-action-bar .action-label.disabled, -.monaco-workbench .zoom-status > .zoom-status-center .monaco-action-bar .action-label.disabled:hover { +.monaco-workbench .zoom-status > .zoom-status-left .monaco-action-bar .action-label.disabled, +.monaco-workbench .zoom-status > .zoom-status-left .monaco-action-bar .action-label.disabled:hover { /* * we use a disabled action as label for the zoom level * so we override the style to not show it disabled @@ -45,7 +36,9 @@ .monaco-workbench .zoom-status > .zoom-status-right { display: flex; - margin-top: auto; - margin-bottom: auto; - margin-left: 16px; + margin-left: 10px; +} + +.monaco-workbench .zoom-status > .zoom-status-right .monaco-action-bar .action-label { + color: unset; } diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index dd01d61f1b0..ce51f4f4fc8 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -18,7 +18,7 @@ import { WindowMinimumSize, IOpenFileRequest, getTitleBarStyle, IAddFoldersReque import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ApplyZoomTarget, applyZoom } from 'vs/platform/window/electron-sandbox/window'; -import { setFullscreen, getZoomLevel, onDidChangeZoomLevel, getZoomFactor } from 'vs/base/browser/browser'; +import { setFullscreen, getZoomLevel, onDidChangeZoomLevel } from 'vs/base/browser/browser'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { ipcRenderer, process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; @@ -1117,8 +1117,7 @@ class ZoomStatusEntry extends Disposable { private readonly disposable = this._register(new MutableDisposable()); - private zoomLabel: Action | undefined = undefined; - private zoomReset: Action | undefined = undefined; + private zoomLevelLabel: Action | undefined = undefined; constructor( @IStatusbarService private readonly statusbarService: IStatusbarService, @@ -1133,11 +1132,12 @@ class ZoomStatusEntry extends Disposable { this.createZoomEntry(targetWindowId, visibleOrText); } - this.updateZoomActions(targetWindowId); + this.updateZoomLevelLabel(targetWindowId); } else { this.disposable.clear(); } } + private createZoomEntry(targetWindowId: number, visibleOrText: string) { const disposables = new DisposableStore(); this.disposable.value = disposables; @@ -1147,24 +1147,20 @@ class ZoomStatusEntry extends Disposable { const left = document.createElement('div'); left.classList.add('zoom-status-left'); - left.textContent = localize('zoomLabel', "Zoom"); container.appendChild(left); - const center = document.createElement('div'); - center.classList.add('zoom-status-center'); - container.appendChild(center); + const actionBarLeft = disposables.add(new ActionBar(left)); - const actionBarCenter = disposables.add(new ActionBar(center)); - actionBarCenter.push( + actionBarLeft.push( disposables.add(new Action('zoomOut', localize('zoomOut', "Zoom Out"), ThemeIcon.asClassName(Codicon.remove), true, () => this.commandService.executeCommand('workbench.action.zoomOut'))), { icon: true, label: false } ); - this.zoomLabel = disposables.add(new Action('zoomLabel', '', undefined, false)); - disposables.add(toDisposable(() => this.zoomLabel = undefined)); - actionBarCenter.push(this.zoomLabel, { icon: false, label: true }); + this.zoomLevelLabel = disposables.add(new Action('zoomLabel', '', undefined, false)); + disposables.add(toDisposable(() => this.zoomLevelLabel = undefined)); + actionBarLeft.push(this.zoomLevelLabel, { icon: false, label: true }); - actionBarCenter.push( + actionBarLeft.push( disposables.add(new Action('zoomIn', localize('zoomIn', "Zoom In"), ThemeIcon.asClassName(Codicon.plus), true, () => this.commandService.executeCommand('workbench.action.zoomIn'))), { icon: true, label: false } ); @@ -1175,14 +1171,13 @@ class ZoomStatusEntry extends Disposable { const actionBarRight = disposables.add(new ActionBar(right)); - this.zoomReset = disposables.add(new Action('zoomReset', localize('zoomReset', "Reset Zoom"), undefined, true, () => this.commandService.executeCommand('workbench.action.zoomReset'))); - disposables.add(toDisposable(() => this.zoomReset = undefined)); + actionBarRight.push( + disposables.add(new Action('zoomReset', localize('zoomReset', "Reset"), undefined, true, () => this.commandService.executeCommand('workbench.action.zoomReset'))), + { icon: false, label: true } + ); actionBarRight.push( - [ - this.zoomReset, - disposables.add(new Action('zoomSettings', localize('zoomSettings', "Settings"), ThemeIcon.asClassName(Codicon.settingsGear), true, () => this.commandService.executeCommand('workbench.action.openSettings', 'window.zoom'))) - ], + disposables.add(new Action('zoomSettings', localize('zoomSettings', "Settings"), ThemeIcon.asClassName(Codicon.settingsGear), true, () => this.commandService.executeCommand('workbench.action.openSettings', 'window.zoom'))), { icon: true, label: false } ); @@ -1197,18 +1192,11 @@ class ZoomStatusEntry extends Disposable { }, 'status.windowZoom', StatusbarAlignment.RIGHT, 102)); } - private updateZoomActions(targetWindowId: number): void { - const targetWindow = getWindowById(targetWindowId)?.window ?? mainWindow; - const zoomFactor = Math.round(getZoomFactor(targetWindow) * 100); - const zoomLevel = getZoomLevel(targetWindow); - - if (this.zoomLabel) { - this.zoomLabel.label = localize('zoomLevel', "{0}%", zoomFactor); - this.zoomLabel.tooltip = localize('zoomNumber', "Zoom Level: {0}", zoomLevel); - } - - if (this.zoomReset) { - this.zoomReset.class = zoomLevel > 0 ? ThemeIcon.asClassName(Codicon.screenNormal) : ThemeIcon.asClassName(Codicon.screenFull); + private updateZoomLevelLabel(targetWindowId: number): void { + if (this.zoomLevelLabel) { + const zoomLevel = getZoomLevel(getWindowById(targetWindowId)?.window ?? mainWindow); + this.zoomLevelLabel.label = `${zoomLevel}`; + this.zoomLevelLabel.tooltip = localize('zoomNumber', "Zoom Level: {0}", zoomLevel); } } } From a7068b4aafd0bf9f7ec49005a769c6f79f179ae1 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 15 Jan 2024 11:32:46 +0100 Subject: [PATCH 0306/1897] Changes the default of multiDiffEditor.experimental.enabled --- .../multiDiffEditor/browser/multiDiffEditor.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts index 19631c2f3b7..97f4d5af816 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts @@ -28,7 +28,7 @@ Registry.as(Extensions.Configuration) properties: { 'multiDiffEditor.experimental.enabled': { type: 'boolean', - default: false, + default: true, description: 'Enable experimental multi diff editor.', }, } From ded56b307cba09e65b7910a8dcc610077e14c585 Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 15 Jan 2024 11:50:33 +0100 Subject: [PATCH 0307/1897] Fix sticky scroll shadow bug (#202490) --- src/vs/base/browser/ui/tree/abstractTree.ts | 4 ++-- src/vs/base/browser/ui/tree/media/tree.css | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index d3ce16ad04d..920e8234bfc 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1569,7 +1569,7 @@ class StickyScrollWidget implements IDisposable { private readonly accessibilityProvider: IListAccessibilityProvider | undefined, ) { - this._rootDomNode = $('.monaco-tree-sticky-container'); + this._rootDomNode = $('.monaco-tree-sticky-container.empty'); container.appendChild(this._rootDomNode); const shadow = $('.monaco-tree-sticky-container-shadow'); @@ -1725,7 +1725,7 @@ class StickyScrollWidget implements IDisposable { } private setVisible(visible: boolean): void { - this._rootDomNode.style.display = visible ? 'block' : 'none'; + this._rootDomNode.classList.toggle('empty', !visible); if (!visible) { this.stickyScrollFocus.updateElements([], undefined); diff --git a/src/vs/base/browser/ui/tree/media/tree.css b/src/vs/base/browser/ui/tree/media/tree.css index 123ca640fcd..a7c3befd964 100644 --- a/src/vs/base/browser/ui/tree/media/tree.css +++ b/src/vs/base/browser/ui/tree/media/tree.css @@ -156,6 +156,11 @@ cursor: pointer; } +.monaco-list .monaco-scrollable-element .monaco-tree-sticky-container.empty, +.monaco-list .monaco-scrollable-element .monaco-tree-sticky-container.empty .monaco-tree-sticky-container-shadow { + display: none; +} + .monaco-list .monaco-scrollable-element .monaco-tree-sticky-container .monaco-tree-sticky-container-shadow{ position: absolute; bottom: -3px; From 746cc3b79f69216519ef76115a5c22743d6047a6 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 15 Jan 2024 11:49:33 +0100 Subject: [PATCH 0308/1897] Improves observable tests. --- src/vs/base/test/common/observable.test.ts | 61 ++++++++++++++++++---- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/src/vs/base/test/common/observable.test.ts b/src/vs/base/test/common/observable.test.ts index 5f33dcbfc4c..9227c465579 100644 --- a/src/vs/base/test/common/observable.test.ts +++ b/src/vs/base/test/common/observable.test.ts @@ -18,27 +18,42 @@ suite('observables', () => { suite('tutorial', () => { test('observable + autorun', () => { const log = new Log(); + // This creates a new observable value. The name is only used for debugging purposes. + // The second arg is the initial value. const myObservable = observableValue('myObservable', 0); + // This creates an autorun. The @description is only used for debugging purposes. + // The autorun has to be disposed! This is very important. ds.add(autorun(reader => { /** @description myAutorun */ + + // This code is run immediately. + + // Use the `reader` to read observable values and track the dependency to them. + // If you use `observable.get()` instead of `observable.read(reader)`, you will just + // get the value and not track the dependency. log.log(`myAutorun.run(myObservable: ${myObservable.read(reader)})`); + + // Now that all dependencies are tracked, the autorun is re-run whenever any of the + // dependencies change. })); // The autorun runs immediately assert.deepStrictEqual(log.getAndClearEntries(), ['myAutorun.run(myObservable: 0)']); + // We set the observable. myObservable.set(1, undefined); - // The autorun runs again when any read observable changed + // -> The autorun runs again when any read observable changed assert.deepStrictEqual(log.getAndClearEntries(), ['myAutorun.run(myObservable: 1)']); + // We set the observable again. myObservable.set(1, undefined); - // But only if the value changed + // -> The autorun does not run again, because the observable didn't change. assert.deepStrictEqual(log.getAndClearEntries(), []); // Transactions batch autorun runs transaction((tx) => { myObservable.set(2, tx); - // No auto-run ran yet, even though the value changed + // No auto-run ran yet, even though the value changed! assert.deepStrictEqual(log.getAndClearEntries(), []); myObservable.set(3, tx); @@ -46,24 +61,29 @@ suite('observables', () => { }); // Only at the end of the transaction the autorun re-runs assert.deepStrictEqual(log.getAndClearEntries(), ['myAutorun.run(myObservable: 3)']); + + // Note that the autorun did not see the intermediate value `2`! }); - test('computed + autorun', () => { + test('derived + autorun', () => { const log = new Log(); const observable1 = observableValue('myObservable1', 0); const observable2 = observableValue('myObservable2', 0); + // A derived value is an observable that is derived from other observables. const myDerived = derived(reader => { /** @description myDerived */ - const value1 = observable1.read(reader); + const value1 = observable1.read(reader); // Use the reader to track dependencies. const value2 = observable2.read(reader); const sum = value1 + value2; log.log(`myDerived.recompute: ${value1} + ${value2} = ${sum}`); return sum; }); + // We create an autorun that reacts on changes to our derived value. ds.add(autorun(reader => { /** @description myAutorun */ + // Autoruns work with observable values and deriveds - in short, they work with any observable. log.log(`myAutorun(myDerived: ${myDerived.read(reader)})`); })); // autorun runs immediately @@ -86,6 +106,7 @@ suite('observables', () => { "myAutorun(myDerived: 2)", ]); + // Now we change multiple observables in a transaction to batch process the effects. transaction((tx) => { observable1.set(5, tx); assert.deepStrictEqual(log.getAndClearEntries(), []); @@ -95,6 +116,7 @@ suite('observables', () => { }); // When changing multiple observables in a transaction, // deriveds are only recomputed on demand. + // (Note that you cannot see the intermediate value when `obs1 == 5` and `obs2 == 1`) assert.deepStrictEqual(log.getAndClearEntries(), [ "myDerived.recompute: 5 + 5 = 10", "myAutorun(myDerived: 10)", @@ -139,8 +161,10 @@ suite('observables', () => { observable1.set(-10, tx); assert.deepStrictEqual(log.getAndClearEntries(), []); - myDerived.get(); // This forces a (sync) recomputation of the current value + myDerived.get(); // This forces a (sync) recomputation of the current value! assert.deepStrictEqual(log.getAndClearEntries(), (["myDerived.recompute: -10 + 0 = -10"])); + // This means, that even in transactions you can assume that all values you can read with `get` and `read` are up-to-date. + // Read these values just might cause additional (potentially unneeded) recomputations. observable2.set(10, tx); assert.deepStrictEqual(log.getAndClearEntries(), []); @@ -155,6 +179,8 @@ suite('observables', () => { test('get without observers', () => { const log = new Log(); const observable1 = observableValue('myObservableValue1', 0); + + // We set up some computeds. const computed1 = derived((reader) => { /** @description computed */ const value1 = observable1.read(reader); @@ -189,6 +215,7 @@ suite('observables', () => { observable1.set(1, undefined); assert.deepStrictEqual(log.getAndClearEntries(), []); + // And now read the computed that dependens on all the others. log.log(`value: ${computedSum.get()}`); assert.deepStrictEqual(log.getAndClearEntries(), [ 'recompute1: 1 % 3 = 1', @@ -199,7 +226,7 @@ suite('observables', () => { ]); log.log(`value: ${computedSum.get()}`); - // Because there are no observers, the derived values are not cached, but computed from scratch. + // Because there are no observers, the derived values are not cached (!), but computed from scratch. assert.deepStrictEqual(log.getAndClearEntries(), [ 'recompute1: 1 % 3 = 1', 'recompute2: 1 * 2 = 2', @@ -208,7 +235,8 @@ suite('observables', () => { 'value: 5', ]); - const disposable = keepObserved(computedSum); // Use keepAlive to keep the cache + const disposable = keepObserved(computedSum); // Use keepObserved to keep the cache. + // You can also use `computedSum.keepObserved(store)` for an inline experience. log.log(`value: ${computedSum.get()}`); assert.deepStrictEqual(log.getAndClearEntries(), [ 'recompute1: 1 % 3 = 1', @@ -222,13 +250,14 @@ suite('observables', () => { assert.deepStrictEqual(log.getAndClearEntries(), [ 'value: 5', ]); + // Tada, no recomputations! observable1.set(2, undefined); - // The keep alive does not force deriveds to be recomputed + // The keepObserved does not force deriveds to be recomputed! They are still lazy. assert.deepStrictEqual(log.getAndClearEntries(), ([])); log.log(`value: ${computedSum.get()}`); - // Those deriveds are recomputed on demand + // Those deriveds are recomputed on demand, i.e. when someone reads them. assert.deepStrictEqual(log.getAndClearEntries(), [ "recompute1: 2 % 3 = 2", "recompute2: 2 * 2 = 4", @@ -240,7 +269,7 @@ suite('observables', () => { // ... and then cached again assert.deepStrictEqual(log.getAndClearEntries(), (["value: 10"])); - disposable.dispose(); // Don't forget to dispose the keepAlive to prevent memory leaks + disposable.dispose(); // Don't forget to dispose the keepAlive to prevent memory leaks! log.log(`value: ${computedSum.get()}`); // Which disables the cache again @@ -260,7 +289,17 @@ suite('observables', () => { "recompute4: 4 + 6 = 10", "value: 10", ]); + + // Why don't we just always keep the cache alive? + // This is because in order to keep the cache alive, we have to keep our subscriptions to our dependencies alive, + // which could cause memory-leaks. + // So instead, when the last observer of a derived is disposed, we dispose our subscriptions to our dependencies. + // `keepObserved` just prevents this from happening. }); + + // That is the end of the tutorial. + // There are lots of utilities you can explore now, like `observableFromEvent`, `Event.fromObservableLight`, + // autorunWithStore, observableWithStore and so on. }); test('topological order', () => { From 55af1bc7fbd6f0040f0d67e24ab0122c3f82018c Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 15 Jan 2024 12:23:47 +0100 Subject: [PATCH 0309/1897] Git - checkout command improvements (#202495) Git - complete checkout command when checkout quick pick is cancelled --- extensions/git/src/commands.ts | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 18c982d3b94..ec71d84a684 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -14,7 +14,7 @@ import { Model } from './model'; import { Repository, Resource, ResourceGroupType } from './repository'; import { applyLineChanges, getModifiedRange, intersectDiffWithRange, invertLineChange, toLineRanges } from './staging'; import { fromGitUri, toGitUri, isGitUri, toMergeUris } from './uri'; -import { grep, isDescendant, pathEquals, relativePath } from './util'; +import { dispose, grep, isDescendant, pathEquals, relativePath } from './util'; import { GitTimelineItem } from './timelineProvider'; import { ApiRepository } from './api/api1'; import { getRemoteSourceActions, pickRemoteSource } from './remoteSource'; @@ -2447,39 +2447,44 @@ export class CommandCenter { picks.push(createBranch, createBranchFrom, checkoutDetached); } - const quickpick = window.createQuickPick(); - quickpick.busy = true; - quickpick.placeholder = opts?.detached + const disposables: Disposable[] = []; + const quickPick = window.createQuickPick(); + quickPick.busy = true; + quickPick.placeholder = opts?.detached ? l10n.t('Select a branch to checkout in detached mode') : l10n.t('Select a branch or tag to checkout'); - quickpick.show(); + quickPick.show(); picks.push(... await createCheckoutItems(repository, opts?.detached)); - quickpick.items = picks; - quickpick.busy = false; + quickPick.items = picks; + quickPick.busy = false; const choice = await new Promise(c => { - quickpick.onDidAccept(() => c(quickpick.activeItems[0])); - quickpick.onDidTriggerItemButton((e) => { - quickpick.hide(); + disposables.push(quickPick.onDidHide(() => c(undefined))); + disposables.push(quickPick.onDidAccept(() => c(quickPick.activeItems[0]))); + disposables.push((quickPick.onDidTriggerItemButton((e) => { const button = e.button as QuickInputButton & { actual: RemoteSourceAction }; const item = e.item as CheckoutItem; if (button.actual && item.refName) { button.actual.run(item.refRemote ? item.refName.substring(item.refRemote.length + 1) : item.refName); } - }); + + c(undefined); + }))); }); - quickpick.hide(); + + dispose(disposables); + quickPick.dispose(); if (!choice) { return false; } if (choice === createBranch) { - await this._branch(repository, quickpick.value); + await this._branch(repository, quickPick.value); } else if (choice === createBranchFrom) { - await this._branch(repository, quickpick.value, true); + await this._branch(repository, quickPick.value, true); } else if (choice === checkoutDetached) { return this._checkout(repository, { detached: true }); } else { From 62599aa08e4ebc8b841c0331ed10a338d97c3e14 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 15 Jan 2024 12:27:44 +0100 Subject: [PATCH 0310/1897] zoom - more polish (#202496) --- .../browser/ui/iconLabel/iconLabelHover.ts | 5 +- .../editor/browser/services/hoverService.ts | 12 ++--- src/vs/platform/hover/browser/hover.ts | 7 --- .../browser/parts/statusbar/statusbarItem.ts | 7 ++- .../browser/parts/statusbar/statusbarPart.ts | 3 +- .../electron-sandbox/desktop.contribution.ts | 3 +- src/vs/workbench/electron-sandbox/window.ts | 46 +++++++++---------- 7 files changed, 33 insertions(+), 50 deletions(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts index 5f1947e1410..23878f78ea8 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts @@ -47,7 +47,6 @@ interface IHoverAction { export interface IUpdatableHoverOptions { actions?: IHoverAction[]; linkHandler?(url: string): void; - disableHideOnClick?: boolean; } export interface ICustomHover extends IDisposable { @@ -195,9 +194,7 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM let isMouseDown = false; const mouseDownEmitter = dom.addDisposableListener(htmlElement, dom.EventType.MOUSE_DOWN, () => { isMouseDown = true; - if (!options?.disableHideOnClick) { - hideHover(true, true); - } + hideHover(true, true); }, true); const mouseUpEmitter = dom.addDisposableListener(htmlElement, dom.EventType.MOUSE_UP, () => { isMouseDown = false; diff --git a/src/vs/editor/browser/services/hoverService.ts b/src/vs/editor/browser/services/hoverService.ts index 3551e5205bd..73e3a819587 100644 --- a/src/vs/editor/browser/services/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService.ts @@ -97,14 +97,12 @@ export class HoverService implements IHoverService { } })); } else { - if (!options.disableHideOnClick) { - if ('targetElements' in options.target) { - for (const element of options.target.targetElements) { - hoverDisposables.add(addDisposableListener(element, EventType.CLICK, () => this.hideHover())); - } - } else { - hoverDisposables.add(addDisposableListener(options.target, EventType.CLICK, () => this.hideHover())); + if ('targetElements' in options.target) { + for (const element of options.target.targetElements) { + hoverDisposables.add(addDisposableListener(element, EventType.CLICK, () => this.hideHover())); } + } else { + hoverDisposables.add(addDisposableListener(options.target, EventType.CLICK, () => this.hideHover())); } const focusedElement = getActiveElement(); if (focusedElement) { diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 7bec87eb176..5f3bb1e3317 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -114,13 +114,6 @@ export interface IHoverOptions { * Options that define how the hover looks. */ appearance?: IHoverAppearanceOptions; - - /** - * Option to disable automated hiding of the hover when clicking its - * target element. This should be set when clicking the target element - * shows the hover to prevent flicker. - */ - disableHideOnClick?: boolean; } export interface IHoverPositionOptions { diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts index 2186eb86511..e18622523c5 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts @@ -117,13 +117,12 @@ export class StatusbarEntryItem extends Disposable { // Update: Hover if (!this.entry || !this.isEqualTooltip(this.entry, entry)) { const hoverContents = isMarkdownString(entry.tooltip) ? { markdown: entry.tooltip, markdownNotSupportedFallback: undefined } : entry.tooltip; - const entryOpensTooltip = entry.command === ShowTooltipCommand; if (this.hover) { - this.hover.update(hoverContents, { disableHideOnClick: entryOpensTooltip }); + this.hover.update(hoverContents); } else { - this.hover = this._register(setupCustomHover(this.hoverDelegate, this.container, hoverContents, { disableHideOnClick: entryOpensTooltip })); + this.hover = this._register(setupCustomHover(this.hoverDelegate, this.container, hoverContents)); } - if (!entryOpensTooltip) { + if (entry.command !== ShowTooltipCommand /* prevents flicker on click */) { this.focusListener.value = addDisposableListener(this.labelContainer, EventType.FOCUS, e => { EventHelper.stop(e); this.hover?.show(false); diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index e3b6e8d4bed..940b074561a 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -161,7 +161,8 @@ class StatusbarPart extends Part implements IStatusbarEntryContainer { return this.hoverService.showHover({ ...options, persistence: { - hideOnKeyDown: true + hideOnKeyDown: true, + sticky: focus } }, focus); } diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index d2690d052f3..f5f5772fccd 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -27,7 +27,6 @@ import { ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; import { ModifierKeyEmitter } from 'vs/base/browser/dom'; import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from 'vs/workbench/common/configuration'; -import product from 'vs/platform/product/common/product'; // Actions (function registerActions(): void { @@ -199,7 +198,7 @@ import product from 'vs/platform/product/common/product'; }, 'window.zoomPerWindow': { 'type': 'boolean', - 'default': product.quality !== 'stable', // TODO@bpasero flip the default eventually + 'default': true, 'markdownDescription': localize({ comment: ['{0} will be a setting name rendered as a link'], key: 'zoomPerWindow' }, "Controls if the 'Zoom In' and 'Zoom Out' commands apply the zoom level to all windows or only the active window. See {0} for configuring a default zoom level for all windows.", '`#window.zoomLevel#`'), tags: ['accessibility'] }, diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index ce51f4f4fc8..6fe76f60879 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -18,7 +18,7 @@ import { WindowMinimumSize, IOpenFileRequest, getTitleBarStyle, IAddFoldersReque import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ApplyZoomTarget, applyZoom } from 'vs/platform/window/electron-sandbox/window'; -import { setFullscreen, getZoomLevel, onDidChangeZoomLevel } from 'vs/base/browser/browser'; +import { setFullscreen, getZoomLevel, onDidChangeZoomLevel, getZoomFactor } from 'vs/base/browser/browser'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { ipcRenderer, process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; @@ -1121,7 +1121,8 @@ class ZoomStatusEntry extends Disposable { constructor( @IStatusbarService private readonly statusbarService: IStatusbarService, - @ICommandService private readonly commandService: ICommandService + @ICommandService private readonly commandService: ICommandService, + @IKeybindingService private readonly keybindingService: IKeybindingService ) { super(); } @@ -1149,21 +1150,20 @@ class ZoomStatusEntry extends Disposable { left.classList.add('zoom-status-left'); container.appendChild(left); - const actionBarLeft = disposables.add(new ActionBar(left)); + const zoomOutAction: Action = disposables.add(new Action('workbench.action.zoomOut', localize('zoomOut', "Zoom Out"), ThemeIcon.asClassName(Codicon.remove), true, () => this.commandService.executeCommand(zoomOutAction.id))); + const zoomInAction: Action = disposables.add(new Action('workbench.action.zoomIn', localize('zoomIn', "Zoom In"), ThemeIcon.asClassName(Codicon.plus), true, () => this.commandService.executeCommand(zoomInAction.id))); + const zoomResetAction: Action = disposables.add(new Action('workbench.action.zoomReset', localize('zoomReset', "Reset"), undefined, true, () => this.commandService.executeCommand(zoomResetAction.id))); + zoomResetAction.tooltip = localize('zoomResetLabel', "Reset Zoom ({1})", zoomResetAction.label, this.keybindingService.lookupKeybinding(zoomResetAction.id)?.getLabel()); + const zoomSettingsAction: Action = disposables.add(new Action('workbench.action.openSettings', localize('zoomSettings', "Settings"), ThemeIcon.asClassName(Codicon.settingsGear), true, () => this.commandService.executeCommand(zoomSettingsAction.id, 'window.zoom'))); + const zoomLevelLabel = disposables.add(new Action('zoomLabel', undefined, undefined, false)); - actionBarLeft.push( - disposables.add(new Action('zoomOut', localize('zoomOut', "Zoom Out"), ThemeIcon.asClassName(Codicon.remove), true, () => this.commandService.executeCommand('workbench.action.zoomOut'))), - { icon: true, label: false } - ); - - this.zoomLevelLabel = disposables.add(new Action('zoomLabel', '', undefined, false)); + this.zoomLevelLabel = zoomLevelLabel; disposables.add(toDisposable(() => this.zoomLevelLabel = undefined)); - actionBarLeft.push(this.zoomLevelLabel, { icon: false, label: true }); - actionBarLeft.push( - disposables.add(new Action('zoomIn', localize('zoomIn', "Zoom In"), ThemeIcon.asClassName(Codicon.plus), true, () => this.commandService.executeCommand('workbench.action.zoomIn'))), - { icon: true, label: false } - ); + const actionBarLeft = disposables.add(new ActionBar(left)); + actionBarLeft.push(zoomOutAction, { icon: true, label: false, keybinding: this.keybindingService.lookupKeybinding(zoomOutAction.id)?.getLabel() }); + actionBarLeft.push(this.zoomLevelLabel, { icon: false, label: true }); + actionBarLeft.push(zoomInAction, { icon: true, label: false, keybinding: this.keybindingService.lookupKeybinding(zoomInAction.id)?.getLabel() }); const right = document.createElement('div'); right.classList.add('zoom-status-right'); @@ -1171,15 +1171,8 @@ class ZoomStatusEntry extends Disposable { const actionBarRight = disposables.add(new ActionBar(right)); - actionBarRight.push( - disposables.add(new Action('zoomReset', localize('zoomReset', "Reset"), undefined, true, () => this.commandService.executeCommand('workbench.action.zoomReset'))), - { icon: false, label: true } - ); - - actionBarRight.push( - disposables.add(new Action('zoomSettings', localize('zoomSettings', "Settings"), ThemeIcon.asClassName(Codicon.settingsGear), true, () => this.commandService.executeCommand('workbench.action.openSettings', 'window.zoom'))), - { icon: true, label: false } - ); + actionBarRight.push(zoomResetAction, { icon: false, label: true }); + actionBarRight.push(zoomSettingsAction, { icon: true, label: false, keybinding: this.keybindingService.lookupKeybinding(zoomSettingsAction.id)?.getLabel() }); const name = localize('status.windowZoom', "Window Zoom"); disposables.add(this.statusbarService.addEntry({ @@ -1194,9 +1187,12 @@ class ZoomStatusEntry extends Disposable { private updateZoomLevelLabel(targetWindowId: number): void { if (this.zoomLevelLabel) { - const zoomLevel = getZoomLevel(getWindowById(targetWindowId)?.window ?? mainWindow); + const targetWindow = getWindowById(targetWindowId)?.window ?? mainWindow; + const zoomFactor = Math.round(getZoomFactor(targetWindow) * 100); + const zoomLevel = getZoomLevel(targetWindow); + this.zoomLevelLabel.label = `${zoomLevel}`; - this.zoomLevelLabel.tooltip = localize('zoomNumber', "Zoom Level: {0}", zoomLevel); + this.zoomLevelLabel.tooltip = localize('zoomNumber', "Zoom Level: {0} ({1}%)", zoomLevel, zoomFactor); } } } From 8622b235e2084fec9eb8d171472311a439411f76 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 15 Jan 2024 12:15:13 +0100 Subject: [PATCH 0311/1897] Revert "Remove bigNumbersDelta (#194712)" This reverts commit fe249aa1349cda9d39de9fdf41cef2e5febc1d89. Fixes #202138 --- src/vs/editor/browser/view/renderingContext.ts | 2 ++ .../viewParts/contentWidgets/contentWidgets.ts | 2 +- src/vs/editor/browser/viewParts/lines/viewLines.ts | 2 +- src/vs/editor/browser/viewParts/margin/margin.ts | 2 +- .../browser/viewParts/viewCursors/viewCursor.ts | 4 ++-- .../editor/browser/viewParts/viewZones/viewZones.ts | 2 +- src/vs/editor/common/viewLayout/linesLayout.ts | 12 ++++++++++++ .../common/viewLayout/viewLinesViewportData.ts | 6 ++++++ src/vs/editor/common/viewModel.ts | 4 ++++ 9 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/browser/view/renderingContext.ts b/src/vs/editor/browser/view/renderingContext.ts index 86c489e60cb..3396fb74b78 100644 --- a/src/vs/editor/browser/view/renderingContext.ts +++ b/src/vs/editor/browser/view/renderingContext.ts @@ -22,6 +22,7 @@ export abstract class RestrictedRenderingContext { public readonly scrollHeight: number; public readonly visibleRange: Range; + public readonly bigNumbersDelta: number; public readonly scrollTop: number; public readonly scrollLeft: number; @@ -39,6 +40,7 @@ export abstract class RestrictedRenderingContext { this.scrollHeight = this._viewLayout.getScrollHeight(); this.visibleRange = this.viewportData.visibleRange; + this.bigNumbersDelta = this.viewportData.bigNumbersDelta; const vInfo = this._viewLayout.getCurrentViewport(); this.scrollTop = vInfo.top; diff --git a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts index 6190b81be51..3112c19c324 100644 --- a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts +++ b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts @@ -537,7 +537,7 @@ class Widget { this.domNode.setTop(this._renderData.coordinate.top); this.domNode.setLeft(this._renderData.coordinate.left); } else { - this.domNode.setTop(this._renderData.coordinate.top + ctx.scrollTop); + this.domNode.setTop(this._renderData.coordinate.top + ctx.scrollTop - ctx.bigNumbersDelta); this.domNode.setLeft(this._renderData.coordinate.left); } diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index e85a47ebf2e..220ef987255 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -662,7 +662,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, // (3) handle scrolling this._linesContent.setLayerHinting(this._canUseLayerHinting); this._linesContent.setContain('strict'); - const adjustedScrollTop = this._context.viewLayout.getCurrentScrollTop(); + const adjustedScrollTop = this._context.viewLayout.getCurrentScrollTop() - viewportData.bigNumbersDelta; this._linesContent.setTop(-adjustedScrollTop); this._linesContent.setLeft(-this._context.viewLayout.getCurrentScrollLeft()); } diff --git a/src/vs/editor/browser/viewParts/margin/margin.ts b/src/vs/editor/browser/viewParts/margin/margin.ts index 1de685e5ad8..5748c0564dd 100644 --- a/src/vs/editor/browser/viewParts/margin/margin.ts +++ b/src/vs/editor/browser/viewParts/margin/margin.ts @@ -80,7 +80,7 @@ export class Margin extends ViewPart { public render(ctx: RestrictedRenderingContext): void { this._domNode.setLayerHinting(this._canUseLayerHinting); this._domNode.setContain('strict'); - const adjustedScrollTop = ctx.scrollTop; + const adjustedScrollTop = ctx.scrollTop - ctx.bigNumbersDelta; this._domNode.setTop(-adjustedScrollTop); const height = Math.min(ctx.scrollHeight, 1000000); diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts index ed9c39bf373..36506e9fed0 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts @@ -166,7 +166,7 @@ export class ViewCursor { left -= paddingLeft; } - const top = ctx.getVerticalOffsetForLineNumber(position.lineNumber); + const top = ctx.getVerticalOffsetForLineNumber(position.lineNumber) - ctx.bigNumbersDelta; return new ViewCursorRenderData(top, left, paddingLeft, width, this._lineHeight, textContent, textContentClassName); } @@ -196,7 +196,7 @@ export class ViewCursor { textContentClassName = this._getTokenClassName(position); } - let top = ctx.getVerticalOffsetForLineNumber(position.lineNumber); + let top = ctx.getVerticalOffsetForLineNumber(position.lineNumber) - ctx.bigNumbersDelta; let height = this._lineHeight; // Underline might interfere with clicking diff --git a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts index d328eab467c..37914a70335 100644 --- a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts +++ b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts @@ -374,7 +374,7 @@ export class ViewZones extends ViewPart { let newHeight = 0; let newDisplay = 'none'; if (visibleZones.hasOwnProperty(id)) { - newTop = visibleZones[id].verticalOffset; + newTop = visibleZones[id].verticalOffset - ctx.bigNumbersDelta; newHeight = visibleZones[id].height; newDisplay = 'block'; // zone is visible diff --git a/src/vs/editor/common/viewLayout/linesLayout.ts b/src/vs/editor/common/viewLayout/linesLayout.ts index d80f1495d97..7bb55aeef6e 100644 --- a/src/vs/editor/common/viewLayout/linesLayout.ts +++ b/src/vs/editor/common/viewLayout/linesLayout.ts @@ -644,6 +644,17 @@ export class LinesLayout { let currentVerticalOffset = startLineNumberVerticalOffset; let currentLineRelativeOffset = currentVerticalOffset; + // IE (all versions) cannot handle units above about 1,533,908 px, so every 500k pixels bring numbers down + const STEP_SIZE = 500000; + let bigNumbersDelta = 0; + if (startLineNumberVerticalOffset >= STEP_SIZE) { + // Compute a delta that guarantees that lines are positioned at `lineHeight` increments + bigNumbersDelta = Math.floor(startLineNumberVerticalOffset / STEP_SIZE) * STEP_SIZE; + bigNumbersDelta = Math.floor(bigNumbersDelta / lineHeight) * lineHeight; + + currentLineRelativeOffset -= bigNumbersDelta; + } + const linesOffsets: number[] = []; const verticalCenter = verticalOffset1 + (verticalOffset2 - verticalOffset1) / 2; @@ -710,6 +721,7 @@ export class LinesLayout { } return { + bigNumbersDelta: bigNumbersDelta, startLineNumber: startLineNumber, endLineNumber: endLineNumber, relativeVerticalOffset: linesOffsets, diff --git a/src/vs/editor/common/viewLayout/viewLinesViewportData.ts b/src/vs/editor/common/viewLayout/viewLinesViewportData.ts index 91c46eeedd6..8ddcfddb99d 100644 --- a/src/vs/editor/common/viewLayout/viewLinesViewportData.ts +++ b/src/vs/editor/common/viewLayout/viewLinesViewportData.ts @@ -34,6 +34,11 @@ export class ViewportData { */ public readonly visibleRange: Range; + /** + * Value to be substracted from `scrollTop` (in order to vertical offset numbers < 1MM) + */ + public readonly bigNumbersDelta: number; + /** * Positioning information about gaps whitespace. */ @@ -51,6 +56,7 @@ export class ViewportData { this.startLineNumber = partialData.startLineNumber | 0; this.endLineNumber = partialData.endLineNumber | 0; this.relativeVerticalOffset = partialData.relativeVerticalOffset; + this.bigNumbersDelta = partialData.bigNumbersDelta | 0; this.whitespaceViewportData = whitespaceViewportData; this._model = model; diff --git a/src/vs/editor/common/viewModel.ts b/src/vs/editor/common/viewModel.ts index adf6cd307c6..4f92417e89b 100644 --- a/src/vs/editor/common/viewModel.ts +++ b/src/vs/editor/common/viewModel.ts @@ -153,6 +153,10 @@ export interface IWhitespaceChangeAccessor { } export interface IPartialViewLinesViewportData { + /** + * Value to be substracted from `scrollTop` (in order to vertical offset numbers < 1MM) + */ + readonly bigNumbersDelta: number; /** * The first (partially) visible line number. */ From e09b5f48ccfbee54d5d887dd536c76f454096b36 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 15 Jan 2024 05:02:10 -0800 Subject: [PATCH 0312/1897] Filter out consecutive separators in custom menus (#202388) filter out consecutive separators in custom menus --- src/vs/base/browser/ui/menu/menu.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 0a4415803db..35343e4f878 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -259,12 +259,24 @@ export class Menu extends ActionBar { const window = getWindow(container); menuElement.style.maxHeight = `${Math.max(10, window.innerHeight - container.getBoundingClientRect().top - 35)}px`; - actions = actions.filter(a => { + actions = actions.filter((a, idx) => { if (options.submenuIds?.has(a.id)) { console.warn(`Found submenu cycle: ${a.id}`); return false; } + // Filter out consecutive or useless separators + if (a instanceof Separator) { + if (idx === actions.length - 1 || idx === 0) { + return false; + } + + const prevAction = actions[idx - 1]; + if (prevAction instanceof Separator) { + return false; + } + } + return true; }); From 2b0622458245db166a15e9ba41929b247b34267c Mon Sep 17 00:00:00 2001 From: John Murray Date: Mon, 15 Jan 2024 13:20:33 +0000 Subject: [PATCH 0313/1897] Preserve sort order when filtering Git branch / tag quickpicks (fix #199471) (#199473) * Preserve sort order when filtering Git branch / tag quickpicks (fix #199471) * Fix the merge * Dispose quickpick listeners * Changes arising from PR feedback * Rework listener disposal --- extensions/git/package.json | 1 + extensions/git/src/commands.ts | 28 +++++++++++++++++++++++----- extensions/git/tsconfig.json | 1 + 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 913656a1121..6bbafcaeea8 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -27,6 +27,7 @@ "contribMergeEditorMenus", "contribMultiDiffEditorMenus", "contribSourceControlInputBoxMenu", + "quickPickSortByLabel", "contribSourceControlHistoryItemMenu" ], "categories": [ diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index ec71d84a684..ce3d3f0aba0 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -2459,6 +2459,7 @@ export class CommandCenter { picks.push(... await createCheckoutItems(repository, opts?.detached)); quickPick.items = picks; quickPick.busy = false; + quickPick.sortByLabel = false; const choice = await new Promise(c => { disposables.push(quickPick.onDidHide(() => c(undefined))); @@ -2658,6 +2659,22 @@ export class CommandCenter { await repository.branch(branchName, true, target); } + private async pickRef(items: Promise, placeHolder: string): Promise { + const listeners: Disposable[] = []; + const quickPick = window.createQuickPick(); + quickPick.placeholder = placeHolder; + quickPick.sortByLabel = false; + quickPick.items = await items; + quickPick.show(); + const choice = await new Promise(resolve => { + listeners.push(quickPick.onDidHide(() => resolve(undefined))); + listeners.push(quickPick.onDidAccept(() => resolve(quickPick.activeItems[0]))); + }); + quickPick.dispose(); + listeners.forEach(d => d.dispose()); + return choice; + } + @command('git.deleteBranch', { repository: true }) async deleteBranch(repository: Repository, name: string, force?: boolean): Promise { let run: (force?: boolean) => Promise; @@ -2672,7 +2689,7 @@ export class CommandCenter { }; const placeHolder = l10n.t('Select a branch to delete'); - const choice = await window.showQuickPick(getBranchPicks(), { placeHolder }); + const choice = await this.pickRef(getBranchPicks(), placeHolder); if (!choice || !choice.refName) { return; @@ -2737,7 +2754,7 @@ export class CommandCenter { }; const placeHolder = l10n.t('Select a branch or tag to merge from'); - const choice = await window.showQuickPick(getQuickPickItems(), { placeHolder }); + const choice = await this.pickRef(getQuickPickItems(), placeHolder); if (choice instanceof MergeItem) { await choice.run(repository); @@ -2759,7 +2776,7 @@ export class CommandCenter { }; const placeHolder = l10n.t('Select a branch to rebase onto'); - const choice = await window.showQuickPick(getQuickPickItems(), { placeHolder }); + const choice = await this.pickRef(getQuickPickItems(), placeHolder); if (choice instanceof RebaseItem) { await choice.run(repository); @@ -2796,7 +2813,8 @@ export class CommandCenter { }; const placeHolder = l10n.t('Select a tag to delete'); - const choice = await window.showQuickPick(tagPicks(), { placeHolder }); + const choice = await this.pickRef(tagPicks(), placeHolder); + if (choice instanceof TagDeleteItem) { await choice.run(repository); @@ -2944,7 +2962,7 @@ export class CommandCenter { }; const branchPlaceHolder = l10n.t('Pick a branch to pull from'); - const branchPick = await window.showQuickPick(getBranchPicks(), { placeHolder: branchPlaceHolder }); + const branchPick = await this.pickRef(getBranchPicks(), branchPlaceHolder); if (!branchPick || !branchPick.refName) { return; diff --git a/extensions/git/tsconfig.json b/extensions/git/tsconfig.json index b9658b04666..48b9d4227f0 100644 --- a/extensions/git/tsconfig.json +++ b/extensions/git/tsconfig.json @@ -11,6 +11,7 @@ "src/**/*", "../../src/vscode-dts/vscode.d.ts", "../../src/vscode-dts/vscode.proposed.diffCommand.d.ts", + "../../src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts", "../../src/vscode-dts/vscode.proposed.scmActionButton.d.ts", "../../src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts", "../../src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts", From 3ae8c8ea62c0a5cf17d7b5589be8a96acda951b5 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 15 Jan 2024 14:49:01 +0100 Subject: [PATCH 0314/1897] Git - polish view stash command (#202499) * Implement drop stash command * Add apply and pop commands * Rename "preview" command to "view" * Fixed action order * Add icons, update commands --- extensions/git/package.json | 52 +++++++++- extensions/git/package.nls.json | 5 +- extensions/git/src/commands.ts | 98 +++++++++++++++--- .../browser/ui/codicons/codicon/codicon.ttf | Bin 77796 -> 78244 bytes src/vs/base/common/codicons.ts | 3 + .../multiDiffEditor/browser/actions.ts | 1 + 6 files changed, 139 insertions(+), 20 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 6bbafcaeea8..0a9c6904555 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -668,6 +668,13 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.stashPopEditor", + "title": "%command.stashPopEditor%", + "icon": "$(git-stash-pop)", + "category": "Git", + "enablement": "!operationInProgress" + }, { "command": "git.stashApply", "title": "%command.stashApply%", @@ -680,6 +687,13 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.stashApplyEditor", + "title": "%command.stashApplyEditor%", + "icon": "$(git-stash-apply)", + "category": "Git", + "enablement": "!operationInProgress" + }, { "command": "git.stashDrop", "title": "%command.stashDrop%", @@ -693,8 +707,15 @@ "enablement": "!operationInProgress" }, { - "command": "git.stashPreview", - "title": "%command.stashPreview%", + "command": "git.stashDropEditor", + "title": "%command.stashDropEditor%", + "icon": "$(trash)", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.stashView", + "title": "%command.stashView%", "category": "Git", "enablement": "!operationInProgress" }, @@ -1215,6 +1236,10 @@ "command": "git.stashPopLatest", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, + { + "command": "git.stashPopEditor", + "when": "false" + }, { "command": "git.stashApply", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" @@ -1223,6 +1248,10 @@ "command": "git.stashApplyLatest", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, + { + "command": "git.stashApplyEditor", + "when": "false" + }, { "command": "git.stashDrop", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" @@ -1231,6 +1260,10 @@ "command": "git.stashDropAll", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, + { + "command": "git.stashDropEditor", + "when": "false" + }, { "command": "git.timeline.openDiff", "when": "false" @@ -1284,7 +1317,7 @@ "when": "config.git.enabled && !git.missing && git.parentRepositoryCount != 0" }, { - "command": "git.stashPreview", + "command": "git.stashView", "when": "config.git.enabled && !git.missing && config.multiDiffEditor.experimental.enabled" }, { @@ -1837,6 +1870,17 @@ "command": "git.revertSelectedRanges", "group": "2_git@3", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + }, + { + "command": "git.stashApplyEditor", + "alt": "git.stashPopEditor", + "group": "navigation@1", + "when": "config.git.enabled && !git.missing && resourceScheme == git-stash" + }, + { + "command": "git.stashDropEditor", + "group": "navigation@2", + "when": "config.git.enabled && !git.missing && resourceScheme == git-stash" } ], "editor/context": [ @@ -2168,7 +2212,7 @@ "group": "4_drop@2" }, { - "command": "git.stashPreview", + "command": "git.stashView", "when": "config.multiDiffEditor.experimental.enabled", "group": "5_preview@1" } diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index b4db8a31ca8..5cc838d25c3 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -100,11 +100,14 @@ "command.stashStaged": "Stash Staged", "command.stashPop": "Pop Stash...", "command.stashPopLatest": "Pop Latest Stash", + "command.stashPopEditor": "Pop Stash", "command.stashApply": "Apply Stash...", "command.stashApplyLatest": "Apply Latest Stash", + "command.stashApplyEditor": "Apply Stash", "command.stashDrop": "Drop Stash...", "command.stashDropAll": "Drop All Stashes...", - "command.stashPreview": "Preview Stash...", + "command.stashDropEditor": "Drop Stash", + "command.stashView": "View Stash...", "command.timelineOpenDiff": "Open Changes", "command.timelineCopyCommitId": "Copy Commit ID", "command.timelineCopyCommitMessage": "Copy Commit Message", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index ce3d3f0aba0..4b048e51d2b 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3535,6 +3535,17 @@ export class CommandCenter { await repository.popStash(); } + @command('git.stashPopEditor') + async stashPopEditor(uri: Uri): Promise { + const result = await this.getStashFromUri(uri); + if (!result) { + return; + } + + await result.repository.popStash(result.stash.index); + await commands.executeCommand('workbench.action.closeActiveEditor'); + } + @command('git.stashApply', { repository: true }) async stashApply(repository: Repository): Promise { const placeHolder = l10n.t('Pick a stash to apply'); @@ -3559,6 +3570,17 @@ export class CommandCenter { await repository.applyStash(); } + @command('git.stashApplyEditor') + async stashApplyEditor(uri: Uri): Promise { + const result = await this.getStashFromUri(uri); + if (!result) { + return; + } + + await result.repository.applyStash(result.stash.index); + await commands.executeCommand('workbench.action.closeActiveEditor'); + } + @command('git.stashDrop', { repository: true }) async stashDrop(repository: Repository): Promise { const placeHolder = l10n.t('Pick a stash to drop'); @@ -3568,18 +3590,7 @@ export class CommandCenter { return; } - // request confirmation for the operation - const yes = l10n.t('Yes'); - const result = await window.showWarningMessage( - l10n.t('Are you sure you want to drop the stash: {0}?', stash.description), - { modal: true }, - yes - ); - if (result !== yes) { - return; - } - - await repository.dropStash(stash.index); + await this._stashDrop(repository, stash); } @command('git.stashDropAll', { repository: true }) @@ -3605,9 +3616,36 @@ export class CommandCenter { await repository.dropStash(); } - @command('git.stashPreview', { repository: true }) - async stashPreview(repository: Repository): Promise { - const placeHolder = l10n.t('Pick a stash to preview'); + @command('git.stashDropEditor') + async stashDropEditor(uri: Uri): Promise { + const result = await this.getStashFromUri(uri); + if (!result) { + return; + } + + if (await this._stashDrop(result.repository, result.stash)) { + await commands.executeCommand('workbench.action.closeActiveEditor'); + } + } + + async _stashDrop(repository: Repository, stash: Stash): Promise { + const yes = l10n.t('Yes'); + const result = await window.showWarningMessage( + l10n.t('Are you sure you want to drop the stash: {0}?', stash.description), + { modal: true }, + yes + ); + if (result !== yes) { + return false; + } + + await repository.dropStash(stash.index); + return true; + } + + @command('git.stashView', { repository: true }) + async stashView(repository: Repository): Promise { + const placeHolder = l10n.t('Pick a stash to view'); const stash = await this.pickStash(repository, placeHolder); if (!stash) { @@ -3646,6 +3684,36 @@ export class CommandCenter { return result?.stash; } + private async getStashFromUri(uri: Uri | undefined): Promise<{ repository: Repository; stash: Stash } | undefined> { + if (!uri || uri.scheme !== 'git-stash') { + return undefined; + } + + const stashUri = fromGitUri(uri); + + // Repository + const repository = this.model.getRepository(stashUri.path); + if (!repository) { + return undefined; + } + + // Stash + const regex = /^stash@{(\d+)}$/; + const match = regex.exec(stashUri.ref); + if (!match) { + return undefined; + } + + const [, index] = match; + const stashes = await repository.getStashes(); + const stash = stashes.find(stash => stash.index === parseInt(index)); + if (!stash) { + return undefined; + } + + return { repository, stash }; + } + @command('git.timeline.openDiff', { repository: false }) async timelineOpenDiff(item: TimelineItem, uri: Uri | undefined, _source: string) { const cmd = this.resolveTimelineOpenDiffCommand( diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 8ccc035403b32077d1a3e118d84355a2e87fd706..cf782c9395efaad94ff7dbe7d6abd7e23f4c6e4c 100644 GIT binary patch delta 5388 zcma*r33yi3od)ptO~R6pgcyini4YPcC;>u379fNW0t6DagoLn#BqU+ULLgyr<3mKG zNEP^SDN;qHN)b0g0TB_bRYXfIbr{DwZAIGA+^Md;#bczp&xfx;?Y*x(xK+4qVu` zsJ5!6{NY_CI`?;yu}B{T%?S!<|ES!!sIhf*M2By$>U#RDQ%J+o1yx@Kr-TEEQ9$te z#;VoLLFagvzVD&)=Ry8&7~Xa)Br7DNL&$gMA@~~vf47&c3-Nf7=W7rUVA~lS4CW102H~yu=~g#2dJPowyi9JjhbcX9ey@ItSoxoWh@x z%Z*rx>)D1!a1TfFdPXpkKg1h&3Wxb1S70MIu!(o_b}r=_-ov%L6-U_0b^eo~@A!U# zgLoOQ;58h_Pw_LniMMbPr|}EChhO3heuek(Z)nFK@j3p4OZWYrZaavG=lOzqYCjM0Fkj{o`d;B{JdQ7L0T=Nl=Ai*`XaXlK)j3p>Xe>ctd5o{}0dC<|Zlj;uc|WehbjD!^ ze}aX07a!o?aTZfC1(VSoKgT{ik22J9GM>N!#NbsNV_)<|Jg%S{_pvKJ#ed*K{1*R- z-{51M!+HDx|Amk6Yu<;qnSy)K5uMN(Gehxvy*ZztH(tj#_!~x`Cwia|FW^O%U=qt% zi+9+8k8lUZu?o9+04eOlE)2n5OoPXK)?h8}WFzO|0QRFzuSUnVkHdrLgWfYBCeCg3 z=?OjSmFBzWzDoN7y_xORhGUcqjNSmW*BHE{tTh~>tTWuCTxfWMa*?6C#`MM72sHH3T`FuUu+a;ol#bIBI~7T8v&ow6_|)j%Z(D^kSlYr9rN8 zmElHzbk{_-`7^pE`bO%rHHOzKeTEUrwT6+(yNq6Hw68OIz0vN<7QN_bUvF?&d5__P z$_+-t0NU?0*r*hr+t8ih+ED1Ke4pW+%FTwiD<3d)O|iw$-TYQVSLilFck_Ouu?FoL zsT~|qx| zr(Y$G21|I?84a88qKyVnc-@SKQh42s232@5M#HNQo*n~7BQ3mGqcInrem6N9ec|;q z8i(QaG8&QL^)?!t;pvTYG)lwMTjAhaWgnx4h1b{6wQ{_p=Z~)0`Wd>WN-%Uym1r2I zywT9L)J=x275W>xR!A~*wH;vSYJ0PBzi_=<3|*ys1GN#lyGb^51r0KE1r0WI1r0HD z1*I6eiw`w)7f&^G*G@BZ*B)l*uAOdtbQjMsbSGyTx|6dE-O0lZ-MJ$S-MJ$T-MQI@ z?%Yv^?pzIO4&C>o4c+&-uK#oOh5K-fp^m*gLziO>UFI8(Q(nXI+P{Vqw0{i?w0{jJ zYX2He(f%aEsp@~P&~Tcv$Z)!HvSG2({g7~m(w!%qrF7>BXDi)#!a2$k!+A>gGr~$` zsbRIUOnlm~PCGLV7b?pQ7b#sK!X-*qh_GJi3K2Fd=Nc|kx&npEl@*39%K3&XmF`A_ ztK9ovr32w=rMoNPZOR3PcPML&e!0P`HE#Ooxf>VWt#mgoZVKzU8y9|Gx!CX_rK^PS zVWq1?z51U&&`yKlqsm6ZoysP|$CMg{9X_sfwHEGDx>^gLP`X+RpH#Y93!hfD8tzf9 zF#NG{rK^#egnN~04u{VvU26!RSKey)g3`5&@FnH#hWnMSm4t_scN!j6t}%RB>BGt}{HYbj>Q7sli)sG-rc%kJ0Q6-Ufp&{dK)dxZZ!HcX1Gd{E=~P z$`0=l!)N?CaYKBc>F9@su}ashLiYwdZg{WKHLGZ%2hTODz}3t(tFT=8l;KR}Zo}Tn z9~rtXbxki!R6gS#Hf=Otgy;G};C|TkgJ=#3&vk=ftnyi-c_qAkMl(!!u6x9-bv)NS zf~!i`J;H8EcP-IO6rSrU!Oig3-w+njPls)Wz5MHL7~=b$j$B^}U7@b8gsvv8uY|6) zuB${7S$M8+fxDSk4JRp&85SvBcM7AG#|;)MUB3w3HC}hEq7B^-{?yQ2@D0NUls_}v zqI}bEtMVqxm_!Ge$FYc)v25v%`De;H*Eg?@0dKKS|rk{tbPH^mG;b#K2YfykTGe>As1+ zD?0krz;)thhOYD5jb{1qJfrzOynxZnAKo90)&TH6H(CwA`@(2l0Plj)3IX1qjMfV9 zF1miuM#~0xUm7hS;9W9WO2GTdXi)+0veEJa-q!}dR=P@vmK^Z@Y~cFis?jn8-d~Is zB=G)fv^4R55kGk1@3k_Z!S9qoMvD~$f{m6f2!t3dU=ZkFw3I=hqru<&b^S_8OLP=z zprsfG$d|F-jH2G-b+bMX-e6da&Bne z(B(t74E;@NbZSQG?$oPk8EJKC2Zl8adn`R9y&?U0Mo>mdMnOhp#+r<$Gfrg$GQ%+o;}%7pb3&Q1sv#1&K*>?qh@ z@L|E#iL)o(KdHy0m6JXw3@h}_F5FNUD2gk}E806uOGlKJmu@eM zFWWRTWM0(8sG6dhEj4Fq zBWq{W?y9{~7gtwT_sznx#bx#3^;z|E>rd8SXozbV)==GWpy6EOu*Ne@;Z1Q($xYc! zMNRvf0!x#Zu3UPwIk~y4dH1qgmc6;`^74Y^w=F-|(l5MaO3RLxBdy7;8(N=kJ-;Gq z#gY{pR~D^2x~gK;+10&Pm$l99|7@wRlUA3Z`p=AhxJkbdkQtMf9-Enz*eSLvV`9@X z(~}Y-nb1EeHZ3MGJ*^K@Gm^9yBX@U8jmV40i_8cO{WvTvFQiw$Ucrl#PY>)IpAj)t}vC=>sn=znVuGt8QZBiF)8z()%0H#_FO~5(1^Ueh&lhaazNX+x1V~HCk~y8 zn?wKS=+M*~`Cu0f Gb$ zwT;S}nKKo|GUr@twt08AX5G8HySvT1T(j#ia?KgJX8Zj5?vMBRT!-u8cYf!4&iTH6 zXa8p*mp=*F5#w71a4Ue)in_AK?S)fz0NpMF5!aX0K3qNjn~XC+Y%%cVoSLe#%A%)R z3-sGdl3b%d1T75uLZ45|(3-lY)nV(uyP@$qfY7emhKjP?*B2xLLp}zA*VUD+ZVdW_ z>-Bwq{XV+Btgg!c^?=uBfOj6zAG;bGRyLh@@k}w;?*Pz$?9S6MJg{9P_naQ zB+vMxc$CB1-eYHwKR)=|wu>P(`q1rzsF0iA{}#br2>$+4oU%KhzlQ|Bh$~Zp30W4(*tC7Pt=Cha!u?Tc^miP zS9k#@xQi>WnU8ZBAK_}&@=-pIRJmW^EZ)Sg@fLoA|G+!=PrQrY<30QV7w~5U;Gq+L#aH+nuAvLx;9L9?HyO-Z z7|L52#-8lO+u4VA@-7bI-5kOg4rMIw;k~?%!+AdwIg+C|nh!9E$xPuGrumr8ah$*m zPU2)v;Z#oJ4CZhqb2*E%IhXTT#06Z$#jIc@tGI+UtYbYJ*vOS^;zL};hq;F9*uo!h z3;o>9J>1J5@fkkLAM+>tDPQ1=e2G8fLB7I6Jj_>loF~}Mlk~mD)BFWG@E3fEt5}2* z)L{(jQKtK&7L8bnBE;eze1|RAjx*e^E7}j`_?&&2#SI*c<1FMH_UD6)W<2Kc=a{V9 zsEA9e40$_Q0R;NBlQF z#z**HT*N2%e|(0^_#b@8%{a#qcmkog72Pm144>lv^c)SuFYyoDKpG;@A2aYOjxZ0| zEMPf)%OF0*r!kc!*w3R#KaS(a9!ig+H3)XTe=eKqC=s{oKEsnqelt>wH60C z%38xVWu0NZvffZFCeUEG(EnDim~q3jx6H4FLgH zD#2!@_}qqWkS&I;EKeFfqTFh@TDjfOl}nx2;iJl(hHhf+fWoy(zfspgV7I|ZnyHEN>p?ld}cAH2J?BkHX1`Wf|Dc>RsKExZV$z6(!xyrT{bZ=g{xh8Jnn zmEjFC>d)|^j5;;E!3H;#(T?svy3*cl=*l_7(3LaB(3Nwjp)27qLszL-LszML3|&R; zHFOoZ&(NLua6@|L-S^WCXDG7`b9DVZ_Xpuj zWsYI4((MapDc!zswldFfuF~xb=PC0Iib^*x#3TgD-3_CbZ07jL8&u!_@dICsPH9av(Ig`^E0J8YvDoVYQtBQ zt^&eC%0~0WI}hKR-=U-yypzunLTeQp}P!1H!(M`(4Flm!&c=RMhjPX?otWdWZb0^W-49Hh0~S4H0-TB zYv8K#D?@jjH-T!mp&R_yhVGzm8TU5m{l@Tz%5#R>ly4huSN_&;hw^uZJC)}RpY$Jy ziYbiH-n$0-l)pDxV#9mSXt53N4@S#wco&Qo;PBozT8hK_H@xqU9z2%k{l(g5`}Yn` zh;Z}&%)p)3Wy47SrNJ@2ziaP`!5zvk3}03Tj27?kJfmeiybhxUJ-ojdE$!iT8ZGkS zeQC7Zhj-Oz;ScYxMy~+yzH;T(MsEY~{$})E0PmX78v;BxLD9Pcyz5494e;DeCiqa< zW%MQi?;8VmX>J&f@L!3}oc<}$q5Id-3kEuZ3@#~yjb1v?5n}ZEfsR{@UPRClYH-89 z`tE|lJni)|$X4pUa#)}YH+oS)$L$8XB|7>Tx+AOD4xdu?HGJA1J)~bmiT3&#>{s?T zJnGNWw{C|2#%}r?X-I#|keCQv%8qEmiORbT%lxN@#PnXOy^ge=II8 z?%as15t~Nr8*wQU?Y*OE(xk($6o=rNQbRp?Ra#V6ga!K-<ZytYad|<-x2_+K_Pq>uPD2CJx?<{U(|SzXFzv(4;h7nkjhXG4omrt-@maZ9hqFGNUOWAKc3gHz z_TlW0W@OCpHO|;Gqcf*_PXC+>GgD_4&-`OM`Hs{csD}_yk7v~nuZJmdC&GQb=yHYf% zsJ-ah`8D&eE@)ry!Gf~`kkC%5=R93WBT&|3*EU!FJd95m^s@C;qI2b-UMpKxezfvNQ)*LX)0w7^A8Kt5Z$7qa_^SF<2X%|= hj(w%D?di2|KEpF77esJP@X3!O85VZ3XDpw(?O)tmUi<(6 diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 37343ad65c6..09ee9693d23 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -588,6 +588,9 @@ export const Codicon = { diffSingle: register('diff-single', 0xec22), diffMultiple: register('diff-multiple', 0xec23), surroundWith: register('surround-with', 0xec24), + gitStash: register('git-stash', 0xec26), + gitStashApply: register('git-stash-apply', 0xec27), + gitStashPop: register('git-stash-pop', 0xec28), // derived icons, that could become separate icons diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts index 296861e895e..631376ed1a0 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts @@ -48,6 +48,7 @@ export class CollapseAllAction extends Action2 { when: ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), ContextKeyExpr.not('multiDiffEditorAllCollapsed')), id: MenuId.EditorTitle, group: 'navigation', + order: 100 }, f1: true, }); From ee5f2226b78d5f70b384102986cac59aff8eac1b Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 15 Jan 2024 15:13:51 +0100 Subject: [PATCH 0315/1897] Fix Sticky Scroll Double Header in Settings UI (#202506) fix #202090 --- src/vs/base/browser/ui/list/listWidget.ts | 4 ++ src/vs/base/browser/ui/tree/abstractTree.ts | 49 +++++++++++---------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 4e497b12fbd..4d61198158c 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -1580,6 +1580,10 @@ export class List implements ISpliceable, IDisposable { return this.view.indexOf(element); } + indexAt(position: number): number { + return this.view.indexAt(position); + } + get length(): number { return this.view.length; } diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 920e8234bfc..f6d5ac1ad81 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1265,16 +1265,6 @@ class StickyScrollController extends Disposable { private readonly _widget: StickyScrollWidget; - private get firstVisibleNode() { - const index = this.view.firstVisibleIndex; - - if (index < 0 || index >= this.view.length) { - return undefined; - } - - return this.view.element(index); - } - constructor( private readonly tree: AbstractTree, private readonly model: ITreeModel, @@ -1313,8 +1303,23 @@ class StickyScrollController extends Disposable { return this._widget.getNode(node); } + private getNodeAtHeight(height: number): ITreeNode | undefined { + let index; + if (height === 0) { + index = this.view.firstVisibleIndex; + } else { + index = this.view.indexAt(height + this.view.scrollTop); + } + + if (index < 0 || index >= this.view.length) { + return undefined; + } + + return this.view.element(index); + } + private update() { - const firstVisibleNode = this.firstVisibleNode; + const firstVisibleNode = this.getNodeAtHeight(0); // Don't render anything if there are no elements if (!firstVisibleNode || this.tree.scrollTop === 0) { @@ -1338,7 +1343,7 @@ class StickyScrollController extends Disposable { stickyNodesHeight += nextStickyNode.height; if (stickyNodes.length <= this.stickyScrollMaxItemCount) { - firstVisibleNodeUnderWidget = this.getNextVisibleNode(firstVisibleNodeUnderWidget); + firstVisibleNodeUnderWidget = this.getNextVisibleNode(nextStickyNode); if (!firstVisibleNodeUnderWidget) { break; } @@ -1351,13 +1356,8 @@ class StickyScrollController extends Disposable { return contrainedStickyNodes.length ? new StickyScrollState(contrainedStickyNodes) : undefined; } - private getNextVisibleNode(node: ITreeNode): ITreeNode | undefined { - const nodeIndex = this.getNodeIndex(node); - if (nodeIndex === -1 || nodeIndex === this.view.length - 1) { - return undefined; - } - const nextNode = this.view.element(nodeIndex + 1); - return nextNode; + private getNextVisibleNode(previousStickyNode: StickyScrollNode): ITreeNode | undefined { + return this.getNodeAtHeight(previousStickyNode.position + previousStickyNode.height); } private getNextStickyNode(firstVisibleNodeUnderWidget: ITreeNode, previousStickyNode: ITreeNode | undefined, stickyNodesHeight: number): StickyScrollNode | undefined { @@ -1390,7 +1390,7 @@ class StickyScrollController extends Disposable { const height = this.treeDelegate.getHeight(node); const { startIndex, endIndex } = this.getNodeRange(node); - const position = this.calculateStickyNodePosition(endIndex, currentStickyNodesHeight); + const position = this.calculateStickyNodePosition(endIndex, currentStickyNodesHeight, height); return { node, position, height, startIndex, endIndex }; } @@ -1414,7 +1414,7 @@ class StickyScrollController extends Disposable { return undefined; } - private calculateStickyNodePosition(lastDescendantIndex: number, stickyRowPositionTop: number): number { + private calculateStickyNodePosition(lastDescendantIndex: number, stickyRowPositionTop: number, stickyNodeHeight: number): number { let lastChildRelativeTop = this.view.getRelativeTop(lastDescendantIndex); // If the last descendant is only partially visible at the top of the view, getRelativeTop() returns null @@ -1434,8 +1434,8 @@ class StickyScrollController extends Disposable { const topOfLastChild = lastChildRelativeTop * this.view.renderHeight; const bottomOfLastChild = topOfLastChild + lastChildHeight; - if (stickyRowPositionTop > topOfLastChild && stickyRowPositionTop <= bottomOfLastChild) { - return topOfLastChild; + if (stickyRowPositionTop + stickyNodeHeight > bottomOfLastChild && stickyRowPositionTop <= bottomOfLastChild) { + return bottomOfLastChild - stickyNodeHeight; } return stickyRowPositionTop; @@ -1641,10 +1641,11 @@ class StickyScrollWidget implements IDisposable { this.stickyScrollFocus.updateElements(elements, state); - this._previousState = state; this._previousElements = elements; } + this._previousState = state; + // Set the height of the widget to the bottom of the last sticky node this._rootDomNode.style.height = `${lastStickyNode.position + lastStickyNode.height}px`; } From b94347eebfc0484b23ee7bb011b8a7921cd8dfa9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 15 Jan 2024 15:30:41 +0100 Subject: [PATCH 0316/1897] debt - ensure `partOptions` are accessible only from service (#202511) Otherwise we have a potential timing problem before the options propagate down to the floating windows. --- .../browser/parts/editor/editorsObserver.ts | 14 ++++++------- .../browser/parts/titlebar/titlebarPart.ts | 6 +++--- .../editor/common/editorGroupsService.ts | 20 +++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index c6a09b4fcfd..ac30da415d2 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -97,7 +97,7 @@ export class EditorsObserver extends Disposable { private registerListeners(): void { this._register(this.editorGroupsContainer.onDidAddGroup(group => this.onGroupAdded(group))); - this._register(this.editorGroupsContainer.onDidChangeEditorPartOptions(e => this.onDidChangeEditorPartOptions(e))); + this._register(this.editorGroupService.onDidChangeEditorPartOptions(e => this.onDidChangeEditorPartOptions(e))); this._register(this.storageService.onWillSaveState(() => this.saveState())); } @@ -310,17 +310,17 @@ export class EditorsObserver extends Disposable { private async ensureOpenedEditorsLimit(exclude: IEditorIdentifier | undefined, groupId?: GroupIdentifier): Promise { if ( - !this.editorGroupsContainer.partOptions.limit?.enabled || - typeof this.editorGroupsContainer.partOptions.limit.value !== 'number' || - this.editorGroupsContainer.partOptions.limit.value <= 0 + !this.editorGroupService.partOptions.limit?.enabled || + typeof this.editorGroupService.partOptions.limit.value !== 'number' || + this.editorGroupService.partOptions.limit.value <= 0 ) { return; // return early if not enabled or invalid } - const limit = this.editorGroupsContainer.partOptions.limit.value; + const limit = this.editorGroupService.partOptions.limit.value; // In editor group - if (this.editorGroupsContainer.partOptions.limit?.perEditorGroup) { + if (this.editorGroupService.partOptions.limit?.perEditorGroup) { // For specific editor groups if (typeof groupId === 'number') { @@ -349,7 +349,7 @@ export class EditorsObserver extends Disposable { // Check for `excludeDirty` setting and apply it by excluding // any recent editor that is dirty from the opened editors limit let mostRecentEditorsCountingForLimit: IEditorIdentifier[]; - if (this.editorGroupsContainer.partOptions.limit?.excludeDirty) { + if (this.editorGroupService.partOptions.limit?.excludeDirty) { mostRecentEditorsCountingForLimit = mostRecentEditors.filter(({ editor }) => { if ((editor.isDirty() && !editor.isSaving()) || editor.hasCapability(EditorInputCapabilities.Scratchpad)) { return false; // not dirty editors (unless in the process of saving) or scratchpads diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index c66a4dabd81..3ca421396a6 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -701,10 +701,10 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } private get editorActionsEnabled(): boolean { - return this.editorGroupsContainer.partOptions.editorActionsLocation === 'titleBar' || + return this.editorGroupService.partOptions.editorActionsLocation === 'titleBar' || ( - this.editorGroupsContainer.partOptions.editorActionsLocation === 'default' && - this.editorGroupsContainer.partOptions.showTabs === 'none' + this.editorGroupService.partOptions.editorActionsLocation === 'default' && + this.editorGroupService.partOptions.showTabs === 'none' ); } diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index f2d29edc549..bd39f81d3cc 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -410,16 +410,6 @@ export interface IEditorGroupsContainer { */ copyGroup(group: IEditorGroup | GroupIdentifier, location: IEditorGroup | GroupIdentifier, direction: GroupDirection): IEditorGroup; - /** - * Access the options of the editor part. - */ - readonly partOptions: IEditorPartOptions; - - /** - * An event that notifies when editor part options change. - */ - readonly onDidChangeEditorPartOptions: Event; - /** * Allows to register a drag and drop target for editors * on the provided `container`. @@ -526,6 +516,16 @@ export interface IEditorGroupsService extends IEditorGroupsContainer { */ getPart(container: unknown /* HTMLElement */): IEditorPart; + /** + * Access the options of the editor part. + */ + readonly partOptions: IEditorPartOptions; + + /** + * An event that notifies when editor part options change. + */ + readonly onDidChangeEditorPartOptions: Event; + /** * Opens a new window with a full editor part instantiated * in there at the optional position and size on screen. From e5dcf413bd38f36904c4bcfa384b5efa6b98d7ea Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 15 Jan 2024 20:20:03 +0530 Subject: [PATCH 0317/1897] report user and workspace modified configurations (#202489) --- .../standalone/browser/standaloneServices.ts | 1 - .../configuration/common/configuration.ts | 3 - .../common/configurationModels.ts | 1 - .../common/configurationService.ts | 11 --- .../telemetry/common/telemetryUtils.ts | 31 +----- .../browser/parts/sidebar/sidebarPart.ts | 12 --- .../browser/extensionsWorkbenchService.ts | 8 -- .../notebook/browser/notebookOptions.ts | 1 - .../browser/telemetry.contribution.ts | 5 +- .../browser/configurationService.ts | 95 +++++++++++++++---- 10 files changed, 81 insertions(+), 87 deletions(-) diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 1e915d95a25..ec7e0075f74 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -655,7 +655,6 @@ export class StandaloneConfigurationService implements IConfigurationService { if (changedKeys.length > 0) { const configurationChangeEvent = new ConfigurationChangeEvent({ keys: changedKeys, overrides: [] }, previous, this._configuration); configurationChangeEvent.source = ConfigurationTarget.MEMORY; - configurationChangeEvent.sourceConfig = null; this._onDidChangeConfiguration.fire(configurationChangeEvent); } diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index eb419a6929a..ca25bf1cad3 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -68,9 +68,6 @@ export interface IConfigurationChangeEvent { readonly change: IConfigurationChange; affectsConfiguration(configuration: string, overrides?: IConfigurationOverrides): boolean; - - // Following data is used for telemetry - readonly sourceConfig: any; } export interface IConfigurationValue { diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 0e609c9ba1d..09d12675520 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -1087,7 +1087,6 @@ export class ConfigurationChangeEvent implements IConfigurationChangeEvent { readonly affectedKeys = new Set(); source!: ConfigurationTarget; - sourceConfig: any; constructor(readonly change: IConfigurationChange, private readonly previous: { workspace?: Workspace; data: IConfigurationData } | undefined, private readonly currentConfiguraiton: Configuration, private readonly currentWorkspace?: Workspace) { for (const key of change.keys) { diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index a985ac1bf14..c040a98c145 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -159,19 +159,8 @@ export class ConfigurationService extends Disposable implements IConfigurationSe private trigger(configurationChange: IConfigurationChange, previous: IConfigurationData, source: ConfigurationTarget): void { const event = new ConfigurationChangeEvent(configurationChange, { data: previous }, this.configuration); event.source = source; - event.sourceConfig = this.getTargetConfiguration(source); this._onDidChangeConfiguration.fire(event); } - - private getTargetConfiguration(target: ConfigurationTarget): any { - switch (target) { - case ConfigurationTarget.DEFAULT: - return this.configuration.defaults.contents; - case ConfigurationTarget.USER: - return this.configuration.localUserConfiguration.contents; - } - return {}; - } } class ConfigurationEditing { diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index fa443c0fd69..5deb494c3b4 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -3,12 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable } from 'vs/base/common/lifecycle'; import { cloneAndChange, safeStringify } from 'vs/base/common/objects'; import { isObject } from 'vs/base/common/types'; -import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { ConfigurationTarget, ConfigurationTargetToString, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IProductService } from 'vs/platform/product/common/productService'; import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; @@ -81,33 +79,6 @@ export interface URIDescriptor { path?: string; } -export function configurationTelemetry(telemetryService: ITelemetryService, configurationService: IConfigurationService): IDisposable { - // Debounce the event by 1000 ms and merge all affected keys into one event - const debouncedConfigService = Event.debounce(configurationService.onDidChangeConfiguration, (last, cur) => { - const newAffectedKeys: ReadonlySet = last ? new Set([...last.affectedKeys, ...cur.affectedKeys]) : cur.affectedKeys; - return { ...cur, affectedKeys: newAffectedKeys }; - }, 1000, true); - - return debouncedConfigService(event => { - if (event.source !== ConfigurationTarget.DEFAULT) { - type UpdateConfigurationClassification = { - owner: 'lramos15, sbatten'; - comment: 'Event which fires when user updates settings'; - configurationSource: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'What configuration file was updated i.e user or workspace' }; - configurationKeys: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'What configuration keys were updated' }; - }; - type UpdateConfigurationEvent = { - configurationSource: string; - configurationKeys: string[]; - }; - telemetryService.publicLog2('updateConfiguration', { - configurationSource: ConfigurationTargetToString(event.source), - configurationKeys: Array.from(event.affectedKeys) - }); - } - }); -} - /** * Determines whether or not we support logging telemetry. * This checks if the product is capable of collecting telemetry but not whether or not it can send it diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 2b70ce85cee..e0b21f32517 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -28,8 +28,6 @@ import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { IPaneCompositeBarOptions } from 'vs/workbench/browser/parts/paneCompositeBar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Action2, IMenuService, registerAction2 } from 'vs/platform/actions/common/actions'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Separator } from 'vs/base/common/actions'; import { ToggleActivityBarVisibilityActionId } from 'vs/workbench/browser/actions/layoutActions'; import { localize } from 'vs/nls'; @@ -79,8 +77,6 @@ export class SidebarPart extends AbstractPaneCompositePart { @IContextKeyService contextKeyService: IContextKeyService, @IExtensionService extensionService: IExtensionService, @IConfigurationService private readonly configurationService: IConfigurationService, - @ITelemetryService telemetryService: ITelemetryService, - @ILifecycleService lifecycleService: ILifecycleService, @IMenuService menuService: IMenuService, ) { super( @@ -114,14 +110,6 @@ export class SidebarPart extends AbstractPaneCompositePart { })); this.registerActions(); - - lifecycleService.when(LifecyclePhase.Eventually).then(() => { - telemetryService.publicLog2<{ location: string }, { - owner: 'sandy081'; - location: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Locaiton where the activity bar is shown' }; - comment: 'This is used to know where activity bar is shown in the workbench.'; - }>('activityBar:location', { location: configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) }); - }); } private onDidChangeActivityBarLocation(): void { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 889b216554b..65b2e09affa 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -804,14 +804,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension urlService.registerHandler(this); this.whenInitialized = this.initialize(); - - lifecycleService.when(LifecyclePhase.Eventually).then(() => { - telemetryService.publicLog2<{ mode: string }, { - owner: 'sandy081'; - mode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Auto Update Mode' }; - comment: 'This is used to know if extensions are getting auto updated or not'; - }>('extensions:autoupdate', { mode: `${this.getAutoUpdateValue()}` }); - }); } private async initialize(): Promise { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts index 9e4c743e3ef..eb4bbe768ac 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts @@ -269,7 +269,6 @@ export class NotebookOptions extends Disposable { source: ConfigurationTarget.DEFAULT, affectedKeys: new Set([NotebookSetting.insertToolbarLocation]), change: { keys: [NotebookSetting.insertToolbarLocation], overrides: [] }, - sourceConfig: undefined }); } } diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index 91a902f4b20..5293305cc56 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -15,7 +15,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { language } from 'vs/base/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; -import { configurationTelemetry, TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUtils'; +import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUtils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITextFileService, ITextFileSaveEvent, ITextFileResolveEvent } from 'vs/workbench/services/textfile/common/textfiles'; import { extname, basename, isEqual, isEqualOrParent } from 'vs/base/common/resources'; @@ -126,9 +126,6 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr // Error Telemetry this._register(new ErrorTelemetry(telemetryService)); - // Configuration Telemetry - this._register(configurationTelemetry(telemetryService, configurationService)); - // Files Telemetry this._register(textFileService.files.onDidResolve(e => this.onTextFileModelResolved(e))); this._register(textFileService.files.onDidSave(e => this.onTextFileModelSaved(e))); diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 7784cde557f..58761151288 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -37,7 +37,7 @@ import { delta, distinct, equals as arrayEquals } from 'vs/base/common/arrays'; import { IStringDictionary } from 'vs/base/common/collections'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; -import { isUndefined } from 'vs/base/common/types'; +import { isBoolean, isNumber, isString, isUndefined } from 'vs/base/common/types'; import { localize } from 'vs/nls'; import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; @@ -47,6 +47,7 @@ import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/envir import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { mainWindow } from 'vs/base/browser/window'; import { runWhenWindowIdle } from 'vs/base/browser/dom'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; function getLocalUserConfigurationScopes(userDataProfile: IUserDataProfile, hasRemote: boolean): ConfigurationScope[] | undefined { return (userDataProfile.isDefault || userDataProfile.useDefaultFlags?.settings) @@ -1018,7 +1019,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } if (overrides?.overrideIdentifiers?.length && overrides.overrideIdentifiers.length > 1) { - const configurationModel = this.getConfigurationModel(editableConfigurationTarget, overrides.resource); + const configurationModel = this.getConfigurationModelForEditableConfigurationTarget(editableConfigurationTarget, overrides.resource); if (configurationModel) { const overrideIdentifiers = overrides.overrideIdentifiers.sort(); const existingOverrides = configurationModel.overrides.find(override => arrayEquals([...override.identifiers].sort(), overrideIdentifiers)); @@ -1052,7 +1053,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } } - private getConfigurationModel(target: EditableConfigurationTarget, resource?: URI | null): ConfigurationModel | undefined { + private getConfigurationModelForEditableConfigurationTarget(target: EditableConfigurationTarget, resource?: URI | null): ConfigurationModel | undefined { switch (target) { case EditableConfigurationTarget.USER_LOCAL: return this._configuration.localUserConfiguration; case EditableConfigurationTarget.USER_REMOTE: return this._configuration.remoteUserConfiguration; @@ -1061,6 +1062,16 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } } + getConfigurationModel(target: ConfigurationTarget, resource?: URI | null): ConfigurationModel | undefined { + switch (target) { + case ConfigurationTarget.USER_LOCAL: return this._configuration.localUserConfiguration; + case ConfigurationTarget.USER_REMOTE: return this._configuration.remoteUserConfiguration; + case ConfigurationTarget.WORKSPACE: return this._configuration.workspaceConfiguration; + case ConfigurationTarget.WORKSPACE_FOLDER: return resource ? this._configuration.folderConfigurations.get(resource) : undefined; + default: return undefined; + } + } + private deriveConfigurationTargets(key: string, value: any, inspect: IConfigurationValue): ConfigurationTarget[] { if (equals(value, inspect.value)) { return []; @@ -1095,23 +1106,10 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } const configurationChangeEvent = new ConfigurationChangeEvent(change, previous, this._configuration, this.workspace); configurationChangeEvent.source = target; - configurationChangeEvent.sourceConfig = this.getTargetConfiguration(target); this._onDidChangeConfiguration.fire(configurationChangeEvent); } } - private getTargetConfiguration(target: ConfigurationTarget): any { - switch (target) { - case ConfigurationTarget.DEFAULT: - return this._configuration.defaults.contents; - case ConfigurationTarget.USER: - return this._configuration.userConfiguration.contents; - case ConfigurationTarget.WORKSPACE: - return this._configuration.workspaceConfiguration.contents; - } - return {}; - } - private toEditableConfigurationTarget(target: ConfigurationTarget, key: string): EditableConfigurationTarget | null { if (target === ConfigurationTarget.USER) { if (this.remoteUserConfiguration) { @@ -1322,6 +1320,70 @@ class ResetConfigurationDefaultsOverridesCache extends Disposable implements IWo } } +class ConfigurationTelemetryContribution extends Disposable implements IWorkbenchContribution { + + private readonly configurationRegistry = Registry.as(Extensions.Configuration); + + constructor( + @IConfigurationService private readonly configurationService: WorkspaceService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + ) { + super(); + + const { user, workspace } = configurationService.keys(); + for (const key of user) { + this.reportConfiguration(key, ConfigurationTarget.USER_LOCAL); + } + for (const key of workspace) { + this.reportConfiguration(key, ConfigurationTarget.WORKSPACE); + } + } + + private reportConfiguration(key: string, target: ConfigurationTarget): void { + type UpdateConfigurationClassification = { + owner: 'sandy081'; + comment: 'Event which fires for updated configurations'; + source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'What configuration file was updated i.e user or workspace' }; + key: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'What configuration key was updated' }; + value?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Value of the key that was updated' }; + }; + type UpdateConfigurationEvent = { + source: string; + key: string; + value?: any; + }; + this.telemetryService.publicLog2('updateConfiguration', { + source: ConfigurationTargetToString(target), + key, + value: this.getValueToReport(key, target) + }); + } + + private getValueToReport(key: string, target: ConfigurationTarget): any { + const schema = this.configurationRegistry.getConfigurationProperties()[key]; + if (!schema) { + return undefined; + } + const configurationModel = this.configurationService.getConfigurationModel(target); + const value = configurationModel?.getValue(key); + if (isNumber(value) || isBoolean(value)) { + return value; + } + if (isString(value)) { + if (schema.enum?.includes(value)) { + return value; + } + return undefined; + } + if (Array.isArray(value)) { + if (value.every(v => isNumber(v) || isBoolean(v) || (isString(v) && schema.enum?.includes(v)))) { + return value; + } + } + return undefined; + } +} + class UpdateExperimentalSettingsDefaults extends Disposable implements IWorkbenchContribution { private readonly processedExperimentalSettings = new Set(); @@ -1364,6 +1426,7 @@ const workbenchContributionsRegistry = Registry.as(Extensions.Configuration); configurationRegistry.registerConfiguration({ From b245a42ed008ad595da237fc937e69e861dbe07e Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 15 Jan 2024 15:51:19 +0100 Subject: [PATCH 0318/1897] Git - Improve "Close Repository" and "Close Other Repositories" commands (#202512) --- extensions/git/src/commands.ts | 23 ++++++++++---- extensions/git/src/util.ts | 21 +++++++++++++ .../scm/browser/scmRepositoriesViewPane.ts | 31 +++++++++++++++++-- 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 4b048e51d2b..608912cdea0 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ import * as os from 'os'; import * as path from 'path'; -import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem } from 'vscode'; +import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import { ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git'; @@ -14,7 +14,7 @@ import { Model } from './model'; import { Repository, Resource, ResourceGroupType } from './repository'; import { applyLineChanges, getModifiedRange, intersectDiffWithRange, invertLineChange, toLineRanges } from './staging'; import { fromGitUri, toGitUri, isGitUri, toMergeUris } from './uri'; -import { dispose, grep, isDescendant, pathEquals, relativePath } from './util'; +import { dispose, grep, isDefined, isDescendant, pathEquals, relativePath } from './util'; import { GitTimelineItem } from './timelineProvider'; import { ApiRepository } from './api/api1'; import { getRemoteSourceActions, pickRemoteSource } from './remoteSource'; @@ -1126,14 +1126,25 @@ export class CommandCenter { } @command('git.close', { repository: true }) - async close(repository: Repository): Promise { - this.model.close(repository); + async close(repository: Repository, ...args: SourceControl[]): Promise { + const otherRepositories = args + .map(sourceControl => this.model.getRepository(sourceControl)) + .filter(isDefined); + + for (const r of [repository, ...otherRepositories]) { + this.model.close(r); + } } @command('git.closeOtherRepositories', { repository: true }) - async closeOtherRepositories(repository: Repository): Promise { + async closeOtherRepositories(repository: Repository, ...args: SourceControl[]): Promise { + const otherRepositories = args + .map(sourceControl => this.model.getRepository(sourceControl)) + .filter(isDefined); + + const selectedRepositories = [repository, ...otherRepositories]; for (const r of this.model.repositories) { - if (r === repository) { + if (selectedRepositories.includes(r)) { continue; } this.model.close(r); diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 9b40b61ca1d..0d4b9241b55 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -352,6 +352,27 @@ export function* splitInChunks(array: string[], maxChunkLength: number): Iterabl } } +/** + * @returns whether the provided parameter is defined. + */ +export function isDefined(arg: T | null | undefined): arg is T { + return !isUndefinedOrNull(arg); +} + +/** + * @returns whether the provided parameter is undefined or null. + */ +export function isUndefinedOrNull(obj: unknown): obj is undefined | null { + return (isUndefined(obj) || obj === null); +} + +/** + * @returns whether the provided parameter is undefined. + */ +export function isUndefined(obj: unknown): obj is undefined { + return (typeof obj === 'undefined'); +} + interface ILimitedTaskFactory { factory: () => Promise; c: (value: T | Promise) => void; diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index d47e7ac4a2c..cd52a47f851 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -9,7 +9,7 @@ import { Event } from 'vs/base/common/event'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane'; import { append, $ } from 'vs/base/browser/dom'; import { IListVirtualDelegate, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list'; -import { ISCMRepository, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm'; +import { ISCMProvider, ISCMRepository, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -26,7 +26,8 @@ import { collectContextMenuActions, getActionViewItemProvider } from 'vs/workben import { Orientation } from 'vs/base/browser/ui/sash/sash'; import { Iterable } from 'vs/base/common/iterator'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { MenuId } from 'vs/platform/actions/common/actions'; +import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { ActionRunner, IAction } from 'vs/base/common/actions'; class ListDelegate implements IListVirtualDelegate { @@ -39,6 +40,23 @@ class ListDelegate implements IListVirtualDelegate { } } +class RepositoryActionRunner extends ActionRunner { + constructor(private readonly getSelectedProviders: () => ISCMProvider[]) { + super(); + } + + protected override async runAction(action: IAction, context: ISCMProvider): Promise { + if (!(action instanceof MenuItemAction)) { + return super.runAction(action, context); + } + + const selection = this.getSelectedProviders(); + const actionContext = selection.some(s => s === context) ? selection : [context]; + + await action.run(...actionContext); + } +} + export class SCMRepositoriesViewPane extends ViewPane { private list!: WorkbenchList; @@ -150,7 +168,16 @@ export class SCMRepositoriesViewPane extends ViewPane { const menu = menus.repositoryContextMenu; const actions = collectContextMenuActions(menu); + const actionRunner = this._register(new RepositoryActionRunner(() => { + const focusedProviders = this.list.getFocusedElements().map(e => e.provider); + const selectedProviders = this.list.getSelectedElements().map(e => e.provider); + + return Array.from(new Set([...focusedProviders, ...selectedProviders])); + })); + actionRunner.onWillRun(() => this.list.domFocus()); + this.contextMenuService.showContextMenu({ + actionRunner, getAnchor: () => e.anchor, getActions: () => actions, getActionsContext: () => provider From f42cf8d8a51c0c59eeadd674a50d8a1122fa5340 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 15 Jan 2024 16:05:24 +0100 Subject: [PATCH 0319/1897] =?UTF-8?q?Git=20-=20=F0=9F=92=84=20show=20busy?= =?UTF-8?q?=20state=20while=20populating=20refs=20(#202513)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/git/src/commands.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 608912cdea0..a5f20e41086 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -2461,6 +2461,7 @@ export class CommandCenter { const disposables: Disposable[] = []; const quickPick = window.createQuickPick(); quickPick.busy = true; + quickPick.sortByLabel = false; quickPick.placeholder = opts?.detached ? l10n.t('Select a branch to checkout in detached mode') : l10n.t('Select a branch or tag to checkout'); @@ -2470,7 +2471,6 @@ export class CommandCenter { picks.push(... await createCheckoutItems(repository, opts?.detached)); quickPick.items = picks; quickPick.busy = false; - quickPick.sortByLabel = false; const choice = await new Promise(c => { disposables.push(quickPick.onDidHide(() => c(undefined))); @@ -2671,18 +2671,26 @@ export class CommandCenter { } private async pickRef(items: Promise, placeHolder: string): Promise { - const listeners: Disposable[] = []; + const disposables: Disposable[] = []; const quickPick = window.createQuickPick(); + quickPick.placeholder = placeHolder; quickPick.sortByLabel = false; - quickPick.items = await items; + quickPick.busy = true; + quickPick.show(); + + quickPick.items = await items; + quickPick.busy = false; + const choice = await new Promise(resolve => { - listeners.push(quickPick.onDidHide(() => resolve(undefined))); - listeners.push(quickPick.onDidAccept(() => resolve(quickPick.activeItems[0]))); + disposables.push(quickPick.onDidHide(() => resolve(undefined))); + disposables.push(quickPick.onDidAccept(() => resolve(quickPick.activeItems[0]))); }); + + dispose(disposables); quickPick.dispose(); - listeners.forEach(d => d.dispose()); + return choice; } From 60d1eed149d758a30e6f4c5e8cf9d7d6d8da79d5 Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 15 Jan 2024 16:21:53 +0100 Subject: [PATCH 0320/1897] Remove Sticky Scroll focus outline and background in settings editor (#202514) Settings editor remove focus outline and background --- .../contrib/preferences/browser/media/settingsEditor2.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index a2bfc42d085..74caeb8c44e 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -196,6 +196,10 @@ background-color: var(--vscode-settings-focusedRowBackground); } +.settings-editor > .settings-body .settings-tree-container .monaco-tree-sticky-container .monaco-list-row.focused .settings-row-inner-container { + background-color: unset; /* Remove Sticky Scroll focus */ +} + .settings-editor > .settings-body .settings-tree-container .monaco-list-row:not(.focused) .settings-row-inner-container:hover { background-color: var(--vscode-settings-rowHoverBackground); } @@ -205,6 +209,10 @@ outline: 1px solid var(--vscode-settings-focusedRowBorder); } +.settings-editor > .settings-body .settings-tree-container .monaco-list:focus-within .monaco-tree-sticky-container .monaco-list-row.focused .settings-group-title-label { + outline: none; /* Remove Sticky Scroll focus */ +} + .settings-editor > .settings-body .settings-tree-container .settings-editor-tree > .monaco-scrollable-element > .shadow.top { z-index: 11; } From 57abc5f73f526249759d3341d00b315e5b97016d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 15 Jan 2024 21:09:21 +0530 Subject: [PATCH 0321/1897] fix #202181 (#202519) * fix #202181 * remove --- src/vs/workbench/browser/parts/paneCompositePart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index df6b3d312d9..db7996ceefe 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -465,7 +465,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart Date: Mon, 15 Jan 2024 21:24:48 +0530 Subject: [PATCH 0322/1897] fix #201346 (#202520) --- package.json | 2 +- remote/package.json | 2 +- remote/yarn.lock | 8 ++++---- yarn.lock | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 59ecb4c4c67..905e19ba9dd 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@vscode/policy-watcher": "^1.1.4", "@vscode/proxy-agent": "^0.18.2", "@vscode/ripgrep": "^1.15.9", - "@vscode/spdlog": "^0.13.12", + "@vscode/spdlog": "^0.14.0", "@vscode/sqlite3": "5.1.6-vscode", "@vscode/sudo-prompt": "9.3.1", "@vscode/vscode-languagedetection": "1.0.21", diff --git a/remote/package.json b/remote/package.json index 07d070cc8f2..b1fbc7b435c 100644 --- a/remote/package.json +++ b/remote/package.json @@ -9,7 +9,7 @@ "@vscode/iconv-lite-umd": "0.7.0", "@vscode/proxy-agent": "^0.18.2", "@vscode/ripgrep": "^1.15.9", - "@vscode/spdlog": "^0.13.12", + "@vscode/spdlog": "^0.14.0", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.5.0", "@vscode/windows-registry": "^1.1.0", diff --git a/remote/yarn.lock b/remote/yarn.lock index 81546b79451..bb20531f409 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -81,10 +81,10 @@ proxy-from-env "^1.1.0" yauzl "^2.9.2" -"@vscode/spdlog@^0.13.12": - version "0.13.12" - resolved "https://registry.yarnpkg.com/@vscode/spdlog/-/spdlog-0.13.12.tgz#8b8e3308468fd7d613c350ecd7fcfaa27064960a" - integrity sha512-8Qw8VwBzFkH6AWVKh+ULeVDff0lwTh/PkEKUOwRpyuu7Mr7W2zQoc+oAaGmTWKb1NPmvVeXmo+qF9X+jXuBCOg== +"@vscode/spdlog@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@vscode/spdlog/-/spdlog-0.14.0.tgz#3cdf589ca59b9ce792ef58b5f773e29a732a360f" + integrity sha512-mpblZa3v6AGEJC1qTwIkpgTc6NItdiiuTxufGxr6XD14srXCvVAUXFIYNqszxC6RY57qDQMX1d9Wd4/oZDxuUQ== dependencies: bindings "^1.5.0" mkdirp "^1.0.4" diff --git a/yarn.lock b/yarn.lock index 71c12eac492..5e1850eb69f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1416,10 +1416,10 @@ proxy-from-env "^1.1.0" yauzl "^2.9.2" -"@vscode/spdlog@^0.13.12": - version "0.13.12" - resolved "https://registry.yarnpkg.com/@vscode/spdlog/-/spdlog-0.13.12.tgz#8b8e3308468fd7d613c350ecd7fcfaa27064960a" - integrity sha512-8Qw8VwBzFkH6AWVKh+ULeVDff0lwTh/PkEKUOwRpyuu7Mr7W2zQoc+oAaGmTWKb1NPmvVeXmo+qF9X+jXuBCOg== +"@vscode/spdlog@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@vscode/spdlog/-/spdlog-0.14.0.tgz#3cdf589ca59b9ce792ef58b5f773e29a732a360f" + integrity sha512-mpblZa3v6AGEJC1qTwIkpgTc6NItdiiuTxufGxr6XD14srXCvVAUXFIYNqszxC6RY57qDQMX1d9Wd4/oZDxuUQ== dependencies: bindings "^1.5.0" mkdirp "^1.0.4" From 5ae0bf1f79d52e8dac2a83c83a289c549f39034a Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 15 Jan 2024 16:55:59 +0100 Subject: [PATCH 0323/1897] Git - "Close Repository" and "Close Other Repositories" now works in the main Source Control view (#202521) --- .../scm/browser/scmRepositoriesViewPane.ts | 30 ++++--------------- .../scm/browser/scmRepositoryRenderer.ts | 23 ++++++++++++-- .../contrib/scm/browser/scmViewPane.ts | 13 ++++++-- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index cd52a47f851..c7d13a31077 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -9,7 +9,7 @@ import { Event } from 'vs/base/common/event'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane'; import { append, $ } from 'vs/base/browser/dom'; import { IListVirtualDelegate, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list'; -import { ISCMProvider, ISCMRepository, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm'; +import { ISCMRepository, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -21,13 +21,12 @@ import { IViewDescriptorService } from 'vs/workbench/common/views'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { RepositoryRenderer } from 'vs/workbench/contrib/scm/browser/scmRepositoryRenderer'; +import { RepositoryActionRunner, RepositoryRenderer } from 'vs/workbench/contrib/scm/browser/scmRepositoryRenderer'; import { collectContextMenuActions, getActionViewItemProvider } from 'vs/workbench/contrib/scm/browser/util'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; import { Iterable } from 'vs/base/common/iterator'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; -import { ActionRunner, IAction } from 'vs/base/common/actions'; +import { MenuId } from 'vs/platform/actions/common/actions'; class ListDelegate implements IListVirtualDelegate { @@ -40,23 +39,6 @@ class ListDelegate implements IListVirtualDelegate { } } -class RepositoryActionRunner extends ActionRunner { - constructor(private readonly getSelectedProviders: () => ISCMProvider[]) { - super(); - } - - protected override async runAction(action: IAction, context: ISCMProvider): Promise { - if (!(action instanceof MenuItemAction)) { - return super.runAction(action, context); - } - - const selection = this.getSelectedProviders(); - const actionContext = selection.some(s => s === context) ? selection : [context]; - - await action.run(...actionContext); - } -} - export class SCMRepositoriesViewPane extends ViewPane { private list!: WorkbenchList; @@ -169,10 +151,10 @@ export class SCMRepositoriesViewPane extends ViewPane { const actions = collectContextMenuActions(menu); const actionRunner = this._register(new RepositoryActionRunner(() => { - const focusedProviders = this.list.getFocusedElements().map(e => e.provider); - const selectedProviders = this.list.getSelectedElements().map(e => e.provider); + const focusedRepositories = this.list.getFocusedElements(); + const selectedRepositories = this.list.getSelectedElements(); - return Array.from(new Set([...focusedProviders, ...selectedProviders])); + return Array.from(new Set([...focusedRepositories, ...selectedRepositories])); })); actionRunner.onWillRun(() => this.list.domFocus()); diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index dee6321e3d0..7e544893911 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -6,11 +6,11 @@ import 'vs/css!./media/scm'; import { IDisposable, DisposableStore, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { append, $ } from 'vs/base/browser/dom'; -import { ISCMRepository, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm'; +import { ISCMProvider, ISCMRepository, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IAction } from 'vs/base/common/actions'; +import { ActionRunner, IAction } from 'vs/base/common/actions'; import { connectPrimaryMenu, isSCMRepository, StatusBarAction } from './util'; import { ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; @@ -19,11 +19,28 @@ import { IListRenderer } from 'vs/base/browser/ui/list/list'; import { IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; -import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +export class RepositoryActionRunner extends ActionRunner { + constructor(private readonly getSelectedRepositories: () => ISCMRepository[]) { + super(); + } + + protected override async runAction(action: IAction, context: ISCMProvider): Promise { + if (!(action instanceof MenuItemAction)) { + return super.runAction(action, context); + } + + const selection = this.getSelectedRepositories().map(r => r.provider); + const actionContext = selection.some(s => s === context) ? selection : [context]; + + await action.run(...actionContext); + } +} + interface RepositoryTemplate { readonly label: HTMLElement; readonly name: HTMLElement; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 9236047335e..1f4a37bcc07 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -70,7 +70,7 @@ import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; -import { RepositoryRenderer } from 'vs/workbench/contrib/scm/browser/scmRepositoryRenderer'; +import { RepositoryActionRunner, RepositoryRenderer } from 'vs/workbench/contrib/scm/browser/scmRepositoryRenderer'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { LabelFuzzyScore } from 'vs/base/browser/ui/tree/abstractTree'; import { Selection } from 'vs/editor/common/core/selection'; @@ -2992,7 +2992,9 @@ export class SCMViewPane extends ViewPane { } } - const actionRunner = new RepositoryPaneActionRunner(() => this.getSelectedResources()); + const actionRunner = isSCMRepository(element) ? + new RepositoryActionRunner(() => this.getSelectedRepositories()) : + new RepositoryPaneActionRunner(() => this.getSelectedResources()); actionRunner.onWillRun(() => this.tree.domFocus()); this.contextMenuService.showContextMenu({ @@ -3003,6 +3005,13 @@ export class SCMViewPane extends ViewPane { }); } + private getSelectedRepositories(): ISCMRepository[] { + const focusedRepositories = this.tree.getFocus().filter(r => !!r && isSCMRepository(r))! as ISCMRepository[]; + const selectedRepositories = this.tree.getSelection().filter(r => !!r && isSCMRepository(r))! as ISCMRepository[]; + + return Array.from(new Set([...focusedRepositories, ...selectedRepositories])); + } + private getSelectedResources(): (ISCMResource | IResourceNode)[] { return this.tree.getSelection() .filter(r => !!r && !isSCMResourceGroup(r))! as any; From f70f159413d34931965e2cf8d19f39b70f3287ae Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 15 Jan 2024 16:44:49 +0100 Subject: [PATCH 0324/1897] Fixes #202325 --- .../multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index a63e5338ff5..0bbeb3be7d5 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -82,7 +82,9 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } ); - private readonly _totalHeight = this._viewItems.map(this, (items, reader) => items.reduce((r, i) => r + i.contentHeight.read(reader), 0)); + private readonly _spaceBetweenPx = 10; + + private readonly _totalHeight = this._viewItems.map(this, (items, reader) => items.reduce((r, i) => r + i.contentHeight.read(reader) + this._spaceBetweenPx, 0)); public readonly activeDiffItem = derived(this, reader => this._viewItems.read(reader).find(i => i.template.read(reader)?.isFocused.read(reader))); public readonly lastActiveDiffItem = derivedObservableWithCache((reader, lastValue) => this.activeDiffItem.read(reader) ?? lastValue); public readonly activeControl = derived(this, reader => this.lastActiveDiffItem.read(reader)?.template.read(reader)?.editor); @@ -240,10 +242,8 @@ export class MultiDiffEditorWidgetImpl extends Disposable { v.render(itemRange, scroll, width, viewPort); } - const spaceBetween = 10; - - itemHeightSumBefore += itemHeight + spaceBetween; - itemContentHeightSumBefore += itemContentHeight + spaceBetween; + itemHeightSumBefore += itemHeight + this._spaceBetweenPx; + itemContentHeightSumBefore += itemContentHeight + this._spaceBetweenPx; } this._elements.content.style.transform = `translateY(${-(scrollTop + contentScrollOffsetToScrollOffset)}px)`; From 7109701918548725a6efbd8e1ed57d978afe043d Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 15 Jan 2024 17:22:43 +0100 Subject: [PATCH 0325/1897] Expose git merge as extension API (#202503) * Expose git merge as extension API Part of microsoft/vscode-pull-request-github#200 * Respond to PR feedback --- extensions/git/src/api/api1.ts | 8 ++++++++ extensions/git/src/api/git.d.ts | 2 ++ 2 files changed, 10 insertions(+) diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 747a2ada11b..c36362c2823 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -256,6 +256,14 @@ export class ApiRepository implements Repository { commit(message: string, opts?: CommitOptions): Promise { return this.repository.commit(message, opts); } + + merge(ref: string): Promise { + return this.repository.merge(ref); + } + + mergeAbort(): Promise { + return this.repository.mergeAbort(); + } } export class ApiGit implements Git { diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 9ac4fdd0161..0b155423619 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -257,6 +257,8 @@ export interface Repository { log(options?: LogOptions): Promise; commit(message: string, opts?: CommitOptions): Promise; + merge(ref: string): Promise; + mergeAbort(): Promise; } export interface RemoteSource { From 2571b285f5ed6ce1c6b0482c638d3f83545f869c Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 15 Jan 2024 17:15:21 +0100 Subject: [PATCH 0326/1897] Fixes #199325 --- .../widget/multiDiffEditorWidget/diffEditorItemTemplate.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts index 451b5bbe270..11fdb1e5ccc 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts @@ -166,6 +166,7 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< }, renderOverviewRuler: false, fixedOverflowWidgets: true, + overviewRulerBorder: false, }; } From 3541d8e3c54958dbd23c7dd182dec186bc94d8b8 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 15 Jan 2024 18:28:21 +0100 Subject: [PATCH 0327/1897] Bump distro (#202531) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 905e19ba9dd..5a1353b031e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.86.0", - "distro": "4665912689c3b96279fd50cc65117ab6969e7132", + "distro": "e74e6b268c5f26bfeb37b3468500c70b4dbd98da", "author": { "name": "Microsoft Corporation" }, From dfafddabf4203c46b344753ac97e6903337ebd21 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 15 Jan 2024 19:23:55 +0100 Subject: [PATCH 0328/1897] Consider focus of overflowWidgetsDomNode for editor.focus/blur. This is important when overflowWidgetsDomNode is outside of the editor. Fixes rename in multi diff editor. Related #105346. --- .../editor/browser/widget/codeEditorWidget.ts | 48 +++++++++++++++---- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 9e1a7f0df17..1950771ff41 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -322,7 +322,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._modelData = null; - this._focusTracker = new CodeEditorWidgetFocusTracker(domElement); + this._focusTracker = new CodeEditorWidgetFocusTracker(domElement, this._overflowWidgetsDomNode); this._register(this._focusTracker.onChange(() => { this._editorWidgetFocus.setValue(this._focusTracker.hasFocus()); })); @@ -2211,34 +2211,62 @@ export class EditorModeContext extends Disposable { class CodeEditorWidgetFocusTracker extends Disposable { - private _hasFocus: boolean; + private _hasDomElementFocus: boolean; private readonly _domFocusTracker: dom.IFocusTracker; + private readonly _overflowWidgetsDomNode: dom.IFocusTracker | undefined; private readonly _onChange: Emitter = this._register(new Emitter()); public readonly onChange: Event = this._onChange.event; - constructor(domElement: HTMLElement) { + private _overflowWidgetsDomNodeHasFocus: boolean; + + private _hadFocus: boolean | undefined = undefined; + + constructor(domElement: HTMLElement, overflowWidgetsDomNode: HTMLElement | undefined) { super(); - this._hasFocus = false; + this._hasDomElementFocus = false; this._domFocusTracker = this._register(dom.trackFocus(domElement)); + this._overflowWidgetsDomNodeHasFocus = false; + this._register(this._domFocusTracker.onDidFocus(() => { - this._hasFocus = true; - this._onChange.fire(undefined); + this._hasDomElementFocus = true; + this._update(); })); this._register(this._domFocusTracker.onDidBlur(() => { - this._hasFocus = false; - this._onChange.fire(undefined); + this._hasDomElementFocus = false; + this._update(); })); + + if (overflowWidgetsDomNode) { + this._overflowWidgetsDomNode = this._register(dom.trackFocus(overflowWidgetsDomNode)); + this._register(this._overflowWidgetsDomNode.onDidFocus(() => { + this._overflowWidgetsDomNodeHasFocus = true; + this._update(); + })); + this._register(this._overflowWidgetsDomNode.onDidBlur(() => { + this._overflowWidgetsDomNodeHasFocus = false; + this._update(); + })); + } + } + + private _update() { + const focused = this._hasDomElementFocus || this._overflowWidgetsDomNodeHasFocus; + if (this._hadFocus !== focused) { + this._hadFocus = focused; + this._onChange.fire(undefined); + } } public hasFocus(): boolean { - return this._hasFocus; + return this._hadFocus ?? false; } public refreshState(): void { - this._domFocusTracker.refreshState?.(); + this._domFocusTracker.refreshState(); + this._overflowWidgetsDomNode?.refreshState?.(); } } From 9f15e17e798f3a56bdf0a2104dfdaddd96127db1 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 15 Jan 2024 18:55:35 +0100 Subject: [PATCH 0329/1897] Multi Diff Editor: Keeps selection when revealing file --- .../multiDiffEditorWidget.ts | 7 ++++++ .../multiDiffEditorWidgetImpl.ts | 19 ++++++++++++++++ .../multiDiffEditor/browser/actions.ts | 22 +++++++++++++++++-- .../browser/multiDiffEditor.ts | 6 +++++ 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index 418e2c3b81d..938edfad4b3 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -15,6 +15,9 @@ import './colors'; import { DiffEditorItemTemplate } from 'vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate'; import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory'; import { Event } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; +import { IDiffEditor } from 'vs/editor/common/editorCommon'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; export class MultiDiffEditorWidget extends Disposable { private readonly _dimension = observableValue(this, undefined); @@ -68,4 +71,8 @@ export class MultiDiffEditorWidget extends Disposable { public setViewState(viewState: IMultiDiffEditorViewState): void { this._widgetImpl.get().setViewState(viewState); } + + public tryGetCodeEditor(resource: URI): { diffEditor: IDiffEditor; editor: ICodeEditor } | undefined { + return this._widgetImpl.get().tryGetCodeEditor(resource); + } } diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 0bbeb3be7d5..9bfbae0c9ac 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -22,6 +22,9 @@ import { ContextKeyValue, IContextKeyService } from 'vs/platform/contextkey/comm import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; +import { URI } from 'vs/base/common/uri'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IDiffEditor } from 'vs/editor/common/editorCommon'; export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _elements = h('div.monaco-component.multiDiffEditor', [ @@ -214,6 +217,22 @@ export class MultiDiffEditorWidgetImpl extends Disposable { }); } + public tryGetCodeEditor(resource: URI): { diffEditor: IDiffEditor; editor: ICodeEditor } | undefined { + const item = this._viewItems.get().find(v => + v.viewModel.diffEditorViewModel.model.modified.uri.toString() === resource.toString() + || v.viewModel.diffEditorViewModel.model.original.uri.toString() === resource.toString() + ); + const editor = item?.template.get()?.editor; + if (!editor) { + return undefined; + } + if (item.viewModel.diffEditorViewModel.model.modified.uri.toString() === resource.toString()) { + return { diffEditor: editor, editor: editor.getModifiedEditor() }; + } else { + return { diffEditor: editor, editor: editor.getOriginalEditor() }; + } + } + private render(reader: IReader | undefined) { const scrollTop = this.scrollTop.read(reader); let contentScrollOffsetToScrollOffset = 0; diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts index 631376ed1a0..921026f4731 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts @@ -5,11 +5,13 @@ import { Codicon } from 'vs/base/common/codicons'; import { URI } from 'vs/base/common/uri'; +import { Selection } from 'vs/editor/common/core/selection'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { localize2 } from 'vs/nls'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; import { MultiDiffEditor } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor'; import { MultiDiffEditorInput } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -30,10 +32,26 @@ export class GoToFileAction extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]): void { + async run(accessor: ServicesAccessor, ...args: any[]): Promise { const uri = args[0] as URI; const editorService = accessor.get(IEditorService); - editorService.openEditor({ resource: uri }); + const activeEditorPane = editorService.activeEditorPane; + let selections: Selection[] | undefined = undefined; + if (activeEditorPane instanceof MultiDiffEditor) { + const editor = activeEditorPane.tryGetCodeEditor(uri); + if (editor) { + selections = editor.editor.getSelections() ?? undefined; + } + } + + const editor = await editorService.openEditor({ resource: uri }); + if (selections && (editor instanceof TextFileEditor)) { + const c = editor.getControl(); + if (c) { + c.setSelections(selections); + c.revealLineInCenter(selections[0].selectionStartLineNumber); + } + } } } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 1fd67736d98..e100522ed4c 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -25,6 +25,8 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { URI } from 'vs/base/common/uri'; import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; import { IMultiDiffEditorViewState } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IDiffEditor } from 'vs/editor/common/editorCommon'; export class MultiDiffEditor extends AbstractEditorWithViewState { static readonly ID = 'multiDiffEditor'; @@ -105,6 +107,10 @@ export class MultiDiffEditor extends AbstractEditorWithViewState Date: Mon, 15 Jan 2024 13:57:11 -0800 Subject: [PATCH 0330/1897] Ctrl/CMD+Arrow for navigating across cell editor and widget (#202539) * Fix up arrow when cell widget is visible * Use Ctrl/CMD+Arrow for navigating across cell editor and widget --- .../view/cellParts/chat/cellChatActions.ts | 163 +++++++++++++++++- .../view/cellParts/chat/cellChatController.ts | 8 + 2 files changed, 168 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts index 378668f09dc..3afb16e5b08 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts @@ -5,19 +5,23 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { localize } from 'vs/nls'; +import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; import { INotebookCellActionContext, NotebookAction, NotebookCellAction } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { insertNewCell } from 'vs/workbench/contrib/notebook/browser/controller/insertCellActions'; +import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS, NotebookCellChatController } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; -import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_EDITABLE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; registerAction2(class extends NotebookCellAction { @@ -54,6 +58,159 @@ registerAction2(class extends NotebookCellAction { } }); +registerAction2(class extends NotebookCellAction { + constructor() { + super( + { + id: 'notebook.cell.chat.arrowOutUp', + title: localize('arrowUp', 'Cursor Up'), + keybinding: { + when: ContextKeyExpr.and( + CTX_NOTEBOOK_CELL_CHAT_FOCUSED, + CTX_INLINE_CHAT_FOCUSED, + CTX_INLINE_CHAT_INNER_CURSOR_FIRST, + CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate() + ), + weight: KeybindingWeight.EditorCore + 7, + primary: KeyMod.CtrlCmd | KeyCode.UpArrow + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + const editor = context.notebookEditor; + const activeCell = context.cell; + + const idx = editor.getCellIndex(activeCell); + if (typeof idx !== 'number') { + return; + } + + if (idx < 1 || editor.getLength() === 0) { + // we don't do loop + return; + } + + const newCell = editor.cellAt(idx - 1); + const newFocusMode = newCell.cellKind === CellKind.Markup && newCell.getEditState() === CellEditState.Preview ? 'container' : 'editor'; + const focusEditorLine = newCell.textBuffer.getLineCount(); + await editor.focusNotebookCell(newCell, newFocusMode, { focusEditorLine: focusEditorLine }); + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super( + { + id: 'notebook.cell.chat.arrowOutDown', + title: localize('arrowDown', 'Cursor Down'), + keybinding: { + when: ContextKeyExpr.and( + CTX_NOTEBOOK_CELL_CHAT_FOCUSED, + CTX_INLINE_CHAT_FOCUSED, + CTX_INLINE_CHAT_INNER_CURSOR_LAST, + CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate() + ), + weight: KeybindingWeight.EditorCore + 7, + primary: KeyMod.CtrlCmd | KeyCode.DownArrow + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + const editor = context.notebookEditor; + const activeCell = context.cell; + await editor.focusNotebookCell(activeCell, 'editor'); + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super( + { + id: 'notebook.cell.focusChatWidget', + title: localize('focusChatWidget', 'Focus Chat Widget'), + keybinding: { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate(), + ContextKeyExpr.and( + ContextKeyExpr.has(InputFocusedContextKey), + EditorContextKeys.editorTextFocus, + NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('bottom'), + NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'), + ), + EditorContextKeys.isEmbeddedDiffEditor.negate() + ), + weight: KeybindingWeight.EditorCore + 7, + primary: KeyMod.CtrlCmd | KeyCode.UpArrow + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + const activeCell = context.cell; + // Navigate to cell chat widget if it exists + const controller = NotebookCellChatController.get(activeCell); + if (controller && controller.isWidgetVisible()) { + controller.focusWidget(); + return; + } + + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super( + { + id: 'notebook.cell.focusNextChatWidget', + title: localize('focusNextChatWidget', 'Focus Next Cell Chat Widget'), + keybinding: { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate(), + ContextKeyExpr.and( + ContextKeyExpr.has(InputFocusedContextKey), + EditorContextKeys.editorTextFocus, + NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), + NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'), + ), + EditorContextKeys.isEmbeddedDiffEditor.negate() + ), + weight: KeybindingWeight.EditorCore + 7, + primary: KeyMod.CtrlCmd | KeyCode.DownArrow + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + const editor = context.notebookEditor; + const activeCell = context.cell; + + const idx = editor.getCellIndex(activeCell); + if (typeof idx !== 'number') { + return; + } + + if (idx >= editor.getLength() - 1) { + // last one + return; + } + + const targetCell = editor.cellAt(idx + 1); + + if (targetCell) { + // Navigate to cell chat widget if it exists + const controller = NotebookCellChatController.get(targetCell); + if (controller && controller.isWidgetVisible()) { + controller.focusWidget(); + return; + } + } + } +}); + registerAction2(class extends NotebookCellAction { constructor() { super( diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts index 1406eab2b18..106caabc296 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts @@ -167,6 +167,10 @@ export class NotebookCellChatController extends Disposable { super.dispose(); } + isWidgetVisible() { + return this._isVisible; + } + layout() { if (this._isVisible && this._widget) { const width = this._notebookEditor.getLayoutInfo().width - (/** margin */ 16 + 6) - (/** padding */ 6 * 2); @@ -231,6 +235,10 @@ export class NotebookCellChatController extends Disposable { }); } + async focusWidget() { + this._widget?.focus(); + } + private _getCellEditor() { const editors = this._notebookEditor.codeEditors.find(editor => editor[0] === this._chatPart.activeCell); if (!editors || !editors[1].hasModel()) { From 2c8f29174a63b3ace3d00ad0028cdc7ed9853b8b Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 15 Jan 2024 20:57:21 -0800 Subject: [PATCH 0331/1897] Allow extensions invoke cell chat with prefilled input. (#202549) * Allow extensions invoke cell chat with prefilled input. * Add autoSend support --- .../view/cellParts/chat/cellChatActions.ts | 72 +++++++++++++++++-- .../view/cellParts/chat/cellChatController.ts | 10 ++- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts index 3afb16e5b08..7763a3ea061 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts @@ -16,9 +16,9 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; -import { INotebookCellActionContext, NotebookAction, NotebookCellAction } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; +import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, getEditorFromArgsOrActivePane } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { insertNewCell } from 'vs/workbench/contrib/notebook/browser/controller/insertCellActions'; -import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS, NotebookCellChatController } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; @@ -411,8 +411,12 @@ registerAction2(class extends NotebookCellAction { } }); +interface IInsertCellWithChatArgs extends INotebookActionContext { + input?: string; + autoSend?: boolean; +} -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super( { @@ -422,6 +426,29 @@ registerAction2(class extends NotebookCellAction { original: '$(sparkle) Generate', }, tooltip: localize('notebookActions.menu.insertCodeCellWithChat.tooltip', "Generate Code Cell with Chat"), + metadata: { + description: localize('notebookActions.menu.insertCodeCellWithChat.tooltip', "Generate Code Cell with Chat"), + args: [ + { + name: 'args', + schema: { + type: 'object', + required: ['index'], + properties: { + 'index': { + type: 'number' + }, + 'input': { + type: 'string' + }, + 'autoSend': { + type: 'boolean' + } + } + } + } + ] + }, menu: [ { id: MenuId.NotebookCellBetween, @@ -437,12 +464,45 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const newCell = await insertNewCell(accessor, context, CellKind.Code, 'below', true); + override getEditorContextFromArgsOrActive(accessor: ServicesAccessor, ...args: any[]): IInsertCellWithChatArgs | undefined { + const [firstArg] = args; + if (!firstArg) { + return undefined; + } + + if (typeof firstArg !== 'object' || typeof firstArg.index !== 'number') { + return undefined; + } + + const notebookEditor = getEditorFromArgsOrActivePane(accessor); + if (!notebookEditor) { + return undefined; + } + + const cell = firstArg.index === 0 ? undefined : notebookEditor.cellAt(firstArg.index); + + return { + cell, + notebookEditor, + input: firstArg.input, + autoSend: firstArg.autoSend + }; + } + + async runWithContext(accessor: ServicesAccessor, context: IInsertCellWithChatArgs) { + let newCell: ICellViewModel | null = null; + if (!context.cell) { + // insert at the top + const languageService = accessor.get(ILanguageService); + newCell = insertCell(languageService, context.notebookEditor, 0, CellKind.Code, 'above', undefined, true); + } else { + newCell = insertNewCell(accessor, context, CellKind.Code, 'above', true); + } if (!newCell) { return; } + await context.notebookEditor.focusNotebookCell(newCell, 'container'); const ctrl = NotebookCellChatController.get(newCell); if (!ctrl) { @@ -456,7 +516,7 @@ registerAction2(class extends NotebookCellAction { } }); - ctrl.show(); + ctrl.show(context.input, context.autoSend); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts index 106caabc296..fa7eb4cfc9a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts @@ -193,7 +193,7 @@ export class NotebookCellChatController extends Disposable { this._partContainer.style.height = `${heightWithPadding - surrounding}px`; } - async show() { + async show(input?: string, autoSend?: boolean) { this._isVisible = true; if (!this._widget) { const editor = this._getCellEditor(); @@ -232,6 +232,14 @@ export class NotebookCellChatController extends Disposable { this._widget.updateInfo(this._activeSession?.session.message ?? localize('welcome.1', "AI-generated code may be incorrect")); this._widget.focus(); } + + if (this._widget && input) { + this._widget.value = input; + + if (autoSend) { + this.acceptInput(); + } + } }); } From 7f825208bf67fe5f81131e0ed36dcb9fa063e403 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 16 Jan 2024 12:58:50 +0530 Subject: [PATCH 0332/1897] fix duplicate registrations (#202557) --- .../workbench/contrib/userDataSync/browser/userDataSync.ts | 1 + .../contrib/userDataSync/browser/userDataSyncViews.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index ce24dc14b3e..a4dd67a7f8b 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -840,6 +840,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private conflictsActionDisposable = this._register(new MutableDisposable()); private registerShowConflictsAction(): void { + this.conflictsActionDisposable.value = undefined; const that = this; this.conflictsActionDisposable.value = registerAction2(class TurningOnSyncAction extends Action2 { constructor() { diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts index 4362953931d..248d8c0756f 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts @@ -224,7 +224,7 @@ export class UserDataSyncDataViews extends Disposable { registerAction2(class extends Action2 { constructor() { super({ - id: `workbench.actions.sync.resolveResource`, + id: `workbench.actions.sync.${viewId}.resolveResource`, title: localize('workbench.actions.sync.resolveResourceRef', "Show raw JSON sync data"), menu: { id: MenuId.ViewItemContext, @@ -242,7 +242,7 @@ export class UserDataSyncDataViews extends Disposable { registerAction2(class extends Action2 { constructor() { super({ - id: `workbench.actions.sync.compareWithLocal`, + id: `workbench.actions.sync.${viewId}.compareWithLocal`, title: localize('workbench.actions.sync.compareWithLocal', "Compare with Local"), menu: { id: MenuId.ViewItemContext, @@ -267,7 +267,7 @@ export class UserDataSyncDataViews extends Disposable { registerAction2(class extends Action2 { constructor() { super({ - id: `workbench.actions.sync.replaceCurrent`, + id: `workbench.actions.sync.${viewId}.replaceCurrent`, title: localize('workbench.actions.sync.replaceCurrent', "Restore"), icon: Codicon.discard, menu: { From a3e16ceaaeaa8e40106bd274c2590094d3ec35db Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 16 Jan 2024 08:49:36 +0100 Subject: [PATCH 0333/1897] zoom - fix keybinding tooltips (#202560) --- src/vs/workbench/electron-sandbox/window.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 6fe76f60879..be37ea1a41a 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -1153,7 +1153,7 @@ class ZoomStatusEntry extends Disposable { const zoomOutAction: Action = disposables.add(new Action('workbench.action.zoomOut', localize('zoomOut', "Zoom Out"), ThemeIcon.asClassName(Codicon.remove), true, () => this.commandService.executeCommand(zoomOutAction.id))); const zoomInAction: Action = disposables.add(new Action('workbench.action.zoomIn', localize('zoomIn', "Zoom In"), ThemeIcon.asClassName(Codicon.plus), true, () => this.commandService.executeCommand(zoomInAction.id))); const zoomResetAction: Action = disposables.add(new Action('workbench.action.zoomReset', localize('zoomReset', "Reset"), undefined, true, () => this.commandService.executeCommand(zoomResetAction.id))); - zoomResetAction.tooltip = localize('zoomResetLabel', "Reset Zoom ({1})", zoomResetAction.label, this.keybindingService.lookupKeybinding(zoomResetAction.id)?.getLabel()); + zoomResetAction.tooltip = localize('zoomResetLabel', "{0} ({1})", zoomResetAction.label, this.keybindingService.lookupKeybinding(zoomResetAction.id)?.getLabel()); const zoomSettingsAction: Action = disposables.add(new Action('workbench.action.openSettings', localize('zoomSettings', "Settings"), ThemeIcon.asClassName(Codicon.settingsGear), true, () => this.commandService.executeCommand(zoomSettingsAction.id, 'window.zoom'))); const zoomLevelLabel = disposables.add(new Action('zoomLabel', undefined, undefined, false)); From 70869b7bc6206fe54062bf0ed41405931b7590cf Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 16 Jan 2024 13:35:22 +0530 Subject: [PATCH 0334/1897] fix #78245 (#202562) --- src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index 6c498870ddd..2e978db3d4d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -241,10 +241,6 @@ function canToggleWordWrap(codeEditorService: ICodeEditorService, editor: ICodeE if (!model) { return false; } - if (model.uri.scheme === 'output') { - // in output editor - return false; - } if (editor.getOption(EditorOption.inDiffEditor)) { // this editor belongs to a diff editor for (const diffEditor of codeEditorService.listDiffEditors()) { From 61112949a619959f3fe709d21a549a594c53ebe9 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 16 Jan 2024 09:33:07 +0100 Subject: [PATCH 0335/1897] Git - do not show checkout commands when searching (#202567) --- extensions/git/src/commands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index a5f20e41086..c803c618425 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -23,7 +23,7 @@ import { RemoteSourceAction } from './api/git-base'; abstract class CheckoutCommandItem implements QuickPickItem { abstract get label(): string; get description(): string { return ''; } - get alwaysShow(): boolean { return true; } + get alwaysShow(): boolean { return false; } } class CreateBranchItem extends CheckoutCommandItem { From 48bc94d6465cdd54ae51b81c90401d2e97b6f28c Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 16 Jan 2024 11:02:32 +0100 Subject: [PATCH 0336/1897] =?UTF-8?q?Git=20-=20=F0=9F=92=84=20make=20stash?= =?UTF-8?q?=20picker=20async=20(#202573)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/git/src/commands.ts | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index c803c618425..81abbd94aa0 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -262,6 +262,14 @@ class RepositoryItem implements QuickPickItem { constructor(public readonly path: string) { } } +class StashItem implements QuickPickItem { + get label(): string { return `#${this.stash.index}: ${this.stash.description}`; } + + get description(): string | undefined { return this.stash.branchName; } + + constructor(readonly stash: Stash) { } +} + interface ScmCommandOptions { repository?: boolean; diff?: boolean; @@ -3690,17 +3698,15 @@ export class CommandCenter { } private async pickStash(repository: Repository, placeHolder: string): Promise { - const stashes = await repository.getStashes(); + const getStashQuickPickItems = async (): Promise => { + const stashes = await repository.getStashes(); + return stashes.length > 0 ? + stashes.map(stash => new StashItem(stash)) : + [{ label: l10n.t('$(info) This repository has no stashes.') }]; + }; - if (stashes.length === 0) { - window.showInformationMessage(l10n.t('There are no stashes in the repository.')); - return; - } - - const picks = stashes.map(stash => ({ label: `#${stash.index}: ${stash.description}`, description: stash.branchName, stash })); - const result = await window.showQuickPick(picks, { placeHolder }); - - return result?.stash; + const result = await window.showQuickPick(getStashQuickPickItems(), { placeHolder }); + return result instanceof StashItem ? result.stash : undefined; } private async getStashFromUri(uri: Uri | undefined): Promise<{ repository: Repository; stash: Stash } | undefined> { From da97e117733bbf68b1e2fac8612a9a1f3ea822d6 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 16 Jan 2024 14:38:26 +0100 Subject: [PATCH 0337/1897] update quick voice keybindings/chords (disable) --- .../electron-sandbox/inlineChatQuickVoice.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index b666c9bbbc3..1e47eb5d485 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -6,7 +6,7 @@ import 'vs/css!./inlineChatQuickVoice'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { Codicon } from 'vs/base/common/codicons'; -import { KeyChord, KeyCode } from 'vs/base/common/keyCodes'; +import { KeyCode } from 'vs/base/common/keyCodes'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; @@ -32,10 +32,11 @@ export class StartAction extends EditorAction2 { title: localize2('start', "Start Inline Voice Chat"), category: AbstractInlineChatAction.category, precondition: ContextKeyExpr.and(HasSpeechProvider, CTX_QUICK_CHAT_IN_PROGRESS.toNegated()), - keybinding: { - primary: KeyChord(KeyCode.F12, KeyCode.F12), - weight: KeybindingWeight.WorkbenchContrib - } + f1: true, + // keybinding: { + // primary: KeyChord(KeyCode.F12, KeyCode.F12), + // weight: KeybindingWeight.WorkbenchContrib + // } }); } @@ -52,8 +53,9 @@ export class StopAction extends EditorAction2 { title: localize2('stop', "Stop Inline Voice Chat"), category: AbstractInlineChatAction.category, precondition: ContextKeyExpr.and(HasSpeechProvider, CTX_QUICK_CHAT_IN_PROGRESS), + f1: true, keybinding: { - primary: KeyChord(KeyCode.F12, KeyCode.F12), + primary: KeyCode.Escape, weight: KeybindingWeight.WorkbenchContrib } }); From b8b5a4aad050f5ffabce934aa36f72b0f2c13665 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 16 Jan 2024 14:50:19 +0100 Subject: [PATCH 0338/1897] Move hunk computation into sessions, let strategies render them (#202578) * chore - move chat session service implementation and interface into their own files * chore - move chat saving service implementation and interface into their own files * - move hunks into session (instead of strategy) - recompute them after receiving AI changes - accept& discard moves hunks from textModelN to textModel0 and vice versa - service renames - tests * - session doesn't know about an editor, only service does - allow to "move" session to a different editor - let controller pickup session after move to its editor - session saving picks up orphand sessions * try to restore editors when group is still valid * ctrl - don't pause when cancellation happens during session create * fix tests --- .../emptyTextEditorHint.ts | 2 +- .../browser/inlineChat.contribution.ts | 10 +- .../inlineChat/browser/inlineChatActions.ts | 2 +- .../browser/inlineChatController.ts | 90 ++-- .../inlineChat/browser/inlineChatNotebook.ts | 2 +- .../browser/inlineChatSavingService.ts | 16 + ...ving.ts => inlineChatSavingServiceImpl.ts} | 157 ++++--- .../inlineChat/browser/inlineChatSession.ts | 374 +++++++-------- .../browser/inlineChatSessionService.ts | 53 +++ .../browser/inlineChatSessionServiceImpl.ts | 223 +++++++++ .../browser/inlineChatStrategies.ts | 434 ++++++------------ .../contrib/inlineChat/browser/utils.ts | 6 + .../test/browser/inlineChatController.test.ts | 8 +- .../test/browser/inlineChatSession.test.ts | 316 ++++++++++++- .../contrib/editorHint/emptyCellEditorHint.ts | 2 +- .../view/cellParts/chat/cellChatController.ts | 3 +- 16 files changed, 1116 insertions(+), 582 deletions(-) create mode 100644 src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingService.ts rename src/vs/workbench/contrib/inlineChat/browser/{inlineChatSaving.ts => inlineChatSavingServiceImpl.ts} (52%) create mode 100644 src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts create mode 100644 src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts diff --git a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts index ba47bfed2e7..c103f95c23a 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts @@ -21,7 +21,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IContentActionHandler, renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; import { ApplyFileSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets'; -import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; import { IInlineChatService, IInlineChatSessionProvider } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index bf3ff77ac18..d68c3dc3dc8 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -10,17 +10,19 @@ import * as InlineChatActions from 'vs/workbench/contrib/inlineChat/browser/inli import { IInlineChatService, INLINE_CHAT_ID, INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl'; -import { IInlineChatSessionService, InlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { InlineChatSessionServiceImpl } from './inlineChatSessionServiceImpl'; +import { IInlineChatSessionService } from './inlineChatSessionService'; 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 { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { InlineChatAccessibleViewContribution } from './inlineChatAccessibleView'; -import { IInlineChatSavingService, InlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSaving'; +import { InlineChatSavingServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl'; +import { IInlineChatSavingService } from './inlineChatSavingService'; registerSingleton(IInlineChatService, InlineChatServiceImpl, InstantiationType.Delayed); -registerSingleton(IInlineChatSessionService, InlineChatSessionService, InstantiationType.Delayed); -registerSingleton(IInlineChatSavingService, InlineChatSavingService, InstantiationType.Delayed); +registerSingleton(IInlineChatSessionService, InlineChatSessionServiceImpl, InstantiationType.Delayed); +registerSingleton(IInlineChatSavingService, InlineChatSavingServiceImpl, InstantiationType.Delayed); registerEditorContribution(INLINE_CHAT_ID, InlineChatController, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors registerEditorContribution(INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID, InlineChatActions.InlineAccessibilityHelpContribution, EditorContributionInstantiation.Eventually); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index ecca297e868..c16518017fb 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -22,7 +22,7 @@ import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/ import { IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { fromNow } from 'vs/base/common/date'; -import { IInlineChatSessionService, Recording } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IInlineChatSessionService, Recording } from './inlineChatSessionService'; import { runAccessibilityHelpAction } from 'vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { Disposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index a6f3a10de37..91e8f5a8126 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -39,8 +39,9 @@ import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/cont import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSaving'; -import { EmptyResponse, ErrorResponse, ExpansionState, IInlineChatSessionService, ReplyResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IInlineChatSavingService } from './inlineChatSavingService'; +import { EmptyResponse, ErrorResponse, ExpansionState, ReplyResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IInlineChatSessionService } from './inlineChatSessionService'; import { EditModeStrategy, LivePreviewStrategy, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; import { IInlineChatMessageAppender, InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatProgressItem, IInlineChatRequest, IInlineChatResponse, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -122,7 +123,7 @@ export class InlineChatController implements IEditorContribution { readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store); private readonly _sessionStore: DisposableStore = this._store.add(new DisposableStore()); - private readonly _pausedStrategies = new Map(); + private _session?: Session; private _strategy?: EditModeStrategy; private _ignoreModelContentChanged = false; @@ -161,17 +162,19 @@ export class InlineChatController implements IEditorContribution { return; } - this._log('session RESUMING', e); + this._log('session RESUMING after model change', e); await this.run({ existingSession }); - this._log('session done or paused'); })); - this._log('NEW controller'); - this._store.add(this._inlineChatSessionService.onDidEndSession(e => { - this._pausedStrategies.get(e.session)?.dispose(); - this._pausedStrategies.delete(e.session); + this._store.add(this._inlineChatSessionService.onDidMoveSession(async e => { + if (e.editor === this._editor) { + this._log('session RESUMING after move', e); + await this.run({ existingSession: e.session }); + } })); + this._log('NEW controller'); + InlineChatController._promptHistory = JSON.parse(_storageService.get(InlineChatController._storageKey, StorageScope.PROFILE, '[]')); this._historyUpdate = (prompt: string) => { const idx = InlineChatController._promptHistory.indexOf(prompt); @@ -264,7 +267,7 @@ export class InlineChatController implements IEditorContribution { } } - private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { + private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { assertType(this._session === undefined); assertType(this._editor.hasModel()); @@ -305,7 +308,10 @@ export class InlineChatController implements IEditorContribution { msgListener.dispose(); if (createSessionCts.token.isCancellationRequested) { - return State.PAUSE; + if (session) { + this._inlineChatSessionService.releaseSession(session); + } + return State.CANCEL; } } @@ -317,24 +323,18 @@ export class InlineChatController implements IEditorContribution { return State.CANCEL; } - if (this._pausedStrategies.has(session)) { - // maybe a strategy was previously paused, use it - this._strategy = this._pausedStrategies.get(session)!; - this._pausedStrategies.delete(session); - } else { - // create a new strategy - switch (session.editMode) { - case EditMode.Live: - this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._zone.value); - break; - case EditMode.Preview: - this._strategy = this._instaService.createInstance(PreviewStrategy, session, this._zone.value); - break; - case EditMode.LivePreview: - default: - this._strategy = this._instaService.createInstance(LivePreviewStrategy, session, this._editor, this._zone.value); - break; - } + // create a new strategy + switch (session.editMode) { + case EditMode.Live: + this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._zone.value); + break; + case EditMode.Preview: + this._strategy = this._instaService.createInstance(PreviewStrategy, session, this._zone.value); + break; + case EditMode.LivePreview: + default: + this._strategy = this._instaService.createInstance(LivePreviewStrategy, session, this._editor, this._zone.value); + break; } this._session = session; @@ -690,7 +690,13 @@ export class InlineChatController implements IEditorContribution { msgListener.dispose(); typeListener.dispose(); - if (request.live && !(response instanceof ReplyResponse)) { + if (response instanceof ReplyResponse) { + // update hunks after a reply response + await this._session.hunkData.recompute(); + + } else if (request.live) { + // undo changes that might have been made when not + // having a reply response this._strategy?.undoChanges(modelAltVersionIdNow); } @@ -803,7 +809,6 @@ export class InlineChatController implements IEditorContribution { this._resetWidget(); - this._pausedStrategies.set(this._session, this._strategy); this._strategy.pause?.(); this._session = undefined; } @@ -831,21 +836,22 @@ export class InlineChatController implements IEditorContribution { } private async[State.CANCEL]() { - assertType(this._session); - assertType(this._strategy); - this._sessionStore.clear(); + if (this._session) { + // assertType(this._session); + assertType(this._strategy); + this._sessionStore.clear(); - try { - await this._strategy.cancel(); - } catch (err) { - this._dialogService.error(localize('err.discard', "Failed to discard changes.", toErrorMessage(err))); - this._log('FAILED to discard changes'); - this._log(err); + try { + await this._strategy.cancel(); + } catch (err) { + this._dialogService.error(localize('err.discard', "Failed to discard changes.", toErrorMessage(err))); + this._log('FAILED to discard changes'); + this._log(err); + } + this._inlineChatSessionService.releaseSession(this._session); } this._resetWidget(); - this._inlineChatSessionService.releaseSession(this._session); - this._strategy?.dispose(); this._strategy = undefined; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts index 7846aa594a9..2a360f95b2a 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts @@ -9,7 +9,7 @@ import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IInlineChatSessionService } from './inlineChatSessionService'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingService.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingService.ts new file mode 100644 index 00000000000..0ed8719ecf8 --- /dev/null +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingService.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; + + +export const IInlineChatSavingService = createDecorator('IInlineChatSavingService '); + +export interface IInlineChatSavingService { + _serviceBrand: undefined; + + markChanged(session: Session): void; + +} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSaving.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts similarity index 52% rename from src/vs/workbench/contrib/inlineChat/browser/inlineChatSaving.ts rename to src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts index c6f7dba516b..82effd20020 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSaving.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts @@ -5,37 +5,31 @@ import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { DisposableStore, MutableDisposable, dispose } from 'vs/base/common/lifecycle'; -import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { DisposableStore, IDisposable, MutableDisposable, dispose } from 'vs/base/common/lifecycle'; +import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { ITextModel } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/editor'; -import { IInlineChatSessionService, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IInlineChatSessionService } from './inlineChatSessionService'; import { InlineChatConfigKeys } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { GroupsOrder, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; - -export const IInlineChatSavingService = createDecorator('IInlineChatSavingService '); - -export interface IInlineChatSavingService { - _serviceBrand: undefined; - - markChanged(session: Session): void; - -} +import { IInlineChatSavingService } from './inlineChatSavingService'; +import { Iterable } from 'vs/base/common/iterator'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; interface SessionData { readonly dispose: () => void; readonly session: Session; - readonly group: IEditorGroup; + readonly groupCandidate: IEditorGroup; } -export class InlineChatSavingService implements IInlineChatSavingService { +export class InlineChatSavingServiceImpl implements IInlineChatSavingService { declare readonly _serviceBrand: undefined; @@ -68,13 +62,12 @@ export class InlineChatSavingService implements IInlineChatSavingService { this._installSaveParticpant(); } - const disposable = this._fileConfigService.disableAutoSave(session.textModelN.uri); - const group = this._getEditorGroup(session); + const saveConfig = this._fileConfigService.disableAutoSave(session.textModelN.uri); this._sessionData.set(session, { + groupCandidate: this._editorGroupService.activeGroup, session, - group, dispose: () => { - disposable.dispose(); + saveConfig.dispose(); this._sessionData.delete(session); if (this._sessionData.size === 0) { this._saveParticipant.clear(); @@ -114,60 +107,104 @@ export class InlineChatSavingService implements IInlineChatSavingService { return; } - const store = new DisposableStore(); - - const allDone = new Promise(resolve => { - store.add(this._inlineChatSessionService.onDidEndSession(e => { - - const data = sessions.get(e.session); - if (!data) { - return; - } - - data.dispose(); - sessions.delete(e.session); - - if (sessions.size === 0) { - resolve(); // DONE, release save block! - } - })); - }); - progress.report({ message: sessions.size === 1 ? localize('inlineChat', "Waiting for Inline Chat changes to be Accepted or Discarded...") : localize('inlineChat.N', "Waiting for Inline Chat changes in {0} editors to be Accepted or Discarded...", sessions.size) }); - await this._revealInlineChatSessions(sessions.values()); - - try { - await raceCancellation(allDone, token); - } finally { - store.dispose(); - } - } - - private _getEditorGroup(session: Session): IEditorGroup { - const candidate = this._editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(group => { - return getCodeEditor(group.activeEditorPane?.getControl()) === session.editor; + // reveal all sessions in order and also show dangling sessions + const { groups, orphans } = this._getGroupsAndOrphans(sessions.values()); + const editorsOpenedAndSessionsEnded = this._openAndWait(groups, token).then(() => { + if (token.isCancellationRequested) { + return; + } + return this._openAndWait(Iterable.map(orphans, s => [this._editorGroupService.activeGroup, s]), token); }); - return candidate ?? this._editorGroupService.activeGroup; + + // fallback: resolve when all sessions for this model have been resolved. this is independent of the editor opening + const allSessionsEnded = this._waitForSessions(Iterable.concat(groups.values(), orphans), token); + + await Promise.race([allSessionsEnded, editorsOpenedAndSessionsEnded]); } - private async _revealInlineChatSessions(sessions: Iterable): Promise { + private _getGroupsAndOrphans(sessions: Iterable) { + + const groupByEditor = new Map(); + for (const group of this._editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) { + const candidate = group.activeEditorPane?.getControl(); + if (isCodeEditor(candidate)) { + groupByEditor.set(candidate, group); + } + } + + const groups = new Map(); + const orphans = new Set(); for (const data of sessions) { - - const inputs = data.group - .findEditors(data.session.textModelN.uri) - .filter(input => input.editorId === DEFAULT_EDITOR_ASSOCIATION.id); - - if (inputs.length === 0) { - await this._editorService.openEditor({ resource: data.session.textModelN.uri }, data.group); + const editor = this._inlineChatSessionService.getCodeEditor(data.session); + const group = groupByEditor.get(editor); + if (group) { + // there is only one session per group because all sessions have the same model + // because we save one file. + groups.set(group, data); + } else if (this._editorGroupService.groups.includes(data.groupCandidate)) { + // the group candidate is still there. use it + groups.set(data.groupCandidate, data); } else { - await data.group.openEditor(inputs[0]); + orphans.add(data); } } + return { groups, orphans }; + } + + private async _openAndWait(groups: Iterable<[IEditorGroup, SessionData]>, token: CancellationToken) { + const sessions = new Set(); + for (const [group, data] of groups) { + const input: IResourceEditorInput = { resource: data.session.textModelN.uri, options: { override: DEFAULT_EDITOR_ASSOCIATION.id } }; + const pane = await this._editorService.openEditor(input, group); + const ctrl = pane?.getControl(); + if (!isCodeEditor(ctrl)) { + // PANIC + return; + } + this._inlineChatSessionService.moveSession(data.session, ctrl); + sessions.add(data); + } + await this._waitForSessions(sessions, token); + } + + private async _waitForSessions(iterable: Iterable, token: CancellationToken) { + + const sessions = new Map(); + for (const item of iterable) { + sessions.set(item.session, item); + } + + if (sessions.size === 0) { + // nothing to do + return; + } + + let listener: IDisposable | undefined; + + const whenEnded = new Promise(resolve => { + listener = this._inlineChatSessionService.onDidEndSession(e => { + const data = sessions.get(e.session); + if (data) { + data.dispose(); + sessions.delete(e.session); + if (sessions.size === 0) { + resolve(); // DONE, release waiting + } + } + }); + }); + + try { + await raceCancellation(whenEnded, token); + } finally { + listener?.dispose(); + } } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 4ece029e74b..97c7d2cab7c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -8,23 +8,13 @@ import { Emitter, Event } from 'vs/base/common/event'; import { ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; import { IWorkspaceTextEdit, TextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; import { IModelDecorationOptions, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; -import { EditMode, IInlineChatSessionProvider, IInlineChatSession, IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatResponse, IInlineChatService, InlineChatResponseType, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { EditMode, IInlineChatSessionProvider, IInlineChatSession, IInlineChatBulkEditResponse, IInlineChatEditResponse, InlineChatResponseType, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IModelService } from 'vs/editor/common/services/model'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ModelDecorationOptions, createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; -import { ILogService } from 'vs/platform/log/common/log'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { Iterable } from 'vs/base/common/iterator'; +import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { isCancellationError } from 'vs/base/common/errors'; import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation'; -import { raceCancellation } from 'vs/base/common/async'; -import { DetailedLineRangeMapping, LineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; +import { DetailedLineRangeMapping, LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -32,14 +22,15 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; +import { Recording } from './inlineChatSessionService'; +import { LineRange } from 'vs/editor/common/core/lineRange'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; +import { asRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; +import { coalesceInPlace } from 'vs/base/common/arrays'; +import { Iterable } from 'vs/base/common/iterator'; -export type Recording = { - when: Date; - session: IInlineChatSession; - exchanges: { prompt: string; res: IInlineChatResponse }[]; -}; -type TelemetryData = { +export type TelemetryData = { extension: string; rounds: string; undos: string; @@ -50,7 +41,7 @@ type TelemetryData = { editMode: string; }; -type TelemetryDataClassification = { +export type TelemetryDataClassification = { owner: 'jrieken'; comment: 'Data about an interaction editor session'; extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension providing the data' }; @@ -69,7 +60,7 @@ export enum ExpansionState { NOT_CROPPED = 'not_cropped' } -class SessionWholeRange { +export class SessionWholeRange { private static readonly _options: IModelDecorationOptions = ModelDecorationOptions.register({ description: 'inlineChat/session/wholeRange' }); @@ -149,12 +140,12 @@ export class Session { constructor( readonly editMode: EditMode, - readonly editor: ICodeEditor, readonly textModel0: ITextModel, readonly textModelN: ITextModel, readonly provider: IInlineChatSessionProvider, readonly session: IInlineChatSession, - readonly wholeRange: SessionWholeRange + readonly wholeRange: SessionWholeRange, + readonly hunkData: HunkData, ) { this.textModelNAltVersion = textModelN.getAlternativeVersionId(); this._teldata = { @@ -412,182 +403,201 @@ export class ReplyResponse { } } -export interface ISessionKeyComputer { - getComparisonKey(editor: ICodeEditor, uri: URI): string; -} +// --- -export const IInlineChatSessionService = createDecorator('IInlineChatSessionService'); +export class HunkData { -export interface IInlineChatSessionService { - _serviceBrand: undefined; + private static readonly _HUNK_TRACKED_RANGE = ModelDecorationOptions.register({ + description: 'inline-chat-hunk-tracked-range', + }); - onWillStartSession: Event; + private static readonly _HUNK_THRESHOLD = 8; - onDidEndSession: Event<{ editor: ICodeEditor; session: Session }>; - - createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: IRange }, token: CancellationToken): Promise; - - getSession(editor: ICodeEditor, uri: URI): Session | undefined; - - releaseSession(session: Session): void; - - registerSessionKeyComputer(scheme: string, value: ISessionKeyComputer): IDisposable; - - // - - recordings(): readonly Recording[]; - - dispose(): void; -} - -type SessionData = { - session: Session; - store: IDisposable; -}; - -export class InlineChatSessionService implements IInlineChatSessionService { - - declare _serviceBrand: undefined; - - private readonly _onWillStartSession = new Emitter(); - readonly onWillStartSession: Event = this._onWillStartSession.event; - - private readonly _onDidEndSession = new Emitter<{ editor: ICodeEditor; session: Session }>(); - readonly onDidEndSession: Event<{ editor: ICodeEditor; session: Session }> = this._onDidEndSession.event; - - private readonly _sessions = new Map(); - private readonly _keyComputers = new Map(); - private _recordings: Recording[] = []; + private readonly _data = new Map(); constructor( - @IInlineChatService private readonly _inlineChatService: IInlineChatService, - @ITelemetryService private readonly _telemetryService: ITelemetryService, - @IModelService private readonly _modelService: IModelService, - @ITextModelService private readonly _textModelService: ITextModelService, - @ILogService private readonly _logService: ILogService, + @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, + private readonly _textModel0: ITextModel, + private readonly _textModelN: ITextModel, ) { } - dispose() { - this._onWillStartSession.dispose(); - this._onDidEndSession.dispose(); - this._sessions.forEach(x => x.store.dispose()); - this._sessions.clear(); + dispose(): void { + if (!this._textModelN.isDisposed()) { + this._textModelN.changeDecorations(accessor => { + for (const { textModelNDecorations } of this._data.values()) { + textModelNDecorations.forEach(accessor.removeDecoration, accessor); + } + }); + } + if (!this._textModel0.isDisposed()) { + this._textModel0.changeDecorations(accessor => { + for (const { textModel0Decorations } of this._data.values()) { + textModel0Decorations.forEach(accessor.removeDecoration, accessor); + } + }); + } } - async createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: Range }, token: CancellationToken): Promise { + async recompute() { - const provider = Iterable.first(this._inlineChatService.getAllProvider()); - if (!provider) { - this._logService.trace('[IE] NO provider found'); - return undefined; + const diff = await this._editorWorkerService.computeDiff(this._textModel0.uri, this._textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, computeMoves: false }, 'advanced'); + + if (!diff || diff.changes.length === 0) { + // return new HunkData([], session); + return; } - this._onWillStartSession.fire(editor); - - const textModel = editor.getModel(); - const selection = editor.getSelection(); - let raw: IInlineChatSession | undefined | null; - try { - raw = await raceCancellation( - Promise.resolve(provider.prepareInlineChatSession(textModel, selection, token)), - token - ); - } catch (error) { - this._logService.error('[IE] FAILED to prepare session', provider.debugName); - this._logService.error(error); - return undefined; - } - if (!raw) { - this._logService.trace('[IE] NO session', provider.debugName); - return undefined; - } - this._logService.trace('[IE] NEW session', provider.debugName); - - this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${provider.debugName}`); - const store = new DisposableStore(); - - // create: keep a reference to prevent disposal of the "actual" model - const refTextModelN = await this._textModelService.createModelReference(textModel.uri); - store.add(refTextModelN); - - // create: keep a snapshot of the "actual" model - const textModel0 = this._modelService.createModel( - createTextBufferFactoryFromSnapshot(textModel.createSnapshot()), - { languageId: textModel.getLanguageId(), onDidChange: Event.None }, - undefined, true - ); - store.add(textModel0); - - let wholeRange = options.wholeRange; - if (!wholeRange) { - wholeRange = raw.wholeRange ? Range.lift(raw.wholeRange) : editor.getSelection(); - } - - - // install managed-marker for the decoration range - const wholeRangeMgr = new SessionWholeRange(textModel, wholeRange); - store.add(wholeRangeMgr); - - const session = new Session(options.editMode, editor, textModel0, textModel, provider, raw, wholeRangeMgr); - - // store: key -> session - const key = this._key(editor, textModel.uri); - if (this._sessions.has(key)) { - store.dispose(); - throw new Error(`Session already stored for ${key}`); - } - this._sessions.set(key, { session, store }); - return session; - } - - releaseSession(session: Session): void { - - const { editor } = session; - - // cleanup - for (const [key, value] of this._sessions) { - if (value.session === session) { - value.store.dispose(); - this._sessions.delete(key); - this._logService.trace(`[IE] did RELEASED session for ${editor.getId()}, ${session.provider.debugName}`); - break; + // merge changes neighboring changes + const mergedChanges = [diff.changes[0]]; + for (let i = 1; i < diff.changes.length; i++) { + const lastChange = mergedChanges[mergedChanges.length - 1]; + const thisChange = diff.changes[i]; + if (thisChange.modified.startLineNumber - lastChange.modified.endLineNumberExclusive <= HunkData._HUNK_THRESHOLD) { + mergedChanges[mergedChanges.length - 1] = new DetailedLineRangeMapping( + lastChange.original.join(thisChange.original), + lastChange.modified.join(thisChange.modified), + (lastChange.innerChanges ?? []).concat(thisChange.innerChanges ?? []) + ); + } else { + mergedChanges.push(thisChange); } } - // keep recording - const newLen = this._recordings.unshift(session.asRecording()); - if (newLen > 5) { - this._recordings.pop(); + const hunks = mergedChanges.map(change => new RawHunk(change.original, change.modified, change.innerChanges ?? [])); + + this._textModelN.changeDecorations(accessorN => { + + this._textModel0.changeDecorations(accessor0 => { + + // clean up old decorations + for (const { textModelNDecorations, textModel0Decorations } of this._data.values()) { + textModelNDecorations.forEach(accessorN.removeDecoration, accessorN); + textModel0Decorations.forEach(accessor0.removeDecoration, accessor0); + } + + this._data.clear(); + + // add new decorations + for (const hunk of hunks) { + + const textModelNDecorations: string[] = []; + const textModel0Decorations: string[] = []; + + textModelNDecorations.push(accessorN.addDecoration(asRange(hunk.modified, this._textModelN), HunkData._HUNK_TRACKED_RANGE)); + textModel0Decorations.push(accessor0.addDecoration(asRange(hunk.original, this._textModel0), HunkData._HUNK_TRACKED_RANGE)); + + for (const change of hunk.changes) { + textModelNDecorations.push(accessorN.addDecoration(change.modifiedRange, HunkData._HUNK_TRACKED_RANGE)); + textModel0Decorations.push(accessor0.addDecoration(change.originalRange, HunkData._HUNK_TRACKED_RANGE)); + } + + this._data.set(hunk, { + textModelNDecorations, + textModel0Decorations, + state: HunkState.Pending + }); + } + }); + }); + } + + get size(): number { + return this._data.size; + } + + get pending(): number { + return Iterable.reduce(this._data.values(), (r, { state }) => r + (state === HunkState.Pending ? 1 : 0), 0); + } + + getInfo(): HunkInformation[] { + + const result: HunkInformation[] = []; + + for (const [hunk, data] of this._data.entries()) { + const item: HunkInformation = { + getState: () => { + return data.state; + }, + isInsertion: () => { + return hunk.original.isEmpty; + }, + getRangesN: () => { + const ranges = data.textModelNDecorations.map(id => this._textModelN.getDecorationRange(id)); + coalesceInPlace(ranges); + return ranges; + }, + getRanges0: () => { + const ranges = data.textModel0Decorations.map(id => this._textModel0.getDecorationRange(id)); + coalesceInPlace(ranges); + return ranges; + }, + discardChanges: () => { + // DISCARD: replace modified range with original value. The modified range is retrieved from a decoration + // which was created above so that typing in the editor keeps discard working. + if (data.state === HunkState.Pending) { + const edits: ISingleEditOperation[] = []; + const rangesN = item.getRangesN(); + const ranges0 = item.getRanges0(); + for (let i = 1; i < rangesN.length; i++) { + const modifiedRange = rangesN[i]; + const originalValue = this._textModel0.getValueInRange(ranges0[i]); + edits.push(EditOperation.replace(modifiedRange, originalValue)); + } + this._textModelN.pushEditOperations(null, edits, () => null); + data.state = HunkState.Rejected; + } + }, + acceptChanges: () => { + // ACCEPT: replace original range with modified value. The modified value is retrieved from the model via + // its decoration and the original range is retrieved from the hunk. + if (data.state === HunkState.Pending) { + const edits: ISingleEditOperation[] = []; + const rangesN = item.getRangesN(); + const ranges0 = item.getRanges0(); + for (let i = 1; i < ranges0.length; i++) { + const originalRange = ranges0[i]; + const modifiedValue = this._textModelN.getValueInRange(rangesN[i]); + edits.push(EditOperation.replace(originalRange, modifiedValue)); + } + this._textModel0.pushEditOperations(null, edits, () => null); + data.state = HunkState.Accepted; + } + } + }; + result.push(item); } - // send telemetry - this._telemetryService.publicLog2('interactiveEditor/session', session.asTelemetryData()); - - this._onDidEndSession.fire({ editor, session }); + return result; } - - getSession(editor: ICodeEditor, uri: URI): Session | undefined { - const key = this._key(editor, uri); - return this._sessions.get(key)?.session; - } - - private _key(editor: ICodeEditor, uri: URI): string { - const item = this._keyComputers.get(uri.scheme); - return item - ? item.getComparisonKey(editor, uri) - : `${editor.getId()}@${uri.toString()}`; - - } - - registerSessionKeyComputer(scheme: string, value: ISessionKeyComputer): IDisposable { - this._keyComputers.set(scheme, value); - return toDisposable(() => this._keyComputers.delete(scheme)); - } - - // --- debug - - recordings(): readonly Recording[] { - return this._recordings; - } - +} + +class RawHunk { + constructor( + readonly original: LineRange, + readonly modified: LineRange, + readonly changes: RangeMapping[] + ) { } +} + +export const enum HunkState { + Pending = 0, + Accepted = 1, + Rejected = 2 +} + +export interface HunkInformation { + /** + * The first element [0] is the whole modified range and subsequent elements are word-level changes + */ + getRangesN(): Range[]; + + getRanges0(): Range[]; + + isInsertion(): boolean; + + discardChanges(): void; + + acceptChanges(): void; + + getState(): HunkState; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts new file mode 100644 index 00000000000..ee8de25c12d --- /dev/null +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.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 { URI } from 'vs/base/common/uri'; +import { Event } from 'vs/base/common/event'; +import { EditMode, IInlineChatSession, IInlineChatResponse } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IRange } from 'vs/editor/common/core/range'; +import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Session } from './inlineChatSession'; + + +export type Recording = { + when: Date; + session: IInlineChatSession; + exchanges: { prompt: string; res: IInlineChatResponse }[]; +}; + +export interface ISessionKeyComputer { + getComparisonKey(editor: ICodeEditor, uri: URI): string; +} + +export const IInlineChatSessionService = createDecorator('IInlineChatSessionService'); + +export interface IInlineChatSessionService { + _serviceBrand: undefined; + + onWillStartSession: Event; + + onDidMoveSession: Event<{ editor: ICodeEditor; session: Session }>; + + onDidEndSession: Event<{ editor: ICodeEditor; session: Session }>; + + createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: IRange }, token: CancellationToken): Promise; + + moveSession(session: Session, newEditor: ICodeEditor): void; + + getCodeEditor(session: Session): ICodeEditor; + + getSession(editor: ICodeEditor, uri: URI): Session | undefined; + + releaseSession(session: Session): void; + + registerSessionKeyComputer(scheme: string, value: ISessionKeyComputer): IDisposable; + + // + recordings(): readonly Recording[]; + + dispose(): void; +} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts new file mode 100644 index 00000000000..57e865322f9 --- /dev/null +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -0,0 +1,223 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { URI } from 'vs/base/common/uri'; +import { Emitter, Event } from 'vs/base/common/event'; +import { EditMode, IInlineChatSession, IInlineChatService } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { Range } from 'vs/editor/common/core/range'; +import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; +import { ILogService } from 'vs/platform/log/common/log'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Iterable } from 'vs/base/common/iterator'; +import { raceCancellation } from 'vs/base/common/async'; +import { Recording, IInlineChatSessionService, ISessionKeyComputer } from './inlineChatSessionService'; +import { HunkData, Session, SessionWholeRange, TelemetryData, TelemetryDataClassification } from './inlineChatSession'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; + +type SessionData = { + editor: ICodeEditor; + session: Session; + store: IDisposable; +}; + +export class InlineChatSessionServiceImpl implements IInlineChatSessionService { + + declare _serviceBrand: undefined; + + private readonly _onWillStartSession = new Emitter(); + readonly onWillStartSession: Event = this._onWillStartSession.event; + + private readonly _onDidMoveSession = new Emitter<{ session: Session; editor: ICodeEditor }>(); + readonly onDidMoveSession: Event<{ session: Session; editor: ICodeEditor }> = this._onDidMoveSession.event; + + private readonly _onDidEndSession = new Emitter<{ editor: ICodeEditor; session: Session }>(); + readonly onDidEndSession: Event<{ editor: ICodeEditor; session: Session }> = this._onDidEndSession.event; + + private readonly _sessions = new Map(); + private readonly _keyComputers = new Map(); + private _recordings: Recording[] = []; + + constructor( + @IInlineChatService private readonly _inlineChatService: IInlineChatService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IModelService private readonly _modelService: IModelService, + @ITextModelService private readonly _textModelService: ITextModelService, + @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, + @ILogService private readonly _logService: ILogService + ) { } + + dispose() { + this._onWillStartSession.dispose(); + this._onDidEndSession.dispose(); + this._sessions.forEach(x => x.store.dispose()); + this._sessions.clear(); + } + + async createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: Range }, token: CancellationToken): Promise { + + const provider = Iterable.first(this._inlineChatService.getAllProvider()); + if (!provider) { + this._logService.trace('[IE] NO provider found'); + return undefined; + } + + this._onWillStartSession.fire(editor); + + const textModel = editor.getModel(); + const selection = editor.getSelection(); + let raw: IInlineChatSession | undefined | null; + try { + raw = await raceCancellation( + Promise.resolve(provider.prepareInlineChatSession(textModel, selection, token)), + token + ); + } catch (error) { + this._logService.error('[IE] FAILED to prepare session', provider.debugName); + this._logService.error(error); + return undefined; + } + if (!raw) { + this._logService.trace('[IE] NO session', provider.debugName); + return undefined; + } + this._logService.trace('[IE] NEW session', provider.debugName); + + this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${provider.debugName}`); + const store = new DisposableStore(); + + // create: keep a reference to prevent disposal of the "actual" model + const refTextModelN = await this._textModelService.createModelReference(textModel.uri); + store.add(refTextModelN); + + // create: keep a snapshot of the "actual" model + const textModel0 = this._modelService.createModel( + createTextBufferFactoryFromSnapshot(textModel.createSnapshot()), + { languageId: textModel.getLanguageId(), onDidChange: Event.None }, + undefined, true + ); + store.add(textModel0); + + let wholeRange = options.wholeRange; + if (!wholeRange) { + wholeRange = raw.wholeRange ? Range.lift(raw.wholeRange) : editor.getSelection(); + } + + + // install managed-marker for the decoration range + const wholeRangeMgr = new SessionWholeRange(textModel, wholeRange); + store.add(wholeRangeMgr); + + const hunkData = new HunkData(this._editorWorkerService, textModel0, textModel); + store.add(hunkData); + + const session = new Session(options.editMode, textModel0, textModel, provider, raw, wholeRangeMgr, hunkData); + + // store: key -> session + const key = this._key(editor, textModel.uri); + if (this._sessions.has(key)) { + store.dispose(); + throw new Error(`Session already stored for ${key}`); + } + this._sessions.set(key, { session, editor, store }); + return session; + } + + moveSession(session: Session, target: ICodeEditor): void { + const newKey = this._key(target, session.textModelN.uri); + const existing = this._sessions.get(newKey); + if (existing) { + if (existing.session !== session) { + throw new Error(`Cannot move session because the target editor already/still has one`); + } else { + // noop + return; + } + } + + let found = false; + for (const [oldKey, data] of this._sessions) { + if (data.session === session) { + found = true; + this._sessions.delete(oldKey); + this._sessions.set(newKey, { ...data, editor: target }); + this._logService.trace(`[IE] did MOVE session for ${data.editor.getId()} to NEW EDITOR ${target.getId()}, ${session.provider.debugName}`); + this._onDidMoveSession.fire({ session, editor: target }); + break; + } + } + if (!found) { + throw new Error(`Cannot move session because it is not stored`); + } + } + + releaseSession(session: Session): void { + + let data: SessionData | undefined; + + // cleanup + for (const [key, value] of this._sessions) { + if (value.session === session) { + data = value; + value.store.dispose(); + this._sessions.delete(key); + this._logService.trace(`[IE] did RELEASED session for ${value.editor.getId()}, ${session.provider.debugName}`); + break; + } + } + + if (!data) { + // double remove + return; + } + + // keep recording + const newLen = this._recordings.unshift(session.asRecording()); + if (newLen > 5) { + this._recordings.pop(); + } + + // send telemetry + this._telemetryService.publicLog2('interactiveEditor/session', session.asTelemetryData()); + + this._onDidEndSession.fire({ editor: data.editor, session }); + } + + getCodeEditor(session: Session): ICodeEditor { + for (const [, data] of this._sessions) { + if (data.session === session) { + return data.editor; + } + } + throw new Error('session not found'); + } + + getSession(editor: ICodeEditor, uri: URI): Session | undefined { + const key = this._key(editor, uri); + return this._sessions.get(key)?.session; + } + + private _key(editor: ICodeEditor, uri: URI): string { + const item = this._keyComputers.get(uri.scheme); + return item + ? item.getComparisonKey(editor, uri) + : `${editor.getId()}@${uri.toString()}`; + + } + + registerSessionKeyComputer(scheme: string, value: ISessionKeyComputer): IDisposable { + this._keyComputers.set(scheme, value); + return toDisposable(() => this._keyComputers.delete(scheme)); + } + + // --- debug + recordings(): readonly Recording[] { + return this._recordings; + } + +} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 7fe41b4a244..f118144130e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -8,9 +8,8 @@ import { coalesceInPlace, equals, tail } from 'vs/base/common/arrays'; import { AsyncIterableSource, IntervalTimer } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { Iterable } from 'vs/base/common/iterator'; import { Lazy } from 'vs/base/common/lazy'; -import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { themeColorFromId } from 'vs/base/common/themables'; import { ICodeEditor, IViewZone, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; @@ -20,7 +19,7 @@ import { LineRange } from 'vs/editor/common/core/lineRange'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import { DetailedLineRangeMapping, LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; +import { LineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; import { TextEdit } from 'vs/editor/common/languages'; import { ICursorStateComputer, IIdentifiedSingleEditOperation, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel, IValidEditOperation, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; @@ -34,9 +33,11 @@ import { IProgress, Progress } from 'vs/platform/progress/common/progress'; import { SaveReason } from 'vs/workbench/common/editor'; import { countWords, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { InlineChatFileCreatePreviewWidget, InlineChatLivePreviewWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget'; -import { ReplyResponse, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { HunkInformation, ReplyResponse, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { HunkState } from './inlineChatSession'; +import { assertType } from 'vs/base/common/types'; export abstract class EditModeStrategy { @@ -464,32 +465,8 @@ export function asProgressiveEdit(interval: IntervalTimer, edit: IIdentifiedSing } -// --- - -class Hunk { - constructor( - readonly original: LineRange, - readonly modified: LineRange, - readonly changes: RangeMapping[] - ) { } -} - -type HunkTrackedRange = { - /** - * The first element [0] is the whole modified range and subsequent elements are word-level changes - */ - getRanges(): Range[]; - - discardChanges(): void; -}; - -const enum HunkState { - Accepted = 1, - Rejected = 2, -} - type HunkDisplayData = { - acceptedOrRejected: HunkState | undefined; + decorationIds: string[]; viewZoneId: string | undefined; @@ -500,21 +477,12 @@ type HunkDisplayData = { acceptHunk: () => void; discardHunk: () => void; toggleDiff?: () => any; + remove(): void; }; -interface HunkDisplay { - renderHunks(): HunkDisplayData | undefined; - hideHunks(): void; - discardHunks(): void; -} export class LiveStrategy extends EditModeStrategy { - private readonly _decoTrackedRange = ModelDecorationOptions.register({ - description: 'inline-tracked-range', - className: 'inline-chat-tracked-range' - }); - private readonly _decoInsertedText = ModelDecorationOptions.register({ description: 'inline-modified-line', className: 'inline-chat-inserted-range-linehighlight', @@ -531,7 +499,6 @@ export class LiveStrategy extends EditModeStrategy { }); private readonly _store = new DisposableStore(); - private readonly _renderStore = new DisposableStore(); private readonly _previewZone: Lazy; private readonly _ctxCurrentChangeHasDiff: IContextKey; @@ -571,13 +538,16 @@ export class LiveStrategy extends EditModeStrategy { private _resetDiff(): void { this._ctxCurrentChangeHasDiff.reset(); this._ctxCurrentChangeShowsDiff.reset(); - this._renderStore.clear(); this._zone.widget.updateStatus(''); this._progressiveEditingDecorations.clear(); + + + for (const data of this._hunkDisplayData.values()) { + data.remove(); + } } override pause = () => { - this._hunkDisplay?.hideHunks(); this._ctxCurrentChangeShowsDiff.reset(); }; @@ -596,7 +566,9 @@ export class LiveStrategy extends EditModeStrategy { } async cancel() { - this._hunkDisplay?.discardHunks(); + for (const item of this._session.hunkData.getInfo()) { + item.discardChanges(); + } this._resetDiff(); } @@ -659,7 +631,7 @@ export class LiveStrategy extends EditModeStrategy { } } - private _hunkDisplay?: HunkDisplay; + private readonly _hunkDisplayData = new Map(); override async renderChanges(response: ReplyResponse) { @@ -671,271 +643,172 @@ export class LiveStrategy extends EditModeStrategy { this._progressiveEditingDecorations.clear(); - if (!this._hunkDisplay) { - - this._renderStore.add(toDisposable(() => { - this._hunkDisplay?.hideHunks(); - this._hunkDisplay = undefined; - })); - - const hunkTrackedRanges = new Map(); - const hunkDisplayData = new Map(); - - // (INIT) compute hunks - const hunks = await this._computeHunks(); - if (hunks.length === 0) { - this._hunkDisplay = { renderHunks() { return undefined; }, hideHunks() { }, discardHunks() { } }; - return undefined; - } - - // (INIT) add tracked ranges per hunk - const model = this._editor.getModel()!; - model.changeDecorations(accessor => { - for (const hunk of hunks) { - const decorationIds: string[] = []; - const modifiedRange = asRange(hunk.modified, this._session.textModelN); - decorationIds.push(accessor.addDecoration(modifiedRange, this._decoTrackedRange)); - for (const change of hunk.changes) { - decorationIds.push(accessor.addDecoration(change.modifiedRange, this._decoTrackedRange)); - } - const hunkLiveInfo: HunkTrackedRange = { - getRanges: () => { - const ranges = decorationIds.map(id => model.getDecorationRange(id)); - coalesceInPlace(ranges); - return ranges; - }, - discardChanges: () => { - const edits: ISingleEditOperation[] = []; - const ranges = hunkLiveInfo.getRanges(); - for (let i = 1; i < ranges.length; i++) { - // DISCARD: replace modified range with original value. The modified range is retrieved from a decoration - // which was created above so that typing in the editor keeps discard working. - const modifiedRange = ranges[i]; - const originalValue = this._session.textModel0.getValueInRange(hunk.changes[i - 1].originalRange); - edits.push(EditOperation.replace(modifiedRange, originalValue)); - } - this._session.textModelN.pushEditOperations(null, edits, () => null); - } - }; - hunkTrackedRanges.set(hunk, hunkLiveInfo); - this._renderStore.add(toDisposable(() => { - model.deltaDecorations(decorationIds, []); - })); - } - }); + const renderHunks = () => { let widgetData: HunkDisplayData | undefined; - const renderHunks = () => { + changeDecorationsAndViewZones(this._editor, (decorationsAccessor, viewZoneAccessor) => { - changeDecorationsAndViewZones(this._editor, (decorationsAccessor, viewZoneAccessor) => { + const keysNow = new Set(this._hunkDisplayData.keys()); + widgetData = undefined; - widgetData = undefined; + for (const hunkData of this._session.hunkData.getInfo()) { - for (const hunk of hunks) { + keysNow.delete(hunkData); - const hunkRanges = hunkTrackedRanges.get(hunk)!.getRanges(); - let data = hunkDisplayData.get(hunk); - if (!data) { - // first time -> create decoration - const decorationIds: string[] = []; - for (let i = 0; i < hunkRanges.length; i++) { - decorationIds.push(decorationsAccessor.addDecoration(hunkRanges[i], i === 0 - ? this._decoInsertedText - : this._decoInsertedTextRange) - ); - } - - const acceptHunk = () => { - // ACCEPT: stop rendering this as inserted - hunkDisplayData.get(hunk)!.acceptedOrRejected = HunkState.Accepted; - renderHunks(); - }; - - const discardHunk = () => { - const info = hunkTrackedRanges.get(hunk)!; - info.discardChanges(); - hunkDisplayData.get(hunk)!.acceptedOrRejected = HunkState.Rejected; - renderHunks(); - }; - - // original view zone - const mightContainNonBasicASCII = this._session.textModel0.mightContainNonBasicASCII() ?? false; - const mightContainRTL = this._session.textModel0.mightContainRTL() ?? false; - const renderOptions = RenderOptions.fromEditor(this._editor); - const source = new LineSource( - hunk.original.mapToLineArray(l => this._session.textModel0.tokenization.getLineTokens(l)), - [], - mightContainNonBasicASCII, - mightContainRTL, + const hunkRanges = hunkData.getRangesN(); + let data = this._hunkDisplayData.get(hunkData); + if (!data) { + // first time -> create decoration + const decorationIds: string[] = []; + for (let i = 0; i < hunkRanges.length; i++) { + decorationIds.push(decorationsAccessor.addDecoration(hunkRanges[i], i === 0 + ? this._decoInsertedText + : this._decoInsertedTextRange) ); - const domNode = document.createElement('div'); - domNode.className = 'inline-chat-original-zone2'; - const result = renderLines(source, renderOptions, [new InlineDecoration(new Range(hunk.original.startLineNumber, 1, hunk.original.startLineNumber, 1), '', InlineDecorationType.Regular)], domNode); - const viewZoneData: IViewZone = { - afterLineNumber: -1, - heightInLines: result.heightInLines, - domNode, - }; + } - const toggleDiff = () => { - const scrollState = StableEditorScrollState.capture(this._editor); - if (!data!.viewZoneId) { + const acceptHunk = () => { + hunkData.acceptChanges(); + renderHunks(); + }; - this._editor.changeViewZones(accessor => { - const [hunkRange] = hunkTrackedRanges.get(hunk)!.getRanges(); - viewZoneData.afterLineNumber = hunkRange.startLineNumber - 1; - data!.viewZoneId = accessor.addZone(viewZoneData); - }); - this._ctxCurrentChangeShowsDiff.set(true); + const discardHunk = () => { + hunkData.discardChanges(); + renderHunks(); + }; + + // original view zone + const mightContainNonBasicASCII = this._session.textModel0.mightContainNonBasicASCII(); + const mightContainRTL = this._session.textModel0.mightContainRTL(); + const renderOptions = RenderOptions.fromEditor(this._editor); + const originalRange = hunkData.getRanges0()[0]; + const source = new LineSource( + LineRange.fromRangeInclusive(originalRange).mapToLineArray(l => this._session.textModel0.tokenization.getLineTokens(l)), + [], + mightContainNonBasicASCII, + mightContainRTL, + ); + const domNode = document.createElement('div'); + domNode.className = 'inline-chat-original-zone2'; + const result = renderLines(source, renderOptions, [new InlineDecoration(new Range(originalRange.startLineNumber, 1, originalRange.startLineNumber, 1), '', InlineDecorationType.Regular)], domNode); + const viewZoneData: IViewZone = { + afterLineNumber: -1, + heightInLines: result.heightInLines, + domNode, + }; + + const toggleDiff = () => { + const scrollState = StableEditorScrollState.capture(this._editor); + changeDecorationsAndViewZones(this._editor, (_decorationsAccessor, viewZoneAccessor) => { + assertType(data); + if (!data.viewZone) { + const [hunkRange] = hunkData.getRangesN(); + viewZoneData.afterLineNumber = hunkRange.startLineNumber - 1; + data.viewZoneId = viewZoneAccessor.addZone(viewZoneData); } else { - this._editor.changeViewZones(accessor => { - accessor.removeZone(data!.viewZoneId!); - data!.viewZoneId = undefined; - }); - this._ctxCurrentChangeShowsDiff.set(false); + viewZoneAccessor.removeZone(data.viewZoneId!); + data.viewZoneId = undefined; } - scrollState.restore(this._editor); - }; + }); + this._ctxCurrentChangeShowsDiff.set(typeof data?.viewZoneId === 'number'); + scrollState.restore(this._editor); + }; - const zoneLineNumber = this._zone.position!.lineNumber; - const myDistance = zoneLineNumber <= hunkRanges[0].startLineNumber - ? hunkRanges[0].startLineNumber - zoneLineNumber - : zoneLineNumber - hunkRanges[0].endLineNumber; + const remove = () => { + changeDecorationsAndViewZones(this._editor, (decorationsAccessor, viewZoneAccessor) => { + assertType(data); + for (const decorationId of data.decorationIds) { + decorationsAccessor.removeDecoration(decorationId); + } + if (data.viewZoneId) { + viewZoneAccessor.removeZone(data.viewZoneId); + } + data.decorationIds = []; + data.viewZoneId = undefined; + }); + }; - data = { - acceptedOrRejected: undefined, - decorationIds, - viewZoneId: '', - viewZone: viewZoneData, - distance: myDistance, - position: hunkRanges[0].getStartPosition().delta(-1), - acceptHunk, - discardHunk, - toggleDiff: !hunk.original.isEmpty ? toggleDiff : undefined, - }; + const zoneLineNumber = this._zone.position!.lineNumber; + const myDistance = zoneLineNumber <= hunkRanges[0].startLineNumber + ? hunkRanges[0].startLineNumber - zoneLineNumber + : zoneLineNumber - hunkRanges[0].endLineNumber; - hunkDisplayData.set(hunk, data); + data = { + decorationIds, + viewZoneId: '', + viewZone: viewZoneData, + distance: myDistance, + position: hunkRanges[0].getStartPosition().delta(-1), + acceptHunk, + discardHunk, + toggleDiff: !hunkData.isInsertion() ? toggleDiff : undefined, + remove, + }; - } else if (data.acceptedOrRejected !== undefined) { - // accepted or rejected -> remove decoration - for (const decorationId of data.decorationIds) { - decorationsAccessor.removeDecoration(decorationId); - } - if (data.viewZoneId) { - viewZoneAccessor.removeZone(data.viewZoneId); - } + this._hunkDisplayData.set(hunkData, data); - data.decorationIds = []; - data.viewZoneId = undefined; + } else if (hunkData.getState() !== HunkState.Pending) { + data.remove(); - } else { - // update distance and position based on modifiedRange-decoration - const zoneLineNumber = this._zone.position!.lineNumber; - const modifiedRangeNow = hunkRanges[0]; - data.position = modifiedRangeNow.getStartPosition().delta(-1); - data.distance = zoneLineNumber <= modifiedRangeNow.startLineNumber - ? modifiedRangeNow.startLineNumber - zoneLineNumber - : zoneLineNumber - modifiedRangeNow.endLineNumber; - } - - if (!data.acceptedOrRejected) { - if (!widgetData || data.distance < widgetData.distance) { - widgetData = data; - } - } - } - }); - - if (widgetData) { - this._zone.updatePositionAndHeight(widgetData.position); - this._editor.revealPositionInCenterIfOutsideViewport(widgetData.position); - - const remainingHunks = Iterable.reduce(hunkDisplayData.values(), (p, c) => { return p + (c.acceptedOrRejected ? 0 : 1); }, 0); - this._updateSummaryMessage(remainingHunks); - - this._ctxCurrentChangeHasDiff.set(Boolean(widgetData.toggleDiff)); - this.toggleDiff = widgetData.toggleDiff; - this.acceptHunk = async () => widgetData!.acceptHunk(); - this.discardHunk = async () => widgetData!.discardHunk(); - - } else if (hunkDisplayData.size > 0) { - // everything accepted or rejected - let oneAccepted = false; - for (const data of hunkDisplayData.values()) { - if (data.acceptedOrRejected === HunkState.Accepted) { - oneAccepted = true; - break; - } - } - if (oneAccepted) { - this._onDidAccept.fire(); } else { - this._onDidDiscard.fire(); + // update distance and position based on modifiedRange-decoration + const zoneLineNumber = this._zone.position!.lineNumber; + const modifiedRangeNow = hunkRanges[0]; + data.position = modifiedRangeNow.getStartPosition().delta(-1); + data.distance = zoneLineNumber <= modifiedRangeNow.startLineNumber + ? modifiedRangeNow.startLineNumber - zoneLineNumber + : zoneLineNumber - modifiedRangeNow.endLineNumber; + } + + if (hunkData.getState() === HunkState.Pending && (!widgetData || data.distance < widgetData.distance)) { + widgetData = data; } } - return widgetData; - }; - - const hideHunks = () => { - changeDecorationsAndViewZones(this._editor, (decorationsAccessor, viewZoneAccessor) => { - for (const data of hunkDisplayData.values()) { - // remove decorations - for (const decorationId of data.decorationIds) { - decorationsAccessor.removeDecoration(decorationId); - } - // remove view zone - if (data.viewZoneId) { - viewZoneAccessor.removeZone(data.viewZoneId); - } - data.viewZone.domNode.remove(); + for (const key of keysNow) { + const data = this._hunkDisplayData.get(key); + if (data) { + this._hunkDisplayData.delete(key); + data.remove(); } - }); - hunkDisplayData.clear(); - }; - - const discardHunks = () => { - for (const data of hunkTrackedRanges.values()) { - data.discardChanges(); } - }; + }); - this._hunkDisplay = { renderHunks, hideHunks, discardHunks }; - } + if (widgetData) { + this._zone.updatePositionAndHeight(widgetData.position); + this._editor.revealPositionInCenterIfOutsideViewport(widgetData.position); - return this._hunkDisplay?.renderHunks()?.position; - } + const remainingHunks = this._session.hunkData.pending; + this._updateSummaryMessage(remainingHunks); - private static readonly HUNK_THRESHOLD = 8; + this._ctxCurrentChangeHasDiff.set(Boolean(widgetData.toggleDiff)); + this.toggleDiff = widgetData.toggleDiff; + this.acceptHunk = async () => widgetData!.acceptHunk(); + this.discardHunk = async () => widgetData!.discardHunk(); - private async _computeHunks(): Promise { - const diff = await this._editorWorkerService.computeDiff(this._session.textModel0.uri, this._session.textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, computeMoves: false }, 'advanced'); - - if (!diff || diff.changes.length === 0) { - return []; - } - - // merge changes neighboring changes - const mergedChanges = [diff.changes[0]]; - for (let i = 1; i < diff.changes.length; i++) { - const lastChange = mergedChanges[mergedChanges.length - 1]; - const thisChange = diff.changes[i]; - if (thisChange.modified.startLineNumber - lastChange.modified.endLineNumberExclusive <= LiveStrategy.HUNK_THRESHOLD) { - mergedChanges[mergedChanges.length - 1] = new DetailedLineRangeMapping( - lastChange.original.join(thisChange.original), - lastChange.modified.join(thisChange.modified), - (lastChange.innerChanges ?? []).concat(thisChange.innerChanges ?? []) - ); - } else { - mergedChanges.push(thisChange); + } else if (this._hunkDisplayData.size > 0) { + // everything accepted or rejected + let oneAccepted = false; + for (const hunkData of this._session.hunkData.getInfo()) { + if (hunkData.getState() === HunkState.Accepted) { + oneAccepted = true; + break; + } + } + if (oneAccepted) { + this._onDidAccept.fire(); + } else { + this._onDidDiscard.fire(); + } } - } - return mergedChanges.map(change => new Hunk(change.original, change.modified, change.innerChanges ?? [])); + return widgetData; + }; + + return renderHunks()?.position; } - protected _updateSummaryMessage(hunkCount: number) { let message: string; if (hunkCount === 0) { @@ -966,13 +839,6 @@ async function undoModelUntil(model: ITextModel, targetAltVersion: number): Prom } -function asRange(lineRange: LineRange, model: ITextModel): Range { - return lineRange.isEmpty - ? new Range(lineRange.startLineNumber, 1, lineRange.startLineNumber, model.getLineLength(lineRange.startLineNumber)) - : new Range(lineRange.startLineNumber, 1, lineRange.endLineNumberExclusive - 1, model.getLineLength(lineRange.endLineNumberExclusive - 1)); -} - - function changeDecorationsAndViewZones(editor: ICodeEditor, callback: (accessor: IModelDecorationsChangeAccessor, viewZoneAccessor: IViewZoneChangeAccessor) => void): void { editor.changeDecorations(decorationsAccessor => { editor.changeViewZones(viewZoneAccessor => { diff --git a/src/vs/workbench/contrib/inlineChat/browser/utils.ts b/src/vs/workbench/contrib/inlineChat/browser/utils.ts index a2a77ac6f2d..43364755deb 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/utils.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/utils.ts @@ -24,3 +24,9 @@ export function invertLineRange(range: LineRange, model: ITextModel): LineRange[ export function lineRangeAsRange(r: LineRange): Range { return new Range(r.startLineNumber, 1, r.endLineNumberExclusive - 1, 1); } + +export function asRange(lineRange: LineRange, model: ITextModel): Range { + return lineRange.isEmpty + ? new Range(lineRange.startLineNumber, 1, lineRange.startLineNumber, model.getLineLength(lineRange.startLineNumber)) + : new Range(lineRange.startLineNumber, 1, lineRange.endLineNumberExclusive - 1, model.getLineLength(lineRange.endLineNumberExclusive - 1)); +} diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 4219dd38c12..d2b9fe55c34 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -32,8 +32,10 @@ import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/brows import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { InlineChatController, InlineChatRunOptions, State } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSaving'; -import { IInlineChatSessionService, InlineChatSessionService, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IInlineChatSavingService } from '../../browser/inlineChatSavingService'; +import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionServiceImpl'; +import { IInlineChatSessionService } from '../../browser/inlineChatSessionService'; import { EditMode, IInlineChatService, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; @@ -106,7 +108,7 @@ suite('InteractiveChatController', function () { [IContextKeyService, contextKeyService], [IInlineChatService, inlineChatService], [IDiffProviderFactoryService, new SyncDescriptor(TestDiffProviderFactoryService)], - [IInlineChatSessionService, new SyncDescriptor(InlineChatSessionService)], + [IInlineChatSessionService, new SyncDescriptor(InlineChatSessionServiceImpl)], [IInlineChatSavingService, new class extends mock() { override markChanged(session: Session): void { // noop diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts index 1242e625ef7..f5f42a2bac8 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts @@ -3,14 +3,51 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; +import { Event } from 'vs/base/common/event'; import { mock } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { TestDiffProviderFactoryService } from 'vs/editor/browser/diff/testDiffProviderFactoryService'; +import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IDiffProviderFactoryService } from 'vs/editor/browser/widget/diffEditor/diffProviderFactoryService'; import { Range } from 'vs/editor/common/core/range'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { ReplyResponse } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { IInlineChatEditResponse, InlineChatResponseType, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ITextModel } from 'vs/editor/common/model'; +import { IModelService } from 'vs/editor/common/services/model'; +import { instantiateTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; +import { IEditorProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; +import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingService'; +import { HunkState, ReplyResponse, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; +import { InlineChatSessionServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; +import { EditMode, IInlineChatEditResponse, IInlineChatService, InlineChatResponseType, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { assertType } from 'vs/base/common/types'; +import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { Position } from 'vs/editor/common/core/position'; +import { DiffAlgorithmName, IEditorWorkerService, ILineChange } from 'vs/editor/common/services/editorWorker'; +import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; +import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; +import { LineRange } from 'vs/editor/common/core/lineRange'; +import { MovedText } from 'vs/editor/common/diff/linesDiffComputer'; +import { LineRangeMapping, DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; + suite('ReplyResponse', function () { @@ -48,3 +85,278 @@ suite('ReplyResponse', function () { } }); }); + +class TestWorkerService extends mock() { + + private readonly _worker = new EditorSimpleWorker(null!, null); + + constructor(@IModelService private readonly _modelService: IModelService) { + super(); + } + + override async computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise { + + const originalModel = this._modelService.getModel(original); + const modifiedModel = this._modelService.getModel(modified); + + assertType(originalModel); + assertType(modifiedModel); + + this._worker.acceptNewModel({ + url: originalModel.uri.toString(), + versionId: originalModel.getVersionId(), + lines: originalModel.getLinesContent(), + EOL: originalModel.getEOL(), + }); + + this._worker.acceptNewModel({ + url: modifiedModel.uri.toString(), + versionId: modifiedModel.getVersionId(), + lines: modifiedModel.getLinesContent(), + EOL: modifiedModel.getEOL(), + }); + + const result = await this._worker.computeDiff(originalModel.uri.toString(), modifiedModel.uri.toString(), options, algorithm); + if (!result) { + return result; + } + // Convert from space efficient JSON data to rich objects. + const diff: IDocumentDiff = { + identical: result.identical, + quitEarly: result.quitEarly, + changes: toLineRangeMappings(result.changes), + moves: result.moves.map(m => new MovedText( + new LineRangeMapping(new LineRange(m[0], m[1]), new LineRange(m[2], m[3])), + toLineRangeMappings(m[4]) + )) + }; + return diff; + + function toLineRangeMappings(changes: readonly ILineChange[]): readonly DetailedLineRangeMapping[] { + return changes.map( + (c) => new DetailedLineRangeMapping( + new LineRange(c[0], c[1]), + new LineRange(c[2], c[3]), + c[4]?.map( + (c) => new RangeMapping( + new Range(c[0], c[1], c[2], c[3]), + new Range(c[4], c[5], c[6], c[7]) + ) + ) + ) + ); + } + } +} + +suite('InlineChatSession', function () { + + const store = new DisposableStore(); + let editor: IActiveCodeEditor; + let model: ITextModel; + let instaService: TestInstantiationService; + let inlineChatService: InlineChatServiceImpl; + + let inlineChatSessionService: IInlineChatSessionService; + + setup(function () { + const contextKeyService = new MockContextKeyService(); + inlineChatService = new InlineChatServiceImpl(contextKeyService); + + const serviceCollection = new ServiceCollection( + [IEditorWorkerService, new SyncDescriptor(TestWorkerService)], + [IInlineChatService, inlineChatService], + [IContextKeyService, contextKeyService], + [IDiffProviderFactoryService, new SyncDescriptor(TestDiffProviderFactoryService)], + [IInlineChatSessionService, new SyncDescriptor(InlineChatSessionServiceImpl)], + [IInlineChatSavingService, new class extends mock() { + override markChanged(session: Session): void { + // noop + } + }], + [IEditorProgressService, new class extends mock() { + override show(total: unknown, delay?: unknown): IProgressRunner { + return { + total() { }, + worked(value) { }, + done() { }, + }; + } + }], + [IChatAccessibilityService, new class extends mock() { + override acceptResponse(response: IChatResponseViewModel | undefined, requestId: number): void { } + override acceptRequest(): number { return -1; } + }], + [IAccessibleViewService, new class extends mock() { + override getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string | null { + return null; + } + }], + [IConfigurationService, new TestConfigurationService()], + [IViewDescriptorService, new class extends mock() { + override onDidChangeLocation = Event.None; + }] + ); + + store.add(inlineChatService.addProvider({ + debugName: 'Unit Test', + label: 'Unit Test', + prepareInlineChatSession() { + return { + id: Math.random() + }; + }, + provideResponse(session, request) { + return { + type: InlineChatResponseType.EditorEdit, + id: Math.random(), + edits: [{ + range: new Range(1, 1, 1, 1), + text: request.prompt + }] + }; + } + })); + + instaService = store.add(workbenchInstantiationService(undefined, store).createChild(serviceCollection)); + inlineChatSessionService = store.add(instaService.get(IInlineChatSessionService)); + + model = store.add(instaService.get(IModelService).createModel('one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\neleven', null)); + editor = store.add(instantiateTestCodeEditor(instaService, model)); + }); + + teardown(function () { + store.clear(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('Create, release', async function () { + + const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(session); + inlineChatSessionService.releaseSession(session); + }); + + test('HunkData, info', async function () { + + const decorationCountThen = model.getAllDecorations().length; + + const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(session); + assert.ok(session.textModelN === model); + + editor.executeEdits('test', [EditOperation.insert(new Position(1, 1), 'AI_EDIT\n')]); + + await session.hunkData.recompute(); + assert.strictEqual(session.hunkData.size, 1); + const [hunk] = session.hunkData.getInfo(); + assertType(hunk); + + assert.ok(!session.textModel0.equalsTextBuffer(session.textModelN.getTextBuffer())); + assert.strictEqual(hunk.getState(), HunkState.Pending); + assert.ok(hunk.getRangesN()[0].equalsRange({ startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 7 })); + + editor.executeEdits('test', [EditOperation.insert(new Position(1, 3), 'foobar')]); + assert.ok(hunk.getRangesN()[0].equalsRange({ startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 13 })); + + inlineChatSessionService.releaseSession(session); + + assert.strictEqual(model.getAllDecorations().length, decorationCountThen); // no leaked decorations! + }); + + test('HunkData, accept', async function () { + + const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(session); + + editor.executeEdits('test', [EditOperation.insert(new Position(1, 1), 'AI_EDIT\n'), EditOperation.insert(new Position(10, 1), 'AI_EDIT\n')]); + + await session.hunkData.recompute(); + assert.strictEqual(session.hunkData.size, 2); + assert.ok(!session.textModel0.equalsTextBuffer(session.textModelN.getTextBuffer())); + + for (const hunk of session.hunkData.getInfo()) { + assertType(hunk); + assert.strictEqual(hunk.getState(), HunkState.Pending); + hunk.acceptChanges(); + assert.strictEqual(hunk.getState(), HunkState.Accepted); + } + + assert.strictEqual(session.textModel0.getValue(), session.textModelN.getValue()); + inlineChatSessionService.releaseSession(session); + }); + + test('HunkData, reject', async function () { + + const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(session); + + editor.executeEdits('test', [EditOperation.insert(new Position(1, 1), 'AI_EDIT\n'), EditOperation.insert(new Position(10, 1), 'AI_EDIT\n')]); + + await session.hunkData.recompute(); + assert.strictEqual(session.hunkData.size, 2); + assert.ok(!session.textModel0.equalsTextBuffer(session.textModelN.getTextBuffer())); + + for (const hunk of session.hunkData.getInfo()) { + assertType(hunk); + assert.strictEqual(hunk.getState(), HunkState.Pending); + hunk.discardChanges(); + assert.strictEqual(hunk.getState(), HunkState.Rejected); + } + + assert.strictEqual(session.textModel0.getValue(), session.textModelN.getValue()); + inlineChatSessionService.releaseSession(session); + }); + + test('HunkData, N rounds', async function () { + + model.setValue('one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\neleven\ntwelwe\nthirteen\nfourteen\nfifteen\nsixteen\nseventeen\neighteen\nnineteen\n'); + + const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(session); + + assert.ok(session.textModel0.equalsTextBuffer(session.textModelN.getTextBuffer())); + + assert.strictEqual(session.hunkData.size, 0); + + // ROUND #1 + editor.executeEdits('test', [ + EditOperation.insert(new Position(1, 1), 'AI1'), + EditOperation.insert(new Position(4, 1), 'AI2'), + EditOperation.insert(new Position(19, 1), 'AI3') + ]); + + await session.hunkData.recompute(); + assert.strictEqual(session.hunkData.size, 2); // AI1, AI2 are merged into one hunk, AI3 is a separate hunk + + let [first, second] = session.hunkData.getInfo(); + + assert.ok(model.getValueInRange(first.getRangesN()[0]).includes('AI1')); + assert.ok(model.getValueInRange(first.getRangesN()[0]).includes('AI2')); + assert.ok(model.getValueInRange(second.getRangesN()[0]).includes('AI3')); + + assert.ok(!session.textModel0.getValueInRange(first.getRangesN()[0]).includes('AI1')); + assert.ok(!session.textModel0.getValueInRange(first.getRangesN()[0]).includes('AI2')); + assert.ok(!session.textModel0.getValueInRange(second.getRangesN()[0]).includes('AI3')); + + first.acceptChanges(); + assert.ok(session.textModel0.getValueInRange(first.getRangesN()[0]).includes('AI1')); + assert.ok(session.textModel0.getValueInRange(first.getRangesN()[0]).includes('AI2')); + assert.ok(!session.textModel0.getValueInRange(second.getRangesN()[0]).includes('AI3')); + + + // ROUND #2 + editor.executeEdits('test', [ + EditOperation.insert(new Position(7, 1), 'AI4'), + ]); + await session.hunkData.recompute(); + assert.strictEqual(session.hunkData.size, 2); + + [first, second] = session.hunkData.getInfo(); + assert.ok(model.getValueInRange(first.getRangesN()[0]).includes('AI4')); // the new hunk (in line-order) + assert.ok(model.getValueInRange(second.getRangesN()[0]).includes('AI3')); // the previous hunk remains + + inlineChatSessionService.releaseSession(session); + }); +}); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts index 974f005d284..8bbbe4c7753 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts @@ -12,7 +12,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { EmptyTextEditorHintContribution, IEmptyTextEditorHintOptions } from 'vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint'; -import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; import { IInlineChatService } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts index fa7eb4cfc9a..cfc2b2fc2d3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts @@ -29,7 +29,8 @@ import { AsyncProgress } from 'vs/platform/progress/common/progress'; import { SaveReason } from 'vs/workbench/common/editor'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { EmptyResponse, ErrorResponse, IInlineChatSessionService, ReplyResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { EmptyResponse, ErrorResponse, ReplyResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; import { ProgressingEditsOptions, asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_VISIBLE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; From 62cb62c07bd4646fa1fe9929e82fba32e81976a7 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 16 Jan 2024 19:21:50 +0530 Subject: [PATCH 0339/1897] fix #161906 (#202582) --- .../contrib/extensions/browser/extensionsViews.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index db5a0672ed9..cd8a1b647cd 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -1004,7 +1004,7 @@ export class ExtensionsListView extends ViewPane { this.updateSize(); } - private updateSize() { + protected updateSize() { if (this.options.flexibleHeight) { this.maximumBodySize = this.list?.model.length ? Number.POSITIVE_INFINITY : 0; this.storageService.store(`${this.id}.size`, this.list?.model.length || 0, StorageScope.PROFILE, StorageTarget.MACHINE); @@ -1228,10 +1228,12 @@ export class OutdatedExtensionsView extends ExtensionsListView { if (ExtensionsListView.isSearchExtensionUpdatesQuery(query)) { query = query.replace('@updates', '@outdated'); } + return super.show(query.trim()); + } - const model = await super.show(query.trim()); - this.setExpanded(model.length > 0); - return model; + protected override updateSize() { + super.updateSize(); + this.setExpanded(this.count() > 0); } } From 17130bbeab809557fc9ef57d0ffdc712e06491e9 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Tue, 16 Jan 2024 15:14:52 +0100 Subject: [PATCH 0340/1897] Refactored title bar style configuration and updated related usage across multiple files --- .../platform/menubar/electron-main/menubar.ts | 6 +-- src/vs/platform/window/common/window.ts | 44 +++++++++++++++---- .../windows/electron-main/windowImpl.ts | 4 +- .../platform/windows/electron-main/windows.ts | 4 +- src/vs/workbench/browser/layout.ts | 17 +++---- .../parts/editor/auxiliaryEditorPart.ts | 4 +- .../browser/parts/titlebar/menubarControl.ts | 4 +- .../browser/parts/titlebar/titlebarPart.ts | 24 +++++----- .../browser/relauncher.contribution.ts | 12 +++-- .../windowIgnoreMenuShortcutsManager.ts | 2 +- .../electron-sandbox/desktop.contribution.ts | 4 +- .../parts/titlebar/titlebarPart.ts | 10 ++--- src/vs/workbench/electron-sandbox/window.ts | 6 +-- .../electron-sandbox/contextmenuService.ts | 4 +- 14 files changed, 87 insertions(+), 58 deletions(-) diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index d8bafd6263e..6c9e3fcffda 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -22,7 +22,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IStateService } from 'vs/platform/state/node/state'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUpdateService, StateType } from 'vs/platform/update/common/update'; -import { getTitleBarStyle, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, IWindowOpenable } from 'vs/platform/window/common/window'; +import { INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, IWindowOpenable, showNativeTitlebar } from 'vs/platform/window/common/window'; import { IWindowsCountChangedEvent, IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; @@ -85,7 +85,7 @@ export class Menubar { this.menubarMenus = Object.create(null); this.keybindings = Object.create(null); - if (isMacintosh || getTitleBarStyle(this.configurationService) === 'native') { + if (isMacintosh || showNativeTitlebar(configurationService)) { this.restoreCachedMenubarData(); } @@ -469,7 +469,7 @@ export class Menubar { private shouldDrawMenu(menuId: string): boolean { // We need to draw an empty menu to override the electron default - if (!isMacintosh && getTitleBarStyle(this.configurationService) === 'custom') { + if (!isMacintosh && !showNativeTitlebar(this.configurationService)) { return false; } diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index 25eb202ca4e..a4514efd126 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -130,7 +130,7 @@ export function getMenuBarVisibility(configurationService: IConfigurationService const titleBarStyle = getTitleBarStyle(configurationService); const menuBarVisibility = configurationService.getValue('window.menuBarVisibility'); - if (menuBarVisibility === 'default' || (titleBarStyle === 'native' && menuBarVisibility === 'compact') || (isMacintosh && isNative)) { + if (menuBarVisibility === 'default' || (titleBarStyle !== TitlebarStyle.CUSTOM && menuBarVisibility === 'compact') || (isMacintosh && isNative)) { return 'classic'; } else { return menuBarVisibility; @@ -148,7 +148,7 @@ export interface IWindowSettings { readonly restoreWindows: 'preserve' | 'all' | 'folders' | 'one' | 'none'; readonly restoreFullscreen: boolean; readonly zoomLevel: number; - readonly titleBarStyle: 'native' | 'custom'; + readonly titleBarStyle: TitlebarStyle; readonly autoDetectHighContrast: boolean; readonly autoDetectColorScheme: boolean; readonly menuBarVisibility: MenuBarVisibility; @@ -165,30 +165,56 @@ export interface IDensitySettings { readonly editorTabHeight: 'default' | 'compact'; } -export function getTitleBarStyle(configurationService: IConfigurationService): 'native' | 'custom' { +export const enum TitlebarStyle { + NATIVE = 'native', + CUSTOM = 'custom', + NATIVE_AND_CUSTOM = 'native-and-custom' +} + +export function showCustomTitlebar(configurationServiceOrTitleStyle: IConfigurationService | TitlebarStyle): boolean { + let titleBarStyle: TitlebarStyle; + if (typeof configurationServiceOrTitleStyle === 'string') { + titleBarStyle = configurationServiceOrTitleStyle; + } else { + titleBarStyle = getTitleBarStyle(configurationServiceOrTitleStyle); + } + return titleBarStyle !== TitlebarStyle.NATIVE; +} + +export function showNativeTitlebar(configurationServiceOrTitleStyle: IConfigurationService | TitlebarStyle): boolean { + let titleBarStyle: TitlebarStyle; + if (typeof configurationServiceOrTitleStyle === 'string') { + titleBarStyle = configurationServiceOrTitleStyle; + } else { + titleBarStyle = getTitleBarStyle(configurationServiceOrTitleStyle); + } + return titleBarStyle !== TitlebarStyle.CUSTOM; +} + +export function getTitleBarStyle(configurationService: IConfigurationService): TitlebarStyle { if (isWeb) { - return 'custom'; + return TitlebarStyle.CUSTOM; } const configuration = configurationService.getValue('window'); if (configuration) { const useNativeTabs = isMacintosh && configuration.nativeTabs === true; if (useNativeTabs) { - return 'native'; // native tabs on sierra do not work with custom title style + return TitlebarStyle.NATIVE; // native tabs on sierra do not work with custom title style } const useSimpleFullScreen = isMacintosh && configuration.nativeFullScreen === false; if (useSimpleFullScreen) { - return 'native'; // simple fullscreen does not work well with custom title style (https://github.com/microsoft/vscode/issues/63291) + return TitlebarStyle.NATIVE; // simple fullscreen does not work well with custom title style (https://github.com/microsoft/vscode/issues/63291) } const style = configuration.titleBarStyle; - if (style === 'native' || style === 'custom') { + if (style === TitlebarStyle.NATIVE || style === TitlebarStyle.CUSTOM || style === TitlebarStyle.NATIVE_AND_CUSTOM) { return style; } } - return isLinux ? 'native' : 'custom'; // default to custom on all macOS and Windows + return isLinux ? TitlebarStyle.NATIVE : TitlebarStyle.CUSTOM; // default to custom on all macOS and Windows } export function useWindowControlsOverlay(configurationService: IConfigurationService): boolean { @@ -196,7 +222,7 @@ export function useWindowControlsOverlay(configurationService: IConfigurationSer return false; // only supported on a desktop Windows instance } - if (getTitleBarStyle(configurationService) === 'native') { + if (showNativeTitlebar(configurationService)) { return false; // only supported when title bar is custom } diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 4f2bb8182de..43ce7c4f4ac 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -32,7 +32,7 @@ import { IApplicationStorageMainService, IStorageMainService } from 'vs/platform import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ThemeIcon } from 'vs/base/common/themables'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; -import { getMenuBarVisibility, getTitleBarStyle, IFolderToOpen, INativeWindowConfiguration, IWindowSettings, IWorkspaceToOpen, MenuBarVisibility, useNativeFullScreen, useWindowControlsOverlay } from 'vs/platform/window/common/window'; +import { getMenuBarVisibility, IFolderToOpen, INativeWindowConfiguration, IWindowSettings, IWorkspaceToOpen, MenuBarVisibility, showNativeTitlebar, useNativeFullScreen, useWindowControlsOverlay } from 'vs/platform/window/common/window'; import { defaultBrowserWindowOptions, IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; @@ -131,7 +131,7 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { this._register(Event.fromNodeEventEmitter(this._win, 'leave-full-screen')(() => this._onDidLeaveFullScreen.fire())); // Sheet Offsets - const useCustomTitleStyle = getTitleBarStyle(this.configurationService) === 'custom'; + const useCustomTitleStyle = !showNativeTitlebar(this.configurationService); if (isMacintosh && useCustomTitleStyle) { win.setSheetOffset(isBigSurOrNewer(release()) ? 28 : 22); // offset dialogs by the height of the custom title bar if we have any } diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index a658f3fa5ba..e667ba6f39d 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ICodeWindow, IWindowState } from 'vs/platform/window/electron-main/window'; -import { IOpenEmptyWindowOptions, IWindowOpenable, IWindowSettings, WindowMinimumSize, getTitleBarStyle, useNativeFullScreen, useWindowControlsOverlay, zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; +import { IOpenEmptyWindowOptions, IWindowOpenable, IWindowSettings, WindowMinimumSize, showNativeTitlebar, useNativeFullScreen, useWindowControlsOverlay, zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { IProductService } from 'vs/platform/product/common/productService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -173,7 +173,7 @@ export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowSt options.tabbingIdentifier = productService.nameShort; // this opts in to sierra tabs } - const useCustomTitleStyle = getTitleBarStyle(configurationService) === 'custom'; + const useCustomTitleStyle = !showNativeTitlebar(configurationService); if (useCustomTitleStyle) { options.titleBarStyle = 'hidden'; if (!isMacintosh) { diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index bef47d88deb..a36068ea2d8 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -19,7 +19,7 @@ import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/co import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { StartupKind, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { getTitleBarStyle, getMenuBarVisibility, IPath } from 'vs/platform/window/common/window'; +import { getMenuBarVisibility, IPath, showNativeTitlebar, showCustomTitlebar } from 'vs/platform/window/common/window'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -366,7 +366,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._register(addDisposableListener(this.mainContainer, EventType.SCROLL, () => this.mainContainer.scrollTop = 0)); // Menubar visibility changes - if ((isWindows || isLinux || isWeb) && getTitleBarStyle(this.configurationService) === 'custom') { + if ((isWindows || isLinux || isWeb) && !showNativeTitlebar(this.configurationService)) { this._register(this.titleService.onMenubarVisibilityChange(visible => this.onMenubarToggled(visible))); } @@ -453,7 +453,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Changing fullscreen state of the main window has an impact // on custom title bar visibility, so we need to update - if (getTitleBarStyle(this.configurationService) === 'custom') { + if (showCustomTitlebar(this.configurationService)) { // Propagate to grid this.workbenchGrid.setViewVisible(this.titleBarPartView, this.shouldShowTitleBar()); @@ -535,7 +535,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if ( isWeb || isWindows || // not working well with zooming and window control overlays - getTitleBarStyle(this.configurationService) !== 'custom' + showNativeTitlebar(this.configurationService) ) { return; } @@ -1220,15 +1220,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private shouldShowTitleBar(): boolean { // Using the native title bar, don't ever show the custom one - if (getTitleBarStyle(this.configurationService) === 'native') { + if (!showCustomTitlebar(this.configurationService)) { return false; } - // with the command center enabled, we should always show - if (this.configurationService.getValue(LayoutSettings.COMMAND_CENTER)) { - return true; - } - // with the activity bar on top, we should always show if (this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP) { return true; @@ -2068,7 +2063,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi let newVisibilityValue: string; if (currentVisibilityValue === 'visible' || currentVisibilityValue === 'classic') { - newVisibilityValue = getTitleBarStyle(this.configurationService) === 'native' ? 'toggle' : 'compact'; + newVisibilityValue = showNativeTitlebar(this.configurationService) ? 'toggle' : 'compact'; } else { newVisibilityValue = 'classic'; } diff --git a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts index d50e62f6095..3b94637bd57 100644 --- a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts @@ -14,7 +14,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { getTitleBarStyle } from 'vs/platform/window/common/window'; +import { showCustomTitlebar } from 'vs/platform/window/common/window'; import { IEditorGroupView, IEditorPartsView } from 'vs/workbench/browser/parts/editor/editor'; import { EditorPart, IEditorPartUIState } from 'vs/workbench/browser/parts/editor/editorPart'; import { IAuxiliaryTitlebarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; @@ -107,7 +107,7 @@ export class AuxiliaryEditorPart { // Titlebar let titlebarPart: IAuxiliaryTitlebarPart | undefined = undefined; let titlebarPartVisible = false; - const useCustomTitle = isNative && getTitleBarStyle(this.configurationService) === 'custom'; // custom title in aux windows only enabled in native + const useCustomTitle = isNative && showCustomTitlebar(this.configurationService); // custom title in aux windows only enabled in native if (useCustomTitle) { titlebarPart = disposables.add(this.titleService.createAuxiliaryTitlebarPart(auxiliaryWindow.container, editorPart)); titlebarPartVisible = true; diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 80a172c0d15..43481ae2ad4 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/menubarControl'; import { localize, localize2 } from 'vs/nls'; import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action2, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; -import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable, getMenuBarVisibility } from 'vs/platform/window/common/window'; +import { MenuBarVisibility, IWindowOpenable, getMenuBarVisibility, showNativeTitlebar } from 'vs/platform/window/common/window'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAction, Action, SubmenuAction, Separator, IActionRunner, ActionRunner, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, toAction } from 'vs/base/common/actions'; import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom'; @@ -351,7 +351,7 @@ export abstract class MenubarControl extends Disposable { } const hasBeenNotified = this.storageService.getBoolean('menubar/accessibleMenubarNotified', StorageScope.APPLICATION, false); - const usingCustomMenubar = getTitleBarStyle(this.configurationService) === 'custom'; + const usingCustomMenubar = !showNativeTitlebar(this.configurationService); if (hasBeenNotified || usingCustomMenubar || !this.accessibilityService.isScreenReaderOptimized()) { return; diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 3ca421396a6..1d5fb04ab8e 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -8,7 +8,7 @@ import { localize, localize2 } from 'vs/nls'; import { MultiWindowParts, Part } from 'vs/workbench/browser/part'; import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; import { getZoomFactor, isWCOEnabled } from 'vs/base/browser/browser'; -import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility } from 'vs/platform/window/common/window'; +import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, TitlebarStyle, showCustomTitlebar, showNativeTitlebar } from 'vs/platform/window/common/window'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; @@ -235,7 +235,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { private readonly hoverDelegate = new TitlebarPartHoverDelegate(this.hoverService, this.configurationService); private readonly titleDisposables = this._register(new DisposableStore()); - private titleBarStyle: 'native' | 'custom' = getTitleBarStyle(this.configurationService); + private titleBarStyle: TitlebarStyle = getTitleBarStyle(this.configurationService); private isInactive: boolean = false; private readonly isAuxiliary: boolean; @@ -299,7 +299,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { oldPartOptions.editorActionsLocation !== newPartOptions.editorActionsLocation || oldPartOptions.showTabs !== newPartOptions.showTabs ) { - if (this.titleBarStyle !== 'native' && this.actionToolBar) { + if (showCustomTitlebar(this.titleBarStyle) && this.actionToolBar) { this.createActionToolBar(); this.createActionToolBarMenus({ editorActions: true }); this._onDidChange.fire(undefined); @@ -310,7 +310,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { protected onConfigurationChanged(event: IConfigurationChangeEvent): void { // Custom menu bar (disabled if auxiliary) - if (!this.isAuxiliary && this.titleBarStyle !== 'native' && (!isMacintosh || isWeb)) { + if (!this.isAuxiliary && !showNativeTitlebar(this.titleBarStyle) && (!isMacintosh || isWeb)) { if (event.affectsConfiguration('window.menuBarVisibility')) { if (this.currentMenubarVisibility === 'compact') { this.uninstallMenubar(); @@ -321,7 +321,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } // Actions - if (this.titleBarStyle !== 'native' && this.actionToolBar) { + if (showCustomTitlebar(this.titleBarStyle) && this.actionToolBar) { const affectsLayoutControl = event.affectsConfiguration('workbench.layoutControl.enabled'); const affectsActivityControl = event.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION); @@ -388,7 +388,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.rightContent = append(this.rootContainer, $('.titlebar-right')); // App Icon (Native Windows/Linux and Web) - if (!isMacintosh && !isWeb) { + if (!isMacintosh && !isWeb && !showNativeTitlebar(this.titleBarStyle)) { this.appIcon = prepend(this.leftContent, $('a.window-appicon')); // Web-only home indicator and menu (not for auxiliary windows) @@ -412,7 +412,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { // Menubar: install a custom menu bar depending on configuration if ( !this.isAuxiliary && - this.titleBarStyle !== 'native' && + !showNativeTitlebar(this.titleBarStyle) && (!isMacintosh || isWeb) && this.currentMenubarVisibility !== 'compact' ) { @@ -424,7 +424,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.createTitle(); // Create Toolbar Actions - if (this.titleBarStyle !== 'native') { + if (showCustomTitlebar(this.titleBarStyle)) { this.actionToolBarElement = append(this.rightContent, $('div.action-toolbar-container')); this.createActionToolBar(); this.createActionToolBarMenus(); @@ -442,8 +442,10 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } } - this.primaryWindowControls = append(primaryControlLocation === 'left' ? this.leftContent : this.rightContent, $('div.window-controls-container.primary')); - append(primaryControlLocation === 'left' ? this.rightContent : this.leftContent, $('div.window-controls-container.secondary')); + if (!showNativeTitlebar(this.titleBarStyle)) { + this.primaryWindowControls = append(primaryControlLocation === 'left' ? this.leftContent : this.rightContent, $('div.window-controls-container.primary')); + append(primaryControlLocation === 'left' ? this.rightContent : this.leftContent, $('div.window-controls-container.secondary')); + } // Context menu over title bar: depending on the OS and the location of the click this will either be // the overall context menu for the entire title bar or a specific title context menu. @@ -736,7 +738,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { private updateLayout(dimension: Dimension): void { this.lastLayoutDimensions = dimension; - if (getTitleBarStyle(this.configurationService) === 'custom') { + if (showCustomTitlebar(this.titleBarStyle)) { const zoomFactor = getZoomFactor(getWindow(this.element)); this.element.style.setProperty('--zoom-factor', zoomFactor.toString()); diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index c41739df745..a12266a279b 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -6,7 +6,7 @@ import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContributionsRegistry, IWorkbenchContribution, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWindowsConfiguration, IWindowSettings } from 'vs/platform/window/common/window'; +import { IWindowsConfiguration, IWindowSettings, TitlebarStyle } from 'vs/platform/window/common/window'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ConfigurationTarget, IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { localize } from 'vs/nls'; @@ -46,7 +46,8 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo 'security.restrictUNCAccess' ]; - private readonly titleBarStyle = new ChangeObserver<'native' | 'custom'>('string'); + private readonly titleBarStyle = new ChangeObserver('string'); + private previousTitleBarStyle: TitlebarStyle | undefined = undefined; private readonly nativeTabs = new ChangeObserver('boolean'); private readonly nativeFullScreen = new ChangeObserver('boolean'); private readonly clickThroughInactive = new ChangeObserver('boolean'); @@ -85,7 +86,12 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo if (isNative) { // Titlebar style - processChanged((config.window.titleBarStyle === 'native' || config.window.titleBarStyle === 'custom') && this.titleBarStyle.handleChange(config.window?.titleBarStyle)); + processChanged( + (config.window.titleBarStyle === TitlebarStyle.NATIVE || config.window.titleBarStyle === TitlebarStyle.CUSTOM || config.window.titleBarStyle === TitlebarStyle.NATIVE_AND_CUSTOM) && + this.titleBarStyle.handleChange(config.window.titleBarStyle) && + (this.previousTitleBarStyle === TitlebarStyle.CUSTOM || config.window.titleBarStyle === TitlebarStyle.CUSTOM) // only if we come from custom or go to custom + ); + this.previousTitleBarStyle = config.window.titleBarStyle; // macOS: Native tabs processChanged(isMacintosh && this.nativeTabs.handleChange(config.window?.nativeTabs)); diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.ts b/src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.ts index c3265219d37..7ae47c23f5d 100644 --- a/src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.ts +++ b/src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.ts @@ -21,7 +21,7 @@ export class WindowIgnoreMenuShortcutsManager { mainProcessService: IMainProcessService, private readonly _nativeHostService: INativeHostService ) { - this._isUsingNativeTitleBars = configurationService.getValue('window.titleBarStyle') === 'native'; + this._isUsingNativeTitleBars = configurationService.getValue('window.titleBarStyle') !== 'custom'; this._webviewMainService = ProxyChannel.toService(mainProcessService.getChannel('webview')); } diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index f5f5772fccd..33be5c4b2d9 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -227,9 +227,9 @@ import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from 'scope': ConfigurationScope.APPLICATION, 'markdownDescription': localize('window.doubleClickIconToClose', "If enabled, this setting will close the window when the application icon in the title bar is double-clicked. The window will not be able to be dragged by the icon. This setting is effective only if `#window.titleBarStyle#` is set to `custom`.") }, - 'window.titleBarStyle': { + 'window.titleBarStyle': { // Todo 'type': 'string', - 'enum': ['native', 'custom'], + 'enum': ['native', 'custom', 'native-and-custom'], 'default': isLinux ? 'native' : 'custom', 'scope': ConfigurationScope.APPLICATION, 'description': localize('titleBarStyle', "Adjust the appearance of the window title bar to be native by the OS or custom. On Linux and Windows, this setting also affects the application and context menu appearances. Changes require a full restart to apply.") diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index 7b95b0c342c..8288e6999fe 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -18,7 +18,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { INativeHostService } from 'vs/platform/native/common/native'; -import { getTitleBarStyle, useWindowControlsOverlay } from 'vs/platform/window/common/window'; +import { showNativeTitlebar, useWindowControlsOverlay } from 'vs/platform/window/common/window'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; @@ -145,7 +145,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { const targetWindowId = getWindowId(targetWindow); // Native menu controller - if (isMacintosh || getTitleBarStyle(this.configurationService) === 'native') { + if (isMacintosh || showNativeTitlebar(this.configurationService)) { this._register(this.instantiationService.createInstance(NativeMenubarControl)); } @@ -159,7 +159,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { } // Window Controls (Native Windows/Linux) - if (!isMacintosh && getTitleBarStyle(this.configurationService) !== 'native' && !isWCOEnabled() && this.primaryWindowControls) { + if (!isMacintosh && !showNativeTitlebar(this.configurationService) && !isWCOEnabled() && this.primaryWindowControls) { // Minimize const minimizeIcon = append(this.primaryWindowControls, $('div.window-icon.window-minimize' + ThemeIcon.asCSSSelector(Codicon.chromeMinimize))); @@ -195,7 +195,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { // Window System Context Menu // See https://github.com/electron/electron/issues/24893 - if (isWindows && getTitleBarStyle(this.configurationService) === 'custom') { + if (isWindows && !showNativeTitlebar(this.configurationService)) { this._register(this.nativeHostService.onDidTriggerWindowSystemContextMenu(({ windowId, x, y }) => { if (targetWindowId !== windowId) { return; @@ -253,7 +253,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { if ( useWindowControlsOverlay(this.configurationService) || - (isMacintosh && isNative && getTitleBarStyle(this.configurationService) === 'custom') + (isMacintosh && isNative && !showNativeTitlebar(this.configurationService)) ) { // When the user goes into full screen mode, the height of the title bar becomes 0. diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 6fe76f60879..1a9cbce221a 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -14,7 +14,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { EditorResourceAccessor, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors, IResourceDiffEditorInput, IUntypedEditorInput, IEditorPane, isResourceEditorInput, IResourceMergeEditorInput } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { WindowMinimumSize, IOpenFileRequest, getTitleBarStyle, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest } from 'vs/platform/window/common/window'; +import { WindowMinimumSize, IOpenFileRequest, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest, showNativeTitlebar } from 'vs/platform/window/common/window'; import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ApplyZoomTarget, applyZoom } from 'vs/platform/window/electron-sandbox/window'; @@ -377,7 +377,7 @@ export class NativeWindow extends BaseWindow { } // Maximize/Restore on doubleclick (for macOS custom title) - if (isMacintosh && getTitleBarStyle(this.configurationService) === 'custom') { + if (isMacintosh && !showNativeTitlebar(this.configurationService)) { this._register(Event.runAndSubscribe(this.layoutService.onDidAddContainer, ({ container, disposables }) => { const targetWindow = getWindow(container); const targetWindowId = targetWindow.vscodeWindowId; @@ -611,7 +611,7 @@ export class NativeWindow extends BaseWindow { this.customTitleContextMenuDisposable.clear(); // Provide new menu if a file is opened and we are on a custom title - if (!filePath || getTitleBarStyle(this.configurationService) !== 'custom') { + if (!filePath || !showNativeTitlebar(this.configurationService)) { return; } diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts index 381c8be72ec..b0ca3f2175e 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts @@ -15,7 +15,7 @@ import { IContextMenuDelegate, IContextMenuEvent } from 'vs/base/browser/context import { createSingleCallFunction } from 'vs/base/common/functional'; import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu'; import { popup } from 'vs/base/parts/contextmenu/electron-sandbox/contextmenu'; -import { getTitleBarStyle } from 'vs/platform/window/common/window'; +import { showNativeTitlebar } from 'vs/platform/window/common/window'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextMenuMenuDelegate, ContextMenuService as HTMLContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; @@ -48,7 +48,7 @@ export class ContextMenuService implements IContextMenuService { ) { // Custom context menu: Linux/Windows if custom title is enabled - if (!isMacintosh && getTitleBarStyle(configurationService) === 'custom') { + if (!isMacintosh && !showNativeTitlebar(configurationService)) { this.impl = new HTMLContextMenuService(telemetryService, notificationService, contextViewService, keybindingService, menuService, contextKeyService); } From 697936c5f8d87350a59d454108ccb1ab29b42136 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 16 Jan 2024 16:17:40 +0100 Subject: [PATCH 0341/1897] Git - update getBranchBase so that it returns an upstream branch (#202586) --- extensions/git/src/repository.ts | 41 ++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 94ce234bb01..8ac566827a6 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1475,33 +1475,35 @@ export class Repository implements Disposable { async getBranchBase(ref: string): Promise { const branch = await this.getBranch(ref); - const branchMergeBaseConfigKey = `branch.${branch.name}.vscode-merge-base`; + const branchUpstream = await this.getUpstreamBranch(branch); - // Upstream - if (branch.upstream) { - return await this.getBranch(`refs/remotes/${branch.upstream.remote}/${branch.upstream.name}`); + if (branchUpstream) { + return branchUpstream; } // Git config + const mergeBaseConfigKey = `branch.${branch.name}.vscode-merge-base`; + try { - const mergeBase = await this.getConfig(branchMergeBaseConfigKey); - if (mergeBase !== '') { - const mergeBaseBranch = await this.getBranch(mergeBase); - return mergeBaseBranch; + const mergeBase = await this.getConfig(mergeBaseConfigKey); + const branchFromConfig = mergeBase !== '' ? await this.getBranch(mergeBase) : undefined; + if (branchFromConfig) { + return branchFromConfig; } } catch (err) { } // Reflog const branchFromReflog = await this.getBranchBaseFromReflog(ref); - if (branchFromReflog) { - await this.setConfig(branchMergeBaseConfigKey, branchFromReflog.name!); - return branchFromReflog; + const branchFromReflogUpstream = branchFromReflog ? await this.getUpstreamBranch(branchFromReflog) : undefined; + if (branchFromReflogUpstream) { + await this.setConfig(mergeBaseConfigKey, `${branchFromReflogUpstream.remote}/${branchFromReflogUpstream.name}`); + return branchFromReflogUpstream; } // Default branch const defaultBranch = await this.getDefaultBranch(); if (defaultBranch) { - await this.setConfig(branchMergeBaseConfigKey, defaultBranch.name!); + await this.setConfig(mergeBaseConfigKey, `${defaultBranch.remote}/${defaultBranch.name}`); return defaultBranch; } @@ -1552,6 +1554,21 @@ export class Repository implements Disposable { return undefined; } + private async getUpstreamBranch(branch: Branch): Promise { + if (!branch.upstream) { + return undefined; + } + + try { + const upstreamBranch = await this.getBranch(`refs/remotes/${branch.upstream.remote}/${branch.upstream.name}`); + return upstreamBranch; + } + catch (err) { + this.logger.warn(`Failed to get branch details for 'refs/remotes/${branch.upstream.remote}/${branch.upstream.name}': ${err.message}.`); + return undefined; + } + } + async getRefs(query: RefQuery = {}, cancellationToken?: CancellationToken): Promise { const config = workspace.getConfiguration('git'); let defaultSort = config.get<'alphabetically' | 'committerdate'>('branchSortOrder'); From c9127a68f16dccbe4a6f2c3ab461ddfa20e79d83 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 16 Jan 2024 20:58:22 +0530 Subject: [PATCH 0342/1897] rename (#202588) --- src/vs/platform/download/common/downloadIpc.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/download/common/downloadIpc.ts b/src/vs/platform/download/common/downloadIpc.ts index e83417eed87..3e88e8f668e 100644 --- a/src/vs/platform/download/common/downloadIpc.ts +++ b/src/vs/platform/download/common/downloadIpc.ts @@ -32,10 +32,10 @@ export class DownloadServiceChannelClient implements IDownloadService { constructor(private channel: IChannel, private getUriTransformer: () => IURITransformer | null) { } async download(from: URI, to: URI): Promise { - const uriTransfomer = this.getUriTransformer(); - if (uriTransfomer) { - from = uriTransfomer.transformOutgoingURI(from); - to = uriTransfomer.transformOutgoingURI(to); + const uriTransformer = this.getUriTransformer(); + if (uriTransformer) { + from = uriTransformer.transformOutgoingURI(from); + to = uriTransformer.transformOutgoingURI(to); } await this.channel.call('download', [from, to]); } From 9204cd8b4b35afbd46040385854da2e803c5b652 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 16 Jan 2024 08:05:18 -0800 Subject: [PATCH 0343/1897] testing: fix error hovering with no coverage open (#202591) Fixes #202564 --- .../testing/browser/codeCoverageDecorations.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts index 4c7a6caa211..c981e552a97 100644 --- a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts @@ -105,12 +105,13 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri })); this._register(editor.onMouseMove(e => { - if (e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS) { + const model = editor.getModel(); + if (e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS && model) { this.hoverLineNumber(editor.getModel()!, e.target.position.lineNumber); } else if (this.lineHoverWidget.hasValue && this.lineHoverWidget.value.getDomNode().contains(e.target.element)) { // don't dismiss the hover - } else if (CodeCoverageDecorations.showInline.get() && e.target.type === MouseTargetType.CONTENT_TEXT) { - this.hoverInlineDecoration(editor.getModel()!, e.target.position); + } else if (CodeCoverageDecorations.showInline.get() && e.target.type === MouseTargetType.CONTENT_TEXT && model) { + this.hoverInlineDecoration(model, e.target.position); } else { this.hoveredStore.clear(); } @@ -168,7 +169,7 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri } private hoverLineNumber(model: ITextModel, lineNumber: number) { - if (lineNumber === this.hoveredSubject) { + if (lineNumber === this.hoveredSubject || !this.details) { return; } @@ -208,7 +209,7 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri }); } - this.lineHoverWidget.value.startShowingAt(lineNumber, this.details!, wasPreviouslyHovering); + this.lineHoverWidget.value.startShowingAt(lineNumber, this.details, wasPreviouslyHovering); this.hoveredStore.add(this.editor.onMouseLeave(() => { this.hoveredStore.clear(); From f64beed46107217e0faaa2f3fd67211b64941d6c Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Tue, 16 Jan 2024 08:54:24 -0800 Subject: [PATCH 0344/1897] don't layout notebook when not visible (#202392) * don't layout notebook when not visible * only call this when hiding --- .../contrib/notebook/browser/notebookEditor.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 694914c9946..f95c61f73f5 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -150,6 +150,13 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { return this._widget.value; } + override setVisible(visible: boolean, group?: IEditorGroup | undefined): void { + super.setVisible(visible, group); + if (!visible) { + this._widget.value?.onWillHide(); + } + } + protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { super.setEditorVisible(visible, group); if (group) { @@ -552,7 +559,9 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { return; } - this._widget.value.layout(dimension, this._rootElement, position); + if (this.isVisible()) { + this._widget.value.layout(dimension, this._rootElement, position); + } } //#endregion From ca3a8724f4a3e6b53cac6426c4ea6f0c9239e207 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 16 Jan 2024 18:05:25 +0100 Subject: [PATCH 0345/1897] add `IKeybindingService#enableKeybindingHoldMode` --- .../common/abstractKeybindingService.ts | 26 ++++++++++++++++--- .../platform/keybinding/common/keybinding.ts | 9 ++++++- .../test/common/mockKeybindingService.ts | 4 +++ .../keybinding/browser/keybindingService.ts | 4 +++ 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/keybinding/common/abstractKeybindingService.ts b/src/vs/platform/keybinding/common/abstractKeybindingService.ts index cfc5e7e9e11..0df56233ba1 100644 --- a/src/vs/platform/keybinding/common/abstractKeybindingService.ts +++ b/src/vs/platform/keybinding/common/abstractKeybindingService.ts @@ -53,6 +53,8 @@ export abstract class AbstractKeybindingService extends Disposable implements IK private _ignoreSingleModifiers: KeybindingModifierSet; private _currentSingleModifier: SingleModifierChord | null; private _currentSingleModifierClearTimeout: TimeoutTimer; + private _currentlyDispatchingCommandId: string | null; + protected _isInKeybindingHoldMode: boolean; protected _logging: boolean; @@ -75,6 +77,8 @@ export abstract class AbstractKeybindingService extends Disposable implements IK this._ignoreSingleModifiers = KeybindingModifierSet.EMPTY; this._currentSingleModifier = null; this._currentSingleModifierClearTimeout = new TimeoutTimer(); + this._currentlyDispatchingCommandId = null; + this._isInKeybindingHoldMode = false; this._logging = false; } @@ -362,10 +366,15 @@ export abstract class AbstractKeybindingService extends Disposable implements IK } this._log(`+ Invoking command ${resolveResult.commandId}.`); - if (typeof resolveResult.commandArgs === 'undefined') { - this._commandService.executeCommand(resolveResult.commandId).then(undefined, err => this._notificationService.warn(err)); - } else { - this._commandService.executeCommand(resolveResult.commandId, resolveResult.commandArgs).then(undefined, err => this._notificationService.warn(err)); + this._currentlyDispatchingCommandId = resolveResult.commandId; + try { + if (typeof resolveResult.commandArgs === 'undefined') { + this._commandService.executeCommand(resolveResult.commandId).then(undefined, err => this._notificationService.warn(err)); + } else { + this._commandService.executeCommand(resolveResult.commandId, resolveResult.commandArgs).then(undefined, err => this._notificationService.warn(err)); + } + } finally { + this._currentlyDispatchingCommandId = null; } if (!HIGH_FREQ_COMMANDS.test(resolveResult.commandId)) { @@ -378,6 +387,15 @@ export abstract class AbstractKeybindingService extends Disposable implements IK } } + enableKeybindingHoldMode(commandId: string): boolean { + if (this._currentlyDispatchingCommandId !== commandId) { + return false; + } + this._isInKeybindingHoldMode = true; + this._log(`+ Enabled hold-mode for ${commandId}.`); + return true; + } + mightProducePrintableCharacter(event: IKeyboardEvent): boolean { if (event.ctrlKey || event.metaKey) { // ignore ctrl/cmd-combination but not shift/alt-combinatios diff --git a/src/vs/platform/keybinding/common/keybinding.ts b/src/vs/platform/keybinding/common/keybinding.ts index 9a1e812a22e..789d044a64f 100644 --- a/src/vs/platform/keybinding/common/keybinding.ts +++ b/src/vs/platform/keybinding/common/keybinding.ts @@ -65,6 +65,14 @@ export interface IKeybindingService { */ softDispatch(keyboardEvent: IKeyboardEvent, target: IContextKeyServiceTarget): ResolutionResult; + /** + * Enable hold mode for this command. This is only possible if the command is current being dispatched, meaning + * we are after its keydown and before is keyup event. + * + * @returns true is hold mode was enabled + */ + enableKeybindingHoldMode(commandId: string): boolean; + dispatchByUserSettingsLabel(userSettingsLabel: string, target: IContextKeyServiceTarget): void; /** @@ -100,4 +108,3 @@ export interface IKeybindingService { _dumpDebugInfo(): string; _dumpDebugInfoJSON(): string; } - diff --git a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts index c655b9f9b7a..a9d352e24ea 100644 --- a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts +++ b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts @@ -147,6 +147,10 @@ export class MockKeybindingService implements IKeybindingService { return false; } + public enableKeybindingHoldMode(commandId: string): boolean { + return false; + } + public mightProducePrintableCharacter(e: IKeyboardEvent): boolean { return false; } diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index ad218733589..3222c7dfab6 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -269,6 +269,9 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { // for standard keybindings disposables.add(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { + if (this._isInKeybindingHoldMode) { + return; + } this.isComposingGlobalContextKey.set(e.isComposing); const keyEvent = new StandardKeyboardEvent(e); this._log(`/ Received keydown event - ${printKeyboardEvent(e)}`); @@ -282,6 +285,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { // for single modifier chord keybindings (e.g. shift shift) disposables.add(dom.addDisposableListener(window, dom.EventType.KEY_UP, (e: KeyboardEvent) => { + this._isInKeybindingHoldMode = false; this.isComposingGlobalContextKey.set(e.isComposing); const keyEvent = new StandardKeyboardEvent(e); const shouldPreventDefault = this._singleModifierDispatch(keyEvent, keyEvent.target); From 351e07bbb6bbc9bcf00baf009610fb31f726b946 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 16 Jan 2024 18:10:26 +0100 Subject: [PATCH 0346/1897] Error: Cannot register two commands with the same id: workbench.action.focusCommentsPanel (#202596) Fixes #202563 --- .../comments/browser/comments.contribution.ts | 58 +++++++++++++++- .../contrib/comments/browser/commentsView.ts | 66 ++----------------- 2 files changed, 63 insertions(+), 61 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts index d6e05c6e872..eb7c912fb6d 100644 --- a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts +++ b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts @@ -14,7 +14,7 @@ import * as strings from 'vs/base/common/strings'; import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -27,6 +27,62 @@ import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/co import { COMMENTS_VIEW_ID } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; import { CommentThreadState } from 'vs/editor/common/languages'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { CONTEXT_KEY_HAS_COMMENTS, CONTEXT_KEY_SOME_COMMENTS_EXPANDED, CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsView'; +import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; +import { Codicon } from 'vs/base/common/codicons'; + +CommandsRegistry.registerCommand({ + id: 'workbench.action.focusCommentsPanel', + handler: async (accessor) => { + const viewsService = accessor.get(IViewsService); + viewsService.openView(COMMENTS_VIEW_ID, true); + } +}); + +registerAction2(class Collapse extends ViewAction { + constructor() { + super({ + viewId: COMMENTS_VIEW_ID, + id: 'comments.collapse', + title: nls.localize('collapseAll', "Collapse All"), + f1: false, + icon: Codicon.collapseAll, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyExpr.and(ContextKeyExpr.and(ContextKeyExpr.equals('view', COMMENTS_VIEW_ID), CONTEXT_KEY_HAS_COMMENTS), CONTEXT_KEY_SOME_COMMENTS_EXPANDED), + order: 100 + } + }); + } + runInView(_accessor: ServicesAccessor, view: CommentsPanel) { + view.collapseAll(); + } +}); + +registerAction2(class Expand extends ViewAction { + constructor() { + super({ + viewId: COMMENTS_VIEW_ID, + id: 'comments.expand', + title: nls.localize('expandAll', "Expand All"), + f1: false, + icon: Codicon.expandAll, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyExpr.and(ContextKeyExpr.and(ContextKeyExpr.equals('view', COMMENTS_VIEW_ID), CONTEXT_KEY_HAS_COMMENTS), ContextKeyExpr.not(CONTEXT_KEY_SOME_COMMENTS_EXPANDED.key)), + order: 100 + } + }); + } + runInView(_accessor: ServicesAccessor, view: CommentsPanel) { + view.expandAll(); + } +}); Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ id: 'comments', diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index 598f2501d3f..ef8d51a1313 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -8,27 +8,23 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { basename } from 'vs/base/common/resources'; import { isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { CommentNode, ResourceWithCommentThreads, ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; import { IWorkspaceCommentThreadsEvent, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { textLinkForeground, textLinkActiveForeground, focusBorder, textPreformatForeground } from 'vs/platform/theme/common/colorRegistry'; import { ResourceLabels } from 'vs/workbench/browser/labels'; -import { CommentsList, COMMENTS_VIEW_ID, COMMENTS_VIEW_TITLE, Filter } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; -import { IViewPaneOptions, ViewAction, FilterViewPane } from 'vs/workbench/browser/parts/views/viewPane'; +import { CommentsList, COMMENTS_VIEW_TITLE, Filter } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; +import { IViewPaneOptions, FilterViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewDescriptorService } from 'vs/workbench/common/views'; -import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; -import { Codicon } from 'vs/base/common/codicons'; import { IEditor } from 'vs/editor/common/editorCommon'; import { TextModel } from 'vs/editor/common/model/textModel'; import { CommentsViewFilterFocusContextKey, ICommentsView } from 'vs/workbench/contrib/comments/browser/comments'; @@ -44,8 +40,8 @@ import { Range } from 'vs/editor/common/core/range'; import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands'; import { CommentsModel, ICommentsModel } from 'vs/workbench/contrib/comments/browser/commentsModel'; -const CONTEXT_KEY_HAS_COMMENTS = new RawContextKey('commentsView.hasComments', false); -const CONTEXT_KEY_SOME_COMMENTS_EXPANDED = new RawContextKey('commentsView.someCommentsExpanded', false); +export const CONTEXT_KEY_HAS_COMMENTS = new RawContextKey('commentsView.hasComments', false); +export const CONTEXT_KEY_SOME_COMMENTS_EXPANDED = new RawContextKey('commentsView.someCommentsExpanded', false); const VIEW_STORAGE_ID = 'commentsViewState'; function createResourceCommentsIterator(model: ICommentsModel): Iterable> { @@ -505,53 +501,3 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { return false; } } - -CommandsRegistry.registerCommand({ - id: 'workbench.action.focusCommentsPanel', - handler: async (accessor) => { - const viewsService = accessor.get(IViewsService); - viewsService.openView(COMMENTS_VIEW_ID, true); - } -}); - -registerAction2(class Collapse extends ViewAction { - constructor() { - super({ - viewId: COMMENTS_VIEW_ID, - id: 'comments.collapse', - title: nls.localize('collapseAll', "Collapse All"), - f1: false, - icon: Codicon.collapseAll, - menu: { - id: MenuId.ViewTitle, - group: 'navigation', - when: ContextKeyExpr.and(ContextKeyExpr.and(ContextKeyExpr.equals('view', COMMENTS_VIEW_ID), CONTEXT_KEY_HAS_COMMENTS), CONTEXT_KEY_SOME_COMMENTS_EXPANDED), - order: 100 - } - }); - } - runInView(_accessor: ServicesAccessor, view: CommentsPanel) { - view.collapseAll(); - } -}); - -registerAction2(class Expand extends ViewAction { - constructor() { - super({ - viewId: COMMENTS_VIEW_ID, - id: 'comments.expand', - title: nls.localize('expandAll', "Expand All"), - f1: false, - icon: Codicon.expandAll, - menu: { - id: MenuId.ViewTitle, - group: 'navigation', - when: ContextKeyExpr.and(ContextKeyExpr.and(ContextKeyExpr.equals('view', COMMENTS_VIEW_ID), CONTEXT_KEY_HAS_COMMENTS), ContextKeyExpr.not(CONTEXT_KEY_SOME_COMMENTS_EXPANDED.key)), - order: 100 - } - }); - } - runInView(_accessor: ServicesAccessor, view: CommentsPanel) { - view.expandAll(); - } -}); From 481d7bb72d759a02a81df86bee8c657e6c9b3474 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 16 Jan 2024 18:16:31 +0100 Subject: [PATCH 0347/1897] Use hold mode in start inline chat action to enable speech --- .../browser/inlineChat.contribution.ts | 62 ------------- .../inlineChat/browser/inlineChatActions.ts | 36 +------- .../contrib/inlineChat/common/inlineChat.ts | 6 ++ .../inlineChat.contribution.ts | 59 +++++++++++++ .../electron-sandbox/inlineChatActions.ts | 88 +++++++++++++++++++ src/vs/workbench/workbench.common.main.ts | 1 - 6 files changed, 156 insertions(+), 96 deletions(-) delete mode 100644 src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts create mode 100644 src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts deleted file mode 100644 index d68c3dc3dc8..00000000000 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { registerAction2 } from 'vs/platform/actions/common/actions'; -import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import * as InlineChatActions from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; -import { IInlineChatService, INLINE_CHAT_ID, INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl'; -import { InlineChatSessionServiceImpl } from './inlineChatSessionServiceImpl'; -import { IInlineChatSessionService } from './inlineChatSessionService'; -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 { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { InlineChatAccessibleViewContribution } from './inlineChatAccessibleView'; -import { InlineChatSavingServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl'; -import { IInlineChatSavingService } from './inlineChatSavingService'; - -registerSingleton(IInlineChatService, InlineChatServiceImpl, InstantiationType.Delayed); -registerSingleton(IInlineChatSessionService, InlineChatSessionServiceImpl, InstantiationType.Delayed); -registerSingleton(IInlineChatSavingService, InlineChatSavingServiceImpl, InstantiationType.Delayed); - -registerEditorContribution(INLINE_CHAT_ID, InlineChatController, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors -registerEditorContribution(INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID, InlineChatActions.InlineAccessibilityHelpContribution, EditorContributionInstantiation.Eventually); - -registerAction2(InlineChatActions.StartSessionAction); -registerAction2(InlineChatActions.CloseAction); -registerAction2(InlineChatActions.ConfigureInlineChatAction); -// registerAction2(InlineChatActions.UnstashSessionAction); -registerAction2(InlineChatActions.MakeRequestAction); -registerAction2(InlineChatActions.StopRequestAction); -registerAction2(InlineChatActions.ReRunRequestAction); -registerAction2(InlineChatActions.DiscardHunkAction); -registerAction2(InlineChatActions.DiscardAction); -registerAction2(InlineChatActions.DiscardToClipboardAction); -registerAction2(InlineChatActions.DiscardUndoToNewFileAction); -registerAction2(InlineChatActions.CancelSessionAction); - -registerAction2(InlineChatActions.ArrowOutUpAction); -registerAction2(InlineChatActions.ArrowOutDownAction); -registerAction2(InlineChatActions.FocusInlineChat); -registerAction2(InlineChatActions.PreviousFromHistory); -registerAction2(InlineChatActions.NextFromHistory); -registerAction2(InlineChatActions.ViewInChatAction); -registerAction2(InlineChatActions.ExpandMessageAction); -registerAction2(InlineChatActions.ContractMessageAction); - -registerAction2(InlineChatActions.ToggleDiffForChange); -registerAction2(InlineChatActions.FeebackHelpfulCommand); -registerAction2(InlineChatActions.FeebackUnhelpfulCommand); -registerAction2(InlineChatActions.ReportIssueForBugCommand); -registerAction2(InlineChatActions.AcceptChanges); - -registerAction2(InlineChatActions.CopyRecordings); - -const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(InlineChatNotebookContribution, LifecyclePhase.Restored); -workbenchContributionsRegistry.registerWorkbenchContribution(InlineChatAccessibleViewContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 0a753c92802..af47d8f2b76 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { Codicon } from 'vs/base/common/codicons'; -import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_EDIT_MODE, EditMode, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, InlineChatResponseFeedbackKind, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, MENU_INLINE_CHAT_WIDGET } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { localize, localize2 } from 'vs/nls'; import { IAction2Options, MenuRegistry } from 'vs/platform/actions/common/actions'; @@ -33,37 +33,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); export const LOCALIZED_START_INLINE_CHAT_STRING = localize('run', 'Start Inline Chat'); -const START_INLINE_CHAT = registerIcon('start-inline-chat', Codicon.sparkle, localize('startInlineChat', 'Icon which spawns the inline chat from the editor toolbar.')); - -export class StartSessionAction extends EditorAction2 { - - constructor() { - super({ - id: 'inlineChat.start', - title: { value: LOCALIZED_START_INLINE_CHAT_STRING, original: 'Start Inline Chat' }, - category: AbstractInlineChatAction.category, - f1: true, - precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_PROVIDER, EditorContextKeys.writable), - keybinding: { - when: EditorContextKeys.focus, - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyCode.KeyI, - secondary: [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyI)], - }, - icon: START_INLINE_CHAT - }); - } - - - override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { - let options: InlineChatRunOptions | undefined; - const arg = _args[0]; - if (arg && InlineChatRunOptions.isInteractiveEditorOptions(arg)) { - options = arg; - } - InlineChatController.get(editor)?.run({ ...options }); - } -} +export const START_INLINE_CHAT = registerIcon('start-inline-chat', Codicon.sparkle, localize('startInlineChat', 'Icon which spawns the inline chat from the editor toolbar.')); export class UnstashSessionAction extends EditorAction2 { constructor() { diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index cef2b7500ca..1ac18061206 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -202,6 +202,7 @@ export const enum InlineChatConfigKeys { Mode = 'inlineChat.mode', FinishOnType = 'inlineChat.finishOnType', AcceptedOrDiscardBeforeSave = 'inlineChat.acceptedOrDiscardBeforeSave', + HoldToSpeech = 'inlineChat.holdToSpeech', } Registry.as(Extensions.Configuration).registerConfiguration({ @@ -227,6 +228,11 @@ Registry.as(Extensions.Configuration).registerConfigurat description: localize('acceptedOrDiscardBeforeSave', "Whether pending inline chat sessions prevent saving."), default: true, type: 'boolean' + }, + [InlineChatConfigKeys.HoldToSpeech]: { + description: localize('holdToSpeech', "Whether holding the inline chat keybinding will automatically enable speech recognition."), + default: true, + type: 'boolean' } } }); diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts index d60b9420644..33942d1fea4 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts @@ -6,8 +6,67 @@ import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { CancelAction, InlineChatQuickVoice, StartAction, StopAction } from 'vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice'; +import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import * as InlineChatActions from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; +import * as StartSessionAction from './inlineChatActions'; +import { IInlineChatService, INLINE_CHAT_ID, INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl'; +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 { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { InlineChatSavingServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl'; +import { InlineChatAccessibleViewContribution } from 'vs/workbench/contrib/inlineChat/browser/inlineChatAccessibleView'; +import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingService'; +import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; +import { InlineChatSessionServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; +// --- electron-browser registerEditorContribution(InlineChatQuickVoice.ID, InlineChatQuickVoice, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors registerAction2(StartAction); registerAction2(StopAction); registerAction2(CancelAction); + +// --- browser + +registerSingleton(IInlineChatService, InlineChatServiceImpl, InstantiationType.Delayed); +registerSingleton(IInlineChatSessionService, InlineChatSessionServiceImpl, InstantiationType.Delayed); +registerSingleton(IInlineChatSavingService, InlineChatSavingServiceImpl, InstantiationType.Delayed); + +registerEditorContribution(INLINE_CHAT_ID, InlineChatController, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors +registerEditorContribution(INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID, InlineChatActions.InlineAccessibilityHelpContribution, EditorContributionInstantiation.Eventually); + +registerAction2(StartSessionAction.StartSessionAction); +registerAction2(InlineChatActions.CloseAction); +registerAction2(InlineChatActions.ConfigureInlineChatAction); +// registerAction2(InlineChatActions.UnstashSessionAction); +registerAction2(InlineChatActions.MakeRequestAction); +registerAction2(InlineChatActions.StopRequestAction); +registerAction2(InlineChatActions.ReRunRequestAction); +registerAction2(InlineChatActions.DiscardHunkAction); +registerAction2(InlineChatActions.DiscardAction); +registerAction2(InlineChatActions.DiscardToClipboardAction); +registerAction2(InlineChatActions.DiscardUndoToNewFileAction); +registerAction2(InlineChatActions.CancelSessionAction); + +registerAction2(InlineChatActions.ArrowOutUpAction); +registerAction2(InlineChatActions.ArrowOutDownAction); +registerAction2(InlineChatActions.FocusInlineChat); +registerAction2(InlineChatActions.PreviousFromHistory); +registerAction2(InlineChatActions.NextFromHistory); +registerAction2(InlineChatActions.ViewInChatAction); +registerAction2(InlineChatActions.ExpandMessageAction); +registerAction2(InlineChatActions.ContractMessageAction); + +registerAction2(InlineChatActions.ToggleDiffForChange); +registerAction2(InlineChatActions.FeebackHelpfulCommand); +registerAction2(InlineChatActions.FeebackUnhelpfulCommand); +registerAction2(InlineChatActions.ReportIssueForBugCommand); +registerAction2(InlineChatActions.AcceptChanges); + +registerAction2(InlineChatActions.CopyRecordings); + +const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(InlineChatNotebookContribution, LifecyclePhase.Restored); +workbenchContributionsRegistry.registerWorkbenchContribution(InlineChatAccessibleViewContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts new file mode 100644 index 00000000000..c0f8c8f5efb --- /dev/null +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts @@ -0,0 +1,88 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as dom from 'vs/base/browser/dom'; +import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import { AbstractInlineChatAction } from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; +import { LOCALIZED_START_INLINE_CHAT_STRING, START_INLINE_CHAT } from '../browser/inlineChatActions'; +import { disposableTimeout } from 'vs/base/common/async'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { StartVoiceChatAction, StopListeningAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; +import { CTX_INLINE_CHAT_HAS_PROVIDER, InlineChatConfigKeys } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; + + +export class StartSessionAction extends EditorAction2 { + + constructor() { + super({ + id: 'inlineChat.start', + title: { value: LOCALIZED_START_INLINE_CHAT_STRING, original: 'Start Inline Chat' }, + category: AbstractInlineChatAction.category, + f1: true, + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_PROVIDER, EditorContextKeys.writable), + keybinding: { + when: EditorContextKeys.focus, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.KeyI, + secondary: [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyI)], + }, + icon: START_INLINE_CHAT + }); + } + + + override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { + + const configService = accessor.get(IConfigurationService); + const speechService = accessor.get(ISpeechService); + const keybindingService = accessor.get(IKeybindingService); + const commandService = accessor.get(ICommandService); + + if (configService.getValue(InlineChatConfigKeys.HoldToSpeech) // enabled + && speechService.hasSpeechProvider // possible + && keybindingService.enableKeybindingHoldMode(this.desc.id) // holding keys + ) { + + + let listening = false; + const handle = disposableTimeout(() => { + // start VOICE input + commandService.executeCommand(StartVoiceChatAction.ID); + listening = true; + }, 100); + + const listener = dom.addDisposableListener( + dom.getWindow(editor.getDomNode()), + dom.EventType.KEY_UP, + (_e: KeyboardEvent) => { + listener.dispose(); // Event.once + if (listening) { + commandService.executeCommand(StopListeningAction.ID).finally(() => { + InlineChatController.get(editor)?.acceptInput(); + }); + } else { + handle.dispose(); + } + } + ); + } + + let options: InlineChatRunOptions | undefined; + const arg = _args[0]; + if (arg && InlineChatRunOptions.isInteractiveEditorOptions(arg)) { + options = arg; + } + InlineChatController.get(editor)?.run({ ...options }); + } +} diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index bcad16fc802..ae453cf83d4 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -182,7 +182,6 @@ import 'vs/workbench/contrib/speech/common/speech.contribution'; // Chat import 'vs/workbench/contrib/chat/browser/chat.contribution'; -import 'vs/workbench/contrib/inlineChat/browser/inlineChat.contribution'; // Interactive import 'vs/workbench/contrib/interactive/browser/interactive.contribution'; From fee9ef203aa2ef10b4d8ab8fd019994ec422662f Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 16 Jan 2024 09:25:15 -0800 Subject: [PATCH 0348/1897] eng: run with coverage on decoration click for more selfhosting (#202592) --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index bdbb92f271b..99e495ab781 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -170,4 +170,5 @@ }, "css.format.spaceAroundSelectorSeparator": true, "inlineChat.mode": "live", + "testing.defaultGutterClickAction": "runWithCoverage", } From e1e6c5de268228e003033f855f58fa77248271aa Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 16 Jan 2024 09:40:03 -0800 Subject: [PATCH 0349/1897] Fix #202598. Handle browser detection correctly in notebook webview preload (#202599) --- .../notebook/browser/view/renderers/webviewPreloads.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 32082d471ab..b147a72091b 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -9,7 +9,6 @@ import type { IDisposable } from 'vs/base/common/lifecycle'; import type * as webviewMessages from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages'; import type { NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import type * as rendererApi from 'vscode-notebook-renderer'; -import * as browser from 'vs/base/browser/browser'; // !! IMPORTANT !! ---------------------------------------------------------------------------------- // import { RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -92,6 +91,8 @@ declare function __import(path: string): Promise; async function webviewPreloads(ctx: PreloadContext) { // eslint-disable-next-line no-restricted-globals const $window = window as typeof DOM.$window; + const userAgent = navigator.userAgent; + const isChrome = (userAgent.indexOf('Chrome') >= 0); const textEncoder = new TextEncoder(); const textDecoder = new TextDecoder(); @@ -484,9 +485,9 @@ async function webviewPreloads(ctx: PreloadContext) { deltaY: event.deltaY, deltaZ: event.deltaZ, // Refs https://github.com/microsoft/vscode/issues/146403#issuecomment-1854538928 - wheelDelta: event.wheelDelta && browser.isChrome ? (event.wheelDelta / $window.devicePixelRatio) : event.wheelDelta, - wheelDeltaX: event.wheelDeltaX && browser.isChrome ? (event.wheelDeltaX / $window.devicePixelRatio) : event.wheelDeltaX, - wheelDeltaY: event.wheelDeltaY && browser.isChrome ? (event.wheelDeltaY / $window.devicePixelRatio) : event.wheelDeltaY, + wheelDelta: event.wheelDelta && isChrome ? (event.wheelDelta / $window.devicePixelRatio) : event.wheelDelta, + wheelDeltaX: event.wheelDeltaX && isChrome ? (event.wheelDeltaX / $window.devicePixelRatio) : event.wheelDeltaX, + wheelDeltaY: event.wheelDeltaY && isChrome ? (event.wheelDeltaY / $window.devicePixelRatio) : event.wheelDeltaY, detail: event.detail, shiftKey: event.shiftKey, type: event.type From 7b3a15c679ad4b00d0a844ac1c9bfbba3a608c42 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 16 Jan 2024 10:45:36 -0800 Subject: [PATCH 0350/1897] Support rendering cell index in resource label (#202554) * Support rendering cell index in resource label * localize the title --- src/vs/workbench/browser/labels.ts | 20 +++- .../browser/services/notebookServiceImpl.ts | 15 ++- .../contrib/notebook/common/notebookCommon.ts | 37 +------- .../common/notebookDocumentService.ts | 91 +++++++++++++++++++ src/vs/workbench/workbench.common.main.ts | 1 + 5 files changed, 128 insertions(+), 36 deletions(-) create mode 100644 src/vs/workbench/services/notebook/common/notebookDocumentService.ts diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 74fc9277f80..0bb8699861a 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { dirname, isEqual, basenameOrAuthority } from 'vs/base/common/resources'; import { IconLabel, IIconLabelValueOptions, IIconLabelCreationOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; @@ -24,6 +25,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { normalizeDriveLetter } from 'vs/base/common/labels'; import { IRange } from 'vs/editor/common/core/range'; import { ThemeIcon } from 'vs/base/common/themables'; +import { INotebookDocumentService } from 'vs/workbench/services/notebook/common/notebookDocumentService'; export interface IResourceLabelProps { resource?: URI | { primary?: URI; secondary?: URI }; @@ -308,7 +310,8 @@ class ResourceLabelWidget extends IconLabel { @IDecorationsService private readonly decorationsService: IDecorationsService, @ILabelService private readonly labelService: ILabelService, @ITextFileService private readonly textFileService: ITextFileService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @INotebookDocumentService private readonly notebookDocumentService: INotebookDocumentService ) { super(container, options); } @@ -465,6 +468,21 @@ class ResourceLabelWidget extends IconLabel { } } + if (!options.forceLabel && !isSideBySideEditor && resource?.scheme === Schemas.vscodeNotebookCell) { + // Notebook cells are embeded in a notebook document + // As such we always ask the actual notebook document + // for its position in the document. + const notebookDocument = this.notebookDocumentService.getNotebook(resource); + const cellIndex = notebookDocument?.getCellIndex(resource); + if (notebookDocument && cellIndex !== undefined && typeof label.name === 'string') { + options.title = localize('notebookCellLabel', "{0} • Cell {1}", label.name, `${cellIndex + 1}`); + } + + if (typeof label.name === 'string' && notebookDocument && cellIndex !== undefined && typeof label.name === 'string') { + label.name = localize('notebookCellLabel', "{0} • Cell {1}", label.name, `${cellIndex + 1}`); + } + } + const hasResourceChanged = this.hasResourceChanged(label); const hasPathLabelChanged = hasResourceChanged || this.hasPathLabelChanged(label); const hasFileKindChanged = this.hasFileKindChanged(options); diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts index 1e207678d7d..7c0c31448d4 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts @@ -14,6 +14,7 @@ import { Lazy } from 'vs/base/common/lazy'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; +import { isEqual } from 'vs/base/common/resources'; import { isDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -43,6 +44,7 @@ import { IExtensionService, isProposedApiEnabled } from 'vs/workbench/services/e import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { INotebookDocument, INotebookDocumentService } from 'vs/workbench/services/notebook/common/notebookDocumentService'; export class NotebookProviderInfoStore extends Disposable { @@ -414,8 +416,9 @@ export class NotebookOutputRendererInfoStore { } } -class ModelData implements IDisposable { +class ModelData implements IDisposable, INotebookDocument { private readonly _modelEventListeners = new DisposableStore(); + get uri() { return this.model.uri; } constructor( readonly model: NotebookTextModel, @@ -424,6 +427,10 @@ class ModelData implements IDisposable { this._modelEventListeners.add(model.onWillDispose(() => onWillDispose(model))); } + getCellIndex(cellUri: URI): number | undefined { + return this.model.cells.findIndex(cell => isEqual(cell.uri, cellUri)); + } + dispose(): void { this._modelEventListeners.dispose(); } @@ -485,6 +492,7 @@ export class NotebookService extends Disposable implements INotebookService { @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, @IStorageService private readonly _storageService: IStorageService, + @INotebookDocumentService private readonly _notebookDocumentService: INotebookDocumentService ) { super(); @@ -752,7 +760,9 @@ export class NotebookService extends Disposable implements INotebookService { throw new Error(`notebook for ${uri} already exists`); } const notebookModel = this._instantiationService.createInstance(NotebookTextModel, viewType, uri, data.cells, data.metadata, transientOptions); - this._models.set(uri, new ModelData(notebookModel, this._onWillDisposeDocument.bind(this))); + const modelData = new ModelData(notebookModel, this._onWillDisposeDocument.bind(this)); + this._models.set(uri, modelData); + this._notebookDocumentService.addNotebookDocument(modelData); this._onWillAddNotebookDocument.fire(notebookModel); this._onDidAddNotebookDocument.fire(notebookModel); this._postDocumentOpenActivation(viewType); @@ -776,6 +786,7 @@ export class NotebookService extends Disposable implements INotebookService { if (modelData) { this._onWillRemoveNotebookDocument.fire(modelData.model); this._models.delete(model.uri); + this._notebookDocumentService.removeNotebookDocument(modelData); modelData.dispose(); this._onDidRemoveNotebookDocument.fire(modelData.model); } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index e076f6ebf7e..a4d44cdf61e 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { decodeBase64, encodeBase64, VSBuffer } from 'vs/base/common/buffer'; +import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IDiffResult } from 'vs/base/common/diff/diff'; import { Event } from 'vs/base/common/event'; @@ -31,6 +31,7 @@ import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { IWorkingCopyBackupMeta, IWorkingCopySaveEvent } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IFileReadLimits } from 'vs/platform/files/common/files'; +import { parse as parseUri, generate as generateUri } from 'vs/workbench/services/notebook/common/notebookDocumentService'; export const NOTEBOOK_EDITOR_ID = 'workbench.editor.notebook'; export const NOTEBOOK_DIFF_EDITOR_ID = 'workbench.editor.notebookTextDiffEditor'; @@ -546,43 +547,13 @@ export interface INotebookContributionData { export namespace CellUri { - export const scheme = Schemas.vscodeNotebookCell; - - - const _lengths = ['W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f']; - const _padRegexp = new RegExp(`^[${_lengths.join('')}]+`); - const _radix = 7; - export function generate(notebook: URI, handle: number): URI { - - const s = handle.toString(_radix); - const p = s.length < _lengths.length ? _lengths[s.length - 1] : 'z'; - - const fragment = `${p}${s}s${encodeBase64(VSBuffer.fromString(notebook.scheme), true, true)}`; - return notebook.with({ scheme, fragment }); + return generateUri(notebook, handle); } export function parse(cell: URI): { notebook: URI; handle: number } | undefined { - if (cell.scheme !== scheme) { - return undefined; - } - - const idx = cell.fragment.indexOf('s'); - if (idx < 0) { - return undefined; - } - - const handle = parseInt(cell.fragment.substring(0, idx).replace(_padRegexp, ''), _radix); - const _scheme = decodeBase64(cell.fragment.substring(idx + 1)).toString(); - - if (isNaN(handle)) { - return undefined; - } - return { - handle, - notebook: cell.with({ scheme: _scheme, fragment: null }) - }; + return parseUri(cell); } export function generateCellOutputUri(notebook: URI, outputId?: string) { diff --git a/src/vs/workbench/services/notebook/common/notebookDocumentService.ts b/src/vs/workbench/services/notebook/common/notebookDocumentService.ts new file mode 100644 index 00000000000..96af748ef50 --- /dev/null +++ b/src/vs/workbench/services/notebook/common/notebookDocumentService.ts @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer, decodeBase64, encodeBase64 } from 'vs/base/common/buffer'; +import { ResourceMap } from 'vs/base/common/map'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const INotebookDocumentService = createDecorator('notebookDocumentService'); + +export interface INotebookDocument { + readonly uri: URI; + getCellIndex(cellUri: URI): number | undefined; +} + +const _lengths = ['W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f']; +const _padRegexp = new RegExp(`^[${_lengths.join('')}]+`); +const _radix = 7; +export function parse(cell: URI): { notebook: URI; handle: number } | undefined { + if (cell.scheme !== Schemas.vscodeNotebookCell) { + return undefined; + } + + const idx = cell.fragment.indexOf('s'); + if (idx < 0) { + return undefined; + } + + const handle = parseInt(cell.fragment.substring(0, idx).replace(_padRegexp, ''), _radix); + const _scheme = decodeBase64(cell.fragment.substring(idx + 1)).toString(); + + if (isNaN(handle)) { + return undefined; + } + return { + handle, + notebook: cell.with({ scheme: _scheme, fragment: null }) + }; +} + +export function generate(notebook: URI, handle: number): URI { + + const s = handle.toString(_radix); + const p = s.length < _lengths.length ? _lengths[s.length - 1] : 'z'; + + const fragment = `${p}${s}s${encodeBase64(VSBuffer.fromString(notebook.scheme), true, true)}`; + return notebook.with({ scheme: Schemas.vscodeNotebookCell, fragment }); +} + +export interface INotebookDocumentService { + readonly _serviceBrand: undefined; + + getNotebook(uri: URI): INotebookDocument | undefined; + addNotebookDocument(document: INotebookDocument): void; + removeNotebookDocument(document: INotebookDocument): void; +} + +export class NotebookDocumentWorkbenchService implements INotebookDocumentService { + declare readonly _serviceBrand: undefined; + + private readonly _documents = new ResourceMap(); + + getNotebook(uri: URI): INotebookDocument | undefined { + if (uri.scheme === Schemas.vscodeNotebookCell) { + const cellUri = parse(uri); + if (cellUri) { + const document = this._documents.get(cellUri.notebook); + if (document) { + return document; + } + } + } + + return this._documents.get(uri); + } + + addNotebookDocument(document: INotebookDocument) { + this._documents.set(document.uri, document); + } + + removeNotebookDocument(document: INotebookDocument) { + this._documents.delete(document.uri); + } + +} + +registerSingleton(INotebookDocumentService, NotebookDocumentWorkbenchService, InstantiationType.Delayed); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index bcad16fc802..09cd28cf86a 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -77,6 +77,7 @@ import 'vs/workbench/services/textresourceProperties/common/textResourceProperti import 'vs/workbench/services/textfile/common/textEditorService'; import 'vs/workbench/services/language/common/languageService'; import 'vs/workbench/services/model/common/modelService'; +import 'vs/workbench/services/notebook/common/notebookDocumentService'; import 'vs/workbench/services/commands/common/commandService'; import 'vs/workbench/services/themes/browser/workbenchThemeService'; import 'vs/workbench/services/label/common/labelService'; From 70575102e789216a1e4f5c305d40a14650a1a2b1 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 16 Jan 2024 11:31:15 -0800 Subject: [PATCH 0351/1897] move to terminal land --- .../electron-sandbox/voice.contribution.ts | 3 -- .../terminal/browser/terminalActions.ts | 33 +++++++++++- .../terminal/browser/voiceTerminalActions.ts | 53 +------------------ .../contrib/terminal/common/terminal.ts | 2 + 4 files changed, 36 insertions(+), 55 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/voice.contribution.ts b/src/vs/workbench/contrib/chat/electron-sandbox/voice.contribution.ts index 89f8f793d48..9ba5430737e 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/voice.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/voice.contribution.ts @@ -5,7 +5,6 @@ import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; import { registerAction2 } from 'vs/platform/actions/common/actions'; -import { StartTerminalSpeechToTextAction, StopTerminalSpeechToTextAction } from 'vs/workbench/contrib/terminal/browser/voiceTerminalActions'; registerAction2(StartVoiceChatAction); @@ -21,5 +20,3 @@ registerAction2(StopListeningInChatEditorAction); registerAction2(StopListeningInQuickChatAction); registerAction2(StopListeningInInlineChatAction); -registerAction2(StartTerminalSpeechToTextAction); -registerAction2(StopTerminalSpeechToTextAction); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 3517feca516..5e12619c2f1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -20,7 +20,7 @@ import { Action2, registerAction2, IAction2Options, MenuId } from 'vs/platform/a import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ILabelService } from 'vs/platform/label/common/label'; import { IListService } from 'vs/platform/list/browser/listService'; @@ -64,6 +64,8 @@ import { AccessibleViewProviderId, accessibleViewCurrentProviderId, accessibleVi import { isKeyboardEvent, isMouseEvent, isPointerEvent } from 'vs/base/browser/dom'; import { editorGroupToColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { InstanceContext } from 'vs/workbench/contrib/terminal/browser/terminalContextMenu'; +import { TerminalVoiceSession } from 'vs/workbench/contrib/terminal/browser/voiceTerminalActions'; +import { HasSpeechProvider } from 'vs/workbench/contrib/speech/common/speechService'; export const switchTerminalActionViewItemSeparator = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'; export const switchTerminalShowTabsTitle = localize('showTerminalTabs', "Show Tabs"); @@ -1641,6 +1643,35 @@ export function registerTerminalActions() { } } }); + + + registerTerminalAction({ + id: TerminalCommandId.StartSpeechToText, + title: { + value: localize('workbench.action.startTerminalSpeechToText', "Start Terminal Speech To Text"), + original: 'Start Terminal Speech To Text' + }, + precondition: ContextKeyExpr.and(HasSpeechProvider, TerminalContextKeys.focus), + f1: true, + run: (c, accessor) => { + const instantiationService = accessor.get(IInstantiationService); + TerminalVoiceSession.getInstance(instantiationService).start(); + } + }); + + registerTerminalAction({ + id: TerminalCommandId.StopSpeechToText, + title: { + value: localize('workbench.action.stopTerminalSpeechToText', "Stop Terminal Speech To Text"), + original: 'Stop Terminal Speech To Text' + }, + precondition: ContextKeyExpr.and(HasSpeechProvider, TerminalContextKeys.focus), + f1: true, + run: (c, accessor) => { + const instantiationService = accessor.get(IInstantiationService); + TerminalVoiceSession.getInstance(instantiationService).stop(true); + } + }); } interface IRemoteTerminalPick extends IQuickPickItem { diff --git a/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts b/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts index e55f9c4f4cf..1ce2f030317 100644 --- a/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts @@ -6,66 +6,17 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { localize } from 'vs/nls'; -import { Action2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { AccessibilityVoiceSettingId, SpeechTimeoutDefault } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { HasSpeechProvider, ISpeechService, ISpeechToTextEvent, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { ISpeechService, ISpeechToTextEvent, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { isNumber } from 'vs/base/common/types'; import type { IDecoration } from '@xterm/xterm'; import { IXtermMarker } from 'vs/platform/terminal/common/capabilities/capabilities'; import { ThemeIcon } from 'vs/base/common/themables'; import { Codicon } from 'vs/base/common/codicons'; -export class StartTerminalSpeechToTextAction extends Action2 { - - static readonly ID = 'workbench.action.startTerminalSpeechToText'; - - constructor() { - super({ - id: 'workbench.action.startTerminalSpeechToText', - title: { - value: localize('workbench.action.startTerminalSpeechToText', "Start Terminal Speech To Text"), - original: 'Start Terminal Speech To Text' - }, - precondition: ContextKeyExpr.and(HasSpeechProvider, TerminalContextKeys.focus), - f1: true - }); - } - - async run(accessor: ServicesAccessor): Promise { - const instantiationService = accessor.get(IInstantiationService); - TerminalVoiceSession.getInstance(instantiationService).start(); - } -} - - -export class StopTerminalSpeechToTextAction extends Action2 { - - static readonly ID = 'workbench.action.stopTerminalSpeechToText'; - - constructor() { - super({ - id: 'workbench.action.stopTerminalSpeechToText', - title: { - value: localize('workbench.action.stopTerminalSpeechToText', "Stop Terminal Speech To Text"), - original: 'Stop Terminal Speech To Text' - }, - precondition: ContextKeyExpr.and(HasSpeechProvider, TerminalContextKeys.focus), - f1: true - }); - } - - async run(accessor: ServicesAccessor): Promise { - const instantiationService = accessor.get(IInstantiationService); - TerminalVoiceSession.getInstance(instantiationService).stop(true); - } -} - export class TerminalVoiceSession extends Disposable { private _input: string = ''; private _decoration: IDecoration | undefined; diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 1063933ba91..5ff4e943664 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -495,6 +495,8 @@ export const enum TerminalCommandId { FocusHover = 'workbench.action.terminal.focusHover', ShowEnvironmentContributions = 'workbench.action.terminal.showEnvironmentContributions', ToggleStickyScroll = 'workbench.action.terminal.toggleStickyScroll', + StartSpeechToText = 'workbench.action.startTerminalSpeechToText', + StopSpeechToText = 'workbench.action.stopTerminalSpeechToText', // Developer commands From f58d7ff83df6c72084f83893b3ea4ec37fb225ec Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 16 Jan 2024 11:34:18 -0800 Subject: [PATCH 0352/1897] cli: fix GLIBC version requirement hardcoded in error message (#202605) Fixes #202082 --- cli/src/util/errors.rs | 2 +- cli/src/util/prereqs.rs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cli/src/util/errors.rs b/cli/src/util/errors.rs index 38d9b36f54b..03280d12f0a 100644 --- a/cli/src/util/errors.rs +++ b/cli/src/util/errors.rs @@ -471,7 +471,7 @@ pub enum CodeError { #[error("platform not currently supported: {0}")] UnsupportedPlatform(String), - #[error("This machine not meet {name}'s prerequisites, expected either...: {bullets}")] + #[error("This machine does not meet {name}'s prerequisites, expected either...: {bullets}")] PrerequisitesFailed { name: &'static str, bullets: String }, #[error("failed to spawn process: {0:?}")] ProcessSpawnFailed(std::io::Error), diff --git a/cli/src/util/prereqs.rs b/cli/src/util/prereqs.rs index dfb68c48afe..b0a9014cbcf 100644 --- a/cli/src/util/prereqs.rs +++ b/cli/src/util/prereqs.rs @@ -141,8 +141,8 @@ async fn check_glibc_version() -> Result<(), String> { Ok(()) } else { Err(format!( - "find GLIBC >= 2.17 (but found {} instead) for GNU environments", - v + "find GLIBC >= {} (but found {} instead) for GNU environments", + *MIN_LDD_VERSION, v )) }; } @@ -201,7 +201,8 @@ fn check_for_sufficient_glibcxx_versions(contents: Vec) -> Result<(), String if !all_versions.iter().any(|v| &*MIN_CXX_VERSION >= v) { return Err(format!( - "find GLIBCXX >= 3.4.18 (but found {} instead) for GNU environments", + "find GLIBCXX >= {} (but found {} instead) for GNU environments", + *MIN_CXX_VERSION, all_versions .iter() .map(String::from) From 7efe326c490dcf85ea2ac8e41969647b3db4b6c3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 16 Jan 2024 11:34:21 -0800 Subject: [PATCH 0353/1897] better precondition, enable commands to appear in command pallette --- .../contrib/terminal/browser/terminalActions.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 5e12619c2f1..0f6d1344b7b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -1644,30 +1644,29 @@ export function registerTerminalActions() { } }); - - registerTerminalAction({ + registerActiveInstanceAction({ id: TerminalCommandId.StartSpeechToText, title: { value: localize('workbench.action.startTerminalSpeechToText', "Start Terminal Speech To Text"), original: 'Start Terminal Speech To Text' }, - precondition: ContextKeyExpr.and(HasSpeechProvider, TerminalContextKeys.focus), + precondition: ContextKeyExpr.and(HasSpeechProvider, sharedWhenClause.terminalAvailable), f1: true, - run: (c, accessor) => { + run: (activeInstance, c, accessor) => { const instantiationService = accessor.get(IInstantiationService); TerminalVoiceSession.getInstance(instantiationService).start(); } }); - registerTerminalAction({ + registerActiveInstanceAction({ id: TerminalCommandId.StopSpeechToText, title: { value: localize('workbench.action.stopTerminalSpeechToText', "Stop Terminal Speech To Text"), original: 'Stop Terminal Speech To Text' }, - precondition: ContextKeyExpr.and(HasSpeechProvider, TerminalContextKeys.focus), + precondition: ContextKeyExpr.and(HasSpeechProvider, sharedWhenClause.terminalAvailable), f1: true, - run: (c, accessor) => { + run: (activeInstance, c, accessor) => { const instantiationService = accessor.get(IInstantiationService); TerminalVoiceSession.getInstance(instantiationService).stop(true); } From 77894e0a522cfa1adc58faf1a008bf29012a284a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 16 Jan 2024 11:35:51 -0800 Subject: [PATCH 0354/1897] rename --- .../contrib/terminal/browser/terminalActions.ts | 6 +++--- ...iceTerminalActions.ts => terminalSpeechToText.ts} | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) rename src/vs/workbench/contrib/terminal/browser/{voiceTerminalActions.ts => terminalSpeechToText.ts} (93%) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 0f6d1344b7b..8be2aa0dce6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -64,7 +64,7 @@ import { AccessibleViewProviderId, accessibleViewCurrentProviderId, accessibleVi import { isKeyboardEvent, isMouseEvent, isPointerEvent } from 'vs/base/browser/dom'; import { editorGroupToColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { InstanceContext } from 'vs/workbench/contrib/terminal/browser/terminalContextMenu'; -import { TerminalVoiceSession } from 'vs/workbench/contrib/terminal/browser/voiceTerminalActions'; +import { TerminalSpeechToTextSession } from 'vs/workbench/contrib/terminal/browser/terminalSpeechToText'; import { HasSpeechProvider } from 'vs/workbench/contrib/speech/common/speechService'; export const switchTerminalActionViewItemSeparator = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'; @@ -1654,7 +1654,7 @@ export function registerTerminalActions() { f1: true, run: (activeInstance, c, accessor) => { const instantiationService = accessor.get(IInstantiationService); - TerminalVoiceSession.getInstance(instantiationService).start(); + TerminalSpeechToTextSession.getInstance(instantiationService).start(); } }); @@ -1668,7 +1668,7 @@ export function registerTerminalActions() { f1: true, run: (activeInstance, c, accessor) => { const instantiationService = accessor.get(IInstantiationService); - TerminalVoiceSession.getInstance(instantiationService).stop(true); + TerminalSpeechToTextSession.getInstance(instantiationService).stop(true); } }); } diff --git a/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts similarity index 93% rename from src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts rename to src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts index 1ce2f030317..d0ea4427079 100644 --- a/src/vs/workbench/contrib/terminal/browser/voiceTerminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts @@ -17,18 +17,18 @@ import { IXtermMarker } from 'vs/platform/terminal/common/capabilities/capabilit import { ThemeIcon } from 'vs/base/common/themables'; import { Codicon } from 'vs/base/common/codicons'; -export class TerminalVoiceSession extends Disposable { +export class TerminalSpeechToTextSession extends Disposable { private _input: string = ''; private _decoration: IDecoration | undefined; private _marker: IXtermMarker | undefined; - private static _instance: TerminalVoiceSession | undefined = undefined; + private static _instance: TerminalSpeechToTextSession | undefined = undefined; private _acceptTranscriptionScheduler: RunOnceScheduler | undefined; - static getInstance(instantiationService: IInstantiationService): TerminalVoiceSession { - if (!TerminalVoiceSession._instance) { - TerminalVoiceSession._instance = instantiationService.createInstance(TerminalVoiceSession); + static getInstance(instantiationService: IInstantiationService): TerminalSpeechToTextSession { + if (!TerminalSpeechToTextSession._instance) { + TerminalSpeechToTextSession._instance = instantiationService.createInstance(TerminalSpeechToTextSession); } - return TerminalVoiceSession._instance; + return TerminalSpeechToTextSession._instance; } private _cancellationTokenSource: CancellationTokenSource | undefined; private _disposables = new DisposableStore(); From 7f400caa8401101b82bf31372671c6c5d83f54d4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 16 Jan 2024 11:38:20 -0800 Subject: [PATCH 0355/1897] add css class --- src/vs/workbench/contrib/terminal/browser/media/terminal.css | 4 ++++ .../contrib/terminal/browser/terminalSpeechToText.ts | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 488815cf258..abd6805fa4a 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -565,3 +565,7 @@ .monaco-workbench .xterm.terminal.hide { visibility: hidden; } + +.terminal-speech-to-text { + padding-left: 5px; +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts index d0ea4427079..b3738bbc97e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts @@ -153,8 +153,7 @@ export class TerminalSpeechToTextSession extends Disposable { }); this._decoration?.onRender((e: HTMLElement) => { e.classList.add(...ThemeIcon.asClassNameArray(Codicon.mic)); - e.classList.add('quick-fix'); - e.style.paddingLeft = '5px'; + e.classList.add('terminal-speech-to-text'); }); } } From 3c4a74323442e8edd05dc3eac38594742b41a8bd Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 16 Jan 2024 11:40:29 -0800 Subject: [PATCH 0356/1897] Fix insert generated cell location (#202607) --- .../notebook/browser/view/cellParts/chat/cellChatActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts index 7763a3ea061..cf1d6488b0f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts @@ -479,7 +479,7 @@ registerAction2(class extends NotebookAction { return undefined; } - const cell = firstArg.index === 0 ? undefined : notebookEditor.cellAt(firstArg.index); + const cell = firstArg.index <= 0 ? undefined : notebookEditor.cellAt(firstArg.index - 1); return { cell, @@ -496,7 +496,7 @@ registerAction2(class extends NotebookAction { const languageService = accessor.get(ILanguageService); newCell = insertCell(languageService, context.notebookEditor, 0, CellKind.Code, 'above', undefined, true); } else { - newCell = insertNewCell(accessor, context, CellKind.Code, 'above', true); + newCell = insertNewCell(accessor, context, CellKind.Code, 'below', true); } if (!newCell) { From 3d0c49ab4d9186684820746a239c2704dce4aa6b Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 16 Jan 2024 11:52:34 -0800 Subject: [PATCH 0357/1897] testing: use better icon for clear all results (#202608) --- src/vs/workbench/contrib/testing/browser/testExplorerActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index b23a81c7b97..e932cb450df 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -913,7 +913,7 @@ export class ClearTestResultsAction extends Action2 { id: TestCommandId.ClearTestResultsAction, title: localize2('testing.clearResults', 'Clear All Results'), category, - icon: Codicon.trash, + icon: Codicon.clearAll, menu: [{ id: MenuId.TestPeekTitle, }, { From 73178af4c28d9795e439da1dc0f333184291cbb3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 16 Jan 2024 12:28:15 -0800 Subject: [PATCH 0358/1897] add more punctuation to map --- .../contrib/terminal/browser/terminalSpeechToText.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts index b3738bbc97e..a7bc31c3b5d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts @@ -125,7 +125,11 @@ export class TerminalSpeechToTextSession extends Disposable { 'Dot': '.', 'dot': '.', 'Period': '.', - 'period': '.' + 'period': '.', + 'Quote': '\'', + 'quote': '\'', + 'double quote': '"', + 'Double quote': '"', }; for (const symbol in symbolMap) { From 959eac3b517117decd70a2873ee835705ea673b4 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 16 Jan 2024 14:19:39 -0800 Subject: [PATCH 0359/1897] Fix layout errors for hidden cells. (#202617) --- .../contrib/notebook/browser/notebookEditorWidget.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 4484d005a1c..1a0b859cb60 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2251,6 +2251,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return; } + if (this._list.getViewIndex(cell) === undefined) { + // Cell can be hidden + return; + } + if (this._list.elementHeight(cell) === height) { return; } From 54e45bd049170054c906f722806b8e08eda153e7 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 16 Jan 2024 14:38:19 -0800 Subject: [PATCH 0360/1897] fix #199422 --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 3377ae546d0..18c3a6a92f4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -5,7 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { AriaRole } from 'vs/base/browser/ui/aria/aria'; +import { AriaRole, alert } from 'vs/base/browser/ui/aria/aria'; import { Button } from 'vs/base/browser/ui/button/button'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; @@ -833,6 +833,11 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer Date: Tue, 16 Jan 2024 16:59:56 -0600 Subject: [PATCH 0361/1897] Fix partial result transfer in quick search (#201917) --- .../quickTextSearch/textSearchQuickAccess.ts | 44 ++- .../contrib/search/browser/searchModel.ts | 37 +-- .../search/browser/searchResultsView.ts | 9 +- .../contrib/search/browser/searchView.ts | 292 ++++++++++-------- 4 files changed, 216 insertions(+), 166 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index 1f248e45437..171319ef687 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IMatch } from 'vs/base/common/filters'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ResourceSet } from 'vs/base/common/map'; @@ -22,12 +22,12 @@ import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspac import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { searchDetailsIcon, searchOpenInFileIcon, searchActivityBarIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; -import { FileMatch, Match, RenderableMatch, SearchModel, searchComparer } from 'vs/workbench/contrib/search/browser/searchModel'; +import { FileMatch, Match, RenderableMatch, SearchModel, SearchModelLocation, searchComparer } from 'vs/workbench/contrib/search/browser/searchModel'; import { SearchView, getEditorSelectionFromMatch } from 'vs/workbench/contrib/search/browser/searchView'; import { IWorkbenchSearchConfiguration, getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder'; -import { IPatternInfo, ITextQuery, VIEW_ID } from 'vs/workbench/services/search/common/search'; +import { IPatternInfo, ISearchComplete, ITextQuery, VIEW_ID } from 'vs/workbench/services/search/common/search'; export const TEXT_SEARCH_QUICK_ACCESS_PREFIX = '%'; @@ -45,6 +45,10 @@ const MAX_RESULTS_PER_FILE = 10; export class TextSearchQuickAccess extends PickerQuickAccessProvider { private queryBuilder: QueryBuilder; private searchModel: SearchModel; + private currentAsyncSearch: Promise = Promise.resolve({ + results: [], + messages: [] + }); private _getTextQueryBuilderOptions(charsPerLine: number): ITextQueryBuilderOptions { return { @@ -74,6 +78,7 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { - this.moveToSearchViewlet(this.searchModel, undefined); + this.moveToSearchViewlet(undefined); picker.hide(); }); disposables.add(super.provide(picker, token, runOptions)); @@ -137,6 +142,7 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { + this.currentAsyncSearch = result.asyncResults; await result.asyncResults; const syncResultURIs = new ResourceSet(result.syncResults.map(e => e.resource)); return this.searchModel.searchResult.matches().filter(e => !syncResultURIs.has(e.resource)); @@ -147,12 +153,15 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider | undefined = viewlet?.getControl(); if (currentElem) { @@ -181,7 +190,7 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { - this.moveToSearchViewlet(this.searchModel, matches[limit]); + this.moveToSearchViewlet(matches[limit]); } }); break; @@ -212,7 +221,7 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { - this.moveToSearchViewlet(this.searchModel, element); + this.moveToSearchViewlet(element); } }); break; @@ -243,7 +252,7 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { - this.moveToSearchViewlet(this.searchModel, element); + this.moveToSearchViewlet(element); return TriggerAction.CLOSE_PICKER; } }); @@ -270,11 +279,22 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider | Promise | FastAndSlowPicks> | FastAndSlowPicks | null { + const conditionalTokenCts = disposables.add(new CancellationTokenSource()); + + const searchModelAtTimeOfSearch = this.searchModel; + + disposables.add(token.onCancellationRequested(() => { + if (searchModelAtTimeOfSearch.location === SearchModelLocation.QUICK_ACCESS) { + // if the search model has not been imported to the panel, you can cancel + conditionalTokenCts.cancel(); + } + })); + if (contentPattern === '') { this.searchModel.searchResult.clear(); return []; } - const allMatches = this.doSearch(contentPattern, token); + const allMatches = this.doSearch(contentPattern, conditionalTokenCts.token); if (!allMatches) { return null; diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index c52d6d7a914..9ff6b1d1072 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -1580,6 +1580,7 @@ function createParentList(element: RenderableMatch): RenderableMatch[] { return parentArray; } + export class SearchResult extends Disposable { private _onChange = this._register(new PauseableEmitter({ @@ -1598,7 +1599,7 @@ export class SearchResult extends Disposable { private _onDidChangeModelListener: IDisposable | undefined; constructor( - public searchModel: SearchModel, + public readonly searchModel: SearchModel, @IReplaceService private readonly replaceService: IReplaceService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IModelService private readonly modelService: IModelService, @@ -1607,7 +1608,6 @@ export class SearchResult extends Disposable { ) { super(); this._rangeHighlightDecorations = this.instantiationService.createInstance(RangeHighlightDecorations); - this.modelService.getModels().forEach(model => this.onModelAdded(model)); this._register(this.modelService.onModelAdded(model => this.onModelAdded(model))); @@ -1936,6 +1936,11 @@ export class SearchResult extends Disposable { } } +export enum SearchModelLocation { + PANEL, + QUICK_ACCESS +} + export class SearchModel extends Disposable { private _searchResult: SearchResult; @@ -1957,7 +1962,7 @@ export class SearchModel extends Disposable { private currentCancelTokenSource: CancellationTokenSource | null = null; private searchCancelledForNewSearch: boolean = false; - private _searchResultChangedListener: IDisposable; + public location: SearchModelLocation = SearchModelLocation.PANEL; constructor( @ISearchService private readonly searchService: ISearchService, @@ -1969,7 +1974,7 @@ export class SearchModel extends Disposable { ) { super(); this._searchResult = this.instantiationService.createInstance(SearchResult, this); - this._searchResultChangedListener = this._register(this._searchResult.onChange((e) => this._onSearchResultChanged.fire(e))); + this._register(this._searchResult.onChange((e) => this._onSearchResultChanged.fire(e))); } isReplaceActive(): boolean { @@ -2008,15 +2013,6 @@ export class SearchModel extends Disposable { return this._searchResult; } - set searchResult(searchResult: SearchResult) { - this._searchResult.dispose(); - this._searchResultChangedListener.dispose(); - - this._searchResult = searchResult; - this._searchResult.searchModel = this; - this._searchResultChangedListener = this._register(this._searchResult.onChange((e) => this._onSearchResultChanged.fire(e))); - } - private doSearch(query: ITextQuery, progressEmitter: Emitter, searchQuery: ITextQuery, searchInstanceID: string, onProgress?: (result: ISearchProgressItem) => void, callerToken?: CancellationToken): { asyncResults: Promise; @@ -2145,7 +2141,7 @@ export class SearchModel extends Disposable { } } - private onSearchCompleted(completed: ISearchComplete | null, duration: number, searchInstanceID: string): ISearchComplete | null { + private onSearchCompleted(completed: ISearchComplete | undefined, duration: number, searchInstanceID: string): ISearchComplete | undefined { if (!this._searchQuery) { throw new Error('onSearchCompleted must be called after a search is started'); } @@ -2193,7 +2189,7 @@ export class SearchModel extends Disposable { this.onSearchCompleted( this.searchCancelledForNewSearch ? { exit: SearchCompletionExitCode.NewSearchStarted, results: [], messages: [] } - : null, + : undefined, duration, ''); this.searchCancelledForNewSearch = false; } @@ -2238,10 +2234,6 @@ export class SearchModel extends Disposable { super.dispose(); } - transferSearchResult(other: SearchModel): void { - other.searchResult = this._searchResult; - this._searchResult = this.instantiationService.createInstance(SearchResult, this); - } } export type FileMatchOrMatch = FileMatch | Match; @@ -2262,6 +2254,11 @@ export class SearchViewModelWorkbenchService implements ISearchViewModelWorkbenc } return this._searchModel; } + + set searchModel(searchModel: SearchModel) { + this._searchModel?.dispose(); + this._searchModel = searchModel; + } } export const ISearchViewModelWorkbenchService = createDecorator('searchViewModelWorkbenchService'); @@ -2269,7 +2266,7 @@ export const ISearchViewModelWorkbenchService = createDecorator, index: number, templateData: IMatchTemplate): void { const match = node.element; const preview = match.preview(); - const replace = this.searchModel.isReplaceActive() && - !!this.searchModel.replaceString && + const replace = this.searchView.model.isReplaceActive() && + !!this.searchView.model.replaceString && !(match instanceof MatchInNotebook && match.isReadonly()); templateData.before.textContent = preview.before; @@ -402,7 +401,7 @@ export class MatchRenderer extends Disposable implements ICompressibleTreeRender export class SearchAccessibilityProvider implements IListAccessibilityProvider { constructor( - private searchModel: SearchModel, + private searchView: SearchView, @ILabelService private readonly labelService: ILabelService ) { } @@ -427,7 +426,7 @@ export class SearchAccessibilityProvider implements IListAccessibilityProviderelement; - const searchModel: SearchModel = this.searchModel; + const searchModel: SearchModel = this.searchView.model; const replace = searchModel.isReplaceActive() && !!searchModel.replaceString; const matchString = match.getMatchString(); const range = match.range(); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 423e7aa80af..06ee843f9d3 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -15,7 +15,7 @@ import * as errors from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import * as env from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; @@ -70,7 +70,7 @@ import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; import { getOutOfWorkspaceEditorResources, SearchStateKey, SearchUIState } from 'vs/workbench/contrib/search/common/search'; import { ISearchHistoryService, ISearchHistoryValues, SearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; -import { FileMatch, FileMatchOrMatch, FolderMatch, FolderMatchWithResource, IChangeEvent, ISearchViewModelWorkbenchService, Match, MatchInNotebook, RenderableMatch, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; +import { FileMatch, FileMatchOrMatch, FolderMatch, FolderMatchWithResource, IChangeEvent, ISearchViewModelWorkbenchService, Match, MatchInNotebook, RenderableMatch, searchMatchComparer, SearchModel, SearchModelLocation, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; import { createEditorFromSearchResult } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IPreferencesService, ISettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; @@ -161,6 +161,8 @@ export class SearchView extends ViewPane { private _refreshResultsScheduler: RunOnceScheduler; + private _onSearchResultChangedDisposable: IDisposable | undefined; + constructor( options: IViewPaneOptions, @IFileService private readonly fileService: IFileService, @@ -254,7 +256,7 @@ export class SearchView extends ViewPane { this.toggleCollapseStateDelayer = this._register(new Delayer(100)); this.triggerQueryDelayer = this._register(new Delayer(0)); - this.treeAccessibilityProvider = this.instantiationService.createInstance(SearchAccessibilityProvider, this.viewModel); + this.treeAccessibilityProvider = this.instantiationService.createInstance(SearchAccessibilityProvider, this); this.isTreeLayoutViewVisible = this.viewletState['view.treeLayout'] ?? (this.searchConfig.defaultViewMode === ViewMode.Tree); this._refreshResultsScheduler = this._register(new RunOnceScheduler(this._updateResults.bind(this), 80)); @@ -315,6 +317,10 @@ export class SearchView extends ViewPane { return this.viewModel && this.viewModel.searchResult; } + get model(): SearchModel { + return this.viewModel; + } + private onDidChangeWorkbenchState(): void { if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.searchWithoutFolderMessageElement) { dom.hide(this.searchWithoutFolderMessageElement); @@ -333,13 +339,37 @@ export class SearchView extends ViewPane { this.pauseSearching = false; } - public async importSearchResult(searchModel: SearchModel): Promise { - // experimental: used by the quick access search to overwrite a search result - searchModel.transferSearchResult(this.viewModel); + public async replaceSearchModel(searchModel: SearchModel, asyncResults: Promise): Promise { + let progressComplete: () => void; + this.progressService.withProgress({ location: this.getProgressLocation(), delay: 0 }, _progress => { + return new Promise(resolve => progressComplete = resolve); + }); + + const slowTimer = setTimeout(() => { + this.state = SearchUIState.SlowSearch; + }, 2000); + + // remove old model and use the new searchModel + searchModel.location = SearchModelLocation.PANEL; + searchModel.replaceActive = this.viewModel.isReplaceActive(); + this._onSearchResultChangedDisposable?.dispose(); + this._onSearchResultChangedDisposable = this._register(searchModel.onSearchResultChanged((event) => this.onSearchResultsChanged(event))); + + // this call will also dispose of the old model + this.searchViewModelWorkbenchService.searchModel = searchModel; + this.viewModel = searchModel; this.onSearchResultsChanged(); this.refreshInputs(); + asyncResults.then((complete) => { + clearTimeout(slowTimer); + this.onSearchComplete(progressComplete, undefined, undefined, complete); + }, (e) => { + clearTimeout(slowTimer); + this.onSearchError(e, progressComplete, undefined, undefined); + }); + const collapseResults = this.searchConfig.collapseResults; if (collapseResults !== 'alwaysCollapse' && this.viewModel.searchResult.matches().length === 1) { const onlyMatch = this.viewModel.searchResult.matches()[0]; @@ -461,7 +491,7 @@ export class SearchView extends ViewPane { this.toggleQueryDetails(true, true, true); } - this._register(this.viewModel.onSearchResultChanged((event) => this.onSearchResultsChanged(event))); + this._onSearchResultChangedDisposable = this._register(this.viewModel.onSearchResultChanged((event) => this.onSearchResultsChanged(event))); this._register(this.onDidChangeBodyVisibility(visible => this.onVisibilityChanged(visible))); @@ -843,7 +873,7 @@ export class SearchView extends ViewPane { [ this._register(this.instantiationService.createInstance(FolderMatchRenderer, this, this.treeLabels)), this._register(this.instantiationService.createInstance(FileMatchRenderer, this, this.treeLabels)), - this._register(this.instantiationService.createInstance(MatchRenderer, this.viewModel, this)), + this._register(this.instantiationService.createInstance(MatchRenderer, this)), ], { identityProvider, @@ -867,7 +897,6 @@ export class SearchView extends ViewPane { this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); const updateHasSomeCollapsible = () => this.toggleCollapseStateDelayer.trigger(() => this.hasSomeCollapsibleResultKey.set(this.hasSomeCollapsible())); updateHasSomeCollapsible(); - this._register(this.viewModel.onSearchResultChanged(() => updateHasSomeCollapsible())); this._register(this.tree.onDidChangeCollapseState(() => updateHasSomeCollapsible())); this._register(Event.debounce(this.tree.onDidOpen, (last, event) => event, DEBOUNCE_DELAY, true)(options => { @@ -1564,6 +1593,124 @@ export class SearchView extends ViewPane { } } + private onSearchComplete(progressComplete: () => void, excludePatternText?: string, includePatternText?: string, completed?: ISearchComplete) { + + this.state = SearchUIState.Idle; + + // Complete up to 100% as needed + progressComplete(); + + // Do final render, then expand if just 1 file with less than 50 matches + this.onSearchResultsChanged(); + + const collapseResults = this.searchConfig.collapseResults; + if (collapseResults !== 'alwaysCollapse' && this.viewModel.searchResult.matches().length === 1) { + const onlyMatch = this.viewModel.searchResult.matches()[0]; + if (onlyMatch.count() < 50) { + this.tree.expand(onlyMatch); + } + } + + this.viewModel.replaceString = this.searchWidget.getReplaceValue(); + + const hasResults = !this.viewModel.searchResult.isEmpty(); + if (completed?.exit === SearchCompletionExitCode.NewSearchStarted) { + return; + } + + if (!hasResults) { + const hasExcludes = !!excludePatternText; + const hasIncludes = !!includePatternText; + let message: string; + + if (!completed) { + message = SEARCH_CANCELLED_MESSAGE; + } else if (this.inputPatternIncludes.onlySearchInOpenEditors()) { + if (hasIncludes && hasExcludes) { + message = nls.localize('noOpenEditorResultsIncludesExcludes', "No results found in open editors matching '{0}' excluding '{1}' - ", includePatternText, excludePatternText); + } else if (hasIncludes) { + message = nls.localize('noOpenEditorResultsIncludes', "No results found in open editors matching '{0}' - ", includePatternText); + } else if (hasExcludes) { + message = nls.localize('noOpenEditorResultsExcludes', "No results found in open editors excluding '{0}' - ", excludePatternText); + } else { + message = nls.localize('noOpenEditorResultsFound', "No results found in open editors. Review your settings for configured exclusions and check your gitignore files - "); + } + } else { + if (hasIncludes && hasExcludes) { + message = nls.localize('noResultsIncludesExcludes', "No results found in '{0}' excluding '{1}' - ", includePatternText, excludePatternText); + } else if (hasIncludes) { + message = nls.localize('noResultsIncludes', "No results found in '{0}' - ", includePatternText); + } else if (hasExcludes) { + message = nls.localize('noResultsExcludes', "No results found excluding '{0}' - ", excludePatternText); + } else { + message = nls.localize('noResultsFound', "No results found. Review your settings for configured exclusions and check your gitignore files - "); + } + } + + // Indicate as status to ARIA + aria.status(message); + + const messageEl = this.clearMessage(); + dom.append(messageEl, message); + + if (!completed) { + const searchAgainButton = this.messageDisposables.add(new SearchLinkButton( + nls.localize('rerunSearch.message', "Search again"), + () => this.triggerQueryChange({ preserveFocus: false }))); + dom.append(messageEl, searchAgainButton.element); + } else if (hasIncludes || hasExcludes) { + const searchAgainButton = this.messageDisposables.add(new SearchLinkButton(nls.localize('rerunSearchInAll.message', "Search again in all files"), this.onSearchAgain.bind(this))); + dom.append(messageEl, searchAgainButton.element); + } else { + const openSettingsButton = this.messageDisposables.add(new SearchLinkButton(nls.localize('openSettings.message', "Open Settings"), this.onOpenSettings.bind(this))); + dom.append(messageEl, openSettingsButton.element); + } + + if (completed) { + dom.append(messageEl, $('span', undefined, ' - ')); + + const learnMoreButton = this.messageDisposables.add(new SearchLinkButton(nls.localize('openSettings.learnMore', "Learn More"), this.onLearnMore.bind(this))); + dom.append(messageEl, learnMoreButton.element); + } + + if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { + this.showSearchWithoutFolderMessage(); + } + this.reLayout(); + } else { + this.viewModel.searchResult.toggleHighlights(this.isVisible()); // show highlights + + // Indicate final search result count for ARIA + aria.status(nls.localize('ariaSearchResultsStatus', "Search returned {0} results in {1} files", this.viewModel.searchResult.count(), this.viewModel.searchResult.fileCount())); + } + + + if (completed && completed.limitHit) { + completed.messages.push({ type: TextSearchCompleteMessageType.Warning, text: nls.localize('searchMaxResultsWarning', "The result set only contains a subset of all matches. Be more specific in your search to narrow down the results.") }); + } + + if (completed && completed.messages) { + for (const message of completed.messages) { + this.addMessage(message); + } + } + + this.reLayout(); + } + + private onSearchError(e: any, progressComplete: () => void, excludePatternText?: string, includePatternText?: string, completed?: ISearchComplete) { + this.state = SearchUIState.Idle; + if (errors.isCancellationError(e)) { + return this.onSearchComplete(progressComplete, excludePatternText, includePatternText, completed); + } else { + progressComplete(); + this.searchWidget.searchInput?.showMessage({ content: e.message, type: MessageType.ERROR }); + this.viewModel.searchResult.clear(); + + return Promise.resolve(); + } + } + private doSearch(query: ITextQuery, excludePatternText: string, includePatternText: string, triggeredOnType: boolean): Thenable { let progressComplete: () => void; this.progressService.withProgress({ location: this.getProgressLocation(), delay: triggeredOnType ? 300 : 0 }, _progress => { @@ -1578,125 +1725,6 @@ export class SearchView extends ViewPane { this.state = SearchUIState.SlowSearch; }, 2000); - const onComplete = (completed?: ISearchComplete) => { - clearTimeout(slowTimer); - this.state = SearchUIState.Idle; - - // Complete up to 100% as needed - progressComplete(); - - // Do final render, then expand if just 1 file with less than 50 matches - this.onSearchResultsChanged(); - - const collapseResults = this.searchConfig.collapseResults; - if (collapseResults !== 'alwaysCollapse' && this.viewModel.searchResult.matches().length === 1) { - const onlyMatch = this.viewModel.searchResult.matches()[0]; - if (onlyMatch.count() < 50) { - this.tree.expand(onlyMatch); - } - } - - this.viewModel.replaceString = this.searchWidget.getReplaceValue(); - - const hasResults = !this.viewModel.searchResult.isEmpty(); - if (completed?.exit === SearchCompletionExitCode.NewSearchStarted) { - return; - } - - if (!hasResults) { - const hasExcludes = !!excludePatternText; - const hasIncludes = !!includePatternText; - let message: string; - - if (!completed) { - message = SEARCH_CANCELLED_MESSAGE; - } else if (this.inputPatternIncludes.onlySearchInOpenEditors()) { - if (hasIncludes && hasExcludes) { - message = nls.localize('noOpenEditorResultsIncludesExcludes', "No results found in open editors matching '{0}' excluding '{1}' - ", includePatternText, excludePatternText); - } else if (hasIncludes) { - message = nls.localize('noOpenEditorResultsIncludes', "No results found in open editors matching '{0}' - ", includePatternText); - } else if (hasExcludes) { - message = nls.localize('noOpenEditorResultsExcludes', "No results found in open editors excluding '{0}' - ", excludePatternText); - } else { - message = nls.localize('noOpenEditorResultsFound', "No results found in open editors. Review your settings for configured exclusions and check your gitignore files - "); - } - } else { - if (hasIncludes && hasExcludes) { - message = nls.localize('noResultsIncludesExcludes', "No results found in '{0}' excluding '{1}' - ", includePatternText, excludePatternText); - } else if (hasIncludes) { - message = nls.localize('noResultsIncludes', "No results found in '{0}' - ", includePatternText); - } else if (hasExcludes) { - message = nls.localize('noResultsExcludes', "No results found excluding '{0}' - ", excludePatternText); - } else { - message = nls.localize('noResultsFound', "No results found. Review your settings for configured exclusions and check your gitignore files - "); - } - } - - // Indicate as status to ARIA - aria.status(message); - - const messageEl = this.clearMessage(); - dom.append(messageEl, message); - - if (!completed) { - const searchAgainButton = this.messageDisposables.add(new SearchLinkButton( - nls.localize('rerunSearch.message', "Search again"), - () => this.triggerQueryChange({ preserveFocus: false }))); - dom.append(messageEl, searchAgainButton.element); - } else if (hasIncludes || hasExcludes) { - const searchAgainButton = this.messageDisposables.add(new SearchLinkButton(nls.localize('rerunSearchInAll.message', "Search again in all files"), this.onSearchAgain.bind(this))); - dom.append(messageEl, searchAgainButton.element); - } else { - const openSettingsButton = this.messageDisposables.add(new SearchLinkButton(nls.localize('openSettings.message', "Open Settings"), this.onOpenSettings.bind(this))); - dom.append(messageEl, openSettingsButton.element); - } - - if (completed) { - dom.append(messageEl, $('span', undefined, ' - ')); - - const learnMoreButton = this.messageDisposables.add(new SearchLinkButton(nls.localize('openSettings.learnMore', "Learn More"), this.onLearnMore.bind(this))); - dom.append(messageEl, learnMoreButton.element); - } - - if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { - this.showSearchWithoutFolderMessage(); - } - this.reLayout(); - } else { - this.viewModel.searchResult.toggleHighlights(this.isVisible()); // show highlights - - // Indicate final search result count for ARIA - aria.status(nls.localize('ariaSearchResultsStatus', "Search returned {0} results in {1} files", this.viewModel.searchResult.count(), this.viewModel.searchResult.fileCount())); - } - - - if (completed && completed.limitHit) { - completed.messages.push({ type: TextSearchCompleteMessageType.Warning, text: nls.localize('searchMaxResultsWarning', "The result set only contains a subset of all matches. Be more specific in your search to narrow down the results.") }); - } - - if (completed && completed.messages) { - for (const message of completed.messages) { - this.addMessage(message); - } - } - - this.reLayout(); - }; - - const onError = (e: any) => { - clearTimeout(slowTimer); - this.state = SearchUIState.Idle; - if (errors.isCancellationError(e)) { - return onComplete(undefined); - } else { - progressComplete(); - this.searchWidget.searchInput?.showMessage({ content: e.message, type: MessageType.ERROR }); - this.viewModel.searchResult.clear(); - - return Promise.resolve(); - } - }; - this._visibleMatches = 0; this._refreshResultsScheduler.schedule(); @@ -1706,7 +1734,13 @@ export class SearchView extends ViewPane { this.tree.setSelection([]); this.tree.setFocus([]); const result = this.viewModel.search(query); - return result.asyncResults.then(onComplete, onError); + return result.asyncResults.then((complete) => { + clearTimeout(slowTimer); + this.onSearchComplete(progressComplete, excludePatternText, includePatternText, complete); + }, (e) => { + clearTimeout(slowTimer); + this.onSearchError(e, progressComplete, excludePatternText, includePatternText); + }); } private onOpenSettings(e: dom.EventLike): void { From f00ee84937a12ca9e0c2c4492ca38d206d0f29aa Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Tue, 16 Jan 2024 18:21:44 -0600 Subject: [PATCH 0362/1897] add message for blank picker in quick search (#202625) add message for blank picker --- .../quickTextSearch/textSearchQuickAccess.ts | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index 171319ef687..6a43cf2532c 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -94,7 +94,11 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { - this.moveToSearchViewlet(undefined); + if (this.searchModel.searchResult.count() > 0) { + this.moveToSearchViewlet(undefined); + } else { + this._viewsService.openView(VIEW_ID, true); + } picker.hide(); }); disposables.add(super.provide(picker, token, runOptions)); @@ -279,9 +283,16 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider | Promise | FastAndSlowPicks> | FastAndSlowPicks | null { - const conditionalTokenCts = disposables.add(new CancellationTokenSource()); - const searchModelAtTimeOfSearch = this.searchModel; + if (contentPattern === '') { + + this.searchModel.searchResult.clear(); + return [{ + label: localize('enterSearchTerm', "Enter a term to search for across your files.") + }]; + } + + const conditionalTokenCts = disposables.add(new CancellationTokenSource()); disposables.add(token.onCancellationRequested(() => { if (searchModelAtTimeOfSearch.location === SearchModelLocation.QUICK_ACCESS) { @@ -289,11 +300,6 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider Date: Tue, 16 Jan 2024 18:42:09 -0600 Subject: [PATCH 0363/1897] show gradual updates when moving search result to search panel (#202626) --- src/vs/workbench/contrib/search/browser/searchView.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 06ee843f9d3..8286ef92bd2 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -349,6 +349,8 @@ export class SearchView extends ViewPane { this.state = SearchUIState.SlowSearch; }, 2000); + this._refreshResultsScheduler.schedule(); + // remove old model and use the new searchModel searchModel.location = SearchModelLocation.PANEL; searchModel.replaceActive = this.viewModel.isReplaceActive(); From c6f507deeb99925e713271b1048f21dbaab4bd54 Mon Sep 17 00:00:00 2001 From: Bryan Ricker <978899+bricker@users.noreply.github.com> Date: Tue, 16 Jan 2024 18:15:52 -0800 Subject: [PATCH 0364/1897] doc typo fix (#202429) --- src/vscode-dts/vscode.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 36f2cd8ca2c..5a7ea8aec46 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -9598,7 +9598,7 @@ declare module 'vscode' { */ export interface WebviewViewProvider { /** - * Revolves a webview view. + * Resolves a webview view. * * `resolveWebviewView` is called when a view first becomes visible. This may happen when the view is * first loaded or when the user hides and then shows a view again. From 70348b0a63570b8496d6f111043c48aaa1b7c472 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 17 Jan 2024 11:21:37 +0530 Subject: [PATCH 0365/1897] fix #200010 (#202638) --- .../extensionRecommendationNotificationService.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts index e840bd9b331..d175546d78a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts @@ -41,7 +41,6 @@ type ExtensionWorkspaceRecommendationsNotificationClassification = { const ignoreImportantExtensionRecommendationStorageKey = 'extensionsAssistant/importantRecommendationsIgnore'; const donotShowWorkspaceRecommendationsStorageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; -const choiceNever = localize('neverShowAgain', "Don't Show Again"); type RecommendationsNotificationActions = { onDidInstallRecommendedExtensions(extensions: IExtension[]): void; @@ -258,14 +257,17 @@ export class ExtensionRecommendationNotificationService extends Disposable imple searchValue = source === RecommendationSource.WORKSPACE ? '@recommended' : extensions.map(extensionId => `@id:${extensionId.identifier.id}`).join(' '); } + const donotShowAgainLabel = source === RecommendationSource.WORKSPACE ? localize('donotShowAgain', "Don't Show Again for this Repository") + : extensions.length > 1 ? localize('donotShowAgainExtension', "Don't Show Again for these Extensions") : localize('donotShowAgainExtensionSingle', "Don't Show Again for this Extension"); + return raceCancellablePromises([ - this._registerP(this.showRecommendationsNotification(extensions, message, searchValue, source, recommendationsNotificationActions)), + this._registerP(this.showRecommendationsNotification(extensions, message, searchValue, donotShowAgainLabel, source, recommendationsNotificationActions)), this._registerP(this.waitUntilRecommendationsAreInstalled(extensions)) ]); } - private showRecommendationsNotification(extensions: IExtension[], message: string, searchValue: string, source: RecommendationSource, + private showRecommendationsNotification(extensions: IExtension[], message: string, searchValue: string, donotShowAgainLabel: string, source: RecommendationSource, { onDidInstallRecommendedExtensions, onDidShowRecommendedExtensions, onDidCancelRecommendedExtensions, onDidNeverShowRecommendedExtensionsAgain }: RecommendationsNotificationActions): CancelablePromise { return createCancelablePromise(async token => { let accepted = false; @@ -296,7 +298,7 @@ export class ExtensionRecommendationNotificationService extends Disposable imple this.runAction(this.instantiationService.createInstance(SearchExtensionsAction, searchValue)); } }, { - label: choiceNever, + label: donotShowAgainLabel, isSecondary: true, run: () => { onDidNeverShowRecommendedExtensionsAgain(extensions); From aed5f4d65d2dbfda2c98abd7eb371751016838ab Mon Sep 17 00:00:00 2001 From: Kim Reenberg Date: Wed, 17 Jan 2024 07:29:01 +0100 Subject: [PATCH 0366/1897] fix: fallback to strings for non-executable libc.so.6 (#202581) * fix: fallback to strings for non-executable libc.so.6 * chore: use cat over strings --------- Co-authored-by: khreenberg Co-authored-by: Robo --- resources/server/bin/helpers/check-requirements-linux.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/resources/server/bin/helpers/check-requirements-linux.sh b/resources/server/bin/helpers/check-requirements-linux.sh index 625b569602a..c3d3d8ece6a 100644 --- a/resources/server/bin/helpers/check-requirements-linux.sh +++ b/resources/server/bin/helpers/check-requirements-linux.sh @@ -81,7 +81,13 @@ if [ -n "$(ldd --version | grep -v musl)" ]; then # Rather than trusting the output of ldd --version (which is not always accurate) # we instead use the version of the cached libc.so.6 file itself. libc_real_path=$(readlink -f "$libc_path") - libc_version=$($libc_real_path --version | sed -n 's/.*stable release version \([0-9]\+\.[0-9]\+\).*/\1/p') + if [ -x "$libc_real_path" ]; then + # get version from executable + libc_version=$($libc_real_path --version | sed -n 's/.*stable release version \([0-9]\+\.[0-9]\+\).*/\1/p') + else + # .so is not executable on this host; try getting from strings + libc_version=$(cat "$libc_real_path" | sed -n 's/.*stable release version \([0-9]\+\.[0-9]\+\).*/\1/p') + fi if [ "$(printf '%s\n' "2.28" "$libc_version" | sort -V | head -n1)" = "2.28" ]; then found_required_glibc=1 else From eb24c3f99c45bae0e5f4773b3e90a3c63a78940d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 17 Jan 2024 16:15:38 +0530 Subject: [PATCH 0367/1897] fix #199947 (#202654) --- .../browser/parts/views/viewPaneContainer.ts | 34 ++++++++++--------- .../contrib/debug/browser/debugViewlet.ts | 2 +- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 44b825998ab..3021fe51eee 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -13,7 +13,7 @@ import { IAction } from 'vs/base/common/actions'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { combinedDisposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { combinedDisposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { assertIsDefined } from 'vs/base/common/types'; import 'vs/css!./media/paneviewlet'; import * as nls from 'vs/nls'; @@ -324,7 +324,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { private readonly visibleViewsCountFromCache: number | undefined; private readonly visibleViewsStorageId: string; protected readonly viewContainerModel: IViewContainerModel; - private viewDisposables: IDisposable[] = []; private readonly _onTitleAreaUpdate: Emitter = this._register(new Emitter()); readonly onTitleAreaUpdate: Event = this._onTitleAreaUpdate.event; @@ -394,7 +393,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this.viewContainer = container; this.visibleViewsStorageId = `${id}.numberOfVisibleViews`; this.visibleViewsCountFromCache = this.storageService.getNumber(this.visibleViewsStorageId, StorageScope.WORKSPACE, undefined); - this._register(toDisposable(() => this.viewDisposables = dispose(this.viewDisposables))); this.viewContainerModel = this.viewDescriptorService.getViewContainerModel(container); } @@ -657,11 +655,11 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { return optimalWidth + additionalMargin; } - addPanes(panes: { pane: ViewPane; size: number; index?: number }[]): void { + addPanes(panes: { pane: ViewPane; size: number; index?: number; disposable: IDisposable }[]): void { const wasMerged = this.isViewMergedWithContainer(); - for (const { pane: pane, size, index } of panes) { - this.addPane(pane, size, index); + for (const { pane: pane, size, index, disposable } of panes) { + this.addPane(pane, size, disposable, index); } this.updateViewHeaders(); @@ -773,7 +771,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } protected onDidAddViewDescriptors(added: IAddedViewDescriptorRef[]): ViewPane[] { - const panesToAdd: { pane: ViewPane; size: number; index: number }[] = []; + const panesToAdd: { pane: ViewPane; size: number; index: number; disposable: IDisposable }[] = []; for (const { viewDescriptor, collapsed, index, size } of added) { const pane = this.createView(viewDescriptor, @@ -795,8 +793,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this.viewContainerModel.setCollapsed(viewDescriptor.id, collapsed); }); - this.viewDisposables.splice(index, 0, combinedDisposable(contextMenuDisposable, collapseDisposable)); - panesToAdd.push({ pane, size: size || pane.minimumSize, index }); + panesToAdd.push({ pane, size: size || pane.minimumSize, index, disposable: combinedDisposable(contextMenuDisposable, collapseDisposable) }); } this.addPanes(panesToAdd); @@ -814,14 +811,18 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { removed = removed.sort((a, b) => b.index - a.index); const panesToRemove: ViewPane[] = []; for (const { index } of removed) { - const [disposable] = this.viewDisposables.splice(index, 1); - disposable.dispose(); - panesToRemove.push(this.panes[index]); + const paneItem = this.paneItems[index]; + if (paneItem) { + panesToRemove.push(this.paneItems[index].pane); + } } - this.removePanes(panesToRemove); - for (const pane of panesToRemove) { - pane.setVisible(false); + if (panesToRemove.length) { + this.removePanes(panesToRemove); + + for (const pane of panesToRemove) { + pane.setVisible(false); + } } } @@ -833,7 +834,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } } - private addPane(pane: ViewPane, size: number, index = this.paneItems.length - 1): void { + private addPane(pane: ViewPane, size: number, disposable: IDisposable, index = this.paneItems.length - 1): void { const onDidFocus = pane.onDidFocus(() => { this._onDidFocusView.fire(pane); this.lastFocusedPane = pane; @@ -862,6 +863,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { }); const store = new DisposableStore(); + store.add(disposable); store.add(combinedDisposable(pane, onDidFocus, onDidBlur, onDidChangeTitleArea, onDidChange, onDidChangeVisibility)); const paneItem: IViewPaneItem = { pane, disposable: store }; diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 4a8f0e2dc99..24c202b7c34 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -133,7 +133,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer { } } - override addPanes(panes: { pane: ViewPane; size: number; index?: number }[]): void { + override addPanes(panes: { pane: ViewPane; size: number; index?: number; disposable: IDisposable }[]): void { super.addPanes(panes); for (const { pane: pane } of panes) { From 695ed7fb5f0b95a04e14cc6d053e8a7a9c27a2de Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 17 Jan 2024 16:38:57 +0530 Subject: [PATCH 0368/1897] fix #189331 (#202658) --- src/vs/workbench/contrib/markers/browser/markersView.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index 82c27df0f16..b3a9bd0dab1 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -1025,8 +1025,10 @@ class MarkersTree extends WorkbenchObjectTree impleme update(resourceMarkers: ResourceMarkers[]): void { for (const resourceMarker of resourceMarkers) { - this.setChildren(resourceMarker, createResourceMarkersIterator(resourceMarker)); - this.rerender(resourceMarker); + if (this.hasElement(resourceMarker)) { + this.setChildren(resourceMarker, createResourceMarkersIterator(resourceMarker)); + this.rerender(resourceMarker); + } } } From efc4fc231c384d35fb77224d77b714a82e27f02a Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 17 Jan 2024 12:00:41 +0100 Subject: [PATCH 0369/1897] Uses assertFn instead of BugIndicatingError --- src/vs/base/common/observableInternal/derived.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/vs/base/common/observableInternal/derived.ts b/src/vs/base/common/observableInternal/derived.ts index 83abf2444d3..573b5ad5258 100644 --- a/src/vs/base/common/observableInternal/derived.ts +++ b/src/vs/base/common/observableInternal/derived.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BugIndicatingError } from 'vs/base/common/errors'; -import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IReader, IObservable, BaseObservable, IObserver, _setDerivedOpts, IChangeContext, getFunctionName, DebugNameFn, getDebugName, Owner } from 'vs/base/common/observableInternal/base'; +import { assertFn } from 'vs/base/common/assert'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { BaseObservable, DebugNameFn, IChangeContext, IObservable, IObserver, IReader, Owner, _setDerivedOpts, getDebugName, getFunctionName } from 'vs/base/common/observableInternal/base'; import { getLogger } from 'vs/base/common/observableInternal/logging'; export type EqualityComparer = (a: T, b: T) => boolean; @@ -300,9 +300,7 @@ export class Derived extends BaseObservable im r.endUpdate(this); } } - if (this.updateCount < 0) { - throw new BugIndicatingError(); - } + assertFn(() => this.updateCount >= 0); } public handlePossibleChange(observable: IObservable): void { From 7fd2edcf4696f1707be45d38c5bfc1b14dc47f41 Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 17 Jan 2024 20:18:31 +0900 Subject: [PATCH 0370/1897] fix: ensure requirements check works for libc development versions (#202660) --- resources/server/bin/helpers/check-requirements-linux.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/server/bin/helpers/check-requirements-linux.sh b/resources/server/bin/helpers/check-requirements-linux.sh index c3d3d8ece6a..23220930189 100644 --- a/resources/server/bin/helpers/check-requirements-linux.sh +++ b/resources/server/bin/helpers/check-requirements-linux.sh @@ -83,10 +83,10 @@ if [ -n "$(ldd --version | grep -v musl)" ]; then libc_real_path=$(readlink -f "$libc_path") if [ -x "$libc_real_path" ]; then # get version from executable - libc_version=$($libc_real_path --version | sed -n 's/.*stable release version \([0-9]\+\.[0-9]\+\).*/\1/p') + libc_version=$($libc_real_path --version | sed -n 's/.*release version \([0-9]\+\.[0-9]\+\).*/\1/p') else # .so is not executable on this host; try getting from strings - libc_version=$(cat "$libc_real_path" | sed -n 's/.*stable release version \([0-9]\+\.[0-9]\+\).*/\1/p') + libc_version=$(cat "$libc_real_path" | sed -n 's/.*release version \([0-9]\+\.[0-9]\+\).*/\1/p') fi if [ "$(printf '%s\n' "2.28" "$libc_version" | sort -V | head -n1)" = "2.28" ]; then found_required_glibc=1 From bf53fde347bbcf0c1e90d15255d925c93675ab10 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 17 Jan 2024 12:30:40 +0100 Subject: [PATCH 0371/1897] move browser things back so that services and dependents are registered properly --- .../browser/inlineChat.contribution.ts | 64 +++++++++++++++++++ .../inlineChat.contribution.ts | 64 ++----------------- src/vs/workbench/workbench.common.main.ts | 1 + 3 files changed, 71 insertions(+), 58 deletions(-) create mode 100644 src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts new file mode 100644 index 00000000000..0d9c8cce7be --- /dev/null +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import * as InlineChatActions from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; +import { IInlineChatService, INLINE_CHAT_ID, INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl'; +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 { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { InlineChatSavingServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl'; +import { InlineChatAccessibleViewContribution } from 'vs/workbench/contrib/inlineChat/browser/inlineChatAccessibleView'; +import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingService'; +import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; +import { InlineChatSessionServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; + + +// --- browser + +registerSingleton(IInlineChatService, InlineChatServiceImpl, InstantiationType.Delayed); +registerSingleton(IInlineChatSessionService, InlineChatSessionServiceImpl, InstantiationType.Delayed); +registerSingleton(IInlineChatSavingService, InlineChatSavingServiceImpl, InstantiationType.Delayed); + +registerEditorContribution(INLINE_CHAT_ID, InlineChatController, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors +registerEditorContribution(INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID, InlineChatActions.InlineAccessibilityHelpContribution, EditorContributionInstantiation.Eventually); + +registerAction2(InlineChatActions.CloseAction); +registerAction2(InlineChatActions.ConfigureInlineChatAction); +// registerAction2(InlineChatActions.UnstashSessionAction); +registerAction2(InlineChatActions.MakeRequestAction); +registerAction2(InlineChatActions.StopRequestAction); +registerAction2(InlineChatActions.ReRunRequestAction); +registerAction2(InlineChatActions.DiscardHunkAction); +registerAction2(InlineChatActions.DiscardAction); +registerAction2(InlineChatActions.DiscardToClipboardAction); +registerAction2(InlineChatActions.DiscardUndoToNewFileAction); +registerAction2(InlineChatActions.CancelSessionAction); + +registerAction2(InlineChatActions.ArrowOutUpAction); +registerAction2(InlineChatActions.ArrowOutDownAction); +registerAction2(InlineChatActions.FocusInlineChat); +registerAction2(InlineChatActions.PreviousFromHistory); +registerAction2(InlineChatActions.NextFromHistory); +registerAction2(InlineChatActions.ViewInChatAction); +registerAction2(InlineChatActions.ExpandMessageAction); +registerAction2(InlineChatActions.ContractMessageAction); + +registerAction2(InlineChatActions.ToggleDiffForChange); +registerAction2(InlineChatActions.FeebackHelpfulCommand); +registerAction2(InlineChatActions.FeebackUnhelpfulCommand); +registerAction2(InlineChatActions.ReportIssueForBugCommand); +registerAction2(InlineChatActions.AcceptChanges); + +registerAction2(InlineChatActions.CopyRecordings); + +const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(InlineChatNotebookContribution, LifecyclePhase.Restored); +workbenchContributionsRegistry.registerWorkbenchContribution(InlineChatAccessibleViewContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts index 33942d1fea4..d5233317f55 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts @@ -6,67 +6,15 @@ import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { CancelAction, InlineChatQuickVoice, StartAction, StopAction } from 'vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice'; -import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import * as InlineChatActions from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; import * as StartSessionAction from './inlineChatActions'; -import { IInlineChatService, INLINE_CHAT_ID, INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl'; -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 { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { InlineChatSavingServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl'; -import { InlineChatAccessibleViewContribution } from 'vs/workbench/contrib/inlineChat/browser/inlineChatAccessibleView'; -import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingService'; -import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; -import { InlineChatSessionServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; -// --- electron-browser +// start and hold for voice + +registerAction2(StartSessionAction.StartSessionAction); + +// quick voice + registerEditorContribution(InlineChatQuickVoice.ID, InlineChatQuickVoice, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors registerAction2(StartAction); registerAction2(StopAction); registerAction2(CancelAction); - -// --- browser - -registerSingleton(IInlineChatService, InlineChatServiceImpl, InstantiationType.Delayed); -registerSingleton(IInlineChatSessionService, InlineChatSessionServiceImpl, InstantiationType.Delayed); -registerSingleton(IInlineChatSavingService, InlineChatSavingServiceImpl, InstantiationType.Delayed); - -registerEditorContribution(INLINE_CHAT_ID, InlineChatController, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors -registerEditorContribution(INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID, InlineChatActions.InlineAccessibilityHelpContribution, EditorContributionInstantiation.Eventually); - -registerAction2(StartSessionAction.StartSessionAction); -registerAction2(InlineChatActions.CloseAction); -registerAction2(InlineChatActions.ConfigureInlineChatAction); -// registerAction2(InlineChatActions.UnstashSessionAction); -registerAction2(InlineChatActions.MakeRequestAction); -registerAction2(InlineChatActions.StopRequestAction); -registerAction2(InlineChatActions.ReRunRequestAction); -registerAction2(InlineChatActions.DiscardHunkAction); -registerAction2(InlineChatActions.DiscardAction); -registerAction2(InlineChatActions.DiscardToClipboardAction); -registerAction2(InlineChatActions.DiscardUndoToNewFileAction); -registerAction2(InlineChatActions.CancelSessionAction); - -registerAction2(InlineChatActions.ArrowOutUpAction); -registerAction2(InlineChatActions.ArrowOutDownAction); -registerAction2(InlineChatActions.FocusInlineChat); -registerAction2(InlineChatActions.PreviousFromHistory); -registerAction2(InlineChatActions.NextFromHistory); -registerAction2(InlineChatActions.ViewInChatAction); -registerAction2(InlineChatActions.ExpandMessageAction); -registerAction2(InlineChatActions.ContractMessageAction); - -registerAction2(InlineChatActions.ToggleDiffForChange); -registerAction2(InlineChatActions.FeebackHelpfulCommand); -registerAction2(InlineChatActions.FeebackUnhelpfulCommand); -registerAction2(InlineChatActions.ReportIssueForBugCommand); -registerAction2(InlineChatActions.AcceptChanges); - -registerAction2(InlineChatActions.CopyRecordings); - -const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(InlineChatNotebookContribution, LifecyclePhase.Restored); -workbenchContributionsRegistry.registerWorkbenchContribution(InlineChatAccessibleViewContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index ae453cf83d4..bcad16fc802 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -182,6 +182,7 @@ import 'vs/workbench/contrib/speech/common/speech.contribution'; // Chat import 'vs/workbench/contrib/chat/browser/chat.contribution'; +import 'vs/workbench/contrib/inlineChat/browser/inlineChat.contribution'; // Interactive import 'vs/workbench/contrib/interactive/browser/interactive.contribution'; From 6ec5b10065ea3fb689585a303e218ed6e2374953 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 17 Jan 2024 17:04:24 +0530 Subject: [PATCH 0372/1897] fix #155495 (#202662) --- src/vs/base/browser/indexedDB.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/indexedDB.ts b/src/vs/base/browser/indexedDB.ts index 38be907918e..6d56022c98c 100644 --- a/src/vs/base/browser/indexedDB.ts +++ b/src/vs/base/browser/indexedDB.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { getErrorMessage } from 'vs/base/common/errors'; +import { ErrorNoTelemetry, getErrorMessage } from 'vs/base/common/errors'; import { mark } from 'vs/base/common/performance'; class MissingStoresError extends Error { @@ -125,8 +125,8 @@ export class IndexedDB { c(request.result); } }; - transaction.onerror = () => e(transaction.error); - transaction.onabort = () => e(transaction.error); + transaction.onerror = () => e(transaction.error ? ErrorNoTelemetry.fromError(transaction.error) : new ErrorNoTelemetry('unknown error')); + transaction.onabort = () => e(transaction.error ? ErrorNoTelemetry.fromError(transaction.error) : new ErrorNoTelemetry('unknown error')); const request = dbRequestFn(transaction.objectStore(store)); }).finally(() => this.pendingTransactions.splice(this.pendingTransactions.indexOf(transaction), 1)); } From a3f93ac6798cb48e24d5fd6e1981e0b9e99112e6 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 17 Jan 2024 13:02:02 +0100 Subject: [PATCH 0373/1897] checking the variable _rebuildFromLine is undefined before doing early return --- .../editor/contrib/stickyScroll/browser/stickyScrollWidget.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts index f3b0974709c..72cec626e86 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts @@ -127,7 +127,9 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { } setState(_state: StickyScrollWidgetState | undefined, foldingModel: FoldingModel | null, _rebuildFromLine?: number): void { - if ((!this._previousState && !_state) || (this._previousState && this._previousState.equals(_state))) { + if (typeof _rebuildFromLine === 'undefined' && + ((!this._previousState && !_state) || (this._previousState && this._previousState.equals(_state))) + ) { return; } const isWidgetHeightZero = this._isWidgetHeightZero(_state); From 112b74e32b441b6a9c57a548107e3bb35142ad04 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 17 Jan 2024 13:09:19 +0100 Subject: [PATCH 0374/1897] checking that _rebuildFromLine is undefined in the early return --- .../editor/contrib/stickyScroll/browser/stickyScrollWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts index 72cec626e86..06fb2ce428f 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts @@ -127,7 +127,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { } setState(_state: StickyScrollWidgetState | undefined, foldingModel: FoldingModel | null, _rebuildFromLine?: number): void { - if (typeof _rebuildFromLine === 'undefined' && + if (_rebuildFromLine === undefined && ((!this._previousState && !_state) || (this._previousState && this._previousState.equals(_state))) ) { return; From 99c4d2f6745d5673372831f0d911388a776b7324 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Wed, 17 Jan 2024 14:09:59 +0100 Subject: [PATCH 0375/1897] Updated layout settings, context menu service, and title bar configurations in various parts of the application. --- .../platform/menubar/electron-main/menubar.ts | 6 +- src/vs/platform/window/common/window.ts | 28 ++++----- .../windows/electron-main/windowImpl.ts | 4 +- .../platform/windows/electron-main/windows.ts | 4 +- .../browser/actions/layoutActions.ts | 2 +- src/vs/workbench/browser/layout.ts | 48 ++++++++++++--- .../parts/activitybar/activitybarPart.ts | 6 +- .../parts/editor/auxiliaryEditorPart.ts | 4 +- .../parts/editor/editor.contribution.ts | 4 +- .../browser/parts/titlebar/menubarControl.ts | 4 +- .../browser/parts/titlebar/titlebarActions.ts | 59 +++++++++++++++++-- .../browser/parts/titlebar/titlebarPart.ts | 20 +++---- .../browser/workbench.contribution.ts | 6 +- .../browser/relauncher.contribution.ts | 8 +-- .../electron-sandbox/desktop.contribution.ts | 11 +++- .../parts/titlebar/titlebarPart.ts | 10 ++-- src/vs/workbench/electron-sandbox/window.ts | 6 +- .../electron-sandbox/contextmenuService.ts | 4 +- .../services/layout/browser/layoutService.ts | 7 +++ 19 files changed, 160 insertions(+), 81 deletions(-) diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 6c9e3fcffda..ec33007b544 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -22,7 +22,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IStateService } from 'vs/platform/state/node/state'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUpdateService, StateType } from 'vs/platform/update/common/update'; -import { INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, IWindowOpenable, showNativeTitlebar } from 'vs/platform/window/common/window'; +import { INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, IWindowOpenable, hasNativeTitlebar } from 'vs/platform/window/common/window'; import { IWindowsCountChangedEvent, IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; @@ -85,7 +85,7 @@ export class Menubar { this.menubarMenus = Object.create(null); this.keybindings = Object.create(null); - if (isMacintosh || showNativeTitlebar(configurationService)) { + if (isMacintosh || hasNativeTitlebar(configurationService)) { this.restoreCachedMenubarData(); } @@ -469,7 +469,7 @@ export class Menubar { private shouldDrawMenu(menuId: string): boolean { // We need to draw an empty menu to override the electron default - if (!isMacintosh && !showNativeTitlebar(this.configurationService)) { + if (!isMacintosh && !hasNativeTitlebar(this.configurationService)) { return false; } diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index a4514efd126..0a55eaa67e4 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -168,27 +168,19 @@ export interface IDensitySettings { export const enum TitlebarStyle { NATIVE = 'native', CUSTOM = 'custom', - NATIVE_AND_CUSTOM = 'native-and-custom' } -export function showCustomTitlebar(configurationServiceOrTitleStyle: IConfigurationService | TitlebarStyle): boolean { - let titleBarStyle: TitlebarStyle; - if (typeof configurationServiceOrTitleStyle === 'string') { - titleBarStyle = configurationServiceOrTitleStyle; - } else { - titleBarStyle = getTitleBarStyle(configurationServiceOrTitleStyle); - } - return titleBarStyle !== TitlebarStyle.NATIVE; +export function hasCustomTitlebar(configurationService: IConfigurationService, titleBarStyle?: TitlebarStyle): boolean { + // Does not imply that the title bar is visible + + return true; } -export function showNativeTitlebar(configurationServiceOrTitleStyle: IConfigurationService | TitlebarStyle): boolean { - let titleBarStyle: TitlebarStyle; - if (typeof configurationServiceOrTitleStyle === 'string') { - titleBarStyle = configurationServiceOrTitleStyle; - } else { - titleBarStyle = getTitleBarStyle(configurationServiceOrTitleStyle); +export function hasNativeTitlebar(configurationService: IConfigurationService, titleBarStyle?: TitlebarStyle): boolean { + if (!titleBarStyle) { + titleBarStyle = getTitleBarStyle(configurationService); } - return titleBarStyle !== TitlebarStyle.CUSTOM; + return titleBarStyle === TitlebarStyle.NATIVE; } export function getTitleBarStyle(configurationService: IConfigurationService): TitlebarStyle { @@ -209,7 +201,7 @@ export function getTitleBarStyle(configurationService: IConfigurationService): T } const style = configuration.titleBarStyle; - if (style === TitlebarStyle.NATIVE || style === TitlebarStyle.CUSTOM || style === TitlebarStyle.NATIVE_AND_CUSTOM) { + if (style === TitlebarStyle.NATIVE || style === TitlebarStyle.CUSTOM) { return style; } } @@ -222,7 +214,7 @@ export function useWindowControlsOverlay(configurationService: IConfigurationSer return false; // only supported on a desktop Windows instance } - if (showNativeTitlebar(configurationService)) { + if (hasNativeTitlebar(configurationService)) { return false; // only supported when title bar is custom } diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 43ce7c4f4ac..c0ca43e257b 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -32,7 +32,7 @@ import { IApplicationStorageMainService, IStorageMainService } from 'vs/platform import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ThemeIcon } from 'vs/base/common/themables'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; -import { getMenuBarVisibility, IFolderToOpen, INativeWindowConfiguration, IWindowSettings, IWorkspaceToOpen, MenuBarVisibility, showNativeTitlebar, useNativeFullScreen, useWindowControlsOverlay } from 'vs/platform/window/common/window'; +import { getMenuBarVisibility, IFolderToOpen, INativeWindowConfiguration, IWindowSettings, IWorkspaceToOpen, MenuBarVisibility, hasNativeTitlebar, useNativeFullScreen, useWindowControlsOverlay } from 'vs/platform/window/common/window'; import { defaultBrowserWindowOptions, IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; @@ -131,7 +131,7 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { this._register(Event.fromNodeEventEmitter(this._win, 'leave-full-screen')(() => this._onDidLeaveFullScreen.fire())); // Sheet Offsets - const useCustomTitleStyle = !showNativeTitlebar(this.configurationService); + const useCustomTitleStyle = !hasNativeTitlebar(this.configurationService); if (isMacintosh && useCustomTitleStyle) { win.setSheetOffset(isBigSurOrNewer(release()) ? 28 : 22); // offset dialogs by the height of the custom title bar if we have any } diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index e667ba6f39d..d4aac4b6a65 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ICodeWindow, IWindowState } from 'vs/platform/window/electron-main/window'; -import { IOpenEmptyWindowOptions, IWindowOpenable, IWindowSettings, WindowMinimumSize, showNativeTitlebar, useNativeFullScreen, useWindowControlsOverlay, zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; +import { IOpenEmptyWindowOptions, IWindowOpenable, IWindowSettings, WindowMinimumSize, hasNativeTitlebar, useNativeFullScreen, useWindowControlsOverlay, zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { IProductService } from 'vs/platform/product/common/productService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -173,7 +173,7 @@ export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowSt options.tabbingIdentifier = productService.nameShort; // this opts in to sierra tabs } - const useCustomTitleStyle = !showNativeTitlebar(configurationService); + const useCustomTitleStyle = !hasNativeTitlebar(configurationService); if (useCustomTitleStyle) { options.titleBarStyle = 'hidden'; if (!isMacintosh) { diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 0727a5a0889..06f1875ec00 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -802,7 +802,7 @@ if (isWindows || isLinux || isWeb) { title: localize('miMenuBarNoMnemonic', "Menu Bar"), toggled: ContextKeyExpr.and(IsMacNativeContext.toNegated(), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'hidden'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'toggle'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'compact')) }, - when: IsAuxiliaryWindowFocusedContext.toNegated(), + when: ContextKeyExpr.and(IsAuxiliaryWindowFocusedContext.toNegated(), ContextKeyExpr.notEquals(`config.window.titleBarStyle`, 'native')), group: menuId === MenuId.TitleBarTitleContext ? '2_config' : undefined, order: 0 }); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index a36068ea2d8..f276016e8be 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -12,14 +12,14 @@ import { isWindows, isLinux, isMacintosh, isWeb, isNative, isIOS } from 'vs/base import { EditorInputCapabilities, GroupIdentifier, isResourceEditorInput, IUntypedEditorInput, pathsToEditors } from 'vs/workbench/common/editor'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; -import { Position, Parts, PanelOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, panelOpensMaximizedFromString, PanelAlignment, ActivityBarPosition, LayoutSettings, MULTI_WINDOW_PARTS, SINGLE_WINDOW_PARTS, ZenModeSettings, EditorTabsMode } from 'vs/workbench/services/layout/browser/layoutService'; +import { Position, Parts, PanelOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, panelOpensMaximizedFromString, PanelAlignment, ActivityBarPosition, LayoutSettings, MULTI_WINDOW_PARTS, SINGLE_WINDOW_PARTS, ZenModeSettings, EditorTabsMode, EditorActionsLocation } from 'vs/workbench/services/layout/browser/layoutService'; import { isTemporaryWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { StartupKind, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { getMenuBarVisibility, IPath, showNativeTitlebar, showCustomTitlebar } from 'vs/platform/window/common/window'; +import { getMenuBarVisibility, IPath, hasNativeTitlebar, hasCustomTitlebar } from 'vs/platform/window/common/window'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -345,10 +345,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if ([ LayoutSettings.ACTIVITY_BAR_LOCATION, LayoutSettings.COMMAND_CENTER, + LayoutSettings.EDITOR_ACTIONS_LOCATION, LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION, LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE, 'window.menuBarVisibility', 'window.titleBarStyle', + 'window.showCustomToolBar', ].some(setting => e.affectsConfiguration(setting))) { this.doUpdateLayoutConfiguration(); } @@ -366,7 +368,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._register(addDisposableListener(this.mainContainer, EventType.SCROLL, () => this.mainContainer.scrollTop = 0)); // Menubar visibility changes - if ((isWindows || isLinux || isWeb) && !showNativeTitlebar(this.configurationService)) { + if ((isWindows || isLinux || isWeb) && !hasNativeTitlebar(this.configurationService)) { this._register(this.titleService.onMenubarVisibilityChange(visible => this.onMenubarToggled(visible))); } @@ -453,7 +455,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Changing fullscreen state of the main window has an impact // on custom title bar visibility, so we need to update - if (showCustomTitlebar(this.configurationService)) { + if (hasCustomTitlebar(this.configurationService)) { // Propagate to grid this.workbenchGrid.setViewVisible(this.titleBarPartView, this.shouldShowTitleBar()); @@ -489,6 +491,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private doUpdateLayoutConfiguration(skipLayout?: boolean): void { + // Custom Titlebar visibility with native titlebar + this.updateCustomTitleBarVisibility(); + // Menubar visibility this.updateMenubarVisibility(!!skipLayout); @@ -535,7 +540,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if ( isWeb || isWindows || // not working well with zooming and window control overlays - showNativeTitlebar(this.configurationService) + hasNativeTitlebar(this.configurationService) ) { return; } @@ -1219,16 +1224,35 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private shouldShowTitleBar(): boolean { - // Using the native title bar, don't ever show the custom one - if (!showCustomTitlebar(this.configurationService)) { + if (!hasCustomTitlebar(this.configurationService)) { return false; } + const showCustomToolBar = this.configurationService.getValue('window.showCustomToolBar'); + if (showCustomToolBar === 'never' || showCustomToolBar === 'windowed' && this.state.runtime.mainWindowFullscreen) { + return false; + } + + // with the command center enabled, we should always show + if (this.configurationService.getValue(LayoutSettings.COMMAND_CENTER)) { + return true; + } + // with the activity bar on top, we should always show if (this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP) { return true; } + // with the editor actions on top, we should always show + if (this.configurationService.getValue(LayoutSettings.EDITOR_ACTIONS_LOCATION) === EditorActionsLocation.TITLEBAR) { + return true; + } + + // Hide custom title bar when native title bar and custom title bar is empty + if (hasNativeTitlebar(this.configurationService)) { + return false; + } + // macOS desktop does not need a title bar when full screen if (isMacintosh && isNative) { return !this.state.runtime.mainWindowFullscreen; @@ -2055,6 +2079,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } + updateCustomTitleBarVisibility(): void { + const shouldShowTitleBar = this.shouldShowTitleBar(); + const titlebarVisible = this.isVisible(Parts.TITLEBAR_PART); + if (shouldShowTitleBar !== titlebarVisible) { + this.workbenchGrid.setViewVisible(this.titleBarPartView, shouldShowTitleBar); + } + } + toggleMenuBar(): void { let currentVisibilityValue = getMenuBarVisibility(this.configurationService); if (typeof currentVisibilityValue !== 'string') { @@ -2063,7 +2095,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi let newVisibilityValue: string; if (currentVisibilityValue === 'visible' || currentVisibilityValue === 'classic') { - newVisibilityValue = showNativeTitlebar(this.configurationService) ? 'toggle' : 'compact'; + newVisibilityValue = hasNativeTitlebar(this.configurationService) ? 'toggle' : 'compact'; } else { newVisibilityValue = 'classic'; } diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index f3c9c66960a..30cd016ad19 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -35,7 +35,6 @@ import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IViewDescriptorService, ViewContainerLocation, ViewContainerLocationToString } from 'vs/workbench/common/views'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; -import { TitleBarStyleContext } from 'vs/workbench/common/contextkeys'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -387,7 +386,7 @@ registerAction2(class extends Action2 { }, shortTitle: localize('side', "Side"), category: Categories.View, - toggled: ContextKeyExpr.or(ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.SIDE), ContextKeyExpr.and(ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP), TitleBarStyleContext.isEqualTo('native'))), + toggled: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.SIDE), menu: [{ id: MenuId.ActivityBarPositionMenu, order: 1 @@ -417,11 +416,10 @@ registerAction2(class extends Action2 { toggled: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP), menu: [{ id: MenuId.ActivityBarPositionMenu, - when: TitleBarStyleContext.notEqualsTo('native'), order: 2 }, { id: MenuId.CommandPalette, - when: ContextKeyExpr.and(ContextKeyExpr.notEquals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP), TitleBarStyleContext.notEqualsTo('native')), + when: ContextKeyExpr.notEquals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP), }] }); } diff --git a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts index 3b94637bd57..c941138a188 100644 --- a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts @@ -14,7 +14,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { showCustomTitlebar } from 'vs/platform/window/common/window'; +import { hasCustomTitlebar } from 'vs/platform/window/common/window'; import { IEditorGroupView, IEditorPartsView } from 'vs/workbench/browser/parts/editor/editor'; import { EditorPart, IEditorPartUIState } from 'vs/workbench/browser/parts/editor/editorPart'; import { IAuxiliaryTitlebarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; @@ -107,7 +107,7 @@ export class AuxiliaryEditorPart { // Titlebar let titlebarPart: IAuxiliaryTitlebarPart | undefined = undefined; let titlebarPartVisible = false; - const useCustomTitle = isNative && showCustomTitlebar(this.configurationService); // custom title in aux windows only enabled in native + const useCustomTitle = isNative && hasCustomTitlebar(this.configurationService); // custom title in aux windows only enabled in native if (useCustomTitle) { titlebarPart = disposables.add(this.titleService.createAuxiliaryTitlebarPart(auxiliaryWindow.container, editorPart)); titlebarPartVisible = true; diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 7081081e504..014acd8d37a 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -381,8 +381,8 @@ MenuRegistry.appendMenuItem(MenuId.EditorTabsBarShowTabsZenModeSubmenu, { comman MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { submenu: MenuId.EditorActionsPositionSubmenu, title: localize('editorActionsPosition', "Editor Actions Position"), group: '4_config', order: 20 }); MenuRegistry.appendMenuItem(MenuId.EditorActionsPositionSubmenu, { command: { id: EditorActionsDefaultAction.ID, title: localize('tabBar', "Tab Bar"), toggled: ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'default') }, group: '1_config', order: 10, when: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'none').negate() }); -MenuRegistry.appendMenuItem(MenuId.EditorActionsPositionSubmenu, { command: { id: EditorActionsTitleBarAction.ID, title: localize('titleBar', "Title Bar"), toggled: ContextKeyExpr.or(ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'titleBar'), ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.editor.showTabs', 'none'), ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'default'))) }, group: '1_config', order: 20, when: ContextKeyExpr.equals('config.window.titleBarStyle', 'native').negate() }); -MenuRegistry.appendMenuItem(MenuId.EditorActionsPositionSubmenu, { command: { id: HideEditorActionsAction.ID, title: localize('hidden', "Hidden"), toggled: ContextKeyExpr.or(ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'hidden'), ContextKeyExpr.and(ContextKeyExpr.equals('config.window.titleBarStyle', 'native'), ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'titleBar'))) }, group: '1_config', order: 30 }); +MenuRegistry.appendMenuItem(MenuId.EditorActionsPositionSubmenu, { command: { id: EditorActionsTitleBarAction.ID, title: localize('titleBar', "Title Bar"), toggled: ContextKeyExpr.or(ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'titleBar'), ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.editor.showTabs', 'none'), ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'default'))) }, group: '1_config', order: 20 }); +MenuRegistry.appendMenuItem(MenuId.EditorActionsPositionSubmenu, { command: { id: HideEditorActionsAction.ID, title: localize('hidden', "Hidden"), toggled: ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'hidden') }, group: '1_config', order: 30 }); // Editor Title Context Menu MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_EDITOR_COMMAND_ID, title: localize('close', "Close") }, group: '1_close', order: 10 }); diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 43481ae2ad4..2923bcc75bb 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/menubarControl'; import { localize, localize2 } from 'vs/nls'; import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action2, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; -import { MenuBarVisibility, IWindowOpenable, getMenuBarVisibility, showNativeTitlebar } from 'vs/platform/window/common/window'; +import { MenuBarVisibility, IWindowOpenable, getMenuBarVisibility, hasNativeTitlebar } from 'vs/platform/window/common/window'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAction, Action, SubmenuAction, Separator, IActionRunner, ActionRunner, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, toAction } from 'vs/base/common/actions'; import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom'; @@ -351,7 +351,7 @@ export abstract class MenubarControl extends Disposable { } const hasBeenNotified = this.storageService.getBoolean('menubar/accessibleMenubarNotified', StorageScope.APPLICATION, false); - const usingCustomMenubar = !showNativeTitlebar(this.configurationService); + const usingCustomMenubar = !hasNativeTitlebar(this.configurationService); if (hasBeenNotified || usingCustomMenubar || !this.accessibilityService.isScreenReaderOptimized()) { return; diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index 303accb164b..d1fa8f783c1 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -14,9 +14,14 @@ import { ACCOUNTS_ACTIVITY_ID, GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/ac import { IAction } from 'vs/base/common/actions'; import { IsAuxiliaryWindowFocusedContext } from 'vs/workbench/common/contextkeys'; +// --- Context Menu Actions --- // + class ToggleConfigAction extends Action2 { - constructor(private readonly section: string, title: string, order: number, mainWindowOnly: boolean) { + constructor(private readonly section: string, title: string, order: number, mainWindowOnly: boolean, showInCustomToolBar: boolean) { + let when = mainWindowOnly ? IsAuxiliaryWindowFocusedContext.toNegated() : ContextKeyExpr.true(); + when = showInCustomToolBar ? when : ContextKeyExpr.and(when, ContextKeyExpr.equals(`config.window.titleBarStyle`, 'native').negate())!; + super({ id: `toggle.${section}`, title, @@ -24,12 +29,12 @@ class ToggleConfigAction extends Action2 { menu: [ { id: MenuId.TitleBarContext, - when: mainWindowOnly ? IsAuxiliaryWindowFocusedContext.toNegated() : undefined, + when, order }, { id: MenuId.TitleBarTitleContext, - when: mainWindowOnly ? IsAuxiliaryWindowFocusedContext.toNegated() : undefined, + when, order, group: '2_config' } @@ -46,13 +51,33 @@ class ToggleConfigAction extends Action2 { registerAction2(class ToggleCommandCenter extends ToggleConfigAction { constructor() { - super(LayoutSettings.COMMAND_CENTER, localize('toggle.commandCenter', 'Command Center'), 1, false); + super(LayoutSettings.COMMAND_CENTER, localize('toggle.commandCenter', 'Command Center'), 1, false, true); } }); registerAction2(class ToggleLayoutControl extends ToggleConfigAction { constructor() { - super('workbench.layoutControl.enabled', localize('toggle.layout', 'Layout Controls'), 2, true); + super('workbench.layoutControl.enabled', localize('toggle.layout', 'Layout Controls'), 2, true, false); + } +}); + +registerAction2(class ToggleCustomToolBar extends Action2 { + static readonly settingsID = `window.showCustomToolBar`; + + constructor() { + super({ + id: `toggle.${ToggleCustomToolBar.settingsID}`, + title: localize('toggle.toolBar', 'Custom Title Bar'), + toggled: ContextKeyExpr.true(), + menu: [ + { id: MenuId.TitleBarContext, order: 0, when: ContextKeyExpr.equals(`config.window.titleBarStyle`, 'native'), group: '1_toggle' }, + ] + }); + } + + run(accessor: ServicesAccessor, ...args: any[]): void { + const configService = accessor.get(IConfigurationService); + configService.updateValue(ToggleCustomToolBar.settingsID, 'never'); } }); @@ -105,6 +130,30 @@ registerAction2(class ToggleEditorActions extends Action2 { } }); +registerAction2(class ToggleActivityBarActions extends Action2 { + static readonly settingsID = `workbench.activityBar.location`; + constructor() { + + super({ + id: `toggle.${ToggleActivityBarActions.settingsID}`, + title: localize('toggle.activityBarActions', 'Activity Bar Actions'), + toggled: ContextKeyExpr.equals(`config.${ToggleActivityBarActions.settingsID}`, 'top'), + menu: [ + { id: MenuId.TitleBarContext, order: 4, when: ContextKeyExpr.notEquals(`config.${ToggleActivityBarActions.settingsID}`, 'side') }, + { id: MenuId.TitleBarTitleContext, order: 4, when: ContextKeyExpr.notEquals(`config.${ToggleActivityBarActions.settingsID}`, 'side'), group: '2_config' } + ] + }); + } + + run(accessor: ServicesAccessor, ...args: any[]): void { + const configService = accessor.get(IConfigurationService); + const oldLocation = configService.getValue(ToggleActivityBarActions.settingsID); + configService.updateValue(ToggleActivityBarActions.settingsID, oldLocation === 'top' ? 'hidden' : 'top'); + } +}); + +// --- Toolbar actions --- // + export const ACCOUNTS_ACTIVITY_TILE_ACTION: IAction = { id: ACCOUNTS_ACTIVITY_ID, label: localize('accounts', "Accounts"), diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 1d5fb04ab8e..9ffdb87f917 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -8,7 +8,7 @@ import { localize, localize2 } from 'vs/nls'; import { MultiWindowParts, Part } from 'vs/workbench/browser/part'; import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; import { getZoomFactor, isWCOEnabled } from 'vs/base/browser/browser'; -import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, TitlebarStyle, showCustomTitlebar, showNativeTitlebar } from 'vs/platform/window/common/window'; +import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, TitlebarStyle, hasCustomTitlebar, hasNativeTitlebar } from 'vs/platform/window/common/window'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; @@ -299,7 +299,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { oldPartOptions.editorActionsLocation !== newPartOptions.editorActionsLocation || oldPartOptions.showTabs !== newPartOptions.showTabs ) { - if (showCustomTitlebar(this.titleBarStyle) && this.actionToolBar) { + if (hasCustomTitlebar(this.configurationService, this.titleBarStyle) && this.actionToolBar) { this.createActionToolBar(); this.createActionToolBarMenus({ editorActions: true }); this._onDidChange.fire(undefined); @@ -310,7 +310,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { protected onConfigurationChanged(event: IConfigurationChangeEvent): void { // Custom menu bar (disabled if auxiliary) - if (!this.isAuxiliary && !showNativeTitlebar(this.titleBarStyle) && (!isMacintosh || isWeb)) { + if (!this.isAuxiliary && !hasNativeTitlebar(this.configurationService, this.titleBarStyle) && (!isMacintosh || isWeb)) { if (event.affectsConfiguration('window.menuBarVisibility')) { if (this.currentMenubarVisibility === 'compact') { this.uninstallMenubar(); @@ -321,7 +321,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } // Actions - if (showCustomTitlebar(this.titleBarStyle) && this.actionToolBar) { + if (hasCustomTitlebar(this.configurationService, this.titleBarStyle) && this.actionToolBar) { const affectsLayoutControl = event.affectsConfiguration('workbench.layoutControl.enabled'); const affectsActivityControl = event.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION); @@ -388,7 +388,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.rightContent = append(this.rootContainer, $('.titlebar-right')); // App Icon (Native Windows/Linux and Web) - if (!isMacintosh && !isWeb && !showNativeTitlebar(this.titleBarStyle)) { + if (!isMacintosh && !isWeb && !hasNativeTitlebar(this.configurationService, this.titleBarStyle)) { this.appIcon = prepend(this.leftContent, $('a.window-appicon')); // Web-only home indicator and menu (not for auxiliary windows) @@ -412,7 +412,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { // Menubar: install a custom menu bar depending on configuration if ( !this.isAuxiliary && - !showNativeTitlebar(this.titleBarStyle) && + !hasNativeTitlebar(this.configurationService, this.titleBarStyle) && (!isMacintosh || isWeb) && this.currentMenubarVisibility !== 'compact' ) { @@ -424,7 +424,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.createTitle(); // Create Toolbar Actions - if (showCustomTitlebar(this.titleBarStyle)) { + if (hasCustomTitlebar(this.configurationService, this.titleBarStyle)) { this.actionToolBarElement = append(this.rightContent, $('div.action-toolbar-container')); this.createActionToolBar(); this.createActionToolBarMenus(); @@ -442,7 +442,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } } - if (!showNativeTitlebar(this.titleBarStyle)) { + if (!hasNativeTitlebar(this.configurationService, this.titleBarStyle)) { this.primaryWindowControls = append(primaryControlLocation === 'left' ? this.leftContent : this.rightContent, $('div.window-controls-container.primary')); append(primaryControlLocation === 'left' ? this.rightContent : this.leftContent, $('div.window-controls-container.secondary')); } @@ -695,7 +695,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } private get layoutControlEnabled(): boolean { - return !this.isAuxiliary && this.configurationService.getValue('workbench.layoutControl.enabled') !== false; + return !this.isAuxiliary && this.configurationService.getValue('workbench.layoutControl.enabled') !== false && !hasNativeTitlebar(this.configurationService, this.titleBarStyle); } protected get isCommandCenterVisible() { @@ -738,7 +738,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { private updateLayout(dimension: Dimension): void { this.lastLayoutDimensions = dimension; - if (showCustomTitlebar(this.titleBarStyle)) { + if (hasCustomTitlebar(this.configurationService, this.titleBarStyle)) { const zoomFactor = getZoomFactor(getWindow(this.element)); this.element.style.setProperty('--zoom-factor', zoomFactor.toString()); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 9fa670968d9..869b35f9572 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -11,7 +11,7 @@ import { ConfigurationMigrationWorkbenchContribution, DynamicWorkbenchConfigurat import { isStandalone } from 'vs/base/browser/browser'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { ActivityBarPosition, EditorTabsMode, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; +import { ActivityBarPosition, EditorActionsLocation, EditorTabsMode, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; const registry = Registry.as(ConfigurationExtensions.Configuration); @@ -49,9 +49,9 @@ const registry = Registry.as(ConfigurationExtensions.Con 'description': localize('showEditorTabs', "Controls whether opened editors should show as individual tabs, one single large tab or if the title area should not be shown."), 'default': 'multiple' }, - 'workbench.editor.editorActionsLocation': { + [LayoutSettings.EDITOR_ACTIONS_LOCATION]: { 'type': 'string', - 'enum': ['default', 'titleBar', 'hidden'], + 'enum': [EditorActionsLocation.DEFAULT, EditorActionsLocation.TITLEBAR, EditorActionsLocation.HIDDEN], 'enumDescriptions': [ localize({ comment: ['{0} will be a setting name rendered as a link'], key: 'workbench.editor.editorActionsLocation.default' }, "Show editor actions in the window title bar when {0} is set to {1}. Otherwise, editor actions are shown in the editor tab bar.", '`#workbench.editor.showTabs#`', '`none`'), localize({ comment: ['{0} will be a setting name rendered as a link'], key: 'workbench.editor.editorActionsLocation.titleBar' }, "Show editor actions in the window title bar. If {0} is set to {1}, editor actions are hidden.", '`#window.titleBarStyle#`', '`native`'), diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index a12266a279b..c3f413e8b09 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -47,7 +47,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo ]; private readonly titleBarStyle = new ChangeObserver('string'); - private previousTitleBarStyle: TitlebarStyle | undefined = undefined; private readonly nativeTabs = new ChangeObserver('boolean'); private readonly nativeFullScreen = new ChangeObserver('boolean'); private readonly clickThroughInactive = new ChangeObserver('boolean'); @@ -86,12 +85,7 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo if (isNative) { // Titlebar style - processChanged( - (config.window.titleBarStyle === TitlebarStyle.NATIVE || config.window.titleBarStyle === TitlebarStyle.CUSTOM || config.window.titleBarStyle === TitlebarStyle.NATIVE_AND_CUSTOM) && - this.titleBarStyle.handleChange(config.window.titleBarStyle) && - (this.previousTitleBarStyle === TitlebarStyle.CUSTOM || config.window.titleBarStyle === TitlebarStyle.CUSTOM) // only if we come from custom or go to custom - ); - this.previousTitleBarStyle = config.window.titleBarStyle; + processChanged((config.window.titleBarStyle === TitlebarStyle.NATIVE || config.window.titleBarStyle === TitlebarStyle.CUSTOM) && this.titleBarStyle.handleChange(config.window?.titleBarStyle)); // macOS: Native tabs processChanged(isMacintosh && this.nativeTabs.handleChange(config.window?.nativeTabs)); diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 33be5c4b2d9..9cf09c66a85 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -227,13 +227,20 @@ import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from 'scope': ConfigurationScope.APPLICATION, 'markdownDescription': localize('window.doubleClickIconToClose', "If enabled, this setting will close the window when the application icon in the title bar is double-clicked. The window will not be able to be dragged by the icon. This setting is effective only if `#window.titleBarStyle#` is set to `custom`.") }, - 'window.titleBarStyle': { // Todo + 'window.titleBarStyle': { 'type': 'string', - 'enum': ['native', 'custom', 'native-and-custom'], + 'enum': ['native', 'custom'], 'default': isLinux ? 'native' : 'custom', 'scope': ConfigurationScope.APPLICATION, 'description': localize('titleBarStyle', "Adjust the appearance of the window title bar to be native by the OS or custom. On Linux and Windows, this setting also affects the application and context menu appearances. Changes require a full restart to apply.") }, + 'window.showCustomToolBar': { + 'type': 'string', + 'enum': ['default', 'never', 'windowed'], + 'default': 'default', + 'scope': ConfigurationScope.APPLICATION, + 'description': localize('showCustomToolBar', "") // TODO@Ben + }, 'window.dialogStyle': { 'type': 'string', 'enum': ['native', 'custom'], diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index 8288e6999fe..fbf85313f6c 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -18,7 +18,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { INativeHostService } from 'vs/platform/native/common/native'; -import { showNativeTitlebar, useWindowControlsOverlay } from 'vs/platform/window/common/window'; +import { hasNativeTitlebar, useWindowControlsOverlay } from 'vs/platform/window/common/window'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; @@ -145,7 +145,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { const targetWindowId = getWindowId(targetWindow); // Native menu controller - if (isMacintosh || showNativeTitlebar(this.configurationService)) { + if (isMacintosh || hasNativeTitlebar(this.configurationService)) { this._register(this.instantiationService.createInstance(NativeMenubarControl)); } @@ -159,7 +159,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { } // Window Controls (Native Windows/Linux) - if (!isMacintosh && !showNativeTitlebar(this.configurationService) && !isWCOEnabled() && this.primaryWindowControls) { + if (!isMacintosh && !hasNativeTitlebar(this.configurationService) && !isWCOEnabled() && this.primaryWindowControls) { // Minimize const minimizeIcon = append(this.primaryWindowControls, $('div.window-icon.window-minimize' + ThemeIcon.asCSSSelector(Codicon.chromeMinimize))); @@ -195,7 +195,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { // Window System Context Menu // See https://github.com/electron/electron/issues/24893 - if (isWindows && !showNativeTitlebar(this.configurationService)) { + if (isWindows && !hasNativeTitlebar(this.configurationService)) { this._register(this.nativeHostService.onDidTriggerWindowSystemContextMenu(({ windowId, x, y }) => { if (targetWindowId !== windowId) { return; @@ -253,7 +253,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { if ( useWindowControlsOverlay(this.configurationService) || - (isMacintosh && isNative && !showNativeTitlebar(this.configurationService)) + (isMacintosh && isNative && !hasNativeTitlebar(this.configurationService)) ) { // When the user goes into full screen mode, the height of the title bar becomes 0. diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 1a9cbce221a..08ce756c0da 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -14,7 +14,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { EditorResourceAccessor, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors, IResourceDiffEditorInput, IUntypedEditorInput, IEditorPane, isResourceEditorInput, IResourceMergeEditorInput } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { WindowMinimumSize, IOpenFileRequest, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest, showNativeTitlebar } from 'vs/platform/window/common/window'; +import { WindowMinimumSize, IOpenFileRequest, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest, hasNativeTitlebar } from 'vs/platform/window/common/window'; import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ApplyZoomTarget, applyZoom } from 'vs/platform/window/electron-sandbox/window'; @@ -377,7 +377,7 @@ export class NativeWindow extends BaseWindow { } // Maximize/Restore on doubleclick (for macOS custom title) - if (isMacintosh && !showNativeTitlebar(this.configurationService)) { + if (isMacintosh && !hasNativeTitlebar(this.configurationService)) { this._register(Event.runAndSubscribe(this.layoutService.onDidAddContainer, ({ container, disposables }) => { const targetWindow = getWindow(container); const targetWindowId = targetWindow.vscodeWindowId; @@ -611,7 +611,7 @@ export class NativeWindow extends BaseWindow { this.customTitleContextMenuDisposable.clear(); // Provide new menu if a file is opened and we are on a custom title - if (!filePath || !showNativeTitlebar(this.configurationService)) { + if (!filePath || !hasNativeTitlebar(this.configurationService)) { return; } diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts index b0ca3f2175e..0ca133483ea 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts @@ -15,7 +15,7 @@ import { IContextMenuDelegate, IContextMenuEvent } from 'vs/base/browser/context import { createSingleCallFunction } from 'vs/base/common/functional'; import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu'; import { popup } from 'vs/base/parts/contextmenu/electron-sandbox/contextmenu'; -import { showNativeTitlebar } from 'vs/platform/window/common/window'; +import { hasNativeTitlebar } from 'vs/platform/window/common/window'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextMenuMenuDelegate, ContextMenuService as HTMLContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; @@ -48,7 +48,7 @@ export class ContextMenuService implements IContextMenuService { ) { // Custom context menu: Linux/Windows if custom title is enabled - if (!isMacintosh && !showNativeTitlebar(configurationService)) { + if (!isMacintosh && !hasNativeTitlebar(configurationService)) { this.impl = new HTMLContextMenuService(telemetryService, notificationService, contextViewService, keybindingService, menuService, contextKeyService); } diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 10ee5560632..c64f875ded0 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -37,6 +37,7 @@ export const enum ZenModeSettings { export const enum LayoutSettings { ACTIVITY_BAR_LOCATION = 'workbench.activityBar.location', EDITOR_TABS_MODE = 'workbench.editor.showTabs', + EDITOR_ACTIONS_LOCATION = 'workbench.editor.editorActionsLocation', COMMAND_CENTER = 'window.commandCenter', } @@ -52,6 +53,12 @@ export const enum EditorTabsMode { NONE = 'none' } +export const enum EditorActionsLocation { + DEFAULT = 'default', + TITLEBAR = 'titleBar', + HIDDEN = 'hidden' +} + export const enum Position { LEFT, RIGHT, From 25c484e173ab90522eeb3fadc59a2f612ad37c4f Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Wed, 17 Jan 2024 14:41:34 +0100 Subject: [PATCH 0376/1897] Updated titlebar actions grouping --- .../browser/parts/titlebar/titlebarActions.ts | 9 +++++---- .../workbench/browser/parts/titlebar/titlebarPart.ts | 10 +++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index d1fa8f783c1..6fdc5df5b09 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -30,7 +30,8 @@ class ToggleConfigAction extends Action2 { { id: MenuId.TitleBarContext, when, - order + order, + group: '2_config' }, { id: MenuId.TitleBarTitleContext, @@ -70,7 +71,7 @@ registerAction2(class ToggleCustomToolBar extends Action2 { title: localize('toggle.toolBar', 'Custom Title Bar'), toggled: ContextKeyExpr.true(), menu: [ - { id: MenuId.TitleBarContext, order: 0, when: ContextKeyExpr.equals(`config.window.titleBarStyle`, 'native'), group: '1_toggle' }, + { id: MenuId.TitleBarContext, order: 0, when: ContextKeyExpr.equals(`config.window.titleBarStyle`, 'native'), group: '3_toggle' }, ] }); } @@ -95,7 +96,7 @@ registerAction2(class ToggleEditorActions extends Action2 { title: localize('toggle.editorActions', 'Editor Actions'), toggled: ContextKeyExpr.equals(`config.${ToggleEditorActions.settingsID}`, 'hidden').negate(), menu: [ - { id: MenuId.TitleBarContext, order: 3, when: titleBarContextCondition }, + { id: MenuId.TitleBarContext, order: 3, when: titleBarContextCondition, group: '2_config' }, { id: MenuId.TitleBarTitleContext, order: 3, when: titleBarContextCondition, group: '2_config' } ] }); @@ -139,7 +140,7 @@ registerAction2(class ToggleActivityBarActions extends Action2 { title: localize('toggle.activityBarActions', 'Activity Bar Actions'), toggled: ContextKeyExpr.equals(`config.${ToggleActivityBarActions.settingsID}`, 'top'), menu: [ - { id: MenuId.TitleBarContext, order: 4, when: ContextKeyExpr.notEquals(`config.${ToggleActivityBarActions.settingsID}`, 'side') }, + { id: MenuId.TitleBarContext, order: 4, when: ContextKeyExpr.notEquals(`config.${ToggleActivityBarActions.settingsID}`, 'side'), group: '2_config' }, { id: MenuId.TitleBarTitleContext, order: 4, when: ContextKeyExpr.notEquals(`config.${ToggleActivityBarActions.settingsID}`, 'side'), group: '2_config' } ] }); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 9ffdb87f917..bb83d9199fc 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -487,10 +487,14 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { // Text Title if (!this.isCommandCenterVisible) { - this.title.innerText = this.windowTitle.value; - this.titleDisposables.add(this.windowTitle.onDidChange(() => { + if (this.titleBarStyle === 'custom') { + this.title.innerText = this.windowTitle.value; - })); + this.titleDisposables.add(this.windowTitle.onDidChange(() => { + this.title.innerText = this.windowTitle.value; + })); + + } } // Menu Title From d0d22003f7538b27de26460dfe2506bca019905e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 17 Jan 2024 14:42:47 +0100 Subject: [PATCH 0377/1897] make sure save constent also works for notebooks (#202666) --- .../browser/inlineChatController.ts | 2 +- .../inlineChat/browser/inlineChatNotebook.ts | 37 +++++- .../browser/inlineChatSavingServiceImpl.ts | 122 ++++++++++++++---- 3 files changed, 129 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 91e8f5a8126..d4aab939149 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -194,7 +194,7 @@ export class InlineChatController implements IEditorContribution { } this._strategy?.dispose(); this._store.dispose(); - this._log('controller disposed'); + this._log('DISPOSED controller'); } private _log(message: string | Error, ...more: any[]): void { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts index 2a360f95b2a..e074c182fc4 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts @@ -23,17 +23,42 @@ export class InlineChatNotebookContribution { ) { this._store.add(sessionService.registerSessionKeyComputer(Schemas.vscodeNotebookCell, { - getComparisonKey: (_editor, uri) => { + getComparisonKey: (editor, uri) => { const data = CellUri.parse(uri); if (!data) { - throw illegalState('Expected notebook'); + throw illegalState('Expected notebook cell uri'); } - for (const editor of notebookEditorService.listNotebookEditors()) { - if (isEqual(editor.textModel?.uri, data.notebook)) { - return `${editor.getId()}#${uri}`; + let fallback: string | undefined; + for (const notebookEditor of notebookEditorService.listNotebookEditors()) { + if (notebookEditor.hasModel() && isEqual(notebookEditor.textModel.uri, data.notebook)) { + + const candidate = `${notebookEditor.getId()}#${uri}`; + + if (!fallback) { + fallback = candidate; + } + + // find the code editor in the list of cell-code editors + if (notebookEditor.codeEditors.find((tuple) => tuple[1] === editor)) { + return candidate; + } + + // // reveal cell and try to find code editor again + // const cell = notebookEditor.getCellByHandle(data.handle); + // if (cell) { + // notebookEditor.revealInViewAtTop(cell); + // if (notebookEditor.codeEditors.find((tuple) => tuple[1] === editor)) { + // return candidate; + // } + // } } } - throw illegalState('Expected notebook'); + + if (fallback) { + return fallback; + } + + throw illegalState('Expected notebook editor'); } })); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts index 82effd20020..55658018f73 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts @@ -3,15 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { raceCancellation } from 'vs/base/common/async'; +import { Queue, raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { DisposableStore, IDisposable, MutableDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, MutableDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ITextModel } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; -import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/editor'; +import { SaveReason } from 'vs/workbench/common/editor'; import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { IInlineChatSessionService } from './inlineChatSessionService'; import { InlineChatConfigKeys } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -22,8 +21,16 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { IInlineChatSavingService } from './inlineChatSavingService'; import { Iterable } from 'vs/base/common/iterator'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; +import { Schemas } from 'vs/base/common/network'; +import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { compare } from 'vs/base/common/strings'; +import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { URI } from 'vs/base/common/uri'; +import { ILogService } from 'vs/platform/log/common/log'; interface SessionData { + readonly resourceUri: URI; readonly dispose: () => void; readonly session: Session; readonly groupCandidate: IEditorGroup; @@ -44,6 +51,8 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService { @IEditorService private readonly _editorService: IEditorService, @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, @IConfigurationService private readonly _configService: IConfigurationService, + @IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService, + @ILogService private readonly _logService: ILogService, ) { this._store.add(_inlineChatSessionService.onDidEndSession(e => { this._sessionData.get(e.session)?.dispose(); @@ -58,16 +67,28 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService { markChanged(session: Session): void { if (!this._sessionData.has(session)) { + let uri = session.textModelN.uri; + + // notebooks: use the notebook-uri because saving happens on the notebook-level + if (uri.scheme === Schemas.vscodeNotebookCell) { + const data = CellUri.parse(uri); + if (!data) { + return; + } + uri = data?.notebook; + } + if (this._sessionData.size === 0) { this._installSaveParticpant(); } - const saveConfig = this._fileConfigService.disableAutoSave(session.textModelN.uri); + const saveConfigOverride = this._fileConfigService.disableAutoSave(uri); this._sessionData.set(session, { + resourceUri: uri, groupCandidate: this._editorGroupService.activeGroup, session, dispose: () => { - saveConfig.dispose(); + saveConfigOverride.dispose(); this._sessionData.delete(session); if (this._sessionData.size === 0) { this._saveParticipant.clear(); @@ -78,12 +99,23 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService { } private _installSaveParticpant(): void { - this._saveParticipant.value = this._textFileService.files.addSaveParticipant({ - participate: (model, context, progress, token) => this._participate(model.textEditorModel, context.reason, progress, token) + + const queue = new Queue(); + + const d1 = this._textFileService.files.addSaveParticipant({ + participate: (model, context, progress, token) => { + return queue.queue(() => this._participate(model.textEditorModel?.uri, context.reason, progress, token)); + } }); + const d2 = this._workingCopyFileService.addSaveParticipant({ + participate: (workingCopy, env, progress, token) => { + return queue.queue(() => this._participate(workingCopy.resource, env.reason, progress, token)); + } + }); + this._saveParticipant.value = combinedDisposable(d1, d2, queue); } - private async _participate(model: ITextModel | null, reason: SaveReason, progress: IProgress, token: CancellationToken): Promise { + private async _participate(uri: URI | undefined, reason: SaveReason, progress: IProgress, token: CancellationToken): Promise { if (reason !== SaveReason.EXPLICIT) { // all saves that we are concerned about are explicit @@ -98,7 +130,7 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService { const sessions = new Map(); for (const [session, data] of this._sessionData) { - if (model === session.textModelN) { + if (uri?.toString() === data.resourceUri.toString()) { sessions.set(session, data); } } @@ -123,7 +155,7 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService { }); // fallback: resolve when all sessions for this model have been resolved. this is independent of the editor opening - const allSessionsEnded = this._waitForSessions(Iterable.concat(groups.values(), orphans), token); + const allSessionsEnded = this._whenSessionsEnded(Iterable.concat(groups.map(tuple => tuple[1]), orphans), token); await Promise.race([allSessionsEnded, editorsOpenedAndSessionsEnded]); } @@ -138,19 +170,20 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService { } } - const groups = new Map(); + const groups: [IEditorGroup, SessionData][] = []; const orphans = new Set(); for (const data of sessions) { + const editor = this._inlineChatSessionService.getCodeEditor(data.session); const group = groupByEditor.get(editor); if (group) { // there is only one session per group because all sessions have the same model // because we save one file. - groups.set(group, data); + groups.push([group, data]); } else if (this._editorGroupService.groups.includes(data.groupCandidate)) { // the group candidate is still there. use it - groups.set(data.groupCandidate, data); + groups.push([data.groupCandidate, data]); } else { orphans.add(data); } @@ -159,22 +192,61 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService { } private async _openAndWait(groups: Iterable<[IEditorGroup, SessionData]>, token: CancellationToken) { - const sessions = new Set(); + + const dataByGroup = new Map(); for (const [group, data] of groups) { - const input: IResourceEditorInput = { resource: data.session.textModelN.uri, options: { override: DEFAULT_EDITOR_ASSOCIATION.id } }; - const pane = await this._editorService.openEditor(input, group); - const ctrl = pane?.getControl(); - if (!isCodeEditor(ctrl)) { - // PANIC - return; + let array = dataByGroup.get(group); + if (!array) { + array = []; + dataByGroup.set(group, array); + } + array.push(data); + } + + for (const [group, array] of dataByGroup) { + + if (token.isCancellationRequested) { + break; + } + + array.sort((a, b) => compare(a.session.textModelN.uri.toString(), b.session.textModelN.uri.toString())); + + + for (const data of array) { + + const input: IResourceEditorInput = { resource: data.resourceUri }; + const pane = await this._editorService.openEditor(input, group); + let editor: ICodeEditor | undefined; + if (data.session.textModelN.uri.scheme === Schemas.vscodeNotebookCell) { + const notebookEditor = getNotebookEditorFromEditorPane(pane); + const uriData = CellUri.parse(data.session.textModelN.uri); + if (notebookEditor && notebookEditor.hasModel() && uriData) { + const cell = notebookEditor.getCellByHandle(uriData.handle); + if (cell) { + await notebookEditor.revealRangeInCenterIfOutsideViewportAsync(cell, data.session.wholeRange.value); + } + const tuple = notebookEditor.codeEditors.find(tuple => tuple[1].getModel()?.uri.toString() === data.session.textModelN.uri.toString()); + editor = tuple?.[1]; + } + + } else { + if (isCodeEditor(pane?.getControl())) { + editor = pane.getControl(); + } + } + + if (!editor) { + // PANIC + break; + } + this._inlineChatSessionService.moveSession(data.session, editor); + this._logService.info('WAIT for session to end', editor.getId(), data.session.textModelN.uri.toString()); + await this._whenSessionsEnded(Iterable.single(data), token); } - this._inlineChatSessionService.moveSession(data.session, ctrl); - sessions.add(data); } - await this._waitForSessions(sessions, token); } - private async _waitForSessions(iterable: Iterable, token: CancellationToken) { + private async _whenSessionsEnded(iterable: Iterable, token: CancellationToken) { const sessions = new Map(); for (const item of iterable) { From 4134a5b3d0b77f4066e3961c02914c9a1aed6153 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Wed, 17 Jan 2024 14:51:44 +0100 Subject: [PATCH 0378/1897] Menu Bar Submenu --- src/vs/workbench/browser/actions/layoutActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 06f1875ec00..bf10920f0ff 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -803,7 +803,7 @@ if (isWindows || isLinux || isWeb) { toggled: ContextKeyExpr.and(IsMacNativeContext.toNegated(), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'hidden'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'toggle'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'compact')) }, when: ContextKeyExpr.and(IsAuxiliaryWindowFocusedContext.toNegated(), ContextKeyExpr.notEquals(`config.window.titleBarStyle`, 'native')), - group: menuId === MenuId.TitleBarTitleContext ? '2_config' : undefined, + group: '2_config', order: 0 }); } From ee6babafa786fe86765cd3687a1a8339064b1819 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 17 Jan 2024 16:09:36 +0100 Subject: [PATCH 0379/1897] make `enableKeybindingHoldMode` return a promise that resolves when hold-mode is over --- .../standalone/browser/standaloneServices.ts | 7 +++++ .../common/abstractKeybindingService.ts | 13 ++------ .../platform/keybinding/common/keybinding.ts | 4 +-- .../common/abstractKeybindingService.test.ts | 4 +++ .../test/common/mockKeybindingService.ts | 4 +-- .../electron-sandbox/inlineChatActions.ts | 30 ++++++++----------- .../keybinding/browser/keybindingService.ts | 20 +++++++++++-- 7 files changed, 46 insertions(+), 36 deletions(-) diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index ec7e0075f74..b20bde39b15 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -594,6 +594,13 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { public registerSchemaContribution(contribution: KeybindingsSchemaContribution): void { // noop } + + /** + * not yet supported + */ + public override enableKeybindingHoldMode(commandId: string): Promise | undefined { + return undefined; + } } class DomNodeListeners extends Disposable { diff --git a/src/vs/platform/keybinding/common/abstractKeybindingService.ts b/src/vs/platform/keybinding/common/abstractKeybindingService.ts index 0df56233ba1..40e5567ab00 100644 --- a/src/vs/platform/keybinding/common/abstractKeybindingService.ts +++ b/src/vs/platform/keybinding/common/abstractKeybindingService.ts @@ -53,8 +53,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK private _ignoreSingleModifiers: KeybindingModifierSet; private _currentSingleModifier: SingleModifierChord | null; private _currentSingleModifierClearTimeout: TimeoutTimer; - private _currentlyDispatchingCommandId: string | null; - protected _isInKeybindingHoldMode: boolean; + protected _currentlyDispatchingCommandId: string | null; protected _logging: boolean; @@ -78,7 +77,6 @@ export abstract class AbstractKeybindingService extends Disposable implements IK this._currentSingleModifier = null; this._currentSingleModifierClearTimeout = new TimeoutTimer(); this._currentlyDispatchingCommandId = null; - this._isInKeybindingHoldMode = false; this._logging = false; } @@ -387,14 +385,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK } } - enableKeybindingHoldMode(commandId: string): boolean { - if (this._currentlyDispatchingCommandId !== commandId) { - return false; - } - this._isInKeybindingHoldMode = true; - this._log(`+ Enabled hold-mode for ${commandId}.`); - return true; - } + abstract enableKeybindingHoldMode(commandId: string): Promise | undefined; mightProducePrintableCharacter(event: IKeyboardEvent): boolean { if (event.ctrlKey || event.metaKey) { diff --git a/src/vs/platform/keybinding/common/keybinding.ts b/src/vs/platform/keybinding/common/keybinding.ts index 789d044a64f..5a8c2b5dcec 100644 --- a/src/vs/platform/keybinding/common/keybinding.ts +++ b/src/vs/platform/keybinding/common/keybinding.ts @@ -69,9 +69,9 @@ export interface IKeybindingService { * Enable hold mode for this command. This is only possible if the command is current being dispatched, meaning * we are after its keydown and before is keyup event. * - * @returns true is hold mode was enabled + * @returns A promise that resolves when hold stops, returns undefined if hold mode could not be enabled. */ - enableKeybindingHoldMode(commandId: string): boolean; + enableKeybindingHoldMode(commandId: string): Promise | undefined; dispatchByUserSettingsLabel(userSettingsLabel: string, target: IContextKeyServiceTarget): void; diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index 246ed148a4d..24386a8cab2 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -95,6 +95,10 @@ suite('AbstractKeybindingService', () => { public registerSchemaContribution() { // noop } + + public enableKeybindingHoldMode() { + return undefined; + } } let createTestKeybindingService: (items: ResolvedKeybindingItem[], contextValue?: any) => TestKeybindingService = null!; diff --git a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts index a9d352e24ea..9991b85f24b 100644 --- a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts +++ b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts @@ -147,8 +147,8 @@ export class MockKeybindingService implements IKeybindingService { return false; } - public enableKeybindingHoldMode(commandId: string): boolean { - return false; + public enableKeybindingHoldMode(commandId: string): undefined { + return undefined; } public mightProducePrintableCharacter(e: IKeyboardEvent): boolean { diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts index c0f8c8f5efb..6a94b91a0d5 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from 'vs/base/browser/dom'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; @@ -51,31 +50,26 @@ export class StartSessionAction extends EditorAction2 { if (configService.getValue(InlineChatConfigKeys.HoldToSpeech) // enabled && speechService.hasSpeechProvider // possible - && keybindingService.enableKeybindingHoldMode(this.desc.id) // holding keys ) { + const holdMode = keybindingService.enableKeybindingHoldMode(this.desc.id); + if (holdMode) { // holding keys + let listening = false; + const handle = disposableTimeout(() => { + // start VOICE input + commandService.executeCommand(StartVoiceChatAction.ID); + listening = true; + }, 100); - let listening = false; - const handle = disposableTimeout(() => { - // start VOICE input - commandService.executeCommand(StartVoiceChatAction.ID); - listening = true; - }, 100); - - const listener = dom.addDisposableListener( - dom.getWindow(editor.getDomNode()), - dom.EventType.KEY_UP, - (_e: KeyboardEvent) => { - listener.dispose(); // Event.once + holdMode.finally(() => { if (listening) { commandService.executeCommand(StopListeningAction.ID).finally(() => { InlineChatController.get(editor)?.acceptInput(); }); - } else { - handle.dispose(); } - } - ); + handle.dispose(); + }); + } } let options: InlineChatRunOptions | undefined; diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 3222c7dfab6..1345094497c 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -10,7 +10,7 @@ import * as browser from 'vs/base/browser/browser'; import { BrowserFeatures, KeyboardSupport } from 'vs/base/browser/canIUse'; import * as dom from 'vs/base/browser/dom'; import { printKeyboardEvent, printStandardKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { RunOnceScheduler } from 'vs/base/common/async'; +import { DeferredPromise, RunOnceScheduler } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { parse } from 'vs/base/common/json'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; @@ -183,6 +183,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { private _cachedResolver: KeybindingResolver | null; private userKeybindings: UserKeybindings; private isComposingGlobalContextKey: IContextKey; + private _keybindingHoldMode: DeferredPromise | null; private readonly _contributions: KeybindingsSchemaContribution[] = []; private readonly kbsJsonSchema: KeybindingsJsonSchema; @@ -212,6 +213,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { this.updateResolver(); }); + this._keybindingHoldMode = null; this._cachedResolver = null; this.userKeybindings = this._register(new UserKeybindings(userDataProfileService, uriIdentityService, fileService, logService)); @@ -269,7 +271,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { // for standard keybindings disposables.add(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { - if (this._isInKeybindingHoldMode) { + if (this._keybindingHoldMode) { return; } this.isComposingGlobalContextKey.set(e.isComposing); @@ -285,7 +287,10 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { // for single modifier chord keybindings (e.g. shift shift) disposables.add(dom.addDisposableListener(window, dom.EventType.KEY_UP, (e: KeyboardEvent) => { - this._isInKeybindingHoldMode = false; + if (this._keybindingHoldMode) { + this._keybindingHoldMode.complete(); + this._keybindingHoldMode = null; + } this.isComposingGlobalContextKey.set(e.isComposing); const keyEvent = new StandardKeyboardEvent(e); const shouldPreventDefault = this._singleModifierDispatch(keyEvent, keyEvent.target); @@ -395,6 +400,15 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { return JSON.stringify(info, null, '\t'); } + public override enableKeybindingHoldMode(commandId: string): Promise | undefined { + if (this._currentlyDispatchingCommandId !== commandId) { + return undefined; + } + this._keybindingHoldMode = new DeferredPromise(); + this._log(`+ Enabled hold-mode for ${commandId}.`); + return this._keybindingHoldMode.p; + } + public override customKeybindingsCount(): number { return this.userKeybindings.keybindings.length; } From 85d3cd0eef83f41f74311b79b5c7c46d9ce52423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Wed, 17 Jan 2024 16:54:23 +0100 Subject: [PATCH 0380/1897] do not create a double semi colon when adding to path (#202583) * do not create a double semi colon when adding to path fixes #202268 * parens * bad signature * hm --- build/win32/code.iss | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/build/win32/code.iss b/build/win32/code.iss index cca821e647d..f8d231f5858 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -1296,7 +1296,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Drive\shell\{#RegValu #define Uninstall32RootKey "HKLM32" #endif -Root: {#EnvironmentRootKey}; Subkey: "{#EnvironmentKey}"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\bin"; Tasks: addtopath; Check: NeedsAddPath(ExpandConstant('{app}\bin')) +Root: {#EnvironmentRootKey}; Subkey: "{#EnvironmentKey}"; ValueType: expandsz; ValueName: "Path"; ValueData: "{code:AddToPath|{app}\bin}"; Tasks: addtopath; Check: NeedsAddToPath(ExpandConstant('{app}\bin')) [Code] function IsBackgroundUpdate(): Boolean; @@ -1553,7 +1553,7 @@ begin until Length(Text)=0; end; -function NeedsAddPath(Param: string): boolean; +function NeedsAddToPath(VSCode: string): boolean; var OrigPath: string; begin @@ -1562,7 +1562,19 @@ begin Result := True; exit; end; - Result := Pos(';' + Param + ';', ';' + OrigPath + ';') = 0; + Result := Pos(';' + VSCode + ';', ';' + OrigPath + ';') = 0; +end; + +function AddToPath(VSCode: string): string; +var + OrigPath: string; +begin + RegQueryStringValue({#EnvironmentRootKey}, '{#EnvironmentKey}', 'Path', OrigPath) + + if (Length(OrigPath) > 0) and (OrigPath[Length(OrigPath)] = ';') then + Result := OrigPath + VSCode + else + Result := OrigPath + ';' + VSCode end; procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); From b87961277683bf962da8b18c42c851929e125e7b Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Wed, 17 Jan 2024 17:03:06 +0100 Subject: [PATCH 0381/1897] editor actions default --- src/vs/workbench/browser/layout.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index f276016e8be..3e45b0db08d 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -1244,7 +1244,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // with the editor actions on top, we should always show - if (this.configurationService.getValue(LayoutSettings.EDITOR_ACTIONS_LOCATION) === EditorActionsLocation.TITLEBAR) { + const editorActionsLocation = this.configurationService.getValue(LayoutSettings.EDITOR_ACTIONS_LOCATION); + const editorTabsMode = this.configurationService.getValue(LayoutSettings.EDITOR_TABS_MODE); + if (editorActionsLocation === EditorActionsLocation.TITLEBAR || editorActionsLocation === EditorActionsLocation.DEFAULT && editorTabsMode === EditorTabsMode.NONE) { return true; } From 80f7da88d517bc9191c2a9c7c2018653a4f90aa0 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Wed, 17 Jan 2024 10:17:16 -0600 Subject: [PATCH 0382/1897] Search result tree drawing glitches (#202624) Fixes #158785 --- src/vs/workbench/contrib/search/browser/searchView.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 8286ef92bd2..babe5ac41fc 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -354,6 +354,7 @@ export class SearchView extends ViewPane { // remove old model and use the new searchModel searchModel.location = SearchModelLocation.PANEL; searchModel.replaceActive = this.viewModel.isReplaceActive(); + searchModel.replaceString = this.searchWidget.getReplaceValue(); this._onSearchResultChangedDisposable?.dispose(); this._onSearchResultChangedDisposable = this._register(searchModel.onSearchResultChanged((event) => this.onSearchResultsChanged(event))); @@ -1613,8 +1614,6 @@ export class SearchView extends ViewPane { } } - this.viewModel.replaceString = this.searchWidget.getReplaceValue(); - const hasResults = !this.viewModel.searchResult.isEmpty(); if (completed?.exit === SearchCompletionExitCode.NewSearchStarted) { return; @@ -1735,6 +1734,8 @@ export class SearchView extends ViewPane { this.tree.setSelection([]); this.tree.setFocus([]); + + this.viewModel.replaceString = this.searchWidget.getReplaceValue(); const result = this.viewModel.search(query); return result.asyncResults.then((complete) => { clearTimeout(slowTimer); From f05f54e74c68bd989a3beaf5c7f8363552fdee0a Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 17 Jan 2024 08:23:55 -0800 Subject: [PATCH 0383/1897] Support command in inline chat followup (#202634) --- src/vs/workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostInlineChat.ts | 6 +++--- .../inlineChat/browser/inlineChatController.ts | 10 ++++++++-- .../contrib/inlineChat/browser/inlineChatWidget.ts | 6 +++--- .../workbench/contrib/inlineChat/common/inlineChat.ts | 4 ++-- src/vscode-dts/vscode.proposed.interactive.d.ts | 11 ++++++++++- 6 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 346c1ddd264..e622ea5d57e 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1233,7 +1233,7 @@ export type IInlineChatResponseDto = Dto; $provideResponse(handle: number, session: IInlineChatSession, request: IInlineChatRequest, token: CancellationToken): Promise; - $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise; + $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise; $handleFeedback(handle: number, sessionId: number, responseId: number, kind: InlineChatResponseFeedbackKind): void; $releaseSession(handle: number, sessionId: number): void; } diff --git a/src/vs/workbench/api/common/extHostInlineChat.ts b/src/vs/workbench/api/common/extHostInlineChat.ts index 7baad711305..72fbb41c99f 100644 --- a/src/vs/workbench/api/common/extHostInlineChat.ts +++ b/src/vs/workbench/api/common/extHostInlineChat.ts @@ -19,7 +19,7 @@ import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } fro import { IRange } from 'vs/editor/common/core/range'; import { IPosition } from 'vs/editor/common/core/position'; import { raceCancellation } from 'vs/base/common/async'; -import { IChatReplyFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; class ProviderWrapper { @@ -227,14 +227,14 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { } } - async $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise { + async $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise { const entry = this._inputProvider.get(handle); const sessionData = this._inputSessions.get(sessionId); const response = sessionData?.responses[responseId]; if (entry && response && entry.provider.provideFollowups) { const task = Promise.resolve(entry.provider.provideFollowups(sessionData.session, response, token)); const followups = await raceCancellation(task, token); - return followups?.map(typeConvert.ChatReplyFollowup.from); + return followups?.map(typeConvert.ChatFollowup.from); } return undefined; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index d4aab939149..be3aa770913 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -45,6 +45,7 @@ import { IInlineChatSessionService } from './inlineChatSessionService'; import { EditModeStrategy, LivePreviewStrategy, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; import { IInlineChatMessageAppender, InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatProgressItem, IInlineChatRequest, IInlineChatResponse, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ICommandService } from 'vs/platform/commands/common/commands'; export const enum State { CREATE_SESSION = 'CREATE_SESSION', @@ -143,6 +144,7 @@ export class InlineChatController implements IEditorContribution { @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IBulkEditService private readonly _bulkEditService: IBulkEditService, @IStorageService private readonly _storageService: IStorageService, + @ICommandService private readonly _commandService: ICommandService, ) { this._ctxHasActiveRequest = CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.bindTo(contextKeyService); this._ctxDidEdit = CTX_INLINE_CHAT_DID_EDIT.bindTo(contextKeyService); @@ -788,8 +790,12 @@ export class InlineChatController implements IEditorContribution { if (followupReply && this._session) { this._log('followup request received', this._session.provider.debugName, this._session.session, followupReply); this._zone.value.widget.updateFollowUps(followupReply, followup => { - this.updateInput(followup.message); - this.acceptInput(); + if (followup.kind === 'reply') { + this.updateInput(followup.message); + this.acceptInput(); + } else { + this._commandService.executeCommand(followup.commandId, ...(followup.args ?? [])); + } }); } }).finally(() => { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 98c163bf0d5..f0509750b63 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -64,7 +64,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IChatReplyFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; @@ -635,9 +635,9 @@ export class InlineChatWidget { return resultingAppender; } - updateFollowUps(items: IChatReplyFollowup[], onFollowup: (followup: IChatReplyFollowup) => void): void; + updateFollowUps(items: IChatFollowup[], onFollowup: (followup: IChatFollowup) => void): void; updateFollowUps(items: undefined): void; - updateFollowUps(items: IChatReplyFollowup[] | undefined, onFollowup?: ((followup: IChatReplyFollowup) => void)) { + updateFollowUps(items: IChatFollowup[] | undefined, onFollowup?: ((followup: IChatFollowup) => void)) { this._followUpDisposables.clear(); this._elements.followUps.classList.toggle('hidden', !items || items.length === 0); reset(this._elements.followUps); diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 1ac18061206..64370f4950c 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -20,7 +20,7 @@ import { IProgress } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; import { diffInserted, diffRemoved, editorHoverHighlight, editorWidgetBackground, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { Extensions as ExtensionsMigration, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; -import { IChatReplyFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; export interface IInlineChatSlashCommand { command: string; @@ -106,7 +106,7 @@ export interface IInlineChatSessionProvider { provideResponse(item: IInlineChatSession, request: IInlineChatRequest, progress: IProgress, token: CancellationToken): ProviderResult; - provideFollowups?(session: IInlineChatSession, response: IInlineChatResponse, token: CancellationToken): ProviderResult; + provideFollowups?(session: IInlineChatSession, response: IInlineChatResponse, token: CancellationToken): ProviderResult; handleInlineChatResponseFeedback?(session: IInlineChatSession, response: IInlineChatResponse, kind: InlineChatResponseFeedbackKind): void; } diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index 98955ea4dd2..e233f52fa5c 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -83,6 +83,15 @@ declare module 'vscode' { title?: string; } + export interface InteractiveEditorCommandFollowup { + commandId: string; + args?: any[]; + title: string; + when?: string; + } + + export type InteractiveEditorFollowup = InteractiveEditorReplyFollowup | InteractiveEditorCommandFollowup; + export interface InteractiveEditorSessionProvider { // Create a session. The lifetime of this session is the duration of the editing session with the input mode widget. @@ -90,7 +99,7 @@ declare module 'vscode' { provideInteractiveEditorResponse(session: S, request: InteractiveEditorRequest, progress: Progress, token: CancellationToken): ProviderResult; - provideFollowups?(session: S, response: R, token: CancellationToken): ProviderResult; + provideFollowups?(session: S, response: R, token: CancellationToken): ProviderResult; // eslint-disable-next-line local/vscode-dts-provider-naming handleInteractiveEditorResponseFeedback?(session: S, response: R, kind: InteractiveEditorResponseFeedbackKind): void; From 1deb0e602ee679737d9abcc77c40aa9d7532ce22 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 17 Jan 2024 09:09:09 -0800 Subject: [PATCH 0384/1897] move above cursor --- src/vs/workbench/contrib/terminal/browser/media/terminal.css | 4 +--- src/vs/workbench/contrib/terminal/browser/terminal.ts | 3 ++- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 4 ++-- .../contrib/terminal/browser/terminalSpeechToText.ts | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index abd6805fa4a..f044e6b04f4 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -566,6 +566,4 @@ visibility: hidden; } -.terminal-speech-to-text { - padding-left: 5px; -} + diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index f5dfc7d63a0..68fc67ea4e1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -789,8 +789,9 @@ export interface ITerminalInstance extends IBaseTerminalInstance { /** * Registers and returns a marker + * @param offset The offset from the cursor */ - registerMarker(): IMarker | undefined; + registerMarker(offset?: number): IMarker | undefined; /** * Adds a marker to the buffer, mapping it to an ID if provided. diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 5ac44dc2718..9bd13171c8f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1440,8 +1440,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } - public registerMarker(): IMarker | undefined { - return this.xterm?.raw.registerMarker(); + public registerMarker(offset?: number): IMarker | undefined { + return this.xterm?.raw.registerMarker(offset); } public addBufferMarker(properties: IMarkProperties): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts index a7bc31c3b5d..78001da19a5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts @@ -146,7 +146,7 @@ export class TerminalSpeechToTextSession extends Disposable { if (!xterm) { return; } - this._marker = activeInstance.registerMarker(); + this._marker = activeInstance.registerMarker(-1); if (!this._marker) { return; } @@ -157,7 +157,7 @@ export class TerminalSpeechToTextSession extends Disposable { }); this._decoration?.onRender((e: HTMLElement) => { e.classList.add(...ThemeIcon.asClassNameArray(Codicon.mic)); - e.classList.add('terminal-speech-to-text'); + e.style.transform = 'translate(-5px, -5px)'; }); } } From af44afef6d786bfe5500b0c32cf839f81e81a0cc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 17 Jan 2024 09:42:32 -0800 Subject: [PATCH 0385/1897] Add terminalTextToSpeech.css for speech-to-text feature --- .../terminal/browser/media/terminal.css | 2 - .../browser/media/terminalTextToSpeech.css | 40 +++++++++++++++++++ .../terminal/browser/terminal.contribution.ts | 1 + .../terminal/browser/terminalSpeechToText.ts | 7 +++- 4 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 src/vs/workbench/contrib/terminal/browser/media/terminalTextToSpeech.css diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index f044e6b04f4..488815cf258 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -565,5 +565,3 @@ .monaco-workbench .xterm.terminal.hide { visibility: hidden; } - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminalTextToSpeech.css b/src/vs/workbench/contrib/terminal/browser/media/terminalTextToSpeech.css new file mode 100644 index 00000000000..bbdfa4fed27 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/media/terminalTextToSpeech.css @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.terminal-speech-to-text { + background-color: var(--vscode-terminal-background, var(--vscode-panel-background)); + padding: 2px; + border-radius: 8px; + display: flex; + align-items: center; + white-space: nowrap; + z-index: 1000; +} + +.terminal-speech-to-text.codicon.codicon-mic-filled { + display: flex; + align-items: center; + width: 16px; + height: 16px; +} + +.terminal-speech-to-text.recording.codicon.codicon-mic-filled { + color: var(--vscode-activityBarBadge-background); + animation: ani-terminal-speech 1s infinite; +} + +@keyframes ani-terminal-speech { + 0% { + color: var(--vscode-terminalCursor-background); + } + + 50% { + color: var(--vscode-activityBarBadge-background); + } + + 100% { + color: var(--vscode-terminalCursor-background); + } +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 76e54cc3613..f73a9daeafd 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -8,6 +8,7 @@ import 'vs/css!./media/scrollbar'; import 'vs/css!./media/widgets'; import 'vs/css!./media/xterm'; import 'vs/css!./media/terminal'; +import 'vs/css!./media/terminalTextToSpeech'; import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts index 78001da19a5..8a1d27ac406 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts @@ -88,6 +88,7 @@ export class TerminalSpeechToTextSession extends Disposable { })); } stop(send?: boolean): void { + this._setInactive(); if (send) { this._acceptTranscriptionScheduler!.cancel(); this._terminalService.activeInstance?.sendText(this._input, false); @@ -156,10 +157,14 @@ export class TerminalSpeechToTextSession extends Disposable { x: xterm.buffer.active.cursorX ?? 0, }); this._decoration?.onRender((e: HTMLElement) => { - e.classList.add(...ThemeIcon.asClassNameArray(Codicon.mic)); + e.classList.add(...ThemeIcon.asClassNameArray(Codicon.micFilled), 'terminal-speech-to-text', 'recording'); e.style.transform = 'translate(-5px, -5px)'; }); } + + private _setInactive(): void { + this._decoration?.element?.classList.remove('recording'); + } } From 5d941e436f3c38893bd3725418ecdd1476fff2f3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 17 Jan 2024 09:46:03 -0800 Subject: [PATCH 0386/1897] fix css issue --- .../{terminalTextToSpeech.css => terminalSpeechToText.css} | 0 .../workbench/contrib/terminal/browser/terminal.contribution.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/vs/workbench/contrib/terminal/browser/media/{terminalTextToSpeech.css => terminalSpeechToText.css} (100%) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminalTextToSpeech.css b/src/vs/workbench/contrib/terminal/browser/media/terminalSpeechToText.css similarity index 100% rename from src/vs/workbench/contrib/terminal/browser/media/terminalTextToSpeech.css rename to src/vs/workbench/contrib/terminal/browser/media/terminalSpeechToText.css diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index f73a9daeafd..87443272043 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -8,7 +8,7 @@ import 'vs/css!./media/scrollbar'; import 'vs/css!./media/widgets'; import 'vs/css!./media/xterm'; import 'vs/css!./media/terminal'; -import 'vs/css!./media/terminalTextToSpeech'; +import 'vs/css!./media/terminalSpeechToText'; import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; From c4e96bcada3bded7fb683010e70a209ec68a40b5 Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 17 Jan 2024 18:49:29 +0100 Subject: [PATCH 0387/1897] Added incompressible element check in CompressibleStickyScrollDelegate (#202675) Added check for incompressible element in CompressibleStickyScrollDelegate --- src/vs/base/browser/ui/tree/objectTree.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts index 41903e32813..a174d011511 100644 --- a/src/vs/base/browser/ui/tree/objectTree.ts +++ b/src/vs/base/browser/ui/tree/objectTree.ts @@ -199,6 +199,9 @@ class CompressibleStickyScrollDelegate implements IStickyScrollD const compressedNode = this.modelProvider().getCompressedTreeNode(stickyNode.node.element); if (compressedNode.element) { + if (compressedNode.element.incompressible) { + break; + } elements.push(...compressedNode.element.elements); } } From e3af4ea1ee86fe49ddfa121c535231959ae68672 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 17 Jan 2024 10:23:30 -0800 Subject: [PATCH 0388/1897] add ghost text --- .../browser/media/terminalSpeechToText.css | 7 ++++ .../terminal/browser/terminalSpeechToText.ts | 33 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminalSpeechToText.css b/src/vs/workbench/contrib/terminal/browser/media/terminalSpeechToText.css index bbdfa4fed27..e227a4c3321 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminalSpeechToText.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminalSpeechToText.css @@ -38,3 +38,10 @@ color: var(--vscode-terminalCursor-background); } } + +.terminal-speech-progress-text { + font-style: italic; + color: var(--vscode-editorGhostText-foreground) !important; + border: 1px solid var(--vscode-editorGhostText-border); + z-index: 1000; +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts index 8a1d27ac406..a548acc9877 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts @@ -19,8 +19,10 @@ import { Codicon } from 'vs/base/common/codicons'; export class TerminalSpeechToTextSession extends Disposable { private _input: string = ''; + private _ghostText: IDecoration | undefined; private _decoration: IDecoration | undefined; private _marker: IXtermMarker | undefined; + private _ghostTextMarker: IXtermMarker | undefined; private static _instance: TerminalSpeechToTextSession | undefined = undefined; private _acceptTranscriptionScheduler: RunOnceScheduler | undefined; static getInstance(instantiationService: IInstantiationService): TerminalSpeechToTextSession { @@ -69,6 +71,7 @@ export class TerminalSpeechToTextSession extends Disposable { break; case SpeechToTextStatus.Recognizing: { this._updateInput(e); + this._renderGhostText(e); if (voiceTimeout > 0) { this._acceptTranscriptionScheduler!.cancel(); } @@ -94,6 +97,9 @@ export class TerminalSpeechToTextSession extends Disposable { this._terminalService.activeInstance?.sendText(this._input, false); } this._marker?.dispose(); + this._ghostTextMarker?.dispose(); + this._ghostText?.dispose(); + this._ghostText = undefined; this._decoration?.dispose(); this._decoration = undefined; this._cancellationTokenSource?.cancel(); @@ -165,6 +171,33 @@ export class TerminalSpeechToTextSession extends Disposable { private _setInactive(): void { this._decoration?.element?.classList.remove('recording'); } + + private _renderGhostText(e: ISpeechToTextEvent): void { + this._ghostText?.dispose(); + const text = e.text; + if (!text) { + return; + } + const activeInstance = this._terminalService.activeInstance; + const xterm = activeInstance?.xterm?.raw; + if (!xterm) { + return; + } + this._ghostTextMarker = activeInstance.registerMarker(); + if (!this._ghostTextMarker) { + return; + } + this._ghostText = xterm.registerDecoration({ + marker: this._ghostTextMarker, + layer: 'top', + x: xterm.buffer.active.cursorX + 1 ?? 0, + }); + this._ghostText?.onRender((e: HTMLElement) => { + e.classList.add('terminal-speech-progress-text'); + e.textContent = text; + e.style.width = 'fit-content'; + }); + } } From 2e3df8a8b11fd6b7887c4df8d91227ef3c7e5413 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 17 Jan 2024 11:15:21 -0800 Subject: [PATCH 0389/1897] testing: coverage decorations in high contrast, better default behavior for position-only exts (#202676) * testing: coverage decorations in high contrast, better default behavior for position-only exts * update snapshot --- .../lib/stylelint/vscode-known-variables.json | 4 ++- .../browser/codeCoverageDecorations.ts | 2 +- .../contrib/testing/browser/media/testing.css | 16 +++++++++ .../contrib/testing/browser/theme.ts | 34 ++++++++++++------- ..._Decorations_CoverageDetailsModel_4.0.snap | 22 ++++++++---- 5 files changed, 56 insertions(+), 22 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index e0fcdff3c6d..cbdea6a9ea2 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -680,8 +680,9 @@ "--vscode-terminalOverviewRuler-findMatchForeground", "--vscode-terminalStickyScroll-background", "--vscode-terminalStickyScrollHover-background", - "--vscode-testing-coveredBackground", "--vscode-testing-coverage-lineHeight", + "--vscode-testing-coveredBackground", + "--vscode-testing-coveredBorder", "--vscode-testing-coveredGutterBackground", "--vscode-testing-iconErrored", "--vscode-testing-iconFailed", @@ -697,6 +698,7 @@ "--vscode-testing-peekHeaderBackground", "--vscode-testing-runAction", "--vscode-testing-uncoveredBackground", + "--vscode-testing-uncoveredBorder", "--vscode-testing-uncoveredGutterBackground", "--vscode-textBlockQuote-background", "--vscode-textBlockQuote-border", diff --git a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts index c981e552a97..ac801660ccd 100644 --- a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts @@ -452,7 +452,7 @@ export class CoverageDetailsModel { // and trailing whitespace. function tidyLocation(location: Range | Position): Range { if (location instanceof Position) { - return Range.fromPositions(location); + return Range.fromPositions(location, new Position(location.lineNumber, 0x7FFFFFFF)); } return location; diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index f0cf2ea8a49..86b858a5d1e 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -430,10 +430,19 @@ .coverage-deco-gutter.coverage-deco-hit::before { background: var(--vscode-testing-coveredGutterBackground); + border-color: var(--vscode-testing-coveredGutterBackground); } .coverage-deco-gutter.coverage-deco-miss::before { background: var(--vscode-testing-uncoveredGutterBackground); + border-color: var(--vscode-testing-uncoveredGutterBackground); +} + +.hc-light .coverage-deco-gutter::before, +.hc-black .coverage-deco-gutter::before { + border-width: 3px 0 3px 5px; + border-style: solid; + background: none; } .coverage-deco-gutter.coverage-deco-miss.coverage-deco-hit::before { @@ -456,10 +465,17 @@ .coverage-deco-inline.coverage-deco-hit { background: var(--vscode-testing-coveredBackground); + outline: 1px solid var(--vscode-testing-coveredBorder); } .coverage-deco-inline.coverage-deco-miss { background: var(--vscode-testing-uncoveredBackground); + outline: 1px solid var(--vscode-testing-uncoveredBorder); +} + +.hc-light .coverage-deco-inline.coverage-deco-hit, +.hc-black .coverage-deco-inline.coverage-deco-hit { + outline-style: dashed; } .coverage-deco-branch-miss-indicator { diff --git a/src/vs/workbench/contrib/testing/browser/theme.ts b/src/vs/workbench/contrib/testing/browser/theme.ts index a7c8cd7c553..fe155816c0d 100644 --- a/src/vs/workbench/contrib/testing/browser/theme.ts +++ b/src/vs/workbench/contrib/testing/browser/theme.ts @@ -5,7 +5,7 @@ import { Color, RGBA } from 'vs/base/common/color'; import { localize } from 'vs/nls'; -import { contrastBorder, diffInserted, diffRemoved, editorBackground, editorErrorForeground, editorForeground, editorInfoForeground, opaque, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; +import { chartsGreen, chartsRed, contrastBorder, diffInserted, diffRemoved, editorBackground, editorErrorForeground, editorForeground, editorInfoForeground, opaque, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { TestMessageType, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes'; @@ -93,11 +93,18 @@ export const testingCoveredBackground = registerColor('testing.coveredBackground hcLight: null }, localize('testing.coveredBackground', 'Background color of text that was covered.')); +export const testingCoveredBorder = registerColor('testing.coveredBorder', { + dark: transparent(testingCoveredBackground, 0.75), + light: transparent(testingCoveredBackground, 0.75), + hcDark: contrastBorder, + hcLight: contrastBorder +}, localize('testing.coveredBorder', 'Border color of text that was covered.')); + export const testingCoveredGutterBackground = registerColor('testing.coveredGutterBackground', { dark: transparent(diffInserted, 0.6), light: transparent(diffInserted, 0.6), - hcDark: null, - hcLight: null + hcDark: chartsGreen, + hcLight: chartsGreen }, localize('testing.coveredGutterBackground', 'Gutter color of regions where code was covered.')); export const testingUncoveredBranchBackground = registerColor('testing.uncoveredBranchBackground', { @@ -114,11 +121,18 @@ export const testingUncoveredBackground = registerColor('testing.uncoveredBackgr hcLight: null }, localize('testing.uncoveredBackground', 'Background color of text that was not covered.')); +export const testingUncoveredBorder = registerColor('testing.uncoveredBorder', { + dark: transparent(testingUncoveredBackground, 0.75), + light: transparent(testingUncoveredBackground, 0.75), + hcDark: contrastBorder, + hcLight: contrastBorder +}, localize('testing.uncoveredBorder', 'Border color of text that was not covered.')); + export const testingUncoveredGutterBackground = registerColor('testing.uncoveredGutterBackground', { dark: transparent(diffRemoved, 1.5), light: transparent(diffRemoved, 1.5), - hcDark: null, - hcLight: null + hcDark: chartsRed, + hcLight: chartsRed }, localize('testing.uncoveredGutterBackground', 'Gutter color of regions where code not covered.')); export const testMessageSeverityColors: { @@ -168,19 +182,13 @@ registerThemingParticipant((theme, collector) => { const missBadgeBackground = editorBg && theme.getColor(testingUncoveredBackground)?.transparent(2).makeOpaque(editorBg); collector.addRule(` - .coverage-deco-inline.coverage-deco-hit { - outline: 1px solid ${theme.getColor(testingCoveredBackground)?.transparent(0.75)}; - } .coverage-deco-inline.coverage-deco-hit.coverage-deco-hovered { background: ${theme.getColor(testingCoveredBackground)?.transparent(1.3)}; - outline: 1px solid ${theme.getColor(testingCoveredBackground)?.transparent(2)}; - } - .coverage-deco-inline.coverage-deco-miss { - outline: 1px solid ${theme.getColor(testingUncoveredBackground)?.transparent(0.75)}; + outline-color: ${theme.getColor(testingCoveredBorder)?.transparent(2)}; } .coverage-deco-inline.coverage-deco-miss.coverage-deco-hovered { background: ${theme.getColor(testingUncoveredBackground)?.transparent(1.3)}; - outline: 1px solid ${theme.getColor(testingUncoveredBackground)?.transparent(2)}; + outline-color: ${theme.getColor(testingUncoveredBorder)?.transparent(2)}; } .coverage-deco-branch-miss-indicator::before { border-color: ${missBadgeBackground?.transparent(1.3)}; diff --git a/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_4.0.snap b/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_4.0.snap index a7523744be8..f894d982979 100644 --- a/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_4.0.snap +++ b/src/vs/workbench/contrib/testing/test/browser/__snapshots__/Code_Coverage_Decorations_CoverageDetailsModel_4.0.snap @@ -1,18 +1,26 @@ [ { - range: "[2,0 -> 2,0]", - count: 2 - }, - { - range: "[1,0 -> 4,0]", + range: "[1,0 -> 2,0]", count: 1 }, { - range: "[4,3 -> 4,3]", + range: "[2,0 -> 2,2147483647]", + count: 2 + }, + { + range: "[2,2147483647 -> 4,0]", + count: 1 + }, + { + range: "[4,0 -> 4,3]", + count: 3 + }, + { + range: "[4,3 -> 4,2147483647]", count: 4 }, { - range: "[4,0 -> 5,0]", + range: "[4,2147483647 -> 5,0]", count: 3 } ] \ No newline at end of file From fe7233fdfb73dc63f3753e6c25795ea8c08a678f Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 18 Jan 2024 04:26:54 +0900 Subject: [PATCH 0390/1897] fix: avoid using libc as an executable test (#202680) --- resources/server/bin/helpers/check-requirements-linux.sh | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/resources/server/bin/helpers/check-requirements-linux.sh b/resources/server/bin/helpers/check-requirements-linux.sh index 23220930189..b366a190abf 100644 --- a/resources/server/bin/helpers/check-requirements-linux.sh +++ b/resources/server/bin/helpers/check-requirements-linux.sh @@ -81,13 +81,7 @@ if [ -n "$(ldd --version | grep -v musl)" ]; then # Rather than trusting the output of ldd --version (which is not always accurate) # we instead use the version of the cached libc.so.6 file itself. libc_real_path=$(readlink -f "$libc_path") - if [ -x "$libc_real_path" ]; then - # get version from executable - libc_version=$($libc_real_path --version | sed -n 's/.*release version \([0-9]\+\.[0-9]\+\).*/\1/p') - else - # .so is not executable on this host; try getting from strings - libc_version=$(cat "$libc_real_path" | sed -n 's/.*release version \([0-9]\+\.[0-9]\+\).*/\1/p') - fi + libc_version=$(cat "$libc_real_path" | sed -n 's/.*release version \([0-9]\+\.[0-9]\+\).*/\1/p') if [ "$(printf '%s\n' "2.28" "$libc_version" | sort -V | head -n1)" = "2.28" ]; then found_required_glibc=1 else From 48905201c0aeea80012eec4e22bae2bfe743d7dc Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 17 Jan 2024 20:58:54 +0100 Subject: [PATCH 0391/1897] Sanity Bounds for "window.zoomLevel" (fix #152028) (#202682) --- src/vs/platform/window/electron-sandbox/window.ts | 5 +++++ src/vs/workbench/electron-sandbox/actions/windowActions.ts | 7 ++----- src/vs/workbench/electron-sandbox/desktop.contribution.ts | 5 +++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/vs/platform/window/electron-sandbox/window.ts b/src/vs/platform/window/electron-sandbox/window.ts index d8a3301ff3c..ab0e479d1a3 100644 --- a/src/vs/platform/window/electron-sandbox/window.ts +++ b/src/vs/platform/window/electron-sandbox/window.ts @@ -14,11 +14,16 @@ export enum ApplyZoomTarget { ALL_WINDOWS } +export const MAX_ZOOM_LEVEL = 8; +export const MIN_ZOOM_LEVEL = -8; + /** * Apply a zoom level to the window. Also sets it in our in-memory * browser helper so that it can be accessed in non-electron layers. */ export function applyZoom(zoomLevel: number, target: ApplyZoomTarget | Window): void { + zoomLevel = Math.min(Math.max(zoomLevel, MIN_ZOOM_LEVEL), MAX_ZOOM_LEVEL); // cap zoom levels between -8 and 8 + const targetWindows: Window[] = []; if (target === ApplyZoomTarget.ACTIVE_WINDOW) { targetWindows.push(getActiveWindow()); diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index 8f5a4a5f0f0..2d49534034c 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/actions'; import { URI } from 'vs/base/common/uri'; import { localize, localize2 } from 'vs/nls'; -import { ApplyZoomTarget, applyZoom } from 'vs/platform/window/electron-sandbox/window'; +import { ApplyZoomTarget, MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL, applyZoom } from 'vs/platform/window/electron-sandbox/window'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { getZoomLevel } from 'vs/base/browser/browser'; import { FileKind } from 'vs/platform/files/common/files'; @@ -68,9 +68,6 @@ abstract class BaseZoomAction extends Action2 { private static readonly ZOOM_LEVEL_SETTING_KEY = 'window.zoomLevel'; private static readonly ZOOM_PER_WINDOW_SETTING_KEY = 'window.zoomPerWindow'; - private static readonly MAX_ZOOM_LEVEL = 8; - private static readonly MIN_ZOOM_LEVEL = -8; - constructor(desc: Readonly) { super(desc); } @@ -108,7 +105,7 @@ abstract class BaseZoomAction extends Action2 { level = Math.round(level); // when reaching smallest zoom, prevent fractional zoom levels - if (level > BaseZoomAction.MAX_ZOOM_LEVEL || level < BaseZoomAction.MIN_ZOOM_LEVEL) { + if (level > MAX_ZOOM_LEVEL || level < MIN_ZOOM_LEVEL) { return; // https://github.com/microsoft/vscode/issues/48357 } diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index f5f5772fccd..3887dd8856a 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -27,6 +27,7 @@ import { ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; import { ModifierKeyEmitter } from 'vs/base/browser/dom'; import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from 'vs/workbench/common/configuration'; +import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from 'vs/platform/window/electron-sandbox/window'; // Actions (function registerActions(): void { @@ -190,8 +191,8 @@ import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from 'window.zoomLevel': { 'type': 'number', 'default': 0, - 'minimum': -8, - 'maximum': 8, + 'minimum': MIN_ZOOM_LEVEL, + 'maximum': MAX_ZOOM_LEVEL, 'markdownDescription': localize({ comment: ['{0} will be a setting name rendered as a link'], key: 'zoomLevel' }, "Adjust the default zoom level for all windows. Each increment above `0` (e.g. `1`) or below (e.g. `-1`) represents zooming `20%` larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity. See {0} for configuring if the 'Zoom In' and 'Zoom Out' commands apply the zoom level to all windows or only the active window.", '`#window.zoomPerWindow#`'), ignoreSync: true, tags: ['accessibility'] From b0b0c2b0de7b7b179a438ec71171c3fca643898e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 17 Jan 2024 13:08:21 -0800 Subject: [PATCH 0392/1897] undo change --- src/vs/workbench/workbench.desktop.main.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 4e56e19903c..6ddcd72d1f2 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -170,9 +170,7 @@ import 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contributio import 'vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution'; // Chat -import 'vs/workbench/contrib/chat/electron-sandbox/chat.contribution'; -import 'vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution'; - +import 'vs/workbench/contrib/chat/electron-sandbox/voice.contribution'; //#endregion From 27dd62ac4d7b9183884c9a043fe5e315b55131d6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 17 Jan 2024 13:09:51 -0800 Subject: [PATCH 0393/1897] undo another change --- .../{voice.contribution.ts => chat.contribution.ts} | 0 src/vs/workbench/workbench.desktop.main.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/vs/workbench/contrib/chat/electron-sandbox/{voice.contribution.ts => chat.contribution.ts} (100%) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/voice.contribution.ts b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/chat/electron-sandbox/voice.contribution.ts rename to src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 6ddcd72d1f2..28715bf1613 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -170,7 +170,7 @@ import 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contributio import 'vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution'; // Chat -import 'vs/workbench/contrib/chat/electron-sandbox/voice.contribution'; +import 'vs/workbench/contrib/chat/electron-sandbox/chat.contribution'; //#endregion From 051c5d0107cd88236834e0e71f0970da4b4adc3c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 17 Jan 2024 13:14:46 -0800 Subject: [PATCH 0394/1897] Update src/vs/workbench/contrib/terminal/browser/terminal.ts --- src/vs/workbench/contrib/terminal/browser/terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 68fc67ea4e1..9a1bd1ab1b7 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -789,7 +789,7 @@ export interface ITerminalInstance extends IBaseTerminalInstance { /** * Registers and returns a marker - * @param offset The offset from the cursor + * @param the y offset from the cursor */ registerMarker(offset?: number): IMarker | undefined; From edf9855973766d62516f102a8d56b9d595f2c8a7 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 17 Jan 2024 13:15:50 -0800 Subject: [PATCH 0395/1897] Update src/vs/workbench/workbench.desktop.main.ts --- src/vs/workbench/workbench.desktop.main.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 28715bf1613..4e56e19903c 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -171,6 +171,8 @@ import 'vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribu // Chat import 'vs/workbench/contrib/chat/electron-sandbox/chat.contribution'; +import 'vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution'; + //#endregion From 675afadae9044af02ad5eebe181490b187983adc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 17 Jan 2024 13:44:45 -0800 Subject: [PATCH 0396/1897] try something --- .../contrib/chat/browser/chatListRenderer.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 3377ae546d0..fc10d019a1d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -781,8 +781,17 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { + const selection = list.getSelection(); + if (selection.length === 1) { + const selectionElement = selection[0]; + if (selectionElement) { + list.setFocus([selectionElement]); + } + } + }); listDisposables.add(list.onDidOpen((e) => { if (e.element) { this.editorService.openEditor({ From 8807e092d4648896c931fe2d0f94dd732fbf9304 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 17 Jan 2024 14:02:06 -0800 Subject: [PATCH 0397/1897] try something --- .../contrib/chat/browser/chatListRenderer.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index fc10d019a1d..efecc0b8a7d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -783,15 +783,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - const selection = list.getSelection(); - if (selection.length === 1) { - const selectionElement = selection[0]; - if (selectionElement) { - list.setFocus([selectionElement]); - } - } - }); listDisposables.add(list.onDidOpen((e) => { if (e.element) { this.editorService.openEditor({ @@ -1243,6 +1234,8 @@ class ContentReferencesListRenderer implements IListRenderer Date: Wed, 17 Jan 2024 14:02:50 -0800 Subject: [PATCH 0398/1897] try something --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index efecc0b8a7d..29b1f19538a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -1235,7 +1235,7 @@ class ContentReferencesListRenderer implements IListRenderer Date: Wed, 17 Jan 2024 14:07:57 -0800 Subject: [PATCH 0399/1897] try something --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 29b1f19538a..43c71b3cdec 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -1180,6 +1180,10 @@ class ContentReferencesListPool extends Disposable { [new ContentReferencesListRenderer(resourceLabels)], { alwaysConsumeMouseWheel: false, + accessibilityProvider: { + getAriaLabel: (element: IChatResponseProgressFileTreeData) => element.label, + getWidgetAriaLabel: () => localize('usedReferences', "Used References") + }, }); return list; From 8f5c0d5324565044104566c6b996217425d88603 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 17 Jan 2024 14:26:13 -0800 Subject: [PATCH 0400/1897] Update src/vs/workbench/contrib/chat/browser/chatListRenderer.ts --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 18c3a6a92f4..ed78c17b82f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -835,7 +835,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer Date: Wed, 17 Jan 2024 15:51:27 -0800 Subject: [PATCH 0401/1897] Fix markdown smart paste setting check (#202688) --- .../src/languageFeatures/copyFiles/pasteUrlProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts index c051683ce96..86fa146f1af 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts @@ -68,7 +68,7 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { workspaceEdit.set(document.uri, edit.edits); pasteEdit.additionalEdit = workspaceEdit; - if (!shouldInsertMarkdownLinkByDefault(this._parser, document, pasteUrlSetting, ranges, token)) { + if (!(await shouldInsertMarkdownLinkByDefault(this._parser, document, pasteUrlSetting, ranges, token))) { pasteEdit.yieldTo = [{ mimeType: Mime.textPlain }]; } From 895e218ac87fa536ca104eede0ba408b554354ab Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 17 Jan 2024 19:59:16 -0800 Subject: [PATCH 0402/1897] register disposables --- .../contrib/terminal/browser/terminalSpeechToText.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts index a548acc9877..81b8fe489ea 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts @@ -33,7 +33,7 @@ export class TerminalSpeechToTextSession extends Disposable { return TerminalSpeechToTextSession._instance; } private _cancellationTokenSource: CancellationTokenSource | undefined; - private _disposables = new DisposableStore(); + private readonly _disposables: DisposableStore; constructor( @ISpeechService private readonly _speechService: ISpeechService, @ITerminalService readonly _terminalService: ITerminalService, @@ -43,6 +43,7 @@ export class TerminalSpeechToTextSession extends Disposable { super(); this._register(this._terminalService.onDidChangeActiveInstance(() => this.stop())); this._register(this._terminalService.onDidDisposeInstance(() => this.stop())); + this._disposables = this._register(new DisposableStore()); } start(): void { @@ -55,7 +56,7 @@ export class TerminalSpeechToTextSession extends Disposable { this._terminalService.activeInstance?.sendText(this._input, false); this.stop(); }, voiceTimeout)); - this._cancellationTokenSource = new CancellationTokenSource(); + this._cancellationTokenSource = this._register(new CancellationTokenSource()); const session = this._disposables.add(this._speechService.createSpeechToTextSession(this._cancellationTokenSource!.token)); this._disposables.add(session.onDidChange((e) => { From b67e7f7b992c5663695e75424abb42b7aaacc805 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 17 Jan 2024 20:03:18 -0800 Subject: [PATCH 0403/1897] address feedback --- .../terminal/browser/terminalSpeechToText.ts | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts index 81b8fe489ea..fcdfdce86b7 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts @@ -17,6 +17,35 @@ import { IXtermMarker } from 'vs/platform/terminal/common/capabilities/capabilit import { ThemeIcon } from 'vs/base/common/themables'; import { Codicon } from 'vs/base/common/codicons'; +const symbolMap: { [key: string]: string } = { + 'Ampersand': '&', + 'ampersand': '&', + 'Dollar': '$', + 'dollar': '$', + 'Percent': '%', + 'percent': '%', + 'Asterisk': '*', + 'asterisk': '*', + 'Plus': '+', + 'plus': '+', + 'Equals': '=', + 'equals': '=', + 'Exclamation': '!', + 'exclamation': '!', + 'Slash': '/', + 'slash': '/', + 'Backslash': '\\', + 'backslash': '\\', + 'Dot': '.', + 'dot': '.', + 'Period': '.', + 'period': '.', + 'Quote': '\'', + 'quote': '\'', + 'double quote': '"', + 'Double quote': '"', +}; + export class TerminalSpeechToTextSession extends Disposable { private _input: string = ''; private _ghostText: IDecoration | undefined; @@ -111,37 +140,9 @@ export class TerminalSpeechToTextSession extends Disposable { private _updateInput(e: ISpeechToTextEvent): void { if (e.text) { let input = e.text.replaceAll(/[.,?;!]/g, ''); - const symbolMap: { [key: string]: string } = { - 'Ampersand': '&', - 'ampersand': '&', - 'Dollar': '$', - 'dollar': '$', - 'Percent': '%', - 'percent': '%', - 'Asterisk': '*', - 'asterisk': '*', - 'Plus': '+', - 'plus': '+', - 'Equals': '=', - 'equals': '=', - 'Exclamation': '!', - 'exclamation': '!', - 'Slash': '/', - 'slash': '/', - 'Backslash': '\\', - 'backslash': '\\', - 'Dot': '.', - 'dot': '.', - 'Period': '.', - 'period': '.', - 'Quote': '\'', - 'quote': '\'', - 'double quote': '"', - 'Double quote': '"', - }; - for (const symbol in symbolMap) { - const regex: RegExp = new RegExp(symbol); + for (const symbol of Object.values(symbolMap)) { + const regex: RegExp = new RegExp(symbol + '\b'); input = input.replace(regex, symbolMap[symbol]); } this._input = ' ' + input; From 74f9854bca7dccacae890e28fe2242d775f62cbf Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 17 Jan 2024 20:12:27 -0800 Subject: [PATCH 0404/1897] Refactor symbol replacement logic in TerminalSpeechToTextSession --- .../contrib/terminal/browser/terminalSpeechToText.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts index fcdfdce86b7..ec8c71ee363 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts @@ -140,10 +140,8 @@ export class TerminalSpeechToTextSession extends Disposable { private _updateInput(e: ISpeechToTextEvent): void { if (e.text) { let input = e.text.replaceAll(/[.,?;!]/g, ''); - - for (const symbol of Object.values(symbolMap)) { - const regex: RegExp = new RegExp(symbol + '\b'); - input = input.replace(regex, symbolMap[symbol]); + for (const symbol of Object.entries(symbolMap)) { + input = input.replace(new RegExp('\\b' + symbol[0] + '\\b'), symbol[1]); } this._input = ' ' + input; } From 0b571a26e213bf1ec6266e37152ced80c006bd1e Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 18 Jan 2024 01:14:36 -0300 Subject: [PATCH 0405/1897] Change ChatAgent history to include real requests, response data, variables, etc. (#202541) * Change ChatAgent history to include real requests, response data, variables, etc. For #199908 * Tweak * Update snapshots * Clean up * Update addCompleteRequest * Filter out other progress types * Fix chat slash command data in history * Don't include slashCommand in history request at all, since it's deprecated anyway * undefined --- .../api/browser/mainThreadChatAgents2.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 13 +++- .../api/common/extHostChatAgents2.ts | 50 ++++++++----- .../api/common/extHostTypeConverters.ts | 73 ++++++++++++++++++- .../contrib/chat/browser/chatQuick.ts | 1 + .../contrib/chat/browser/chatVariables.ts | 8 +- .../contrib/chat/browser/chatWidget.ts | 2 +- .../contrib/chat/common/chatAgents.ts | 15 +++- .../contrib/chat/common/chatModel.ts | 42 +++++++---- .../contrib/chat/common/chatService.ts | 4 +- .../contrib/chat/common/chatServiceImpl.ts | 38 +++++----- .../contrib/chat/common/chatVariables.ts | 9 +-- .../chat/test/browser/chatVariables.test.ts | 8 +- .../__snapshots__/Chat_can_deserialize.0.snap | 4 + .../__snapshots__/Chat_can_serialize.1.snap | 4 + .../chat/test/common/chatModel.test.ts | 2 +- .../chat/test/common/chatService.test.ts | 6 +- .../chat/test/common/mockChatVariables.ts | 8 +- .../browser/inlineChatController.ts | 2 +- .../inlineChat/browser/inlineChatWidget.ts | 2 +- .../vscode.proposed.chatAgents2.d.ts | 19 ++++- .../vscode.proposed.chatAgents2Additions.d.ts | 2 +- 22 files changed, 226 insertions(+), 88 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index 14f12dc5719..fc7271c1516 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -86,7 +86,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA invoke: async (request, progress, history, token) => { this._pendingProgress.set(request.requestId, progress); try { - return await this._proxy.$invokeAgent(handle, request.sessionId, request.requestId, request, { history }, token) ?? {}; + return await this._proxy.$invokeAgent(handle, request, { history }, token) ?? {}; } finally { this._pendingProgress.delete(request.requestId); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index e622ea5d57e..4f789f46598 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -51,6 +51,7 @@ import { SaveReason } from 'vs/workbench/common/editor'; import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; import { IChatAsyncContent, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -1201,8 +1202,18 @@ export interface IChatAgentCompletionItem { documentation?: string | IMarkdownString; } +export type IChatContentProgressDto = + | Dto> + | IChatAsyncContentDto; + +export type IChatAgentHistoryEntryDto = { + request: IChatAgentRequest; + response: ReadonlyArray; + result: IChatAgentResult; +}; + export interface ExtHostChatAgentsShape2 { - $invokeAgent(handle: number, sessionId: string, requestId: string, request: IChatAgentRequest, context: { history: IChatMessage[] }, token: CancellationToken): Promise; + $invokeAgent(handle: number, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; $provideSlashCommands(handle: number, token: CancellationToken): Promise; $provideFollowups(handle: number, sessionId: string, token: CancellationToken): Promise; $acceptFeedback(handle: number, sessionId: string, requestId: string, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index fe236452814..7832e02cbb1 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { coalesce } from 'vs/base/common/arrays'; import { DeferredPromise, raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -14,12 +15,11 @@ import { localize } from 'vs/nls'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { Progress } from 'vs/platform/progress/common/progress'; -import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { IChatAgentCommand, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { IChatFollowup, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import type * as vscode from 'vscode'; @@ -51,10 +51,10 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { return agent.apiAgent; } - async $invokeAgent(handle: number, sessionId: string, requestId: string, request: IChatAgentRequest, context: { history: IChatMessage[] }, token: CancellationToken): Promise { + async $invokeAgent(handle: number, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise { // Clear the previous result so that $acceptFeedback or $acceptAction during a request will be ignored. // We may want to support sending those during a request. - this._previousResultMap.delete(sessionId); + this._previousResultMap.delete(request.sessionId); const agent = this._agents.get(handle); if (!agent) { @@ -79,13 +79,10 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const stopWatch = StopWatch.create(false); let firstProgress: number | undefined; try { + const convertedHistory = await this.prepareHistory(agent, request, context); const task = agent.invoke( - { - prompt: request.message, - variables: typeConvert.ChatVariable.objectTo(request.variables), - slashCommand - }, - { history: context.history.map(typeConvert.ChatMessage.to) }, + typeConvert.ChatAgentRequest.to(request, slashCommand), + { history: convertedHistory }, new Progress(progress => { throwIfDone(); @@ -101,7 +98,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } if ('placeholder' in progress && 'resolvedContent' in progress) { - const resolvedContent = Promise.all([this._proxy.$handleProgressChunk(requestId, convertedProgress), progress.resolvedContent]); + const resolvedContent = Promise.all([this._proxy.$handleProgressChunk(request.requestId, convertedProgress), progress.resolvedContent]); raceCancellation(resolvedContent, token).then(res => { if (!res) { return; /* Cancelled */ @@ -113,10 +110,10 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { return; } - this._proxy.$handleProgressChunk(requestId, convertedContent, progressHandle ?? undefined); + this._proxy.$handleProgressChunk(request.requestId, convertedContent, progressHandle ?? undefined); }); } else { - this._proxy.$handleProgressChunk(requestId, convertedProgress); + this._proxy.$handleProgressChunk(request.requestId, convertedProgress); } }), token @@ -124,18 +121,18 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { return await raceCancellation(Promise.resolve(task).then((result) => { if (result) { - this._previousResultMap.set(sessionId, result); - let sessionResults = this._resultsBySessionAndRequestId.get(sessionId); + this._previousResultMap.set(request.sessionId, result); + let sessionResults = this._resultsBySessionAndRequestId.get(request.sessionId); if (!sessionResults) { sessionResults = new Map(); - this._resultsBySessionAndRequestId.set(sessionId, sessionResults); + this._resultsBySessionAndRequestId.set(request.sessionId, sessionResults); } - sessionResults.set(requestId, result); + sessionResults.set(request.requestId, result); const timings = { firstProgress: firstProgress, totalElapsed: stopWatch.elapsed() }; return { errorDetails: result.errorDetails, timings }; } else { - this._previousResultMap.delete(sessionId); + this._previousResultMap.delete(request.sessionId); } return undefined; @@ -151,6 +148,19 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } } + private async prepareHistory(agent: ExtHostChatAgent, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise { + return coalesce(await Promise.all(context.history + .map(async h => { + const result = request.agentId === h.request.agentId && this._resultsBySessionAndRequestId.get(request.sessionId)?.get(h.request.requestId) + || h.result; + return { + request: typeConvert.ChatAgentRequest.to(h.request, undefined), + response: coalesce(h.response.map(r => typeConvert.ChatResponseProgress.toProgressContent(r))), + result + } satisfies vscode.ChatAgentHistoryEntry; + }))); + } + $releaseSession(sessionId: string): void { this._previousResultMap.delete(sessionId); this._resultsBySessionAndRequestId.delete(sessionId); @@ -248,7 +258,7 @@ class ExtHostChatAgent { constructor( public readonly extension: IExtensionDescription, - private readonly _id: string, + public readonly id: string, private readonly _proxy: MainThreadChatAgentsShape2, private readonly _handle: number, private readonly _callback: vscode.ChatAgentExtendedHandler, @@ -352,7 +362,7 @@ class ExtHostChatAgent { const that = this; return { get name() { - return that._id; + return that.id; }, get description() { return that._description ?? ''; diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 9a12f53fb53..2170b91a053 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -11,7 +11,7 @@ import * as htmlContent from 'vs/base/common/htmlContent'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ResourceMap, ResourceSet } from 'vs/base/common/map'; import { marked } from 'vs/base/common/marked/marked'; -import { parse } from 'vs/base/common/marshalling'; +import { parse, revive } from 'vs/base/common/marshalling'; import { Mimes } from 'vs/base/common/mime'; import { cloneAndChange } from 'vs/base/common/objects'; import { isEmptyObject, isNumber, isString, isUndefinedOrNull } from 'vs/base/common/types'; @@ -34,6 +34,7 @@ import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; import { getPrivateApiFor } from 'vs/workbench/api/common/extHostTestingPrivateApi'; import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/editor'; import { IViewBadge } from 'vs/workbench/common/views'; +import { IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents'; import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; import { IChatFollowup, IChatReplyFollowup, IChatResponseCommandFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -46,6 +47,7 @@ import { CoverageDetails, DetailType, ICoveredCount, IFileCoverage, ISerializedT import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import type * as vscode from 'vscode'; import * as types from './extHostTypes'; @@ -120,6 +122,12 @@ export namespace Range { } } +export namespace Location { + export function to(location: Dto): vscode.Location { + return new types.Location(URI.revive(location.uri), Range.to(location.range)); + } +} + export namespace TokenType { export function to(type: encodedTokenAttributes.StandardTokenType): types.StandardTokenType { switch (type) { @@ -2368,6 +2376,69 @@ export namespace ChatResponseProgress { return undefined; } } + + export function to(progress: extHostProtocol.IChatProgressDto): vscode.ChatAgentProgress | undefined { + switch (progress.kind) { + case 'markdownContent': + case 'inlineReference': + case 'treeData': + return ChatResponseProgress.to(progress); + case 'content': + return { content: progress.content }; + case 'usedContext': + return { documents: progress.documents.map(d => ({ uri: URI.revive(d.uri), version: d.version, ranges: d.ranges.map(r => Range.to(r)) })) }; + case 'reference': + return { + reference: + isUriComponents(progress.reference) ? + URI.revive(progress.reference) : + Location.to(progress.reference) + }; + case 'agentDetection': + // For simplicity, don't sent back the 'extended' types + return undefined; + case 'progressMessage': + return { message: progress.content.value }; + case 'vulnerability': + return { content: progress.content, vulnerabilities: progress.vulnerabilities }; + default: + // Unknown type, eg something in history that was removed? Ignore + return undefined; + } + } + + export function toProgressContent(progress: extHostProtocol.IChatContentProgressDto): vscode.ChatAgentContentProgress | undefined { + switch (progress.kind) { + case 'markdownContent': + // For simplicity, don't sent back the 'extended' types, so downgrade markdown to just some text + return { content: progress.content.value }; + case 'inlineReference': + return { + inlineReference: + isUriComponents(progress.inlineReference) ? + URI.revive(progress.inlineReference) : + Location.to(progress.inlineReference), + title: progress.name + }; + case 'treeData': + return { treeData: revive(progress.treeData) }; + default: + // Unknown type, eg something in history that was removed? Ignore + return undefined; + } + } +} + +export namespace ChatAgentRequest { + export function to(request: IChatAgentRequest, slashCommand: vscode.ChatAgentSlashCommand | undefined): vscode.ChatAgentRequest { + return { + prompt: request.message, + variables: ChatVariable.objectTo(request.variables), + slashCommand, + subCommand: request.command, + agentId: request.agentId, + }; + } } export namespace ChatAgentCompletionItem { diff --git a/src/vs/workbench/contrib/chat/browser/chatQuick.ts b/src/vs/workbench/contrib/chat/browser/chatQuick.ts index c4e06196df2..a23257610dc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatQuick.ts +++ b/src/vs/workbench/contrib/chat/browser/chatQuick.ts @@ -292,6 +292,7 @@ class QuickChat extends Disposable { if (request.response?.response.value || request.response?.errorDetails) { this.chatService.addCompleteRequest(widget.viewModel.sessionId, request.message as IParsedChatRequest, + request.variableData, { message: request.response.response.value, errorDetails: request.response.errorDetails, diff --git a/src/vs/workbench/contrib/chat/browser/chatVariables.ts b/src/vs/workbench/contrib/chat/browser/chatVariables.ts index 578d5369667..3bb6fa6b7ca 100644 --- a/src/vs/workbench/contrib/chat/browser/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/chatVariables.ts @@ -9,9 +9,9 @@ import { Iterable } from 'vs/base/common/iterator'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatDynamicVariableModel } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables'; -import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest, ChatRequestVariablePart, ChatRequestDynamicVariablePart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatVariablesService, IChatRequestVariableValue, IChatVariableData, IChatVariableResolver, IChatVariableResolveResult, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { IChatVariablesService, IChatRequestVariableValue, IChatVariableData, IChatVariableResolver, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables'; interface IChatData { data: IChatVariableData; @@ -28,7 +28,7 @@ export class ChatVariablesService implements IChatVariablesService { ) { } - async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise { + async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise { const resolvedVariables: Record = {}; const jobs: Promise[] = []; @@ -62,7 +62,7 @@ export class ChatVariablesService implements IChatVariablesService { return { variables: resolvedVariables, - prompt: parsedPrompt.join('').trim() + message: parsedPrompt.join('').trim() }; } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index b4c02dc0b01..745ec7e1c04 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -539,7 +539,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const input = !opts ? editorValue : 'query' in opts ? opts.query : `${opts.prefix} ${editorValue}`; - const isUserQuery = !opts || 'query' in opts; + const isUserQuery = !opts || 'prefix' in opts; const result = await this.chatService.sendRequest(this.viewModel.sessionId, input); if (result) { diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 157e4be57a2..bab428dd38c 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -11,7 +11,7 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -22,8 +22,14 @@ export interface IChatAgentData { metadata: IChatAgentMetadata; } +export interface IChatAgentHistoryEntry { + request: IChatAgentRequest; + response: ReadonlyArray; + result: IChatAgentResult; +} + export interface IChatAgent extends IChatAgentData { - invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatMessage[], token: CancellationToken): Promise; + invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; provideFollowups?(sessionId: string, token: CancellationToken): Promise; provideSlashCommands(token: CancellationToken): Promise; } @@ -72,6 +78,7 @@ export interface IChatAgentMetadata { export interface IChatAgentRequest { sessionId: string; requestId: string; + agentId: string; command?: string; message: string; variables: Record; @@ -93,7 +100,7 @@ export interface IChatAgentService { _serviceBrand: undefined; readonly onDidChangeAgents: Event; registerAgent(agent: IChatAgent): IDisposable; - invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatMessage[], token: CancellationToken): Promise; + invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; getFollowups(id: string, sessionId: string, token: CancellationToken): Promise; getAgents(): Array; getAgent(id: string): IChatAgent | undefined; @@ -163,7 +170,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService { return data?.agent; } - async invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatMessage[], token: CancellationToken): Promise { + async invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { const data = this._agents.get(id); if (!data) { throw new Error(`No agent with id ${id}`); diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 503cdb3b762..0a3ebbaa0c8 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -17,6 +17,16 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatAsyncContent, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; + +export interface IChatRequestVariableData { + /** + * The user's message with variable references for extension API. + */ + message: string; + + variables: Record; +} export interface IChatRequestModel { readonly id: string; @@ -24,7 +34,8 @@ export interface IChatRequestModel { readonly avatarIconUri?: URI; readonly session: IChatModel; readonly message: IParsedChatRequest | IChatReplyFollowup; - readonly response: IChatResponseModel | undefined; + readonly variableData: IChatRequestVariableData; + readonly response?: IChatResponseModel; } export type IChatProgressResponseContent = @@ -84,7 +95,8 @@ export class ChatRequestModel implements IChatRequestModel { constructor( public readonly session: ChatModel, - public readonly message: IParsedChatRequest) { + public readonly message: IParsedChatRequest, + public readonly variableData: IChatRequestVariableData) { this._id = 'request_' + ChatRequestModel.nextId++; } } @@ -239,12 +251,10 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel private _followups?: IChatFollowup[]; - private _agent: IChatAgentData | undefined; public get agent(): IChatAgentData | undefined { return this._agent; } - private _slashCommand: IChatAgentCommand | undefined; public get slashCommand(): IChatAgentCommand | undefined { return this._slashCommand; } @@ -267,7 +277,8 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel constructor( _response: IMarkdownString | ReadonlyArray, public readonly session: ChatModel, - agent: IChatAgentData | undefined, + private _agent: IChatAgentData | undefined, + private _slashCommand: IChatAgentCommand | undefined, public readonly requestId: string, private _isComplete: boolean = false, private _isCanceled = false, @@ -276,7 +287,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel followups?: ReadonlyArray ) { super(); - this._agent = agent; this._followups = followups ? [...followups] : undefined; this._response = new Response(_response); this._register(this._response.onDidChangeValue(() => this._onDidChange.fire())); @@ -364,7 +374,8 @@ export interface ISerializableChatsData { export type ISerializableChatAgentData = UriDto; export interface ISerializableChatRequestData { - message: string | IParsedChatRequest; + message: string | IParsedChatRequest; // string => old format + variableData: IChatRequestVariableData; // make optional response: ReadonlyArray | undefined; agent?: ISerializableChatAgentData; slashCommand?: IChatAgentCommand; @@ -549,11 +560,13 @@ export class ChatModel extends Disposable implements IChatModel { typeof raw.message === 'string' ? this.getParsedRequestFromString(raw.message) : reviveParsedChatRequest(raw.message); - const request = new ChatRequestModel(this, parsedRequest); + // Only old messages don't have variableData + const variableData: IChatRequestVariableData = raw.variableData ?? { message: parsedRequest.text, variables: {} }; + const request = new ChatRequestModel(this, parsedRequest, variableData); if (raw.response || raw.responseErrorDetails) { const agent = (raw.agent && 'metadata' in raw.agent) ? // Check for the new format, ignore entries in the old format revive(raw.agent) : undefined; - request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, request.id, true, raw.isCanceled, raw.vote, raw.responseErrorDetails, raw.followups); + request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, raw.slashCommand, request.id, true, raw.isCanceled, raw.vote, raw.responseErrorDetails, raw.followups); if (raw.usedContext) { // @ulugbekna: if this's a new vscode sessions, doc versions are incorrect anyway? request.response.applyReference(raw.usedContext); } @@ -627,13 +640,13 @@ export class ChatModel extends Disposable implements IChatModel { return this._requests; } - addRequest(message: IParsedChatRequest, chatAgent?: IChatAgentData): ChatRequestModel { + addRequest(message: IParsedChatRequest, variableData: IChatRequestVariableData, chatAgent?: IChatAgentData, slashCommand?: IChatAgentCommand): ChatRequestModel { if (!this._session) { throw new Error('addRequest: No session'); } - const request = new ChatRequestModel(this, message); - request.response = new ChatResponseModel([], this, chatAgent, request.id); + const request = new ChatRequestModel(this, message, variableData); + request.response = new ChatResponseModel([], this, chatAgent, slashCommand, request.id); this._requests.push(request); this._onDidChange.fire({ kind: 'addRequest', request }); @@ -646,7 +659,7 @@ export class ChatModel extends Disposable implements IChatModel { } if (!request.response) { - request.response = new ChatResponseModel([], this, undefined, request.id); + request.response = new ChatResponseModel([], this, undefined, undefined, request.id); } if (request.response.isComplete) { @@ -693,7 +706,7 @@ export class ChatModel extends Disposable implements IChatModel { } if (!request.response) { - request.response = new ChatResponseModel([], this, undefined, request.id); + request.response = new ChatResponseModel([], this, undefined, undefined, request.id); } request.response.setErrorDetails(rawResponse.errorDetails); @@ -737,6 +750,7 @@ export class ChatModel extends Disposable implements IChatModel { requests: this._requests.map((r): ISerializableChatRequestData => { return { message: r.message, + variableData: r.variableData, response: r.response ? r.response.response.value.map(item => { // Keeping the shape of the persisted data the same for back compat diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 8239ec32294..f16e5f1ce39 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -13,7 +13,7 @@ import { Location, ProviderResult } from 'vs/editor/common/languages'; import { FileType } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { ChatModel, IChatModel, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatModel, IChatModel, IChatRequestVariableData, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -311,7 +311,7 @@ export interface IChatService { removeRequest(sessionid: string, requestId: string): Promise; cancelCurrentRequestForSession(sessionId: string): void; clearSession(sessionId: string): void; - addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, response: IChatCompleteResponse): void; + addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, response: IChatCompleteResponse): void; sendRequestToProvider(sessionId: string, message: IChatDynamicRequest): void; getHistory(): IChatDetail[]; clearAllHistoryEntries(): void; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 1de767f97a0..77db6de1d07 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -20,13 +20,13 @@ 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 { IChatAgentCommand, IChatAgentData, IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatResponse, IChatService, IChatTransferredSessionData, IChatUserActionEvent, ChatAgentCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatResponse, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -514,29 +514,33 @@ export class ChatService extends Disposable implements IChatService { const defaultAgent = this.chatAgentService.getDefaultAgent(); if (agentPart || (defaultAgent && !commandPart)) { const agent = (agentPart?.agent ?? defaultAgent)!; - const history: IChatMessage[] = []; + const history: IChatAgentHistoryEntry[] = []; for (const request of model.getRequests()) { if (!request.response) { continue; } - history.push({ role: ChatMessageRole.User, content: request.message.text }); - history.push({ role: ChatMessageRole.Assistant, content: request.response.response.asString() }); + const historyRequest: IChatAgentRequest = { + sessionId, + requestId: request.id, + agentId: request.response.agent?.id ?? '', + message: request.variableData.message, + variables: request.variableData.variables, + command: request.response.slashCommand?.name + }; + history.push({ request: historyRequest, response: request.response.response.value, result: { errorDetails: request.response.errorDetails } }); } - request = model.addRequest(parsedRequest, agent); + const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); + request = model.addRequest(parsedRequest, variableData, agent, agentSlashCommandPart?.command); const requestProps: IChatAgentRequest = { sessionId, requestId: request.id, - message, - variables: {}, + agentId: agent.id, + message: variableData.message, + variables: variableData.variables, command: agentSlashCommandPart?.command.name ?? '', }; - if ('parts' in parsedRequest) { - const varResult = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); - requestProps.variables = varResult.variables; - requestProps.message = varResult.prompt; - } const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); rawResponse = { @@ -547,7 +551,7 @@ export class ChatService extends Disposable implements IChatService { agentOrCommandFollowups = agentResult?.followUp ? Promise.resolve(agentResult.followUp) : this.chatAgentService.getFollowups(agent.id, sessionId, CancellationToken.None); } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { - request = model.addRequest(parsedRequest); + request = model.addRequest(parsedRequest, { message, variables: {} }); // contributed slash commands // TODO: spell this out in the UI const history: IChatMessage[] = []; @@ -639,7 +643,7 @@ export class ChatService extends Disposable implements IChatService { return Array.from(this._providers.keys()); } - async addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, response: IChatCompleteResponse): Promise { + async addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, response: IChatCompleteResponse): Promise { this.trace('addCompleteRequest', `message: ${message}`); const model = this._sessionModels.get(sessionId); @@ -651,7 +655,7 @@ export class ChatService extends Disposable implements IChatService { const parsedRequest = typeof message === 'string' ? await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message) : message; - const request = model.addRequest(parsedRequest); + const request = model.addRequest(parsedRequest, variableData || { message: parsedRequest.text, variables: {} }); if (typeof response.message === 'string') { model.acceptResponseProgress(request, { content: response.message, kind: 'content' }); } else { diff --git a/src/vs/workbench/contrib/chat/common/chatVariables.ts b/src/vs/workbench/contrib/chat/common/chatVariables.ts index 951e5bcf161..f95b9da120e 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariables.ts @@ -8,7 +8,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IRange } from 'vs/editor/common/core/range'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; export interface IChatVariableData { @@ -42,12 +42,7 @@ export interface IChatVariablesService { /** * Resolves all variables that occur in `prompt` */ - resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise; -} - -export interface IChatVariableResolveResult { - variables: Record; - prompt: string; + resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise; } export interface IDynamicVariable { diff --git a/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts index 81517985bde..bda3f40baac 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts @@ -49,13 +49,13 @@ suite('ChatVariables', function () { const data = await resolveVariables('Hello #foo and#far'); assert.strictEqual(Object.keys(data.variables).length, 1); assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); - assert.strictEqual(data.prompt, 'Hello [#foo](values:foo) and#far'); + assert.strictEqual(data.message, 'Hello [#foo](values:foo) and#far'); } { const data = await resolveVariables('#foo Hello'); assert.strictEqual(Object.keys(data.variables).length, 1); assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); - assert.strictEqual(data.prompt, '[#foo](values:foo) Hello'); + assert.strictEqual(data.message, '[#foo](values:foo) Hello'); } { const data = await resolveVariables('Hello #foo'); @@ -66,7 +66,7 @@ suite('ChatVariables', function () { const data = await resolveVariables('Hello #foo?'); assert.strictEqual(Object.keys(data.variables).length, 1); assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); - assert.strictEqual(data.prompt, 'Hello [#foo](values:foo)?'); + assert.strictEqual(data.message, 'Hello [#foo](values:foo)?'); } { const data = await resolveVariables('Hello #foo and#far #foo'); @@ -82,7 +82,7 @@ suite('ChatVariables', function () { const data = await resolveVariables('Hello #foo and #far #foo #unknown'); assert.strictEqual(Object.keys(data.variables).length, 2); assert.deepEqual(Object.keys(data.variables).sort(), ['far', 'foo']); - assert.strictEqual(data.prompt, 'Hello [#foo](values:foo) and [#far](values:far) [#foo](values:foo) #unknown'); + assert.strictEqual(data.message, 'Hello [#foo](values:foo) and [#far](values:far) [#foo](values:foo) #unknown'); } v1.dispose(); diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap index 223b9763bfe..e9ee90ae021 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap @@ -44,6 +44,10 @@ } ] }, + variableData: { + message: "@ChatProviderWithUsedContext test request", + variables: { } + }, response: [ ], responseErrorDetails: undefined, followups: [ ], diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap index fbeb0fa885f..a332c9b3af7 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap @@ -44,6 +44,10 @@ ], text: "@ChatProviderWithUsedContext test request" }, + variableData: { + message: "@ChatProviderWithUsedContext test request", + variables: { } + }, response: [ ], responseErrorDetails: undefined, followups: [ ], diff --git a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index 60197332945..7ffcef4448a 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -112,7 +112,7 @@ suite('ChatModel', () => { model.startInitialize(); model.initialize({} as any, undefined); const text = 'hello'; - model.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }); + model.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }, { message: text, variables: {} }); const requests = model.getRequests(); assert.strictEqual(requests.length, 1); 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 a69148ce360..4e69d04418e 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -123,11 +123,11 @@ suite('Chat', () => { const session1 = testDisposables.add(testService.startSession('provider1', CancellationToken.None)); await session1.waitForInitialization(); - session1!.addRequest({ parts: [], text: 'request 1' }); + session1!.addRequest({ parts: [], text: 'request 1' }, { message: 'request 1', variables: {} }); const session2 = testDisposables.add(testService.startSession('provider2', CancellationToken.None)); await session2.waitForInitialization(); - session2!.addRequest({ parts: [], text: 'request 2' }); + session2!.addRequest({ parts: [], text: 'request 2' }, { message: 'request 2', variables: {} }); storageService.flush(); const testService2 = testDisposables.add(instantiationService.createInstance(ChatService)); @@ -208,7 +208,7 @@ suite('Chat', () => { const model = testDisposables.add(testService.startSession('testProvider', CancellationToken.None)); assert.strictEqual(model.getRequests().length, 0); - await testService.addCompleteRequest(model.sessionId, 'test request', { message: 'test response' }); + await testService.addCompleteRequest(model.sessionId, 'test request', undefined, { message: 'test response' }); assert.strictEqual(model.getRequests().length, 1); assert.ok(model.getRequests()[0].response); assert.strictEqual(model.getRequests()[0].response?.response.asString(), 'test response'); diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts b/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts index f13d1154145..73d64ca339b 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts @@ -5,9 +5,9 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatVariableData, IChatVariableResolveResult, IChatVariableResolver, IChatVariablesService, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { IChatVariableData, IChatVariableResolver, IChatVariablesService, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables'; export class MockChatVariablesService implements IChatVariablesService { _serviceBrand: undefined; @@ -27,9 +27,9 @@ export class MockChatVariablesService implements IChatVariablesService { return []; } - async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise { + async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise { return { - prompt: prompt.text, + message: prompt.text, variables: {} }; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index be3aa770913..4a7fce76f96 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -1190,7 +1190,7 @@ async function showMessageResponse(accessor: ServicesAccessor, query: string, re const chatWidgetService = accessor.get(IChatWidgetService); const widget = await chatWidgetService.revealViewForProvider(providerId); if (widget && widget.viewModel) { - chatService.addCompleteRequest(widget.viewModel.sessionId, query, { message: response }); + chatService.addCompleteRequest(widget.viewModel.sessionId, query, undefined, { message: response }); widget.focusLastMessage(); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index f0509750b63..fa5e8dc762c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -597,7 +597,7 @@ export class InlineChatWidget { expansionState = ExpansionState.NOT_CROPPED; } else { const sessionModel = this._chatMessageDisposables.add(new ChatModel(message.providerId, undefined, this._logService, this._chatAgentService)); - const responseModel = this._chatMessageDisposables.add(new ChatResponseModel(message.message, sessionModel, undefined, message.requestId, !isIncomplete, false, undefined)); + const responseModel = this._chatMessageDisposables.add(new ChatResponseModel(message.message, sessionModel, undefined, undefined, message.requestId, !isIncomplete, false, undefined)); const viewModel = this._chatMessageDisposables.add(new ChatResponseViewModel(responseModel, this._logService)); const renderOptions: IChatListItemRendererOptions = { renderStyle: 'compact', noHeader: true, noPadding: true }; const chatRendererDelegate: IChatRendererDelegate = { getListLength() { return 1; } }; diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 172f4df6d67..bcc565500c9 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -5,11 +5,17 @@ declare module 'vscode' { + export interface ChatAgentHistoryEntry { + request: ChatAgentRequest; + response: ChatAgentContentProgress[]; + result: ChatAgentResult2; + } + export interface ChatAgentContext { /** * All of the chat messages so far in the current chat session. */ - history: ChatMessage[]; + history: ChatAgentHistoryEntry[]; } /** @@ -241,12 +247,23 @@ declare module 'vscode' { */ prompt: string; + /** + * The ID of the chat agent to which this request was directed. + */ + agentId: string; + /** * The {@link ChatAgentSlashCommand slash command} that was selected for this request. It is guaranteed that the passed slash * command is an instance that was previously returned from the {@link ChatAgentSlashCommandProvider.provideSlashCommands slash command provider}. + * @deprecated this will be replaced by `subCommand` */ slashCommand?: ChatAgentSlashCommand; + /** + * The name of the {@link ChatAgentSlashCommand slash command} that was selected for this request. + */ + subCommand?: string; + variables: Record; } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index 146ba3bbe92..2794d273dd8 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -111,7 +111,7 @@ declare module 'vscode' { export interface ChatAgentCommandAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'command'; - command: any; // ChatAgentCommandButton; + command: ChatAgentCommandFollowup; } export interface ChatAgentSessionFollowupAction { From 3695bca69f984867060244fb058c46d89865d942 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 18 Jan 2024 11:55:15 +0530 Subject: [PATCH 0406/1897] fix #199736 (#202703) --- .../configuration/browser/configurationService.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 58761151288..4d27af9fb2d 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -104,7 +104,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private readonly configurationRegistry: IConfigurationRegistry; private instantiationService: IInstantiationService | undefined; - private configurationEditing: ConfigurationEditing | undefined; + private configurationEditing: Promise | undefined; constructor( { remoteAuthority, configurationCache }: { remoteAuthority?: string; configurationCache: IConfigurationCache }, @@ -1030,8 +1030,8 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } // Use same instance of ConfigurationEditing to make sure all writes go through the same queue - this.configurationEditing = this.configurationEditing ?? this.instantiationService.createInstance(ConfigurationEditing, (await this.remoteAgentService.getEnvironment())?.settingsPath ?? null); - await this.configurationEditing.writeConfiguration(editableConfigurationTarget, { key, value }, { scopes: overrides, ...options }); + this.configurationEditing = this.configurationEditing ?? this.createConfigurationEditingService(this.instantiationService); + await (await this.configurationEditing).writeConfiguration(editableConfigurationTarget, { key, value }, { scopes: overrides, ...options }); switch (editableConfigurationTarget) { case EditableConfigurationTarget.USER_LOCAL: if (this.applicationConfiguration && this.isSettingAppliedForAllProfiles(key)) { @@ -1053,6 +1053,11 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } } + private async createConfigurationEditingService(instantiationService: IInstantiationService): Promise { + const remoteSettingsResource = (await this.remoteAgentService.getEnvironment())?.settingsPath ?? null; + return instantiationService.createInstance(ConfigurationEditing, remoteSettingsResource); + } + private getConfigurationModelForEditableConfigurationTarget(target: EditableConfigurationTarget, resource?: URI | null): ConfigurationModel | undefined { switch (target) { case EditableConfigurationTarget.USER_LOCAL: return this._configuration.localUserConfiguration; From 5eac27c2ed726889ebb227f0dcc9ceb578fe6d94 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 18 Jan 2024 13:01:49 +0530 Subject: [PATCH 0407/1897] report single event (#202705) --- .../browser/configurationService.ts | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 4d27af9fb2d..6b842ab9400 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -15,7 +15,7 @@ import { ConfigurationModel, ConfigurationChangeEvent, mergeChanges } from 'vs/p import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString, IConfigurationUpdateOverrides, isConfigurationUpdateOverrides, IConfigurationService, IConfigurationUpdateOptions } from 'vs/platform/configuration/common/configuration'; import { IPolicyConfiguration, NullPolicyConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; -import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings, PROFILE_SCOPES, LOCAL_MACHINE_PROFILE_SCOPES, profileSettingsSchemaId, APPLY_ALL_PROFILES_SETTING } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings, PROFILE_SCOPES, LOCAL_MACHINE_PROFILE_SCOPES, profileSettingsSchemaId, APPLY_ALL_PROFILES_SETTING, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema, keyFromOverrideIdentifiers, OVERRIDE_PROPERTY_PATTERN, resourceLanguageSettingsSchemaId, configurationDefaultsSchemaId } from 'vs/platform/configuration/common/configurationRegistry'; import { IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, getStoredWorkspaceFolder, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; @@ -1331,39 +1331,37 @@ class ConfigurationTelemetryContribution extends Disposable implements IWorkbenc constructor( @IConfigurationService private readonly configurationService: WorkspaceService, - @ITelemetryService private readonly telemetryService: ITelemetryService, + @ITelemetryService telemetryService: ITelemetryService, ) { super(); - const { user, workspace } = configurationService.keys(); - for (const key of user) { - this.reportConfiguration(key, ConfigurationTarget.USER_LOCAL); + const updatedUserSettings: { [key: string]: any } = {}; + for (const k of user) { + if (!k.startsWith(`${TASKS_CONFIGURATION_KEY}.`) && !k.startsWith(`${LAUNCH_CONFIGURATION_KEY}.`)) { + updatedUserSettings[k] = this.getValueToReport(k, ConfigurationTarget.USER_LOCAL) ?? ''; + } } - for (const key of workspace) { - this.reportConfiguration(key, ConfigurationTarget.WORKSPACE); + const updatedWorkspaceSettings: { [key: string]: any } = {}; + for (const k of workspace) { + if (!k.startsWith(`${TASKS_CONFIGURATION_KEY}.`) && !k.startsWith(`${LAUNCH_CONFIGURATION_KEY}.`)) { + updatedWorkspaceSettings[k] = this.getValueToReport(k, ConfigurationTarget.WORKSPACE) ?? ''; + } } - } - - private reportConfiguration(key: string, target: ConfigurationTarget): void { - type UpdateConfigurationClassification = { + type UpdatedSettingsClassification = { owner: 'sandy081'; - comment: 'Event which fires for updated configurations'; - source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'What configuration file was updated i.e user or workspace' }; - key: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'What configuration key was updated' }; - value?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Value of the key that was updated' }; + comment: 'Event reporting updated settings'; + settings: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'stringified updated settings object' }; }; - type UpdateConfigurationEvent = { - source: string; - key: string; - value?: any; + type UpdatedSettingsEvent = { + settings: string; }; - this.telemetryService.publicLog2('updateConfiguration', { - source: ConfigurationTargetToString(target), - key, - value: this.getValueToReport(key, target) - }); + telemetryService.publicLog2('updatedsettings:user', { settings: JSON.stringify(updatedUserSettings) }); + telemetryService.publicLog2('updatedsettings:workspace', { settings: JSON.stringify(updatedWorkspaceSettings) }); } + /** + * Report value of a setting only if it is an enum, boolean, or number or an array of those. + */ private getValueToReport(key: string, target: ConfigurationTarget): any { const schema = this.configurationRegistry.getConfigurationProperties()[key]; if (!schema) { From b0f0dcb54454760d475efee8192de588c1ddd320 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 18 Jan 2024 12:19:45 +0100 Subject: [PATCH 0408/1897] window - fix fullscreen detection (#202714) Only use `detectFullScreen` in web as it seems to return bad results on desktop. --- .../auxiliaryWindow/electron-main/auxiliaryWindows.ts | 2 +- .../electron-main/auxiliaryWindowsMainService.ts | 6 +++--- src/vs/platform/native/common/native.ts | 2 +- .../native/electron-main/nativeHostMainService.ts | 4 ++-- src/vs/platform/windows/electron-main/windows.ts | 2 +- .../windows/electron-main/windowsMainService.ts | 6 +++--- .../browser/parts/editor/auxiliaryEditorPart.ts | 6 +++--- src/vs/workbench/browser/window.ts | 6 +++--- .../services/host/browser/browserHostService.ts | 10 +++++----- src/vs/workbench/services/host/browser/host.ts | 2 +- .../host/electron-sandbox/nativeHostService.ts | 2 +- src/vs/workbench/test/browser/workbenchTestServices.ts | 2 +- 12 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts index c67d3eac6fa..d437cc7f6ba 100644 --- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts +++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts @@ -17,7 +17,7 @@ export interface IAuxiliaryWindowsMainService { readonly onDidMaximizeWindow: Event; readonly onDidUnmaximizeWindow: Event; - readonly onDidChangeFullScreen: Event; + readonly onDidChangeFullScreen: Event<{ window: IAuxiliaryWindow; fullscreen: boolean }>; readonly onDidTriggerSystemContextMenu: Event<{ readonly window: IAuxiliaryWindow; readonly x: number; readonly y: number }>; createWindow(): BrowserWindowConstructorOptions; diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts index f87d8078a26..d0afe918cf4 100644 --- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts +++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts @@ -24,7 +24,7 @@ export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliar private readonly _onDidUnmaximizeWindow = this._register(new Emitter()); readonly onDidUnmaximizeWindow = this._onDidUnmaximizeWindow.event; - private readonly _onDidChangeFullScreen = this._register(new Emitter()); + private readonly _onDidChangeFullScreen = this._register(new Emitter<{ window: IAuxiliaryWindow; fullscreen: boolean }>()); readonly onDidChangeFullScreen = this._onDidChangeFullScreen.event; private readonly _onDidTriggerSystemContextMenu = this._register(new Emitter<{ window: IAuxiliaryWindow; x: number; y: number }>()); @@ -88,8 +88,8 @@ export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliar disposables.add(auxiliaryWindow.onDidMaximize(() => this._onDidMaximizeWindow.fire(auxiliaryWindow))); disposables.add(auxiliaryWindow.onDidUnmaximize(() => this._onDidUnmaximizeWindow.fire(auxiliaryWindow))); - disposables.add(auxiliaryWindow.onDidEnterFullScreen(() => this._onDidChangeFullScreen.fire(auxiliaryWindow))); - disposables.add(auxiliaryWindow.onDidLeaveFullScreen(() => this._onDidChangeFullScreen.fire(auxiliaryWindow))); + disposables.add(auxiliaryWindow.onDidEnterFullScreen(() => this._onDidChangeFullScreen.fire({ window: auxiliaryWindow, fullscreen: true }))); + disposables.add(auxiliaryWindow.onDidLeaveFullScreen(() => this._onDidChangeFullScreen.fire({ window: auxiliaryWindow, fullscreen: false }))); disposables.add(auxiliaryWindow.onDidTriggerSystemContextMenu(({ x, y }) => this._onDidTriggerSystemContextMenu.fire({ window: auxiliaryWindow, x, y }))); Event.once(auxiliaryWindow.onDidClose)(() => disposables.dispose()); diff --git a/src/vs/platform/native/common/native.ts b/src/vs/platform/native/common/native.ts index 106505e908a..239dcad9322 100644 --- a/src/vs/platform/native/common/native.ts +++ b/src/vs/platform/native/common/native.ts @@ -53,7 +53,7 @@ export interface ICommonNativeHostService { readonly onDidFocusMainWindow: Event; readonly onDidBlurMainWindow: Event; - readonly onDidChangeWindowFullScreen: Event; + readonly onDidChangeWindowFullScreen: Event<{ windowId: number; fullscreen: boolean }>; readonly onDidFocusMainOrAuxiliaryWindow: Event; readonly onDidBlurMainOrAuxiliaryWindow: Event; diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index 5d73705288b..a9f4b9ed735 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -94,8 +94,8 @@ export class NativeHostMainService extends Disposable implements INativeHostMain ); readonly onDidChangeWindowFullScreen = Event.any( - Event.map(this.windowsMainService.onDidChangeFullScreen, window => window.id), - Event.map(this.auxiliaryWindowsMainService.onDidChangeFullScreen, window => window.id) + Event.map(this.windowsMainService.onDidChangeFullScreen, e => ({ windowId: e.window.id, fullscreen: e.fullscreen })), + Event.map(this.auxiliaryWindowsMainService.onDidChangeFullScreen, e => ({ windowId: e.window.id, fullscreen: e.fullscreen })) ); readonly onDidBlurMainWindow = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-blur', (event, window: BrowserWindow) => window.id), windowId => !!this.windowsMainService.getWindowById(windowId)); diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index a658f3fa5ba..5fe00f09595 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -32,7 +32,7 @@ export interface IWindowsMainService { readonly onDidSignalReadyWindow: Event; readonly onDidMaximizeWindow: Event; readonly onDidUnmaximizeWindow: Event; - readonly onDidChangeFullScreen: Event; + readonly onDidChangeFullScreen: Event<{ window: ICodeWindow; fullscreen: boolean }>; readonly onDidTriggerSystemContextMenu: Event<{ readonly window: ICodeWindow; readonly x: number; readonly y: number }>; readonly onDidDestroyWindow: Event; diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 32019b8b42c..11ea40f1325 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -198,7 +198,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private readonly _onDidUnmaximizeWindow = this._register(new Emitter()); readonly onDidUnmaximizeWindow = this._onDidUnmaximizeWindow.event; - private readonly _onDidChangeFullScreen = this._register(new Emitter()); + private readonly _onDidChangeFullScreen = this._register(new Emitter<{ window: ICodeWindow; fullscreen: boolean }>()); readonly onDidChangeFullScreen = this._onDidChangeFullScreen.event; private readonly _onDidTriggerSystemContextMenu = this._register(new Emitter<{ window: ICodeWindow; x: number; y: number }>()); @@ -1500,8 +1500,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic disposables.add(Event.once(createdWindow.onDidDestroy)(() => this.onWindowDestroyed(createdWindow))); disposables.add(createdWindow.onDidMaximize(() => this._onDidMaximizeWindow.fire(createdWindow))); disposables.add(createdWindow.onDidUnmaximize(() => this._onDidUnmaximizeWindow.fire(createdWindow))); - disposables.add(createdWindow.onDidEnterFullScreen(() => this._onDidChangeFullScreen.fire(createdWindow))); - disposables.add(createdWindow.onDidLeaveFullScreen(() => this._onDidChangeFullScreen.fire(createdWindow))); + disposables.add(createdWindow.onDidEnterFullScreen(() => this._onDidChangeFullScreen.fire({ window: createdWindow, fullscreen: true }))); + disposables.add(createdWindow.onDidLeaveFullScreen(() => this._onDidChangeFullScreen.fire({ window: createdWindow, fullscreen: false }))); disposables.add(createdWindow.onDidTriggerSystemContextMenu(({ x, y }) => this._onDidTriggerSystemContextMenu.fire({ window: createdWindow, x, y }))); const webContents = assertIsDefined(createdWindow.win?.webContents); diff --git a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts index d50e62f6095..03596d07bfa 100644 --- a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { onDidChangeFullscreen, onDidChangeZoomLevel } from 'vs/base/browser/browser'; -import { detectFullscreen, hide, show } from 'vs/base/browser/dom'; +import { isFullscreen, onDidChangeFullscreen, onDidChangeZoomLevel } from 'vs/base/browser/browser'; +import { hide, show } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isNative } from 'vs/base/common/platform'; @@ -134,7 +134,7 @@ export class AuxiliaryEditorPart { // Make sure to hide the custom title when we enter // fullscren mode and show it when we lave it. - const fullscreen = detectFullscreen(auxiliaryWindow.window); + const fullscreen = isFullscreen(auxiliaryWindow.window); const oldTitlebarPartVisible = titlebarPartVisible; titlebarPartVisible = !fullscreen; if (titlebarPart && oldTitlebarPartVisible !== titlebarPartVisible) { diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index 9d5416d007c..17354e5f403 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isSafari, setFullscreen } from 'vs/base/browser/browser'; -import { addDisposableListener, detectFullscreen, EventHelper, EventType, getActiveWindow, getWindow, getWindowById, getWindows, getWindowsCount, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom'; +import { addDisposableListener, EventHelper, EventType, getActiveWindow, getWindow, getWindowById, getWindows, getWindowsCount, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { HidDeviceData, requestHidDevice, requestSerialPort, requestUsbDevice, SerialPortData, UsbDeviceData } from 'vs/base/browser/deviceAccess'; import { timeout } from 'vs/base/common/async'; @@ -136,11 +136,11 @@ export abstract class BaseWindow extends Disposable { //#endregion private registerFullScreenListeners(targetWindowId: number): void { - this._register(this.hostService.onDidChangeFullScreen(windowId => { + this._register(this.hostService.onDidChangeFullScreen(({ windowId, fullscreen }) => { if (windowId === targetWindowId) { const targetWindow = getWindowById(targetWindowId); if (targetWindow) { - setFullscreen(!!detectFullscreen(targetWindow.window), targetWindow.window); + setFullscreen(fullscreen, targetWindow.window); } } })); diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 982ba91d8ce..79c91e10dba 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -15,7 +15,7 @@ import { whenEditorClosed } from 'vs/workbench/browser/editor'; import { IWorkspace, IWorkspaceProvider } from 'vs/workbench/browser/web.api'; import { IFileService } from 'vs/platform/files/common/files'; import { ILabelService, Verbosity } from 'vs/platform/label/common/label'; -import { EventType, ModifierKeyEmitter, addDisposableListener, addDisposableThrottledListener, disposableWindowInterval, getActiveDocument, getWindowId, onDidRegisterWindow, trackFocus } from 'vs/base/browser/dom'; +import { EventType, ModifierKeyEmitter, addDisposableListener, addDisposableThrottledListener, detectFullscreen, disposableWindowInterval, getActiveDocument, getWindowId, onDidRegisterWindow, trackFocus } from 'vs/base/browser/dom'; import { Disposable } from 'vs/base/common/lifecycle'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { memoize } from 'vs/base/common/decorators'; @@ -206,8 +206,8 @@ export class BrowserHostService extends Disposable implements IHostService { } @memoize - get onDidChangeFullScreen(): Event { - const emitter = this._register(new Emitter()); + get onDidChangeFullScreen(): Event<{ windowId: number; fullscreen: boolean }> { + const emitter = this._register(new Emitter<{ windowId: number; fullscreen: boolean }>()); this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => { const windowId = getWindowId(window); @@ -215,11 +215,11 @@ export class BrowserHostService extends Disposable implements IHostService { // Fullscreen (Browser) for (const event of [EventType.FULLSCREEN_CHANGE, EventType.WK_FULLSCREEN_CHANGE]) { - disposables.add(addDisposableListener(window.document, event, () => emitter.fire(windowId))); + disposables.add(addDisposableListener(window.document, event, () => emitter.fire({ windowId, fullscreen: !!detectFullscreen(window) }))); } // Fullscreen (Native) - disposables.add(addDisposableThrottledListener(viewport, EventType.RESIZE, () => emitter.fire(windowId), undefined, isMacintosh ? 2000 /* adjust for macOS animation */ : 800 /* can be throttled */)); + disposables.add(addDisposableThrottledListener(viewport, EventType.RESIZE, () => emitter.fire({ windowId, fullscreen: !!detectFullscreen(window) }), undefined, isMacintosh ? 2000 /* adjust for macOS animation */ : 800 /* can be throttled */)); }, { window: mainWindow, disposables: this._store })); return emitter.event; diff --git a/src/vs/workbench/services/host/browser/host.ts b/src/vs/workbench/services/host/browser/host.ts index 02d7fcea201..f1586f4b9c4 100644 --- a/src/vs/workbench/services/host/browser/host.ts +++ b/src/vs/workbench/services/host/browser/host.ts @@ -69,7 +69,7 @@ export interface IHostService { * Emitted when the window with the given identifier changes * its fullscreen state. */ - readonly onDidChangeFullScreen: Event; + readonly onDidChangeFullScreen: Event<{ windowId: number; fullscreen: boolean }>; /** * Opens an empty window. The optional parameter allows to define if diff --git a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts index 4cd56f09eda..d3fbff27c39 100644 --- a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts +++ b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts @@ -94,7 +94,7 @@ class WorkbenchHostService extends Disposable implements IHostService { return Event.latch(emitter.event, undefined, this._store); } - readonly onDidChangeFullScreen = Event.filter(this.nativeHostService.onDidChangeWindowFullScreen, id => hasWindow(id), this._store); + readonly onDidChangeFullScreen = Event.filter(this.nativeHostService.onDidChangeWindowFullScreen, e => hasWindow(e.windowId), this._store); openWindow(options?: IOpenEmptyWindowOptions): Promise; openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise; diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 4ba2d0fb3ab..0eacb21061c 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1493,7 +1493,7 @@ export class TestHostService implements IHostService { private _onDidChangeWindow = new Emitter(); readonly onDidChangeActiveWindow = this._onDidChangeWindow.event; - readonly onDidChangeFullScreen: Event = Event.None; + readonly onDidChangeFullScreen: Event<{ windowId: number; fullscreen: boolean }> = Event.None; setFocus(focus: boolean) { this._hasFocus = focus; From c005fc38a92b84826e5d56b7ce37c2c8183ca0f2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 18 Jan 2024 16:58:40 +0530 Subject: [PATCH 0409/1897] fix #197345 (#202715) --- .../lib/stylelint/vscode-known-variables.json | 2 ++ .../auxiliarybar/media/auxiliaryBarPart.css | 5 ++++ .../browser/parts/media/paneCompositePart.css | 4 --- .../browser/parts/panel/media/panelpart.css | 5 ++++ .../parts/sidebar/media/sidebarpart.css | 10 +++++++ .../browser/parts/sidebar/sidebarPart.ts | 10 +++---- src/vs/workbench/common/theme.ts | 30 ++++++++++++++++++- 7 files changed, 56 insertions(+), 10 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index cbdea6a9ea2..cbb1a5e14dd 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -461,6 +461,8 @@ "--vscode-notificationsWarningIcon-foreground", "--vscode-outputView-background", "--vscode-outputViewStickyScroll-background", + "--vscode-activityBarTop-activeBorder", + "--vscode-activityBarTop-foreground", "--vscode-panel-background", "--vscode-panel-border", "--vscode-panel-dropBorder", diff --git a/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css b/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css index 0b2a5c80740..3d382b3e39d 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css +++ b/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css @@ -18,6 +18,11 @@ flex: 1; } +.monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, +.monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked.clicked:focus .active-item-indicator:before { + border-top-color: var(--vscode-panelTitle-activeBorder) !important; +} + .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label, .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .action-label { color: var(--vscode-sideBarTitle-foreground) !important; diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index 11145c96299..e3799464f4d 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -232,10 +232,6 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { border-top-color: var(--vscode-focusBorder) !important; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked.clicked:focus .active-item-indicator:before { - border-top-color: var(--vscode-panelTitle-activeBorder) !important; -} .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label { diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index e1f42582e78..4ccb53b0c80 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -47,6 +47,11 @@ background-color: inherit; } +.monaco-workbench .part.panel > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, +.monaco-workbench .part.panel > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked.clicked:focus .active-item-indicator:before { + border-top-color: var(--vscode-panelTitle-activeBorder) !important; +} + .monaco-workbench .part.panel > .title > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item:focus .action-label, .monaco-workbench .part.panel > .title > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item:hover .action-label { color: var(--vscode-panelTitle-activeForeground) !important; diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index 5d0a0da2e63..65f962e8241 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -60,6 +60,16 @@ height: 16px; } +.monaco-workbench .part.sidebar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, +.monaco-workbench .part.sidebar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked.clicked:focus .active-item-indicator:before { + border-top-color: var(--vscode-activityBarTop-activeBorder) !important; +} + +.monaco-workbench .part.sidebar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label, +.monaco-workbench .part.sidebar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .action-label { + color: var(--vscode-activityBarTop-foreground) !important; +} + .monaco-workbench .sidebar.pane-composite-part > .title > .composite-bar-container { flex: 1; } diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index e0b21f32517..abadf927fd9 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -13,7 +13,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_DRAG_AND_DROP_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND } from 'vs/workbench/common/theme'; +import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_TOP_FOREGROUND, ACTIVITY_BAR_TOP_ACTIVE_BORDER, ACTIVITY_BAR_TOP_INACTIVE_FOREGROUND, ACTIVITY_BAR_TOP_DRAG_AND_DROP_BORDER } from 'vs/workbench/common/theme'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; @@ -183,12 +183,12 @@ export class SidebarPart extends AbstractPaneCompositePart { colors: theme => ({ activeBackgroundColor: theme.getColor(SIDE_BAR_BACKGROUND), inactiveBackgroundColor: theme.getColor(SIDE_BAR_BACKGROUND), - activeBorderBottomColor: theme.getColor(PANEL_ACTIVE_TITLE_BORDER), - activeForegroundColor: theme.getColor(PANEL_ACTIVE_TITLE_FOREGROUND), - inactiveForegroundColor: theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND), + activeBorderBottomColor: theme.getColor(ACTIVITY_BAR_TOP_ACTIVE_BORDER), + activeForegroundColor: theme.getColor(ACTIVITY_BAR_TOP_FOREGROUND), + inactiveForegroundColor: theme.getColor(ACTIVITY_BAR_TOP_INACTIVE_FOREGROUND), badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND), - dragAndDropBorder: theme.getColor(PANEL_DRAG_AND_DROP_BORDER) + dragAndDropBorder: theme.getColor(ACTIVITY_BAR_TOP_DRAG_AND_DROP_BORDER) }), compact: true }; diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 1c56711b8d0..4977a08dbce 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -365,7 +365,6 @@ export const PANEL_DRAG_AND_DROP_BORDER = registerColor('panel.dropBorder', { hcLight: PANEL_ACTIVE_TITLE_FOREGROUND }, localize('panelDragAndDropBorder', "Drag and drop feedback color for the panel titles. Panels are shown below the editor area and contain views like output and integrated terminal.")); - export const PANEL_SECTION_DRAG_AND_DROP_BACKGROUND = registerColor('panelSection.dropBackground', { dark: EDITOR_DRAG_AND_DROP_BACKGROUND, light: EDITOR_DRAG_AND_DROP_BACKGROUND, @@ -685,6 +684,35 @@ export const ACTIVITY_BAR_BADGE_FOREGROUND = registerColor('activityBarBadge.for hcLight: Color.white }, localize('activityBarBadgeForeground', "Activity notification badge foreground color. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); +export const ACTIVITY_BAR_TOP_FOREGROUND = registerColor('activityBarTop.foreground', { + dark: '#E7E7E7', + light: '#424242', + hcDark: Color.white, + hcLight: editorForeground +}, localize('activityBarTop', "Active foreground color of the item in the Activity bar when it is on top. The activity allows to switch between views of the side bar.")); + +export const ACTIVITY_BAR_TOP_ACTIVE_BORDER = registerColor('activityBarTop.activeBorder', { + dark: ACTIVITY_BAR_TOP_FOREGROUND, + light: ACTIVITY_BAR_TOP_FOREGROUND, + hcDark: contrastBorder, + hcLight: '#B5200D' +}, localize('activityBarTopActiveFocusBorder', "Focus border color for the active item in the Activity bar when it is on top. The activity allows to switch between views of the side bar.")); + +export const ACTIVITY_BAR_TOP_INACTIVE_FOREGROUND = registerColor('activityBarTop.inactiveForeground', { + dark: transparent(ACTIVITY_BAR_TOP_FOREGROUND, 0.6), + light: transparent(ACTIVITY_BAR_TOP_FOREGROUND, 0.75), + hcDark: Color.white, + hcLight: editorForeground +}, localize('activityBarTopInActiveForeground', "Inactive foreground color of the item in the Activity bar when it is on top. The activity allows to switch between views of the side bar.")); + +export const ACTIVITY_BAR_TOP_DRAG_AND_DROP_BORDER = registerColor('activityBarTop.dropBorder', { + dark: ACTIVITY_BAR_TOP_FOREGROUND, + light: ACTIVITY_BAR_TOP_FOREGROUND, + hcDark: ACTIVITY_BAR_TOP_FOREGROUND, + hcLight: ACTIVITY_BAR_TOP_FOREGROUND +}, localize('activityBarTopDragAndDropBorder', "Drag and drop feedback color for the items in the Activity bar when it is on top. The activity allows to switch between views of the side bar.")); + + // < --- Profiles --- > export const PROFILE_BADGE_BACKGROUND = registerColor('profileBadge.background', { From 42ffc9c0d22e177a29896cb71cafedbc4e1a9f46 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 18 Jan 2024 12:29:12 +0100 Subject: [PATCH 0410/1897] Rename setting --- src/vs/platform/window/common/window.ts | 11 ++++++++++ src/vs/workbench/browser/layout.ts | 11 +++++----- .../browser/parts/titlebar/menubarControl.ts | 4 ++-- .../browser/parts/titlebar/titlebarActions.ts | 2 +- .../browser/parts/titlebar/titlebarPart.ts | 12 +++++------ .../browser/relauncher.contribution.ts | 4 ++-- .../contrib/splash/browser/partsSplash.ts | 3 ++- .../windowIgnoreMenuShortcutsManager.ts | 3 ++- .../electron-sandbox/desktop.contribution.ts | 20 ++++++++++++------- 9 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index 0a55eaa67e4..14f3914639c 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -165,11 +165,22 @@ export interface IDensitySettings { readonly editorTabHeight: 'default' | 'compact'; } +export const enum TitleBarSetting { + TITLE_BAR_STYLE = 'window.titleBarStyle', + CUSTOM_TITLE_BAR_VISIBILITY = 'window.customTitleBarVisibility', +} + export const enum TitlebarStyle { NATIVE = 'native', CUSTOM = 'custom', } +export const enum CustomTitleBarVisibility { + AUTO = 'auto', + WINDOWED = 'windowed', + NEVER = 'never', +} + export function hasCustomTitlebar(configurationService: IConfigurationService, titleBarStyle?: TitlebarStyle): boolean { // Does not imply that the title bar is visible diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 3e45b0db08d..972d0771b08 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -19,7 +19,7 @@ import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/co import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { StartupKind, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { getMenuBarVisibility, IPath, hasNativeTitlebar, hasCustomTitlebar } from 'vs/platform/window/common/window'; +import { getMenuBarVisibility, IPath, hasNativeTitlebar, hasCustomTitlebar, TitleBarSetting } from 'vs/platform/window/common/window'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -48,6 +48,7 @@ import { AuxiliaryBarPart } from 'vs/workbench/browser/parts/auxiliarybar/auxili import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService'; import { mainWindow } from 'vs/base/browser/window'; +import { CustomTitleBarVisibility } from '../../platform/window/common/window'; //#region Layout Implementation @@ -349,8 +350,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION, LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE, 'window.menuBarVisibility', - 'window.titleBarStyle', - 'window.showCustomToolBar', + TitleBarSetting.TITLE_BAR_STYLE, + TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, ].some(setting => e.affectsConfiguration(setting))) { this.doUpdateLayoutConfiguration(); } @@ -1228,8 +1229,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return false; } - const showCustomToolBar = this.configurationService.getValue('window.showCustomToolBar'); - if (showCustomToolBar === 'never' || showCustomToolBar === 'windowed' && this.state.runtime.mainWindowFullscreen) { + const showCustomTitleBar = this.configurationService.getValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY); + if (showCustomTitleBar === CustomTitleBarVisibility.NEVER || showCustomTitleBar === CustomTitleBarVisibility.WINDOWED && this.state.runtime.mainWindowFullscreen) { return false; } diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 2923bcc75bb..eeec3dbcc5e 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/menubarControl'; import { localize, localize2 } from 'vs/nls'; import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action2, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; -import { MenuBarVisibility, IWindowOpenable, getMenuBarVisibility, hasNativeTitlebar } from 'vs/platform/window/common/window'; +import { MenuBarVisibility, IWindowOpenable, getMenuBarVisibility, hasNativeTitlebar, TitleBarSetting } from 'vs/platform/window/common/window'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAction, Action, SubmenuAction, Separator, IActionRunner, ActionRunner, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, toAction } from 'vs/base/common/actions'; import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom'; @@ -362,7 +362,7 @@ export abstract class MenubarControl extends Disposable { { label: localize('goToSetting', "Open Settings"), run: () => { - return this.preferencesService.openUserSettings({ query: 'window.titleBarStyle' }); + return this.preferencesService.openUserSettings({ query: TitleBarSetting.TITLE_BAR_STYLE }); } } ]); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index 6fdc5df5b09..4ac42e76029 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -63,7 +63,7 @@ registerAction2(class ToggleLayoutControl extends ToggleConfigAction { }); registerAction2(class ToggleCustomToolBar extends Action2 { - static readonly settingsID = `window.showCustomToolBar`; + static readonly settingsID = `window.showCustomTitleBar`; constructor() { super({ diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index bb83d9199fc..32361f3be14 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -24,7 +24,7 @@ import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menuba import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { Parts, IWorkbenchLayoutService, ActivityBarPosition, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; +import { Parts, IWorkbenchLayoutService, ActivityBarPosition, LayoutSettings, EditorActionsLocation, EditorTabsMode } from 'vs/workbench/services/layout/browser/layoutService'; import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -487,7 +487,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { // Text Title if (!this.isCommandCenterVisible) { - if (this.titleBarStyle === 'custom') { + if (!hasNativeTitlebar(this.configurationService, this.titleBarStyle)) { this.title.innerText = this.windowTitle.value; this.titleDisposables.add(this.windowTitle.onDidChange(() => { @@ -707,15 +707,15 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } private get editorActionsEnabled(): boolean { - return this.editorGroupService.partOptions.editorActionsLocation === 'titleBar' || + return this.editorGroupService.partOptions.editorActionsLocation === EditorActionsLocation.TITLEBAR || ( - this.editorGroupService.partOptions.editorActionsLocation === 'default' && - this.editorGroupService.partOptions.showTabs === 'none' + this.editorGroupService.partOptions.editorActionsLocation === EditorActionsLocation.DEFAULT && + this.editorGroupService.partOptions.showTabs === EditorTabsMode.MULTIPLE ); } private get activityActionsEnabled(): boolean { - return !this.isAuxiliary && this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP; + return !this.isAuxiliary && this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP; } get hasZoomableElements(): boolean { diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index c3f413e8b09..a75a1b4b09b 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -6,7 +6,7 @@ import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContributionsRegistry, IWorkbenchContribution, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWindowsConfiguration, IWindowSettings, TitlebarStyle } from 'vs/platform/window/common/window'; +import { IWindowsConfiguration, IWindowSettings, TitleBarSetting, TitlebarStyle } from 'vs/platform/window/common/window'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ConfigurationTarget, IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { localize } from 'vs/nls'; @@ -34,7 +34,7 @@ interface IConfiguration extends IWindowsConfiguration { export class SettingsChangeRelauncher extends Disposable implements IWorkbenchContribution { private static SETTINGS = [ - 'window.titleBarStyle', + TitleBarSetting.TITLE_BAR_STYLE, 'window.nativeTabs', 'window.nativeFullScreen', 'window.clickThroughInactive', diff --git a/src/vs/workbench/contrib/splash/browser/partsSplash.ts b/src/vs/workbench/contrib/splash/browser/partsSplash.ts index 63f2d79012d..5d637c44893 100644 --- a/src/vs/workbench/contrib/splash/browser/partsSplash.ts +++ b/src/vs/workbench/contrib/splash/browser/partsSplash.ts @@ -21,6 +21,7 @@ import { assertIsDefined } from 'vs/base/common/types'; import { ISplashStorageService } from 'vs/workbench/contrib/splash/browser/splash'; import { mainWindow } from 'vs/base/browser/window'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { TitleBarSetting } from 'vs/platform/window/common/window'; export class PartsSplash { @@ -54,7 +55,7 @@ export class PartsSplash { }); _configService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('window.titleBarStyle')) { + if (e.affectsConfiguration(TitleBarSetting.TITLE_BAR_STYLE)) { this._didChangeTitleBarStyle = true; this._savePartsSplash(); } diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.ts b/src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.ts index 7ae47c23f5d..49345645524 100644 --- a/src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.ts +++ b/src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.ts @@ -9,6 +9,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IMainProcessService } from 'vs/platform/ipc/common/mainProcessService'; import { INativeHostService } from 'vs/platform/native/common/native'; import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService'; +import { hasNativeTitlebar } from 'vs/platform/window/common/window'; export class WindowIgnoreMenuShortcutsManager { @@ -21,7 +22,7 @@ export class WindowIgnoreMenuShortcutsManager { mainProcessService: IMainProcessService, private readonly _nativeHostService: INativeHostService ) { - this._isUsingNativeTitleBars = configurationService.getValue('window.titleBarStyle') !== 'custom'; + this._isUsingNativeTitleBars = hasNativeTitlebar(configurationService); this._webviewMainService = ProxyChannel.toService(mainProcessService.getChannel('webview')); } diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 9cf09c66a85..7c7dc5250e7 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -27,6 +27,7 @@ import { ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; import { ModifierKeyEmitter } from 'vs/base/browser/dom'; import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from 'vs/workbench/common/configuration'; +import { CustomTitleBarVisibility, TitleBarSetting, TitlebarStyle } from 'vs/platform/window/common/window'; // Actions (function registerActions(): void { @@ -227,19 +228,24 @@ import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from 'scope': ConfigurationScope.APPLICATION, 'markdownDescription': localize('window.doubleClickIconToClose', "If enabled, this setting will close the window when the application icon in the title bar is double-clicked. The window will not be able to be dragged by the icon. This setting is effective only if `#window.titleBarStyle#` is set to `custom`.") }, - 'window.titleBarStyle': { + [TitleBarSetting.TITLE_BAR_STYLE]: { 'type': 'string', - 'enum': ['native', 'custom'], - 'default': isLinux ? 'native' : 'custom', + 'enum': [TitlebarStyle.NATIVE, TitlebarStyle.CUSTOM], + 'default': isLinux ? TitlebarStyle.NATIVE : TitlebarStyle.CUSTOM, 'scope': ConfigurationScope.APPLICATION, 'description': localize('titleBarStyle', "Adjust the appearance of the window title bar to be native by the OS or custom. On Linux and Windows, this setting also affects the application and context menu appearances. Changes require a full restart to apply.") }, - 'window.showCustomToolBar': { + [TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY]: { 'type': 'string', - 'enum': ['default', 'never', 'windowed'], - 'default': 'default', + 'enum': [CustomTitleBarVisibility.AUTO, CustomTitleBarVisibility.WINDOWED, CustomTitleBarVisibility.NEVER], + 'markdownEnumDescriptions': [ + localize(`${TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY}.${CustomTitleBarVisibility.AUTO}`, "Automatically changes custom titlebar visibility."), + localize(`${TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY}.${CustomTitleBarVisibility.WINDOWED}`, "Hide custom titlebar in full screen. Automatically changes custom titlebar visibility in windowed."), + localize(`${TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY}.${CustomTitleBarVisibility.NEVER}`, "Hide custom titlebar when `#window.titleBarStyle#` is set to `native`."), + ], + 'default': isLinux ? CustomTitleBarVisibility.NEVER : CustomTitleBarVisibility.AUTO, 'scope': ConfigurationScope.APPLICATION, - 'description': localize('showCustomToolBar', "") // TODO@Ben + 'description': localize(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, "Adjust when the custom title bar should be shown."), }, 'window.dialogStyle': { 'type': 'string', From 4fe28027f77749bd3d530f6949c9e36f7bab6276 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 18 Jan 2024 12:30:08 +0100 Subject: [PATCH 0411/1897] =?UTF-8?q?don=C3=9Ft=20affect=20none=20native?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/vs/workbench/browser/layout.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 972d0771b08..02d84822c0d 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -1229,8 +1229,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return false; } + const nativeTitleBarVisible = hasNativeTitlebar(this.configurationService); const showCustomTitleBar = this.configurationService.getValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY); - if (showCustomTitleBar === CustomTitleBarVisibility.NEVER || showCustomTitleBar === CustomTitleBarVisibility.WINDOWED && this.state.runtime.mainWindowFullscreen) { + if (showCustomTitleBar === CustomTitleBarVisibility.NEVER && nativeTitleBarVisible || showCustomTitleBar === CustomTitleBarVisibility.WINDOWED && this.state.runtime.mainWindowFullscreen) { return false; } @@ -1252,7 +1253,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Hide custom title bar when native title bar and custom title bar is empty - if (hasNativeTitlebar(this.configurationService)) { + if (nativeTitleBarVisible) { return false; } From 105180ac7b05eb605b0a84b473e000f093791d69 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 18 Jan 2024 13:00:26 +0100 Subject: [PATCH 0412/1897] cleanup actions --- .../browser/actions/layoutActions.ts | 27 ++++++------- .../browser/parts/titlebar/titlebarActions.ts | 39 ++++++++++++++----- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index bf10920f0ff..ca73beb2038 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -7,7 +7,7 @@ import { localize, localize2 } from 'vs/nls'; import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { EditorTabsMode, IWorkbenchLayoutService, LayoutSettings, Parts, Position, ZenModeSettings, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; +import { EditorActionsLocation, EditorTabsMode, IWorkbenchLayoutService, LayoutSettings, Parts, Position, ZenModeSettings, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { isWindows, isLinux, isWeb, isMacintosh, isNative } from 'vs/base/common/platform'; @@ -30,6 +30,7 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { ICommandActionTitle } from 'vs/platform/action/common/action'; import { mainWindow } from 'vs/base/browser/window'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { TitleBarSetting, TitlebarStyle } from 'vs/platform/window/common/window'; // Register Icons const menubarIcon = registerIcon('menuBar', Codicon.layoutMenubar, localize('menuBarIcon', "Represents the menu bar")); @@ -587,8 +588,8 @@ export class EditorActionsTitleBarAction extends Action2 { }, category: Categories.View, precondition: ContextKeyExpr.and( - ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'titleBar').negate(), - ContextKeyExpr.equals('config.window.style', 'native').negate(), + ContextKeyExpr.equals(`config.${LayoutSettings.EDITOR_ACTIONS_LOCATION}`, EditorActionsLocation.TITLEBAR).negate(), + ContextKeyExpr.equals(`config.${TitleBarSetting.TITLE_BAR_STYLE}`, TitlebarStyle.NATIVE).negate(), ), f1: true }); @@ -596,7 +597,7 @@ export class EditorActionsTitleBarAction extends Action2 { run(accessor: ServicesAccessor): Promise { const configurationService = accessor.get(IConfigurationService); - return configurationService.updateValue('workbench.editor.editorActionsLocation', 'titleBar'); + return configurationService.updateValue(LayoutSettings.EDITOR_ACTIONS_LOCATION, EditorActionsLocation.TITLEBAR); } } registerAction2(EditorActionsTitleBarAction); @@ -616,8 +617,8 @@ export class EditorActionsDefaultAction extends Action2 { }, category: Categories.View, precondition: ContextKeyExpr.and( - ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'default').negate(), - ContextKeyExpr.equals('config.workbench.editor.showTabs', 'none').negate(), + ContextKeyExpr.equals(`config.${LayoutSettings.EDITOR_ACTIONS_LOCATION}`, EditorActionsLocation.DEFAULT).negate(), + ContextKeyExpr.equals(`config.${LayoutSettings.EDITOR_TABS_MODE}`, EditorTabsMode.NONE).negate(), ), f1: true }); @@ -625,7 +626,7 @@ export class EditorActionsDefaultAction extends Action2 { run(accessor: ServicesAccessor): Promise { const configurationService = accessor.get(IConfigurationService); - return configurationService.updateValue('workbench.editor.editorActionsLocation', 'default'); + return configurationService.updateValue(LayoutSettings.EDITOR_ACTIONS_LOCATION, EditorActionsLocation.DEFAULT); } } registerAction2(EditorActionsDefaultAction); @@ -644,14 +645,14 @@ export class HideEditorActionsAction extends Action2 { original: 'Hide Editor Actions' }, category: Categories.View, - precondition: ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'hidden').negate(), + precondition: ContextKeyExpr.equals(`config.${LayoutSettings.EDITOR_ACTIONS_LOCATION}`, EditorActionsLocation.HIDDEN).negate(), f1: true }); } run(accessor: ServicesAccessor): Promise { const configurationService = accessor.get(IConfigurationService); - return configurationService.updateValue('workbench.editor.editorActionsLocation', 'hidden'); + return configurationService.updateValue(LayoutSettings.EDITOR_ACTIONS_LOCATION, EditorActionsLocation.HIDDEN); } } registerAction2(HideEditorActionsAction); @@ -670,14 +671,14 @@ export class ShowEditorActionsAction extends Action2 { original: 'Show Editor Actions' }, category: Categories.View, - precondition: ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'hidden'), + precondition: ContextKeyExpr.equals(`config.${LayoutSettings.EDITOR_ACTIONS_LOCATION}`, EditorActionsLocation.HIDDEN), f1: true }); } run(accessor: ServicesAccessor): Promise { const configurationService = accessor.get(IConfigurationService); - return configurationService.updateValue('workbench.editor.editorActionsLocation', 'default'); + return configurationService.updateValue(LayoutSettings.EDITOR_ACTIONS_LOCATION, EditorActionsLocation.DEFAULT); } } registerAction2(ShowEditorActionsAction); @@ -703,7 +704,7 @@ registerAction2(class extends Action2 { original: 'Separate Pinned Editor Tabs' }, category: Categories.View, - precondition: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'multiple'), + precondition: ContextKeyExpr.equals(`config.${LayoutSettings.EDITOR_TABS_MODE}`, EditorTabsMode.MULTIPLE), f1: true }); } @@ -802,7 +803,7 @@ if (isWindows || isLinux || isWeb) { title: localize('miMenuBarNoMnemonic', "Menu Bar"), toggled: ContextKeyExpr.and(IsMacNativeContext.toNegated(), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'hidden'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'toggle'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'compact')) }, - when: ContextKeyExpr.and(IsAuxiliaryWindowFocusedContext.toNegated(), ContextKeyExpr.notEquals(`config.window.titleBarStyle`, 'native')), + when: ContextKeyExpr.and(IsAuxiliaryWindowFocusedContext.toNegated(), ContextKeyExpr.notEquals(TitleBarSetting.TITLE_BAR_STYLE, TitlebarStyle.NATIVE)), group: '2_config', order: 0 }); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index 4ac42e76029..27937ac6396 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -12,15 +12,16 @@ import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/act import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ACCOUNTS_ACTIVITY_ID, GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { IAction } from 'vs/base/common/actions'; -import { IsAuxiliaryWindowFocusedContext } from 'vs/workbench/common/contextkeys'; +import { IsAuxiliaryWindowFocusedContext, IsMainWindowFullscreenContext } from 'vs/workbench/common/contextkeys'; +import { CustomTitleBarVisibility, TitleBarSetting, TitlebarStyle } from 'vs/platform/window/common/window'; // --- Context Menu Actions --- // class ToggleConfigAction extends Action2 { - constructor(private readonly section: string, title: string, order: number, mainWindowOnly: boolean, showInCustomToolBar: boolean) { + constructor(private readonly section: string, title: string, order: number, mainWindowOnly: boolean, showInCustomTitleBarWhenNativeTitle: boolean) { let when = mainWindowOnly ? IsAuxiliaryWindowFocusedContext.toNegated() : ContextKeyExpr.true(); - when = showInCustomToolBar ? when : ContextKeyExpr.and(when, ContextKeyExpr.equals(`config.window.titleBarStyle`, 'native').negate())!; + when = showInCustomTitleBarWhenNativeTitle ? when : ContextKeyExpr.and(when, ContextKeyExpr.equals(TitleBarSetting.TITLE_BAR_STYLE, TitlebarStyle.NATIVE).negate())!; super({ id: `toggle.${section}`, @@ -62,23 +63,43 @@ registerAction2(class ToggleLayoutControl extends ToggleConfigAction { } }); -registerAction2(class ToggleCustomToolBar extends Action2 { - static readonly settingsID = `window.showCustomTitleBar`; +registerAction2(class ToggleCustomTitleBar extends Action2 { + static readonly settingsID = TitleBarSetting.TITLE_BAR_STYLE; constructor() { super({ - id: `toggle.${ToggleCustomToolBar.settingsID}`, - title: localize('toggle.toolBar', 'Custom Title Bar'), + id: `toggle.${ToggleCustomTitleBar.settingsID}`, + title: localize('toggle.customTitleBar', 'Custom Title Bar'), toggled: ContextKeyExpr.true(), menu: [ - { id: MenuId.TitleBarContext, order: 0, when: ContextKeyExpr.equals(`config.window.titleBarStyle`, 'native'), group: '3_toggle' }, + { id: MenuId.TitleBarContext, order: 0, when: ContextKeyExpr.equals(TitleBarSetting.TITLE_BAR_STYLE, TitlebarStyle.NATIVE), group: '3_toggle' }, ] }); } run(accessor: ServicesAccessor, ...args: any[]): void { const configService = accessor.get(IConfigurationService); - configService.updateValue(ToggleCustomToolBar.settingsID, 'never'); + configService.updateValue(ToggleCustomTitleBar.settingsID, CustomTitleBarVisibility.NEVER); + } +}); + +registerAction2(class ToggleCustomTitleBarWindowed extends Action2 { + static readonly settingsID = TitleBarSetting.TITLE_BAR_STYLE; + + constructor() { + super({ + id: `toggle.${ToggleCustomTitleBarWindowed.settingsID}.windowed`, + title: localize('toggle.customTitleBarWindowed', 'Custom Title Bar In Full Screen'), + toggled: ContextKeyExpr.true(), + menu: [ + { id: MenuId.TitleBarContext, order: 0, when: IsMainWindowFullscreenContext, group: '3_toggle' }, + ] + }); + } + + run(accessor: ServicesAccessor, ...args: any[]): void { + const configService = accessor.get(IConfigurationService); + configService.updateValue(ToggleCustomTitleBarWindowed.settingsID, CustomTitleBarVisibility.WINDOWED); } }); From e5a7abbc6ab7e690994c807b8154b2b2b9643829 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 18 Jan 2024 14:51:52 +0100 Subject: [PATCH 0413/1897] Mirror user edits into base model (#202723) --- .../browser/inlineChatController.ts | 36 ++--- .../inlineChat/browser/inlineChatSession.ts | 114 ++++++++++++- .../browser/inlineChatStrategies.ts | 36 +++-- .../test/browser/inlineChatSession.test.ts | 150 ++++++++++++++++-- 4 files changed, 291 insertions(+), 45 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 4a7fce76f96..dc8433eaf9b 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -42,7 +42,7 @@ import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IInlineChatSavingService } from './inlineChatSavingService'; import { EmptyResponse, ErrorResponse, ExpansionState, ReplyResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { IInlineChatSessionService } from './inlineChatSessionService'; -import { EditModeStrategy, LivePreviewStrategy, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; +import { EditModeStrategy, IEditObserver, LivePreviewStrategy, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; import { IInlineChatMessageAppender, InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatProgressItem, IInlineChatRequest, IInlineChatResponse, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -127,7 +127,6 @@ export class InlineChatController implements IEditorContribution { private _session?: Session; private _strategy?: EditModeStrategy; - private _ignoreModelContentChanged = false; constructor( private readonly _editor: ICodeEditor, @@ -390,11 +389,11 @@ export class InlineChatController implements IEditorContribution { this._sessionStore.add(this._editor.onDidChangeModelContent(e => { - if (!this._ignoreModelContentChanged && this._strategy?.hasFocus()) { + if (!this._session?.hunkData.ignoreTextModelNChanges && this._strategy?.hasFocus()) { this._ctxUserDidEdit.set(altVersionNow !== this._editor.getModel()?.getAlternativeVersionId()); } - if (this._ignoreModelContentChanged || this._strategy?.hasFocus()) { + if (this._session?.hunkData.ignoreTextModelNChanges || this._strategy?.hasFocus()) { return; } @@ -479,10 +478,10 @@ export class InlineChatController implements IEditorContribution { } if (lastExchange.response instanceof ReplyResponse) { try { - this._ignoreModelContentChanged = true; + this._session.hunkData.ignoreTextModelNChanges = true; await this._strategy.undoChanges(lastExchange.response.modelAltVersionId); } finally { - this._ignoreModelContentChanged = false; + this._session.hunkData.ignoreTextModelNChanges = false; } } return State.MAKE_REQUEST; @@ -933,19 +932,20 @@ export class InlineChatController implements IEditorContribution { const actualEdits = !opts && moreMinimalEdits ? moreMinimalEdits : edits; const editOperations = actualEdits.map(TextEdit.asEditOperation); - try { - this._ignoreModelContentChanged = true; - this._inlineChatSavingService.markChanged(this._session); - this._session.wholeRange.trackEdits(editOperations); - if (opts) { - await this._strategy.makeProgressiveChanges(editOperations, opts); - } else { - await this._strategy.makeChanges(editOperations); - } - this._ctxDidEdit.set(this._session.hasChangedText); - } finally { - this._ignoreModelContentChanged = false; + const editsObserver: IEditObserver = { + start: () => this._session!.hunkData.ignoreTextModelNChanges = true, + stop: () => this._session!.hunkData.ignoreTextModelNChanges = false, + }; + + this._inlineChatSavingService.markChanged(this._session); + this._session.wholeRange.trackEdits(editOperations); + if (opts) { + await this._strategy.makeProgressiveChanges(editOperations, editsObserver, opts); + } else { + await this._strategy.makeChanges(editOperations, editsObserver); } + this._ctxDidEdit.set(this._session.hasChangedText); + } private _forcedPlaceholder: string | undefined = undefined; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 97c7d2cab7c..63d31a12445 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { Emitter, Event } from 'vs/base/common/event'; import { ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; import { IWorkspaceTextEdit, TextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; -import { IModelDecorationOptions, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; +import { IIdentifiedSingleEditOperation, IModelDecorationOptions, IModelDeltaDecoration, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; import { EditMode, IInlineChatSessionProvider, IInlineChatSession, IInlineChatBulkEditResponse, IInlineChatEditResponse, InlineChatResponseType, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; @@ -28,6 +28,8 @@ import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { asRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { coalesceInPlace } from 'vs/base/common/arrays'; import { Iterable } from 'vs/base/common/iterator'; +import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents'; +import { DisposableStore } from 'vs/base/common/lifecycle'; export type TelemetryData = { @@ -409,17 +411,27 @@ export class HunkData { private static readonly _HUNK_TRACKED_RANGE = ModelDecorationOptions.register({ description: 'inline-chat-hunk-tracked-range', + stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges }); private static readonly _HUNK_THRESHOLD = 8; + private readonly _store = new DisposableStore(); private readonly _data = new Map(); + private _ignoreChanges: boolean = false; constructor( @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, private readonly _textModel0: ITextModel, private readonly _textModelN: ITextModel, - ) { } + ) { + + this._store.add(_textModelN.onDidChangeContent(e => { + if (!this._ignoreChanges) { + this._mirrorChanges(e); + } + })); + } dispose(): void { if (!this._textModelN.isDisposed()) { @@ -436,6 +448,104 @@ export class HunkData { } }); } + this._data.clear(); + this._store.dispose(); + } + + set ignoreTextModelNChanges(value: boolean) { + this._ignoreChanges = value; + } + + get ignoreTextModelNChanges(): boolean { + return this._ignoreChanges; + } + + private _mirrorChanges(event: IModelContentChangedEvent) { + + // mirror textModelN changes to textModel0 execept for those that + // overlap with a hunk + + type HunkRangePair = { rangeN: Range; range0: Range }; + const hunkRanges: HunkRangePair[] = []; + + const ranges0: Range[] = []; + + for (const { textModelNDecorations, textModel0Decorations, state } of this._data.values()) { + + if (state === HunkState.Pending) { + // pending means the hunk's changes aren't "sync'd" yet + for (let i = 1; i < textModelNDecorations.length; i++) { + const rangeN = this._textModelN.getDecorationRange(textModelNDecorations[i]); + const range0 = this._textModel0.getDecorationRange(textModel0Decorations[i]); + if (rangeN && range0) { + hunkRanges.push({ rangeN, range0 }); + } + } + + } else if (state === HunkState.Accepted) { + // accepted means the hunk's changes are also in textModel0 + for (let i = 1; i < textModel0Decorations.length; i++) { + const range = this._textModel0.getDecorationRange(textModel0Decorations[i]); + if (range) { + ranges0.push(range); + } + } + } + } + + hunkRanges.sort((a, b) => Range.compareRangesUsingStarts(a.rangeN, b.rangeN)); + ranges0.sort(Range.compareRangesUsingStarts); + + const edits: IIdentifiedSingleEditOperation[] = []; + + for (const change of event.changes) { + + let isOverlapping = false; + + let pendingChangesLen = 0; + + for (const { rangeN, range0 } of hunkRanges) { + if (rangeN.getEndPosition().isBefore(Range.getStartPosition(change.range))) { + // pending hunk _before_ this change. When projecting into textModel0 we need to + // subtract that. Because diffing is relaxed it might include changes that are not + // actual insertions/deletions. Therefore we need to take the length of the original + // range into account. + pendingChangesLen += this._textModelN.getValueLengthInRange(rangeN); + pendingChangesLen -= this._textModel0.getValueLengthInRange(range0); + + } else if (Range.areIntersectingOrTouching(rangeN, change.range)) { + isOverlapping = true; + break; + + } else { + // hunks past this change aren't relevant + break; + } + } + + if (isOverlapping) { + // hunk overlaps, it grew + continue; + } + + const offset0 = change.rangeOffset - pendingChangesLen; + const start0 = this._textModel0.getPositionAt(offset0); + + let acceptedChangesLen = 0; + for (const range of ranges0) { + if (range.getEndPosition().isBefore(start0)) { + // accepted hunk _before_ this projected change. When projecting into textModel0 + // we need to add that + acceptedChangesLen += this._textModel0.getValueLengthInRange(range); + } + } + + const start = this._textModel0.getPositionAt(offset0 + acceptedChangesLen); + const end = this._textModel0.getPositionAt(offset0 + acceptedChangesLen + change.rangeLength); + edits.push(EditOperation.replace(Range.fromPositions(start, end), change.text)); + } + + this._textModel0.pushEditOperations(null, edits, () => null); } async recompute() { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index f118144130e..9ece4b67967 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -39,6 +39,11 @@ import { CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX import { HunkState } from './inlineChatSession'; import { assertType } from 'vs/base/common/types'; +export interface IEditObserver { + start(): void; + stop(): void; +} + export abstract class EditModeStrategy { protected static _decoBlock = ModelDecorationOptions.register({ @@ -80,9 +85,9 @@ export abstract class EditModeStrategy { this._onDidDiscard.fire(); } - abstract makeProgressiveChanges(edits: ISingleEditOperation[], timings: ProgressingEditsOptions): Promise; + abstract makeProgressiveChanges(edits: ISingleEditOperation[], obs: IEditObserver, timings: ProgressingEditsOptions): Promise; - abstract makeChanges(edits: ISingleEditOperation[]): Promise; + abstract makeChanges(edits: ISingleEditOperation[], obs: IEditObserver): Promise; abstract undoChanges(altVersionId: number): Promise; @@ -239,7 +244,7 @@ export class LivePreviewStrategy extends EditModeStrategy { await undoModelUntil(modelN, targetAltVersion); } - override async makeChanges(edits: ISingleEditOperation[]): Promise { + override async makeChanges(edits: ISingleEditOperation[], obs: IEditObserver): Promise { const cursorStateComputerAndInlineDiffCollection: ICursorStateComputer = (undoEdits) => { let last: Position | null = null; for (const edit of undoEdits) { @@ -252,7 +257,9 @@ export class LivePreviewStrategy extends EditModeStrategy { if (++this._editCount === 1) { this._editor.pushUndoStop(); } + obs.start(); this._editor.executeEdits('inline-chat-live', edits, cursorStateComputerAndInlineDiffCollection); + obs.stop(); } override async undoChanges(altVersionId: number): Promise { @@ -261,7 +268,7 @@ export class LivePreviewStrategy extends EditModeStrategy { await this._updateDiffZones(); } - override async makeProgressiveChanges(edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise { + override async makeProgressiveChanges(edits: ISingleEditOperation[], obs: IEditObserver, opts: ProgressingEditsOptions): Promise { // push undo stop before first edit if (++this._editCount === 1) { @@ -280,7 +287,7 @@ export class LivePreviewStrategy extends EditModeStrategy { const wordCount = countWords(edit.text ?? ''); const speed = wordCount / durationInSec; // console.log({ durationInSec, wordCount, speed: wordCount / durationInSec }); - await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(new WindowIntervalTimer(this._zone.domNode), edit, speed, opts.token)); + await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(new WindowIntervalTimer(this._zone.domNode), edit, speed, opts.token), undefined, obs); } await renderTask; @@ -398,7 +405,7 @@ export interface AsyncTextEdit { readonly newText: AsyncIterable; } -export async function performAsyncTextEdit(model: ITextModel, edit: AsyncTextEdit, progress?: IProgress) { +export async function performAsyncTextEdit(model: ITextModel, edit: AsyncTextEdit, progress?: IProgress, obs?: IEditObserver) { const [id] = model.deltaDecorations([], [{ range: edit.range, @@ -423,11 +430,12 @@ export async function performAsyncTextEdit(model: ITextModel, edit: AsyncTextEdi const edit = first ? EditOperation.replace(range, part) // first edit needs to override the "anchor" : EditOperation.insert(range.getEndPosition(), part); - + obs?.start(); model.pushEditOperations(null, [edit], (undoEdits) => { progress?.report(undoEdits); return null; }); + obs?.stop(); first = false; } } @@ -578,15 +586,15 @@ export class LiveStrategy extends EditModeStrategy { await undoModelUntil(textModelN, altVersionId); } - override async makeChanges(edits: ISingleEditOperation[]): Promise { - return this._makeChanges(edits, undefined); + override async makeChanges(edits: ISingleEditOperation[], obs: IEditObserver): Promise { + return this._makeChanges(edits, obs, undefined); } - override async makeProgressiveChanges(edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise { - return this._makeChanges(edits, opts); + override async makeProgressiveChanges(edits: ISingleEditOperation[], obs: IEditObserver, opts: ProgressingEditsOptions): Promise { + return this._makeChanges(edits, obs, opts); } - private async _makeChanges(edits: ISingleEditOperation[], opts: ProgressingEditsOptions | undefined): Promise { + private async _makeChanges(edits: ISingleEditOperation[], obs: IEditObserver, opts: ProgressingEditsOptions | undefined): Promise { // push undo stop before first edit if (++this._editCount === 1) { @@ -619,15 +627,17 @@ export class LiveStrategy extends EditModeStrategy { const wordCount = countWords(edit.text ?? ''); const speed = wordCount / durationInSec; // console.log({ durationInSec, wordCount, speed: wordCount / durationInSec }); - await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(new WindowIntervalTimer(this._zone.domNode), edit, speed, opts.token), progress); + await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(new WindowIntervalTimer(this._zone.domNode), edit, speed, opts.token), progress, obs); } } else { // SYNC + obs.start(); this._editor.executeEdits('inline-chat-live', edits, undoEdits => { progress.report(undoEdits); return null; }); + obs.stop(); } } diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts index f5f42a2bac8..e21cc59da32 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts @@ -231,6 +231,22 @@ suite('InlineChatSession', function () { ensureNoDisposablesAreLeakedInTestSuite(); + async function makeEditAsAi(edit: EditOperation | EditOperation[]) { + const session = inlineChatSessionService.getSession(editor, editor.getModel()!.uri); + assertType(session); + session.hunkData.ignoreTextModelNChanges = true; + try { + editor.executeEdits('test', Array.isArray(edit) ? edit : [edit]); + } finally { + session.hunkData.ignoreTextModelNChanges = false; + } + await session.hunkData.recompute(); + } + + function makeEdit(edit: EditOperation | EditOperation[]) { + editor.executeEdits('test', Array.isArray(edit) ? edit : [edit]); + } + test('Create, release', async function () { const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); @@ -246,18 +262,19 @@ suite('InlineChatSession', function () { assertType(session); assert.ok(session.textModelN === model); - editor.executeEdits('test', [EditOperation.insert(new Position(1, 1), 'AI_EDIT\n')]); + await makeEditAsAi(EditOperation.insert(new Position(1, 1), 'AI_EDIT\n')); + - await session.hunkData.recompute(); assert.strictEqual(session.hunkData.size, 1); - const [hunk] = session.hunkData.getInfo(); + let [hunk] = session.hunkData.getInfo(); assertType(hunk); assert.ok(!session.textModel0.equalsTextBuffer(session.textModelN.getTextBuffer())); assert.strictEqual(hunk.getState(), HunkState.Pending); assert.ok(hunk.getRangesN()[0].equalsRange({ startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 7 })); - editor.executeEdits('test', [EditOperation.insert(new Position(1, 3), 'foobar')]); + await makeEditAsAi(EditOperation.insert(new Position(1, 3), 'foobar')); + [hunk] = session.hunkData.getInfo(); assert.ok(hunk.getRangesN()[0].equalsRange({ startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 13 })); inlineChatSessionService.releaseSession(session); @@ -270,9 +287,8 @@ suite('InlineChatSession', function () { const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); assertType(session); - editor.executeEdits('test', [EditOperation.insert(new Position(1, 1), 'AI_EDIT\n'), EditOperation.insert(new Position(10, 1), 'AI_EDIT\n')]); + await makeEditAsAi([EditOperation.insert(new Position(1, 1), 'AI_EDIT\n'), EditOperation.insert(new Position(10, 1), 'AI_EDIT\n')]); - await session.hunkData.recompute(); assert.strictEqual(session.hunkData.size, 2); assert.ok(!session.textModel0.equalsTextBuffer(session.textModelN.getTextBuffer())); @@ -292,9 +308,8 @@ suite('InlineChatSession', function () { const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); assertType(session); - editor.executeEdits('test', [EditOperation.insert(new Position(1, 1), 'AI_EDIT\n'), EditOperation.insert(new Position(10, 1), 'AI_EDIT\n')]); + await makeEditAsAi([EditOperation.insert(new Position(1, 1), 'AI_EDIT\n'), EditOperation.insert(new Position(10, 1), 'AI_EDIT\n')]); - await session.hunkData.recompute(); assert.strictEqual(session.hunkData.size, 2); assert.ok(!session.textModel0.equalsTextBuffer(session.textModelN.getTextBuffer())); @@ -321,13 +336,12 @@ suite('InlineChatSession', function () { assert.strictEqual(session.hunkData.size, 0); // ROUND #1 - editor.executeEdits('test', [ + await makeEditAsAi([ EditOperation.insert(new Position(1, 1), 'AI1'), EditOperation.insert(new Position(4, 1), 'AI2'), EditOperation.insert(new Position(19, 1), 'AI3') ]); - await session.hunkData.recompute(); assert.strictEqual(session.hunkData.size, 2); // AI1, AI2 are merged into one hunk, AI3 is a separate hunk let [first, second] = session.hunkData.getInfo(); @@ -347,10 +361,9 @@ suite('InlineChatSession', function () { // ROUND #2 - editor.executeEdits('test', [ + await makeEditAsAi([ EditOperation.insert(new Position(7, 1), 'AI4'), ]); - await session.hunkData.recompute(); assert.strictEqual(session.hunkData.size, 2); [first, second] = session.hunkData.getInfo(); @@ -359,4 +372,117 @@ suite('InlineChatSession', function () { inlineChatSessionService.releaseSession(session); }); + + test('HunkData, (mirror) edit before', async function () { + + const lines = ['one', 'two', 'three']; + model.setValue(lines.join('\n')); + const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(session); + + await makeEditAsAi([EditOperation.insert(new Position(3, 1), 'AI WAS HERE\n')]); + assert.strictEqual(session.textModelN.getValue(), ['one', 'two', 'AI WAS HERE', 'three'].join('\n')); + assert.strictEqual(session.textModel0.getValue(), lines.join('\n')); + + makeEdit([EditOperation.replace(new Range(1, 1, 1, 4), 'ONE')]); + assert.strictEqual(session.textModelN.getValue(), ['ONE', 'two', 'AI WAS HERE', 'three'].join('\n')); + assert.strictEqual(session.textModel0.getValue(), ['ONE', 'two', 'three'].join('\n')); + }); + + test('HunkData, (mirror) edit after', async function () { + + const lines = ['one', 'two', 'three', 'four', 'five']; + model.setValue(lines.join('\n')); + + const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(session); + + await makeEditAsAi([EditOperation.insert(new Position(3, 1), 'AI_EDIT\n')]); + + assert.strictEqual(session.hunkData.size, 1); + const [hunk] = session.hunkData.getInfo(); + + makeEdit([EditOperation.insert(new Position(1, 1), 'USER1')]); + assert.strictEqual(session.textModelN.getValue(), ['USER1one', 'two', 'AI_EDIT', 'three', 'four', 'five'].join('\n')); + assert.strictEqual(session.textModel0.getValue(), ['USER1one', 'two', 'three', 'four', 'five'].join('\n')); + + makeEdit([EditOperation.insert(new Position(5, 1), 'USER2')]); + assert.strictEqual(session.textModelN.getValue(), ['USER1one', 'two', 'AI_EDIT', 'three', 'USER2four', 'five'].join('\n')); + assert.strictEqual(session.textModel0.getValue(), ['USER1one', 'two', 'three', 'USER2four', 'five'].join('\n')); + + hunk.acceptChanges(); + assert.strictEqual(session.textModelN.getValue(), ['USER1one', 'two', 'AI_EDIT', 'three', 'USER2four', 'five'].join('\n')); + assert.strictEqual(session.textModel0.getValue(), ['USER1one', 'two', 'AI_EDIT', 'three', 'USER2four', 'five'].join('\n')); + }); + + test('HunkData, (mirror) edit inside ', async function () { + + const lines = ['one', 'two', 'three']; + model.setValue(lines.join('\n')); + const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(session); + + await makeEditAsAi([EditOperation.insert(new Position(3, 1), 'AI WAS HERE\n')]); + assert.strictEqual(session.textModelN.getValue(), ['one', 'two', 'AI WAS HERE', 'three'].join('\n')); + assert.strictEqual(session.textModel0.getValue(), lines.join('\n')); + + makeEdit([EditOperation.replace(new Range(3, 4, 3, 7), 'wwaaassss')]); + assert.strictEqual(session.textModelN.getValue(), ['one', 'two', 'AI wwaaassss HERE', 'three'].join('\n')); + assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'three'].join('\n')); + }); + + test('HunkData, (mirror) edit after, multi turn', async function () { + + const lines = ['one', 'two', 'three', 'four', 'five']; + model.setValue(lines.join('\n')); + + const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(session); + + await makeEditAsAi([EditOperation.insert(new Position(3, 1), 'AI_EDIT\n')]); + + assert.strictEqual(session.hunkData.size, 1); + + makeEdit([EditOperation.insert(new Position(5, 1), 'FOO')]); + assert.strictEqual(session.textModelN.getValue(), ['one', 'two', 'AI_EDIT', 'three', 'FOOfour', 'five'].join('\n')); + assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'three', 'FOOfour', 'five'].join('\n')); + + await makeEditAsAi([EditOperation.insert(new Position(2, 4), ' zwei')]); + assert.strictEqual(session.hunkData.size, 1); + + assert.strictEqual(session.textModelN.getValue(), ['one', 'two zwei', 'AI_EDIT', 'three', 'FOOfour', 'five'].join('\n')); + assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'three', 'FOOfour', 'five'].join('\n')); + + makeEdit([EditOperation.replace(new Range(6, 3, 6, 5), 'vefivefi')]); + assert.strictEqual(session.textModelN.getValue(), ['one', 'two zwei', 'AI_EDIT', 'three', 'FOOfour', 'fivefivefi'].join('\n')); + assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'three', 'FOOfour', 'fivefivefi'].join('\n')); + }); + + test('HunkData, (mirror) edit after, multi turn 2', async function () { + + const lines = ['one', 'two', 'three', 'four', 'five']; + model.setValue(lines.join('\n')); + + const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(session); + + await makeEditAsAi([EditOperation.insert(new Position(3, 1), 'AI_EDIT\n')]); + + assert.strictEqual(session.hunkData.size, 1); + + makeEdit([EditOperation.insert(new Position(5, 1), 'FOO')]); + assert.strictEqual(session.textModelN.getValue(), ['one', 'two', 'AI_EDIT', 'three', 'FOOfour', 'five'].join('\n')); + assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'three', 'FOOfour', 'five'].join('\n')); + + await makeEditAsAi([EditOperation.insert(new Position(2, 4), 'zwei')]); + assert.strictEqual(session.hunkData.size, 1); + + assert.strictEqual(session.textModelN.getValue(), ['one', 'twozwei', 'AI_EDIT', 'three', 'FOOfour', 'five'].join('\n')); + assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'three', 'FOOfour', 'five'].join('\n')); + + makeEdit([EditOperation.replace(new Range(6, 3, 6, 5), 'vefivefi')]); + assert.strictEqual(session.textModelN.getValue(), ['one', 'twozwei', 'AI_EDIT', 'three', 'FOOfour', 'fivefivefi'].join('\n')); + assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'three', 'FOOfour', 'fivefivefi'].join('\n')); + }); + }); From 3530c3bedce5bd03e0ccf59610a53eb73f218fdd Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 18 Jan 2024 14:57:26 +0100 Subject: [PATCH 0414/1897] update WorkspaceEdit#set signature to how it is implemented. Bulk setting allows for optional metadata (#202725) --- src/vs/workbench/api/common/extHostTypes.ts | 6 +++--- .../api/test/browser/extHostTypes.test.ts | 16 ++++++++++++++++ src/vscode-dts/vscode.d.ts | 4 ++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index cd84f1b1675..12fe0b4779d 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -862,11 +862,11 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { } set(uri: URI, edits: ReadonlyArray): void; - set(uri: URI, edits: ReadonlyArray<[TextEdit | SnippetTextEdit, vscode.WorkspaceEditEntryMetadata]>): void; + set(uri: URI, edits: ReadonlyArray<[TextEdit | SnippetTextEdit, vscode.WorkspaceEditEntryMetadata | undefined]>): void; set(uri: URI, edits: readonly NotebookEdit[]): void; - set(uri: URI, edits: ReadonlyArray<[NotebookEdit, vscode.WorkspaceEditEntryMetadata]>): void; + set(uri: URI, edits: ReadonlyArray<[NotebookEdit, vscode.WorkspaceEditEntryMetadata | undefined]>): void; - set(uri: URI, edits: null | undefined | ReadonlyArray): void { + set(uri: URI, edits: null | undefined | ReadonlyArray): void { if (!edits) { // remove all text, snippet, or notebook edits for `uri` for (let i = 0; i < this._edits.length; i++) { diff --git a/src/vs/workbench/api/test/browser/extHostTypes.test.ts b/src/vs/workbench/api/test/browser/extHostTypes.test.ts index 2586aaf331c..8afe5e31237 100644 --- a/src/vs/workbench/api/test/browser/extHostTypes.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTypes.test.ts @@ -435,6 +435,22 @@ suite('ExtHostTypes', function () { assert.strictEqual(second.edit.newText, 'Foo'); }); + test('WorkspaceEdit - set with metadata accepts undefined', function () { + const edit = new types.WorkspaceEdit(); + const uri = URI.parse('foo:bar'); + + edit.set(uri, [ + [types.TextEdit.insert(new types.Position(0, 0), 'Hello'), { needsConfirmation: true, label: 'foo' }], + [types.TextEdit.insert(new types.Position(0, 0), 'Hello'), undefined], + ]); + + const all = edit._allEntries(); + assert.strictEqual(all.length, 2); + const [first, second] = all; + assert.ok(first.metadata); + assert.ok(!second.metadata); + }); + test('DocumentLink', () => { assert.throws(() => new types.DocumentLink(null!, null!)); assert.throws(() => new types.DocumentLink(new types.Range(1, 1, 1, 1), null!)); diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 5a7ea8aec46..da13bcf7a35 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -3952,7 +3952,7 @@ declare module 'vscode' { * @param uri A resource identifier. * @param edits An array of edits. */ - set(uri: Uri, edits: ReadonlyArray<[TextEdit | SnippetTextEdit, WorkspaceEditEntryMetadata]>): void; + set(uri: Uri, edits: ReadonlyArray<[TextEdit | SnippetTextEdit, WorkspaceEditEntryMetadata | undefined]>): void; /** * Set (and replace) notebook edits for a resource. @@ -3968,7 +3968,7 @@ declare module 'vscode' { * @param uri A resource identifier. * @param edits An array of edits. */ - set(uri: Uri, edits: ReadonlyArray<[NotebookEdit, WorkspaceEditEntryMetadata]>): void; + set(uri: Uri, edits: ReadonlyArray<[NotebookEdit, WorkspaceEditEntryMetadata | undefined]>): void; /** * Get the text edits for a resource. From 04f554b0af1abb516c022a87f568a5b91548bb52 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 18 Jan 2024 14:59:47 +0100 Subject: [PATCH 0415/1897] speech - scaffold keyword activation (#202643) * speech - scaffold keyword activation * first cut settting for keyword activation * handle lifecycle better * . * . * . * tweaks * show a status bar entry * add in context option * cleanup --- src/vs/base/common/event.ts | 21 -- src/vs/base/test/common/event.test.ts | 32 +-- .../workbench/api/browser/mainThreadSpeech.ts | 45 +++- .../workbench/api/common/extHost.api.impl.ts | 3 +- .../workbench/api/common/extHost.protocol.ts | 6 +- src/vs/workbench/api/common/extHostSpeech.ts | 28 +++ src/vs/workbench/api/common/extHostTypes.ts | 5 + .../browser/accessibility.contribution.ts | 3 +- .../browser/accessibilityConfiguration.ts | 60 +++-- .../actions/voiceChatActions.ts | 223 +++++++++++++++++- .../electron-sandbox/chat.contribution.ts | 7 +- .../services/notebookEditorServiceImpl.ts | 4 +- .../contrib/speech/common/speechService.ts | 146 +++++++++++- .../editor/common/editorGroupsService.ts | 12 +- .../test/browser/workbenchTestServices.ts | 2 + src/vscode-dts/vscode.proposed.speech.d.ts | 20 +- 16 files changed, 514 insertions(+), 103 deletions(-) diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index e0070330e1d..3686b27d8e8 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -622,27 +622,6 @@ export namespace Event { return event(e => handler(e)); } - /** - * Adds a listener to an event and calls the listener immediately with undefined as the event object. A new - * {@link DisposableStore} is passed to the listener which is disposed when the returned disposable is disposed. - */ - export function runAndSubscribeWithStore(event: Event, handler: (e: T | undefined, disposableStore: DisposableStore) => any): IDisposable { - let store: DisposableStore | null = null; - - function run(e: T | undefined) { - store?.dispose(); - store = new DisposableStore(); - handler(e, store); - } - - run(undefined); - const disposable = event(e => run(e)); - return toDisposable(() => { - disposable.dispose(); - store?.dispose(); - }); - } - class EmitterObserver implements IObserver { readonly emitter: Emitter; diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts index df7aac4363d..49962c89d5a 100644 --- a/src/vs/base/test/common/event.test.ts +++ b/src/vs/base/test/common/event.test.ts @@ -8,7 +8,7 @@ import { DeferredPromise, timeout } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { errorHandler, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { AsyncEmitter, DebounceEmitter, DynamicListEventMultiplexer, Emitter, Event, EventBufferer, EventMultiplexer, IWaitUntil, MicrotaskEmitter, PauseableEmitter, Relay, createEventDeliveryQueue } from 'vs/base/common/event'; -import { DisposableStore, IDisposable, isDisposable, setDisposableTracker, toDisposable, DisposableTracker } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, isDisposable, setDisposableTracker, DisposableTracker } from 'vs/base/common/lifecycle'; import { observableValue, transaction } from 'vs/base/common/observable'; import { MicrotaskDelay } from 'vs/base/common/symbols'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; @@ -1272,36 +1272,6 @@ suite('Event utils', () => { }); }); - test('runAndSubscribeWithStore', () => { - const eventEmitter = ds.add(new Emitter()); - const event = eventEmitter.event; - - let i = 0; - const log = new Array(); - const disposable = Event.runAndSubscribeWithStore(event, (e, disposables) => { - const idx = i++; - log.push({ label: 'handleEvent', data: e || null, idx }); - disposables.add(toDisposable(() => { - log.push({ label: 'dispose', idx }); - })); - }); - - log.push({ label: 'fire' }); - eventEmitter.fire('someEventData'); - - log.push({ label: 'disposeAll' }); - disposable.dispose(); - - assert.deepStrictEqual(log, [ - { label: 'handleEvent', data: null, idx: 0 }, - { label: 'fire' }, - { label: 'dispose', idx: 0 }, - { label: 'handleEvent', data: 'someEventData', idx: 1 }, - { label: 'disposeAll' }, - { label: 'dispose', idx: 1 }, - ]); - }); - suite('accumulate', () => { test('should not fire after a listener is disposed with undefined or []', async () => { const eventEmitter = ds.add(new Emitter()); diff --git a/src/vs/workbench/api/browser/mainThreadSpeech.ts b/src/vs/workbench/api/browser/mainThreadSpeech.ts index d0c7acdc2b6..fa52270cdf0 100644 --- a/src/vs/workbench/api/browser/mainThreadSpeech.ts +++ b/src/vs/workbench/api/browser/mainThreadSpeech.ts @@ -8,20 +8,26 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostContext, ExtHostSpeechShape, MainContext, MainThreadSpeechShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ISpeechProviderMetadata, ISpeechService, ISpeechToTextEvent } from 'vs/workbench/contrib/speech/common/speechService'; +import { IKeywordRecognitionEvent, ISpeechProviderMetadata, ISpeechService, ISpeechToTextEvent } from 'vs/workbench/contrib/speech/common/speechService'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; type SpeechToTextSession = { readonly onDidChange: Emitter; }; +type KeywordRecognitionSession = { + readonly onDidChange: Emitter; +}; + @extHostNamedCustomer(MainContext.MainThreadSpeech) export class MainThreadSpeech extends Disposable implements MainThreadSpeechShape { private readonly proxy: ExtHostSpeechShape; private readonly providerRegistrations = new Map(); - private readonly providerSessions = new Map(); + + private readonly speechToTextSessions = new Map(); + private readonly keywordRecognitionSessions = new Map(); constructor( extHostContext: IExtHostContext, @@ -47,13 +53,33 @@ export class MainThreadSpeech extends Disposable implements MainThreadSpeechShap disposables.add(token.onCancellationRequested(() => this.proxy.$cancelSpeechToTextSession(session))); const onDidChange = disposables.add(new Emitter()); - this.providerSessions.set(session, { onDidChange }); + this.speechToTextSessions.set(session, { onDidChange }); return { onDidChange: onDidChange.event, dispose: () => { cts.dispose(true); - this.providerSessions.delete(session); + this.speechToTextSessions.delete(session); + disposables.dispose(); + } + }; + }, + createKeywordRecognitionSession: token => { + const disposables = new DisposableStore(); + const cts = new CancellationTokenSource(token); + const session = Math.random(); + + this.proxy.$createKeywordRecognitionSession(handle, session); + disposables.add(token.onCancellationRequested(() => this.proxy.$cancelKeywordRecognitionSession(session))); + + const onDidChange = disposables.add(new Emitter()); + this.keywordRecognitionSessions.set(session, { onDidChange }); + + return { + onDidChange: onDidChange.event, + dispose: () => { + cts.dispose(true); + this.keywordRecognitionSessions.delete(session); disposables.dispose(); } }; @@ -75,9 +101,12 @@ export class MainThreadSpeech extends Disposable implements MainThreadSpeechShap } $emitSpeechToTextEvent(session: number, event: ISpeechToTextEvent): void { - const providerSession = this.providerSessions.get(session); - if (providerSession) { - providerSession.onDidChange.fire(event); - } + const providerSession = this.speechToTextSessions.get(session); + providerSession?.onDidChange.fire(event); + } + + $emitKeywordRecognitionEvent(session: number, event: IKeywordRecognitionEvent): void { + const providerSession = this.keywordRecognitionSessions.get(session); + providerSession?.onDidChange.fire(event); } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index ee65e81f97f..dd36b43f34f 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1627,7 +1627,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I StackFrameFocus: extHostTypes.StackFrameFocus, ThreadFocus: extHostTypes.ThreadFocus, RelatedInformationType: extHostTypes.RelatedInformationType, - SpeechToTextStatus: extHostTypes.SpeechToTextStatus + SpeechToTextStatus: extHostTypes.SpeechToTextStatus, + KeywordRecognitionStatus: extHostTypes.KeywordRecognitionStatus }; }; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 4f789f46598..978e0179e80 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -64,7 +64,7 @@ import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { InputValidationType } from 'vs/workbench/contrib/scm/common/scm'; import { IWorkspaceSymbol, NotebookPriorityInfo } from 'vs/workbench/contrib/search/common/search'; import { IRawClosedNotebookFileMatch } from 'vs/workbench/contrib/search/common/searchNotebookHelpers'; -import { ISpeechProviderMetadata, ISpeechToTextEvent } from 'vs/workbench/contrib/speech/common/speechService'; +import { IKeywordRecognitionEvent, ISpeechProviderMetadata, ISpeechToTextEvent } from 'vs/workbench/contrib/speech/common/speechService'; import { CoverageDetails, ExtensionRunTestsRequest, ICallProfileRunHandler, IFileCoverage, ISerializedTestResults, IStartControllerTests, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, TestResultState, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; import { TypeHierarchyItem } from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; @@ -1159,11 +1159,15 @@ export interface MainThreadSpeechShape extends IDisposable { $unregisterProvider(handle: number): void; $emitSpeechToTextEvent(session: number, event: ISpeechToTextEvent): void; + $emitKeywordRecognitionEvent(session: number, event: IKeywordRecognitionEvent): void; } export interface ExtHostSpeechShape { $createSpeechToTextSession(handle: number, session: number): Promise; $cancelSpeechToTextSession(session: number): Promise; + + $createKeywordRecognitionSession(handle: number, session: number): Promise; + $cancelKeywordRecognitionSession(session: number): Promise; } export interface MainThreadChatProviderShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostSpeech.ts b/src/vs/workbench/api/common/extHostSpeech.ts index 8207ab47a47..8d230fd19f2 100644 --- a/src/vs/workbench/api/common/extHostSpeech.ts +++ b/src/vs/workbench/api/common/extHostSpeech.ts @@ -52,6 +52,34 @@ export class ExtHostSpeech implements ExtHostSpeechShape { this.sessions.delete(session); } + async $createKeywordRecognitionSession(handle: number, session: number): Promise { + const provider = this.providers.get(handle); + if (!provider) { + return; + } + + const disposables = new DisposableStore(); + + const cts = new CancellationTokenSource(); + this.sessions.set(session, cts); + + const keywordRecognitionSession = disposables.add(provider.provideKeywordRecognitionSession(cts.token)); + disposables.add(keywordRecognitionSession.onDidChange(e => { + if (cts.token.isCancellationRequested) { + return; + } + + this.proxy.$emitKeywordRecognitionEvent(session, e); + })); + + disposables.add(cts.token.onCancellationRequested(() => disposables.dispose())); + } + + async $cancelKeywordRecognitionSession(session: number): Promise { + this.sessions.get(session)?.dispose(true); + this.sessions.delete(session); + } + registerProvider(extension: ExtensionIdentifier, identifier: string, provider: vscode.SpeechProvider): IDisposable { const handle = ExtHostSpeech.ID_POOL++; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 12fe0b4779d..4c45b385fa8 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4205,4 +4205,9 @@ export enum SpeechToTextStatus { Stopped = 4 } +export enum KeywordRecognitionStatus { + Recognized = 1, + Stopped = 2 +} + //#endregion diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 5df70bb0761..ac3d45e7a70 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { registerAccessibilityConfiguration } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { DynamicSpeechAccessibilityConfiguration, 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'; @@ -30,3 +30,4 @@ workbenchContributionsRegistry.registerWorkbenchContribution(NotificationAccessi workbenchContributionsRegistry.registerWorkbenchContribution(InlineCompletionsAccessibleViewContribution, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(AccessibilityStatus, LifecyclePhase.Ready); workbenchContributionsRegistry.registerWorkbenchContribution(SaveAudioCueContribution, LifecyclePhase.Ready); +workbenchContributionsRegistry.registerWorkbenchContribution(DynamicSpeechAccessibilityConfiguration, LifecyclePhase.Ready); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 16b03b216cd..5c37fed8c8c 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -9,6 +9,10 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { AccessibilityAlertSettingId } from 'vs/platform/audioCues/browser/audioCueService'; +import { ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { Event } from 'vs/base/common/event'; export const accessibilityHelpIsShown = new RawContextKey('accessibilityHelpIsShown', false, true); export const accessibleViewIsShown = new RawContextKey('accessibleViewIsShown', false, true); @@ -35,11 +39,6 @@ export const enum ViewDimUnfocusedOpacityProperties { Maximum = 1 } -export const enum AccessibilityVoiceSettingId { - SpeechTimeout = 'accessibility.voice.speechTimeout', -} -export const SpeechTimeoutDefault = 1200; - export const enum AccessibilityVerbositySettingId { Terminal = 'accessibility.verbosity.terminal', DiffEditor = 'accessibility.verbosity.diffEditor', @@ -77,10 +76,14 @@ const baseProperty: object = { tags: ['accessibility'] }; -const configuration: IConfigurationNode = { +export const accessibilityConfigurationNodeBase = Object.freeze({ id: 'accessibility', title: localize('accessibilityConfigurationTitle', "Accessibility"), - type: 'object', + type: 'object' +}); + +const configuration: IConfigurationNode = { + ...accessibilityConfigurationNodeBase, properties: { [AccessibilityVerbositySettingId.Terminal]: { description: localize('verbosity.terminal.description', 'Provide information about how to access the terminal accessibility help menu when the terminal is focused.'), @@ -251,13 +254,6 @@ const configuration: IConfigurationNode = { 'default': true, tags: ['accessibility'] }, - [AccessibilityVoiceSettingId.SpeechTimeout]: { - 'markdownDescription': localize('voice.speechTimeout', "The duration in milliseconds that voice speech recognition remains active after you stop speaking. For example in a chat session, the transcribed text is submitted automatically after the timeout is met. Set to `0` to disable this feature."), - 'type': 'number', - 'default': SpeechTimeoutDefault, - 'minimum': 0, - 'tags': ['accessibility'] - }, [AccessibilityWorkbenchSettingId.AccessibleViewCloseOnKeyPress]: { markdownDescription: localize('terminal.integrated.accessibleView.closeOnKeyPress', "On keypress, close the Accessible View and focus the element from which it was invoked."), type: 'boolean', @@ -298,3 +294,39 @@ export function registerAccessibilityConfiguration() { } }); } + +export const enum AccessibilityVoiceSettingId { + SpeechTimeout = 'accessibility.voice.speechTimeout' +} +export const SpeechTimeoutDefault = 1200; + +export class DynamicSpeechAccessibilityConfiguration extends Disposable implements IWorkbenchContribution { + + constructor( + @ISpeechService private readonly speechService: ISpeechService + ) { + super(); + + this._register(Event.runAndSubscribe(speechService.onDidRegisterSpeechProvider, () => this.updateConfiguration())); + } + + private updateConfiguration(): void { + if (!this.speechService.hasSpeechProvider) { + return; // these settings require a speech provider + } + + const registry = Registry.as(Extensions.Configuration); + registry.registerConfiguration({ + ...accessibilityConfigurationNodeBase, + properties: { + [AccessibilityVoiceSettingId.SpeechTimeout]: { + 'markdownDescription': localize('voice.speechTimeout', "The duration in milliseconds that voice speech recognition remains active after you stop speaking. For example in a chat session, the transcribed text is submitted automatically after the timeout is met. Set to `0` to disable this feature."), + 'type': 'number', + 'default': SpeechTimeoutDefault, + 'minimum': 0, + 'tags': ['accessibility'] + } + } + }); + } +} diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 2a684fff64b..11dcef4882a 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -8,7 +8,7 @@ import { Event } from 'vs/base/common/event'; import { firstOrDefault } from 'vs/base/common/arrays'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; @@ -23,14 +23,14 @@ import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_PROVIDER_EXISTS } from 'vs/wo import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; -import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { HasSpeechProvider, ISpeechService, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; import { RunOnceScheduler } from 'vs/base/common/async'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ACTIVITY_BAR_BADGE_BACKGROUND } from 'vs/workbench/common/theme'; @@ -39,8 +39,13 @@ import { Color } from 'vs/base/common/color'; import { contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { isNumber } from 'vs/base/common/types'; -import { AccessibilityVoiceSettingId, SpeechTimeoutDefault } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVoiceSettingId, SpeechTimeoutDefault, accessibilityConfigurationNodeBase } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IChatExecuteActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); @@ -747,3 +752,213 @@ registerThemingParticipant((theme, collector) => { } `); }); + +export class KeywordActivationContribution extends Disposable implements IWorkbenchContribution { + + static SETTINGS_ID = 'accessibility.voice.keywordActivation'; + + static SETTINGS_VALUE = { + OFF: 'off', + INLINE_CHAT: 'inlineChat', + QUICK_CHAT: 'quickChat', + VIEW_CHAT: 'chatInView', + CHAT_IN_CONTEXT: 'chatInContext' + }; + + private activeSession: CancellationTokenSource | undefined = undefined; + + constructor( + @ISpeechService private readonly speechService: ISpeechService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @ICommandService private readonly commandService: ICommandService, + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, + @IInstantiationService instantiationService: IInstantiationService + ) { + super(); + + this._register(instantiationService.createInstance(KeywordActivationStatusEntry)); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(Event.runAndSubscribe(this.speechService.onDidRegisterSpeechProvider, () => { + this.updateConfiguration(); + this.handleKeywordActivation(); + })); + + this._register(this.speechService.onDidStartSpeechToTextSession(() => this.handleKeywordActivation())); + this._register(this.speechService.onDidEndSpeechToTextSession(() => this.handleKeywordActivation())); + + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(KeywordActivationContribution.SETTINGS_ID)) { + this.handleKeywordActivation(); + } + })); + + this._register(this.editorGroupService.onDidCreateAuxiliaryEditorPart(({ instantiationService, disposables }) => { + disposables.add(instantiationService.createInstance(KeywordActivationStatusEntry)); + })); + } + + private updateConfiguration(): void { + if (!this.speechService.hasSpeechProvider) { + return; // these settings require a speech provider + } + + const registry = Registry.as(Extensions.Configuration); + registry.registerConfiguration({ + ...accessibilityConfigurationNodeBase, + properties: { + [KeywordActivationContribution.SETTINGS_ID]: { + 'type': 'string', + 'enum': [ + KeywordActivationContribution.SETTINGS_VALUE.OFF, + KeywordActivationContribution.SETTINGS_VALUE.VIEW_CHAT, + KeywordActivationContribution.SETTINGS_VALUE.QUICK_CHAT, + KeywordActivationContribution.SETTINGS_VALUE.INLINE_CHAT, + KeywordActivationContribution.SETTINGS_VALUE.CHAT_IN_CONTEXT + ], + 'enumDescriptions': [ + localize('voice.keywordActivation.off', "Keyword activation is disabled."), + localize('voice.keywordActivation.chatInView', "Keyword activation is enabled and listening for 'Hey Code' to start a voice chat session in the chat view."), + localize('voice.keywordActivation.quickChat', "Keyword activation is enabled and listening for 'Hey Code' to start a voice chat session in the quick chat."), + localize('voice.keywordActivation.inlineChat', "Keyword activation is enabled and listening for 'Hey Code' to start a voice chat session in the active editor."), + localize('voice.keywordActivation.chatInContext', "Keyword activation is enabled and listening for 'Hey Code' to start a voice chat session in the active editor or view depending on keyboard focus.") + ], + 'description': localize('voice.keywordActivation', "Controls whether the phrase 'Hey Code' should be speech recognized to start a voice chat session."), + 'default': 'off', + 'tags': ['accessibility'] + } + } + }); + } + + private handleKeywordActivation(): void { + const enabled = + this.speechService.hasSpeechProvider && + this.configurationService.getValue(KeywordActivationContribution.SETTINGS_ID) !== KeywordActivationContribution.SETTINGS_VALUE.OFF && + !this.speechService.hasActiveSpeechToTextSession; + if ( + (enabled && this.activeSession) || + (!enabled && !this.activeSession) + ) { + return; // already running or stopped + } + + // Start keyword activation + if (enabled) { + this.enableKeywordActivation(); + } + + // Stop keyword activation + else { + this.disableKeywordActivation(); + } + } + + private async enableKeywordActivation(): Promise { + const session = this.activeSession = new CancellationTokenSource(); + const result = await this.speechService.recognizeKeyword(session.token); + if (session.token.isCancellationRequested || session !== this.activeSession) { + return; // cancelled + } + + this.activeSession = undefined; + + if (result === KeywordRecognitionStatus.Recognized) { + this.commandService.executeCommand(this.getKeywordCommand()); + } + } + + private getKeywordCommand(): string { + const setting = this.configurationService.getValue(KeywordActivationContribution.SETTINGS_ID); + switch (setting) { + case KeywordActivationContribution.SETTINGS_VALUE.INLINE_CHAT: + return InlineVoiceChatAction.ID; + case KeywordActivationContribution.SETTINGS_VALUE.QUICK_CHAT: + return QuickVoiceChatAction.ID; + case KeywordActivationContribution.SETTINGS_VALUE.CHAT_IN_CONTEXT: + return StartVoiceChatAction.ID; + default: + return VoiceChatInChatViewAction.ID; + } + } + + private disableKeywordActivation(): void { + this.activeSession?.dispose(true); + this.activeSession = undefined; + } + + override dispose(): void { + this.activeSession?.dispose(); + + super.dispose(); + } +} + +class KeywordActivationStatusEntry extends Disposable { + + private readonly entry = this._register(new MutableDisposable()); + + private static STATUS_NAME = localize('keywordActivation.status.name', "Voice Keyword Activation"); + private static STATUS_COMMAND = 'keywordActivation.status.command'; + private static STATUS_ACTIVE = localize('keywordActivation.status.active', "Voice Keyword Activation: Active"); + private static STATUS_INACTIVE = localize('keywordActivation.status.inactive', "Voice Keyword Activation: Inactive"); + + constructor( + @ISpeechService private readonly speechService: ISpeechService, + @IStatusbarService private readonly statusbarService: IStatusbarService, + @ICommandService private readonly commandService: ICommandService, + @IConfigurationService private readonly configurationService: IConfigurationService, + ) { + super(); + + CommandsRegistry.registerCommand(KeywordActivationStatusEntry.STATUS_COMMAND, () => this.commandService.executeCommand('workbench.action.openSettings', KeywordActivationContribution.SETTINGS_ID)); + + this.registerListeners(); + this.updateStatusEntry(); + } + + private registerListeners(): void { + this._register(this.speechService.onDidStartKeywordRecognition(() => this.updateStatusEntry())); + this._register(this.speechService.onDidEndKeywordRecognition(() => this.updateStatusEntry())); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(KeywordActivationContribution.SETTINGS_ID)) { + this.updateStatusEntry(); + } + })); + } + + private updateStatusEntry(): void { + const visible = this.configurationService.getValue(KeywordActivationContribution.SETTINGS_ID) !== KeywordActivationContribution.SETTINGS_VALUE.OFF; + if (visible) { + if (!this.entry.value) { + this.createStatusEntry(); + } + + this.updateStatusLabel(); + } else { + this.entry.clear(); + } + } + + private createStatusEntry() { + this.entry.value = this.statusbarService.addEntry(this.getStatusEntryProperties(), 'status.voiceKeywordActivation', StatusbarAlignment.RIGHT, 103); + } + + private getStatusEntryProperties(): IStatusbarEntry { + return { + name: KeywordActivationStatusEntry.STATUS_NAME, + text: '$(mic)', + tooltip: this.speechService.hasActiveKeywordRecognition ? KeywordActivationStatusEntry.STATUS_ACTIVE : KeywordActivationStatusEntry.STATUS_INACTIVE, + ariaLabel: this.speechService.hasActiveKeywordRecognition ? KeywordActivationStatusEntry.STATUS_ACTIVE : KeywordActivationStatusEntry.STATUS_INACTIVE, + command: KeywordActivationStatusEntry.STATUS_COMMAND, + kind: this.speechService.hasActiveKeywordRecognition ? 'prominent' : 'standard' + }; + } + + private updateStatusLabel(): void { + this.entry.value?.update(this.getStatusEntryProperties()); + } +} diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts index 9ba5430737e..4d260fce917 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts @@ -3,8 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; +import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction, KeywordActivationContribution } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; import { registerAction2 } from 'vs/platform/actions/common/actions'; +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'; registerAction2(StartVoiceChatAction); @@ -20,3 +23,5 @@ registerAction2(StopListeningInChatEditorAction); registerAction2(StopListeningInQuickChatAction); registerAction2(StopListeningInInlineChatAction); +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(KeywordActivationContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts index 46f2bd5e3bc..fddd0c63854 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts @@ -7,7 +7,7 @@ import { CodeWindow } from 'vs/base/browser/window'; import { ResourceMap } from 'vs/base/common/map'; import { getDefaultNotebookCreationOptions, NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { IEditorGroupsService, IEditorGroup, IAuxiliaryEditorPart } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { isCompositeNotebookEditorInput, NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; @@ -130,7 +130,7 @@ export class NotebookEditorWidgetService implements INotebookEditorService { const sourcePart = this.editorGroupService.getPart(sourceID); const targetPart = this.editorGroupService.getPart(targetID); - if ((sourcePart as IAuxiliaryEditorPart).windowId !== (targetPart as IAuxiliaryEditorPart).windowId) { + if (sourcePart.windowId !== targetPart.windowId) { return; } diff --git a/src/vs/workbench/contrib/speech/common/speechService.ts b/src/vs/workbench/contrib/speech/common/speechService.ts index 13286662bb5..807dc5f7a95 100644 --- a/src/vs/workbench/contrib/speech/common/speechService.ts +++ b/src/vs/workbench/contrib/speech/common/speechService.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { firstOrDefault } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -34,14 +34,29 @@ export interface ISpeechToTextEvent { readonly text?: string; } +export interface ISpeechToTextSession extends IDisposable { + readonly onDidChange: Event; +} + +export enum KeywordRecognitionStatus { + Recognized = 1, + Stopped = 2 +} + +export interface IKeywordRecognitionEvent { + readonly status: KeywordRecognitionStatus; + readonly text?: string; +} + +export interface IKeywordRecognitionSession extends IDisposable { + readonly onDidChange: Event; +} + export interface ISpeechProvider { readonly metadata: ISpeechProviderMetadata; createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession; -} - -export interface ISpeechToTextSession extends IDisposable { - readonly onDidChange: Event; + createKeywordRecognitionSession(token: CancellationToken): IKeywordRecognitionSession; } export interface ISpeechService { @@ -55,20 +70,41 @@ export interface ISpeechService { registerSpeechProvider(identifier: string, provider: ISpeechProvider): IDisposable; + readonly onDidStartSpeechToTextSession: Event; + readonly onDidEndSpeechToTextSession: Event; + + readonly hasActiveSpeechToTextSession: boolean; + + /** + * Starts to transcribe speech from the default microphone. The returned + * session object provides an event to subscribe for transcribed text. + */ createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession; + + readonly onDidStartKeywordRecognition: Event; + readonly onDidEndKeywordRecognition: Event; + + readonly hasActiveKeywordRecognition: boolean; + + /** + * Starts to recognize a keyword from the default microphone. The returned + * status indicates if the keyword was recognized or if the session was + * stopped. + */ + recognizeKeyword(token: CancellationToken): Promise; } -export class SpeechService implements ISpeechService { +export class SpeechService extends Disposable implements ISpeechService { readonly _serviceBrand: undefined; - private readonly _onDidRegisterSpeechProvider = new Emitter(); + private readonly _onDidRegisterSpeechProvider = this._register(new Emitter()); readonly onDidRegisterSpeechProvider = this._onDidRegisterSpeechProvider.event; - private readonly _onDidUnregisterSpeechProvider = new Emitter(); + private readonly _onDidUnregisterSpeechProvider = this._register(new Emitter()); readonly onDidUnregisterSpeechProvider = this._onDidUnregisterSpeechProvider.event; - get hasSpeechProvider(): boolean { return this.providers.size > 0; } + get hasSpeechProvider() { return this.providers.size > 0; } private readonly providers = new Map(); @@ -78,6 +114,7 @@ export class SpeechService implements ISpeechService { @ILogService private readonly logService: ILogService, @IContextKeyService private readonly contextKeyService: IContextKeyService ) { + super(); } registerSpeechProvider(identifier: string, provider: ISpeechProvider): IDisposable { @@ -100,6 +137,15 @@ export class SpeechService implements ISpeechService { }); } + private readonly _onDidStartSpeechToTextSession = this._register(new Emitter()); + readonly onDidStartSpeechToTextSession = this._onDidStartSpeechToTextSession.event; + + private readonly _onDidEndSpeechToTextSession = this._register(new Emitter()); + readonly onDidEndSpeechToTextSession = this._onDidEndSpeechToTextSession.event; + + private _activeSpeechToTextSession: ISpeechToTextSession | undefined = undefined; + get hasActiveSpeechToTextSession() { return !!this._activeSpeechToTextSession; } + createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { const provider = firstOrDefault(Array.from(this.providers.values())); if (!provider) { @@ -108,6 +154,86 @@ export class SpeechService implements ISpeechService { this.logService.warn(`Multiple speech providers registered. Picking first one: ${provider.metadata.displayName}`); } - return provider.createSpeechToTextSession(token); + const session = this._activeSpeechToTextSession = provider.createSpeechToTextSession(token); + + const disposables = new DisposableStore(); + + const onSessionStoppedOrCanceled = () => { + if (session === this._activeSpeechToTextSession) { + this._activeSpeechToTextSession = undefined; + this._onDidEndSpeechToTextSession.fire(); + } + + disposables.dispose(); + }; + + disposables.add(token.onCancellationRequested(() => onSessionStoppedOrCanceled())); + if (token.isCancellationRequested) { + onSessionStoppedOrCanceled(); + } + + disposables.add(session.onDidChange(e => { + switch (e.status) { + case SpeechToTextStatus.Started: + if (session === this._activeSpeechToTextSession) { + this._onDidStartSpeechToTextSession.fire(); + } + break; + case SpeechToTextStatus.Stopped: + onSessionStoppedOrCanceled(); + break; + } + })); + + return session; + } + + private readonly _onDidStartKeywordRecognition = this._register(new Emitter()); + readonly onDidStartKeywordRecognition = this._onDidStartKeywordRecognition.event; + + private readonly _onDidEndKeywordRecognition = this._register(new Emitter()); + readonly onDidEndKeywordRecognition = this._onDidEndKeywordRecognition.event; + + private _activeKeywordRecognitionSession: IKeywordRecognitionSession | undefined = undefined; + get hasActiveKeywordRecognition() { return !!this._activeKeywordRecognitionSession; } + + async recognizeKeyword(token: CancellationToken): Promise { + const provider = firstOrDefault(Array.from(this.providers.values())); + if (!provider) { + throw new Error(`No Speech provider is registered.`); + } else if (this.providers.size > 1) { + this.logService.warn(`Multiple speech providers registered. Picking first one: ${provider.metadata.displayName}`); + } + + const session = this._activeKeywordRecognitionSession = provider.createKeywordRecognitionSession(token); + this._onDidStartKeywordRecognition.fire(); + + const disposables = new DisposableStore(); + + const onSessionStoppedOrCanceled = () => { + if (session === this._activeKeywordRecognitionSession) { + this._activeKeywordRecognitionSession = undefined; + this._onDidEndKeywordRecognition.fire(); + } + + disposables.dispose(); + }; + + disposables.add(token.onCancellationRequested(() => onSessionStoppedOrCanceled())); + if (token.isCancellationRequested) { + onSessionStoppedOrCanceled(); + } + + disposables.add(session.onDidChange(e => { + if (e.status === KeywordRecognitionStatus.Stopped) { + onSessionStoppedOrCanceled(); + } + })); + + try { + return (await Event.toPromise(session.onDidChange)).status; + } finally { + onSessionStoppedOrCanceled(); + } } } diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index bd39f81d3cc..0dd568153f3 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -433,6 +433,11 @@ export interface IEditorPart extends IEditorGroupsContainer { */ readonly onDidScroll: Event; + /** + * The identifier of the window the editor part is contained in. + */ + readonly windowId: number; + /** * The size of the editor part. */ @@ -461,11 +466,6 @@ export interface IEditorPart extends IEditorGroupsContainer { export interface IAuxiliaryEditorPart extends IEditorPart { - /** - * The identifier of the window the auxiliary editor part is contained in. - */ - readonly windowId: number; - /** * Close this auxiliary editor part after moving all * editors of all groups back to the main editor part. @@ -590,7 +590,7 @@ export interface IEditorGroup { readonly id: GroupIdentifier; /** - * The identifier of the `CodeWindow` this editor group is part of. + * The identifier of the window this editor group is part of. */ readonly windowId: number; diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 0eacb21061c..dac55f97d95 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -828,6 +828,8 @@ export class TestEditorGroupsService implements IEditorGroupsService { readonly parts: readonly IEditorPart[] = [this]; + windowId = mainWindow.vscodeWindowId; + onDidCreateAuxiliaryEditorPart: Event = Event.None; onDidChangeActiveGroup: Event = Event.None; onDidActivateGroup: Event = Event.None; diff --git a/src/vscode-dts/vscode.proposed.speech.d.ts b/src/vscode-dts/vscode.proposed.speech.d.ts index 81c90e39bac..42fbbb03320 100644 --- a/src/vscode-dts/vscode.proposed.speech.d.ts +++ b/src/vscode-dts/vscode.proposed.speech.d.ts @@ -5,6 +5,8 @@ declare module 'vscode' { + // todo@bpasero work in progress speech API + export enum SpeechToTextStatus { Started = 1, Recognizing = 2, @@ -21,15 +23,27 @@ declare module 'vscode' { readonly onDidChange: Event; } + export enum KeywordRecognitionStatus { + Recognized = 1, + Stopped = 2 + } + + export interface KeywordRecognitionEvent { + readonly status: KeywordRecognitionStatus; + readonly text?: string; + } + + export interface KeywordRecognitionSession extends Disposable { + readonly onDidChange: Event; + } + export interface SpeechProvider { provideSpeechToTextSession(token: CancellationToken): SpeechToTextSession; + provideKeywordRecognitionSession(token: CancellationToken): KeywordRecognitionSession; } export namespace speech { - /** - * TODO@bpasero work in progress speech provider API - */ export function registerSpeechProvider(id: string, provider: SpeechProvider): Disposable; } } From 284417d02a70b5230e74549539a444d56f980c04 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 18 Jan 2024 15:08:22 +0100 Subject: [PATCH 0416/1897] Feature: change font family of inline completions (#202671) Feature: change font family of inline completions --------- Co-authored-by: Henning Dieterichs --- src/vs/base/browser/dom.ts | 32 +++++++++++++++++++ src/vs/editor/common/config/editorOptions.ts | 12 +++++++ .../browser/inlineCompletionsController.ts | 13 ++++++++ src/vs/monaco.d.ts | 4 +++ 4 files changed, 61 insertions(+) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 18e56736dd2..c5fea2d0c7b 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -927,6 +927,38 @@ export function isGlobalStylesheet(node: Node): boolean { return globalStylesheets.has(node as HTMLStyleElement); } +/** + * A version of createStyleSheet which has a unified API to initialize/set the style content. + */ +export function createStyleSheet2(): WrappedStyleElement { + return new WrappedStyleElement(); +} + +class WrappedStyleElement { + private _currentCssStyle = ''; + private _styleSheet: HTMLStyleElement | undefined = undefined; + + public setStyle(cssStyle: string): void { + if (cssStyle !== this._currentCssStyle) { + return; + } + this._currentCssStyle = cssStyle; + + if (!this._styleSheet) { + this._styleSheet = createStyleSheet(mainWindow.document.head, (s) => s.innerText = cssStyle); + } else { + this._styleSheet.innerText = cssStyle; + } + } + + public dispose(): void { + if (this._styleSheet) { + clearNode(this._styleSheet); + this._styleSheet = undefined; + } + } +} + export function createStyleSheet(container: HTMLElement = mainWindow.document.head, beforeAppend?: (style: HTMLStyleElement) => void, disposableStore?: DisposableStore): HTMLStyleElement { const style = document.createElement('style'); style.type = 'text/css'; diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 2890c08e763..944041b7fc3 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -3993,6 +3993,11 @@ export interface IInlineSuggestOptions { * Does not clear active inline suggestions when the editor loses focus. */ keepOnBlur?: boolean; + + /** + * Font family for inline suggestions. + */ + fontFamily?: string | 'default'; } /** @@ -4011,6 +4016,7 @@ class InlineEditorSuggest extends BaseEditorOption this.editor.getOption(EditorOption.inlineSuggest).enabled); + private readonly _fontFamily = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).fontFamily); private _ghostTextWidget = this._register(this._instantiationService.createInstance(GhostTextWidget, this.editor, { ghostText: this.model.map((v, reader) => /** ghostText */ v?.ghostText.read(reader)), @@ -112,6 +114,17 @@ export class InlineCompletionsController extends Disposable { }); })); + const styleElement = this._register(createStyleSheet2()); + this._register(autorun(reader => { + const fontFamily = this._fontFamily.read(reader); + styleElement.setStyle(fontFamily === '' || fontFamily === 'default' ? `` : ` +.monaco-editor .ghost-text-decoration, +.monaco-editor .ghost-text-decoration-preview, +.monaco-editor .ghost-text { + font-family: ${fontFamily}; +}`); + })); + const getReason = (e: IModelContentChangedEvent): VersionIdChangeReason => { if (e.isUndoing) { return VersionIdChangeReason.Undo; } if (e.isRedoing) { return VersionIdChangeReason.Redo; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 51a4fa5d458..cea289bcfda 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4502,6 +4502,10 @@ declare namespace monaco.editor { * Does not clear active inline suggestions when the editor loses focus. */ keepOnBlur?: boolean; + /** + * Font family for inline suggestions. + */ + fontFamily?: string | 'default'; } export interface IBracketPairColorizationOptions { From e4a1e14bc68d8fd92cb9b81926cd9ea23ebd928d Mon Sep 17 00:00:00 2001 From: wy-luke Date: Thu, 18 Jan 2024 22:09:08 +0800 Subject: [PATCH 0417/1897] docs: update comment for hideFromUser --- src/vs/platform/terminal/common/terminal.ts | 2 +- src/vscode-dts/vscode.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 37a2af309ae..7c20cb3305c 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -565,7 +565,7 @@ export interface IShellLaunchConfig { * until `Terminal.show` is called. The typical usage for this is when you need to run * something that may need interactivity but only want to tell the user about it when * interaction is needed. Note that the terminals will still be exposed to all extensions - * as normal. + * as normal. And the hidden terminals won't be restored when the workspace is next opened. */ hideFromUser?: boolean; diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 5a7ea8aec46..223b1adc6be 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -11737,7 +11737,7 @@ declare module 'vscode' { * until `Terminal.show` is called. The typical usage for this is when you need to run * something that may need interactivity but only want to tell the user about it when * interaction is needed. Note that the terminals will still be exposed to all extensions - * as normal. + * as normal. And the hidden terminals won't be restored when the workspace is next opened. */ hideFromUser?: boolean; From 4a5169c7e03a7e86138192303958167996dd468c Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:25:31 +0100 Subject: [PATCH 0418/1897] =?UTF-8?q?Git=20-=20=F0=9F=92=84=20remove=20unu?= =?UTF-8?q?sed=20event=20handlers=20(#202727)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/git/src/repository.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 8ac566827a6..b0d6ef6235a 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -681,12 +681,6 @@ export class Repository implements Disposable { private _onDidChangeBranchProtection = new EventEmitter(); readonly onDidChangeBranchProtection: Event = this._onDidChangeBranchProtection.event; - private _onDidStartCommitMessageGeneration = new EventEmitter(); - readonly onDidStartCommitMessageGeneration: Event = this._onDidStartCommitMessageGeneration.event; - - private _onDidEndCommitMessageGeneration = new EventEmitter(); - readonly onDidEndCommitMessageGeneration: Event = this._onDidEndCommitMessageGeneration.event; - @memoize get onDidChangeOperations(): Event { return anyEvent(this.onRunOperation as Event, this.onDidRunOperation as Event); From fba98d1cdfb2470955a294cf42622d2c4e09e4bb Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 18 Jan 2024 15:39:03 +0100 Subject: [PATCH 0419/1897] :lipstick: --- .../browser/actions/layoutActions.ts | 2 +- src/vs/workbench/browser/layout.ts | 48 ++++++++++++------- .../browser/parts/titlebar/titlebarActions.ts | 8 ++-- .../browser/parts/titlebar/titlebarPart.ts | 6 +-- .../browser/workbench.contribution.ts | 2 +- .../services/layout/browser/layoutService.ts | 1 + 6 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index ca73beb2038..d6e446d04f1 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -803,7 +803,7 @@ if (isWindows || isLinux || isWeb) { title: localize('miMenuBarNoMnemonic', "Menu Bar"), toggled: ContextKeyExpr.and(IsMacNativeContext.toNegated(), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'hidden'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'toggle'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'compact')) }, - when: ContextKeyExpr.and(IsAuxiliaryWindowFocusedContext.toNegated(), ContextKeyExpr.notEquals(TitleBarSetting.TITLE_BAR_STYLE, TitlebarStyle.NATIVE)), + when: ContextKeyExpr.and(IsAuxiliaryWindowFocusedContext.toNegated(), ContextKeyExpr.notEquals(TitleBarSetting.TITLE_BAR_STYLE, TitlebarStyle.NATIVE), IsMainWindowFullscreenContext.negate()), group: '2_config', order: 0 }); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 02d84822c0d..f40144d8cc6 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -347,6 +347,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi LayoutSettings.ACTIVITY_BAR_LOCATION, LayoutSettings.COMMAND_CENTER, LayoutSettings.EDITOR_ACTIONS_LOCATION, + LayoutSettings.LAYOUT_ACTIONS, LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION, LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE, 'window.menuBarVisibility', @@ -1229,31 +1230,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return false; } - const nativeTitleBarVisible = hasNativeTitlebar(this.configurationService); + const nativeTitleBarEnabled = hasNativeTitlebar(this.configurationService); const showCustomTitleBar = this.configurationService.getValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY); - if (showCustomTitleBar === CustomTitleBarVisibility.NEVER && nativeTitleBarVisible || showCustomTitleBar === CustomTitleBarVisibility.WINDOWED && this.state.runtime.mainWindowFullscreen) { + if (showCustomTitleBar === CustomTitleBarVisibility.NEVER && nativeTitleBarEnabled || showCustomTitleBar === CustomTitleBarVisibility.WINDOWED && this.state.runtime.mainWindowFullscreen) { return false; } - // with the command center enabled, we should always show - if (this.configurationService.getValue(LayoutSettings.COMMAND_CENTER)) { - return true; - } - - // with the activity bar on top, we should always show - if (this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP) { - return true; - } - - // with the editor actions on top, we should always show - const editorActionsLocation = this.configurationService.getValue(LayoutSettings.EDITOR_ACTIONS_LOCATION); - const editorTabsMode = this.configurationService.getValue(LayoutSettings.EDITOR_TABS_MODE); - if (editorActionsLocation === EditorActionsLocation.TITLEBAR || editorActionsLocation === EditorActionsLocation.DEFAULT && editorTabsMode === EditorTabsMode.NONE) { + if (!this.isTitleBarEmpty(nativeTitleBarEnabled)) { return true; } // Hide custom title bar when native title bar and custom title bar is empty - if (nativeTitleBarVisible) { + if (nativeTitleBarEnabled) { return false; } @@ -1288,6 +1276,32 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } + private isTitleBarEmpty(nativeTitleBarEnabled: boolean): boolean { + // with the command center enabled, we should always show + if (this.configurationService.getValue(LayoutSettings.COMMAND_CENTER)) { + return false; + } + + // with the activity bar on top, we should always show + if (this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP) { + return false; + } + + // with the editor actions on top, we should always show + const editorActionsLocation = this.configurationService.getValue(LayoutSettings.EDITOR_ACTIONS_LOCATION); + const editorTabsMode = this.configurationService.getValue(LayoutSettings.EDITOR_TABS_MODE); + if (editorActionsLocation === EditorActionsLocation.TITLEBAR || editorActionsLocation === EditorActionsLocation.DEFAULT && editorTabsMode === EditorTabsMode.NONE) { + return false; + } + + // Layout don't show with native title bar + if (!nativeTitleBarEnabled && this.configurationService.getValue(LayoutSettings.LAYOUT_ACTIONS)) { + return false; + } + + return true; + } + private shouldShowBannerFirst(): boolean { return isWeb && !isWCOEnabled(); } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index 27937ac6396..e2d88e50c31 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -69,8 +69,7 @@ registerAction2(class ToggleCustomTitleBar extends Action2 { constructor() { super({ id: `toggle.${ToggleCustomTitleBar.settingsID}`, - title: localize('toggle.customTitleBar', 'Custom Title Bar'), - toggled: ContextKeyExpr.true(), + title: localize('toggle.hideCustomTitleBar', 'Hide Custom Title Bar'), menu: [ { id: MenuId.TitleBarContext, order: 0, when: ContextKeyExpr.equals(TitleBarSetting.TITLE_BAR_STYLE, TitlebarStyle.NATIVE), group: '3_toggle' }, ] @@ -84,13 +83,12 @@ registerAction2(class ToggleCustomTitleBar extends Action2 { }); registerAction2(class ToggleCustomTitleBarWindowed extends Action2 { - static readonly settingsID = TitleBarSetting.TITLE_BAR_STYLE; + static readonly settingsID = TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY; constructor() { super({ id: `toggle.${ToggleCustomTitleBarWindowed.settingsID}.windowed`, - title: localize('toggle.customTitleBarWindowed', 'Custom Title Bar In Full Screen'), - toggled: ContextKeyExpr.true(), + title: localize('toggle.hideCustomTitleBarInFullScreen', 'Hide Custom Title Bar In Full Screen'), menu: [ { id: MenuId.TitleBarContext, order: 0, when: IsMainWindowFullscreenContext, group: '3_toggle' }, ] diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 32361f3be14..2f90f198a36 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -322,7 +322,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { // Actions if (hasCustomTitlebar(this.configurationService, this.titleBarStyle) && this.actionToolBar) { - const affectsLayoutControl = event.affectsConfiguration('workbench.layoutControl.enabled'); + const affectsLayoutControl = event.affectsConfiguration(LayoutSettings.LAYOUT_ACTIONS); const affectsActivityControl = event.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION); if (affectsLayoutControl || affectsActivityControl) { @@ -699,7 +699,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } private get layoutControlEnabled(): boolean { - return !this.isAuxiliary && this.configurationService.getValue('workbench.layoutControl.enabled') !== false && !hasNativeTitlebar(this.configurationService, this.titleBarStyle); + return !this.isAuxiliary && this.configurationService.getValue(LayoutSettings.LAYOUT_ACTIONS) !== false && !hasNativeTitlebar(this.configurationService, this.titleBarStyle); } protected get isCommandCenterVisible() { @@ -710,7 +710,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { return this.editorGroupService.partOptions.editorActionsLocation === EditorActionsLocation.TITLEBAR || ( this.editorGroupService.partOptions.editorActionsLocation === EditorActionsLocation.DEFAULT && - this.editorGroupService.partOptions.showTabs === EditorTabsMode.MULTIPLE + this.editorGroupService.partOptions.showTabs === EditorTabsMode.NONE ); } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 869b35f9572..6153d527c98 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -566,7 +566,7 @@ const registry = Registry.as(ConfigurationExtensions.Con tags: ['accessibility'], enum: ['on', 'off', 'auto'] }, - 'workbench.layoutControl.enabled': { + [LayoutSettings.LAYOUT_ACTIONS]: { 'type': 'boolean', 'default': true, 'markdownDescription': isWeb ? diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index c64f875ded0..c1091f8e7a6 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -39,6 +39,7 @@ export const enum LayoutSettings { EDITOR_TABS_MODE = 'workbench.editor.showTabs', EDITOR_ACTIONS_LOCATION = 'workbench.editor.editorActionsLocation', COMMAND_CENTER = 'window.commandCenter', + LAYOUT_ACTIONS = 'workbench.layoutControl.enabled', } export const enum ActivityBarPosition { From 272d8f096e507e1239d6094025d123407bd366a7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 18 Jan 2024 15:43:34 +0100 Subject: [PATCH 0420/1897] Tweak inline chat voice gestures (#202731) - special rendering for recognized text - enable hold more for quick inline voice chat - increase/adjust hold mode timeouts --- .../electron-sandbox/inlineChatActions.ts | 4 +- .../electron-sandbox/inlineChatQuickVoice.css | 4 ++ .../electron-sandbox/inlineChatQuickVoice.ts | 59 +++++++++++++------ 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts index 6a94b91a0d5..f3becd537a9 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts @@ -49,7 +49,7 @@ export class StartSessionAction extends EditorAction2 { const commandService = accessor.get(ICommandService); if (configService.getValue(InlineChatConfigKeys.HoldToSpeech) // enabled - && speechService.hasSpeechProvider // possible + && speechService.hasSpeechProvider // possible ) { const holdMode = keybindingService.enableKeybindingHoldMode(this.desc.id); @@ -59,7 +59,7 @@ export class StartSessionAction extends EditorAction2 { // start VOICE input commandService.executeCommand(StartVoiceChatAction.ID); listening = true; - }, 100); + }, 250); holdMode.finally(() => { if (listening) { diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.css b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.css index 784d716d80f..64ff58649ee 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.css +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.css @@ -37,3 +37,7 @@ color: var(--vscode-editorCursor-background); } } + +.monaco-editor .inline-chat-quick-voice .message.preview { + opacity: .4; +} diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index 1e47eb5d485..4713733f0f3 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -6,7 +6,7 @@ import 'vs/css!./inlineChatQuickVoice'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { Codicon } from 'vs/base/common/codicons'; -import { KeyCode } from 'vs/base/common/keyCodes'; +import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; @@ -17,10 +17,11 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { h, reset } from 'vs/base/browser/dom'; +import { getWindow, h, reset, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { IDimension } from 'vs/editor/common/core/dimension'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { AbstractInlineChatAction } from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; const CTX_QUICK_CHAT_IN_PROGRESS = new RawContextKey('inlineChat.quickChatInProgress', false); @@ -33,14 +34,29 @@ export class StartAction extends EditorAction2 { category: AbstractInlineChatAction.category, precondition: ContextKeyExpr.and(HasSpeechProvider, CTX_QUICK_CHAT_IN_PROGRESS.toNegated()), f1: true, - // keybinding: { - // primary: KeyChord(KeyCode.F12, KeyCode.F12), - // weight: KeybindingWeight.WorkbenchContrib - // } + keybinding: { + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyI), + weight: KeybindingWeight.WorkbenchContrib + 100 + } }); } - override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) { + override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor) { + const keybindingService = accessor.get(IKeybindingService); + + const holdMode = keybindingService.enableKeybindingHoldMode(this.desc.id); + if (holdMode) { + let shouldCallStop = false; + const handle = setTimeout(() => { + shouldCallStop = true; + }, 500); + holdMode.finally(() => { + clearTimeout(handle); + if (shouldCallStop) { + InlineChatQuickVoice.get(editor)?.stop(); + } + }); + } InlineChatQuickVoice.get(editor)?.start(); } } @@ -55,13 +71,13 @@ export class StopAction extends EditorAction2 { precondition: ContextKeyExpr.and(HasSpeechProvider, CTX_QUICK_CHAT_IN_PROGRESS), f1: true, keybinding: { - primary: KeyCode.Escape, - weight: KeybindingWeight.WorkbenchContrib + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyI), + weight: KeybindingWeight.WorkbenchContrib + 100 } }); } - override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) { + override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) { InlineChatQuickVoice.get(editor)?.stop(); } } @@ -93,12 +109,14 @@ class QuickVoiceWidget implements IContentWidget { private readonly _domNode = document.createElement('div'); private readonly _elements = h('.inline-chat-quick-voice@main', [ h('span@mic'), - h('span@message'), + h('span.message@message'), ]); constructor(private readonly _editor: ICodeEditor) { this._domNode.appendChild(this._elements.root); this._domNode.style.zIndex = '1000'; + this._domNode.tabIndex = -1; + this._domNode.style.outline = 'none'; reset(this._elements.mic, renderIcon(Codicon.micFilled)); } @@ -134,17 +152,22 @@ class QuickVoiceWidget implements IContentWidget { // --- - updateInput(input: string | undefined): void { + updateInput(input: string | undefined, isDefinite: boolean): void { + this._elements.message.classList.toggle('preview', !isDefinite); this._elements.message.textContent = input ?? ''; } + focus(): void { + this._domNode.focus(); + } + active(): void { this._elements.main.classList.add('recording'); } reset(): void { this._elements.main.classList.remove('recording'); - this.updateInput(undefined); + this.updateInput(undefined, true); } } @@ -179,8 +202,11 @@ export class InlineChatQuickVoice implements IEditorContribution { this._editor.addContentWidget(this._widget); this._ctxQuickChatInProgress.set(true); - let message: string | undefined; + runAtThisOrScheduleAtNextAnimationFrame(getWindow(this._widget.getDomNode()), () => { + this._widget.focus(); // requires RAF because... + }); + let message: string | undefined; const session = this._speechService.createSpeechToTextSession(cts.token); const listener = session.onDidChange(e => { @@ -195,12 +221,11 @@ export class InlineChatQuickVoice implements IEditorContribution { case SpeechToTextStatus.Stopped: break; case SpeechToTextStatus.Recognizing: - // TODO@jrieken special rendering for "in-flight" message? - this._widget.updateInput(!message ? e.text : `${message} ${e.text}`); + this._widget.updateInput(!message ? e.text : `${message} ${e.text}`, false); break; case SpeechToTextStatus.Recognized: message = !message ? e.text : `${message} ${e.text}`; - this._widget.updateInput(message); + this._widget.updateInput(message, true); break; } }); From 1a386828ac254074af29ebe968bd690a90ad46c4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 18 Jan 2024 15:59:10 +0100 Subject: [PATCH 0421/1897] voice - support any focused code editor (including notebooks) (#202733) --- .../browser/contrib/chatInputEditorContrib.ts | 4 ++-- .../electron-sandbox/actions/voiceChatActions.ts | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index ccfa0c7a23d..c83c4e211d8 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -662,8 +662,8 @@ class ChatTokenDeleter extends Disposable { const change = e.changes[0]; // If this was a simple delete, try to find out whether it was inside a token - if (!change.text) { - parser.parseChatRequest(this.widget.viewModel!.sessionId, previousInputValue).then(previousParsedValue => { + if (!change.text && this.widget.viewModel) { + parser.parseChatRequest(this.widget.viewModel.sessionId, previousInputValue).then(previousParsedValue => { // For dynamic variables, this has to happen in ChatDynamicVariableModel with the other bookkeeping const deletableTokens = previousParsedValue.parts.filter(p => p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart || p instanceof ChatRequestSlashCommandPart || p instanceof ChatRequestVariablePart); deletableTokens.forEach(token => { diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 11dcef4882a..42a62788704 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -21,8 +21,6 @@ import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, MENU_INLINE_CHAT_INPUT } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; @@ -46,6 +44,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); @@ -84,7 +83,7 @@ class VoiceChatSessionControllerFactory { const chatService = accessor.get(IChatService); const viewsService = accessor.get(IViewsService); const chatContributionService = accessor.get(IChatContributionService); - const editorService = accessor.get(IEditorService); + const codeEditorService = accessor.get(ICodeEditorService); const quickChatService = accessor.get(IQuickChatService); const layoutService = accessor.get(IWorkbenchLayoutService); @@ -114,7 +113,7 @@ class VoiceChatSessionControllerFactory { } // Try with the inline chat - const activeCodeEditor = getCodeEditor(editorService.activeTextEditorControl); + const activeCodeEditor = codeEditorService.getFocusedCodeEditor(); if (activeCodeEditor) { const inlineChat = InlineChatController.get(activeCodeEditor); if (inlineChat?.hasFocus()) { @@ -136,7 +135,7 @@ class VoiceChatSessionControllerFactory { // Inline Chat if (context === 'inline') { - const activeCodeEditor = getCodeEditor(editorService.activeTextEditorControl); + const activeCodeEditor = codeEditorService.getFocusedCodeEditor(); if (activeCodeEditor) { const inlineChat = InlineChatController.get(activeCodeEditor); if (inlineChat) { @@ -772,7 +771,8 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe @IConfigurationService private readonly configurationService: IConfigurationService, @ICommandService private readonly commandService: ICommandService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @IInstantiationService instantiationService: IInstantiationService + @IInstantiationService instantiationService: IInstantiationService, + @ICodeEditorService private readonly codeEditorService: ICodeEditorService ) { super(); @@ -879,7 +879,9 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe case KeywordActivationContribution.SETTINGS_VALUE.QUICK_CHAT: return QuickVoiceChatAction.ID; case KeywordActivationContribution.SETTINGS_VALUE.CHAT_IN_CONTEXT: - return StartVoiceChatAction.ID; + if (this.codeEditorService.getFocusedCodeEditor()) { + return InlineVoiceChatAction.ID; + } default: return VoiceChatInChatViewAction.ID; } From 2cec7a8d8872ea6a614de756b5f0ba3f5922b1df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 18 Jan 2024 16:07:13 +0100 Subject: [PATCH 0422/1897] fix: make sure showKeybindings stills works in screencast mode (#202735) fixes #201689 --- src/vs/workbench/browser/actions/developerActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index 7b40f8df1cf..61d008d6404 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -350,7 +350,7 @@ class ToggleScreencastModeAction extends Action2 { append(keyboardMarker, $('span.title', {}, `${commandAndGroupLabel} `)); } - if ((options.showKeys ?? true) || (commandDetails && (options.showKeybindings ?? true))) { + if ((options.showKeys ?? true) || ((options.showKeybindings ?? true) && this._isKbFound(shortcut))) { // Fix label for arrow keys keyLabel = keyLabel?.replace('UpArrow', '↑') ?.replace('DownArrow', '↓') From 98c253817043f784e7ffbe05f098b5f0acf43a45 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 18 Jan 2024 16:09:38 +0100 Subject: [PATCH 0423/1897] voice - tweak status bar entry (#202736) --- .../chat/electron-sandbox/actions/voiceChatActions.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 42a62788704..03f10c273ce 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -905,8 +905,8 @@ class KeywordActivationStatusEntry extends Disposable { private static STATUS_NAME = localize('keywordActivation.status.name', "Voice Keyword Activation"); private static STATUS_COMMAND = 'keywordActivation.status.command'; - private static STATUS_ACTIVE = localize('keywordActivation.status.active', "Voice Keyword Activation: Active"); - private static STATUS_INACTIVE = localize('keywordActivation.status.inactive', "Voice Keyword Activation: Inactive"); + private static STATUS_ACTIVE = localize('keywordActivation.status.active', "Listening to 'Hey Code'..."); + private static STATUS_INACTIVE = localize('keywordActivation.status.inactive', "Waiting for voice chat to end..."); constructor( @ISpeechService private readonly speechService: ISpeechService, @@ -952,11 +952,11 @@ class KeywordActivationStatusEntry extends Disposable { private getStatusEntryProperties(): IStatusbarEntry { return { name: KeywordActivationStatusEntry.STATUS_NAME, - text: '$(mic)', + text: this.speechService.hasActiveKeywordRecognition ? '$(mic-filled)' : '$(mic)', tooltip: this.speechService.hasActiveKeywordRecognition ? KeywordActivationStatusEntry.STATUS_ACTIVE : KeywordActivationStatusEntry.STATUS_INACTIVE, ariaLabel: this.speechService.hasActiveKeywordRecognition ? KeywordActivationStatusEntry.STATUS_ACTIVE : KeywordActivationStatusEntry.STATUS_INACTIVE, command: KeywordActivationStatusEntry.STATUS_COMMAND, - kind: this.speechService.hasActiveKeywordRecognition ? 'prominent' : 'standard' + kind: 'prominent' }; } From ad240ae2d26bf560406c63ac31b8186f53870867 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 18 Jan 2024 16:20:11 +0100 Subject: [PATCH 0424/1897] :lipstick: --- src/vs/workbench/browser/parts/titlebar/titlebarActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index e2d88e50c31..db34e4f1d46 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -21,7 +21,7 @@ class ToggleConfigAction extends Action2 { constructor(private readonly section: string, title: string, order: number, mainWindowOnly: boolean, showInCustomTitleBarWhenNativeTitle: boolean) { let when = mainWindowOnly ? IsAuxiliaryWindowFocusedContext.toNegated() : ContextKeyExpr.true(); - when = showInCustomTitleBarWhenNativeTitle ? when : ContextKeyExpr.and(when, ContextKeyExpr.equals(TitleBarSetting.TITLE_BAR_STYLE, TitlebarStyle.NATIVE).negate())!; + when = showInCustomTitleBarWhenNativeTitle ? when : ContextKeyExpr.and(when, ContextKeyExpr.equals(`config.${TitleBarSetting.TITLE_BAR_STYLE}`, TitlebarStyle.NATIVE).negate())!; super({ id: `toggle.${section}`, From a0628b674edf6186ef7012747204521c62713c7a Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 18 Jan 2024 16:24:14 +0100 Subject: [PATCH 0425/1897] :lipstick: --- src/vs/workbench/browser/parts/titlebar/titlebarActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index db34e4f1d46..f336a9f25fa 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -71,7 +71,7 @@ registerAction2(class ToggleCustomTitleBar extends Action2 { id: `toggle.${ToggleCustomTitleBar.settingsID}`, title: localize('toggle.hideCustomTitleBar', 'Hide Custom Title Bar'), menu: [ - { id: MenuId.TitleBarContext, order: 0, when: ContextKeyExpr.equals(TitleBarSetting.TITLE_BAR_STYLE, TitlebarStyle.NATIVE), group: '3_toggle' }, + { id: MenuId.TitleBarContext, order: 0, when: ContextKeyExpr.equals(`config.${TitleBarSetting.TITLE_BAR_STYLE}`, TitlebarStyle.NATIVE), group: '3_toggle' }, ] }); } From 2df8c8428b9981fe3704e0e1cf945e725c692d4a Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 18 Jan 2024 16:27:02 +0100 Subject: [PATCH 0426/1897] :lipstick: --- src/vs/workbench/browser/parts/titlebar/titlebarActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index f336a9f25fa..ef0c27873c3 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -64,7 +64,7 @@ registerAction2(class ToggleLayoutControl extends ToggleConfigAction { }); registerAction2(class ToggleCustomTitleBar extends Action2 { - static readonly settingsID = TitleBarSetting.TITLE_BAR_STYLE; + static readonly settingsID = TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY; constructor() { super({ From 18b601beed5c87c2a6d54664d25ca144c51e2c5b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 18 Jan 2024 16:27:57 +0100 Subject: [PATCH 0427/1897] voice - trigger keyword only when window has focus (#202737) --- .../chat/electron-sandbox/actions/voiceChatActions.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 03f10c273ce..f18e22447b9 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -45,6 +45,7 @@ import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/co import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); @@ -772,7 +773,8 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe @ICommandService private readonly commandService: ICommandService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IInstantiationService instantiationService: IInstantiationService, - @ICodeEditorService private readonly codeEditorService: ICodeEditorService + @ICodeEditorService private readonly codeEditorService: ICodeEditorService, + @IHostService private readonly hostService: IHostService ) { super(); @@ -867,7 +869,11 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe this.activeSession = undefined; if (result === KeywordRecognitionStatus.Recognized) { - this.commandService.executeCommand(this.getKeywordCommand()); + if (this.hostService.hasFocus) { + this.commandService.executeCommand(this.getKeywordCommand()); + } else { + this.handleKeywordActivation(); + } } } From a7f0c4953a1c869ca93dcbd4e22ff4d0b38b4359 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 18 Jan 2024 16:34:25 +0100 Subject: [PATCH 0428/1897] :lipstick: --- .../browser/parts/titlebar/titlebarActions.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index ef0c27873c3..25df6adaaf3 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -64,40 +64,38 @@ registerAction2(class ToggleLayoutControl extends ToggleConfigAction { }); registerAction2(class ToggleCustomTitleBar extends Action2 { - static readonly settingsID = TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY; - constructor() { super({ - id: `toggle.${ToggleCustomTitleBar.settingsID}`, + id: `toggle.${TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY}`, title: localize('toggle.hideCustomTitleBar', 'Hide Custom Title Bar'), menu: [ { id: MenuId.TitleBarContext, order: 0, when: ContextKeyExpr.equals(`config.${TitleBarSetting.TITLE_BAR_STYLE}`, TitlebarStyle.NATIVE), group: '3_toggle' }, + { id: MenuId.TitleBarTitleContext, order: 0, when: ContextKeyExpr.equals(`config.${TitleBarSetting.TITLE_BAR_STYLE}`, TitlebarStyle.NATIVE), group: '3_toggle' }, ] }); } run(accessor: ServicesAccessor, ...args: any[]): void { const configService = accessor.get(IConfigurationService); - configService.updateValue(ToggleCustomTitleBar.settingsID, CustomTitleBarVisibility.NEVER); + configService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.NEVER); } }); registerAction2(class ToggleCustomTitleBarWindowed extends Action2 { - static readonly settingsID = TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY; - constructor() { super({ - id: `toggle.${ToggleCustomTitleBarWindowed.settingsID}.windowed`, + id: `toggle.${TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY}.windowed`, title: localize('toggle.hideCustomTitleBarInFullScreen', 'Hide Custom Title Bar In Full Screen'), menu: [ { id: MenuId.TitleBarContext, order: 0, when: IsMainWindowFullscreenContext, group: '3_toggle' }, + { id: MenuId.TitleBarTitleContext, order: 0, when: IsMainWindowFullscreenContext, group: '3_toggle' }, ] }); } run(accessor: ServicesAccessor, ...args: any[]): void { const configService = accessor.get(IConfigurationService); - configService.updateValue(ToggleCustomTitleBarWindowed.settingsID, CustomTitleBarVisibility.WINDOWED); + configService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.WINDOWED); } }); From 37740ecbb044e581d328392b42677fba4fe39092 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 18 Jan 2024 16:52:56 +0100 Subject: [PATCH 0429/1897] add hold for speech command to inline chat which is for when a session already started (#202741) * - cancel quick voice when loosing focus * add hold for speech command to inline chat which is for when a session already started --- .../inlineChat.contribution.ts | 5 +- .../electron-sandbox/inlineChatActions.ts | 74 +++++++++++++------ .../electron-sandbox/inlineChatQuickVoice.ts | 57 +++++++++----- 3 files changed, 93 insertions(+), 43 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts index d5233317f55..4a543a8ea54 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts @@ -6,11 +6,12 @@ import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { CancelAction, InlineChatQuickVoice, StartAction, StopAction } from 'vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice'; -import * as StartSessionAction from './inlineChatActions'; +import { StartSessionAction, HoldToSpeak } from './inlineChatActions'; // start and hold for voice -registerAction2(StartSessionAction.StartSessionAction); +registerAction2(StartSessionAction); +registerAction2(HoldToSpeak); // quick voice diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts index f3becd537a9..a6b3bd9a496 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts @@ -16,9 +16,10 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { StartVoiceChatAction, StopListeningAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; -import { CTX_INLINE_CHAT_HAS_PROVIDER, InlineChatConfigKeys } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_VISIBLE, InlineChatConfigKeys } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; +import { HasSpeechProvider, ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; +import { localize2 } from 'vs/nls'; export class StartSessionAction extends EditorAction2 { @@ -45,31 +46,11 @@ export class StartSessionAction extends EditorAction2 { const configService = accessor.get(IConfigurationService); const speechService = accessor.get(ISpeechService); - const keybindingService = accessor.get(IKeybindingService); - const commandService = accessor.get(ICommandService); if (configService.getValue(InlineChatConfigKeys.HoldToSpeech) // enabled && speechService.hasSpeechProvider // possible ) { - - const holdMode = keybindingService.enableKeybindingHoldMode(this.desc.id); - if (holdMode) { // holding keys - let listening = false; - const handle = disposableTimeout(() => { - // start VOICE input - commandService.executeCommand(StartVoiceChatAction.ID); - listening = true; - }, 250); - - holdMode.finally(() => { - if (listening) { - commandService.executeCommand(StopListeningAction.ID).finally(() => { - InlineChatController.get(editor)?.acceptInput(); - }); - } - handle.dispose(); - }); - } + holdForSpeech(accessor, InlineChatController.get(editor), this.desc.id); } let options: InlineChatRunOptions | undefined; @@ -80,3 +61,50 @@ export class StartSessionAction extends EditorAction2 { InlineChatController.get(editor)?.run({ ...options }); } } + +export class HoldToSpeak extends AbstractInlineChatAction { + + constructor() { + super({ + id: 'inlineChat.holdForSpeech', + precondition: ContextKeyExpr.and(HasSpeechProvider, CTX_INLINE_CHAT_VISIBLE), + title: localize2('holdForSpeech', "Hold for Speech"), + keybinding: { + when: EditorContextKeys.textInputFocus, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.KeyI, + }, + }); + } + + override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, editor: ICodeEditor, ...args: any[]): void { + holdForSpeech(accessor, ctrl, this.desc.id); + } +} + +function holdForSpeech(accessor: ServicesAccessor, ctrl: InlineChatController | null, commandId: string): void { + const keybindingService = accessor.get(IKeybindingService); + const commandService = accessor.get(ICommandService); + if (!ctrl) { + return; + } + const holdMode = keybindingService.enableKeybindingHoldMode(commandId); + if (!holdMode) { + return; + } + let listening = false; + const handle = disposableTimeout(() => { + // start VOICE input + commandService.executeCommand(StartVoiceChatAction.ID); + listening = true; + }, 250); + + holdMode.finally(() => { + if (listening) { + commandService.executeCommand(StopListeningAction.ID).finally(() => { + ctrl!.acceptInput(); + }); + } + handle.dispose(); + }); +} diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index 4713733f0f3..8c7f9e05b67 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -17,11 +17,13 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { getWindow, h, reset, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import * as dom from 'vs/base/browser/dom'; import { IDimension } from 'vs/editor/common/core/dimension'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { AbstractInlineChatAction } from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore } from 'vs/base/common/lifecycle'; const CTX_QUICK_CHAT_IN_PROGRESS = new RawContextKey('inlineChat.quickChatInProgress', false); @@ -107,17 +109,27 @@ class QuickVoiceWidget implements IContentWidget { readonly suppressMouseDown = true; private readonly _domNode = document.createElement('div'); - private readonly _elements = h('.inline-chat-quick-voice@main', [ - h('span@mic'), - h('span.message@message'), + private readonly _elements = dom.h('.inline-chat-quick-voice@main', [ + dom.h('span@mic'), + dom.h('span.message@message'), ]); + private _focusTracker: dom.IFocusTracker | undefined; + + private readonly _onDidBlur = new Emitter(); + readonly onDidBlur: Event = this._onDidBlur.event; + constructor(private readonly _editor: ICodeEditor) { this._domNode.appendChild(this._elements.root); this._domNode.style.zIndex = '1000'; this._domNode.tabIndex = -1; this._domNode.style.outline = 'none'; - reset(this._elements.mic, renderIcon(Codicon.micFilled)); + dom.reset(this._elements.mic, renderIcon(Codicon.micFilled)); + } + + dispose(): void { + this._focusTracker?.dispose(); + this._onDidBlur.dispose(); } getId(): string { @@ -150,6 +162,13 @@ class QuickVoiceWidget implements IContentWidget { return null; } + afterRender(): void { + this._domNode.focus(); + this._focusTracker?.dispose(); + this._focusTracker = dom.trackFocus(this._domNode); + this._focusTracker.onDidBlur(() => this._onDidBlur.fire()); + } + // --- updateInput(input: string | undefined, isDefinite: boolean): void { @@ -157,17 +176,19 @@ class QuickVoiceWidget implements IContentWidget { this._elements.message.textContent = input ?? ''; } - focus(): void { - this._domNode.focus(); + show() { + this._editor.addContentWidget(this); } active(): void { this._elements.main.classList.add('recording'); } - reset(): void { + hide() { this._elements.main.classList.remove('recording'); this.updateInput(undefined, true); + this._editor.removeContentWidget(this); + this._focusTracker?.dispose(); } } @@ -179,6 +200,7 @@ export class InlineChatQuickVoice implements IEditorContribution { return editor.getContribution(InlineChatQuickVoice.ID); } + private readonly _store = new DisposableStore(); private readonly _ctxQuickChatInProgress: IContextKey; private readonly _widget: QuickVoiceWidget; private _finishCallback?: (abort: boolean) => void; @@ -188,23 +210,24 @@ export class InlineChatQuickVoice implements IEditorContribution { @ISpeechService private readonly _speechService: ISpeechService, @IContextKeyService contextKeyService: IContextKeyService, ) { - this._widget = new QuickVoiceWidget(this._editor); + this._widget = this._store.add(new QuickVoiceWidget(this._editor)); + this._widget.onDidBlur(() => this._finishCallback?.(true), undefined, this._store); this._ctxQuickChatInProgress = CTX_QUICK_CHAT_IN_PROGRESS.bindTo(contextKeyService); } dispose(): void { this._finishCallback?.(true); + this._ctxQuickChatInProgress.reset(); + this._store.dispose(); } start() { - const cts = new CancellationTokenSource(); - this._editor.addContentWidget(this._widget); - this._ctxQuickChatInProgress.set(true); + this._finishCallback?.(true); - runAtThisOrScheduleAtNextAnimationFrame(getWindow(this._widget.getDomNode()), () => { - this._widget.focus(); // requires RAF because... - }); + const cts = new CancellationTokenSource(); + this._widget.show(); + this._ctxQuickChatInProgress.set(true); let message: string | undefined; const session = this._speechService.createSpeechToTextSession(cts.token); @@ -233,8 +256,7 @@ export class InlineChatQuickVoice implements IEditorContribution { const done = (abort: boolean) => { cts.dispose(true); listener.dispose(); - this._widget.reset(); - this._editor.removeContentWidget(this._widget); + this._widget.hide(); this._ctxQuickChatInProgress.reset(); if (!abort && message) { @@ -252,5 +274,4 @@ export class InlineChatQuickVoice implements IEditorContribution { cancel(): void { this._finishCallback?.(true); } - } From 4057a58c76b286a40b9b30c16cb4ad2d539bc4f0 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 18 Jan 2024 17:10:33 +0100 Subject: [PATCH 0430/1897] don't count changes but make sure error'd edits don't make it into buffer (#202745) fixes https://github.com/microsoft/vscode/issues/202702 --- .../test/browser/inlineChatController.test.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index d2b9fe55c34..1648debcf8c 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -382,6 +382,8 @@ suite('InteractiveChatController', function () { assert.strictEqual(editor.getModel().getValue(), valueThen); }); + + test('UI is streaming edits minutes after the response is finished #3345', async function () { configurationService.setUserConfiguration(InlineChatConfigKeys.Mode, EditMode.Live); @@ -411,8 +413,8 @@ suite('InteractiveChatController', function () { }); - let modelChangeCounter = 0; - store.add(editor.getModel().onDidChangeContent(() => { modelChangeCounter++; })); + // let modelChangeCounter = 0; + // store.add(editor.getModel().onDidChangeContent(() => { modelChangeCounter++; })); store.add(d); ctrl = instaService.createInstance(TestController, editor); @@ -420,17 +422,18 @@ suite('InteractiveChatController', function () { const r = ctrl.run({ message: 'Hello', autoSend: true }); await p; - assert.ok(modelChangeCounter > 0); // some changes have been made - - const modelChangeCounterNow = modelChangeCounter; + // assert.ok(modelChangeCounter > 0, modelChangeCounter.toString()); // some changes have been made + // const modelChangeCounterNow = modelChangeCounter; + assert.ok(!editor.getModel().getValue().includes('DONE')); await timeout(10); - assert.strictEqual(modelChangeCounterNow, modelChangeCounter); + // assert.strictEqual(modelChangeCounterNow, modelChangeCounter); assert.ok(!editor.getModel().getValue().includes('DONE')); await ctrl.cancelSession(); await r; }); }); + }); From 1404ba5d83abb0e84fba1e00b4ae217da1e4fab0 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 18 Jan 2024 17:24:17 +0100 Subject: [PATCH 0431/1897] voice - keep keyword activation alive after keyword is recognized (#202746) --- .../chat/electron-sandbox/actions/voiceChatActions.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index f18e22447b9..17e6514a1b4 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -871,9 +871,13 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe if (result === KeywordRecognitionStatus.Recognized) { if (this.hostService.hasFocus) { this.commandService.executeCommand(this.getKeywordCommand()); - } else { - this.handleKeywordActivation(); } + + // Immediately start another keyboard activation session + // because we cannot assume that the command we execute + // will trigger a speech recognition session. + + this.handleKeywordActivation(); } } From c42c4ab61f72192819b40032ee1a5636090b769f Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 18 Jan 2024 17:31:44 +0100 Subject: [PATCH 0432/1897] :lipstick: --- src/vs/workbench/browser/parts/titlebar/titlebarActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index 25df6adaaf3..eff2013a056 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -87,8 +87,8 @@ registerAction2(class ToggleCustomTitleBarWindowed extends Action2 { id: `toggle.${TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY}.windowed`, title: localize('toggle.hideCustomTitleBarInFullScreen', 'Hide Custom Title Bar In Full Screen'), menu: [ - { id: MenuId.TitleBarContext, order: 0, when: IsMainWindowFullscreenContext, group: '3_toggle' }, - { id: MenuId.TitleBarTitleContext, order: 0, when: IsMainWindowFullscreenContext, group: '3_toggle' }, + { id: MenuId.TitleBarContext, order: 1, when: IsMainWindowFullscreenContext, group: '3_toggle' }, + { id: MenuId.TitleBarTitleContext, order: 1, when: IsMainWindowFullscreenContext, group: '3_toggle' }, ] }); } From 504aeb7da3cecbb81c927f6ed9cc23a30c0c5798 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 18 Jan 2024 17:35:34 +0100 Subject: [PATCH 0433/1897] :lipstick: --- src/vs/workbench/browser/parts/titlebar/titlebarActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index eff2013a056..c66ddf2cffb 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -69,8 +69,8 @@ registerAction2(class ToggleCustomTitleBar extends Action2 { id: `toggle.${TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY}`, title: localize('toggle.hideCustomTitleBar', 'Hide Custom Title Bar'), menu: [ - { id: MenuId.TitleBarContext, order: 0, when: ContextKeyExpr.equals(`config.${TitleBarSetting.TITLE_BAR_STYLE}`, TitlebarStyle.NATIVE), group: '3_toggle' }, - { id: MenuId.TitleBarTitleContext, order: 0, when: ContextKeyExpr.equals(`config.${TitleBarSetting.TITLE_BAR_STYLE}`, TitlebarStyle.NATIVE), group: '3_toggle' }, + { id: MenuId.TitleBarContext, order: 0, when: ContextKeyExpr.equals(`config.window.titleBarStyle`, TitlebarStyle.NATIVE), group: '3_toggle' }, + { id: MenuId.TitleBarTitleContext, order: 0, when: ContextKeyExpr.equals(`config.window.titleBarStyle`, TitlebarStyle.NATIVE), group: '3_toggle' }, ] }); } From 4bc23ab5d23ec4e312f82caa1e1105ea67e46927 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 18 Jan 2024 17:40:55 +0100 Subject: [PATCH 0434/1897] :lipstick: --- src/vs/workbench/browser/parts/titlebar/titlebarActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index c66ddf2cffb..c010eac936a 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -69,8 +69,8 @@ registerAction2(class ToggleCustomTitleBar extends Action2 { id: `toggle.${TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY}`, title: localize('toggle.hideCustomTitleBar', 'Hide Custom Title Bar'), menu: [ - { id: MenuId.TitleBarContext, order: 0, when: ContextKeyExpr.equals(`config.window.titleBarStyle`, TitlebarStyle.NATIVE), group: '3_toggle' }, - { id: MenuId.TitleBarTitleContext, order: 0, when: ContextKeyExpr.equals(`config.window.titleBarStyle`, TitlebarStyle.NATIVE), group: '3_toggle' }, + { id: MenuId.TitleBarContext, order: 0, when: ContextKeyExpr.or(ContextKeyExpr.equals(`config.${TitleBarSetting.TITLE_BAR_STYLE}`, TitlebarStyle.NATIVE), ContextKeyExpr.equals(`config.window.nativeTabs`, ContextKeyExpr.true())), group: '3_toggle' }, + { id: MenuId.TitleBarTitleContext, order: 0, when: ContextKeyExpr.or(ContextKeyExpr.equals(`config.${TitleBarSetting.TITLE_BAR_STYLE}`, TitlebarStyle.NATIVE), ContextKeyExpr.equals(`config.window.nativeTabs`, ContextKeyExpr.true())), group: '3_toggle' }, ] }); } From 373ed50b0db434ec860be6713a2c6a4bfcaf49f2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 18 Jan 2024 17:47:01 +0100 Subject: [PATCH 0435/1897] voice - do not treat embedded editor as text editor (#202748) --- .../actions/voiceChatActions.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 17e6514a1b4..5874c2c3713 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -46,6 +46,9 @@ import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarA import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { ICodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); @@ -73,6 +76,15 @@ interface IVoiceChatSessionController { clearInputPlaceholder(): void; } +function getFocusedCodeEditor(editorService: IEditorService, codeEditorService: ICodeEditorService): ICodeEditor | null { + const codeEditor = getCodeEditor(codeEditorService.getFocusedCodeEditor()); + if (codeEditor && !(codeEditor instanceof EmbeddedCodeEditorWidget)) { + return codeEditor; + } + + return getCodeEditor(editorService.activeTextEditorControl); +} + class VoiceChatSessionControllerFactory { static create(accessor: ServicesAccessor, context: 'inline'): Promise; @@ -87,6 +99,7 @@ class VoiceChatSessionControllerFactory { const codeEditorService = accessor.get(ICodeEditorService); const quickChatService = accessor.get(IQuickChatService); const layoutService = accessor.get(IWorkbenchLayoutService); + const editorService = accessor.get(IEditorService); // Currently Focused Context if (context === 'focused') { @@ -114,7 +127,7 @@ class VoiceChatSessionControllerFactory { } // Try with the inline chat - const activeCodeEditor = codeEditorService.getFocusedCodeEditor(); + const activeCodeEditor = getFocusedCodeEditor(editorService, codeEditorService); if (activeCodeEditor) { const inlineChat = InlineChatController.get(activeCodeEditor); if (inlineChat?.hasFocus()) { @@ -136,7 +149,7 @@ class VoiceChatSessionControllerFactory { // Inline Chat if (context === 'inline') { - const activeCodeEditor = codeEditorService.getFocusedCodeEditor(); + const activeCodeEditor = getFocusedCodeEditor(editorService, codeEditorService); if (activeCodeEditor) { const inlineChat = InlineChatController.get(activeCodeEditor); if (inlineChat) { @@ -774,6 +787,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IInstantiationService instantiationService: IInstantiationService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, + @IEditorService private readonly editorService: IEditorService, @IHostService private readonly hostService: IHostService ) { super(); @@ -889,7 +903,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe case KeywordActivationContribution.SETTINGS_VALUE.QUICK_CHAT: return QuickVoiceChatAction.ID; case KeywordActivationContribution.SETTINGS_VALUE.CHAT_IN_CONTEXT: - if (this.codeEditorService.getFocusedCodeEditor()) { + if (getFocusedCodeEditor(this.editorService, this.codeEditorService)) { return InlineVoiceChatAction.ID; } default: From b50d5f44ea78f84b11d7d7d4cc3f9f37e90d1ebf Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 18 Jan 2024 17:48:54 +0100 Subject: [PATCH 0436/1897] enable experimentation for `inlineChat.mode` (#202750) --- src/vs/workbench/contrib/inlineChat/common/inlineChat.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 64370f4950c..f7becc10ec4 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -217,7 +217,8 @@ Registry.as(Extensions.Configuration).registerConfigurat localize('mode.livePreview', "Changes are applied directly to the document and are highlighted visually via inline or side-by-side diffs. Ending a session will keep the changes."), localize('mode.preview', "Changes are previewed only and need to be accepted via the apply button. Ending a session will discard the changes."), localize('mode.live', "Changes are applied directly to the document, can be highlighted via inline diffs, and accepted/discarded by hunks. Ending a session will keep the changes."), - ] + ], + tags: ['experimental'] }, [InlineChatConfigKeys.FinishOnType]: { description: localize('finishOnType', "Whether to finish an inline chat session when typing outside of changed regions."), From 3473bac9638fc3fc5fa51209a7086ea0a3412597 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 18 Jan 2024 18:10:50 +0100 Subject: [PATCH 0437/1897] :lipstick: --- src/vs/platform/window/common/window.ts | 5 +++-- src/vs/platform/windows/electron-main/windows.ts | 4 ++-- src/vs/workbench/browser/layout.ts | 5 +++-- src/vs/workbench/browser/parts/titlebar/titlebarPart.ts | 1 + 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index 14f3914639c..2b991c2b203 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -127,10 +127,10 @@ export function isFileToOpen(uriToOpen: IWindowOpenable): uriToOpen is IFileToOp export type MenuBarVisibility = 'classic' | 'visible' | 'toggle' | 'hidden' | 'compact'; export function getMenuBarVisibility(configurationService: IConfigurationService): MenuBarVisibility { - const titleBarStyle = getTitleBarStyle(configurationService); + const nativeTitleBarEnabled = hasNativeTitlebar(configurationService); const menuBarVisibility = configurationService.getValue('window.menuBarVisibility'); - if (menuBarVisibility === 'default' || (titleBarStyle !== TitlebarStyle.CUSTOM && menuBarVisibility === 'compact') || (isMacintosh && isNative)) { + if (menuBarVisibility === 'default' || (nativeTitleBarEnabled && menuBarVisibility === 'compact') || (isMacintosh && isNative)) { return 'classic'; } else { return menuBarVisibility; @@ -182,6 +182,7 @@ export const enum CustomTitleBarVisibility { } export function hasCustomTitlebar(configurationService: IConfigurationService, titleBarStyle?: TitlebarStyle): boolean { + // Returns if it possible to have a custom title bar in the curren session // Does not imply that the title bar is visible return true; diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 7cf53969e95..5985e2f9691 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -173,8 +173,8 @@ export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowSt options.tabbingIdentifier = productService.nameShort; // this opts in to sierra tabs } - const useCustomTitleStyle = !hasNativeTitlebar(configurationService); - if (useCustomTitleStyle) { + const hideNativeTitleBar = !hasNativeTitlebar(configurationService); + if (hideNativeTitleBar) { options.titleBarStyle = 'hidden'; if (!isMacintosh) { options.frame = false; diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index f40144d8cc6..6eb24dbdeca 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -370,7 +370,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._register(addDisposableListener(this.mainContainer, EventType.SCROLL, () => this.mainContainer.scrollTop = 0)); // Menubar visibility changes - if ((isWindows || isLinux || isWeb) && !hasNativeTitlebar(this.configurationService)) { + const showingCustomMenu = (isWindows || isLinux || isWeb) && !hasNativeTitlebar(this.configurationService); + if (showingCustomMenu) { this._register(this.titleService.onMenubarVisibilityChange(visible => this.onMenubarToggled(visible))); } @@ -1240,7 +1241,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return true; } - // Hide custom title bar when native title bar and custom title bar is empty + // Hide custom title bar when native title bar enabled and custom title bar is empty if (nativeTitleBarEnabled) { return false; } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 2f90f198a36..13549aec7d3 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -487,6 +487,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { // Text Title if (!this.isCommandCenterVisible) { + // Don't show title in custom titlebar when native title is shown if (!hasNativeTitlebar(this.configurationService, this.titleBarStyle)) { this.title.innerText = this.windowTitle.value; From 6c462f37de22e7be4b47963a6596e08629b92827 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 18 Jan 2024 10:35:46 -0800 Subject: [PATCH 0438/1897] speech -> voice --- ...minalSpeechToText.css => terminalVoice.css} | 8 ++++---- .../terminal/browser/terminal.contribution.ts | 2 +- .../terminal/browser/terminalActions.ts | 18 +++++++++--------- ...erminalSpeechToText.ts => terminalVoice.ts} | 16 ++++++++-------- .../contrib/terminal/common/terminal.ts | 4 ++-- 5 files changed, 24 insertions(+), 24 deletions(-) rename src/vs/workbench/contrib/terminal/browser/media/{terminalSpeechToText.css => terminalVoice.css} (86%) rename src/vs/workbench/contrib/terminal/browser/{terminalSpeechToText.ts => terminalVoice.ts} (93%) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminalSpeechToText.css b/src/vs/workbench/contrib/terminal/browser/media/terminalVoice.css similarity index 86% rename from src/vs/workbench/contrib/terminal/browser/media/terminalSpeechToText.css rename to src/vs/workbench/contrib/terminal/browser/media/terminalVoice.css index e227a4c3321..fda61dd435a 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminalSpeechToText.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminalVoice.css @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.terminal-speech-to-text { +.terminal-voice { background-color: var(--vscode-terminal-background, var(--vscode-panel-background)); padding: 2px; border-radius: 8px; @@ -13,14 +13,14 @@ z-index: 1000; } -.terminal-speech-to-text.codicon.codicon-mic-filled { +.terminal-voice.codicon.codicon-mic-filled { display: flex; align-items: center; width: 16px; height: 16px; } -.terminal-speech-to-text.recording.codicon.codicon-mic-filled { +.terminal-voice.recording.codicon.codicon-mic-filled { color: var(--vscode-activityBarBadge-background); animation: ani-terminal-speech 1s infinite; } @@ -39,7 +39,7 @@ } } -.terminal-speech-progress-text { +.terminal-voice-progress-text { font-style: italic; color: var(--vscode-editorGhostText-foreground) !important; border: 1px solid var(--vscode-editorGhostText-border); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 87443272043..fba9c78e785 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -8,7 +8,7 @@ import 'vs/css!./media/scrollbar'; import 'vs/css!./media/widgets'; import 'vs/css!./media/xterm'; import 'vs/css!./media/terminal'; -import 'vs/css!./media/terminalSpeechToText'; +import 'vs/css!./media/terminalVoice'; import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 8be2aa0dce6..05e73b607ce 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -64,7 +64,7 @@ import { AccessibleViewProviderId, accessibleViewCurrentProviderId, accessibleVi import { isKeyboardEvent, isMouseEvent, isPointerEvent } from 'vs/base/browser/dom'; import { editorGroupToColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { InstanceContext } from 'vs/workbench/contrib/terminal/browser/terminalContextMenu'; -import { TerminalSpeechToTextSession } from 'vs/workbench/contrib/terminal/browser/terminalSpeechToText'; +import { TerminalVoiceSession } from 'vs/workbench/contrib/terminal/browser/terminalVoice'; import { HasSpeechProvider } from 'vs/workbench/contrib/speech/common/speechService'; export const switchTerminalActionViewItemSeparator = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'; @@ -1645,30 +1645,30 @@ export function registerTerminalActions() { }); registerActiveInstanceAction({ - id: TerminalCommandId.StartSpeechToText, + id: TerminalCommandId.StartVoice, title: { - value: localize('workbench.action.startTerminalSpeechToText', "Start Terminal Speech To Text"), - original: 'Start Terminal Speech To Text' + value: localize('workbench.action.startTerminalVoice', "Start Terminal Voice"), + original: 'Start Terminal Voice' }, precondition: ContextKeyExpr.and(HasSpeechProvider, sharedWhenClause.terminalAvailable), f1: true, run: (activeInstance, c, accessor) => { const instantiationService = accessor.get(IInstantiationService); - TerminalSpeechToTextSession.getInstance(instantiationService).start(); + TerminalVoiceSession.getInstance(instantiationService).start(); } }); registerActiveInstanceAction({ - id: TerminalCommandId.StopSpeechToText, + id: TerminalCommandId.StopVoice, title: { - value: localize('workbench.action.stopTerminalSpeechToText', "Stop Terminal Speech To Text"), - original: 'Stop Terminal Speech To Text' + value: localize('workbench.action.stopTerminalVoice', "Stop Terminal Voice"), + original: 'Stop Terminal Voice' }, precondition: ContextKeyExpr.and(HasSpeechProvider, sharedWhenClause.terminalAvailable), f1: true, run: (activeInstance, c, accessor) => { const instantiationService = accessor.get(IInstantiationService); - TerminalSpeechToTextSession.getInstance(instantiationService).stop(true); + TerminalVoiceSession.getInstance(instantiationService).stop(true); } }); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts b/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts similarity index 93% rename from src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts rename to src/vs/workbench/contrib/terminal/browser/terminalVoice.ts index ec8c71ee363..e2db1158ed4 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalSpeechToText.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts @@ -46,20 +46,20 @@ const symbolMap: { [key: string]: string } = { 'Double quote': '"', }; -export class TerminalSpeechToTextSession extends Disposable { +export class TerminalVoiceSession extends Disposable { private _input: string = ''; private _ghostText: IDecoration | undefined; private _decoration: IDecoration | undefined; private _marker: IXtermMarker | undefined; private _ghostTextMarker: IXtermMarker | undefined; - private static _instance: TerminalSpeechToTextSession | undefined = undefined; + private static _instance: TerminalVoiceSession | undefined = undefined; private _acceptTranscriptionScheduler: RunOnceScheduler | undefined; - static getInstance(instantiationService: IInstantiationService): TerminalSpeechToTextSession { - if (!TerminalSpeechToTextSession._instance) { - TerminalSpeechToTextSession._instance = instantiationService.createInstance(TerminalSpeechToTextSession); + static getInstance(instantiationService: IInstantiationService): TerminalVoiceSession { + if (!TerminalVoiceSession._instance) { + TerminalVoiceSession._instance = instantiationService.createInstance(TerminalVoiceSession); } - return TerminalSpeechToTextSession._instance; + return TerminalVoiceSession._instance; } private _cancellationTokenSource: CancellationTokenSource | undefined; private readonly _disposables: DisposableStore; @@ -163,7 +163,7 @@ export class TerminalSpeechToTextSession extends Disposable { x: xterm.buffer.active.cursorX ?? 0, }); this._decoration?.onRender((e: HTMLElement) => { - e.classList.add(...ThemeIcon.asClassNameArray(Codicon.micFilled), 'terminal-speech-to-text', 'recording'); + e.classList.add(...ThemeIcon.asClassNameArray(Codicon.micFilled), 'terminal-voice', 'recording'); e.style.transform = 'translate(-5px, -5px)'; }); } @@ -193,7 +193,7 @@ export class TerminalSpeechToTextSession extends Disposable { x: xterm.buffer.active.cursorX + 1 ?? 0, }); this._ghostText?.onRender((e: HTMLElement) => { - e.classList.add('terminal-speech-progress-text'); + e.classList.add('terminal-voice-progress-text'); e.textContent = text; e.style.width = 'fit-content'; }); diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 5ff4e943664..80c91d51992 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -495,8 +495,8 @@ export const enum TerminalCommandId { FocusHover = 'workbench.action.terminal.focusHover', ShowEnvironmentContributions = 'workbench.action.terminal.showEnvironmentContributions', ToggleStickyScroll = 'workbench.action.terminal.toggleStickyScroll', - StartSpeechToText = 'workbench.action.startTerminalSpeechToText', - StopSpeechToText = 'workbench.action.stopTerminalSpeechToText', + StartVoice = 'workbench.action.startTerminalVoice', + StopVoice = 'workbench.action.stopTerminalVoice', // Developer commands From 0cd38c74e69a76905ea6a90eb21844ff9c19567f Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 18 Jan 2024 10:46:25 -0800 Subject: [PATCH 0439/1897] Add more `ensureNoDisposablesAreLeakedInTestSuite` (#202292) For #200091 --- .eslintrc.json | 5 ----- src/vs/base/test/browser/comparers.test.ts | 3 ++- src/vs/base/test/browser/hash.test.ts | 3 +++ .../test/browser/editSort.test.ts | 4 ++++ .../test/common/instantiationService.test.ts | 3 +++ .../api/test/browser/extHostTextEditor.test.ts | 16 ++++++++++------ 6 files changed, 22 insertions(+), 12 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 3b616d8a44e..733bcd3e036 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -149,13 +149,10 @@ // Files should (only) be removed from the list they adopt the leak detector "exclude": [ "src/vs/base/test/browser/browser.test.ts", - "src/vs/base/test/browser/comparers.test.ts", - "src/vs/base/test/browser/hash.test.ts", "src/vs/base/test/browser/ui/scrollbar/scrollableElement.test.ts", "src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts", "src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts", "src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts", - "src/vs/editor/contrib/dropOrPasteInto/test/browser/editSort.test.ts", "src/vs/editor/contrib/gotoSymbol/test/browser/referencesModel.test.ts", "src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts", "src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts", @@ -173,7 +170,6 @@ "src/vs/platform/contextkey/test/common/scanner.test.ts", "src/vs/platform/extensions/test/common/extensionValidator.test.ts", "src/vs/platform/instantiation/test/common/graph.test.ts", - "src/vs/platform/instantiation/test/common/instantiationService.test.ts", "src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts", "src/vs/platform/keybinding/test/common/keybindingLabels.test.ts", "src/vs/platform/keybinding/test/common/keybindingResolver.test.ts", @@ -188,7 +184,6 @@ "src/vs/workbench/api/test/browser/extHostApiCommands.test.ts", "src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts", "src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts", - "src/vs/workbench/api/test/browser/extHostTextEditor.test.ts", "src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts", "src/vs/workbench/api/test/browser/extHostWorkspace.test.ts", "src/vs/workbench/api/test/browser/mainThreadConfiguration.test.ts", diff --git a/src/vs/base/test/browser/comparers.test.ts b/src/vs/base/test/browser/comparers.test.ts index 6e7047c8699..8848b04a6e8 100644 --- a/src/vs/base/test/browser/comparers.test.ts +++ b/src/vs/base/test/browser/comparers.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { compareFileExtensions, compareFileExtensionsDefault, compareFileExtensionsLower, compareFileExtensionsUnicode, compareFileExtensionsUpper, compareFileNames, compareFileNamesDefault, compareFileNamesLower, compareFileNamesUnicode, compareFileNamesUpper } from 'vs/base/common/comparers'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; const compareLocale = (a: string, b: string) => a.localeCompare(b); const compareLocaleNumeric = (a: string, b: string) => a.localeCompare(b, undefined, { numeric: true }); @@ -694,7 +695,7 @@ suite('Comparers', () => { assert(compareFileExtensionsUnicode('txt.abc01', 'txt.abc1') < 0, 'extensions with equivalent numbers sort in unicode order'); assert(compareFileExtensionsUnicode('a.ext1', 'b.Ext1') < 0, 'if extensions with numbers are equal except for case, unicode full filenames should be compared'); assert(compareFileExtensionsUnicode('a.ext1', 'a.Ext1') > 0, 'if extensions with numbers are equal except for case, unicode full filenames should be compared'); - }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/base/test/browser/hash.test.ts b/src/vs/base/test/browser/hash.test.ts index b029d748f19..e613a1913f1 100644 --- a/src/vs/base/test/browser/hash.test.ts +++ b/src/vs/base/test/browser/hash.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import { sha1Hex } from 'vs/base/browser/hash'; import { hash, StringSHA1 } from 'vs/base/common/hash'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Hash', () => { test('string', () => { @@ -101,4 +102,6 @@ suite('Hash', () => { test('sha1-4', () => { return checkSHA1('hello', 'aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d'); }); + + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/editor/contrib/dropOrPasteInto/test/browser/editSort.test.ts b/src/vs/editor/contrib/dropOrPasteInto/test/browser/editSort.test.ts index 44939652de3..f41f6866982 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/test/browser/editSort.test.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/test/browser/editSort.test.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { DocumentOnDropEdit } from 'vs/editor/common/languages'; import { sortEditsByYieldTo } from 'vs/editor/contrib/dropOrPasteInto/browser/edit'; @@ -18,6 +19,7 @@ function createTestEdit(providerId: string, args?: Partial): DropEdit } suite('sortEditsByYieldTo', () => { + test('Should noop for empty edits', () => { const edits: DropEdit[] = []; @@ -62,4 +64,6 @@ suite('sortEditsByYieldTo', () => { assert.deepStrictEqual(sortEditsByYieldTo(edits).map(x => x.providerId), ['c', 'a', 'b']); }); + + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/platform/instantiation/test/common/instantiationService.test.ts b/src/vs/platform/instantiation/test/common/instantiationService.test.ts index 349e31056ff..d3fa8924baa 100644 --- a/src/vs/platform/instantiation/test/common/instantiationService.test.ts +++ b/src/vs/platform/instantiation/test/common/instantiationService.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; import { dispose } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { createDecorator, IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; @@ -653,4 +654,6 @@ suite('Instantiation Service', () => { c.a.doIt(); assert.strictEqual(eventCount, 1); }); + + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts b/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts index b6db2fb92fe..106a30df4f8 100644 --- a/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts @@ -3,15 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { TextEditorLineNumbersStyle, Range } from 'vs/workbench/api/common/extHostTypes'; -import { TextEditorCursorStyle, RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; -import { MainThreadTextEditorsShape, IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostTextEditorOptions, ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor'; -import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; +import { Lazy } from 'vs/base/common/lazy'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { RenderLineNumbersType, TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions'; import { NullLogService } from 'vs/platform/log/common/log'; -import { Lazy } from 'vs/base/common/lazy'; +import { IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, MainThreadTextEditorsShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; +import { ExtHostTextEditor, ExtHostTextEditorOptions } from 'vs/workbench/api/common/extHostTextEditor'; +import { Range, TextEditorLineNumbersStyle } from 'vs/workbench/api/common/extHostTypes'; suite('ExtHostTextEditor', () => { @@ -59,6 +60,8 @@ suite('ExtHostTextEditor', () => { await editor.value.edit(edit => { edit.delete(new Range(0, 0, 1, 1)); }); assert.strictEqual(applyCount, 2); }); + + ensureNoDisposablesAreLeakedInTestSuite(); }); suite('ExtHostTextEditorOptions', () => { @@ -513,4 +516,5 @@ suite('ExtHostTextEditorOptions', () => { assert.deepStrictEqual(calls, [{ cursorStyle: TextEditorCursorStyle.Block, lineNumbers: RenderLineNumbersType.Relative }]); }); + ensureNoDisposablesAreLeakedInTestSuite(); }); From 2df662e02b2fd3de1575d4e3507de1d90881d570 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 19 Jan 2024 00:21:40 +0530 Subject: [PATCH 0440/1897] fix #197215 (#202756) --- .../workbench/services/views/browser/viewDescriptorService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index d841d165c09..16e775ba028 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -795,7 +795,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor }, { id: MenuId.ViewTitleContext, when: ContextKeyExpr.and( - viewContainerModel.visibleViewDescriptors.length > 1 ? ContextKeyExpr.or(...viewContainerModel.visibleViewDescriptors.map(v => ContextKeyExpr.equals('view', v.id))) : ContextKeyExpr.false() + ContextKeyExpr.or(...viewContainerModel.visibleViewDescriptors.map(v => ContextKeyExpr.equals('view', v.id))) ), order: index, group: '2_toggleVisibility' From 309915ba0c819bc77baba19ea8b7afdb611b26a1 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Thu, 18 Jan 2024 11:05:54 -0800 Subject: [PATCH 0441/1897] Nb Sticky Scroll z-index & css fixes (#201837) * no absolute positioning, scrolltop compute based on sticky lines. * remove z-index var * compute re-write, remove init, reduce pop-in * dispose delayer * remove debounce * edge case for cell 0 header, next animation frame instead of debounce * add delayer back, further improve pop in * remove unused param, update testing snapshots --- .../lib/stylelint/vscode-known-variables.json | 1 - .../media/notebookEditorStickyScroll.css | 15 - .../notebook/browser/notebookEditorWidget.ts | 24 +- .../view/cellParts/cellToolbarStickyScroll.ts | 3 +- .../viewParts/notebookEditorStickyScroll.ts | 359 +++++++----------- ...ing_against_equivalent_level_header.0.snap | 1 - ...ing_against_equivalent_level_header.0.snap | 1 + ...___scrolltop_halfway_through_cell_2.0.snap | 2 +- .../test/browser/notebookStickyScroll.test.ts | 15 +- 9 files changed, 177 insertions(+), 244 deletions(-) delete mode 100644 src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test3__should_render_0-_1___collapsing_against_equivalent_level_header.0.snap create mode 100644 src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test3__should_render_0-_2___collapsing_against_equivalent_level_header.0.snap diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index cbb1a5e14dd..96124fe8a87 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -807,7 +807,6 @@ "--z-index-notebook-progress-bar", "--z-index-notebook-scrollbar", "--z-index-run-button-container", - "--z-index-notebook-sticky-scroll", "--zoom-factor", "--test-bar-width" ] diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css b/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css index ce91cb50910..4b4078aecbe 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css @@ -5,9 +5,7 @@ .monaco-workbench .notebookOverlay .notebook-sticky-scroll-container { display: none; - position: absolute; background-color: var(--vscode-notebook-editorBackground); - z-index: var(--z-index-notebook-sticky-scroll); width: 100%; } @@ -17,7 +15,6 @@ .notebook-sticky-scroll-line { background-color: var(--vscode-notebook-editorBackground); position: relative; - z-index: 0; padding-left: 12px; /* transition: margin-top 0.2s ease-in-out; */ } @@ -35,15 +32,3 @@ background-color: var(--vscode-editorStickyScrollHover-background); cursor: pointer; } - -.monaco-workbench - .notebookOverlay - .notebook-sticky-scroll-container - .notebook-shadow { - display: block; - top: 0; - left: 3px; - height: 3px; - width: 100%; - box-shadow: var(--vscode-scrollbar-shadow) 0 6px 6px -6px inset; -} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 1a0b859cb60..e70b4b62870 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1070,6 +1070,27 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD private _registerNotebookStickyScroll() { this._notebookStickyScroll = this._register(this.instantiationService.createInstance(NotebookStickyScroll, this._notebookStickyScrollContainer, this, this._notebookOutline, this._list)); + + const localDisposableStore = this._register(new DisposableStore()); + + this._register(this._notebookStickyScroll.onDidChangeNotebookStickyScroll((sizeDelta) => { + const d = localDisposableStore.add(DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this.getDomNode()), () => { + if (this.isDisposed) { + return; + } + + if (this._dimension) { + if (sizeDelta > 0) { // delta > 0 ==> sticky is growing, cell list shrinking + this.layout(this._dimension); + this.setScrollTop(this.scrollTop + sizeDelta); + } else if (sizeDelta < 0) { // delta < 0 ==> sticky is shrinking, cell list growing + this.setScrollTop(this.scrollTop + sizeDelta); + this.layout(this._dimension); + } + } + localDisposableStore.delete(d); + })); + })); } private _updateOutputRenderers() { @@ -1823,7 +1844,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._dimension = dimension; this._position = position; - const newBodyHeight = this.getBodyHeight(dimension.height); + const newBodyHeight = this.getBodyHeight(dimension.height) - this.getLayoutInfo().stickyHeight; DOM.size(this._body, dimension.width, newBodyHeight); const topInserToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType); @@ -3152,7 +3173,6 @@ registerZIndex(ZIndex.Base, 28, 'notebook-cell-bottom-toolbar-container'); registerZIndex(ZIndex.Base, 29, 'notebook-run-button-container'); registerZIndex(ZIndex.Base, 29, 'notebook-input-collapse-condicon'); registerZIndex(ZIndex.Base, 30, 'notebook-cell-output-toolbar'); -registerZIndex(ZIndex.Base, 31, 'notebook-sticky-scroll'); registerZIndex(ZIndex.Sash, 1, 'notebook-cell-expand-part-button'); registerZIndex(ZIndex.Sash, 2, 'notebook-cell-toolbar'); registerZIndex(ZIndex.Sash, 3, 'notebook-cell-toolbar-dropdown-active'); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbarStickyScroll.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbarStickyScroll.ts index 55d4de6eb49..f425ea10dcb 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbarStickyScroll.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbarStickyScroll.ts @@ -15,10 +15,9 @@ export function registerCellToolbarStickyScroll(notebookEditor: INotebookEditor, if (cell.isInputCollapsed) { element.style.top = ''; } else { - const stickyHeight = notebookEditor.getLayoutInfo().stickyHeight; const scrollTop = notebookEditor.scrollTop; const elementTop = notebookEditor.getAbsoluteTopOfElement(cell); - const diff = scrollTop - elementTop + extraOffset + stickyHeight; + const diff = scrollTop - elementTop + extraOffset; const maxTop = cell.layoutInfo.editorHeight + cell.layoutInfo.statusBarHeight - 45; // subtract roughly the height of the execution order label plus padding const top = maxTop > 20 ? // Don't move the run button if it can only move a very short distance clamp(min, diff, maxTop) : diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts index f7b02b2e465..0c812230c03 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts @@ -6,6 +6,7 @@ import { localize } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; @@ -15,10 +16,10 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; -import { NotebookCellOutlineProvider } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; - -import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { OutlineEntry } from 'vs/workbench/contrib/notebook/browser/viewModel/OutlineEntry'; +import { NotebookCellOutlineProvider } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { Delayer } from 'vs/base/common/async'; export class ToggleNotebookStickyScroll extends Action2 { @@ -81,32 +82,48 @@ export class NotebookStickyLine extends Disposable { } } -// TODO @Yoyokrazy: -// BEHAVIOR -// - [ ] bug with some popping around the cell transition -// - [ ] bug with only bottom most sticky being partially transitioned -// - partial rendering/transition only occuring when the headers shrink against a new section -// - **and only for BOTTOM of that initial sticky tree** -// - issues with HC themes -// UX -// - [ ] render symbols instead of #'s? -// - maybe 'Hx >' where x is the level export class NotebookStickyScroll extends Disposable { private readonly _disposables = new DisposableStore(); private currentStickyLines = new Map(); + private filteredOutlineEntries: OutlineEntry[] = []; + + private readonly _onDidChangeNotebookStickyScroll = this._register(new Emitter()); + readonly onDidChangeNotebookStickyScroll: Event = this._onDidChangeNotebookStickyScroll.event; + getDomNode(): HTMLElement { return this.domNode; } getCurrentStickyHeight() { - return this.currentStickyLines.size * 22; + let height = 0; + this.currentStickyLines.forEach((value) => { + if (value.rendered) { + height += 22; + } + }); + return height; } private setCurrentStickyLines(newStickyLines: Map) { this.currentStickyLines = newStickyLines; } + private compareStickyLineMaps(mapA: Map, mapB: Map): boolean { + if (mapA.size !== mapB.size) { + return false; + } + + for (const [key, value] of mapA) { + const otherValue = mapB.get(key); + if (!otherValue || value.rendered !== otherValue.rendered) { + return false; + } + } + + return true; + } + constructor( private readonly domNode: HTMLElement, private readonly notebookEditor: INotebookEditor, @@ -124,9 +141,6 @@ export class NotebookStickyScroll extends Disposable { if (e.stickyScroll) { this.updateConfig(); } - if (e.globalToolbar) { - this.setTop(); - } })); this._register(DOM.addDisposableListener(this.domNode, DOM.EventType.CONTEXT_MENU, async (event: MouseEvent) => { @@ -153,36 +167,36 @@ export class NotebookStickyScroll extends Disposable { } } - private setTop() { - if (this.notebookEditor.notebookOptions.getDisplayOptions().globalToolbar) { - this.domNode.style.top = '26px'; - } else { - this.domNode.style.top = '0px'; - } - } - private init() { this.notebookOutline.init(); - this.initializeContent(); + this.filteredOutlineEntries = this.notebookOutline.entries.filter(entry => entry.level !== 7); this._disposables.add(this.notebookOutline.onDidChange(() => { - DOM.clearNode(this.domNode); - this.disposeCurrentStickyLines(); - this.updateContent(computeContent(this.domNode, this.notebookEditor, this.notebookCellList, this.notebookOutline.entries)); + this.filteredOutlineEntries = this.notebookOutline.entries.filter(entry => entry.level !== 7); + const recompute = computeContent(this.notebookEditor, this.notebookCellList, this.filteredOutlineEntries, this.getCurrentStickyHeight()); + if (!this.compareStickyLineMaps(recompute, this.currentStickyLines)) { + this.updateContent(recompute); + } })); this._disposables.add(this.notebookEditor.onDidAttachViewModel(() => { this.notebookOutline.init(); - this.initializeContent(); + this.updateContent(computeContent(this.notebookEditor, this.notebookCellList, this.filteredOutlineEntries, this.getCurrentStickyHeight())); })); this._disposables.add(this.notebookEditor.onDidScroll(() => { - DOM.clearNode(this.domNode); - this.disposeCurrentStickyLines(); - this.updateContent(computeContent(this.domNode, this.notebookEditor, this.notebookCellList, this.notebookOutline.entries)); + const d = new Delayer(100); + d.trigger(() => { + d.dispose(); + const recompute = computeContent(this.notebookEditor, this.notebookCellList, this.filteredOutlineEntries, this.getCurrentStickyHeight()); + if (!this.compareStickyLineMaps(recompute, this.currentStickyLines)) { + this.updateContent(recompute); + } + }); })); } + // take in an cell index, and get the corresponding outline entry static getVisibleOutlineEntry(visibleIndex: number, notebookOutlineEntries: OutlineEntry[]): OutlineEntry | undefined { let left = 0; let right = notebookOutlineEntries.length - 1; @@ -210,91 +224,35 @@ export class NotebookStickyScroll extends Disposable { return undefined; } - private initializeContent() { - - // find last code cell of section, store bottom scroll position in sectionBottom - const visibleRange = this.notebookEditor.visibleRanges[0]; - if (!visibleRange) { - return; - } - - DOM.clearNode(this.domNode); - const editorScrollTop = this.notebookEditor.scrollTop; - - let trackedEntry = undefined; - let sectionBottom = 0; - for (let i = visibleRange.start; i < visibleRange.end; i++) { - if (i === 0) { // don't show headers when you're viewing the top cell - this.updateDisplay(); - this.setCurrentStickyLines(new Map()); - return; - } - const cell = this.notebookEditor.cellAt(i); - if (!cell) { - return; - } - - // if we are here, the cell is a code cell. - // check next cell, if markdown, that means this is the end of the section - // check if cell is within visible range - const nextCell = this.notebookEditor.cellAt(i + 1); - if (nextCell && i + 1 < visibleRange.end) { - if (nextCell.cellKind === CellKind.Markup) { - // this is the end of the section - // store the bottom scroll position of this cell - sectionBottom = this.notebookCellList.getCellViewScrollBottom(cell); - // compute sticky scroll height - const entry = NotebookStickyScroll.getVisibleOutlineEntry(i, this.notebookOutline.entries); - if (!entry) { - return; - } - // using 22 instead of stickyscrollheight, as we don't necessarily render each line. 22 starts rendering sticky when we have space for at least 1 of them - const newStickyHeight = NotebookStickyScroll.computeStickyHeight(entry!); - if (editorScrollTop + newStickyHeight < sectionBottom) { - trackedEntry = entry; - break; - } else { - // if (editorScrollTop + stickyScrollHeight > sectionBottom), then continue to next section - continue; - } - } - } else { - // there is no next cell, so use the bottom of the editor as the sectionBottom, using scrolltop + height - sectionBottom = this.notebookEditor.scrollTop + this.notebookEditor.getLayoutInfo().scrollHeight; - trackedEntry = NotebookStickyScroll.getVisibleOutlineEntry(i, this.notebookOutline.entries); - break; - } - } // cell loop close - - // ------------------------------------------------------------------------------------- - // we now know the cell which the sticky is determined by, and the sectionBottom value to determine how many sticky lines to render - // compute the space available for sticky lines, and render sticky lines - - const linesToRender = Math.floor((sectionBottom - editorScrollTop) / 22); - let newMap: Map = new Map(); - newMap = NotebookStickyScroll.renderStickyLines(trackedEntry?.parent, this.domNode, linesToRender, newMap, this.notebookEditor); - this.setCurrentStickyLines(newMap); - this.updateDisplay(); - } - private updateContent(newMap: Map) { + DOM.clearNode(this.domNode); + this.disposeCurrentStickyLines(); + this.renderStickyLines(newMap, this.domNode); + + const oldStickyHeight = this.getCurrentStickyHeight(); this.setCurrentStickyLines(newMap); + + // (+) = sticky height increased + // (-) = sticky height decreased + const sizeDelta = this.getCurrentStickyHeight() - oldStickyHeight; + if (sizeDelta !== 0) { + this._onDidChangeNotebookStickyScroll.fire(sizeDelta); + } this.updateDisplay(); } private updateDisplay() { - const hasSticky = this.currentStickyLines.size > 0; + const hasSticky = this.getCurrentStickyHeight() > 0; if (!hasSticky) { this.domNode.style.display = 'none'; } else { this.domNode.style.display = 'block'; } - this.setTop(); } static computeStickyHeight(entry: OutlineEntry) { let height = 0; - if (entry.cell.cellKind === CellKind.Markup) { + if (entry.cell.cellKind === CellKind.Markup && entry.level !== 7) { height += 22; } while (entry.parent) { @@ -304,8 +262,9 @@ export class NotebookStickyScroll extends Disposable { return height; } - static renderStickyLines(entry: OutlineEntry | undefined, containerElement: HTMLElement, numLinesToRender: number, newMap: Map, notebookEditor: INotebookEditor) { + static checkCollapsedStickyLines(entry: OutlineEntry | undefined, numLinesToRender: number, notebookEditor: INotebookEditor) { let currentEntry = entry; + const newMap = new Map(); const elementsToRender = []; while (currentEntry) { @@ -320,32 +279,27 @@ export class NotebookStickyScroll extends Disposable { currentEntry = currentEntry.parent; } - // TODO: clean up partial cell animation - // [ ] slight pop as lines finish disappearing - // [ ] only actually works when shrunk against new section. **and only for BOTTOM of that initial sticky tree** - // [ ] issues with HC themes - // use negative margins to render the bottom sticky line as a partial element - // todo: partial render logic here - // if (numLinesToRender % 1 !== 0) { - // const partialHeight = 22 - Math.floor((numLinesToRender % 1) * 22); - // elementsToRender[elementsToRender.length - 1].element.style.zIndex = '-1'; - // elementsToRender[elementsToRender.length - 1].element.style.marginTop = `-${partialHeight}px`; - // } - // iterate over elements to render, and append to container // break when we reach numLinesToRender for (let i = 0; i < elementsToRender.length; i++) { if (i >= numLinesToRender) { break; } - containerElement.append(elementsToRender[i].element); newMap.set(elementsToRender[i].entry, { line: elementsToRender[i], rendered: true }); } - - containerElement.append(DOM.$('div', { class: 'notebook-shadow' })); // ensure we have dropShadow at base of sticky scroll return newMap; } + private renderStickyLines(stickyMap: Map, containerElement: HTMLElement) { + const reversedEntries = Array.from(stickyMap.entries()).reverse(); + for (const [, value] of reversedEntries) { + if (!value.rendered) { + continue; + } + containerElement.append(value.line.element); + } + } + static createStickyElement(entry: OutlineEntry, notebookEditor: INotebookEditor) { const stickyElement = document.createElement('div'); stickyElement.classList.add('notebook-sticky-scroll-line'); @@ -366,111 +320,92 @@ export class NotebookStickyScroll extends Disposable { } } -export function computeContent(domNode: HTMLElement, notebookEditor: INotebookEditor, notebookCellList: INotebookCellList, notebookOutlineEntries: OutlineEntry[]): Map { - // find first code cell in visible range. this marks the start of the first section - // find the last code cell in the first section of the visible range, store the bottom scroll position in a const sectionBottom - // compute sticky scroll height, and check if editorScrolltop + stickyScrollHeight < sectionBottom - // if that condition is true, break out of the loop with that cell as the tracked cell - // if that condition is false, continue to next cell - - const editorScrollTop = notebookEditor.scrollTop; - - // find last code cell of section, store bottom scroll position in sectionBottom +export function computeContent(notebookEditor: INotebookEditor, notebookCellList: INotebookCellList, notebookOutlineEntries: OutlineEntry[], renderedStickyHeight: number): Map { + // get data about the cell list within viewport ---------------------------------------------------------------------------------------- + const editorScrollTop = notebookEditor.scrollTop - renderedStickyHeight; const visibleRange = notebookEditor.visibleRanges[0]; if (!visibleRange) { return new Map(); } - let trackedEntry = undefined; - let sectionBottom = 0; - for (let i = visibleRange.start; i < visibleRange.end; i++) { - const cell = notebookEditor.cellAt(i); + // edge case for cell 0 in the notebook is a header ------------------------------------------------------------------------------------ + if (visibleRange.start === 0) { + const firstCell = notebookEditor.cellAt(0); + const firstCellEntry = NotebookStickyScroll.getVisibleOutlineEntry(0, notebookOutlineEntries); + if (firstCell && firstCellEntry && firstCell.cellKind === CellKind.Markup && firstCellEntry.level !== 7) { + if (notebookEditor.scrollTop > 22) { + const newMap = NotebookStickyScroll.checkCollapsedStickyLines(firstCellEntry, 100, notebookEditor); + return newMap; + } + } + } + + // iterate over cells in viewport ------------------------------------------------------------------------------------------------------ + let cell; + let cellEntry; + const startIndex = visibleRange.start - 1; // -1 to account for cells hidden "under" sticky lines. + for (let currentIndex = startIndex; currentIndex < visibleRange.end; currentIndex++) { + // store data for current cell, and next cell + cell = notebookEditor.cellAt(currentIndex); if (!cell) { return new Map(); } - - const nextCell = notebookEditor.cellAt(i + 1); - - // account for transitions between top level headers - if (cell.cellKind === CellKind.Markup) { - sectionBottom = notebookCellList.getCellViewScrollBottom(cell); - const entry = NotebookStickyScroll.getVisibleOutlineEntry(i, notebookOutlineEntries); - if (!entry) { - return new Map(); - } - - if (!entry.parent) { - // if the cell is a top level header, only render once we have scrolled past the bottom of the cell - // todo: (polish) figure out what padding value to use here. need to account properly for bottom insert cell toolbar, cell toolbar, and md cell bottom padding - if (sectionBottom > editorScrollTop) { - return new Map(); - } - } + cellEntry = NotebookStickyScroll.getVisibleOutlineEntry(currentIndex, notebookOutlineEntries); + if (!cellEntry) { + return new Map(); } - // if we are here, the cell is a code cell. - // check next cell, if markdown, that means this is the end of the section - if (nextCell && i + 1 < visibleRange.end) { - if (nextCell.cellKind === CellKind.Markup) { - // this is the end of the section - // store the bottom scroll position of this cell - sectionBottom = notebookCellList.getCellViewScrollBottom(cell); - // compute sticky scroll height - const entry = NotebookStickyScroll.getVisibleOutlineEntry(i, notebookOutlineEntries); - if (!entry) { - return new Map(); - } - // check if we can render this section of sticky - const currentSectionStickyHeight = NotebookStickyScroll.computeStickyHeight(entry!); - if (editorScrollTop + currentSectionStickyHeight < sectionBottom) { - const linesToRender = Math.floor((sectionBottom - editorScrollTop) / 22); - let newMap: Map = new Map(); - newMap = NotebookStickyScroll.renderStickyLines(entry, domNode, linesToRender, newMap, notebookEditor); - return newMap; - } - - let nextSectionEntry = undefined; - for (let j = 1; j < visibleRange.end - i; j++) { - // find next section after this one - const cellCheck = notebookEditor.cellAt(i + j); - if (cellCheck) { - nextSectionEntry = NotebookStickyScroll.getVisibleOutlineEntry(i + j, notebookOutlineEntries); - if (nextSectionEntry) { - break; - } - } - } - const nextSectionStickyHeight = NotebookStickyScroll.computeStickyHeight(nextSectionEntry!); - - // recompute section bottom based on the top of the next section - sectionBottom = notebookCellList.getCellViewScrollTop(nextSectionEntry!.cell) - 10; - - // this block of logic cleans transitions between two sections that share a parent. - // if the current section and the next section share a parent, then we can render the next section's sticky lines to avoid pop-in between - if (entry?.parent?.parent === nextSectionEntry?.parent) { - const linesToRender = Math.floor((sectionBottom - editorScrollTop) / 22) + 100; - let newMap: Map = new Map(); - newMap = NotebookStickyScroll.renderStickyLines(nextSectionEntry?.parent, domNode, linesToRender, newMap, notebookEditor); - return newMap; - } else if (Math.abs(currentSectionStickyHeight - nextSectionStickyHeight) > 22) { // only shrink sticky - const linesToRender = (sectionBottom - editorScrollTop) / 22; - let newMap: Map = new Map(); - newMap = NotebookStickyScroll.renderStickyLines(entry?.parent, domNode, linesToRender, newMap, notebookEditor); - return newMap; - } - } - } else { - // there is no next visible cell, so use the bottom of the editor as the sectionBottom, using scrolltop + height - sectionBottom = notebookEditor.getLayoutInfo().scrollHeight; - trackedEntry = NotebookStickyScroll.getVisibleOutlineEntry(i, notebookOutlineEntries); - const linesToRender = Math.floor((sectionBottom - editorScrollTop) / 22); - - let newMap: Map = new Map(); - newMap = NotebookStickyScroll.renderStickyLines(trackedEntry?.parent, domNode, linesToRender, newMap, notebookEditor); + const nextCell = notebookEditor.cellAt(currentIndex + 1); + if (!nextCell) { + const sectionBottom = notebookEditor.getLayoutInfo().scrollHeight; + const linesToRender = Math.floor((sectionBottom) / 22); + const newMap = NotebookStickyScroll.checkCollapsedStickyLines(cellEntry, linesToRender, notebookEditor); return newMap; } - } // for cell loop close - return new Map(); + const nextCellEntry = NotebookStickyScroll.getVisibleOutlineEntry(currentIndex + 1, notebookOutlineEntries); + if (!nextCellEntry) { + return new Map(); + } + + // check next cell, if markdown with non level 7 entry, that means this is the end of the section (new header) --------------------- + if (nextCell.cellKind === CellKind.Markup && nextCellEntry.level !== 7) { + const sectionBottom = notebookCellList.getCellViewScrollTop(nextCell); + const currentSectionStickyHeight = NotebookStickyScroll.computeStickyHeight(cellEntry); + const nextSectionStickyHeight = NotebookStickyScroll.computeStickyHeight(nextCellEntry); + + // case: we can render the all sticky lines for the current section ------------------------------------------------------------ + if (editorScrollTop + currentSectionStickyHeight < sectionBottom) { + const linesToRender = Math.floor((sectionBottom - editorScrollTop) / 22); + const newMap = NotebookStickyScroll.checkCollapsedStickyLines(cellEntry, linesToRender, notebookEditor); + return newMap; + } + + // case: next section is the same size or bigger, render next entry ----------------------------------------------------------- + else if (nextSectionStickyHeight >= currentSectionStickyHeight) { + const newMap = NotebookStickyScroll.checkCollapsedStickyLines(nextCellEntry, 100, notebookEditor); + return newMap; + } + // case: next section is the smaller, shrink until next section height is greater than the available space --------------------- + else if (nextSectionStickyHeight < currentSectionStickyHeight) { + const availableSpace = sectionBottom - editorScrollTop; + + if (availableSpace >= nextSectionStickyHeight) { + const linesToRender = Math.floor((availableSpace) / 22); + const newMap = NotebookStickyScroll.checkCollapsedStickyLines(cellEntry, linesToRender, notebookEditor); + return newMap; + } else { + const newMap = NotebookStickyScroll.checkCollapsedStickyLines(nextCellEntry, 100, notebookEditor); + return newMap; + } + } + } + } // visible range loop close + + // case: all visible cells were non-header cells, so render any headers relevant to their section -------------------------------------- + const sectionBottom = notebookEditor.getLayoutInfo().scrollHeight; + const linesToRender = Math.floor((sectionBottom - editorScrollTop) / 22); + const newMap = NotebookStickyScroll.checkCollapsedStickyLines(cellEntry, linesToRender, notebookEditor); + return newMap; } registerAction2(ToggleNotebookStickyScroll); diff --git a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test3__should_render_0-_1___collapsing_against_equivalent_level_header.0.snap b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test3__should_render_0-_1___collapsing_against_equivalent_level_header.0.snap deleted file mode 100644 index 1bcf0a58d43..00000000000 --- a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test3__should_render_0-_1___collapsing_against_equivalent_level_header.0.snap +++ /dev/null @@ -1 +0,0 @@ -[ "# header a", "## header aa" ] \ No newline at end of file diff --git a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test3__should_render_0-_2___collapsing_against_equivalent_level_header.0.snap b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test3__should_render_0-_2___collapsing_against_equivalent_level_header.0.snap new file mode 100644 index 00000000000..3b3d5999fd8 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test3__should_render_0-_2___collapsing_against_equivalent_level_header.0.snap @@ -0,0 +1 @@ +[ "# header a", "## header aa", "### header aab" ] \ No newline at end of file diff --git a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test5__should_render_0-_2___scrolltop_halfway_through_cell_2.0.snap b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test5__should_render_0-_2___scrolltop_halfway_through_cell_2.0.snap index cf5583b0ab9..2e13ea43822 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test5__should_render_0-_2___scrolltop_halfway_through_cell_2.0.snap +++ b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test5__should_render_0-_2___scrolltop_halfway_through_cell_2.0.snap @@ -1 +1 @@ -[ "# header a", "## header aa", "### header aaa" ] +[ "# header a", "## header aa", "### header aaa" ] \ No newline at end of file diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookStickyScroll.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookStickyScroll.test.ts index 5c9682417a9..0cb027d1beb 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookStickyScroll.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookStickyScroll.test.ts @@ -50,7 +50,7 @@ suite('NotebookEditorStickyScroll', () => { } function nbStickyTestHelper(domNode: HTMLElement, notebookEditor: INotebookEditor, notebookCellList: INotebookCellList, notebookOutlineEntries: OutlineEntry[], disposables: Pick) { - const output = computeContent(domNode, notebookEditor, notebookCellList, notebookOutlineEntries); + const output = computeContent(notebookEditor, notebookCellList, notebookOutlineEntries, 0); for (const stickyLine of output.values()) { disposables.add(stickyLine.line); } @@ -181,7 +181,7 @@ suite('NotebookEditorStickyScroll', () => { }); }); - test('test3: should render 0->1, collapsing against equivalent level header', async function () { + test('test3: should render 0->2, collapsing against equivalent level header', async function () { await withTestNotebook( [ ['# header a', 'markdown', CellKind.Markup, [], {}], // 0 @@ -222,7 +222,7 @@ suite('NotebookEditorStickyScroll', () => { }); // outdated/improper behavior - test.skip('test4: should render 0, scrolltop halfway through cell 0', async function () { + test('test4: should render 0, scrolltop halfway through cell 0', async function () { await withTestNotebook( [ ['# header a', 'markdown', CellKind.Markup, [], {}], @@ -260,8 +260,7 @@ suite('NotebookEditorStickyScroll', () => { }); }); - // outdated/improper behavior - test.skip('test5: should render 0->2, scrolltop halfway through cell 2', async function () { + test('test5: should render 0->2, scrolltop halfway through cell 2', async function () { await withTestNotebook( [ ['# header a', 'markdown', CellKind.Markup, [], {}], @@ -301,8 +300,7 @@ suite('NotebookEditorStickyScroll', () => { }); }); - // outdated/improper behavior - test.skip('test6: should render 6->7, scrolltop halfway through cell 7', async function () { + test('test6: should render 6->7, scrolltop halfway through cell 7', async function () { await withTestNotebook( [ ['# header a', 'markdown', CellKind.Markup, [], {}], @@ -342,7 +340,6 @@ suite('NotebookEditorStickyScroll', () => { }); }); - // waiting on behavior push to fix this. test('test7: should render 0->1, collapsing against next section', async function () { await withTestNotebook( [ @@ -384,6 +381,4 @@ suite('NotebookEditorStickyScroll', () => { outline.dispose(); }); }); - - }); From dc545a6242c994e33676352d2c8648f303e944ee Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 18 Jan 2024 20:06:09 +0100 Subject: [PATCH 0442/1897] Update icons ttf --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 78244 -> 79504 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index cf782c9395efaad94ff7dbe7d6abd7e23f4c6e4c..4894dfa316d4a98f4804f67b7840b00e0e20051a 100644 GIT binary patch delta 8341 zcmai(33ycH)yMy5GT9)Rglv<9?2}|5Kp-K>gnbW$HSDV)fg~U%1j4T9AR1WpjDKG2QVU&kvB?xlN0gC-%5oC6)Kt5Cf#WN= zmA@zR{;c`6P4$8HKfF8w(*77P{*p&J_6yrWcQSY
if zawL!;dmP`&-jh--j~9eUpde&TpfDthT-tlqx!4gXj5xH(5fS(%BHo|c`Z$~k2x&d* zOl<>%rotKWC0`teSYB~h>#PmdyVg-V(jFZw{8p}90jwHpy>*Xu)H-IjwX5_RpZMoL z;q$Z5fHOEFRdOa|6MqcBIT*oaao!0kAOzoEa} zh2=<+{n&~-q>m&^jKs=z9L5uPMYhU~STA?VB3Uh~WU<^P>*aR21qWn>+yMui2xVv5 zA_~!PA%fg5oXR^n#df>pQ`w_y#|Vjb?p-M9yv5Wr^Khx@Sw58y%k6c6JOJc@024BN2- zPhlsX<~`5gSv-e**pFB78s5Yqyn}cBID(^i4=3>}oWieh8o%MNy^kRNjBoHAzQ+&v z5r4(saalwh5+V`OM%qf0xFk;EB}v?pD(xjl^2IA1q@#3_&eBB+rK@z4QYjOkluHlk zDZQk(^p*ZHPzK2msgPk(DZ|A-LPpAXnIIEol1!GVTs$+SMrKL9%#j9SDrq&ud{&oM|saTi|4B}tSLbQF)Ip$tA$%H6U@o{^pMG@h4H zT#O00LEe&g~81U|$IP^pck}(oL!wWJ6BV@YF!!IRV9+pQ@Aq{v^UPd>`l4uE&ei)0d zWvE<_)$mJ;)Z!()h@Z==@(KpaSQ(A+*tPduRJ8aP=m#lB%onTh8-!J>;2f1E%^-uR zvlV`!u<8`-rPeD_L~T&y9_k!LGO2SFF?zi+Ng{bNPvLhC>jnj*sEvxeMr~4L8r9rK z#5AB;p=E%@&KR&UaMIAoowF%@l4#5Bgt4iPgf8x=7PxK|PL4q9Y`+>WlW z0xS?(P*|H4ZHINr3_A}r^RRxc(BQ*5t(!A1=jBstfGFR(C5JVRH65Q^_fCH1nYB!9tqYL z3Vjo-KPdE4u>PpfU%~p5fh(A<3)YtkofxdI6uK%{mO{q{%U0;(U3!unpJM}_r+Lf;DO zM}=M%)?XF+TUZwrdR|z6Q(8NAiRZ*mcMR+A3LP`7%L-jItSbtgHQ4!9=(g#C6gqI& z4u!59c8Ef!4%?~Fy~7Sw=;&dGDd6I^!&yHMp!0_vp>P`jJ5u3>0Cro2TLaip3O5O` zqZMu!V7nA<9AL*N+(N*PRk)de-A>`Q0(P8&_c{M|yu!T(>;#3o4%mqb_aCs66z)V| zyA|$9U?(fwoxn~}xKDwds&K~wyS>7_3+yxn9A!J5_<0aBoEeIk;mlOT3}==iW^}R@ zX+zCX#Egzd5i>fuikK0}Q^fQ(UlG$;uOjS}-|oNy5!0)VikQ|HC}LXQNfA?NXGP2l zyC`B_Sg443UspxU`?@J&-ch88c}H<8eu#NVcSX!gN)$0KDOJS0q)ZWW&8LXDR<4M- z)Q!VQ&j%NG_b zP^T-hggQf!rPS*bS!TX}(?TN4siuWQZlanN5?M*DRpe%>X)F;xb+%%HM!QatHB{3H zB5SD)imaoW4iUMRI@iFL$R?g#ugFiS^AvfAYI;rN5vu7mk!@7dYa)+P=PU9!)rD45nb4IVTLOU~f{8ABaeE7dctFPhqeH`+kLC7wjzx-l9IB z$gV(Dn#W(ql360ect-nSMN+6{xeysceN-_?6nmS(;0?A}Is`9Mw<~fT^>IaJP4C7j|H=i2xCClI~7c&KCLh=g#C2Tf0jn8v)Kh-vVv z3WHPF2Nak}UsGfh)ijDo74;2;K`iWp3T~jjX-0(yF_UH1ERlDprcC5*>Q!Vj&&_W{ z%;X`xRX1YigDW+tSV`T1PI zLFyNZWCuRU^!P8b^reD$>Q{>VjA|*&Bfz#5W)xrt73LOTf2}af0Q;Q6d;{!1E6hB= z{zidm+P4a`5wOpj6~Y4(6R^Kin4*AvL1D52_FojHEnt7IFo6O42L)zKepHy$fc;km zW)+yu5hgld|4m`a1NJ3_$&Y|!7gqiW2#Ufi2!alU`49v{6lO*cbSmJY4TdUw48br3 zR|3nkyZT46)K+2o1i>f;FHoZurc@AgDHuVGQDizbR$;;gL5{ouT)DwGMZ&3^DT6#r zMassMgDD@TI#M%IOHvzB*QM@C{Ur6r_RHJvY=1m0 zEv;u-P1>ro9cgFNE~clYk4(SCpZ-kxvGfZWSsBAJR%LvUIW2QT=98IcvwCOUlJ!}3 z-|VH?M{>I5EYEp8=d!2Hv)1#X=dkCq+=kpWx!ZCN2Te6GvfLPz16!b62;x~6sQ-?g#puCAxL z`MY(i>2|2w<)Y3-Ek&n_9mQG2^NY6^AMRe#eSP=C-M=pxTym+juyk|jrLwBBO=Snm zJ}CRf=kk^LrukO-w)*z_&iKxiN0sN64=kTn-cr7+{L3Cadz|i>(6hYf)}Al)Jl?CW z*UDZG^*Y|WxOY|W?Y)opzSL)6pSgY3_1V_vSfBHKbNqc*_kF1E`FDc6m$(tu%p0a$(!KqnO@1J^nTEw(j)6Ps!o4#&F z?2I!rE?gH^jq2j+Vb$}i_g7z@nK83!=H{B*nu?lLH3w_HsEw^HuAN(Zc~RCJd zvuDg+TNhS0ux?k~>3UcFn)-tc&W4JH)eT4HB+O}?vvJOcb6e)VH}Ad1RgJqEPd4># zs%zTVw6p2V{0{Rc&EMHv(Y&g8WAnD=JCJC5zI&p_N8BXwD&bUbcw+ZbYN;pD-Q8E}t&We+ z8!;k(MC%{ry7>4{KP7Q-5`N-9e0*GdE63xit4}Ok7#DXU&SZmGX972>VSD!vnG?ZA z@Ra7a19Ru_#faDE_PP5?X-T1Ya>DaHW!;N2Bq1!kEIg-BO8Po794-kj?koH~$0Z3F z;(n^DD=j*)LuF)}xQw{ysOT7XhTH2-NeHcHIW8kNqn+@??M+JZ7Mg>5W4WZc61#PZ zOpcE8WW|B^TA&gz_D@l(!!@`_? zXK1L?ocsq3jUOC6A$p=Ks=VBpSCkjhqelp(vAwswqStvH~SwAWDCc4A9SQ5iaeO`W|`7X(jGG9?yX^FQe@cgmFhSs&@dvw{> zwX^5B9vxJ;HZD6k-_@>TVQfr%L}Hid_;%4@5t8J|hzScF?ul*}6_wt#$~7l(>cCy$ z^ITmLGpF`(zj#ne1DlQ& zhiQd@6UTDH$K<*A31$Ya94iP*%!}vg-kXmt80gnE6wYuhl>7Ef(9y?p?Ua@zdh>H) z_;1BbDJ%6QrAtD%uP^`iT9)q(=fs8Obd~&)ePcRw-r$G`i4GCxW1%jmv&89?Q0JFP zneFvz+_22-USV73IYOdBB11NZhJ6|tc_K10#S!6r!WkjACi>G7TYuw#wvK8z*E07P zzA$foSGihCKk??5^pmpUOkoo;IjZU6DGhgX3DNu<3kwSgsVQi!FC;fNgpG-C6wGo) zIE$Um-j$VoLY>}_5OGY&(ne(Guq9C;j;yJ?m=Yxs&KU#BeT9|I2**C#CG*?Jk$uxr zWOazk@s{Il$2*S0j&~hL97p$klqS(3krA_->*~8SFIu$at4w*o6qnYw)Xu5jcWahp zw{6>|rG7zkmxlVqv*&Kv-bwE47TdC9ewW(D#xB=x48M9I(&()E#^z;F=Iq+f`}%d3 dkDczArHfiCx_+9-U7KLefOInev`yYv_j=lf@ delta 7271 zcmY+}33v`y-v;pepH(6u@?>9RkDV;CAPFJ}(jd0jlMrhX#IEX5RaH_&6jfDKRa7;V zzNo6wx7Ai#RaMK@zeeVq*c@uE;z_iNp$)#I%4&%1pQ06p# z;WpOI#m~nnaa#4fMgE@u+~WTDowrBTtV!j+yQla82?2on%Ifk(v)wMqz5II^w~wtU zudb|*{NtrTkkDTI^5*PWbLZWD>)I=j@N6J#`iA2P{-+?^mb~l7?BC!@l~eVtrNPbB zw(*=ykoq+DzZ%m#JUr_2o4r===i_jCG;I&}+i-9C)RE~6czVFY{XF*+!2jFq7nu(A2ik}2XD+!Wd36pS%l|)IBWN9Pqq`h>Ij?zgw zOS*KG49S!%$(C-?U2-H>dP*#jplM0z4 zQ)QaWkeM=D=18r~l?Ad;7ReG>Do(jq?vpxsNH)tBc~l;g?Xp9jke%|R?2^YVfmk|T0dUXo+-vb-uM@e8iv8h*ogCnlg8-B5#aS&1sl#tf7q8EwQ3Yw$2m z$uqqBLQ#RABvb~;0}_uTQYxb)QmQ0DQZY&z&_~>{me0;DaY!24Nt{F=6Im#f2jy9L zTAq^KcuodNkpyD89G4UFiaadqWrH|3O1*57b!dg55`}H@FHFU|_yiy0A_k)vcOeMx z;Q$U|Br4@DJc&sN#w$1>F^EAdZo(l4q$MunQ+$T6@g=^(x44Aw@gx3&FYvjn#oLmC z2jB&7G=mR*;6wi%BJmn-xVT2+yNIhGC$LluEoKp0ZW8p|6xB5|%WDRqwl4#~MC1xMfl~cU=VTKa(j zd||zz*bKuut=J&LdQ-7!hV`~$BMs|a#q)yoo?^od>#SlE4(olz#vIl;#bzDW2Z{|m zEcRhevtU~f>qEs>AJzrM_8-sMu-2`j5dKvj2j0S+OUB^^; z{UNNYiajH&UlsdEK0(*GAa<9qepBo?VO>}3LSg-`*qOq*p|F$rhhhf{>!xB?3+qqC zP8ZfK#qJl@e^t|o{l(AZWSXBKAz6v3g*HlfvQE1#S4hm+wo~Sn+HSAJoK*)UrZ63qm;-fEVh+?< zi8)XgCFVdWO3c1fm6&~}HE}}B&eN5cop)7YcAlZc>^xJ6*)~gw**05=*|wV!vu$@J zX4@W0%(gj7%(l7a`secO&Bl33%mzJ`@Naf6CFZ-{O3ZipN=)`qVzNL}F9myD`*^Xo^ zbC|jQT*!E?m>-dpF-It=V2)HWg*i&eRA#A?X-snvk{L{M5R#cpa}bi*%yCNQFwOBu zYR&UMfh#0)nG=;PV48xEEM%4|S;RDjB3Z(mq+}^`vJ!JiE0q}^+NR(n_c2Yu$&68L zQ}AirSRUes=}I;;%~_CaVVbibd6ZeDnU~f<; zt?wL_km|;@M-<0Yu+3#Aj;>&v%S<@M+^Xc6`V~#e80ggV~@sWQ1*Q7eYIxxm`%&n2kyz>Q6@} zG&NY}gh(D_n)`?3S*E#vNX&_t`-jAwths-PLr~bJGz3$+SCkB7o={T6H1`;Bs0#a} z!gMAtk<%>1?Bq2i=7;}QVs`kt5_4_eP-5}*yj{S)v!NM9ACpeuQ<|%{h{KR8}c3JUxhy9ZhQ!Gny z_5il6IFA6^r8uJi`)9?u1=v>Nv11MFWF=OAETGj|LZoQ{C~o8rU- z?CXkC6tI6+oUDL-Lvh*y_8$tS?#xLLr!rvwsbKDwTZ+>gu>Y$#(Ea^tjFg ze&FPTD6l$RZi=%YaJehahrs2bI5Ps5r{Ww5TwV%)*YA((;vB@a=8BUhaQP`TF#Q!L zQs8Q#z~{@w8a9L%-4&oX%>q{|g?E^NO3aaYr3~51|Pr z{egsq&W3=TfN25i0$i<@=-IHmuo+=X!w!cdJSBW!cy0K)@ZI4T!*51JMGT3U z7I8T;EHW>$E^>e5xyY+gK~cp~GotFEjz?XIj)=~2Mo*4jAAL0XYU{$*XIlRi6B1J# zb0FqQY)S0F*dODv<95bfj_({_6MrH8MnYmjW@XIe^4%J!5?srjk3sq0g}N()TOOxvAyD?KxPO8SdktGe#U@W`mj zIGO2|nUYzUS(dphb5G`(Ojnj~R@IJ5+uPl@ zdwlo2?iaeddieFo>#?iH$(+EPyqv0>Lph)1+{_Kg&B?9E-I#kV&(E1xk+(bVX3zMZ z6MAm$d9jyouQ9#qdL8R^y?0>mk-eAYd*tWkAMNAT=f{H91w#rB7kt*YZQnJ0FZFBP zZ*sq*{Zsm{?0>Ppt1zmtqHtT`p~BA!Zw(kTVBNs5feQwHGRSw(m_e%sxr(BS@{6_= z9WHwPu4Q+9U)-#CMe&)z-3RX+>>5%q#92FJ*U+?~bwe+gQ%6o8In!@u_RMiJ-$P&pAEkMr~p3 zlG?*_{pJ?W-8T2wypHo$&D%5Y`}qO$XUt!{plHGIg%cKDTokcrBpN%l35#ibsb6ig z#*YUtneSwS#>df{v(9ly2OR04!QKfO=^0Ie(}NS@y`m*3q=y72cqM0LBxlA*Rr!P6Zo5~c%GIc;Fq;mGO8~T{W z&=H)Fk-$P}3FE!Jyn|S{;E>R!^2N7FZj+oVU9&Q?GJA-3MyT18yt&NsHft<*MoZzT z1t(m0>^tr|e9%F6H&1tWPd9f@PcJWjPd7JDe=n!kk=L^w`frPW;(|rV{uvFSp4pxf z5armM&(jchPj@$8{>WVd%`dzT@yA|n2iAHx9DxqU$fono@LwI3oej3h|5w3Dd`C;? zd3JVh z-}k#Y9Da8Wn&WUtxTD1(ZWq{8)uzsq1AV>?m9-BzMrmDVv8MtgKd8vnmT zE|8Sbc<`;cp7kG`-P+jd+_?NBiyczsE2ob97%eN@k1U82fA=kQiL&)brzDAMzGZZZ itPS+8onO Date: Thu, 18 Jan 2024 13:42:45 -0600 Subject: [PATCH 0443/1897] Implement preview on highlighting quickpick in quick search (#202306) --- src/vs/workbench/browser/quickaccess.ts | 45 ++++++++++++++ .../search/browser/anythingQuickAccess.ts | 54 ++++------------- .../quickTextSearch/textSearchQuickAccess.ts | 60 +++++++++++++++---- 3 files changed, 106 insertions(+), 53 deletions(-) diff --git a/src/vs/workbench/browser/quickaccess.ts b/src/vs/workbench/browser/quickaccess.ts index afc3f54369b..a73c3d7072c 100644 --- a/src/vs/workbench/browser/quickaccess.ts +++ b/src/vs/workbench/browser/quickaccess.ts @@ -8,6 +8,12 @@ import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/con import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { getIEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditorViewState, IDiffEditorViewState } from 'vs/editor/common/editorCommon'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorOptions } from 'vs/platform/editor/common/editor'; export const inQuickPickContextKeyValue = 'inQuickOpen'; export const InQuickPickContextKey = new RawContextKey(inQuickPickContextKeyValue, false, localize('inQuickOpen', "Whether keyboard focus is inside the quick open control")); @@ -45,3 +51,42 @@ export function getQuickNavigateHandler(id: string, next?: boolean): ICommandHan quickInputService.navigate(!!next, quickNavigate); }; } +export class EditorViewState { + private _editorViewState: { + editor: EditorInput; + group: IEditorGroup; + state: ICodeEditorViewState | IDiffEditorViewState | undefined; + } | undefined = undefined; + + constructor(private readonly editorService: IEditorService) { } + + set(): void { + if (this._editorViewState) { + return; // return early if already done + } + + const activeEditorPane = this.editorService.activeEditorPane; + if (activeEditorPane) { + this._editorViewState = { + group: activeEditorPane.group, + editor: activeEditorPane.input, + state: getIEditor(activeEditorPane.getControl())?.saveViewState() ?? undefined, + }; + } + } + + async restore(): Promise { + if (this._editorViewState) { + const options: IEditorOptions = { + viewState: this._editorViewState.state, + preserveFocus: true /* import to not close the picker as a result */ + }; + + await this._editorViewState.group.openEditor(this._editorViewState.editor, options); + } + } + + reset() { + this._editorViewState = undefined; + } +} diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index 00c01996488..03a37d5384e 100644 --- a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -35,19 +35,17 @@ import { ThrottledDelayer } from 'vs/base/common/async'; import { top } from 'vs/base/common/arrays'; import { FileQueryCacheState } from 'vs/workbench/contrib/search/common/cacheState'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IEditorOptions, IResourceEditorInput, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { Schemas } from 'vs/base/common/network'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ResourceMap } from 'vs/base/common/map'; import { SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/symbolsQuickAccess'; import { AnythingQuickAccessProviderRunOptions, DefaultQuickAccessFilterValue, Extensions, IQuickAccessRegistry } from 'vs/platform/quickinput/common/quickAccess'; -import { IWorkbenchQuickAccessConfiguration } from 'vs/workbench/browser/quickaccess'; +import { EditorViewState, IWorkbenchQuickAccessConfiguration } from 'vs/workbench/browser/quickaccess'; import { GotoSymbolQuickAccessProvider } from 'vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { ScrollType, IEditor, ICodeEditorViewState, IDiffEditorViewState } from 'vs/editor/common/editorCommon'; +import { ScrollType, IEditor } from 'vs/editor/common/editorCommon'; import { Event } from 'vs/base/common/event'; -import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { getIEditor } from 'vs/editor/browser/editorBrowser'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; @@ -89,11 +87,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider | undefined = undefined; - editorViewState: { - editor: EditorInput; - group: IEditorGroup; - state: ICodeEditorViewState | IDiffEditorViewState | undefined; - } | undefined = undefined; + editorViewState: EditorViewState; scorerCache: FuzzyScorerCache = Object.create(null); fileQueryCache: FileQueryCacheState | undefined = undefined; @@ -106,7 +100,9 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider): void { @@ -131,33 +127,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider { - if (this.editorViewState) { - const options: IEditorOptions = { - viewState: this.editorViewState.state, - preserveFocus: true /* import to not close the picker as a result */ - }; - - await this.editorViewState.group.openEditor(this.editorViewState.editor, options); - } + this.editorViewState.reset(); } }(this, this.editorService); @@ -237,7 +207,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider { if (reason === QuickInputHideReason.Gesture) { - this.pickState.restoreEditorViewState(); + this.pickState.editorViewState.restore(); } })); @@ -259,7 +229,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider { +interface ITextSearchQuickAccessItem extends IPickerQuickAccessItem { + match?: Match; +} +export class TextSearchQuickAccess extends PickerQuickAccessProvider { private queryBuilder: QueryBuilder; private searchModel: SearchModel; private currentAsyncSearch: Promise = Promise.resolve({ results: [], messages: [] }); + private storedOriginalLocation = false; + private readonly editorViewState = new EditorViewState( + this._editorService + ); private _getTextQueryBuilderOptions(charsPerLine: number): ITextQueryBuilderOptions { return { @@ -72,7 +81,7 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable { + override provide(picker: IQuickPick, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable { const disposables = new DisposableStore(); if (TEXT_SEARCH_QUICK_ACCESS_PREFIX.length < picker.value.length) { picker.valueSelection = [TEXT_SEARCH_QUICK_ACCESS_PREFIX.length, picker.value.length]; } picker.customButton = true; picker.customLabel = '$(link-external)'; - picker.onDidCustom(() => { + disposables.add(picker.onDidCustom(() => { if (this.searchModel.searchResult.count() > 0) { this.moveToSearchViewlet(undefined); } else { this._viewsService.openView(VIEW_ID, true); } picker.hide(); - }); + })); + disposables.add(picker.onDidChangeActive(() => { + const [item] = picker.activeItems; + + if (item?.match) { + // only store location once, or else it will store new state every time we change active pick + if (!this.storedOriginalLocation) { + // we must remember our curret view state to be able to restore + this.editorViewState.set(); + this.storedOriginalLocation = true; + } + // open it + this._editorService.openEditor({ + resource: item.match.parent().resource, + options: { preserveFocus: true, revealIfOpened: true, ignoreError: true, selection: item.match.range() } + }); + } + })); + + disposables.add(Event.once(picker.onDidHide)(({ reason }) => { + // Restore view state upon cancellation if we changed it + // but only when the picker was closed via explicit user + // gesture and not e.g. when focus was lost because that + // could mean the user clicked into the editor directly. + if (reason === QuickInputHideReason.Gesture) { + this.editorViewState.restore(); + } + this.searchModel.searchResult.toggleHighlights(false); + })); + disposables.add(super.provide(picker, token, runOptions)); - disposables.add(picker.onDidHide(() => this.searchModel.searchResult.toggleHighlights(false))); disposables.add(picker.onDidAccept(() => this.searchModel.searchResult.toggleHighlights(false))); return disposables; } @@ -177,11 +214,11 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider limit ? matches.slice(0, limit) : matches; - const picks: Array = []; + const picks: Array = []; for (let fileIndex = 0; fileIndex < matches.length; fileIndex++) { if (fileIndex === limit) { @@ -258,7 +295,8 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { this.moveToSearchViewlet(element); return TriggerAction.CLOSE_PICKER; - } + }, + match: element }); } } From fa989a17f1985a641e8338229e7d8ce6980a4d5c Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 18 Jan 2024 11:48:54 -0800 Subject: [PATCH 0444/1897] Switch to non-deprecated overload for rename (#202755) --- build/lib/mangle/renameWorker.js | 6 ++++-- build/lib/mangle/renameWorker.ts | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/build/lib/mangle/renameWorker.js b/build/lib/mangle/renameWorker.js index 428a9626a1f..6cd429b8c9a 100644 --- a/build/lib/mangle/renameWorker.js +++ b/build/lib/mangle/renameWorker.js @@ -7,12 +7,14 @@ Object.defineProperty(exports, "__esModule", { value: true }); const ts = require("typescript"); const workerpool = require("workerpool"); const staticLanguageServiceHost_1 = require("./staticLanguageServiceHost"); -let service; // = ts.createLanguageService(new StaticLanguageServiceHost(projectPath)); +let service; function findRenameLocations(projectPath, fileName, position) { if (!service) { service = ts.createLanguageService(new staticLanguageServiceHost_1.StaticLanguageServiceHost(projectPath)); } - return service.findRenameLocations(fileName, position, false, false, true) ?? []; + return service.findRenameLocations(fileName, position, false, false, { + providePrefixAndSuffixTextForRename: true, + }) ?? []; } workerpool.worker({ findRenameLocations diff --git a/build/lib/mangle/renameWorker.ts b/build/lib/mangle/renameWorker.ts index b5d6bcd5bc9..29b34e8c514 100644 --- a/build/lib/mangle/renameWorker.ts +++ b/build/lib/mangle/renameWorker.ts @@ -7,7 +7,7 @@ import * as ts from 'typescript'; import * as workerpool from 'workerpool'; import { StaticLanguageServiceHost } from './staticLanguageServiceHost'; -let service: ts.LanguageService | undefined;// = ts.createLanguageService(new StaticLanguageServiceHost(projectPath)); +let service: ts.LanguageService | undefined; function findRenameLocations( projectPath: string, @@ -18,7 +18,9 @@ function findRenameLocations( service = ts.createLanguageService(new StaticLanguageServiceHost(projectPath)); } - return service.findRenameLocations(fileName, position, false, false, true) ?? []; + return service.findRenameLocations(fileName, position, false, false, { + providePrefixAndSuffixTextForRename: true, + }) ?? []; } workerpool.worker({ From 63349889d932a10d03c25dd48fe9cd7a0d3fb301 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 18 Jan 2024 17:05:28 -0300 Subject: [PATCH 0445/1897] Avoid "slash command" name in agent API (#202729) * Avoid "slash command" name in agent API * Fix reference * Fix --- .../src/singlefolder-tests/chat.test.ts | 4 +- .../api/common/extHostChatAgents2.ts | 10 ++--- .../api/common/extHostTypeConverters.ts | 2 +- .../vscode.proposed.chatAgents2.d.ts | 38 +++++++++---------- .../vscode.proposed.chatAgents2Additions.d.ts | 2 +- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index 1d1da2658c3..d88eea64c96 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -35,8 +35,8 @@ suite('chat', () => { deferred.complete(request); return null; }); - agent.slashCommandProvider = { - provideSlashCommands: (_token) => { + agent.subCommandProvider = { + provideSubCommands: (_token) => { return [{ name: 'hello', description: 'Hello' }]; } }; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 7832e02cbb1..5a80b2edbf6 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -240,8 +240,8 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { class ExtHostChatAgent { - private _slashCommandProvider: vscode.ChatAgentSlashCommandProvider | undefined; - private _lastSlashCommands: vscode.ChatAgentSlashCommand[] | undefined; + private _slashCommandProvider: vscode.ChatAgentSubCommandProvider | undefined; + private _lastSlashCommands: vscode.ChatAgentSubCommand[] | undefined; private _followupProvider: vscode.FollowupProvider | undefined; private _description: string | undefined; private _fullName: string | undefined; @@ -297,7 +297,7 @@ class ExtHostChatAgent { if (!this._slashCommandProvider) { return []; } - const result = await this._slashCommandProvider.provideSlashCommands(token); + const result = await this._slashCommandProvider.provideSubCommands(token); if (!result) { return []; } @@ -385,10 +385,10 @@ class ExtHostChatAgent { that._iconPath = v; updateMetadataSoon(); }, - get slashCommandProvider() { + get subCommandProvider() { return that._slashCommandProvider; }, - set slashCommandProvider(v) { + set subCommandProvider(v) { that._slashCommandProvider = v; updateMetadataSoon(); }, diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 2170b91a053..1a07e6b23f2 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2430,7 +2430,7 @@ export namespace ChatResponseProgress { } export namespace ChatAgentRequest { - export function to(request: IChatAgentRequest, slashCommand: vscode.ChatAgentSlashCommand | undefined): vscode.ChatAgentRequest { + export function to(request: IChatAgentRequest, slashCommand: vscode.ChatAgentSubCommand | undefined): vscode.ChatAgentRequest { return { prompt: request.message, variables: ChatVariable.objectTo(request.variables), diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index bcc565500c9..12166a4f50c 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -83,12 +83,12 @@ declare module 'vscode' { readonly kind: ChatAgentResultFeedbackKind; } - export interface ChatAgentSlashCommand { + export interface ChatAgentSubCommand { /** * A short name by which this command is referred to in the UI, e.g. `fix` or * `explain` for commands that fix an issue or explain code. * - * **Note**: The name should be unique among the slash commands provided by this agent. + * **Note**: The name should be unique among the subCommands provided by this agent. */ readonly name: string; @@ -98,39 +98,39 @@ declare module 'vscode' { readonly description: string; /** - * When the user clicks this slash command in `/help`, this text will be submitted to this slash command + * When the user clicks this subCommand in `/help`, this text will be submitted to this subCommand */ readonly sampleRequest?: string; /** * Whether executing the command puts the * chat into a persistent mode, where the - * slash command is prepended to the chat input. + * subCommand is prepended to the chat input. */ readonly shouldRepopulate?: boolean; /** * Placeholder text to render in the chat input - * when the slash command has been repopulated. + * when the subCommand has been repopulated. * Has no effect if `shouldRepopulate` is `false`. */ // TODO@API merge this with shouldRepopulate? so that invalid state cannot be represented? readonly followupPlaceholder?: string; } - export interface ChatAgentSlashCommandProvider { + export interface ChatAgentSubCommandProvider { /** - * Returns a list of slash commands that its agent is capable of handling. A slash command + * Returns a list of subCommands that its agent is capable of handling. A subCommand * can be selected by the user and will then be passed to the {@link ChatAgentHandler handler} - * via the {@link ChatAgentRequest.slashCommand slashCommand} property. + * via the {@link ChatAgentRequest.subCommand subCommand} property. * * * @param token A cancellation token. - * @returns A list of slash commands. The lack of a result can be signaled by returning `undefined`, `null`, or + * @returns A list of subCommands. The lack of a result can be signaled by returning `undefined`, `null`, or * an empty array. */ - provideSlashCommands(token: CancellationToken): ProviderResult; + provideSubCommands(token: CancellationToken): ProviderResult; } // TODO@API This should become a progress type, and use vscode.Command @@ -208,9 +208,9 @@ declare module 'vscode' { } | ThemeIcon; /** - * This provider will be called to retrieve the agent's slash commands. + * This provider will be called to retrieve the agent's subCommands. */ - slashCommandProvider?: ChatAgentSlashCommandProvider; + subCommandProvider?: ChatAgentSubCommandProvider; /** * This provider will be called once after each request to retrieve suggested followup questions. @@ -218,7 +218,7 @@ declare module 'vscode' { followupProvider?: FollowupProvider; /** - * When the user clicks this agent in `/help`, this text will be submitted to this slash command + * When the user clicks this agent in `/help`, this text will be submitted to this subCommand */ sampleRequest?: string; @@ -240,10 +240,10 @@ declare module 'vscode' { export interface ChatAgentRequest { /** - * The prompt entered by the user. The {@link ChatAgent2.name name} of the agent or the {@link ChatAgentSlashCommand.name slash command} + * The prompt entered by the user. The {@link ChatAgent2.name name} of the agent or the {@link ChatAgentSubCommand.name subCommand} * are not part of the prompt. * - * @see {@link ChatAgentRequest.slashCommand} + * @see {@link ChatAgentRequest.subCommand} */ prompt: string; @@ -253,14 +253,14 @@ declare module 'vscode' { agentId: string; /** - * The {@link ChatAgentSlashCommand slash command} that was selected for this request. It is guaranteed that the passed slash - * command is an instance that was previously returned from the {@link ChatAgentSlashCommandProvider.provideSlashCommands slash command provider}. + * The {@link ChatAgentSubCommand subCommand} that was selected for this request. It is guaranteed that the passed subCommand + * is an instance that was previously returned from the {@link ChatAgentSubCommandProvider.provideSubCommands subCommand provider}. * @deprecated this will be replaced by `subCommand` */ - slashCommand?: ChatAgentSlashCommand; + slashCommand?: ChatAgentSubCommand; /** - * The name of the {@link ChatAgentSlashCommand slash command} that was selected for this request. + * The name of the {@link ChatAgentSubCommand subCommand} that was selected for this request. */ subCommand?: string; diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index 2794d273dd8..0f9de5a05bd 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -26,7 +26,7 @@ declare module 'vscode' { export interface ChatAgentDetectedAgent { agentName: string; - command?: ChatAgentSlashCommand; + command?: ChatAgentSubCommand; } export interface ChatAgentVulnerability { From 216a8e66d3d3b42ebfc4fb02c36403d260f53b09 Mon Sep 17 00:00:00 2001 From: "Sixia \"Leask\" Huang" Date: Thu, 18 Jan 2024 15:05:56 -0500 Subject: [PATCH 0446/1897] fix: tweak check-requirements for calling ldconfig (#202645) * tweak check-requirements for calling ldconfig * apply patch --- resources/server/bin/helpers/check-requirements-linux.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/server/bin/helpers/check-requirements-linux.sh b/resources/server/bin/helpers/check-requirements-linux.sh index b366a190abf..ac1840d61d8 100644 --- a/resources/server/bin/helpers/check-requirements-linux.sh +++ b/resources/server/bin/helpers/check-requirements-linux.sh @@ -32,7 +32,7 @@ elif [ -f /usr/lib/libstdc++.so.6 ]; then libstdcpp_path='/usr/lib/libstdc++.so.6' elif [ -f /sbin/ldconfig ]; then # Look up path - libstdcpp_paths=$(ldconfig -p | grep 'libstdc++.so.6') + libstdcpp_paths=$(/sbin/ldconfig -p | grep 'libstdc++.so.6') if [ "$(echo "$libstdcpp_paths" | wc -l)" -gt 1 ]; then libstdcpp_path=$(echo "$libstdcpp_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}') @@ -66,7 +66,7 @@ if [ -n "$(ldd --version | grep -v musl)" ]; then libc_path='/usr/lib/libc.so.6' elif [ -f /sbin/ldconfig ]; then # Look up path - libc_paths=$(ldconfig -p | grep 'libc.so.6') + libc_paths=$(/sbin/ldconfig -p | grep 'libc.so.6') if [ "$(echo "$libc_paths" | wc -l)" -gt 1 ]; then libc_path=$(echo "$libc_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}') From 9ed02ced9256e9ff0571a479e82c8fa00ed805a2 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 18 Jan 2024 21:39:40 +0100 Subject: [PATCH 0447/1897] =?UTF-8?q?SCM=20-=20=F0=9F=92=84=20polish=20inc?= =?UTF-8?q?oming/outgoing=20proposed=20API=20(#202766)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/git/src/historyProvider.ts | 118 +++++++++--------- src/vs/workbench/api/browser/mainThreadSCM.ts | 45 +------ .../workbench/api/common/extHost.protocol.ts | 8 +- src/vs/workbench/api/common/extHostSCM.ts | 7 +- .../contrib/scm/browser/scmViewPane.ts | 67 ++++++---- .../workbench/contrib/scm/common/history.ts | 26 +--- .../vscode.proposed.scmHistoryProvider.d.ts | 5 +- 7 files changed, 115 insertions(+), 161 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index b5c735586cc..9560aef5e8e 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -8,10 +8,18 @@ import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider import { Repository, Resource } from './repository'; import { IDisposable, filterEvent } from './util'; import { toGitUri } from './uri'; -import { Branch, RefType, Status } from './api/git'; +import { Branch, RefType, UpstreamRef } from './api/git'; import { emojify, ensureEmojis } from './emoji'; import { Operation } from './operation'; +function isBranchRefEqual(brach1: Branch | undefined, branch2: Branch | undefined): boolean { + return brach1?.name === branch2?.name && brach1?.commit === branch2?.commit; +} + +function isUpstreamRefEqual(upstream1: UpstreamRef | undefined, upstream2: UpstreamRef | undefined): boolean { + return upstream1?.name === upstream2?.name && upstream1?.remote === upstream2?.remote && upstream1?.commit === upstream2?.commit; +} + export class GitHistoryProvider implements SourceControlHistoryProvider, FileDecorationProvider, IDisposable { private readonly _onDidChangeCurrentHistoryItemGroup = new EventEmitter(); @@ -21,10 +29,15 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; private _HEAD: Branch | undefined; + private _HEADBase: UpstreamRef | undefined; private _currentHistoryItemGroup: SourceControlHistoryItemGroup | undefined; get currentHistoryItemGroup(): SourceControlHistoryItemGroup | undefined { return this._currentHistoryItemGroup; } set currentHistoryItemGroup(value: SourceControlHistoryItemGroup | undefined) { + if (this._currentHistoryItemGroup === undefined && value === undefined) { + return; + } + this._currentHistoryItemGroup = value; this._onDidChangeCurrentHistoryItemGroup.fire(); } @@ -41,30 +54,31 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec } private async onDidRunGitStatus(): Promise { - // Check if HEAD has changed - if (this._HEAD?.name === this.repository.HEAD?.name && - this._HEAD?.commit === this.repository.HEAD?.commit && - this._HEAD?.upstream?.name === this.repository.HEAD?.upstream?.name && - this._HEAD?.upstream?.remote === this.repository.HEAD?.upstream?.remote && - this._HEAD?.upstream?.commit === this.repository.HEAD?.upstream?.commit) { - return; - } - - this._HEAD = this.repository.HEAD; - - // Check if HEAD supports incoming/outgoing (not a tag, not detached) - if (!this._HEAD?.name || !this._HEAD?.commit || this._HEAD.type === RefType.Tag) { + // Check if HEAD does not support incoming/outgoing (detached commit, tag) + if (!this.repository.HEAD?.name || !this.repository.HEAD?.commit || this.repository.HEAD.type === RefType.Tag) { + this._HEAD = this._HEADBase = undefined; this.currentHistoryItemGroup = undefined; return; } + // Resolve HEAD base + const HEADBase = await this.resolveHEADBase(this.repository.HEAD); + + // Check if HEAD or HEADBase has changed + if (isBranchRefEqual(this._HEAD, this.repository.HEAD) && isUpstreamRefEqual(this._HEADBase, HEADBase)) { + return; + } + + this._HEAD = this.repository.HEAD; + this._HEADBase = HEADBase; + this.currentHistoryItemGroup = { - id: `refs/heads/${this._HEAD.name}`, - label: this._HEAD.name, - upstream: this._HEAD.upstream ? + id: `refs/heads/${this._HEAD.name ?? ''}`, + label: this._HEAD.name ?? '', + base: this._HEADBase ? { - id: `refs/remotes/${this._HEAD.upstream.remote}/${this._HEAD.upstream.name}`, - label: `${this._HEAD.upstream.remote}/${this._HEAD.upstream.name}`, + id: `refs/remotes/${this._HEADBase.remote}/${this._HEADBase.name}`, + label: `${this._HEADBase.remote}/${this._HEADBase.name}`, } : undefined }; } @@ -138,7 +152,10 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec }); // History item change decoration - const fileDecoration = this.getHistoryItemChangeFileDecoration(change.status); + const letter = Resource.getStatusLetter(change.status); + const tooltip = Resource.getStatusText(change.status); + const color = Resource.getStatusColor(change.status); + const fileDecoration = new FileDecoration(letter, tooltip, color); this.historyItemDecorations.set(historyItemUri.toString(), fileDecoration); historyItemChangesUri.push(historyItemUri); @@ -148,40 +165,6 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return historyItemChanges; } - async resolveHistoryItemGroupBase(historyItemGroupId: string): Promise { - // TODO - support for all history item groups - if (historyItemGroupId !== this.currentHistoryItemGroup?.id) { - return undefined; - } - - if (this.currentHistoryItemGroup?.upstream) { - return this.currentHistoryItemGroup.upstream; - } - - // Branch base - try { - const branchBase = await this.repository.getBranchBase(historyItemGroupId); - - if (branchBase?.name && branchBase?.type === RefType.Head) { - return { - id: `refs/heads/${branchBase.name}`, - label: branchBase.name - }; - } - if (branchBase?.name && branchBase.remote && branchBase?.type === RefType.RemoteHead) { - return { - id: `refs/remotes/${branchBase.remote}/${branchBase.name}`, - label: `${branchBase.remote}/${branchBase.name}` - }; - } - } - catch (err) { - this.logger.error(`Failed to get branch base for '${historyItemGroupId}': ${err.message}`); - } - - return undefined; - } - async resolveHistoryItemGroupCommonAncestor(refId1: string, refId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined> { const ancestor = await this.repository.getMergeBase(refId1, refId2); if (!ancestor) { @@ -202,12 +185,29 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return this.historyItemDecorations.get(uri.toString()); } - private getHistoryItemChangeFileDecoration(status: Status): FileDecoration { - const letter = Resource.getStatusLetter(status); - const tooltip = Resource.getStatusText(status); - const color = Resource.getStatusColor(status); + private async resolveHEADBase(HEAD: Branch): Promise { + // Upstream + if (HEAD.upstream) { + return HEAD.upstream; + } - return new FileDecoration(letter, tooltip, color); + try { + const remoteBranch = await this.repository.getBranchBase(HEAD.name ?? ''); + if (!remoteBranch?.remote || !remoteBranch?.name || !remoteBranch?.commit || remoteBranch?.type !== RefType.RemoteHead) { + return undefined; + } + + return { + name: remoteBranch.name, + remote: remoteBranch.remote, + commit: remoteBranch.commit + }; + } + catch (err) { + this.logger.error(`Failed to get branch base for '${HEAD.name}': ${err.message}`); + } + + return undefined; } dispose(): void { diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 81a43a35387..2c82aa6e2ad 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -15,10 +15,9 @@ import { MarshalledId } from 'vs/base/common/marshallingIds'; import { ThemeIcon } from 'vs/base/common/themables'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IQuickDiffService, QuickDiffProvider } from 'vs/workbench/contrib/scm/common/quickDiff'; -import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGroup, ISCMHistoryItemGroupDetails, ISCMHistoryItemGroupEntry, ISCMHistoryOptions, ISCMHistoryProvider } from 'vs/workbench/contrib/scm/common/history'; +import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGroup, ISCMHistoryOptions, ISCMHistoryProvider } from 'vs/workbench/contrib/scm/common/history'; import { ResourceTree } from 'vs/base/common/resourceTree'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { Codicon } from 'vs/base/common/codicons'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { basename } from 'vs/base/common/resources'; @@ -141,48 +140,6 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider { constructor(private readonly proxy: ExtHostSCMShape, private readonly handle: number) { } - async resolveHistoryItemGroupDetails(historyItemGroup: ISCMHistoryItemGroup): Promise { - // History item group base - const historyItemGroupBase = await this.resolveHistoryItemGroupBase(historyItemGroup.id); - - if (!historyItemGroupBase) { - return undefined; - } - - // Common ancestor, ahead, behind - const ancestor = await this.resolveHistoryItemGroupCommonAncestor(historyItemGroup.id, historyItemGroupBase.id); - - if (!ancestor) { - return undefined; - } - - // Incoming - const incoming: ISCMHistoryItemGroupEntry = { - id: historyItemGroupBase.id, - label: historyItemGroupBase.label, - icon: Codicon.arrowCircleDown, - direction: 'incoming', - ancestor: ancestor.id, - count: ancestor.behind, - }; - - // Outgoing - const outgoing: ISCMHistoryItemGroupEntry = { - id: historyItemGroup.id, - label: historyItemGroup.label, - icon: Codicon.arrowCircleUp, - direction: 'outgoing', - ancestor: ancestor.id, - count: ancestor.ahead, - }; - - return { incoming, outgoing }; - } - - async resolveHistoryItemGroupBase(historyItemGroupId: string): Promise { - return this.proxy.$resolveHistoryItemGroupBase(this.handle, historyItemGroupId, CancellationToken.None); - } - async resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined> { return this.proxy.$resolveHistoryItemGroupCommonAncestor(this.handle, historyItemGroupId1, historyItemGroupId2, CancellationToken.None); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 978e0179e80..f34388f4875 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1477,12 +1477,7 @@ export type SCMRawResourceSplices = [ export interface SCMHistoryItemGroupDto { readonly id: string; readonly label: string; - readonly upstream?: SCMRemoteHistoryItemGroupDto; -} - -export interface SCMRemoteHistoryItemGroupDto { - readonly id: string; - readonly label: string; + readonly base?: Omit; } export interface SCMHistoryItemDto { @@ -2241,7 +2236,6 @@ export interface ExtHostSCMShape { $provideHistoryItems(sourceControlHandle: number, historyItemGroupId: string, options: any, token: CancellationToken): Promise; $provideHistoryItemSummary(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise; $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise; - $resolveHistoryItemGroupBase(sourceControlHandle: number, historyItemGroupId: string, token: CancellationToken): Promise; $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined>; } diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 3e40a0917ae..da1e67ab604 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -11,7 +11,7 @@ import { debounce } from 'vs/base/common/decorators'; import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { asPromise } from 'vs/base/common/async'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; -import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures, SCMHistoryItemDto, SCMHistoryItemChangeDto, SCMHistoryItemGroupDto } from './extHost.protocol'; +import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures, SCMHistoryItemDto, SCMHistoryItemChangeDto } from './extHost.protocol'; import { sortedDiff, equals } from 'vs/base/common/arrays'; import { comparePaths } from 'vs/base/common/comparers'; import type * as vscode from 'vscode'; @@ -960,11 +960,6 @@ export class ExtHostSCM implements ExtHostSCMShape { return Promise.resolve(undefined); } - async $resolveHistoryItemGroupBase(sourceControlHandle: number, historyItemGroupId: string, token: CancellationToken): Promise { - const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; - return await historyProvider?.resolveHistoryItemGroupBase(historyItemGroupId, token) ?? undefined; - } - async $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined> { const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; return await historyProvider?.resolveHistoryItemGroupCommonAncestor(historyItemGroupId1, historyItemGroupId2, token) ?? undefined; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 1f4a37bcc07..5f53e86c9f4 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -3329,44 +3329,66 @@ class SCMTreeDataSource implements IAsyncDataSource 0))) { - children.push({ - ...historyItemGroupDetails.incoming, - ariaLabel: localize('incomingChangesAriaLabel', "Incoming changes from {0}", historyItemGroupDetails.incoming.label), - repository: element, - type: 'historyItemGroup' - }); + (showIncomingChanges === 'auto' && (incomingHistoryItemGroup.count ?? 0) > 0))) { + children.push(incomingHistoryItemGroup); } // Outgoing - if (historyItemGroupDetails?.outgoing && + if (outgoingHistoryItemGroup && (showOutgoingChanges === 'always' || - (showOutgoingChanges === 'auto' && (historyItemGroupDetails.outgoing.count ?? 0) > 0))) { - children.push({ - ...historyItemGroupDetails.outgoing, - ariaLabel: localize('outgoingChangesAriaLabel', "Outgoing changes from {0}", historyItemGroupDetails.outgoing.label), - repository: element, - type: 'historyItemGroup' - }); + (showOutgoingChanges === 'auto' && (outgoingHistoryItemGroup.count ?? 0) > 0))) { + children.push(outgoingHistoryItemGroup); } return children; @@ -3540,7 +3562,8 @@ class SCMTreeDataSource implements IAsyncDataSource(), historyItemChanges: new Map() }; diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index 385019d9ede..b9ac2eed994 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -23,13 +23,12 @@ export interface ISCMHistoryProvider { provideHistoryItems(historyItemGroupId: string, options: ISCMHistoryOptions): Promise; provideHistoryItemSummary(historyItemId: string, historyItemParentId: string | undefined): Promise; provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise; - resolveHistoryItemGroupBase(historyItemGroupId: string): Promise; resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined>; - resolveHistoryItemGroupDetails(historyItemGroup: ISCMHistoryItemGroup): Promise; } export interface ISCMHistoryProviderCacheEntry { - readonly historyItemGroupDetails?: ISCMHistoryItemGroupDetails; + readonly incomingHistoryItemGroup: SCMHistoryItemGroupTreeElement | undefined; + readonly outgoingHistoryItemGroup: SCMHistoryItemGroupTreeElement | undefined; readonly historyItems: Map; readonly historyItemChanges: Map; } @@ -39,34 +38,21 @@ export interface ISCMHistoryOptions { readonly limit?: number | { id?: string }; } -export interface ISCMRemoteHistoryItemGroup { - readonly id: string; - readonly label: string; -} - export interface ISCMHistoryItemGroup { readonly id: string; readonly label: string; - readonly upstream?: ISCMRemoteHistoryItemGroup; + readonly base?: Omit; } -export interface ISCMHistoryItemGroupDetails { - readonly incoming?: ISCMHistoryItemGroupEntry; - readonly outgoing: ISCMHistoryItemGroupEntry; -} - -export interface ISCMHistoryItemGroupEntry { +export interface SCMHistoryItemGroupTreeElement { readonly id: string; readonly label: string; - readonly direction: 'incoming' | 'outgoing'; + readonly ariaLabel?: string; readonly icon?: URI | { light: URI; dark: URI } | ThemeIcon; readonly description?: string; + readonly direction: 'incoming' | 'outgoing'; readonly ancestor?: string; readonly count?: number; -} - -export interface SCMHistoryItemGroupTreeElement extends ISCMHistoryItemGroupEntry { - readonly ariaLabel?: string; readonly repository: ISCMRepository; readonly type: 'historyItemGroup'; } diff --git a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts index b030e44ee98..24f4dcd8453 100644 --- a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts @@ -28,8 +28,7 @@ declare module 'vscode' { provideHistoryItemSummary?(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): ProviderResult; provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): ProviderResult; - resolveHistoryItemGroupBase(historyItemGroupId: string, token: CancellationToken): ProviderResult; - resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId: string, token: CancellationToken): ProviderResult<{ id: string; ahead: number; behind: number }>; + resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string, token: CancellationToken): ProviderResult<{ id: string; ahead: number; behind: number }>; } export interface SourceControlHistoryOptions { @@ -40,7 +39,7 @@ declare module 'vscode' { export interface SourceControlHistoryItemGroup { readonly id: string; readonly label: string; - readonly upstream?: SourceControlRemoteHistoryItemGroup; + readonly base?: Omit; } export interface SourceControlRemoteHistoryItemGroup { From 8d63804f0874686aa6c797b5d49e5f6b35264f2a Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Thu, 18 Jan 2024 13:25:31 -0800 Subject: [PATCH 0448/1897] get long collections in chunks (#202758) * stateless Datasource, improve initialization * retreive long collections in chunks --- .../notebookVariables/notebookVariables.ts | 21 ++++-- .../notebookVariablesDataSource.ts | 74 +++++++++++++------ .../notebookVariablesView.ts | 26 +++++-- 3 files changed, 81 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts index 03c9c7b819e..524f8685626 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts @@ -16,25 +16,30 @@ import { NOTEBOOK_KERNEL } from 'vs/workbench/contrib/notebook/common/notebookCo import { variablesViewIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; export class NotebookVariables extends Disposable implements IWorkbenchContribution { - private listener: IDisposable | undefined; + private listeners: IDisposable[] = []; constructor( @IEditorService private readonly editorService: IEditorService, @IConfigurationService configurationService: IConfigurationService, + @INotebookExecutionStateService private readonly notebookExecutionStateService: INotebookExecutionStateService ) { super(); - this.listener = this.editorService.onDidEditorsChange(() => { - if (configurationService.getValue('notebook.experimental.notebookVariablesView') - && this.editorService.activeEditorPane?.getId() === 'workbench.editor.notebook') { - if (this.initializeView()) { - this.listener?.dispose(); - } + this.listeners.push(this.editorService.onDidEditorsChange(() => this.handleInitEvent(configurationService))); + this.listeners.push(this.notebookExecutionStateService.onDidChangeExecution(() => this.handleInitEvent(configurationService))); + } + + private handleInitEvent(configurationService: IConfigurationService) { + if (configurationService.getValue('notebook.experimental.notebookVariablesView') + && this.editorService.activeEditorPane?.getId() === 'workbench.editor.notebook') { + if (this.initializeView()) { + this.listeners.forEach(listener => listener.dispose()); } - }); + } } private initializeView() { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts index bc64f09d6e7..70511741f65 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts @@ -6,11 +6,11 @@ import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { CancellationToken } from 'vs/base/common/cancellation'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { INotebookKernelService, VariablesResult } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelService, VariablesResult } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; export interface INotebookScope { type: 'root'; - readonly notebook: NotebookTextModel | undefined; + readonly notebook: NotebookTextModel; } export interface INotebookVariableElement { @@ -19,13 +19,13 @@ export interface INotebookVariableElement { readonly name: string; readonly value: string; readonly indexedChildrenCount: number; + readonly indexStart?: number; readonly hasNamedChildren: boolean; + readonly notebook: NotebookTextModel; } export class NotebookVariableDataSource implements IAsyncDataSource { - private notebook: NotebookTextModel | undefined = undefined; - constructor(private readonly notebookKernelService: INotebookKernelService) { } hasChildren(element: INotebookScope | INotebookVariableElement): boolean { @@ -34,33 +34,26 @@ export class NotebookVariableDataSource implements IAsyncDataSource> { if (element.type === 'root') { - this.notebook = element.notebook; - return this.getRootVariables(); + return this.getRootVariables(element.notebook); } else { return this.getVariables(element); } } async getVariables(parent: INotebookVariableElement): Promise { - if (!this.notebook) { - return []; - } - const selectedKernel = this.notebookKernelService.getMatchingKernel(this.notebook).selected; + const selectedKernel = this.notebookKernelService.getMatchingKernel(parent.notebook).selected; if (selectedKernel && selectedKernel.hasVariableProvider) { let children: INotebookVariableElement[] = []; if (parent.hasNamedChildren) { - const variables = selectedKernel.provideVariables(this.notebook.uri, parent.id, 'named', 0, CancellationToken.None); + const variables = selectedKernel.provideVariables(parent.notebook.uri, parent.id, 'named', 0, CancellationToken.None); const childNodes = await variables - .map(variable => { return this.createVariableElement(variable); }) + .map(variable => { return this.createVariableElement(variable, parent.notebook); }) .toPromise(); children = children.concat(childNodes); } if (parent.indexedChildrenCount > 0) { - const variables = selectedKernel.provideVariables(this.notebook.uri, parent.id, 'indexed', 0, CancellationToken.None); - const childNodes = await variables - .map(variable => { return this.createVariableElement(variable); }) - .toPromise(); + const childNodes = await this.getIndexedChildren(parent, selectedKernel); children = children.concat(childNodes); } @@ -69,25 +62,58 @@ export class NotebookVariableDataSource implements IAsyncDataSource { - if (!this.notebook) { - return []; - } + async getIndexedChildren(parent: INotebookVariableElement, kernel: INotebookKernel) { + const childNodes: INotebookVariableElement[] = []; - const selectedKernel = this.notebookKernelService.getMatchingKernel(this.notebook).selected; + if (parent.indexedChildrenCount > 100) { + for (let start = 0; start < parent.indexedChildrenCount; start += 100) { + let end = start + 100; + if (end > parent.indexedChildrenCount) { + end = parent.indexedChildrenCount; + } + + childNodes.push({ + type: 'variable', + notebook: parent.notebook, + id: parent.id, + name: `[${start}..${end - 1}]`, + value: '', + indexedChildrenCount: end - start, + indexStart: start, + hasNamedChildren: false + }); + } + } + else if (parent.indexedChildrenCount > 0) { + const variables = kernel.provideVariables(parent.notebook.uri, parent.id, 'indexed', parent.indexStart ?? 0, CancellationToken.None); + + for await (const variable of variables) { + childNodes.push(this.createVariableElement(variable, parent.notebook)); + if (childNodes.length >= 100) { + break; + } + } + + } + return childNodes; + } + + async getRootVariables(notebook: NotebookTextModel): Promise { + const selectedKernel = this.notebookKernelService.getMatchingKernel(notebook).selected; if (selectedKernel && selectedKernel.hasVariableProvider) { - const variables = selectedKernel.provideVariables(this.notebook.uri, undefined, 'named', 0, CancellationToken.None); + const variables = selectedKernel.provideVariables(notebook.uri, undefined, 'named', 0, CancellationToken.None); return await variables - .map(variable => { return this.createVariableElement(variable); }) + .map(variable => { return this.createVariableElement(variable, notebook); }) .toPromise(); } return []; } - private createVariableElement(variable: VariablesResult): INotebookVariableElement { + private createVariableElement(variable: VariablesResult, notebook: NotebookTextModel): INotebookVariableElement { return { type: 'variable', + notebook, ...variable }; } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index c2b00fd9f17..698f39e668c 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -55,8 +55,10 @@ export class NotebookVariablesView extends ViewPane { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this._register(this.editorService.onDidActiveEditorChange(this.handleActiveEditorChange.bind(this))); - this._register(this.notebookExecutionStateService.onDidChangeExecution(this.handleExecutionStateChange.bind(this))); this._register(this.notebookKernelService.onDidNotebookVariablesUpdate(this.handleVariablesChanged.bind(this))); + this._register(this.notebookExecutionStateService.onDidChangeExecution(this.handleExecutionStateChange.bind(this))); + + this.setActiveNotebook(); } protected override renderBody(container: HTMLElement): void { @@ -75,7 +77,9 @@ export class NotebookVariablesView extends ViewPane { }); this.tree.layout(); - this.tree.setInput({ type: 'root', notebook: this.activeNotebook }); + if (this.activeNotebook) { + this.tree.setInput({ type: 'root', notebook: this.activeNotebook }); + } } protected override layoutBody(height: number, width: number): void { @@ -83,15 +87,21 @@ export class NotebookVariablesView extends ViewPane { this.tree?.layout(height, width); } - private handleActiveEditorChange() { + setActiveNotebook() { + const current = this.activeNotebook; const activeEditorPane = this.editorService.activeEditorPane; if (activeEditorPane && activeEditorPane.getId() === 'workbench.editor.notebook') { const notebookDocument = getNotebookEditorFromEditorPane(activeEditorPane)?.getViewModel()?.notebookDocument; - if (notebookDocument && notebookDocument !== this.activeNotebook) { - this.activeNotebook = notebookDocument; - this.tree?.setInput({ type: 'root', notebook: this.activeNotebook }); - this.tree?.updateChildren(); - } + this.activeNotebook = notebookDocument; + } + + return current !== this.activeNotebook; + } + + private handleActiveEditorChange() { + if (this.setActiveNotebook() && this.activeNotebook) { + this.tree?.setInput({ type: 'root', notebook: this.activeNotebook }); + this.tree?.updateChildren(); } } From 1db9db32a49a2dc76fe6ebe334ea03a76273f707 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 18 Jan 2024 22:25:44 +0100 Subject: [PATCH 0449/1897] SCM - add incoming/outgoing menu proposal (#202772) --- src/vs/platform/actions/common/actions.ts | 2 + .../contrib/scm/browser/media/scm.css | 5 ++ src/vs/workbench/contrib/scm/browser/menus.ts | 14 +++- .../contrib/scm/browser/scmViewPane.ts | 65 +++++++++++++++++-- .../workbench/contrib/scm/common/history.ts | 3 + .../actions/common/menusExtensionPoint.ts | 12 ++++ .../common/extensionsApiProposals.ts | 1 + ...tribSourceControlHistoryItemGroupMenu.d.ts | 8 +++ 8 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemGroupMenu.d.ts diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 5af2f709c13..0e39f772a78 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -107,6 +107,8 @@ export class MenuId { static readonly OpenEditorsContextShare = new MenuId('OpenEditorsContextShare'); static readonly ProblemsPanelContext = new MenuId('ProblemsPanelContext'); static readonly SCMInputBox = new MenuId('SCMInputBox'); + static readonly SCMIncomingChanges = new MenuId('SCMIncomingChanges'); + static readonly SCMOutgoingChanges = new MenuId('SCMOutgoingChanges'); static readonly SCMIncomingChangesAllChangesContext = new MenuId('SCMIncomingChangesAllChangesContext'); static readonly SCMIncomingChangesHistoryItemContext = new MenuId('SCMIncomingChangesHistoryItemContext'); static readonly SCMOutgoingChangesAllChangesContext = new MenuId('SCMOutgoingChangesAllChangesContext'); diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 335b67c2cad..13eb48d7df4 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -255,6 +255,7 @@ .scm-view .monaco-list .monaco-list-row .resource-group > .actions, .scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions, +.scm-view .monaco-list .monaco-list-row .history-item-group > .actions, .scm-view .monaco-list .monaco-list-row .history-item > .actions { display: none; max-width: fit-content; @@ -267,6 +268,9 @@ .scm-view .monaco-list .monaco-list-row.selected .resource > .name > .monaco-icon-label > .actions, .scm-view .monaco-list .monaco-list-row.focused .resource > .name > .monaco-icon-label > .actions, .scm-view .monaco-list:not(.selection-multiple) .monaco-list-row .resource:hover > .actions, +.scm-view .monaco-list .monaco-list-row:hover .history-item-group > .actions, +.scm-view .monaco-list .monaco-list-row.selected .history-item-group > .actions, +.scm-view .monaco-list .monaco-list-row.focused .history-item-group > .actions, .scm-view .monaco-list .monaco-list-row:hover .history-item > .actions, .scm-view .monaco-list .monaco-list-row.selected .history-item > .actions, .scm-view .monaco-list .monaco-list-row.focused .history-item > .actions { @@ -288,6 +292,7 @@ .scm-view.show-actions > .monaco-list .monaco-list-row .scm-input > .scm-editor > .actions, .scm-view.show-actions > .monaco-list .monaco-list-row .resource-group > .actions, .scm-view.show-actions > .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions, +.scm-view.show-actions > .monaco-list .monaco-list-row .history-item-group > .actions, .scm-view.show-actions > .monaco-list .monaco-list-row .history-item > .actions { display: block; } diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index 1cf5d9b4174..968788eb6f5 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -258,9 +258,21 @@ export class SCMHistoryProviderMenus implements ISCMHistoryProviderMenus, IDispo private readonly historyItemMenus = new Map(); private readonly disposables = new DisposableStore(); + private _incomingHistoryItemGroupMenu: IMenu; + get incomingHistoryItemGroupMenu(): IMenu { return this._incomingHistoryItemGroupMenu; } + + private _outgoingHistoryItemGroupMenu: IMenu; + get outgoingHistoryItemGroupMenu(): IMenu { return this._outgoingHistoryItemGroupMenu; } + constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IMenuService private readonly menuService: IMenuService) { } + @IMenuService private readonly menuService: IMenuService) { + this._incomingHistoryItemGroupMenu = this.menuService.createMenu(MenuId.SCMIncomingChanges, this.contextKeyService); + this.disposables.add(this._incomingHistoryItemGroupMenu); + + this._outgoingHistoryItemGroupMenu = this.menuService.createMenu(MenuId.SCMOutgoingChanges, this.contextKeyService); + this.disposables.add(this._outgoingHistoryItemGroupMenu); + } getHistoryItemMenu(historyItem: SCMHistoryItemTreeElement): IMenu { return this.getOrCreateHistoryItemMenu(historyItem); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 5f53e86c9f4..7938ec48156 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -24,7 +24,7 @@ import { MenuItemAction, IMenuService, registerAction2, MenuId, IAction2Options, import { IAction, ActionRunner, Action, Separator, IActionRunner } from 'vs/base/common/actions'; import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService'; -import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, getActionViewItemProvider, isSCMActionButton, isSCMViewService, isSCMHistoryItemGroupTreeElement, isSCMHistoryItemTreeElement, isSCMHistoryItemChangeTreeElement, toDiffEditorArguments, isSCMResourceNode, isSCMHistoryItemChangeNode, isSCMViewSeparator } from './util'; +import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, getActionViewItemProvider, isSCMActionButton, isSCMViewService, isSCMHistoryItemGroupTreeElement, isSCMHistoryItemTreeElement, isSCMHistoryItemChangeTreeElement, toDiffEditorArguments, isSCMResourceNode, isSCMHistoryItemChangeNode, isSCMViewSeparator, connectPrimaryMenu } from './util'; import { WorkbenchCompressibleAsyncDataTree, IOpenEvent } from 'vs/platform/list/browser/listService'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { disposableTimeout, Sequencer, ThrottledDelayer, Throttler } from 'vs/base/common/async'; @@ -761,11 +761,25 @@ class ResourceRenderer implements ICompressibleTreeRenderer { + if (!(action instanceof MenuItemAction)) { + return super.runAction(action, context); + } + + return action.run(context.repository.provider, context.id); + } +} + interface HistoryItemGroupTemplate { readonly iconContainer: HTMLElement; readonly label: IconLabel; + readonly toolBar: WorkbenchToolBar; readonly count: CountBadge; - readonly disposables: IDisposable; + readonly elementDisposables: DisposableStore; + readonly templateDisposables: DisposableStore; } class HistoryItemGroupRenderer implements ICompressibleTreeRenderer { @@ -773,6 +787,16 @@ class HistoryItemGroupRenderer implements ICompressibleTreeRenderer, index: number, templateData: HistoryItemGroupTemplate, height: number | undefined): void { @@ -798,13 +826,36 @@ class HistoryItemGroupRenderer implements ICompressibleTreeRenderer { + templateData.toolBar.setActions(primary, secondary, [menuId]); + })); + + templateData.toolBar.context = historyItemGroup; + } else { + templateData.toolBar.setActions([], []); + templateData.toolBar.context = undefined; + } } renderCompressedElements(node: ITreeNode, void>, index: number, templateData: HistoryItemGroupTemplate, height: number | undefined): void { throw new Error('Should never happen since node is incompressible'); } + + disposeElement(node: ITreeNode, index: number, templateData: HistoryItemGroupTemplate, height: number | undefined): void { + templateData.elementDisposables.clear(); + } + disposeTemplate(templateData: HistoryItemGroupTemplate): void { - templateData.disposables.dispose(); + templateData.elementDisposables.dispose(); + templateData.templateDisposables.dispose(); } } @@ -2709,6 +2760,10 @@ export class SCMViewPane extends ViewPane { resourceActionRunner.onWillRun(() => this.tree.domFocus(), this, this.disposables); this.disposables.add(resourceActionRunner); + const historyItemGroupActionRunner = new HistoryItemGroupActionRunner(); + historyItemGroupActionRunner.onWillRun(() => this.tree.domFocus(), this, this.disposables); + this.disposables.add(historyItemGroupActionRunner); + const historyItemActionRunner = new HistoryItemActionRunner(); historyItemActionRunner.onWillRun(() => this.tree.domFocus(), this, this.disposables); this.disposables.add(historyItemActionRunner); @@ -2728,7 +2783,7 @@ export class SCMViewPane extends ViewPane { this.instantiationService.createInstance(RepositoryRenderer, MenuId.SCMTitle, getActionViewItemProvider(this.instantiationService)), this.instantiationService.createInstance(ResourceGroupRenderer, getActionViewItemProvider(this.instantiationService)), this.instantiationService.createInstance(ResourceRenderer, () => this.viewMode, this.listLabels, getActionViewItemProvider(this.instantiationService), resourceActionRunner), - this.instantiationService.createInstance(HistoryItemGroupRenderer), + this.instantiationService.createInstance(HistoryItemGroupRenderer, historyItemGroupActionRunner), this.instantiationService.createInstance(HistoryItemRenderer, historyItemActionRunner, getActionViewItemProvider(this.instantiationService)), this.instantiationService.createInstance(HistoryItemChangeRenderer, () => this.viewMode, this.listLabels), this.instantiationService.createInstance(SeparatorRenderer) diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index b9ac2eed994..5037cc03e3f 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -10,6 +10,9 @@ import { IMenu } from 'vs/platform/actions/common/actions'; import { ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; export interface ISCMHistoryProviderMenus { + readonly incomingHistoryItemGroupMenu: IMenu; + readonly outgoingHistoryItemGroupMenu: IMenu; + getHistoryItemMenu(historyItem: SCMHistoryItemTreeElement): IMenu; } diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index df25f3c2008..ead97697772 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -150,6 +150,18 @@ const apiMenus: IAPIMenu[] = [ description: localize('menus.input', "The Source Control input box menu"), proposed: 'contribSourceControlInputBoxMenu' }, + { + key: 'scm/incomingChanges', + id: MenuId.SCMIncomingChanges, + description: localize('menus.incomingChanges', "The Source Control incoming changes menu"), + proposed: 'contribSourceControlHistoryItemGroupMenu' + }, + { + key: 'scm/outgoingChanges', + id: MenuId.SCMOutgoingChanges, + description: localize('menus.outgoingChanges', "The Source Control outgoing changes menu"), + proposed: 'contribSourceControlHistoryItemGroupMenu' + }, { key: 'scm/incomingChanges/allChanges/context', id: MenuId.SCMIncomingChangesAllChangesContext, diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 225fd886589..ce32f5bf309 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -32,6 +32,7 @@ export const allApiProposals = Object.freeze({ contribNotebookStaticPreloads: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribNotebookStaticPreloads.d.ts', contribRemoteHelp: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribRemoteHelp.d.ts', contribShareMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts', + contribSourceControlHistoryItemGroupMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemGroupMenu.d.ts', contribSourceControlHistoryItemMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemMenu.d.ts', contribSourceControlInputBoxMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlInputBoxMenu.d.ts', contribStatusBarItems: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribStatusBarItems.d.ts', diff --git a/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemGroupMenu.d.ts b/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemGroupMenu.d.ts new file mode 100644 index 00000000000..a67c208736e --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemGroupMenu.d.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `scm/incomingChanges`-menu contribution point +// empty placeholder declaration for the `scm/outgoingChanges`-menu contribution point +// https://github.com/microsoft/vscode/issues/201997 From 834e74297f8d607f2104be32ae79316862834aca Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Thu, 18 Jan 2024 13:40:02 -0800 Subject: [PATCH 0450/1897] allow contributed range of higlight for code actions (#202197) * groundwork for segttings * laying groundowrk for new API * more groundwork, api fixed * cleanup * more cleanup * added updates * fixed spacing --- src/vs/editor/common/languages.ts | 1 + .../codeAction/browser/codeActionController.ts | 7 ++++++- src/vs/monaco.d.ts | 1 + src/vs/workbench/api/common/extHost.protocol.ts | 1 + .../api/common/extHostLanguageFeatures.ts | 4 ++++ .../extensions/common/extensionsApiProposals.ts | 1 + .../vscode.proposed.codeActionRanges.d.ts | 17 +++++++++++++++++ 7 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/vscode-dts/vscode.proposed.codeActionRanges.d.ts diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 9b3613efc66..43d17fb0609 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -749,6 +749,7 @@ export interface CodeAction { isPreferred?: boolean; isAI?: boolean; disabled?: string; + ranges?: IRange[]; } export const enum CodeActionTriggerType { diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index 81b765253ab..57aa30ff33f 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -279,7 +279,12 @@ export class CodeActionController extends Disposable implements IEditorContribut return { canPreview: !!action.action.edit?.edits.length }; }, onFocus: (action: CodeActionItem | undefined) => { - if (action && action.action.diagnostics) { + // If provider contributes ranges, then highlight contributed range over diagnostic range. + if (action && action.action.ranges) { + currentDecorations.clear(); + const decorations: IModelDeltaDecoration[] = action.action.ranges.map(range => ({ range, options: CodeActionController.DECORATION })); + currentDecorations.set(decorations); + } else if (action && action.action.diagnostics) { currentDecorations.clear(); const decorations: IModelDeltaDecoration[] = action.action.diagnostics.map(diagnostic => ({ range: diagnostic, options: CodeActionController.DECORATION })); currentDecorations.set(decorations); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index cea289bcfda..ed1b548900f 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7102,6 +7102,7 @@ declare namespace monaco.languages { isPreferred?: boolean; isAI?: boolean; disabled?: string; + ranges?: IRange[]; } export enum CodeActionTriggerType { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f34388f4875..b345c62cce8 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1992,6 +1992,7 @@ export interface ICodeActionDto { isPreferred?: boolean; isAI?: boolean; disabled?: string; + ranges?: IRange[]; } export interface ICodeActionListDto { diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 3190f3eed84..9e35d5c766e 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -474,6 +474,9 @@ class CodeActionAdapter { } } + // Ensures that this is either a Range[] or an empty array so we don't get Array + const range = candidate.ranges ?? []; + // new school: convert code action actions.push({ cacheId: [cacheId, i], @@ -484,6 +487,7 @@ class CodeActionAdapter { kind: candidate.kind && candidate.kind.value, isPreferred: candidate.isPreferred, isAI: isProposedApiEnabled(this._extension, 'codeActionAI') ? candidate.isAI : false, + ranges: isProposedApiEnabled(this._extension, 'codeActionRanges') ? coalesce(range.map(typeConvert.Range.from)) : undefined, disabled: candidate.disabled?.reason }); } diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index ce32f5bf309..669bff233f9 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -17,6 +17,7 @@ export const allApiProposals = Object.freeze({ chatRequestAccess: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts', chatTab: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatTab.d.ts', codeActionAI: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionAI.d.ts', + codeActionRanges: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts', codiconDecoration: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts', commentReactor: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentReactor.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.codeActionRanges.d.ts b/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts new file mode 100644 index 00000000000..704208454d7 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * 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 CodeAction { + /** + * + * The range to which this Code Action applies to, which will be highlighted. + * + * Ex: A refactoring action will highlight the range of text that will be affected. + */ + ranges?: Range[]; + } +} From 3c8e0fc16d21da7bb43abffd37720d93bdd644a3 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 18 Jan 2024 19:26:36 -0300 Subject: [PATCH 0451/1897] Get rid of chat progress Task (#202777) --- .../api/common/extHostChatAgents2.ts | 15 +------------ .../api/common/extHostTypeConverters.ts | 4 +--- .../vscode.proposed.chatAgents2.d.ts | 21 +------------------ 3 files changed, 3 insertions(+), 37 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 5a80b2edbf6..0a835a965e1 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -98,20 +98,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } if ('placeholder' in progress && 'resolvedContent' in progress) { - const resolvedContent = Promise.all([this._proxy.$handleProgressChunk(request.requestId, convertedProgress), progress.resolvedContent]); - raceCancellation(resolvedContent, token).then(res => { - if (!res) { - return; /* Cancelled */ - } - const [progressHandle, progressContent] = res; - const convertedContent = typeConvert.ChatResponseProgress.from(agent.extension, progressContent); - if (!convertedContent) { - this._logService.error('Unknown progress type: ' + JSON.stringify(progressContent)); - return; - } - - this._proxy.$handleProgressChunk(request.requestId, convertedContent, progressHandle ?? undefined); - }); + // Ignore for now, this is the deleted Task type } else { this._proxy.$handleProgressChunk(request.requestId, convertedProgress); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 1a07e6b23f2..62d20bbea37 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2320,9 +2320,7 @@ export namespace InteractiveEditorResponseFeedbackKind { export namespace ChatResponseProgress { export function from(extension: IExtensionDescription, progress: vscode.ChatAgentExtendedProgress): extHostProtocol.IChatProgressDto | undefined { - if ('placeholder' in progress && 'resolvedContent' in progress) { - return { content: progress.placeholder, kind: 'asyncContent' } satisfies extHostProtocol.IChatAsyncContentDto; - } else if ('markdownContent' in progress) { + if ('markdownContent' in progress) { checkProposedApiEnabled(extension, 'chatAgents2Additions'); return { content: MarkdownString.from(progress.markdownContent), kind: 'markdownContent' }; } else if ('content' in progress) { diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 12166a4f50c..e303c682925 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -270,8 +270,7 @@ declare module 'vscode' { export type ChatAgentContentProgress = | ChatAgentContent | ChatAgentFileTree - | ChatAgentInlineContentReference - | ChatAgentTask; + | ChatAgentInlineContentReference; export type ChatAgentMetadataProgress = | ChatAgentUsedContext @@ -322,24 +321,6 @@ declare module 'vscode' { content: string; } - /** - * Represents a piece of the chat response's content that is resolved asynchronously. It is rendered immediately with a placeholder, - * which is replaced once the full content is available. - */ - export interface ChatAgentTask { - /** - * The markdown string to be rendered immediately. - */ - placeholder: string; - - /** - * A Thenable resolving to the real content. The placeholder will be replaced with this content once it's available. - */ - // TODO@API Should this be an async iterable or progress instance instead - // TODO@API Should this include more inline-renderable items like `ChatAgentInlineContentReference` - resolvedContent: Thenable; - } - /** * Represents a tree, such as a file and directory structure, rendered in the chat response. */ From 0e51e7161908bfc78efacadf7cb01db911fc7d59 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 18 Jan 2024 15:18:39 -0800 Subject: [PATCH 0452/1897] Pick up latest katex (#202776) --- extensions/markdown-math/package.json | 2 +- extensions/markdown-math/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/markdown-math/package.json b/extensions/markdown-math/package.json index 85d09cbc9dc..44b442b3df4 100644 --- a/extensions/markdown-math/package.json +++ b/extensions/markdown-math/package.json @@ -99,7 +99,7 @@ "build-notebook": "node ./esbuild" }, "dependencies": { - "@vscode/markdown-it-katex": "^1.0.2" + "@vscode/markdown-it-katex": "^1.0.3" }, "devDependencies": { "@types/markdown-it": "^0.0.0", diff --git a/extensions/markdown-math/yarn.lock b/extensions/markdown-math/yarn.lock index e49c27e7bf8..38e50260d03 100644 --- a/extensions/markdown-math/yarn.lock +++ b/extensions/markdown-math/yarn.lock @@ -12,10 +12,10 @@ resolved "https://registry.yarnpkg.com/@types/vscode-notebook-renderer/-/vscode-notebook-renderer-1.72.0.tgz#8943dc3cef0ced2dfb1e04c0a933bd289e7d5199" integrity sha512-5iTjb39DpLn03ULUwrDR3L2Dy59RV4blSUHy0oLdQuIY11PhgWO4mXIcoFS0VxY1GZQ4IcjSf3ooT2Jrrcahnw== -"@vscode/markdown-it-katex@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@vscode/markdown-it-katex/-/markdown-it-katex-1.0.2.tgz#27ba579fa3896b2944b71209dd30d0f983983f11" - integrity sha512-QY/OnOHPTqc8tQoCoAjVblILX4yE6xGZHKODtiTKqA328OXra+lSpeJO5Ouo9AAvrs9AwcCLz6xvW3zwcsPBQg== +"@vscode/markdown-it-katex@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@vscode/markdown-it-katex/-/markdown-it-katex-1.0.3.tgz#5364e4dbcb0f7e7fd2fdab3847ba5d6b0c3ce9d9" + integrity sha512-a8ppdac0CG2lAQC6E6lT8dxmXkUk9gRtYNtILx31FyrPEwj875AAHc6tpRGeJBpWMpiMtcvz7ymWYBwYgxuFmw== dependencies: katex "^0.16.4" From 42dea84bda99d42f018a21fa3f969216e0c4c262 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 18 Jan 2024 15:30:26 -0800 Subject: [PATCH 0453/1897] testing: close coverage -> clear coverage, clear if run with no coverage (#202783) Fixes #202713 Fixes #202712 --- .../contrib/testing/browser/testExplorerActions.ts | 8 ++++---- src/vs/workbench/contrib/testing/common/constants.ts | 2 +- .../contrib/testing/common/testCoverageService.ts | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index e932cb450df..b3cd0a1f306 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -1611,11 +1611,11 @@ export class CancelTestRefreshAction extends Action2 { } } -export class CloseCoverage extends Action2 { +export class CleareCoverage extends Action2 { constructor() { super({ - id: TestCommandId.CoverageClose, - title: localize2('testing.closeCoverage', 'Close Coverage'), + id: TestCommandId.CoverageClear, + title: localize2('testing.clearCoverage', 'Clear Coverage'), icon: widgetClose, category, menu: [{ @@ -1665,7 +1665,7 @@ export const allTestActions = [ CancelTestRefreshAction, CancelTestRunAction, ClearTestResultsAction, - CloseCoverage, + CleareCoverage, CollapseAllAction, ConfigureTestProfilesAction, ContinuousRunTestAction, diff --git a/src/vs/workbench/contrib/testing/common/constants.ts b/src/vs/workbench/contrib/testing/common/constants.ts index d05d75c4d65..35d9eb5e552 100644 --- a/src/vs/workbench/contrib/testing/common/constants.ts +++ b/src/vs/workbench/contrib/testing/common/constants.ts @@ -62,7 +62,7 @@ export const enum TestCommandId { CoverageAtCursor = 'testing.coverageAtCursor', CoverageByUri = 'testing.coverage.uri', CoverageViewChangeSorting = 'testing.coverageViewChangeSorting', - CoverageClose = 'testing.coverage.close', + CoverageClear = 'testing.coverage.close', CoverageCurrentFile = 'testing.coverageCurrentFile', CoverageLastRun = 'testing.coverageLastRun', CoverageSelectedAction = 'testing.coverageSelected', diff --git a/src/vs/workbench/contrib/testing/common/testCoverageService.ts b/src/vs/workbench/contrib/testing/common/testCoverageService.ts index e9278f7739f..57c0832fdfd 100644 --- a/src/vs/workbench/contrib/testing/common/testCoverageService.ts +++ b/src/vs/workbench/contrib/testing/common/testCoverageService.ts @@ -60,6 +60,8 @@ export class TestCoverageService extends Disposable implements ITestCoverageServ const coverage = evt.completed.tasks.find(t => t.coverage.get()); if (coverage) { this.openCoverage(coverage, false); + } else { + this.closeCoverage(); } } else if ('removed' in evt && this.selected.get()) { const taskId = this.selected.get()?.fromTaskId; From 76e1ccf4c073b7aacafa24494452a2065f37914b Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 18 Jan 2024 15:31:44 -0800 Subject: [PATCH 0454/1897] debug: initial visualization extension points (#202775) wip --- .../platform/extensions/common/extensions.ts | 6 + .../api/browser/mainThreadDebugService.ts | 28 ++- .../workbench/api/common/extHost.api.impl.ts | 5 + .../workbench/api/common/extHost.protocol.ts | 8 +- .../api/common/extHostDebugService.ts | 154 ++++++++++++- src/vs/workbench/api/common/extHostTypes.ts | 7 + .../workbench/api/node/extHostDebugService.ts | 4 +- .../contrib/debug/browser/baseDebugView.ts | 12 +- .../debug/browser/debug.contribution.ts | 2 + .../contrib/debug/browser/variablesView.ts | 75 ++++-- .../workbench/contrib/debug/common/debug.ts | 50 +++- .../contrib/debug/common/debugContext.ts | 26 +++ .../contrib/debug/common/debugModel.ts | 6 +- .../contrib/debug/common/debugVisualizers.ts | 213 ++++++++++++++++++ .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.debugVisualization.d.ts | 100 ++++++++ 16 files changed, 658 insertions(+), 39 deletions(-) create mode 100644 src/vs/workbench/contrib/debug/common/debugContext.ts create mode 100644 src/vs/workbench/contrib/debug/common/debugVisualizers.ts create mode 100644 src/vscode-dts/vscode.proposed.debugVisualization.d.ts diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 222f6b91861..413c1db06f1 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -162,6 +162,11 @@ export interface INotebookRendererContribution { readonly mimeTypes: string[]; } +export interface IDebugVisualizationContribution { + readonly id: string; + readonly when: string; +} + export interface ITranslation { id: string; path: string; @@ -199,6 +204,7 @@ export interface IExtensionContributions { startEntries?: IStartEntry[]; readonly notebooks?: INotebookEntry[]; readonly notebookRenderer?: INotebookRendererContribution[]; + readonly debugVisualizers?: IDebugVisualizationContribution[]; } export interface IExtensionCapabilities { diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index c98dd8af072..283c0e78372 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DisposableMap, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI as uri, UriComponents } from 'vs/base/common/uri'; -import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDataBreakpoint, IDebugSessionOptions, IInstructionBreakpoint, DebugConfigurationProviderTriggerKind } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDataBreakpoint, IDebugSessionOptions, IInstructionBreakpoint, DebugConfigurationProviderTriggerKind, IDebugVisualization } from 'vs/workbench/contrib/debug/common/debug'; import { ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto, IDataBreakpointDto, IStartDebuggingOptions, IDebugConfiguration, IThreadFocusDto, IStackFrameFocusDto @@ -16,6 +16,8 @@ import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstract import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { convertToVSCPaths, convertToDAPaths, isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils'; import { ErrorNoTelemetry } from 'vs/base/common/errors'; +import { IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @extHostNamedCustomer(MainContext.MainThreadDebugService) export class MainThreadDebugService implements MainThreadDebugServiceShape, IDebugAdapterFactory { @@ -27,10 +29,12 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb private readonly _debugConfigurationProviders: Map; private readonly _debugAdapterDescriptorFactories: Map; private readonly _extHostKnownSessions: Set; + private readonly _visualizerHandles = new Map(); constructor( extHostContext: IExtHostContext, - @IDebugService private readonly debugService: IDebugService + @IDebugService private readonly debugService: IDebugService, + @IDebugVisualizerService private readonly visualizerService: IDebugVisualizerService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDebugService); @@ -108,6 +112,24 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb this.sendBreakpointsAndListen(); } + $registerDebugVisualizer(extensionId: string, id: string): void { + const handle = this.visualizerService.register({ + extensionId: new ExtensionIdentifier(extensionId), + id, + disposeDebugVisualizers: ids => this._proxy.$disposeDebugVisualizers(ids), + executeDebugVisualizerCommand: id => this._proxy.$executeDebugVisualizerCommand(id), + provideDebugVisualizers: (context, token) => this._proxy.$provideDebugVisualizers(extensionId, id, context, token).then(r => r.map(IDebugVisualization.deserialize)), + resolveDebugVisualizer: (viz, token) => this._proxy.$resolveDebugVisualizer(viz.id, token), + }); + this._visualizerHandles.set(`${extensionId}/${id}`, handle); + } + + $unregisterDebugVisualizer(extensionId: string, id: string): void { + const key = `${extensionId}/${id}`; + this._visualizerHandles.get(key)?.dispose(); + this._visualizerHandles.delete(key); + } + private sendBreakpointsAndListen(): void { // set up a handler to send more this._toDispose.add(this.debugService.getModel().onDidChangeBreakpoints(e => { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index dd36b43f34f..d3982e71c82 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1211,6 +1211,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I get stackFrameFocus() { return extHostDebugService.stackFrameFocus; }, + registerDebugVisualizationProvider(id, provider) { + checkProposedApiEnabled(extension, 'debugVisualization'); + return extHostDebugService.registerDebugVisualizationProvider(extension, id, provider); + }, onDidStartDebugSession(listener, thisArg?, disposables?) { return _asExtensionEvent(extHostDebugService.onDidStartDebugSession)(listener, thisArg, disposables); }, @@ -1465,6 +1469,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I DebugAdapterServer: extHostTypes.DebugAdapterServer, DebugConfigurationProviderTriggerKind: DebugConfigurationProviderTriggerKind, DebugConsoleMode: extHostTypes.DebugConsoleMode, + DebugVisualization: extHostTypes.DebugVisualization, DecorationRangeBehavior: extHostTypes.DecorationRangeBehavior, Diagnostic: extHostTypes.Diagnostic, DiagnosticRelatedInformation: extHostTypes.DiagnosticRelatedInformation, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b345c62cce8..21261315689 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -55,7 +55,7 @@ import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/c import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; import { IChatAsyncContent, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; -import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug'; +import { DebugConfigurationProviderTriggerKind, MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext } from 'vs/workbench/contrib/debug/common/debug'; import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; @@ -1562,6 +1562,8 @@ export interface MainThreadDebugServiceShape extends IDisposable { $appendDebugConsole(value: string): void; $registerBreakpoints(breakpoints: Array): Promise; $unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[], dataBreakpointIds: string[]): Promise; + $registerDebugVisualizer(extensionId: string, id: string): void; + $unregisterDebugVisualizer(extensionId: string, id: string): void; } export interface IOpenUriOptions { @@ -2352,6 +2354,10 @@ export interface ExtHostDebugServiceShape { $acceptBreakpointsDelta(delta: IBreakpointsDeltaDto): void; $acceptDebugSessionNameChanged(session: IDebugSessionDto, name: string): void; $acceptStackFrameFocus(focus: IThreadFocusDto | IStackFrameFocusDto | undefined): void; + $provideDebugVisualizers(extensionId: string, id: string, context: IDebugVisualizationContext, token: CancellationToken): Promise; + $resolveDebugVisualizer(id: number, token: CancellationToken): Promise; + $executeDebugVisualizerCommand(id: number): Promise; + $disposeDebugVisualizers(ids: number[]): void; } diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index 8eacd6f83b2..ae9ba7e1b42 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -7,7 +7,7 @@ import { asPromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ISignService } from 'vs/platform/sign/common/sign'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -15,16 +15,19 @@ import { DebugSessionUUID, ExtHostDebugServiceShape, IBreakpointsDeltaDto, IThre import { IExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import { Breakpoint, DataBreakpoint, DebugAdapterExecutable, DebugAdapterInlineImplementation, DebugAdapterNamedPipeServer, DebugAdapterServer, DebugConsoleMode, Disposable, FunctionBreakpoint, Location, Position, setBreakpointId, SourceBreakpoint, ThreadFocus, StackFrameFocus } from 'vs/workbench/api/common/extHostTypes'; +import { Breakpoint, DataBreakpoint, DebugAdapterExecutable, DebugAdapterInlineImplementation, DebugAdapterNamedPipeServer, DebugAdapterServer, DebugConsoleMode, Disposable, FunctionBreakpoint, Location, Position, setBreakpointId, SourceBreakpoint, ThreadFocus, StackFrameFocus, ThemeIcon } from 'vs/workbench/api/common/extHostTypes'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; -import { IAdapterDescriptor, IConfig, IDebugAdapter, IDebugAdapterExecutable, IDebugAdapterNamedPipeServer, IDebugAdapterServer, IDebuggerContribution } from 'vs/workbench/contrib/debug/common/debug'; +import { MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugAdapter, IDebugAdapterExecutable, IDebugAdapterNamedPipeServer, IDebugAdapterServer, IDebugVisualization, IDebugVisualizationContext, IDebuggerContribution, DebugVisualizationType } from 'vs/workbench/contrib/debug/common/debug'; import { convertToDAPaths, convertToVSCPaths, isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import type * as vscode from 'vscode'; import { IExtHostConfiguration } from '../common/extHostConfiguration'; import { IExtHostVariableResolverProvider } from './extHostVariableResolverService'; +import { toDisposable } from 'vs/base/common/lifecycle'; +import { ThemeIcon as ThemeIconUtils } from 'vs/base/common/themables'; +import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; export const IExtHostDebugService = createDecorator('IExtHostDebugService'); @@ -50,6 +53,7 @@ export interface IExtHostDebugService extends ExtHostDebugServiceShape { registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider, trigger: vscode.DebugConfigurationProviderTriggerKind): vscode.Disposable; registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable; registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable; + registerDebugVisualizationProvider(extension: IExtensionDescription, id: string, provider: vscode.DebugVisualizationProvider): vscode.Disposable; asDebugSourceUri(source: vscode.DebugProtocolSource, session?: vscode.DebugSession): vscode.Uri; } @@ -96,9 +100,13 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E private _debugAdapters: Map; private _debugAdaptersTrackers: Map; + private readonly _debugVisualizationProviders = new Map(); private _signService: ISignService | undefined; + private readonly _visualizers = new Map(); + private _visualizerIdCounter = 0; + constructor( @IExtHostRpcService extHostRpcService: IExtHostRpcService, @IExtHostWorkspace protected _workspaceService: IExtHostWorkspace, @@ -106,6 +114,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E @IExtHostConfiguration protected _configurationService: IExtHostConfiguration, @IExtHostEditorTabs protected _editorTabs: IExtHostEditorTabs, @IExtHostVariableResolverProvider private _variableResolver: IExtHostVariableResolverProvider, + @IExtHostCommands private _commands: IExtHostCommands, ) { this._configProviderHandleCounter = 0; this._configProviders = []; @@ -209,6 +218,96 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E return result; } + public async $resolveDebugVisualizer(id: number, token: CancellationToken): Promise { + const visualizer = this._visualizers.get(id); + if (!visualizer) { + throw new Error(`No debug visualizer found with id '${id}'`); + } + + let { v, provider } = visualizer; + if (!v.visualization) { + v = await provider.resolveDebugVisualization?.(v, token) || v; + visualizer.v = v; + } + + if (!v.visualization) { + throw new Error(`No visualization returned from resolveDebugVisualization in '${provider}'`); + } + + return this.serializeVisualization(v.visualization)!; + } + + public async $executeDebugVisualizerCommand(id: number): Promise { + const visualizer = this._visualizers.get(id); + if (!visualizer) { + throw new Error(`No debug visualizer found with id '${id}'`); + } + + const command = visualizer.v.visualization; + if (command && 'command' in command) { + this._commands.executeCommand(command.command, ...(command.arguments || [])); + } + } + + public async $provideDebugVisualizers(extensionId: string, id: string, context: IDebugVisualizationContext, token: CancellationToken): Promise { + const session = this._debugSessions.get(context.sessionId); + const key = this.extensionVisKey(extensionId, id); + const provider = this._debugVisualizationProviders.get(key); + if (!session || !provider) { + return []; // probably ended in the meantime + } + + const visualizations = await provider.provideDebugVisualization({ + session, + variable: context.variable, + containerId: context.containerId, + frameId: context.frameId, + threadId: context.threadId, + }, token); + + if (!visualizations) { + return []; + } + + return visualizations.map(v => { + const id = ++this._visualizerIdCounter; + this._visualizers.set(id, { v, provider }); + const icon = v.iconPath ? this.getIconPathOrClass(v.iconPath) : undefined; + return { + id, + name: v.name, + iconClass: icon?.iconClass, + iconPath: icon?.iconPath, + visualization: this.serializeVisualization(v.visualization), + }; + }); + } + + public $disposeDebugVisualizers(ids: number[]): void { + for (const id of ids) { + this._visualizers.delete(id); + } + } + + public registerDebugVisualizationProvider(manifest: IExtensionDescription, id: string, provider: vscode.DebugVisualizationProvider): vscode.Disposable { + if (!manifest.contributes?.debugVisualizers?.some(r => r.id === id)) { + throw new Error(`Extensions may only call registerDebugVisualizationProvider() for renderers they contribute (got ${id})`); + } + + const extensionId = ExtensionIdentifier.toKey(manifest.identifier); + const key = this.extensionVisKey(extensionId, id); + if (this._debugVisualizationProviders.has(key)) { + throw new Error(`A debug visualization provider with id '${id}' is already registered`); + } + + this._debugVisualizationProviders.set(key, provider); + this._debugServiceProxy.$registerDebugVisualizer(extensionId, id); + return toDisposable(() => { + this._debugServiceProxy.$unregisterDebugVisualizer(extensionId, id); + this._debugVisualizationProviders.delete(id); + }); + } + public addBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise { // filter only new breakpoints const breakpoints = breakpoints0.filter(bp => { @@ -858,6 +957,50 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E } return Promise.resolve(undefined); } + + private extensionVisKey(extensionId: string, id: string) { + return `${extensionId}\0${id}`; + } + + private serializeVisualization(viz: vscode.DebugVisualization['visualization']): MainThreadDebugVisualization | undefined { + if (!viz) { + return undefined; + } + + if ('title' in viz && 'command' in viz) { + return { type: DebugVisualizationType.Command }; + } + + throw new Error('Unsupported debug visualization type'); + } + + private getIconPathOrClass(icon: vscode.DebugVisualization['iconPath']) { + const iconPathOrIconClass = this.getIconUris(icon); + let iconPath: { dark: URI; light?: URI | undefined } | undefined; + let iconClass: string | undefined; + if ('id' in iconPathOrIconClass) { + iconClass = ThemeIconUtils.asClassName(iconPathOrIconClass); + } else { + iconPath = iconPathOrIconClass; + } + + return { + iconPath, + iconClass + }; + } + + private getIconUris(iconPath: vscode.DebugVisualization['iconPath']): { dark: URI; light?: URI } | { id: string } { + if (iconPath instanceof ThemeIcon) { + return { id: iconPath.id }; + } + const dark = typeof iconPath === 'object' && 'dark' in iconPath ? iconPath.dark : iconPath; + const light = typeof iconPath === 'object' && 'light' in iconPath ? iconPath.light : iconPath; + return { + dark: (typeof dark === 'string' ? URI.file(dark) : dark) as URI, + light: (typeof light === 'string' ? URI.file(light) : light) as URI, + }; + } } export class ExtHostDebugSession implements vscode.DebugSession { @@ -1013,8 +1156,9 @@ export class WorkerExtHostDebugService extends ExtHostDebugServiceBase { @IExtHostExtensionService extensionService: IExtHostExtensionService, @IExtHostConfiguration configurationService: IExtHostConfiguration, @IExtHostEditorTabs editorTabs: IExtHostEditorTabs, - @IExtHostVariableResolverProvider variableResolver: IExtHostVariableResolverProvider + @IExtHostVariableResolverProvider variableResolver: IExtHostVariableResolverProvider, + @IExtHostCommands commands: IExtHostCommands ) { - super(extHostRpcService, workspaceService, extensionService, configurationService, editorTabs, variableResolver); + super(extHostRpcService, workspaceService, extensionService, configurationService, editorTabs, variableResolver, commands); } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 4c45b385fa8..1307d23e705 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3448,6 +3448,13 @@ export enum DebugConsoleMode { MergeWithParent = 1 } +export class DebugVisualization { + iconPath?: URI | { light: URI; dark: URI } | ThemeIcon; + visualization?: vscode.Command | vscode.TreeDataProvider; + + constructor(public name: string) { } +} + //#endregion @es5ClassCompat diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 55cd060d364..07bb7582839 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -26,6 +26,7 @@ import { hasChildProcesses, prepareCommand } from 'vs/workbench/contrib/debug/no import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import type * as vscode from 'vscode'; import { ExtHostConfigProvider, IExtHostConfiguration } from '../common/extHostConfiguration'; +import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; export class ExtHostDebugService extends ExtHostDebugServiceBase { @@ -42,8 +43,9 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { @IExtHostTerminalService private _terminalService: IExtHostTerminalService, @IExtHostEditorTabs editorTabs: IExtHostEditorTabs, @IExtHostVariableResolverProvider variableResolver: IExtHostVariableResolverProvider, + @IExtHostCommands commands: IExtHostCommands, ) { - super(extHostRpcService, workspaceService, extensionService, configurationService, editorTabs, variableResolver); + super(extHostRpcService, workspaceService, extensionService, configurationService, editorTabs, variableResolver, commands); } protected override createDebugAdapter(adapter: IAdapterDescriptor, session: ExtHostDebugSession): AbstractDebugAdapter | undefined { diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index a3e99830ac3..058167334d8 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -138,7 +138,7 @@ export interface IExpressionTemplateData { value: HTMLSpanElement; inputBoxContainer: HTMLElement; actionBar?: ActionBar; - elementDisposable: IDisposable[]; + elementDisposable: DisposableStore; templateDisposable: IDisposable; label: HighlightedLabel; lazyButton: HTMLElement; @@ -174,7 +174,7 @@ export abstract class AbstractExpressionsRenderer implements IT actionBar = templateDisposable.add(new ActionBar(expression)); } - const template: IExpressionTemplateData = { expression, name, value, label, inputBoxContainer, actionBar, elementDisposable: [], templateDisposable, lazyButton, currentElement: undefined }; + const template: IExpressionTemplateData = { expression, name, value, label, inputBoxContainer, actionBar, elementDisposable: new DisposableStore(), templateDisposable, lazyButton, currentElement: undefined }; templateDisposable.add(dom.addDisposableListener(lazyButton, dom.EventType.CLICK, () => { if (template.currentElement) { @@ -188,6 +188,7 @@ export abstract class AbstractExpressionsRenderer implements IT public abstract renderElement(node: ITreeNode, index: number, data: IExpressionTemplateData): void; protected renderExpressionElement(element: IExpression, node: ITreeNode, data: IExpressionTemplateData): void { + data.elementDisposable.clear(); data.currentElement = element; this.renderExpression(node.element, data, createMatches(node.filterData)); if (data.actionBar) { @@ -197,7 +198,7 @@ export abstract class AbstractExpressionsRenderer implements IT if (element === selectedExpression?.expression || (element instanceof Variable && element.errorMessage)) { const options = this.getInputBoxOptions(element, !!selectedExpression?.settingWatch); if (options) { - data.elementDisposable.push(this.renderInputBox(data.name, data.value, data.inputBoxContainer, options)); + data.elementDisposable.add(this.renderInputBox(data.name, data.value, data.inputBoxContainer, options)); } } } @@ -259,12 +260,11 @@ export abstract class AbstractExpressionsRenderer implements IT protected renderActionBar?(actionBar: ActionBar, expression: IExpression, data: IExpressionTemplateData): void; disposeElement(node: ITreeNode, index: number, templateData: IExpressionTemplateData): void { - dispose(templateData.elementDisposable); - templateData.elementDisposable = []; + templateData.elementDisposable.clear(); } disposeTemplate(templateData: IExpressionTemplateData): void { - dispose(templateData.elementDisposable); + templateData.elementDisposable.dispose(); templateData.templateDisposable.dispose(); } } diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 7324f4bedfe..76ca42eeadd 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -51,6 +51,7 @@ import { WelcomeView } from 'vs/workbench/contrib/debug/browser/welcomeView'; import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CALLSTACK_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_UX, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_HAS_DEBUGGED, CONTEXT_IN_DEBUG_MODE, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_WATCH_ITEM_TYPE, DEBUG_PANEL_ID, DISASSEMBLY_VIEW_ID, EDITOR_CONTRIBUTION_ID, IDebugService, INTERNAL_CONSOLE_OPTIONS_SCHEMA, LOADED_SCRIPTS_VIEW_ID, REPL_VIEW_ID, State, VARIABLES_VIEW_ID, VIEWLET_ID, WATCH_VIEW_ID, getStateLabel } from 'vs/workbench/contrib/debug/common/debug'; import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; import { DebugLifecycle } from 'vs/workbench/contrib/debug/common/debugLifecycle'; +import { DebugVisualizerService, IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -58,6 +59,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle const debugCategory = nls.localize('debugCategory', "Debug"); registerColors(); registerSingleton(IDebugService, DebugService, InstantiationType.Delayed); +registerSingleton(IDebugVisualizerService, DebugVisualizerService, InstantiationType.Delayed); // Register Debug Workbench Contributions Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugStatusContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index a0f887673ae..df67a66260a 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -10,12 +10,14 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; import { IAsyncDataSource, ITreeContextMenuEvent, ITreeMouseEvent, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; -import { IAction } from 'vs/base/common/actions'; +import { Action, IAction } from 'vs/base/common/actions'; import { coalesce } from 'vs/base/common/arrays'; import { RunOnceScheduler, timeout } from 'vs/base/common/async'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; -import { createMatches, FuzzyScore } from 'vs/base/common/filters'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { FuzzyScore, createMatches } from 'vs/base/common/filters'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -37,8 +39,10 @@ import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewl import { IViewDescriptorService } from 'vs/workbench/common/views'; import { AbstractExpressionsRenderer, IExpressionTemplateData, IInputBoxOptions, renderVariable, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; -import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT, CONTEXT_VARIABLES_FOCUSED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, CONTEXT_VARIABLE_IS_READONLY, IDataBreakpointInfoResponse, IDebugService, IExpression, IScope, IStackFrame, VARIABLES_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; -import { ErrorScope, Expression, getUriForDebugMemory, Scope, StackFrame, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; +import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_VARIABLES_FOCUSED, DebugVisualizationType, IDataBreakpointInfoResponse, IDebugService, IExpression, IScope, IStackFrame, VARIABLES_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { getContextForVariable } from 'vs/workbench/contrib/debug/common/debugContext'; +import { ErrorScope, Expression, Scope, StackFrame, Variable, getUriForDebugMemory } from 'vs/workbench/contrib/debug/common/debugModel'; +import { DebugVisualizer, IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -254,7 +258,7 @@ const getVariablesContext = (variable: Variable): IVariablesContext => ({ async function getContextForVariableMenuWithDataAccess(parentContext: IContextKeyService, variable: Variable) { const session = variable.getSession(); if (!session || !session.capabilities.supportsDataBreakpoints) { - return getContextForVariableMenu(parentContext, variable); + return getContextForVariableMenuBase(parentContext, variable); } const contextKeys: [string, unknown][] = []; @@ -280,25 +284,15 @@ async function getContextForVariableMenuWithDataAccess(parentContext: IContextKe } } - return getContextForVariableMenu(parentContext, variable, contextKeys); + return getContextForVariableMenuBase(parentContext, variable, contextKeys); } /** * Gets a context key overlay that has context for the given variable. */ -function getContextForVariableMenu(parentContext: IContextKeyService, variable: Variable, additionalContext: [string, unknown][] = []) { - const session = variable.getSession(); - const contextKeys: [string, unknown][] = [ - [CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT.key, variable.variableMenuContext || ''], - [CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT.key, !!variable.evaluateName], - [CONTEXT_CAN_VIEW_MEMORY.key, !!session?.capabilities.supportsReadMemoryRequest && variable.memoryReference !== undefined], - [CONTEXT_VARIABLE_IS_READONLY.key, !!variable.presentationHint?.attributes?.includes('readOnly') || variable.presentationHint?.lazy], - ...additionalContext, - ]; - +function getContextForVariableMenuBase(parentContext: IContextKeyService, variable: Variable, additionalContext: [string, unknown][] = []) { variableInternalContext = variable; - - return parentContext.createOverlay(contextKeys); + return getContextForVariable(parentContext, variable, additionalContext); } function isStackFrame(obj: any): obj is IStackFrame { @@ -410,6 +404,8 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { private readonly linkDetector: LinkDetector, @IMenuService private readonly menuService: IMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IDebugVisualizerService private readonly visualization: IDebugVisualizerService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, @IDebugService debugService: IDebugService, @IContextViewService contextViewService: IContextViewService, ) { @@ -452,9 +448,9 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { }; } - protected override renderActionBar(actionBar: ActionBar, expression: IExpression) { + protected override renderActionBar(actionBar: ActionBar, expression: IExpression, data: IExpressionTemplateData) { const variable = expression as Variable; - const contextKeyService = getContextForVariableMenu(this.contextKeyService, variable); + const contextKeyService = getContextForVariableMenuBase(this.contextKeyService, variable); const menu = this.menuService.createMenu(MenuId.DebugVariablesContext, contextKeyService); const primary: IAction[] = []; @@ -464,6 +460,43 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { actionBar.clear(); actionBar.context = context; actionBar.push(primary, { icon: true, label: false }); + + const cts = new CancellationTokenSource(); + data.elementDisposable.add(toDisposable(() => cts.dispose(true))); + this.visualization.getApplicableFor(expression, cts.token).then(result => { + data.elementDisposable.add(result); + + const actions = result.object.map(v => new Action('debugViz', v.name, v.iconClass || 'debug-viz-icon', undefined, this.useVisualizer(v, cts.token))); + if (actions.length === 0) { + // no-op + } else if (actions.length === 1) { + actionBar.push(actions[0], { icon: true, label: false }); + } else { + actionBar.push(new Action('debugViz', localize('useVisualizer', 'Visualize Variable...'), ThemeIcon.asClassName(Codicon.eye), undefined, () => this.pickVisualizer(actions, expression, data)), { icon: true, label: false }); + } + }); + } + + private pickVisualizer(actions: IAction[], expression: IExpression, data: IExpressionTemplateData) { + this.contextMenuService.showContextMenu({ + getAnchor: () => data.actionBar!.getContainer(), + getActions: () => actions, + }); + } + + private useVisualizer(viz: DebugVisualizer, token: CancellationToken) { + return async () => { + const resolved = await viz.resolve(token); + if (token.isCancellationRequested) { + return; + } + + if (resolved.type === DebugVisualizationType.Command) { + viz.execute(); + } else { + throw new Error('not implemented, yet'); + } + }; } } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 308b0ac9bc0..2bdd7fa756c 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -11,7 +11,7 @@ import { Event } from 'vs/base/common/event'; import { IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; import { IDisposable } from 'vs/base/common/lifecycle'; import severity from 'vs/base/common/severity'; -import { URI as uri } from 'vs/base/common/uri'; +import { URI, UriComponents, URI as uri } from 'vs/base/common/uri'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; @@ -84,6 +84,9 @@ export const CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED = new RawContextKey(' export const CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED = new RawContextKey('suspendDebuggeeSupported', false, { type: 'boolean', description: nls.localize('suspendDebuggeeSupported', "True when the focused session supports the suspend debuggee capability.") }); export const CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT = new RawContextKey('variableEvaluateNamePresent', false, { type: 'boolean', description: nls.localize('variableEvaluateNamePresent', "True when the focused variable has an 'evalauteName' field set.") }); export const CONTEXT_VARIABLE_IS_READONLY = new RawContextKey('variableIsReadonly', false, { type: 'boolean', description: nls.localize('variableIsReadonly', "True when the focused variable is read-only.") }); +export const CONTEXT_VARIABLE_VALUE = new RawContextKey('variableValue', false, { type: 'string', description: nls.localize('variableValue', "Value of the variable, present for debug visualization clauses.") }); +export const CONTEXT_VARIABLE_TYPE = new RawContextKey('variableType', false, { type: 'string', description: nls.localize('variableType', "Type of the variable, present for debug visualization clauses.") }); +export const CONTEXT_VARIABLE_NAME = new RawContextKey('variableName', false, { type: 'string', description: nls.localize('variableName', "Name of the variable, present for debug visualization clauses.") }); export const CONTEXT_EXCEPTION_WIDGET_VISIBLE = new RawContextKey('exceptionWidgetVisible', false, { type: 'boolean', description: nls.localize('exceptionWidgetVisible', "True when the exception widget is visible.") }); export const CONTEXT_MULTI_SESSION_REPL = new RawContextKey('multiSessionRepl', false, { type: 'boolean', description: nls.localize('multiSessionRepl', "True when there is more than 1 debug console.") }); export const CONTEXT_MULTI_SESSION_DEBUG = new RawContextKey('multiSessionDebug', false, { type: 'boolean', description: nls.localize('multiSessionDebug', "True when there is more than 1 active debug session.") }); @@ -1247,3 +1250,48 @@ export interface IReplConfiguration { export interface IReplOptions { readonly replConfiguration: IReplConfiguration; } + +export interface IDebugVisualizationContext { + variable: DebugProtocol.Variable; + containerId?: string; + frameId?: number; + threadId: number; + sessionId: string; +} + +export const enum DebugVisualizationType { + Command, + Tree, +} + +export type MainThreadDebugVisualization = { + type: DebugVisualizationType.Command; +}; // todo: tree + +export interface IDebugVisualization { + id: number; + name: string; + iconPath: { light?: URI; dark: URI } | undefined; + iconClass: string | undefined; + visualization: MainThreadDebugVisualization | undefined; +} + +export namespace IDebugVisualization { + export interface Serialized { + id: number; + name: string; + iconPath?: { light?: UriComponents; dark: UriComponents }; + iconClass?: string; + visualization?: MainThreadDebugVisualization; + } + + export const deserialize = (v: Serialized): IDebugVisualization => ({ + id: v.id, + name: v.name, + iconPath: v.iconPath && { light: URI.revive(v.iconPath.light), dark: URI.revive(v.iconPath.dark) }, + iconClass: v.iconClass, + visualization: v.visualization, + }); + + export const serialize = (visualizer: IDebugVisualization): Serialized => visualizer; +} diff --git a/src/vs/workbench/contrib/debug/common/debugContext.ts b/src/vs/workbench/contrib/debug/common/debugContext.ts new file mode 100644 index 00000000000..26a69533107 --- /dev/null +++ b/src/vs/workbench/contrib/debug/common/debugContext.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_DEBUG_TYPE } from 'vs/workbench/contrib/debug/common/debug'; +import { Variable } from 'vs/workbench/contrib/debug/common/debugModel'; + + +/** + * Gets a context key overlay that has context for the given variable. + */ +export function getContextForVariable(parentContext: IContextKeyService, variable: Variable, additionalContext: [string, unknown][] = []) { + const session = variable.getSession(); + const contextKeys: [string, unknown][] = [ + [CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT.key, variable.variableMenuContext || ''], + [CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT.key, !!variable.evaluateName], + [CONTEXT_CAN_VIEW_MEMORY.key, !!session?.capabilities.supportsReadMemoryRequest && variable.memoryReference !== undefined], + [CONTEXT_VARIABLE_IS_READONLY.key, !!variable.presentationHint?.attributes?.includes('readOnly') || variable.presentationHint?.lazy], + [CONTEXT_DEBUG_TYPE.key, session?.configuration.type], + ...additionalContext, + ]; + + return parentContext.createOverlay(contextKeys); +} diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index c4f5c811018..5a33aecfbeb 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -305,6 +305,10 @@ export class Variable extends ExpressionContainer implements IExpression { this.type = type; } + getThreadId() { + return this.threadId; + } + async setVariable(value: string, stackFrame: IStackFrame): Promise { if (!this.session) { return; @@ -354,7 +358,7 @@ export class Variable extends ExpressionContainer implements IExpression { export class Scope extends ExpressionContainer implements IScope { constructor( - stackFrame: IStackFrame, + public readonly stackFrame: IStackFrame, id: number, public readonly name: string, reference: number, diff --git a/src/vs/workbench/contrib/debug/common/debugVisualizers.ts b/src/vs/workbench/contrib/debug/common/debugVisualizers.ts new file mode 100644 index 00000000000..9a5cf6885c2 --- /dev/null +++ b/src/vs/workbench/contrib/debug/common/debugVisualizers.ts @@ -0,0 +1,213 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable, IReference, toDisposable } from 'vs/base/common/lifecycle'; +import { isDefined } from 'vs/base/common/types'; +import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ExtensionIdentifier, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { CONTEXT_VARIABLE_NAME, CONTEXT_VARIABLE_TYPE, CONTEXT_VARIABLE_VALUE, MainThreadDebugVisualization, IDebugVisualization, IDebugVisualizationContext, IExpression, IExpressionContainer } from 'vs/workbench/contrib/debug/common/debug'; +import { getContextForVariable } from 'vs/workbench/contrib/debug/common/debugContext'; +import { Scope, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; + +export const IDebugVisualizerService = createDecorator('debugVisualizerService'); + +interface VisualizerHandle { + id: string; + extensionId: ExtensionIdentifier; + provideDebugVisualizers(context: IDebugVisualizationContext, token: CancellationToken): Promise; + resolveDebugVisualizer(viz: IDebugVisualization, token: CancellationToken): Promise; + executeDebugVisualizerCommand(id: number): Promise; + disposeDebugVisualizers(ids: number[]): void; +} + +export class DebugVisualizer { + public get name() { + return this.viz.name; + } + + public get iconPath() { + return this.viz.iconPath; + } + + public get iconClass() { + return this.viz.iconClass; + } + + constructor(private readonly handle: VisualizerHandle, private readonly viz: IDebugVisualization) { } + + public async resolve(token: CancellationToken) { + return this.viz.visualization ??= await this.handle.resolveDebugVisualizer(this.viz, token); + } + + public async execute() { + await this.handle.executeDebugVisualizerCommand(this.viz.id); + } +} + +export interface IDebugVisualizerService { + _serviceBrand: undefined; + + /** + * Gets visualizers applicable for the given Expression. + */ + getApplicableFor(expression: IExpression, token: CancellationToken): Promise>; + + /** + * Registers a new visualizer (called from the main thread debug service) + */ + register(handle: VisualizerHandle): IDisposable; +} + +export class DebugVisualizerService implements IDebugVisualizerService { + declare public readonly _serviceBrand: undefined; + + private readonly handles = new Map(); + private readonly didActivate = new Map>(); + private registrations: { expr: ContextKeyExpression; id: string; extensionId: ExtensionIdentifier }[] = []; + + constructor( + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IExtensionService private readonly extensionService: IExtensionService, + @ILogService private readonly logService: ILogService, + ) { + visualizersExtensionPoint.setHandler((_, { added, removed }) => { + this.registrations = this.registrations.filter(r => + !removed.some(e => ExtensionIdentifier.equals(e.description.identifier, r.extensionId))); + added.forEach(e => this.processExtensionRegistration(e.description)); + }); + } + + /** @inheritdoc */ + public async getApplicableFor(variable: Variable, token: CancellationToken): Promise> { + const threadId = variable.getThreadId(); + if (threadId === undefined) { // an expression, not a variable + return { object: [], dispose: () => { } }; + } + + const context: IDebugVisualizationContext = { + sessionId: variable.getSession()?.getId() || '', + containerId: variable.parent.getId(), + threadId, + variable: { + name: variable.name, + value: variable.value, + type: variable.type, + evaluateName: variable.evaluateName, + variablesReference: variable.reference || 0, + indexedVariables: variable.indexedVariables, + memoryReference: variable.memoryReference, + namedVariables: variable.namedVariables, + presentationHint: variable.presentationHint, + } + }; + + for (let p: IExpressionContainer = variable; p instanceof Variable; p = p.parent) { + if (p.parent instanceof Scope) { + context.frameId = p.parent.stackFrame.frameId; + } + } + + const overlay = getContextForVariable(this.contextKeyService, variable, [ + [CONTEXT_VARIABLE_NAME.key, variable.name], + [CONTEXT_VARIABLE_VALUE.key, variable.value], + [CONTEXT_VARIABLE_TYPE.key, variable.type], + ]); + + const maybeVisualizers = await Promise.all(this.registrations.map(async registration => { + if (!overlay.contextMatchesRules(registration.expr)) { + return; + } + + let prom = this.didActivate.get(registration.id); + if (!prom) { + prom = this.extensionService.activateByEvent(`onDebugVisualizer:${registration.id}`); + this.didActivate.set(registration.id, prom); + } + + await prom; + if (token.isCancellationRequested) { + return; + } + + const handle = this.handles.get(toKey(registration.extensionId, registration.id)); + return handle && { handle, result: await handle.provideDebugVisualizers(context, token) }; + })); + + const ref = { + object: maybeVisualizers.filter(isDefined).flatMap(v => v.result.map(r => new DebugVisualizer(v.handle, r))), + dispose: () => { + for (const viz of maybeVisualizers) { + viz?.handle.disposeDebugVisualizers(viz.result.map(r => r.id)); + } + }, + }; + + if (token.isCancellationRequested) { + ref.dispose(); + } + + return ref; + } + + /** @inheritdoc */ + public register(handle: VisualizerHandle): IDisposable { + const key = toKey(handle.extensionId, handle.id); + this.handles.set(key, handle); + return toDisposable(() => this.handles.delete(key)); + } + + private processExtensionRegistration(ext: Readonly) { + const viz = ext.contributes?.debugVisualizers; + if (!(viz instanceof Array)) { + return; + } + + for (const { when, id } of viz) { + try { + const expr = ContextKeyExpr.deserialize(when); + if (expr) { + this.registrations.push({ expr, id, extensionId: ext.identifier }); + } + } catch (e) { + this.logService.error(`Error processing debug visualizer registration from extension '${ext.identifier.value}'`, e); + } + } + } +} + +const toKey = (extensionId: ExtensionIdentifier, id: string) => `${ExtensionIdentifier.toKey(extensionId)}\0${id}`; + +const visualizersExtensionPoint = ExtensionsRegistry.registerExtensionPoint<{ id: string; when: string }[]>({ + extensionPoint: 'debugVisualizers', + jsonSchema: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'string', + description: 'Name of the debug visualizer' + }, + when: { + type: 'string', + description: 'Condition when the debug visualizer is applicable' + } + }, + required: ['id', 'when'] + } + }, + activationEventsGenerator: (contribs, result: { push(item: string): void }) => { + for (const contrib of contribs) { + if (contrib.id) { + result.push(`onDebugVisualizer:${contrib.id}`); + } + } + } +}); diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 669bff233f9..c8d09cf0969 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -42,6 +42,7 @@ export const allApiProposals = Object.freeze({ createFileSystemWatcher: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.createFileSystemWatcher.d.ts', customEditorMove: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.customEditorMove.d.ts', debugFocus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.debugFocus.d.ts', + debugVisualization: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.debugVisualization.d.ts', defaultChatAgent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts', diffCommand: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffCommand.d.ts', diffContentOptions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffContentOptions.d.ts', diff --git a/src/vscode-dts/vscode.proposed.debugVisualization.d.ts b/src/vscode-dts/vscode.proposed.debugVisualization.d.ts new file mode 100644 index 00000000000..29a3c22d832 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.debugVisualization.d.ts @@ -0,0 +1,100 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +declare module 'vscode' { + export namespace debug { + /** + * Registers a custom data visualization for variables when debugging. + * + * @param id The corresponding ID in the package.json `debugVisualizers` contribution point. + * @param provider The {@link DebugVisualizationProvider} to register + */ + export function registerDebugVisualizationProvider( + id: string, + provider: DebugVisualizationProvider + ): Disposable; + } + + export class DebugVisualization { + /** + * The name of the visualization to show to the user. + */ + name: string; + + /** + * An icon for the view when it's show in inline actions. + */ + iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon; + + /** + * Visualization to use for the variable. This may be either: + * - A command to run when the visualization is selected for a variable. + * - A {@link TreeDataProvider} which is used to display the data in-line + * where the variable is shown. If a single root item is returned from + * the data provider, it will replace the variable in its tree. + * Otherwise, the items will be shown as children of the variable. + */ + visualization?: Command | TreeDataProvider; + + /** + * Creates a new debug visualization object. + * @param name Name of the visualization to show to the user. + */ + constructor(name: string); + } + + export interface DebugVisualizationProvider { + /** + * Called for each variable when the debug session stops. It should return + * any visualizations the extension wishes to show to the user. + * + * Note that this is only called when its `when` clause defined under the + * `debugVisualizers` contribution point in the `package.json` evaluates + * to true. + */ + provideDebugVisualization(context: DebugVisualizationContext, token: CancellationToken): ProviderResult; + + /** + * Invoked for a variable when a user picks the visualizer. + * + * It may return a {@link TreeView} that's shown in the Debug Console or + * inline in a hover. A visualizer may choose to return `undefined` from + * this function and instead trigger other actions in the UI, such as opening + * a custom {@link WebviewView}. + */ + resolveDebugVisualization?(visualization: T, token: CancellationToken): ProviderResult; + } + + export interface DebugVisualizationContext { + /** + * The Debug Adapter Protocol Variable to be visualized. + * @see https://microsoft.github.io/debug-adapter-protocol/specification#Types_Variable + */ + variable: any; + /** + * The Debug Adapter Protocol variable reference the type (such as a scope + * or another variable) that contained this one. Empty for variables + * that came from user evaluations in the Debug Console. + * @see https://microsoft.github.io/debug-adapter-protocol/specification#Types_Variable + */ + containerId?: string; + /** + * The ID of the Debug Adapter Protocol StackFrame in which the variable was found, + * for variables that came from scopes in a stack frame. + * @see https://microsoft.github.io/debug-adapter-protocol/specification#Types_StackFrame + */ + frameId?: number; + /** + * The ID of the Debug Adapter Protocol Thread in which the variable was found. + * @see https://microsoft.github.io/debug-adapter-protocol/specification#Types_StackFrame + */ + threadId: number; + /** + * The debug session the variable belongs to. + */ + session: DebugSession; + } +} From 271fb7fbd599b49a1482ea7284a50b3229317f96 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Thu, 18 Jan 2024 15:48:15 -0800 Subject: [PATCH 0455/1897] dont ask for all children for large collections (#202784) * limit number of children retreived * share const for page size --- src/vs/workbench/api/common/extHostNotebookKernels.ts | 8 +++++++- .../notebookVariables/notebookVariablesDataSource.ts | 10 +++++----- .../contrib/notebook/common/notebookKernelService.ts | 2 ++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index a83caeb5c84..8a087a434e7 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -25,6 +25,7 @@ import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/no import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import * as vscode from 'vscode'; +import { variablePageSize } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; interface IKernelData { extensionId: ExtensionIdentifier; @@ -431,7 +432,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { } let parent: vscode.Variable | undefined = undefined; - if (parentId) { + if (parentId !== undefined) { parent = this.variableStore[parentId]; if (!parent) { // request for unknown parent @@ -446,6 +447,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { const requestKind = kind === 'named' ? NotebookVariablesRequestKind.Named : NotebookVariablesRequestKind.Indexed; const variableResults = variableProvider.provideVariables(document.apiNotebook, parent, requestKind, start, token); + let resultCount = 0; for await (const result of variableResults) { if (token.isCancellationRequested) { return; @@ -459,6 +461,10 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { }; this.variableStore[variable.id] = result.variable; this._proxy.$receiveVariable(requestId, variable); + + if (resultCount++ >= variablePageSize) { + return; + } } } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts index 70511741f65..5a61cec80ff 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts @@ -6,7 +6,7 @@ import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { CancellationToken } from 'vs/base/common/cancellation'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { INotebookKernel, INotebookKernelService, VariablesResult } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelService, VariablesResult, variablePageSize } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; export interface INotebookScope { type: 'root'; @@ -65,9 +65,9 @@ export class NotebookVariableDataSource implements IAsyncDataSource 100) { - for (let start = 0; start < parent.indexedChildrenCount; start += 100) { - let end = start + 100; + if (parent.indexedChildrenCount > variablePageSize) { + for (let start = 0; start < parent.indexedChildrenCount; start += variablePageSize) { + let end = start + variablePageSize; if (end > parent.indexedChildrenCount) { end = parent.indexedChildrenCount; } @@ -89,7 +89,7 @@ export class NotebookVariableDataSource implements IAsyncDataSource= 100) { + if (childNodes.length >= variablePageSize) { break; } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index 212fe0ef0ba..ca3e3f7bf3a 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -46,6 +46,8 @@ export interface VariablesResult { indexedChildrenCount: number; } +export const variablePageSize = 100; + export interface INotebookKernel { readonly id: string; readonly viewType: string; From e5bf9dac9048c0517bc1f6f15fc4f0fdeda63cdc Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Fri, 19 Jan 2024 09:18:28 +0100 Subject: [PATCH 0456/1897] fix error --- .../electron-sandbox/desktop.contribution.ts | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index f9c5c09c16d..19549fc876a 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -27,7 +27,6 @@ import { ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; import { ModifierKeyEmitter } from 'vs/base/browser/dom'; import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from 'vs/workbench/common/configuration'; -import { CustomTitleBarVisibility, TitleBarSetting, TitlebarStyle } from 'vs/platform/window/common/window'; import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from 'vs/platform/window/electron-sandbox/window'; // Actions @@ -229,24 +228,24 @@ import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from 'vs/platform/window/electron-sand 'scope': ConfigurationScope.APPLICATION, 'markdownDescription': localize('window.doubleClickIconToClose', "If enabled, this setting will close the window when the application icon in the title bar is double-clicked. The window will not be able to be dragged by the icon. This setting is effective only if `#window.titleBarStyle#` is set to `custom`.") }, - [TitleBarSetting.TITLE_BAR_STYLE]: { + 'window.titleBarStyle': { 'type': 'string', - 'enum': [TitlebarStyle.NATIVE, TitlebarStyle.CUSTOM], - 'default': isLinux ? TitlebarStyle.NATIVE : TitlebarStyle.CUSTOM, + 'enum': ['native', 'custom'], + 'default': isLinux ? 'native' : 'custom', 'scope': ConfigurationScope.APPLICATION, 'description': localize('titleBarStyle', "Adjust the appearance of the window title bar to be native by the OS or custom. On Linux and Windows, this setting also affects the application and context menu appearances. Changes require a full restart to apply.") }, - [TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY]: { + 'window.customTitleBarVisibility': { 'type': 'string', - 'enum': [CustomTitleBarVisibility.AUTO, CustomTitleBarVisibility.WINDOWED, CustomTitleBarVisibility.NEVER], + 'enum': ['auto', 'windowed', 'never'], 'markdownEnumDescriptions': [ - localize(`${TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY}.${CustomTitleBarVisibility.AUTO}`, "Automatically changes custom titlebar visibility."), - localize(`${TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY}.${CustomTitleBarVisibility.WINDOWED}`, "Hide custom titlebar in full screen. Automatically changes custom titlebar visibility in windowed."), - localize(`${TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY}.${CustomTitleBarVisibility.NEVER}`, "Hide custom titlebar when `#window.titleBarStyle#` is set to `native`."), + localize(`window.customTitleBarVisibility.auto`, "Automatically changes custom titlebar visibility."), + localize(`window.customTitleBarVisibility.windowed`, "Hide custom titlebar in full screen. Automatically changes custom titlebar visibility in windowed."), + localize(`window.customTitleBarVisibility.never`, "Hide custom titlebar when `#window.titleBarStyle#` is set to `native`."), ], - 'default': isLinux ? CustomTitleBarVisibility.NEVER : CustomTitleBarVisibility.AUTO, + 'default': isLinux ? 'never' : 'auto', 'scope': ConfigurationScope.APPLICATION, - 'description': localize(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, "Adjust when the custom title bar should be shown."), + 'description': localize('window.customTitleBarVisibility', "Adjust when the custom title bar should be shown."), }, 'window.dialogStyle': { 'type': 'string', From 77f94e28d21ebd043e76181aae9cce456777436e Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 19 Jan 2024 10:23:19 +0100 Subject: [PATCH 0457/1897] comments.contribution.ts should only be included once (#202803) --- .../browser/accessibility.contribution.ts | 2 +- .../browser/editorAccessibilityHelp.ts | 2 +- .../comments/browser/comments.contribution.ts | 78 +---------------- .../comments/browser/commentsAccessibility.ts | 86 +++++++++++++++++++ 4 files changed, 89 insertions(+), 79 deletions(-) create mode 100644 src/vs/workbench/contrib/comments/browser/commentsAccessibility.ts diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index ac3d45e7a70..ffdf8d00676 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -12,9 +12,9 @@ import { IAccessibleViewService, AccessibleViewService } from 'vs/workbench/cont import { UnfocusedViewDimmingContribution } from 'vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution'; import { HoverAccessibleViewContribution, InlineCompletionsAccessibleViewContribution, NotificationAccessibleViewContribution } from 'vs/workbench/contrib/accessibility/browser/accessibilityContributions'; import { AccessibilityStatus } from 'vs/workbench/contrib/accessibility/browser/accessibilityStatus'; -import { CommentsAccessibilityHelpContribution } from 'vs/workbench/contrib/comments/browser/comments.contribution'; import { EditorAccessibilityHelpContribution } from 'vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp'; import { SaveAudioCueContribution } from 'vs/workbench/contrib/accessibility/browser/saveAudioCue'; +import { CommentsAccessibilityHelpContribution } from 'vs/workbench/contrib/comments/browser/commentsAccessibility'; registerAccessibilityConfiguration(); registerSingleton(IAccessibleViewService, AccessibleViewService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts index d4bde61062f..2eafab6402d 100644 --- a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts @@ -18,7 +18,7 @@ import { AccessibleViewProviderId, AccessibilityVerbositySettingId } from 'vs/wo import { descriptionForCommand } from 'vs/workbench/contrib/accessibility/browser/accessibilityContributions'; import { IAccessibleViewService, IAccessibleContentProvider, IAccessibleViewOptions, AccessibleViewType } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; -import { CommentAccessibilityHelpNLS } from 'vs/workbench/contrib/comments/browser/comments.contribution'; +import { CommentAccessibilityHelpNLS } from 'vs/workbench/contrib/comments/browser/commentsAccessibility'; import { CommentCommandId } from 'vs/workbench/contrib/comments/common/commentCommandIds'; import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; import { NEW_UNTITLED_FILE_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileConstants'; diff --git a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts index eb7c912fb6d..6545fa6c444 100644 --- a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts +++ b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts @@ -9,19 +9,9 @@ import { Registry } from 'vs/platform/registry/common/platform'; import 'vs/workbench/contrib/comments/browser/commentsEditorContribution'; import { ICommentService, CommentService, IWorkspaceCommentThreadsEvent } from 'vs/workbench/contrib/comments/browser/commentService'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { ctxCommentEditorFocused } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; -import * as strings from 'vs/base/common/strings'; -import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; -import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; -import { CommentCommandId } from 'vs/workbench/contrib/comments/common/commentCommandIds'; -import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode'; -import { getActiveElement } from 'vs/base/browser/dom'; import { Extensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { COMMENTS_VIEW_ID } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; @@ -129,72 +119,6 @@ Registry.as(ConfigurationExtensions.Configuration).regis registerSingleton(ICommentService, CommentService, InstantiationType.Delayed); -export namespace CommentAccessibilityHelpNLS { - export const intro = nls.localize('intro', "The editor contains commentable range(s). Some useful commands include:"); - export const introWidget = nls.localize('introWidget', "This widget contains a text area, for composition of new comments, and actions, that can be tabbed to once tab moves focus mode has been enabled ({0})."); - export const introWidgetNoKb = nls.localize('introWidgetNoKb', "This widget contains a text area, for composition of new comments, and actions, that can be tabbed to once tab moves focus mode has been enabled with the command Toggle Tab Key Moves Focus, which is currently not triggerable via keybinding."); - export const commentCommands = nls.localize('commentCommands', "Some useful comment commands include:"); - export const escape = nls.localize('escape', "- Dismiss Comment (Escape)"); - export const nextRange = nls.localize('next', "- Go to Next Commenting Range ({0})"); - export const nextRangeNoKb = nls.localize('nextNoKb', "- Go to Next Commenting Range, which is currently not triggerable via keybinding."); - export const previousRange = nls.localize('previous', "- Go to Previous Commenting Range ({0})"); - export const previousRangeNoKb = nls.localize('previousNoKb', "- Go to Previous Commenting Range, which is currently not triggerable via keybinding."); - export const nextCommentThreadKb = nls.localize('nextCommentThreadKb', "- Go to Next Comment Thread ({0})"); - export const nextCommentThreadNoKb = nls.localize('nextCommentThreadNoKb', "- Go to Next Comment Thread, which is currently not triggerable via keybinding."); - export const previousCommentThreadKb = nls.localize('previousCommentThreadKb', "- Go to Previous Comment Thread ({0})"); - export const previousCommentThreadNoKb = nls.localize('previousCommentThreadNoKb', "- Go to Previous Comment Thread, which is currently not triggerable via keybinding."); - export const addComment = nls.localize('addComment', "- Add Comment ({0})"); - export const addCommentNoKb = nls.localize('addCommentNoKb', "- Add Comment on Current Selection, which is currently not triggerable via keybinding."); - export const submitComment = nls.localize('submitComment', "- Submit Comment ({0})"); - export const submitCommentNoKb = nls.localize('submitCommentNoKb', "- Submit Comment, accessible via tabbing, as it's currently not triggerable with a keybinding."); -} - -export class CommentsAccessibilityHelpContribution extends Disposable { - static ID: 'commentsAccessibilityHelpContribution'; - constructor() { - super(); - this._register(AccessibilityHelpAction.addImplementation(110, 'comments', accessor => { - const instantiationService = accessor.get(IInstantiationService); - const accessibleViewService = accessor.get(IAccessibleViewService); - accessibleViewService.show(instantiationService.createInstance(CommentsAccessibilityHelpProvider)); - return true; - }, ContextKeyExpr.or(ctxCommentEditorFocused, CommentContextKeys.commentFocused))); - } -} -export class CommentsAccessibilityHelpProvider implements IAccessibleContentProvider { - id = AccessibleViewProviderId.Comments; - verbositySettingKey: AccessibilityVerbositySettingId = AccessibilityVerbositySettingId.Comments; - options: IAccessibleViewOptions = { type: AccessibleViewType.Help }; - private _element: HTMLElement | undefined; - constructor( - @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 { - this._element = getActiveElement() as HTMLElement; - const content: string[] = []; - content.push(this._descriptionForCommand(ToggleTabFocusModeAction.ID, CommentAccessibilityHelpNLS.introWidget, CommentAccessibilityHelpNLS.introWidgetNoKb) + '\n'); - content.push(CommentAccessibilityHelpNLS.commentCommands); - content.push(CommentAccessibilityHelpNLS.escape); - content.push(this._descriptionForCommand(CommentCommandId.Add, CommentAccessibilityHelpNLS.addComment, CommentAccessibilityHelpNLS.addCommentNoKb)); - content.push(this._descriptionForCommand(CommentCommandId.Submit, CommentAccessibilityHelpNLS.submitComment, CommentAccessibilityHelpNLS.submitCommentNoKb)); - content.push(this._descriptionForCommand(CommentCommandId.NextRange, CommentAccessibilityHelpNLS.nextRange, CommentAccessibilityHelpNLS.nextRangeNoKb)); - content.push(this._descriptionForCommand(CommentCommandId.PreviousRange, CommentAccessibilityHelpNLS.previousRange, CommentAccessibilityHelpNLS.previousRangeNoKb)); - return content.join('\n'); - } - onClose(): void { - this._element?.focus(); - } -} - export class UnresolvedCommentsBadge extends Disposable implements IWorkbenchContribution { private readonly activity = this._register(new MutableDisposable()); private totalUnresolved = 0; diff --git a/src/vs/workbench/contrib/comments/browser/commentsAccessibility.ts b/src/vs/workbench/contrib/comments/browser/commentsAccessibility.ts new file mode 100644 index 00000000000..27ba7d7e663 --- /dev/null +++ b/src/vs/workbench/contrib/comments/browser/commentsAccessibility.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; +import { ctxCommentEditorFocused } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; +import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; +import * as nls from 'vs/nls'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import * as strings from 'vs/base/common/strings'; +import { getActiveElement } from 'vs/base/browser/dom'; +import { CommentCommandId } from 'vs/workbench/contrib/comments/common/commentCommandIds'; +import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode'; + +export namespace CommentAccessibilityHelpNLS { + export const intro = nls.localize('intro', "The editor contains commentable range(s). Some useful commands include:"); + export const introWidget = nls.localize('introWidget', "This widget contains a text area, for composition of new comments, and actions, that can be tabbed to once tab moves focus mode has been enabled ({0})."); + export const introWidgetNoKb = nls.localize('introWidgetNoKb', "This widget contains a text area, for composition of new comments, and actions, that can be tabbed to once tab moves focus mode has been enabled with the command Toggle Tab Key Moves Focus, which is currently not triggerable via keybinding."); + export const commentCommands = nls.localize('commentCommands', "Some useful comment commands include:"); + export const escape = nls.localize('escape', "- Dismiss Comment (Escape)"); + export const nextRange = nls.localize('next', "- Go to Next Commenting Range ({0})"); + export const nextRangeNoKb = nls.localize('nextNoKb', "- Go to Next Commenting Range, which is currently not triggerable via keybinding."); + export const previousRange = nls.localize('previous', "- Go to Previous Commenting Range ({0})"); + export const previousRangeNoKb = nls.localize('previousNoKb', "- Go to Previous Commenting Range, which is currently not triggerable via keybinding."); + export const nextCommentThreadKb = nls.localize('nextCommentThreadKb', "- Go to Next Comment Thread ({0})"); + export const nextCommentThreadNoKb = nls.localize('nextCommentThreadNoKb', "- Go to Next Comment Thread, which is currently not triggerable via keybinding."); + export const previousCommentThreadKb = nls.localize('previousCommentThreadKb', "- Go to Previous Comment Thread ({0})"); + export const previousCommentThreadNoKb = nls.localize('previousCommentThreadNoKb', "- Go to Previous Comment Thread, which is currently not triggerable via keybinding."); + export const addComment = nls.localize('addComment', "- Add Comment ({0})"); + export const addCommentNoKb = nls.localize('addCommentNoKb', "- Add Comment on Current Selection, which is currently not triggerable via keybinding."); + export const submitComment = nls.localize('submitComment', "- Submit Comment ({0})"); + export const submitCommentNoKb = nls.localize('submitCommentNoKb', "- Submit Comment, accessible via tabbing, as it's currently not triggerable with a keybinding."); +} + +export class CommentsAccessibilityHelpProvider implements IAccessibleContentProvider { + id = AccessibleViewProviderId.Comments; + verbositySettingKey: AccessibilityVerbositySettingId = AccessibilityVerbositySettingId.Comments; + options: IAccessibleViewOptions = { type: AccessibleViewType.Help }; + private _element: HTMLElement | undefined; + constructor( + @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 { + this._element = getActiveElement() as HTMLElement; + const content: string[] = []; + content.push(this._descriptionForCommand(ToggleTabFocusModeAction.ID, CommentAccessibilityHelpNLS.introWidget, CommentAccessibilityHelpNLS.introWidgetNoKb) + '\n'); + content.push(CommentAccessibilityHelpNLS.commentCommands); + content.push(CommentAccessibilityHelpNLS.escape); + content.push(this._descriptionForCommand(CommentCommandId.Add, CommentAccessibilityHelpNLS.addComment, CommentAccessibilityHelpNLS.addCommentNoKb)); + content.push(this._descriptionForCommand(CommentCommandId.Submit, CommentAccessibilityHelpNLS.submitComment, CommentAccessibilityHelpNLS.submitCommentNoKb)); + content.push(this._descriptionForCommand(CommentCommandId.NextRange, CommentAccessibilityHelpNLS.nextRange, CommentAccessibilityHelpNLS.nextRangeNoKb)); + content.push(this._descriptionForCommand(CommentCommandId.PreviousRange, CommentAccessibilityHelpNLS.previousRange, CommentAccessibilityHelpNLS.previousRangeNoKb)); + return content.join('\n'); + } + onClose(): void { + this._element?.focus(); + } +} + +export class CommentsAccessibilityHelpContribution extends Disposable { + static ID: 'commentsAccessibilityHelpContribution'; + constructor() { + super(); + this._register(AccessibilityHelpAction.addImplementation(110, 'comments', accessor => { + const instantiationService = accessor.get(IInstantiationService); + const accessibleViewService = accessor.get(IAccessibleViewService); + accessibleViewService.show(instantiationService.createInstance(CommentsAccessibilityHelpProvider)); + return true; + }, ContextKeyExpr.or(ctxCommentEditorFocused, CommentContextKeys.commentFocused))); + } +} From f964dcc08a106d3ace3af2d98bd0ad633bf1583b Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 19 Jan 2024 10:38:51 +0100 Subject: [PATCH 0458/1897] Fix for 'Can't compress when there are no elements to compress' error (#202804) fix #202800 --- src/vs/base/browser/ui/tree/objectTree.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts index a174d011511..894f74ce921 100644 --- a/src/vs/base/browser/ui/tree/objectTree.ts +++ b/src/vs/base/browser/ui/tree/objectTree.ts @@ -189,6 +189,10 @@ class CompressibleStickyScrollDelegate implements IStickyScrollD private compressStickyNodes(stickyNodes: StickyScrollNode[]): StickyScrollNode { + if (stickyNodes.length === 0) { + throw new Error('Can\'t compress empty sticky nodes'); + } + if (!this.modelProvider().isCompressionEnabled()) { return stickyNodes[0]; } @@ -206,11 +210,7 @@ class CompressibleStickyScrollDelegate implements IStickyScrollD } } - if (elements.length === 0) { - throw new Error('Can\'t compress when there are no elements to compress'); - } - - if (elements.length === 1) { + if (elements.length < 2) { return stickyNodes[0]; } From 3b9f4a340c4433b4cff3635d53ad20125eb028e9 Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 19 Jan 2024 10:53:53 +0100 Subject: [PATCH 0459/1897] Fix for incorrect file icon margins with compact pinned tabs and left tab action location (#202808) fix #202505 --- .../browser/parts/editor/media/multieditortabscontrol.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css index 9d470d6c160..c826a4f8725 100644 --- a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css @@ -228,7 +228,7 @@ pointer-events: none; /* prevents cursor flickering (fixes https://github.com/microsoft/vscode/issues/38753) */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-left { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-left:not(.sticky-compact) { flex-direction: row-reverse; padding-left: 0; padding-right: 10px; From 0287cb9fe78a006e02488892182b0791cfe8988b Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 19 Jan 2024 11:25:59 +0100 Subject: [PATCH 0460/1897] Git - add fetch, pull, push commands to incoming/outgoing (#202809) --- extensions/git/package.json | 81 ++++++++++++++++++++++----- extensions/git/src/commands.ts | 29 ++++++++++ extensions/git/src/historyProvider.ts | 28 ++++----- 3 files changed, 110 insertions(+), 28 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 0a9c6904555..e909d5f5030 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -10,25 +10,26 @@ }, "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "enabledApiProposals": [ - "diffCommand", - "contribEditorContentMenu", - "contribEditSessions", "canonicalUriProvider", - "contribViewsWelcome", - "editSessionIdentityProvider", - "quickDiffProvider", - "scmActionButton", - "scmHistoryProvider", - "scmSelectedProvider", - "scmValidation", - "scmMultiDiffEditor", - "tabInputTextMerge", - "timeline", + "contribEditSessions", + "contribEditorContentMenu", "contribMergeEditorMenus", "contribMultiDiffEditorMenus", + "contribSourceControlHistoryItemGroupMenu", + "contribSourceControlHistoryItemMenu", "contribSourceControlInputBoxMenu", + "contribViewsWelcome", + "diffCommand", + "editSessionIdentityProvider", + "quickDiffProvider", "quickPickSortByLabel", - "contribSourceControlHistoryItemMenu" + "scmActionButton", + "scmHistoryProvider", + "scmMultiDiffEditor", + "scmSelectedProvider", + "scmValidation", + "tabInputTextMerge", + "timeline" ], "categories": [ "Other" @@ -511,6 +512,13 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.fetchRef", + "title": "%command.fetch%", + "icon": "$(git-fetch)", + "category": "Git", + "enablement": "!operationInProgress" + }, { "command": "git.pull", "title": "%command.pull%", @@ -529,6 +537,13 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.pullRef", + "title": "%command.pull%", + "icon": "$(repo-pull)", + "category": "Git", + "enablement": "!operationInProgress" + }, { "command": "git.push", "title": "%command.push%", @@ -571,6 +586,13 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.pushRef", + "title": "%command.push%", + "icon": "$(repo-push)", + "category": "Git", + "enablement": "!operationInProgress" + }, { "command": "git.cherryPick", "title": "%command.cherryPick%", @@ -1335,6 +1357,18 @@ { "command": "git.unstageFile", "when": "false" + }, + { + "command": "git.fetchRef", + "when": "false" + }, + { + "command": "git.pullRef", + "when": "false" + }, + { + "command": "git.pushRef", + "when": "false" } ], "scm/title": [ @@ -1802,6 +1836,18 @@ "group": "1_modification@3" } ], + "scm/incomingChanges": [ + { + "command": "git.fetchRef", + "group": "navigation", + "when": "scmProvider == git" + }, + { + "command": "git.pullRef", + "group": "navigation", + "when": "scmProvider == git" + } + ], "scm/incomingChanges/allChanges/context": [ { "command": "git.viewAllChanges", @@ -1816,6 +1862,13 @@ "group": "inline@1" } ], + "scm/outgoingChanges": [ + { + "command": "git.pushRef", + "group": "navigation", + "when": "scmProvider == git" + } + ], "scm/outgoingChanges/allChanges/context": [ { "command": "git.viewAllChanges", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 81abbd94aa0..6a15bd94455 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -2961,6 +2961,16 @@ export class CommandCenter { await repository.fetchAll(); } + @command('git.fetchRef', { repository: true }) + async fetchRef(repository: Repository, ref: string): Promise { + if (!repository || !ref) { + return; + } + + const branch = await repository.getBranch(ref); + await repository.fetch({ remote: branch.remote, ref: branch.name }); + } + @command('git.pullFrom', { repository: true }) async pullFrom(repository: Repository): Promise { const remotes = repository.remotes; @@ -3023,6 +3033,16 @@ export class CommandCenter { await repository.pullWithRebase(repository.HEAD); } + @command('git.pullRef', { repository: true }) + async pullRef(repository: Repository, ref: string): Promise { + if (!repository || !ref) { + return; + } + + const branch = await repository.getBranch(ref); + await repository.pullFrom(false, branch.remote, branch.name); + } + private async _push(repository: Repository, pushOptions: PushOptions) { const remotes = repository.remotes; @@ -3160,6 +3180,15 @@ export class CommandCenter { await this._push(repository, { pushType: PushType.PushFollowTags, forcePush: true }); } + @command('git.pushRef', { repository: true }) + async pushRef(repository: Repository, ref: string): Promise { + if (!repository || !ref) { + return; + } + + await this._push(repository, { pushType: PushType.Push }); + } + @command('git.cherryPick', { repository: true }) async cherryPick(repository: Repository): Promise { const hash = await window.showInputBox({ diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 9560aef5e8e..5d395c502c1 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -191,21 +191,21 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return HEAD.upstream; } - try { - const remoteBranch = await this.repository.getBranchBase(HEAD.name ?? ''); - if (!remoteBranch?.remote || !remoteBranch?.name || !remoteBranch?.commit || remoteBranch?.type !== RefType.RemoteHead) { - return undefined; - } + // try { + // const remoteBranch = await this.repository.getBranchBase(HEAD.name ?? ''); + // if (!remoteBranch?.remote || !remoteBranch?.name || !remoteBranch?.commit || remoteBranch?.type !== RefType.RemoteHead) { + // return undefined; + // } - return { - name: remoteBranch.name, - remote: remoteBranch.remote, - commit: remoteBranch.commit - }; - } - catch (err) { - this.logger.error(`Failed to get branch base for '${HEAD.name}': ${err.message}`); - } + // return { + // name: remoteBranch.name, + // remote: remoteBranch.remote, + // commit: remoteBranch.commit + // }; + // } + // catch (err) { + // this.logger.error(`Failed to get branch base for '${HEAD.name}': ${err.message}`); + // } return undefined; } From 99a2fd53bd4a8cfd9eed7751c8b269af005eec3d Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 18 Jan 2024 17:12:01 +0100 Subject: [PATCH 0461/1897] Adds substring --- src/vs/editor/common/core/offsetRange.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/editor/common/core/offsetRange.ts b/src/vs/editor/common/core/offsetRange.ts index e35a27c56bc..17857dd9488 100644 --- a/src/vs/editor/common/core/offsetRange.ts +++ b/src/vs/editor/common/core/offsetRange.ts @@ -136,6 +136,10 @@ export class OffsetRange implements IOffsetRange { return arr.slice(this.start, this.endExclusive); } + public substring(str: string): string { + return str.substring(this.start, this.endExclusive); + } + /** * Returns the given value if it is contained in this instance, otherwise the closest value that is contained. * The range must not be empty. From 600e284b848cfd9cb3b94552611c309a0645d551 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 19 Jan 2024 12:47:12 +0100 Subject: [PATCH 0462/1897] "Voice Keyword Activation" appeared in status bar with latest Insiders update (fix #202838) (#202841) --- .../electron-sandbox/actions/voiceChatActions.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 5874c2c3713..f83d6f8ff49 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -766,6 +766,16 @@ registerThemingParticipant((theme, collector) => { `); }); +function supportsKeywordActivation(configurationService: IConfigurationService, speechService: ISpeechService): boolean { + if (!speechService.hasSpeechProvider) { + return false; + } + + const value = configurationService.getValue(KeywordActivationContribution.SETTINGS_ID); + + return typeof value === 'string' && value !== KeywordActivationContribution.SETTINGS_VALUE.OFF; +} + export class KeywordActivationContribution extends Disposable implements IWorkbenchContribution { static SETTINGS_ID = 'accessibility.voice.keywordActivation'; @@ -852,8 +862,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe private handleKeywordActivation(): void { const enabled = - this.speechService.hasSpeechProvider && - this.configurationService.getValue(KeywordActivationContribution.SETTINGS_ID) !== KeywordActivationContribution.SETTINGS_VALUE.OFF && + supportsKeywordActivation(this.configurationService, this.speechService) && !this.speechService.hasActiveSpeechToTextSession; if ( (enabled && this.activeSession) || @@ -957,7 +966,7 @@ class KeywordActivationStatusEntry extends Disposable { } private updateStatusEntry(): void { - const visible = this.configurationService.getValue(KeywordActivationContribution.SETTINGS_ID) !== KeywordActivationContribution.SETTINGS_VALUE.OFF; + const visible = supportsKeywordActivation(this.configurationService, this.speechService); if (visible) { if (!this.entry.value) { this.createStatusEntry(); From 945bbd01aa9c90c3dcc6ae51595e834fde5adbad Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 19 Jan 2024 12:56:34 +0100 Subject: [PATCH 0463/1897] Git - only show "Incoming changes" node for upstream (#202836) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Git - only show "Incoming changes" node for upstream * 💄 Revert some of the changes now that base is limited to upstream * Fixed a bug * 💄 More clean-up --- extensions/git/src/historyProvider.ts | 96 +++++++++---------- src/vs/workbench/api/browser/mainThreadSCM.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostSCM.ts | 2 +- .../contrib/scm/browser/scmViewPane.ts | 18 ++-- .../workbench/contrib/scm/common/history.ts | 2 +- .../vscode.proposed.scmHistoryProvider.d.ts | 2 +- 7 files changed, 59 insertions(+), 65 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 5d395c502c1..88dc4e0a668 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -12,14 +12,6 @@ import { Branch, RefType, UpstreamRef } from './api/git'; import { emojify, ensureEmojis } from './emoji'; import { Operation } from './operation'; -function isBranchRefEqual(brach1: Branch | undefined, branch2: Branch | undefined): boolean { - return brach1?.name === branch2?.name && brach1?.commit === branch2?.commit; -} - -function isUpstreamRefEqual(upstream1: UpstreamRef | undefined, upstream2: UpstreamRef | undefined): boolean { - return upstream1?.name === upstream2?.name && upstream1?.remote === upstream2?.remote && upstream1?.commit === upstream2?.commit; -} - export class GitHistoryProvider implements SourceControlHistoryProvider, FileDecorationProvider, IDisposable { private readonly _onDidChangeCurrentHistoryItemGroup = new EventEmitter(); @@ -29,15 +21,10 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; private _HEAD: Branch | undefined; - private _HEADBase: UpstreamRef | undefined; private _currentHistoryItemGroup: SourceControlHistoryItemGroup | undefined; get currentHistoryItemGroup(): SourceControlHistoryItemGroup | undefined { return this._currentHistoryItemGroup; } set currentHistoryItemGroup(value: SourceControlHistoryItemGroup | undefined) { - if (this._currentHistoryItemGroup === undefined && value === undefined) { - return; - } - this._currentHistoryItemGroup = value; this._onDidChangeCurrentHistoryItemGroup.fire(); } @@ -54,31 +41,30 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec } private async onDidRunGitStatus(): Promise { - // Check if HEAD does not support incoming/outgoing (detached commit, tag) - if (!this.repository.HEAD?.name || !this.repository.HEAD?.commit || this.repository.HEAD.type === RefType.Tag) { - this._HEAD = this._HEADBase = undefined; - this.currentHistoryItemGroup = undefined; - return; - } - - // Resolve HEAD base - const HEADBase = await this.resolveHEADBase(this.repository.HEAD); - - // Check if HEAD or HEADBase has changed - if (isBranchRefEqual(this._HEAD, this.repository.HEAD) && isUpstreamRefEqual(this._HEADBase, HEADBase)) { + // Check if HEAD has changed + if (this._HEAD?.name === this.repository.HEAD?.name && + this._HEAD?.commit === this.repository.HEAD?.commit && + this._HEAD?.upstream?.name === this.repository.HEAD?.upstream?.name && + this._HEAD?.upstream?.remote === this.repository.HEAD?.upstream?.remote && + this._HEAD?.upstream?.commit === this.repository.HEAD?.upstream?.commit) { return; } this._HEAD = this.repository.HEAD; - this._HEADBase = HEADBase; + + // Check if HEAD does not support incoming/outgoing (detached commit, tag) + if (!this._HEAD?.name || !this._HEAD?.commit || this._HEAD.type === RefType.Tag) { + this.currentHistoryItemGroup = undefined; + return; + } this.currentHistoryItemGroup = { id: `refs/heads/${this._HEAD.name ?? ''}`, label: this._HEAD.name ?? '', - base: this._HEADBase ? + base: this._HEAD.upstream ? { - id: `refs/remotes/${this._HEADBase.remote}/${this._HEADBase.name}`, - label: `${this._HEADBase.remote}/${this._HEADBase.name}`, + id: `refs/remotes/${this._HEAD.upstream.remote}/${this._HEAD.upstream.name}`, + label: `${this._HEAD.upstream.remote}/${this._HEAD.upstream.name}`, } : undefined }; } @@ -165,17 +151,26 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return historyItemChanges; } - async resolveHistoryItemGroupCommonAncestor(refId1: string, refId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined> { - const ancestor = await this.repository.getMergeBase(refId1, refId2); + async resolveHistoryItemGroupCommonAncestor(historyItemId1: string, historyItemId2: string | undefined): Promise<{ id: string; ahead: number; behind: number } | undefined> { + if (!historyItemId2) { + const upstreamRef = await this.resolveHistoryItemGroupBase(historyItemId1); + if (!upstreamRef) { + return undefined; + } + + historyItemId2 = `refs/remotes/${upstreamRef.remote}/${upstreamRef.name}`; + } + + const ancestor = await this.repository.getMergeBase(historyItemId1, historyItemId2); if (!ancestor) { return undefined; } try { - const commitCount = await this.repository.getCommitCount(`${refId1}...${refId2}`); + const commitCount = await this.repository.getCommitCount(`${historyItemId1}...${historyItemId2}`); return { id: ancestor, ahead: commitCount.ahead, behind: commitCount.behind }; } catch (err) { - this.logger.error(`Failed to get ahead/behind for '${refId1}...${refId2}': ${err.message}`); + this.logger.error(`Failed to get ahead/behind for '${historyItemId1}...${historyItemId2}': ${err.message}`); } return undefined; @@ -185,27 +180,22 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return this.historyItemDecorations.get(uri.toString()); } - private async resolveHEADBase(HEAD: Branch): Promise { - // Upstream - if (HEAD.upstream) { - return HEAD.upstream; + private async resolveHistoryItemGroupBase(historyItemId: string): Promise { + try { + const remoteBranch = await this.repository.getBranchBase(historyItemId); + if (!remoteBranch?.remote || !remoteBranch?.name || !remoteBranch?.commit || remoteBranch?.type !== RefType.RemoteHead) { + return undefined; + } + + return { + name: remoteBranch.name, + remote: remoteBranch.remote, + commit: remoteBranch.commit + }; + } + catch (err) { + this.logger.error(`Failed to get branch base for '${historyItemId}': ${err.message}`); } - - // try { - // const remoteBranch = await this.repository.getBranchBase(HEAD.name ?? ''); - // if (!remoteBranch?.remote || !remoteBranch?.name || !remoteBranch?.commit || remoteBranch?.type !== RefType.RemoteHead) { - // return undefined; - // } - - // return { - // name: remoteBranch.name, - // remote: remoteBranch.remote, - // commit: remoteBranch.commit - // }; - // } - // catch (err) { - // this.logger.error(`Failed to get branch base for '${HEAD.name}': ${err.message}`); - // } return undefined; } diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 2c82aa6e2ad..0ae7ac97e96 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -140,7 +140,7 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider { constructor(private readonly proxy: ExtHostSCMShape, private readonly handle: number) { } - async resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined> { + async resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string | undefined): Promise<{ id: string; ahead: number; behind: number } | undefined> { return this.proxy.$resolveHistoryItemGroupCommonAncestor(this.handle, historyItemGroupId1, historyItemGroupId2, CancellationToken.None); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 21261315689..68ac001fd36 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2239,7 +2239,7 @@ export interface ExtHostSCMShape { $provideHistoryItems(sourceControlHandle: number, historyItemGroupId: string, options: any, token: CancellationToken): Promise; $provideHistoryItemSummary(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise; $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise; - $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined>; + $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string | undefined, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined>; } export interface ExtHostQuickDiffShape { diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index da1e67ab604..ee2be0b1bcc 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -960,7 +960,7 @@ export class ExtHostSCM implements ExtHostSCMShape { return Promise.resolve(undefined); } - async $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined> { + async $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string | undefined, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined> { const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; return await historyProvider?.resolveHistoryItemGroupCommonAncestor(historyItemGroupId1, historyItemGroupId2, token) ?? undefined; } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 7938ec48156..7ce51f64928 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -3291,7 +3291,7 @@ class SCMTreeDataSource implements IAsyncDataSource> { - const { alwaysShowRepositories, showActionButton, showIncomingChanges, showOutgoingChanges } = this.getConfiguration(); + const { alwaysShowRepositories, showActionButton } = this.getConfiguration(); const repositoryCount = this.scmViewService.visibleRepositories.length; if (isSCMViewService(inputOrElement) && (repositoryCount > 1 || alwaysShowRepositories)) { @@ -3331,10 +3331,13 @@ class SCMTreeDataSource implements IAsyncDataSource g.direction === 'incoming'); + const outgoingHistoryItems = historyItemGroups.find(g => g.direction === 'outgoing'); + + if (incomingHistoryItems && !outgoingHistoryItems) { label = localize('syncIncomingSeparatorHeader', "Incoming"); ariaLabel = localize('syncIncomingSeparatorHeaderAriaLabel', "Incoming changes"); - } else if (showIncomingChanges === 'never' && showOutgoingChanges !== 'never') { + } else if (!incomingHistoryItems && outgoingHistoryItems) { label = localize('syncOutgoingSeparatorHeader', "Outgoing"); ariaLabel = localize('syncOutgoingSeparatorHeaderAriaLabel', "Outgoing changes"); } @@ -3384,7 +3387,7 @@ class SCMTreeDataSource implements IAsyncDataSource; provideHistoryItemSummary(historyItemId: string, historyItemParentId: string | undefined): Promise; provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise; - resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined>; + resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string | undefined): Promise<{ id: string; ahead: number; behind: number } | undefined>; } export interface ISCMHistoryProviderCacheEntry { diff --git a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts index 24f4dcd8453..3f1509d0cc6 100644 --- a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts @@ -28,7 +28,7 @@ declare module 'vscode' { provideHistoryItemSummary?(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): ProviderResult; provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): ProviderResult; - resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string, token: CancellationToken): ProviderResult<{ id: string; ahead: number; behind: number }>; + resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string | undefined, token: CancellationToken): ProviderResult<{ id: string; ahead: number; behind: number }>; } export interface SourceControlHistoryOptions { From 053bbeb47cab4c13f26e93b6f779ee0d58b650c5 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 19 Jan 2024 13:14:28 +0100 Subject: [PATCH 0464/1897] Use hunks also for livePreview and preview mode (#202843) * more test coverage * - Session has textModelN and targetUri where they must not always be equal - for preview mode create textModelN copy so that AI edits can always be applied - let preview mode work with hunk information * - live preview uses hunk information - tweak utils * fix live-mode toggle diff --- .../contrib/inlineChat/browser/inlineChat.css | 18 +- .../browser/inlineChatController.ts | 2 +- .../browser/inlineChatLivePreviewWidget.ts | 44 +- .../browser/inlineChatSavingServiceImpl.ts | 12 +- .../inlineChat/browser/inlineChatSession.ts | 13 + .../browser/inlineChatSessionServiceImpl.ts | 57 ++- .../browser/inlineChatStrategies.ts | 427 +++++++----------- .../inlineChat/browser/inlineChatWidget.ts | 44 +- .../contrib/inlineChat/browser/utils.ts | 88 +++- .../test/browser/inlineChatSession.test.ts | 28 ++ .../test/browser/inlineChatStrategies.test.ts | 2 +- .../view/cellParts/chat/cellChatController.ts | 3 +- 12 files changed, 368 insertions(+), 370 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css index 9f50942dafc..ee46f6df25e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css @@ -295,28 +295,18 @@ display: none; } -.monaco-editor .inline-chat .previewDiff { +.monaco-editor .inline-chat .previewDiff, +.monaco-editor .inline-chat .previewCreate { display: inherit; - padding: 6px; border: 1px solid var(--vscode-inlineChat-border); - border-top: none; - border-bottom-left-radius: 2px; - border-bottom-right-radius: 2px; - margin: 0 2px 6px 2px; + border-radius: 2px; + margin: 6px 0px; } .monaco-editor .inline-chat .previewCreateTitle { padding-top: 6px; } -.monaco-editor .inline-chat .previewCreate { - display: inherit; - padding: 6px; - border: 1px solid var(--vscode-inlineChat-border); - border-radius: 2px; - margin: 0 2px 6px 2px; -} - .monaco-editor .inline-chat .previewDiff.hidden, .monaco-editor .inline-chat .previewCreate.hidden, .monaco-editor .inline-chat .previewCreateTitle.hidden { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index dc8433eaf9b..2318b0bdd06 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -330,7 +330,7 @@ export class InlineChatController implements IEditorContribution { this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._zone.value); break; case EditMode.Preview: - this._strategy = this._instaService.createInstance(PreviewStrategy, session, this._zone.value); + this._strategy = this._instaService.createInstance(PreviewStrategy, session, this._editor, this._zone.value); break; case EditMode.LivePreview: default: diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts index f4badff7d4d..1af9ecfa2f1 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts @@ -18,15 +18,14 @@ import * as editorColorRegistry from 'vs/editor/common/core/editorColorRegistry' import { IThemeService } from 'vs/platform/theme/common/themeService'; import { INLINE_CHAT_ID, inlineChatDiffInserted, inlineChatDiffRemoved, inlineChatRegionHighlight } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { LineRange } from 'vs/editor/common/core/lineRange'; -import { LineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { Position } from 'vs/editor/common/core/position'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; import { ILogService } from 'vs/platform/log/common/log'; -import { lineRangeAsRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; +import { invertLineRange, asRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { ResourceLabel } from 'vs/workbench/browser/labels'; import { FileKind } from 'vs/platform/files/common/files'; -import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { HunkInformation, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { generateUuid } from 'vs/base/common/uuid'; @@ -170,22 +169,22 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { throw new Error('use showForChanges'); } - showForChanges(changes: readonly LineRangeMapping[]): void { + showForChanges(hunk: HunkInformation): void { const hasFocus = this._diffEditor.hasTextFocus(); this._isVisible = true; - const onlyInserts = changes.every(change => change.original.isEmpty); + const onlyInserts = hunk.isInsertion(); - if (onlyInserts || changes.length === 0 || this._session.textModel0.getValueLength() === 0) { + if (onlyInserts || this._session.textModel0.getValueLength() === 0) { // no change or changes to an empty file this._logService.debug('[IE] livePreview-mode: no diff'); this._cleanupFullDiff(); - this._renderInsertWithHighlight(changes); + this._renderInsertWithHighlight(hunk); } else { // complex changes this._logService.debug('[IE] livePreview-mode: full diff'); this._decorationCollection.clear(); - this._renderChangesWithFullDiff(changes); + this._renderChangesWithFullDiff(hunk); } // TODO@jrieken find a better fix for this. this is the challenge: @@ -197,7 +196,7 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { } } - private _renderInsertWithHighlight(changes: readonly LineRangeMapping[]) { + private _renderInsertWithHighlight(hunk: HunkInformation) { assertType(this.editor.hasModel()); const options: IModelDecorationOptions = { @@ -207,21 +206,18 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { className: 'inline-chat-lines-inserted-range', }; - this._decorationCollection.set(changes.map(change => { - return { - range: lineRangeAsRange(change.modified), - options, - }; - })); + this._decorationCollection.set([{ + range: hunk.getRangesN()[0], + options + }]); } // --- full diff - private _renderChangesWithFullDiff(changes: readonly LineRangeMapping[]) { + private _renderChangesWithFullDiff(hunk: HunkInformation) { assertType(this.editor.hasModel()); - const modified = this.editor.getModel(); - const ranges = this._computeHiddenRanges(modified, changes); + const ranges = this._computeHiddenRanges(this._session.textModelN, hunk); this._hideEditorRanges(this.editor, [ranges.modifiedHidden]); this._hideEditorRanges(this._diffEditor.getOriginalEditor(), ranges.originalDiffHidden); @@ -246,15 +242,11 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { this._isVisible = false; } - private _computeHiddenRanges(model: ITextModel, changes: readonly LineRangeMapping[]) { + private _computeHiddenRanges(model: ITextModel, hunk: HunkInformation) { - let originalLineRange = changes[0].original; - let modifiedLineRange = changes[0].modified; - for (let i = 1; i < changes.length; i++) { - originalLineRange = originalLineRange.join(changes[i].original); - modifiedLineRange = modifiedLineRange.join(changes[i].modified); - } + const modifiedLineRange = LineRange.fromRangeInclusive(hunk.getRangesN()[0]); + let originalLineRange = LineRange.fromRangeInclusive(hunk.getRanges0()[0]); if (originalLineRange.isEmpty) { originalLineRange = new LineRange(originalLineRange.startLineNumber, originalLineRange.endLineNumberExclusive + 1); } @@ -287,7 +279,7 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { // TODO: not every line can be hidden, keep the first line around hiddenRanges = [editor.getModel().getFullModelRange().delta(1)]; } else { - hiddenRanges = lineRanges.map(lineRangeAsRange); + hiddenRanges = lineRanges.map(lr => asRange(lr, editor.getModel())); } editor.setHiddenAreas(hiddenRanges, this._hideId); this._logService.debug(`[IE] diff HIDING ${hiddenRanges} for ${editor.getId()} with ${String(editor.getModel()?.uri)}`); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts index 55658018f73..bbd97b97408 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts @@ -67,7 +67,7 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService { markChanged(session: Session): void { if (!this._sessionData.has(session)) { - let uri = session.textModelN.uri; + let uri = session.targetUri; // notebooks: use the notebook-uri because saving happens on the notebook-level if (uri.scheme === Schemas.vscodeNotebookCell) { @@ -209,7 +209,7 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService { break; } - array.sort((a, b) => compare(a.session.textModelN.uri.toString(), b.session.textModelN.uri.toString())); + array.sort((a, b) => compare(a.session.targetUri.toString(), b.session.targetUri.toString())); for (const data of array) { @@ -217,15 +217,15 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService { const input: IResourceEditorInput = { resource: data.resourceUri }; const pane = await this._editorService.openEditor(input, group); let editor: ICodeEditor | undefined; - if (data.session.textModelN.uri.scheme === Schemas.vscodeNotebookCell) { + if (data.session.targetUri.scheme === Schemas.vscodeNotebookCell) { const notebookEditor = getNotebookEditorFromEditorPane(pane); - const uriData = CellUri.parse(data.session.textModelN.uri); + const uriData = CellUri.parse(data.session.targetUri); if (notebookEditor && notebookEditor.hasModel() && uriData) { const cell = notebookEditor.getCellByHandle(uriData.handle); if (cell) { await notebookEditor.revealRangeInCenterIfOutsideViewportAsync(cell, data.session.wholeRange.value); } - const tuple = notebookEditor.codeEditors.find(tuple => tuple[1].getModel()?.uri.toString() === data.session.textModelN.uri.toString()); + const tuple = notebookEditor.codeEditors.find(tuple => tuple[1].getModel()?.uri.toString() === data.session.targetUri.toString()); editor = tuple?.[1]; } @@ -240,7 +240,7 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService { break; } this._inlineChatSessionService.moveSession(data.session, editor); - this._logService.info('WAIT for session to end', editor.getId(), data.session.textModelN.uri.toString()); + this._logService.info('WAIT for session to end', editor.getId(), data.session.targetUri.toString()); await this._whenSessionsEnded(Iterable.single(data), token); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 63d31a12445..fb2cdf8e54c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -142,7 +142,17 @@ export class Session { constructor( readonly editMode: EditMode, + /** + * The URI of the document which is being EditorEdit + */ + readonly targetUri: URI, + /** + * A copy of the document at the time the session was started + */ readonly textModel0: ITextModel, + /** + * The document into which AI edits went, when live this is `targetUri` otherwise it is a temporary document + */ readonly textModelN: ITextModel, readonly provider: IInlineChatSessionProvider, readonly session: IInlineChatSession, @@ -707,6 +717,9 @@ export interface HunkInformation { discardChanges(): void; + /** + * Accept the hunk. Applies the corresponding edits into textModel0 + */ acceptChanges(): void; getState(): HunkState; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index 57e865322f9..acd05d3527d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -19,6 +19,8 @@ import { raceCancellation } from 'vs/base/common/async'; import { Recording, IInlineChatSessionService, ISessionKeyComputer } from './inlineChatSessionService'; import { HunkData, Session, SessionWholeRange, TelemetryData, TelemetryDataClassification } from './inlineChatSession'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; +import { ITextModel } from 'vs/editor/common/model'; +import { Schemas } from 'vs/base/common/network'; type SessionData = { editor: ICodeEditor; @@ -71,9 +73,9 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { const textModel = editor.getModel(); const selection = editor.getSelection(); - let raw: IInlineChatSession | undefined | null; + let rawSession: IInlineChatSession | undefined | null; try { - raw = await raceCancellation( + rawSession = await raceCancellation( Promise.resolve(provider.prepareInlineChatSession(textModel, selection, token)), token ); @@ -82,7 +84,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { this._logService.error(error); return undefined; } - if (!raw) { + if (!rawSession) { this._logService.trace('[IE] NO session', provider.debugName); return undefined; } @@ -91,35 +93,46 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${provider.debugName}`); const store = new DisposableStore(); - // create: keep a reference to prevent disposal of the "actual" model - const refTextModelN = await this._textModelService.createModelReference(textModel.uri); - store.add(refTextModelN); + const targetUri = textModel.uri; + + let textModelN: ITextModel; + if (options.editMode === EditMode.Preview) { + // AI edits happen in a copy + textModelN = store.add(this._modelService.createModel( + createTextBufferFactoryFromSnapshot(textModel.createSnapshot()), + { languageId: textModel.getLanguageId(), onDidChange: Event.None }, + targetUri.with({ scheme: Schemas.inMemory, query: 'inline-chat-textModelN' }), true + )); + } else { + // AI edits happen in the actual model, keep a reference but make no copy + store.add((await this._textModelService.createModelReference(textModel.uri))); + textModelN = textModel; + } // create: keep a snapshot of the "actual" model - const textModel0 = this._modelService.createModel( + const textModel0 = store.add(this._modelService.createModel( createTextBufferFactoryFromSnapshot(textModel.createSnapshot()), { languageId: textModel.getLanguageId(), onDidChange: Event.None }, - undefined, true - ); - store.add(textModel0); + targetUri.with({ scheme: Schemas.inMemory, query: 'inline-chat-textModel0' }), true + )); let wholeRange = options.wholeRange; if (!wholeRange) { - wholeRange = raw.wholeRange ? Range.lift(raw.wholeRange) : editor.getSelection(); + wholeRange = rawSession.wholeRange ? Range.lift(rawSession.wholeRange) : editor.getSelection(); } - - // install managed-marker for the decoration range - const wholeRangeMgr = new SessionWholeRange(textModel, wholeRange); - store.add(wholeRangeMgr); - - const hunkData = new HunkData(this._editorWorkerService, textModel0, textModel); - store.add(hunkData); - - const session = new Session(options.editMode, textModel0, textModel, provider, raw, wholeRangeMgr, hunkData); + const session = new Session( + options.editMode, + targetUri, + textModel0, + textModelN, + provider, rawSession, + store.add(new SessionWholeRange(textModelN, wholeRange)), + store.add(new HunkData(this._editorWorkerService, textModel0, textModelN)) + ); // store: key -> session - const key = this._key(editor, textModel.uri); + const key = this._key(editor, session.targetUri); if (this._sessions.has(key)) { store.dispose(); throw new Error(`Session already stored for ${key}`); @@ -129,7 +142,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { } moveSession(session: Session, target: ICodeEditor): void { - const newKey = this._key(target, session.textModelN.uri); + const newKey = this._key(target, session.targetUri); const existing = this._sessions.get(newKey); if (existing) { if (existing.session !== session) { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 9ece4b67967..b7009c9184d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { WindowIntervalTimer } from 'vs/base/browser/dom'; -import { coalesceInPlace, equals, tail } from 'vs/base/common/arrays'; -import { AsyncIterableSource, IntervalTimer } from 'vs/base/common/async'; +import { coalesceInPlace } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { themeColorFromId } from 'vs/base/common/themables'; import { ICodeEditor, IViewZone, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; @@ -17,27 +16,26 @@ import { LineSource, RenderOptions, renderLines } from 'vs/editor/browser/widget import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { LineRange } from 'vs/editor/common/core/lineRange'; import { Position } from 'vs/editor/common/core/position'; -import { IRange, Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; -import { LineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; +import { Range } from 'vs/editor/common/core/range'; import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; -import { TextEdit } from 'vs/editor/common/languages'; -import { ICursorStateComputer, IIdentifiedSingleEditOperation, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel, IValidEditOperation, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel, IValidEditOperation, OverviewRulerLane } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewModel'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IProgress, Progress } from 'vs/platform/progress/common/progress'; +import { Progress } from 'vs/platform/progress/common/progress'; import { SaveReason } from 'vs/workbench/common/editor'; -import { countWords, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; +import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { InlineChatFileCreatePreviewWidget, InlineChatLivePreviewWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget'; import { HunkInformation, ReplyResponse, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { HunkState } from './inlineChatSession'; import { assertType } from 'vs/base/common/types'; +import { IModelService } from 'vs/editor/common/services/model'; +import { performAsyncTextEdit, asProgressiveEdit } from './utils'; export interface IEditObserver { start(): void; @@ -53,9 +51,11 @@ export abstract class EditModeStrategy { className: 'inline-chat-block-selection', }); + protected readonly _store = new DisposableStore(); + protected readonly _onDidAccept = this._store.add(new Emitter()); + protected readonly _onDidDiscard = this._store.add(new Emitter()); - protected readonly _onDidAccept = new Emitter(); - protected readonly _onDidDiscard = new Emitter(); + protected _editCount: number = 0; readonly onDidAccept: Event = this._onDidAccept.event; readonly onDidDiscard: Event = this._onDidDiscard.event; @@ -65,12 +65,12 @@ export abstract class EditModeStrategy { constructor( protected readonly _session: Session, + protected readonly _editor: ICodeEditor, protected readonly _zone: InlineChatZoneWidget, ) { } dispose(): void { - this._onDidAccept.dispose(); - this._onDidDiscard.dispose(); + this._store.dispose(); } abstract apply(): Promise; @@ -89,6 +89,35 @@ export abstract class EditModeStrategy { abstract makeChanges(edits: ISingleEditOperation[], obs: IEditObserver): Promise; + protected async _makeChanges(edits: ISingleEditOperation[], obs: IEditObserver, opts: ProgressingEditsOptions | undefined, progress: Progress | undefined): Promise { + + // push undo stop before first edit + if (++this._editCount === 1) { + this._editor.pushUndoStop(); + } + + if (opts) { + // ASYNC + const durationInSec = opts.duration / 1000; + for (const edit of edits) { + const wordCount = countWords(edit.text ?? ''); + const speed = wordCount / durationInSec; + // console.log({ durationInSec, wordCount, speed: wordCount / durationInSec }); + const asyncEdit = asProgressiveEdit(new WindowIntervalTimer(this._zone.domNode), edit, speed, opts.token); + await performAsyncTextEdit(this._session.textModelN, asyncEdit, progress, obs); + } + + } else { + // SYNC + obs.start(); + this._session.textModelN.pushEditOperations(null, edits, (undoEdits) => { + progress?.report(undoEdits); + return null; + }); + obs.stop(); + } + } + abstract undoChanges(altVersionId: number): Promise; abstract renderChanges(response: ReplyResponse): Promise; @@ -106,48 +135,54 @@ export abstract class EditModeStrategy { export class PreviewStrategy extends EditModeStrategy { private readonly _ctxDocumentChanged: IContextKey; - private readonly _listener: IDisposable; constructor( session: Session, + editor: ICodeEditor, zone: InlineChatZoneWidget, + @IModelService modelService: IModelService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super(session, zone); + super(session, editor, zone); this._ctxDocumentChanged = CTX_INLINE_CHAT_DOCUMENT_CHANGED.bindTo(contextKeyService); - this._listener = Event.debounce(session.textModelN.onDidChangeContent.bind(session.textModelN), () => { }, 350)(_ => { - if (!session.textModelN.isDisposed() && !session.textModel0.isDisposed()) { + + const baseModel = modelService.getModel(session.targetUri)!; + Event.debounce(baseModel.onDidChangeContent.bind(baseModel), () => { }, 350)(_ => { + if (!baseModel.isDisposed() && !session.textModel0.isDisposed()) { this._ctxDocumentChanged.set(session.hasChangedText); } - }); + }, undefined, this._store); } override dispose(): void { - this._listener.dispose(); this._ctxDocumentChanged.reset(); super.dispose(); } async apply() { - if (!(this._session.lastExchange?.response instanceof ReplyResponse)) { - return; - } - const editResponse = this._session.lastExchange?.response; - const { textModelN: modelN } = this._session; + // (1) ensure the editor still shows the original text + // (2) accept all pending hunks (moves changes from N to 0) + // (3) replace editor model with textModel0 + const textModel = this._editor.getModel(); + if (textModel?.equalsTextBuffer(this._session.textModel0.getTextBuffer())) { - if (modelN.equalsTextBuffer(this._session.textModel0.getTextBuffer())) { - modelN.pushStackElement(); - for (const edits of editResponse.allLocalEdits) { - modelN.pushEditOperations(null, edits.map(TextEdit.asEditOperation), () => null); + this._session.hunkData.getInfo().forEach(item => item.acceptChanges()); + + const newText = this._session.textModel0.getValue(); + const range = textModel.getFullModelRange(); + + textModel.pushStackElement(); + textModel.pushEditOperations(null, [EditOperation.replace(range, newText)], () => null); + textModel.pushStackElement(); + } + + if (this._session.lastExchange?.response instanceof ReplyResponse) { + const { untitledTextModel } = this._session.lastExchange.response; + if (untitledTextModel && !untitledTextModel.isDisposed() && untitledTextModel.isDirty()) { + await untitledTextModel.save({ reason: SaveReason.EXPLICIT }); } - modelN.pushStackElement(); - } - - const { untitledTextModel } = this._session.lastExchange.response; - if (untitledTextModel && !untitledTextModel.isDisposed() && untitledTextModel.isDirty()) { - await untitledTextModel.save({ reason: SaveReason.EXPLICIT }); } } @@ -155,22 +190,22 @@ export class PreviewStrategy extends EditModeStrategy { // nothing to do } - override async makeChanges(_edits: ISingleEditOperation[]): Promise { - // nothing to do + override async makeChanges(edits: ISingleEditOperation[], obs: IEditObserver): Promise { + return this._makeChanges(edits, obs, undefined, undefined); } - override async undoChanges(_altVersionId: number): Promise { - // nothing to do + override async makeProgressiveChanges(edits: ISingleEditOperation[], obs: IEditObserver, opts: ProgressingEditsOptions): Promise { + return this._makeChanges(edits, obs, opts, undefined); } - override async makeProgressiveChanges(): Promise { - // nothing to do + override async undoChanges(altVersionId: number): Promise { + const { textModelN } = this._session; + await undoModelUntil(textModelN, altVersionId); } override async renderChanges(response: ReplyResponse): Promise { if (response.allLocalEdits.length > 0) { - const allEditOperation = response.allLocalEdits.map(edits => edits.map(TextEdit.asEditOperation)); - await this._zone.widget.showEditsPreview(this._session.textModel0, this._session.textModelN, allEditOperation); + await this._zone.widget.showEditsPreview(this._session.textModel0, this._session.textModelN); } else { this._zone.widget.hideEditsPreview(); } @@ -197,19 +232,16 @@ export class LivePreviewStrategy extends EditModeStrategy { private readonly _previewZone: Lazy; private readonly _diffZonePool: InlineChatLivePreviewWidget[] = []; - private _currentLineRangeGroups: LineRangeMapping[][] = []; - private _editCount: number = 0; constructor( session: Session, - private readonly _editor: ICodeEditor, + editor: ICodeEditor, zone: InlineChatZoneWidget, - @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, @IInstantiationService private readonly _instaService: IInstantiationService, ) { - super(session, zone); + super(session, editor, zone); - this._previewZone = new Lazy(() => _instaService.createInstance(InlineChatFileCreatePreviewWidget, _editor)); + this._previewZone = new Lazy(() => _instaService.createInstance(InlineChatFileCreatePreviewWidget, editor)); } override dispose(): void { @@ -244,153 +276,119 @@ export class LivePreviewStrategy extends EditModeStrategy { await undoModelUntil(modelN, targetAltVersion); } - override async makeChanges(edits: ISingleEditOperation[], obs: IEditObserver): Promise { - const cursorStateComputerAndInlineDiffCollection: ICursorStateComputer = (undoEdits) => { - let last: Position | null = null; - for (const edit of undoEdits) { - last = !last || last.isBefore(edit.range.getEndPosition()) ? edit.range.getEndPosition() : last; - } - return last && [Selection.fromPositions(last)]; - }; - - // push undo stop before first edit - if (++this._editCount === 1) { - this._editor.pushUndoStop(); - } - obs.start(); - this._editor.executeEdits('inline-chat-live', edits, cursorStateComputerAndInlineDiffCollection); - obs.stop(); - } - override async undoChanges(altVersionId: number): Promise { const { textModelN } = this._session; await undoModelUntil(textModelN, altVersionId); - await this._updateDiffZones(); + this._updateDiffZones(); + } + + override async makeChanges(edits: ISingleEditOperation[], obs: IEditObserver): Promise { + return this._makeChanges(edits, obs, undefined, undefined); } override async makeProgressiveChanges(edits: ISingleEditOperation[], obs: IEditObserver, opts: ProgressingEditsOptions): Promise { - - // push undo stop before first edit - if (++this._editCount === 1) { - this._editor.pushUndoStop(); - } - - //add a listener that shows the diff zones as soon as the first edit is applied - let renderTask = Promise.resolve(); - const changeListener = this._session.textModelN.onDidChangeContent(() => { - changeListener.dispose(); - renderTask = this._updateDiffZones(); - }); - - const durationInSec = opts.duration / 1000; - for (const edit of edits) { - const wordCount = countWords(edit.text ?? ''); - const speed = wordCount / durationInSec; - // console.log({ durationInSec, wordCount, speed: wordCount / durationInSec }); - await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(new WindowIntervalTimer(this._zone.domNode), edit, speed, opts.token), undefined, obs); - } - - await renderTask; - changeListener.dispose(); + await this._makeChanges(edits, obs, opts, new Progress(() => { + this._updateDiffZones(); + })); } - override async renderChanges(response: ReplyResponse): Promise { - - await this._updateDiffZones(); + override async renderChanges(response: ReplyResponse): Promise { if (response.untitledTextModel && !response.untitledTextModel.isDisposed()) { this._previewZone.value.showCreation(this._session.wholeRange.value.getStartPosition().delta(-1), response.untitledTextModel); } else { this._previewZone.value.hide(); } + + return this._updateDiffZones(); } - protected _updateSummaryMessage(mappings: readonly LineRangeMapping[]) { - let linesChanged = 0; - for (const change of mappings) { - linesChanged += change.changedLineCount; - } + + protected _updateSummaryMessage(hunkCount: number) { let message: string; - if (linesChanged === 0) { - message = localize('lines.0', "Nothing changed"); - } else if (linesChanged === 1) { - message = localize('lines.1', "Changed 1 line"); + if (hunkCount === 0) { + message = localize('change.0', "Nothing changed"); + } else if (hunkCount === 1) { + message = localize('change.1', "1 change"); } else { - message = localize('lines.N', "Changed {0} lines", linesChanged); + message = localize('lines.NM', "{0} changes", hunkCount); } this._zone.widget.updateStatus(message); } - private async _updateDiffZones() { - const diff = await this._editorWorkerService.computeDiff(this._session.textModel0.uri, this._session.textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000, computeMoves: false }, 'advanced'); - if (!diff || diff.changes.length === 0) { + + private _updateDiffZones(): Position | undefined { + + const { hunkData } = this._session; + const hunks = hunkData.getInfo().filter(hunk => hunk.getState() === HunkState.Pending); + + if (hunks.length === 0) { for (const zone of this._diffZonePool) { zone.hide(); } - return; - } - const originalStartLineNumber = this._session.session.wholeRange?.startLineNumber ?? 1; - - const mainGroup: LineRangeMapping[] = []; - let lastGroup: LineRangeMapping[] | undefined; - const groups: LineRangeMapping[][] = [mainGroup]; - - for (let i = 0; i < diff.changes.length; i++) { - const change = diff.changes[i]; - - // everything below the original start line is one group - if (change.original.startLineNumber >= originalStartLineNumber || 'true') { // TODO@jrieken be smarter and fix this - mainGroup.push(change); - continue; - } - - if (!lastGroup) { - lastGroup = [change]; - groups.push(lastGroup); - continue; - } - - // when the distance between the two changes is less than 75% of the total number of lines changed - // they get merged into the same group - const last = tail(lastGroup); - const treshold = Math.ceil((change.modified.length + last.modified.length) * .75); - if (change.modified.startLineNumber - last.modified.endLineNumberExclusive <= treshold) { - lastGroup.push(change); + if (hunkData.getInfo().find(hunk => hunk.getState() === HunkState.Accepted)) { + this._onDidAccept.fire(); } else { - lastGroup = [change]; - groups.push(lastGroup); + this._onDidDiscard.fire(); } - } - const beforeAndNowAreEqual = equals(this._currentLineRangeGroups, groups, (groupA, groupB) => { - return equals(groupA, groupB, (mappingA, mappingB) => { - return mappingA.original.equals(mappingB.original) && mappingA.modified.equals(mappingB.modified); - }); - }); - - if (beforeAndNowAreEqual) { return; } - this._updateSummaryMessage(diff.changes); - this._currentLineRangeGroups = groups; - - const handleDiff = () => { - this._updateDiffZones(); - }; + this._updateSummaryMessage(hunks.length); // create enough zones - while (groups.length > this._diffZonePool.length) { + const handleDiff = () => this._updateDiffZones(); + + type Data = { position: Position; distance: number; accept: Function; discard: Function }; + let nearest: Data | undefined; + + // create enough zones + while (hunks.length > this._diffZonePool.length) { this._diffZonePool.push(this._instaService.createInstance(InlineChatLivePreviewWidget, this._editor, this._session, {}, this._diffZonePool.length === 0 ? handleDiff : undefined)); } - for (let i = 0; i < groups.length; i++) { - this._diffZonePool[i].showForChanges(groups[i]); + + for (let i = 0; i < hunks.length; i++) { + const hunk = hunks[i]; + this._diffZonePool[i].showForChanges(hunk); + + const modifiedRange = hunk.getRangesN()[0]; + const zoneLineNumber = this._zone.position!.lineNumber; + const distance = zoneLineNumber <= modifiedRange.startLineNumber + ? modifiedRange.startLineNumber - zoneLineNumber + : zoneLineNumber - modifiedRange.endLineNumber; + + if (!nearest || nearest.distance > distance) { + nearest = { + position: modifiedRange.getStartPosition().delta(-1), + distance, + accept: () => { + hunk.acceptChanges(); + handleDiff(); + }, + discard: () => { + hunk.discardChanges(); + handleDiff(); + } + }; + } + } // hide unused zones - for (let i = groups.length; i < this._diffZonePool.length; i++) { + for (let i = hunks.length; i < this._diffZonePool.length; i++) { this._diffZonePool[i].hide(); } + + this.acceptHunk = async () => nearest?.accept(); + this.discardHunk = async () => nearest?.discard(); + + if (nearest) { + this._zone.updatePositionAndHeight(nearest.position); + this._editor.revealPositionInCenterIfOutsideViewport(nearest.position); + } + + return nearest?.position; } override hasFocus(): boolean { @@ -400,79 +398,6 @@ export class LivePreviewStrategy extends EditModeStrategy { } } -export interface AsyncTextEdit { - readonly range: IRange; - readonly newText: AsyncIterable; -} - -export async function performAsyncTextEdit(model: ITextModel, edit: AsyncTextEdit, progress?: IProgress, obs?: IEditObserver) { - - const [id] = model.deltaDecorations([], [{ - range: edit.range, - options: { - description: 'asyncTextEdit', - stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges - } - }]); - - let first = true; - for await (const part of edit.newText) { - - if (model.isDisposed()) { - break; - } - - const range = model.getDecorationRange(id); - if (!range) { - throw new Error('FAILED to perform async replace edit because the anchor decoration was removed'); - } - - const edit = first - ? EditOperation.replace(range, part) // first edit needs to override the "anchor" - : EditOperation.insert(range.getEndPosition(), part); - obs?.start(); - model.pushEditOperations(null, [edit], (undoEdits) => { - progress?.report(undoEdits); - return null; - }); - obs?.stop(); - first = false; - } -} - -export function asProgressiveEdit(interval: IntervalTimer, edit: IIdentifiedSingleEditOperation, wordsPerSec: number, token: CancellationToken): AsyncTextEdit { - - wordsPerSec = Math.max(10, wordsPerSec); - - const stream = new AsyncIterableSource(); - let newText = edit.text ?? ''; - - interval.cancelAndSet(() => { - const r = getNWords(newText, 1); - stream.emitOne(r.value); - newText = newText.substring(r.value.length); - if (r.isFullString) { - interval.cancel(); - stream.resolve(); - d.dispose(); - } - - }, 1000 / wordsPerSec); - - // cancel ASAP - const d = token.onCancellationRequested(() => { - interval.cancel(); - stream.resolve(); - d.dispose(); - }); - - return { - range: edit.range, - newText: stream.asyncIterable - }; -} - - type HunkDisplayData = { decorationIds: string[]; @@ -506,7 +431,6 @@ export class LiveStrategy extends EditModeStrategy { className: 'inline-chat-inserted-range', }); - private readonly _store = new DisposableStore(); private readonly _previewZone: Lazy; private readonly _ctxCurrentChangeHasDiff: IContextKey; @@ -514,32 +438,30 @@ export class LiveStrategy extends EditModeStrategy { private readonly _progressiveEditingDecorations: IEditorDecorationsCollection; - private _editCount: number = 0; override acceptHunk: () => Promise = () => super.acceptHunk(); override discardHunk: () => Promise = () => super.discardHunk(); constructor( session: Session, - protected readonly _editor: ICodeEditor, + editor: ICodeEditor, zone: InlineChatZoneWidget, @IContextKeyService contextKeyService: IContextKeyService, @IEditorWorkerService protected readonly _editorWorkerService: IEditorWorkerService, @IInstantiationService protected readonly _instaService: IInstantiationService, ) { - super(session, zone); + super(session, editor, zone); this._ctxCurrentChangeHasDiff = CTX_INLINE_CHAT_CHANGE_HAS_DIFF.bindTo(contextKeyService); this._ctxCurrentChangeShowsDiff = CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF.bindTo(contextKeyService); this._progressiveEditingDecorations = this._editor.createDecorationsCollection(); - this._previewZone = new Lazy(() => _instaService.createInstance(InlineChatFileCreatePreviewWidget, _editor)); + this._previewZone = new Lazy(() => _instaService.createInstance(InlineChatFileCreatePreviewWidget, editor)); } override dispose(): void { this._resetDiff(); this._previewZone.rawValue?.dispose(); - this._store.dispose(); super.dispose(); } @@ -581,25 +503,15 @@ export class LiveStrategy extends EditModeStrategy { } override async undoChanges(altVersionId: number): Promise { - const { textModelN } = this._session; await undoModelUntil(textModelN, altVersionId); } override async makeChanges(edits: ISingleEditOperation[], obs: IEditObserver): Promise { - return this._makeChanges(edits, obs, undefined); + return this._makeChanges(edits, obs, undefined, undefined); } override async makeProgressiveChanges(edits: ISingleEditOperation[], obs: IEditObserver, opts: ProgressingEditsOptions): Promise { - return this._makeChanges(edits, obs, opts); - } - - private async _makeChanges(edits: ISingleEditOperation[], obs: IEditObserver, opts: ProgressingEditsOptions | undefined): Promise { - - // push undo stop before first edit - if (++this._editCount === 1) { - this._editor.pushUndoStop(); - } // add decorations once per line that got edited const progress = new Progress(edits => { @@ -619,26 +531,7 @@ export class LiveStrategy extends EditModeStrategy { this._progressiveEditingDecorations.append(newDecorations); }); - - if (opts) { - // ASYNC - const durationInSec = opts.duration / 1000; - for (const edit of edits) { - const wordCount = countWords(edit.text ?? ''); - const speed = wordCount / durationInSec; - // console.log({ durationInSec, wordCount, speed: wordCount / durationInSec }); - await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(new WindowIntervalTimer(this._zone.domNode), edit, speed, opts.token), progress, obs); - } - - } else { - // SYNC - obs.start(); - this._editor.executeEdits('inline-chat-live', edits, undoEdits => { - progress.report(undoEdits); - return null; - }); - obs.stop(); - } + return this._makeChanges(edits, obs, opts, progress); } private readonly _hunkDisplayData = new Map(); @@ -712,7 +605,7 @@ export class LiveStrategy extends EditModeStrategy { const scrollState = StableEditorScrollState.capture(this._editor); changeDecorationsAndViewZones(this._editor, (_decorationsAccessor, viewZoneAccessor) => { assertType(data); - if (!data.viewZone) { + if (!data.viewZoneId) { const [hunkRange] = hunkData.getRangesN(); viewZoneData.afterLineNumber = hunkRange.startLineNumber - 1; data.viewZoneId = viewZoneAccessor.addZone(viewZoneData); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index fa5e8dc762c..ae291d9d462 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -29,14 +29,11 @@ import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestCont import { Position } from 'vs/editor/common/core/position'; import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; import { CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages'; -import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; -import { ILanguageSelection } from 'vs/editor/common/languages/language'; import { ResourceLabel } from 'vs/workbench/browser/labels'; import { FileKind } from 'vs/platform/files/common/files'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { LanguageSelector } from 'vs/editor/common/languageSelector'; -import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; -import { invertLineRange, lineRangeAsRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; +import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; import { LineRange } from 'vs/editor/common/core/lineRange'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; @@ -388,10 +385,6 @@ export class InlineChatWidget { toolbarOptions: { primaryGroup: 'main' } })); - this._progressBar = new ProgressBar(this._elements.progress); - this._store.add(this._progressBar); - - const workbenchMenubarOptions: IWorkbenchButtonBarOptions = { telemetrySource: 'interactiveEditorWidget-toolbar', buttonConfigProvider: action => { @@ -423,6 +416,7 @@ export class InlineChatWidget { // preview editors this._previewDiffEditor = new Lazy(() => this._store.add(_instantiationService.createInstance(EmbeddedDiffEditorWidget, this._elements.previewDiff, { + useInlineViewWhenSpaceIsLimited: false, ..._previewEditorEditorOptions, onlyShowAccessibleDiffViewer: this._accessibilityService.isScreenReaderOptimized(), }, { modifiedEditor: codeEditorWidgetOptions, originalEditor: codeEditorWidgetOptions }, parentEditor))); @@ -472,21 +466,23 @@ export class InlineChatWidget { return this._elements.root; } - layout(dim: Dimension) { + layout(_dim: Dimension) { this._isLayouting = true; try { const widgetToolbarWidth = getTotalWidth(this._elements.widgetToolbar); - const innerEditorWidth = dim.width - (getTotalWidth(this._elements.editorToolbar) + 8 /* L/R-padding */) - (widgetToolbarWidth); - dim = new Dimension(innerEditorWidth, dim.height); + const editorToolbarWidth = getTotalWidth(this._elements.editorToolbar) + 8 /* L/R-padding */; + const innerEditorWidth = _dim.width - editorToolbarWidth - widgetToolbarWidth; + const dim = new Dimension(innerEditorWidth, _dim.height); if (!this._lastDim || !Dimension.equals(this._lastDim, dim)) { this._lastDim = dim; this._inputEditor.layout(new Dimension(innerEditorWidth, this._inputEditor.getContentHeight())); this._elements.placeholder.style.width = `${innerEditorWidth /* input-padding*/}px`; if (this._previewDiffEditor.hasValue) { - const previewDiffDim = new Dimension(dim.width, Math.min(300, Math.max(0, this._previewDiffEditor.value.getContentHeight()))); - this._previewDiffEditor.value.layout(previewDiffDim); + const previewDiffDim = new Dimension(_dim.width - 12, Math.min(300, Math.max(0, this._previewDiffEditor.value.getContentHeight()))); + this._elements.previewDiff.style.width = `${previewDiffDim.width}px`; this._elements.previewDiff.style.height = `${previewDiffDim.height}px`; + this._previewDiffEditor.value.layout(previewDiffDim); } if (this._previewCreateEditor.hasValue) { @@ -521,9 +517,11 @@ export class InlineChatWidget { updateProgress(show: boolean) { if (show) { + this._progressBar.show(); this._progressBar.infinite(); } else { this._progressBar.stop(); + this._progressBar.hide(); } } @@ -751,23 +749,17 @@ export class InlineChatWidget { // --- preview - async showEditsPreview(textModel0: ITextModel, textModelN: ITextModel, allEdits: ISingleEditOperation[][]) { + async showEditsPreview(textModel0: ITextModel, textModelN: ITextModel) { this._elements.previewDiff.classList.remove('hidden'); - const languageSelection: ILanguageSelection = { languageId: textModel0.getLanguageId(), onDidChange: Event.None }; - const modified = this._modelService.createModel(createTextBufferFactoryFromSnapshot(textModel0.createSnapshot()), languageSelection, undefined, true); - for (const edits of allEdits) { - modified.applyEdits(edits, false); - } - - const diff = await this._editorWorkerService.computeDiff(textModel0.uri, modified.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000, computeMoves: false }, 'advanced'); + const diff = await this._editorWorkerService.computeDiff(textModel0.uri, textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000, computeMoves: false }, 'advanced'); if (!diff || diff.changes.length === 0) { this.hideEditsPreview(); return; } - this._previewDiffEditor.value.setModel({ original: textModel0, modified }); + this._previewDiffEditor.value.setModel({ original: textModel0, modified: textModelN }); // joined ranges let originalLineRange = diff.changes[0].original; @@ -783,15 +775,15 @@ export class InlineChatWidget { modifiedLineRange = new LineRange(newStartLine, modifiedLineRange.endLineNumberExclusive); originalLineRange = new LineRange(newStartLine, originalLineRange.endLineNumberExclusive); - const newEndLineModified = Math.min(modifiedLineRange.endLineNumberExclusive + pad, modified.getLineCount()); + const newEndLineModified = Math.min(modifiedLineRange.endLineNumberExclusive + pad, textModelN.getLineCount()); modifiedLineRange = new LineRange(modifiedLineRange.startLineNumber, newEndLineModified); const newEndLineOriginal = Math.min(originalLineRange.endLineNumberExclusive + pad, textModel0.getLineCount()); originalLineRange = new LineRange(originalLineRange.startLineNumber, newEndLineOriginal); const hiddenOriginal = invertLineRange(originalLineRange, textModel0); - const hiddenModified = invertLineRange(modifiedLineRange, modified); - this._previewDiffEditor.value.getOriginalEditor().setHiddenAreas(hiddenOriginal.map(lineRangeAsRange), 'diff-hidden'); - this._previewDiffEditor.value.getModifiedEditor().setHiddenAreas(hiddenModified.map(lineRangeAsRange), 'diff-hidden'); + const hiddenModified = invertLineRange(modifiedLineRange, textModelN); + this._previewDiffEditor.value.getOriginalEditor().setHiddenAreas(hiddenOriginal.map(lr => asRange(lr, textModel0)), 'diff-hidden'); + this._previewDiffEditor.value.getModifiedEditor().setHiddenAreas(hiddenModified.map(lr => asRange(lr, textModelN)), 'diff-hidden'); this._previewDiffEditor.value.revealLine(modifiedLineRange.startLineNumber, ScrollType.Immediate); this._onDidChangeHeight.fire(); diff --git a/src/vs/workbench/contrib/inlineChat/browser/utils.ts b/src/vs/workbench/contrib/inlineChat/browser/utils.ts index 43364755deb..fbd21af7b65 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/utils.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/utils.ts @@ -3,9 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { EditOperation } from 'vs/editor/common/core/editOperation'; import { LineRange } from 'vs/editor/common/core/lineRange'; -import { Range } from 'vs/editor/common/core/range'; -import { ITextModel } from 'vs/editor/common/model'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { IIdentifiedSingleEditOperation, ITextModel, IValidEditOperation, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { IEditObserver } from './inlineChatStrategies'; +import { IProgress } from 'vs/platform/progress/common/progress'; +import { IntervalTimer, AsyncIterableSource } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; export function invertLineRange(range: LineRange, model: ITextModel): LineRange[] { if (range.isEmpty) { @@ -21,12 +27,82 @@ export function invertLineRange(range: LineRange, model: ITextModel): LineRange[ return result.filter(r => !r.isEmpty); } -export function lineRangeAsRange(r: LineRange): Range { - return new Range(r.startLineNumber, 1, r.endLineNumberExclusive - 1, 1); -} - export function asRange(lineRange: LineRange, model: ITextModel): Range { return lineRange.isEmpty ? new Range(lineRange.startLineNumber, 1, lineRange.startLineNumber, model.getLineLength(lineRange.startLineNumber)) : new Range(lineRange.startLineNumber, 1, lineRange.endLineNumberExclusive - 1, model.getLineLength(lineRange.endLineNumberExclusive - 1)); } + +// --- async edit + +export interface AsyncTextEdit { + readonly range: IRange; + readonly newText: AsyncIterable; +} + +export async function performAsyncTextEdit(model: ITextModel, edit: AsyncTextEdit, progress?: IProgress, obs?: IEditObserver) { + + const [id] = model.deltaDecorations([], [{ + range: edit.range, + options: { + description: 'asyncTextEdit', + stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges + } + }]); + + let first = true; + for await (const part of edit.newText) { + + if (model.isDisposed()) { + break; + } + + const range = model.getDecorationRange(id); + if (!range) { + throw new Error('FAILED to perform async replace edit because the anchor decoration was removed'); + } + + const edit = first + ? EditOperation.replace(range, part) // first edit needs to override the "anchor" + : EditOperation.insert(range.getEndPosition(), part); + obs?.start(); + model.pushEditOperations(null, [edit], (undoEdits) => { + progress?.report(undoEdits); + return null; + }); + obs?.stop(); + first = false; + } +} + +export function asProgressiveEdit(interval: IntervalTimer, edit: IIdentifiedSingleEditOperation, wordsPerSec: number, token: CancellationToken): AsyncTextEdit { + + wordsPerSec = Math.max(10, wordsPerSec); + + const stream = new AsyncIterableSource(); + let newText = edit.text ?? ''; + + interval.cancelAndSet(() => { + const r = getNWords(newText, 1); + stream.emitOne(r.value); + newText = newText.substring(r.value.length); + if (r.isFullString) { + interval.cancel(); + stream.resolve(); + d.dispose(); + } + + }, 1000 / wordsPerSec); + + // cancel ASAP + const d = token.onCancellationRequested(() => { + interval.cancel(); + stream.resolve(); + d.dispose(); + }); + + return { + range: edit.range, + newText: stream.asyncIterable + }; +} diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts index e21cc59da32..0c6a58b4036 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts @@ -431,6 +431,28 @@ suite('InlineChatSession', function () { assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'three'].join('\n')); }); + test('HunkData, (mirror) edit after dicard ', async function () { + + const lines = ['one', 'two', 'three']; + model.setValue(lines.join('\n')); + const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(session); + + await makeEditAsAi([EditOperation.insert(new Position(3, 1), 'AI WAS HERE\n')]); + assert.strictEqual(session.textModelN.getValue(), ['one', 'two', 'AI WAS HERE', 'three'].join('\n')); + assert.strictEqual(session.textModel0.getValue(), lines.join('\n')); + + assert.strictEqual(session.hunkData.size, 1); + const [hunk] = session.hunkData.getInfo(); + hunk.discardChanges(); + assert.strictEqual(session.textModelN.getValue(), lines.join('\n')); + assert.strictEqual(session.textModel0.getValue(), lines.join('\n')); + + makeEdit([EditOperation.replace(new Range(3, 4, 3, 6), '3333')]); + assert.strictEqual(session.textModelN.getValue(), ['one', 'two', 'thr3333'].join('\n')); + assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'thr3333'].join('\n')); + }); + test('HunkData, (mirror) edit after, multi turn', async function () { const lines = ['one', 'two', 'three', 'four', 'five']; @@ -483,6 +505,12 @@ suite('InlineChatSession', function () { makeEdit([EditOperation.replace(new Range(6, 3, 6, 5), 'vefivefi')]); assert.strictEqual(session.textModelN.getValue(), ['one', 'twozwei', 'AI_EDIT', 'three', 'FOOfour', 'fivefivefi'].join('\n')); assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'three', 'FOOfour', 'fivefivefi'].join('\n')); + + session.hunkData.getInfo()[0].acceptChanges(); + assert.strictEqual(session.textModelN.getValue(), session.textModel0.getValue()); + + makeEdit([EditOperation.replace(new Range(1, 1, 1, 1), 'done')]); + assert.strictEqual(session.textModelN.getValue(), session.textModel0.getValue()); }); }); diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatStrategies.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatStrategies.test.ts index d91aa020190..e8d229d81fc 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatStrategies.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatStrategies.test.ts @@ -6,7 +6,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IntervalTimer } from 'vs/base/common/async'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { asProgressiveEdit } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; +import { asProgressiveEdit } from '../../browser/utils'; import * as assert from 'assert'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts index cfc2b2fc2d3..145bd1772e8 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts @@ -31,7 +31,8 @@ import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { EmptyResponse, ErrorResponse, ReplyResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; -import { ProgressingEditsOptions, asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; +import { ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; +import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_VISIBLE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; From b921c3efc15afff6a1387448756d2cdb6c66ad76 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 19 Jan 2024 18:21:31 +0530 Subject: [PATCH 0465/1897] report telemetry only for opted in settings (#202844) --- .../browser/workbench.contribution.ts | 3 +- .../browser/extensions.contribution.ts | 2 +- .../browser/configurationService.ts | 61 ++++++++++--------- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 6153d527c98..bca80d33bb9 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -504,7 +504,8 @@ const registry = Registry.as(ConfigurationExtensions.Con localize('workbench.activityBar.location.side', "Show the Activity Bar to the side of the Primary Side Bar."), localize('workbench.activityBar.location.top', "Show the Activity Bar on top of the Primary Side Bar."), localize('workbench.activityBar.location.hide', "Hide the Activity Bar.") - ] + ], + tags: ['FeatureInsight'] }, 'workbench.activityBar.iconClickBehavior': { 'type': 'string', diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index b8f44f8df23..de4e96c1c25 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -146,7 +146,7 @@ Registry.as(ConfigurationExtensions.Configuration) description: localize('extensions.autoUpdate', "Controls the automatic update behavior of extensions. The updates are fetched from a Microsoft online service."), default: true, scope: ConfigurationScope.APPLICATION, - tags: ['usesOnlineServices'] + tags: ['usesOnlineServices', 'FeatureInsight'] }, 'extensions.autoCheckUpdates': { type: 'boolean', diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 6b842ab9400..4057b5bc7cc 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -15,9 +15,9 @@ import { ConfigurationModel, ConfigurationChangeEvent, mergeChanges } from 'vs/p import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString, IConfigurationUpdateOverrides, isConfigurationUpdateOverrides, IConfigurationService, IConfigurationUpdateOptions } from 'vs/platform/configuration/common/configuration'; import { IPolicyConfiguration, NullPolicyConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; -import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings, PROFILE_SCOPES, LOCAL_MACHINE_PROFILE_SCOPES, profileSettingsSchemaId, APPLY_ALL_PROFILES_SETTING, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings, PROFILE_SCOPES, LOCAL_MACHINE_PROFILE_SCOPES, profileSettingsSchemaId, APPLY_ALL_PROFILES_SETTING } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema, keyFromOverrideIdentifiers, OVERRIDE_PROPERTY_PATTERN, resourceLanguageSettingsSchemaId, configurationDefaultsSchemaId } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema, keyFromOverrideIdentifiers, OVERRIDE_PROPERTY_PATTERN, resourceLanguageSettingsSchemaId, configurationDefaultsSchemaId, IRegisteredConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, getStoredWorkspaceFolder, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ConfigurationEditing, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; @@ -1325,48 +1325,49 @@ class ResetConfigurationDefaultsOverridesCache extends Disposable implements IWo } } -class ConfigurationTelemetryContribution extends Disposable implements IWorkbenchContribution { +class ConfigurationTelemetryContribution implements IWorkbenchContribution { private readonly configurationRegistry = Registry.as(Extensions.Configuration); constructor( @IConfigurationService private readonly configurationService: WorkspaceService, - @ITelemetryService telemetryService: ITelemetryService, + @ITelemetryService private readonly telemetryService: ITelemetryService, ) { - super(); - const { user, workspace } = configurationService.keys(); - const updatedUserSettings: { [key: string]: any } = {}; - for (const k of user) { - if (!k.startsWith(`${TASKS_CONFIGURATION_KEY}.`) && !k.startsWith(`${LAUNCH_CONFIGURATION_KEY}.`)) { - updatedUserSettings[k] = this.getValueToReport(k, ConfigurationTarget.USER_LOCAL) ?? ''; - } - } - const updatedWorkspaceSettings: { [key: string]: any } = {}; - for (const k of workspace) { - if (!k.startsWith(`${TASKS_CONFIGURATION_KEY}.`) && !k.startsWith(`${LAUNCH_CONFIGURATION_KEY}.`)) { - updatedWorkspaceSettings[k] = this.getValueToReport(k, ConfigurationTarget.WORKSPACE) ?? ''; - } - } - type UpdatedSettingsClassification = { + type UpdatedSettingClassification = { owner: 'sandy081'; - comment: 'Event reporting updated settings'; - settings: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'stringified updated settings object' }; + comment: 'Event reporting the updated setting'; + setting: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'name of the setting' }; + value: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'value of the setting' }; + source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'source of the setting' }; }; - type UpdatedSettingsEvent = { - settings: string; + type UpdatedSettingEvent = { + setting: string; + value: string; + source: string; }; - telemetryService.publicLog2('updatedsettings:user', { settings: JSON.stringify(updatedUserSettings) }); - telemetryService.publicLog2('updatedsettings:workspace', { settings: JSON.stringify(updatedWorkspaceSettings) }); + const { user, workspace } = configurationService.keys(); + const userSource = ConfigurationTargetToString(ConfigurationTarget.USER_LOCAL); + for (const setting of user) { + const schema = this.configurationRegistry.getConfigurationProperties()[setting]; + if (schema.tags?.includes('FeatureInsight')) { + const value = this.getValueToReport(setting, ConfigurationTarget.USER_LOCAL, schema); + this.telemetryService.publicLog2('updatedsetting', { setting, value, source: userSource }); + } + } + const worskpaceSource = ConfigurationTargetToString(ConfigurationTarget.WORKSPACE); + for (const setting of workspace) { + const schema = this.configurationRegistry.getConfigurationProperties()[setting]; + if (schema.tags?.includes('FeatureInsight')) { + const value = this.getValueToReport(setting, ConfigurationTarget.WORKSPACE, schema); + this.telemetryService.publicLog2('updatedsetting', { setting, value, source: worskpaceSource }); + } + } } /** * Report value of a setting only if it is an enum, boolean, or number or an array of those. */ - private getValueToReport(key: string, target: ConfigurationTarget): any { - const schema = this.configurationRegistry.getConfigurationProperties()[key]; - if (!schema) { - return undefined; - } + private getValueToReport(key: string, target: ConfigurationTarget, schema: IRegisteredConfigurationPropertySchema): any { const configurationModel = this.configurationService.getConfigurationModel(target); const value = configurationModel?.getValue(key); if (isNumber(value) || isBoolean(value)) { From 53661a149511a8ba4afd9e0fd9bfbedbc777664a Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 19 Jan 2024 14:40:06 +0100 Subject: [PATCH 0466/1897] focus editor back when quick voice ends --- .../contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index 8c7f9e05b67..8be1350ac93 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -258,6 +258,7 @@ export class InlineChatQuickVoice implements IEditorContribution { listener.dispose(); this._widget.hide(); this._ctxQuickChatInProgress.reset(); + this._editor.focus(); if (!abort && message) { InlineChatController.get(this._editor)?.run({ message, autoSend: true }); From 53e467c211f370ca4f38fadece02ec664efbf8da Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 19 Jan 2024 15:14:39 +0100 Subject: [PATCH 0467/1897] fix https://github.com/microsoft/vscode-copilot/issues/315 --- .../browser/inlineChatController.ts | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 2318b0bdd06..9c89e11d7ba 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -106,6 +106,7 @@ export class InlineChatController implements IEditorContribution { private _historyCandidate: string = ''; private _historyUpdate: (prompt: string) => void; + private _isDisposed: boolean = false; private readonly _store = new DisposableStore(); private readonly _zone: Lazy; private readonly _ctxHasActiveRequest: IContextKey; @@ -189,12 +190,15 @@ export class InlineChatController implements IEditorContribution { }; } - dispose(): void { - if (this._session) { - this._inlineChatSessionService.releaseSession(this._session); + async dispose(): Promise { + if (this._currentRun) { + this._messages.fire((this._session?.lastExchange + ? Message.PAUSE_SESSION + : Message.CANCEL_SESSION) + ); } - this._strategy?.dispose(); this._store.dispose(); + this._isDisposed = true; this._log('DISPOSED controller'); } @@ -262,7 +266,7 @@ export class InlineChatController implements IEditorContribution { protected async _nextState(state: State, options: InlineChatRunOptions): Promise { let nextState: State | void = state; - while (nextState) { + while (nextState && !this._isDisposed) { this._log('setState to ', nextState); nextState = await this[nextState](options); } @@ -457,20 +461,20 @@ export class InlineChatController implements IEditorContribution { store.dispose(); } - this._zone.value.widget.selectAll(false); if (message & (Message.CANCEL_INPUT | Message.CANCEL_SESSION)) { return State.CANCEL; } - if (message & Message.ACCEPT_SESSION) { - return State.ACCEPT; - } - if (message & Message.PAUSE_SESSION) { return State.PAUSE; } + if (message & Message.ACCEPT_SESSION) { + this._zone.value.widget.selectAll(false); + return State.ACCEPT; + } + if (message & Message.RERUN_INPUT && this._session.lastExchange) { const { lastExchange } = this._session; if (options.withIntentDetection === undefined) { // @ulugbekna: if we're re-running with intent detection turned off, no need to update `attempt` # From 335b2d5fd4dfc9909b36d10ad6cede7243152f4a Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 19 Jan 2024 15:29:42 +0100 Subject: [PATCH 0468/1897] quick voice should wrap when getting too long --- build/lib/stylelint/vscode-known-variables.json | 2 ++ .../inlineChat/electron-sandbox/inlineChatQuickVoice.css | 4 +++- .../inlineChat/electron-sandbox/inlineChatQuickVoice.ts | 7 +++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 96124fe8a87..0b1767fba21 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -778,6 +778,8 @@ "--vscode-hover-whiteSpace", "--vscode-inline-chat-cropped", "--vscode-inline-chat-expanded", + "--vscode-inline-chat-quick-voice-height", + "--vscode-inline-chat-quick-voice-width", "--vscode-interactive-session-foreground", "--vscode-interactive-result-editor-background-color", "--vscode-repl-font-family", diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.css b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.css index 64ff58649ee..f62e5b2852b 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.css +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.css @@ -10,8 +10,10 @@ display: flex; align-items: center; box-shadow: 0 4px 8px var(--vscode-inlineChat-shadow); - white-space: nowrap; z-index: 1000; + min-height: var(--vscode-inline-chat-quick-voice-height); + line-height: var(--vscode-inline-chat-quick-voice-height); + max-width: var(--vscode-inline-chat-quick-voice-width); } .monaco-editor .inline-chat-quick-voice .codicon.codicon-mic-filled { diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index 8be1350ac93..ec0e5c60c80 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -107,6 +107,7 @@ export class CancelAction extends EditorAction2 { class QuickVoiceWidget implements IContentWidget { readonly suppressMouseDown = true; + readonly allowEditorOverflow = true; private readonly _domNode = document.createElement('div'); private readonly _elements = dom.h('.inline-chat-quick-voice@main', [ @@ -157,8 +158,10 @@ class QuickVoiceWidget implements IContentWidget { beforeRender(): IDimension | null { const lineHeight = this._editor.getOption(EditorOption.lineHeight); - this._elements.main.style.lineHeight = `${lineHeight}px`; - this._elements.main.style.height = `${lineHeight}px`; + const width = this._editor.getLayoutInfo().contentWidth * 0.7; + + this._elements.main.style.setProperty('--vscode-inline-chat-quick-voice-height', `${lineHeight}px`); + this._elements.main.style.setProperty('--vscode-inline-chat-quick-voice-width', `${width}px`); return null; } From 84477e958ea604dc280f825d4002aabe9e62318b Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 19 Jan 2024 15:48:42 +0100 Subject: [PATCH 0469/1897] render recognizing and recognized text differently --- .../electron-sandbox/inlineChatQuickVoice.css | 6 +++++- .../electron-sandbox/inlineChatQuickVoice.ts | 21 ++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.css b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.css index f62e5b2852b..61d96056a00 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.css +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.css @@ -40,6 +40,10 @@ } } -.monaco-editor .inline-chat-quick-voice .message.preview { +.monaco-editor .inline-chat-quick-voice .preview { opacity: .4; } + +.monaco-editor .inline-chat-quick-voice .message:not(:empty) { + margin-right: 0.4ch; +} diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index ec0e5c60c80..1e33dc7e5cd 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -112,7 +112,10 @@ class QuickVoiceWidget implements IContentWidget { private readonly _domNode = document.createElement('div'); private readonly _elements = dom.h('.inline-chat-quick-voice@main', [ dom.h('span@mic'), - dom.h('span.message@message'), + dom.h('span', [ + dom.h('span.message@message'), + dom.h('span.preview@preview'), + ]) ]); private _focusTracker: dom.IFocusTracker | undefined; @@ -174,9 +177,9 @@ class QuickVoiceWidget implements IContentWidget { // --- - updateInput(input: string | undefined, isDefinite: boolean): void { - this._elements.message.classList.toggle('preview', !isDefinite); - this._elements.message.textContent = input ?? ''; + updateInput(data: { message?: string; preview?: string }): void { + this._elements.message.textContent = data.message ?? ''; + this._elements.preview.textContent = data.preview ?? ''; } show() { @@ -189,7 +192,8 @@ class QuickVoiceWidget implements IContentWidget { hide() { this._elements.main.classList.remove('recording'); - this.updateInput(undefined, true); + this._elements.message.textContent = ''; + this._elements.preview.textContent = ''; this._editor.removeContentWidget(this); this._focusTracker?.dispose(); } @@ -233,6 +237,7 @@ export class InlineChatQuickVoice implements IEditorContribution { this._ctxQuickChatInProgress.set(true); let message: string | undefined; + let preview: string | undefined; const session = this._speechService.createSpeechToTextSession(cts.token); const listener = session.onDidChange(e => { @@ -247,11 +252,13 @@ export class InlineChatQuickVoice implements IEditorContribution { case SpeechToTextStatus.Stopped: break; case SpeechToTextStatus.Recognizing: - this._widget.updateInput(!message ? e.text : `${message} ${e.text}`, false); + preview = e.text; + this._widget.updateInput({ message, preview }); break; case SpeechToTextStatus.Recognized: message = !message ? e.text : `${message} ${e.text}`; - this._widget.updateInput(message, true); + preview = ''; + this._widget.updateInput({ message, preview }); break; } }); From ceb5e559bde77f42daf8b1ac8262939390ffb335 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 19 Jan 2024 16:10:55 +0100 Subject: [PATCH 0470/1897] fix compilo --- .../contrib/inlineChat/browser/inlineChatController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 9c89e11d7ba..262736553ab 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -190,7 +190,7 @@ export class InlineChatController implements IEditorContribution { }; } - async dispose(): Promise { + dispose(): void { if (this._currentRun) { this._messages.fire((this._session?.lastExchange ? Message.PAUSE_SESSION From 41ec830a62fffa2596c07f043ec2a240cfab7053 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 19 Jan 2024 08:19:09 -0700 Subject: [PATCH 0471/1897] Add extra test case for log error and common properties (#202858) * Add an additional test for common properties * Add one more test --- .../api/test/browser/extHostTelemetry.test.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/vs/workbench/api/test/browser/extHostTelemetry.test.ts b/src/vs/workbench/api/test/browser/extHostTelemetry.test.ts index d38fb955373..0f79b399d9d 100644 --- a/src/vs/workbench/api/test/browser/extHostTelemetry.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTelemetry.test.ts @@ -215,6 +215,34 @@ suite('ExtHostTelemetry', function () { }); + test('Log error should get common properties #193205', function () { + const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false }; + + const logger = createLogger(functionSpy, undefined, { additionalCommonProperties: { 'common.foo': 'bar' } }); + logger.logError(new Error('Test error')); + assert.strictEqual(functionSpy.exceptionArr.length, 1); + assert.strictEqual(functionSpy.exceptionArr[0].data['common.foo'], 'bar'); + assert.strictEqual(functionSpy.exceptionArr[0].data['common.product'], 'test'); + + logger.logError('test-error-event'); + assert.strictEqual(functionSpy.dataArr.length, 1); + assert.strictEqual(functionSpy.dataArr[0].data['common.foo'], 'bar'); + assert.strictEqual(functionSpy.dataArr[0].data['common.product'], 'test'); + + logger.logError('test-error-event', { 'test-data': 'test-data' }); + assert.strictEqual(functionSpy.dataArr.length, 2); + assert.strictEqual(functionSpy.dataArr[1].data['common.foo'], 'bar'); + assert.strictEqual(functionSpy.dataArr[1].data['common.product'], 'test'); + + logger.logError('test-error-event', { properties: { 'test-data': 'test-data' } }); + assert.strictEqual(functionSpy.dataArr.length, 3); + assert.strictEqual(functionSpy.dataArr[2].data.properties['common.foo'], 'bar'); + assert.strictEqual(functionSpy.dataArr[2].data.properties['common.product'], 'test'); + + logger.dispose(); + assert.strictEqual(functionSpy.flushCalled, true); + }); + test('Ensure logger properly cleans PII', function () { const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false }; From 79cb8061ed8860f16fb660ced34a160c286b505f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 19 Jan 2024 17:04:06 +0100 Subject: [PATCH 0472/1897] fix: use correct max-old-space-size flag syntax, increase mem limit for extensions-ci (#202862) fixes #202720 --- .github/workflows/basic.yml | 2 +- .github/workflows/ci.yml | 6 +++--- package.json | 34 +++++++++++++++++----------------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index 92c75ebdd9a..b0f7d8de27c 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -61,7 +61,7 @@ jobs: run: yarn --frozen-lockfile --network-timeout 180000 - name: Compile and Download - run: yarn npm-run-all --max_old_space_size=4095 -lp compile "electron x64" + run: yarn npm-run-all --max-old-space-size=4095 -lp compile "electron x64" - name: Run Unit Tests id: electron-unit-tests diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b75e45b49c5..94a552b5fb8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,7 +69,7 @@ jobs: 7z.exe a .build/node_modules_cache/cache.7z -mx3 `@.build/node_modules_list.txt - name: Compile and Download - run: yarn npm-run-all --max_old_space_size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions + run: yarn npm-run-all --max-old-space-size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions - name: Compile Integration Tests run: yarn --cwd test/integration/browser compile @@ -145,7 +145,7 @@ jobs: run: yarn --frozen-lockfile --network-timeout 180000 - name: Compile and Download - run: yarn npm-run-all --max_old_space_size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions + run: yarn npm-run-all --max-old-space-size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions - name: Compile Integration Tests run: yarn --cwd test/integration/browser compile @@ -216,7 +216,7 @@ jobs: run: yarn --frozen-lockfile --network-timeout 180000 - name: Compile and Download - run: yarn npm-run-all --max_old_space_size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions + run: yarn npm-run-all --max-old-space-size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions - name: Compile Integration Tests run: yarn --cwd test/integration/browser compile diff --git a/package.json b/package.json index 5a1353b031e..b2214715728 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "test-extension": "vscode-test", "preinstall": "node build/npm/preinstall.js", "postinstall": "node build/npm/postinstall.js", - "compile": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile", + "compile": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js compile", "watch": "npm-run-all -lp watch-client watch-extensions", "watchd": "deemon yarn watch", "watch-webd": "deemon yarn watch-web", @@ -24,14 +24,14 @@ "kill-watch-webd": "deemon --kill yarn watch-web", "restart-watchd": "deemon --restart yarn watch", "restart-watch-webd": "deemon --restart yarn watch-web", - "watch-client": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js watch-client", + "watch-client": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js watch-client", "watch-clientd": "deemon yarn watch-client", "kill-watch-clientd": "deemon --kill yarn watch-client", - "watch-extensions": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js watch-extensions watch-extension-media", + "watch-extensions": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js watch-extensions watch-extension-media", "watch-extensionsd": "deemon yarn watch-extensions", "kill-watch-extensionsd": "deemon --kill yarn watch-extensions", "precommit": "node build/hygiene.js", - "gulp": "node --max_old_space_size=8192 ./node_modules/gulp/bin/gulp.js", + "gulp": "node --max-old-space-size=8192 ./node_modules/gulp/bin/gulp.js", "electron": "node build/lib/electron", "7z": "7z", "update-grammars": "node build/npm/update-all-grammars.mjs", @@ -47,22 +47,22 @@ "update-distro": "node build/npm/update-distro.mjs", "web": "echo 'yarn web' is replaced by './scripts/code-server' or './scripts/code-web'", "compile-cli": "gulp compile-cli", - "compile-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile-web", - "watch-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js watch-web", - "watch-cli": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js watch-cli", + "compile-web": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js compile-web", + "watch-web": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js watch-web", + "watch-cli": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js watch-cli", "eslint": "node build/eslint", "stylelint": "node build/stylelint", "playwright-install": "node build/azure-pipelines/common/installPlaywright.js", - "compile-build": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile-build", - "compile-extensions-build": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile-extensions-build", - "minify-vscode": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js minify-vscode", - "minify-vscode-reh": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js minify-vscode-reh", - "minify-vscode-reh-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js minify-vscode-reh-web", - "hygiene": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js hygiene", - "core-ci": "node --max_old_space_size=8095 ./node_modules/gulp/bin/gulp.js core-ci", - "core-ci-pr": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js core-ci-pr", - "extensions-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js extensions-ci", - "extensions-ci-pr": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js extensions-ci-pr", + "compile-build": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js compile-build", + "compile-extensions-build": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js compile-extensions-build", + "minify-vscode": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js minify-vscode", + "minify-vscode-reh": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js minify-vscode-reh", + "minify-vscode-reh-web": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js minify-vscode-reh-web", + "hygiene": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js hygiene", + "core-ci": "node --max-old-space-size=8095 ./node_modules/gulp/bin/gulp.js core-ci", + "core-ci-pr": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js core-ci-pr", + "extensions-ci": "node --max-old-space-size=8095 ./node_modules/gulp/bin/gulp.js extensions-ci", + "extensions-ci-pr": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js extensions-ci-pr", "perf": "node scripts/code-perf.js" }, "dependencies": { From 5be6890cf5f60bec243715b92f33e97a197e1413 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 19 Jan 2024 17:34:44 +0100 Subject: [PATCH 0473/1897] Git - add onCommit event to vscode.git extension API (#202863) --- extensions/git/src/api/api1.ts | 5 ++++- extensions/git/src/api/git.d.ts | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index c36362c2823..1a0a483409c 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -7,11 +7,12 @@ import { Model } from '../model'; import { Repository as BaseRepository, Resource } from '../repository'; import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions } from './git'; import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode'; -import { combinedDisposable, mapEvent } from '../util'; +import { combinedDisposable, filterEvent, mapEvent } from '../util'; import { toGitUri } from '../uri'; import { GitExtensionImpl } from './extension'; import { GitBaseApi } from '../git-base'; import { PickRemoteSourceOptions } from './git-base'; +import { Operation, OperationResult } from '../operation'; class ApiInputBox implements InputBox { set value(value: string) { this._inputBox.value = value; } @@ -65,6 +66,8 @@ export class ApiRepository implements Repository { readonly state: RepositoryState = new ApiRepositoryState(this.repository); readonly ui: RepositoryUIState = new ApiRepositoryUIState(this.repository.sourceControl); + readonly onDidCommit: Event = mapEvent(filterEvent(this.repository.onDidRunOperation, e => e.operation === Operation.Commit), () => null); + constructor(readonly repository: BaseRepository) { } apply(patch: string, reverse?: boolean): Promise { diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 0b155423619..6a53d893352 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -194,6 +194,8 @@ export interface Repository { readonly state: RepositoryState; readonly ui: RepositoryUIState; + readonly onDidCommit: Event; + getConfigs(): Promise<{ key: string; value: string; }[]>; getConfig(key: string): Promise; setConfig(key: string, value: string): Promise; From e9a2a5823b50d1176ab6766c24c14e12b92fb184 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 19 Jan 2024 10:32:42 -0800 Subject: [PATCH 0474/1897] add terminal voice improvements --- .../contrib/terminal/browser/terminalVoice.ts | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts b/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts index e2db1158ed4..dc1cf7c5878 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts @@ -16,6 +16,7 @@ import type { IDecoration } from '@xterm/xterm'; import { IXtermMarker } from 'vs/platform/terminal/common/capabilities/capabilities'; import { ThemeIcon } from 'vs/base/common/themables'; import { Codicon } from 'vs/base/common/codicons'; +import { alert } from 'vs/base/browser/ui/aria/aria'; const symbolMap: { [key: string]: string } = { 'Ampersand': '&', @@ -82,7 +83,7 @@ export class TerminalVoiceSession extends Disposable { voiceTimeout = SpeechTimeoutDefault; } this._acceptTranscriptionScheduler = this._disposables.add(new RunOnceScheduler(() => { - this._terminalService.activeInstance?.sendText(this._input, false); + this._sendText(); this.stop(); }, voiceTimeout)); this._cancellationTokenSource = this._register(new CancellationTokenSource()); @@ -124,7 +125,7 @@ export class TerminalVoiceSession extends Disposable { this._setInactive(); if (send) { this._acceptTranscriptionScheduler!.cancel(); - this._terminalService.activeInstance?.sendText(this._input, false); + this._sendText(); } this._marker?.dispose(); this._ghostTextMarker?.dispose(); @@ -137,6 +138,11 @@ export class TerminalVoiceSession extends Disposable { this._input = ''; } + private _sendText(): void { + this._terminalService.activeInstance?.sendText(this._input, false); + alert(this._input + ' inserted'); + } + private _updateInput(e: ISpeechToTextEvent): void { if (e.text) { let input = e.text.replaceAll(/[.,?;!]/g, ''); @@ -153,7 +159,8 @@ export class TerminalVoiceSession extends Disposable { if (!xterm) { return; } - this._marker = activeInstance.registerMarker(-1); + const onFirstLine = xterm.buffer.active.cursorY === 0; + this._marker = activeInstance.registerMarker(onFirstLine ? 0 : -1); if (!this._marker) { return; } @@ -164,7 +171,7 @@ export class TerminalVoiceSession extends Disposable { }); this._decoration?.onRender((e: HTMLElement) => { e.classList.add(...ThemeIcon.asClassNameArray(Codicon.micFilled), 'terminal-voice', 'recording'); - e.style.transform = 'translate(-5px, -5px)'; + e.style.transform = onFirstLine ? 'translate(10px, -2px)' : 'translate(-6px, -5px)'; }); } @@ -187,15 +194,16 @@ export class TerminalVoiceSession extends Disposable { if (!this._ghostTextMarker) { return; } + const onFirstLine = xterm.buffer.active.cursorY === 0; this._ghostText = xterm.registerDecoration({ marker: this._ghostTextMarker, layer: 'top', - x: xterm.buffer.active.cursorX + 1 ?? 0, + x: onFirstLine ? xterm.buffer.active.cursorX + 4 : xterm.buffer.active.cursorX + 1 ?? 0, }); this._ghostText?.onRender((e: HTMLElement) => { e.classList.add('terminal-voice-progress-text'); e.textContent = text; - e.style.width = 'fit-content'; + e.style.width = (xterm.cols - xterm.buffer.active.cursorX) / xterm.cols * 100 + '%'; }); } } From 3565346a782fc7aed7412de34b95ff359eadb971 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 19 Jan 2024 10:34:28 -0800 Subject: [PATCH 0475/1897] localize --- src/vs/workbench/contrib/terminal/browser/terminalVoice.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts b/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts index dc1cf7c5878..57464855e57 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts @@ -17,6 +17,7 @@ import { IXtermMarker } from 'vs/platform/terminal/common/capabilities/capabilit import { ThemeIcon } from 'vs/base/common/themables'; import { Codicon } from 'vs/base/common/codicons'; import { alert } from 'vs/base/browser/ui/aria/aria'; +import { localize } from 'vs/nls'; const symbolMap: { [key: string]: string } = { 'Ampersand': '&', @@ -140,7 +141,7 @@ export class TerminalVoiceSession extends Disposable { private _sendText(): void { this._terminalService.activeInstance?.sendText(this._input, false); - alert(this._input + ' inserted'); + alert(localize('terminalVoiceTextInserted', '{0} inserted', this._input)); } private _updateInput(e: ISpeechToTextEvent): void { From a903fe6edde3a1d12c4a9ac90d5b7d3ee21c06a8 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 19 Jan 2024 10:54:17 -0800 Subject: [PATCH 0476/1897] fix #202873 --- src/vs/editor/common/standaloneStrings.ts | 4 ++++ .../browser/editorAccessibilityHelp.ts | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/vs/editor/common/standaloneStrings.ts b/src/vs/editor/common/standaloneStrings.ts index 112c5ff1caf..c6472a81899 100644 --- a/src/vs/editor/common/standaloneStrings.ts +++ b/src/vs/editor/common/standaloneStrings.ts @@ -27,6 +27,10 @@ export namespace AccessibilityHelpNLS { export const showAccessibilityHelpAction = nls.localize("showAccessibilityHelpAction", "Show Accessibility Help"); export const listAudioCues = nls.localize("listAudioCuesCommand", "Run the command: List Audio Cues for an overview of all audio cues and their current status."); export const listAlerts = nls.localize("listAlertsCommand", "Run the command: List Alerts for an overview of alerts and their current status."); + export const quickChat = nls.localize("quickChatCommand", "Toggle quick chat ({0}) to open or close a chat session."); + export const quickChatNoKb = nls.localize("quickChatCommandNoKb", "Toggle quick chat is not currently triggerable by a keybinding."); + export const startInlineChat = nls.localize("startInlineChatCommand", "Start inline chat ({0}) to create an in editor chat session."); + export const startInlineChatNoKb = nls.localize("startInlineChatCommandNoKb", "The command: Start inline chat is not currentlyt riggerable by a keybinding."); } export namespace InspectTokensNLS { diff --git a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts index 2eafab6402d..a084c6b378d 100644 --- a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts @@ -18,6 +18,7 @@ import { AccessibleViewProviderId, AccessibilityVerbositySettingId } from 'vs/wo import { descriptionForCommand } from 'vs/workbench/contrib/accessibility/browser/accessibilityContributions'; import { IAccessibleViewService, IAccessibleContentProvider, IAccessibleViewOptions, AccessibleViewType } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; +import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { CommentAccessibilityHelpNLS } from 'vs/workbench/contrib/comments/browser/commentsAccessibility'; import { CommentCommandId } from 'vs/workbench/contrib/comments/common/commentCommandIds'; import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; @@ -77,6 +78,11 @@ class EditorAccessibilityHelpProvider implements IAccessibleContentProvider { content.push(AccessibilityHelpNLS.listAudioCues); content.push(AccessibilityHelpNLS.listAlerts); + const chatCommandInfo = getChatCommandInfo(this._keybindingService, this._contextKeyService); + if (chatCommandInfo) { + content.push(chatCommandInfo); + } + const commentCommandInfo = getCommentCommandInfo(this._keybindingService, this._contextKeyService, this._editor); if (commentCommandInfo) { content.push(commentCommandInfo); @@ -109,3 +115,13 @@ export function getCommentCommandInfo(keybindingService: IKeybindingService, con } return; } + +export function getChatCommandInfo(keybindingService: IKeybindingService, contextKeyService: IContextKeyService): string | undefined { + if (CONTEXT_PROVIDER_EXISTS.getValue(contextKeyService)) { + const commentCommandInfo: string[] = []; + commentCommandInfo.push(descriptionForCommand('workbench.action.quickchat.toggle', AccessibilityHelpNLS.quickChat, AccessibilityHelpNLS.quickChatNoKb, keybindingService)); + commentCommandInfo.push(descriptionForCommand('inlineChat.start', AccessibilityHelpNLS.startInlineChat, AccessibilityHelpNLS.startInlineChatNoKb, keybindingService)); + return commentCommandInfo.join('\n'); + } + return; +} From b0c5818e205b59584093b322fb38b6f73c6bbd6f Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 19 Jan 2024 11:07:08 -0800 Subject: [PATCH 0477/1897] variable view styling (#202886) * tree view styling * use the render method from the debug view * no colon when value is empty --- .../notebookVariablesTree.ts | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts index 2fa75095b6e..be76d2647ce 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts @@ -10,8 +10,11 @@ import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { FuzzyScore } from 'vs/base/common/filters'; import { localize } from 'vs/nls'; import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; +import { renderExpressionValue } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { INotebookVariableElement } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource'; +const $ = dom.$; + export class NotebookVariablesTree extends WorkbenchObjectTree { } export class NotebookVariablesDelegate implements IListVirtualDelegate { @@ -25,7 +28,13 @@ export class NotebookVariablesDelegate implements IListVirtualDelegate { +export interface IVariableTemplateData { + expression: HTMLElement; + name: HTMLSpanElement; + value: HTMLSpanElement; +} + +export class NotebookVariableRenderer implements ITreeRenderer { static readonly ID = 'variableElement'; @@ -33,13 +42,21 @@ export class NotebookVariableRenderer implements ITreeRenderer, _index: number, templateData: { wrapper: HTMLElement }): void { - templateData.wrapper.innerText = `${element.element.name}: ${element.element.value}`; + renderElement(element: ITreeNode, _index: number, data: IVariableTemplateData): void { + const text = element.element.value.trim() !== '' ? `${element.element.name}:` : element.element.name; + data.name.textContent = text; + + renderExpressionValue(element.element.value, data.value, { colorize: true, showHover: true }); } disposeTemplate(): void { From 6c8cbf45d2bad0c653318830475baa08902f3b0a Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 19 Jan 2024 11:31:07 -0800 Subject: [PATCH 0478/1897] Enable followup for Cell Chat (#202769) * Enable followup for Cell Chat --- .../view/cellParts/chat/cellChatController.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts index 145bd1772e8..0c2bfa6a1ae 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts @@ -23,6 +23,7 @@ import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { localize } from 'vs/nls'; import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { MenuId } from 'vs/platform/actions/common/actions'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { AsyncProgress } from 'vs/platform/progress/common/progress'; @@ -80,6 +81,7 @@ export class NotebookCellChatController extends Disposable { @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService, + @ICommandService private readonly _commandService: ICommandService, ) { super(); @@ -369,6 +371,22 @@ export class NotebookCellChatController extends Disposable { for (let i = progressEdits.length; i < replyResponse.allLocalEdits.length; i++) { await this._makeChanges(editor, replyResponse.allLocalEdits[i], undefined); } + + if (this._activeSession?.provider.provideFollowups) { + const followupCts = new CancellationTokenSource(); + const followups = await this._activeSession.provider.provideFollowups(this._activeSession.session, replyResponse.raw, followupCts.token); + if (followups && this._widget) { + const widget = this._widget; + widget.updateFollowUps(followups, followup => { + if (followup.kind === 'reply') { + widget.value = followup.message; + this.acceptInput(); + } else { + this._commandService.executeCommand(followup.commandId, ...(followup.args ?? [])); + } + }); + } + } } } catch (e) { response = new ErrorResponse(e); From 0865a604d2fd74db5b86f4587503085c9c29d8a9 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 19 Jan 2024 11:43:02 -0800 Subject: [PATCH 0479/1897] testing: finalize selected profiles (#202878) Closes #193160 --- src/vs/workbench/api/common/extHostTesting.ts | 6 +----- .../api/test/browser/extHostTesting.test.ts | 2 +- .../extensions/common/extensionsApiProposals.ts | 1 - src/vscode-dts/vscode.d.ts | 6 ++++++ .../vscode.proposed.testingActiveProfile.d.ts | 16 ---------------- 5 files changed, 8 insertions(+), 23 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.testingActiveProfile.d.ts diff --git a/src/vs/workbench/api/common/extHostTesting.ts b/src/vs/workbench/api/common/extHostTesting.ts index fa062b02547..597865e61c0 100644 --- a/src/vs/workbench/api/common/extHostTesting.ts +++ b/src/vs/workbench/api/common/extHostTesting.ts @@ -148,7 +148,7 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { profileId++; } - return new TestRunProfileImpl(this.proxy, profiles, extension, activeProfiles, this.defaultProfilesChangedEmitter.event, controllerId, profileId, label, group, runHandler, isDefault, tag, supportsContinuousRun); + return new TestRunProfileImpl(this.proxy, profiles, activeProfiles, this.defaultProfilesChangedEmitter.event, controllerId, profileId, label, group, runHandler, isDefault, tag, supportsContinuousRun); }, createTestItem(id, label, uri) { return new TestItemImpl(controllerId, id, label, uri); @@ -1068,7 +1068,6 @@ const updateProfile = (impl: TestRunProfileImpl, proxy: MainThreadTestingShape, export class TestRunProfileImpl implements vscode.TestRunProfile { readonly #proxy: MainThreadTestingShape; - readonly #extension: IRelaxedExtensionDescription; readonly #activeProfiles: Set; readonly #onDidChangeDefaultProfiles: Event; #initialPublish?: ITestRunProfile; @@ -1140,7 +1139,6 @@ export class TestRunProfileImpl implements vscode.TestRunProfile { } public get onDidChangeDefault() { - checkProposedApiEnabled(this.#extension, 'testingActiveProfile'); return Event.chain(this.#onDidChangeDefaultProfiles, $ => $ .map(ev => ev.get(this.controllerId)?.get(this.profileId)) .filter(isDefined) @@ -1150,7 +1148,6 @@ export class TestRunProfileImpl implements vscode.TestRunProfile { constructor( proxy: MainThreadTestingShape, profiles: Map, - extension: IRelaxedExtensionDescription, activeProfiles: Set, onDidChangeActiveProfiles: Event, public readonly controllerId: string, @@ -1164,7 +1161,6 @@ export class TestRunProfileImpl implements vscode.TestRunProfile { ) { this.#proxy = proxy; this.#profiles = profiles; - this.#extension = extension; this.#activeProfiles = activeProfiles; this.#onDidChangeDefaultProfiles = onDidChangeActiveProfiles; profiles.set(profileId, this); diff --git a/src/vs/workbench/api/test/browser/extHostTesting.test.ts b/src/vs/workbench/api/test/browser/extHostTesting.test.ts index 62df7ae029f..f52b523cb1c 100644 --- a/src/vs/workbench/api/test/browser/extHostTesting.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTesting.test.ts @@ -610,7 +610,7 @@ suite('ExtHost Testing', () => { cts = new CancellationTokenSource(); c = new TestRunCoordinator(proxy, new NullLogService()); - configuration = new TestRunProfileImpl(mockObject()(), new Map(), nullExtensionDescription, new Set(), Event.None, 'ctrlId', 42, 'Do Run', TestRunProfileKind.Run, () => { }, false); + configuration = new TestRunProfileImpl(mockObject()(), new Map(), new Set(), Event.None, 'ctrlId', 42, 'Do Run', TestRunProfileKind.Run, () => { }, false); await single.expand(single.root.id, Infinity); single.collectDiff(); diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index c8d09cf0969..e80c095be84 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -101,7 +101,6 @@ export const allApiProposals = Object.freeze({ terminalSelection: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalSelection.d.ts', testCoverage: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testCoverage.d.ts', testObserver: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testObserver.d.ts', - testingActiveProfile: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testingActiveProfile.d.ts', textSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts', timeline: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.timeline.d.ts', tokenInformation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tokenInformation.d.ts', diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index da13bcf7a35..b35ff0b13f4 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -17149,6 +17149,12 @@ declare module 'vscode' { */ isDefault: boolean; + /** + * Fired when a user has changed whether this is a default profile. The + * event contains the new value of {@link isDefault} + */ + onDidChangeDefault: Event; + /** * Whether this profile supports continuous running of requests. If so, * then {@link TestRunRequest.continuous} may be set to `true`. Defaults diff --git a/src/vscode-dts/vscode.proposed.testingActiveProfile.d.ts b/src/vscode-dts/vscode.proposed.testingActiveProfile.d.ts deleted file mode 100644 index 474489f45a6..00000000000 --- a/src/vscode-dts/vscode.proposed.testingActiveProfile.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// https://github.com/microsoft/vscode/issues/193160 @connor4312 - -declare module 'vscode' { - export interface TestRunProfile { - /** - * Fired when a user has changed whether this is a default profile. The - * event contains the new value of {@link isDefault} - */ - onDidChangeDefault: Event; - } -} From b6dc6c4a80948ea4b112d9ab616f43d624162712 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 19 Jan 2024 11:57:03 -0800 Subject: [PATCH 0480/1897] fix every child of a long array being highlighted when expanded (#202893) provide unique id for each tree node --- .../notebookVariablesDataSource.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts index 5a61cec80ff..8ddec399c2c 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts @@ -15,7 +15,8 @@ export interface INotebookScope { export interface INotebookVariableElement { type: 'variable'; - readonly id: number; + readonly id: string; + readonly extHostId: number; readonly name: string; readonly value: string; readonly indexedChildrenCount: number; @@ -46,7 +47,7 @@ export class NotebookVariableDataSource implements IAsyncDataSource { return this.createVariableElement(variable, parent.notebook); }) .toPromise(); @@ -75,7 +76,8 @@ export class NotebookVariableDataSource implements IAsyncDataSource 0) { - const variables = kernel.provideVariables(parent.notebook.uri, parent.id, 'indexed', parent.indexStart ?? 0, CancellationToken.None); + const variables = kernel.provideVariables(parent.notebook.uri, parent.extHostId, 'indexed', parent.indexStart ?? 0, CancellationToken.None); for await (const variable of variables) { childNodes.push(this.createVariableElement(variable, parent.notebook)); @@ -112,9 +114,11 @@ export class NotebookVariableDataSource implements IAsyncDataSource Date: Fri, 19 Jan 2024 12:04:06 -0800 Subject: [PATCH 0481/1897] Cell chat: disable auto save for auto generated code (#202892) * Cell chat: disable auto save for auto generated code --- .../notebook/browser/view/cellParts/chat/cellChatController.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts index 0c2bfa6a1ae..58aa70314cf 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts @@ -38,6 +38,7 @@ import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inline import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_VISIBLE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingService'; export const CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused")); export const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request")); @@ -82,6 +83,7 @@ export class NotebookCellChatController extends Disposable { @IInstantiationService private readonly _instantiationService: IInstantiationService, @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService, @ICommandService private readonly _commandService: ICommandService, + @IInlineChatSavingService private readonly _inlineChatSavingService: IInlineChatSavingService, ) { super(); @@ -466,6 +468,7 @@ export class NotebookCellChatController extends Disposable { const actualEdits = !opts && moreMinimalEdits ? moreMinimalEdits : edits; const editOperations = actualEdits.map(TextEdit.asEditOperation); + this._inlineChatSavingService.markChanged(this._activeSession); try { // this._ignoreModelContentChanged = true; this._activeSession.wholeRange.trackEdits(editOperations); From 4c316c04ca3ef6c34ad01005bb0d9a4710532303 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 19 Jan 2024 13:27:15 -0800 Subject: [PATCH 0482/1897] enable move to previous cell command when screen reader enabled (#202883) * remove inline chat related move between cells hotkey * remove precondition --- .../browser/contrib/navigation/arrow.ts | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts index 93c94c90d60..2436576bbce 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts @@ -18,7 +18,6 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_LAST } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, findTargetCellEditor } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -78,39 +77,6 @@ registerAction2(class FocusNextCellAction extends NotebookCellAction { mac: { primary: KeyMod.WinCtrl | KeyMod.CtrlCmd | KeyCode.DownArrow, }, weight: KeybindingWeight.WorkbenchContrib }, - { - when: ContextKeyExpr.and( - NOTEBOOK_EDITOR_FOCUSED, - CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate(), - ContextKeyExpr.equals('config.notebook.navigation.allowNavigateToSurroundingCells', true), - ContextKeyExpr.and( - ContextKeyExpr.has(InputFocusedContextKey), - NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), - NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'), - ), - CTX_INLINE_CHAT_FOCUSED, - CTX_INLINE_CHAT_INNER_CURSOR_LAST, - EditorContextKeys.isEmbeddedDiffEditor.negate() - ), - primary: KeyCode.DownArrow, - weight: KeybindingWeight.EditorCore - }, - { - when: ContextKeyExpr.and( - NOTEBOOK_EDITOR_FOCUSED, - CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate(), - ContextKeyExpr.equals('config.notebook.navigation.allowNavigateToSurroundingCells', true), - ContextKeyExpr.and( - NOTEBOOK_CELL_TYPE.isEqualTo('markup'), - NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.isEqualTo(false), - NOTEBOOK_CURSOR_NAVIGATION_MODE), - CTX_INLINE_CHAT_FOCUSED, - CTX_INLINE_CHAT_INNER_CURSOR_LAST, - EditorContextKeys.isEmbeddedDiffEditor.negate() - ), - primary: KeyCode.DownArrow, - weight: KeybindingWeight.EditorCore - }, { when: NOTEBOOK_EDITOR_FOCUSED, primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, @@ -155,7 +121,6 @@ registerAction2(class FocusPreviousCellAction extends NotebookCellAction { super({ id: NOTEBOOK_FOCUS_PREVIOUS_EDITOR, title: localize('cursorMoveUp', 'Focus Previous Cell Editor'), - precondition: CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate(), keybinding: [ { when: ContextKeyExpr.and( From 5b18cd75cb347b9813be48f2cffdba616924531a Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 19 Jan 2024 13:55:04 -0800 Subject: [PATCH 0483/1897] include variable type information (#202896) include type information --- .../workbench/api/common/extHostNotebookKernels.ts | 1 + .../notebookVariablesDataSource.ts | 13 +++++++------ .../notebookVariables/notebookVariablesTree.ts | 1 + .../notebookVariables/notebookVariablesView.ts | 6 +++--- .../notebook/common/notebookKernelService.ts | 1 + .../vscode.proposed.notebookVariableProvider.d.ts | 3 +++ 6 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index 8a087a434e7..265b29c33d3 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -456,6 +456,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { id: this.id++, name: result.variable.name, value: result.variable.value, + type: result.variable.type, hasNamedChildren: result.hasNamedChildren, indexedChildrenCount: result.indexedChildrenCount }; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts index 8ddec399c2c..bb17daadf2d 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts @@ -9,16 +9,17 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { INotebookKernel, INotebookKernelService, VariablesResult, variablePageSize } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; export interface INotebookScope { - type: 'root'; + kind: 'root'; readonly notebook: NotebookTextModel; } export interface INotebookVariableElement { - type: 'variable'; + kind: 'variable'; readonly id: string; readonly extHostId: number; readonly name: string; readonly value: string; + readonly type?: string; readonly indexedChildrenCount: number; readonly indexStart?: number; readonly hasNamedChildren: boolean; @@ -30,11 +31,11 @@ export class NotebookVariableDataSource implements IAsyncDataSource 0; + return element.kind === 'root' || element.hasNamedChildren || element.indexedChildrenCount > 0; } async getChildren(element: INotebookScope | INotebookVariableElement): Promise> { - if (element.type === 'root') { + if (element.kind === 'root') { return this.getRootVariables(element.notebook); } else { return this.getVariables(element); @@ -74,7 +75,7 @@ export class NotebookVariableDataSource implements IAsyncDataSource, _index: number, data: IVariableTemplateData): void { const text = element.element.value.trim() !== '' ? `${element.element.name}:` : element.element.name; data.name.textContent = text; + data.name.title = element.element.type ?? ''; renderExpressionValue(element.element.value, data.value, { colorize: true, showHover: true }); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index 698f39e668c..3ecc61d3428 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -78,7 +78,7 @@ export class NotebookVariablesView extends ViewPane { this.tree.layout(); if (this.activeNotebook) { - this.tree.setInput({ type: 'root', notebook: this.activeNotebook }); + this.tree.setInput({ kind: 'root', notebook: this.activeNotebook }); } } @@ -100,7 +100,7 @@ export class NotebookVariablesView extends ViewPane { private handleActiveEditorChange() { if (this.setActiveNotebook() && this.activeNotebook) { - this.tree?.setInput({ type: 'root', notebook: this.activeNotebook }); + this.tree?.setInput({ kind: 'root', notebook: this.activeNotebook }); this.tree?.updateChildren(); } } @@ -116,7 +116,7 @@ export class NotebookVariablesView extends ViewPane { private handleVariablesChanged(notebookUri: URI) { if (this.activeNotebook && notebookUri.toString() === this.activeNotebook.uri.toString()) { - this.tree?.setInput({ type: 'root', notebook: this.activeNotebook }); + this.tree?.setInput({ kind: 'root', notebook: this.activeNotebook }); this.tree?.updateChildren(); } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index ca3e3f7bf3a..be999203d75 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -42,6 +42,7 @@ export interface VariablesResult { id: number; name: string; value: string; + type?: string; hasNamedChildren: boolean; indexedChildrenCount: number; } diff --git a/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts b/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts index ce3de6ebbfc..46258e0432c 100644 --- a/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts @@ -38,6 +38,9 @@ declare module 'vscode' { An empty string can be used if no value should be shown in the UI. */ value: string; + + /** The type of the variable's value */ + type?: string; } } From 3ec9b2aed85ad6062deaa3a1ab8f797ed8d5c813 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 19 Jan 2024 15:03:23 -0800 Subject: [PATCH 0484/1897] type based coloring (#202901) * type based coloring * include valueChanged in value interface --- .../workbench/contrib/debug/browser/baseDebugView.ts | 6 +++--- src/vs/workbench/contrib/debug/common/debug.ts | 11 +++++++---- .../notebookVariables/notebookVariablesTree.ts | 7 ++++++- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 058167334d8..4399464dbc0 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -19,7 +19,7 @@ import { localize } from 'vs/nls'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; -import { IDebugService, IExpression, IExpressionContainer } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IExpression, IExpressionValue } from 'vs/workbench/contrib/debug/common/debug'; import { Expression, ExpressionContainer, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; import { ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; @@ -51,7 +51,7 @@ export function renderViewTree(container: HTMLElement): HTMLElement { return treeContainer; } -export function renderExpressionValue(expressionOrValue: IExpressionContainer | string, container: HTMLElement, options: IRenderValueOptions): void { +export function renderExpressionValue(expressionOrValue: IExpressionValue | string, container: HTMLElement, options: IRenderValueOptions): void { let value = typeof expressionOrValue === 'string' ? expressionOrValue : expressionOrValue.value; // remove stale classes @@ -63,7 +63,7 @@ export function renderExpressionValue(expressionOrValue: IExpressionContainer | container.classList.add('error'); } } else { - if ((expressionOrValue instanceof ExpressionContainer) && options.showChanged && expressionOrValue.valueChanged && value !== Expression.DEFAULT_VALUE) { + if (typeof expressionOrValue !== 'string' && options.showChanged && expressionOrValue.valueChanged && value !== Expression.DEFAULT_VALUE) { // value changed color has priority over other colors. container.className = 'value changed'; expressionOrValue.valueChanged = false; diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 2bdd7fa756c..06d374cbb80 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -148,15 +148,18 @@ export interface IReplElementSource { readonly column: number; } -export interface IExpressionContainer extends ITreeElement { +export interface IExpressionValue { + readonly value: string; + readonly type?: string; + valueChanged?: boolean; +} + +export interface IExpressionContainer extends ITreeElement, IExpressionValue { readonly hasChildren: boolean; evaluateLazy(): Promise; getChildren(): Promise; readonly reference?: number; readonly memoryReference?: string; - readonly value: string; - readonly type?: string; - valueChanged?: boolean; readonly presentationHint?: DebugProtocol.VariablePresentationHint | undefined; } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts index 3d2355006e5..5ffe8b8c96f 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts @@ -14,6 +14,7 @@ import { renderExpressionValue } from 'vs/workbench/contrib/debug/browser/baseDe import { INotebookVariableElement } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource'; const $ = dom.$; +const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; export class NotebookVariablesTree extends WorkbenchObjectTree { } @@ -57,7 +58,11 @@ export class NotebookVariableRenderer implements ITreeRenderer Date: Fri, 19 Jan 2024 18:43:40 -0600 Subject: [PATCH 0485/1897] fix local file search in vscode.dev (#202905) Can no longer search across all the files in the workspace Fixes #201138 --- .../workbench/services/search/browser/searchService.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/search/browser/searchService.ts b/src/vs/workbench/services/search/browser/searchService.ts index 9a8bd127476..f9aea8ee65a 100644 --- a/src/vs/workbench/services/search/browser/searchService.ts +++ b/src/vs/workbench/services/search/browser/searchService.ts @@ -100,9 +100,16 @@ export class LocalFileSearchWorkerClient extends Disposable implements ISearchRe return; } + // force resource to revive using URI.revive. + // TODO @andrea see why we can't just use `revive()` below. For some reason, (obj).$mid was undefined for result.resource + const reviveMatch = (result: IFileMatch): IFileMatch => ({ + resource: URI.revive(result.resource), + results: revive(result.results) + }); + queryDisposables.add(this.onDidReceiveTextSearchMatch(e => { if (e.queryId === queryId) { - onProgress?.(revive(e.match)); + onProgress?.(reviveMatch(e.match)); } })); From 68f4339e5ec96cfb8a0077f3957016fa2c083856 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 19 Jan 2024 17:11:29 -0800 Subject: [PATCH 0486/1897] manage scheduling of getting children to prevent too many requests (#202904) --- .../notebookVariablesDataSource.ts | 20 +++++++++---- .../notebookVariablesView.ts | 29 ++++++++++++++----- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts index bb17daadf2d..d23184d77a5 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookKernel, INotebookKernelService, VariablesResult, variablePageSize } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; @@ -28,12 +28,22 @@ export interface INotebookVariableElement { export class NotebookVariableDataSource implements IAsyncDataSource { - constructor(private readonly notebookKernelService: INotebookKernelService) { } + private cancellationTokenSource: CancellationTokenSource; + + constructor(private readonly notebookKernelService: INotebookKernelService) { + this.cancellationTokenSource = new CancellationTokenSource(); + } hasChildren(element: INotebookScope | INotebookVariableElement): boolean { return element.kind === 'root' || element.hasNamedChildren || element.indexedChildrenCount > 0; } + public cancel(): void { + this.cancellationTokenSource.cancel(); + this.cancellationTokenSource.dispose(); + this.cancellationTokenSource = new CancellationTokenSource(); + } + async getChildren(element: INotebookScope | INotebookVariableElement): Promise> { if (element.kind === 'root') { return this.getRootVariables(element.notebook); @@ -48,7 +58,7 @@ export class NotebookVariableDataSource implements IAsyncDataSource { return this.createVariableElement(variable, parent.notebook); }) .toPromise(); @@ -88,7 +98,7 @@ export class NotebookVariableDataSource implements IAsyncDataSource 0) { - const variables = kernel.provideVariables(parent.notebook.uri, parent.extHostId, 'indexed', parent.indexStart ?? 0, CancellationToken.None); + const variables = kernel.provideVariables(parent.notebook.uri, parent.extHostId, 'indexed', parent.indexStart ?? 0, this.cancellationTokenSource.token); for await (const variable of variables) { childNodes.push(this.createVariableElement(variable, parent.notebook)); @@ -104,7 +114,7 @@ export class NotebookVariableDataSource implements IAsyncDataSource { const selectedKernel = this.notebookKernelService.getMatchingKernel(notebook).selected; if (selectedKernel && selectedKernel.hasVariableProvider) { - const variables = selectedKernel.provideVariables(notebook.uri, undefined, 'named', 0, CancellationToken.None); + const variables = selectedKernel.provideVariables(notebook.uri, undefined, 'named', 0, this.cancellationTokenSource.token); return await variables .map(variable => { return this.createVariableElement(variable, notebook); }) .toPromise(); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index 3ecc61d3428..d96d4933328 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { RunOnceScheduler } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { ILocalizedString } from 'vs/platform/action/common/action'; @@ -34,6 +35,9 @@ export class NotebookVariablesView extends ViewPane { private tree: WorkbenchAsyncDataTree | undefined; private activeNotebook: NotebookTextModel | undefined; + private readonly dataSource: NotebookVariableDataSource; + + private updateScheduler: RunOnceScheduler; constructor( options: IViewPaneOptions, @@ -59,6 +63,9 @@ export class NotebookVariablesView extends ViewPane { this._register(this.notebookExecutionStateService.onDidChangeExecution(this.handleExecutionStateChange.bind(this))); this.setActiveNotebook(); + + this.dataSource = new NotebookVariableDataSource(this.notebookKernelService); + this.updateScheduler = new RunOnceScheduler(() => this.tree?.updateChildren(), 100); } protected override renderBody(container: HTMLElement): void { @@ -70,7 +77,7 @@ export class NotebookVariablesView extends ViewPane { container, new NotebookVariablesDelegate(), [new NotebookVariableRenderer()], - new NotebookVariableDataSource(this.notebookKernelService), + this.dataSource, { accessibilityProvider: new NotebookVariableAccessibilityProvider(), identityProvider: { getId: (e: INotebookVariableElement) => e.id }, @@ -87,7 +94,7 @@ export class NotebookVariablesView extends ViewPane { this.tree?.layout(height, width); } - setActiveNotebook() { + private setActiveNotebook() { const current = this.activeNotebook; const activeEditorPane = this.editorService.activeEditorPane; if (activeEditorPane && activeEditorPane.getId() === 'workbench.editor.notebook') { @@ -101,15 +108,23 @@ export class NotebookVariablesView extends ViewPane { private handleActiveEditorChange() { if (this.setActiveNotebook() && this.activeNotebook) { this.tree?.setInput({ kind: 'root', notebook: this.activeNotebook }); - this.tree?.updateChildren(); + this.updateScheduler.schedule(); } } private handleExecutionStateChange(event: ICellExecutionStateChangedEvent | IExecutionStateChangedEvent) { if (this.activeNotebook) { - // changed === undefined -> excecution ended - if (event.changed === undefined && event.affectsNotebook(this.activeNotebook?.uri)) { - this.tree?.updateChildren(); + if (event.affectsNotebook(this.activeNotebook.uri)) { + // new execution state means either new variables or the kernel is busy so we shouldn't ask + this.dataSource.cancel(); + + // changed === undefined -> excecution ended + if (event.changed === undefined) { + this.updateScheduler.schedule(); + } + else { + this.updateScheduler.cancel(); + } } } } @@ -117,7 +132,7 @@ export class NotebookVariablesView extends ViewPane { private handleVariablesChanged(notebookUri: URI) { if (this.activeNotebook && notebookUri.toString() === this.activeNotebook.uri.toString()) { this.tree?.setInput({ kind: 'root', notebook: this.activeNotebook }); - this.tree?.updateChildren(); + this.updateScheduler.schedule(); } } } From 564d6ae4c7c0e1158d3d6bd15d925a0c4430ecaf Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sat, 20 Jan 2024 05:56:07 -0300 Subject: [PATCH 0487/1897] Remove internal implementation of ChatAgentTask (#202908) * Get rid of chat progress Task * Remove internal implementation of ChatAgentTask * Clean up more --- .../api/browser/mainThreadChatAgents2.ts | 31 ++----------------- .../workbench/api/common/extHost.protocol.ts | 12 +++---- .../contrib/chat/browser/chatListRenderer.ts | 26 ++-------------- .../contrib/chat/common/chatModel.ts | 31 ++----------------- .../contrib/chat/common/chatService.ts | 10 ------ .../chat/test/common/chatModel.test.ts | 17 +--------- 6 files changed, 13 insertions(+), 114 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index fc7271c1516..7be5e2e6ea0 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -3,9 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DeferredPromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableMap, IDisposable } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; @@ -23,7 +21,7 @@ import { AddDynamicVariableAction, IAddDynamicVariableContext } from 'vs/workben import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestAgentPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { IChatFollowup, IChatProgress, IChatService, IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; type AgentData = { @@ -42,9 +40,6 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA private readonly _pendingProgress = new Map void>(); private readonly _proxy: ExtHostChatAgentsShape2; - private _responsePartHandlePool = 0; - private readonly _activeResponsePartPromises = new Map>(); - constructor( extHostContext: IExtHostContext, @IChatAgentService private readonly _chatAgentService: IChatAgentService, @@ -123,29 +118,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA this._chatAgentService.updateAgent(data.name, revive(metadataUpdate)); } - async $handleProgressChunk(requestId: string, progress: IChatProgressDto, responsePartHandle?: number): Promise { - if (progress.kind === 'asyncContent') { - const handle = ++this._responsePartHandlePool; - const responsePartId = `${requestId}_${handle}`; - const deferredContentPromise = new DeferredPromise(); - this._activeResponsePartPromises.set(responsePartId, deferredContentPromise); - this._pendingProgress.get(requestId)?.({ ...progress, resolvedContent: deferredContentPromise.p }); - return handle; - } else if (typeof responsePartHandle === 'number') { - // Complete an existing deferred promise with resolved content - const responsePartId = `${requestId}_${responsePartHandle}`; - const deferredContentPromise = this._activeResponsePartPromises.get(responsePartId); - if (deferredContentPromise && progress.kind === 'treeData') { - const withRevivedUris = revive(progress); - deferredContentPromise.complete(withRevivedUris); - this._activeResponsePartPromises.delete(responsePartId); - } else if (deferredContentPromise && progress.kind === 'content') { - deferredContentPromise.complete(progress.content); - this._activeResponsePartPromises.delete(responsePartId); - } - return responsePartHandle; - } - + async $handleProgressChunk(requestId: string, progress: IChatProgressDto): Promise { const revivedProgress = revive(progress); this._pendingProgress.get(requestId)?.(revivedProgress as IChatProgress); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 68ac001fd36..b4605fd2111 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -53,7 +53,7 @@ import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/cal import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatAsyncContent, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatDynamicRequest, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; import { DebugConfigurationProviderTriggerKind, MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext } from 'vs/workbench/contrib/debug/common/debug'; import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -1195,7 +1195,7 @@ export interface MainThreadChatAgentsShape2 extends IDisposable { $unregisterAgentCompletionsProvider(handle: number): void; $updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void; $unregisterAgent(handle: number): void; - $handleProgressChunk(requestId: string, chunk: IChatProgressDto, responsePartHandle?: number): Promise; + $handleProgressChunk(requestId: string, chunk: IChatProgressDto): Promise; } export interface IChatAgentCompletionItem { @@ -1207,8 +1207,7 @@ export interface IChatAgentCompletionItem { } export type IChatContentProgressDto = - | Dto> - | IChatAsyncContentDto; + | Dto; export type IChatAgentHistoryEntryDto = { request: IChatAgentRequest; @@ -1293,11 +1292,8 @@ export type IDocumentContextDto = { ranges: IRange[]; }; -export type IChatAsyncContentDto = Dto>; - export type IChatProgressDto = - | Dto> - | IChatAsyncContentDto; + | Dto; export interface MainThreadChatShape extends IDisposable { $registerChatProvider(handle: number, id: string): Promise; diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index ed78c17b82f..3e8aa42bb21 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -442,9 +442,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer content.dispose() }; - } - private renderProgressMessage(progress: IChatProgressMessage, showSpinner: boolean): IMarkdownRenderResult { if (showSpinner) { // this step is in progress, communicate it to SR users diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 0a3ebbaa0c8..8bec2aa5ec7 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -16,7 +16,7 @@ import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { ILogService } from 'vs/platform/log/common/log'; import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatAsyncContent, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; export interface IChatRequestVariableData { @@ -42,7 +42,6 @@ export type IChatProgressResponseContent = | IChatMarkdownContent | IChatAgentMarkdownContentWithVulnerability | IChatTreeData - | IChatAsyncContent | IChatContentInlineReference | IChatProgressMessage; @@ -160,22 +159,6 @@ export class Response implements IResponse { } this._updateRepr(quiet); - } else if (progress.kind === 'asyncContent') { - // Add a new resolving part - const responsePosition = this._responseParts.push(progress) - 1; - this._updateRepr(quiet); - - progress.resolvedContent?.then((content) => { - // Replace the resolving part's content with the resolved response - if (typeof content === 'string') { - this._responseParts[responsePosition] = { content: new MarkdownString(content), kind: 'markdownContent' }; - } else if (isMarkdownString(content)) { - this._responseParts[responsePosition] = { content, kind: 'markdownContent' }; - } else { - this._responseParts[responsePosition] = content; - } - this._updateRepr(quiet); - }); } else if (progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage') { this._responseParts.push(progress); this._updateRepr(quiet); @@ -188,8 +171,6 @@ export class Response implements IResponse { return ''; } else if (part.kind === 'inlineReference') { return basename('uri' in part.inlineReference ? part.inlineReference.uri : part.inlineReference); - } else if (part.kind === 'asyncContent') { - return part.content; } else { return part.content.value; } @@ -303,15 +284,12 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel /** * Apply one of the progress updates that are not part of the actual response content. */ - applyReference(progress: IChatUsedContext | IChatContentReference | IChatProgressMessage) { + applyReference(progress: IChatUsedContext | IChatContentReference) { if (progress.kind === 'usedContext') { this._usedContext = progress; } else if (progress.kind === 'reference') { this._contentReferences.push(progress); this._onDidChange.fire(); - } else if (progress.kind === 'progressMessage') { - // this._progressMessages.push(progress); - // this._onDidChange.fire(); } } @@ -667,9 +645,8 @@ export class ChatModel extends Disposable implements IChatModel { } if (progress.kind === 'vulnerability') { - // TODO@roblourens ChatModel should just work with strings request.response.updateContent({ kind: 'markdownVuln', content: { value: progress.content }, vulnerabilities: progress.vulnerabilities }, quiet); - } else if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'asyncContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage') { + } else if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage') { request.response.updateContent(progress, quiet); } else if (progress.kind === 'usedContext' || progress.kind === 'reference') { request.response.applyReference(progress); @@ -758,8 +735,6 @@ export class ChatModel extends Disposable implements IChatModel { return item.treeData; } else if (item.kind === 'markdownContent') { return item.content; - } else if (item.kind === 'asyncContent') { - return new MarkdownString(item.content); } else { return item as any; // TODO } diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index f16e5f1ce39..5b90ef33acf 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -119,15 +119,6 @@ export interface IChatTreeData { kind: 'treeData'; } -export interface IChatAsyncContent { - /** - * The placeholder to show while the content is loading - */ - content: string; - resolvedContent: Promise; - kind: 'asyncContent'; -} - export interface IChatProgressMessage { content: IMarkdownString; kind: 'progressMessage'; @@ -157,7 +148,6 @@ export type IChatProgress = | IChatAgentContentWithVulnerabilities | IChatAgentMarkdownContentWithVulnerability | IChatTreeData - | IChatAsyncContent | IChatUsedContext | IChatContentReference | IChatContentInlineReference diff --git a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index 7ffcef4448a..ed18235fe4f 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { DeferredPromise, timeout } from 'vs/base/common/async'; +import { timeout } from 'vs/base/common/async'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { URI } from 'vs/base/common/uri'; import { assertSnapshot } from 'vs/base/test/common/snapshot'; @@ -17,7 +17,6 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModel, Response } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestTextPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -140,20 +139,6 @@ suite('Response', () => { await assertSnapshot(response.value); }); - test('async content', async () => { - const response = new Response([]); - const deferred = new DeferredPromise(); - const deferred2 = new DeferredPromise(); - response.updateContent({ resolvedContent: deferred.p, content: 'text', kind: 'asyncContent' }); - response.updateContent({ resolvedContent: deferred2.p, content: 'text2', kind: 'asyncContent' }); - await assertSnapshot(response.value); - - await deferred2.complete({ kind: 'treeData', treeData: { label: 'label', uri: URI.parse('https://microsoft.com') } }); - await deferred.complete('resolved'); - await assertSnapshot(response.value); - }); - - test('inline reference', async () => { const response = new Response([]); response.updateContent({ content: 'text before', kind: 'content' }); From 442419c7a24beda953fe229ede17cef870c0ac9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Jan 2024 11:06:39 -0800 Subject: [PATCH 0488/1897] Bump h2 from 0.3.17 to 0.3.24 in /cli (#202867) Bumps [h2](https://github.com/hyperium/h2) from 0.3.17 to 0.3.24. - [Release notes](https://github.com/hyperium/h2/releases) - [Changelog](https://github.com/hyperium/h2/blob/v0.3.24/CHANGELOG.md) - [Commits](https://github.com/hyperium/h2/compare/v0.3.17...v0.3.24) --- updated-dependencies: - dependency-name: h2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cli/Cargo.lock | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 632ec0af176..311614b4301 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -680,6 +680,12 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.1" @@ -917,9 +923,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.17" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b91535aa35fea1523ad1b86cb6b53c28e0ae566ba4a460f4457e936cad7c6f" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", @@ -927,7 +933,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", @@ -940,6 +946,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + [[package]] name = "heck" version = "0.4.0" @@ -1094,7 +1106,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", ] [[package]] @@ -1529,7 +1551,7 @@ checksum = "ed41783a5bf567688eb38372f2b7a8530f5a607a4b49d38dd7573236c23ca7e2" dependencies = [ "futures-channel", "futures-util", - "indexmap", + "indexmap 1.9.1", "once_cell", "pin-project-lite", "thiserror", From cf13b387868102076947cbd010ac4a9755a06cc6 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Sat, 20 Jan 2024 22:57:51 +0100 Subject: [PATCH 0489/1897] changing the milestone to the correct milestone for the next endgame --- .vscode/notebooks/endgame.github-issues | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index 9743e647077..975758eb747 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"November 2023 Recovery 1\"\n" + "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"December / January 2024\"\n" }, { "kind": 1, From e244acbb172c428cb219717a07bf55d2737492ca Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Sat, 20 Jan 2024 21:38:46 -0800 Subject: [PATCH 0490/1897] testing: avoid large hovers in test coverage, show inline counts instead (#202944) Closes #202600 I still have a hover to make the "toggle line coverage" action visible, not sure a better place to put that... --- .../lib/stylelint/vscode-known-variables.json | 2 + src/vs/platform/theme/common/colorRegistry.ts | 4 +- .../browser/codeCoverageDecorations.ts | 77 ++++++++++--------- .../contrib/testing/browser/media/testing.css | 56 ++++++++++---- .../contrib/testing/browser/theme.ts | 16 +++- 5 files changed, 100 insertions(+), 55 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 0b1767fba21..9fb16d8459d 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -683,6 +683,8 @@ "--vscode-terminalStickyScroll-background", "--vscode-terminalStickyScrollHover-background", "--vscode-testing-coverage-lineHeight", + "--vscode-testing-coverCountBadgeBackground", + "--vscode-testing-coverCountBadgeForeground", "--vscode-testing-coveredBackground", "--vscode-testing-coveredBorder", "--vscode-testing-coveredGutterBackground", diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 05989bbbb4d..3702702f542 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { assertNever } from 'vs/base/common/assert'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Color, RGBA } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; -import { assertNever } from 'vs/base/common/assert'; import * as nls from 'vs/nls'; -import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import * as platform from 'vs/platform/registry/common/platform'; import { IColorTheme } from 'vs/platform/theme/common/themeService'; diff --git a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts index ac801660ccd..831ee05e699 100644 --- a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts @@ -13,14 +13,13 @@ import { Lazy } from 'vs/base/common/lazy'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { autorun, derived, observableFromEvent, observableValue } from 'vs/base/common/observable'; import { ThemeIcon } from 'vs/base/common/themables'; -import { isDefined } from 'vs/base/common/types'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { IModelDecorationOptions, ITextModel, InjectedTextCursorStops } from 'vs/editor/common/model'; +import { IModelDecorationOptions, ITextModel, InjectedTextCursorStops, InjectedTextOptions } from 'vs/editor/common/model'; import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/browser/hoverOperation'; import { localize } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; @@ -173,12 +172,12 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri return; } - const wasPreviouslyHovering = typeof this.hoveredSubject === 'number'; this.hoveredStore.clear(); this.hoveredSubject = lineNumber; const todo = [{ line: lineNumber, dir: 0 }]; const toEnable = new Set(); + const inlineEnabled = CodeCoverageDecorations.showInline.get(); if (!CodeCoverageDecorations.showInline.get()) { for (let i = 0; i < todo.length && i < MAX_HOVERED_LINES; i++) { const { line, dir } = todo[i]; @@ -209,7 +208,9 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri }); } - this.lineHoverWidget.value.startShowingAt(lineNumber, this.details, wasPreviouslyHovering); + if (toEnable.size || inlineEnabled) { + this.lineHoverWidget.value.startShowingAt(lineNumber); + } this.hoveredStore.add(this.editor.onMouseLeave(() => { this.hoveredStore.clear(); @@ -240,7 +241,7 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri model.changeDecorations(e => { for (const detailRange of details.ranges) { - const { metadata: { detail, description }, range } = detailRange; + const { metadata: { detail, description }, range, primary } = detailRange; if (detail.type === DetailType.Branch) { const hits = detail.detail.branches![detail.branch].count; const cls = hits ? CLASS_HIT : CLASS_MISS; @@ -263,6 +264,9 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri }; } else { target.className = `coverage-deco-inline ${cls}`; + if (primary) { + target.before = countBadge(hits); + } } }; @@ -282,6 +286,9 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri const applyHoverOptions = (target: IModelDecorationOptions) => { target.className = `coverage-deco-inline ${cls}`; target.hoverMessage = description; + if (primary) { + target.before = countBadge(detail.count); + } }; if (showInlineByDefault) { @@ -336,8 +343,21 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri } } +const countBadge = (count: number): InjectedTextOptions | undefined => { + if (count === 0) { + return undefined; + } + + return { + content: `${count > 99 ? '99+' : count}x`, + cursorStops: InjectedTextCursorStops.None, + inlineClassName: `coverage-deco-inline-count`, + inlineClassNameAffectsLetterSpacing: true, + }; +}; + type CoverageDetailsWithBranch = CoverageDetails | { type: DetailType.Branch; branch: number; detail: IStatementCoverage }; -type DetailRange = { range: Range; metadata: { detail: CoverageDetailsWithBranch; description: IMarkdownString | undefined } }; +type DetailRange = { range: Range; primary: boolean; metadata: { detail: CoverageDetailsWithBranch; description: IMarkdownString | undefined } }; export class CoverageDetailsModel { public readonly ranges: DetailRange[] = []; @@ -351,6 +371,7 @@ export class CoverageDetailsModel { // the editor without ugly overlaps. const detailRanges: DetailRange[] = details.map(detail => ({ range: tidyLocation(detail.location), + primary: true, metadata: { detail, description: this.describe(detail, textModel) } })); @@ -360,6 +381,7 @@ export class CoverageDetailsModel { const branch: CoverageDetailsWithBranch = { type: DetailType.Branch, branch: i, detail }; detailRanges.push({ range: tidyLocation(detail.branches[i].location || Range.fromPositions(range.getEndPosition())), + primary: true, metadata: { detail: branch, description: this.describe(branch, textModel), @@ -404,11 +426,13 @@ export class CoverageDetailsModel { // until after the `item.range` ends. const prev = stack[stack.length - 1]; if (prev) { + const primary = prev.primary; const si = prev.range.setEndPosition(start.lineNumber, start.column); prev.range = prev.range.setStartPosition(item.range.endLineNumber, item.range.endColumn); + prev.primary = false; // discard the previous range if it became empty, e.g. a nested statement if (prev.range.isEmpty()) { stack.pop(); } - result.push({ range: si, metadata: prev.metadata }); + result.push({ range: si, primary, metadata: prev.metadata }); } stack.push(item); @@ -460,39 +484,20 @@ function tidyLocation(location: Range | Position): Range { class LineHoverComputer implements IHoverComputer { public line = -1; - public textModel!: ITextModel; - public details!: CoverageDetailsModel; constructor(@IKeybindingService private readonly keybindingService: IKeybindingService) { } /** @inheritdoc */ public computeSync(): IMarkdownString[] { - const bestDetails: DetailRange[] = []; - let bestLine = -1; - for (const detail of this.details.ranges) { - if (detail.range.startLineNumber > this.line) { - break; - } - if (detail.range.endLineNumber < this.line) { - continue; - } - if (detail.range.startLineNumber !== bestLine) { - bestDetails.length = 0; - } - bestLine = detail.range.startLineNumber; - bestDetails.push(detail); - } + const strs: IMarkdownString[] = []; - const strs = bestDetails.map(d => d.metadata.detail.type === DetailType.Branch ? undefined : d.metadata.description).filter(isDefined); - if (strs.length) { - const s = new MarkdownString().appendMarkdown(`[${TOGGLE_INLINE_COMMAND_TEXT}](command:${TOGGLE_INLINE_COMMAND_ID})`); - s.isTrusted = true; - const binding = this.keybindingService.lookupKeybinding(TOGGLE_INLINE_COMMAND_ID); - if (binding) { - s.appendText(` (${binding.getLabel()})`); - } - strs.push(s); + const s = new MarkdownString().appendMarkdown(`[${TOGGLE_INLINE_COMMAND_TEXT}](command:${TOGGLE_INLINE_COMMAND_ID})`); + s.isTrusted = true; + const binding = this.keybindingService.lookupKeybinding(TOGGLE_INLINE_COMMAND_ID); + if (binding) { + s.appendText(` (${binding.getLabel()})`); } + strs.push(s); return strs; } @@ -556,7 +561,7 @@ class LineHoverWidget extends Disposable implements IOverlayWidget { } /** Shows the hover widget at the given line */ - public startShowingAt(lineNumber: number, details: CoverageDetailsModel, showImmediate: boolean) { + public startShowingAt(lineNumber: number) { this.hide(); const textModel = this.editor.getModel(); if (!textModel) { @@ -564,9 +569,7 @@ class LineHoverWidget extends Disposable implements IOverlayWidget { } this.computer.line = lineNumber; - this.computer.textModel = textModel; - this.computer.details = details; - this.hoverOperation.start(showImmediate ? HoverStartMode.Immediate : HoverStartMode.Delayed); + this.hoverOperation.start(HoverStartMode.Delayed); } /** Hides the hover widget */ diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index 86b858a5d1e..9f577c776de 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -147,7 +147,7 @@ .test-explorer .computed-state.retired, .testing-run-glyph.retired { - opacity: 0.7 !important; + opacity: 0.7 !important; } .test-explorer .test-is-hidden { @@ -171,11 +171,7 @@ flex-grow: 1; } -.monaco-workbench - .test-explorer - .monaco-action-bar - .action-item - > .action-label { +.monaco-workbench .test-explorer .monaco-action-bar .action-item > .action-label { padding: 1px 2px; margin-right: 2px; } @@ -358,6 +354,7 @@ .monaco-editor .testing-inline-message-severity-0 { color: var(--vscode-testing-message-error-decorationForeground) !important; } + .monaco-editor .testing-inline-message-severity-1 { color: var(--vscode-testing-message-info-decorationForeground) !important; } @@ -411,8 +408,10 @@ .explorer-item-with-test-coverage .explorer-item { flex-grow: 1; } + .explorer-item-with-test-coverage .monaco-icon-label::after { - margin-right: 12px; /* slightly reduce because the bars handle the scrollbar margin */ + margin-right: 12px; + /* slightly reduce because the bars handle the scrollbar margin */ } /** -- coverage decorations */ @@ -447,14 +446,13 @@ .coverage-deco-gutter.coverage-deco-miss.coverage-deco-hit::before { background-image: linear-gradient(45deg, - var(--vscode-testing-coveredGutterBackground) 25%, - var(--vscode-testing-uncoveredGutterBackground) 25%, - var(--vscode-testing-uncoveredGutterBackground) 50%, - var(--vscode-testing-coveredGutterBackground) 50%, - 75%, - var(--vscode-testing-uncoveredGutterBackground) 75%, - var(--vscode-testing-uncoveredGutterBackground) 100% - ); + var(--vscode-testing-coveredGutterBackground) 25%, + var(--vscode-testing-uncoveredGutterBackground) 25%, + var(--vscode-testing-uncoveredGutterBackground) 50%, + var(--vscode-testing-coveredGutterBackground) 50%, + 75%, + var(--vscode-testing-uncoveredGutterBackground) 75%, + var(--vscode-testing-uncoveredGutterBackground) 100%); background-size: 6px 6px; background-color: transparent; } @@ -497,3 +495,31 @@ font: normal normal normal calc(var(--vscode-testing-coverage-lineHeight) / 2)/1 codicon; border: 1px solid; } + +.coverage-deco-inline-count { + position: relative; + background: var(--vscode-testing-coverCountBadgeBackground); + color: var(--vscode-testing-coverCountBadgeForeground); + font-size: 0.7em; + margin: 0 0.7em 0 0.4em; + padding: 0.2em 0 0.2em 0.2em; + /* display: inline-block; */ + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; +} + +.coverage-deco-inline-count::after { + content: ''; + display: block; + position: absolute; + left: 100%; + top: 0; + bottom: 0; + width: 0.5em; + background-image: + linear-gradient(to bottom left, transparent 50%, var(--vscode-testing-coverCountBadgeBackground) 0), + linear-gradient(to bottom right, var(--vscode-testing-coverCountBadgeBackground) 50%, transparent 0); + background-size: 100% 50%; + background-repeat: no-repeat; + background-position: top, bottom; +} diff --git a/src/vs/workbench/contrib/testing/browser/theme.ts b/src/vs/workbench/contrib/testing/browser/theme.ts index fe155816c0d..4e232405411 100644 --- a/src/vs/workbench/contrib/testing/browser/theme.ts +++ b/src/vs/workbench/contrib/testing/browser/theme.ts @@ -5,7 +5,7 @@ import { Color, RGBA } from 'vs/base/common/color'; import { localize } from 'vs/nls'; -import { chartsGreen, chartsRed, contrastBorder, diffInserted, diffRemoved, editorBackground, editorErrorForeground, editorForeground, editorInfoForeground, opaque, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; +import { badgeBackground, badgeForeground, chartsGreen, chartsRed, contrastBorder, diffInserted, diffRemoved, editorBackground, editorErrorForeground, editorForeground, editorInfoForeground, opaque, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { TestMessageType, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes'; @@ -135,6 +135,20 @@ export const testingUncoveredGutterBackground = registerColor('testing.uncovered hcLight: chartsRed }, localize('testing.uncoveredGutterBackground', 'Gutter color of regions where code not covered.')); +export const testingCoverCountBadgeBackground = registerColor('testing.coverCountBadgeBackground', { + dark: badgeBackground, + light: badgeBackground, + hcDark: badgeBackground, + hcLight: badgeBackground +}, localize('testing.coverCountBadgeBackground', 'Background for the badge indicating execution count')); + +export const testingCoverCountBadgeForeground = registerColor('testing.coverCountBadgeForeground', { + dark: badgeForeground, + light: badgeForeground, + hcDark: badgeForeground, + hcLight: badgeForeground +}, localize('testing.coverCountBadgeForeground', 'Foreground for the badge indicating execution count')); + export const testMessageSeverityColors: { [K in TestMessageType]: { decorationForeground: string; From 1396f9c8274692e5ed6f7089a62297f89238f894 Mon Sep 17 00:00:00 2001 From: Robo Date: Mon, 22 Jan 2024 16:12:40 +0900 Subject: [PATCH 0491/1897] ci: fix debian packaging step failing to download sysroot (#202973) --- build/azure-pipelines/linux/product-build-linux.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index a4b7faa3918..848c47fe823 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -288,6 +288,8 @@ steps: - script: | set -e yarn gulp "vscode-linux-$(VSCODE_ARCH)-prepare-deb" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Prepare deb package - script: | From 7c471f6415d8cb9b5e1b77bc1663e187fe8ca5cb Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Sun, 21 Jan 2024 23:20:21 -0800 Subject: [PATCH 0492/1897] ensures correct diagnostic contributions to highlight code actions (#202978) ensures correct diagnostic contributions to highlight --- .../contrib/codeAction/browser/codeActionController.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index 57aa30ff33f..ad34973eddf 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -289,8 +289,10 @@ export class CodeActionController extends Disposable implements IEditorContribut const decorations: IModelDeltaDecoration[] = action.action.diagnostics.map(diagnostic => ({ range: diagnostic, options: CodeActionController.DECORATION })); currentDecorations.set(decorations); const diagnostic = action.action.diagnostics[0]; - const selectionText = this._editor.getModel()?.getWordAtPosition({ lineNumber: diagnostic.startLineNumber, column: diagnostic.startColumn })?.word; - aria.status(localize('editingNewSelection', "Context: {0} at line {1} and column {2}.", selectionText, diagnostic.startLineNumber, diagnostic.startColumn)); + if (diagnostic.startLineNumber && diagnostic.startColumn) { + const selectionText = this._editor.getModel()?.getWordAtPosition({ lineNumber: diagnostic.startLineNumber, column: diagnostic.startColumn })?.word; + aria.status(localize('editingNewSelection', "Context: {0} at line {1} and column {2}.", selectionText, diagnostic.startLineNumber, diagnostic.startColumn)); + } } else { currentDecorations.clear(); } From ba3e2066981110b9a5e12571b506c4fd212bbd09 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 22 Jan 2024 13:10:17 +0530 Subject: [PATCH 0493/1897] fix #141282 (#202814) * fix #141282 * fix tests --- .../common/extensionManagement.ts | 5 +- .../common/extensionsProfileScannerService.ts | 12 +- .../node/extensionManagementService.ts | 19 ++- .../extensionsProfileScannerService.test.ts | 118 ------------------ .../extensions/browser/extensionEditor.ts | 21 ++-- .../browser/extensions.contribution.ts | 35 ++---- .../extensions/browser/extensionsActions.ts | 73 +++++------ .../extensions/browser/extensionsList.ts | 4 +- .../extensions/browser/extensionsWidgets.ts | 5 +- .../browser/extensionsWorkbenchService.ts | 26 +++- .../contrib/extensions/common/extensions.ts | 3 + .../browser/webExtensionsScannerService.ts | 4 + .../common/webExtensionManagementService.ts | 9 +- 13 files changed, 113 insertions(+), 221 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 853b9c182ba..15907ec4882 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -237,6 +237,7 @@ export type Metadata = Partial; @@ -248,6 +249,7 @@ export interface ILocalExtension extends IExtension { publisherDisplayName: string | null; installedTimestamp?: number; isPreReleaseVersion: boolean; + hasPreReleaseVersion: boolean; preRelease: boolean; updated: boolean; pinned: boolean; @@ -446,14 +448,15 @@ export type InstallOptions = { pinned?: boolean; donotIncludePackAndDependencies?: boolean; installGivenVersion?: boolean; + preRelease?: boolean; installPreReleaseVersion?: boolean; donotVerifySignature?: boolean; operation?: InstallOperation; + profileLocation?: URI; /** * Context passed through to InstallExtensionResult */ context?: IStringDictionary; - profileLocation?: URI; }; export type InstallVSIXOptions = InstallOptions & { installOnlyNewlyAddedFromExtensionPack?: boolean }; export type UninstallOptions = { readonly donotIncludePack?: boolean; readonly donotCheckDependents?: boolean; readonly versionOnly?: boolean; readonly remove?: boolean; readonly profileLocation?: URI }; diff --git a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts index 40eb2584305..4fcf403d999 100644 --- a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts @@ -253,12 +253,8 @@ export abstract class AbstractExtensionsProfileScannerService extends Disposable // Extension in new format. No migration needed. location = this.resolveExtensionLocation(e.relativeLocation); } else if (isString(e.location)) { - // Extension in intermediate format. Migrate to new format. - location = this.resolveExtensionLocation(e.location); - migrate = true; - e.relativeLocation = e.location; - // retain old format so that old clients can read it - e.location = location.toJSON(); + this.logService.warn(`Extensions profile: Ignoring extension with invalid location: ${e.location}`); + continue; } else { location = URI.revive(e.location); const relativePath = this.toRelativePath(location); @@ -268,6 +264,10 @@ export abstract class AbstractExtensionsProfileScannerService extends Disposable e.relativeLocation = relativePath; } } + if (isUndefined(e.metadata?.hasPreReleaseVersion) && e.metadata?.preRelease) { + migrate = true; + e.metadata.hasPreReleaseVersion = true; + } extensions.push({ identifier: e.identifier, location, diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 8bcf57b346a..c7137910f44 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -192,6 +192,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id); if (metadata.isPreReleaseVersion) { metadata.preRelease = true; + metadata.hasPreReleaseVersion = true; } // unset if false if (metadata.isMachineScoped === false) { @@ -493,7 +494,9 @@ export class ExtensionsScanner extends Disposable { exists = false; } - if (!exists) { + if (exists) { + await this.extensionsScannerService.updateMetadata(extensionLocation, metadata); + } else { try { // Extract try { @@ -700,6 +703,7 @@ export class ExtensionsScanner extends Disposable { isApplicationScoped: !!extension.metadata?.isApplicationScoped, isMachineScoped: !!extension.metadata?.isMachineScoped, isPreReleaseVersion: !!extension.metadata?.isPreReleaseVersion, + hasPreReleaseVersion: !!extension.metadata?.hasPreReleaseVersion, preRelease: !!extension.metadata?.preRelease, installedTimestamp: extension.metadata?.installedTimestamp, updated: !!extension.metadata?.updated, @@ -815,7 +819,9 @@ abstract class InstallExtensionTask extends AbstractExtensionTask { let local = await this.unsetIfUninstalled(key); - if (!local) { + if (local) { + local = await this.extensionsScanner.updateMetadata(local, metadata); + } else { this.logService.trace('Extracting extension...', key.id); local = await this.extensionsScanner.extractUserExtension(key, zipPath, metadata, removeIfExists, token); this.logService.info('Extracting extension completed.', key.id); @@ -888,12 +894,12 @@ export class InstallGalleryExtensionTask extends InstallExtensionTask { isSystem: existingExtension?.type === ExtensionType.System ? true : undefined, updated: !!existingExtension, isPreReleaseVersion: this.gallery.properties.isPreReleaseVersion, + hasPreReleaseVersion: existingExtension?.hasPreReleaseVersion || this.gallery.properties.isPreReleaseVersion, installedTimestamp: Date.now(), pinned: this.options.installGivenVersion ? true : (this.options.pinned ?? existingExtension?.pinned), - preRelease: this.gallery.properties.isPreReleaseVersion || - (isBoolean(this.options.installPreReleaseVersion) - ? this.options.installPreReleaseVersion /* Respect the passed flag */ - : existingExtension?.preRelease /* Respect the existing pre-release flag if it was set */) + preRelease: isBoolean(this.options.preRelease) + ? this.options.preRelease + : this.options.installPreReleaseVersion || this.gallery.properties.isPreReleaseVersion || existingExtension?.preRelease }; if (existingExtension?.manifest.version === this.gallery.version) { @@ -1007,6 +1013,7 @@ class InstallVSIXTask extends InstallExtensionTask { publisherDisplayName: galleryExtension.publisherDisplayName, publisherId: galleryExtension.publisherId, isPreReleaseVersion: galleryExtension.properties.isPreReleaseVersion, + hasPreReleaseVersion: extension.hasPreReleaseVersion || galleryExtension.properties.isPreReleaseVersion, preRelease: galleryExtension.properties.isPreReleaseVersion || this.options.installPreReleaseVersion }; await this.extensionsScanner.updateMetadata(extension, metadata, this.options.profileLocation); diff --git a/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts b/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts index 9636eabcf69..5e71b85714e 100644 --- a/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts +++ b/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts @@ -186,124 +186,6 @@ suite('ExtensionsProfileScannerService', () => { ]); }); - test('extension in intermediate format is read and migrated', async () => { - const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); - const extension = aExtension('pub.a', joinPath(extensionsLocation, 'pub.a-1.0.0')); - await instantiationService.get(IFileService).writeFile(extensionsManifest, VSBuffer.fromString(JSON.stringify([{ - identifier: extension.identifier, - location: 'pub.a-1.0.0', - version: extension.manifest.version, - }]))); - - const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); - - const actual = await testObject.scanProfileExtensions(extensionsManifest); - assert.deepStrictEqual(actual.map(a => ({ ...a, location: a.location.toJSON() })), [{ identifier: extension.identifier, location: extension.location.toJSON(), version: extension.manifest.version, metadata: undefined }]); - - const manifestContent = JSON.parse((await instantiationService.get(IFileService).readFile(extensionsManifest)).value.toString()); - assert.deepStrictEqual(manifestContent, [{ identifier: extension.identifier, location: extension.location.toJSON(), relativeLocation: 'pub.a-1.0.0', version: extension.manifest.version }]); - }); - - test('extension in intermediate format is read and migrated during write', async () => { - const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); - const extension = aExtension('pub.a', joinPath(extensionsLocation, 'pub.a-1.0.0')); - await instantiationService.get(IFileService).writeFile(extensionsManifest, VSBuffer.fromString(JSON.stringify([{ - identifier: extension.identifier, - location: 'pub.a-1.0.0', - version: extension.manifest.version, - }]))); - - const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); - const extension2 = aExtension('pub.b', joinPath(extensionsLocation, 'pub.b-1.0.0')); - await testObject.addExtensionsToProfile([[extension2, undefined]], extensionsManifest); - - const actual = await testObject.scanProfileExtensions(extensionsManifest); - assert.deepStrictEqual(actual.map(a => ({ ...a, location: a.location.toJSON() })), [ - { identifier: extension.identifier, location: extension.location.toJSON(), version: extension.manifest.version, metadata: undefined }, - { identifier: extension2.identifier, location: extension2.location.toJSON(), version: extension2.manifest.version, metadata: undefined } - ]); - - const manifestContent = JSON.parse((await instantiationService.get(IFileService).readFile(extensionsManifest)).value.toString()); - assert.deepStrictEqual(manifestContent, [ - { identifier: extension.identifier, location: extension.location.toJSON(), relativeLocation: 'pub.a-1.0.0', version: extension.manifest.version }, - { identifier: extension2.identifier, location: extension2.location.toJSON(), relativeLocation: 'pub.b-1.0.0', version: extension2.manifest.version } - ]); - }); - - test('extensions in intermediate and new format is read and migrated', async () => { - const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); - const extension = aExtension('pub.a', joinPath(extensionsLocation, 'pub.a-1.0.0')); - const extension2 = aExtension('pub.b', joinPath(extensionsLocation, 'pub.b-1.0.0')); - await instantiationService.get(IFileService).writeFile(extensionsManifest, VSBuffer.fromString(JSON.stringify([{ - identifier: extension.identifier, - location: 'pub.a-1.0.0', - version: extension.manifest.version, - }, { - identifier: extension2.identifier, - location: extension2.location.toJSON(), - relativeLocation: 'pub.b-1.0.0', - version: extension2.manifest.version, - }]))); - - const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); - - const actual = await testObject.scanProfileExtensions(extensionsManifest); - assert.deepStrictEqual(actual.map(a => ({ ...a, location: a.location.toJSON() })), [ - { identifier: extension.identifier, location: extension.location.toJSON(), version: extension.manifest.version, metadata: undefined }, - { identifier: extension2.identifier, location: extension2.location.toJSON(), version: extension2.manifest.version, metadata: undefined } - ]); - - const manifestContent = JSON.parse((await instantiationService.get(IFileService).readFile(extensionsManifest)).value.toString()); - assert.deepStrictEqual(manifestContent, [ - { identifier: extension.identifier, location: extension.location.toJSON(), relativeLocation: 'pub.a-1.0.0', version: extension.manifest.version }, - { identifier: extension2.identifier, location: extension2.location.toJSON(), relativeLocation: 'pub.b-1.0.0', version: extension2.manifest.version } - ]); - }); - - test('extensions in mixed format is read and migrated', async () => { - const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); - const extension1 = aExtension('pub.a', joinPath(extensionsLocation, 'pub.a-1.0.0')); - const extension2 = aExtension('pub.b', joinPath(extensionsLocation, 'pub.b-1.0.0')); - const extension3 = aExtension('pub.c', joinPath(extensionsLocation, 'pub.c-1.0.0')); - const extension4 = aExtension('pub.d', joinPath(ROOT, 'pub.d-1.0.0')); - await instantiationService.get(IFileService).writeFile(extensionsManifest, VSBuffer.fromString(JSON.stringify([{ - identifier: extension1.identifier, - location: 'pub.a-1.0.0', - version: extension1.manifest.version, - }, { - identifier: extension2.identifier, - location: extension2.location.toJSON(), - version: extension2.manifest.version, - }, { - identifier: extension3.identifier, - location: extension3.location.toJSON(), - relativeLocation: 'pub.c-1.0.0', - version: extension3.manifest.version, - }, { - identifier: extension4.identifier, - location: extension4.location.toJSON(), - version: extension4.manifest.version, - }]))); - - const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); - - const actual = await testObject.scanProfileExtensions(extensionsManifest); - assert.deepStrictEqual(actual.map(a => ({ ...a, location: a.location.toJSON() })), [ - { identifier: extension1.identifier, location: extension1.location.toJSON(), version: extension1.manifest.version, metadata: undefined }, - { identifier: extension2.identifier, location: extension2.location.toJSON(), version: extension2.manifest.version, metadata: undefined }, - { identifier: extension3.identifier, location: extension3.location.toJSON(), version: extension3.manifest.version, metadata: undefined }, - { identifier: extension4.identifier, location: extension4.location.toJSON(), version: extension4.manifest.version, metadata: undefined } - ]); - - const manifestContent = JSON.parse((await instantiationService.get(IFileService).readFile(extensionsManifest)).value.toString()); - assert.deepStrictEqual(manifestContent, [ - { identifier: extension1.identifier, location: extension1.location.toJSON(), relativeLocation: 'pub.a-1.0.0', version: extension1.manifest.version }, - { identifier: extension2.identifier, location: extension2.location.toJSON(), relativeLocation: 'pub.b-1.0.0', version: extension2.manifest.version }, - { identifier: extension3.identifier, location: extension3.location.toJSON(), relativeLocation: 'pub.c-1.0.0', version: extension3.manifest.version }, - { identifier: extension4.identifier, location: extension4.location.toJSON(), version: extension4.manifest.version } - ]); - }); - test('throws error if extension has invalid relativePath', async () => { const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); const extension = aExtension('pub.a', joinPath(extensionsLocation, 'pub.a-1.0.0')); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index e991e2d5f8d..a977ee1ed2f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -73,10 +73,10 @@ import { SetLanguageAction, SetProductIconThemeAction, ToggleAutoUpdateForExtensionAction, - SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, UninstallAction, UpdateAction, - WebInstallAction + WebInstallAction, + TogglePreReleaseExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { errorIcon, infoIcon, preReleaseIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { Delegate } from 'vs/workbench/contrib/extensions/browser/extensionsList'; @@ -218,16 +218,7 @@ class PreReleaseTextWidget extends ExtensionWithDifferentGalleryVersionWidget { this.render(); } render(): void { - this.element.style.display = this.isPreReleaseVersion() ? 'inherit' : 'none'; - } - private isPreReleaseVersion(): boolean { - if (!this.extension) { - return false; - } - if (this.gallery) { - return this.gallery.properties.isPreReleaseVersion; - } - return !!(this.extension.state === ExtensionState.Installed ? this.extension.local?.isPreReleaseVersion : this.extension.gallery?.properties.isPreReleaseVersion); + this.element.style.display = this.extension?.isPreReleaseVersion ? 'inherit' : 'none'; } } @@ -365,8 +356,7 @@ export class ExtensionEditor extends EditorPane { this.instantiationService.createInstance(InstallAnotherVersionAction), ] ]), - this.instantiationService.createInstance(SwitchToPreReleaseVersionAction, false), - this.instantiationService.createInstance(SwitchToReleasedVersionAction, false), + this.instantiationService.createInstance(TogglePreReleaseExtensionAction), this.instantiationService.createInstance(ToggleAutoUpdateForExtensionAction, false, [false, 'onlySelectedExtensions']), new ExtensionEditorManageExtensionAction(this.scopedContextKeyService || this.contextKeyService, this.instantiationService), ]; @@ -384,6 +374,9 @@ export class ExtensionEditor extends EditorPane { if (action instanceof ToggleAutoUpdateForExtensionAction) { return new CheckboxActionViewItem(undefined, action, { icon: true, label: true, checkboxStyles: defaultCheckboxStyles }); } + if (action instanceof TogglePreReleaseExtensionAction) { + return new CheckboxActionViewItem(undefined, action, { icon: true, label: true, checkboxStyles: defaultCheckboxStyles }); + } return undefined; }, focusOnlyEnabledItems: true diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index de4e96c1c25..412cbbc3b5a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -14,7 +14,7 @@ import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsServi import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, IWorkspaceRecommendedExtensionsView, AutoUpdateConfigurationKey, HasOutdatedExtensionsContext, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, ExtensionEditorTab, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, OUTDATED_EXTENSIONS_VIEW_ID, CONTEXT_HAS_GALLERY, IExtension, extensionsSearchActionsMenu, UPDATE_ACTIONS_GROUP } from 'vs/workbench/contrib/extensions/common/extensions'; -import { ReinstallAction, InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, PromptExtensionInstallFailureAction, SearchExtensionsAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, SetColorThemeAction, SetFileIconThemeAction, SetProductIconThemeAction, ClearLanguageAction, ToggleAutoUpdateForExtensionAction, ToggleAutoUpdatesForPublisherAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { ReinstallAction, InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, PromptExtensionInstallFailureAction, SearchExtensionsAction, SetColorThemeAction, SetFileIconThemeAction, SetProductIconThemeAction, ClearLanguageAction, ToggleAutoUpdateForExtensionAction, ToggleAutoUpdatesForPublisherAction, TogglePreReleaseExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor'; import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContribution, ExtensionsViewPaneContainer, BuiltInExtensionsContext, SearchMarketplaceExtensionsContext, RecommendedExtensionsContext, DefaultViewsContext, ExtensionsSortByContext, SearchHasTextContext } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; @@ -1372,39 +1372,24 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi }); this.registerExtensionAction({ - id: SwitchToPreReleaseVersionAction.ID, - title: SwitchToPreReleaseVersionAction.TITLE, + id: TogglePreReleaseExtensionAction.ID, + title: TogglePreReleaseExtensionAction.LABEL, + category: ExtensionsLocalizedLabel, menu: { id: MenuId.ExtensionContext, group: INSTALL_ACTIONS_GROUP, order: 2, - when: ContextKeyExpr.and(ContextKeyExpr.not('installedExtensionIsPreReleaseVersion'), ContextKeyExpr.not('installedExtensionIsOptedTpPreRelease'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) + when: ContextKeyExpr.and(CONTEXT_HAS_GALLERY, ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) }, + toggled: ContextKeyExpr.has('installedExtensionIsOptedToPreRelease'), run: async (accessor: ServicesAccessor, id: string) => { + const instantiationService = accessor.get(IInstantiationService); const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); const extension = extensionWorkbenchService.local.find(e => areSameExtensions(e.identifier, { id })); if (extension) { - extensionWorkbenchService.open(extension, { showPreReleaseVersion: true }); - await extensionWorkbenchService.install(extension, { installPreReleaseVersion: true }); - } - } - }); - - this.registerExtensionAction({ - id: SwitchToReleasedVersionAction.ID, - title: SwitchToReleasedVersionAction.TITLE, - menu: { - id: MenuId.ExtensionContext, - group: INSTALL_ACTIONS_GROUP, - order: 3, - when: ContextKeyExpr.and(ContextKeyExpr.has('installedExtensionIsPreReleaseVersion'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.has('extensionHasReleaseVersion'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) - }, - run: async (accessor: ServicesAccessor, id: string) => { - const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); - const extension = extensionWorkbenchService.local.find(e => areSameExtensions(e.identifier, { id })); - if (extension) { - extensionWorkbenchService.open(extension, { showPreReleaseVersion: false }); - await extensionWorkbenchService.install(extension, { installPreReleaseVersion: false }); + const action = instantiationService.createInstance(TogglePreReleaseExtensionAction); + action.extension = extension; + return action.run(); } } }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 853426bcdd4..fbd480a719f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -55,7 +55,7 @@ import { IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/ import { ActionWithDropdownActionViewItem, IActionWithDropdownActionViewItemOptions } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; import { ILogService } from 'vs/platform/log/common/log'; -import { errorIcon, infoIcon, manageExtensionIcon, preReleaseIcon, syncEnabledIcon, syncIgnoredIcon, trustIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; +import { errorIcon, infoIcon, manageExtensionIcon, syncEnabledIcon, syncIgnoredIcon, trustIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { isIOS, isWeb, language } from 'vs/base/common/platform'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; @@ -1085,7 +1085,7 @@ async function getContextMenuActionsGroups(extension: IExtension | undefined | n cksOverlay.push(['extensionStatus', 'installed']); } cksOverlay.push(['installedExtensionIsPreReleaseVersion', !!extension.local?.isPreReleaseVersion]); - cksOverlay.push(['installedExtensionIsOptedTpPreRelease', !!extension.local?.preRelease]); + cksOverlay.push(['installedExtensionIsOptedToPreRelease', !!extension.local?.preRelease]); cksOverlay.push(['galleryExtensionIsPreReleaseVersion', !!extension.gallery?.properties.isPreReleaseVersion]); cksOverlay.push(['extensionHasPreReleaseVersion', extension.hasPreReleaseVersion]); cksOverlay.push(['extensionHasReleaseVersion', extension.hasReleaseVersion]); @@ -1267,55 +1267,50 @@ export class MenuItemExtensionAction extends ExtensionAction { } } -export class SwitchToPreReleaseVersionAction extends ExtensionAction { +export class TogglePreReleaseExtensionAction extends ExtensionAction { - static readonly ID = 'workbench.extensions.action.switchToPreReleaseVersion'; - static readonly TITLE = { value: localize('switch to pre-release version', "Switch to Pre-Release Version"), original: 'Switch to Pre-Release Version' }; + static readonly ID = 'workbench.extensions.action.togglePreRlease'; + static readonly LABEL = localize('togglePreRleaseLabel', "Pre-Release"); + + private static readonly EnabledClass = `${ExtensionAction.EXTENSION_ACTION_CLASS} pre-release`; + private static readonly DisabledClass = `${TogglePreReleaseExtensionAction.EnabledClass} hide`; constructor( - icon: boolean, - @ICommandService private readonly commandService: ICommandService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, ) { - super(SwitchToPreReleaseVersionAction.ID, icon ? '' : SwitchToPreReleaseVersionAction.TITLE.value, `${icon ? ExtensionAction.ICON_ACTION_CLASS + ' ' + ThemeIcon.asClassName(preReleaseIcon) : ExtensionAction.LABEL_ACTION_CLASS} hide-when-disabled switch-to-prerelease`, true); - this.tooltip = localize('switch to pre-release version tooltip', "Switch to Pre-Release version of this extension"); + super(TogglePreReleaseExtensionAction.ID, TogglePreReleaseExtensionAction.LABEL, TogglePreReleaseExtensionAction.DisabledClass); this.update(); } - update(): void { - this.enabled = !!this.extension && !this.extension.isBuiltin && !this.extension.local?.isPreReleaseVersion && !this.extension.local?.preRelease && this.extension.hasPreReleaseVersion && this.extension.state === ExtensionState.Installed; + override update() { + this.enabled = false; + this.class = TogglePreReleaseExtensionAction.DisabledClass; + if (!this.extension) { + return; + } + if (this.extension.isBuiltin) { + return; + } + if (this.extension.state !== ExtensionState.Installed) { + return; + } + if (!this.extension.hasPreReleaseVersion) { + return; + } + if (!this.extension.gallery) { + return; + } + this.enabled = true; + this.class = TogglePreReleaseExtensionAction.EnabledClass; + this.checked = this.extension.preRelease; } override async run(): Promise { - if (!this.enabled) { + if (!this.extension) { return; } - return this.commandService.executeCommand(SwitchToPreReleaseVersionAction.ID, this.extension?.identifier.id); - } -} - -export class SwitchToReleasedVersionAction extends ExtensionAction { - - static readonly ID = 'workbench.extensions.action.switchToReleaseVersion'; - static readonly TITLE = localize2('switch to release version', 'Switch to Release Version'); - - constructor( - icon: boolean, - @ICommandService private readonly commandService: ICommandService, - ) { - super(SwitchToReleasedVersionAction.ID, icon ? '' : SwitchToReleasedVersionAction.TITLE.value, `${icon ? ExtensionAction.ICON_ACTION_CLASS + ' ' + ThemeIcon.asClassName(preReleaseIcon) : ExtensionAction.LABEL_ACTION_CLASS} hide-when-disabled switch-to-released`); - this.tooltip = localize('switch to release version tooltip', "Switch to Release version of this extension"); - this.update(); - } - - update(): void { - this.enabled = !!this.extension && !this.extension.isBuiltin && this.extension.state === ExtensionState.Installed && !!this.extension.local?.isPreReleaseVersion && !!this.extension.hasReleaseVersion; - } - - override async run(): Promise { - if (!this.enabled) { - return; - } - return this.commandService.executeCommand(SwitchToReleasedVersionAction.ID, this.extension?.identifier.id); + this.extensionsWorkbenchService.open(this.extension, { showPreReleaseVersion: !this.extension.preRelease }); + await this.extensionsWorkbenchService.togglePreRelease(this.extension); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index d021b584a18..0f3b1b04d08 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -13,7 +13,7 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { Event } from 'vs/base/common/event'; import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; -import { ManageExtensionAction, ReloadAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction, SetLanguageAction, ClearLanguageAction, UpdateAction, ToggleAutoUpdateForExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { ManageExtensionAction, ReloadAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction, MigrateDeprecatedExtensionAction, SetLanguageAction, ClearLanguageAction, UpdateAction, ToggleAutoUpdateForExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget, SyncIgnoredWidget, ExtensionHoverWidget, ExtensionActivationStatusWidget, PreReleaseBookmarkWidget, extensionVerifiedPublisherIconColor, VerifiedPublisherWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions'; @@ -128,8 +128,6 @@ export class Renderer implements IPagedRenderer { this.instantiationService.createInstance(LocalInstallAction), this.instantiationService.createInstance(WebInstallAction), extensionStatusIconAction, - this.instantiationService.createInstance(SwitchToReleasedVersionAction, true), - this.instantiationService.createInstance(SwitchToPreReleaseVersionAction, true), this.instantiationService.createInstance(ManageExtensionAction) ]; const extensionHoverWidget = this.instantiationService.createInstance(ExtensionHoverWidget, { target: root, position: this.options.hoverOptions.position }, extensionStatusIconAction); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 2145b7dfbee..8b95c604371 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -323,10 +323,7 @@ export class PreReleaseBookmarkWidget extends ExtensionWidget { if (!this.extension) { return; } - if (!this.extension.hasPreReleaseVersion) { - return; - } - if (this.extension.state === ExtensionState.Installed && !this.extension.local?.isPreReleaseVersion) { + if (!this.extension.isPreReleaseVersion) { return; } this.element = append(this.parent, $('div.extension-bookmark')); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 65b2e09affa..62731c2520a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -290,8 +290,19 @@ export class Extension implements IExtension { return this.local?.manifest.preview ?? this.gallery?.preview ?? false; } + get preRelease(): boolean { + return !!this.local?.preRelease; + } + + get isPreReleaseVersion(): boolean { + if (this.local) { + return this.local.isPreReleaseVersion; + } + return !!this.gallery?.properties.isPreReleaseVersion; + } + get hasPreReleaseVersion(): boolean { - return !!this.gallery?.hasPreReleaseVersion; + return !!this.gallery?.hasPreReleaseVersion || !!this.local?.hasPreReleaseVersion; } get hasReleaseVersion(): boolean { @@ -1637,6 +1648,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return false; } + + install(extension: URI | IExtension, installOptions?: InstallOptions | InstallVSIXOptions, progressLocation?: ProgressLocation): Promise { return this.doInstall(extension, async () => { if (extension instanceof URI) { @@ -1766,6 +1779,17 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension : this.extensionsSyncManagementService.hasToNeverSyncExtension(extension.identifier.id); } + async togglePreRelease(extension: IExtension): Promise { + if (!extension.local) { + return; + } + if (extension.preRelease !== extension.isPreReleaseVersion) { + await this.extensionManagementService.updateMetadata(extension.local, { preRelease: !extension.preRelease }); + return; + } + await this.install(extension, { installPreReleaseVersion: !extension.preRelease, preRelease: !extension.preRelease }); + } + async toggleExtensionIgnoredToSync(extension: IExtension): Promise { const isIgnored = this.isExtensionIgnoredToSync(extension); if (extension.local && isIgnored) { diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 97ee589ea19..cd7375688b3 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -53,6 +53,8 @@ export interface IExtension { readonly publisherSponsorLink?: URI; readonly version: string; readonly latestVersion: string; + readonly preRelease: boolean; + readonly isPreReleaseVersion: boolean; readonly hasPreReleaseVersion: boolean; readonly hasReleaseVersion: boolean; readonly description: string; @@ -110,6 +112,7 @@ export interface IExtensionsWorkbenchService { uninstall(extension: IExtension): Promise; installVersion(extension: IExtension, version: string, installOptions?: InstallOptions): Promise; reinstall(extension: IExtension): Promise; + togglePreRelease(extension: IExtension): Promise; canSetLanguage(extension: IExtension): boolean; setLanguage(extension: IExtension): Promise; setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise; diff --git a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts index de40f04aeaf..9e0eab4f88f 100644 --- a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts @@ -936,6 +936,10 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten update = true; webExtension.location = migratedLocation; } + if (isUndefined(webExtension.metadata?.hasPreReleaseVersion) && webExtension.metadata?.preRelease) { + update = true; + webExtension.metadata.hasPreReleaseVersion = true; + } return webExtension; })); if (update) { diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index 9927bd3d342..074f0d70d66 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -230,6 +230,7 @@ function toLocalExtension(extension: IExtension): ILocalExtension { publisherDisplayName: metadata.publisherDisplayName || null, installedTimestamp: metadata.installedTimestamp, isPreReleaseVersion: !!metadata.isPreReleaseVersion, + hasPreReleaseVersion: !!metadata.hasPreReleaseVersion, preRelease: !!metadata.preRelease, targetPlatform: TargetPlatform.WEB, updated: !!metadata.updated, @@ -280,14 +281,14 @@ class InstallExtensionTask extends AbstractExtensionTask implem metadata.publisherId = this.extension.publisherId; metadata.installedTimestamp = Date.now(); metadata.isPreReleaseVersion = this.extension.properties.isPreReleaseVersion; + metadata.hasPreReleaseVersion = metadata.hasPreReleaseVersion || this.extension.properties.isPreReleaseVersion; metadata.isBuiltin = this.options.isBuiltin || existingExtension?.isBuiltin; metadata.isSystem = existingExtension?.type === ExtensionType.System ? true : undefined; metadata.updated = !!existingExtension; metadata.isApplicationScoped = this.options.isApplicationScoped || metadata.isApplicationScoped; - metadata.preRelease = this.extension.properties.isPreReleaseVersion || - (isBoolean(this.options.installPreReleaseVersion) - ? this.options.installPreReleaseVersion /* Respect the passed flag */ - : metadata?.preRelease /* Respect the existing pre-release flag if it was set */); + metadata.preRelease = isBoolean(this.options.preRelease) + ? this.options.preRelease + : this.options.installPreReleaseVersion || this.extension.properties.isPreReleaseVersion || metadata.preRelease; } metadata.pinned = this.options.installGivenVersion ? true : (this.options.pinned ?? metadata.pinned); From 143e023867ac7520a92aa8c708e99029850491f2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 22 Jan 2024 13:24:09 +0530 Subject: [PATCH 0494/1897] schema can be undefined (#202980) --- .../services/configuration/browser/configurationService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 4057b5bc7cc..509ec2874c4 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -1349,7 +1349,7 @@ class ConfigurationTelemetryContribution implements IWorkbenchContribution { const userSource = ConfigurationTargetToString(ConfigurationTarget.USER_LOCAL); for (const setting of user) { const schema = this.configurationRegistry.getConfigurationProperties()[setting]; - if (schema.tags?.includes('FeatureInsight')) { + if (schema?.tags?.includes('FeatureInsight')) { const value = this.getValueToReport(setting, ConfigurationTarget.USER_LOCAL, schema); this.telemetryService.publicLog2('updatedsetting', { setting, value, source: userSource }); } @@ -1357,7 +1357,7 @@ class ConfigurationTelemetryContribution implements IWorkbenchContribution { const worskpaceSource = ConfigurationTargetToString(ConfigurationTarget.WORKSPACE); for (const setting of workspace) { const schema = this.configurationRegistry.getConfigurationProperties()[setting]; - if (schema.tags?.includes('FeatureInsight')) { + if (schema?.tags?.includes('FeatureInsight')) { const value = this.getValueToReport(setting, ConfigurationTarget.WORKSPACE, schema); this.telemetryService.publicLog2('updatedsetting', { setting, value, source: worskpaceSource }); } From 0d71d26b8ca9ece39c43ac513c963a5b697b51ec Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 22 Jan 2024 13:51:04 +0530 Subject: [PATCH 0495/1897] fix #202868 (#202981) --- .../workbench/browser/parts/globalCompositeBar.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/parts/globalCompositeBar.ts b/src/vs/workbench/browser/parts/globalCompositeBar.ts index d65d239c959..50a63bb9e00 100644 --- a/src/vs/workbench/browser/parts/globalCompositeBar.ts +++ b/src/vs/workbench/browser/parts/globalCompositeBar.ts @@ -584,17 +584,18 @@ export class GlobalActivityActionViewItem extends AbstractGlobalActivityActionVi return; } + if (this.userDataProfileService.currentProfile.icon && this.userDataProfileService.currentProfile.icon !== DEFAULT_ICON.id) { + return; + } + if ((this.action as CompositeBarAction).activity) { return; } - if (!this.userDataProfileService.currentProfile.icon || this.userDataProfileService.currentProfile.icon === DEFAULT_ICON.id) { - this.profileBadgeContent.classList.toggle('profile-text-overlay', true); - this.profileBadgeContent.classList.toggle('profile-icon-overlay', false); - this.profileBadgeContent.textContent = this.userDataProfileService.currentProfile.name.substring(0, 2).toUpperCase(); - } - show(this.profileBadge); + this.profileBadgeContent.classList.toggle('profile-text-overlay', true); + this.profileBadgeContent.classList.toggle('profile-icon-overlay', false); + this.profileBadgeContent.textContent = this.userDataProfileService.currentProfile.name.substring(0, 2).toUpperCase(); } protected override updateActivity(): void { From 8baf105cbd35e538d8133a857d4b4fede89f902c Mon Sep 17 00:00:00 2001 From: Robo Date: Mon, 22 Jan 2024 17:51:44 +0900 Subject: [PATCH 0496/1897] fix: skip glibc requirements check on nixos (#202982) --- resources/server/bin/helpers/check-requirements-linux.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/resources/server/bin/helpers/check-requirements-linux.sh b/resources/server/bin/helpers/check-requirements-linux.sh index ac1840d61d8..af77f0b9d82 100644 --- a/resources/server/bin/helpers/check-requirements-linux.sh +++ b/resources/server/bin/helpers/check-requirements-linux.sh @@ -10,6 +10,13 @@ ARCH=$(uname -m) found_required_glibc=0 found_required_glibcxx=0 +# Extract the ID value from /etc/os-release +OS_ID="$(cat /etc/os-release | grep -Eo 'ID=([^"]+)' | sed 's/ID=//')" +if [ "$OS_ID" = "nixos" ]; then + echo "Warning: NixOS detected, skipping GLIBC check" + exit 0 +fi + # Based on https://github.com/bminor/glibc/blob/520b1df08de68a3de328b65a25b86300a7ddf512/elf/cache.c#L162-L245 case $ARCH in x86_64) LDCONFIG_ARCH="x86-64";; From c17371c2c4002fbc88d57db914e348efc0a4396c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 22 Jan 2024 12:05:27 +0100 Subject: [PATCH 0497/1897] eng - enlist some settings to report as telemetry (#202986) * eng - enlist some settings to report as telemetry * include more custom titlebar insights --------- Co-authored-by: BeniBenj --- .../chat/electron-sandbox/actions/voiceChatActions.ts | 2 +- .../contrib/files/browser/files.contribution.ts | 3 ++- .../electron-sandbox/desktop.contribution.ts | 11 +++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index f83d6f8ff49..a68d2a95678 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -854,7 +854,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe ], 'description': localize('voice.keywordActivation', "Controls whether the phrase 'Hey Code' should be speech recognized to start a voice chat session."), 'default': 'off', - 'tags': ['accessibility'] + 'tags': ['accessibility', 'FeatureInsight'] } } }); diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index f64fe59659d..4f6e5691daa 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -251,7 +251,8 @@ configurationRegistry.registerConfiguration({ ], 'default': isWeb ? AutoSaveConfiguration.AFTER_DELAY : AutoSaveConfiguration.OFF, 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSave' }, "Controls [auto save](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save) of editors that have unsaved changes.", AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE, AutoSaveConfiguration.AFTER_DELAY), - scope: ConfigurationScope.LANGUAGE_OVERRIDABLE + scope: ConfigurationScope.LANGUAGE_OVERRIDABLE, + tags: ['FeatureInsight'] }, 'files.autoSaveDelay': { 'type': 'number', diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 19549fc876a..7246962aa75 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -195,13 +195,13 @@ import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from 'vs/platform/window/electron-sand 'maximum': MAX_ZOOM_LEVEL, 'markdownDescription': localize({ comment: ['{0} will be a setting name rendered as a link'], key: 'zoomLevel' }, "Adjust the default zoom level for all windows. Each increment above `0` (e.g. `1`) or below (e.g. `-1`) represents zooming `20%` larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity. See {0} for configuring if the 'Zoom In' and 'Zoom Out' commands apply the zoom level to all windows or only the active window.", '`#window.zoomPerWindow#`'), ignoreSync: true, - tags: ['accessibility'] + tags: ['accessibility', 'FeatureInsight'] }, 'window.zoomPerWindow': { 'type': 'boolean', 'default': true, 'markdownDescription': localize({ comment: ['{0} will be a setting name rendered as a link'], key: 'zoomPerWindow' }, "Controls if the 'Zoom In' and 'Zoom Out' commands apply the zoom level to all windows or only the active window. See {0} for configuring a default zoom level for all windows.", '`#window.zoomLevel#`'), - tags: ['accessibility'] + tags: ['accessibility', 'FeatureInsight'] }, 'window.newWindowDimensions': { 'type': 'string', @@ -233,7 +233,8 @@ import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from 'vs/platform/window/electron-sand 'enum': ['native', 'custom'], 'default': isLinux ? 'native' : 'custom', 'scope': ConfigurationScope.APPLICATION, - 'description': localize('titleBarStyle', "Adjust the appearance of the window title bar to be native by the OS or custom. On Linux and Windows, this setting also affects the application and context menu appearances. Changes require a full restart to apply.") + 'description': localize('titleBarStyle', "Adjust the appearance of the window title bar to be native by the OS or custom. On Linux and Windows, this setting also affects the application and context menu appearances. Changes require a full restart to apply."), + tags: ['FeatureInsight'] }, 'window.customTitleBarVisibility': { 'type': 'string', @@ -246,6 +247,7 @@ import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from 'vs/platform/window/electron-sand 'default': isLinux ? 'never' : 'auto', 'scope': ConfigurationScope.APPLICATION, 'description': localize('window.customTitleBarVisibility', "Adjust when the custom title bar should be shown."), + tags: ['FeatureInsight'] }, 'window.dialogStyle': { 'type': 'string', @@ -259,7 +261,8 @@ import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from 'vs/platform/window/electron-sand 'default': false, 'scope': ConfigurationScope.APPLICATION, 'description': localize('window.nativeTabs', "Enables macOS Sierra window tabs. Note that changes require a full restart to apply and that native tabs will disable a custom title bar style if configured."), - 'included': isMacintosh + 'included': isMacintosh, + tags: ['FeatureInsight'] }, 'window.nativeFullScreen': { 'type': 'boolean', From 1d914f2bf258a005302b01542ef3df9e96826777 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 22 Jan 2024 12:12:53 +0100 Subject: [PATCH 0498/1897] Define default of 'editor.lightbulb.enabled' by experiment (#202989) --- src/vs/editor/common/config/editorOptions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 944041b7fc3..8e9ef0b8ef3 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2748,12 +2748,13 @@ export type EditorLightbulbOptions = Readonly> class EditorLightbulb extends BaseEditorOption { constructor() { - const defaults: EditorLightbulbOptions = { enabled: ShowLightbulbIconMode.On }; + const defaults: EditorLightbulbOptions = { enabled: ShowLightbulbIconMode.OnCode }; super( EditorOption.lightbulb, 'lightbulb', defaults, { 'editor.lightbulb.enabled': { type: 'string', + tags: ['experimental'], enum: [ShowLightbulbIconMode.Off, ShowLightbulbIconMode.OnCode, ShowLightbulbIconMode.On], default: defaults.enabled, enumDescriptions: [ From e1c930d5be4e618944116b1eb09191aa99124316 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 22 Jan 2024 16:47:54 +0530 Subject: [PATCH 0499/1897] Enable old telemetry event (#202990) --- .../browser/configurationService.ts | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 509ec2874c4..f3cc5a0eff6 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -1325,7 +1325,7 @@ class ResetConfigurationDefaultsOverridesCache extends Disposable implements IWo } } -class ConfigurationTelemetryContribution implements IWorkbenchContribution { +class ConfigurationTelemetryContribution extends Disposable implements IWorkbenchContribution { private readonly configurationRegistry = Registry.as(Extensions.Configuration); @@ -1333,6 +1333,33 @@ class ConfigurationTelemetryContribution implements IWorkbenchContribution { @IConfigurationService private readonly configurationService: WorkspaceService, @ITelemetryService private readonly telemetryService: ITelemetryService, ) { + super(); + + // Debounce the event by 1000 ms and merge all affected keys into one event + const debouncedConfigService = Event.debounce(configurationService.onDidChangeConfiguration, (last, cur) => { + const newAffectedKeys: ReadonlySet = last ? new Set([...last.affectedKeys, ...cur.affectedKeys]) : cur.affectedKeys; + return { ...cur, affectedKeys: newAffectedKeys }; + }, 1000, true); + + debouncedConfigService(event => { + if (event.source !== ConfigurationTarget.DEFAULT) { + type UpdateConfigurationClassification = { + owner: 'sandy081'; + comment: 'Event which fires when user updates settings'; + configurationSource: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'What configuration file was updated i.e user or workspace' }; + configurationKeys: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'What configuration keys were updated' }; + }; + type UpdateConfigurationEvent = { + configurationSource: string; + configurationKeys: string[]; + }; + telemetryService.publicLog2('updateConfiguration', { + configurationSource: ConfigurationTargetToString(event.source), + configurationKeys: Array.from(event.affectedKeys) + }); + } + }); + type UpdatedSettingClassification = { owner: 'sandy081'; comment: 'Event reporting the updated setting'; From 7d164333a7238e1e3db6449c0b494b0c4b39948d Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 22 Jan 2024 12:20:55 +0100 Subject: [PATCH 0500/1897] adding the experimental and the feature insight tag --- src/vs/editor/common/config/editorOptions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 8e9ef0b8ef3..73fc32d3b34 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2817,7 +2817,8 @@ class EditorStickyScroll extends BaseEditorOption Date: Mon, 22 Jan 2024 12:35:09 +0100 Subject: [PATCH 0501/1897] Update notebook (#202992) --- .vscode/notebooks/my-endgame.github-issues | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index 5278439e57a..46a88544941 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"November 2023\"\n\n$MINE=assignee:@me\n" + "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"December / January 2024\"\n\n$MINE=assignee:@me\n" }, { "kind": 1, From dca6f399d32b5ead87efc32fb2ada736ce9596c3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 22 Jan 2024 12:28:16 +0100 Subject: [PATCH 0502/1897] Fixes diff editor bug --- src/vs/base/common/assert.ts | 6 ++++++ .../browser/widget/diffEditor/diffEditorViewModel.ts | 11 +++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/vs/base/common/assert.ts b/src/vs/base/common/assert.ts index 5efd7244ff4..5c1bff27990 100644 --- a/src/vs/base/common/assert.ts +++ b/src/vs/base/common/assert.ts @@ -35,6 +35,12 @@ export function assert(condition: boolean): void { } } +export function softAssert(condition: boolean): void { + if (!condition) { + onUnexpectedError(new BugIndicatingError('Assertion Failed')); + } +} + /** * condition must be side-effect free! */ diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts index ab824ae4c1e..b9a3880f0d9 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts @@ -22,6 +22,7 @@ import { DiffEditorOptions } from './diffEditorOptions'; import { optimizeSequenceDiffs } from 'vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations'; import { isDefined } from 'vs/base/common/types'; import { groupAdjacentBy } from 'vs/base/common/arrays'; +import { softAssert } from 'vs/base/common/assert'; export class DiffEditorViewModel extends Disposable implements IDiffEditorViewModel { private readonly _isDiffUpToDate = observableValue(this, false); @@ -509,8 +510,14 @@ export class UnchangedRegion { visibleLineCountTop: number, visibleLineCountBottom: number, ) { - this._visibleLineCountTop.set(visibleLineCountTop, undefined); - this._visibleLineCountBottom.set(visibleLineCountBottom, undefined); + const visibleLineCountTop2 = Math.max(Math.min(visibleLineCountTop, this.lineCount), 0); + const visibleLineCountBottom2 = Math.max(Math.min(visibleLineCountBottom, this.lineCount - visibleLineCountTop), 0); + + softAssert(visibleLineCountTop === visibleLineCountTop2); + softAssert(visibleLineCountBottom === visibleLineCountBottom2); + + this._visibleLineCountTop.set(visibleLineCountTop2, undefined); + this._visibleLineCountBottom.set(visibleLineCountBottom2, undefined); } public setVisibleRanges(visibleRanges: LineRangeMapping[], tx: ITransaction): UnchangedRegion[] { From e5e5cb8632abd7fe6f4c522b84b3ee776b4c4f3b Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 22 Jan 2024 14:12:14 +0100 Subject: [PATCH 0503/1897] adding the license changes --- ThirdPartyNotices.txt | 102 ++++++--------- cglicenses.json | 88 +++++++++++++ cli/ThirdPartyNotices.txt | 253 +++++++++----------------------------- package.json | 2 +- 4 files changed, 187 insertions(+), 258 deletions(-) diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 4fd1d6a460b..14af9ca8b8a 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -263,34 +263,6 @@ suitability for any purpose. --------------------------------------------------------- -better-go-syntax 1.0.0 - MIT -https://github.com/jeff-hykin/better-go-syntax/ - -MIT License - -Copyright (c) 2019 Jeff Hykin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ---------------------------------------------------------- - ---------------------------------------------------------- - Colorsublime-Themes 0.1.0 https://github.com/Colorsublime/Colorsublime-Themes @@ -545,6 +517,34 @@ to the base-name name of the original file, and an extension of txt, html, or si --------------------------------------------------------- +go-syntax 0.5.1 - MIT +https://github.com/worlpaker/go-syntax + +MIT License + +Copyright (c) 2023 Furkan Ozalp + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +--------------------------------------------------------- + +--------------------------------------------------------- + HTML 5.1 W3C Working Draft 08 October 2015 - W3C Document License http://www.w3.org/TR/2015/WD-html51-20151008/ @@ -1146,38 +1146,12 @@ Apache License --------------------------------------------------------- -language-less 0.34.2 - MIT -https://github.com/atom/language-less +language-less 0.6.1 - MIT +https://github.com/radium-v/Better-Less -The MIT License (MIT) +MIT License -Copyright (c) 2014 GitHub Inc. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - -This package was derived from a TextMate bundle located at -https://github.com/textmate/less.tmbundle and distributed under the following -license, located in `LICENSE.md`: - -Copyright (c) 2010 Scott Kyle and Rasmus Andersson +Copyright (c) 2017 John Kreitlow Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -1186,16 +1160,16 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- @@ -1441,7 +1415,7 @@ SOFTWARE. --------------------------------------------------------- -microsoft/vscode-mssql 1.20.0 - MIT +microsoft/vscode-mssql 1.23.0 - MIT https://github.com/microsoft/vscode-mssql ------------------------------------------ START OF LICENSE ----------------------------------------- @@ -1532,7 +1506,7 @@ SOFTWARE. --------------------------------------------------------- -redhat-developer/vscode-java 1.22.0 - MIT +redhat-developer/vscode-java 1.26.0 - MIT https://github.com/redhat-developer/vscode-java The MIT License (MIT) diff --git a/cglicenses.json b/cglicenses.json index a5b108d4f79..3d4e0f80443 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -543,5 +543,93 @@ "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", "SOFTWARE" ] + }, + { + "name": "vscode-webview-tools", + "fullLicenseText": [ + "MIT License", + "", + "Copyright (c) 2020 Connor Peet", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the 'Software'), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE." + ] + }, + { + "name": "@vscode/v8-heap-parser", + "fullLicenseText": [ + "The code in this package is built upon that in the Chrome devtools", + "(https://github.com/ChromeDevTools/devtools-frontend) which is made available", + "under the following license:", + "", + "// Copyright 2014 The Chromium Authors. All rights reserved.", + "//", + "// Redistribution and use in source and binary forms, with or without", + "// modification, are permitted provided that the following conditions are", + "// met:", + "//", + "// * Redistributions of source code must retain the above copyright", + "// notice, this list of conditions and the following disclaimer.", + "// * Redistributions in binary form must reproduce the above", + "// copyright notice, this list of conditions and the following disclaimer", + "// in the documentation and/or other materials provided with the", + "// distribution.", + "// * Neither the name of Google Inc. nor the names of its", + "// contributors may be used to endorse or promote products derived from", + "// this software without specific prior written permission.", + "//", + "// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS", + "// 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT", + "// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR", + "// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT", + "// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,", + "// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT", + "// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,", + "// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY", + "// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT", + "// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE", + "// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + ] + }, + { + "name": "heap", + "fullLicenseText": [ + "The MIT License", + "", + "Copyright (c) Xueqiao (Joe) Xu", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the 'Software'), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in", + "all copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN", + "THE SOFTWARE." + ] } ] diff --git a/cli/ThirdPartyNotices.txt b/cli/ThirdPartyNotices.txt index 8903f9e531b..a867935f2b4 100644 --- a/cli/ThirdPartyNotices.txt +++ b/cli/ThirdPartyNotices.txt @@ -618,6 +618,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- bitflags 1.3.2 - MIT/Apache-2.0 +bitflags 2.4.1 - MIT OR Apache-2.0 https://github.com/bitflags/bitflags Copyright (c) 2014 The Rust Project Developers @@ -2368,6 +2369,38 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- +equivalent 1.0.1 - Apache-2.0 OR MIT +https://github.com/indexmap-rs/equivalent + +Copyright (c) 2016--2023 + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +--------------------------------------------------------- + +--------------------------------------------------------- + errno 0.3.1 - MIT OR Apache-2.0 https://github.com/lambda-fairy/rust-errno @@ -3235,7 +3268,7 @@ getrandom 0.1.16 - MIT OR Apache-2.0 getrandom 0.2.7 - MIT OR Apache-2.0 https://github.com/rust-random/getrandom -Copyright 2018 Developers of the Rand project +Copyright (c) 2018-2024 The rust-random Project Developers Copyright (c) 2014 The Rust Project Developers Permission is hereby granted, free of charge, to any @@ -3265,7 +3298,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -h2 0.3.17 - MIT +h2 0.3.24 - MIT https://github.com/hyperium/h2 The MIT License (MIT) @@ -3300,6 +3333,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- hashbrown 0.12.3 - MIT OR Apache-2.0 +hashbrown 0.14.3 - MIT OR Apache-2.0 https://github.com/rust-lang/hashbrown Copyright (c) 2016 Amanieu d'Antras @@ -3792,6 +3826,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- indexmap 1.9.1 - Apache-2.0 OR MIT +indexmap 2.1.0 - Apache-2.0 OR MIT https://github.com/bluss/indexmap Copyright (c) 2016--2017 @@ -5007,7 +5042,7 @@ OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -openssl 0.10.55 - Apache-2.0 +openssl 0.10.60 - Apache-2.0 https://github.com/sfackler/rust-openssl Copyright 2011-2017 Google Inc. @@ -5086,7 +5121,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -openssl-sys 0.9.90 - MIT +openssl-sys 0.9.96 - MIT https://github.com/sfackler/rust-openssl The MIT License (MIT) @@ -8038,10 +8073,10 @@ DEALINGS IN THE SOFTWARE. sha1 0.10.5 - MIT OR Apache-2.0 https://github.com/RustCrypto/hashes -All crates licensed under either of +All crates in this repository are licensed under either of - * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - * [MIT license](http://opensource.org/licenses/MIT) +* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) +* [MIT license](http://opensource.org/licenses/MIT) at your option. @@ -8056,9 +8091,9 @@ Unless you explicitly state otherwise, any contribution intentionally submitted [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg [deps-image]: https://deps.rs/repo/github/RustCrypto/hashes/status.svg [deps-link]: https://deps.rs/repo/github/RustCrypto/hashes -[msrv-1.41]: https://img.shields.io/badge/rustc-1.41.0+-blue.svg -[msrv-1.56]: https://img.shields.io/badge/rustc-1.56.0+-blue.svg -[msrv-1.57]: https://img.shields.io/badge/rustc-1.57.0+-blue.svg +[msrv-1.71]: https://img.shields.io/badge/rustc-1.71.0+-blue.svg +[msrv-1.72]: https://img.shields.io/badge/rustc-1.72.0+-blue.svg +[msrv-1.74]: https://img.shields.io/badge/rustc-1.74.0+-blue.svg [//]: # (crates) @@ -8131,10 +8166,10 @@ Unless you explicitly state otherwise, any contribution intentionally submitted sha2 0.10.6 - MIT OR Apache-2.0 https://github.com/RustCrypto/hashes -All crates licensed under either of +All crates in this repository are licensed under either of - * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - * [MIT license](http://opensource.org/licenses/MIT) +* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) +* [MIT license](http://opensource.org/licenses/MIT) at your option. @@ -8149,9 +8184,9 @@ Unless you explicitly state otherwise, any contribution intentionally submitted [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg [deps-image]: https://deps.rs/repo/github/RustCrypto/hashes/status.svg [deps-link]: https://deps.rs/repo/github/RustCrypto/hashes -[msrv-1.41]: https://img.shields.io/badge/rustc-1.41.0+-blue.svg -[msrv-1.56]: https://img.shields.io/badge/rustc-1.56.0+-blue.svg -[msrv-1.57]: https://img.shields.io/badge/rustc-1.57.0+-blue.svg +[msrv-1.71]: https://img.shields.io/badge/rustc-1.71.0+-blue.svg +[msrv-1.72]: https://img.shields.io/badge/rustc-1.72.0+-blue.svg +[msrv-1.74]: https://img.shields.io/badge/rustc-1.74.0+-blue.svg [//]: # (crates) @@ -8476,7 +8511,7 @@ SOFTWARE. --------------------------------------------------------- strsim 0.10.0 - MIT -https://github.com/dguo/strsim-rs +https://github.com/rapidfuzz/strsim-rs The MIT License (MIT) @@ -9273,7 +9308,7 @@ https://github.com/seanmonstar/try-lock The MIT License (MIT) -Copyright (c) 2018 Sean McArthur +Copyright (c) 2018-2023 Sean McArthur Copyright (c) 2016 Alex Crichton Permission is hereby granted, free of charge, to any person obtaining a copy @@ -10622,31 +10657,7 @@ DEALINGS IN THE SOFTWARE. xdg-home 1.0.0 - MIT https://github.com/zeenix/xdg-home -The MIT License (MIT) - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +LICENSE-MIT --------------------------------------------------------- --------------------------------------------------------- @@ -10668,31 +10679,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI zbus 3.13.1 - MIT https://github.com/dbus2/zbus/ -The MIT License (MIT) - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +LICENSE-MIT --------------------------------------------------------- --------------------------------------------------------- @@ -10700,31 +10687,7 @@ DEALINGS IN THE SOFTWARE. zbus_macros 3.13.1 - MIT https://github.com/dbus2/zbus/ -The MIT License (MIT) - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +LICENSE-MIT --------------------------------------------------------- --------------------------------------------------------- @@ -10732,31 +10695,7 @@ DEALINGS IN THE SOFTWARE. zbus_names 2.5.1 - MIT https://github.com/dbus2/zbus/ -The MIT License (MIT) - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +LICENSE-MIT --------------------------------------------------------- --------------------------------------------------------- @@ -10846,31 +10785,7 @@ SOFTWARE. zvariant 3.14.0 - MIT https://github.com/dbus2/zbus/ -The MIT License (MIT) - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +LICENSE-MIT --------------------------------------------------------- --------------------------------------------------------- @@ -10878,31 +10793,7 @@ DEALINGS IN THE SOFTWARE. zvariant_derive 3.14.0 - MIT https://github.com/dbus2/zbus/ -The MIT License (MIT) - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +LICENSE-MIT --------------------------------------------------------- --------------------------------------------------------- @@ -10910,29 +10801,5 @@ DEALINGS IN THE SOFTWARE. zvariant_utils 1.0.1 - MIT https://github.com/dbus2/zbus/ -The MIT License (MIT) - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +LICENSE-MIT --------------------------------------------------------- \ No newline at end of file diff --git a/package.json b/package.json index b2214715728..c27208c3fb8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.86.0", - "distro": "e74e6b268c5f26bfeb37b3468500c70b4dbd98da", + "distro": "4d476863b75b35121a8d2a49c6adbdd2d9ea7572", "author": { "name": "Microsoft Corporation" }, From 4217af76967d061d673ad0d3464c1c8abbe8cd80 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 22 Jan 2024 14:31:23 +0100 Subject: [PATCH 0504/1897] restore stashed sessions for when accidentially dismissing a session (#203006) --- .../browser/inlineChat.contribution.ts | 2 +- .../inlineChat/browser/inlineChatActions.ts | 2 +- .../browser/inlineChatController.ts | 69 +++--------- .../browser/inlineChatSavingServiceImpl.ts | 3 +- .../inlineChat/browser/inlineChatSession.ts | 102 +++++++++++++++--- .../browser/inlineChatSessionService.ts | 17 ++- .../browser/inlineChatSessionServiceImpl.ts | 45 +++++--- .../browser/inlineChatStrategies.ts | 23 +--- 8 files changed, 156 insertions(+), 107 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index 0d9c8cce7be..e86fb8b900f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -32,7 +32,7 @@ registerEditorContribution(INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID, InlineChatA registerAction2(InlineChatActions.CloseAction); registerAction2(InlineChatActions.ConfigureInlineChatAction); -// registerAction2(InlineChatActions.UnstashSessionAction); +registerAction2(InlineChatActions.UnstashSessionAction); registerAction2(InlineChatActions.MakeRequestAction); registerAction2(InlineChatActions.StopRequestAction); registerAction2(InlineChatActions.ReRunRequestAction); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index af47d8f2b76..dd492579f8b 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -49,7 +49,7 @@ export class UnstashSessionAction extends EditorAction2 { }); } - override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { + override async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { const ctrl = InlineChatController.get(editor); if (ctrl) { const session = ctrl.unstashLastSession(); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 262736553ab..2c7f73adbf5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -12,7 +12,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Lazy } from 'vs/base/common/lazy'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { MovingAverage } from 'vs/base/common/numbers'; import { StopWatch } from 'vs/base/common/stopwatch'; import { assertType } from 'vs/base/common/types'; @@ -46,6 +46,8 @@ import { EditModeStrategy, IEditObserver, LivePreviewStrategy, LiveStrategy, Pre import { IInlineChatMessageAppender, InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatProgressItem, IInlineChatRequest, IInlineChatResponse, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { StashedSession } from './inlineChatSession'; +import { IValidEditOperation } from 'vs/editor/common/model'; export const enum State { CREATE_SESSION = 'CREATE_SESSION', @@ -124,8 +126,8 @@ export class InlineChatController implements IEditorContribution { readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store); readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store); - private readonly _sessionStore: DisposableStore = this._store.add(new DisposableStore()); - + private readonly _sessionStore = this._store.add(new DisposableStore()); + private readonly _stashedSession = this._store.add(new MutableDisposable()); private _session?: Session; private _strategy?: EditModeStrategy; @@ -850,14 +852,22 @@ export class InlineChatController implements IEditorContribution { assertType(this._strategy); this._sessionStore.clear(); + let undoCancelEdits: IValidEditOperation[] = []; try { - await this._strategy.cancel(); + undoCancelEdits = this._strategy.cancel(); } catch (err) { this._dialogService.error(localize('err.discard', "Failed to discard changes.", toErrorMessage(err))); this._log('FAILED to discard changes'); this._log(err); } - this._inlineChatSessionService.releaseSession(this._session); + + this._stashedSession.clear(); + if (!this._session.isUnstashed && !!this._session.lastExchange && this._session.hunkData.size === this._session.hunkData.pending) { + // only stash sessions that were not unstashed, not "empty", and not interacted with + this._stashedSession.value = this._inlineChatSessionService.stashSession(this._session, this._editor, undoCancelEdits); + } else { + this._inlineChatSessionService.releaseSession(this._session); + } } this._resetWidget(); @@ -1132,7 +1142,7 @@ export class InlineChatController implements IEditorContribution { } unstashLastSession(): Session | undefined { - return undefined; + return this._stashedSession.value?.unstash(); } joinCurrentRun(): Promise | undefined { @@ -1140,53 +1150,6 @@ export class InlineChatController implements IEditorContribution { } } -// class StashedSession { - -// private readonly _listener: IDisposable; -// private readonly _ctxHasStashedSession: IContextKey; -// private _session: Session | undefined; - -// constructor( -// editor: ICodeEditor, -// session: Session, -// @IContextKeyService contextKeyService: IContextKeyService, -// @IInlineChatSessionService private readonly _sessionService: IInlineChatSessionService, -// @ILogService private readonly _logService: ILogService, -// ) { -// this._ctxHasStashedSession = CTX_INLINE_CHAT_HAS_STASHED_SESSION.bindTo(contextKeyService); - -// // keep session for a little bit, only release when user continues to work (type, move cursor, etc.) -// this._session = session; -// this._ctxHasStashedSession.set(true); -// this._listener = Event.once(Event.any(editor.onDidChangeCursorSelection, editor.onDidChangeModelContent, editor.onDidChangeModel))(() => { -// this._session = undefined; -// this._sessionService.releaseSession(session); -// this._ctxHasStashedSession.reset(); -// }); -// } - -// dispose() { -// this._listener.dispose(); -// this._ctxHasStashedSession.reset(); -// if (this._session) { -// this._sessionService.releaseSession(this._session); -// } -// } - -// unstash(): Session | undefined { -// if (!this._session) { -// return undefined; -// } -// this._listener.dispose(); -// const result = this._session; -// result.markUnstashed(); -// this._session = undefined; -// this._logService.debug('[IE] Unstashed session'); -// return result; -// } - -// } - async function showMessageResponse(accessor: ServicesAccessor, query: string, response: string) { const chatService = accessor.get(IChatService); const providerId = chatService.getProviderInfos()[0]?.id; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts index bbd97b97408..b68c999bd6c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts @@ -28,6 +28,7 @@ import { compare } from 'vs/base/common/strings'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; +import { Event } from 'vs/base/common/event'; interface SessionData { readonly resourceUri: URI; @@ -261,7 +262,7 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService { let listener: IDisposable | undefined; const whenEnded = new Promise(resolve => { - listener = this._inlineChatSessionService.onDidEndSession(e => { + listener = Event.any(this._inlineChatSessionService.onDidEndSession, this._inlineChatSessionService.onDidStashSession)(e => { const data = sessions.get(e.session); if (data) { data.dispose(); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index fb2cdf8e54c..94f64a77e18 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -7,8 +7,8 @@ import { URI } from 'vs/base/common/uri'; import { Emitter, Event } from 'vs/base/common/event'; import { ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; import { IWorkspaceTextEdit, TextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; -import { IIdentifiedSingleEditOperation, IModelDecorationOptions, IModelDeltaDecoration, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; -import { EditMode, IInlineChatSessionProvider, IInlineChatSession, IInlineChatBulkEditResponse, IInlineChatEditResponse, InlineChatResponseType, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IIdentifiedSingleEditOperation, IModelDecorationOptions, IModelDeltaDecoration, ITextModel, IValidEditOperation, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { EditMode, IInlineChatSessionProvider, IInlineChatSession, IInlineChatBulkEditResponse, IInlineChatEditResponse, InlineChatResponseType, InlineChatResponseTypes, CTX_INLINE_CHAT_HAS_STASHED_SESSION } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -22,14 +22,17 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; -import { Recording } from './inlineChatSessionService'; +import { IInlineChatSessionService, Recording } from './inlineChatSessionService'; import { LineRange } from 'vs/editor/common/core/lineRange'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { asRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { coalesceInPlace } from 'vs/base/common/arrays'; import { Iterable } from 'vs/base/common/iterator'; import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ILogService } from 'vs/platform/log/common/log'; export type TelemetryData = { @@ -37,6 +40,7 @@ export type TelemetryData = { rounds: string; undos: string; edits: boolean; + unstashed: number; finishedByEdit: boolean; startTime: string; endTime: string; @@ -50,6 +54,7 @@ export type TelemetryDataClassification = { rounds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of request that were made' }; undos: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Requests that have been undone' }; edits: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Did edits happen while the session was active' }; + unstashed: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'How often did this session become stashed and resumed' }; finishedByEdit: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Did edits cause the session to terminate' }; startTime: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'When the session started' }; endTime: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'When the session ended' }; @@ -167,7 +172,8 @@ export class Session { finishedByEdit: false, rounds: '', undos: '', - editMode + editMode, + unstashed: 0 }; } @@ -184,6 +190,7 @@ export class Session { } markUnstashed() { + this._teldata.unstashed! += 1; this._isUnstashed = true; } @@ -415,6 +422,56 @@ export class ReplyResponse { } } +export class StashedSession { + + private readonly _listener: IDisposable; + private readonly _ctxHasStashedSession: IContextKey; + private _session: Session | undefined; + + constructor( + editor: ICodeEditor, + session: Session, + private readonly _undoCancelEdits: IValidEditOperation[], + @IContextKeyService contextKeyService: IContextKeyService, + @IInlineChatSessionService private readonly _sessionService: IInlineChatSessionService, + @ILogService private readonly _logService: ILogService + ) { + this._ctxHasStashedSession = CTX_INLINE_CHAT_HAS_STASHED_SESSION.bindTo(contextKeyService); + + // keep session for a little bit, only release when user continues to work (type, move cursor, etc.) + this._session = session; + this._ctxHasStashedSession.set(true); + this._listener = Event.once(Event.any(editor.onDidChangeCursorSelection, editor.onDidChangeModelContent, editor.onDidChangeModel))(() => { + this._session = undefined; + this._sessionService.releaseSession(session); + this._ctxHasStashedSession.reset(); + }); + } + + dispose() { + this._listener.dispose(); + this._ctxHasStashedSession.reset(); + if (this._session) { + this._sessionService.releaseSession(this._session); + } + } + + unstash(): Session | undefined { + if (!this._session) { + return undefined; + } + this._listener.dispose(); + const result = this._session; + result.markUnstashed(); + result.hunkData.ignoreTextModelNChanges = true; + result.textModelN.pushEditOperations(null, this._undoCancelEdits, () => null); + result.hunkData.ignoreTextModelNChanges = false; + this._session = undefined; + this._logService.debug('[IE] Unstashed session'); + return result; + } +} + // --- export class HunkData { @@ -629,6 +686,32 @@ export class HunkData { return Iterable.reduce(this._data.values(), (r, { state }) => r + (state === HunkState.Pending ? 1 : 0), 0); } + private _discardEdits(item: HunkInformation): ISingleEditOperation[] { + const edits: ISingleEditOperation[] = []; + const rangesN = item.getRangesN(); + const ranges0 = item.getRanges0(); + for (let i = 1; i < rangesN.length; i++) { + const modifiedRange = rangesN[i]; + + const originalValue = this._textModel0.getValueInRange(ranges0[i]); + edits.push(EditOperation.replace(modifiedRange, originalValue)); + } + return edits; + } + + discardAll() { + const edits: ISingleEditOperation[][] = []; + for (const item of this.getInfo()) { + edits.push(this._discardEdits(item)); + } + const undoEdits: IValidEditOperation[][] = []; + this._textModelN.pushEditOperations(null, edits.flat(), (_undoEdits) => { + undoEdits.push(_undoEdits); + return null; + }); + return undoEdits.flat(); + } + getInfo(): HunkInformation[] { const result: HunkInformation[] = []; @@ -655,14 +738,7 @@ export class HunkData { // DISCARD: replace modified range with original value. The modified range is retrieved from a decoration // which was created above so that typing in the editor keeps discard working. if (data.state === HunkState.Pending) { - const edits: ISingleEditOperation[] = []; - const rangesN = item.getRangesN(); - const ranges0 = item.getRanges0(); - for (let i = 1; i < rangesN.length; i++) { - const modifiedRange = rangesN[i]; - const originalValue = this._textModel0.getValueInRange(ranges0[i]); - edits.push(EditOperation.replace(modifiedRange, originalValue)); - } + const edits = this._discardEdits(item); this._textModelN.pushEditOperations(null, edits, () => null); data.state = HunkState.Rejected; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts index ee8de25c12d..0f2f0fb4142 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts @@ -10,7 +10,8 @@ import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser' import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Session } from './inlineChatSession'; +import { Session, StashedSession } from './inlineChatSession'; +import { IValidEditOperation } from 'vs/editor/common/model'; export type Recording = { @@ -25,14 +26,18 @@ export interface ISessionKeyComputer { export const IInlineChatSessionService = createDecorator('IInlineChatSessionService'); +export interface IInlineChatSessionEvent { + readonly editor: ICodeEditor; + readonly session: Session; +} + export interface IInlineChatSessionService { _serviceBrand: undefined; onWillStartSession: Event; - - onDidMoveSession: Event<{ editor: ICodeEditor; session: Session }>; - - onDidEndSession: Event<{ editor: ICodeEditor; session: Session }>; + onDidMoveSession: Event; + onDidStashSession: Event; + onDidEndSession: Event; createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: IRange }, token: CancellationToken): Promise; @@ -44,6 +49,8 @@ export interface IInlineChatSessionService { releaseSession(session: Session): void; + stashSession(session: Session, editor: ICodeEditor, undoCancelEdits: IValidEditOperation[]): StashedSession; + registerSessionKeyComputer(scheme: string, value: ISessionKeyComputer): IDisposable; // diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index acd05d3527d..ab3d899aed6 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -16,11 +16,12 @@ import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Iterable } from 'vs/base/common/iterator'; import { raceCancellation } from 'vs/base/common/async'; -import { Recording, IInlineChatSessionService, ISessionKeyComputer } from './inlineChatSessionService'; -import { HunkData, Session, SessionWholeRange, TelemetryData, TelemetryDataClassification } from './inlineChatSession'; +import { Recording, IInlineChatSessionService, ISessionKeyComputer, IInlineChatSessionEvent } from './inlineChatSessionService'; +import { HunkData, Session, SessionWholeRange, StashedSession, TelemetryData, TelemetryDataClassification } from './inlineChatSession'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; -import { ITextModel } from 'vs/editor/common/model'; +import { ITextModel, IValidEditOperation } from 'vs/editor/common/model'; import { Schemas } from 'vs/base/common/network'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; type SessionData = { editor: ICodeEditor; @@ -35,11 +36,14 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { private readonly _onWillStartSession = new Emitter(); readonly onWillStartSession: Event = this._onWillStartSession.event; - private readonly _onDidMoveSession = new Emitter<{ session: Session; editor: ICodeEditor }>(); - readonly onDidMoveSession: Event<{ session: Session; editor: ICodeEditor }> = this._onDidMoveSession.event; + private readonly _onDidMoveSession = new Emitter(); + readonly onDidMoveSession: Event = this._onDidMoveSession.event; - private readonly _onDidEndSession = new Emitter<{ editor: ICodeEditor; session: Session }>(); - readonly onDidEndSession: Event<{ editor: ICodeEditor; session: Session }> = this._onDidEndSession.event; + private readonly _onDidEndSession = new Emitter(); + readonly onDidEndSession: Event = this._onDidEndSession.event; + + private readonly _onDidStashSession = new Emitter(); + readonly onDidStashSession: Event = this._onDidStashSession.event; private readonly _sessions = new Map(); private readonly _keyComputers = new Map(); @@ -51,7 +55,8 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { @IModelService private readonly _modelService: IModelService, @ITextModelService private readonly _textModelService: ITextModelService, @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, - @ILogService private readonly _logService: ILogService + @ILogService private readonly _logService: ILogService, + @IInstantiationService private readonly _instaService: IInstantiationService, ) { } dispose() { @@ -189,18 +194,20 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { return; } - // keep recording - const newLen = this._recordings.unshift(session.asRecording()); - if (newLen > 5) { - this._recordings.pop(); - } - - // send telemetry + this._keepRecording(session); this._telemetryService.publicLog2('interactiveEditor/session', session.asTelemetryData()); this._onDidEndSession.fire({ editor: data.editor, session }); } + stashSession(session: Session, editor: ICodeEditor, undoCancelEdits: IValidEditOperation[]): StashedSession { + this._keepRecording(session); + const result = this._instaService.createInstance(StashedSession, editor, session, undoCancelEdits); + this._onDidStashSession.fire({ editor, session }); + this._logService.trace(`[IE] did STASH session for ${editor.getId()}, ${session.provider.debugName}`); + return result; + } + getCodeEditor(session: Session): ICodeEditor { for (const [, data] of this._sessions) { if (data.session === session) { @@ -229,6 +236,14 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { } // --- debug + + private _keepRecording(session: Session) { + const newLen = this._recordings.unshift(session.asRecording()); + if (newLen > 5) { + this._recordings.pop(); + } + } + recordings(): readonly Recording[] { return this._recordings; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index b7009c9184d..dab83d92c17 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -75,7 +75,9 @@ export abstract class EditModeStrategy { abstract apply(): Promise; - abstract cancel(): Promise; + cancel() { + return this._session.hunkData.discardAll(); + } async acceptHunk(): Promise { this._onDidAccept.fire(); @@ -186,10 +188,6 @@ export class PreviewStrategy extends EditModeStrategy { } } - async cancel(): Promise { - // nothing to do - } - override async makeChanges(edits: ISingleEditOperation[], obs: IEditObserver): Promise { return this._makeChanges(edits, obs, undefined, undefined); } @@ -267,15 +265,6 @@ export class LivePreviewStrategy extends EditModeStrategy { } } - async cancel() { - const { textModelN: modelN, textModelNAltVersion, textModelNSnapshotAltVersion } = this._session; - if (modelN.isDisposed()) { - return; - } - const targetAltVersion = textModelNSnapshotAltVersion ?? textModelNAltVersion; - await undoModelUntil(modelN, targetAltVersion); - } - override async undoChanges(altVersionId: number): Promise { const { textModelN } = this._session; await undoModelUntil(textModelN, altVersionId); @@ -495,11 +484,9 @@ export class LiveStrategy extends EditModeStrategy { } } - async cancel() { - for (const item of this._session.hunkData.getInfo()) { - item.discardChanges(); - } + override cancel() { this._resetDiff(); + return super.cancel(); } override async undoChanges(altVersionId: number): Promise { From cc593a387ec2f7f22d67492633ae638953bbb2e3 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 22 Jan 2024 14:39:08 +0100 Subject: [PATCH 0505/1897] inline chat tweaks and tests (#202984) * update action ids * detect user edits inside and outside of chat UI, add test for https://github.com/microsoft/vscode-copilot/issues/3523 * fix tests --- .../inlineChat/browser/inlineChatActions.ts | 2 + .../browser/inlineChatController.ts | 2 +- .../contrib/inlineChat/common/inlineChat.ts | 2 +- .../test/browser/inlineChatController.test.ts | 44 +++++++++++++++++-- 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index dd492579f8b..c1c0679e305 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -32,6 +32,8 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); +CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); + export const LOCALIZED_START_INLINE_CHAT_STRING = localize('run', 'Start Inline Chat'); export const START_INLINE_CHAT = registerIcon('start-inline-chat', Codicon.sparkle, localize('startInlineChat', 'Icon which spawns the inline chat from the editor toolbar.')); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 2c7f73adbf5..977f9b71bdf 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -395,7 +395,7 @@ export class InlineChatController implements IEditorContribution { this._sessionStore.add(this._editor.onDidChangeModelContent(e => { - if (!this._session?.hunkData.ignoreTextModelNChanges && this._strategy?.hasFocus()) { + if (!this._session?.hunkData.ignoreTextModelNChanges) { this._ctxUserDidEdit.set(altVersionNow !== this._editor.getModel()?.getAlternativeVersionId()); } diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index f7becc10ec4..1b5712c36e2 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -150,7 +150,7 @@ export const CTX_INLINE_CHAT_EDIT_MODE = new RawContextKey('config.inl // --- (select) action identifier -export const ACTION_ACCEPT_CHANGES = 'interactive.acceptChanges'; +export const ACTION_ACCEPT_CHANGES = 'inlineChat.acceptChanges'; export const ACTION_REGENERATE_RESPONSE = 'inlineChat.regenerate'; export const ACTION_VIEW_IN_CHAT = 'inlineChat.viewInChat'; diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 1648debcf8c..ae238b40501 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -36,9 +36,10 @@ import { IInlineChatSavingService } from '../../browser/inlineChatSavingService' import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionServiceImpl'; import { IInlineChatSessionService } from '../../browser/inlineChatSessionService'; -import { EditMode, IInlineChatService, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatService, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; suite('InteractiveChatController', function () { class TestController extends InlineChatController { @@ -90,14 +91,14 @@ suite('InteractiveChatController', function () { let editor: IActiveCodeEditor; let model: ITextModel; let ctrl: TestController; - // let contextKeys: MockContextKeyService; + let contextKeyService: MockContextKeyService; let inlineChatService: InlineChatServiceImpl; let inlineChatSessionService: IInlineChatSessionService; let instaService: TestInstantiationService; setup(function () { - const contextKeyService = new MockContextKeyService(); + contextKeyService = new MockContextKeyService(); inlineChatService = new InlineChatServiceImpl(contextKeyService); configurationService = new TestConfigurationService(); @@ -436,4 +437,41 @@ suite('InteractiveChatController', function () { }); }); + test('escape doesn\'t remove code added from inline editor chat #3523 1/2', async function () { + + + // NO manual edits -> cancel + ctrl = instaService.createInstance(TestController, editor); + const p = ctrl.waitFor([...TestController.INIT_SEQUENCE, State.MAKE_REQUEST, State.APPLY_RESPONSE, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + const r = ctrl.run({ message: 'GENERATED', autoSend: true }); + await p; + + assert.ok(model.getValue().includes('GENERATED')); + assert.strictEqual(contextKeyService.getContextKeyValue(CTX_INLINE_CHAT_USER_DID_EDIT.key), undefined); + ctrl.cancelSession(); + await r; + assert.ok(!model.getValue().includes('GENERATED')); + + }); + + test('escape doesn\'t remove code added from inline editor chat #3523, 2/2', async function () { + + // manual edits -> finish + ctrl = instaService.createInstance(TestController, editor); + const p = ctrl.waitFor([...TestController.INIT_SEQUENCE, State.MAKE_REQUEST, State.APPLY_RESPONSE, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + const r = ctrl.run({ message: 'GENERATED', autoSend: true }); + await p; + + assert.ok(model.getValue().includes('GENERATED')); + + editor.executeEdits('test', [EditOperation.insert(model.getFullModelRange().getEndPosition(), 'MANUAL')]); + assert.strictEqual(contextKeyService.getContextKeyValue(CTX_INLINE_CHAT_USER_DID_EDIT.key), true); + + ctrl.finishExistingSession(); + await r; + assert.ok(model.getValue().includes('GENERATED')); + assert.ok(model.getValue().includes('MANUAL')); + + }); + }); From 236d5dc072622d511e2f8f6ddd83fd24a7165467 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 22 Jan 2024 14:40:18 +0100 Subject: [PATCH 0506/1897] `sha256` over `md5` (#202985) * `sha256` over `md5` * add missing JS file --- build/lib/tsb/builder.js | 4 ++-- build/lib/tsb/builder.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/lib/tsb/builder.js b/build/lib/tsb/builder.js index e041a754c4f..e87945ea9cc 100644 --- a/build/lib/tsb/builder.js +++ b/build/lib/tsb/builder.js @@ -89,7 +89,7 @@ function createTypeScriptBuilder(config, projectFile, cmd) { if (/\.d\.ts$/.test(fileName)) { // if it's already a d.ts file just emit it signature const snapshot = host.getScriptSnapshot(fileName); - const signature = crypto.createHash('md5') + const signature = crypto.createHash('sha256') .update(snapshot.getText(0, snapshot.getLength())) .digest('base64'); return resolve({ @@ -106,7 +106,7 @@ function createTypeScriptBuilder(config, projectFile, cmd) { continue; } if (/\.d\.ts$/.test(file.name)) { - signature = crypto.createHash('md5') + signature = crypto.createHash('sha256') .update(file.text) .digest('base64'); if (!userWantsDeclarations) { diff --git a/build/lib/tsb/builder.ts b/build/lib/tsb/builder.ts index 7d40239e715..9fc476ae702 100644 --- a/build/lib/tsb/builder.ts +++ b/build/lib/tsb/builder.ts @@ -113,7 +113,7 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str if (/\.d\.ts$/.test(fileName)) { // if it's already a d.ts file just emit it signature const snapshot = host.getScriptSnapshot(fileName); - const signature = crypto.createHash('md5') + const signature = crypto.createHash('sha256') .update(snapshot.getText(0, snapshot.getLength())) .digest('base64'); @@ -134,7 +134,7 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str } if (/\.d\.ts$/.test(file.name)) { - signature = crypto.createHash('md5') + signature = crypto.createHash('sha256') .update(file.text) .digest('base64'); From a8dfc65ebd90122ded88577df386bf78ed404ebe Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 22 Jan 2024 15:59:55 +0100 Subject: [PATCH 0507/1897] fix tests, use real diff (slow) (#203022) --- .../test/browser/inlineChatController.test.ts | 11 ++- .../test/browser/inlineChatSession.test.ts | 71 +--------------- .../test/browser/testWorkerService.ts | 84 +++++++++++++++++++ 3 files changed, 93 insertions(+), 73 deletions(-) create mode 100644 src/vs/workbench/contrib/inlineChat/test/browser/testWorkerService.ts diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index ae238b40501..8efc9619cc9 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -40,6 +40,8 @@ import { CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatService, InlineChat import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { TestWorkerService } from './testWorkerService'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; suite('InteractiveChatController', function () { class TestController extends InlineChatController { @@ -106,6 +108,7 @@ suite('InteractiveChatController', function () { configurationService.setUserConfiguration('editor', {}); const serviceCollection = new ServiceCollection( + [IEditorWorkerService, new SyncDescriptor(TestWorkerService)], [IContextKeyService, contextKeyService], [IInlineChatService, inlineChatService], [IDiffProviderFactoryService, new SyncDescriptor(TestDiffProviderFactoryService)], @@ -263,7 +266,7 @@ suite('InteractiveChatController', function () { const session = inlineChatSessionService.getSession(editor, editor.getModel()!.uri); assert.ok(session); - assert.deepStrictEqual(session.wholeRange.value, new Range(1, 1, 1, 6)); + assert.deepStrictEqual(session.wholeRange.value, new Range(1, 1, 1, 10 /* line length */)); editor.setSelection(new Range(2, 1, 2, 1)); editor.trigger('test', 'type', { text: 'a' }); @@ -299,19 +302,19 @@ suite('InteractiveChatController', function () { store.add(d); ctrl = instaService.createInstance(TestController, editor); const p = ctrl.waitFor(TestController.INIT_SEQUENCE); - const r = ctrl.run({ message: 'Hello', autoSend: false }); + const r = ctrl.run({ message: 'GENGEN', autoSend: false }); await p; const session = inlineChatSessionService.getSession(editor, editor.getModel()!.uri); assert.ok(session); - assert.deepStrictEqual(session.wholeRange.value, new Range(3, 1, 3, 3)); + assert.deepStrictEqual(session.wholeRange.value, new Range(3, 1, 3, 3)); // initial ctrl.acceptInput(); await ctrl.waitFor([State.MAKE_REQUEST, State.APPLY_RESPONSE, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); - assert.deepStrictEqual(session.wholeRange.value, new Range(4, 1, 4, 3)); + assert.deepStrictEqual(session.wholeRange.value, new Range(1, 1, 4, 3)); await ctrl.cancelSession(); await r; diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts index 0c6a58b4036..53b0cb87519 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts @@ -41,12 +41,8 @@ import { assertType } from 'vs/base/common/types'; import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; -import { DiffAlgorithmName, IEditorWorkerService, ILineChange } from 'vs/editor/common/services/editorWorker'; -import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; -import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; -import { LineRange } from 'vs/editor/common/core/lineRange'; -import { MovedText } from 'vs/editor/common/diff/linesDiffComputer'; -import { LineRangeMapping, DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; +import { TestWorkerService } from './testWorkerService'; suite('ReplyResponse', function () { @@ -86,69 +82,6 @@ suite('ReplyResponse', function () { }); }); -class TestWorkerService extends mock() { - - private readonly _worker = new EditorSimpleWorker(null!, null); - - constructor(@IModelService private readonly _modelService: IModelService) { - super(); - } - - override async computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise { - - const originalModel = this._modelService.getModel(original); - const modifiedModel = this._modelService.getModel(modified); - - assertType(originalModel); - assertType(modifiedModel); - - this._worker.acceptNewModel({ - url: originalModel.uri.toString(), - versionId: originalModel.getVersionId(), - lines: originalModel.getLinesContent(), - EOL: originalModel.getEOL(), - }); - - this._worker.acceptNewModel({ - url: modifiedModel.uri.toString(), - versionId: modifiedModel.getVersionId(), - lines: modifiedModel.getLinesContent(), - EOL: modifiedModel.getEOL(), - }); - - const result = await this._worker.computeDiff(originalModel.uri.toString(), modifiedModel.uri.toString(), options, algorithm); - if (!result) { - return result; - } - // Convert from space efficient JSON data to rich objects. - const diff: IDocumentDiff = { - identical: result.identical, - quitEarly: result.quitEarly, - changes: toLineRangeMappings(result.changes), - moves: result.moves.map(m => new MovedText( - new LineRangeMapping(new LineRange(m[0], m[1]), new LineRange(m[2], m[3])), - toLineRangeMappings(m[4]) - )) - }; - return diff; - - function toLineRangeMappings(changes: readonly ILineChange[]): readonly DetailedLineRangeMapping[] { - return changes.map( - (c) => new DetailedLineRangeMapping( - new LineRange(c[0], c[1]), - new LineRange(c[2], c[3]), - c[4]?.map( - (c) => new RangeMapping( - new Range(c[0], c[1], c[2], c[3]), - new Range(c[4], c[5], c[6], c[7]) - ) - ) - ) - ); - } - } -} - suite('InlineChatSession', function () { const store = new DisposableStore(); diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/testWorkerService.ts b/src/vs/workbench/contrib/inlineChat/test/browser/testWorkerService.ts new file mode 100644 index 00000000000..d9b64666384 --- /dev/null +++ b/src/vs/workbench/contrib/inlineChat/test/browser/testWorkerService.ts @@ -0,0 +1,84 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { URI } from 'vs/base/common/uri'; +import { mock } from 'vs/base/test/common/mock'; +import { Range } from 'vs/editor/common/core/range'; +import { IModelService } from 'vs/editor/common/services/model'; +import { assertType } from 'vs/base/common/types'; +import { DiffAlgorithmName, IEditorWorkerService, ILineChange } from 'vs/editor/common/services/editorWorker'; +import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; +import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; +import { LineRange } from 'vs/editor/common/core/lineRange'; +import { MovedText } from 'vs/editor/common/diff/linesDiffComputer'; +import { LineRangeMapping, DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; +import { TextEdit } from 'vs/editor/common/languages'; + + +export class TestWorkerService extends mock() { + + private readonly _worker = new EditorSimpleWorker(null!, null); + + constructor(@IModelService private readonly _modelService: IModelService) { + super(); + } + + override async computeMoreMinimalEdits(resource: URI, edits: TextEdit[] | null | undefined, pretty?: boolean | undefined): Promise { + return undefined; + } + + override async computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise { + + const originalModel = this._modelService.getModel(original); + const modifiedModel = this._modelService.getModel(modified); + + assertType(originalModel); + assertType(modifiedModel); + + this._worker.acceptNewModel({ + url: originalModel.uri.toString(), + versionId: originalModel.getVersionId(), + lines: originalModel.getLinesContent(), + EOL: originalModel.getEOL(), + }); + + this._worker.acceptNewModel({ + url: modifiedModel.uri.toString(), + versionId: modifiedModel.getVersionId(), + lines: modifiedModel.getLinesContent(), + EOL: modifiedModel.getEOL(), + }); + + const result = await this._worker.computeDiff(originalModel.uri.toString(), modifiedModel.uri.toString(), options, algorithm); + if (!result) { + return result; + } + // Convert from space efficient JSON data to rich objects. + const diff: IDocumentDiff = { + identical: result.identical, + quitEarly: result.quitEarly, + changes: toLineRangeMappings(result.changes), + moves: result.moves.map(m => new MovedText( + new LineRangeMapping(new LineRange(m[0], m[1]), new LineRange(m[2], m[3])), + toLineRangeMappings(m[4]) + )) + }; + return diff; + + function toLineRangeMappings(changes: readonly ILineChange[]): readonly DetailedLineRangeMapping[] { + return changes.map( + (c) => new DetailedLineRangeMapping( + new LineRange(c[0], c[1]), + new LineRange(c[2], c[3]), + c[4]?.map( + (c) => new RangeMapping( + new Range(c[0], c[1], c[2], c[3]), + new Range(c[4], c[5], c[6], c[7]) + ) + ) + ) + ); + } + } +} From 4e31c84e0fe43e0df34b39d70ba279f04240f720 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 22 Jan 2024 16:21:09 +0100 Subject: [PATCH 0508/1897] make sure textModel0 (and textModekN) has a unique uri (#203015) --- .../inlineChat/browser/inlineChatSessionServiceImpl.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index ab3d899aed6..5e211c6ac6d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -22,6 +22,7 @@ import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { ITextModel, IValidEditOperation } from 'vs/editor/common/model'; import { Schemas } from 'vs/base/common/network'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { generateUuid } from 'vs/base/common/uuid'; type SessionData = { editor: ICodeEditor; @@ -98,6 +99,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${provider.debugName}`); const store = new DisposableStore(); + const id = generateUuid(); const targetUri = textModel.uri; let textModelN: ITextModel; @@ -106,7 +108,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { textModelN = store.add(this._modelService.createModel( createTextBufferFactoryFromSnapshot(textModel.createSnapshot()), { languageId: textModel.getLanguageId(), onDidChange: Event.None }, - targetUri.with({ scheme: Schemas.inMemory, query: 'inline-chat-textModelN' }), true + targetUri.with({ scheme: Schemas.inMemory, query: new URLSearchParams({ id, 'inline-chat-textModelN': '' }).toString() }), true )); } else { // AI edits happen in the actual model, keep a reference but make no copy @@ -118,7 +120,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { const textModel0 = store.add(this._modelService.createModel( createTextBufferFactoryFromSnapshot(textModel.createSnapshot()), { languageId: textModel.getLanguageId(), onDidChange: Event.None }, - targetUri.with({ scheme: Schemas.inMemory, query: 'inline-chat-textModel0' }), true + targetUri.with({ scheme: Schemas.inMemory, query: new URLSearchParams({ id, 'inline-chat-textModel0': '' }).toString() }), true )); let wholeRange = options.wholeRange; From e5a6595f135c2fb27ee1d83dbfb6d4bc2115b55f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 22 Jan 2024 16:25:01 +0100 Subject: [PATCH 0509/1897] fix https://github.com/microsoft/vscode/issues/203008 (#203011) --- .../inlineChat/electron-sandbox/inlineChatQuickVoice.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index 1e33dc7e5cd..948093ae666 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -24,6 +24,7 @@ import { AbstractInlineChatAction } from 'vs/workbench/contrib/inlineChat/browse import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; const CTX_QUICK_CHAT_IN_PROGRESS = new RawContextKey('inlineChat.quickChatInProgress', false); @@ -34,7 +35,7 @@ export class StartAction extends EditorAction2 { id: 'inlineChat.quickVoice.start', title: localize2('start', "Start Inline Voice Chat"), category: AbstractInlineChatAction.category, - precondition: ContextKeyExpr.and(HasSpeechProvider, CTX_QUICK_CHAT_IN_PROGRESS.toNegated()), + precondition: ContextKeyExpr.and(HasSpeechProvider, CTX_QUICK_CHAT_IN_PROGRESS.toNegated(), EditorContextKeys.focus), f1: true, keybinding: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyI), From 40ecea93e9ec15368df68d1bf795959a2f72f598 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 22 Jan 2024 16:27:42 +0100 Subject: [PATCH 0510/1897] debt - prevent simple code editors in results (#203019) Co-authored-by: Johannes Rieken --- .../workbench/contrib/files/browser/fileCommands.ts | 2 +- .../services/dialogs/browser/fileDialogService.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 434d8623aca..6fa04545a0e 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -414,7 +414,7 @@ async function saveSelectedEditors(accessor: ServicesAccessor, options?: ISaveEd // find it in our text file models. Currently, only textual editors // support embedded editors. const focusedCodeEditor = codeEditorService.getFocusedCodeEditor(); - if (focusedCodeEditor instanceof EmbeddedCodeEditorWidget) { + if (focusedCodeEditor instanceof EmbeddedCodeEditorWidget && !focusedCodeEditor.isSimpleWidget) { const resource = focusedCodeEditor.getModel()?.uri; // Check that the resource of the model was not saved already diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index aa590e8a240..c55daa6fdc3 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -19,6 +19,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { extractFileListData } from 'vs/platform/dnd/browser/dnd'; import { Iterable } from 'vs/base/common/iterator'; import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; export class FileDialogService extends AbstractFileDialogService implements IFileDialogService { @@ -217,10 +218,13 @@ export class FileDialogService extends AbstractFileDialogService implements IFil // When saving, try to just download the contents // of the active text editor if any as a workaround if (context === 'save') { - const activeTextModel = this.codeEditorService.getActiveCodeEditor()?.getModel(); - if (activeTextModel) { - triggerDownload(VSBuffer.fromString(activeTextModel.getValue()).buffer, basename(activeTextModel.uri)); - return; + const activeCodeEditor = this.codeEditorService.getActiveCodeEditor(); + if (!(activeCodeEditor instanceof EmbeddedCodeEditorWidget)) { + const activeTextModel = activeCodeEditor?.getModel(); + if (activeTextModel) { + triggerDownload(VSBuffer.fromString(activeTextModel.getValue()).buffer, basename(activeTextModel.uri)); + return; + } } } From 13196099b60ac204be38a9b13ff04264dbfb9c30 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 22 Jan 2024 16:31:42 +0100 Subject: [PATCH 0511/1897] polish pre-release UX (#203021) * improve pre-releas UX * rename labels --------- Co-authored-by: Johannes Rieken --- .../extensions/browser/extensionEditor.ts | 26 ++--------------- .../browser/extensions.contribution.ts | 29 ++++++++++++++++--- .../extensions/browser/extensionsActions.ts | 7 +++-- .../extensions/browser/extensionsWidgets.ts | 23 ++++++--------- .../browser/extensionsWorkbenchService.ts | 3 +- 5 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index a977ee1ed2f..8b49ea2903f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -78,7 +78,7 @@ import { WebInstallAction, TogglePreReleaseExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; -import { errorIcon, infoIcon, preReleaseIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; +import { errorIcon, infoIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { Delegate } from 'vs/workbench/contrib/extensions/browser/extensionsList'; import { ExtensionData, ExtensionsGridView, ExtensionsTree, getExtensions } from 'vs/workbench/contrib/extensions/browser/extensionsViewer'; import { ExtensionRecommendationWidget, ExtensionStatusWidget, ExtensionWidget, InstallCountWidget, RatingsWidget, RemoteBadgeWidget, SponsorWidget, VerifiedPublisherWidget, onClick } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; @@ -203,22 +203,7 @@ class VersionWidget extends ExtensionWithDifferentGalleryVersionWidget { if (!this.extension || !semver.valid(this.extension.version)) { return; } - this.element.textContent = `v${this.gallery?.version ?? this.extension.version}`; - } -} - -class PreReleaseTextWidget extends ExtensionWithDifferentGalleryVersionWidget { - private readonly element: HTMLElement; - constructor(container: HTMLElement) { - super(); - this.element = append(container, $('span.pre-release')); - append(this.element, $('span' + ThemeIcon.asCSSSelector(preReleaseIcon))); - const textElement = append(this.element, $('span.pre-release-text')); - textElement.textContent = localize('preRelease', "Pre-Release"); - this.render(); - } - render(): void { - this.element.style.display = this.extension?.isPreReleaseVersion ? 'inherit' : 'none'; + this.element.textContent = `v${this.gallery?.version ?? this.extension.version}${this.extension.isPreReleaseVersion ? ' (pre-release)' : ''}`; } } @@ -295,8 +280,6 @@ export class ExtensionEditor extends EditorPane { const name = append(title, $('span.name.clickable', { title: localize('name', "Extension name"), role: 'heading', tabIndex: 0 })); const versionWidget = new VersionWidget(title); - const preReleaseWidget = new PreReleaseTextWidget(title); - const preview = append(title, $('span.preview', { title: localize('preview', "Preview") })); preview.textContent = localize('preview', "Preview"); @@ -321,7 +304,6 @@ export class ExtensionEditor extends EditorPane { const widgets: ExtensionWidget[] = [ remoteBadge, versionWidget, - preReleaseWidget, verifiedPublisherWidget, installCountWidget, ratingsWidget, @@ -374,9 +356,6 @@ export class ExtensionEditor extends EditorPane { if (action instanceof ToggleAutoUpdateForExtensionAction) { return new CheckboxActionViewItem(undefined, action, { icon: true, label: true, checkboxStyles: defaultCheckboxStyles }); } - if (action instanceof TogglePreReleaseExtensionAction) { - return new CheckboxActionViewItem(undefined, action, { icon: true, label: true, checkboxStyles: defaultCheckboxStyles }); - } return undefined; }, focusOnlyEnabledItems: true @@ -448,7 +427,6 @@ export class ExtensionEditor extends EditorPane { }, set gallery(gallery: IGalleryExtension | null) { versionWidget.gallery = gallery; - preReleaseWidget.gallery = gallery; }, set manifest(manifest: IExtensionManifest | null) { installAction.manifest = manifest; diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 412cbbc3b5a..38a576546dc 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -1372,16 +1372,37 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi }); this.registerExtensionAction({ - id: TogglePreReleaseExtensionAction.ID, - title: TogglePreReleaseExtensionAction.LABEL, + id: 'workbench.extensions.action.enablePreRlease', + title: localize('enablePreRleaseLabel', "Switch to Pre-Release Version"), category: ExtensionsLocalizedLabel, menu: { id: MenuId.ExtensionContext, group: INSTALL_ACTIONS_GROUP, order: 2, - when: ContextKeyExpr.and(CONTEXT_HAS_GALLERY, ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) + when: ContextKeyExpr.and(CONTEXT_HAS_GALLERY, ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('installedExtensionIsOptedToPreRelease'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) + }, + run: async (accessor: ServicesAccessor, id: string) => { + const instantiationService = accessor.get(IInstantiationService); + const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const extension = extensionWorkbenchService.local.find(e => areSameExtensions(e.identifier, { id })); + if (extension) { + const action = instantiationService.createInstance(TogglePreReleaseExtensionAction); + action.extension = extension; + return action.run(); + } + } + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.disablPreRlease', + title: localize('disablePreRleaseLabel', "Switch to Release Version"), + category: ExtensionsLocalizedLabel, + menu: { + id: MenuId.ExtensionContext, + group: INSTALL_ACTIONS_GROUP, + order: 2, + when: ContextKeyExpr.and(CONTEXT_HAS_GALLERY, ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.has('installedExtensionIsOptedToPreRelease'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) }, - toggled: ContextKeyExpr.has('installedExtensionIsOptedToPreRelease'), run: async (accessor: ServicesAccessor, id: string) => { const instantiationService = accessor.get(IInstantiationService); const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index fbd480a719f..8b4d951ed4e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1272,7 +1272,7 @@ export class TogglePreReleaseExtensionAction extends ExtensionAction { static readonly ID = 'workbench.extensions.action.togglePreRlease'; static readonly LABEL = localize('togglePreRleaseLabel', "Pre-Release"); - private static readonly EnabledClass = `${ExtensionAction.EXTENSION_ACTION_CLASS} pre-release`; + private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} pre-release`; private static readonly DisabledClass = `${TogglePreReleaseExtensionAction.EnabledClass} hide`; constructor( @@ -1302,7 +1302,10 @@ export class TogglePreReleaseExtensionAction extends ExtensionAction { } this.enabled = true; this.class = TogglePreReleaseExtensionAction.EnabledClass; - this.checked = this.extension.preRelease; + this.label = this.extension.preRelease ? localize('togglePreRleaseDisableLabel', "Switch to Release Version") : localize('togglePreRleaseEnableLabel', "Switch to Pre-Release Version"); + this.tooltip = this.extension.preRelease + ? localize('togglePreRleaseDisableTooltip1', "This will switch and enable updates to release versions") + : localize('togglePreRleaseEnableTooltip', "This will switch to pre-release version and enable updates to latest version always"); } override async run(): Promise { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 8b95c604371..64639cd23de 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -320,15 +320,11 @@ export class PreReleaseBookmarkWidget extends ExtensionWidget { render(): void { this.clear(); - if (!this.extension) { - return; + if (this.extension?.state === ExtensionState.Installed ? this.extension.preRelease : this.extension?.hasPreReleaseVersion) { + this.element = append(this.parent, $('div.extension-bookmark')); + const preRelease = append(this.element, $('.pre-release')); + append(preRelease, $('span' + ThemeIcon.asCSSSelector(preReleaseIcon))); } - if (!this.extension.isPreReleaseVersion) { - return; - } - this.element = append(this.parent, $('div.extension-bookmark')); - const preRelease = append(this.element, $('.pre-release')); - append(preRelease, $('span' + ThemeIcon.asCSSSelector(preReleaseIcon))); } } @@ -553,11 +549,7 @@ export class ExtensionHoverWidget extends ExtensionWidget { markdown.appendMarkdown(`**${this.extension.displayName}**`); if (semver.valid(this.extension.version)) { - markdown.appendMarkdown(` ** _v${this.extension.version}_** `); - } - if (this.extension.state === ExtensionState.Installed ? this.extension.local?.isPreReleaseVersion : this.extension.gallery?.properties.isPreReleaseVersion) { - const extensionPreReleaseIcon = this.themeService.getColorTheme().getColor(extensionPreReleaseIconColor); - markdown.appendMarkdown(`** **  $(${preReleaseIcon.id}) ${localize('pre-release-label', "Pre-Release")} `); + markdown.appendMarkdown(` ** _v${this.extension.version}${(this.extension.isPreReleaseVersion ? ' (pre-release)' : '')}_** `); } markdown.appendText(`\n`); @@ -695,7 +687,10 @@ export class ExtensionHoverWidget extends ExtensionWidget { if (extension.isBuiltin) { return undefined; } - if (extension.local?.isPreReleaseVersion || extension.gallery?.properties.isPreReleaseVersion) { + if (extension.isPreReleaseVersion) { + return undefined; + } + if (extension.preRelease) { return undefined; } const preReleaseVersionLink = `[${localize('Show prerelease version', "Pre-Release version")}](${URI.parse(`command:workbench.extensions.action.showPreReleaseVersion?${encodeURIComponent(JSON.stringify([extension.identifier.id]))}`)})`; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 62731c2520a..bb3f66c7175 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -656,7 +656,8 @@ class Extensions extends Disposable { private async onDidUpdateExtensionMetadata(local: ILocalExtension): Promise { const extension = this.installed.find(e => areSameExtensions(e.identifier, local.identifier)); if (extension?.local) { - const hasChanged = extension.local.pinned !== local.pinned; + const hasChanged = extension.local.pinned !== local.pinned + || extension.local.preRelease !== local.preRelease; extension.local = local; if (hasChanged) { this._onChange.fire({ extension }); From 9e96f6f7398582eb19a2124bda47e04b8df6a002 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 22 Jan 2024 15:32:05 +0100 Subject: [PATCH 0512/1897] multiFileDiffEditorOriginalUri -> multiDiffEditorOriginalUri --- src/vs/workbench/api/browser/mainThreadSCM.ts | 10 ++--- .../browser/scmMultiDiffSourceResolver.ts | 38 +++++++++++-------- src/vs/workbench/contrib/scm/common/scm.ts | 4 +- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 0ae7ac97e96..1300c25f4f0 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -108,8 +108,8 @@ class MainThreadSCMResource implements ISCMResource { readonly decorations: ISCMResourceDecorations, readonly contextValue: string | undefined, readonly command: Command | undefined, - readonly multiFileDiffEditorOriginalUri: URI | undefined, - readonly multiFileDiffEditorModifiedUri: URI | undefined, + readonly multiDiffEditorOriginalUri: URI | undefined, + readonly multiDiffEditorModifiedUri: URI | undefined, ) { } open(preserveFocus: boolean): Promise { @@ -336,7 +336,7 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { for (const [start, deleteCount, rawResources] of groupSlices) { const resources = rawResources.map(rawResource => { - const [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue, command, multiFileDiffEditorOriginalUri, multiFileDiffEditorModifiedUri] = rawResource; + const [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue, command, multiDiffEditorOriginalUri, multiDiffEditorModifiedUri] = rawResource; const [light, dark] = icons; const icon = ThemeIcon.isThemeIcon(light) ? light : URI.revive(light); @@ -360,8 +360,8 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { decorations, contextValue || undefined, command, - URI.revive(multiFileDiffEditorOriginalUri), - URI.revive(multiFileDiffEditorModifiedUri), + URI.revive(multiDiffEditorOriginalUri), + URI.revive(multiDiffEditorModifiedUri), ); }); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts index 1c1a4c8fc56..75d8776a785 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts @@ -6,12 +6,13 @@ import { Codicon } from 'vs/base/common/codicons'; import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { IObservable, observableFromEvent } from 'vs/base/common/observable'; import { URI } from 'vs/base/common/uri'; import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, ContextKeyValue } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IMultiDiffSourceResolver, IMultiDiffSourceResolverService, IResolvedMultiDiffSource } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService'; +import { IMultiDiffSourceResolver, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService'; import { ISCMResourceGroup, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -77,19 +78,26 @@ export class ScmMultiDiffSourceResolver implements IMultiDiffSourceResolver { } ); - return { - get resources() { - return group.resources.map(e => ({ - original: e.multiFileDiffEditorOriginalUri, - modified: e.multiFileDiffEditorModifiedUri - })); - }, - onDidChange: e => group.onDidChangeResources(() => e()), - contextKeys: { - scmResourceGroup: groupId, - scmProvider: repository.provider.contextValue, - }, - }; + const resources = observableFromEvent(group.onDidChangeResources, () => group.resources.map(e => ({ + original: e.multiDiffEditorOriginalUri, + modified: e.multiDiffEditorModifiedUri + }))); + + return new ScmResolvedMultiDiffSource(resources, { + scmResourceGroup: groupId, + scmProvider: repository.provider.contextValue, + }); + } +} + +class ScmResolvedMultiDiffSource implements IResolvedMultiDiffSource { + get resources(): readonly MultiDiffEditorItem[] { return this._resources.get(); } + public readonly onDidChange = Event.fromObservableLight(this._resources); + + constructor( + private readonly _resources: IObservable, + public readonly contextKeys: Record | undefined, + ) { } } diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 1ff1a8c59fd..95bb756359d 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -39,8 +39,8 @@ export interface ISCMResource { readonly decorations: ISCMResourceDecorations; readonly contextValue: string | undefined; readonly command: Command | undefined; - readonly multiFileDiffEditorOriginalUri: URI | undefined; - readonly multiFileDiffEditorModifiedUri: URI | undefined; + readonly multiDiffEditorOriginalUri: URI | undefined; + readonly multiDiffEditorModifiedUri: URI | undefined; open(preserveFocus: boolean): Promise; } From db62e3b46c829fb15349ad6c26468b077f1ca4dc Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 22 Jan 2024 14:54:02 +0100 Subject: [PATCH 0513/1897] Fixes multi file diff editor bug that deleted files would be shown as added files. --- extensions/git/src/repository.ts | 59 ++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index b0d6ef6235a..698ab8affd5 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -154,19 +154,19 @@ export class Resource implements SourceControlResourceState { } get leftUri(): Uri | undefined { - return this.resources[0]; + return this.resources.left; } get rightUri(): Uri | undefined { - return this.resources[1]; + return this.resources.right; } get multiDiffEditorOriginalUri(): Uri | undefined { - return this.leftUri; + return this.resources.original; } get multiFileDiffEditorModifiedUri(): Uri | undefined { - return this.rightUri; + return this.resources.modified; } @memoize @@ -175,7 +175,7 @@ export class Resource implements SourceControlResourceState { } @memoize - private get resources(): [Uri | undefined, Uri | undefined] { + private get resources(): { left: Uri | undefined; right: Uri | undefined; original: Uri | undefined; modified: Uri | undefined } { return this._commandResolver.getResources(this); } @@ -534,53 +534,63 @@ class ResourceCommandResolver { } } - getResources(resource: Resource): [Uri | undefined, Uri | undefined] { + getResources(resource: Resource): { left: Uri | undefined; right: Uri | undefined; original: Uri | undefined; modified: Uri | undefined } { for (const submodule of this.repository.submodules) { if (path.join(this.repository.root, submodule.path) === resource.resourceUri.fsPath) { - return [undefined, toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: this.repository.root })]; + const original = undefined; + const modified = toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: this.repository.root }); + return { left: original, right: modified, original, modified }; } } - return [this.getLeftResource(resource), this.getRightResource(resource)]; + const left = this.getLeftResource(resource); + const right = this.getRightResource(resource); + + return { + left: left.original ?? left.modified, + right: right.original ?? right.modified, + original: left.original ?? right.original, + modified: left.modified ?? right.modified, + }; } - private getLeftResource(resource: Resource): Uri | undefined { + private getLeftResource(resource: Resource): ModifiedOrOriginal { switch (resource.type) { case Status.INDEX_MODIFIED: case Status.INDEX_RENAMED: case Status.INDEX_ADDED: case Status.INTENT_TO_RENAME: case Status.TYPE_CHANGED: - return toGitUri(resource.original, 'HEAD'); + return { original: toGitUri(resource.original, 'HEAD') }; case Status.MODIFIED: case Status.UNTRACKED: - return toGitUri(resource.resourceUri, '~'); + return { original: toGitUri(resource.resourceUri, '~') }; case Status.DELETED_BY_US: case Status.DELETED_BY_THEM: - return toGitUri(resource.resourceUri, '~1'); + return { original: toGitUri(resource.resourceUri, '~1') }; } - return undefined; + return {}; } - private getRightResource(resource: Resource): Uri | undefined { + private getRightResource(resource: Resource): ModifiedOrOriginal { switch (resource.type) { case Status.INDEX_MODIFIED: case Status.INDEX_ADDED: case Status.INDEX_COPIED: case Status.INDEX_RENAMED: - return toGitUri(resource.resourceUri, ''); + return { modified: toGitUri(resource.resourceUri, '') }; case Status.INDEX_DELETED: case Status.DELETED: - return toGitUri(resource.resourceUri, 'HEAD'); + return { original: toGitUri(resource.resourceUri, 'HEAD') }; case Status.DELETED_BY_US: - return toGitUri(resource.resourceUri, '~3'); + return { original: toGitUri(resource.resourceUri, '~3') }; case Status.DELETED_BY_THEM: - return toGitUri(resource.resourceUri, '~2'); + return { original: toGitUri(resource.resourceUri, '~2') }; case Status.MODIFIED: case Status.UNTRACKED: @@ -592,17 +602,17 @@ class ResourceCommandResolver { const [indexStatus] = this.repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); if (indexStatus && indexStatus.renameResourceUri) { - return indexStatus.renameResourceUri; + return { modified: indexStatus.renameResourceUri }; } - return resource.resourceUri; + return { modified: resource.resourceUri }; } case Status.BOTH_ADDED: case Status.BOTH_MODIFIED: - return resource.resourceUri; + return { modified: resource.resourceUri }; } - return undefined; + return {}; } private getTitle(resource: Resource): string { @@ -645,6 +655,11 @@ class ResourceCommandResolver { } } +interface ModifiedOrOriginal { + modified?: Uri | undefined; + original?: Uri | undefined; +} + interface BranchProtectionMatcher { include?: picomatch.Matcher; exclude?: picomatch.Matcher; From 615e7eccca5e6f26242f357531402b452060453c Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 22 Jan 2024 16:38:23 +0100 Subject: [PATCH 0514/1897] Polish custom titlebar visibility behaviour (#203017) * polish custom titlebar visibility behaviour * cleanup --------- Co-authored-by: Johannes Rieken --- .../browser/actions/layoutActions.ts | 11 +- src/vs/workbench/browser/layout.ts | 19 +++- .../browser/parts/sidebar/sidebarPart.ts | 3 +- .../browser/parts/titlebar/titlebarActions.ts | 107 ++++++++++++++++-- .../browser/parts/titlebar/titlebarPart.ts | 13 +-- .../browser/workbench.contribution.ts | 8 +- 6 files changed, 124 insertions(+), 37 deletions(-) diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index d6e446d04f1..08e0fb78357 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -22,7 +22,7 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { ToggleAuxiliaryBarAction } from 'vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions'; import { TogglePanelAction } from 'vs/workbench/browser/parts/panel/panelActions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { AuxiliaryBarVisibleContext, PanelAlignmentContext, PanelVisibleContext, SideBarVisibleContext, FocusedViewContext, InEditorZenModeContext, IsCenteredLayoutContext, MainEditorAreaVisibleContext, IsMainWindowFullscreenContext, PanelPositionContext, IsAuxiliaryWindowFocusedContext } from 'vs/workbench/common/contextkeys'; +import { AuxiliaryBarVisibleContext, PanelAlignmentContext, PanelVisibleContext, SideBarVisibleContext, FocusedViewContext, InEditorZenModeContext, IsCenteredLayoutContext, MainEditorAreaVisibleContext, IsMainWindowFullscreenContext, PanelPositionContext, IsAuxiliaryWindowFocusedContext, TitleBarStyleContext } from 'vs/workbench/common/contextkeys'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -30,7 +30,7 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { ICommandActionTitle } from 'vs/platform/action/common/action'; import { mainWindow } from 'vs/base/browser/window'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { TitleBarSetting, TitlebarStyle } from 'vs/platform/window/common/window'; +import { TitlebarStyle } from 'vs/platform/window/common/window'; // Register Icons const menubarIcon = registerIcon('menuBar', Codicon.layoutMenubar, localize('menuBarIcon', "Represents the menu bar")); @@ -587,10 +587,7 @@ export class EditorActionsTitleBarAction extends Action2 { original: 'Move Editor Actions to Title Bar' }, category: Categories.View, - precondition: ContextKeyExpr.and( - ContextKeyExpr.equals(`config.${LayoutSettings.EDITOR_ACTIONS_LOCATION}`, EditorActionsLocation.TITLEBAR).negate(), - ContextKeyExpr.equals(`config.${TitleBarSetting.TITLE_BAR_STYLE}`, TitlebarStyle.NATIVE).negate(), - ), + precondition: ContextKeyExpr.equals(`config.${LayoutSettings.EDITOR_ACTIONS_LOCATION}`, EditorActionsLocation.TITLEBAR).negate(), f1: true }); } @@ -803,7 +800,7 @@ if (isWindows || isLinux || isWeb) { title: localize('miMenuBarNoMnemonic', "Menu Bar"), toggled: ContextKeyExpr.and(IsMacNativeContext.toNegated(), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'hidden'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'toggle'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'compact')) }, - when: ContextKeyExpr.and(IsAuxiliaryWindowFocusedContext.toNegated(), ContextKeyExpr.notEquals(TitleBarSetting.TITLE_BAR_STYLE, TitlebarStyle.NATIVE), IsMainWindowFullscreenContext.negate()), + when: ContextKeyExpr.and(IsAuxiliaryWindowFocusedContext.toNegated(), ContextKeyExpr.notEquals(TitleBarStyleContext.key, TitlebarStyle.NATIVE), IsMainWindowFullscreenContext.negate()), group: '2_config', order: 0 }); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 6eb24dbdeca..e8322cbf07d 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -354,6 +354,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi TitleBarSetting.TITLE_BAR_STYLE, TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, ].some(setting => e.affectsConfiguration(setting))) { + // Show Custom TitleBar if actions moved to the titlebar + const activityBarMovedToTop = e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION) && this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP; + const editorActionsMovedToTitlebar = e.affectsConfiguration(LayoutSettings.EDITOR_ACTIONS_LOCATION) && this.configurationService.getValue(LayoutSettings.EDITOR_ACTIONS_LOCATION) === EditorActionsLocation.TITLEBAR; + if (activityBarMovedToTop || editorActionsMovedToTitlebar) { + if (this.configurationService.getValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY) === CustomTitleBarVisibility.NEVER) { + this.configurationService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.AUTO); + } + } + this.doUpdateLayoutConfiguration(); } })); @@ -1237,7 +1246,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return false; } - if (!this.isTitleBarEmpty(nativeTitleBarEnabled)) { + if (!this.isTitleBarEmpty()) { return true; } @@ -1277,7 +1286,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } - private isTitleBarEmpty(nativeTitleBarEnabled: boolean): boolean { + private isTitleBarEmpty(): boolean { // with the command center enabled, we should always show if (this.configurationService.getValue(LayoutSettings.COMMAND_CENTER)) { return false; @@ -1295,8 +1304,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return false; } - // Layout don't show with native title bar - if (!nativeTitleBarEnabled && this.configurationService.getValue(LayoutSettings.LAYOUT_ACTIONS)) { + // with the layout actions on top, we should always show + if (this.configurationService.getValue(LayoutSettings.LAYOUT_ACTIONS)) { return false; } @@ -1742,7 +1751,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private canActivityBarBeHidden(): boolean { - return !(this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP && !this.isVisible(Parts.TITLEBAR_PART, mainWindow)); + return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP; } private setBannerHidden(hidden: boolean): void { diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index abadf927fd9..ec276e02f5a 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -31,7 +31,6 @@ import { Action2, IMenuService, registerAction2 } from 'vs/platform/actions/comm import { Separator } from 'vs/base/common/actions'; import { ToggleActivityBarVisibilityActionId } from 'vs/workbench/browser/actions/layoutActions'; import { localize } from 'vs/nls'; -import { mainWindow } from 'vs/base/browser/window'; export class SidebarPart extends AbstractPaneCompositePart { @@ -195,7 +194,7 @@ export class SidebarPart extends AbstractPaneCompositePart { } protected shouldShowCompositeBar(): boolean { - return this.layoutService.isVisible(Parts.TITLEBAR_PART, mainWindow) && this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP; + return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP; } private shouldShowActivityBar(): boolean { diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index c010eac936a..f800a48b72d 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -9,20 +9,18 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ACCOUNTS_ACTIVITY_ID, GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { IAction } from 'vs/base/common/actions'; -import { IsAuxiliaryWindowFocusedContext, IsMainWindowFullscreenContext } from 'vs/workbench/common/contextkeys'; +import { IsAuxiliaryWindowFocusedContext, IsMainWindowFullscreenContext, TitleBarStyleContext, TitleBarVisibleContext } from 'vs/workbench/common/contextkeys'; import { CustomTitleBarVisibility, TitleBarSetting, TitlebarStyle } from 'vs/platform/window/common/window'; // --- Context Menu Actions --- // class ToggleConfigAction extends Action2 { - constructor(private readonly section: string, title: string, order: number, mainWindowOnly: boolean, showInCustomTitleBarWhenNativeTitle: boolean) { - let when = mainWindowOnly ? IsAuxiliaryWindowFocusedContext.toNegated() : ContextKeyExpr.true(); - when = showInCustomTitleBarWhenNativeTitle ? when : ContextKeyExpr.and(when, ContextKeyExpr.equals(`config.${TitleBarSetting.TITLE_BAR_STYLE}`, TitlebarStyle.NATIVE).negate())!; - + constructor(private readonly section: string, title: string, order: number, mainWindowOnly: boolean) { + const when = mainWindowOnly ? IsAuxiliaryWindowFocusedContext.toNegated() : ContextKeyExpr.true(); super({ id: `toggle.${section}`, title, @@ -53,13 +51,13 @@ class ToggleConfigAction extends Action2 { registerAction2(class ToggleCommandCenter extends ToggleConfigAction { constructor() { - super(LayoutSettings.COMMAND_CENTER, localize('toggle.commandCenter', 'Command Center'), 1, false, true); + super(LayoutSettings.COMMAND_CENTER, localize('toggle.commandCenter', 'Command Center'), 1, false); } }); registerAction2(class ToggleLayoutControl extends ToggleConfigAction { constructor() { - super('workbench.layoutControl.enabled', localize('toggle.layout', 'Layout Controls'), 2, true, false); + super('workbench.layoutControl.enabled', localize('toggle.layout', 'Layout Controls'), 2, true); } }); @@ -69,8 +67,8 @@ registerAction2(class ToggleCustomTitleBar extends Action2 { id: `toggle.${TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY}`, title: localize('toggle.hideCustomTitleBar', 'Hide Custom Title Bar'), menu: [ - { id: MenuId.TitleBarContext, order: 0, when: ContextKeyExpr.or(ContextKeyExpr.equals(`config.${TitleBarSetting.TITLE_BAR_STYLE}`, TitlebarStyle.NATIVE), ContextKeyExpr.equals(`config.window.nativeTabs`, ContextKeyExpr.true())), group: '3_toggle' }, - { id: MenuId.TitleBarTitleContext, order: 0, when: ContextKeyExpr.or(ContextKeyExpr.equals(`config.${TitleBarSetting.TITLE_BAR_STYLE}`, TitlebarStyle.NATIVE), ContextKeyExpr.equals(`config.window.nativeTabs`, ContextKeyExpr.true())), group: '3_toggle' }, + { id: MenuId.TitleBarContext, order: 0, when: ContextKeyExpr.equals(TitleBarStyleContext.key, TitlebarStyle.NATIVE), group: '3_toggle' }, + { id: MenuId.TitleBarTitleContext, order: 0, when: ContextKeyExpr.equals(TitleBarStyleContext.key, TitlebarStyle.NATIVE), group: '3_toggle' }, ] }); } @@ -99,6 +97,95 @@ registerAction2(class ToggleCustomTitleBarWindowed extends Action2 { } }); + +class ToggleCustomTitleBar extends Action2 { + + constructor() { + super({ + id: `toggle.toggleCustomTitleBar`, + title: localize('toggle.customTitleBar', 'Custom Title Bar'), + toggled: TitleBarVisibleContext, + menu: [ + { id: MenuId.MenubarAppearanceMenu, order: 6, when: ContextKeyExpr.or(ContextKeyExpr.equals(TitleBarStyleContext.key, TitlebarStyle.NATIVE), IsMainWindowFullscreenContext), group: '2_workbench_layout' }, + ] + }); + } + + run(accessor: ServicesAccessor, ...args: any[]): void { + const configService = accessor.get(IConfigurationService); + const contextKeyService = accessor.get(IContextKeyService); + const titleBarVisibility = configService.getValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY); + switch (titleBarVisibility) { + case CustomTitleBarVisibility.NEVER: + configService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.AUTO); + break; + case CustomTitleBarVisibility.WINDOWED: { + const isFullScreen = IsMainWindowFullscreenContext.evaluate(contextKeyService.getContext(null)); + if (isFullScreen) { + configService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.AUTO); + } else { + configService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.NEVER); + } + break; + } + case CustomTitleBarVisibility.AUTO: + default: + configService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.NEVER); + break; + } + } +} +registerAction2(ToggleCustomTitleBar); + +registerAction2(class ShowCustomTitleBar extends Action2 { + constructor() { + super({ + id: `showCustomTitleBar`, + title: { value: localize('showCustomTitleBar', 'Show Custom Title Bar'), original: 'Show Custom Title Bar' }, + precondition: TitleBarVisibleContext.negate(), + f1: true + }); + } + + run(accessor: ServicesAccessor, ...args: any[]): void { + const configService = accessor.get(IConfigurationService); + configService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.AUTO); + } +}); + + +registerAction2(class HideCustomTitleBar extends Action2 { + constructor() { + super({ + id: `hideCustomTitleBar`, + title: { value: localize('hideCustomTitleBar', 'Hide Custom Title Bar'), original: 'Hide Custom Title Bar' }, + precondition: TitleBarVisibleContext, + f1: true + }); + } + + run(accessor: ServicesAccessor, ...args: any[]): void { + const configService = accessor.get(IConfigurationService); + configService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.NEVER); + } +}); + +registerAction2(class HideCustomTitleBar extends Action2 { + constructor() { + super({ + id: `hideCustomTitleBarInFullScreen`, + title: { value: localize('hideCustomTitleBarInFullScreen', 'Hide Custom Title Bar In Full Screen'), original: 'Hide Custom Title Bar In Full Screen' }, + precondition: ContextKeyExpr.and(TitleBarVisibleContext, IsMainWindowFullscreenContext), + f1: true + }); + } + + run(accessor: ServicesAccessor, ...args: any[]): void { + const configService = accessor.get(IConfigurationService); + configService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.WINDOWED); + } +}); + registerAction2(class ToggleEditorActions extends Action2 { static readonly settingsID = `workbench.editor.editorActionsLocation`; constructor() { diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 13549aec7d3..7e481cf928d 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -487,15 +487,10 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { // Text Title if (!this.isCommandCenterVisible) { - // Don't show title in custom titlebar when native title is shown - if (!hasNativeTitlebar(this.configurationService, this.titleBarStyle)) { - + this.title.innerText = this.windowTitle.value; + this.titleDisposables.add(this.windowTitle.onDidChange(() => { this.title.innerText = this.windowTitle.value; - this.titleDisposables.add(this.windowTitle.onDidChange(() => { - this.title.innerText = this.windowTitle.value; - })); - - } + })); } // Menu Title @@ -700,7 +695,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } private get layoutControlEnabled(): boolean { - return !this.isAuxiliary && this.configurationService.getValue(LayoutSettings.LAYOUT_ACTIONS) !== false && !hasNativeTitlebar(this.configurationService, this.titleBarStyle); + return !this.isAuxiliary && this.configurationService.getValue(LayoutSettings.LAYOUT_ACTIONS) !== false; } protected get isCommandCenterVisible() { diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index bca80d33bb9..43b12a63bab 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -54,7 +54,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'enum': [EditorActionsLocation.DEFAULT, EditorActionsLocation.TITLEBAR, EditorActionsLocation.HIDDEN], 'enumDescriptions': [ localize({ comment: ['{0} will be a setting name rendered as a link'], key: 'workbench.editor.editorActionsLocation.default' }, "Show editor actions in the window title bar when {0} is set to {1}. Otherwise, editor actions are shown in the editor tab bar.", '`#workbench.editor.showTabs#`', '`none`'), - localize({ comment: ['{0} will be a setting name rendered as a link'], key: 'workbench.editor.editorActionsLocation.titleBar' }, "Show editor actions in the window title bar. If {0} is set to {1}, editor actions are hidden.", '`#window.titleBarStyle#`', '`native`'), + localize({ comment: ['{0} will be a setting name rendered as a link'], key: 'workbench.editor.editorActionsLocation.titleBar' }, "Show editor actions in the window title bar. If {0} is set to {1}, editor actions are hidden.", '`#window.customTitleBarVisibility#`', '`never`'), localize('workbench.editor.editorActionsLocation.hidden', "Editor actions are not shown."), ], 'markdownDescription': localize('editorActionsLocation', "Controls where the editor actions are shown."), @@ -499,7 +499,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'type': 'string', 'enum': ['side', 'top', 'hidden'], 'default': 'side', - 'markdownDescription': localize({ comment: ['This is the description for a setting'], key: 'activityBarLocation' }, "Controls the location of the Activity Bar. It can either show to the `side` or `top` (requires {0} set to {1}) of the Primary Side Bar or `hidden`.", '`#window.titleBarStyle#`', '`custom`'), + 'markdownDescription': localize({ comment: ['This is the description for a setting'], key: 'activityBarLocation' }, "Controls the location of the Activity Bar. It can either show to the `side` or `top` of the Primary Side Bar or `hidden`."), 'enumDescriptions': [ localize('workbench.activityBar.location.side', "Show the Activity Bar to the side of the Primary Side Bar."), localize('workbench.activityBar.location.top', "Show the Activity Bar on top of the Primary Side Bar."), @@ -572,7 +572,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'default': true, 'markdownDescription': isWeb ? localize('layoutControlEnabledWeb', "Controls whether the layout control in the title bar is shown.") : - localize({ key: 'layoutControlEnabled', comment: ['{0}, {1} is a placeholder for a setting identifier.'] }, "Controls whether the layout control is shown in the custom title bar. This setting only has an effect when {0} is set to {1}.", '`#window.titleBarStyle#`', '`custom`') + localize({ key: 'layoutControlEnabled', comment: ['{0}, {1} is a placeholder for a setting identifier.'] }, "Controls whether the layout control is shown in the custom title bar. This setting only has an effect when {0} is not set to {1}.", '`#window.customTitleBarVisibility#`', '`never`') }, 'workbench.layoutControl.type': { 'type': 'string', @@ -648,7 +648,7 @@ const registry = Registry.as(ConfigurationExtensions.Con default: true, markdownDescription: isWeb ? localize('window.commandCenterWeb', "Show command launcher together with the window title.") : - localize({ key: 'window.commandCenter', comment: ['{0}, {1} is a placeholder for a setting identifier.'] }, "Show command launcher together with the window title. This setting only has an effect when {0} is set to {1}.", '`#window.titleBarStyle#`', '`custom`') + localize({ key: 'window.commandCenter', comment: ['{0}, {1} is a placeholder for a setting identifier.'] }, "Show command launcher together with the window title. This setting only has an effect when {0} is not set to {1}.", '`#window.customTitleBarVisibility#`', '`never`') }, 'window.menuBarVisibility': { 'type': 'string', From 2d0b92d89b446713dcb123532b7e1b15cc0e9e56 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 22 Jan 2024 16:46:40 +0100 Subject: [PATCH 0515/1897] some `ensureNoDisposablesAreLeakedInTestSuite` (#203026) * some `ensureNoDisposablesAreLeakedInTestSuite` https://github.com/microsoft/vscode/issues/200091 * update eslint file --- .eslintrc.json | 2 -- .../contrib/snippet/test/browser/snippetParser.test.ts | 3 +++ .../contrib/snippet/test/browser/snippetVariables.test.ts | 7 ++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 733bcd3e036..e9670fc9c83 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -155,9 +155,7 @@ "src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts", "src/vs/editor/contrib/gotoSymbol/test/browser/referencesModel.test.ts", "src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts", - "src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts", "src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts", - "src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts", "src/vs/editor/contrib/suggest/test/browser/completionModel.test.ts", "src/vs/editor/contrib/suggest/test/browser/suggestMemory.test.ts", "src/vs/editor/test/common/services/languageService.test.ts", diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts index bc1e224b187..625efaf846e 100644 --- a/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts +++ b/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts @@ -3,10 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Choice, FormatString, Marker, Placeholder, Scanner, SnippetParser, Text, TextmateSnippet, TokenType, Transform, Variable } from 'vs/editor/contrib/snippet/browser/snippetParser'; suite('SnippetParser', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('Scanner', () => { const scanner = new Scanner(); diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts index 8ab633ed81a..53a9b272757 100644 --- a/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts @@ -10,6 +10,7 @@ import { isWindows } from 'vs/base/common/platform'; import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Selection } from 'vs/editor/common/core/selection'; import { TextModel } from 'vs/editor/common/model/textModel'; import { SnippetParser, Variable, VariableResolver } from 'vs/editor/contrib/snippet/browser/snippetParser'; @@ -22,6 +23,7 @@ import { toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; suite('Snippet Variables Resolver', function () { + const labelService = new class extends mock() { override getUriLabel(uri: URI) { return uri.fsPath; @@ -48,6 +50,9 @@ suite('Snippet Variables Resolver', function () { model.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + + function assertVariableResolve(resolver: VariableResolver, varName: string, expected?: string) { const snippet = new SnippetParser().parse(`$${varName}`); const variable = snippet.children[0]; @@ -270,7 +275,7 @@ suite('Snippet Variables Resolver', function () { assertVariableResolve(new ClipboardBasedVariableResolver(() => 'foo', 1, 0, true), 'cLIPBOARD', undefined); }); - test('Add variable to insert value from clipboard to a snippet #40153', function () { + test('Add variable to insert value from clipboard to a snippet #40153, 2', function () { assertVariableResolve(new ClipboardBasedVariableResolver(() => 'line1', 1, 2, true), 'CLIPBOARD', 'line1'); assertVariableResolve(new ClipboardBasedVariableResolver(() => 'line1\nline2\nline3', 1, 2, true), 'CLIPBOARD', 'line1\nline2\nline3'); From 9555d9dfff5e312fd2af6ea6599b23d220a82968 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 22 Jan 2024 12:54:35 -0300 Subject: [PATCH 0516/1897] Properly deserialize these fields (#203029) --- src/vs/workbench/contrib/chat/common/chatModel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 8bec2aa5ec7..235c547b2e4 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -546,11 +546,11 @@ export class ChatModel extends Disposable implements IChatModel { revive(raw.agent) : undefined; request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, raw.slashCommand, request.id, true, raw.isCanceled, raw.vote, raw.responseErrorDetails, raw.followups); if (raw.usedContext) { // @ulugbekna: if this's a new vscode sessions, doc versions are incorrect anyway? - request.response.applyReference(raw.usedContext); + request.response.applyReference(revive(raw.usedContext)); } if (raw.contentReferences) { - raw.contentReferences.forEach(r => request.response!.applyReference(r)); + raw.contentReferences.forEach(r => request.response!.applyReference(revive(r))); } } return request; From ecb6dd684e9cef2e4c4cd57d6ee40de7d0755838 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 22 Jan 2024 16:57:01 +0100 Subject: [PATCH 0517/1897] debt - silence CodeQL warnings (#203031) --- src/vs/platform/backup/electron-main/backupMainService.ts | 2 +- src/vs/platform/workspaces/node/workspaces.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/backup/electron-main/backupMainService.ts b/src/vs/platform/backup/electron-main/backupMainService.ts index b8182072439..fd7b1ae2350 100644 --- a/src/vs/platform/backup/electron-main/backupMainService.ts +++ b/src/vs/platform/backup/electron-main/backupMainService.ts @@ -410,6 +410,6 @@ export class BackupMainService implements IBackupMainService { key = folderUri.toString().toLowerCase(); } - return createHash('md5').update(key).digest('hex'); + return createHash('md5').update(key).digest('hex'); // CodeQL [SM04514] Using MD5 to convert a file path to a fixed length } } diff --git a/src/vs/platform/workspaces/node/workspaces.ts b/src/vs/platform/workspaces/node/workspaces.ts index beffbbe2776..86e7f3ebeee 100644 --- a/src/vs/platform/workspaces/node/workspaces.ts +++ b/src/vs/platform/workspaces/node/workspaces.ts @@ -29,7 +29,7 @@ export function getWorkspaceIdentifier(configPath: URI): IWorkspaceIdentifier { configPathStr = configPathStr.toLowerCase(); // sanitize for platform file system } - return createHash('md5').update(configPathStr).digest('hex'); + return createHash('md5').update(configPathStr).digest('hex'); // CodeQL [SM04514] Using MD5 to convert a file path to a fixed length } return { @@ -50,7 +50,7 @@ export function getSingleFolderWorkspaceIdentifier(folderUri: URI, folderStat?: // Remote: produce a hash from the entire URI if (folderUri.scheme !== Schemas.file) { - return createHash('md5').update(folderUri.toString()).digest('hex'); + return createHash('md5').update(folderUri.toString()).digest('hex'); // CodeQL [SM04514] Using MD5 to convert a file path to a fixed length } // Local: we use the ctime as extra salt to the @@ -77,7 +77,7 @@ export function getSingleFolderWorkspaceIdentifier(folderUri: URI, folderStat?: } } - return createHash('md5').update(folderUri.fsPath).update(ctime ? String(ctime) : '').digest('hex'); + return createHash('md5').update(folderUri.fsPath).update(ctime ? String(ctime) : '').digest('hex'); // CodeQL [SM04514] Using MD5 to convert a file path to a fixed length } const folderId = getFolderId(); From 2a9a0a06f8c9cf687056d4d2634c942481c8c0dd Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 22 Jan 2024 06:17:58 -1000 Subject: [PATCH 0518/1897] end: use a better hash for built-in cache key (#203033) For CodeQL --- build/azure-pipelines/common/computeBuiltInDepsCacheKey.js | 2 +- build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js index fa230bb6849..2d747f56cc7 100644 --- a/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js +++ b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js @@ -8,7 +8,7 @@ const fs = require("fs"); const path = require("path"); const crypto = require("crypto"); const productjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../../product.json'), 'utf8')); -const shasum = crypto.createHash('sha1'); +const shasum = crypto.createHash('sha256'); for (const ext of productjson.builtInExtensions) { shasum.update(`${ext.name}@${ext.version}`); } diff --git a/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts index f0554361607..53d6c501ea9 100644 --- a/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts +++ b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import * as crypto from 'crypto'; const productjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../../product.json'), 'utf8')); -const shasum = crypto.createHash('sha1'); +const shasum = crypto.createHash('sha256'); for (const ext of productjson.builtInExtensions) { shasum.update(`${ext.name}@${ext.version}`); From 4c00663055759e03f6459f3ecc537c7d79c86c89 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 22 Jan 2024 17:20:13 +0100 Subject: [PATCH 0519/1897] supress CodeQL warning (#203032) --- src/vs/platform/languagePacks/node/languagePacks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/languagePacks/node/languagePacks.ts b/src/vs/platform/languagePacks/node/languagePacks.ts index d0d56e57823..3a7df771bc2 100644 --- a/src/vs/platform/languagePacks/node/languagePacks.ts +++ b/src/vs/platform/languagePacks/node/languagePacks.ts @@ -169,7 +169,7 @@ class LanguagePacksCache extends Disposable { private updateHash(languagePack: ILanguagePack): void { if (languagePack) { - const md5 = createHash('md5'); + const md5 = createHash('md5'); // CodeQL [SM04514] Used to create an hash for language pack extension version, which is not a security issue for (const extension of languagePack.extensions) { md5.update(extension.extensionIdentifier.uuid || extension.extensionIdentifier.id).update(extension.version); // CodeQL [SM01510] The extension UUID is not sensitive info and is not manually created by a user } From 74411a0543a7e54b982524bdf2d4f110c1d904e4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 22 Jan 2024 08:29:33 -0800 Subject: [PATCH 0520/1897] use basename --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 6df202d8434..b691ff6b461 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -26,6 +26,7 @@ import { ResourceMap } from 'vs/base/common/map'; import { marked } from 'vs/base/common/marked/marked'; import { FileAccess } from 'vs/base/common/network'; import { clamp } from 'vs/base/common/numbers'; +import { basename } from 'vs/base/common/path'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; @@ -1165,7 +1166,7 @@ class ContentReferencesListPool extends Disposable { { alwaysConsumeMouseWheel: false, accessibilityProvider: { - getAriaLabel: (element: IChatResponseProgressFileTreeData) => element.label, + getAriaLabel: (element: IChatResponseProgressFileTreeData) => basename(element.uri.path), getWidgetAriaLabel: () => localize('usedReferences', "Used References") }, }); From f6bf4c92c03ecf84266729f8d2c8aa59a8e1fa30 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 22 Jan 2024 17:37:00 +0100 Subject: [PATCH 0521/1897] toggle tree sticky scroll --- .../workbench/browser/actions/listCommands.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index ff6e3cbf3d4..1c84f2f9f3e 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -19,6 +19,8 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { Table } from 'vs/base/browser/ui/table/tableWidget'; import { AbstractTree, TreeFindMatchType, TreeFindMode } from 'vs/base/browser/ui/tree/abstractTree'; import { isActiveElement } from 'vs/base/browser/dom'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; function ensureDOMFocus(widget: ListWidget | undefined): void { // it can happen that one of the commands is executed while @@ -904,3 +906,20 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ focused.scrollLeft += 10; } }); + +registerAction2(class ToggleStickyScroll extends Action2 { + constructor() { + super({ + id: 'tree.toggleStickyScroll', + title: { value: 'Toggle Tree Sticky Scroll', original: 'Toggle Tree Sticky Scroll' }, + category: 'View', + f1: true + }); + } + + run(accessor: ServicesAccessor) { + const configurationService = accessor.get(IConfigurationService); + const newValue = !configurationService.getValue('workbench.tree.enableStickyScroll'); + configurationService.updateValue('workbench.tree.enableStickyScroll', newValue); + } +}); From 17fe94ae2a73cff6b1e38c91a56adb551dbb7a27 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 22 Jan 2024 09:14:36 -0800 Subject: [PATCH 0522/1897] fix issue --- .../contrib/chat/browser/chatListRenderer.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index b691ff6b461..8804cf94e30 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -1166,7 +1166,16 @@ class ContentReferencesListPool extends Disposable { { alwaysConsumeMouseWheel: false, accessibilityProvider: { - getAriaLabel: (element: IChatResponseProgressFileTreeData) => basename(element.uri.path), + getAriaLabel: (element: IChatContentReference) => { + if (URI.isUri(element.reference)) { + console.log(basename(element.reference.path)); + return basename(element.reference.path); + } else { + console.log(basename((element.reference.uri.path))); + return basename(element.reference.uri.path); + } + }, + getWidgetAriaLabel: () => localize('usedReferences', "Used References") }, }); From 27ab324c06617d20e30e6e99b9e998592e3f22c4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 22 Jan 2024 09:17:23 -0800 Subject: [PATCH 0523/1897] rm console.logs --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 8804cf94e30..9e6ec93e6ce 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -1168,10 +1168,8 @@ class ContentReferencesListPool extends Disposable { accessibilityProvider: { getAriaLabel: (element: IChatContentReference) => { if (URI.isUri(element.reference)) { - console.log(basename(element.reference.path)); return basename(element.reference.path); } else { - console.log(basename((element.reference.uri.path))); return basename(element.reference.uri.path); } }, From 5efaf9b360b9ca8df50eefc9074265e312eb34ab Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 22 Jan 2024 09:17:48 -0800 Subject: [PATCH 0524/1897] fix issue --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 9e6ec93e6ce..e14185c7444 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -1230,8 +1230,6 @@ class ContentReferencesListRenderer implements IListRenderer Date: Mon, 22 Jan 2024 09:18:39 -0800 Subject: [PATCH 0525/1897] revert change --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index e14185c7444..6c9cc49c006 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -775,7 +775,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { if (e.element) { this.editorService.openEditor({ From de360da900172c91e05497d6fb45c8d0862344cd Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 22 Jan 2024 09:19:11 -0800 Subject: [PATCH 0526/1897] revert change --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 6c9cc49c006..e14185c7444 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -775,6 +775,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { if (e.element) { this.editorService.openEditor({ From a073a416552ac1ea2e4ae20bd21079aec05957f2 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 22 Jan 2024 09:19:45 -0800 Subject: [PATCH 0527/1897] undo move --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index e14185c7444..7d5eaf14e4a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -774,8 +774,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { if (e.element) { this.editorService.openEditor({ From 67ae7ddf0ec98929d4a8daaefc6bdec14afb8516 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 22 Jan 2024 19:01:13 +0100 Subject: [PATCH 0528/1897] fix #203038 (#203046) --- .../workbench/contrib/extensions/browser/extensionsActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 8b4d951ed4e..47def38fec0 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -880,7 +880,7 @@ export class ToggleAutoUpdateForExtensionAction extends ExtensionAction { if (this.extension.isBuiltin) { return; } - if (this.enableWhenOutdated && !this.extension.outdated) { + if (this.enableWhenOutdated && (this.extension.state !== ExtensionState.Installed || !this.extension.outdated)) { return; } if (!this.enableWhenAutoUpdateValue.includes(this.extensionsWorkbenchService.getAutoUpdateValue())) { From 603bc41a0731822f92dc4110238be71d81d93583 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 22 Jan 2024 18:13:12 +0100 Subject: [PATCH 0529/1897] Fixes diff editor soft assert --- .../widget/diffEditor/diffEditorViewModel.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts index b9a3880f0d9..bd065a313f7 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts @@ -170,15 +170,20 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo .map(id => model.modified.getDecorationRange(id)) .map(r => r ? LineRange.fromRangeInclusive(r) : undefined); const updatedLastUnchangedRegions = filterWithPrevious( - lastUnchangedRegions.regions.map((r, idx) => - (!lastUnchangedRegionsOrigRanges[idx] || !lastUnchangedRegionsModRanges[idx]) ? undefined : - new UnchangedRegion( + lastUnchangedRegions.regions + .map((r, idx) => { + if (!lastUnchangedRegionsOrigRanges[idx] || !lastUnchangedRegionsModRanges[idx]) { return undefined; } + const length = lastUnchangedRegionsOrigRanges[idx]!.length; + return new UnchangedRegion( lastUnchangedRegionsOrigRanges[idx]!.startLineNumber, lastUnchangedRegionsModRanges[idx]!.startLineNumber, - lastUnchangedRegionsOrigRanges[idx]!.length, - r.visibleLineCountTop.get(), - r.visibleLineCountBottom.get(), - )).filter(isDefined), + length, + // The visible area can shrink by edits -> we have to account for this + Math.min(r.visibleLineCountTop.get(), length), + Math.min(r.visibleLineCountBottom.get(), length - r.visibleLineCountTop.get()), + ); + } + ).filter(isDefined), (cur, prev) => !prev || (cur.modifiedLineNumber >= prev.modifiedLineNumber + prev.lineCount && cur.originalLineNumber >= prev.originalLineNumber + prev.lineCount) ); From 74f4ad53b8e0e5ad3f662f34fde1d34a8238b710 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 22 Jan 2024 19:24:18 +0100 Subject: [PATCH 0530/1897] donot show switch action when not needed (#203043) --- .../browser/extensions.contribution.ts | 12 +++++------ .../extensions/browser/extensionsActions.ts | 20 ++++++++++++++----- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 38a576546dc..31972316d65 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -1302,7 +1302,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi id: MenuId.ExtensionContext, group: INSTALL_ACTIONS_GROUP, order: 0, - when: ContextKeyExpr.and(ContextKeyExpr.has('inExtensionEditor'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('showPreReleaseVersion'), ContextKeyExpr.not('isBuiltinExtension')) + when: ContextKeyExpr.and(ContextKeyExpr.has('inExtensionEditor'), ContextKeyExpr.has('galleryExtensionHasPreReleaseVersion'), ContextKeyExpr.not('showPreReleaseVersion'), ContextKeyExpr.not('isBuiltinExtension')) }, run: async (accessor: ServicesAccessor, extensionId: string) => { const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); @@ -1318,7 +1318,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi id: MenuId.ExtensionContext, group: INSTALL_ACTIONS_GROUP, order: 1, - when: ContextKeyExpr.and(ContextKeyExpr.has('inExtensionEditor'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.has('extensionHasReleaseVersion'), ContextKeyExpr.has('showPreReleaseVersion'), ContextKeyExpr.not('isBuiltinExtension')) + when: ContextKeyExpr.and(ContextKeyExpr.has('inExtensionEditor'), ContextKeyExpr.has('galleryExtensionHasPreReleaseVersion'), ContextKeyExpr.has('extensionHasReleaseVersion'), ContextKeyExpr.has('showPreReleaseVersion'), ContextKeyExpr.not('isBuiltinExtension')) }, run: async (accessor: ServicesAccessor, extensionId: string) => { const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); @@ -1372,14 +1372,14 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi }); this.registerExtensionAction({ - id: 'workbench.extensions.action.enablePreRlease', + id: 'workbench.extensions.action.switchToPreRlease', title: localize('enablePreRleaseLabel', "Switch to Pre-Release Version"), category: ExtensionsLocalizedLabel, menu: { id: MenuId.ExtensionContext, group: INSTALL_ACTIONS_GROUP, order: 2, - when: ContextKeyExpr.and(CONTEXT_HAS_GALLERY, ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('installedExtensionIsOptedToPreRelease'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) + when: ContextKeyExpr.and(CONTEXT_HAS_GALLERY, ContextKeyExpr.has('galleryExtensionHasPreReleaseVersion'), ContextKeyExpr.not('installedExtensionIsOptedToPreRelease'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) }, run: async (accessor: ServicesAccessor, id: string) => { const instantiationService = accessor.get(IInstantiationService); @@ -1394,14 +1394,14 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi }); this.registerExtensionAction({ - id: 'workbench.extensions.action.disablPreRlease', + id: 'workbench.extensions.action.switchToRelease', title: localize('disablePreRleaseLabel', "Switch to Release Version"), category: ExtensionsLocalizedLabel, menu: { id: MenuId.ExtensionContext, group: INSTALL_ACTIONS_GROUP, order: 2, - when: ContextKeyExpr.and(CONTEXT_HAS_GALLERY, ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.has('installedExtensionIsOptedToPreRelease'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) + when: ContextKeyExpr.and(CONTEXT_HAS_GALLERY, ContextKeyExpr.has('galleryExtensionHasPreReleaseVersion'), ContextKeyExpr.has('installedExtensionIsOptedToPreRelease'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) }, run: async (accessor: ServicesAccessor, id: string) => { const instantiationService = accessor.get(IInstantiationService); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 47def38fec0..eed284ef570 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1087,7 +1087,7 @@ async function getContextMenuActionsGroups(extension: IExtension | undefined | n cksOverlay.push(['installedExtensionIsPreReleaseVersion', !!extension.local?.isPreReleaseVersion]); cksOverlay.push(['installedExtensionIsOptedToPreRelease', !!extension.local?.preRelease]); cksOverlay.push(['galleryExtensionIsPreReleaseVersion', !!extension.gallery?.properties.isPreReleaseVersion]); - cksOverlay.push(['extensionHasPreReleaseVersion', extension.hasPreReleaseVersion]); + cksOverlay.push(['galleryExtensionHasPreReleaseVersion', extension.gallery?.hasPreReleaseVersion]); cksOverlay.push(['extensionHasReleaseVersion', extension.hasReleaseVersion]); const [colorThemes, fileIconThemes, productIconThemes] = await Promise.all([workbenchThemeService.getColorThemes(), workbenchThemeService.getFileIconThemes(), workbenchThemeService.getProductIconThemes()]); @@ -1300,12 +1300,22 @@ export class TogglePreReleaseExtensionAction extends ExtensionAction { if (!this.extension.gallery) { return; } + if (this.extension.preRelease && !this.extension.isPreReleaseVersion) { + return; + } + if (!this.extension.preRelease && !this.extension.gallery.hasPreReleaseVersion) { + return; + } this.enabled = true; this.class = TogglePreReleaseExtensionAction.EnabledClass; - this.label = this.extension.preRelease ? localize('togglePreRleaseDisableLabel', "Switch to Release Version") : localize('togglePreRleaseEnableLabel', "Switch to Pre-Release Version"); - this.tooltip = this.extension.preRelease - ? localize('togglePreRleaseDisableTooltip1', "This will switch and enable updates to release versions") - : localize('togglePreRleaseEnableTooltip', "This will switch to pre-release version and enable updates to latest version always"); + + if (this.extension.preRelease) { + this.label = localize('togglePreRleaseDisableLabel', "Switch to Release Version"); + this.tooltip = localize('togglePreRleaseDisableTooltip', "This will switch and enable updates to release versions"); + } else { + this.label = localize('switchToPreReleaseLabel', "Switch to Pre-Release Version"); + this.tooltip = localize('switchToPreReleaseTooltip', "This will switch to pre-release version and enable updates to latest version always"); + } } override async run(): Promise { From df48846f267a2fd693a6e10dd1b3af40e0537fb2 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Mon, 22 Jan 2024 10:25:15 -0800 Subject: [PATCH 0531/1897] Don't show account entitlements for vscode.dev (#203051) --- .../browser/accountsEntitlements.contribution.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accountEntitlements/browser/accountsEntitlements.contribution.ts b/src/vs/workbench/contrib/accountEntitlements/browser/accountsEntitlements.contribution.ts index 8ce4726c5f3..ecc7d689e27 100644 --- a/src/vs/workbench/contrib/accountEntitlements/browser/accountsEntitlements.contribution.ts +++ b/src/vs/workbench/contrib/accountEntitlements/browser/accountsEntitlements.contribution.ts @@ -26,6 +26,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IRequestService, asText } from 'vs/platform/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { isWeb } from 'vs/base/common/platform'; const configurationKey = 'workbench.accounts.experimental.showEntitlements'; @@ -61,7 +62,7 @@ class AccountsEntitlement extends Disposable implements IWorkbenchContribution { ) { super(); - if (!this.productService.gitHubEntitlement) { + if (!this.productService.gitHubEntitlement || isWeb) { return; } From bdf1f049d3130cc332d4fcc557788fc99174da84 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Mon, 22 Jan 2024 10:58:57 -0800 Subject: [PATCH 0532/1897] publish setting for the notebook variables view (#203056) * publish experimental setting * grammar --- .../browser/contrib/notebookVariables/notebookVariables.ts | 3 ++- .../contrib/notebook/browser/notebook.contribution.ts | 5 +++++ src/vs/workbench/contrib/notebook/common/notebookCommon.ts | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts index 524f8685626..962f144cc70 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts @@ -17,6 +17,7 @@ import { variablesViewIcon } from 'vs/workbench/contrib/notebook/browser/noteboo import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export class NotebookVariables extends Disposable implements IWorkbenchContribution { @@ -34,7 +35,7 @@ export class NotebookVariables extends Disposable implements IWorkbenchContribut } private handleInitEvent(configurationService: IConfigurationService) { - if (configurationService.getValue('notebook.experimental.notebookVariablesView') + if (configurationService.getValue(NotebookSetting.notebookVariablesView) && this.editorService.activeEditorPane?.getId() === 'workbench.editor.notebook') { if (this.initializeView()) { this.listeners.forEach(listener => listener.dispose()); diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index b0c8c072e62..6a7b115bb62 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -1064,6 +1064,11 @@ configurationRegistry.registerConfiguration({ markdownDescription: nls.localize('notebook.cellChat', "Enable experimental cell chat for notebooks."), type: 'boolean', default: false + }, + [NotebookSetting.notebookVariablesView]: { + markdownDescription: nls.localize('notebook.VariablesView.description', "Enable the experimental notebook variables view within the debug panel."), + type: 'boolean', + default: false } } }); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index a4d44cdf61e..d24e108e6d3 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -946,7 +946,8 @@ export const NotebookSetting = { gotoSymbolsAllSymbols: 'notebook.gotoSymbols.showAllSymbols', scrollToRevealCell: 'notebook.scrolling.revealNextCellOnExecute', anchorToFocusedCell: 'notebook.scrolling.experimental.anchorToFocusedCell', - cellChat: 'notebook.experimental.cellChat' + cellChat: 'notebook.experimental.cellChat', + notebookVariablesView: 'notebook.experimental.variablesView' } as const; export const enum CellStatusbarAlignment { From 80750ef3ebafd869c49a269b3aa0967c871adffd Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:00:31 -0800 Subject: [PATCH 0533/1897] chore: bump electron/get to 2.0.3 (#203055) chore: bump electron/get to ^2.0.0 --- build/package.json | 2 +- build/yarn.lock | 281 ++++++++++++++++++++------------------------- 2 files changed, 123 insertions(+), 160 deletions(-) diff --git a/build/package.json b/build/package.json index a06baecbdeb..52b1f47a5eb 100644 --- a/build/package.json +++ b/build/package.json @@ -6,7 +6,7 @@ "@azure/cosmos": "^3", "@azure/identity": "^3.4.1", "@azure/storage-blob": "^12.17.0", - "@electron/get": "^1.12.4", + "@electron/get": "^2.0.0", "@types/ansi-colors": "^3.2.0", "@types/byline": "^4.2.32", "@types/cssnano": "^4.0.0", diff --git a/build/yarn.lock b/build/yarn.lock index c7d9bb3a37b..51ca8c276d0 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -223,21 +223,20 @@ events "^3.0.0" tslib "^2.2.0" -"@electron/get@^1.12.4": - version "1.12.4" - resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.12.4.tgz#a5971113fc1bf8fa12a8789dc20152a7359f06ab" - integrity sha512-6nr9DbJPUR9Xujw6zD3y+rS95TyItEVM0NVjt1EehY2vUWfIgPiIPVHxCvaTS0xr2B+DRxovYVKbuOWqC35kjg== +"@electron/get@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@electron/get/-/get-2.0.3.tgz#fba552683d387aebd9f3fcadbcafc8e12ee4f960" + integrity sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ== dependencies: debug "^4.1.1" env-paths "^2.2.0" fs-extra "^8.1.0" - got "^9.6.0" + got "^11.8.5" progress "^2.0.3" semver "^6.2.0" sumchecker "^3.0.1" optionalDependencies: - global-agent "^2.0.2" - global-tunnel-ng "^2.7.1" + global-agent "^3.0.0" "@esbuild/android-arm64@0.17.14": version "0.17.14" @@ -361,17 +360,17 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.0.3.tgz#13a12ae9e05c2a782f7b5e84c3cbfda4225eaf80" integrity sha512-puWxACExDe9nxbBB3lOymQFrLYml2dVOrd7USiVRnSbgXE+KwBu+HxFvxrzfqsiSda9IWsXJG1ef7C1O2/GmKQ== -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== +"@sindresorhus/is@^4.0.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== +"@szmarczak/http-timer@^4.0.5": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" + integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== dependencies: - defer-to-connect "^1.0.1" + defer-to-connect "^2.0.0" "@tootallnate/once@1": version "1.1.2" @@ -390,6 +389,16 @@ dependencies: "@types/node" "*" +"@types/cacheable-request@^6.0.1": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" + integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "^3.1.4" + "@types/node" "*" + "@types/responselike" "^1.0.0" + "@types/chokidar@*": version "1.7.5" resolved "https://registry.yarnpkg.com/@types/chokidar/-/chokidar-1.7.5.tgz#1fa78c8803e035bed6d98e6949e514b133b0c9b6" @@ -513,11 +522,23 @@ "@types/undertaker" "*" "@types/vinyl-fs" "*" +"@types/http-cache-semantics@*": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" + integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== + "@types/js-beautify@*": version "1.8.0" resolved "https://registry.yarnpkg.com/@types/js-beautify/-/js-beautify-1.8.0.tgz#0369d3d0e1f35a6aec07cb4da2ee2bcda111367c" integrity sha512-/siF86XrwDKLuHe8l7h6NhrAWgLdgqbxmjZv9NvGWmgYRZoTipkjKiWb0SQHy/jcR+ee0GvbG6uGd+LEBMGNvA== +"@types/keyv@^3.1.4": + version "3.1.4" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" + integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== + dependencies: + "@types/node" "*" + "@types/mime@0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-0.0.29.tgz#fbcfd330573b912ef59eeee14602bface630754b" @@ -577,6 +598,13 @@ dependencies: "@types/node" "*" +"@types/responselike@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.3.tgz#cc29706f0a397cfe6df89debfe4bf5cea159db50" + integrity sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw== + dependencies: + "@types/node" "*" + "@types/rimraf@^2.0.4": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.4.tgz#403887b0b53c6100a6c35d2ab24f6ccc042fec46" @@ -895,18 +923,23 @@ byline@^5.0.0: resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE= -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + +cacheable-request@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817" + integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg== dependencies: clone-response "^1.0.2" get-stream "^5.1.0" http-cache-semantics "^4.0.0" - keyv "^3.0.0" + keyv "^4.0.0" lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" + normalize-url "^6.0.1" + responselike "^2.0.0" call-bind@^1.0.0: version "1.0.2" @@ -1092,19 +1125,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -config-chain@^1.1.11: - version "1.1.13" - resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" - integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== - dependencies: - ini "^1.3.4" - proto-list "~1.2.1" - -core-js@^3.6.5: - version "3.15.2" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.15.2.tgz#740660d2ff55ef34ce664d7e2455119c5bdd3d61" - integrity sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q== - core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -1149,13 +1169,6 @@ debug@^2.6.8: dependencies: ms "2.0.0" -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= - dependencies: - mimic-response "^1.0.0" - decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -1168,10 +1181,10 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -defer-to-connect@^1.0.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" - integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== +defer-to-connect@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== define-lazy-prop@^2.0.0: version "2.0.0" @@ -1240,11 +1253,6 @@ domutils@^3.0.1: domelementtype "^2.3.0" domhandler "^5.0.1" -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= - duplexify@^4.1.1: version "4.1.2" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" @@ -1274,11 +1282,6 @@ electron-osx-sign@^0.4.16: minimist "^1.2.0" plist "^3.0.1" -encodeurl@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= - end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -1480,13 +1483,6 @@ get-intrinsic@^1.0.2: has "^1.0.3" has-symbols "^1.0.3" -get-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - get-stream@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" @@ -1530,29 +1526,18 @@ glob@^7.1.3, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -global-agent@^2.0.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-2.2.0.tgz#566331b0646e6bf79429a16877685c4a1fbf76dc" - integrity sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg== +global-agent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6" + integrity sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q== dependencies: boolean "^3.0.1" - core-js "^3.6.5" es6-error "^4.1.1" matcher "^3.0.0" roarr "^2.15.3" semver "^7.3.2" serialize-error "^7.0.1" -global-tunnel-ng@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz#d03b5102dfde3a69914f5ee7d86761ca35d57d8f" - integrity sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg== - dependencies: - encodeurl "^1.0.2" - lodash "^4.17.10" - npm-conf "^1.1.3" - tunnel "^0.0.6" - globalthis@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.2.tgz#2a235d34f4d8036219f7e34929b5de9e18166b8b" @@ -1560,22 +1545,22 @@ globalthis@^1.0.1: dependencies: define-properties "^1.1.3" -got@^9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== +got@^11.8.5: + version "11.8.6" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" + integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.2" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" graceful-fs@^4.1.2: version "4.2.10" @@ -1668,6 +1653,14 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + https-proxy-agent@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" @@ -1694,7 +1687,7 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@^1.3.4, ini@~1.3.0: +ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -1788,10 +1781,10 @@ jsbi@^3.1.3: resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.1.4.tgz#9654dd02207a66a4911b4e4bb74265bc2cbc9dd0" integrity sha512-52QRRFSsi9impURE8ZUbzAMCLjPm4THO7H2fcuIvaaeFTbSysvkodbQQXIVsNgq/ypDbq6dJiuGKL0vZ/i9hUg== -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== json-stringify-safe@^5.0.1: version "5.0.1" @@ -1876,12 +1869,12 @@ keytar@^7.7.0: node-addon-api "^4.3.0" prebuild-install "^7.0.1" -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== +keyv@^4.0.0: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== dependencies: - json-buffer "3.0.0" + json-buffer "3.0.1" leven@^3.1.0: version "3.1.0" @@ -1920,16 +1913,11 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "^3.0.0" -lodash@^4.17.10, lodash@^4.17.21: +lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - lowercase-keys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" @@ -1987,7 +1975,7 @@ mime@^1.3.4, mime@^1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mimic-response@^1.0.0, mimic-response@^1.0.1: +mimic-response@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== @@ -2090,18 +2078,10 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@^4.1.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" - integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== - -npm-conf@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" - integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw== - dependencies: - config-chain "^1.1.11" - pify "^3.0.0" +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== nth-check@^2.0.1: version "2.1.1" @@ -2141,10 +2121,10 @@ open@^8.0.0: is-docker "^2.1.1" is-wsl "^2.2.0" -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== +p-cancelable@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" + integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== parse-node-version@^1.0.0: version "1.0.1" @@ -2203,11 +2183,6 @@ pify@^2.3.0: resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= - plist@^3.0.1: version "3.0.5" resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.5.tgz#2cbeb52d10e3cdccccf0c11a63a85d830970a987" @@ -2253,11 +2228,6 @@ prebuild-install@^7.0.1, prebuild-install@^7.1.1: tar-fs "^2.0.0" tunnel-agent "^0.6.0" -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= - priorityqueuejs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/priorityqueuejs/-/priorityqueuejs-1.0.0.tgz#2ee4f23c2560913e08c07ce5ccdd6de3df2c5af8" @@ -2278,11 +2248,6 @@ progress@^2.0.3: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -proto-list@~1.2.1: - version "1.2.4" - resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" - integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= - pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -2298,6 +2263,11 @@ qs@^6.9.1: dependencies: side-channel "^1.0.4" +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -2354,12 +2324,17 @@ replace-ext@^1.0.0: resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a" integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= +resolve-alpn@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + +responselike@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" + integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== dependencies: - lowercase-keys "^1.0.0" + lowercase-keys "^2.0.0" rimraf@^3.0.0: version "3.0.2" @@ -2616,11 +2591,6 @@ tmp@^0.2.1: dependencies: rimraf "^3.0.0" -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -2724,13 +2694,6 @@ url-join@^4.0.1: resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= - dependencies: - prepend-http "^2.0.0" - util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" From 39fe2dab670354239b4bb457dbbf4dfdaa14669d Mon Sep 17 00:00:00 2001 From: Robo Date: Tue, 23 Jan 2024 04:25:03 +0900 Subject: [PATCH 0534/1897] chore: bump electron@27.2.3 (#202771) * chore: bump electron@27.2.3 * chore: bump distro --- .yarnrc | 4 +- build/checksums/electron.txt | 150 +++++++++++++++++------------------ cgmanifest.json | 4 +- package.json | 4 +- remote/.yarnrc | 2 +- yarn.lock | 8 +- 6 files changed, 86 insertions(+), 86 deletions(-) diff --git a/.yarnrc b/.yarnrc index 1614ee06ded..12a0b977bd9 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,5 +1,5 @@ disturl "https://electronjs.org/headers" -target "27.2.1" -ms_build_id "26149897" +target "27.2.3" +ms_build_id "26378995" runtime "electron" build_from_source "true" diff --git a/build/checksums/electron.txt b/build/checksums/electron.txt index d5d99bdf924..bcd4bfe0da6 100644 --- a/build/checksums/electron.txt +++ b/build/checksums/electron.txt @@ -1,75 +1,75 @@ -fa257474711c65fe0f929299fc21a7171a548ef655e393246a48319ee53f702e *chromedriver-v27.2.1-darwin-arm64.zip -d3f279d281221ca87510716afda22be95f8ad66641b3d46943885de0ae94b046 *chromedriver-v27.2.1-darwin-x64.zip -fc0c512cb87c82b1d5327bb04ef7dfb2046ea884d00896bab89652d607ce1c97 *chromedriver-v27.2.1-linux-arm64.zip -4c33d1a323bc59d776a14b2ebb40229dbf1b8de30d51e5643e6584bd63cd7593 *chromedriver-v27.2.1-linux-armv7l.zip -a60c7fc2f278b7a177eb1ec3dccff6fc9c1c8d120596deb595b544a2b8018a79 *chromedriver-v27.2.1-linux-x64.zip -80bc0e0ce66028436853bb5a625f8a1c6c8456c1a8435070a6e30d0e06b9312b *chromedriver-v27.2.1-mas-arm64.zip -67fcfafbc24902d1dff455527115f4453d49308d95010214dda7a1e46a96e6e7 *chromedriver-v27.2.1-mas-x64.zip -1c33c6cadb07c62e7a80ae64de520cb3122cc8496ee73533da67770d0ab1bf5c *chromedriver-v27.2.1-win32-arm64.zip -666042a40a30ab322a66bbcf8552f777c0564bb4a7647122eb7bd260269cd1c3 *chromedriver-v27.2.1-win32-ia32.zip -af92d72b04760be18ac748c5a0072751b497cff4ebabb4bfeecc23538ea9de8e *chromedriver-v27.2.1-win32-x64.zip -9f626747797f005cfe41076bc905a11ab17ac380a2d7865c3cfba05c63ea6514 *electron-api.json -a1ea4e720b5ce964bb4aa6f723f0305ec81a0024f8e9fe474fb1b05b79402e51 *electron-v27.2.1-darwin-arm64-dsym-snapshot.zip -60f69390ca803845992dae4aecb0d360c8c3da62c842523f307a8d495bec84e7 *electron-v27.2.1-darwin-arm64-dsym.zip -33ed9025a4ac39ec597c6cdcc5f7c425a34aca4e34713732de159685a7884d52 *electron-v27.2.1-darwin-arm64-symbols.zip -a8d12908b5aad53083e1b257ba8028ea55ac099cab859260c2a8b5ce512597ca *electron-v27.2.1-darwin-arm64.zip -68b5e5a021239fc7849986a24dddb54d21f4cc9ffbceb8777b7bfd696c940c50 *electron-v27.2.1-darwin-x64-dsym-snapshot.zip -ed33f31e69925642c3b09b2c318ec66be8877bdfb4984c91ee3b90052fb1bcac *electron-v27.2.1-darwin-x64-dsym.zip -88f7b0fec77ee4e0ae9659cfee4d9cbe3a26b378cc92878236b928254568fb07 *electron-v27.2.1-darwin-x64-symbols.zip -7ea6d061520afa77483b891bb8623574d87458deb54841f740c51a1adfa88fea *electron-v27.2.1-darwin-x64.zip -e4af2089beecdcdb7625918ce1dfd6b3401b10239c1c0435951a8642ff17568f *electron-v27.2.1-linux-arm64-debug.zip -c9b009ce848f02fc31c072b546758fbccb39a6ff2779dfea794920c1a83f8f8d *electron-v27.2.1-linux-arm64-symbols.zip -32c86c89a4fdc55aac107c8807bf3181fc31cf5c6dc4e0ca00319bb7837ed695 *electron-v27.2.1-linux-arm64.zip -e4af2089beecdcdb7625918ce1dfd6b3401b10239c1c0435951a8642ff17568f *electron-v27.2.1-linux-armv7l-debug.zip -ceb3f9e7b19f8e1ca0e94858492b1b5eb8bb3c7e91ebde230bdbe463838f2a0a *electron-v27.2.1-linux-armv7l-symbols.zip -b4496ae943fa8ee19d418f9f871ce05ad2ffab35b5dfc79052c842478464b5d6 *electron-v27.2.1-linux-armv7l.zip -35bdcb99f7f63a6892dde033046b4eb315ca71a459124461b43f9ffcf9cae579 *electron-v27.2.1-linux-x64-debug.zip -5271540e93fbdb1f69f21a37a3deb51886afaf7e1b9200ed0cbb6dc91f83854c *electron-v27.2.1-linux-x64-symbols.zip -b4be96cd85797d23afd422dc7cc6bf321234001810d08b52e32b055a7e8afbd7 *electron-v27.2.1-linux-x64.zip -dc7feb71d7a65bde3ddbcc43e514940a070af9fd8b385f819ac012b9d694ce64 *electron-v27.2.1-mas-arm64-dsym-snapshot.zip -98317e6af5ddb3db98511958a9ad46a54de00c897b67c6d1e3dc3baaf3e3878a *electron-v27.2.1-mas-arm64-dsym.zip -91a78763ac1959c3b4865902e378830c7304917dc20877db7c12283c1d34e510 *electron-v27.2.1-mas-arm64-symbols.zip -01414a8e5ef3c1f7efb888d32bdfc85ba0ea8b42d4e8093ca7cf0e7a8a728415 *electron-v27.2.1-mas-arm64.zip -e082d37c20b1af792307fa2db9ff576e4345256d26dd0e0f5f2d807a45673991 *electron-v27.2.1-mas-x64-dsym-snapshot.zip -3a93a95bf05ec4d01268a64db2d58a07bff90463f3059a3dd4a30968297aab36 *electron-v27.2.1-mas-x64-dsym.zip -138ac2d1fea73d0f677fa5218a0ba2d5422d22cb6090d865c7ddce99383aa415 *electron-v27.2.1-mas-x64-symbols.zip -87457c8938858570b20a122c1cdb23c0a41374b7010253626d49e9b6b127a50a *electron-v27.2.1-mas-x64.zip -ce3638d29dfb46fc5b375ead5c0bbb98b50eec2c9e42c5b4a0a948a60ef25439 *electron-v27.2.1-win32-arm64-pdb.zip -f24636b71937e9b4ca46d046e6131a3acb87edca93be0b26d500d93c6a78abeb *electron-v27.2.1-win32-arm64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.2.1-win32-arm64-toolchain-profile.zip -5b4fd051b04de97ef14a711533ecd44c99d31ce6fc83f2593c4e041e9550eeb1 *electron-v27.2.1-win32-arm64.zip -c8cd49e0ba8a87666a50d358dc3a2810d0d5f2c13e345c48febf488aed513856 *electron-v27.2.1-win32-ia32-pdb.zip -7d28badd01d7f357f0cbbff12df55b1816f2dd08bcceea00fa03b027d7c8bb9b *electron-v27.2.1-win32-ia32-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.2.1-win32-ia32-toolchain-profile.zip -508f29dd2d07c19b1ae6d7546772103230d601d44f33443a79a3b42a5fa9d293 *electron-v27.2.1-win32-ia32.zip -1b40186cd080f9d45bb03b1d7f6e4f3c4fc6332bbbe36453f99bd8643545f384 *electron-v27.2.1-win32-x64-pdb.zip -e2bc044c06a463d29699b5a0bedecb68714cacfdb5661a70db4047cb211a8246 *electron-v27.2.1-win32-x64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.2.1-win32-x64-toolchain-profile.zip -4d0810c1bc9985e7e471efb06380bd190b5d07f46ef1938c1d3ee496e0cdcccf *electron-v27.2.1-win32-x64.zip -f48477c175fe9bc34c1541cb75bd9d3eda2c540a799e2f3b715adcdd626c3dc1 *electron.d.ts -c951898cbf3c65013a21b5b4581d24e38f568183893f785f7d3bab0aedf49d6f *ffmpeg-v27.2.1-darwin-arm64.zip -5306f41915ced8307c44858a91250a228819304c82dfdd80c9f584fe092436ad *ffmpeg-v27.2.1-darwin-x64.zip -be517ba93c5b5f36d2e55af148213f4c2fc43177563825601bee6f88dd4f7b05 *ffmpeg-v27.2.1-linux-arm64.zip -926d0da25ffcea3d05a6cbcae15e5d7729d93bc43394ae4439747669d2210e1d *ffmpeg-v27.2.1-linux-armv7l.zip -6f9c0ef52af14828ad547a80b17f8c63cac51a18b8d5769a2f33e4fa6cccfc7e *ffmpeg-v27.2.1-linux-x64.zip -bb0036c363cdc2558d9feec90cb896c66133400b499b14fa099421e1d0117bc6 *ffmpeg-v27.2.1-mas-arm64.zip -33203048fa78450cac7ae1988b708ad51d1a49933d212b505fcf272da201e6d6 *ffmpeg-v27.2.1-mas-x64.zip -954870aaa5ffa56bbe06b04308bd6a4fc937b82cc11d8780542e4a9f25790b37 *ffmpeg-v27.2.1-win32-arm64.zip -a46eab4db71ff41a0a1056b8777ec2d01f83f4b7a85687514562045f1f2b4820 *ffmpeg-v27.2.1-win32-ia32.zip -74841b03157199f2439d2182cf4da66dfdc4014d4e2d76a043055b4dc5d33a9b *ffmpeg-v27.2.1-win32-x64.zip -7f15f0f64083f093a98f9dc002d6256a3a56cc7d720cd861d820a7223d46be83 *hunspell_dictionaries.zip -6cc8cbde27c4e0ed7689f87a4c444aa50a1bee9bfec35289e26e9854864c1d69 *libcxx-objects-v27.2.1-linux-arm64.zip -ce935631fa896af9d34e8ed01a431c7f4b289d9cc872cede5bfd9e4663ac4d6e *libcxx-objects-v27.2.1-linux-armv7l.zip -979a65ea5cd9c8c44e6d9760c2cb79bec0914396b68be9270b52d09c1b1a72ee *libcxx-objects-v27.2.1-linux-x64.zip -86f961adc7015bbf8762453bc5148d381ff7c7c4580ceed9189c93fd01139fcf *libcxx_headers.zip -acb2c0d967dc14fe63784c79c58648193348252efb25a691b61d3c19142d67e9 *libcxxabi_headers.zip -07bc91f769fb7320bfb331d7e40d963e787960dec0f404b165c7df9ef49f531b *mksnapshot-v27.2.1-darwin-arm64.zip -c9127c412b6f10a706c406865a72119abf34692efd19e58231dec631af91f3c5 *mksnapshot-v27.2.1-darwin-x64.zip -d52e6deec09571b4618dab3ee1c615f2996d3d52e97f0df4c8a7ecddc443d847 *mksnapshot-v27.2.1-linux-arm64-x64.zip -23aa985b9d4431d9a92eefe8f635bcd54a50a1b02d008d9883dce1ca90670d9b *mksnapshot-v27.2.1-linux-armv7l-x64.zip -9aac8616fcfaeda69945742f4fe99309cabfcc355f5a6edae5ac6bdb5de78552 *mksnapshot-v27.2.1-linux-x64.zip -f53004a849ea0d5e19569f48561fc79b95015c3395405ef28800fa34e5a6b37c *mksnapshot-v27.2.1-mas-arm64.zip -8b7458854d6e3339220623a53927eeaac2d97e02133172f2615ee976e32a1337 *mksnapshot-v27.2.1-mas-x64.zip -5314c938447f1f24aee9007719d8d65ef4f68a3fac0e103897bc53f26685ed6a *mksnapshot-v27.2.1-win32-arm64-x64.zip -fbae6d4323d20af0a7c5d5c044e9927b6f8396e0d9c8a45c1f6945ce32250715 *mksnapshot-v27.2.1-win32-ia32.zip -08556f224fcb5dee0928dce8beda23f058258cc85e85da3fe651bf0e84469f5d *mksnapshot-v27.2.1-win32-x64.zip +5e12800023944ba203820eac76382b57ccd5dad371f70650889bd77decbc372b *chromedriver-v27.2.3-darwin-arm64.zip +d271649b18bbbe7aa2c8fe8633bbb335611d018c95ddb6b4f54e6cd037163219 *chromedriver-v27.2.3-darwin-x64.zip +1c94c33c01cb23b1e9bf6ab34093abd1a8b8cfa8b6021a70c8142022b6d8f0a0 *chromedriver-v27.2.3-linux-arm64.zip +4491b78e7fc45f27388abc022841aeecb48fd2907f7aed2be42caf3db3149284 *chromedriver-v27.2.3-linux-armv7l.zip +1ec4ddb99209017df0f3252799e87bd04f2ae6d14b1ed3dc638e8bfbc512441b *chromedriver-v27.2.3-linux-x64.zip +a9384b769f0b47eab221a0576c6eb5b6f8a390943621dac0b4b03c6c11b4db9b *chromedriver-v27.2.3-mas-arm64.zip +2dc6bb386adb60426ed9902cae118073d289bea9d2ad739162aa3534464637b0 *chromedriver-v27.2.3-mas-x64.zip +23587604cdc9aa9b434879a8615f96d023cb5f7bc1bbb1d672b232bda21f3c48 *chromedriver-v27.2.3-win32-arm64.zip +aceae2b417ae6a83bc6ddd3a762bd99e48455765b9db4d65fcfbc1fe17182bae *chromedriver-v27.2.3-win32-ia32.zip +a3797c5d58fb73b22b8d06b58250c57be94673f2c7e6862095a0bd768feee278 *chromedriver-v27.2.3-win32-x64.zip +39ec5429329af2990470a0c46ba865851863610ff46b47c4a25f7055670f1604 *electron-api.json +6cd6e53a0fdccbf3e89da8634d6fda7f04e0e521ffd3f494dfb4e9f7087c5ff2 *electron-v27.2.3-darwin-arm64-dsym-snapshot.zip +cc13dbdb4f319cbc31c8ef76560a92b695d19e1f88709dda5f2f26d87fc04469 *electron-v27.2.3-darwin-arm64-dsym.zip +949cfa456931a3f9190504a7b135a726eb75d223fd769d38c5c0258ab2328db0 *electron-v27.2.3-darwin-arm64-symbols.zip +20d6d486f6e0e428182dbbafd293f3007fc91760d37bbc3559e813d55ee15d85 *electron-v27.2.3-darwin-arm64.zip +b1b4e7d7212336fe28e8a461b920e7ca0a358a313000592d160adbf335c7e65b *electron-v27.2.3-darwin-x64-dsym-snapshot.zip +55f88e9491c237675cb1f7e3d055d404db70e4682e906ede79ff433d8794679f *electron-v27.2.3-darwin-x64-dsym.zip +75262139cb49ce5070501f6e74dade18bf9f0eb3b968d848765a937560f6338b *electron-v27.2.3-darwin-x64-symbols.zip +602b89308fb114631d90bda4bbef611aa571c57588fbb1bfcec781e22d584c22 *electron-v27.2.3-darwin-x64.zip +94989909484abff147b4334a100ace00571d810da94b717e54c82cb76d7acf11 *electron-v27.2.3-linux-arm64-debug.zip +9e57241fa5df5e09c70a99aff5099350f5b7126895f7a4ab8ca401352b4bd99a *electron-v27.2.3-linux-arm64-symbols.zip +86c98c25f1a7e02f3c8e3fc837e8785e1cd88518c4e5f005c59e039bb6e79054 *electron-v27.2.3-linux-arm64.zip +94989909484abff147b4334a100ace00571d810da94b717e54c82cb76d7acf11 *electron-v27.2.3-linux-armv7l-debug.zip +43a89997cb8acd88f37d69649ef4e8f6bbb9ffce187004e09604c410e396e788 *electron-v27.2.3-linux-armv7l-symbols.zip +7a581252c59514696f920f21af0e97f997e01c9df44d6788c457825bf398c0ea *electron-v27.2.3-linux-armv7l.zip +8a27e845f4405a44fb16297362782531aff03219a002a3e23b3b439fd610b6dd *electron-v27.2.3-linux-x64-debug.zip +b8ddc3f92f23d5351c096287b76ff106c53560bf44cdbc3ba33ed2bd9609bb63 *electron-v27.2.3-linux-x64-symbols.zip +3347f4ff220fa63b286d91fec129d7f8d636ce883dba3c53c50b7e5834c940e1 *electron-v27.2.3-linux-x64.zip +b474af6f1681e204e09906f92d26e1474bc84b35510e4454a3f6bbaaf4df387b *electron-v27.2.3-mas-arm64-dsym-snapshot.zip +3c829ad8fafd12a3ee5c633bee0ae12137a9d780b7bc003785fd1badfafe3d97 *electron-v27.2.3-mas-arm64-dsym.zip +1a0ff4e32b980aabbcc21b533b17c16dbe1eb431408e9cc61dccddbcb673c695 *electron-v27.2.3-mas-arm64-symbols.zip +dd59ca093811646c953c71aaa12bf30c773ed7a77ea1344029d87af40281a114 *electron-v27.2.3-mas-arm64.zip +70b04af2ef42f99becdf0de53efaa4cf419151ceeed1b2ed77759c1f7c1332cc *electron-v27.2.3-mas-x64-dsym-snapshot.zip +89c379f6648a1864250adb420e09bcd1ac7422958a4daf6c830c0b15cfb2babc *electron-v27.2.3-mas-x64-dsym.zip +b0215c7b1496352b92547543c2a107fcf4c70023c10043a5a6777bdf82b22287 *electron-v27.2.3-mas-x64-symbols.zip +781f618ec0b16fdee5c0a76430b617f66152aa2e02ccf953fae1c81e57fab950 *electron-v27.2.3-mas-x64.zip +d7deac3628b145c006ab935d90694a9b009fbfdce2e73f4ccc861ce739452af7 *electron-v27.2.3-win32-arm64-pdb.zip +4b767cd84c8fccdd0350fa5b82a3424779994bfcd4e3c4f864f003ea3ec4fd33 *electron-v27.2.3-win32-arm64-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.2.3-win32-arm64-toolchain-profile.zip +407f86a40574152ee89e4f01878e43d389876bbf23058352b77d5d3363ab98d3 *electron-v27.2.3-win32-arm64.zip +b5de17188f154b5041978b90f0471089a94d552d2c96e1f953154484f65bfe31 *electron-v27.2.3-win32-ia32-pdb.zip +f7ac401c08cdfe3d1f60d3e61c1de995f20a476f4762e3a21057a5132ff42735 *electron-v27.2.3-win32-ia32-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.2.3-win32-ia32-toolchain-profile.zip +f637d01467cdb808530391508ac268b397675eaa620b71d9d2f0d71d25cafe53 *electron-v27.2.3-win32-ia32.zip +a7463a6993a6d938c110dfa61c4af7c751081aac581f81d803efa9af063f4530 *electron-v27.2.3-win32-x64-pdb.zip +32fd37c76cfe8ac40738f0c9775f991971ec6a787bd81216ae2822ade8695e7a *electron-v27.2.3-win32-x64-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.2.3-win32-x64-toolchain-profile.zip +36b75f90dde9013e13afd47576192fba1ee1ddf6790cca285e78fd58828d5961 *electron-v27.2.3-win32-x64.zip +57900d1631e5f15f976d8aac2ce11dab56127d83358bbbc09ae8db4aa5698516 *electron.d.ts +a6863fa69d352b46e28966dc0f7ca2af08d91cc5e894421d9c8b21d9a552cf1c *ffmpeg-v27.2.3-darwin-arm64.zip +e35fa1f8f5884dc3dca29b6e7b03f30509dc20012abffcdac027ddda4d2e1602 *ffmpeg-v27.2.3-darwin-x64.zip +be517ba93c5b5f36d2e55af148213f4c2fc43177563825601bee6f88dd4f7b05 *ffmpeg-v27.2.3-linux-arm64.zip +926d0da25ffcea3d05a6cbcae15e5d7729d93bc43394ae4439747669d2210e1d *ffmpeg-v27.2.3-linux-armv7l.zip +6f9c0ef52af14828ad547a80b17f8c63cac51a18b8d5769a2f33e4fa6cccfc7e *ffmpeg-v27.2.3-linux-x64.zip +e2e099bba965b13050d65980705aca2fb26698c9b0bcf1e66725896e6a3702cf *ffmpeg-v27.2.3-mas-arm64.zip +6104f719783d7d910a520dde756555ee77a284e29c3b14d8c2e9c53d409e8efa *ffmpeg-v27.2.3-mas-x64.zip +c7c574fe9afc3a42f207b805543b56b36e3f4fe65b48a5c6a5a4dc03353a6d41 *ffmpeg-v27.2.3-win32-arm64.zip +165bb1e9b42cc8f1bc233f26e823e1f53ae7f1e278fac1ec864273ceb3bfe5eb *ffmpeg-v27.2.3-win32-ia32.zip +1ccc98816757a19f01c838bd67c73fa53e11495bf075fbc101754aeb38a09d8d *ffmpeg-v27.2.3-win32-x64.zip +27490d54e045651a7c2c3d220bb23cf76a98fd9f7566737f5b06e75809e7959e *hunspell_dictionaries.zip +b49f4a784414819807de510a3d8ace198118997c11693cd693807c31260ac47d *libcxx-objects-v27.2.3-linux-arm64.zip +c62630afb544f42749bd2b9f7d0b54d8d44e109fb115f88822a5d7102f61088c *libcxx-objects-v27.2.3-linux-armv7l.zip +f4f8c72bc6b545959a18ba2caf4db05f05ce8c70b234509f95d2826dd54273e2 *libcxx-objects-v27.2.3-linux-x64.zip +f26fd6d3216afb9e9c16be9f88d27d82bb13500eac4957ae2d83c90682a201fc *libcxx_headers.zip +ba6952490ded27c344150527543dee9693e5906ca419a6975a38cb61da6be794 *libcxxabi_headers.zip +fd1e8aed99530f94bec125a60f5b6ea34f2586deb8034f5f437e95f58c951340 *mksnapshot-v27.2.3-darwin-arm64.zip +8508fbbd71c1654b79a02336cdd042101f9596ebf4e1666d2d55ab8d3f363c8f *mksnapshot-v27.2.3-darwin-x64.zip +9c0ecb392aab08f59cddd54b0f3116f3a19442549ec3d3523208ac0807e3d504 *mksnapshot-v27.2.3-linux-arm64-x64.zip +ccc26dd636dbfc9c99c8e76fa55535dc66154c19a7de18d2f462ee4e1269ae95 *mksnapshot-v27.2.3-linux-armv7l-x64.zip +d7ceffae1aeb4022660fd2354bb4b8e3f62fd4531c1f77989039a3e47be17303 *mksnapshot-v27.2.3-linux-x64.zip +efc12f63c71b290c8f07b337098bcd0f54aa9dfa92a1ed16603db5e9f232d100 *mksnapshot-v27.2.3-mas-arm64.zip +65f9fc4c3a639d4b5279fd80b57be79099815a04bcdc504631170a15f8a7efdb *mksnapshot-v27.2.3-mas-x64.zip +b46cc5f28cfb49a2a2b5f4c66cb8d0cfc530877201d4df85b7568c1ad8d5da1c *mksnapshot-v27.2.3-win32-arm64-x64.zip +39878ef70de05b000b86c2e593a62f9d76703d46d1bd5bfdb525e2826ea85d9f *mksnapshot-v27.2.3-win32-ia32.zip +c19a90b8149d0ba9a623d1134c058f514234975dce32ca37e7e97bdf474eb9fd *mksnapshot-v27.2.3-win32-x64.zip diff --git a/cgmanifest.json b/cgmanifest.json index 139424f2613..1a676f2ac1e 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -528,12 +528,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "8471e4750734ea33a85720ce20bca0d8d9d56985" + "commitHash": "b3ca1a1f96bae5352f420ef1f77cc9d7fd5957c6" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "27.2.1" + "version": "27.2.3" }, { "component": { diff --git a/package.json b/package.json index c27208c3fb8..c632d597fa2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.86.0", - "distro": "4d476863b75b35121a8d2a49c6adbdd2d9ea7572", + "distro": "be7d9acdb535bf4095cbbb28c88cfd05a2a11533", "author": { "name": "Microsoft Corporation" }, @@ -150,7 +150,7 @@ "cssnano": "^4.1.11", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "27.2.1", + "electron": "27.2.3", "eslint": "8.36.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^46.5.0", diff --git a/remote/.yarnrc b/remote/.yarnrc index 45af692342a..adbe2d2a5f4 100644 --- a/remote/.yarnrc +++ b/remote/.yarnrc @@ -1,5 +1,5 @@ disturl "https://nodejs.org/dist" target "18.17.1" -ms_build_id "241679" +ms_build_id "252256" runtime "node" build_from_source "true" diff --git a/yarn.lock b/yarn.lock index 5e1850eb69f..0fc6389685a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3570,10 +3570,10 @@ electron-to-chromium@^1.4.202: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.207.tgz#9c3310ebace2952903d05dcaba8abe3a4ed44c01" integrity sha512-piH7MJDJp4rJCduWbVvmUd59AUne1AFBJ8JaRQvk0KzNTSUnZrVXHCZc+eg+CGE4OujkcLJznhGKD6tuAshj5Q== -electron@27.2.1: - version "27.2.1" - resolved "https://registry.yarnpkg.com/electron/-/electron-27.2.1.tgz#9251b151fea39f7ef2b011e45dd04fb06db58c25" - integrity sha512-bYUzyptYrUIFtPnyF2x6DnhF1E9FCthctjbNSKMqg7dG4NqSwyuZzEku3Wts55u8R+ddNFbLoLwRHHLvYTCQlA== +electron@27.2.3: + version "27.2.3" + resolved "https://registry.yarnpkg.com/electron/-/electron-27.2.3.tgz#ef8c7a33175bb9fc91ae5508c560aea719b49232" + integrity sha512-AbDL5WhoyN56Nb6Ox26eFNXo3PcoxPQGUfBMBT8WXY19uD8kI+l6XF8mI09ezMejo3JjKu4kh3zZUPcgskzIng== dependencies: "@electron/get" "^2.0.0" "@types/node" "^18.11.18" From 4a85ff3e4efe1221920a32906798cf51bd91f379 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Mon, 22 Jan 2024 11:57:45 -0800 Subject: [PATCH 0535/1897] add simple limit for number of children (#203061) --- .../notebookVariablesDataSource.ts | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts index d23184d77a5..035e5b0ee7f 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts @@ -5,6 +5,7 @@ import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { localize } from 'vs/nls'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookKernel, INotebookKernelService, VariablesResult, variablePageSize } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; @@ -78,10 +79,13 @@ export class NotebookVariableDataSource implements IAsyncDataSource variablePageSize) { - for (let start = 0; start < parent.indexedChildrenCount; start += variablePageSize) { + // TODO: improve handling of large number of children + const indexedChildCountLimit = 100000; + const limit = Math.min(parent.indexedChildrenCount, indexedChildCountLimit); + for (let start = 0; start < limit; start += variablePageSize) { let end = start + variablePageSize; - if (end > parent.indexedChildrenCount) { - end = parent.indexedChildrenCount; + if (end > limit) { + end = limit; } childNodes.push({ @@ -96,6 +100,19 @@ export class NotebookVariableDataSource implements IAsyncDataSource indexedChildCountLimit) { + childNodes.push({ + kind: 'variable', + notebook: parent.notebook, + id: parent.id + `${limit + 1}`, + extHostId: parent.extHostId, + name: localize('notebook.indexedChildrenLimitReached', "Display limit reached"), + value: '', + indexedChildrenCount: 0, + hasNamedChildren: false + }); + } } else if (parent.indexedChildrenCount > 0) { const variables = kernel.provideVariables(parent.notebook.uri, parent.extHostId, 'indexed', parent.indexStart ?? 0, this.cancellationTokenSource.token); From de8957372679016ef1d2b08515844287eb9b968a Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Mon, 22 Jan 2024 12:00:40 -0800 Subject: [PATCH 0536/1897] wip -- Support folding notebook cells via sticky scroll (#202785) * CSS tweaks, folding icon, indentation option, high contrast background * CSS cleaning, listen to touch events, folding functionality and icons * update testing snapshots, no hashtag prefixing * foldingController nit --- .../media/notebookEditorStickyScroll.css | 53 +++++++++--- .../viewParts/notebookEditorStickyScroll.ts | 86 +++++++++++++++++-- ...ld_render_0-_1___visible_range_3-_8.0.snap | 2 +- ...ng_next_2_against_following_section.0.snap | 2 +- ...ing_against_equivalent_level_header.0.snap | 2 +- ...___scrolltop_halfway_through_cell_0.0.snap | 2 +- ...___scrolltop_halfway_through_cell_2.0.snap | 2 +- ...___scrolltop_halfway_through_cell_7.0.snap | 2 +- ...1___collapsing_against_next_section.0.snap | 2 +- 9 files changed, 128 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css b/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css index 4b4078aecbe..25fa90546d6 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css @@ -6,29 +6,62 @@ .monaco-workbench .notebookOverlay .notebook-sticky-scroll-container { display: none; background-color: var(--vscode-notebook-editorBackground); - width: 100%; + padding-left: 9.5px; } .monaco-workbench .notebookOverlay .notebook-sticky-scroll-container - .notebook-sticky-scroll-line { - background-color: var(--vscode-notebook-editorBackground); - position: relative; - padding-left: 12px; - /* transition: margin-top 0.2s ease-in-out; */ + .notebook-sticky-scroll-element { + display: flex; + align-items: center; +} + +.monaco-workbench + .notebookOverlay + .notebook-sticky-scroll-container + .notebook-sticky-scroll-element + .notebook-sticky-scroll-folding-icon:hover { + outline: 1px dashed var(--vscode-contrastActiveBorder); + outline-offset: -1px; +} + +.monaco-workbench + .notebookOverlay + .notebook-sticky-scroll-container + .notebook-sticky-scroll-element + .notebook-sticky-scroll-header { + width: 100%; + padding-left: 6px; +} + +.monaco-workbench + .notebookOverlay + .notebook-sticky-scroll-container + .notebook-sticky-scroll-element + .notebook-sticky-scroll-header:hover { + background-color: var(--vscode-editorStickyScrollHover-background); + width: 100%; + cursor: pointer; } .monaco-workbench.hc-light .notebookOverlay .notebook-sticky-scroll-container, .monaco-workbench.hc-black .notebookOverlay .notebook-sticky-scroll-container { background-color: var(--vscode-editorStickyScroll-background); border-bottom: 1px solid var(--vscode-contrastBorder); + padding-bottom: 3px; } -.monaco-workbench +.monaco-workbench.hc-light .notebookOverlay .notebook-sticky-scroll-container - .notebook-sticky-scroll-line:hover { - background-color: var(--vscode-editorStickyScrollHover-background); - cursor: pointer; + .notebook-sticky-scroll-element + .notebook-sticky-scroll-header:hover, +.monaco-workbench.hc-black + .notebookOverlay + .notebook-sticky-scroll-container + .notebook-sticky-scroll-element + .notebook-sticky-scroll-header:hover { + outline: 1px dashed var(--vscode-contrastActiveBorder); + outline-offset: -2px; } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts index 0c812230c03..e8eb6c5a7ec 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts @@ -5,6 +5,7 @@ import { localize } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; +import { EventType as TouchEventType } from 'vs/base/browser/touch'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -14,12 +15,16 @@ import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/act import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellFoldingState, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; import { OutlineEntry } from 'vs/workbench/contrib/notebook/browser/viewModel/OutlineEntry'; import { NotebookCellOutlineProvider } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Delayer } from 'vs/base/common/async'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { foldingCollapsedIcon, foldingExpandedIcon } from 'vs/editor/contrib/folding/browser/foldingDecorations'; +import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; +import { FoldingController } from 'vs/workbench/contrib/notebook/browser/controller/foldingController'; export class ToggleNotebookStickyScroll extends Action2 { @@ -54,26 +59,56 @@ export class ToggleNotebookStickyScroll extends Action2 { export class NotebookStickyLine extends Disposable { constructor( public readonly element: HTMLElement, + public readonly foldingIcon: StickyFoldingIcon, + public readonly header: HTMLElement, public readonly entry: OutlineEntry, public readonly notebookEditor: INotebookEditor, ) { super(); - this._register(DOM.addDisposableListener(this.element, DOM.EventType.CLICK, () => { + // click the header to focus the cell + this._register(DOM.addDisposableListener(this.header, DOM.EventType.CLICK || TouchEventType.Tap, () => { this.focusCell(); })); + + // click the folding icon to fold the range covered by the header + this._register(DOM.addDisposableListener(this.foldingIcon.domNode, DOM.EventType.CLICK || TouchEventType.Tap, () => { + if (this.entry.cell.cellKind === CellKind.Markup) { + const currentFoldingState = (this.entry.cell as MarkupCellViewModel).foldingState; + this.toggleFoldRange(currentFoldingState); + } + })); + + // folding icon hovers + // this._register(DOM.addDisposableListener(this.element, DOM.EventType.MOUSE_OVER, () => { + // this.foldingIcon.setVisible(true); + // })); + // this._register(DOM.addDisposableListener(this.element, DOM.EventType.MOUSE_OUT, () => { + // this.foldingIcon.setVisible(false); + // })); + + } + + private toggleFoldRange(currentState: CellFoldingState) { + const foldingController = this.notebookEditor.getContribution(FoldingController.id); + + const index = this.entry.index; + const headerLevel = this.entry.level; + const newFoldingState = (currentState === CellFoldingState.Collapsed) ? CellFoldingState.Expanded : CellFoldingState.Collapsed; + + foldingController.setFoldingStateUp(index, newFoldingState, headerLevel); + this.focusCell(); } private focusCell() { this.notebookEditor.focusNotebookCell(this.entry.cell, 'container'); const cellScrollTop = this.notebookEditor.getAbsoluteTopOfElement(this.entry.cell); - const parentCount = this.getParentCount(); + const parentCount = NotebookStickyLine.getParentCount(this.entry); // 1.1 addresses visible cell padding, to make sure we don't focus md cell and also render its sticky line this.notebookEditor.setScrollTop(cellScrollTop - (parentCount + 1.1) * 22); } - private getParentCount() { + static getParentCount(entry: OutlineEntry) { let count = 0; - let entry = this.entry; while (entry.parent) { count++; entry = entry.parent; @@ -82,6 +117,26 @@ export class NotebookStickyLine extends Disposable { } } +class StickyFoldingIcon { + + public domNode: HTMLElement; + + constructor( + public isCollapsed: boolean, + public dimension: number + ) { + this.domNode = document.createElement('div'); + this.domNode.style.width = `${dimension}px`; + this.domNode.style.height = `${dimension}px`; + this.domNode.className = ThemeIcon.asClassName(isCollapsed ? foldingCollapsedIcon : foldingExpandedIcon); + } + + public setVisible(visible: boolean) { + this.domNode.style.cursor = visible ? 'pointer' : 'default'; + this.domNode.style.opacity = visible ? '1' : '0'; + } +} + export class NotebookStickyScroll extends Disposable { private readonly _disposables = new DisposableStore(); private currentStickyLines = new Map(); @@ -302,9 +357,24 @@ export class NotebookStickyScroll extends Disposable { static createStickyElement(entry: OutlineEntry, notebookEditor: INotebookEditor) { const stickyElement = document.createElement('div'); - stickyElement.classList.add('notebook-sticky-scroll-line'); - stickyElement.innerText = '#'.repeat(entry.level) + ' ' + entry.label; - return new NotebookStickyLine(stickyElement, entry, notebookEditor); + stickyElement.classList.add('notebook-sticky-scroll-element'); + stickyElement.style.paddingLeft = NotebookStickyLine.getParentCount(entry) * 10 + 'px'; + + let isCollapsed = false; + if (entry.cell.cellKind === CellKind.Markup) { + isCollapsed = (entry.cell as MarkupCellViewModel).foldingState === CellFoldingState.Collapsed; + } + + const stickyFoldingIcon = new StickyFoldingIcon(isCollapsed, 16); + stickyFoldingIcon.domNode.classList.add('notebook-sticky-scroll-folding-icon'); + stickyFoldingIcon.setVisible(true); + + const stickyHeader = document.createElement('div'); + stickyHeader.classList.add('notebook-sticky-scroll-header'); + stickyHeader.innerText = entry.label; + + stickyElement.append(stickyFoldingIcon.domNode, stickyHeader); + return new NotebookStickyLine(stickyElement, stickyFoldingIcon, stickyHeader, entry, notebookEditor); } private disposeCurrentStickyLines() { diff --git a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test1__should_render_0-_1___visible_range_3-_8.0.snap b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test1__should_render_0-_1___visible_range_3-_8.0.snap index 1bcf0a58d43..81faf81e4af 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test1__should_render_0-_1___visible_range_3-_8.0.snap +++ b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test1__should_render_0-_1___visible_range_3-_8.0.snap @@ -1 +1 @@ -[ "# header a", "## header aa" ] \ No newline at end of file +[ "header a", "header aa" ] \ No newline at end of file diff --git a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test2__should_render_0____visible_range_6-_9_so_collapsing_next_2_against_following_section.0.snap b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test2__should_render_0____visible_range_6-_9_so_collapsing_next_2_against_following_section.0.snap index 3f23335f240..ff5d6d92e88 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test2__should_render_0____visible_range_6-_9_so_collapsing_next_2_against_following_section.0.snap +++ b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test2__should_render_0____visible_range_6-_9_so_collapsing_next_2_against_following_section.0.snap @@ -1 +1 @@ -[ "# header a" ] \ No newline at end of file +[ "header a" ] \ No newline at end of file diff --git a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test3__should_render_0-_2___collapsing_against_equivalent_level_header.0.snap b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test3__should_render_0-_2___collapsing_against_equivalent_level_header.0.snap index 3b3d5999fd8..9ba2fcd338e 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test3__should_render_0-_2___collapsing_against_equivalent_level_header.0.snap +++ b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test3__should_render_0-_2___collapsing_against_equivalent_level_header.0.snap @@ -1 +1 @@ -[ "# header a", "## header aa", "### header aab" ] \ No newline at end of file +[ "header a", "header aa", "header aab" ] \ No newline at end of file diff --git a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test4__should_render_0____scrolltop_halfway_through_cell_0.0.snap b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test4__should_render_0____scrolltop_halfway_through_cell_0.0.snap index 3f23335f240..ff5d6d92e88 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test4__should_render_0____scrolltop_halfway_through_cell_0.0.snap +++ b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test4__should_render_0____scrolltop_halfway_through_cell_0.0.snap @@ -1 +1 @@ -[ "# header a" ] \ No newline at end of file +[ "header a" ] \ No newline at end of file diff --git a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test5__should_render_0-_2___scrolltop_halfway_through_cell_2.0.snap b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test5__should_render_0-_2___scrolltop_halfway_through_cell_2.0.snap index 2e13ea43822..5ebd7d5ec87 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test5__should_render_0-_2___scrolltop_halfway_through_cell_2.0.snap +++ b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test5__should_render_0-_2___scrolltop_halfway_through_cell_2.0.snap @@ -1 +1 @@ -[ "# header a", "## header aa", "### header aaa" ] \ No newline at end of file +[ "header a", "header aa", "header aaa" ] \ No newline at end of file diff --git a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test6__should_render_6-_7___scrolltop_halfway_through_cell_7.0.snap b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test6__should_render_6-_7___scrolltop_halfway_through_cell_7.0.snap index 77fe21fe781..e2a34d2a708 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test6__should_render_6-_7___scrolltop_halfway_through_cell_7.0.snap +++ b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test6__should_render_6-_7___scrolltop_halfway_through_cell_7.0.snap @@ -1 +1 @@ -[ "# header b", "## header bb" ] \ No newline at end of file +[ "header b", "header bb" ] \ No newline at end of file diff --git a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test7__should_render_0-_1___collapsing_against_next_section.0.snap b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test7__should_render_0-_1___collapsing_against_next_section.0.snap index 1bcf0a58d43..81faf81e4af 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test7__should_render_0-_1___collapsing_against_next_section.0.snap +++ b/src/vs/workbench/contrib/notebook/test/browser/__snapshots__/NotebookEditorStickyScroll_test7__should_render_0-_1___collapsing_against_next_section.0.snap @@ -1 +1 @@ -[ "# header a", "## header aa" ] \ No newline at end of file +[ "header a", "header aa" ] \ No newline at end of file From e0f13622d780fc275fe8481848340389f48362b4 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Mon, 22 Jan 2024 13:09:25 -0700 Subject: [PATCH 0537/1897] Trim insider suffix from version when sent to ExP (#203071) * Trim insiders suffix from version * Fix comment indent * Fix more indent --- src/vs/platform/assignment/common/assignment.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/assignment/common/assignment.ts b/src/vs/platform/assignment/common/assignment.ts index 1fe2da6a875..8a2fe3becf7 100644 --- a/src/vs/platform/assignment/common/assignment.ts +++ b/src/vs/platform/assignment/common/assignment.ts @@ -90,10 +90,24 @@ export class AssignmentFilterProvider implements IExperimentationFilterProvider private targetPopulation: TargetPopulation ) { } + /** + * Returns a version string that can be parsed by the TAS client. + * The tas client cannot handle suffixes lke "-insider" + * Ref: https://github.com/microsoft/tas-client/blob/30340d5e1da37c2789049fcf45928b954680606f/vscode-tas-client/src/vscode-tas-client/VSCodeFilterProvider.ts#L35 + * + * @param version Version string to be trimmed. + */ + private static trimVersionSuffix(version: string): string { + const regex = /\-[a-zA-Z0-9]+$/; + const result = version.split(regex); + + return result[0]; + } + getFilterValue(filter: string): string | null { switch (filter) { case Filters.ApplicationVersion: - return this.version; // productService.version + return AssignmentFilterProvider.trimVersionSuffix(this.version); // productService.version case Filters.Build: return this.appName; // productService.nameLong case Filters.ClientId: From f9bb272b285da871e51a8d56160e988209900d45 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Mon, 22 Jan 2024 14:18:22 -0600 Subject: [PATCH 0538/1897] Focus in Quick Search doesn't go back to previous position after hitting ESC when zero results (#203073) Fixes #202911 --- .../browser/quickTextSearch/textSearchQuickAccess.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index 3be24ea6f34..b1ba725a1b3 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -54,7 +54,6 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { if (this.searchModel.searchResult.count() > 0) { this.moveToSearchViewlet(undefined); @@ -114,12 +114,8 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider folder.uri), this._getTextQueryBuilderOptions(charsPerLine)); From 0c882aa7709b3060c63f6e3ddc6ecc81c3df9005 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 22 Jan 2024 10:58:52 -1000 Subject: [PATCH 0539/1897] debug: update js-debug (#203076) --- product.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/product.json b/product.json index 49b692f2c2f..7f445fef7a2 100644 --- a/product.json +++ b/product.json @@ -50,8 +50,8 @@ }, { "name": "ms-vscode.js-debug", - "version": "1.85.0", - "sha256": "85a97d373a6f92359f5db7bfc5a2fa6c5763b22c6ad07c962dfe32c84a079f3b", + "version": "1.86.0", + "sha256": "cdd3187384c953160c3d0ad1e8ebd869cc420930a9993f977f7d739029a02a54", "repo": "https://github.com/microsoft/vscode-js-debug", "metadata": { "id": "25629058-ddac-4e17-abba-74678e126c5d", From 2d30630ab494f403bb19af922fee673255f210ee Mon Sep 17 00:00:00 2001 From: RedCMD <33529441+RedCMD@users.noreply.github.com> Date: Tue, 23 Jan 2024 10:11:24 +1300 Subject: [PATCH 0540/1897] `onDidChangeEmmiter` fires constantly when it shouldn't (#202198) `onDidChangeEmmiter` fires when it shouldn't --- .../html-language-features/client/src/languageParticipants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/html-language-features/client/src/languageParticipants.ts b/extensions/html-language-features/client/src/languageParticipants.ts index 6abe49237c8..e3d5612b9b3 100644 --- a/extensions/html-language-features/client/src/languageParticipants.ts +++ b/extensions/html-language-features/client/src/languageParticipants.ts @@ -55,7 +55,7 @@ export function getLanguageParticipants(): LanguageParticipants { } } } - return !isEqualSet(languages, oldLanguages) || !isEqualSet(oldLanguages, oldAutoInsert); + return !isEqualSet(languages, oldLanguages) || !isEqualSet(autoInsert, oldAutoInsert); } update(); From 1ddc10601dcd3d9045c8f84109783d7d9ceb0fbc Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 22 Jan 2024 14:58:28 -0800 Subject: [PATCH 0541/1897] Add angle brackets as surrounding pairs in markdown (#203083) Add angle brackets as surrounding pairs in md This is useful for html and for autolinks --- extensions/markdown-basics/language-configuration.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extensions/markdown-basics/language-configuration.json b/extensions/markdown-basics/language-configuration.json index 0d36cf29d7a..4a79f1c5c07 100644 --- a/extensions/markdown-basics/language-configuration.json +++ b/extensions/markdown-basics/language-configuration.json @@ -79,6 +79,10 @@ [ "\"", "\"" + ], + [ + "<", + ">" ] ], "folding": { From 0152062c6eeae231932c592edd45f9d68d0b393d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 22 Jan 2024 15:02:28 -0800 Subject: [PATCH 0542/1897] fix punctuation issues --- .../contrib/chat/browser/actions/chatAccessibilityHelp.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 302c4d0c61b..6be43b0fcff 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -21,11 +21,11 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'pane if (type === 'panelChat') { content.push(localize('chat.overview', 'The chat view is comprised of an input box and a request/response list. The input box is used to make requests and the list is used to display responses.')); content.push(localize('chat.requestHistory', 'In the input box, use up and down arrows to navigate your request history. Edit input and use enter or the submit button to run a new request.')); - content.push(openAccessibleViewKeybinding ? localize('chat.inspectResponse', 'In the input box, inspect the last response in the accessible view via {0}', openAccessibleViewKeybinding) : localize('chat.inspectResponseNoKb', 'With the input box focused, inspect the last response in the accessible view via the Open Accessible View command, which is currently not triggerable by a keybinding.')); + content.push(openAccessibleViewKeybinding ? localize('chat.inspectResponse', 'In the input box, inspect the last response in the accessible view {0}', openAccessibleViewKeybinding) : localize('chat.inspectResponseNoKb', 'With the input box focused, inspect the last response in the accessible view via the Open Accessible View command, which is currently not triggerable by a keybinding.')); content.push(localize('chat.followUp', 'In the input box, navigate to the suggested follow up question (Shift+Tab) and press Enter to run it.')); content.push(localize('chat.announcement', 'Chat responses will be announced as they come in. A response will indicate the number of code blocks, if any, and then the rest of the response.')); content.push(descriptionForCommand('chat.action.focus', localize('workbench.action.chat.focus', 'To focus the chat request/response list, which can be navigated with up and down arrows, invoke the Focus Chat command ({0}).',), localize('workbench.action.chat.focusNoKb', 'To focus the chat request/response list, which can be navigated with up and down arrows, invoke The Focus Chat List command, which is currently not triggerable by a keybinding.'), keybindingService)); - content.push(descriptionForCommand('workbench.action.chat.focusInput', localize('workbench.action.chat.focusInput', 'To focus the input box for chat requests, invoke the Focus Chat Input command ({0})'), localize('workbench.action.interactiveSession.focusInputNoKb', 'To focus the input box for chat requests, invoke the Focus Chat Input command, which is currently not triggerable by a keybinding.'), keybindingService)); + content.push(descriptionForCommand('workbench.action.chat.focusInput', localize('workbench.action.chat.focusInput', 'To focus the input box for chat requests, invoke the Focus Chat Input command ({0}).'), localize('workbench.action.interactiveSession.focusInputNoKb', 'To focus the input box for chat requests, invoke the Focus Chat Input command, which is currently not triggerable by a keybinding.'), keybindingService)); content.push(descriptionForCommand('workbench.action.chat.nextCodeBlock', localize('workbench.action.chat.nextCodeBlock', 'To focus the next code block within a response, invoke the Chat: Next Code Block command ({0}).'), localize('workbench.action.chat.nextCodeBlockNoKb', 'To focus the next code block within a response, invoke the Chat: Next Code Block command, which is currently not triggerable by a keybinding.'), keybindingService)); content.push(descriptionForCommand('workbench.action.chat.nextFileTree', localize('workbench.action.chat.nextFileTree', 'To focus the next file tree within a response, invoke the Chat: Next File Tree command ({0}).'), localize('workbench.action.chat.nextFileTreeNoKb', 'To focus the next file tree within a response, invoke the Chat: Next File Tree command, which is currently not triggerable by a keybinding.'), keybindingService)); content.push(descriptionForCommand('workbench.action.chat.clear', localize('workbench.action.chat.clear', 'To clear the request/response list, invoke the Chat Clear command ({0}).'), localize('workbench.action.chat.clearNoKb', 'To clear the request/response list, invoke the Chat Clear command, which is currently not triggerable by a keybinding.'), keybindingService)); @@ -38,7 +38,7 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'pane if (upHistoryKeybinding && downHistoryKeybinding) { content.push(localize('inlineChat.requestHistory', 'In the input box, use {0} and {1} to navigate your request history. Edit input and use enter or the submit button to run a new request.', upHistoryKeybinding, downHistoryKeybinding)); } - content.push(openAccessibleViewKeybinding ? localize('inlineChat.inspectResponse', 'In the input box, inspect the response in the accessible view via {0}', openAccessibleViewKeybinding) : localize('inlineChat.inspectResponseNoKb', 'With the input box focused, inspect the response in the accessible view via the Open Accessible View command, which is currently not triggerable by a keybinding.')); + content.push(openAccessibleViewKeybinding ? localize('inlineChat.inspectResponse', 'In the input box, inspect the response in the accessible view {0}.', openAccessibleViewKeybinding) : localize('inlineChat.inspectResponseNoKb', 'With the input box focused, inspect the response in the accessible view via the Open Accessible View command, which is currently not triggerable by a keybinding.')); content.push(localize('inlineChat.contextActions', "Context menu actions may run a request prefixed with a /. Type / to discover such ready-made commands.")); content.push(localize('inlineChat.fix', "If a fix action is invoked, a response will indicate the problem with the current code. A diff editor will be rendered and can be reached by tabbing.")); const diffReviewKeybinding = keybindingService.lookupKeybinding(AccessibleDiffViewerNext.id)?.getAriaLabel(); From a6c109418c60054224a1d6a51f1d3d64b0c20db4 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Mon, 22 Jan 2024 15:29:12 -0800 Subject: [PATCH 0543/1897] Add setting for nb sticky indent mode (#203085) add setting for nb sticky indent mode --- .../interactive/browser/interactiveEditor.ts | 2 +- .../notebook/browser/notebook.contribution.ts | 15 ++++++-- .../notebook/browser/notebookOptions.ts | 35 ++++++++++++------ .../viewParts/notebookEditorStickyScroll.ts | 36 ++++++++++++------- .../contrib/notebook/common/notebookCommon.ts | 3 +- 5 files changed, 65 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index b15b5a16a66..f422393ef5f 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -160,7 +160,7 @@ export class InteractiveEditor extends EditorPane { this._editorOptions = this._computeEditorOptions(); } })); - this._notebookOptions = new NotebookOptions(configurationService, notebookExecutionStateService, true, { cellToolbarInteraction: 'hover', globalToolbar: true, stickyScroll: false, dragAndDropEnabled: false }); + this._notebookOptions = new NotebookOptions(configurationService, notebookExecutionStateService, true, { cellToolbarInteraction: 'hover', globalToolbar: true, stickyScrollEnabled: false, dragAndDropEnabled: false }); this._editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY); codeEditorService.registerDecorationType('interactive-decoration', DECORATION_KEY, {}); diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 6a7b115bb62..ce28e166b21 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -872,12 +872,23 @@ configurationRegistry.registerConfiguration({ default: true, tags: ['notebookLayout'] }, - [NotebookSetting.stickyScroll]: { - description: nls.localize('notebook.stickyScroll.description', "Experimental. Control whether to render notebook Sticky Scroll headers in the notebook editor."), + [NotebookSetting.stickyScrollEnabled]: { + description: nls.localize('notebook.stickyScrollEnabled.description', "Experimental. Control whether to render notebook Sticky Scroll headers in the notebook editor."), type: 'boolean', default: false, tags: ['notebookLayout'] }, + [NotebookSetting.stickyScrollMode]: { + description: nls.localize('notebook.stickyScrollMode.description', "Control whether nested sticky lines appear to stack flat or indented."), + type: 'string', + enum: ['flat', 'indented'], + enumDescriptions: [ + nls.localize('notebook.stickyScrollMode.flat', "Nested sticky lines appear flat."), + nls.localize('notebook.stickyScrollMode.indented', "Nested sticky lines appear indented."), + ], + default: 'flat', + tags: ['notebookLayout'] + }, [NotebookSetting.consolidatedOutputButton]: { description: nls.localize('notebook.consolidatedOutputButton.description', "Control whether outputs action should be rendered in the output toolbar."), type: 'boolean', diff --git a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts index eb4bbe768ac..7530b8e91f9 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts @@ -41,7 +41,8 @@ export interface NotebookDisplayOptions { insertToolbarPosition: 'betweenCells' | 'notebookToolbar' | 'both' | 'hidden'; insertToolbarAlignment: 'left' | 'center'; globalToolbar: boolean; - stickyScroll: boolean; + stickyScrollEnabled: boolean; + stickyScrollMode: 'flat' | 'indented'; consolidatedOutputButton: boolean; consolidatedRunButton: boolean; showFoldingControls: 'always' | 'never' | 'mouseover'; @@ -92,7 +93,8 @@ export interface NotebookOptionsChangeEvent { readonly insertToolbarPosition?: boolean; readonly insertToolbarAlignment?: boolean; readonly globalToolbar?: boolean; - readonly stickyScroll?: boolean; + readonly stickyScrollEnabled?: boolean; + readonly stickyScrollMode?: boolean; readonly showFoldingControls?: boolean; readonly consolidatedOutputButton?: boolean; readonly consolidatedRunButton?: boolean; @@ -139,12 +141,13 @@ export class NotebookOptions extends Disposable { private readonly configurationService: IConfigurationService, private readonly notebookExecutionStateService: INotebookExecutionStateService, private isReadonly: boolean, - private readonly overrides?: { cellToolbarInteraction: string; globalToolbar: boolean; stickyScroll: boolean; dragAndDropEnabled: boolean } + private readonly overrides?: { cellToolbarInteraction: string; globalToolbar: boolean; stickyScrollEnabled: boolean; dragAndDropEnabled: boolean } ) { super(); const showCellStatusBar = this.configurationService.getValue(NotebookSetting.showCellStatusBar); const globalToolbar = overrides?.globalToolbar ?? this.configurationService.getValue(NotebookSetting.globalToolbar) ?? true; - const stickyScroll = overrides?.stickyScroll ?? this.configurationService.getValue(NotebookSetting.stickyScroll) ?? false; + const stickyScrollEnabled = overrides?.stickyScrollEnabled ?? this.configurationService.getValue(NotebookSetting.stickyScrollEnabled) ?? false; + const stickyScrollMode = this._computeStickyScrollModeOption(); const consolidatedOutputButton = this.configurationService.getValue(NotebookSetting.consolidatedOutputButton) ?? true; const consolidatedRunButton = this.configurationService.getValue(NotebookSetting.consolidatedRunButton) ?? false; const dragAndDropEnabled = overrides?.dragAndDropEnabled ?? this.configurationService.getValue(NotebookSetting.dragAndDropEnabled) ?? true; @@ -220,7 +223,8 @@ export class NotebookOptions extends Disposable { collapsedIndicatorHeight: 28, showCellStatusBar, globalToolbar, - stickyScroll, + stickyScrollEnabled, + stickyScrollMode, consolidatedOutputButton, consolidatedRunButton, dragAndDropEnabled, @@ -343,7 +347,8 @@ export class NotebookOptions extends Disposable { const insertToolbarPosition = e.affectsConfiguration(NotebookSetting.insertToolbarLocation); const insertToolbarAlignment = e.affectsConfiguration(NotebookSetting.experimentalInsertToolbarAlignment); const globalToolbar = e.affectsConfiguration(NotebookSetting.globalToolbar); - const stickyScroll = e.affectsConfiguration(NotebookSetting.stickyScroll); + const stickyScrollEnabled = e.affectsConfiguration(NotebookSetting.stickyScrollEnabled); + const stickyScrollMode = e.affectsConfiguration(NotebookSetting.stickyScrollMode); const consolidatedOutputButton = e.affectsConfiguration(NotebookSetting.consolidatedOutputButton); const consolidatedRunButton = e.affectsConfiguration(NotebookSetting.consolidatedRunButton); const showFoldingControls = e.affectsConfiguration(NotebookSetting.showFoldingControls); @@ -369,7 +374,8 @@ export class NotebookOptions extends Disposable { && !insertToolbarPosition && !insertToolbarAlignment && !globalToolbar - && !stickyScroll + && !stickyScrollEnabled + && !stickyScrollMode && !consolidatedOutputButton && !consolidatedRunButton && !showFoldingControls @@ -426,8 +432,12 @@ export class NotebookOptions extends Disposable { configuration.globalToolbar = this.configurationService.getValue(NotebookSetting.globalToolbar) ?? true; } - if (stickyScroll && this.overrides?.stickyScroll === undefined) { - configuration.stickyScroll = this.configurationService.getValue(NotebookSetting.stickyScroll) ?? false; + if (stickyScrollEnabled && this.overrides?.stickyScrollEnabled === undefined) { + configuration.stickyScrollEnabled = this.configurationService.getValue(NotebookSetting.stickyScrollEnabled) ?? false; + } + + if (stickyScrollMode) { + configuration.stickyScrollMode = this.configurationService.getValue<'flat' | 'indented'>(NotebookSetting.stickyScrollMode) ?? 'flat'; } if (consolidatedOutputButton) { @@ -499,7 +509,8 @@ export class NotebookOptions extends Disposable { insertToolbarPosition, insertToolbarAlignment, globalToolbar, - stickyScroll, + stickyScrollEnabled, + stickyScrollMode, showFoldingControls, consolidatedOutputButton, consolidatedRunButton, @@ -534,6 +545,10 @@ export class NotebookOptions extends Disposable { return this.configurationService.getValue<'border' | 'gutter'>(NotebookSetting.focusIndicator) ?? 'gutter'; } + private _computeStickyScrollModeOption() { + return this.configurationService.getValue<'flat' | 'indented'>(NotebookSetting.stickyScrollMode) ?? 'flat'; + } + getCellCollapseDefault(): NotebookCellDefaultCollapseConfig { return this._layoutConfiguration.interactiveWindowCollapseCodeCells === 'never' ? { diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts index e8eb6c5a7ec..c614ea2ef46 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts @@ -25,6 +25,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { foldingCollapsedIcon, foldingExpandedIcon } from 'vs/editor/contrib/folding/browser/foldingDecorations'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; import { FoldingController } from 'vs/workbench/contrib/notebook/browser/controller/foldingController'; +import { NotebookOptionsChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; export class ToggleNotebookStickyScroll extends Action2 { @@ -188,13 +189,13 @@ export class NotebookStickyScroll extends Disposable { ) { super(); - if (this.notebookEditor.notebookOptions.getDisplayOptions().stickyScroll) { + if (this.notebookEditor.notebookOptions.getDisplayOptions().stickyScrollEnabled) { this.init(); } this._register(this.notebookEditor.notebookOptions.onDidChangeOptions((e) => { - if (e.stickyScroll) { - this.updateConfig(); + if (e.stickyScrollEnabled || e.stickyScrollMode) { + this.updateConfig(e); } })); @@ -211,20 +212,26 @@ export class NotebookStickyScroll extends Disposable { }); } - private updateConfig() { - if (this.notebookEditor.notebookOptions.getDisplayOptions().stickyScroll) { - this.init(); - } else { - this._disposables.clear(); - this.disposeCurrentStickyLines(); - DOM.clearNode(this.domNode); - this.updateDisplay(); + private updateConfig(e: NotebookOptionsChangeEvent) { + if (e.stickyScrollEnabled) { + if (this.notebookEditor.notebookOptions.getDisplayOptions().stickyScrollEnabled) { + this.init(); + } else { + this._disposables.clear(); + this.notebookOutline.dispose(); + this.disposeCurrentStickyLines(); + DOM.clearNode(this.domNode); + this.updateDisplay(); + } + } else if (e.stickyScrollMode && this.notebookEditor.notebookOptions.getDisplayOptions().stickyScrollEnabled) { + this.updateContent(computeContent(this.notebookEditor, this.notebookCellList, this.filteredOutlineEntries, this.getCurrentStickyHeight())); } } private init() { this.notebookOutline.init(); this.filteredOutlineEntries = this.notebookOutline.entries.filter(entry => entry.level !== 7); + this.updateContent(computeContent(this.notebookEditor, this.notebookCellList, this.filteredOutlineEntries, this.getCurrentStickyHeight())); this._disposables.add(this.notebookOutline.onDidChange(() => { this.filteredOutlineEntries = this.notebookOutline.entries.filter(entry => entry.level !== 7); @@ -358,7 +365,11 @@ export class NotebookStickyScroll extends Disposable { static createStickyElement(entry: OutlineEntry, notebookEditor: INotebookEditor) { const stickyElement = document.createElement('div'); stickyElement.classList.add('notebook-sticky-scroll-element'); - stickyElement.style.paddingLeft = NotebookStickyLine.getParentCount(entry) * 10 + 'px'; + + const indentMode = notebookEditor.notebookOptions.getLayoutConfiguration().stickyScrollMode; + if (indentMode === 'indented') { + stickyElement.style.paddingLeft = NotebookStickyLine.getParentCount(entry) * 10 + 'px'; + } let isCollapsed = false; if (entry.cell.cellKind === CellKind.Markup) { @@ -386,6 +397,7 @@ export class NotebookStickyScroll extends Disposable { override dispose() { this._disposables.dispose(); this.disposeCurrentStickyLines(); + this.notebookOutline.dispose(); super.dispose(); } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index d24e108e6d3..c673ae8ad79 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -913,7 +913,8 @@ export const NotebookSetting = { focusIndicator: 'notebook.cellFocusIndicator', insertToolbarLocation: 'notebook.insertToolbarLocation', globalToolbar: 'notebook.globalToolbar', - stickyScroll: 'notebook.stickyScroll.enabled', + stickyScrollEnabled: 'notebook.stickyScroll.enabled', + stickyScrollMode: 'notebook.stickyScroll.mode', undoRedoPerCell: 'notebook.undoRedoPerCell', consolidatedOutputButton: 'notebook.consolidatedOutputButton', showFoldingControls: 'notebook.showFoldingControls', From fc57e0e1d44497ee0194ea6ed22cddcfbb78f28d Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:40:55 -0800 Subject: [PATCH 0544/1897] issue reporter command enhancements (#203080) * groundwork for command, only strings for now * more groundwork for uri * added uri and clearing suport when switching extensions * cleanup --- .../issue/issueReporterService.ts | 67 ++++++++++++++++--- src/vs/platform/issue/common/issue.ts | 12 +++- 2 files changed, 68 insertions(+), 11 deletions(-) diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index 896747af279..1924ff872a0 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -248,8 +248,13 @@ export class IssueReporter extends Disposable { private async updateIssueReporterUri(extension: IssueReporterExtensionData): Promise { try { - const uri = await this.issueMainService.$getIssueReporterUri(extension.id); - extension.bugsUrl = uri.toString(true); + if (extension.command?.uri) { + extension.bugsUrl = extension.command.uri; + } else { + const uri = await this.issueMainService.$getIssueReporterUri(extension.id); + extension.bugsUrl = uri.toString(true); + } + } catch (e) { extension.hasIssueUriRequestHandler = false; // The issue handler failed so fall back to old issue reporter experience. @@ -806,6 +811,13 @@ export class IssueReporter extends Disposable { show(extensionDataBlock); } + if (fileOnExtension && selectedExtension?.command?.data) { + const data = selectedExtension?.command?.data; + (extensionDataTextArea as HTMLElement).innerText = data.toString(); + (extensionDataTextArea as HTMLTextAreaElement).readOnly = true; + show(extensionDataBlock); + } + if (issueType === IssueType.Bug) { if (!fileOnMarketplace) { show(blockContainer); @@ -893,8 +905,9 @@ export class IssueReporter extends Disposable { } private async createIssue(): Promise { - const hasUri = this.issueReporterModel.getData().selectedExtension?.hasIssueUriRequestHandler; - const hasData = this.issueReporterModel.getData().selectedExtension?.hasIssueDataProviders; + const selectedExtension = this.issueReporterModel.getData().selectedExtension; + const hasUri = selectedExtension?.hasIssueUriRequestHandler; + const hasData = selectedExtension?.hasIssueDataProviders; // Short circuit if the extension provides a custom issue handler if (hasUri && !hasData) { const url = this.getExtensionBugsUrl(); @@ -939,10 +952,15 @@ export class IssueReporter extends Disposable { const issueTitle = (this.getElementById('issue-title')).value; const issueBody = this.issueReporterModel.serialize(); - const issueUrl = hasUri ? this.getExtensionBugsUrl() : this.getIssueUrl(); + let issueUrl = hasUri ? this.getExtensionBugsUrl() : this.getIssueUrl(); if (!issueUrl) { return false; } + + if (selectedExtension?.command?.uri) { + issueUrl = selectedExtension.command.uri; + } + const gitHubDetails = this.parseGitHubUrl(issueUrl); if (this.configuration.data.githubAccessToken && gitHubDetails) { return this.submitToGitHub(issueTitle, issueBody, gitHubDetails); @@ -1145,17 +1163,22 @@ export class IssueReporter extends Disposable { } this.addEventListener('extension-selector', 'change', async (e: Event) => { + this.clearExtensionData(); const selectedExtensionId = (e.target).value; const extensions = this.issueReporterModel.getData().allExtensions; const matches = extensions.filter(extension => extension.id === selectedExtensionId); if (matches.length) { + this.issueReporterModel.update({ selectedExtension: matches[0] }); + const selectedExtension = this.issueReporterModel.getData().selectedExtension; + if (selectedExtension) { + selectedExtension.command = undefined; + } this.updateExtensionStatus(matches[0]); } else { this.issueReporterModel.update({ selectedExtension: undefined }); this.clearSearchResults(); this.validateSelectedExtension(); } - }); } @@ -1164,8 +1187,36 @@ export class IssueReporter extends Disposable { }); } + private clearExtensionData(): void { + this.issueReporterModel.update({ extensionData: undefined }); + this.configuration.data.command = undefined; + } + private async updateExtensionStatus(extension: IssueReporterExtensionData) { this.issueReporterModel.update({ selectedExtension: extension }); + if (this.configuration.data.command) { + const template = this.configuration.data.command.template; + if (template) { + const descriptionTextArea = this.getElementById('description')!; + const descriptionText = (descriptionTextArea as HTMLTextAreaElement).value; + if (descriptionText === '' || !descriptionText.includes(template.toString())) { + const fullTextArea = descriptionText + (descriptionText === '' ? '' : '\n') + template.toString(); + (descriptionTextArea as HTMLTextAreaElement).value = fullTextArea; + this.issueReporterModel.update({ issueDescription: fullTextArea }); + } + } + const data = this.configuration.data.command.data; + if (data) { + const extensionDataBlock = mainWindow.document.querySelector('.block-extension-data')!; + show(extensionDataBlock); + this.issueReporterModel.update({ extensionData: data }); + } + + const uri = this.configuration.data.command.uri; + if (uri) { + this.updateIssueReporterUri(extension); + } + } // if extension does not have provider/handles, will check for either. If extension is already active, IPC will return [false, false] and will proceed as normal. if (!extension.hasIssueDataProviders && !extension.hasIssueUriRequestHandler) { @@ -1198,8 +1249,6 @@ export class IssueReporter extends Disposable { // then update this this.updateIssueReporterUri(extension); - // reset to false so issue url is updated, but won't be affected later. - // extension.hasIssueUriRequestHandler = false; } else if (extension.hasIssueUriRequestHandler) { this.updateIssueReporterUri(extension); } else if (extension.hasIssueDataProviders) { @@ -1222,7 +1271,7 @@ export class IssueReporter extends Disposable { this.removeLoading(iconElement); } else { this.validateSelectedExtension(); - this.issueReporterModel.update({ extensionData: undefined }); + this.issueReporterModel.update({ extensionData: extension.command?.data ?? undefined }); const title = (this.getElementById('issue-title')).value; this.searchExtensionIssues(title); } diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index 93a1f412623..8d9adda1019 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -57,7 +57,11 @@ export interface IssueReporterExtensionData { extensionTemplate?: string; hasIssueUriRequestHandler?: boolean; hasIssueDataProviders?: boolean; - command?: boolean; + command?: { + data?: string; + template?: string; + uri?: string; + }; } export interface IssueReporterData extends WindowData { @@ -71,7 +75,11 @@ export interface IssueReporterData extends WindowData { githubAccessToken: string; readonly issueTitle?: string; readonly issueBody?: string; - readonly command?: boolean; + command?: { + data?: string; + template?: string; + uri?: string; + }; } export interface ISettingSearchResult { From 800e60abdfb1675a59520bac6a9473bc380d49ea Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Mon, 22 Jan 2024 16:38:55 -0800 Subject: [PATCH 0545/1897] Check if should sync model before extension host (#203096) Fix #199385 -- check if should sync model before eh --- .../editor/contrib/wordHighlighter/browser/wordHighlighter.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts index f7bd8e2eede..10b5069c80c 100644 --- a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts @@ -24,7 +24,7 @@ import { IDiffEditor, IEditorContribution, IEditorDecorationsCollection } from ' import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; import { DocumentHighlight, DocumentHighlightKind, DocumentHighlightProvider, MultiDocumentHighlightProvider } from 'vs/editor/common/languages'; -import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; +import { IModelDeltaDecoration, ITextModel, shouldSynchronizeModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { getHighlightDecorationOptions } from 'vs/editor/contrib/wordHighlighter/browser/highlightDecorations'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -65,6 +65,8 @@ export function getOccurrencesAcrossMultipleModels(registry: LanguageFeatureRegi // (good = none empty array) return first | null | undefined>(orderedByScore.map(provider => () => { const filteredModels = otherModels.filter(otherModel => { + return shouldSynchronizeModel(otherModel); + }).filter(otherModel => { return score(provider.selector, otherModel.uri, otherModel.getLanguageId(), true, undefined, undefined) > 0; }); From c1cbd4909947fd3b3ba5212ea22f550be8d7cc67 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 23 Jan 2024 09:56:18 +0100 Subject: [PATCH 0546/1897] Editor scroll position jumps up when returning to editor (#203013) Fixes #202811 --- .../contrib/comments/browser/commentThreadZoneWidget.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index 4784d1c38c5..e5ae9040d50 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -25,6 +25,7 @@ import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { commentThreadStateBackgroundColorVar, commentThreadStateColorVar, getCommentThreadStateBorderColor } from 'vs/workbench/contrib/comments/browser/commentColors'; import { peekViewBorder } from 'vs/editor/contrib/peekView/browser/peekView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; function getCommentThreadWidgetStateColor(thread: languages.CommentThreadState | undefined, theme: IColorTheme): Color | undefined { return getCommentThreadStateBorderColor(thread, theme) ?? theme.getColor(peekViewBorder); @@ -453,7 +454,9 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentThreadWidget.focusCommentEditor(); } + const capture = StableEditorScrollState.capture(this.editor); this._relayout(computedLinesNumber); + capture.restore(this.editor); } } From 6444de2424eff9630e0e2547de8d38559a45667d Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Tue, 23 Jan 2024 12:07:15 +0100 Subject: [PATCH 0547/1897] mnemonicTitle --- src/vs/workbench/browser/actions/listCommands.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index 1c84f2f9f3e..e4482953dd8 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -21,6 +21,7 @@ import { AbstractTree, TreeFindMatchType, TreeFindMode } from 'vs/base/browser/u import { isActiveElement } from 'vs/base/browser/dom'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { localize } from 'vs/nls'; function ensureDOMFocus(widget: ListWidget | undefined): void { // it can happen that one of the commands is executed while @@ -911,7 +912,11 @@ registerAction2(class ToggleStickyScroll extends Action2 { constructor() { super({ id: 'tree.toggleStickyScroll', - title: { value: 'Toggle Tree Sticky Scroll', original: 'Toggle Tree Sticky Scroll' }, + title: { + value: localize('toggleTreeStickyScroll', "Toggle Tree Sticky Scroll"), + mnemonicTitle: localize({ key: 'mitoggleTreeStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Toggle Tree Sticky Scroll"), + original: 'Toggle Tree Sticky Scroll', + }, category: 'View', f1: true }); From 61a80a31d4d39fb0aff146341e2eae93fd847a88 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Tue, 23 Jan 2024 12:09:40 +0100 Subject: [PATCH 0548/1897] editor sticky scroll --- .../contrib/stickyScroll/browser/stickyScrollActions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollActions.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollActions.ts index ece8daa4e51..e50c0f97c50 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollActions.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollActions.ts @@ -21,9 +21,9 @@ export class ToggleStickyScroll extends Action2 { super({ id: 'editor.action.toggleStickyScroll', title: { - value: localize('toggleStickyScroll', "Toggle Sticky Scroll"), - mnemonicTitle: localize({ key: 'mitoggleStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Toggle Sticky Scroll"), - original: 'Toggle Sticky Scroll', + value: localize('toggleEditorStickyScroll', "Toggle Editor Sticky Scroll"), + mnemonicTitle: localize({ key: 'mitoggleStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Toggle Editor Sticky Scroll"), + original: 'Toggle Editor Sticky Scroll', }, category: Categories.View, toggled: { From 4b0d700f7a934912e21295dfd16d82c0cb5fd13d Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 23 Jan 2024 13:00:55 +0100 Subject: [PATCH 0549/1897] [json/css/html] Update dependencies (#203084) * update json/css/html * fix typing * revert to vscode-languageclient@9.0.1 --- extensions/css-language-features/package.json | 2 +- .../css-language-features/server/package.json | 6 +- .../server/src/languageModelCache.ts | 2 +- .../css-language-features/server/yarn.lock | 70 +++++++++++-------- extensions/css-language-features/yarn.lock | 8 +-- .../html-language-features/package.json | 2 +- .../server/package.json | 8 +-- .../html-language-features/server/yarn.lock | 63 +++++++++-------- extensions/html-language-features/yarn.lock | 8 +-- .../json-language-features/package.json | 2 +- .../server/package.json | 8 +-- .../json-language-features/server/yarn.lock | 65 +++++++++-------- extensions/json-language-features/yarn.lock | 8 +-- 13 files changed, 137 insertions(+), 115 deletions(-) diff --git a/extensions/css-language-features/package.json b/extensions/css-language-features/package.json index e105c0090ee..06e58ee064d 100644 --- a/extensions/css-language-features/package.json +++ b/extensions/css-language-features/package.json @@ -994,7 +994,7 @@ ] }, "dependencies": { - "vscode-languageclient": "^9.0.1", + "vscode-languageclient": "9.0.1", "vscode-uri": "^3.0.8" }, "devDependencies": { diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index 523e66333bc..b5ff75c7686 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -10,9 +10,9 @@ "main": "./out/node/cssServerMain", "browser": "./dist/browser/cssServerMain", "dependencies": { - "@vscode/l10n": "^0.0.16", - "vscode-css-languageservice": "^6.2.11", - "vscode-languageserver": "^9.0.1", + "@vscode/l10n": "^0.0.18", + "vscode-css-languageservice": "^6.2.12", + "vscode-languageserver": "^9.0.2-next.1", "vscode-uri": "^3.0.8" }, "devDependencies": { diff --git a/extensions/css-language-features/server/src/languageModelCache.ts b/extensions/css-language-features/server/src/languageModelCache.ts index f39eada6f44..df498ed9f20 100644 --- a/extensions/css-language-features/server/src/languageModelCache.ts +++ b/extensions/css-language-features/server/src/languageModelCache.ts @@ -15,7 +15,7 @@ export function getLanguageModelCache(maxEntries: number, cleanupIntervalTime let languageModels: { [uri: string]: { version: number; languageId: string; cTime: number; languageModel: T } } = {}; let nModels = 0; - let cleanupInterval: NodeJS.Timer | undefined = undefined; + let cleanupInterval: NodeJS.Timeout | undefined = undefined; if (cleanupIntervalTimeInSec > 0) { cleanupInterval = setInterval(() => { const cutoffTime = Date.now() - cleanupIntervalTimeInSec * 1000; diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index 28a355c3b01..2e2a0c9a6ca 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -8,37 +8,44 @@ integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== "@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== - -"@vscode/l10n@^0.0.16": - version "0.0.16" - resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.16.tgz#f075db346d0b08419a12540171b230bd803c42be" - integrity sha512-JT5CvrIYYCrmB+dCana8sUqJEcGB1ZDXNLMQ2+42bW995WmNoenijWMUdZfwmuQUTQcEVVIa2OecZzTYWUW9Cg== - -vscode-css-languageservice@^6.2.11: - version "6.2.11" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.2.11.tgz#40de8b34adb6d68ee96795ffb34e34d99fc26801" - integrity sha512-qn49Wa6K94LnizpVxmlYrcPf1Cb36gq1nNueW0COhi4shylXBzET5wuDbH8ZWQlJD0HM5Mmnn7WE9vQVVs+ULA== + version "18.19.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.8.tgz#c1e42b165e5a526caf1f010747e0522cb2c9c36a" + integrity sha512-g1pZtPhsvGVTwmeVoexWZLTQaOvXwoSq//pTL0DHeNzUDrFnir4fgETdhjhIxjVnN+hKOuh98+E1eMLnUXstFg== dependencies: - "@vscode/l10n" "^0.0.16" + undici-types "~5.26.4" + +"@vscode/l10n@^0.0.18": + version "0.0.18" + resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.18.tgz#916d3a5e960dbab47c1c56f58a7cb5087b135c95" + integrity sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +vscode-css-languageservice@^6.2.12: + version "6.2.12" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.2.12.tgz#f8f9f335fb4b433f557c51c62e687b4f62c0c786" + integrity sha512-PS9r7HgNjqzRl3v91sXpCyZPc8UDotNo6gntFNtGCKPhGA9Frk7g/VjX1Mbv3F00pn56D+rxrFzR9ep4cawOgA== + dependencies: + "@vscode/l10n" "^0.0.18" vscode-languageserver-textdocument "^1.0.11" vscode-languageserver-types "3.17.5" vscode-uri "^3.0.8" -vscode-jsonrpc@8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9" - integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA== +vscode-jsonrpc@8.2.1-next.1: + version "8.2.1-next.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1-next.1.tgz#52e1091907b56759114fabac803b18c44a48f2a9" + integrity sha512-L+DYtdUtqUXGpyMgHqer6IBKvFFhl/1ToiMmCmG85LYHuuX0jllHMz77MYt0RicakoYY+Lq1yLK6Qj3YBqgzDQ== -vscode-languageserver-protocol@3.17.5: - version "3.17.5" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz#864a8b8f390835572f4e13bd9f8313d0e3ac4bea" - integrity sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg== +vscode-languageserver-protocol@3.17.6-next.1: + version "3.17.6-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.1.tgz#5d87f7f708667cf04dbefb5c860901df7d01ebc1" + integrity sha512-2npXUc8oe/fb9Bjcwm2HTWYZXyCbW4NTo7jkOrEciGO+/LfWbSMgqZ6PwKWgqUkgCbkPxQHNjoMqr9ol/Ehjgg== dependencies: - vscode-jsonrpc "8.2.0" - vscode-languageserver-types "3.17.5" + vscode-jsonrpc "8.2.1-next.1" + vscode-languageserver-types "3.17.6-next.1" vscode-languageserver-textdocument@^1.0.11: version "1.0.11" @@ -50,12 +57,17 @@ vscode-languageserver-types@3.17.5: resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a" integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg== -vscode-languageserver@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz#500aef82097eb94df90d008678b0b6b5f474015b" - integrity sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g== +vscode-languageserver-types@3.17.6-next.1: + version "3.17.6-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.1.tgz#a3d2006d52f7d4026ea67668113ec16c73cd8f1d" + integrity sha512-7xVc/xLtNhKuCKX0mINT6mFUrUuRz0EinhwPGT8Gtsv2hlo+xJb5NKbiGailcWa1/T5e4dr5Pb2MfGchHreHAA== + +vscode-languageserver@^9.0.2-next.1: + version "9.0.2-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-9.0.2-next.1.tgz#cc9bbd66716346aa761e5bafa19d64559ab4e030" + integrity sha512-xySldxoHIcKXtxoI0LqRX3QcTdOVFt1SeHV0hyPq28p7xGPqWxUPcmTcfIqYdHefXG22nd8DQbGWOEe52yu08A== dependencies: - vscode-languageserver-protocol "3.17.5" + vscode-languageserver-protocol "3.17.6-next.1" vscode-uri@^3.0.8: version "3.0.8" diff --git a/extensions/css-language-features/yarn.lock b/extensions/css-language-features/yarn.lock index 88aff84e4de..794ee50acbe 100644 --- a/extensions/css-language-features/yarn.lock +++ b/extensions/css-language-features/yarn.lock @@ -27,9 +27,9 @@ lru-cache@^6.0.0: yallist "^4.0.0" minimatch@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" - integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" @@ -45,7 +45,7 @@ vscode-jsonrpc@8.2.0: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9" integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA== -vscode-languageclient@^9.0.1: +vscode-languageclient@9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz#cdfe20267726c8d4db839dc1e9d1816e1296e854" integrity sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA== diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index 58b9cdfdb2a..347af818273 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -259,7 +259,7 @@ }, "dependencies": { "@vscode/extension-telemetry": "^0.9.0", - "vscode-languageclient": "^9.0.1", + "vscode-languageclient": "9.0.1", "vscode-uri": "^3.0.8" }, "devDependencies": { diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 25b8024bd18..13727939f3c 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -9,10 +9,10 @@ }, "main": "./out/node/htmlServerMain", "dependencies": { - "@vscode/l10n": "^0.0.16", - "vscode-css-languageservice": "^6.2.10", - "vscode-html-languageservice": "^5.1.1", - "vscode-languageserver": "^9.0.1", + "@vscode/l10n": "^0.0.18", + "vscode-css-languageservice": "^6.2.12", + "vscode-html-languageservice": "^5.1.2", + "vscode-languageserver": "^9.0.2-next.1", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index 6c052559b50..ba17b14513f 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -12,43 +12,43 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== -"@vscode/l10n@^0.0.16": - version "0.0.16" - resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.16.tgz#f075db346d0b08419a12540171b230bd803c42be" - integrity sha512-JT5CvrIYYCrmB+dCana8sUqJEcGB1ZDXNLMQ2+42bW995WmNoenijWMUdZfwmuQUTQcEVVIa2OecZzTYWUW9Cg== +"@vscode/l10n@^0.0.18": + version "0.0.18" + resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.18.tgz#916d3a5e960dbab47c1c56f58a7cb5087b135c95" + integrity sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ== -vscode-css-languageservice@^6.2.10: - version "6.2.10" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.2.10.tgz#ba114d92d634df7b45f572a6eaaccd29cbde5d9d" - integrity sha512-sYUZPku4mQ06AWGCbMyjv2tdR6juBW6hTbVPFwbJvNVzdtEfBioQOgkdXg7yMJNWnXkvWSU1FL2kb4Vxu5Cdyw== +vscode-css-languageservice@^6.2.12: + version "6.2.12" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.2.12.tgz#f8f9f335fb4b433f557c51c62e687b4f62c0c786" + integrity sha512-PS9r7HgNjqzRl3v91sXpCyZPc8UDotNo6gntFNtGCKPhGA9Frk7g/VjX1Mbv3F00pn56D+rxrFzR9ep4cawOgA== dependencies: - "@vscode/l10n" "^0.0.16" + "@vscode/l10n" "^0.0.18" vscode-languageserver-textdocument "^1.0.11" vscode-languageserver-types "3.17.5" vscode-uri "^3.0.8" -vscode-html-languageservice@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-5.1.1.tgz#8e56f7e11c1e3f4a9d56de0f97badea9296f4e04" - integrity sha512-JenrspIIG/Q+93R6G3L6HdK96itSisMynE0glURqHpQbL3dKAKzdm8L40lAHNkwJeBg+BBPpAshZKv/38onrTQ== +vscode-html-languageservice@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-5.1.2.tgz#8309fa7f319c873af11cf23ddbba4e68d6c42e2c" + integrity sha512-wkWfEx/IIR3s2P5yD4aTGHiOb8IAzFxgkSt1uSC3itJ4oDAm23yG7o0L29JljUdnXDDgLafPAvhv8A2I/8riHw== dependencies: - "@vscode/l10n" "^0.0.16" + "@vscode/l10n" "^0.0.18" vscode-languageserver-textdocument "^1.0.11" vscode-languageserver-types "^3.17.5" vscode-uri "^3.0.8" -vscode-jsonrpc@8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9" - integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA== +vscode-jsonrpc@8.2.1-next.1: + version "8.2.1-next.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1-next.1.tgz#52e1091907b56759114fabac803b18c44a48f2a9" + integrity sha512-L+DYtdUtqUXGpyMgHqer6IBKvFFhl/1ToiMmCmG85LYHuuX0jllHMz77MYt0RicakoYY+Lq1yLK6Qj3YBqgzDQ== -vscode-languageserver-protocol@3.17.5: - version "3.17.5" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz#864a8b8f390835572f4e13bd9f8313d0e3ac4bea" - integrity sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg== +vscode-languageserver-protocol@3.17.6-next.1: + version "3.17.6-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.1.tgz#5d87f7f708667cf04dbefb5c860901df7d01ebc1" + integrity sha512-2npXUc8oe/fb9Bjcwm2HTWYZXyCbW4NTo7jkOrEciGO+/LfWbSMgqZ6PwKWgqUkgCbkPxQHNjoMqr9ol/Ehjgg== dependencies: - vscode-jsonrpc "8.2.0" - vscode-languageserver-types "3.17.5" + vscode-jsonrpc "8.2.1-next.1" + vscode-languageserver-types "3.17.6-next.1" vscode-languageserver-textdocument@^1.0.11: version "1.0.11" @@ -60,12 +60,17 @@ vscode-languageserver-types@3.17.5, vscode-languageserver-types@^3.17.5: resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a" integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg== -vscode-languageserver@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz#500aef82097eb94df90d008678b0b6b5f474015b" - integrity sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g== +vscode-languageserver-types@3.17.6-next.1: + version "3.17.6-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.1.tgz#a3d2006d52f7d4026ea67668113ec16c73cd8f1d" + integrity sha512-7xVc/xLtNhKuCKX0mINT6mFUrUuRz0EinhwPGT8Gtsv2hlo+xJb5NKbiGailcWa1/T5e4dr5Pb2MfGchHreHAA== + +vscode-languageserver@^9.0.2-next.1: + version "9.0.2-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-9.0.2-next.1.tgz#cc9bbd66716346aa761e5bafa19d64559ab4e030" + integrity sha512-xySldxoHIcKXtxoI0LqRX3QcTdOVFt1SeHV0hyPq28p7xGPqWxUPcmTcfIqYdHefXG22nd8DQbGWOEe52yu08A== dependencies: - vscode-languageserver-protocol "3.17.5" + vscode-languageserver-protocol "3.17.6-next.1" vscode-uri@^3.0.8: version "3.0.8" diff --git a/extensions/html-language-features/yarn.lock b/extensions/html-language-features/yarn.lock index 4d096a226ed..c7e1dee98ac 100644 --- a/extensions/html-language-features/yarn.lock +++ b/extensions/html-language-features/yarn.lock @@ -129,9 +129,9 @@ lru-cache@^6.0.0: yallist "^4.0.0" minimatch@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" - integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" @@ -147,7 +147,7 @@ vscode-jsonrpc@8.2.0: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9" integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA== -vscode-languageclient@^9.0.1: +vscode-languageclient@9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz#cdfe20267726c8d4db839dc1e9d1816e1296e854" integrity sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA== diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 1af14170338..46a7603818a 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -160,7 +160,7 @@ "dependencies": { "@vscode/extension-telemetry": "^0.9.0", "request-light": "^0.7.0", - "vscode-languageclient": "^9.0.1" + "vscode-languageclient": "9.0.1" }, "devDependencies": { "@types/node": "18.x" diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index 9ebfd2c94ad..bddc40acd34 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -12,11 +12,11 @@ }, "main": "./out/node/jsonServerMain", "dependencies": { - "@vscode/l10n": "^0.0.16", - "jsonc-parser": "^3.2.0", + "@vscode/l10n": "^0.0.18", + "jsonc-parser": "^3.2.1", "request-light": "^0.7.0", - "vscode-json-languageservice": "^5.3.7", - "vscode-languageserver": "^9.0.1", + "vscode-json-languageservice": "^5.3.9", + "vscode-languageserver": "^9.0.2-next.1", "vscode-uri": "^3.0.8" }, "devDependencies": { diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 45d68ed3468..fc1159b9160 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -12,61 +12,66 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== -"@vscode/l10n@^0.0.16": - version "0.0.16" - resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.16.tgz#f075db346d0b08419a12540171b230bd803c42be" - integrity sha512-JT5CvrIYYCrmB+dCana8sUqJEcGB1ZDXNLMQ2+42bW995WmNoenijWMUdZfwmuQUTQcEVVIa2OecZzTYWUW9Cg== +"@vscode/l10n@^0.0.18": + version "0.0.18" + resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.18.tgz#916d3a5e960dbab47c1c56f58a7cb5087b135c95" + integrity sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ== -jsonc-parser@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" - integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== +jsonc-parser@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz#031904571ccf929d7670ee8c547545081cb37f1a" + integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA== request-light@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.7.0.tgz#885628bb2f8040c26401ebf258ec51c4ae98ac2a" integrity sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q== -vscode-json-languageservice@^5.3.7: - version "5.3.7" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-5.3.7.tgz#63eec2448e5cfcde2eabce39c331681e6158f45e" - integrity sha512-jdDggN2SLMQw4C/tLr11v6/OK4cMVGy7tbyZRHQvukQ6lcflY3UV+ZMkmwHKCqXz2TmxkjQb536eJW6JMEVeew== +vscode-json-languageservice@^5.3.9: + version "5.3.9" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-5.3.9.tgz#512463ed580237d958df9280b43da9e3b5b621ce" + integrity sha512-0IcymTw0ZYX5Zcx+7KLLwTRvg0FzXUVnM1hrUH+sPhqEX0fHGg2h5UUOSp1f8ydGS7/xxzlFI3TR01yaHs6Y0Q== dependencies: - "@vscode/l10n" "^0.0.16" - jsonc-parser "^3.2.0" + "@vscode/l10n" "^0.0.18" + jsonc-parser "^3.2.1" vscode-languageserver-textdocument "^1.0.11" vscode-languageserver-types "^3.17.5" vscode-uri "^3.0.8" -vscode-jsonrpc@8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9" - integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA== +vscode-jsonrpc@8.2.1-next.1: + version "8.2.1-next.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1-next.1.tgz#52e1091907b56759114fabac803b18c44a48f2a9" + integrity sha512-L+DYtdUtqUXGpyMgHqer6IBKvFFhl/1ToiMmCmG85LYHuuX0jllHMz77MYt0RicakoYY+Lq1yLK6Qj3YBqgzDQ== -vscode-languageserver-protocol@3.17.5: - version "3.17.5" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz#864a8b8f390835572f4e13bd9f8313d0e3ac4bea" - integrity sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg== +vscode-languageserver-protocol@3.17.6-next.1: + version "3.17.6-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.1.tgz#5d87f7f708667cf04dbefb5c860901df7d01ebc1" + integrity sha512-2npXUc8oe/fb9Bjcwm2HTWYZXyCbW4NTo7jkOrEciGO+/LfWbSMgqZ6PwKWgqUkgCbkPxQHNjoMqr9ol/Ehjgg== dependencies: - vscode-jsonrpc "8.2.0" - vscode-languageserver-types "3.17.5" + vscode-jsonrpc "8.2.1-next.1" + vscode-languageserver-types "3.17.6-next.1" vscode-languageserver-textdocument@^1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz#0822a000e7d4dc083312580d7575fe9e3ba2e2bf" integrity sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA== -vscode-languageserver-types@3.17.5, vscode-languageserver-types@^3.17.5: +vscode-languageserver-types@3.17.6-next.1: + version "3.17.6-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.1.tgz#a3d2006d52f7d4026ea67668113ec16c73cd8f1d" + integrity sha512-7xVc/xLtNhKuCKX0mINT6mFUrUuRz0EinhwPGT8Gtsv2hlo+xJb5NKbiGailcWa1/T5e4dr5Pb2MfGchHreHAA== + +vscode-languageserver-types@^3.17.5: version "3.17.5" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a" integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg== -vscode-languageserver@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz#500aef82097eb94df90d008678b0b6b5f474015b" - integrity sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g== +vscode-languageserver@^9.0.2-next.1: + version "9.0.2-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-9.0.2-next.1.tgz#cc9bbd66716346aa761e5bafa19d64559ab4e030" + integrity sha512-xySldxoHIcKXtxoI0LqRX3QcTdOVFt1SeHV0hyPq28p7xGPqWxUPcmTcfIqYdHefXG22nd8DQbGWOEe52yu08A== dependencies: - vscode-languageserver-protocol "3.17.5" + vscode-languageserver-protocol "3.17.6-next.1" vscode-uri@^3.0.8: version "3.0.8" diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index be29a81b717..9f29ee2197a 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -129,9 +129,9 @@ lru-cache@^6.0.0: yallist "^4.0.0" minimatch@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" - integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" @@ -152,7 +152,7 @@ vscode-jsonrpc@8.2.0: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9" integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA== -vscode-languageclient@^9.0.1: +vscode-languageclient@9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz#cdfe20267726c8d4db839dc1e9d1816e1296e854" integrity sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA== From 523725a022af3f539131487df0e06bfe0b784b68 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 23 Jan 2024 13:13:55 +0100 Subject: [PATCH 0550/1897] Run OSS Tool (#203132) --- cglicenses.json | 54 ++--------- cli/ThirdPartyNotices.txt | 182 ++++++++++++++++++++++++++++++++++++-- package.json | 2 +- 3 files changed, 182 insertions(+), 56 deletions(-) diff --git a/cglicenses.json b/cglicenses.json index 3d4e0f80443..d61164acd3d 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -545,32 +545,8 @@ ] }, { - "name": "vscode-webview-tools", - "fullLicenseText": [ - "MIT License", - "", - "Copyright (c) 2020 Connor Peet", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy", - "of this software and associated documentation files (the 'Software'), to deal", - "in the Software without restriction, including without limitation the rights", - "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", - "copies of the Software, and to permit persons to whom the Software is", - "furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in all", - "copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", - "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", - "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", - "SOFTWARE." - ] - }, - { + // Reason: missing repository property on package.json + // Issue: https://github.com/microsoft/vscode-v8-heap-tools/issues/14 "name": "@vscode/v8-heap-parser", "fullLicenseText": [ "The code in this package is built upon that in the Chrome devtools", @@ -607,29 +583,11 @@ ] }, { + // Reason: missing copyright statement + // Issue: https://github.com/qiao/heap.js/issues/33 "name": "heap", - "fullLicenseText": [ - "The MIT License", - "", - "Copyright (c) Xueqiao (Joe) Xu", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy", - "of this software and associated documentation files (the 'Software'), to deal", - "in the Software without restriction, including without limitation the rights", - "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", - "copies of the Software, and to permit persons to whom the Software is", - "furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in", - "all copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", - "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", - "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN", - "THE SOFTWARE." + "prependLicenseText": [ + "Copyright (c) heap.js authors" ] } ] diff --git a/cli/ThirdPartyNotices.txt b/cli/ThirdPartyNotices.txt index a867935f2b4..6ec82c8b541 100644 --- a/cli/ThirdPartyNotices.txt +++ b/cli/ThirdPartyNotices.txt @@ -10657,7 +10657,31 @@ DEALINGS IN THE SOFTWARE. xdg-home 1.0.0 - MIT https://github.com/zeenix/xdg-home -LICENSE-MIT +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- @@ -10679,7 +10703,31 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI zbus 3.13.1 - MIT https://github.com/dbus2/zbus/ -LICENSE-MIT +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- @@ -10687,7 +10735,31 @@ LICENSE-MIT zbus_macros 3.13.1 - MIT https://github.com/dbus2/zbus/ -LICENSE-MIT +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- @@ -10695,7 +10767,31 @@ LICENSE-MIT zbus_names 2.5.1 - MIT https://github.com/dbus2/zbus/ -LICENSE-MIT +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- @@ -10785,7 +10881,31 @@ SOFTWARE. zvariant 3.14.0 - MIT https://github.com/dbus2/zbus/ -LICENSE-MIT +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- @@ -10793,7 +10913,31 @@ LICENSE-MIT zvariant_derive 3.14.0 - MIT https://github.com/dbus2/zbus/ -LICENSE-MIT +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- @@ -10801,5 +10945,29 @@ LICENSE-MIT zvariant_utils 1.0.1 - MIT https://github.com/dbus2/zbus/ -LICENSE-MIT +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. --------------------------------------------------------- \ No newline at end of file diff --git a/package.json b/package.json index c632d597fa2..e9cd097d3e8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.86.0", - "distro": "be7d9acdb535bf4095cbbb28c88cfd05a2a11533", + "distro": "2124c72ef2dcad044b30293f7548a7cc63f113fe", "author": { "name": "Microsoft Corporation" }, From bef67be6c6a811efbbf110e91bf908a37b7d5e3c Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 23 Jan 2024 11:20:38 -0300 Subject: [PATCH 0551/1897] Add basic tooltip to file vars (#203074) --- .../contrib/chat/browser/chatListRenderer.ts | 8 ++----- .../chatMarkdownDecorationsRenderer.ts | 23 ++++++++++++++----- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 7d5eaf14e4a..3336d00c788 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -40,11 +40,9 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { FileKind, FileType } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { WorkbenchCompressibleAsyncDataTree, WorkbenchList } from 'vs/platform/list/browser/listService'; import { ILogService } from 'vs/platform/log/common/log'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IProductService } from 'vs/platform/product/common/productService'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -140,9 +138,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.codeBlocksByResponseId.delete(element.id))); } - walkTreeAndAnnotateReferenceLinks(result.element, this.keybindingService); + this.instantiationService.invokeFunction(acc => walkTreeAndAnnotateReferenceLinks(acc, result.element)); orderedDisposablesList.reverse().forEach(d => disposables.add(d)); return { diff --git a/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts index 8c449efcafe..6a72ca4a951 100644 --- a/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts @@ -10,33 +10,43 @@ import { basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { IRange } from 'vs/editor/common/core/range'; import { Location } from 'vs/editor/common/languages'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ILabelService } from 'vs/platform/label/common/label'; import { IChatProgressRenderableResponseContent, IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; -import { ChatRequestTextPart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatRequestDynamicVariablePart, ChatRequestTextPart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatAgentMarkdownContentWithVulnerability, IChatAgentVulnerabilityDetails, IChatContentInlineReference } from 'vs/workbench/contrib/chat/common/chatService'; const variableRefUrl = 'http://_vscodedecoration_'; -export function convertParsedRequestToMarkdown(parsedRequest: IParsedChatRequest): string { +export function convertParsedRequestToMarkdown(accessor: ServicesAccessor, parsedRequest: IParsedChatRequest): string { let result = ''; for (const part of parsedRequest.parts) { if (part instanceof ChatRequestTextPart) { result += part.text; } else { - result += `[${part.text}](${variableRefUrl})`; + const labelService = accessor.get(ILabelService); + const uri = part instanceof ChatRequestDynamicVariablePart && part.data.map(d => d.value).find((d): d is URI => d instanceof URI) + || undefined; + const title = uri ? labelService.getUriLabel(uri, { relative: true }) : ''; + + result += `[${part.text}](${variableRefUrl}${title})`; } } return result; } -export function walkTreeAndAnnotateReferenceLinks(element: HTMLElement, keybindingService: IKeybindingService): void { +export function walkTreeAndAnnotateReferenceLinks(accessor: ServicesAccessor, element: HTMLElement): void { + const keybindingService = accessor.get(IKeybindingService); + element.querySelectorAll('a').forEach(a => { const href = a.getAttribute('data-href'); if (href) { if (href.startsWith(variableRefUrl)) { + const title = href.slice(variableRefUrl.length); a.parentElement!.replaceChild( - renderResourceWidget(a.textContent!), + renderResourceWidget(a.textContent!, title), a); } else if (href.startsWith(contentRefUrl)) { renderFileWidget(href, a); @@ -60,9 +70,10 @@ function injectKeybindingHint(a: HTMLAnchorElement, href: string, keybindingServ } } -function renderResourceWidget(name: string): HTMLElement { +function renderResourceWidget(name: string, title: string): HTMLElement { const container = dom.$('span.chat-resource-widget'); const alias = dom.$('span', undefined, name); + alias.title = title; container.appendChild(alias); return container; } From 09f06adf6519b91ebccf537a8f5f0c1a6048a80a Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 23 Jan 2024 15:38:39 +0100 Subject: [PATCH 0552/1897] Don't hide activity actions at top (#203142) dont hide activity actions top --- .../browser/menuEntryActionViewItem.ts | 3 ++- .../browser/parts/titlebar/titlebarActions.ts | 22 ------------------- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 15abc82b4be..8374e100d3c 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -45,7 +45,8 @@ export function createAndFillInActionBarActions( target: IAction[] | { primary: IAction[]; secondary: IAction[] }, primaryGroup?: string | ((actionGroup: string) => boolean), shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean, - useSeparatorsInPrimaryActions?: boolean): void { + useSeparatorsInPrimaryActions?: boolean +): void { const groups = menu.getActions(options); const isPrimaryAction = typeof primaryGroup === 'string' ? (actionGroup: string) => actionGroup === primaryGroup : primaryGroup; diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index f800a48b72d..a528f6ab90c 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -235,28 +235,6 @@ registerAction2(class ToggleEditorActions extends Action2 { } }); -registerAction2(class ToggleActivityBarActions extends Action2 { - static readonly settingsID = `workbench.activityBar.location`; - constructor() { - - super({ - id: `toggle.${ToggleActivityBarActions.settingsID}`, - title: localize('toggle.activityBarActions', 'Activity Bar Actions'), - toggled: ContextKeyExpr.equals(`config.${ToggleActivityBarActions.settingsID}`, 'top'), - menu: [ - { id: MenuId.TitleBarContext, order: 4, when: ContextKeyExpr.notEquals(`config.${ToggleActivityBarActions.settingsID}`, 'side'), group: '2_config' }, - { id: MenuId.TitleBarTitleContext, order: 4, when: ContextKeyExpr.notEquals(`config.${ToggleActivityBarActions.settingsID}`, 'side'), group: '2_config' } - ] - }); - } - - run(accessor: ServicesAccessor, ...args: any[]): void { - const configService = accessor.get(IConfigurationService); - const oldLocation = configService.getValue(ToggleActivityBarActions.settingsID); - configService.updateValue(ToggleActivityBarActions.settingsID, oldLocation === 'top' ? 'hidden' : 'top'); - } -}); - // --- Toolbar actions --- // export const ACCOUNTS_ACTIVITY_TILE_ACTION: IAction = { From 24f792463434920e387fabd13d458c54b20ba497 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 23 Jan 2024 16:31:25 +0100 Subject: [PATCH 0553/1897] remove save-block when stashing session (#203164) also restore save-block when unstashing and fix and issues that would prevent stashing fixes https://github.com/microsoft/vscode-copilot/issues/3654 --- .../inlineChat/browser/inlineChatController.ts | 11 ++++++++--- .../inlineChat/browser/inlineChatSavingServiceImpl.ts | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 977f9b71bdf..ca86cd75143 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -852,6 +852,8 @@ export class InlineChatController implements IEditorContribution { assertType(this._strategy); this._sessionStore.clear(); + // only stash sessions that were not unstashed, not "empty", and not interacted with + const shouldStash = !this._session.isUnstashed && !!this._session.lastExchange && this._session.hunkData.size === this._session.hunkData.pending; let undoCancelEdits: IValidEditOperation[] = []; try { undoCancelEdits = this._strategy.cancel(); @@ -862,8 +864,7 @@ export class InlineChatController implements IEditorContribution { } this._stashedSession.clear(); - if (!this._session.isUnstashed && !!this._session.lastExchange && this._session.hunkData.size === this._session.hunkData.pending) { - // only stash sessions that were not unstashed, not "empty", and not interacted with + if (shouldStash) { this._stashedSession.value = this._inlineChatSessionService.stashSession(this._session, this._editor, undoCancelEdits); } else { this._inlineChatSessionService.releaseSession(this._session); @@ -1142,7 +1143,11 @@ export class InlineChatController implements IEditorContribution { } unstashLastSession(): Session | undefined { - return this._stashedSession.value?.unstash(); + const result = this._stashedSession.value?.unstash(); + if (result) { + this._inlineChatSavingService.markChanged(result); + } + return result; } joinCurrentRun(): Promise | undefined { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts index b68c999bd6c..133923b13fe 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts @@ -55,7 +55,7 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService { @IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService, @ILogService private readonly _logService: ILogService, ) { - this._store.add(_inlineChatSessionService.onDidEndSession(e => { + this._store.add(Event.any(_inlineChatSessionService.onDidEndSession, _inlineChatSessionService.onDidStashSession)(e => { this._sessionData.get(e.session)?.dispose(); })); } From 42c4cb5036b4fe74e8d672e9501d79363a377b09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jan 2024 08:40:51 -0700 Subject: [PATCH 0554/1897] Bump actions/cache from 3 to 4 (#202999) Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/basic.yml | 12 ++++++------ .github/workflows/ci.yml | 16 ++++++++-------- .github/workflows/monaco-editor.yml | 4 ++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index b0f7d8de27c..8448d05c3b8 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -38,7 +38,7 @@ jobs: run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT - name: Cache node modules id: cacheNodeModules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: "**/node_modules" key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }} @@ -48,7 +48,7 @@ jobs: run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - name: Cache yarn directory if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.yarnCacheDirPath.outputs.dir }} key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} @@ -90,7 +90,7 @@ jobs: run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT - name: Cache node modules id: cacheNodeModules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: "**/node_modules" key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }} @@ -100,7 +100,7 @@ jobs: run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - name: Cache yarn directory if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.yarnCacheDirPath.outputs.dir }} key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} @@ -152,7 +152,7 @@ jobs: run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT - name: Cache node modules id: cacheNodeModules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: "**/node_modules" key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }} @@ -162,7 +162,7 @@ jobs: run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - name: Cache yarn directory if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.yarnCacheDirPath.outputs.dir }} key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94a552b5fb8..ee204799cf6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT - name: Cache node_modules archive id: cacheNodeModules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ".build/node_modules_cache" key: "${{ runner.os }}-cacheNodeModulesArchive-${{ steps.nodeModulesCacheKey.outputs.value }}" @@ -49,7 +49,7 @@ jobs: run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - name: Cache yarn directory if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.yarnCacheDirPath.outputs.dir }} key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} @@ -122,7 +122,7 @@ jobs: run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT - name: Cache node modules id: cacheNodeModules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: "**/node_modules" key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }} @@ -132,7 +132,7 @@ jobs: run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - name: Cache yarn directory if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.yarnCacheDirPath.outputs.dir }} key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} @@ -193,7 +193,7 @@ jobs: run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT - name: Cache node modules id: cacheNodeModules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: "**/node_modules" key: ${{ runner.os }}-cacheNodeModulesMacOS-${{ steps.nodeModulesCacheKey.outputs.value }} @@ -203,7 +203,7 @@ jobs: run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - name: Cache yarn directory if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.yarnCacheDirPath.outputs.dir }} key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} @@ -265,7 +265,7 @@ jobs: run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT - name: Cache node modules id: cacheNodeModules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: "**/node_modules" key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }} @@ -275,7 +275,7 @@ jobs: run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - name: Cache yarn directory if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.yarnCacheDirPath.outputs.dir }} key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} diff --git a/.github/workflows/monaco-editor.yml b/.github/workflows/monaco-editor.yml index c62fd2b2697..8c8ae7b743d 100644 --- a/.github/workflows/monaco-editor.yml +++ b/.github/workflows/monaco-editor.yml @@ -29,7 +29,7 @@ jobs: run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT - name: Cache node modules id: cacheNodeModules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: "**/node_modules" key: ${{ runner.os }}-cacheNodeModules20-${{ steps.nodeModulesCacheKey.outputs.value }} @@ -40,7 +40,7 @@ jobs: run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - name: Cache yarn directory if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.yarnCacheDirPath.outputs.dir }} key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} From 39804fae310663346fd29efeb09e68a99bae5e29 Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 23 Jan 2024 16:45:08 +0100 Subject: [PATCH 0555/1897] Refactor Enum Value Descriptions (#203170) fix #203157 --- src/vs/workbench/electron-sandbox/desktop.contribution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 7246962aa75..ffcd7a7a0a5 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -240,8 +240,8 @@ import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from 'vs/platform/window/electron-sand 'type': 'string', 'enum': ['auto', 'windowed', 'never'], 'markdownEnumDescriptions': [ - localize(`window.customTitleBarVisibility.auto`, "Automatically changes custom titlebar visibility."), - localize(`window.customTitleBarVisibility.windowed`, "Hide custom titlebar in full screen. Automatically changes custom titlebar visibility in windowed."), + localize(`window.customTitleBarVisibility.auto`, "Automatically changes custom title bar visibility."), + localize(`window.customTitleBarVisibility.windowed`, "Hide custom titlebar in full screen. When not in full screen, automatically change custom title bar visibility."), localize(`window.customTitleBarVisibility.never`, "Hide custom titlebar when `#window.titleBarStyle#` is set to `native`."), ], 'default': isLinux ? 'never' : 'auto', From 5bebda832a1feb3cfc4e8be0b3d4900a1e2923ab Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 23 Jan 2024 19:44:25 +0100 Subject: [PATCH 0556/1897] Disabling window.zoomPerWindow doesn't restore window zooms (#203209) Disabling window.zoomPerWindow doesn't restore window zooms (fix #203129) --- src/vs/workbench/electron-sandbox/window.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index cfb720593d5..5c7cb75b085 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -332,7 +332,7 @@ export class NativeWindow extends BaseWindow { // Window Zoom this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('window.zoomLevel')) { + if (e.affectsConfiguration('window.zoomLevel') || (e.affectsConfiguration('window.zoomPerWindow') && this.configurationService.getValue('window.zoomPerWindow') === false)) { this.onDidChangeConfiguredWindowZoomLevel(); } else if (e.affectsConfiguration('keyboard.touchbar.enabled') || e.affectsConfiguration('keyboard.touchbar.ignored')) { this.updateTouchbarMenu(); From e523361ab832fe26deea34f242ebddfc6a1e90a3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 23 Jan 2024 20:01:52 +0100 Subject: [PATCH 0557/1897] Auto Save When No Errors does not work consistently (fix #203125) (#203221) --- src/vs/workbench/contrib/files/browser/files.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 4f6e5691daa..0ea210b535d 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -270,7 +270,7 @@ configurationRegistry.registerConfiguration({ 'files.autoSaveWhenNoErrors': { 'type': 'boolean', 'default': false, - 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSaveWhenNoErrors' }, "When enabled, will limit [auto save](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save) of editors to files that have no errors reported in them. Only applies when `#files.autoSave#` is enabled."), + 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSaveWhenNoErrors' }, "When enabled, will limit [auto save](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save) of editors to files that have no errors reported in them at the time the auto save is triggered. Only applies when `#files.autoSave#` is enabled."), scope: ConfigurationScope.LANGUAGE_OVERRIDABLE }, 'files.watcherExclude': { From 3aa161859aca5bcca03fddd6f1e18c19b5071e2f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 23 Jan 2024 20:09:14 +0100 Subject: [PATCH 0558/1897] Wording " speech recognized" (fix #203171) (#203226) --- .../contrib/chat/electron-sandbox/actions/voiceChatActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index a68d2a95678..1666adf085d 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -852,7 +852,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe localize('voice.keywordActivation.inlineChat', "Keyword activation is enabled and listening for 'Hey Code' to start a voice chat session in the active editor."), localize('voice.keywordActivation.chatInContext', "Keyword activation is enabled and listening for 'Hey Code' to start a voice chat session in the active editor or view depending on keyboard focus.") ], - 'description': localize('voice.keywordActivation', "Controls whether the phrase 'Hey Code' should be speech recognized to start a voice chat session."), + 'description': localize('voice.keywordActivation', "Controls whether the keyword phrase 'Hey Code' is recognized to start a voice chat session. Enabling this will start recording from the microphone but the audio is processed locally and never sent to a server."), 'default': 'off', 'tags': ['accessibility', 'FeatureInsight'] } From f73a212381a509029ef73c1d3c0959e47b6f4d2a Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 22 Jan 2024 18:29:38 +0100 Subject: [PATCH 0559/1897] Improves rendering of deleted and renamed files in multi diff editor. --- .../diffEditorItemTemplate.ts | 40 ++++++++++++++++--- .../widget/multiDiffEditorWidget/model.ts | 11 ++++- .../multiDiffEditorViewModel.ts | 21 +++++++--- .../widget/multiDiffEditorWidget/style.css | 35 +++++++++++++++- .../workbenchUIElementFactory.ts | 6 ++- .../browser/multiDiffEditor.ts | 8 +++- .../browser/multiDiffEditorInput.ts | 23 +++++------ .../browser/scmMultiDiffSourceResolver.ts | 10 +++-- 8 files changed, 120 insertions(+), 34 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts index 11fdb1e5ccc..186fdb35761 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts @@ -60,14 +60,18 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< private readonly _elements = h('div.multiDiffEntry', [ h('div.header@header', [ h('div.collapse-button@collapseButton'), - h('div.title.show-file-icons@title', [] as any), + h('div.file-path', [ + h('div.title.modified.show-file-icons@primaryPath', [] as any), + h('div.status.deleted@status', ['R']), + h('div.title.original.show-file-icons@secondaryPath', [] as any), + ]), h('div.actions@actions'), ]), h('div.editorParent', [ h('div.editorContainer@editor'), ]) - ]); + ]) as Record; public readonly editor = this._register(this._instantiationService.createInstance(DiffEditorWidget, this._elements.editor, { overflowWidgetsDomNode: this._overflowWidgetsDomNode, @@ -78,7 +82,11 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< public readonly isFocused = derived(this, reader => this.isModifedFocused.read(reader) || this.isOriginalFocused.read(reader)); private readonly _resourceLabel = this._workbenchUIElementFactory.createResourceLabel - ? this._register(this._workbenchUIElementFactory.createResourceLabel(this._elements.title)) + ? this._register(this._workbenchUIElementFactory.createResourceLabel(this._elements.primaryPath)) + : undefined; + + private readonly _resourceLabel2 = this._workbenchUIElementFactory.createResourceLabel + ? this._register(this._workbenchUIElementFactory.createResourceLabel(this._elements.secondaryPath)) : undefined; private readonly _outerEditorHeight: number; @@ -132,7 +140,7 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< this._outerEditorHeight = 38; this._register(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.actions, MenuId.MultiDiffEditorFileToolbar, { - actionRunner: this._register(new ActionRunnerWithContext(() => (this._viewModel.get()?.diffEditorViewModel?.model.modified.uri))), + actionRunner: this._register(new ActionRunnerWithContext(() => (this._viewModel.get()?.modifiedUri))), menuOptions: { shouldForwardArgs: true, }, @@ -178,7 +186,29 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< })); } globalTransaction(tx => { - this._resourceLabel?.setUri(data.viewModel.diffEditorViewModel.model.modified.uri); + this._resourceLabel?.setUri(data.viewModel.modifiedUri ?? data.viewModel.originalUri!, { strikethrough: data.viewModel.modifiedUri === undefined }); + + let isRenamed = false; + let isDeleted = false; + let isAdded = false; + let flag = ''; + if (data.viewModel.modifiedUri && data.viewModel.originalUri && data.viewModel.modifiedUri.path !== data.viewModel.originalUri.path) { + flag = 'R'; + isRenamed = true; + } else if (!data.viewModel.modifiedUri) { + flag = 'D'; + isDeleted = true; + } else if (!data.viewModel.originalUri) { + flag = 'A'; + isAdded = true; + } + this._elements.status.classList.toggle('renamed', isRenamed); + this._elements.status.classList.toggle('deleted', isDeleted); + this._elements.status.classList.toggle('added', isAdded); + this._elements.status.innerText = flag; + + this._resourceLabel2?.setUri(isRenamed ? data.viewModel.originalUri : undefined, { strikethrough: true }); + this._dataStore.clear(); this._viewModel.set(data.viewModel, tx); this.editor.setModel(data.viewModel.diffEditorViewModel, tx); diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts index c6973f50cdf..02f37e47ad2 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts @@ -37,8 +37,15 @@ export class ConstLazyPromise implements LazyPromise { } export interface IDocumentDiffItem { - readonly original: ITextModel | undefined; // undefined if the file was created. - readonly modified: ITextModel | undefined; // undefined if the file was deleted. + /** + * undefined if the file was created. + */ + readonly original: ITextModel | undefined; + + /** + * undefined if the file was deleted. + */ + readonly modified: ITextModel | undefined; readonly options?: IDiffEditorOptions; readonly onOptionsDidChange?: Event; } diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts index a2d272329e3..3a26a82c526 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts @@ -12,13 +12,15 @@ import { IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Selection } from 'vs/editor/common/core/selection'; import { IDiffEditorViewModel } from 'vs/editor/common/editorCommon'; +import { IModelService } from 'vs/editor/common/services/model'; import { ContextKeyValue } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; export class MultiDiffEditorViewModel extends Disposable { private readonly _documents = observableFromEvent(this.model.onDidChange, /** @description MultiDiffEditorViewModel.documents */() => this.model.documents); - public readonly items = mapObservableArrayCached(this, this._documents, (d, store) => store.add(new DocumentDiffItemViewModel(d, this._instantiationService))) + public readonly items = mapObservableArrayCached(this, this._documents, (d, store) => store.add(this._instantiationService.createInstance(DocumentDiffItemViewModel, d))) .recomputeInitiallyAndOnChange(this._store); public readonly activeDiffItem = observableValue(this, undefined); @@ -66,9 +68,13 @@ export class DocumentDiffItemViewModel extends Disposable { { contentHeight: 500, selections: undefined, } ); + public get originalUri(): URI | undefined { return this.entry.value!.original?.uri; } + public get modifiedUri(): URI | undefined { return this.entry.value!.modified?.uri; } + constructor( public readonly entry: LazyPromise, - private readonly _instantiationService: IInstantiationService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IModelService private readonly _modelService: IModelService, ) { super(); @@ -88,16 +94,19 @@ export class DocumentDiffItemViewModel extends Disposable { })); } + const originalTextModel = this.entry.value!.original ?? this._register(this._modelService.createModel('', null)); + const modifiedTextModel = this.entry.value!.modified ?? this._register(this._modelService.createModel('', null)); + this.diffEditorViewModel = this._register(this._instantiationService.createInstance(DiffEditorViewModel, { - original: entry.value!.original!, - modified: entry.value!.modified!, + original: originalTextModel, + modified: modifiedTextModel, }, options)); } public getKey(): string { return JSON.stringify([ - this.diffEditorViewModel.model.original.uri.toString(), - this.diffEditorViewModel.model.modified.uri.toString() + this.originalUri?.toString(), + this.modifiedUri?.toString() ]); } } diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css b/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css index f1ce31f7837..9071d1f841b 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css @@ -44,12 +44,45 @@ box-shadow: var(--vscode-scrollbar-shadow) 0px 6px 6px -6px; } -.monaco-component .multiDiffEntry .header .title { +.monaco-component .multiDiffEntry .header .file-path { + display: flex; flex: 1; + min-width: 0; +} + +.monaco-component .multiDiffEntry .header .file-path .title { font-size: 14px; line-height: 22px; } +.monaco-component .multiDiffEntry .header .file-path .status { + + font-weight: 600; + opacity: 0.75; + margin: 0px 10px; + line-height: 22px; +} +/* +TODO@hediet: move colors from git extension to core! + +.monaco-component .multiDiffEntry .header .file-path .status.renamed { + color: va r(--vscode-gitDecoration-renamedResourceForeground); +} + +.monaco-component .multiDiffEntry .header .file-path .status.deleted { + color: va r(--vscode-gitDecoration-deletedResourceForeground); +} + +.monaco-component .multiDiffEntry .header .file-path .status.added { + color: va r(--vscode-gitDecoration-addedResourceForeground); +} +*/ +.monaco-component .multiDiffEntry .header .file-path .title.original { + flex: 1; + min-width: 0; + text-overflow: ellipsis; +} + .monaco-component .multiDiffEntry .header .actions { padding: 0 8px; } diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory.ts index 54b29691c96..80a018dead4 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory.ts @@ -17,5 +17,9 @@ export interface IWorkbenchUIElementFactory { } export interface IResourceLabel extends IDisposable { - setUri(uri: URI): void; + setUri(uri: URI | undefined, options?: IResourceLabelOptions): void; +} + +export interface IResourceLabelOptions { + strikethrough?: boolean; } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index e100522ed4c..26366866759 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -122,8 +122,12 @@ class WorkbenchUIElementFactory implements IWorkbenchUIElementFactory { createResourceLabel(element: HTMLElement): IResourceLabel { const label = this._instantiationService.createInstance(ResourceLabel, element, {}); return { - setUri(uri) { - label.element.setFile(uri, {}); + setUri(uri, options = {}) { + if (!uri) { + label.element.clear(); + } else { + label.element.setFile(uri, { strikethrough: options.strikethrough }); + } }, dispose() { label.dispose(); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 0fa31d32b33..0507111b60d 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -142,9 +142,8 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor const documentsWithPromises = mapObservableArrayCached(undefined, source.resources, async (r, store) => { /** @description documentsWithPromises */ - let originalTextModel: ITextModel; - let modifiedTextModel: ITextModel; - let modifiedRef: IReference | undefined; + let original: IReference | undefined; + let modified: IReference | undefined; const store2 = new DisposableStore(); store.add(toDisposable(() => { // Mark the text model references as garbage when they get stale (don't dispose them yet) @@ -152,14 +151,12 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor })); try { - [originalTextModel, modifiedTextModel] = await Promise.all([ - r.original - ? store2.add(await this._textModelService.createModelReference(r.original)).object.textEditorModel - : store2.add(this._modelService.createModel('', null)), - r.modified - ? store2.add(modifiedRef = await this._textModelService.createModelReference(r.modified)).object.textEditorModel - : store2.add(this._modelService.createModel('', null)), + [original, modified] = await Promise.all([ + r.original ? this._textModelService.createModelReference(r.original) : undefined, + r.modified ? this._textModelService.createModelReference(r.modified) : undefined, ]); + if (original) { store.add(original); } + if (modified) { store.add(modified); } } catch (e) { // e.g. "File seems to be binary and cannot be opened as text" console.error(e); @@ -169,11 +166,11 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor const uri = (r.modified ?? r.original)!; return new ConstLazyPromise({ - original: originalTextModel, - modified: modifiedTextModel, + original: original?.object.textEditorModel, + modified: modified?.object.textEditorModel, get options() { return { - ...getReadonlyConfiguration(modifiedRef?.object.isReadonly() ?? true), + ...getReadonlyConfiguration(modified?.object.isReadonly() ?? true), ...computeOptions(textResourceConfigurationService.getValue(uri)), } satisfies IDiffEditorOptions; }, diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts index 75d8776a785..3326e502d82 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts @@ -78,10 +78,12 @@ export class ScmMultiDiffSourceResolver implements IMultiDiffSourceResolver { } ); - const resources = observableFromEvent(group.onDidChangeResources, () => group.resources.map(e => ({ - original: e.multiDiffEditorOriginalUri, - modified: e.multiDiffEditorModifiedUri - }))); + const resources = observableFromEvent(group.onDidChangeResources, () => group.resources.map(e => { + return { + original: e.multiDiffEditorOriginalUri, + modified: e.multiDiffEditorModifiedUri + }; + })); return new ScmResolvedMultiDiffSource(resources, { scmResourceGroup: groupId, From 0bf3670b5257a485071219048d692da7db3d4770 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 23 Jan 2024 19:39:27 +0100 Subject: [PATCH 0560/1897] Fixes CI --- .../contrib/multiDiffEditor/browser/multiDiffEditorInput.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 0507111b60d..60b430fd744 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -18,8 +18,6 @@ import { URI } from 'vs/base/common/uri'; import { ConstLazyPromise, IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { ITextModel } from 'vs/editor/common/model'; -import { IModelService } from 'vs/editor/common/services/model'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { localize } from 'vs/nls'; @@ -85,7 +83,6 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor @ITextModelService private readonly _textModelService: ITextModelService, @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IModelService private readonly _modelService: IModelService, @IMultiDiffSourceResolverService private readonly _multiDiffSourceResolverService: IMultiDiffSourceResolverService, @ITextFileService private readonly _textFileService: ITextFileService, ) { From 7975a455439d96a3e33a3705b97cc47165d8cdcc Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 23 Jan 2024 20:32:24 +0100 Subject: [PATCH 0561/1897] No prompt after closing editor when piping command that doesn't terminate (fix #203166) (#203217) --- src/vs/code/node/cli.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 0ecdbb32e77..578bf1a282c 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -225,7 +225,24 @@ export async function main(argv: string[]): Promise { try { const readFromStdinDone = new DeferredPromise(); await readFromStdin(stdinFilePath, !!args.verbose, () => readFromStdinDone.complete()); - processCallbacks.push(() => readFromStdinDone.p); + if (!args.wait) { + + // if `--wait` is not provided, we keep this process alive + // for at least as long as the stdin stream is open to + // ensure that we read all the data. + // the downside is that the Code CLI process will then not + // terminate until stdin is closed, but users can always + // pass `--wait` to prevent that from happening (this is + // actually what we enforced until v1.85.x but then was + // changed to not enforce it anymore). + // a solution in the future would possibly be to exit, when + // the Code process exits. this would require some careful + // solution though in case Code is already running and this + // is a second instance telling the first instance what to + // open. + + processCallbacks.push(() => readFromStdinDone.p); + } // Make sure to open tmp file as editor but ignore it in the "recently open" list addArg(argv, stdinFilePath); From ca476115d31eaaf2213c3ea20fa904c10ca583d5 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 23 Jan 2024 20:31:02 +0100 Subject: [PATCH 0562/1897] Fixes bug in WrappedStyleElement --- src/vs/base/browser/dom.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index c5fea2d0c7b..72caa84e069 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -939,7 +939,7 @@ class WrappedStyleElement { private _styleSheet: HTMLStyleElement | undefined = undefined; public setStyle(cssStyle: string): void { - if (cssStyle !== this._currentCssStyle) { + if (cssStyle === this._currentCssStyle) { return; } this._currentCssStyle = cssStyle; From 3cf18155086177df108c38429454bdd8236c7c67 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 23 Jan 2024 21:18:54 +0100 Subject: [PATCH 0563/1897] Do not offer 'Create file' option for readonly file systems (fix #203223) (#203234) --- .../contrib/files/browser/editors/textFileEditor.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index 44dc610e936..c0f1c912060 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -36,6 +36,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; /** * An implementation of editor for file system resources. @@ -61,7 +62,8 @@ export class TextFileEditor extends AbstractTextCodeEditor @IPathService private readonly pathService: IPathService, @IConfigurationService private readonly configurationService: IConfigurationService, @IPreferencesService protected readonly preferencesService: IPreferencesService, - @IHostService private readonly hostService: IHostService + @IHostService private readonly hostService: IHostService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService ) { super(TextFileEditor.ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService, fileService); @@ -201,8 +203,12 @@ export class TextFileEditor extends AbstractTextCodeEditor throw createTooLargeFileError(this.group, input, options, message, this.preferencesService); } - // Offer to create a file from the error if we have a file not found and the name is valid - if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND && await this.pathService.hasValidBasename(input.preferredResource)) { + // Offer to create a file from the error if we have a file not found and the name is valid and not readonly + if ( + (error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND && + !this.filesConfigurationService.isReadonly(input.preferredResource) && + await this.pathService.hasValidBasename(input.preferredResource) + ) { const fileNotFoundError = createEditorOpenError(new FileOperationError(localize('unavailableResourceErrorEditorText', "The editor could not be opened because the file was not found."), FileOperationResult.FILE_NOT_FOUND), [ toAction({ id: 'workbench.files.action.createMissingFile', label: localize('createFile', "Create File"), run: async () => { From d742dffc43cccff5e20847f7e4db865f8111778f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 23 Jan 2024 21:19:10 +0100 Subject: [PATCH 0564/1897] Speech provider with identifier ms-vscode.vscode-speech is already registered (fix #203172) (#203233) --- .../workbench/api/browser/mainThreadSpeech.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadSpeech.ts b/src/vs/workbench/api/browser/mainThreadSpeech.ts index fa52270cdf0..d670ffd66a1 100644 --- a/src/vs/workbench/api/browser/mainThreadSpeech.ts +++ b/src/vs/workbench/api/browser/mainThreadSpeech.ts @@ -5,7 +5,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostContext, ExtHostSpeechShape, MainContext, MainThreadSpeechShape } from 'vs/workbench/api/common/extHost.protocol'; import { IKeywordRecognitionEvent, ISpeechProviderMetadata, ISpeechService, ISpeechToTextEvent } from 'vs/workbench/contrib/speech/common/speechService'; @@ -20,7 +20,7 @@ type KeywordRecognitionSession = { }; @extHostNamedCustomer(MainContext.MainThreadSpeech) -export class MainThreadSpeech extends Disposable implements MainThreadSpeechShape { +export class MainThreadSpeech implements MainThreadSpeechShape { private readonly proxy: ExtHostSpeechShape; @@ -34,8 +34,6 @@ export class MainThreadSpeech extends Disposable implements MainThreadSpeechShap @ISpeechService private readonly speechService: ISpeechService, @ILogService private readonly logService: ILogService ) { - super(); - this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostSpeech); } @@ -109,4 +107,15 @@ export class MainThreadSpeech extends Disposable implements MainThreadSpeechShap const providerSession = this.keywordRecognitionSessions.get(session); providerSession?.onDidChange.fire(event); } + + dispose(): void { + this.providerRegistrations.forEach(disposable => disposable.dispose()); + this.providerRegistrations.clear(); + + this.speechToTextSessions.forEach(session => session.onDidChange.dispose()); + this.speechToTextSessions.clear(); + + this.keywordRecognitionSessions.forEach(session => session.onDidChange.dispose()); + this.keywordRecognitionSessions.clear(); + } } From 1d62c50954360aec5d1247680e5fda5569d45f15 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:28:24 +0100 Subject: [PATCH 0565/1897] Fix Expand All Diffs command order (#203244) Fix #203201 --- src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts index 921026f4731..5298c06ac45 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts @@ -94,6 +94,7 @@ export class ExpandAllAction extends Action2 { when: ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), ContextKeyExpr.has('multiDiffEditorAllCollapsed')), id: MenuId.EditorTitle, group: 'navigation', + order: 100 }, f1: true, }); From d87f1716cf5c23cf51cc66cf151e90eed2fb5169 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 23 Jan 2024 17:35:19 -0300 Subject: [PATCH 0566/1897] Fix bad spacing in /help (#203245) --- 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 8a2486b786e..9dd8bf76be7 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -265,7 +265,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { return `\t* [\`${chatSubcommandLeader}${c.name}\`](command:${SubmitAction.ID}?${urlSafeArg}) - ${c.description}`; }).join('\n'); - return agentLine + '\n' + commandText; + return (agentLine + '\n' + commandText).trim(); }))).join('\n'); progress.report({ content: new MarkdownString(agentText, { isTrusted: { enabledCommands: [SubmitAction.ID] } }), kind: 'markdownContent' }); if (defaultAgent?.metadata.helpTextPostfix) { From 856d46ed86cacc939504e0d9d77fb8dfc12f9efc Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:01:09 +0100 Subject: [PATCH 0567/1897] SCM - fix regression related to the editor.wordWrap language setting (#203248) --- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 7ce51f64928..562bc927780 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -2027,7 +2027,7 @@ class SCMInputWidgetEditorOptions { // editor.wordWrap const wordWrapConfig = this.configurationService.inspect('editor.wordWrap', { overrideIdentifier: 'scminput' }); - const wordWrap = wordWrapConfig.overrideIdentifiers?.includes('scminput') ? EditorOptions.wordWrap.validate(wordWrapConfig) : 'on'; + const wordWrap = wordWrapConfig.overrideIdentifiers?.includes('scminput') ? EditorOptions.wordWrap.validate(wordWrapConfig.value) : 'on'; return { rulers, wordWrap }; } From 79e6d75a4b56aa4a973820200ede94e5ba644043 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 23 Jan 2024 15:37:29 -0800 Subject: [PATCH 0568/1897] fix #203115 --- .../browser/accessibilityConfiguration.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 5c37fed8c8c..9c18fc789c8 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -159,25 +159,25 @@ const configuration: IConfigurationNode = { tags: ['accessibility'] }, [AccessibilityAlertSettingId.Breakpoint]: { - 'markdownDescription': localize('alert.breakpoint', "Alerts when the active line has a breakpoint. Also see {0}.", '`#audioCues.breakpoint#`'), + 'markdownDescription': localize('alert.breakpoint', "Alerts when the active line has a breakpoint. Also see {0}.", '`#audioCues.onDebugBreak#`'), 'type': 'boolean', 'default': true, tags: ['accessibility'] }, [AccessibilityAlertSettingId.Error]: { - 'markdownDescription': localize('alert.error', "Alerts when the active line has an error. Also see {0}.", '`#audioCues.error#`'), + 'markdownDescription': localize('alert.error', "Alerts when the active line has an error. Also see {0}.", '`#audioCues.lineHasError#`'), 'type': 'boolean', 'default': true, tags: ['accessibility'] }, [AccessibilityAlertSettingId.Warning]: { - 'markdownDescription': localize('alert.warning', "Alerts when the active line has a warning. Also see {0}.", '`#audioCues.warning#`'), + 'markdownDescription': localize('alert.warning', "Alerts when the active line has a warning. Also see {0}.", '`#audioCues.lineHasWarning#`'), 'type': 'boolean', 'default': true, tags: ['accessibility'] }, [AccessibilityAlertSettingId.FoldedArea]: { - 'markdownDescription': localize('alert.foldedArea', "Alerts when the active line has a folded area that can be unfolded. Also see {0}.", '`#audioCues.foldedArea#`'), + 'markdownDescription': localize('alert.foldedArea', "Alerts when the active line has a folded area that can be unfolded. Also see {0}.", '`#audioCues.lineHasFoldedArea#`'), 'type': 'boolean', 'default': true, tags: ['accessibility'] @@ -189,7 +189,7 @@ const configuration: IConfigurationNode = { tags: ['accessibility'] }, [AccessibilityAlertSettingId.TerminalBell]: { - 'markdownDescription': localize('alert.terminalBell', "Alerts when the terminal bell is activated. Also see {0}.", '`#audioCues.terminalBell#`'), + 'markdownDescription': localize('alert.terminalBell', "Alerts when the terminal bell is activated."), 'type': 'boolean', 'default': true, tags: ['accessibility'] From 0955a9d9d5f2ad067b3ff1d037b6be6ca224e267 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 23 Jan 2024 20:52:56 -0300 Subject: [PATCH 0569/1897] Add background color to chat agent icon so it doesn't overlap copilot icon (#203256) --- build/lib/stylelint/vscode-known-variables.json | 1 + src/vs/workbench/contrib/chat/browser/chatWidget.ts | 3 +++ src/vs/workbench/contrib/chat/browser/media/chat.css | 1 + 3 files changed, 5 insertions(+) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 9fb16d8459d..7f5b6fa2892 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -42,6 +42,7 @@ "--vscode-chat-requestBorder", "--vscode-chat-slashCommandBackground", "--vscode-chat-slashCommandForeground", + "--vscode-chat-list-background", "--vscode-checkbox-background", "--vscode-checkbox-border", "--vscode-checkbox-foreground", diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 745ec7e1c04..11780bb2479 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -32,6 +32,7 @@ import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatC import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; const $ = dom.$; @@ -138,6 +139,7 @@ export class ChatWidget extends Disposable implements IChatWidget { @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILogService private readonly _logService: ILogService, + @IThemeService private readonly _themeService: IThemeService ) { super(); CONTEXT_IN_CHAT_SESSION.bindTo(contextKeyService).set(true); @@ -439,6 +441,7 @@ export class ChatWidget extends Disposable implements IChatWidget { private onDidStyleChange(): void { this.container.style.setProperty('--vscode-interactive-result-editor-background-color', this.editorOptions.configuration.resultEditor.backgroundColor?.toString() ?? ''); this.container.style.setProperty('--vscode-interactive-session-foreground', this.editorOptions.configuration.foreground?.toString() ?? ''); + this.container.style.setProperty('--vscode-chat-list-background', this._themeService.getColorTheme().getColor(this.styles.listBackground)?.toString() ?? ''); } setModel(model: IChatModel, viewState: IChatViewState): void { diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 009e5b35f49..7b8c230b92f 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -115,6 +115,7 @@ width: 24px; height: 24px; border-radius: 50%; + background-color: var(--vscode-chat-list-background); } .interactive-item-container .header .avatar .codicon { From b9ee99d894ac7704e5e40ea867d971f3b7641d1d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 23 Jan 2024 20:53:08 -0300 Subject: [PATCH 0570/1897] Delete unnecessary newlines (#203257) --- .vscode/notebooks/api.github-issues | 6 ++-- .vscode/notebooks/endgame.github-issues | 31 ++++++++---------- .vscode/notebooks/grooming.github-issues | 18 +++++------ .vscode/notebooks/my-endgame.github-issues | 34 ++++++++++---------- .vscode/notebooks/my-work.github-issues | 30 ++++++++--------- .vscode/notebooks/verification.github-issues | 10 +++--- .vscode/notebooks/vscode-dev.github-issues | 12 +++---- 7 files changed, 68 insertions(+), 73 deletions(-) diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index f990cff7af1..8027d3066ef 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"December / January 2024\"\n" + "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"December / January 2024\"" }, { "kind": 1, @@ -17,7 +17,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPO $MILESTONE label:api-finalization\n" + "value": "$REPO $MILESTONE label:api-finalization" }, { "kind": 1, @@ -27,6 +27,6 @@ { "kind": 2, "language": "github-issues", - "value": "$REPO $MILESTONE is:open label:api-proposal sort:created-asc\n" + "value": "$REPO $MILESTONE is:open label:api-proposal sort:created-asc" } ] \ No newline at end of file diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index 975758eb747..e93d064d8de 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"December / January 2024\"\n" + "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"December / January 2024\"" }, { "kind": 1, @@ -22,7 +22,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:pr is:open\n" + "value": "$REPOS $MILESTONE is:pr is:open" }, { "kind": 1, @@ -32,7 +32,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS -$MILESTONE is:issue is:closed reason:completed label:bug label:insiders-released -label:verified -label:*duplicate -label:*as-designed -label:z-author-verified -label:on-testplan\n" + "value": "$REPOS -$MILESTONE is:issue is:closed reason:completed label:bug label:insiders-released -label:verified -label:*duplicate -label:*as-designed -label:z-author-verified -label:on-testplan" }, { "kind": 1, @@ -42,7 +42,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS -$MILESTONE is:issue is:closed reason:completed label:feature-request label:insiders-released -label:on-testplan -label:verified -label:*duplicate\n" + "value": "$REPOS -$MILESTONE is:issue is:closed reason:completed label:feature-request label:insiders-released -label:on-testplan -label:verified -label:*duplicate" }, { "kind": 1, @@ -52,7 +52,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:open -label:iteration-plan -label:endgame-plan -label:testplan-item\n" + "value": "$REPOS $MILESTONE is:issue is:open -label:iteration-plan -label:endgame-plan -label:testplan-item" }, { "kind": 1, @@ -62,7 +62,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed reason:completed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate\n" + "value": "$REPOS $MILESTONE is:issue is:closed reason:completed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate" }, { "kind": 1, @@ -72,7 +72,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:open label:testplan-item no:milestone\n" + "value": "$REPOS $MILESTONE is:issue is:open label:testplan-item no:milestone" }, { "kind": 1, @@ -87,7 +87,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS is:issue is:open label:testplan-item\n" + "value": "$REPOS is:issue is:open label:testplan-item" }, { "kind": 1, @@ -97,7 +97,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed reason:completed label:verification-needed -label:verified\n" + "value": "$REPOS $MILESTONE is:issue is:closed reason:completed label:verification-needed -label:verified" }, { "kind": 1, @@ -112,7 +112,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:z-author-verified -label:unreleased\n" + "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:z-author-verified -label:unreleased" }, { "kind": 1, @@ -122,7 +122,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:z-author-verified label:unreleased\n" + "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:z-author-verified label:unreleased" }, { "kind": 1, @@ -132,7 +132,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:z-author-verified\n" + "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:z-author-verified" }, { "kind": 1, @@ -142,11 +142,6 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:open label:candidate\n" - }, - { - "kind": 2, - "language": "github-issues", - "value": "" + "value": "$REPOS $MILESTONE is:issue is:open label:candidate" } ] \ No newline at end of file diff --git a/.vscode/notebooks/grooming.github-issues b/.vscode/notebooks/grooming.github-issues index 694862806ed..8bb9a246d5f 100644 --- a/.vscode/notebooks/grooming.github-issues +++ b/.vscode/notebooks/grooming.github-issues @@ -2,27 +2,27 @@ { "kind": 1, "language": "markdown", - "value": "#### Config\n" + "value": "#### Config" }, { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\r\n$repos=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\r\n" + "value": "// list of repos we work in\r\n$repos=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\r" }, { "kind": 1, "language": "markdown", - "value": "#### Missing Type label\r\n" + "value": "#### Missing Type label\r" }, { "kind": 2, "language": "github-issues", - "value": "$repos assignee:@me is:open type:issue -label:bug -label:\"info-needed\" -label:feature-request -label:under-discussion -label:debt -label:plan-item -label:upstream -label:polish -label:testplan-item -label:error-telemetry -label:engineering -label:endgame-plan\r\n" + "value": "$repos assignee:@me is:open type:issue -label:bug -label:\"info-needed\" -label:feature-request -label:under-discussion -label:debt -label:plan-item -label:upstream -label:polish -label:testplan-item -label:error-telemetry -label:engineering -label:endgame-plan\r" }, { "kind": 1, "language": "markdown", - "value": "#### Missing Area Label\r\n\r\nFeature area labels are light or strong blue (`1d76db` or `c5def5`) and they denote a specific feature or feature area, like `editor-clipboard` or `file-explorer`\r\n" + "value": "#### Missing Area Label\r\n\r\nFeature area labels are light or strong blue (`1d76db` or `c5def5`) and they denote a specific feature or feature area, like `editor-clipboard` or `file-explorer`\r" }, { "kind": 2, @@ -32,21 +32,21 @@ { "kind": 1, "language": "markdown", - "value": "### Missing Milestone\r\n" + "value": "### Missing Milestone\r" }, { "kind": 2, "language": "github-issues", - "value": "$repos assignee:@me is:open type:issue no:milestone -label:info-needed -label:triage-needed\r\n" + "value": "$repos assignee:@me is:open type:issue no:milestone -label:info-needed -label:triage-needed\r" }, { "kind": 1, "language": "markdown", - "value": "#### Not Actionable\r\n" + "value": "#### Not Actionable\r" }, { "kind": 2, "language": "github-issues", - "value": "$repos assignee:@me is:open label:\"info-needed\"\r\n" + "value": "$repos assignee:@me is:open label:\"info-needed\"\r" } ] \ No newline at end of file diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index 46a88544941..ddcb541f8ff 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"December / January 2024\"\n\n$MINE=assignee:@me\n" + "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"December / January 2024\"\n\n$MINE=assignee:@me" }, { "kind": 1, @@ -22,7 +22,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:pr is:open\n" + "value": "$REPOS $MILESTONE $MINE is:pr is:open" }, { "kind": 1, @@ -32,7 +32,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:open -label:iteration-plan -label:endgame-plan -label:testplan-item\n" + "value": "$REPOS $MILESTONE $MINE is:issue is:open -label:iteration-plan -label:endgame-plan -label:testplan-item" }, { "kind": 1, @@ -42,7 +42,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate\n" + "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate" }, { "kind": 1, @@ -52,7 +52,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS is:issue is:open author:@me label:testplan-item\n" + "value": "$REPOS is:issue is:open author:@me label:testplan-item" }, { "kind": 1, @@ -62,7 +62,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request label:verification-needed -label:verified\n" + "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request label:verification-needed -label:verified" }, { "kind": 1, @@ -77,7 +77,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MINE is:issue is:open label:testplan-item\n" + "value": "$REPOS $MINE is:issue is:open label:testplan-item" }, { "kind": 1, @@ -87,7 +87,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed -assignee:@me -label:verified -label:z-author-verified label:feature-request label:verification-needed -label:verification-steps-needed -label:unreleased\n" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed -assignee:@me -label:verified -label:z-author-verified label:feature-request label:verification-needed -label:verification-steps-needed -label:unreleased" }, { "kind": 1, @@ -102,7 +102,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:open -label:endgame-plan -label:testplan-item -label:iteration-plan\n" + "value": "$REPOS $MILESTONE $MINE is:issue is:open -label:endgame-plan -label:testplan-item -label:iteration-plan" }, { "kind": 1, @@ -112,7 +112,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:open label:bug\n" + "value": "$REPOS $MILESTONE $MINE is:issue is:open label:bug" }, { "kind": 1, @@ -127,7 +127,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue label:bug label:verification-steps-needed\n" + "value": "$REPOS $MILESTONE $MINE is:issue label:bug label:verification-steps-needed" }, { "kind": 1, @@ -137,7 +137,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue label:bug label:verification-found\n" + "value": "$REPOS $MILESTONE $MINE is:issue label:bug label:verification-found" }, { "kind": 1, @@ -147,7 +147,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:triage-needed -label:verification-found\n" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:triage-needed -label:verification-found" }, { "kind": 1, @@ -157,7 +157,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:andreamah -author:bamurtaugh -author:bpasero -author:chrisdias -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:gregvanl -author:hediet -author:isidorn -author:joaomoreno -author:joyceerhl -author:jrieken -author:karrtikr -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:tanhakabir -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:paulacamargo25 -author:ulugbekna -author:aiday-mar -author:daviddossett\n" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:andreamah -author:bamurtaugh -author:bpasero -author:chrisdias -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:gregvanl -author:hediet -author:isidorn -author:joaomoreno -author:joyceerhl -author:jrieken -author:karrtikr -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:tanhakabir -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:paulacamargo25 -author:ulugbekna -author:aiday-mar -author:daviddossett" }, { "kind": 1, @@ -167,7 +167,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed -author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found\n" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed -author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found" }, { "kind": 1, @@ -177,7 +177,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue label:bug label:verification-steps-needed -label:verified\n" + "value": "$REPOS $MILESTONE -$MINE is:issue label:bug label:verification-steps-needed -label:verified" }, { "kind": 1, @@ -187,6 +187,6 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:on-release-notes\n$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:engineering -label:on-release-notes\n$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:plan-item -label:on-release-notes\n" + "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:on-release-notes\n$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:engineering -label:on-release-notes\n$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:plan-item -label:on-release-notes" } ] \ No newline at end of file diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index c6e8ee4b20e..cbdd18d1653 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\r\n$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\r\n\r\n// current milestone name\r\n$MILESTONE=milestone:\"December / January 2024\"\r\n" + "value": "// list of repos we work in\r\n$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\r\n\r\n// current milestone name\r\n$MILESTONE=milestone:\"December / January 2024\"\r" }, { "kind": 1, @@ -17,7 +17,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE assignee:@me is:open\r\n" + "value": "$REPOS $MILESTONE assignee:@me is:open\r" }, { "kind": 1, @@ -32,7 +32,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS assignee:@me is:open label:bug\r\n" + "value": "$REPOS assignee:@me is:open label:bug\r" }, { "kind": 1, @@ -42,7 +42,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS assignee:@me is:open label:debt,engineering\r\n" + "value": "$REPOS assignee:@me is:open label:debt,engineering\r" }, { "kind": 1, @@ -52,7 +52,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS assignee:@me is:open label:perf,perf-startup,perf-bloat,freeze-slow-crash-leak\r\n" + "value": "$REPOS assignee:@me is:open label:perf,perf-startup,perf-bloat,freeze-slow-crash-leak\r" }, { "kind": 1, @@ -62,17 +62,17 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS assignee:@me is:open label:feature-request milestone:Backlog sort:reactions-+1-desc\r\n" + "value": "$REPOS assignee:@me is:open label:feature-request milestone:Backlog sort:reactions-+1-desc\r" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS assignee:@me is:open milestone:\"Backlog Candidates\"\r\n" + "value": "$REPOS assignee:@me is:open milestone:\"Backlog Candidates\"\r" }, { "kind": 1, "language": "markdown", - "value": "### Personal Inbox\n" + "value": "### Personal Inbox" }, { "kind": 1, @@ -82,7 +82,7 @@ { "kind": 2, "language": "github-issues", - "value": "repo:microsoft/vscode is:open assignee:@me label:triage-needed\r\n" + "value": "repo:microsoft/vscode is:open assignee:@me label:triage-needed\r" }, { "kind": 1, @@ -92,7 +92,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS assignee:@me is:open type:issue -label:bug -label:\"info-needed\" -label:feature-request -label:under-discussion -label:debt -label:plan-item -label:upstream -label:polish -label:testplan-item -label:error-telemetry -label:engineering\r\n" + "value": "$REPOS assignee:@me is:open type:issue -label:bug -label:\"info-needed\" -label:feature-request -label:under-discussion -label:debt -label:plan-item -label:upstream -label:polish -label:testplan-item -label:error-telemetry -label:engineering\r" }, { "kind": 1, @@ -112,7 +112,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS assignee:@me is:open type:issue no:milestone -label:info-needed -label:triage-needed\r\n" + "value": "$REPOS assignee:@me is:open type:issue no:milestone -label:info-needed -label:triage-needed\r" }, { "kind": 1, @@ -122,7 +122,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS assignee:@me is:open label:\"info-needed\"\r\n" + "value": "$REPOS assignee:@me is:open label:\"info-needed\"\r" }, { "kind": 1, @@ -137,7 +137,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS author:@me is:open is:pr review:approved\r\n" + "value": "$REPOS author:@me is:open is:pr review:approved\r" }, { "kind": 1, @@ -147,7 +147,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS author:@me is:open is:pr review:required\r\n" + "value": "$REPOS author:@me is:open is:pr review:required\r" }, { "kind": 1, @@ -157,6 +157,6 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS author:@me is:open is:pr review:changes_requested\r\n" + "value": "$REPOS author:@me is:open is:pr review:changes_requested\r" } ] \ No newline at end of file diff --git a/.vscode/notebooks/verification.github-issues b/.vscode/notebooks/verification.github-issues index 6145b43a2ec..9740d9557e3 100644 --- a/.vscode/notebooks/verification.github-issues +++ b/.vscode/notebooks/verification.github-issues @@ -12,7 +12,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n$milestone=milestone:\"November 2023\"\n$closedRecently=closed:>2023-09-29\n" + "value": "$repos=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n$milestone=milestone:\"November 2023\"\n$closedRecently=closed:>2023-09-29" }, { "kind": 1, @@ -22,7 +22,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos $milestone is:closed reason:completed -assignee:@me label:bug -label:verified -label:*duplicate author:@me\n" + "value": "$repos $milestone is:closed reason:completed -assignee:@me label:bug -label:verified -label:*duplicate author:@me" }, { "kind": 1, @@ -32,7 +32,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos $milestone is:closed reason:completed -assignee:@me label:bug -label:verified -label:*duplicate -author:@me -assignee:@me label:bug -label:verified -author:@me -author:aeschli -author:alexdima -author:alexr00 -author:bpasero -author:chrisdias -author:chrmarti -author:connor4312 -author:dbaeumer -author:deepak1556 -author:eamodio -author:egamma -author:gregvanl -author:isidorn -author:JacksonKearl -author:joaomoreno -author:jrieken -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:RMacfarlane -author:roblourens -author:sana-ajani -author:sandy081 -author:sbatten -author:Tyriar -author:weinand -author:rzhao271 -author:kieferrm -author:TylerLeonhardt -author:bamurtaugh -author:hediet -author:joyceerhl -author:rchiodo\n" + "value": "$repos $milestone is:closed reason:completed -assignee:@me label:bug -label:verified -label:*duplicate -author:@me -assignee:@me label:bug -label:verified -author:@me -author:aeschli -author:alexdima -author:alexr00 -author:bpasero -author:chrisdias -author:chrmarti -author:connor4312 -author:dbaeumer -author:deepak1556 -author:eamodio -author:egamma -author:gregvanl -author:isidorn -author:JacksonKearl -author:joaomoreno -author:jrieken -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:RMacfarlane -author:roblourens -author:sana-ajani -author:sandy081 -author:sbatten -author:Tyriar -author:weinand -author:rzhao271 -author:kieferrm -author:TylerLeonhardt -author:bamurtaugh -author:hediet -author:joyceerhl -author:rchiodo" }, { "kind": 1, @@ -42,7 +42,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos $milestone is:closed reason:completed -assignee:@me label:bug -label:verified -label:*duplicate\n" + "value": "$repos $milestone is:closed reason:completed -assignee:@me label:bug -label:verified -label:*duplicate" }, { "kind": 1, @@ -52,6 +52,6 @@ { "kind": 2, "language": "github-issues", - "value": "$repos is:closed linked:pr $closedRecently no:milestone -label:verified -label:*duplicate\n" + "value": "$repos is:closed linked:pr $closedRecently no:milestone -label:verified -label:*duplicate" } ] \ No newline at end of file diff --git a/.vscode/notebooks/vscode-dev.github-issues b/.vscode/notebooks/vscode-dev.github-issues index 314a541823f..53fad82e6d9 100644 --- a/.vscode/notebooks/vscode-dev.github-issues +++ b/.vscode/notebooks/vscode-dev.github-issues @@ -2,7 +2,7 @@ { "kind": 2, "language": "github-issues", - "value": "$milestone=milestone:\"November 2023\"\n" + "value": "$milestone=milestone:\"November 2023\"" }, { "kind": 1, @@ -12,12 +12,12 @@ { "kind": 2, "language": "github-issues", - "value": "repo:microsoft/vscode-dev $milestone is:open\n" + "value": "repo:microsoft/vscode-dev $milestone is:open" }, { "kind": 2, "language": "github-issues", - "value": "repo:microsoft/vscode-dev milestone:\"Backlog\" is:open\n" + "value": "repo:microsoft/vscode-dev milestone:\"Backlog\" is:open" }, { "kind": 1, @@ -27,7 +27,7 @@ { "kind": 2, "language": "github-issues", - "value": "repo:microsoft/vscode label:vscode.dev is:open\n" + "value": "repo:microsoft/vscode label:vscode.dev is:open" }, { "kind": 1, @@ -37,11 +37,11 @@ { "kind": 2, "language": "github-issues", - "value": "repo:microsoft/vscode-remote-repositories-github $milestone is:open\n" + "value": "repo:microsoft/vscode-remote-repositories-github $milestone is:open" }, { "kind": 2, "language": "github-issues", - "value": "repo:microsoft/vscode-remotehub $milestone is:open\n" + "value": "repo:microsoft/vscode-remotehub $milestone is:open" } ] \ No newline at end of file From 77fe3a11220e8c5594c3398d666fced868dd6dda Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 23 Jan 2024 16:11:19 -0800 Subject: [PATCH 0571/1897] fix #203211 --- src/vs/platform/audioCues/browser/audioCueService.ts | 5 +++++ .../audioCues/browser/audioCueLineFeatureContribution.ts | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 62fd6b5042d..6de9aaf6c4e 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -22,6 +22,7 @@ export interface IAudioCueService { isCueEnabled(cue: AudioCue): boolean; isAlertEnabled(cue: AudioCue): boolean; onEnabledChanged(cue: AudioCue): Event; + onAlertEnabledChanged(cue: AudioCue): Event; playSound(cue: Sound, allowManyInParallel?: boolean): Promise; playAudioCueLoop(cue: AudioCue, milliseconds: number): IDisposable; @@ -235,6 +236,10 @@ export class AudioCueService extends Disposable implements IAudioCueService { public onEnabledChanged(cue: AudioCue): Event { return Event.fromObservableLight(this.isCueEnabledCache.get({ cue })); } + + public onAlertEnabledChanged(cue: AudioCue): Event { + return Event.fromObservableLight(this.isAlertEnabledCache.get({ cue })); + } } diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts index 06a9a8645ed..e7bba6b3f35 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts @@ -37,6 +37,11 @@ export class AudioCueLineFeatureContribution () => this.audioCueService.isCueEnabled(cue) )); + private readonly isAlertEnabledCache = new CachedFunction>((cue) => observableFromEvent( + this.audioCueService.onAlertEnabledChanged(cue), + () => this.audioCueService.isAlertEnabled(cue) + )); + constructor( @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -47,7 +52,7 @@ export class AudioCueLineFeatureContribution const someAudioCueFeatureIsEnabled = derived( (reader) => /** @description someAudioCueFeatureIsEnabled */ this.features.some((feature) => - this.isEnabledCache.get(feature.audioCue).read(reader) + this.isEnabledCache.get(feature.audioCue).read(reader) || this.isAlertEnabledCache.get(feature.audioCue).read(reader) ) ); @@ -116,7 +121,7 @@ export class AudioCueLineFeatureContribution const isFeaturePresent = derivedOpts( { debugName: `isPresentInLine:${feature.audioCue.name}` }, (reader) => { - if (!this.isEnabledCache.get(feature.audioCue).read(reader)) { + if (!this.isEnabledCache.get(feature.audioCue).read(reader) && !this.isAlertEnabledCache.get(feature.audioCue).read(reader)) { return false; } const position = debouncedPosition.read(reader); From 076bd0bd1e49235e2bffe88de4c6ca4638781532 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 23 Jan 2024 16:16:39 -0800 Subject: [PATCH 0572/1897] compile issue --- src/vs/editor/standalone/browser/standaloneServices.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index b20bde39b15..97841a6012b 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -1077,6 +1077,10 @@ class StandaloneAudioService implements IAudioCueService { return Event.None; } + onAlertEnabledChanged(cue: AudioCue): Event { + return Event.None; + } + async playSound(cue: Sound, allowManyInParallel?: boolean | undefined): Promise { } playAudioCueLoop(cue: AudioCue): IDisposable { From c77b63e865bbb2e8d9bd2196dfb51cb5439d8417 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 23 Jan 2024 17:02:49 -0800 Subject: [PATCH 0573/1897] Accept cell chat response before running follow up (#203271) --- .../notebook/browser/view/cellParts/chat/cellChatController.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts index 58aa70314cf..d14a84bed36 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts @@ -379,11 +379,12 @@ export class NotebookCellChatController extends Disposable { const followups = await this._activeSession.provider.provideFollowups(this._activeSession.session, replyResponse.raw, followupCts.token); if (followups && this._widget) { const widget = this._widget; - widget.updateFollowUps(followups, followup => { + widget.updateFollowUps(followups, async followup => { if (followup.kind === 'reply') { widget.value = followup.message; this.acceptInput(); } else { + await this.acceptSession(); this._commandService.executeCommand(followup.commandId, ...(followup.args ?? [])); } }); From 9c958288987e16c4a959eb669ed8cf3142f562c0 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 23 Jan 2024 17:08:58 -0800 Subject: [PATCH 0574/1897] Enable paste url for file uris (#203270) Fixes #203180 --- .../languageFeatures/copyFiles/pasteUrlProvider.ts | 13 +++++++++++-- .../src/languageFeatures/copyFiles/shared.ts | 6 ------ .../markdown-language-features/src/util/schemes.ts | 2 ++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts index 86fa146f1af..68dca194702 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts @@ -7,7 +7,8 @@ import * as vscode from 'vscode'; import { IMdParser } from '../../markdownEngine'; import { ITextDocument } from '../../types/textDocument'; import { Mime } from '../../util/mimes'; -import { createInsertUriListEdit, externalUriSchemes } from './shared'; +import { createInsertUriListEdit } from './shared'; +import { Schemes } from '../../util/schemes'; export enum PasteUrlAsMarkdownLink { Always = 'always', @@ -170,6 +171,14 @@ async function shouldSmartPasteForSelection( return true; } + +const externalUriSchemes: ReadonlySet = new Set([ + Schemes.http, + Schemes.https, + Schemes.mailto, + Schemes.file, +]); + export function findValidUriInText(text: string): string | undefined { const trimmedUrlList = text.trim(); @@ -186,7 +195,7 @@ export function findValidUriInText(text: string): string | undefined { return; } - if (!externalUriSchemes.includes(uri.scheme.toLowerCase()) || uri.authority.length <= 1) { + if (!externalUriSchemes.has(uri.scheme.toLowerCase()) || uri.authority.length <= 1) { return; } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts index bd2bf0b8053..7881e5938a5 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts @@ -19,12 +19,6 @@ enum MediaKind { Audio, } -export const externalUriSchemes = [ - 'http', - 'https', - 'mailto', -]; - export const mediaFileExtensions = new Map([ // Images ['bmp', MediaKind.Image], diff --git a/extensions/markdown-language-features/src/util/schemes.ts b/extensions/markdown-language-features/src/util/schemes.ts index 3eae0754ad2..dbb3d14d5e2 100644 --- a/extensions/markdown-language-features/src/util/schemes.ts +++ b/extensions/markdown-language-features/src/util/schemes.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ export const Schemes = Object.freeze({ + http: 'http', + https: 'https', file: 'file', untitled: 'untitled', mailto: 'mailto', From 1091f68d834974d78cd4f9769034b4ae4581ff2a Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 23 Jan 2024 17:09:38 -0800 Subject: [PATCH 0575/1897] Fix #203214. Read code window correctly (#203267) * Fix #203214. Read code window correctly * fix diff --- .../notebook/browser/diff/notebookDiffEditor.ts | 4 ++-- .../notebook/browser/notebookEditorWidget.ts | 4 ++-- .../browser/view/renderers/backLayerWebView.ts | 14 ++++++++------ .../workbench/contrib/webview/browser/webview.ts | 2 ++ .../contrib/webview/browser/webviewElement.ts | 3 +-- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts index 5b87a0024a2..fb2f2652969 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts @@ -484,7 +484,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD }, undefined) as BackLayerWebView; // attach the webview container to the DOM tree first this._list.rowsContainer.insertAdjacentElement('afterbegin', this._modifiedWebview.element); - this._modifiedWebview.createWebview(); + this._modifiedWebview.createWebview(DOM.getActiveWindow()); this._modifiedWebview.element.style.width = `calc(50% - 16px)`; this._modifiedWebview.element.style.left = `calc(50%)`; } @@ -501,7 +501,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD }, undefined) as BackLayerWebView; // attach the webview container to the DOM tree first this._list.rowsContainer.insertAdjacentElement('afterbegin', this._originalWebview.element); - this._originalWebview.createWebview(); + this._originalWebview.createWebview(DOM.getActiveWindow()); this._originalWebview.element.style.width = `calc(50% - 16px)`; this._originalWebview.element.style.left = `16px`; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index e70b4b62870..5554136a78c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -17,11 +17,11 @@ import 'vs/css!./media/notebookCellOutput'; import 'vs/css!./media/notebookEditorStickyScroll'; import 'vs/css!./media/notebookKernelActionViewItem'; import 'vs/css!./media/notebookOutline'; - import { PixelRatio } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; +import { mainWindow } from 'vs/base/browser/window'; import { DeferredPromise, SequencerByKey } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Color, RGBA } from 'vs/base/common/color'; @@ -1374,7 +1374,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD throw new Error('Notebook output webview object is not created successfully.'); } - await this._webview.createWebview(); + await this._webview.createWebview(this.creationOptions.codeWindow ?? mainWindow); if (!this._webview.webview) { throw new Error('Notebook output webview element was not created successfully.'); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 6891a9ff4bf..9488f736711 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -5,6 +5,7 @@ import { getWindow } from 'vs/base/browser/dom'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; +import { CodeWindow } from 'vs/base/browser/window'; import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; import { coalesce } from 'vs/base/common/arrays'; import { DeferredPromise, runWhenGlobalIdle } from 'vs/base/common/async'; @@ -512,10 +513,10 @@ export class BackLayerWebView extends Themable { return !!this.webview; } - createWebview(): Promise { + createWebview(codeWindow: CodeWindow): Promise { const baseUrl = this.asWebviewUri(this.getNotebookBaseUri(), undefined); const htmlContent = this.generateContent(baseUrl.toString()); - return this._initialize(htmlContent); + return this._initialize(htmlContent, codeWindow); } private getNotebookBaseUri() { @@ -550,12 +551,12 @@ export class BackLayerWebView extends Themable { ]; } - private _initialize(content: string): Promise { + private _initialize(content: string, codeWindow: CodeWindow): Promise { if (!getWindow(this.element).document.body.contains(this.element)) { throw new Error('Element is already detached from the DOM tree'); } - this.webview = this._createInset(this.webviewService, content); + this.webview = this._createInset(this.webviewService, content, codeWindow); this.webview.mountTo(this.element); this._register(this.webview); @@ -1122,7 +1123,7 @@ export class BackLayerWebView extends Themable { await this.openerService.open(newFileUri); } - private _createInset(webviewService: IWebviewService, content: string) { + private _createInset(webviewService: IWebviewService, content: string, codeWindow: CodeWindow) { this.localResourceRootsCache = this._getResourceRootsCache(); const webview = webviewService.createWebviewElement({ origin: BackLayerWebView.getOriginStore(this.storageService).getOrigin(this.notebookViewType, undefined), @@ -1138,7 +1139,8 @@ export class BackLayerWebView extends Themable { localResourceRoots: this.localResourceRootsCache, }, extension: undefined, - providedViewType: 'notebook.output' + providedViewType: 'notebook.output', + codeWindow: codeWindow }); webview.setHtml(content); diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 2a9158a46d1..dfd7c6fc5b4 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -5,6 +5,7 @@ import { Dimension } from 'vs/base/browser/dom'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; +import { CodeWindow } from 'vs/base/browser/window'; import { equals } from 'vs/base/common/arrays'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -77,6 +78,7 @@ export interface WebviewInitInfo { readonly contentOptions: WebviewContentOptions; readonly extension: WebviewExtensionDescription | undefined; + readonly codeWindow?: CodeWindow; } export const enum WebviewContentPurpose { diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index a3f4989a1da..0cfe554c54e 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -180,8 +180,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD this._element = this._createElement(initInfo.options, initInfo.contentOptions); - - const subscription = this._register(addDisposableListener(getActiveWindow(), 'message', (e: MessageEvent) => { + const subscription = this._register(addDisposableListener(initInfo.codeWindow ?? getActiveWindow(), 'message', (e: MessageEvent) => { if (!this._encodedWebviewOrigin || e?.data?.target !== this.id) { return; } From 3b8295758652e275c45238280f0a027ff1dd98b0 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 24 Jan 2024 10:22:28 +0100 Subject: [PATCH 0576/1897] SCM - fix close repository/close other repositories actions (#203292) --- .../workbench/contrib/scm/browser/scmRepositoriesViewPane.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index c7d13a31077..20b33dd78fa 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -151,10 +151,7 @@ export class SCMRepositoriesViewPane extends ViewPane { const actions = collectContextMenuActions(menu); const actionRunner = this._register(new RepositoryActionRunner(() => { - const focusedRepositories = this.list.getFocusedElements(); - const selectedRepositories = this.list.getSelectedElements(); - - return Array.from(new Set([...focusedRepositories, ...selectedRepositories])); + return this.list.getSelectedElements(); })); actionRunner.onWillRun(() => this.list.domFocus()); From b27e28197eee3a6b367d5f0f4e1990ab6fa5c1c1 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 24 Jan 2024 10:32:12 +0100 Subject: [PATCH 0577/1897] remove FeatureInsight tag (#203295) - remove FeatureInsight tag - report event per setting with owner and purpose information - adopt the opted in settings --- src/vs/editor/common/config/editorOptions.ts | 2 +- .../browser/workbench.contribution.ts | 1 - .../contrib/chat/common/chatService.ts | 2 + .../actions/voiceChatActions.ts | 18 +- .../browser/extensions.contribution.ts | 2 +- .../files/browser/files.contribution.ts | 3 +- .../browser/telemetry.contribution.ts | 184 +++++++++++++++++- .../electron-sandbox/desktop.contribution.ts | 7 +- .../browser/configurationService.ts | 96 +-------- 9 files changed, 198 insertions(+), 117 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 73fc32d3b34..5d150a58558 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2818,7 +2818,7 @@ class EditorStickyScroll extends BaseEditorOption(ConfigurationExtensions.Con localize('workbench.activityBar.location.top', "Show the Activity Bar on top of the Primary Side Bar."), localize('workbench.activityBar.location.hide', "Hide the Activity Bar.") ], - tags: ['FeatureInsight'] }, 'workbench.activityBar.iconClickBehavior': { 'type': 'string', diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 5b90ef33acf..3bc59a462ab 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -313,3 +313,5 @@ export interface IChatService { transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void; } + +export const KEYWORD_ACTIVIATION_SETTING_ID = 'accessibility.voice.keywordActivation'; diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 1666adf085d..aaa7195ba5a 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -17,7 +17,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { spinningLoading } from 'vs/platform/theme/common/iconRegistry'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidget, IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat'; -import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatService, KEYWORD_ACTIVIATION_SETTING_ID } from 'vs/workbench/contrib/chat/common/chatService'; import { CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, MENU_INLINE_CHAT_INPUT } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; @@ -771,15 +771,13 @@ function supportsKeywordActivation(configurationService: IConfigurationService, return false; } - const value = configurationService.getValue(KeywordActivationContribution.SETTINGS_ID); + const value = configurationService.getValue(KEYWORD_ACTIVIATION_SETTING_ID); return typeof value === 'string' && value !== KeywordActivationContribution.SETTINGS_VALUE.OFF; } export class KeywordActivationContribution extends Disposable implements IWorkbenchContribution { - static SETTINGS_ID = 'accessibility.voice.keywordActivation'; - static SETTINGS_VALUE = { OFF: 'off', INLINE_CHAT: 'inlineChat', @@ -817,7 +815,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe this._register(this.speechService.onDidEndSpeechToTextSession(() => this.handleKeywordActivation())); this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(KeywordActivationContribution.SETTINGS_ID)) { + if (e.affectsConfiguration(KEYWORD_ACTIVIATION_SETTING_ID)) { this.handleKeywordActivation(); } })); @@ -836,7 +834,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe registry.registerConfiguration({ ...accessibilityConfigurationNodeBase, properties: { - [KeywordActivationContribution.SETTINGS_ID]: { + [KEYWORD_ACTIVIATION_SETTING_ID]: { 'type': 'string', 'enum': [ KeywordActivationContribution.SETTINGS_VALUE.OFF, @@ -854,7 +852,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe ], 'description': localize('voice.keywordActivation', "Controls whether the keyword phrase 'Hey Code' is recognized to start a voice chat session. Enabling this will start recording from the microphone but the audio is processed locally and never sent to a server."), 'default': 'off', - 'tags': ['accessibility', 'FeatureInsight'] + 'tags': ['accessibility'] } } }); @@ -905,7 +903,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe } private getKeywordCommand(): string { - const setting = this.configurationService.getValue(KeywordActivationContribution.SETTINGS_ID); + const setting = this.configurationService.getValue(KEYWORD_ACTIVIATION_SETTING_ID); switch (setting) { case KeywordActivationContribution.SETTINGS_VALUE.INLINE_CHAT: return InlineVoiceChatAction.ID; @@ -949,7 +947,7 @@ class KeywordActivationStatusEntry extends Disposable { ) { super(); - CommandsRegistry.registerCommand(KeywordActivationStatusEntry.STATUS_COMMAND, () => this.commandService.executeCommand('workbench.action.openSettings', KeywordActivationContribution.SETTINGS_ID)); + CommandsRegistry.registerCommand(KeywordActivationStatusEntry.STATUS_COMMAND, () => this.commandService.executeCommand('workbench.action.openSettings', KEYWORD_ACTIVIATION_SETTING_ID)); this.registerListeners(); this.updateStatusEntry(); @@ -959,7 +957,7 @@ class KeywordActivationStatusEntry extends Disposable { this._register(this.speechService.onDidStartKeywordRecognition(() => this.updateStatusEntry())); this._register(this.speechService.onDidEndKeywordRecognition(() => this.updateStatusEntry())); this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(KeywordActivationContribution.SETTINGS_ID)) { + if (e.affectsConfiguration(KEYWORD_ACTIVIATION_SETTING_ID)) { this.updateStatusEntry(); } })); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 31972316d65..2254a205092 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -146,7 +146,7 @@ Registry.as(ConfigurationExtensions.Configuration) description: localize('extensions.autoUpdate', "Controls the automatic update behavior of extensions. The updates are fetched from a Microsoft online service."), default: true, scope: ConfigurationScope.APPLICATION, - tags: ['usesOnlineServices', 'FeatureInsight'] + tags: ['usesOnlineServices'] }, 'extensions.autoCheckUpdates': { type: 'boolean', diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 0ea210b535d..efd9fe59af7 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -251,8 +251,7 @@ configurationRegistry.registerConfiguration({ ], 'default': isWeb ? AutoSaveConfiguration.AFTER_DELAY : AutoSaveConfiguration.OFF, 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSave' }, "Controls [auto save](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save) of editors that have unsaved changes.", AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE, AutoSaveConfiguration.AFTER_DELAY), - scope: ConfigurationScope.LANGUAGE_OVERRIDABLE, - tags: ['FeatureInsight'] + scope: ConfigurationScope.LANGUAGE_OVERRIDABLE }, 'files.autoSaveDelay': { 'type': 'number', diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index 5293305cc56..d52e62ee7a7 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -16,10 +16,11 @@ import { language } from 'vs/base/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUtils'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, ConfigurationTargetToString, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITextFileService, ITextFileSaveEvent, ITextFileResolveEvent } from 'vs/workbench/services/textfile/common/textfiles'; import { extname, basename, isEqual, isEqualOrParent } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; +import { Event } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; import { getMimeTypes } from 'vs/editor/common/services/languagesAssociations'; import { hash } from 'vs/base/common/hash'; @@ -27,6 +28,11 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { ViewContainerLocation } from 'vs/workbench/common/views'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { mainWindow } from 'vs/base/browser/window'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { isBoolean, isNumber, isString } from 'vs/base/common/types'; +import { LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; +import { AutoUpdateConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions'; +import { KEYWORD_ACTIVIATION_SETTING_ID } from 'vs/workbench/contrib/chat/common/chatService'; type TelemetryData = { mimeType: TelemetryTrustedValue; @@ -58,7 +64,6 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr @IWorkbenchThemeService themeService: IWorkbenchThemeService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, - @IConfigurationService configurationService: IConfigurationService, @IPaneCompositePartService paneCompositeService: IPaneCompositePartService, @ITextFileService textFileService: ITextFileService ) { @@ -229,4 +234,177 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TelemetryContribution, LifecyclePhase.Restored); +class ConfigurationTelemetryContribution extends Disposable implements IWorkbenchContribution { + + private readonly configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + ) { + super(); + + // Debounce the event by 1000 ms and merge all affected keys into one event + const debouncedConfigService = Event.debounce(configurationService.onDidChangeConfiguration, (last, cur) => { + const newAffectedKeys: ReadonlySet = last ? new Set([...last.affectedKeys, ...cur.affectedKeys]) : cur.affectedKeys; + return { ...cur, affectedKeys: newAffectedKeys }; + }, 1000, true); + + debouncedConfigService(event => { + if (event.source !== ConfigurationTarget.DEFAULT) { + type UpdateConfigurationClassification = { + owner: 'sandy081'; + comment: 'Event which fires when user updates settings'; + configurationSource: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'What configuration file was updated i.e user or workspace' }; + configurationKeys: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'What configuration keys were updated' }; + }; + type UpdateConfigurationEvent = { + configurationSource: string; + configurationKeys: string[]; + }; + telemetryService.publicLog2('updateConfiguration', { + configurationSource: ConfigurationTargetToString(event.source), + configurationKeys: Array.from(event.affectedKeys) + }); + } + }); + + const { user, workspace } = configurationService.keys(); + for (const setting of user) { + this.reportTelemetry(setting, ConfigurationTarget.USER_LOCAL); + } + for (const setting of workspace) { + this.reportTelemetry(setting, ConfigurationTarget.WORKSPACE); + } + } + + /** + * Report value of a setting only if it is an enum, boolean, or number or an array of those. + */ + private getValueToReport(key: string, target: ConfigurationTarget.USER_LOCAL | ConfigurationTarget.WORKSPACE): any { + const schema = this.configurationRegistry.getConfigurationProperties()[key]; + const inpsectData = this.configurationService.inspect(key); + const value = target === ConfigurationTarget.USER_LOCAL ? inpsectData.user?.value : inpsectData.workspace?.value; + if (isNumber(value) || isBoolean(value)) { + return value; + } + if (isString(value)) { + if (schema?.enum?.includes(value)) { + return value; + } + return undefined; + } + if (Array.isArray(value)) { + if (value.every(v => isNumber(v) || isBoolean(v) || (isString(v) && schema?.enum?.includes(v)))) { + return value; + } + } + return undefined; + } + + private reportTelemetry(key: string, target: ConfigurationTarget.USER_LOCAL | ConfigurationTarget.WORKSPACE): void { + type UpdatedSettingEvent = { + value: any; + source: string; + }; + const source = ConfigurationTargetToString(target); + + switch (key) { + + case LayoutSettings.ACTIVITY_BAR_LOCATION: + this.telemetryService.publicLog2('workbench.activityBar.location', { value: this.getValueToReport(key, target), source }); + return; + + case AutoUpdateConfigurationKey: + this.telemetryService.publicLog2('extensions.autoUpdate', { value: this.getValueToReport(key, target), source }); + return; + + case 'files.autoSave': + this.telemetryService.publicLog2('files.autoSave', { value: this.getValueToReport(key, target), source }); + return; + + case 'editor.stickyScroll.enabled': + this.telemetryService.publicLog2('editor.stickyScroll.enabled', { value: this.getValueToReport(key, target), source }); + return; + + case KEYWORD_ACTIVIATION_SETTING_ID: + this.telemetryService.publicLog2('accessibility.voice.keywordActivation', { value: this.getValueToReport(key, target), source }); + return; + + case 'window.zoomLevel': + this.telemetryService.publicLog2('window.zoomLevel', { value: this.getValueToReport(key, target), source }); + return; + + case 'window.zoomPerWindow': + this.telemetryService.publicLog2('window.zoomPerWindow', { value: this.getValueToReport(key, target), source }); + return; + + case 'window.titleBarStyle': + this.telemetryService.publicLog2('window.titleBarStyle', { value: this.getValueToReport(key, target), source }); + return; + + case 'window.customTitleBarVisibility': + this.telemetryService.publicLog2('window.customTitleBarVisibility', { value: this.getValueToReport(key, target), source }); + return; + + case 'window.nativeTabs': + this.telemetryService.publicLog2('window.nativeTabs', { value: this.getValueToReport(key, target), source }); + return; + } + } + +} + +const workbenchContributionRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchContributionRegistry.registerWorkbenchContribution(TelemetryContribution, LifecyclePhase.Restored); +workbenchContributionRegistry.registerWorkbenchContribution(ConfigurationTelemetryContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index ffcd7a7a0a5..1c38985809b 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -195,13 +195,13 @@ import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from 'vs/platform/window/electron-sand 'maximum': MAX_ZOOM_LEVEL, 'markdownDescription': localize({ comment: ['{0} will be a setting name rendered as a link'], key: 'zoomLevel' }, "Adjust the default zoom level for all windows. Each increment above `0` (e.g. `1`) or below (e.g. `-1`) represents zooming `20%` larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity. See {0} for configuring if the 'Zoom In' and 'Zoom Out' commands apply the zoom level to all windows or only the active window.", '`#window.zoomPerWindow#`'), ignoreSync: true, - tags: ['accessibility', 'FeatureInsight'] + tags: ['accessibility'] }, 'window.zoomPerWindow': { 'type': 'boolean', 'default': true, 'markdownDescription': localize({ comment: ['{0} will be a setting name rendered as a link'], key: 'zoomPerWindow' }, "Controls if the 'Zoom In' and 'Zoom Out' commands apply the zoom level to all windows or only the active window. See {0} for configuring a default zoom level for all windows.", '`#window.zoomLevel#`'), - tags: ['accessibility', 'FeatureInsight'] + tags: ['accessibility'] }, 'window.newWindowDimensions': { 'type': 'string', @@ -234,7 +234,6 @@ import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from 'vs/platform/window/electron-sand 'default': isLinux ? 'native' : 'custom', 'scope': ConfigurationScope.APPLICATION, 'description': localize('titleBarStyle', "Adjust the appearance of the window title bar to be native by the OS or custom. On Linux and Windows, this setting also affects the application and context menu appearances. Changes require a full restart to apply."), - tags: ['FeatureInsight'] }, 'window.customTitleBarVisibility': { 'type': 'string', @@ -247,7 +246,6 @@ import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from 'vs/platform/window/electron-sand 'default': isLinux ? 'never' : 'auto', 'scope': ConfigurationScope.APPLICATION, 'description': localize('window.customTitleBarVisibility', "Adjust when the custom title bar should be shown."), - tags: ['FeatureInsight'] }, 'window.dialogStyle': { 'type': 'string', @@ -262,7 +260,6 @@ import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from 'vs/platform/window/electron-sand 'scope': ConfigurationScope.APPLICATION, 'description': localize('window.nativeTabs', "Enables macOS Sierra window tabs. Note that changes require a full restart to apply and that native tabs will disable a custom title bar style if configured."), 'included': isMacintosh, - tags: ['FeatureInsight'] }, 'window.nativeFullScreen': { 'type': 'boolean', diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index f3cc5a0eff6..c15c42669a3 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -17,7 +17,7 @@ import { IPolicyConfiguration, NullPolicyConfiguration, PolicyConfiguration } fr import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings, PROFILE_SCOPES, LOCAL_MACHINE_PROFILE_SCOPES, profileSettingsSchemaId, APPLY_ALL_PROFILES_SETTING } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema, keyFromOverrideIdentifiers, OVERRIDE_PROPERTY_PATTERN, resourceLanguageSettingsSchemaId, configurationDefaultsSchemaId, IRegisteredConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema, keyFromOverrideIdentifiers, OVERRIDE_PROPERTY_PATTERN, resourceLanguageSettingsSchemaId, configurationDefaultsSchemaId } from 'vs/platform/configuration/common/configurationRegistry'; import { IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, getStoredWorkspaceFolder, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ConfigurationEditing, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; @@ -37,7 +37,7 @@ import { delta, distinct, equals as arrayEquals } from 'vs/base/common/arrays'; import { IStringDictionary } from 'vs/base/common/collections'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; -import { isBoolean, isNumber, isString, isUndefined } from 'vs/base/common/types'; +import { isUndefined } from 'vs/base/common/types'; import { localize } from 'vs/nls'; import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; @@ -47,7 +47,6 @@ import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/envir import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { mainWindow } from 'vs/base/browser/window'; import { runWhenWindowIdle } from 'vs/base/browser/dom'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; function getLocalUserConfigurationScopes(userDataProfile: IUserDataProfile, hasRemote: boolean): ConfigurationScope[] | undefined { return (userDataProfile.isDefault || userDataProfile.useDefaultFlags?.settings) @@ -1325,96 +1324,6 @@ class ResetConfigurationDefaultsOverridesCache extends Disposable implements IWo } } -class ConfigurationTelemetryContribution extends Disposable implements IWorkbenchContribution { - - private readonly configurationRegistry = Registry.as(Extensions.Configuration); - - constructor( - @IConfigurationService private readonly configurationService: WorkspaceService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - ) { - super(); - - // Debounce the event by 1000 ms and merge all affected keys into one event - const debouncedConfigService = Event.debounce(configurationService.onDidChangeConfiguration, (last, cur) => { - const newAffectedKeys: ReadonlySet = last ? new Set([...last.affectedKeys, ...cur.affectedKeys]) : cur.affectedKeys; - return { ...cur, affectedKeys: newAffectedKeys }; - }, 1000, true); - - debouncedConfigService(event => { - if (event.source !== ConfigurationTarget.DEFAULT) { - type UpdateConfigurationClassification = { - owner: 'sandy081'; - comment: 'Event which fires when user updates settings'; - configurationSource: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'What configuration file was updated i.e user or workspace' }; - configurationKeys: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'What configuration keys were updated' }; - }; - type UpdateConfigurationEvent = { - configurationSource: string; - configurationKeys: string[]; - }; - telemetryService.publicLog2('updateConfiguration', { - configurationSource: ConfigurationTargetToString(event.source), - configurationKeys: Array.from(event.affectedKeys) - }); - } - }); - - type UpdatedSettingClassification = { - owner: 'sandy081'; - comment: 'Event reporting the updated setting'; - setting: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'name of the setting' }; - value: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'value of the setting' }; - source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'source of the setting' }; - }; - type UpdatedSettingEvent = { - setting: string; - value: string; - source: string; - }; - const { user, workspace } = configurationService.keys(); - const userSource = ConfigurationTargetToString(ConfigurationTarget.USER_LOCAL); - for (const setting of user) { - const schema = this.configurationRegistry.getConfigurationProperties()[setting]; - if (schema?.tags?.includes('FeatureInsight')) { - const value = this.getValueToReport(setting, ConfigurationTarget.USER_LOCAL, schema); - this.telemetryService.publicLog2('updatedsetting', { setting, value, source: userSource }); - } - } - const worskpaceSource = ConfigurationTargetToString(ConfigurationTarget.WORKSPACE); - for (const setting of workspace) { - const schema = this.configurationRegistry.getConfigurationProperties()[setting]; - if (schema?.tags?.includes('FeatureInsight')) { - const value = this.getValueToReport(setting, ConfigurationTarget.WORKSPACE, schema); - this.telemetryService.publicLog2('updatedsetting', { setting, value, source: worskpaceSource }); - } - } - } - - /** - * Report value of a setting only if it is an enum, boolean, or number or an array of those. - */ - private getValueToReport(key: string, target: ConfigurationTarget, schema: IRegisteredConfigurationPropertySchema): any { - const configurationModel = this.configurationService.getConfigurationModel(target); - const value = configurationModel?.getValue(key); - if (isNumber(value) || isBoolean(value)) { - return value; - } - if (isString(value)) { - if (schema.enum?.includes(value)) { - return value; - } - return undefined; - } - if (Array.isArray(value)) { - if (value.every(v => isNumber(v) || isBoolean(v) || (isString(v) && schema.enum?.includes(v)))) { - return value; - } - } - return undefined; - } -} - class UpdateExperimentalSettingsDefaults extends Disposable implements IWorkbenchContribution { private readonly processedExperimentalSettings = new Set(); @@ -1457,7 +1366,6 @@ const workbenchContributionsRegistry = Registry.as(Extensions.Configuration); configurationRegistry.registerConfiguration({ From afd4f631aa6703773767567d8d291e431ae77bc4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 24 Jan 2024 10:40:58 +0100 Subject: [PATCH 0578/1897] inline chat fixes (#203297) * Pause state should be more relaxed and expect partial state fixes https://github.com/microsoft/vscode/issues/203173 * clear stashed session before starting a new session fixes https://github.com/microsoft/vscode-copilot/issues/3719 --- .../contrib/inlineChat/browser/inlineChatController.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index ca86cd75143..fd19bd595a9 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -247,6 +247,7 @@ export class InlineChatController implements IEditorContribution { } this._historyOffset = -1; this._historyCandidate = ''; + this._stashedSession.clear(); this._onWillStartSession.fire(); this._currentRun = this._nextState(State.CREATE_SESSION, options); await this._currentRun; @@ -815,12 +816,10 @@ export class InlineChatController implements IEditorContribution { } private async[State.PAUSE]() { - assertType(this._session); - assertType(this._strategy); this._resetWidget(); - this._strategy.pause?.(); + this._strategy?.pause?.(); this._session = undefined; } From 8458f875df6190889248c68568efe66c31a792ca Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 24 Jan 2024 11:03:54 +0100 Subject: [PATCH 0579/1897] Custom Titlebar Menu Fix (#203300) custom titlebar menu fix --- .../browser/parts/titlebar/titlebarActions.ts | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index a528f6ab90c..1ed7b88b35a 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -106,8 +106,24 @@ class ToggleCustomTitleBar extends Action2 { title: localize('toggle.customTitleBar', 'Custom Title Bar'), toggled: TitleBarVisibleContext, menu: [ - { id: MenuId.MenubarAppearanceMenu, order: 6, when: ContextKeyExpr.or(ContextKeyExpr.equals(TitleBarStyleContext.key, TitlebarStyle.NATIVE), IsMainWindowFullscreenContext), group: '2_workbench_layout' }, - ] + { + id: MenuId.MenubarAppearanceMenu, + order: 6, + when: ContextKeyExpr.or( + ContextKeyExpr.and( + ContextKeyExpr.equals(TitleBarStyleContext.key, TitlebarStyle.NATIVE), + ContextKeyExpr.and( + ContextKeyExpr.equals('config.workbench.layoutControl.enabled', false), + ContextKeyExpr.equals('config.window.commandCenter', false), + ContextKeyExpr.notEquals('config.workbench.editor.editorActionsLocation', 'titleBar'), + ContextKeyExpr.notEquals('config.workbench.activityBar.location', 'top') + )?.negate() + ), + IsMainWindowFullscreenContext + ), + group: '2_workbench_layout' + }, + ], }); } From 4a3c073d9e5214d667e873888cde5c4446e70e05 Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 24 Jan 2024 11:12:27 +0100 Subject: [PATCH 0580/1897] Fix #203235 (#203302) fix #203235 --- src/vs/workbench/browser/workbench.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 3e2fd545421..c3ec65eaee1 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -52,7 +52,7 @@ const registry = Registry.as(ConfigurationExtensions.Con [LayoutSettings.EDITOR_ACTIONS_LOCATION]: { 'type': 'string', 'enum': [EditorActionsLocation.DEFAULT, EditorActionsLocation.TITLEBAR, EditorActionsLocation.HIDDEN], - 'enumDescriptions': [ + 'markdownEnumDescriptions': [ localize({ comment: ['{0} will be a setting name rendered as a link'], key: 'workbench.editor.editorActionsLocation.default' }, "Show editor actions in the window title bar when {0} is set to {1}. Otherwise, editor actions are shown in the editor tab bar.", '`#workbench.editor.showTabs#`', '`none`'), localize({ comment: ['{0} will be a setting name rendered as a link'], key: 'workbench.editor.editorActionsLocation.titleBar' }, "Show editor actions in the window title bar. If {0} is set to {1}, editor actions are hidden.", '`#window.customTitleBarVisibility#`', '`never`'), localize('workbench.editor.editorActionsLocation.hidden', "Editor actions are not shown."), From 205e4e53a1415424c6e2194017bb4395aba143d6 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 24 Jan 2024 11:19:17 +0100 Subject: [PATCH 0581/1897] Fixes #201713 (#201809) * Fixes #201713 --- .../heuristicSequenceOptimizations.ts | 2 +- .../node/diffing/fixtures/issue-201713/1.tst | 13 ++++++ .../node/diffing/fixtures/issue-201713/2.tst | 20 +++++++++ .../issue-201713/advanced.expected.diff.json | 44 +++++++++++++++++++ .../issue-201713/legacy.expected.diff.json | 39 ++++++++++++++++ .../advanced.expected.diff.json | 2 +- .../advanced.expected.diff.json | 2 +- 7 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 src/vs/editor/test/node/diffing/fixtures/issue-201713/1.tst create mode 100644 src/vs/editor/test/node/diffing/fixtures/issue-201713/2.tst create mode 100644 src/vs/editor/test/node/diffing/fixtures/issue-201713/advanced.expected.diff.json create mode 100644 src/vs/editor/test/node/diffing/fixtures/issue-201713/legacy.expected.diff.json diff --git a/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts b/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts index 08ead77d7c6..08efd578136 100644 --- a/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts +++ b/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts @@ -272,7 +272,7 @@ export function extendDiffsToEntireWordIfAppropriate(sequence1: LinesSliceCharSe } if (equalChars1 + equalChars2 < (w.seq1Range.length + w.seq2Range.length) * 2 / 3) { - additional.push(new SequenceDiff(w1, w2)); + additional.push(w); } lastPoint = w.getEndExclusives(); diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-201713/1.tst b/src/vs/editor/test/node/diffing/fixtures/issue-201713/1.tst new file mode 100644 index 00000000000..1287957879c --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-201713/1.tst @@ -0,0 +1,13 @@ +const deletedCodeLineBreaksComputer = !renderSideBySide ? this._editors.modified._getViewModel()?.createLineBreaksComputer() : undefined; +if (deletedCodeLineBreaksComputer) { + for (const a of alignmentsVal) { + if (a.diff) { + for (let i = a.originalRange.startLineNumber; i < a.originalRange.endLineNumberExclusive; i++) { + deletedCodeLineBreaksComputer?.addRequest(this._editors.original.getModel()!.getLineContent(i), null, null); + } + } + } +} + +const lineBreakData = deletedCodeLineBreaksComputer?.finalize() ?? []; +let lineBreakDataIdx = 0; \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-201713/2.tst b/src/vs/editor/test/node/diffing/fixtures/issue-201713/2.tst new file mode 100644 index 00000000000..3b91ab56302 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-201713/2.tst @@ -0,0 +1,20 @@ +const deletedCodeLineBreaksComputer = !renderSideBySide ? this._editors.modified._getViewModel()?.createLineBreaksComputer() : undefined; +if (deletedCodeLineBreaksComputer) { + const originalModel = this._editors.original.getModel()!; + for (const a of alignmentsVal) { + if (a.diff) { + for (let i = a.originalRange.startLineNumber; i < a.originalRange.endLineNumberExclusive; i++) { + // `i` can be out of bound when the diff has not been updated yet. + // In this case, we do an early return. + // TODO@hediet: Fix this by applying the edit directly to the diff model, so that the diff is always valid. + if (i > originalModel.getLineCount()) { + return { orig: origViewZones, mod: modViewZones }; + } + deletedCodeLineBreaksComputer?.addRequest(originalModel.getLineContent(i), null, null); + } + } + } +} + +const lineBreakData = deletedCodeLineBreaksComputer?.finalize() ?? []; +let lineBreakDataIdx = 0; \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-201713/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/issue-201713/advanced.expected.diff.json new file mode 100644 index 00000000000..a3aebbb57ed --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-201713/advanced.expected.diff.json @@ -0,0 +1,44 @@ +{ + "original": { + "content": "const deletedCodeLineBreaksComputer = !renderSideBySide ? this._editors.modified._getViewModel()?.createLineBreaksComputer() : undefined;\nif (deletedCodeLineBreaksComputer) {\n\tfor (const a of alignmentsVal) {\n\t\tif (a.diff) {\n\t\t\tfor (let i = a.originalRange.startLineNumber; i < a.originalRange.endLineNumberExclusive; i++) {\n\t\t\t\tdeletedCodeLineBreaksComputer?.addRequest(this._editors.original.getModel()!.getLineContent(i), null, null);\n\t\t\t}\n\t\t}\n\t}\n}\n\nconst lineBreakData = deletedCodeLineBreaksComputer?.finalize() ?? [];\nlet lineBreakDataIdx = 0;", + "fileName": "./1.tst" + }, + "modified": { + "content": "const deletedCodeLineBreaksComputer = !renderSideBySide ? this._editors.modified._getViewModel()?.createLineBreaksComputer() : undefined;\nif (deletedCodeLineBreaksComputer) {\n\tconst originalModel = this._editors.original.getModel()!;\n\tfor (const a of alignmentsVal) {\n\t\tif (a.diff) {\n\t\t\tfor (let i = a.originalRange.startLineNumber; i < a.originalRange.endLineNumberExclusive; i++) {\n\t\t\t\t// `i` can be out of bound when the diff has not been updated yet.\n\t\t\t\t// In this case, we do an early return.\n\t\t\t\t// TODO@hediet: Fix this by applying the edit directly to the diff model, so that the diff is always valid.\n\t\t\t\tif (i > originalModel.getLineCount()) {\n\t\t\t\t\treturn { orig: origViewZones, mod: modViewZones };\n\t\t\t\t}\n\t\t\t\tdeletedCodeLineBreaksComputer?.addRequest(originalModel.getLineContent(i), null, null);\n\t\t\t}\n\t\t}\n\t}\n}\n\nconst lineBreakData = deletedCodeLineBreaksComputer?.finalize() ?? [];\nlet lineBreakDataIdx = 0;", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[3,3)", + "modifiedRange": "[3,4)", + "innerChanges": [ + { + "originalRange": "[3,1 -> 3,1]", + "modifiedRange": "[3,1 -> 4,1]" + } + ] + }, + { + "originalRange": "[6,7)", + "modifiedRange": "[7,14)", + "innerChanges": [ + { + "originalRange": "[6,1 -> 6,34]", + "modifiedRange": "[7,1 -> 13,34]" + }, + { + "originalRange": "[6,47 -> 6,61]", + "modifiedRange": "[13,47 -> 13,47]" + }, + { + "originalRange": "[6,69 -> 6,73]", + "modifiedRange": "[13,55 -> 13,55]" + }, + { + "originalRange": "[6,78 -> 6,81]", + "modifiedRange": "[13,60 -> 13,60]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-201713/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/issue-201713/legacy.expected.diff.json new file mode 100644 index 00000000000..57d210b6c64 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-201713/legacy.expected.diff.json @@ -0,0 +1,39 @@ +{ + "original": { + "content": "const deletedCodeLineBreaksComputer = !renderSideBySide ? this._editors.modified._getViewModel()?.createLineBreaksComputer() : undefined;\nif (deletedCodeLineBreaksComputer) {\n\tfor (const a of alignmentsVal) {\n\t\tif (a.diff) {\n\t\t\tfor (let i = a.originalRange.startLineNumber; i < a.originalRange.endLineNumberExclusive; i++) {\n\t\t\t\tdeletedCodeLineBreaksComputer?.addRequest(this._editors.original.getModel()!.getLineContent(i), null, null);\n\t\t\t}\n\t\t}\n\t}\n}\n\nconst lineBreakData = deletedCodeLineBreaksComputer?.finalize() ?? [];\nlet lineBreakDataIdx = 0;", + "fileName": "./1.tst" + }, + "modified": { + "content": "const deletedCodeLineBreaksComputer = !renderSideBySide ? this._editors.modified._getViewModel()?.createLineBreaksComputer() : undefined;\nif (deletedCodeLineBreaksComputer) {\n\tconst originalModel = this._editors.original.getModel()!;\n\tfor (const a of alignmentsVal) {\n\t\tif (a.diff) {\n\t\t\tfor (let i = a.originalRange.startLineNumber; i < a.originalRange.endLineNumberExclusive; i++) {\n\t\t\t\t// `i` can be out of bound when the diff has not been updated yet.\n\t\t\t\t// In this case, we do an early return.\n\t\t\t\t// TODO@hediet: Fix this by applying the edit directly to the diff model, so that the diff is always valid.\n\t\t\t\tif (i > originalModel.getLineCount()) {\n\t\t\t\t\treturn { orig: origViewZones, mod: modViewZones };\n\t\t\t\t}\n\t\t\t\tdeletedCodeLineBreaksComputer?.addRequest(originalModel.getLineContent(i), null, null);\n\t\t\t}\n\t\t}\n\t}\n}\n\nconst lineBreakData = deletedCodeLineBreaksComputer?.finalize() ?? [];\nlet lineBreakDataIdx = 0;", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[3,3)", + "modifiedRange": "[3,4)", + "innerChanges": null + }, + { + "originalRange": "[6,7)", + "modifiedRange": "[7,14)", + "innerChanges": [ + { + "originalRange": "[6,1 -> 6,1]", + "modifiedRange": "[7,1 -> 13,1]" + }, + { + "originalRange": "[6,47 -> 6,61]", + "modifiedRange": "[13,47 -> 13,47]" + }, + { + "originalRange": "[6,69 -> 6,73]", + "modifiedRange": "[13,55 -> 13,55]" + }, + { + "originalRange": "[6,78 -> 6,81]", + "modifiedRange": "[13,60 -> 13,60]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/advanced.expected.diff.json index e5ad584f33e..71a6075ada8 100644 --- a/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/advanced.expected.diff.json @@ -75,7 +75,7 @@ "modifiedRange": "[23,25)", "innerChanges": [ { - "originalRange": "[25,9 -> 26,13]", + "originalRange": "[25,9 -> 26,18]", "modifiedRange": "[23,9 -> 23,18]" }, { diff --git a/src/vs/editor/test/node/diffing/fixtures/word-shared-letters/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/word-shared-letters/advanced.expected.diff.json index 7d964a94419..9396ddae0c8 100644 --- a/src/vs/editor/test/node/diffing/fixtures/word-shared-letters/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/word-shared-letters/advanced.expected.diff.json @@ -112,7 +112,7 @@ "innerChanges": [ { "originalRange": "[53,8 -> 53,17]", - "modifiedRange": "[53,8 -> 53,28]" + "modifiedRange": "[53,8 -> 53,31]" } ] } From c75ad1fd11fb0d8d24621db4408c93c240033f0b Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 24 Jan 2024 11:41:01 +0100 Subject: [PATCH 0582/1897] Fix for Sticky Scroll Focus Space Key Issue (#203314) sticky scroll focus "Space" fix --- src/vs/base/browser/ui/tree/abstractTree.ts | 6 ++++-- src/vs/workbench/browser/actions/listCommands.ts | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index f6d5ac1ad81..788d9c23852 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1767,8 +1767,10 @@ class StickyScrollFocus extends Disposable { private _domHasFocus: boolean = false; private get domHasFocus(): boolean { return this._domHasFocus; } private set domHasFocus(hasFocus: boolean) { - this._domHasFocus = hasFocus; - this._onDidChangeHasFocus.fire(hasFocus); + if (hasFocus !== this._domHasFocus) { + this._onDidChangeHasFocus.fire(hasFocus); + this._domHasFocus = hasFocus; + } } constructor( diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index e4482953dd8..33f21c3b41b 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -266,7 +266,7 @@ function revealFocusedStickyScroll(tree: ObjectTree | DataTree widget.setSelection([focus])); + revealFocusedStickyScroll(widget); } }); From fa7c021a82036a3ecc3a3e6aa70c41e499b8e954 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 24 Jan 2024 12:54:39 +0100 Subject: [PATCH 0583/1897] SCM - fix treeview comparison function (#203325) --- .../contrib/scm/browser/scmViewPane.ts | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 562bc927780..336f381c964 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1171,28 +1171,16 @@ export class SCMTreeSorter implements ITreeSorter { return 1; } - if (isSCMViewSeparator(one)) { - if (!isSCMHistoryItemGroupTreeElement(other) && !isSCMResourceGroup(other)) { - throw new Error('Invalid comparison'); - } - - return 0; + if (isSCMResourceGroup(one)) { + return isSCMResourceGroup(other) ? 0 : -1; } - if (isSCMResourceGroup(one)) { - if (!isSCMResourceGroup(other)) { - throw new Error('Invalid comparison'); - } - - return 0; + if (isSCMViewSeparator(one)) { + return isSCMResourceGroup(other) ? 1 : -1; } if (isSCMHistoryItemGroupTreeElement(one)) { - if (!isSCMHistoryItemGroupTreeElement(other) && !isSCMResourceGroup(other) && !isSCMViewSeparator(other)) { - throw new Error('Invalid comparison'); - } - - return 0; + return isSCMHistoryItemGroupTreeElement(other) ? 0 : 1; } if (isSCMHistoryItemTreeElement(one)) { From 1f607d67c507091d70c66e9bedfb7704560f87e7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 24 Jan 2024 13:08:06 +0100 Subject: [PATCH 0584/1897] fix https://github.com/microsoft/vscode-copilot/issues/3652 (#203305) --- .../contrib/inlineChat/browser/inlineChatController.ts | 2 +- .../contrib/inlineChat/browser/inlineChatStrategies.ts | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index fd19bd595a9..bdc49d917ef 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -819,7 +819,7 @@ export class InlineChatController implements IEditorContribution { this._resetWidget(); - this._strategy?.pause?.(); + this._strategy?.dispose?.(); this._session = undefined; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index dab83d92c17..4c347325e64 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -61,7 +61,6 @@ export abstract class EditModeStrategy { readonly onDidDiscard: Event = this._onDidDiscard.event; toggleDiff?: () => any; - pause?: () => any; constructor( protected readonly _session: Session, @@ -466,10 +465,6 @@ export class LiveStrategy extends EditModeStrategy { } } - override pause = () => { - this._ctxCurrentChangeShowsDiff.reset(); - }; - async apply() { this._resetDiff(); if (this._editCount > 0) { From 7970376dda21f53bb592d6f723a589f89975991f Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 24 Jan 2024 13:08:41 +0100 Subject: [PATCH 0585/1897] Protect against empty readonly-message (#203328) Fixes #203122 --- src/vs/workbench/api/common/extHostFileSystem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostFileSystem.ts b/src/vs/workbench/api/common/extHostFileSystem.ts index 8d06a8dd025..dc740ddf397 100644 --- a/src/vs/workbench/api/common/extHostFileSystem.ts +++ b/src/vs/workbench/api/common/extHostFileSystem.ts @@ -166,7 +166,7 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { } let readOnlyMessage: IMarkdownString | undefined; - if (options.isReadonly && isMarkdownString(options.isReadonly)) { + if (options.isReadonly && isMarkdownString(options.isReadonly) && options.isReadonly.value !== '') { readOnlyMessage = { value: options.isReadonly.value, isTrusted: options.isReadonly.isTrusted, From 4bc3583c4e20ff3e24a17f387377503ff155eaea Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 24 Jan 2024 13:48:40 +0100 Subject: [PATCH 0586/1897] handle `rerun` message during make_request state (#203333) handle `rerun` message during make_request state, fixes https://github.com/microsoft/vscode-copilot/issues/3650 --- .../contrib/inlineChat/browser/inlineChatController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index bdc49d917ef..9fb003a71d6 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -716,7 +716,7 @@ export class InlineChatController implements IEditorContribution { return State.PAUSE; } else if (message & Message.ACCEPT_SESSION) { return State.ACCEPT; - } else if (message & Message.ACCEPT_INPUT) { + } else if (message & (Message.ACCEPT_INPUT | Message.RERUN_INPUT)) { return State.MAKE_REQUEST; } else { return State.APPLY_RESPONSE; From 5375e374812f080ae23755a78f2b2dd27b61e94b Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 24 Jan 2024 13:49:08 +0100 Subject: [PATCH 0587/1897] Error: Cannot register two commands with the same id: workbench.action.focusCommentsPanel (#203331) Third time's the charm Fixes #202563 --- .../contrib/comments/browser/comments.contribution.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts index 6545fa6c444..b47cdb2b883 100644 --- a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts +++ b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts @@ -17,21 +17,11 @@ import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/co import { COMMENTS_VIEW_ID } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; import { CommentThreadState } from 'vs/editor/common/languages'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { CONTEXT_KEY_HAS_COMMENTS, CONTEXT_KEY_SOME_COMMENTS_EXPANDED, CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsView'; import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { Codicon } from 'vs/base/common/codicons'; -CommandsRegistry.registerCommand({ - id: 'workbench.action.focusCommentsPanel', - handler: async (accessor) => { - const viewsService = accessor.get(IViewsService); - viewsService.openView(COMMENTS_VIEW_ID, true); - } -}); - registerAction2(class Collapse extends ViewAction { constructor() { super({ From c9040d0567113043893ab3c10e75f393860fc34b Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:49:21 +0100 Subject: [PATCH 0588/1897] Fix Drag n Drop behavior in Open Editors with multiple groups (#203330) fix #203179 --- src/vs/base/browser/ui/list/listView.ts | 11 ++++++++++- src/vs/base/browser/ui/list/listWidget.ts | 3 +-- .../files/browser/views/openEditorsView.ts | 19 +++++++++++++++---- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 4f8ab395ce6..5465520c08c 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -1221,7 +1221,7 @@ export class ListView implements IListView { feedback = distinct(feedback).filter(i => i >= -1 && i < this.length).sort((a, b) => a - b); feedback = feedback[0] === -1 ? [-1] : feedback; - const dragOverEffectPosition = typeof result !== 'boolean' && result.effect && result.effect.position ? result.effect.position : ListDragOverEffectPosition.Over; + let dragOverEffectPosition = typeof result !== 'boolean' && result.effect && result.effect.position ? result.effect.position : ListDragOverEffectPosition.Over; if (equalsDragFeedback(this.currentDragFeedback, feedback) && this.currentDragFeedbackPosition === dragOverEffectPosition) { return true; @@ -1244,6 +1244,15 @@ export class ListView implements IListView { throw new Error('Can\'t use multiple feedbacks with position different than \'over\''); } + // Make sure there is no flicker when moving between two items + // Always use the before feedback if possible + if (dragOverEffectPosition === ListDragOverEffectPosition.After) { + if (feedback[0] < this.length - 1) { + feedback[0] += 1; + dragOverEffectPosition = ListDragOverEffectPosition.Before; + } + } + for (const index of feedback) { const item = this.items[index]!; item.dropTarget = true; diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 4d61198158c..1a72e4e64d0 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -979,14 +979,13 @@ export class DefaultStyleController implements IStyleController { if (styles.listDropBetweenBackground) { content.push(` .monaco-list${suffix} .monaco-list-rows.drop-target-before .monaco-list-row:first-child::before, - .monaco-list${suffix} .monaco-list-row.drop-target-after + .monaco-list-row::before, .monaco-list${suffix} .monaco-list-row.drop-target-before::before { content: ""; position: absolute; top: 0px; left: 0px; width: 100%; height: 1px; background-color: ${styles.listDropBetweenBackground}; }`); content.push(` .monaco-list${suffix} .monaco-list-rows.drop-target-after .monaco-list-row:last-child::after, - .monaco-list${suffix} .monaco-list-row:last-child.drop-target-after::after { + .monaco-list${suffix} .monaco-list-row.drop-target-after::after { content: ""; position: absolute; bottom: 0px; left: 0px; width: 100%; height: 1px; background-color: ${styles.listDropBetweenBackground}; }`); diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index d38538f3e97..e4b7b0ca2c9 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -53,6 +53,7 @@ import { Schemas } from 'vs/base/common/network'; import { extUriIgnorePathCase } from 'vs/base/common/resources'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { mainWindow } from 'vs/base/browser/window'; +import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView'; const $ = dom.$; @@ -727,7 +728,7 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop Date: Wed, 24 Jan 2024 15:03:59 +0100 Subject: [PATCH 0589/1897] Clarify custom title bar style setting description (#203336) fix #203290 --- src/vs/workbench/electron-sandbox/desktop.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 1c38985809b..f1e76e53c99 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -245,7 +245,7 @@ import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from 'vs/platform/window/electron-sand ], 'default': isLinux ? 'never' : 'auto', 'scope': ConfigurationScope.APPLICATION, - 'description': localize('window.customTitleBarVisibility', "Adjust when the custom title bar should be shown."), + 'markdownDescription': localize('window.customTitleBarVisibility', "Adjust when the custom title bar should be shown. The custom title bar can be hidden when in full screen mode with `windowed`. The custom title bar can only be hidden in none full screen mode with `never` when `#window.titleBarStyle#` is set to `native`."), }, 'window.dialogStyle': { 'type': 'string', From 97b87273430f80c2179d258d337539c4c1bc2937 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 24 Jan 2024 16:09:02 +0100 Subject: [PATCH 0590/1897] Git - fix viewing stahes with added/deleted/renamed files (#203341) --- extensions/git/src/commands.ts | 19 +++++--- extensions/git/src/git.ts | 75 +++++++++++++++++++++++++++++--- extensions/git/src/repository.ts | 2 +- 3 files changed, 84 insertions(+), 12 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 6a15bd94455..acf699eeca3 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3708,19 +3708,26 @@ export class CommandCenter { return; } - const stashFiles = await repository.showStash(stash.index); + const stashChanges = await repository.showStash(stash.index); - if (!stashFiles || stashFiles.length === 0) { + if (!stashChanges || stashChanges.length === 0) { return; } const title = `Git Stash #${stash.index}: ${stash.description}`; const multiDiffSourceUri = toGitUri(Uri.file(repository.root), `stash@{${stash.index}}`, { scheme: 'git-stash' }); - const resources: { originalUri: Uri; modifiedUri: Uri }[] = []; - for (const file of stashFiles) { - const fileUri = Uri.file(path.join(repository.root, file)); - resources.push({ originalUri: fileUri, modifiedUri: toGitUri(fileUri, `stash@{${stash.index}}`) }); + const resources: { originalUri: Uri | undefined; modifiedUri: Uri | undefined }[] = []; + for (const change of stashChanges) { + if (change.status === Status.INDEX_ADDED) { + resources.push({ originalUri: undefined, modifiedUri: toGitUri(change.uri, `stash@{${stash.index}}`) }); + } else if (change.status === Status.DELETED) { + resources.push({ originalUri: change.uri, modifiedUri: undefined }); + } else if (change.status === Status.INDEX_RENAMED) { + resources.push({ originalUri: change.originalUri, modifiedUri: toGitUri(change.uri, `stash@{${stash.index}}`) }); + } else { + resources.push({ originalUri: change.uri, modifiedUri: toGitUri(change.uri, `stash@{${stash.index}}`) }); + } } commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 2c8be161784..f86c5ad99f5 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -997,6 +997,70 @@ function parseGitStashes(raw: string): Stash[] { return result; } +// TODO@lszomoru - adopt in diffFiles() +function parseGitChanges(repositoryRoot: string, raw: string): Change[] { + let index = 0; + const result: Change[] = []; + const segments = raw.trim() + .split('\x00') + .filter(s => s); + + segmentsLoop: + while (index < segments.length - 1) { + const change = segments[index++]; + const resourcePath = segments[index++]; + + if (!change || !resourcePath) { + break; + } + + const originalUri = Uri.file(path.isAbsolute(resourcePath) ? resourcePath : path.join(repositoryRoot, resourcePath)); + + let uri = originalUri; + let renameUri = originalUri; + let status = Status.UNTRACKED; + + // Copy or Rename status comes with a number (ex: 'R100'). + // We don't need the number, we use only first character of the status. + switch (change[0]) { + case 'A': + status = Status.INDEX_ADDED; + break; + + case 'M': + status = Status.MODIFIED; + break; + + case 'D': + status = Status.DELETED; + break; + + // Rename contains two paths, the second one is what the file is renamed/copied to. + case 'R': { + if (index >= segments.length) { + break; + } + + const newPath = segments[index++]; + if (!newPath) { + break; + } + + status = Status.INDEX_RENAMED; + uri = renameUri = Uri.file(path.isAbsolute(newPath) ? newPath : path.join(repositoryRoot, newPath)); + break; + } + default: + // Unknown status + break segmentsLoop; + } + + result.push({ status, uri, originalUri, renameUri }); + } + + return result; +} + export interface PullOptions { unshallow?: boolean; tags?: boolean; @@ -2147,15 +2211,16 @@ export class Repository { } } - async showStash(index: number): Promise { - const args = ['stash', 'show', `stash@{${index}}`, '--name-only']; + async showStash(index: number): Promise { + const args = ['stash', 'show', `stash@{${index}}`, '--name-status', '-z']; try { const result = await this.exec(args); + if (result.exitCode) { + return []; + } - return result.stdout.trim() - .split('\n') - .filter(line => !!line); + return parseGitChanges(this.repositoryRoot, result.stdout.trim()); } catch (err) { if (/No stash found/.test(err.stderr || '')) { return undefined; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 698ab8affd5..70e4e98c43a 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1995,7 +1995,7 @@ export class Repository implements Disposable { return await this.run(Operation.Stash, () => this.repository.applyStash(index)); } - async showStash(index: number): Promise { + async showStash(index: number): Promise { return await this.run(Operation.Stash, () => this.repository.showStash(index)); } From 5fea9b4f26fd9de09820a78c9da643d0ff6b8498 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 24 Jan 2024 16:54:56 +0100 Subject: [PATCH 0591/1897] Setting default enablement state to false for the experiment (#203324) setting default enablement state to false for the experiment --- src/vs/editor/common/config/editorOptions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 5d150a58558..720944ca3e1 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2810,7 +2810,7 @@ export type EditorStickyScrollOptions = Readonly { constructor() { - const defaults: EditorStickyScrollOptions = { enabled: true, maxLineCount: 5, defaultModel: 'outlineModel', scrollWithEditor: true }; + const defaults: EditorStickyScrollOptions = { enabled: false, maxLineCount: 5, defaultModel: 'outlineModel', scrollWithEditor: true }; super( EditorOption.stickyScroll, 'stickyScroll', defaults, { From cc6f22bf75abaf22cd12e817e4fd716448a8ee62 Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 25 Jan 2024 01:49:21 +0900 Subject: [PATCH 0592/1897] chore: update builds for electron@27.2.3 (#203346) * chore: update builds for electron@27.2.3 * chore: bump distro --- .yarnrc | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.yarnrc b/.yarnrc index 12a0b977bd9..c95a4be2afa 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,5 +1,5 @@ disturl "https://electronjs.org/headers" target "27.2.3" -ms_build_id "26378995" +ms_build_id "26495564" runtime "electron" build_from_source "true" diff --git a/package.json b/package.json index e9cd097d3e8..835c8df8c7a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.86.0", - "distro": "2124c72ef2dcad044b30293f7548a7cc63f113fe", + "distro": "bfe80ecb51ea0f99e6c250ba625938dcc55461c1", "author": { "name": "Microsoft Corporation" }, From 0c109dbcdec16d415bebe5ef2ff7b1e8c9cf9d14 Mon Sep 17 00:00:00 2001 From: Benjamin Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 24 Jan 2024 18:15:22 +0100 Subject: [PATCH 0593/1897] Remove redundant canActivityBarBeHidden() method (#203351) canActivityBarBeHidden() no longer needed --- src/vs/workbench/browser/layout.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index e8322cbf07d..ccdbaf9d6bd 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -702,13 +702,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } - // Activity bar cannot be hidden - // This check must be called after state is set - // because canActivityBarBeHidden calls isVisible - if (this.stateModel.getRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN) && !this.canActivityBarBeHidden()) { - this.stateModel.setRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN, false); - } - // Window border this.updateWindowsBorder(true); } @@ -1742,18 +1735,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private setActivityBarHidden(hidden: boolean, skipLayout?: boolean): void { - if (hidden && !this.canActivityBarBeHidden()) { - return; - } this.stateModel.setRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN, hidden); // Propagate to grid this.workbenchGrid.setViewVisible(this.activityBarPartView, !hidden); } - private canActivityBarBeHidden(): boolean { - return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP; - } - private setBannerHidden(hidden: boolean): void { this.workbenchGrid.setViewVisible(this.bannerPartView, !hidden); } From b96127eea4f71b47daca57e3e4f8b9271833866b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 24 Jan 2024 09:17:09 -0800 Subject: [PATCH 0594/1897] fix #203266 --- .../platform/accessibility/browser/accessibilityService.ts | 6 +++++- src/vs/platform/accessibility/common/accessibility.ts | 1 + .../accessibility/test/common/testAccessibilityService.ts | 1 + src/vs/platform/audioCues/browser/audioCueService.ts | 4 ++-- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/accessibility/browser/accessibilityService.ts b/src/vs/platform/accessibility/browser/accessibilityService.ts index 1db5b0a24e5..bd84abbc6dc 100644 --- a/src/vs/platform/accessibility/browser/accessibilityService.ts +++ b/src/vs/platform/accessibility/browser/accessibilityService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { addDisposableListener } from 'vs/base/browser/dom'; -import { alert } from 'vs/base/browser/ui/aria/aria'; +import { alert, status } from 'vs/base/browser/ui/aria/aria'; import { mainWindow } from 'vs/base/browser/window'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -110,4 +110,8 @@ export class AccessibilityService extends Disposable implements IAccessibilitySe alert(message: string): void { alert(message); } + + status(message: string): void { + status(message); + } } diff --git a/src/vs/platform/accessibility/common/accessibility.ts b/src/vs/platform/accessibility/common/accessibility.ts index b370b1e0267..d6a89b230f8 100644 --- a/src/vs/platform/accessibility/common/accessibility.ts +++ b/src/vs/platform/accessibility/common/accessibility.ts @@ -21,6 +21,7 @@ export interface IAccessibilityService { getAccessibilitySupport(): AccessibilitySupport; setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void; alert(message: string): void; + status(message: string): void; } export const enum AccessibilitySupport { diff --git a/src/vs/platform/accessibility/test/common/testAccessibilityService.ts b/src/vs/platform/accessibility/test/common/testAccessibilityService.ts index 0789812b905..f8dbe1da875 100644 --- a/src/vs/platform/accessibility/test/common/testAccessibilityService.ts +++ b/src/vs/platform/accessibility/test/common/testAccessibilityService.ts @@ -19,4 +19,5 @@ export class TestAccessibilityService implements IAccessibilityService { setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void { } getAccessibilitySupport(): AccessibilitySupport { return AccessibilitySupport.Unknown; } alert(message: string): void { } + status(message: string): void { } } diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 6de9aaf6c4e..295b3c65f4d 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -59,7 +59,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { public async playAudioCue(cue: AudioCue, options: IAudioCueOptions = {}): Promise { const alertMessage = cue.alertMessage; if (this.isAlertEnabled(cue, options.userGesture) && alertMessage) { - this.accessibilityService.alert(alertMessage); + this.accessibilityService.status(alertMessage); } if (this.isCueEnabled(cue, options.userGesture)) { @@ -75,7 +75,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { const cueArray = cues.map(c => 'cue' in c ? c.cue : c); const alerts = cueArray.filter(cue => this.isAlertEnabled(cue)).map(c => c.alertMessage); if (alerts.length) { - this.accessibilityService.alert(alerts.join(', ')); + this.accessibilityService.status(alerts.join(', ')); } // Some audio cues might reuse sounds. Don't play the same sound twice. From 4fb1e923f931ab52a8fb2b20a9f02a6342edf9bc Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 24 Jan 2024 18:45:07 +0100 Subject: [PATCH 0595/1897] :lipstick: move new save-functions close to existing saveAll function (#203356) --- src/vscode-dts/vscode.d.ts | 46 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index b35ff0b13f4..9393c2ff51e 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -13286,6 +13286,29 @@ declare module 'vscode' { */ export function findFiles(include: GlobPattern, exclude?: GlobPattern | null, maxResults?: number, token?: CancellationToken): Thenable; + /** + * Saves the editor identified by the given resource and returns the resulting resource or `undefined` + * if save was not successful or no editor with the given resource was found. + * + * **Note** that an editor with the provided resource must be opened in order to be saved. + * + * @param uri the associated uri for the opened editor to save. + * @returns A thenable that resolves when the save operation has finished. + */ + export function save(uri: Uri): Thenable; + + /** + * Saves the editor identified by the given resource to a new file name as provided by the user and + * returns the resulting resource or `undefined` if save was not successful or cancelled or no editor + * with the given resource was found. + * + * **Note** that an editor with the provided resource must be opened in order to be saved as. + * + * @param uri the associated uri for the opened editor to save as. + * @returns A thenable that resolves when the save-as operation has finished. + */ + export function saveAs(uri: Uri): Thenable; + /** * Save all dirty files. * @@ -13649,29 +13672,6 @@ declare module 'vscode' { * Event that fires when the current workspace has been trusted. */ export const onDidGrantWorkspaceTrust: Event; - - /** - * Saves the editor identified by the given resource and returns the resulting resource or `undefined` - * if save was not successful or no editor with the given resource was found. - * - * **Note** that an editor with the provided resource must be opened in order to be saved. - * - * @param uri the associated uri for the opened editor to save. - * @returns A thenable that resolves when the save operation has finished. - */ - export function save(uri: Uri): Thenable; - - /** - * Saves the editor identified by the given resource to a new file name as provided by the user and - * returns the resulting resource or `undefined` if save was not successful or cancelled or no editor - * with the given resource was found. - * - * **Note** that an editor with the provided resource must be opened in order to be saved as. - * - * @param uri the associated uri for the opened editor to save as. - * @returns A thenable that resolves when the save-as operation has finished. - */ - export function saveAs(uri: Uri): Thenable; } /** From 8e5d9da1e4a2f97bd00537f1cbf9798cf02416f1 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 24 Jan 2024 09:53:41 -0800 Subject: [PATCH 0596/1897] fix #203197 --- .../contrib/speech/common/speechService.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/speech/common/speechService.ts b/src/vs/workbench/contrib/speech/common/speechService.ts index 807dc5f7a95..1e0bdc569c3 100644 --- a/src/vs/workbench/contrib/speech/common/speechService.ts +++ b/src/vs/workbench/contrib/speech/common/speechService.ts @@ -16,6 +16,7 @@ import { ILogService } from 'vs/platform/log/common/log'; export const ISpeechService = createDecorator('speechService'); export const HasSpeechProvider = new RawContextKey('hasSpeechProvider', false, { type: 'string', description: localize('hasSpeechProvider', "A speech provider is registered to the speech service.") }); +export const SpeechInProgress = new RawContextKey('speechInProgress', false, { type: 'string', description: localize('speechInProgress', "A speech session is in progress.") }); export interface ISpeechProviderMetadata { readonly extension: ExtensionIdentifier; @@ -109,12 +110,21 @@ export class SpeechService extends Disposable implements ISpeechService { private readonly providers = new Map(); private readonly hasSpeechProviderContext = HasSpeechProvider.bindTo(this.contextKeyService); + private readonly speechInProgress = SpeechInProgress.bindTo(this.contextKeyService); + + private readonly _onDidStartSpeechToTextSession = this._register(new Emitter()); + readonly onDidStartSpeechToTextSession = this._onDidStartSpeechToTextSession.event; + + private readonly _onDidEndSpeechToTextSession = this._register(new Emitter()); + readonly onDidEndSpeechToTextSession = this._onDidEndSpeechToTextSession.event; constructor( @ILogService private readonly logService: ILogService, @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); + this._register(this.onDidStartSpeechToTextSession(() => this.speechInProgress.set(true))); + this._register(this.onDidEndSpeechToTextSession(() => this.speechInProgress.reset())); } registerSpeechProvider(identifier: string, provider: ISpeechProvider): IDisposable { @@ -137,11 +147,6 @@ export class SpeechService extends Disposable implements ISpeechService { }); } - private readonly _onDidStartSpeechToTextSession = this._register(new Emitter()); - readonly onDidStartSpeechToTextSession = this._onDidStartSpeechToTextSession.event; - - private readonly _onDidEndSpeechToTextSession = this._register(new Emitter()); - readonly onDidEndSpeechToTextSession = this._onDidEndSpeechToTextSession.event; private _activeSpeechToTextSession: ISpeechToTextSession | undefined = undefined; get hasActiveSpeechToTextSession() { return !!this._activeSpeechToTextSession; } From 905ed4ffd5f5ac218de334cfd330e567968a241e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 24 Jan 2024 19:08:22 +0100 Subject: [PATCH 0597/1897] fix #203131 (#203360) --- src/vs/workbench/browser/parts/paneCompositeBar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/paneCompositeBar.ts b/src/vs/workbench/browser/parts/paneCompositeBar.ts index dacce52de48..4387df1a138 100644 --- a/src/vs/workbench/browser/parts/paneCompositeBar.ts +++ b/src/vs/workbench/browser/parts/paneCompositeBar.ts @@ -490,7 +490,7 @@ export class PaneCompositeBar extends Disposable { name: cachedViewContainer.name, order: cachedViewContainer.order, pinned: cachedViewContainer.pinned, - visible: cachedViewContainer.visible, + visible: cachedViewContainer.visible && !!this.getViewContainer(cachedViewContainer.id), }); } From 2d2f5bdb900688709a510d9d52e8e7bd5f4ffbaa Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 24 Jan 2024 08:33:04 -1000 Subject: [PATCH 0598/1897] debug: address triggered breakpoint feedback (#203363) Fixes #203154 Fixes #203155 Fixes #203156 Fixes #203158 --- .../browser/breakpointEditorContribution.ts | 2 +- .../contrib/debug/browser/breakpointWidget.ts | 17 +++++++++++++---- .../debug/browser/media/debug.contribution.css | 3 +++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 67df7ad254b..1c36e83c55c 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -457,7 +457,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi )); actions.push(new Action( 'addTriggeredBreakpoint', - nls.localize('addTriggeredBreakpoint', "Add Triggered Breakpoint.."), + nls.localize('addTriggeredBreakpoint', "Add Triggered Breakpoint..."), undefined, true, () => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.TRIGGER_POINT)) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 14ea1c09176..877c0921a4c 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -84,6 +84,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi private inputContainer!: HTMLElement; private selectBreakpointContainer!: HTMLElement; private input!: IActiveCodeEditor; + private selectBreakpointBox!: SelectBox; private toDispose: lifecycle.IDisposable[]; private conditionInput = ''; private hitCountInput = ''; @@ -205,7 +206,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi { text: nls.localize('expression', "Expression") }, { text: nls.localize('hitCount', "Hit Count") }, { text: nls.localize('logMessage', "Log Message") }, - { text: nls.localize('triggeredBy', "Wait For Breakpoint") }, + { text: nls.localize('triggeredBy', "Wait for Breakpoint") }, ], this.context, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('breakpointType', 'Breakpoint Type') }); this.selectContainer = $('.breakpoint-select-container'); selectBox.render(dom.append(container, this.selectContainer)); @@ -228,7 +229,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.updateContextInput(); // Due to an electron bug we have to do the timeout, otherwise we do not get focus - setTimeout(() => this.input.focus(), 150); + setTimeout(() => this.focusInput(), 150); } private createTriggerBreakpointInput(container: HTMLElement) { @@ -256,7 +257,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi ], select); }); - const selectBreakpointBox = new SelectBox([{ text: nls.localize('triggerByLoading', 'Loading...'), isDisabled: true }], 0, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('selectBreakpoint', 'Select breakpoint') }); + const selectBreakpointBox = this.selectBreakpointBox = new SelectBox([{ text: nls.localize('triggerByLoading', 'Loading...'), isDisabled: true }], 0, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('selectBreakpoint', 'Select breakpoint') }); selectBreakpointBox.onDidSelect(e => { if (e.index === 0) { this.triggeredByBreakpointInput = undefined; @@ -295,7 +296,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.setInputMode(); const value = this.getInputValue(this.breakpoint); this.input.getModel().setValue(value); - this.input.focus(); + this.focusInput(); } } @@ -455,6 +456,14 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.dispose(); } + private focusInput() { + if (this.context === Context.TRIGGER_POINT) { + this.selectBreakpointBox.focus(); + } else { + this.input.focus(); + } + } + override dispose(): void { super.dispose(); this.input.dispose(); diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index 36390d28f6a..269e0c453d9 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -31,6 +31,9 @@ margin-top: -1px; } + +.codicon-debug-breakpoint-conditional.codicon-debug-stackframe-focused::after, +.codicon-debug-breakpoint-conditional.codicon-debug-stackframe::after, .codicon-debug-breakpoint.codicon-debug-stackframe-focused::after, .codicon-debug-breakpoint.codicon-debug-stackframe::after { content: '\eb8a'; From f5d895ae04187711c0df92a74cb174588b91713c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 24 Jan 2024 19:44:10 +0100 Subject: [PATCH 0599/1897] VS Code Speech does not warn when no chat providers are found. (fix #203241) (#203365) --- .../chat/electron-sandbox/actions/voiceChatActions.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index aaa7195ba5a..08a689112f6 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -796,7 +796,8 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe @IInstantiationService instantiationService: IInstantiationService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, @IEditorService private readonly editorService: IEditorService, - @IHostService private readonly hostService: IHostService + @IHostService private readonly hostService: IHostService, + @IChatService private readonly chatService: IChatService ) { super(); @@ -811,6 +812,8 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe this.handleKeywordActivation(); })); + this._register(this.chatService.onDidRegisterProvider(() => this.updateConfiguration())); + this._register(this.speechService.onDidStartSpeechToTextSession(() => this.handleKeywordActivation())); this._register(this.speechService.onDidEndSpeechToTextSession(() => this.handleKeywordActivation())); @@ -826,8 +829,8 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe } private updateConfiguration(): void { - if (!this.speechService.hasSpeechProvider) { - return; // these settings require a speech provider + if (!this.speechService.hasSpeechProvider || this.chatService.getProviderInfos().length === 0) { + return; // these settings require a speech and chat provider } const registry = Registry.as(Extensions.Configuration); From 8494a40f51580026ba241617eb6847ff585b8f70 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 24 Jan 2024 17:55:50 +0100 Subject: [PATCH 0600/1897] Fixes #203186 --- .../multiDiffEditor/browser/multiDiffEditorInput.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 60b430fd744..65f6defe7ed 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -21,10 +21,11 @@ import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { localize } from 'vs/nls'; +import { ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; import { DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, EditorInputWithOptions, IEditorSerializer, IResourceMultiDiffEditorInput, ISaveOptions, IUntypedEditorInput } from 'vs/workbench/common/editor'; -import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput'; import { MultiDiffEditorIcon } from 'vs/workbench/contrib/multiDiffEditor/browser/icons.contribution'; import { ConstResolvedMultiDiffSource, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService'; import { ObservableLazyStatefulPromise } from 'vs/workbench/contrib/multiDiffEditor/browser/utils'; @@ -255,6 +256,15 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor } return undefined; } + + override readonly closeHandler: IEditorCloseHandler = { + async confirm() { + return ConfirmResult.DONT_SAVE; + }, + showConfirm() { + return false; + } + }; } function isUriDirty(textFileService: ITextFileService, uri: URI) { From c4ea2a881dea86cf90c17fa785db1d7288c52bdb Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 24 Jan 2024 20:19:27 +0100 Subject: [PATCH 0601/1897] :lipstick: --- .../contrib/speech/common/speechService.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/speech/common/speechService.ts b/src/vs/workbench/contrib/speech/common/speechService.ts index 1e0bdc569c3..48f21fb8ac1 100644 --- a/src/vs/workbench/contrib/speech/common/speechService.ts +++ b/src/vs/workbench/contrib/speech/common/speechService.ts @@ -16,7 +16,7 @@ import { ILogService } from 'vs/platform/log/common/log'; export const ISpeechService = createDecorator('speechService'); export const HasSpeechProvider = new RawContextKey('hasSpeechProvider', false, { type: 'string', description: localize('hasSpeechProvider', "A speech provider is registered to the speech service.") }); -export const SpeechInProgress = new RawContextKey('speechInProgress', false, { type: 'string', description: localize('speechInProgress', "A speech session is in progress.") }); +export const SpeechToTextInProgress = new RawContextKey('speechToTextInProgress', false, { type: 'string', description: localize('speechToTextInProgress', "A speech-to-text session is in progress.") }); export interface ISpeechProviderMetadata { readonly extension: ExtensionIdentifier; @@ -110,21 +110,12 @@ export class SpeechService extends Disposable implements ISpeechService { private readonly providers = new Map(); private readonly hasSpeechProviderContext = HasSpeechProvider.bindTo(this.contextKeyService); - private readonly speechInProgress = SpeechInProgress.bindTo(this.contextKeyService); - - private readonly _onDidStartSpeechToTextSession = this._register(new Emitter()); - readonly onDidStartSpeechToTextSession = this._onDidStartSpeechToTextSession.event; - - private readonly _onDidEndSpeechToTextSession = this._register(new Emitter()); - readonly onDidEndSpeechToTextSession = this._onDidEndSpeechToTextSession.event; constructor( @ILogService private readonly logService: ILogService, @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); - this._register(this.onDidStartSpeechToTextSession(() => this.speechInProgress.set(true))); - this._register(this.onDidEndSpeechToTextSession(() => this.speechInProgress.reset())); } registerSpeechProvider(identifier: string, provider: ISpeechProvider): IDisposable { @@ -147,10 +138,17 @@ export class SpeechService extends Disposable implements ISpeechService { }); } + private readonly _onDidStartSpeechToTextSession = this._register(new Emitter()); + readonly onDidStartSpeechToTextSession = this._onDidStartSpeechToTextSession.event; + + private readonly _onDidEndSpeechToTextSession = this._register(new Emitter()); + readonly onDidEndSpeechToTextSession = this._onDidEndSpeechToTextSession.event; private _activeSpeechToTextSession: ISpeechToTextSession | undefined = undefined; get hasActiveSpeechToTextSession() { return !!this._activeSpeechToTextSession; } + private readonly speechToTextInProgress = SpeechToTextInProgress.bindTo(this.contextKeyService); + createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { const provider = firstOrDefault(Array.from(this.providers.values())); if (!provider) { @@ -166,6 +164,7 @@ export class SpeechService extends Disposable implements ISpeechService { const onSessionStoppedOrCanceled = () => { if (session === this._activeSpeechToTextSession) { this._activeSpeechToTextSession = undefined; + this.speechToTextInProgress.reset(); this._onDidEndSpeechToTextSession.fire(); } @@ -181,6 +180,7 @@ export class SpeechService extends Disposable implements ISpeechService { switch (e.status) { case SpeechToTextStatus.Started: if (session === this._activeSpeechToTextSession) { + this.speechToTextInProgress.set(true); this._onDidStartSpeechToTextSession.fire(); } break; From 5b912041848a8e5a1a839910aa6a85d1d671e141 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 24 Jan 2024 21:15:51 +0100 Subject: [PATCH 0602/1897] SCM - do not show "View Commit" or "View All Changes" action for empty commits (#203381) --- extensions/git/package.json | 8 ++++---- src/vs/workbench/contrib/scm/browser/menus.ts | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index e909d5f5030..e9d12a97405 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1851,14 +1851,14 @@ "scm/incomingChanges/allChanges/context": [ { "command": "git.viewAllChanges", - "when": "scmProvider == git && config.multiDiffEditor.experimental.enabled", + "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", "group": "inline@1" } ], "scm/incomingChanges/historyItem/context": [ { "command": "git.viewCommit", - "when": "scmProvider == git && config.multiDiffEditor.experimental.enabled", + "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", "group": "inline@1" } ], @@ -1872,14 +1872,14 @@ "scm/outgoingChanges/allChanges/context": [ { "command": "git.viewAllChanges", - "when": "scmProvider == git && config.multiDiffEditor.experimental.enabled", + "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", "group": "inline@1" } ], "scm/outgoingChanges/historyItem/context": [ { "command": "git.viewCommit", - "when": "scmProvider == git && config.multiDiffEditor.experimental.enabled", + "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", "group": "inline@1" } ], diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index 968788eb6f5..22997f8c9c9 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -293,7 +293,11 @@ export class SCMHistoryProviderMenus implements ISCMHistoryProviderMenus, IDispo MenuId.SCMOutgoingChangesHistoryItemContext; } - result = this.menuService.createMenu(menuId, this.contextKeyService); + const contextKeyService = this.contextKeyService.createOverlay([ + ['scmHistoryItemFileCount', historyItem.statistics?.files ?? 0], + ]); + + result = this.menuService.createMenu(menuId, contextKeyService); this.historyItemMenus.set(historyItem, result); } From 17c3d007a792b2b6e32a1f791b27744466c73afb Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Wed, 24 Jan 2024 12:24:54 -0800 Subject: [PATCH 0603/1897] add check for empty diagnostic/range in code action highlighting (#203382) * add check for empty diagnostics/ranges * cleanup --- .../browser/codeActionController.ts | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index ad34973eddf..b91f85e6afd 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -279,19 +279,21 @@ export class CodeActionController extends Disposable implements IEditorContribut return { canPreview: !!action.action.edit?.edits.length }; }, onFocus: (action: CodeActionItem | undefined) => { - // If provider contributes ranges, then highlight contributed range over diagnostic range. - if (action && action.action.ranges) { + if (action && action.action) { + const ranges = action.action.ranges; + const diagnostics = action.action.diagnostics; currentDecorations.clear(); - const decorations: IModelDeltaDecoration[] = action.action.ranges.map(range => ({ range, options: CodeActionController.DECORATION })); - currentDecorations.set(decorations); - } else if (action && action.action.diagnostics) { - currentDecorations.clear(); - const decorations: IModelDeltaDecoration[] = action.action.diagnostics.map(diagnostic => ({ range: diagnostic, options: CodeActionController.DECORATION })); - currentDecorations.set(decorations); - const diagnostic = action.action.diagnostics[0]; - if (diagnostic.startLineNumber && diagnostic.startColumn) { - const selectionText = this._editor.getModel()?.getWordAtPosition({ lineNumber: diagnostic.startLineNumber, column: diagnostic.startColumn })?.word; - aria.status(localize('editingNewSelection', "Context: {0} at line {1} and column {2}.", selectionText, diagnostic.startLineNumber, diagnostic.startColumn)); + if (ranges && ranges.length > 0) { + const decorations: IModelDeltaDecoration[] = ranges.map(range => ({ range, options: CodeActionController.DECORATION })); + currentDecorations.set(decorations); + } else if (diagnostics && diagnostics.length > 0) { + const decorations: IModelDeltaDecoration[] = diagnostics.map(diagnostic => ({ range: diagnostic, options: CodeActionController.DECORATION })); + currentDecorations.set(decorations); + const diagnostic = diagnostics[0]; + if (diagnostic.startLineNumber && diagnostic.startColumn) { + const selectionText = this._editor.getModel()?.getWordAtPosition({ lineNumber: diagnostic.startLineNumber, column: diagnostic.startColumn })?.word; + aria.status(localize('editingNewSelection', "Context: {0} at line {1} and column {2}.", selectionText, diagnostic.startLineNumber, diagnostic.startColumn)); + } } } else { currentDecorations.clear(); From 1606308e1e0d6f3d2c2ad858fcc176db97ede376 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 24 Jan 2024 12:37:13 -0800 Subject: [PATCH 0604/1897] Add Bhavya to endgame notebook (#203383) She's cool --- .vscode/notebooks/my-endgame.github-issues | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index ddcb541f8ff..1031aa2c29f 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -157,7 +157,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:andreamah -author:bamurtaugh -author:bpasero -author:chrisdias -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:gregvanl -author:hediet -author:isidorn -author:joaomoreno -author:joyceerhl -author:jrieken -author:karrtikr -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:tanhakabir -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:paulacamargo25 -author:ulugbekna -author:aiday-mar -author:daviddossett" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:andreamah -author:bamurtaugh -author:bpasero -author:chrisdias -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:gregvanl -author:hediet -author:isidorn -author:joaomoreno -author:joyceerhl -author:jrieken -author:karrtikr -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:tanhakabir -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:paulacamargo25 -author:ulugbekna -author:aiday-mar -author:daviddossett -author:bhavyaus" }, { "kind": 1, From bd30771483b0d4d05a9f2184d144a1a38bbd62f4 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 24 Jan 2024 15:11:26 -0800 Subject: [PATCH 0605/1897] Add Justin to Endgame notebook (#203392) He is also cool. --- .vscode/notebooks/my-endgame.github-issues | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index 1031aa2c29f..b0bd9d5b3fb 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -157,7 +157,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:andreamah -author:bamurtaugh -author:bpasero -author:chrisdias -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:gregvanl -author:hediet -author:isidorn -author:joaomoreno -author:joyceerhl -author:jrieken -author:karrtikr -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:tanhakabir -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:paulacamargo25 -author:ulugbekna -author:aiday-mar -author:daviddossett -author:bhavyaus" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:andreamah -author:bamurtaugh -author:bpasero -author:chrisdias -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:gregvanl -author:hediet -author:isidorn -author:joaomoreno -author:joyceerhl -author:jrieken -author:karrtikr -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:tanhakabir -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:paulacamargo25 -author:ulugbekna -author:aiday-mar -author:daviddossett -author:bhavyaus -author:justschen" }, { "kind": 1, From dc10e26a05fe2f0329589739eae981dbbcdf6a9a Mon Sep 17 00:00:00 2001 From: Michael Rienstra Date: Wed, 24 Jan 2024 16:39:51 -0800 Subject: [PATCH 0606/1897] docs: document new `configuration.markdown.copyFiles.destination` options (#203391) docs: document new `configuration.markdown.copyFiles.destination` And clean-up / standardize existing docs of same --- .../package.nls.json | 2 +- .../languageFeatures/copyFiles/copyFiles.ts | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index b99593a6301..af77144f15c 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -64,7 +64,7 @@ "configuration.markdown.updateLinksOnFileMove.include.property": "The glob pattern to match file paths against. Set to true to enable the pattern.", "configuration.markdown.updateLinksOnFileMove.enableForDirectories": "Enable updating links when a directory is moved or renamed in the workspace.", "configuration.markdown.occurrencesHighlight.enabled": "Enable highlighting link occurrences in the current document.", - "configuration.markdown.copyFiles.destination": "Defines where files copied created by drop or paste should be created. This is a map from globs that match on the Markdown document to destinations.\n\nThe destinations may use the following variables:\n\n- `${documentFileName}` — The full filename of the Markdown document, for example: `readme.md`.\n- `${documentBaseName}` — The basename of Markdown document, for example: `readme`.\n- `${documentExtName}` — The extension of the Markdown document, for example: `md`.\n- `${documentDirName}` — The name of the Markdown document's parent directory.\n- `${documentWorkspaceFolder}` — The workspace folder for the Markdown document, for example: `/Users/me/myProject`. This is the same as `${documentDirName}` if the file is not part of a workspace.\n- `${fileName}` — The file name of the dropped file, for example: `image.png`.", + "configuration.markdown.copyFiles.destination": "Defines where files copied created by drop or paste should be created. This is a map from globs that match on the Markdown document to destinations.\n\nThe destinations may use the following variables:\n\n- `${documentDirName}` — Absolute parent directory path of the Markdown document, e.g. `/Users/me/myProject/docs`.\n- `${documentRelativeDirName}` — Relative parent directory path of the Markdown document, e.g. `docs`. This is the same as `${documentDirName}` if the file is not part of a workspace.\n- `${documentFileName}` — The full filename of the Markdown document, e.g. `README.md`.\n- `${documentBaseName}` — The basename of the Markdown document, e.g. `README`.\n- `${documentExtName}` — The extension of the Markdown document, e.g. `md`.\n- `${documentFilePath}` — Absolute path of the Markdown document, e.g. `/Users/me/myProject/docs/README.md`.\n- `${documentRelativeFilePath}` — Relative path of the Markdown document, e.g. `docs/README.md`. This is the same as `${documentFilePath}` if the file is not part of a workspace.\n- `${documentWorkspaceFolder}` — The workspace folder for the Markdown document, e.g. `/Users/me/myProject`. This is the same as `${documentDirName}` if the file is not part of a workspace.\n- `${fileName}` — The file name of the dropped file, e.g. `image.png`.\n- `${fileExtName}` — The extension of the dropped file, e.g. `png`.", "configuration.markdown.copyFiles.overwriteBehavior": "Controls if files created by drop or paste should overwrite existing files.", "configuration.markdown.copyFiles.overwriteBehavior.nameIncrementally": "If a file with the same name already exists, append a number to the file name, for example: `image.png` becomes `image-1.png`.", "configuration.markdown.copyFiles.overwriteBehavior.overwrite": "If a file with the same name already exists, overwrite it.", diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts index 299a8347b1c..dd41b5fa924 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts @@ -82,20 +82,20 @@ function resolveCopyDestinationSetting(documentUri: vscode.Uri, fileName: string const vars = new Map([ // Document - ['documentDirName', documentDirName.path], // Absolute parent directory path - ['documentRelativeDirName', workspaceFolder ? path.posix.relative(workspaceFolder.path, documentDirName.path) : documentDirName.path], // Absolute parent directory path - ['documentFileName', documentBaseName], // Full filename: file.md - ['documentBaseName', documentBaseName.slice(0, documentBaseName.length - documentExtName.length)], // Just the name: file - ['documentExtName', documentExtName.replace('.', '')], // The document ext (without dot): md - ['documentFilePath', documentUri.path], // Full document path - ['documentRelativeFilePath', workspaceFolder ? path.posix.relative(workspaceFolder.path, documentUri.path) : documentUri.path], // Full document path relative to workspace + ['documentDirName', documentDirName.path], // Absolute parent directory path of the Markdown document, e.g. `/Users/me/myProject/docs`. + ['documentRelativeDirName', workspaceFolder ? path.posix.relative(workspaceFolder.path, documentDirName.path) : documentDirName.path], // Relative parent directory path of the Markdown document, e.g. `docs`. This is the same as `${documentDirName}` if the file is not part of a workspace. + ['documentFileName', documentBaseName], // The full filename of the Markdown document, e.g. `README.md`. + ['documentBaseName', documentBaseName.slice(0, documentBaseName.length - documentExtName.length)], // The basename of the Markdown document, e.g. `README`. + ['documentExtName', documentExtName.replace('.', '')], // The extension of the Markdown document, e.g. `md`. + ['documentFilePath', documentUri.path], // Absolute path of the Markdown document, e.g. `/Users/me/myProject/docs/README.md`. + ['documentRelativeFilePath', workspaceFolder ? path.posix.relative(workspaceFolder.path, documentUri.path) : documentUri.path], // Relative path of the Markdown document, e.g. `docs/README.md`. This is the same as `${documentFilePath}` if the file is not part of a workspace. // Workspace - ['documentWorkspaceFolder', ((workspaceFolder ?? documentDirName).path)], + ['documentWorkspaceFolder', ((workspaceFolder ?? documentDirName).path)], // The workspace folder for the Markdown document, e.g. `/Users/me/myProject`. This is the same as `${documentDirName}` if the file is not part of a workspace. // File - ['fileName', fileName], // Full file name - ['fileExtName', path.extname(fileName).replace('.', '')], // File extension (without dot): png + ['fileName', fileName], // The file name of the dropped file, e.g. `image.png`. + ['fileExtName', path.extname(fileName).replace('.', '')], // The extension of the dropped file, e.g. `png`. ]); return outDest.replaceAll(/(?\\\$)|(?\w+)(?:\/(?(?:\\\/|[^\}\/])+)\/(?(?:\\\/|[^\}\/])*)\/)?\}/g, (match, _escape, name, pattern, replacement, _offset, _str, groups) => { From b02edaa75a39dfbc67fe6194065a3613f4ea69f8 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 24 Jan 2024 17:49:34 -0800 Subject: [PATCH 0607/1897] Add Ben to endgame notebook (#203403) He is also cool! --- .vscode/notebooks/my-endgame.github-issues | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index b0bd9d5b3fb..6adad9d8532 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -157,7 +157,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:andreamah -author:bamurtaugh -author:bpasero -author:chrisdias -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:gregvanl -author:hediet -author:isidorn -author:joaomoreno -author:joyceerhl -author:jrieken -author:karrtikr -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:tanhakabir -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:paulacamargo25 -author:ulugbekna -author:aiday-mar -author:daviddossett -author:bhavyaus -author:justschen" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:andreamah -author:bamurtaugh -author:bpasero -author:chrisdias -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:gregvanl -author:hediet -author:isidorn -author:joaomoreno -author:joyceerhl -author:jrieken -author:karrtikr -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:tanhakabir -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:paulacamargo25 -author:ulugbekna -author:aiday-mar -author:daviddossett -author:bhavyaus -author:justschen -author:benibenj" }, { "kind": 1, From 3dea5cbbcb88d7f6dad71148f3d3f98b853ab836 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 24 Jan 2024 18:54:32 -0800 Subject: [PATCH 0608/1897] Pick up latest markdown language service (#203405) Picking up the latest stable release. This is mainly just a tag release compared to alpha-8 --- extensions/markdown-language-features/server/package.json | 4 ++-- extensions/markdown-language-features/server/yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/markdown-language-features/server/package.json b/extensions/markdown-language-features/server/package.json index 8d641861217..5c389810979 100644 --- a/extensions/markdown-language-features/server/package.json +++ b/extensions/markdown-language-features/server/package.json @@ -1,7 +1,7 @@ { "name": "vscode-markdown-languageserver", "description": "Markdown language server", - "version": "0.4.0-alpha.8", + "version": "0.4.0", "author": "Microsoft Corporation", "license": "MIT", "engines": { @@ -18,7 +18,7 @@ "vscode-languageserver": "^8.1.0", "vscode-languageserver-textdocument": "^1.0.8", "vscode-languageserver-types": "^3.17.3", - "vscode-markdown-languageservice": "^0.4.0-alpha.8", + "vscode-markdown-languageservice": "^0.4.0", "vscode-uri": "^3.0.7" }, "devDependencies": { diff --git a/extensions/markdown-language-features/server/yarn.lock b/extensions/markdown-language-features/server/yarn.lock index fcb85f9a94b..f3a8efb6023 100644 --- a/extensions/markdown-language-features/server/yarn.lock +++ b/extensions/markdown-language-features/server/yarn.lock @@ -128,10 +128,10 @@ vscode-languageserver@^8.1.0: dependencies: vscode-languageserver-protocol "3.17.3" -vscode-markdown-languageservice@^0.4.0-alpha.8: - version "0.4.0-alpha.8" - resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.4.0-alpha.8.tgz#5c3aaf3b3cd3f6b309f8dfcf93cbfb1397041adc" - integrity sha512-6S6RE5s+4biWg2xk9bpwNi6GihUYQIVxdO3I+jb/XDyvfmqYVxrN86cKLF8QSbaQvX3fMuBAxBLFfX93FdJi3w== +vscode-markdown-languageservice@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.4.0.tgz#1ccca383703d38043b58e096fd3224796a2b2ab5" + integrity sha512-3C8pZlC0ofHEYmWwHgenxL6//XrpkrgyytrqNpMlft46q9uBxSUfcXtEGt7wIDNLWsvmgqPqHBwEnBFtLwrWFA== dependencies: "@vscode/l10n" "^0.0.10" node-html-parser "^6.1.5" From a87e69f04892d8757ee5086436f40970a2a2b18c Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 25 Jan 2024 09:20:18 +0100 Subject: [PATCH 0609/1897] reset `holdMode` when losing window focus (#203368) fyi @ulugbekna, re https://github.com/microsoft/vscode/issues/203187 --- .../keybinding/browser/keybindingService.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 1345094497c..708268ba3c3 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -287,10 +287,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { // for single modifier chord keybindings (e.g. shift shift) disposables.add(dom.addDisposableListener(window, dom.EventType.KEY_UP, (e: KeyboardEvent) => { - if (this._keybindingHoldMode) { - this._keybindingHoldMode.complete(); - this._keybindingHoldMode = null; - } + this._resetKeybindingHoldMode(); this.isComposingGlobalContextKey.set(e.isComposing); const keyEvent = new StandardKeyboardEvent(e); const shouldPreventDefault = this._singleModifierDispatch(keyEvent, keyEvent.target); @@ -405,10 +402,23 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { return undefined; } this._keybindingHoldMode = new DeferredPromise(); + const focusTracker = dom.trackFocus(dom.getWindow(undefined)); + const listener = focusTracker.onDidBlur(() => this._resetKeybindingHoldMode()); + this._keybindingHoldMode.p.finally(() => { + listener.dispose(); + focusTracker.dispose(); + }); this._log(`+ Enabled hold-mode for ${commandId}.`); return this._keybindingHoldMode.p; } + private _resetKeybindingHoldMode(): void { + if (this._keybindingHoldMode) { + this._keybindingHoldMode?.complete(); + this._keybindingHoldMode = null; + } + } + public override customKeybindingsCount(): number { return this.userKeybindings.keybindings.length; } From 4da46535190c445603200ad62d10965185814bb3 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 25 Jan 2024 10:18:37 +0100 Subject: [PATCH 0610/1897] Use descriptive reaction hover on whole comment reaction, not just count (#203421) Fixes https://github.com/microsoft/vscode-pull-request-github/issues/978 --- src/vs/workbench/contrib/comments/browser/reactionsAction.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/comments/browser/reactionsAction.ts b/src/vs/workbench/contrib/comments/browser/reactionsAction.ts index 14c771d3134..fc77ef38ccf 100644 --- a/src/vs/workbench/contrib/comments/browser/reactionsAction.ts +++ b/src/vs/workbench/contrib/comments/browser/reactionsAction.ts @@ -48,7 +48,6 @@ export class ReactionActionViewItem extends ActionViewItem { const reactionIcon = dom.append(this.label, dom.$('.reaction-icon')); const uri = URI.revive(action.icon); reactionIcon.style.backgroundImage = dom.asCSSUrl(uri); - reactionIcon.title = action.label; } if (action.count) { const reactionCount = dom.append(this.label, dom.$('span.reaction-count')); From b10846f1e5064190d571bfb37596bf5bfd7fb356 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 25 Jan 2024 10:23:30 +0100 Subject: [PATCH 0611/1897] only convert a selection when it is a selection (#203423) re https://github.com/microsoft/vscode-copilot/issues/3758 --- src/vs/workbench/api/common/extHostInlineChat.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostInlineChat.ts b/src/vs/workbench/api/common/extHostInlineChat.ts index 72fbb41c99f..c6e4266df75 100644 --- a/src/vs/workbench/api/common/extHostInlineChat.ts +++ b/src/vs/workbench/api/common/extHostInlineChat.ts @@ -84,7 +84,7 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { return { initialRange: v.initialRange ? typeConvert.Range.from(v.initialRange) : undefined, - initialSelection: v.initialSelection ? typeConvert.Selection.from(v.initialSelection) : undefined, + initialSelection: extHostTypes.Selection.isSelection(v.initialSelection) ? typeConvert.Selection.from(v.initialSelection) : undefined, message: v.message, autoSend: v.autoSend, position: v.position ? typeConvert.Position.from(v.position) : undefined, From 589d37d2e8b04ad1d08913363782baae9c29668c Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 25 Jan 2024 10:33:42 +0100 Subject: [PATCH 0612/1897] restore inline suggestion (#203418) * restore inline suggestion fixes https://github.com/microsoft/vscode/issues/175190 * fix leak in test --- .../contrib/suggest/browser/suggestInlineCompletions.ts | 9 ++++++--- .../test/browser/suggestInlineCompletions.test.ts | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/suggest/browser/suggestInlineCompletions.ts b/src/vs/editor/contrib/suggest/browser/suggestInlineCompletions.ts index 2aa208f3576..81d6f7d4d8f 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestInlineCompletions.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestInlineCompletions.ts @@ -6,7 +6,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { FuzzyScore } from 'vs/base/common/filters'; import { Iterable } from 'vs/base/common/iterator'; -import { RefCountedDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, RefCountedDisposable } 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'; @@ -104,7 +104,7 @@ class InlineCompletionResults extends RefCountedDisposable implements InlineComp } -export class SuggestInlineCompletions implements InlineCompletionsProvider { +export class SuggestInlineCompletions extends Disposable implements InlineCompletionsProvider { private _lastResult?: InlineCompletionResults; @@ -113,7 +113,10 @@ export class SuggestInlineCompletions implements InlineCompletionsProvider { diff --git a/src/vs/editor/contrib/suggest/test/browser/suggestInlineCompletions.test.ts b/src/vs/editor/contrib/suggest/test/browser/suggestInlineCompletions.test.ts index 2a52c3166bc..1aaca84d484 100644 --- a/src/vs/editor/contrib/suggest/test/browser/suggestInlineCompletions.test.ts +++ b/src/vs/editor/contrib/suggest/test/browser/suggestInlineCompletions.test.ts @@ -76,7 +76,7 @@ suite('Suggest Inline Completions', function () { test('Aggressive inline completions when typing within line #146948', async function () { - const completions: SuggestInlineCompletions = insta.createInstance(SuggestInlineCompletions); + const completions: SuggestInlineCompletions = disposables.add(insta.createInstance(SuggestInlineCompletions)); { // (1,3), end of word -> suggestions @@ -92,7 +92,7 @@ suite('Suggest Inline Completions', function () { }); test('Snippets show in inline suggestions even though they are turned off #175190', async function () { - const completions: SuggestInlineCompletions = insta.createInstance(SuggestInlineCompletions); + const completions: SuggestInlineCompletions = disposables.add(insta.createInstance(SuggestInlineCompletions)); { // unfiltered From f055b096fdcb95df4b7f5c79a2f78a61f1a63c36 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 25 Jan 2024 11:40:46 +0100 Subject: [PATCH 0613/1897] show message when accept or discard is need for saving (#203427) re https://github.com/microsoft/vscode-copilot/issues/3653 --- .../contrib/inlineChat/browser/inlineChatController.ts | 4 ++++ .../contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 9fb003a71d6..be5046ad137 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -974,6 +974,10 @@ export class InlineChatController implements IEditorContribution { // ---- controller API + showSaveHint(): void { + const status = localize('savehint', "Accept or discard changes to continue saving"); + this._zone.value.widget.updateStatus(status, { classes: ['warn'] }); + } setPlaceholder(text: string): void { this._forcedPlaceholder = text; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts index 133923b13fe..23f8f2cf917 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts @@ -29,6 +29,7 @@ import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/commo import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { Event } from 'vs/base/common/event'; +import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; interface SessionData { readonly resourceUri: URI; @@ -241,6 +242,7 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService { break; } this._inlineChatSessionService.moveSession(data.session, editor); + InlineChatController.get(editor)?.showSaveHint(); this._logService.info('WAIT for session to end', editor.getId(), data.session.targetUri.toString()); await this._whenSessionsEnded(Iterable.single(data), token); } From a1168b16d9afbb7ed9a2f28acd0af6b932caef5f Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 25 Jan 2024 11:41:43 +0100 Subject: [PATCH 0614/1897] Readonly hover stays on top of dialog (#203424) Fixes #203176 --- src/vs/editor/contrib/message/browser/messageController.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/message/browser/messageController.ts b/src/vs/editor/contrib/message/browser/messageController.ts index 6c27301f8b3..dd32227654c 100644 --- a/src/vs/editor/contrib/message/browser/messageController.ts +++ b/src/vs/editor/contrib/message/browser/messageController.ts @@ -70,7 +70,10 @@ export class MessageController implements IEditorContribution { this._messageListeners.clear(); this._message = isMarkdownString(message) ? renderMarkdown(message, { actionHandler: { - callback: (url) => openLinkFromMarkdown(this._openerService, url, isMarkdownString(message) ? message.isTrusted : undefined), + callback: (url) => { + this.closeMessage(); + openLinkFromMarkdown(this._openerService, url, isMarkdownString(message) ? message.isTrusted : undefined); + }, disposables: this._messageListeners }, }) : undefined; From 290a1153b8dbf6171c0b087c07d3c47dc2c8f1f3 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 25 Jan 2024 11:52:51 +0100 Subject: [PATCH 0615/1897] disable frequently failing chat-suite (#203430) https://github.com/microsoft/vscode/issues/203429 --- .../vscode-api-tests/src/singlefolder-tests/chat.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index d88eea64c96..f0a3a304064 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -8,7 +8,8 @@ import 'mocha'; import { CancellationToken, chat, ChatAgentRequest, ChatVariableLevel, Disposable, interactive, InteractiveSession, ProviderResult } from 'vscode'; import { assertNoRpc, closeAllEditors, DeferredPromise, disposeAll } from '../utils'; -suite('chat', () => { +suite.skip('chat', () => { + let disposables: Disposable[] = []; setup(() => { disposables = []; From a908e9d6d5a7e3dcfae58268625c46f5326bfe92 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 25 Jan 2024 11:56:50 +0100 Subject: [PATCH 0616/1897] Bump distro (#203431) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 835c8df8c7a..7720eab38e3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.86.0", - "distro": "bfe80ecb51ea0f99e6c250ba625938dcc55461c1", + "distro": "6c14240015f9a76b34c29b4212fb8df761106443", "author": { "name": "Microsoft Corporation" }, From b0d1f894c38a163ac23e400d41c800decede6117 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 25 Jan 2024 12:25:45 +0100 Subject: [PATCH 0617/1897] speech recording stops after log pause in audio (fix #203189) (#203433) --- .../browser/actions/chatExecuteActions.ts | 5 ++++ .../actions/voiceChatActions.ts | 26 +++++++++---------- .../electron-sandbox/inlineChatActions.ts | 3 ++- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 9798132da98..49ed40adf22 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -12,9 +12,14 @@ import { IChatWidget, IChatWidgetService } from 'vs/workbench/contrib/chat/brows import { CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_CHAT_REQUEST_IN_PROGRESS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +export interface IVoiceChatExecuteActionContext { + readonly disableTimeout?: boolean; +} + export interface IChatExecuteActionContext { widget?: IChatWidget; inputValue?: string; + voice?: IVoiceChatExecuteActionContext; } export class SubmitAction extends Action2 { diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 08a689112f6..bbaf3fa124d 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -262,7 +262,7 @@ class VoiceChatSessions { @IConfigurationService private readonly configurationService: IConfigurationService ) { } - async start(controller: IVoiceChatSessionController): Promise { + async start(controller: IVoiceChatSessionController, context?: IChatExecuteActionContext): Promise { this.stop(); const sessionId = ++this.voiceChatSessionIds; @@ -304,7 +304,7 @@ class VoiceChatSessions { case SpeechToTextStatus.Recognizing: if (text) { session.controller.updateInput([inputValue, text].join(' ')); - if (voiceChatTimeout > 0) { + if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true) { acceptTranscriptionScheduler.cancel(); } } @@ -313,7 +313,7 @@ class VoiceChatSessions { if (text) { inputValue = [inputValue, text].join(' '); session.controller.updateInput(inputValue); - if (voiceChatTimeout > 0) { + if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true) { acceptTranscriptionScheduler.schedule(); } } @@ -408,12 +408,12 @@ export class VoiceChatInChatViewAction extends Action2 { }); } - async run(accessor: ServicesAccessor): Promise { + async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { const instantiationService = accessor.get(IInstantiationService); const controller = await VoiceChatSessionControllerFactory.create(accessor, 'view'); if (controller) { - VoiceChatSessions.getInstance(instantiationService).start(controller); + VoiceChatSessions.getInstance(instantiationService).start(controller, context); } } } @@ -435,12 +435,12 @@ export class InlineVoiceChatAction extends Action2 { }); } - async run(accessor: ServicesAccessor): Promise { + async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { const instantiationService = accessor.get(IInstantiationService); const controller = await VoiceChatSessionControllerFactory.create(accessor, 'inline'); if (controller) { - VoiceChatSessions.getInstance(instantiationService).start(controller); + VoiceChatSessions.getInstance(instantiationService).start(controller, context); } } } @@ -462,12 +462,12 @@ export class QuickVoiceChatAction extends Action2 { }); } - async run(accessor: ServicesAccessor): Promise { + async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { const instantiationService = accessor.get(IInstantiationService); const controller = await VoiceChatSessionControllerFactory.create(accessor, 'quick'); if (controller) { - VoiceChatSessions.getInstance(instantiationService).start(controller); + VoiceChatSessions.getInstance(instantiationService).start(controller, context); } } } @@ -500,11 +500,11 @@ export class StartVoiceChatAction extends Action2 { }); } - async run(accessor: ServicesAccessor, context: unknown): Promise { + async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { const instantiationService = accessor.get(IInstantiationService); const commandService = accessor.get(ICommandService); - const widget = (context as IChatExecuteActionContext)?.widget; + const widget = context?.widget; if (widget) { // if we already get a context when the action is executed // from a toolbar within the chat widget, then make sure @@ -518,10 +518,10 @@ export class StartVoiceChatAction extends Action2 { const controller = await VoiceChatSessionControllerFactory.create(accessor, 'focused'); if (controller) { - VoiceChatSessions.getInstance(instantiationService).start(controller); + VoiceChatSessions.getInstance(instantiationService).start(controller, context); } else { // fallback to Quick Voice Chat command - commandService.executeCommand(QuickVoiceChatAction.ID); + commandService.executeCommand(QuickVoiceChatAction.ID, context); } } } diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts index a6b3bd9a496..7af9b3971bc 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts @@ -16,6 +16,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { StartVoiceChatAction, StopListeningAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; +import { IChatExecuteActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; import { CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_VISIBLE, InlineChatConfigKeys } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { HasSpeechProvider, ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; @@ -95,7 +96,7 @@ function holdForSpeech(accessor: ServicesAccessor, ctrl: InlineChatController | let listening = false; const handle = disposableTimeout(() => { // start VOICE input - commandService.executeCommand(StartVoiceChatAction.ID); + commandService.executeCommand(StartVoiceChatAction.ID, { voice: { disableTimeout: true } } satisfies IChatExecuteActionContext); listening = true; }, 250); From 5e10388f1eb21694c0d36e2f04e1e173388bc1f0 Mon Sep 17 00:00:00 2001 From: Lei LI Date: Thu, 25 Jan 2024 11:31:57 +0800 Subject: [PATCH 0618/1897] Fix typo in shellIntegration-bash.sh fix typo `exceute` to `execute` in `shellIntegration-bash.sh`. --- .../contrib/terminal/browser/media/shellIntegration-bash.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh index a7b0b2cfb94..e8c7ac8bada 100755 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh @@ -22,7 +22,7 @@ if [ "$VSCODE_INJECTION" == "1" ]; then if [ -r /etc/profile ]; then . /etc/profile fi - # exceute the first that exists + # execute the first that exists if [ -r ~/.bash_profile ]; then . ~/.bash_profile elif [ -r ~/.bash_login ]; then From adf93c270acf019a7ab0385ff5d9a66eba7aafc3 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 25 Jan 2024 15:53:20 +0100 Subject: [PATCH 0619/1897] Git - view stash should use the stash's parent commit for the left hand side (#203450) --- extensions/git/src/commands.ts | 13 +++++----- extensions/git/src/git.ts | 47 ++++++++++++++++------------------ 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index acf699eeca3..33bd2ce2393 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3598,8 +3598,8 @@ export class CommandCenter { return; } - await result.repository.popStash(result.stash.index); await commands.executeCommand('workbench.action.closeActiveEditor'); + await result.repository.popStash(result.stash.index); } @command('git.stashApply', { repository: true }) @@ -3633,8 +3633,8 @@ export class CommandCenter { return; } - await result.repository.applyStash(result.stash.index); await commands.executeCommand('workbench.action.closeActiveEditor'); + await result.repository.applyStash(result.stash.index); } @command('git.stashDrop', { repository: true }) @@ -3709,6 +3709,7 @@ export class CommandCenter { } const stashChanges = await repository.showStash(stash.index); + const stashParentCommit = stash.parents.length > 0 ? stash.parents[0] : `${stash.hash}^`; if (!stashChanges || stashChanges.length === 0) { return; @@ -3720,13 +3721,13 @@ export class CommandCenter { const resources: { originalUri: Uri | undefined; modifiedUri: Uri | undefined }[] = []; for (const change of stashChanges) { if (change.status === Status.INDEX_ADDED) { - resources.push({ originalUri: undefined, modifiedUri: toGitUri(change.uri, `stash@{${stash.index}}`) }); + resources.push({ originalUri: undefined, modifiedUri: toGitUri(change.uri, stash.hash) }); } else if (change.status === Status.DELETED) { - resources.push({ originalUri: change.uri, modifiedUri: undefined }); + resources.push({ originalUri: toGitUri(change.uri, stashParentCommit), modifiedUri: undefined }); } else if (change.status === Status.INDEX_RENAMED) { - resources.push({ originalUri: change.originalUri, modifiedUri: toGitUri(change.uri, `stash@{${stash.index}}`) }); + resources.push({ originalUri: toGitUri(change.originalUri, stashParentCommit), modifiedUri: toGitUri(change.uri, stash.hash) }); } else { - resources.push({ originalUri: change.uri, modifiedUri: toGitUri(change.uri, `stash@{${stash.index}}`) }); + resources.push({ originalUri: toGitUri(change.uri, stashParentCommit), modifiedUri: toGitUri(change.uri, stash.hash) }); } } diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index f86c5ad99f5..119fce2815d 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -35,9 +35,11 @@ export interface IFileStatus { } export interface Stash { - index: number; - description: string; - branchName?: string; + readonly hash: string; + readonly parents: string[]; + readonly index: number; + readonly description: string; + readonly branchName?: string; } interface MutableRemote extends Remote { @@ -353,6 +355,7 @@ function sanitizePath(path: string): string { } const COMMIT_FORMAT = '%H%n%aN%n%aE%n%at%n%ct%n%P%n%D%n%B'; +const STASH_FORMAT = '%H%n%P%n%gd%n%gs'; export interface ICloneOptions { readonly parentPath: string; @@ -965,34 +968,28 @@ export function parseLsFiles(raw: string): LsFilesElement[] { .map(([, mode, object, stage, file]) => ({ mode, object, stage, file })); } +const stashRegex = /([0-9a-f]{40})\n(.*)\nstash@{(\d+)}\n(WIP\s)*on([^:]+):(.*)(?:\x00)/gmi; + function parseGitStashes(raw: string): Stash[] { const result: Stash[] = []; - const regex = /^stash@{(\d+)}:(.+)$/; - const descriptionRegex = /(WIP\s)*on([^:]+):(.*)$/i; - for (const stash of raw.split('\n').filter(s => !!s)) { - // Extract index and description - const match = regex.exec(stash); - if (!match) { - continue; + let match, hash, parents, index, wip, branchName, description; + + do { + match = stashRegex.exec(raw); + if (match === null) { + break; } - const [, index, description] = match; - - // Extract branch name from description - const descriptionMatch = descriptionRegex.exec(description); - if (!descriptionMatch) { - result.push({ index: parseInt(index), description: description.trim() }); - continue; - } - - const [, wip, branchName, message] = descriptionMatch; + [, hash, parents, index, wip, branchName, description] = match; result.push({ + hash, + parents: parents.split(' '), index: parseInt(index), - description: wip ? `WIP (${message.trim()})` : message.trim(), - branchName: branchName.trim() + branchName: branchName.trim(), + description: wip ? `WIP (${description.trim()})` : description.trim() }); - } + } while (true); return result; } @@ -2212,7 +2209,7 @@ export class Repository { } async showStash(index: number): Promise { - const args = ['stash', 'show', `stash@{${index}}`, '--name-status', '-z']; + const args = ['stash', 'show', `stash@{${index}}`, '--name-status', '-z', '-u']; try { const result = await this.exec(args); @@ -2500,7 +2497,7 @@ export class Repository { } async getStashes(): Promise { - const result = await this.exec(['stash', 'list']); + const result = await this.exec(['stash', 'list', `--format=${STASH_FORMAT}`, '-z']); return parseGitStashes(result.stdout.trim()); } From 5cbd1a836b1186b96bb945e6b4240dec5787db03 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 25 Jan 2024 16:21:47 +0100 Subject: [PATCH 0620/1897] release inline chat session for untitled file when it becomes saved (non-dirty) (#203454) fixes https://github.com/microsoft/vscode/issues/203448 --- .../browser/inlineChatSessionServiceImpl.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index 5e211c6ac6d..6784574fc21 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -23,6 +23,7 @@ import { ITextModel, IValidEditOperation } from 'vs/editor/common/model'; import { Schemas } from 'vs/base/common/network'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { generateUuid } from 'vs/base/common/uuid'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; type SessionData = { editor: ICodeEditor; @@ -58,6 +59,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, @ILogService private readonly _logService: ILogService, @IInstantiationService private readonly _instaService: IInstantiationService, + @ITextFileService private readonly _textFileService: ITextFileService, ) { } dispose() { @@ -123,6 +125,18 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { targetUri.with({ scheme: Schemas.inMemory, query: new URLSearchParams({ id, 'inline-chat-textModel0': '' }).toString() }), true )); + // untitled documents are special + if (targetUri.scheme === Schemas.untitled) { + const untitledTextModel = this._textFileService.untitled.get(targetUri); + if (untitledTextModel) { + store.add(untitledTextModel.onDidChangeDirty(() => { + if (!untitledTextModel.isDirty()) { + this.releaseSession(session); + } + })); + } + } + let wholeRange = options.wholeRange; if (!wholeRange) { wholeRange = rawSession.wholeRange ? Range.lift(rawSession.wholeRange) : editor.getSelection(); From 05664832f5586a7c6b3bdd5b06cac1de11d48a3c Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 25 Jan 2024 17:52:38 +0100 Subject: [PATCH 0621/1897] multicursor for inline completions --- .../browser/inlineCompletionsModel.ts | 59 +++++- .../browser/inlineCompletionsProvider.test.ts | 196 ++++++++++++++++++ 2 files changed, 247 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 1307a18785c..83df0374823 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -7,14 +7,16 @@ import { mapFindFirst } from 'vs/base/common/arraysFind'; import { BugIndicatingError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; import { IObservable, IReader, ITransaction, autorun, derived, derivedHandleChanges, derivedOpts, recomputeInitiallyAndOnChange, observableSignal, observableValue, subtransaction, transaction } from 'vs/base/common/observable'; +import { commonPrefixLength } from 'vs/base/common/strings'; import { isDefined } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { InlineCompletionContext, InlineCompletionTriggerKind } from 'vs/editor/common/languages'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; -import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; +import { EndOfLinePreference, IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; import { IFeatureDebounceInformation } from 'vs/editor/common/services/languageFeatureDebounce'; import { GhostText, GhostTextOrReplacement, ghostTextOrReplacementEquals } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; import { InlineCompletionWithUpdatedRange, InlineCompletionsSource } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource'; @@ -24,6 +26,7 @@ import { addPositions, lengthOfText } from 'vs/editor/contrib/inlineCompletions/ import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { InlineCompletionItem } from 'vs/editor/contrib/inlineCompletions/browser/provideInlineCompletions'; export enum VersionIdChangeReason { Undo, @@ -304,10 +307,11 @@ export class InlineCompletionsModel extends Disposable { editor.setPosition(completion.snippetInfo.range.getStartPosition(), 'inlineCompletionAccept'); SnippetController2.get(editor)?.insert(completion.snippetInfo.snippet, { undoStopBefore: false }); } else { + const primaryEdit = EditOperation.replace(completion.range, completion.insertText); editor.executeEdits( 'inlineSuggestion.accept', [ - EditOperation.replaceMove(completion.range, completion.insertText), + ...this._getEdits(editor, completion, primaryEdit).edits, ...completion.additionalTextEdits ] ); @@ -409,11 +413,12 @@ export class InlineCompletionsModel extends Disposable { this._isAcceptingPartially = true; try { editor.pushUndoStop(); - editor.executeEdits('inlineSuggestion.accept', [ - EditOperation.replace(Range.fromPositions(position), partialText), - ]); - const length = lengthOfText(partialText); - editor.setPosition(addPositions(position, length), 'inlineCompletionPartialAccept'); + const primaryEditRange = Range.fromPositions(completion.range.getStartPosition(), position); + const primaryEditText = completion.insertText.substring(0, firstPart.column + acceptUntilIndexExclusive - 1); + const primaryEdit = EditOperation.replace(primaryEditRange, primaryEditText); + const edits = this._getEdits(editor, completion, primaryEdit, firstPart.column + acceptUntilIndexExclusive - 1); + editor.executeEdits('inlineSuggestion.accept', edits.edits); + editor.setSelections(edits.editorSelections, 'inlineCompletionPartialAccept'); } finally { this._isAcceptingPartially = false; } @@ -433,6 +438,44 @@ export class InlineCompletionsModel extends Disposable { } } + private _getEdits(editor: ICodeEditor, completion: InlineCompletionItem, primaryEdit: ISingleEditOperation, acceptInsertTextUntilIndexExclusive?: number): { edits: IIdentifiedSingleEditOperation[]; editorSelections: ISelection[] } { + + const selections = editor.getSelections() ?? []; + const secondaryPositions = selections.slice(1).map(selection => selection.getPosition()); + const primaryPosition = selections[0].getPosition(); + const textModel = editor.getModel()!; + const replacedTextAfterPrimaryCursor = textModel + .getLineContent(primaryPosition.lineNumber) + .substring(primaryPosition.column - 1, completion.range.endColumn - 1); + const secondaryEditText = completion.insertText.substring(primaryPosition.column - completion.range.startColumn, acceptInsertTextUntilIndexExclusive); + const edits = [ + primaryEdit, + ...secondaryPositions.map(pos => { + const textAfterSecondaryCursor = this.textModel + .getLineContent(pos.lineNumber) + .substring(pos.column - 1); + const l = commonPrefixLength(replacedTextAfterPrimaryCursor, textAfterSecondaryCursor); + const range = Range.fromPositions(pos, pos.delta(0, l)); + return EditOperation.replaceMove(range, secondaryEditText); + }) + ]; + + let editorSelections: ISelection[] = []; + if (acceptInsertTextUntilIndexExclusive !== undefined) { + const primaryEditLength = lengthOfText(primaryEdit.text ?? ''); + const secondaryEditLength = lengthOfText(secondaryEditText); + editorSelections = [ + Selection.fromPositions(addPositions(new Position(primaryEdit.range.startLineNumber, primaryEdit.range.startColumn), primaryEditLength)), + ...secondaryPositions.map(pos => Selection.fromPositions(addPositions(pos, secondaryEditLength))) + ]; + } + + return { + edits, + editorSelections + }; + } + public handleSuggestAccepted(item: SuggestItemInfo) { const itemEdit = item.toSingleTextEdit().removeCommonPrefix(this.textModel); const augmentedCompletion = this._computeAugmentedCompletion(itemEdit, undefined); diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts index 4a836a2b3d5..3555531a759 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts @@ -21,6 +21,7 @@ import { ITestCodeEditor, TestCodeEditorInstantiationOptions, withAsyncTestCodeE import { createTextModel } from 'vs/editor/test/common/testTextModel'; import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { Selection } from 'vs/editor/common/core/selection'; suite('Inline Completions', () => { ensureNoDisposablesAreLeakedInTestSuite(); @@ -561,6 +562,201 @@ suite('Inline Completions', () => { } ); }); + + suite('inlineCompletionMultiCursor', () => { + + test('Basic', async function () { + const provider = new MockInlineCompletionsProvider(); + await withAsyncTestCodeEditorAndInlineCompletionsModel('', + { fakeClock: true, provider }, + async ({ editor, editorViewModel, model, context }) => { + context.keyboardType('console\nconsole\n'); + editor.setSelections([ + new Selection(1, 1000, 1, 1000), + new Selection(2, 1000, 2, 1000), + ]); + provider.setReturnValue({ + insertText: 'console.log("hello");', + range: new Range(1, 1, 1, 1000), + }); + model.triggerExplicitly(); + await timeout(1000); + model.accept(editor); + assert.deepStrictEqual( + editor.getValue(), + [ + `console.log("hello");`, + `console.log("hello");`, + `` + ].join('\n') + ); + } + ); + }); + + test('Multi Part', async function () { + const provider = new MockInlineCompletionsProvider(); + await withAsyncTestCodeEditorAndInlineCompletionsModel('', + { fakeClock: true, provider }, + async ({ editor, editorViewModel, model, context }) => { + context.keyboardType('console.log()\nconsole.log\n'); + editor.setSelections([ + new Selection(1, 12, 1, 12), + new Selection(2, 1000, 2, 1000), + ]); + provider.setReturnValue({ + insertText: 'console.log("hello");', + range: new Range(1, 1, 1, 1000), + }); + model.triggerExplicitly(); + await timeout(1000); + model.accept(editor); + assert.deepStrictEqual( + editor.getValue(), + [ + `console.log("hello");`, + `console.log("hello");`, + `` + ].join('\n') + ); + } + ); + }); + + test('Multi Part and Different Cursor Columns', async function () { + const provider = new MockInlineCompletionsProvider(); + await withAsyncTestCodeEditorAndInlineCompletionsModel('', + { fakeClock: true, provider }, + async ({ editor, editorViewModel, model, context }) => { + context.keyboardType('console.log()\nconsole.warn\n'); + editor.setSelections([ + new Selection(1, 12, 1, 12), + new Selection(2, 14, 2, 14), + ]); + provider.setReturnValue({ + insertText: 'console.log("hello");', + range: new Range(1, 1, 1, 1000), + }); + model.triggerExplicitly(); + await timeout(1000); + model.accept(editor); + assert.deepStrictEqual( + editor.getValue(), + [ + `console.log("hello");`, + `console.warn("hello");`, + `` + ].join('\n') + ); + } + ); + }); + + async function acceptNextWord(model: InlineCompletionsModel, editor: ITestCodeEditor, timesToAccept: number = 1): Promise { + for (let i = 0; i < timesToAccept; i++) { + await model.acceptNextWord(editor); + } + } + + test('Basic Partial Completion', async function () { + const provider = new MockInlineCompletionsProvider(); + await withAsyncTestCodeEditorAndInlineCompletionsModel('', + { fakeClock: true, provider }, + async ({ editor, editorViewModel, model, context }) => { + context.keyboardType('let\nlet\n'); + editor.setSelections([ + new Selection(1, 1000, 1, 1000), + new Selection(2, 1000, 2, 1000), + ]); + + provider.setReturnValue({ + insertText: `let a = 'some word'; `, + range: new Range(1, 1, 1, 1000), + }); + + model.triggerExplicitly(); + await timeout(1000); + + await acceptNextWord(model, editor, 2); + + assert.deepStrictEqual( + editor.getValue(), + [ + `let a`, + `let a`, + `` + ].join('\n') + ); + } + ); + }); + + test('Partial Multi-Part Completion', async function () { + const provider = new MockInlineCompletionsProvider(); + await withAsyncTestCodeEditorAndInlineCompletionsModel('', + { fakeClock: true, provider }, + async ({ editor, editorViewModel, model, context }) => { + context.keyboardType('for ()\nfor \n'); + editor.setSelections([ + new Selection(1, 5, 1, 5), + new Selection(2, 1000, 2, 1000), + ]); + + provider.setReturnValue({ + insertText: `for (let i = 0; i < 10; i++) {`, + range: new Range(1, 1, 1, 1000), + }); + + model.triggerExplicitly(); + await timeout(1000); + + await acceptNextWord(model, editor, 3); + + assert.deepStrictEqual( + editor.getValue(), + [ + `for (let i)`, + `for (let i`, + `` + ].join('\n') + ); + } + ); + }); + + test('Partial Mutli-Part and Different Cursor Columns Completion', async function () { + const provider = new MockInlineCompletionsProvider(); + await withAsyncTestCodeEditorAndInlineCompletionsModel('', + { fakeClock: true, provider }, + async ({ editor, editorViewModel, model, context }) => { + context.keyboardType(`console.log()\nconsole.warnnnn\n`); + editor.setSelections([ + new Selection(1, 12, 1, 12), + new Selection(2, 16, 2, 16), + ]); + + provider.setReturnValue({ + insertText: `console.log("hello" + " " + "world");`, + range: new Range(1, 1, 1, 1000), + }); + + model.triggerExplicitly(); + await timeout(1000); + + await acceptNextWord(model, editor, 4); + + assert.deepStrictEqual( + editor.getValue(), + [ + `console.log("hello" + )`, + `console.warnnnn("hello" + `, + `` + ].join('\n') + ); + } + ); + }); + }); }); async function withAsyncTestCodeEditorAndInlineCompletionsModel( From fde80ce4fbde1c406f07025d65d4730465ca5148 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 25 Jan 2024 08:56:24 -0800 Subject: [PATCH 0622/1897] eng: fix missing rustup command in build (#203462) Only call rustup when explicitly installing --- build/azure-pipelines/cli/cli-compile-and-publish.yml | 2 +- build/azure-pipelines/cli/install-rust-posix.yml | 1 + build/azure-pipelines/cli/test.yml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/cli/cli-compile-and-publish.yml b/build/azure-pipelines/cli/cli-compile-and-publish.yml index 4f0623b84fc..a9aaac73254 100644 --- a/build/azure-pipelines/cli/cli-compile-and-publish.yml +++ b/build/azure-pipelines/cli/cli-compile-and-publish.yml @@ -29,7 +29,7 @@ steps: displayName: Set product.json path - ${{ if parameters.VSCODE_CHECK_ONLY }}: - - script: rustup component add clippy && cargo clippy --target ${{ parameters.VSCODE_CLI_TARGET }} --bin=code + - script: cargo clippy --target ${{ parameters.VSCODE_CLI_TARGET }} --bin=code displayName: Lint ${{ parameters.VSCODE_CLI_TARGET }} workingDirectory: $(Build.SourcesDirectory)/cli env: diff --git a/build/azure-pipelines/cli/install-rust-posix.yml b/build/azure-pipelines/cli/install-rust-posix.yml index 00e3ecbdc51..78641cfb3a8 100644 --- a/build/azure-pipelines/cli/install-rust-posix.yml +++ b/build/azure-pipelines/cli/install-rust-posix.yml @@ -33,6 +33,7 @@ steps: set -e rustup default $RUSTUP_TOOLCHAIN rustup update $RUSTUP_TOOLCHAIN + rustup component add clippy env: RUSTUP_TOOLCHAIN: ${{ parameters.channel }} displayName: "Set Rust version" diff --git a/build/azure-pipelines/cli/test.yml b/build/azure-pipelines/cli/test.yml index 24ab9682bfe..29dcf502f6a 100644 --- a/build/azure-pipelines/cli/test.yml +++ b/build/azure-pipelines/cli/test.yml @@ -1,7 +1,7 @@ steps: - template: ./install-rust-posix.yml - - script: rustup component add clippy && cargo clippy -- -D warnings + - script: cargo clippy -- -D warnings workingDirectory: cli displayName: Clippy lint From 6429752b2bfb99f3a1ed6cef713ba63d6dc86698 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 25 Jan 2024 17:59:38 +0100 Subject: [PATCH 0623/1897] aux window - hide custom title when native title is on (#203464) --- src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts index 093881157b4..22786a7c2cb 100644 --- a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts @@ -14,7 +14,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { hasCustomTitlebar } from 'vs/platform/window/common/window'; +import { hasNativeTitlebar } from 'vs/platform/window/common/window'; import { IEditorGroupView, IEditorPartsView } from 'vs/workbench/browser/parts/editor/editor'; import { EditorPart, IEditorPartUIState } from 'vs/workbench/browser/parts/editor/editorPart'; import { IAuxiliaryTitlebarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; @@ -107,7 +107,7 @@ export class AuxiliaryEditorPart { // Titlebar let titlebarPart: IAuxiliaryTitlebarPart | undefined = undefined; let titlebarPartVisible = false; - const useCustomTitle = isNative && hasCustomTitlebar(this.configurationService); // custom title in aux windows only enabled in native + const useCustomTitle = isNative && !hasNativeTitlebar(this.configurationService); // custom title in aux windows only enabled in native if (useCustomTitle) { titlebarPart = disposables.add(this.titleService.createAuxiliaryTitlebarPart(auxiliaryWindow.container, editorPart)); titlebarPartVisible = true; From bc7fc03d6a3950a709f968bedc4dd8990062d3d6 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 25 Jan 2024 18:18:20 +0100 Subject: [PATCH 0624/1897] multi diff - implement `revert` to enable "Do not save" (#203376) * multi diff - implement `revert` to enable "Do not save" * restore close handler * align save/revert handling for original side with diff editor --- .../browser/multiDiffEditorInput.ts | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 65f6defe7ed..a8f59a63c36 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -9,6 +9,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableStore, IDisposable, IReference, toDisposable } from 'vs/base/common/lifecycle'; import { parse } from 'vs/base/common/marshalling'; +import { Schemas } from 'vs/base/common/network'; import { deepClone } from 'vs/base/common/objects'; import { autorun, derived, observableFromEvent } from 'vs/base/common/observable'; import { constObservable, mapObservableArrayCached } from 'vs/base/common/observableInternal/utils'; @@ -24,7 +25,7 @@ import { localize } from 'vs/nls'; import { ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; -import { DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, EditorInputWithOptions, IEditorSerializer, IResourceMultiDiffEditorInput, ISaveOptions, IUntypedEditorInput } from 'vs/workbench/common/editor'; +import { DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, EditorInputWithOptions, GroupIdentifier, IEditorSerializer, IResourceMultiDiffEditorInput, IRevertOptions, ISaveOptions, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput'; import { MultiDiffEditorIcon } from 'vs/workbench/contrib/multiDiffEditor/browser/icons.contribution'; import { ConstResolvedMultiDiffSource, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService'; @@ -242,15 +243,27 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor override readonly onDidChangeDirty = Event.fromObservableLight(this._isDirtyObservable); override isDirty() { return this._isDirtyObservable.get(); } - override async save(group: number, options?: ISaveOptions | undefined): Promise { + override async save(group: number, options?: ISaveOptions | undefined): Promise { + await this.doSaveOrRevert('save', group, options); + return this; + } + + override revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + return this.doSaveOrRevert('revert', group, options); + } + + private async doSaveOrRevert(mode: 'save', group: GroupIdentifier, options?: ISaveOptions): Promise; + private async doSaveOrRevert(mode: 'revert', group: GroupIdentifier, options?: IRevertOptions): Promise; + private async doSaveOrRevert(mode: 'save' | 'revert', group: GroupIdentifier, options?: ISaveOptions | IRevertOptions): Promise { const items = this._viewModel.currentValue?.items.get(); if (items) { await Promise.all(items.map(async item => { const model = item.diffEditorViewModel.model; + const handleOriginal = model.original.uri.scheme !== Schemas.untitled && this._textFileService.isDirty(model.original.uri); // match diff editor behaviour await Promise.all([ - this._textFileService.save(model.original.uri, options), - this._textFileService.save(model.modified.uri, options), + handleOriginal ? mode === 'save' ? this._textFileService.save(model.original.uri, options) : this._textFileService.revert(model.original.uri, options) : Promise.resolve(), + mode === 'save' ? this._textFileService.save(model.modified.uri, options) : this._textFileService.revert(model.modified.uri, options), ]); })); } @@ -258,6 +271,11 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor } override readonly closeHandler: IEditorCloseHandler = { + + // TODO@bpasero TODO@hediet this is a workaround for + // not having a better way to figure out if the + // editors this input wraps around are opened or not + async confirm() { return ConfirmResult.DONT_SAVE; }, From 4f143e0e2823f888833f03e9afcb99513989adc2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 25 Jan 2024 21:01:02 +0100 Subject: [PATCH 0625/1897] aux window - align `layout()` behaviour with main window (#203479) * aux window - dont forget to apply `filter` when copying attributes * actually align layouts --- src/vs/base/browser/dom.ts | 8 +++++--- .../browser/auxiliaryWindowService.ts | 14 ++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 72caa84e069..6d69e834847 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -2341,9 +2341,11 @@ function camelCaseToHyphenCase(str: string) { return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); } -export function copyAttributes(from: Element, to: Element): void { +export function copyAttributes(from: Element, to: Element, filter?: string[]): void { for (const { name, value } of from.attributes) { - to.setAttribute(name, value); + if (!filter || filter.includes(name)) { + to.setAttribute(name, value); + } } } @@ -2357,7 +2359,7 @@ function copyAttribute(from: Element, to: Element, name: string): void { } export function trackAttributes(from: Element, to: Element, filter?: string[]): IDisposable { - copyAttributes(from, to); + copyAttributes(from, to, filter); const disposables = new DisposableStore(); diff --git a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts index 53803bb6316..57c904416e0 100644 --- a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts @@ -97,13 +97,7 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { e.preventDefault(); })); - this._register(addDisposableListener(this.window, EventType.RESIZE, () => { - const dimension = getClientArea(this.window.document.body, this.container); - position(this.container, 0, 0, 0, 0, 'relative'); - size(this.container, dimension.width, dimension.height); - - this._onDidLayout.fire(dimension); - })); + this._register(addDisposableListener(this.window, EventType.RESIZE, () => this.layout())); this._register(addDisposableListener(this.container, EventType.SCROLL, () => this.container.scrollTop = 0)); // Prevent container from scrolling (#55456) @@ -142,7 +136,11 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { } layout(): void { - this._onDidLayout.fire(getClientArea(this.window.document.body, this.container)); + const dimension = getClientArea(this.window.document.body, this.container); + position(this.container, 0, 0, 0, 0, 'relative'); + size(this.container, dimension.width, dimension.height); + + this._onDidLayout.fire(dimension); } override dispose(): void { From ed6c2eca2d09587ff2470fc397ccf2414e7609d7 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 25 Jan 2024 13:05:50 -0800 Subject: [PATCH 0626/1897] Fix #203434. Avoid layout from sticky scroll event if the editor is set to invisible. (#203485) --- .../workbench/contrib/notebook/browser/notebookEditorWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 5554136a78c..576970d7a33 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1079,7 +1079,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return; } - if (this._dimension) { + if (this._dimension && this._isVisible) { if (sizeDelta > 0) { // delta > 0 ==> sticky is growing, cell list shrinking this.layout(this._dimension); this.setScrollTop(this.scrollTop + sizeDelta); From 6e2ea0ed2756722d1e46caae50c8f3a8f5f427b3 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 25 Jan 2024 21:21:45 +0000 Subject: [PATCH 0627/1897] Also disable `` ` `` as autoclosing pair (#203487) Fixes #192676 Not ideal but likely better than the current behavior that inserts extra backticks --- extensions/markdown-basics/language-configuration.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/extensions/markdown-basics/language-configuration.json b/extensions/markdown-basics/language-configuration.json index 4a79f1c5c07..f1e7859ccca 100644 --- a/extensions/markdown-basics/language-configuration.json +++ b/extensions/markdown-basics/language-configuration.json @@ -42,10 +42,6 @@ "string" ] }, - { - "open": "`", - "close": "`" - }, ], "surroundingPairs": [ [ From 274a528e33b7afca26cde92ab97a9e47c3eb3461 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Thu, 25 Jan 2024 13:23:20 -0800 Subject: [PATCH 0628/1897] Change default nb sticky mode based on feedback (#203488) change default nb sticky mode based on feedback --- .../workbench/contrib/notebook/browser/notebook.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index ce28e166b21..e7dc3cc4f97 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -886,7 +886,7 @@ configurationRegistry.registerConfiguration({ nls.localize('notebook.stickyScrollMode.flat', "Nested sticky lines appear flat."), nls.localize('notebook.stickyScrollMode.indented', "Nested sticky lines appear indented."), ], - default: 'flat', + default: 'indented', tags: ['notebookLayout'] }, [NotebookSetting.consolidatedOutputButton]: { From 661db2e6d3a965db59f7af4f231279291006dd6a Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Thu, 25 Jan 2024 13:52:42 -0800 Subject: [PATCH 0629/1897] chore: exclude not-reproducible issues (#203482) * Exclude not-reproducible issues * Add more missing labels --- .vscode/notebooks/endgame.github-issues | 8 ++++---- .vscode/notebooks/my-endgame.github-issues | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index e93d064d8de..5e2c7f9a815 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"December / January 2024\"" + "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\r\n\r\n$MILESTONE=milestone:\"December / January 2024\"" }, { "kind": 1, @@ -112,7 +112,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:z-author-verified -label:unreleased" + "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:z-author-verified -label:unreleased -label:*not-reproducible" }, { "kind": 1, @@ -122,7 +122,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:z-author-verified label:unreleased" + "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:z-author-verified label:unreleased -label:*not-reproducible" }, { "kind": 1, @@ -132,7 +132,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:z-author-verified" + "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:z-author-verified -label:*not-reproducible" }, { "kind": 1, diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index 6adad9d8532..4d3d34a8c92 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"December / January 2024\"\n\n$MINE=assignee:@me" + "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\r\n\r\n$MILESTONE=milestone:\"December / January 2024\"\r\n\r\n$MINE=assignee:@me" }, { "kind": 1, @@ -147,7 +147,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:triage-needed -label:verification-found" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:triage-needed -label:verification-found -label:*not-reproducible" }, { "kind": 1, @@ -167,7 +167,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed -author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed -author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -label:*not-reproducible" }, { "kind": 1, @@ -187,6 +187,6 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:on-release-notes\n$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:engineering -label:on-release-notes\n$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:plan-item -label:on-release-notes" + "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:on-release-notes\r\n$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:engineering -label:on-release-notes\r\n$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:plan-item -label:on-release-notes" } ] \ No newline at end of file From 7f056a705e65c9b386ecc35e24fd5ffaaa6e8fc2 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Thu, 25 Jan 2024 15:01:03 -0800 Subject: [PATCH 0630/1897] =?UTF-8?q?Revert=20"Change=20Welcome=20page=20c?= =?UTF-8?q?ontribution=20activation=20to=20LifecyclePhase=E2=80=A6=20(#203?= =?UTF-8?q?494)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert "Change Welcome page contribution activation to LifecyclePhase.Starting (#201844)" This reverts commit 0d735d01abd17a5edc7bed45fdd7f15d4902954a. --- .../browser/gettingStarted.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts index 6e052c1019c..3d0ae3e607d 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts @@ -327,4 +327,4 @@ configurationRegistry.registerConfiguration({ }); Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(StartupPageContribution, LifecyclePhase.Starting); + .registerWorkbenchContribution(StartupPageContribution, LifecyclePhase.Restored); From feff7c8a7e1c82320380f42d898a1e03051fefb7 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 25 Jan 2024 16:00:27 -0800 Subject: [PATCH 0631/1897] debug: fix closeReadonlyTabsOnEnd not working (#203500) The input itself is not readonly, the filesystem is, so this check was always failing. The entire `debug` scheme is readonly, so we don't need to try to replace the check with anything else. This is pretty safe since this code path is only hit when the new setting is enabled. Closes #197949 --- src/vs/workbench/contrib/debug/browser/debugService.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 805156e1210..07116a63fa3 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -710,10 +710,7 @@ export class DebugService implements IDebugService { if (this.configurationService.getValue('debug').closeReadonlyTabsOnEnd) { const editorsToClose = this.editorService.getEditors(EditorsOrder.SEQUENTIAL).filter(({ editor }) => { - if (editor.resource?.scheme === DEBUG_SCHEME) { - return editor.isReadonly() && session.getId() === Source.getEncodedDebugData(editor.resource).sessionId; - } - return false; + return editor.resource?.scheme === DEBUG_SCHEME && session.getId() === Source.getEncodedDebugData(editor.resource).sessionId; }); this.editorService.closeEditors(editorsToClose); } From a0d78507f4f7493badbc1066f2d00a8ced073922 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 26 Jan 2024 02:51:15 +0100 Subject: [PATCH 0632/1897] commiting the wip --- .../browser/inlineCompletionsModel.ts | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 83df0374823..70526ffd82e 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -10,7 +10,7 @@ import { IObservable, IReader, ITransaction, autorun, derived, derivedHandleChan import { commonPrefixLength } from 'vs/base/common/strings'; import { isDefined } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; @@ -26,7 +26,6 @@ import { addPositions, lengthOfText } from 'vs/editor/contrib/inlineCompletions/ import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { InlineCompletionItem } from 'vs/editor/contrib/inlineCompletions/browser/provideInlineCompletions'; export enum VersionIdChangeReason { Undo, @@ -307,14 +306,15 @@ export class InlineCompletionsModel extends Disposable { editor.setPosition(completion.snippetInfo.range.getStartPosition(), 'inlineCompletionAccept'); SnippetController2.get(editor)?.insert(completion.snippetInfo.snippet, { undoStopBefore: false }); } else { - const primaryEdit = EditOperation.replace(completion.range, completion.insertText); editor.executeEdits( 'inlineSuggestion.accept', [ - ...this._getEdits(editor, completion, primaryEdit).edits, + ...this._getEdits(editor, completion.toSingleTextEdit()).edits, ...completion.additionalTextEdits ] ); + + // set the selections after } if (completion.command) { @@ -413,10 +413,13 @@ export class InlineCompletionsModel extends Disposable { this._isAcceptingPartially = true; try { editor.pushUndoStop(); - const primaryEditRange = Range.fromPositions(completion.range.getStartPosition(), position); - const primaryEditText = completion.insertText.substring(0, firstPart.column + acceptUntilIndexExclusive - 1); - const primaryEdit = EditOperation.replace(primaryEditRange, primaryEditText); - const edits = this._getEdits(editor, completion, primaryEdit, firstPart.column + acceptUntilIndexExclusive - 1); + const replaceRange = Range.fromPositions(completion.range.getStartPosition(), position); + const newText = completion.insertText.substring( + 0, + firstPart.column - completion.range.startColumn + acceptUntilIndexExclusive + ); + const singleTextEdit = new SingleTextEdit(replaceRange, newText); + const edits = this._getEdits(editor, singleTextEdit); editor.executeEdits('inlineSuggestion.accept', edits.edits); editor.setSelections(edits.editorSelections, 'inlineCompletionPartialAccept'); } finally { @@ -438,7 +441,7 @@ export class InlineCompletionsModel extends Disposable { } } - private _getEdits(editor: ICodeEditor, completion: InlineCompletionItem, primaryEdit: ISingleEditOperation, acceptInsertTextUntilIndexExclusive?: number): { edits: IIdentifiedSingleEditOperation[]; editorSelections: ISelection[] } { + private _getEdits(editor: ICodeEditor, completion: SingleTextEdit): { edits: IIdentifiedSingleEditOperation[]; editorSelections: ISelection[] } { const selections = editor.getSelections() ?? []; const secondaryPositions = selections.slice(1).map(selection => selection.getPosition()); @@ -447,9 +450,11 @@ export class InlineCompletionsModel extends Disposable { const replacedTextAfterPrimaryCursor = textModel .getLineContent(primaryPosition.lineNumber) .substring(primaryPosition.column - 1, completion.range.endColumn - 1); - const secondaryEditText = completion.insertText.substring(primaryPosition.column - completion.range.startColumn, acceptInsertTextUntilIndexExclusive); + console.log('completion : ', JSON.stringify(completion)); + const secondaryEditText = completion.text.substring(primaryPosition.column - completion.range.startColumn); + console.log('secondaryEditText : ', secondaryEditText); const edits = [ - primaryEdit, + EditOperation.replaceMove(completion.range, completion.text), ...secondaryPositions.map(pos => { const textAfterSecondaryCursor = this.textModel .getLineContent(pos.lineNumber) @@ -460,15 +465,15 @@ export class InlineCompletionsModel extends Disposable { }) ]; + // 1. need to figure out the new selections using the information at hand + // 2. polish and clean the code, should be able to simplify the below let editorSelections: ISelection[] = []; - if (acceptInsertTextUntilIndexExclusive !== undefined) { - const primaryEditLength = lengthOfText(primaryEdit.text ?? ''); - const secondaryEditLength = lengthOfText(secondaryEditText); - editorSelections = [ - Selection.fromPositions(addPositions(new Position(primaryEdit.range.startLineNumber, primaryEdit.range.startColumn), primaryEditLength)), - ...secondaryPositions.map(pos => Selection.fromPositions(addPositions(pos, secondaryEditLength))) - ]; - } + const primaryEditLength = lengthOfText(completion.text ?? ''); + const secondaryEditLength = lengthOfText(secondaryEditText); + editorSelections = [ + Selection.fromPositions(addPositions(new Position(completion.range.startLineNumber, completion.range.startColumn), primaryEditLength)), + ...secondaryPositions.map(pos => Selection.fromPositions(addPositions(pos, secondaryEditLength))) + ]; return { edits, From 661cf5147bb4bc288b043272e9560fd916f0aaa7 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 26 Jan 2024 03:29:04 +0100 Subject: [PATCH 0633/1897] increasing the version to 1.87 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7720eab38e3..a79f01cabda 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "code-oss-dev", - "version": "1.86.0", + "version": "1.87.0", "distro": "6c14240015f9a76b34c29b4212fb8df761106443", "author": { "name": "Microsoft Corporation" From b26b05031ed6a62dfe0fe3a93c27ffd4acf9f4a4 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 26 Jan 2024 10:50:25 +0100 Subject: [PATCH 0634/1897] SCM - Add "Reopen Closed Repositories..." action to the "Source control Repositories" view title bar (#203512) --- extensions/git/package.json | 9 +++++++++ src/vs/platform/actions/common/actions.ts | 1 + .../contrib/scm/browser/scmRepositoriesViewPane.ts | 2 +- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 5 ++--- .../services/actions/common/menusExtensionPoint.ts | 6 ++++++ .../services/extensions/common/extensionsApiProposals.ts | 1 + .../vscode.proposed.contribSourceControlTitleMenu.d.ts | 7 +++++++ 7 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.contribSourceControlTitleMenu.d.ts diff --git a/extensions/git/package.json b/extensions/git/package.json index e9d12a97405..4017a8618ed 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -18,6 +18,7 @@ "contribSourceControlHistoryItemGroupMenu", "contribSourceControlHistoryItemMenu", "contribSourceControlInputBoxMenu", + "contribSourceControlTitleMenu", "contribViewsWelcome", "diffCommand", "editSessionIdentityProvider", @@ -94,6 +95,7 @@ { "command": "git.reopenClosedRepositories", "title": "%command.reopenClosedRepositories%", + "icon": "$(repo)", "category": "Git", "enablement": "!operationInProgress && git.closedRepositoryCount != 0" }, @@ -1448,6 +1450,13 @@ "when": "scmProvider == git" } ], + "scm/sourceControl/title": [ + { + "command": "git.reopenClosedRepositories", + "group": "navigation@1", + "when": "scmProvider == git && git.closedRepositoryCount > 0" + } + ], "scm/sourceControl": [ { "command": "git.close", diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 0e39f772a78..ece50e4de49 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -120,6 +120,7 @@ export class MenuId { static readonly SCMResourceGroupContext = new MenuId('SCMResourceGroupContext'); static readonly SCMSourceControl = new MenuId('SCMSourceControl'); static readonly SCMSourceControlInline = new MenuId('SCMSourceControlInline'); + static readonly SCMSourceControlTitle = new MenuId('SCMSourceControlTitle'); static readonly SCMTitle = new MenuId('SCMTitle'); static readonly SearchContext = new MenuId('SearchContext'); static readonly SearchActionMenu = new MenuId('SearchActionContext'); diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index 20b33dd78fa..7ec52b6aabd 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -57,7 +57,7 @@ export class SCMRepositoriesViewPane extends ViewPane { @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super({ ...options, titleMenuId: MenuId.SCMSourceControlTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); } protected override renderBody(container: HTMLElement): void { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 336f381c964..084d36256e7 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -11,7 +11,7 @@ import { ViewPane, IViewPaneOptions, ViewAction } from 'vs/workbench/browser/par import { append, $, Dimension, asCSSUrl, trackFocus, clearNode, prepend } from 'vs/base/browser/dom'; import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryProviderCacheEntry, SCMHistoryItemChangeTreeElement, SCMHistoryItemGroupTreeElement, SCMHistoryItemTreeElement, SCMViewSeparatorElement } from 'vs/workbench/contrib/scm/common/history'; -import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, ISCMService, SCMInputChangeReason, VIEW_PANE_ID, ISCMActionButton, ISCMActionButtonDescriptor, ISCMRepositorySortKey, REPOSITORIES_VIEW_PANE_ID, ISCMInputValueProviderContext, ISCMProvider } from 'vs/workbench/contrib/scm/common/scm'; +import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, ISCMService, SCMInputChangeReason, VIEW_PANE_ID, ISCMActionButton, ISCMActionButtonDescriptor, ISCMRepositorySortKey, ISCMInputValueProviderContext, ISCMProvider } from 'vs/workbench/contrib/scm/common/scm'; import { ResourceLabels, IResourceLabel, IFileLabelOptions } from 'vs/workbench/browser/labels'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -1639,8 +1639,7 @@ abstract class RepositorySortAction extends ViewAction { group: '1_sort' }, { - id: MenuId.ViewTitle, - when: ContextKeyExpr.equals('view', REPOSITORIES_VIEW_PANE_ID), + id: MenuId.SCMSourceControlTitle, group: '1_sort', }, ] diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index ead97697772..d8918e89e62 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -124,6 +124,12 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.SCMSourceControl, description: localize('menus.scmSourceControl', "The Source Control menu") }, + { + key: 'scm/sourceControl/title', + id: MenuId.SCMSourceControlTitle, + description: localize('menus.scmSourceControlTitle', "The Source Control title menu"), + proposed: 'contribSourceControlTitleMenu' + }, { key: 'scm/resourceState/context', id: MenuId.SCMResourceContext, diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index e80c095be84..0a1f29e9d7a 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -36,6 +36,7 @@ export const allApiProposals = Object.freeze({ contribSourceControlHistoryItemGroupMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemGroupMenu.d.ts', contribSourceControlHistoryItemMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemMenu.d.ts', contribSourceControlInputBoxMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlInputBoxMenu.d.ts', + contribSourceControlTitleMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlTitleMenu.d.ts', contribStatusBarItems: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribStatusBarItems.d.ts', contribViewsRemote: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts', contribViewsWelcome: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsWelcome.d.ts', diff --git a/src/vscode-dts/vscode.proposed.contribSourceControlTitleMenu.d.ts b/src/vscode-dts/vscode.proposed.contribSourceControlTitleMenu.d.ts new file mode 100644 index 00000000000..af1aa65e7fb --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribSourceControlTitleMenu.d.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `scm/sourceControl/title`-menu contribution point +// https://github.com/microsoft/vscode/issues/203511 From 0e7e31b4be159545a8fc5407c05808567b7bfdc4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 26 Jan 2024 11:53:26 +0100 Subject: [PATCH 0635/1897] save participants - provide access to the `from` resource in "Save As" scenarios (#203516) --- src/vs/platform/product/common/product.ts | 2 +- .../mainThreadNotebookSaveParticipant.ts | 7 ++-- .../api/browser/mainThreadSaveParticipant.ts | 7 ++-- .../codeEditor/browser/saveParticipants.ts | 24 ++++++------- .../saveParticipants/saveParticipants.ts | 12 +++---- .../textfile/browser/textFileService.ts | 5 ++- .../textfile/common/textFileEditorModel.ts | 12 +++---- .../common/textFileEditorModelManager.ts | 5 ++- .../common/textFileSaveParticipant.ts | 5 ++- .../services/textfile/common/textfiles.ts | 27 ++++++++++++-- .../test/browser/textFileEditorModel.test.ts | 27 ++++++++++++++ .../common/fileWorkingCopyManager.ts | 6 +++- .../common/storedFileWorkingCopy.ts | 27 +++++++++++--- .../storedFileWorkingCopySaveParticipant.ts | 5 ++- .../common/workingCopyFileService.ts | 21 +++++++++-- .../browser/storedFileWorkingCopy.test.ts | 36 +++++++++++++++++-- .../test/common/workbenchTestServices.ts | 4 +-- 17 files changed, 172 insertions(+), 60 deletions(-) diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 9482a2c954e..467e11cc56a 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -58,7 +58,7 @@ else { // Running out of sources if (Object.keys(product).length === 0) { Object.assign(product, { - version: '1.82.0-dev', + version: '1.87.0-dev', nameShort: 'Code - OSS Dev', nameLong: 'Code - OSS Dev', applicationName: 'code-oss', diff --git a/src/vs/workbench/api/browser/mainThreadNotebookSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadNotebookSaveParticipant.ts index eeba6fa2b4e..dd17844187d 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookSaveParticipant.ts @@ -8,11 +8,10 @@ import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; import { extHostCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { SaveReason } from 'vs/workbench/common/editor'; import { ExtHostContext, ExtHostNotebookDocumentSaveParticipantShape } from '../common/extHost.protocol'; import { IDisposable } from 'vs/base/common/lifecycle'; import { raceCancellationError } from 'vs/base/common/async'; -import { IStoredFileWorkingCopySaveParticipant, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { IStoredFileWorkingCopySaveParticipant, IStoredFileWorkingCopySaveParticipantContext, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IStoredFileWorkingCopy, IStoredFileWorkingCopyModel } from 'vs/workbench/services/workingCopy/common/storedFileWorkingCopy'; import { NotebookFileWorkingCopyModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; @@ -24,7 +23,7 @@ class ExtHostNotebookDocumentSaveParticipant implements IStoredFileWorkingCopySa this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebookDocumentSaveParticipant); } - async participate(workingCopy: IStoredFileWorkingCopy, env: { reason: SaveReason }, _progress: IProgress, token: CancellationToken): Promise { + async participate(workingCopy: IStoredFileWorkingCopy, context: IStoredFileWorkingCopySaveParticipantContext, _progress: IProgress, token: CancellationToken): Promise { if (!workingCopy.model || !(workingCopy.model instanceof NotebookFileWorkingCopyModel)) { return undefined; @@ -38,7 +37,7 @@ class ExtHostNotebookDocumentSaveParticipant implements IStoredFileWorkingCopySa () => reject(new Error(localize('timeout.onWillSave', "Aborted onWillSaveNotebookDocument-event after 1750ms"))), 1750 ); - this._proxy.$participateInSave(workingCopy.resource, env.reason, token).then(_ => { + this._proxy.$participateInSave(workingCopy.resource, context.reason, token).then(_ => { clearTimeout(_warningTimeout); return undefined; }).then(resolve, reject); diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index fd39c8a6fbf..a8c110c2ed4 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -9,8 +9,7 @@ import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; import { extHostCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { ITextFileSaveParticipant, ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; -import { SaveReason } from 'vs/workbench/common/editor'; +import { ITextFileSaveParticipant, ITextFileService, ITextFileEditorModel, ITextFileSaveParticipantContext } from 'vs/workbench/services/textfile/common/textfiles'; import { ExtHostContext, ExtHostDocumentSaveParticipantShape } from '../common/extHost.protocol'; import { IDisposable } from 'vs/base/common/lifecycle'; import { raceCancellationError } from 'vs/base/common/async'; @@ -23,7 +22,7 @@ class ExtHostSaveParticipant implements ITextFileSaveParticipant { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentSaveParticipant); } - async participate(editorModel: ITextFileEditorModel, env: { reason: SaveReason }, _progress: IProgress, token: CancellationToken): Promise { + async participate(editorModel: ITextFileEditorModel, context: ITextFileSaveParticipantContext, _progress: IProgress, token: CancellationToken): Promise { if (!editorModel.textEditorModel || !shouldSynchronizeModel(editorModel.textEditorModel)) { // the model never made it to the extension @@ -37,7 +36,7 @@ class ExtHostSaveParticipant implements ITextFileSaveParticipant { () => reject(new Error(localize('timeout.onWillSave', "Aborted onWillSaveTextDocument-event after 1750ms"))), 1750 ); - this._proxy.$participateInSave(editorModel.resource, env.reason).then(values => { + this._proxy.$participateInSave(editorModel.resource, context.reason).then(values => { if (!values.every(success => success)) { return Promise.reject(new Error('listener failed')); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts index 122278e217a..ad3243f9a55 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts @@ -30,7 +30,7 @@ import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as import { SaveReason } from 'vs/workbench/common/editor'; import { getModifiedRanges } from 'vs/workbench/contrib/format/browser/formatModified'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { ITextFileEditorModel, ITextFileSaveParticipant, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileEditorModel, ITextFileSaveParticipant, ITextFileSaveParticipantContext, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; export class TrimWhitespaceParticipant implements ITextFileSaveParticipant { @@ -41,13 +41,13 @@ export class TrimWhitespaceParticipant implements ITextFileSaveParticipant { // Nothing } - async participate(model: ITextFileEditorModel, env: { reason: SaveReason }): Promise { + async participate(model: ITextFileEditorModel, context: ITextFileSaveParticipantContext): Promise { if (!model.textEditorModel) { return; } if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageId(), resource: model.resource })) { - this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO); + this.doTrimTrailingWhitespace(model.textEditorModel, context.reason === SaveReason.AUTO); } } @@ -107,7 +107,7 @@ export class FinalNewLineParticipant implements ITextFileSaveParticipant { // Nothing } - async participate(model: ITextFileEditorModel, _env: { reason: SaveReason }): Promise { + async participate(model: ITextFileEditorModel, context: ITextFileSaveParticipantContext): Promise { if (!model.textEditorModel) { return; } @@ -145,13 +145,13 @@ export class TrimFinalNewLinesParticipant implements ITextFileSaveParticipant { // Nothing } - async participate(model: ITextFileEditorModel, env: { reason: SaveReason }): Promise { + async participate(model: ITextFileEditorModel, context: ITextFileSaveParticipantContext): Promise { if (!model.textEditorModel) { return; } if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageId(), resource: model.resource })) { - this.doTrimFinalNewLines(model.textEditorModel, env.reason === SaveReason.AUTO); + this.doTrimFinalNewLines(model.textEditorModel, context.reason === SaveReason.AUTO); } } @@ -217,11 +217,11 @@ class FormatOnSaveParticipant implements ITextFileSaveParticipant { // Nothing } - async participate(model: ITextFileEditorModel, env: { reason: SaveReason }, progress: IProgress, token: CancellationToken): Promise { + async participate(model: ITextFileEditorModel, context: ITextFileSaveParticipantContext, progress: IProgress, token: CancellationToken): Promise { if (!model.textEditorModel) { return; } - if (env.reason === SaveReason.AUTO) { + if (context.reason === SaveReason.AUTO) { return undefined; } @@ -272,7 +272,7 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, ) { } - async participate(model: ITextFileEditorModel, env: { reason: SaveReason }, progress: IProgress, token: CancellationToken): Promise { + async participate(model: ITextFileEditorModel, context: ITextFileSaveParticipantContext, progress: IProgress, token: CancellationToken): Promise { if (!model.textEditorModel) { return; } @@ -286,11 +286,11 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { return undefined; } - if (env.reason === SaveReason.AUTO) { + if (context.reason === SaveReason.AUTO) { return undefined; } - if (env.reason !== SaveReason.EXPLICIT && Array.isArray(setting)) { + if (context.reason !== SaveReason.EXPLICIT && Array.isArray(setting)) { return undefined; } @@ -326,7 +326,7 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { progress.report({ message: localize('codeaction', "Quick Fixes") }); - const filteredSaveList = Array.isArray(setting) ? codeActionsOnSave : codeActionsOnSave.filter(x => setting[x.value] === 'always' || ((setting[x.value] === 'explicit' || setting[x.value] === true) && env.reason === SaveReason.EXPLICIT)); + const filteredSaveList = Array.isArray(setting) ? codeActionsOnSave : codeActionsOnSave.filter(x => setting[x.value] === 'always' || ((setting[x.value] === 'explicit' || setting[x.value] === true) && context.reason === SaveReason.EXPLICIT)); await this.applyOnSaveActions(textEditorModel, filteredSaveList, excludedActions, progress, token); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts b/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts index 04cdc764d38..0ffd61f705a 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts @@ -36,7 +36,7 @@ import { NotebookFileWorkingCopyModel } from 'vs/workbench/contrib/notebook/comm import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IStoredFileWorkingCopy, IStoredFileWorkingCopyModel } from 'vs/workbench/services/workingCopy/common/storedFileWorkingCopy'; -import { IStoredFileWorkingCopySaveParticipant, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { IStoredFileWorkingCopySaveParticipant, IStoredFileWorkingCopySaveParticipantContext, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; class FormatOnSaveParticipant implements IStoredFileWorkingCopySaveParticipant { constructor( @@ -47,7 +47,7 @@ class FormatOnSaveParticipant implements IStoredFileWorkingCopySaveParticipant { @IConfigurationService private readonly configurationService: IConfigurationService, ) { } - async participate(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, progress: IProgress, token: CancellationToken): Promise { + async participate(workingCopy: IStoredFileWorkingCopy, context: IStoredFileWorkingCopySaveParticipantContext, progress: IProgress, token: CancellationToken): Promise { if (!workingCopy.model || !(workingCopy.model instanceof NotebookFileWorkingCopyModel)) { return; } @@ -108,7 +108,7 @@ class TrimWhitespaceParticipant implements IStoredFileWorkingCopySaveParticipant @IBulkEditService private readonly bulkEditService: IBulkEditService, ) { } - async participate(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, progress: IProgress, _token: CancellationToken): Promise { + async participate(workingCopy: IStoredFileWorkingCopy, context: IStoredFileWorkingCopySaveParticipantContext, progress: IProgress, _token: CancellationToken): Promise { if (this.configurationService.getValue('files.trimTrailingWhitespace')) { await this.doTrimTrailingWhitespace(workingCopy, context.reason === SaveReason.AUTO, progress); } @@ -175,7 +175,7 @@ class TrimFinalNewLinesParticipant implements IStoredFileWorkingCopySaveParticip @IBulkEditService private readonly bulkEditService: IBulkEditService, ) { } - async participate(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, progress: IProgress, _token: CancellationToken): Promise { + async participate(workingCopy: IStoredFileWorkingCopy, context: IStoredFileWorkingCopySaveParticipantContext, progress: IProgress, _token: CancellationToken): Promise { if (this.configurationService.getValue('files.trimFinalNewlines')) { await this.doTrimFinalNewLines(workingCopy, context.reason === SaveReason.AUTO, progress); } @@ -252,7 +252,7 @@ class FinalNewLineParticipant implements IStoredFileWorkingCopySaveParticipant { @IEditorService private readonly editorService: IEditorService, ) { } - async participate(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, progress: IProgress, _token: CancellationToken): Promise { + async participate(workingCopy: IStoredFileWorkingCopy, context: IStoredFileWorkingCopySaveParticipantContext, progress: IProgress, _token: CancellationToken): Promise { // waiting on notebook-specific override before this feature can sync with 'files.insertFinalNewline' // if (this.configurationService.getValue('files.insertFinalNewline')) { @@ -317,7 +317,7 @@ class CodeActionOnSaveParticipant implements IStoredFileWorkingCopySaveParticipa ) { } - async participate(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, progress: IProgress, token: CancellationToken): Promise { + async participate(workingCopy: IStoredFileWorkingCopy, context: IStoredFileWorkingCopySaveParticipantContext, progress: IProgress, token: CancellationToken): Promise { const nbDisposable = new DisposableStore(); const isTrusted = this.workspaceTrustManagementService.isWorkspaceTrusted(); if (!isTrusted) { diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index b877f973eb6..b388e7cc334 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -560,7 +560,10 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } // save model - return targetModel.save(options); + return targetModel.save({ + ...options, + from: source + }); } private async confirmOverwrite(resource: URI): Promise { diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 32c62dc4dd6..4fb09ccd015 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -8,7 +8,7 @@ import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { mark } from 'vs/base/common/performance'; 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 { EncodingMode, ITextFileService, TextFileEditorModelState, ITextFileEditorModel, ITextFileStreamContent, ITextFileResolveOptions, IResolvedTextFileEditorModel, TextFileResolveReason, ITextFileEditorModelSaveEvent, ITextFileSaveAsOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { IRevertOptions, SaveReason, SaveSourceRegistry } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { IWorkingCopyBackupService, IResolvedWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; @@ -723,7 +723,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil //#region Save - async save(options: ITextFileSaveOptions = Object.create(null)): Promise { + async save(options: ITextFileSaveAsOptions = Object.create(null)): Promise { if (!this.isResolved()) { return false; } @@ -751,7 +751,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.hasState(TextFileEditorModelState.SAVED); } - private async doSave(options: ITextFileSaveOptions): Promise { + private async doSave(options: ITextFileSaveAsOptions): Promise { if (typeof options.reason !== 'number') { options.reason = SaveReason.EXPLICIT; } @@ -852,7 +852,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (!saveCancellation.token.isCancellationRequested) { this.ignoreSaveFromSaveParticipants = true; try { - await this.textFileService.files.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveCancellation.token); + await this.textFileService.files.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT, savedFrom: options.from }, saveCancellation.token); } finally { this.ignoreSaveFromSaveParticipants = false; } @@ -919,7 +919,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil })(), () => saveCancellation.cancel()); } - private handleSaveSuccess(stat: IFileStatWithMetadata, versionId: number, options: ITextFileSaveOptions): void { + private handleSaveSuccess(stat: IFileStatWithMetadata, versionId: number, options: ITextFileSaveAsOptions): void { // Updated resolved stat with updated stat this.updateLastResolvedFileStat(stat); @@ -939,7 +939,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this._onDidSave.fire({ reason: options.reason, stat, source: options.source }); } - private handleSaveError(error: Error, versionId: number, options: ITextFileSaveOptions): void { + private handleSaveError(error: Error, versionId: number, options: ITextFileSaveAsOptions): void { (options.ignoreErrorHandler ? this.logService.trace : this.logService.error).apply(this.logService, [`[text file model] handleSaveError(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource.toString()]); // Return early if the save() call was made asking to diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 7328c060936..7aa0c00f8c1 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -16,10 +16,9 @@ import { IFileService, FileChangesEvent, FileOperation, FileChangeType, IFileSys import { Promises, ResourceQueue } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; import { TextFileSaveParticipant } from 'vs/workbench/services/textfile/common/textFileSaveParticipant'; -import { SaveReason } from 'vs/workbench/common/editor'; import { CancellationToken } from 'vs/base/common/cancellation'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IWorkingCopyFileService, WorkingCopyFileEvent } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { IStoredFileWorkingCopySaveParticipantContext, IWorkingCopyFileService, WorkingCopyFileEvent } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { ITextSnapshot } from 'vs/editor/common/model'; import { extname, joinPath } from 'vs/base/common/resources'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; @@ -536,7 +535,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this.saveParticipants.addSaveParticipant(participant); } - runSaveParticipants(model: ITextFileEditorModel, context: { reason: SaveReason }, token: CancellationToken): Promise { + runSaveParticipants(model: ITextFileEditorModel, context: IStoredFileWorkingCopySaveParticipantContext, token: CancellationToken): Promise { return this.saveParticipants.participate(model, context, token); } diff --git a/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts b/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts index 3dc16d1a46e..97d74549b9a 100644 --- a/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts +++ b/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts @@ -8,8 +8,7 @@ import { raceCancellation } from 'vs/base/common/async'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import { ILogService } from 'vs/platform/log/common/log'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { ITextFileSaveParticipant, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; -import { SaveReason } from 'vs/workbench/common/editor'; +import { ITextFileSaveParticipant, ITextFileEditorModel, ITextFileSaveParticipantContext } from 'vs/workbench/services/textfile/common/textfiles'; import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { insert } from 'vs/base/common/arrays'; @@ -30,7 +29,7 @@ export class TextFileSaveParticipant extends Disposable { return toDisposable(() => remove()); } - participate(model: ITextFileEditorModel, context: { reason: SaveReason }, token: CancellationToken): Promise { + participate(model: ITextFileEditorModel, context: ITextFileSaveParticipantContext, token: CancellationToken): Promise { const cts = new CancellationTokenSource(token); return this.progressService.withProgress({ diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 314e30a451b..306bca1e19c 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -313,6 +313,22 @@ export interface ITextFileResolveEvent { readonly reason: TextFileResolveReason; } +export interface ITextFileSaveParticipantContext { + + /** + * The reason why the save was triggered. + */ + readonly reason: SaveReason; + + /** + * Only applies to when a text file was saved as, for + * example when starting with untitled and saving. This + * provides access to the initial resource the text + * file had before. + */ + readonly savedFrom?: URI; +} + export interface ITextFileSaveParticipant { /** @@ -321,7 +337,7 @@ export interface ITextFileSaveParticipant { */ participate( model: ITextFileEditorModel, - context: { reason: SaveReason }, + context: ITextFileSaveParticipantContext, progress: IProgress, token: CancellationToken ): Promise; @@ -369,7 +385,7 @@ export interface ITextFileEditorModelManager { /** * Runs the registered save participants on the provided model. */ - runSaveParticipants(model: ITextFileEditorModel, context: { reason: SaveReason }, token: CancellationToken): Promise; + runSaveParticipants(model: ITextFileEditorModel, context: ITextFileSaveParticipantContext, token: CancellationToken): Promise; /** * Waits for the model to be ready to be disposed. There may be conditions @@ -406,6 +422,11 @@ export interface ITextFileSaveOptions extends ISaveOptions { export interface ITextFileSaveAsOptions extends ITextFileSaveOptions { + /** + * Optional URI of the resource the text file is saved from if known. + */ + readonly from?: URI; + /** * Optional URI to use as suggested file path to save as. */ @@ -498,7 +519,7 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport updatePreferredEncoding(encoding: string | undefined): void; - save(options?: ITextFileSaveOptions): Promise; + save(options?: ITextFileSaveAsOptions): Promise; revert(options?: IRevertOptions): Promise; resolve(options?: ITextFileResolveOptions): Promise; diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts index 8528d367687..4b42d9de86b 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts @@ -19,6 +19,7 @@ import { SaveReason, SaveSourceRegistry } from 'vs/workbench/common/editor'; import { isEqual } from 'vs/base/common/resources'; import { UTF16be } from 'vs/workbench/services/textfile/common/encoding'; import { isWeb } from 'vs/base/common/platform'; +import { URI } from 'vs/base/common/uri'; suite('Files - TextFileEditorModel', () => { @@ -849,5 +850,31 @@ suite('Files - TextFileEditorModel', () => { await savePromise; } + test('Save Participant carries context', async function () { + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); + + const from = URI.file('testFrom'); + let e: Error | undefined = undefined; + disposables.add(accessor.textFileService.files.addSaveParticipant({ + participate: async (wc, context) => { + try { + assert.strictEqual(context.reason, SaveReason.EXPLICIT); + assert.strictEqual(context.savedFrom?.toString(), from.toString()); + } catch (error) { + e = error; + } + } + })); + + await model.resolve(); + model.updateTextEditorModel(createTextBufferFactory('foo')); + + await model.save({ force: true, from }); + + if (e) { + throw e; + } + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts b/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts index 8493b64cc30..829afbd8c47 100644 --- a/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts +++ b/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts @@ -429,7 +429,11 @@ export class FileWorkingCopyManager e * Whether the stored file working copy is readonly or not. */ isReadonly(): boolean | IMarkdownString; + + /** + * Asks the stored file working copy to save. If the stored file + * working copy was dirty, it is expected to be non-dirty after + * this operation has finished. + * + * @returns `true` if the operation was successful and `false` otherwise. + */ + save(options?: IStoredFileWorkingCopySaveAsOptions): Promise; } export interface IResolvedStoredFileWorkingCopy extends IStoredFileWorkingCopy { @@ -236,6 +245,14 @@ export interface IStoredFileWorkingCopySaveOptions extends ISaveOptions { readonly ignoreErrorHandler?: boolean; } +export interface IStoredFileWorkingCopySaveAsOptions extends IStoredFileWorkingCopySaveOptions { + + /** + * Optional URI of the resource the text file is saved from if known. + */ + readonly from?: URI; +} + export interface IStoredFileWorkingCopyResolver { /** @@ -815,7 +832,7 @@ export class StoredFileWorkingCopy extend private ignoreSaveFromSaveParticipants = false; - async save(options: IStoredFileWorkingCopySaveOptions = Object.create(null)): Promise { + async save(options: IStoredFileWorkingCopySaveAsOptions = Object.create(null)): Promise { if (!this.isResolved()) { return false; } @@ -843,7 +860,7 @@ export class StoredFileWorkingCopy extend return this.hasState(StoredFileWorkingCopyState.SAVED); } - private async doSave(options: IStoredFileWorkingCopySaveOptions): Promise { + private async doSave(options: IStoredFileWorkingCopySaveAsOptions): Promise { if (typeof options.reason !== 'number') { options.reason = SaveReason.EXPLICIT; } @@ -947,7 +964,7 @@ export class StoredFileWorkingCopy extend if (!saveCancellation.token.isCancellationRequested) { this.ignoreSaveFromSaveParticipants = true; try { - await this.workingCopyFileService.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveCancellation.token); + await this.workingCopyFileService.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT, savedFrom: options.from }, saveCancellation.token); } finally { this.ignoreSaveFromSaveParticipants = false; } @@ -1039,7 +1056,7 @@ export class StoredFileWorkingCopy extend })(), () => saveCancellation.cancel()); } - private handleSaveSuccess(stat: IFileStatWithMetadata, versionId: number, options: IStoredFileWorkingCopySaveOptions): void { + private handleSaveSuccess(stat: IFileStatWithMetadata, versionId: number, options: IStoredFileWorkingCopySaveAsOptions): void { // Updated resolved stat with updated stat this.updateLastResolvedFileStat(stat); @@ -1059,7 +1076,7 @@ export class StoredFileWorkingCopy extend this._onDidSave.fire({ reason: options.reason, stat, source: options.source }); } - private handleSaveError(error: Error, versionId: number, options: IStoredFileWorkingCopySaveOptions): void { + private handleSaveError(error: Error, versionId: number, options: IStoredFileWorkingCopySaveAsOptions): void { (options.ignoreErrorHandler ? this.logService.trace : this.logService.error).apply(this.logService, [`[stored file working copy] handleSaveError(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource.toString(), this.typeId]); // Return early if the save() call was made asking to diff --git a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopySaveParticipant.ts b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopySaveParticipant.ts index 86177563f33..2473efad9f0 100644 --- a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopySaveParticipant.ts +++ b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopySaveParticipant.ts @@ -8,10 +8,9 @@ import { raceCancellation } from 'vs/base/common/async'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import { ILogService } from 'vs/platform/log/common/log'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { SaveReason } from 'vs/workbench/common/editor'; import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { insert } from 'vs/base/common/arrays'; -import { IStoredFileWorkingCopySaveParticipant } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { IStoredFileWorkingCopySaveParticipant, IStoredFileWorkingCopySaveParticipantContext } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IStoredFileWorkingCopy, IStoredFileWorkingCopyModel } from 'vs/workbench/services/workingCopy/common/storedFileWorkingCopy'; export class StoredFileWorkingCopySaveParticipant extends Disposable { @@ -33,7 +32,7 @@ export class StoredFileWorkingCopySaveParticipant extends Disposable { return toDisposable(() => remove()); } - participate(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, token: CancellationToken): Promise { + participate(workingCopy: IStoredFileWorkingCopy, context: IStoredFileWorkingCopySaveParticipantContext, token: CancellationToken): Promise { const cts = new CancellationTokenSource(token); return this.progressService.withProgress({ diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts index b70d40f4676..1896c957597 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts @@ -84,6 +84,21 @@ export interface IWorkingCopyFileOperationParticipant { ): Promise; } +export interface IStoredFileWorkingCopySaveParticipantContext { + /** + * The reason why the save was triggered. + */ + readonly reason: SaveReason; + + /** + * Only applies to when a text file was saved as, for + * example when starting with untitled and saving. This + * provides access to the initial resource the text + * file had before. + */ + readonly savedFrom?: URI; +} + export interface IStoredFileWorkingCopySaveParticipant { /** @@ -92,7 +107,7 @@ export interface IStoredFileWorkingCopySaveParticipant { */ participate( workingCopy: IStoredFileWorkingCopy, - context: { reason: SaveReason }, + context: IStoredFileWorkingCopySaveParticipantContext, progress: IProgress, token: CancellationToken ): Promise; @@ -191,7 +206,7 @@ export interface IWorkingCopyFileService { /** * Runs all available save participants for stored file working copies. */ - runSaveParticipants(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, token: CancellationToken): Promise; + runSaveParticipants(workingCopy: IStoredFileWorkingCopy, context: IStoredFileWorkingCopySaveParticipantContext, token: CancellationToken): Promise; //#endregion @@ -492,7 +507,7 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi return this.saveParticipants.addSaveParticipant(participant); } - runSaveParticipants(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, token: CancellationToken): Promise { + runSaveParticipants(workingCopy: IStoredFileWorkingCopy, context: IStoredFileWorkingCopySaveParticipantContext, token: CancellationToken): Promise { return this.saveParticipants.participate(workingCopy, context, token); } diff --git a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts index ba5268aff39..afe82979c8d 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts @@ -879,11 +879,12 @@ suite('StoredFileWorkingCopy', function () { }); async function testSaveFromSaveParticipant(workingCopy: StoredFileWorkingCopy, async: boolean): Promise { - + const from = URI.file('testFrom'); assert.strictEqual(accessor.workingCopyFileService.hasSaveParticipants, false); const disposable = accessor.workingCopyFileService.addSaveParticipant({ - participate: async () => { + participate: async (wc, context) => { + if (async) { await timeout(10); } @@ -894,11 +895,40 @@ suite('StoredFileWorkingCopy', function () { assert.strictEqual(accessor.workingCopyFileService.hasSaveParticipants, true); - await workingCopy.save({ force: true }); + await workingCopy.save({ force: true, from }); disposable.dispose(); } + test('Save Participant carries context', async function () { + await workingCopy.resolve(); + + const from = URI.file('testFrom'); + assert.strictEqual(accessor.workingCopyFileService.hasSaveParticipants, false); + + let e: Error | undefined = undefined; + const disposable = accessor.workingCopyFileService.addSaveParticipant({ + participate: async (wc, context) => { + try { + assert.strictEqual(context.reason, SaveReason.EXPLICIT); + assert.strictEqual(context.savedFrom?.toString(), from.toString()); + } catch (error) { + e = error; + } + } + }); + + assert.strictEqual(accessor.workingCopyFileService.hasSaveParticipants, true); + + await workingCopy.save({ force: true, from }); + + if (e) { + throw e; + } + + disposable.dispose(); + }); + test('revert', async () => { await workingCopy.resolve(); workingCopy.model?.updateContents('hello revert'); diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index 864c6e9ed8f..1a938d7dbd6 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -15,7 +15,7 @@ import { isLinux, isMacintosh } from 'vs/base/common/platform'; import { InMemoryStorageService, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IWorkingCopy, IWorkingCopyBackup, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkingCopyFileService, IWorkingCopyFileOperationParticipant, WorkingCopyFileEvent, IDeleteOperation, ICopyOperation, IMoveOperation, IFileOperationUndoRedoInfo, ICreateFileOperation, ICreateOperation, IStoredFileWorkingCopySaveParticipant } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { IWorkingCopyFileService, IWorkingCopyFileOperationParticipant, WorkingCopyFileEvent, IDeleteOperation, ICopyOperation, IMoveOperation, IFileOperationUndoRedoInfo, ICreateFileOperation, ICreateOperation, IStoredFileWorkingCopySaveParticipant, IStoredFileWorkingCopySaveParticipantContext } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IBaseFileStat, IFileStatWithMetadata } from 'vs/platform/files/common/files'; import { ISaveOptions, IRevertOptions, SaveReason, GroupIdentifier } from 'vs/workbench/common/editor'; @@ -253,7 +253,7 @@ export class TestWorkingCopyFileService implements IWorkingCopyFileService { readonly hasSaveParticipants = false; addSaveParticipant(participant: IStoredFileWorkingCopySaveParticipant): IDisposable { return Disposable.None; } - async runSaveParticipants(workingCopy: IWorkingCopy, context: { reason: SaveReason }, token: CancellationToken): Promise { } + async runSaveParticipants(workingCopy: IWorkingCopy, context: IStoredFileWorkingCopySaveParticipantContext, token: CancellationToken): Promise { } async delete(operations: IDeleteOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise { } From 35d97bc7e439fce0f50f42074041ab2d8571b20a Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 26 Jan 2024 13:06:22 +0100 Subject: [PATCH 0636/1897] Update grammars (#203521) --- extensions/csharp/cgmanifest.json | 2 +- .../csharp/syntaxes/csharp.tmLanguage.json | 33 +- extensions/fsharp/cgmanifest.json | 2 +- .../fsharp/syntaxes/fsharp.tmLanguage.json | 7 +- extensions/go/cgmanifest.json | 4 +- extensions/go/syntaxes/go.tmLanguage.json | 423 ++++++++------ .../syntaxes/JavaScript.tmLanguage.json | 9 +- .../syntaxes/JavaScriptReact.tmLanguage.json | 9 +- extensions/julia/cgmanifest.json | 2 +- .../julia/syntaxes/julia.tmLanguage.json | 24 +- extensions/latex/cgmanifest.json | 2 +- .../latex/syntaxes/LaTeX.tmLanguage.json | 47 +- .../markdown-latex-combined.tmLanguage.json | 9 +- extensions/python/cgmanifest.json | 2 +- extensions/rust/cgmanifest.json | 2 +- extensions/rust/syntaxes/rust.tmLanguage.json | 16 +- extensions/typescript-basics/cgmanifest.json | 2 +- .../syntaxes/TypeScript.tmLanguage.json | 9 +- .../syntaxes/TypeScriptReact.tmLanguage.json | 9 +- .../vb/syntaxes/asp-vb-net.tmLanguage.json | 2 +- .../test/colorize-results/test_jl.json | 522 +++++++++++++++++- 21 files changed, 891 insertions(+), 246 deletions(-) diff --git a/extensions/csharp/cgmanifest.json b/extensions/csharp/cgmanifest.json index 0dceaebc966..1c88bd17296 100644 --- a/extensions/csharp/cgmanifest.json +++ b/extensions/csharp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "dotnet/csharp-tmLanguage", "repositoryUrl": "https://github.com/dotnet/csharp-tmLanguage", - "commitHash": "7bf5709ac1a713e340a618d1b41f44a043e393c6" + "commitHash": "7a7482ffc72a6677a87eb1ed76005593a4f7f131" } }, "license": "MIT", diff --git a/extensions/csharp/syntaxes/csharp.tmLanguage.json b/extensions/csharp/syntaxes/csharp.tmLanguage.json index 4e6a722d413..96dbe04473c 100644 --- a/extensions/csharp/syntaxes/csharp.tmLanguage.json +++ b/extensions/csharp/syntaxes/csharp.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/dotnet/csharp-tmLanguage/commit/7bf5709ac1a713e340a618d1b41f44a043e393c6", + "version": "https://github.com/dotnet/csharp-tmLanguage/commit/7a7482ffc72a6677a87eb1ed76005593a4f7f131", "name": "C#", "scopeName": "source.cs", "patterns": [ @@ -164,6 +164,9 @@ { "include": "#comment" }, + { + "include": "#storage-modifier" + }, { "include": "#property-declaration" }, @@ -176,6 +179,9 @@ { "include": "#method-declaration" }, + { + "include": "#operator-declaration" + }, { "include": "#attribute-section" }, @@ -1050,6 +1056,18 @@ "name": "storage.type.struct.cs", "match": "\\bstruct\\b" }, + { + "name": "keyword.other.constraint.default.cs", + "match": "\\bdefault\\b" + }, + { + "name": "keyword.other.constraint.notnull.cs", + "match": "\\bnotnull\\b" + }, + { + "name": "keyword.other.constraint.unmanaged.cs", + "match": "\\bunmanaged\\b" + }, { "match": "(new)\\s*(\\()\\s*(\\))", "captures": { @@ -2632,7 +2650,7 @@ }, "patterns": [ { - "begin": "\\G", + "begin": "(?=[^;\\)])", "end": "(?=;|\\))", "patterns": [ { @@ -2695,22 +2713,25 @@ "include": "#intrusive" }, { - "match": "(?x)\n(?:\n (\\bvar\\b)|\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\s+\n\\b(in)\\b", + "match": "(?x)\n(?:\n (?:(\\bref)\\s+)?(\\bvar\\b)| # ref local\n (?\n (?:\n (?:ref\\s+)? # ref local\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\s+\n\\b(in)\\b", "captures": { "1": { - "name": "storage.type.var.cs" + "name": "storage.modifier.ref.cs" }, "2": { + "name": "storage.type.var.cs" + }, + "3": { "patterns": [ { "include": "#type" } ] }, - "7": { + "8": { "name": "entity.name.variable.local.cs" }, - "8": { + "9": { "name": "keyword.control.loop.in.cs" } } diff --git a/extensions/fsharp/cgmanifest.json b/extensions/fsharp/cgmanifest.json index 03dad6e72c9..524b3fa0d46 100644 --- a/extensions/fsharp/cgmanifest.json +++ b/extensions/fsharp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "ionide/ionide-fsgrammar", "repositoryUrl": "https://github.com/ionide/ionide-fsgrammar", - "commitHash": "472c6b2030c962217cbbb26e4ddcce1b8ffe0867" + "commitHash": "7d029a46f17637228b2ee85dd02e511c3e8039b3" } }, "license": "MIT", diff --git a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json index 20151138ccf..5063f1c5210 100644 --- a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json +++ b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/ionide/ionide-fsgrammar/commit/472c6b2030c962217cbbb26e4ddcce1b8ffe0867", + "version": "https://github.com/ionide/ionide-fsgrammar/commit/7d029a46f17637228b2ee85dd02e511c3e8039b3", "name": "fsharp", "scopeName": "source.fsharp", "patterns": [ @@ -574,6 +574,11 @@ "name": "fast-capture.comment.line.double-slash.fsharp", "match": "//" }, + { + "comments": "Capture (*) when inside of (* *) so that it doesn't prematurely end the comment block.", + "name": "fast-capture.comment.line.mul-operator.fsharp", + "match": "\\(\\*\\)" + }, { "include": "#comments" } diff --git a/extensions/go/cgmanifest.json b/extensions/go/cgmanifest.json index 90df81c75ec..dd832ef5145 100644 --- a/extensions/go/cgmanifest.json +++ b/extensions/go/cgmanifest.json @@ -6,12 +6,12 @@ "git": { "name": "go-syntax", "repositoryUrl": "https://github.com/worlpaker/go-syntax", - "commitHash": "4014e9376f32e5317eb0c6be286db7ffcba838f9" + "commitHash": "80a9e153c018b6c3b9c52766b39d7be9d915f68b" } }, "license": "MIT", "description": "The file syntaxes/go.tmLanguage.json is from https://github.com/worlpaker/go-syntax, which in turn was derived from https://github.com/jeff-hykin/better-go-syntax.", - "version": "0.5.1" + "version": "0.5.5" } ], "version": 1 diff --git a/extensions/go/syntaxes/go.tmLanguage.json b/extensions/go/syntaxes/go.tmLanguage.json index 85e2bbfe78e..5917568d4cc 100644 --- a/extensions/go/syntaxes/go.tmLanguage.json +++ b/extensions/go/syntaxes/go.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/worlpaker/go-syntax/commit/4014e9376f32e5317eb0c6be286db7ffcba838f9", + "version": "https://github.com/worlpaker/go-syntax/commit/80a9e153c018b6c3b9c52766b39d7be9d915f68b", "name": "Go", "scopeName": "source.go", "patterns": [ @@ -61,6 +61,9 @@ { "include": "#other_struct_interface_expressions" }, + { + "include": "#type_assertion_inline" + }, { "include": "#struct_variables_types" }, @@ -91,19 +94,22 @@ "comment": "all statements related to variables", "patterns": [ { - "include": "#after_control_variables" + "include": "#var_const_assignment" }, { - "include": "#var_const_single_assignment" + "include": "#variable_assignment" }, { - "include": "#var_assignment" + "include": "#label_loop_variables" }, { - "include": "#var_other_assignment" + "include": "#slice_index_variables" }, { - "include": "#var_const_multi_assignment" + "include": "#property_variables" + }, + { + "include": "#switch_select_case_variables" }, { "include": "#other_variables" @@ -141,7 +147,7 @@ "include": "#storage_types" }, { - "include": "#raw_strings_literals" + "include": "#raw_string_literals" }, { "include": "#string_literals" @@ -182,7 +188,7 @@ "include": "#storage_types" }, { - "include": "#raw_strings_literals" + "include": "#raw_string_literals" }, { "include": "#string_literals" @@ -582,7 +588,7 @@ } ] }, - "raw_strings_literals": { + "raw_string_literals": { "comment": "Raw string literals", "begin": "`", "beginCaptures": { @@ -1272,7 +1278,7 @@ }, { "comment": "struct type declaration", - "match": "((?:(?:\\w+\\,\\s*)+)?\\w+)\\s+(?=(?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:[\\[\\]\\*]+)?\\bstruct\\b\\s*\\{)", + "match": "((?:(?:\\b\\w+\\,\\s*)+)?\\b\\w+)\\s+(?=(?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:[\\[\\]\\*]+)?\\bstruct\\b\\s*\\{)", "captures": { "1": { "patterns": [ @@ -1289,7 +1295,7 @@ }, { "comment": "multiple parameters one type -with multilines", - "match": "(?:(?:(?<=\\()|^\\s*)((?:(?:\\w+\\,\\s*)+)(?:/(?:/|\\*).*)?)$)", + "match": "(?:(?:(?<=\\()|^\\s*)((?:(?:\\b\\w+\\,\\s*)+)(?:/(?:/|\\*).*)?)$)", "captures": { "1": { "patterns": [ @@ -1306,7 +1312,7 @@ }, { "comment": "multiple params and types | multiple params one type | one param one type", - "match": "(?:((?:(?:\\w+\\,\\s*)+)?\\w+)(?:\\s+)((?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:(?:(?:[\\w\\[\\]\\.\\*]+)?(?:(?:\\bfunc\\b\\((?:[^\\)]+)?\\))(?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:\\s*))+(?:(?:(?:[\\w\\*\\.\\[\\]]+)|(?:\\((?:[^\\)]+)?\\))))?)|(?:(?:[\\[\\]\\*]+)?[\\w\\*\\.]+(?:\\[(?:[^\\]]+)\\])?(?:[\\w\\.\\*]+)?)+)))", + "match": "(?:((?:(?:\\b\\w+\\,\\s*)+)?\\b\\w+)(?:\\s+)((?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:(?:(?:[\\w\\[\\]\\.\\*]+)?(?:(?:\\bfunc\\b\\((?:[^\\)]+)?\\))(?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:\\s*))+(?:(?:(?:[\\w\\*\\.\\[\\]]+)|(?:\\((?:[^\\)]+)?\\))))?)|(?:(?:[\\[\\]\\*]+)?[\\w\\*\\.]+(?:\\[(?:[^\\]]+)\\])?(?:[\\w\\.\\*]+)?)+)))", "captures": { "1": { "patterns": [ @@ -1368,7 +1374,7 @@ }, { "comment": "multiple parameters one type -with multilines", - "match": "(?:(?:(?<=\\()|^\\s*)((?:(?:\\w+\\,\\s*)+)(?:/(?:/|\\*).*)?)$)", + "match": "(?:(?:(?<=\\()|^\\s*)((?:(?:\\b\\w+\\,\\s*)+)(?:/(?:/|\\*).*)?)$)", "captures": { "1": { "patterns": [ @@ -1385,7 +1391,7 @@ }, { "comment": "multiple params and types | multiple types one param", - "match": "(?:((?:(?:\\w+\\,\\s*)+)?\\w+)(?:\\s+)((?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:(?:(?:[\\w\\[\\]\\.\\*]+)?(?:(?:\\bfunc\\b\\((?:[^\\)]+)?\\))(?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:\\s*))+(?:(?:(?:[\\w\\*\\.]+)|(?:\\((?:[^\\)]+)?\\))))?)|(?:(?:(?:[\\w\\*\\.\\~]+)|(?:\\[(?:(?:[\\w\\.\\*]+)?(?:\\[(?:[^\\]]+)?\\])?(?:\\,\\s+)?)+\\]))(?:[\\w\\.\\*]+)?)+)))", + "match": "(?:((?:(?:\\b\\w+\\,\\s*)+)?\\b\\w+)(?:\\s+)((?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:(?:(?:[\\w\\[\\]\\.\\*]+)?(?:(?:\\bfunc\\b\\((?:[^\\)]+)?\\))(?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:\\s*))+(?:(?:(?:[\\w\\*\\.]+)|(?:\\((?:[^\\)]+)?\\))))?)|(?:(?:(?:[\\w\\*\\.\\~]+)|(?:\\[(?:(?:[\\w\\.\\*]+)?(?:\\[(?:[^\\]]+)?\\])?(?:\\,\\s+)?)+\\]))(?:[\\w\\.\\*]+)?)+)))", "captures": { "1": { "patterns": [ @@ -1430,7 +1436,7 @@ }, { "comment": "other types", - "match": "([\\w\\.]+)", + "match": "(?:\\b([\\w\\.]+))", "captures": { "1": { "patterns": [ @@ -1590,25 +1596,13 @@ } }, "other_struct_interface_expressions": { + "comment": "struct and interface expression in-line (before curly bracket)", "patterns": [ - { - "include": "#storage_types" - }, - { - "include": "#label_loop_variable" - }, - { - "include": "#property_variables" - }, - { - "include": "#switch_select_case_variables" - }, { "include": "#after_control_variables" }, { - "comment": "struct expression before curly bracket", - "match": "((?:(?:\\w+\\.)+)?\\w+)(\\[(?:[^\\]]+)?\\])?(?=\\{)(?|\\<\\=|\\>\\=|\\=\\=|\\!\\=|\\w(?:\\+|/|\\-|\\*|\\%)(?:\\=)?|\\|\\||\\&\\&)(?:\\s*)([[:alnum:]\\-\\_\\!\\.\\[\\]\\<\\>\\=\\*/\\+\\%\\:]+)(?:\\s*)(?=\\{))", + "match": "(?:(?<=\\brange\\b|\\bswitch\\b|\\;|\\bif\\b|\\bfor\\b|\\<|\\>|\\<\\=|\\>\\=|\\=\\=|\\!\\=|\\w(?:\\+|/|\\-|\\*|\\%)|\\w(?:\\+|/|\\-|\\*|\\%)\\=|\\|\\||\\&\\&)(?:\\s*)([[:alnum:]\\-\\_\\!\\.\\[\\]\\<\\>\\=\\*/\\+\\%\\:]+)(?:\\s*)(?=\\{))", "captures": { "1": { "patterns": [ @@ -2385,90 +2379,40 @@ } ] }, - "var_const_single_assignment": { - "comment": "var and const with single type assignment", - "match": "(?:(?<=\\bvar\\b|\\bconst\\b)(?:\\s*)([\\w\\.]+(?:(?:\\,\\s*[\\w\\.]+)+)?)(?:\\s*)(?:(?!(?:(?:[\\[\\]\\*]+)?\\bstruct\\b\\s*\\{)|(?:(?:[\\[\\]\\*]+)?\\bfunc\\b))((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:(?:(?:[\\w\\.\\*]+)?(?:\\[(?:.*)\\])?(?:[\\w\\.\\*]+)?))?\\s*(?:\\=)?))?)", - "captures": { - "1": { - "patterns": [ - { - "include": "#delimiters" - }, - { - "match": "(?:\\w+)", - "name": "variable.other.assignment.go" - } - ] - }, - "2": { - "patterns": [ - { - "include": "#type-declarations" - }, - { - "include": "#generic_types" - }, - { - "match": "(?:\\w+)", - "name": "entity.name.type.go" - } - ] - } - } - }, - "var_assignment": { - "comment": "variable assignment", - "match": "(?,\\s*\\w+(?:\\.\\w+)*)*)(?=\\s*=(?!=))", - "captures": { - "1": { - "patterns": [ - { - "match": "\\d\\w*", - "name": "invalid.illegal.identifier.go" - }, - { - "match": "\\w+(?:\\.\\w+)*", - "name": "variable.other.assignment.go", - "captures": { - "0": { - "patterns": [ - { - "include": "#delimiters" - } - ] - } - } - }, - { - "include": "#delimiters" - } - ] - } - } - }, - "var_other_assignment": { - "comment": "variable other assignment", - "match": "\\b\\w+(?:,\\s*\\w+)*(?=\\s*:=)", - "captures": { - "0": { - "patterns": [ - { - "match": "\\d\\w*", - "name": "invalid.illegal.identifier.go" - }, - { - "match": "\\w+", - "name": "variable.other.assignment.go" - }, - { - "include": "#delimiters" - } - ] - } - } - }, - "var_const_multi_assignment": { + "var_const_assignment": { + "comment": "variable assignment with var and const keyword", "patterns": [ + { + "comment": "var and const with single type assignment", + "match": "(?:(?<=\\bvar\\b|\\bconst\\b)(?:\\s*)([\\w\\.]+(?:(?:\\,\\s*[\\w\\.]+)+)?)(?:\\s*)(?:((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?!(?:(?:[\\[\\]\\*]+)?\\bstruct\\b\\s*\\{)|(?:(?:[\\[\\]\\*]+)?\\bfunc\\b))(?:(?:(?:[\\w\\.\\*]+)?(?:\\[(?:.*)\\])?(?:[\\w\\.\\*]+)?))?\\s*(?:\\=)?))?)", + "captures": { + "1": { + "patterns": [ + { + "include": "#delimiters" + }, + { + "match": "(?:\\w+)", + "name": "variable.other.assignment.go" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "include": "#generic_types" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + } + } + }, { "comment": "var and const with multi type assignment", "begin": "(?:(?<=\\bvar\\b|\\bconst\\b)(?:\\s*)(\\())", @@ -2485,7 +2429,7 @@ }, "patterns": [ { - "match": "(?:(?:^\\s+)([\\w\\.]+(?:(?:\\,\\s*[\\w\\.]+)+)?)(?:\\s*)((?:(?!(?:(?:[\\[\\]\\*]+)?\\bstruct\\b\\s*\\{)|(?:(?:[\\[\\]\\*]+)?\\bfunc\\b))((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:(?:(?:[\\w\\.\\*]+)?(?:\\[(?:.*)\\])?(?:[\\w\\.\\*]+)?))?\\s*(?:\\=)?))?))", + "match": "(?:(?:^\\s+)([\\w\\.]+(?:(?:\\,\\s*[\\w\\.]+)+)?)(?:\\s*)((?:((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?!(?:(?:[\\[\\]\\*]+)?\\bstruct\\b\\s*\\{)|(?:(?:[\\[\\]\\*]+)?\\bfunc\\b))(?:(?:(?:[\\w\\.\\*]+)?(?:\\[(?:.*)\\])?(?:[\\w\\.\\*]+)?))?\\s*(?:\\=)?))?))", "captures": { "1": { "patterns": [ @@ -2521,6 +2465,56 @@ } ] }, + "variable_assignment": { + "comment": "variable assignment", + "patterns": [ + { + "comment": "variable assignment with :=", + "match": "\\b\\w+(?:\\,\\s*\\w+)*(?=\\s*:=)", + "captures": { + "0": { + "patterns": [ + { + "include": "#delimiters" + }, + { + "match": "\\d\\w*", + "name": "invalid.illegal.identifier.go" + }, + { + "match": "\\w+", + "name": "variable.other.assignment.go" + } + ] + } + } + }, + { + "comment": "variable assignment with =", + "match": "\\b[\\w\\.\\*]+(?:\\,\\s*[\\w\\.\\*]+)*(?=\\s*=(?!=))", + "captures": { + "0": { + "patterns": [ + { + "include": "#delimiters" + }, + { + "include": "#operators" + }, + { + "match": "\\d\\w*", + "name": "invalid.illegal.identifier.go" + }, + { + "match": "\\w+", + "name": "variable.other.assignment.go" + } + ] + } + } + } + ] + }, "generic_types": { "comment": "Generic support for all types", "match": "(?:([\\w\\.\\*]+)(\\[(?:[^\\]]+)?\\]))", @@ -2568,52 +2562,103 @@ } } }, - "property_variables": { - "patterns": [ - { - "comment": "Property variables in struct", - "match": "(?:((?:[\\w\\.]+)(?:\\:))(?!\\=))", - "captures": { - "1": { - "patterns": [ - { - "include": "#type-declarations" - }, - { - "match": "\\w+", - "name": "variable.other.property.go" - } - ] - } - } - }, - { - "comment": "property variables as parameter field in struct initialization", - "match": "(?<=[\\w\\.]\\:)(?:\\s*)([\\w\\.\\*\\&\\[\\]]+)(\\.\\w+)(?![\\w\\.\\*\\&\\[\\]]*(?:\\{|\\())", - "captures": { - "1": { - "patterns": [ - { - "include": "$self" - } - ] + "slice_index_variables": { + "comment": "slice index and capacity variables, to not scope them as property variables", + "match": "(?<=\\w\\[)((?:(?:\\b[\\w\\.\\*\\+/\\-\\*\\%\\<\\>\\|\\&]+\\:)|(?:\\:\\b[\\w\\.\\*\\+/\\-\\*\\%\\<\\>\\|\\&]+))(?:\\b[\\w\\.\\*\\+/\\-\\*\\%\\<\\>\\|\\&]+)?(?:\\:\\b[\\w\\.\\*\\+/\\-\\*\\%\\<\\>\\|\\&]+)?)(?=\\])", + "captures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" }, - "2": { - "patterns": [ - { - "include": "#type-declarations" - }, - { - "match": "(?:\\w+)", - "name": "variable.other.property.field.go" - } - ] + { + "match": "\\w+", + "name": "variable.other.go" } - } + ] } - ] + } }, - "label_loop_variable": { + "property_variables": { + "comment": "Property variables in struct | parameter field in struct initialization", + "match": "(?:(?:((?:\\b[\\w\\.]+)(?:\\:(?!\\=))))(?:(?:\\s*([\\w\\.\\*\\&\\[\\]]+)(\\.\\w+)(?![\\w\\.\\*\\&\\[\\]]*(?:\\{|\\()))((?:\\s*(?:\\<|\\>|\\<\\=|\\>\\=|\\=\\=|\\!\\=|\\|\\||\\&\\&|\\+|/|\\-|\\*|\\%|\\||\\&)\\s*(?:[\\w\\.\\*\\&\\[\\]]+)(?:\\.\\w+)(?![\\w\\.\\*\\&\\[\\]]*(?:\\{|\\()))*))?)", + "captures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "variable.other.property.go" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "variable.other.go" + }, + { + "include": "$self" + } + ] + }, + "3": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "variable.other.property.field.go" + }, + { + "include": "$self" + } + ] + }, + "4": { + "patterns": [ + { + "match": "([\\w\\.\\*\\&\\[\\]]+)(\\.\\w+)", + "captures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "variable.other.go" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "variable.other.property.field.go" + } + ] + } + } + }, + { + "include": "$self" + } + ] + } + } + }, + "label_loop_variables": { "comment": "labeled loop variable name", "match": "((?:^\\s*\\w+:\\s*$)|(?:^\\s*(?:\\bbreak\\b|\\bgoto\\b|\\bcontinue\\b)\\s+\\w+(?:\\s*/(?:/|\\*)\\s*.*)?$))", "captures": { @@ -2632,24 +2677,34 @@ }, "double_parentheses_types": { "comment": "double parentheses types", - "match": "(?:(?:(\\()([^\\)]+)(\\)))(?=\\((?:[\\w\\.\\*\\&]+)\\)))", + "match": "(?:(\\((?:[\\w\\.\\[\\]\\*\\&]+)\\))(?=\\())", "captures": { "1": { - "name": "punctuation.definition.begin.bracket.round.go" - }, - "2": { "patterns": [ { - "include": "#type-declarations" + "include": "#type-declarations-without-brackets" + }, + { + "match": "\\(", + "name": "punctuation.definition.begin.bracket.round.go" + }, + { + "match": "\\)", + "name": "punctuation.definition.end.bracket.round.go" + }, + { + "match": "\\[", + "name": "punctuation.definition.begin.bracket.square.go" + }, + { + "match": "\\]", + "name": "punctuation.definition.end.bracket.square.go" }, { "match": "\\w+", "name": "entity.name.type.go" } ] - }, - "3": { - "name": "punctuation.definition.end.bracket.round.go" } } }, diff --git a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json index 4fe09e087aa..bec7b9f5b92 100644 --- a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/TypeScript-TmLanguage/commit/8c7482b94b548eab56da64dbfb30b82589b3f747", + "version": "https://github.com/microsoft/TypeScript-TmLanguage/commit/b80b7509a78e642f789c567e144ed951ab98b4e3", "name": "JavaScript (with React support)", "scopeName": "source.js", "patterns": [ @@ -2351,12 +2351,15 @@ ] }, "import-export-assert-clause": { - "begin": "(? Date: Fri, 26 Jan 2024 13:19:43 +0100 Subject: [PATCH 0637/1897] fix #203523 (#203524) --- .../node/extensionManagementService.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index c7137910f44..905dec7cd90 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -599,7 +599,13 @@ export class ExtensionsScanner extends Disposable { metadata = { ...source?.metadata, ...metadata }; if (target) { - await this.extensionsProfileScannerService.updateMetadata([[extension, { ...target.metadata, ...metadata }]], toProfileLocation); + if (this.uriIdentityService.extUri.isEqual(target.location, extension.location)) { + await this.extensionsProfileScannerService.updateMetadata([[extension, { ...target.metadata, ...metadata }]], toProfileLocation); + } else { + const targetExtension = await this.scanLocalExtension(target.location, extension.type, toProfileLocation); + await this.extensionsProfileScannerService.removeExtensionFromProfile(targetExtension, toProfileLocation); + await this.extensionsProfileScannerService.addExtensionsToProfile([[extension, { ...target.metadata, ...metadata }]], toProfileLocation); + } } else { await this.extensionsProfileScannerService.addExtensionsToProfile([[extension, metadata]], toProfileLocation); } From fb5a1aca6de5209439268dc853e31464df69600c Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 26 Jan 2024 13:59:40 +0100 Subject: [PATCH 0638/1897] add more leakage checks in tests (#203525) re https://github.com/microsoft/vscode/issues/200091 --- .eslintrc.json | 14 --------- .../test/browser/smartSelect.test.ts | 3 ++ .../test/browser/snippetSession.test.ts | 3 ++ .../test/browser/completionModel.test.ts | 3 ++ .../test/browser/suggestMemory.test.ts | 3 ++ .../instantiation/test/common/graph.test.ts | 4 +++ .../markers/test/common/markerService.test.ts | 31 +++++++++++++------ .../progress/test/common/progress.test.ts | 4 +++ .../test/browser/extHostApiCommands.test.ts | 11 ++++--- .../api/test/browser/extHostBulkEdits.test.ts | 3 ++ .../extHostDocumentSaveParticipant.test.ts | 8 ++++- .../test/browser/extHostTypeConverter.test.ts | 5 ++- .../test/browser/mainThreadDocuments.test.ts | 3 ++ .../snippets/test/browser/snippetFile.test.ts | 3 ++ .../test/browser/snippetsRegistry.test.ts | 3 ++ .../test/browser/snippetsRewrite.test.ts | 3 ++ 16 files changed, 74 insertions(+), 30 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index e9670fc9c83..4bb1fc53e36 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -154,10 +154,6 @@ "src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts", "src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts", "src/vs/editor/contrib/gotoSymbol/test/browser/referencesModel.test.ts", - "src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts", - "src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts", - "src/vs/editor/contrib/suggest/test/browser/completionModel.test.ts", - "src/vs/editor/contrib/suggest/test/browser/suggestMemory.test.ts", "src/vs/editor/test/common/services/languageService.test.ts", "src/vs/editor/test/node/classification/typescript.test.ts", "src/vs/editor/test/node/diffing/defaultLinesDiffComputer.test.ts", @@ -167,22 +163,15 @@ "src/vs/platform/contextkey/test/common/parser.test.ts", "src/vs/platform/contextkey/test/common/scanner.test.ts", "src/vs/platform/extensions/test/common/extensionValidator.test.ts", - "src/vs/platform/instantiation/test/common/graph.test.ts", "src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts", "src/vs/platform/keybinding/test/common/keybindingLabels.test.ts", "src/vs/platform/keybinding/test/common/keybindingResolver.test.ts", - "src/vs/platform/markers/test/common/markerService.test.ts", "src/vs/platform/opener/test/common/opener.test.ts", - "src/vs/platform/progress/test/common/progress.test.ts", "src/vs/platform/registry/test/common/platform.test.ts", "src/vs/platform/remote/test/common/remoteHosts.test.ts", "src/vs/platform/telemetry/test/browser/1dsAppender.test.ts", "src/vs/platform/workspace/test/common/workspace.test.ts", "src/vs/platform/workspaces/test/electron-main/workspaces.test.ts", - "src/vs/workbench/api/test/browser/extHostApiCommands.test.ts", - "src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts", - "src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts", - "src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts", "src/vs/workbench/api/test/browser/extHostWorkspace.test.ts", "src/vs/workbench/api/test/browser/mainThreadConfiguration.test.ts", "src/vs/workbench/api/test/browser/mainThreadDocuments.test.ts", @@ -194,9 +183,6 @@ "src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts", "src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts", "src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts", - "src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts", - "src/vs/workbench/contrib/snippets/test/browser/snippetsRegistry.test.ts", - "src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts", "src/vs/workbench/contrib/tasks/test/common/problemMatcher.test.ts", "src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts", "src/vs/workbench/services/commands/test/common/commandService.test.ts", diff --git a/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts b/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts index dadceb8254a..fb1307529f6 100644 --- a/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts @@ -19,6 +19,7 @@ import { createModelServices } from 'vs/editor/test/common/testTextModel'; import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; import { ILanguageSelection, ILanguageService } from 'vs/editor/common/languages/language'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; class StaticLanguageSelector implements ILanguageSelection { readonly onDidChange: Event = Event.None; @@ -64,6 +65,8 @@ suite('SmartSelect', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + async function assertGetRangesToPosition(text: string[], lineNumber: number, column: number, ranges: Range[], selectLeadingAndTrailingWhitespace = true): Promise { const uri = URI.file('test.js'); const model = modelService.createModel(text.join('\n'), new StaticLanguageSelector(languageId), uri); diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts index a9563c30f13..61061846724 100644 --- a/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts +++ b/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { mock } from 'vs/base/test/common/mock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -58,6 +59,8 @@ suite('SnippetSession', function () { editor.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('normalize whitespace', function () { function assertNormalized(position: IPosition, input: string, expected: string): void { diff --git a/src/vs/editor/contrib/suggest/test/browser/completionModel.test.ts b/src/vs/editor/contrib/suggest/test/browser/completionModel.test.ts index 2cc955597b4..cb08e56fb7f 100644 --- a/src/vs/editor/contrib/suggest/test/browser/completionModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/browser/completionModel.test.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { EditorOptions, InternalSuggestOptions } from 'vs/editor/common/config/editorOptions'; import { IPosition } from 'vs/editor/common/core/position'; import * as languages from 'vs/editor/common/languages'; @@ -83,6 +84,8 @@ suite('CompletionModel', function () { }, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('filtering - cached', function () { const itemsNow = model.items; diff --git a/src/vs/editor/contrib/suggest/test/browser/suggestMemory.test.ts b/src/vs/editor/contrib/suggest/test/browser/suggestMemory.test.ts index e0c1496c0d0..3bbc8c58838 100644 --- a/src/vs/editor/contrib/suggest/test/browser/suggestMemory.test.ts +++ b/src/vs/editor/contrib/suggest/test/browser/suggestMemory.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IPosition } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; import { CompletionItem } from 'vs/editor/contrib/suggest/browser/suggest'; @@ -30,6 +31,8 @@ suite('SuggestMemories', function () { buffer.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('AbstractMemory, select', function () { const mem = new class extends Memory { diff --git a/src/vs/platform/instantiation/test/common/graph.test.ts b/src/vs/platform/instantiation/test/common/graph.test.ts index 29ef9de95b3..c5abc22ccff 100644 --- a/src/vs/platform/instantiation/test/common/graph.test.ts +++ b/src/vs/platform/instantiation/test/common/graph.test.ts @@ -3,15 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Graph } from 'vs/platform/instantiation/common/graph'; suite('Graph', () => { + let graph: Graph; setup(() => { graph = new Graph(s => s); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('is possible to lookup nodes that don\'t exist', function () { assert.strictEqual(graph.lookup('ddd'), undefined); }); diff --git a/src/vs/platform/markers/test/common/markerService.test.ts b/src/vs/platform/markers/test/common/markerService.test.ts index 73ab130a90a..d8ebccf746f 100644 --- a/src/vs/platform/markers/test/common/markerService.test.ts +++ b/src/vs/platform/markers/test/common/markerService.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import * as markerService from 'vs/platform/markers/common/markerService'; @@ -21,9 +22,17 @@ function randomMarkerData(severity = MarkerSeverity.Error): IMarkerData { suite('Marker Service', () => { + let service: markerService.MarkerService; + + teardown(function () { + service.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + test('query', () => { - const service = new markerService.MarkerService(); + service = new markerService.MarkerService(); service.changeAll('far', [{ resource: URI.parse('file:///c/test/file.cs'), @@ -55,7 +64,7 @@ suite('Marker Service', () => { test('changeOne override', () => { - const service = new markerService.MarkerService(); + service = new markerService.MarkerService(); service.changeOne('far', URI.parse('file:///path/only.cs'), [randomMarkerData()]); assert.strictEqual(service.read().length, 1); assert.strictEqual(service.read({ owner: 'far' }).length, 1); @@ -73,7 +82,7 @@ suite('Marker Service', () => { test('changeOne/All clears', () => { - const service = new markerService.MarkerService(); + service = new markerService.MarkerService(); service.changeOne('far', URI.parse('file:///path/only.cs'), [randomMarkerData()]); service.changeOne('boo', URI.parse('file:///path/only.cs'), [randomMarkerData()]); assert.strictEqual(service.read({ owner: 'far' }).length, 1); @@ -93,7 +102,7 @@ suite('Marker Service', () => { test('changeAll sends event for cleared', () => { - const service = new markerService.MarkerService(); + service = new markerService.MarkerService(); service.changeAll('far', [{ resource: URI.parse('file:///d/path'), marker: randomMarkerData() @@ -104,17 +113,19 @@ suite('Marker Service', () => { assert.strictEqual(service.read({ owner: 'far' }).length, 2); - service.onMarkerChanged(changedResources => { + const d = service.onMarkerChanged(changedResources => { assert.strictEqual(changedResources.length, 1); changedResources.forEach(u => assert.strictEqual(u.toString(), 'file:///d/path')); assert.strictEqual(service.read({ owner: 'far' }).length, 0); }); service.changeAll('far', []); + + d.dispose(); }); test('changeAll merges', () => { - const service = new markerService.MarkerService(); + service = new markerService.MarkerService(); service.changeAll('far', [{ resource: URI.parse('file:///c/test/file.cs'), @@ -128,7 +139,7 @@ suite('Marker Service', () => { }); test('changeAll must not break integrety, issue #12635', () => { - const service = new markerService.MarkerService(); + service = new markerService.MarkerService(); service.changeAll('far', [{ resource: URI.parse('scheme:path1'), @@ -158,7 +169,7 @@ suite('Marker Service', () => { test('invalid marker data', () => { const data = randomMarkerData(); - const service = new markerService.MarkerService(); + service = new markerService.MarkerService(); data.message = undefined!; service.changeOne('far', URI.parse('some:uri/path'), [data]); @@ -174,7 +185,7 @@ suite('Marker Service', () => { }); test('MapMap#remove returns bad values, https://github.com/microsoft/vscode/issues/13548', () => { - const service = new markerService.MarkerService(); + service = new markerService.MarkerService(); service.changeOne('o', URI.parse('some:uri/1'), [randomMarkerData()]); service.changeOne('o', URI.parse('some:uri/2'), []); @@ -192,7 +203,7 @@ suite('Marker Service', () => { severity: 0 as MarkerSeverity, source: 'me' }; - const service = new markerService.MarkerService(); + service = new markerService.MarkerService(); service.changeOne('far', URI.parse('some:thing'), [data]); const marker = service.read({ resource: URI.parse('some:thing') }); diff --git a/src/vs/platform/progress/test/common/progress.test.ts b/src/vs/platform/progress/test/common/progress.test.ts index 638f7d59524..85bce306781 100644 --- a/src/vs/platform/progress/test/common/progress.test.ts +++ b/src/vs/platform/progress/test/common/progress.test.ts @@ -5,9 +5,13 @@ import * as assert from 'assert'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { AsyncProgress } from 'vs/platform/progress/common/progress'; suite('Progress', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('multiple report calls are processed in sequence', async () => { await runWithFakedTimers({ useFakeTimers: true, maxTaskCount: 100 }, async () => { const executionOrder: string[] = []; diff --git a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts index 3a60e4c025b..2e4e2560bfc 100644 --- a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts +++ b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts @@ -34,7 +34,6 @@ import { mock } from 'vs/base/test/common/mock'; import { NullApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; import { URITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; @@ -62,7 +61,7 @@ import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ensureFileSystemProviderError } from 'vs/platform/files/common/files'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; function assertRejects(fn: () => Promise, message: string = 'Expected rejection') { return fn().then(() => assert.ok(false, message), _err => assert.ok(true)); @@ -77,6 +76,7 @@ suite('ExtHostLanguageFeatureCommands', function () { const defaultSelector = { scheme: 'far' }; let model: ITextModel; + let insta: TestInstantiationService; let rpcProtocol: TestRPCProtocol; let extHost: ExtHostLanguageFeatures; let mainThread: MainThreadLanguageFeatures; @@ -153,7 +153,7 @@ suite('ExtHostLanguageFeatureCommands', function () { services.set(IOutlineModelService, new SyncDescriptor(OutlineModelService)); services.set(IConfigurationService, new TestConfigurationService()); - const insta = new InstantiationService(services); + insta = new TestInstantiationService(services); const extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ @@ -197,6 +197,9 @@ suite('ExtHostLanguageFeatureCommands', function () { setUnexpectedErrorHandler(originalErrorHandler); model.dispose(); mainThread.dispose(); + + (insta.get(IOutlineModelService)).dispose(); + insta.dispose(); }); teardown(() => { @@ -204,7 +207,7 @@ suite('ExtHostLanguageFeatureCommands', function () { return rpcProtocol.sync(); }); - ensureFileSystemProviderError(); + // ensureNoDisposablesAreLeakedInTestSuite(); // --- workspace symbols diff --git a/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts b/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts index f1f18d641e8..f17a6c5b0e9 100644 --- a/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts +++ b/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts @@ -12,6 +12,7 @@ import { SingleProxyRPCProtocol, TestRPCProtocol } from 'vs/workbench/api/test/c import { NullLogService } from 'vs/platform/log/common/log'; import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits'; import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ExtHostBulkEdits.applyWorkspaceEdit', () => { @@ -43,6 +44,8 @@ suite('ExtHostBulkEdits.applyWorkspaceEdit', () => { bulkEdits = new ExtHostBulkEdits(rpcProtocol, documentsAndEditors); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('uses version id if document available', async () => { const edit = new extHostTypes.WorkspaceEdit(); edit.replace(resource, new extHostTypes.Range(0, 0, 0, 0), 'hello'); diff --git a/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts index c50aabb812f..59c02e4d3b1 100644 --- a/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts @@ -14,8 +14,12 @@ import { SaveReason } from 'vs/workbench/common/editor'; import type * as vscode from 'vscode'; import { mock } from 'vs/base/test/common/mock'; import { NullLogService } from 'vs/platform/log/common/log'; -import { timeout } from 'vs/base/common/async'; import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; + +function timeout(n: number) { + return new Promise(resolve => setTimeout(resolve, n)); +} suite('ExtHostDocumentSaveParticipant', () => { @@ -39,6 +43,8 @@ suite('ExtHostDocumentSaveParticipant', () => { documents = new ExtHostDocuments(SingleProxyRPCProtocol(null), documentsAndEditors); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('no listeners, no problem', () => { const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits); return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(() => assert.ok(true)); diff --git a/src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts b/src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts index 07060c51e4d..727c4dea880 100644 --- a/src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts @@ -8,11 +8,14 @@ import * as assert from 'assert'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { MarkdownString, NotebookCellOutputItem, NotebookData, LanguageSelector, WorkspaceEdit } from 'vs/workbench/api/common/extHostTypeConverters'; import { isEmptyObject } from 'vs/base/common/types'; -import { LogLevel as _MainLogLevel } from 'vs/platform/log/common/log'; import { URI } from 'vs/base/common/uri'; import { IWorkspaceTextEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ExtHostTypeConverter', function () { + + ensureNoDisposablesAreLeakedInTestSuite(); + function size(from: Record): number { let count = 0; for (const key in from) { diff --git a/src/vs/workbench/api/test/browser/mainThreadDocuments.test.ts b/src/vs/workbench/api/test/browser/mainThreadDocuments.test.ts index 69554639bd2..8331e51cc03 100644 --- a/src/vs/workbench/api/test/browser/mainThreadDocuments.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadDocuments.test.ts @@ -8,6 +8,7 @@ import { BoundModelReferenceCollection } from 'vs/workbench/api/browser/mainThre import { timeout } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; import { extUri } from 'vs/base/common/resources'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('BoundModelReferenceCollection', function () { @@ -21,6 +22,8 @@ suite('BoundModelReferenceCollection', function () { col.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('max age', async function () { let didDispose = false; diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts index e9bb5b9853c..82e14ccc1a6 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts @@ -8,9 +8,12 @@ import { SnippetFile, Snippet, SnippetSource } from 'vs/workbench/contrib/snippe import { URI } from 'vs/base/common/uri'; import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; import { generateUuid } from 'vs/base/common/uuid'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Snippets', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + class TestSnippetFile extends SnippetFile { constructor(filepath: URI, snippets: Snippet[]) { super(SnippetSource.Extension, filepath, undefined, undefined, undefined!, undefined!); diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsRegistry.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsRegistry.test.ts index 8a9d39422e4..8c185e766a5 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsRegistry.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsRegistry.test.ts @@ -6,9 +6,12 @@ import * as assert from 'assert'; import { getNonWhitespacePrefix } from 'vs/workbench/contrib/snippets/browser/snippetsService'; import { Position } from 'vs/editor/common/core/position'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('getNonWhitespacePrefix', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function assertGetNonWhitespacePrefix(line: string, column: number, expected: string): void { const model = { getLineContent: (lineNumber: number) => line diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts index 54e7e2794f0..5ac2018ef7d 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts @@ -5,10 +5,13 @@ import * as assert from 'assert'; import { generateUuid } from 'vs/base/common/uuid'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; suite('SnippetRewrite', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + function assertRewrite(input: string, expected: string | boolean): void { const actual = new Snippet(false, ['foo'], 'foo', 'foo', 'foo', input, 'foo', SnippetSource.User, generateUuid()); if (typeof expected === 'boolean') { From 86a50fd23151abc6c633296a65423b72fbce812e Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 26 Jan 2024 14:15:30 +0100 Subject: [PATCH 0639/1897] polishing the code --- .../browser/inlineCompletionsModel.ts | 30 ++++++------------- .../browser/inlineCompletionsProvider.test.ts | 5 ++-- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 70526ffd82e..c79372277f0 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -306,15 +306,9 @@ export class InlineCompletionsModel extends Disposable { editor.setPosition(completion.snippetInfo.range.getStartPosition(), 'inlineCompletionAccept'); SnippetController2.get(editor)?.insert(completion.snippetInfo.snippet, { undoStopBefore: false }); } else { - editor.executeEdits( - 'inlineSuggestion.accept', - [ - ...this._getEdits(editor, completion.toSingleTextEdit()).edits, - ...completion.additionalTextEdits - ] - ); - - // set the selections after + const edits = this._getEdits(editor, completion.toSingleTextEdit()); + editor.executeEdits('inlineSuggestion.accept', [...edits.edits, ...completion.additionalTextEdits]); + editor.setSelections(edits.editorSelections, 'inlineCompletionPartialAccept'); } if (completion.command) { @@ -450,9 +444,7 @@ export class InlineCompletionsModel extends Disposable { const replacedTextAfterPrimaryCursor = textModel .getLineContent(primaryPosition.lineNumber) .substring(primaryPosition.column - 1, completion.range.endColumn - 1); - console.log('completion : ', JSON.stringify(completion)); const secondaryEditText = completion.text.substring(primaryPosition.column - completion.range.startColumn); - console.log('secondaryEditText : ', secondaryEditText); const edits = [ EditOperation.replaceMove(completion.range, completion.text), ...secondaryPositions.map(pos => { @@ -464,16 +456,12 @@ export class InlineCompletionsModel extends Disposable { return EditOperation.replaceMove(range, secondaryEditText); }) ]; - - // 1. need to figure out the new selections using the information at hand - // 2. polish and clean the code, should be able to simplify the below - let editorSelections: ISelection[] = []; - const primaryEditLength = lengthOfText(completion.text ?? ''); - const secondaryEditLength = lengthOfText(secondaryEditText); - editorSelections = [ - Selection.fromPositions(addPositions(new Position(completion.range.startLineNumber, completion.range.startColumn), primaryEditLength)), - ...secondaryPositions.map(pos => Selection.fromPositions(addPositions(pos, secondaryEditLength))) - ]; + const editorSelections = edits.map(edit => Selection.fromPositions( + addPositions( + Position.lift({ lineNumber: edit.range.startLineNumber, column: edit.range.startColumn }), + lengthOfText(edit.text ?? '') + ) + )); return { edits, diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts index 3555531a759..20dd10e2d67 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts @@ -654,6 +654,8 @@ suite('Inline Completions', () => { async function acceptNextWord(model: InlineCompletionsModel, editor: ITestCodeEditor, timesToAccept: number = 1): Promise { for (let i = 0; i < timesToAccept; i++) { + model.triggerExplicitly(); + await timeout(1000); await model.acceptNextWord(editor); } } @@ -674,9 +676,6 @@ suite('Inline Completions', () => { range: new Range(1, 1, 1, 1000), }); - model.triggerExplicitly(); - await timeout(1000); - await acceptNextWord(model, editor, 2); assert.deepStrictEqual( From cd003c2df3a6bc75163b4ed50ade525342696475 Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 26 Jan 2024 22:24:02 +0900 Subject: [PATCH 0640/1897] fix: double account for DPR in mouse wheel classifier (#203513) --- .../base/browser/ui/scrollbar/scrollableElement.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index 85253acf938..83e2d27eef7 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getZoomFactor } from 'vs/base/browser/browser'; +import { getZoomFactor, isChrome } from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { createFastDomNode, FastDomNode } from 'vs/base/browser/fastDomNode'; import { IMouseEvent, IMouseWheelEvent, StandardWheelEvent } from 'vs/base/browser/mouseEvent'; @@ -87,12 +87,12 @@ export class MouseWheelClassifier { } public acceptStandardWheelEvent(e: StandardWheelEvent): void { - const targetWindow = dom.getWindow(e.browserEvent); - const osZoomFactor = targetWindow.devicePixelRatio / getZoomFactor(targetWindow); - if (platform.isWindows || platform.isLinux) { - // On Windows and Linux, the incoming delta events are multiplied with the OS zoom factor. + if (isChrome) { + const targetWindow = dom.getWindow(e.browserEvent); + const pageZoomFactor = getZoomFactor(targetWindow); + // On Chrome, the incoming delta events are multiplied with the OS zoom factor. // The OS zoom factor can be reverse engineered by using the device pixel ratio and the configured zoom factor into account. - this.accept(Date.now(), e.deltaX / osZoomFactor, e.deltaY / osZoomFactor); + this.accept(Date.now(), e.deltaX * pageZoomFactor, e.deltaY * pageZoomFactor); } else { this.accept(Date.now(), e.deltaX, e.deltaY); } From a10ecfaefdd72727ad0633b541f5eecbd6c5c71d Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 26 Jan 2024 14:31:18 +0100 Subject: [PATCH 0641/1897] Git - fix issue related to a renamed resource (#203529) --- extensions/git/src/historyProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 88dc4e0a668..82c43be52fb 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -133,7 +133,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec historyItemChanges.push({ uri: historyItemUri, originalUri: toGitUri(change.originalUri, historyItemParentId), - modifiedUri: toGitUri(change.originalUri, historyItemId), + modifiedUri: toGitUri(change.uri, historyItemId), renameUri: change.renameUri, }); From 6eb3213066b6d07b65071859aec795200428ae70 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 26 Jan 2024 15:40:43 +0100 Subject: [PATCH 0642/1897] inline chat work (#203534) * debt - use hunks to render preview diff * add `InteractiveEditorRequest#previewDocument` to indicate for which document edits are previewed before being applied --- .../workbench/api/common/extHostInlineChat.ts | 1 + .../browser/inlineChatController.ts | 1 + .../browser/inlineChatSessionServiceImpl.ts | 4 +- .../browser/inlineChatStrategies.ts | 6 ++- .../inlineChat/browser/inlineChatWidget.ts | 40 +++++++-------- .../contrib/inlineChat/common/inlineChat.ts | 2 + .../test/browser/inlineChatController.test.ts | 51 ++++++++++++++++++- .../view/cellParts/chat/cellChatController.ts | 1 + .../vscode.proposed.interactive.d.ts | 1 + 9 files changed, 78 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/api/common/extHostInlineChat.ts b/src/vs/workbench/api/common/extHostInlineChat.ts index c6e4266df75..47ec20ccea2 100644 --- a/src/vs/workbench/api/common/extHostInlineChat.ts +++ b/src/vs/workbench/api/common/extHostInlineChat.ts @@ -151,6 +151,7 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { wholeRange: typeConvert.Range.to(request.wholeRange), attempt: request.attempt, live: request.live, + previewDocument: this._documents.getDocument(URI.revive(request.previewDocument)), withIntentDetection: request.withIntentDetection, }; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index be5046ad137..d0ff5ed48e5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -559,6 +559,7 @@ export class InlineChatController implements IEditorContribution { selection: this._editor.getSelection(), wholeRange: this._session.wholeRange.trackedInitialRange, live: this._session.editMode !== EditMode.Preview, // TODO@jrieken let extension know what document is used for previewing + previewDocument: this._session.textModelN.uri, withIntentDetection: options.withIntentDetection ?? true /* use intent detection by default */, }; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index 6784574fc21..e711ffb0fbe 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -110,7 +110,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { textModelN = store.add(this._modelService.createModel( createTextBufferFactoryFromSnapshot(textModel.createSnapshot()), { languageId: textModel.getLanguageId(), onDidChange: Event.None }, - targetUri.with({ scheme: Schemas.inMemory, query: new URLSearchParams({ id, 'inline-chat-textModelN': '' }).toString() }), true + targetUri.with({ scheme: Schemas.vscode, authority: 'inline-chat', path: '', query: new URLSearchParams({ id, 'textModelN': '' }).toString() }) )); } else { // AI edits happen in the actual model, keep a reference but make no copy @@ -122,7 +122,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { const textModel0 = store.add(this._modelService.createModel( createTextBufferFactoryFromSnapshot(textModel.createSnapshot()), { languageId: textModel.getLanguageId(), onDidChange: Event.None }, - targetUri.with({ scheme: Schemas.inMemory, query: new URLSearchParams({ id, 'inline-chat-textModel0': '' }).toString() }), true + targetUri.with({ scheme: Schemas.vscode, authority: 'inline-chat', path: '', query: new URLSearchParams({ id, 'textModel0': '' }).toString() }), true )); // untitled documents are special diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 4c347325e64..fdc930567c1 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -192,7 +192,9 @@ export class PreviewStrategy extends EditModeStrategy { } override async makeProgressiveChanges(edits: ISingleEditOperation[], obs: IEditObserver, opts: ProgressingEditsOptions): Promise { - return this._makeChanges(edits, obs, opts, undefined); + await this._makeChanges(edits, obs, opts, new Progress(() => { + this._zone.widget.showEditsPreview(this._session.hunkData, this._session.textModel0, this._session.textModelN); + })); } override async undoChanges(altVersionId: number): Promise { @@ -202,7 +204,7 @@ export class PreviewStrategy extends EditModeStrategy { override async renderChanges(response: ReplyResponse): Promise { if (response.allLocalEdits.length > 0) { - await this._zone.widget.showEditsPreview(this._session.textModel0, this._session.textModelN); + this._zone.widget.showEditsPreview(this._session.hunkData, this._session.textModel0, this._session.textModelN); } else { this._zone.widget.hideEditsPreview(); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index ae291d9d462..14a780fd04a 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -41,7 +41,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { ExpansionState } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { ExpansionState, HunkData } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { IWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; @@ -54,7 +54,6 @@ import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions import { MenuId } from 'vs/platform/actions/common/actions'; import { editorForeground, inputBackground, editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { Lazy } from 'vs/base/common/lazy'; -import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { ILogService } from 'vs/platform/log/common/log'; @@ -246,7 +245,6 @@ export class InlineChatWidget { @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService, - @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, @ILogService private readonly _logService: ILogService, @ITextModelService private readonly _textModelResolverService: ITextModelService, @IChatAgentService private readonly _chatAgentService: IChatAgentService, @@ -749,36 +747,32 @@ export class InlineChatWidget { // --- preview - async showEditsPreview(textModel0: ITextModel, textModelN: ITextModel) { + showEditsPreview(hunks: HunkData, textModel0: ITextModel, textModelN: ITextModel) { - this._elements.previewDiff.classList.remove('hidden'); - - const diff = await this._editorWorkerService.computeDiff(textModel0.uri, textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000, computeMoves: false }, 'advanced'); - if (!diff || diff.changes.length === 0) { + if (hunks.size === 0) { this.hideEditsPreview(); return; } + this._elements.previewDiff.classList.remove('hidden'); + this._previewDiffEditor.value.setModel({ original: textModel0, modified: textModelN }); // joined ranges - let originalLineRange = diff.changes[0].original; - let modifiedLineRange = diff.changes[0].modified; - for (let i = 1; i < diff.changes.length; i++) { - originalLineRange = originalLineRange.join(diff.changes[i].original); - modifiedLineRange = modifiedLineRange.join(diff.changes[i].modified); + let originalLineRange: LineRange | undefined; + let modifiedLineRange: LineRange | undefined; + for (const item of hunks.getInfo()) { + const [first0] = item.getRanges0(); + const [firstN] = item.getRangesN(); + + originalLineRange = !originalLineRange ? LineRange.fromRangeInclusive(first0) : originalLineRange.join(LineRange.fromRangeInclusive(first0)); + modifiedLineRange = !modifiedLineRange ? LineRange.fromRangeInclusive(firstN) : modifiedLineRange.join(LineRange.fromRangeInclusive(firstN)); } - // apply extra padding - const pad = 3; - const newStartLine = Math.max(1, originalLineRange.startLineNumber - pad); - modifiedLineRange = new LineRange(newStartLine, modifiedLineRange.endLineNumberExclusive); - originalLineRange = new LineRange(newStartLine, originalLineRange.endLineNumberExclusive); - - const newEndLineModified = Math.min(modifiedLineRange.endLineNumberExclusive + pad, textModelN.getLineCount()); - modifiedLineRange = new LineRange(modifiedLineRange.startLineNumber, newEndLineModified); - const newEndLineOriginal = Math.min(originalLineRange.endLineNumberExclusive + pad, textModel0.getLineCount()); - originalLineRange = new LineRange(originalLineRange.startLineNumber, newEndLineOriginal); + if (!originalLineRange || !modifiedLineRange) { + this.hideEditsPreview(); + return; + } const hiddenOriginal = invertLineRange(originalLineRange, textModel0); const hiddenModified = invertLineRange(modifiedLineRange, textModelN); diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 1b5712c36e2..bf8c26d3a7d 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -21,6 +21,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { diffInserted, diffRemoved, editorHoverHighlight, editorWidgetBackground, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { Extensions as ExtensionsMigration, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { URI } from 'vs/base/common/uri'; export interface IInlineChatSlashCommand { command: string; @@ -45,6 +46,7 @@ export interface IInlineChatRequest { attempt: number; requestId: string; live: boolean; + previewDocument: URI; withIntentDetection: boolean; } diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 8efc9619cc9..cb1260e7a0f 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -36,12 +36,13 @@ import { IInlineChatSavingService } from '../../browser/inlineChatSavingService' import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionServiceImpl'; import { IInlineChatSessionService } from '../../browser/inlineChatSessionService'; -import { CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatService, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatRequest, IInlineChatService, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { TestWorkerService } from './testWorkerService'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; +import { Schemas } from 'vs/base/common/network'; suite('InteractiveChatController', function () { class TestController extends InlineChatController { @@ -68,7 +69,7 @@ suite('InteractiveChatController', function () { setTimeout(() => { d.dispose(); - reject(`timeout, \nWANTED ${states.join('>')}, \nGOT ${actual.join('>')}`); + reject(new Error(`timeout, \nEXPECTED: ${states.join('>')}, \nACTUAL : ${actual.join('>')}`)); }, 1000); }); } @@ -105,6 +106,7 @@ suite('InteractiveChatController', function () { configurationService = new TestConfigurationService(); configurationService.setUserConfiguration('chat', { editor: { fontSize: 14, fontFamily: 'default' } }); + configurationService.setUserConfiguration('inlineChat', { mode: 'livePreview' }); configurationService.setUserConfiguration('editor', {}); const serviceCollection = new ServiceCollection( @@ -477,4 +479,49 @@ suite('InteractiveChatController', function () { }); + test('context has correct preview document', async function () { + + const requests: IInlineChatRequest[] = []; + + store.add(inlineChatService.addProvider({ + debugName: 'Unit Test', + label: 'Unit Test', + prepareInlineChatSession() { + return { + id: Math.random() + }; + }, + provideResponse(_session, request) { + requests.push(request); + return undefined; + } + })); + + async function makeRequest() { + const p = ctrl.waitFor(TestController.INIT_SEQUENCE_AUTO_SEND); + const r = ctrl.run({ message: 'Hello', autoSend: true }); + await p; + await ctrl.cancelSession(); + await r; + } + + // manual edits -> finish + ctrl = instaService.createInstance(TestController, editor); + + configurationService.setUserConfiguration('inlineChat', { mode: EditMode.Live }); + await makeRequest(); + + configurationService.setUserConfiguration('inlineChat', { mode: EditMode.LivePreview }); + await makeRequest(); + + configurationService.setUserConfiguration('inlineChat', { mode: EditMode.Preview }); + await makeRequest(); + + assert.strictEqual(requests.length, 3); + + assert.strictEqual(requests[0].previewDocument.toString(), model.uri.toString()); // live + assert.strictEqual(requests[1].previewDocument.toString(), model.uri.toString()); // live preview + assert.strictEqual(requests[2].previewDocument.scheme, Schemas.vscode); // preview + assert.strictEqual(requests[2].previewDocument.authority, 'inline-chat'); + }); }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts index d14a84bed36..829d4d0c4a5 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts @@ -307,6 +307,7 @@ export class NotebookCellChatController extends Disposable { selection: { selectionStartLineNumber: 1, selectionStartColumn: 1, positionLineNumber: 1, positionColumn: 1 }, wholeRange: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, live: true, + previewDocument: editor.getModel().uri, withIntentDetection: true, // TODO: don't hard code but allow in corresponding UI to run without intent detection? }; diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index e233f52fa5c..3b3e6e53c31 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -33,6 +33,7 @@ declare module 'vscode' { wholeRange: Range; attempt: number; live: boolean; + previewDocument: TextDocument | undefined; withIntentDetection: boolean; } From 5abe782c374e87e15f1c060452500ac6356eb9b2 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 26 Jan 2024 16:09:47 +0100 Subject: [PATCH 0643/1897] log memory usage after each mangling step https://github.com/microsoft/vscode/issues/202720#issuecomment-1912208685 --- build/lib/mangle/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/lib/mangle/index.ts b/build/lib/mangle/index.ts index 035653c6f5b..da5fc78b1c0 100644 --- a/build/lib/mangle/index.ts +++ b/build/lib/mangle/index.ts @@ -721,7 +721,7 @@ export class Mangler { result.set(item.fileName, { out: newFullText, sourceMap: generator?.toString() }); } - this.log(`Done: ${savedBytes / 1000}kb saved`); + this.log(`Done: ${savedBytes / 1000}kb saved, memory-usage: ${JSON.stringify(process.memoryUsage())}`); return result; } } From fa558765b1cc3af3981fafc8e8d185ce3cace263 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 26 Jan 2024 16:19:08 +0100 Subject: [PATCH 0644/1897] Joh/spatial-pig (#203535) * more `ensureNoDisposablesAreLeakedInTestSuite` * adopt `ensureNoDisposablesAreLeakedInTestSuite` for api command tests --- .eslintrc.json | 3 - .../test/browser/referencesModel.test.ts | 3 + .../test/browser/extHostApiCommands.test.ts | 175 ++++++++++-------- .../api/test/browser/extHostWorkspace.test.ts | 3 + 4 files changed, 103 insertions(+), 81 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 4bb1fc53e36..5ce74c4cc10 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -153,7 +153,6 @@ "src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts", "src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts", "src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts", - "src/vs/editor/contrib/gotoSymbol/test/browser/referencesModel.test.ts", "src/vs/editor/test/common/services/languageService.test.ts", "src/vs/editor/test/node/classification/typescript.test.ts", "src/vs/editor/test/node/diffing/defaultLinesDiffComputer.test.ts", @@ -172,9 +171,7 @@ "src/vs/platform/telemetry/test/browser/1dsAppender.test.ts", "src/vs/platform/workspace/test/common/workspace.test.ts", "src/vs/platform/workspaces/test/electron-main/workspaces.test.ts", - "src/vs/workbench/api/test/browser/extHostWorkspace.test.ts", "src/vs/workbench/api/test/browser/mainThreadConfiguration.test.ts", - "src/vs/workbench/api/test/browser/mainThreadDocuments.test.ts", "src/vs/workbench/api/test/common/extHostExtensionActivator.test.ts", "src/vs/workbench/api/test/node/extHostTunnelService.test.ts", "src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts", diff --git a/src/vs/editor/contrib/gotoSymbol/test/browser/referencesModel.test.ts b/src/vs/editor/contrib/gotoSymbol/test/browser/referencesModel.test.ts index 55291bf4d13..d1195449779 100644 --- a/src/vs/editor/contrib/gotoSymbol/test/browser/referencesModel.test.ts +++ b/src/vs/editor/contrib/gotoSymbol/test/browser/referencesModel.test.ts @@ -5,12 +5,15 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ReferencesModel } from 'vs/editor/contrib/gotoSymbol/browser/referencesModel'; suite('references', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + test('nearestReference', () => { const model = new ReferencesModel([{ uri: URI.file('/out/obj/can'), diff --git a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts index 2e4e2560bfc..25bcbd3f930 100644 --- a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts +++ b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts @@ -3,6 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/editor/contrib/codeAction/browser/codeAction'; +import 'vs/editor/contrib/codelens/browser/codelens'; +import 'vs/editor/contrib/colorPicker/browser/color'; +import 'vs/editor/contrib/format/browser/format'; +import 'vs/editor/contrib/gotoSymbol/browser/goToCommands'; +import 'vs/editor/contrib/documentSymbols/browser/documentSymbols'; +import 'vs/editor/contrib/hover/browser/getHover'; +import 'vs/editor/contrib/links/browser/getLinks'; +import 'vs/editor/contrib/parameterHints/browser/provideSignatureHelp'; +import 'vs/editor/contrib/smartSelect/browser/smartSelect'; +import 'vs/editor/contrib/suggest/browser/suggest'; +import 'vs/editor/contrib/rename/browser/rename'; +import 'vs/editor/contrib/inlayHints/browser/inlayHintsController'; + import * as assert from 'assert'; import { setUnexpectedErrorHandler, errorHandler } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; @@ -39,20 +53,6 @@ import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSyste import { URITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { IOutlineModelService, OutlineModelService } from 'vs/editor/contrib/documentSymbols/browser/outlineModel'; import { ILanguageFeatureDebounceService, LanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; - -import 'vs/editor/contrib/codeAction/browser/codeAction'; -import 'vs/editor/contrib/codelens/browser/codelens'; -import 'vs/editor/contrib/colorPicker/browser/color'; -import 'vs/editor/contrib/format/browser/format'; -import 'vs/editor/contrib/gotoSymbol/browser/goToCommands'; -import 'vs/editor/contrib/documentSymbols/browser/documentSymbols'; -import 'vs/editor/contrib/hover/browser/getHover'; -import 'vs/editor/contrib/links/browser/getLinks'; -import 'vs/editor/contrib/parameterHints/browser/provideSignatureHelp'; -import 'vs/editor/contrib/smartSelect/browser/smartSelect'; -import 'vs/editor/contrib/suggest/browser/suggest'; -import 'vs/editor/contrib/rename/browser/rename'; -import 'vs/editor/contrib/inlayHints/browser/inlayHintsController'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService'; import { assertType } from 'vs/base/common/types'; @@ -62,6 +62,9 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; +import { timeout } from 'vs/base/common/async'; function assertRejects(fn: () => Promise, message: string = 'Expected rejection') { return fn().then(() => assert.ok(false, message), _err => assert.ok(true)); @@ -190,6 +193,9 @@ suite('ExtHostLanguageFeatureCommands', function () { mainThread = rpcProtocol.set(MainContext.MainThreadLanguageFeatures, insta.createInstance(MainThreadLanguageFeatures, rpcProtocol)); + // forcefully create the outline service so that `ensureNoDisposablesAreLeakedInTestSuite` doesn't bark + insta.get(IOutlineModelService); + return rpcProtocol.sync(); }); @@ -207,10 +213,21 @@ suite('ExtHostLanguageFeatureCommands', function () { return rpcProtocol.sync(); }); - // ensureNoDisposablesAreLeakedInTestSuite(); + ensureNoDisposablesAreLeakedInTestSuite(); // --- workspace symbols + function testApiCmd(name: string, fn: () => Promise) { + test(name, async function () { + await runWithFakedTimers({}, async () => { + await fn(); + await timeout(10000); // API commands for things that allow commands dispose their result delay. This is to be nice + // because otherwise properties like command are disposed too early + }); + }); + + } + test('WorkspaceSymbols, invalid arguments', function () { const promises = [ assertRejects(() => commands.executeCommand('vscode.executeWorkspaceSymbolProvider')), @@ -767,7 +784,7 @@ suite('ExtHostLanguageFeatureCommands', function () { // --- suggest - test('triggerCharacter is null when completion provider is called programmatically #159914', async function () { + testApiCmd('triggerCharacter is null when completion provider is called programmatically #159914', async function () { let actualContext: vscode.CompletionContext | undefined; @@ -784,9 +801,11 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.ok(actualContext); assert.deepStrictEqual(actualContext, { triggerKind: types.CompletionTriggerKind.Invoke, triggerCharacter: undefined }); + }); - test('Suggest, back and forth', function () { + testApiCmd('Suggest, back and forth', async function () { + disposables.push(extHost.registerCompletionItemProvider(nullExtensionDescription, defaultSelector, { provideCompletionItems(): any { const a = new types.CompletionItem('item1'); @@ -804,49 +823,45 @@ suite('ExtHostLanguageFeatureCommands', function () { } }, [])); - return rpcProtocol.sync().then(() => { - return commands.executeCommand('vscode.executeCompletionItemProvider', model.uri, new types.Position(0, 4)).then(list => { + await rpcProtocol.sync(); - assert.ok(list instanceof types.CompletionList); - const values = list.items; - assert.ok(Array.isArray(values)); - assert.strictEqual(values.length, 4); - const [first, second, third, fourth] = values; - assert.strictEqual(first.label, 'item1'); - assert.strictEqual(first.textEdit, undefined);// no text edit, default ranges - assert.ok(!types.Range.isRange(first.range)); - assert.strictEqual((first.documentation).value, 'hello_md_string'); + const list = await commands.executeCommand('vscode.executeCompletionItemProvider', model.uri, new types.Position(0, 4)); + assert.ok(list instanceof types.CompletionList); + const values = list.items; + assert.ok(Array.isArray(values)); + assert.strictEqual(values.length, 4); + const [first, second, third, fourth] = values; + assert.strictEqual(first.label, 'item1'); + assert.strictEqual(first.textEdit, undefined); // no text edit, default ranges + assert.ok(!types.Range.isRange(first.range)); + assert.strictEqual((first.documentation).value, 'hello_md_string'); + assert.strictEqual(second.label, 'item2'); + assert.strictEqual(second.textEdit!.newText, 'foo'); + assert.strictEqual(second.textEdit!.range.start.line, 0); + assert.strictEqual(second.textEdit!.range.start.character, 4); + assert.strictEqual(second.textEdit!.range.end.line, 0); + assert.strictEqual(second.textEdit!.range.end.character, 8); + assert.strictEqual(third.label, 'item3'); + assert.strictEqual(third.textEdit!.newText, 'foobar'); + assert.strictEqual(third.textEdit!.range.start.line, 0); + assert.strictEqual(third.textEdit!.range.start.character, 1); + assert.strictEqual(third.textEdit!.range.end.line, 0); + assert.strictEqual(third.textEdit!.range.end.character, 6); + assert.strictEqual(fourth.label, 'item4'); + assert.strictEqual(fourth.textEdit, undefined); + const range: any = fourth.range!; + assert.ok(types.Range.isRange(range)); + assert.strictEqual(range.start.line, 0); + assert.strictEqual(range.start.character, 1); + assert.strictEqual(range.end.line, 0); + assert.strictEqual(range.end.character, 4); + assert.ok(fourth.insertText instanceof types.SnippetString); + assert.strictEqual((fourth.insertText).value, 'foo$0bar'); - assert.strictEqual(second.label, 'item2'); - assert.strictEqual(second.textEdit!.newText, 'foo'); - assert.strictEqual(second.textEdit!.range.start.line, 0); - assert.strictEqual(second.textEdit!.range.start.character, 4); - assert.strictEqual(second.textEdit!.range.end.line, 0); - assert.strictEqual(second.textEdit!.range.end.character, 8); - - assert.strictEqual(third.label, 'item3'); - assert.strictEqual(third.textEdit!.newText, 'foobar'); - assert.strictEqual(third.textEdit!.range.start.line, 0); - assert.strictEqual(third.textEdit!.range.start.character, 1); - assert.strictEqual(third.textEdit!.range.end.line, 0); - assert.strictEqual(third.textEdit!.range.end.character, 6); - - assert.strictEqual(fourth.label, 'item4'); - assert.strictEqual(fourth.textEdit, undefined); - - const range: any = fourth.range!; - assert.ok(types.Range.isRange(range)); - assert.strictEqual(range.start.line, 0); - assert.strictEqual(range.start.character, 1); - assert.strictEqual(range.end.line, 0); - assert.strictEqual(range.end.character, 4); - assert.ok(fourth.insertText instanceof types.SnippetString); - assert.strictEqual((fourth.insertText).value, 'foo$0bar'); - }); - }); }); - test('Suggest, return CompletionList !array', function () { + testApiCmd('Suggest, return CompletionList !array', async function () { + disposables.push(extHost.registerCompletionItemProvider(nullExtensionDescription, defaultSelector, { provideCompletionItems(): any { const a = new types.CompletionItem('item1'); @@ -855,15 +870,16 @@ suite('ExtHostLanguageFeatureCommands', function () { } }, [])); - return rpcProtocol.sync().then(() => { - return commands.executeCommand('vscode.executeCompletionItemProvider', model.uri, new types.Position(0, 4)).then(list => { - assert.ok(list instanceof types.CompletionList); - assert.strictEqual(list.isIncomplete, true); - }); - }); + await rpcProtocol.sync(); + + const list = await commands.executeCommand('vscode.executeCompletionItemProvider', model.uri, new types.Position(0, 4)); + + assert.ok(list instanceof types.CompletionList); + assert.strictEqual(list.isIncomplete, true); }); - test('Suggest, resolve completion items', async function () { + testApiCmd('Suggest, resolve completion items', async function () { + let resolveCount = 0; @@ -896,7 +912,10 @@ suite('ExtHostLanguageFeatureCommands', function () { }); - test('"vscode.executeCompletionItemProvider" doesnot return a preselect field #53749', async function () { + testApiCmd('"vscode.executeCompletionItemProvider" doesnot return a preselect field #53749', async function () { + + + disposables.push(extHost.registerCompletionItemProvider(nullExtensionDescription, defaultSelector, { provideCompletionItems(): any { const a = new types.CompletionItem('item1'); @@ -928,7 +947,7 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.strictEqual(d.preselect, undefined); }); - test('executeCompletionItemProvider doesn\'t capture commitCharacters #58228', async function () { + testApiCmd('executeCompletionItemProvider doesn\'t capture commitCharacters #58228', async function () { disposables.push(extHost.registerCompletionItemProvider(nullExtensionDescription, defaultSelector, { provideCompletionItems(): any { const a = new types.CompletionItem('item1'); @@ -955,7 +974,7 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.strictEqual(b.commitCharacters, undefined); }); - test('vscode.executeCompletionItemProvider returns the wrong CompletionItemKinds in insiders #95715', async function () { + testApiCmd('vscode.executeCompletionItemProvider returns the wrong CompletionItemKinds in insiders #95715', async function () { disposables.push(extHost.registerCompletionItemProvider(nullExtensionDescription, defaultSelector, { provideCompletionItems(): any { return [ @@ -1013,7 +1032,7 @@ suite('ExtHostLanguageFeatureCommands', function () { // --- quickfix - test('QuickFix, back and forth', function () { + testApiCmd('QuickFix, back and forth', function () { disposables.push(extHost.registerCodeActionProvider(nullExtensionDescription, defaultSelector, { provideCodeActions(): vscode.Command[] { return [{ command: 'testing', title: 'Title', arguments: [1, 2, true] }]; @@ -1031,7 +1050,7 @@ suite('ExtHostLanguageFeatureCommands', function () { }); }); - test('vscode.executeCodeActionProvider results seem to be missing their `command` property #45124', function () { + testApiCmd('vscode.executeCodeActionProvider results seem to be missing their `command` property #45124', function () { disposables.push(extHost.registerCodeActionProvider(nullExtensionDescription, defaultSelector, { provideCodeActions(document, range): vscode.CodeAction[] { return [{ @@ -1060,7 +1079,7 @@ suite('ExtHostLanguageFeatureCommands', function () { }); }); - test('vscode.executeCodeActionProvider passes Range to provider although Selection is passed in #77997', function () { + testApiCmd('vscode.executeCodeActionProvider passes Range to provider although Selection is passed in #77997', function () { disposables.push(extHost.registerCodeActionProvider(nullExtensionDescription, defaultSelector, { provideCodeActions(document, rangeOrSelection): vscode.CodeAction[] { return [{ @@ -1088,7 +1107,7 @@ suite('ExtHostLanguageFeatureCommands', function () { }); }); - test('vscode.executeCodeActionProvider results seem to be missing their `isPreferred` property #78098', function () { + testApiCmd('vscode.executeCodeActionProvider results seem to be missing their `isPreferred` property #78098', function () { disposables.push(extHost.registerCodeActionProvider(nullExtensionDescription, defaultSelector, { provideCodeActions(document, rangeOrSelection): vscode.CodeAction[] { return [{ @@ -1115,7 +1134,7 @@ suite('ExtHostLanguageFeatureCommands', function () { }); }); - test('resolving code action', async function () { + testApiCmd('resolving code action', async function () { let didCallResolve = 0; class MyAction extends types.CodeAction { } @@ -1149,7 +1168,7 @@ suite('ExtHostLanguageFeatureCommands', function () { // --- code lens - test('CodeLens, back and forth', function () { + testApiCmd('CodeLens, back and forth', function () { const complexArg = { foo() { }, @@ -1177,7 +1196,7 @@ suite('ExtHostLanguageFeatureCommands', function () { }); }); - test('CodeLens, resolve', async function () { + testApiCmd('CodeLens, resolve', async function () { let resolveCount = 0; @@ -1211,7 +1230,7 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.strictEqual(resolveCount, 0); }); - test('Links, back and forth', function () { + testApiCmd('Links, back and forth', function () { disposables.push(extHost.registerDocumentLinkProvider(nullExtensionDescription, defaultSelector, { provideDocumentLinks(): any { @@ -1233,7 +1252,7 @@ suite('ExtHostLanguageFeatureCommands', function () { }); }); - test('What\'s the condition for DocumentLink target to be undefined? #106308', async function () { + testApiCmd('What\'s the condition for DocumentLink target to be undefined? #106308', async function () { disposables.push(extHost.registerDocumentLinkProvider(nullExtensionDescription, defaultSelector, { provideDocumentLinks(): any { return [new types.DocumentLink(new types.Range(0, 0, 0, 20), undefined)]; @@ -1325,7 +1344,7 @@ suite('ExtHostLanguageFeatureCommands', function () { // --- inline hints - test('Inlay Hints, back and forth', async function () { + testApiCmd('Inlay Hints, back and forth', async function () { disposables.push(extHost.registerInlayHintsProvider(nullExtensionDescription, defaultSelector, { provideInlayHints() { return [new types.InlayHint(new types.Position(0, 1), 'Foo')]; @@ -1343,7 +1362,7 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.strictEqual(first.position.character, 1); }); - test('Inline Hints, merge', async function () { + testApiCmd('Inline Hints, merge', async function () { disposables.push(extHost.registerInlayHintsProvider(nullExtensionDescription, defaultSelector, { provideInlayHints() { const part = new types.InlayHintLabelPart('Bar'); @@ -1391,7 +1410,7 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.strictEqual(label.command?.title, 'part'); }); - test('Inline Hints, bad provider', async function () { + testApiCmd('Inline Hints, bad provider', async function () { disposables.push(extHost.registerInlayHintsProvider(nullExtensionDescription, defaultSelector, { provideInlayHints() { return [new types.InlayHint(new types.Position(0, 1), 'Foo')]; diff --git a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts index 9d980e14502..0dc3b5ab6ed 100644 --- a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts +++ b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts @@ -25,6 +25,7 @@ import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSyste import { FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { nullExtensionDescription as extensionDescriptor } from 'vs/workbench/services/extensions/common/extensions'; import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; function createExtHostWorkspace(mainContext: IMainContext, data: IWorkspaceData, logService: ILogService): ExtHostWorkspace { const result = new ExtHostWorkspace( @@ -40,6 +41,8 @@ function createExtHostWorkspace(mainContext: IMainContext, data: IWorkspaceData, suite('ExtHostWorkspace', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + function assertAsRelativePath(workspace: ExtHostWorkspace, input: string, expected: string, includeWorkspace?: boolean) { const actual = workspace.getRelativePath(input, includeWorkspace); assert.strictEqual(actual, expected); From 4e231448f8605486c50a6a12d975c5215e76214e Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 26 Jan 2024 16:20:23 +0100 Subject: [PATCH 0645/1897] those JS files... --- build/lib/mangle/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/lib/mangle/index.js b/build/lib/mangle/index.js index 6a85a1a82f0..97935a8e8ec 100644 --- a/build/lib/mangle/index.js +++ b/build/lib/mangle/index.js @@ -614,7 +614,7 @@ class Mangler { } result.set(item.fileName, { out: newFullText, sourceMap: generator?.toString() }); } - this.log(`Done: ${savedBytes / 1000}kb saved`); + this.log(`Done: ${savedBytes / 1000}kb saved, memory-usage: ${JSON.stringify(process.memoryUsage())}`); return result; } } From a6f9e08a429256186880dd573ae9fdcc85fad727 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 26 Jan 2024 16:33:54 +0100 Subject: [PATCH 0646/1897] make language service a local var, dispose when new file contents are computed, terminate worker pool --- build/lib/mangle/index.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/build/lib/mangle/index.ts b/build/lib/mangle/index.ts index da5fc78b1c0..ec023535975 100644 --- a/build/lib/mangle/index.ts +++ b/build/lib/mangle/index.ts @@ -399,7 +399,6 @@ export class Mangler { private readonly allClassDataByKey = new Map(); private readonly allExportedSymbols = new Set(); - private readonly service: ts.LanguageService; private readonly renameWorkerPool: workerpool.WorkerPool; constructor( @@ -407,7 +406,6 @@ export class Mangler { private readonly log: typeof console.log = () => { }, private readonly config: { readonly manglePrivateFields: boolean; readonly mangleExports: boolean }, ) { - this.service = ts.createLanguageService(new StaticLanguageServiceHost(projectPath)); this.renameWorkerPool = workerpool.pool(path.join(__dirname, 'renameWorker.js'), { maxWorkers: 1, @@ -417,6 +415,8 @@ export class Mangler { async computeNewFileContents(strictImplicitPublicHandling?: Set): Promise> { + const service = ts.createLanguageService(new StaticLanguageServiceHost(this.projectPath)); + // STEP: // - Find all classes and their field info. // - Find exported symbols. @@ -471,14 +471,14 @@ export class Mangler { return; } - this.allExportedSymbols.add(new DeclarationData(node.getSourceFile().fileName, node, this.service, fileIdents)); + this.allExportedSymbols.add(new DeclarationData(node.getSourceFile().fileName, node, service, fileIdents)); } } ts.forEachChild(node, visit); }; - for (const file of this.service.getProgram()!.getSourceFiles()) { + for (const file of service.getProgram()!.getSourceFiles()) { if (!file.isDeclarationFile) { ts.forEachChild(file, visit); } @@ -495,7 +495,7 @@ export class Mangler { return; } - const info = this.service.getDefinitionAtPosition(data.fileName, extendsClause.types[0].expression.getEnd()); + const info = service.getDefinitionAtPosition(data.fileName, extendsClause.types[0].expression.getEnd()); if (!info || info.length === 0) { // throw new Error('SUPER type not found'); return; @@ -641,9 +641,9 @@ export class Mangler { const result = new Map(); let savedBytes = 0; - for (const item of this.service.getProgram()!.getSourceFiles()) { + for (const item of service.getProgram()!.getSourceFiles()) { - const { mapRoot, sourceRoot } = this.service.getProgram()!.getCompilerOptions(); + const { mapRoot, sourceRoot } = service.getProgram()!.getCompilerOptions(); const projectDir = path.dirname(this.projectPath); const sourceMapRoot = mapRoot ?? pathToFileURL(sourceRoot ?? projectDir).toString(); @@ -721,6 +721,8 @@ export class Mangler { result.set(item.fileName, { out: newFullText, sourceMap: generator?.toString() }); } + service.dispose(); + this.renameWorkerPool.terminate(); this.log(`Done: ${savedBytes / 1000}kb saved, memory-usage: ${JSON.stringify(process.memoryUsage())}`); return result; } From b7d1c336ee0f02e3125dd92a7e7a2c866ed4d39a Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 26 Jan 2024 16:39:19 +0100 Subject: [PATCH 0647/1897] grrrrr --- build/lib/mangle/index.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/build/lib/mangle/index.js b/build/lib/mangle/index.js index 97935a8e8ec..14137c0db31 100644 --- a/build/lib/mangle/index.js +++ b/build/lib/mangle/index.js @@ -346,19 +346,18 @@ class Mangler { config; allClassDataByKey = new Map(); allExportedSymbols = new Set(); - service; renameWorkerPool; constructor(projectPath, log = () => { }, config) { this.projectPath = projectPath; this.log = log; this.config = config; - this.service = ts.createLanguageService(new staticLanguageServiceHost_1.StaticLanguageServiceHost(projectPath)); this.renameWorkerPool = workerpool.pool(path.join(__dirname, 'renameWorker.js'), { maxWorkers: 1, minWorkers: 'max' }); } async computeNewFileContents(strictImplicitPublicHandling) { + const service = ts.createLanguageService(new staticLanguageServiceHost_1.StaticLanguageServiceHost(this.projectPath)); // STEP: // - Find all classes and their field info. // - Find exported symbols. @@ -405,12 +404,12 @@ class Mangler { if (isInAmbientContext(node)) { return; } - this.allExportedSymbols.add(new DeclarationData(node.getSourceFile().fileName, node, this.service, fileIdents)); + this.allExportedSymbols.add(new DeclarationData(node.getSourceFile().fileName, node, service, fileIdents)); } } ts.forEachChild(node, visit); }; - for (const file of this.service.getProgram().getSourceFiles()) { + for (const file of service.getProgram().getSourceFiles()) { if (!file.isDeclarationFile) { ts.forEachChild(file, visit); } @@ -423,7 +422,7 @@ class Mangler { // no EXTENDS-clause return; } - const info = this.service.getDefinitionAtPosition(data.fileName, extendsClause.types[0].expression.getEnd()); + const info = service.getDefinitionAtPosition(data.fileName, extendsClause.types[0].expression.getEnd()); if (!info || info.length === 0) { // throw new Error('SUPER type not found'); return; @@ -545,8 +544,8 @@ class Mangler { // STEP: apply all rename edits (per file) const result = new Map(); let savedBytes = 0; - for (const item of this.service.getProgram().getSourceFiles()) { - const { mapRoot, sourceRoot } = this.service.getProgram().getCompilerOptions(); + for (const item of service.getProgram().getSourceFiles()) { + const { mapRoot, sourceRoot } = service.getProgram().getCompilerOptions(); const projectDir = path.dirname(this.projectPath); const sourceMapRoot = mapRoot ?? (0, url_1.pathToFileURL)(sourceRoot ?? projectDir).toString(); // source maps @@ -614,6 +613,8 @@ class Mangler { } result.set(item.fileName, { out: newFullText, sourceMap: generator?.toString() }); } + service.dispose(); + this.renameWorkerPool.terminate(); this.log(`Done: ${savedBytes / 1000}kb saved, memory-usage: ${JSON.stringify(process.memoryUsage())}`); return result; } From 7b873a31170e4bda2faea577066d79c638d3be79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 08:49:07 -0700 Subject: [PATCH 0648/1897] Bump actions/setup-python from 4 to 5 (#200546) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .github/workflows/deep-classifier-runner.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee204799cf6..097bdcf8c2c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: with: node-version-file: .nvmrc - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: "2.x" diff --git a/.github/workflows/deep-classifier-runner.yml b/.github/workflows/deep-classifier-runner.yml index 5ee7d048945..576bfa12fc3 100644 --- a/.github/workflows/deep-classifier-runner.yml +++ b/.github/workflows/deep-classifier-runner.yml @@ -33,7 +33,7 @@ jobs: token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} - name: Set up Python 3.7 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.7 - name: Install dependencies From 01827d8f2009f738cf1427ba3a942f42b1c2f938 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 26 Jan 2024 17:13:18 +0100 Subject: [PATCH 0649/1897] aux window - restore `role: application` (#203540) --- .../services/auxiliaryWindow/browser/auxiliaryWindowService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts index 57c904416e0..6480665c278 100644 --- a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts @@ -417,6 +417,7 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili // Create workbench container and apply classes const container = document.createElement('div'); + container.setAttribute('role', 'application'); auxiliaryWindow.document.body.append(container); // Track attributes From 20fb4c4bd977fae94109d3aec1b6159e7b5bcded Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 26 Jan 2024 17:26:31 +0100 Subject: [PATCH 0650/1897] using directly the lines, not the split lines --- .../contrib/inlineCompletions/browser/ghostText.ts | 11 ++++++++--- .../browser/inlineCompletionsModel.ts | 8 ++++---- .../inlineCompletions/browser/singleTextEdit.ts | 8 +++----- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts b/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts index abd73b8f0de..68f913238a7 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { splitLines } from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; import { ColumnRange, applyEdits } from 'vs/editor/contrib/inlineCompletions/browser/utils'; @@ -61,7 +62,7 @@ export class GhostText { export class GhostTextPart { constructor( readonly column: number, - readonly lines: readonly string[], + readonly text: string, /** * Indicates if this part is a preview of an inline suggestion when a suggestion is previewed. */ @@ -69,6 +70,8 @@ export class GhostTextPart { ) { } + readonly lines = splitLines(this.text);; + equals(other: GhostTextPart): boolean { return this.column === other.column && this.lines.length === other.lines.length && @@ -80,7 +83,7 @@ export class GhostTextReplacement { public readonly parts: ReadonlyArray = [ new GhostTextPart( this.columnRange.endColumnExclusive, - this.newLines, + this.text, false ), ]; @@ -88,10 +91,12 @@ export class GhostTextReplacement { constructor( readonly lineNumber: number, readonly columnRange: ColumnRange, - readonly newLines: readonly string[], + readonly text: string, public readonly additionalReservedLineCount: number = 0, ) { } + readonly newLines = splitLines(this.text); + renderForScreenReader(_lineText: string): string { return this.newLines.join('\n'); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index c79372277f0..695a140bc40 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -391,15 +391,15 @@ export class InlineCompletionsModel extends Disposable { const firstPart = ghostText.parts[0]; const position = new Position(ghostText.lineNumber, firstPart.column); - const line = firstPart.lines.join('\n'); - const acceptUntilIndexExclusive = getAcceptUntilIndex(position, line); + const text = firstPart.text; + const acceptUntilIndexExclusive = getAcceptUntilIndex(position, text); - if (acceptUntilIndexExclusive === line.length && ghostText.parts.length === 1) { + if (acceptUntilIndexExclusive === text.length && ghostText.parts.length === 1) { this.accept(editor); return; } - const partialText = line.substring(0, acceptUntilIndexExclusive); + const partialText = text.substring(0, acceptUntilIndexExclusive); // Executing the edit might free the completion, so we have to hold a reference on it. completion.source.addRef(); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts index 8d74bd7bdcd..c958fcdb605 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDiffChange, LcsDiff } from 'vs/base/common/diff/diff'; -import { commonPrefixLength, getLeadingWhitespace, splitLines } from 'vs/base/common/strings'; +import { commonPrefixLength, getLeadingWhitespace } from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; @@ -134,12 +134,10 @@ export class SingleTextEdit { const italicText = edit.text.substring(nonPreviewTextEnd, Math.max(c.modifiedStart, modifiedEnd)); if (nonPreviewText.length > 0) { - const lines = splitLines(nonPreviewText); - parts.push(new GhostTextPart(insertColumn, lines, false)); + parts.push(new GhostTextPart(insertColumn, nonPreviewText, false)); } if (italicText.length > 0) { - const lines = splitLines(italicText); - parts.push(new GhostTextPart(insertColumn, lines, true)); + parts.push(new GhostTextPart(insertColumn, italicText, true)); } } From d21ba4a403b27d370a36082aeccbc5a69125a87e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 26 Jan 2024 17:46:01 +0100 Subject: [PATCH 0651/1897] use `savedFrom` to participate in the right resource when doing save-as or when saving an untitled file (#203543) fixes https://github.com/microsoft/vscode-copilot/issues/3690 --- .../inlineChat/browser/inlineChatSavingServiceImpl.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts index 23f8f2cf917..a86426304a3 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts @@ -105,13 +105,13 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService { const queue = new Queue(); const d1 = this._textFileService.files.addSaveParticipant({ - participate: (model, context, progress, token) => { - return queue.queue(() => this._participate(model.textEditorModel?.uri, context.reason, progress, token)); + participate: (model, ctx, progress, token) => { + return queue.queue(() => this._participate(ctx.savedFrom ?? model.textEditorModel?.uri, ctx.reason, progress, token)); } }); const d2 = this._workingCopyFileService.addSaveParticipant({ - participate: (workingCopy, env, progress, token) => { - return queue.queue(() => this._participate(workingCopy.resource, env.reason, progress, token)); + participate: (workingCopy, ctx, progress, token) => { + return queue.queue(() => this._participate(ctx.savedFrom ?? workingCopy.resource, ctx.reason, progress, token)); } }); this._saveParticipant.value = combinedDisposable(d1, d2, queue); From 6be4d6cc529a5d5112d02d730808e861a3c5917f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 26 Jan 2024 18:03:09 +0100 Subject: [PATCH 0652/1897] Don't let actual decorations grow, but still grow the "ambient" background decoration (#203546) fixes https://github.com/microsoft/vscode-copilot/issues/3713 --- .../contrib/inlineChat/browser/inlineChatStrategies.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index fdc930567c1..ea6a747f627 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -18,7 +18,7 @@ import { LineRange } from 'vs/editor/common/core/lineRange'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; -import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel, IValidEditOperation, OverviewRulerLane } from 'vs/editor/common/model'; +import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel, IValidEditOperation, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewModel'; @@ -419,6 +419,7 @@ export class LiveStrategy extends EditModeStrategy { private readonly _decoInsertedTextRange = ModelDecorationOptions.register({ description: 'inline-chat-inserted-range-linehighlight', className: 'inline-chat-inserted-range', + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, }); private readonly _previewZone: Lazy; From 1b58747581ed45da9838b6d231c098885d25ea48 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 26 Jan 2024 11:22:01 -0800 Subject: [PATCH 0653/1897] simplify the workspace trust status bar entry (#203557) --- .../browser/workspace.contribution.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index dbb1822d053..45b9ee2b5a1 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -222,7 +222,7 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben */ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchContribution { - private readonly entryId = `status.workspaceTrust.${this.workspaceContextService.getWorkspace().id}`; + private readonly entryId = `status.workspaceTrust`; private readonly statusbarEntryAccessor: MutableDisposable; @@ -253,7 +253,7 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon if (this.workspaceTrustEnablementService.isWorkspaceTrustEnabled()) { this.registerListeners(); - this.createStatusbarEntry(); + this.updateStatusbarEntry(this.workspaceTrustManagementService.isWorkspaceTrusted()); // Show modal dialog if (this.hostService.hasFocus) { @@ -555,12 +555,6 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon //#region Statusbar - private createStatusbarEntry(): void { - const entry = this.getStatusbarEntry(this.workspaceTrustManagementService.isWorkspaceTrusted()); - this.statusbarEntryAccessor.value = this.statusbarService.addEntry(entry, this.entryId, StatusbarAlignment.LEFT, 0.99 * Number.MAX_VALUE /* Right of remote indicator */); - this.statusbarService.updateEntryVisibility(this.entryId, false); - } - private getStatusbarEntry(trusted: boolean): IStatusbarEntry { const text = workspaceTrustToString(trusted); @@ -625,8 +619,15 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon } private updateStatusbarEntry(trusted: boolean): void { - this.statusbarEntryAccessor.value?.update(this.getStatusbarEntry(trusted)); - this.statusbarService.updateEntryVisibility(this.entryId, !trusted); + if (trusted && this.statusbarEntryAccessor.value) { + this.statusbarEntryAccessor.clear(); + return; + } + + if (!trusted && !this.statusbarEntryAccessor.value) { + const entry = this.getStatusbarEntry(trusted); + this.statusbarEntryAccessor.value = this.statusbarService.addEntry(entry, this.entryId, StatusbarAlignment.LEFT, 0.99 * Number.MAX_VALUE /* Right of remote indicator */); + } } //#endregion From 5233f3b81e5cd43c779bde9b454c2d3954234e26 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 26 Jan 2024 11:26:08 -0800 Subject: [PATCH 0654/1897] debug: bump js-debug (#203558) --- product.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/product.json b/product.json index 7f445fef7a2..b17eeda9610 100644 --- a/product.json +++ b/product.json @@ -50,8 +50,8 @@ }, { "name": "ms-vscode.js-debug", - "version": "1.86.0", - "sha256": "cdd3187384c953160c3d0ad1e8ebd869cc420930a9993f977f7d739029a02a54", + "version": "1.86.1", + "sha256": "e382de75b63a57d3419bbb110e17551d40d88b4bb0a49452dab2c7278b815e72", "repo": "https://github.com/microsoft/vscode-js-debug", "metadata": { "id": "25629058-ddac-4e17-abba-74678e126c5d", From 394659848a23b02c0bfd86abeb18aa70fced5b21 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 26 Jan 2024 16:39:40 -0300 Subject: [PATCH 0655/1897] Fix #203556 (#203561) --- src/vs/workbench/contrib/debug/browser/debugCommands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 30272d26dcc..e10b4bbd72b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -224,7 +224,7 @@ async function navigateCallStack(debugService: IDebugService, down: boolean) { } if (nextVisibleFrame) { - debugService.focusStackFrame(nextVisibleFrame); + debugService.focusStackFrame(nextVisibleFrame, undefined, undefined, { preserveFocus: false }); } } } From ac56e6eb5a536b661f50dd63fad38351dcf44438 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 26 Jan 2024 21:22:16 +0100 Subject: [PATCH 0656/1897] debt - reduce large `workbench.statusbar.hidden` state (#203564) --- src/vs/workbench/browser/parts/statusbar/statusbarModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarModel.ts b/src/vs/workbench/browser/parts/statusbar/statusbarModel.ts index 98c2fd7a37d..ae4ef6a97e4 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarModel.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarModel.ts @@ -48,7 +48,7 @@ export class StatusbarViewModel extends Disposable { if (hiddenRaw) { try { const hiddenArray: string[] = JSON.parse(hiddenRaw); - this.hidden = new Set(hiddenArray); + this.hidden = new Set(hiddenArray.filter(entry => !entry.startsWith('status.workspaceTrust.'))); // TODO@bpasero remove this migration eventually } catch (error) { // ignore parsing errors } From 9237441cd23f7d92af81106a0e34578f791f1423 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Fri, 26 Jan 2024 12:37:11 -0800 Subject: [PATCH 0657/1897] Add terminal as a startup editor (#203567) --- .../browser/gettingStarted.contribution.ts | 3 ++- .../contrib/welcomeGettingStarted/browser/startupPage.ts | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts index 3d0ae3e607d..a804657f8fd 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts @@ -305,13 +305,14 @@ configurationRegistry.registerConfiguration({ 'workbench.startupEditor': { 'scope': ConfigurationScope.RESOURCE, 'type': 'string', - 'enum': ['none', 'welcomePage', 'readme', 'newUntitledFile', 'welcomePageInEmptyWorkbench'], + 'enum': ['none', 'welcomePage', 'readme', 'newUntitledFile', 'welcomePageInEmptyWorkbench', 'terminal'], 'enumDescriptions': [ localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.none' }, "Start without an editor."), localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePage' }, "Open the Welcome page, with content to aid in getting started with VS Code and extensions."), localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.readme' }, "Open the README when opening a folder that contains one, fallback to 'welcomePage' otherwise. Note: This is only observed as a global configuration, it will be ignored if set in a workspace or folder configuration."), localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.newUntitledFile' }, "Open a new untitled text file (only applies when opening an empty window)."), localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePageInEmptyWorkbench' }, "Open the Welcome page when opening an empty workbench."), + localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.terminal' }, "Open a new terminal in the editor area."), ], 'default': 'welcomePage', 'description': localize('workbench.startupEditor', "Controls which editor is shown at startup, if none are restored from the previous session.") diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/startupPage.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/startupPage.ts index e69262830cf..da1a4f35e0f 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/startupPage.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/startupPage.ts @@ -28,6 +28,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; +import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; export const restoreWalkthroughsConfigurationKey = 'workbench.welcomePage.restorableWalkthroughs'; export type RestoreWalkthroughsConfigurationValue = { folder: string; category?: string; step?: string }; @@ -126,6 +127,8 @@ export class StartupPageContribution implements IWorkbenchContribution { await this.openReadme(); } else if (startupEditorSetting.value === 'welcomePage' || startupEditorSetting.value === 'welcomePageInEmptyWorkbench') { await this.openGettingStarted(); + } else if (startupEditorSetting.value === 'terminal') { + this.commandService.executeCommand(TerminalCommandId.CreateTerminalEditor); } } } @@ -213,5 +216,6 @@ function isStartupPageEnabled(configurationService: IConfigurationService, conte return startupEditor.value === 'welcomePage' || startupEditor.value === 'readme' && (startupEditor.userValue === 'readme' || startupEditor.defaultValue === 'readme') - || (contextService.getWorkbenchState() === WorkbenchState.EMPTY && startupEditor.value === 'welcomePageInEmptyWorkbench'); + || (contextService.getWorkbenchState() === WorkbenchState.EMPTY && startupEditor.value === 'welcomePageInEmptyWorkbench') + || startupEditor.value === 'terminal'; } From c0de87c441a6505c3bd038e9e89a43ab0424bb22 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Fri, 26 Jan 2024 13:07:46 -0800 Subject: [PATCH 0658/1897] Complete walkthrough steps with commands after command execution (#203573) --- .../contrib/welcomeGettingStarted/browser/gettingStarted.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 85a3eebf4c2..8f8e501e677 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -718,7 +718,7 @@ export class GettingStartedPage extends EditorPane { stepElement.classList.add('expanded'); stepElement.setAttribute('aria-expanded', 'true'); this.buildMediaComponent(id); - this.gettingStartedService.progressStep(id); + this.gettingStartedService.progressByEvent('stepSelected:' + id); } else { this.editorInput.selectedStep = undefined; } From fe0632cbb2d6d3517676748a9601d3cb3dc36b01 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 26 Jan 2024 23:28:45 +0100 Subject: [PATCH 0659/1897] Git - handle stashes that contain untracked files (#203572) --- extensions/git/src/commands.ts | 39 ++++++++++++++++++++++++-------- extensions/git/src/git.ts | 11 ++++++--- extensions/git/src/operation.ts | 15 +++++++----- extensions/git/src/repository.ts | 6 ++++- 4 files changed, 51 insertions(+), 20 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 33bd2ce2393..5ff4fc4f4a6 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3709,25 +3709,44 @@ export class CommandCenter { } const stashChanges = await repository.showStash(stash.index); - const stashParentCommit = stash.parents.length > 0 ? stash.parents[0] : `${stash.hash}^`; - if (!stashChanges || stashChanges.length === 0) { return; } + // A stash commit can have up to 3 parents: + // 1. The first parent is the commit that was HEAD when the stash was created. + // 2. The second parent is the commit that represents the index when the stash was created. + // 3. The third parent (when present) represents the untracked files when the stash was created. + const stashFirstParentCommit = stash.parents.length > 0 ? stash.parents[0] : `${stash.hash}^`; + const stashUntrackedFilesParentCommit = stash.parents.length === 3 ? stash.parents[2] : undefined; + const stashUntrackedFiles: string[] = []; + + if (stashUntrackedFilesParentCommit) { + const untrackedFiles = await repository.getObjectFiles(stashUntrackedFilesParentCommit); + stashUntrackedFiles.push(...untrackedFiles.map(f => path.join(repository.root, f.file))); + } + const title = `Git Stash #${stash.index}: ${stash.description}`; const multiDiffSourceUri = toGitUri(Uri.file(repository.root), `stash@{${stash.index}}`, { scheme: 'git-stash' }); const resources: { originalUri: Uri | undefined; modifiedUri: Uri | undefined }[] = []; for (const change of stashChanges) { - if (change.status === Status.INDEX_ADDED) { - resources.push({ originalUri: undefined, modifiedUri: toGitUri(change.uri, stash.hash) }); - } else if (change.status === Status.DELETED) { - resources.push({ originalUri: toGitUri(change.uri, stashParentCommit), modifiedUri: undefined }); - } else if (change.status === Status.INDEX_RENAMED) { - resources.push({ originalUri: toGitUri(change.originalUri, stashParentCommit), modifiedUri: toGitUri(change.uri, stash.hash) }); - } else { - resources.push({ originalUri: toGitUri(change.uri, stashParentCommit), modifiedUri: toGitUri(change.uri, stash.hash) }); + const isChangeUntracked = !!stashUntrackedFiles.find(f => pathEquals(f, change.uri.fsPath)); + const modifiedUriRef = !isChangeUntracked ? stash.hash : stashUntrackedFilesParentCommit ?? stash.hash; + + switch (change.status) { + case Status.INDEX_ADDED: + resources.push({ originalUri: undefined, modifiedUri: toGitUri(change.uri, modifiedUriRef) }); + break; + case Status.DELETED: + resources.push({ originalUri: toGitUri(change.uri, stashFirstParentCommit), modifiedUri: undefined }); + break; + case Status.INDEX_RENAMED: + resources.push({ originalUri: toGitUri(change.originalUri, stashFirstParentCommit), modifiedUri: toGitUri(change.uri, modifiedUriRef) }); + break; + default: + resources.push({ originalUri: toGitUri(change.uri, stashFirstParentCommit), modifiedUri: toGitUri(change.uri, modifiedUriRef) }); + break; } } diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 119fce2815d..785a9cd2267 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -937,7 +937,7 @@ function parseGitDiffShortStat(data: string): CommitShortStat { return { files: parseInt(files), insertions: parseInt(insertions ?? '0'), deletions: parseInt(deletions ?? '0') }; } -interface LsTreeElement { +export interface LsTreeElement { mode: string; type: string; object: string; @@ -1294,8 +1294,13 @@ export class Repository { return { mode, object, size: parseInt(size) }; } - async lstree(treeish: string, path: string): Promise { - const { stdout } = await this.exec(['ls-tree', '-l', treeish, '--', sanitizePath(path)]); + async lstree(treeish: string, path?: string): Promise { + const args = ['ls-tree', '-l', treeish]; + if (path) { + args.push('--', sanitizePath(path)); + } + + const { stdout } = await this.exec(args); return parseLsTree(stdout); } diff --git a/extensions/git/src/operation.ts b/extensions/git/src/operation.ts index 15e494f5fab..ead345c0b06 100644 --- a/extensions/git/src/operation.ts +++ b/extensions/git/src/operation.ts @@ -28,6 +28,7 @@ export const enum OperationKind { GetBranches = 'GetBranches', GetCommitTemplate = 'GetCommitTemplate', GetObjectDetails = 'GetObjectDetails', + GetObjectFiles = 'GetObjectFiles', GetRefs = 'GetRefs', GetRemoteRefs = 'GetRemoteRefs', HashObject = 'HashObject', @@ -65,12 +66,12 @@ export const enum OperationKind { export type Operation = AddOperation | ApplyOperation | BlameOperation | BranchOperation | CheckIgnoreOperation | CherryPickOperation | CheckoutOperation | CheckoutTrackingOperation | CleanOperation | CommitOperation | ConfigOperation | DeleteBranchOperation | DeleteRefOperation | DeleteRemoteTagOperation | DeleteTagOperation | DiffOperation | FetchOperation | FindTrackingBranchesOperation | - GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetRefsOperation | GetRemoteRefsOperation | - HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation | MergeBaseOperation | - MoveOperation | PostCommitCommandOperation | PullOperation | PushOperation | RemoteOperation | RenameBranchOperation | RemoveOperation | - ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RefreshOperation | RevertFilesOperation | RevListOperation | - RevParseOperation | SetBranchUpstreamOperation | ShowOperation | StageOperation | StatusOperation | StashOperation | SubmoduleUpdateOperation | - SyncOperation | TagOperation; + GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetObjectFilesOperation | GetRefsOperation | + GetRemoteRefsOperation | HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation | + MergeBaseOperation | MoveOperation | PostCommitCommandOperation | PullOperation | PushOperation | RemoteOperation | RenameBranchOperation | + RemoveOperation | ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RefreshOperation | RevertFilesOperation | + RevListOperation | RevParseOperation | SetBranchUpstreamOperation | ShowOperation | StageOperation | StatusOperation | StashOperation | + SubmoduleUpdateOperation | SyncOperation | TagOperation; type BaseOperation = { kind: OperationKind; blocking: boolean; readOnly: boolean; remote: boolean; retry: boolean; showProgress: boolean }; export type AddOperation = BaseOperation & { kind: OperationKind.Add }; @@ -95,6 +96,7 @@ export type GetBranchOperation = BaseOperation & { kind: OperationKind.GetBranch export type GetBranchesOperation = BaseOperation & { kind: OperationKind.GetBranches }; export type GetCommitTemplateOperation = BaseOperation & { kind: OperationKind.GetCommitTemplate }; export type GetObjectDetailsOperation = BaseOperation & { kind: OperationKind.GetObjectDetails }; +export type GetObjectFilesOperation = BaseOperation & { kind: OperationKind.GetObjectFiles }; export type GetRefsOperation = BaseOperation & { kind: OperationKind.GetRefs }; export type GetRemoteRefsOperation = BaseOperation & { kind: OperationKind.GetRemoteRefs }; export type HashObjectOperation = BaseOperation & { kind: OperationKind.HashObject }; @@ -151,6 +153,7 @@ export const Operation = { GetBranches: { kind: OperationKind.GetBranches, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as GetBranchesOperation, GetCommitTemplate: { kind: OperationKind.GetCommitTemplate, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as GetCommitTemplateOperation, GetObjectDetails: { kind: OperationKind.GetObjectDetails, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as GetObjectDetailsOperation, + GetObjectFiles: { kind: OperationKind.GetObjectFiles, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as GetObjectFilesOperation, GetRefs: { kind: OperationKind.GetRefs, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as GetRefsOperation, GetRemoteRefs: { kind: OperationKind.GetRemoteRefs, blocking: false, readOnly: true, remote: true, retry: false, showProgress: false } as GetRemoteRefsOperation, HashObject: { kind: OperationKind.HashObject, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as HashObjectOperation, diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 70e4e98c43a..89bdc37f624 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -11,7 +11,7 @@ import TelemetryReporter from '@vscode/extension-telemetry'; import { Branch, Change, ForcePushMode, GitErrorCodes, LogOptions, Ref, Remote, Status, CommitOptions, BranchQuery, FetchOptions, RefQuery, RefType } from './api/git'; import { AutoFetcher } from './autofetch'; import { debounce, memoize, throttle } from './decorators'; -import { Commit, GitError, Repository as BaseRepository, Stash, Submodule, LogFileOptions, PullOptions } from './git'; +import { Commit, GitError, Repository as BaseRepository, Stash, Submodule, LogFileOptions, PullOptions, LsTreeElement } from './git'; import { StatusBarCommands } from './statusbar'; import { toGitUri } from './uri'; import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, IDisposable, isDescendant, onceEvent, pathEquals, relativePath } from './util'; @@ -1955,6 +1955,10 @@ export class Repository implements Disposable { }); } + getObjectFiles(ref: string): Promise { + return this.run(Operation.GetObjectFiles, () => this.repository.lstree(ref)); + } + getObjectDetails(ref: string, filePath: string): Promise<{ mode: string; object: string; size: number }> { return this.run(Operation.GetObjectDetails, () => this.repository.getObjectDetails(ref, filePath)); } From 3adea2f5b17b8ce087a69f0de2aa2c5fc607038d Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 26 Jan 2024 23:17:18 -0800 Subject: [PATCH 0660/1897] testing: handle default profile changes in watch, nested cancellations (#203583) Fixes #203496 Fixes #203497 --- src/vs/base/common/prefixTree.ts | 93 ++++++++++++++----- src/vs/base/test/common/prefixTree.test.ts | 22 +++++ .../testing/browser/testExplorerActions.ts | 9 +- .../common/testingContinuousRunService.ts | 76 ++++++++++----- 4 files changed, 144 insertions(+), 56 deletions(-) diff --git a/src/vs/base/common/prefixTree.ts b/src/vs/base/common/prefixTree.ts index 00fa5ac99b8..0c2bdca384b 100644 --- a/src/vs/base/common/prefixTree.ts +++ b/src/vs/base/common/prefixTree.ts @@ -48,25 +48,44 @@ export class WellDefinedPrefixTree { /** Deletes a node from the prefix tree, returning the value it contained. */ delete(key: Iterable): V | undefined { - const path = [{ part: '', node: this.root }]; - let i = 0; - for (const part of key) { - const node = path[i].node.children?.get(part); - if (!node) { - return undefined; // node not in tree - } - - path.push({ part, node }); - i++; + const path = this.getPathToKey(key); + if (!path) { + return; } + let i = path.length - 1; const value = path[i].node._value; if (value === unset) { return; // not actually a real node } this._size--; + path[i].node._value = unset; + for (; i > 0; i--) { + const { node, part } = path[i]; + if (node.children?.size || node._value !== unset) { + break; + } + + path[i - 1].node.children!.delete(part); + } + + return value; + } + + /** Deletes a subtree from the prefix tree, returning the values they contained. */ + *deleteRecursive(key: Iterable): Iterable { + const path = this.getPathToKey(key); + if (!path) { + return; + } + + const subtree = path[path.length - 1].node; + + // important: run the deletion before we start to yield results, so that + // it still runs even if the caller doesn't consumer the iterator + for (let i = path.length - 1; i > 0; i--) { const parent = path[i - 1]; parent.node.children!.delete(path[i].part); if (parent.node.children!.size > 0 || parent.node._value !== unset) { @@ -74,7 +93,12 @@ export class WellDefinedPrefixTree { } } - return value; + for (const node of bfsIterate(subtree)) { + if (node._value !== unset) { + this._size--; + yield node._value; + } + } } /** Gets a value from the tree. */ @@ -140,6 +164,22 @@ export class WellDefinedPrefixTree { return node._value !== unset; } + private getPathToKey(key: Iterable) { + const path = [{ part: '', node: this.root }]; + let i = 0; + for (const part of key) { + const node = path[i].node.children?.get(part); + if (!node) { + return; // node not in tree + } + + path.push({ part, node }); + i++; + } + + return path; + } + private opNode(key: Iterable, fn: (node: Node) => void, onDescend?: (node: Node) => void): void { let node = this.root; for (const part of key) { @@ -157,26 +197,31 @@ export class WellDefinedPrefixTree { onDescend?.(node); } - if (node._value === unset) { - this._size++; - } - + const sizeBefore = node._value === unset ? 0 : 1; fn(node); + const sizeAfter = node._value === unset ? 0 : 1; + this._size += sizeAfter - sizeBefore; } /** Returns an iterable of the tree values in no defined order. */ *values() { - const stack = [this.root]; - while (stack.length > 0) { - const node = stack.pop()!; - if (node._value !== unset) { - yield node._value; + for (const { _value } of bfsIterate(this.root)) { + if (_value !== unset) { + yield _value; } + } + } +} - if (node.children) { - for (const child of node.children.values()) { - stack.push(child); - } +function* bfsIterate(root: Node): Iterable> { + const stack = [root]; + while (stack.length > 0) { + const node = stack.pop()!; + yield node; + + if (node.children) { + for (const child of node.children.values()) { + stack.push(child); } } } diff --git a/src/vs/base/test/common/prefixTree.test.ts b/src/vs/base/test/common/prefixTree.test.ts index 1973e6a8b5d..e5545734a8a 100644 --- a/src/vs/base/test/common/prefixTree.test.ts +++ b/src/vs/base/test/common/prefixTree.test.ts @@ -137,4 +137,26 @@ suite('WellDefinedPrefixTree', () => { assert.deepStrictEqual([...tree.values()], [43, 42]); }); + + + test('delete recursive', () => { + const key1 = ['foo', 'bar']; + const key2 = ['foo', 'bar', 'baz']; + const key3 = ['foo', 'bar', 'baz2', 'baz3']; + const key4 = ['foo', 'bar2']; + tree.insert(key1, 42); + tree.insert(key2, 43); + tree.insert(key3, 44); + tree.insert(key4, 45); + assert.strictEqual(tree.size, 4); + + assert.deepStrictEqual([...tree.deleteRecursive(key1)], [42, 44, 43]); + assert.strictEqual(tree.size, 1); + + assert.deepStrictEqual([...tree.deleteRecursive(key1)], []); + assert.strictEqual(tree.size, 1); + + assert.deepStrictEqual([...tree.deleteRecursive(key4)], [45]); + assert.strictEqual(tree.size, 0); + }); }); diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index b3cd0a1f306..9ffed3a5fa9 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -290,7 +290,6 @@ export class ContinuousRunTestAction extends Action2 { public override async run(accessor: ServicesAccessor, ...elements: TestItemTreeElement[]): Promise { const crService = accessor.get(ITestingContinuousRunService); - const profileService = accessor.get(ITestProfileService); for (const element of elements) { const id = element.test.item.extId; if (crService.isSpecificallyEnabledFor(id)) { @@ -298,13 +297,7 @@ export class ContinuousRunTestAction extends Action2 { continue; } - const profiles = profileService.getGroupDefaultProfiles(TestRunProfileBitset.Run) - .filter(p => p.supportsContinuousRun && p.controllerId === element.test.controllerId); - if (!profiles.length) { - continue; - } - - crService.start(profiles, id); + crService.start(TestRunProfileBitset.Run, id); } } } diff --git a/src/vs/workbench/contrib/testing/common/testingContinuousRunService.ts b/src/vs/workbench/contrib/testing/common/testingContinuousRunService.ts index 0e088ea4ff3..b8053615c6b 100644 --- a/src/vs/workbench/contrib/testing/common/testingContinuousRunService.ts +++ b/src/vs/workbench/contrib/testing/common/testingContinuousRunService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; @@ -12,10 +12,12 @@ import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue'; import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; import { TestService } from 'vs/workbench/contrib/testing/common/testServiceImpl'; -import { ITestRunProfile } from 'vs/workbench/contrib/testing/common/testTypes'; +import { ITestRunProfile, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes'; import { Emitter, Event } from 'vs/base/common/event'; import { TestId } from 'vs/workbench/contrib/testing/common/testId'; import { WellDefinedPrefixTree } from 'vs/base/common/prefixTree'; +import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService'; +import * as arrays from 'vs/base/common/arrays'; export const ITestingContinuousRunService = createDecorator('testingContinuousRunService'); @@ -56,10 +58,11 @@ export interface ITestingContinuousRunService { isEnabled(): boolean; /** - * Starts a continuous auto run with a specific profile or set of profiles. - * Globally if no test is given, for a specific test otherwise. + * Starts a continuous auto run with a specific set of profiles, or all + * default profiles in a group. Globally if no test is given, + * for a specific test otherwise. */ - start(profile: ITestRunProfile[], testId?: string): void; + start(profile: ITestRunProfile[] | TestRunProfileBitset, testId?: string): void; /** * Stops any continuous run @@ -72,8 +75,8 @@ export class TestingContinuousRunService extends Disposable implements ITestingC declare readonly _serviceBrand: undefined; private readonly changeEmitter = new Emitter(); - private globallyRunning?: CancellationTokenSource; - private readonly running = new WellDefinedPrefixTree(); + private globallyRunning?: IDisposable; + private readonly running = new WellDefinedPrefixTree(); private readonly lastRun: StoredValue>; private readonly isGloballyOn: IContextKey; @@ -87,6 +90,7 @@ export class TestingContinuousRunService extends Disposable implements ITestingC @ITestService private readonly testService: TestService, @IStorageService storageService: IStorageService, @IContextKeyService contextKeyService: IContextKeyService, + @ITestProfileService private readonly testProfileService: ITestProfileService, ) { super(); this.isGloballyOn = TestingContextKeys.isContinuousModeOn.bindTo(contextKeyService); @@ -133,34 +137,54 @@ export class TestingContinuousRunService extends Disposable implements ITestingC } /** @inheritdoc */ - public start(profile: ITestRunProfile[], testId?: string): void { + public start(profiles: ITestRunProfile[] | TestRunProfileBitset, testId?: string): void { + const store = new DisposableStore(); const cts = new CancellationTokenSource(); + store.add(toDisposable(() => cts.dispose(true))); if (testId === undefined) { this.isGloballyOn.set(true); } if (!testId) { - this.globallyRunning?.dispose(true); - this.globallyRunning = cts; + this.globallyRunning?.dispose(); + this.globallyRunning = store; } else { this.running.mutate(TestId.fromString(testId).path, c => { - c?.dispose(true); - return cts; + c?.dispose(); + return store; }); } - this.lastRun.store(new Set(profile.map(p => p.profileId))); + let actualProfiles: ITestRunProfile[]; + if (profiles instanceof Array) { + actualProfiles = profiles; + } else { + // restart the continuous run when default profiles change, if we were + // asked to run for a group + const getRelevant = () => this.testProfileService.getGroupDefaultProfiles(profiles) + .filter(p => p.supportsContinuousRun && (!testId || TestId.root(testId) === p.controllerId)); + actualProfiles = getRelevant(); + store.add(this.testProfileService.onDidChange(() => { + if (!arrays.equals(getRelevant(), actualProfiles)) { + this.start(profiles, testId); + } + })); + } - this.testService.startContinuousRun({ - continuous: true, - targets: profile.map(p => ({ - testIds: [testId ?? p.controllerId], - controllerId: p.controllerId, - profileGroup: p.group, - profileId: p.profileId - })), - }, cts.token); + this.lastRun.store(new Set(actualProfiles.map(p => p.profileId))); + + if (actualProfiles.length) { + this.testService.startContinuousRun({ + continuous: true, + targets: actualProfiles.map(p => ({ + testIds: [testId ?? p.controllerId], + controllerId: p.controllerId, + profileGroup: p.group, + profileId: p.profileId + })), + }, cts.token); + } this.changeEmitter.fire(testId); } @@ -168,10 +192,14 @@ export class TestingContinuousRunService extends Disposable implements ITestingC /** @inheritdoc */ public stop(testId?: string): void { if (!testId) { - this.globallyRunning?.dispose(true); + this.globallyRunning?.dispose(); this.globallyRunning = undefined; } else { - this.running.delete(TestId.fromString(testId).path)?.dispose(true); + const cancellations = [...this.running.deleteRecursive(TestId.fromString(testId).path)]; + // deleteRecursive returns a BFS order, reverse it so children are cancelled before parents + for (let i = cancellations.length - 1; i >= 0; i--) { + cancellations[i].dispose(); + } } if (testId === undefined) { From a19b2d5fb0202e00fb930dc850d2695ec512e495 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 27 Jan 2024 09:38:22 +0100 Subject: [PATCH 0661/1897] actions - ensure `checked: undefined` when action is not a checkbox (fix #203491) (#203593) --- src/vs/base/common/actions.ts | 2 +- .../workbench/contrib/preferences/browser/settingsSearchMenu.ts | 1 - .../workbench/contrib/testing/browser/testingExplorerFilter.ts | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index 36ed201bdc5..28402b5ffda 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -264,7 +264,7 @@ export function toAction(props: { id: string; label: string; enabled?: boolean; label: props.label, class: props.class, enabled: props.enabled ?? true, - checked: props.checked ?? false, + checked: props.checked, run: async (...args: unknown[]) => props.run(...args), tooltip: props.label }; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts index 74e5ac3c744..93e13c0234d 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts @@ -57,7 +57,6 @@ export class SettingsSearchFilterDropdownMenuActionViewItem extends DropdownMenu tooltip, class: undefined, enabled: true, - checked: false, run: () => { this.doSearchWidgetAction(queryToAppend, triggerSuggest); } }; } diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts index 05be7da398c..ba9050a610b 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts @@ -230,7 +230,6 @@ class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem { tooltip: '' }, { - checked: false, class: undefined, enabled: this.testService.excluded.hasAny, id: 'removeExcluded', From 0d22ba354b5e9482d3953759dd817150e15c181f Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Sat, 27 Jan 2024 17:16:16 +0100 Subject: [PATCH 0662/1897] =?UTF-8?q?Git=20-=20=F0=9F=92=84=20remove=20dup?= =?UTF-8?q?licated=20code=20(#203609)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/git/src/git.ts | 70 ++------------------------------------- 1 file changed, 2 insertions(+), 68 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 785a9cd2267..738295bdec1 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -994,13 +994,10 @@ function parseGitStashes(raw: string): Stash[] { return result; } -// TODO@lszomoru - adopt in diffFiles() function parseGitChanges(repositoryRoot: string, raw: string): Change[] { let index = 0; const result: Change[] = []; - const segments = raw.trim() - .split('\x00') - .filter(s => s); + const segments = raw.trim().split('\x00').filter(s => s); segmentsLoop: while (index < segments.length - 1) { @@ -1492,70 +1489,7 @@ export class Repository { return []; } - const entries = gitResult.stdout.split('\x00'); - let index = 0; - const result: Change[] = []; - - entriesLoop: - while (index < entries.length - 1) { - const change = entries[index++]; - const resourcePath = entries[index++]; - if (!change || !resourcePath) { - break; - } - - const originalUri = Uri.file(path.isAbsolute(resourcePath) ? resourcePath : path.join(this.repositoryRoot, resourcePath)); - let status: Status = Status.UNTRACKED; - - // Copy or Rename status comes with a number, e.g. 'R100'. We don't need the number, so we use only first character of the status. - switch (change[0]) { - case 'M': - status = Status.MODIFIED; - break; - - case 'A': - status = Status.INDEX_ADDED; - break; - - case 'D': - status = Status.DELETED; - break; - - // Rename contains two paths, the second one is what the file is renamed/copied to. - case 'R': { - if (index >= entries.length) { - break; - } - - const newPath = entries[index++]; - if (!newPath) { - break; - } - - const uri = Uri.file(path.isAbsolute(newPath) ? newPath : path.join(this.repositoryRoot, newPath)); - result.push({ - uri, - renameUri: uri, - originalUri, - status: Status.INDEX_RENAMED - }); - - continue; - } - default: - // Unknown status - break entriesLoop; - } - - result.push({ - status, - originalUri, - uri: originalUri, - renameUri: originalUri, - }); - } - - return result; + return parseGitChanges(this.repositoryRoot, gitResult.stdout); } async getMergeBase(ref1: string, ref2: string): Promise { From 98d55333b90bb0a58727da56447ff2018beb83d4 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 29 Jan 2024 07:55:19 +0000 Subject: [PATCH 0663/1897] Fix md pasting inside of incomplete html block (#203476) Fix pasting inside of incomplete html block Fixes #188868 --- .../copyFiles/pasteUrlProvider.ts | 28 +++++++++++++++++-- ...{markdownLink.test.ts => pasteUrl.test.ts} | 19 +++++++++++-- 2 files changed, 42 insertions(+), 5 deletions(-) rename extensions/markdown-language-features/src/test/{markdownLink.test.ts => pasteUrl.test.ts} (92%) diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts index 68dca194702..4776ccd2599 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts @@ -124,6 +124,8 @@ export async function shouldInsertMarkdownLinkByDefault( } } +const textTokenTypes = new Set(['paragraph_open', 'inline', 'heading_open', 'ordered_list_open', 'bullet_list_open', 'list_item_open', 'blockquote_open']); + async function shouldSmartPasteForSelection( parser: IMdParser, document: ITextDocument, @@ -150,9 +152,29 @@ async function shouldSmartPasteForSelection( if (token.isCancellationRequested) { return false; } - for (const token of tokens) { - if (token.map && token.map[0] <= selectedRange.start.line && token.map[1] > selectedRange.start.line) { - if (!['paragraph_open', 'inline', 'heading_open', 'ordered_list_open', 'bullet_list_open', 'list_item_open', 'blockquote_open'].includes(token.type)) { + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + if (!token.map) { + continue; + } + if (token.map[0] <= selectedRange.start.line && token.map[1] > selectedRange.start.line) { + if (!textTokenTypes.has(token.type)) { + return false; + } + } + + // Special case for html such as: + // + // + // | + // + // + // In this case pasting will cause the html block to be created even though the cursor is not currently inside a block + if (token.type === 'html_block' && token.map[1] === selectedRange.start.line) { + const nextToken = tokens.at(i + 1); + // The next token does not need to be a html_block, but it must be on the next line + if (nextToken?.map?.[0] === selectedRange.end.line + 1) { return false; } } diff --git a/extensions/markdown-language-features/src/test/markdownLink.test.ts b/extensions/markdown-language-features/src/test/pasteUrl.test.ts similarity index 92% rename from extensions/markdown-language-features/src/test/markdownLink.test.ts rename to extensions/markdown-language-features/src/test/pasteUrl.test.ts index 7b5849e47e2..4835af35e59 100644 --- a/extensions/markdown-language-features/src/test/markdownLink.test.ts +++ b/extensions/markdown-language-features/src/test/pasteUrl.test.ts @@ -203,6 +203,23 @@ suite('createEditAddingLinksForUriList', () => { false); }); + test('Smart should be disabled in html blocks where paste creates the block', async () => { + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('

\n\n

'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(1, 0, 1, 0)], noopToken), + false, + 'Between two html tags should be treated as html block'); + + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('

\n\ntext'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(1, 0, 1, 0)], noopToken), + false, + 'Between opening html tag and text should be treated as html block'); + + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('

\n\n\n

'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(1, 0, 1, 0)], noopToken), + true, + 'Extra new line after paste should not be treated as html block'); + }); + test('Smart should be disabled in Markdown links', async () => { assert.strictEqual( await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('[a](bcdef)'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 4, 0, 6)], noopToken), @@ -266,7 +283,5 @@ suite('createEditAddingLinksForUriList', () => { await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc(' \r\n\r\n'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 7)], noopToken), false); }); - - }); }); From 9d6d60f41d6fc7e0207501cedb01b281ae8d4197 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 29 Jan 2024 07:57:47 +0000 Subject: [PATCH 0664/1897] Fix possible TerminalInstance leak (#203390) * Fix possible TerminalInstance leak Fixes #200400 Two fixes: - Make sure an event is disposed of - In EventMultiplexer, explicitly dispose of all listeners when the multiplexer is disposed * Remove extra import --- src/vs/base/common/event.ts | 9 ++++++--- .../workbench/api/browser/mainThreadTerminalService.ts | 6 +++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 3686b27d8e8..7e5f3e90f2a 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -1456,14 +1456,17 @@ export class EventMultiplexer implements IDisposable { } private unhook(e: { event: Event; listener: IDisposable | null }): void { - if (e.listener) { - e.listener.dispose(); - } + e.listener?.dispose(); e.listener = null; } dispose(): void { this.emitter.dispose(); + + for (const e of this.events) { + e.listener?.dispose(); + } + this.events = []; } } diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index e9a1645abdb..c84c2cd14cc 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DisposableStore, Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, Disposable, IDisposable, MutableDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, TerminalLaunchConfig, ITerminalDimensionsDto, ExtHostTerminalIdentifier, TerminalQuickFix, ITerminalCommandDto } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; @@ -236,7 +236,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } const multiplexer = this._terminalService.createOnInstanceCapabilityEvent(TerminalCapability.CommandDetection, capability => capability.onCommandFinished); - multiplexer.event(e => { + const sub = multiplexer.event(e => { this._onDidExecuteCommand(e.instance.instanceId, { commandLine: e.data.command, // TODO: Convert to URI if possible @@ -245,7 +245,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape output: e.data.getOutput() }); }); - this._sendCommandEventListener.value = multiplexer; + this._sendCommandEventListener.value = combinedDisposable(multiplexer, sub); } public $stopSendingCommandEvents(): void { From 772791e9e529a7c80c83f6fcc8c3767442f69030 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 29 Jan 2024 08:10:52 +0000 Subject: [PATCH 0665/1897] Fix markdown link pasting when selection is inline code (#203657) If the user selects a complete inline code block, we should paste as a markdown link with the code as the link text --- .../copyFiles/pasteUrlProvider.ts | 12 +++++++++-- .../src/test/pasteUrl.test.ts | 21 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts index 4776ccd2599..995e2a05819 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts @@ -89,7 +89,7 @@ const smartPasteLineRegexes = [ { regex: /\$\$[\s\S]*?\$\$/gm }, // In a fenced math block { regex: /`[^`]*`/g }, // In inline code { regex: /\$[^$]*\$/g }, // In inline math - { regex: /^[ ]{0,3}\[\w+\]:\s.*$/g }, // Block link definition (needed as tokens are not generated for these) + { regex: /^[ ]{0,3}\[\w+\]:\s.*$/g, isWholeLine: true }, // Block link definition (needed as tokens are not generated for these) ]; export async function shouldInsertMarkdownLinkByDefault( @@ -184,7 +184,15 @@ async function shouldSmartPasteForSelection( const line = document.getText(new vscode.Range(selectedRange.start.line, 0, selectedRange.start.line, Number.MAX_SAFE_INTEGER)); for (const regex of smartPasteLineRegexes) { for (const match of line.matchAll(regex.regex)) { - if (match.index !== undefined && selectedRange.start.character >= match.index && selectedRange.start.character <= match.index + match[0].length) { + if (match.index === undefined) { + continue; + } + + if (regex.isWholeLine) { + return false; + } + + if (selectedRange.start.character > match.index && selectedRange.start.character < match.index + match[0].length) { return false; } } diff --git a/extensions/markdown-language-features/src/test/pasteUrl.test.ts b/extensions/markdown-language-features/src/test/pasteUrl.test.ts index 4835af35e59..88b996de37d 100644 --- a/extensions/markdown-language-features/src/test/pasteUrl.test.ts +++ b/extensions/markdown-language-features/src/test/pasteUrl.test.ts @@ -195,6 +195,10 @@ suite('createEditAddingLinksForUriList', () => { assert.strictEqual( await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('[ref]: '), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 7, 0, 7)], noopToken), false); + + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('[ref]: '), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 0, 0, 0)], noopToken), + false); }); test('Smart should be disabled in html blocks', async () => { @@ -235,11 +239,24 @@ suite('createEditAddingLinksForUriList', () => { test('Smart should be disabled in inline code', async () => { assert.strictEqual( await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('``'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 1, 0, 1)], noopToken), - false); + false, + 'Should be disabled inside of inline code'); assert.strictEqual( await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('``'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 0, 0, 0)], noopToken), - false); + true, + 'Should be enabled when cursor is outside but next to inline code'); + + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('`a`'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 3, 0, 3)], noopToken), + true, + 'Should be enabled when cursor is outside but next to inline code'); + }); + + test('Smart should be enabled when pasting over inline code ', async () => { + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('`xyz`'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 0, 0, 5)], noopToken), + true); }); test('Smart should be disabled in inline math', async () => { From 4ed4f62534dbf7a6b2b195d2fda4206e383d2c21 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 29 Jan 2024 09:29:48 +0100 Subject: [PATCH 0666/1897] update notebook milestones (#203659) --- .vscode/notebooks/api.github-issues | 2 +- .vscode/notebooks/my-work.github-issues | 28 ++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index 8027d3066ef..2a6f3ec1bc5 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"December / January 2024\"" + "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"February 2024\"" }, { "kind": 1, diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index cbdd18d1653..2a3f9159703 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\r\n$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\r\n\r\n// current milestone name\r\n$MILESTONE=milestone:\"December / January 2024\"\r" + "value": "// list of repos we work in\n$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n// current milestone name\n$MILESTONE=milestone:\"February 2024\"\n" }, { "kind": 1, @@ -17,7 +17,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE assignee:@me is:open\r" + "value": "$REPOS $MILESTONE assignee:@me is:open\n" }, { "kind": 1, @@ -32,7 +32,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS assignee:@me is:open label:bug\r" + "value": "$REPOS assignee:@me is:open label:bug\n" }, { "kind": 1, @@ -42,7 +42,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS assignee:@me is:open label:debt,engineering\r" + "value": "$REPOS assignee:@me is:open label:debt,engineering\n" }, { "kind": 1, @@ -52,7 +52,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS assignee:@me is:open label:perf,perf-startup,perf-bloat,freeze-slow-crash-leak\r" + "value": "$REPOS assignee:@me is:open label:perf,perf-startup,perf-bloat,freeze-slow-crash-leak\n" }, { "kind": 1, @@ -62,12 +62,12 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS assignee:@me is:open label:feature-request milestone:Backlog sort:reactions-+1-desc\r" + "value": "$REPOS assignee:@me is:open label:feature-request milestone:Backlog sort:reactions-+1-desc\n" }, { "kind": 2, "language": "github-issues", - "value": "$REPOS assignee:@me is:open milestone:\"Backlog Candidates\"\r" + "value": "$REPOS assignee:@me is:open milestone:\"Backlog Candidates\"\n" }, { "kind": 1, @@ -82,7 +82,7 @@ { "kind": 2, "language": "github-issues", - "value": "repo:microsoft/vscode is:open assignee:@me label:triage-needed\r" + "value": "repo:microsoft/vscode is:open assignee:@me label:triage-needed\n" }, { "kind": 1, @@ -92,7 +92,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS assignee:@me is:open type:issue -label:bug -label:\"info-needed\" -label:feature-request -label:under-discussion -label:debt -label:plan-item -label:upstream -label:polish -label:testplan-item -label:error-telemetry -label:engineering\r" + "value": "$REPOS assignee:@me is:open type:issue -label:bug -label:\"info-needed\" -label:feature-request -label:under-discussion -label:debt -label:plan-item -label:upstream -label:polish -label:testplan-item -label:error-telemetry -label:engineering\n" }, { "kind": 1, @@ -112,7 +112,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS assignee:@me is:open type:issue no:milestone -label:info-needed -label:triage-needed\r" + "value": "$REPOS assignee:@me is:open type:issue no:milestone -label:info-needed -label:triage-needed\n" }, { "kind": 1, @@ -122,7 +122,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS assignee:@me is:open label:\"info-needed\"\r" + "value": "$REPOS assignee:@me is:open label:\"info-needed\"\n" }, { "kind": 1, @@ -137,7 +137,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS author:@me is:open is:pr review:approved\r" + "value": "$REPOS author:@me is:open is:pr review:approved\n" }, { "kind": 1, @@ -147,7 +147,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS author:@me is:open is:pr review:required\r" + "value": "$REPOS author:@me is:open is:pr review:required\n" }, { "kind": 1, @@ -157,6 +157,6 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS author:@me is:open is:pr review:changes_requested\r" + "value": "$REPOS author:@me is:open is:pr review:changes_requested\n" } ] \ No newline at end of file From 16f38b9d1a4e50df7a06567f86e2db683cd9069e Mon Sep 17 00:00:00 2001 From: Aleksandr Kondrashov <116561995+aramikuto@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:31:52 +0900 Subject: [PATCH 0667/1897] Ensure that cursor state change is always emitted upon restoring state (#203451) * Set initial cursor position for --- .../browser/viewParts/overviewRuler/decorationsOverviewRuler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts index dc3ecf70528..1338656acab 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts @@ -249,7 +249,7 @@ export class DecorationsOverviewRuler extends ViewPart { } }); - this._cursorPositions = []; + this._cursorPositions = [new Position(1, 1)]; } public override dispose(): void { From d9bc6b05de01188be96b787237caf14b22266b7f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 29 Jan 2024 10:58:19 +0100 Subject: [PATCH 0668/1897] aux window - adjust to a full `flex` layout (#203669) --- .../parts/editor/auxiliaryEditorPart.ts | 31 +++++-------------- .../browser/auxiliaryWindowService.ts | 22 ++++++++++--- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts index 22786a7c2cb..3c6553b23fb 100644 --- a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isFullscreen, onDidChangeFullscreen, onDidChangeZoomLevel } from 'vs/base/browser/browser'; +import { isFullscreen, onDidChangeFullscreen } from 'vs/base/browser/browser'; import { hide, show } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -77,12 +77,6 @@ export class AuxiliaryEditorPart { hide(statusbarPart.container); } - updateEditorPartHeight(fromEvent); - } - - function updateEditorPartHeight(fromEvent: boolean): void { - editorPartContainer.style.height = `calc(100% - ${computeEditorPartHeightOffset()}px)`; - if (fromEvent) { auxiliaryWindow.layout(); } @@ -112,20 +106,7 @@ export class AuxiliaryEditorPart { titlebarPart = disposables.add(this.titleService.createAuxiliaryTitlebarPart(auxiliaryWindow.container, editorPart)); titlebarPartVisible = true; - disposables.add(titlebarPart.onDidChange(() => updateEditorPartHeight(true))); - disposables.add(onDidChangeZoomLevel(targetWindowId => { - if (auxiliaryWindow.window.vscodeWindowId === targetWindowId && titlebarPartVisible) { - - // This is a workaround for https://github.com/microsoft/vscode/issues/202377 - // The title bar part prevents zooming in certain cases and when doing so, - // adjusts its size accordingly. This is however not reported from the - // `onDidchange` event that we listen to above, so we manually update the - // editor part height here. - - updateEditorPartHeight(true); - } - })); - + disposables.add(titlebarPart.onDidChange(() => auxiliaryWindow.layout())); disposables.add(onDidChangeFullscreen(windowId => { if (windowId !== auxiliaryWindow.window.vscodeWindowId) { return; // ignore all but our window @@ -140,7 +121,7 @@ export class AuxiliaryEditorPart { if (titlebarPart && oldTitlebarPartVisible !== titlebarPartVisible) { titlebarPart.container.style.display = titlebarPartVisible ? '' : 'none'; - updateEditorPartHeight(true); + auxiliaryWindow.layout(); } })); } else { @@ -173,8 +154,10 @@ export class AuxiliaryEditorPart { })); disposables.add(Event.once(this.lifecycleService.onDidShutdown)(() => disposables.dispose())); - // Layout - disposables.add(auxiliaryWindow.onDidLayout(dimension => { + // Layout: specifically `onWillLayout` to have a chance + // to build the aux editor part before other components + // have a chance to react. + disposables.add(auxiliaryWindow.onWillLayout(dimension => { const titlebarPartHeight = titlebarPart?.height ?? 0; titlebarPart?.layout(dimension.width, titlebarPartHeight, 0, 0); diff --git a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts index 6480665c278..ce6f32b0914 100644 --- a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { mark } from 'vs/base/common/performance'; import { Emitter, Event } from 'vs/base/common/event'; -import { Dimension, EventHelper, EventType, ModifierKeyEmitter, addDisposableListener, cloneGlobalStylesheets, copyAttributes, createLinkElement, createMetaElement, getActiveWindow, getClientArea, getWindowId, isGlobalStylesheet, position, registerWindow, sharedMutationObserver, size, trackAttributes } from 'vs/base/browser/dom'; +import { Dimension, EventHelper, EventType, ModifierKeyEmitter, addDisposableListener, cloneGlobalStylesheets, copyAttributes, createLinkElement, createMetaElement, getActiveWindow, getClientArea, getWindowId, isGlobalStylesheet, position, registerWindow, sharedMutationObserver, trackAttributes } from 'vs/base/browser/dom'; import { CodeWindow, ensureCodeWindow, mainWindow } from 'vs/base/browser/window'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -46,6 +46,7 @@ export interface IAuxiliaryWindowService { export interface IAuxiliaryWindow extends IDisposable { + readonly onWillLayout: Event; readonly onDidLayout: Event; readonly onBeforeUnload: Event; @@ -61,6 +62,9 @@ export interface IAuxiliaryWindow extends IDisposable { export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { + private readonly _onWillLayout = this._register(new Emitter()); + readonly onWillLayout = this._onWillLayout.event; + private readonly _onDidLayout = this._register(new Emitter()); readonly onDidLayout = this._onDidLayout.event; @@ -136,10 +140,16 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { } layout(): void { - const dimension = getClientArea(this.window.document.body, this.container); - position(this.container, 0, 0, 0, 0, 'relative'); - size(this.container, dimension.width, dimension.height); + // Split layout up into two events so that downstream components + // have a chance to participate in the beginning or end of the + // layout phase. + // This helps to build the auxiliary window in another component + // in the `onWillLayout` phase and then let other compoments + // react when the overall layout has finished in `onDidLayout`. + + const dimension = getClientArea(this.window.document.body, this.container); + this._onWillLayout.fire(dimension); this._onDidLayout.fire(dimension); } @@ -418,6 +428,10 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili // Create workbench container and apply classes const container = document.createElement('div'); container.setAttribute('role', 'application'); + position(container, 0, 0, 0, 0, 'relative'); + container.style.display = 'flex'; + container.style.height = '100%'; + container.style.flexDirection = 'column'; auxiliaryWindow.document.body.append(container); // Track attributes From 2b181f1b7924bce778a04f69dbd622d950410f0e Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 29 Jan 2024 10:59:02 +0100 Subject: [PATCH 0669/1897] Can't enable settings sync UI State option - "too large payload" (#203544) Fixes #202359 --- .../services/remote/common/tunnelModel.ts | 87 +++++++++++++++---- 1 file changed, 72 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/services/remote/common/tunnelModel.ts b/src/vs/workbench/services/remote/common/tunnelModel.ts index 31ce3fca59f..5394416d717 100644 --- a/src/vs/workbench/services/remote/common/tunnelModel.ts +++ b/src/vs/workbench/services/remote/common/tunnelModel.ts @@ -27,9 +27,25 @@ import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common const MISMATCH_LOCAL_PORT_COOLDOWN = 10 * 1000; // 10 seconds const TUNNELS_TO_RESTORE = 'remote.tunnels.toRestore'; +const TUNNELS_TO_RESTORE_EXPIRATION = 'remote.tunnels.toRestoreExpiration'; +const RESTORE_EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 14; // 2 weeks export const ACTIVATION_EVENT = 'onTunnel'; export const forwardedPortsViewEnabled = new RawContextKey('forwardedPortsViewEnabled', false, nls.localize('tunnel.forwardedPortsViewEnabled', "Whether the Ports view is enabled.")); +export interface RestorableTunnel { + remoteHost: string; + remotePort: number; + localAddress: string; + localUri: URI; + protocol: TunnelProtocol; + localPort?: number; + name?: string; + source: { + source: TunnelSource; + description: string; + }; +} + export interface Tunnel { remoteHost: string; remotePort: number; @@ -406,7 +422,7 @@ export class TunnelModel extends Disposable { private knownPortsRestoreValue: string | undefined; private restoreComplete = false; private onRestoreComplete: Emitter = new Emitter(); - private unrestoredExtensionTunnels: Map = new Map(); + private unrestoredExtensionTunnels: Map = new Map(); private sessionCachedProperties: Map> = new Map(); private portAttributesProviders: PortAttributesProvider[] = []; @@ -526,14 +542,22 @@ export class TunnelModel extends Disposable { return URI.parse(`${protocol}://${localAddress}`); } - private async getStorageKey(): Promise { + private async addStorageKeyPostfix(prefix: string): 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}`; + return `${prefix}.${this.environmentService.remoteAuthority}.${workspaceHash}`; + } + + private async getTunnelRestoreStorageKey(): Promise { + return this.addStorageKeyPostfix(TUNNELS_TO_RESTORE); + } + + private async getRestoreExpirationStorageKey(): Promise { + return this.addStorageKeyPostfix(TUNNELS_TO_RESTORE_EXPIRATION); } private async getTunnelRestoreValue(): Promise { @@ -543,7 +567,7 @@ export class TunnelModel extends Disposable { await this.storeForwarded(); return deprecatedValue; } - const storageKey = await this.getStorageKey(); + const storageKey = await this.getTunnelRestoreStorageKey(); if (!storageKey) { return undefined; } @@ -551,10 +575,11 @@ export class TunnelModel extends Disposable { } async restoreForwarded() { + this.cleanupExpiredTunnelsForRestore(); if (this.configurationService.getValue('remote.restoreForwardedPorts')) { const tunnelRestoreValue = await this.tunnelRestoreValue; if (tunnelRestoreValue && (tunnelRestoreValue !== this.knownPortsRestoreValue)) { - const tunnels = JSON.parse(tunnelRestoreValue) ?? []; + 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); @@ -564,7 +589,6 @@ export class TunnelModel extends Disposable { remote: { host: tunnel.remoteHost, port: tunnel.remotePort }, local: tunnel.localPort, name: tunnel.name, - privacy: tunnel.privacy, elevateIfNeeded: true, source: tunnel.source }); @@ -580,7 +604,7 @@ export class TunnelModel extends Disposable { if (!this.restoreListener) { // It's possible that at restore time the value hasn't synced. - const key = await this.getStorageKey(); + const key = await this.getTunnelRestoreStorageKey(); this.restoreListener = this._register(new DisposableStore()); this.restoreListener.add(this.storageService.onDidChangeValue(StorageScope.PROFILE, undefined, this.restoreListener)(async (e) => { if (e.key === key) { @@ -591,17 +615,50 @@ export class TunnelModel extends Disposable { } } + private cleanupExpiredTunnelsForRestore() { + const keys = this.storageService.keys(StorageScope.PROFILE, StorageTarget.USER).filter(key => key.startsWith(TUNNELS_TO_RESTORE_EXPIRATION)); + for (const key of keys) { + const expiration = this.storageService.getNumber(key, StorageScope.PROFILE); + if (expiration && expiration < Date.now()) { + this.tunnelRestoreValue = Promise.resolve(undefined); + const storageKey = key.replace(TUNNELS_TO_RESTORE_EXPIRATION, TUNNELS_TO_RESTORE); + this.storageService.remove(key, StorageScope.PROFILE); + this.storageService.remove(storageKey, StorageScope.PROFILE); + } + } + } + @debounce(1000) private async storeForwarded() { if (this.configurationService.getValue('remote.restoreForwardedPorts')) { - const valueToStore = JSON.stringify(Array.from(this.forwarded.values())); - if (valueToStore !== this.knownPortsRestoreValue) { - this.knownPortsRestoreValue = valueToStore; - const key = await this.getStorageKey(); - if (key) { - this.storageService.store(key, this.knownPortsRestoreValue, StorageScope.PROFILE, StorageTarget.USER); - } + const forwarded = Array.from(this.forwarded.values()); + const restorableTunnels: RestorableTunnel[] = forwarded.map(tunnel => { + return { + remoteHost: tunnel.remoteHost, + remotePort: tunnel.remotePort, + localPort: tunnel.localPort, + name: tunnel.name, + localAddress: tunnel.localAddress, + localUri: tunnel.localUri, + protocol: tunnel.protocol, + source: tunnel.source, + }; + }); + let valueToStore: string | undefined; + if (forwarded.length > 0) { + valueToStore = JSON.stringify(restorableTunnels); } + + const key = await this.getTunnelRestoreStorageKey(); + const expirationKey = await this.getRestoreExpirationStorageKey(); + if (!valueToStore && key && expirationKey) { + this.storageService.remove(key, StorageScope.PROFILE); + this.storageService.remove(expirationKey, StorageScope.PROFILE); + } else if ((valueToStore !== this.knownPortsRestoreValue) && key && expirationKey) { + this.storageService.store(key, valueToStore, StorageScope.PROFILE, StorageTarget.USER); + this.storageService.store(expirationKey, Date.now() + RESTORE_EXPIRATION_TIME, StorageScope.PROFILE, StorageTarget.USER); + } + this.knownPortsRestoreValue = valueToStore; } } @@ -699,7 +756,7 @@ export class TunnelModel extends Disposable { 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; + tunnelProperties.privacy = tunnelProperties.privacy; } } return tunnelProperties; From c75fdbd711bc7dda0da18393bd1c2b9b6c5eb229 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 29 Jan 2024 10:59:52 +0100 Subject: [PATCH 0670/1897] Adding colors from running `scripts/test-documentation.sh` (#203661) adding colors --- .../lib/stylelint/vscode-known-variables.json | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 7f5b6fa2892..1851ba7f0d7 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -11,6 +11,10 @@ "--vscode-activityBar-inactiveForeground", "--vscode-activityBarBadge-background", "--vscode-activityBarBadge-foreground", + "--vscode-activityBarTop-activeBorder", + "--vscode-activityBarTop-dropBorder", + "--vscode-activityBarTop-foreground", + "--vscode-activityBarTop-inactiveForeground", "--vscode-badge-background", "--vscode-badge-foreground", "--vscode-banner-background", @@ -42,7 +46,6 @@ "--vscode-chat-requestBorder", "--vscode-chat-slashCommandBackground", "--vscode-chat-slashCommandForeground", - "--vscode-chat-list-background", "--vscode-checkbox-background", "--vscode-checkbox-border", "--vscode-checkbox-foreground", @@ -268,6 +271,8 @@ "--vscode-editorOverviewRuler-findMatchForeground", "--vscode-editorOverviewRuler-incomingContentForeground", "--vscode-editorOverviewRuler-infoForeground", + "--vscode-editorOverviewRuler-inlineChatInserted", + "--vscode-editorOverviewRuler-inlineChatRemoved", "--vscode-editorOverviewRuler-modifiedForeground", "--vscode-editorOverviewRuler-rangeHighlightForeground", "--vscode-editorOverviewRuler-selectionHighlightForeground", @@ -347,6 +352,8 @@ "--vscode-inputValidation-warningBackground", "--vscode-inputValidation-warningBorder", "--vscode-inputValidation-warningForeground", + "--vscode-interactive-activeCodeBorder", + "--vscode-interactive-inactiveCodeBorder", "--vscode-keybindingLabel-background", "--vscode-keybindingLabel-border", "--vscode-keybindingLabel-bottomBorder", @@ -358,6 +365,7 @@ "--vscode-list-activeSelectionIconForeground", "--vscode-list-deemphasizedForeground", "--vscode-list-dropBackground", + "--vscode-list-dropBetweenBackground", "--vscode-list-errorForeground", "--vscode-list-filterMatchBackground", "--vscode-list-filterMatchBorder", @@ -424,6 +432,8 @@ "--vscode-minimapSlider-activeBackground", "--vscode-minimapSlider-background", "--vscode-minimapSlider-hoverBackground", + "--vscode-multiDiffEditor-background", + "--vscode-multiDiffEditor-border", "--vscode-multiDiffEditor-headerBackground", "--vscode-notebook-cellBorderColor", "--vscode-notebook-cellEditorBackground", @@ -462,8 +472,6 @@ "--vscode-notificationsWarningIcon-foreground", "--vscode-outputView-background", "--vscode-outputViewStickyScroll-background", - "--vscode-activityBarTop-activeBorder", - "--vscode-activityBarTop-foreground", "--vscode-panel-background", "--vscode-panel-border", "--vscode-panel-dropBorder", @@ -683,7 +691,6 @@ "--vscode-terminalOverviewRuler-findMatchForeground", "--vscode-terminalStickyScroll-background", "--vscode-terminalStickyScrollHover-background", - "--vscode-testing-coverage-lineHeight", "--vscode-testing-coverCountBadgeBackground", "--vscode-testing-coverCountBadgeForeground", "--vscode-testing-coveredBackground", @@ -699,11 +706,14 @@ "--vscode-testing-message-error-lineBackground", "--vscode-testing-message-info-decorationForeground", "--vscode-testing-message-info-lineBackground", + "--vscode-testing-messagePeekBorder", + "--vscode-testing-messagePeekHeaderBackground", "--vscode-testing-peekBorder", "--vscode-testing-peekHeaderBackground", "--vscode-testing-runAction", "--vscode-testing-uncoveredBackground", "--vscode-testing-uncoveredBorder", + "--vscode-testing-uncoveredBranchBackground", "--vscode-testing-uncoveredGutterBackground", "--vscode-textBlockQuote-background", "--vscode-textBlockQuote-border", @@ -736,9 +746,7 @@ "--vscode-widget-border", "--vscode-widget-shadow", "--vscode-window-activeBorder", - "--vscode-window-inactiveBorder", - "--vscode-multiDiffEditor-background", - "--vscode-multiDiffEditor-border" + "--vscode-window-inactiveBorder" ], "others": [ "--background-dark", @@ -815,4 +823,4 @@ "--zoom-factor", "--test-bar-width" ] -} +} \ No newline at end of file From 1a37acbdf9c83ad21f68a48562346980a31ffa1b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 29 Jan 2024 11:08:59 +0100 Subject: [PATCH 0671/1897] macOS window - leave fullscreen state if transition does not happen in 10s (#203670) This should help with issues where upon failed fullscreen restore (e.g. after OS update), you see a window without window controls because we do not render the custom title bar. --- .../electron-main/auxiliaryWindow.ts | 4 +- .../windows/electron-main/windowImpl.ts | 100 +++++++++++------- 2 files changed, 66 insertions(+), 38 deletions(-) diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts index 1730e9a0817..ce498d598b6 100644 --- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts +++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts @@ -32,12 +32,12 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { constructor( private readonly contents: WebContents, @IEnvironmentMainService environmentMainService: IEnvironmentMainService, - @ILogService private readonly logService: ILogService, + @ILogService logService: ILogService, @IConfigurationService configurationService: IConfigurationService, @IStateService stateService: IStateService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService ) { - super(configurationService, stateService, environmentMainService); + super(configurationService, stateService, environmentMainService, logService); // Try to claim window this.tryClaimWindow(); diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index c0ca43e257b..c63139f8668 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -194,12 +194,24 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { if (this.environmentMainService.args['open-devtools'] === true) { win.webContents.openDevTools(); } + + // macOS: Window Fullscreen Transitions + if (isMacintosh) { + this._register(this.onDidEnterFullScreen(() => { + this.joinNativeFullScreenTransition?.complete(true); + })); + + this._register(this.onDidLeaveFullScreen(() => { + this.joinNativeFullScreenTransition?.complete(true); + })); + } } constructor( protected readonly configurationService: IConfigurationService, protected readonly stateService: IStateService, - protected readonly environmentMainService: IEnvironmentMainService + protected readonly environmentMainService: IEnvironmentMainService, + protected readonly logService: ILogService ) { super(); } @@ -333,21 +345,18 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { //#region Fullscreen - // TODO@electron workaround for https://github.com/electron/electron/issues/35360 - // where on macOS the window will report a wrong state for `isFullScreen()` while - // transitioning into and out of native full screen. - protected transientIsNativeFullScreen: boolean | undefined = undefined; - protected joinNativeFullScreenTransition: DeferredPromise | undefined = undefined; + private transientIsNativeFullScreen: boolean | undefined = undefined; + private joinNativeFullScreenTransition: DeferredPromise | undefined = undefined; toggleFullScreen(): void { - this.setFullScreen(!this.isFullScreen); + this.setFullScreen(!this.isFullScreen, false); } - protected setFullScreen(fullscreen: boolean): void { + protected setFullScreen(fullscreen: boolean, fromRestore: boolean): void { // Set fullscreen state if (useNativeFullScreen(this.configurationService)) { - this.setNativeFullScreen(fullscreen); + this.setNativeFullScreen(fullscreen, fromRestore); } else { this.setSimpleFullScreen(fullscreen); } @@ -365,31 +374,56 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { return Boolean(isFullScreen || isSimpleFullScreen); } - private setNativeFullScreen(fullscreen: boolean): void { + private setNativeFullScreen(fullscreen: boolean, fromRestore: boolean): void { const win = this.win; if (win?.isSimpleFullScreen()) { win?.setSimpleFullScreen(false); } - this.doSetNativeFullScreen(fullscreen); + this.doSetNativeFullScreen(fullscreen, fromRestore); } - private doSetNativeFullScreen(fullscreen: boolean): void { + private doSetNativeFullScreen(fullscreen: boolean, fromRestore: boolean): void { if (isMacintosh) { + + // macOS: Electron windows report `false` for `isFullScreen()` for as long + // as the fullscreen transition animation takes place. As such, we need to + // listen to the transition events and carry around an intermediate state + // for knowing if we are in fullscreen or not + // Refs: https://github.com/electron/electron/issues/35360 + this.transientIsNativeFullScreen = fullscreen; - this.joinNativeFullScreenTransition = new DeferredPromise(); - Promise.race([ - this.joinNativeFullScreenTransition.p, - // still timeout after some time in case the transition is unusually slow - // this can easily happen for an OS update where macOS tries to reopen - // previous applications and that can take multiple seconds, probably due - // to security checks. its worth noting that if this takes more than - // 10 seconds, users would see a window that is not-fullscreen but without - // custom titlebar... - timeout(10000) - ]).finally(() => { + + const joinNativeFullScreenTransition = this.joinNativeFullScreenTransition = new DeferredPromise(); + (async () => { + const transitioned = await Promise.race([ + joinNativeFullScreenTransition.p, + timeout(10000).then(() => false) + ]); + + if (this.joinNativeFullScreenTransition !== joinNativeFullScreenTransition) { + return; // another transition was requested later + } + this.transientIsNativeFullScreen = undefined; - }); + this.joinNativeFullScreenTransition = undefined; + + if (!transitioned && fullscreen && fromRestore) { + + // We have seen requests for fullscreen failing eventually after some + // time, for example when an OS update was performed and windows restore. + // In those cases a user would find a window that is not in fullscreen + // but also does not show any custom titlebar (and thus window controls) + // because we think the window is in fullscreen. + // + // As a workaround in that case we emit a warning and leave fullscreen + // so that at least the window controls are back. + + this.logService.warn('window: native macOS fullscreen transition did not happen within 10s from restoring'); + + this._onDidLeaveFullScreen.fire(); + } + })(); } const win = this.win; @@ -399,7 +433,7 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { private setSimpleFullScreen(fullscreen: boolean): void { const win = this.win; if (win?.isFullScreen()) { - this.doSetNativeFullScreen(false); + this.doSetNativeFullScreen(false, false); } win?.setSimpleFullScreen(fullscreen); @@ -486,7 +520,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { constructor( config: IWindowCreationOptions, - @ILogService private readonly logService: ILogService, + @ILogService logService: ILogService, @ILoggerMainService private readonly loggerMainService: ILoggerMainService, @IEnvironmentMainService environmentMainService: IEnvironmentMainService, @IPolicyService private readonly policyService: IPolicyService, @@ -507,7 +541,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { @IStateService stateService: IStateService, @IInstantiationService instantiationService: IInstantiationService ) { - super(configurationService, stateService, environmentMainService); + super(configurationService, stateService, environmentMainService, logService); //#region create browser window { @@ -570,7 +604,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { this._win.maximize(); if (this.windowState.mode === WindowMode.Fullscreen) { - this.setFullScreen(true); + this.setFullScreen(true, true); } // to reduce flicker from the default window size @@ -682,16 +716,10 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { // Window Fullscreen this._register(this.onDidEnterFullScreen(() => { this.sendWhenReady('vscode:enterFullScreen', CancellationToken.None); - - this.joinNativeFullScreenTransition?.complete(); - this.joinNativeFullScreenTransition = undefined; })); this._register(this.onDidLeaveFullScreen(() => { this.sendWhenReady('vscode:leaveFullScreen', CancellationToken.None); - - this.joinNativeFullScreenTransition?.complete(); - this.joinNativeFullScreenTransition = undefined; })); // Handle configuration changes @@ -1384,8 +1412,8 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { return { x, y, width, height }; } - protected override setFullScreen(fullscreen: boolean): void { - super.setFullScreen(fullscreen); + protected override setFullScreen(fullscreen: boolean, fromRestore: boolean): void { + super.setFullScreen(fullscreen, fromRestore); // Events this.sendWhenReady(fullscreen ? 'vscode:enterFullScreen' : 'vscode:leaveFullScreen', CancellationToken.None); From 2027f7003224f659136257a33a9a7d0468bd8cec Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 29 Jan 2024 11:45:18 +0100 Subject: [PATCH 0672/1897] aux window - make `FontMeasurements` and `DevicePixelRatio` per-window aware (#195888) (#203671) --- src/vs/base/browser/browser.ts | 105 +--------------- src/vs/base/browser/dom.ts | 15 ++- src/vs/base/browser/pixelRatio.ts | 114 ++++++++++++++++++ src/vs/base/test/browser/dom.test.ts | 1 + .../editor/browser/config/charWidthReader.ts | 11 +- .../browser/config/editorConfiguration.ts | 10 +- .../editor/browser/config/fontMeasurements.ts | 76 ++++++------ .../colorPicker/browser/colorPickerWidget.ts | 4 +- .../parts/editor/breadcrumbsControl.ts | 4 +- .../browser/parts/editor/editorGroupView.ts | 2 +- .../browser/parts/editor/editorStatus.ts | 2 +- .../browser/parts/editor/editorTabsControl.ts | 2 +- src/vs/workbench/browser/workbench.ts | 9 +- .../contrib/debug/browser/debugToolBar.ts | 14 ++- .../contrib/debug/browser/disassemblyView.ts | 30 +++-- .../browser/diff/notebookDiffEditor.ts | 30 +++-- .../notebook/browser/diff/notebookDiffList.ts | 5 +- .../browser/diff/notebookDiffOverviewRuler.ts | 6 +- .../notebook/browser/notebookEditor.ts | 3 +- .../notebook/browser/notebookEditorWidget.ts | 7 +- .../notebook/browser/notebookOptions.ts | 5 +- .../browser/services/notebookServiceImpl.ts | 5 +- .../browser/view/renderers/cellRenderer.ts | 5 +- .../viewParts/notebookOverviewRuler.ts | 7 +- src/vs/workbench/electron-sandbox/window.ts | 2 +- 25 files changed, 271 insertions(+), 203 deletions(-) create mode 100644 src/vs/base/browser/pixelRatio.ts diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index 5020e44a36b..77f55b943a5 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $window, CodeWindow, mainWindow } from 'vs/base/browser/window'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, markAsSingleton } from 'vs/base/common/lifecycle'; +import { CodeWindow, mainWindow } from 'vs/base/browser/window'; +import { Emitter } from 'vs/base/common/event'; class WindowManager { @@ -67,97 +66,6 @@ class WindowManager { } } -/** - * See https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#monitoring_screen_resolution_or_zoom_level_changes - */ -class DevicePixelRatioMonitor extends Disposable { - - private readonly _onDidChange = this._register(new Emitter()); - readonly onDidChange = this._onDidChange.event; - - private readonly _listener: () => void; - private _mediaQueryList: MediaQueryList | null; - - constructor() { - super(); - - this._listener = () => this._handleChange(true); - this._mediaQueryList = null; - this._handleChange(false); - } - - private _handleChange(fireEvent: boolean): void { - this._mediaQueryList?.removeEventListener('change', this._listener); - - this._mediaQueryList = $window.matchMedia(`(resolution: ${$window.devicePixelRatio}dppx)`); - this._mediaQueryList.addEventListener('change', this._listener); - - if (fireEvent) { - this._onDidChange.fire(); - } - } -} - -class PixelRatioImpl extends Disposable { - - private readonly _onDidChange = this._register(new Emitter()); - readonly onDidChange = this._onDidChange.event; - - private _value: number; - - get value(): number { - return this._value; - } - - constructor() { - super(); - - this._value = this._getPixelRatio(); - - const dprMonitor = this._register(new DevicePixelRatioMonitor()); - this._register(dprMonitor.onDidChange(() => { - this._value = this._getPixelRatio(); - this._onDidChange.fire(this._value); - })); - } - - private _getPixelRatio(): number { - const ctx: any = document.createElement('canvas').getContext('2d'); - const dpr = $window.devicePixelRatio || 1; - const bsr = ctx.webkitBackingStorePixelRatio || - ctx.mozBackingStorePixelRatio || - ctx.msBackingStorePixelRatio || - ctx.oBackingStorePixelRatio || - ctx.backingStorePixelRatio || 1; - return dpr / bsr; - } -} - -class PixelRatioFacade { - - private _pixelRatioMonitor: PixelRatioImpl | null = null; - private _getOrCreatePixelRatioMonitor(): PixelRatioImpl { - if (!this._pixelRatioMonitor) { - this._pixelRatioMonitor = markAsSingleton(new PixelRatioImpl()); - } - return this._pixelRatioMonitor; - } - - /** - * Get the current value. - */ - get value(): number { - return this._getOrCreatePixelRatioMonitor().value; - } - - /** - * Listen for changes. - */ - get onDidChange(): Event { - return this._getOrCreatePixelRatioMonitor().onDidChange; - } -} - export function addMatchMediaChangeListener(targetWindow: Window, query: string | MediaQueryList, callback: (this: MediaQueryList, ev: MediaQueryListEvent) => any): void { if (typeof query === 'string') { query = targetWindow.matchMedia(query); @@ -165,15 +73,6 @@ export function addMatchMediaChangeListener(targetWindow: Window, query: string query.addEventListener('change', callback); } -/** - * Returns the pixel ratio. - * - * This is useful for rendering elements at native screen resolution or for being used as - * a cache key when storing font measurements. Fonts might render differently depending on resolution - * and any measurements need to be discarded for example when a window is moved from a monitor to another. - */ -export const PixelRatio = new PixelRatioFacade(); - /** A zoom index, e.g. 1, 2, 3 */ export function setZoomLevel(zoomLevel: number, targetWindow: Window): void { WindowManager.INSTANCE.setZoomLevel(zoomLevel, targetWindow); diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 6d69e834847..c00faddd1db 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -42,12 +42,21 @@ export const { const windows = new Map(); ensureCodeWindow(mainWindow, 1); - windows.set(mainWindow.vscodeWindowId, { window: mainWindow, disposables: new DisposableStore() }); + const mainWindowRegistration = { window: mainWindow, disposables: new DisposableStore() }; + windows.set(mainWindow.vscodeWindowId, mainWindowRegistration); const onDidRegisterWindow = new event.Emitter(); const onDidUnregisterWindow = new event.Emitter(); const onWillUnregisterWindow = new event.Emitter(); + function getWindowById(windowId: number): IRegisteredCodeWindow | undefined; + function getWindowById(windowId: number | undefined, fallbackToMain: true): IRegisteredCodeWindow; + function getWindowById(windowId: number | undefined, fallbackToMain?: boolean): IRegisteredCodeWindow | undefined { + const window = typeof windowId === 'number' ? windows.get(windowId) : undefined; + + return window ?? (fallbackToMain ? mainWindowRegistration : undefined); + } + return { onDidRegisterWindow: onDidRegisterWindow.event, onWillUnregisterWindow: onWillUnregisterWindow.event, @@ -90,9 +99,7 @@ export const { hasWindow(windowId: number): boolean { return windows.has(windowId); }, - getWindowById(windowId: number): IRegisteredCodeWindow | undefined { - return windows.get(windowId); - }, + getWindowById, getWindow(e: Node | UIEvent | undefined | null): CodeWindow { const candidateNode = e as Node | undefined | null; if (candidateNode?.ownerDocument?.defaultView) { diff --git a/src/vs/base/browser/pixelRatio.ts b/src/vs/base/browser/pixelRatio.ts new file mode 100644 index 00000000000..197a802ff7e --- /dev/null +++ b/src/vs/base/browser/pixelRatio.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getWindowId, onDidUnregisterWindow } from 'vs/base/browser/dom'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, markAsSingleton } from 'vs/base/common/lifecycle'; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#monitoring_screen_resolution_or_zoom_level_changes + */ +class DevicePixelRatioMonitor extends Disposable { + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange = this._onDidChange.event; + + private readonly _listener: () => void; + private _mediaQueryList: MediaQueryList | null; + + constructor(targetWindow: Window) { + super(); + + this._listener = () => this._handleChange(targetWindow, true); + this._mediaQueryList = null; + this._handleChange(targetWindow, false); + } + + private _handleChange(targetWindow: Window, fireEvent: boolean): void { + this._mediaQueryList?.removeEventListener('change', this._listener); + + this._mediaQueryList = targetWindow.matchMedia(`(resolution: ${targetWindow.devicePixelRatio}dppx)`); + this._mediaQueryList.addEventListener('change', this._listener); + + if (fireEvent) { + this._onDidChange.fire(); + } + } +} + +export interface IPixelRatioMonitor { + readonly value: number; + readonly onDidChange: Event; +} + +class PixelRatioMonitorImpl extends Disposable implements IPixelRatioMonitor { + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange = this._onDidChange.event; + + private _value: number; + + get value(): number { + return this._value; + } + + constructor(targetWindow: Window) { + super(); + + this._value = this._getPixelRatio(targetWindow); + + const dprMonitor = this._register(new DevicePixelRatioMonitor(targetWindow)); + this._register(dprMonitor.onDidChange(() => { + this._value = this._getPixelRatio(targetWindow); + this._onDidChange.fire(this._value); + })); + } + + private _getPixelRatio(targetWindow: Window): number { + const ctx: any = document.createElement('canvas').getContext('2d'); + const dpr = targetWindow.devicePixelRatio || 1; + const bsr = ctx.webkitBackingStorePixelRatio || + ctx.mozBackingStorePixelRatio || + ctx.msBackingStorePixelRatio || + ctx.oBackingStorePixelRatio || + ctx.backingStorePixelRatio || 1; + return dpr / bsr; + } +} + +class PixelRatioMonitorFacade { + + private readonly mapWindowIdToPixelRatioMonitor = new Map(); + + private _getOrCreatePixelRatioMonitor(targetWindow: Window): PixelRatioMonitorImpl { + const targetWindowId = getWindowId(targetWindow); + let pixelRatioMonitor = this.mapWindowIdToPixelRatioMonitor.get(targetWindowId); + if (!pixelRatioMonitor) { + pixelRatioMonitor = markAsSingleton(new PixelRatioMonitorImpl(targetWindow)); + this.mapWindowIdToPixelRatioMonitor.set(targetWindowId, pixelRatioMonitor); + + markAsSingleton(Event.once(onDidUnregisterWindow)(({ vscodeWindowId }) => { + if (vscodeWindowId === targetWindowId) { + pixelRatioMonitor?.dispose(); + this.mapWindowIdToPixelRatioMonitor.delete(targetWindowId); + } + })); + } + return pixelRatioMonitor; + } + + getInstance(targetWindow: Window): IPixelRatioMonitor { + return this._getOrCreatePixelRatioMonitor(targetWindow); + } +} + +/** + * Returns the pixel ratio. + * + * This is useful for rendering elements at native screen resolution or for being used as + * a cache key when storing font measurements. Fonts might render differently depending on resolution + * and any measurements need to be discarded for example when a window is moved from a monitor to another. + */ +export const PixelRatio = new PixelRatioMonitorFacade(); diff --git a/src/vs/base/test/browser/dom.test.ts b/src/vs/base/test/browser/dom.test.ts index 601f75d3523..f3ad0d55eff 100644 --- a/src/vs/base/test/browser/dom.test.ts +++ b/src/vs/base/test/browser/dom.test.ts @@ -346,6 +346,7 @@ suite('dom', () => { const windowId = getWindowId(mainWindow); assert.ok(typeof windowId === 'number'); assert.strictEqual(getWindowById(windowId)?.window, mainWindow); + assert.strictEqual(getWindowById(undefined, true).window, mainWindow); assert.strictEqual(hasWindow(windowId), true); assert.strictEqual(isAuxiliaryWindow(mainWindow), false); ensureCodeWindow(mainWindow, 1); diff --git a/src/vs/editor/browser/config/charWidthReader.ts b/src/vs/editor/browser/config/charWidthReader.ts index c2097a6074a..90bafb66284 100644 --- a/src/vs/editor/browser/config/charWidthReader.ts +++ b/src/vs/editor/browser/config/charWidthReader.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $window } from 'vs/base/browser/window'; import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; @@ -46,18 +45,18 @@ class DomCharWidthReader { this._testElements = null; } - public read(): void { + public read(targetWindow: Window): void { // Create a test container with all these test elements this._createDomElements(); // Add the container to the DOM - $window.document.body.appendChild(this._container!); + targetWindow.document.body.appendChild(this._container!); // Read character widths this._readFromDomElements(); // Remove the container from the DOM - $window.document.body.removeChild(this._container!); + targetWindow.document.body.removeChild(this._container!); this._container = null; this._testElements = null; @@ -138,7 +137,7 @@ class DomCharWidthReader { } } -export function readCharWidths(bareFontInfo: BareFontInfo, requests: CharWidthRequest[]): void { +export function readCharWidths(targetWindow: Window, bareFontInfo: BareFontInfo, requests: CharWidthRequest[]): void { const reader = new DomCharWidthReader(bareFontInfo, requests); - reader.read(); + reader.read(targetWindow); } diff --git a/src/vs/editor/browser/config/editorConfiguration.ts b/src/vs/editor/browser/config/editorConfiguration.ts index cf9154ce7d6..06e8339cbc7 100644 --- a/src/vs/editor/browser/config/editorConfiguration.ts +++ b/src/vs/editor/browser/config/editorConfiguration.ts @@ -19,6 +19,8 @@ import { BareFontInfo, FontInfo, IValidatedEditorOptions } from 'vs/editor/commo import { IDimension } from 'vs/editor/common/core/dimension'; import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration'; import { AccessibilitySupport, IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { getWindow, getWindowById } from 'vs/base/browser/dom'; +import { PixelRatio } from 'vs/base/browser/pixelRatio'; export interface IEditorConstructionOptions extends IEditorOptions { /** @@ -48,6 +50,7 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat private _lineNumbersDigitCount: number = 1; private _reservedHeight: number = 0; private _glyphMarginDecorationLaneCount: number = 1; + private _targetWindowId: number; private readonly _computeOptionsMemory: ComputeOptionsMemory = new ComputeOptionsMemory(); /** @@ -72,6 +75,7 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat super(); this.isSimpleWidget = isSimpleWidget; this._containerObserver = this._register(new ElementSizeObserver(container, options.dimension)); + this._targetWindowId = getWindow(container).vscodeWindowId; this._rawOptions = deepCloneAndMigrateOptions(options); this._validatedOptions = EditorOptionsUtil.validateOptions(this._rawOptions); @@ -85,7 +89,7 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat this._register(TabFocus.onDidChangeTabFocus(() => this._recomputeOptions())); this._register(this._containerObserver.onDidChange(() => this._recomputeOptions())); this._register(FontMeasurements.onDidChange(() => this._recomputeOptions())); - this._register(browser.PixelRatio.onDidChange(() => this._recomputeOptions())); + this._register(PixelRatio.getInstance(getWindow(container)).onDidChange(() => this._recomputeOptions())); this._register(this._accessibilityService.onDidChangeScreenReaderOptimized(() => this._recomputeOptions())); } @@ -130,7 +134,7 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat outerWidth: this._containerObserver.getWidth(), outerHeight: this._containerObserver.getHeight(), emptySelectionClipboard: browser.isWebKit || browser.isFirefox, - pixelRatio: browser.PixelRatio.value, + pixelRatio: PixelRatio.getInstance(getWindowById(this._targetWindowId, true).window).value, accessibilitySupport: ( this._accessibilityService.isScreenReaderOptimized() ? AccessibilitySupport.Enabled @@ -140,7 +144,7 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat } protected _readFontInfo(bareFontInfo: BareFontInfo): FontInfo { - return FontMeasurements.readFontInfo(bareFontInfo); + return FontMeasurements.readFontInfo(getWindowById(this._targetWindowId, true).window, bareFontInfo); } public getRawOptions(): IEditorOptions { diff --git a/src/vs/editor/browser/config/fontMeasurements.ts b/src/vs/editor/browser/config/fontMeasurements.ts index 0e984df7df0..4b1ac7e21c9 100644 --- a/src/vs/editor/browser/config/fontMeasurements.ts +++ b/src/vs/editor/browser/config/fontMeasurements.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as browser from 'vs/base/browser/browser'; -import { mainWindow } from 'vs/base/browser/window'; -import { Emitter, Event } from 'vs/base/common/event'; +import { getWindowId } from 'vs/base/browser/dom'; +import { PixelRatio } from 'vs/base/browser/pixelRatio'; +import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { CharWidthRequest, CharWidthRequestType, readCharWidths } from 'vs/editor/browser/config/charWidthReader'; import { EditorFontLigatures } from 'vs/editor/common/config/editorOptions'; @@ -36,18 +36,12 @@ export interface ISerializedFontInfo { export class FontMeasurementsImpl extends Disposable { - private _cache: FontMeasurementsCache; - private _evictUntrustedReadingsTimeout: number; + private readonly _cache = new Map(); + + private _evictUntrustedReadingsTimeout = -1; private readonly _onDidChange = this._register(new Emitter()); - public readonly onDidChange: Event = this._onDidChange.event; - - constructor() { - super(); - - this._cache = new FontMeasurementsCache(); - this._evictUntrustedReadingsTimeout = -1; - } + public readonly onDidChange = this._onDidChange.event; public override dispose(): void { if (this._evictUntrustedReadingsTimeout !== -1) { @@ -61,29 +55,41 @@ export class FontMeasurementsImpl extends Disposable { * Clear all cached font information and trigger a change event. */ public clearAllFontInfos(): void { - this._cache = new FontMeasurementsCache(); + this._cache.clear(); this._onDidChange.fire(); } - private _writeToCache(item: BareFontInfo, value: FontInfo): void { - this._cache.put(item, value); + private _ensureCache(targetWindow: Window): FontMeasurementsCache { + const windowId = getWindowId(targetWindow); + let cache = this._cache.get(windowId); + if (!cache) { + cache = new FontMeasurementsCache(); + this._cache.set(windowId, cache); + } + return cache; + } + + private _writeToCache(targetWindow: Window, item: BareFontInfo, value: FontInfo): void { + const cache = this._ensureCache(targetWindow); + cache.put(item, value); if (!value.isTrusted && this._evictUntrustedReadingsTimeout === -1) { // Try reading again after some time - this._evictUntrustedReadingsTimeout = mainWindow.setTimeout(() => { + this._evictUntrustedReadingsTimeout = targetWindow.setTimeout(() => { this._evictUntrustedReadingsTimeout = -1; - this._evictUntrustedReadings(); + this._evictUntrustedReadings(targetWindow); }, 5000); } } - private _evictUntrustedReadings(): void { - const values = this._cache.getValues(); + private _evictUntrustedReadings(targetWindow: Window): void { + const cache = this._ensureCache(targetWindow); + const values = cache.getValues(); let somethingRemoved = false; for (const item of values) { if (!item.isTrusted) { somethingRemoved = true; - this._cache.remove(item); + cache.remove(item); } } if (somethingRemoved) { @@ -94,15 +100,16 @@ export class FontMeasurementsImpl extends Disposable { /** * Serialized currently cached font information. */ - public serializeFontInfo(): ISerializedFontInfo[] { + public serializeFontInfo(targetWindow: Window): ISerializedFontInfo[] { // Only save trusted font info (that has been measured in this running instance) - return this._cache.getValues().filter(item => item.isTrusted); + const cache = this._ensureCache(targetWindow); + return cache.getValues().filter(item => item.isTrusted); } /** * Restore previously serialized font informations. */ - public restoreFontInfo(savedFontInfos: ISerializedFontInfo[]): void { + public restoreFontInfo(targetWindow: Window, savedFontInfos: ISerializedFontInfo[]): void { // Take all the saved font info and insert them in the cache without the trusted flag. // The reason for this is that a font might have been installed on the OS in the meantime. for (const savedFontInfo of savedFontInfos) { @@ -111,21 +118,22 @@ export class FontMeasurementsImpl extends Disposable { continue; } const fontInfo = new FontInfo(savedFontInfo, false); - this._writeToCache(fontInfo, fontInfo); + this._writeToCache(targetWindow, fontInfo, fontInfo); } } /** * Read font information. */ - public readFontInfo(bareFontInfo: BareFontInfo): FontInfo { - if (!this._cache.has(bareFontInfo)) { - let readConfig = this._actualReadFontInfo(bareFontInfo); + public readFontInfo(targetWindow: Window, bareFontInfo: BareFontInfo): FontInfo { + const cache = this._ensureCache(targetWindow); + if (!cache.has(bareFontInfo)) { + let readConfig = this._actualReadFontInfo(targetWindow, bareFontInfo); if (readConfig.typicalHalfwidthCharacterWidth <= 2 || readConfig.typicalFullwidthCharacterWidth <= 2 || readConfig.spaceWidth <= 2 || readConfig.maxDigitWidth <= 2) { // Hey, it's Bug 14341 ... we couldn't read readConfig = new FontInfo({ - pixelRatio: browser.PixelRatio.value, + pixelRatio: PixelRatio.getInstance(targetWindow).value, fontFamily: readConfig.fontFamily, fontWeight: readConfig.fontWeight, fontSize: readConfig.fontSize, @@ -144,9 +152,9 @@ export class FontMeasurementsImpl extends Disposable { }, false); } - this._writeToCache(bareFontInfo, readConfig); + this._writeToCache(targetWindow, bareFontInfo, readConfig); } - return this._cache.get(bareFontInfo); + return cache.get(bareFontInfo); } private _createRequest(chr: string, type: CharWidthRequestType, all: CharWidthRequest[], monospace: CharWidthRequest[] | null): CharWidthRequest { @@ -156,7 +164,7 @@ export class FontMeasurementsImpl extends Disposable { return result; } - private _actualReadFontInfo(bareFontInfo: BareFontInfo): FontInfo { + private _actualReadFontInfo(targetWindow: Window, bareFontInfo: BareFontInfo): FontInfo { const all: CharWidthRequest[] = []; const monospace: CharWidthRequest[] = []; @@ -192,7 +200,7 @@ export class FontMeasurementsImpl extends Disposable { this._createRequest(monospaceTestChars.charAt(i), CharWidthRequestType.Bold, all, monospace); } - readCharWidths(bareFontInfo, all); + readCharWidths(targetWindow, bareFontInfo, all); const maxDigitWidth = Math.max(digit0.width, digit1.width, digit2.width, digit3.width, digit4.width, digit5.width, digit6.width, digit7.width, digit8.width, digit9.width); @@ -217,7 +225,7 @@ export class FontMeasurementsImpl extends Disposable { } return new FontInfo({ - pixelRatio: browser.PixelRatio.value, + pixelRatio: PixelRatio.getInstance(targetWindow).value, fontFamily: bareFontInfo.fontFamily, fontWeight: bareFontInfo.fontWeight, fontSize: bareFontInfo.fontSize, diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts index d7fc9e51eb6..7688defca5f 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { PixelRatio } from 'vs/base/browser/browser'; +import { PixelRatio } from 'vs/base/browser/pixelRatio'; import * as dom from 'vs/base/browser/dom'; import { GlobalPointerMoveMonitor } from 'vs/base/browser/globalPointerMoveMonitor'; import { Widget } from 'vs/base/browser/ui/widget'; @@ -475,7 +475,7 @@ export class ColorPickerWidget extends Widget implements IEditorHoverColorPicker constructor(container: Node, readonly model: ColorPickerModel, private pixelRatio: number, themeService: IThemeService, standaloneColorPicker: boolean = false) { super(); - this._register(PixelRatio.onDidChange(() => this.layout())); + this._register(PixelRatio.getInstance(dom.getWindow(container)).onDidChange(() => this.layout())); const element = $('.colorpicker-widget'); container.appendChild(element); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 52f7c6d6776..db18c13d461 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -31,7 +31,7 @@ import { IEditorPartOptions, EditorResourceAccessor, SideBySideEditor } from 'vs import { ACTIVE_GROUP, ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; -import { PixelRatio } from 'vs/base/browser/browser'; +import { PixelRatio } from 'vs/base/browser/pixelRatio'; import { ILabelService } from 'vs/platform/label/common/label'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { ITreeNode } from 'vs/base/browser/ui/tree/tree'; @@ -424,7 +424,7 @@ export class BreadcrumbsControl { } const selectListener = picker.onWillPickElement(() => this._contextViewService.hideContextView({ source: this, didPick: true })); - const zoomListener = PixelRatio.onDidChange(() => this._contextViewService.hideContextView({ source: this })); + const zoomListener = PixelRatio.getInstance(dom.getWindow(this.domNode)).onDidChange(() => this._contextViewService.hideContextView({ source: this })); const focusTracker = dom.trackFocus(parent); const blurListener = focusTracker.onDidBlur(() => { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index acd91eb9c33..f0bdfdd5bc9 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -264,7 +264,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.scopedContextKeyService.bufferChangeEvents(() => { const activeEditor = this.activeEditor; - this.resourceContext.set(EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY } ?? null)); + this.resourceContext.set(EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY })); applyAvailableEditorIds(groupActiveEditorAvailableEditorIds, activeEditor, this.editorResolverService); diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 0a8d16e996b..b8f1bc68944 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -561,7 +561,7 @@ class EditorStatus extends Disposable { if (!this.toRender) { this.toRender = changed; - this.delayedRender.value = runAtThisOrScheduleAtNextAnimationFrame(getWindowById(this.targetWindowId)?.window ?? mainWindow, () => { + this.delayedRender.value = runAtThisOrScheduleAtNextAnimationFrame(getWindowById(this.targetWindowId, true).window, () => { this.delayedRender.clear(); const toRender = this.toRender; diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index 315f38ff774..3d202d8d48b 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -402,7 +402,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC protected onTabContextMenu(editor: EditorInput, e: Event, node: HTMLElement): void { // Update contexts based on editor picked and remember previous to restore - this.resourceContext.set(EditorResourceAccessor.getOriginalUri(editor, { supportSideBySide: SideBySideEditor.PRIMARY } ?? null)); + this.resourceContext.set(EditorResourceAccessor.getOriginalUri(editor, { supportSideBySide: SideBySideEditor.PRIMARY })); this.editorPinnedContext.set(this.tabsModel.isPinned(editor)); this.editorIsFirstContext.set(this.tabsModel.isFirst(editor)); this.editorIsLastContext.set(this.tabsModel.isLast(editor)); diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index bce16d5cb77..9ac40779c6e 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { addDisposableListener, runWhenWindowIdle } from 'vs/base/browser/dom'; import { Event, Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event'; import { RunOnceScheduler, timeout } from 'vs/base/common/async'; -import { isFirefox, isSafari, isChrome, PixelRatio } from 'vs/base/browser/browser'; +import { isFirefox, isSafari, isChrome } from 'vs/base/browser/browser'; import { mark } from 'vs/base/common/performance'; import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -42,6 +42,7 @@ import { Layout } from 'vs/workbench/browser/layout'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { mainWindow } from 'vs/base/browser/window'; +import { PixelRatio } from 'vs/base/browser/pixelRatio'; export interface IWorkbenchOptions { @@ -296,18 +297,18 @@ export class Workbench extends Layout { try { const storedFontInfo = JSON.parse(storedFontInfoRaw); if (Array.isArray(storedFontInfo)) { - FontMeasurements.restoreFontInfo(storedFontInfo); + FontMeasurements.restoreFontInfo(mainWindow, storedFontInfo); } } catch (err) { /* ignore */ } } - FontMeasurements.readFontInfo(BareFontInfo.createFromRawSettings(configurationService.getValue('editor'), PixelRatio.value)); + FontMeasurements.readFontInfo(mainWindow, BareFontInfo.createFromRawSettings(configurationService.getValue('editor'), PixelRatio.getInstance(mainWindow).value)); } private storeFontInfo(storageService: IStorageService): void { - const serializedFontInfo = FontMeasurements.serializeFontInfo(); + const serializedFontInfo = FontMeasurements.serializeFontInfo(mainWindow); if (serializedFontInfo) { storageService.store('editorFontInfo', JSON.stringify(serializedFontInfo), StorageScope.APPLICATION, StorageTarget.MACHINE); } diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index e769411e5bb..888e0b12048 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { ActionBar, ActionsOrientation, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -40,6 +39,7 @@ import { EditorTabsMode, IWorkbenchLayoutService, LayoutSettings, Parts } from ' import { Codicon } from 'vs/base/common/codicons'; import { CodeWindow, mainWindow } from 'vs/base/browser/window'; import { clamp } from 'vs/base/common/numbers'; +import { PixelRatio } from 'vs/base/browser/pixelRatio'; const DEBUG_TOOLBAR_POSITION_KEY = 'debug.actionswidgetposition'; const DEBUG_TOOLBAR_Y_KEY = 'debug.actionswidgety'; @@ -61,6 +61,8 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { /** coordinate of the debug toolbar per aux window */ private readonly auxWindowCoordinates = new WeakMap(); + private readonly trackPixelRatioListener = this._register(new MutableDisposable()); + constructor( @INotificationService private readonly notificationService: INotificationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @@ -185,7 +187,6 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { })); this._register(this.layoutService.onDidChangePartVisibility(() => this.setYCoordinate())); - this._register(browser.PixelRatio.onDidChange(() => this.setYCoordinate())); const resizeListener = this._register(new MutableDisposable()); @@ -196,7 +197,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { // `then` clause to avoid any races due to quickly switching windows. this.layoutService.whenActiveContainerStylesLoaded.then(() => { if (this.isBuilt) { - this.layoutService.activeContainer.appendChild(this.$el); + this.doShowInActiveContainer(); this.setCoordinates(); } @@ -311,7 +312,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } if (!this.isBuilt) { this.isBuilt = true; - this.layoutService.activeContainer.appendChild(this.$el); + this.doShowInActiveContainer(); } this.isVisible = true; @@ -319,6 +320,11 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this.setCoordinates(); } + private doShowInActiveContainer(): void { + this.layoutService.activeContainer.appendChild(this.$el); + this.trackPixelRatioListener.value = PixelRatio.getInstance(dom.getWindow(this.$el)).onDidChange(() => this.setYCoordinate()); + } + private hide(): void { this.isVisible = false; dom.hide(this.$el); diff --git a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts index 2464ac056a3..f32915eedf1 100644 --- a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts +++ b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { PixelRatio } from 'vs/base/browser/browser'; -import { $, Dimension, addStandardDisposableListener, append } from 'vs/base/browser/dom'; +import { PixelRatio } from 'vs/base/browser/pixelRatio'; +import { $, Dimension, addStandardDisposableListener, append, getWindowById } from 'vs/base/browser/dom'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { ITableRenderer, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table'; import { binarySearch2 } from 'vs/base/common/arrays'; @@ -82,7 +82,7 @@ export class DisassemblyView extends EditorPane { private static readonly NUM_INSTRUCTIONS_TO_LOAD = 50; // Used in instruction renderer - private _fontInfo: BareFontInfo; + private _fontInfo: BareFontInfo | undefined; private _disassembledInstructions: WorkbenchTable | undefined; private _onDidChangeStackFrame: Emitter; private _previousDebuggingState: State; @@ -104,12 +104,7 @@ export class DisassemblyView extends EditorPane { this._disassembledInstructions = undefined; this._onDidChangeStackFrame = this._register(new Emitter({ leakWarningThreshold: 1000 })); this._previousDebuggingState = _debugService.state; - this._fontInfo = BareFontInfo.createFromRawSettings(_configurationService.getValue('editor'), PixelRatio.value); this._register(_configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('editor')) { - this._fontInfo = BareFontInfo.createFromRawSettings(_configurationService.getValue('editor'), PixelRatio.value); - } - if (e.affectsConfiguration('debug')) { // show/hide source code requires changing height which WorkbenchTable doesn't support dynamic height, thus force a total reload. const newValue = this._configurationService.getValue('debug').disassemblyView.showSourceCode; @@ -123,7 +118,24 @@ export class DisassemblyView extends EditorPane { })); } - get fontInfo() { return this._fontInfo; } + get fontInfo() { + if (!this._fontInfo) { + this._fontInfo = this.createFontInfo(); + + this._register(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('editor')) { + this._fontInfo = this.createFontInfo(); + } + })); + } + + return this._fontInfo; + } + + private createFontInfo() { + const window = getWindowById(this.group?.windowId, true).window; + return BareFontInfo.createFromRawSettings(this._configurationService.getValue('editor'), PixelRatio.getInstance(window).value); + } get currentInstructionAddresses() { return this._debugService.getModel().getSessions(false). diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts index fb2f2652969..2b713787dc0 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts @@ -23,7 +23,7 @@ import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/comm import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; -import { PixelRatio } from 'vs/base/browser/browser'; +import { PixelRatio } from 'vs/base/browser/pixelRatio'; import { CellEditState, ICellOutputViewModel, IDisplayOutputLayoutUpdateRequest, IGenericCellViewModel, IInsetRenderOutput, INotebookEditorCreationOptions, INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { DiffSide, DIFF_CELL_MARGIN, IDiffCellInfo, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; import { Emitter, Event } from 'vs/base/common/event'; @@ -153,11 +153,23 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD super(NotebookTextDiffEditor.ID, telemetryService, themeService, storageService); this._notebookOptions = new NotebookOptions(this.configurationService, notebookExecutionStateService, false); this._register(this._notebookOptions); - const editorOptions = this.configurationService.getValue('editor'); - this._fontInfo = FontMeasurements.readFontInfo(BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.value)); this._revealFirst = true; } + private get fontInfo() { + if (!this._fontInfo) { + this._fontInfo = this.createFontInfo(); + } + + return this._fontInfo; + } + + private createFontInfo() { + const window = DOM.getWindowById(this.group?.windowId, true).window; + const editorOptions = this.configurationService.getValue('editor'); + return FontMeasurements.readFontInfo(window, BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(window).value)); + } + private isOverviewRulerEnabled(): boolean { return this.configurationService.getValue(NotebookSetting.diffOverviewRuler) ?? false; } @@ -256,7 +268,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD NotebookTextDiffList, 'NotebookTextDiff', this._listViewContainer, - this.instantiationService.createInstance(NotebookCellTextDiffListDelegate), + this.instantiationService.createInstance(NotebookCellTextDiffListDelegate, DOM.getWindow(this._listViewContainer)), renderers, this.contextKeyService, { @@ -489,7 +501,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this._modifiedWebview.element.style.left = `calc(50%)`; } _generateFontFamily(): string { - return this._fontInfo?.fontFamily ?? `"SF Mono", Monaco, Menlo, Consolas, "Ubuntu Mono", "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace`; + return this.fontInfo.fontFamily ?? `"SF Mono", Monaco, Menlo, Consolas, "Ubuntu Mono", "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace`; } private async _createOriginalWebview(id: string, viewType: string, resource: URI): Promise { @@ -526,7 +538,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } NotebookTextDiffEditor.prettyChanges(this._model, diffResult.cellsDiff); - const { viewModels, firstChangeIndex } = NotebookTextDiffEditor.computeDiff(this.instantiationService, this.configurationService, this._model, this._eventDispatcher!, diffResult, this._fontInfo); + const { viewModels, firstChangeIndex } = NotebookTextDiffEditor.computeDiff(this.instantiationService, this.configurationService, this._model, this._eventDispatcher!, diffResult, this.fontInfo); const isSame = this._isViewModelTheSame(viewModels); if (!isSame) { @@ -993,7 +1005,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD return { width: this._dimension!.width, height: this._dimension!.height, - fontInfo: this._fontInfo!, + fontInfo: this.fontInfo, scrollHeight: this._list?.getScrollHeight() ?? 0, stickyHeight: 0, }; @@ -1034,7 +1046,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD return { width: this._dimension!.width / 2, height: this._dimension!.height / 2, - fontInfo: this._fontInfo! + fontInfo: this.fontInfo }; } @@ -1042,7 +1054,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD return { width: this._dimension!.width / 2, height: this._dimension!.height / 2, - fontInfo: this._fontInfo! + fontInfo: this.fontInfo }; } else { return this.getLayoutInfo(); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts index bfa29e624ee..11aa9bfb6b1 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts @@ -26,7 +26,7 @@ import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/vie import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; -import { PixelRatio } from 'vs/base/browser/browser'; +import { PixelRatio } from 'vs/base/browser/pixelRatio'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { fixedDiffEditorOptions, fixedEditorOptions } from 'vs/workbench/contrib/notebook/browser/diff/diffCellEditorOptions'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; @@ -35,10 +35,11 @@ export class NotebookCellTextDiffListDelegate implements IListVirtualDelegate('editor'); - this.lineHeight = BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.value).lineHeight; + this.lineHeight = BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(targetWindow).value).lineHeight; } getHeight(element: DiffElementViewModelBase): number { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts index 71032920fcb..eb11f3299a5 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as browser from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; import { createFastDomNode, FastDomNode } from 'vs/base/browser/fastDomNode'; +import { PixelRatio } from 'vs/base/browser/pixelRatio'; import { Color } from 'vs/base/common/color'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { defaultInsertColor, defaultRemoveColor, diffInserted, diffOverviewRulerInserted, diffOverviewRulerRemoved, diffRemoved } from 'vs/platform/theme/common/colorRegistry'; @@ -52,7 +52,7 @@ export class NotebookDiffOverviewRuler extends Themable { this._overviewViewportDomElement.setWidth(width); container.appendChild(this._overviewViewportDomElement.domNode); - this._register(browser.PixelRatio.onDidChange(() => { + this._register(PixelRatio.getInstance(DOM.getWindow(this._domNode.domNode)).onDidChange(() => { this._scheduleRender(); })); @@ -127,7 +127,7 @@ export class NotebookDiffOverviewRuler extends Themable { const layoutInfo = this.notebookEditor.getLayoutInfo(); const height = layoutInfo.height; const contentHeight = this._diffElementViewModels.map(view => view.layoutInfo.totalHeight).reduce((a, b) => a + b, 0); - const ratio = browser.PixelRatio.value; + const ratio = PixelRatio.getInstance(DOM.getWindow(this._domNode.domNode)).value; this._domNode.setWidth(this.width); this._domNode.setHeight(height); this._domNode.domNode.width = this.width * ratio; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index f95c61f73f5..2418917ad3c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -5,7 +5,6 @@ import * as DOM from 'vs/base/browser/dom'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { mainWindow } from 'vs/base/browser/window'; import { IAction, toAction } from 'vs/base/common/actions'; import { timeout } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -213,7 +212,7 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { // we need to hide it before getting a new widget this._widget.value?.onWillHide(); - this._widget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, group, input, undefined, this._pagePosition?.dimension, DOM.getWindowById(group.windowId)?.window ?? mainWindow); + this._widget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, group, input, undefined, this._pagePosition?.dimension, DOM.getWindowById(group.windowId, true).window); if (this._rootElement && this._widget.value!.getDomNode()) { this._rootElement.setAttribute('aria-flowto', this._widget.value!.getDomNode().id || ''); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 576970d7a33..19c6bd8118b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -17,7 +17,6 @@ import 'vs/css!./media/notebookCellOutput'; import 'vs/css!./media/notebookEditorStickyScroll'; import 'vs/css!./media/notebookKernelActionViewItem'; import 'vs/css!./media/notebookOutline'; -import { PixelRatio } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; @@ -102,6 +101,7 @@ import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibil 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'; +import { PixelRatio } from 'vs/base/browser/pixelRatio'; const $ = DOM.$; @@ -579,7 +579,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD private _generateFontInfo(): void { const editorOptions = this.configurationService.getValue('editor'); - this._fontInfo = FontMeasurements.readFontInfo(BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.value)); + const targetWindow = DOM.getWindow(this.getDomNode()); + this._fontInfo = FontMeasurements.readFontInfo(targetWindow, BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(targetWindow).value)); } private _createBody(parent: HTMLElement): void { @@ -890,7 +891,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._register(renderer); }); - this._listDelegate = this.instantiationService.createInstance(NotebookCellListDelegate); + this._listDelegate = this.instantiationService.createInstance(NotebookCellListDelegate, DOM.getWindow(this.getDomNode())); this._register(this._listDelegate); const createNotebookAriaLabel = () => { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts index 7530b8e91f9..2cdbce9fad3 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { PixelRatio } from 'vs/base/browser/browser'; +import { PixelRatio } from 'vs/base/browser/pixelRatio'; +import { $window } from 'vs/base/browser/window'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -317,7 +318,7 @@ export class NotebookOptions extends Disposable { if (lineHeight === 0) { // use editor line height const editorOptions = this.configurationService.getValue('editor'); - const fontInfo = FontMeasurements.readFontInfo(BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.value)); + const fontInfo = FontMeasurements.readFontInfo($window, BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance($window).value)); lineHeight = fontInfo.lineHeight; } else if (lineHeight < minimumLineHeight) { // Values too small to be line heights in pixels are in ems. diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts index 7c0c31448d4..3fbde15c36b 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts @@ -6,7 +6,6 @@ import { localize } from 'vs/nls'; import { toAction } from 'vs/base/common/actions'; import { createErrorWithActions } from 'vs/base/common/errorMessage'; -import { PixelRatio } from 'vs/base/browser/browser'; import { Emitter, Event } from 'vs/base/common/event'; import * as glob from 'vs/base/common/glob'; import { Iterable } from 'vs/base/common/iterator'; @@ -45,6 +44,8 @@ import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/ext import { InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { INotebookDocument, INotebookDocumentService } from 'vs/workbench/services/notebook/common/notebookDocumentService'; +import { $window } from 'vs/base/browser/window'; +import { PixelRatio } from 'vs/base/browser/pixelRatio'; export class NotebookProviderInfoStore extends Disposable { @@ -603,7 +604,7 @@ export class NotebookService extends Disposable implements INotebookService { // there is a `::before` or `::after` text decoration whose position is above or below current line // we at least make sure that the editor top padding is at least one line const editorOptions = this.configurationService.getValue('editor'); - updateEditorTopPadding(BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.value).lineHeight + 2); + updateEditorTopPadding(BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance($window).value).lineHeight + 2); decorationTriggeredAdjustment = true; break; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index cc0be19c3f0..69133337125 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { PixelRatio } from 'vs/base/browser/browser'; +import { PixelRatio } from 'vs/base/browser/pixelRatio'; import * as DOM from 'vs/base/browser/dom'; import { FastDomNode } from 'vs/base/browser/fastDomNode'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; @@ -56,12 +56,13 @@ export class NotebookCellListDelegate extends Disposable implements IListVirtual private readonly lineHeight: number; constructor( + targetWindow: Window, @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); const editorOptions = this.configurationService.getValue('editor'); - this.lineHeight = BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.value).lineHeight; + this.lineHeight = BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(targetWindow).value).lineHeight; } getHeight(element: CellViewModel): number { diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookOverviewRuler.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookOverviewRuler.ts index ce31818ac27..b40af87b805 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookOverviewRuler.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookOverviewRuler.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as browser from 'vs/base/browser/browser'; +import { getWindow } from 'vs/base/browser/dom'; import { createFastDomNode, FastDomNode } from 'vs/base/browser/fastDomNode'; +import { PixelRatio } from 'vs/base/browser/pixelRatio'; import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { INotebookEditorDelegate, NotebookOverviewRulerLane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -25,7 +26,7 @@ export class NotebookOverviewRuler extends Themable { this.layout(); })); - this._register(browser.PixelRatio.onDidChange(() => { + this._register(PixelRatio.getInstance(getWindow(this._domNode.domNode)).onDidChange(() => { this.layout(); })); } @@ -35,7 +36,7 @@ export class NotebookOverviewRuler extends Themable { const layoutInfo = this.notebookEditor.getLayoutInfo(); const scrollHeight = layoutInfo.scrollHeight; const height = layoutInfo.height; - const ratio = browser.PixelRatio.value; + const ratio = PixelRatio.getInstance(getWindow(this._domNode.domNode)).value; this._domNode.setWidth(width); this._domNode.setHeight(height); this._domNode.domNode.width = width * ratio; diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 5c7cb75b085..46fd0515a1f 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -1187,7 +1187,7 @@ class ZoomStatusEntry extends Disposable { private updateZoomLevelLabel(targetWindowId: number): void { if (this.zoomLevelLabel) { - const targetWindow = getWindowById(targetWindowId)?.window ?? mainWindow; + const targetWindow = getWindowById(targetWindowId, true).window; const zoomFactor = Math.round(getZoomFactor(targetWindow) * 100); const zoomLevel = getZoomLevel(targetWindow); From d5d51bae0501c4f6d000ab0bbb7a535270414ee9 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 29 Jan 2024 12:02:35 +0100 Subject: [PATCH 0673/1897] add telemetry for clicking on sparkle (#203674) add telemetry for clicking on sparke --- .../contrib/codeAction/browser/codeAction.ts | 3 ++- .../browser/codeActionController.ts | 25 +++++++++++++++---- .../codeAction/browser/lightBulbWidget.ts | 16 ------------ 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeAction.ts b/src/vs/editor/contrib/codeAction/browser/codeAction.ts index 050639f1a45..fb6ce190c81 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeAction.ts @@ -245,7 +245,8 @@ function getDocumentationFromProvider( export enum ApplyCodeActionReason { OnSave = 'onSave', FromProblemsView = 'fromProblemsView', - FromCodeActions = 'fromCodeActions' + FromCodeActions = 'fromCodeActions', + FromAILightbulb = 'fromAILightbulb' // direct invocation when clicking on the AI lightbulb } export async function applyCodeAction( diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index b91f85e6afd..3fbf1bf476b 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -88,7 +88,7 @@ export class CodeActionController extends Disposable implements IEditorContribut this._lightBulbWidget = new Lazy(() => { const widget = this._editor.getContribution(LightBulbWidget.ID); if (widget) { - this._register(widget.onClick(e => this.showCodeActionList(e.actions, e, { includeDisabledActions: false, fromLightbulb: true }))); + this._register(widget.onClick(e => this.showCodeActionsFromLightbulb(e.actions, e))); } return widget; }); @@ -103,6 +103,21 @@ export class CodeActionController extends Disposable implements IEditorContribut super.dispose(); } + private async showCodeActionsFromLightbulb(actions: CodeActionSet, at: IAnchor | IPosition): Promise { + if (actions.allAIFixes && actions.validActions.length === 1) { + const actionItem = actions.validActions[0]; + const command = actionItem.action.command; + if (command && command.id === 'inlineChat.start') { + if (command.arguments && command.arguments.length >= 1) { + command.arguments[0] = { ...command.arguments[0], autoSend: false }; + } + } + await this._applyCodeAction(actionItem, false, false, ApplyCodeActionReason.FromAILightbulb); + return; + } + await this.showCodeActionList(actions, at, { includeDisabledActions: false, fromLightbulb: true }); + } + public showCodeActions(_trigger: CodeActionTrigger, actions: CodeActionSet, at: IAnchor | IPosition) { return this.showCodeActionList(actions, at, { includeDisabledActions: false, fromLightbulb: false }); } @@ -130,9 +145,9 @@ export class CodeActionController extends Disposable implements IEditorContribut return this._model.trigger(trigger); } - private async _applyCodeAction(action: CodeActionItem, retrigger: boolean, preview: boolean): Promise { + private async _applyCodeAction(action: CodeActionItem, retrigger: boolean, preview: boolean, actionReason: ApplyCodeActionReason): Promise { try { - await this._instantiationService.invokeFunction(applyCodeAction, action, ApplyCodeActionReason.FromCodeActions, { preview, editor: this._editor }); + await this._instantiationService.invokeFunction(applyCodeAction, action, actionReason, { preview, editor: this._editor }); } finally { if (retrigger) { this._trigger({ type: CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.QuickFix, filter: {} }); @@ -172,7 +187,7 @@ export class CodeActionController extends Disposable implements IEditorContribut if (validActionToApply) { try { this._lightBulbWidget.value?.hide(); - await this._applyCodeAction(validActionToApply, false, false); + await this._applyCodeAction(validActionToApply, false, false, ApplyCodeActionReason.FromCodeActions); } finally { actions.dispose(); } @@ -264,7 +279,7 @@ export class CodeActionController extends Disposable implements IEditorContribut const delegate: IActionListDelegate = { onSelect: async (action: CodeActionItem, preview?: boolean) => { - this._applyCodeAction(action, /* retrigger */ true, !!preview); + this._applyCodeAction(action, /* retrigger */ true, !!preview, ApplyCodeActionReason.FromCodeActions); this._actionWidgetService.hide(); currentDecorations.clear(); }, diff --git a/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts index 35c1afcd7a1..42564d79f66 100644 --- a/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts @@ -86,22 +86,6 @@ export class LightBulbWidget extends Disposable implements IContentWidget { return; } - if ( - this.state.actions.allAIFixes - && this.state.actions.validActions.length === 1 - ) { - const action = this.state.actions.validActions[0].action; - const id = action.command?.id; - if (id) { - let args = action.command?.arguments; - if (id === 'inlineChat.start' && args && args.length === 1) { - args = [{ ...args[0], autoSend: false }]; - } - commandService.executeCommand(id, ...(args || [])); - e.preventDefault(); - return; - } - } // Make sure that focus / cursor location is not lost when clicking widget icon this._editor.focus(); e.preventDefault(); From 536960bf36f422da033d8dab32a1eb87856587b8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 29 Jan 2024 12:08:31 +0100 Subject: [PATCH 0674/1897] Original window regrabs focus when using QuickOpen to switch to other window (fix #203111) (#203676) --- src/vs/platform/quickinput/browser/quickInputController.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index f5c2d37135e..d93c12cdcb1 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -183,7 +183,11 @@ export class QuickInputController extends Disposable { })); this._register(list.onLeave(() => { // Defer to avoid the input field reacting to the triggering key. + // TODO@TylerLeonhardt https://github.com/microsoft/vscode/issues/203675 setTimeout(() => { + if (!this.controller) { + return; + } inputBox.setFocus(); if (this.controller instanceof QuickPick && this.controller.canSelectMany) { list.clearFocus(); From c3f7a1ca11a5079b5779e686ba2c753542d27f39 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 29 Jan 2024 12:10:34 +0100 Subject: [PATCH 0675/1897] Added tab drag and drop between tabs indicator --- .../editor/media/multieditortabscontrol.css | 46 ++++- .../parts/editor/multiEditorTabsControl.ts | 165 ++++++++++++------ src/vs/workbench/common/theme.ts | 11 ++ 3 files changed, 159 insertions(+), 63 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css index c826a4f8725..2f244d4136c 100644 --- a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css @@ -220,14 +220,6 @@ padding-right: 5px; /* we need less room when sizing is shrink/fixed */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dragged { - transform: translate3d(0px, 0px, 0px); /* forces tab to be drawn on a separate layer (fixes https://github.com/microsoft/vscode/issues/18733) */ -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dragged-over div { - pointer-events: none; /* prevents cursor flickering (fixes https://github.com/microsoft/vscode/issues/38753) */ -} - .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-left:not(.sticky-compact) { flex-direction: row-reverse; padding-left: 0; @@ -466,3 +458,41 @@ /* When multiple tab bars are visible, only show editor actions for the last tab bar */ display: none; } + +/* Drag and drop target */ + +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.drop-target-left::after , +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.drop-target-right::before { + content: ""; + position: absolute; + top: 0; + height: 100%; + width: 1px; + pointer-events: none; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.drop-target-right::before { + left: 0; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.drop-target-left::after { + right: -1px; /* -1 to connect with drop-target-right */ +} + +/* Make drop target edge cases more visible (wrapped tabs & first/last) */ + +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.last-in-row.drop-target-left:not(:last-child)::after { + right: 1px; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.drop-target-right:first-child:before, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.last-in-row + .tab.drop-target-right::before { + left: 1px; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.last-in-row.drop-target-left::after, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.last-in-row + .tab.drop-target-right::before, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:last-child.drop-target-left::after, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:first-child.drop-target-right::before { + width: 2px; +} diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 9bdf29a3be5..aaacaaceade 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -26,7 +26,7 @@ import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElemen import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { getOrSet } from 'vs/base/common/map'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_BACKGROUND, TAB_HOVER_FOREGROUND, TAB_UNFOCUSED_HOVER_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BORDER, TAB_LAST_PINNED_BORDER } from 'vs/workbench/common/theme'; +import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_BACKGROUND, TAB_HOVER_FOREGROUND, TAB_UNFOCUSED_HOVER_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BORDER, TAB_LAST_PINNED_BORDER, TAB_Drag_And_Drop_Between_Indicator } from 'vs/workbench/common/theme'; import { activeContrastBorder, contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, extractTreeDropData, isWindowDraggedOver } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; @@ -132,6 +132,29 @@ export class MultiEditorTabsControl extends EditorTabsControl { private lastMouseWheelEventTime = 0; private isMouseOverTabs = false; + private _dndDropTarget: { leftElement: HTMLElement | undefined; rightElement: HTMLElement | undefined } | undefined; + private set dndDropTarget(target: { leftElement: HTMLElement | undefined; rightElement: HTMLElement | undefined } | undefined) { + const oldTargets = this._dndDropTarget; + if (oldTargets === target || oldTargets && target && oldTargets.leftElement === target.leftElement && oldTargets.rightElement === target.rightElement) { + return; + } + + const dropClassLeft = 'drop-target-left'; + const dropClassRight = 'drop-target-right'; + + if (oldTargets) { + oldTargets.leftElement?.classList.remove(dropClassLeft); + oldTargets.rightElement?.classList.remove(dropClassRight); + } + + if (target) { + target.leftElement?.classList.add(dropClassLeft); + target.rightElement?.classList.add(dropClassRight); + } + + this._dndDropTarget = target; + } + constructor( parent: HTMLElement, editorPartsView: IEditorPartsView, @@ -335,7 +358,6 @@ export class MultiEditorTabsControl extends EditorTabsControl { // Return if the target is not on the tabs container if (e.target !== tabsContainer) { - this.updateDropFeedback(tabsContainer, false); // fixes https://github.com/microsoft/vscode/issues/52093 return; } @@ -352,18 +374,6 @@ export class MultiEditorTabsControl extends EditorTabsControl { let isLocalDragAndDrop = false; if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { isLocalDragAndDrop = true; - - const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype); - if (Array.isArray(data)) { - const localDraggedEditor = data[0].identifier; - if (this.groupView.id === localDraggedEditor.groupId && this.tabsModel.isLast(localDraggedEditor.editor)) { - if (e.dataTransfer) { - e.dataTransfer.dropEffect = 'none'; - } - - return; - } - } } // Update the dropEffect to "copy" if there is no local data to be dragged because @@ -374,23 +384,23 @@ export class MultiEditorTabsControl extends EditorTabsControl { } } - this.updateDropFeedback(tabsContainer, true); + this.updateDropFeedback(tabsContainer, true, e); }, onDragLeave: e => { - this.updateDropFeedback(tabsContainer, false); + this.updateDropFeedback(tabsContainer, false, e); tabsContainer.classList.remove('scroll'); }, onDragEnd: e => { - this.updateDropFeedback(tabsContainer, false); + this.updateDropFeedback(tabsContainer, false, e); tabsContainer.classList.remove('scroll'); this.onGroupDragEnd(e, lastDragEvent, tabsContainer, isNewWindowOperation); }, onDrop: e => { - this.updateDropFeedback(tabsContainer, false); + this.updateDropFeedback(tabsContainer, false, e); tabsContainer.classList.remove('scroll'); if (e.target === tabsContainer) { @@ -1038,14 +1048,15 @@ export class MultiEditorTabsControl extends EditorTabsControl { if (e.dataTransfer) { e.dataTransfer.effectAllowed = 'copyMove'; + e.dataTransfer.setDragImage(tab, 0, 0); // top left corner of dragged tab set to cursor position } // Apply some datatransfer types to allow for dragging the element outside of the application this.doFillResourceDataTransfers([editor], e, isNewWindowOperation); // Fixes https://github.com/microsoft/vscode/issues/18733 - tab.classList.add('dragged'); - scheduleAtNextAnimationFrame(getWindow(this.parent), () => tab.classList.remove('dragged')); + this.updateDropFeedback(tab.cloneNode(true) as HTMLElement, true, e, tabIndex); + scheduleAtNextAnimationFrame(getWindow(this.parent), () => this.updateDropFeedback(tab, false, e, tabIndex)); }, onDrag: e => { @@ -1053,10 +1064,6 @@ export class MultiEditorTabsControl extends EditorTabsControl { }, onDragEnter: e => { - - // Update class to signal drag operation - tab.classList.add('dragged-over'); - // Return if transfer is unsupported if (!this.isSupportedDropTransfer(e)) { if (e.dataTransfer) { @@ -1066,22 +1073,9 @@ export class MultiEditorTabsControl extends EditorTabsControl { return; } - // Return if dragged editor is the current tab dragged over let isLocalDragAndDrop = false; if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { isLocalDragAndDrop = true; - - const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype); - if (Array.isArray(data)) { - const localDraggedEditor = data[0].identifier; - if (localDraggedEditor.editor === this.tabsModel.getEditorByIndex(tabIndex) && localDraggedEditor.groupId === this.groupView.id) { - if (e.dataTransfer) { - e.dataTransfer.dropEffect = 'none'; - } - - return; - } - } } // Update the dropEffect to "copy" if there is no local data to be dragged because @@ -1092,26 +1086,24 @@ export class MultiEditorTabsControl extends EditorTabsControl { } } - this.updateDropFeedback(tab, true, tabIndex); + this.updateDropFeedback(tab, true, e, tabIndex); }, - onDragOver: (_, dragDuration) => { + onDragOver: (e, dragDuration) => { if (dragDuration >= MultiEditorTabsControl.DRAG_OVER_OPEN_TAB_THRESHOLD) { const draggedOverTab = this.tabsModel.getEditorByIndex(tabIndex); if (draggedOverTab && this.tabsModel.activeEditor !== draggedOverTab) { this.groupView.openEditor(draggedOverTab, { preserveFocus: true }); } } - }, - onDragLeave: () => { - tab.classList.remove('dragged-over'); - this.updateDropFeedback(tab, false, tabIndex); + if (e.dataTransfer?.dropEffect !== 'none') { + this.updateDropFeedback(tab, true, e, tabIndex); + } }, onDragEnd: async e => { - tab.classList.remove('dragged-over'); - this.updateDropFeedback(tab, false, tabIndex); + this.updateDropFeedback(tab, false, e, tabIndex); this.editorTransfer.clearData(DraggedEditorIdentifier.prototype); @@ -1140,10 +1132,29 @@ export class MultiEditorTabsControl extends EditorTabsControl { }, onDrop: e => { - tab.classList.remove('dragged-over'); - this.updateDropFeedback(tab, false, tabIndex); + this.updateDropFeedback(tab, false, e, tabIndex); - this.onDrop(e, tabIndex, tabsContainer); + // compute the target index + let targetIndex = tabIndex; + if (!this.isHeadOfTab(e, tab)) { + targetIndex++; + } + + const editorIdentifiers = this.editorTransfer.getData(DraggedEditorIdentifier.prototype); + if (editorIdentifiers !== undefined) { + + const draggedEditorIdentifier = editorIdentifiers[0].identifier; + const sourceGroup = this.editorPartsView.getGroup(draggedEditorIdentifier.groupId); + if (sourceGroup?.id === this.groupView.id) { + + const editorIndex = sourceGroup.getIndexOfEditor(draggedEditorIdentifier.editor); + if (editorIndex < targetIndex) { + targetIndex--; + } + } + } + + this.onDrop(e, targetIndex, tabsContainer); } })); @@ -1174,14 +1185,18 @@ export class MultiEditorTabsControl extends EditorTabsControl { return false; } - private updateDropFeedback(element: HTMLElement, isDND: boolean, tabIndex?: number): void { + private updateDropFeedback(element: HTMLElement, isDND: boolean, e: DragEvent, tabIndex?: number): void { const isTab = (typeof tabIndex === 'number'); - const editor = typeof tabIndex === 'number' ? this.tabsModel.getEditorByIndex(tabIndex) : undefined; - const isActiveTab = isTab && !!editor && this.tabsModel.isActive(editor); - // Background - const noDNDBackgroundColor = isTab ? this.getColor(isActiveTab ? TAB_ACTIVE_BACKGROUND : TAB_INACTIVE_BACKGROUND) : ''; - element.style.backgroundColor = (isDND ? this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) : noDNDBackgroundColor) || ''; + if (isDND) { + if (isTab) { + this.dndDropTarget = this.computeDropTarget(e, tabIndex, element); + } else { + this.dndDropTarget = { leftElement: element.lastElementChild as HTMLElement, rightElement: undefined }; + } + } else { + this.dndDropTarget = undefined; + } // Outline const activeContrastBorderColor = this.getColor(activeContrastBorder); @@ -1198,6 +1213,34 @@ export class MultiEditorTabsControl extends EditorTabsControl { } } + private isHeadOfTab(e: DragEvent, tab: HTMLElement): boolean { + const rect = tab.getBoundingClientRect(); + const offsetXRelativeToParent = e.clientX - rect.left; + return offsetXRelativeToParent <= rect.width / 2; + } + + private computeDropTarget(e: DragEvent, tabIndex: number, targetTab: HTMLElement): { leftElement: HTMLElement | undefined; rightElement: HTMLElement | undefined } | undefined { + const isHeadOfTab = this.isHeadOfTab(e, targetTab); + const isLastTab = tabIndex === this.tabsModel.count - 1; + const isFirstTab = tabIndex === 0; + + // Before first tab + if (isHeadOfTab && isFirstTab) { + return { leftElement: undefined, rightElement: targetTab }; + } + + // After last tab + if (!isHeadOfTab && isLastTab) { + return { leftElement: targetTab, rightElement: undefined }; + } + + // Between two tabs + const tabBefore = isHeadOfTab ? targetTab.previousElementSibling : targetTab; + const tabAfter = isHeadOfTab ? targetTab : targetTab.nextElementSibling; + + return { leftElement: tabBefore as HTMLElement, rightElement: tabAfter as HTMLElement }; + } + private computeTabLabels(): void { const { labelFormat } = this.groupsView.partOptions; const { verbosity, shortenDuplicates } = this.getLabelConfigFlags(labelFormat); @@ -2044,7 +2087,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { private async onDrop(e: DragEvent, targetTabIndex: number, tabsContainer: HTMLElement): Promise { EventHelper.stop(e, true); - this.updateDropFeedback(tabsContainer, false); + this.updateDropFeedback(tabsContainer, false, e, targetTabIndex); tabsContainer.classList.remove('scroll'); const targetEditorIndex = this.tabsModel instanceof UnstickyEditorGroupModel ? targetTabIndex + this.groupView.stickyCount : targetTabIndex; @@ -2367,4 +2410,16 @@ registerThemingParticipant((theme, collector) => { collector.addRule(makeTabBackgroundRule(adjustedColor, adjustedColorDrag, false, false)); } } + + const tabDndIndicatorColor = theme.getColor(TAB_Drag_And_Drop_Between_Indicator); + if (tabDndIndicatorColor) { + // DnD Feedback + + collector.addRule(` + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.drop-target-left::after, + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.drop-target-right::before { + background-color: ${tabDndIndicatorColor}; + } + `); + } }); diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 4977a08dbce..57c46b4574d 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -182,6 +182,17 @@ export const TAB_UNFOCUSED_HOVER_BORDER = registerColor('tab.unfocusedHoverBorde //#endregion +//#region Tab Drag and Drop Indicator + +export const TAB_Drag_And_Drop_Between_Indicator = registerColor('tab.dragAndDropBetweenIndicator', { + dark: TAB_INACTIVE_FOREGROUND, + light: TAB_INACTIVE_FOREGROUND, + hcDark: activeContrastBorder, + hcLight: activeContrastBorder +}, localize('tabDragAndDropBetweenIndicator', "Indicator between tabs to indicate that a tab can be dropped on the editor group between two tabs. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + +//#endregion + //#region Tab Modified Border export const TAB_ACTIVE_MODIFIED_BORDER = registerColor('tab.activeModifiedBorder', { From 3573b7a310b8855b2e1c791d9e2d6bf32c09ca1e Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 29 Jan 2024 12:14:03 +0100 Subject: [PATCH 0676/1897] :lipstick: --- src/vs/workbench/common/theme.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 57c46b4574d..07737d2d8f8 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -189,7 +189,7 @@ export const TAB_Drag_And_Drop_Between_Indicator = registerColor('tab.dragAndDro light: TAB_INACTIVE_FOREGROUND, hcDark: activeContrastBorder, hcLight: activeContrastBorder -}, localize('tabDragAndDropBetweenIndicator', "Indicator between tabs to indicate that a tab can be dropped on the editor group between two tabs. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabDragAndDropBetweenIndicator', "Indicator between tabs to indicate that a tab can be dropped between two tabs. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); //#endregion From 2ba398845f23ceb00fd46929800f564a655cf52a Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 29 Jan 2024 13:53:28 +0100 Subject: [PATCH 0677/1897] Git - extract toMultiFileDiffEditorUris (#203688) --- extensions/git/src/commands.ts | 36 ++++++++++---------------------- extensions/git/src/repository.ts | 4 ---- extensions/git/src/uri.ts | 14 +++++++++++++ 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 5ff4fc4f4a6..049f38e44e0 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -13,7 +13,7 @@ import { Git, Stash } from './git'; import { Model } from './model'; import { Repository, Resource, ResourceGroupType } from './repository'; import { applyLineChanges, getModifiedRange, intersectDiffWithRange, invertLineChange, toLineRanges } from './staging'; -import { fromGitUri, toGitUri, isGitUri, toMergeUris } from './uri'; +import { fromGitUri, toGitUri, isGitUri, toMergeUris, toMultiFileDiffEditorUris } from './uri'; import { dispose, grep, isDefined, isDescendant, pathEquals, relativePath } from './util'; import { GitTimelineItem } from './timelineProvider'; import { ApiRepository } from './api/api1'; @@ -3734,20 +3734,7 @@ export class CommandCenter { const isChangeUntracked = !!stashUntrackedFiles.find(f => pathEquals(f, change.uri.fsPath)); const modifiedUriRef = !isChangeUntracked ? stash.hash : stashUntrackedFilesParentCommit ?? stash.hash; - switch (change.status) { - case Status.INDEX_ADDED: - resources.push({ originalUri: undefined, modifiedUri: toGitUri(change.uri, modifiedUriRef) }); - break; - case Status.DELETED: - resources.push({ originalUri: toGitUri(change.uri, stashFirstParentCommit), modifiedUri: undefined }); - break; - case Status.INDEX_RENAMED: - resources.push({ originalUri: toGitUri(change.originalUri, stashFirstParentCommit), modifiedUri: toGitUri(change.uri, modifiedUriRef) }); - break; - default: - resources.push({ originalUri: toGitUri(change.uri, stashFirstParentCommit), modifiedUri: toGitUri(change.uri, modifiedUriRef) }); - break; - } + resources.push(toMultiFileDiffEditorUris(change, stashFirstParentCommit, modifiedUriRef)); } commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); @@ -3868,15 +3855,15 @@ export class CommandCenter { } const commit = await repository.getCommit(item.ref); - const commitFiles = await repository.getCommitFiles(item.ref); + const commitParentId = commit.parents.length > 0 ? commit.parents[0] : `${commit.hash}^`; + const changes = await repository.diffBetween(commitParentId, commit.hash); const title = `${item.shortRef} - ${commit.message}`; const multiDiffSourceUri = toGitUri(Uri.file(repository.root), item.ref, { scheme: 'git-commit' }); - const resources: { originalUri: Uri; modifiedUri: Uri }[] = []; - for (const commitFile of commitFiles) { - const commitFileUri = Uri.file(path.join(repository.root, commitFile)); - resources.push({ originalUri: toGitUri(commitFileUri, item.previousRef), modifiedUri: toGitUri(commitFileUri, item.ref) }); + const resources: { originalUri: Uri | undefined; modifiedUri: Uri | undefined }[] = []; + for (const change of changes) { + resources.push(toMultiFileDiffEditorUris(change, item.previousRef, item.ref)); } return { @@ -4073,15 +4060,14 @@ export class CommandCenter { } async _viewChanges(repository: Repository, historyItem: SourceControlHistoryItem, multiDiffSourceUri: Uri, title: string): Promise { - const historyProvider = repository.historyProvider; - const historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined; - const files = await historyProvider.provideHistoryItemChanges(historyItem.id, historyItemParentId); + const historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : `${historyItem.id}^`; + const changes = await repository.diffBetween(historyItemParentId, historyItem.id); - if (files.length === 0) { + if (changes.length === 0) { return; } - const resources = files.map(f => ({ originalUri: f.originalUri, modifiedUri: f.modifiedUri })); + const resources = changes.map(c => toMultiFileDiffEditorUris(c, historyItemParentId, historyItem.id)); await commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); } diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 89bdc37f624..7fa410c615c 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1652,10 +1652,6 @@ export class Repository implements Disposable { return await this.repository.getCommit(ref); } - async getCommitFiles(ref: string): Promise { - return await this.repository.getCommitFiles(ref); - } - async getCommitCount(range: string): Promise<{ ahead: number; behind: number }> { return await this.run(Operation.RevList, () => this.repository.getCommitCount(range)); } diff --git a/extensions/git/src/uri.ts b/extensions/git/src/uri.ts index 3a39bfeaa64..169abd1bc35 100644 --- a/extensions/git/src/uri.ts +++ b/extensions/git/src/uri.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Uri } from 'vscode'; +import { Change, Status } from './api/git'; export interface GitUriParams { path: string; @@ -59,3 +60,16 @@ export function toMergeUris(uri: Uri): { base: Uri; ours: Uri; theirs: Uri } { theirs: toGitUri(uri, ':3'), }; } + +export function toMultiFileDiffEditorUris(change: Change, originalRef: string, modifiedRef: string): { originalUri: Uri | undefined; modifiedUri: Uri | undefined } { + switch (change.status) { + case Status.INDEX_ADDED: + return { originalUri: undefined, modifiedUri: toGitUri(change.uri, modifiedRef) }; + case Status.DELETED: + return { originalUri: toGitUri(change.uri, originalRef), modifiedUri: undefined }; + case Status.INDEX_RENAMED: + return { originalUri: toGitUri(change.originalUri, originalRef), modifiedUri: toGitUri(change.uri, modifiedRef) }; + default: + return { originalUri: toGitUri(change.uri, originalRef), modifiedUri: toGitUri(change.uri, modifiedRef) }; + } +} From 73fe65f903c3adf2a3b4ea4555143cdfa77f4228 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 29 Jan 2024 14:18:27 +0100 Subject: [PATCH 0678/1897] find a better compromise between dogfooding and time-lossing with `defaultGutterClickAction: contextMenu` (#203693) fyi @connor4312 --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 99e495ab781..f200b221b37 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -170,5 +170,5 @@ }, "css.format.spaceAroundSelectorSeparator": true, "inlineChat.mode": "live", - "testing.defaultGutterClickAction": "runWithCoverage", + "testing.defaultGutterClickAction": "contextMenu", } From 54ed9f5982269e49c6b996110faa59fbcab4aa18 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 29 Jan 2024 14:39:15 +0100 Subject: [PATCH 0679/1897] Original window regrabs focus when using QuickOpen to switch to other window (fix #203111) (#203676) (#203695) Aux window: window flickers briefly when changing window focus (fix #200615) --- .../auxiliaryWindow/electron-main/auxiliaryWindow.ts | 5 +++++ src/vs/platform/menubar/electron-main/menubar.ts | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts index ce498d598b6..beb5028ebf4 100644 --- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts +++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts @@ -4,11 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { BrowserWindow, WebContents } from 'electron'; +import { isLinux, isWindows } from 'vs/base/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { ILogService } from 'vs/platform/log/common/log'; import { IStateService } from 'vs/platform/state/node/state'; +import { hasNativeTitlebar } from 'vs/platform/window/common/window'; import { IBaseWindow } from 'vs/platform/window/electron-main/window'; import { BaseWindow } from 'vs/platform/windows/electron-main/windowImpl'; @@ -61,6 +63,9 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { // Disable Menu window.setMenu(null); + if ((isWindows || isLinux) && hasNativeTitlebar(this.configurationService)) { + window.setAutoHideMenuBar(true); // Fix for https://github.com/microsoft/vscode/issues/200615 + } // Lifecycle this.lifecycleMainService.registerAuxWindow(this); diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index ec33007b544..2ab5bcecbfa 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -379,6 +379,13 @@ export class Menubar { // Setting the application menu sets it to all opened windows, // but we currently do not support a menu in auxiliary windows, // so we need to unset it there. + // + // This is a bit ugly but `setApplicationMenu()` has some nice + // behaviour we want: + // - on macOS it is required because menus are application set + // - we use `getApplicationMenu()` to access the current state + // - new windows immediately get the same menu when opening + // reducing overall flicker for these Menu.setApplicationMenu(menu); From 30ee2ec96794121ebb886ae6a208c2c634844d55 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 29 Jan 2024 14:49:06 +0100 Subject: [PATCH 0680/1897] :lipstick: --- .../workbench/browser/parts/editor/multiEditorTabsControl.ts | 4 ++-- src/vs/workbench/common/theme.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index aaacaaceade..559248e45f8 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -26,7 +26,7 @@ import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElemen import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { getOrSet } from 'vs/base/common/map'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_BACKGROUND, TAB_HOVER_FOREGROUND, TAB_UNFOCUSED_HOVER_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BORDER, TAB_LAST_PINNED_BORDER, TAB_Drag_And_Drop_Between_Indicator } from 'vs/workbench/common/theme'; +import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_BACKGROUND, TAB_HOVER_FOREGROUND, TAB_UNFOCUSED_HOVER_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BORDER, TAB_LAST_PINNED_BORDER, TAB_DRAG_AND_DROP_BETWEEEN_INDICATOR } from 'vs/workbench/common/theme'; import { activeContrastBorder, contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, extractTreeDropData, isWindowDraggedOver } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; @@ -2411,7 +2411,7 @@ registerThemingParticipant((theme, collector) => { } } - const tabDndIndicatorColor = theme.getColor(TAB_Drag_And_Drop_Between_Indicator); + const tabDndIndicatorColor = theme.getColor(TAB_DRAG_AND_DROP_BETWEEEN_INDICATOR); if (tabDndIndicatorColor) { // DnD Feedback diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 07737d2d8f8..58dc1f8e569 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -184,7 +184,7 @@ export const TAB_UNFOCUSED_HOVER_BORDER = registerColor('tab.unfocusedHoverBorde //#region Tab Drag and Drop Indicator -export const TAB_Drag_And_Drop_Between_Indicator = registerColor('tab.dragAndDropBetweenIndicator', { +export const TAB_DRAG_AND_DROP_BETWEEEN_INDICATOR = registerColor('tab.dragAndDropBetweenIndicator', { dark: TAB_INACTIVE_FOREGROUND, light: TAB_INACTIVE_FOREGROUND, hcDark: activeContrastBorder, From 7a30d301c3e016a7cc3a2e5df9b429e78bc1e2a5 Mon Sep 17 00:00:00 2001 From: Jannik Lehmann <6465434+jnnklhmnn@users.noreply.github.com> Date: Mon, 29 Jan 2024 15:13:24 +0100 Subject: [PATCH 0681/1897] Introduce Collapse All Action to Loaded Scripts (#203560) * Introduce Collapse All Action to Loaded Scripts Fixes: https://github.com/microsoft/vscode/issues/64314 * Remove unneeded precondition * removed unneeded imports --- .../debug/browser/loadedScriptsView.ts | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 6666997525c..ca8ab62b7d9 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -6,16 +6,16 @@ import * as nls from 'vs/nls'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { normalize, isAbsolute, posix } from 'vs/base/common/path'; -import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; +import { ViewPane, ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; -import { IDebugSession, IDebugService, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugSession, IDebugService, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE, LOADED_SCRIPTS_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { normalizeDriveLetter, tildify } from 'vs/base/common/labels'; import { isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; @@ -34,6 +34,9 @@ import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugCon import { ILabelService } from 'vs/platform/label/common/label'; import type { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import type { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; +import { registerAction2, MenuId } from 'vs/platform/actions/common/actions'; +import { Codicon } from 'vs/base/common/codicons'; + import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -622,6 +625,10 @@ export class LoadedScriptsView extends ViewPane { this.tree.layout(height, width); } + collapseAll(): void { + this.tree.collapseAll(); + } + override dispose(): void { dispose(this.tree); dispose(this.treeLabels); @@ -764,3 +771,24 @@ class LoadedScriptsFilter implements ITreeFilter { return TreeVisibility.Recurse; } } +registerAction2(class Collapse extends ViewAction { + constructor() { + super({ + id: 'loadedScripts.collapse', + viewId: LOADED_SCRIPTS_VIEW_ID, + title: nls.localize('collapse', "Collapse All"), + f1: false, + icon: Codicon.collapseAll, + menu: { + id: MenuId.ViewTitle, + order: 30, + group: 'navigation', + when: ContextKeyExpr.equals('view', LOADED_SCRIPTS_VIEW_ID) + } + }); + } + + runInView(_accessor: ServicesAccessor, view: LoadedScriptsView) { + view.collapseAll(); + } +}); From 6e4746002215e19367cd5821b5fc0e90e32e5f70 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 29 Jan 2024 15:21:30 +0100 Subject: [PATCH 0682/1897] remove logging control manifest (#203700) --- .../common/abstractExtensionManagementService.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 6c3b902e362..f3d243e816f 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -734,12 +734,10 @@ export abstract class AbstractExtensionManagementService extends Disposable impl private async updateControlCache(): Promise { try { - this.logService.trace('ExtensionManagementService.refreshReportedCache'); - const manifest = await this.galleryService.getExtensionsControlManifest(); - this.logService.trace(`ExtensionManagementService.refreshControlCache`, manifest); - return manifest; + this.logService.trace('ExtensionManagementService.updateControlCache'); + return await this.galleryService.getExtensionsControlManifest(); } catch (err) { - this.logService.trace('ExtensionManagementService.refreshControlCache - failed to get extension control manifest'); + this.logService.trace('ExtensionManagementService.refreshControlCache - failed to get extension control manifest', getErrorMessage(err)); return { malicious: [], deprecated: {}, search: [] }; } } From 3fe7086daf3f85c4d86d1a8ac285ff2510dffb92 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 29 Jan 2024 15:39:31 +0100 Subject: [PATCH 0683/1897] Fix Drag and Drop Issue in Open Editors (#203701) fix #203696 --- src/vs/base/browser/ui/list/listView.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 5465520c08c..bd76a97ec87 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -24,6 +24,7 @@ import { IObservableValue } from 'vs/base/common/observableValue'; import { BugIndicatingError } from 'vs/base/common/errors'; import { AriaRole } from 'vs/base/browser/ui/aria/aria'; import { ScrollableElementChangeOptions } from 'vs/base/browser/ui/scrollbar/scrollableElementOptions'; +import { clamp } from 'vs/base/common/numbers'; interface IItem { readonly id: string; @@ -1371,7 +1372,8 @@ export class ListView implements IListView { } const relativePosition = browserEvent.offsetY / this.items[targetIndex].size; - return Math.floor(relativePosition / 0.25); + const sector = Math.floor(relativePosition / 0.25); + return clamp(sector, 0, 3); } private getItemIndexFromEventTarget(target: EventTarget | null): number | undefined { From c15e48bc2770b275aabd776e67eb46310bdf1ebe Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 29 Jan 2024 12:51:06 -0300 Subject: [PATCH 0684/1897] Reenable chat test (#203706) I think this isn't an issue with the test- every time it fails, we see "The Web Worker Extension Host did not start in 60s". The chat tests are the first tests, and so they just time out due to being affected by the slow startup. That EH timeout issue looks old. Fix #203429 --- extensions/vscode-api-tests/package.json | 1 - .../src/singlefolder-tests/chat.test.ts | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index fc973ba6541..5fe2c956670 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -7,7 +7,6 @@ "enabledApiProposals": [ "authSession", "chatAgents2", - "chatVariables", "contribViewsRemote", "contribStatusBarItems", "createFileSystemWatcher", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index f0a3a304064..38be992edae 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -8,7 +8,7 @@ import 'mocha'; import { CancellationToken, chat, ChatAgentRequest, ChatVariableLevel, Disposable, interactive, InteractiveSession, ProviderResult } from 'vscode'; import { assertNoRpc, closeAllEditors, DeferredPromise, disposeAll } from '../utils'; -suite.skip('chat', () => { +suite('chat', () => { let disposables: Disposable[] = []; setup(() => { @@ -48,9 +48,9 @@ suite.skip('chat', () => { test('agent and slash command', async () => { const deferred = getDeferredForRequest(); interactive.sendInteractiveRequestToProvider('provider', { message: '@agent /hello friend' }); - const lastResult = await deferred.p; - assert.deepStrictEqual(lastResult.slashCommand, { name: 'hello', description: 'Hello' }); - assert.strictEqual(lastResult.prompt, 'friend'); + const request = await deferred.p; + assert.deepStrictEqual(request.subCommand, 'hello'); + assert.strictEqual(request.prompt, 'friend'); }); test('agent and variable', async () => { @@ -62,8 +62,8 @@ suite.skip('chat', () => { const deferred = getDeferredForRequest(); interactive.sendInteractiveRequestToProvider('provider', { message: '@agent hi #myVar' }); - const lastResult = await deferred.p; - assert.strictEqual(lastResult.prompt, 'hi [#myVar](values:myVar)'); - assert.strictEqual(lastResult.variables['myVar'][0].value, 'myValue'); + const request = await deferred.p; + assert.strictEqual(request.prompt, 'hi [#myVar](values:myVar)'); + assert.strictEqual(request.variables['myVar'][0].value, 'myValue'); }); }); From 88f74b7713b44988a74249929d4fe1259362d831 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 29 Jan 2024 16:54:22 +0100 Subject: [PATCH 0685/1897] ext host - log error when attempting to open/show a document of invalid remote URI (#199844) (#203594) * ext host - log error when attempting to open/show a document of invalid remote URI (#199844) * use `extHostApiDeprecation` --- src/vs/workbench/api/common/extHost.api.impl.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index d3982e71c82..18cef92d5fb 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -675,6 +675,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostTerminalService.terminals; }, async showTextDocument(documentOrUri: vscode.TextDocument | vscode.Uri, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions, preserveFocus?: boolean): Promise { + if (URI.isUri(documentOrUri) && documentOrUri.scheme === Schemas.vscodeRemote && !documentOrUri.authority) { + extHostApiDeprecation.report('workspace.showTextDocument', extension, `A URI of 'vscode-remote' scheme requires an authority.`); + } const document = await (URI.isUri(documentOrUri) ? Promise.resolve(workspace.openTextDocument(documentOrUri)) : Promise.resolve(documentOrUri)); @@ -1004,6 +1007,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } return uriPromise.then(uri => { + if (uri.scheme === Schemas.vscodeRemote && !uri.authority) { + extHostApiDeprecation.report('workspace.openTextDocument', extension, `A URI of 'vscode-remote' scheme requires an authority.`); + } return extHostDocuments.ensureDocumentData(uri).then(documentData => { return documentData.document; }); From 837b74c1b499db59bfd203b160b5cdf816740551 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 29 Jan 2024 17:27:24 +0100 Subject: [PATCH 0686/1897] SCM - history item group context menu (#203712) --- extensions/git/package.json | 19 +++++++++++++++++++ src/vs/platform/actions/common/actions.ts | 2 ++ src/vs/workbench/contrib/scm/browser/menus.ts | 12 ++++++++++++ .../contrib/scm/browser/scmViewPane.ts | 15 ++++++++++++--- .../workbench/contrib/scm/common/history.ts | 2 ++ .../actions/common/menusExtensionPoint.ts | 12 ++++++++++++ ...tribSourceControlHistoryItemGroupMenu.d.ts | 2 ++ 7 files changed, 61 insertions(+), 3 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 4017a8618ed..874588dd9aa 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1857,6 +1857,18 @@ "when": "scmProvider == git" } ], + "scm/incomingChanges/context": [ + { + "command": "git.fetchRef", + "group": "1_modification@1", + "when": "scmProvider == git" + }, + { + "command": "git.pullRef", + "group": "1_modification@2", + "when": "scmProvider == git" + } + ], "scm/incomingChanges/allChanges/context": [ { "command": "git.viewAllChanges", @@ -1878,6 +1890,13 @@ "when": "scmProvider == git" } ], + "scm/outgoingChanges/context": [ + { + "command": "git.pushRef", + "group": "1_modification@1", + "when": "scmProvider == git" + } + ], "scm/outgoingChanges/allChanges/context": [ { "command": "git.viewAllChanges", diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index ece50e4de49..0dceffcaaaa 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -108,7 +108,9 @@ export class MenuId { static readonly ProblemsPanelContext = new MenuId('ProblemsPanelContext'); static readonly SCMInputBox = new MenuId('SCMInputBox'); static readonly SCMIncomingChanges = new MenuId('SCMIncomingChanges'); + static readonly SCMIncomingChangesContext = new MenuId('SCMIncomingChangesContext'); static readonly SCMOutgoingChanges = new MenuId('SCMOutgoingChanges'); + static readonly SCMOutgoingChangesContext = new MenuId('SCMOutgoingChangesContext'); static readonly SCMIncomingChangesAllChangesContext = new MenuId('SCMIncomingChangesAllChangesContext'); static readonly SCMIncomingChangesHistoryItemContext = new MenuId('SCMIncomingChangesHistoryItemContext'); static readonly SCMOutgoingChangesAllChangesContext = new MenuId('SCMOutgoingChangesAllChangesContext'); diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index 22997f8c9c9..f79b6050a70 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -261,17 +261,29 @@ export class SCMHistoryProviderMenus implements ISCMHistoryProviderMenus, IDispo private _incomingHistoryItemGroupMenu: IMenu; get incomingHistoryItemGroupMenu(): IMenu { return this._incomingHistoryItemGroupMenu; } + private _incomingHistoryItemGroupContextMenu: IMenu; + get incomingHistoryItemGroupContextMenu(): IMenu { return this._incomingHistoryItemGroupContextMenu; } + private _outgoingHistoryItemGroupMenu: IMenu; get outgoingHistoryItemGroupMenu(): IMenu { return this._outgoingHistoryItemGroupMenu; } + private _outgoingHistoryItemGroupContextMenu: IMenu; + get outgoingHistoryItemGroupContextMenu(): IMenu { return this._outgoingHistoryItemGroupContextMenu; } + constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, @IMenuService private readonly menuService: IMenuService) { this._incomingHistoryItemGroupMenu = this.menuService.createMenu(MenuId.SCMIncomingChanges, this.contextKeyService); this.disposables.add(this._incomingHistoryItemGroupMenu); + this._incomingHistoryItemGroupContextMenu = this.menuService.createMenu(MenuId.SCMIncomingChangesContext, this.contextKeyService); + this.disposables.add(this._incomingHistoryItemGroupContextMenu); + this._outgoingHistoryItemGroupMenu = this.menuService.createMenu(MenuId.SCMOutgoingChanges, this.contextKeyService); this.disposables.add(this._outgoingHistoryItemGroupMenu); + + this._outgoingHistoryItemGroupContextMenu = this.menuService.createMenu(MenuId.SCMOutgoingChangesContext, this.contextKeyService); + this.disposables.add(this._outgoingHistoryItemGroupContextMenu); } getHistoryItemMenu(historyItem: SCMHistoryItemTreeElement): IMenu { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 084d36256e7..cb8145f0120 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -3006,11 +3006,13 @@ export class SCMViewPane extends ViewPane { const element = e.element; let context: any = element; let actions: IAction[] = []; + let actionRunner: IActionRunner = new RepositoryPaneActionRunner(() => this.getSelectedResources()); if (isSCMRepository(element)) { const menus = this.scmViewService.menus.getRepositoryMenus(element.provider); const menu = menus.repositoryContextMenu; context = element.provider; + actionRunner = new RepositoryActionRunner(() => this.getSelectedRepositories()); actions = collectContextMenuActions(menu); } else if (isSCMInput(element) || isSCMActionButton(element)) { // noop @@ -3032,11 +3034,18 @@ export class SCMViewPane extends ViewPane { const menu = menus.getResourceFolderMenu(element.context); actions = collectContextMenuActions(menu); } + } else if (isSCMHistoryItemGroupTreeElement(element)) { + const menus = this.scmViewService.menus.getRepositoryMenus(element.repository.provider); + const menu = element.direction === 'incoming' ? + menus.historyProviderMenu?.incomingHistoryItemGroupContextMenu : + menus.historyProviderMenu?.outgoingHistoryItemGroupContextMenu; + + if (menu) { + actionRunner = new HistoryItemGroupActionRunner(); + createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, actions); + } } - const actionRunner = isSCMRepository(element) ? - new RepositoryActionRunner(() => this.getSelectedRepositories()) : - new RepositoryPaneActionRunner(() => this.getSelectedResources()); actionRunner.onWillRun(() => this.tree.domFocus()); this.contextMenuService.showContextMenu({ diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index cd27ba0c3c1..ff86e3693fc 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -11,7 +11,9 @@ import { ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; export interface ISCMHistoryProviderMenus { readonly incomingHistoryItemGroupMenu: IMenu; + readonly incomingHistoryItemGroupContextMenu: IMenu; readonly outgoingHistoryItemGroupMenu: IMenu; + readonly outgoingHistoryItemGroupContextMenu: IMenu; getHistoryItemMenu(historyItem: SCMHistoryItemTreeElement): IMenu; } diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index d8918e89e62..3a9ff88e5dd 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -162,12 +162,24 @@ const apiMenus: IAPIMenu[] = [ description: localize('menus.incomingChanges', "The Source Control incoming changes menu"), proposed: 'contribSourceControlHistoryItemGroupMenu' }, + { + key: 'scm/incomingChanges/context', + id: MenuId.SCMIncomingChangesContext, + description: localize('menus.incomingChangesContext', "The Source Control incoming changes context menu"), + proposed: 'contribSourceControlHistoryItemGroupMenu' + }, { key: 'scm/outgoingChanges', id: MenuId.SCMOutgoingChanges, description: localize('menus.outgoingChanges', "The Source Control outgoing changes menu"), proposed: 'contribSourceControlHistoryItemGroupMenu' }, + { + key: 'scm/outgoingChanges/context', + id: MenuId.SCMOutgoingChangesContext, + description: localize('menus.outgoingChangesContext', "The Source Control outgoing changes context menu"), + proposed: 'contribSourceControlHistoryItemGroupMenu' + }, { key: 'scm/incomingChanges/allChanges/context', id: MenuId.SCMIncomingChangesAllChangesContext, diff --git a/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemGroupMenu.d.ts b/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemGroupMenu.d.ts index a67c208736e..34315bc14af 100644 --- a/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemGroupMenu.d.ts +++ b/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemGroupMenu.d.ts @@ -4,5 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // empty placeholder declaration for the `scm/incomingChanges`-menu contribution point +// empty placeholder declaration for the `scm/incomingChanges/context`-menu contribution point // empty placeholder declaration for the `scm/outgoingChanges`-menu contribution point +// empty placeholder declaration for the `scm/outgoingChanges/context`-menu contribution point // https://github.com/microsoft/vscode/issues/201997 From 053610ec1c40838dbad7c1778a16fe1a6f18fe80 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 29 Jan 2024 17:42:41 +0100 Subject: [PATCH 0687/1897] Enable tree sticky scroll by default (#203713) * tree sticky scroll on by default * :lipstick: --- src/vs/platform/list/browser/listService.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index b87a1604e7b..17bf065c76e 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -27,7 +27,6 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator, IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; -import product from 'vs/platform/product/common/product'; import { Registry } from 'vs/platform/registry/common/platform'; import { IStyleOverride, defaultFindWidgetStyles, defaultListStyles, getListStyles } from 'vs/platform/theme/browser/defaultStyles'; @@ -1484,7 +1483,7 @@ configurationRegistry.registerConfiguration({ }, [treeStickyScroll]: { type: 'boolean', - default: typeof product.quality === 'string' && product.quality !== 'stable', // only enable as default in insiders + default: true, description: localize('sticky scroll', "Controls whether sticky scrolling is enabled in trees."), }, [treeStickyScrollMaxElements]: { From 4be04d5d8ce07817fa1ffaf6f2ef892513cbd07c Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 29 Jan 2024 17:24:28 +0000 Subject: [PATCH 0688/1897] Fix file uri markdown link pasting (#203377) Fixes #203180 Enables this feature for uris without authorities and also makes sure these uris are not rewritten to relative paths --- .../copyFiles/pasteUrlProvider.ts | 26 +++++++++++++++---- .../src/languageFeatures/copyFiles/shared.ts | 11 +++++++- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts index 995e2a05819..4968f3cb227 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts @@ -7,8 +7,8 @@ import * as vscode from 'vscode'; import { IMdParser } from '../../markdownEngine'; import { ITextDocument } from '../../types/textDocument'; import { Mime } from '../../util/mimes'; -import { createInsertUriListEdit } from './shared'; import { Schemes } from '../../util/schemes'; +import { createInsertUriListEdit } from './shared'; export enum PasteUrlAsMarkdownLink { Always = 'always', @@ -59,7 +59,7 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { return; } - const edit = createInsertUriListEdit(document, ranges, uriText); + const edit = createInsertUriListEdit(document, ranges, uriText, { preserveAbsoluteUris: true }); if (!edit) { return; } @@ -212,8 +212,10 @@ const externalUriSchemes: ReadonlySet = new Set([ export function findValidUriInText(text: string): string | undefined { const trimmedUrlList = text.trim(); - // Uri must consist of a single sequence of characters without spaces - if (!/^\S+$/.test(trimmedUrlList)) { + if ( + !/^\S+$/.test(trimmedUrlList) // Uri must consist of a single sequence of characters without spaces + || !trimmedUrlList.includes(':') // And it must have colon somewhere for the scheme. We will verify the schema again later + ) { return; } @@ -225,7 +227,21 @@ export function findValidUriInText(text: string): string | undefined { return; } - if (!externalUriSchemes.has(uri.scheme.toLowerCase()) || uri.authority.length <= 1) { + // `Uri.parse` is lenient and will return a `file:` uri even for non-uri text such as `abc` + // Make sure that the resolved scheme starts the original text + if (!trimmedUrlList.toLowerCase().startsWith(uri.scheme.toLowerCase() + ':')) { + return; + } + + // Only enable for an allow list of schemes. Otherwise this can be accidentally activated for non-uri text + // such as `c:\abc` or `value:foo` + if (!externalUriSchemes.has(uri.scheme.toLowerCase())) { + return; + } + + // Some part of the uri must not be empty + // This disables the feature for text such as `http:` + if (!uri.authority && uri.path.length < 2 && !uri.query && !uri.fragment) { return; } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts index 7881e5938a5..8bfc9ae2ff5 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts @@ -69,6 +69,7 @@ export function createInsertUriListEdit( document: ITextDocument, ranges: readonly vscode.Range[], urlList: string, + options?: UriListSnippetOptions, ): { edits: vscode.SnippetTextEdit[]; label: string } | undefined { if (!ranges.length) { return; @@ -103,6 +104,7 @@ export function createInsertUriListEdit( const snippet = createUriListSnippet(document.uri, entries, { placeholderText: range.isEmpty ? undefined : document.getText(range), placeholderStartIndex: allRangesAreEmpty ? 1 : placeHolderStartIndex, + ...options, }); if (!snippet) { continue; @@ -134,6 +136,13 @@ interface UriListSnippetOptions { readonly insertAsMedia?: boolean; readonly separator?: string; + + /** + * Prevents uris from being made relative to the document. + * + * This is mostly useful for `file:` uris. + */ + readonly preserveAbsoluteUris?: boolean; } @@ -168,7 +177,7 @@ export function createUriListSnippet( let placeholderIndex = options?.placeholderStartIndex ?? 1; uris.forEach((uri, i) => { - const mdPath = getRelativeMdPath(documentDir, uri.uri) ?? uri.str ?? uri.uri.toString(); + const mdPath = (!options?.preserveAbsoluteUris ? getRelativeMdPath(documentDir, uri.uri) : undefined) ?? uri.str ?? uri.uri.toString(); const ext = URI.Utils.extname(uri.uri).toLowerCase().replace('.', ''); const insertAsMedia = options?.insertAsMedia || (typeof options?.insertAsMedia === 'undefined' && mediaFileExtensions.has(ext)); From e9ce823bf17cb2527f8dc6ad87ea3b4ad6c7cb34 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 29 Jan 2024 17:24:41 +0000 Subject: [PATCH 0689/1897] Fix potential race creating JS/TS temp dir (#203367) Fixes #203335 --- .../src/extension.ts | 2 +- .../src/utils/temp.electron.ts | 35 ++++++------------- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/extensions/typescript-language-features/src/extension.ts b/extensions/typescript-language-features/src/extension.ts index 22fdd25bb71..dee3929baba 100644 --- a/extensions/typescript-language-features/src/extension.ts +++ b/extensions/typescript-language-features/src/extension.ts @@ -88,5 +88,5 @@ export function activate( } export function deactivate() { - fs.rmSync(temp.getInstanceTempDir(), { recursive: true, force: true }); + fs.rmSync(temp.instanceTempDir.value, { recursive: true, force: true }); } diff --git a/extensions/typescript-language-features/src/utils/temp.electron.ts b/extensions/typescript-language-features/src/utils/temp.electron.ts index 886c8f014fe..cf099411246 100644 --- a/extensions/typescript-language-features/src/utils/temp.electron.ts +++ b/extensions/typescript-language-features/src/utils/temp.electron.ts @@ -6,6 +6,7 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; +import { lazy } from './lazy'; function makeRandomHexString(length: number): string { const chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; @@ -17,31 +18,17 @@ function makeRandomHexString(length: number): string { return result; } -const getRootTempDir = (() => { - let dir: string | undefined; - return () => { - if (!dir) { - const filename = `vscode-typescript${process.platform !== 'win32' && process.getuid ? process.getuid() : ''}`; - dir = path.join(os.tmpdir(), filename); - } - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } - return dir; - }; -})(); +const rootTempDir = lazy(() => { + const filename = `vscode-typescript${process.platform !== 'win32' && process.getuid ? process.getuid() : ''}`; + return path.join(os.tmpdir(), filename); +}); -export const getInstanceTempDir = (() => { - let dir: string | undefined; - return () => { - dir ??= path.join(getRootTempDir(), makeRandomHexString(20)); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } - return dir; - }; -})(); +export const instanceTempDir = lazy(() => { + const dir = path.join(rootTempDir.value, makeRandomHexString(20)); + fs.mkdirSync(dir, { recursive: true }); + return dir; +}); export function getTempFile(prefix: string): string { - return path.join(getInstanceTempDir(), `${prefix}-${makeRandomHexString(20)}.tmp`); + return path.join(instanceTempDir.value, `${prefix}-${makeRandomHexString(20)}.tmp`); } From 16d3c2da9c8102ee81bfc594deb4b206dea7611c Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 29 Jan 2024 18:56:49 +0100 Subject: [PATCH 0690/1897] Updated API discussion points (#203723) --- src/vscode-dts/vscode.proposed.debugVisualization.d.ts | 1 + src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts | 3 +++ src/vscode-dts/vscode.proposed.testCoverage.d.ts | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/src/vscode-dts/vscode.proposed.debugVisualization.d.ts b/src/vscode-dts/vscode.proposed.debugVisualization.d.ts index 29a3c22d832..d23bbfe2edd 100644 --- a/src/vscode-dts/vscode.proposed.debugVisualization.d.ts +++ b/src/vscode-dts/vscode.proposed.debugVisualization.d.ts @@ -37,6 +37,7 @@ declare module 'vscode' { * the data provider, it will replace the variable in its tree. * Otherwise, the items will be shown as children of the variable. */ + // @API don't return TreeDataProvider but a reference to it, like its ids visualization?: Command | TreeDataProvider; /** diff --git a/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts b/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts index 405d67671d7..01db687c22c 100644 --- a/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts +++ b/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts @@ -11,6 +11,9 @@ declare module 'vscode' { /** * An optional flag to sort the final results by index of first query match in label. Defaults to true. */ + // @API is a bug that we need this API at all. why do we change the sort order + // when extensions give us a (sorted) array of items? + // @API sortByLabel isn't a great name sortByLabel: boolean; } } diff --git a/src/vscode-dts/vscode.proposed.testCoverage.d.ts b/src/vscode-dts/vscode.proposed.testCoverage.d.ts index db0b52c4731..b7eadce83cf 100644 --- a/src/vscode-dts/vscode.proposed.testCoverage.d.ts +++ b/src/vscode-dts/vscode.proposed.testCoverage.d.ts @@ -26,6 +26,9 @@ declare module 'vscode' { * @param token A cancellation token. * @return Coverage metadata for all files involved in the test. */ + // @API - pass something into the provide method: + // (1) have TestController#coverageProvider: TestCoverageProvider + // (2) pass TestRun into this method provideFileCoverage(token: CancellationToken): ProviderResult; /** @@ -118,6 +121,9 @@ declare module 'vscode' { ); } + // @API are StatementCoverage and BranchCoverage etc really needed + // or is a generic type with a kind-property enough + /** * Contains coverage information for a single statement or line. */ From bc00185b3b8c6557b4d23f66871fa0307f8004aa Mon Sep 17 00:00:00 2001 From: Hans Date: Tue, 30 Jan 2024 02:09:11 +0800 Subject: [PATCH 0691/1897] Fix the broken links to the latest valid links (#184131) --- extensions/notebook-renderers/src/ansi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/notebook-renderers/src/ansi.ts b/extensions/notebook-renderers/src/ansi.ts index 48d280b18b4..ee26d37d1f6 100644 --- a/extensions/notebook-renderers/src/ansi.ts +++ b/extensions/notebook-renderers/src/ansi.ts @@ -26,7 +26,7 @@ export function handleANSIOutput(text: string, linkOptions: LinkOptions): HTMLSp let sequenceFound: boolean = false; // Potentially an ANSI escape sequence. - // See http://ascii-table.com/ansi-escape-sequences.php & https://en.wikipedia.org/wiki/ANSI_escape_code + // See https://www.asciitable.com/ansi-escape-sequences.php & https://en.wikipedia.org/wiki/ANSI_escape_code if (text.charCodeAt(currentPos) === 27 && text.charAt(currentPos + 1) === '[') { const startPos: number = currentPos; From a6470cba11ae567093f61bd13189c134045590e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Mon, 29 Jan 2024 19:19:06 +0100 Subject: [PATCH 0692/1897] Replace map by foreach (#199194) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: replace map by forEach * fix: fix suspicious code --------- Co-authored-by: Loïc Mangeonjean --- src/vs/editor/contrib/find/browser/findController.ts | 7 ++++--- src/vs/editor/contrib/find/browser/findWidget.ts | 7 ++++--- .../workbench/browser/parts/editor/editorGroupWatermark.ts | 2 +- .../contrib/extensions/browser/extensions.contribution.ts | 2 +- src/vs/workbench/contrib/search/browser/searchModel.ts | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/contrib/find/browser/findController.ts b/src/vs/editor/contrib/find/browser/findController.ts index 1c661f651c2..5dd9bcc6fb5 100644 --- a/src/vs/editor/contrib/find/browser/findController.ts +++ b/src/vs/editor/contrib/find/browser/findController.ts @@ -30,6 +30,7 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IThemeService, themeColorFromId } from 'vs/platform/theme/common/themeService'; +import { Selection } from 'vs/editor/common/core/selection'; const SEARCH_STRING_MAX_LENGTH = 524288; @@ -258,8 +259,8 @@ export class CommonFindController extends Disposable implements IEditorContribut this._state.change({ searchScope: null }, true); } else { if (this._editor.hasModel()) { - const selections = this._editor.getSelections(); - selections.map(selection => { + let selections = this._editor.getSelections(); + selections = selections.map(selection => { if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) { selection = selection.setEndPosition( selection.endLineNumber - 1, @@ -270,7 +271,7 @@ export class CommonFindController extends Disposable implements IEditorContribut return selection; } return null; - }).filter(element => !!element); + }).filter((element): element is Selection => !!element); if (selections.length) { this._state.change({ searchScope: selections }, true); diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index 7f675f67e9d..9c20173bd7a 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -43,6 +43,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { isHighContrast } from 'vs/platform/theme/common/theme'; import { assertIsDefined } from 'vs/base/common/types'; import { defaultInputBoxStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { Selection } from 'vs/editor/common/core/selection'; const findSelectionIcon = registerIcon('find-selection', Codicon.selection, nls.localize('findSelectionIcon', 'Icon for \'Find in Selection\' in the editor find widget.')); const findCollapsedIcon = registerIcon('find-collapsed', Codicon.chevronRight, nls.localize('findCollapsedIcon', 'Icon to indicate that the editor find widget is collapsed.')); @@ -1051,8 +1052,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._register(this._toggleSelectionFind.onChange(() => { if (this._toggleSelectionFind.checked) { if (this._codeEditor.hasModel()) { - const selections = this._codeEditor.getSelections(); - selections.map(selection => { + let selections = this._codeEditor.getSelections(); + selections = selections.map(selection => { if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) { selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel()!.getLineMaxColumn(selection.endLineNumber - 1)); } @@ -1060,7 +1061,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL return selection; } return null; - }).filter(element => !!element); + }).filter((element): element is Selection => !!element); if (selections.length) { this._state.change({ searchScope: selections as Range[] }, true); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts b/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts index f2f8c9704f0..b0f71cdc5fc 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts @@ -135,7 +135,7 @@ export class EditorGroupWatermark extends Disposable { const update = () => { clearNode(box); - selected.map(entry => { + selected.forEach(entry => { const keys = this.keybindingService.lookupKeybinding(entry.id); if (!keys) { return; diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 2254a205092..98e056c9005 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -992,7 +992,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi order: 1, }); - EXTENSION_CATEGORIES.map((category, index) => { + EXTENSION_CATEGORIES.forEach((category, index) => { this.registerExtensionAction({ id: `extensions.actions.searchByCategory.${category}`, title: category, diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 9ff6b1d1072..0f280b7f1d0 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -2372,7 +2372,7 @@ function textSearchResultToMatches(rawMatch: ITextSearchMatch, fileMatch: FileMa export function textSearchMatchesToNotebookMatches(textSearchMatches: ITextSearchMatch[], cell: CellMatch): MatchInNotebook[] { const notebookMatches: MatchInNotebook[] = []; - textSearchMatches.map((textSearchMatch) => { + textSearchMatches.forEach((textSearchMatch) => { const previewLines = textSearchMatch.preview.text.split('\n'); if (Array.isArray(textSearchMatch.ranges)) { textSearchMatch.ranges.forEach((r, i) => { From 0d9e0996ddc90f95c4c644035f499c6b1fea6751 Mon Sep 17 00:00:00 2001 From: xiejialong <48614781+tisilent@users.noreply.github.com> Date: Tue, 30 Jan 2024 02:37:16 +0800 Subject: [PATCH 0693/1897] dispose sash (#199081) Co-authored-by: Peng Lyu --- src/vs/editor/contrib/find/browser/findWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index 9c20173bd7a..84a24173b16 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -1204,7 +1204,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._domNode.appendChild(this._closeBtn.domNode); this._domNode.appendChild(replacePart); - this._resizeSash = new Sash(this._domNode, this, { orientation: Orientation.VERTICAL, size: 2 }); + this._resizeSash = this._register(new Sash(this._domNode, this, { orientation: Orientation.VERTICAL, size: 2 })); this._resized = false; let originalWidth = FIND_WIDGET_INITIAL_WIDTH; From a9ba0672ae314fdc62d7bf456ea0e9aafb88d60e Mon Sep 17 00:00:00 2001 From: tomqwpl <42344626+tomqwpl@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:37:27 +0000 Subject: [PATCH 0694/1897] fix: Not populating extension when selecting notebook kernel (#197619) (#197810) When selecting a notebook kernel, prompt was saying "Select kernel from undefined". "Undefined" here was supported to be the extension name. When the quick pick items are generated, they are generated with a value of "label". Nothing else references or populates a property of "source", so seems safe to change the code to reference "label" instead. --- .../browser/viewParts/notebookKernelQuickPickStrategy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts index 9a9ae9e0fdb..5221b50b117 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts @@ -600,7 +600,7 @@ export class KernelPickerMRUStrategy extends KernelPickerStrategyBase { await this._selecteKernel(notebook, selectedKernelPickItem.kernel); return true; } else if (isGroupedKernelsPick(selectedKernelPickItem)) { - await this._selectOneKernel(notebook, selectedKernelPickItem.source, selectedKernelPickItem.kernels); + await this._selectOneKernel(notebook, selectedKernelPickItem.label, selectedKernelPickItem.kernels); return true; } else if (isSourcePick(selectedKernelPickItem)) { // selected explicilty, it should trigger the execution? From fc9dcad098d9f790a14a31b1640e26a9e29bff97 Mon Sep 17 00:00:00 2001 From: Robo Date: Tue, 30 Jan 2024 03:55:17 +0900 Subject: [PATCH 0695/1897] fix: skip server requirements check based on file presence (#203729) --- cli/src/util/prereqs.rs | 23 ++++++++++++++++++++--- resources/server/bin/code-server-linux.sh | 13 ++++++++++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/cli/src/util/prereqs.rs b/cli/src/util/prereqs.rs index b0a9014cbcf..4f2a6ada8bb 100644 --- a/cli/src/util/prereqs.rs +++ b/cli/src/util/prereqs.rs @@ -25,6 +25,7 @@ lazy_static! { } const NIXOS_TEST_PATH: &str = "/etc/NIXOS"; +const SKIP_REQ_FILE: &str = "/tmp/vscode-skip-server-requirements-check"; pub struct PreReqChecker {} @@ -52,13 +53,20 @@ impl PreReqChecker { #[cfg(target_os = "linux")] pub async fn verify(&self) -> Result { - let (is_nixos, gnu_a, gnu_b, or_musl) = tokio::join!( + let (is_nixos, skip_glibc_checks, or_musl) = tokio::join!( check_is_nixos(), - check_glibc_version(), - check_glibcxx_version(), + check_skip_req_file(), check_musl_interpreter() ); + let (gnu_a, gnu_b) = if !skip_glibc_checks { + tokio::join!(check_glibc_version(), check_glibcxx_version()) + } else { + println!("!!! WARNING: Skipping server pre-requisite check !!!"); + println!("!!! Server stability is not guaranteed. Proceed at your own risk. !!!"); + (Ok(()), Ok(())) + }; + if (gnu_a.is_ok() && gnu_b.is_ok()) || is_nixos { return Ok(if cfg!(target_arch = "x86_64") { Platform::LinuxX64 @@ -157,6 +165,15 @@ async fn check_is_nixos() -> bool { fs::metadata(NIXOS_TEST_PATH).await.is_ok() } +/// Do not remove this check. +/// Provides a way to skip the server glibc requirements check from +/// outside the install flow. A system process can create this +/// file before the server is downloaded and installed. +#[allow(dead_code)] +async fn check_skip_req_file() -> bool { + fs::metadata(SKIP_REQ_FILE).await.is_ok() +} + #[allow(dead_code)] async fn check_glibcxx_version() -> Result<(), String> { let mut libstdc_path: Option = None; diff --git a/resources/server/bin/code-server-linux.sh b/resources/server/bin/code-server-linux.sh index c0d6b29f194..e3d96bdadf2 100644 --- a/resources/server/bin/code-server-linux.sh +++ b/resources/server/bin/code-server-linux.sh @@ -9,8 +9,19 @@ esac ROOT="$(dirname "$(dirname "$(readlink -f "$0")")")" +# Do not remove this check. +# Provides a way to skip the server requirements check from +# outside the install flow. A system process can create this +# file before the server is downloaded and installed. +skip_check=0 +if [ -f "/tmp/vscode-skip-server-requirements-check" ]; then + echo "!!! WARNING: Skipping server pre-requisite check !!!" + echo "!!! Server stability is not guaranteed. Proceed at your own risk. !!!" + skip_check=1 +fi + # Check platform requirements -if [ "$(echo "$@" | grep -c -- "--skip-requirements-check")" -eq 0 ]; then +if [ "$(echo "$@" | grep -c -- "--skip-requirements-check")" -eq 0 ] && [ $skip_check -eq 0 ]; then $ROOT/bin/helpers/check-requirements.sh exit_code=$? if [ $exit_code -ne 0 ]; then From c9555f641bce7ff6d6d79d58db1c16f8ca50b484 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 29 Jan 2024 11:10:06 -0800 Subject: [PATCH 0696/1897] Unified go to actions (#201166) * Unified go to actions * Update for labeless and dynamic --- src/vs/platform/actions/common/actions.ts | 1 + .../browser/controller/executeActions.ts | 18 ++-- .../browser/view/cellParts/cellActionView.ts | 67 +++++++++++++- .../viewParts/notebookEditorToolbar.ts | 89 +++++++++++++++---- 4 files changed, 154 insertions(+), 21 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 0dceffcaaaa..78a18824a45 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -171,6 +171,7 @@ export class MenuId { static readonly NotebookCellBetween = new MenuId('NotebookCellBetween'); static readonly NotebookCellListTop = new MenuId('NotebookCellTop'); static readonly NotebookCellExecute = new MenuId('NotebookCellExecute'); + static readonly NotebookCellExecuteGoTo = new MenuId('NotebookCellExecuteGoTo'); static readonly NotebookCellExecutePrimary = new MenuId('NotebookCellExecutePrimary'); static readonly NotebookDiffCellInputTitle = new MenuId('NotebookDiffCellInputTitle'); static readonly NotebookDiffCellMetadataTitle = new MenuId('NotebookDiffCellMetadataTitle'); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index d9c99e9cd17..14af3383e84 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -11,7 +11,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { localize } from 'vs/nls'; -import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -622,13 +622,21 @@ registerAction2(class InterruptNotebook extends CancelNotebook { }); +MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, { + title: localize('revealRunningCellShort', "Go To"), + submenu: MenuId.NotebookCellExecuteGoTo, + group: 'navigation/execute', + order: 20, + icon: ThemeIcon.modify(icons.executingStateIcon, 'spin') +}); + registerAction2(class RevealRunningCellAction extends NotebookAction { constructor() { super({ id: REVEAL_RUNNING_CELL, title: localize('revealRunningCell', "Go to Running Cell"), tooltip: localize('revealRunningCell', "Go to Running Cell"), - shortTitle: localize('revealRunningCellShort', "Go To"), + shortTitle: localize('revealRunningCell', "Go to Running Cell"), precondition: NOTEBOOK_HAS_RUNNING_CELL, menu: [ { @@ -642,7 +650,7 @@ registerAction2(class RevealRunningCellAction extends NotebookAction { order: 0 }, { - id: MenuId.NotebookToolbar, + id: MenuId.NotebookCellExecuteGoTo, when: ContextKeyExpr.and( NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_HAS_RUNNING_CELL, @@ -703,7 +711,7 @@ registerAction2(class RevealLastFailedCellAction extends NotebookAction { id: REVEAL_LAST_FAILED_CELL, title: localize('revealLastFailedCell', "Go to Most Recently Failed Cell"), tooltip: localize('revealLastFailedCell', "Go to Most Recently Failed Cell"), - shortTitle: localize('revealLastFailedCellShort', "Go To"), + shortTitle: localize('revealLastFailedCellShort', "Go to Most Recently Failed Cell"), precondition: NOTEBOOK_LAST_CELL_FAILED, menu: [ { @@ -718,7 +726,7 @@ registerAction2(class RevealLastFailedCellAction extends NotebookAction { order: 0 }, { - id: MenuId.NotebookToolbar, + id: MenuId.NotebookCellExecuteGoTo, when: ContextKeyExpr.and( NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_LAST_CELL_FAILED, diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts index 38e1424f5f3..a50b67ca2b6 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts @@ -5,7 +5,14 @@ import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import * as DOM from 'vs/base/browser/dom'; -import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenuEntryActionViewItemOptions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionProvider } from 'vs/base/browser/ui/dropdown/dropdown'; +import { MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ThemeIcon } from 'vs/base/common/themables'; export class CodiconActionViewItem extends MenuEntryActionViewItem { @@ -35,3 +42,61 @@ export class ActionViewWithLabel extends MenuEntryActionViewItem { } } } +export class UnifiedSubmenuActionView extends SubmenuEntryActionViewItem { + private _actionLabel?: HTMLAnchorElement; + + constructor( + action: SubmenuItemAction, + options: IMenuEntryActionViewItemOptions | undefined, + readonly renderLabel: boolean, + readonly subActionProvider: IActionProvider, + readonly subActionViewItemProvider: IActionViewItemProvider | undefined, + @IKeybindingService _keybindingService: IKeybindingService, + @IContextMenuService _contextMenuService: IContextMenuService, + @IThemeService _themeService: IThemeService + ) { + super(action, options, _keybindingService, _contextMenuService, _themeService); + } + + override render(container: HTMLElement): void { + super.render(container); + container.classList.add('notebook-action-view-item'); + this._actionLabel = document.createElement('a'); + container.appendChild(this._actionLabel); + this.updateLabel(); + } + + protected override updateLabel() { + const actions = this.subActionProvider.getActions(); + if (this._actionLabel) { + const primaryAction = actions[0]; + + if (primaryAction && primaryAction instanceof MenuItemAction) { + const element = this.element; + + if (element && primaryAction.item.icon && ThemeIcon.isThemeIcon(primaryAction.item.icon)) { + const iconClasses = ThemeIcon.asClassNameArray(primaryAction.item.icon); + // remove all classes started with 'codicon-' + element.classList.forEach((cl) => { + if (cl.startsWith('codicon-')) { + element.classList.remove(cl); + } + }); + element.classList.add(...iconClasses); + } + + if (this.renderLabel) { + this._actionLabel.classList.add('notebook-label'); + this._actionLabel.innerText = this._action.label; + this._actionLabel.title = primaryAction.tooltip.length ? primaryAction.tooltip : primaryAction.label; + } + } else { + if (this.renderLabel) { + this._actionLabel.classList.add('notebook-label'); + this._actionLabel.innerText = this._action.label; + this._actionLabel.title = this._action.tooltip.length ? this._action.tooltip : this._action.label; + } + } + } + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts index d7cb7317788..e2972e82e8a 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts @@ -10,7 +10,7 @@ import { IAction, Separator } from 'vs/base/common/actions'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu, IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -21,13 +21,12 @@ import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controll import { NOTEBOOK_EDITOR_ID, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebooKernelActionViewItem } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView'; -import { ActionViewWithLabel } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView'; +import { ActionViewWithLabel, UnifiedSubmenuActionView } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; -import { IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItem, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { disposableTimeout } from 'vs/base/common/async'; -import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { HiddenItemStrategy, IWorkbenchToolBarOptions, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; interface IActionModel { @@ -73,15 +72,28 @@ class WorkbenchAlwaysLabelStrategy implements IActionLayoutStrategy { constructor( readonly notebookEditor: INotebookEditorDelegate, readonly editorToolbar: NotebookEditorWorkbenchToolbar, + readonly goToMenu: IMenu, readonly instantiationService: IInstantiationService) { } - actionProvider(action: IAction): ActionViewItem | undefined { + actionProvider(action: IAction): IActionViewItem | undefined { if (action.id === SELECT_KERNEL_ID) { // this is being disposed by the consumer return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor); } - return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined; + if (action instanceof MenuItemAction) { + return this.instantiationService.createInstance(ActionViewWithLabel, action, undefined); + } + + if (action instanceof SubmenuItemAction && action.item.submenu.id === MenuId.NotebookCellExecuteGoTo.id) { + return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, undefined, true, { + getActions: () => { + return this.goToMenu.getActions().find(([group]) => group === 'navigation/execute')?.[1] ?? []; + } + }, this.actionProvider.bind(this)); + } + + return undefined; } calculateActions(leftToolbarContainerMaxWidth: number): { primaryActions: IAction[]; secondaryActions: IAction[] } { @@ -100,15 +112,32 @@ class WorkbenchNeverLabelStrategy implements IActionLayoutStrategy { constructor( readonly notebookEditor: INotebookEditorDelegate, readonly editorToolbar: NotebookEditorWorkbenchToolbar, + readonly goToMenu: IMenu, readonly instantiationService: IInstantiationService) { } - actionProvider(action: IAction): ActionViewItem | undefined { + actionProvider(action: IAction): IActionViewItem | undefined { if (action.id === SELECT_KERNEL_ID) { // this is being disposed by the consumer return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor); } - return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined; + if (action instanceof MenuItemAction) { + return this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined); + } + + if (action instanceof SubmenuItemAction) { + if (action.item.submenu.id === MenuId.NotebookCellExecuteGoTo.id) { + return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, undefined, false, { + getActions: () => { + return this.goToMenu.getActions().find(([group]) => group === 'navigation/execute')?.[1] ?? []; + } + }, this.actionProvider.bind(this)); + } else { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, undefined); + } + } + + return undefined; } calculateActions(leftToolbarContainerMaxWidth: number): { primaryActions: IAction[]; secondaryActions: IAction[] } { @@ -127,9 +156,10 @@ class WorkbenchDynamicLabelStrategy implements IActionLayoutStrategy { constructor( readonly notebookEditor: INotebookEditorDelegate, readonly editorToolbar: NotebookEditorWorkbenchToolbar, + readonly goToMenu: IMenu, readonly instantiationService: IInstantiationService) { } - actionProvider(action: IAction): ActionViewItem | undefined { + actionProvider(action: IAction): IActionViewItem | undefined { if (action.id === SELECT_KERNEL_ID) { // this is being disposed by the consumer return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor); @@ -137,9 +167,37 @@ class WorkbenchDynamicLabelStrategy implements IActionLayoutStrategy { const a = this.editorToolbar.primaryActions.find(a => a.action.id === action.id); if (!a || a.renderLabel) { - return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined; + if (action instanceof MenuItemAction) { + return this.instantiationService.createInstance(ActionViewWithLabel, action, undefined); + } + + if (action instanceof SubmenuItemAction && action.item.submenu.id === MenuId.NotebookCellExecuteGoTo.id) { + return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, undefined, true, { + getActions: () => { + return this.goToMenu.getActions().find(([group]) => group === 'navigation/execute')?.[1] ?? []; + } + }, this.actionProvider.bind(this)); + } + + return undefined; } else { - return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined; + if (action instanceof MenuItemAction) { + this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined); + } + + if (action instanceof SubmenuItemAction) { + if (action.item.submenu.id === MenuId.NotebookCellExecuteGoTo.id) { + return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, undefined, false, { + getActions: () => { + return this.goToMenu.getActions().find(([group]) => group === 'navigation/execute')?.[1] ?? []; + } + }, this.actionProvider.bind(this)); + } else { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, undefined); + } + } + + return undefined; } } @@ -160,6 +218,7 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { private _notebookTopLeftToolbarContainer!: HTMLElement; private _notebookTopRightToolbarContainer!: HTMLElement; private _notebookGlobalActionsMenu!: IMenu; + private _executeGoToActionsMenu!: IMenu; private _notebookLeftToolbar!: WorkbenchToolBar; private _primaryActions: IActionModel[]; get primaryActions(): IActionModel[] { @@ -251,7 +310,7 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { private _registerNotebookActionsToolbar() { this._notebookGlobalActionsMenu = this._register(this.menuService.createMenu(this.notebookEditor.creationOptions.menuIds.notebookToolbar, this.contextKeyService)); - this._register(this._notebookGlobalActionsMenu); + this._executeGoToActionsMenu = this._register(this.menuService.createMenu(MenuId.NotebookCellExecuteGoTo, this.contextKeyService)); this._useGlobalToolbar = this.notebookOptions.getDisplayOptions().globalToolbar; this._renderLabel = this._convertConfiguration(this.configurationService.getValue(NotebookSetting.globalToolbarShowLabel)); @@ -379,13 +438,13 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { private _updateStrategy() { switch (this._renderLabel) { case RenderLabel.Always: - this._strategy = new WorkbenchAlwaysLabelStrategy(this.notebookEditor, this, this.instantiationService); + this._strategy = new WorkbenchAlwaysLabelStrategy(this.notebookEditor, this, this._executeGoToActionsMenu, this.instantiationService); break; case RenderLabel.Never: - this._strategy = new WorkbenchNeverLabelStrategy(this.notebookEditor, this, this.instantiationService); + this._strategy = new WorkbenchNeverLabelStrategy(this.notebookEditor, this, this._executeGoToActionsMenu, this.instantiationService); break; case RenderLabel.Dynamic: - this._strategy = new WorkbenchDynamicLabelStrategy(this.notebookEditor, this, this.instantiationService); + this._strategy = new WorkbenchDynamicLabelStrategy(this.notebookEditor, this, this._executeGoToActionsMenu, this.instantiationService); break; } } From 0f323440e5e6b8a33b7ec9545fe25c9a7094f8f5 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 29 Jan 2024 11:19:37 -0800 Subject: [PATCH 0697/1897] eng: allow css nesting via postcss plugin (#203726) * eng: allow css nesting via postcss plugin CSS nesting has landed in most browsers at this point, but we don't want to break users who are still stuck on old browser (mainly older iOS devices.) This PR adds a postcss plugin to the build process that de-nests nested CSS. The plugin required a newer version of postcss as well, so I have updated that and a couple other modules to their latest versions. * update build's package.json versions too --- build/lib/compilation.js | 4 + build/lib/compilation.ts | 5 + build/package.json | 3 +- build/yarn.lock | 33 +- package.json | 11 +- .../contrib/testing/browser/media/testing.css | 36 +- yarn.lock | 1114 +++++++---------- 7 files changed, 473 insertions(+), 733 deletions(-) diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 3d243a45232..35bc464d34a 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -55,11 +55,15 @@ function createCompile(src, build, emitError, transpileOnly) { const tsFilter = util.filter(data => /\.ts$/.test(data.path)); const isUtf8Test = (f) => /(\/|\\)test(\/|\\).*utf8/.test(f.path); const isRuntimeJs = (f) => f.path.endsWith('.js') && !f.path.includes('fixtures'); + const isCSS = (f) => f.path.endsWith('.css') && !f.path.includes('fixtures'); const noDeclarationsFilter = util.filter(data => !(/\.d\.ts$/.test(data.path))); + const postcss = require('gulp-postcss'); + const postcssNesting = require('postcss-nesting'); const input = es.through(); const output = input .pipe(util.$if(isUtf8Test, bom())) // this is required to preserve BOM in test files that loose it otherwise .pipe(util.$if(!build && isRuntimeJs, util.appendOwnPathSourceURL())) + .pipe(util.$if(isCSS, postcss([postcssNesting()]))) .pipe(tsFilter) .pipe(util.loadSourcemaps()) .pipe(compilation(token)) diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index 228bca4e6a6..94bfe6e832d 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -64,12 +64,17 @@ function createCompile(src: string, build: boolean, emitError: boolean, transpil const tsFilter = util.filter(data => /\.ts$/.test(data.path)); const isUtf8Test = (f: File) => /(\/|\\)test(\/|\\).*utf8/.test(f.path); const isRuntimeJs = (f: File) => f.path.endsWith('.js') && !f.path.includes('fixtures'); + const isCSS = (f: File) => f.path.endsWith('.css') && !f.path.includes('fixtures'); const noDeclarationsFilter = util.filter(data => !(/\.d\.ts$/.test(data.path))); + const postcss = require('gulp-postcss') as typeof import('gulp-postcss'); + const postcssNesting = require('postcss-nesting'); + const input = es.through(); const output = input .pipe(util.$if(isUtf8Test, bom())) // this is required to preserve BOM in test files that loose it otherwise .pipe(util.$if(!build && isRuntimeJs, util.appendOwnPathSourceURL())) + .pipe(util.$if(isCSS, postcss([postcssNesting()]))) .pipe(tsFilter) .pipe(util.loadSourcemaps()) .pipe(compilation(token)) diff --git a/build/package.json b/build/package.json index 52b1f47a5eb..54c0bc8c586 100644 --- a/build/package.json +++ b/build/package.json @@ -9,7 +9,6 @@ "@electron/get": "^2.0.0", "@types/ansi-colors": "^3.2.0", "@types/byline": "^4.2.32", - "@types/cssnano": "^4.0.0", "@types/debounce": "^1.0.0", "@types/debug": "^4.1.5", "@types/fancy-log": "^1.3.0", @@ -20,7 +19,7 @@ "@types/gulp-filter": "^3.0.32", "@types/gulp-gzip": "^0.0.31", "@types/gulp-json-editor": "^2.2.31", - "@types/gulp-postcss": "^8.0.0", + "@types/gulp-postcss": "^8.0.6", "@types/gulp-rename": "^0.0.33", "@types/gulp-sourcemaps": "^0.0.32", "@types/mime": "0.0.29", diff --git a/build/yarn.lock b/build/yarn.lock index 51ca8c276d0..4d2e6e75730 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -407,13 +407,6 @@ "@types/events" "*" "@types/node" "*" -"@types/cssnano@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/cssnano/-/cssnano-4.0.1.tgz#67fa912753d80973a016e7684a47fedf338aacff" - integrity sha512-hGOroxRTBkYl5gSBRJOffhV4+io+Y2bFX1VP7LgKEVHJt/LPPJaWUIuDAz74Vlp7l7hCDZfaDi7iPxwNwuVA4Q== - dependencies: - postcss "5 - 7" - "@types/debounce@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.0.0.tgz#417560200331e1bb84d72da85391102c2fcd61b7" @@ -491,10 +484,10 @@ "@types/js-beautify" "*" "@types/node" "*" -"@types/gulp-postcss@^8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@types/gulp-postcss/-/gulp-postcss-8.0.0.tgz#f7e86d45e4999fd43e6d8c55b00504c88a67ad61" - integrity sha512-AVgjA03bpkYONKZpzuJviB9PzaNbDzrovYPbenj8/XxivUc35C/dIzJanyaQv7CFqfLLPLsqSalmtP3GLq6iag== +"@types/gulp-postcss@^8.0.6": + version "8.0.6" + resolved "https://registry.yarnpkg.com/@types/gulp-postcss/-/gulp-postcss-8.0.6.tgz#d314c785876c8a1779154ba1d152125082ecde0f" + integrity sha512-mjGEmTvurqRHFeJQnrgtMC9GtKNkI2+56n92zIzff5UFr2jUfilw1elKRxS7bK0FYRvuEcnMX9JH0AUpCxBrpg== dependencies: "@types/node" "*" "@types/vinyl" "*" @@ -2201,15 +2194,6 @@ plugin-error@1.0.1, plugin-error@^1.0.1: arr-union "^3.1.0" extend-shallow "^3.0.2" -"postcss@5 - 7": - version "7.0.36" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.36.tgz#056f8cffa939662a8f5905950c07d5285644dfcb" - integrity sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw== - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" - prebuild-install@^7.0.1, prebuild-install@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" @@ -2439,7 +2423,7 @@ simple-get@^4.0.0: once "^1.3.1" simple-concat "^1.0.0" -source-map@0.6.1, source-map@^0.6.1: +source-map@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -2514,13 +2498,6 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" diff --git a/package.json b/package.json index a79f01cabda..71f3fb7ae95 100644 --- a/package.json +++ b/package.json @@ -110,10 +110,9 @@ "@playwright/test": "^1.40.1", "@swc/core": "1.3.62", "@types/cookie": "^0.3.3", - "@types/cssnano": "^4.0.0", "@types/debug": "^4.1.5", "@types/graceful-fs": "4.1.2", - "@types/gulp-postcss": "^8.0.0", + "@types/gulp-postcss": "^8.0.6", "@types/gulp-svgmin": "^1.2.1", "@types/http-proxy-agent": "^2.0.1", "@types/kerberos": "^1.1.2", @@ -146,8 +145,8 @@ "chromium-pickle-js": "^0.2.0", "cookie": "^0.4.0", "copy-webpack-plugin": "^11.0.0", - "css-loader": "^6.7.3", - "cssnano": "^4.1.11", + "css-loader": "^6.9.1", + "cssnano": "^6.0.3", "debounce": "^1.0.0", "deemon": "^1.8.0", "electron": "27.2.3", @@ -171,7 +170,7 @@ "gulp-gzip": "^1.4.2", "gulp-json-editor": "^2.5.0", "gulp-plumber": "^1.2.0", - "gulp-postcss": "^9.0.0", + "gulp-postcss": "^9.1.0", "gulp-rename": "^1.2.0", "gulp-replace": "^0.5.4", "gulp-sourcemaps": "^3.0.0", @@ -197,6 +196,8 @@ "opn": "^6.0.0", "p-all": "^1.0.0", "path-browserify": "^1.0.1", + "postcss": "^8.4.33", + "postcss-nesting": "^12.0.2", "pump": "^1.0.1", "rcedit": "^1.1.0", "rimraf": "^2.2.8", diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index 9f577c776de..010fd1c5c75 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -5,28 +5,30 @@ /** -- icons */ -.monaco-workbench .codicon-testing-error-icon { - color: var(--vscode-testing-iconErrored); -} +.monaco-workbench { + .codicon-testing-error-icon { + color: var(--vscode-testing-iconErrored); + } -.monaco-workbench .codicon-testing-failed-icon { - color: var(--vscode-testing-iconFailed); -} + .codicon-testing-failed-icon { + color: var(--vscode-testing-iconFailed); + } -.monaco-workbench .codicon-testing-passed-icon { - color: var(--vscode-testing-iconPassed); -} + .codicon-testing-passed-icon { + color: var(--vscode-testing-iconPassed); + } -.monaco-workbench .codicon-testing-queued-icon { - color: var(--vscode-testing-iconQueued); -} + .codicon-testing-queued-icon { + color: var(--vscode-testing-iconQueued); + } -.monaco-workbench .codicon-testing-skipped-icon { - color: var(--vscode-testing-iconSkipped); -} + .codicon-testing-skipped-icon { + color: var(--vscode-testing-iconSkipped); + } -.monaco-workbench .codicon-testing-unset-icon { - color: var(--vscode-testing-iconUnset); + .codicon-testing-unset-icon { + color: var(--vscode-testing-iconUnset); + } } /** -- explorer */ diff --git a/yarn.lock b/yarn.lock index 0fc6389685a..6c82081f225 100644 --- a/yarn.lock +++ b/yarn.lock @@ -377,6 +377,11 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@csstools/selector-specificity@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-3.0.1.tgz#d84597fbc0f897240c12fc0a31e492b036c70e40" + integrity sha512-NPljRHkq4a14YzZ3YD406uaxh7s0g6eAq3L9aLOWywoqe8PkYamAvtsh7KNX6c++ihDrJ0RiU+/z7rGnhlZ5ww== + "@discoveryjs/json-ext@^0.5.0": version "0.5.3" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz#90420f9f9c6d3987f176a19a7d8e764271a2f55d" @@ -975,13 +980,6 @@ resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803" integrity sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow== -"@types/cssnano@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/cssnano/-/cssnano-4.0.0.tgz#f1bb29d6d0813861a3d87e02946b2988d0110d4e" - integrity sha512-BC/2ibKZfPIaBLBNzkitdW1IvvX/LKW6/QXGc4Su/tAJ7mQ3f2CKBuGCCKaqGAnoKwzfuC7G/recpkARwdOwuA== - dependencies: - postcss "5 - 7" - "@types/debug@^4.1.5": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.9.tgz#906996938bc672aaf2fb8c0d3733ae1dda05b005" @@ -1035,10 +1033,10 @@ dependencies: "@types/node" "*" -"@types/gulp-postcss@^8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@types/gulp-postcss/-/gulp-postcss-8.0.0.tgz#f7e86d45e4999fd43e6d8c55b00504c88a67ad61" - integrity sha512-AVgjA03bpkYONKZpzuJviB9PzaNbDzrovYPbenj8/XxivUc35C/dIzJanyaQv7CFqfLLPLsqSalmtP3GLq6iag== +"@types/gulp-postcss@^8.0.6": + version "8.0.6" + resolved "https://registry.yarnpkg.com/@types/gulp-postcss/-/gulp-postcss-8.0.6.tgz#d314c785876c8a1779154ba1d152125082ecde0f" + integrity sha512-mjGEmTvurqRHFeJQnrgtMC9GtKNkI2+56n92zIzff5UFr2jUfilw1elKRxS7bK0FYRvuEcnMX9JH0AUpCxBrpg== dependencies: "@types/node" "*" "@types/vinyl" "*" @@ -1149,11 +1147,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.19.tgz#cb03fca8910fdeb7595b755126a8a78144714eea" integrity sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA== -"@types/q@^1.5.1": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" - integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== - "@types/responselike@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" @@ -1826,11 +1819,6 @@ ajv@^8.0.0, ajv@^8.8.0: require-from-string "^2.0.2" uri-js "^4.2.2" -alphanum-sort@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" - integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= - amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" @@ -2274,7 +2262,7 @@ block-stream@*: dependencies: inherits "~2.0.0" -boolbase@^1.0.0, boolbase@~1.0.0: +boolbase@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= @@ -2355,6 +2343,16 @@ browserslist@^4.20.2: node-releases "^2.0.6" update-browserslist-db "^1.0.5" +browserslist@^4.22.2: + version "4.22.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.3.tgz#299d11b7e947a6b843981392721169e27d60c5a6" + integrity sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A== + dependencies: + caniuse-lite "^1.0.30001580" + electron-to-chromium "^1.4.648" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -2445,25 +2443,6 @@ call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" -caller-callsite@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" - integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= - dependencies: - callsites "^2.0.0" - -caller-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" - integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= - dependencies: - caller-callsite "^2.0.0" - -callsites@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" - integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -2504,6 +2483,11 @@ caniuse-lite@^1.0.30001370: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001373.tgz#2dc3bc3bfcb5d5a929bec11300883040d7b4b4be" integrity sha512-pJYArGHrPp3TUqQzFYRmP/lwJlj8RCbVe3Gd3eJQkAV8SAC6b19XS9BjMvRdvaS8RMkaTN8ZhoHP6S1y8zzwEQ== +caniuse-lite@^1.0.30001580: + version "1.0.30001581" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001581.tgz#0dfd4db9e94edbdca67d57348ebc070dece279f4" + integrity sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ== + chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -2741,15 +2725,6 @@ co@^4.6.0: resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= -coa@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" - integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== - dependencies: - "@types/q" "^1.5.1" - chalk "^2.4.1" - q "^1.1.2" - code-block-writer@^12.0.0: version "12.0.0" resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-12.0.0.tgz#4dd58946eb4234105aff7f0035977b2afdc2a770" @@ -2777,7 +2752,7 @@ collection-visit@^1.0.0: map-visit "^1.0.0" object-visit "^1.0.0" -color-convert@^1.9.0, color-convert@^1.9.1: +color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -2796,31 +2771,20 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@^1.0.0, color-name@~1.1.4: +color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.5.4: - version "1.5.5" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014" - integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -color@^3.0.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e" - integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ== - dependencies: - color-convert "^1.9.1" - color-string "^1.5.4" +colord@^2.9.1: + version "2.9.3" + resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" + integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== colorette@^1.2.2: version "1.2.2" @@ -2991,16 +2955,6 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cosmiconfig@^5.0.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" - integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== - dependencies: - import-fresh "^2.0.0" - is-directory "^0.3.1" - js-yaml "^3.13.1" - parse-json "^4.0.0" - create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" @@ -3031,47 +2985,24 @@ crypt@0.0.2: resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== -css-color-names@0.0.4, css-color-names@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" - integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= +css-declaration-sorter@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-7.1.1.tgz#9796bcc257b4647c39993bda8d431ce32b666f80" + integrity sha512-dZ3bVTEEc1vxr3Bek9vGwfB5Z6ESPULhcRvO472mfjVnj8jRcTnKO8/JTczlvxM10Myb+wBM++1MtdO76eWcaQ== -css-declaration-sorter@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" - integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== - dependencies: - postcss "^7.0.1" - timsort "^0.3.0" - -css-loader@^6.7.3: - version "6.7.3" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.3.tgz#1e8799f3ccc5874fdd55461af51137fcc5befbcd" - integrity sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ== +css-loader@^6.9.1: + version "6.9.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.9.1.tgz#9ec9a434368f2bdfeffbf8f6901a1ce773586c6b" + integrity sha512-OzABOh0+26JKFdMzlK6PY1u5Zx8+Ck7CVRlcGNZoY9qwJjdfu2VWFuprTIpPW+Av5TZTVViYWcFQaEEQURLknQ== dependencies: icss-utils "^5.1.0" - postcss "^8.4.19" + postcss "^8.4.33" postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" - postcss-modules-scope "^3.0.0" + postcss-modules-local-by-default "^4.0.4" + postcss-modules-scope "^3.1.1" postcss-modules-values "^4.0.0" postcss-value-parser "^4.2.0" - semver "^7.3.8" - -css-select-base-adapter@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" - integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== - -css-select@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" - integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== - dependencies: - boolbase "^1.0.0" - css-what "^3.2.1" - domutils "^1.7.0" - nth-check "^1.0.2" + semver "^7.5.4" css-select@^4.1.3: version "4.2.1" @@ -3084,13 +3015,16 @@ css-select@^4.1.3: domutils "^2.8.0" nth-check "^2.0.1" -css-tree@1.0.0-alpha.37: - version "1.0.0-alpha.37" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" - integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== dependencies: - mdn-data "2.0.4" - source-map "^0.6.1" + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" css-tree@^1.1.2: version "1.1.2" @@ -3108,16 +3042,32 @@ css-tree@^1.1.3: mdn-data "2.0.14" source-map "^0.6.1" -css-what@^3.2.1: - version "3.4.2" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" - integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== +css-tree@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" + integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== + dependencies: + mdn-data "2.0.30" + source-map-js "^1.0.1" + +css-tree@~2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032" + integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA== + dependencies: + mdn-data "2.0.28" + source-map-js "^1.0.1" css-what@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== +css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + css@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" @@ -3132,81 +3082,68 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssnano-preset-default@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz#920622b1fc1e95a34e8838203f1397a504f2d3ff" - integrity sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ== +cssnano-preset-default@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-6.0.3.tgz#b4ce755974f4dc8d3d09ac13bb6281cce3ced45e" + integrity sha512-4y3H370aZCkT9Ev8P4SO4bZbt+AExeKhh8wTbms/X7OLDo5E7AYUUy6YPxa/uF5Grf+AJwNcCnxKhZynJ6luBA== dependencies: - css-declaration-sorter "^4.0.1" - cssnano-util-raw-cache "^4.0.1" - postcss "^7.0.0" - postcss-calc "^7.0.1" - postcss-colormin "^4.0.3" - postcss-convert-values "^4.0.1" - postcss-discard-comments "^4.0.2" - postcss-discard-duplicates "^4.0.2" - postcss-discard-empty "^4.0.1" - postcss-discard-overridden "^4.0.1" - postcss-merge-longhand "^4.0.11" - postcss-merge-rules "^4.0.3" - postcss-minify-font-values "^4.0.2" - postcss-minify-gradients "^4.0.2" - postcss-minify-params "^4.0.2" - postcss-minify-selectors "^4.0.2" - postcss-normalize-charset "^4.0.1" - postcss-normalize-display-values "^4.0.2" - postcss-normalize-positions "^4.0.2" - postcss-normalize-repeat-style "^4.0.2" - postcss-normalize-string "^4.0.2" - postcss-normalize-timing-functions "^4.0.2" - postcss-normalize-unicode "^4.0.1" - postcss-normalize-url "^4.0.1" - postcss-normalize-whitespace "^4.0.2" - postcss-ordered-values "^4.1.2" - postcss-reduce-initial "^4.0.3" - postcss-reduce-transforms "^4.0.2" - postcss-svgo "^4.0.3" - postcss-unique-selectors "^4.0.1" + css-declaration-sorter "^7.1.1" + cssnano-utils "^4.0.1" + postcss-calc "^9.0.1" + postcss-colormin "^6.0.2" + postcss-convert-values "^6.0.2" + postcss-discard-comments "^6.0.1" + postcss-discard-duplicates "^6.0.1" + postcss-discard-empty "^6.0.1" + postcss-discard-overridden "^6.0.1" + postcss-merge-longhand "^6.0.2" + postcss-merge-rules "^6.0.3" + postcss-minify-font-values "^6.0.1" + postcss-minify-gradients "^6.0.1" + postcss-minify-params "^6.0.2" + postcss-minify-selectors "^6.0.2" + postcss-normalize-charset "^6.0.1" + postcss-normalize-display-values "^6.0.1" + postcss-normalize-positions "^6.0.1" + postcss-normalize-repeat-style "^6.0.1" + postcss-normalize-string "^6.0.1" + postcss-normalize-timing-functions "^6.0.1" + postcss-normalize-unicode "^6.0.2" + postcss-normalize-url "^6.0.1" + postcss-normalize-whitespace "^6.0.1" + postcss-ordered-values "^6.0.1" + postcss-reduce-initial "^6.0.2" + postcss-reduce-transforms "^6.0.1" + postcss-svgo "^6.0.2" + postcss-unique-selectors "^6.0.2" -cssnano-util-get-arguments@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" - integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= - -cssnano-util-get-match@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" - integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= - -cssnano-util-raw-cache@^4.0.1: +cssnano-utils@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" - integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-4.0.1.tgz#fd18b42f95938bf55ab47967705355d6047bf1da" + integrity sha512-6qQuYDqsGoiXssZ3zct6dcMxiqfT6epy7x4R0TQJadd4LWO3sPR6JH6ZByOvVLoZ6EdwPGgd7+DR1EmX3tiXQQ== + +cssnano@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-6.0.3.tgz#46db972da71aa159437287fb4c6bc9c5d3cc5d93" + integrity sha512-MRq4CIj8pnyZpcI2qs6wswoYoDD1t0aL28n+41c1Ukcpm56m1h6mCexIHBGjfZfnTqtGSSCP4/fB1ovxgjBOiw== dependencies: - postcss "^7.0.0" + cssnano-preset-default "^6.0.3" + lilconfig "^3.0.0" -cssnano-util-same-parent@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" - integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== - -cssnano@^4.1.11: - version "4.1.11" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.11.tgz#c7b5f5b81da269cb1fd982cb960c1200910c9a99" - integrity sha512-6gZm2htn7xIPJOHY824ERgj8cNPgPxyCSnkXc4v7YvNW+TdVfzgngHcEhy/8D11kUWRUMbke+tC+AUcUsnMz2g== - dependencies: - cosmiconfig "^5.0.0" - cssnano-preset-default "^4.0.8" - is-resolvable "^1.0.0" - postcss "^7.0.0" - -csso@^4.0.2, csso@^4.2.0: +csso@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== dependencies: css-tree "^1.1.2" +csso@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6" + integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ== + dependencies: + css-tree "~2.2.0" + d@1, d@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" @@ -3440,14 +3377,6 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-serializer@0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" - dom-serializer@^1.0.1: version "1.3.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" @@ -3457,10 +3386,14 @@ dom-serializer@^1.0.1: domhandler "^4.2.0" entities "^2.0.0" -domelementtype@1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" domelementtype@^2.0.1: version "2.1.0" @@ -3472,6 +3405,11 @@ domelementtype@^2.2.0: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + domhandler@^4.2.0, domhandler@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626" @@ -3479,13 +3417,12 @@ domhandler@^4.2.0, domhandler@^4.3.0: dependencies: domelementtype "^2.2.0" -domutils@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== dependencies: - dom-serializer "0" - domelementtype "1" + domelementtype "^2.3.0" domutils@^2.8.0: version "2.8.0" @@ -3496,12 +3433,14 @@ domutils@^2.8.0: domelementtype "^2.2.0" domhandler "^4.2.0" -dot-prop@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" - integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== dependencies: - is-obj "^2.0.0" + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" duplexer@^0.1.1, duplexer@~0.1.1: version "0.1.1" @@ -3570,6 +3509,11 @@ electron-to-chromium@^1.4.202: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.207.tgz#9c3310ebace2952903d05dcaba8abe3a4ed44c01" integrity sha512-piH7MJDJp4rJCduWbVvmUd59AUne1AFBJ8JaRQvk0KzNTSUnZrVXHCZc+eg+CGE4OujkcLJznhGKD6tuAshj5Q== +electron-to-chromium@^1.4.648: + version "1.4.648" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.648.tgz#c7b46c9010752c37bb4322739d6d2dd82354fbe4" + integrity sha512-EmFMarXeqJp9cUKu/QEciEApn0S/xRcpZWuAm32U7NgoZCimjsilKXHRO9saeEW55eHZagIDg6XTUOv32w9pjg== + electron@27.2.3: version "27.2.3" resolved "https://registry.yarnpkg.com/electron/-/electron-27.2.3.tgz#ef8c7a33175bb9fc91ae5508c560aea719b49232" @@ -3632,6 +3576,11 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== +entities@^4.2.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + env-paths@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" @@ -3656,23 +3605,6 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.2: - version "1.17.7" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" - integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-regex "^1.1.1" - object-inspect "^1.8.0" - object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - es-abstract@^1.18.0-next.1: version "1.18.0-next.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" @@ -4139,6 +4071,13 @@ fancy-log@^1.3.2, fancy-log@^1.3.3: parse-node-version "^1.0.0" time-stamp "^1.0.0" +fancy-log@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-2.0.0.tgz#cad207b8396d69ae4796d74d17dff5f68b2f7343" + integrity sha512-9CzxZbACXMUXW13tS0tI8XsGGmxWzO2DmYrGuBJOJ8k8q2K7hwfJA5qHjuPPe8wtsco33YR9wc+Rlr5wYFvhSA== + dependencies: + color-support "^1.1.3" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -4954,14 +4893,14 @@ gulp-plumber@^1.2.0: plugin-error "^0.1.2" through2 "^2.0.3" -gulp-postcss@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/gulp-postcss/-/gulp-postcss-9.0.0.tgz#2ade18809ab475dae743a88bd6501af0b04ee54e" - integrity sha512-5mSQ9CK8salSagrXgrVyILfEMy6I5rUGPRiR9rVjgJV9m/rwdZYUhekMr+XxDlApfc5ZdEJ8gXNZrU/TsgT5dQ== +gulp-postcss@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/gulp-postcss/-/gulp-postcss-9.1.0.tgz#0d317134d40d9565f265bd32c7f71605a54cadd8" + integrity sha512-a843mcKPApfeI987uqQbc8l50xXeWIXBsiVvYxtCI5XtVAMzTi/HnU2qzQpGwkB/PAOfsLV8OsqDv2iJZ9qvdw== dependencies: - fancy-log "^1.3.3" - plugin-error "^1.0.1" - postcss-load-config "^2.1.1" + fancy-log "^2.0.0" + plugin-error "^2.0.1" + postcss-load-config "^5.0.0" vinyl-sourcemaps-apply "^0.2.1" gulp-rename@1.2.2, gulp-rename@^1.2.0: @@ -5137,7 +5076,7 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" -has@^1.0.0, has@^1.0.3: +has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== @@ -5149,11 +5088,6 @@ he@1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -hex-color-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" - integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== - homedir-polyfill@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" @@ -5166,16 +5100,6 @@ hosted-git-info@^2.1.4: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== -hsl-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" - integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= - -hsla-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" - integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= - html-escaper@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.0.tgz#71e87f931de3fe09e56661ab9a29aadec707b491" @@ -5314,21 +5238,6 @@ immediate@~3.0.5: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== -import-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" - integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk= - dependencies: - import-from "^2.1.0" - -import-fresh@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" - integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= - dependencies: - caller-path "^2.0.0" - resolve-from "^3.0.0" - import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -5337,13 +5246,6 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -import-from@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" - integrity sha1-M1238qev/VOqpHHUuAId7ja387E= - dependencies: - resolve-from "^3.0.0" - import-local@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" @@ -5434,11 +5336,6 @@ ip@^2.0.0: resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== -is-absolute-url@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" - integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= - is-absolute@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" @@ -5474,11 +5371,6 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" @@ -5522,18 +5414,6 @@ is-ci@^1.0.9: dependencies: ci-info "^1.5.0" -is-color-stop@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" - integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= - dependencies: - css-color-names "^0.0.4" - hex-color-regex "^1.1.0" - hsl-regex "^1.0.0" - hsla-regex "^1.0.0" - rgb-regex "^1.0.1" - rgba-regex "^1.0.0" - is-core-module@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" @@ -5590,11 +5470,6 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-data-descriptor "^1.0.0" kind-of "^6.0.2" -is-directory@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" - integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= - is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -5687,11 +5562,6 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - is-path-inside@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" @@ -5738,11 +5608,6 @@ is-relative@^1.0.0: dependencies: is-unc-path "^1.0.0" -is-resolvable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" - integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== - is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -5932,7 +5797,7 @@ js-yaml@4.1.0, js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -js-yaml@^3.13.0, js-yaml@^3.13.1: +js-yaml@^3.13.0: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -6226,6 +6091,11 @@ liftoff@^3.1.0: rechoir "^0.6.2" resolve "^1.1.7" +lilconfig@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.0.0.tgz#f8067feb033b5b74dab4602a5f5029420be749bc" + integrity sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g== + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -6455,10 +6325,15 @@ mdn-data@2.0.14: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== -mdn-data@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" - integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== +mdn-data@2.0.28: + version "2.0.28" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba" + integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g== + +mdn-data@2.0.30: + version "2.0.30" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" + integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== media-typer@0.3.0: version "0.3.0" @@ -6703,7 +6578,7 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== -"mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +"mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@~0.5.0: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -6832,10 +6707,10 @@ nanoid@3.3.3: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== -nanoid@^3.3.4: - version "3.3.6" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== nanomatch@^1.2.9: version "1.2.13" @@ -6983,6 +6858,11 @@ node-releases@^1.1.71: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe" integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw== +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + node-releases@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" @@ -7023,11 +6903,6 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" - integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== - normalize-url@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" @@ -7062,13 +6937,6 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -nth-check@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== - dependencies: - boolbase "~1.0.0" - nth-check@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" @@ -7137,15 +7005,6 @@ object.defaults@^1.0.0, object.defaults@^1.1.0: for-own "^1.0.0" isobject "^3.0.0" -object.getownpropertydescriptors@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.1.tgz#0dfda8d108074d9c563e80490c883b6661091544" - integrity sha512-6DtXgZ/lIZ9hqx4GtZETobXLR/ZLaa0aqV0kzbn80Rf8Z2e/XFnhA0I7p07N2wH8bBBltr2xQPi6sbKWAY2Eng== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" - object.map@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37" @@ -7169,16 +7028,6 @@ object.reduce@^1.0.0: for-own "^1.0.0" make-iterator "^1.0.0" -object.values@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.2.tgz#7a2015e06fcb0f546bd652486ce8583a4731c731" - integrity sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" - has "^1.0.3" - on-finished@^2.3.0, on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -7655,155 +7504,140 @@ plugin-error@^1.0.0, plugin-error@^1.0.1: arr-union "^3.1.0" extend-shallow "^3.0.2" +plugin-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-2.0.1.tgz#f2ac92bac8c85e3e23492d76d0c3ca12f30eb00b" + integrity sha512-zMakqvIDyY40xHOvzXka0kUvf40nYIuwRE8dWhti2WtjQZ31xAgBZBhxsK7vK3QbRXS1Xms/LO7B5cuAsfB2Gg== + dependencies: + ansi-colors "^1.0.1" + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= -postcss-calc@^7.0.1: - version "7.0.5" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.5.tgz#f8a6e99f12e619c2ebc23cf6c486fdc15860933e" - integrity sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg== +postcss-calc@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-9.0.1.tgz#a744fd592438a93d6de0f1434c572670361eb6c6" + integrity sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ== dependencies: - postcss "^7.0.27" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.2" + postcss-selector-parser "^6.0.11" + postcss-value-parser "^4.2.0" -postcss-colormin@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" - integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== +postcss-colormin@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-6.0.2.tgz#2af9ce753937b08e058dbc6879e4aedfab42806b" + integrity sha512-TXKOxs9LWcdYo5cgmcSHPkyrLAh86hX1ijmyy6J8SbOhyv6ua053M3ZAM/0j44UsnQNIWdl8gb5L7xX2htKeLw== dependencies: - browserslist "^4.0.0" - color "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-convert-values@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" - integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-discard-comments@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" - integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== - dependencies: - postcss "^7.0.0" - -postcss-discard-duplicates@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" - integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== - dependencies: - postcss "^7.0.0" - -postcss-discard-empty@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" - integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== - dependencies: - postcss "^7.0.0" - -postcss-discard-overridden@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" - integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== - dependencies: - postcss "^7.0.0" - -postcss-load-config@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz#c5ea504f2c4aef33c7359a34de3573772ad7502a" - integrity sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw== - dependencies: - cosmiconfig "^5.0.0" - import-cwd "^2.0.0" - -postcss-merge-longhand@^4.0.11: - version "4.0.11" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" - integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== - dependencies: - css-color-names "0.0.4" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - stylehacks "^4.0.0" - -postcss-merge-rules@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" - integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== - dependencies: - browserslist "^4.0.0" + browserslist "^4.22.2" caniuse-api "^3.0.0" - cssnano-util-same-parent "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - vendors "^1.0.0" + colord "^2.9.1" + postcss-value-parser "^4.2.0" -postcss-minify-font-values@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" - integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== +postcss-convert-values@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-6.0.2.tgz#c4a7509aeb1cc7ac3f6948fcbffc2bf8cac7c56a" + integrity sha512-aeBmaTnGQ+NUSVQT8aY0sKyAD/BaLJenEKZ03YK0JnDE1w1Rr8XShoxdal2V2H26xTJKr3v5haByOhJuyT4UYw== dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + browserslist "^4.22.2" + postcss-value-parser "^4.2.0" -postcss-minify-gradients@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" - integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== - dependencies: - cssnano-util-get-arguments "^4.0.0" - is-color-stop "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" +postcss-discard-comments@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-6.0.1.tgz#46176212bd9c3e5f48aa4b8b4868786726c41d36" + integrity sha512-f1KYNPtqYLUeZGCHQPKzzFtsHaRuECe6jLakf/RjSRqvF5XHLZnM2+fXLhb8Qh/HBFHs3M4cSLb1k3B899RYIg== -postcss-minify-params@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" - integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== - dependencies: - alphanum-sort "^1.0.0" - browserslist "^4.0.0" - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - uniqs "^2.0.0" +postcss-discard-duplicates@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.1.tgz#112b1a95948e69b3484fdd43584dda6930977939" + integrity sha512-1hvUs76HLYR8zkScbwyJ8oJEugfPV+WchpnA+26fpJ7Smzs51CzGBHC32RS03psuX/2l0l0UKh2StzNxOrKCYg== -postcss-minify-selectors@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" - integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== +postcss-discard-empty@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-6.0.1.tgz#b34cb45ec891246da4506b53e352390fdef126c4" + integrity sha512-yitcmKwmVWtNsrrRqGJ7/C0YRy53i0mjexBDQ9zYxDwTWVBgbU4+C9jIZLmQlTDT9zhml+u0OMFJh8+31krmOg== + +postcss-discard-overridden@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-6.0.1.tgz#c63c559237758d74bc505452393a64dda9b19ef4" + integrity sha512-qs0ehZMMZpSESbRkw1+inkf51kak6OOzNRaoLd/U7Fatp0aN2HQ1rxGOrJvYcRAN9VpX8kUF13R2ofn8OlvFVA== + +postcss-load-config@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-5.0.2.tgz#3d4261d616428e3d6e41c8236c3e456c0f49266f" + integrity sha512-Q8QR3FYbqOKa0bnC1UQ2bFq9/ulHX5Bi34muzitMr8aDtUelO5xKeJEYC/5smE0jNE9zdB/NBnOwXKexELbRlw== dependencies: - alphanum-sort "^1.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" + lilconfig "^3.0.0" + yaml "^2.3.4" + +postcss-merge-longhand@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-6.0.2.tgz#cd4e83014851da59545e9a906b245615550f4064" + integrity sha512-+yfVB7gEM8SrCo9w2lCApKIEzrTKl5yS1F4yGhV3kSim6JzbfLGJyhR1B6X+6vOT0U33Mgx7iv4X9MVWuaSAfw== + dependencies: + postcss-value-parser "^4.2.0" + stylehacks "^6.0.2" + +postcss-merge-rules@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-6.0.3.tgz#08fcf714faaad75b1980ecd961b080ae2f8ddeb3" + integrity sha512-yfkDqSHGohy8sGYIJwBmIGDv4K4/WrJPX355XrxQb/CSsT4Kc/RxDi6akqn5s9bap85AWgv21ArcUWwWdGNSHA== + dependencies: + browserslist "^4.22.2" + caniuse-api "^3.0.0" + cssnano-utils "^4.0.1" + postcss-selector-parser "^6.0.15" + +postcss-minify-font-values@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-6.0.1.tgz#788eb930168be90225f3937f0b70aa19d8b532b2" + integrity sha512-tIwmF1zUPoN6xOtA/2FgVk1ZKrLcCvE0dpZLtzyyte0j9zUeB8RTbCqrHZGjJlxOvNWKMYtunLrrl7HPOiR46w== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-minify-gradients@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-6.0.1.tgz#4faf1880b483dc37016658aa186b42194ff9b5bc" + integrity sha512-M1RJWVjd6IOLPl1hYiOd5HQHgpp6cvJVLrieQYS9y07Yo8itAr6jaekzJphaJFR0tcg4kRewCk3kna9uHBxn/w== + dependencies: + colord "^2.9.1" + cssnano-utils "^4.0.1" + postcss-value-parser "^4.2.0" + +postcss-minify-params@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-6.0.2.tgz#bd64af642fa5610281b8a9461598bbb91f92ae05" + integrity sha512-zwQtbrPEBDj+ApELZ6QylLf2/c5zmASoOuA4DzolyVGdV38iR2I5QRMsZcHkcdkZzxpN8RS4cN7LPskOkTwTZw== + dependencies: + browserslist "^4.22.2" + cssnano-utils "^4.0.1" + postcss-value-parser "^4.2.0" + +postcss-minify-selectors@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-6.0.2.tgz#62065b38d3453ddc6627ba50e4f4a2154b031aa0" + integrity sha512-0b+m+w7OAvZejPQdN2GjsXLv5o0jqYHX3aoV0e7RBKPCsB7TYG5KKWBFhGnB/iP3213Ts8c5H4wLPLMm7z28Sg== + dependencies: + postcss-selector-parser "^6.0.15" postcss-modules-extract-imports@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== -postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== +postcss-modules-local-by-default@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz#7cbed92abd312b94aaea85b68226d3dec39a14e6" + integrity sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q== dependencies: icss-utils "^5.0.0" postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== +postcss-modules-scope@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz#32cfab55e84887c079a19bbb215e721d683ef134" + integrity sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA== dependencies: postcss-selector-parser "^6.0.4" @@ -7814,124 +7648,106 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" -postcss-normalize-charset@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" - integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== +postcss-nesting@^12.0.2: + version "12.0.2" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-12.0.2.tgz#cb92061347db3e7c38c174c97cb01feff2eef439" + integrity sha512-63PpJHSeNs93S3ZUIyi+7kKx4JqOIEJ6QYtG3x+0qA4J03+4n0iwsyA1GAHyWxsHYljQS4/4ZK1o2sMi70b5wQ== dependencies: - postcss "^7.0.0" + "@csstools/selector-specificity" "^3.0.1" + postcss-selector-parser "^6.0.13" -postcss-normalize-display-values@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" - integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== - dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" +postcss-normalize-charset@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-6.0.1.tgz#5f70e1eb8bbdbcfcbed060ef70f179e8fef57d0c" + integrity sha512-aW5LbMNRZ+oDV57PF9K+WI1Z8MPnF+A8qbajg/T8PP126YrGX1f9IQx21GI2OlGz7XFJi/fNi0GTbY948XJtXg== -postcss-normalize-positions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" - integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== +postcss-normalize-display-values@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.1.tgz#ff9aa30bbf1283294bfd9cc8b6fb81ff060a7f2d" + integrity sha512-mc3vxp2bEuCb4LgCcmG1y6lKJu1Co8T+rKHrcbShJwUmKJiEl761qb/QQCfFwlrvSeET3jksolCR/RZuMURudw== dependencies: - cssnano-util-get-arguments "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-normalize-repeat-style@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" - integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== +postcss-normalize-positions@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-6.0.1.tgz#41ffdc72994f024c6cd6e91dbfb40ab9abe6fe90" + integrity sha512-HRsq8u/0unKNvm0cvwxcOUEcakFXqZ41fv3FOdPn916XFUrympjr+03oaLkuZENz3HE9RrQE9yU0Xv43ThWjQg== dependencies: - cssnano-util-get-arguments "^4.0.0" - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-normalize-string@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" - integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== +postcss-normalize-repeat-style@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.1.tgz#55dc54b6f80305b280a379899a6626e0a07b04a8" + integrity sha512-Gbb2nmCy6tTiA7Sh2MBs3fj9W8swonk6lw+dFFeQT68B0Pzwp1kvisJQkdV6rbbMSd9brMlS8I8ts52tAGWmGQ== dependencies: - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-normalize-timing-functions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" - integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== +postcss-normalize-string@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-6.0.1.tgz#7605e0fb4ec7bf2709709991d13a949e4419db1d" + integrity sha512-5Fhx/+xzALJD9EI26Aq23hXwmv97Zfy2VFrt5PLT8lAhnBIZvmaT5pQk+NuJ/GWj/QWaKSKbnoKDGLbV6qnhXg== dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-normalize-unicode@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" - integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== +postcss-normalize-timing-functions@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.1.tgz#ef937b7ca2fd62ed0b46645ea5728b842a3600db" + integrity sha512-4zcczzHqmCU7L5dqTB9rzeqPWRMc0K2HoR+Bfl+FSMbqGBUcP5LRfgcH4BdRtLuzVQK1/FHdFoGT3F7rkEnY+g== dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-normalize-url@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" - integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== +postcss-normalize-unicode@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.2.tgz#361026744ff11baebaec771b60c2a5f36f274fd0" + integrity sha512-Ff2VdAYCTGyMUwpevTZPZ4w0+mPjbZzLLyoLh/RMpqUqeQKZ+xMm31hkxBavDcGKcxm6ACzGk0nBfZ8LZkStKA== dependencies: - is-absolute-url "^2.0.0" - normalize-url "^3.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + browserslist "^4.22.2" + postcss-value-parser "^4.2.0" -postcss-normalize-whitespace@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" - integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== +postcss-normalize-url@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-6.0.1.tgz#eae58cb4f5f9a4fa5bbbf6d4222dff534ad46186" + integrity sha512-jEXL15tXSvbjm0yzUV7FBiEXwhIa9H88JOXDGQzmcWoB4mSjZIsmtto066s2iW9FYuIrIF4k04HA2BKAOpbsaQ== dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-ordered-values@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" - integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== +postcss-normalize-whitespace@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.1.tgz#b5933750b938814c028d3d2b2e5c0199e0037b53" + integrity sha512-76i3NpWf6bB8UHlVuLRxG4zW2YykF9CTEcq/9LGAiz2qBuX5cBStadkk0jSkg9a9TCIXbMQz7yzrygKoCW9JuA== dependencies: - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-reduce-initial@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" - integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== +postcss-ordered-values@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-6.0.1.tgz#553e735d009065b362da93340e57f43d5f2d0fbc" + integrity sha512-XXbb1O/MW9HdEhnBxitZpPFbIvDgbo9NK4c/5bOfiKpnIGZDoL2xd7/e6jW5DYLsWxBbs+1nZEnVgnjnlFViaA== dependencies: - browserslist "^4.0.0" + cssnano-utils "^4.0.1" + postcss-value-parser "^4.2.0" + +postcss-reduce-initial@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-6.0.2.tgz#763d25902406c872264041df69f182eb15a5d9be" + integrity sha512-YGKalhNlCLcjcLvjU5nF8FyeCTkCO5UtvJEt0hrPZVCTtRLSOH4z00T1UntQPj4dUmIYZgMj8qK77JbSX95hSw== + dependencies: + browserslist "^4.22.2" caniuse-api "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" -postcss-reduce-transforms@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" - integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== +postcss-reduce-transforms@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.1.tgz#7bf59d7c6e7066e3b18ef17237d2344bd3da6d75" + integrity sha512-fUbV81OkUe75JM+VYO1gr/IoA2b/dRiH6HvMwhrIBSUrxq3jNZQZitSnugcTLDi1KkQh1eR/zi+iyxviUNBkcQ== dependencies: - cssnano-util-get-match "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-selector-parser@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" - integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== +postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.13, postcss-selector-parser@^6.0.15: + version "6.0.15" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz#11cc2b21eebc0b99ea374ffb9887174855a01535" + integrity sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw== dependencies: - dot-prop "^5.2.0" - indexes-of "^1.0.1" - uniq "^1.0.1" + cssesc "^3.0.0" + util-deprecate "^1.0.2" postcss-selector-parser@^6.0.2: version "6.0.2" @@ -7950,40 +7766,27 @@ postcss-selector-parser@^6.0.4: cssesc "^3.0.0" util-deprecate "^1.0.2" -postcss-svgo@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.3.tgz#343a2cdbac9505d416243d496f724f38894c941e" - integrity sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw== +postcss-svgo@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-6.0.2.tgz#dbc9d03e7f346bc0d82443078602a951e0214836" + integrity sha512-IH5R9SjkTkh0kfFOQDImyy1+mTCb+E830+9SV1O+AaDcoHTvfsvt6WwJeo7KwcHbFnevZVCsXhDmjFiGVuwqFQ== dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - svgo "^1.0.0" + postcss-value-parser "^4.2.0" + svgo "^3.2.0" -postcss-unique-selectors@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" - integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== +postcss-unique-selectors@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-6.0.2.tgz#09a34a5a31a649d3e9bca5962af0616f39d071d2" + integrity sha512-8IZGQ94nechdG7Y9Sh9FlIY2b4uS8/k8kdKRX040XHsS3B6d1HrJAkXrBSsSu4SuARruSsUjW3nlSw8BHkaAYQ== dependencies: - alphanum-sort "^1.0.0" - postcss "^7.0.0" - uniqs "^2.0.0" - -postcss-value-parser@^3.0.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" - integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== - -postcss-value-parser@^4.0.2: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" - integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== + postcss-selector-parser "^6.0.15" postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -"postcss@5 - 7", postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.16, postcss@^7.0.27: +postcss@^7.0.16: version "7.0.39" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309" integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== @@ -7991,12 +7794,12 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: picocolors "^0.2.1" source-map "^0.6.1" -postcss@^8.4.19: - version "8.4.21" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" - integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== +postcss@^8.4.33: + version "8.4.33" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" + integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== dependencies: - nanoid "^3.3.4" + nanoid "^3.3.7" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -8126,11 +7929,6 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -q@^1.1.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= - querystringify@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" @@ -8407,11 +8205,6 @@ resolve-dir@^1.0.0, resolve-dir@^1.0.1: expand-tilde "^2.0.0" global-modules "^1.0.0" -resolve-from@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - integrity sha1-six699nWiBvItuZTM17rywoYh0g= - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -8484,16 +8277,6 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rgb-regex@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" - integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= - -rgba-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" - integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= - rimraf@2, rimraf@^2.2.8, rimraf@^2.4.2: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -8566,7 +8349,7 @@ safe-regex@^1.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sax@>=0.6.0, sax@~1.2.4: +sax@>=0.6.0: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -8626,7 +8409,7 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.4: +semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -8742,13 +8525,6 @@ simple-get@^4.0.0: once "^1.3.1" simple-concat "^1.0.0" -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= - dependencies: - is-arrayish "^0.3.1" - sinon-test@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/sinon-test/-/sinon-test-3.1.3.tgz#b261e0efd0b479891a243201387f957d7ca61daa" @@ -8837,7 +8613,7 @@ socks@^2.7.1: ip "^2.0.0" smart-buffer "^4.2.0" -source-map-js@^1.0.2: +source-map-js@^1.0.1, source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== @@ -9237,14 +9013,13 @@ style-loader@^3.3.2: resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.2.tgz#eaebca714d9e462c19aa1e3599057bc363924899" integrity sha512-RHs/vcrKdQK8wZliteNK4NKzxvLBzpuHMqYmUVWeKa6MkaIQ97ZTOS0b+zapZhy6GcrgWnvWYCMHRirC3FsUmw== -stylehacks@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" - integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== +stylehacks@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-6.0.2.tgz#5bf2654561752547d4548765f35c9a49659b3742" + integrity sha512-00zvJGnCu64EpMjX8b5iCZ3us2Ptyw8+toEkb92VdmkEaRaSGBNKAoK6aWZckhXxmQP8zWiTaFaiMGIU8Ve8sg== dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" + browserslist "^4.22.2" + postcss-selector-parser "^6.0.15" sumchecker@^3.0.1: version "3.0.1" @@ -9304,25 +9079,6 @@ sver-compat@^1.5.0: es6-iterator "^2.0.1" es6-symbol "^3.1.1" -svgo@^1.0.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" - integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== - dependencies: - chalk "^2.4.1" - coa "^2.0.2" - css-select "^2.0.0" - css-select-base-adapter "^0.1.1" - css-tree "1.0.0-alpha.37" - csso "^4.0.2" - js-yaml "^3.13.1" - mkdirp "~0.5.1" - object.values "^1.1.0" - sax "~1.2.4" - stable "^0.1.8" - unquote "~1.1.1" - util.promisify "~1.0.0" - svgo@^2.7.0: version "2.8.0" resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" @@ -9336,6 +9092,19 @@ svgo@^2.7.0: picocolors "^1.0.0" stable "^0.1.8" +svgo@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.2.0.tgz#7a5dff2938d8c6096e00295c2390e8e652fa805d" + integrity sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^5.1.0" + css-tree "^2.3.1" + css-what "^6.1.0" + csso "^5.0.5" + picocolors "^1.0.0" + table@^5.2.3: version "5.4.6" resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" @@ -9527,11 +9296,6 @@ timers-ext@^0.1.7: es5-ext "~0.10.46" next-tick "1" -timsort@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" - integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -9821,11 +9585,6 @@ uniq@^1.0.1: resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= -uniqs@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" - integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= - unique-stream@^2.0.2: version "2.3.1" resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.3.1.tgz#c65d110e9a4adf9a6c5948b28053d9a8d04cbeac" @@ -9849,11 +9608,6 @@ universalify@^0.2.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== -unquote@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" - integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= - unset-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" @@ -9867,6 +9621,14 @@ upath@^1.1.1: resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + update-browserslist-db@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz#be06a5eedd62f107b7c19eb5bcefb194411abf38" @@ -9905,16 +9667,6 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -util.promisify@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" - integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.2" - has-symbols "^1.0.1" - object.getownpropertydescriptors "^2.1.0" - util@^0.12.4: version "0.12.5" resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" @@ -9968,11 +9720,6 @@ vary@^1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= -vendors@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" - integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== - vinyl-fs@^3.0.0, vinyl-fs@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" @@ -10439,6 +10186,11 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml@^2.3.4: + version "2.3.4" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2" + integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA== + yargs-parser@20.2.4: version "20.2.4" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" From b637950b4b773e749c6a718884c1445af60f7dd5 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 29 Jan 2024 16:21:19 -0300 Subject: [PATCH 0698/1897] Reenable debug integration test (#203733) --- .../src/singlefolder-tests/debug.test.ts | 2 +- .../testWorkspace/.vscode/launch.json | 2 +- .../vscode-api-tests/testWorkspace/debug.js | 8 ++- .../api/common/extHostDebugService.ts | 72 +++++++++---------- 4 files changed, 43 insertions(+), 41 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts index 84226f62988..ee33d61d1e0 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts @@ -49,7 +49,7 @@ suite('vscode API - debug', function () { disposeAll(toDispose); }); - test.skip('start debugging', async function () { + test('start debugging', async function () { let stoppedEvents = 0; let variablesReceived: () => void; let initializedReceived: () => void; diff --git a/extensions/vscode-api-tests/testWorkspace/.vscode/launch.json b/extensions/vscode-api-tests/testWorkspace/.vscode/launch.json index 518e00c6724..aab0367ea2b 100644 --- a/extensions/vscode-api-tests/testWorkspace/.vscode/launch.json +++ b/extensions/vscode-api-tests/testWorkspace/.vscode/launch.json @@ -5,7 +5,7 @@ "version": "0.2.0", "configurations": [ { - "type": "pwa-node", + "type": "node", "request": "launch", "name": "Launch debug.js", "stopOnEntry": true, diff --git a/extensions/vscode-api-tests/testWorkspace/debug.js b/extensions/vscode-api-tests/testWorkspace/debug.js index 1ca212281c7..e0753f49cf8 100644 --- a/extensions/vscode-api-tests/testWorkspace/debug.js +++ b/extensions/vscode-api-tests/testWorkspace/debug.js @@ -4,7 +4,9 @@ *--------------------------------------------------------------------------------------------*/ let y = 0; -for (let i = 0; i < 100; i++) { - console.log(y); - y = y + i; +let z = 1; +hello(); + +function hello() { + console.log('hello'); } diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index ae9ba7e1b42..1fc8e403272 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -83,7 +83,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E get onDidChangeActiveDebugSession(): Event { return this._onDidChangeActiveDebugSession.event; } private _activeDebugSession: ExtHostDebugSession | undefined; - get activeDebugSession(): ExtHostDebugSession | undefined { return this._activeDebugSession; } + get activeDebugSession(): vscode.DebugSession | undefined { return this._activeDebugSession?.api; } private readonly _onDidReceiveDebugSessionCustomEvent: Emitter; get onDidReceiveDebugSessionCustomEvent(): Event { return this._onDidReceiveDebugSessionCustomEvent.event; } @@ -258,7 +258,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E } const visualizations = await provider.provideDebugVisualization({ - session, + session: session.api, variable: context.variable, containerId: context.containerId, frameId: context.frameId, @@ -691,9 +691,9 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E } if (focusDto.kind === 'thread') { - focus = new ThreadFocus(session, focusDto.threadId); + focus = new ThreadFocus(session.api, focusDto.threadId); } else { - focus = new StackFrameFocus(session, focusDto.threadId, focusDto.frameId); + focus = new StackFrameFocus(session.api, focusDto.threadId, focusDto.frameId); } this._stackFrameFocus = focus; @@ -763,20 +763,20 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E public async $acceptDebugSessionStarted(sessionDto: IDebugSessionDto): Promise { const session = await this.getSession(sessionDto); - this._onDidStartDebugSession.fire(session); + this._onDidStartDebugSession.fire(session.api); } public async $acceptDebugSessionTerminated(sessionDto: IDebugSessionDto): Promise { const session = await this.getSession(sessionDto); if (session) { - this._onDidTerminateDebugSession.fire(session); + this._onDidTerminateDebugSession.fire(session.api); this._debugSessions.delete(session.id); } } public async $acceptDebugSessionActiveChanged(sessionDto: IDebugSessionDto | undefined): Promise { this._activeDebugSession = sessionDto ? await this.getSession(sessionDto) : undefined; - this._onDidChangeActiveDebugSession.fire(this._activeDebugSession); + this._onDidChangeActiveDebugSession.fire(this._activeDebugSession?.api); } public async $acceptDebugSessionNameChanged(sessionDto: IDebugSessionDto, name: string): Promise { @@ -787,7 +787,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E public async $acceptDebugSessionCustomEvent(sessionDto: IDebugSessionDto, event: any): Promise { const session = await this.getSession(sessionDto); const ee: vscode.DebugSessionCustomEvent = { - session: session, + session: session.api, event: event.event, body: event.body }; @@ -874,7 +874,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E const promises = this._trackerFactories .filter(tuple => tuple.type === type || tuple.type === '*') - .map(tuple => asPromise>(() => tuple.factory.createDebugAdapterTracker(session)).then(p => p, err => null)); + .map(tuple => asPromise>(() => tuple.factory.createDebugAdapterTracker(session.api)).then(p => p, err => null)); return Promise.race([ Promise.all(promises).then(result => { @@ -901,7 +901,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E if (adapterDescriptorFactory) { const extensionRegistry = await this._extensionService.getExtensionRegistry(); - return asPromise(() => adapterDescriptorFactory.createDebugAdapterDescriptor(session, this.daExecutableFromPackage(session, extensionRegistry))).then(daDescriptor => { + return asPromise(() => adapterDescriptorFactory.createDebugAdapterDescriptor(session.api, this.daExecutableFromPackage(session, extensionRegistry))).then(daDescriptor => { if (daDescriptor) { return daDescriptor; } @@ -940,7 +940,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E if (!ds) { const folder = await this.getFolder(dto.folderUri); const parent = dto.parent ? this._debugSessions.get(dto.parent) : undefined; - ds = new ExtHostDebugSession(this._debugServiceProxy, dto.id, dto.type, dto.name, folder, dto.configuration, parent); + ds = new ExtHostDebugSession(this._debugServiceProxy, dto.id, dto.type, dto.name, folder, dto.configuration, parent?.api); this._debugSessions.set(ds.id, ds); this._debugServiceProxy.$sessionCached(ds.id); } @@ -1003,7 +1003,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E } } -export class ExtHostDebugSession implements vscode.DebugSession { +export class ExtHostDebugSession { constructor( private _debugServiceProxy: MainThreadDebugServiceShape, @@ -1015,6 +1015,30 @@ export class ExtHostDebugSession implements vscode.DebugSession { private _parentSession: vscode.DebugSession | undefined) { } + public get api(): vscode.DebugSession { + const that = this; + return Object.freeze({ + id: that._id, + type: that._type, + get name() { + return that._name; + }, + set name(name: string) { + that._name = name; + that._debugServiceProxy.$setDebugSessionName(that._id, name); + }, + parentSession: that._parentSession, + workspaceFolder: that._workspaceFolder, + configuration: that._configuration, + customRequest(command: string, args: any): Promise { + return that._debugServiceProxy.$customDebugAdapterRequest(that._id, command, args); + }, + getDebugProtocolBreakpoint(breakpoint: vscode.Breakpoint): Promise { + return that._debugServiceProxy.$getDebugProtocolBreakpoint(that._id, breakpoint.id); + } + }); + } + public get id(): string { return this._id; } @@ -1023,37 +1047,13 @@ export class ExtHostDebugSession implements vscode.DebugSession { return this._type; } - public get name(): string { - return this._name; - } - public set name(name: string) { - this._name = name; - this._debugServiceProxy.$setDebugSessionName(this._id, name); - } - - public get parentSession(): vscode.DebugSession | undefined { - return this._parentSession; - } - _acceptNameChanged(name: string) { this._name = name; } - public get workspaceFolder(): vscode.WorkspaceFolder | undefined { - return this._workspaceFolder; - } - public get configuration(): vscode.DebugConfiguration { return this._configuration; } - - public customRequest(command: string, args: any): Promise { - return this._debugServiceProxy.$customDebugAdapterRequest(this._id, command, args); - } - - public getDebugProtocolBreakpoint(breakpoint: vscode.Breakpoint): Promise { - return this._debugServiceProxy.$getDebugProtocolBreakpoint(this._id, breakpoint.id); - } } export class ExtHostDebugConsole { From 70f1fd9b8f7a97694231d018a90c76f034fa6c39 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 29 Jan 2024 12:01:00 -0800 Subject: [PATCH 0699/1897] Reduce code duplication in pty impls Fixes #203663 --- .../contrib/terminal/browser/remotePty.ts | 101 +-------------- .../contrib/terminal/common/basePty.ts | 112 +++++++++++++++++ .../terminal/electron-sandbox/localPty.ts | 115 +++--------------- 3 files changed, 134 insertions(+), 194 deletions(-) create mode 100644 src/vs/workbench/contrib/terminal/common/basePty.ts diff --git a/src/vs/workbench/contrib/terminal/browser/remotePty.ts b/src/vs/workbench/contrib/terminal/browser/remotePty.ts index 8ca70a8726d..9e7d6d92623 100644 --- a/src/vs/workbench/contrib/terminal/browser/remotePty.ts +++ b/src/vs/workbench/contrib/terminal/browser/remotePty.ts @@ -10,48 +10,21 @@ import { mark } from 'vs/base/common/performance'; import { URI } from 'vs/base/common/uri'; import { IPtyHostProcessReplayEvent, ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { IProcessDataEvent, ITerminalChildProcess, ITerminalLaunchError, IProcessProperty, IProcessPropertyMap, ProcessPropertyType, IProcessReadyEvent, ITerminalLogService } from 'vs/platform/terminal/common/terminal'; +import { BasePty } from 'vs/workbench/contrib/terminal/common/basePty'; import { RemoteTerminalChannelClient } from 'vs/workbench/contrib/terminal/common/remote/remoteTerminalChannel'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -export class RemotePty extends Disposable implements ITerminalChildProcess { +export class RemotePty extends BasePty implements ITerminalChildProcess { private readonly _startBarrier: Barrier; - private readonly _properties: IProcessPropertyMap = { - cwd: '', - initialCwd: '', - fixedDimensions: { cols: undefined, rows: undefined }, - title: '', - shellType: undefined, - hasChildProcesses: true, - resolvedShellLaunchConfig: {}, - overrideDimensions: undefined, - failedShellIntegrationActivation: false, - usedShellIntegrationInjection: undefined - }; - private readonly _lastDimensions: { cols: number; rows: number } = { cols: -1, rows: -1 }; - - private _inReplay = false; - - private readonly _onProcessData = this._register(new Emitter()); - readonly onProcessData = this._onProcessData.event; - private readonly _onProcessReplayComplete = this._register(new Emitter()); - readonly onProcessReplayComplete = this._onProcessReplayComplete.event; - private readonly _onProcessReady = this._register(new Emitter()); - readonly onProcessReady = this._onProcessReady.event; - private readonly _onDidChangeProperty = this._register(new Emitter>()); - readonly onDidChangeProperty = this._onDidChangeProperty.event; - private readonly _onProcessExit = this._register(new Emitter()); - readonly onProcessExit = this._onProcessExit.event; - private readonly _onRestoreCommands = this._register(new Emitter()); - readonly onRestoreCommands = this._onRestoreCommands.event; constructor( - readonly id: number, - readonly shouldPersist: boolean, + id: number, + shouldPersist: boolean, private readonly _remoteTerminalChannel: RemoteTerminalChannelClient, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @ITerminalLogService private readonly _logService: ITerminalLogService ) { - super(); + super(id, shouldPersist); this._startBarrier = new Barrier(); } @@ -134,14 +107,6 @@ export class RemotePty extends Disposable implements ITerminalChildProcess { return this._remoteTerminalChannel.setUnicodeVersion(this.id, version); } - async getInitialCwd(): Promise { - return this._properties.initialCwd; - } - - async getCwd(): Promise { - return this._properties.cwd || this._properties.initialCwd; - } - async refreshProperty(type: T): Promise { return this._remoteTerminalChannel.refreshProperty(this.id, type); } @@ -150,62 +115,6 @@ export class RemotePty extends Disposable implements ITerminalChildProcess { return this._remoteTerminalChannel.updateProperty(this.id, type, value); } - handleData(e: string | IProcessDataEvent) { - this._onProcessData.fire(e); - } - handleExit(e: number | undefined) { - this._onProcessExit.fire(e); - } - processBinary(e: string): Promise { - return this._remoteTerminalChannel.processBinary(this.id, e); - } - handleReady(e: IProcessReadyEvent) { - this._onProcessReady.fire(e); - } - handleDidChangeProperty({ type, value }: IProcessProperty) { - switch (type) { - case ProcessPropertyType.Cwd: - this._properties.cwd = value; - break; - case ProcessPropertyType.InitialCwd: - this._properties.initialCwd = value; - break; - case ProcessPropertyType.ResolvedShellLaunchConfig: - if (value.cwd && typeof value.cwd !== 'string') { - value.cwd = URI.revive(value.cwd); - } - } - this._onDidChangeProperty.fire({ type, value }); - } - - async handleReplay(e: IPtyHostProcessReplayEvent) { - mark(`code/terminal/willHandleReplay/${this.id}`); - try { - this._inReplay = true; - for (const innerEvent of e.events) { - if (innerEvent.cols !== 0 || innerEvent.rows !== 0) { - // never override with 0x0 as that is a marker for an unknown initial size - this._onDidChangeProperty.fire({ type: ProcessPropertyType.OverrideDimensions, value: { cols: innerEvent.cols, rows: innerEvent.rows, forceExactSize: true } }); - } - const e: IProcessDataEvent = { data: innerEvent.data, trackCommit: true }; - this._onProcessData.fire(e); - await e.writePromise; - } - } finally { - this._inReplay = false; - } - - if (e.commands) { - this._onRestoreCommands.fire(e.commands); - } - - // remove size override - this._onDidChangeProperty.fire({ type: ProcessPropertyType.OverrideDimensions, value: undefined }); - - mark(`code/terminal/didHandleReplay/${this.id}`); - this._onProcessReplayComplete.fire(); - } - handleOrphanQuestion() { this._remoteTerminalChannel.orphanQuestionReply(this.id); } diff --git a/src/vs/workbench/contrib/terminal/common/basePty.ts b/src/vs/workbench/contrib/terminal/common/basePty.ts new file mode 100644 index 00000000000..3680b1ff1ff --- /dev/null +++ b/src/vs/workbench/contrib/terminal/common/basePty.ts @@ -0,0 +1,112 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { mark } from 'vs/base/common/performance'; +import { URI } from 'vs/base/common/uri'; +import type { IPtyHostProcessReplayEvent, ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; +import { ProcessPropertyType, type IProcessDataEvent, type IProcessProperty, type IProcessPropertyMap, type IProcessReadyEvent, type ITerminalChildProcess } from 'vs/platform/terminal/common/terminal'; + +/** + * Responsible for establishing and maintaining a connection with an existing terminal process + * created on the local pty host. + */ +export abstract class BasePty extends Disposable implements Partial { + protected readonly _properties: IProcessPropertyMap = { + cwd: '', + initialCwd: '', + fixedDimensions: { cols: undefined, rows: undefined }, + title: '', + shellType: undefined, + hasChildProcesses: true, + resolvedShellLaunchConfig: {}, + overrideDimensions: undefined, + failedShellIntegrationActivation: false, + usedShellIntegrationInjection: undefined + }; + protected readonly _lastDimensions: { cols: number; rows: number } = { cols: -1, rows: -1 }; + protected _inReplay = false; + + protected readonly _onProcessData = this._register(new Emitter()); + readonly onProcessData = this._onProcessData.event; + protected readonly _onProcessReplayComplete = this._register(new Emitter()); + readonly onProcessReplayComplete = this._onProcessReplayComplete.event; + protected readonly _onProcessReady = this._register(new Emitter()); + readonly onProcessReady = this._onProcessReady.event; + protected readonly _onDidChangeProperty = this._register(new Emitter>()); + readonly onDidChangeProperty = this._onDidChangeProperty.event; + protected readonly _onProcessExit = this._register(new Emitter()); + readonly onProcessExit = this._onProcessExit.event; + protected readonly _onRestoreCommands = this._register(new Emitter()); + readonly onRestoreCommands = this._onRestoreCommands.event; + + constructor( + readonly id: number, + readonly shouldPersist: boolean + ) { + super(); + } + + async getInitialCwd(): Promise { + return this._properties.initialCwd; + } + + async getCwd(): Promise { + return this._properties.cwd || this._properties.initialCwd; + } + + handleData(e: string | IProcessDataEvent) { + this._onProcessData.fire(e); + } + handleExit(e: number | undefined) { + this._onProcessExit.fire(e); + } + handleReady(e: IProcessReadyEvent) { + this._onProcessReady.fire(e); + } + handleDidChangeProperty({ type, value }: IProcessProperty) { + switch (type) { + case ProcessPropertyType.Cwd: + this._properties.cwd = value; + break; + case ProcessPropertyType.InitialCwd: + this._properties.initialCwd = value; + break; + case ProcessPropertyType.ResolvedShellLaunchConfig: + if (value.cwd && typeof value.cwd !== 'string') { + value.cwd = URI.revive(value.cwd); + } + } + this._onDidChangeProperty.fire({ type, value }); + } + async handleReplay(e: IPtyHostProcessReplayEvent) { + mark(`code/terminal/willHandleReplay/${this.id}`); + try { + this._inReplay = true; + for (const innerEvent of e.events) { + if (innerEvent.cols !== 0 || innerEvent.rows !== 0) { + // never override with 0x0 as that is a marker for an unknown initial size + this._onDidChangeProperty.fire({ type: ProcessPropertyType.OverrideDimensions, value: { cols: innerEvent.cols, rows: innerEvent.rows, forceExactSize: true } }); + } + const e: IProcessDataEvent = { data: innerEvent.data, trackCommit: true }; + this._onProcessData.fire(e); + await e.writePromise; + } + } finally { + this._inReplay = false; + } + + if (e.commands) { + this._onRestoreCommands.fire(e.commands); + } + + // remove size override + this._onDidChangeProperty.fire({ type: ProcessPropertyType.OverrideDimensions, value: undefined }); + + mark(`code/terminal/didHandleReplay/${this.id}`); + this._onProcessReplayComplete.fire(); + } +} diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts index fd083280d28..340f958856c 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts @@ -3,76 +3,48 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IProcessDataEvent, ITerminalChildProcess, ITerminalLaunchError, IProcessProperty, IProcessPropertyMap, ProcessPropertyType, IProcessReadyEvent, IPtyService } from 'vs/platform/terminal/common/terminal'; -import { URI } from 'vs/base/common/uri'; -import { IPtyHostProcessReplayEvent, ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; -import { mark } from 'vs/base/common/performance'; +import { IProcessPropertyMap, IPtyService, ITerminalChildProcess, ITerminalLaunchError, ProcessPropertyType } from 'vs/platform/terminal/common/terminal'; +import { BasePty } from 'vs/workbench/contrib/terminal/common/basePty'; /** * Responsible for establishing and maintaining a connection with an existing terminal process * created on the local pty host. */ -export class LocalPty extends Disposable implements ITerminalChildProcess { - private readonly _properties: IProcessPropertyMap = { - cwd: '', - initialCwd: '', - fixedDimensions: { cols: undefined, rows: undefined }, - title: '', - shellType: undefined, - hasChildProcesses: true, - resolvedShellLaunchConfig: {}, - overrideDimensions: undefined, - failedShellIntegrationActivation: false, - usedShellIntegrationInjection: undefined - }; - private readonly _lastDimensions: { cols: number; rows: number } = { cols: -1, rows: -1 }; - - private _inReplay = false; - - private readonly _onProcessData = this._register(new Emitter()); - readonly onProcessData = this._onProcessData.event; - private readonly _onProcessReplayComplete = this._register(new Emitter()); - readonly onProcessReplayComplete = this._onProcessReplayComplete.event; - private readonly _onProcessReady = this._register(new Emitter()); - readonly onProcessReady = this._onProcessReady.event; - private readonly _onDidChangeProperty = this._register(new Emitter>()); - readonly onDidChangeProperty = this._onDidChangeProperty.event; - private readonly _onProcessExit = this._register(new Emitter()); - readonly onProcessExit = this._onProcessExit.event; - private readonly _onRestoreCommands = this._register(new Emitter()); - readonly onRestoreCommands = this._onRestoreCommands.event; - +export class LocalPty extends BasePty implements ITerminalChildProcess { constructor( - readonly id: number, - readonly shouldPersist: boolean, + id: number, + shouldPersist: boolean, private readonly _proxy: IPtyService ) { - super(); + super(id, shouldPersist); } start(): Promise { return this._proxy.start(this.id); } + detach(forcePersist?: boolean): Promise { return this._proxy.detachFromProcess(this.id, forcePersist); } + shutdown(immediate: boolean): void { this._proxy.shutdown(this.id, immediate); } + async processBinary(data: string): Promise { if (this._inReplay) { return; } return this._proxy.processBinary(this.id, data); } + input(data: string): void { if (this._inReplay) { return; } this._proxy.input(this.id, data); } + resize(cols: number, rows: number): void { if (this._inReplay || this._lastDimensions.cols === cols && this._lastDimensions.rows === rows) { return; @@ -81,90 +53,37 @@ export class LocalPty extends Disposable implements ITerminalChildProcess { this._lastDimensions.rows = rows; this._proxy.resize(this.id, cols, rows); } + async clearBuffer(): Promise { this._proxy.clearBuffer?.(this.id); } + freePortKillProcess(port: string): Promise<{ port: string; processId: string }> { if (!this._proxy.freePortKillProcess) { throw new Error('freePortKillProcess does not exist on the local pty service'); } return this._proxy.freePortKillProcess(port); } - async getInitialCwd(): Promise { - return this._properties.initialCwd; - } - async getCwd(): Promise { - return this._properties.cwd || this._properties.initialCwd; - } + async refreshProperty(type: T): Promise { return this._proxy.refreshProperty(this.id, type); } + async updateProperty(type: T, value: IProcessPropertyMap[T]): Promise { return this._proxy.updateProperty(this.id, type, value); } + acknowledgeDataEvent(charCount: number): void { if (this._inReplay) { return; } this._proxy.acknowledgeDataEvent(this.id, charCount); } + setUnicodeVersion(version: '6' | '11'): Promise { return this._proxy.setUnicodeVersion(this.id, version); } - handleData(e: string | IProcessDataEvent) { - this._onProcessData.fire(e); - } - handleExit(e: number | undefined) { - this._onProcessExit.fire(e); - } - handleReady(e: IProcessReadyEvent) { - this._onProcessReady.fire(e); - } - handleDidChangeProperty({ type, value }: IProcessProperty) { - switch (type) { - case ProcessPropertyType.Cwd: - this._properties.cwd = value; - break; - case ProcessPropertyType.InitialCwd: - this._properties.initialCwd = value; - break; - case ProcessPropertyType.ResolvedShellLaunchConfig: - if (value.cwd && typeof value.cwd !== 'string') { - value.cwd = URI.revive(value.cwd); - } - } - this._onDidChangeProperty.fire({ type, value }); - } - - async handleReplay(e: IPtyHostProcessReplayEvent) { - mark(`code/terminal/willHandleReplay/${this.id}`); - try { - this._inReplay = true; - for (const innerEvent of e.events) { - if (innerEvent.cols !== 0 || innerEvent.rows !== 0) { - // never override with 0x0 as that is a marker for an unknown initial size - this._onDidChangeProperty.fire({ type: ProcessPropertyType.OverrideDimensions, value: { cols: innerEvent.cols, rows: innerEvent.rows, forceExactSize: true } }); - } - const e: IProcessDataEvent = { data: innerEvent.data, trackCommit: true }; - this._onProcessData.fire(e); - await e.writePromise; - } - } finally { - this._inReplay = false; - } - - if (e.commands) { - this._onRestoreCommands.fire(e.commands); - } - - // remove size override - this._onDidChangeProperty.fire({ type: ProcessPropertyType.OverrideDimensions, value: undefined }); - - mark(`code/terminal/didHandleReplay/${this.id}`); - this._onProcessReplayComplete.fire(); - } - handleOrphanQuestion() { this._proxy.orphanQuestionReply(this.id); } From 11315eea8a171f1ca6e8f39ec0669e0159d10433 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 29 Jan 2024 12:17:51 -0800 Subject: [PATCH 0700/1897] Remove duplicate styles Fixes #203652 --- .../suggest/browser/media/suggest.css | 58 ------------------- 1 file changed, 58 deletions(-) diff --git a/src/vs/workbench/services/suggest/browser/media/suggest.css b/src/vs/workbench/services/suggest/browser/media/suggest.css index 366f7b7d6fd..cf39f56b816 100644 --- a/src/vs/workbench/services/suggest/browser/media/suggest.css +++ b/src/vs/workbench/services/suggest/browser/media/suggest.css @@ -132,64 +132,6 @@ color: var(--vscode-editorSuggestWidget-focusHighlightForeground); } -/** Styles for each row in the list element **/ - -.workbench-suggest-widget .monaco-list .monaco-list-row { - display: flex; - -mox-box-sizing: border-box; - box-sizing: border-box; - padding-right: 10px; - background-repeat: no-repeat; - background-position: 2px 2px; - white-space: nowrap; - cursor: pointer; - touch-action: none; -} - -.workbench-suggest-widget .monaco-list .monaco-list-row.focused { - color: var(--vscode-editorSuggestWidget-selectedForeground); -} - -.workbench-suggest-widget .monaco-list .monaco-list-row.focused .codicon { - color: var(--vscode-editorSuggestWidget-selectedIconForeground); -} - -.workbench-suggest-widget .monaco-list .monaco-list-row > .contents { - flex: 1; - height: 100%; - overflow: hidden; - padding-left: 2px; -} - -.workbench-suggest-widget .monaco-list .monaco-list-row > .contents > .main { - display: flex; - overflow: hidden; - text-overflow: ellipsis; - white-space: pre; - justify-content: space-between; -} - -.workbench-suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left, -.workbench-suggest-widget .monaco-list .monaco-list-row > .contents > .main > .right { - display: flex; -} - -.workbench-suggest-widget .monaco-list .monaco-list-row:not(.focused) > .contents > .main .monaco-icon-label { - color: var(--vscode-editorSuggestWidget-foreground); -} - -.workbench-suggest-widget:not(.frozen) .monaco-highlighted-label .highlight { - font-weight: bold; -} - -.workbench-suggest-widget .monaco-list .monaco-list-row > .contents > .main .monaco-highlighted-label .highlight { - color: var(--vscode-editorSuggestWidget-highlightForeground); -} - -.workbench-suggest-widget .monaco-list .monaco-list-row.focused > .contents > .main .monaco-highlighted-label .highlight { - color: var(--vscode-editorSuggestWidget-focusHighlightForeground); -} - /** Styles for each row in the list **/ .workbench-suggest-widget .monaco-list .monaco-list-row .monaco-icon-label.deprecated { From 9a8ca92096df679fa0be8ab52c8fa4f118b5c550 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 29 Jan 2024 12:20:51 -0800 Subject: [PATCH 0701/1897] Fix compile/hygiene --- .../workbench/contrib/terminal/browser/remotePty.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/remotePty.ts b/src/vs/workbench/contrib/terminal/browser/remotePty.ts index 9e7d6d92623..f48e808a45e 100644 --- a/src/vs/workbench/contrib/terminal/browser/remotePty.ts +++ b/src/vs/workbench/contrib/terminal/browser/remotePty.ts @@ -4,12 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Barrier } from 'vs/base/common/async'; -import { Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { mark } from 'vs/base/common/performance'; -import { URI } from 'vs/base/common/uri'; -import { IPtyHostProcessReplayEvent, ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; -import { IProcessDataEvent, ITerminalChildProcess, ITerminalLaunchError, IProcessProperty, IProcessPropertyMap, ProcessPropertyType, IProcessReadyEvent, ITerminalLogService } from 'vs/platform/terminal/common/terminal'; +import { IProcessPropertyMap, ITerminalChildProcess, ITerminalLaunchError, ITerminalLogService, ProcessPropertyType } from 'vs/platform/terminal/common/terminal'; import { BasePty } from 'vs/workbench/contrib/terminal/common/basePty'; import { RemoteTerminalChannelClient } from 'vs/workbench/contrib/terminal/common/remote/remoteTerminalChannel'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -70,6 +65,10 @@ export class RemotePty extends BasePty implements ITerminalChildProcess { }); } + processBinary(e: string): Promise { + return this._remoteTerminalChannel.processBinary(this.id, e); + } + resize(cols: number, rows: number): void { if (this._inReplay || this._lastDimensions.cols === cols && this._lastDimensions.rows === rows) { return; From 5407ab62385bc1fd0e45f5aa708aac16533478a9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 29 Jan 2024 12:29:30 -0800 Subject: [PATCH 0702/1897] Share context key in terminal Context: https://github.com/microsoft/vscode/commit/8fdc7542e99265a07f3bc6e512d074c4c0fa8721 --- .../contrib/terminal/browser/terminalMenus.ts | 48 +------------------ .../terminal/common/terminalContextKey.ts | 28 ++++++++++- 2 files changed, 29 insertions(+), 47 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index c7ba657cb79..7f119914b09 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -426,29 +426,7 @@ export function setupTerminalMenus(): void { }, group: 'navigation', order: 2, - when: ContextKeyExpr.and( - ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), - ContextKeyExpr.notEquals(`config.${TerminalSettingId.TabsHideCondition}`, 'never'), - ContextKeyExpr.or( - ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`), - ContextKeyExpr.and( - ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActions}`, 'singleTerminal'), - ContextKeyExpr.equals(TerminalContextKeyStrings.GroupCount, 1) - ), - ContextKeyExpr.and( - ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActions}`, 'singleTerminalOrNarrow'), - ContextKeyExpr.or( - ContextKeyExpr.equals(TerminalContextKeyStrings.GroupCount, 1), - ContextKeyExpr.has(TerminalContextKeyStrings.TabsNarrow) - ) - ), - ContextKeyExpr.and( - ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActions}`, 'singleGroup'), - ContextKeyExpr.equals(TerminalContextKeyStrings.GroupCount, 1) - ), - ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActions}`, 'always') - ) - ) + when: TerminalContextKeys.shouldShowViewInlineActions } }, { @@ -461,29 +439,7 @@ export function setupTerminalMenus(): void { }, group: 'navigation', order: 3, - when: ContextKeyExpr.and( - ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), - ContextKeyExpr.notEquals(`config.${TerminalSettingId.TabsHideCondition}`, 'never'), - ContextKeyExpr.or( - ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`), - ContextKeyExpr.and( - ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActions}`, 'singleTerminal'), - ContextKeyExpr.equals(TerminalContextKeyStrings.GroupCount, 1) - ), - ContextKeyExpr.and( - ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActions}`, 'singleTerminalOrNarrow'), - ContextKeyExpr.or( - ContextKeyExpr.equals(TerminalContextKeyStrings.GroupCount, 1), - ContextKeyExpr.has(TerminalContextKeyStrings.TabsNarrow) - ) - ), - ContextKeyExpr.and( - ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActions}`, 'singleGroup'), - ContextKeyExpr.equals(TerminalContextKeyStrings.GroupCount, 1) - ), - ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActions}`, 'always') - ) - ) + when: TerminalContextKeys.shouldShowViewInlineActions } }, { diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 1073cf8767d..72335480aee 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -4,7 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; export const enum TerminalContextKeyStrings { IsOpen = 'terminalIsOpen', @@ -132,4 +134,28 @@ export namespace TerminalContextKeys { /** Whether shell integration is enabled in the active terminal. This only considers full VS Code shell integration. */ export const terminalShellIntegrationEnabled = new RawContextKey(TerminalContextKeyStrings.TerminalShellIntegrationEnabled, false, localize('terminalShellIntegrationEnabled', "Whether shell integration is enabled in the active terminal")); + + export const shouldShowViewInlineActions = ContextKeyExpr.and( + ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), + ContextKeyExpr.notEquals(`config.${TerminalSettingId.TabsHideCondition}`, 'never'), + ContextKeyExpr.or( + ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`), + ContextKeyExpr.and( + ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActions}`, 'singleTerminal'), + ContextKeyExpr.equals(TerminalContextKeyStrings.GroupCount, 1) + ), + ContextKeyExpr.and( + ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActions}`, 'singleTerminalOrNarrow'), + ContextKeyExpr.or( + ContextKeyExpr.equals(TerminalContextKeyStrings.GroupCount, 1), + ContextKeyExpr.has(TerminalContextKeyStrings.TabsNarrow) + ) + ), + ContextKeyExpr.and( + ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActions}`, 'singleGroup'), + ContextKeyExpr.equals(TerminalContextKeyStrings.GroupCount, 1) + ), + ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActions}`, 'always') + ) + ); } From 6693b1d8ed371a3ac2a9993e3b2fa4d79c9306e6 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 29 Jan 2024 18:26:20 -0300 Subject: [PATCH 0703/1897] Fix tooltip on inline references in chat (#203737) --- .../contrib/chat/browser/chatListRenderer.ts | 8 +- .../chatMarkdownDecorationsRenderer.ts | 139 +++++++++++------- 2 files changed, 88 insertions(+), 59 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 3336d00c788..7ca6a947efc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -51,7 +51,7 @@ import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibil import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; -import { annotateSpecialMarkdownContent, convertParsedRequestToMarkdown, extractVulnerabilitiesFromText, walkTreeAndAnnotateReferenceLinks } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; +import { ChatMarkdownDecorationsRenderer, annotateSpecialMarkdownContent, extractVulnerabilitiesFromText } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; import { CodeBlockPart, ICodeBlockData, ICodeBlockPart } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents'; @@ -109,6 +109,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer(); private readonly renderer: MarkdownRenderer; + private readonly markdownDecorationsRenderer: ChatMarkdownDecorationsRenderer; protected readonly _onDidClickFollowup = this._register(new Emitter()); readonly onDidClickFollowup: Event = this._onDidClickFollowup.event; @@ -142,6 +143,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.codeBlocksByResponseId.delete(element.id))); } - this.instantiationService.invokeFunction(acc => walkTreeAndAnnotateReferenceLinks(acc, result.element)); + this.markdownDecorationsRenderer.walkTreeAndAnnotateReferenceLinks(result.element); orderedDisposablesList.reverse().forEach(d => disposables.add(d)); return { diff --git a/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts index 6a72ca4a951..1f3dad5c4df 100644 --- a/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts @@ -4,88 +4,115 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { revive } from 'vs/base/common/marshalling'; import { basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { IRange } from 'vs/editor/common/core/range'; import { Location } from 'vs/editor/common/languages'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ILabelService } from 'vs/platform/label/common/label'; +import { ILogService } from 'vs/platform/log/common/log'; import { IChatProgressRenderableResponseContent, IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestDynamicVariablePart, ChatRequestTextPart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatAgentMarkdownContentWithVulnerability, IChatAgentVulnerabilityDetails, IChatContentInlineReference } from 'vs/workbench/contrib/chat/common/chatService'; const variableRefUrl = 'http://_vscodedecoration_'; -export function convertParsedRequestToMarkdown(accessor: ServicesAccessor, parsedRequest: IParsedChatRequest): string { - let result = ''; - for (const part of parsedRequest.parts) { - if (part instanceof ChatRequestTextPart) { - result += part.text; - } else { - const labelService = accessor.get(ILabelService); - const uri = part instanceof ChatRequestDynamicVariablePart && part.data.map(d => d.value).find((d): d is URI => d instanceof URI) - || undefined; - const title = uri ? labelService.getUriLabel(uri, { relative: true }) : ''; +export class ChatMarkdownDecorationsRenderer { + constructor( + @IKeybindingService private readonly keybindingService: IKeybindingService, + @ILabelService private readonly labelService: ILabelService, + @ILogService private readonly logService: ILogService + ) { - result += `[${part.text}](${variableRefUrl}${title})`; - } } - return result; -} + convertParsedRequestToMarkdown(parsedRequest: IParsedChatRequest): string { + let result = ''; + for (const part of parsedRequest.parts) { + if (part instanceof ChatRequestTextPart) { + result += part.text; + } else { + const uri = part instanceof ChatRequestDynamicVariablePart && part.data.map(d => d.value).find((d): d is URI => d instanceof URI) + || undefined; + const title = uri ? this.labelService.getUriLabel(uri, { relative: true }) : ''; -export function walkTreeAndAnnotateReferenceLinks(accessor: ServicesAccessor, element: HTMLElement): void { - const keybindingService = accessor.get(IKeybindingService); - - element.querySelectorAll('a').forEach(a => { - const href = a.getAttribute('data-href'); - if (href) { - if (href.startsWith(variableRefUrl)) { - const title = href.slice(variableRefUrl.length); - a.parentElement!.replaceChild( - renderResourceWidget(a.textContent!, title), - a); - } else if (href.startsWith(contentRefUrl)) { - renderFileWidget(href, a); - } else if (href.startsWith('command:')) { - injectKeybindingHint(a, href, keybindingService); + result += `[${part.text}](${variableRefUrl}${title})`; } } - }); -} -function injectKeybindingHint(a: HTMLAnchorElement, href: string, keybindingService: IKeybindingService): void { - const command = href.match(/command:([^\)]+)/)?.[1]; - if (command) { - const kb = keybindingService.lookupKeybinding(command); - if (kb) { - const keybinding = kb.getLabel(); - if (keybinding) { - a.textContent = `${a.textContent} (${keybinding})`; + return result; + } + + walkTreeAndAnnotateReferenceLinks(element: HTMLElement): void { + element.querySelectorAll('a').forEach(a => { + const href = a.getAttribute('data-href'); + if (href) { + if (href.startsWith(variableRefUrl)) { + const title = href.slice(variableRefUrl.length); + a.parentElement!.replaceChild( + this.renderResourceWidget(a.textContent!, title), + a); + } else if (href.startsWith(contentRefUrl)) { + this.renderFileWidget(href, a); + } else if (href.startsWith('command:')) { + this.injectKeybindingHint(a, href, this.keybindingService); + } + } + }); + } + + private renderFileWidget(href: string, a: HTMLAnchorElement): void { + // TODO this can be a nicer FileLabel widget with an icon. Do a simple link for now. + const fullUri = URI.parse(href); + let location: Location | { uri: URI; range: undefined }; + try { + location = revive(JSON.parse(fullUri.fragment)); + } catch (err) { + this.logService.error('Invalid chat widget render data JSON', toErrorMessage(err)); + return; + } + + if (!location.uri || !URI.isUri(location.uri)) { + this.logService.error(`Invalid chat widget render data: ${fullUri.fragment}`); + return; + } + + const fragment = location.range ? `${location.range.startLineNumber}-${location.range.endLineNumber}` : ''; + a.setAttribute('data-href', location.uri.with({ fragment }).toString()); + + const label = this.labelService.getUriLabel(location.uri, { relative: true }); + a.title = location.range ? + `${label}#${location.range.startLineNumber}-${location.range.endLineNumber}` : + label; + } + + + private renderResourceWidget(name: string, title: string): HTMLElement { + const container = dom.$('span.chat-resource-widget'); + const alias = dom.$('span', undefined, name); + alias.title = title; + container.appendChild(alias); + return container; + } + + + private injectKeybindingHint(a: HTMLAnchorElement, href: string, keybindingService: IKeybindingService): void { + const command = href.match(/command:([^\)]+)/)?.[1]; + if (command) { + const kb = keybindingService.lookupKeybinding(command); + if (kb) { + const keybinding = kb.getLabel(); + if (keybinding) { + a.textContent = `${a.textContent} (${keybinding})`; + } } } } } -function renderResourceWidget(name: string, title: string): HTMLElement { - const container = dom.$('span.chat-resource-widget'); - const alias = dom.$('span', undefined, name); - alias.title = title; - container.appendChild(alias); - return container; -} - -function renderFileWidget(href: string, a: HTMLAnchorElement): void { - // TODO this can be a nicer FileLabel widget with an icon. Do a simple link for now. - const fullUri = URI.parse(href); - const location: Location | { uri: URI; range: undefined } = revive(JSON.parse(fullUri.fragment)); - const fragment = location.range ? `${location.range.startLineNumber}-${location.range.endLineNumber}` : ''; - a.setAttribute('data-href', location.uri.with({ fragment }).toString()); -} - export interface IMarkdownVulnerability { title: string; description: string; From 7fa7d085a3c8ae4feb67c9b0219a8d0bada3df17 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 29 Jan 2024 13:32:44 -0800 Subject: [PATCH 0704/1897] fix #203630 --- .../contrib/terminal/browser/terminalActions.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 05e73b607ce..9691ee4fc7b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -811,8 +811,12 @@ export function registerTerminalActions() { title: terminalStrings.changeIcon, f1: false, precondition: sharedWhenClause.terminalAvailable_and_singularSelection, - run: async (c, accessor) => { + run: async (c, accessor, args) => { let icon: TerminalIcon | undefined; + if (c.groupService.lastAccessedMenu === 'inline-tab') { + getResourceOrActiveInstance(c, args)?.changeIcon(); + return; + } for (const terminal of getSelectedInstances(accessor) ?? []) { icon = await terminal.changeIcon(icon); } @@ -831,9 +835,13 @@ export function registerTerminalActions() { title: terminalStrings.changeColor, f1: false, precondition: sharedWhenClause.terminalAvailable_and_singularSelection, - run: async (c, accessor) => { + run: async (c, accessor, args) => { let color: string | undefined; let i = 0; + if (c.groupService.lastAccessedMenu === 'inline-tab') { + getResourceOrActiveInstance(c, args)?.changeColor(); + return; + } for (const terminal of getSelectedInstances(accessor) ?? []) { const skipQuickPick = i !== 0; // Always show the quickpick on the first iteration @@ -1726,7 +1734,7 @@ function getSelectedInstances(accessor: ServicesAccessor, args?: unknown, args2? for (const selection of selections) { result.push(terminalService.getInstanceFromIndex(selection) as ITerminalInstance); } - return result; + return result.filter(r => !!r); } export function validateTerminalName(name: string): { content: string; severity: Severity } | null { From 1b25a13761d5e0db1308309a30ea8cf98a383796 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 29 Jan 2024 14:46:13 -0800 Subject: [PATCH 0705/1897] Fix #160621. Do no steal escape when focus is in input. (#203746) --- .../gotoSymbol/browser/peek/referencesController.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesController.ts b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesController.ts index 1eefd9eaa69..40d344f3e3a 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesController.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesController.ts @@ -27,6 +27,8 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { OneReference, ReferencesModel } from '../referencesModel'; import { LayoutData, ReferenceWidget } from './referencesWidget'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; export const ctxReferenceSearchVisible = new RawContextKey('referenceSearchVisible', false, nls.localize('referenceSearchVisible', "Whether reference peek is visible, like 'Peek References' or 'Peek Definition'")); @@ -367,7 +369,14 @@ KeybindingsRegistry.registerKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib + 50, primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], - when: ContextKeyExpr.and(ctxReferenceSearchVisible, ContextKeyExpr.not('config.editor.stablePeek')) + when: ContextKeyExpr.and( + ctxReferenceSearchVisible, + ContextKeyExpr.not('config.editor.stablePeek'), + ContextKeyExpr.or( + EditorContextKeys.editorTextFocus, + InputFocusedContext.negate() + ) + ) }); From ed35abb942246e199631601b5d3d39442d230ff7 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Mon, 29 Jan 2024 14:50:21 -0800 Subject: [PATCH 0706/1897] fix copy output command without context (#203582) * fix copy output without context * better title * clear output focus --- extensions/ipynb/package.json | 267 +++++++++--------- extensions/ipynb/package.nls.json | 2 +- .../browser/controller/cellOutputActions.ts | 17 +- .../notebook/browser/notebookBrowser.ts | 1 + .../notebook/browser/notebookEditorWidget.ts | 3 + .../view/renderers/backLayerWebView.ts | 2 +- 6 files changed, 157 insertions(+), 135 deletions(-) diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index aee7e85a30a..f29ed650162 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -1,133 +1,138 @@ { - "name": "ipynb", - "displayName": "%displayName%", - "description": "%description%", - "publisher": "vscode", - "version": "1.0.0", - "license": "MIT", - "icon": "media/icon.png", - "engines": { - "vscode": "^1.57.0" - }, - "enabledApiProposals": [ - "documentPaste", - "diffContentOptions", - "dropMetadata" - ], - "activationEvents": [ - "onNotebook:jupyter-notebook", - "onNotebookSerializer:interactive" - ], - "extensionKind": [ - "workspace", - "ui" - ], - "main": "./out/ipynbMain.js", - "browser": "./dist/browser/ipynbMain.js", - "capabilities": { - "virtualWorkspaces": true, - "untrustedWorkspaces": { - "supported": true - } - }, - "contributes": { - "configuration": [ - { - "properties": { - "ipynb.pasteImagesAsAttachments.enabled": { - "type": "boolean", - "scope": "resource", - "markdownDescription": "%ipynb.pasteImagesAsAttachments.enabled%", - "default": true - } - } - } - ], - "commands": [ - { - "command": "ipynb.newUntitledIpynb", - "title": "%newUntitledIpynb.title%", - "shortTitle": "%newUntitledIpynb.shortTitle%", - "category": "Create" - }, - { - "command": "ipynb.openIpynbInNotebookEditor", - "title": "%openIpynbInNotebookEditor.title%" - }, - { - "command": "ipynb.cleanInvalidImageAttachment", - "title": "%cleanInvalidImageAttachment.title%" - }, - { - "command": "notebook.cellOutput.copy", - "title": "%copyCellOutput.title%" - } - ], - "notebooks": [ - { - "type": "jupyter-notebook", - "displayName": "Jupyter Notebook", - "selector": [ - { - "filenamePattern": "*.ipynb" - } - ], - "priority": "default" - } - ], - "notebookRenderer": [ - { - "id": "vscode.markdown-it-cell-attachment-renderer", - "displayName": "%markdownAttachmentRenderer.displayName%", - "entrypoint": { - "extends": "vscode.markdown-it-renderer", - "path": "./notebook-out/cellAttachmentRenderer.js" - } - } - ], - "menus": { - "file/newFile": [ - { - "command": "ipynb.newUntitledIpynb", - "group": "notebook" - } - ], - "commandPalette": [ - { - "command": "ipynb.newUntitledIpynb" - }, - { - "command": "ipynb.openIpynbInNotebookEditor", - "when": "false" - }, - { - "command": "ipynb.cleanInvalidImageAttachment", - "when": "false" - } - ], - "webview/context": [ - { - "command": "notebook.cellOutput.copy", - "when": "webviewId == 'notebook.output' && webviewSection == 'image'" - } - ] - } - }, - "scripts": { - "compile": "npx gulp compile-extension:ipynb && npm run build-notebook", - "watch": "npx gulp watch-extension:ipynb", - "build-notebook": "node ./esbuild" - }, - "dependencies": { - "@enonic/fnv-plus": "^1.3.0", - "detect-indent": "^6.0.0" - }, - "devDependencies": { - "@jupyterlab/nbformat": "^3.2.9", - "@types/markdown-it": "12.2.3" - }, - "repository": { - "type": "git", - "url": "https://github.com/microsoft/vscode.git" - } + "name": "ipynb", + "displayName": "%displayName%", + "description": "%description%", + "publisher": "vscode", + "version": "1.0.0", + "license": "MIT", + "icon": "media/icon.png", + "engines": { + "vscode": "^1.57.0" + }, + "enabledApiProposals": [ + "documentPaste", + "diffContentOptions", + "dropMetadata" + ], + "activationEvents": [ + "onNotebook:jupyter-notebook", + "onNotebookSerializer:interactive" + ], + "extensionKind": [ + "workspace", + "ui" + ], + "main": "./out/ipynbMain.js", + "browser": "./dist/browser/ipynbMain.js", + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": true + } + }, + "contributes": { + "configuration": [ + { + "properties": { + "ipynb.pasteImagesAsAttachments.enabled": { + "type": "boolean", + "scope": "resource", + "markdownDescription": "%ipynb.pasteImagesAsAttachments.enabled%", + "default": true + } + } + } + ], + "commands": [ + { + "command": "ipynb.newUntitledIpynb", + "title": "%newUntitledIpynb.title%", + "shortTitle": "%newUntitledIpynb.shortTitle%", + "category": "Create" + }, + { + "command": "ipynb.openIpynbInNotebookEditor", + "title": "%openIpynbInNotebookEditor.title%" + }, + { + "command": "ipynb.cleanInvalidImageAttachment", + "title": "%cleanInvalidImageAttachment.title%" + }, + { + "command": "notebook.cellOutput.copy", + "title": "%copyCellOutput.title%", + "category": "Notebook" + } + ], + "notebooks": [ + { + "type": "jupyter-notebook", + "displayName": "Jupyter Notebook", + "selector": [ + { + "filenamePattern": "*.ipynb" + } + ], + "priority": "default" + } + ], + "notebookRenderer": [ + { + "id": "vscode.markdown-it-cell-attachment-renderer", + "displayName": "%markdownAttachmentRenderer.displayName%", + "entrypoint": { + "extends": "vscode.markdown-it-renderer", + "path": "./notebook-out/cellAttachmentRenderer.js" + } + } + ], + "menus": { + "file/newFile": [ + { + "command": "ipynb.newUntitledIpynb", + "group": "notebook" + } + ], + "commandPalette": [ + { + "command": "ipynb.newUntitledIpynb" + }, + { + "command": "ipynb.openIpynbInNotebookEditor", + "when": "false" + }, + { + "command": "ipynb.cleanInvalidImageAttachment", + "when": "false" + }, + { + "command": "notebook.cellOutput.copy", + "when": "notebookCellHasOutputs" + } + ], + "webview/context": [ + { + "command": "notebook.cellOutput.copy", + "when": "webviewId == 'notebook.output' && webviewSection == 'image'" + } + ] + } + }, + "scripts": { + "compile": "npx gulp compile-extension:ipynb && npm run build-notebook", + "watch": "npx gulp watch-extension:ipynb", + "build-notebook": "node ./esbuild" + }, + "dependencies": { + "@enonic/fnv-plus": "^1.3.0", + "detect-indent": "^6.0.0" + }, + "devDependencies": { + "@jupyterlab/nbformat": "^3.2.9", + "@types/markdown-it": "12.2.3" + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + } } diff --git a/extensions/ipynb/package.nls.json b/extensions/ipynb/package.nls.json index 1f281c32dd3..af7d8f4ab47 100644 --- a/extensions/ipynb/package.nls.json +++ b/extensions/ipynb/package.nls.json @@ -6,7 +6,7 @@ "newUntitledIpynb.shortTitle": "Jupyter Notebook", "openIpynbInNotebookEditor.title": "Open IPYNB File In Notebook Editor", "cleanInvalidImageAttachment.title": "Clean Invalid Image Attachment Reference", - "copyCellOutput.title": "Copy Output", + "copyCellOutput.title": "Copy Cell Output", "markdownAttachmentRenderer.displayName": { "message": "Markdown-It ipynb Cell Attachment renderer", "comment": [ diff --git a/src/vs/workbench/contrib/notebook/browser/controller/cellOutputActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/cellOutputActions.ts index 1f73db4342a..1469d991522 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/cellOutputActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/cellOutputActions.ts @@ -33,7 +33,7 @@ registerAction2(class CopyCellOutputAction extends Action2 { }); } - async run(accessor: ServicesAccessor, outputContext: INotebookOutputActionContext | { outputViewModel: ICellOutputViewModel }): Promise { + async run(accessor: ServicesAccessor, outputContext: INotebookOutputActionContext | { outputViewModel: ICellOutputViewModel } | undefined): Promise { const editorService = accessor.get(IEditorService); const notebookEditor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); @@ -42,7 +42,20 @@ registerAction2(class CopyCellOutputAction extends Action2 { } let outputViewModel: ICellOutputViewModel | undefined; - if ('outputId' in outputContext && typeof outputContext.outputId === 'string') { + if (!outputContext) { + const activeCell = notebookEditor.getActiveCell(); + if (!activeCell) { + return; + } + + if (activeCell.focusedOutputId !== undefined) { + outputViewModel = activeCell.outputsViewModels.find(output => { + return output.model.outputId === activeCell.focusedOutputId; + }); + } else { + outputViewModel = activeCell.outputsViewModels.find(output => output.pickedMimeType?.isTrusted); + } + } else if ('outputId' in outputContext && typeof outputContext.outputId === 'string') { outputViewModel = getOutputViewModelFromId(outputContext.outputId, notebookEditor); } else { outputViewModel = outputContext.outputViewModel; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index d1841f036fe..fe53a8c5a0a 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -256,6 +256,7 @@ export interface ICellViewModel extends IGenericCellViewModel { lineNumbers: 'on' | 'off' | 'inherit'; chatHeight: number; focusMode: CellFocusMode; + focusedOutputId?: string | undefined; outputIsHovered: boolean; getText(): string; getTextLength(): number; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 19c6bd8118b..d8a3c555247 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2363,6 +2363,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return; } + cell.focusedOutputId = undefined; + if (focusItem === 'editor') { this.focusElement(cell); this._list.focusView(); @@ -2408,6 +2410,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD cell.updateEditState(CellEditState.Preview, 'focusNotebookCell'); cell.focusMode = CellFocusMode.Output; + cell.focusedOutputId = options?.outputId; if (!options?.skipReveal) { this.revealInCenterIfOutsideViewport(cell); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 9488f736711..dd421641fb8 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -667,7 +667,7 @@ export class BackLayerWebView extends Themable { const latestCell = this.notebookEditor.getCellByInfo(resolvedResult.cellInfo); if (latestCell) { latestCell.outputIsFocused = true; - this.notebookEditor.focusNotebookCell(latestCell, 'output', { skipReveal: true, outputWebviewFocused: true }); + this.notebookEditor.focusNotebookCell(latestCell, 'output', { outputId: resolvedResult.output.model.outputId, skipReveal: true, outputWebviewFocused: true }); } } break; From ea700efe616ab98f7fc4776ec4fa74ffcf7baa42 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 29 Jan 2024 15:12:14 -0800 Subject: [PATCH 0707/1897] fix #202025 --- .../browser/audioCues.contribution.ts | 4 + .../contrib/audioCues/browser/commands.ts | 109 ++++++++++-------- 2 files changed, 66 insertions(+), 47 deletions(-) diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index dcb9c506701..6d206412a5f 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -97,6 +97,10 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'description': localize('audioCues.terminalQuickFix', "Plays a sound when terminal Quick Fixes are available."), ...audioCueFeatureBase, }, + 'audioCues.terminalBell': { + 'description': localize('audioCues.terminalBell', "Plays a sound when the terminal bell is ringing."), + ...audioCueFeatureBase, + }, 'audioCues.diffLineInserted': { 'description': localize('audioCues.diffLineInserted', "Plays a sound when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), ...audioCueFeatureBase, diff --git a/src/vs/workbench/contrib/audioCues/browser/commands.ts b/src/vs/workbench/contrib/audioCues/browser/commands.ts index bda17b0a3e9..d4a5cd47f72 100644 --- a/src/vs/workbench/contrib/audioCues/browser/commands.ts +++ b/src/vs/workbench/contrib/audioCues/browser/commands.ts @@ -9,9 +9,9 @@ import { localize } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { Action2 } from 'vs/platform/actions/common/actions'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; export class ShowAudioCueHelp extends Action2 { static readonly ID = 'audioCues.help'; @@ -29,36 +29,44 @@ export class ShowAudioCueHelp extends Action2 { override async run(accessor: ServicesAccessor): Promise { const audioCueService = accessor.get(IAudioCueService); - const quickPickService = accessor.get(IQuickInputService); - const preferencesService = accessor.get(IPreferencesService); + const quickInputService = accessor.get(IQuickInputService); + const configurationService = accessor.get(IConfigurationService); const accessibilityService = accessor.get(IAccessibilityService); - + const userGestureCues = [AudioCue.save, AudioCue.format]; const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.map((cue, idx) => ({ - label: accessibilityService.isScreenReaderOptimized() ? - `${cue.name}${audioCueService.isCueEnabled(cue) ? '' : ' (' + localize('disabled', "Disabled") + ')'}` - : `${audioCueService.isCueEnabled(cue) ? '$(check)' : ' '} ${cue.name}`, + label: userGestureCues.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.settingsKey)})` : cue.name, audioCue: cue, - buttons: [{ + buttons: userGestureCues.includes(cue) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), tooltip: localize('audioCues.help.settings', 'Enable/Disable Audio Cue'), - }], + alwaysVisible: true + }] : [] })); - - const quickPick = quickPickService.pick( - items, - { - activeItem: items[0], - onDidFocus: (item) => { - audioCueService.playSound(item.audioCue.sound.getSound(true), true); - }, - onDidTriggerItemButton: (context) => { - preferencesService.openSettings({ query: context.item.audioCue.settingsKey }); - }, - placeHolder: localize('audioCues.help.placeholder', 'Select an audio cue to play'), + const qp = quickInputService.createQuickPick(); + qp.items = items; + qp.selectedItems = items.filter(i => audioCueService.isCueEnabled(i.audioCue)); + qp.onDidHide(() => { + const enabledCues = qp.selectedItems.map(i => i.audioCue); + const disabledCues = AudioCue.allAudioCues.filter(cue => !enabledCues.includes(cue)); + for (const cue of enabledCues) { + if (!userGestureCues.includes(cue)) { + configurationService.updateValue(cue.settingsKey, accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'); + } } - ); - - await quickPick; + for (const cue of disabledCues) { + if (userGestureCues.includes(cue)) { + configurationService.updateValue(cue.settingsKey, 'never'); + } else { + configurationService.updateValue(cue.settingsKey, 'off'); + } + } + }); + qp.onDidChangeActive(() => { + audioCueService.playSound(qp.activeItems[0].audioCue.sound.getSound(true), true); + }); + qp.placeholder = localize('audioCues.help.placeholder', 'Select an audio cue to play and configure'); + qp.canSelectMany = true; + await qp.show(); } } @@ -78,32 +86,39 @@ export class ShowAccessibilityAlertHelp extends Action2 { override async run(accessor: ServicesAccessor): Promise { const audioCueService = accessor.get(IAudioCueService); - const quickPickService = accessor.get(IQuickInputService); - const preferencesService = accessor.get(IPreferencesService); - const accessibilityService = accessor.get(IAccessibilityService); - - const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.filter(c => !!c.alertMessage).map((cue, idx) => ({ - label: accessibilityService.isScreenReaderOptimized() ? - `${cue.name}${audioCueService.isAlertEnabled(cue) ? '' : ' (' + localize('disabled', "Disabled") + ')'}` - : `${audioCueService.isAlertEnabled(cue) ? '$(check)' : ' '} ${cue.name}`, + const quickInputService = accessor.get(IQuickInputService); + const configurationService = accessor.get(IConfigurationService); + const userGestureAlerts = [AudioCue.save, AudioCue.format]; + const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.filter(c => c.alertSettingsKey).map((cue, idx) => ({ + label: userGestureAlerts.includes(cue) && cue.alertSettingsKey ? `${cue.name} (${configurationService.getValue(cue.alertSettingsKey)})` : cue.name, audioCue: cue, - buttons: [{ + buttons: userGestureAlerts.includes(cue) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), - tooltip: localize('alerts.help.settings', 'Enable/Disable Audio Cue'), - }], + tooltip: localize('alert.help.settings', 'Enable/Disable Alert'), + alwaysVisible: true + }] : [] })); - - const quickPick = quickPickService.pick( - items, - { - activeItem: items[0], - onDidTriggerItemButton: (context) => { - preferencesService.openSettings({ query: context.item.audioCue.alertSettingsKey }); - }, - placeHolder: localize('alerts.help.placeholder', 'Inspect and configure the status of an alert'), + const qp = quickInputService.createQuickPick(); + qp.items = items; + qp.selectedItems = items.filter(i => audioCueService.isAlertEnabled(i.audioCue)); + qp.onDidHide(() => { + const enabledAlerts = qp.selectedItems.map(i => i.audioCue); + const disabledAlerts = AudioCue.allAudioCues.filter(cue => !enabledAlerts.includes(cue)); + for (const cue of enabledAlerts) { + if (!userGestureAlerts.includes(cue)) { + configurationService.updateValue(cue.alertSettingsKey!, true); + } } - ); - - await quickPick; + for (const cue of disabledAlerts) { + if (userGestureAlerts.includes(cue)) { + configurationService.updateValue(cue.alertSettingsKey!, 'never'); + } else { + configurationService.updateValue(cue.alertSettingsKey!, false); + } + } + }); + qp.placeholder = localize('alert.help.placeholder', 'Select an alert to configure'); + qp.canSelectMany = true; + await qp.show(); } } From f29cc2750ee0c7ab394ab4b38ff3ebcd0d0be6ac Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 29 Jan 2024 15:14:27 -0800 Subject: [PATCH 0708/1897] set default for terminal bell --- .../contrib/audioCues/browser/audioCues.contribution.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index 6d206412a5f..1fced335827 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -100,6 +100,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'audioCues.terminalBell': { 'description': localize('audioCues.terminalBell', "Plays a sound when the terminal bell is ringing."), ...audioCueFeatureBase, + default: 'on' }, 'audioCues.diffLineInserted': { 'description': localize('audioCues.diffLineInserted', "Plays a sound when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), From f8a6171282a5b618ad1d205fd50f67042c1fbc04 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 29 Jan 2024 15:32:11 -0800 Subject: [PATCH 0709/1897] fix issues --- src/vs/platform/audioCues/browser/audioCueService.ts | 7 +++++-- src/vs/workbench/contrib/audioCues/browser/commands.ts | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 295b3c65f4d..0e35e7f9e3c 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -209,12 +209,12 @@ export class AudioCueService extends Disposable implements IAudioCueService { private readonly isAlertEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.settingsKey) + e.affectsConfiguration(event.cue.alertSettingsKey!) ), () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.alertSettingsKey) : false ); return derived(reader => { - /** @description audio cue enabled */ + /** @description alert enabled */ const setting = settingObservable.read(reader); if ( !this.screenReaderAttached.read(reader) @@ -226,6 +226,9 @@ export class AudioCueService extends Disposable implements IAudioCueService { }, JSON.stringify); public isAlertEnabled(cue: AudioCue, userGesture?: boolean): boolean { + if (!cue.alertSettingsKey) { + return false; + } return this.isAlertEnabledCache.get({ cue, userGesture }).get() ?? false; } diff --git a/src/vs/workbench/contrib/audioCues/browser/commands.ts b/src/vs/workbench/contrib/audioCues/browser/commands.ts index d4a5cd47f72..5b87791b573 100644 --- a/src/vs/workbench/contrib/audioCues/browser/commands.ts +++ b/src/vs/workbench/contrib/audioCues/browser/commands.ts @@ -45,7 +45,7 @@ export class ShowAudioCueHelp extends Action2 { const qp = quickInputService.createQuickPick(); qp.items = items; qp.selectedItems = items.filter(i => audioCueService.isCueEnabled(i.audioCue)); - qp.onDidHide(() => { + qp.onDidAccept(() => { const enabledCues = qp.selectedItems.map(i => i.audioCue); const disabledCues = AudioCue.allAudioCues.filter(cue => !enabledCues.includes(cue)); for (const cue of enabledCues) { @@ -60,6 +60,7 @@ export class ShowAudioCueHelp extends Action2 { configurationService.updateValue(cue.settingsKey, 'off'); } } + qp.hide(); }); qp.onDidChangeActive(() => { audioCueService.playSound(qp.activeItems[0].audioCue.sound.getSound(true), true); @@ -101,7 +102,7 @@ export class ShowAccessibilityAlertHelp extends Action2 { const qp = quickInputService.createQuickPick(); qp.items = items; qp.selectedItems = items.filter(i => audioCueService.isAlertEnabled(i.audioCue)); - qp.onDidHide(() => { + qp.onDidAccept(() => { const enabledAlerts = qp.selectedItems.map(i => i.audioCue); const disabledAlerts = AudioCue.allAudioCues.filter(cue => !enabledAlerts.includes(cue)); for (const cue of enabledAlerts) { @@ -116,6 +117,7 @@ export class ShowAccessibilityAlertHelp extends Action2 { configurationService.updateValue(cue.alertSettingsKey!, false); } } + qp.hide(); }); qp.placeholder = localize('alert.help.placeholder', 'Select an alert to configure'); qp.canSelectMany = true; From 84bdd343e759edd18b3fbc839acf890bd7ba3d9a Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Mon, 29 Jan 2024 17:56:58 -0600 Subject: [PATCH 0710/1897] Notebook Search eagerly activates extensions (#203748) * initial attempt for 'hasResult' in remote code * Notebook Search eagerly activates extensions Fixes #203389 --- .../notebookSearch/notebookSearchService.ts | 65 +++++++++++++++---- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts b/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts index daabfe2db21..368ca775406 100644 --- a/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts +++ b/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; +import * as glob from 'vs/base/common/glob'; import { ResourceSet, ResourceMap } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -12,13 +13,16 @@ import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/note import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { INotebookSearchService } from 'vs/workbench/contrib/search/common/notebookSearch'; import { INotebookCellMatchWithModel, INotebookFileMatchWithModel, contentMatchesToTextSearchMatches, webviewMatchesToTextSearchMatches } from 'vs/workbench/contrib/search/browser/notebookSearch/searchNotebookHelpers'; -import { ITextQuery, QueryType, ISearchProgressItem, ISearchComplete, ISearchConfigurationProperties, pathIncludedInQuery } from 'vs/workbench/services/search/common/search'; +import { ITextQuery, QueryType, ISearchProgressItem, ISearchComplete, ISearchConfigurationProperties, pathIncludedInQuery, ISearchService, IFolderQuery } from 'vs/workbench/services/search/common/search'; import * as arrays from 'vs/base/common/arrays'; import { isNumber } from 'vs/base/common/types'; import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; import { INotebookFileMatchNoModel } from 'vs/workbench/contrib/search/common/searchNotebookHelpers'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { NotebookPriorityInfo } from 'vs/workbench/contrib/search/common/search'; +import { INotebookExclusiveDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; interface IOpenNotebookSearchResults { results: ResourceMap; @@ -30,17 +34,20 @@ interface IClosedNotebookSearchResults { } export class NotebookSearchService implements INotebookSearchService { declare readonly _serviceBrand: undefined; + private queryBuilder: QueryBuilder; constructor( @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @INotebookEditorService private readonly notebookEditorService: INotebookEditorService, @ILogService private readonly logService: ILogService, @INotebookService private readonly notebookService: INotebookService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IEditorResolverService private readonly editorResolverService: IEditorResolverService + @IEditorResolverService private readonly editorResolverService: IEditorResolverService, + @ISearchService private readonly searchService: ISearchService, + @IInstantiationService instantiationService: IInstantiationService ) { + this.queryBuilder = instantiationService.createInstance(QueryBuilder); } - notebookSearch(query: ITextQuery, token: CancellationToken | undefined, searchInstanceID: string, onProgress?: (result: ISearchProgressItem) => void): { openFilesToScan: ResourceSet; completeData: Promise; @@ -109,14 +116,38 @@ export class NotebookSearchService implements INotebookSearchService { }; } + private async doesFileExist(includes: string[], folderQueries: IFolderQuery[], token: CancellationToken): Promise { + const promises: Promise[] = includes.map(async includePattern => { + const query = this.queryBuilder.file(folderQueries.map(e => e.folder), { + includePattern: includePattern.startsWith('/') ? includePattern : '**/' + includePattern, // todo: find cleaner way to ensure that globs match all appropriate filetypes + exists: true + }); + return this.searchService.fileSearch( + query, + token + ).then((ret) => { + if (!ret.limitHit) { + throw Error('File not found'); + } + }); + }); + + return Promise.any(promises).then(() => true).catch(() => false); + } + private async getClosedNotebookResults(textQuery: ITextQuery, scannedFiles: ResourceSet, token: CancellationToken): Promise { const userAssociations = this.editorResolverService.getAllUserAssociations(); const allPriorityInfo: Map = new Map(); const contributedNotebookTypes = this.notebookService.getContributedNotebookTypes(); + userAssociations.forEach(association => { + // we gather the editor associations here, but cannot check them until we actually have the files that the glob matches + // this is because longer patterns take precedence over shorter ones, and even if there is a user association that + // specifies the exact same glob as a contributed notebook type, there might be another user association that is longer/more specific + // that still matches the path and should therefore take more precedence. if (!association.filenamePattern) { return; } @@ -140,14 +171,26 @@ export class NotebookSearchService implements INotebookSearchService { } | undefined>[] = []; contributedNotebookTypes.forEach((notebook) => { - promises.push((async () => { - const canResolve = await this.notebookService.canResolve(notebook.id); - if (!canResolve) { - return undefined; - } - const serializer = (await this.notebookService.withNotebookDataProvider(notebook.id)).serializer; - return await serializer.searchInNotebooks(textQuery, token, allPriorityInfo); - })()); + if (notebook.selectors.length > 0) { + promises.push((async () => { + const includes = notebook.selectors.map((selector) => { + const globPattern = (selector as INotebookExclusiveDocumentFilter).include || selector as glob.IRelativePattern | string; + return globPattern.toString(); + }); + + const isInWorkspace = await this.doesFileExist(includes, textQuery.folderQueries, token); + if (isInWorkspace) { + const canResolve = await this.notebookService.canResolve(notebook.id); + if (!canResolve) { + return undefined; + } + const serializer = (await this.notebookService.withNotebookDataProvider(notebook.id)).serializer; + return await serializer.searchInNotebooks(textQuery, token, allPriorityInfo); + } else { + return undefined; + } + })()); + } }); const start = Date.now(); From b9511a9520eedb46c6d2e1ae6497784e45abc7d7 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Mon, 29 Jan 2024 16:06:36 -0800 Subject: [PATCH 0711/1897] Adjust WordHighlighter to use `hasTextFocus()` instead of `hasWidgetFocus()` (#203754) Potential fix for #201814 --- .../editor/contrib/wordHighlighter/browser/wordHighlighter.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts index 10b5069c80c..7824f8a6ea0 100644 --- a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts @@ -491,6 +491,7 @@ class WordHighlighter { private _stopAll(): void { // Remove any existing decorations + // TODO: @Yoyokrazy - this triggers as notebooks scroll, causing highlights to disappear momentarily. this._removeAllDecorations(); // Cancel any renderDecorationsTimer @@ -608,7 +609,7 @@ class WordHighlighter { private _run(): void { let workerRequestIsValid; - if (!this.editor.hasWidgetFocus()) { // no focus (new nb cell, etc) + if (!this.editor.hasTextFocus()) { // no focus (new nb cell, etc) if (WordHighlighter.query === null) { // no previous query, nothing to highlight return; From d6885f672a5357cc838e5edf483376134a9ec042 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Mon, 29 Jan 2024 19:30:20 -0600 Subject: [PATCH 0712/1897] Use const enum over const for search command IDs (#203757) Fixes #190249 --- .../search/browser/search.contribution.ts | 4 +- .../search/browser/searchActionsCopy.ts | 16 +-- .../search/browser/searchActionsFind.ts | 20 +-- .../search/browser/searchActionsNav.ts | 68 ++++----- .../browser/searchActionsRemoveReplace.ts | 28 ++-- .../search/browser/searchActionsSymbol.ts | 2 +- .../browser/searchActionsTextQuickAccess.ts | 2 +- .../search/browser/searchActionsTopBar.ts | 38 ++--- .../search/browser/searchResultsView.ts | 30 ++-- .../contrib/search/browser/searchView.ts | 42 +++--- .../contrib/search/browser/searchWidget.ts | 16 +-- .../contrib/search/common/constants.ts | 135 +++++++++--------- .../browser/searchEditor.contribution.ts | 8 +- .../searchEditor/browser/searchEditor.ts | 4 +- 14 files changed, 208 insertions(+), 205 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index f0db4c34f6a..cb03bc03f31 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -121,7 +121,7 @@ quickAccessRegistry.registerQuickAccessProvider({ prefix: SymbolsQuickAccessProvider.PREFIX, placeholder: nls.localize('symbolsQuickAccessPlaceholder', "Type the name of a symbol to open."), contextKey: 'inWorkspaceSymbolsPicker', - helpEntries: [{ description: nls.localize('symbolsQuickAccess', "Go to Symbol in Workspace"), commandId: Constants.ShowAllSymbolsActionId }] + helpEntries: [{ description: nls.localize('symbolsQuickAccess', "Go to Symbol in Workspace"), commandId: Constants.SearchCommandIds.ShowAllSymbolsActionId }] }); quickAccessRegistry.registerQuickAccessProvider({ @@ -132,7 +132,7 @@ quickAccessRegistry.registerQuickAccessProvider({ helpEntries: [ { description: nls.localize('textSearchPickerHelp', "Search for Text (Experimental)"), - commandId: Constants.QuickTextSearchActionId, + commandId: Constants.SearchCommandIds.QuickTextSearchActionId, commandCenterOrder: 65, } ] diff --git a/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts b/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts index c48d4ce4e63..358b66d751e 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts @@ -21,7 +21,7 @@ registerAction2(class CopyMatchCommandAction extends Action2 { constructor( ) { super({ - id: Constants.CopyMatchCommandId, + id: Constants.SearchCommandIds.CopyMatchCommandId, title: { value: nls.localize('copyMatchLabel', "Copy"), original: 'Copy' @@ -29,12 +29,12 @@ registerAction2(class CopyMatchCommandAction extends Action2 { category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - when: Constants.FileMatchOrMatchFocusKey, + when: Constants.SearchContext.FileMatchOrMatchFocusKey, primary: KeyMod.CtrlCmd | KeyCode.KeyC, }, menu: [{ id: MenuId.SearchContext, - when: Constants.FileMatchOrMatchFocusKey, + when: Constants.SearchContext.FileMatchOrMatchFocusKey, group: 'search_2', order: 1 }] @@ -52,7 +52,7 @@ registerAction2(class CopyPathCommandAction extends Action2 { constructor( ) { super({ - id: Constants.CopyPathCommandId, + id: Constants.SearchCommandIds.CopyPathCommandId, title: { value: nls.localize('copyPathLabel', "Copy Path"), original: 'Copy Path' @@ -60,7 +60,7 @@ registerAction2(class CopyPathCommandAction extends Action2 { category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - when: Constants.FileMatchOrFolderMatchWithResourceFocusKey, + when: Constants.SearchContext.FileMatchOrFolderMatchWithResourceFocusKey, primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyC, win: { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KeyC @@ -68,7 +68,7 @@ registerAction2(class CopyPathCommandAction extends Action2 { }, menu: [{ id: MenuId.SearchContext, - when: Constants.FileMatchOrFolderMatchWithResourceFocusKey, + when: Constants.SearchContext.FileMatchOrFolderMatchWithResourceFocusKey, group: 'search_2', order: 2 }] @@ -86,7 +86,7 @@ registerAction2(class CopyAllCommandAction extends Action2 { constructor( ) { super({ - id: Constants.CopyAllCommandId, + id: Constants.SearchCommandIds.CopyAllCommandId, title: { value: nls.localize('copyAllLabel', "Copy All"), original: 'Copy All' @@ -94,7 +94,7 @@ registerAction2(class CopyAllCommandAction extends Action2 { category, menu: [{ id: MenuId.SearchContext, - when: Constants.HasSearchResults, + when: Constants.SearchContext.HasSearchResults, group: 'search_2', order: 3 }] diff --git a/src/vs/workbench/contrib/search/browser/searchActionsFind.ts b/src/vs/workbench/contrib/search/browser/searchActionsFind.ts index a4fe70aa949..97525dd4fff 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsFind.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsFind.ts @@ -54,7 +54,7 @@ export interface IFindInFilesArgs { registerAction2(class RestrictSearchToFolderAction extends Action2 { constructor() { super({ - id: Constants.RestrictSearchToFolderId, + id: Constants.SearchCommandIds.RestrictSearchToFolderId, title: { value: nls.localize('restrictResultsToFolder', "Restrict Search to Folder"), original: 'Restrict Search to Folder' @@ -62,7 +62,7 @@ registerAction2(class RestrictSearchToFolderAction extends Action2 { category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ResourceFolderFocusKey), + when: ContextKeyExpr.and(Constants.SearchContext.SearchViewVisibleKey, Constants.SearchContext.ResourceFolderFocusKey), primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KeyF, }, menu: [ @@ -70,7 +70,7 @@ registerAction2(class RestrictSearchToFolderAction extends Action2 { id: MenuId.SearchContext, group: 'search', order: 3, - when: ContextKeyExpr.and(Constants.ResourceFolderFocusKey) + when: ContextKeyExpr.and(Constants.SearchContext.ResourceFolderFocusKey) } ] }); @@ -83,7 +83,7 @@ registerAction2(class RestrictSearchToFolderAction extends Action2 { registerAction2(class ExcludeFolderFromSearchAction extends Action2 { constructor() { super({ - id: Constants.ExcludeFolderFromSearchId, + id: Constants.SearchCommandIds.ExcludeFolderFromSearchId, title: { value: nls.localize('excludeFolderFromSearch', "Exclude Folder from Search"), original: 'Exclude Folder from Search' @@ -94,7 +94,7 @@ registerAction2(class ExcludeFolderFromSearchAction extends Action2 { id: MenuId.SearchContext, group: 'search', order: 4, - when: ContextKeyExpr.and(Constants.ResourceFolderFocusKey) + when: ContextKeyExpr.and(Constants.SearchContext.ResourceFolderFocusKey) } ] }); @@ -109,7 +109,7 @@ registerAction2(class RevealInSideBarForSearchResultsAction extends Action2 { constructor( ) { super({ - id: Constants.RevealInSideBarForSearchResults, + id: Constants.SearchCommandIds.RevealInSideBarForSearchResults, title: { value: nls.localize('revealInSideBar', "Reveal in Explorer View"), original: 'Reveal in Explorer View' @@ -117,7 +117,7 @@ registerAction2(class RevealInSideBarForSearchResultsAction extends Action2 { category, menu: [{ id: MenuId.SearchContext, - when: ContextKeyExpr.and(Constants.FileFocusKey, Constants.HasSearchResults), + when: ContextKeyExpr.and(Constants.SearchContext.FileFocusKey, Constants.SearchContext.HasSearchResults), group: 'search_3', order: 1 }] @@ -167,7 +167,7 @@ registerAction2(class FindInFilesAction extends Action2 { constructor( ) { super({ - id: Constants.FindInFilesActionId, + id: Constants.SearchCommandIds.FindInFilesActionId, title: { value: nls.localize('findInFiles', "Find in Files"), mnemonicTitle: nls.localize({ key: 'miFindInFiles', comment: ['&& denotes a mnemonic'] }, "Find &&in Files"), @@ -221,7 +221,7 @@ registerAction2(class FindInFolderAction extends Action2 { // from explorer constructor() { super({ - id: Constants.FindInFolderId, + id: Constants.SearchCommandIds.FindInFolderId, title: { value: nls.localize('findInFolder', "Find in Folder..."), original: 'Find in Folder...' @@ -251,7 +251,7 @@ registerAction2(class FindInWorkspaceAction extends Action2 { // from explorer constructor() { super({ - id: Constants.FindInWorkspaceId, + id: Constants.SearchCommandIds.FindInWorkspaceId, title: { value: nls.localize('findInWorkspace', "Find in Workspace..."), original: 'Find in Workspace...' diff --git a/src/vs/workbench/contrib/search/browser/searchActionsNav.ts b/src/vs/workbench/contrib/search/browser/searchActionsNav.ts index c9ec0a91ae0..4712349398b 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsNav.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsNav.ts @@ -30,7 +30,7 @@ import { getActiveElement } from 'vs/base/browser/dom'; registerAction2(class ToggleQueryDetailsAction extends Action2 { constructor() { super({ - id: Constants.ToggleQueryDetailsActionId, + id: Constants.SearchCommandIds.ToggleQueryDetailsActionId, title: { value: nls.localize('ToggleQueryDetailsAction.label', "Toggle Query Details"), original: 'Toggle Query Details' @@ -38,7 +38,7 @@ registerAction2(class ToggleQueryDetailsAction extends Action2 { category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.or(Constants.SearchViewFocusedKey, SearchEditorConstants.InSearchEditor), + when: ContextKeyExpr.or(Constants.SearchContext.SearchViewFocusedKey, SearchEditorConstants.InSearchEditor), primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyJ, }, }); @@ -47,7 +47,7 @@ registerAction2(class ToggleQueryDetailsAction extends Action2 { const contextService = accessor.get(IContextKeyService).getContext(getActiveElement()); if (contextService.getValue(SearchEditorConstants.InSearchEditor.serialize())) { (accessor.get(IEditorService).activeEditorPane as SearchEditor).toggleQueryDetails(args[0]?.show); - } else if (contextService.getValue(Constants.SearchViewFocusedKey.serialize())) { + } else if (contextService.getValue(Constants.SearchContext.SearchViewFocusedKey.serialize())) { const searchView = getSearchView(accessor.get(IViewsService)); assertIsDefined(searchView).toggleQueryDetails(undefined, args[0]?.show); } @@ -57,7 +57,7 @@ registerAction2(class ToggleQueryDetailsAction extends Action2 { registerAction2(class CloseReplaceAction extends Action2 { constructor() { super({ - id: Constants.CloseReplaceWidgetActionId, + id: Constants.SearchCommandIds.CloseReplaceWidgetActionId, title: { value: nls.localize('CloseReplaceWidget.label', "Close Replace Widget"), original: 'Close Replace Widget' @@ -65,7 +65,7 @@ registerAction2(class CloseReplaceAction extends Action2 { category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceInputBoxFocusedKey), + when: ContextKeyExpr.and(Constants.SearchContext.SearchViewVisibleKey, Constants.SearchContext.ReplaceInputBoxFocusedKey), primary: KeyCode.Escape, }, }); @@ -87,7 +87,7 @@ registerAction2(class ToggleCaseSensitiveCommandAction extends Action2 { ) { super({ - id: Constants.ToggleCaseSensitiveCommandId, + id: Constants.SearchCommandIds.ToggleCaseSensitiveCommandId, title: { value: nls.localize('ToggleCaseSensitiveCommandId.label', "Toggle Case Sensitive"), original: 'Toggle Case Sensitive' @@ -95,7 +95,7 @@ registerAction2(class ToggleCaseSensitiveCommandAction extends Action2 { category, keybinding: Object.assign({ weight: KeybindingWeight.WorkbenchContrib, - when: isMacintosh ? ContextKeyExpr.and(Constants.SearchViewFocusedKey, Constants.FileMatchOrFolderMatchFocusKey.toNegated()) : Constants.SearchViewFocusedKey, + when: isMacintosh ? ContextKeyExpr.and(Constants.SearchContext.SearchViewFocusedKey, Constants.SearchContext.FileMatchOrFolderMatchFocusKey.toNegated()) : Constants.SearchContext.SearchViewFocusedKey, }, ToggleCaseSensitiveKeybinding) }); @@ -110,14 +110,14 @@ registerAction2(class ToggleCaseSensitiveCommandAction extends Action2 { registerAction2(class ToggleWholeWordCommandAction extends Action2 { constructor() { super({ - id: Constants.ToggleWholeWordCommandId, + id: Constants.SearchCommandIds.ToggleWholeWordCommandId, title: { value: nls.localize('ToggleWholeWordCommandId.label', 'Toggle Whole Word'), original: 'Toggle Whole Word' }, keybinding: Object.assign({ weight: KeybindingWeight.WorkbenchContrib, - when: Constants.SearchViewFocusedKey, + when: Constants.SearchContext.SearchViewFocusedKey, }, ToggleWholeWordKeybinding), category, }); @@ -131,14 +131,14 @@ registerAction2(class ToggleWholeWordCommandAction extends Action2 { registerAction2(class ToggleRegexCommandAction extends Action2 { constructor() { super({ - id: Constants.ToggleRegexCommandId, + id: Constants.SearchCommandIds.ToggleRegexCommandId, title: { value: nls.localize('ToggleRegexCommandId.label', 'Toggle Regex'), original: 'Toggle Regex' }, keybinding: Object.assign({ weight: KeybindingWeight.WorkbenchContrib, - when: Constants.SearchViewFocusedKey, + when: Constants.SearchContext.SearchViewFocusedKey, }, ToggleRegexKeybinding), category, }); @@ -152,14 +152,14 @@ registerAction2(class ToggleRegexCommandAction extends Action2 { registerAction2(class TogglePreserveCaseAction extends Action2 { constructor() { super({ - id: Constants.TogglePreserveCaseId, + id: Constants.SearchCommandIds.TogglePreserveCaseId, title: { value: nls.localize('TogglePreserveCaseId.label', 'Toggle Preserve Case'), original: 'Toggle Preserve Case' }, keybinding: Object.assign({ weight: KeybindingWeight.WorkbenchContrib, - when: Constants.SearchViewFocusedKey, + when: Constants.SearchContext.SearchViewFocusedKey, }, TogglePreserveCaseKeybinding), category, }); @@ -175,7 +175,7 @@ registerAction2(class TogglePreserveCaseAction extends Action2 { registerAction2(class OpenMatchAction extends Action2 { constructor() { super({ - id: Constants.OpenMatch, + id: Constants.SearchCommandIds.OpenMatch, title: { value: nls.localize('OpenMatch.label', "Open Match"), original: 'Open Match' @@ -183,7 +183,7 @@ registerAction2(class OpenMatchAction extends Action2 { category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FileMatchOrMatchFocusKey), + when: ContextKeyExpr.and(Constants.SearchContext.SearchViewVisibleKey, Constants.SearchContext.FileMatchOrMatchFocusKey), primary: KeyCode.Enter, mac: { primary: KeyCode.Enter, @@ -211,7 +211,7 @@ registerAction2(class OpenMatchAction extends Action2 { registerAction2(class OpenMatchToSideAction extends Action2 { constructor() { super({ - id: Constants.OpenMatchToSide, + id: Constants.SearchCommandIds.OpenMatchToSide, title: { value: nls.localize('OpenMatchToSide.label', "Open Match To Side"), original: 'Open Match To Side' @@ -219,7 +219,7 @@ registerAction2(class OpenMatchToSideAction extends Action2 { category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FileMatchOrMatchFocusKey), + when: ContextKeyExpr.and(Constants.SearchContext.SearchViewVisibleKey, Constants.SearchContext.FileMatchOrMatchFocusKey), primary: KeyMod.CtrlCmd | KeyCode.Enter, mac: { primary: KeyMod.WinCtrl | KeyCode.Enter @@ -239,14 +239,14 @@ registerAction2(class OpenMatchToSideAction extends Action2 { registerAction2(class AddCursorsAtSearchResultsAction extends Action2 { constructor() { super({ - id: Constants.AddCursorsAtSearchResults, + id: Constants.SearchCommandIds.AddCursorsAtSearchResults, title: { value: nls.localize('AddCursorsAtSearchResults.label', 'Add Cursors at Search Results'), original: 'Add Cursors at Search Results' }, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FileMatchOrMatchFocusKey), + when: ContextKeyExpr.and(Constants.SearchContext.SearchViewVisibleKey, Constants.SearchContext.FileMatchOrMatchFocusKey), primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyL, }, category, @@ -267,7 +267,7 @@ registerAction2(class AddCursorsAtSearchResultsAction extends Action2 { registerAction2(class FocusNextInputAction extends Action2 { constructor() { super({ - id: Constants.FocusNextInputActionId, + id: Constants.SearchCommandIds.FocusNextInputActionId, title: { value: nls.localize('FocusNextInputAction.label', "Focus Next Input"), original: 'Focus Next Input' @@ -276,8 +276,8 @@ registerAction2(class FocusNextInputAction extends Action2 { keybinding: { weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.or( - ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, Constants.InputBoxFocusedKey), - ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.InputBoxFocusedKey)), + ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, Constants.SearchContext.InputBoxFocusedKey), + ContextKeyExpr.and(Constants.SearchContext.SearchViewVisibleKey, Constants.SearchContext.InputBoxFocusedKey)), primary: KeyMod.CtrlCmd | KeyCode.DownArrow, }, }); @@ -299,7 +299,7 @@ registerAction2(class FocusNextInputAction extends Action2 { registerAction2(class FocusPreviousInputAction extends Action2 { constructor() { super({ - id: Constants.FocusPreviousInputActionId, + id: Constants.SearchCommandIds.FocusPreviousInputActionId, title: { value: nls.localize('FocusPreviousInputAction.label', "Focus Previous Input"), original: 'Focus Previous Input' @@ -308,8 +308,8 @@ registerAction2(class FocusPreviousInputAction extends Action2 { keybinding: { weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.or( - ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, Constants.InputBoxFocusedKey), - ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.InputBoxFocusedKey, Constants.SearchInputBoxFocusedKey.toNegated())), + ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, Constants.SearchContext.InputBoxFocusedKey), + ContextKeyExpr.and(Constants.SearchContext.SearchViewVisibleKey, Constants.SearchContext.InputBoxFocusedKey, Constants.SearchContext.SearchInputBoxFocusedKey.toNegated())), primary: KeyMod.CtrlCmd | KeyCode.UpArrow, }, }); @@ -331,7 +331,7 @@ registerAction2(class FocusPreviousInputAction extends Action2 { registerAction2(class FocusSearchFromResultsAction extends Action2 { constructor() { super({ - id: Constants.FocusSearchFromResults, + id: Constants.SearchCommandIds.FocusSearchFromResults, title: { value: nls.localize('FocusSearchFromResults.label', "Focus Search From Results"), original: 'Focus Search From Results' @@ -339,7 +339,7 @@ registerAction2(class FocusSearchFromResultsAction extends Action2 { category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, ContextKeyExpr.or(Constants.FirstMatchFocusKey, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), + when: ContextKeyExpr.and(Constants.SearchContext.SearchViewVisibleKey, ContextKeyExpr.or(Constants.SearchContext.FirstMatchFocusKey, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), primary: KeyMod.CtrlCmd | KeyCode.UpArrow, }, }); @@ -356,7 +356,7 @@ registerAction2(class ToggleSearchOnTypeAction extends Action2 { constructor( ) { super({ - id: Constants.ToggleSearchOnTypeActionId, + id: Constants.SearchCommandIds.ToggleSearchOnTypeActionId, title: { value: nls.localize('toggleTabs', 'Toggle Search on Type'), original: 'Toggle Search on Type' @@ -378,7 +378,7 @@ registerAction2(class FocusSearchListCommandAction extends Action2 { constructor( ) { super({ - id: Constants.FocusSearchListCommandID, + id: Constants.SearchCommandIds.FocusSearchListCommandID, title: { value: nls.localize('focusSearchListCommandLabel', "Focus List"), original: 'Focus List' @@ -396,7 +396,7 @@ registerAction2(class FocusSearchListCommandAction extends Action2 { registerAction2(class FocusNextSearchResultAction extends Action2 { constructor() { super({ - id: Constants.FocusNextSearchResultActionId, + id: Constants.SearchCommandIds.FocusNextSearchResultActionId, title: { value: nls.localize('FocusNextSearchResult.label', 'Focus Next Search Result'), original: 'Focus Next Search Result' @@ -407,7 +407,7 @@ registerAction2(class FocusNextSearchResultAction extends Action2 { }], category, f1: true, - precondition: ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor), + precondition: ContextKeyExpr.or(Constants.SearchContext.HasSearchResults, SearchEditorConstants.InSearchEditor), }); } @@ -419,7 +419,7 @@ registerAction2(class FocusNextSearchResultAction extends Action2 { registerAction2(class FocusPreviousSearchResultAction extends Action2 { constructor() { super({ - id: Constants.FocusPreviousSearchResultActionId, + id: Constants.SearchCommandIds.FocusPreviousSearchResultActionId, title: { value: nls.localize('FocusPreviousSearchResult.label', 'Focus Previous Search Result'), original: 'Focus Previous Search Result' @@ -430,7 +430,7 @@ registerAction2(class FocusPreviousSearchResultAction extends Action2 { }], category, f1: true, - precondition: ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor), + precondition: ContextKeyExpr.or(Constants.SearchContext.HasSearchResults, SearchEditorConstants.InSearchEditor), }); } @@ -442,7 +442,7 @@ registerAction2(class FocusPreviousSearchResultAction extends Action2 { registerAction2(class ReplaceInFilesAction extends Action2 { constructor() { super({ - id: Constants.ReplaceInFilesActionId, + id: Constants.SearchCommandIds.ReplaceInFilesActionId, title: { value: nls.localize('replaceInFiles', 'Replace in Files'), original: 'Replace in Files' diff --git a/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts b/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts index d116782fd0f..23d4e39a96d 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts @@ -54,7 +54,7 @@ registerAction2(class RemoveAction extends Action2 { constructor( ) { super({ - id: Constants.RemoveActionId, + id: Constants.SearchCommandIds.RemoveActionId, title: { value: nls.localize('RemoveAction.label', "Dismiss"), original: 'Dismiss' @@ -63,7 +63,7 @@ registerAction2(class RemoveAction extends Action2 { icon: searchRemoveIcon, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FileMatchOrMatchFocusKey), + when: ContextKeyExpr.and(Constants.SearchContext.SearchViewVisibleKey, Constants.SearchContext.FileMatchOrMatchFocusKey), primary: KeyCode.Delete, mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace, @@ -148,7 +148,7 @@ registerAction2(class ReplaceAction extends Action2 { constructor( ) { super({ - id: Constants.ReplaceActionId, + id: Constants.SearchCommandIds.ReplaceActionId, title: { value: nls.localize('match.replace.label', "Replace"), original: 'Replace' @@ -156,20 +156,20 @@ registerAction2(class ReplaceAction extends Action2 { category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, Constants.MatchFocusKey, Constants.IsEditableItemKey), + when: ContextKeyExpr.and(Constants.SearchContext.SearchViewVisibleKey, Constants.SearchContext.ReplaceActiveKey, Constants.SearchContext.MatchFocusKey, Constants.SearchContext.IsEditableItemKey), primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.Digit1, }, icon: searchReplaceIcon, menu: [ { id: MenuId.SearchContext, - when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.MatchFocusKey, Constants.IsEditableItemKey), + when: ContextKeyExpr.and(Constants.SearchContext.ReplaceActiveKey, Constants.SearchContext.MatchFocusKey, Constants.SearchContext.IsEditableItemKey), group: 'search', order: 1 }, { id: MenuId.SearchActionMenu, - when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.MatchFocusKey, Constants.IsEditableItemKey), + when: ContextKeyExpr.and(Constants.SearchContext.ReplaceActiveKey, Constants.SearchContext.MatchFocusKey, Constants.SearchContext.IsEditableItemKey), group: 'inline', order: 1 } @@ -187,7 +187,7 @@ registerAction2(class ReplaceAllAction extends Action2 { constructor( ) { super({ - id: Constants.ReplaceAllInFileActionId, + id: Constants.SearchCommandIds.ReplaceAllInFileActionId, title: { value: nls.localize('file.replaceAll.label', "Replace All"), original: 'Replace All' @@ -195,7 +195,7 @@ registerAction2(class ReplaceAllAction extends Action2 { category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, Constants.FileFocusKey, Constants.IsEditableItemKey), + when: ContextKeyExpr.and(Constants.SearchContext.SearchViewVisibleKey, Constants.SearchContext.ReplaceActiveKey, Constants.SearchContext.FileFocusKey, Constants.SearchContext.IsEditableItemKey), primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.Digit1, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter], }, @@ -203,13 +203,13 @@ registerAction2(class ReplaceAllAction extends Action2 { menu: [ { id: MenuId.SearchContext, - when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.FileFocusKey, Constants.IsEditableItemKey), + when: ContextKeyExpr.and(Constants.SearchContext.ReplaceActiveKey, Constants.SearchContext.FileFocusKey, Constants.SearchContext.IsEditableItemKey), group: 'search', order: 1 }, { id: MenuId.SearchActionMenu, - when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.FileFocusKey, Constants.IsEditableItemKey), + when: ContextKeyExpr.and(Constants.SearchContext.ReplaceActiveKey, Constants.SearchContext.FileFocusKey, Constants.SearchContext.IsEditableItemKey), group: 'inline', order: 1 } @@ -226,7 +226,7 @@ registerAction2(class ReplaceAllInFolderAction extends Action2 { constructor( ) { super({ - id: Constants.ReplaceAllInFolderActionId, + id: Constants.SearchCommandIds.ReplaceAllInFolderActionId, title: { value: nls.localize('file.replaceAll.label', "Replace All"), original: 'Replace All' @@ -234,7 +234,7 @@ registerAction2(class ReplaceAllInFolderAction extends Action2 { category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, Constants.FolderFocusKey, Constants.IsEditableItemKey), + when: ContextKeyExpr.and(Constants.SearchContext.SearchViewVisibleKey, Constants.SearchContext.ReplaceActiveKey, Constants.SearchContext.FolderFocusKey, Constants.SearchContext.IsEditableItemKey), primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.Digit1, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter], }, @@ -242,13 +242,13 @@ registerAction2(class ReplaceAllInFolderAction extends Action2 { menu: [ { id: MenuId.SearchContext, - when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.FolderFocusKey, Constants.IsEditableItemKey), + when: ContextKeyExpr.and(Constants.SearchContext.ReplaceActiveKey, Constants.SearchContext.FolderFocusKey, Constants.SearchContext.IsEditableItemKey), group: 'search', order: 1 }, { id: MenuId.SearchActionMenu, - when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.FolderFocusKey, Constants.IsEditableItemKey), + when: ContextKeyExpr.and(Constants.SearchContext.ReplaceActiveKey, Constants.SearchContext.FolderFocusKey, Constants.SearchContext.IsEditableItemKey), group: 'inline', order: 1 } diff --git a/src/vs/workbench/contrib/search/browser/searchActionsSymbol.ts b/src/vs/workbench/contrib/search/browser/searchActionsSymbol.ts index e6dec6fab8a..d9c74cc18e7 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsSymbol.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsSymbol.ts @@ -21,7 +21,7 @@ registerAction2(class ShowAllSymbolsAction extends Action2 { constructor( ) { super({ - id: Constants.ShowAllSymbolsActionId, + id: Constants.SearchCommandIds.ShowAllSymbolsActionId, title: { value: nls.localize('showTriggerActions', "Go to Symbol in Workspace..."), original: 'Go to Symbol in Workspace...', diff --git a/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts b/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts index a5e68918206..d2bf9f778af 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts @@ -20,7 +20,7 @@ registerAction2(class TextSearchQuickAccessAction extends Action2 { constructor( ) { super({ - id: Constants.QuickTextSearchActionId, + id: Constants.SearchCommandIds.QuickTextSearchActionId, title: { value: nls.localize('quickTextSearch', "Quick Search (Experimental)"), original: 'Quick Search (Experimental)' diff --git a/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts b/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts index f7e0dae7b12..bb07efc51a1 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts @@ -26,7 +26,7 @@ registerAction2(class ClearSearchHistoryCommandAction extends Action2 { constructor( ) { super({ - id: Constants.ClearSearchHistoryCommandId, + id: Constants.SearchCommandIds.ClearSearchHistoryCommandId, title: { value: nls.localize('clearSearchHistoryLabel', "Clear Search History"), original: 'Clear Search History' @@ -45,7 +45,7 @@ registerAction2(class ClearSearchHistoryCommandAction extends Action2 { registerAction2(class CancelSearchAction extends Action2 { constructor() { super({ - id: Constants.CancelSearchActionId, + id: Constants.SearchCommandIds.CancelSearchActionId, title: { value: nls.localize('CancelSearchAction.label', "Cancel Search"), original: 'Cancel Search' @@ -56,7 +56,7 @@ registerAction2(class CancelSearchAction extends Action2 { precondition: SearchStateKey.isEqualTo(SearchUIState.Idle).negate(), keybinding: { weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, WorkbenchListFocusContextKey), + when: ContextKeyExpr.and(Constants.SearchContext.SearchViewVisibleKey, WorkbenchListFocusContextKey), primary: KeyCode.Escape, }, menu: [{ @@ -75,13 +75,13 @@ registerAction2(class CancelSearchAction extends Action2 { registerAction2(class RefreshAction extends Action2 { constructor() { super({ - id: Constants.RefreshSearchResultsActionId, + id: Constants.SearchCommandIds.RefreshSearchResultsActionId, title: { value: nls.localize('RefreshAction.label', "Refresh"), original: 'Refresh' }, icon: searchRefreshIcon, - precondition: Constants.ViewHasSearchPatternKey, + precondition: Constants.SearchContext.ViewHasSearchPatternKey, category, f1: true, menu: [{ @@ -100,7 +100,7 @@ registerAction2(class RefreshAction extends Action2 { registerAction2(class CollapseDeepestExpandedLevelAction extends Action2 { constructor() { super({ - id: Constants.CollapseSearchResultsActionId, + id: Constants.SearchCommandIds.CollapseSearchResultsActionId, title: { value: nls.localize('CollapseDeepestExpandedLevelAction.label', "Collapse All"), original: 'Collapse All' @@ -108,12 +108,12 @@ registerAction2(class CollapseDeepestExpandedLevelAction extends Action2 { category, icon: searchCollapseAllIcon, f1: true, - precondition: ContextKeyExpr.and(Constants.HasSearchResults, Constants.ViewHasSomeCollapsibleKey), + precondition: ContextKeyExpr.and(Constants.SearchContext.HasSearchResults, Constants.SearchContext.ViewHasSomeCollapsibleKey), menu: [{ id: MenuId.ViewTitle, group: 'navigation', order: 3, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), ContextKeyExpr.or(Constants.HasSearchResults.negate(), Constants.ViewHasSomeCollapsibleKey)), + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), ContextKeyExpr.or(Constants.SearchContext.HasSearchResults.negate(), Constants.SearchContext.ViewHasSomeCollapsibleKey)), }] }); } @@ -125,7 +125,7 @@ registerAction2(class CollapseDeepestExpandedLevelAction extends Action2 { registerAction2(class ExpandAllAction extends Action2 { constructor() { super({ - id: Constants.ExpandSearchResultsActionId, + id: Constants.SearchCommandIds.ExpandSearchResultsActionId, title: { value: nls.localize('ExpandAllAction.label', "Expand All"), original: 'Expand All' @@ -133,12 +133,12 @@ registerAction2(class ExpandAllAction extends Action2 { category, icon: searchExpandAllIcon, f1: true, - precondition: ContextKeyExpr.and(Constants.HasSearchResults, Constants.ViewHasSomeCollapsibleKey.toNegated()), + precondition: ContextKeyExpr.and(Constants.SearchContext.HasSearchResults, Constants.SearchContext.ViewHasSomeCollapsibleKey.toNegated()), menu: [{ id: MenuId.ViewTitle, group: 'navigation', order: 3, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), Constants.HasSearchResults, Constants.ViewHasSomeCollapsibleKey.toNegated()), + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), Constants.SearchContext.HasSearchResults, Constants.SearchContext.ViewHasSomeCollapsibleKey.toNegated()), }] }); } @@ -150,7 +150,7 @@ registerAction2(class ExpandAllAction extends Action2 { registerAction2(class ClearSearchResultsAction extends Action2 { constructor() { super({ - id: Constants.ClearSearchResultsActionId, + id: Constants.SearchCommandIds.ClearSearchResultsActionId, title: { value: nls.localize('ClearSearchResultsAction.label', "Clear Search Results"), original: 'Clear Search Results' @@ -158,7 +158,7 @@ registerAction2(class ClearSearchResultsAction extends Action2 { category, icon: searchClearIcon, f1: true, - precondition: ContextKeyExpr.or(Constants.HasSearchResults, Constants.ViewHasSearchPatternKey, Constants.ViewHasReplacePatternKey, Constants.ViewHasFilePatternKey), + precondition: ContextKeyExpr.or(Constants.SearchContext.HasSearchResults, Constants.SearchContext.ViewHasSearchPatternKey, Constants.SearchContext.ViewHasReplacePatternKey, Constants.SearchContext.ViewHasFilePatternKey), menu: [{ id: MenuId.ViewTitle, group: 'navigation', @@ -176,7 +176,7 @@ registerAction2(class ClearSearchResultsAction extends Action2 { registerAction2(class ViewAsTreeAction extends Action2 { constructor() { super({ - id: Constants.ViewAsTreeActionId, + id: Constants.SearchCommandIds.ViewAsTreeActionId, title: { value: nls.localize('ViewAsTreeAction.label', "View as Tree"), original: 'View as Tree' @@ -184,12 +184,12 @@ registerAction2(class ViewAsTreeAction extends Action2 { category, icon: searchShowAsList, f1: true, - precondition: ContextKeyExpr.and(Constants.HasSearchResults, Constants.InTreeViewKey.toNegated()), + precondition: ContextKeyExpr.and(Constants.SearchContext.HasSearchResults, Constants.SearchContext.InTreeViewKey.toNegated()), menu: [{ id: MenuId.ViewTitle, group: 'navigation', order: 2, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), Constants.InTreeViewKey.toNegated()), + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), Constants.SearchContext.InTreeViewKey.toNegated()), }] }); } @@ -204,7 +204,7 @@ registerAction2(class ViewAsTreeAction extends Action2 { registerAction2(class ViewAsListAction extends Action2 { constructor() { super({ - id: Constants.ViewAsListActionId, + id: Constants.SearchCommandIds.ViewAsListActionId, title: { value: nls.localize('ViewAsListAction.label', "View as List"), original: 'View as List' @@ -212,12 +212,12 @@ registerAction2(class ViewAsListAction extends Action2 { category, icon: searchShowAsTree, f1: true, - precondition: ContextKeyExpr.and(Constants.HasSearchResults, Constants.InTreeViewKey), + precondition: ContextKeyExpr.and(Constants.SearchContext.HasSearchResults, Constants.SearchContext.InTreeViewKey), menu: [{ id: MenuId.ViewTitle, group: 'navigation', order: 2, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), Constants.InTreeViewKey), + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), Constants.SearchContext.InTreeViewKey), }] }); } diff --git a/src/vs/workbench/contrib/search/browser/searchResultsView.ts b/src/vs/workbench/contrib/search/browser/searchResultsView.ts index be7d1025364..3f71827f12c 100644 --- a/src/vs/workbench/contrib/search/browser/searchResultsView.ts +++ b/src/vs/workbench/contrib/search/browser/searchResultsView.ts @@ -28,8 +28,8 @@ import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/br import { ISearchActionContext } from 'vs/workbench/contrib/search/browser/searchActionsRemoveReplace'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IsEditableItemKey, FileFocusKey, FolderFocusKey, MatchFocusKey } from 'vs/workbench/contrib/search/common/constants'; import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { SearchContext } from 'vs/workbench/contrib/search/common/constants'; interface IFolderMatchTemplate { label: IResourceLabel; @@ -130,9 +130,9 @@ export class FolderMatchRenderer extends Disposable implements ICompressibleTree disposables.add(elementDisposables); const contextKeyServiceMain = disposables.add(this.contextKeyService.createScoped(container)); - MatchFocusKey.bindTo(contextKeyServiceMain).set(false); - FileFocusKey.bindTo(contextKeyServiceMain).set(false); - FolderFocusKey.bindTo(contextKeyServiceMain).set(true); + SearchContext.MatchFocusKey.bindTo(contextKeyServiceMain).set(false); + SearchContext.FileFocusKey.bindTo(contextKeyServiceMain).set(false); + SearchContext.FolderFocusKey.bindTo(contextKeyServiceMain).set(true); const instantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyServiceMain])); const actions = disposables.add(instantiationService.createInstance(MenuWorkbenchToolBar, actionBarContainer, MenuId.SearchActionMenu, { @@ -168,10 +168,10 @@ export class FolderMatchRenderer extends Disposable implements ICompressibleTree templateData.label.setLabel(nls.localize('searchFolderMatch.other.label', "Other files")); } - IsEditableItemKey.bindTo(templateData.contextKeyService).set(!folderMatch.hasOnlyReadOnlyMatches()); + SearchContext.IsEditableItemKey.bindTo(templateData.contextKeyService).set(!folderMatch.hasOnlyReadOnlyMatches()); templateData.elementDisposables.add(folderMatch.onChange(() => { - IsEditableItemKey.bindTo(templateData.contextKeyService).set(!folderMatch.hasOnlyReadOnlyMatches()); + SearchContext.IsEditableItemKey.bindTo(templateData.contextKeyService).set(!folderMatch.hasOnlyReadOnlyMatches()); })); this.renderFolderDetails(folderMatch, templateData); @@ -229,9 +229,9 @@ export class FileMatchRenderer extends Disposable implements ICompressibleTreeRe const actionBarContainer = DOM.append(fileMatchElement, DOM.$('.actionBarContainer')); const contextKeyServiceMain = disposables.add(this.contextKeyService.createScoped(container)); - MatchFocusKey.bindTo(contextKeyServiceMain).set(false); - FileFocusKey.bindTo(contextKeyServiceMain).set(true); - FolderFocusKey.bindTo(contextKeyServiceMain).set(false); + SearchContext.MatchFocusKey.bindTo(contextKeyServiceMain).set(false); + SearchContext.FileFocusKey.bindTo(contextKeyServiceMain).set(true); + SearchContext.FolderFocusKey.bindTo(contextKeyServiceMain).set(false); const instantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyServiceMain])); const actions = disposables.add(instantiationService.createInstance(MenuWorkbenchToolBar, actionBarContainer, MenuId.SearchActionMenu, { @@ -267,10 +267,10 @@ export class FileMatchRenderer extends Disposable implements ICompressibleTreeRe templateData.actions.context = { viewer: this.searchView.getControl(), element: fileMatch }; - IsEditableItemKey.bindTo(templateData.contextKeyService).set(!fileMatch.hasOnlyReadOnlyMatches()); + SearchContext.IsEditableItemKey.bindTo(templateData.contextKeyService).set(!fileMatch.hasOnlyReadOnlyMatches()); templateData.elementDisposables.add(fileMatch.onChange(() => { - IsEditableItemKey.bindTo(templateData.contextKeyService).set(!fileMatch.hasOnlyReadOnlyMatches()); + SearchContext.IsEditableItemKey.bindTo(templateData.contextKeyService).set(!fileMatch.hasOnlyReadOnlyMatches()); })); // when hidesExplorerArrows: true, then the file nodes should still have a twistie because it would otherwise @@ -320,9 +320,9 @@ export class MatchRenderer extends Disposable implements ICompressibleTreeRender const disposables = new DisposableStore(); const contextKeyServiceMain = disposables.add(this.contextKeyService.createScoped(container)); - MatchFocusKey.bindTo(contextKeyServiceMain).set(true); - FileFocusKey.bindTo(contextKeyServiceMain).set(false); - FolderFocusKey.bindTo(contextKeyServiceMain).set(false); + SearchContext.MatchFocusKey.bindTo(contextKeyServiceMain).set(true); + SearchContext.FileFocusKey.bindTo(contextKeyServiceMain).set(false); + SearchContext.FolderFocusKey.bindTo(contextKeyServiceMain).set(false); const instantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyServiceMain])); const actions = disposables.add(instantiationService.createInstance(MenuWorkbenchToolBar, actionBarContainer, MenuId.SearchActionMenu, { @@ -362,7 +362,7 @@ export class MatchRenderer extends Disposable implements ICompressibleTreeRender templateData.after.textContent = preview.after; templateData.parent.title = (preview.fullBefore + (replace ? match.replaceString : preview.inside) + preview.after).trim().substr(0, 999); - IsEditableItemKey.bindTo(templateData.contextKeyService).set(!(match instanceof MatchInNotebook && match.isReadonly())); + SearchContext.IsEditableItemKey.bindTo(templateData.contextKeyService).set(!(match instanceof MatchInNotebook && match.isReadonly())); const numLines = match.range().endLineNumber - match.range().startLineNumber; const extraLinesStr = numLines > 0 ? `+${numLines}` : ''; diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index babe5ac41fc..e54f8fe8260 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -200,30 +200,30 @@ export class SearchView extends ViewPane { this.container = dom.$('.search-view'); // globals - this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(this.contextKeyService); - this.firstMatchFocused = Constants.FirstMatchFocusKey.bindTo(this.contextKeyService); - this.fileMatchOrMatchFocused = Constants.FileMatchOrMatchFocusKey.bindTo(this.contextKeyService); - this.fileMatchOrFolderMatchFocus = Constants.FileMatchOrFolderMatchFocusKey.bindTo(this.contextKeyService); - this.fileMatchOrFolderMatchWithResourceFocus = Constants.FileMatchOrFolderMatchWithResourceFocusKey.bindTo(this.contextKeyService); - this.fileMatchFocused = Constants.FileFocusKey.bindTo(this.contextKeyService); - this.folderMatchFocused = Constants.FolderFocusKey.bindTo(this.contextKeyService); - this.folderMatchWithResourceFocused = Constants.ResourceFolderFocusKey.bindTo(this.contextKeyService); - this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService); - this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService); + this.viewletVisible = Constants.SearchContext.SearchViewVisibleKey.bindTo(this.contextKeyService); + this.firstMatchFocused = Constants.SearchContext.FirstMatchFocusKey.bindTo(this.contextKeyService); + this.fileMatchOrMatchFocused = Constants.SearchContext.FileMatchOrMatchFocusKey.bindTo(this.contextKeyService); + this.fileMatchOrFolderMatchFocus = Constants.SearchContext.FileMatchOrFolderMatchFocusKey.bindTo(this.contextKeyService); + this.fileMatchOrFolderMatchWithResourceFocus = Constants.SearchContext.FileMatchOrFolderMatchWithResourceFocusKey.bindTo(this.contextKeyService); + this.fileMatchFocused = Constants.SearchContext.FileFocusKey.bindTo(this.contextKeyService); + this.folderMatchFocused = Constants.SearchContext.FolderFocusKey.bindTo(this.contextKeyService); + this.folderMatchWithResourceFocused = Constants.SearchContext.ResourceFolderFocusKey.bindTo(this.contextKeyService); + this.hasSearchResultsKey = Constants.SearchContext.HasSearchResults.bindTo(this.contextKeyService); + this.matchFocused = Constants.SearchContext.MatchFocusKey.bindTo(this.contextKeyService); this.searchStateKey = SearchStateKey.bindTo(this.contextKeyService); - this.hasSearchPatternKey = Constants.ViewHasSearchPatternKey.bindTo(this.contextKeyService); - this.hasReplacePatternKey = Constants.ViewHasReplacePatternKey.bindTo(this.contextKeyService); - this.hasFilePatternKey = Constants.ViewHasFilePatternKey.bindTo(this.contextKeyService); - this.hasSomeCollapsibleResultKey = Constants.ViewHasSomeCollapsibleKey.bindTo(this.contextKeyService); - this.treeViewKey = Constants.InTreeViewKey.bindTo(this.contextKeyService); + this.hasSearchPatternKey = Constants.SearchContext.ViewHasSearchPatternKey.bindTo(this.contextKeyService); + this.hasReplacePatternKey = Constants.SearchContext.ViewHasReplacePatternKey.bindTo(this.contextKeyService); + this.hasFilePatternKey = Constants.SearchContext.ViewHasFilePatternKey.bindTo(this.contextKeyService); + this.hasSomeCollapsibleResultKey = Constants.SearchContext.ViewHasSomeCollapsibleKey.bindTo(this.contextKeyService); + this.treeViewKey = Constants.SearchContext.InTreeViewKey.bindTo(this.contextKeyService); // scoped this.contextKeyService = this._register(this.contextKeyService.createScoped(this.container)); - Constants.SearchViewFocusedKey.bindTo(this.contextKeyService).set(true); - this.inputBoxFocused = Constants.InputBoxFocusedKey.bindTo(this.contextKeyService); - this.inputPatternIncludesFocused = Constants.PatternIncludesFocusedKey.bindTo(this.contextKeyService); - this.inputPatternExclusionsFocused = Constants.PatternExcludesFocusedKey.bindTo(this.contextKeyService); - this.isEditableItem = Constants.IsEditableItemKey.bindTo(this.contextKeyService); + Constants.SearchContext.SearchViewFocusedKey.bindTo(this.contextKeyService).set(true); + this.inputBoxFocused = Constants.SearchContext.InputBoxFocusedKey.bindTo(this.contextKeyService); + this.inputPatternIncludesFocused = Constants.SearchContext.PatternIncludesFocusedKey.bindTo(this.contextKeyService); + this.inputPatternExclusionsFocused = Constants.SearchContext.PatternExcludesFocusedKey.bindTo(this.contextKeyService); + this.isEditableItem = Constants.SearchContext.IsEditableItemKey.bindTo(this.contextKeyService); this.instantiationService = this.instantiationService.createChild( new ServiceCollection([IContextKeyService, this.contextKeyService])); @@ -1808,7 +1808,7 @@ export class SearchView extends ViewPane { const openInEditorTooltip = appendKeyBindingLabel( nls.localize('openInEditor.tooltip', "Copy current search results to an editor"), - this.keybindingService.lookupKeybinding(Constants.OpenInEditorCommandId)); + this.keybindingService.lookupKeybinding(Constants.SearchCommandIds.OpenInEditorCommandId)); const openInEditorButton = this.messageDisposables.add(new SearchLinkButton( nls.localize('openInEditor.message', "Open in editor"), () => this.instantiationService.invokeFunction(createEditorFromSearchResult, this.searchResult, this.searchIncludePattern.getValue(), this.searchExcludePattern.getValue(), this.searchIncludePattern.onlySearchInOpenEditors()), diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 0b38cd404bb..26acd42d459 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -184,9 +184,9 @@ export class SearchWidget extends Widget { @IEditorService private readonly editorService: IEditorService, ) { super(); - this.replaceActive = Constants.ReplaceActiveKey.bindTo(this.contextKeyService); - this.searchInputBoxFocused = Constants.SearchInputBoxFocusedKey.bindTo(this.contextKeyService); - this.replaceInputBoxFocused = Constants.ReplaceInputBoxFocusedKey.bindTo(this.contextKeyService); + this.replaceActive = Constants.SearchContext.ReplaceActiveKey.bindTo(this.contextKeyService); + this.searchInputBoxFocused = Constants.SearchContext.SearchInputBoxFocusedKey.bindTo(this.contextKeyService); + this.replaceInputBoxFocused = Constants.SearchContext.ReplaceInputBoxFocusedKey.bindTo(this.contextKeyService); const notebookOptions = options.notebookOptions ?? { @@ -386,9 +386,9 @@ export class SearchWidget extends Widget { label: nls.localize('label.Search', 'Search: Type Search Term and press Enter to search'), validation: (value: string) => this.validateSearchInput(value), placeholder: nls.localize('search.placeHolder', "Search"), - appendCaseSensitiveLabel: appendKeyBindingLabel('', this.keybindingService.lookupKeybinding(Constants.ToggleCaseSensitiveCommandId)), - appendWholeWordsLabel: appendKeyBindingLabel('', this.keybindingService.lookupKeybinding(Constants.ToggleWholeWordCommandId)), - appendRegexLabel: appendKeyBindingLabel('', this.keybindingService.lookupKeybinding(Constants.ToggleRegexCommandId)), + appendCaseSensitiveLabel: appendKeyBindingLabel('', this.keybindingService.lookupKeybinding(Constants.SearchCommandIds.ToggleCaseSensitiveCommandId)), + appendWholeWordsLabel: appendKeyBindingLabel('', this.keybindingService.lookupKeybinding(Constants.SearchCommandIds.ToggleWholeWordCommandId)), + appendRegexLabel: appendKeyBindingLabel('', this.keybindingService.lookupKeybinding(Constants.SearchCommandIds.ToggleRegexCommandId)), history: options.searchHistory, showHistoryHint: () => showHistoryKeybindingHint(this.keybindingService), flexibleHeight: true, @@ -486,7 +486,7 @@ export class SearchWidget extends Widget { this.replaceInput = this._register(new ContextScopedReplaceInput(replaceBox, this.contextViewService, { label: nls.localize('label.Replace', 'Replace: Type replace term and press Enter to preview'), placeholder: nls.localize('search.replace.placeHolder', "Replace"), - appendPreserveCaseLabel: appendKeyBindingLabel('', this.keybindingService.lookupKeybinding(Constants.TogglePreserveCaseId)), + appendPreserveCaseLabel: appendKeyBindingLabel('', this.keybindingService.lookupKeybinding(Constants.SearchCommandIds.TogglePreserveCaseId)), history: options.replaceHistory, showHistoryHint: () => showHistoryKeybindingHint(this.keybindingService), flexibleHeight: true, @@ -759,7 +759,7 @@ export function registerContributions() { KeybindingsRegistry.registerCommandAndKeybindingRule({ id: ReplaceAllAction.ID, weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, CONTEXT_FIND_WIDGET_NOT_VISIBLE), + when: ContextKeyExpr.and(Constants.SearchContext.SearchViewVisibleKey, Constants.SearchContext.ReplaceActiveKey, CONTEXT_FIND_WIDGET_NOT_VISIBLE), primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.Enter, handler: accessor => { const viewsService = accessor.get(IViewsService); diff --git a/src/vs/workbench/contrib/search/common/constants.ts b/src/vs/workbench/contrib/search/common/constants.ts index 1f98bc77b35..a8f1bc84311 100644 --- a/src/vs/workbench/contrib/search/common/constants.ts +++ b/src/vs/workbench/contrib/search/common/constants.ts @@ -5,70 +5,73 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -export const FindInFilesActionId = 'workbench.action.findInFiles'; -export const FocusActiveEditorCommandId = 'search.action.focusActiveEditor'; +export const enum SearchCommandIds { + FindInFilesActionId = 'workbench.action.findInFiles', + FocusActiveEditorCommandId = 'search.action.focusActiveEditor', + FocusSearchFromResults = 'search.action.focusSearchFromResults', + OpenMatch = 'search.action.openResult', + OpenMatchToSide = 'search.action.openResultToSide', + RemoveActionId = 'search.action.remove', + CopyPathCommandId = 'search.action.copyPath', + CopyMatchCommandId = 'search.action.copyMatch', + CopyAllCommandId = 'search.action.copyAll', + OpenInEditorCommandId = 'search.action.openInEditor', + ClearSearchHistoryCommandId = 'search.action.clearHistory', + FocusSearchListCommandID = 'search.action.focusSearchList', + ReplaceActionId = 'search.action.replace', + ReplaceAllInFileActionId = 'search.action.replaceAllInFile', + ReplaceAllInFolderActionId = 'search.action.replaceAllInFolder', + CloseReplaceWidgetActionId = 'closeReplaceInFilesWidget', + ToggleCaseSensitiveCommandId = 'toggleSearchCaseSensitive', + ToggleWholeWordCommandId = 'toggleSearchWholeWord', + ToggleRegexCommandId = 'toggleSearchRegex', + TogglePreserveCaseId = 'toggleSearchPreserveCase', + AddCursorsAtSearchResults = 'addCursorsAtSearchResults', + RevealInSideBarForSearchResults = 'search.action.revealInSideBar', + ReplaceInFilesActionId = 'workbench.action.replaceInFiles', + ShowAllSymbolsActionId = 'workbench.action.showAllSymbols', + QuickTextSearchActionId = 'workbench.action.experimental.quickTextSearch', + CancelSearchActionId = 'search.action.cancel', + RefreshSearchResultsActionId = 'search.action.refreshSearchResults', + FocusNextSearchResultActionId = 'search.action.focusNextSearchResult', + FocusPreviousSearchResultActionId = 'search.action.focusPreviousSearchResult', + ToggleSearchOnTypeActionId = 'workbench.action.toggleSearchOnType', + CollapseSearchResultsActionId = 'search.action.collapseSearchResults', + ExpandSearchResultsActionId = 'search.action.expandSearchResults', + ClearSearchResultsActionId = 'search.action.clearSearchResults', + ViewAsTreeActionId = 'search.action.viewAsTree', + ViewAsListActionId = 'search.action.viewAsList', + ToggleQueryDetailsActionId = 'workbench.action.search.toggleQueryDetails', + ExcludeFolderFromSearchId = 'search.action.excludeFromSearch', + FocusNextInputActionId = 'search.focus.nextInputBox', + FocusPreviousInputActionId = 'search.focus.previousInputBox', + RestrictSearchToFolderId = 'search.action.restrictSearchToFolder', + FindInFolderId = 'filesExplorer.findInFolder', + FindInWorkspaceId = 'filesExplorer.findInWorkspace', +} -export const FocusSearchFromResults = 'search.action.focusSearchFromResults'; -export const OpenMatch = 'search.action.openResult'; -export const OpenMatchToSide = 'search.action.openResultToSide'; -export const RemoveActionId = 'search.action.remove'; -export const CopyPathCommandId = 'search.action.copyPath'; -export const CopyMatchCommandId = 'search.action.copyMatch'; -export const CopyAllCommandId = 'search.action.copyAll'; -export const OpenInEditorCommandId = 'search.action.openInEditor'; -export const ClearSearchHistoryCommandId = 'search.action.clearHistory'; -export const FocusSearchListCommandID = 'search.action.focusSearchList'; -export const ReplaceActionId = 'search.action.replace'; -export const ReplaceAllInFileActionId = 'search.action.replaceAllInFile'; -export const ReplaceAllInFolderActionId = 'search.action.replaceAllInFolder'; -export const CloseReplaceWidgetActionId = 'closeReplaceInFilesWidget'; -export const ToggleCaseSensitiveCommandId = 'toggleSearchCaseSensitive'; -export const ToggleWholeWordCommandId = 'toggleSearchWholeWord'; -export const ToggleRegexCommandId = 'toggleSearchRegex'; -export const TogglePreserveCaseId = 'toggleSearchPreserveCase'; -export const AddCursorsAtSearchResults = 'addCursorsAtSearchResults'; -export const RevealInSideBarForSearchResults = 'search.action.revealInSideBar'; -export const ReplaceInFilesActionId = 'workbench.action.replaceInFiles'; -export const ShowAllSymbolsActionId = 'workbench.action.showAllSymbols'; -export const QuickTextSearchActionId = 'workbench.action.experimental.quickTextSearch'; -export const CancelSearchActionId = 'search.action.cancel'; -export const RefreshSearchResultsActionId = 'search.action.refreshSearchResults'; -export const FocusNextSearchResultActionId = 'search.action.focusNextSearchResult'; -export const FocusPreviousSearchResultActionId = 'search.action.focusPreviousSearchResult'; -export const ToggleSearchOnTypeActionId = 'workbench.action.toggleSearchOnType'; -export const CollapseSearchResultsActionId = 'search.action.collapseSearchResults'; -export const ExpandSearchResultsActionId = 'search.action.expandSearchResults'; -export const ClearSearchResultsActionId = 'search.action.clearSearchResults'; -export const ViewAsTreeActionId = 'search.action.viewAsTree'; -export const ViewAsListActionId = 'search.action.viewAsList'; -export const ToggleQueryDetailsActionId = 'workbench.action.search.toggleQueryDetails'; -export const ExcludeFolderFromSearchId = 'search.action.excludeFromSearch'; -export const FocusNextInputActionId = 'search.focus.nextInputBox'; -export const FocusPreviousInputActionId = 'search.focus.previousInputBox'; -export const RestrictSearchToFolderId = 'search.action.restrictSearchToFolder'; -export const FindInFolderId = 'filesExplorer.findInFolder'; -export const FindInWorkspaceId = 'filesExplorer.findInWorkspace'; - -export const SearchViewVisibleKey = new RawContextKey('searchViewletVisible', true); -export const SearchViewFocusedKey = new RawContextKey('searchViewletFocus', false); -export const InputBoxFocusedKey = new RawContextKey('inputBoxFocus', false); -export const SearchInputBoxFocusedKey = new RawContextKey('searchInputBoxFocus', false); -export const ReplaceInputBoxFocusedKey = new RawContextKey('replaceInputBoxFocus', false); -export const PatternIncludesFocusedKey = new RawContextKey('patternIncludesInputBoxFocus', false); -export const PatternExcludesFocusedKey = new RawContextKey('patternExcludesInputBoxFocus', false); -export const ReplaceActiveKey = new RawContextKey('replaceActive', false); -export const HasSearchResults = new RawContextKey('hasSearchResult', false); -export const FirstMatchFocusKey = new RawContextKey('firstMatchFocus', false); -export const FileMatchOrMatchFocusKey = new RawContextKey('fileMatchOrMatchFocus', false); // This is actually, Match or File or Folder -export const FileMatchOrFolderMatchFocusKey = new RawContextKey('fileMatchOrFolderMatchFocus', false); -export const FileMatchOrFolderMatchWithResourceFocusKey = new RawContextKey('fileMatchOrFolderMatchWithResourceFocus', false); // Excludes "Other files" -export const FileFocusKey = new RawContextKey('fileMatchFocus', false); -export const FolderFocusKey = new RawContextKey('folderMatchFocus', false); -export const ResourceFolderFocusKey = new RawContextKey('folderMatchWithResourceFocus', false); -export const IsEditableItemKey = new RawContextKey('isEditableItem', true); -export const MatchFocusKey = new RawContextKey('matchFocus', false); -export const ViewHasSearchPatternKey = new RawContextKey('viewHasSearchPattern', false); -export const ViewHasReplacePatternKey = new RawContextKey('viewHasReplacePattern', false); -export const ViewHasFilePatternKey = new RawContextKey('viewHasFilePattern', false); -export const ViewHasSomeCollapsibleKey = new RawContextKey('viewHasSomeCollapsibleResult', false); -export const InTreeViewKey = new RawContextKey('inTreeView', false); +export const SearchContext = { + SearchViewVisibleKey: new RawContextKey('searchViewletVisible', true), + SearchViewFocusedKey: new RawContextKey('searchViewletFocus', false), + InputBoxFocusedKey: new RawContextKey('inputBoxFocus', false), + SearchInputBoxFocusedKey: new RawContextKey('searchInputBoxFocus', false), + ReplaceInputBoxFocusedKey: new RawContextKey('replaceInputBoxFocus', false), + PatternIncludesFocusedKey: new RawContextKey('patternIncludesInputBoxFocus', false), + PatternExcludesFocusedKey: new RawContextKey('patternExcludesInputBoxFocus', false), + ReplaceActiveKey: new RawContextKey('replaceActive', false), + HasSearchResults: new RawContextKey('hasSearchResult', false), + FirstMatchFocusKey: new RawContextKey('firstMatchFocus', false), + FileMatchOrMatchFocusKey: new RawContextKey('fileMatchOrMatchFocus', false), // This is actually, Match or File or Folder + FileMatchOrFolderMatchFocusKey: new RawContextKey('fileMatchOrFolderMatchFocus', false), + FileMatchOrFolderMatchWithResourceFocusKey: new RawContextKey('fileMatchOrFolderMatchWithResourceFocus', false), // Excludes "Other files" + FileFocusKey: new RawContextKey('fileMatchFocus', false), + FolderFocusKey: new RawContextKey('folderMatchFocus', false), + ResourceFolderFocusKey: new RawContextKey('folderMatchWithResourceFocus', false), + IsEditableItemKey: new RawContextKey('isEditableItem', true), + MatchFocusKey: new RawContextKey('matchFocus', false), + ViewHasSearchPatternKey: new RawContextKey('viewHasSearchPattern', false), + ViewHasReplacePatternKey: new RawContextKey('viewHasReplacePattern', false), + ViewHasFilePatternKey: new RawContextKey('viewHasFilePattern', false), + ViewHasSomeCollapsibleKey: new RawContextKey('viewHasSomeCollapsibleResult', false), + InTreeViewKey: new RawContextKey('inTreeView', false), +}; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index db9f8621b93..d618cdb57e1 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -303,7 +303,7 @@ registerAction2(class extends Action2 { f1: true, keybinding: { primary: KeyMod.Alt | KeyCode.Enter, - when: ContextKeyExpr.and(SearchConstants.HasSearchResults, SearchConstants.SearchViewFocusedKey), + when: ContextKeyExpr.and(SearchConstants.SearchContext.HasSearchResults, SearchConstants.SearchContext.SearchViewFocusedKey), weight: KeybindingWeight.WorkbenchContrib, mac: { primary: KeyMod.CtrlCmd | KeyCode.Enter @@ -424,7 +424,7 @@ registerAction2(class extends Action2 { precondition: SearchEditorConstants.InSearchEditor, keybinding: Object.assign({ weight: KeybindingWeight.WorkbenchContrib, - when: SearchConstants.SearchInputBoxFocusedKey, + when: SearchConstants.SearchContext.SearchInputBoxFocusedKey, }, ToggleCaseSensitiveKeybinding) }); } @@ -443,7 +443,7 @@ registerAction2(class extends Action2 { precondition: SearchEditorConstants.InSearchEditor, keybinding: Object.assign({ weight: KeybindingWeight.WorkbenchContrib, - when: SearchConstants.SearchInputBoxFocusedKey, + when: SearchConstants.SearchContext.SearchInputBoxFocusedKey, }, ToggleWholeWordKeybinding) }); } @@ -462,7 +462,7 @@ registerAction2(class extends Action2 { precondition: SearchEditorConstants.InSearchEditor, keybinding: Object.assign({ weight: KeybindingWeight.WorkbenchContrib, - when: SearchConstants.SearchInputBoxFocusedKey, + when: SearchConstants.SearchContext.SearchInputBoxFocusedKey, }, ToggleRegexKeybinding) }); } diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index b18fbdbf655..5f36473df15 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -41,7 +41,6 @@ import { EditorInputCapabilities, IEditorOpenContext } from 'vs/workbench/common import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ExcludePatternInputWidget, IncludePatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; import { SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget'; -import { InputBoxFocusedKey } from 'vs/workbench/contrib/search/common/constants'; import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder'; import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; import { SearchModel, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; @@ -62,6 +61,7 @@ import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/edi import { UnusualLineTerminatorsDetector } from 'vs/editor/contrib/unusualLineTerminators/browser/unusualLineTerminators'; import { defaultToggleStyles, getInputBoxStyle } from 'vs/platform/theme/browser/defaultStyles'; import { ILogService } from 'vs/platform/log/common/log'; +import { SearchContext } from 'vs/workbench/contrib/search/common/constants'; const RESULT_LINE_REGEX = /^(\s+)(\d+)(: | )(\s*)(.*)$/; const FILE_LINE_REGEX = /^(\S.*):$/; @@ -138,7 +138,7 @@ export class SearchEditor extends AbstractTextCodeEditor this.createQueryEditor( this.queryEditorContainer, this.instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])), - InputBoxFocusedKey.bindTo(scopedContextKeyService) + SearchContext.InputBoxFocusedKey.bindTo(scopedContextKeyService) ); } From 01a85f298b17fae41b6f887d4a296d48346331b1 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 29 Jan 2024 18:01:03 -0800 Subject: [PATCH 0713/1897] Polish find widget css rules (#203762) * refactor css variables for findWidget * adapt css variable for findWidget * Polish find widget css rules --------- Co-authored-by: weartist --- .../contrib/find/browser/findWidget.css | 48 ++++++++++++ .../editor/contrib/find/browser/findWidget.ts | 74 +------------------ 2 files changed, 49 insertions(+), 73 deletions(-) diff --git a/src/vs/editor/contrib/find/browser/findWidget.css b/src/vs/editor/contrib/find/browser/findWidget.css index 9c20f8682a0..782c19a53b2 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.css +++ b/src/vs/editor/contrib/find/browser/findWidget.css @@ -14,8 +14,15 @@ padding: 0 4px; box-sizing: border-box; transform: translateY(calc(-100% - 10px)); /* shadow (10px) */ + box-shadow: 0 0 8px 2px var(--vscode-widget-shadow); + border: 1px solid var(--vscode-contrastBorder); + color: var(--vscode-editorWidget-foreground); + border-left: 1px solid var(--vscode-widget-border); + border-right: 1px solid var(--vscode-widget-border); + border-bottom: 1px solid var(--vscode-widget-border); border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; + background-color: var(--vscode-editorWidget-background); } .monaco-workbench.reduce-motion .monaco-editor .find-widget { @@ -39,9 +46,11 @@ transform: translateY(0); } +/* This outline-color rule is used to override the outline color for synthetic-focus find input. */ .monaco-editor .find-widget .monaco-inputbox.synthetic-focus { outline: 1px solid -webkit-focus-ring-color; outline-offset: -1px; + outline-color: var(--vscode-focusBorder); } .monaco-editor .find-widget .monaco-inputbox .input { @@ -214,13 +223,30 @@ display:none; } +.monaco-editor .find-widget.no-results .matchesCount { + color: var(--vscode-errorForeground); +} + .monaco-editor .findMatch { animation-duration: 0; animation-name: inherit !important; + background-color: var(--vscode-editor-findMatchHighlightBackground); +} + +.monaco-editor .currentFindMatch { + background-color: var(--vscode-editor-findMatchBackground); + border: 2px solid var(--vscode-editor-findMatchBorder); + padding: 1px; + box-sizing: border-box; +} + +.monaco-editor .findScope { + background-color: var(--vscode-editor-findRangeHighlightBackground); } .monaco-editor .find-widget .monaco-sash { left: 0 !important; + background-color: var(--vscode-editorWidget-resizeBorder, var(--vscode-editorWidget-border)); } .monaco-editor.hc-black .find-widget .button:before { @@ -229,6 +255,28 @@ left: 2px; } +/* Action bars */ +.monaco-editor .find-widget .button:not(.disabled):hover, +.monaco-editor .find-widget .codicon-find-selection:hover { + background-color: var(--vscode-toolbar-hoverBackground) !important; +} + +.monaco-editor.findMatch { + background-color: var(--vscode-editor-findMatchHighlightBackground); +} + +.monaco-editor.currentFindMatch { + background-color: var(--vscode-editor-findMatchBackground); +} + +.monaco-editor.findScope { + background-color: var(--vscode-editor-findRangeHighlightBackground); +} + +.monaco-editor.findMatch { + background-color: var(--vscode-editorWidget-background); +} + /* Close button position. */ .monaco-editor .find-widget > .button.codicon-widget-close { position: absolute; diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index 84a24173b16..9ac46cbee07 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -16,7 +16,6 @@ import { ISashEvent, IVerticalSashLayoutProvider, Orientation, Sash } from 'vs/b import { Widget } from 'vs/base/browser/ui/widget'; import { Delayer } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; -import { Color } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { toDisposable } from 'vs/base/common/lifecycle'; @@ -36,7 +35,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { asCssVariable, contrastBorder, editorFindMatch, editorFindMatchBorder, editorFindMatchHighlight, editorFindMatchHighlightBorder, editorFindRangeHighlight, editorFindRangeHighlightBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetForeground, editorWidgetResizeBorder, errorForeground, focusBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, toolbarHoverBackground, widgetBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { asCssVariable, editorFindMatchHighlightBorder, editorFindRangeHighlightBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerIcon, widgetClose } from 'vs/platform/theme/common/iconRegistry'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ThemeIcon } from 'vs/base/common/themables'; @@ -1376,84 +1375,13 @@ export class SimpleButton extends Widget { // theming registerThemingParticipant((theme, collector) => { - const addBackgroundColorRule = (selector: string, color: Color | undefined): void => { - if (color) { - collector.addRule(`.monaco-editor ${selector} { background-color: ${color}; }`); - } - }; - - addBackgroundColorRule('.findMatch', theme.getColor(editorFindMatchHighlight)); - addBackgroundColorRule('.currentFindMatch', theme.getColor(editorFindMatch)); - addBackgroundColorRule('.findScope', theme.getColor(editorFindRangeHighlight)); - - const widgetBackground = theme.getColor(editorWidgetBackground); - addBackgroundColorRule('.find-widget', widgetBackground); - - const widgetShadowColor = theme.getColor(widgetShadow); - if (widgetShadowColor) { - collector.addRule(`.monaco-editor .find-widget { box-shadow: 0 0 8px 2px ${widgetShadowColor}; }`); - } - - const widgetBorderColor = theme.getColor(widgetBorder); - if (widgetBorderColor) { - collector.addRule(`.monaco-editor .find-widget { border-left: 1px solid ${widgetBorderColor}; border-right: 1px solid ${widgetBorderColor}; border-bottom: 1px solid ${widgetBorderColor}; }`); - } - const findMatchHighlightBorder = theme.getColor(editorFindMatchHighlightBorder); if (findMatchHighlightBorder) { collector.addRule(`.monaco-editor .findMatch { border: 1px ${isHighContrast(theme.type) ? 'dotted' : 'solid'} ${findMatchHighlightBorder}; box-sizing: border-box; }`); } - const findMatchBorder = theme.getColor(editorFindMatchBorder); - if (findMatchBorder) { - collector.addRule(`.monaco-editor .currentFindMatch { border: 2px solid ${findMatchBorder}; padding: 1px; box-sizing: border-box; }`); - } - const findRangeHighlightBorder = theme.getColor(editorFindRangeHighlightBorder); if (findRangeHighlightBorder) { collector.addRule(`.monaco-editor .findScope { border: 1px ${isHighContrast(theme.type) ? 'dashed' : 'solid'} ${findRangeHighlightBorder}; }`); } - - const hcBorder = theme.getColor(contrastBorder); - if (hcBorder) { - collector.addRule(`.monaco-editor .find-widget { border: 1px solid ${hcBorder}; }`); - } - - const foreground = theme.getColor(editorWidgetForeground); - if (foreground) { - collector.addRule(`.monaco-editor .find-widget { color: ${foreground}; }`); - } - - const error = theme.getColor(errorForeground); - if (error) { - collector.addRule(`.monaco-editor .find-widget.no-results .matchesCount { color: ${error}; }`); - } - - const resizeBorderBackground = theme.getColor(editorWidgetResizeBorder); - if (resizeBorderBackground) { - collector.addRule(`.monaco-editor .find-widget .monaco-sash { background-color: ${resizeBorderBackground}; }`); - } else { - const border = theme.getColor(editorWidgetBorder); - if (border) { - collector.addRule(`.monaco-editor .find-widget .monaco-sash { background-color: ${border}; }`); - } - } - - // Action bars - const toolbarHoverBackgroundColor = theme.getColor(toolbarHoverBackground); - if (toolbarHoverBackgroundColor) { - collector.addRule(` - .monaco-editor .find-widget .button:not(.disabled):hover, - .monaco-editor .find-widget .codicon-find-selection:hover { - background-color: ${toolbarHoverBackgroundColor} !important; - } - `); - } - - // This rule is used to override the outline color for synthetic-focus find input. - const focusOutline = theme.getColor(focusBorder); - if (focusOutline) { - collector.addRule(`.monaco-editor .find-widget .monaco-inputbox.synthetic-focus { outline-color: ${focusOutline}; }`); - - } }); From d7cfba7af7ff69830a973278f4470a8147d7a3bf Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 29 Jan 2024 18:01:21 -0800 Subject: [PATCH 0714/1897] cli: update dev tunnel sdk (#203763) --- cli/Cargo.lock | 33 ++++++++++-- cli/Cargo.toml | 2 +- cli/src/tunnels/dev_tunnels.rs | 98 +++++++++++++++++----------------- 3 files changed, 77 insertions(+), 56 deletions(-) diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 311614b4301..c102791a69b 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -384,7 +384,7 @@ dependencies = [ "url", "uuid", "winapi", - "winreg", + "winreg 0.50.0", "zbus", "zip", ] @@ -1594,6 +1594,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "os_info" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" +dependencies = [ + "log", + "winapi", +] + [[package]] name = "parking" version = "2.0.0" @@ -1888,7 +1898,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "winreg", + "winreg 0.50.0", ] [[package]] @@ -2516,13 +2526,15 @@ dependencies = [ [[package]] name = "tunnels" version = "0.1.0" -source = "git+https://github.com/microsoft/dev-tunnels?rev=97233d20448e1c3cb0e0fd9114acf68c7e5c0249#97233d20448e1c3cb0e0fd9114acf68c7e5c0249" +source = "git+https://github.com/microsoft/dev-tunnels?rev=c1bf1424846ce3a1297ed3be7b8401cef6904bee#c1bf1424846ce3a1297ed3be7b8401cef6904bee" dependencies = [ "async-trait", "chrono", "futures", "hyper", "log", + "os_info", + "rand 0.8.5", "reqwest", "russh", "russh-keys", @@ -2534,7 +2546,9 @@ dependencies = [ "tokio-util", "tungstenite", "url", + "urlencoding", "uuid", + "winreg 0.8.0", ] [[package]] @@ -2599,9 +2613,9 @@ dependencies = [ [[package]] name = "urlencoding" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "utf-8" @@ -2987,6 +3001,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d107f8c6e916235c4c01cabb3e8acf7bea8ef6a63ca2e7fa0527c049badfc48c" +dependencies = [ + "winapi", +] + [[package]] name = "winreg" version = "0.50.0" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index f6de2a2c616..334ac37d73c 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -34,7 +34,7 @@ serde_bytes = "0.11.9" chrono = { version = "0.4.26", features = ["serde", "std", "clock"], default-features = false } gethostname = "0.4.3" libc = "0.2.144" -tunnels = { git = "https://github.com/microsoft/dev-tunnels", rev = "97233d20448e1c3cb0e0fd9114acf68c7e5c0249", default-features = false, features = ["connections"] } +tunnels = { git = "https://github.com/microsoft/dev-tunnels", rev = "c1bf1424846ce3a1297ed3be7b8401cef6904bee", default-features = false, features = ["connections"] } keyring = { version = "2.0.3", default-features = false, features = ["linux-secret-service-rt-tokio-crypto-openssl"] } dialoguer = "0.10.4" hyper = { version = "0.14.26", features = ["server", "http1", "runtime"] } diff --git a/cli/src/tunnels/dev_tunnels.rs b/cli/src/tunnels/dev_tunnels.rs index 994b5060f40..94396e89977 100644 --- a/cli/src/tunnels/dev_tunnels.rs +++ b/cli/src/tunnels/dev_tunnels.rs @@ -409,14 +409,14 @@ impl DevTunnels { } }?; - let desired_tags = self.get_tags(&name); - if is_new || vec_eq_as_set(&full_tunnel.tags, &desired_tags) { + let desired_tags = self.get_labels(&name); + if is_new || vec_eq_as_set(&full_tunnel.labels, &desired_tags) { return Ok((full_tunnel, persisted)); } debug!(self.log, "Tunnel name changed, applying updates..."); - full_tunnel.tags = desired_tags; + full_tunnel.labels = desired_tags; let updated_tunnel = spanf!( self.log, @@ -531,7 +531,6 @@ impl DevTunnels { let fut = self.client.delete_tunnel_endpoints( &locator, &endpoint.host_id, - None, NO_REQUEST_OPTIONS, ); @@ -572,52 +571,51 @@ impl DevTunnels { ) .map_err(|e| wrap(e, "failed to lookup tunnel"))? } - None => { - let new_tunnel = Tunnel { - tags: self.get_tags(name), - ..Default::default() - }; + None => loop { + let result = spanf!( + self.log, + self.log.span("dev-tunnel.create"), + self.client.create_tunnel( + Tunnel { + labels: self.get_labels(name), + ..Default::default() + }, + options + ) + ); - loop { - let result = spanf!( - self.log, - self.log.span("dev-tunnel.create"), - self.client.create_tunnel(&new_tunnel, options) - ); - - match result { - Err(HttpError::ResponseError(e)) - if e.status_code == StatusCode::TOO_MANY_REQUESTS => - { - if let Some(d) = e.get_details() { - let detail = d.detail.unwrap_or_else(|| "unknown".to_string()); - if detail.contains(TUNNEL_COUNT_LIMIT_NAME) - && self.try_recycle_tunnel().await? - { - continue; - } - - return Err(AnyError::from(TunnelCreationFailed( - name.to_string(), - detail, - ))); + match result { + Err(HttpError::ResponseError(e)) + if e.status_code == StatusCode::TOO_MANY_REQUESTS => + { + if let Some(d) = e.get_details() { + let detail = d.detail.unwrap_or_else(|| "unknown".to_string()); + if detail.contains(TUNNEL_COUNT_LIMIT_NAME) + && self.try_recycle_tunnel().await? + { + continue; } return Err(AnyError::from(TunnelCreationFailed( name.to_string(), - "You have exceeded a limit for the port fowarding service. Please remove other machines before trying to add this machine.".to_string(), + detail, ))); } - Err(e) => { - return Err(AnyError::from(TunnelCreationFailed( + + return Err(AnyError::from(TunnelCreationFailed( name.to_string(), - format!("{:?}", e), - ))) - } - Ok(t) => break t, + "You have exceeded a limit for the port fowarding service. Please remove other machines before trying to add this machine.".to_string(), + ))); } + Err(e) => { + return Err(AnyError::from(TunnelCreationFailed( + name.to_string(), + format!("{:?}", e), + ))) + } + Ok(t) => break t, } - } + }, }; let pt = PersistedTunnel { @@ -631,7 +629,7 @@ impl DevTunnels { } /// Gets the expected tunnel tags - fn get_tags(&self, name: &str) -> Vec { + fn get_labels(&self, name: &str) -> Vec { vec![ name.to_string(), PROTOCOL_VERSION_TAG.to_string(), @@ -649,20 +647,20 @@ impl DevTunnels { tunnel: Tunnel, options: &TunnelRequestOptions, ) -> Result { - let new_tags = self.get_tags(name); - if vec_eq_as_set(&tunnel.tags, &new_tags) { + let new_labels = self.get_labels(name); + if vec_eq_as_set(&tunnel.labels, &new_labels) { return Ok(tunnel); } debug!( self.log, "Updating tunnel tags {} -> {}", - tunnel.tags.join(", "), - new_tags.join(", ") + tunnel.labels.join(", "), + new_labels.join(", ") ); let tunnel_update = Tunnel { - tags: new_tags, + labels: new_labels, tunnel_id: tunnel.tunnel_id.clone(), cluster_id: tunnel.cluster_id.clone(), ..Default::default() @@ -725,7 +723,7 @@ impl DevTunnels { self.log, self.log.span("dev-tunnel.listall"), self.client.list_all_tunnels(&TunnelRequestOptions { - tags: tags.iter().map(|t| t.to_string()).collect(), + labels: tags.iter().map(|t| t.to_string()).collect(), ..Default::default() }) ) @@ -739,8 +737,8 @@ impl DevTunnels { self.log, self.log.span("dev-tunnel.rename.search"), self.client.list_all_tunnels(&TunnelRequestOptions { - tags: vec![self.tag.to_string(), name.to_string()], - require_all_tags: true, + labels: vec![self.tag.to_string(), name.to_string()], + require_all_labels: true, limit: 1, include_ports: true, token_scopes: vec!["host".to_string()], @@ -770,7 +768,7 @@ impl DevTunnels { v.status .as_ref() .and_then(|s| s.host_connection_count.as_ref().map(|c| c.get_count())) - .unwrap_or(0) > 0 && v.tags.iter().any(|t| t == n) + .unwrap_or(0) > 0 && v.labels.iter().any(|t| t == n) }) }; From c95fb565baaedfcb013093d5e4d7b7e426a659e3 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:32:58 -0800 Subject: [PATCH 0715/1897] changed issueReporterCommand types and bug fix (#203747) changed uri from string, word wrap --- src/vs/code/electron-sandbox/issue/issueReporterPage.ts | 2 +- src/vs/code/electron-sandbox/issue/issueReporterService.ts | 7 +++++-- src/vs/platform/issue/common/issue.ts | 6 +++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/vs/code/electron-sandbox/issue/issueReporterPage.ts b/src/vs/code/electron-sandbox/issue/issueReporterPage.ts index 96ec5f7d7bc..bf5dca66aeb 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterPage.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterPage.ts @@ -93,7 +93,7 @@ export default (): string => `
${escape(localize('show', "show"))} -
+			
 				
 			
diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index 1924ff872a0..ebd76a1d4d5 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -15,6 +15,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { isLinuxSnap, isMacintosh } from 'vs/base/common/platform'; import { escape } from 'vs/base/common/strings'; import { ThemeIcon } from 'vs/base/common/themables'; +import { URI } from 'vs/base/common/uri'; import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from 'vs/code/electron-sandbox/issue/issueReporterModel'; import { localize } from 'vs/nls'; import { isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; @@ -249,7 +250,8 @@ export class IssueReporter extends Disposable { private async updateIssueReporterUri(extension: IssueReporterExtensionData): Promise { try { if (extension.command?.uri) { - extension.bugsUrl = extension.command.uri; + const uri = URI.revive(extension.command.uri); + extension.bugsUrl = uri.toString(); } else { const uri = await this.issueMainService.$getIssueReporterUri(extension.id); extension.bugsUrl = uri.toString(true); @@ -958,7 +960,8 @@ export class IssueReporter extends Disposable { } if (selectedExtension?.command?.uri) { - issueUrl = selectedExtension.command.uri; + const uri = URI.revive(selectedExtension.command.uri); + issueUrl = uri.toString(); } const gitHubDetails = this.parseGitHubUrl(issueUrl); diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index 8d9adda1019..b4e911f1c02 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; import { PerformanceInfo, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -60,7 +60,7 @@ export interface IssueReporterExtensionData { command?: { data?: string; template?: string; - uri?: string; + uri?: UriComponents; }; } @@ -78,7 +78,7 @@ export interface IssueReporterData extends WindowData { command?: { data?: string; template?: string; - uri?: string; + uri?: UriComponents; }; } From e28439607484596a3c95f19eec9292c3fec69170 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Mon, 29 Jan 2024 19:24:14 -0800 Subject: [PATCH 0716/1897] chore: use newest node-gyp for SDL pipeline (#199820) * chore: use latest upstream node-gyp * fix: use node-gyp.js file directly * apply PR feedback * chore: delete patch file * Apply PR feedback * Update cache salt --- build/.cachesalt | 2 +- build/azure-pipelines/sdl-scan.yml | 25 +++------ .../win32/product-build-win32.yml | 30 +++-------- .../gyp_spectre_mitigation_support.patch | 51 ------------------- 4 files changed, 14 insertions(+), 94 deletions(-) delete mode 100644 build/npm/gyp/patches/gyp_spectre_mitigation_support.patch diff --git a/build/.cachesalt b/build/.cachesalt index a8a329ac0b8..4f3f91b0edf 100644 --- a/build/.cachesalt +++ b/build/.cachesalt @@ -1 +1 @@ -2023-12-07T16:21:36.646Z +2024-01-29T19:26:27.993Z diff --git a/build/azure-pipelines/sdl-scan.yml b/build/azure-pipelines/sdl-scan.yml index 91c8a2477af..927cd5e04ae 100644 --- a/build/azure-pipelines/sdl-scan.yml +++ b/build/azure-pipelines/sdl-scan.yml @@ -95,29 +95,16 @@ stages: displayName: CodeQL Initialize condition: eq(variables['Codeql.enabled'], 'True') - - powershell: | - mkdir -Force .build/node-gyp - displayName: Create custom node-gyp directory - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - powershell: | - . ../../build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - # TODO: Should be replaced with upstream URL once https://github.com/nodejs/node-gyp/pull/2825 - # gets merged. - exec { git clone https://github.com/rzhao271/node-gyp.git . } "Cloning rzhao271/node-gyp failed" - exec { git checkout 102b347da0c92c29f9c67df22e864e70249cf086 } "Checking out 102b347 failed" - exec { npm install } "Building rzhao271/node-gyp failed" - exec { python3 -m pip install setuptools } "Installing setuptools failed" - displayName: Install custom node-gyp - workingDirectory: .build/node-gyp - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - powershell: | . build/azure-pipelines/win32/exec.ps1 . build/azure-pipelines/win32/retry.ps1 $ErrorActionPreference = "Stop" - $env:npm_config_node_gyp = "$(Join-Path $pwd.Path '.build/node-gyp/bin/node-gyp.js')" + # TODO: remove custom node-gyp when updating to Node v20, + # refs https://github.com/npm/cli/releases/tag/v10.2.3 which is available with Node >= 20.10.0 + $nodeGypDir = "$(Agent.TempDirectory)/custom-packages" + mkdir "$nodeGypDir" + npm install node-gyp@10.0.1 -g --prefix "$nodeGypDir" + $env:npm_config_node_gyp = "${nodeGypDir}/node_modules/node-gyp/bin/node-gyp.js" $env:npm_config_arch = "$(NPM_ARCH)" retry { exec { yarn --frozen-lockfile --check-files } } env: diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 8727c5b5a2b..ed316e721bc 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -89,33 +89,17 @@ steps: condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) displayName: Setup NPM Authentication - - powershell: | - mkdir -Force .build/node-gyp - displayName: Create custom node-gyp directory - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - powershell: | - . ../../build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - # TODO: Should be replaced with upstream URL once https://github.com/nodejs/node-gyp/pull/2825 - # gets merged. - exec { git config --global user.email "vscode@microsoft.com" } "git config user.email failed" - exec { git config --global user.name "VSCode" } "git config user.name failed" - exec { git clone https://github.com/nodejs/node-gyp.git . } "Cloning nodejs/node-gyp failed" - exec { git checkout v9.4.0 } "Checking out v9.4.0 failed" - exec { git am --3way --whitespace=fix ../../build/npm/gyp/patches/gyp_spectre_mitigation_support.patch } "Apply spectre patch failed" - exec { npm install } "Building node-gyp failed" - exec { python3 -m pip install setuptools } "Installing setuptools failed" - displayName: Install custom node-gyp - workingDirectory: .build/node-gyp - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - powershell: | . build/azure-pipelines/win32/exec.ps1 . build/azure-pipelines/win32/retry.ps1 $ErrorActionPreference = "Stop" - $env:npm_config_node_gyp="$(Join-Path $pwd.Path '.build/node-gyp/bin/node-gyp.js')" - $env:npm_config_arch="$(VSCODE_ARCH)" + # TODO: remove custom node-gyp when updating to Node v20, + # refs https://github.com/npm/cli/releases/tag/v10.2.3 which is available with Node >= 20.10.0 + $nodeGypDir = "$(Agent.TempDirectory)/custom-packages" + mkdir "$nodeGypDir" + npm install node-gyp@10.0.1 -g --prefix "$nodeGypDir" + $env:npm_config_node_gyp = "${nodeGypDir}/node_modules/node-gyp/bin/node-gyp.js" + $env:npm_config_arch = "$(VSCODE_ARCH)" $env:CHILD_CONCURRENCY="1" retry { exec { yarn --frozen-lockfile --check-files } } env: diff --git a/build/npm/gyp/patches/gyp_spectre_mitigation_support.patch b/build/npm/gyp/patches/gyp_spectre_mitigation_support.patch deleted file mode 100644 index e64f42e2b04..00000000000 --- a/build/npm/gyp/patches/gyp_spectre_mitigation_support.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 853e4643b6737224a5aa0720a4108461a0230991 Mon Sep 17 00:00:00 2001 -From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> -Date: Thu, 30 Mar 2023 05:23:36 -0700 -Subject: [PATCH] feat(msvs): add SpectreMitigation attribute (#190) - -Backports https://github.com/nodejs/gyp-next/commit/853e4643b6737224a5aa0720a4108461a0230991 - -diff --git a/gyp/pylib/gyp/easy_xml_test.py b/gyp/pylib/gyp/easy_xml_test.py -index 342f693..c5808b8 100755 ---- a/gyp/pylib/gyp/easy_xml_test.py -+++ b/gyp/pylib/gyp/easy_xml_test.py -@@ -76,6 +76,7 @@ def test_EasyXml_complex(self): - '\'Debug|Win32\'" Label="Configuration">' - "Application" - "Unicode" -+ "SpectreLoadCF" - "" - "" - ) -@@ -99,6 +100,7 @@ def test_EasyXml_complex(self): - }, - ["ConfigurationType", "Application"], - ["CharacterSet", "Unicode"], -+ ["SpectreMitigation", "SpectreLoadCF"] - ], - ] - ) -diff --git a/gyp/pylib/gyp/generator/msvs.py b/gyp/pylib/gyp/generator/msvs.py -index 72269bd..85c354f 100644 ---- a/gyp/pylib/gyp/generator/msvs.py -+++ b/gyp/pylib/gyp/generator/msvs.py -@@ -3006,6 +3006,10 @@ def _GetMSBuildConfigurationDetails(spec, build_file): - character_set = msbuild_attributes.get("CharacterSet") - config_type = msbuild_attributes.get("ConfigurationType") - _AddConditionalProperty(properties, condition, "ConfigurationType", config_type) -+ spectre_mitigation = msbuild_attributes.get('SpectreMitigation') -+ if spectre_mitigation: -+ _AddConditionalProperty(properties, condition, "SpectreMitigation", -+ spectre_mitigation) - if config_type == "Driver": - _AddConditionalProperty(properties, condition, "DriverType", "WDM") - _AddConditionalProperty( -@@ -3094,6 +3098,8 @@ def _ConvertMSVSBuildAttributes(spec, config, build_file): - msbuild_attributes[a] = _ConvertMSVSCharacterSet(msvs_attributes[a]) - elif a == "ConfigurationType": - msbuild_attributes[a] = _ConvertMSVSConfigurationType(msvs_attributes[a]) -+ elif a == "SpectreMitigation": -+ msbuild_attributes[a] = msvs_attributes[a] - else: - print("Warning: Do not know how to convert MSVS attribute " + a) - return msbuild_attributes From 74ef67b4abbdd8c55ba612f690b832e7ba4598e5 Mon Sep 17 00:00:00 2001 From: Robo Date: Tue, 30 Jan 2024 15:09:47 +0900 Subject: [PATCH 0717/1897] fix: update rpm release tag to el8 (#203770) --- resources/linux/rpm/code.spec.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/linux/rpm/code.spec.template b/resources/linux/rpm/code.spec.template index c0851097d9b..c9e57db9562 100644 --- a/resources/linux/rpm/code.spec.template +++ b/resources/linux/rpm/code.spec.template @@ -1,6 +1,6 @@ Name: @@NAME@@ Version: @@VERSION@@ -Release: @@RELEASE@@.el7 +Release: @@RELEASE@@.el8 Summary: Code editing. Redefined. Group: Development/Tools Vendor: Microsoft Corporation From 860f38ba5312a3f7cbecc10e6de92f9b3ffa2de3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 30 Jan 2024 09:24:06 +0100 Subject: [PATCH 0718/1897] aux window renames (#203775) --- .../browser/actions/layoutActions.ts | 6 ++--- src/vs/workbench/browser/contextkeys.ts | 10 ++++---- src/vs/workbench/browser/layout.ts | 24 +++++++++---------- src/vs/workbench/common/contextkeys.ts | 2 +- .../services/layout/browser/layoutService.ts | 4 ++-- .../test/browser/workbenchTestServices.ts | 2 +- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 08e0fb78357..3a741d1cdc7 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -22,7 +22,7 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { ToggleAuxiliaryBarAction } from 'vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions'; import { TogglePanelAction } from 'vs/workbench/browser/parts/panel/panelActions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { AuxiliaryBarVisibleContext, PanelAlignmentContext, PanelVisibleContext, SideBarVisibleContext, FocusedViewContext, InEditorZenModeContext, IsCenteredLayoutContext, MainEditorAreaVisibleContext, IsMainWindowFullscreenContext, PanelPositionContext, IsAuxiliaryWindowFocusedContext, TitleBarStyleContext } from 'vs/workbench/common/contextkeys'; +import { AuxiliaryBarVisibleContext, PanelAlignmentContext, PanelVisibleContext, SideBarVisibleContext, FocusedViewContext, InEditorZenModeContext, IsMainEditorCenteredLayoutContext, MainEditorAreaVisibleContext, IsMainWindowFullscreenContext, PanelPositionContext, IsAuxiliaryWindowFocusedContext, TitleBarStyleContext } from 'vs/workbench/common/contextkeys'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -88,7 +88,7 @@ registerAction2(class extends Action2 { precondition: IsAuxiliaryWindowFocusedContext.toNegated(), category: Categories.View, f1: true, - toggled: IsCenteredLayoutContext, + toggled: IsMainEditorCenteredLayoutContext, menu: [{ id: MenuId.MenubarAppearanceMenu, group: '1_toggle_view', @@ -1388,7 +1388,7 @@ const AlignPanelActions: CustomizeLayoutItem[] = [ const MiscLayoutOptions: CustomizeLayoutItem[] = [ CreateOptionLayoutItem('workbench.action.toggleFullScreen', IsMainWindowFullscreenContext, localize('fullscreen', "Full Screen"), fullscreenIcon), CreateOptionLayoutItem('workbench.action.toggleZenMode', InEditorZenModeContext, localize('zenMode', "Zen Mode"), zenModeIcon), - CreateOptionLayoutItem('workbench.action.toggleCenteredLayout', IsCenteredLayoutContext, localize('centeredLayout', "Centered Layout"), centerLayoutIcon), + CreateOptionLayoutItem('workbench.action.toggleCenteredLayout', IsMainEditorCenteredLayoutContext, localize('centeredLayout', "Centered Layout"), centerLayoutIcon), ]; const LayoutContextKeySet = new Set(); diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index c10da3b722c..a1663743d76 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from 'vs/platform/contextkey/common/contextkeys'; -import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, MainEditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, TitleBarVisibleContext, TitleBarStyleContext, MultipleEditorGroupsContext, IsAuxiliaryWindowFocusedContext, ActiveCompareEditorOriginalWriteableContext } from 'vs/workbench/common/contextkeys'; +import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsMainEditorCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, MainEditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, TitleBarVisibleContext, TitleBarStyleContext, MultipleEditorGroupsContext, IsAuxiliaryWindowFocusedContext, ActiveCompareEditorOriginalWriteableContext } from 'vs/workbench/common/contextkeys'; import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType, onDidRegisterWindow, getActiveWindow } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -71,7 +71,7 @@ export class WorkbenchContextKeysHandler extends Disposable { private inZenModeContext: IContextKey; private isMainWindowFullscreenContext: IContextKey; private isAuxiliaryWindowFocusedContext: IContextKey; - private isCenteredLayoutContext: IContextKey; + private isMainEditorCenteredLayoutContext: IContextKey; private sideBarVisibleContext: IContextKey; private mainEditorAreaVisibleContext: IContextKey; private panelPositionContext: IContextKey; @@ -195,8 +195,8 @@ export class WorkbenchContextKeysHandler extends Disposable { // Zen Mode this.inZenModeContext = InEditorZenModeContext.bindTo(this.contextKeyService); - // Centered Layout - this.isCenteredLayoutContext = IsCenteredLayoutContext.bindTo(this.contextKeyService); + // Centered Layout (Main Editor) + this.isMainEditorCenteredLayoutContext = IsMainEditorCenteredLayoutContext.bindTo(this.contextKeyService); // Editor Area this.mainEditorAreaVisibleContext = MainEditorAreaVisibleContext.bindTo(this.contextKeyService); @@ -266,7 +266,7 @@ export class WorkbenchContextKeysHandler extends Disposable { this.isMainWindowFullscreenContext.set(isFullscreen(mainWindow)); } })); - this._register(this.layoutService.onDidChangeCenteredLayout(centered => this.isCenteredLayoutContext.set(centered))); + this._register(this.layoutService.onDidChangeMainEditorCenteredLayout(centered => this.isMainEditorCenteredLayoutContext.set(centered))); this._register(this.layoutService.onDidChangePanelPosition(position => this.panelPositionContext.set(position))); this._register(this.layoutService.onDidChangePanelAlignment(alignment => this.panelAlignmentContext.set(alignment))); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index ccdbaf9d6bd..11fe93c2cf6 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -126,8 +126,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private readonly _onDidChangeZenMode = this._register(new Emitter()); readonly onDidChangeZenMode = this._onDidChangeZenMode.event; - private readonly _onDidChangeCenteredLayout = this._register(new Emitter()); - readonly onDidChangeCenteredLayout = this._onDidChangeCenteredLayout.event; + private readonly _onDidChangeMainEditorCenteredLayout = this._register(new Emitter()); + readonly onDidChangeMainEditorCenteredLayout = this._onDidChangeMainEditorCenteredLayout.event; private readonly _onDidChangePanelAlignment = this._register(new Emitter()); readonly onDidChangePanelAlignment = this._onDidChangePanelAlignment.event; @@ -338,7 +338,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._register(this.editorGroupService.mainPart.onDidActivateGroup(showEditorIfHidden)); // Revalidate center layout when active editor changes: diff editor quits centered mode. - this._register(this.mainPartEditorService.onDidActiveEditorChange(() => this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED)))); + this._register(this.mainPartEditorService.onDidActiveEditorChange(() => this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED)))); }); // Configuration changes @@ -371,9 +371,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._register(onDidChangeFullscreen(windowId => this.onFullscreenChanged(windowId))); // Group changes - this._register(this.editorGroupService.mainPart.onDidAddGroup(() => this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED)))); - this._register(this.editorGroupService.mainPart.onDidRemoveGroup(() => this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED)))); - this._register(this.editorGroupService.mainPart.onDidChangeGroupMaximized(() => this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED)))); + this._register(this.editorGroupService.mainPart.onDidAddGroup(() => this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED)))); + this._register(this.editorGroupService.mainPart.onDidRemoveGroup(() => this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED)))); + this._register(this.editorGroupService.mainPart.onDidChangeGroupMaximized(() => this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED)))); // Prevent workbench from scrolling #55456 this._register(addDisposableListener(this.mainContainer, EventType.SCROLL, () => this.mainContainer.scrollTop = 0)); @@ -511,7 +511,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Centered Layout this.editorGroupService.whenRestored.then(() => { - this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED), skipLayout); + this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED), skipLayout); }); } @@ -1069,7 +1069,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Restore Main Editor Center Mode - if (this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED)) { + if (this.stateModel.getRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED)) { this.centerMainEditorLayout(true, true); } @@ -1631,11 +1631,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } isMainEditorLayoutCentered(): boolean { - return this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED); + return this.stateModel.getRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED); } centerMainEditorLayout(active: boolean, skipLayout?: boolean): void { - this.stateModel.setRuntimeValue(LayoutStateKeys.EDITOR_CENTERED, active); + this.stateModel.setRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED, active); const activeMainEditor = this.mainPartEditorService.activeEditor; @@ -1662,7 +1662,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } - this._onDidChangeCenteredLayout.fire(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED)); + this._onDidChangeMainEditorCenteredLayout.fire(this.stateModel.getRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED)); } resizePart(part: Parts, sizeChangeWidth: number, sizeChangeHeight: number): void { @@ -2550,7 +2550,7 @@ class InitializationStateKey extends WorkbenchLayoutSt const LayoutStateKeys = { // Editor - EDITOR_CENTERED: new RuntimeStateKey('editor.centered', StorageScope.WORKSPACE, StorageTarget.MACHINE, false), + MAIN_EDITOR_CENTERED: new RuntimeStateKey('editor.centered', StorageScope.WORKSPACE, StorageTarget.MACHINE, false), // Zen Mode ZEN_MODE_ACTIVE: new RuntimeStateKey('zenMode.active', StorageScope.WORKSPACE, StorageTarget.MACHINE, false), diff --git a/src/vs/workbench/common/contextkeys.ts b/src/vs/workbench/common/contextkeys.ts index b778b1689df..a6464aeacd9 100644 --- a/src/vs/workbench/common/contextkeys.ts +++ b/src/vs/workbench/common/contextkeys.ts @@ -82,7 +82,7 @@ export const IsAuxiliaryEditorPartContext = new RawContextKey('isAuxili // Editor Layout Context Keys export const EditorsVisibleContext = new RawContextKey('editorIsOpen', false, localize('editorIsOpen', "Whether an editor is open")); export const InEditorZenModeContext = new RawContextKey('inZenMode', false, localize('inZenMode', "Whether Zen mode is enabled")); -export const IsCenteredLayoutContext = new RawContextKey('isCenteredLayout', false, localize('isCenteredLayout', "Whether centered layout is enabled")); +export const IsMainEditorCenteredLayoutContext = new RawContextKey('isCenteredLayout', false, localize('isMainEditorCenteredLayout', "Whether centered layout is enabled for the main editor")); export const SplitEditorsVertically = new RawContextKey('splitEditorsVertically', false, localize('splitEditorsVertically', "Whether editors split vertically")); export const MainEditorAreaVisibleContext = new RawContextKey('mainEditorAreaVisible', true, localize('mainEditorAreaVisible', "Whether the editor area in the main window is visible")); export const EditorTabsVisibleContext = new RawContextKey('editorTabsVisible', true, localize('editorTabsVisible', "Whether editor tabs are visible")); diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index c1091f8e7a6..fe70a7cf89a 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -130,9 +130,9 @@ export interface IWorkbenchLayoutService extends ILayoutService { readonly onDidChangeWindowMaximized: Event<{ readonly windowId: number; readonly maximized: boolean }>; /** - * Emits when centered layout is enabled or disabled. + * Emits when main editor centered layout is enabled or disabled. */ - readonly onDidChangeCenteredLayout: Event; + readonly onDidChangeMainEditorCenteredLayout: Event; /* * Emit when panel position changes. diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index dac55f97d95..f006962a209 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -605,7 +605,7 @@ export class TestLayoutService implements IWorkbenchLayoutService { activeContainer: HTMLElement = mainWindow.document.body; onDidChangeZenMode: Event = Event.None; - onDidChangeCenteredLayout: Event = Event.None; + onDidChangeMainEditorCenteredLayout: Event = Event.None; onDidChangeWindowMaximized: Event<{ windowId: number; maximized: boolean }> = Event.None; onDidChangePanelPosition: Event = Event.None; onDidChangePanelAlignment: Event = Event.None; From 1e2ee110ecfaca2cd029e6900aa901bcdb92a1e5 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 30 Jan 2024 09:35:14 +0100 Subject: [PATCH 0719/1897] changing the reason --- .../contrib/inlineCompletions/browser/inlineCompletionsModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 695a140bc40..8a027eefbb3 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -308,7 +308,7 @@ export class InlineCompletionsModel extends Disposable { } else { const edits = this._getEdits(editor, completion.toSingleTextEdit()); editor.executeEdits('inlineSuggestion.accept', [...edits.edits, ...completion.additionalTextEdits]); - editor.setSelections(edits.editorSelections, 'inlineCompletionPartialAccept'); + editor.setSelections(edits.editorSelections, 'inlineCompletionAccept'); } if (completion.command) { From 6215ccd0e79a6ea5569c984233c814328cf31323 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 30 Jan 2024 12:09:34 +0100 Subject: [PATCH 0720/1897] send telemetry about accepted/discarded hunks and about response types (#203780) --- .../inlineChat/browser/inlineChatSession.ts | 39 ++++++++++++++----- .../browser/inlineChatSessionServiceImpl.ts | 21 +++++----- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 94f64a77e18..693fde05bab 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -39,12 +39,15 @@ export type TelemetryData = { extension: string; rounds: string; undos: string; - edits: boolean; unstashed: number; + edits: number; finishedByEdit: boolean; startTime: string; endTime: string; editMode: string; + acceptedHunks: number; + discardedHunks: number; + responseTypes: string; }; export type TelemetryDataClassification = { @@ -59,6 +62,9 @@ export type TelemetryDataClassification = { startTime: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'When the session started' }; endTime: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'When the session ended' }; editMode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'What edit mode was choosen: live, livePreview, preview' }; + acceptedHunks: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of accepted hunks' }; + discardedHunks: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of discarded hunks' }; + responseTypes: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Comma separated list of response types like edits, message, mixed' }; }; export enum ExpansionState { @@ -140,7 +146,7 @@ export class Session { private _isUnstashed: boolean = false; private readonly _exchange: SessionExchange[] = []; private readonly _startTime = new Date(); - private readonly _teldata: Partial; + private readonly _teldata: TelemetryData; readonly textModelNAltVersion: number; private _textModelNSnapshotAltVersion: number | undefined; @@ -168,12 +174,16 @@ export class Session { this._teldata = { extension: provider.debugName, startTime: this._startTime.toISOString(), - edits: false, + endTime: this._startTime.toISOString(), + edits: 0, finishedByEdit: false, rounds: '', undos: '', editMode, - unstashed: 0 + unstashed: 0, + acceptedHunks: 0, + discardedHunks: 0, + responseTypes: '' }; } @@ -214,6 +224,7 @@ export class Session { this._isUnstashed = false; const newLen = this._exchange.push(exchange); this._teldata.rounds += `${newLen}|`; + this._teldata.responseTypes += `${exchange.response instanceof ReplyResponse ? exchange.response.responseType : InlineChatResponseTypes.Empty}|`; } get exchanges(): Iterable { @@ -244,15 +255,25 @@ export class Session { } recordExternalEditOccurred(didFinish: boolean) { - this._teldata.edits = true; + this._teldata.edits += 1; this._teldata.finishedByEdit = didFinish; } asTelemetryData(): TelemetryData { - return { - ...this._teldata, - endTime: new Date().toISOString(), - }; + + for (const item of this.hunkData.getInfo()) { + switch (item.getState()) { + case HunkState.Accepted: + this._teldata.acceptedHunks += 1; + break; + case HunkState.Rejected: + this._teldata.discardedHunks += 1; + break; + } + } + + this._teldata.endTime = new Date().toISOString(); + return this._teldata; } asRecording(): Recording { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index e711ffb0fbe..6d696545068 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -192,20 +192,18 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { releaseSession(session: Session): void { - let data: SessionData | undefined; + let tuple: [string, SessionData] | undefined; // cleanup - for (const [key, value] of this._sessions) { - if (value.session === session) { - data = value; - value.store.dispose(); - this._sessions.delete(key); - this._logService.trace(`[IE] did RELEASED session for ${value.editor.getId()}, ${session.provider.debugName}`); + for (const candidate of this._sessions) { + if (candidate[1].session === session) { + // if (value.session === session) { + tuple = candidate; break; } } - if (!data) { + if (!tuple) { // double remove return; } @@ -213,7 +211,12 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { this._keepRecording(session); this._telemetryService.publicLog2('interactiveEditor/session', session.asTelemetryData()); - this._onDidEndSession.fire({ editor: data.editor, session }); + const [key, value] = tuple; + value.store.dispose(); + this._sessions.delete(key); + this._logService.trace(`[IE] did RELEASED session for ${value.editor.getId()}, ${session.provider.debugName}`); + + this._onDidEndSession.fire({ editor: value.editor, session }); } stashSession(session: Session, editor: ICodeEditor, undoCancelEdits: IValidEditOperation[]): StashedSession { From eee4b5fc50ba85a5f488496d5a600e158cdc462e Mon Sep 17 00:00:00 2001 From: RedCMD <33529441+RedCMD@users.noreply.github.com> Date: Wed, 31 Jan 2024 00:10:23 +1300 Subject: [PATCH 0721/1897] Contribute to json language server with a custom language. (#198583) * Contribute to json language server with a custom language. * Add `snippets` to `"activationEvents"` * Remove hardcoded `snippets` from `documentSettings` * Fix wrong variable in `!isEqualSet()` * Use `extensions.allAcrossExtensionHosts` instead of `extensions.all` * enable `"enabledApiProposals"` for `extensions.allAcrossExtensionHosts` * Fix error: `Property 'allAcrossExtensionHosts' does not exist on type 'typeof extensions'` * Remove `snippets` --- .../client/src/browser/jsonClientMain.ts | 19 ++-- .../client/src/jsonClient.ts | 59 ++++++++++-- .../client/src/languageParticipants.ts | 89 +++++++++++++++++++ .../client/src/languageStatus.ts | 5 +- .../client/src/node/jsonClientMain.ts | 19 ++-- .../client/tsconfig.json | 1 + .../json-language-features/package.json | 4 +- 7 files changed, 176 insertions(+), 20 deletions(-) create mode 100644 extensions/json-language-features/client/src/languageParticipants.ts diff --git a/extensions/json-language-features/client/src/browser/jsonClientMain.ts b/extensions/json-language-features/client/src/browser/jsonClientMain.ts index fb0da9c44fa..f7c87fbf9fa 100644 --- a/extensions/json-language-features/client/src/browser/jsonClientMain.ts +++ b/extensions/json-language-features/client/src/browser/jsonClientMain.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtensionContext, Uri, l10n } from 'vscode'; -import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient'; -import { startClient, LanguageClientConstructor, SchemaRequestService } from '../jsonClient'; +import { Disposable, ExtensionContext, Uri, l10n } from 'vscode'; +import { LanguageClientOptions } from 'vscode-languageclient'; +import { startClient, LanguageClientConstructor, SchemaRequestService, AsyncDisposable } from '../jsonClient'; import { LanguageClient } from 'vscode-languageclient/browser'; declare const Worker: { @@ -14,7 +14,7 @@ declare const Worker: { declare function fetch(uri: string, options: any): any; -let client: BaseLanguageClient | undefined; +let client: AsyncDisposable | undefined; // this method is called when vs code is activated export async function activate(context: ExtensionContext) { @@ -36,7 +36,14 @@ export async function activate(context: ExtensionContext) { } }; - client = await startClient(context, newLanguageClient, { schemaRequests }); + const timer = { + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable { + const handle = setTimeout(callback, ms, ...args); + return { dispose: () => clearTimeout(handle) }; + } + }; + + client = await startClient(context, newLanguageClient, { schemaRequests, timer }); } catch (e) { console.log(e); @@ -45,7 +52,7 @@ export async function activate(context: ExtensionContext) { export async function deactivate(): Promise { if (client) { - await client.stop(); + await client.dispose(); client = undefined; } } diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index 3f191f165cf..ce81dcb4c9e 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -6,7 +6,7 @@ export type JSONLanguageStatus = { schemas: string[] }; import { - workspace, window, languages, commands, ExtensionContext, extensions, Uri, ColorInformation, + workspace, window, languages, commands, OutputChannel, ExtensionContext, extensions, Uri, ColorInformation, Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken, FoldingRange, ProviderResult, TextEdit, Range, Position, Disposable, CompletionItem, CompletionList, CompletionContext, Hover, MarkdownString, FoldingContext, DocumentSymbol, SymbolInformation, l10n } from 'vscode'; @@ -19,6 +19,7 @@ import { import { hash } from './utils/hash'; import { createDocumentSymbolsLimitItem, createLanguageStatusItem, createLimitStatusItem } from './languageStatus'; +import { getLanguageParticipants, LanguageParticipants } from './languageParticipants'; namespace VSCodeContentRequest { export const type: RequestType = new RequestType('vscode/content'); @@ -126,6 +127,9 @@ export type LanguageClientConstructor = (name: string, description: string, clie export interface Runtime { schemaRequests: SchemaRequestService; telemetry?: TelemetryReporter; + readonly timer: { + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable; + }; } export interface SchemaRequestService { @@ -141,13 +145,51 @@ let jsoncFoldingLimit = 5000; let jsonColorDecoratorLimit = 5000; let jsoncColorDecoratorLimit = 5000; -export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { +export interface AsyncDisposable { + dispose(): Promise; +} - const toDispose = context.subscriptions; +export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { + const outputChannel = window.createOutputChannel(languageServerDescription); + + const languageParticipants = getLanguageParticipants(); + context.subscriptions.push(languageParticipants); + + let client: Disposable | undefined = await startClientWithParticipants(context, languageParticipants, newLanguageClient, outputChannel, runtime); + + let restartTrigger: Disposable | undefined; + languageParticipants.onDidChange(() => { + if (restartTrigger) { + restartTrigger.dispose(); + } + restartTrigger = runtime.timer.setTimeout(async () => { + if (client) { + outputChannel.appendLine('Extensions have changed, restarting JSON server...'); + outputChannel.appendLine(''); + const oldClient = client; + client = undefined; + await oldClient.dispose(); + client = await startClientWithParticipants(context, languageParticipants, newLanguageClient, outputChannel, runtime); + } + }, 2000); + }); + + return { + dispose: async () => { + restartTrigger?.dispose(); + await client?.dispose(); + outputChannel.dispose(); + } + }; +} + +async function startClientWithParticipants(context: ExtensionContext, languageParticipants: LanguageParticipants, newLanguageClient: LanguageClientConstructor, outputChannel: OutputChannel, runtime: Runtime): Promise { + + const toDispose: Disposable[] = []; let rangeFormatting: Disposable | undefined = undefined; - const documentSelector = ['json', 'jsonc']; + const documentSelector = languageParticipants.documentSelector; const schemaResolutionErrorStatusBarItem = window.createStatusBarItem('status.json.resolveError', StatusBarAlignment.Right, 0); schemaResolutionErrorStatusBarItem.name = l10n.t('JSON: Schema Resolution Error'); @@ -306,6 +348,7 @@ export async function startClient(context: ExtensionContext, newLanguageClient: } }; + clientOptions.outputChannel = outputChannel; // Create the language client and start the client. const client = newLanguageClient('json', languageServerDescription, clientOptions); client.registerProposedFeatures(); @@ -490,7 +533,13 @@ export async function startClient(context: ExtensionContext, newLanguageClient: }); } - return client; + return { + dispose: async () => { + await client.stop(); + toDispose.forEach(d => d.dispose()); + rangeFormatting?.dispose(); + } + }; } function getSchemaAssociations(_context: ExtensionContext): ISchemaAssociation[] { diff --git a/extensions/json-language-features/client/src/languageParticipants.ts b/extensions/json-language-features/client/src/languageParticipants.ts new file mode 100644 index 00000000000..6bd1a086e0a --- /dev/null +++ b/extensions/json-language-features/client/src/languageParticipants.ts @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DocumentSelector } from 'vscode-languageclient'; +import { Event, EventEmitter, extensions } from 'vscode'; + +/** + * JSON language participant contribution. + */ +interface LanguageParticipantContribution { + /** + * The id of the language which participates with the JSON language server. + */ + languageId: string; + /** + * true if the language allows comments and false otherwise. + * TODO: implement server side setting + */ + comments?: boolean; +} + +export interface LanguageParticipants { + readonly onDidChange: Event; + readonly documentSelector: DocumentSelector; + hasLanguage(languageId: string): boolean; + useComments(languageId: string): boolean; + dispose(): void; +} + +export function getLanguageParticipants(): LanguageParticipants { + const onDidChangeEmmiter = new EventEmitter(); + let languages = new Set(); + let comments = new Set(); + + function update() { + const oldLanguages = languages, oldComments = comments; + + languages = new Set(); + languages.add('json'); + languages.add('jsonc'); + comments = new Set(); + comments.add('jsonc'); + + for (const extension of extensions.allAcrossExtensionHosts) { + const jsonLanguageParticipants = extension.packageJSON?.contributes?.jsonLanguageParticipants as LanguageParticipantContribution[]; + if (Array.isArray(jsonLanguageParticipants)) { + for (const jsonLanguageParticipant of jsonLanguageParticipants) { + const languageId = jsonLanguageParticipant.languageId; + if (typeof languageId === 'string') { + languages.add(languageId); + if (jsonLanguageParticipant.comments === true) { + comments.add(languageId); + } + } + } + } + } + return !isEqualSet(languages, oldLanguages) || !isEqualSet(comments, oldComments); + } + update(); + + const changeListener = extensions.onDidChange(_ => { + if (update()) { + onDidChangeEmmiter.fire(); + } + }); + + return { + onDidChange: onDidChangeEmmiter.event, + get documentSelector() { return Array.from(languages); }, + hasLanguage(languageId: string) { return languages.has(languageId); }, + useComments(languageId: string) { return comments.has(languageId); }, + dispose: () => changeListener.dispose() + }; +} + +function isEqualSet(s1: Set, s2: Set) { + if (s1.size !== s2.size) { + return false; + } + for (const e of s1) { + if (!s2.has(e)) { + return false; + } + } + return true; +} diff --git a/extensions/json-language-features/client/src/languageStatus.ts b/extensions/json-language-features/client/src/languageStatus.ts index f2c8b923c30..4aead6f99f2 100644 --- a/extensions/json-language-features/client/src/languageStatus.ts +++ b/extensions/json-language-features/client/src/languageStatus.ts @@ -9,6 +9,7 @@ import { ThemeIcon, TextDocument, LanguageStatusSeverity, l10n } from 'vscode'; import { JSONLanguageStatus, JSONSchemaSettings } from './jsonClient'; +import { DocumentSelector } from 'vscode-languageclient'; type ShowSchemasInput = { schemas: string[]; @@ -163,7 +164,7 @@ function showSchemaList(input: ShowSchemasInput) { }); } -export function createLanguageStatusItem(documentSelector: string[], statusRequest: (uri: string) => Promise): Disposable { +export function createLanguageStatusItem(documentSelector: DocumentSelector, statusRequest: (uri: string) => Promise): Disposable { const statusItem = languages.createLanguageStatusItem('json.projectStatus', documentSelector); statusItem.name = l10n.t('JSON Validation Status'); statusItem.severity = LanguageStatusSeverity.Information; @@ -268,7 +269,7 @@ export function createLimitStatusItem(newItem: (limit: number) => Disposable) { const openSettingsCommand = 'workbench.action.openSettings'; const configureSettingsLabel = l10n.t('Configure'); -export function createDocumentSymbolsLimitItem(documentSelector: string[], settingId: string, limit: number): Disposable { +export function createDocumentSymbolsLimitItem(documentSelector: DocumentSelector, settingId: string, limit: number): Disposable { const statusItem = languages.createLanguageStatusItem('json.documentSymbolsStatus', documentSelector); statusItem.name = l10n.t('JSON Outline Status'); statusItem.severity = LanguageStatusSeverity.Warning; diff --git a/extensions/json-language-features/client/src/node/jsonClientMain.ts b/extensions/json-language-features/client/src/node/jsonClientMain.ts index 457a40f6a74..79d66e32dda 100644 --- a/extensions/json-language-features/client/src/node/jsonClientMain.ts +++ b/extensions/json-language-features/client/src/node/jsonClientMain.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtensionContext, OutputChannel, window, workspace, l10n, env } from 'vscode'; -import { startClient, LanguageClientConstructor, SchemaRequestService, languageServerDescription } from '../jsonClient'; -import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient, BaseLanguageClient } from 'vscode-languageclient/node'; +import { Disposable, ExtensionContext, OutputChannel, window, workspace, l10n, env } from 'vscode'; +import { startClient, LanguageClientConstructor, SchemaRequestService, languageServerDescription, AsyncDisposable } from '../jsonClient'; +import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node'; import { promises as fs } from 'fs'; import * as path from 'path'; @@ -15,7 +15,7 @@ import TelemetryReporter from '@vscode/extension-telemetry'; import { JSONSchemaCache } from './schemaCache'; let telemetry: TelemetryReporter | undefined; -let client: BaseLanguageClient | undefined; +let client: AsyncDisposable | undefined; // this method is called when vs code is activated export async function activate(context: ExtensionContext) { @@ -44,17 +44,24 @@ export async function activate(context: ExtensionContext) { const log = getLog(outputChannel); context.subscriptions.push(log); + const timer = { + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable { + const handle = setTimeout(callback, ms, ...args); + return { dispose: () => clearTimeout(handle) }; + } + }; + // pass the location of the localization bundle to the server process.env['VSCODE_L10N_BUNDLE_LOCATION'] = l10n.uri?.toString() ?? ''; const schemaRequests = await getSchemaRequestService(context, log); - client = await startClient(context, newLanguageClient, { schemaRequests, telemetry }); + client = await startClient(context, newLanguageClient, { schemaRequests, telemetry, timer }); } export async function deactivate(): Promise { if (client) { - await client.stop(); + await client.dispose(); client = undefined; } telemetry?.dispose(); diff --git a/extensions/json-language-features/client/tsconfig.json b/extensions/json-language-features/client/tsconfig.json index 4254a37490e..aa51e4d0157 100644 --- a/extensions/json-language-features/client/tsconfig.json +++ b/extensions/json-language-features/client/tsconfig.json @@ -7,5 +7,6 @@ "src/**/*", "../../../src/vscode-dts/vscode.d.ts", "../../../src/vscode-dts/vscode.proposed.languageStatus.d.ts", + "../../../src/vscode-dts/vscode.proposed.extensionsAny.d.ts" ] } diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 46a7603818a..33d2bbb09bd 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -9,7 +9,9 @@ "engines": { "vscode": "^1.77.0" }, - "enabledApiProposals": [], + "enabledApiProposals": [ + "extensionsAny" + ], "icon": "icons/json.png", "activationEvents": [ "onLanguage:json", From a78b270441fef6e0c9151e854fc95eb9a0ead3df Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Tue, 30 Jan 2024 12:49:32 +0100 Subject: [PATCH 0722/1897] :lipstick: --- .../lib/stylelint/vscode-known-variables.json | 1 + .../editor/media/multieditortabscontrol.css | 13 +-- .../parts/editor/multiEditorTabsControl.ts | 98 ++++++++----------- src/vs/workbench/common/theme.ts | 10 +- 4 files changed, 53 insertions(+), 69 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 7f5b6fa2892..e007c9a6a62 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -627,6 +627,7 @@ "--vscode-tab-activeForeground", "--vscode-tab-activeModifiedBorder", "--vscode-tab-border", + "--vscode-tab-dragAndDropBorder", "--vscode-tab-hoverBackground", "--vscode-tab-hoverBorder", "--vscode-tab-hoverForeground", diff --git a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css index 2f244d4136c..8a883475400 100644 --- a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css @@ -28,7 +28,7 @@ [z-index] [kind] 12 drag and drop overlay - 11 scrollbar + 11 scrollbar / tabs dnd border 10 active-tab border-bottom 9 tabs, title border bottom 8 sticky-tab @@ -461,14 +461,16 @@ /* Drag and drop target */ -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.drop-target-left::after , +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.drop-target-left::after, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.drop-target-right::before { content: ""; position: absolute; top: 0; height: 100%; width: 1px; + background-color: var(--vscode-tab-dragAndDropBorder); pointer-events: none; + z-index: 11; } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.drop-target-right::before { @@ -482,12 +484,7 @@ /* Make drop target edge cases more visible (wrapped tabs & first/last) */ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.last-in-row.drop-target-left:not(:last-child)::after { - right: 1px; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.drop-target-right:first-child:before, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.last-in-row + .tab.drop-target-right::before { - left: 1px; + right: 0px; } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.last-in-row.drop-target-left::after, diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 559248e45f8..23906f57065 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -26,7 +26,7 @@ import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElemen import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { getOrSet } from 'vs/base/common/map'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_BACKGROUND, TAB_HOVER_FOREGROUND, TAB_UNFOCUSED_HOVER_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BORDER, TAB_LAST_PINNED_BORDER, TAB_DRAG_AND_DROP_BETWEEEN_INDICATOR } from 'vs/workbench/common/theme'; +import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_BACKGROUND, TAB_HOVER_FOREGROUND, TAB_UNFOCUSED_HOVER_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BORDER, TAB_LAST_PINNED_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, extractTreeDropData, isWindowDraggedOver } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; @@ -131,30 +131,6 @@ export class MultiEditorTabsControl extends EditorTabsControl { private lastMouseWheelEventTime = 0; private isMouseOverTabs = false; - - private _dndDropTarget: { leftElement: HTMLElement | undefined; rightElement: HTMLElement | undefined } | undefined; - private set dndDropTarget(target: { leftElement: HTMLElement | undefined; rightElement: HTMLElement | undefined } | undefined) { - const oldTargets = this._dndDropTarget; - if (oldTargets === target || oldTargets && target && oldTargets.leftElement === target.leftElement && oldTargets.rightElement === target.rightElement) { - return; - } - - const dropClassLeft = 'drop-target-left'; - const dropClassRight = 'drop-target-right'; - - if (oldTargets) { - oldTargets.leftElement?.classList.remove(dropClassLeft); - oldTargets.rightElement?.classList.remove(dropClassRight); - } - - if (target) { - target.leftElement?.classList.add(dropClassLeft); - target.rightElement?.classList.add(dropClassRight); - } - - this._dndDropTarget = target; - } - constructor( parent: HTMLElement, editorPartsView: IEditorPartsView, @@ -370,15 +346,9 @@ export class MultiEditorTabsControl extends EditorTabsControl { return; } - // Return if dragged editor is last tab because then this is a no-op - let isLocalDragAndDrop = false; - if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { - isLocalDragAndDrop = true; - } - // Update the dropEffect to "copy" if there is no local data to be dragged because // in that case we can only copy the data into and not move it from its source - if (!isLocalDragAndDrop) { + if (!this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { if (e.dataTransfer) { e.dataTransfer.dropEffect = 'copy'; } @@ -1055,7 +1025,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { this.doFillResourceDataTransfers([editor], e, isNewWindowOperation); // Fixes https://github.com/microsoft/vscode/issues/18733 - this.updateDropFeedback(tab.cloneNode(true) as HTMLElement, true, e, tabIndex); + this.updateDropFeedback(tab, true, e, tabIndex); scheduleAtNextAnimationFrame(getWindow(this.parent), () => this.updateDropFeedback(tab, false, e, tabIndex)); }, @@ -1136,10 +1106,13 @@ export class MultiEditorTabsControl extends EditorTabsControl { // compute the target index let targetIndex = tabIndex; - if (!this.isHeadOfTab(e, tab)) { + if (this.getTabDragOverLocation(e, tab) === 'right') { targetIndex++; } + // If we are moving an editor inside the same group and it is located before the target index + // wee need to reduce the index by one to account for the fact that the move will cause all subsequent + // tabs to move one to the left. const editorIdentifiers = this.editorTransfer.getData(DraggedEditorIdentifier.prototype); if (editorIdentifiers !== undefined) { @@ -1188,15 +1161,17 @@ export class MultiEditorTabsControl extends EditorTabsControl { private updateDropFeedback(element: HTMLElement, isDND: boolean, e: DragEvent, tabIndex?: number): void { const isTab = (typeof tabIndex === 'number'); + let dropTarget; if (isDND) { if (isTab) { - this.dndDropTarget = this.computeDropTarget(e, tabIndex, element); + dropTarget = this.computeDropTarget(e, tabIndex, element); } else { - this.dndDropTarget = { leftElement: element.lastElementChild as HTMLElement, rightElement: undefined }; + dropTarget = { leftElement: element.lastElementChild as HTMLElement, rightElement: undefined }; } } else { - this.dndDropTarget = undefined; + dropTarget = undefined; } + this.updateDropTarget(dropTarget); // Outline const activeContrastBorderColor = this.getColor(activeContrastBorder); @@ -1213,30 +1188,53 @@ export class MultiEditorTabsControl extends EditorTabsControl { } } - private isHeadOfTab(e: DragEvent, tab: HTMLElement): boolean { + private dropTarget: { leftElement: HTMLElement | undefined; rightElement: HTMLElement | undefined } | undefined; + private updateDropTarget(target: { leftElement: HTMLElement | undefined; rightElement: HTMLElement | undefined } | undefined): void { + const oldTargets = this.dropTarget; + if (oldTargets === target || oldTargets && target && oldTargets.leftElement === target.leftElement && oldTargets.rightElement === target.rightElement) { + return; + } + + const dropClassLeft = 'drop-target-left'; + const dropClassRight = 'drop-target-right'; + + if (oldTargets) { + oldTargets.leftElement?.classList.remove(dropClassLeft); + oldTargets.rightElement?.classList.remove(dropClassRight); + } + + if (target) { + target.leftElement?.classList.add(dropClassLeft); + target.rightElement?.classList.add(dropClassRight); + } + + this.dropTarget = target; + } + + private getTabDragOverLocation(e: DragEvent, tab: HTMLElement): 'left' | 'right' { const rect = tab.getBoundingClientRect(); const offsetXRelativeToParent = e.clientX - rect.left; - return offsetXRelativeToParent <= rect.width / 2; + return offsetXRelativeToParent <= rect.width / 2 ? 'left' : 'right'; } private computeDropTarget(e: DragEvent, tabIndex: number, targetTab: HTMLElement): { leftElement: HTMLElement | undefined; rightElement: HTMLElement | undefined } | undefined { - const isHeadOfTab = this.isHeadOfTab(e, targetTab); + const isLeftSideOfTab = this.getTabDragOverLocation(e, targetTab) === 'left'; const isLastTab = tabIndex === this.tabsModel.count - 1; const isFirstTab = tabIndex === 0; // Before first tab - if (isHeadOfTab && isFirstTab) { + if (isLeftSideOfTab && isFirstTab) { return { leftElement: undefined, rightElement: targetTab }; } // After last tab - if (!isHeadOfTab && isLastTab) { + if (!isLeftSideOfTab && isLastTab) { return { leftElement: targetTab, rightElement: undefined }; } // Between two tabs - const tabBefore = isHeadOfTab ? targetTab.previousElementSibling : targetTab; - const tabAfter = isHeadOfTab ? targetTab : targetTab.nextElementSibling; + const tabBefore = isLeftSideOfTab ? targetTab.previousElementSibling : targetTab; + const tabAfter = isLeftSideOfTab ? targetTab : targetTab.nextElementSibling; return { leftElement: tabBefore as HTMLElement, rightElement: tabAfter as HTMLElement }; } @@ -2410,16 +2408,4 @@ registerThemingParticipant((theme, collector) => { collector.addRule(makeTabBackgroundRule(adjustedColor, adjustedColorDrag, false, false)); } } - - const tabDndIndicatorColor = theme.getColor(TAB_DRAG_AND_DROP_BETWEEEN_INDICATOR); - if (tabDndIndicatorColor) { - // DnD Feedback - - collector.addRule(` - .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.drop-target-left::after, - .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.drop-target-right::before { - background-color: ${tabDndIndicatorColor}; - } - `); - } }); diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 58dc1f8e569..febf755414e 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -182,14 +182,14 @@ export const TAB_UNFOCUSED_HOVER_BORDER = registerColor('tab.unfocusedHoverBorde //#endregion -//#region Tab Drag and Drop Indicator +//#region Tab Drag and Drop Border -export const TAB_DRAG_AND_DROP_BETWEEEN_INDICATOR = registerColor('tab.dragAndDropBetweenIndicator', { - dark: TAB_INACTIVE_FOREGROUND, - light: TAB_INACTIVE_FOREGROUND, +export const TAB_DRAG_AND_DROP_BORDER = registerColor('tab.dragAndDropBorder', { + dark: TAB_ACTIVE_FOREGROUND, + light: TAB_ACTIVE_FOREGROUND, hcDark: activeContrastBorder, hcLight: activeContrastBorder -}, localize('tabDragAndDropBetweenIndicator', "Indicator between tabs to indicate that a tab can be dropped between two tabs. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabDragAndDropBorder', "Border between tabs to indicate that a tab can be inserted between two tabs. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); //#endregion From c481f95364dfae1bfbdd342478a8e4359515f72f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 30 Jan 2024 12:50:47 +0100 Subject: [PATCH 0723/1897] Fix extension installation from location - check for valid manifest (#203785) --- .../node/extensionManagementService.ts | 2 +- .../browser/extensions.contribution.ts | 42 +++++++++++-------- .../common/webExtensionManagementService.ts | 4 +- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 905dec7cd90..217d7720d15 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -169,7 +169,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi async installFromLocation(location: URI, profileLocation: URI): Promise { this.logService.trace('ExtensionManagementService#installFromLocation', location.toString()); const local = await this.extensionsScanner.scanUserExtensionAtLocation(location); - if (!local) { + if (!local || !local.manifest.name || !local.manifest.version) { throw new Error(`Cannot find a valid extension from the location ${location.toString()}`); } await this.addExtensionsToProfile([[local, undefined]], profileLocation); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 98e056c9005..ba14cde4134 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -866,22 +866,30 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi run: async (accessor: ServicesAccessor) => { const extensionManagementService = accessor.get(IWorkbenchExtensionManagementService); if (isWeb) { - const quickInputService = accessor.get(IQuickInputService); - const disposables = new DisposableStore(); - const quickPick = disposables.add(quickInputService.createQuickPick()); - quickPick.title = localize('installFromLocation', "Install Extension from Location"); - quickPick.customButton = true; - quickPick.customLabel = localize('install button', "Install"); - quickPick.placeholder = localize('installFromLocationPlaceHolder', "Location of the web extension"); - quickPick.ignoreFocusOut = true; - disposables.add(Event.any(quickPick.onDidAccept, quickPick.onDidCustom)(() => { - quickPick.hide(); - if (quickPick.value) { - extensionManagementService.installFromLocation(URI.parse(quickPick.value)); - } - })); - disposables.add(quickPick.onDidHide(() => disposables.dispose())); - quickPick.show(); + return new Promise((c, e) => { + const quickInputService = accessor.get(IQuickInputService); + const disposables = new DisposableStore(); + const quickPick = disposables.add(quickInputService.createQuickPick()); + quickPick.title = localize('installFromLocation', "Install Extension from Location"); + quickPick.customButton = true; + quickPick.customLabel = localize('install button', "Install"); + quickPick.placeholder = localize('installFromLocationPlaceHolder', "Location of the web extension"); + quickPick.ignoreFocusOut = true; + disposables.add(Event.any(quickPick.onDidAccept, quickPick.onDidCustom)(async () => { + quickPick.hide(); + if (quickPick.value) { + try { + await extensionManagementService.installFromLocation(URI.parse(quickPick.value)); + } catch (error) { + e(error); + return; + } + } + c(); + })); + disposables.add(quickPick.onDidHide(() => disposables.dispose())); + quickPick.show(); + }); } else { const fileDialogService = accessor.get(IFileDialogService); const extensionLocation = await fileDialogService.showOpenDialog({ @@ -891,7 +899,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi title: localize('installFromLocation', "Install Extension from Location"), }); if (extensionLocation?.[0]) { - extensionManagementService.installFromLocation(extensionLocation[0]); + await extensionManagementService.installFromLocation(extensionLocation[0]); } } } diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index 074f0d70d66..e58051fda3f 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -102,8 +102,8 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe async install(location: URI, options: InstallOptions = {}): Promise { this.logService.trace('ExtensionManagementService#install', location.toString()); const manifest = await this.webExtensionsScannerService.scanExtensionManifest(location); - if (!manifest) { - throw new Error(`Cannot find packageJSON from the location ${location.toString()}`); + if (!manifest || !manifest.name || !manifest.version) { + throw new Error(`Cannot find a valid extension from the location ${location.toString()}`); } const result = await this.installExtensions([{ manifest, extension: location, options }]); if (result[0]?.local) { From e2ce367fc8b433680b7abe7a649855578fc5344f Mon Sep 17 00:00:00 2001 From: Robo Date: Tue, 30 Jan 2024 22:22:03 +0900 Subject: [PATCH 0724/1897] fix: update server requirements check for alpine distro (#203790) --- .../bin/helpers/check-requirements-linux.sh | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/resources/server/bin/helpers/check-requirements-linux.sh b/resources/server/bin/helpers/check-requirements-linux.sh index af77f0b9d82..b2ad871fa11 100644 --- a/resources/server/bin/helpers/check-requirements-linux.sh +++ b/resources/server/bin/helpers/check-requirements-linux.sh @@ -5,13 +5,27 @@ set -e +# Do not remove this check. +# Provides a way to skip the server requirements check from +# outside the install flow. A system process can create this +# file before the server is downloaded and installed. +# +# This check is duplicated between code-server-linux.sh and here +# since remote container calls into this script directly quite early +# before the usual server startup flow. +if [ -f "/tmp/vscode-skip-server-requirements-check" ]; then + echo "!!! WARNING: Skipping server pre-requisite check !!!" + echo "!!! Server stability is not guaranteed. Proceed at your own risk. !!!" + exit 0 +fi + BITNESS=$(getconf LONG_BIT) ARCH=$(uname -m) found_required_glibc=0 found_required_glibcxx=0 # Extract the ID value from /etc/os-release -OS_ID="$(cat /etc/os-release | grep -Eo 'ID=([^"]+)' | sed 's/ID=//')" +OS_ID="$(cat /etc/os-release | grep -Eo 'ID=([^"]+)' | sed -n '1s/ID=//p')" if [ "$OS_ID" = "nixos" ]; then echo "Warning: NixOS detected, skipping GLIBC check" exit 0 @@ -64,7 +78,7 @@ if [ -n "$libstdcpp_path" ]; then fi fi -if [ -n "$(ldd --version | grep -v musl)" ]; then +if [ -z "$(ldd --version 2>&1 | grep 'musl libc')" ]; then if [ -f /usr/lib64/libc.so.6 ]; then # Typical path libc_path='/usr/lib64/libc.so.6' @@ -96,7 +110,16 @@ if [ -n "$(ldd --version | grep -v musl)" ]; then fi fi else - echo "Warning: musl detected, skipping GLIBC check" + if [ "$OS_ID" = "alpine" ]; then + musl_version=$(ldd --version 2>&1 | grep "Version" | awk '{print $NF}') + if [ "$(printf '%s\n' "1.2.3" "$musl_version" | sort -V | head -n1)" != "1.2.3" ]; then + echo "Error: Unsupported alpine distribution. Please refer to our supported distro section https://aka.ms/vscode-remote/linux for additional information." + exit 99 + fi + else + echo "Warning: musl detected, skipping GLIBC check" + fi + found_required_glibc=1 fi From e9823d792881ef49aead9c172cb187bf73e5b02a Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 30 Jan 2024 14:41:41 +0100 Subject: [PATCH 0725/1897] adding code for discussion --- .../browser/inlineCompletionsModel.ts | 63 +++++++++++++++++-- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 8a027eefbb3..b5545cca147 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -7,13 +7,13 @@ import { mapFindFirst } from 'vs/base/common/arraysFind'; import { BugIndicatingError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; import { IObservable, IReader, ITransaction, autorun, derived, derivedHandleChanges, derivedOpts, recomputeInitiallyAndOnChange, observableSignal, observableValue, subtransaction, transaction } from 'vs/base/common/observable'; -import { commonPrefixLength } from 'vs/base/common/strings'; +import { commonPrefixLength, splitLines } from 'vs/base/common/strings'; import { isDefined } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { ISelection, Selection } from 'vs/editor/common/core/selection'; +import { Selection } from 'vs/editor/common/core/selection'; import { InlineCompletionContext, InlineCompletionTriggerKind } from 'vs/editor/common/languages'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { EndOfLinePreference, IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; @@ -435,7 +435,7 @@ export class InlineCompletionsModel extends Disposable { } } - private _getEdits(editor: ICodeEditor, completion: SingleTextEdit): { edits: IIdentifiedSingleEditOperation[]; editorSelections: ISelection[] } { + private _getEdits(editor: ICodeEditor, completion: SingleTextEdit): { edits: IIdentifiedSingleEditOperation[]; editorSelections: Selection[] } { const selections = editor.getSelections() ?? []; const secondaryPositions = selections.slice(1).map(selection => selection.getPosition()); @@ -445,8 +445,9 @@ export class InlineCompletionsModel extends Disposable { .getLineContent(primaryPosition.lineNumber) .substring(primaryPosition.column - 1, completion.range.endColumn - 1); const secondaryEditText = completion.text.substring(primaryPosition.column - completion.range.startColumn); + const primaryEdit = EditOperation.replaceMove(completion.range, completion.text); const edits = [ - EditOperation.replaceMove(completion.range, completion.text), + primaryEdit, ...secondaryPositions.map(pos => { const textAfterSecondaryCursor = this.textModel .getLineContent(pos.lineNumber) @@ -456,12 +457,66 @@ export class InlineCompletionsModel extends Disposable { return EditOperation.replaceMove(range, secondaryEditText); }) ]; + + // editor selections + let numberOfLinesAdded = 0; + const sortedEdits = edits.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); + let primarySelection: Selection | undefined = undefined; + const editorSelections: Selection[] = []; + + for (let i = 0; i < sortedEdits.length; i++) { + const edit = sortedEdits[i]; + const text = edit.text!; + const numberOfLinesOfText = splitLines(text).length; + // But we want to remove from that the number of lines that were previously used + const numberOfLinesOfRange = edit.range.endLineNumber - edit.range.startLineNumber; + numberOfLinesAdded += numberOfLinesOfText - numberOfLinesOfRange - 1; + + let numberOfColumnsAdded = 0; + if (numberOfLinesOfText === 1) { + // If only one line of text is added, then it could be we need to update also the column of the final editor selection + const otherEditsEndingOnSameLine = sortedEdits.slice(0, i).filter(edit => edit.range.endLineNumber === i); + if (otherEditsEndingOnSameLine.length > 0) { + // If there are other edits with range end line number ending on the same line then need to update the column + for (let j = otherEditsEndingOnSameLine.length - 1; j >= 0; j--) { + // need to iterate in reverse order, in case the next text has a new line + const otherEdit = otherEditsEndingOnSameLine[j]; + const otherEditText = splitLines(otherEdit.text!); + if (otherEditText.length === 1) { + numberOfColumnsAdded += otherEditText[otherEditText.length - 1].length - otherEdit.range.endColumn; + break; + } else { + numberOfColumnsAdded += otherEditText.length - (otherEdit.range.endColumn - otherEdit.range.startColumn); + } + } + } + } + console.log('numberOfLinesAdded', numberOfLinesAdded); + console.log('numberOfColumnsAdded : ', numberOfColumnsAdded); + + const editorSelection = Selection.fromPositions( + addPositions( + Position.lift({ lineNumber: edit.range.startLineNumber + numberOfLinesAdded, column: edit.range.startColumn + numberOfColumnsAdded }), + lengthOfText(edit.text ?? '') + ) + ); + if (edit === primaryEdit) { + primarySelection = editorSelection; + } else { + editorSelections.push(editorSelection); + } + } + + editorSelections.unshift(primarySelection!); + + /* const editorSelections = edits.map(edit => Selection.fromPositions( addPositions( Position.lift({ lineNumber: edit.range.startLineNumber, column: edit.range.startColumn }), lengthOfText(edit.text ?? '') ) )); + */ return { edits, From 2cfe4ca61e96805fad9d6265c260bea8faf1d229 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Tue, 30 Jan 2024 14:51:12 +0100 Subject: [PATCH 0726/1897] Removed unused CSS classes from tab elements in MultiEditorTabsControl --- .../workbench/browser/parts/editor/multiEditorTabsControl.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 23906f57065..0f9390ca97e 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -1770,6 +1770,11 @@ export class MultiEditorTabsControl extends EditorTabsControl { // positioned editor actions container when tabs wrap. The margin needs to // be the width of the editor actions container to avoid screen cheese. tabsContainer.style.setProperty('--last-tab-margin-right', tabsWrapMultiLine ? `${editorToolbarContainer.offsetWidth}px` : '0'); + + // Remove old css classes that are not needed anymore + for (const tab of tabsContainer.children) { + tab.classList.remove('last-in-row'); + } } // Setting enabled: selectively enable wrapping if possible From e76b1ff761c56da78ac8fb8750a0849a12dbb3e9 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Tue, 30 Jan 2024 15:13:46 +0100 Subject: [PATCH 0727/1897] :lipstick: --- .../parts/editor/multiEditorTabsControl.ts | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 0f9390ca97e..fdea610285f 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -1171,27 +1171,14 @@ export class MultiEditorTabsControl extends EditorTabsControl { } else { dropTarget = undefined; } - this.updateDropTarget(dropTarget); - // Outline - const activeContrastBorderColor = this.getColor(activeContrastBorder); - if (activeContrastBorderColor && isDND) { - element.style.outlineWidth = '2px'; - element.style.outlineStyle = 'dashed'; - element.style.outlineColor = activeContrastBorderColor; - element.style.outlineOffset = isTab ? '-5px' : '-3px'; - } else { - element.style.outlineWidth = ''; - element.style.outlineStyle = ''; - element.style.outlineColor = activeContrastBorderColor || ''; - element.style.outlineOffset = ''; - } + this.updateDropTarget(dropTarget); } private dropTarget: { leftElement: HTMLElement | undefined; rightElement: HTMLElement | undefined } | undefined; - private updateDropTarget(target: { leftElement: HTMLElement | undefined; rightElement: HTMLElement | undefined } | undefined): void { + private updateDropTarget(newTarget: { leftElement: HTMLElement | undefined; rightElement: HTMLElement | undefined } | undefined): void { const oldTargets = this.dropTarget; - if (oldTargets === target || oldTargets && target && oldTargets.leftElement === target.leftElement && oldTargets.rightElement === target.rightElement) { + if (oldTargets === newTarget || oldTargets && newTarget && oldTargets.leftElement === newTarget.leftElement && oldTargets.rightElement === newTarget.rightElement) { return; } @@ -1203,12 +1190,12 @@ export class MultiEditorTabsControl extends EditorTabsControl { oldTargets.rightElement?.classList.remove(dropClassRight); } - if (target) { - target.leftElement?.classList.add(dropClassLeft); - target.rightElement?.classList.add(dropClassRight); + if (newTarget) { + newTarget.leftElement?.classList.add(dropClassLeft); + newTarget.rightElement?.classList.add(dropClassRight); } - this.dropTarget = target; + this.dropTarget = newTarget; } private getTabDragOverLocation(e: DragEvent, tab: HTMLElement): 'left' | 'right' { From 1f80d8b8e4d91fc81a9194870f9ee779c79c6b16 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 30 Jan 2024 15:54:13 +0100 Subject: [PATCH 0728/1897] fix https://github.com/microsoft/vscode/issues/203510 (#203798) --- .../browser/inlineChatSessionServiceImpl.ts | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index 6d696545068..59ac9bf22ca 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -23,7 +23,9 @@ import { ITextModel, IValidEditOperation } from 'vs/editor/common/model'; import { Schemas } from 'vs/base/common/network'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { generateUuid } from 'vs/base/common/uuid'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; type SessionData = { editor: ICodeEditor; @@ -59,7 +61,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, @ILogService private readonly _logService: ILogService, @IInstantiationService private readonly _instaService: IInstantiationService, - @ITextFileService private readonly _textFileService: ITextFileService, + @IEditorService private readonly _editorService: IEditorService, ) { } dispose() { @@ -125,16 +127,13 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { targetUri.with({ scheme: Schemas.vscode, authority: 'inline-chat', path: '', query: new URLSearchParams({ id, 'textModel0': '' }).toString() }), true )); - // untitled documents are special + // untitled documents are special and we are releasing their session when their last editor closes if (targetUri.scheme === Schemas.untitled) { - const untitledTextModel = this._textFileService.untitled.get(targetUri); - if (untitledTextModel) { - store.add(untitledTextModel.onDidChangeDirty(() => { - if (!untitledTextModel.isDirty()) { - this.releaseSession(session); - } - })); - } + store.add(this._editorService.onDidCloseEditor(() => { + if (!this._editorService.isOpened({ resource: targetUri, typeId: UntitledTextEditorInput.ID, editorId: DEFAULT_EDITOR_ASSOCIATION.id })) { + this.releaseSession(session); + } + })); } let wholeRange = options.wholeRange; From 5c4925bc5f5a2242b4838f6d5f096f77a1d41907 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 30 Jan 2024 16:12:04 +0100 Subject: [PATCH 0729/1897] updating the selections correctly --- .../browser/inlineCompletionsModel.ts | 111 ++++++++---------- 1 file changed, 49 insertions(+), 62 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index b5545cca147..60284872706 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -16,7 +16,7 @@ import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { InlineCompletionContext, InlineCompletionTriggerKind } from 'vs/editor/common/languages'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; -import { EndOfLinePreference, IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; +import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { IFeatureDebounceInformation } from 'vs/editor/common/services/languageFeatureDebounce'; import { GhostText, GhostTextOrReplacement, ghostTextOrReplacementEquals } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; import { InlineCompletionWithUpdatedRange, InlineCompletionsSource } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource'; @@ -307,7 +307,7 @@ export class InlineCompletionsModel extends Disposable { SnippetController2.get(editor)?.insert(completion.snippetInfo.snippet, { undoStopBefore: false }); } else { const edits = this._getEdits(editor, completion.toSingleTextEdit()); - editor.executeEdits('inlineSuggestion.accept', [...edits.edits, ...completion.additionalTextEdits]); + editor.executeEdits('inlineSuggestion.accept', [...edits.edits.map(edit => EditOperation.replaceMove(edit.range, edit.text)), ...completion.additionalTextEdits]); editor.setSelections(edits.editorSelections, 'inlineCompletionAccept'); } @@ -414,7 +414,7 @@ export class InlineCompletionsModel extends Disposable { ); const singleTextEdit = new SingleTextEdit(replaceRange, newText); const edits = this._getEdits(editor, singleTextEdit); - editor.executeEdits('inlineSuggestion.accept', edits.edits); + editor.executeEdits('inlineSuggestion.accept', edits.edits.map(edit => EditOperation.replaceMove(edit.range, edit.text))); editor.setSelections(edits.editorSelections, 'inlineCompletionPartialAccept'); } finally { this._isAcceptingPartially = false; @@ -435,7 +435,7 @@ export class InlineCompletionsModel extends Disposable { } } - private _getEdits(editor: ICodeEditor, completion: SingleTextEdit): { edits: IIdentifiedSingleEditOperation[]; editorSelections: Selection[] } { + private _getEdits(editor: ICodeEditor, completion: SingleTextEdit): { edits: SingleTextEdit[]; editorSelections: Selection[] } { const selections = editor.getSelections() ?? []; const secondaryPositions = selections.slice(1).map(selection => selection.getPosition()); @@ -447,76 +447,24 @@ export class InlineCompletionsModel extends Disposable { const secondaryEditText = completion.text.substring(primaryPosition.column - completion.range.startColumn); const primaryEdit = EditOperation.replaceMove(completion.range, completion.text); const edits = [ - primaryEdit, + new SingleTextEdit(completion.range, completion.text), ...secondaryPositions.map(pos => { const textAfterSecondaryCursor = this.textModel .getLineContent(pos.lineNumber) .substring(pos.column - 1); const l = commonPrefixLength(replacedTextAfterPrimaryCursor, textAfterSecondaryCursor); const range = Range.fromPositions(pos, pos.delta(0, l)); - return EditOperation.replaceMove(range, secondaryEditText); + return new SingleTextEdit(range, secondaryEditText); }) ]; - - // editor selections - let numberOfLinesAdded = 0; - const sortedEdits = edits.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); - let primarySelection: Selection | undefined = undefined; - const editorSelections: Selection[] = []; - - for (let i = 0; i < sortedEdits.length; i++) { - const edit = sortedEdits[i]; - const text = edit.text!; - const numberOfLinesOfText = splitLines(text).length; - // But we want to remove from that the number of lines that were previously used - const numberOfLinesOfRange = edit.range.endLineNumber - edit.range.startLineNumber; - numberOfLinesAdded += numberOfLinesOfText - numberOfLinesOfRange - 1; - - let numberOfColumnsAdded = 0; - if (numberOfLinesOfText === 1) { - // If only one line of text is added, then it could be we need to update also the column of the final editor selection - const otherEditsEndingOnSameLine = sortedEdits.slice(0, i).filter(edit => edit.range.endLineNumber === i); - if (otherEditsEndingOnSameLine.length > 0) { - // If there are other edits with range end line number ending on the same line then need to update the column - for (let j = otherEditsEndingOnSameLine.length - 1; j >= 0; j--) { - // need to iterate in reverse order, in case the next text has a new line - const otherEdit = otherEditsEndingOnSameLine[j]; - const otherEditText = splitLines(otherEdit.text!); - if (otherEditText.length === 1) { - numberOfColumnsAdded += otherEditText[otherEditText.length - 1].length - otherEdit.range.endColumn; - break; - } else { - numberOfColumnsAdded += otherEditText.length - (otherEdit.range.endColumn - otherEdit.range.startColumn); - } - } - } - } - console.log('numberOfLinesAdded', numberOfLinesAdded); - console.log('numberOfColumnsAdded : ', numberOfColumnsAdded); - - const editorSelection = Selection.fromPositions( - addPositions( - Position.lift({ lineNumber: edit.range.startLineNumber + numberOfLinesAdded, column: edit.range.startColumn + numberOfColumnsAdded }), - lengthOfText(edit.text ?? '') - ) - ); - if (edit === primaryEdit) { - primarySelection = editorSelection; - } else { - editorSelections.push(editorSelection); - } - } - - editorSelections.unshift(primarySelection!); - - /* - const editorSelections = edits.map(edit => Selection.fromPositions( + const positions = this._getEditStartPositions(edits); + const editorSelections = edits.map((edit: SingleTextEdit, index: number) => Selection.fromPositions( addPositions( - Position.lift({ lineNumber: edit.range.startLineNumber, column: edit.range.startColumn }), + positions[index], lengthOfText(edit.text ?? '') ) )); - */ + * / return { edits, @@ -524,6 +472,45 @@ export class InlineCompletionsModel extends Disposable { }; } + private _getEditStartPositions(edits: SingleTextEdit[]): Position[] { + + if (!edits.length) { + return []; + } + + const primaryEdit = edits[0]; + const sortedEdits = edits.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); + const indexOfPrimaryEdit = sortedEdits.indexOf(primaryEdit); + const editStartPositions = sortedEdits.map(edit => edit.range.getStartPosition()); + + for (let i = 0; i < sortedEdits.length; i++) { + + const edit = sortedEdits[i]; + const splitText = splitLines(edit.text!); + const numberOfLinesToAdd = splitText.length - (edit.range.endLineNumber - edit.range.startLineNumber) - 1; + + for (let j = i + 1; j < editStartPositions.length; j++) { + const position = editStartPositions[j]; + let columnDelta = 0; + if (position.lineNumber === edit.range.endLineNumber) { + let rangeLength; + if (edit.range.startLineNumber === edit.range.endLineNumber) { + rangeLength = edit.range.endColumn - edit.range.startColumn; + } else { + rangeLength = edit.range.endColumn; + } + const lastLineLength = splitText[splitText.length - 1].length; + columnDelta += lastLineLength - rangeLength; + } + editStartPositions[j] = position.delta(numberOfLinesToAdd, columnDelta); + } + } + + const primaryPosition = editStartPositions.splice(indexOfPrimaryEdit, 1)[0]; + editStartPositions.unshift(primaryPosition); + return editStartPositions; + } + public handleSuggestAccepted(item: SuggestItemInfo) { const itemEdit = item.toSingleTextEdit().removeCommonPrefix(this.textModel); const augmentedCompletion = this._computeAugmentedCompletion(itemEdit, undefined); From edd20d1726f1b662c97be33c551e26fb258ed01f Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 30 Jan 2024 16:13:35 +0100 Subject: [PATCH 0730/1897] removing the code which is causing the error --- .../contrib/inlineCompletions/browser/inlineCompletionsModel.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 60284872706..9f3d5e2db5b 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -445,7 +445,6 @@ export class InlineCompletionsModel extends Disposable { .getLineContent(primaryPosition.lineNumber) .substring(primaryPosition.column - 1, completion.range.endColumn - 1); const secondaryEditText = completion.text.substring(primaryPosition.column - completion.range.startColumn); - const primaryEdit = EditOperation.replaceMove(completion.range, completion.text); const edits = [ new SingleTextEdit(completion.range, completion.text), ...secondaryPositions.map(pos => { @@ -464,7 +463,6 @@ export class InlineCompletionsModel extends Disposable { lengthOfText(edit.text ?? '') ) )); - * / return { edits, From bc3dc5a073b0ed53542603f1aa8554724759ba77 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 30 Jan 2024 16:16:57 +0100 Subject: [PATCH 0731/1897] simplifying the code --- .../inlineCompletions/browser/inlineCompletionsModel.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 9f3d5e2db5b..9b0a3e1d0b4 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -497,8 +497,7 @@ export class InlineCompletionsModel extends Disposable { } else { rangeLength = edit.range.endColumn; } - const lastLineLength = splitText[splitText.length - 1].length; - columnDelta += lastLineLength - rangeLength; + columnDelta = splitText[splitText.length - 1].length - rangeLength; } editStartPositions[j] = position.delta(numberOfLinesToAdd, columnDelta); } From 9116c8c9d9f501414031e5fce78dd0d7a098bd35 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Tue, 30 Jan 2024 08:05:02 -0800 Subject: [PATCH 0732/1897] notebook variable data source tests (#203753) * indexed children tests * one more assert --- .../notebookVariablesDataSource.test.ts | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 src/vs/workbench/contrib/notebook/test/browser/notebookVariablesDataSource.test.ts diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookVariablesDataSource.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookVariablesDataSource.test.ts new file mode 100644 index 00000000000..8de6a93e27e --- /dev/null +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookVariablesDataSource.test.ts @@ -0,0 +1,116 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as assert from 'assert'; +import { AsyncIterableObject, AsyncIterableSource } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { URI } from 'vs/base/common/uri'; +import { mock } from 'vs/base/test/common/mock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { INotebookVariableElement, NotebookVariableDataSource } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { INotebookKernel, INotebookKernelService, VariablesResult } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; + + +suite('NotebookVariableDataSource', () => { + let dataSource: NotebookVariableDataSource; + const notebookModel = { uri: 'one.ipynb', languages: ['python'] } as unknown as NotebookTextModel; + let provideVariablesCalled = false; + + type VariablesResultWithAction = VariablesResult & { action?: () => void }; + let results: VariablesResultWithAction[] = [ + { id: 1, name: 'a', value: '1', hasNamedChildren: false, indexedChildrenCount: 0 }, + ]; + + const kernel = new class extends mock() { + override hasVariableProvider = true; + override provideVariables( + notebookUri: URI, + parentId: number | undefined, + kind: 'named' | 'indexed', + start: number, + token: CancellationToken + ): AsyncIterableObject { + provideVariablesCalled = true; + const source = new AsyncIterableSource(); + for (let i = 0; i < results.length; i++) { + if (token.isCancellationRequested) { + break; + } + if (results[i].action) { + results[i].action!(); + } + source.emitOne(results[i]); + } + + setTimeout(() => source.resolve(), 0); + return source.asyncIterable; + } + }; + + const kernelService = new class extends mock() { + override getMatchingKernel(notebook: NotebookTextModel) { + return { selected: kernel, all: [], suggestions: [], hidden: [] }; + } + }; + + ensureNoDisposablesAreLeakedInTestSuite(); + + setup(() => { + provideVariablesCalled = false; + dataSource = new NotebookVariableDataSource(kernelService); + }); + + test('Root element should return children', async () => { + const variables = await dataSource.getChildren({ kind: 'root', notebook: notebookModel }); + + assert.strictEqual(variables.length, 1); + }); + + test('Get children of list element', async () => { + const parent = { kind: 'variable', notebook: notebookModel, id: '1', extHostId: 1, name: 'list', value: '[...]', hasNamedChildren: false, indexedChildrenCount: 5 } as INotebookVariableElement; + results = [ + { id: 2, name: 'first', value: '1', hasNamedChildren: false, indexedChildrenCount: 0 }, + { id: 3, name: 'second', value: '2', hasNamedChildren: false, indexedChildrenCount: 0 }, + { id: 4, name: 'third', value: '3', hasNamedChildren: false, indexedChildrenCount: 0 }, + { id: 5, name: 'fourth', value: '4', hasNamedChildren: false, indexedChildrenCount: 0 }, + { id: 6, name: 'fifth', value: '5', hasNamedChildren: false, indexedChildrenCount: 0 }, + ]; + + const variables = await dataSource.getChildren(parent); + + assert.strictEqual(variables.length, 5); + }); + + test('Get children for large list', async () => { + const parent = { kind: 'variable', notebook: notebookModel, id: '1', extHostId: 1, name: 'list', value: '[...]', hasNamedChildren: false, indexedChildrenCount: 2000 } as INotebookVariableElement; + results = []; + + const variables = await dataSource.getChildren(parent); + + assert(variables.length > 1, 'We should have results for groups of children'); + assert(!provideVariablesCalled, 'provideVariables should not be called'); + assert.equal(variables[0].extHostId, parent.extHostId, 'ExtHostId should match the parent since we will use it to get the real children'); + }); + + test('Cancel while enumerating through children', async () => { + const parent = { kind: 'variable', notebook: notebookModel, id: '1', extHostId: 1, name: 'list', value: '[...]', hasNamedChildren: false, indexedChildrenCount: 10 } as INotebookVariableElement; + results = [ + { id: 2, name: 'first', value: '1', hasNamedChildren: false, indexedChildrenCount: 0 }, + { id: 3, name: 'second', value: '2', hasNamedChildren: false, indexedChildrenCount: 0 }, + { id: 4, name: 'third', value: '3', hasNamedChildren: false, indexedChildrenCount: 0 }, + { id: 5, name: 'fourth', value: '4', hasNamedChildren: false, indexedChildrenCount: 0 }, + { id: 5, name: 'fifth', value: '4', hasNamedChildren: false, indexedChildrenCount: 0, action: () => dataSource.cancel() } as VariablesResult, + { id: 7, name: 'sixth', value: '6', hasNamedChildren: false, indexedChildrenCount: 0 }, + { id: 8, name: 'seventh', value: '7', hasNamedChildren: false, indexedChildrenCount: 0 }, + { id: 9, name: 'eighth', value: '8', hasNamedChildren: false, indexedChildrenCount: 0 }, + { id: 10, name: 'ninth', value: '9', hasNamedChildren: false, indexedChildrenCount: 0 }, + { id: 11, name: 'tenth', value: '10', hasNamedChildren: false, indexedChildrenCount: 0 }, + ]; + + const variables = await dataSource.getChildren(parent); + + assert.equal(variables.length, 5, 'Iterating should have been cancelled'); + }); +}); From 06a8ed6e0eb303edc6fd3d7d7520fd4685e15244 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 30 Jan 2024 13:29:00 -0300 Subject: [PATCH 0733/1897] Fix incorrectly disposing some disposables (#203809) Fix #203794 --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 7ca6a947efc..ba054389137 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -527,8 +527,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { From f40051b610e7a8333ab90e30c7bf581fd0ac758c Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 30 Jan 2024 13:54:19 -0300 Subject: [PATCH 0734/1897] Port candidates to main (#203815) * Port candidate to main Bringing in #203574 * Enable #file for stable (#203549) Fix #203548 --- .../chat/browser/chatMarkdownDecorationsRenderer.ts | 10 ++++------ .../chat/browser/contrib/chatInputEditorContrib.ts | 8 -------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts index 1f3dad5c4df..fc1f0795b3c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts @@ -25,9 +25,7 @@ export class ChatMarkdownDecorationsRenderer { @IKeybindingService private readonly keybindingService: IKeybindingService, @ILabelService private readonly labelService: ILabelService, @ILogService private readonly logService: ILogService - ) { - - } + ) { } convertParsedRequestToMarkdown(parsedRequest: IParsedChatRequest): string { let result = ''; @@ -37,9 +35,9 @@ export class ChatMarkdownDecorationsRenderer { } else { const uri = part instanceof ChatRequestDynamicVariablePart && part.data.map(d => d.value).find((d): d is URI => d instanceof URI) || undefined; - const title = uri ? this.labelService.getUriLabel(uri, { relative: true }) : ''; + const title = uri ? encodeURIComponent(this.labelService.getUriLabel(uri, { relative: true })) : ''; - result += `[${part.text}](${variableRefUrl}${title})`; + result += `[${part.text}](${variableRefUrl}?${title})`; } } @@ -51,7 +49,7 @@ export class ChatMarkdownDecorationsRenderer { const href = a.getAttribute('data-href'); if (href) { if (href.startsWith(variableRefUrl)) { - const title = href.slice(variableRefUrl.length); + const title = decodeURIComponent(href.slice(variableRefUrl.length + 1)); a.parentElement!.replaceChild( this.renderResourceWidget(a.textContent!, title), a); diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index c83c4e211d8..503d7c98258 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -17,7 +17,6 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -512,8 +511,6 @@ class BuiltinDynamicCompletions extends Disposable { constructor( @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IProductService private readonly productService: IProductService, ) { super(); @@ -521,11 +518,6 @@ class BuiltinDynamicCompletions extends Disposable { _debugDisplayName: 'chatDynamicCompletions', triggerCharacters: [chatVariableLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { - const fileVariablesEnabled = this.configurationService.getValue('chat.experimental.fileVariables') ?? this.productService.quality !== 'stable'; - if (!fileVariablesEnabled) { - return; - } - const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); if (!widget || !widget.supportsFileReferences) { return null; From bd0c11e1d4c4b126b0ef9433c092d895f09cb103 Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Wed, 15 Nov 2023 11:32:53 -0800 Subject: [PATCH 0735/1897] Prevent incorrect indentation for Ruby's in and when keywords Co-authored-by: Soutaro Matsumoto --- extensions/ruby/language-configuration.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/ruby/language-configuration.json b/extensions/ruby/language-configuration.json index a86f592e3bd..e61f3ac410f 100644 --- a/extensions/ruby/language-configuration.json +++ b/extensions/ruby/language-configuration.json @@ -26,6 +26,6 @@ ], "indentationRules": { "increaseIndentPattern": "^\\s*((begin|class|(private|protected)\\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|in|while|case)|([^#]*\\sdo\\b)|([^#]*=\\s*(case|if|unless)))\\b([^#\\{;]|(\"|'|\/).*\\4)*(#.*)?$", - "decreaseIndentPattern": "^\\s*([}\\]]([,)]?\\s*(#|$)|\\.[a-zA-Z_]\\w*\\b)|(end|rescue|ensure|else|elsif|when|in)\\b)" + "decreaseIndentPattern": "^\\s*([}\\]]([,)]?\\s*(#|$)|\\.[a-zA-Z_]\\w*\\b)|(end|rescue|ensure|else|elsif)\\b)|((in|when)\\s)" } } From 978342ea412c3cd25a8b8e1b4258c16c47b0e060 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 30 Jan 2024 08:58:47 -0800 Subject: [PATCH 0736/1897] add basics --- .../audioCues/browser/audioCueService.ts | 16 + .../browser/audioCues.contribution.ts | 345 ++++++++++++++++++ 2 files changed, 361 insertions(+) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 0e35e7f9e3c..f58928e3a24 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -54,6 +54,22 @@ export class AudioCueService extends Disposable implements IAudioCueService { @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); + this._migrateSettings(); + } + + private _migrateSettings(): void { + const audioCuesConfig = this.configurationService.getValue<{ [key: string]: boolean }>('audioCues'); + for (const entry in audioCuesConfig) { + if (entry) { + this.configurationService.updateValue('audioCues.' + entry, undefined); + if (entry === 'debouncePositionChanges') { + this.configurationService.updateValue('signals.' + entry, audioCuesConfig[entry]); + } else { + this.configurationService.updateValue('signals.' + entry + '.' + 'audioCue', audioCuesConfig[entry]); + } + } + } + // do this for alerts as well } public async playAudioCue(cue: AudioCue, options: IAudioCueOptions = {}): Promise { diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index 1fced335827..d74fe0c1ee2 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -32,6 +32,351 @@ const audioCueFeatureBase: IConfigurationPropertySchema = { tags: ['accessibility'] }; +const alertFeatureBase: IConfigurationPropertySchema = { + 'type': 'boolean', + 'default': true, + 'tags': ['accessibility'] +}; + +Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + 'properties': { + 'signals.debouncePositionChanges': { + 'description': localize('signals.debouncePositionChanges', "Whether or not position changes should be debounced"), + 'type': 'boolean', + 'default': false, + tags: ['accessibility'] + }, + 'signals.lineHasBreakpoint': { + 'description': localize('signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), + ...alertFeatureBase + }, + } + }, + 'signals.lineHasInlineSuggestion': { + 'description': localize('signals.lineHasInlineSuggestion', "Plays a signal when the active line has an inline suggestion."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasInlineSuggestion.alert', "Alerts when the active line has an inline suggestion."), + ...alertFeatureBase + }, + } + }, + 'signals.lineHasError': { + 'description': localize('signals.lineHasError', "Plays a signal when the active line has an error."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasError.alert', "Alerts when the active line has an error."), + ...alertFeatureBase + }, + } + }, + 'signals.lineHasFoldedArea': { + 'description': localize('signals.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasFoldedArea.alert', "Alerts when the active line has a folded area that can be unfolded."), + ...alertFeatureBase + }, + } + }, + 'signals.lineHasWarning': { + 'description': localize('signals.lineHasWarning', "Plays a signal when the active line has a warning."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasWarning.alert', "Alerts when the active line has a warning."), + ...alertFeatureBase + }, + }, + }, + 'signals.onDebugBreak': { + 'description': localize('signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), + 'properties': { + 'audioCue': { + 'description': localize('signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.onDebugBreak.alert', "Alerts when the debugger stopped on a breakpoint."), + ...alertFeatureBase + }, + } + }, + 'signals.noInlayHints': { + 'description': localize('signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), + 'properties': { + 'audioCue': { + 'description': localize('signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.noInlayHints.alert', "Alerts when trying to read a line with inlay hints that has no inlay hints."), + ...alertFeatureBase + }, + } + }, + 'signals.taskCompleted': { + 'description': localize('signals.taskCompleted', "Plays a signal when a task is completed."), + 'properties': { + 'audioCue': { + 'description': localize('signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.taskCompleted.alert', "Alerts when a task is completed."), + ...alertFeatureBase + }, + } + }, + 'signals.taskFailed': { + 'description': localize('signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), + 'properties': { + 'audioCue': { + 'description': localize('signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.taskFailed.alert', "Alerts when a task fails (non-zero exit code)."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalCommandFailed': { + 'description': localize('signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalCommandFailed.alert', "Alerts when a terminal command fails (non-zero exit code)."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalQuickFix': { + 'description': localize('signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalQuickFix.alert', "Alerts when terminal Quick Fixes are available."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalBell': { + 'description': localize('signals.terminalBell', "Plays a signal when the terminal bell is ringing."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalBell.alert', "Alerts when the terminal bell is ringing."), + ...alertFeatureBase + }, + }, + default: 'on' + }, + 'signals.diffLineInserted': { + 'description': localize('signals.diffLineInserted', "Plays a signal when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + 'properties': { + 'audioCue': { + 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.diffLineInserted.alert', "Alerts when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + ...alertFeatureBase + }, + } + }, + 'signals.diffLineDeleted': { + 'description': localize('signals.diffLineDeleted', "Plays a signal when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), + 'properties': { + 'audioCue': { + 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.diffLineDeleted.alert', "Alerts when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), + ...alertFeatureBase + }, + } + }, + 'signals.diffLineModified': { + 'description': localize('signals.diffLineModified', "Plays a signal when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + 'properties': { + 'audioCue': { + 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.diffLineModified.alert', "Alerts when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + ...alertFeatureBase + }, + } + }, + 'signals.notebookCellCompleted': { + 'description': localize('signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), + 'properties': { + 'audioCue': { + 'description': localize('signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.notebookCellCompleted.alert', "Alerts when a notebook cell execution is successfully completed."), + ...alertFeatureBase + }, + } + }, + 'signals.notebookCellFailed': { + 'description': localize('signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), + 'properties': { + 'audioCue': { + 'description': localize('signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.notebookCellFailed.alert', "Alerts when a notebook cell execution fails."), + ...alertFeatureBase + }, + } + }, + 'signals.chatRequestSent': { + 'description': localize('signals.chatRequestSent', "Plays a signal when a chat request is made."), + 'properties': { + 'audioCue': { + 'description': localize('signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.chatRequestSent.alert', "Alerts when a chat request is made."), + ...alertFeatureBase + }, + }, + }, + 'signals.chatResponsePending': { + 'description': localize('signals.chatResponsePending', "Plays a signal on loop while the response is pending."), + 'properties': { + 'audioCue': { + 'description': localize('signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), + ...alertFeatureBase + }, + }, + }, + 'signals.chatResponseReceived': { + 'description': localize('signals.chatResponseReceived', "Plays a signal on loop while the response has been received."), + 'properties': { + 'audioCue': { + 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.chatResponseReceived.alert', "Alerts on loop while the response has been received."), + ...alertFeatureBase + }, + }, + }, + 'signals.clear': { + 'description': localize('signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), + 'properties': { + 'audioCue': { + 'description': localize('signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.clear.alert', "Alerts when a feature is cleared."), + ...alertFeatureBase + }, + }, + }, + 'signals.save': { + 'markdownDescription': localize('signals.save', "Plays a signal when a file is saved."), + 'properties': { + 'audioCue': { + 'description': localize('signals.save.audioCue', "Plays an audio cue when a file is saved."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), + localize('signals.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), + localize('signals.save.audioCue.never', "Never plays the audio cue.") + ], + }, + 'alert': { + 'description': localize('signals.save.alert', "Alerts when a file is saved."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), + localize('signals.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), + localize('signals.save.alert.never', "Never plays the audio cue.") + ], + }, + } + }, + 'signals.format': { + 'markdownDescription': localize('signals.format', "Plays a signal when a file or notebook is formatted."), + 'properties': { + 'audioCue': { + 'description': localize('signals.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), + localize('signals.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('signals.format.never', "Never plays the audio cue.") + ], + }, + 'alert': { + 'description': localize('signals.format.alert', "Alerts when a file or notebook is formatted."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), + localize('signals.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('signals.format.alert.never', "Never plays the alert.") + ], + }, + } + }, + } +}); + Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ 'properties': { 'audioCues.enabled': { From b6c4a4df2834a2793e8cc5faff424b21e7e82f22 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 30 Jan 2024 18:17:49 +0100 Subject: [PATCH 0737/1897] preserving the order of the edits from the method --- .../browser/inlineCompletionsModel.ts | 67 +++++++++++++------ 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 9b0a3e1d0b4..f74e56c11b6 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -456,13 +456,8 @@ export class InlineCompletionsModel extends Disposable { return new SingleTextEdit(range, secondaryEditText); }) ]; - const positions = this._getEditStartPositions(edits); - const editorSelections = edits.map((edit: SingleTextEdit, index: number) => Selection.fromPositions( - addPositions( - positions[index], - lengthOfText(edit.text ?? '') - ) - )); + const newRanges = this._getNewRanges(edits); + const editorSelections = newRanges.map(range => Selection.fromPositions(range.getEndPosition())); return { edits, @@ -470,27 +465,48 @@ export class InlineCompletionsModel extends Disposable { }; } - private _getEditStartPositions(edits: SingleTextEdit[]): Position[] { + // todo: extract this function into a separate place, write some tests for this function + // todo: investigate that bug, that we previously saw inside of test 21, there appears to be a logical error somewhere that needs to be resolved + private _getNewRanges(edits: SingleTextEdit[]): Range[] { - if (!edits.length) { + if (edits.length === 0) { return []; } - const primaryEdit = edits[0]; - const sortedEdits = edits.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); - const indexOfPrimaryEdit = sortedEdits.indexOf(primaryEdit); - const editStartPositions = sortedEdits.map(edit => edit.range.getStartPosition()); + const sortIndices = Array.from(edits.keys()).sort((a, b) => + Range.compareRangesUsingStarts(edits[a].range, edits[b].range) + ); + const indexOfEdit = sortIndices.indexOf(0); + const firstEdit = edits[indexOfEdit]; + const firstEditStartPosition = firstEdit.range.getStartPosition(); + const rangesMap = new Map([[ + 0, Range.fromPositions( + firstEditStartPosition, + addPositions( + firstEdit.range.getStartPosition(), + lengthOfText(firstEdit.text) + )) + ]]); - for (let i = 0; i < sortedEdits.length; i++) { + for (let i = 0; i < edits.length; i++) { - const edit = sortedEdits[i]; + const indexOfEdit = sortIndices.indexOf(i); + const edit = edits[indexOfEdit]; + if (!edit) { + return []; + } const splitText = splitLines(edit.text!); const numberOfLinesToAdd = splitText.length - (edit.range.endLineNumber - edit.range.startLineNumber) - 1; - for (let j = i + 1; j < editStartPositions.length; j++) { - const position = editStartPositions[j]; + for (let j = i + 1; j < edits.length; j++) { + const indexOfEdit = sortIndices.indexOf(j); + const affectedEdit = edits[indexOfEdit]; + const affectedRange = rangesMap.get(j) ?? affectedEdit?.range; + if (!affectedRange || !affectedEdit) { + return []; + } let columnDelta = 0; - if (position.lineNumber === edit.range.endLineNumber) { + if (affectedRange.startLineNumber === edit.range.endLineNumber) { let rangeLength; if (edit.range.startLineNumber === edit.range.endLineNumber) { rangeLength = edit.range.endColumn - edit.range.startColumn; @@ -499,13 +515,20 @@ export class InlineCompletionsModel extends Disposable { } columnDelta = splitText[splitText.length - 1].length - rangeLength; } - editStartPositions[j] = position.delta(numberOfLinesToAdd, columnDelta); + const rangeStart = affectedRange.getStartPosition().delta(numberOfLinesToAdd, columnDelta); + const rangeEnd = addPositions( + rangeStart, + lengthOfText(affectedEdit.text) + ); + rangesMap.set(j, Range.fromPositions(rangeStart, rangeEnd)); } } - const primaryPosition = editStartPositions.splice(indexOfPrimaryEdit, 1)[0]; - editStartPositions.unshift(primaryPosition); - return editStartPositions; + const ranges = []; + for (let i = 0; i < edits.length; i++) { + ranges.push(rangesMap.get(i)!); + } + return ranges; } public handleSuggestAccepted(item: SuggestItemInfo) { From 83d2f0673fc1b025954be82fb537929bf6d38657 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:32:09 -0600 Subject: [PATCH 0738/1897] "editor.find.seedSearchStringFromSelection" is ignored (#203759) Fixes #142864 --- src/vs/workbench/contrib/search/browser/searchView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index e54f8fe8260..fc890846182 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1125,7 +1125,7 @@ export class SearchView extends ViewPane { private updateTextFromSelection({ allowUnselectedWord = true, allowSearchOnType = true }, editor?: IEditor): boolean { const seedSearchStringFromSelection = this.configurationService.getValue('editor').find!.seedSearchStringFromSelection; - if (!seedSearchStringFromSelection) { + if (!seedSearchStringFromSelection || seedSearchStringFromSelection === 'never') { return false; } From 0d1811a62a8a20959458d43dd6639b0c609f40c4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 30 Jan 2024 09:35:17 -0800 Subject: [PATCH 0739/1897] Ensure MutableDisposable is not disposed Fixes #203708 --- .../stickyScroll/browser/terminalStickyScrollOverlay.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts index 2001ba551a8..8868f63beef 100644 --- a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts @@ -409,15 +409,17 @@ export class TerminalStickyScrollOverlay extends Disposable { const overlay = this._stickyScrollOverlay; // The Webgl renderer isn't used here as there are a limited number of webgl contexts - // available within a given page. This is a single row that isn't rendered to often so the + // available within a given page. This is a single row that isn't rendered too often so the // performance isn't as important if (this._xterm.isGpuAccelerated) { if (!this._canvasAddon.value && !this._pendingCanvasAddon) { this._pendingCanvasAddon = createCancelablePromise(async token => { const CanvasAddon = await this._getCanvasAddonConstructor(); - if (!token.isCancellationRequested) { + if (!token.isCancellationRequested && !this._store.isDisposed) { this._canvasAddon.value = new CanvasAddon(); - overlay.loadAddon(this._canvasAddon.value); + if (this._canvasAddon.value) { // The MutableDisposable could be disposed + overlay.loadAddon(this._canvasAddon.value); + } } this._pendingCanvasAddon = undefined; }); From 6291fb21ec348899ccc0a1b1aa5a54dd2e13b617 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 30 Jan 2024 09:52:18 -0800 Subject: [PATCH 0740/1897] cli: fix adveristing file extension on windows (#203818) Fixes #202919 --- cli/src/commands/args.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cli/src/commands/args.rs b/cli/src/commands/args.rs index 0272f63e73c..229d54ad061 100644 --- a/cli/src/commands/args.rs +++ b/cli/src/commands/args.rs @@ -10,9 +10,13 @@ use clap::{Args, Parser, Subcommand, ValueEnum}; use const_format::concatcp; const CLI_NAME: &str = concatcp!(constants::PRODUCT_NAME_LONG, " CLI"); -const HELP_COMMANDS: &str = "Usage: {name} [options][paths...] +const HELP_COMMANDS: &str = concatcp!( + "Usage: ", + constants::APPLICATION_NAME, + " [options][paths...] -To read output from another program, append '-' (e.g. 'echo Hello World | {name} -')"; +To read output from another program, append '-' (e.g. 'echo Hello World | {name} -')" +); const STANDALONE_TEMPLATE: &str = concatcp!( CLI_NAME, From 55e64beda6de2d3a4cd626fff5251ab90f2cd010 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 30 Jan 2024 10:03:58 -0800 Subject: [PATCH 0741/1897] Tweak wording --- src/vscode-dts/vscode.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index eea27de862f..bc0e61bbf1d 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -11737,7 +11737,7 @@ declare module 'vscode' { * until `Terminal.show` is called. The typical usage for this is when you need to run * something that may need interactivity but only want to tell the user about it when * interaction is needed. Note that the terminals will still be exposed to all extensions - * as normal. And the hidden terminals won't be restored when the workspace is next opened. + * as normal and they will remain hidden when the workspace is reloaded. */ hideFromUser?: boolean; From 1802552fc5512cc4c86f074affb23bc91fed511a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 30 Jan 2024 10:44:53 -0800 Subject: [PATCH 0742/1897] Add change to missed file --- src/vs/platform/terminal/common/terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 7c20cb3305c..54942de0d4d 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -565,7 +565,7 @@ export interface IShellLaunchConfig { * until `Terminal.show` is called. The typical usage for this is when you need to run * something that may need interactivity but only want to tell the user about it when * interaction is needed. Note that the terminals will still be exposed to all extensions - * as normal. And the hidden terminals won't be restored when the workspace is next opened. + * as normal and they will remain hidden when the workspace is reloaded. */ hideFromUser?: boolean; From 28cf876f29477dc1e6fb7d9f7eec24ed510b04ac Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 30 Jan 2024 10:57:58 -0800 Subject: [PATCH 0743/1897] wip --- .../audioCues/browser/audioCueService.ts | 25 ++++- .../browser/audioCues.contribution.ts | 99 +++++++++++++------ .../preferences/browser/settingsLayout.ts | 5 + 3 files changed, 99 insertions(+), 30 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index f58928e3a24..dfdb1979b9b 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -61,15 +61,36 @@ export class AudioCueService extends Disposable implements IAudioCueService { const audioCuesConfig = this.configurationService.getValue<{ [key: string]: boolean }>('audioCues'); for (const entry in audioCuesConfig) { if (entry) { + console.log('cue ', entry); this.configurationService.updateValue('audioCues.' + entry, undefined); - if (entry === 'debouncePositionChanges') { + if (entry === 'volume' || entry === 'enabled') { + // ignore, these are audio cue settings + } else if (entry === 'debouncePositionChanges') { this.configurationService.updateValue('signals.' + entry, audioCuesConfig[entry]); } else { this.configurationService.updateValue('signals.' + entry + '.' + 'audioCue', audioCuesConfig[entry]); } } } - // do this for alerts as well + const alertsConfig = this.configurationService.getValue<{ [key: string]: boolean }>('accessibility.alert'); + for (let entry in alertsConfig) { + if (entry) { + console.log('alert ', entry); + if (entry === 'warning') { + entry = 'lineHasWarning'; + } else if (entry === 'error') { + entry = 'lineHasError'; + } else if (entry === 'foldedArea') { + entry = 'lineHasFoldedArea'; + } else if (entry === 'breakpoint') { + entry = 'onDebugBreak'; + } + this.configurationService.updateValue('accessibility.alert.' + entry, undefined); + this.configurationService.updateValue('signals.' + entry + '.' + 'alert', alertsConfig[entry]); + } + } + const signals = this.configurationService.getValue<{ [key: string]: boolean }>('signals'); + console.log(JSON.stringify(signals)); } public async playAudioCue(cue: AudioCue, options: IAudioCueOptions = {}): Promise { diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index d74fe0c1ee2..a5df5042d0d 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -35,10 +35,34 @@ const audioCueFeatureBase: IConfigurationPropertySchema = { const alertFeatureBase: IConfigurationPropertySchema = { 'type': 'boolean', 'default': true, - 'tags': ['accessibility'] + 'tags': ['accessibility'], + required: ['audioCue'] +}; + +const signalFeatureBase: IConfigurationPropertySchema = { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, + default: { + audioCue: 'auto', + alert: true + } +}; + +const defaultNoAlert: IConfigurationPropertySchema = { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, + 'default': { + 'audioCue': 'auto', + } }; Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + id: 'signals', + order: 100, + title: localize('signals', "Signals"), + type: 'object', 'properties': { 'signals.debouncePositionChanges': { 'description': localize('signals.debouncePositionChanges', "Whether or not position changes should be debounced"), @@ -47,6 +71,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis tags: ['accessibility'] }, 'signals.lineHasBreakpoint': { + ...signalFeatureBase, 'description': localize('signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), 'properties': { 'audioCue': { @@ -57,9 +82,10 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'description': localize('signals.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), ...alertFeatureBase }, - } + }, }, 'signals.lineHasInlineSuggestion': { + ...signalFeatureBase, 'description': localize('signals.lineHasInlineSuggestion', "Plays a signal when the active line has an inline suggestion."), 'properties': { 'audioCue': { @@ -73,6 +99,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.lineHasError': { + ...signalFeatureBase, 'description': localize('signals.lineHasError', "Plays a signal when the active line has an error."), 'properties': { 'audioCue': { @@ -83,9 +110,10 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'description': localize('signals.lineHasError.alert', "Alerts when the active line has an error."), ...alertFeatureBase }, - } + }, }, 'signals.lineHasFoldedArea': { + ...signalFeatureBase, 'description': localize('signals.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), 'properties': { 'audioCue': { @@ -99,6 +127,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.lineHasWarning': { + ...signalFeatureBase, 'description': localize('signals.lineHasWarning', "Plays a signal when the active line has a warning."), 'properties': { 'audioCue': { @@ -112,6 +141,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, }, 'signals.onDebugBreak': { + ...signalFeatureBase, 'description': localize('signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), 'properties': { 'audioCue': { @@ -125,6 +155,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.noInlayHints': { + ...signalFeatureBase, 'description': localize('signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), 'properties': { 'audioCue': { @@ -138,6 +169,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.taskCompleted': { + ...signalFeatureBase, 'description': localize('signals.taskCompleted', "Plays a signal when a task is completed."), 'properties': { 'audioCue': { @@ -151,6 +183,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.taskFailed': { + ...signalFeatureBase, 'description': localize('signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), 'properties': { 'audioCue': { @@ -164,6 +197,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.terminalCommandFailed': { + ...signalFeatureBase, 'description': localize('signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), 'properties': { 'audioCue': { @@ -177,6 +211,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.terminalQuickFix': { + ...signalFeatureBase, 'description': localize('signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), 'properties': { 'audioCue': { @@ -190,6 +225,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.terminalBell': { + ...signalFeatureBase, 'description': localize('signals.terminalBell', "Plays a signal when the terminal bell is ringing."), 'properties': { 'audioCue': { @@ -200,49 +236,40 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'description': localize('signals.terminalBell.alert', "Alerts when the terminal bell is ringing."), ...alertFeatureBase }, - }, - default: 'on' + } }, 'signals.diffLineInserted': { + ...defaultNoAlert, 'description': localize('signals.diffLineInserted', "Plays a signal when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.diffLineInserted.alert', "Alerts when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - ...alertFeatureBase - }, + } } }, 'signals.diffLineDeleted': { + ...defaultNoAlert, 'description': localize('signals.diffLineDeleted', "Plays a signal when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.diffLineDeleted.alert', "Alerts when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), - ...alertFeatureBase - }, + } } }, 'signals.diffLineModified': { + ...defaultNoAlert, 'description': localize('signals.diffLineModified', "Plays a signal when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.diffLineModified.alert', "Alerts when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - ...alertFeatureBase - }, + } } }, 'signals.notebookCellCompleted': { + ...signalFeatureBase, 'description': localize('signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), 'properties': { 'audioCue': { @@ -256,6 +283,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.notebookCellFailed': { + ...signalFeatureBase, 'description': localize('signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), 'properties': { 'audioCue': { @@ -269,19 +297,21 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.chatRequestSent': { + ...signalFeatureBase, 'description': localize('signals.chatRequestSent', "Plays a signal when a chat request is made."), 'properties': { - 'audioCue': { + 'signals.chatRequestSent.audioCue': { 'description': localize('signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), ...audioCueFeatureBase }, - 'alert': { + 'signals.chatRequestSent.alert': { 'description': localize('signals.chatRequestSent.alert', "Alerts when a chat request is made."), ...alertFeatureBase }, - }, + } }, 'signals.chatResponsePending': { + ...signalFeatureBase, 'description': localize('signals.chatResponsePending', "Plays a signal on loop while the response is pending."), 'properties': { 'audioCue': { @@ -295,19 +325,17 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, }, 'signals.chatResponseReceived': { + ...defaultNoAlert, 'description': localize('signals.chatResponseReceived', "Plays a signal on loop while the response has been received."), 'properties': { - 'audioCue': { + 'signals.chatResponseReceived.audioCue': { 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), ...audioCueFeatureBase }, - 'alert': { - 'description': localize('signals.chatResponseReceived.alert', "Alerts on loop while the response has been received."), - ...alertFeatureBase - }, }, }, 'signals.clear': { + ...signalFeatureBase, 'description': localize('signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), 'properties': { 'audioCue': { @@ -321,6 +349,9 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, }, 'signals.save': { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, 'markdownDescription': localize('signals.save', "Plays a signal when a file is saved."), 'properties': { 'audioCue': { @@ -345,9 +376,16 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('signals.save.alert.never', "Never plays the audio cue.") ], }, + }, + default: { + 'audioCue': 'never', + 'alert': 'never' } }, 'signals.format': { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, 'markdownDescription': localize('signals.format', "Plays a signal when a file or notebook is formatted."), 'properties': { 'audioCue': { @@ -372,6 +410,10 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('signals.format.alert.never', "Never plays the alert.") ], }, + }, + default: { + 'audioCue': 'never', + 'alert': 'never' } }, } @@ -516,3 +558,4 @@ Registry.as(ConfigurationExtensions.Configuration).regis registerAction2(ShowAudioCueHelp); registerAction2(ShowAccessibilityAlertHelp); + diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index b4b225d7bc8..f10aaea48eb 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -226,6 +226,11 @@ export const tocData: ITOCEntry = { label: localize('audioCues', 'Audio Cues'), settings: ['audioCues.*'] }, + { + id: 'features/signals', + label: localize('signals', 'Signals'), + settings: ['signals.*'] + }, { id: 'features/mergeEditor', label: localize('mergeEditor', 'Merge Editor'), From ab5dd705b0465d899fae4dd0692b8f9a474d9ded Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 30 Jan 2024 18:58:55 +0000 Subject: [PATCH 0744/1897] Add promiseWithResolvers shim (#203826) Adds a shim for `Promise.withResolvers`. This is slightly reduces lines of code. More importantly it plays more nicely with TypeScript's type checking and lets us mark more vars const and properties readonly --- src/vs/base/common/async.ts | 23 +++++++++++++------ .../test/browser/codeActionModel.test.ts | 11 ++++----- .../test/browser/parameterHintsModel.test.ts | 22 +++++++----------- .../remote/common/remoteAgentConnection.ts | 12 ++++------ src/vs/server/node/remoteTerminalChannel.ts | 10 +++----- .../common/extHostExtensionActivator.test.ts | 11 ++++----- .../browser/view/renderers/webviewPreloads.ts | 16 +++++-------- .../tasks/browser/task.contribution.ts | 15 ++++++------ .../browser/terminalInstanceService.ts | 7 +++--- .../contrib/webview/browser/webviewElement.ts | 7 +++--- .../webviewView/browser/webviewViewService.ts | 8 +++---- .../common/newFile.contribution.ts | 6 ++--- .../common/extensionDescriptionRegistry.ts | 9 ++++---- .../extensions/common/workspaceContains.ts | 10 ++++---- .../workspaces/common/workspaceTrust.ts | 17 ++++++-------- 15 files changed, 82 insertions(+), 102 deletions(-) diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 2b12a493357..0bd6945a014 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -137,6 +137,21 @@ export function asPromise(callback: () => T | Thenable): Promise { }); } +/** + * Creates and returns a new promise, plus its `resolve` and `reject` callbacks. + * + * Replace with standardized [`Promise.withResolvers`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers) once it is supported + */ +export function promiseWithResolvers(): { promise: Promise; resolve: (value: T | PromiseLike) => void; reject: (err?: any) => void } { + let resolve: (value: T | PromiseLike) => void; + let reject: (reason?: any) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve: resolve!, reject: reject! }; +} + export interface ITask { (): T; } @@ -1467,13 +1482,7 @@ export class TaskSequentializer { // so that we can return a promise that completes when the task has // completed. if (!this._queued) { - let promiseResolve: () => void; - let promiseReject: (error: Error) => void; - const promise = new Promise((resolve, reject) => { - promiseResolve = resolve; - promiseReject = reject; - }); - + const { promise, resolve: promiseResolve, reject: promiseReject } = promiseWithResolvers(); this._queued = { run, promise, diff --git a/src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts b/src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts index 5205ab1a46b..71d6df33c0b 100644 --- a/src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts +++ b/src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { promiseWithResolvers } from 'vs/base/common/async'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -56,10 +57,8 @@ suite('CodeActionModel', () => { }); test('Oracle -> marker added', async () => { - let done: () => void; - const donePromise = new Promise(resolve => { - done = resolve; - }); + const { promise: donePromise, resolve: done } = promiseWithResolvers(); + await runWithFakedTimers({ useFakeTimers: true }, () => { const reg = registry.register(languageId, testProvider); disposables.add(reg); @@ -127,9 +126,7 @@ suite('CodeActionModel', () => { }); test('Oracle -> should only auto trigger once for cursor and marker update right after each other', async () => { - let done: () => void; - const donePromise = new Promise(resolve => { done = resolve; }); - + const { promise: donePromise, resolve: done } = promiseWithResolvers(); await runWithFakedTimers({ useFakeTimers: true }, () => { const reg = registry.register(languageId, testProvider); disposables.add(reg); diff --git a/src/vs/editor/contrib/parameterHints/test/browser/parameterHintsModel.test.ts b/src/vs/editor/contrib/parameterHints/test/browser/parameterHintsModel.test.ts index 013fa190f13..6c84215bc72 100644 --- a/src/vs/editor/contrib/parameterHints/test/browser/parameterHintsModel.test.ts +++ b/src/vs/editor/contrib/parameterHints/test/browser/parameterHintsModel.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { promiseWithResolvers } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -76,8 +77,7 @@ suite('ParameterHintsModel', () => { } test('Provider should get trigger character on type', async () => { - let done: () => void; - const donePromise = new Promise(resolve => { done = resolve; }); + const { promise: donePromise, resolve: done } = promiseWithResolvers(); const triggerChar = '('; @@ -103,8 +103,7 @@ suite('ParameterHintsModel', () => { }); test('Provider should be retriggered if already active', async () => { - let done: () => void; - const donePromise = new Promise(resolve => { done = resolve; }); + const { promise: donePromise, resolve: done } = promiseWithResolvers(); const triggerChar = '('; @@ -151,8 +150,7 @@ suite('ParameterHintsModel', () => { }); test('Provider should not be retriggered if previous help is canceled first', async () => { - let done: () => void; - const donePromise = new Promise(resolve => { done = resolve; }); + const { promise: donePromise, resolve: done } = promiseWithResolvers(); const triggerChar = '('; @@ -199,8 +197,7 @@ suite('ParameterHintsModel', () => { }); test('Provider should get last trigger character when triggered multiple times and only be invoked once', async () => { - let done: () => void; - const donePromise = new Promise(resolve => { done = resolve; }); + const { promise: donePromise, resolve: done } = promiseWithResolvers(); const editor = createMockEditor(''); disposables.add(new ParameterHintsModel(editor, registry, 5)); @@ -242,8 +239,7 @@ suite('ParameterHintsModel', () => { }); test('Provider should be retriggered if already active', async () => { - let done: () => void; - const donePromise = new Promise(resolve => { done = resolve; }); + const { promise: donePromise, resolve: done } = promiseWithResolvers(); const editor = createMockEditor(''); disposables.add(new ParameterHintsModel(editor, registry, 5)); @@ -351,8 +347,7 @@ suite('ParameterHintsModel', () => { }); test('Provider should be retriggered by retrigger character', async () => { - let done: () => void; - const donePromise = new Promise(resolve => { done = resolve; }); + const { promise: donePromise, resolve: done } = promiseWithResolvers(); const triggerChar = 'a'; const retriggerChar = 'b'; @@ -521,8 +516,7 @@ suite('ParameterHintsModel', () => { }); test('Retrigger while a pending resolve is still going on should preserve last active signature #96702', async () => { - let done: (r?: any) => void; - const donePromise = new Promise(resolve => { done = resolve; }); + const { promise: donePromise, resolve: done } = promiseWithResolvers(); const editor = createMockEditor(''); const model = disposables.add(new ParameterHintsModel(editor, registry, 50)); diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index 2951fc91f63..9539650dec0 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise, promiseWithResolvers } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { isCancellationError, onUnexpectedError } from 'vs/base/common/errors'; @@ -105,8 +105,8 @@ class PromiseWithTimeout { private _state: 'pending' | 'resolved' | 'rejected' | 'timedout'; private readonly _disposables: DisposableStore; public readonly promise: Promise; - private _resolvePromise!: (value: T) => void; - private _rejectPromise!: (err: any) => void; + private readonly _resolvePromise: (value: T) => void; + private readonly _rejectPromise: (err: any) => void; public get didTimeout(): boolean { return (this._state === 'timedout'); @@ -115,10 +115,8 @@ class PromiseWithTimeout { constructor(timeoutCancellationToken: CancellationToken) { this._state = 'pending'; this._disposables = new DisposableStore(); - this.promise = new Promise((resolve, reject) => { - this._resolvePromise = resolve; - this._rejectPromise = reject; - }); + + ({ promise: this.promise, resolve: this._resolvePromise, reject: this._rejectPromise } = promiseWithResolvers()); if (timeoutCancellationToken.isCancellationRequested) { this._timeout(); diff --git a/src/vs/server/node/remoteTerminalChannel.ts b/src/vs/server/node/remoteTerminalChannel.ts index 3b08dc6bbd9..caec44f7f3a 100644 --- a/src/vs/server/node/remoteTerminalChannel.ts +++ b/src/vs/server/node/remoteTerminalChannel.ts @@ -31,6 +31,7 @@ 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 { ILogService } from 'vs/platform/log/common/log'; +import { promiseWithResolvers } from 'vs/base/common/async'; class CustomVariableResolver extends AbstractVariableResolverService { constructor( @@ -266,12 +267,7 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel< } private _executeCommand(persistentProcessId: number, commandId: string, commandArgs: any[], uriTransformer: IURITransformer): Promise { - let resolve!: (data: any) => void; - let reject!: (err: any) => void; - const result = new Promise((_resolve, _reject) => { - resolve = _resolve; - reject = _reject; - }); + const { resolve, reject, promise } = promiseWithResolvers(); const reqId = ++this._lastReqId; this._pendingCommands.set(reqId, { resolve, reject, uriTransformer }); @@ -293,7 +289,7 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel< commandArgs: serializedCommandArgs }); - return result; + return promise; } private _sendCommandResult(reqId: number, isError: boolean, serializedPayload: any): void { diff --git a/src/vs/workbench/api/test/common/extHostExtensionActivator.test.ts b/src/vs/workbench/api/test/common/extHostExtensionActivator.test.ts index 535efb51ef7..6f37db49441 100644 --- a/src/vs/workbench/api/test/common/extHostExtensionActivator.test.ts +++ b/src/vs/workbench/api/test/common/extHostExtensionActivator.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { timeout } from 'vs/base/common/async'; +import { promiseWithResolvers, timeout } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier, IExtensionDescription, IRelaxedExtensionDescription, TargetPlatform } from 'vs/platform/extensions/common/extensions'; import { NullLogService } from 'vs/platform/log/common/log'; @@ -228,15 +228,12 @@ suite('ExtensionsActivator', () => { } class ExtensionActivationPromiseSource { - private _resolve!: (value: ActivatedExtension) => void; - private _reject!: (err: Error) => void; + private readonly _resolve: (value: ActivatedExtension) => void; + private readonly _reject: (err: Error) => void; public readonly promise: Promise; constructor() { - this.promise = new Promise((resolve, reject) => { - this._resolve = resolve; - this._reject = reject; - }); + ({ promise: this.promise, resolve: this._resolve, reject: this._reject } = promiseWithResolvers()); } public resolve(): void { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index b147a72091b..1e88981efdb 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import type * as DOM from 'vs/base/browser/window'; +import { promiseWithResolvers } from 'vs/base/common/async'; import type { Event } from 'vs/base/common/event'; import type { IDisposable } from 'vs/base/common/lifecycle'; import type * as webviewMessages from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages'; @@ -800,12 +801,11 @@ async function webviewPreloads(ctx: PreloadContext) { getOutputItem(outputId: string, mime: string) { const requestId = this._requestPool++; - let resolve: ((x: webviewMessages.OutputItemEntry | undefined) => void) | undefined; - const p = new Promise(r => resolve = r); - this._requests.set(requestId, { resolve: resolve! }); + const { promise, resolve } = promiseWithResolvers(); + this._requests.set(requestId, { resolve }); postNotebookMessage('getOutputItem', { requestId, outputId, mime }); - return p; + return promise; } resolveOutputItem(requestId: number, output: webviewMessages.OutputItemEntry | undefined) { @@ -2254,12 +2254,8 @@ async function webviewPreloads(ctx: PreloadContext) { this.id = id; this._content = { value: content, version: 0, metadata: metadata }; - let resolve: () => void; - let reject: () => void; - this.ready = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); + const { promise, resolve, reject } = promiseWithResolvers(); + this.ready = promise; let cachedData: { readonly version: number; readonly value: Uint8Array } | undefined; this.outputItem = Object.freeze({ diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 764209f2d01..11d92dc73f1 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -39,6 +39,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; import { TerminalMenuBarGroup } from 'vs/workbench/contrib/terminal/browser/terminalMenus'; import { isString } from 'vs/base/common/types'; +import { promiseWithResolvers } from 'vs/base/common/async'; const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(RunAutomaticTasks, LifecyclePhase.Eventually); @@ -68,7 +69,7 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench private _registerListeners(): void { let promise: Promise | undefined = undefined; - let resolver: (value?: void | Thenable) => void; + let resolve: (value?: void | Thenable) => void; this._taskService.onDidStateChange(event => { if (event.kind === TaskEventKind.Changed) { this._updateRunningTasksStatus(); @@ -80,9 +81,7 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench this._activeTasksCount++; if (this._activeTasksCount === 1) { if (!promise) { - promise = new Promise((resolve) => { - resolver = resolve; - }); + ({ promise, resolve } = promiseWithResolvers()); } } break; @@ -92,8 +91,8 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench if (this._activeTasksCount > 0) { this._activeTasksCount--; if (this._activeTasksCount === 0) { - if (promise && resolver!) { - resolver!(); + if (promise && resolve) { + resolve!(); } } } @@ -101,8 +100,8 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench case TaskEventKind.Terminated: if (this._activeTasksCount !== 0) { this._activeTasksCount = 0; - if (promise && resolver!) { - resolver!(); + if (promise && resolve) { + resolve!(); } } break; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts index 95c9a1b36ae..1dc0a7b24c1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts @@ -16,6 +16,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { promiseWithResolvers } from 'vs/base/common/async'; export class TerminalInstanceService extends Disposable implements ITerminalInstanceService { declare _serviceBrand: undefined; @@ -37,11 +38,9 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst this._terminalInRunCommandPicker = TerminalContextKeys.inTerminalRunCommandPicker.bindTo(this._contextKeyService); this._configHelper = _instantiationService.createInstance(TerminalConfigHelper); - for (const remoteAuthority of [undefined, _environmentService.remoteAuthority]) { - let resolve: () => void; - const p = new Promise(r => resolve = r); - this._backendRegistration.set(remoteAuthority, { promise: p, resolve: resolve! }); + const { promise, resolve } = promiseWithResolvers(); + this._backendRegistration.set(remoteAuthority, { promise, resolve }); } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 0cfe554c54e..641842dd0de 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -6,7 +6,7 @@ import { isFirefox } from 'vs/base/browser/browser'; import { addDisposableListener, EventType, getActiveWindow } from 'vs/base/browser/dom'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; -import { ThrottledDelayer } from 'vs/base/common/async'; +import { promiseWithResolvers, ThrottledDelayer } from 'vs/base/common/async'; import { streamToBuffer, VSBufferReadableStream } from 'vs/base/common/buffer'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; @@ -418,9 +418,8 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD private async _send(channel: K, data: ToWebviewMessage[K], _createElement: Transferable[] = []): Promise { if (this._state.type === WebviewState.Type.Initializing) { - let resolve: (x: boolean) => void; - const promise = new Promise(r => resolve = r); - this._state.pendingMessages.push({ channel, data, transferable: _createElement, resolve: resolve! }); + const { promise, resolve } = promiseWithResolvers(); + this._state.pendingMessages.push({ channel, data, transferable: _createElement, resolve }); return promise; } else { return this.doPostMessage(channel, data, _createElement); diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewService.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewService.ts index dcd13a7a683..6aef6db005b 100644 --- a/src/vs/workbench/contrib/webviewView/browser/webviewViewService.ts +++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { promiseWithResolvers } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -130,10 +131,9 @@ export class WebviewViewService extends Disposable implements IWebviewViewServic throw new Error('View already awaiting revival'); } - let resolve: () => void; - const p = new Promise(r => resolve = r); - this._awaitingRevival.set(viewType, { webview, resolve: resolve! }); - return p; + const { promise, resolve } = promiseWithResolvers(); + this._awaitingRevival.set(viewType, { webview, resolve }); + return promise; } return resolver.resolve(webview, cancellation); diff --git a/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts b/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts index beaa0363420..eae75a6e994 100644 --- a/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts +++ b/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { promiseWithResolvers } from 'vs/base/common/async'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { assertIsDefined } from 'vs/base/common/types'; @@ -95,10 +96,7 @@ class NewFileTemplatesManager extends Disposable { } private async selectNewEntry(entries: NewFileItem[]): Promise { - let resolveResult: (res: boolean) => void; - const resultPromise = new Promise(resolve => { - resolveResult = resolve; - }); + const { promise: resultPromise, resolve: resolveResult } = promiseWithResolvers(); const disposables = new DisposableStore(); const qp = this.quickInputService.createQuickPick(); diff --git a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts index c25c9a7ca47..f771e88f43a 100644 --- a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts @@ -7,6 +7,7 @@ import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IE import { Emitter } from 'vs/base/common/event'; import * as path from 'vs/base/common/path'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { promiseWithResolvers } from 'vs/base/common/async'; export class DeltaExtensionsResult { constructor( @@ -323,14 +324,14 @@ export class ExtensionDescriptionRegistryLock extends Disposable { class LockCustomer { public readonly promise: Promise; - private _resolve!: (value: IDisposable) => void; + private readonly _resolve: (value: IDisposable) => void; constructor( public readonly name: string ) { - this.promise = new Promise((resolve, reject) => { - this._resolve = resolve; - }); + const withResolvers = promiseWithResolvers(); + this.promise = withResolvers.promise; + this._resolve = withResolvers.resolve; } resolve(value: IDisposable): void { diff --git a/src/vs/workbench/services/extensions/common/workspaceContains.ts b/src/vs/workbench/services/extensions/common/workspaceContains.ts index d6b4b676d22..cc2c6917eb7 100644 --- a/src/vs/workbench/services/extensions/common/workspaceContains.ts +++ b/src/vs/workbench/services/extensions/common/workspaceContains.ts @@ -13,6 +13,7 @@ import { QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder'; import { ISearchService } from 'vs/workbench/services/search/common/search'; import { toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ILogService } from 'vs/platform/log/common/log'; +import { promiseWithResolvers } from 'vs/base/common/async'; const WORKSPACE_CONTAINS_TIMEOUT = 7000; @@ -53,19 +54,18 @@ export function checkActivateWorkspaceContainsExtension(host: IExtensionActivati return Promise.resolve(undefined); } - let resolveResult: (value: IExtensionActivationResult | undefined) => void; - const result = new Promise((resolve, reject) => { resolveResult = resolve; }); - const activate = (activationEvent: string) => resolveResult({ activationEvent }); + const { promise, resolve } = promiseWithResolvers(); + const activate = (activationEvent: string) => resolve({ activationEvent }); const fileNamePromise = Promise.all(fileNames.map((fileName) => _activateIfFileName(host, fileName, activate))).then(() => { }); const globPatternPromise = _activateIfGlobPatterns(host, desc.identifier, globPatterns, activate); Promise.all([fileNamePromise, globPatternPromise]).then(() => { // when all are done, resolve with undefined (relevant only if it was not activated so far) - resolveResult(undefined); + resolve(undefined); }); - return result; + return promise; } async function _activateIfFileName(host: IExtensionActivationHost, fileName: string, activate: (activationEvent: string) => void): Promise { diff --git a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts index 1eba278c943..0c786c3628b 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts @@ -23,6 +23,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { isEqualAuthority } from 'vs/base/common/resources'; import { isWeb } from 'vs/base/common/platform'; import { IFileService } from 'vs/platform/files/common/files'; +import { promiseWithResolvers } from 'vs/base/common/async'; export const WORKSPACE_TRUST_ENABLED = 'security.workspace.trust.enabled'; export const WORKSPACE_TRUST_STARTUP_PROMPT = 'security.workspace.trust.startupPrompt'; @@ -90,10 +91,10 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork private readonly storageKey = WORKSPACE_TRUST_STORAGE_KEY; - private _workspaceResolvedPromise: Promise; - private _workspaceResolvedPromiseResolve!: () => void; - private _workspaceTrustInitializedPromise: Promise; - private _workspaceTrustInitializedPromiseResolve!: () => void; + private readonly _workspaceResolvedPromise: Promise; + private readonly _workspaceResolvedPromiseResolve: () => void; + private readonly _workspaceTrustInitializedPromise: Promise; + private readonly _workspaceTrustInitializedPromiseResolve: () => void; private readonly _onDidChangeTrust = this._register(new Emitter()); readonly onDidChangeTrust = this._onDidChangeTrust.event; @@ -127,12 +128,8 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork this._canonicalUrisResolved = false; this._canonicalWorkspace = this.workspaceService.getWorkspace(); - this._workspaceResolvedPromise = new Promise((resolve) => { - this._workspaceResolvedPromiseResolve = resolve; - }); - this._workspaceTrustInitializedPromise = new Promise((resolve) => { - this._workspaceTrustInitializedPromiseResolve = resolve; - }); + ({ promise: this._workspaceResolvedPromise, resolve: this._workspaceResolvedPromiseResolve } = promiseWithResolvers()); + ({ promise: this._workspaceTrustInitializedPromise, resolve: this._workspaceTrustInitializedPromiseResolve } = promiseWithResolvers()); this._storedTrustState = new WorkspaceTrustMemento(isWeb && this.isEmptyWorkspace() ? undefined : this.storageService); this._trustTransitionManager = this._register(new WorkspaceTrustTransitionManager()); From c9f0aa5b7303c9f60cae5889a1f04f0be56d333f Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 30 Jan 2024 11:37:56 -0800 Subject: [PATCH 0745/1897] fix: rerender welcome view when chat provider is disposed (#203740) --- src/vs/workbench/contrib/chat/browser/chatViewPane.ts | 11 ++++++++++- src/vs/workbench/contrib/chat/common/chatService.ts | 1 + .../workbench/contrib/chat/common/chatServiceImpl.ts | 4 ++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index f8f7aca63de..fabf8073013 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -45,6 +45,7 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { private memento: Memento; private readonly viewState: IViewPaneState; private didProviderRegistrationFail = false; + private didUnregisterProvider = false; constructor( private readonly chatViewOptions: IChatViewOptions, @@ -78,12 +79,20 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { try { this._widget.setVisible(false); this.updateModel(model); + this.didProviderRegistrationFail = false; + this.didUnregisterProvider = false; this._onDidChangeViewWelcomeState.fire(); } finally { this.widget.setVisible(true); } } })); + this._register(this.chatService.onDidUnregisterProvider(({ providerId }) => { + if (providerId === this.chatViewOptions.providerId) { + this.didUnregisterProvider = true; + this._onDidChangeViewWelcomeState.fire(); + } + })); } private updateModel(model?: IChatModel | undefined, viewState?: IViewPaneState): void { @@ -102,7 +111,7 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { override shouldShowWelcome(): boolean { const noPersistedSessions = !this.chatService.hasSessions(this.chatViewOptions.providerId); - return !this._widget?.viewModel && (noPersistedSessions || this.didProviderRegistrationFail); + return this.didUnregisterProvider || !this._widget?.viewModel && (noPersistedSessions || this.didProviderRegistrationFail); } private getSessionId() { diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 3bc59a462ab..52f6bcceb6d 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -285,6 +285,7 @@ export interface IChatService { onDidSubmitAgent: Event<{ agent: IChatAgentData; slashCommand: IChatAgentCommand; sessionId: string }>; onDidRegisterProvider: Event<{ providerId: string }>; + onDidUnregisterProvider: Event<{ providerId: string }>; registerProvider(provider: IChatProvider): IDisposable; hasSessions(providerId: string): boolean; getProviderInfos(): IChatProviderInfo[]; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 77db6de1d07..b2f83d74ec6 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -155,6 +155,9 @@ export class ChatService extends Disposable implements IChatService { private readonly _onDidRegisterProvider = this._register(new Emitter<{ providerId: string }>()); public readonly onDidRegisterProvider = this._onDidRegisterProvider.event; + private readonly _onDidUnregisterProvider = this._register(new Emitter<{ providerId: string }>()); + public readonly onDidUnregisterProvider = this._onDidUnregisterProvider.event; + constructor( @IStorageService private readonly storageService: IStorageService, @ILogService private readonly logService: ILogService, @@ -718,6 +721,7 @@ export class ChatService extends Disposable implements IChatService { Array.from(this._sessionModels.values()) .filter(model => model.providerId === provider.id) .forEach(model => model.deinitialize()); + this._onDidUnregisterProvider.fire({ providerId: provider.id }); }); } From e13013289e630812714c8c60e626fa782d90b4a6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 30 Jan 2024 12:02:53 -0800 Subject: [PATCH 0746/1897] Update xterm Fixes #200428 --- package.json | 16 +++++------ remote/package.json | 16 +++++------ remote/web/package.json | 14 ++++----- remote/web/yarn.lock | 56 ++++++++++++++++++------------------ remote/yarn.lock | 64 ++++++++++++++++++++--------------------- yarn.lock | 64 ++++++++++++++++++++--------------------- 6 files changed, 115 insertions(+), 115 deletions(-) diff --git a/package.json b/package.json index 71f3fb7ae95..cf5c8b43d3b 100644 --- a/package.json +++ b/package.json @@ -80,14 +80,14 @@ "@vscode/windows-mutex": "^0.4.4", "@vscode/windows-process-tree": "^0.5.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-canvas": "0.6.0-beta.20", - "@xterm/addon-image": "0.7.0-beta.18", - "@xterm/addon-search": "0.14.0-beta.20", - "@xterm/addon-serialize": "0.12.0-beta.20", - "@xterm/addon-unicode11": "0.7.0-beta.20", - "@xterm/addon-webgl": "0.17.0-beta.20", - "@xterm/headless": "5.4.0-beta.20", - "@xterm/xterm": "5.4.0-beta.20", + "@xterm/addon-canvas": "0.6.0-beta.30", + "@xterm/addon-image": "0.7.0-beta.28", + "@xterm/addon-search": "0.14.0-beta.30", + "@xterm/addon-serialize": "0.12.0-beta.30", + "@xterm/addon-unicode11": "0.7.0-beta.30", + "@xterm/addon-webgl": "0.17.0-beta.30", + "@xterm/headless": "5.4.0-beta.30", + "@xterm/xterm": "5.4.0-beta.30", "graceful-fs": "4.2.11", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", diff --git a/remote/package.json b/remote/package.json index b1fbc7b435c..7a6c7145324 100644 --- a/remote/package.json +++ b/remote/package.json @@ -13,14 +13,14 @@ "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.5.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-canvas": "0.6.0-beta.20", - "@xterm/addon-image": "0.7.0-beta.18", - "@xterm/addon-search": "0.14.0-beta.20", - "@xterm/addon-serialize": "0.12.0-beta.20", - "@xterm/addon-unicode11": "0.7.0-beta.20", - "@xterm/addon-webgl": "0.17.0-beta.20", - "@xterm/headless": "5.4.0-beta.20", - "@xterm/xterm": "5.4.0-beta.20", + "@xterm/addon-canvas": "0.6.0-beta.30", + "@xterm/addon-image": "0.7.0-beta.28", + "@xterm/addon-search": "0.14.0-beta.30", + "@xterm/addon-serialize": "0.12.0-beta.30", + "@xterm/addon-unicode11": "0.7.0-beta.30", + "@xterm/addon-webgl": "0.17.0-beta.30", + "@xterm/headless": "5.4.0-beta.30", + "@xterm/xterm": "5.4.0-beta.30", "cookie": "^0.4.0", "graceful-fs": "4.2.11", "http-proxy-agent": "^7.0.0", diff --git a/remote/web/package.json b/remote/web/package.json index 8bedb5c4f10..c24d8c77df8 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -7,13 +7,13 @@ "@microsoft/1ds-post-js": "^3.2.13", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-canvas": "0.6.0-beta.20", - "@xterm/addon-image": "0.7.0-beta.18", - "@xterm/addon-search": "0.14.0-beta.20", - "@xterm/addon-serialize": "0.12.0-beta.20", - "@xterm/addon-unicode11": "0.7.0-beta.20", - "@xterm/addon-webgl": "0.17.0-beta.20", - "@xterm/xterm": "5.4.0-beta.20", + "@xterm/addon-canvas": "0.6.0-beta.30", + "@xterm/addon-image": "0.7.0-beta.28", + "@xterm/addon-search": "0.14.0-beta.30", + "@xterm/addon-serialize": "0.12.0-beta.30", + "@xterm/addon-unicode11": "0.7.0-beta.30", + "@xterm/addon-webgl": "0.17.0-beta.30", + "@xterm/xterm": "5.4.0-beta.30", "jschardet": "3.0.0", "tas-client-umd": "0.1.8", "vscode-oniguruma": "1.7.0", diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index f540fcee68f..e6f9e15bd03 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -48,40 +48,40 @@ resolved "https://registry.yarnpkg.com/@vscode/vscode-languagedetection/-/vscode-languagedetection-1.0.21.tgz#89b48f293f6aa3341bb888c1118d16ff13b032d3" integrity sha512-zSUH9HYCw5qsCtd7b31yqkpaCU6jhtkKLkvOOA8yTrIRfBSOFb8PPhgmMicD7B/m+t4PwOJXzU1XDtrM9Fd3/g== -"@xterm/addon-canvas@0.6.0-beta.20": - version "0.6.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.20.tgz#078dddef70caf880b2cb121fdda37d301fc13156" - integrity sha512-tHhsuqElE7LNiDJPbZzgVpmbcG2Dk6i2vh1EI+DzSByUWScDqLoeJbVPE5Xd2UW2garo24lxErpnIAlsytcA3A== +"@xterm/addon-canvas@0.6.0-beta.30": + version "0.6.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.30.tgz#71139253d6ba037a2a6f26895c6e45363d9f496f" + integrity sha512-rDnIgtxOrRxwDiTHec3k0+K9PVeGR79RM7kjuOd39JEHRiH9JqPjwLHLAuN4RkCDh3fHbK/rTMYe73uWj5FvBw== -"@xterm/addon-image@0.7.0-beta.18": - version "0.7.0-beta.18" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.18.tgz#588ea2d0841cff48c63bde1bfcdf56e9494dc6af" - integrity sha512-+HQ+IBmHPelzjRJ5zO3XkjbeQNr2Zrf5wAlbPhy4EGSD0mDCqHJSfzZ8wKrhx7t8qpfiA8eTpWu/M76WsEnlnA== +"@xterm/addon-image@0.7.0-beta.28": + version "0.7.0-beta.28" + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.28.tgz#c06fe0c581e5c32b71a56b8c31b16580a596ae50" + integrity sha512-EIw+8vQNrl5i9rN+crc4DmYLcN66zdED1pEppShAFyQjwT+yLigbcF4QQa00eeq8xzG4NEnQbRb5R+4ZR1/G1Q== -"@xterm/addon-search@0.14.0-beta.20": - version "0.14.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.20.tgz#cac366b1be1eb02cf9fe9537933f26f227d030c8" - integrity sha512-1LOL/OzWSrCBpndiBeeE2S1rxtKKgU1ucYFSG3P68W0J4VQz/Ksci1BgDKsgspj9jzpsGhdql3zwa5WEM7n4Pg== +"@xterm/addon-search@0.14.0-beta.30": + version "0.14.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.30.tgz#313a44258121ed30205e18170a2865e0a432f725" + integrity sha512-x4bjV++cWWHg+HZ+WyOw76AjOe+lvh/rQF7XF1wO40ROLDSR9jBUH1oN9uR4Yr4XxxFgXLEdpswMhRYKrSJG/A== -"@xterm/addon-serialize@0.12.0-beta.20": - version "0.12.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.20.tgz#5fe126194ff4dc466b92a0946e081e039a14ad21" - integrity sha512-GdRCQDjLyVNBxCFnhfCWsMmuqv2PryUkOaNl4z5MqB5lBUkiEnRNY0u/s5f34+2zrijp3h0O/f9JDLW4gSUQgw== +"@xterm/addon-serialize@0.12.0-beta.30": + version "0.12.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.30.tgz#3472fa8cbf7d43603da558fe5b95bbeee7f7349f" + integrity sha512-AQC3Yf0Ld5uURJkMak/pJGsuSaajt1hdKabFPz7MYtnCy5KHsY97yIGFkUPGYKqNg6nT7gOCS2P/tCGjDaKOqA== -"@xterm/addon-unicode11@0.7.0-beta.20": - version "0.7.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.20.tgz#5d3c97320898dd6766f2dc127deb4f071c8698c2" - integrity sha512-4/uwJ6lV/xJplT7hJc7sO4Im4XNvEXHnUEFIs03FFp8ZUfu3U6wcBk6/GoKMwJKJtGVNxotiD6ZzJ5v8IBH6nA== +"@xterm/addon-unicode11@0.7.0-beta.30": + version "0.7.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.30.tgz#49e0caebed508f8d684363c48c09da5cb1d06619" + integrity sha512-FzN+L2rvXAwsjGs+UyySPz8e6EvgZNQImX63XqUOIvPnpLWkFLc+ket4n8Bu0MwmZg99atqxepEGNpXVDRPY9A== -"@xterm/addon-webgl@0.17.0-beta.20": - version "0.17.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.20.tgz#443845ac5ac755cf762b105ed237b30426b07137" - integrity sha512-iqvXNSTfKIcO9FBraNwdO/ixPrTHok8CBN/wjlnGLv0ZMc4zLAiKE8+PHyg9ZY38QJfS+4Ouo8KsuZwoOYfnNA== +"@xterm/addon-webgl@0.17.0-beta.30": + version "0.17.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.30.tgz#159fa6bfa8e68f37c54a9a3fbf505d389f6c9374" + integrity sha512-n/4ncwUXc6rRyYA+KDVv5e/VnF/OCo4bFqycWf6wNFSEvD1AJavHm/CHi//p9rPC5gLzulKuTJdOj4jQ5St+Yg== -"@xterm/xterm@5.4.0-beta.20": - version "5.4.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.20.tgz#28bbbbc73eceb6ef3e1e095de195cf849d0cbfb6" - integrity sha512-nkY91qBy5pe1HlW9LOoLcyG6v4teEsliEtUVshAO42NrJDaPniSn28O5m5832UjZOdjLCY58QlcBkZUquODGrQ== +"@xterm/xterm@5.4.0-beta.30": + version "5.4.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.30.tgz#79d913f0ca0c2cd357e5f3f938d48a99d0ee4e5c" + integrity sha512-ShLgCpnA9WH4EMcasJYuWoUCGu5iHp0aZNsu5L/dw16CJZ7S1lkPg2dcVu3m/agOUZDJkaHiSG7j6V+P9ejvQQ== jschardet@3.0.0: version "3.0.0" diff --git a/remote/yarn.lock b/remote/yarn.lock index bb20531f409..26ae24f2518 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -114,45 +114,45 @@ resolved "https://registry.yarnpkg.com/@vscode/windows-registry/-/windows-registry-1.1.0.tgz#03dace7c29c46f658588b9885b9580e453ad21f9" integrity sha512-5AZzuWJpGscyiMOed0IuyEwt6iKmV5Us7zuwCDCFYMIq7tsvooO9BUiciywsvuthGz6UG4LSpeDeCxvgMVhnIw== -"@xterm/addon-canvas@0.6.0-beta.20": - version "0.6.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.20.tgz#078dddef70caf880b2cb121fdda37d301fc13156" - integrity sha512-tHhsuqElE7LNiDJPbZzgVpmbcG2Dk6i2vh1EI+DzSByUWScDqLoeJbVPE5Xd2UW2garo24lxErpnIAlsytcA3A== +"@xterm/addon-canvas@0.6.0-beta.30": + version "0.6.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.30.tgz#71139253d6ba037a2a6f26895c6e45363d9f496f" + integrity sha512-rDnIgtxOrRxwDiTHec3k0+K9PVeGR79RM7kjuOd39JEHRiH9JqPjwLHLAuN4RkCDh3fHbK/rTMYe73uWj5FvBw== -"@xterm/addon-image@0.7.0-beta.18": - version "0.7.0-beta.18" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.18.tgz#588ea2d0841cff48c63bde1bfcdf56e9494dc6af" - integrity sha512-+HQ+IBmHPelzjRJ5zO3XkjbeQNr2Zrf5wAlbPhy4EGSD0mDCqHJSfzZ8wKrhx7t8qpfiA8eTpWu/M76WsEnlnA== +"@xterm/addon-image@0.7.0-beta.28": + version "0.7.0-beta.28" + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.28.tgz#c06fe0c581e5c32b71a56b8c31b16580a596ae50" + integrity sha512-EIw+8vQNrl5i9rN+crc4DmYLcN66zdED1pEppShAFyQjwT+yLigbcF4QQa00eeq8xzG4NEnQbRb5R+4ZR1/G1Q== -"@xterm/addon-search@0.14.0-beta.20": - version "0.14.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.20.tgz#cac366b1be1eb02cf9fe9537933f26f227d030c8" - integrity sha512-1LOL/OzWSrCBpndiBeeE2S1rxtKKgU1ucYFSG3P68W0J4VQz/Ksci1BgDKsgspj9jzpsGhdql3zwa5WEM7n4Pg== +"@xterm/addon-search@0.14.0-beta.30": + version "0.14.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.30.tgz#313a44258121ed30205e18170a2865e0a432f725" + integrity sha512-x4bjV++cWWHg+HZ+WyOw76AjOe+lvh/rQF7XF1wO40ROLDSR9jBUH1oN9uR4Yr4XxxFgXLEdpswMhRYKrSJG/A== -"@xterm/addon-serialize@0.12.0-beta.20": - version "0.12.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.20.tgz#5fe126194ff4dc466b92a0946e081e039a14ad21" - integrity sha512-GdRCQDjLyVNBxCFnhfCWsMmuqv2PryUkOaNl4z5MqB5lBUkiEnRNY0u/s5f34+2zrijp3h0O/f9JDLW4gSUQgw== +"@xterm/addon-serialize@0.12.0-beta.30": + version "0.12.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.30.tgz#3472fa8cbf7d43603da558fe5b95bbeee7f7349f" + integrity sha512-AQC3Yf0Ld5uURJkMak/pJGsuSaajt1hdKabFPz7MYtnCy5KHsY97yIGFkUPGYKqNg6nT7gOCS2P/tCGjDaKOqA== -"@xterm/addon-unicode11@0.7.0-beta.20": - version "0.7.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.20.tgz#5d3c97320898dd6766f2dc127deb4f071c8698c2" - integrity sha512-4/uwJ6lV/xJplT7hJc7sO4Im4XNvEXHnUEFIs03FFp8ZUfu3U6wcBk6/GoKMwJKJtGVNxotiD6ZzJ5v8IBH6nA== +"@xterm/addon-unicode11@0.7.0-beta.30": + version "0.7.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.30.tgz#49e0caebed508f8d684363c48c09da5cb1d06619" + integrity sha512-FzN+L2rvXAwsjGs+UyySPz8e6EvgZNQImX63XqUOIvPnpLWkFLc+ket4n8Bu0MwmZg99atqxepEGNpXVDRPY9A== -"@xterm/addon-webgl@0.17.0-beta.20": - version "0.17.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.20.tgz#443845ac5ac755cf762b105ed237b30426b07137" - integrity sha512-iqvXNSTfKIcO9FBraNwdO/ixPrTHok8CBN/wjlnGLv0ZMc4zLAiKE8+PHyg9ZY38QJfS+4Ouo8KsuZwoOYfnNA== +"@xterm/addon-webgl@0.17.0-beta.30": + version "0.17.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.30.tgz#159fa6bfa8e68f37c54a9a3fbf505d389f6c9374" + integrity sha512-n/4ncwUXc6rRyYA+KDVv5e/VnF/OCo4bFqycWf6wNFSEvD1AJavHm/CHi//p9rPC5gLzulKuTJdOj4jQ5St+Yg== -"@xterm/headless@5.4.0-beta.20": - version "5.4.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.20.tgz#af26d3d0e2cdd615ccfac4a8660181fee19898fd" - integrity sha512-H/as1d67J43/CB8xt1Yg/eJMbq1yopwG1bDBKdsf2ro8A1PmJFXNzaDB+wSgoH42fCusSpLJvXtUvDLtqfvBTg== +"@xterm/headless@5.4.0-beta.30": + version "5.4.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.30.tgz#be819f4253083bf104aa6fb0d21d57dd9ef6adb4" + integrity sha512-2gHdJ5NUsFZyFS8H8X+98I7NUw9cYR2+ey7lLsFvfhd1gv8eqiCPGamiWDN1sZ/fbZf+cx39h/c+AaWlLlGuBA== -"@xterm/xterm@5.4.0-beta.20": - version "5.4.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.20.tgz#28bbbbc73eceb6ef3e1e095de195cf849d0cbfb6" - integrity sha512-nkY91qBy5pe1HlW9LOoLcyG6v4teEsliEtUVshAO42NrJDaPniSn28O5m5832UjZOdjLCY58QlcBkZUquODGrQ== +"@xterm/xterm@5.4.0-beta.30": + version "5.4.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.30.tgz#79d913f0ca0c2cd357e5f3f938d48a99d0ee4e5c" + integrity sha512-ShLgCpnA9WH4EMcasJYuWoUCGu5iHp0aZNsu5L/dw16CJZ7S1lkPg2dcVu3m/agOUZDJkaHiSG7j6V+P9ejvQQ== agent-base@^7.0.1, agent-base@^7.0.2, agent-base@^7.1.0: version "7.1.0" diff --git a/yarn.lock b/yarn.lock index 6c82081f225..05a9854b9a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1668,45 +1668,45 @@ resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.1.tgz#34bdc31727a1889198855913db2f270ace6d7bf8" integrity sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw== -"@xterm/addon-canvas@0.6.0-beta.20": - version "0.6.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.20.tgz#078dddef70caf880b2cb121fdda37d301fc13156" - integrity sha512-tHhsuqElE7LNiDJPbZzgVpmbcG2Dk6i2vh1EI+DzSByUWScDqLoeJbVPE5Xd2UW2garo24lxErpnIAlsytcA3A== +"@xterm/addon-canvas@0.6.0-beta.30": + version "0.6.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.30.tgz#71139253d6ba037a2a6f26895c6e45363d9f496f" + integrity sha512-rDnIgtxOrRxwDiTHec3k0+K9PVeGR79RM7kjuOd39JEHRiH9JqPjwLHLAuN4RkCDh3fHbK/rTMYe73uWj5FvBw== -"@xterm/addon-image@0.7.0-beta.18": - version "0.7.0-beta.18" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.18.tgz#588ea2d0841cff48c63bde1bfcdf56e9494dc6af" - integrity sha512-+HQ+IBmHPelzjRJ5zO3XkjbeQNr2Zrf5wAlbPhy4EGSD0mDCqHJSfzZ8wKrhx7t8qpfiA8eTpWu/M76WsEnlnA== +"@xterm/addon-image@0.7.0-beta.28": + version "0.7.0-beta.28" + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.28.tgz#c06fe0c581e5c32b71a56b8c31b16580a596ae50" + integrity sha512-EIw+8vQNrl5i9rN+crc4DmYLcN66zdED1pEppShAFyQjwT+yLigbcF4QQa00eeq8xzG4NEnQbRb5R+4ZR1/G1Q== -"@xterm/addon-search@0.14.0-beta.20": - version "0.14.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.20.tgz#cac366b1be1eb02cf9fe9537933f26f227d030c8" - integrity sha512-1LOL/OzWSrCBpndiBeeE2S1rxtKKgU1ucYFSG3P68W0J4VQz/Ksci1BgDKsgspj9jzpsGhdql3zwa5WEM7n4Pg== +"@xterm/addon-search@0.14.0-beta.30": + version "0.14.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.30.tgz#313a44258121ed30205e18170a2865e0a432f725" + integrity sha512-x4bjV++cWWHg+HZ+WyOw76AjOe+lvh/rQF7XF1wO40ROLDSR9jBUH1oN9uR4Yr4XxxFgXLEdpswMhRYKrSJG/A== -"@xterm/addon-serialize@0.12.0-beta.20": - version "0.12.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.20.tgz#5fe126194ff4dc466b92a0946e081e039a14ad21" - integrity sha512-GdRCQDjLyVNBxCFnhfCWsMmuqv2PryUkOaNl4z5MqB5lBUkiEnRNY0u/s5f34+2zrijp3h0O/f9JDLW4gSUQgw== +"@xterm/addon-serialize@0.12.0-beta.30": + version "0.12.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.30.tgz#3472fa8cbf7d43603da558fe5b95bbeee7f7349f" + integrity sha512-AQC3Yf0Ld5uURJkMak/pJGsuSaajt1hdKabFPz7MYtnCy5KHsY97yIGFkUPGYKqNg6nT7gOCS2P/tCGjDaKOqA== -"@xterm/addon-unicode11@0.7.0-beta.20": - version "0.7.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.20.tgz#5d3c97320898dd6766f2dc127deb4f071c8698c2" - integrity sha512-4/uwJ6lV/xJplT7hJc7sO4Im4XNvEXHnUEFIs03FFp8ZUfu3U6wcBk6/GoKMwJKJtGVNxotiD6ZzJ5v8IBH6nA== +"@xterm/addon-unicode11@0.7.0-beta.30": + version "0.7.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.30.tgz#49e0caebed508f8d684363c48c09da5cb1d06619" + integrity sha512-FzN+L2rvXAwsjGs+UyySPz8e6EvgZNQImX63XqUOIvPnpLWkFLc+ket4n8Bu0MwmZg99atqxepEGNpXVDRPY9A== -"@xterm/addon-webgl@0.17.0-beta.20": - version "0.17.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.20.tgz#443845ac5ac755cf762b105ed237b30426b07137" - integrity sha512-iqvXNSTfKIcO9FBraNwdO/ixPrTHok8CBN/wjlnGLv0ZMc4zLAiKE8+PHyg9ZY38QJfS+4Ouo8KsuZwoOYfnNA== +"@xterm/addon-webgl@0.17.0-beta.30": + version "0.17.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.30.tgz#159fa6bfa8e68f37c54a9a3fbf505d389f6c9374" + integrity sha512-n/4ncwUXc6rRyYA+KDVv5e/VnF/OCo4bFqycWf6wNFSEvD1AJavHm/CHi//p9rPC5gLzulKuTJdOj4jQ5St+Yg== -"@xterm/headless@5.4.0-beta.20": - version "5.4.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.20.tgz#af26d3d0e2cdd615ccfac4a8660181fee19898fd" - integrity sha512-H/as1d67J43/CB8xt1Yg/eJMbq1yopwG1bDBKdsf2ro8A1PmJFXNzaDB+wSgoH42fCusSpLJvXtUvDLtqfvBTg== +"@xterm/headless@5.4.0-beta.30": + version "5.4.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.30.tgz#be819f4253083bf104aa6fb0d21d57dd9ef6adb4" + integrity sha512-2gHdJ5NUsFZyFS8H8X+98I7NUw9cYR2+ey7lLsFvfhd1gv8eqiCPGamiWDN1sZ/fbZf+cx39h/c+AaWlLlGuBA== -"@xterm/xterm@5.4.0-beta.20": - version "5.4.0-beta.20" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.20.tgz#28bbbbc73eceb6ef3e1e095de195cf849d0cbfb6" - integrity sha512-nkY91qBy5pe1HlW9LOoLcyG6v4teEsliEtUVshAO42NrJDaPniSn28O5m5832UjZOdjLCY58QlcBkZUquODGrQ== +"@xterm/xterm@5.4.0-beta.30": + version "5.4.0-beta.30" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.30.tgz#79d913f0ca0c2cd357e5f3f938d48a99d0ee4e5c" + integrity sha512-ShLgCpnA9WH4EMcasJYuWoUCGu5iHp0aZNsu5L/dw16CJZ7S1lkPg2dcVu3m/agOUZDJkaHiSG7j6V+P9ejvQQ== "@xtuc/ieee754@^1.2.0": version "1.2.0" From 531cc0ee6241c79a38f38896b94c7aeb8931a9e5 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 30 Jan 2024 20:31:38 +0000 Subject: [PATCH 0747/1897] Don't accidentally create InlineChatFileCreatePreviewWidget when hiding it (#203830) --- .../contrib/inlineChat/browser/inlineChatController.ts | 2 +- .../contrib/inlineChat/browser/inlineChatStrategies.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index d0ff5ed48e5..8b140227cdd 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -924,7 +924,7 @@ export class InlineChatController implements IEditorContribution { this._ctxLastFeedbackKind.reset(); this._ctxSupportIssueReporting.reset(); - this._zone.value.hide(); + this._zone.rawValue?.hide(); // Return focus to the editor only if the current focus is within the editor widget if (this._editor.hasWidgetFocus()) { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index ea6a747f627..56777b09ba2 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -287,7 +287,7 @@ export class LivePreviewStrategy extends EditModeStrategy { if (response.untitledTextModel && !response.untitledTextModel.isDisposed()) { this._previewZone.value.showCreation(this._session.wholeRange.value.getStartPosition().delta(-1), response.untitledTextModel); } else { - this._previewZone.value.hide(); + this._previewZone.rawValue?.hide(); } return this._updateDiffZones(); @@ -526,7 +526,7 @@ export class LiveStrategy extends EditModeStrategy { if (response.untitledTextModel && !response.untitledTextModel.isDisposed()) { this._previewZone.value.showCreation(this._session.wholeRange.value.getStartPosition().delta(-1), response.untitledTextModel); } else { - this._previewZone.value.hide(); + this._previewZone.rawValue?.hide(); } this._progressiveEditingDecorations.clear(); From dd2038279d17ca6c9b21619dfe34a25991145540 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 30 Jan 2024 21:54:36 +0100 Subject: [PATCH 0748/1897] Git - fix repository deduplication logic (#203836) --- extensions/git/src/model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index ccab4e07857..b7dc0fe35fb 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -788,7 +788,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi // Use the repository real path const repoPathRealPath = await fs.promises.realpath(repoPath, { encoding: 'utf8' }); const openRepositoryRealPath = this.openRepositories - .find(r => pathEquals(r.repository.rootRealPath ?? '', repoPathRealPath)); + .find(r => pathEquals(r.repository.rootRealPath ?? r.repository.root, repoPathRealPath)); return openRepositoryRealPath?.repository; } catch (err) { From 32150ac72e29e5c6915b02efa02abbbab7c1636c Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Tue, 30 Jan 2024 12:55:39 -0800 Subject: [PATCH 0749/1897] Announce checkbox status in walkthrough step (#203835) --- .../contrib/welcomeGettingStarted/browser/gettingStarted.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 8f8e501e677..8ff5cabc4cb 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -1440,6 +1440,7 @@ export class GettingStartedPage extends EditorPane { 'x-dispatch': 'toggleStepCompletion:' + step.id, 'role': 'checkbox', 'tabindex': '0', + 'aria-checked': step.done ? 'true' : 'false' }); const container = $('.step-description-container', { 'x-step-description-for': step.id }); From 8cf2750bb6589710d22066e113ad59f846aebfe8 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Tue, 30 Jan 2024 14:05:44 -0800 Subject: [PATCH 0750/1897] Fix persistent syntax highlighting in split windows (#203837) fix splitview highlighting #198866 --- .../wordHighlighter/browser/wordHighlighter.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts index 7824f8a6ea0..949ef715d83 100644 --- a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts @@ -429,6 +429,7 @@ class WordHighlighter { private _removeAllDecorations(): void { const currentEditors = this.codeEditorService.listCodeEditors(); + const deleteURI = []; // iterate over editors and store models in currentModels for (const editor of currentEditors) { if (!editor.hasModel()) { @@ -441,7 +442,7 @@ class WordHighlighter { } editor.removeDecorations(currentDecorationIDs); - WordHighlighter.storedDecorations.delete(editor.getModel().uri); + deleteURI.push(editor.getModel().uri); const editorHighlighterContrib = WordHighlighterContribution.get(editor); if (!editorHighlighterContrib?.wordHighlighter) { @@ -453,13 +454,17 @@ class WordHighlighter { editorHighlighterContrib.wordHighlighter._hasWordHighlights.set(false); } } + + for (const uri of deleteURI) { + WordHighlighter.storedDecorations.delete(uri); + } } private _stopSingular(): void { // Remove any existing decorations + a possible query, and re - run to update decorations this._removeSingleDecorations(); - if (this.editor.hasWidgetFocus()) { + if (this.editor.hasTextFocus()) { if (this.editor.getModel()?.uri.scheme !== Schemas.vscodeNotebookCell && WordHighlighter.query?.modelInfo?.model.uri.scheme !== Schemas.vscodeNotebookCell) { // clear query if focused non-nb editor WordHighlighter.query = null; this._run(); @@ -489,9 +494,10 @@ class WordHighlighter { } } - private _stopAll(): void { + private _stopAll() { // Remove any existing decorations - // TODO: @Yoyokrazy - this triggers as notebooks scroll, causing highlights to disappear momentarily. + // TODO: @Yoyokrazy -- this triggers as notebooks scroll, causing highlights to disappear momentarily. + // maybe a nb type check? this._removeAllDecorations(); // Cancel any renderDecorationsTimer @@ -684,6 +690,7 @@ class WordHighlighter { // a) there is no stored query model, but there is a word. This signals the editor that drove the highlight is disposed (cell out of viewport, etc) // b) the queried model is not the current model. This signals that the editor that drove the highlight is still in the viewport, but we are highlighting a different cell // otherwise, we send null in place of the word, and the model and selection are used to compute the word + // TODO: @Yoyokrazy -- investigate this logic for sendWord const sendWord = (!WordHighlighter.query.modelInfo && WordHighlighter.query.word) || (WordHighlighter.query.modelInfo?.model.uri !== this.model.uri) ? true : false; @@ -747,6 +754,9 @@ class WordHighlighter { const newDocumentHighlights = this.workerRequestValue.get(uri); if (newDocumentHighlights) { for (const highlight of newDocumentHighlights) { + if (!highlight.range) { + continue; + } newDecorations.push({ range: highlight.range, options: getHighlightDecorationOptions(highlight.kind) From adaf974eb21f894a628f7f84b2144c1a533a4473 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 30 Jan 2024 22:19:07 +0000 Subject: [PATCH 0751/1897] Pick up latest TS for building (#203840) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index cf5c8b43d3b..7471fc6d041 100644 --- a/package.json +++ b/package.json @@ -209,7 +209,7 @@ "ts-loader": "^9.4.2", "ts-node": "^10.9.1", "tsec": "0.2.7", - "typescript": "^5.4.0-dev.20240108", + "typescript": "^5.4.0-dev.20240130", "typescript-formatter": "7.1.0", "util": "^0.12.4", "vscode-nls-dev": "^3.3.1", diff --git a/yarn.lock b/yarn.lock index 05a9854b9a3..9c0f656aad4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9534,10 +9534,10 @@ typescript@^4.7.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== -typescript@^5.4.0-dev.20240108: - version "5.4.0-dev.20240108" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.0-dev.20240108.tgz#2b5d1a1aa0e7fc91f739cc190f4cc73944463af6" - integrity sha512-flXeU+FYwW3mL6zcOz1lNX0juSolUIeIRs4nO8j77jB+N/3lrqWNOSz05dzRC4eYJcjFIIOvv5y9u8MQ5nTtzg== +typescript@^5.4.0-dev.20240130: + version "5.4.0-dev.20240130" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.0-dev.20240130.tgz#6716a8f43a0bf9feb21a850f73eb34443d9017dd" + integrity sha512-LelY/DlG9yPe807IRs7ZYvKp0eGgk4ehYr/9RvQsZg31E7sF3oYz0r44fsvnqxFWCsM11CWOrmY1r+MD5CDMfQ== typical@^4.0.0: version "4.0.0" From 31fbc3dc94469ef7ebbd0eb8efc791ff69e6dcbc Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 30 Jan 2024 23:43:09 +0000 Subject: [PATCH 0752/1897] Remove extra not null assertions (#202201) * Remove extra not null assertions With https://github.com/microsoft/TypeScript/pull/56908, TS should better preserve type refinements. To help test this out, I made a quick pass through our code to remove type assertions that are no longer needed Most of these are not impacted by https://github.com/microsoft/TypeScript/pull/56908 but removing them helps TS test changes like this Not adding an eslint rule for now as it requires whole program intellisense, which is too slow for commit hooks * Fix merge --- src/vs/base/browser/formattedTextRenderer.ts | 2 +- .../browser/ui/contextview/contextview.ts | 4 +- src/vs/base/browser/ui/list/listView.ts | 4 +- src/vs/base/browser/ui/tree/abstractTree.ts | 6 +- src/vs/base/browser/ui/tree/asyncDataTree.ts | 20 ++-- src/vs/base/common/decorators.ts | 2 +- src/vs/base/common/lifecycle.ts | 4 +- src/vs/base/common/linkedList.ts | 6 +- src/vs/base/common/ternarySearchTree.ts | 2 +- .../base/test/browser/ui/menu/menubar.test.ts | 4 +- src/vs/base/test/common/fuzzyScorer.test.ts | 74 +++++++------- src/vs/base/test/common/troubleshooting.ts | 2 +- src/vs/code/browser/workbench/workbench.ts | 4 +- src/vs/editor/browser/editorExtensions.ts | 2 +- .../editor/browser/services/hoverService.ts | 2 +- .../overlayWidgets/overlayWidgets.ts | 2 +- src/vs/editor/common/config/editorOptions.ts | 2 +- src/vs/editor/common/core/wordHelper.ts | 4 +- .../bracketPairsTree/tokenizer.ts | 2 +- .../contextmenu/browser/contextmenu.ts | 2 +- .../documentSymbols/browser/outlineModel.ts | 4 +- .../browser/keybindingCancellation.ts | 2 +- .../editor/contrib/find/browser/findWidget.ts | 2 +- .../find/test/browser/findModel.test.ts | 12 +-- .../editor/contrib/folding/browser/folding.ts | 4 +- .../contrib/gotoError/browser/gotoError.ts | 2 +- .../gotoSymbol/browser/goToCommands.ts | 2 +- .../inPlaceReplace/browser/inPlaceReplace.ts | 8 +- .../test/browser/moveLinesCommand.test.ts | 8 +- .../editor/contrib/rename/browser/rename.ts | 2 +- .../test/browser/smartSelect.test.ts | 6 +- .../test/browser/snippetParser.test.ts | 16 +-- .../common/model/textModelWithTokens.test.ts | 6 +- .../common/modes/languageSelector.test.ts | 2 +- src/vs/platform/actions/common/menuService.ts | 2 +- .../electron-main/backupMainService.test.ts | 2 +- .../common/configurationService.ts | 2 +- .../test/common/configurationModels.test.ts | 4 +- .../common/extensionManagementIpc.ts | 2 +- .../files/test/browser/fileService.test.ts | 2 +- .../indexedDBFileService.integrationTest.ts | 18 ++-- .../node/diskFileService.integrationTest.ts | 98 +++++++++---------- .../common/instantiationService.ts | 2 +- .../issue/electron-main/issueMainService.ts | 4 +- src/vs/platform/list/browser/listService.ts | 6 +- .../quickinput/browser/quickInputList.ts | 4 +- src/vs/platform/sign/browser/signService.ts | 2 +- .../platform/terminal/node/terminalProcess.ts | 2 +- .../terminal/test/common/requestStore.test.ts | 2 +- src/vs/platform/tunnel/common/tunnel.ts | 4 +- .../test/browser/fileUserDataProvider.test.ts | 8 +- .../userDataSync/common/settingsMerge.ts | 2 +- .../userDataSync/common/snippetsSync.ts | 6 +- .../common/userDataSyncStoreService.ts | 4 +- .../test/common/globalStateSync.test.ts | 14 +-- .../test/common/keybindingsSync.test.ts | 22 ++--- .../test/common/settingsSync.test.ts | 28 +++--- .../test/common/snippetsSync.test.ts | 24 ++--- .../test/common/tasksSync.test.ts | 46 ++++----- .../userDataProfilesManifestSync.test.ts | 16 +-- .../electron-main/windowsMainService.ts | 2 +- src/vs/server/node/extensionHostConnection.ts | 2 +- .../api/browser/mainThreadAuthentication.ts | 2 +- .../api/browser/mainThreadDebugService.ts | 2 +- .../api/browser/mainThreadDialogs.ts | 2 +- .../api/browser/mainThreadExtensionService.ts | 2 +- src/vs/workbench/api/browser/mainThreadSCM.ts | 2 +- .../workbench/api/browser/mainThreadTask.ts | 4 +- .../api/browser/mainThreadTimeline.ts | 2 +- .../api/common/extHostLanguageFeatures.ts | 6 +- .../api/common/extHostTerminalService.ts | 2 +- src/vs/workbench/api/common/extHostTypes.ts | 4 +- .../workbench/api/node/extHostDebugService.ts | 2 +- .../test/browser/extHostApiCommands.test.ts | 10 +- .../api/test/browser/extHostTesting.test.ts | 2 +- .../api/test/browser/extHostTypes.test.ts | 2 +- .../api/test/browser/extHostWorkspace.test.ts | 4 +- .../browser/actions/layoutActions.ts | 6 +- src/vs/workbench/browser/dnd.ts | 22 ++--- .../browser/parts/compositeBarActions.ts | 2 +- .../browser/parts/editor/editorPart.ts | 4 +- .../browser/parts/sidebar/sidebarPart.ts | 2 +- .../browser/parts/views/viewFilter.ts | 4 +- .../accessibility/browser/accessibleView.ts | 4 +- .../contrib/chat/browser/chat.contribution.ts | 2 +- .../contrib/chat/browser/chatEditor.ts | 4 +- .../contrib/chat/browser/chatWidget.ts | 10 +- .../contrib/chat/common/chatViewModel.ts | 4 +- .../chat/test/common/chatService.test.ts | 8 +- .../browser/outline/documentSymbolsOutline.ts | 2 +- .../comments/browser/commentThreadBody.ts | 2 +- .../comments/browser/commentsController.ts | 16 +-- .../contrib/comments/common/commentModel.ts | 2 +- .../customEditor/browser/customEditors.ts | 2 +- .../common/customEditorModelManager.ts | 2 +- .../debug/browser/debugEditorActions.ts | 2 +- .../contrib/debug/browser/debugService.ts | 4 +- .../contrib/debug/browser/debugSession.ts | 2 +- .../contrib/debug/browser/disassemblyView.ts | 6 +- .../workbench/contrib/debug/browser/repl.ts | 2 +- .../debug/test/browser/baseDebugView.test.ts | 2 +- .../debug/test/browser/breakpoints.test.ts | 4 +- .../debug/test/browser/callStack.test.ts | 4 +- .../debug/test/browser/debugHover.test.ts | 4 +- .../browser/editSessionsStorageService.ts | 2 +- .../abstractRuntimeExtensionsEditor.ts | 4 +- ...ensionRecommendationNotificationService.ts | 4 +- .../extensions/browser/extensionsActions.ts | 12 +-- .../browser/extensionsWorkbenchService.ts | 16 +-- .../test/electron-sandbox/extension.test.ts | 2 +- .../browser/externalTerminal.contribution.ts | 2 +- .../test/browser/fileOnDiskProvider.test.ts | 2 +- .../browser/inlineChatController.ts | 2 +- .../inlineChat/browser/inlineChatWidget.ts | 2 +- .../browser/interactive.contribution.ts | 2 +- .../interactive/browser/interactiveEditor.ts | 2 +- .../browser/interactiveEditorInput.ts | 4 +- .../contrib/markers/browser/markersView.ts | 4 +- .../browser/view/scrollSynchronizer.ts | 12 +-- .../browser/controller/cellOperations.ts | 4 +- .../notebook/browser/diff/diffComponents.ts | 44 ++++----- .../browser/diff/diffElementViewModel.ts | 8 +- .../browser/diff/notebookDiffEditor.ts | 6 +- .../notebook/browser/notebookEditor.ts | 14 +-- .../notebook/browser/notebookEditorWidget.ts | 10 +- .../browser/services/notebookServiceImpl.ts | 2 +- .../browser/view/cellParts/cellDnd.ts | 2 +- .../browser/view/cellParts/markupCell.ts | 2 +- .../browser/view/renderers/webviewPreloads.ts | 2 +- .../browser/viewModel/baseCellViewModel.ts | 2 +- .../notebookKernelQuickPickStrategy.ts | 2 +- .../preferences/browser/settingsEditor2.ts | 4 +- .../contrib/remote/browser/remoteExplorer.ts | 2 +- .../search/browser/anythingQuickAccess.ts | 2 +- .../search/test/common/cacheState.test.ts | 2 +- .../tasks/browser/abstractTaskService.ts | 2 +- .../tasks/browser/terminalTaskSystem.ts | 12 +-- .../contrib/tasks/common/taskConfiguration.ts | 2 +- .../contrib/terminal/browser/terminalGroup.ts | 2 +- .../browser/terminalProcessManager.ts | 4 +- .../terminal/browser/terminalTabbedView.ts | 2 +- .../terminal/browser/terminalTabsList.ts | 2 +- .../terminal/browser/xterm/xtermTerminal.ts | 2 +- .../browser/terminalTypeAheadAddon.ts | 4 +- .../testing/browser/testingOutputPeek.ts | 2 +- .../testing/common/testExplorerFilterState.ts | 2 +- .../contrib/testing/common/testService.ts | 4 +- .../contrib/timeline/browser/timelinePane.ts | 4 +- .../contrib/update/browser/update.ts | 2 +- .../browser/userDataProfileActions.ts | 2 +- .../userDataSync/browser/userDataSync.ts | 2 +- .../contrib/webview/browser/webviewElement.ts | 2 +- .../webviewView/browser/webviewViewPane.ts | 2 +- .../activity/browser/activityService.ts | 8 +- .../builtinExtensionsScannerService.ts | 6 +- .../extensionManagementServerService.ts | 2 +- .../extensionManagementServerService.ts | 2 +- .../common/workspaceExtensionsConfig.ts | 2 +- .../extensions/browser/extensionUrlHandler.ts | 2 +- .../test/node/macLinuxKeyboardMapper.test.ts | 2 +- .../browser/languageDetectionSimpleWorker.ts | 2 +- .../browser/keybindingsEditorModel.test.ts | 4 +- .../test/common/preferencesValidation.test.ts | 4 +- .../services/search/node/rawSearchService.ts | 4 +- .../textMateTokenizationSupport.ts | 2 +- .../test/browser/textFileEditorModel.test.ts | 6 +- .../test/browser/textFileService.test.ts | 4 +- .../test/common/textFileService.io.test.ts | 6 +- .../test/node/encoding/encoding.test.ts | 2 +- .../common/userActivityRegistry.ts | 2 +- .../userDataProfileImportExportService.ts | 2 +- .../services/views/browser/viewsService.ts | 2 +- .../browser/storedFileWorkingCopy.test.ts | 4 +- .../browser/workingCopyFileService.test.ts | 4 +- .../test/browser/parts/editor/editor.test.ts | 8 +- .../parts/editor/editorGroupModel.test.ts | 4 +- .../browser/parts/editor/editorPane.test.ts | 10 +- 177 files changed, 529 insertions(+), 531 deletions(-) diff --git a/src/vs/base/browser/formattedTextRenderer.ts b/src/vs/base/browser/formattedTextRenderer.ts index 0355a7ac51d..12371671c7f 100644 --- a/src/vs/base/browser/formattedTextRenderer.ts +++ b/src/vs/base/browser/formattedTextRenderer.ts @@ -118,7 +118,7 @@ function _renderFormattedText(element: Node, treeNode: IFormatParseTree, actionH if (child && Array.isArray(treeNode.children)) { treeNode.children.forEach((nodeChild) => { - _renderFormattedText(child!, nodeChild, actionHandler, renderCodeSegments); + _renderFormattedText(child, nodeChild, actionHandler, renderCodeSegments); }); } } diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index 239cc4171a2..af49847a810 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -253,9 +253,7 @@ export class ContextView extends Disposable { return; } - if (this.delegate!.layout) { - this.delegate!.layout!(); - } + this.delegate?.layout?.(); this.doLayout(); } diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index bd76a97ec87..2166d25e02b 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -183,7 +183,7 @@ export class NativeDragAndDropData implements IDragAndDropData { function equalsDragFeedback(f1: number[] | undefined, f2: number[] | undefined): boolean { if (Array.isArray(f1) && Array.isArray(f2)) { - return equals(f1, f2!); + return equals(f1, f2); } return f1 === f2; @@ -910,7 +910,7 @@ export class ListView implements IListView { const checked = this.accessibilityProvider.isChecked(item.element); if (typeof checked === 'boolean') { - item.row!.domNode.setAttribute('aria-checked', String(!!checked)); + item.row.domNode.setAttribute('aria-checked', String(!!checked)); } else if (checked) { const update = (checked: boolean) => item.row!.domNode.setAttribute('aria-checked', String(!!checked)); update(checked.value); diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 788d9c23852..16f985264d4 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1602,7 +1602,7 @@ class StickyScrollWidget implements IDisposable { const isVisible = !!state && state.count > 0; // If state has not changed, do nothing - if ((!wasVisible && !isVisible) || (wasVisible && isVisible && this._previousState!.equal(state!))) { + if ((!wasVisible && !isVisible) || (wasVisible && isVisible && this._previousState!.equal(state))) { return; } @@ -2551,7 +2551,7 @@ export abstract class AbstractTree implements IDisposable this.findController = new FindController(this, this.model, this.view, filter!, _options.contextViewProvider, opts); this.focusNavigationFilter = node => this.findController!.shouldAllowFocus(node); this.onDidChangeFindOpenState = this.findController.onDidChangeOpenState; - this.disposables.add(this.findController!); + this.disposables.add(this.findController); this.onDidChangeFindMode = this.findController.onDidChangeMode; this.onDidChangeFindMatchType = this.findController.onDidChangeMatchType; } else { @@ -2960,7 +2960,7 @@ export abstract class AbstractTree implements IDisposable const node = queue.shift()!; if (node !== root && node.collapsible) { - state.expanded[getId(node.element!)] = node.collapsed ? 0 : 1; + state.expanded[getId(node.element)] = node.collapsed ? 0 : 1; } queue.push(...node.children); diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 30f84e151f9..77a72d50343 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -239,10 +239,10 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt ...options.accessibilityProvider, getPosInSet: undefined, getSetSize: undefined, - getRole: options.accessibilityProvider!.getRole ? (el) => { + getRole: options.accessibilityProvider.getRole ? (el) => { return options.accessibilityProvider!.getRole!(el.element as T); } : () => 'treeitem', - isChecked: options.accessibilityProvider!.isChecked ? (e) => { + isChecked: options.accessibilityProvider.isChecked ? (e) => { return !!(options.accessibilityProvider?.isChecked!(e.element as T)); } : undefined, getAriaLabel(e) { @@ -251,8 +251,8 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt getWidgetAriaLabel() { return options.accessibilityProvider!.getWidgetAriaLabel(); }, - getWidgetRole: options.accessibilityProvider!.getWidgetRole ? () => options.accessibilityProvider!.getWidgetRole!() : () => 'tree', - getAriaLevel: options.accessibilityProvider!.getAriaLevel && (node => { + getWidgetRole: options.accessibilityProvider.getWidgetRole ? () => options.accessibilityProvider!.getWidgetRole!() : () => 'tree', + getAriaLevel: options.accessibilityProvider.getAriaLevel && (node => { return options.accessibilityProvider!.getAriaLevel!(node.element as T); }), getActiveDescendantId: options.accessibilityProvider.getActiveDescendantId && (node => { @@ -825,7 +825,7 @@ export class AsyncDataTree implements IDisposable const treeNode = this.tree.getNode(node); if (treeNode.collapsed) { - node.hasChildren = !!this.dataSource.hasChildren(node.element!); + node.hasChildren = !!this.dataSource.hasChildren(node.element); node.stale = true; return; } @@ -855,7 +855,7 @@ export class AsyncDataTree implements IDisposable } private async doRefreshNode(node: IAsyncDataTreeNode, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext): Promise[]> { - node.hasChildren = !!this.dataSource.hasChildren(node.element!); + node.hasChildren = !!this.dataSource.hasChildren(node.element); let childrenPromise: Promise>; @@ -904,7 +904,7 @@ export class AsyncDataTree implements IDisposable if (result) { return result; } - const children = this.dataSource.getChildren(node.element!); + const children = this.dataSource.getChildren(node.element); if (isIterable(children)) { return this.processChildren(children); } else { @@ -1033,9 +1033,9 @@ export class AsyncDataTree implements IDisposable const children = node.children.map(node => this.asTreeElement(node, viewStateContext)); const objectTreeOptions: IObjectTreeSetChildrenOptions> | undefined = options && { ...options, - diffIdentityProvider: options!.diffIdentityProvider && { + diffIdentityProvider: options.diffIdentityProvider && { getId(node: IAsyncDataTreeNode): { toString(): string } { - return options!.diffIdentityProvider!.getId(node.element as T); + return options.diffIdentityProvider!.getId(node.element as T); } } }; @@ -1210,7 +1210,7 @@ function asCompressibleObjectTreeOptions(options?: IComp keyboardNavigationLabelProvider: objectTreeOptions.keyboardNavigationLabelProvider && { ...objectTreeOptions.keyboardNavigationLabelProvider, getCompressedNodeKeyboardNavigationLabel(els) { - return options!.keyboardNavigationLabelProvider!.getCompressedNodeKeyboardNavigationLabel(els.map(e => e.element as T)); + return options.keyboardNavigationLabelProvider!.getCompressedNodeKeyboardNavigationLabel(els.map(e => e.element as T)); } } }; diff --git a/src/vs/base/common/decorators.ts b/src/vs/base/common/decorators.ts index 2410c4ae485..34592a7b814 100644 --- a/src/vs/base/common/decorators.ts +++ b/src/vs/base/common/decorators.ts @@ -51,7 +51,7 @@ export function memoize(_target: any, key: string, descriptor: any) { configurable: false, enumerable: false, writable: false, - value: fn!.apply(this, args) + value: fn.apply(this, args) }); } diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 4e5aed3d277..f298da62318 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -653,8 +653,8 @@ export abstract class ReferenceCollection { const { object } = reference; const dispose = createSingleCallFunction(() => { - if (--reference!.counter === 0) { - this.destroyReferencedObject(key, reference!.object); + if (--reference.counter === 0) { + this.destroyReferencedObject(key, reference.object); this.references.delete(key); } }); diff --git a/src/vs/base/common/linkedList.ts b/src/vs/base/common/linkedList.ts index 6a3d9d7af6b..42a1c2aad94 100644 --- a/src/vs/base/common/linkedList.ts +++ b/src/vs/base/common/linkedList.ts @@ -62,7 +62,7 @@ export class LinkedList { } else if (atTheEnd) { // push - const oldLast = this._last!; + const oldLast = this._last; this._last = newNode; newNode.prev = oldLast; oldLast.next = newNode; @@ -119,12 +119,12 @@ export class LinkedList { } else if (node.next === Node.Undefined) { // last - this._last = this._last!.prev!; + this._last = this._last.prev!; this._last.next = Node.Undefined; } else if (node.prev === Node.Undefined) { // first - this._first = this._first!.next!; + this._first = this._first.next!; this._first.prev = Node.Undefined; } diff --git a/src/vs/base/common/ternarySearchTree.ts b/src/vs/base/common/ternarySearchTree.ts index c16704e3e5e..dafcdc76aeb 100644 --- a/src/vs/base/common/ternarySearchTree.ts +++ b/src/vs/base/common/ternarySearchTree.ts @@ -552,7 +552,7 @@ export class TernarySearchTree { const min = this._min(node.right); if (min.key) { const { key, value, segment } = min; - this._delete(min.key!, false); + this._delete(min.key, false); node.key = key; node.value = value; node.segment = segment; diff --git a/src/vs/base/test/browser/ui/menu/menubar.test.ts b/src/vs/base/test/browser/ui/menu/menubar.test.ts index 97eaaa49373..49420cc0404 100644 --- a/src/vs/base/test/browser/ui/menu/menubar.test.ts +++ b/src/vs/base/test/browser/ui/menu/menubar.test.ts @@ -54,10 +54,10 @@ function validateMenuBarItem(menubar: MenuBar, menubarContainer: HTMLElement, la const buttonElement = getButtonElementByAriaLabel(menubarContainer, readableLabel); assert(buttonElement !== null, `Button element not found for ${readableLabel} button.`); - const titleDiv = getTitleDivFromButtonDiv(buttonElement!); + const titleDiv = getTitleDivFromButtonDiv(buttonElement); assert(titleDiv !== null, `Title div not found for ${readableLabel} button.`); - const mnem = getMnemonicFromTitleDiv(titleDiv!); + const mnem = getMnemonicFromTitleDiv(titleDiv); assert.strictEqual(mnem, mnemonic, 'Mnemonic not correct'); } diff --git a/src/vs/base/test/common/fuzzyScorer.test.ts b/src/vs/base/test/common/fuzzyScorer.test.ts index 86dfd034418..747e2ee1841 100644 --- a/src/vs/base/test/common/fuzzyScorer.test.ts +++ b/src/vs/base/test/common/fuzzyScorer.test.ts @@ -199,12 +199,12 @@ suite('Fuzzy Scorer', () => { assert.ok(pathRes.score); assert.ok(pathRes.descriptionMatch); assert.ok(pathRes.labelMatch); - assert.strictEqual(pathRes.labelMatch!.length, 1); - assert.strictEqual(pathRes.labelMatch![0].start, 8); - assert.strictEqual(pathRes.labelMatch![0].end, 11); - assert.strictEqual(pathRes.descriptionMatch!.length, 1); - assert.strictEqual(pathRes.descriptionMatch![0].start, 1); - assert.strictEqual(pathRes.descriptionMatch![0].end, 4); + assert.strictEqual(pathRes.labelMatch.length, 1); + assert.strictEqual(pathRes.labelMatch[0].start, 8); + assert.strictEqual(pathRes.labelMatch[0].end, 11); + assert.strictEqual(pathRes.descriptionMatch.length, 1); + assert.strictEqual(pathRes.descriptionMatch[0].start, 1); + assert.strictEqual(pathRes.descriptionMatch[0].end, 4); // No Match const noRes = scoreItem(resource, '987', true, ResourceAccessor); @@ -232,41 +232,41 @@ suite('Fuzzy Scorer', () => { const res1 = scoreItem(resource, 'xyz some', true, ResourceAccessor); assert.ok(res1.score); assert.strictEqual(res1.labelMatch?.length, 1); - assert.strictEqual(res1.labelMatch![0].start, 0); - assert.strictEqual(res1.labelMatch![0].end, 4); + assert.strictEqual(res1.labelMatch[0].start, 0); + assert.strictEqual(res1.labelMatch[0].end, 4); assert.strictEqual(res1.descriptionMatch?.length, 1); - assert.strictEqual(res1.descriptionMatch![0].start, 1); - assert.strictEqual(res1.descriptionMatch![0].end, 4); + assert.strictEqual(res1.descriptionMatch[0].start, 1); + assert.strictEqual(res1.descriptionMatch[0].end, 4); const res2 = scoreItem(resource, 'some xyz', true, ResourceAccessor); assert.ok(res2.score); assert.strictEqual(res1.score, res2.score); assert.strictEqual(res2.labelMatch?.length, 1); - assert.strictEqual(res2.labelMatch![0].start, 0); - assert.strictEqual(res2.labelMatch![0].end, 4); + assert.strictEqual(res2.labelMatch[0].start, 0); + assert.strictEqual(res2.labelMatch[0].end, 4); assert.strictEqual(res2.descriptionMatch?.length, 1); - assert.strictEqual(res2.descriptionMatch![0].start, 1); - assert.strictEqual(res2.descriptionMatch![0].end, 4); + assert.strictEqual(res2.descriptionMatch[0].start, 1); + assert.strictEqual(res2.descriptionMatch[0].end, 4); const res3 = scoreItem(resource, 'some xyz file file123', true, ResourceAccessor); assert.ok(res3.score); assert.ok(res3.score > res2.score); assert.strictEqual(res3.labelMatch?.length, 1); - assert.strictEqual(res3.labelMatch![0].start, 0); - assert.strictEqual(res3.labelMatch![0].end, 11); + assert.strictEqual(res3.labelMatch[0].start, 0); + assert.strictEqual(res3.labelMatch[0].end, 11); assert.strictEqual(res3.descriptionMatch?.length, 1); - assert.strictEqual(res3.descriptionMatch![0].start, 1); - assert.strictEqual(res3.descriptionMatch![0].end, 4); + assert.strictEqual(res3.descriptionMatch[0].start, 1); + assert.strictEqual(res3.descriptionMatch[0].end, 4); const res4 = scoreItem(resource, 'path z y', true, ResourceAccessor); assert.ok(res4.score); assert.ok(res4.score < res2.score); assert.strictEqual(res4.labelMatch?.length, 0); assert.strictEqual(res4.descriptionMatch?.length, 2); - assert.strictEqual(res4.descriptionMatch![0].start, 2); - assert.strictEqual(res4.descriptionMatch![0].end, 4); - assert.strictEqual(res4.descriptionMatch![1].start, 10); - assert.strictEqual(res4.descriptionMatch![1].end, 14); + assert.strictEqual(res4.descriptionMatch[0].start, 2); + assert.strictEqual(res4.descriptionMatch[0].end, 4); + assert.strictEqual(res4.descriptionMatch[1].start, 10); + assert.strictEqual(res4.descriptionMatch[1].end, 14); }); test('scoreItem - multiple with cache yields different results', function () { @@ -299,12 +299,12 @@ suite('Fuzzy Scorer', () => { assert.ok(pathRes.score); assert.ok(pathRes.descriptionMatch); assert.ok(pathRes.labelMatch); - assert.strictEqual(pathRes.labelMatch!.length, 1); - assert.strictEqual(pathRes.labelMatch![0].start, 0); - assert.strictEqual(pathRes.labelMatch![0].end, 7); - assert.strictEqual(pathRes.descriptionMatch!.length, 1); - assert.strictEqual(pathRes.descriptionMatch![0].start, 23); - assert.strictEqual(pathRes.descriptionMatch![0].end, 26); + assert.strictEqual(pathRes.labelMatch.length, 1); + assert.strictEqual(pathRes.labelMatch[0].start, 0); + assert.strictEqual(pathRes.labelMatch[0].end, 7); + assert.strictEqual(pathRes.descriptionMatch.length, 1); + assert.strictEqual(pathRes.descriptionMatch[0].start, 23); + assert.strictEqual(pathRes.descriptionMatch[0].end, 26); }); test('scoreItem - avoid match scattering (bug #36119)', function () { @@ -314,9 +314,9 @@ suite('Fuzzy Scorer', () => { assert.ok(pathRes.score); assert.ok(pathRes.descriptionMatch); assert.ok(pathRes.labelMatch); - assert.strictEqual(pathRes.labelMatch!.length, 1); - assert.strictEqual(pathRes.labelMatch![0].start, 0); - assert.strictEqual(pathRes.labelMatch![0].end, 9); + assert.strictEqual(pathRes.labelMatch.length, 1); + assert.strictEqual(pathRes.labelMatch[0].start, 0); + assert.strictEqual(pathRes.labelMatch[0].end, 9); }); test('scoreItem - prefers more compact matches', function () { @@ -328,11 +328,11 @@ suite('Fuzzy Scorer', () => { assert.ok(res.score); assert.ok(res.descriptionMatch); assert.ok(!res.labelMatch!.length); - assert.strictEqual(res.descriptionMatch!.length, 2); - assert.strictEqual(res.descriptionMatch![0].start, 11); - assert.strictEqual(res.descriptionMatch![0].end, 12); - assert.strictEqual(res.descriptionMatch![1].start, 13); - assert.strictEqual(res.descriptionMatch![1].end, 14); + assert.strictEqual(res.descriptionMatch.length, 2); + assert.strictEqual(res.descriptionMatch[0].start, 11); + assert.strictEqual(res.descriptionMatch[0].end, 12); + assert.strictEqual(res.descriptionMatch[1].start, 13); + assert.strictEqual(res.descriptionMatch[1].end, 14); }); test('scoreItem - proper target offset', function () { @@ -1121,7 +1121,7 @@ suite('Fuzzy Scorer', () => { assert.strictEqual(query.values?.[1].normalized, 'World'); assert.strictEqual(query.values?.[1].normalizedLowercase, 'World'.toLowerCase()); - const restoredQuery = pieceToQuery(query.values!); + const restoredQuery = pieceToQuery(query.values); assert.strictEqual(restoredQuery.original, query.original); assert.strictEqual(restoredQuery.values?.length, query.values?.length); assert.strictEqual(restoredQuery.containsPathSeparator, query.containsPathSeparator); diff --git a/src/vs/base/test/common/troubleshooting.ts b/src/vs/base/test/common/troubleshooting.ts index 7cfee55bd0b..0a728e8405c 100644 --- a/src/vs/base/test/common/troubleshooting.ts +++ b/src/vs/base/test/common/troubleshooting.ts @@ -41,7 +41,7 @@ export function beginTrackingDisposables(): void { export function endTrackingDisposables(): void { if (currentTracker) { setDisposableTracker(null); - console.log(currentTracker!.allDisposables.map(e => `${e[0]}\n${e[1]}`).join('\n\n')); + console.log(currentTracker.allDisposables.map(e => `${e[0]}\n${e[1]}`).join('\n\n')); currentTracker = null; } } diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 16c18d0e0ac..f8875029a8a 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -221,9 +221,9 @@ export class LocalStorageSecretStorageProvider implements ISecretStorageProvider const authAccount = JSON.stringify({ extensionId: 'vscode.github-authentication', key: 'github.auth' }); record[authAccount] = JSON.stringify(authSessionInfo.scopes.map(scopes => ({ - id: authSessionInfo!.id, + id: authSessionInfo.id, scopes, - accessToken: authSessionInfo!.accessToken + accessToken: authSessionInfo.accessToken }))); return record; diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index ba3a0bd4f97..072be21a41c 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -466,7 +466,7 @@ export abstract class EditorAction2 extends Action2 { logService.debug(`[EditorAction2] NOT running command because its precondition is FALSE`, this.desc.id, this.desc.precondition?.serialize()); return; } - return this.runEditorCommand(editorAccessor, editor!, ...args); + return this.runEditorCommand(editorAccessor, editor, ...args); }); } diff --git a/src/vs/editor/browser/services/hoverService.ts b/src/vs/editor/browser/services/hoverService.ts index 73e3a819587..9dea5dab812 100644 --- a/src/vs/editor/browser/services/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService.ts @@ -66,7 +66,7 @@ export class HoverService implements IHoverService { hover.isLocked = true; } hover.onDispose(() => { - const hoverWasFocused = this._currentHover?.domNode && isAncestorOfActiveElement(this._currentHover.domNode!); + const hoverWasFocused = this._currentHover?.domNode && isAncestorOfActiveElement(this._currentHover.domNode); if (hoverWasFocused) { // Required to handle cases such as closing the hover with the escape key this._lastFocusedElementBeforeOpen?.focus(); diff --git a/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts b/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts index 9bc7a585356..0953248e2ab 100644 --- a/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts +++ b/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts @@ -173,7 +173,7 @@ export class ViewOverlayWidgets extends ViewPart { const fixedOverflowWidgets = this._context.configuration.options.get(EditorOption.fixedOverflowWidgets); if (fixedOverflowWidgets && widgetData.widget.allowEditorOverflow) { // top, left are computed relative to the editor and we need them relative to the page - const editorBoundingBox = this._viewDomNodeRect!; + const editorBoundingBox = this._viewDomNodeRect; domNode.setTop(top + editorBoundingBox.top); domNode.setLeft(left + editorBoundingBox.left); domNode.setPosition('fixed'); diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 720944ca3e1..c0225ebc0b8 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2003,7 +2003,7 @@ class EditorGoToLocation extends BaseEditorOption(input.multiple, this.defaultValue.multiple!, ['peek', 'gotoAndPeek', 'goto']), + multiple: stringSet(input.multiple, this.defaultValue.multiple, ['peek', 'gotoAndPeek', 'goto']), multipleDefinitions: input.multipleDefinitions ?? stringSet(input.multipleDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), multipleTypeDefinitions: input.multipleTypeDefinitions ?? stringSet(input.multipleTypeDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), multipleDeclarations: input.multipleDeclarations ?? stringSet(input.multipleDeclarations, 'peek', ['peek', 'gotoAndPeek', 'goto']), diff --git a/src/vs/editor/common/core/wordHelper.ts b/src/vs/editor/common/core/wordHelper.ts index 9b7cf2e0fa0..96e378d0eca 100644 --- a/src/vs/editor/common/core/wordHelper.ts +++ b/src/vs/editor/common/core/wordHelper.ts @@ -151,8 +151,8 @@ export function getWordAtText(column: number, wordDefinition: RegExp, text: stri if (match) { const result = { word: match[0], - startColumn: textOffset + 1 + match.index!, - endColumn: textOffset + 1 + match.index! + match[0].length + startColumn: textOffset + 1 + match.index, + endColumn: textOffset + 1 + match.index + match[0].length }; wordDefinition.lastIndex = 0; return result; diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer.ts index c5d8898ded7..54c9daaa90d 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer.ts @@ -173,7 +173,7 @@ class NonPeekableTextBufferTokenizer { if (this.line === null) { this.lineTokens = this.textModel.tokenization.getLineTokens(this.lineIdx + 1); this.line = this.lineTokens.getLineContent(); - this.lineTokenOffset = this.lineCharOffset === 0 ? 0 : this.lineTokens!.findTokenIndexAtOffset(this.lineCharOffset); + this.lineTokenOffset = this.lineCharOffset === 0 ? 0 : this.lineTokens.findTokenIndexAtOffset(this.lineCharOffset); } const startLineIdx = this.lineIdx; diff --git a/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts b/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts index e6f6c2b49e4..f6cbfc425d7 100644 --- a/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts @@ -229,7 +229,7 @@ export class ContextMenuController implements IEditorContribution { this._contextMenuService.showContextMenu({ domForShadowRoot: useShadowDOM ? this._editor.getDomNode() : undefined, - getAnchor: () => anchor!, + getAnchor: () => anchor, getActions: () => actions, diff --git a/src/vs/editor/contrib/documentSymbols/browser/outlineModel.ts b/src/vs/editor/contrib/documentSymbols/browser/outlineModel.ts index 1d5e97e363d..e7f72b1a2a0 100644 --- a/src/vs/editor/contrib/documentSymbols/browser/outlineModel.ts +++ b/src/vs/editor/contrib/documentSymbols/browser/outlineModel.ts @@ -469,8 +469,8 @@ export class OutlineModelService implements IOutlineModelService { const listener = token.onCancellationRequested(() => { // last -> cancel provider request, remove cached promise - if (--data!.promiseCnt === 0) { - data!.source.cancel(); + if (--data.promiseCnt === 0) { + data.source.cancel(); this._cache.delete(textModel.id); } }); diff --git a/src/vs/editor/contrib/editorState/browser/keybindingCancellation.ts b/src/vs/editor/contrib/editorState/browser/keybindingCancellation.ts index 5a385e6c754..76de62975c5 100644 --- a/src/vs/editor/contrib/editorState/browser/keybindingCancellation.ts +++ b/src/vs/editor/contrib/editorState/browser/keybindingCancellation.ts @@ -51,7 +51,7 @@ registerSingleton(IEditorCancellationTokens, class implements IEditorCancellatio // remove w/o cancellation if (removeFn) { removeFn(); - data!.key.set(!data!.tokens.isEmpty()); + data.key.set(!data.tokens.isEmpty()); removeFn = undefined; } }; diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index 9ac46cbee07..d278f2133a1 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -1220,7 +1220,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL return; } - const maxWidth = parseFloat(dom.getComputedStyle(this._domNode).maxWidth!) || 0; + const maxWidth = parseFloat(dom.getComputedStyle(this._domNode).maxWidth) || 0; if (width > maxWidth) { return; } diff --git a/src/vs/editor/contrib/find/test/browser/findModel.test.ts b/src/vs/editor/contrib/find/test/browser/findModel.test.ts index 9b3450c2ff6..7d1ae5f5bd5 100644 --- a/src/vs/editor/contrib/find/test/browser/findModel.test.ts +++ b/src/vs/editor/contrib/find/test/browser/findModel.test.ts @@ -1643,7 +1643,7 @@ suite('FindModel', () => { ] ); - editor!.getModel()!.setValue('hello\nhi'); + editor.getModel()!.setValue('hello\nhi'); assertFindState( editor, [1, 1, 1, 1], @@ -1674,7 +1674,7 @@ suite('FindModel', () => { findModel.selectAllMatches(); - assert.deepStrictEqual(editor!.getSelections()!.map(s => s.toString()), [ + assert.deepStrictEqual(editor.getSelections()!.map(s => s.toString()), [ new Selection(6, 14, 6, 19), new Selection(6, 27, 6, 32), new Selection(7, 14, 7, 19), @@ -1718,14 +1718,14 @@ suite('FindModel', () => { findModel.selectAllMatches(); - assert.deepStrictEqual(editor!.getSelections()!.map(s => s.toString()), [ + assert.deepStrictEqual(editor.getSelections()!.map(s => s.toString()), [ new Selection(7, 14, 7, 19), new Selection(6, 14, 6, 19), new Selection(6, 27, 6, 32), new Selection(8, 14, 8, 19) ].map(s => s.toString())); - assert.deepStrictEqual(editor!.getSelection()!.toString(), new Selection(7, 14, 7, 19).toString()); + assert.deepStrictEqual(editor.getSelection()!.toString(), new Selection(7, 14, 7, 19).toString()); assertFindState( editor, @@ -2156,7 +2156,7 @@ suite('FindModel', () => { for (let i = 0; i < 1100; i++) { initialText += 'line' + i + '\n'; } - editor!.getModel()!.setValue(initialText); + editor.getModel()!.setValue(initialText); const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: '^', replaceString: 'a ', isRegex: true }, false); const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); @@ -2168,7 +2168,7 @@ suite('FindModel', () => { expectedText += 'a line' + i + '\n'; } expectedText += 'a '; - assert.strictEqual(editor!.getModel()!.getValue(), expectedText); + assert.strictEqual(editor.getModel()!.getValue(), expectedText); findModel.dispose(); findState.dispose(); diff --git a/src/vs/editor/contrib/folding/browser/folding.ts b/src/vs/editor/contrib/folding/browser/folding.ts index 095d200a91c..ee2d344a7ea 100644 --- a/src/vs/editor/contrib/folding/browser/folding.ts +++ b/src/vs/editor/contrib/folding/browser/folding.ts @@ -216,7 +216,7 @@ export class FoldingController extends Disposable implements IEditorContribution if (state.collapsedRegions && state.collapsedRegions.length > 0 && this.foldingModel) { this._restoringViewState = true; try { - this.foldingModel.applyMemento(state.collapsedRegions!); + this.foldingModel.applyMemento(state.collapsedRegions); } finally { this._restoringViewState = false; } @@ -476,7 +476,7 @@ export class FoldingController extends Disposable implements IEditorContribution const surrounding = e.event.altKey; let toToggle = []; if (surrounding) { - const filter = (otherRegion: FoldingRegion) => !otherRegion.containedBy(region!) && !region!.containedBy(otherRegion); + const filter = (otherRegion: FoldingRegion) => !otherRegion.containedBy(region) && !region.containedBy(otherRegion); const toMaybeToggle = foldingModel.getRegionsInside(null, filter); for (const r of toMaybeToggle) { if (r.isCollapsed) { diff --git a/src/vs/editor/contrib/gotoError/browser/gotoError.ts b/src/vs/editor/contrib/gotoError/browser/gotoError.ts index 032d05c3ebb..ae9c8eceb23 100644 --- a/src/vs/editor/contrib/gotoError/browser/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/browser/gotoError.ts @@ -99,7 +99,7 @@ export class MarkerController implements IEditorContribution { if (!this._widget || !this._widget.position || !this._model) { return; } - const info = this._model.find(this._editor.getModel()!.uri, this._widget!.position!); + const info = this._model.find(this._editor.getModel()!.uri, this._widget.position); if (info) { this._widget.updateMarker(info.marker); } else { diff --git a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts index b2c2e16a925..f4b26dfff0b 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts @@ -146,7 +146,7 @@ export abstract class SymbolNavigationAction extends EditorAction2 { } else if (referenceCount === 1 && altAction) { // already at the only result, run alternative SymbolNavigationAction._activeAlternativeCommands.add(this.desc.id); - instaService.invokeFunction((accessor) => altAction!.runEditorCommand(accessor, editor, arg, range).finally(() => { + instaService.invokeFunction((accessor) => altAction.runEditorCommand(accessor, editor, arg, range).finally(() => { SymbolNavigationAction._activeAlternativeCommands.delete(this.desc.id); })); diff --git a/src/vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace.ts b/src/vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace.ts index 41638f5c3c7..6b207f2785a 100644 --- a/src/vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace.ts +++ b/src/vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace.ts @@ -74,7 +74,7 @@ class InPlaceReplaceController implements IEditorContribution { return Promise.resolve(undefined); } - this.currentRequest = createCancelablePromise(token => this.editorWorkerService.navigateValueSet(modelURI, selection!, up)); + this.currentRequest = createCancelablePromise(token => this.editorWorkerService.navigateValueSet(modelURI, selection, up)); return this.currentRequest.then(result => { @@ -91,7 +91,7 @@ class InPlaceReplaceController implements IEditorContribution { // Selection const editRange = Range.lift(result.range); let highlightRange = result.range; - const diff = result.value.length - (selection!.endColumn - selection!.startColumn); + const diff = result.value.length - (selection.endColumn - selection.startColumn); // highlight highlightRange = { @@ -101,11 +101,11 @@ class InPlaceReplaceController implements IEditorContribution { endColumn: highlightRange.startColumn + result.value.length }; if (diff > 1) { - selection = new Selection(selection!.startLineNumber, selection!.startColumn, selection!.endLineNumber, selection!.endColumn + diff - 1); + selection = new Selection(selection.startLineNumber, selection.startColumn, selection.endLineNumber, selection.endColumn + diff - 1); } // Insert new text - const command = new InPlaceReplaceCommand(editRange, selection!, result.value); + const command = new InPlaceReplaceCommand(editRange, selection, result.value); this.editor.pushUndoStop(); this.editor.executeCommand(source, command); diff --git a/src/vs/editor/contrib/linesOperations/test/browser/moveLinesCommand.test.ts b/src/vs/editor/contrib/linesOperations/test/browser/moveLinesCommand.test.ts index 3a05dbc08c8..750eb192c43 100644 --- a/src/vs/editor/contrib/linesOperations/test/browser/moveLinesCommand.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/browser/moveLinesCommand.test.ts @@ -19,7 +19,7 @@ function testMoveLinesDownCommand(lines: string[], selection: Selection, expecte if (!languageConfigurationService) { languageConfigurationService = disposables.add(new TestLanguageConfigurationService()); } - testCommand(lines, null, selection, (accessor, sel) => new MoveLinesCommand(sel, true, EditorAutoIndentStrategy.Advanced, languageConfigurationService!), expectedLines, expectedSelection); + testCommand(lines, null, selection, (accessor, sel) => new MoveLinesCommand(sel, true, EditorAutoIndentStrategy.Advanced, languageConfigurationService), expectedLines, expectedSelection); disposables.dispose(); } @@ -28,7 +28,7 @@ function testMoveLinesUpCommand(lines: string[], selection: Selection, expectedL if (!languageConfigurationService) { languageConfigurationService = disposables.add(new TestLanguageConfigurationService()); } - testCommand(lines, null, selection, (accessor, sel) => new MoveLinesCommand(sel, false, EditorAutoIndentStrategy.Advanced, languageConfigurationService!), expectedLines, expectedSelection); + testCommand(lines, null, selection, (accessor, sel) => new MoveLinesCommand(sel, false, EditorAutoIndentStrategy.Advanced, languageConfigurationService), expectedLines, expectedSelection); disposables.dispose(); } @@ -37,7 +37,7 @@ function testMoveLinesDownWithIndentCommand(languageId: string, lines: string[], if (!languageConfigurationService) { languageConfigurationService = disposables.add(new TestLanguageConfigurationService()); } - testCommand(lines, languageId, selection, (accessor, sel) => new MoveLinesCommand(sel, true, EditorAutoIndentStrategy.Full, languageConfigurationService!), expectedLines, expectedSelection); + testCommand(lines, languageId, selection, (accessor, sel) => new MoveLinesCommand(sel, true, EditorAutoIndentStrategy.Full, languageConfigurationService), expectedLines, expectedSelection); disposables.dispose(); } @@ -46,7 +46,7 @@ function testMoveLinesUpWithIndentCommand(languageId: string, lines: string[], s if (!languageConfigurationService) { languageConfigurationService = disposables.add(new TestLanguageConfigurationService()); } - testCommand(lines, languageId, selection, (accessor, sel) => new MoveLinesCommand(sel, false, EditorAutoIndentStrategy.Full, languageConfigurationService!), expectedLines, expectedSelection); + testCommand(lines, languageId, selection, (accessor, sel) => new MoveLinesCommand(sel, false, EditorAutoIndentStrategy.Full, languageConfigurationService), expectedLines, expectedSelection); disposables.dispose(); } diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index 2acae7e75c2..cd88b21da7e 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -254,7 +254,7 @@ class RenameController implements IEditorContribution { respectAutoSaveConfig: true }).then(result => { if (result.ariaSummary) { - alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", loc!.text, inputFieldResult.newName, result.ariaSummary)); + alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", loc.text, inputFieldResult.newName, result.ariaSummary)); } }).catch(err => { this._notificationService.error(nls.localize('rename.failedApply', "Rename failed to apply edits")); diff --git a/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts b/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts index fb1307529f6..c4a17ea7e76 100644 --- a/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts @@ -71,7 +71,7 @@ suite('SmartSelect', () => { const uri = URI.file('test.js'); const model = modelService.createModel(text.join('\n'), new StaticLanguageSelector(languageId), uri); const [actual] = await provideSelectionRanges(providers, model, [new Position(lineNumber, column)], { selectLeadingAndTrailingWhitespace, selectSubwords: true }, CancellationToken.None); - const actualStr = actual!.map(r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn).toString()); + const actualStr = actual.map(r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn).toString()); const desiredStr = ranges.reverse().map(r => String(r)); assert.deepStrictEqual(actualStr, desiredStr, `\nA: ${actualStr} VS \nE: ${desiredStr}`); @@ -223,8 +223,8 @@ suite('SmartSelect', () => { modelService.destroyModel(model.uri); - assert.strictEqual(expected.length, ranges!.length); - for (const range of ranges!) { + assert.strictEqual(expected.length, ranges.length); + for (const range of ranges) { const exp = expected.shift() || null; assert.ok(Range.equalsRange(range.range, exp), `A=${range.range} <> E=${exp}`); } diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts index 625efaf846e..ee2f1f1d24f 100644 --- a/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts +++ b/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts @@ -776,10 +776,10 @@ suite('SnippetParser', () => { assert.strictEqual(snippet.children.length, 1); assert.ok(variable instanceof Variable); assert.ok(variable.transform); - assert.strictEqual(variable.transform!.children.length, 1); - assert.ok(variable.transform!.children[0] instanceof FormatString); - assert.strictEqual((variable.transform!.children[0]).ifValue, 'import { hello } from world'); - assert.strictEqual((variable.transform!.children[0]).elseValue, undefined); + assert.strictEqual(variable.transform.children.length, 1); + assert.ok(variable.transform.children[0] instanceof FormatString); + assert.strictEqual((variable.transform.children[0]).ifValue, 'import { hello } from world'); + assert.strictEqual((variable.transform.children[0]).elseValue, undefined); }); test('Snippet escape backslashes inside conditional insertion variable replacement #80394', function () { @@ -789,10 +789,10 @@ suite('SnippetParser', () => { assert.strictEqual(snippet.children.length, 1); assert.ok(variable instanceof Variable); assert.ok(variable.transform); - assert.strictEqual(variable.transform!.children.length, 1); - assert.ok(variable.transform!.children[0] instanceof FormatString); - assert.strictEqual((variable.transform!.children[0]).ifValue, '\\'); - assert.strictEqual((variable.transform!.children[0]).elseValue, undefined); + assert.strictEqual(variable.transform.children.length, 1); + assert.ok(variable.transform.children[0] instanceof FormatString); + assert.strictEqual((variable.transform.children[0]).ifValue, '\\'); + assert.strictEqual((variable.transform.children[0]).elseValue, undefined); }); test('Snippet placeholder empty right after expansion #152553', function () { diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index f2b4aa2b595..cdb8528819b 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -360,8 +360,8 @@ suite('TextModelWithTokens 2', () => { disposables.add(languageService.registerLanguage({ id: mode1 })); disposables.add(languageService.registerLanguage({ id: mode2 })); - const encodedMode1 = languageIdCodec!.encodeLanguageId(mode1); - const encodedMode2 = languageIdCodec!.encodeLanguageId(mode2); + const encodedMode1 = languageIdCodec.encodeLanguageId(mode1); + const encodedMode2 = languageIdCodec.encodeLanguageId(mode2); const otherMetadata1 = ( (encodedMode1 << MetadataConsts.LANGUAGEID_OFFSET) @@ -466,7 +466,7 @@ suite('TextModelWithTokens 2', () => { const languageIdCodec = instantiationService.get(ILanguageService).languageIdCodec; - const encodedMode = languageIdCodec!.encodeLanguageId(mode); + const encodedMode = languageIdCodec.encodeLanguageId(mode); const otherMetadata = ( (encodedMode << MetadataConsts.LANGUAGEID_OFFSET) diff --git a/src/vs/editor/test/common/modes/languageSelector.test.ts b/src/vs/editor/test/common/modes/languageSelector.test.ts index 7f90aa1d962..ce3aa3f4078 100644 --- a/src/vs/editor/test/common/modes/languageSelector.test.ts +++ b/src/vs/editor/test/common/modes/languageSelector.test.ts @@ -19,7 +19,7 @@ suite('LanguageSelector', function () { test('score, invalid selector', function () { assert.strictEqual(score({}, model.uri, model.language, true, undefined, undefined), 0); - assert.strictEqual(score(undefined!, model.uri, model.language, true, undefined, undefined), 0); + assert.strictEqual(score(undefined, model.uri, model.language, true, undefined, undefined), 0); assert.strictEqual(score(null!, model.uri, model.language, true, undefined, undefined), 0); assert.strictEqual(score('', model.uri, model.language, true, undefined, undefined), 0); }); diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index fdab22e372d..64a42990a54 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -199,7 +199,7 @@ class MenuInfo { group = [groupName, []]; this._menuGroups.push(group); } - group![1].push(item); + group[1].push(item); // keep keys for eventing this._collectContextKeys(item); diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index 8f3048c267a..34a5162b2d1 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -293,7 +293,7 @@ flakySuite('BackupMainService', () => { const emptyBackups = service.getEmptyWindowBackups(); assert.strictEqual(1, emptyBackups.length); - assert.strictEqual(1, fs.readdirSync(path.join(backupHome, emptyBackups[0].backupFolder!)).length); + assert.strictEqual(1, fs.readdirSync(path.join(backupHome, emptyBackups[0].backupFolder)).length); }); suite('loadSync', () => { diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index c040a98c145..051d7f2e771 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -234,6 +234,6 @@ class ConfigurationEditing { tabSize: this.configurationService.getValue('editor.tabSize', { overrideIdentifier: 'jsonc' }) }; } - return this._formattingOptions!; + return this._formattingOptions; } } diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index 41bae16625f..c1a5a0623c6 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -493,12 +493,12 @@ suite('CustomConfigurationModel', () => { assert.deepStrictEqual(testObject.configurationModel.contents, Object.create(null)); assert.deepStrictEqual(testObject.configurationModel.keys, []); - testObject.parse(null!); + testObject.parse(null); assert.deepStrictEqual(testObject.configurationModel.contents, Object.create(null)); assert.deepStrictEqual(testObject.configurationModel.keys, []); - testObject.parse(undefined!); + testObject.parse(undefined); assert.deepStrictEqual(testObject.configurationModel.contents, Object.create(null)); assert.deepStrictEqual(testObject.configurationModel.keys, []); diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index fbefe7b1247..d4f15349aad 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -264,7 +264,7 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt } uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise { - return Promise.resolve(this.channel.call('uninstall', [extension!, options])); + return Promise.resolve(this.channel.call('uninstall', [extension, options])); } reinstallFromGallery(extension: ILocalExtension): Promise { diff --git a/src/vs/platform/files/test/browser/fileService.test.ts b/src/vs/platform/files/test/browser/fileService.test.ts index 7c32e878b39..114e98adefc 100644 --- a/src/vs/platform/files/test/browser/fileService.test.ts +++ b/src/vs/platform/files/test/browser/fileService.test.ts @@ -81,7 +81,7 @@ suite('File Service', () => { assert.strictEqual(service.hasCapability(resource, FileSystemProviderCapabilities.Readonly), true); assert.strictEqual(service.hasCapability(resource, FileSystemProviderCapabilities.FileOpenReadWriteClose), false); - registrationDisposable!.dispose(); + registrationDisposable.dispose(); assert.strictEqual(await service.canHandleResource(resource), false); assert.strictEqual(service.hasProvider(resource), false); diff --git a/src/vs/platform/files/test/browser/indexedDBFileService.integrationTest.ts b/src/vs/platform/files/test/browser/indexedDBFileService.integrationTest.ts index e88991ab7fc..9290520ecda 100644 --- a/src/vs/platform/files/test/browser/indexedDBFileService.integrationTest.ts +++ b/src/vs/platform/files/test/browser/indexedDBFileService.integrationTest.ts @@ -100,10 +100,10 @@ flakySuite('IndexedDBFileSystemProvider', function () { assert.strictEqual((await userdataFileProvider.stat(newFolderResource)).type, FileType.Directory); assert.ok(event); - assert.strictEqual(event!.resource.path, newFolderResource.path); - assert.strictEqual(event!.operation, FileOperation.CREATE); - assert.strictEqual(event!.target!.resource.path, newFolderResource.path); - assert.strictEqual(event!.target!.isDirectory, true); + assert.strictEqual(event.resource.path, newFolderResource.path); + assert.strictEqual(event.operation, FileOperation.CREATE); + assert.strictEqual(event.target!.resource.path, newFolderResource.path); + assert.strictEqual(event.target!.isDirectory, true); }); test('createFolder: creating multiple folders at once', async () => { @@ -162,17 +162,17 @@ flakySuite('IndexedDBFileSystemProvider', function () { assert.strictEqual(result.resource.toString(), resource.toString()); assert.strictEqual(result.name, 'resolver'); assert.ok(result.children); - assert.ok(result.children!.length > 0); - assert.ok(result!.isDirectory); - assert.strictEqual(result.children!.length, testsElements.length); + assert.ok(result.children.length > 0); + assert.ok(result.isDirectory); + assert.strictEqual(result.children.length, testsElements.length); - assert.ok(result.children!.every(entry => { + assert.ok(result.children.every(entry => { return testsElements.some(name => { return basename(entry.resource) === name; }); })); - result.children!.forEach(value => { + result.children.forEach(value => { assert.ok(basename(value.resource)); if (['examples', 'other'].indexOf(basename(value.resource)) >= 0) { assert.ok(value.isDirectory); diff --git a/src/vs/platform/files/test/node/diskFileService.integrationTest.ts b/src/vs/platform/files/test/node/diskFileService.integrationTest.ts index 5c4ddd08900..fbf1ea0d870 100644 --- a/src/vs/platform/files/test/node/diskFileService.integrationTest.ts +++ b/src/vs/platform/files/test/node/diskFileService.integrationTest.ts @@ -181,10 +181,10 @@ flakySuite('Disk File Service', function () { assert.strictEqual(existsSync(newFolder.resource.fsPath), true); assert.ok(event); - assert.strictEqual(event!.resource.fsPath, newFolderResource.fsPath); - assert.strictEqual(event!.operation, FileOperation.CREATE); - assert.strictEqual(event!.target!.resource.fsPath, newFolderResource.fsPath); - assert.strictEqual(event!.target!.isDirectory, true); + assert.strictEqual(event.resource.fsPath, newFolderResource.fsPath); + assert.strictEqual(event.operation, FileOperation.CREATE); + assert.strictEqual(event.target!.resource.fsPath, newFolderResource.fsPath); + assert.strictEqual(event.target!.isDirectory, true); }); test('createFolder: creating multiple folders at once', async () => { @@ -243,20 +243,20 @@ flakySuite('Disk File Service', function () { assert.strictEqual(result.resource.toString(), resource.toString()); assert.strictEqual(result.name, 'resolver'); assert.ok(result.children); - assert.ok(result.children!.length > 0); + assert.ok(result.children.length > 0); assert.ok(result.isDirectory); assert.strictEqual(result.readonly, false); assert.ok(result.mtime! > 0); assert.ok(result.ctime! > 0); - assert.strictEqual(result.children!.length, testsElements.length); + assert.strictEqual(result.children.length, testsElements.length); - assert.ok(result.children!.every(entry => { + assert.ok(result.children.every(entry => { return testsElements.some(name => { return basename(entry.resource.fsPath) === name; }); })); - result.children!.forEach(value => { + result.children.forEach(value => { assert.ok(basename(value.resource.fsPath)); if (['examples', 'other'].indexOf(basename(value.resource.fsPath)) >= 0) { assert.ok(value.isDirectory); @@ -286,36 +286,36 @@ flakySuite('Disk File Service', function () { assert.ok(result); assert.strictEqual(result.name, 'resolver'); assert.ok(result.children); - assert.ok(result.children!.length > 0); + assert.ok(result.children.length > 0); assert.ok(result.isDirectory); - assert.ok(result.mtime! > 0); - assert.ok(result.ctime! > 0); - assert.strictEqual(result.children!.length, testsElements.length); + assert.ok(result.mtime > 0); + assert.ok(result.ctime > 0); + assert.strictEqual(result.children.length, testsElements.length); - assert.ok(result.children!.every(entry => { + assert.ok(result.children.every(entry => { return testsElements.some(name => { return basename(entry.resource.fsPath) === name; }); })); - assert.ok(result.children!.every(entry => entry.etag.length > 0)); + assert.ok(result.children.every(entry => entry.etag.length > 0)); - result.children!.forEach(value => { + result.children.forEach(value => { assert.ok(basename(value.resource.fsPath)); if (['examples', 'other'].indexOf(basename(value.resource.fsPath)) >= 0) { assert.ok(value.isDirectory); - assert.ok(value.mtime! > 0); - assert.ok(value.ctime! > 0); + assert.ok(value.mtime > 0); + assert.ok(value.ctime > 0); } else if (basename(value.resource.fsPath) === 'index.html') { assert.ok(!value.isDirectory); assert.ok(!value.children); - assert.ok(value.mtime! > 0); - assert.ok(value.ctime! > 0); + assert.ok(value.mtime > 0); + assert.ok(value.ctime > 0); } else if (basename(value.resource.fsPath) === 'site.css') { assert.ok(!value.isDirectory); assert.ok(!value.children); - assert.ok(value.mtime! > 0); - assert.ok(value.ctime! > 0); + assert.ok(value.mtime > 0); + assert.ok(value.ctime > 0); } else { assert.ok(!'Unexpected value ' + basename(value.resource.fsPath)); } @@ -336,20 +336,20 @@ flakySuite('Disk File Service', function () { assert.ok(result); assert.ok(result.children); - assert.ok(result.children!.length > 0); + assert.ok(result.children.length > 0); assert.ok(result.isDirectory); - const children = result.children!; + const children = result.children; assert.strictEqual(children.length, 4); const other = getByName(result, 'other'); assert.ok(other); - assert.ok(other!.children!.length > 0); + assert.ok(other.children!.length > 0); - const deep = getByName(other!, 'deep'); + const deep = getByName(other, 'deep'); assert.ok(deep); - assert.ok(deep!.children!.length > 0); - assert.strictEqual(deep!.children!.length, 4); + assert.ok(deep.children!.length > 0); + assert.strictEqual(deep.children!.length, 4); }); test('resolve directory - resolveTo multiple directories', () => { @@ -371,25 +371,25 @@ flakySuite('Disk File Service', function () { assert.ok(result); assert.ok(result.children); - assert.ok(result.children!.length > 0); + assert.ok(result.children.length > 0); assert.ok(result.isDirectory); - const children = result.children!; + const children = result.children; assert.strictEqual(children.length, 4); const other = getByName(result, 'other'); assert.ok(other); - assert.ok(other!.children!.length > 0); + assert.ok(other.children!.length > 0); - const deep = getByName(other!, 'deep'); + const deep = getByName(other, 'deep'); assert.ok(deep); - assert.ok(deep!.children!.length > 0); - assert.strictEqual(deep!.children!.length, 4); + assert.ok(deep.children!.length > 0); + assert.strictEqual(deep.children!.length, 4); const examples = getByName(result, 'examples'); assert.ok(examples); - assert.ok(examples!.children!.length > 0); - assert.strictEqual(examples!.children!.length, 4); + assert.ok(examples.children!.length > 0); + assert.strictEqual(examples.children!.length, 4); } test('resolve directory - resolveSingleChildFolders', async () => { @@ -398,16 +398,16 @@ flakySuite('Disk File Service', function () { assert.ok(result); assert.ok(result.children); - assert.ok(result.children!.length > 0); + assert.ok(result.children.length > 0); assert.ok(result.isDirectory); - const children = result.children!; + const children = result.children; assert.strictEqual(children.length, 1); const deep = getByName(result, 'deep'); assert.ok(deep); - assert.ok(deep!.children!.length > 0); - assert.strictEqual(deep!.children!.length, 4); + assert.ok(deep.children!.length > 0); + assert.strictEqual(deep.children!.length, 4); }); test('resolves', async () => { @@ -470,9 +470,9 @@ flakySuite('Disk File Service', function () { assert.strictEqual(resolved.readonly, false); assert.strictEqual(resolved.isSymbolicLink, false); assert.strictEqual(resolved.resource.toString(), resource.toString()); - assert.ok(resolved.mtime! > 0); - assert.ok(resolved.ctime! > 0); - assert.ok(resolved.size! > 0); + assert.ok(resolved.mtime > 0); + assert.ok(resolved.ctime > 0); + assert.ok(resolved.size > 0); }); test('stat - directory', async () => { @@ -484,8 +484,8 @@ flakySuite('Disk File Service', function () { assert.strictEqual(result.name, 'resolver'); assert.ok(result.isDirectory); assert.strictEqual(result.readonly, false); - assert.ok(result.mtime! > 0); - assert.ok(result.ctime! > 0); + assert.ok(result.mtime > 0); + assert.ok(result.ctime > 0); }); test('deleteFile (non recursive)', async () => { @@ -1554,7 +1554,7 @@ flakySuite('Disk File Service', function () { } assert.ok(error); - assert.strictEqual(error!.fileOperationResult, FileOperationResult.FILE_IS_DIRECTORY); + assert.strictEqual(error.fileOperationResult, FileOperationResult.FILE_IS_DIRECTORY); }); (isWindows /* error code does not seem to be supported on windows */ ? test.skip : test)('readFile - FILE_NOT_DIRECTORY', async () => { @@ -1568,7 +1568,7 @@ flakySuite('Disk File Service', function () { } assert.ok(error); - assert.strictEqual(error!.fileOperationResult, FileOperationResult.FILE_NOT_DIRECTORY); + assert.strictEqual(error.fileOperationResult, FileOperationResult.FILE_NOT_DIRECTORY); }); test('readFile - FILE_NOT_FOUND', async () => { @@ -1582,7 +1582,7 @@ flakySuite('Disk File Service', function () { } assert.ok(error); - assert.strictEqual(error!.fileOperationResult, FileOperationResult.FILE_NOT_FOUND); + assert.strictEqual(error.fileOperationResult, FileOperationResult.FILE_NOT_FOUND); }); test('readFile - FILE_NOT_MODIFIED_SINCE - default', async () => { @@ -1621,7 +1621,7 @@ flakySuite('Disk File Service', function () { } assert.ok(error); - assert.strictEqual(error!.fileOperationResult, FileOperationResult.FILE_NOT_MODIFIED_SINCE); + assert.strictEqual(error.fileOperationResult, FileOperationResult.FILE_NOT_MODIFIED_SINCE); assert.ok(error instanceof NotModifiedSinceFileOperationError && error.stat); assert.strictEqual(fileProvider.totalBytesRead, 0); } @@ -2377,7 +2377,7 @@ flakySuite('Disk File Service', function () { assert.ok(error); assert.ok(error instanceof FileOperationError); - assert.strictEqual(error!.fileOperationResult, FileOperationResult.FILE_MODIFIED_SINCE); + assert.strictEqual(error.fileOperationResult, FileOperationResult.FILE_MODIFIED_SINCE); }); test('writeFile - no error when writing to file where size is the same', async () => { diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index cd9c15da0df..ef9ae6601ce 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -293,7 +293,7 @@ export class InstantiationService implements IInstantiationService { return idle.value[key](callback, thisArg, disposables); } else { const entry: EaryListenerData = { listener: [callback, thisArg, disposables], disposable: undefined }; - const rm = list!.push(entry); + const rm = list.push(entry); const result = toDisposable(() => { rm(); entry.disposable?.dispose(); diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index c34ab307285..3998b23ead9 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -567,11 +567,11 @@ export class IssueMainService implements IIssueMainService { state.y = displayBounds.y; // prevent window from falling out of the screen to the bottom } - if (state.width! > displayBounds.width) { + if (state.width > displayBounds.width) { state.width = displayBounds.width; // prevent window from exceeding display bounds width } - if (state.height! > displayBounds.height) { + if (state.height > displayBounds.height) { state.height = displayBounds.height; // prevent window from exceeding display bounds height } } diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 17bf065c76e..6857531cb66 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -706,7 +706,7 @@ abstract class ResourceNavigator extends Disposable { this._register(this.widget.onMouseDblClick((e: { browserEvent: MouseEvent; element: T | undefined }) => this.onMouseDblClick(e.element, e.browserEvent))); if (typeof options?.openOnSingleClick !== 'boolean' && options?.configurationService) { - this.openOnSingleClick = options?.configurationService!.getValue(openModeSettingKey) !== 'doubleClick'; + this.openOnSingleClick = options?.configurationService.getValue(openModeSettingKey) !== 'doubleClick'; this._register(options?.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(openModeSettingKey)) { this.openOnSingleClick = options?.configurationService!.getValue(openModeSettingKey) !== 'doubleClick'; @@ -723,8 +723,8 @@ abstract class ResourceNavigator extends Disposable { } const selectionKeyboardEvent = event.browserEvent as SelectionKeyboardEvent; - const preserveFocus = typeof selectionKeyboardEvent.preserveFocus === 'boolean' ? selectionKeyboardEvent.preserveFocus! : true; - const pinned = typeof selectionKeyboardEvent.pinned === 'boolean' ? selectionKeyboardEvent.pinned! : !preserveFocus; + const preserveFocus = typeof selectionKeyboardEvent.preserveFocus === 'boolean' ? selectionKeyboardEvent.preserveFocus : true; + const pinned = typeof selectionKeyboardEvent.pinned === 'boolean' ? selectionKeyboardEvent.pinned : !preserveFocus; const sideBySide = false; this._open(this.getSelectedElement(), preserveFocus, pinned, sideBySide, event.browserEvent); diff --git a/src/vs/platform/quickinput/browser/quickInputList.ts b/src/vs/platform/quickinput/browser/quickInputList.ts index f5da014dabb..d8871456aee 100644 --- a/src/vs/platform/quickinput/browser/quickInputList.ts +++ b/src/vs/platform/quickinput/browser/quickInputList.ts @@ -837,8 +837,8 @@ export class QuickInputList { return; } this._lastHover = this.options.hoverDelegate.showHover({ - content: element.saneTooltip!, - target: element.element!, + content: element.saneTooltip, + target: element.element, linkHandler: (url) => { this.options.linkOpenerDelegate(url); }, diff --git a/src/vs/platform/sign/browser/signService.ts b/src/vs/platform/sign/browser/signService.ts index b2b9ad6cfab..dadf7a0f418 100644 --- a/src/vs/platform/sign/browser/signService.ts +++ b/src/vs/platform/sign/browser/signService.ts @@ -71,7 +71,7 @@ export class SignService extends AbstractSignService implements ISignService { resolve(); } }, 50, $window); - }).finally(() => checkInterval!.dispose()), + }).finally(() => checkInterval.dispose()), ]); diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index 9cf5d005af4..023b6dc66e0 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -261,7 +261,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess const cwd = slc.cwd instanceof URI ? slc.cwd.path : slc.cwd; const envPaths: string[] | undefined = (slc.env && slc.env.PATH) ? slc.env.PATH.split(path.delimiter) : undefined; - const executable = await findExecutable(slc.executable!, cwd, envPaths, this._executableEnv); + const executable = await findExecutable(slc.executable, cwd, envPaths, this._executableEnv); if (!executable) { return { message: localize('launchFail.executableDoesNotExist', "Path to shell executable \"{0}\" does not exist", slc.executable) }; } diff --git a/src/vs/platform/terminal/test/common/requestStore.test.ts b/src/vs/platform/terminal/test/common/requestStore.test.ts index a2026e91c65..7488c32cd8c 100644 --- a/src/vs/platform/terminal/test/common/requestStore.test.ts +++ b/src/vs/platform/terminal/test/common/requestStore.test.ts @@ -27,7 +27,7 @@ suite('RequestStore', () => { const request = requestStore.createRequest({ arg: 'foo' }); strictEqual(typeof eventArgs?.requestId, 'number'); strictEqual(eventArgs?.arg, 'foo'); - requestStore.acceptReply(eventArgs!.requestId, { data: 'bar' }); + requestStore.acceptReply(eventArgs.requestId, { data: 'bar' }); const result = await request; strictEqual(result.data, 'bar'); }); diff --git a/src/vs/platform/tunnel/common/tunnel.ts b/src/vs/platform/tunnel/common/tunnel.ts index 1038d0056fb..62c9059d2f8 100644 --- a/src/vs/platform/tunnel/common/tunnel.ts +++ b/src/vs/platform/tunnel/common/tunnel.ts @@ -354,11 +354,11 @@ export abstract class AbstractTunnelService implements ITunnelService { return resolvedTunnel.then(tunnel => { if (!tunnel) { this.logService.trace('ForwardedPorts: (TunnelService) New tunnel is undefined.'); - this.removeEmptyOrErrorTunnelFromMap(remoteHost!, remotePort); + this.removeEmptyOrErrorTunnelFromMap(remoteHost, remotePort); return undefined; } else if (typeof tunnel === 'string') { this.logService.trace('ForwardedPorts: (TunnelService) The tunnel provider returned an error when creating the tunnel.'); - this.removeEmptyOrErrorTunnelFromMap(remoteHost!, remotePort); + this.removeEmptyOrErrorTunnelFromMap(remoteHost, remotePort); return tunnel; } this.logService.trace('ForwardedPorts: (TunnelService) New tunnel established.'); diff --git a/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts b/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts index eeeb2486c46..b6a05ce558f 100644 --- a/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts +++ b/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts @@ -247,8 +247,8 @@ suite('FileUserDataProvider', () => { const result = await testObject.resolve(userDataProfilesService.defaultProfile.snippetsHome); assert.ok(result.isDirectory); assert.ok(result.children !== undefined); - assert.strictEqual(result.children!.length, 1); - assert.strictEqual(result.children![0].resource.toString(), joinPath(userDataProfilesService.defaultProfile.snippetsHome, 'settings.json').toString()); + assert.strictEqual(result.children.length, 1); + assert.strictEqual(result.children[0].resource.toString(), joinPath(userDataProfilesService.defaultProfile.snippetsHome, 'settings.json').toString()); }); test('read backup file', async () => { @@ -275,8 +275,8 @@ suite('FileUserDataProvider', () => { const result = await testObject.resolve(backupWorkspaceHomeOnDisk.with({ scheme: environmentService.userRoamingDataHome.scheme })); assert.ok(result.isDirectory); assert.ok(result.children !== undefined); - assert.strictEqual(result.children!.length, 1); - assert.strictEqual(result.children![0].resource.toString(), joinPath(backupWorkspaceHomeOnDisk.with({ scheme: environmentService.userRoamingDataHome.scheme }), `backup.json`).toString()); + assert.strictEqual(result.children.length, 1); + assert.strictEqual(result.children[0].resource.toString(), joinPath(backupWorkspaceHomeOnDisk.with({ scheme: environmentService.userRoamingDataHome.scheme }), `backup.json`).toString()); }); }); diff --git a/src/vs/platform/userDataSync/common/settingsMerge.ts b/src/vs/platform/userDataSync/common/settingsMerge.ts index 59aea7283d7..fdda70dfc8c 100644 --- a/src/vs/platform/userDataSync/common/settingsMerge.ts +++ b/src/vs/platform/userDataSync/common/settingsMerge.ts @@ -477,7 +477,7 @@ function getEditToInsertAtLocation(content: string, key: string, value: any, loc const isPreviouisSettingIncludesComment = previousSettingCommaOffset !== undefined && previousSettingCommaOffset > node.endOffset; edits.push({ - offset: isPreviouisSettingIncludesComment ? previousSettingCommaOffset! + 1 : node.endOffset, + offset: isPreviouisSettingIncludesComment ? previousSettingCommaOffset + 1 : node.endOffset, length: 0, content: nextSettingNode ? eol + newProperty + ',' : eol + newProperty }); diff --git a/src/vs/platform/userDataSync/common/snippetsSync.ts b/src/vs/platform/userDataSync/common/snippetsSync.ts index d89e5a95fe8..a538e9e7af4 100644 --- a/src/vs/platform/userDataSync/common/snippetsSync.ts +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -404,7 +404,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD const local: IStringDictionary = {}; for (const resourcePreview of resourcePreviews) { if (resourcePreview.fileContent) { - local[this.extUri.basename(resourcePreview.localResource!)] = resourcePreview.fileContent; + local[this.extUri.basename(resourcePreview.localResource)] = resourcePreview.fileContent; } } await this.backupLocal(JSON.stringify(this.toSnippetsContents(local))); @@ -413,7 +413,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD private async updateLocalSnippets(resourcePreviews: ISnippetsAcceptedResourcePreview[], force: boolean): Promise { for (const { fileContent, acceptResult, localResource, remoteResource, localChange } of resourcePreviews) { if (localChange !== Change.None) { - const key = remoteResource ? this.extUri.basename(remoteResource) : this.extUri.basename(localResource!); + const key = remoteResource ? this.extUri.basename(remoteResource) : this.extUri.basename(localResource); const resource = this.extUri.joinPath(this.snippetsFolder, key); // Removed @@ -446,7 +446,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD for (const { acceptResult, localResource, remoteResource, remoteChange } of resourcePreviews) { if (remoteChange !== Change.None) { - const key = localResource ? this.extUri.basename(localResource) : this.extUri.basename(remoteResource!); + const key = localResource ? this.extUri.basename(localResource) : this.extUri.basename(remoteResource); if (remoteChange === Change.Deleted) { delete newSnippets[key]; } else { diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 046f969de91..32c4086b550 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -76,7 +76,7 @@ export abstract class AbstractUserDataSyncStoreManagementService extends Disposa configurationSyncStore = isWeb && configurationSyncStore.web ? { ...configurationSyncStore, ...configurationSyncStore.web } : configurationSyncStore; if (isString(configurationSyncStore.url) && isObject(configurationSyncStore.authenticationProviders) - && Object.keys(configurationSyncStore.authenticationProviders).every(authenticationProviderId => Array.isArray(configurationSyncStore!.authenticationProviders![authenticationProviderId].scopes)) + && Object.keys(configurationSyncStore.authenticationProviders).every(authenticationProviderId => Array.isArray(configurationSyncStore.authenticationProviders[authenticationProviderId].scopes)) ) { const syncStore = configurationSyncStore as ConfigurationSyncStore; const canSwitch = !!syncStore.canSwitch; @@ -94,7 +94,7 @@ export abstract class AbstractUserDataSyncStoreManagementService extends Disposa insidersUrl: URI.parse(syncStore.insidersUrl), canSwitch, authenticationProviders: Object.keys(syncStore.authenticationProviders).reduce((result, id) => { - result.push({ id, scopes: syncStore!.authenticationProviders[id].scopes }); + result.push({ id, scopes: syncStore.authenticationProviders[id].scopes }); return result; }, []) }; diff --git a/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts b/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts index 9e0c8296474..d27a2d8d6ac 100644 --- a/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts @@ -98,7 +98,7 @@ suite('GlobalStateSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseGlobalState(content!); + const actual = parseGlobalState(content); assert.deepStrictEqual(actual.storage, { 'globalState.argv.locale': { version: 1, value: 'en' }, 'a': { version: 1, value: 'value1' } }); })); @@ -129,7 +129,7 @@ suite('GlobalStateSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseGlobalState(content!); + const actual = parseGlobalState(content); assert.deepStrictEqual(actual.storage, { 'a': { version: 1, value: 'value1' }, 'b': { version: 1, value: 'value2' } }); })); @@ -147,7 +147,7 @@ suite('GlobalStateSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseGlobalState(content!); + const actual = parseGlobalState(content); assert.deepStrictEqual(actual.storage, { 'a': { version: 1, value: 'value1' } }); })); @@ -165,7 +165,7 @@ suite('GlobalStateSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseGlobalState(content!); + const actual = parseGlobalState(content); assert.deepStrictEqual(actual.storage, { 'a': { version: 1, value: 'value1' }, 'b': { version: 1, value: 'value2' } }); })); @@ -182,7 +182,7 @@ suite('GlobalStateSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseGlobalState(content!); + const actual = parseGlobalState(content); assert.deepStrictEqual(actual.storage, { 'a': { version: 1, value: 'value2' } }); })); @@ -201,7 +201,7 @@ suite('GlobalStateSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseGlobalState(content!); + const actual = parseGlobalState(content); assert.deepStrictEqual(actual.storage, { 'a': { version: 1, value: 'value1' } }); })); @@ -222,7 +222,7 @@ suite('GlobalStateSync', () => { const { content } = await testClient.read(testObject.resource, '1'); assert.ok(content !== null); - const actual = parseGlobalState(content!); + const actual = parseGlobalState(content); assert.deepStrictEqual(actual.storage, { 'a': { version: 1, value: 'value1' } }); })); diff --git a/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts index 3e241a7bd9d..4799201077a 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts @@ -73,8 +73,8 @@ suite('KeybindingsSync', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true, client.instantiationService.get(ILogService)), '[]'); - assert.strictEqual(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true, client.instantiationService.get(ILogService)), '[]'); + assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content, true, client.instantiationService.get(ILogService)), '[]'); + assert.strictEqual(getKeybindingsContentFromSyncContent(remoteUserData.syncData!.content, true, client.instantiationService.get(ILogService)), '[]'); assert.strictEqual((await fileService.readFile(keybindingsResource)).value.toString(), ''); }); @@ -98,8 +98,8 @@ suite('KeybindingsSync', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true, client.instantiationService.get(ILogService)), content); - assert.strictEqual(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content, true, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getKeybindingsContentFromSyncContent(remoteUserData.syncData!.content, true, client.instantiationService.get(ILogService)), content); assert.strictEqual((await fileService.readFile(keybindingsResource)).value.toString(), content); }); @@ -113,8 +113,8 @@ suite('KeybindingsSync', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true, client.instantiationService.get(ILogService)), expectedContent); - assert.strictEqual(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true, client.instantiationService.get(ILogService)), expectedContent); + assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content, true, client.instantiationService.get(ILogService)), expectedContent); + assert.strictEqual(getKeybindingsContentFromSyncContent(remoteUserData.syncData!.content, true, client.instantiationService.get(ILogService)), expectedContent); assert.strictEqual((await fileService.readFile(keybindingsResource)).value.toString(), expectedContent); }); @@ -138,8 +138,8 @@ suite('KeybindingsSync', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true, client.instantiationService.get(ILogService)), content); - assert.strictEqual(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content, true, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getKeybindingsContentFromSyncContent(remoteUserData.syncData!.content, true, client.instantiationService.get(ILogService)), content); assert.strictEqual((await fileService.readFile(keybindingsResource)).value.toString(), content); }); @@ -162,8 +162,8 @@ suite('KeybindingsSync', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true, client.instantiationService.get(ILogService)), content); - assert.strictEqual(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content, true, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getKeybindingsContentFromSyncContent(remoteUserData.syncData!.content, true, client.instantiationService.get(ILogService)), content); assert.strictEqual((await fileService.readFile(keybindingsResource)).value.toString(), expectedLocalContent); }); @@ -186,7 +186,7 @@ suite('KeybindingsSync', () => { const remoteUserData = await testObject.getRemoteUserData(null); assert.deepStrictEqual(lastSyncUserData!.ref, remoteUserData.ref); assert.deepStrictEqual(lastSyncUserData!.syncData, remoteUserData.syncData); - assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true, client.instantiationService.get(ILogService)), '[]'); + assert.strictEqual(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content, true, client.instantiationService.get(ILogService)), '[]'); }); test('test apply remote when keybindings file does not exist', async () => { diff --git a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts index e6908111ebc..0cd110208b8 100644 --- a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts @@ -89,8 +89,8 @@ suite('SettingsSync - Auto', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}'); - assert.strictEqual(parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, '{}'); + assert.strictEqual(parseSettingsSyncContent(lastSyncUserData!.syncData!.content)?.settings, '{}'); + assert.strictEqual(parseSettingsSyncContent(remoteUserData.syncData!.content)?.settings, '{}'); assert.strictEqual((await fileService.readFile(settingsResource)).value.toString(), ''); })); @@ -130,8 +130,8 @@ suite('SettingsSync - Auto', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, content); - assert.strictEqual(parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, content); + assert.strictEqual(parseSettingsSyncContent(lastSyncUserData!.syncData!.content)?.settings, content); + assert.strictEqual(parseSettingsSyncContent(remoteUserData.syncData!.content)?.settings, content); assert.strictEqual((await fileService.readFile(settingsResource)).value.toString(), content); })); @@ -155,7 +155,7 @@ suite('SettingsSync - Auto', () => { const remoteUserData = await testObject.getRemoteUserData(null); assert.deepStrictEqual(lastSyncUserData!.ref, remoteUserData.ref); assert.deepStrictEqual(lastSyncUserData!.syncData, remoteUserData.syncData); - assert.strictEqual(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}'); + assert.strictEqual(parseSettingsSyncContent(lastSyncUserData!.syncData!.content)?.settings, '{}'); })); test('sync for first time to the server', () => runWithFakedTimers({ useFakeTimers: true }, async () => { @@ -187,7 +187,7 @@ suite('SettingsSync - Auto', () => { const { content } = await client.read(testObject.resource); assert.ok(content !== null); - const actual = parseSettings(content!); + const actual = parseSettings(content); assert.deepStrictEqual(actual, expected); })); @@ -211,7 +211,7 @@ suite('SettingsSync - Auto', () => { const { content } = await client.read(testObject.resource); assert.ok(content !== null); - const actual = parseSettings(content!); + const actual = parseSettings(content); assert.deepStrictEqual(actual, `{ // Always "files.autoSave": "afterDelay", @@ -242,7 +242,7 @@ suite('SettingsSync - Auto', () => { const { content } = await client.read(testObject.resource); assert.ok(content !== null); - const actual = parseSettings(content!); + const actual = parseSettings(content); assert.deepStrictEqual(actual, `{ // Always "files.autoSave": "afterDelay", @@ -273,7 +273,7 @@ suite('SettingsSync - Auto', () => { const { content } = await client.read(testObject.resource); assert.ok(content !== null); - const actual = parseSettings(content!); + const actual = parseSettings(content); assert.deepStrictEqual(actual, `{ // Always "files.autoSave": "afterDelay", @@ -297,7 +297,7 @@ suite('SettingsSync - Auto', () => { const { content } = await client.read(testObject.resource); assert.ok(content !== null); - const actual = parseSettings(content!); + const actual = parseSettings(content); assert.deepStrictEqual(actual, `{ }`); })); @@ -315,7 +315,7 @@ suite('SettingsSync - Auto', () => { const { content } = await client.read(testObject.resource); assert.ok(content !== null); - const actual = parseSettings(content!); + const actual = parseSettings(content); assert.deepStrictEqual(actual, `{ , }`); @@ -367,7 +367,7 @@ suite('SettingsSync - Auto', () => { const { content } = await client.read(testObject.resource); assert.ok(content !== null); - const actual = parseSettings(content!); + const actual = parseSettings(content); assert.deepStrictEqual(actual, `{ // Always "files.autoSave": "afterDelay", @@ -415,7 +415,7 @@ suite('SettingsSync - Auto', () => { const { content } = await client.read(testObject.resource); assert.ok(content !== null); - const actual = parseSettings(content!); + const actual = parseSettings(content); assert.deepStrictEqual(actual, `{ // Always "files.autoSave": "afterDelay", @@ -576,7 +576,7 @@ suite('SettingsSync - Manual', () => { const { content } = await client.read(testObject.resource); assert.ok(content !== null); - const actual = parseSettings(content!); + const actual = parseSettings(content); assert.deepStrictEqual(actual, `{ // Always "files.autoSave": "afterDelay", diff --git a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts index 531665ceddd..fc9644c2245 100644 --- a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts @@ -230,7 +230,7 @@ suite('SnippetsSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseSnippets(content!); + const actual = parseSnippets(content); assert.deepStrictEqual(actual, { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }); }); @@ -265,7 +265,7 @@ suite('SnippetsSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseSnippets(content!); + const actual = parseSnippets(content); assert.deepStrictEqual(actual, { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }); }); @@ -300,7 +300,7 @@ suite('SnippetsSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseSnippets(content!); + const actual = parseSnippets(content); assert.deepStrictEqual(actual, { 'html.json': htmlSnippet1 }); }); @@ -363,7 +363,7 @@ suite('SnippetsSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseSnippets(content!); + const actual = parseSnippets(content); assert.deepStrictEqual(actual, { 'html.json': htmlSnippet2, 'typescript.json': tsSnippet1 }); }); @@ -383,7 +383,7 @@ suite('SnippetsSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseSnippets(content!); + const actual = parseSnippets(content); assert.deepStrictEqual(actual, { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }); }); @@ -419,7 +419,7 @@ suite('SnippetsSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseSnippets(content!); + const actual = parseSnippets(content); assert.deepStrictEqual(actual, { 'html.json': htmlSnippet2 }); }); @@ -476,7 +476,7 @@ suite('SnippetsSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseSnippets(content!); + const actual = parseSnippets(content); assert.deepStrictEqual(actual, { 'html.json': htmlSnippet2 }); }); @@ -497,7 +497,7 @@ suite('SnippetsSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseSnippets(content!); + const actual = parseSnippets(content); assert.deepStrictEqual(actual, { 'typescript.json': tsSnippet1 }); }); @@ -583,7 +583,7 @@ suite('SnippetsSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseSnippets(content!); + const actual = parseSnippets(content); assert.deepStrictEqual(actual, { 'typescript.json': tsSnippet1, 'html.json': htmlSnippet3 }); }); @@ -611,7 +611,7 @@ suite('SnippetsSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseSnippets(content!); + const actual = parseSnippets(content); assert.deepStrictEqual(actual, { 'typescript.json': tsSnippet1 }); }); @@ -631,7 +631,7 @@ suite('SnippetsSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseSnippets(content!); + const actual = parseSnippets(content); assert.deepStrictEqual(actual, { 'html.json': htmlSnippet1, 'global.code-snippets': globalSnippet }); }); @@ -654,7 +654,7 @@ suite('SnippetsSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseSnippets(content!); + const actual = parseSnippets(content); assert.deepStrictEqual(actual, { 'typescript.json': tsSnippet1, 'global.code-snippets': globalSnippet }); }); diff --git a/src/vs/platform/userDataSync/test/common/tasksSync.test.ts b/src/vs/platform/userDataSync/test/common/tasksSync.test.ts index 52669939bef..bfb44062bcd 100644 --- a/src/vs/platform/userDataSync/test/common/tasksSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/tasksSync.test.ts @@ -86,8 +86,8 @@ suite('TasksSync', () => { assert.deepStrictEqual(testObject.status, SyncStatus.Idle); const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); - assert.strictEqual(getTasksContentFromSyncContent(remoteUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(remoteUserData.syncData!.content, client.instantiationService.get(ILogService)), content); assert.strictEqual((await fileService.readFile(tasksResource)).value.toString(), content); }); @@ -109,8 +109,8 @@ suite('TasksSync', () => { assert.deepStrictEqual(testObject.status, SyncStatus.Idle); const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); - assert.strictEqual(getTasksContentFromSyncContent(remoteUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(remoteUserData.syncData!.content, client.instantiationService.get(ILogService)), content); }); test('first time sync: when tasks file exists locally with same content as remote', async () => { @@ -137,8 +137,8 @@ suite('TasksSync', () => { assert.deepStrictEqual(testObject.status, SyncStatus.Idle); const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); - assert.strictEqual(getTasksContentFromSyncContent(remoteUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(remoteUserData.syncData!.content, client.instantiationService.get(ILogService)), content); assert.strictEqual((await fileService.readFile(tasksResource)).value.toString(), content); }); @@ -167,8 +167,8 @@ suite('TasksSync', () => { assert.deepStrictEqual(testObject.status, SyncStatus.Idle); const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); - assert.strictEqual(getTasksContentFromSyncContent(remoteUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(remoteUserData.syncData!.content, client.instantiationService.get(ILogService)), content); }); test('when tasks file remotely has moved forward', async () => { @@ -203,8 +203,8 @@ suite('TasksSync', () => { assert.deepStrictEqual(testObject.status, SyncStatus.Idle); const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); - assert.strictEqual(getTasksContentFromSyncContent(remoteUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(remoteUserData.syncData!.content, client.instantiationService.get(ILogService)), content); assert.strictEqual((await fileService.readFile(tasksResource)).value.toString(), content); }); @@ -241,8 +241,8 @@ suite('TasksSync', () => { assert.deepStrictEqual(testObject.status, SyncStatus.Idle); const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); - assert.strictEqual(getTasksContentFromSyncContent(remoteUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(remoteUserData.syncData!.content, client.instantiationService.get(ILogService)), content); assert.strictEqual((await fileService.readFile(tasksResource)).value.toString(), content); }); @@ -294,8 +294,8 @@ suite('TasksSync', () => { assert.deepStrictEqual(testObject.status, SyncStatus.Idle); const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content!, client.instantiationService.get(ILogService)), previewContent); - assert.strictEqual(getTasksContentFromSyncContent(remoteUserData!.syncData!.content!, client.instantiationService.get(ILogService)), previewContent); + assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content, client.instantiationService.get(ILogService)), previewContent); + assert.strictEqual(getTasksContentFromSyncContent(remoteUserData.syncData!.content, client.instantiationService.get(ILogService)), previewContent); assert.strictEqual((await fileService.readFile(tasksResource)).value.toString(), previewContent); }); @@ -347,8 +347,8 @@ suite('TasksSync', () => { assert.deepStrictEqual(testObject.status, SyncStatus.Idle); const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); - assert.strictEqual(getTasksContentFromSyncContent(remoteUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(remoteUserData.syncData!.content, client.instantiationService.get(ILogService)), content); assert.strictEqual((await fileService.readFile(tasksResource)).value.toString(), content); }); @@ -394,8 +394,8 @@ suite('TasksSync', () => { assert.deepStrictEqual(testObject.status, SyncStatus.Idle); const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); - assert.strictEqual(getTasksContentFromSyncContent(remoteUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(remoteUserData.syncData!.content, client.instantiationService.get(ILogService)), content); assert.strictEqual((await fileService.readFile(tasksResource)).value.toString(), content); }); @@ -441,8 +441,8 @@ suite('TasksSync', () => { assert.deepStrictEqual(testObject.status, SyncStatus.Idle); const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); - assert.strictEqual(getTasksContentFromSyncContent(remoteUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(remoteUserData.syncData!.content, client.instantiationService.get(ILogService)), content); assert.strictEqual((await fileService.readFile(tasksResource)).value.toString(), content); }); @@ -469,8 +469,8 @@ suite('TasksSync', () => { assert.deepStrictEqual(testObject.status, SyncStatus.Idle); const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content!, client.instantiationService.get(ILogService)), null); - assert.strictEqual(getTasksContentFromSyncContent(remoteUserData!.syncData!.content!, client.instantiationService.get(ILogService)), null); + assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content, client.instantiationService.get(ILogService)), null); + assert.strictEqual(getTasksContentFromSyncContent(remoteUserData.syncData!.content, client.instantiationService.get(ILogService)), null); assert.strictEqual(await fileService.exists(tasksResource), false); }); @@ -502,7 +502,7 @@ suite('TasksSync', () => { const remoteUserData = await testObject.getRemoteUserData(null); assert.deepStrictEqual(lastSyncUserData!.ref, remoteUserData.ref); assert.deepStrictEqual(lastSyncUserData!.syncData, remoteUserData.syncData); - assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content!, client.instantiationService.get(ILogService)), content); + assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content, client.instantiationService.get(ILogService)), content); }); test('apply remote when tasks file does not exist', async () => { diff --git a/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts b/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts index 745eac0ae58..87c687f10c1 100644 --- a/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts @@ -119,7 +119,7 @@ suite('UserDataProfilesManifestSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseRemoteProfiles(content!); + const actual = parseRemoteProfiles(content); assert.deepStrictEqual(actual, [{ id: '1', name: 'name 1', collection: '1' }, { id: '2', name: 'name 2', collection: '2' }]); }); @@ -138,7 +138,7 @@ suite('UserDataProfilesManifestSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseRemoteProfiles(content!); + const actual = parseRemoteProfiles(content); assert.deepStrictEqual(actual, [{ id: '1', name: 'name 1', collection: '1' }]); }); @@ -158,7 +158,7 @@ suite('UserDataProfilesManifestSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseRemoteProfiles(content!); + const actual = parseRemoteProfiles(content); assert.deepStrictEqual(actual, [{ id: '1', name: 'name 1', collection: '1', shortName: 'short 1' }, { id: '2', name: 'name 2', collection: '2' }]); }); @@ -178,7 +178,7 @@ suite('UserDataProfilesManifestSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseRemoteProfiles(content!); + const actual = parseRemoteProfiles(content); assert.deepStrictEqual(actual, [{ id: '1', name: 'name 2', collection: '1', shortName: '2' }]); }); @@ -199,7 +199,7 @@ suite('UserDataProfilesManifestSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseRemoteProfiles(content!); + const actual = parseRemoteProfiles(content); assert.deepStrictEqual(actual, [{ id: '2', name: 'name 2', collection: '2' }]); }); @@ -213,7 +213,7 @@ suite('UserDataProfilesManifestSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseRemoteProfiles(content!); + const actual = parseRemoteProfiles(content); assert.deepStrictEqual(actual, [{ id: '1', name: 'name 1', collection: '1', useDefaultFlags: { keybindings: true } }]); assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '1', name: 'name 1', shortName: undefined, useDefaultFlags: { keybindings: true } }]); @@ -234,7 +234,7 @@ suite('UserDataProfilesManifestSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseRemoteProfiles(content!); + const actual = parseRemoteProfiles(content); assert.deepStrictEqual(actual, [{ id: '1', name: 'name 1', collection: '1', useDefaultFlags: { keybindings: true } }]); assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '1', name: 'name 1', shortName: undefined, useDefaultFlags: { keybindings: true } }]); }); @@ -254,7 +254,7 @@ suite('UserDataProfilesManifestSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); - const actual = parseRemoteProfiles(content!); + const actual = parseRemoteProfiles(content); assert.deepStrictEqual(actual, [{ id: '1', name: 'name 1', collection: '1', useDefaultFlags: { keybindings: true } }]); assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '1', name: 'name 1', shortName: undefined, useDefaultFlags: { keybindings: true } }]); diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 11ea40f1325..5ebe9212d44 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -1546,7 +1546,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic if (window.isReady) { this.lifecycleMainService.unload(window, UnloadReason.LOAD).then(async veto => { if (!veto) { - await this.doOpenInBrowserWindow(window!, configuration, options, defaultProfile); + await this.doOpenInBrowserWindow(window, configuration, options, defaultProfile); } }); } else { diff --git a/src/vs/server/node/extensionHostConnection.ts b/src/vs/server/node/extensionHostConnection.ts index 60a5f8cab47..f259ea2cbaf 100644 --- a/src/vs/server/node/extensionHostConnection.ts +++ b/src/vs/server/node/extensionHostConnection.ts @@ -289,7 +289,7 @@ export class ExtensionHostConnection { if (extHostNamedPipeServer) { extHostNamedPipeServer.on('connection', (socket) => { - extHostNamedPipeServer!.close(); + extHostNamedPipeServer.close(); this._pipeSockets(socket, this._connectionData!); }); } else { diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 9bc8b7b1685..22c82811bb4 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -155,7 +155,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu // modal flows if (options.createIfNone || options.forceNewSession) { const providerName = this.authenticationService.getLabel(providerId); - const detail = (typeof options.forceNewSession === 'object') ? options.forceNewSession!.detail : undefined; + const detail = (typeof options.forceNewSession === 'object') ? options.forceNewSession.detail : undefined; // We only want to show the "recreating session" prompt if we are using forceNewSession & there are sessions // that we will be "forcing through". diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 283c0e78372..ea668f70f3c 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -92,7 +92,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb const dto: IThreadFocusDto = { kind: 'thread', threadId: thread?.threadId, - sessionId: session!.getId(), + sessionId: session.getId(), }; this._proxy.$acceptStackFrameFocus(dto); } diff --git a/src/vs/workbench/api/browser/mainThreadDialogs.ts b/src/vs/workbench/api/browser/mainThreadDialogs.ts index 3ac34ea6b7b..424b70bcc78 100644 --- a/src/vs/workbench/api/browser/mainThreadDialogs.ts +++ b/src/vs/workbench/api/browser/mainThreadDialogs.ts @@ -52,7 +52,7 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { if (options?.filters) { result.filters = []; for (const [key, value] of Object.entries(options.filters)) { - result.filters!.push({ name: key, extensions: value }); + result.filters.push({ name: key, extensions: value }); } } return result; diff --git a/src/vs/workbench/api/browser/mainThreadExtensionService.ts b/src/vs/workbench/api/browser/mainThreadExtensionService.ts index 5afc945cc16..6732d38f5e6 100644 --- a/src/vs/workbench/api/browser/mainThreadExtensionService.ts +++ b/src/vs/workbench/api/browser/mainThreadExtensionService.ts @@ -171,7 +171,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha message: localize('uninstalledDep', "Cannot activate the '{0}' extension because it depends on the '{1}' extension from '{2}', which is not installed. Would you like to install the extension and reload the window?", extName, dependencyExtension.displayName, dependencyExtension.publisherDisplayName), actions: { primary: [new Action('install', localize('install missing dep', "Install and Reload"), '', true, - () => this._extensionsWorkbenchService.install(dependencyExtension!) + () => this._extensionsWorkbenchService.install(dependencyExtension) .then(() => this._hostService.reload(), e => this._notificationService.error(e)))] } }); diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 1300c25f4f0..8f2f658e810 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -253,7 +253,7 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { this._onDidChange.fire(); if (typeof features.commitTemplate !== 'undefined') { - this._onDidChangeCommitTemplate.fire(this.commitTemplate!); + this._onDidChangeCommitTemplate.fire(this.commitTemplate); } if (typeof features.statusBarCommands !== 'undefined') { diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index b24abe59657..8fe9a431321 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -445,9 +445,9 @@ export class MainThreadTask implements MainThreadTaskShape { resolvedDefinition = await this._configurationResolverService.resolveAnyAsync(task.getWorkspaceFolder(), execution.task.definition, dictionary); } - this._proxy.$onDidStartTask(execution, event.terminalId!, resolvedDefinition); + this._proxy.$onDidStartTask(execution, event.terminalId, resolvedDefinition); } else if (event.kind === TaskEventKind.ProcessStarted) { - this._proxy.$onDidStartTaskProcess(TaskProcessStartedDTO.from(task.getTaskExecution(), event.processId!)); + this._proxy.$onDidStartTaskProcess(TaskProcessStartedDTO.from(task.getTaskExecution(), event.processId)); } else if (event.kind === TaskEventKind.ProcessEnded) { this._proxy.$onDidEndTaskProcess(TaskProcessEndedDTO.from(task.getTaskExecution(), event.exitCode)); } else if (event.kind === TaskEventKind.End) { diff --git a/src/vs/workbench/api/browser/mainThreadTimeline.ts b/src/vs/workbench/api/browser/mainThreadTimeline.ts index d6547f29752..1cffecdef90 100644 --- a/src/vs/workbench/api/browser/mainThreadTimeline.ts +++ b/src/vs/workbench/api/browser/mainThreadTimeline.ts @@ -59,7 +59,7 @@ export class MainThreadTimeline implements MainThreadTimelineShape { $emitTimelineChangeEvent(e: TimelineChangeEvent): void { this.logService.trace(`MainThreadTimeline#emitChangeEvent: id=${e.id}, uri=${e.uri?.toString(true)}`); - const emitter = this._providerEmitters.get(e.id!); + const emitter = this._providerEmitters.get(e.id); emitter?.fire(e); } diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 9e35d5c766e..b0a8742c349 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1071,7 +1071,7 @@ class CompletionsAdapter { const dto1 = this._convertCompletionItem(item, id); - const resolvedItem = await this._provider.resolveCompletionItem!(item, token); + const resolvedItem = await this._provider.resolveCompletionItem(item, token); if (!resolvedItem) { return undefined; @@ -1426,7 +1426,7 @@ class InlayHintsAdapter { if (!item) { return undefined; } - const hint = await this._provider.resolveInlayHint!(item, token); + const hint = await this._provider.resolveInlayHint(item, token); if (!hint) { return undefined; } @@ -1560,7 +1560,7 @@ class LinkProviderAdapter { if (!item) { return undefined; } - const link = await this._provider.resolveDocumentLink!(item, token); + const link = await this._provider.resolveDocumentLink(item, token); if (!link || !LinkProviderAdapter._validateLink(link)) { return undefined; } diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index c3bc1f10309..e004acba809 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -934,7 +934,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I // following calls to createTerminal will be created with the new environment. It will // result in more noise by sending multiple updates when called but collections are // expected to be small. - this._syncEnvironmentVariableCollection(extensionIdentifier, collection!); + this._syncEnvironmentVariableCollection(extensionIdentifier, collection); }); } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 1307d23e705..b67a8408f8e 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -102,7 +102,7 @@ export class Position { let result = positions[0]; for (let i = 1; i < positions.length; i++) { const p = positions[i]; - if (p.isBefore(result!)) { + if (p.isBefore(result)) { result = p; } } @@ -116,7 +116,7 @@ export class Position { let result = positions[0]; for (let i = 1; i < positions.length; i++) { const p = positions[i]; - if (p.isAfter(result!)) { + if (p.isAfter(result)) { result = p; } } diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 07bb7582839..1ccfd2101da 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -142,7 +142,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { // Mark terminal as unused when its session ends, see #112055 const sessionListener = this.onDidTerminateDebugSession(s => { if (s.id === sessionId) { - this._integratedTerminalInstances.free(terminal!); + this._integratedTerminalInstances.free(terminal); sessionListener.dispose(); } }); diff --git a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts index 25bcbd3f930..b49d05ed00f 100644 --- a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts +++ b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts @@ -1070,8 +1070,8 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.strictEqual(value.length, 1); const [first] = value; assert.ok(first.command); - assert.strictEqual(first.command!.command, 'command'); - assert.strictEqual(first.command!.title, 'command_title'); + assert.strictEqual(first.command.command, 'command'); + assert.strictEqual(first.command.title, 'command_title'); assert.strictEqual(first.kind!.value, 'foo'); assert.strictEqual(first.title, 'title'); @@ -1101,8 +1101,8 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.strictEqual(value.length, 1); const [first] = value; assert.ok(first.command); - assert.ok(first.command!.arguments![1] instanceof types.Selection); - assert.ok(first.command!.arguments![1].isEqual(selection)); + assert.ok(first.command.arguments![1] instanceof types.Selection); + assert.ok(first.command.arguments![1].isEqual(selection)); }); }); }); @@ -1394,7 +1394,7 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.strictEqual(first.position.line, 0); assert.strictEqual(first.position.character, 1); assert.strictEqual(first.textEdits?.length, 1); - assert.strictEqual(first.textEdits![0].newText, 'Hello'); + assert.strictEqual(first.textEdits[0].newText, 'Hello'); assert.strictEqual(second.position.line, 10); assert.strictEqual(second.position.character, 11); diff --git a/src/vs/workbench/api/test/browser/extHostTesting.test.ts b/src/vs/workbench/api/test/browser/extHostTesting.test.ts index f52b523cb1c..f7e03c8d80f 100644 --- a/src/vs/workbench/api/test/browser/extHostTesting.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTesting.test.ts @@ -803,7 +803,7 @@ suite('ExtHost Testing', () => { contextValue: undefined, expected: undefined, actual: undefined, - location: convert.location.from({ uri: test2.uri!, range: test2.range! }), + location: convert.location.from({ uri: test2.uri!, range: test2.range }), }] ]); diff --git a/src/vs/workbench/api/test/browser/extHostTypes.test.ts b/src/vs/workbench/api/test/browser/extHostTypes.test.ts index 8afe5e31237..6cef861c9c8 100644 --- a/src/vs/workbench/api/test/browser/extHostTypes.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTypes.test.ts @@ -357,7 +357,7 @@ suite('ExtHostTypes', function () { assert.strictEqual(edit.newText, ''); assertToJSON(edit, { range: [{ line: 1, character: 1 }, { line: 2, character: 11 }], newText: '' }); - edit = new types.TextEdit(range, null!); + edit = new types.TextEdit(range, null); assert.strictEqual(edit.newText, ''); edit = new types.TextEdit(range, ''); diff --git a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts index 0dc3b5ab6ed..da33adc7de7 100644 --- a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts +++ b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts @@ -644,7 +644,7 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles(new RelativePattern('/other/folder', 'glob/**'), null!, 10, new ExtensionIdentifier('test')).then(() => { + return ws.findFiles(new RelativePattern('/other/folder', 'glob/**'), null, 10, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); }); @@ -664,7 +664,7 @@ suite('ExtHostWorkspace', function () { const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); const token = CancellationToken.Cancelled; - return ws.findFiles(new RelativePattern('/other/folder', 'glob/**'), null!, 10, new ExtensionIdentifier('test'), token).then(() => { + return ws.findFiles(new RelativePattern('/other/folder', 'glob/**'), null, 10, new ExtensionIdentifier('test'), token).then(() => { assert(!mainThreadCalled, '!mainThreadCalled'); }); }); diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 3a741d1cdc7..4ef386aea49 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -1110,13 +1110,13 @@ class MoveFocusedViewAction extends Action2 { const destination = quickPick.selectedItems[0]; if (destination.id === '_.panel.newcontainer') { - viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.Panel, this.desc.id); + viewDescriptorService.moveViewToLocation(viewDescriptor, ViewContainerLocation.Panel, this.desc.id); viewsService.openView(focusedViewId, true); } else if (destination.id === '_.sidebar.newcontainer') { - viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.Sidebar, this.desc.id); + viewDescriptorService.moveViewToLocation(viewDescriptor, ViewContainerLocation.Sidebar, this.desc.id); viewsService.openView(focusedViewId, true); } else if (destination.id === '_.auxiliarybar.newcontainer') { - viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.AuxiliaryBar, this.desc.id); + viewDescriptorService.moveViewToLocation(viewDescriptor, ViewContainerLocation.AuxiliaryBar, this.desc.id); viewsService.openView(focusedViewId, true); } else if (destination.id) { viewDescriptorService.moveViewsToContainer([viewDescriptor], viewDescriptorService.getViewContainerById(destination.id)!, undefined, this.desc.id); diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 80e00159bbc..3fa36301d9c 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -479,14 +479,14 @@ export class CompositeDragAndDropObserver extends Disposable { if (callbacks.onDragEnter) { const data = this.readDragData('composite') || this.readDragData('view'); if (data) { - callbacks.onDragEnter({ eventData: e, dragAndDropData: data! }); + callbacks.onDragEnter({ eventData: e, dragAndDropData: data }); } } }, onDragLeave: e => { const data = this.readDragData('composite') || this.readDragData('view'); if (callbacks.onDragLeave && data) { - callbacks.onDragLeave({ eventData: e, dragAndDropData: data! }); + callbacks.onDragLeave({ eventData: e, dragAndDropData: data }); } }, onDrop: e => { @@ -496,10 +496,10 @@ export class CompositeDragAndDropObserver extends Disposable { return; } - callbacks.onDrop({ eventData: e, dragAndDropData: data! }); + callbacks.onDrop({ eventData: e, dragAndDropData: data }); // Fire drag event in case drop handler destroys the dragged element - this.onDragEnd.fire({ eventData: e, dragAndDropData: data! }); + this.onDragEnd.fire({ eventData: e, dragAndDropData: data }); } }, onDragOver: e => { @@ -511,7 +511,7 @@ export class CompositeDragAndDropObserver extends Disposable { return; } - callbacks.onDragOver({ eventData: e, dragAndDropData: data! }); + callbacks.onDragOver({ eventData: e, dragAndDropData: data }); } } })); @@ -552,7 +552,7 @@ export class CompositeDragAndDropObserver extends Disposable { return; } - this.onDragEnd.fire({ eventData: e, dragAndDropData: data! }); + this.onDragEnd.fire({ eventData: e, dragAndDropData: data }); }, onDragEnter: e => { if (callbacks.onDragEnter) { @@ -562,7 +562,7 @@ export class CompositeDragAndDropObserver extends Disposable { } if (data) { - callbacks.onDragEnter({ eventData: e, dragAndDropData: data! }); + callbacks.onDragEnter({ eventData: e, dragAndDropData: data }); } } }, @@ -572,7 +572,7 @@ export class CompositeDragAndDropObserver extends Disposable { return; } - callbacks.onDragLeave?.({ eventData: e, dragAndDropData: data! }); + callbacks.onDragLeave?.({ eventData: e, dragAndDropData: data }); }, onDrop: e => { if (callbacks.onDrop) { @@ -581,10 +581,10 @@ export class CompositeDragAndDropObserver extends Disposable { return; } - callbacks.onDrop({ eventData: e, dragAndDropData: data! }); + callbacks.onDrop({ eventData: e, dragAndDropData: data }); // Fire drag event in case drop handler destroys the dragged element - this.onDragEnd.fire({ eventData: e, dragAndDropData: data! }); + this.onDragEnd.fire({ eventData: e, dragAndDropData: data }); } }, onDragOver: e => { @@ -594,7 +594,7 @@ export class CompositeDragAndDropObserver extends Disposable { return; } - callbacks.onDragOver({ eventData: e, dragAndDropData: data! }); + callbacks.onDragOver({ eventData: e, dragAndDropData: data }); } } })); diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 2c8cb8f9840..e1945a1cbc2 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -421,7 +421,7 @@ export class CompoisteBarActionViewItem extends BaseActionViewItem { return; } - const hoverPosition = this.options.hoverOptions!.position(); + const hoverPosition = this.options.hoverOptions.position(); this.lastHover = this.hoverService.showHover({ target: this.container, content: this.computeTitle(), diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index c50d1aa9b75..8b37f345733 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -1110,12 +1110,12 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { if (!horizontalOpenerTimeout && openHorizontalPosition !== undefined) { lastOpenHorizontalPosition = openHorizontalPosition; - horizontalOpenerTimeout = setTimeout(() => openPartAtPosition(openHorizontalPosition!), 200); + horizontalOpenerTimeout = setTimeout(() => openPartAtPosition(openHorizontalPosition), 200); } if (!verticalOpenerTimeout && openVerticalPosition !== undefined) { lastOpenVerticalPosition = openVerticalPosition; - verticalOpenerTimeout = setTimeout(() => openPartAtPosition(openVerticalPosition!), 200); + verticalOpenerTimeout = setTimeout(() => openPartAtPosition(openVerticalPosition), 200); } }, onDragLeave: () => clearAllTimeouts(), diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index ec276e02f5a..6dae52689e6 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -115,7 +115,7 @@ export class SidebarPart extends AbstractPaneCompositePart { this.updateTitleArea(); const id = this.getActiveComposite()?.getId(); if (id) { - this.onTitleAreaUpdate(id!); + this.onTitleAreaUpdate(id); } this.updateActivityBarVisiblity(); this.rememberActivityBarVisiblePosition(); diff --git a/src/vs/workbench/browser/parts/views/viewFilter.ts b/src/vs/workbench/browser/parts/views/viewFilter.ts index cf47403d4cc..3e5763a2203 100644 --- a/src/vs/workbench/browser/parts/views/viewFilter.ts +++ b/src/vs/workbench/browser/parts/views/viewFilter.ts @@ -166,8 +166,8 @@ export class FilterWidget extends Widget { if (this.options.text) { inputBox.value = this.options.text; } - this._register(inputBox.onDidChange(filter => this.delayedFilterUpdate.trigger(() => this.onDidInputChange(inputBox!)))); - this._register(DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: any) => this.onInputKeyDown(e, inputBox!))); + this._register(inputBox.onDidChange(filter => this.delayedFilterUpdate.trigger(() => this.onDidInputChange(inputBox)))); + this._register(DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: any) => this.onInputKeyDown(e, inputBox))); this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_DOWN, this.handleKeyboardEvent)); this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_UP, this.handleKeyboardEvent)); this._register(DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.CLICK, (e) => { diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index cdbae82d83b..5f3a2df9d06 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -268,7 +268,7 @@ export class AccessibleView extends Disposable { getAnchor: () => { return { x: (getActiveWindow().innerWidth / 2) - ((Math.min(this._layoutService.activeContainerDimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.activeContainerOffset.quickPickTop }; }, render: (container) => { container.classList.add('accessible-view-container'); - return this._render(provider!, container, showAccessibleViewHelp); + return this._render(provider, container, showAccessibleViewHelp); }, onHide: () => { if (!showAccessibleViewHelp) { @@ -512,7 +512,7 @@ export class AccessibleView extends Disposable { if (e.keyCode === KeyCode.Escape || shouldHide(e.browserEvent, this._keybindingService, this._configurationService)) { hide(e); } else if (e.keyCode === KeyCode.KeyH && provider.options.readMoreUrl) { - const url: string = provider.options.readMoreUrl!; + const url: string = provider.options.readMoreUrl; alert(AccessibilityHelpNLS.openingDocs); this._openerService.open(URI.parse(url)); e.preventDefault(); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 9dd8bf76be7..87375411120 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -191,7 +191,7 @@ class ChatAccessibleViewContribution extends Disposable { accessibleViewService.show({ id: AccessibleViewProviderId.Chat, verbositySettingKey: AccessibilityVerbositySettingId.Chat, - provideContent(): string { return responseContent!; }, + provideContent(): string { return responseContent; }, onClose() { verifiedWidget.reveal(focusedItem); if (chatInputFocused) { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index ee8045df6c3..4c2146167f9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -107,8 +107,8 @@ export class ChatEditor extends EditorPane { if (this._memento && this._viewState) { const widgetViewState = this.widget.getViewState(); - this._viewState!.inputValue = widgetViewState.inputValue; - this._memento!.saveMemento(); + this._viewState.inputValue = widgetViewState.inputValue; + this._memento.saveMemento(); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 11780bb2479..7487035e490 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -157,7 +157,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } get inputEditor(): ICodeEditor { - return this.inputPart.inputEditor!; + return this.inputPart.inputEditor; } get inputUri(): URI { @@ -683,22 +683,22 @@ export class ChatWidget extends Disposable implements IChatWidget { } const width = this.bodyDimension?.width ?? this.container.offsetWidth; - const inputHeight = this.inputPart.layout(this._dynamicMessageLayoutData!.maxHeight, width); + const inputHeight = this.inputPart.layout(this._dynamicMessageLayoutData.maxHeight, width); const totalMessages = this.viewModel.getItems(); // grab the last N messages - const messages = totalMessages.slice(-this._dynamicMessageLayoutData!.numOfMessages); + const messages = totalMessages.slice(-this._dynamicMessageLayoutData.numOfMessages); const needsRerender = messages.some(m => m.currentRenderedHeight === undefined); const listHeight = needsRerender - ? this._dynamicMessageLayoutData!.maxHeight + ? this._dynamicMessageLayoutData.maxHeight : messages.reduce((acc, message) => acc + message.currentRenderedHeight!, 0); this.layout( Math.min( // we add an additional 18px in order to show that there is scrollable content inputHeight + listHeight + (totalMessages.length > 2 ? 18 : 0), - this._dynamicMessageLayoutData!.maxHeight + this._dynamicMessageLayoutData.maxHeight ), width ); diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index 1abbb3d22b9..e3712490d9a 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -401,11 +401,11 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi // This should be true, if the model is changing const now = Date.now(); const wordCount = countWords(_model.response.asString()); - const timeDiff = now - this._contentUpdateTimings!.loadingStartTime; + const timeDiff = now - this._contentUpdateTimings.loadingStartTime; const impliedWordLoadRate = this._contentUpdateTimings.lastWordCount / (timeDiff / 1000); this.trace('onDidChange', `Update- got ${this._contentUpdateTimings.lastWordCount} words over ${timeDiff}ms = ${impliedWordLoadRate} words/s. ${wordCount} words are now available.`); this._contentUpdateTimings = { - loadingStartTime: this._contentUpdateTimings!.loadingStartTime, + loadingStartTime: this._contentUpdateTimings.loadingStartTime, lastUpdateTime: now, impliedWordLoadRate, lastWordCount: wordCount 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 4e69d04418e..227df61de91 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -123,20 +123,20 @@ suite('Chat', () => { const session1 = testDisposables.add(testService.startSession('provider1', CancellationToken.None)); await session1.waitForInitialization(); - session1!.addRequest({ parts: [], text: 'request 1' }, { message: 'request 1', variables: {} }); + session1.addRequest({ parts: [], text: 'request 1' }, { message: 'request 1', variables: {} }); const session2 = testDisposables.add(testService.startSession('provider2', CancellationToken.None)); await session2.waitForInitialization(); - session2!.addRequest({ parts: [], text: 'request 2' }, { message: 'request 2', variables: {} }); + session2.addRequest({ parts: [], text: 'request 2' }, { message: 'request 2', variables: {} }); storageService.flush(); const testService2 = testDisposables.add(instantiationService.createInstance(ChatService)); testDisposables.add(testService2.registerProvider(provider1)); testDisposables.add(testService2.registerProvider(provider2)); const retrieved1 = testDisposables.add(testService2.getOrRestoreSession(session1.sessionId)!); - await retrieved1!.waitForInitialization(); + await retrieved1.waitForInitialization(); const retrieved2 = testDisposables.add(testService2.getOrRestoreSession(session2.sessionId)!); - await retrieved2!.waitForInitialization(); + await retrieved2.waitForInitialization(); assert.deepStrictEqual(retrieved1.getRequests()[0]?.message.text, 'request 1'); assert.deepStrictEqual(retrieved2.getRequests()[0]?.message.text, 'request 2'); }); diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts index b5c575a093b..10c5c92bd46 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts @@ -433,7 +433,7 @@ class DocumentSymbolsOutlineCreator implements IOutlineCreator accessor.get(IInstantiationService).createInstance(DocumentSymbolsOutline, editor!, target, firstLoadBarrier)); + const result = editor.invokeWithinContext(accessor => accessor.get(IInstantiationService).createInstance(DocumentSymbolsOutline, editor, target, firstLoadBarrier)); await firstLoadBarrier.wait(); return result; } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts index 10c45a6f9df..035f9057c37 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts @@ -261,7 +261,7 @@ export class CommentThreadBody extends D this._parentEditor, this._commentThread, comment, - this._pendingEdits ? this._pendingEdits[comment.uniqueIdInThread!] : undefined, + this._pendingEdits ? this._pendingEdits[comment.uniqueIdInThread] : undefined, this.owner, this.parentResourceUri, this._parentCommentThreadWidget, diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index 88c3daef30e..d2fa2c78e8a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -875,9 +875,9 @@ export class CommentController implements IEditorContribution { continueOnCommentText = this._inProcessContinueOnComments.get(e.owner)?.splice(continueOnCommentIndex, 1)[0].body; } - const pendingCommentText = (this._pendingNewCommentCache[e.owner] && this._pendingNewCommentCache[e.owner][thread.threadId!]) + const pendingCommentText = (this._pendingNewCommentCache[e.owner] && this._pendingNewCommentCache[e.owner][thread.threadId]) ?? continueOnCommentText; - const pendingEdits = this._pendingEditsCache[e.owner] && this._pendingEditsCache[e.owner][thread.threadId!]; + const pendingEdits = this._pendingEditsCache[e.owner] && this._pendingEditsCache[e.owner][thread.threadId]; this.displayCommentThread(e.owner, thread, pendingCommentText, pendingEdits); this._commentInfos.filter(info => info.owner === e.owner)[0].threads.push(thread); this.tryUpdateReservedSpace(); @@ -1234,12 +1234,12 @@ export class CommentController implements IEditorContribution { info.threads.forEach(thread => { let pendingComment: string | undefined = undefined; if (providerCacheStore) { - pendingComment = providerCacheStore[thread.threadId!]; + pendingComment = providerCacheStore[thread.threadId]; } let pendingEdits: { [key: number]: string } | undefined = undefined; if (providerEditsCacheStore) { - pendingEdits = providerEditsCacheStore[thread.threadId!]; + pendingEdits = providerEditsCacheStore[thread.threadId]; } this.displayCommentThread(info.owner, thread, pendingComment, pendingEdits); @@ -1288,10 +1288,10 @@ export class CommentController implements IEditorContribution { this._pendingNewCommentCache[zone.owner] = {}; } - this._pendingNewCommentCache[zone.owner][zone.commentThread.threadId!] = pendingNewComment; + this._pendingNewCommentCache[zone.owner][zone.commentThread.threadId] = pendingNewComment; } else { if (providerNewCommentCacheStore) { - delete providerNewCommentCacheStore[zone.commentThread.threadId!]; + delete providerNewCommentCacheStore[zone.commentThread.threadId]; } } @@ -1301,9 +1301,9 @@ export class CommentController implements IEditorContribution { if (!providerEditsCacheStore) { this._pendingEditsCache[zone.owner] = {}; } - this._pendingEditsCache[zone.owner][zone.commentThread.threadId!] = pendingEdits; + this._pendingEditsCache[zone.owner][zone.commentThread.threadId] = pendingEdits; } else if (providerEditsCacheStore) { - delete providerEditsCacheStore[zone.commentThread.threadId!]; + delete providerEditsCacheStore[zone.commentThread.threadId]; } zone.dispose(); diff --git a/src/vs/workbench/contrib/comments/common/commentModel.ts b/src/vs/workbench/contrib/comments/common/commentModel.ts index a2c31f03758..9a6d8786372 100644 --- a/src/vs/workbench/contrib/comments/common/commentModel.ts +++ b/src/vs/workbench/contrib/comments/common/commentModel.ts @@ -53,7 +53,7 @@ export class ResourceWithCommentThreads { public static createCommentNode(owner: string, resource: URI, commentThread: CommentThread): CommentNode { const { threadId, comments, range } = commentThread; - const commentNodes: CommentNode[] = comments!.map(comment => new CommentNode(owner, threadId!, resource, comment, range, commentThread.state)); + const commentNodes: CommentNode[] = comments!.map(comment => new CommentNode(owner, threadId, resource, comment, range, commentThread.state)); if (commentNodes.length > 1) { commentNodes[0].replies = commentNodes.slice(1, commentNodes.length); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 5d801af174d..6c99db87599 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -245,7 +245,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ let replacement: EditorInput | IResourceEditorInput; if (possibleEditors.defaultEditor) { const viewType = possibleEditors.defaultEditor.id; - replacement = CustomEditorInput.create(this.instantiationService, newResource, viewType!, group); + replacement = CustomEditorInput.create(this.instantiationService, newResource, viewType, group); } else { replacement = { resource: newResource, options: { override: DEFAULT_EDITOR_ASSOCIATION.id } }; } diff --git a/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts b/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts index e62b646de33..1423fc37af5 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts @@ -46,7 +46,7 @@ export class CustomEditorModelManager implements ICustomEditorModelManager { return { object: model, dispose: createSingleCallFunction(() => { - if (--entry!.counter <= 0) { + if (--entry.counter <= 0) { entry.model.then(x => x.dispose()); this._references.delete(key); } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index c3dea1cac40..170f997e285 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -368,7 +368,7 @@ export class SelectionToReplAction extends EditorAction { text = editor.getModel().getValueInRange(selection); } - await session.addReplExpression(viewModel.focusedStackFrame!, text); + await session.addReplExpression(viewModel.focusedStackFrame, text); await viewsService.openView(REPL_VIEW_ID, false); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 07116a63fa3..73750df8fb6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -387,7 +387,7 @@ export class DebugService implements IDebugService { const values = await Promise.all(compound.configurations.map(configData => { const name = typeof configData === 'string' ? configData : configData.name; - if (name === compound!.name) { + if (name === compound.name) { return Promise.resolve(false); } @@ -408,7 +408,7 @@ export class DebugService implements IDebugService { if (launchesMatchingConfigData.length === 1) { launchForName = launchesMatchingConfigData[0]; } else { - throw new Error(nls.localize('noFolderWithName', "Can not find folder with name '{0}' for configuration '{1}' in compound '{2}'.", configData.folder, configData.name, compound!.name)); + throw new Error(nls.localize('noFolderWithName', "Can not find folder with name '{0}' for configuration '{1}' in compound '{2}'.", configData.folder, configData.name, compound.name)); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 847075cce33..b7502e41c74 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -322,7 +322,7 @@ export class DebugSession implements IDebugSession, IDisposable { await this.raw.start(); this.registerListeners(); - await this.raw!.initialize({ + await this.raw.initialize({ clientID: 'vscode', clientName: this.productService.nameLong, adapterID: this.configuration.type, diff --git a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts index f32915eedf1..f196bb14aa4 100644 --- a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts +++ b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts @@ -472,7 +472,7 @@ export class DisassemblyView extends EditorPane { const currentLine: IRange = { startLineNumber: instruction.line, startColumn: instruction.column ?? 0, - endLineNumber: instruction.endLine ?? instruction.line!, + endLineNumber: instruction.endLine ?? instruction.line, endColumn: instruction.endColumn ?? 0, }; @@ -788,7 +788,7 @@ class InstructionRenderer extends Disposable implements ITableRenderer this.rerenderBackground(instruction, sourcecode, currentElement.element)), - addStandardDisposableListener(sourcecode, 'dblclick', () => this.openSourceCode(currentElement.element?.instruction!)), + addStandardDisposableListener(sourcecode, 'dblclick', () => this.openSourceCode(currentElement.element?.instruction)), ]; return { currentElement, instruction, sourcecode, cellDisposable, disposables }; @@ -893,7 +893,7 @@ class InstructionRenderer extends Disposable implements ITableRenderer { - this.refreshReplElements(session!.getReplElements().length === 0); + this.refreshReplElements(session.getReplElements().length === 0); }); if (this.tree && treeInput !== session) { diff --git a/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts index 0268c9a4006..43c4f7d0b11 100644 --- a/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts @@ -86,7 +86,7 @@ suite('Debug - Base Debug View', () => { const stackFrame = new StackFrame(thread, 1, null!, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: undefined!, endColumn: undefined! }, 0, true); const scope = new Scope(stackFrame, 1, 'local', 1, false, 10, 10); - let variable = new Variable(session, 1, scope, 2, 'foo', 'bar.foo', undefined!, 0, 0, undefined, {}, 'string'); + let variable = new Variable(session, 1, scope, 2, 'foo', 'bar.foo', undefined, 0, 0, undefined, {}, 'string'); let expression = $('.'); let name = $('.'); let value = $('.'); diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index b655b4b6603..1b7bc10db56 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -39,8 +39,8 @@ function addBreakpointsAndCheckEvents(model: DebugModel, uri: uri, data: IBreakp eventCount++; dispose(toDispose); for (let i = 0; i < data.length; i++) { - assert.strictEqual(e!.added![i] instanceof Breakpoint, true); - assert.strictEqual((e!.added![i] as Breakpoint).lineNumber, data[i].lineNumber); + assert.strictEqual(e.added![i] instanceof Breakpoint, true); + assert.strictEqual((e.added![i] as Breakpoint).lineNumber, data[i].lineNumber); } }); const bps = model.addBreakpoints(uri, data); diff --git a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts index bf3203a19c9..f5ec3d56c5a 100644 --- a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts @@ -33,7 +33,7 @@ const mockWorkspaceContextService = { } as any; export function createTestSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession { - return new DebugSession(generateUuid(), { resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, { + return new DebugSession(generateUuid(), { resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined, model, options, { getViewModel(): any { return { updateViews(): void { @@ -445,7 +445,7 @@ suite('Debug - CallStack', () => { override get state(): State { return State.Stopped; } - }(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!, undefined!, new NullLogService()); + }(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!, undefined!, new NullLogService()); disposables.add(session); const runningSession = createTestSession(model); diff --git a/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts index 17f064f318a..c45e6dba417 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts @@ -49,8 +49,8 @@ suite('Debug - Hover', () => { override getChildren(): Promise { return Promise.resolve([variableB]); } - }(session, 1, scope, 2, 'A', 'A', undefined!, 0, 0, undefined, {}, 'string'); - const variableB = new Variable(session, 1, scope, 2, 'B', 'A.B', undefined!, 0, 0, undefined, {}, 'string'); + }(session, 1, scope, 2, 'A', 'A', undefined, 0, 0, undefined, {}, 'string'); + const variableB = new Variable(session, 1, scope, 2, 'B', 'A.B', undefined, 0, 0, undefined, {}, 'string'); assert.strictEqual(await findExpressionInStackFrame(stackFrame, []), undefined); assert.strictEqual(await findExpressionInStackFrame(stackFrame, ['A']), variableA); diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts b/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts index e8585ae96e5..6ba3511f0ee 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts @@ -221,7 +221,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes })); if (this.machineClient === undefined) { - this.machineClient = new UserDataSyncMachinesService(this.environmentService, this.fileService, this.storageService, this.storeClient!, this.logService, this.productService); + this.machineClient = new UserDataSyncMachinesService(this.environmentService, this.fileService, this.storageService, this.storeClient, this.logService, this.productService); } // If we already have an existing auth session in memory, use that diff --git a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index b9c28057f04..b05e563275c 100644 --- a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -443,7 +443,7 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { actions.push(new Action( 'runtimeExtensionsEditor.action.copyId', - nls.localize('copy id', "Copy id ({0})", e.element!.description.identifier.value), + nls.localize('copy id', "Copy id ({0})", e.element.description.identifier.value), undefined, true, () => { @@ -457,7 +457,7 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { } actions.push(new Separator()); - if (e.element!.marketplaceInfo) { + if (e.element.marketplaceInfo) { actions.push(new Action('runtimeExtensionsEditor.action.disableWorkspace', nls.localize('disable workspace', "Disable (Workspace)"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo!, EnablementState.DisabledWorkspace))); actions.push(new Action('runtimeExtensionsEditor.action.disable', nls.localize('disable', "Disable"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo!, EnablementState.DisabledGlobally))); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts index d175546d78a..18274088c5b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts @@ -356,7 +356,7 @@ export class ExtensionRecommendationNotificationService extends Disposable imple const index = this.pendingNotificaitons.length; disposables.add(token.onCancellationRequested(() => this.pendingNotificaitons.splice(index, 1))); this.pendingNotificaitons.push({ recommendationsNotification, source, token }); - if (source !== RecommendationSource.EXE && source <= this.visibleNotification!.source) { + if (source !== RecommendationSource.EXE && source <= this.visibleNotification.source) { this.hideVisibleNotification(3000); } } else { @@ -404,7 +404,7 @@ export class ExtensionRecommendationNotificationService extends Disposable imple if (this.visibleNotification && !this.hideVisibleNotificationPromise) { const visibleNotification = this.visibleNotification; this.hideVisibleNotificationPromise = timeout(Math.max(timeInMillis - (Date.now() - visibleNotification.from), 0)); - this.hideVisibleNotificationPromise.then(() => visibleNotification!.recommendationsNotification.hide()); + this.hideVisibleNotificationPromise.then(() => visibleNotification.recommendationsNotification.hide()); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index eed284ef570..d6d13ae2850 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -766,7 +766,7 @@ export class UninstallAction extends ExtensionAction { try { await this.extensionsWorkbenchService.uninstall(this.extension); - alert(localize('uninstallExtensionComplete', "Please reload Visual Studio Code to complete the uninstallation of the extension {0}.", this.extension!.displayName)); + alert(localize('uninstallExtensionComplete', "Please reload Visual Studio Code to complete the uninstallation of the extension {0}.", this.extension.displayName)); } catch (error) { this.dialogService.error(getErrorMessage(error)); } @@ -2331,7 +2331,7 @@ export class ExtensionStatusAction extends ExtensionAction { if (this.extension.gallery && this.extension.state === ExtensionState.Uninstalled && !await this.extensionsWorkbenchService.canInstall(this.extension)) { if (this.extensionManagementServerService.localExtensionManagementServer || this.extensionManagementServerService.remoteExtensionManagementServer) { - const targetPlatform = await (this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.getTargetPlatform() : this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getTargetPlatform()); + const targetPlatform = await (this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getTargetPlatform() : this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getTargetPlatform()); const message = new MarkdownString(`${localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", this.extension.displayName || this.extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform))} [${localize('learn more', "Learn More")}](https://aka.ms/vscode-platform-specific-extensions)`); this.updateStatus({ icon: warningIcon, message }, true); return; @@ -2450,21 +2450,21 @@ export class ExtensionStatusAction extends ExtensionAction { const runningExtension = this.extensionService.extensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))[0]; const runningExtensionServer = runningExtension ? this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension)) : null; if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { - if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(this.extension.local!.manifest)) { + if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(this.extension.local.manifest)) { this.updateStatus({ icon: infoIcon, message: new MarkdownString(`${localize('enabled remotely', "This extension is enabled in the Remote Extension Host because it prefers to run there.")} [${localize('learn more', "Learn More")}](https://aka.ms/vscode-remote/developing-extensions/architecture)`) }, true); } return; } if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { - if (this.extensionManifestPropertiesService.prefersExecuteOnUI(this.extension.local!.manifest)) { + if (this.extensionManifestPropertiesService.prefersExecuteOnUI(this.extension.local.manifest)) { this.updateStatus({ icon: infoIcon, message: new MarkdownString(`${localize('enabled locally', "This extension is enabled in the Local Extension Host because it prefers to run there.")} [${localize('learn more', "Learn More")}](https://aka.ms/vscode-remote/developing-extensions/architecture)`) }, true); } return; } if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.webExtensionManagementServer) { - if (this.extensionManifestPropertiesService.canExecuteOnWeb(this.extension.local!.manifest)) { + if (this.extensionManifestPropertiesService.canExecuteOnWeb(this.extension.local.manifest)) { this.updateStatus({ icon: infoIcon, message: new MarkdownString(`${localize('enabled in web worker', "This extension is enabled in the Web Worker Extension Host because it prefers to run there.")} [${localize('learn more', "Learn More")}](https://aka.ms/vscode-remote/developing-extensions/architecture)`) }, true); } return; @@ -2743,7 +2743,7 @@ export abstract class AbstractInstallExtensionsInServerAction extends Action { private async onDidAccept(selectedItems: ReadonlyArray): Promise { if (selectedItems.length) { - const localExtensionsToInstall = selectedItems.filter(r => !!r.extension).map(r => r.extension!); + const localExtensionsToInstall = selectedItems.filter(r => !!r.extension).map(r => r.extension); if (localExtensionsToInstall.length) { await this.progressService.withProgress( { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index bb3f66c7175..7787578f02a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -1105,7 +1105,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private getReloadStatus(extension: IExtension): string | undefined { const isUninstalled = extension.state === ExtensionState.Uninstalled; - const runningExtension = this.extensionService.extensions.find(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension!.identifier)); + const runningExtension = this.extensionService.extensions.find(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier)); if (isUninstalled) { const canRemoveRunningExtension = runningExtension && this.extensionService.canRemoveExtension(runningExtension); @@ -1135,15 +1135,15 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } if (this.extensionsServers.length > 1) { - const extensionInOtherServer = this.installed.filter(e => areSameExtensions(e.identifier, extension!.identifier) && e.server !== extension!.server)[0]; + const extensionInOtherServer = this.installed.filter(e => areSameExtensions(e.identifier, extension.identifier) && e.server !== extension.server)[0]; if (extensionInOtherServer) { // This extension prefers to run on UI/Local side but is running in remote - if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local!.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.localExtensionManagementServer) { + if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.localExtensionManagementServer) { return nls.localize('enable locally', "Please reload Visual Studio Code to enable this extension locally."); } // This extension prefers to run on Workspace/Remote side but is running in local - if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local!.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.remoteExtensionManagementServer) { + if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.remoteExtensionManagementServer) { return nls.localize('enable remote', "Please reload Visual Studio Code to enable this extension in {0}.", this.extensionManagementServerService.remoteExtensionManagementServer?.label); } } @@ -1153,13 +1153,13 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { // This extension prefers to run on UI/Local side but is running in remote - if (this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local!.manifest)) { + if (this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest)) { return nls.localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension."); } } if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { // This extension prefers to run on Workspace/Remote side but is running in local - if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local!.manifest)) { + if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest)) { return nls.localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension."); } } @@ -1181,7 +1181,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension const otherServer = extension.server ? extension.server === this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer : null; if (otherServer && extension.enablementState === EnablementState.DisabledByExtensionKind) { - const extensionInOtherServer = this.local.filter(e => areSameExtensions(e.identifier, extension!.identifier) && e.server === otherServer)[0]; + const extensionInOtherServer = this.local.filter(e => areSameExtensions(e.identifier, extension.identifier) && e.server === otherServer)[0]; // Same extension in other server exists and if (extensionInOtherServer && extensionInOtherServer.local && this.extensionEnablementService.isEnabled(extensionInOtherServer.local)) { return nls.localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension."); @@ -1754,7 +1754,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension const targetPlatform = extension.server ? await extension.server.extensionManagementService.getTargetPlatform() : undefined; const [gallery] = await this.galleryService.getExtensions([{ id: extension.gallery.identifier.id, version }], { targetPlatform }, CancellationToken.None); if (!gallery) { - throw new Error(nls.localize('not found', "Unable to install extension '{0}' because the requested version '{1}' is not found.", extension.gallery!.identifier.id, version)); + throw new Error(nls.localize('not found', "Unable to install extension '{0}' because the requested version '{1}' is not found.", extension.gallery.identifier.id, version)); } installOptions.installGivenVersion = true; diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts index 1a32f81a7db..4186a04ec35 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts @@ -107,7 +107,7 @@ suite('Extension Test', () => { properties = { type: ExtensionType.User, location: URI.file(`pub.${name}`), - identifier: { id: getGalleryExtensionId(manifest.publisher!, manifest.name!) }, + identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name!) }, targetPlatform: TargetPlatform.UNDEFINED, ...properties }; diff --git a/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts b/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts index 7baf8dd0aed..2e6373fe684 100644 --- a/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts +++ b/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts @@ -83,7 +83,7 @@ function registerOpenTerminalCommand(id: string, explorerKind: 'integrated' | 'e } } else if (externalTerminalService) { distinct(targets.map(({ stat }) => stat!.isDirectory ? stat!.resource.fsPath : dirname(stat!.resource.fsPath))).forEach(cwd => { - externalTerminalService!.openTerminal(config.terminal.external, cwd); + externalTerminalService.openTerminal(config.terminal.external, cwd); }); } }); diff --git a/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts b/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts index fb5b9a9286b..ca865ea076c 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts @@ -34,7 +34,7 @@ suite('Files - FileOnDiskContentProvider', () => { const content = await provider.provideTextContent(uri.with({ scheme: 'conflictResolution', query: JSON.stringify({ scheme: uri.scheme }) })); assert.ok(content); - assert.strictEqual(snapshotToString(content!.createSnapshot()), 'Hello Html'); + assert.strictEqual(snapshotToString(content.createSnapshot()), 'Hello Html'); assert.strictEqual(accessor.fileService.getLastReadFileUri().scheme, uri.scheme); assert.strictEqual(accessor.fileService.getLastReadFileUri().path, uri.path); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 8b140227cdd..11b8f8de857 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -503,7 +503,7 @@ export class InlineChatController implements IEditorContribution { this._historyUpdate(input); - const refer = this._session.session.slashCommands?.some(value => value.refer && input!.startsWith(`/${value.command}`)); + const refer = this._session.session.slashCommands?.some(value => value.refer && input.startsWith(`/${value.command}`)); if (refer) { this._log('[IE] seeing refer command, continuing outside editor', this._session.provider.debugName); this._editor.setSelection(this._session.wholeRange.value); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 14a780fd04a..36da2489a01 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -827,7 +827,7 @@ export class InlineChatWidget { if (commands.length === 0) { return; } - this._slashCommandDetails = commands.filter(c => c.command && c.detail).map(c => { return { command: c.command!, detail: c.detail! }; }); + this._slashCommandDetails = commands.filter(c => c.command && c.detail).map(c => { return { command: c.command, detail: c.detail! }; }); const selector: LanguageSelector = { scheme: this._inputModel.uri.scheme, pattern: this._inputModel.uri.path, language: this._inputModel.getLanguageId() }; this._slashCommands.add(this._languageFeaturesService.completionProvider.register(selector, new class implements CompletionItemProvider { diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index 5f28a60360b..e44cb29c562 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -371,7 +371,7 @@ registerAction2(class extends Action2 { const editorControl = editor?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; return { - notebookUri: editorInput.resource!, + notebookUri: editorInput.resource, inputUri: editorInput.inputResource, notebookEditorId: editorControl?.notebookEditor?.getId() }; diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index f422393ef5f..4ec8f8acc6d 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -526,7 +526,7 @@ export class InteractiveEditor extends EditorPane { })); this._widgetDisposableStore.add(editorModel.onDidChangeContent(() => { - const value = editorModel!.getValue(); + const value = editorModel.getValue(); if (this.input?.resource && value !== '') { (this.input as InteractiveEditorInput).historyService.replaceLast(this.input.resource, value); } diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts index ecc3caa3815..e8317893d1e 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts @@ -164,7 +164,7 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot } const resolvedLanguage = language ?? this._initLanguage ?? PLAINTEXT_LANGUAGE_ID; - this._interactiveDocumentService.willCreateInteractiveDocument(this.resource!, this.inputResource, resolvedLanguage); + this._interactiveDocumentService.willCreateInteractiveDocument(this.resource, this.inputResource, resolvedLanguage); this._inputModelRef = await this._textModelService.createModelReference(this.inputResource); return this._inputModelRef.object.textEditorModel; @@ -232,7 +232,7 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot this._notebookEditorInput?.dispose(); this._editorModelReference?.dispose(); this._editorModelReference = null; - this._interactiveDocumentService.willRemoveInteractiveDocument(this.resource!, this.inputResource); + this._interactiveDocumentService.willRemoveInteractiveDocument(this.resource, this.inputResource); this._inputModelRef?.dispose(); this._inputModelRef = null; super.dispose(); diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index b3a9bd0dab1..192fbd9563a 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -541,7 +541,7 @@ export class MarkersView extends FilterViewPane implements IMarkersView { this.markersModel.setResourceMarkers(groupBy(readMarkers(), compareMarkersByUri).map(group => [group[0].resource, group])); disposables.push(Event.debounce>(this.markerService.onMarkerChanged, (resourcesMap, resources) => { resourcesMap = resourcesMap || new ResourceMap(); - resources.forEach(resource => resourcesMap!.set(resource, resource)); + resources.forEach(resource => resourcesMap.set(resource, resource)); return resourcesMap; }, 64)(resourcesMap => { this.markersModel.setResourceMarkers([...resourcesMap.values()].map(resource => [resource, readMarkers(resource)])); @@ -807,7 +807,7 @@ export class MarkersView extends FilterViewPane implements IMarkersView { e.browserEvent.stopPropagation(); this.contextMenuService.showContextMenu({ - getAnchor: () => e.anchor!, + getAnchor: () => e.anchor, menuId: MenuId.ProblemsPanelContext, contextKeyService: this.widget.contextKeyService, getActions: () => this.getMenuActions(element), diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer.ts index 07b8641eba8..bbbe978f302 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer.ts @@ -45,7 +45,7 @@ export class ScrollSynchronizer extends Disposable { if (this.shouldAlignResult) { this.inputResultView.editor.setScrollTop(this.input1View.editor.getScrollTop(), ScrollType.Immediate); } else { - const mappingInput1Result = this.model!.input1ResultMapping.get(); + const mappingInput1Result = this.model.input1ResultMapping.get(); this.synchronizeScrolling(this.input1View.editor, this.inputResultView.editor, mappingInput1Result); } @@ -54,7 +54,7 @@ export class ScrollSynchronizer extends Disposable { if (this.shouldAlignBase) { this.baseView.get()?.editor.setScrollTop(this.input1View.editor.getScrollTop(), ScrollType.Immediate); } else { - const mapping = new DocumentLineRangeMap(this.model!.baseInput1Diffs.get(), -1).reverse(); + const mapping = new DocumentLineRangeMap(this.model.baseInput1Diffs.get(), -1).reverse(); this.synchronizeScrolling(this.input1View.editor, baseView.editor, mapping); } } @@ -88,7 +88,7 @@ export class ScrollSynchronizer extends Disposable { if (this.shouldAlignResult) { this.inputResultView.editor.setScrollTop(this.input2View.editor.getScrollTop(), ScrollType.Immediate); } else { - const mappingInput2Result = this.model!.input2ResultMapping.get(); + const mappingInput2Result = this.model.input2ResultMapping.get(); this.synchronizeScrolling(this.input2View.editor, this.inputResultView.editor, mappingInput2Result); } @@ -97,7 +97,7 @@ export class ScrollSynchronizer extends Disposable { if (this.shouldAlignBase) { this.baseView.get()?.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); } else { - const mapping = new DocumentLineRangeMap(this.model!.baseInput2Diffs.get(), -1).reverse(); + const mapping = new DocumentLineRangeMap(this.model.baseInput2Diffs.get(), -1).reverse(); this.synchronizeScrolling(this.input2View.editor, baseView.editor, mapping); } } @@ -155,10 +155,10 @@ export class ScrollSynchronizer extends Disposable { this.input1View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); this.input2View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); } else { - const baseInput1Mapping = new DocumentLineRangeMap(this.model!.baseInput1Diffs.get(), -1); + const baseInput1Mapping = new DocumentLineRangeMap(this.model.baseInput1Diffs.get(), -1); this.synchronizeScrolling(baseView.editor, this.input1View.editor, baseInput1Mapping); - const baseInput2Mapping = new DocumentLineRangeMap(this.model!.baseInput2Diffs.get(), -1); + const baseInput2Mapping = new DocumentLineRangeMap(this.model.baseInput2Diffs.get(), -1); this.synchronizeScrolling(baseView.editor, this.input2View.editor, baseInput2Mapping); } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts b/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts index 651e9a29837..64c90739dea 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts @@ -53,7 +53,7 @@ export async function changeCellToKind(kind: CellKind, context: INotebookActionC cells: [{ cellKind: kind, source: text, - language: language!, + language: language, mime: mime ?? cell.mime, outputs: cell.model.outputs, metadata: cell.metadata, @@ -96,7 +96,7 @@ export async function changeCellToKind(kind: CellKind, context: INotebookActionC cells: [{ cellKind: kind, source: text, - language: language!, + language: language, mime: mime ?? cell.mime, outputs: cell.model.outputs, metadata: cell.metadata, diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index bee5a8db7c4..23e8dec4478 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -513,8 +513,8 @@ abstract class AbstractElementRenderer extends Disposable { this._metadataEditorContainer?.classList.add('diff'); - const originalMetadataModel = await this.textModelService.createModelReference(CellUri.generateCellPropertyUri(this.cell.originalDocument.uri, this.cell.original!.handle, Schemas.vscodeNotebookCellMetadata)); - const modifiedMetadataModel = await this.textModelService.createModelReference(CellUri.generateCellPropertyUri(this.cell.modifiedDocument.uri, this.cell.modified!.handle, Schemas.vscodeNotebookCellMetadata)); + const originalMetadataModel = await this.textModelService.createModelReference(CellUri.generateCellPropertyUri(this.cell.originalDocument.uri, this.cell.original.handle, Schemas.vscodeNotebookCellMetadata)); + const modifiedMetadataModel = await this.textModelService.createModelReference(CellUri.generateCellPropertyUri(this.cell.modifiedDocument.uri, this.cell.modified.handle, Schemas.vscodeNotebookCellMetadata)); this._metadataEditor.setModel({ original: originalMetadataModel.object.textEditorModel, modified: modifiedMetadataModel.object.textEditorModel @@ -541,7 +541,7 @@ abstract class AbstractElementRenderer extends Disposable { respondingToContentChange = false; })); - this._metadataEditorDisposeStore.add(this.cell.modified!.textModel.onDidChangeMetadata(() => { + this._metadataEditorDisposeStore.add(this.cell.modified.textModel.onDidChangeMetadata(() => { if (respondingToContentChange) { return; } @@ -1016,7 +1016,7 @@ export class DeletedElement extends SingleSideDiffElement { this.cell.layoutChange(); - this._outputLeftView = this.instantiationService.createInstance(OutputContainer, this.notebookEditor, this.notebookEditor.textModel!, this.cell, this.cell.original!, DiffSide.Original, this._outputViewContainer!); + this._outputLeftView = this.instantiationService.createInstance(OutputContainer, this.notebookEditor, this.notebookEditor.textModel!, this.cell, this.cell.original!, DiffSide.Original, this._outputViewContainer); this._register(this._outputLeftView); this._outputLeftView.render(); @@ -1138,7 +1138,7 @@ export class InsertElement extends SingleSideDiffElement { this.cell.layoutChange(); - this._outputRightView = this.instantiationService.createInstance(OutputContainer, this.notebookEditor, this.notebookEditor.textModel!, this.cell, this.cell.modified!, DiffSide.Modified, this._outputViewContainer!); + this._outputRightView = this.instantiationService.createInstance(OutputContainer, this.notebookEditor, this.notebookEditor.textModel!, this.cell, this.cell.modified!, DiffSide.Modified, this._outputViewContainer); this._register(this._outputRightView); this._outputRightView.render(); @@ -1370,16 +1370,16 @@ export class ModifiedElement extends AbstractElementRenderer { this._decorate(); })); - this._outputLeftContainer = DOM.append(this._outputViewContainer!, DOM.$('.output-view-container-left')); - this._outputRightContainer = DOM.append(this._outputViewContainer!, DOM.$('.output-view-container-right')); - this._outputMetadataContainer = DOM.append(this._outputViewContainer!, DOM.$('.output-view-container-metadata')); + this._outputLeftContainer = DOM.append(this._outputViewContainer, DOM.$('.output-view-container-left')); + this._outputRightContainer = DOM.append(this._outputViewContainer, DOM.$('.output-view-container-right')); + this._outputMetadataContainer = DOM.append(this._outputViewContainer, DOM.$('.output-view-container-metadata')); const outputModified = this.cell.checkIfOutputsModified(); const outputMetadataChangeOnly = outputModified && outputModified.kind === OutputComparison.Metadata - && this.cell.original!.outputs.length === 1 - && this.cell.modified!.outputs.length === 1 - && outputEqual(this.cell.original!.outputs[0], this.cell.modified!.outputs[0]) === OutputComparison.Metadata; + && this.cell.original.outputs.length === 1 + && this.cell.modified.outputs.length === 1 + && outputEqual(this.cell.original.outputs[0], this.cell.modified.outputs[0]) === OutputComparison.Metadata; if (outputModified && !outputMetadataChangeOnly) { const originalOutputRenderListener = this.notebookEditor.onDidDynamicOutputRendered(e => { @@ -1401,10 +1401,10 @@ export class ModifiedElement extends AbstractElementRenderer { } // We should use the original text model here - this._outputLeftView = this.instantiationService.createInstance(OutputContainer, this.notebookEditor, this.notebookEditor.textModel!, this.cell, this.cell.original!, DiffSide.Original, this._outputLeftContainer!); + this._outputLeftView = this.instantiationService.createInstance(OutputContainer, this.notebookEditor, this.notebookEditor.textModel!, this.cell, this.cell.original, DiffSide.Original, this._outputLeftContainer); this._outputLeftView.render(); this._register(this._outputLeftView); - this._outputRightView = this.instantiationService.createInstance(OutputContainer, this.notebookEditor, this.notebookEditor.textModel!, this.cell, this.cell.modified!, DiffSide.Modified, this._outputRightContainer!); + this._outputRightView = this.instantiationService.createInstance(OutputContainer, this.notebookEditor, this.notebookEditor.textModel!, this.cell, this.cell.modified, DiffSide.Modified, this._outputRightContainer); this._outputRightView.render(); this._register(this._outputRightView); @@ -1416,7 +1416,7 @@ export class ModifiedElement extends AbstractElementRenderer { this._outputMetadataContainer.style.top = `${this.cell.layoutInfo.rawOutputHeight}px`; // single output, metadata change, let's render a diff editor for metadata - this._outputMetadataEditor = this.instantiationService.createInstance(DiffEditorWidget, this._outputMetadataContainer!, { + this._outputMetadataEditor = this.instantiationService.createInstance(DiffEditorWidget, this._outputMetadataContainer, { ...fixedDiffEditorOptions, overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode(), readOnly: true, @@ -1431,8 +1431,8 @@ export class ModifiedElement extends AbstractElementRenderer { modifiedEditor: getOptimizedNestedCodeEditorWidgetOptions() }); this._register(this._outputMetadataEditor); - const originalOutputMetadataSource = JSON.stringify(this.cell.original!.outputs[0].metadata ?? {}, undefined, '\t'); - const modifiedOutputMetadataSource = JSON.stringify(this.cell.modified!.outputs[0].metadata ?? {}, undefined, '\t'); + const originalOutputMetadataSource = JSON.stringify(this.cell.original.outputs[0].metadata ?? {}, undefined, '\t'); + const modifiedOutputMetadataSource = JSON.stringify(this.cell.modified.outputs[0].metadata ?? {}, undefined, '\t'); const mode = this.languageService.createById('json'); const originalModel = this.modelService.createModel(originalOutputMetadataSource, mode, undefined, true); @@ -1485,7 +1485,7 @@ export class ModifiedElement extends AbstractElementRenderer { } updateSourceEditor(): void { - const modifiedCell = this.cell.modified!; + const modifiedCell = this.cell.modified; const lineCount = modifiedCell.textModel.textBuffer.getLineCount(); const lineHeight = this.notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; @@ -1520,7 +1520,7 @@ export class ModifiedElement extends AbstractElementRenderer { cell: this.cell }; - if (this.cell.modified!.textModel.getValue() !== this.cell.original!.textModel.getValue()) { + if (this.cell.modified.textModel.getValue() !== this.cell.original.textModel.getValue()) { this._inputToolbarContainer.style.display = 'block'; inputChanged.set(true); } else { @@ -1528,8 +1528,8 @@ export class ModifiedElement extends AbstractElementRenderer { inputChanged.set(false); } - this._register(this.cell.modified!.textModel.onDidChangeContent(() => { - if (this.cell.modified!.textModel.getValue() !== this.cell.original!.textModel.getValue()) { + this._register(this.cell.modified.textModel.onDidChangeContent(() => { + if (this.cell.modified.textModel.getValue() !== this.cell.original.textModel.getValue()) { this._inputToolbarContainer.style.display = 'block'; inputChanged.set(true); } else { @@ -1546,8 +1546,8 @@ export class ModifiedElement extends AbstractElementRenderer { } private async _initializeSourceDiffEditor() { - const originalCell = this.cell.original!; - const modifiedCell = this.cell.modified!; + const originalCell = this.cell.original; + const modifiedCell = this.cell.modified; const originalRef = await this.textModelService.createModelReference(originalCell.uri); const modifiedRef = await this.textModelService.createModelReference(modifiedCell.uri); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts index 7d6f1068778..fd3336fc32b 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts @@ -567,7 +567,7 @@ export class SingleSideDiffElementViewModel extends DiffElementViewModelBase { super(mainDocumentTextModel, original, modified, type, editorEventDispatcher, initData); this.type = type; - this._register(this.cellViewModel!.onDidChangeOutputLayout(() => { + this._register(this.cellViewModel.onDidChangeOutputLayout(() => { this._layout({ recomputeOutput: true }); })); } @@ -590,11 +590,11 @@ export class SingleSideDiffElementViewModel extends DiffElementViewModelBase { } getOutputOffsetInContainer(diffSide: DiffSide, index: number) { - return this.cellViewModel!.getOutputOffset(index); + return this.cellViewModel.getOutputOffset(index); } getOutputOffsetInCell(diffSide: DiffSide, index: number) { - const offsetInOutputsContainer = this.cellViewModel!.getOutputOffset(index); + const offsetInOutputsContainer = this.cellViewModel.getOutputOffset(index); return this._layoutInfo.editorHeight + this._layoutInfo.editorMargin @@ -620,7 +620,7 @@ export class SingleSideDiffElementViewModel extends DiffElementViewModelBase { } getCellByUri(cellUri: URI): IGenericCellViewModel { - return this.cellViewModel!; + return this.cellViewModel; } } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts index 2b713787dc0..5e57e97606f 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts @@ -282,7 +282,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD typeNavigationEnabled: true, paddingBottom: 0, // transformOptimization: (isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native', - styleController: (_suffix: string) => { return this._list!; }, + styleController: (_suffix: string) => { return this._list; }, overrideStyles: { listBackground: editorBackground, listActiveSelectionBackground: editorBackground, @@ -355,7 +355,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } private _registerOverviewRuler() { - this._overviewRuler = this._register(this.instantiationService.createInstance(NotebookDiffOverviewRuler, this, NotebookTextDiffEditor.ENTIRE_DIFF_OVERVIEW_WIDTH, this._overviewRulerContainer!)); + this._overviewRuler = this._register(this.instantiationService.createInstance(NotebookDiffOverviewRuler, this, NotebookTextDiffEditor.ENTIRE_DIFF_OVERVIEW_WIDTH, this._overviewRulerContainer)); } private _updateOutputsOffsetsInWebview(scrollTop: number, scrollHeight: number, activeWebview: BackLayerWebView, getActiveNestedCell: (diffElement: DiffElementViewModelBase) => DiffNestedCellViewModel | undefined, diffSide: DiffSide) { @@ -670,7 +670,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD instantiationService.createInstance(DiffNestedCellViewModel, originalCell), instantiationService.createInstance(DiffNestedCellViewModel, modifiedCell), 'modified', - eventDispatcher!, + eventDispatcher, initData )); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 2418917ad3c..10ae44a781d 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -308,16 +308,16 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { const viewState = options?.viewState ?? this._loadNotebookEditorViewState(input); // We might be moving the notebook widget between groups, and these services are tied to the group - this._widget.value!.setParentContextKeyService(this._contextKeyService); - this._widget.value!.setEditorProgressService(this._editorProgressService); + this._widget.value.setParentContextKeyService(this._contextKeyService); + this._widget.value.setEditorProgressService(this._editorProgressService); - await this._widget.value!.setModel(model.notebook, viewState, perf); + await this._widget.value.setModel(model.notebook, viewState, perf); const isReadOnly = !!input.isReadonly(); - await this._widget.value!.setOptions({ ...options, isReadOnly }); - this._widgetDisposableStore.add(this._widget.value!.onDidFocusWidget(() => this._onDidFocusWidget.fire())); - this._widgetDisposableStore.add(this._widget.value!.onDidBlurWidget(() => this._onDidBlurWidget.fire())); + await this._widget.value.setOptions({ ...options, isReadOnly }); + this._widgetDisposableStore.add(this._widget.value.onDidFocusWidget(() => this._onDidFocusWidget.fire())); + this._widgetDisposableStore.add(this._widget.value.onDidBlurWidget(() => this._onDidBlurWidget.fire())); - this._widgetDisposableStore.add(this._editorGroupService.createEditorDropTarget(this._widget.value!.getDomNode(), { + this._widgetDisposableStore.add(this._editorGroupService.createEditorDropTarget(this._widget.value.getDomNode(), { containsGroup: (group) => this.group?.id === group.id })); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index d8a3c555247..2de78f318de 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1057,7 +1057,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } private _registerNotebookOverviewRuler() { - this._notebookOverviewRuler = this._register(this.instantiationService.createInstance(NotebookOverviewRuler, this, this._notebookOverviewRulerContainer!)); + this._notebookOverviewRuler = this._register(this.instantiationService.createInstance(NotebookOverviewRuler, this, this._notebookOverviewRulerContainer)); } private _registerNotebookActionsToolbar() { @@ -1719,8 +1719,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } if (viewState?.scrollPosition !== undefined) { - this._list.scrollTop = viewState!.scrollPosition.top; - this._list.scrollLeft = viewState!.scrollPosition.left; + this._list.scrollTop = viewState.scrollPosition.top; + this._list.scrollLeft = viewState.scrollPosition.left; } else { this._list.scrollTop = 0; this._list.scrollLeft = 0; @@ -2376,7 +2376,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._cursorNavMode.set(true); await this.revealLineInViewAsync(cell, options.focusEditorLine); const editor = this._renderedEditors.get(cell)!; - const focusEditorLine = options.focusEditorLine!; + const focusEditorLine = options.focusEditorLine; editor?.setSelection({ startLineNumber: focusEditorLine, startColumn: 1, @@ -2934,7 +2934,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } const scrollHeight = this._list.scrollHeight; - this._webview!.element.style.height = `${scrollHeight + NOTEBOOK_WEBVIEW_BOUNDARY * 2}px`; + this._webview.element.style.height = `${scrollHeight + NOTEBOOK_WEBVIEW_BOUNDARY * 2}px`; const webviewTop = parseInt(this._list.webviewElement.domNode.style.top, 10); const top = !!webviewTop ? (0 - webviewTop) : 0; diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts index 3fbde15c36b..a86b7a9701a 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts @@ -212,7 +212,7 @@ export class NotebookProviderInfoStore extends Disposable { // untitled notebooks are disposed when they get saved. we should not hold a reference // to such a disposed notebook and therefore dispose the reference as well ref.object.notebook.onWillDispose(() => { - ref!.dispose(); + ref.dispose(); }); return { editor: NotebookEditorInput.getOrCreate(this._instantiationService, ref.object.resource, undefined, notebookProviderInfo.id), options }; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts index 5ab12dc04a3..a6feed982f2 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts @@ -335,7 +335,7 @@ export class CellDragAndDropController extends Disposable { const dragImage = dragImageProvider(); cellRoot.parentElement!.appendChild(dragImage); event.dataTransfer.setDragImage(dragImage, 0, 0); - setTimeout(() => cellRoot.parentElement!.removeChild(dragImage!), 0); // Comment this out to debug drag image layout + setTimeout(() => cellRoot.parentElement!.removeChild(dragImage), 0); // Comment this out to debug drag image layout }; for (const dragHandle of dragHandles) { templateData.templateDisposables.add(DOM.addDisposableListener(dragHandle, DOM.EventType.DRAG_START, onDragStart)); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts index e6b32bcef21..7636dbf004c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts @@ -380,7 +380,7 @@ export class MarkupCell extends Disposable { this.viewCell.editorHeight = editorHeight; this.focusEditorIfNeeded(); - this.renderedEditors.set(this.viewCell, this.editor!); + this.renderedEditors.set(this.viewCell, this.editor); } private viewUpdatePreview(): void { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 1e88981efdb..ddf248f2f81 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -498,7 +498,7 @@ async function webviewPreloads(ctx: PreloadContext) { function focusFirstFocusableOrContainerInOutput(cellOrOutputId: string, alternateId?: string) { const cellOutputContainer = $window.document.getElementById(cellOrOutputId) ?? - (alternateId ? $window.document.getElementById(alternateId!) : undefined); + (alternateId ? $window.document.getElementById(alternateId) : undefined); if (cellOutputContainer) { if (cellOutputContainer.contains($window.document.activeElement)) { return; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index f6aee10d65c..a95fe0b23da 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -560,7 +560,7 @@ export abstract class BaseCellViewModel extends Disposable { } const firstViewLineTop = this._textEditor.getTopForPosition(1, 1); - const lastViewLineTop = this._textEditor.getTopForPosition(this.textModel!.getLineCount(), this.textModel!.getLineLength(this.textModel!.getLineCount())); + const lastViewLineTop = this._textEditor.getTopForPosition(this.textModel.getLineCount(), this.textModel.getLineLength(this.textModel.getLineCount())); const selectionTop = this._textEditor.getTopForPosition(selection.startLineNumber, selection.startColumn); if (selectionTop === lastViewLineTop) { diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts index 5221b50b117..41136233486 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts @@ -674,7 +674,7 @@ export class KernelPickerMRUStrategy extends KernelPickerStrategyBase { tooltip: localize('learnMoreTooltip', 'Learn More'), }] : []; return { - id: typeof action.command! === 'string' ? action.command! : action.command!.id, + id: typeof action.command! === 'string' ? action.command : action.command!.id, label: action.label, description: action.description, command: action.command, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index c4ae3caaf3f..63108a1a7dc 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -1552,8 +1552,8 @@ export class SettingsEditor2 extends EditorPane { query = parsedQuery.query; parsedQuery.tags.forEach(tag => this.viewState.tagFilters!.add(tag)); parsedQuery.extensionFilters.forEach(extensionId => this.viewState.extensionFilters!.add(extensionId)); - parsedQuery.featureFilters!.forEach(feature => this.viewState.featureFilters!.add(feature)); - parsedQuery.idFilters!.forEach(id => this.viewState.idFilters!.add(id)); + parsedQuery.featureFilters.forEach(feature => this.viewState.featureFilters!.add(feature)); + parsedQuery.idFilters.forEach(id => this.viewState.idFilters!.add(id)); this.viewState.languageFilter = parsedQuery.languageFilter; } diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index 929d79543c0..dbec2f47685 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -88,7 +88,7 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu const viewsRegistry = Registry.as(Extensions.ViewsRegistry); if (viewContainer) { this.remoteExplorerService.enablePortsFeatures(); - viewsRegistry.registerViews([tunnelPanelDescriptor!], viewContainer); + viewsRegistry.registerViews([tunnelPanelDescriptor], viewContainer); } } else { this.contextKeyListener = this.contextKeyService.onDidChangeContext(e => { diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index 03a37d5384e..a85e27af833 100644 --- a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -747,7 +747,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider { cacheKey => cache.query(cacheKey), query => cache.load(query), cacheKey => cache.dispose(cacheKey), - previous! + previous ); } diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index d96c5abe1e6..0da433a0065 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -2314,7 +2314,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer await ProblemMatcherRegistry.onReady(); const taskSystemInfo: ITaskSystemInfo | undefined = this._getTaskSystemInfo(workspaceFolder.uri.scheme); const problemReporter = new ProblemReporter(this._outputChannel); - const parseResult = TaskConfig.parse(workspaceFolder, undefined, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, workspaceFolderConfiguration.config!, problemReporter, TaskConfig.TaskConfigSource.TasksJson, this._contextKeyService); + const parseResult = TaskConfig.parse(workspaceFolder, undefined, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, workspaceFolderConfiguration.config, problemReporter, TaskConfig.TaskConfigSource.TasksJson, this._contextKeyService); let hasErrors = false; if (!parseResult.validationStatus.isOK() && (parseResult.validationStatus.state !== ValidationState.Info)) { hasErrors = true; diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index a35f507a53f..5e0eed955c8 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -505,7 +505,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { if (task.configurationProperties.dependsOn) { const nextLiveDependencies = new Set(liveDependencies).add(task.getCommonTaskId()); for (const dependency of task.configurationProperties.dependsOn) { - const dependencyTask = await resolver.resolve(dependency.uri, dependency.task!); + const dependencyTask = await resolver.resolve(dependency.uri, dependency.task); if (dependencyTask) { this._adoptConfigurationForDependencyTask(dependencyTask, task); let taskResult; @@ -792,13 +792,13 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { return { exitCode: 0 }; } this._currentTask.resolvedVariables = resolvedVariables; - return this._executeInTerminal(task, trigger, new VariableResolver(lastTask.getVerifiedTask().workspaceFolder, lastTask.getVerifiedTask().systemInfo, resolvedVariables.variables, this._configurationResolverService), workspaceFolder!); + return this._executeInTerminal(task, trigger, new VariableResolver(lastTask.getVerifiedTask().workspaceFolder, lastTask.getVerifiedTask().systemInfo, resolvedVariables.variables, this._configurationResolverService), workspaceFolder); }, reason => { return Promise.reject(reason); }); } else { this._currentTask.resolvedVariables = lastTask.getVerifiedTask().resolvedVariables; - return this._executeInTerminal(task, trigger, new VariableResolver(lastTask.getVerifiedTask().workspaceFolder, lastTask.getVerifiedTask().systemInfo, lastTask.getVerifiedTask().resolvedVariables.variables, this._configurationResolverService), workspaceFolder!); + return this._executeInTerminal(task, trigger, new VariableResolver(lastTask.getVerifiedTask().workspaceFolder, lastTask.getVerifiedTask().systemInfo, lastTask.getVerifiedTask().resolvedVariables.variables, this._configurationResolverService), workspaceFolder); } } @@ -1125,7 +1125,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { if (shellLaunchConfig.args === undefined) { shellLaunchConfig.args = []; } - const shellArgs = Array.isArray(shellLaunchConfig.args!) ? shellLaunchConfig.args!.slice(0) : [shellLaunchConfig.args!]; + const shellArgs = Array.isArray(shellLaunchConfig.args) ? shellLaunchConfig.args.slice(0) : [shellLaunchConfig.args]; const toAdd: string[] = []; const basename = path.posix.basename((await this._pathService.fileURI(shellLaunchConfig.executable!)).path).toLowerCase(); const commandLine = this._buildShellCommandLine(platform, basename, shellOptions, command, originalCommand, args); @@ -1447,7 +1447,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { return [terminalToReuse.terminal, undefined]; } - this._terminalCreationQueue = this._terminalCreationQueue.then(() => this._doCreateTerminal(task, group, launchConfigs!)); + this._terminalCreationQueue = this._terminalCreationQueue.then(() => this._doCreateTerminal(task, group, launchConfigs)); const terminal: ITerminalInstance = (await this._terminalCreationQueue)!; if (task.configurationProperties.isBackground) { terminal.shellLaunchConfig.reconnectionProperties = { ownerId: ReconnectionType, data: { lastTask: task.getCommonTaskId(), group, label: task._label, id: task._id } }; @@ -1774,7 +1774,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { if (options.env) { result.env = Object.create(null); for (const key of Object.keys(options.env)) { - const value: any = options.env![key]; + const value: any = options.env[key]; if (Types.isString(value)) { result.env![key] = await this._resolveVariable(resolver, value); } else { diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 8ea82730adf..511383f85c1 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -1668,7 +1668,7 @@ namespace CustomTask { fillProperty(resultConfigProps, contributedConfigProps, 'promptOnClose'); fillProperty(resultConfigProps, contributedConfigProps, 'detail'); result.command.presentation = CommandConfiguration.PresentationOptions.fillProperties( - result.command.presentation!, contributedConfigProps.presentation)!; + result.command.presentation, contributedConfigProps.presentation)!; result.command.options = CommandOptions.fillProperties(result.command.options, contributedConfigProps.options); result.runOptions = RunOptions.fillProperties(result.runOptions, contributedTask.runOptions); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index d50bd624688..d993243770f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -332,7 +332,7 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { this._initInstanceListeners(instance); if (this._splitPaneContainer) { - this._splitPaneContainer!.split(instance, parentIndex + 1); + this._splitPaneContainer.split(instance, parentIndex + 1); } this._onInstancesChanged.fire(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 34aeba0eba5..81770243f91 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -359,7 +359,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce if (this._preLaunchInputQueue.length > 0 && this._process) { // Send any queued data that's waiting - newProcess!.input(this._preLaunchInputQueue.join('')); + newProcess.input(this._preLaunchInputQueue.join('')); this._preLaunchInputQueue.length = 0; } }), @@ -575,7 +575,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce } // The child process could already be terminated try { - this._process!.resize(cols, rows); + this._process.resize(cols, rows); } catch (error) { // We tried to write to a closed pipe / channel. if (error.code !== 'EPIPE' && error.code !== 'ERR_IPC_CHANNEL_CLOSED') { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts index 45ce76d7b7e..8cd412341c5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts @@ -387,7 +387,7 @@ export class TerminalTabbedView extends Disposable { } terminalContainer.focus(); if (!this._cancelContextMenu) { - openContextMenu(dom.getWindow(terminalContainer), event, this._terminalGroupService.activeInstance!, this._instanceMenu, this._contextMenuService); + openContextMenu(dom.getWindow(terminalContainer), event, this._terminalGroupService.activeInstance, this._instanceMenu, this._contextMenuService); } event.preventDefault(); event.stopImmediatePropagation(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 88b4f4e11ec..e9bf671e021 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -318,7 +318,7 @@ class TerminalTabsRenderer implements IListRenderer { + ad.add(dom.addDisposableListener(this.raw.element, dom.EventType.MOUSE_WHEEL, (e: IMouseWheelEvent) => { const classifier = MouseWheelClassifier.INSTANCE; classifier.acceptStandardWheelEvent(new StandardWheelEvent(e)); const value = classifier.isPhysicalMouseWheel(); diff --git a/src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts b/src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts index 9d197172aed..d6d672c9aca 100644 --- a/src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts @@ -1487,12 +1487,12 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon { flushOutput(this._timeline.terminal); } - if (this._timeline.tentativeCursor(buffer).x <= this._lastRow!.startingX) { + if (this._timeline.tentativeCursor(buffer).x <= this._lastRow.startingX) { this._timeline.addBoundary(buffer, new BackspacePrediction(this._timeline.terminal)); } else { // Backspace decrements our ability to go right. this._lastRow.endingX--; - this._timeline!.addPrediction(buffer, new BackspacePrediction(this._timeline.terminal)); + this._timeline.addPrediction(buffer, new BackspacePrediction(this._timeline.terminal)); } continue; diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index ffe03ea5df3..8185982f284 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -586,7 +586,7 @@ export class TestingOutputPeekController extends Disposable implements IEditorCo }); this.visible.set(true); - this.peek.value!.create(); + this.peek.value.create(); } if (subject instanceof MessageSubject) { diff --git a/src/vs/workbench/contrib/testing/common/testExplorerFilterState.ts b/src/vs/workbench/contrib/testing/common/testExplorerFilterState.ts index a92eb4ae7a4..3ab130d114f 100644 --- a/src/vs/workbench/contrib/testing/common/testExplorerFilterState.ts +++ b/src/vs/workbench/contrib/testing/common/testExplorerFilterState.ts @@ -123,7 +123,7 @@ export class TestExplorerFilterState extends Disposable implements ITestExplorer let globText = ''; let lastIndex = 0; for (const match of text.matchAll(tagRe)) { - let nextIndex = match.index! + match[0].length; + let nextIndex = match.index + match[0].length; const tag = match[0]; if (allTestFilterTerms.includes(tag as TestFilterTerm)) { diff --git a/src/vs/workbench/contrib/testing/common/testService.ts b/src/vs/workbench/contrib/testing/common/testService.ts index e43ee187d28..e26b2f2bd80 100644 --- a/src/vs/workbench/contrib/testing/common/testService.ts +++ b/src/vs/workbench/contrib/testing/common/testService.ts @@ -198,7 +198,7 @@ export const testsUnderUri = async function* (testService: ITestService, ident: } else if (!test.item.uri) { queue.push(test.children.values()); continue; - } else if (ident.extUri.isEqualOrParent(uri, test.item.uri!)) { + } else if (ident.extUri.isEqualOrParent(uri, test.item.uri)) { if (test.expand === TestItemExpandState.Expandable) { await testService.collection.expand(test.item.extId, 1); } @@ -206,7 +206,7 @@ export const testsUnderUri = async function* (testService: ITestService, ident: await waitForTestToBeIdle(testService, test); } queue.push(test.children.values()); - } else if (ident.extUri.isEqualOrParent(test.item.uri!, uri)) { + } else if (ident.extUri.isEqualOrParent(test.item.uri, uri)) { yield test; } } diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index b01a40523e8..d20b31e0641 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -754,8 +754,8 @@ export class TimelinePane extends ViewPane { function getNextMostRecentSource() { return sources - .filter(source => !source.nextItem!.done) - .reduce((previous, current) => (previous === undefined || current.nextItem!.value.timestamp >= previous.nextItem!.value.timestamp) ? current : previous, undefined!); + .filter(source => !source.nextItem.done) + .reduce((previous, current) => (previous === undefined || current.nextItem.value.timestamp >= previous.nextItem.value.timestamp) ? current : previous, undefined!); } let lastRelativeTime: string | undefined; diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index b0ada2acd81..d57afcd149a 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -380,7 +380,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu // if version != stored version, save version and date if (currentVersion !== lastKnownVersion) { - this.storageService.store('update/lastKnownVersion', currentVersion!, StorageScope.APPLICATION, StorageTarget.MACHINE); + this.storageService.store('update/lastKnownVersion', currentVersion, StorageScope.APPLICATION, StorageTarget.MACHINE); this.storageService.store('update/updateNotificationTime', currentMillis, StorageScope.APPLICATION, StorageTarget.MACHINE); } diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfileActions.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfileActions.ts index 08aac5046d1..3e83ff641d9 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfileActions.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfileActions.ts @@ -74,7 +74,7 @@ export class RenameProfileAction extends Action2 { value: profile.name, title: localize('select profile to rename', 'Rename {0}', profile.name), validateInput: async (value: string) => { - if (profile!.name !== value && userDataProfilesService.profiles.some(p => p.name === value)) { + if (profile.name !== value && userDataProfilesService.profiles.some(p => p.name === value)) { return localize('profileExists', "Profile with name {0} already exists.", value); } return undefined; diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index a4dd67a7f8b..5a716a0880e 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -589,7 +589,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo const wasEnabled = this.userDataSyncEnablementService.isResourceEnabled(item.id); const isEnabled = !!selectedItems.filter(selected => selected.id === item.id)[0]; if (wasEnabled !== isEnabled) { - this.userDataSyncEnablementService.setResourceEnablement(item.id!, isEnabled); + this.userDataSyncEnablementService.setResourceEnablement(item.id, isEnabled); } } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 641842dd0de..6514979da3d 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -283,7 +283,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD return; } const elementBox = this.element.getBoundingClientRect(); - const contextKeyService = this._contextKeyService!.createOverlay([ + const contextKeyService = this._contextKeyService.createOverlay([ ...Object.entries(data.context), [webviewIdContext, this.providedViewType], ]); diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts index e217b68758b..e44af5edc0c 100644 --- a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts +++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts @@ -199,7 +199,7 @@ export class WebviewViewPane extends ViewPane { // Re-dispatch all drag events back to the drop target to support view drag drop for (const event of [EventType.DRAG, EventType.DRAG_END, EventType.DRAG_ENTER, EventType.DRAG_LEAVE, EventType.DRAG_START]) { - this._webviewDisposables.add(addDisposableListener(this._webview.value.container!, event, e => { + this._webviewDisposables.add(addDisposableListener(this._webview.value.container, event, e => { e.preventDefault(); e.stopImmediatePropagation(); this.dropTargetElement.dispatchEvent(new DragEvent(e.type, e)); diff --git a/src/vs/workbench/services/activity/browser/activityService.ts b/src/vs/workbench/services/activity/browser/activityService.ts index ba6b8177f87..11657360f13 100644 --- a/src/vs/workbench/services/activity/browser/activityService.ts +++ b/src/vs/workbench/services/activity/browser/activityService.ts @@ -94,8 +94,8 @@ export class ActivityService extends Disposable implements IActivityService { } this._onDidChangeActivity.fire(viewContainer); return toDisposable(() => { - activities!.splice(activities!.indexOf(activity), 1); - if (activities!.length === 0) { + activities.splice(activities.indexOf(activity), 1); + if (activities.length === 0) { this.viewContainerActivities.delete(viewContainerId); } this._onDidChangeActivity.fire(viewContainer); @@ -159,8 +159,8 @@ export class ActivityService extends Disposable implements IActivityService { activities.push(activity); this._onDidChangeActivity.fire(id); return toDisposable(() => { - activities!.splice(activities!.indexOf(activity), 1); - if (activities!.length === 0) { + activities.splice(activities.indexOf(activity), 1); + if (activities.length === 0) { this.globalActivities.delete(id); } this._onDidChangeActivity.fire(id); diff --git a/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts index 1666e9af34b..e1913359976 100644 --- a/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts @@ -69,12 +69,12 @@ export class BuiltinExtensionsScannerService implements IBuiltinExtensionsScanne const id = getGalleryExtensionId(e.packageJSON.publisher, e.packageJSON.name); return { identifier: { id }, - location: uriIdentityService.extUri.joinPath(builtinExtensionsServiceUrl!, e.extensionPath), + location: uriIdentityService.extUri.joinPath(builtinExtensionsServiceUrl, e.extensionPath), type: ExtensionType.System, isBuiltin: true, manifest: e.packageNLS ? await this.localizeManifest(id, e.packageJSON, e.packageNLS) : e.packageJSON, - readmeUrl: e.readmePath ? uriIdentityService.extUri.joinPath(builtinExtensionsServiceUrl!, e.readmePath) : undefined, - changelogUrl: e.changelogPath ? uriIdentityService.extUri.joinPath(builtinExtensionsServiceUrl!, e.changelogPath) : undefined, + readmeUrl: e.readmePath ? uriIdentityService.extUri.joinPath(builtinExtensionsServiceUrl, e.readmePath) : undefined, + changelogUrl: e.changelogPath ? uriIdentityService.extUri.joinPath(builtinExtensionsServiceUrl, e.changelogPath) : undefined, targetPlatform: TargetPlatform.WEB, validations: [], isValid: true diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts index c56e6177daf..d491d528978 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts @@ -35,7 +35,7 @@ export class ExtensionManagementServerService implements IExtensionManagementSer this.remoteExtensionManagementServer = { id: 'remote', extensionManagementService, - get label() { return labelService.getHostLabel(Schemas.vscodeRemote, remoteAgentConnection!.remoteAuthority) || localize('remote', "Remote"); }, + get label() { return labelService.getHostLabel(Schemas.vscodeRemote, remoteAgentConnection.remoteAuthority) || localize('remote', "Remote"); }, }; } if (isWeb) { diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts index d539186ddb6..69a430503f9 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts @@ -44,7 +44,7 @@ export class ExtensionManagementServerService extends Disposable implements IExt this.remoteExtensionManagementServer = { id: 'remote', extensionManagementService, - get label() { return labelService.getHostLabel(Schemas.vscodeRemote, remoteAgentConnection!.remoteAuthority) || localize('remote', "Remote"); }, + get label() { return labelService.getHostLabel(Schemas.vscodeRemote, remoteAgentConnection.remoteAuthority) || localize('remote', "Remote"); }, }; } diff --git a/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts b/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts index d3293857cc0..e1b6f5880f8 100644 --- a/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts +++ b/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts @@ -272,7 +272,7 @@ export class WorkspaceExtensionsConfigService extends Disposable implements IWor } const result = await this.quickInputService.pick(folderPicks, { placeHolder, canPickMany: true }) || []; - return result.map(r => r.workspaceOrFolder!); + return result.map(r => r.workspaceOrFolder); } private async resolveWorkspaceExtensionConfig(workspaceConfigurationResource: URI): Promise { diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index ecc45f88c83..c6e6bc77fb7 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -284,7 +284,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { extension = await this.progressService.withProgress({ location: ProgressLocation.Notification, title: localize('Installing', "Installing Extension '{0}'...", galleryExtension.displayName || galleryExtension.name) - }, () => this.extensionManagementService.installFromGallery(galleryExtension!)); + }, () => this.extensionManagementService.installFromGallery(galleryExtension)); } catch (error) { this.notificationService.error(error); return; diff --git a/src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts index 6a64b83b174..41741515fad 100644 --- a/src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts @@ -1600,7 +1600,7 @@ suite('keyboardMapper', () => { assertNumpadKeyboardEvent(KeyCode.DownArrow, 'Numpad2', 'DownArrow', 'Down', 'down', '[ArrowDown]'); assertNumpadKeyboardEvent(KeyCode.PageDown, 'Numpad3', 'PageDown', 'PageDown', 'pagedown', '[PageDown]'); assertNumpadKeyboardEvent(KeyCode.LeftArrow, 'Numpad4', 'LeftArrow', 'Left', 'left', '[ArrowLeft]'); - assertNumpadKeyboardEvent(KeyCode.Unknown, 'Numpad5', 'NumPad5', null!, 'numpad5', '[Numpad5]'); + assertNumpadKeyboardEvent(KeyCode.Unknown, 'Numpad5', 'NumPad5', null, 'numpad5', '[Numpad5]'); assertNumpadKeyboardEvent(KeyCode.RightArrow, 'Numpad6', 'RightArrow', 'Right', 'right', '[ArrowRight]'); assertNumpadKeyboardEvent(KeyCode.Home, 'Numpad7', 'Home', 'Home', 'home', '[Home]'); assertNumpadKeyboardEvent(KeyCode.UpArrow, 'Numpad8', 'UpArrow', 'Up', 'up', '[ArrowUp]'); diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts index e8c8239b35f..f967fc38127 100644 --- a/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts @@ -156,7 +156,7 @@ export class LanguageDetectionSimpleWorker extends EditorSimpleWorker { } }); - return this._modelOperations!; + return this._modelOperations; } // This adjusts the language confidence scores to be more accurate based on: diff --git a/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts b/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts index b7b974d2d49..af8f0deed6b 100644 --- a/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts +++ b/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts @@ -727,7 +727,7 @@ suite('KeybindingsEditorModel', () => { assert.strictEqual(actual.command, expected.command); if (actual.when) { assert.ok(!!expected.when); - assert.strictEqual(actual.when.serialize(), expected.when!.serialize()); + assert.strictEqual(actual.when.serialize(), expected.when.serialize()); } else { assert.ok(!expected.when); } @@ -735,7 +735,7 @@ suite('KeybindingsEditorModel', () => { if (actual.resolvedKeybinding) { assert.ok(!!expected.resolvedKeybinding); - assert.strictEqual(actual.resolvedKeybinding.getLabel(), expected.resolvedKeybinding!.getLabel()); + assert.strictEqual(actual.resolvedKeybinding.getLabel(), expected.resolvedKeybinding.getLabel()); } else { assert.ok(!expected.resolvedKeybinding); } diff --git a/src/vs/workbench/services/preferences/test/common/preferencesValidation.test.ts b/src/vs/workbench/services/preferences/test/common/preferencesValidation.test.ts index 2d4110876ef..2b4f6f8d667 100644 --- a/src/vs/workbench/services/preferences/test/common/preferencesValidation.test.ts +++ b/src/vs/workbench/services/preferences/test/common/preferencesValidation.test.ts @@ -30,7 +30,7 @@ suite('Preferences Validation', () => { (message: string) => { const actual = this.validator(input); assert.ok(actual); - assert(actual!.indexOf(message) > -1, + assert(actual.indexOf(message) > -1, `Expected error of ${JSON.stringify(this.settings)} on \`${input}\` to contain ${message}. Got ${this.validator(input)}.`); } }; @@ -357,7 +357,7 @@ suite('Preferences Validation', () => { (message: string) => { const actual = this.validator(input); assert.ok(actual); - assert(actual!.indexOf(message) > -1, + assert(actual.indexOf(message) > -1, `Expected error of ${JSON.stringify(this.settings)} on \`${input}\` to contain ${message}. Got ${this.validator(input)}.`); } }; diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 57c48651cee..caad308a7d2 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -112,7 +112,7 @@ export class SearchService implements IRawSearchService { } return new Promise((c, e) => { - sortedSearch!.then(([result, rawMatches]) => { + sortedSearch.then(([result, rawMatches]) => { const serializedMatches = rawMatches.map(rawMatch => this.rawMatchToSearchItem(rawMatch)); this.sendProgress(serializedMatches, progressCallback, batchSize); c(result); @@ -327,7 +327,7 @@ export class SearchService implements IRawSearchService { } return [complete, results, { - cacheWasResolved: cachedRow!.resolved, + cacheWasResolved: cachedRow.resolved, cacheLookupTime, cacheFilterTime: cacheFilterSW.elapsed(), cacheEntryCount: cachedEntries.length diff --git a/src/vs/workbench/services/textMate/browser/tokenizationSupport/textMateTokenizationSupport.ts b/src/vs/workbench/services/textMate/browser/tokenizationSupport/textMateTokenizationSupport.ts index 6a7328d31cd..3a3b8078751 100644 --- a/src/vs/workbench/services/textMate/browser/tokenizationSupport/textMateTokenizationSupport.ts +++ b/src/vs/workbench/services/textMate/browser/tokenizationSupport/textMateTokenizationSupport.ts @@ -55,7 +55,7 @@ export class TextMateTokenizationSupport extends Disposable implements ITokeniza if (shouldMeasure) { const timeMS = sw!.elapsed(); if (isRandomSample || timeMS > 32) { - this._reportTokenizationTime!(timeMS, line.length, isRandomSample); + this._reportTokenizationTime(timeMS, line.length, isRandomSample); } } diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts index 4b42d9de86b..069b90a09d9 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts @@ -429,7 +429,7 @@ suite('Files - TextFileEditorModel', () => { // verify that we do not mark the model as saved when undoing once because // we never really had a saved state - await model.textEditorModel!.undo(); + await model.textEditorModel.undo(); assert.ok(model.isDirty()); model.dispose(); @@ -501,7 +501,7 @@ suite('Files - TextFileEditorModel', () => { await model.revert({ soft: true }); assert.strictEqual(model.isDirty(), false); assert.strictEqual(model.isModified(), false); - assert.strictEqual(model.textEditorModel!.getValue(), 'foo'); + assert.strictEqual(model.textEditorModel.getValue(), 'foo'); assert.strictEqual(eventCounter, 1); assert.ok(workingCopyEvent); @@ -515,7 +515,7 @@ suite('Files - TextFileEditorModel', () => { model.updateTextEditorModel(createTextBufferFactory('Hello Text')); assert.ok(model.isDirty()); - await model.textEditorModel!.undo(); + await model.textEditorModel.undo(); assert.ok(!model.isDirty()); }); diff --git a/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts index 7730d1149a5..e3f16f16065 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts @@ -92,7 +92,7 @@ suite('Files - TextFileService', () => { accessor.fileDialogService.setPickFileToSave(model.resource); await model.resolve(); - model!.textEditorModel!.setValue('foo'); + model.textEditorModel!.setValue('foo'); assert.ok(accessor.textFileService.isDirty(model.resource)); await accessor.textFileService.revert(model.resource); @@ -104,7 +104,7 @@ suite('Files - TextFileService', () => { (accessor.textFileService.files).add(model.resource, model); await model.resolve(); - model!.textEditorModel!.setValue('foo'); + model.textEditorModel!.setValue('foo'); assert.ok(accessor.textFileService.isDirty(model.resource)); let eventCounter = 0; diff --git a/src/vs/workbench/services/textfile/test/common/textFileService.io.test.ts b/src/vs/workbench/services/textfile/test/common/textFileService.io.test.ts index 02f4f1e4bc2..c01ba24353e 100644 --- a/src/vs/workbench/services/textfile/test/common/textFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/common/textFileService.io.test.ts @@ -375,7 +375,7 @@ export default function createSuite(params: Params) { let detectedEncoding = await detectEncodingByBOM(resource.fsPath); assert.strictEqual(detectedEncoding, UTF8_with_bom); - await service.write(resource, 'Hello World', { encoding: detectedEncoding! }); + await service.write(resource, 'Hello World', { encoding: detectedEncoding }); detectedEncoding = await detectEncodingByBOM(resource.fsPath); assert.strictEqual(detectedEncoding, UTF8_with_bom); }); @@ -603,7 +603,7 @@ export default function createSuite(params: Params) { } assert.ok(error); - assert.strictEqual(error!.textFileOperationResult, TextFileOperationResult.FILE_IS_BINARY); + assert.strictEqual(error.textFileOperationResult, TextFileOperationResult.FILE_IS_BINARY); const result = await service.readStream(URI.file(join(testDir, 'small.txt')), { acceptTextOnly: true }); assert.strictEqual(result.name, 'small.txt'); @@ -620,7 +620,7 @@ export default function createSuite(params: Params) { } assert.ok(error); - assert.strictEqual(error!.textFileOperationResult, TextFileOperationResult.FILE_IS_BINARY); + assert.strictEqual(error.textFileOperationResult, TextFileOperationResult.FILE_IS_BINARY); const result = await service.read(URI.file(join(testDir, 'small.txt')), { acceptTextOnly: true }); assert.strictEqual(result.name, 'small.txt'); diff --git a/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts b/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts index b3cf625c809..f2a811f9807 100644 --- a/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts +++ b/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts @@ -214,7 +214,7 @@ suite('Encoding', () => { if (err) { reject(err); } else { - resolve(importAMDNodeModule('@vscode/iconv-lite-umd', 'lib/iconv-lite-umd.js').then(iconv => iconv.decode(data, encoding.toNodeEncoding(fileEncoding!)))); + resolve(importAMDNodeModule('@vscode/iconv-lite-umd', 'lib/iconv-lite-umd.js').then(iconv => iconv.decode(data, encoding.toNodeEncoding(fileEncoding)))); } }); }); diff --git a/src/vs/workbench/services/userActivity/common/userActivityRegistry.ts b/src/vs/workbench/services/userActivity/common/userActivityRegistry.ts index ed07a4264f5..ea81e92b75d 100644 --- a/src/vs/workbench/services/userActivity/common/userActivityRegistry.ts +++ b/src/vs/workbench/services/userActivity/common/userActivityRegistry.ts @@ -10,7 +10,7 @@ class UserActivityRegistry { private todo: { new(s: IUserActivityService, ...args: any[]): any }[] = []; public add = (ctor: { new(s: IUserActivityService, ...args: any[]): any }) => { - this.todo!.push(ctor); + this.todo.push(ctor); }; public take(userActivityService: IUserActivityService, instantiation: IInstantiationService) { diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index 7850000eec8..45ea476ec9a 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -763,7 +763,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU disposables.add(Event.debounce(this.extensionManagementService.onDidInstallExtensions, () => undefined, 100)(async () => { const profileTemplate = await userDataProfileImportState.getProfileTemplateToImport(); if (profileTemplate.extensions) { - const profileExtensions = await that.instantiationService.createInstance(ExtensionsResource).getProfileExtensions(profileTemplate.extensions!); + const profileExtensions = await that.instantiationService.createInstance(ExtensionsResource).getProfileExtensions(profileTemplate.extensions); const installed = await this.extensionManagementService.getInstalled(ExtensionType.User); if (profileExtensions.every(e => installed.some(i => areSameExtensions(e.identifier, i.identifier)))) { disposable.dispose(); diff --git a/src/vs/workbench/services/views/browser/viewsService.ts b/src/vs/workbench/services/views/browser/viewsService.ts index 8e7a0e6b753..26cad586603 100644 --- a/src/vs/workbench/services/views/browser/viewsService.ts +++ b/src/vs/workbench/services/views/browser/viewsService.ts @@ -633,7 +633,7 @@ export class ViewsService extends Disposable implements IViewsService { } private createViewPaneContainer(element: HTMLElement, viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation, disposables: DisposableStore, instantiationService: IInstantiationService): ViewPaneContainer { - const viewPaneContainer: ViewPaneContainer = (instantiationService as any).createInstance(viewContainer.ctorDescriptor!.ctor, ...(viewContainer.ctorDescriptor!.staticArguments || [])); + const viewPaneContainer: ViewPaneContainer = (instantiationService as any).createInstance(viewContainer.ctorDescriptor.ctor, ...(viewContainer.ctorDescriptor.staticArguments || [])); this.viewPaneContainers.set(viewPaneContainer.getId(), viewPaneContainer); disposables.add(toDisposable(() => this.viewPaneContainers.delete(viewPaneContainer.getId()))); diff --git a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts index afe82979c8d..b070e642c67 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts @@ -599,8 +599,8 @@ suite('StoredFileWorkingCopy', function () { assert.strictEqual(savedCounter, 1); assert.strictEqual(saveErrorCounter, 0); assert.strictEqual(workingCopy.isDirty(), false); - assert.strictEqual((lastSaveEvent! as IStoredFileWorkingCopySaveEvent).reason, SaveReason.AUTO); - assert.strictEqual((lastSaveEvent! as IStoredFileWorkingCopySaveEvent).source, source); + assert.strictEqual(lastSaveEvent!.reason, SaveReason.AUTO); + assert.strictEqual(lastSaveEvent!.source, source); }); test('save (no errors) - multiple', async () => { diff --git a/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts b/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts index e63425a837c..af1ee0b5b00 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts @@ -416,7 +416,7 @@ suite('WorkingCopyFileService', () => { (accessor.textFileService.files).add(model.resource, model); await model.resolve(); - model!.textEditorModel!.setValue('foo'); + model.textEditorModel!.setValue('foo'); assert.ok(accessor.workingCopyService.isDirty(model.resource)); return model; })); @@ -476,7 +476,7 @@ suite('WorkingCopyFileService', () => { (accessor.textFileService.files).add(model.resource, model); await model.resolve(); - model!.textEditorModel!.setValue('foo'); + model.textEditorModel!.setValue('foo'); assert.ok(accessor.workingCopyService.isDirty(model.resource)); let eventCounter = 0; diff --git a/src/vs/workbench/test/browser/parts/editor/editor.test.ts b/src/vs/workbench/test/browser/parts/editor/editor.test.ts index 7ef086fac59..eaf064119d0 100644 --- a/src/vs/workbench/test/browser/parts/editor/editor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editor.test.ts @@ -146,8 +146,8 @@ suite('Workbench editor utils', () => { test('EditorResourceAccessor - typed inputs', () => { const service = accessor.untitledTextEditorService; - assert.ok(!EditorResourceAccessor.getCanonicalUri(null!)); - assert.ok(!EditorResourceAccessor.getOriginalUri(null!)); + assert.ok(!EditorResourceAccessor.getCanonicalUri(null)); + assert.ok(!EditorResourceAccessor.getOriginalUri(null)); const untitled = disposables.add(instantiationService.createInstance(UntitledTextEditorInput, service.create())); @@ -241,8 +241,8 @@ suite('Workbench editor utils', () => { test('EditorResourceAccessor - untyped inputs', () => { - assert.ok(!EditorResourceAccessor.getCanonicalUri(null!)); - assert.ok(!EditorResourceAccessor.getOriginalUri(null!)); + assert.ok(!EditorResourceAccessor.getCanonicalUri(null)); + assert.ok(!EditorResourceAccessor.getOriginalUri(null)); const untitledURI = URI.from({ scheme: Schemas.untitled, diff --git a/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts index 51ac2e42212..49957dbef22 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts @@ -1532,7 +1532,7 @@ suite('EditorGroupModel', () => { // Close Left assert.strictEqual(group.activeEditor, input3); - closeEditors(group, group.activeEditor!, CloseDirection.LEFT); + closeEditors(group, group.activeEditor, CloseDirection.LEFT); assert.strictEqual(group.activeEditor, input3); assert.strictEqual(group.count, 3); assert.strictEqual(group.getEditors(EditorsOrder.SEQUENTIAL)[0], input3); @@ -1549,7 +1549,7 @@ suite('EditorGroupModel', () => { // Close Right assert.strictEqual(group.activeEditor, input3); - closeEditors(group, group.activeEditor!, CloseDirection.RIGHT); + closeEditors(group, group.activeEditor, CloseDirection.RIGHT); assert.strictEqual(group.activeEditor, input3); assert.strictEqual(group.count, 3); assert.strictEqual(group.getEditors(EditorsOrder.SEQUENTIAL)[0], input1); diff --git a/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts b/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts index f01c4b3ce31..142d49dadc1 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts @@ -231,12 +231,12 @@ suite('EditorPane', () => { memento.saveEditorState(testGroup0, URI.file('/A'), { line: 3 }); res = memento.loadEditorState(testGroup0, URI.file('/A')); assert.ok(res); - assert.strictEqual(res!.line, 3); + assert.strictEqual(res.line, 3); memento.saveEditorState(testGroup1, URI.file('/A'), { line: 5 }); res = memento.loadEditorState(testGroup1, URI.file('/A')); assert.ok(res); - assert.strictEqual(res!.line, 5); + assert.strictEqual(res.line, 5); // Ensure capped at 3 elements memento.saveEditorState(testGroup0, URI.file('/B'), { line: 1 }); @@ -337,7 +337,7 @@ suite('EditorPane', () => { memento.saveEditorState(testGroup0, testInputA, { line: 3 }); res = memento.loadEditorState(testGroup0, testInputA); assert.ok(res); - assert.strictEqual(res!.line, 3); + assert.strictEqual(res.line, 3); // State removed when input gets disposed testInputA.dispose(); @@ -375,7 +375,7 @@ suite('EditorPane', () => { memento.saveEditorState(testGroup0, testInputA.resource, { line: 3 }); res = memento.loadEditorState(testGroup0, testInputA); assert.ok(res); - assert.strictEqual(res!.line, 3); + assert.strictEqual(res.line, 3); // State not yet removed when input gets disposed // because we used resource @@ -391,7 +391,7 @@ suite('EditorPane', () => { memento.saveEditorState(testGroup0, testInputB.resource, { line: 3 }); res = memento.loadEditorState(testGroup0, testInputB); assert.ok(res); - assert.strictEqual(res!.line, 3); + assert.strictEqual(res.line, 3); memento.clearEditorStateOnDispose(testInputB.resource, testInputB); From 0404d17da6e8f23f8eeef8b642b60a37e81af0c1 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 31 Jan 2024 00:40:20 +0000 Subject: [PATCH 0753/1897] Fix nls build script on line such as `localize2(...) ... localize(...)` (#203851) For #203842 I believe we need to apply the patches in character order. Otherwise the line ends up corrupted after the patching --- build/lib/nls.js | 42 +++++++++++++++++++++++++++--------------- build/lib/nls.ts | 41 ++++++++++++++++++++++++++--------------- 2 files changed, 53 insertions(+), 30 deletions(-) diff --git a/build/lib/nls.js b/build/lib/nls.js index 50825224b28..982f74bcf4d 100644 --- a/build/lib/nls.js +++ b/build/lib/nls.js @@ -311,36 +311,48 @@ var _nls; function patch(ts, moduleId, typescript, javascript, sourcemap) { const { localizeCalls, nlsExpressions } = analyze(ts, typescript, 'localize'); const { localizeCalls: localize2Calls, nlsExpressions: nls2Expressions } = analyze(ts, typescript, 'localize2'); - if (localizeCalls.length === 0) { + if (localizeCalls.length === 0 && localize2Calls.length === 0) { return { javascript, sourcemap }; } const nlsKeys = template(localizeCalls.map(lc => lc.key).concat(localize2Calls.map(lc => lc.key))); const nls = template(localizeCalls.map(lc => lc.value).concat(localize2Calls.map(lc => lc.value))); const smc = new sm.SourceMapConsumer(sourcemap); const positionFrom = mappedPositionFrom.bind(null, sourcemap.sources[0]); - let i = 0; // build patches + const toPatch = (c) => { + const start = lcFrom(smc.generatedPositionFor(positionFrom(c.range.start))); + const end = lcFrom(smc.generatedPositionFor(positionFrom(c.range.end))); + return { span: { start, end }, content: c.content }; + }; + let i = 0; const localizePatches = lazy(localizeCalls) .map(lc => ([ { range: lc.keySpan, content: '' + (i++) }, { range: lc.valueSpan, content: 'null' } ])) .flatten() - .map(c => { - const start = lcFrom(smc.generatedPositionFor(positionFrom(c.range.start))); - const end = lcFrom(smc.generatedPositionFor(positionFrom(c.range.end))); - return { span: { start, end }, content: c.content }; - }); + .map(toPatch); const localize2Patches = lazy(localize2Calls) - .map(lc => ([ - { range: lc.keySpan, content: '' + (i++) } - ])).flatten() - .map(c => { - const start = lcFrom(smc.generatedPositionFor(positionFrom(c.range.start))); - const end = lcFrom(smc.generatedPositionFor(positionFrom(c.range.end))); - return { span: { start, end }, content: c.content }; + .map(lc => ({ range: lc.keySpan, content: '' + (i++) })) + .map(toPatch); + // Sort patches by their start position + const patches = localizePatches.concat(localize2Patches).toArray().sort((a, b) => { + if (a.span.start.line < b.span.start.line) { + return -1; + } + else if (a.span.start.line > b.span.start.line) { + return 1; + } + else if (a.span.start.character < b.span.start.character) { + return -1; + } + else if (a.span.start.character > b.span.start.character) { + return 1; + } + else { + return 0; + } }); - const patches = localizePatches.concat(localize2Patches).toArray(); javascript = patchJavascript(patches, javascript, moduleId); // since imports are not within the sourcemap information, // we must do this MacGyver style diff --git a/build/lib/nls.ts b/build/lib/nls.ts index a1d64a1f8cf..c4ee031b2eb 100644 --- a/build/lib/nls.ts +++ b/build/lib/nls.ts @@ -414,7 +414,7 @@ module _nls { const { localizeCalls, nlsExpressions } = analyze(ts, typescript, 'localize'); const { localizeCalls: localize2Calls, nlsExpressions: nls2Expressions } = analyze(ts, typescript, 'localize2'); - if (localizeCalls.length === 0) { + if (localizeCalls.length === 0 && localize2Calls.length === 0) { return { javascript, sourcemap }; } @@ -422,32 +422,43 @@ module _nls { const nls = template(localizeCalls.map(lc => lc.value).concat(localize2Calls.map(lc => lc.value))); const smc = new sm.SourceMapConsumer(sourcemap); const positionFrom = mappedPositionFrom.bind(null, sourcemap.sources[0]); - let i = 0; // build patches + const toPatch = (c: { range: ISpan; content: string }): IPatch => { + const start = lcFrom(smc.generatedPositionFor(positionFrom(c.range.start))); + const end = lcFrom(smc.generatedPositionFor(positionFrom(c.range.end))); + return { span: { start, end }, content: c.content }; + }; + + let i = 0; const localizePatches = lazy(localizeCalls) .map(lc => ([ { range: lc.keySpan, content: '' + (i++) }, { range: lc.valueSpan, content: 'null' } ])) .flatten() - .map(c => { - const start = lcFrom(smc.generatedPositionFor(positionFrom(c.range.start))); - const end = lcFrom(smc.generatedPositionFor(positionFrom(c.range.end))); - return { span: { start, end }, content: c.content }; - }); + .map(toPatch); const localize2Patches = lazy(localize2Calls) - .map(lc => ([ + .map(lc => ( { range: lc.keySpan, content: '' + (i++) } - ])).flatten() - .map(c => { - const start = lcFrom(smc.generatedPositionFor(positionFrom(c.range.start))); - const end = lcFrom(smc.generatedPositionFor(positionFrom(c.range.end))); - return { span: { start, end }, content: c.content }; - }); + )) + .map(toPatch); - const patches = localizePatches.concat(localize2Patches).toArray(); + // Sort patches by their start position + const patches = localizePatches.concat(localize2Patches).toArray().sort((a, b) => { + if (a.span.start.line < b.span.start.line) { + return -1; + } else if (a.span.start.line > b.span.start.line) { + return 1; + } else if (a.span.start.character < b.span.start.character) { + return -1; + } else if (a.span.start.character > b.span.start.character) { + return 1; + } else { + return 0; + } + }); javascript = patchJavascript(patches, javascript, moduleId); From 8c39690fc48801d9a3d6cd1c071bebe863386a00 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Tue, 30 Jan 2024 17:12:29 -0800 Subject: [PATCH 0754/1897] Fix broken highlights when clicking a previously selected symbol (#203853) streamline request logic + kill worker req on decoration removal --- .../browser/wordHighlighter.ts | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts index 949ef715d83..8b9790bce21 100644 --- a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts @@ -309,6 +309,11 @@ class WordHighlighter { this._onPositionChanged(e); })); + this.toUnhook.add(editor.onDidFocusEditorText((e) => { + if (!this.workerRequest) { + this._run(); + } + })); this.toUnhook.add(editor.onDidChangeModelContent((e) => { this._stopAll(); })); @@ -451,6 +456,7 @@ class WordHighlighter { if (editorHighlighterContrib.wordHighlighter.decorations.length > 0) { editorHighlighterContrib.wordHighlighter.decorations.clear(); + editorHighlighterContrib.wordHighlighter.workerRequest = null; editorHighlighterContrib.wordHighlighter._hasWordHighlights.set(false); } } @@ -467,7 +473,7 @@ class WordHighlighter { if (this.editor.hasTextFocus()) { if (this.editor.getModel()?.uri.scheme !== Schemas.vscodeNotebookCell && WordHighlighter.query?.modelInfo?.model.uri.scheme !== Schemas.vscodeNotebookCell) { // clear query if focused non-nb editor WordHighlighter.query = null; - this._run(); + this._run(); // TODO: @Yoyokrazy -- investigate why we need a full rerun here. likely addressed a case/patch in the first iteration of this feature } else { // remove modelInfo to account for nb cell being disposed if (WordHighlighter.query?.modelInfo) { WordHighlighter.query.modelInfo = null; @@ -615,16 +621,18 @@ class WordHighlighter { private _run(): void { let workerRequestIsValid; - if (!this.editor.hasTextFocus()) { // no focus (new nb cell, etc) - if (WordHighlighter.query === null) { - // no previous query, nothing to highlight + const hasTextFocus = this.editor.hasTextFocus(); + + if (!hasTextFocus) { // new nb cell scrolled in, didChangeModel fires + if (!WordHighlighter.query) { // no previous query, nothing to highlight off of return; } - } else { + } else { // has text focus const editorSelection = this.editor.getSelection(); // ignore multiline selection if (!editorSelection || editorSelection.startLineNumber !== editorSelection.endLineNumber) { + WordHighlighter.query = null; this._stopAll(); return; } @@ -686,20 +694,14 @@ class WordHighlighter { const otherModelsToHighlight = this.getOtherModelsToHighlight(this.editor.getModel()); - // 2 cases where we want to send the word - // a) there is no stored query model, but there is a word. This signals the editor that drove the highlight is disposed (cell out of viewport, etc) - // b) the queried model is not the current model. This signals that the editor that drove the highlight is still in the viewport, but we are highlighting a different cell - // otherwise, we send null in place of the word, and the model and selection are used to compute the word - // TODO: @Yoyokrazy -- investigate this logic for sendWord - const sendWord = (!WordHighlighter.query.modelInfo && WordHighlighter.query.word) || - (WordHighlighter.query.modelInfo?.model.uri !== this.model.uri) - ? true : false; - - if (!WordHighlighter.query.modelInfo || (WordHighlighter.query.modelInfo.model.uri !== this.model.uri)) { // use this.model - this.workerRequest = this.computeWithModel(this.model, this.editor.getSelection(), sendWord ? WordHighlighter.query.word : null, otherModelsToHighlight); - } else { // use stored query model + selection - this.workerRequest = this.computeWithModel(WordHighlighter.query.modelInfo.model, WordHighlighter.query.modelInfo.selection, WordHighlighter.query.word, otherModelsToHighlight); + // when reaching here, there are two possible states. + // 1) we have text focus, and a valid query was updated. + // 2) we do not have text focus, and a valid query is cached. + // the query will ALWAYS have the correct data for the current highlight request, so it can always be passed to the workerRequest safely + if (!WordHighlighter.query.modelInfo || WordHighlighter.query.modelInfo.model.isDisposed()) { + return; } + this.workerRequest = this.computeWithModel(WordHighlighter.query.modelInfo.model, WordHighlighter.query.modelInfo.selection, WordHighlighter.query.word, otherModelsToHighlight); this.workerRequest?.result.then(data => { if (myRequestId === this.workerRequestTokenId) { From 777ea450a1307db0ae27a6b83fa45f14339f7316 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 31 Jan 2024 01:16:25 +0000 Subject: [PATCH 0755/1897] Adopt localize2 in more places (#203842) Switches many callers to use `localize2` instead of duplicating strings --- .../diffEditor/diffEditor.contribution.ts | 10 +- .../contrib/clipboard/browser/clipboard.ts | 10 +- .../gotoSymbol/browser/goToCommands.ts | 35 +---- .../browser/stickyScrollActions.ts | 22 +-- .../actionWidget/browser/actionWidget.ts | 27 +--- .../actions/common/menuResetAction.ts | 7 +- .../browser/actions/layoutActions.ts | 57 ++------ .../browser/actions/quickAccessActions.ts | 5 +- .../parts/editor/breadcrumbsControl.ts | 12 +- .../browser/parts/panel/panelActions.ts | 44 ++---- .../browser/parts/sidebar/sidebarPart.ts | 7 +- .../browser/parts/titlebar/titlebarActions.ts | 8 +- src/vs/workbench/browser/web.main.ts | 4 +- .../contrib/audioCues/browser/commands.ts | 12 +- .../chat/browser/actions/chatActions.ts | 5 +- .../browser/actions/chatCodeblockActions.ts | 34 ++--- .../chat/browser/actions/chatCopyActions.ts | 12 +- .../browser/actions/chatExecuteActions.ts | 12 +- .../browser/actions/chatFileTreeActions.ts | 16 +-- .../chat/browser/actions/chatMoveActions.ts | 17 +-- .../browser/actions/chatQuickInputActions.ts | 15 +-- .../chat/browser/actions/chatTitleActions.ts | 27 +--- .../actions/voiceChatActions.ts | 52 ++------ .../browser/accessibility/accessibility.ts | 2 +- .../electron-sandbox/startDebugTextMate.ts | 2 +- .../commands/common/commands.contribution.ts | 2 +- .../contrib/debug/browser/debugCommands.ts | 48 +++---- .../browser/editSessions.contribution.ts | 2 +- .../abstractRuntimeExtensionsEditor.ts | 2 +- .../externalTerminal.contribution.ts | 2 +- .../files/browser/fileActions.contribution.ts | 12 +- .../files/browser/views/explorerView.ts | 4 +- .../files/browser/views/openEditorsView.ts | 4 +- .../fileActions.contribution.ts | 2 +- .../browser/inlayHintsAccessibilty.ts | 12 +- .../inlineChat/browser/inlineChatActions.ts | 13 +- .../browser/interactive.contribution.ts | 2 +- .../browser/keybindings.contribution.ts | 2 +- .../browser/languageDetection.contribution.ts | 4 +- .../browser/languageStatus.contribution.ts | 7 +- .../contrib/logs/common/logs.contribution.ts | 4 +- .../contrib/logs/common/logsActions.ts | 4 +- .../logs/electron-sandbox/logsActions.ts | 4 +- .../mergeEditor/browser/commands/commands.ts | 125 +++--------------- .../browser/commands/devCommands.ts | 24 +--- .../electron-sandbox/devCommands.ts | 16 +-- .../contrib/cellCommands/cellCommands.ts | 102 +++----------- .../gettingStarted/notebookGettingStarted.ts | 13 +- .../browser/contrib/troubleshoot/layout.ts | 9 +- .../browser/controller/editActions.ts | 4 +- .../browser/controller/executeActions.ts | 12 +- .../browser/controller/layoutActions.ts | 27 +--- .../notebookKernelHistoryServiceImpl.ts | 7 +- .../view/cellParts/chat/cellChatActions.ts | 16 +-- .../browser/keyboardLayoutPicker.ts | 2 +- .../browser/preferences.contribution.ts | 50 +++---- .../preferences/browser/preferencesActions.ts | 2 +- .../contrib/remote/browser/remote.ts | 2 +- .../contrib/remote/browser/remoteExplorer.ts | 2 +- .../contrib/remote/browser/remoteIndicator.ts | 8 +- .../remote/browser/remoteStartEntry.ts | 4 +- .../contrib/remote/browser/tunnelView.ts | 4 +- .../search/browser/searchActionsBase.ts | 2 +- .../search/browser/searchActionsCopy.ts | 15 +-- .../search/browser/searchActionsFind.ts | 25 +--- .../search/browser/searchActionsNav.ts | 85 +++--------- .../browser/searchActionsRemoveReplace.ts | 20 +-- .../browser/searchActionsTextQuickAccess.ts | 5 +- .../search/browser/searchActionsTopBar.ts | 40 ++---- .../browser/searchEditor.contribution.ts | 6 +- .../commands/abstractSnippetsActions.ts | 7 +- .../browser/commands/configureSnippets.ts | 5 +- .../browser/commands/fileTemplateSnippets.ts | 7 +- .../browser/commands/insertSnippet.ts | 5 +- .../browser/commands/surroundWithSnippet.ts | 7 +- .../tasks/browser/task.contribution.ts | 33 ++--- .../workbench/contrib/tasks/common/tasks.ts | 2 +- .../terminal/browser/terminalActions.ts | 16 +-- .../terminal/common/terminalStrings.ts | 92 +++---------- .../terminal.accessibility.contribution.ts | 8 +- .../browser/codeCoverageDecorations.ts | 4 +- .../testing/browser/testExplorerActions.ts | 6 +- .../browser/userDataProfile.ts | 20 +-- .../browser/userDataProfileActions.ts | 43 ++---- .../electron-sandbox/webviewCommands.ts | 2 +- .../browser/gettingStartedService.ts | 6 +- .../browser/workspace.contribution.ts | 4 +- .../browser/workspaces.contribution.ts | 4 +- .../extensions/common/extensionHostManager.ts | 5 +- .../nativeExtensionService.ts | 2 +- .../userDataSync/common/userDataSync.ts | 2 +- 91 files changed, 405 insertions(+), 1086 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts b/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts index b6bbd656c3f..2cc7ffacdc4 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts @@ -110,10 +110,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { when: ContextKeyExpr.has('isInDiffEditor'), }); -const diffEditorCategory: ILocalizedString = { - value: localize('diffEditor', 'Diff Editor'), - original: 'Diff Editor', -}; +const diffEditorCategory: ILocalizedString = localize2('diffEditor', "Diff Editor"); export class SwitchSide extends EditorAction2 { constructor() { @@ -212,10 +209,7 @@ export class ShowAllUnchangedRegions extends EditorAction2 { registerAction2(ShowAllUnchangedRegions); -const accessibleDiffViewerCategory: ILocalizedString = { - value: localize('accessibleDiffViewer', 'Accessible Diff Viewer'), - original: 'Accessible Diff Viewer', -}; +const accessibleDiffViewerCategory: ILocalizedString = localize2('accessibleDiffViewer', "Accessible Diff Viewer"); export class AccessibleDiffViewerNext extends Action2 { public static id = 'editor.action.accessibleDiffViewer.next'; diff --git a/src/vs/editor/contrib/clipboard/browser/clipboard.ts b/src/vs/editor/contrib/clipboard/browser/clipboard.ts index 8ca27d4fbae..b18e3dd0b81 100644 --- a/src/vs/editor/contrib/clipboard/browser/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/browser/clipboard.ts @@ -108,11 +108,11 @@ export const CopyAction = supportsCopy ? registerCommand(new MultiCommand({ }] })) : undefined; -MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { submenu: MenuId.MenubarCopy, title: { value: nls.localize('copy as', "Copy As"), original: 'Copy As', }, group: '2_ccp', order: 3 }); -MenuRegistry.appendMenuItem(MenuId.EditorContext, { submenu: MenuId.EditorContextCopy, title: { value: nls.localize('copy as', "Copy As"), original: 'Copy As', }, group: CLIPBOARD_CONTEXT_MENU_GROUP, order: 3 }); -MenuRegistry.appendMenuItem(MenuId.EditorContext, { submenu: MenuId.EditorContextShare, title: { value: nls.localize('share', "Share"), original: 'Share', }, group: '11_share', order: -1, when: ContextKeyExpr.and(ContextKeyExpr.notEquals('resourceScheme', 'output'), EditorContextKeys.editorTextFocus) }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { submenu: MenuId.EditorTitleContextShare, title: { value: nls.localize('share', "Share"), original: 'Share', }, group: '11_share', order: -1 }); -MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { submenu: MenuId.ExplorerContextShare, title: { value: nls.localize('share', "Share"), original: 'Share', }, group: '11_share', order: -1 }); +MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { submenu: MenuId.MenubarCopy, title: nls.localize2('copy as', "Copy As"), group: '2_ccp', order: 3 }); +MenuRegistry.appendMenuItem(MenuId.EditorContext, { submenu: MenuId.EditorContextCopy, title: nls.localize2('copy as', "Copy As"), group: CLIPBOARD_CONTEXT_MENU_GROUP, order: 3 }); +MenuRegistry.appendMenuItem(MenuId.EditorContext, { submenu: MenuId.EditorContextShare, title: nls.localize2('share', "Share"), group: '11_share', order: -1, when: ContextKeyExpr.and(ContextKeyExpr.notEquals('resourceScheme', 'output'), EditorContextKeys.editorTextFocus) }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { submenu: MenuId.EditorTitleContextShare, title: nls.localize2('share', "Share"), group: '11_share', order: -1 }); +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { submenu: MenuId.ExplorerContextShare, title: nls.localize2('share', "Share"), group: '11_share', order: -1 }); export const PasteAction = supportsPaste ? registerCommand(new MultiCommand({ id: 'editor.action.clipboardPasteAction', diff --git a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts index f4b26dfff0b..a0f216cf3a8 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts @@ -325,10 +325,7 @@ registerAction2(class OpenDefinitionToSideAction extends DefinitionAction { muteMessage: false }, { id: OpenDefinitionToSideAction.id, - title: { - value: nls.localize('actions.goToDeclToSide.label', "Open Definition to the Side"), - original: 'Open Definition to the Side' - }, + title: nls.localize2('actions.goToDeclToSide.label', "Open Definition to the Side"), precondition: ContextKeyExpr.and( EditorContextKeys.hasDefinitionProvider, EditorContextKeys.isInWalkThroughSnippet.toNegated()), @@ -357,10 +354,7 @@ registerAction2(class PeekDefinitionAction extends DefinitionAction { muteMessage: false }, { id: PeekDefinitionAction.id, - title: { - value: nls.localize('actions.previewDecl.label', "Peek Definition"), - original: 'Peek Definition' - }, + title: nls.localize2('actions.previewDecl.label', "Peek Definition"), precondition: ContextKeyExpr.and( EditorContextKeys.hasDefinitionProvider, PeekContext.notInPeekEditor, @@ -455,10 +449,7 @@ registerAction2(class PeekDeclarationAction extends DeclarationAction { muteMessage: false }, { id: 'editor.action.peekDeclaration', - title: { - value: nls.localize('actions.peekDecl.label', "Peek Declaration"), - original: 'Peek Declaration' - }, + title: nls.localize2('actions.peekDecl.label', "Peek Declaration"), precondition: ContextKeyExpr.and( EditorContextKeys.hasDeclarationProvider, PeekContext.notInPeekEditor, @@ -547,10 +538,7 @@ registerAction2(class PeekTypeDefinitionAction extends TypeDefinitionAction { muteMessage: false }, { id: PeekTypeDefinitionAction.ID, - title: { - value: nls.localize('actions.peekTypeDefinition.label', "Peek Type Definition"), - original: 'Peek Type Definition' - }, + title: nls.localize2('actions.peekTypeDefinition.label', "Peek Type Definition"), precondition: ContextKeyExpr.and( EditorContextKeys.hasTypeDefinitionProvider, PeekContext.notInPeekEditor, @@ -639,10 +627,7 @@ registerAction2(class PeekImplementationAction extends ImplementationAction { muteMessage: false }, { id: PeekImplementationAction.ID, - title: { - value: nls.localize('actions.peekImplementation.label', "Peek Implementations"), - original: 'Peek Implementations' - }, + title: nls.localize2('actions.peekImplementation.label', "Peek Implementations"), precondition: ContextKeyExpr.and( EditorContextKeys.hasImplementationProvider, PeekContext.notInPeekEditor, @@ -734,10 +719,7 @@ registerAction2(class PeekReferencesAction extends ReferencesAction { muteMessage: false }, { id: 'editor.action.referenceSearch.trigger', - title: { - value: nls.localize('references.action.label', "Peek References"), - original: 'Peek References' - }, + title: nls.localize2('references.action.label', "Peek References"), precondition: ContextKeyExpr.and( EditorContextKeys.hasReferenceProvider, PeekContext.notInPeekEditor, @@ -770,10 +752,7 @@ class GenericGoToLocationAction extends SymbolNavigationAction { ) { super(config, { id: 'editor.action.goToLocation', - title: { - value: nls.localize('label.generic', "Go to Any Symbol"), - original: 'Go to Any Symbol' - }, + title: nls.localize2('label.generic', "Go to Any Symbol"), precondition: ContextKeyExpr.and( PeekContext.notInPeekEditor, EditorContextKeys.isInWalkThroughSnippet.toNegated() diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollActions.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollActions.ts index e50c0f97c50..5a1b6b9e48b 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollActions.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollActions.ts @@ -5,7 +5,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -74,10 +74,7 @@ export class SelectNextStickyScrollLine extends EditorAction2 { constructor() { super({ id: 'editor.action.selectNextStickyScrollLine', - title: { - value: localize('selectNextStickyScrollLine.title', "Select next sticky scroll line"), - original: 'Select next sticky scroll line' - }, + title: localize2('selectNextStickyScrollLine.title', "Select next sticky scroll line"), precondition: EditorContextKeys.stickyScrollFocused.isEqualTo(true), keybinding: { weight, @@ -95,10 +92,7 @@ export class SelectPreviousStickyScrollLine extends EditorAction2 { constructor() { super({ id: 'editor.action.selectPreviousStickyScrollLine', - title: { - value: localize('selectPreviousStickyScrollLine.title', "Select previous sticky scroll line"), - original: 'Select previous sticky scroll line' - }, + title: localize2('selectPreviousStickyScrollLine.title', "Select previous sticky scroll line"), precondition: EditorContextKeys.stickyScrollFocused.isEqualTo(true), keybinding: { weight, @@ -116,10 +110,7 @@ export class GoToStickyScrollLine extends EditorAction2 { constructor() { super({ id: 'editor.action.goToFocusedStickyScrollLine', - title: { - value: localize('goToFocusedStickyScrollLine.title', "Go to focused sticky scroll line"), - original: 'Go to focused sticky scroll line' - }, + title: localize2('goToFocusedStickyScrollLine.title', "Go to focused sticky scroll line"), precondition: EditorContextKeys.stickyScrollFocused.isEqualTo(true), keybinding: { weight, @@ -138,10 +129,7 @@ export class SelectEditor extends EditorAction2 { constructor() { super({ id: 'editor.action.selectEditor', - title: { - value: localize('selectEditor.title', "Select Editor"), - original: 'Select Editor' - }, + title: localize2('selectEditor.title', "Select Editor"), precondition: EditorContextKeys.stickyScrollFocused.isEqualTo(true), keybinding: { weight, diff --git a/src/vs/platform/actionWidget/browser/actionWidget.ts b/src/vs/platform/actionWidget/browser/actionWidget.ts index 33398d68b48..d9fb3fbb3f6 100644 --- a/src/vs/platform/actionWidget/browser/actionWidget.ts +++ b/src/vs/platform/actionWidget/browser/actionWidget.ts @@ -9,7 +9,7 @@ import { IAction } from 'vs/base/common/actions'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import 'vs/css!./actionWidget'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { acceptSelectedActionCommand, ActionList, IActionListDelegate, IActionListItem, previewSelectedActionCommand } from 'vs/platform/actionWidget/browser/actionList'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -168,10 +168,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'hideCodeActionWidget', - title: { - value: localize('hideCodeActionWidget.title', "Hide action widget"), - original: 'Hide action widget' - }, + title: localize2('hideCodeActionWidget.title', "Hide action widget"), precondition: ActionWidgetContextKeys.Visible, keybinding: { weight, @@ -190,10 +187,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'selectPrevCodeAction', - title: { - value: localize('selectPrevCodeAction.title', "Select previous action"), - original: 'Select previous action' - }, + title: localize2('selectPrevCodeAction.title', "Select previous action"), precondition: ActionWidgetContextKeys.Visible, keybinding: { weight, @@ -216,10 +210,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'selectNextCodeAction', - title: { - value: localize('selectNextCodeAction.title', "Select next action"), - original: 'Select next action' - }, + title: localize2('selectNextCodeAction.title', "Select next action"), precondition: ActionWidgetContextKeys.Visible, keybinding: { weight, @@ -242,10 +233,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: acceptSelectedActionCommand, - title: { - value: localize('acceptSelected.title', "Accept selected action"), - original: 'Accept selected action' - }, + title: localize2('acceptSelected.title', "Accept selected action"), precondition: ActionWidgetContextKeys.Visible, keybinding: { weight, @@ -267,10 +255,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: previewSelectedActionCommand, - title: { - value: localize('previewSelected.title', "Preview selected action"), - original: 'Preview selected action' - }, + title: localize2('previewSelected.title', "Preview selected action"), precondition: ActionWidgetContextKeys.Visible, keybinding: { weight, diff --git a/src/vs/platform/actions/common/menuResetAction.ts b/src/vs/platform/actions/common/menuResetAction.ts index 6cebce22257..01e77dd3c56 100644 --- a/src/vs/platform/actions/common/menuResetAction.ts +++ b/src/vs/platform/actions/common/menuResetAction.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Action2, IMenuService } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -14,10 +14,7 @@ export class MenuHiddenStatesReset extends Action2 { constructor() { super({ id: 'menu.resetHiddenStates', - title: { - value: localize('title', 'Reset All Menus'), - original: 'Reset All Menus' - }, + title: localize2('title', "Reset All Menus"), category: Categories.View, f1: true }); diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 4ef386aea49..24a8bfa0151 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -131,10 +131,7 @@ class MoveSidebarRightAction extends MoveSidebarPositionAction { static readonly ID = 'workbench.action.moveSideBarRight'; constructor() { - super(MoveSidebarRightAction.ID, { - value: localize('moveSidebarRight', "Move Primary Side Bar Right"), - original: 'Move Primary Side Bar Right' - }, Position.RIGHT); + super(MoveSidebarRightAction.ID, localize2('moveSidebarRight', "Move Primary Side Bar Right"), Position.RIGHT); } } @@ -142,10 +139,7 @@ class MoveSidebarLeftAction extends MoveSidebarPositionAction { static readonly ID = 'workbench.action.moveSideBarLeft'; constructor() { - super(MoveSidebarLeftAction.ID, { - value: localize('moveSidebarLeft', "Move Primary Side Bar Left"), - original: 'Move Primary Side Bar Left' - }, Position.LEFT); + super(MoveSidebarLeftAction.ID, localize2('moveSidebarLeft', "Move Primary Side Bar Left"), Position.LEFT); } } @@ -582,10 +576,7 @@ export class EditorActionsTitleBarAction extends Action2 { constructor() { super({ id: EditorActionsTitleBarAction.ID, - title: { - value: localize('moveEditorActionsToTitleBar', "Move Editor Actions to Title Bar"), - original: 'Move Editor Actions to Title Bar' - }, + title: localize2('moveEditorActionsToTitleBar', "Move Editor Actions to Title Bar"), category: Categories.View, precondition: ContextKeyExpr.equals(`config.${LayoutSettings.EDITOR_ACTIONS_LOCATION}`, EditorActionsLocation.TITLEBAR).negate(), f1: true @@ -608,10 +599,7 @@ export class EditorActionsDefaultAction extends Action2 { constructor() { super({ id: EditorActionsDefaultAction.ID, - title: { - value: localize('moveEditorActionsToTabBar', "Move Editor Actions to Tab Bar"), - original: 'Move Editor Actions to Tab Bar' - }, + title: localize2('moveEditorActionsToTabBar', "Move Editor Actions to Tab Bar"), category: Categories.View, precondition: ContextKeyExpr.and( ContextKeyExpr.equals(`config.${LayoutSettings.EDITOR_ACTIONS_LOCATION}`, EditorActionsLocation.DEFAULT).negate(), @@ -637,10 +625,7 @@ export class HideEditorActionsAction extends Action2 { constructor() { super({ id: HideEditorActionsAction.ID, - title: { - value: localize('hideEditorActons', "Hide Editor Actions"), - original: 'Hide Editor Actions' - }, + title: localize2('hideEditorActons', "Hide Editor Actions"), category: Categories.View, precondition: ContextKeyExpr.equals(`config.${LayoutSettings.EDITOR_ACTIONS_LOCATION}`, EditorActionsLocation.HIDDEN).negate(), f1: true @@ -663,10 +648,7 @@ export class ShowEditorActionsAction extends Action2 { constructor() { super({ id: ShowEditorActionsAction.ID, - title: { - value: localize('showEditorActons', "Show Editor Actions"), - original: 'Show Editor Actions' - }, + title: localize2('showEditorActons', "Show Editor Actions"), category: Categories.View, precondition: ContextKeyExpr.equals(`config.${LayoutSettings.EDITOR_ACTIONS_LOCATION}`, EditorActionsLocation.HIDDEN), f1: true @@ -696,10 +678,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.toggleSeparatePinnedEditorTabs', - title: { - value: localize('toggleSeparatePinnedEditorTabs', "Separate Pinned Editor Tabs"), - original: 'Separate Pinned Editor Tabs' - }, + title: localize2('toggleSeparatePinnedEditorTabs', "Separate Pinned Editor Tabs"), category: Categories.View, precondition: ContextKeyExpr.equals(`config.${LayoutSettings.EDITOR_TABS_MODE}`, EditorTabsMode.MULTIPLE), f1: true @@ -814,10 +793,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.resetViewLocations', - title: { - value: localize('resetViewLocations', "Reset View Locations"), - original: 'Reset View Locations' - }, + title: localize2('resetViewLocations', "Reset View Locations"), category: Categories.View, f1: true }); @@ -835,10 +811,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.moveView', - title: { - value: localize('moveView', "Move View"), - original: 'Move View' - }, + title: localize2('moveView', "Move View"), category: Categories.View, f1: true }); @@ -980,10 +953,7 @@ class MoveFocusedViewAction extends Action2 { constructor() { super({ id: 'workbench.action.moveFocusedView', - title: { - value: localize('moveFocusedView', "Move Focused View"), - original: 'Move Focused View' - }, + title: localize2('moveFocusedView', "Move Focused View"), category: Categories.View, precondition: FocusedViewContext.notEqualsTo(''), f1: true @@ -1139,10 +1109,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.resetFocusedViewLocation', - title: { - value: localize('resetFocusedViewLocation', "Reset Focused View Location"), - original: 'Reset Focused View Location' - }, + title: localize2('resetFocusedViewLocation', "Reset Focused View Location"), category: Categories.View, f1: true, precondition: FocusedViewContext.notEqualsTo('') @@ -1405,7 +1372,7 @@ registerAction2(class CustomizeLayoutAction extends Action2 { constructor() { super({ id: 'workbench.action.customizeLayout', - title: { original: 'Customize Layout...', value: localize('customizeLayout', "Customize Layout...") }, + title: localize2('customizeLayout', "Customize Layout..."), f1: true, icon: configureLayoutIcon, menu: [ diff --git a/src/vs/workbench/browser/actions/quickAccessActions.ts b/src/vs/workbench/browser/actions/quickAccessActions.ts index ce41b3e787f..9890994cfdf 100644 --- a/src/vs/workbench/browser/actions/quickAccessActions.ts +++ b/src/vs/workbench/browser/actions/quickAccessActions.ts @@ -122,10 +122,7 @@ registerAction2(class QuickAccessAction extends Action2 { constructor() { super({ id: 'workbench.action.quickOpen', - title: { - value: localize('quickOpen', "Go to File..."), - original: 'Go to File...' - }, + title: localize2('quickOpen', "Go to File..."), metadata: { description: `Quick access`, args: [{ diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index db18c13d461..5470d409f4b 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -13,7 +13,7 @@ import { combinedDisposable, DisposableStore, MutableDisposable, toDisposable } import { extUri } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/breadcrumbscontrol'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -643,10 +643,7 @@ registerAction2(class FocusAndSelectBreadcrumbs extends Action2 { constructor() { super({ id: 'breadcrumbs.focusAndSelect', - title: { - value: localize('cmd.focusAndSelect', "Focus and Select Breadcrumbs"), - original: 'Focus and Select Breadcrumbs' - }, + title: localize2('cmd.focusAndSelect', "Focus and Select Breadcrumbs"), precondition: BreadcrumbsControl.CK_BreadcrumbsVisible, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -665,10 +662,7 @@ registerAction2(class FocusBreadcrumbs extends Action2 { constructor() { super({ id: 'breadcrumbs.focus', - title: { - value: localize('cmd.focus', "Focus Breadcrumbs"), - original: 'Focus Breadcrumbs' - }, + title: localize2('cmd.focus', "Focus Breadcrumbs"), precondition: BreadcrumbsControl.CK_BreadcrumbsVisible, keybinding: { weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index a872e4a89d4..f3b12394a95 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -136,17 +136,17 @@ function createAlignmentPanelActionConfig(id: string, title: ICommandActionTitle const PositionPanelActionConfigs: PanelActionConfig[] = [ - createPositionPanelActionConfig(PositionPanelActionId.LEFT, { value: localize('positionPanelLeft', 'Move Panel Left'), original: 'Move Panel Left' }, localize('positionPanelLeftShort', "Left"), Position.LEFT), - createPositionPanelActionConfig(PositionPanelActionId.RIGHT, { value: localize('positionPanelRight', 'Move Panel Right'), original: 'Move Panel Right' }, localize('positionPanelRightShort', "Right"), Position.RIGHT), - createPositionPanelActionConfig(PositionPanelActionId.BOTTOM, { value: localize('positionPanelBottom', 'Move Panel To Bottom'), original: 'Move Panel To Bottom' }, localize('positionPanelBottomShort', "Bottom"), Position.BOTTOM), + createPositionPanelActionConfig(PositionPanelActionId.LEFT, localize2('positionPanelLeft', "Move Panel Left"), localize('positionPanelLeftShort', "Left"), Position.LEFT), + createPositionPanelActionConfig(PositionPanelActionId.RIGHT, localize2('positionPanelRight', "Move Panel Right"), localize('positionPanelRightShort', "Right"), Position.RIGHT), + createPositionPanelActionConfig(PositionPanelActionId.BOTTOM, localize2('positionPanelBottom', "Move Panel To Bottom"), localize('positionPanelBottomShort', "Bottom"), Position.BOTTOM), ]; const AlignPanelActionConfigs: PanelActionConfig[] = [ - createAlignmentPanelActionConfig(AlignPanelActionId.LEFT, { value: localize('alignPanelLeft', 'Set Panel Alignment to Left'), original: 'Set Panel Alignment to Left' }, localize('alignPanelLeftShort', "Left"), 'left'), - createAlignmentPanelActionConfig(AlignPanelActionId.RIGHT, { value: localize('alignPanelRight', 'Set Panel Alignment to Right'), original: 'Set Panel Alignment to Right' }, localize('alignPanelRightShort', "Right"), 'right'), - createAlignmentPanelActionConfig(AlignPanelActionId.CENTER, { value: localize('alignPanelCenter', 'Set Panel Alignment to Center'), original: 'Set Panel Alignment to Center' }, localize('alignPanelCenterShort', "Center"), 'center'), - createAlignmentPanelActionConfig(AlignPanelActionId.JUSTIFY, { value: localize('alignPanelJustify', 'Set Panel Alignment to Justify'), original: 'Set Panel Alignment to Justify' }, localize('alignPanelJustifyShort', "Justify"), 'justify'), + createAlignmentPanelActionConfig(AlignPanelActionId.LEFT, localize2('alignPanelLeft', "Set Panel Alignment to Left"), localize('alignPanelLeftShort', "Left"), 'left'), + createAlignmentPanelActionConfig(AlignPanelActionId.RIGHT, localize2('alignPanelRight', "Set Panel Alignment to Right"), localize('alignPanelRightShort', "Right"), 'right'), + createAlignmentPanelActionConfig(AlignPanelActionId.CENTER, localize2('alignPanelCenter', "Set Panel Alignment to Center"), localize('alignPanelCenterShort', "Center"), 'center'), + createAlignmentPanelActionConfig(AlignPanelActionId.JUSTIFY, localize2('alignPanelJustify', "Set Panel Alignment to Justify"), localize('alignPanelJustifyShort', "Justify"), 'justify'), ]; @@ -254,10 +254,7 @@ class SwitchPanelViewAction extends Action2 { registerAction2(class extends SwitchPanelViewAction { constructor() { - super('workbench.action.previousPanelView', { - value: localize('previousPanelView', 'Previous Panel View'), - original: 'Previous Panel View' - }); + super('workbench.action.previousPanelView', localize2('previousPanelView', "Previous Panel View")); } override run(accessor: ServicesAccessor): Promise { @@ -267,10 +264,7 @@ registerAction2(class extends SwitchPanelViewAction { registerAction2(class extends SwitchPanelViewAction { constructor() { - super('workbench.action.nextPanelView', { - value: localize('nextPanelView', 'Next Panel View'), - original: 'Next Panel View' - }); + super('workbench.action.nextPanelView', localize2('nextPanelView', "Next Panel View")); } override run(accessor: ServicesAccessor): Promise { @@ -425,10 +419,7 @@ class MovePanelToSidePanelAction extends MoveViewsBetweenPanelsAction { constructor() { super(ViewContainerLocation.Panel, ViewContainerLocation.AuxiliaryBar, { id: MovePanelToSidePanelAction.ID, - title: { - value: localize('movePanelToSecondarySideBar', "Move Panel Views To Secondary Side Bar"), - original: 'Move Panel Views To Secondary Side Bar' - }, + title: localize2('movePanelToSecondarySideBar', "Move Panel Views To Secondary Side Bar"), category: Categories.View, f1: false }); @@ -440,10 +431,7 @@ export class MovePanelToSecondarySideBarAction extends MoveViewsBetweenPanelsAct constructor() { super(ViewContainerLocation.Panel, ViewContainerLocation.AuxiliaryBar, { id: MovePanelToSecondarySideBarAction.ID, - title: { - value: localize('movePanelToSecondarySideBar', "Move Panel Views To Secondary Side Bar"), - original: 'Move Panel Views To Secondary Side Bar' - }, + title: localize2('movePanelToSecondarySideBar', "Move Panel Views To Secondary Side Bar"), category: Categories.View, f1: true }); @@ -461,10 +449,7 @@ class MoveSidePanelToPanelAction extends MoveViewsBetweenPanelsAction { constructor() { super(ViewContainerLocation.AuxiliaryBar, ViewContainerLocation.Panel, { id: MoveSidePanelToPanelAction.ID, - title: { - value: localize('moveSidePanelToPanel', "Move Secondary Side Bar Views To Panel"), - original: 'Move Secondary Side Bar Views To Panel' - }, + title: localize2('moveSidePanelToPanel', "Move Secondary Side Bar Views To Panel"), category: Categories.View, f1: false }); @@ -477,10 +462,7 @@ export class MoveSecondarySideBarToPanelAction extends MoveViewsBetweenPanelsAct constructor() { super(ViewContainerLocation.AuxiliaryBar, ViewContainerLocation.Panel, { id: MoveSecondarySideBarToPanelAction.ID, - title: { - value: localize('moveSidePanelToPanel', "Move Secondary Side Bar Views To Panel"), - original: 'Move Secondary Side Bar Views To Panel' - }, + title: localize2('moveSidePanelToPanel', "Move Secondary Side Bar Views To Panel"), category: Categories.View, f1: true }); diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 6dae52689e6..783a16cfbf6 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -30,7 +30,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { Action2, IMenuService, registerAction2 } from 'vs/platform/actions/common/actions'; import { Separator } from 'vs/base/common/actions'; import { ToggleActivityBarVisibilityActionId } from 'vs/workbench/browser/actions/layoutActions'; -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; export class SidebarPart extends AbstractPaneCompositePart { @@ -256,10 +256,7 @@ export class SidebarPart extends AbstractPaneCompositePart { constructor() { super({ id: ToggleActivityBarVisibilityActionId, - title: { - value: localize('toggleActivityBar', "Toggle Activity Bar Visibility"), - original: 'Toggle Activity Bar Visibility' - }, + title: localize2('toggleActivityBar', "Toggle Activity Bar Visibility"), }); } run(): Promise { diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index 1ed7b88b35a..8b9ec83840d 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; @@ -157,7 +157,7 @@ registerAction2(class ShowCustomTitleBar extends Action2 { constructor() { super({ id: `showCustomTitleBar`, - title: { value: localize('showCustomTitleBar', 'Show Custom Title Bar'), original: 'Show Custom Title Bar' }, + title: localize2('showCustomTitleBar', "Show Custom Title Bar"), precondition: TitleBarVisibleContext.negate(), f1: true }); @@ -174,7 +174,7 @@ registerAction2(class HideCustomTitleBar extends Action2 { constructor() { super({ id: `hideCustomTitleBar`, - title: { value: localize('hideCustomTitleBar', 'Hide Custom Title Bar'), original: 'Hide Custom Title Bar' }, + title: localize2('hideCustomTitleBar', "Hide Custom Title Bar"), precondition: TitleBarVisibleContext, f1: true }); @@ -190,7 +190,7 @@ registerAction2(class HideCustomTitleBar extends Action2 { constructor() { super({ id: `hideCustomTitleBarInFullScreen`, - title: { value: localize('hideCustomTitleBarInFullScreen', 'Hide Custom Title Bar In Full Screen'), original: 'Hide Custom Title Bar In Full Screen' }, + title: localize2('hideCustomTitleBarInFullScreen', "Hide Custom Title Bar In Full Screen"), precondition: ContextKeyExpr.and(TitleBarVisibleContext, IsMainWindowFullscreenContext), f1: true }); diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index c49eec92da6..9061af27859 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -49,7 +49,7 @@ import { IUserDataSyncStoreManagementService } from 'vs/platform/userDataSync/co import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IHostService } from 'vs/workbench/services/host/browser/host'; @@ -480,7 +480,7 @@ export class BrowserMain extends Disposable { constructor() { super({ id: 'workbench.action.resetUserData', - title: { original: 'Reset User Data', value: localize('reset', "Reset User Data") }, + title: localize2('reset', "Reset User Data"), category: Categories.Developer, menu: { id: MenuId.CommandPalette diff --git a/src/vs/workbench/contrib/audioCues/browser/commands.ts b/src/vs/workbench/contrib/audioCues/browser/commands.ts index 5b87791b573..dbaac656df6 100644 --- a/src/vs/workbench/contrib/audioCues/browser/commands.ts +++ b/src/vs/workbench/contrib/audioCues/browser/commands.ts @@ -5,7 +5,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { Action2 } from 'vs/platform/actions/common/actions'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; @@ -19,10 +19,7 @@ export class ShowAudioCueHelp extends Action2 { constructor() { super({ id: ShowAudioCueHelp.ID, - title: { - value: localize('audioCues.help', "Help: List Audio Cues"), - original: 'Help: List Audio Cues' - }, + title: localize2('audioCues.help', "Help: List Audio Cues"), f1: true, }); } @@ -77,10 +74,7 @@ export class ShowAccessibilityAlertHelp extends Action2 { constructor() { super({ id: ShowAccessibilityAlertHelp.ID, - title: { - value: localize('accessibility.alert.help', "Help: List Alerts"), - original: 'Help: List Alerts' - }, + title: localize2('accessibility.alert.help', "Help: List Alerts"), f1: true, }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 6da0b90162a..c343d09d89e 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -229,10 +229,7 @@ export function registerChatActions() { constructor() { super({ id: 'workbench.action.chat.focusInput', - title: { - value: localize('interactiveSession.focusInput.label', "Focus Chat Input"), - original: 'Focus Chat Input' - }, + title: localize2('interactiveSession.focusInput.label', "Focus Chat Input"), f1: false, keybinding: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index bbf5ba106b9..2d616c7be68 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -17,7 +17,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { CopyAction } from 'vs/editor/contrib/clipboard/browser/clipboard'; -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -28,7 +28,7 @@ import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatAct import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { IChatService, IDocumentContext, ChatAgentCopyKind } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatAgentCopyKind, IChatService, IDocumentContext } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CTX_INLINE_CHAT_VISIBLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; @@ -81,10 +81,7 @@ export function registerChatCodeBlockActions() { constructor() { super({ id: 'workbench.action.chat.copyCodeBlock', - title: { - value: localize('interactive.copyCodeBlock.label', "Copy"), - original: 'Copy' - }, + title: localize2('interactive.copyCodeBlock.label', "Copy"), f1: false, category: CHAT_CATEGORY, icon: Codicon.copy, @@ -177,10 +174,7 @@ export function registerChatCodeBlockActions() { constructor() { super({ id: 'workbench.action.chat.insertCodeBlock', - title: { - value: localize('interactive.insertCodeBlock.label', "Insert at Cursor"), - original: 'Insert at Cursor' - }, + title: localize2('interactive.insertCodeBlock.label', "Insert at Cursor"), precondition: CONTEXT_PROVIDER_EXISTS, f1: true, category: CHAT_CATEGORY, @@ -346,10 +340,7 @@ export function registerChatCodeBlockActions() { constructor() { super({ id: 'workbench.action.chat.insertIntoNewFile', - title: { - value: localize('interactive.insertIntoNewFile.label', "Insert into New File"), - original: 'Insert into New File' - }, + title: localize2('interactive.insertIntoNewFile.label', "Insert into New File"), precondition: CONTEXT_PROVIDER_EXISTS, f1: true, category: CHAT_CATEGORY, @@ -398,10 +389,7 @@ export function registerChatCodeBlockActions() { constructor() { super({ id: 'workbench.action.chat.runInTerminal', - title: { - value: localize('interactive.runInTerminal.label', "Insert into Terminal"), - original: 'Insert into Terminal' - }, + title: localize2('interactive.runInTerminal.label', "Insert into Terminal"), precondition: CONTEXT_PROVIDER_EXISTS, f1: true, category: CHAT_CATEGORY, @@ -518,10 +506,7 @@ export function registerChatCodeBlockActions() { constructor() { super({ id: 'workbench.action.chat.nextCodeBlock', - title: { - value: localize('interactive.nextCodeBlock.label', "Next Code Block"), - original: 'Next Code Block' - }, + title: localize2('interactive.nextCodeBlock.label', "Next Code Block"), keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, }, @@ -543,10 +528,7 @@ export function registerChatCodeBlockActions() { constructor() { super({ id: 'workbench.action.chat.previousCodeBlock', - title: { - value: localize('interactive.previousCodeBlock.label', "Previous Code Block"), - original: 'Previous Code Block' - }, + title: localize2('interactive.previousCodeBlock.label', "Previous Code Block"), keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp, }, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts index b679221edce..c55739a639a 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; @@ -17,10 +17,7 @@ export function registerChatCopyActions() { constructor() { super({ id: 'workbench.action.chat.copyAll', - title: { - value: localize('interactive.copyAll.label', "Copy All"), - original: 'Copy All' - }, + title: localize2('interactive.copyAll.label', "Copy All"), f1: false, category: CHAT_CATEGORY, menu: { @@ -52,10 +49,7 @@ export function registerChatCopyActions() { constructor() { super({ id: 'workbench.action.chat.copyItem', - title: { - value: localize('interactive.copyItem.label', "Copy"), - original: 'Copy' - }, + title: localize2('interactive.copyItem.label', "Copy"), f1: false, category: CHAT_CATEGORY, menu: { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 49ed40adf22..9ceec5ff1d1 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -5,7 +5,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidget, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; @@ -28,10 +28,7 @@ export class SubmitAction extends Action2 { constructor() { super({ id: SubmitAction.ID, - title: { - value: localize('interactive.submit.label', "Submit"), - original: 'Submit' - }, + title: localize2('interactive.submit.label', "Submit"), f1: false, category: CHAT_CATEGORY, icon: Codicon.send, @@ -59,10 +56,7 @@ export function registerChatExecuteActions() { constructor() { super({ id: 'workbench.action.chat.cancel', - title: { - value: localize('interactive.cancel.label', "Cancel"), - original: 'Cancel' - }, + title: localize2('interactive.cancel.label', "Cancel"), f1: false, category: CHAT_CATEGORY, icon: Codicon.debugStop, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts index a33ddd9abed..4b769210522 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts @@ -5,23 +5,20 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { localize } from 'vs/nls'; -import { registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { localize2 } from 'vs/nls'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { isResponseVM, IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; export function registerChatFileTreeActions() { registerAction2(class NextFileTreeAction extends Action2 { constructor() { super({ id: 'workbench.action.chat.nextFileTree', - title: { - value: localize('interactive.nextFileTree.label', "Next File Tree"), - original: 'Next File Tree' - }, + title: localize2('interactive.nextFileTree.label', "Next File Tree"), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.F9, weight: KeybindingWeight.WorkbenchContrib, @@ -42,10 +39,7 @@ export function registerChatFileTreeActions() { constructor() { super({ id: 'workbench.action.chat.previousFileTree', - title: { - value: localize('interactive.previousFileTree.label', "Previous File Tree"), - original: 'Previous File Tree' - }, + title: localize2('interactive.previousFileTree.label', "Previous File Tree"), keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F9, weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts index a9742a70414..23da3273ec1 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, IAction2Options, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -77,10 +77,7 @@ export function registerMoveActions() { constructor() { super({ id: `workbench.action.chat.openInEditor`, - title: { - value: localize('interactiveSession.openInEditor.label', "Open Chat in Editor"), - original: 'Open Chat in Editor' - }, + title: localize2('interactiveSession.openInEditor.label', "Open Chat in Editor"), category: CHAT_CATEGORY, precondition: CONTEXT_PROVIDER_EXISTS, f1: true @@ -97,10 +94,7 @@ export function registerMoveActions() { constructor() { super({ id: `workbench.action.chat.openInNewWindow`, - title: { - value: localize('interactiveSession.openInNewWindow.label', "Open Chat in New Window"), - original: 'Open Chat In New Window' - }, + title: localize2('interactiveSession.openInNewWindow.label', "Open Chat in New Window"), category: CHAT_CATEGORY, precondition: CONTEXT_PROVIDER_EXISTS, f1: true @@ -116,10 +110,7 @@ export function registerMoveActions() { constructor() { super({ id: `workbench.action.chat.openInSidebar`, - title: { - value: localize('interactiveSession.openInSidebar.label', "Open Chat in Side Bar"), - original: 'Open Chat in Side Bar' - }, + title: localize2('interactiveSession.openInSidebar.label', "Open Chat in Side Bar"), category: CHAT_CATEGORY, precondition: CONTEXT_PROVIDER_EXISTS, f1: true, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts index dfa463316a9..9381aa94208 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts @@ -24,10 +24,7 @@ export function registerQuickChatActions() { constructor() { super({ id: 'workbench.action.quickchat.openInChatView', - title: { - value: localize('chat.openInChatView.label', "Open in Chat View"), - original: 'Open in Chat View' - }, + title: localize2('chat.openInChatView.label', "Open in Chat View"), f1: false, category: CHAT_CATEGORY, icon: Codicon.commentDiscussion, @@ -49,10 +46,7 @@ export function registerQuickChatActions() { constructor() { super({ id: 'workbench.action.quickchat.close', - title: { - value: localize('chat.closeQuickChat.label', "Close Quick Chat"), - original: 'Close Quick Chat' - }, + title: localize2('chat.closeQuickChat.label', "Close Quick Chat"), f1: false, category: CHAT_CATEGORY, icon: Codicon.close, @@ -74,10 +68,7 @@ export function registerQuickChatActions() { constructor() { super({ id: 'workbench.action.quickchat.launchInlineChat', - title: { - value: localize('chat.launchInlineChat.label', "Launch Inline Chat"), - original: 'Launch Inline Chat' - }, + title: localize2('chat.launchInlineChat.label', "Launch Inline Chat"), f1: false, category: CHAT_CATEGORY }); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index 24f43dc2200..f98c6fc6524 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -8,7 +8,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { marked } from 'vs/base/common/marked/marked'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; -import { localize } from 'vs/nls'; +import { localize2 } 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'; @@ -28,10 +28,7 @@ export function registerChatTitleActions() { constructor() { super({ id: 'workbench.action.chat.markHelpful', - title: { - value: localize('interactive.helpful.label', "Helpful"), - original: 'Helpful' - }, + title: localize2('interactive.helpful.label', "Helpful"), f1: false, category: CHAT_CATEGORY, icon: Codicon.thumbsup, @@ -70,10 +67,7 @@ export function registerChatTitleActions() { constructor() { super({ id: 'workbench.action.chat.markUnhelpful', - title: { - value: localize('interactive.unhelpful.label', "Unhelpful"), - original: 'Unhelpful' - }, + title: localize2('interactive.unhelpful.label', "Unhelpful"), f1: false, category: CHAT_CATEGORY, icon: Codicon.thumbsdown, @@ -112,10 +106,7 @@ export function registerChatTitleActions() { constructor() { super({ id: 'workbench.action.chat.reportIssueForBug', - title: { - value: localize('interactive.reportIssueForBug.label', "Report Issue"), - original: 'Report Issue' - }, + title: localize2('interactive.reportIssueForBug.label', "Report Issue"), f1: false, category: CHAT_CATEGORY, icon: Codicon.report, @@ -151,10 +142,7 @@ export function registerChatTitleActions() { constructor() { super({ id: 'workbench.action.chat.insertIntoNotebook', - title: { - value: localize('interactive.insertIntoNotebook.label', "Insert into Notebook"), - original: 'Insert into Notebook' - }, + title: localize2('interactive.insertIntoNotebook.label', "Insert into Notebook"), f1: false, category: CHAT_CATEGORY, icon: Codicon.insert, @@ -227,10 +215,7 @@ export function registerChatTitleActions() { constructor() { super({ id: 'workbench.action.chat.remove', - title: { - value: localize('chat.remove.label', "Remove Request and Response"), - original: 'Remove Request and Response' - }, + title: localize2('chat.remove.label', "Remove Request and Response"), f1: false, category: CHAT_CATEGORY, icon: Codicon.x, diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index bbaf3fa124d..8083e797911 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -10,7 +10,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -398,10 +398,7 @@ export class VoiceChatInChatViewAction extends Action2 { constructor() { super({ id: VoiceChatInChatViewAction.ID, - title: { - value: localize('workbench.action.chat.voiceChatInView.label', "Voice Chat in Chat View"), - original: 'Voice Chat in Chat View' - }, + title: localize2('workbench.action.chat.voiceChatInView.label', "Voice Chat in Chat View"), category: CHAT_CATEGORY, precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_PROVIDER_EXISTS, CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), f1: true @@ -425,10 +422,7 @@ export class InlineVoiceChatAction extends Action2 { constructor() { super({ id: InlineVoiceChatAction.ID, - title: { - value: localize('workbench.action.chat.inlineVoiceChat', "Inline Voice Chat"), - original: 'Inline Voice Chat' - }, + title: localize2('workbench.action.chat.inlineVoiceChat', "Inline Voice Chat"), category: CHAT_CATEGORY, precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_PROVIDER_EXISTS, ActiveEditorContext, CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), f1: true @@ -452,10 +446,7 @@ export class QuickVoiceChatAction extends Action2 { constructor() { super({ id: QuickVoiceChatAction.ID, - title: { - value: localize('workbench.action.chat.quickVoiceChat.label', "Quick Voice Chat"), - original: 'Quick Voice Chat' - }, + title: localize2('workbench.action.chat.quickVoiceChat.label', "Quick Voice Chat"), category: CHAT_CATEGORY, precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_PROVIDER_EXISTS, CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), f1: true @@ -479,10 +470,7 @@ export class StartVoiceChatAction extends Action2 { constructor() { super({ id: StartVoiceChatAction.ID, - title: { - value: localize('workbench.action.chat.startVoiceChat.label', "Use Microphone"), - original: 'Use Microphone' - }, + title: localize2('workbench.action.chat.startVoiceChat.label', "Use Microphone"), category: CHAT_CATEGORY, icon: Codicon.mic, precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_GETTING_READY.negate(), CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.negate()), @@ -533,10 +521,7 @@ export class StopListeningAction extends Action2 { constructor() { super({ id: StopListeningAction.ID, - title: { - value: localize('workbench.action.chat.stopListening.label', "Stop Listening"), - original: 'Stop Listening' - }, + title: localize2('workbench.action.chat.stopListening.label', "Stop Listening"), category: CHAT_CATEGORY, f1: true, keybinding: { @@ -560,10 +545,7 @@ export class StopListeningInChatViewAction extends Action2 { constructor() { super({ id: StopListeningInChatViewAction.ID, - title: { - value: localize('workbench.action.chat.stopListeningInChatView.label', "Stop Listening"), - original: 'Stop Listening' - }, + title: localize2('workbench.action.chat.stopListeningInChatView.label', "Stop Listening"), category: CHAT_CATEGORY, keybinding: { weight: KeybindingWeight.WorkbenchContrib + 100, @@ -593,10 +575,7 @@ export class StopListeningInChatEditorAction extends Action2 { constructor() { super({ id: StopListeningInChatEditorAction.ID, - title: { - value: localize('workbench.action.chat.stopListeningInChatEditor.label', "Stop Listening"), - original: 'Stop Listening' - }, + title: localize2('workbench.action.chat.stopListeningInChatEditor.label', "Stop Listening"), category: CHAT_CATEGORY, keybinding: { weight: KeybindingWeight.WorkbenchContrib + 100, @@ -626,10 +605,7 @@ export class StopListeningInQuickChatAction extends Action2 { constructor() { super({ id: StopListeningInQuickChatAction.ID, - title: { - value: localize('workbench.action.chat.stopListeningInQuickChat.label', "Stop Listening"), - original: 'Stop Listening' - }, + title: localize2('workbench.action.chat.stopListeningInQuickChat.label', "Stop Listening"), category: CHAT_CATEGORY, keybinding: { weight: KeybindingWeight.WorkbenchContrib + 100, @@ -659,10 +635,7 @@ export class StopListeningInInlineChatAction extends Action2 { constructor() { super({ id: StopListeningInInlineChatAction.ID, - title: { - value: localize('workbench.action.chat.stopListeningInInlineChat.label', "Stop Listening"), - original: 'Stop Listening' - }, + title: localize2('workbench.action.chat.stopListeningInInlineChat.label', "Stop Listening"), category: CHAT_CATEGORY, keybinding: { weight: KeybindingWeight.WorkbenchContrib + 100, @@ -692,10 +665,7 @@ export class StopListeningAndSubmitAction extends Action2 { constructor() { super({ id: StopListeningAndSubmitAction.ID, - title: { - value: localize('workbench.action.chat.stopListeningAndSubmit.label', "Stop Listening and Submit"), - original: 'Stop Listening and Submit' - }, + title: localize2('workbench.action.chat.stopListeningAndSubmit.label', "Stop Listening and Submit"), category: CHAT_CATEGORY, f1: true, precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_PROGRESS) diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index b33741f6315..9e12975a5c9 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -20,7 +20,7 @@ class ToggleScreenReaderMode extends Action2 { constructor() { super({ id: 'editor.action.toggleScreenReaderAccessibilityMode', - title: { value: nls.localize('toggleScreenReaderMode', "Toggle Screen Reader Accessibility Mode"), original: 'Toggle Screen Reader Accessibility Mode' }, + title: nls.localize2('toggleScreenReaderMode', "Toggle Screen Reader Accessibility Mode"), f1: true, keybinding: [{ primary: KeyMod.CtrlCmd | KeyCode.KeyE, diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/startDebugTextMate.ts b/src/vs/workbench/contrib/codeEditor/electron-sandbox/startDebugTextMate.ts index b73832636f7..653f28e4d15 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-sandbox/startDebugTextMate.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-sandbox/startDebugTextMate.ts @@ -29,7 +29,7 @@ class StartDebugTextMate extends Action2 { constructor() { super({ id: 'editor.action.startDebugTextMate', - title: { value: nls.localize('startDebugTextMate', "Start Text Mate Syntax Grammar Logging"), original: 'Start Text Mate Syntax Grammar Logging' }, + title: nls.localize2('startDebugTextMate', "Start Text Mate Syntax Grammar Logging"), category: Categories.Developer, f1: true }); diff --git a/src/vs/workbench/contrib/commands/common/commands.contribution.ts b/src/vs/workbench/contrib/commands/common/commands.contribution.ts index c830ac9ef6a..c4d4a5b3649 100644 --- a/src/vs/workbench/contrib/commands/common/commands.contribution.ts +++ b/src/vs/workbench/contrib/commands/common/commands.contribution.ts @@ -22,7 +22,7 @@ class RunCommands extends Action2 { constructor() { super({ id: 'runCommands', - title: { value: nls.localize('runCommands', "Run Commands"), original: 'Run Commands' }, + title: nls.localize2('runCommands', "Run Commands"), f1: false, metadata: { description: nls.localize('runCommands.description', "Run several commands"), diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index e10b4bbd72b..d676d243997 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -75,31 +75,31 @@ export const CALLSTACK_UP_ID = 'workbench.action.debug.callStackUp'; export const CALLSTACK_DOWN_ID = 'workbench.action.debug.callStackDown'; export const DEBUG_COMMAND_CATEGORY: ILocalizedString = { original: 'Debug', value: nls.localize('debug', 'Debug') }; -export const RESTART_LABEL = { value: nls.localize('restartDebug', "Restart"), original: 'Restart' }; -export const STEP_OVER_LABEL = { value: nls.localize('stepOverDebug', "Step Over"), original: 'Step Over' }; -export const STEP_INTO_LABEL = { value: nls.localize('stepIntoDebug', "Step Into"), original: 'Step Into' }; -export const STEP_INTO_TARGET_LABEL = { value: nls.localize('stepIntoTargetDebug', "Step Into Target"), original: 'Step Into Target' }; -export const STEP_OUT_LABEL = { value: nls.localize('stepOutDebug', "Step Out"), original: 'Step Out' }; -export const PAUSE_LABEL = { value: nls.localize('pauseDebug', "Pause"), original: 'Pause' }; -export const DISCONNECT_LABEL = { value: nls.localize('disconnect', "Disconnect"), original: 'Disconnect' }; -export const DISCONNECT_AND_SUSPEND_LABEL = { value: nls.localize('disconnectSuspend', "Disconnect and Suspend"), original: 'Disconnect and Suspend' }; -export const STOP_LABEL = { value: nls.localize('stop', "Stop"), original: 'Stop' }; -export const CONTINUE_LABEL = { value: nls.localize('continueDebug', "Continue"), original: 'Continue' }; -export const FOCUS_SESSION_LABEL = { value: nls.localize('focusSession', "Focus Session"), original: 'Focus Session' }; -export const SELECT_AND_START_LABEL = { value: nls.localize('selectAndStartDebugging', "Select and Start Debugging"), original: 'Select and Start Debugging' }; +export const RESTART_LABEL = nls.localize2('restartDebug', "Restart"); +export const STEP_OVER_LABEL = nls.localize2('stepOverDebug', "Step Over"); +export const STEP_INTO_LABEL = nls.localize2('stepIntoDebug', "Step Into"); +export const STEP_INTO_TARGET_LABEL = nls.localize2('stepIntoTargetDebug', "Step Into Target"); +export const STEP_OUT_LABEL = nls.localize2('stepOutDebug', "Step Out"); +export const PAUSE_LABEL = nls.localize2('pauseDebug', "Pause"); +export const DISCONNECT_LABEL = nls.localize2('disconnect', "Disconnect"); +export const DISCONNECT_AND_SUSPEND_LABEL = nls.localize2('disconnectSuspend', "Disconnect and Suspend"); +export const STOP_LABEL = nls.localize2('stop', "Stop"); +export const CONTINUE_LABEL = nls.localize2('continueDebug', "Continue"); +export const FOCUS_SESSION_LABEL = nls.localize2('focusSession', "Focus Session"); +export const SELECT_AND_START_LABEL = nls.localize2('selectAndStartDebugging', "Select and Start Debugging"); export const DEBUG_CONFIGURE_LABEL = nls.localize('openLaunchJson', "Open '{0}'", 'launch.json'); -export const DEBUG_START_LABEL = { value: nls.localize('startDebug', "Start Debugging"), original: 'Start Debugging' }; -export const DEBUG_RUN_LABEL = { value: nls.localize('startWithoutDebugging', "Start Without Debugging"), original: 'Start Without Debugging' }; -export const NEXT_DEBUG_CONSOLE_LABEL = { value: nls.localize('nextDebugConsole', "Focus Next Debug Console"), original: 'Focus Next Debug Console' }; -export const PREV_DEBUG_CONSOLE_LABEL = { value: nls.localize('prevDebugConsole', "Focus Previous Debug Console"), original: 'Focus Previous Debug Console' }; -export const OPEN_LOADED_SCRIPTS_LABEL = { value: nls.localize('openLoadedScript', "Open Loaded Script..."), original: 'Open Loaded Script...' }; -export const CALLSTACK_TOP_LABEL = { value: nls.localize('callStackTop', "Navigate to Top of Call Stack"), original: 'Navigate to Top of Call Stack' }; -export const CALLSTACK_BOTTOM_LABEL = { value: nls.localize('callStackBottom', "Navigate to Bottom of Call Stack"), original: 'Navigate to Bottom of Call Stack' }; -export const CALLSTACK_UP_LABEL = { value: nls.localize('callStackUp', "Navigate Up Call Stack"), original: 'Navigate Up Call Stack' }; -export const CALLSTACK_DOWN_LABEL = { value: nls.localize('callStackDown', "Navigate Down Call Stack"), original: 'Navigate Down Call Stack' }; +export const DEBUG_START_LABEL = nls.localize2('startDebug', "Start Debugging"); +export const DEBUG_RUN_LABEL = nls.localize2('startWithoutDebugging', "Start Without Debugging"); +export const NEXT_DEBUG_CONSOLE_LABEL = nls.localize2('nextDebugConsole', "Focus Next Debug Console"); +export const PREV_DEBUG_CONSOLE_LABEL = nls.localize2('prevDebugConsole', "Focus Previous Debug Console"); +export const OPEN_LOADED_SCRIPTS_LABEL = nls.localize2('openLoadedScript', "Open Loaded Script..."); +export const CALLSTACK_TOP_LABEL = nls.localize2('callStackTop', "Navigate to Top of Call Stack"); +export const CALLSTACK_BOTTOM_LABEL = nls.localize2('callStackBottom', "Navigate to Bottom of Call Stack"); +export const CALLSTACK_UP_LABEL = nls.localize2('callStackUp', "Navigate Up Call Stack"); +export const CALLSTACK_DOWN_LABEL = nls.localize2('callStackDown', "Navigate Down Call Stack"); -export const SELECT_DEBUG_CONSOLE_LABEL = { value: nls.localize('selectDebugConsole', "Select Debug Console"), original: 'Select Debug Console' }; -export const SELECT_DEBUG_SESSION_LABEL = { value: nls.localize('selectDebugSession', "Select Debug Session"), original: 'Select Debug Session' }; +export const SELECT_DEBUG_CONSOLE_LABEL = nls.localize2('selectDebugConsole', "Select Debug Console"); +export const SELECT_DEBUG_SESSION_LABEL = nls.localize2('selectDebugSession', "Select Debug Session"); export const DEBUG_QUICK_ACCESS_PREFIX = 'debug '; export const DEBUG_CONSOLE_QUICK_ACCESS_PREFIX = 'debug consoles '; @@ -943,7 +943,7 @@ registerAction2(class AddConfigurationAction extends Action2 { constructor() { super({ id: ADD_CONFIGURATION_ID, - title: { value: nls.localize('addConfiguration', "Add Configuration..."), original: 'Add Configuration...' }, + title: nls.localize2('addConfiguration', "Add Configuration..."), category: DEBUG_COMMAND_CATEGORY, f1: true, menu: { diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index 6dd73fa5326..e984c264281 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -88,7 +88,7 @@ const openLocalFolderCommand: IAction2Options = { }; const showOutputChannelCommand: IAction2Options = { id: 'workbench.editSessions.actions.showOutputChannel', - title: { value: localize('show log', 'Show Log'), original: 'Show Log' }, + title: localize2('show log', "Show Log"), category: EDIT_SESSION_SYNC_CATEGORY }; const installAdditionalContinueOnOptionsCommand = { diff --git a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index b05e563275c..fc0f45125f8 100644 --- a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -501,7 +501,7 @@ export class ShowRuntimeExtensionsAction extends Action2 { constructor() { super({ id: 'workbench.action.showRuntimeExtensions', - title: { value: nls.localize('showRuntimeExtensions', "Show Running Extensions"), original: 'Show Running Extensions' }, + title: nls.localize2('showRuntimeExtensions', "Show Running Extensions"), category: Categories.Developer, f1: true, menu: { diff --git a/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts b/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts index 7b36ebba216..e58341fc486 100644 --- a/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts +++ b/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts @@ -77,7 +77,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: OPEN_NATIVE_CONSOLE_COMMAND_ID, - title: { value: nls.localize('globalConsoleAction', "Open New External Terminal"), original: 'Open New External Terminal' } + title: nls.localize2('globalConsoleAction', "Open New External Terminal") } }); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index bb3ef995447..8fcb0937fd9 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -204,12 +204,12 @@ export function appendToCommandPalette({ id, title, category, metadata }: IComma appendToCommandPalette({ id: COPY_PATH_COMMAND_ID, - title: { value: nls.localize('copyPathOfActive', "Copy Path of Active File"), original: 'Copy Path of Active File' }, + title: nls.localize2('copyPathOfActive', "Copy Path of Active File"), category: Categories.File }); appendToCommandPalette({ id: COPY_RELATIVE_PATH_COMMAND_ID, - title: { value: nls.localize('copyRelativePathOfActive', "Copy Relative Path of Active File"), original: 'Copy Relative Path of Active File' }, + title: nls.localize2('copyRelativePathOfActive', "Copy Relative Path of Active File"), category: Categories.File }); @@ -227,25 +227,25 @@ appendToCommandPalette({ appendToCommandPalette({ id: SAVE_ALL_IN_GROUP_COMMAND_ID, - title: { value: nls.localize('saveAllInGroup', "Save All in Group"), original: 'Save All in Group' }, + title: nls.localize2('saveAllInGroup', "Save All in Group"), category: Categories.File }); appendToCommandPalette({ id: SAVE_FILES_COMMAND_ID, - title: { value: nls.localize('saveFiles', "Save All Files"), original: 'Save All Files' }, + title: nls.localize2('saveFiles', "Save All Files"), category: Categories.File }); appendToCommandPalette({ id: REVERT_FILE_COMMAND_ID, - title: { value: nls.localize('revert', "Revert File"), original: 'Revert File' }, + title: nls.localize2('revert', "Revert File"), category: Categories.File }); appendToCommandPalette({ id: COMPARE_WITH_SAVED_COMMAND_ID, - title: { value: nls.localize('compareActiveWithSaved', "Compare Active File with Saved"), original: 'Compare Active File with Saved' }, + title: nls.localize2('compareActiveWithSaved', "Compare Active File with Saved"), category: Categories.File }); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 739d6d19ce2..71960988d83 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -1026,7 +1026,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.files.action.refreshFilesExplorer', - title: { value: nls.localize('refreshExplorer', "Refresh Explorer"), original: 'Refresh Explorer' }, + title: nls.localize2('refreshExplorer', "Refresh Explorer"), f1: true, icon: Codicon.refresh, menu: { @@ -1050,7 +1050,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.files.action.collapseExplorerFolders', - title: { value: nls.localize('collapseExplorerFolders', "Collapse Folders in Explorer"), original: 'Collapse Folders in Explorer' }, + title: nls.localize2('collapseExplorerFolders', "Collapse Folders in Explorer"), f1: true, icon: Codicon.collapseAll, menu: { diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index e4b7b0ca2c9..a6b15081da2 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -795,7 +795,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.toggleEditorGroupLayout', - title: { value: nls.localize('flipLayout', "Toggle Vertical/Horizontal Editor Layout"), original: 'Toggle Vertical/Horizontal Editor Layout' }, + title: nls.localize2('flipLayout', "Toggle Vertical/Horizontal Editor Layout"), f1: true, keybinding: { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.Digit0, @@ -882,7 +882,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'openEditors.newUntitledFile', - title: { value: nls.localize('newUntitledFile', "New Untitled Text File"), original: 'New Untitled Text File' }, + title: nls.localize2('newUntitledFile', "New Untitled Text File"), f1: false, icon: Codicon.newFile, menu: { diff --git a/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts b/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts index 256a2ec9786..92a340e8175 100644 --- a/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts @@ -89,7 +89,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { // Command Palette -const category = { value: nls.localize('filesCategory', "File"), original: 'File' }; +const category = nls.localize2('filesCategory', "File"); appendToCommandPalette({ id: REVEAL_IN_OS_COMMAND_ID, title: { value: REVEAL_IN_OS_LABEL, original: isWindows ? 'Reveal in File Explorer' : isMacintosh ? 'Reveal in Finder' : 'Open Containing Folder' }, diff --git a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts index 3f5e22cb5cc..16e3d189f00 100644 --- a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts +++ b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts @@ -13,7 +13,7 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InlayHintItem, asCommandLink } from 'vs/editor/contrib/inlayHints/browser/inlayHints'; import { InlayHintsController } from 'vs/editor/contrib/inlayHints/browser/inlayHintsController'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -174,10 +174,7 @@ registerAction2(class StartReadHints extends EditorAction2 { constructor() { super({ id: 'inlayHints.startReadingLineWithHint', - title: { - value: localize('read.title', 'Read Line With Inline Hints'), - original: 'Read Line With Inline Hints' - }, + title: localize2('read.title', "Read Line With Inline Hints"), precondition: EditorContextKeys.hasInlayHintsProvider, f1: true }); @@ -194,10 +191,7 @@ registerAction2(class StopReadHints extends EditorAction2 { constructor() { super({ id: 'inlayHints.stopReadingLineWithHint', - title: { - value: localize('stop.title', 'Stop Inlay Hints Reading'), - original: 'Stop Inlay Hints Reading' - }, + title: localize2('stop.title', "Stop Inlay Hints Reading"), precondition: InlayHintsAccessibility.IsReading, f1: true, keybinding: { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index c1c0679e305..f2402537afb 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -41,7 +41,7 @@ export class UnstashSessionAction extends EditorAction2 { constructor() { super({ id: 'inlineChat.unstash', - title: { value: localize('unstash', 'Resume Last Dismissed Inline Chat'), original: 'Resume Last Dismissed Inline Chat' }, + title: localize2('unstash', "Resume Last Dismissed Inline Chat"), category: AbstractInlineChatAction.category, precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_STASHED_SESSION, EditorContextKeys.writable), keybinding: { @@ -67,7 +67,7 @@ export class UnstashSessionAction extends EditorAction2 { export abstract class AbstractInlineChatAction extends EditorAction2 { - static readonly category = { value: localize('cat', 'Inline Chat'), original: 'Inline Chat' }; + static readonly category = localize2('cat', "Inline Chat"); constructor(desc: IAction2Options) { super({ @@ -216,7 +216,7 @@ export class FocusInlineChat extends EditorAction2 { constructor() { super({ id: 'inlineChat.focus', - title: { value: localize('focus', 'Focus Input'), original: 'Focus Input' }, + title: localize2('focus', "Focus Input"), f1: true, category: AbstractInlineChatAction.category, precondition: ContextKeyExpr.and(EditorContextKeys.editorTextFocus, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_FOCUSED.negate(), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), @@ -491,7 +491,7 @@ export class AcceptChanges extends AbstractInlineChatAction { constructor() { super({ id: ACTION_ACCEPT_CHANGES, - title: { value: localize('apply1', 'Accept Changes'), original: 'Accept Changes' }, + title: localize2('apply1', "Accept Changes"), shortTitle: localize('apply2', 'Accept'), icon: Codicon.check, f1: true, @@ -596,10 +596,7 @@ export class CopyRecordings extends AbstractInlineChatAction { super({ id: 'inlineChat.copyRecordings', f1: true, - title: { - value: localize('copyRecordings', '(Developer) Write Exchange to Clipboard'), - original: '(Developer) Write Exchange to Clipboard' - } + title: localize2('copyRecordings', "(Developer) Write Exchange to Clipboard") }); } diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index e44cb29c562..03ca0d672ee 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -64,7 +64,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; -const interactiveWindowCategory: ILocalizedString = { value: localize('interactiveWindow', 'Interactive Window'), original: 'Interactive Window' }; +const interactiveWindowCategory: ILocalizedString = localize2('interactiveWindow', "Interactive Window"); Registry.as(EditorExtensions.EditorPane).registerEditorPane( EditorPaneDescriptor.create( diff --git a/src/vs/workbench/contrib/keybindings/browser/keybindings.contribution.ts b/src/vs/workbench/contrib/keybindings/browser/keybindings.contribution.ts index 21fb4293a9c..8c4bd19bb0d 100644 --- a/src/vs/workbench/contrib/keybindings/browser/keybindings.contribution.ts +++ b/src/vs/workbench/contrib/keybindings/browser/keybindings.contribution.ts @@ -16,7 +16,7 @@ class ToggleKeybindingsLogAction extends Action2 { constructor() { super({ id: 'workbench.action.toggleKeybindingsLog', - title: { value: nls.localize('toggleKeybindingsLog', "Toggle Keyboard Shortcuts Troubleshooting"), original: 'Toggle Keyboard Shortcuts Troubleshooting' }, + title: nls.localize2('toggleKeybindingsLog', "Toggle Keyboard Shortcuts Troubleshooting"), category: Categories.Developer, f1: true }); diff --git a/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts index ea29b76b146..b9735fd8973 100644 --- a/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts +++ b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts @@ -5,7 +5,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -123,7 +123,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: detectLanguageCommandId, - title: { value: localize('detectlang', 'Detect Language from Content'), original: 'Detect Language from Content' }, + title: localize2('detectlang', "Detect Language from Content"), f1: true, precondition: ContextKeyExpr.and(NOTEBOOK_EDITOR_EDITABLE.toNegated(), EditorContextKeys.editorTextFocus), keybinding: { primary: KeyCode.KeyD | KeyMod.Alt | KeyMod.Shift, weight: KeybindingWeight.WorkbenchContrib } diff --git a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts index 508d9d4b0c9..45e898b8fc1 100644 --- a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts +++ b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts @@ -9,7 +9,7 @@ import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { Disposable, DisposableStore, dispose, toDisposable } from 'vs/base/common/lifecycle'; import Severity from 'vs/base/common/severity'; import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { ThemeIcon } from 'vs/base/common/themables'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -422,10 +422,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'editor.inlayHints.Reset', - title: { - value: localize('reset', 'Reset Language Status Interaction Counter'), - original: 'Reset Language Status Interaction Counter' - }, + title: localize2('reset', "Reset Language Status Interaction Counter"), category: Categories.View, f1: true }); diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index 2b5ec6c3cfe..19257411641 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -45,7 +45,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.setDefaultLogLevel', - title: { value: nls.localize('setDefaultLogLevel', "Set Default Log Level"), original: 'Set Default Log Level' }, + title: nls.localize2('setDefaultLogLevel', "Set Default Log Level"), category: Categories.Developer, }); } @@ -199,7 +199,7 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { constructor() { super({ id: showWindowLogActionId, - title: { value: nls.localize('show window log', "Show Window Log"), original: 'Show Window Log' }, + title: nls.localize2('show window log', "Show Window Log"), category: Categories.Developer, f1: true }); diff --git a/src/vs/workbench/contrib/logs/common/logsActions.ts b/src/vs/workbench/contrib/logs/common/logsActions.ts index 70e067ef6d5..519fab7f93a 100644 --- a/src/vs/workbench/contrib/logs/common/logsActions.ts +++ b/src/vs/workbench/contrib/logs/common/logsActions.ts @@ -25,7 +25,7 @@ type LogChannelQuickPickItem = IQuickPickItem & { id: string; resource: URI; ext export class SetLogLevelAction extends Action { static readonly ID = 'workbench.action.setLogLevel'; - static readonly TITLE = { value: nls.localize('setLogLevel', "Set Log Level..."), original: 'Set Log Level...' }; + static readonly TITLE = nls.localize2('setLogLevel', "Set Log Level..."); constructor(id: string, label: string, @IQuickInputService private readonly quickInputService: IQuickInputService, @@ -162,7 +162,7 @@ export class SetLogLevelAction extends Action { export class OpenWindowSessionLogFileAction extends Action { static readonly ID = 'workbench.action.openSessionLogFile'; - static readonly TITLE = { value: nls.localize('openSessionLogFile', "Open Window Log File (Session)..."), original: 'Open Window Log File (Session)...' }; + static readonly TITLE = nls.localize2('openSessionLogFile', "Open Window Log File (Session)..."); constructor(id: string, label: string, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, diff --git a/src/vs/workbench/contrib/logs/electron-sandbox/logsActions.ts b/src/vs/workbench/contrib/logs/electron-sandbox/logsActions.ts index cbc2a01dbb5..b0fb77fd4b4 100644 --- a/src/vs/workbench/contrib/logs/electron-sandbox/logsActions.ts +++ b/src/vs/workbench/contrib/logs/electron-sandbox/logsActions.ts @@ -14,7 +14,7 @@ import { Schemas } from 'vs/base/common/network'; export class OpenLogsFolderAction extends Action { static readonly ID = 'workbench.action.openLogsFolder'; - static readonly TITLE = { value: nls.localize('openLogsFolder', "Open Logs Folder"), original: 'Open Logs Folder' }; + static readonly TITLE = nls.localize2('openLogsFolder', "Open Logs Folder"); constructor(id: string, label: string, @INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, @@ -31,7 +31,7 @@ export class OpenLogsFolderAction extends Action { export class OpenExtensionLogsFolderAction extends Action { static readonly ID = 'workbench.action.openExtensionLogsFolder'; - static readonly TITLE = { value: nls.localize('openExtensionLogsFolder', "Open Extension Logs Folder"), original: 'Open Extension Logs Folder' }; + static readonly TITLE = nls.localize2('openExtensionLogsFolder', "Open Extension Logs Folder"); constructor(id: string, label: string, @INativeWorkbenchEnvironmentService private readonly environmentSerice: INativeWorkbenchEnvironmentService, diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index 90d23b25a11..39659a12926 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -172,10 +172,7 @@ export class SetMixedLayout extends Action2 { constructor() { super({ id: 'merge.mixedLayout', - title: { - value: localize('layout.mixed', 'Mixed Layout'), - original: 'Mixed Layout', - }, + title: localize2('layout.mixed', "Mixed Layout"), toggled: ctxMergeEditorLayout.isEqualTo('mixed'), menu: [ { @@ -225,10 +222,7 @@ export class ShowNonConflictingChanges extends Action2 { constructor() { super({ id: 'merge.showNonConflictingChanges', - title: { - value: localize('showNonConflictingChanges', 'Show Non-Conflicting Changes'), - original: 'Show Non-Conflicting Changes', - }, + title: localize2('showNonConflictingChanges', "Show Non-Conflicting Changes"), toggled: ctxMergeEditorShowNonConflictingChanges.isEqualTo(true), menu: [ { @@ -254,10 +248,7 @@ export class ShowHideBase extends Action2 { constructor() { super({ id: 'merge.showBase', - title: { - value: localize('layout.showBase', 'Show Base'), - original: 'Show Base', - }, + title: localize2('layout.showBase', "Show Base"), toggled: ctxMergeEditorShowBase.isEqualTo(true), menu: [ { @@ -282,10 +273,7 @@ export class ShowHideTopBase extends Action2 { constructor() { super({ id: 'merge.showBaseTop', - title: { - value: localize('layout.showBaseTop', 'Show Base Top'), - original: 'Show Base Top', - }, + title: localize2('layout.showBaseTop', "Show Base Top"), toggled: ContextKeyExpr.and(ctxMergeEditorShowBase, ctxMergeEditorShowBaseAtTop), menu: [ { @@ -310,10 +298,7 @@ export class ShowHideCenterBase extends Action2 { constructor() { super({ id: 'merge.showBaseCenter', - title: { - value: localize('layout.showBaseCenter', 'Show Base Center'), - original: 'Show Base Center', - }, + title: localize2('layout.showBaseCenter', "Show Base Center"), toggled: ContextKeyExpr.and(ctxMergeEditorShowBase, ctxMergeEditorShowBaseAtTop.negate()), menu: [ { @@ -334,20 +319,14 @@ export class ShowHideCenterBase extends Action2 { } } -const mergeEditorCategory: ILocalizedString = { - value: localize('mergeEditor', 'Merge Editor'), - original: 'Merge Editor', -}; +const mergeEditorCategory: ILocalizedString = localize2('mergeEditor', "Merge Editor"); export class OpenResultResource extends MergeEditorAction { constructor() { super({ id: 'merge.openResult', icon: Codicon.goToFile, - title: { - value: localize('openfile', 'Open File'), - original: 'Open File', - }, + title: localize2('openfile', "Open File"), category: mergeEditorCategory, menu: [{ id: MenuId.EditorTitle, @@ -370,10 +349,7 @@ export class GoToNextUnhandledConflict extends MergeEditorAction { super({ id: 'merge.goToNextUnhandledConflict', category: mergeEditorCategory, - title: { - value: localize('merge.goToNextUnhandledConflict', 'Go to Next Unhandled Conflict'), - original: 'Go to Next Unhandled Conflict', - }, + title: localize2('merge.goToNextUnhandledConflict', "Go to Next Unhandled Conflict"), icon: Codicon.arrowDown, menu: [ { @@ -399,13 +375,7 @@ export class GoToPreviousUnhandledConflict extends MergeEditorAction { super({ id: 'merge.goToPreviousUnhandledConflict', category: mergeEditorCategory, - title: { - value: localize( - 'merge.goToPreviousUnhandledConflict', - 'Go to Previous Unhandled Conflict' - ), - original: 'Go to Previous Unhandled Conflict', - }, + title: localize2('merge.goToPreviousUnhandledConflict', "Go to Previous Unhandled Conflict"), icon: Codicon.arrowUp, menu: [ { @@ -431,13 +401,7 @@ export class ToggleActiveConflictInput1 extends MergeEditorAction { super({ id: 'merge.toggleActiveConflictInput1', category: mergeEditorCategory, - title: { - value: localize( - 'merge.toggleCurrentConflictFromLeft', - 'Toggle Current Conflict from Left' - ), - original: 'Toggle Current Conflict from Left', - }, + title: localize2('merge.toggleCurrentConflictFromLeft', "Toggle Current Conflict from Left"), f1: true, precondition: ctxIsMergeEditor, }); @@ -453,13 +417,7 @@ export class ToggleActiveConflictInput2 extends MergeEditorAction { super({ id: 'merge.toggleActiveConflictInput2', category: mergeEditorCategory, - title: { - value: localize( - 'merge.toggleCurrentConflictFromRight', - 'Toggle Current Conflict from Right' - ), - original: 'Toggle Current Conflict from Right', - }, + title: localize2('merge.toggleCurrentConflictFromRight', "Toggle Current Conflict from Right"), f1: true, precondition: ctxIsMergeEditor, }); @@ -475,13 +433,7 @@ export class CompareInput1WithBaseCommand extends MergeEditorAction { super({ id: 'mergeEditor.compareInput1WithBase', category: mergeEditorCategory, - title: { - value: localize( - 'mergeEditor.compareInput1WithBase', - 'Compare Input 1 With Base' - ), - original: 'Compare Input 1 With Base', - }, + title: localize2('mergeEditor.compareInput1WithBase', "Compare Input 1 With Base"), shortTitle: localize('mergeEditor.compareWithBase', 'Compare With Base'), f1: true, precondition: ctxIsMergeEditor, @@ -501,13 +453,7 @@ export class CompareInput2WithBaseCommand extends MergeEditorAction { super({ id: 'mergeEditor.compareInput2WithBase', category: mergeEditorCategory, - title: { - value: localize( - 'mergeEditor.compareInput2WithBase', - 'Compare Input 2 With Base' - ), - original: 'Compare Input 2 With Base', - }, + title: localize2('mergeEditor.compareInput2WithBase', "Compare Input 2 With Base"), shortTitle: localize('mergeEditor.compareWithBase', 'Compare With Base'), f1: true, precondition: ctxIsMergeEditor, @@ -550,10 +496,7 @@ export class OpenBaseFile extends MergeEditorAction { super({ id: 'merge.openBaseEditor', category: mergeEditorCategory, - title: { - value: localize('merge.openBaseEditor', 'Open Base File'), - original: 'Open Base File', - }, + title: localize2('merge.openBaseEditor', "Open Base File"), f1: true, precondition: ctxIsMergeEditor, }); @@ -570,13 +513,7 @@ export class AcceptAllInput1 extends MergeEditorAction { super({ id: 'merge.acceptAllInput1', category: mergeEditorCategory, - title: { - value: localize( - 'merge.acceptAllInput1', - 'Accept All Changes from Left' - ), - original: 'Accept All Changes from Left', - }, + title: localize2('merge.acceptAllInput1', "Accept All Changes from Left"), f1: true, precondition: ctxIsMergeEditor, menu: { id: MenuId.MergeInput1Toolbar, group: 'primary' }, @@ -594,13 +531,7 @@ export class AcceptAllInput2 extends MergeEditorAction { super({ id: 'merge.acceptAllInput2', category: mergeEditorCategory, - title: { - value: localize( - 'merge.acceptAllInput2', - 'Accept All Changes from Right' - ), - original: 'Accept All Changes from Right', - }, + title: localize2('merge.acceptAllInput2', "Accept All Changes from Right"), f1: true, precondition: ctxIsMergeEditor, menu: { id: MenuId.MergeInput2Toolbar, group: 'primary' }, @@ -618,13 +549,7 @@ export class ResetToBaseAndAutoMergeCommand extends MergeEditorAction { super({ id: 'mergeEditor.resetResultToBaseAndAutoMerge', category: mergeEditorCategory, - title: { - value: localize( - 'mergeEditor.resetResultToBaseAndAutoMerge', - 'Reset Result' - ), - original: 'Reset Result', - }, + title: localize2('mergeEditor.resetResultToBaseAndAutoMerge', "Reset Result"), shortTitle: localize('mergeEditor.resetResultToBaseAndAutoMerge.short', 'Reset'), f1: true, precondition: ctxIsMergeEditor, @@ -643,13 +568,7 @@ export class ResetCloseWithConflictsChoice extends Action2 { super({ id: 'mergeEditor.resetCloseWithConflictsChoice', category: mergeEditorCategory, - title: { - value: localize( - 'mergeEditor.resetChoice', - 'Reset Choice for \'Close with Conflicts\'' - ), - original: 'Reset Choice for \'Close with Conflicts\'', - }, + title: localize2('mergeEditor.resetChoice', "Reset Choice for \'Close with Conflicts\'"), f1: true, }); } @@ -664,13 +583,7 @@ export class AcceptMerge extends MergeEditorAction2 { super({ id: 'mergeEditor.acceptMerge', category: mergeEditorCategory, - title: { - value: localize( - 'mergeEditor.acceptMerge', - 'Complete Merge' - ), - original: 'Complete Merge', - }, + title: localize2('mergeEditor.acceptMerge', "Complete Merge"), f1: false, precondition: ctxIsMergeEditor }); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts index 8ddbfaf4d96..264d71f6ac4 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts @@ -28,13 +28,7 @@ export class MergeEditorCopyContentsToJSON extends Action2 { super({ id: 'merge.dev.copyContentsJson', category: MERGE_EDITOR_CATEGORY, - title: { - value: localize( - 'merge.dev.copyState', - 'Copy Merge Editor State as JSON' - ), - original: 'Copy Merge Editor State as JSON', - }, + title: localize2('merge.dev.copyState', "Copy Merge Editor State as JSON"), icon: Codicon.layoutCentered, f1: true, precondition: ctxIsMergeEditor, @@ -80,13 +74,7 @@ export class MergeEditorSaveContentsToFolder extends Action2 { super({ id: 'merge.dev.saveContentsToFolder', category: MERGE_EDITOR_CATEGORY, - title: { - value: localize( - 'merge.dev.saveContentsToFolder', - 'Save Merge Editor State to Folder' - ), - original: 'Save Merge Editor State to Folder', - }, + title: localize2('merge.dev.saveContentsToFolder', "Save Merge Editor State to Folder"), icon: Codicon.layoutCentered, f1: true, precondition: ctxIsMergeEditor, @@ -149,13 +137,7 @@ export class MergeEditorLoadContentsFromFolder extends Action2 { super({ id: 'merge.dev.loadContentsFromFolder', category: MERGE_EDITOR_CATEGORY, - title: { - value: localize( - 'merge.dev.loadContentsFromFolder', - 'Load Merge Editor State from Folder' - ), - original: 'Load Merge Editor State from Folder', - }, + title: localize2('merge.dev.loadContentsFromFolder', "Load Merge Editor State from Folder"), icon: Codicon.layoutCentered, f1: true }); diff --git a/src/vs/workbench/contrib/mergeEditor/electron-sandbox/devCommands.ts b/src/vs/workbench/contrib/mergeEditor/electron-sandbox/devCommands.ts index e302a2e5a2c..cfd541d1d3f 100644 --- a/src/vs/workbench/contrib/mergeEditor/electron-sandbox/devCommands.ts +++ b/src/vs/workbench/contrib/mergeEditor/electron-sandbox/devCommands.ts @@ -29,13 +29,7 @@ export class MergeEditorOpenContentsFromJSON extends Action2 { super({ id: 'merge.dev.openContentsJson', category: MERGE_EDITOR_CATEGORY, - title: { - value: localize( - 'merge.dev.openState', - 'Open Merge Editor State from JSON' - ), - original: 'Open Merge Editor State from JSON', - }, + title: localize2('merge.dev.openState', "Open Merge Editor State from JSON"), icon: Codicon.layoutCentered, f1: true, }); @@ -136,13 +130,7 @@ export class OpenSelectionInTemporaryMergeEditor extends MergeEditorAction { super({ id: 'merge.dev.openSelectionInTemporaryMergeEditor', category: MERGE_EDITOR_CATEGORY, - title: { - value: localize( - 'merge.dev.openSelectionInTemporaryMergeEditor', - 'Open Selection In Temporary Merge Editor' - ), - original: 'Open Selection In Temporary Merge Editor', - }, + title: localize2('merge.dev.openSelectionInTemporaryMergeEditor', "Open Selection In Temporary Merge Editor"), icon: Codicon.layoutCentered, f1: true, }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts index 12a076ab8e3..77a91525426 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts @@ -6,7 +6,7 @@ import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Mimes } from 'vs/base/common/mime'; import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; @@ -34,10 +34,7 @@ registerAction2(class extends NotebookCellAction { super( { id: MOVE_CELL_UP_COMMAND_ID, - title: { - value: localize('notebookActions.moveCellUp', "Move Cell Up"), - original: 'Move Cell Up' - }, + title: localize2('notebookActions.moveCellUp', "Move Cell Up"), icon: icons.moveUpIcon, keybinding: { primary: KeyMod.Alt | KeyCode.UpArrow, @@ -63,10 +60,7 @@ registerAction2(class extends NotebookCellAction { super( { id: MOVE_CELL_DOWN_COMMAND_ID, - title: { - value: localize('notebookActions.moveCellDown', "Move Cell Down"), - original: 'Move Cell Down' - }, + title: localize2('notebookActions.moveCellDown', "Move Cell Down"), icon: icons.moveDownIcon, keybinding: { primary: KeyMod.Alt | KeyCode.DownArrow, @@ -92,10 +86,7 @@ registerAction2(class extends NotebookCellAction { super( { id: COPY_CELL_UP_COMMAND_ID, - title: { - value: localize('notebookActions.copyCellUp', "Copy Cell Up"), - original: 'Copy Cell Up' - }, + title: localize2('notebookActions.copyCellUp', "Copy Cell Up"), keybinding: { primary: KeyMod.Alt | KeyMod.Shift | KeyCode.UpArrow, when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()), @@ -114,10 +105,7 @@ registerAction2(class extends NotebookCellAction { super( { id: COPY_CELL_DOWN_COMMAND_ID, - title: { - value: localize('notebookActions.copyCellDown', "Copy Cell Down"), - original: 'Copy Cell Down' - }, + title: localize2('notebookActions.copyCellDown', "Copy Cell Down"), keybinding: { primary: KeyMod.Alt | KeyMod.Shift | KeyCode.DownArrow, when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()), @@ -153,10 +141,7 @@ registerAction2(class extends NotebookCellAction { super( { id: SPLIT_CELL_COMMAND_ID, - title: { - value: localize('notebookActions.splitCell', "Split Cell"), - original: 'Split Cell' - }, + title: localize2('notebookActions.splitCell', "Split Cell"), menu: { id: MenuId.NotebookCellTitle, when: ContextKeyExpr.and( @@ -231,10 +216,7 @@ registerAction2(class extends NotebookCellAction { super( { id: JOIN_CELL_ABOVE_COMMAND_ID, - title: { - value: localize('notebookActions.joinCellAbove', "Join With Previous Cell"), - original: 'Join With Previous Cell' - }, + title: localize2('notebookActions.joinCellAbove', "Join With Previous Cell"), keybinding: { when: NOTEBOOK_EDITOR_FOCUSED, primary: KeyMod.WinCtrl | KeyMod.Alt | KeyMod.Shift | KeyCode.KeyJ, @@ -261,10 +243,7 @@ registerAction2(class extends NotebookCellAction { super( { id: JOIN_CELL_BELOW_COMMAND_ID, - title: { - value: localize('notebookActions.joinCellBelow', "Join With Next Cell"), - original: 'Join With Next Cell' - }, + title: localize2('notebookActions.joinCellBelow', "Join With Next Cell"), keybinding: { when: NOTEBOOK_EDITOR_FOCUSED, primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KeyJ, @@ -290,10 +269,7 @@ registerAction2(class extends NotebookCellAction { super( { id: JOIN_SELECTED_CELLS_COMMAND_ID, - title: { - value: localize('notebookActions.joinSelectedCells', "Join Selected Cells"), - original: 'Join Selected Cells' - }, + title: localize2('notebookActions.joinSelectedCells', "Join Selected Cells"), menu: { id: MenuId.NotebookCellTitle, when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE), @@ -321,10 +297,7 @@ registerAction2(class ChangeCellToCodeAction extends NotebookMultiCellAction { constructor() { super({ id: CHANGE_CELL_TO_CODE_COMMAND_ID, - title: { - value: localize('notebookActions.changeCellToCode', "Change Cell to Code"), - original: 'Change Cell to Code' - }, + title: localize2('notebookActions.changeCellToCode', "Change Cell to Code"), keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), NOTEBOOK_OUTPUT_FOCUSED.toNegated()), primary: KeyCode.KeyY, @@ -348,10 +321,7 @@ registerAction2(class ChangeCellToMarkdownAction extends NotebookMultiCellAction constructor() { super({ id: CHANGE_CELL_TO_MARKDOWN_COMMAND_ID, - title: { - value: localize('notebookActions.changeCellToMarkdown', "Change Cell to Markdown"), - original: 'Change Cell to Markdown' - }, + title: localize2('notebookActions.changeCellToMarkdown', "Change Cell to Markdown"), keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), NOTEBOOK_OUTPUT_FOCUSED.toNegated()), primary: KeyCode.KeyM, @@ -388,10 +358,7 @@ registerAction2(class CollapseCellInputAction extends NotebookMultiCellAction { constructor() { super({ id: COLLAPSE_CELL_INPUT_COMMAND_ID, - title: { - value: localize('notebookActions.collapseCellInput', "Collapse Cell Input"), - original: 'Collapse Cell Input' - }, + title: localize2('notebookActions.collapseCellInput', "Collapse Cell Input"), keybinding: { when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_INPUT_COLLAPSED.toNegated(), InputFocusedContext.toNegated()), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyC), @@ -417,10 +384,7 @@ registerAction2(class ExpandCellInputAction extends NotebookMultiCellAction { constructor() { super({ id: EXPAND_CELL_INPUT_COMMAND_ID, - title: { - value: localize('notebookActions.expandCellInput', "Expand Cell Input"), - original: 'Expand Cell Input' - }, + title: localize2('notebookActions.expandCellInput', "Expand Cell Input"), keybinding: { when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_INPUT_COLLAPSED), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyC), @@ -446,10 +410,7 @@ registerAction2(class CollapseCellOutputAction extends NotebookMultiCellAction { constructor() { super({ id: COLLAPSE_CELL_OUTPUT_COMMAND_ID, - title: { - value: localize('notebookActions.collapseCellOutput', "Collapse Cell Output"), - original: 'Collapse Cell Output' - }, + title: localize2('notebookActions.collapseCellOutput', "Collapse Cell Output"), keybinding: { when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED.toNegated(), InputFocusedContext.toNegated(), NOTEBOOK_CELL_HAS_OUTPUTS), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyT), @@ -471,10 +432,7 @@ registerAction2(class ExpandCellOuputAction extends NotebookMultiCellAction { constructor() { super({ id: EXPAND_CELL_OUTPUT_COMMAND_ID, - title: { - value: localize('notebookActions.expandCellOutput', "Expand Cell Output"), - original: 'Expand Cell Output' - }, + title: localize2('notebookActions.expandCellOutput', "Expand Cell Output"), keybinding: { when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyT), @@ -497,10 +455,7 @@ registerAction2(class extends NotebookMultiCellAction { super({ id: TOGGLE_CELL_OUTPUTS_COMMAND_ID, precondition: NOTEBOOK_CELL_LIST_FOCUSED, - title: { - value: localize('notebookActions.toggleOutputs', "Toggle Outputs"), - original: 'Toggle Outputs' - }, + title: localize2('notebookActions.toggleOutputs', "Toggle Outputs"), metadata: { description: localize('notebookActions.toggleOutputs', "Toggle Outputs"), args: cellExecutionArgs @@ -530,10 +485,7 @@ registerAction2(class CollapseAllCellInputsAction extends NotebookMultiCellActio constructor() { super({ id: COLLAPSE_ALL_CELL_INPUTS_COMMAND_ID, - title: { - value: localize('notebookActions.collapseAllCellInput', "Collapse All Cell Inputs"), - original: 'Collapse All Cell Inputs' - }, + title: localize2('notebookActions.collapseAllCellInput', "Collapse All Cell Inputs"), f1: true, }); } @@ -547,10 +499,7 @@ registerAction2(class ExpandAllCellInputsAction extends NotebookMultiCellAction constructor() { super({ id: EXPAND_ALL_CELL_INPUTS_COMMAND_ID, - title: { - value: localize('notebookActions.expandAllCellInput', "Expand All Cell Inputs"), - original: 'Expand All Cell Inputs' - }, + title: localize2('notebookActions.expandAllCellInput', "Expand All Cell Inputs"), f1: true }); } @@ -564,10 +513,7 @@ registerAction2(class CollapseAllCellOutputsAction extends NotebookMultiCellActi constructor() { super({ id: COLLAPSE_ALL_CELL_OUTPUTS_COMMAND_ID, - title: { - value: localize('notebookActions.collapseAllCellOutput', "Collapse All Cell Outputs"), - original: 'Collapse All Cell Outputs' - }, + title: localize2('notebookActions.collapseAllCellOutput', "Collapse All Cell Outputs"), f1: true, }); } @@ -581,10 +527,7 @@ registerAction2(class ExpandAllCellOutputsAction extends NotebookMultiCellAction constructor() { super({ id: EXPAND_ALL_CELL_OUTPUTS_COMMAND_ID, - title: { - value: localize('notebookActions.expandAllCellOutput', "Expand All Cell Outputs"), - original: 'Expand All Cell Outputs' - }, + title: localize2('notebookActions.expandAllCellOutput', "Expand All Cell Outputs"), f1: true }); } @@ -598,10 +541,7 @@ registerAction2(class ToggleCellOutputScrolling extends NotebookMultiCellAction constructor() { super({ id: TOGGLE_CELL_OUTPUT_SCROLLING, - title: { - value: localize('notebookActions.toggleScrolling', "Toggle Scroll Cell Output"), - original: 'Toggle Scroll Cell Output' - }, + title: localize2('notebookActions.toggleScrolling', "Toggle Scroll Cell Output"), keybinding: { when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, InputFocusedContext.toNegated(), NOTEBOOK_CELL_HAS_OUTPUTS), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyY), diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts b/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts index 7c37a41ac75..90b77faa7bc 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; +import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -12,11 +13,10 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Memento } from 'vs/workbench/common/memento'; -import { HAS_OPENED_NOTEBOOK } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { HAS_OPENED_NOTEBOOK } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -81,10 +81,7 @@ registerAction2(class NotebookClearNotebookLayoutAction extends Action2 { constructor() { super({ id: 'workbench.notebook.layout.gettingStarted', - title: { - value: localize('workbench.notebook.layout.gettingStarted.label', "Reset notebook getting started"), - original: 'Reset notebook getting started' - }, + title: localize2('workbench.notebook.layout.gettingStarted.label', "Reset notebook getting started"), f1: true, precondition: ContextKeyExpr.equals(`config.${NotebookSetting.openGettingStarted}`, true), category: Categories.Developer, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts index 6404536b1df..e13d8fc2a84 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; +import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { getNotebookEditorFromEditorPane, ICellViewModel, ICommonCellViewModelLayoutChangeInfo, INotebookDeltaCellStatusBarItems, INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; @@ -148,10 +148,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.inspectLayout', - title: { - value: localize('workbench.notebook.inspectLayout', "Inspect Notebook Layout"), - original: 'Inspect Notebook Layout' - }, + title: localize2('workbench.notebook.inspectLayout', "Inspect Notebook Layout"), category: Categories.Developer, f1: true }); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index 62f5aadb7ec..d43cc47b2c7 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -10,7 +10,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/model'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; @@ -517,7 +517,7 @@ registerAction2(class DetectCellLanguageAction extends NotebookCellAction { constructor() { super({ id: DETECT_CELL_LANGUAGE, - title: { value: localize('detectLanguage', 'Accept Detected Language for Cell'), original: 'Accept Detected Language for Cell' }, + title: localize2('detectLanguage', "Accept Detected Language for Cell"), f1: true, precondition: ContextKeyExpr.and(NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), keybinding: { primary: KeyCode.KeyD | KeyMod.Alt | KeyMod.Shift, weight: KeybindingWeight.WorkbenchContrib } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index 14af3383e84..2ca62a92f6f 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -10,7 +10,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -545,10 +545,7 @@ registerAction2(class CancelAllNotebook extends CancelNotebook { constructor() { super({ id: CANCEL_NOTEBOOK_COMMAND_ID, - title: { - value: localize('notebookActions.cancelNotebook', "Stop Execution"), - original: 'Stop Execution' - }, + title: localize2('notebookActions.cancelNotebook', "Stop Execution"), icon: icons.stopIcon, menu: [ { @@ -581,10 +578,7 @@ registerAction2(class InterruptNotebook extends CancelNotebook { constructor() { super({ id: INTERRUPT_NOTEBOOK_COMMAND_ID, - title: { - value: localize('notebookActions.interruptNotebook', "Interrupt"), - original: 'Interrupt' - }, + title: localize2('notebookActions.interruptNotebook', "Interrupt"), precondition: ContextKeyExpr.and( NOTEBOOK_HAS_SOMETHING_RUNNING, NOTEBOOK_INTERRUPTIBLE_KERNEL diff --git a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts index cbe53c348cf..2dfc3c83bc3 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts @@ -25,10 +25,7 @@ registerAction2(class NotebookConfigureLayoutAction extends Action2 { constructor() { super({ id: 'workbench.notebook.layout.select', - title: { - value: localize('workbench.notebook.layout.select.label', "Select between Notebook Layouts"), - original: 'Select between Notebook Layouts' - }, + title: localize2('workbench.notebook.layout.select.label', "Select between Notebook Layouts"), f1: true, precondition: ContextKeyExpr.equals(`config.${NotebookSetting.openGettingStarted}`, true), category: NOTEBOOK_ACTIONS_CATEGORY, @@ -64,10 +61,7 @@ registerAction2(class NotebookConfigureLayoutAction extends Action2 { constructor() { super({ id: 'workbench.notebook.layout.configure', - title: { - value: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"), - original: 'Customize Notebook Layout' - }, + title: localize2('workbench.notebook.layout.configure.label', "Customize Notebook Layout"), f1: true, category: NOTEBOOK_ACTIONS_CATEGORY, menu: [ @@ -89,10 +83,7 @@ registerAction2(class NotebookConfigureLayoutFromEditorTitle extends Action2 { constructor() { super({ id: 'workbench.notebook.layout.configure.editorTitle', - title: { - value: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"), - original: 'Customize Notebook Layout' - }, + title: localize2('workbench.notebook.layout.configure.label', "Customize Notebook Layout"), f1: false, category: NOTEBOOK_ACTIONS_CATEGORY, menu: [ @@ -113,7 +104,7 @@ registerAction2(class NotebookConfigureLayoutFromEditorTitle extends Action2 { MenuRegistry.appendMenuItem(MenuId.EditorTitle, { submenu: MenuId.NotebookEditorLayoutConfigure, rememberDefaultAction: false, - title: { value: localize('customizeNotebook', "Customize Notebook..."), original: 'Customize Notebook...', }, + title: localize2('customizeNotebook', "Customize Notebook..."), icon: Codicon.gear, group: 'navigation', order: -1, @@ -190,10 +181,7 @@ registerAction2(class SaveMimeTypeDisplayOrder extends Action2 { constructor() { super({ id: 'notebook.saveMimeTypeOrder', - title: { - value: localize('notebook.saveMimeTypeOrder', 'Save Mimetype Display Order'), - original: 'Save Mimetype Display Order' - }, + title: localize2('notebook.saveMimeTypeOrder', "Save Mimetype Display Order"), f1: true, category: NOTEBOOK_ACTIONS_CATEGORY, precondition: NOTEBOOK_IS_ACTIVE_EDITOR, @@ -227,10 +215,7 @@ registerAction2(class NotebookWebviewResetAction extends Action2 { constructor() { super({ id: 'workbench.notebook.layout.webview.reset', - title: { - value: localize('workbench.notebook.layout.webview.reset.label', "Reset Notebook Webview"), - original: 'Reset Notebook Webview' - }, + title: localize2('workbench.notebook.layout.webview.reset.label', "Reset Notebook Webview"), f1: false, category: NOTEBOOK_ACTIONS_CATEGORY }); diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl.ts index 597bcfab6fd..3e3446349b8 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { LinkedMap, Touch } from 'vs/base/common/map'; -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -138,10 +138,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.clearNotebookKernelsMRUCache', - title: { - value: localize('workbench.notebook.clearNotebookKernelsMRUCache', "Clear Notebook Kernels MRU Cache"), - original: 'Clear Notebook Kernels MRU Cache' - }, + title: localize2('workbench.notebook.clearNotebookKernelsMRUCache', "Clear Notebook Kernels MRU Cache"), category: Categories.Developer, f1: true }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts index cf1d6488b0f..a353217d214 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts @@ -7,7 +7,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -29,10 +29,7 @@ registerAction2(class extends NotebookCellAction { super( { id: 'notebook.cell.chat.accept', - title: { - value: localize('notebook.cell.chat.accept', "Make Request"), - original: 'Make Request' - }, + title: localize2('notebook.cell.chat.accept', "Make Request"), icon: Codicon.send, keybinding: { when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED), @@ -245,10 +242,7 @@ registerAction2(class extends NotebookCellAction { super( { id: 'notebook.cell.chat.close', - title: { - value: localize('notebook.cell.chat.close', "Close Chat"), - original: 'Close Chat' - }, + title: localize2('notebook.cell.chat.close', "Close Chat"), icon: Codicon.close, menu: { id: MENU_CELL_CHAT_WIDGET, @@ -273,10 +267,10 @@ registerAction2(class extends NotebookAction { super( { id: 'notebook.cell.chat.acceptChanges', - title: { value: localize('apply1', 'Accept Changes'), original: 'Accept Changes' }, + title: localize2('apply1', "Accept Changes"), shortTitle: localize('apply2', 'Accept'), icon: Codicon.check, - tooltip: localize('apply1', 'Accept Changes'), + tooltip: localize('apply3', 'Accept Changes'), keybinding: { when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED), weight: KeybindingWeight.EditorContrib + 10, diff --git a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts index fdbef87f153..6530ddb0781 100644 --- a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts +++ b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts @@ -104,7 +104,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: KEYBOARD_LAYOUT_OPEN_PICKER, - title: { value: nls.localize('keyboard.chooseLayout', "Change Keyboard Layout"), original: 'Change Keyboard Layout' }, + title: nls.localize2('keyboard.chooseLayout', "Change Keyboard Layout"), f1: true }); } diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 4531a26a590..44ef443bd84 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -118,10 +118,10 @@ class SettingsEditor2InputSerializer implements IEditorSerializer { Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(KeybindingsEditorInput.ID, KeybindingsEditorInputSerializer); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(SettingsEditor2Input.ID, SettingsEditor2InputSerializer); -const OPEN_USER_SETTINGS_UI_TITLE = { value: nls.localize('openSettings2', "Open Settings (UI)"), original: 'Open Settings (UI)' }; -const OPEN_USER_SETTINGS_JSON_TITLE = { value: nls.localize('openUserSettingsJson', "Open User Settings (JSON)"), original: 'Open User Settings (JSON)' }; -const OPEN_APPLICATION_SETTINGS_JSON_TITLE = { value: nls.localize('openApplicationSettingsJson', "Open Application Settings (JSON)"), original: 'Open Application Settings (JSON)' }; -const category = { value: nls.localize('preferences', "Preferences"), original: 'Preferences' }; +const OPEN_USER_SETTINGS_UI_TITLE = nls.localize2('openSettings2', "Open Settings (UI)"); +const OPEN_USER_SETTINGS_JSON_TITLE = nls.localize2('openUserSettingsJson', "Open User Settings (JSON)"); +const OPEN_APPLICATION_SETTINGS_JSON_TITLE = nls.localize2('openApplicationSettingsJson', "Open Application Settings (JSON)"); +const category = nls.localize2('preferences', "Preferences"); interface IOpenSettingsActionOptions { openToSide?: boolean; @@ -222,7 +222,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: 'workbench.action.openSettings2', - title: { value: nls.localize('openSettings2', "Open Settings (UI)"), original: 'Open Settings (UI)' }, + title: nls.localize2('openSettings2', "Open Settings (UI)"), category, f1: true, }); @@ -272,7 +272,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: 'workbench.action.openGlobalSettings', - title: { value: nls.localize('openGlobalSettings', "Open User Settings"), original: 'Open User Settings' }, + title: nls.localize2('openGlobalSettings', "Open User Settings"), category, f1: true, }); @@ -286,7 +286,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: 'workbench.action.openRawDefaultSettings', - title: { value: nls.localize('openRawDefaultSettings', "Open Default Settings (JSON)"), original: 'Open Default Settings (JSON)' }, + title: nls.localize2('openRawDefaultSettings', "Open Default Settings (JSON)"), category, f1: true, }); @@ -313,7 +313,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: 'workbench.action.openWorkspaceSettings', - title: { value: nls.localize('openWorkspaceSettings', "Open Workspace Settings"), original: 'Open Workspace Settings' }, + title: nls.localize2('openWorkspaceSettings', "Open Workspace Settings"), category, menu: { id: MenuId.CommandPalette, @@ -332,7 +332,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: 'workbench.action.openAccessibilitySettings', - title: { value: nls.localize('openAccessibilitySettings', "Open Accessibility Settings"), original: 'Open Accessibility Settings' }, + title: nls.localize2('openAccessibilitySettings', "Open Accessibility Settings"), category, menu: { id: MenuId.CommandPalette, @@ -348,7 +348,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: 'workbench.action.openWorkspaceSettingsFile', - title: { value: nls.localize('openWorkspaceSettingsFile', "Open Workspace Settings (JSON)"), original: 'Open Workspace Settings (JSON)' }, + title: nls.localize2('openWorkspaceSettingsFile', "Open Workspace Settings (JSON)"), category, menu: { id: MenuId.CommandPalette, @@ -365,7 +365,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: 'workbench.action.openFolderSettings', - title: { value: nls.localize('openFolderSettings', "Open Folder Settings"), original: 'Open Folder Settings' }, + title: nls.localize2('openFolderSettings', "Open Folder Settings"), category, menu: { id: MenuId.CommandPalette, @@ -387,7 +387,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: 'workbench.action.openFolderSettingsFile', - title: { value: nls.localize('openFolderSettingsFile', "Open Folder Settings (JSON)"), original: 'Open Folder Settings (JSON)' }, + title: nls.localize2('openFolderSettingsFile', "Open Folder Settings (JSON)"), category, menu: { id: MenuId.CommandPalette, @@ -409,7 +409,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: '_workbench.action.openFolderSettings', - title: { value: nls.localize('openFolderSettings', "Open Folder Settings"), original: 'Open Folder Settings' }, + title: nls.localize2('openFolderSettings', "Open Folder Settings"), category, menu: { id: MenuId.ExplorerContext, @@ -449,7 +449,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: SETTINGS_EDITOR_COMMAND_FILTER_UNTRUSTED, - title: { value: nls.localize('filterUntrusted', "Show untrusted workspace settings"), original: 'Show untrusted workspace settings' }, + title: nls.localize2('filterUntrusted', "Show untrusted workspace settings"), }); } run(accessor: ServicesAccessor) { @@ -545,7 +545,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon }, category, f1: true, - title: { value: nls.localize('settings.focusSearch', "Focus Settings Search"), original: 'Focus Settings Search' } + title: nls.localize2('settings.focusSearch', "Focus Settings Search") }); } @@ -564,7 +564,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon }, category, f1: true, - title: { value: nls.localize('settings.clearResults', "Clear Settings Search Results"), original: 'Clear Settings Search Results' } + title: nls.localize2('settings.clearResults', "Clear Settings Search Results") }); } @@ -649,7 +649,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon when: CONTEXT_SETTINGS_ROW_FOCUS }], category, - title: { value: nls.localize('settings.focusSettingsTOC', "Focus Settings Table of Contents"), original: 'Focus Settings Table of Contents' } + title: nls.localize2('settings.focusSettingsTOC', "Focus Settings Table of Contents") }); } @@ -701,7 +701,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon }, f1: true, category, - title: { value: nls.localize('settings.showContextMenu', "Show Setting Context Menu"), original: 'Show Setting Context Menu' } + title: nls.localize2('settings.showContextMenu', "Show Setting Context Menu") }); } @@ -725,7 +725,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon }, f1: true, category, - title: { value: nls.localize('settings.focusLevelUp', "Move Focus Up One Level"), original: 'Move Focus Up One Level' } + title: nls.localize2('settings.focusLevelUp', "Move Focus Up One Level") }); } @@ -748,13 +748,13 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon private registerKeybindingsActions() { const that = this; - const category = { value: nls.localize('preferences', "Preferences"), original: 'Preferences' }; + const category = nls.localize2('preferences', "Preferences"); const id = 'workbench.action.openGlobalKeybindings'; this._register(registerAction2(class extends Action2 { constructor() { super({ id, - title: { value: nls.localize('openGlobalKeybindings', "Open Keyboard Shortcuts"), original: 'Open Keyboard Shortcuts' }, + title: nls.localize2('openGlobalKeybindings', "Open Keyboard Shortcuts"), shortTitle: nls.localize('keyboardShortcuts', "Keyboard Shortcuts"), category, icon: preferencesOpenSettingsIcon, @@ -796,7 +796,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: 'workbench.action.openDefaultKeybindingsFile', - title: { value: nls.localize('openDefaultKeybindingsFile', "Open Default Keyboard Shortcuts (JSON)"), original: 'Open Default Keyboard Shortcuts (JSON)' }, + title: nls.localize2('openDefaultKeybindingsFile', "Open Default Keyboard Shortcuts (JSON)"), category, menu: { id: MenuId.CommandPalette } }); @@ -809,7 +809,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: 'workbench.action.openGlobalKeybindingsFile', - title: { value: nls.localize('openGlobalKeybindingsFile', "Open Keyboard Shortcuts (JSON)"), original: 'Open Keyboard Shortcuts (JSON)' }, + title: nls.localize2('openGlobalKeybindingsFile', "Open Keyboard Shortcuts (JSON)"), category, icon: preferencesOpenSettingsIcon, menu: [ @@ -1145,7 +1145,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon const when = ResourceContextKey.Resource.isEqualTo(that.userDataProfileService.currentProfile.keybindingsResource.toString()); super({ id: 'editor.action.defineKeybinding', - title: { value: nls.localize('defineKeybinding.start', "Define Keybinding"), original: 'Define Keybinding' }, + title: nls.localize2('defineKeybinding.start', "Define Keybinding"), f1: true, precondition: when, keybinding: { @@ -1266,7 +1266,7 @@ class SettingsEditorTitleContribution extends Disposable implements IWorkbenchCo constructor() { super({ id: SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, - title: { value: nls.localize('openSettingsJson', "Open Settings (JSON)"), original: 'Open Settings (JSON)' }, + title: nls.localize2('openSettingsJson', "Open Settings (JSON)"), icon: preferencesOpenSettingsIcon, menu: [{ id: MenuId.EditorTitle, diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts index a5fd996c40a..341b0b9e673 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts @@ -21,7 +21,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; export class ConfigureLanguageBasedSettingsAction extends Action { static readonly ID = 'workbench.action.configureLanguageBasedSettings'; - static readonly LABEL = { value: nls.localize('configureLanguageBasedSettings', "Configure Language Specific Settings..."), original: 'Configure Language Specific Settings...' }; + static readonly LABEL = nls.localize2('configureLanguageBasedSettings', "Configure Language Specific Settings..."); constructor( id: string, diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index ffa5cdd0204..3aedafa13aa 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -628,7 +628,7 @@ class RemoteViewPaneContainer extends FilterViewPaneContainer implements IViewMo Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( { id: VIEWLET_ID, - title: { value: nls.localize('remote.explorer', "Remote Explorer"), original: 'Remote Explorer' }, + title: nls.localize2('remote.explorer', "Remote Explorer"), ctorDescriptor: new SyncDescriptor(RemoteViewPaneContainer), hideIfEmpty: true, viewOrderDelegate: { diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index dbec2f47685..880ccebd8e0 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -65,7 +65,7 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu private async getViewContainer(): Promise { return Registry.as(Extensions.ViewContainersRegistry).registerViewContainer({ id: TUNNEL_VIEW_CONTAINER_ID, - title: { value: nls.localize('ports', "Ports"), original: 'Ports' }, + title: nls.localize2('ports', "Ports"), icon: portsViewIcon, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [TUNNEL_VIEW_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true }]), storageId: TUNNEL_VIEW_CONTAINER_ID, diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index 582978e9052..ee80c41f05b 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -156,7 +156,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr } private registerActions(): void { - const category = { value: nls.localize('remote.category', "Remote"), original: 'Remote' }; + const category = nls.localize2('remote.category', "Remote"); // Show Remote Menu const that = this; @@ -165,7 +165,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr super({ id: RemoteStatusIndicator.REMOTE_ACTIONS_COMMAND_ID, category, - title: { value: nls.localize('remote.showMenu', "Show Remote Menu"), original: 'Show Remote Menu' }, + title: nls.localize2('remote.showMenu', "Show Remote Menu"), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -183,7 +183,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr super({ id: RemoteStatusIndicator.CLOSE_REMOTE_COMMAND_ID, category, - title: { value: nls.localize('remote.close', "Close Remote Connection"), original: 'Close Remote Connection' }, + title: nls.localize2('remote.close', "Close Remote Connection"), f1: true, precondition: ContextKeyExpr.or(RemoteNameContext, VirtualWorkspaceContext) }); @@ -208,7 +208,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr super({ id: RemoteStatusIndicator.INSTALL_REMOTE_EXTENSIONS_ID, category, - title: { value: nls.localize('remote.install', "Install Remote Development Extensions"), original: 'Install Remote Development Extensions' }, + title: nls.localize2('remote.install', "Install Remote Development Extensions"), f1: true }); } diff --git a/src/vs/workbench/contrib/remote/browser/remoteStartEntry.ts b/src/vs/workbench/contrib/remote/browser/remoteStartEntry.ts index 8c38e5e7245..e4e2f80af4b 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteStartEntry.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteStartEntry.ts @@ -44,7 +44,7 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi } private registerActions(): void { - const category = { value: nls.localize('remote.category', "Remote"), original: 'Remote' }; + const category = nls.localize2('remote.category', "Remote"); // Show Remote Start Action const startEntry = this; @@ -53,7 +53,7 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi super({ id: RemoteStartEntry.REMOTE_WEB_START_ENTRY_ACTIONS_COMMAND_ID, category, - title: { value: nls.localize('remote.showWebStartEntryActions', "Show Remote Start Entry for web"), original: 'Show Remote Start Entry for web' }, + title: nls.localize2('remote.showWebStartEntryActions', "Show Remote Start Entry for web"), f1: false }); } diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index b96a7ee7e78..0753f9429c5 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -1171,7 +1171,7 @@ const alreadyForwarded: string = nls.localize('remote.tunnelView.alreadyForwarde export namespace ForwardPortAction { export const INLINE_ID = 'remote.tunnel.forwardInline'; export const COMMANDPALETTE_ID = 'remote.tunnel.forwardCommandPalette'; - export const LABEL: ILocalizedString = { value: nls.localize('remote.tunnel.forward', "Forward a Port"), original: 'Forward a Port' }; + export const LABEL: ILocalizedString = nls.localize2('remote.tunnel.forward', "Forward a Port"); export const TREEITEM_LABEL = nls.localize('remote.tunnel.forwardItem', "Forward Port"); const forwardPrompt = nls.localize('remote.tunnel.forwardPrompt', "Port number or address (eg. 3000 or 10.10.10.10:2000)."); @@ -1266,7 +1266,7 @@ function makeTunnelPicks(tunnels: Tunnel[], remoteExplorerService: IRemoteExplor namespace ClosePortAction { export const INLINE_ID = 'remote.tunnel.closeInline'; export const COMMANDPALETTE_ID = 'remote.tunnel.closeCommandPalette'; - export const LABEL: ILocalizedString = { value: nls.localize('remote.tunnel.close', "Stop Forwarding Port"), original: 'Stop Forwarding Port' }; + export const LABEL: ILocalizedString = nls.localize2('remote.tunnel.close', "Stop Forwarding Port"); export function inlineHandler(): ICommandHandler { return async (accessor, arg) => { diff --git a/src/vs/workbench/contrib/search/browser/searchActionsBase.ts b/src/vs/workbench/contrib/search/browser/searchActionsBase.ts index 6fb0525ecf4..20ae11b9dab 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsBase.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsBase.ts @@ -12,7 +12,7 @@ import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import { FileMatch, FolderMatch, Match, RenderableMatch, searchComparer } from 'vs/workbench/contrib/search/browser/searchModel'; import { ISearchConfigurationProperties, VIEW_ID } from 'vs/workbench/services/search/common/search'; -export const category = { value: nls.localize('search', "Search"), original: 'Search' }; +export const category = nls.localize2('search', "Search"); export function isSearchViewFocused(viewsService: IViewsService): boolean { const searchView = getSearchView(viewsService); diff --git a/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts b/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts index 358b66d751e..1d1c64f6ca5 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts @@ -22,10 +22,7 @@ registerAction2(class CopyMatchCommandAction extends Action2 { ) { super({ id: Constants.SearchCommandIds.CopyMatchCommandId, - title: { - value: nls.localize('copyMatchLabel', "Copy"), - original: 'Copy' - }, + title: nls.localize2('copyMatchLabel', "Copy"), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -53,10 +50,7 @@ registerAction2(class CopyPathCommandAction extends Action2 { ) { super({ id: Constants.SearchCommandIds.CopyPathCommandId, - title: { - value: nls.localize('copyPathLabel', "Copy Path"), - original: 'Copy Path' - }, + title: nls.localize2('copyPathLabel', "Copy Path"), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -87,10 +81,7 @@ registerAction2(class CopyAllCommandAction extends Action2 { ) { super({ id: Constants.SearchCommandIds.CopyAllCommandId, - title: { - value: nls.localize('copyAllLabel', "Copy All"), - original: 'Copy All' - }, + title: nls.localize2('copyAllLabel', "Copy All"), category, menu: [{ id: MenuId.SearchContext, diff --git a/src/vs/workbench/contrib/search/browser/searchActionsFind.ts b/src/vs/workbench/contrib/search/browser/searchActionsFind.ts index 97525dd4fff..85307857461 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsFind.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsFind.ts @@ -55,10 +55,7 @@ registerAction2(class RestrictSearchToFolderAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.RestrictSearchToFolderId, - title: { - value: nls.localize('restrictResultsToFolder', "Restrict Search to Folder"), - original: 'Restrict Search to Folder' - }, + title: nls.localize2('restrictResultsToFolder', "Restrict Search to Folder"), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -84,10 +81,7 @@ registerAction2(class ExcludeFolderFromSearchAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.ExcludeFolderFromSearchId, - title: { - value: nls.localize('excludeFolderFromSearch', "Exclude Folder from Search"), - original: 'Exclude Folder from Search' - }, + title: nls.localize2('excludeFolderFromSearch', "Exclude Folder from Search"), category, menu: [ { @@ -110,10 +104,7 @@ registerAction2(class RevealInSideBarForSearchResultsAction extends Action2 { ) { super({ id: Constants.SearchCommandIds.RevealInSideBarForSearchResults, - title: { - value: nls.localize('revealInSideBar', "Reveal in Explorer View"), - original: 'Reveal in Explorer View' - }, + title: nls.localize2('revealInSideBar', "Reveal in Explorer View"), category, menu: [{ id: MenuId.SearchContext, @@ -222,10 +213,7 @@ registerAction2(class FindInFolderAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.FindInFolderId, - title: { - value: nls.localize('findInFolder', "Find in Folder..."), - original: 'Find in Folder...' - }, + title: nls.localize2('findInFolder', "Find in Folder..."), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -252,10 +240,7 @@ registerAction2(class FindInWorkspaceAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.FindInWorkspaceId, - title: { - value: nls.localize('findInWorkspace', "Find in Workspace..."), - original: 'Find in Workspace...' - }, + title: nls.localize2('findInWorkspace', "Find in Workspace..."), category, menu: [ { diff --git a/src/vs/workbench/contrib/search/browser/searchActionsNav.ts b/src/vs/workbench/contrib/search/browser/searchActionsNav.ts index 4712349398b..3ae42ec2945 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsNav.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsNav.ts @@ -31,10 +31,7 @@ registerAction2(class ToggleQueryDetailsAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.ToggleQueryDetailsActionId, - title: { - value: nls.localize('ToggleQueryDetailsAction.label', "Toggle Query Details"), - original: 'Toggle Query Details' - }, + title: nls.localize2('ToggleQueryDetailsAction.label', "Toggle Query Details"), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -58,10 +55,7 @@ registerAction2(class CloseReplaceAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.CloseReplaceWidgetActionId, - title: { - value: nls.localize('CloseReplaceWidget.label', "Close Replace Widget"), - original: 'Close Replace Widget' - }, + title: nls.localize2('CloseReplaceWidget.label', "Close Replace Widget"), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -88,10 +82,7 @@ registerAction2(class ToggleCaseSensitiveCommandAction extends Action2 { super({ id: Constants.SearchCommandIds.ToggleCaseSensitiveCommandId, - title: { - value: nls.localize('ToggleCaseSensitiveCommandId.label', "Toggle Case Sensitive"), - original: 'Toggle Case Sensitive' - }, + title: nls.localize2('ToggleCaseSensitiveCommandId.label', "Toggle Case Sensitive"), category, keybinding: Object.assign({ weight: KeybindingWeight.WorkbenchContrib, @@ -111,10 +102,7 @@ registerAction2(class ToggleWholeWordCommandAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.ToggleWholeWordCommandId, - title: { - value: nls.localize('ToggleWholeWordCommandId.label', 'Toggle Whole Word'), - original: 'Toggle Whole Word' - }, + title: nls.localize2('ToggleWholeWordCommandId.label', "Toggle Whole Word"), keybinding: Object.assign({ weight: KeybindingWeight.WorkbenchContrib, when: Constants.SearchContext.SearchViewFocusedKey, @@ -132,10 +120,7 @@ registerAction2(class ToggleRegexCommandAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.ToggleRegexCommandId, - title: { - value: nls.localize('ToggleRegexCommandId.label', 'Toggle Regex'), - original: 'Toggle Regex' - }, + title: nls.localize2('ToggleRegexCommandId.label', "Toggle Regex"), keybinding: Object.assign({ weight: KeybindingWeight.WorkbenchContrib, when: Constants.SearchContext.SearchViewFocusedKey, @@ -153,10 +138,7 @@ registerAction2(class TogglePreserveCaseAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.TogglePreserveCaseId, - title: { - value: nls.localize('TogglePreserveCaseId.label', 'Toggle Preserve Case'), - original: 'Toggle Preserve Case' - }, + title: nls.localize2('TogglePreserveCaseId.label', "Toggle Preserve Case"), keybinding: Object.assign({ weight: KeybindingWeight.WorkbenchContrib, when: Constants.SearchContext.SearchViewFocusedKey, @@ -176,10 +158,7 @@ registerAction2(class OpenMatchAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.OpenMatch, - title: { - value: nls.localize('OpenMatch.label', "Open Match"), - original: 'Open Match' - }, + title: nls.localize2('OpenMatch.label', "Open Match"), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -212,10 +191,7 @@ registerAction2(class OpenMatchToSideAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.OpenMatchToSide, - title: { - value: nls.localize('OpenMatchToSide.label', "Open Match To Side"), - original: 'Open Match To Side' - }, + title: nls.localize2('OpenMatchToSide.label', "Open Match To Side"), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -240,10 +216,7 @@ registerAction2(class AddCursorsAtSearchResultsAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.AddCursorsAtSearchResults, - title: { - value: nls.localize('AddCursorsAtSearchResults.label', 'Add Cursors at Search Results'), - original: 'Add Cursors at Search Results' - }, + title: nls.localize2('AddCursorsAtSearchResults.label', "Add Cursors at Search Results"), keybinding: { weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and(Constants.SearchContext.SearchViewVisibleKey, Constants.SearchContext.FileMatchOrMatchFocusKey), @@ -268,10 +241,7 @@ registerAction2(class FocusNextInputAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.FocusNextInputActionId, - title: { - value: nls.localize('FocusNextInputAction.label', "Focus Next Input"), - original: 'Focus Next Input' - }, + title: nls.localize2('FocusNextInputAction.label', "Focus Next Input"), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -300,10 +270,7 @@ registerAction2(class FocusPreviousInputAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.FocusPreviousInputActionId, - title: { - value: nls.localize('FocusPreviousInputAction.label', "Focus Previous Input"), - original: 'Focus Previous Input' - }, + title: nls.localize2('FocusPreviousInputAction.label', "Focus Previous Input"), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -332,10 +299,7 @@ registerAction2(class FocusSearchFromResultsAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.FocusSearchFromResults, - title: { - value: nls.localize('FocusSearchFromResults.label', "Focus Search From Results"), - original: 'Focus Search From Results' - }, + title: nls.localize2('FocusSearchFromResults.label', "Focus Search From Results"), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -357,10 +321,7 @@ registerAction2(class ToggleSearchOnTypeAction extends Action2 { ) { super({ id: Constants.SearchCommandIds.ToggleSearchOnTypeActionId, - title: { - value: nls.localize('toggleTabs', 'Toggle Search on Type'), - original: 'Toggle Search on Type' - }, + title: nls.localize2('toggleTabs', "Toggle Search on Type"), category, }); @@ -379,10 +340,7 @@ registerAction2(class FocusSearchListCommandAction extends Action2 { ) { super({ id: Constants.SearchCommandIds.FocusSearchListCommandID, - title: { - value: nls.localize('focusSearchListCommandLabel', "Focus List"), - original: 'Focus List' - }, + title: nls.localize2('focusSearchListCommandLabel', "Focus List"), category, f1: true }); @@ -397,10 +355,7 @@ registerAction2(class FocusNextSearchResultAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.FocusNextSearchResultActionId, - title: { - value: nls.localize('FocusNextSearchResult.label', 'Focus Next Search Result'), - original: 'Focus Next Search Result' - }, + title: nls.localize2('FocusNextSearchResult.label', "Focus Next Search Result"), keybinding: [{ primary: KeyCode.F4, weight: KeybindingWeight.WorkbenchContrib, @@ -420,10 +375,7 @@ registerAction2(class FocusPreviousSearchResultAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.FocusPreviousSearchResultActionId, - title: { - value: nls.localize('FocusPreviousSearchResult.label', 'Focus Previous Search Result'), - original: 'Focus Previous Search Result' - }, + title: nls.localize2('FocusPreviousSearchResult.label', "Focus Previous Search Result"), keybinding: [{ primary: KeyMod.Shift | KeyCode.F4, weight: KeybindingWeight.WorkbenchContrib, @@ -443,10 +395,7 @@ registerAction2(class ReplaceInFilesAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.ReplaceInFilesActionId, - title: { - value: nls.localize('replaceInFiles', 'Replace in Files'), - original: 'Replace in Files' - }, + title: nls.localize2('replaceInFiles', "Replace in Files"), keybinding: [{ primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyH, weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts b/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts index 23d4e39a96d..fff773645a5 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts @@ -55,10 +55,7 @@ registerAction2(class RemoveAction extends Action2 { ) { super({ id: Constants.SearchCommandIds.RemoveActionId, - title: { - value: nls.localize('RemoveAction.label', "Dismiss"), - original: 'Dismiss' - }, + title: nls.localize2('RemoveAction.label', "Dismiss"), category, icon: searchRemoveIcon, keybinding: { @@ -149,10 +146,7 @@ registerAction2(class ReplaceAction extends Action2 { ) { super({ id: Constants.SearchCommandIds.ReplaceActionId, - title: { - value: nls.localize('match.replace.label', "Replace"), - original: 'Replace' - }, + title: nls.localize2('match.replace.label', "Replace"), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -188,10 +182,7 @@ registerAction2(class ReplaceAllAction extends Action2 { ) { super({ id: Constants.SearchCommandIds.ReplaceAllInFileActionId, - title: { - value: nls.localize('file.replaceAll.label', "Replace All"), - original: 'Replace All' - }, + title: nls.localize2('file.replaceAll.label', "Replace All"), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -227,10 +218,7 @@ registerAction2(class ReplaceAllInFolderAction extends Action2 { ) { super({ id: Constants.SearchCommandIds.ReplaceAllInFolderActionId, - title: { - value: nls.localize('file.replaceAll.label', "Replace All"), - original: 'Replace All' - }, + title: nls.localize2('file.replaceAll.label', "Replace All"), category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts b/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts index d2bf9f778af..3c9c912207d 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts @@ -21,10 +21,7 @@ registerAction2(class TextSearchQuickAccessAction extends Action2 { ) { super({ id: Constants.SearchCommandIds.QuickTextSearchActionId, - title: { - value: nls.localize('quickTextSearch', "Quick Search (Experimental)"), - original: 'Quick Search (Experimental)' - }, + title: nls.localize2('quickTextSearch', "Quick Search (Experimental)"), category, f1: true }); diff --git a/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts b/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts index bb07efc51a1..e7dbb128b7b 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts @@ -27,10 +27,7 @@ registerAction2(class ClearSearchHistoryCommandAction extends Action2 { ) { super({ id: Constants.SearchCommandIds.ClearSearchHistoryCommandId, - title: { - value: nls.localize('clearSearchHistoryLabel', "Clear Search History"), - original: 'Clear Search History' - }, + title: nls.localize2('clearSearchHistoryLabel', "Clear Search History"), category, f1: true }); @@ -46,10 +43,7 @@ registerAction2(class CancelSearchAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.CancelSearchActionId, - title: { - value: nls.localize('CancelSearchAction.label', "Cancel Search"), - original: 'Cancel Search' - }, + title: nls.localize2('CancelSearchAction.label', "Cancel Search"), icon: searchStopIcon, category, f1: true, @@ -76,10 +70,7 @@ registerAction2(class RefreshAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.RefreshSearchResultsActionId, - title: { - value: nls.localize('RefreshAction.label', "Refresh"), - original: 'Refresh' - }, + title: nls.localize2('RefreshAction.label', "Refresh"), icon: searchRefreshIcon, precondition: Constants.SearchContext.ViewHasSearchPatternKey, category, @@ -101,10 +92,7 @@ registerAction2(class CollapseDeepestExpandedLevelAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.CollapseSearchResultsActionId, - title: { - value: nls.localize('CollapseDeepestExpandedLevelAction.label', "Collapse All"), - original: 'Collapse All' - }, + title: nls.localize2('CollapseDeepestExpandedLevelAction.label', "Collapse All"), category, icon: searchCollapseAllIcon, f1: true, @@ -126,10 +114,7 @@ registerAction2(class ExpandAllAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.ExpandSearchResultsActionId, - title: { - value: nls.localize('ExpandAllAction.label', "Expand All"), - original: 'Expand All' - }, + title: nls.localize2('ExpandAllAction.label', "Expand All"), category, icon: searchExpandAllIcon, f1: true, @@ -151,10 +136,7 @@ registerAction2(class ClearSearchResultsAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.ClearSearchResultsActionId, - title: { - value: nls.localize('ClearSearchResultsAction.label', "Clear Search Results"), - original: 'Clear Search Results' - }, + title: nls.localize2('ClearSearchResultsAction.label', "Clear Search Results"), category, icon: searchClearIcon, f1: true, @@ -177,10 +159,7 @@ registerAction2(class ViewAsTreeAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.ViewAsTreeActionId, - title: { - value: nls.localize('ViewAsTreeAction.label', "View as Tree"), - original: 'View as Tree' - }, + title: nls.localize2('ViewAsTreeAction.label', "View as Tree"), category, icon: searchShowAsList, f1: true, @@ -205,10 +184,7 @@ registerAction2(class ViewAsListAction extends Action2 { constructor() { super({ id: Constants.SearchCommandIds.ViewAsListActionId, - title: { - value: nls.localize('ViewAsListAction.label', "View as List"), - original: 'View as List' - }, + title: nls.localize2('ViewAsListAction.label', "View as List"), category, icon: searchShowAsTree, f1: true, diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index d618cdb57e1..44ac96842ef 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -495,7 +495,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: IncreaseSearchEditorContextLinesCommandId, - title: { original: 'Increase Context Lines', value: localize('searchEditor.action.increaseSearchEditorContextLines', "Increase Context Lines") }, + title: localize2('searchEditor.action.increaseSearchEditorContextLines', "Increase Context Lines"), category, f1: true, precondition: SearchEditorConstants.InSearchEditor, @@ -512,7 +512,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: DecreaseSearchEditorContextLinesCommandId, - title: { original: 'Decrease Context Lines', value: localize('searchEditor.action.decreaseSearchEditorContextLines', "Decrease Context Lines") }, + title: localize2('searchEditor.action.decreaseSearchEditorContextLines', "Decrease Context Lines"), category, f1: true, precondition: SearchEditorConstants.InSearchEditor, @@ -529,7 +529,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: SelectAllSearchEditorMatchesCommandId, - title: { original: 'Select All Matches', value: localize('searchEditor.action.selectAllSearchEditorMatches', "Select All Matches") }, + title: localize2('searchEditor.action.selectAllSearchEditorMatches', "Select All Matches"), category, f1: true, precondition: SearchEditorConstants.InSearchEditor, diff --git a/src/vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions.ts b/src/vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions.ts index cf919a8c50b..c41f912b673 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions.ts @@ -4,14 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { Action2, IAction2Options } from 'vs/platform/actions/common/actions'; const defaultOptions = { - category: { - value: localize('snippets', 'Snippets'), - original: 'Snippets' - }, + category: localize2('snippets', "Snippets"), } as const; export abstract class SnippetsAction extends Action2 { diff --git a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts index 396482865cc..f80793442e3 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts @@ -225,10 +225,7 @@ export class ConfigureSnippetsAction extends SnippetsAction { constructor() { super({ id: 'workbench.action.openSnippets', - title: { - value: nls.localize('openSnippet.label', "Configure User Snippets"), - original: 'Configure User Snippets' - }, + title: nls.localize2('openSnippet.label', "Configure User Snippets"), shortTitle: { value: nls.localize('userSnippets', "User Snippets"), mnemonicTitle: nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets"), diff --git a/src/vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets.ts index 600ff83d1ed..90b3e92d04c 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets.ts @@ -8,7 +8,7 @@ import { compare } from 'vs/base/common/strings'; import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { SnippetsAction } from 'vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions'; @@ -23,10 +23,7 @@ export class ApplyFileSnippetAction extends SnippetsAction { constructor() { super({ id: ApplyFileSnippetAction.Id, - title: { - value: localize('label', 'Fill File with Snippet'), - original: 'Fill File with Snippet' - }, + title: localize2('label', "Fill File with Snippet"), f1: true, }); } diff --git a/src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts b/src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts index 39fe40c4e2f..b0eb865ee92 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts @@ -49,10 +49,7 @@ export class InsertSnippetAction extends SnippetEditorAction { constructor() { super({ id: 'editor.action.insertSnippet', - title: { - value: nls.localize('snippet.suggestions.label', "Insert Snippet"), - original: 'Insert Snippet' - }, + title: nls.localize2('snippet.suggestions.label', "Insert Snippet"), f1: true, precondition: EditorContextKeys.writable, metadata: { diff --git a/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts index 03f55ac3597..3b4a46f65df 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts @@ -8,7 +8,6 @@ import { Position } from 'vs/editor/common/core/position'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITextModel } from 'vs/editor/common/model'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; -import { localize } from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -16,6 +15,7 @@ import { SnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/comma import { pickSnippet } from 'vs/workbench/contrib/snippets/browser/snippetPicker'; import { Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { ISnippetsService } from '../snippets'; +import { localize2 } from 'vs/nls'; export async function getSurroundableSnippets(snippetsService: ISnippetsService, model: ITextModel, position: Position, includeDisabledSnippets: boolean): Promise { @@ -31,10 +31,7 @@ export class SurroundWithSnippetEditorAction extends SnippetEditorAction { static readonly options = { id: 'editor.action.surroundWithSnippet', - title: { - value: localize('label', 'More...'), - original: 'More...' - } + title: localize2('label', "More...") }; constructor() { diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 11d92dc73f1..d92f58923b0 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -237,7 +237,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: 'workbench.action.tasks.openWorkspaceFileTasks', - title: { value: nls.localize('workbench.action.tasks.openWorkspaceFileTasks', "Open Workspace Tasks"), original: 'Open Workspace Tasks' }, + title: nls.localize2('workbench.action.tasks.openWorkspaceFileTasks', "Open Workspace Tasks"), category: TASKS_CATEGORY }, when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), TaskExecutionSupportedContext) @@ -254,7 +254,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: 'workbench.action.tasks.showLog', - title: { value: nls.localize('ShowLogAction.label', "Show Task Log"), original: 'Show Task Log' }, + title: nls.localize2('ShowLogAction.label', "Show Task Log"), category: TASKS_CATEGORY }, when: TaskExecutionSupportedContext @@ -262,14 +262,14 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: 'workbench.action.tasks.runTask', - title: { value: nls.localize('RunTaskAction.label', "Run Task"), original: 'Run Task' }, + title: nls.localize2('RunTaskAction.label', "Run Task"), category: TASKS_CATEGORY } }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: 'workbench.action.tasks.reRunTask', - title: { value: nls.localize('ReRunTaskAction.label', "Rerun Last Task"), original: 'Rerun Last Task' }, + title: nls.localize2('ReRunTaskAction.label', "Rerun Last Task"), category: TASKS_CATEGORY }, when: TaskExecutionSupportedContext @@ -277,7 +277,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: 'workbench.action.tasks.restartTask', - title: { value: nls.localize('RestartTaskAction.label', "Restart Running Task"), original: 'Restart Running Task' }, + title: nls.localize2('RestartTaskAction.label', "Restart Running Task"), category: TASKS_CATEGORY }, when: TaskExecutionSupportedContext @@ -285,7 +285,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: 'workbench.action.tasks.showTasks', - title: { value: nls.localize('ShowTasksAction.label', "Show Running Tasks"), original: 'Show Running Tasks' }, + title: nls.localize2('ShowTasksAction.label', "Show Running Tasks"), category: TASKS_CATEGORY }, when: TaskExecutionSupportedContext @@ -293,7 +293,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: 'workbench.action.tasks.terminate', - title: { value: nls.localize('TerminateAction.label', "Terminate Task"), original: 'Terminate Task' }, + title: nls.localize2('TerminateAction.label', "Terminate Task"), category: TASKS_CATEGORY }, when: TaskExecutionSupportedContext @@ -301,7 +301,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: 'workbench.action.tasks.build', - title: { value: nls.localize('BuildAction.label', "Run Build Task"), original: 'Run Build Task' }, + title: nls.localize2('BuildAction.label', "Run Build Task"), category: TASKS_CATEGORY }, when: TaskExecutionSupportedContext @@ -309,7 +309,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: 'workbench.action.tasks.test', - title: { value: nls.localize('TestAction.label', "Run Test Task"), original: 'Run Test Task' }, + title: nls.localize2('TestAction.label', "Run Test Task"), category: TASKS_CATEGORY }, when: TaskExecutionSupportedContext @@ -317,10 +317,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: 'workbench.action.tasks.configureDefaultBuildTask', - title: { - value: nls.localize('ConfigureDefaultBuildTask.label', "Configure Default Build Task"), - original: 'Configure Default Build Task' - }, + title: nls.localize2('ConfigureDefaultBuildTask.label', "Configure Default Build Task"), category: TASKS_CATEGORY }, when: TaskExecutionSupportedContext @@ -328,10 +325,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: 'workbench.action.tasks.configureDefaultTestTask', - title: { - value: nls.localize('ConfigureDefaultTestTask.label', "Configure Default Test Task"), - original: 'Configure Default Test Task' - }, + title: nls.localize2('ConfigureDefaultTestTask.label', "Configure Default Test Task"), category: TASKS_CATEGORY }, when: TaskExecutionSupportedContext @@ -339,10 +333,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: 'workbench.action.tasks.openUserTasks', - title: { - value: nls.localize('workbench.action.tasks.openUserTasks', "Open User Tasks"), - original: 'Open User Tasks' - }, category: TASKS_CATEGORY + title: nls.localize2('workbench.action.tasks.openUserTasks', "Open User Tasks"), category: TASKS_CATEGORY }, when: TaskExecutionSupportedContext }); diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index b7884faa838..8795bee3f33 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -23,7 +23,7 @@ import { TerminalExitReason } from 'vs/platform/terminal/common/terminal'; export const USER_TASKS_GROUP_KEY = 'settings'; export const TASK_RUNNING_STATE = new RawContextKey('taskRunning', false, nls.localize('tasks.taskRunningContext', "Whether a task is currently running.")); -export const TASKS_CATEGORY = { value: nls.localize('tasksCategory', "Tasks"), original: 'Tasks' }; +export const TASKS_CATEGORY = nls.localize2('tasksCategory', "Tasks"); export enum ShellQuoting { /** diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 9691ee4fc7b..8decd44525f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -457,7 +457,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.CopyLastCommand, - title: { value: localize('workbench.action.terminal.copyLastCommand', 'Copy Last Command'), original: 'Copy Last Command' }, + title: localize2('workbench.action.terminal.copyLastCommand', "Copy Last Command"), precondition: sharedWhenClause.terminalAvailable, run: async (instance, c, accessor) => { const clipboardService = accessor.get(IClipboardService); @@ -475,7 +475,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.CopyLastCommandOutput, - title: { value: localize('workbench.action.terminal.copyLastCommandOutput', 'Copy Last Command Output'), original: 'Copy Last Command Output' }, + title: localize2('workbench.action.terminal.copyLastCommandOutput', "Copy Last Command Output"), precondition: sharedWhenClause.terminalAvailable, run: async (instance, c, accessor) => { const clipboardService = accessor.get(IClipboardService); @@ -496,7 +496,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.CopyLastCommandAndLastCommandOutput, - title: { value: localize('workbench.action.terminal.copyLastCommandAndOutput', 'Copy Last Command and Output'), original: 'Copy Last Command and Output' }, + title: localize2('workbench.action.terminal.copyLastCommandAndOutput', "Copy Last Command and Output"), precondition: sharedWhenClause.terminalAvailable, run: async (instance, c, accessor) => { const clipboardService = accessor.get(IClipboardService); @@ -1654,10 +1654,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.StartVoice, - title: { - value: localize('workbench.action.startTerminalVoice', "Start Terminal Voice"), - original: 'Start Terminal Voice' - }, + title: localize2('workbench.action.startTerminalVoice', "Start Terminal Voice"), precondition: ContextKeyExpr.and(HasSpeechProvider, sharedWhenClause.terminalAvailable), f1: true, run: (activeInstance, c, accessor) => { @@ -1668,10 +1665,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.StopVoice, - title: { - value: localize('workbench.action.stopTerminalVoice', "Stop Terminal Voice"), - original: 'Stop Terminal Voice' - }, + title: localize2('workbench.action.stopTerminalVoice', "Stop Terminal Voice"), precondition: ContextKeyExpr.and(HasSpeechProvider, sharedWhenClause.terminalAvailable), f1: true, run: (activeInstance, c, accessor) => { diff --git a/src/vs/workbench/contrib/terminal/common/terminalStrings.ts b/src/vs/workbench/contrib/terminal/common/terminalStrings.ts index a1003b54822..2bf1534e685 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalStrings.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalStrings.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; /** * An object holding strings shared by multiple parts of the terminal @@ -16,86 +16,32 @@ export const terminalStrings = { previousSessionCategory: localize('previousSessionCategory', 'previous session'), typeTask: localize('task', "Task"), typeLocal: localize('local', "Local"), - actionCategory: { - value: localize('terminalCategory', "Terminal"), - original: 'Terminal' - }, - focus: { - value: localize('workbench.action.terminal.focus', "Focus Terminal"), - original: 'Focus Terminal' - }, - focusAndHideAccessibleBuffer: { - value: localize('workbench.action.terminal.focusAndHideAccessibleBuffer', "Focus Terminal and Hide Accessible Buffer"), - original: 'Focus Terminal and Hide Accessible Buffer' - }, + actionCategory: localize2('terminalCategory', "Terminal"), + focus: localize2('workbench.action.terminal.focus', "Focus Terminal"), + focusAndHideAccessibleBuffer: localize2('workbench.action.terminal.focusAndHideAccessibleBuffer', "Focus Terminal and Hide Accessible Buffer"), kill: { value: localize('killTerminal', "Kill Terminal"), original: 'Kill Terminal', short: localize('killTerminal.short', "Kill"), }, - moveToEditor: { - value: localize('moveToEditor', "Move Terminal into Editor Area"), - original: 'Move Terminal into Editor Area', - }, - moveIntoNewWindow: { - value: localize('moveIntoNewWindow', "Move Terminal into New Window"), - original: 'Move Terminal into New Window', - }, - moveToTerminalPanel: { - value: localize('workbench.action.terminal.moveToTerminalPanel', "Move Terminal into Panel"), - original: 'Move Terminal into Panel' - }, - changeIcon: { - value: localize('workbench.action.terminal.changeIcon', "Change Icon..."), - original: 'Change Icon...' - }, - changeColor: { - value: localize('workbench.action.terminal.changeColor', "Change Color..."), - original: 'Change Color...' - }, + moveToEditor: localize2('moveToEditor', "Move Terminal into Editor Area"), + moveIntoNewWindow: localize2('moveIntoNewWindow', "Move Terminal into New Window"), + moveToTerminalPanel: localize2('workbench.action.terminal.moveToTerminalPanel', "Move Terminal into Panel"), + changeIcon: localize2('workbench.action.terminal.changeIcon', "Change Icon..."), + changeColor: localize2('workbench.action.terminal.changeColor', "Change Color..."), split: { value: localize('splitTerminal', "Split Terminal"), original: 'Split Terminal', short: localize('splitTerminal.short', "Split"), }, - unsplit: { - value: localize('unsplitTerminal', "Unsplit Terminal"), - original: 'Unsplit Terminal' - }, - rename: { - value: localize('workbench.action.terminal.rename', "Rename..."), - original: 'Rename...' - }, - toggleSizeToContentWidth: { - value: localize('workbench.action.terminal.sizeToContentWidthInstance', "Toggle Size to Content Width"), - original: 'Toggle Size to Content Width' - }, - focusHover: { - value: localize('workbench.action.terminal.focusHover', "Focus Hover"), - original: 'Focus Hover' - }, - sendSequence: { - value: localize('workbench.action.terminal.sendSequence', "Send Custom Sequence To Terminal"), - original: 'Send Custom Sequence To Terminal' - }, - newWithCwd: { - value: localize('workbench.action.terminal.newWithCwd', "Create New Terminal Starting in a Custom Working Directory"), - original: 'Create New Terminal Starting in a Custom Working Directory' - }, - renameWithArgs: { - value: localize('workbench.action.terminal.renameWithArg', "Rename the Currently Active Terminal"), - original: 'Rename the Currently Active Terminal' - }, - stickyScroll: { - value: localize('stickyScroll', "Sticky Scroll"), - original: 'Sticky Scroll' - }, - scrollToPreviousCommand: { - value: localize('workbench.action.terminal.scrollToPreviousCommand', "Scroll To Previous Command"), - original: 'Scroll To Previous Command' - }, - scrollToNextCommand: { - value: localize('workbench.action.terminal.scrollToNextCommand', "Scroll To Next Command"), - original: 'Scroll To Next Command' - } + unsplit: localize2('unsplitTerminal', "Unsplit Terminal"), + rename: localize2('workbench.action.terminal.rename', "Rename..."), + toggleSizeToContentWidth: localize2('workbench.action.terminal.sizeToContentWidthInstance', "Toggle Size to Content Width"), + focusHover: localize2('workbench.action.terminal.focusHover', "Focus Hover"), + sendSequence: localize2('workbench.action.terminal.sendSequence', "Send Custom Sequence To Terminal"), + newWithCwd: localize2('workbench.action.terminal.newWithCwd', "Create New Terminal Starting in a Custom Working Directory"), + renameWithArgs: localize2('workbench.action.terminal.renameWithArg', "Rename the Currently Active Terminal"), + stickyScroll: localize2('stickyScroll', "Sticky Scroll"), + scrollToPreviousCommand: localize2('workbench.action.terminal.scrollToPreviousCommand', "Scroll To Previous Command"), + scrollToNextCommand: localize2('workbench.action.terminal.scrollToNextCommand', "Scroll To Next Command") }; 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 b280dfd54c8..03916c06855 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 @@ -5,7 +5,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { localize, localize2 } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -255,7 +255,7 @@ class FocusAccessibleBufferAction extends Action2 { constructor() { super({ id: TerminalCommandId.FocusAccessibleBuffer, - title: { value: localize('workbench.action.terminal.focusAccessibleBuffer', 'Focus Accessible Terminal View'), original: 'Focus Accessible Terminal View' }, + title: localize2('workbench.action.terminal.focusAccessibleBuffer', "Focus Accessible Terminal View"), precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), keybinding: [ { @@ -284,7 +284,7 @@ registerAction2(FocusAccessibleBufferAction); registerTerminalAction({ id: TerminalCommandId.AccessibleBufferGoToNextCommand, - title: { value: localize('workbench.action.terminal.accessibleBufferGoToNextCommand', 'Accessible Buffer Go to Next Command'), original: 'Accessible Buffer Go to Next Command' }, + title: localize2('workbench.action.terminal.accessibleBufferGoToNextCommand', "Accessible Buffer Go to Next Command"), precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated, ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), keybinding: [ { @@ -305,7 +305,7 @@ registerTerminalAction({ registerTerminalAction({ id: TerminalCommandId.AccessibleBufferGoToPreviousCommand, - title: { value: localize('workbench.action.terminal.accessibleBufferGoToPreviousCommand', 'Accessible Buffer Go to Previous Command'), original: 'Accessible Buffer Go to Previous Command' }, + title: localize2('workbench.action.terminal.accessibleBufferGoToPreviousCommand', "Accessible Buffer Go to Previous Command"), precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), keybinding: [ { diff --git a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts index 831ee05e699..da87e3c5f36 100644 --- a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts @@ -21,7 +21,7 @@ import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IModelDecorationOptions, ITextModel, InjectedTextCursorStops, InjectedTextOptions } from 'vs/editor/common/model'; import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/browser/hoverOperation'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -610,7 +610,7 @@ registerAction2(class ToggleInlineCoverage extends Action2 { constructor() { super({ id: TOGGLE_INLINE_COMMAND_ID, - title: { value: localize('coverage.toggleInline', "Toggle Inline Coverage"), original: 'Toggle Inline Coverage' }, + title: localize2('coverage.toggleInline', "Toggle Inline Coverage"), category: Categories.Test, keybinding: { weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index 9ffed3a5fa9..85f92722620 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -75,8 +75,8 @@ const enum ActionOrder { const hasAnyTestProvider = ContextKeyGreaterExpr.create(TestingContextKeys.providerCount.key, 0); -const LABEL_RUN_TESTS = { value: localize('runSelectedTests', 'Run Tests'), original: 'Run Tests' }; -const LABEL_DEBUG_TESTS = { value: localize('debugSelectedTests', 'Debug Tests'), original: 'Debug Tests' }; +const LABEL_RUN_TESTS = localize2('runSelectedTests', "Run Tests"); +const LABEL_DEBUG_TESTS = localize2('debugSelectedTests', "Debug Tests"); const LABEL_COVERAGE_TESTS = { value: localize('coverageSelectedTests', 'Run Tests with Coverage'), original: 'Run Tests withCoverage' }; export class HideTestAction extends Action2 { @@ -343,7 +343,7 @@ export class ConfigureTestProfilesAction extends Action2 { constructor() { super({ id: TestCommandId.ConfigureTestProfilesAction, - title: { value: localize('testing.configureProfile', 'Configure Test Profiles'), original: 'Configure Test Profiles' }, + title: localize2('testing.configureProfile', "Configure Test Profiles"), icon: icons.testingUpdateProfiles, f1: true, category, diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index fc86e344d3e..bf077946dcb 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -293,10 +293,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements constructor() { super({ id, - title: { - value: localize('import profile', "Import Profile..."), - original: 'Import Profile...' - }, + title: localize2('import profile', "Import Profile..."), category: PROFILES_CATEGORY, precondition: IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT.toNegated(), menu: [ @@ -400,10 +397,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements constructor() { super({ id: 'workbench.profiles.actions.createFromCurrentProfile', - title: { - value: localize('save profile as', "Save Current Profile As..."), - original: 'Save Current Profile As...' - }, + title: localize2('save profile as', "Save Current Profile As..."), category: PROFILES_CATEGORY, f1: true, precondition: PROFILES_ENABLEMENT_CONTEXT @@ -422,10 +416,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements constructor() { super({ id: 'workbench.profiles.actions.createProfile', - title: { - value: localize('create profile', "Create Profile..."), - original: 'Create Profile...' - }, + title: localize2('create profile', "Create Profile..."), category: PROFILES_CATEGORY, precondition: PROFILES_ENABLEMENT_CONTEXT, f1: true, @@ -451,10 +442,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements constructor() { super({ id: 'workbench.profiles.actions.deleteProfile', - title: { - value: localize('delete profile', "Delete Profile..."), - original: 'Delete Profile...' - }, + title: localize2('delete profile', "Delete Profile..."), category: PROFILES_CATEGORY, f1: true, precondition: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, HAS_PROFILES_CONTEXT), diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfileActions.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfileActions.ts index 3e83ff641d9..a9b85a44e65 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfileActions.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfileActions.ts @@ -3,26 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { IAction, Separator } from 'vs/base/common/actions'; +import { Codicon } from 'vs/base/common/codicons'; +import { localize, localize2 } from 'vs/nls'; +import { Categories } from 'vs/platform/action/common/actionCommonCategories'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { Action2, IMenuService, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { QuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { IUserDataProfileManagementService, PROFILES_CATEGORY, IUserDataProfileService, PROFILES_ENABLEMENT_CONTEXT, HAS_PROFILES_CONTEXT, MANAGE_PROFILES_ACTION_ID, ProfilesMenu } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IQuickInputService, QuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { Codicon } from 'vs/base/common/codicons'; -import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IAction, Separator } from 'vs/base/common/actions'; +import { HAS_PROFILES_CONTEXT, IUserDataProfileManagementService, IUserDataProfileService, MANAGE_PROFILES_ACTION_ID, PROFILES_CATEGORY, PROFILES_ENABLEMENT_CONTEXT, ProfilesMenu } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; class CreateTransientProfileAction extends Action2 { static readonly ID = 'workbench.profiles.actions.createTemporaryProfile'; - static readonly TITLE = { - value: localize('create temporary profile', "Create a Temporary Profile"), - original: 'Create a Temporary Profile' - }; + static readonly TITLE = localize2('create temporary profile', "Create a Temporary Profile"); constructor() { super({ id: CreateTransientProfileAction.ID, @@ -45,10 +42,7 @@ export class RenameProfileAction extends Action2 { constructor() { super({ id: RenameProfileAction.ID, - title: { - value: localize('rename profile', "Rename..."), - original: 'Rename...' - }, + title: localize2('rename profile', "Rename..."), category: PROFILES_CATEGORY, f1: true, precondition: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, HAS_PROFILES_CONTEXT), @@ -114,10 +108,7 @@ registerAction2(class ManageProfilesAction extends Action2 { constructor() { super({ id: MANAGE_PROFILES_ACTION_ID, - title: { - value: localize('mange', "Manage..."), - original: 'Manage...' - }, + title: localize2('mange', "Manage..."), category: PROFILES_CATEGORY, precondition: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, HAS_PROFILES_CONTEXT), }); @@ -158,10 +149,7 @@ registerAction2(class CleanupProfilesAction extends Action2 { constructor() { super({ id: 'workbench.profiles.actions.cleanupProfiles', - title: { - value: localize('cleanup profile', "Cleanup Profiles"), - original: 'Cleanup Profiles' - }, + title: localize2('cleanup profile', "Cleanup Profiles"), category: Categories.Developer, f1: true, precondition: PROFILES_ENABLEMENT_CONTEXT, @@ -177,10 +165,7 @@ registerAction2(class ResetWorkspacesAction extends Action2 { constructor() { super({ id: 'workbench.profiles.actions.resetWorkspaces', - title: { - value: localize('reset workspaces', "Reset Workspace Profiles Associations"), - original: 'Reset Workspace Profiles Associations' - }, + title: localize2('reset workspaces', "Reset Workspace Profiles Associations"), category: Categories.Developer, f1: true, precondition: PROFILES_ENABLEMENT_CONTEXT, diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/webviewCommands.ts b/src/vs/workbench/contrib/webview/electron-sandbox/webviewCommands.ts index e4358f9de10..3dae10c1086 100644 --- a/src/vs/workbench/contrib/webview/electron-sandbox/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/electron-sandbox/webviewCommands.ts @@ -15,7 +15,7 @@ export class OpenWebviewDeveloperToolsAction extends Action2 { constructor() { super({ id: 'workbench.action.webview.openDeveloperTools', - title: { value: nls.localize('openToolsLabel', "Open Webview Developer Tools"), original: 'Open Webview Developer Tools' }, + title: nls.localize2('openToolsLabel', "Open Webview Developer Tools"), category: Categories.Developer, f1: true }); diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts index b6683748bc8..8a068851f28 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts @@ -28,7 +28,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { dirname } from 'vs/base/common/path'; import { coalesce, flatten } from 'vs/base/common/arrays'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { checkGlobFileExists } from 'vs/workbench/services/extensions/common/workspaceContains'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -672,8 +672,8 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'resetGettingStartedProgress', - category: { original: 'Developer', value: localize('developer', "Developer") }, - title: { original: 'Reset Welcome Page Walkthrough Progress', value: localize('resetWelcomePageWalkthroughProgress', "Reset Welcome Page Walkthrough Progress") }, + category: localize2('developer', "Developer"), + title: localize2('resetWelcomePageWalkthroughProgress', "Reset Welcome Page Walkthrough Progress"), f1: true }); } diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index 45b9ee2b5a1..8809c99805f 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -683,7 +683,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: CONFIGURE_TRUST_COMMAND_ID, - title: { original: 'Configure Workspace Trust Settings', value: localize('configureWorkspaceTrustSettings', "Configure Workspace Trust Settings") }, + title: localize2('configureWorkspaceTrustSettings', "Configure Workspace Trust Settings"), precondition: ContextKeyExpr.and(WorkspaceTrustContext.IsEnabled, ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true)), category: WORKSPACES_CATEGORY, f1: true @@ -701,7 +701,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: MANAGE_TRUST_COMMAND_ID, - title: { original: 'Manage Workspace Trust', value: localize('manageWorkspaceTrust', "Manage Workspace Trust") }, + title: localize2('manageWorkspaceTrust', "Manage Workspace Trust"), precondition: ContextKeyExpr.and(WorkspaceTrustContext.IsEnabled, ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true)), category: WORKSPACES_CATEGORY, f1: true, diff --git a/src/vs/workbench/contrib/workspaces/browser/workspaces.contribution.ts b/src/vs/workbench/contrib/workspaces/browser/workspaces.contribution.ts index 989a57870d5..81b906cfe79 100644 --- a/src/vs/workbench/contrib/workspaces/browser/workspaces.contribution.ts +++ b/src/vs/workbench/contrib/workspaces/browser/workspaces.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -113,7 +113,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.openWorkspaceFromEditor', - title: { original: 'Open Workspace', value: localize('openWorkspace', "Open Workspace") }, + title: localize2('openWorkspace', "Open Workspace"), f1: false, menu: { id: MenuId.EditorContent, diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts index 4946a0219a6..b6ca3344bad 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts @@ -626,10 +626,7 @@ registerAction2(class MeasureExtHostLatencyAction extends Action2 { constructor() { super({ id: 'editor.action.measureExtHostLatency', - title: { - value: nls.localize('measureExtHostLatency', "Measure Extension Host Latency"), - original: 'Measure Extension Host Latency' - }, + title: nls.localize2('measureExtHostLatency', "Measure Extension Host Latency"), category: Categories.Developer, f1: true }); diff --git a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts index 123f189265e..cdd507e6fff 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts @@ -725,7 +725,7 @@ class RestartExtensionHostAction extends Action2 { constructor() { super({ id: 'workbench.action.restartExtensionHost', - title: { value: nls.localize('restartExtensionHost', "Restart Extension Host"), original: 'Restart Extension Host' }, + title: nls.localize2('restartExtensionHost', "Restart Extension Host"), category: Categories.Developer, f1: true }); diff --git a/src/vs/workbench/services/userDataSync/common/userDataSync.ts b/src/vs/workbench/services/userDataSync/common/userDataSync.ts index e5438a63c14..9aa88c9e1d0 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSync.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSync.ts @@ -95,7 +95,7 @@ export const SYNC_CONFLICTS_VIEW_ID = 'workbench.views.sync.conflicts'; export const DOWNLOAD_ACTIVITY_ACTION_DESCRIPTOR: Readonly = { id: 'workbench.userDataSync.actions.downloadSyncActivity', - title: { original: 'Download Settings Sync Activity', value: localize('download sync activity title', "Download Settings Sync Activity") }, + title: localize2('download sync activity title', "Download Settings Sync Activity"), category: Categories.Developer, f1: true, precondition: ContextKeyExpr.and(CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)) From 3b5ae15f95a5927597a5d41e208d6ed19668bbc5 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 31 Jan 2024 11:04:30 +0100 Subject: [PATCH 0756/1897] perf - `registerWorkbenchContribution2` and some adoptions (#203802) * perf - first cut workbench contributions 2 * log warning * add tests * tests * fix tests * . * adopt a bit and add access to times * adopt for some * adopt for some * . * fix tests * . --- extensions/vscode-api-tests/package.json | 2 - .../api/browser/extensionHost.contribution.ts | 7 +- .../browser/actions/textInputActions.ts | 7 +- .../actions/widgetNavigationCommands.ts | 8 +- .../parts/dialogs/dialog.web.contribution.ts | 8 +- .../parts/editor/editor.contribution.ts | 11 +- .../browser/parts/editor/editorAutoSave.ts | 2 + .../parts/editor/editorConfiguration.ts | 2 + .../browser/parts/editor/editorStatus.ts | 2 + .../browser/workbench.contribution.ts | 4 +- src/vs/workbench/common/configuration.ts | 2 + src/vs/workbench/common/contributions.ts | 234 +++++++++++++++--- .../browser/accessibility.contribution.ts | 8 +- .../browser/accessibilityConfiguration.ts | 2 + .../browser/accessibilityStatus.ts | 3 + .../accessibility/browser/saveAudioCue.ts | 3 + .../contrib/chat/browser/chat.contribution.ts | 7 +- .../browser/chatContributionServiceImpl.ts | 7 +- .../codeEditor/browser/editorFeatures.ts | 7 +- .../codeEditor/browser/toggleWordWrap.ts | 7 +- .../electron-sandbox/selectionClipboard.ts | 8 +- .../browser/customEditor.contribution.ts | 5 +- .../browser/customEditorInputFactory.ts | 2 + .../browser/editors/fileEditorHandler.ts | 2 + .../browser/editors/textFileEditorTracker.ts | 2 + .../editors/textFileSaveErrorHandler.ts | 2 + .../contrib/files/browser/explorerViewlet.ts | 2 + .../files/browser/files.contribution.ts | 16 +- .../files/common/dirtyFilesIndicator.ts | 3 + .../browser/interactive.contribution.ts | 16 +- .../contrib/list/browser/list.contribution.ts | 7 +- .../browser/localHistory.contribution.ts | 5 +- .../browser/localHistoryTimeline.ts | 10 +- .../browser/mergeEditor.contribution.ts | 4 +- .../mergeEditor/browser/view/mergeEditor.ts | 2 + .../browser/multiDiffEditor.contribution.ts | 7 +- .../browser/multiDiffEditorInput.ts | 3 + .../browser/scmMultiDiffSourceResolver.ts | 3 + .../contrib/clipboard/notebookClipboard.ts | 7 +- .../browser/contrib/marker/markerProvider.ts | 7 +- .../contrib/profile/notebookProfile.ts | 8 +- .../contrib/undoRedo/notebookUndoRedo.ts | 7 +- .../notebook/browser/notebook.contribution.ts | 33 ++- .../browser/keyboardLayoutPicker.ts | 8 +- .../browser/preferences.contribution.ts | 8 +- .../common/preferencesContribution.ts | 3 + .../remote/browser/remote.contribution.ts | 8 +- .../contrib/remote/browser/remoteIndicator.ts | 2 + .../contrib/remote/browser/showCandidate.ts | 3 + .../contrib/remote/browser/tunnelFactory.ts | 2 + .../remote/common/remote.contribution.ts | 11 +- .../electron-sandbox/remote.contribution.ts | 16 +- .../notebookSearchContributions.ts | 5 - .../search/browser/replaceContributions.ts | 5 +- .../contrib/search/browser/replaceService.ts | 2 + .../search/browser/search.contribution.ts | 8 +- .../browser/searchEditor.contribution.ts | 12 +- .../electron-sandbox/localTerminalBackend.ts | 3 + .../electron-sandbox/terminal.contribution.ts | 4 +- .../browser/userDataProfile.contribution.ts | 4 +- .../browser/userDataProfile.ts | 2 + .../userDataSync.contribution.ts | 7 +- .../browser/webviewPanel.contribution.ts | 7 +- .../browser/workspace.contribution.ts | 7 +- .../parts/dialogs/dialog.contribution.ts | 8 +- .../electron-sandbox/accessibilityService.ts | 8 +- .../browser/configurationService.ts | 6 +- .../extensions/browser/extensionUrlHandler.ts | 7 +- .../remote/browser/remoteAgentService.ts | 7 +- .../electron-sandbox/remoteAgentService.ts | 7 +- ...extMateTokenizationFeature.contribution.ts | 8 +- .../common/untitledTextEditorHandler.ts | 2 + .../browser/workingCopyBackupService.ts | 5 +- .../browser/workingCopyBackupTracker.ts | 2 + .../workingCopyBackupService.ts | 6 +- .../workingCopyBackupTracker.ts | 2 + .../test/browser/contributions.test.ts | 155 ++++++++++++ .../test/browser/workbenchTestServices.ts | 40 ++- 78 files changed, 684 insertions(+), 200 deletions(-) create mode 100644 src/vs/workbench/test/browser/contributions.test.ts diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 5fe2c956670..abb85f8f902 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -30,7 +30,6 @@ "notebookMime", "portsAttributes", "quickPickSortByLabel", - "readonlyMessage", "languageStatusText", "resolvers", "scmActionButton", @@ -50,7 +49,6 @@ "treeViewReveal", "workspaceTrust", "telemetry", - "testingActiveProfile", "windowActivity" ], "private": true, diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index a232035c72b..a814d781c79 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; // --- other interested parties import { JSONValidationExtensionPoint } from 'vs/workbench/api/common/jsonValidationExtensionPoint'; @@ -93,6 +92,8 @@ import './mainThreadIssueReporter'; export class ExtensionPoints implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.extensionPoints'; + constructor( @IInstantiationService private readonly instantiationService: IInstantiationService ) { @@ -106,4 +107,4 @@ export class ExtensionPoints implements IWorkbenchContribution { } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ExtensionPoints, LifecyclePhase.Starting); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(ExtensionPoints.ID, ExtensionPoints, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/browser/actions/textInputActions.ts b/src/vs/workbench/browser/actions/textInputActions.ts index 23956da0775..9c8939abf75 100644 --- a/src/vs/workbench/browser/actions/textInputActions.ts +++ b/src/vs/workbench/browser/actions/textInputActions.ts @@ -9,9 +9,8 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Disposable } from 'vs/base/common/lifecycle'; import { EventHelper, addDisposableListener, getActiveDocument, getWindow } from 'vs/base/browser/dom'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { isNative } from 'vs/base/common/platform'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -19,6 +18,8 @@ import { Event as BaseEvent } from 'vs/base/common/event'; export class TextInputActionsProvider extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.textInputActionsProvider'; + private textInputActions: IAction[] = []; constructor( @@ -105,4 +106,4 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TextInputActionsProvider, LifecyclePhase.Ready); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(TextInputActionsProvider.ID, TextInputActionsProvider, WorkbenchContributionInstantiation.BlockRestore); diff --git a/src/vs/workbench/browser/actions/widgetNavigationCommands.ts b/src/vs/workbench/browser/actions/widgetNavigationCommands.ts index e62c9af8d1f..c98008e966e 100644 --- a/src/vs/workbench/browser/actions/widgetNavigationCommands.ts +++ b/src/vs/workbench/browser/actions/widgetNavigationCommands.ts @@ -10,8 +10,7 @@ import { WorkbenchListFocusContextKey, WorkbenchListScrollAtBottomContextKey, Wo import { Event } from 'vs/base/common/event'; import { combinedDisposable, toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; /** INavigableContainer represents a logical container composed of widgets that can be navigated back and forth with key shortcuts */ @@ -57,6 +56,9 @@ function handleFocusEventsGroup(group: readonly IFocusNotifier[], handler: (isFo const NavigableContainerFocusedContextKey = new RawContextKey('navigableContainerFocused', false); class NavigableContainerManager implements IDisposable { + + static readonly ID = 'workbench.contrib.navigableContainerManager'; + private static INSTANCE: NavigableContainerManager | undefined; private readonly containers = new Set(); @@ -112,7 +114,7 @@ export function registerNavigableContainer(container: INavigableContainer): IDis } Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(NavigableContainerManager, LifecyclePhase.Starting); + .registerWorkbenchContribution2(NavigableContainerManager.ID, NavigableContainerManager, WorkbenchContributionInstantiation.BlockStartup); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'widgetNavigation.focusPrevious', diff --git a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts index 673cc39dab0..fd3512321c1 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts @@ -10,15 +10,17 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IDialogsModel, IDialogViewItem } from 'vs/workbench/common/dialogs'; import { BrowserDialogHandler } from 'vs/workbench/browser/parts/dialogs/dialogHandler'; import { DialogService } from 'vs/workbench/services/dialogs/common/dialogService'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.dialogHandler'; + private readonly model: IDialogsModel; private readonly impl: IDialogHandler; @@ -77,4 +79,4 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC } const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(DialogHandlerContribution, LifecyclePhase.Starting); +workbenchRegistry.registerWorkbenchContribution2(DialogHandlerContribution.ID, DialogHandlerContribution, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 014acd8d37a..535c06914ef 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -59,8 +59,7 @@ import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/com import { isMacintosh } from 'vs/base/common/platform'; import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { FloatingEditorClickMenu } from 'vs/workbench/browser/codeeditor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { EditorAutoSave } from 'vs/workbench/browser/parts/editor/editorAutoSave'; import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess, AllEditorsByAppearanceQuickAccess, AllEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; @@ -128,10 +127,10 @@ Registry.as(EditorExtensions.EditorFactory).registerEdit //#region Workbench Contributions -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorAutoSave, LifecyclePhase.Ready); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorStatusContribution, LifecyclePhase.Ready); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(UntitledTextEditorWorkingCopyEditorHandler, LifecyclePhase.Ready); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DynamicEditorConfigurations, LifecyclePhase.Ready); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(EditorAutoSave.ID, EditorAutoSave, WorkbenchContributionInstantiation.BlockRestore); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(EditorStatusContribution.ID, EditorStatusContribution, WorkbenchContributionInstantiation.BlockRestore); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(UntitledTextEditorWorkingCopyEditorHandler.ID, UntitledTextEditorWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(DynamicEditorConfigurations.ID, DynamicEditorConfigurations, WorkbenchContributionInstantiation.BlockRestore); registerEditorContribution(FloatingEditorClickMenu.ID, FloatingEditorClickMenu, EditorContributionInstantiation.AfterFirstRender); //#endregion diff --git a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts index 26f11a6a224..ddcce4134c0 100644 --- a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts +++ b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts @@ -21,6 +21,8 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' export class EditorAutoSave extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.editorAutoSave'; + // Auto save: after delay private readonly scheduledAutoSavesAfterDelay = new Map(); diff --git a/src/vs/workbench/browser/parts/editor/editorConfiguration.ts b/src/vs/workbench/browser/parts/editor/editorConfiguration.ts index 0374332a083..9f3008f7385 100644 --- a/src/vs/workbench/browser/parts/editor/editorConfiguration.ts +++ b/src/vs/workbench/browser/parts/editor/editorConfiguration.ts @@ -19,6 +19,8 @@ import { ByteSize, getLargeFileConfirmationLimit } from 'vs/platform/files/commo export class DynamicEditorConfigurations extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.dynamicEditorConfigurations'; + private static readonly AUTO_LOCK_DEFAULT_ENABLED = new Set([ 'terminalEditor', 'mainThreadWebview-simpleBrowser.view', diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index b8f1bc68944..0c08179077a 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -872,6 +872,8 @@ class EditorStatus extends Disposable { export class EditorStatusContribution extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.editorStatus'; + constructor( @IInstantiationService instantiationService: IInstantiationService, @IEditorGroupsService editorGroupService: IEditorGroupsService, diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index c3ec65eaee1..8a0edf0c26e 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -9,7 +9,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform'; import { ConfigurationMigrationWorkbenchContribution, DynamicWorkbenchConfigurationWorkbenchContribution, IConfigurationMigrationRegistry, workbenchConfigurationNodeBase, Extensions, ConfigurationKeyValuePairs, problemsConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { isStandalone } from 'vs/base/browser/browser'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ActivityBarPosition, EditorActionsLocation, EditorTabsMode, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; @@ -22,7 +22,7 @@ const registry = Registry.as(ConfigurationExtensions.Con Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ConfigurationMigrationWorkbenchContribution, LifecyclePhase.Eventually); // Dynamic Configuration - Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DynamicWorkbenchConfigurationWorkbenchContribution, LifecyclePhase.Ready); + Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(DynamicWorkbenchConfigurationWorkbenchContribution.ID, DynamicWorkbenchConfigurationWorkbenchContribution, WorkbenchContributionInstantiation.BlockRestore); // Workbench registry.registerConfiguration({ diff --git a/src/vs/workbench/common/configuration.ts b/src/vs/workbench/common/configuration.ts index e0db1ec2745..d9ebf02b99b 100644 --- a/src/vs/workbench/common/configuration.ts +++ b/src/vs/workbench/common/configuration.ts @@ -162,6 +162,8 @@ export class ConfigurationMigrationWorkbenchContribution extends Disposable impl export class DynamicWorkbenchConfigurationWorkbenchContribution extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.dynamicWorkbenchConfiguration'; + constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService ) { diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index c1e192e94d1..2b1db039906 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -22,24 +22,96 @@ export namespace Extensions { export const Workbench = 'workbench.contributions.kind'; } +export const enum WorkbenchContributionInstantiation { + + /** + * The first phase signals that we are about to startup getting ready. + * + * Note: doing work in this phase blocks an editor from showing to + * the user, so please rather consider to use the other types, preferable + * `Lazy` to only instantiate the contribution when really needed. + */ + BlockStartup = LifecyclePhase.Starting, + + /** + * Services are ready and the window is about to restore its UI state. + * + * Note: doing work in this phase blocks an editor from showing to + * the user, so please rather consider to use the other types, preferable + * `Lazy` to only instantiate the contribution when really needed. + */ + BlockRestore = LifecyclePhase.Ready, + + /** + * Views, panels and editors have restored. Editors are given a bit of + * time to restore their contents. + */ + AfterRestored = LifecyclePhase.Restored, + + /** + * The last phase after views, panels and editors have restored and + * some time has passed (2-5 seconds). + */ + Eventually = LifecyclePhase.Eventually, + + /** + * The contribution is created only when explicitly requested via + * `getContribution()`. + */ + Lazy = LifecyclePhase.Eventually + 1 +} + +function toInstantiation(phase: LifecyclePhase): WorkbenchContributionInstantiation { + switch (phase) { + case LifecyclePhase.Starting: + return WorkbenchContributionInstantiation.BlockStartup; + case LifecyclePhase.Ready: + return WorkbenchContributionInstantiation.BlockRestore; + case LifecyclePhase.Restored: + return WorkbenchContributionInstantiation.AfterRestored; + case LifecyclePhase.Eventually: + return WorkbenchContributionInstantiation.Eventually; + } +} + +function toPhase(instantiation: WorkbenchContributionInstantiation.BlockStartup | WorkbenchContributionInstantiation.BlockRestore | WorkbenchContributionInstantiation.AfterRestored | WorkbenchContributionInstantiation.Eventually): LifecyclePhase { + switch (instantiation) { + case WorkbenchContributionInstantiation.BlockStartup: + return LifecyclePhase.Starting; + case WorkbenchContributionInstantiation.BlockRestore: + return LifecyclePhase.Ready; + case WorkbenchContributionInstantiation.AfterRestored: + return LifecyclePhase.Restored; + case WorkbenchContributionInstantiation.Eventually: + return LifecyclePhase.Eventually; + } +} + type IWorkbenchContributionSignature = new (...services: Service) => IWorkbenchContribution; export interface IWorkbenchContributionsRegistry { /** - * Registers a workbench contribution to the platform that will - * be loaded when the workbench starts and disposed when the - * workbench shuts down. - * - * The parameter `phase` controls when the contribution is instantiated. - * Phases `Starting` and `Ready` are synchronous, all other phases are - * delayed until the workbench is idle. Contributions are guaranteed to - * be created in the order of their phases, even when delayed to idle. - * - * @param phase the lifecycle phase when to instantiate the contribution. + * @deprecated use `registerWorkbenchContribution2` instead. */ registerWorkbenchContribution(contribution: IWorkbenchContributionSignature, phase: LifecyclePhase): void; + /** + * Register a workbench contribution that will be instantiated + * based on the `instantiation` property. + */ + registerWorkbenchContribution2(id: string, ctor: IWorkbenchContributionSignature, instantiation: WorkbenchContributionInstantiation): void; + + /** + * Provides access to a workbench contribution with a specific identifier. + * The contribution is created if not yet done. + * + * Note: will throw an error if + * - called too early before the registry has started + * - no contribution is known for the given identifier + */ + getWorkbenchContribution(id: string): T; + /** * Starts the registry by providing the required services. */ @@ -50,39 +122,111 @@ export interface IWorkbenchContributionsRegistry { * phase have been instantiated. */ readonly whenRestored: Promise; + + /** + * Provides access to the instantiation times of all contributions by + * lifecycle phase. + */ + readonly timings: Map>; } -class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry { +interface IWorkbenchContributionRegistration { + readonly id: string | undefined; + readonly ctor: IConstructorSignature; +} + +export class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry { + + private static readonly BLOCK_BEFORE_RESTORE_WARN_THRESHOLD = 20; + private static readonly BLOCK_AFTER_RESTORE_WARN_THRESHOLD = 100; private instantiationService: IInstantiationService | undefined; private lifecycleService: ILifecycleService | undefined; private logService: ILogService | undefined; private environmentService: IEnvironmentService | undefined; - private readonly contributions = new Map[]>(); + private readonly contributionsByPhase = new Map(); + private readonly contributionsById = new Map(); + + private readonly instancesById = new Map(); + + private readonly timingsByPhase = new Map>(); + get timings() { return this.timingsByPhase; } private readonly pendingRestoredContributions = new DeferredPromise(); readonly whenRestored = this.pendingRestoredContributions.p; - registerWorkbenchContribution(contribution: IConstructorSignature, phase: LifecyclePhase = LifecyclePhase.Starting): void { + registerWorkbenchContribution2(id: string | undefined, ctor: IConstructorSignature, instantiation: WorkbenchContributionInstantiation): void { + const contribution: IWorkbenchContributionRegistration = { id, ctor }; // Instantiate directly if we are already matching the provided phase - if (this.instantiationService && this.lifecycleService && this.logService && this.environmentService && this.lifecycleService.phase >= phase) { - this.safeCreateContribution(this.instantiationService, this.logService, this.environmentService, contribution, phase); + if (instantiation !== WorkbenchContributionInstantiation.Lazy && this.instantiationService && this.lifecycleService && this.logService && this.environmentService && this.lifecycleService.phase >= instantiation) { + this.safeCreateContribution(this.instantiationService, this.logService, this.environmentService, contribution, toPhase(instantiation)); } - // Otherwise keep contributions by lifecycle phase + // Otherwise keep contributions by instantiation kind for later instantiation else { - let contributions = this.contributions.get(phase); - if (!contributions) { - contributions = []; - this.contributions.set(phase, contributions); + + // by phase + if (instantiation !== WorkbenchContributionInstantiation.Lazy) { + const phase = toPhase(instantiation); + let contributionsForPhase = this.contributionsByPhase.get(phase); + if (!contributionsForPhase) { + contributionsForPhase = []; + this.contributionsByPhase.set(phase, contributionsForPhase); + } + + contributionsForPhase.push(contribution); } - contributions.push(contribution); + // by id + if (typeof id === 'string') { + if (!this.contributionsById.has(id)) { + this.contributionsById.set(id, contribution); + } else { + console.error(`IWorkbenchContributionsRegistry#registerWorkbenchContribution(): Can't register multiple contributions with same id '${id}'`); + } + } } } + registerWorkbenchContribution(ctor: IConstructorSignature, phase: LifecyclePhase): void { + this.registerWorkbenchContribution2(undefined, ctor, toInstantiation(phase)); + } + + getWorkbenchContribution(id: string): T { + if (this.instancesById.has(id)) { + return this.instancesById.get(id) as T; + } + + const instantiationService = this.instantiationService; + const lifecycleService = this.lifecycleService; + const logService = this.logService; + const environmentService = this.environmentService; + if (!instantiationService || !lifecycleService || !logService || !environmentService) { + throw new Error(`IWorkbenchContributionsRegistry#getContribution('${id}'): cannot be called before registry started`); + } + + const contribution = this.contributionsById.get(id); + if (!contribution) { + throw new Error(`IWorkbenchContributionsRegistry#getContribution('${id}'): contribution with that identifier is unknown.`); + } + + const phase = lifecycleService.phase; + if (phase < LifecyclePhase.Restored) { + logService.warn(`IWorkbenchContributionsRegistry#getContribution('${id}'): lazy contribution instantiated before LifecyclePhase.Restored!`); + } + + this.safeCreateContribution(instantiationService, logService, environmentService, contribution, lifecycleService.phase); + + const instance = this.instancesById.get(id); + if (!instance) { + throw new Error(`IWorkbenchContributionsRegistry#getContribution('${id}'): failed to create contribution.`); + } + + return instance as T; + } + start(accessor: ServicesAccessor): void { const instantiationService = this.instantiationService = accessor.get(IInstantiationService); const lifecycleService = this.lifecycleService = accessor.get(ILifecycleService); @@ -108,9 +252,9 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry } private async doInstantiateByPhase(instantiationService: IInstantiationService, logService: ILogService, environmentService: IEnvironmentService, phase: LifecyclePhase): Promise { - const contributions = this.contributions.get(phase); + const contributions = this.contributionsByPhase.get(phase); if (contributions) { - this.contributions.delete(phase); + this.contributionsByPhase.delete(phase); switch (phase) { case LifecyclePhase.Starting: @@ -151,7 +295,7 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry } } - private doInstantiateWhenIdle(contributions: IConstructorSignature[], instantiationService: IInstantiationService, logService: ILogService, environmentService: IEnvironmentService, phase: LifecyclePhase): void { + private doInstantiateWhenIdle(contributions: IWorkbenchContributionRegistration[], instantiationService: IInstantiationService, logService: ILogService, environmentService: IEnvironmentService, phase: LifecyclePhase): void { mark(`code/willCreateWorkbenchContributions/${phase}`); let i = 0; @@ -180,19 +324,41 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry runWhenGlobalIdle(instantiateSome, forcedTimeout); } - private safeCreateContribution(instantiationService: IInstantiationService, logService: ILogService, environmentService: IEnvironmentService, contribution: IConstructorSignature, phase: LifecyclePhase): void { - const now: number | undefined = phase < LifecyclePhase.Restored ? Date.now() : undefined; - - try { - instantiationService.createInstance(contribution); - } catch (error) { - logService.error(`Unable to create workbench contribution ${contribution.name}.`, error); + private safeCreateContribution(instantiationService: IInstantiationService, logService: ILogService, environmentService: IEnvironmentService, contribution: IWorkbenchContributionRegistration, phase: LifecyclePhase): void { + if (typeof contribution.id === 'string' && this.instancesById.has(contribution.id)) { + return; } - if (typeof now === 'number' && !environmentService.isBuilt /* only log out of sources where we have good ctor names */) { + const now = Date.now(); + + try { + mark(`code/willCreateWorkbenchContribution/${phase}/${contribution.id}`); + + const instance = instantiationService.createInstance(contribution.ctor); + if (typeof contribution.id === 'string') { + this.instancesById.set(contribution.id, instance); + this.contributionsById.delete(contribution.id); + } + } catch (error) { + logService.error(`Unable to create workbench contribution '${contribution.id ?? contribution.ctor.name}'.`, error); + } finally { + mark(`code/didCreateWorkbenchContribution/${phase}/${contribution.id}`); + } + + if (typeof contribution.id === 'string' || !environmentService.isBuilt /* only log out of sources where we have good ctor names (TODO@bpasero remove when adopted IDs) */) { const time = Date.now() - now; - if (time > 20) { - logService.warn(`Workbench contribution ${contribution.name} blocked restore phase by ${time}ms.`); + if (time > (phase < LifecyclePhase.Restored ? WorkbenchContributionsRegistry.BLOCK_BEFORE_RESTORE_WARN_THRESHOLD : WorkbenchContributionsRegistry.BLOCK_AFTER_RESTORE_WARN_THRESHOLD)) { + logService.warn(`Creation of workbench contribution '${contribution.id ?? contribution.ctor.name}' took ${time}ms.`); + } + + if (typeof contribution.id === 'string') { + let timingsForPhase = this.timingsByPhase.get(phase); + if (!timingsForPhase) { + timingsForPhase = []; + this.timingsByPhase.set(phase, timingsForPhase); + } + + timingsForPhase.push([contribution.id, time]); } } } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index ffdf8d00676..884163dd8e2 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -5,7 +5,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { DynamicSpeechAccessibilityConfiguration, registerAccessibilityConfiguration } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, 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 { IAccessibleViewService, AccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; @@ -28,6 +28,6 @@ const workbenchContributionsRegistry = Registry.as(); diff --git a/src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts b/src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts index 45a0c582aa9..32ef2425e81 100644 --- a/src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts +++ b/src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts @@ -10,6 +10,9 @@ import { SaveReason } from 'vs/workbench/common/editor'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; export class SaveAudioCueContribution extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.saveAudioCues'; + constructor( @IAudioCueService private readonly _audioCueService: IAudioCueService, @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 87375411120..e21da867b90 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -13,7 +13,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { registerChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { registerChatCodeBlockActions } from 'vs/workbench/contrib/chat/browser/actions/chatCodeblockActions'; @@ -108,6 +108,9 @@ Registry.as(EditorExtensions.EditorPane).registerEditorPane ); class ChatResolverContribution extends Disposable { + + static readonly ID = 'workbench.contrib.chatResolver'; + constructor( @IEditorResolverService editorResolverService: IEditorResolverService, @IInstantiationService instantiationService: IInstantiationService, @@ -281,7 +284,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(ChatResolverContribution, LifecyclePhase.Starting); +workbenchContributionsRegistry.registerWorkbenchContribution2(ChatResolverContribution.ID, ChatResolverContribution, WorkbenchContributionInstantiation.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(ChatAccessibleViewContribution, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(ChatSlashStaticSlashCommandsContribution, LifecyclePhase.Eventually); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(ChatEditorInput.TypeID, ChatEditorInputSerializer); diff --git a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts index dd167ee1175..bcaecb8d6d0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts @@ -11,7 +11,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation, Extensions as ViewExtensions } from 'vs/workbench/common/views'; import { getHistoryAction, getOpenChatEditorAction } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { getNewChatAction } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; @@ -20,7 +20,6 @@ import { getQuickChatActionForProvider } from 'vs/workbench/contrib/chat/browser import { CHAT_SIDEBAR_PANEL_ID, ChatViewPane, IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { IChatContributionService, IChatProviderContribution, IRawChatProviderContribution } from 'vs/workbench/contrib/chat/common/chatContributionService'; import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; const chatExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ @@ -62,6 +61,8 @@ const chatExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensi export class ChatExtensionPointHandler implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.chatExtensionPointHandler'; + private _viewContainer: ViewContainer; private _registrationDisposables = new Map(); @@ -154,7 +155,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { } const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(ChatExtensionPointHandler, LifecyclePhase.Starting); +workbenchRegistry.registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchContributionInstantiation.BlockStartup); export class ChatContributionService implements IChatContributionService { diff --git a/src/vs/workbench/contrib/codeEditor/browser/editorFeatures.ts b/src/vs/workbench/contrib/codeEditor/browser/editorFeatures.ts index 87e94175531..534473115c3 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/editorFeatures.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/editorFeatures.ts @@ -9,11 +9,12 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { getEditorFeatures } from 'vs/editor/common/editorFeatures'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; class EditorFeaturesInstantiator extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.editorFeaturesInstantiator'; + private _instantiated = false; constructor( @@ -51,4 +52,4 @@ class EditorFeaturesInstantiator extends Disposable implements IWorkbenchContrib } const workbenchRegistry = Registry.as(Extensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(EditorFeaturesInstantiator, LifecyclePhase.Ready); +workbenchRegistry.registerWorkbenchContribution2(EditorFeaturesInstantiator.ID, EditorFeaturesInstantiator, WorkbenchContributionInstantiation.BlockRestore); diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index 2e978db3d4d..f1945b9bae8 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -18,8 +18,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { Codicon } from 'vs/base/common/codicons'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Event } from 'vs/base/common/event'; import { addDisposableListener, onDidRegisterWindow } from 'vs/base/browser/dom'; @@ -256,6 +255,8 @@ function canToggleWordWrap(codeEditorService: ICodeEditorService, editor: ICodeE class EditorWordWrapContextKeyTracker extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.editorWordWrapContextKeyTracker'; + private readonly _canToggleWordWrap: IContextKey; private readonly _editorWordWrap: IContextKey; private _activeEditor: ICodeEditor | null; @@ -315,7 +316,7 @@ class EditorWordWrapContextKeyTracker extends Disposable implements IWorkbenchCo } const workbenchRegistry = Registry.as(Extensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(EditorWordWrapContextKeyTracker, LifecyclePhase.Ready); +workbenchRegistry.registerWorkbenchContribution2(EditorWordWrapContextKeyTracker.ID, EditorWordWrapContextKeyTracker, WorkbenchContributionInstantiation.BlockRestore); registerEditorContribution(ToggleWordWrapController.ID, ToggleWordWrapController, EditorContributionInstantiation.Eager); // eager because it needs to change the editor word wrap configuration registerDiffEditorContribution(DiffToggleWordWrapController.ID, DiffToggleWordWrapController); diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts index 025c038bfe4..4b8c89ae88a 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts @@ -16,9 +16,8 @@ import { IEditorContribution, Handler } from 'vs/editor/common/editorCommon'; import { EndOfLinePreference } from 'vs/editor/common/model'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { mainWindow } from 'vs/base/browser/window'; @@ -93,6 +92,9 @@ export class SelectionClipboard extends Disposable implements IEditorContributio } class SelectionClipboardPastePreventer extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.selectionClipboardPastePreventer'; + constructor( @IConfigurationService configurationService: IConfigurationService ) { @@ -142,7 +144,7 @@ class PasteSelectionClipboardAction extends EditorAction { } registerEditorContribution(SelectionClipboardContributionID, SelectionClipboard, EditorContributionInstantiation.Eager); // eager because it needs to listen to selection change events -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SelectionClipboardPastePreventer, LifecyclePhase.Ready); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(SelectionClipboardPastePreventer.ID, SelectionClipboardPastePreventer, WorkbenchContributionInstantiation.BlockRestore); if (platform.isLinux) { registerEditorAction(PasteSelectionClipboardAction); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts b/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts index f240b088486..1a68794ce2c 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts @@ -7,12 +7,11 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { ComplexCustomWorkingCopyEditorHandler as ComplexCustomWorkingCopyEditorHandler, CustomEditorInputSerializer } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory'; import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { WebviewEditor } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditor'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { CustomEditorInput } from './customEditorInput'; import { CustomEditorService } from './customEditors'; @@ -32,4 +31,4 @@ Registry.as(EditorExtensions.EditorFactory) .registerEditorSerializer(CustomEditorInputSerializer.ID, CustomEditorInputSerializer); Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(ComplexCustomWorkingCopyEditorHandler, LifecyclePhase.Starting); + .registerWorkbenchContribution2(ComplexCustomWorkingCopyEditorHandler.ID, ComplexCustomWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index 37d18f02f0e..8dc11b1af6b 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -119,6 +119,8 @@ function reviveWebview(webviewService: IWebviewService, data: { origin: string | export class ComplexCustomWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution, IWorkingCopyEditorHandler { + static readonly ID = 'workbench.contrib.complexCustomWorkingCopyEditorHandler'; + constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, @IWorkingCopyEditorService _workingCopyEditorService: IWorkingCopyEditorService, diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorHandler.ts index abf78370b6c..9aef2c0dcd3 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorHandler.ts @@ -69,6 +69,8 @@ export class FileEditorInputSerializer implements IEditorSerializer { export class FileEditorWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution, IWorkingCopyEditorHandler { + static readonly ID = 'workbench.contrib.fileEditorWorkingCopyEditorHandler'; + constructor( @IWorkingCopyEditorService workingCopyEditorService: IWorkingCopyEditorService, @ITextEditorService private readonly textEditorService: ITextEditorService, diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts index fa8593ad23e..ad9b863d3f8 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts @@ -22,6 +22,8 @@ import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; export class TextFileEditorTracker extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.textFileEditorTracker'; + constructor( @IEditorService private readonly editorService: IEditorService, @ITextFileService private readonly textFileService: ITextFileService, diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts index cb1e773949d..1b0efaca20f 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts @@ -42,6 +42,8 @@ const conflictEditorHelp = localize('userGuide', "Use the actions in the editor // A handler for text file save error happening with conflict resolution actions export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHandler, IWorkbenchContribution { + static readonly ID = 'workbench.contrib.textFileSaveErrorHandler'; + private readonly messages = new ResourceMap(); private readonly conflictResolutionContext = new RawContextKey(CONFLICT_RESOLUTION_CONTEXT, false, true).bindTo(this.contextKeyService); private activeConflictResolutionResource: URI | undefined = undefined; diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 5b064b6c620..010e42cb837 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -44,6 +44,8 @@ const openEditorsViewIcon = registerIcon('open-editors-view-icon', Codicon.book, export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.explorerViewletViews'; + constructor( @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IProgressService progressService: IProgressService diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index efd9fe59af7..f1090570d8a 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { sep } from 'vs/base/common/path'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { IFileEditorInput, IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { AutoSaveConfiguration, HotExitConfiguration, FILES_EXCLUDE_CONFIG, FILES_ASSOCIATIONS_CONFIG, FILES_READONLY_INCLUDE_CONFIG, FILES_READONLY_EXCLUDE_CONFIG, FILES_READONLY_FROM_PERMISSIONS_CONFIG } from 'vs/platform/files/common/files'; import { SortOrder, LexicographicOptions, FILE_EDITOR_INPUT_ID, BINARY_TEXT_FILE_MODE, UndoConfirmLevel, IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; @@ -39,6 +39,8 @@ import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textF class FileUriLabelContribution implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.fileUriLabel'; + constructor(@ILabelService labelService: ILabelService) { labelService.registerFormatter({ scheme: Schemas.file, @@ -96,25 +98,25 @@ Registry.as(EditorExtensions.EditorFactory).registerFile // Register Editor Input Serializer & Handler Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(FILE_EDITOR_INPUT_ID, FileEditorInputSerializer); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(FileEditorWorkingCopyEditorHandler, LifecyclePhase.Ready); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(FileEditorWorkingCopyEditorHandler.ID, FileEditorWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); // Register Explorer views -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ExplorerViewletViewsContribution, LifecyclePhase.Starting); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(ExplorerViewletViewsContribution.ID, ExplorerViewletViewsContribution, WorkbenchContributionInstantiation.BlockStartup); // Register Text File Editor Tracker -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TextFileEditorTracker, LifecyclePhase.Starting); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(TextFileEditorTracker.ID, TextFileEditorTracker, WorkbenchContributionInstantiation.BlockStartup); // Register Text File Save Error Handler -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TextFileSaveErrorHandler, LifecyclePhase.Starting); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(TextFileSaveErrorHandler.ID, TextFileSaveErrorHandler, WorkbenchContributionInstantiation.BlockStartup); // Register uri display for file uris -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(FileUriLabelContribution, LifecyclePhase.Starting); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(FileUriLabelContribution.ID, FileUriLabelContribution, WorkbenchContributionInstantiation.BlockStartup); // Register Workspace Watcher Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceWatcher, LifecyclePhase.Restored); // Register Dirty Files Indicator -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DirtyFilesIndicator, LifecyclePhase.Starting); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(DirtyFilesIndicator.ID, DirtyFilesIndicator, WorkbenchContributionInstantiation.BlockStartup); // Configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); diff --git a/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts b/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts index c17726dec0f..94849cda9f7 100644 --- a/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts +++ b/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts @@ -14,6 +14,9 @@ import { IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/wor import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; export class DirtyFilesIndicator extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.dirtyFilesIndicator'; + private readonly badgeHandle = this._register(new MutableDisposable()); private lastKnownDirtyCount = 0; diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index 03ca0d672ee..e28c6fd1798 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -36,7 +36,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { Registry } from 'vs/platform/registry/common/platform'; import { contrastBorder, ifDefinedThenElse, listInactiveSelectionBackground, registerColor } from 'vs/platform/theme/common/colorRegistry'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { EditorExtensions, EditorsOrder, IEditorFactoryRegistry, IEditorSerializer, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { PANEL_BORDER } from 'vs/workbench/common/theme'; @@ -60,7 +60,6 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; @@ -78,6 +77,9 @@ Registry.as(EditorExtensions.EditorPane).registerEditorPane ); export class InteractiveDocumentContribution extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.interactiveDocument'; + constructor( @INotebookService notebookService: INotebookService, @IEditorResolverService editorResolverService: IEditorResolverService, @@ -175,6 +177,8 @@ export class InteractiveDocumentContribution extends Disposable implements IWork class InteractiveInputContentProvider implements ITextModelContentProvider { + static readonly ID = 'workbench.contrib.interactiveInputContentProvider'; + private readonly _registration: IDisposable; constructor( @@ -209,6 +213,8 @@ function createEditor(resource: URI, instantiationService: IInstantiationService class InteractiveWindowWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution, IWorkingCopyEditorHandler { + static readonly ID = 'workbench.contrib.interactiveWindowWorkingCopyEditorHandler'; + constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, @IWorkingCopyEditorService private readonly _workingCopyEditorService: IWorkingCopyEditorService, @@ -251,9 +257,9 @@ class InteractiveWindowWorkingCopyEditorHandler extends Disposable implements IW const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(InteractiveDocumentContribution, LifecyclePhase.Ready); -workbenchContributionsRegistry.registerWorkbenchContribution(InteractiveInputContentProvider, LifecyclePhase.Ready); -workbenchContributionsRegistry.registerWorkbenchContribution(InteractiveWindowWorkingCopyEditorHandler, LifecyclePhase.Ready); +workbenchContributionsRegistry.registerWorkbenchContribution2(InteractiveDocumentContribution.ID, InteractiveDocumentContribution, WorkbenchContributionInstantiation.BlockRestore); +workbenchContributionsRegistry.registerWorkbenchContribution2(InteractiveInputContentProvider.ID, InteractiveInputContentProvider, WorkbenchContributionInstantiation.BlockRestore); +workbenchContributionsRegistry.registerWorkbenchContribution2(InteractiveWindowWorkingCopyEditorHandler.ID, InteractiveWindowWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); type interactiveEditorInputData = { resource: URI; inputResource: URI; name: string; language: string }; diff --git a/src/vs/workbench/contrib/list/browser/list.contribution.ts b/src/vs/workbench/contrib/list/browser/list.contribution.ts index a2347a33886..9526e40671f 100644 --- a/src/vs/workbench/contrib/list/browser/list.contribution.ts +++ b/src/vs/workbench/contrib/list/browser/list.contribution.ts @@ -5,11 +5,12 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; export class ListContext implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.listContext'; + constructor( @IContextKeyService contextKeyService: IContextKeyService ) { @@ -20,4 +21,4 @@ export class ListContext implements IWorkbenchContribution { } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ListContext, LifecyclePhase.Starting); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(ListContext.ID, ListContext, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/contrib/localHistory/browser/localHistory.contribution.ts b/src/vs/workbench/contrib/localHistory/browser/localHistory.contribution.ts index dda52a6f3a5..bb3efa3ecb4 100644 --- a/src/vs/workbench/contrib/localHistory/browser/localHistory.contribution.ts +++ b/src/vs/workbench/contrib/localHistory/browser/localHistory.contribution.ts @@ -5,9 +5,8 @@ import 'vs/workbench/contrib/localHistory/browser/localHistoryCommands'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LocalHistoryTimeline } from 'vs/workbench/contrib/localHistory/browser/localHistoryTimeline'; // Register Local History Timeline -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LocalHistoryTimeline, LifecyclePhase.Ready); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(LocalHistoryTimeline.ID, LocalHistoryTimeline, WorkbenchContributionInstantiation.BlockRestore); diff --git a/src/vs/workbench/contrib/localHistory/browser/localHistoryTimeline.ts b/src/vs/workbench/contrib/localHistory/browser/localHistoryTimeline.ts index 417b8fa0570..acd3ca80d4a 100644 --- a/src/vs/workbench/contrib/localHistory/browser/localHistoryTimeline.ts +++ b/src/vs/workbench/contrib/localHistory/browser/localHistoryTimeline.ts @@ -27,11 +27,11 @@ import { getVirtualWorkspaceAuthority } from 'vs/platform/workspace/common/virtu export class LocalHistoryTimeline extends Disposable implements IWorkbenchContribution, TimelineProvider { - private static readonly ID = 'timeline.localHistory'; + static readonly ID = 'workbench.contrib.localHistoryTimeline'; private static readonly LOCAL_HISTORY_ENABLED_SETTINGS_KEY = 'workbench.localHistory.enabled'; - readonly id = LocalHistoryTimeline.ID; + readonly id = 'timeline.localHistory'; readonly label = localize('localHistory', "Local History"); @@ -96,7 +96,7 @@ export class LocalHistoryTimeline extends Disposable implements IWorkbenchContri // Re-emit as timeline change event this._onDidChange.fire({ - id: LocalHistoryTimeline.ID, + id: this.id, uri: entry?.workingCopy.resource, reset: true // there is no other way to indicate that items might have been replaced/removed }); @@ -142,7 +142,7 @@ export class LocalHistoryTimeline extends Disposable implements IWorkbenchContri } return { - source: LocalHistoryTimeline.ID, + source: this.id, items }; } @@ -152,7 +152,7 @@ export class LocalHistoryTimeline extends Disposable implements IWorkbenchContri handle: entry.id, label: SaveSourceRegistry.getSourceLabel(entry.source), tooltip: new MarkdownString(`$(history) ${getLocalHistoryDateFormatter().format(entry.timestamp)}\n\n${SaveSourceRegistry.getSourceLabel(entry.source)}`, { supportThemeIcons: true }), - source: LocalHistoryTimeline.ID, + source: this.id, timestamp: entry.timestamp, themeIcon: LOCAL_HISTORY_ICON_ENTRY, contextValue: LOCAL_HISTORY_MENU_CONTEXT_VALUE, diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts index 98912ab7345..74f55531541 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts @@ -9,7 +9,7 @@ import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/co import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { AcceptAllInput1, AcceptAllInput2, AcceptMerge, CompareInput1WithBaseCommand, @@ -96,4 +96,4 @@ Registry Registry .as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(MergeEditorResolverContribution, LifecyclePhase.Starting); + .registerWorkbenchContribution2(MergeEditorResolverContribution.ID, MergeEditorResolverContribution, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index d7f09010e2e..45af28b94f0 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -746,6 +746,8 @@ export class MergeEditorOpenHandlerContribution extends Disposable { export class MergeEditorResolverContribution extends Disposable { + static readonly ID = 'workbench.contrib.mergeEditorResolver'; + constructor( @IEditorResolverService editorResolverService: IEditorResolverService, @IInstantiationService instantiationService: IInstantiationService, diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts index 97f4d5af816..dcdca8a46cc 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts @@ -9,11 +9,10 @@ import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/co import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { MultiDiffEditor } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor'; import { MultiDiffEditorInput, MultiDiffEditorResolverContribution, MultiDiffEditorSerializer } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { CollapseAllAction, ExpandAllAction, GoToFileAction } from './actions'; import { IMultiDiffSourceResolverService, MultiDiffSourceResolverService } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -38,7 +37,7 @@ registerSingleton(IMultiDiffSourceResolverService, MultiDiffSourceResolverServic // Editor Integration Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(MultiDiffEditorResolverContribution, LifecyclePhase.Starting); + .registerWorkbenchContribution2(MultiDiffEditorResolverContribution.ID, MultiDiffEditorResolverContribution, WorkbenchContributionInstantiation.BlockStartup); Registry.as(EditorExtensions.EditorPane) .registerEditorPane( @@ -52,4 +51,4 @@ Registry.as(EditorExtensions.EditorFactory) // SCM integration registerAction2(OpenScmGroupAction); Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(ScmMultiDiffSourceResolverContribution, LifecyclePhase.Starting); + .registerWorkbenchContribution2(ScmMultiDiffSourceResolverContribution.ID, ScmMultiDiffSourceResolverContribution, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index a8f59a63c36..64304bb13bb 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -320,6 +320,9 @@ function computeOptions(configuration: IEditorConfiguration): IDiffEditorOptions } export class MultiDiffEditorResolverContribution extends Disposable { + + static readonly ID = 'workbench.contrib.multiDiffEditorResolver'; + constructor( @IEditorResolverService editorResolverService: IEditorResolverService, @IInstantiationService instantiationService: IInstantiationService, diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts index 3326e502d82..318566cc486 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts @@ -126,6 +126,9 @@ function promiseFromEventState(event: Event, checkState: () => T | false } export class ScmMultiDiffSourceResolverContribution extends Disposable { + + static readonly ID = 'workbench.contrib.scmMultiDiffSourceResolver'; + constructor( @IInstantiationService instantiationService: IInstantiationService, @IMultiDiffSourceResolverService multiDiffSourceResolverService: IMultiDiffSourceResolverService, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts index 47b68f9f10b..ee531fe588e 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts @@ -5,9 +5,8 @@ import { localize, localize2 } from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { NOTEBOOK_CELL_EDITABLE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { cellRangeToViewCells, expandCellRangesWithHiddenCells, getNotebookEditorFromEditorPane, ICellViewModel, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -274,6 +273,8 @@ export function runCutCells(accessor: ServicesAccessor, editor: INotebookEditor, export class NotebookClipboardContribution extends Disposable { + static readonly ID = 'workbench.contrib.notebookClipboard'; + constructor(@IEditorService private readonly _editorService: IEditorService) { super(); @@ -406,7 +407,7 @@ export class NotebookClipboardContribution extends Disposable { } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(NotebookClipboardContribution, LifecyclePhase.Ready); +workbenchContributionsRegistry.registerWorkbenchContribution2(NotebookClipboardContribution.ID, NotebookClipboardContribution, WorkbenchContributionInstantiation.BlockRestore); const COPY_CELL_COMMAND_ID = 'notebook.cell.copy'; const CUT_CELL_COMMAND_ID = 'notebook.cell.cut'; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider.ts b/src/vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider.ts index 157cdfec73c..f7e3c8c3b2d 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { IMarkerListProvider, MarkerList, IMarkerNavigationService } from 'vs/editor/contrib/gotoError/browser/markerNavigationService'; import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; @@ -20,6 +19,8 @@ import { isEqual } from 'vs/base/common/resources'; class MarkerListProvider implements IMarkerListProvider { + static readonly ID = 'workbench.contrib.markerListProvider'; + private readonly _dispoables: IDisposable; constructor( @@ -99,6 +100,6 @@ class NotebookMarkerDecorationContribution extends Disposable implements INotebo Registry .as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(MarkerListProvider, LifecyclePhase.Ready); + .registerWorkbenchContribution2(MarkerListProvider.ID, MarkerListProvider, WorkbenchContributionInstantiation.BlockRestore); registerNotebookContribution(NotebookMarkerDecorationContribution.id, NotebookMarkerDecorationContribution); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts b/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts index 42089a40651..2bfb3ffc06e 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts @@ -11,8 +11,7 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; export enum NotebookProfileType { default = 'default', @@ -92,6 +91,9 @@ function isSetProfileArgs(args: unknown): args is ISetProfileArgs { } export class NotebookProfileContribution extends Disposable { + + static readonly ID = 'workbench.contrib.notebookProfile'; + constructor(@IConfigurationService configService: IConfigurationService, @IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService) { super(); @@ -125,5 +127,5 @@ export class NotebookProfileContribution extends Disposable { } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(NotebookProfileContribution, LifecyclePhase.Ready); +workbenchContributionsRegistry.registerWorkbenchContribution2(NotebookProfileContribution.ID, NotebookProfileContribution, WorkbenchContributionInstantiation.BlockRestore); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts b/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts index 4a74f442f94..8d7f17c2b75 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CellEditState, getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -15,6 +14,8 @@ import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod class NotebookUndoRedoContribution extends Disposable { + static readonly ID = 'workbench.contrib.notebookUndoRedo'; + constructor(@IEditorService private readonly _editorService: IEditorService) { super(); @@ -65,4 +66,4 @@ class NotebookUndoRedoContribution extends Disposable { } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(NotebookUndoRedoContribution, LifecyclePhase.Ready); +workbenchContributionsRegistry.registerWorkbenchContribution2(NotebookUndoRedoContribution.ID, NotebookUndoRedoContribution, WorkbenchContributionInstantiation.BlockRestore); diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index e7dc3cc4f97..79ddeebfc89 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -22,7 +22,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { IEditorSerializer, IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; @@ -223,6 +223,9 @@ Registry.as(EditorExtensions.EditorFactory).registerEdit ); export class NotebookContribution extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.notebook'; + private _uriComparisonKeyComputer?: IDisposable; constructor( @@ -285,6 +288,8 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri class CellContentProvider implements ITextModelContentProvider { + static readonly ID = 'workbench.contrib.cellContentProvider'; + private readonly _registration: IDisposable; constructor( @@ -356,6 +361,9 @@ class CellContentProvider implements ITextModelContentProvider { } class CellInfoContentProvider { + + static readonly ID = 'workbench.contrib.cellInfoContentProvider'; + private readonly _disposables: IDisposable[] = []; constructor( @@ -531,6 +539,9 @@ class CellInfoContentProvider { } class RegisterSchemasContribution extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.registerCellSchemas'; + constructor() { super(); this.registerMetadataSchemas(); @@ -557,6 +568,8 @@ class RegisterSchemasContribution extends Disposable implements IWorkbenchContri class NotebookEditorManager implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.notebookEditorManager'; + private readonly _disposables = new DisposableStore(); constructor( @@ -604,6 +617,8 @@ class NotebookEditorManager implements IWorkbenchContribution { class SimpleNotebookWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution, IWorkingCopyEditorHandler { + static readonly ID = 'workbench.contrib.simpleNotebookWorkingCopyEditorHandler'; + constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, @IWorkingCopyEditorService private readonly _workingCopyEditorService: IWorkingCopyEditorService, @@ -658,6 +673,8 @@ class SimpleNotebookWorkingCopyEditorHandler extends Disposable implements IWork class NotebookLanguageSelectorScoreRefine { + static readonly ID = 'workbench.contrib.notebookLanguageSelectorScoreRefine'; + constructor( @INotebookService private readonly _notebookService: INotebookService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, @@ -713,13 +730,13 @@ class NotebookAccessibleViewContribution extends Disposable { } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(NotebookContribution, LifecyclePhase.Starting); -workbenchContributionsRegistry.registerWorkbenchContribution(CellContentProvider, LifecyclePhase.Starting); -workbenchContributionsRegistry.registerWorkbenchContribution(CellInfoContentProvider, LifecyclePhase.Starting); -workbenchContributionsRegistry.registerWorkbenchContribution(RegisterSchemasContribution, LifecyclePhase.Starting); -workbenchContributionsRegistry.registerWorkbenchContribution(NotebookEditorManager, LifecyclePhase.Ready); -workbenchContributionsRegistry.registerWorkbenchContribution(NotebookLanguageSelectorScoreRefine, LifecyclePhase.Ready); -workbenchContributionsRegistry.registerWorkbenchContribution(SimpleNotebookWorkingCopyEditorHandler, LifecyclePhase.Ready); +workbenchContributionsRegistry.registerWorkbenchContribution2(NotebookContribution.ID, NotebookContribution, WorkbenchContributionInstantiation.BlockStartup); +workbenchContributionsRegistry.registerWorkbenchContribution2(CellContentProvider.ID, CellContentProvider, WorkbenchContributionInstantiation.BlockStartup); +workbenchContributionsRegistry.registerWorkbenchContribution2(CellInfoContentProvider.ID, CellInfoContentProvider, WorkbenchContributionInstantiation.BlockStartup); +workbenchContributionsRegistry.registerWorkbenchContribution2(RegisterSchemasContribution.ID, RegisterSchemasContribution, WorkbenchContributionInstantiation.BlockStartup); +workbenchContributionsRegistry.registerWorkbenchContribution2(NotebookEditorManager.ID, NotebookEditorManager, WorkbenchContributionInstantiation.BlockRestore); +workbenchContributionsRegistry.registerWorkbenchContribution2(NotebookLanguageSelectorScoreRefine.ID, NotebookLanguageSelectorScoreRefine, WorkbenchContributionInstantiation.BlockRestore); +workbenchContributionsRegistry.registerWorkbenchContribution2(SimpleNotebookWorkingCopyEditorHandler.ID, SimpleNotebookWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookAccessibilityHelpContribution, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookAccessibleViewContribution, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookVariables, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts index 6530ddb0781..ed9a0c87e30 100644 --- a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts +++ b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts @@ -7,9 +7,8 @@ import * as nls from 'vs/nls'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/browser/statusbar'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { parseKeyboardLayoutDescription, areKeyboardLayoutsEqual, getKeyboardLayoutId, IKeyboardLayoutService, IKeyboardLayoutInfo } from 'vs/platform/keyboardLayout/common/keyboardLayout'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { KEYBOARD_LAYOUT_OPEN_PICKER } from 'vs/workbench/contrib/preferences/common/preferences'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { QuickPickInput, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; @@ -23,6 +22,9 @@ import { IEditorPane } from 'vs/workbench/common/editor'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; export class KeyboardLayoutPickerContribution extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.keyboardLayoutPicker'; + private readonly pickerElement = this._register(new MutableDisposable()); constructor( @@ -80,7 +82,7 @@ export class KeyboardLayoutPickerContribution extends Disposable implements IWor } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(KeyboardLayoutPickerContribution, LifecyclePhase.Starting); +workbenchContributionsRegistry.registerWorkbenchContribution2(KeyboardLayoutPickerContribution.ID, KeyboardLayoutPickerContribution, WorkbenchContributionInstantiation.BlockStartup); interface LayoutQuickPickItem extends IQuickPickItem { layout: IKeyboardLayoutInfo; diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 44ef443bd84..e42c77e4ef9 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -24,7 +24,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry, IEditorSerializer } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ResourceContextKey, RemoteNameContext, WorkbenchStateContext } from 'vs/workbench/common/contextkeys'; @@ -167,6 +167,8 @@ function sanitizeOpenSettingsArgs(args: any): IOpenSettingsActionOptions { class PreferencesActionsContribution extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.preferencesActions'; + constructor( @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @@ -1288,8 +1290,8 @@ class SettingsEditorTitleContribution extends Disposable implements IWorkbenchCo } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(PreferencesActionsContribution, LifecyclePhase.Starting); -workbenchContributionsRegistry.registerWorkbenchContribution(PreferencesContribution, LifecyclePhase.Starting); +workbenchContributionsRegistry.registerWorkbenchContribution2(PreferencesActionsContribution.ID, PreferencesActionsContribution, WorkbenchContributionInstantiation.BlockStartup); +workbenchContributionsRegistry.registerWorkbenchContribution2(PreferencesContribution.ID, PreferencesContribution, WorkbenchContributionInstantiation.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(SettingsEditorTitleContribution, LifecyclePhase.Restored); registerEditorContribution(SettingsEditorContribution.ID, SettingsEditorContribution, EditorContributionInstantiation.AfterFirstRender); diff --git a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts index ad60f18f80f..877d449605c 100644 --- a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts +++ b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts @@ -28,6 +28,9 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c const schemaRegistry = Registry.as(JSONContributionRegistry.Extensions.JSONContribution); export class PreferencesContribution implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.preferences'; + private editorOpeningListener: IDisposable | undefined; private settingsListener: IDisposable; diff --git a/src/vs/workbench/contrib/remote/browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/browser/remote.contribution.ts index 63e7f2df0b4..195277a6f9a 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { ShowCandidateContribution } from 'vs/workbench/contrib/remote/browser/showCandidate'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -13,10 +13,10 @@ import { RemoteStatusIndicator } from 'vs/workbench/contrib/remote/browser/remot import { AutomaticPortForwarding, ForwardedPortsView, PortRestore } from 'vs/workbench/contrib/remote/browser/remoteExplorer'; const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(ShowCandidateContribution, LifecyclePhase.Ready); -workbenchContributionsRegistry.registerWorkbenchContribution(TunnelFactoryContribution, LifecyclePhase.Ready); +workbenchContributionsRegistry.registerWorkbenchContribution2(ShowCandidateContribution.ID, ShowCandidateContribution, WorkbenchContributionInstantiation.BlockRestore); +workbenchContributionsRegistry.registerWorkbenchContribution2(TunnelFactoryContribution.ID, TunnelFactoryContribution, WorkbenchContributionInstantiation.BlockRestore); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually); -workbenchContributionsRegistry.registerWorkbenchContribution(RemoteStatusIndicator, LifecyclePhase.Starting); +workbenchContributionsRegistry.registerWorkbenchContribution2(RemoteStatusIndicator.ID, RemoteStatusIndicator, WorkbenchContributionInstantiation.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(ForwardedPortsView, LifecyclePhase.Restored); workbenchContributionsRegistry.registerWorkbenchContribution(PortRestore, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(AutomaticPortForwarding, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index ee80c41f05b..f362b61aa1b 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -66,6 +66,8 @@ interface RemoteExtensionMetadata { export class RemoteStatusIndicator extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.remoteStatusIndicator'; + private static readonly REMOTE_ACTIONS_COMMAND_ID = 'workbench.action.remote.showMenu'; private static readonly CLOSE_REMOTE_COMMAND_ID = 'workbench.action.remote.close'; private static readonly SHOW_CLOSE_REMOTE_COMMAND_ID = !isWeb; // web does not have a "Close Remote" command diff --git a/src/vs/workbench/contrib/remote/browser/showCandidate.ts b/src/vs/workbench/contrib/remote/browser/showCandidate.ts index 47f2f690e33..fdb6c6ee007 100644 --- a/src/vs/workbench/contrib/remote/browser/showCandidate.ts +++ b/src/vs/workbench/contrib/remote/browser/showCandidate.ts @@ -10,6 +10,9 @@ import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remo import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel'; export class ShowCandidateContribution extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.showPortCandidate'; + constructor( @IRemoteExplorerService remoteExplorerService: IRemoteExplorerService, @IBrowserWorkbenchEnvironmentService environmentService: IBrowserWorkbenchEnvironmentService, diff --git a/src/vs/workbench/contrib/remote/browser/tunnelFactory.ts b/src/vs/workbench/contrib/remote/browser/tunnelFactory.ts index 74a94d7c21b..0447f1a07b7 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelFactory.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelFactory.ts @@ -17,6 +17,8 @@ import { forwardedPortsViewEnabled } from 'vs/workbench/services/remote/common/t export class TunnelFactoryContribution extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.tunnelFactory'; + constructor( @ITunnelService tunnelService: ITunnelService, @IBrowserWorkbenchEnvironmentService environmentService: IBrowserWorkbenchEnvironmentService, diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 6108eeacda4..4b83aa17a35 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILabelService, ResourceLabelFormatting } from 'vs/platform/label/common/label'; @@ -31,6 +31,9 @@ import { DownloadServiceChannel } from 'vs/platform/download/common/downloadIpc' import { RemoteLoggerChannelClient } from 'vs/platform/log/common/logIpc'; export class LabelContribution implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.remoteLabel'; + constructor( @ILabelService private readonly labelService: ILabelService, @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService) { @@ -80,6 +83,8 @@ class RemoteChannelsContribution extends Disposable implements IWorkbenchContrib class RemoteInvalidWorkspaceDetector extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.remoteInvalidWorkspaceDetector'; + constructor( @IFileService private readonly fileService: IFileService, @IDialogService private readonly dialogService: IDialogService, @@ -229,9 +234,9 @@ class InitialRemoteConnectionHealthContribution implements IWorkbenchContributio } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(LabelContribution, LifecyclePhase.Starting); +workbenchContributionsRegistry.registerWorkbenchContribution2(LabelContribution.ID, LabelContribution, WorkbenchContributionInstantiation.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteChannelsContribution, LifecyclePhase.Restored); -workbenchContributionsRegistry.registerWorkbenchContribution(RemoteInvalidWorkspaceDetector, LifecyclePhase.Starting); +workbenchContributionsRegistry.registerWorkbenchContribution2(RemoteInvalidWorkspaceDetector.ID, RemoteInvalidWorkspaceDetector, WorkbenchContributionInstantiation.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(InitialRemoteConnectionHealthContribution, LifecyclePhase.Restored); const enableDiagnostics = true; diff --git a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts index 7b7c23ae0f5..b9d5e5a6a02 100644 --- a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts @@ -10,7 +10,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILabelService } from 'vs/platform/label/common/label'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -86,6 +86,9 @@ class RemoteExtensionHostEnvironmentUpdater implements IWorkbenchContribution { } class RemoteTelemetryEnablementUpdater extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.remoteTelemetryEnablementUpdater'; + constructor( @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, @IConfigurationService private readonly configurationService: IConfigurationService @@ -108,6 +111,9 @@ class RemoteTelemetryEnablementUpdater extends Disposable implements IWorkbenchC class RemoteEmptyWorkbenchPresentation extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.remoteEmptyWorkbenchPresentation'; + constructor( @INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, @@ -145,6 +151,8 @@ class RemoteEmptyWorkbenchPresentation extends Disposable implements IWorkbenchC */ class WSLContextKeyInitializer extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.wslContextKeyInitializer'; + constructor( @IContextKeyService contextKeyService: IContextKeyService, @INativeHostService nativeHostService: INativeHostService, @@ -178,10 +186,10 @@ class WSLContextKeyInitializer extends Disposable implements IWorkbenchContribut const workbenchContributionsRegistry = Registry.as(WorkbenchContributionsExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentDiagnosticListener, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteExtensionHostEnvironmentUpdater, LifecyclePhase.Eventually); -workbenchContributionsRegistry.registerWorkbenchContribution(RemoteTelemetryEnablementUpdater, LifecyclePhase.Ready); -workbenchContributionsRegistry.registerWorkbenchContribution(RemoteEmptyWorkbenchPresentation, LifecyclePhase.Ready); +workbenchContributionsRegistry.registerWorkbenchContribution2(RemoteTelemetryEnablementUpdater.ID, RemoteTelemetryEnablementUpdater, WorkbenchContributionInstantiation.BlockRestore); +workbenchContributionsRegistry.registerWorkbenchContribution2(RemoteEmptyWorkbenchPresentation.ID, RemoteEmptyWorkbenchPresentation, WorkbenchContributionInstantiation.BlockRestore); if (isWindows) { - workbenchContributionsRegistry.registerWorkbenchContribution(WSLContextKeyInitializer, LifecyclePhase.Ready); + workbenchContributionsRegistry.registerWorkbenchContribution2(WSLContextKeyInitializer.ID, WSLContextKeyInitializer, WorkbenchContributionInstantiation.BlockRestore); } Registry.as(ConfigurationExtensions.Configuration) diff --git a/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchContributions.ts b/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchContributions.ts index a1ac98e26b2..2fe6e0d39d0 100644 --- a/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchContributions.ts +++ b/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchContributions.ts @@ -3,14 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ReplacePreviewContentProvider } from 'vs/workbench/contrib/search/browser/replaceService'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { INotebookSearchService } from 'vs/workbench/contrib/search/common/notebookSearch'; import { NotebookSearchService } from 'vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService'; export function registerContributions(): void { registerSingleton(INotebookSearchService, NotebookSearchService, InstantiationType.Delayed); - Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ReplacePreviewContentProvider, LifecyclePhase.Starting); } diff --git a/src/vs/workbench/contrib/search/browser/replaceContributions.ts b/src/vs/workbench/contrib/search/browser/replaceContributions.ts index 1fbf571ff2b..55f97965231 100644 --- a/src/vs/workbench/contrib/search/browser/replaceContributions.ts +++ b/src/vs/workbench/contrib/search/browser/replaceContributions.ts @@ -6,10 +6,9 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; import { ReplaceService, ReplacePreviewContentProvider } from 'vs/workbench/contrib/search/browser/replaceService'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; export function registerContributions(): void { registerSingleton(IReplaceService, ReplaceService, InstantiationType.Delayed); - Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ReplacePreviewContentProvider, LifecyclePhase.Starting); + Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(ReplacePreviewContentProvider.ID, ReplacePreviewContentProvider, WorkbenchContributionInstantiation.BlockStartup); } diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index 3f7fe26fa79..186983b5a8a 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -42,6 +42,8 @@ const toFileResource = (replaceResource: URI): URI => { export class ReplacePreviewContentProvider implements ITextModelContentProvider, IWorkbenchContribution { + static readonly ID = 'workbench.contrib.replacePreviewContentProvider'; + constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @ITextModelService private readonly textModelResolverService: ITextModelService diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index cb03bc03f31..b926098f8c3 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -16,7 +16,7 @@ import { Extensions as QuickAccessExtensions, IQuickAccessRegistry } from 'vs/pl import { Registry } from 'vs/platform/registry/common/platform'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { defaultQuickAccessContextKeyValue } from 'vs/workbench/browser/quickaccess'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { Extensions as ViewExtensions, IViewContainersRegistry, IViewDescriptor, IViewDescriptorService, IViewsRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { GotoSymbolQuickAccessProvider } from 'vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess'; import { AnythingQuickAccessProvider } from 'vs/workbench/contrib/search/browser/anythingQuickAccess'; @@ -28,7 +28,6 @@ import { registerContributions as searchWidgetContributions } from 'vs/workbench import { SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/symbolsQuickAccess'; import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { ISearchViewModelWorkbenchService, SearchViewModelWorkbenchService } from 'vs/workbench/contrib/search/browser/searchModel'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { SearchSortOrder, SEARCH_EXCLUDE_CONFIG, VIEWLET_ID, ViewMode, VIEW_ID } from 'vs/workbench/services/search/common/search'; import { Extensions, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -87,6 +86,9 @@ Registry.as(ViewExtensions.ViewsRegistry).registerViews([viewDes // Migrate search location setting to new model class RegisterSearchViewContribution implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.registerSearchView'; + constructor( @IConfigurationService configurationService: IConfigurationService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService @@ -99,7 +101,7 @@ class RegisterSearchViewContribution implements IWorkbenchContribution { .registerConfigurationMigrations([{ key: 'search.location', migrateFn: (value: any) => ({ value: undefined }) }]); } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(RegisterSearchViewContribution, LifecyclePhase.Starting); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(RegisterSearchViewContribution.ID, RegisterSearchViewContribution, WorkbenchContributionInstantiation.BlockStartup); // Register Quick Access Handler const quickAccessRegistry = Registry.as(QuickAccessExtensions.Quickaccess); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 44ac96842ef..fdb1811ac3f 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -16,10 +16,9 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { IEditorSerializer, IEditorFactoryRegistry, EditorExtensions, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; @@ -73,6 +72,9 @@ Registry.as(EditorExtensions.EditorPane).registerEditorPane //#region Startup Contribution class SearchEditorContribution implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.searchEditor'; + constructor( @IEditorResolverService editorResolverService: IEditorResolverService, @IInstantiationService instantiationService: IInstantiationService, @@ -99,7 +101,7 @@ class SearchEditorContribution implements IWorkbenchContribution { } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(SearchEditorContribution, LifecyclePhase.Starting); +workbenchContributionsRegistry.registerWorkbenchContribution2(SearchEditorContribution.ID, SearchEditorContribution, WorkbenchContributionInstantiation.BlockStartup); //#endregion //#region Input Serializer @@ -568,6 +570,8 @@ registerAction2(class OpenSearchEditorAction extends Action2 { //#region Search Editor Working Copy Editor Handler class SearchEditorWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution, IWorkingCopyEditorHandler { + static readonly ID = 'workbench.contrib.searchEditorWorkingCopyEditorHandler'; + constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkingCopyEditorService workingCopyEditorService: IWorkingCopyEditorService, @@ -597,5 +601,5 @@ class SearchEditorWorkingCopyEditorHandler extends Disposable implements IWorkbe } } -workbenchContributionsRegistry.registerWorkbenchContribution(SearchEditorWorkingCopyEditorHandler, LifecyclePhase.Ready); +workbenchContributionsRegistry.registerWorkbenchContribution2(SearchEditorWorkingCopyEditorHandler.ID, SearchEditorWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); //#endregion diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts index 2b7716dd718..06779c99e21 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts @@ -39,6 +39,9 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; export class LocalTerminalBackendContribution implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.localTerminalBackend'; + constructor( @IInstantiationService instantiationService: IInstantiationService, @ITerminalInstanceService terminalInstanceService: ITerminalInstanceService diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts index 556b5e818eb..dac77a57959 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts @@ -7,7 +7,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; import { Registry } from 'vs/platform/registry/common/platform'; import { ILocalPtyService, TerminalIpcChannels } from 'vs/platform/terminal/common/terminal'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalNativeContribution } from 'vs/workbench/contrib/terminal/electron-sandbox/terminalNativeContribution'; import { ElectronTerminalProfileResolverService } from 'vs/workbench/contrib/terminal/electron-sandbox/terminalProfileResolverService'; @@ -23,5 +23,5 @@ const workbenchRegistry = Registry.as(Workbench // This contribution needs to be active during the Startup phase to be available when a remote resolver tries to open a local // terminal while connecting to the remote. -workbenchRegistry.registerWorkbenchContribution(LocalTerminalBackendContribution, LifecyclePhase.Starting); +workbenchRegistry.registerWorkbenchContribution2(LocalTerminalBackendContribution.ID, LocalTerminalBackendContribution, WorkbenchContributionInstantiation.BlockStartup); workbenchRegistry.registerWorkbenchContribution(TerminalNativeContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts index a4bdd82f91e..a606a322302 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, Extensions, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { UserDataProfilesWorkbenchContribution } from 'vs/workbench/contrib/userDataProfile/browser/userDataProfile'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import './userDataProfileActions'; import { UserDataProfilePreviewContribution } from 'vs/workbench/contrib/userDataProfile/browser/userDataProfilePreview'; const workbenchRegistry = Registry.as(Extensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(UserDataProfilesWorkbenchContribution, LifecyclePhase.Ready); +workbenchRegistry.registerWorkbenchContribution2(UserDataProfilesWorkbenchContribution.ID, UserDataProfilesWorkbenchContribution, WorkbenchContributionInstantiation.BlockRestore); workbenchRegistry.registerWorkbenchContribution(UserDataProfilePreviewContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index bf077946dcb..b849286fa54 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -28,6 +28,8 @@ type IProfileTemplateQuickPickItem = IQuickPickItem & IProfileTemplateInfo; export class UserDataProfilesWorkbenchContribution extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.userDataProfiles'; + private readonly currentProfileContext: IContextKey; private readonly isCurrentProfileTransientContext: IContextKey; private readonly hasProfilesContext: IContextKey; diff --git a/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts index dd126556bca..ed103f415a3 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { IUserDataSyncUtilService, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync'; import { Registry } from 'vs/platform/registry/common/platform'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; import { localize, localize2 } from 'vs/nls'; @@ -22,6 +21,8 @@ import { Disposable } from 'vs/base/common/lifecycle'; class UserDataSyncServicesContribution extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.userDataSyncServices'; + constructor( @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @ISharedProcessService sharedProcessService: ISharedProcessService, @@ -32,7 +33,7 @@ class UserDataSyncServicesContribution extends Disposable implements IWorkbenchC } const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(UserDataSyncServicesContribution, LifecyclePhase.Starting); +workbenchRegistry.registerWorkbenchContribution2(UserDataSyncServicesContribution.ID, UserDataSyncServicesContribution, WorkbenchContributionInstantiation.BlockStartup); registerAction2(class OpenSyncBackupsFolder extends Action2 { constructor() { diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts index 8d49c0221a1..2c5caf3070f 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts @@ -11,11 +11,10 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetAction, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand } from './webviewCommands'; import { WebviewEditor } from './webviewEditor'; import { WebviewInput } from './webviewEditorInput'; @@ -30,6 +29,8 @@ import { IWebviewWorkbenchService, WebviewEditorService } from './webviewWorkben class WebviewPanelContribution extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.webviewPanel'; + constructor( @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, ) { @@ -82,7 +83,7 @@ class WebviewPanelContribution extends Disposable implements IWorkbenchContribut } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(WebviewPanelContribution, LifecyclePhase.Starting); +workbenchContributionsRegistry.registerWorkbenchContribution2(WebviewPanelContribution.ID, WebviewPanelContribution, WorkbenchContributionInstantiation.BlockStartup); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer( WebviewEditorInputSerializer.ID, diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index 8809c99805f..d8fd9800144 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -14,7 +14,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { Severity } from 'vs/platform/notification/common/notification'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService, IWorkspaceTrustRequestService, workspaceTrustToString, WorkspaceTrustUriResponse } from 'vs/platform/workspace/common/workspaceTrust'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Codicon } from 'vs/base/common/codicons'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -85,6 +85,9 @@ Registry.as(WorkbenchExtensions.Workbench).regi */ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.workspaceTrustRequestHandler'; + constructor( @IDialogService private readonly dialogService: IDialogService, @ICommandService private readonly commandService: ICommandService, @@ -633,7 +636,7 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon //#endregion } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceTrustRequestHandler, LifecyclePhase.Ready); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(WorkspaceTrustRequestHandler.ID, WorkspaceTrustRequestHandler, WorkbenchContributionInstantiation.BlockRestore); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceTrustUXHandler, LifecyclePhase.Restored); diff --git a/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts b/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts index 87af9fe6ab4..95faf4d10fe 100644 --- a/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts +++ b/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts @@ -12,16 +12,18 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INativeHostService } from 'vs/platform/native/common/native'; import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IDialogsModel, IDialogViewItem } from 'vs/workbench/common/dialogs'; import { BrowserDialogHandler } from 'vs/workbench/browser/parts/dialogs/dialogHandler'; import { NativeDialogHandler } from 'vs/workbench/electron-sandbox/parts/dialogs/dialogHandler'; import { DialogService } from 'vs/workbench/services/dialogs/common/dialogService'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.dialogHandler'; + private nativeImpl: IDialogHandler; private browserImpl: IDialogHandler; @@ -107,4 +109,4 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC } const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(DialogHandlerContribution, LifecyclePhase.Starting); +workbenchRegistry.registerWorkbenchContribution2(DialogHandlerContribution.ID, DialogHandlerContribution, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts b/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts index 05aa8e55bc2..130c5561855 100644 --- a/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts @@ -13,8 +13,7 @@ import { AccessibilityService } from 'vs/platform/accessibility/browser/accessib import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { INativeHostService } from 'vs/platform/native/common/native'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; @@ -71,6 +70,9 @@ registerSingleton(IAccessibilityService, NativeAccessibilityService, Instantiati // On linux we do not automatically detect that a screen reader is detected, thus we have to implicitly notify the renderer to enable accessibility when user configures it in settings class LinuxAccessibilityContribution implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.linuxAccessibility'; + constructor( @IJSONEditingService jsonEditingService: IJSONEditingService, @IAccessibilityService accessibilityService: IAccessibilityService, @@ -87,5 +89,5 @@ class LinuxAccessibilityContribution implements IWorkbenchContribution { } if (isLinux) { - Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LinuxAccessibilityContribution, LifecyclePhase.Ready); + Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(LinuxAccessibilityContribution.ID, LinuxAccessibilityContribution, WorkbenchContributionInstantiation.BlockRestore); } diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index c15c42669a3..5a8c05e7eca 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -27,7 +27,7 @@ import { mark } from 'vs/base/common/performance'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -1326,6 +1326,8 @@ class ResetConfigurationDefaultsOverridesCache extends Disposable implements IWo class UpdateExperimentalSettingsDefaults extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.updateExperimentalSettingsDefaults'; + private readonly processedExperimentalSettings = new Set(); private readonly configurationRegistry = Registry.as(Extensions.Configuration); @@ -1365,7 +1367,7 @@ class UpdateExperimentalSettingsDefaults extends Disposable implements IWorkbenc const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(RegisterConfigurationSchemasContribution, LifecyclePhase.Restored); workbenchContributionsRegistry.registerWorkbenchContribution(ResetConfigurationDefaultsOverridesCache, LifecyclePhase.Eventually); -workbenchContributionsRegistry.registerWorkbenchContribution(UpdateExperimentalSettingsDefaults, LifecyclePhase.Ready); +workbenchContributionsRegistry.registerWorkbenchContribution2(UpdateExperimentalSettingsDefaults.ID, UpdateExperimentalSettingsDefaults, WorkbenchContributionInstantiation.BlockRestore); const configurationRegistry = Registry.as(Extensions.Configuration); configurationRegistry.registerConfiguration({ diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index c6e6bc77fb7..3889556e786 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -20,8 +20,7 @@ import { ActivationKind, IExtensionService, toExtensionDescription } from 'vs/wo import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IWorkbenchContribution, Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; @@ -400,6 +399,8 @@ registerSingleton(IExtensionUrlHandler, ExtensionUrlHandler, InstantiationType.E */ class ExtensionUrlBootstrapHandler implements IWorkbenchContribution, IURLHandler { + static readonly ID = 'workbench.contrib.extensionUrlBootstrapHandler'; + private static _cache: [URI, IOpenURLOptions | undefined][] = []; private static disposable: IDisposable; @@ -426,7 +427,7 @@ class ExtensionUrlBootstrapHandler implements IWorkbenchContribution, IURLHandle } const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(ExtensionUrlBootstrapHandler, LifecyclePhase.Ready); +workbenchRegistry.registerWorkbenchContribution2(ExtensionUrlBootstrapHandler.ID, ExtensionUrlBootstrapHandler, WorkbenchContributionInstantiation.BlockRestore); class ManageAuthorizedExtensionURIsAction extends Action2 { diff --git a/src/vs/workbench/services/remote/browser/remoteAgentService.ts b/src/vs/workbench/services/remote/browser/remoteAgentService.ts index 0170a861289..68e0f1ab397 100644 --- a/src/vs/workbench/services/remote/browser/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/browser/remoteAgentService.ts @@ -14,9 +14,8 @@ import { ILogService } from 'vs/platform/log/common/log'; import { Severity } from 'vs/platform/notification/common/notification'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; @@ -37,6 +36,8 @@ export class RemoteAgentService extends AbstractRemoteAgentService implements IR class RemoteConnectionFailureNotificationContribution implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.browserRemoteConnectionFailureNotification'; + constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, @IDialogService private readonly _dialogService: IDialogService, @@ -68,4 +69,4 @@ class RemoteConnectionFailureNotificationContribution implements IWorkbenchContr } const workbenchRegistry = Registry.as(Extensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(RemoteConnectionFailureNotificationContribution, LifecyclePhase.Ready); +workbenchRegistry.registerWorkbenchContribution2(RemoteConnectionFailureNotificationContribution.ID, RemoteConnectionFailureNotificationContribution, WorkbenchContributionInstantiation.BlockRestore); diff --git a/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts b/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts index 550b988c939..0fd96b56dc2 100644 --- a/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts @@ -13,8 +13,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INativeHostService } from 'vs/platform/native/common/native'; import { URI } from 'vs/base/common/uri'; @@ -38,6 +37,8 @@ export class RemoteAgentService extends AbstractRemoteAgentService implements IR class RemoteConnectionFailureNotificationContribution implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.nativeRemoteConnectionFailureNotification'; + constructor( @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @INotificationService notificationService: INotificationService, @@ -93,4 +94,4 @@ class RemoteConnectionFailureNotificationContribution implements IWorkbenchContr } const workbenchRegistry = Registry.as(Extensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(RemoteConnectionFailureNotificationContribution, LifecyclePhase.Ready); +workbenchRegistry.registerWorkbenchContribution2(RemoteConnectionFailureNotificationContribution.ID, RemoteConnectionFailureNotificationContribution, WorkbenchContributionInstantiation.BlockRestore); diff --git a/src/vs/workbench/services/textMate/browser/textMateTokenizationFeature.contribution.ts b/src/vs/workbench/services/textMate/browser/textMateTokenizationFeature.contribution.ts index f2584c7a722..d1cb9d068fb 100644 --- a/src/vs/workbench/services/textMate/browser/textMateTokenizationFeature.contribution.ts +++ b/src/vs/workbench/services/textMate/browser/textMateTokenizationFeature.contribution.ts @@ -7,13 +7,15 @@ import { registerSingleton, InstantiationType } from 'vs/platform/instantiation/ import { ITextMateTokenizationService } from 'vs/workbench/services/textMate/browser/textMateTokenizationFeature'; import { TextMateTokenizationFeature } from 'vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; /** * Makes sure the ITextMateTokenizationService is instantiated */ class TextMateTokenizationInstantiator implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.textMateTokenizationInstantiator'; + constructor( @ITextMateTokenizationService _textMateTokenizationService: ITextMateTokenizationService ) { } @@ -22,4 +24,4 @@ class TextMateTokenizationInstantiator implements IWorkbenchContribution { registerSingleton(ITextMateTokenizationService, TextMateTokenizationFeature, InstantiationType.Eager); const workbenchRegistry = Registry.as(Extensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(TextMateTokenizationInstantiator, LifecyclePhase.Ready); +workbenchRegistry.registerWorkbenchContribution2(TextMateTokenizationInstantiator.ID, TextMateTokenizationInstantiator, WorkbenchContributionInstantiation.BlockRestore); diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts index 3674546202b..f786bd25f04 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts @@ -86,6 +86,8 @@ export class UntitledTextEditorInputSerializer implements IEditorSerializer { export class UntitledTextEditorWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution, IWorkingCopyEditorHandler { + static readonly ID = 'workbench.contrib.untitledTextEditorWorkingCopyEditorHandler'; + constructor( @IWorkingCopyEditorService workingCopyEditorService: IWorkingCopyEditorService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, diff --git a/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts index 14f99f72911..057a70bb3b4 100644 --- a/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts @@ -12,8 +12,7 @@ import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/com import { joinPath } from 'vs/base/common/resources'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { BrowserWorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/browser/workingCopyBackupTracker'; export class BrowserWorkingCopyBackupService extends WorkingCopyBackupService { @@ -32,4 +31,4 @@ export class BrowserWorkingCopyBackupService extends WorkingCopyBackupService { registerSingleton(IWorkingCopyBackupService, BrowserWorkingCopyBackupService, InstantiationType.Eager); // Register Backup Tracker -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BrowserWorkingCopyBackupTracker, LifecyclePhase.Starting); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(BrowserWorkingCopyBackupTracker.ID, BrowserWorkingCopyBackupTracker, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts index 2b21497e1bc..29ca48be967 100644 --- a/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts @@ -16,6 +16,8 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor export class BrowserWorkingCopyBackupTracker extends WorkingCopyBackupTracker implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.browserWorkingCopyBackupTracker'; + constructor( @IWorkingCopyBackupService workingCopyBackupService: IWorkingCopyBackupService, @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, diff --git a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts index 5e597eaa2bc..ea764cf379c 100644 --- a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts @@ -12,8 +12,8 @@ import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { NativeWorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker'; export class NativeWorkingCopyBackupService extends WorkingCopyBackupService { @@ -42,4 +42,4 @@ export class NativeWorkingCopyBackupService extends WorkingCopyBackupService { registerSingleton(IWorkingCopyBackupService, NativeWorkingCopyBackupService, InstantiationType.Eager); // Register Backup Tracker -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NativeWorkingCopyBackupTracker, LifecyclePhase.Starting); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(NativeWorkingCopyBackupTracker.ID, NativeWorkingCopyBackupTracker, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts index aa5bf69c227..17e3cd0e4b9 100644 --- a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts @@ -28,6 +28,8 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.nativeWorkingCopyBackupTracker'; + constructor( @IWorkingCopyBackupService workingCopyBackupService: IWorkingCopyBackupService, @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, diff --git a/src/vs/workbench/test/browser/contributions.test.ts b/src/vs/workbench/test/browser/contributions.test.ts new file mode 100644 index 00000000000..99ee60ff251 --- /dev/null +++ b/src/vs/workbench/test/browser/contributions.test.ts @@ -0,0 +1,155 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { DeferredPromise } from 'vs/base/common/async'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { WorkbenchContributionInstantiation, WorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; + +suite('Contributions', () => { + const disposables = new DisposableStore(); + + let aCreated: boolean; + let aCreatedPromise: DeferredPromise; + + let bCreated: boolean; + let bCreatedPromise: DeferredPromise; + + setup(() => { + aCreated = false; + aCreatedPromise = new DeferredPromise(); + + bCreated = false; + bCreatedPromise = new DeferredPromise(); + }); + + teardown(() => { + disposables.clear(); + }); + + class TestContributionA { + constructor() { + aCreated = true; + aCreatedPromise.complete(); + } + } + class TestContributionB { + constructor() { + bCreated = true; + bCreatedPromise.complete(); + } + } + class TestContributionError { + constructor() { + throw new Error(); + } + } + + test('getWorkbenchContribution() - with lazy contributions', () => { + const registry = new WorkbenchContributionsRegistry(); + + assert.throws(() => registry.getWorkbenchContribution('a')); + + registry.registerWorkbenchContribution2('a', TestContributionA, WorkbenchContributionInstantiation.Lazy); + assert.throws(() => registry.getWorkbenchContribution('a')); + + registry.registerWorkbenchContribution2('b', TestContributionB, WorkbenchContributionInstantiation.Lazy); + registry.registerWorkbenchContribution2('c', TestContributionError, WorkbenchContributionInstantiation.Lazy); + + const instantiationService = workbenchInstantiationService(undefined, disposables); + registry.start(instantiationService); + + const instanceA = registry.getWorkbenchContribution('a'); + assert.ok(instanceA instanceof TestContributionA); + assert.ok(aCreated); + assert.strictEqual(instanceA, registry.getWorkbenchContribution('a')); + + const instanceB = registry.getWorkbenchContribution('b'); + assert.ok(instanceB instanceof TestContributionB); + + assert.throws(() => registry.getWorkbenchContribution('c')); + }); + + test('getWorkbenchContribution() - with non-lazy contributions', async () => { + const registry = new WorkbenchContributionsRegistry(); + + const instantiationService = workbenchInstantiationService(undefined, disposables); + const accessor = instantiationService.createInstance(TestServiceAccessor); + accessor.lifecycleService.usePhases = true; + registry.start(instantiationService); + + assert.throws(() => registry.getWorkbenchContribution('a')); + + registry.registerWorkbenchContribution2('a', TestContributionA, WorkbenchContributionInstantiation.BlockRestore); + + const instanceA = registry.getWorkbenchContribution('a'); + assert.ok(instanceA instanceof TestContributionA); + assert.ok(aCreated); + + accessor.lifecycleService.phase = LifecyclePhase.Ready; + await aCreatedPromise.p; + + assert.strictEqual(instanceA, registry.getWorkbenchContribution('a')); + }); + + test('lifecycle phase instantiation works when phase changes', async () => { + const registry = new WorkbenchContributionsRegistry(); + + const instantiationService = workbenchInstantiationService(undefined, disposables); + const accessor = instantiationService.createInstance(TestServiceAccessor); + registry.start(instantiationService); + + registry.registerWorkbenchContribution2('a', TestContributionA, WorkbenchContributionInstantiation.BlockRestore); + assert.ok(!aCreated); + + accessor.lifecycleService.phase = LifecyclePhase.Ready; + await aCreatedPromise.p; + assert.ok(aCreated); + }); + + test('lifecycle phase instantiation works when phase was already met', async () => { + const registry = new WorkbenchContributionsRegistry(); + + const instantiationService = workbenchInstantiationService(undefined, disposables); + const accessor = instantiationService.createInstance(TestServiceAccessor); + accessor.lifecycleService.usePhases = true; + accessor.lifecycleService.phase = LifecyclePhase.Restored; + + registry.registerWorkbenchContribution2('a', TestContributionA, WorkbenchContributionInstantiation.BlockRestore); + registry.start(instantiationService); + + await aCreatedPromise.p; + assert.ok(aCreated); + }); + + test('lifecycle phase instantiation works for late phases', async () => { + const registry = new WorkbenchContributionsRegistry(); + + const instantiationService = workbenchInstantiationService(undefined, disposables); + const accessor = instantiationService.createInstance(TestServiceAccessor); + accessor.lifecycleService.usePhases = true; + registry.start(instantiationService); + + registry.registerWorkbenchContribution2('a', TestContributionA, WorkbenchContributionInstantiation.AfterRestored); + registry.registerWorkbenchContribution2('b', TestContributionB, WorkbenchContributionInstantiation.Eventually); + assert.ok(!aCreated); + assert.ok(!bCreated); + + accessor.lifecycleService.phase = LifecyclePhase.Starting; + accessor.lifecycleService.phase = LifecyclePhase.Ready; + accessor.lifecycleService.phase = LifecyclePhase.Restored; + await aCreatedPromise.p; + assert.ok(aCreated); + + accessor.lifecycleService.phase = LifecyclePhase.Eventually; + await bCreatedPromise.p; + assert.ok(bCreated); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); +}); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index f006962a209..8a7b4e3e9c1 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -59,7 +59,7 @@ import { IEditorPaneRegistry, EditorPaneDescriptor } from 'vs/workbench/browser/ import { IDimension } from 'vs/base/browser/dom'; import { ILoggerService, ILogService, NullLogService } from 'vs/platform/log/common/log'; import { ILabelService } from 'vs/platform/label/common/label'; -import { timeout } from 'vs/base/common/async'; +import { DeferredPromise, timeout } from 'vs/base/common/async'; import { PaneComposite, PaneCompositeDescriptor } from 'vs/workbench/browser/panecomposite'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IProcessEnvironment, isLinux, isWindows, OperatingSystem } from 'vs/base/common/platform'; @@ -1310,7 +1310,41 @@ export class TestLifecycleService extends Disposable implements ILifecycleServic declare readonly _serviceBrand: undefined; - phase!: LifecyclePhase; + usePhases = false; + _phase!: LifecyclePhase; + get phase(): LifecyclePhase { return this._phase; } + set phase(value: LifecyclePhase) { + this._phase = value; + if (value === LifecyclePhase.Starting) { + this.whenStarted.complete(); + } else if (value === LifecyclePhase.Ready) { + this.whenReady.complete(); + } else if (value === LifecyclePhase.Restored) { + this.whenRestored.complete(); + } else if (value === LifecyclePhase.Eventually) { + this.whenEventually.complete(); + } + } + + private readonly whenStarted = new DeferredPromise(); + private readonly whenReady = new DeferredPromise(); + private readonly whenRestored = new DeferredPromise(); + private readonly whenEventually = new DeferredPromise(); + async when(phase: LifecyclePhase): Promise { + if (!this.usePhases) { + return; + } + if (phase === LifecyclePhase.Starting) { + await this.whenStarted.p; + } else if (phase === LifecyclePhase.Ready) { + await this.whenReady.p; + } else if (phase === LifecyclePhase.Restored) { + await this.whenRestored.p; + } else if (phase === LifecyclePhase.Eventually) { + await this.whenEventually.p; + } + } + startupKind!: StartupKind; private readonly _onBeforeShutdown = this._register(new Emitter()); @@ -1328,8 +1362,6 @@ export class TestLifecycleService extends Disposable implements ILifecycleServic private readonly _onDidShutdown = this._register(new Emitter()); get onDidShutdown(): Event { return this._onDidShutdown.event; } - async when(): Promise { } - shutdownJoiners: Promise[] = []; fireShutdown(reason = ShutdownReason.QUIT): void { From 9d88f5a3e7ab45602cca54b912661dc18b06b836 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 31 Jan 2024 11:08:25 +0100 Subject: [PATCH 0757/1897] wip --- .../browser/inlineCompletionsModel.ts | 74 ++----------------- .../browser/singleTextEdit.ts | 72 +++++++++++++++++- .../test/browser/singleTextEdit.test.ts | 60 +++++++++++++++ 3 files changed, 136 insertions(+), 70 deletions(-) create mode 100644 src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index f74e56c11b6..56e1184c809 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -7,7 +7,7 @@ import { mapFindFirst } from 'vs/base/common/arraysFind'; import { BugIndicatingError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; import { IObservable, IReader, ITransaction, autorun, derived, derivedHandleChanges, derivedOpts, recomputeInitiallyAndOnChange, observableSignal, observableValue, subtransaction, transaction } from 'vs/base/common/observable'; -import { commonPrefixLength, splitLines } from 'vs/base/common/strings'; +import { commonPrefixLength } from 'vs/base/common/strings'; import { isDefined } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditOperation } from 'vs/editor/common/core/editOperation'; @@ -20,7 +20,7 @@ import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { IFeatureDebounceInformation } from 'vs/editor/common/services/languageFeatureDebounce'; import { GhostText, GhostTextOrReplacement, ghostTextOrReplacementEquals } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; import { InlineCompletionWithUpdatedRange, InlineCompletionsSource } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource'; -import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; +import { SingleTextEdit, getNewRanges } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; import { SuggestItemInfo } from 'vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider'; import { addPositions, lengthOfText } from 'vs/editor/contrib/inlineCompletions/browser/utils'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; @@ -435,6 +435,8 @@ export class InlineCompletionsModel extends Disposable { } } + // todo: extract this function into a separate place, write some tests for this function + // todo: investigate that bug, that we previously saw inside of test 21, there appears to be a logical error somewhere that needs to be resolved private _getEdits(editor: ICodeEditor, completion: SingleTextEdit): { edits: SingleTextEdit[]; editorSelections: Selection[] } { const selections = editor.getSelections() ?? []; @@ -456,7 +458,7 @@ export class InlineCompletionsModel extends Disposable { return new SingleTextEdit(range, secondaryEditText); }) ]; - const newRanges = this._getNewRanges(edits); + const newRanges = getNewRanges(edits); const editorSelections = newRanges.map(range => Selection.fromPositions(range.getEndPosition())); return { @@ -465,72 +467,6 @@ export class InlineCompletionsModel extends Disposable { }; } - // todo: extract this function into a separate place, write some tests for this function - // todo: investigate that bug, that we previously saw inside of test 21, there appears to be a logical error somewhere that needs to be resolved - private _getNewRanges(edits: SingleTextEdit[]): Range[] { - - if (edits.length === 0) { - return []; - } - - const sortIndices = Array.from(edits.keys()).sort((a, b) => - Range.compareRangesUsingStarts(edits[a].range, edits[b].range) - ); - const indexOfEdit = sortIndices.indexOf(0); - const firstEdit = edits[indexOfEdit]; - const firstEditStartPosition = firstEdit.range.getStartPosition(); - const rangesMap = new Map([[ - 0, Range.fromPositions( - firstEditStartPosition, - addPositions( - firstEdit.range.getStartPosition(), - lengthOfText(firstEdit.text) - )) - ]]); - - for (let i = 0; i < edits.length; i++) { - - const indexOfEdit = sortIndices.indexOf(i); - const edit = edits[indexOfEdit]; - if (!edit) { - return []; - } - const splitText = splitLines(edit.text!); - const numberOfLinesToAdd = splitText.length - (edit.range.endLineNumber - edit.range.startLineNumber) - 1; - - for (let j = i + 1; j < edits.length; j++) { - const indexOfEdit = sortIndices.indexOf(j); - const affectedEdit = edits[indexOfEdit]; - const affectedRange = rangesMap.get(j) ?? affectedEdit?.range; - if (!affectedRange || !affectedEdit) { - return []; - } - let columnDelta = 0; - if (affectedRange.startLineNumber === edit.range.endLineNumber) { - let rangeLength; - if (edit.range.startLineNumber === edit.range.endLineNumber) { - rangeLength = edit.range.endColumn - edit.range.startColumn; - } else { - rangeLength = edit.range.endColumn; - } - columnDelta = splitText[splitText.length - 1].length - rangeLength; - } - const rangeStart = affectedRange.getStartPosition().delta(numberOfLinesToAdd, columnDelta); - const rangeEnd = addPositions( - rangeStart, - lengthOfText(affectedEdit.text) - ); - rangesMap.set(j, Range.fromPositions(rangeStart, rangeEnd)); - } - } - - const ranges = []; - for (let i = 0; i < edits.length; i++) { - ranges.push(rangesMap.get(i)!); - } - return ranges; - } - public handleSuggestAccepted(item: SuggestItemInfo) { const itemEdit = item.toSingleTextEdit().removeCommonPrefix(this.textModel); const augmentedCompletion = this._computeAugmentedCompletion(itemEdit, undefined); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts index c958fcdb605..dad5711a9cd 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDiffChange, LcsDiff } from 'vs/base/common/diff/diff'; -import { commonPrefixLength, getLeadingWhitespace } from 'vs/base/common/strings'; +import { commonPrefixLength, getLeadingWhitespace, splitLines } from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; @@ -246,3 +246,73 @@ function smartDiff(originalValue: string, newValue: string, smartBracketMatching return new LcsDiff({ getElements: () => elements1 }, { getElements: () => elements2 }).ComputeDiff(false).changes; } + +/** + * Given some single text edits, this function finds the new ranges of the editted text post all edits. + * Assumes that the edit ranges are disjoint + * @param edits edits applied + * @returns new ranges post edits for every edit + */ +export function getNewRanges(edits: SingleTextEdit[]): Range[] { + + if (edits.length === 0) { + return []; + } + + const sortIndices = Array.from(edits.keys()).sort((a, b) => + Range.compareRangesUsingStarts(edits[a].range, edits[b].range) + ); + const indexOfEdit = sortIndices.indexOf(0); + const firstEdit = edits[indexOfEdit]; + const firstEditStartPosition = firstEdit.range.getStartPosition(); + const rangesMap = new Map([[ + 0, Range.fromPositions( + firstEditStartPosition, + addPositions( + firstEdit.range.getStartPosition(), + lengthOfText(firstEdit.text) + )) + ]]); + + for (let i = 0; i < edits.length; i++) { + + const indexOfEdit = sortIndices.indexOf(i); + const edit = edits[indexOfEdit]; + if (!edit) { + return []; + } + const splitText = splitLines(edit.text!); + const numberOfLinesToAdd = splitText.length - (edit.range.endLineNumber - edit.range.startLineNumber) - 1; + + for (let j = i + 1; j < edits.length; j++) { + const indexOfEdit = sortIndices.indexOf(j); + const affectedEdit = edits[indexOfEdit]; + const affectedRange = rangesMap.get(j) ?? affectedEdit?.range; + if (!affectedRange || !affectedEdit) { + return []; + } + let columnDelta = 0; + if (affectedRange.startLineNumber === edit.range.endLineNumber) { + let rangeLength; + if (edit.range.startLineNumber === edit.range.endLineNumber) { + rangeLength = edit.range.endColumn - edit.range.startColumn; + } else { + rangeLength = edit.range.endColumn; + } + columnDelta = splitText[splitText.length - 1].length - rangeLength; + } + const rangeStart = affectedRange.getStartPosition().delta(numberOfLinesToAdd, columnDelta); + const rangeEnd = addPositions( + rangeStart, + lengthOfText(affectedEdit.text) + ); + rangesMap.set(j, Range.fromPositions(rangeStart, rangeEnd)); + } + } + + const ranges = []; + for (let i = 0; i < edits.length; i++) { + ranges.push(rangesMap.get(i)!); + } + return ranges; +} diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts new file mode 100644 index 00000000000..2b704c1cced --- /dev/null +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { SingleTextEdit, getNewRanges } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; +import { Range } from 'vs/editor/common/core/range'; +import * as assert from 'assert'; + +suite('Single Text Edit', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('getNewRanges 1', () => { + const edits: SingleTextEdit[] = [ + new SingleTextEdit(new Range(0, 0, 2, 0), 'short text'), + new SingleTextEdit(new Range(3, 0, 3, 0), [ + `text that spans`, + `multiple lines`, + `.` + ].join('\n')), + new SingleTextEdit(new Range(4, 0, 4, 1), 'some short text'), + ]; + const ranges = getNewRanges(edits); + assert.deepStrictEqual(ranges, [ + new Range(0, 0, 0, 1), + new Range(0, 1, 0, 2), + new Range(0, 2, 0, 3), + ]); + }); + + test('getNewRanges 2', () => { + const edits: SingleTextEdit[] = [ + new SingleTextEdit(new Range(0, 0, 0, 0), 'a'), + new SingleTextEdit(new Range(0, 0, 0, 0), 'a'), + new SingleTextEdit(new Range(0, 0, 0, 0), 'a'), + ]; + const ranges = getNewRanges(edits); + assert.deepStrictEqual(ranges, [ + new Range(0, 0, 0, 1), + new Range(0, 1, 0, 2), + new Range(0, 2, 0, 3), + ]); + }); + + test('getNewRanges 3', () => { + const edits: SingleTextEdit[] = [ + new SingleTextEdit(new Range(0, 0, 0, 0), 'a'), + new SingleTextEdit(new Range(0, 0, 0, 0), 'a'), + new SingleTextEdit(new Range(0, 0, 0, 0), 'a'), + ]; + const ranges = getNewRanges(edits); + assert.deepStrictEqual(ranges, [ + new Range(0, 0, 0, 1), + new Range(0, 1, 0, 2), + new Range(0, 2, 0, 3), + ]); + }); + +}); From e0277c1feedcb5c6cfc8b897d2d9e969d6f7f9f4 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 31 Jan 2024 12:03:08 +0100 Subject: [PATCH 0758/1897] a11y theme fixes (#203870) actionbar: Replace opacity with color for disabled state --- src/vs/base/browser/ui/actionbar/actionbar.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index 45e5f3800a3..cf699047f02 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -55,7 +55,7 @@ .monaco-action-bar .action-item.disabled .action-label, .monaco-action-bar .action-item.disabled .action-label::before, .monaco-action-bar .action-item.disabled .action-label:hover { - opacity: 0.6; + color: var(--vscode-disabledForeground); } /* Vertical actions */ From 3f7e44287c35e0df549ed6e07273582ce3544c8a Mon Sep 17 00:00:00 2001 From: Drew Pirrone-Brusse Date: Wed, 31 Jan 2024 06:05:59 -0500 Subject: [PATCH 0759/1897] Extend TextEditorLineNumbersStyle with Interval (#198787) --- src/vs/workbench/api/browser/mainThreadEditor.ts | 5 ++++- src/vs/workbench/api/common/extHostTypeConverters.ts | 4 ++++ src/vs/workbench/api/common/extHostTypes.ts | 3 ++- src/vscode-dts/vscode.d.ts | 6 +++++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index b56ab1cb970..575c4033787 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -400,7 +400,7 @@ export class MainThreadTextEditor { } if (typeof newConfiguration.lineNumbers !== 'undefined') { - let lineNumbers: 'on' | 'off' | 'relative'; + let lineNumbers: 'on' | 'off' | 'relative' | 'interval'; switch (newConfiguration.lineNumbers) { case RenderLineNumbersType.On: lineNumbers = 'on'; @@ -408,6 +408,9 @@ export class MainThreadTextEditor { case RenderLineNumbersType.Relative: lineNumbers = 'relative'; break; + case RenderLineNumbersType.Interval: + lineNumbers = 'interval'; + break; default: lineNumbers = 'off'; } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 62d20bbea37..1afcc8102e8 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1379,6 +1379,8 @@ export namespace TextEditorLineNumbersStyle { return RenderLineNumbersType.Off; case types.TextEditorLineNumbersStyle.Relative: return RenderLineNumbersType.Relative; + case types.TextEditorLineNumbersStyle.Interval: + return RenderLineNumbersType.Interval; case types.TextEditorLineNumbersStyle.On: default: return RenderLineNumbersType.On; @@ -1390,6 +1392,8 @@ export namespace TextEditorLineNumbersStyle { return types.TextEditorLineNumbersStyle.Off; case RenderLineNumbersType.Relative: return types.TextEditorLineNumbersStyle.Relative; + case RenderLineNumbersType.Interval: + return types.TextEditorLineNumbersStyle.Interval; case RenderLineNumbersType.On: default: return types.TextEditorLineNumbersStyle.On; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index b67a8408f8e..2b4dd7f530d 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -1821,7 +1821,8 @@ export function asStatusBarItemIdentifier(extension: ExtensionIdentifier, id: st export enum TextEditorLineNumbersStyle { Off = 0, On = 1, - Relative = 2 + Relative = 2, + Interval = 3 } export enum TextDocumentSaveReason { diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index bc0e61bbf1d..a8fef891e89 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -657,7 +657,11 @@ declare module 'vscode' { /** * Render the line numbers with values relative to the primary cursor location. */ - Relative = 2 + Relative = 2, + /** + * Render the line numbers on every 10th line number. + */ + Interval = 3, } /** From f90b72a1364e460ae5d184ce282bcd0629ec740b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 31 Jan 2024 12:12:48 +0100 Subject: [PATCH 0760/1897] perf - more workbench contrib work (#203880) * perf - more workbench contributions work * . * add to perf editor --- .../api/browser/viewsExtensionPoint.ts | 7 +++-- src/vs/workbench/common/contributions.ts | 10 +++++-- .../browser/preview/bulkEdit.contribution.ts | 9 +++--- .../browser/performance.contribution.ts | 7 +++-- .../performance/browser/perfviewEditor.ts | 29 +++++++++++++++++-- .../contrib/splash/browser/partsSplash.ts | 2 ++ .../splash/browser/splash.contribution.ts | 8 ++--- .../electron-sandbox/splash.contribution.ts | 8 ++--- .../url/browser/externalUriResolver.ts | 3 ++ .../trustedDomainsFileSystemProvider.ts | 3 ++ .../contrib/url/browser/url.contribution.ts | 12 ++++---- .../browser/walkThrough.contribution.ts | 5 ++-- .../common/walkThroughContentProvider.ts | 3 ++ 13 files changed, 75 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index c51db51e1b5..cdbc3d7d432 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -17,7 +17,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { Extensions as ViewletExtensions, PaneCompositeRegistry } from 'vs/workbench/browser/panecomposite'; import { CustomTreeView, RawCustomTreeViewContextKey, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { Extensions as ViewContainerExtensions, ICustomViewDescriptor, IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ResolvableTreeItem, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug'; import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files'; @@ -26,7 +26,6 @@ import { VIEWLET_ID as SCM } from 'vs/workbench/contrib/scm/common/scm'; import { WebviewViewPane } from 'vs/workbench/contrib/webviewView/browser/webviewViewPane'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; @@ -270,6 +269,8 @@ const CUSTOM_VIEWS_START_ORDER = 7; class ViewsExtensionHandler implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.viewsExtensionHandler'; + private viewContainersRegistry: IViewContainersRegistry; private viewsRegistry: IViewsRegistry; @@ -666,4 +667,4 @@ class ViewsExtensionHandler implements IWorkbenchContribution { } const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(ViewsExtensionHandler, LifecyclePhase.Starting); +workbenchRegistry.registerWorkbenchContribution2(ViewsExtensionHandler.ID, ViewsExtensionHandler, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index 2b1db039906..45481aff1ac 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -94,7 +94,7 @@ export interface IWorkbenchContributionsRegistry { /** * @deprecated use `registerWorkbenchContribution2` instead. */ - registerWorkbenchContribution(contribution: IWorkbenchContributionSignature, phase: LifecyclePhase): void; + registerWorkbenchContribution(contribution: IWorkbenchContributionSignature, phase: LifecyclePhase.Restored | LifecyclePhase.Eventually): void; /** * Register a workbench contribution that will be instantiated @@ -332,7 +332,9 @@ export class WorkbenchContributionsRegistry implements IWorkbenchContributionsRe const now = Date.now(); try { - mark(`code/willCreateWorkbenchContribution/${phase}/${contribution.id}`); + if (typeof contribution.id === 'string') { + mark(`code/willCreateWorkbenchContribution/${phase}/${contribution.id}`); + } const instance = instantiationService.createInstance(contribution.ctor); if (typeof contribution.id === 'string') { @@ -342,7 +344,9 @@ export class WorkbenchContributionsRegistry implements IWorkbenchContributionsRe } catch (error) { logService.error(`Unable to create workbench contribution '${contribution.id ?? contribution.ctor.name}'.`, error); } finally { - mark(`code/didCreateWorkbenchContribution/${phase}/${contribution.id}`); + if (typeof contribution.id === 'string') { + mark(`code/didCreateWorkbenchContribution/${phase}/${contribution.id}`); + } } if (typeof contribution.id === 'string' || !environmentService.isBuilt /* only log out of sources where we have good ctor names (TODO@bpasero remove when adopted IDs) */) { diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts index 070f87e2feb..a20120d6900 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { IBulkEditService, ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { BulkEditPane } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane'; import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views'; @@ -90,6 +89,8 @@ class PreviewSession { class BulkEditPreviewContribution { + static readonly ID = 'workbench.contrib.bulkEditPreview'; + static readonly ctxEnabled = new RawContextKey('refactorPreview.enabled', false); private readonly _ctxEnabled: IContextKey; @@ -319,8 +320,8 @@ registerAction2(class ToggleGrouping extends Action2 { } }); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( - BulkEditPreviewContribution, LifecyclePhase.Ready +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2( + BulkEditPreviewContribution.ID, BulkEditPreviewContribution, WorkbenchContributionInstantiation.BlockRestore ); const refactorPreviewViewIcon = registerIcon('refactor-preview-view-icon', Codicon.lightbulb, localize('refactorPreviewViewIcon', 'View icon of the refactor preview view.')); diff --git a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts index 1a32b7dc306..d523f1580f8 100644 --- a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts +++ b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts @@ -9,7 +9,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorSerializer, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { PerfviewContrib, PerfviewInput } from 'vs/workbench/contrib/performance/browser/perfviewEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -19,9 +19,10 @@ import { InputLatencyContrib } from 'vs/workbench/contrib/performance/browser/in // -- startup performance view -Registry.as(Extensions.Workbench).registerWorkbenchContribution( +Registry.as(Extensions.Workbench).registerWorkbenchContribution2( + PerfviewContrib.ID, PerfviewContrib, - LifecyclePhase.Ready + WorkbenchContributionInstantiation.BlockRestore ); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer( diff --git a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts index db3605eb6bd..d99fb52b115 100644 --- a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts @@ -28,9 +28,13 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import * as perf from 'vs/base/common/performance'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; export class PerfviewContrib { + static readonly ID = 'workbench.contrib.perfview'; + private readonly _registration: IDisposable; constructor( @@ -134,6 +138,8 @@ class PerfModelContentProvider implements ITextModelContentProvider { md.blank(); this._addPerfMarksTable('Terminal Stats', md, this._timerService.getPerformanceMarks().find(e => e[0] === 'renderer')?.[1].filter(e => e.name.startsWith('code/terminal/'))); md.blank(); + this._addWorkbenchContributionsPerfMarksTable(md); + md.blank(); this._addRawPerfMarks(md); if (!isESM) { md.blank(); @@ -232,7 +238,7 @@ class PerfModelContentProvider implements ITextModelContentProvider { } } - private _addPerfMarksTable(name: string, md: MarkdownBuilder, marks: readonly perf.PerformanceMark[] | undefined): void { + private _addPerfMarksTable(name: string | undefined, md: MarkdownBuilder, marks: readonly perf.PerformanceMark[] | undefined): void { if (!marks) { return; } @@ -245,10 +251,29 @@ class PerfModelContentProvider implements ITextModelContentProvider { table.push([name, Math.round(startTime), Math.round(delta), Math.round(total)]); lastStartTime = startTime; } - md.heading(2, name); + if (name) { + md.heading(2, name); + } md.table(['Name', 'Timestamp', 'Delta', 'Total'], table); } + private _addWorkbenchContributionsPerfMarksTable(md: MarkdownBuilder): void { + md.heading(2, 'Workbench Contributions Blocking Restore'); + + const timings = Registry.as(WorkbenchExtensions.Workbench).timings; + md.li(`Total (LifecyclePhase.Starting): ${timings.get(LifecyclePhase.Starting)?.length} (${timings.get(LifecyclePhase.Starting)?.reduce((p, c) => p + c[1], 0)}ms)`); + md.li(`Total (LifecyclePhase.Ready): ${timings.get(LifecyclePhase.Ready)?.length} (${timings.get(LifecyclePhase.Ready)?.reduce((p, c) => p + c[1], 0)}ms)`); + md.blank(); + + const marks = this._timerService.getPerformanceMarks().find(e => e[0] === 'renderer')?.[1].filter(e => + e.name.startsWith('code/willCreateWorkbenchContribution/1') || + e.name.startsWith('code/didCreateWorkbenchContribution/1') || + e.name.startsWith('code/willCreateWorkbenchContribution/2') || + e.name.startsWith('code/didCreateWorkbenchContribution/2') + ); + this._addPerfMarksTable(undefined, md, marks); + } + private _addRawPerfMarks(md: MarkdownBuilder): void { for (const [source, marks] of this._timerService.getPerformanceMarks()) { diff --git a/src/vs/workbench/contrib/splash/browser/partsSplash.ts b/src/vs/workbench/contrib/splash/browser/partsSplash.ts index 5d637c44893..6982a2cd50b 100644 --- a/src/vs/workbench/contrib/splash/browser/partsSplash.ts +++ b/src/vs/workbench/contrib/splash/browser/partsSplash.ts @@ -25,6 +25,8 @@ import { TitleBarSetting } from 'vs/platform/window/common/window'; export class PartsSplash { + static readonly ID = 'workbench.contrib.partsSplash'; + private static readonly _splashElementId = 'monaco-parts-splash'; private readonly _disposables = new DisposableStore(); diff --git a/src/vs/workbench/contrib/splash/browser/splash.contribution.ts b/src/vs/workbench/contrib/splash/browser/splash.contribution.ts index a6b4093aed9..171f6b0db71 100644 --- a/src/vs/workbench/contrib/splash/browser/splash.contribution.ts +++ b/src/vs/workbench/contrib/splash/browser/splash.contribution.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { ISplashStorageService } from 'vs/workbench/contrib/splash/browser/splash'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { PartsSplash } from 'vs/workbench/contrib/splash/browser/partsSplash'; @@ -20,7 +19,8 @@ registerSingleton(ISplashStorageService, class SplashStorageService implements I } }, InstantiationType.Delayed); -Registry.as(Extensions.Workbench).registerWorkbenchContribution( +Registry.as(Extensions.Workbench).registerWorkbenchContribution2( + PartsSplash.ID, PartsSplash, - LifecyclePhase.Starting + WorkbenchContributionInstantiation.BlockStartup ); diff --git a/src/vs/workbench/contrib/splash/electron-sandbox/splash.contribution.ts b/src/vs/workbench/contrib/splash/electron-sandbox/splash.contribution.ts index 39743527276..a28b65f081c 100644 --- a/src/vs/workbench/contrib/splash/electron-sandbox/splash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-sandbox/splash.contribution.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { ISplashStorageService } from 'vs/workbench/contrib/splash/browser/splash'; import { INativeHostService } from 'vs/platform/native/common/native'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -23,7 +22,8 @@ class SplashStorageService implements ISplashStorageService { registerSingleton(ISplashStorageService, SplashStorageService, InstantiationType.Delayed); -Registry.as(Extensions.Workbench).registerWorkbenchContribution( +Registry.as(Extensions.Workbench).registerWorkbenchContribution2( + PartsSplash.ID, PartsSplash, - LifecyclePhase.Starting + WorkbenchContributionInstantiation.BlockStartup ); diff --git a/src/vs/workbench/contrib/url/browser/externalUriResolver.ts b/src/vs/workbench/contrib/url/browser/externalUriResolver.ts index 63434bc1990..0e629b4714e 100644 --- a/src/vs/workbench/contrib/url/browser/externalUriResolver.ts +++ b/src/vs/workbench/contrib/url/browser/externalUriResolver.ts @@ -9,6 +9,9 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; export class ExternalUriResolverContribution extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.externalUriResolver'; + constructor( @IOpenerService _openerService: IOpenerService, @IBrowserWorkbenchEnvironmentService _workbenchEnvironmentService: IBrowserWorkbenchEnvironmentService, diff --git a/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts b/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts index c13d93e6721..c5c225827e5 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts @@ -89,6 +89,9 @@ function computeTrustedDomainContent(defaultTrustedDomains: string[], trustedDom } export class TrustedDomainsFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability, IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.trustedDomainsFileSystemProvider'; + readonly capabilities = FileSystemProviderCapabilities.FileReadWrite; readonly onDidChangeCapabilities = Event.None; diff --git a/src/vs/workbench/contrib/url/browser/url.contribution.ts b/src/vs/workbench/contrib/url/browser/url.contribution.ts index 26d0c77eade..c9d31afa63d 100644 --- a/src/vs/workbench/contrib/url/browser/url.contribution.ts +++ b/src/vs/workbench/contrib/url/browser/url.contribution.ts @@ -11,7 +11,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { IURLService } from 'vs/platform/url/common/url'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; import { ExternalUriResolverContribution } from 'vs/workbench/contrib/url/browser/externalUriResolver'; import { manageTrustedDomainSettingsCommand } from 'vs/workbench/contrib/url/browser/trustedDomains'; import { TrustedDomainsFileSystemProvider } from 'vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider'; @@ -66,13 +66,15 @@ Registry.as(WorkbenchExtensions.Workbench).regi OpenerValidatorContributions, LifecyclePhase.Restored ); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2( + TrustedDomainsFileSystemProvider.ID, TrustedDomainsFileSystemProvider, - LifecyclePhase.Ready + WorkbenchContributionInstantiation.BlockRestore ); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2( + ExternalUriResolverContribution.ID, ExternalUriResolverContribution, - LifecyclePhase.Ready + WorkbenchContributionInstantiation.BlockRestore ); diff --git a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.ts b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.ts index f2039fbf03f..0057bd498d7 100644 --- a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.ts +++ b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.ts @@ -13,9 +13,8 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { MenuRegistry, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IEditorPaneRegistry, EditorPaneDescriptor } from 'vs/workbench/browser/editor'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; Registry.as(EditorExtensions.EditorPane) @@ -31,7 +30,7 @@ registerAction2(EditorWalkThroughAction); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(EditorWalkThroughInputSerializer.ID, EditorWalkThroughInputSerializer); Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(WalkThroughSnippetContentProvider, LifecyclePhase.Ready /* cannot be on a later phase because an editor might need this on startup */); + .registerWorkbenchContribution2(WalkThroughSnippetContentProvider.ID, WalkThroughSnippetContentProvider, WorkbenchContributionInstantiation.BlockRestore /* cannot be on a later phase because an editor might need this on startup */); KeybindingsRegistry.registerCommandAndKeybindingRule(WalkThroughArrowUp); diff --git a/src/vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider.ts b/src/vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider.ts index 94a4879c2f9..24764f1e31a 100644 --- a/src/vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider.ts +++ b/src/vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider.ts @@ -40,6 +40,9 @@ export function requireToContent(instantiationService: IInstantiationService, re } export class WalkThroughSnippetContentProvider implements ITextModelContentProvider, IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.walkThroughSnippetContentProvider'; + private loads = new Map>(); constructor( From 95722d2256400554e3376e12105dd24c280a4474 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Wed, 31 Jan 2024 11:39:55 +0100 Subject: [PATCH 0761/1897] add `ensureNoDisposablesAreLeakedInTestSuite()` to tests --- .eslintrc.json | 6 ----- .../contextkey/test/common/contextkey.test.ts | 4 ++++ .../contextkey/test/common/parser.test.ts | 3 +++ .../contextkey/test/common/scanner.test.ts | 4 ++++ .../common/abstractKeybindingService.test.ts | 22 +++++++++++-------- .../test/common/keybindingResolver.test.ts | 3 +++ .../test/browser/keybindingIO.test.ts | 2 ++ 7 files changed, 29 insertions(+), 15 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 5ce74c4cc10..c4ce64c7970 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -158,13 +158,8 @@ "src/vs/editor/test/node/diffing/defaultLinesDiffComputer.test.ts", "src/vs/editor/test/node/diffing/fixtures.test.ts", "src/vs/platform/configuration/test/common/configuration.test.ts", - "src/vs/platform/contextkey/test/common/contextkey.test.ts", - "src/vs/platform/contextkey/test/common/parser.test.ts", - "src/vs/platform/contextkey/test/common/scanner.test.ts", "src/vs/platform/extensions/test/common/extensionValidator.test.ts", - "src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts", "src/vs/platform/keybinding/test/common/keybindingLabels.test.ts", - "src/vs/platform/keybinding/test/common/keybindingResolver.test.ts", "src/vs/platform/opener/test/common/opener.test.ts", "src/vs/platform/registry/test/common/platform.test.ts", "src/vs/platform/remote/test/common/remoteHosts.test.ts", @@ -184,7 +179,6 @@ "src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts", "src/vs/workbench/services/commands/test/common/commandService.test.ts", "src/vs/workbench/services/extensions/test/common/extensionDescriptionRegistry.test.ts", - "src/vs/workbench/services/keybinding/test/browser/keybindingIO.test.ts", "src/vs/workbench/services/keybinding/test/node/fallbackKeyboardMapper.test.ts", "src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts", "src/vs/workbench/services/keybinding/test/node/windowsKeyboardMapper.test.ts", diff --git a/src/vs/platform/contextkey/test/common/contextkey.test.ts b/src/vs/platform/contextkey/test/common/contextkey.test.ts index 85544b15a0e..8f388fb13ee 100644 --- a/src/vs/platform/contextkey/test/common/contextkey.test.ts +++ b/src/vs/platform/contextkey/test/common/contextkey.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ContextKeyExpr, ContextKeyExpression, implies } from 'vs/platform/contextkey/common/contextkey'; function createContext(ctx: any) { @@ -15,6 +16,9 @@ function createContext(ctx: any) { } suite('ContextKeyExpr', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('ContextKeyExpr.equals', () => { const a = ContextKeyExpr.and( ContextKeyExpr.has('a1'), diff --git a/src/vs/platform/contextkey/test/common/parser.test.ts b/src/vs/platform/contextkey/test/common/parser.test.ts index 6d08961dae8..c5be2596341 100644 --- a/src/vs/platform/contextkey/test/common/parser.test.ts +++ b/src/vs/platform/contextkey/test/common/parser.test.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Parser } from 'vs/platform/contextkey/common/contextkey'; function parseToStr(input: string): string { @@ -34,6 +35,8 @@ function parseToStr(input: string): string { suite('Context Key Parser', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test(' foo', () => { const input = ' foo'; assert.deepStrictEqual(parseToStr(input), "foo"); diff --git a/src/vs/platform/contextkey/test/common/scanner.test.ts b/src/vs/platform/contextkey/test/common/scanner.test.ts index de4e9f7c3c5..df897db9e8a 100644 --- a/src/vs/platform/contextkey/test/common/scanner.test.ts +++ b/src/vs/platform/contextkey/test/common/scanner.test.ts @@ -3,9 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Scanner, Token, TokenType } from 'vs/platform/contextkey/common/scanner'; suite('Context Key Scanner', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + function tokenTypeToStr(token: Token) { switch (token.type) { case TokenType.LParen: diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index 24386a8cab2..2f79c811d5e 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -8,6 +8,7 @@ import { createSimpleKeybinding, ResolvedKeybinding, KeyCodeChord, Keybinding } import { Disposable } from 'vs/base/common/lifecycle'; import { OS } from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, ContextKeyExpression, IContext, IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey'; import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService'; @@ -108,6 +109,18 @@ suite('AbstractKeybindingService', () => { let statusMessageCalls: string[] | null = null; let statusMessageCallsDisposed: string[] | null = null; + + teardown(() => { + currentContextValue = null; + executeCommandCalls = null!; + showMessageCalls = null!; + createTestKeybindingService = null!; + statusMessageCalls = null; + statusMessageCallsDisposed = null; + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + setup(() => { executeCommandCalls = []; showMessageCalls = []; @@ -196,15 +209,6 @@ suite('AbstractKeybindingService', () => { }; }); - teardown(() => { - currentContextValue = null; - executeCommandCalls = null!; - showMessageCalls = null!; - createTestKeybindingService = null!; - statusMessageCalls = null; - statusMessageCallsDisposed = null; - }); - function kbItem(keybinding: number | number[], command: string | null, when?: ContextKeyExpression): ResolvedKeybindingItem { return new ResolvedKeybindingItem( createUSLayoutResolvedKeybinding(keybinding, OS), diff --git a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts index 72e8026e5ca..54bd2a6761d 100644 --- a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts +++ b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { decodeKeybinding, createSimpleKeybinding, KeyCodeChord } from 'vs/base/common/keybindings'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { OS } from 'vs/base/common/platform'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ContextKeyExpr, ContextKeyExpression, IContext } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingResolver, ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; @@ -23,6 +24,8 @@ function createContext(ctx: any) { suite('KeybindingResolver', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function kbItem(keybinding: number | number[], command: string, commandArgs: any, when: ContextKeyExpression | undefined, isDefault: boolean): ResolvedKeybindingItem { const resolvedKeybinding = createUSLayoutResolvedKeybinding(keybinding, OS); return new ResolvedKeybindingItem( diff --git a/src/vs/workbench/services/keybinding/test/browser/keybindingIO.test.ts b/src/vs/workbench/services/keybinding/test/browser/keybindingIO.test.ts index a40b938dc94..cfc697b817e 100644 --- a/src/vs/workbench/services/keybinding/test/browser/keybindingIO.test.ts +++ b/src/vs/workbench/services/keybinding/test/browser/keybindingIO.test.ts @@ -9,8 +9,10 @@ import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { OperatingSystem } from 'vs/base/common/platform'; import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO'; import { createUSLayoutResolvedKeybinding } from 'vs/platform/keybinding/test/common/keybindingsTestUtils'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('keybindingIO', () => { + ensureNoDisposablesAreLeakedInTestSuite(); test('serialize/deserialize', () => { From 694f90fbf8003ad6c16363fba653af3e9f3ae36c Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 31 Jan 2024 12:10:01 +0100 Subject: [PATCH 0762/1897] Some renames --- .../browser/model/textModelDiffs.ts | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts index 663973ff4af..2affffb8df3 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts @@ -16,15 +16,15 @@ import { autorun, IObservable, IReader, ITransaction, observableSignal, observab import { UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo'; export class TextModelDiffs extends Disposable { - private recomputeCount = 0; + private _recomputeCount = 0; private readonly _state = observableValue(this, TextModelDiffState.initializing); private readonly _diffs = observableValue(this, []); - private readonly barrier = new ReentrancyBarrier(); - private isDisposed = false; + private readonly _barrier = new ReentrancyBarrier(); + private _isDisposed = false; public get isApplyingChange() { - return this.barrier.isActive; + return this._barrier.isActive; } constructor( @@ -39,25 +39,25 @@ export class TextModelDiffs extends Disposable { this._register(autorun(reader => { /** @description Update diff state */ recomputeSignal.read(reader); - this.recompute(reader); + this._recompute(reader); })); this._register( baseTextModel.onDidChangeContent( - this.barrier.makeExclusive(() => { + this._barrier.makeExclusive(() => { recomputeSignal.trigger(undefined); }) ) ); this._register( textModel.onDidChangeContent( - this.barrier.makeExclusive(() => { + this._barrier.makeExclusive(() => { recomputeSignal.trigger(undefined); }) ) ); this._register(toDisposable(() => { - this.isDisposed = true; + this._isDisposed = true; })); } @@ -72,20 +72,20 @@ export class TextModelDiffs extends Disposable { return this._diffs; } - private isInitializing = true; + private _isInitializing = true; - private recompute(reader: IReader): void { - this.recomputeCount++; - const currentRecomputeIdx = this.recomputeCount; + private _recompute(reader: IReader): void { + this._recomputeCount++; + const currentRecomputeIdx = this._recomputeCount; if (this._state.get() === TextModelDiffState.initializing) { - this.isInitializing = true; + this._isInitializing = true; } transaction(tx => { /** @description Starting Diff Computation. */ this._state.set( - this.isInitializing ? TextModelDiffState.initializing : TextModelDiffState.updating, + this._isInitializing ? TextModelDiffState.initializing : TextModelDiffState.updating, tx, TextModelDiffChangeReason.other ); @@ -94,11 +94,11 @@ export class TextModelDiffs extends Disposable { const result = this.diffComputer.computeDiff(this.baseTextModel, this.textModel, reader); result.then((result) => { - if (this.isDisposed) { + if (this._isDisposed) { return; } - if (currentRecomputeIdx !== this.recomputeCount) { + if (currentRecomputeIdx !== this._recomputeCount) { // There is a newer recompute call return; } @@ -111,7 +111,7 @@ export class TextModelDiffs extends Disposable { } else { this._state.set(TextModelDiffState.error, tx, TextModelDiffChangeReason.textChange); } - this.isInitializing = false; + this._isInitializing = false; }); }); } @@ -138,7 +138,7 @@ export class TextModelDiffs extends Disposable { throw new BugIndicatingError(); } - this.barrier.runExclusivelyOrThrow(() => { + this._barrier.runExclusivelyOrThrow(() => { const edits = diffToRemove.getReverseLineEdit().toEdits(this.textModel.getLineCount()); this.textModel.pushEditOperations(null, edits, () => null, group); }); @@ -193,7 +193,7 @@ export class TextModelDiffs extends Disposable { newDiffs.push(editMapping.addOutputLineDelta(delta)); } - this.barrier.runExclusivelyOrThrow(() => { + this._barrier.runExclusivelyOrThrow(() => { const edits = new LineRangeEdit(edit.range.delta(delta), edit.newLines).toEdits(this.textModel.getLineCount()); this.textModel.pushEditOperations(null, edits, () => null, group); }); From ba2cf46e20df3edf77bdd905acde3e175d985f70 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 31 Jan 2024 12:32:57 +0100 Subject: [PATCH 0763/1897] watcher - do not allow empty strings in exclude/include setting (#203883) --- .../workbench/api/browser/mainThreadFileSystemEventService.ts | 4 ++-- src/vs/workbench/contrib/files/browser/workspaceWatcher.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts index c6bea307e34..20c6c76910d 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts @@ -277,7 +277,7 @@ export class MainThreadFileSystemEventService implements MainThreadFileSystemEve const config = this._configurationService.getValue(); if (config.files?.watcherExclude) { for (const key in config.files.watcherExclude) { - if (config.files.watcherExclude[key] === true) { + if (key && config.files.watcherExclude[key] === true) { opts.excludes.push(key); } } @@ -299,7 +299,7 @@ export class MainThreadFileSystemEventService implements MainThreadFileSystemEve const config = this._configurationService.getValue(); if (config.files?.watcherExclude) { for (const key in config.files.watcherExclude) { - if (config.files.watcherExclude[key] === true) { + if (key && config.files.watcherExclude[key] === true) { if (!opts.includes) { opts.includes = []; } diff --git a/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts b/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts index 6872bfabc67..be179f3d0e3 100644 --- a/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts +++ b/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts @@ -109,7 +109,7 @@ export class WorkspaceWatcher extends Disposable { const config = this.configurationService.getValue({ resource: workspace.uri }); if (config.files?.watcherExclude) { for (const key in config.files.watcherExclude) { - if (config.files.watcherExclude[key] === true) { + if (key && config.files.watcherExclude[key] === true) { excludes.push(key); } } From 4acf2d9fd883d247b903cc9c33221e18e39bffd8 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 31 Jan 2024 13:41:54 +0100 Subject: [PATCH 0764/1897] =?UTF-8?q?SCM=20-=20=F0=9F=92=84=20Only=20show?= =?UTF-8?q?=20Repositories=20sub-menu=20if=20there=20are=20multiple=20repo?= =?UTF-8?q?sitories=20(#203884)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SCM - Only show Repositories sub-menu if there are multiple repositories --- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index cb8145f0120..e64b8ce1809 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1444,6 +1444,7 @@ MenuRegistry.appendMenuItem(MenuId.SCMTitle, { MenuRegistry.appendMenuItem(Menus.ViewSort, { title: localize('repositories', "Repositories"), submenu: Menus.Repositories, + when: ContextKeyExpr.greater(ContextKeys.RepositoryCount.key, 1), group: '0_repositories' }); From 23964e0a95acd7ec9c16af04c56487e4ca6f7197 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 31 Jan 2024 14:19:07 +0100 Subject: [PATCH 0765/1897] rewriting the getNewRanges function --- .../browser/singleTextEdit.ts | 67 +++++-------------- 1 file changed, 17 insertions(+), 50 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts index dad5711a9cd..55a4e42b5b8 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts @@ -262,57 +262,24 @@ export function getNewRanges(edits: SingleTextEdit[]): Range[] { const sortIndices = Array.from(edits.keys()).sort((a, b) => Range.compareRangesUsingStarts(edits[a].range, edits[b].range) ); - const indexOfEdit = sortIndices.indexOf(0); - const firstEdit = edits[indexOfEdit]; - const firstEditStartPosition = firstEdit.range.getStartPosition(); - const rangesMap = new Map([[ - 0, Range.fromPositions( - firstEditStartPosition, - addPositions( - firstEdit.range.getStartPosition(), - lengthOfText(firstEdit.text) - )) - ]]); - - for (let i = 0; i < edits.length; i++) { - - const indexOfEdit = sortIndices.indexOf(i); - const edit = edits[indexOfEdit]; - if (!edit) { - return []; - } + const ranges: Range[] = []; + let offsetLineNumber = 0; + let previousEditLineNumber = 0; + let previousOffsetColumn = 0; + for (const index of sortIndices) { + const edit = edits[index]; const splitText = splitLines(edit.text!); - const numberOfLinesToAdd = splitText.length - (edit.range.endLineNumber - edit.range.startLineNumber) - 1; + const currentOffsetColumn = edit.range.endLineNumber === previousEditLineNumber ? previousOffsetColumn : 0; + const rangeStart = new Position(edit.range.startLineNumber + offsetLineNumber, edit.range.startColumn + currentOffsetColumn); - for (let j = i + 1; j < edits.length; j++) { - const indexOfEdit = sortIndices.indexOf(j); - const affectedEdit = edits[indexOfEdit]; - const affectedRange = rangesMap.get(j) ?? affectedEdit?.range; - if (!affectedRange || !affectedEdit) { - return []; - } - let columnDelta = 0; - if (affectedRange.startLineNumber === edit.range.endLineNumber) { - let rangeLength; - if (edit.range.startLineNumber === edit.range.endLineNumber) { - rangeLength = edit.range.endColumn - edit.range.startColumn; - } else { - rangeLength = edit.range.endColumn; - } - columnDelta = splitText[splitText.length - 1].length - rangeLength; - } - const rangeStart = affectedRange.getStartPosition().delta(numberOfLinesToAdd, columnDelta); - const rangeEnd = addPositions( - rangeStart, - lengthOfText(affectedEdit.text) - ); - rangesMap.set(j, Range.fromPositions(rangeStart, rangeEnd)); - } + offsetLineNumber += splitText.length - (edit.range.endLineNumber - edit.range.startLineNumber) - 1; + const rangeEnd = addPositions( + rangeStart, + lengthOfText(edit.text) + ); + ranges.push(Range.fromPositions(rangeStart, rangeEnd)); + previousEditLineNumber = edit.range.endLineNumber; + previousOffsetColumn = currentOffsetColumn + splitText[splitText.length - 1].length - edit.range.endColumn + (edit.range.startLineNumber === edit.range.endLineNumber ? edit.range.startColumn : 0); } - - const ranges = []; - for (let i = 0; i < edits.length; i++) { - ranges.push(rangesMap.get(i)!); - } - return ranges; + return ranges.map((_, index) => ranges[sortIndices.indexOf(index)]); } From d11211711978f6ed642d195c24a8cc3361a72915 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Wed, 31 Jan 2024 14:46:59 +0100 Subject: [PATCH 0766/1897] add `ensureNoDisposablesAreLeakedInTestSuite()` to tests --- .eslintrc.json | 5 ----- .../codeActionKeybindingResolver.test.ts | 4 ++++ .../test/common/keybindingLabels.test.ts | 3 +++ .../test/node/fallbackKeyboardMapper.test.ts | 5 +++++ .../test/node/macLinuxKeyboardMapper.test.ts | 19 +++++++++++++++++++ .../test/node/windowsKeyboardMapper.test.ts | 12 ++++++++++++ 6 files changed, 43 insertions(+), 5 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index c4ce64c7970..13cd7274b5e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -151,7 +151,6 @@ "src/vs/base/test/browser/browser.test.ts", "src/vs/base/test/browser/ui/scrollbar/scrollableElement.test.ts", "src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts", - "src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts", "src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts", "src/vs/editor/test/common/services/languageService.test.ts", "src/vs/editor/test/node/classification/typescript.test.ts", @@ -159,7 +158,6 @@ "src/vs/editor/test/node/diffing/fixtures.test.ts", "src/vs/platform/configuration/test/common/configuration.test.ts", "src/vs/platform/extensions/test/common/extensionValidator.test.ts", - "src/vs/platform/keybinding/test/common/keybindingLabels.test.ts", "src/vs/platform/opener/test/common/opener.test.ts", "src/vs/platform/registry/test/common/platform.test.ts", "src/vs/platform/remote/test/common/remoteHosts.test.ts", @@ -179,9 +177,6 @@ "src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts", "src/vs/workbench/services/commands/test/common/commandService.test.ts", "src/vs/workbench/services/extensions/test/common/extensionDescriptionRegistry.test.ts", - "src/vs/workbench/services/keybinding/test/node/fallbackKeyboardMapper.test.ts", - "src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts", - "src/vs/workbench/services/keybinding/test/node/windowsKeyboardMapper.test.ts", "src/vs/workbench/services/userActivity/test/browser/domActivityTracker.test.ts", "src/vs/workbench/test/browser/quickAccess.test.ts" ] diff --git a/src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts b/src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts index aa4a9c83670..641f1491d6f 100644 --- a/src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts +++ b/src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { KeyCodeChord } from 'vs/base/common/keybindings'; import { KeyCode } from 'vs/base/common/keyCodes'; import { OperatingSystem } from 'vs/base/common/platform'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { organizeImportsCommandId, refactorCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction'; import { CodeActionKeybindingResolver } from 'vs/editor/contrib/codeAction/browser/codeActionKeybindingResolver'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/common/types'; @@ -15,6 +16,9 @@ import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKe import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; suite('CodeActionKeybindingResolver', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + const refactorKeybinding = createCodeActionKeybinding( KeyCode.KeyA, refactorCommandId, diff --git a/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts b/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts index 36406bccee8..dfc5df1e096 100644 --- a/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts +++ b/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts @@ -6,10 +6,13 @@ import * as assert from 'assert'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { OperatingSystem } from 'vs/base/common/platform'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { createUSLayoutResolvedKeybinding } from 'vs/platform/keybinding/test/common/keybindingsTestUtils'; suite('KeybindingLabels', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function assertUSLabel(OS: OperatingSystem, keybinding: number, expected: string): void { const usResolvedKeybinding = createUSLayoutResolvedKeybinding(keybinding, OS)!; assert.strictEqual(usResolvedKeybinding.getLabel(), expected); diff --git a/src/vs/workbench/services/keybinding/test/node/fallbackKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/node/fallbackKeyboardMapper.test.ts index a559747ea34..4af4570f30b 100644 --- a/src/vs/workbench/services/keybinding/test/node/fallbackKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/node/fallbackKeyboardMapper.test.ts @@ -6,11 +6,14 @@ import { KeyChord, KeyCode, KeyMod, ScanCode } from 'vs/base/common/keyCodes'; import { KeyCodeChord, decodeKeybinding, ScanCodeChord, Keybinding } from 'vs/base/common/keybindings'; import { OperatingSystem } from 'vs/base/common/platform'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { FallbackKeyboardMapper } from 'vs/workbench/services/keybinding/common/fallbackKeyboardMapper'; import { IResolvedKeybinding, assertResolveKeyboardEvent, assertResolveKeybinding } from 'vs/workbench/services/keybinding/test/node/keyboardMapperTestUtils'; suite('keyboardMapper - MAC fallback', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const mapper = new FallbackKeyboardMapper(false, OperatingSystem.Macintosh); function _assertResolveKeybinding(k: number, expected: IResolvedKeybinding[]): void { @@ -229,6 +232,8 @@ suite('keyboardMapper - MAC fallback', () => { suite('keyboardMapper - LINUX fallback', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const mapper = new FallbackKeyboardMapper(false, OperatingSystem.Linux); function _assertResolveKeybinding(k: number, expected: IResolvedKeybinding[]): void { diff --git a/src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts index 41741515fad..05a8db5fc73 100644 --- a/src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts @@ -12,6 +12,7 @@ import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayo import { MacLinuxKeyboardMapper } from 'vs/workbench/services/keybinding/common/macLinuxKeyboardMapper'; import { IResolvedKeybinding, assertMapping, assertResolveKeyboardEvent, assertResolveKeybinding, readRawMapping } from 'vs/workbench/services/keybinding/test/node/keyboardMapperTestUtils'; import { IMacLinuxKeyboardMapping } from 'vs/platform/keyboardLayout/common/keyboardLayout'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; const WRITE_FILE_IF_DIFFERENT = false; @@ -22,6 +23,8 @@ async function createKeyboardMapper(isUSStandard: boolean, file: string, mapAltG suite('keyboardMapper - MAC de_ch', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + let mapper: MacLinuxKeyboardMapper; suiteSetup(async () => { @@ -401,6 +404,8 @@ suite('keyboardMapper - MAC de_ch', () => { suite('keyboardMapper - MAC en_us', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + let mapper: MacLinuxKeyboardMapper; suiteSetup(async () => { @@ -515,6 +520,8 @@ suite('keyboardMapper - MAC en_us', () => { suite('keyboardMapper - LINUX de_ch', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + let mapper: MacLinuxKeyboardMapper; suiteSetup(async () => { @@ -894,6 +901,8 @@ suite('keyboardMapper - LINUX de_ch', () => { suite('keyboardMapper - LINUX en_us', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + let mapper: MacLinuxKeyboardMapper; suiteSetup(async () => { @@ -1532,6 +1541,8 @@ suite('keyboardMapper - LINUX en_us', () => { suite('keyboardMapper', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('issue #23706: Linux UK layout: Ctrl + Apostrophe also toggles terminal', () => { const mapper = new MacLinuxKeyboardMapper(false, { 'Backquote': { @@ -1666,6 +1677,8 @@ suite('keyboardMapper', () => { suite('keyboardMapper - LINUX ru', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + let mapper: MacLinuxKeyboardMapper; suiteSetup(async () => { @@ -1700,6 +1713,8 @@ suite('keyboardMapper - LINUX ru', () => { suite('keyboardMapper - LINUX en_uk', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + let mapper: MacLinuxKeyboardMapper; suiteSetup(async () => { @@ -1740,6 +1755,8 @@ suite('keyboardMapper - LINUX en_uk', () => { suite('keyboardMapper - MAC zh_hant', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + let mapper: MacLinuxKeyboardMapper; suiteSetup(async () => { @@ -1774,6 +1791,8 @@ suite('keyboardMapper - MAC zh_hant', () => { suite('keyboardMapper - MAC zh_hant2', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + let mapper: MacLinuxKeyboardMapper; suiteSetup(async () => { diff --git a/src/vs/workbench/services/keybinding/test/node/windowsKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/node/windowsKeyboardMapper.test.ts index 78c964fda18..08bb66b4610 100644 --- a/src/vs/workbench/services/keybinding/test/node/windowsKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/node/windowsKeyboardMapper.test.ts @@ -9,6 +9,7 @@ import { OperatingSystem } from 'vs/base/common/platform'; import { WindowsKeyboardMapper } from 'vs/workbench/services/keybinding/common/windowsKeyboardMapper'; import { IResolvedKeybinding, assertMapping, assertResolveKeyboardEvent, assertResolveKeybinding, readRawMapping } from 'vs/workbench/services/keybinding/test/node/keyboardMapperTestUtils'; import { IWindowsKeyboardMapping } from 'vs/platform/keyboardLayout/common/keyboardLayout'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; const WRITE_FILE_IF_DIFFERENT = false; @@ -24,6 +25,8 @@ function _assertResolveKeybinding(mapper: WindowsKeyboardMapper, k: number, expe suite('keyboardMapper - WINDOWS de_ch', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + let mapper: WindowsKeyboardMapper; suiteSetup(async () => { @@ -338,6 +341,8 @@ suite('keyboardMapper - WINDOWS de_ch', () => { suite('keyboardMapper - WINDOWS en_us', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + let mapper: WindowsKeyboardMapper; suiteSetup(async () => { @@ -563,6 +568,8 @@ suite('keyboardMapper - WINDOWS en_us', () => { suite('keyboardMapper - WINDOWS por_ptb', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + let mapper: WindowsKeyboardMapper; suiteSetup(async () => { @@ -628,6 +635,8 @@ suite('keyboardMapper - WINDOWS por_ptb', () => { suite('keyboardMapper - WINDOWS ru', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + let mapper: WindowsKeyboardMapper; suiteSetup(async () => { @@ -657,6 +666,9 @@ suite('keyboardMapper - WINDOWS ru', () => { }); suite('keyboardMapper - misc', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('issue #23513: Toggle Sidebar Visibility and Go to Line display same key mapping in Arabic keyboard', () => { const mapper = new WindowsKeyboardMapper(false, { 'KeyB': { From e9b79cde5e9174b616d8a2bf99675fd37ff0acbe Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:07:01 +0100 Subject: [PATCH 0767/1897] Add method for auxillary window to figure out its titlebar layout (#203778) * Provide a method on layout service to know if custom title bar should be visible Fixes #203460 * export title bar setting to be used in aux window layout * adopt in aux windows * fix * fix unit test failure --------- Co-authored-by: Benjamin Pasero --- src/vs/workbench/browser/layout.ts | 114 +++--------------- .../parts/editor/auxiliaryEditorPart.ts | 64 ++++++---- .../services/layout/browser/layoutService.ts | 86 +++++++++++++ 3 files changed, 147 insertions(+), 117 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 11fe93c2cf6..a2b6007fb79 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -8,11 +8,11 @@ import { Event, Emitter } from 'vs/base/common/event'; import { EventType, addDisposableListener, getClientArea, position, size, IDimension, isAncestorUsingFlowTo, computeScreenAwareSize, getActiveDocument, getWindows, getActiveWindow, focusWindow, isActiveDocument, getWindow, getWindowId, getActiveElement } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen, isWCOEnabled } from 'vs/base/browser/browser'; import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; -import { isWindows, isLinux, isMacintosh, isWeb, isNative, isIOS } from 'vs/base/common/platform'; +import { isWindows, isLinux, isMacintosh, isWeb, isIOS } from 'vs/base/common/platform'; import { EditorInputCapabilities, GroupIdentifier, isResourceEditorInput, IUntypedEditorInput, pathsToEditors } from 'vs/workbench/common/editor'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; -import { Position, Parts, PanelOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, panelOpensMaximizedFromString, PanelAlignment, ActivityBarPosition, LayoutSettings, MULTI_WINDOW_PARTS, SINGLE_WINDOW_PARTS, ZenModeSettings, EditorTabsMode, EditorActionsLocation } from 'vs/workbench/services/layout/browser/layoutService'; +import { Position, Parts, PanelOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, panelOpensMaximizedFromString, PanelAlignment, ActivityBarPosition, LayoutSettings, MULTI_WINDOW_PARTS, SINGLE_WINDOW_PARTS, ZenModeSettings, EditorTabsMode, EditorActionsLocation, shouldShowCustomTitleBar } from 'vs/workbench/services/layout/browser/layoutService'; import { isTemporaryWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -117,6 +117,16 @@ interface IInitialEditorsState { readonly layout?: EditorGroupLayout; } +export const TITLE_BAR_SETTINGS = [ + LayoutSettings.ACTIVITY_BAR_LOCATION, + LayoutSettings.COMMAND_CENTER, + LayoutSettings.EDITOR_ACTIONS_LOCATION, + LayoutSettings.LAYOUT_ACTIONS, + 'window.menuBarVisibility', + TitleBarSetting.TITLE_BAR_STYLE, + TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, +]; + export abstract class Layout extends Disposable implements IWorkbenchLayoutService { declare readonly _serviceBrand: undefined; @@ -344,15 +354,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Configuration changes this._register(this.configurationService.onDidChangeConfiguration((e) => { if ([ - LayoutSettings.ACTIVITY_BAR_LOCATION, - LayoutSettings.COMMAND_CENTER, - LayoutSettings.EDITOR_ACTIONS_LOCATION, - LayoutSettings.LAYOUT_ACTIONS, + ...TITLE_BAR_SETTINGS, LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION, LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE, - 'window.menuBarVisibility', - TitleBarSetting.TITLE_BAR_STYLE, - TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, ].some(setting => e.affectsConfiguration(setting))) { // Show Custom TitleBar if actions moved to the titlebar const activityBarMovedToTop = e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION) && this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP; @@ -415,12 +419,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // The menu bar toggles the title bar in web because it does not need to be shown for window controls only if (isWeb && menuBarVisibility === 'toggle') { - this.workbenchGrid.setViewVisible(this.titleBarPartView, this.shouldShowTitleBar()); + this.workbenchGrid.setViewVisible(this.titleBarPartView, shouldShowCustomTitleBar(this.configurationService, mainWindow, this.state.runtime.menuBar.toggled)); } // The menu bar toggles the title bar in full screen for toggle and classic settings else if (this.state.runtime.mainWindowFullscreen && (menuBarVisibility === 'toggle' || menuBarVisibility === 'classic')) { - this.workbenchGrid.setViewVisible(this.titleBarPartView, this.shouldShowTitleBar()); + this.workbenchGrid.setViewVisible(this.titleBarPartView, shouldShowCustomTitleBar(this.configurationService, mainWindow, this.state.runtime.menuBar.toggled)); } // Move layout call to any time the menubar @@ -470,7 +474,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (hasCustomTitlebar(this.configurationService)) { // Propagate to grid - this.workbenchGrid.setViewVisible(this.titleBarPartView, this.shouldShowTitleBar()); + this.workbenchGrid.setViewVisible(this.titleBarPartView, shouldShowCustomTitleBar(this.configurationService, mainWindow, this.state.runtime.menuBar.toggled)); this.updateWindowsBorder(true); } @@ -1209,7 +1213,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi switch (part) { case Parts.TITLEBAR_PART: - return this.shouldShowTitleBar(); + return shouldShowCustomTitleBar(this.configurationService, mainWindow, this.state.runtime.menuBar.toggled); case Parts.SIDEBAR_PART: return !this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN); case Parts.PANEL_PART: @@ -1227,84 +1231,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } - private shouldShowTitleBar(): boolean { - - if (!hasCustomTitlebar(this.configurationService)) { - return false; - } - - const nativeTitleBarEnabled = hasNativeTitlebar(this.configurationService); - const showCustomTitleBar = this.configurationService.getValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY); - if (showCustomTitleBar === CustomTitleBarVisibility.NEVER && nativeTitleBarEnabled || showCustomTitleBar === CustomTitleBarVisibility.WINDOWED && this.state.runtime.mainWindowFullscreen) { - return false; - } - - if (!this.isTitleBarEmpty()) { - return true; - } - - // Hide custom title bar when native title bar enabled and custom title bar is empty - if (nativeTitleBarEnabled) { - return false; - } - - // macOS desktop does not need a title bar when full screen - if (isMacintosh && isNative) { - return !this.state.runtime.mainWindowFullscreen; - } - - // non-fullscreen native must show the title bar - if (isNative && !this.state.runtime.mainWindowFullscreen) { - return true; - } - - // if WCO is visible, we have to show the title bar - if (isWCOEnabled() && !this.state.runtime.mainWindowFullscreen) { - return true; - } - - // remaining behavior is based on menubar visibility - switch (getMenuBarVisibility(this.configurationService)) { - case 'classic': - return !this.state.runtime.mainWindowFullscreen || this.state.runtime.menuBar.toggled; - case 'compact': - case 'hidden': - return false; - case 'toggle': - return this.state.runtime.menuBar.toggled; - case 'visible': - return true; - default: - return isWeb ? false : !this.state.runtime.mainWindowFullscreen || this.state.runtime.menuBar.toggled; - } - } - - private isTitleBarEmpty(): boolean { - // with the command center enabled, we should always show - if (this.configurationService.getValue(LayoutSettings.COMMAND_CENTER)) { - return false; - } - - // with the activity bar on top, we should always show - if (this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP) { - return false; - } - - // with the editor actions on top, we should always show - const editorActionsLocation = this.configurationService.getValue(LayoutSettings.EDITOR_ACTIONS_LOCATION); - const editorTabsMode = this.configurationService.getValue(LayoutSettings.EDITOR_TABS_MODE); - if (editorActionsLocation === EditorActionsLocation.TITLEBAR || editorActionsLocation === EditorActionsLocation.DEFAULT && editorTabsMode === EditorTabsMode.NONE) { - return false; - } - - // with the layout actions on top, we should always show - if (this.configurationService.getValue(LayoutSettings.LAYOUT_ACTIONS)) { - return false; - } - - return true; - } - private shouldShowBannerFirst(): boolean { return isWeb && !isWCOEnabled(); } @@ -2087,14 +2013,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } updateMenubarVisibility(skipLayout: boolean): void { - const shouldShowTitleBar = this.shouldShowTitleBar(); + const shouldShowTitleBar = shouldShowCustomTitleBar(this.configurationService, mainWindow, this.state.runtime.menuBar.toggled); if (!skipLayout && this.workbenchGrid && shouldShowTitleBar !== this.isVisible(Parts.TITLEBAR_PART, mainWindow)) { this.workbenchGrid.setViewVisible(this.titleBarPartView, shouldShowTitleBar); } } updateCustomTitleBarVisibility(): void { - const shouldShowTitleBar = this.shouldShowTitleBar(); + const shouldShowTitleBar = shouldShowCustomTitleBar(this.configurationService, mainWindow, this.state.runtime.menuBar.toggled); const titlebarVisible = this.isVisible(Parts.TITLEBAR_PART); if (shouldShowTitleBar !== titlebarVisible) { this.workbenchGrid.setViewVisible(this.titleBarPartView, shouldShowTitleBar); @@ -2252,7 +2178,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.workbenchGrid.moveView(this.bannerPartView, Sizing.Distribute, this.titleBarPartView, shouldBannerBeFirst ? Direction.Up : Direction.Down); } - this.workbenchGrid.setViewVisible(this.titleBarPartView, this.shouldShowTitleBar()); + this.workbenchGrid.setViewVisible(this.titleBarPartView, shouldShowCustomTitleBar(this.configurationService, mainWindow, this.state.runtime.menuBar.toggled)); } private arrangeEditorNodes(nodes: { editor: ISerializedNode; sideBar?: ISerializedNode; auxiliaryBar?: ISerializedNode }, availableHeight: number, availableWidth: number): ISerializedNode { diff --git a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts index 3c6553b23fb..7e4dd2aeddf 100644 --- a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isFullscreen, onDidChangeFullscreen } from 'vs/base/browser/browser'; +import { onDidChangeFullscreen } from 'vs/base/browser/browser'; import { hide, show } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -14,7 +14,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { hasNativeTitlebar } from 'vs/platform/window/common/window'; +import { hasCustomTitlebar } from 'vs/platform/window/common/window'; import { IEditorGroupView, IEditorPartsView } from 'vs/workbench/browser/parts/editor/editor'; import { EditorPart, IEditorPartUIState } from 'vs/workbench/browser/parts/editor/editorPart'; import { IAuxiliaryTitlebarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; @@ -23,7 +23,7 @@ import { IAuxiliaryWindowOpenOptions, IAuxiliaryWindowService } from 'vs/workben import { GroupDirection, GroupsOrder, IAuxiliaryEditorPart } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService, shouldShowCustomTitleBar } from 'vs/workbench/services/layout/browser/layoutService'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IStatusbarService } from 'vs/workbench/services/statusbar/browser/statusbar'; import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; @@ -50,7 +50,8 @@ export class AuxiliaryEditorPart { @IConfigurationService private readonly configurationService: IConfigurationService, @IStatusbarService private readonly statusbarService: IStatusbarService, @ITitleService private readonly titleService: ITitleService, - @IEditorService private readonly editorService: IEditorService + @IEditorService private readonly editorService: IEditorService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { } @@ -59,11 +60,11 @@ export class AuxiliaryEditorPart { function computeEditorPartHeightOffset(): number { let editorPartHeightOffset = 0; - if (statusBarVisible) { + if (statusbarVisible) { editorPartHeightOffset += statusbarPart.height; } - if (titlebarPart && titlebarPartVisible) { + if (titlebarPart && titlebarVisible) { editorPartHeightOffset += titlebarPart.height; } @@ -71,7 +72,7 @@ export class AuxiliaryEditorPart { } function updateStatusbarVisibility(fromEvent: boolean): void { - if (statusBarVisible) { + if (statusbarVisible) { show(statusbarPart.container); } else { hide(statusbarPart.container); @@ -82,6 +83,22 @@ export class AuxiliaryEditorPart { } } + function updateTitlebarVisibility(fromEvent: boolean): void { + if (!titlebarPart) { + return; + } + + if (titlebarVisible) { + show(titlebarPart.container); + } else { + hide(titlebarPart.container); + } + + if (fromEvent) { + auxiliaryWindow.layout(); + } + } + const disposables = new DisposableStore(); // Auxiliary Window @@ -100,40 +117,41 @@ export class AuxiliaryEditorPart { // Titlebar let titlebarPart: IAuxiliaryTitlebarPart | undefined = undefined; - let titlebarPartVisible = false; - const useCustomTitle = isNative && !hasNativeTitlebar(this.configurationService); // custom title in aux windows only enabled in native + let titlebarVisible = false; + const useCustomTitle = isNative && hasCustomTitlebar(this.configurationService); // custom title in aux windows only enabled in native if (useCustomTitle) { titlebarPart = disposables.add(this.titleService.createAuxiliaryTitlebarPart(auxiliaryWindow.container, editorPart)); - titlebarPartVisible = true; + titlebarVisible = shouldShowCustomTitleBar(this.configurationService, auxiliaryWindow.window); + + const handleTitleBarVisibilityEvent = () => { + const oldTitlebarPartVisible = titlebarVisible; + titlebarVisible = shouldShowCustomTitleBar(this.configurationService, auxiliaryWindow.window); + if (oldTitlebarPartVisible !== titlebarVisible) { + updateTitlebarVisibility(true); + } + }; disposables.add(titlebarPart.onDidChange(() => auxiliaryWindow.layout())); + disposables.add(this.layoutService.onDidChangePartVisibility(() => handleTitleBarVisibilityEvent())); disposables.add(onDidChangeFullscreen(windowId => { if (windowId !== auxiliaryWindow.window.vscodeWindowId) { return; // ignore all but our window } - // Make sure to hide the custom title when we enter - // fullscren mode and show it when we lave it. - - const fullscreen = isFullscreen(auxiliaryWindow.window); - const oldTitlebarPartVisible = titlebarPartVisible; - titlebarPartVisible = !fullscreen; - if (titlebarPart && oldTitlebarPartVisible !== titlebarPartVisible) { - titlebarPart.container.style.display = titlebarPartVisible ? '' : 'none'; - - auxiliaryWindow.layout(); - } + handleTitleBarVisibilityEvent(); })); + + updateTitlebarVisibility(false); } else { disposables.add(this.instantiationService.createInstance(WindowTitle, auxiliaryWindow.window, editorPart)); } // Statusbar const statusbarPart = disposables.add(this.statusbarService.createAuxiliaryStatusbarPart(auxiliaryWindow.container)); - let statusBarVisible = this.configurationService.getValue(AuxiliaryEditorPart.STATUS_BAR_VISIBILITY) !== false; + let statusbarVisible = this.configurationService.getValue(AuxiliaryEditorPart.STATUS_BAR_VISIBILITY) !== false; disposables.add(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(AuxiliaryEditorPart.STATUS_BAR_VISIBILITY)) { - statusBarVisible = this.configurationService.getValue(AuxiliaryEditorPart.STATUS_BAR_VISIBILITY) !== false; + statusbarVisible = this.configurationService.getValue(AuxiliaryEditorPart.STATUS_BAR_VISIBILITY) !== false; updateStatusbarVisibility(true); } diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index fe70a7cf89a..0a397839220 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -9,6 +9,11 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; import { IDimension } from 'vs/base/browser/dom'; import { Direction } from 'vs/base/browser/ui/grid/grid'; +import { isMacintosh, isNative, isWeb } from 'vs/base/common/platform'; +import { isAuxiliaryWindow } from 'vs/base/browser/window'; +import { CustomTitleBarVisibility, TitleBarSetting, getMenuBarVisibility, hasCustomTitlebar, hasNativeTitlebar } from 'vs/platform/window/common/window'; +import { isFullscreen, isWCOEnabled } from 'vs/base/browser/browser'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export const IWorkbenchLayoutService = refineServiceDecorator(ILayoutService); @@ -303,3 +308,84 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ getVisibleNeighborPart(part: Parts, direction: Direction): Parts | undefined; } + +export function shouldShowCustomTitleBar(configurationService: IConfigurationService, window: Window, menuBarToggled?: boolean): boolean { + + if (!hasCustomTitlebar(configurationService)) { + return false; + } + + const inFullscreen = isFullscreen(window); + const nativeTitleBarEnabled = hasNativeTitlebar(configurationService); + + const showCustomTitleBar = configurationService.getValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY); + if (showCustomTitleBar === CustomTitleBarVisibility.NEVER && nativeTitleBarEnabled || showCustomTitleBar === CustomTitleBarVisibility.WINDOWED && inFullscreen) { + return false; + } + + if (!isTitleBarEmpty(configurationService)) { + return true; + } + + // Hide custom title bar when native title bar enabled and custom title bar is empty + if (nativeTitleBarEnabled) { + return false; + } + + // macOS desktop does not need a title bar when full screen + if (isMacintosh && isNative) { + return !inFullscreen; + } + + // non-fullscreen native must show the title bar + if (isNative && !inFullscreen) { + return true; + } + + // if WCO is visible, we have to show the title bar + if (isWCOEnabled() && !inFullscreen) { + return true; + } + + // remaining behavior is based on menubar visibility + const menuBarVisibility = !isAuxiliaryWindow(window) ? getMenuBarVisibility(configurationService) : 'hidden'; + switch (menuBarVisibility) { + case 'classic': + return !inFullscreen || !!menuBarToggled; + case 'compact': + case 'hidden': + return false; + case 'toggle': + return !!menuBarToggled; + case 'visible': + return true; + default: + return isWeb ? false : !inFullscreen || !!menuBarToggled; + } +} + +function isTitleBarEmpty(configurationService: IConfigurationService): boolean { + // with the command center enabled, we should always show + if (configurationService.getValue(LayoutSettings.COMMAND_CENTER)) { + return false; + } + + // with the activity bar on top, we should always show + if (configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP) { + return false; + } + + // with the editor actions on top, we should always show + const editorActionsLocation = configurationService.getValue(LayoutSettings.EDITOR_ACTIONS_LOCATION); + const editorTabsMode = configurationService.getValue(LayoutSettings.EDITOR_TABS_MODE); + if (editorActionsLocation === EditorActionsLocation.TITLEBAR || editorActionsLocation === EditorActionsLocation.DEFAULT && editorTabsMode === EditorTabsMode.NONE) { + return false; + } + + // with the layout actions on top, we should always show + if (configurationService.getValue(LayoutSettings.LAYOUT_ACTIONS)) { + return false; + } + + return true; +} From 85cc2e53a33cf03d0a3dbabeb0f536fd1a8ecd8c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 31 Jan 2024 15:07:20 +0100 Subject: [PATCH 0768/1897] fix #201286 (#203888) * fix #201286 * fix tests --- .../configuration/common/configuration.ts | 22 +-- .../common/configurationModels.ts | 133 ++++++++++-------- .../test/common/configurationModels.test.ts | 57 +++++--- src/vs/workbench/common/configuration.ts | 49 ++++--- 4 files changed, 150 insertions(+), 111 deletions(-) diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index ca25bf1cad3..559b3c18b55 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -70,6 +70,12 @@ export interface IConfigurationChangeEvent { affectsConfiguration(configuration: string, overrides?: IConfigurationOverrides): boolean; } +export interface IInspectValue { + readonly value?: T; + readonly override?: T; + readonly overrides?: { readonly identifiers: string[]; readonly value: T }[]; +} + export interface IConfigurationValue { readonly defaultValue?: T; @@ -83,14 +89,14 @@ export interface IConfigurationValue { readonly policyValue?: T; readonly value?: T; - readonly default?: { value?: T; override?: T }; - readonly application?: { value?: T; override?: T }; - readonly user?: { value?: T; override?: T }; - readonly userLocal?: { value?: T; override?: T }; - readonly userRemote?: { value?: T; override?: T }; - readonly workspace?: { value?: T; override?: T }; - readonly workspaceFolder?: { value?: T; override?: T }; - readonly memory?: { value?: T; override?: T }; + readonly default?: IInspectValue; + readonly application?: IInspectValue; + readonly user?: IInspectValue; + readonly userLocal?: IInspectValue; + readonly userRemote?: IInspectValue; + readonly workspace?: IInspectValue; + readonly workspaceFolder?: IInspectValue; + readonly memory?: IInspectValue; readonly policy?: { value?: T }; readonly overrideIdentifiers?: string[]; diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 09d12675520..db1f4ee7006 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -13,7 +13,7 @@ import * as objects from 'vs/base/common/objects'; import { IExtUri } from 'vs/base/common/resources'; import * as types from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { addToValueTree, ConfigurationTarget, getConfigurationValue, IConfigurationChange, IConfigurationChangeEvent, IConfigurationCompareResult, IConfigurationData, IConfigurationModel, IConfigurationOverrides, IConfigurationUpdateOverrides, IConfigurationValue, IOverrides, removeFromValueTree, toValuesTree } from 'vs/platform/configuration/common/configuration'; +import { addToValueTree, ConfigurationTarget, getConfigurationValue, IConfigurationChange, IConfigurationChangeEvent, IConfigurationCompareResult, IConfigurationData, IConfigurationModel, IConfigurationOverrides, IConfigurationUpdateOverrides, IConfigurationValue, IInspectValue, IOverrides, removeFromValueTree, toValuesTree } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope, Extensions, IConfigurationPropertySchema, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { FileOperation, IFileService } from 'vs/platform/files/common/files'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -23,11 +23,7 @@ function freeze(data: T): T { return Object.isFrozen(data) ? data : objects.deepFreeze(data); } -interface IInspectValue { - value?: V; - override?: V; - merged?: V; -} +type InspectValue = IInspectValue & { merged?: V }; export class ConfigurationModel implements IConfigurationModel { @@ -82,11 +78,29 @@ export class ConfigurationModel implements IConfigurationModel { return section ? getConfigurationValue(this.contents, section) : this.contents; } - inspect(section: string | undefined, overrideIdentifier?: string | null): IInspectValue { - const value = this.rawConfiguration.getValue(section); - const override = overrideIdentifier ? this.rawConfiguration.getOverrideValue(section, overrideIdentifier) : undefined; - const merged = overrideIdentifier ? this.rawConfiguration.override(overrideIdentifier).getValue(section) : value; - return { value, override, merged }; + inspect(section: string | undefined, overrideIdentifier?: string | null): InspectValue { + const that = this; + return { + get value() { + return freeze(that.rawConfiguration.getValue(section)); + }, + get override() { + return overrideIdentifier ? freeze(that.rawConfiguration.getOverrideValue(section, overrideIdentifier)) : undefined; + }, + get merged() { + return freeze(overrideIdentifier ? that.rawConfiguration.override(overrideIdentifier).getValue(section) : that.rawConfiguration.getValue(section)); + }, + get overrides() { + const overrides: { readonly identifiers: string[]; readonly value: V }[] = []; + for (const { contents, identifiers, keys } of that.rawConfiguration.overrides) { + const value = new ConfigurationModel(contents, keys).getValue(section); + if (value !== undefined) { + overrides.push({ identifiers, value }); + } + } + return overrides.length ? freeze(overrides) : undefined; + } + }; } getOverrideValue(section: string | undefined, overrideIdentifier: string): V | undefined { @@ -504,19 +518,14 @@ class ConfigurationInspectValue implements IConfigurationValue { return freeze(this._value); } - private inspect(model: ConfigurationModel, section: string | undefined, overrideIdentifier?: string | null): IInspectValue { - const inspectValue = model.inspect(section, overrideIdentifier); - return { - get value() { return freeze(inspectValue.value); }, - get override() { return freeze(inspectValue.override); }, - get merged() { return freeze(inspectValue.merged); } - }; + private toInspectValue(inspectValue: IInspectValue | undefined | null): IInspectValue | undefined { + return inspectValue?.value !== undefined || inspectValue?.override !== undefined || inspectValue?.overrides !== undefined ? inspectValue : undefined; } - private _defaultInspectValue: IInspectValue | undefined; - private get defaultInspectValue(): IInspectValue { + private _defaultInspectValue: InspectValue | undefined; + private get defaultInspectValue(): InspectValue { if (!this._defaultInspectValue) { - this._defaultInspectValue = this.inspect(this.defaultConfiguration, this.key, this.overrides.overrideIdentifier); + this._defaultInspectValue = this.defaultConfiguration.inspect(this.key, this.overrides.overrideIdentifier); } return this._defaultInspectValue; } @@ -525,14 +534,14 @@ class ConfigurationInspectValue implements IConfigurationValue { return this.defaultInspectValue.merged; } - get default(): { value?: V; override?: V } | undefined { - return this.defaultInspectValue.value !== undefined || this.defaultInspectValue.override !== undefined ? { value: this.defaultInspectValue.value, override: this.defaultInspectValue.override } : undefined; + get default(): IInspectValue | undefined { + return this.toInspectValue(this.defaultInspectValue); } - private _policyInspectValue: IInspectValue | undefined | null; - private get policyInspectValue(): IInspectValue | null { + private _policyInspectValue: InspectValue | undefined | null; + private get policyInspectValue(): InspectValue | null { if (this._policyInspectValue === undefined) { - this._policyInspectValue = this.policyConfiguration ? this.inspect(this.policyConfiguration, this.key) : null; + this._policyInspectValue = this.policyConfiguration ? this.policyConfiguration.inspect(this.key) : null; } return this._policyInspectValue; } @@ -541,14 +550,14 @@ class ConfigurationInspectValue implements IConfigurationValue { return this.policyInspectValue?.merged; } - get policy(): { value?: V; override?: V } | undefined { + get policy(): IInspectValue | undefined { return this.policyInspectValue?.value !== undefined ? { value: this.policyInspectValue.value } : undefined; } - private _applicationInspectValue: IInspectValue | undefined | null; - private get applicationInspectValue(): IInspectValue | null { + private _applicationInspectValue: InspectValue | undefined | null; + private get applicationInspectValue(): InspectValue | null { if (this._applicationInspectValue === undefined) { - this._applicationInspectValue = this.applicationConfiguration ? this.inspect(this.applicationConfiguration, this.key) : null; + this._applicationInspectValue = this.applicationConfiguration ? this.applicationConfiguration.inspect(this.key) : null; } return this._applicationInspectValue; } @@ -557,14 +566,14 @@ class ConfigurationInspectValue implements IConfigurationValue { return this.applicationInspectValue?.merged; } - get application(): { value?: V; override?: V } | undefined { - return this.applicationInspectValue?.value !== undefined || this.applicationInspectValue?.override !== undefined ? { value: this.applicationInspectValue.value, override: this.applicationInspectValue.override } : undefined; + get application(): IInspectValue | undefined { + return this.toInspectValue(this.applicationInspectValue); } - private _userInspectValue: IInspectValue | undefined; - private get userInspectValue(): IInspectValue { + private _userInspectValue: InspectValue | undefined; + private get userInspectValue(): InspectValue { if (!this._userInspectValue) { - this._userInspectValue = this.inspect(this.userConfiguration, this.key, this.overrides.overrideIdentifier); + this._userInspectValue = this.userConfiguration.inspect(this.key, this.overrides.overrideIdentifier); } return this._userInspectValue; } @@ -573,14 +582,14 @@ class ConfigurationInspectValue implements IConfigurationValue { return this.userInspectValue.merged; } - get user(): { value?: V; override?: V } | undefined { - return this.userInspectValue.value !== undefined || this.userInspectValue.override !== undefined ? { value: this.userInspectValue.value, override: this.userInspectValue.override } : undefined; + get user(): IInspectValue | undefined { + return this.toInspectValue(this.userInspectValue); } - private _userLocalInspectValue: IInspectValue | undefined; - private get userLocalInspectValue(): IInspectValue { + private _userLocalInspectValue: InspectValue | undefined; + private get userLocalInspectValue(): InspectValue { if (!this._userLocalInspectValue) { - this._userLocalInspectValue = this.inspect(this.localUserConfiguration, this.key, this.overrides.overrideIdentifier); + this._userLocalInspectValue = this.localUserConfiguration.inspect(this.key, this.overrides.overrideIdentifier); } return this._userLocalInspectValue; } @@ -589,14 +598,14 @@ class ConfigurationInspectValue implements IConfigurationValue { return this.userLocalInspectValue.merged; } - get userLocal(): { value?: V; override?: V } | undefined { - return this.userLocalInspectValue.value !== undefined || this.userLocalInspectValue.override !== undefined ? { value: this.userLocalInspectValue.value, override: this.userLocalInspectValue.override } : undefined; + get userLocal(): IInspectValue | undefined { + return this.toInspectValue(this.userLocalInspectValue); } - private _userRemoteInspectValue: IInspectValue | undefined; - private get userRemoteInspectValue(): IInspectValue { + private _userRemoteInspectValue: InspectValue | undefined; + private get userRemoteInspectValue(): InspectValue { if (!this._userRemoteInspectValue) { - this._userRemoteInspectValue = this.inspect(this.remoteUserConfiguration, this.key, this.overrides.overrideIdentifier); + this._userRemoteInspectValue = this.remoteUserConfiguration.inspect(this.key, this.overrides.overrideIdentifier); } return this._userRemoteInspectValue; } @@ -605,14 +614,14 @@ class ConfigurationInspectValue implements IConfigurationValue { return this.userRemoteInspectValue.merged; } - get userRemote(): { value?: V; override?: V } | undefined { - return this.userRemoteInspectValue.value !== undefined || this.userRemoteInspectValue.override !== undefined ? { value: this.userRemoteInspectValue.value, override: this.userRemoteInspectValue.override } : undefined; + get userRemote(): IInspectValue | undefined { + return this.toInspectValue(this.userRemoteInspectValue); } - private _workspaceInspectValue: IInspectValue | undefined | null; - private get workspaceInspectValue(): IInspectValue | null { + private _workspaceInspectValue: InspectValue | undefined | null; + private get workspaceInspectValue(): InspectValue | null { if (this._workspaceInspectValue === undefined) { - this._workspaceInspectValue = this.workspaceConfiguration ? this.inspect(this.workspaceConfiguration, this.key, this.overrides.overrideIdentifier) : null; + this._workspaceInspectValue = this.workspaceConfiguration ? this.workspaceConfiguration.inspect(this.key, this.overrides.overrideIdentifier) : null; } return this._workspaceInspectValue; } @@ -621,14 +630,14 @@ class ConfigurationInspectValue implements IConfigurationValue { return this.workspaceInspectValue?.merged; } - get workspace(): { value?: V; override?: V } | undefined { - return this.workspaceInspectValue?.value !== undefined || this.workspaceInspectValue?.override !== undefined ? { value: this.workspaceInspectValue.value, override: this.workspaceInspectValue.override } : undefined; + get workspace(): IInspectValue | undefined { + return this.toInspectValue(this.workspaceInspectValue); } - private _workspaceFolderInspectValue: IInspectValue | undefined | null; - private get workspaceFolderInspectValue(): IInspectValue | null { + private _workspaceFolderInspectValue: InspectValue | undefined | null; + private get workspaceFolderInspectValue(): InspectValue | null { if (this._workspaceFolderInspectValue === undefined) { - this._workspaceFolderInspectValue = this.folderConfigurationModel ? this.inspect(this.folderConfigurationModel, this.key, this.overrides.overrideIdentifier) : null; + this._workspaceFolderInspectValue = this.folderConfigurationModel ? this.folderConfigurationModel.inspect(this.key, this.overrides.overrideIdentifier) : null; } return this._workspaceFolderInspectValue; } @@ -637,14 +646,14 @@ class ConfigurationInspectValue implements IConfigurationValue { return this.workspaceFolderInspectValue?.merged; } - get workspaceFolder(): { value?: V; override?: V } | undefined { - return this.workspaceFolderInspectValue?.value !== undefined || this.workspaceFolderInspectValue?.override !== undefined ? { value: this.workspaceFolderInspectValue.value, override: this.workspaceFolderInspectValue.override } : undefined; + get workspaceFolder(): IInspectValue | undefined { + return this.toInspectValue(this.workspaceFolderInspectValue); } - private _memoryInspectValue: IInspectValue | undefined; - private get memoryInspectValue(): IInspectValue { + private _memoryInspectValue: InspectValue | undefined; + private get memoryInspectValue(): InspectValue { if (this._memoryInspectValue === undefined) { - this._memoryInspectValue = this.inspect(this.memoryConfigurationModel, this.key, this.overrides.overrideIdentifier); + this._memoryInspectValue = this.memoryConfigurationModel.inspect(this.key, this.overrides.overrideIdentifier); } return this._memoryInspectValue; } @@ -653,8 +662,8 @@ class ConfigurationInspectValue implements IConfigurationValue { return this.memoryInspectValue.merged; } - get memory(): { value?: V; override?: V } | undefined { - return this.memoryInspectValue.value !== undefined || this.memoryInspectValue.override !== undefined ? { value: this.memoryInspectValue.value, override: this.memoryInspectValue.override } : undefined; + get memory(): IInspectValue | undefined { + return this.toInspectValue(this.memoryInspectValue); } } diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index c1a5a0623c6..605ae8b6517 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -347,10 +347,10 @@ suite('ConfigurationModel', () => { test('inspect when raw is same', () => { const testObject = new ConfigurationModel({ 'a': 1, 'c': 1 }, ['a', 'c'], [{ identifiers: ['x', 'y'], contents: { 'a': 2, 'b': 1 }, keys: ['a'] }]); - assert.deepStrictEqual(testObject.inspect('a'), { value: 1, override: undefined, merged: 1 }); - assert.deepStrictEqual(testObject.inspect('a', 'x'), { value: 1, override: 2, merged: 2 }); - assert.deepStrictEqual(testObject.inspect('b', 'x'), { value: undefined, override: 1, merged: 1 }); - assert.deepStrictEqual(testObject.inspect('d'), { value: undefined, override: undefined, merged: undefined }); + assert.deepStrictEqual(testObject.inspect('a'), { value: 1, override: undefined, merged: 1, overrides: [{ identifiers: ['x', 'y'], value: 2 }] }); + assert.deepStrictEqual(testObject.inspect('a', 'x'), { value: 1, override: 2, merged: 2, overrides: [{ identifiers: ['x', 'y'], value: 2 }] }); + assert.deepStrictEqual(testObject.inspect('b', 'x'), { value: undefined, override: 1, merged: 1, overrides: [{ identifiers: ['x', 'y'], value: 1 }] }); + assert.deepStrictEqual(testObject.inspect('d'), { value: undefined, override: undefined, merged: undefined, overrides: undefined }); }); test('inspect when raw is not same', () => { @@ -365,11 +365,11 @@ suite('ConfigurationModel', () => { } }]); - assert.deepStrictEqual(testObject.inspect('a'), { value: 1, override: undefined, merged: 1 }); - assert.deepStrictEqual(testObject.inspect('a', 'x'), { value: 1, override: 2, merged: 2 }); - assert.deepStrictEqual(testObject.inspect('b', 'x'), { value: 2, override: 1, merged: 1 }); - assert.deepStrictEqual(testObject.inspect('d'), { value: 3, override: undefined, merged: 3 }); - assert.deepStrictEqual(testObject.inspect('e'), { value: undefined, override: undefined, merged: undefined }); + assert.deepStrictEqual(testObject.inspect('a'), { value: 1, override: undefined, merged: 1, overrides: [{ identifiers: ['x', 'y'], value: 2 }] }); + assert.deepStrictEqual(testObject.inspect('a', 'x'), { value: 1, override: 2, merged: 2, overrides: [{ identifiers: ['x', 'y'], value: 2 }] }); + assert.deepStrictEqual(testObject.inspect('b', 'x'), { value: 2, override: 1, merged: 1, overrides: [{ identifiers: ['x', 'y'], value: 1 }] }); + assert.deepStrictEqual(testObject.inspect('d'), { value: 3, override: undefined, merged: 3, overrides: undefined }); + assert.deepStrictEqual(testObject.inspect('e'), { value: undefined, override: undefined, merged: undefined, overrides: undefined }); }); test('inspect in merged configuration when raw is same', () => { @@ -377,11 +377,11 @@ suite('ConfigurationModel', () => { const target2 = new ConfigurationModel({ 'b': 3 }, ['b'], []); const testObject = target1.merge(target2); - assert.deepStrictEqual(testObject.inspect('a'), { value: 1, override: undefined, merged: 1 }); - assert.deepStrictEqual(testObject.inspect('a', 'x'), { value: 1, override: 2, merged: 2 }); - assert.deepStrictEqual(testObject.inspect('b'), { value: 3, override: undefined, merged: 3 }); - assert.deepStrictEqual(testObject.inspect('b', 'y'), { value: 3, override: undefined, merged: 3 }); - assert.deepStrictEqual(testObject.inspect('c'), { value: undefined, override: undefined, merged: undefined }); + assert.deepStrictEqual(testObject.inspect('a'), { value: 1, override: undefined, merged: 1, overrides: [{ identifiers: ['x', 'y'], value: 2 }] }); + assert.deepStrictEqual(testObject.inspect('a', 'x'), { value: 1, override: 2, merged: 2, overrides: [{ identifiers: ['x', 'y'], value: 2 }] }); + assert.deepStrictEqual(testObject.inspect('b'), { value: 3, override: undefined, merged: 3, overrides: undefined }); + assert.deepStrictEqual(testObject.inspect('b', 'y'), { value: 3, override: undefined, merged: 3, overrides: undefined }); + assert.deepStrictEqual(testObject.inspect('c'), { value: undefined, override: undefined, merged: undefined, overrides: undefined }); }); test('inspect in merged configuration when raw is not same for one model', () => { @@ -397,11 +397,30 @@ suite('ConfigurationModel', () => { const target2 = new ConfigurationModel({ 'b': 3 }, ['b'], []); const testObject = target1.merge(target2); - assert.deepStrictEqual(testObject.inspect('a'), { value: 1, override: undefined, merged: 1 }); - assert.deepStrictEqual(testObject.inspect('a', 'x'), { value: 1, override: 2, merged: 2 }); - assert.deepStrictEqual(testObject.inspect('b'), { value: 3, override: undefined, merged: 3 }); - assert.deepStrictEqual(testObject.inspect('b', 'y'), { value: 3, override: 4, merged: 4 }); - assert.deepStrictEqual(testObject.inspect('c'), { value: 3, override: undefined, merged: 3 }); + assert.deepStrictEqual(testObject.inspect('a'), { value: 1, override: undefined, merged: 1, overrides: [{ identifiers: ['x', 'y'], value: 2 }] }); + assert.deepStrictEqual(testObject.inspect('a', 'x'), { value: 1, override: 2, merged: 2, overrides: [{ identifiers: ['x', 'y'], value: 2 }] }); + assert.deepStrictEqual(testObject.inspect('b'), { value: 3, override: undefined, merged: 3, overrides: [{ identifiers: ['x', 'y'], value: 4 }] }); + assert.deepStrictEqual(testObject.inspect('b', 'y'), { value: 3, override: 4, merged: 4, overrides: [{ identifiers: ['x', 'y'], value: 4 }] }); + assert.deepStrictEqual(testObject.inspect('c'), { value: 3, override: undefined, merged: 3, overrides: undefined }); + }); + + test('inspect: return all overrides', () => { + const testObject = new ConfigurationModel({ 'a': 1, 'c': 1 }, ['a', 'c'], [ + { identifiers: ['x', 'y'], contents: { 'a': 2, 'b': 1 }, keys: ['a', 'b'] }, + { identifiers: ['x'], contents: { 'a': 3 }, keys: ['a'] }, + { identifiers: ['y'], contents: { 'b': 3 }, keys: ['b'] } + ]); + + assert.deepStrictEqual(testObject.inspect('a').overrides, [ + { identifiers: ['x', 'y'], value: 2 }, + { identifiers: ['x'], value: 3 } + ]); + }); + + test('inspect when no overrides', () => { + const testObject = new ConfigurationModel({ 'a': 1, 'c': 1 }, ['a', 'c']); + + assert.strictEqual(testObject.inspect('a').overrides, undefined); }); }); diff --git a/src/vs/workbench/common/configuration.ts b/src/vs/workbench/common/configuration.ts index d9ebf02b99b..67d90393d59 100644 --- a/src/vs/workbench/common/configuration.ts +++ b/src/vs/workbench/common/configuration.ts @@ -8,7 +8,7 @@ import { ConfigurationScope, IConfigurationNode, IConfigurationRegistry, Extensi import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { ConfigurationTarget, IConfigurationOverrides, IConfigurationService, IConfigurationValue } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService, IConfigurationValue, IInspectValue } from 'vs/platform/configuration/common/configuration'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -117,24 +117,27 @@ export class ConfigurationMigrationWorkbenchContribution extends Disposable impl ['workspace', ConfigurationTarget.WORKSPACE], ]; for (const [dataKey, target] of targetPairs) { + const inspectValue = inspectData[dataKey] as IInspectValue | undefined; + if (!inspectValue) { + continue; + } + const migrationValues: [[string, ConfigurationValue], string[]][] = []; - // Collect migrations for language overrides - for (const overrideIdentifier of inspectData.overrideIdentifiers ?? []) { - const keyValuePairs = await this.runMigration(migration, { resource, overrideIdentifier }, dataKey); + if (inspectValue.value !== undefined) { + const keyValuePairs = await this.runMigration(migration, dataKey, inspectValue.value, resource, undefined); for (const keyValuePair of keyValuePairs ?? []) { - let keyValueAndOverridesPair = migrationValues.find(([[k, v]]) => k === keyValuePair[0] && equals(v.value, keyValuePair[1].value)); - if (!keyValueAndOverridesPair) { - migrationValues.push(keyValueAndOverridesPair = [keyValuePair, []]); - } - keyValueAndOverridesPair[1].push(overrideIdentifier); + migrationValues.push([keyValuePair, []]); } } - // Collect migrations - const keyValuePairs = await this.runMigration(migration, { resource }, dataKey, inspectData); - for (const keyValuePair of keyValuePairs ?? []) { - migrationValues.push([keyValuePair, []]); + for (const { identifiers, value } of inspectValue.overrides ?? []) { + if (value !== undefined) { + const keyValuePairs = await this.runMigration(migration, dataKey, value, resource, identifiers); + for (const keyValuePair of keyValuePairs ?? []) { + migrationValues.push([keyValuePair, identifiers]); + } + } } if (migrationValues.length) { @@ -145,16 +148,18 @@ export class ConfigurationMigrationWorkbenchContribution extends Disposable impl } } - private async runMigration(migration: ConfigurationMigration, overrides: IConfigurationOverrides, dataKey: keyof IConfigurationValue, data?: IConfigurationValue): Promise { - const valueAccessor = (key: string) => getInspectValue(this.configurationService.inspect(key, overrides)); - const getInspectValue = (data: IConfigurationValue) => { - const inspectValue: { value?: any; override?: any } | undefined = data[dataKey]; - return overrides.overrideIdentifier ? inspectValue?.override : inspectValue?.value; + private async runMigration(migration: ConfigurationMigration, dataKey: keyof IConfigurationValue, value: any, resource: URI | undefined, overrideIdentifiers: string[] | undefined): Promise { + const valueAccessor = (key: string) => { + const inspectData = this.configurationService.inspect(key, { resource }); + const inspectValue = inspectData[dataKey] as IInspectValue | undefined; + if (!inspectValue) { + return undefined; + } + if (!overrideIdentifiers) { + return inspectValue.value; + } + return inspectValue.overrides?.find(({ identifiers }) => equals(identifiers, overrideIdentifiers))?.value; }; - const value = data ? getInspectValue(data) : valueAccessor(migration.key); - if (value === undefined) { - return undefined; - } const result = await migration.migrateFn(value, valueAccessor); return Array.isArray(result) ? result : [[migration.key, result]]; } From 6d2c0170d739dcd5b3db8503ce2f429fec3b68b7 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Wed, 31 Jan 2024 06:11:40 -0800 Subject: [PATCH 0769/1897] fix: settings search box clears while searching (#203758) * fix: settings search box clears while searching * fix: use isTriggered instead of new variable * polish --- .../workbench/contrib/preferences/browser/settingsEditor2.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 63108a1a7dc..9df8e9b70d6 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -543,7 +543,8 @@ export class SettingsEditor2 extends EditorPane { this.searchWidget.setValue(filter); } - this.searchWidget.focus(selectAll); + // Do not select all if the user is already searching. + this.searchWidget.focus(selectAll && !this.searchInputDelayer.isTriggered); } clearSearchResults(): void { From 6aad7f2ec9f14bdc9c04a83ba587c3c05b3e3eb0 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 31 Jan 2024 15:13:58 +0100 Subject: [PATCH 0770/1897] move `StartSessionAction` back to /browser/, add indirection for holdForSpeech-feature (#203890) https://github.com/microsoft/vscode-copilot/issues/3857 --- .../browser/inlineChat.contribution.ts | 1 + .../inlineChat/browser/inlineChatActions.ts | 58 +++++++++++++-- .../inlineChat.contribution.ts | 3 +- .../electron-sandbox/inlineChatActions.ts | 71 +++++-------------- 4 files changed, 75 insertions(+), 58 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index e86fb8b900f..cc8dc4ca93d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -30,6 +30,7 @@ registerSingleton(IInlineChatSavingService, InlineChatSavingServiceImpl, Instant registerEditorContribution(INLINE_CHAT_ID, InlineChatController, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors registerEditorContribution(INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID, InlineChatActions.InlineAccessibilityHelpContribution, EditorContributionInstantiation.Eventually); +registerAction2(InlineChatActions.StartSessionAction); registerAction2(InlineChatActions.CloseAction); registerAction2(InlineChatActions.ConfigureInlineChatAction); registerAction2(InlineChatActions.UnstashSessionAction); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index f2402537afb..57e2376c569 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -4,18 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { Codicon } from 'vs/base/common/codicons'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_EDIT_MODE, EditMode, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, InlineChatResponseFeedbackKind, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, MENU_INLINE_CHAT_WIDGET } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { localize, localize2 } from 'vs/nls'; -import { IAction2Options, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { Action2, IAction2Options, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; @@ -37,6 +37,56 @@ CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT export const LOCALIZED_START_INLINE_CHAT_STRING = localize('run', 'Start Inline Chat'); export const START_INLINE_CHAT = registerIcon('start-inline-chat', Codicon.sparkle, localize('startInlineChat', 'Icon which spawns the inline chat from the editor toolbar.')); +// some gymnastics to enable hold for speech without moving the StartSessionAction into the electron-layer + +export interface IHoldForSpeech { + (accessor: ServicesAccessor, controller: InlineChatController, source: Action2): void; +} +let _holdForSpeech: IHoldForSpeech | undefined = undefined; +export function setHoldForSpeech(holdForSpeech: IHoldForSpeech) { + _holdForSpeech = holdForSpeech; +} + +export class StartSessionAction extends EditorAction2 { + + constructor() { + super({ + id: 'inlineChat.start', + title: { value: LOCALIZED_START_INLINE_CHAT_STRING, original: 'Start Inline Chat' }, + category: AbstractInlineChatAction.category, + f1: true, + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_PROVIDER, EditorContextKeys.writable), + keybinding: { + when: EditorContextKeys.focus, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.KeyI, + secondary: [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyI)], + }, + icon: START_INLINE_CHAT + }); + } + + + override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { + + const ctrl = InlineChatController.get(editor); + if (!ctrl) { + return; + } + + if (_holdForSpeech) { + accessor.get(IInstantiationService).invokeFunction(_holdForSpeech, ctrl, this); + } + + let options: InlineChatRunOptions | undefined; + const arg = _args[0]; + if (arg && InlineChatRunOptions.isInteractiveEditorOptions(arg)) { + options = arg; + } + InlineChatController.get(editor)?.run({ ...options }); + } +} + export class UnstashSessionAction extends EditorAction2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts index 4a543a8ea54..bb8c1bd4c3e 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution.ts @@ -6,11 +6,10 @@ import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { CancelAction, InlineChatQuickVoice, StartAction, StopAction } from 'vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice'; -import { StartSessionAction, HoldToSpeak } from './inlineChatActions'; +import { HoldToSpeak } from './inlineChatActions'; // start and hold for voice -registerAction2(StartSessionAction); registerAction2(HoldToSpeak); // quick voice diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts index 7af9b3971bc..d30d230a2b7 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts @@ -2,66 +2,24 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { AbstractInlineChatAction } from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; -import { LOCALIZED_START_INLINE_CHAT_STRING, START_INLINE_CHAT } from '../browser/inlineChatActions'; +import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import { AbstractInlineChatAction, setHoldForSpeech } from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; import { disposableTimeout } from 'vs/base/common/async'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { StartVoiceChatAction, StopListeningAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; import { IChatExecuteActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; -import { CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_VISIBLE, InlineChatConfigKeys } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { CTX_INLINE_CHAT_VISIBLE, InlineChatConfigKeys } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { HasSpeechProvider, ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; import { localize2 } from 'vs/nls'; - - -export class StartSessionAction extends EditorAction2 { - - constructor() { - super({ - id: 'inlineChat.start', - title: { value: LOCALIZED_START_INLINE_CHAT_STRING, original: 'Start Inline Chat' }, - category: AbstractInlineChatAction.category, - f1: true, - precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_PROVIDER, EditorContextKeys.writable), - keybinding: { - when: EditorContextKeys.focus, - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyCode.KeyI, - secondary: [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyI)], - }, - icon: START_INLINE_CHAT - }); - } - - - override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { - - const configService = accessor.get(IConfigurationService); - const speechService = accessor.get(ISpeechService); - - if (configService.getValue(InlineChatConfigKeys.HoldToSpeech) // enabled - && speechService.hasSpeechProvider // possible - ) { - holdForSpeech(accessor, InlineChatController.get(editor), this.desc.id); - } - - let options: InlineChatRunOptions | undefined; - const arg = _args[0]; - if (arg && InlineChatRunOptions.isInteractiveEditorOptions(arg)) { - options = arg; - } - InlineChatController.get(editor)?.run({ ...options }); - } -} +import { Action2 } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class HoldToSpeak extends AbstractInlineChatAction { @@ -79,17 +37,23 @@ export class HoldToSpeak extends AbstractInlineChatAction { } override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, editor: ICodeEditor, ...args: any[]): void { - holdForSpeech(accessor, ctrl, this.desc.id); + holdForSpeech(accessor, ctrl, this); } } -function holdForSpeech(accessor: ServicesAccessor, ctrl: InlineChatController | null, commandId: string): void { +function holdForSpeech(accessor: ServicesAccessor, ctrl: InlineChatController, action: Action2): void { + + const configService = accessor.get(IConfigurationService); + const speechService = accessor.get(ISpeechService); const keybindingService = accessor.get(IKeybindingService); const commandService = accessor.get(ICommandService); - if (!ctrl) { + + // enabled or possible? + if (!configService.getValue(InlineChatConfigKeys.HoldToSpeech || !speechService.hasSpeechProvider)) { return; } - const holdMode = keybindingService.enableKeybindingHoldMode(commandId); + + const holdMode = keybindingService.enableKeybindingHoldMode(action.desc.id); if (!holdMode) { return; } @@ -109,3 +73,6 @@ function holdForSpeech(accessor: ServicesAccessor, ctrl: InlineChatController | handle.dispose(); }); } + +// make this accessible to the chat actions from the browser layer +setHoldForSpeech(holdForSpeech); From e2b6dd78354fe095d6858e22cdee1d1d87e51ee3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 31 Jan 2024 15:15:20 +0100 Subject: [PATCH 0771/1897] :lipstick: --- .../parts/editor/multiEditorTabsControl.ts | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index fdea610285f..8b0eb1db46a 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -131,6 +131,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { private lastMouseWheelEventTime = 0; private isMouseOverTabs = false; + constructor( parent: HTMLElement, editorPartsView: IEditorPartsView, @@ -1018,7 +1019,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { if (e.dataTransfer) { e.dataTransfer.effectAllowed = 'copyMove'; - e.dataTransfer.setDragImage(tab, 0, 0); // top left corner of dragged tab set to cursor position + e.dataTransfer.setDragImage(tab, 0, 0); // top left corner of dragged tab set to cursor position to make room for drop-border feedback } // Apply some datatransfer types to allow for dragging the element outside of the application @@ -1034,6 +1035,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { }, onDragEnter: e => { + // Return if transfer is unsupported if (!this.isSupportedDropTransfer(e)) { if (e.dataTransfer) { @@ -1043,14 +1045,9 @@ export class MultiEditorTabsControl extends EditorTabsControl { return; } - let isLocalDragAndDrop = false; - if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { - isLocalDragAndDrop = true; - } - // Update the dropEffect to "copy" if there is no local data to be dragged because // in that case we can only copy the data into and not move it from its source - if (!isLocalDragAndDrop) { + if (!this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { if (e.dataTransfer) { e.dataTransfer.dropEffect = 'copy'; } @@ -1110,16 +1107,15 @@ export class MultiEditorTabsControl extends EditorTabsControl { targetIndex++; } - // If we are moving an editor inside the same group and it is located before the target index - // wee need to reduce the index by one to account for the fact that the move will cause all subsequent - // tabs to move one to the left. + // If we are moving an editor inside the same group and it is + // located before the target index we need to reduce the index + // by one to account for the fact that the move will cause all + // subsequent tabs to move one to the left. const editorIdentifiers = this.editorTransfer.getData(DraggedEditorIdentifier.prototype); if (editorIdentifiers !== undefined) { - const draggedEditorIdentifier = editorIdentifiers[0].identifier; const sourceGroup = this.editorPartsView.getGroup(draggedEditorIdentifier.groupId); if (sourceGroup?.id === this.groupView.id) { - const editorIndex = sourceGroup.getIndexOfEditor(draggedEditorIdentifier.editor); if (editorIndex < targetIndex) { targetIndex--; @@ -1201,6 +1197,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { private getTabDragOverLocation(e: DragEvent, tab: HTMLElement): 'left' | 'right' { const rect = tab.getBoundingClientRect(); const offsetXRelativeToParent = e.clientX - rect.left; + return offsetXRelativeToParent <= rect.width / 2 ? 'left' : 'right'; } From 7a56623b8276d75a77e3093d89568cfa772362df Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Wed, 31 Jan 2024 06:18:37 -0800 Subject: [PATCH 0772/1897] enable copying svg cell output (#203843) * find the correct svg element and write it to the clipboard * add context to allow context menu * simplify selection * better logs for error states --- extensions/ipynb/package.json | 2 +- extensions/notebook-renderers/src/index.ts | 20 +++++++++++-- .../browser/view/renderers/webviewPreloads.ts | 29 ++++++++++++++----- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index f29ed650162..5703a575dc1 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -113,7 +113,7 @@ "webview/context": [ { "command": "notebook.cellOutput.copy", - "when": "webviewId == 'notebook.output' && webviewSection == 'image'" + "when": "webviewId == 'notebook.output'" } ] } diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index ff79bd0e22e..431869ad211 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -38,7 +38,11 @@ function renderImage(outputInfo: OutputItem, element: HTMLElement): IDisposable if (alt) { image.alt = alt; } - image.setAttribute('data-vscode-context', JSON.stringify({ webviewSection: 'image', outputId: outputInfo.id, 'preventDefaultContextMenuItems': true })); + image.setAttribute('data-vscode-context', JSON.stringify({ + webviewSection: 'image', + outputId: outputInfo.id, + 'preventDefaultContextMenuItems': true + })); const display = document.createElement('div'); display.classList.add('display'); display.appendChild(image); @@ -78,7 +82,7 @@ function getAltText(outputInfo: OutputItem) { return undefined; } -function injectTitleForSvg(outputInfo: OutputItem, element: HTMLElement) { +function fixUpSvgElement(outputInfo: OutputItem, element: HTMLElement) { if (outputInfo.mime.indexOf('svg') > -1) { const svgElement = element.querySelector('svg'); const altText = getAltText(outputInfo); @@ -87,6 +91,16 @@ function injectTitleForSvg(outputInfo: OutputItem, element: HTMLElement) { title.innerText = altText; svgElement.prepend(title); } + + if (svgElement) { + svgElement.classList.add('output-image'); + + svgElement.setAttribute('data-vscode-context', JSON.stringify({ + webviewSection: 'image', + outputId: outputInfo.id, + 'preventDefaultContextMenuItems': true + })); + } } } @@ -96,7 +110,7 @@ async function renderHTML(outputInfo: OutputItem, container: HTMLElement, signal const htmlContent = outputInfo.text(); const trustedHtml = ttPolicy?.createHTML(htmlContent) ?? htmlContent; element.innerHTML = trustedHtml as string; - injectTitleForSvg(outputInfo, element); + fixUpSvgElement(outputInfo, element); for (const hook of hooks) { element = (await hook.postRender(outputInfo, element, signal)) ?? element; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index ddf248f2f81..0670f60c5da 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -1394,21 +1394,34 @@ async function webviewPreloads(ctx: PreloadContext) { } try { - const image = $window.document.getElementById(outputId)?.querySelector('img') - ?? $window.document.getElementById(altOutputId)?.querySelector('img'); + const outputElement = $window.document.getElementById(outputId) + ?? $window.document.getElementById(altOutputId); + + let image = outputElement?.querySelector('img'); + + if (!image) { + const svgImage = outputElement?.querySelector('svg.output-image'); + if (svgImage) { + image = new Image(); + image.src = 'data:image/svg+xml,' + encodeURIComponent(svgImage.outerHTML); + } + } + if (image) { + const imageToCopy = image; await navigator.clipboard.write([new ClipboardItem({ 'image/png': new Promise((resolve) => { const canvas = document.createElement('canvas'); - if (canvas !== null) { - canvas.width = image.naturalWidth; - canvas.height = image.naturalHeight; - const context = canvas.getContext('2d'); - context?.drawImage(image, 0, 0); - } + canvas.width = imageToCopy.naturalWidth; + canvas.height = imageToCopy.naturalHeight; + const context = canvas.getContext('2d'); + context!.drawImage(imageToCopy, 0, 0); + canvas.toBlob((blob) => { if (blob) { resolve(blob); + } else { + console.error('No blob data to write to clipboard'); } canvas.remove(); }, 'image/png'); From fe8e4442debc581ab6eb2a7473a5c44ef0fe69de Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 31 Jan 2024 06:45:02 -0800 Subject: [PATCH 0773/1897] Avoid leaking link providers if link manager is already disposed Fixes #203437 --- .../terminalContrib/links/browser/terminalLinkManager.ts | 7 +++++-- .../links/test/browser/terminalLinkManager.test.ts | 8 ++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts index 88b07ac643e..8b2fb0666e4 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts @@ -352,7 +352,11 @@ export class TerminalLinkManager extends DisposableStore { } } - registerExternalLinkProvider(provideLinks: OmitFirstArg): IDisposable { + registerExternalLinkProvider(provideLinks: OmitFirstArg): void { + // Avoid any leaks in case this is already disposed + if (this.isDisposed) { + return; + } // Clear and re-register the standard link providers so they are a lower priority than the new one this._clearLinkProviders(); const detectorId = `extension-${this._externalLinkProviders.length}`; @@ -360,7 +364,6 @@ export class TerminalLinkManager extends DisposableStore { const newLinkProvider = this._xterm.registerLinkProvider(wrappedLinkProvider); this._externalLinkProviders.push(newLinkProvider); this._registerStandardLinkProviders(); - return newLinkProvider; } protected _isLinkActivationModifierDown(event: MouseEvent): boolean { diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts index 79ee061f357..11abe172871 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts @@ -99,6 +99,14 @@ suite('TerminalLinkManager', () => { } as Partial as any, instantiationService.createInstance(TerminalLinkResolver))); }); + suite('registerExternalLinkProvider', () => { + test('should not leak disposables if the link manager is already disposed', () => { + linkManager.registerExternalLinkProvider(async () => undefined); + linkManager.dispose(); + linkManager.registerExternalLinkProvider(async () => undefined); + }); + }); + suite('getLinks and open recent link', () => { test('should return no links', async () => { const links = await linkManager.getLinks(); From 07e767371a13d39406ae5e92565ef8be53bcbe8a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 31 Jan 2024 15:48:03 +0100 Subject: [PATCH 0774/1897] debt - fix flaky test by skipping in CI (#203892) --- src/vs/workbench/test/browser/contributions.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/test/browser/contributions.test.ts b/src/vs/workbench/test/browser/contributions.test.ts index 99ee60ff251..1e97fb9d121 100644 --- a/src/vs/workbench/test/browser/contributions.test.ts +++ b/src/vs/workbench/test/browser/contributions.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import { DeferredPromise } from 'vs/base/common/async'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { isCI } from 'vs/base/common/platform'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { WorkbenchContributionInstantiation, WorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -127,7 +128,7 @@ suite('Contributions', () => { assert.ok(aCreated); }); - test('lifecycle phase instantiation works for late phases', async () => { + (isCI ? test.skip /* runWhenIdle seems flaky in CI on Windows */ : test)('lifecycle phase instantiation works for late phases', async () => { const registry = new WorkbenchContributionsRegistry(); const instantiationService = workbenchInstantiationService(undefined, disposables); From f4b01aeab81bf267b77d4c17fc58a9e8b1bc4490 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 31 Jan 2024 15:41:02 +0100 Subject: [PATCH 0775/1897] Inline completion: missing cancelation token --- .../inlineCompletions/browser/inlineCompletionsSource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts index a5b3b9b46bb..c99767faab9 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts @@ -58,7 +58,7 @@ export class InlineCompletionsSource extends Disposable { const shouldDebounce = updateOngoing || context.triggerKind === InlineCompletionTriggerKind.Automatic; if (shouldDebounce) { // This debounces the operation - await wait(this._debounceValue.get(this.textModel)); + await wait(this._debounceValue.get(this.textModel), source.token); } if (source.token.isCancellationRequested || this.textModel.getVersionId() !== request.versionId) { From 784091ea75a81349ff2702bef6a668fa27ebbb50 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 31 Jan 2024 16:02:38 +0100 Subject: [PATCH 0776/1897] Fix Extra Padding Near Dropdown in Terminal (#203901) fix #202078 --- src/vs/workbench/browser/parts/media/paneCompositePart.css | 2 +- src/vs/workbench/browser/parts/paneCompositePart.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index e3799464f4d..9250cd44de1 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -9,7 +9,7 @@ .monaco-workbench .pane-composite-part > .title.has-composite-bar > .title-actions .monaco-action-bar .action-item, .monaco-workbench .pane-composite-part > .title.has-composite-bar > .global-actions .monaco-action-bar .action-item { - padding-right: 4px; /* Use padding instead of margin for width computation to be correct #200515 */ + margin-right: 4px; } .monaco-workbench .pane-composite-part > .title.has-composite-bar > .title-actions .monaco-action-bar .action-item .action-label { diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index db7996ceefe..ebee8aa4b42 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -479,7 +479,11 @@ export abstract class AbstractPaneCompositePart extends CompositePart Date: Wed, 31 Jan 2024 07:05:33 -0800 Subject: [PATCH 0777/1897] Bind external link providers to link manager lifecycle Fixes #203437 --- .../links/browser/terminal.links.contribution.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts index bc8bb51aeac..e9a87b8299a 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Event } from 'vs/base/common/event'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { localize2 } from 'vs/nls'; @@ -50,27 +51,30 @@ class TerminalLinkContribution extends DisposableStore implements ITerminalContr } xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { - const linkManager = this._instantiationService.createInstance(TerminalLinkManager, xterm.raw, this._processManager, this._instance.capabilities, this._linkResolver); + const linkManager = this._linkManager = this.add(this._instantiationService.createInstance(TerminalLinkManager, xterm.raw, this._processManager, this._instance.capabilities, this._linkResolver)); + + // Set widget manager if (isTerminalProcessManager(this._processManager)) { - this._processManager.onProcessReady(() => { + const disposable = this.add(Event.once(this._processManager.onProcessReady)(() => { linkManager.setWidgetManager(this._widgetManager); - }); + this.delete(disposable); + })); } else { linkManager.setWidgetManager(this._widgetManager); } - this._linkManager = this.add(linkManager); // Attach the link provider(s) to the instance and listen for changes if (!isDetachedTerminalInstance(this._instance)) { for (const linkProvider of this._terminalLinkProviderService.linkProviders) { this._linkManager.registerExternalLinkProvider(linkProvider.provideLinks.bind(linkProvider, this._instance)); } - this.add(this._terminalLinkProviderService.onDidAddLinkProvider(e => { + this._linkManager.add(this._terminalLinkProviderService.onDidAddLinkProvider(e => { linkManager.registerExternalLinkProvider(e.provideLinks.bind(e, this._instance as ITerminalInstance)); })); } + // TODO: Currently only a single link provider is supported; the one registered by the ext host - this.add(this._terminalLinkProviderService.onDidRemoveLinkProvider(e => { + this._linkManager.add(this._terminalLinkProviderService.onDidRemoveLinkProvider(e => { linkManager.dispose(); this.xtermReady(xterm); })); From 547739498738da94302ad0c38815343630b46e58 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 31 Jan 2024 16:07:58 +0100 Subject: [PATCH 0778/1897] adding passing test and utils --- .../browser/singleTextEdit.ts | 16 ++++- .../test/browser/singleTextEdit.test.ts | 67 ++++++------------- .../inlineCompletions/test/browser/utils.ts | 54 +++++++++++++++ 3 files changed, 87 insertions(+), 50 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts index 55a4e42b5b8..accad790e92 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts @@ -8,6 +8,7 @@ import { commonPrefixLength, getLeadingWhitespace, splitLines } from 'vs/base/co import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; +import { TextModel } from 'vs/editor/common/model/textModel'; import { GhostText, GhostTextPart } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; import { addPositions, lengthOfText } from 'vs/editor/contrib/inlineCompletions/browser/utils'; @@ -264,12 +265,12 @@ export function getNewRanges(edits: SingleTextEdit[]): Range[] { ); const ranges: Range[] = []; let offsetLineNumber = 0; + let offsetColumn = 0; let previousEditLineNumber = 0; - let previousOffsetColumn = 0; for (const index of sortIndices) { const edit = edits[index]; const splitText = splitLines(edit.text!); - const currentOffsetColumn = edit.range.endLineNumber === previousEditLineNumber ? previousOffsetColumn : 0; + const currentOffsetColumn = edit.range.endLineNumber === previousEditLineNumber ? offsetColumn : 0; const rangeStart = new Position(edit.range.startLineNumber + offsetLineNumber, edit.range.startColumn + currentOffsetColumn); offsetLineNumber += splitText.length - (edit.range.endLineNumber - edit.range.startLineNumber) - 1; @@ -279,7 +280,16 @@ export function getNewRanges(edits: SingleTextEdit[]): Range[] { ); ranges.push(Range.fromPositions(rangeStart, rangeEnd)); previousEditLineNumber = edit.range.endLineNumber; - previousOffsetColumn = currentOffsetColumn + splitText[splitText.length - 1].length - edit.range.endColumn + (edit.range.startLineNumber === edit.range.endLineNumber ? edit.range.startColumn : 0); + offsetColumn = currentOffsetColumn + splitText[splitText.length - 1].length - edit.range.endColumn + (edit.range.startLineNumber === edit.range.endLineNumber ? edit.range.startColumn : 0); } return ranges.map((_, index) => ranges[sortIndices.indexOf(index)]); } + +export function inverseEdits(model: TextModel, edits: SingleTextEdit[]): SingleTextEdit[] { + const newRanges = getNewRanges(edits); + const inverseEdits: SingleTextEdit[] = []; + for (let i = 0; i < edits.length; i++) { + inverseEdits.push(new SingleTextEdit(newRanges[i], model.getValueInRange(edits[i].range))); + } + return inverseEdits; +} diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts index 2b704c1cced..a4a9c8e0083 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts @@ -3,58 +3,31 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { SingleTextEdit, getNewRanges } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; -import { Range } from 'vs/editor/common/core/range'; import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { inverseEdits } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; +import { createTextModel } from 'vs/editor/test/common/testTextModel'; +import { generateRandomDisjointEdits, randomMultilineStringGenerator } from 'vs/editor/contrib/inlineCompletions/test/browser/utils'; suite('Single Text Edit', () => { ensureNoDisposablesAreLeakedInTestSuite(); - test('getNewRanges 1', () => { - const edits: SingleTextEdit[] = [ - new SingleTextEdit(new Range(0, 0, 2, 0), 'short text'), - new SingleTextEdit(new Range(3, 0, 3, 0), [ - `text that spans`, - `multiple lines`, - `.` - ].join('\n')), - new SingleTextEdit(new Range(4, 0, 4, 1), 'some short text'), - ]; - const ranges = getNewRanges(edits); - assert.deepStrictEqual(ranges, [ - new Range(0, 0, 0, 1), - new Range(0, 1, 0, 2), - new Range(0, 2, 0, 3), - ]); - }); + test('getNewRanges', () => { + function testGetNewRanges() { + const randomText = randomMultilineStringGenerator(10); + const model = createTextModel(randomText); + const initialModel = createTextModel(randomText); + const edits = generateRandomDisjointEdits(3, model); + model.applyEdits(edits); + const invEdits = inverseEdits(initialModel, edits); + model.applyEdits(invEdits); + assert.deepStrictEqual(model.getValue(), randomText); + model.dispose(); + initialModel.dispose(); + } - test('getNewRanges 2', () => { - const edits: SingleTextEdit[] = [ - new SingleTextEdit(new Range(0, 0, 0, 0), 'a'), - new SingleTextEdit(new Range(0, 0, 0, 0), 'a'), - new SingleTextEdit(new Range(0, 0, 0, 0), 'a'), - ]; - const ranges = getNewRanges(edits); - assert.deepStrictEqual(ranges, [ - new Range(0, 0, 0, 1), - new Range(0, 1, 0, 2), - new Range(0, 2, 0, 3), - ]); + for (let i = 0; i < 1; i++) { + testGetNewRanges(); + } }); - - test('getNewRanges 3', () => { - const edits: SingleTextEdit[] = [ - new SingleTextEdit(new Range(0, 0, 0, 0), 'a'), - new SingleTextEdit(new Range(0, 0, 0, 0), 'a'), - new SingleTextEdit(new Range(0, 0, 0, 0), 'a'), - ]; - const ranges = getNewRanges(edits); - assert.deepStrictEqual(ranges, [ - new Range(0, 0, 0, 1), - new Range(0, 1, 0, 2), - new Range(0, 2, 0, 3), - ]); - }); - }); diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts index c3c25e22f9c..1581093fe31 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts @@ -13,6 +13,9 @@ import { InlineCompletion, InlineCompletionContext, InlineCompletionsProvider } import { ITestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel'; import { autorun } from 'vs/base/common/observable'; +import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; +import { Range } from 'vs/editor/common/core/range'; +import { TextModel } from 'vs/editor/common/model/textModel'; export class MockInlineCompletionsProvider implements InlineCompletionsProvider { private returnValue: InlineCompletion[] = []; @@ -131,3 +134,54 @@ export class GhostTextContext extends Disposable { CoreEditingCommands.DeleteLeft.runEditorCommand(null, this.editor, null); } } + +function randomSimpleStringGenerator(stringLength: number): string { + let randomText: string = ''; + const possibleCharacters: string = ' abcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < stringLength; i++) { + randomText += possibleCharacters.charAt(Math.floor(Math.random() * possibleCharacters.length)); + + } + return randomText; +} + +function generateUniqueRandomIntegers(numberOfIntegers: number, minimum: number, maximum: number): number[] { + if (maximum - minimum + 1 < numberOfIntegers) { + throw new Error('Too many integers to sample, specified'); + } + const integers = []; + while (integers.length < numberOfIntegers) { + const integer = Math.floor(Math.random() * maximum) + 1; + if (integers.indexOf(integer) === -1) { + integers.push(integer); + } + } + return integers; +} + +export function randomMultilineStringGenerator(numberOfLines: number, maximumLengthOfLines: number = 20): string { + let randomText: string = ''; + for (let i = 0; i < numberOfLines; i++) { + const lengthOfLine = Math.floor(Math.random() * maximumLengthOfLines); + randomText += randomSimpleStringGenerator(Math.floor(Math.random() * lengthOfLine)) + '\n'; + } + return randomText; +} + +export function generateRandomDisjointEdits(numberOfEdits: number, model: TextModel) { + const numberOfLines = model.getLineCount(); + if (2 * numberOfEdits > numberOfLines) { + throw new Error('Too many edits specified'); + } + const edits = []; + const boundsOfEdits = generateUniqueRandomIntegers(2 * numberOfEdits, 0, numberOfLines - 1).sort((a, b) => a - b); + for (let i = 0; i < numberOfEdits; i++) { + const startLine = boundsOfEdits[2 * i]; + const endLine = boundsOfEdits[2 * i + 1]; + const startColumn = model.getLineLength(startLine) ? generateUniqueRandomIntegers(1, 1, model.getLineLength(startLine))[0] : 1; + const endColumn = model.getLineLength(endLine) ? generateUniqueRandomIntegers(1, 1, model.getLineLength(endLine))[0] : 1; + const numberOfLinesEditText = generateUniqueRandomIntegers(1, 0, 3)[0]; + edits.push(new SingleTextEdit(new Range(startLine, startColumn, endLine, endColumn), randomMultilineStringGenerator(numberOfLinesEditText, 10))); + } + return edits; +} From b4eea3cfc3ca9b5bab88626a71376b5bc39bedfc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 31 Jan 2024 07:12:53 -0800 Subject: [PATCH 0779/1897] Use mutable disposable for link provider --- .../workbench/api/browser/mainThreadTerminalService.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index c84c2cd14cc..2293708c63b 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -43,13 +43,14 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape private readonly _quickFixProviders = new Map(); private readonly _dataEventTracker = new MutableDisposable(); private readonly _sendCommandEventListener = new MutableDisposable(); + /** * A single shared terminal link provider for the exthost. When an ext registers a link * provider, this is registered with the terminal on the renderer side and all links are * provided through this, even from multiple ext link providers. Xterm should remove lower * priority intersecting links itself. */ - private _linkProvider: IDisposable | undefined; + private _linkProvider = this._store.add(new MutableDisposable()); private _os: OperatingSystem = OS; @@ -111,7 +112,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape public dispose(): void { this._store.dispose(); - this._linkProvider?.dispose(); for (const provider of this._profileProviders.values()) { provider.dispose(); } @@ -253,13 +253,11 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } public $startLinkProvider(): void { - this._linkProvider?.dispose(); - this._linkProvider = this._terminalLinkProviderService.registerLinkProvider(new ExtensionTerminalLinkProvider(this._proxy)); + this._linkProvider.value = this._terminalLinkProviderService.registerLinkProvider(new ExtensionTerminalLinkProvider(this._proxy)); } public $stopLinkProvider(): void { - this._linkProvider?.dispose(); - this._linkProvider = undefined; + this._linkProvider.clear(); } public $registerProcessSupport(isSupported: boolean): void { From 37b900d22692eee60e52768cbdc53b55d46ed310 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 31 Jan 2024 16:22:56 +0100 Subject: [PATCH 0780/1897] adding tests which generate random text and random edits --- .../browser/inlineCompletionsModel.ts | 2 -- .../browser/singleTextEdit.ts | 8 +++++- .../test/browser/singleTextEdit.test.ts | 8 +++--- .../inlineCompletions/test/browser/utils.ts | 28 +++++++++---------- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 56e1184c809..3bab5e1947d 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -435,8 +435,6 @@ export class InlineCompletionsModel extends Disposable { } } - // todo: extract this function into a separate place, write some tests for this function - // todo: investigate that bug, that we previously saw inside of test 21, there appears to be a logical error somewhere that needs to be resolved private _getEdits(editor: ICodeEditor, completion: SingleTextEdit): { edits: SingleTextEdit[]; editorSelections: Selection[] } { const selections = editor.getSelections() ?? []; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts index accad790e92..d9097f4efbd 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts @@ -249,7 +249,7 @@ function smartDiff(originalValue: string, newValue: string, smartBracketMatching } /** - * Given some single text edits, this function finds the new ranges of the editted text post all edits. + * Given some text edits, this function finds the new ranges of the editted text post application of all edits. * Assumes that the edit ranges are disjoint * @param edits edits applied * @returns new ranges post edits for every edit @@ -285,6 +285,12 @@ export function getNewRanges(edits: SingleTextEdit[]): Range[] { return ranges.map((_, index) => ranges[sortIndices.indexOf(index)]); } +/** + * Given a text model and edits, this function finds the inverse text edits + * @param model model on which to apply the edits + * @param edits edits applied + * @returns inverse edits + */ export function inverseEdits(model: TextModel, edits: SingleTextEdit[]): SingleTextEdit[] { const newRanges = getNewRanges(edits); const inverseEdits: SingleTextEdit[] = []; diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts index a4a9c8e0083..604b72a2868 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts @@ -7,17 +7,17 @@ import * as assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { inverseEdits } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; -import { generateRandomDisjointEdits, randomMultilineStringGenerator } from 'vs/editor/contrib/inlineCompletions/test/browser/utils'; +import { generateRandomDisjointEdits, generateRandomMultilineString as randomMultilineString } from 'vs/editor/contrib/inlineCompletions/test/browser/utils'; suite('Single Text Edit', () => { ensureNoDisposablesAreLeakedInTestSuite(); - test('getNewRanges', () => { + test('testing getNewRanges', () => { function testGetNewRanges() { - const randomText = randomMultilineStringGenerator(10); + const randomText = randomMultilineString(10); const model = createTextModel(randomText); const initialModel = createTextModel(randomText); - const edits = generateRandomDisjointEdits(3, model); + const edits = generateRandomDisjointEdits(model, 3); model.applyEdits(edits); const invEdits = inverseEdits(initialModel, edits); model.applyEdits(invEdits); diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts index 1581093fe31..05a4ca50b14 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts @@ -135,7 +135,7 @@ export class GhostTextContext extends Disposable { } } -function randomSimpleStringGenerator(stringLength: number): string { +function generateRandomSimpleString(stringLength: number): string { let randomText: string = ''; const possibleCharacters: string = ' abcdefghijklmnopqrstuvwxyz0123456789'; for (let i = 0; i < stringLength; i++) { @@ -145,13 +145,22 @@ function randomSimpleStringGenerator(stringLength: number): string { return randomText; } +export function generateRandomMultilineString(numberOfLines: number, maximumLengthOfLines: number = 20): string { + let randomText: string = ''; + for (let i = 0; i < numberOfLines; i++) { + const lengthOfLine = Math.floor(Math.random() * maximumLengthOfLines); + randomText += generateRandomSimpleString(lengthOfLine) + '\n'; + } + return randomText; +} + function generateUniqueRandomIntegers(numberOfIntegers: number, minimum: number, maximum: number): number[] { if (maximum - minimum + 1 < numberOfIntegers) { - throw new Error('Too many integers to sample, specified'); + throw new Error('Too many integers requested'); } const integers = []; while (integers.length < numberOfIntegers) { - const integer = Math.floor(Math.random() * maximum) + 1; + const integer = Math.floor(Math.random() * (maximum - minimum + 1)) + minimum; if (integers.indexOf(integer) === -1) { integers.push(integer); } @@ -159,16 +168,7 @@ function generateUniqueRandomIntegers(numberOfIntegers: number, minimum: number, return integers; } -export function randomMultilineStringGenerator(numberOfLines: number, maximumLengthOfLines: number = 20): string { - let randomText: string = ''; - for (let i = 0; i < numberOfLines; i++) { - const lengthOfLine = Math.floor(Math.random() * maximumLengthOfLines); - randomText += randomSimpleStringGenerator(Math.floor(Math.random() * lengthOfLine)) + '\n'; - } - return randomText; -} - -export function generateRandomDisjointEdits(numberOfEdits: number, model: TextModel) { +export function generateRandomDisjointEdits(model: TextModel, numberOfEdits: number) { const numberOfLines = model.getLineCount(); if (2 * numberOfEdits > numberOfLines) { throw new Error('Too many edits specified'); @@ -181,7 +181,7 @@ export function generateRandomDisjointEdits(numberOfEdits: number, model: TextMo const startColumn = model.getLineLength(startLine) ? generateUniqueRandomIntegers(1, 1, model.getLineLength(startLine))[0] : 1; const endColumn = model.getLineLength(endLine) ? generateUniqueRandomIntegers(1, 1, model.getLineLength(endLine))[0] : 1; const numberOfLinesEditText = generateUniqueRandomIntegers(1, 0, 3)[0]; - edits.push(new SingleTextEdit(new Range(startLine, startColumn, endLine, endColumn), randomMultilineStringGenerator(numberOfLinesEditText, 10))); + edits.push(new SingleTextEdit(new Range(startLine, startColumn, endLine, endColumn), generateRandomMultilineString(numberOfLinesEditText, 10))); } return edits; } From 4dc4b876653a9b16626a79d5e7e813fda8dcb5c8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 31 Jan 2024 07:28:14 -0800 Subject: [PATCH 0781/1897] Remove service brand from link resolver It's not a service --- .../terminalContrib/links/browser/terminalLinkResolver.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkResolver.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkResolver.ts index 17b989f71f5..6090c628f8a 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkResolver.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkResolver.ts @@ -15,8 +15,6 @@ import { ITerminalBackend } from 'vs/platform/terminal/common/terminal'; import { mainWindow } from 'vs/base/browser/window'; export class TerminalLinkResolver implements ITerminalLinkResolver { - declare _serviceBrand: undefined; - // Link cache could be shared across all terminals, but that could lead to weird results when // both local and remote terminals are present private readonly _resolvedLinkCaches: Map = new Map(); From 98332892fd2cb3c948ced33f542698e20c6279b9 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 31 Jan 2024 16:43:43 +0100 Subject: [PATCH 0782/1897] Joh/superior-reptile (#203899) * perf - make `PerfviewContrib` a lazy contribution, force-create from editor input resolving and action re https://github.com/microsoft/vscode/issues/203445 * add `PerfviewContrib#get` * :lipstick: --------- Co-authored-by: Benjamin Pasero --- .../browser/performance.contribution.ts | 6 ++--- .../performance/browser/perfviewEditor.ts | 22 +++++++++++++++---- .../electron-sandbox/startupProfiler.ts | 5 +++-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts index d523f1580f8..cee7bb59dfa 100644 --- a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts +++ b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts @@ -22,7 +22,7 @@ import { InputLatencyContrib } from 'vs/workbench/contrib/performance/browser/in Registry.as(Extensions.Workbench).registerWorkbenchContribution2( PerfviewContrib.ID, PerfviewContrib, - WorkbenchContributionInstantiation.BlockRestore + WorkbenchContributionInstantiation.Lazy ); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer( @@ -54,8 +54,8 @@ registerAction2(class extends Action2 { run(accessor: ServicesAccessor) { const editorService = accessor.get(IEditorService); - const instaService = accessor.get(IInstantiationService); - return editorService.openEditor(instaService.createInstance(PerfviewInput), { pinned: true }); + const contrib = PerfviewContrib.get(); + return editorService.openEditor(contrib.getEditorInput(), { pinned: true }); } }); diff --git a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts index d99fb52b115..a06483bd1cf 100644 --- a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts @@ -33,26 +33,40 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr export class PerfviewContrib { + static get() { + return Registry. + as(WorkbenchExtensions.Workbench) + .getWorkbenchContribution(PerfviewContrib.ID); + } + static readonly ID = 'workbench.contrib.perfview'; + private readonly _inputUri = URI.from({ scheme: 'perf', path: 'Startup Performance' }); private readonly _registration: IDisposable; constructor( - @IInstantiationService instaService: IInstantiationService, + @IInstantiationService private readonly _instaService: IInstantiationService, @ITextModelService textModelResolverService: ITextModelService ) { - this._registration = textModelResolverService.registerTextModelContentProvider('perf', instaService.createInstance(PerfModelContentProvider)); + this._registration = textModelResolverService.registerTextModelContentProvider('perf', _instaService.createInstance(PerfModelContentProvider)); } dispose(): void { this._registration.dispose(); } + + getInputUri(): URI { + return this._inputUri; + } + + getEditorInput(): PerfviewInput { + return this._instaService.createInstance(PerfviewInput); + } } export class PerfviewInput extends TextResourceEditorInput { static readonly Id = 'PerfviewInput'; - static readonly Uri = URI.from({ scheme: 'perf', path: 'Startup Performance' }); override get typeId(): string { return PerfviewInput.Id; @@ -68,7 +82,7 @@ export class PerfviewInput extends TextResourceEditorInput { @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService ) { super( - PerfviewInput.Uri, + PerfviewContrib.get().getInputUri(), localize('name', "Startup Performance"), undefined, undefined, diff --git a/src/vs/workbench/contrib/performance/electron-sandbox/startupProfiler.ts b/src/vs/workbench/contrib/performance/electron-sandbox/startupProfiler.ts index 1c1d8d4203a..452a8a65971 100644 --- a/src/vs/workbench/contrib/performance/electron-sandbox/startupProfiler.ts +++ b/src/vs/workbench/contrib/performance/electron-sandbox/startupProfiler.ts @@ -10,7 +10,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { PerfviewInput } from 'vs/workbench/contrib/performance/browser/perfviewEditor'; +import { PerfviewContrib } from 'vs/workbench/contrib/performance/browser/perfviewEditor'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { URI } from 'vs/base/common/uri'; @@ -118,7 +118,8 @@ export class StartupProfiler implements IWorkbenchContribution { return; } - const ref = await this._textModelResolverService.createModelReference(PerfviewInput.Uri); + const contrib = PerfviewContrib.get(); + const ref = await this._textModelResolverService.createModelReference(contrib.getInputUri()); try { await this._clipboardService.writeText(ref.object.textEditorModel.getValue()); } finally { From f664e0c8104a2f2dda29b2dcae10b2b3a3b2185c Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Wed, 31 Jan 2024 16:53:12 +0100 Subject: [PATCH 0783/1897] :lipstick: --- .../browser/parts/editor/multiEditorTabsControl.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 8b0eb1db46a..4372fd674a4 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -1025,8 +1025,6 @@ export class MultiEditorTabsControl extends EditorTabsControl { // Apply some datatransfer types to allow for dragging the element outside of the application this.doFillResourceDataTransfers([editor], e, isNewWindowOperation); - // Fixes https://github.com/microsoft/vscode/issues/18733 - this.updateDropFeedback(tab, true, e, tabIndex); scheduleAtNextAnimationFrame(getWindow(this.parent), () => this.updateDropFeedback(tab, false, e, tabIndex)); }, @@ -1064,9 +1062,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { } } - if (e.dataTransfer?.dropEffect !== 'none') { - this.updateDropFeedback(tab, true, e, tabIndex); - } + this.updateDropFeedback(tab, true, e, tabIndex); }, onDragEnd: async e => { From 4addb2442eef2e856e42ce6092bd2bcd61e3e1f4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 31 Jan 2024 08:21:34 -0800 Subject: [PATCH 0784/1897] Fix maxLineContext expression Looks like an accidental / instead of , --- .../links/browser/terminalLinkDetectorAdapter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkDetectorAdapter.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkDetectorAdapter.ts index d8929adcd12..8226841a9a0 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkDetectorAdapter.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkDetectorAdapter.ts @@ -76,7 +76,7 @@ export class TerminalLinkDetectorAdapter extends Disposable implements ILinkProv // Cap the maximum context on either side of the line being provided, by taking the context // around the line being provided for this ensures the line the pointer is on will have // links provided. - const maxLineContext = Math.max(this._detector.maxLinkLength / this._detector.xterm.cols); + const maxLineContext = Math.max(this._detector.maxLinkLength, this._detector.xterm.cols); const minStartLine = Math.max(startLine - maxLineContext, 0); const maxEndLine = Math.min(endLine + maxLineContext, this._detector.xterm.buffer.active.length); From 391ea06db35ad6757e53e74028b6d0eb072a45a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Wed, 31 Jan 2024 17:31:19 +0100 Subject: [PATCH 0785/1897] fix inoperative try/catch --- .../services/keybinding/browser/keyboardLayoutService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts index c55684f0424..bb50e96ff77 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts @@ -396,7 +396,7 @@ export class BrowserKeyboardMapperFactoryBase extends Disposable { private async _getBrowserKeyMapping(keyboardEvent?: IKeyboardEvent): Promise { if ((navigator as any).keyboard) { try { - return (navigator as any).keyboard.getLayoutMap().then((e: any) => { + return await (navigator as any).keyboard.getLayoutMap().then((e: any) => { const ret: IKeyboardMapping = {}; for (const key of e) { ret[key[0]] = { From ef40932ae9d63f86aa6e863f59921797a7e7f73e Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Wed, 31 Jan 2024 08:51:41 -0800 Subject: [PATCH 0786/1897] display unknown text mime outputs as plain text (#203849) --- extensions/notebook-renderers/src/index.ts | 5 +++++ .../browser/view/cellParts/cellOutput.ts | 19 ++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index 431869ad211..ef9240df2b7 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -515,6 +515,11 @@ export const activate: ActivationFunction = (ctx) => { } break; default: + if (outputInfo.mime.indexOf('text/') > -1) { + disposables.get(outputInfo.id)?.dispose(); + const disposable = renderText(outputInfo, element, latestContext); + disposables.set(outputInfo.id, disposable); + } break; } if (element.querySelector('div')) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts index f60ac478dab..3ec60d0ce1d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts @@ -200,18 +200,23 @@ class CellOutputElement extends Disposable { return undefined; } - const pickedMimeTypeRenderer = mimeTypes[pick]; - const innerContainer = this._generateInnerOutputContainer(previousSibling, pickedMimeTypeRenderer); + const selectedPresentation = mimeTypes[pick]; + let renderer = this.notebookService.getRendererInfo(selectedPresentation.rendererId); + if (!renderer && selectedPresentation.mimeType.indexOf('text/') > -1) { + renderer = this.notebookService.getRendererInfo('vscode.builtin-renderer'); + } + + const innerContainer = this._generateInnerOutputContainer(previousSibling, selectedPresentation); this._attachToolbar(innerContainer, notebookTextModel, this.notebookEditor.activeKernel, index, mimeTypes); this.renderedOutputContainer = DOM.append(innerContainer, DOM.$('.rendered-output')); - const renderer = this.notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId); - this.renderResult = renderer - ? { type: RenderOutputType.Extension, renderer, source: this.output, mimeType: pickedMimeTypeRenderer.mimeType } - : this._renderMissingRenderer(this.output, pickedMimeTypeRenderer.mimeType); - this.output.pickedMimeType = pickedMimeTypeRenderer; + this.renderResult = renderer + ? { type: RenderOutputType.Extension, renderer, source: this.output, mimeType: selectedPresentation.mimeType } + : this._renderMissingRenderer(this.output, selectedPresentation.mimeType); + + this.output.pickedMimeType = selectedPresentation; if (!this.renderResult) { this.viewCell.updateOutputHeight(index, 0, 'CellOutputElement#renderResultUndefined'); From e5bbb57c5ad5f18a02c17bc827ac784ce348da7e Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 31 Jan 2024 18:12:58 +0100 Subject: [PATCH 0787/1897] test passes --- .../browser/singleTextEdit.ts | 5 ++--- .../inlineCompletions/test/browser/utils.ts | 21 +++++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts index d9097f4efbd..a85129e976a 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts @@ -270,8 +270,7 @@ export function getNewRanges(edits: SingleTextEdit[]): Range[] { for (const index of sortIndices) { const edit = edits[index]; const splitText = splitLines(edit.text!); - const currentOffsetColumn = edit.range.endLineNumber === previousEditLineNumber ? offsetColumn : 0; - const rangeStart = new Position(edit.range.startLineNumber + offsetLineNumber, edit.range.startColumn + currentOffsetColumn); + const rangeStart = new Position(edit.range.startLineNumber + offsetLineNumber, edit.range.startColumn + (edit.range.startLineNumber === previousEditLineNumber ? offsetColumn : 0)); offsetLineNumber += splitText.length - (edit.range.endLineNumber - edit.range.startLineNumber) - 1; const rangeEnd = addPositions( @@ -280,7 +279,7 @@ export function getNewRanges(edits: SingleTextEdit[]): Range[] { ); ranges.push(Range.fromPositions(rangeStart, rangeEnd)); previousEditLineNumber = edit.range.endLineNumber; - offsetColumn = currentOffsetColumn + splitText[splitText.length - 1].length - edit.range.endColumn + (edit.range.startLineNumber === edit.range.endLineNumber ? edit.range.startColumn : 0); + offsetColumn = rangeEnd.column - edit.range.endColumn; } return ranges.map((_, index) => ranges[sortIndices.indexOf(index)]); } diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts index 05a4ca50b14..806b7ce5cb7 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts @@ -154,14 +154,18 @@ export function generateRandomMultilineString(numberOfLines: number, maximumLeng return randomText; } -function generateUniqueRandomIntegers(numberOfIntegers: number, minimum: number, maximum: number): number[] { +function generateRandomIntegers(numberOfIntegers: number, minimum: number, maximum: number, unique: boolean = false): number[] { if (maximum - minimum + 1 < numberOfIntegers) { throw new Error('Too many integers requested'); } const integers = []; while (integers.length < numberOfIntegers) { const integer = Math.floor(Math.random() * (maximum - minimum + 1)) + minimum; - if (integers.indexOf(integer) === -1) { + if (unique) { + if (integers.indexOf(integer) === -1) { + integers.push(integer); + } + } else { integers.push(integer); } } @@ -174,14 +178,13 @@ export function generateRandomDisjointEdits(model: TextModel, numberOfEdits: num throw new Error('Too many edits specified'); } const edits = []; - const boundsOfEdits = generateUniqueRandomIntegers(2 * numberOfEdits, 0, numberOfLines - 1).sort((a, b) => a - b); + // Generate unique offsets, sort them and convert them to range boundary positions + const offsetBoundaries = generateRandomIntegers(2 * numberOfEdits, 0, model.getValueLength() - 1, true).sort((a, b) => a - b); for (let i = 0; i < numberOfEdits; i++) { - const startLine = boundsOfEdits[2 * i]; - const endLine = boundsOfEdits[2 * i + 1]; - const startColumn = model.getLineLength(startLine) ? generateUniqueRandomIntegers(1, 1, model.getLineLength(startLine))[0] : 1; - const endColumn = model.getLineLength(endLine) ? generateUniqueRandomIntegers(1, 1, model.getLineLength(endLine))[0] : 1; - const numberOfLinesEditText = generateUniqueRandomIntegers(1, 0, 3)[0]; - edits.push(new SingleTextEdit(new Range(startLine, startColumn, endLine, endColumn), generateRandomMultilineString(numberOfLinesEditText, 10))); + const startPosition = model.getPositionAt(offsetBoundaries[2 * i]); + const endPosition = model.getPositionAt(offsetBoundaries[2 * i + 1]); + const numberOfLinesInText = generateRandomIntegers(1, 0, 3)[0]; + edits.push(new SingleTextEdit(Range.fromPositions(startPosition, endPosition), generateRandomMultilineString(numberOfLinesInText, 10))); } return edits; } From 9bb015a84f429a9743f9bfb64e50eb231e2d470e Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 31 Jan 2024 18:16:56 +0100 Subject: [PATCH 0788/1897] simplfifying the tests --- .../test/browser/singleTextEdit.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts index 604b72a2868..5cede383e1d 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts @@ -13,21 +13,23 @@ suite('Single Text Edit', () => { ensureNoDisposablesAreLeakedInTestSuite(); test('testing getNewRanges', () => { - function testGetNewRanges() { + + function testInverseEdits() { const randomText = randomMultilineString(10); const model = createTextModel(randomText); - const initialModel = createTextModel(randomText); + const edits = generateRandomDisjointEdits(model, 3); + const invEdits = inverseEdits(model, edits); + model.applyEdits(edits); - const invEdits = inverseEdits(initialModel, edits); model.applyEdits(invEdits); + assert.deepStrictEqual(model.getValue(), randomText); model.dispose(); - initialModel.dispose(); } for (let i = 0; i < 1; i++) { - testGetNewRanges(); + testInverseEdits(); } }); }); From cb31b8d1b2ce89c6c76b6f7c608024c27bdab3be Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 31 Jan 2024 09:20:50 -0800 Subject: [PATCH 0789/1897] Dispose all link adapters when the link manager is disposed Fixes #184046 --- .../links/browser/terminal.links.contribution.ts | 6 +++--- .../terminalContrib/links/browser/terminalLinkManager.ts | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts index e9a87b8299a..a8d46af2d62 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts @@ -66,15 +66,15 @@ class TerminalLinkContribution extends DisposableStore implements ITerminalContr // Attach the link provider(s) to the instance and listen for changes if (!isDetachedTerminalInstance(this._instance)) { for (const linkProvider of this._terminalLinkProviderService.linkProviders) { - this._linkManager.registerExternalLinkProvider(linkProvider.provideLinks.bind(linkProvider, this._instance)); + linkManager.registerExternalLinkProvider(linkProvider.provideLinks.bind(linkProvider, this._instance)); } - this._linkManager.add(this._terminalLinkProviderService.onDidAddLinkProvider(e => { + linkManager.add(this._terminalLinkProviderService.onDidAddLinkProvider(e => { linkManager.registerExternalLinkProvider(e.provideLinks.bind(e, this._instance as ITerminalInstance)); })); } // TODO: Currently only a single link provider is supported; the one registered by the ext host - this._linkManager.add(this._terminalLinkProviderService.onDidRemoveLinkProvider(e => { + linkManager.add(this._terminalLinkProviderService.onDidRemoveLinkProvider(e => { linkManager.dispose(); this.xtermReady(xterm); })); diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts index 8b2fb0666e4..bfb1a5391ba 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts @@ -91,6 +91,8 @@ export class TerminalLinkManager extends DisposableStore { let activeHoverDisposable: IDisposable | undefined; let activeTooltipScheduler: RunOnceScheduler | undefined; this.add(toDisposable(() => { + this._clearLinkProviders(); + dispose(this._externalLinkProviders); activeHoverDisposable?.dispose(); activeTooltipScheduler?.dispose(); })); From 2021379f3c11cb8da1dab1ded9cc89ae61b17d01 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 31 Jan 2024 18:28:12 +0100 Subject: [PATCH 0790/1897] using the position object and the delta method on it instead --- .../browser/singleTextEdit.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts index a85129e976a..e605ba136b7 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts @@ -264,22 +264,26 @@ export function getNewRanges(edits: SingleTextEdit[]): Range[] { Range.compareRangesUsingStarts(edits[a].range, edits[b].range) ); const ranges: Range[] = []; - let offsetLineNumber = 0; - let offsetColumn = 0; - let previousEditLineNumber = 0; + let previousEditEndLineNumber = 0; + let positionOffset = new Position(0, 0); + for (const index of sortIndices) { const edit = edits[index]; const splitText = splitLines(edit.text!); - const rangeStart = new Position(edit.range.startLineNumber + offsetLineNumber, edit.range.startColumn + (edit.range.startLineNumber === previousEditLineNumber ? offsetColumn : 0)); - - offsetLineNumber += splitText.length - (edit.range.endLineNumber - edit.range.startLineNumber) - 1; + const rangeStart = edit.range.getStartPosition().delta( + positionOffset.lineNumber, + edit.range.startLineNumber === previousEditEndLineNumber ? positionOffset.column : 0 + ); const rangeEnd = addPositions( rangeStart, lengthOfText(edit.text) ); ranges.push(Range.fromPositions(rangeStart, rangeEnd)); - previousEditLineNumber = edit.range.endLineNumber; - offsetColumn = rangeEnd.column - edit.range.endColumn; + previousEditEndLineNumber = edit.range.endLineNumber; + positionOffset = positionOffset.delta( + splitText.length - edit.range.endLineNumber + edit.range.startLineNumber - 1, + rangeEnd.column - edit.range.endColumn - positionOffset.column + ); } return ranges.map((_, index) => ranges[sortIndices.indexOf(index)]); } From 92964ac45fb7750fd072890cc75e51d41e4fed11 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 31 Jan 2024 11:33:02 -0600 Subject: [PATCH 0791/1897] fix issue with help from Daniel --- .../audioCues/browser/audioCueService.ts | 41 +++++-------------- .../browser/audioCues.contribution.ts | 6 +-- .../common/configurationEditing.ts | 2 +- 3 files changed, 14 insertions(+), 35 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index dfdb1979b9b..edb4f480900 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -58,39 +58,18 @@ export class AudioCueService extends Disposable implements IAudioCueService { } private _migrateSettings(): void { - const audioCuesConfig = this.configurationService.getValue<{ [key: string]: boolean }>('audioCues'); - for (const entry in audioCuesConfig) { - if (entry) { - console.log('cue ', entry); - this.configurationService.updateValue('audioCues.' + entry, undefined); - if (entry === 'volume' || entry === 'enabled') { - // ignore, these are audio cue settings - } else if (entry === 'debouncePositionChanges') { - this.configurationService.updateValue('signals.' + entry, audioCuesConfig[entry]); - } else { - this.configurationService.updateValue('signals.' + entry + '.' + 'audioCue', audioCuesConfig[entry]); - } + this.configurationService.updateValue('signals.debouncePositionChanges', this.configurationService.getValue('audioCues.debouncePositionChanges')); + const config = AudioCue.allAudioCues; + for (const c of config) { + const alertValue = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; + const audioCueValue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; + const newSettingsKey = c.settingsKey.replace('audioCues.', 'signals.'); + if (alertValue !== undefined || audioCueValue !== undefined) { + this.configurationService.updateValue(newSettingsKey, { alert: alertValue, audioCue: audioCueValue }); + } else if (audioCueValue) { + this.configurationService.updateValue(newSettingsKey, { audioCue: audioCueValue }); } } - const alertsConfig = this.configurationService.getValue<{ [key: string]: boolean }>('accessibility.alert'); - for (let entry in alertsConfig) { - if (entry) { - console.log('alert ', entry); - if (entry === 'warning') { - entry = 'lineHasWarning'; - } else if (entry === 'error') { - entry = 'lineHasError'; - } else if (entry === 'foldedArea') { - entry = 'lineHasFoldedArea'; - } else if (entry === 'breakpoint') { - entry = 'onDebugBreak'; - } - this.configurationService.updateValue('accessibility.alert.' + entry, undefined); - this.configurationService.updateValue('signals.' + entry + '.' + 'alert', alertsConfig[entry]); - } - } - const signals = this.configurationService.getValue<{ [key: string]: boolean }>('signals'); - console.log(JSON.stringify(signals)); } public async playAudioCue(cue: AudioCue, options: IAudioCueOptions = {}): Promise { diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index a5df5042d0d..3f10e272c21 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -300,11 +300,11 @@ Registry.as(ConfigurationExtensions.Configuration).regis ...signalFeatureBase, 'description': localize('signals.chatRequestSent', "Plays a signal when a chat request is made."), 'properties': { - 'signals.chatRequestSent.audioCue': { + 'audioCue': { 'description': localize('signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), ...audioCueFeatureBase }, - 'signals.chatRequestSent.alert': { + 'alert': { 'description': localize('signals.chatRequestSent.alert', "Alerts when a chat request is made."), ...alertFeatureBase }, @@ -328,7 +328,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis ...defaultNoAlert, 'description': localize('signals.chatResponseReceived', "Plays a signal on loop while the response has been received."), 'properties': { - 'signals.chatResponseReceived.audioCue': { + 'audioCue': { 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), ...audioCueFeatureBase }, diff --git a/src/vs/workbench/services/configuration/common/configurationEditing.ts b/src/vs/workbench/services/configuration/common/configurationEditing.ts index 721f789dadb..0c88c9fc11e 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditing.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditing.ts @@ -471,7 +471,7 @@ export class ConfigurationEditing { } return this.textModelResolverService.createModelReference(resource); } - + // private hasParseErrors(content: string, operation: IConfigurationEditOperation): boolean { // If we write to a workspace standalone file and replace the entire contents (no key provided) // we can return here because any parse errors can safely be ignored since all contents are replaced From 7e0c93cec5f946a4834e0ec7862511a259078963 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 31 Jan 2024 09:33:55 -0800 Subject: [PATCH 0792/1897] cli: skip integrity check when prereq skip flag is present (#203912) --- cli/src/tunnels/code_server.rs | 31 ++++++++++++++++++------------- cli/src/util/prereqs.rs | 16 +++++++++++----- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/cli/src/tunnels/code_server.rs b/cli/src/tunnels/code_server.rs index 27d8bed1845..bb854001d54 100644 --- a/cli/src/tunnels/code_server.rs +++ b/cli/src/tunnels/code_server.rs @@ -21,6 +21,7 @@ use crate::util::errors::{wrap, AnyError, CodeError, ExtensionInstallFailed, Wra use crate::util::http::{self, BoxedHttp}; use crate::util::io::SilentCopyProgress; use crate::util::machine::process_exists; +use crate::util::prereqs::skip_requirements_check; use crate::{debug, info, log, spanf, trace, warning}; use lazy_static::lazy_static; use opentelemetry::KeyValue; @@ -425,20 +426,24 @@ impl<'a> ServerBuilder<'a> { let server_dir = target_dir.join(SERVER_FOLDER_NAME); unzip_downloaded_release(&archive_path, &server_dir, SilentCopyProgress())?; - let output = capture_command_and_check_status( - server_dir - .join("bin") - .join(self.server_params.release.quality.server_entrypoint()), - &["--version"], - ) - .await - .map_err(|e| wrap(e, "error checking server integrity"))?; + if !skip_requirements_check().await { + let output = capture_command_and_check_status( + server_dir + .join("bin") + .join(self.server_params.release.quality.server_entrypoint()), + &["--version"], + ) + .await + .map_err(|e| wrap(e, "error checking server integrity"))?; - trace!( - self.logger, - "Server integrity verified, version: {}", - String::from_utf8_lossy(&output.stdout).replace('\n', " / ") - ); + trace!( + self.logger, + "Server integrity verified, version: {}", + String::from_utf8_lossy(&output.stdout).replace('\n', " / ") + ); + } else { + info!(self.logger, "Skipping server integrity check"); + } Ok(()) }) diff --git a/cli/src/util/prereqs.rs b/cli/src/util/prereqs.rs index 4f2a6ada8bb..3bf2934e255 100644 --- a/cli/src/util/prereqs.rs +++ b/cli/src/util/prereqs.rs @@ -25,7 +25,6 @@ lazy_static! { } const NIXOS_TEST_PATH: &str = "/etc/NIXOS"; -const SKIP_REQ_FILE: &str = "/tmp/vscode-skip-server-requirements-check"; pub struct PreReqChecker {} @@ -55,7 +54,7 @@ impl PreReqChecker { pub async fn verify(&self) -> Result { let (is_nixos, skip_glibc_checks, or_musl) = tokio::join!( check_is_nixos(), - check_skip_req_file(), + skip_requirements_check(), check_musl_interpreter() ); @@ -169,9 +168,16 @@ async fn check_is_nixos() -> bool { /// Provides a way to skip the server glibc requirements check from /// outside the install flow. A system process can create this /// file before the server is downloaded and installed. -#[allow(dead_code)] -async fn check_skip_req_file() -> bool { - fs::metadata(SKIP_REQ_FILE).await.is_ok() +#[cfg(not(windows))] +pub async fn skip_requirements_check() -> bool { + fs::metadata("/tmp/vscode-skip-server-requirements-check") + .await + .is_ok() +} + +#[cfg(windows)] +pub async fn skip_requirements_check() -> bool { + false } #[allow(dead_code)] From 93907a85e52bdb1467e849fc6684ed2617fbdf00 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 31 Jan 2024 11:47:43 -0600 Subject: [PATCH 0793/1897] simplify settings with only cues --- .../audioCues/browser/audioCueService.ts | 4 +- .../browser/audioCues.contribution.ts | 61 ++++--------------- 2 files changed, 14 insertions(+), 51 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index edb4f480900..c8f74e4324d 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -64,10 +64,10 @@ export class AudioCueService extends Disposable implements IAudioCueService { const alertValue = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; const audioCueValue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; const newSettingsKey = c.settingsKey.replace('audioCues.', 'signals.'); - if (alertValue !== undefined || audioCueValue !== undefined) { + if (alertValue && audioCueValue) { this.configurationService.updateValue(newSettingsKey, { alert: alertValue, audioCue: audioCueValue }); } else if (audioCueValue) { - this.configurationService.updateValue(newSettingsKey, { audioCue: audioCueValue }); + this.configurationService.updateValue(newSettingsKey + '.audioCue', audioCueValue); } } } diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index 3f10e272c21..0226b3308bc 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -49,15 +49,6 @@ const signalFeatureBase: IConfigurationPropertySchema = { } }; -const defaultNoAlert: IConfigurationPropertySchema = { - 'type': 'object', - 'tags': ['accessibility'], - additionalProperties: true, - 'default': { - 'audioCue': 'auto', - } -}; - Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ id: 'signals', order: 100, @@ -238,35 +229,17 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, } }, - 'signals.diffLineInserted': { - ...defaultNoAlert, - 'description': localize('signals.diffLineInserted', "Plays a signal when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - 'properties': { - 'audioCue': { - 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase - } - } + 'signals.diffLineInserted.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), }, - 'signals.diffLineDeleted': { - ...defaultNoAlert, - 'description': localize('signals.diffLineDeleted', "Plays a signal when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), - 'properties': { - 'audioCue': { - 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase - } - } + 'signals.diffLineDeleted.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), }, - 'signals.diffLineModified': { - ...defaultNoAlert, - 'description': localize('signals.diffLineModified', "Plays a signal when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - 'properties': { - 'audioCue': { - 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase - } - } + 'signals.diffLineModified.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), }, 'signals.notebookCellCompleted': { ...signalFeatureBase, @@ -324,15 +297,9 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, }, }, - 'signals.chatResponseReceived': { - ...defaultNoAlert, - 'description': localize('signals.chatResponseReceived', "Plays a signal on loop while the response has been received."), - 'properties': { - 'audioCue': { - 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), - ...audioCueFeatureBase - }, - }, + 'signals.chatResponseReceived.audioCue': { + 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), + ...audioCueFeatureBase }, 'signals.clear': { ...signalFeatureBase, @@ -410,10 +377,6 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('signals.format.alert.never', "Never plays the alert.") ], }, - }, - default: { - 'audioCue': 'never', - 'alert': 'never' } }, } From 0cd0c9705335a7dc9f5a8a49683f6f32a40d0153 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 31 Jan 2024 11:56:40 -0600 Subject: [PATCH 0794/1897] Add deprecation messages --- .../browser/accessibilityConfiguration.ts | 106 +++++++----------- .../browser/audioCues.contribution.ts | 58 +++++----- 2 files changed, 74 insertions(+), 90 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 9c18fc789c8..aca8a31a863 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -70,11 +70,19 @@ export const enum AccessibleViewProviderId { Comments = 'comments' } -const baseProperty: object = { +const baseVerbosityProperty: object = { type: 'boolean', default: true, tags: ['accessibility'] }; +const markdownDeprecationMessage = localize('accessibility.alert.deprecationMessage', "This setting is deprecated. Use the `signals` settings instead."); +const baseAlertProperty: object = { + type: 'boolean', + default: true, + tags: ['accessibility'], + markdownDeprecationMessage +}; + export const accessibilityConfigurationNodeBase = Object.freeze({ id: 'accessibility', @@ -87,47 +95,47 @@ const configuration: IConfigurationNode = { properties: { [AccessibilityVerbositySettingId.Terminal]: { description: localize('verbosity.terminal.description', 'Provide information about how to access the terminal accessibility help menu when the terminal is focused.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.DiffEditor]: { description: localize('verbosity.diffEditor.description', 'Provide information about how to navigate changes in the diff editor when it is focused.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.Chat]: { description: localize('verbosity.chat.description', 'Provide information about how to access the chat help menu when the chat input is focused.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.InlineChat]: { description: localize('verbosity.interactiveEditor.description', 'Provide information about how to access the inline editor chat accessibility help menu and alert with hints that describe how to use the feature when the input is focused.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.InlineCompletions]: { description: localize('verbosity.inlineCompletions.description', 'Provide information about how to access the inline completions hover and Accessible View.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.KeybindingsEditor]: { description: localize('verbosity.keybindingsEditor.description', 'Provide information about how to change a keybinding in the keybindings editor when a row is focused.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.Notebook]: { description: localize('verbosity.notebook', 'Provide information about how to focus the cell container or inner editor when a notebook cell is focused.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.Hover]: { description: localize('verbosity.hover', 'Provide information about how to open the hover in an Accessible View.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.Notification]: { description: localize('verbosity.notification', 'Provide information about how to open the notification in an Accessible View.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.EmptyEditorHint]: { description: localize('verbosity.emptyEditorHint', 'Provide information about relevant actions in an empty text editor.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.Comments]: { description: localize('verbosity.comments', 'Provide information about actions that can be taken in the comment widget or in a file which contains comments.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityAlertSettingId.Save]: { 'markdownDescription': localize('alert.save', "Alerts when a file is saved. Also see {0}.", '`#audioCues.save#`'), @@ -138,13 +146,12 @@ const configuration: IConfigurationNode = { localize('alert.save.always', "Alerts whenever is a file is saved, including auto save."), localize('alert.save.never', "Never alerts.") ], - tags: ['accessibility'] + tags: ['accessibility'], + markdownDeprecationMessage }, [AccessibilityAlertSettingId.Clear]: { 'markdownDescription': localize('alert.clear', "Alerts when a feature is cleared (for example, the terminal, Debug Console, or Output channel). Also see {0}.", '`#audioCues.clear#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.Format]: { 'markdownDescription': localize('alert.format', "Alerts when a file or notebook cell is formatted. Also see {0}.", '`#audioCues.format#`'), @@ -156,103 +163,72 @@ const configuration: IConfigurationNode = { localize('alert.format.always', "Alerts whenever is a file is formatted, including auto save, on cell execution, and more."), localize('alert.format.never', "Never alerts.") ], - tags: ['accessibility'] + tags: ['accessibility'], + markdownDeprecationMessage }, [AccessibilityAlertSettingId.Breakpoint]: { 'markdownDescription': localize('alert.breakpoint', "Alerts when the active line has a breakpoint. Also see {0}.", '`#audioCues.onDebugBreak#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.Error]: { 'markdownDescription': localize('alert.error', "Alerts when the active line has an error. Also see {0}.", '`#audioCues.lineHasError#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.Warning]: { 'markdownDescription': localize('alert.warning', "Alerts when the active line has a warning. Also see {0}.", '`#audioCues.lineHasWarning#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.FoldedArea]: { 'markdownDescription': localize('alert.foldedArea', "Alerts when the active line has a folded area that can be unfolded. Also see {0}.", '`#audioCues.lineHasFoldedArea#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalQuickFix]: { 'markdownDescription': localize('alert.terminalQuickFix', "Alerts when there is an available terminal quick fix. Also see {0}.", '`#audioCues.terminalQuickFix#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalBell]: { 'markdownDescription': localize('alert.terminalBell', "Alerts when the terminal bell is activated."), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalCommandFailed]: { 'markdownDescription': localize('alert.terminalCommandFailed', "Alerts when a terminal command fails (non-zero exit code). Also see {0}.", '`#audioCues.terminalCommandFailed#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.TaskFailed]: { 'markdownDescription': localize('alert.taskFailed', "Alerts when a task fails (non-zero exit code). Also see {0}.", '`#audioCues.taskFailed#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.TaskCompleted]: { 'markdownDescription': localize('alert.taskCompleted', "Alerts when a task completes successfully (zero exit code). Also see {0}.", '`#audioCues.taskCompleted#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.ChatRequestSent]: { 'markdownDescription': localize('alert.chatRequestSent', "Alerts when a chat request is sent. Also see {0}.", '`#audioCues.chatRequestSent#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.ChatResponsePending]: { 'markdownDescription': localize('alert.chatResponsePending', "Alerts when a chat response is pending. Also see {0}.", '`#audioCues.chatResponsePending#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.NoInlayHints]: { 'markdownDescription': localize('alert.noInlayHints', "Alerts when there are no inlay hints. Also see {0}.", '`#audioCues.noInlayHints#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.LineHasBreakpoint]: { 'markdownDescription': localize('alert.lineHasBreakpoint', "Alerts when on a line with a breakpoint. Also see {0}.", '`#audioCues.lineHasBreakpoint#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.NotebookCellCompleted]: { 'markdownDescription': localize('alert.notebookCellCompleted', "Alerts when a notebook cell completes successfully. Also see {0}.", '`#audioCues.notebookCellCompleted#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.NotebookCellFailed]: { 'markdownDescription': localize('alert.notebookCellFailed', "Alerts when a notebook cell fails. Also see {0}.", '`#audioCues.notebookCellFailed#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.OnDebugBreak]: { 'markdownDescription': localize('alert.onDebugBreak', "Alerts when the debugger breaks. Also see {0}.", '`#audioCues.onDebugBreak#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityWorkbenchSettingId.AccessibleViewCloseOnKeyPress]: { markdownDescription: localize('terminal.integrated.accessibleView.closeOnKeyPress', "On keypress, close the Accessible View and focus the element from which it was invoked."), diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index 0226b3308bc..e8605ed3434 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -29,7 +29,12 @@ const audioCueFeatureBase: IConfigurationPropertySchema = { localize('audioCues.enabled.on', "Enable audio cue."), localize('audioCues.enabled.off', "Disable audio cue.") ], - tags: ['accessibility'] + tags: ['accessibility'], +}; +const markdownDeprecationMessage = localize('audioCues.enabled.deprecated', "This setting is deprecated. Use `signals` settings instead."); +const audioCueDeprecatedFeatureBase: IConfigurationPropertySchema = { + ...audioCueFeatureBase, + markdownDeprecationMessage }; const alertFeatureBase: IConfigurationPropertySchema = { @@ -400,96 +405,97 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'description': localize('audioCues.debouncePositionChanges', "Whether or not position changes should be debounced"), 'type': 'boolean', 'default': false, - tags: ['accessibility'] + tags: ['accessibility'], + 'markdownDeprecationMessage': localize('audioCues.debouncePositionChangesDeprecated', 'This setting is deprecated, instead use the `signals.debouncePositionChanges` setting.') }, 'audioCues.lineHasBreakpoint': { 'description': localize('audioCues.lineHasBreakpoint', "Plays a sound when the active line has a breakpoint."), - ...audioCueFeatureBase + ...audioCueDeprecatedFeatureBase }, 'audioCues.lineHasInlineSuggestion': { 'description': localize('audioCues.lineHasInlineSuggestion', "Plays a sound when the active line has an inline suggestion."), - ...audioCueFeatureBase + ...audioCueDeprecatedFeatureBase }, 'audioCues.lineHasError': { 'description': localize('audioCues.lineHasError', "Plays a sound when the active line has an error."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.lineHasFoldedArea': { 'description': localize('audioCues.lineHasFoldedArea', "Plays a sound when the active line has a folded area that can be unfolded."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.lineHasWarning': { 'description': localize('audioCues.lineHasWarning', "Plays a sound when the active line has a warning."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'off', }, 'audioCues.onDebugBreak': { 'description': localize('audioCues.onDebugBreak', "Plays a sound when the debugger stopped on a breakpoint."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.noInlayHints': { 'description': localize('audioCues.noInlayHints', "Plays a sound when trying to read a line with inlay hints that has no inlay hints."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.taskCompleted': { 'description': localize('audioCues.taskCompleted', "Plays a sound when a task is completed."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.taskFailed': { 'description': localize('audioCues.taskFailed', "Plays a sound when a task fails (non-zero exit code)."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.terminalCommandFailed': { 'description': localize('audioCues.terminalCommandFailed', "Plays a sound when a terminal command fails (non-zero exit code)."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.terminalQuickFix': { 'description': localize('audioCues.terminalQuickFix', "Plays a sound when terminal Quick Fixes are available."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.terminalBell': { 'description': localize('audioCues.terminalBell', "Plays a sound when the terminal bell is ringing."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'on' }, 'audioCues.diffLineInserted': { 'description': localize('audioCues.diffLineInserted', "Plays a sound when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.diffLineDeleted': { 'description': localize('audioCues.diffLineDeleted', "Plays a sound when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.diffLineModified': { 'description': localize('audioCues.diffLineModified', "Plays a sound when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.notebookCellCompleted': { 'description': localize('audioCues.notebookCellCompleted', "Plays a sound when a notebook cell execution is successfully completed."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.notebookCellFailed': { 'description': localize('audioCues.notebookCellFailed', "Plays a sound when a notebook cell execution fails."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.chatRequestSent': { 'description': localize('audioCues.chatRequestSent', "Plays a sound when a chat request is made."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'off' }, 'audioCues.chatResponsePending': { 'description': localize('audioCues.chatResponsePending', "Plays a sound on loop while the response is pending."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'auto' }, 'audioCues.chatResponseReceived': { 'description': localize('audioCues.chatResponseReceived', "Plays a sound on loop while the response has been received."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'off' }, 'audioCues.clear': { 'description': localize('audioCues.clear', "Plays a sound when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'off' }, 'audioCues.save': { @@ -502,7 +508,8 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('audioCues.save.always', "Plays the audio cue whenever a file is saved, including auto save."), localize('audioCues.save.never', "Never plays the audio cue.") ], - tags: ['accessibility'] + tags: ['accessibility'], + markdownDeprecationMessage }, 'audioCues.format': { 'markdownDescription': localize('audioCues.format', "Plays a sound when a file or notebook is formatted. Also see {0}", '`#accessibility.alert.format#`'), @@ -514,7 +521,8 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('audioCues.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), localize('audioCues.format.never', "Never plays the audio cue.") ], - tags: ['accessibility'] + tags: ['accessibility'], + markdownDeprecationMessage }, }, }); From bb63da6b6271d2b96bf2bd5596e317bb64ecdcdc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 31 Jan 2024 11:58:24 -0600 Subject: [PATCH 0795/1897] better type --- .../accessibility/browser/accessibilityConfiguration.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index aca8a31a863..265697be7e1 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; @@ -70,13 +70,13 @@ export const enum AccessibleViewProviderId { Comments = 'comments' } -const baseVerbosityProperty: object = { +const baseVerbosityProperty: IConfigurationPropertySchema = { type: 'boolean', default: true, tags: ['accessibility'] }; const markdownDeprecationMessage = localize('accessibility.alert.deprecationMessage', "This setting is deprecated. Use the `signals` settings instead."); -const baseAlertProperty: object = { +const baseAlertProperty: IConfigurationPropertySchema = { type: 'boolean', default: true, tags: ['accessibility'], From e27f03ea27022b9c7c93413333bdd7cc627c9a62 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 31 Jan 2024 10:06:28 -0800 Subject: [PATCH 0796/1897] Improve wording, add duration string helper function --- src/vs/base/common/date.ts | 31 +++++++++++++++++++ src/vs/base/test/common/date.test.ts | 27 +++++++++++++++- .../browser/xterm/decorationStyles.ts | 31 ++++++++++++++----- 3 files changed, 80 insertions(+), 9 deletions(-) diff --git a/src/vs/base/common/date.ts b/src/vs/base/common/date.ts index 0865e813249..2ae03a711af 100644 --- a/src/vs/base/common/date.ts +++ b/src/vs/base/common/date.ts @@ -199,6 +199,37 @@ export function fromNow(date: number | Date, appendAgoLabel?: boolean, useFullTi } } +/** + * Gets a readable duration with intelligent/lossy precision. For example "40ms" or "3.040s") + * @param ms The duration to get in milliseconds. + * @param useFullTimeWords Whether to use full words (eg. seconds) instead of + * shortened (eg. secs). + */ +export function getDurationString(ms: number, useFullTimeWords?: boolean) { + const seconds = Math.abs(ms / 1000); + if (seconds < 1) { + return useFullTimeWords + ? localize('duration.ms.full', '{0} milliseconds', ms) + : localize('duration.ms', '{0}ms', ms); + } + if (seconds < minute) { + return useFullTimeWords + ? localize('duration.s.full', '{0} seconds', Math.round(ms) / 1000) + : localize('duration.s', '{0}s', Math.round(ms) / 1000); + } + if (seconds < hour) { + return useFullTimeWords + ? localize('duration.m.full', '{0} minutes', Math.round(ms / (1000 * minute))) + : localize('duration.m', '{0} mins', Math.round(ms / (1000 * minute))); + } + if (seconds < day) { + return useFullTimeWords + ? localize('duration.h.full', '{0} hours', Math.round(ms / (1000 * hour))) + : localize('duration.h', '{0} hrs', Math.round(ms / (1000 * hour))); + } + return localize('duration.d', '{0} days', Math.round(ms / (1000 * day))); +} + export function toLocalISOString(date: Date): string { return date.getFullYear() + '-' + String(date.getMonth() + 1).padStart(2, '0') + diff --git a/src/vs/base/test/common/date.test.ts b/src/vs/base/test/common/date.test.ts index 331c7f0e001..2a140e87747 100644 --- a/src/vs/base/test/common/date.test.ts +++ b/src/vs/base/test/common/date.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { strictEqual } from 'assert'; -import { fromNow } from 'vs/base/common/date'; +import { fromNow, getDurationString } from 'vs/base/common/date'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Date', () => { @@ -27,4 +27,29 @@ suite('Date', () => { strictEqual(fromNow(Date.now() - 5000, undefined, undefined, true), '5 secs'); }); }); + + suite('getDurationString', () => { + test('basic', () => { + strictEqual(getDurationString(1), '1ms'); + strictEqual(getDurationString(999), '999ms'); + strictEqual(getDurationString(1000), '1s'); + strictEqual(getDurationString(1000 * 60 - 1), '59.999s'); + strictEqual(getDurationString(1000 * 60), '1 mins'); + strictEqual(getDurationString(1000 * 60 * 60 - 1), '60 mins'); + strictEqual(getDurationString(1000 * 60 * 60), '1 hrs'); + strictEqual(getDurationString(1000 * 60 * 60 * 24 - 1), '24 hrs'); + strictEqual(getDurationString(1000 * 60 * 60 * 24), '1 days'); + }); + test('useFullTimeWords', () => { + strictEqual(getDurationString(1, true), '1 milliseconds'); + strictEqual(getDurationString(999, true), '999 milliseconds'); + strictEqual(getDurationString(1000, true), '1 seconds'); + strictEqual(getDurationString(1000 * 60 - 1, true), '59.999 seconds'); + strictEqual(getDurationString(1000 * 60, true), '1 minutes'); + strictEqual(getDurationString(1000 * 60 * 60 - 1, true), '60 minutes'); + strictEqual(getDurationString(1000 * 60 * 60, true), '1 hours'); + strictEqual(getDurationString(1000 * 60 * 60 * 24 - 1, true), '24 hours'); + strictEqual(getDurationString(1000 * 60 * 60 * 24, true), '1 days'); + }); + }); }); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationStyles.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationStyles.ts index 8fe6007ca68..e2ada5a5a10 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationStyles.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationStyles.ts @@ -5,7 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import { Delayer } from 'vs/base/common/async'; -import { fromNow } from 'vs/base/common/date'; +import { fromNow, getDurationString } from 'vs/base/common/date'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { combinedDisposable, Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; @@ -71,14 +71,29 @@ export class TerminalDecorationHoverManager extends Disposable { } else { return; } - } else if (command.exitCode) { - if (command.exitCode === -1) { - hoverContent += localize('terminalPromptCommandFailed', 'Command executed {0} for {1} ms and failed', fromNow(command.timestamp, true), command.duration); - } else { - hoverContent += localize('terminalPromptCommandFailedWithExitCode', 'Command executed {0} for {2} ms and failed (Exit Code {1})', fromNow(command.timestamp, true), command.exitCode, command.duration); - } } else { - hoverContent += localize('terminalPromptCommandSuccess', 'Command executed {0} for {1} ms', fromNow(command.timestamp, true), command.duration); + if (command.duration) { + const durationText = getDurationString(command.duration); + if (command.exitCode) { + if (command.exitCode === -1) { + hoverContent += localize('terminalPromptCommandFailed.duration', 'Command executed {0}, took {1} and failed', fromNow(command.timestamp, true), durationText); + } else { + hoverContent += localize('terminalPromptCommandFailedWithExitCode.duration', 'Command executed {0}, took {1} and failed (Exit Code {2})', fromNow(command.timestamp, true), durationText, command.exitCode); + } + } else { + hoverContent += localize('terminalPromptCommandSuccess.duration', 'Command executed {0} and took {1}', fromNow(command.timestamp, true), durationText); + } + } else { + if (command.exitCode) { + if (command.exitCode === -1) { + hoverContent += localize('terminalPromptCommandFailed', 'Command executed {0} and failed', fromNow(command.timestamp, true)); + } else { + hoverContent += localize('terminalPromptCommandFailedWithExitCode', 'Command executed {0} and failed (Exit Code {1})', fromNow(command.timestamp, true), command.exitCode); + } + } else { + hoverContent += localize('terminalPromptCommandSuccess', 'Command executed {0}', fromNow(command.timestamp, true)); + } + } } this._hoverService.showHover({ content: new MarkdownString(hoverContent), target: element }); }); From 3d15752604a00c50e9f40f89226a9b5355bd5f28 Mon Sep 17 00:00:00 2001 From: Francois Marier Date: Wed, 31 Jan 2024 10:46:26 -0800 Subject: [PATCH 0797/1897] Use HTTPS for the apt repository (#203833) --- resources/linux/debian/postinst.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/linux/debian/postinst.template b/resources/linux/debian/postinst.template index 9e91489dd14..16acb1481bf 100755 --- a/resources/linux/debian/postinst.template +++ b/resources/linux/debian/postinst.template @@ -51,7 +51,7 @@ if [ "@@NAME@@" != "code-oss" ]; then if [ "$WRITE_SOURCE" -eq "1" ]; then echo "### THIS FILE IS AUTOMATICALLY CONFIGURED ### # You may comment out this entry, but any other modifications may be lost. -deb [arch=amd64,arm64,armhf] http://packages.microsoft.com/repos/code stable main" > $CODE_SOURCE_PART +deb [arch=amd64,arm64,armhf] https://packages.microsoft.com/repos/code stable main" > $CODE_SOURCE_PART # Sourced from https://packages.microsoft.com/keys/microsoft.asc if [ ! -f $CODE_TRUSTED_PART ]; then From bf0a1b52341af8524e9136cc709a8d1cdc4f0ade Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 31 Jan 2024 12:53:21 -0600 Subject: [PATCH 0798/1897] render in settings ui better --- .../contrib/audioCues/browser/audioCues.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index e8605ed3434..b066ce9a2ba 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -47,7 +47,7 @@ const alertFeatureBase: IConfigurationPropertySchema = { const signalFeatureBase: IConfigurationPropertySchema = { 'type': 'object', 'tags': ['accessibility'], - additionalProperties: true, + additionalProperties: false, default: { audioCue: 'auto', alert: true From 8980d5e2322aed505077336fff8ae817d4296d85 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 31 Jan 2024 19:17:19 +0000 Subject: [PATCH 0799/1897] Adopt `localize2` in a few more places (#203919) Add a more usages of `localize2` This pass covers some more advanced cases my previous pass missed. Also caught a few instances where the original string was different from the actual localize string --- .../browser/standaloneColorPickerActions.ts | 5 +- .../gotoSymbol/browser/goToCommands.ts | 25 +++--- .../browser/stickyScrollActions.ts | 6 +- .../browser/toggleTabFocusMode.ts | 2 +- .../action/common/actionCommonCategories.ts | 4 +- .../common/extensionManagement.ts | 5 +- .../browser/actions/developerActions.ts | 4 +- .../workbench/browser/actions/helpActions.ts | 24 ++---- .../browser/actions/layoutActions.ts | 15 ++-- .../workbench/browser/actions/listCommands.ts | 5 +- .../browser/actions/windowActions.ts | 12 +-- .../parts/activitybar/activitybarPart.ts | 9 +-- .../parts/auxiliarybar/auxiliaryBarActions.ts | 9 +-- .../browser/parts/banner/bannerPart.ts | 6 +- .../parts/editor/breadcrumbsControl.ts | 3 +- .../parts/editor/editor.contribution.ts | 78 ++++++++----------- .../browser/parts/editor/editorActions.ts | 28 +++---- .../browser/parts/editor/editorCommands.ts | 10 +-- .../browser/parts/panel/panelActions.ts | 4 +- .../chat/browser/actions/chatActions.ts | 2 +- .../quickaccess/gotoSymbolQuickAccess.ts | 5 +- .../browser/toggleColumnSelection.ts | 5 +- .../codeEditor/browser/toggleMinimap.ts | 7 +- .../browser/toggleRenderControlCharacter.ts | 5 +- .../browser/toggleRenderWhitespace.ts | 5 +- .../contrib/debug/browser/breakpointsView.ts | 18 ++--- .../contrib/debug/browser/debugCommands.ts | 2 +- .../debug/browser/debugEditorActions.ts | 13 ++-- .../editSessions/common/editSessions.ts | 5 +- .../browser/extensions.contribution.ts | 22 +++--- .../extensions/browser/extensionsActions.ts | 4 +- .../extensions/browser/extensionsViewlet.ts | 2 +- .../files/browser/views/openEditorsView.ts | 3 +- .../browser/contrib/troubleshoot/layout.ts | 12 +-- .../browser/controller/foldingController.ts | 4 +- .../view/cellParts/chat/cellChatActions.ts | 5 +- .../viewParts/notebookEditorStickyScroll.ts | 5 +- .../browser/preferences.contribution.ts | 17 ++-- .../remoteTunnel.contribution.ts | 8 +- .../search/browser/searchActionsFind.ts | 3 +- .../search/browser/searchActionsSymbol.ts | 5 +- .../browser/searchEditor.contribution.ts | 4 +- .../browser/commands/configureSnippets.ts | 3 +- .../terminal/common/terminalStrings.ts | 6 +- .../testing/browser/testExplorerActions.ts | 4 +- .../update/browser/update.contribution.ts | 8 +- .../browser/userDataProfile.ts | 25 ++---- .../electron-sandbox/actions/windowActions.ts | 12 +-- .../views/browser/viewDescriptorService.ts | 7 +- .../services/views/browser/viewsService.ts | 7 +- 50 files changed, 183 insertions(+), 304 deletions(-) diff --git a/src/vs/editor/contrib/colorPicker/browser/standaloneColorPickerActions.ts b/src/vs/editor/contrib/colorPicker/browser/standaloneColorPickerActions.ts index 5ccb725eb8b..a399e560ed4 100644 --- a/src/vs/editor/contrib/colorPicker/browser/standaloneColorPickerActions.ts +++ b/src/vs/editor/contrib/colorPicker/browser/standaloneColorPickerActions.ts @@ -6,7 +6,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorAction2, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { StandaloneColorPickerController } from 'vs/editor/contrib/colorPicker/browser/standaloneColorPickerWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -18,9 +18,8 @@ export class ShowOrFocusStandaloneColorPicker extends EditorAction2 { super({ id: 'editor.action.showOrFocusStandaloneColorPicker', title: { - value: localize('showOrFocusStandaloneColorPicker', "Show or Focus Standalone Color Picker"), + ...localize2('showOrFocusStandaloneColorPicker', "Show or Focus Standalone Color Picker"), mnemonicTitle: localize({ key: 'mishowOrFocusStandaloneColorPicker', comment: ['&& denotes a mnemonic'] }, "&&Show or Focus Standalone Color Picker"), - original: 'Show or Focus Standalone Color Picker', }, precondition: undefined, menu: [ diff --git a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts index a0f216cf3a8..c579e25acc4 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts @@ -283,9 +283,8 @@ registerAction2(class GoToDefinitionAction extends DefinitionAction { }, { id: GoToDefinitionAction.id, title: { - value: nls.localize('actions.goToDecl.label', "Go to Definition"), - original: 'Go to Definition', - mnemonicTitle: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition") + ...nls.localize2('actions.goToDecl.label', "Go to Definition"), + mnemonicTitle: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition"), }, precondition: ContextKeyExpr.and( EditorContextKeys.hasDefinitionProvider, @@ -413,9 +412,8 @@ registerAction2(class GoToDeclarationAction extends DeclarationAction { }, { id: GoToDeclarationAction.id, title: { - value: nls.localize('actions.goToDeclaration.label', "Go to Declaration"), - original: 'Go to Declaration', - mnemonicTitle: nls.localize({ key: 'miGotoDeclaration', comment: ['&& denotes a mnemonic'] }, "Go to &&Declaration") + ...nls.localize2('actions.goToDeclaration.label', "Go to Declaration"), + mnemonicTitle: nls.localize({ key: 'miGotoDeclaration', comment: ['&& denotes a mnemonic'] }, "Go to &&Declaration"), }, precondition: ContextKeyExpr.and( EditorContextKeys.hasDeclarationProvider, @@ -501,9 +499,8 @@ registerAction2(class GoToTypeDefinitionAction extends TypeDefinitionAction { }, { id: GoToTypeDefinitionAction.ID, title: { - value: nls.localize('actions.goToTypeDefinition.label', "Go to Type Definition"), - original: 'Go to Type Definition', - mnemonicTitle: nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definition") + ...nls.localize2('actions.goToTypeDefinition.label', "Go to Type Definition"), + mnemonicTitle: nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definition"), }, precondition: ContextKeyExpr.and( EditorContextKeys.hasTypeDefinitionProvider, @@ -590,9 +587,8 @@ registerAction2(class GoToImplementationAction extends ImplementationAction { }, { id: GoToImplementationAction.ID, title: { - value: nls.localize('actions.goToImplementation.label', "Go to Implementations"), - original: 'Go to Implementations', - mnemonicTitle: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementations") + ...nls.localize2('actions.goToImplementation.label', "Go to Implementations"), + mnemonicTitle: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementations"), }, precondition: ContextKeyExpr.and( EditorContextKeys.hasImplementationProvider, @@ -678,9 +674,8 @@ registerAction2(class GoToReferencesAction extends ReferencesAction { }, { id: 'editor.action.goToReferences', title: { - value: nls.localize('goToReferences.label', "Go to References"), - original: 'Go to References', - mnemonicTitle: nls.localize({ key: 'miGotoReference', comment: ['&& denotes a mnemonic'] }, "Go to &&References") + ...nls.localize2('goToReferences.label', "Go to References"), + mnemonicTitle: nls.localize({ key: 'miGotoReference', comment: ['&& denotes a mnemonic'] }, "Go to &&References"), }, precondition: ContextKeyExpr.and( EditorContextKeys.hasReferenceProvider, diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollActions.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollActions.ts index 5a1b6b9e48b..1e49f122979 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollActions.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollActions.ts @@ -21,9 +21,8 @@ export class ToggleStickyScroll extends Action2 { super({ id: 'editor.action.toggleStickyScroll', title: { - value: localize('toggleEditorStickyScroll', "Toggle Editor Sticky Scroll"), + ...localize2('toggleEditorStickyScroll', "Toggle Editor Sticky Scroll"), mnemonicTitle: localize({ key: 'mitoggleStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Toggle Editor Sticky Scroll"), - original: 'Toggle Editor Sticky Scroll', }, category: Categories.View, toggled: { @@ -54,9 +53,8 @@ export class FocusStickyScroll extends EditorAction2 { super({ id: 'editor.action.focusStickyScroll', title: { - value: localize('focusStickyScroll', "Focus Sticky Scroll"), + ...localize2('focusStickyScroll', "Focus Sticky Scroll"), mnemonicTitle: localize({ key: 'mifocusStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Focus Sticky Scroll"), - original: 'Focus Sticky Scroll', }, precondition: ContextKeyExpr.and(ContextKeyExpr.has('config.editor.stickyScroll.enabled'), EditorContextKeys.stickyScrollVisible), menu: [ diff --git a/src/vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode.ts b/src/vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode.ts index 2dfd2309a8e..64f4b309e8a 100644 --- a/src/vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode.ts +++ b/src/vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode.ts @@ -17,7 +17,7 @@ export class ToggleTabFocusModeAction extends Action2 { constructor() { super({ id: ToggleTabFocusModeAction.ID, - title: { value: nls.localize({ key: 'toggle.tabMovesFocus', comment: ['Turn on/off use of tab key for moving focus around VS Code'] }, 'Toggle Tab Key Moves Focus'), original: 'Toggle Tab Key Moves Focus' }, + title: nls.localize2({ key: 'toggle.tabMovesFocus', comment: ['Turn on/off use of tab key for moving focus around VS Code'] }, 'Toggle Tab Key Moves Focus'), precondition: undefined, keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyM, diff --git a/src/vs/platform/action/common/actionCommonCategories.ts b/src/vs/platform/action/common/actionCommonCategories.ts index 943b9c65149..dddc652d868 100644 --- a/src/vs/platform/action/common/actionCommonCategories.ts +++ b/src/vs/platform/action/common/actionCommonCategories.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize, localize2 } from 'vs/nls'; +import { localize2 } from 'vs/nls'; export const Categories = Object.freeze({ View: localize2('view', 'View'), @@ -11,5 +11,5 @@ export const Categories = Object.freeze({ Test: localize2('test', 'Test'), File: localize2('file', 'File'), Preferences: localize2('preferences', 'Preferences'), - Developer: { value: localize({ key: 'developer', comment: ['A developer on Code itself or someone diagnosing issues in Code'] }, "Developer"), original: 'Developer' } + Developer: localize2({ key: 'developer', comment: ['A developer on Code itself or someone diagnosing issues in Code'] }, "Developer"), }); diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 15907ec4882..756acf86b45 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -9,7 +9,7 @@ import { Event } from 'vs/base/common/event'; import { IPager } from 'vs/base/common/paging'; import { Platform } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; -import { localize, localize2 } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { ExtensionType, IExtension, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -545,6 +545,5 @@ export interface IExtensionTipsService { getOtherExecutableBasedTips(): Promise; } -export const ExtensionsLabel = localize('extensions', "Extensions"); -export const ExtensionsLocalizedLabel = { value: ExtensionsLabel, original: 'Extensions' }; +export const ExtensionsLocalizedLabel = localize2('extensions', "Extensions"); export const PreferencesLocalizedLabel = localize2('preferences', 'Preferences'); diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index 61d008d6404..2c20a3572e2 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -396,7 +396,7 @@ class LogStorageAction extends Action2 { constructor() { super({ id: 'workbench.action.logStorage', - title: { value: localize({ key: 'logStorage', comment: ['A developer only action to log the contents of the storage for the current window.'] }, "Log Storage Database Contents"), original: 'Log Storage Database Contents' }, + title: localize2({ key: 'logStorage', comment: ['A developer only action to log the contents of the storage for the current window.'] }, "Log Storage Database Contents"), category: Categories.Developer, f1: true }); @@ -417,7 +417,7 @@ class LogWorkingCopiesAction extends Action2 { constructor() { super({ id: 'workbench.action.logWorkingCopies', - title: { value: localize({ key: 'logWorkingCopies', comment: ['A developer only action to log the working copies that exist.'] }, "Log Working Copies"), original: 'Log Working Copies' }, + title: localize2({ key: 'logWorkingCopies', comment: ['A developer only action to log the working copies that exist.'] }, "Log Working Copies"), category: Categories.Developer, f1: true }); diff --git a/src/vs/workbench/browser/actions/helpActions.ts b/src/vs/workbench/browser/actions/helpActions.ts index cc993982dc8..fd55b27b185 100644 --- a/src/vs/workbench/browser/actions/helpActions.ts +++ b/src/vs/workbench/browser/actions/helpActions.ts @@ -25,9 +25,8 @@ class KeybindingsReferenceAction extends Action2 { super({ id: KeybindingsReferenceAction.ID, title: { - value: localize('keybindingsReference', "Keyboard Shortcuts Reference"), + ...localize2('keybindingsReference', "Keyboard Shortcuts Reference"), mnemonicTitle: localize({ key: 'miKeyboardShortcuts', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts Reference"), - original: 'Keyboard Shortcuts Reference' }, category: Categories.Help, f1: true, @@ -64,9 +63,8 @@ class OpenIntroductoryVideosUrlAction extends Action2 { super({ id: OpenIntroductoryVideosUrlAction.ID, title: { - value: localize('openVideoTutorialsUrl', "Video Tutorials"), + ...localize2('openVideoTutorialsUrl', "Video Tutorials"), mnemonicTitle: localize({ key: 'miVideoTutorials', comment: ['&& denotes a mnemonic'] }, "&&Video Tutorials"), - original: 'Video Tutorials' }, category: Categories.Help, f1: true, @@ -97,9 +95,8 @@ class OpenTipsAndTricksUrlAction extends Action2 { super({ id: OpenTipsAndTricksUrlAction.ID, title: { - value: localize('openTipsAndTricksUrl', "Tips and Tricks"), + ...localize2('openTipsAndTricksUrl', "Tips and Tricks"), mnemonicTitle: localize({ key: 'miTipsAndTricks', comment: ['&& denotes a mnemonic'] }, "Tips and Tri&&cks"), - original: 'Tips and Tricks' }, category: Categories.Help, f1: true, @@ -130,9 +127,8 @@ class OpenDocumentationUrlAction extends Action2 { super({ id: OpenDocumentationUrlAction.ID, title: { - value: localize('openDocumentationUrl', "Documentation"), + ...localize2('openDocumentationUrl', "Documentation"), mnemonicTitle: localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation"), - original: 'Documentation' }, category: Categories.Help, f1: true, @@ -186,9 +182,8 @@ class OpenYouTubeUrlAction extends Action2 { super({ id: OpenYouTubeUrlAction.ID, title: { - value: localize('openYouTubeUrl', "Join Us on YouTube"), + ...localize2('openYouTubeUrl', "Join Us on YouTube"), mnemonicTitle: localize({ key: 'miYouTube', comment: ['&& denotes a mnemonic'] }, "&&Join Us on YouTube"), - original: 'Join Us on YouTube' }, category: Categories.Help, f1: true, @@ -219,9 +214,8 @@ class OpenRequestFeatureUrlAction extends Action2 { super({ id: OpenRequestFeatureUrlAction.ID, title: { - value: localize('openUserVoiceUrl', "Search Feature Requests"), + ...localize2('openUserVoiceUrl', "Search Feature Requests"), mnemonicTitle: localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests"), - original: 'Search Feature Requests' }, category: Categories.Help, f1: true, @@ -252,9 +246,8 @@ class OpenLicenseUrlAction extends Action2 { super({ id: OpenLicenseUrlAction.ID, title: { - value: localize('openLicenseUrl', "View License"), + ...localize2('openLicenseUrl', "View License"), mnemonicTitle: localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License"), - original: 'View License' }, category: Categories.Help, f1: true, @@ -291,9 +284,8 @@ class OpenPrivacyStatementUrlAction extends Action2 { super({ id: OpenPrivacyStatementUrlAction.ID, title: { - value: localize('openPrivacyStatement', "Privacy Statement"), + ...localize2('openPrivacyStatement', "Privacy Statement"), mnemonicTitle: localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "Privac&&y Statement"), - original: 'Privacy Statement' }, category: Categories.Help, f1: true, diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 24a8bfa0151..765693c16c8 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -81,9 +81,8 @@ registerAction2(class extends Action2 { super({ id: 'workbench.action.toggleCenteredLayout', title: { - value: localize('toggleCenteredLayout', "Toggle Centered Layout"), + ...localize2('toggleCenteredLayout', "Toggle Centered Layout"), mnemonicTitle: localize({ key: 'miToggleCenteredLayout', comment: ['&& denotes a mnemonic'] }, "&&Centered Layout"), - original: 'Toggle Centered Layout' }, precondition: IsAuxiliaryWindowFocusedContext.toNegated(), category: Categories.View, @@ -285,9 +284,8 @@ registerAction2(class extends Action2 { super({ id: 'workbench.action.toggleEditorVisibility', title: { - value: localize('toggleEditor', "Toggle Editor Area Visibility"), + ...localize2('toggleEditor', "Toggle Editor Area Visibility"), mnemonicTitle: localize({ key: 'miShowEditorArea', comment: ['&& denotes a mnemonic'] }, "Show &&Editor Area"), - original: 'Toggle Editor Area Visibility' }, category: Categories.View, f1: true, @@ -418,9 +416,8 @@ export class ToggleStatusbarVisibilityAction extends Action2 { super({ id: ToggleStatusbarVisibilityAction.ID, title: { - value: localize('toggleStatusbar', "Toggle Status Bar Visibility"), + ...localize2('toggleStatusbar', "Toggle Status Bar Visibility"), mnemonicTitle: localize({ key: 'miStatusbar', comment: ['&& denotes a mnemonic'] }, "S&&tatus Bar"), - original: 'Toggle Status Bar Visibility' }, category: Categories.View, f1: true, @@ -703,9 +700,8 @@ registerAction2(class extends Action2 { super({ id: 'workbench.action.toggleZenMode', title: { - value: localize('toggleZenMode', "Toggle Zen Mode"), + ...localize2('toggleZenMode', "Toggle Zen Mode"), mnemonicTitle: localize({ key: 'miToggleZenMode', comment: ['&& denotes a mnemonic'] }, "Zen Mode"), - original: 'Toggle Zen Mode' }, precondition: IsAuxiliaryWindowFocusedContext.toNegated(), category: Categories.View, @@ -751,9 +747,8 @@ if (isWindows || isLinux || isWeb) { super({ id: 'workbench.action.toggleMenuBar', title: { - value: localize('toggleMenuBar', "Toggle Menu Bar"), + ...localize2('toggleMenuBar', "Toggle Menu Bar"), mnemonicTitle: localize({ key: 'miMenuBar', comment: ['&& denotes a mnemonic'] }, "Menu &&Bar"), - original: 'Toggle Menu Bar' }, category: Categories.View, f1: true, diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index 33f21c3b41b..dffef61a015 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -21,7 +21,7 @@ import { AbstractTree, TreeFindMatchType, TreeFindMode } from 'vs/base/browser/u import { isActiveElement } from 'vs/base/browser/dom'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; function ensureDOMFocus(widget: ListWidget | undefined): void { // it can happen that one of the commands is executed while @@ -913,9 +913,8 @@ registerAction2(class ToggleStickyScroll extends Action2 { super({ id: 'tree.toggleStickyScroll', title: { - value: localize('toggleTreeStickyScroll', "Toggle Tree Sticky Scroll"), + ...localize2('toggleTreeStickyScroll', "Toggle Tree Sticky Scroll"), mnemonicTitle: localize({ key: 'mitoggleTreeStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Toggle Tree Sticky Scroll"), - original: 'Toggle Tree Sticky Scroll', }, category: 'View', f1: true diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 1fe675afa5e..4020ac2b0e6 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -237,9 +237,8 @@ export class OpenRecentAction extends BaseOpenRecentAction { super({ id: OpenRecentAction.ID, title: { - value: localize('openRecent', "Open Recent..."), + ...localize2('openRecent', "Open Recent..."), mnemonicTitle: localize({ key: 'miMore', comment: ['&& denotes a mnemonic'] }, "&&More..."), - original: 'Open Recent...' }, category: Categories.File, f1: true, @@ -283,9 +282,8 @@ class ToggleFullScreenAction extends Action2 { super({ id: 'workbench.action.toggleFullScreen', title: { - value: localize('toggleFullScreen', "Toggle Full Screen"), + ...localize2('toggleFullScreen', "Toggle Full Screen"), mnemonicTitle: localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "&&Full Screen"), - original: 'Toggle Full Screen' }, category: Categories.View, f1: true, @@ -344,9 +342,8 @@ class ShowAboutDialogAction extends Action2 { super({ id: 'workbench.action.showAboutDialog', title: { - value: localize('about', "About"), + ...localize2('about', "About"), mnemonicTitle: localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About"), - original: 'About' }, category: Categories.Help, f1: true, @@ -372,9 +369,8 @@ class NewWindowAction extends Action2 { super({ id: 'workbench.action.newWindow', title: { - value: localize('newWindow', "New Window"), + ...localize2('newWindow', "New Window"), mnemonicTitle: localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window"), - original: 'New Window' }, f1: true, keybinding: { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 30cd016ad19..2e2047e8f1d 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -380,8 +380,7 @@ registerAction2(class extends Action2 { super({ id: 'workbench.action.activityBarLocation.side', title: { - value: localize('positionActivityBarSide', 'Move Activity Bar to Side'), - original: 'Move Activity Bar to Side', + ...localize2('positionActivityBarSide', 'Move Activity Bar to Side'), mnemonicTitle: localize({ key: 'miSideActivityBar', comment: ['&& denotes a mnemonic'] }, "&&Side"), }, shortTitle: localize('side', "Side"), @@ -407,8 +406,7 @@ registerAction2(class extends Action2 { super({ id: 'workbench.action.activityBarLocation.top', title: { - value: localize('positionActivityBarTop', 'Move Activity Bar to Top'), - original: 'Move Activity Bar to Top', + ...localize2('positionActivityBarTop', 'Move Activity Bar to Top'), mnemonicTitle: localize({ key: 'miTopActivityBar', comment: ['&& denotes a mnemonic'] }, "&&Top"), }, shortTitle: localize('top', "Top"), @@ -434,8 +432,7 @@ registerAction2(class extends Action2 { super({ id: 'workbench.action.activityBarLocation.hide', title: { - value: localize('hideActivityBar', 'Hide Activity Bar'), - original: 'Hide Activity Bar', + ...localize2('hideActivityBar', 'Hide Activity Bar'), mnemonicTitle: localize({ key: 'miHideActivityBar', comment: ['&& denotes a mnemonic'] }, "&&Hidden"), }, shortTitle: localize('hide', "Hidden"), diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts index ff3f99716c8..3de5a2cc2d7 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts @@ -26,12 +26,12 @@ const auxiliaryBarLeftOffIcon = registerIcon('auxiliarybar-left-off-layout-icon' export class ToggleAuxiliaryBarAction extends Action2 { static readonly ID = 'workbench.action.toggleAuxiliaryBar'; - static readonly LABEL = localize('toggleAuxiliaryBar', "Toggle Secondary Side Bar Visibility"); + static readonly LABEL = localize2('toggleAuxiliaryBar', "Toggle Secondary Side Bar Visibility"); constructor() { super({ id: ToggleAuxiliaryBarAction.ID, - title: { value: ToggleAuxiliaryBarAction.LABEL, original: 'Toggle Secondary Side Bar Visibility' }, + title: ToggleAuxiliaryBarAction.LABEL, toggled: { condition: AuxiliaryBarVisibleContext, title: localize('secondary sidebar', "Secondary Side Bar"), @@ -70,13 +70,12 @@ registerAction2(ToggleAuxiliaryBarAction); registerAction2(class FocusAuxiliaryBarAction extends Action2 { static readonly ID = 'workbench.action.focusAuxiliaryBar'; - static readonly LABEL = localize('focusAuxiliaryBar', "Focus into Secondary Side Bar"); - + static readonly LABEL = localize2('focusAuxiliaryBar', "Focus into Secondary Side Bar"); constructor() { super({ id: FocusAuxiliaryBarAction.ID, - title: { value: FocusAuxiliaryBarAction.LABEL, original: 'Focus into Secondary Side Bar' }, + title: FocusAuxiliaryBarAction.LABEL, category: Categories.View, f1: true, }); diff --git a/src/vs/workbench/browser/parts/banner/bannerPart.ts b/src/vs/workbench/browser/parts/banner/bannerPart.ts index 29fb5f7f53a..ccf73b4808e 100644 --- a/src/vs/workbench/browser/parts/banner/bannerPart.ts +++ b/src/vs/workbench/browser/parts/banner/bannerPart.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/bannerpart'; -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { $, addDisposableListener, append, asCSSUrl, clearNode, EventType } from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -285,12 +285,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ class FocusBannerAction extends Action2 { static readonly ID = 'workbench.action.focusBanner'; - static readonly LABEL = localize('focusBanner', "Focus Banner"); + static readonly LABEL = localize2('focusBanner', "Focus Banner"); constructor() { super({ id: FocusBannerAction.ID, - title: { value: FocusBannerAction.LABEL, original: 'Focus Banner' }, + title: FocusBannerAction.LABEL, category: Categories.View, f1: true }); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 5470d409f4b..0eb27fcfcb6 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -598,9 +598,8 @@ registerAction2(class ToggleBreadcrumb extends Action2 { super({ id: 'breadcrumbs.toggle', title: { - value: localize('cmd.toggle', "Toggle Breadcrumbs"), + ...localize2('cmd.toggle', "Toggle Breadcrumbs"), mnemonicTitle: localize({ key: 'miBreadcrumbs', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breadcrumbs"), - original: 'Toggle Breadcrumbs', }, category: Categories.View, toggled: { diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 535c06914ef..f240fb9aac5 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -678,8 +678,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { command: { id: SPLIT_EDITOR_UP, title: { - original: 'Split Up', - value: localize('miSplitEditorUpWithoutMnemonic', "Split Up"), + ...localize2('miSplitEditorUpWithoutMnemonic', "Split Up"), mnemonicTitle: localize({ key: 'miSplitEditorUp', comment: ['&& denotes a mnemonic'] }, "Split &&Up"), } }, @@ -691,9 +690,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { command: { id: SPLIT_EDITOR_DOWN, title: { - original: 'Split Down', - value: localize('miSplitEditorDownWithoutMnemonic', "Split Down"), - mnemonicTitle: localize({ key: 'miSplitEditorDown', comment: ['&& denotes a mnemonic'] }, "Split &&Down") + ...localize2('miSplitEditorDownWithoutMnemonic', "Split Down"), + mnemonicTitle: localize({ key: 'miSplitEditorDown', comment: ['&& denotes a mnemonic'] }, "Split &&Down"), } }, order: 2 @@ -704,9 +702,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { command: { id: SPLIT_EDITOR_LEFT, title: { - original: 'Split Left', - value: localize('miSplitEditorLeftWithoutMnemonic', "Split Left"), - mnemonicTitle: localize({ key: 'miSplitEditorLeft', comment: ['&& denotes a mnemonic'] }, "Split &&Left") + ...localize2('miSplitEditorLeftWithoutMnemonic', "Split Left"), + mnemonicTitle: localize({ key: 'miSplitEditorLeft', comment: ['&& denotes a mnemonic'] }, "Split &&Left"), } }, order: 3 @@ -717,9 +714,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { command: { id: SPLIT_EDITOR_RIGHT, title: { - original: 'Split Right', - value: localize('miSplitEditorRightWithoutMnemonic', "Split Right"), - mnemonicTitle: localize({ key: 'miSplitEditorRight', comment: ['&& denotes a mnemonic'] }, "Split &&Right") + ...localize2('miSplitEditorRightWithoutMnemonic', "Split Right"), + mnemonicTitle: localize({ key: 'miSplitEditorRight', comment: ['&& denotes a mnemonic'] }, "Split &&Right"), } }, order: 4 @@ -730,9 +726,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { command: { id: SPLIT_EDITOR_IN_GROUP, title: { - original: 'Split in Group', - value: localize('miSplitEditorInGroupWithoutMnemonic', "Split in Group"), - mnemonicTitle: localize({ key: 'miSplitEditorInGroup', comment: ['&& denotes a mnemonic'] }, "Split in &&Group") + ...localize2('miSplitEditorInGroupWithoutMnemonic', "Split in Group"), + mnemonicTitle: localize({ key: 'miSplitEditorInGroup', comment: ['&& denotes a mnemonic'] }, "Split in &&Group"), } }, when: ActiveEditorCanSplitInGroupContext, @@ -744,9 +739,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { command: { id: JOIN_EDITOR_IN_GROUP, title: { - original: 'Join in Group', - value: localize('miJoinEditorInGroupWithoutMnemonic', "Join in Group"), - mnemonicTitle: localize({ key: 'miJoinEditorInGroup', comment: ['&& denotes a mnemonic'] }, "Join in &&Group") + ...localize2('miJoinEditorInGroupWithoutMnemonic', "Join in Group"), + mnemonicTitle: localize({ key: 'miJoinEditorInGroup', comment: ['&& denotes a mnemonic'] }, "Join in &&Group"), } }, when: SideBySideEditorActiveContext, @@ -758,9 +752,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { command: { id: MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, title: { - original: 'Move Editor into New Window', - value: localize('moveEditorToNewWindow', "Move Editor into New Window"), - mnemonicTitle: localize({ key: 'miMoveEditorToNewWindow', comment: ['&& denotes a mnemonic'] }, "&&Move Editor into New Window") + ...localize2('moveEditorToNewWindow', "Move Editor into New Window"), + mnemonicTitle: localize({ key: 'miMoveEditorToNewWindow', comment: ['&& denotes a mnemonic'] }, "&&Move Editor into New Window"), } }, order: 1 @@ -771,9 +764,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { command: { id: COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, title: { - original: 'Copy Editor into New Window', - value: localize('copyEditorToNewWindow', "Copy Editor into New Window"), - mnemonicTitle: localize({ key: 'miCopyEditorToNewWindow', comment: ['&& denotes a mnemonic'] }, "&&Copy Editor into New Window") + ...localize2('copyEditorToNewWindow', "Copy Editor into New Window"), + mnemonicTitle: localize({ key: 'miCopyEditorToNewWindow', comment: ['&& denotes a mnemonic'] }, "&&Copy Editor into New Window"), } }, order: 2 @@ -784,9 +776,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { command: { id: EditorLayoutSingleAction.ID, title: { - original: 'Single', - value: localize('miSingleColumnEditorLayoutWithoutMnemonic', "Single"), - mnemonicTitle: localize({ key: 'miSingleColumnEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Single") + ...localize2('miSingleColumnEditorLayoutWithoutMnemonic', "Single"), + mnemonicTitle: localize({ key: 'miSingleColumnEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Single"), } }, order: 1 @@ -797,9 +788,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { command: { id: EditorLayoutTwoColumnsAction.ID, title: { - original: 'Two Columns', - value: localize('miTwoColumnsEditorLayoutWithoutMnemonic', "Two Columns"), - mnemonicTitle: localize({ key: 'miTwoColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Two Columns") + ...localize2('miTwoColumnsEditorLayoutWithoutMnemonic', "Two Columns"), + mnemonicTitle: localize({ key: 'miTwoColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Two Columns"), } }, order: 3 @@ -810,9 +800,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { command: { id: EditorLayoutThreeColumnsAction.ID, title: { - original: 'Three Columns', - value: localize('miThreeColumnsEditorLayoutWithoutMnemonic', "Three Columns"), - mnemonicTitle: localize({ key: 'miThreeColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&hree Columns") + ...localize2('miThreeColumnsEditorLayoutWithoutMnemonic', "Three Columns"), + mnemonicTitle: localize({ key: 'miThreeColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&hree Columns"), } }, order: 4 @@ -823,9 +812,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { command: { id: EditorLayoutTwoRowsAction.ID, title: { - original: 'Two Rows', - value: localize('miTwoRowsEditorLayoutWithoutMnemonic', "Two Rows"), - mnemonicTitle: localize({ key: 'miTwoRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&wo Rows") + ...localize2('miTwoRowsEditorLayoutWithoutMnemonic', "Two Rows"), + mnemonicTitle: localize({ key: 'miTwoRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&wo Rows"), } }, order: 5 @@ -836,9 +824,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { command: { id: EditorLayoutThreeRowsAction.ID, title: { - original: 'Three Rows', - value: localize('miThreeRowsEditorLayoutWithoutMnemonic', "Three Rows"), - mnemonicTitle: localize({ key: 'miThreeRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "Three &&Rows") + ...localize2('miThreeRowsEditorLayoutWithoutMnemonic', "Three Rows"), + mnemonicTitle: localize({ key: 'miThreeRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "Three &&Rows"), } }, order: 6 @@ -849,9 +836,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { command: { id: EditorLayoutTwoByTwoGridAction.ID, title: { - original: 'Grid (2x2)', - value: localize('miTwoByTwoGridEditorLayoutWithoutMnemonic', "Grid (2x2)"), - mnemonicTitle: localize({ key: 'miTwoByTwoGridEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Grid (2x2)") + ...localize2('miTwoByTwoGridEditorLayoutWithoutMnemonic', "Grid (2x2)"), + mnemonicTitle: localize({ key: 'miTwoByTwoGridEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Grid (2x2)"), } }, order: 7 @@ -862,9 +848,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { command: { id: EditorLayoutTwoRowsRightAction.ID, title: { - original: 'Two Rows Right', - value: localize('miTwoRowsRightEditorLayoutWithoutMnemonic', "Two Rows Right"), - mnemonicTitle: localize({ key: 'miTwoRowsRightEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two R&&ows Right") + ...localize2('miTwoRowsRightEditorLayoutWithoutMnemonic', "Two Rows Right"), + mnemonicTitle: localize({ key: 'miTwoRowsRightEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two R&&ows Right"), } }, order: 8 @@ -875,9 +860,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { command: { id: EditorLayoutTwoColumnsBottomAction.ID, title: { - original: 'Two Columns Bottom', - value: localize('miTwoColumnsBottomEditorLayoutWithoutMnemonic', "Two Columns Bottom"), - mnemonicTitle: localize({ key: 'miTwoColumnsBottomEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two &&Columns Bottom") + ...localize2('miTwoColumnsBottomEditorLayoutWithoutMnemonic', "Two Columns Bottom"), + mnemonicTitle: localize({ key: 'miTwoColumnsBottomEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two &&Columns Bottom"), } }, order: 9 diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index ca749d9e59b..b02d66f3cf4 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -1380,7 +1380,10 @@ export class NavigateForwardAction extends Action2 { constructor() { super({ id: NavigateForwardAction.ID, - title: { value: localize('navigateForward', "Go Forward"), original: 'Go Forward', mnemonicTitle: localize({ key: 'miForward', comment: ['&& denotes a mnemonic'] }, "&&Forward") }, + title: { + ...localize2('navigateForward', "Go Forward"), + mnemonicTitle: localize({ key: 'miForward', comment: ['&& denotes a mnemonic'] }, "&&Forward") + }, f1: true, icon: Codicon.arrowRight, precondition: ContextKeyExpr.has('canNavigateForward'), @@ -1412,7 +1415,10 @@ export class NavigateBackwardsAction extends Action2 { constructor() { super({ id: NavigateBackwardsAction.ID, - title: { value: localize('navigateBack', "Go Back"), original: 'Go Back', mnemonicTitle: localize({ key: 'miBack', comment: ['&& denotes a mnemonic'] }, "&&Back") }, + title: { + ...localize2('navigateBack', "Go Back"), + mnemonicTitle: localize({ key: 'miBack', comment: ['&& denotes a mnemonic'] }, "&&Back") + }, f1: true, precondition: ContextKeyExpr.has('canNavigateBack'), icon: Codicon.arrowLeft, @@ -2530,9 +2536,8 @@ export class MoveEditorToNewWindowAction extends BaseMoveCopyEditorToNewWindowAc super( MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, { - value: localize('moveEditorToNewWindow', "Move Editor into New Window"), + ...localize2('moveEditorToNewWindow', "Move Editor into New Window"), mnemonicTitle: localize({ key: 'miMoveEditorToNewWindow', comment: ['&& denotes a mnemonic'] }, "&&Move Editor into New Window"), - original: 'Move Editor into New Window' }, undefined, true @@ -2546,9 +2551,8 @@ export class CopyEditorToNewindowAction extends BaseMoveCopyEditorToNewWindowAct super( COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, { - value: localize('copyEditorToNewWindow', "Copy Editor into New Window"), + ...localize2('copyEditorToNewWindow', "Copy Editor into New Window"), mnemonicTitle: localize({ key: 'miCopyEditorToNewWindow', comment: ['&& denotes a mnemonic'] }, "&&Copy Editor into New Window"), - original: 'Copy Editor into New Window' }, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyO), weight: KeybindingWeight.WorkbenchContrib }, false @@ -2591,9 +2595,8 @@ export class MoveEditorGroupToNewWindowAction extends BaseMoveCopyEditorGroupToN super( MOVE_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, { - value: localize('moveEditorGroupToNewWindow', "Move Editor Group into New Window"), + ...localize2('moveEditorGroupToNewWindow', "Move Editor Group into New Window"), mnemonicTitle: localize({ key: 'miMoveEditorGroupToNewWindow', comment: ['&& denotes a mnemonic'] }, "&&Move Editor Group into New Window"), - original: 'Move Editor Group into New Window' }, true ); @@ -2606,9 +2609,8 @@ export class CopyEditorGroupToNewWindowAction extends BaseMoveCopyEditorGroupToN super( COPY_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, { - value: localize('copyEditorGroupToNewWindow', "Copy Editor Group into New Window"), + ...localize2('copyEditorGroupToNewWindow', "Copy Editor Group into New Window"), mnemonicTitle: localize({ key: 'miCopyEditorGroupToNewWindow', comment: ['&& denotes a mnemonic'] }, "&&Copy Editor Group into New Window"), - original: 'Copy Editor Group into New Window' }, false ); @@ -2621,9 +2623,8 @@ export class RestoreEditorsToMainWindowAction extends Action2 { super({ id: 'workbench.action.restoreEditorsToMainWindow', title: { - value: localize('restoreEditorsToMainWindow', "Restore Editors into Main Window"), + ...localize2('restoreEditorsToMainWindow', "Restore Editors into Main Window"), mnemonicTitle: localize({ key: 'miRestoreEditorsToMainWindow', comment: ['&& denotes a mnemonic'] }, "&&Restore Editors into Main Window"), - original: 'Restore Editors into Main Window' }, f1: true, precondition: IsAuxiliaryWindowFocusedContext, @@ -2644,9 +2645,8 @@ export class NewEmptyEditorWindowAction extends Action2 { super({ id: NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID, title: { - value: localize('newEmptyEditorWindow', "New Empty Editor Window"), + ...localize2('newEmptyEditorWindow', "New Empty Editor Window"), mnemonicTitle: localize({ key: 'miNewEmptyEditorWindow', comment: ['&& denotes a mnemonic'] }, "&&New Empty Editor Window"), - original: 'New Empty Editor Window' }, f1: true, category: Categories.View diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 4a924f46cc5..895db330ab8 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -563,10 +563,7 @@ function registerDiffEditorCommands(): void { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: TOGGLE_DIFF_SIDE_BY_SIDE, - title: { - value: localize('toggleInlineView', "Toggle Inline View"), - original: 'Compare: Toggle Inline View' - }, + title: localize2('toggleInlineView', "Toggle Inline View"), category: localize('compare', "Compare") }, when: TextCompareEditorActiveContext @@ -575,10 +572,7 @@ function registerDiffEditorCommands(): void { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: DIFF_SWAP_SIDES, - title: { - value: localize('swapDiffSides', "Swap Left and Right Editor Side"), - original: 'Compare: Swap Left and Right Editor Side' - }, + title: localize2('swapDiffSides', "Swap Left and Right Editor Side"), category: localize('compare', "Compare") }, when: TextCompareEditorActiveContext diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index f3b12394a95..5ce8e43ffb2 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -30,12 +30,12 @@ const panelOffIcon = registerIcon('panel-layout-icon-off', Codicon.layoutPanelOf export class TogglePanelAction extends Action2 { static readonly ID = 'workbench.action.togglePanel'; - static readonly LABEL = localize('togglePanelVisibility', "Toggle Panel Visibility"); + static readonly LABEL = localize2('togglePanelVisibility', "Toggle Panel Visibility"); constructor() { super({ id: TogglePanelAction.ID, - title: { value: TogglePanelAction.LABEL, original: 'Toggle Panel Visibility' }, + title: TogglePanelAction.LABEL, toggled: { condition: PanelVisibleContext, title: localize('toggle panel', "Panel"), diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index c343d09d89e..dd8f210d6e3 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -250,7 +250,7 @@ export function getOpenChatEditorAction(id: string, label: string, when?: string constructor() { super({ id: `workbench.action.openChat.${id}`, - title: { value: localize('interactiveSession.open', "Open Editor ({0})", label), original: `Open Editor (${label})` }, + title: localize2('interactiveSession.open', "Open Editor ({0})", label), f1: true, category: CHAT_CATEGORY, precondition: ContextKeyExpr.deserialize(when) diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts index 6d5868c8d33..2ac217dfdb1 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IKeyMods, IQuickPickSeparator, IQuickInputService, IQuickPick, ItemActivation } from 'vs/platform/quickinput/common/quickInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IRange } from 'vs/editor/common/core/range'; @@ -244,9 +244,8 @@ class GotoSymbolAction extends Action2 { super({ id: GotoSymbolAction.ID, title: { - value: localize('gotoSymbol', "Go to Symbol in Editor..."), + ...localize2('gotoSymbol', "Go to Symbol in Editor..."), mnemonicTitle: localize({ key: 'miGotoSymbolInEditor', comment: ['&& denotes a mnemonic'] }, "Go to &&Symbol in Editor..."), - original: 'Go to Symbol in Editor...' }, f1: true, keybinding: { diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts index e0ad88ffb76..8ffe14d4514 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -23,9 +23,8 @@ export class ToggleColumnSelectionAction extends Action2 { super({ id: ToggleColumnSelectionAction.ID, title: { - value: localize('toggleColumnSelection', "Toggle Column Selection Mode"), + ...localize2('toggleColumnSelection', "Toggle Column Selection Mode"), mnemonicTitle: localize({ key: 'miColumnSelection', comment: ['&& denotes a mnemonic'] }, "Column &&Selection Mode"), - original: 'Toggle Column Selection Mode' }, f1: true, toggled: ContextKeyExpr.equals('config.editor.columnSelection', true), diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts index 546e704135c..825944692db 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -18,9 +18,8 @@ export class ToggleMinimapAction extends Action2 { super({ id: ToggleMinimapAction.ID, title: { - value: localize('toggleMinimap', "Toggle Minimap"), - original: 'Toggle Minimap', - mnemonicTitle: localize({ key: 'miMinimap', comment: ['&& denotes a mnemonic'] }, "&&Minimap") + ...localize2('toggleMinimap', "Toggle Minimap"), + mnemonicTitle: localize({ key: 'miMinimap', comment: ['&& denotes a mnemonic'] }, "&&Minimap"), }, category: Categories.View, f1: true, diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts index d4be0883444..0a0795be214 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -18,9 +18,8 @@ export class ToggleRenderControlCharacterAction extends Action2 { super({ id: ToggleRenderControlCharacterAction.ID, title: { - value: localize('toggleRenderControlCharacters', "Toggle Control Characters"), + ...localize2('toggleRenderControlCharacters', "Toggle Control Characters"), mnemonicTitle: localize({ key: 'miToggleRenderControlCharacters', comment: ['&& denotes a mnemonic'] }, "Render &&Control Characters"), - original: 'Toggle Control Characters' }, category: Categories.View, f1: true, diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts index e5255c219b5..3a3c87b953d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -18,9 +18,8 @@ class ToggleRenderWhitespaceAction extends Action2 { super({ id: ToggleRenderWhitespaceAction.ID, title: { - value: localize('toggleRenderWhitespace', "Toggle Render Whitespace"), + ...localize2('toggleRenderWhitespace', "Toggle Render Whitespace"), mnemonicTitle: localize({ key: 'miToggleRenderWhitespace', comment: ['&& denotes a mnemonic'] }, "&&Render Whitespace"), - original: 'Toggle Render Whitespace' }, category: Categories.View, f1: true, diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 11e9a439150..5222b57d2e7 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -1343,9 +1343,8 @@ registerAction2(class extends Action2 { super({ id: 'workbench.debug.viewlet.action.addFunctionBreakpointAction', title: { - value: localize('addFunctionBreakpoint', "Add Function Breakpoint"), - original: 'Add Function Breakpoint', - mnemonicTitle: localize({ key: 'miFunctionBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Function Breakpoint...") + ...localize2('addFunctionBreakpoint', "Add Function Breakpoint"), + mnemonicTitle: localize({ key: 'miFunctionBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Function Breakpoint..."), }, f1: true, icon: icons.watchExpressionsAddFuncBreakpoint, @@ -1430,9 +1429,8 @@ registerAction2(class extends Action2 { super({ id: 'workbench.debug.viewlet.action.removeAllBreakpoints', title: { - original: 'Remove All Breakpoints', - value: localize('removeAllBreakpoints', "Remove All Breakpoints"), - mnemonicTitle: localize({ key: 'miRemoveAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Remove &&All Breakpoints") + ...localize2('removeAllBreakpoints', "Remove All Breakpoints"), + mnemonicTitle: localize({ key: 'miRemoveAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Remove &&All Breakpoints"), }, f1: true, icon: icons.breakpointsRemoveAll, @@ -1469,8 +1467,7 @@ registerAction2(class extends Action2 { super({ id: 'workbench.debug.viewlet.action.enableAllBreakpoints', title: { - original: 'Enable All Breakpoints', - value: localize('enableAllBreakpoints', "Enable All Breakpoints"), + ...localize2('enableAllBreakpoints', "Enable All Breakpoints"), mnemonicTitle: localize({ key: 'miEnableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "&&Enable All Breakpoints"), }, f1: true, @@ -1500,9 +1497,8 @@ registerAction2(class extends Action2 { super({ id: 'workbench.debug.viewlet.action.disableAllBreakpoints', title: { - original: 'Disable All Breakpoints', - value: localize('disableAllBreakpoints', "Disable All Breakpoints"), - mnemonicTitle: localize({ key: 'miDisableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Disable A&&ll Breakpoints") + ...localize2('disableAllBreakpoints', "Disable All Breakpoints"), + mnemonicTitle: localize({ key: 'miDisableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Disable A&&ll Breakpoints"), }, f1: true, precondition: CONTEXT_DEBUGGERS_AVAILABLE, diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index d676d243997..3bb555a1a53 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -74,7 +74,7 @@ export const CALLSTACK_BOTTOM_ID = 'workbench.action.debug.callStackBottom'; export const CALLSTACK_UP_ID = 'workbench.action.debug.callStackUp'; export const CALLSTACK_DOWN_ID = 'workbench.action.debug.callStackDown'; -export const DEBUG_COMMAND_CATEGORY: ILocalizedString = { original: 'Debug', value: nls.localize('debug', 'Debug') }; +export const DEBUG_COMMAND_CATEGORY: ILocalizedString = nls.localize2('debug', 'Debug'); export const RESTART_LABEL = nls.localize2('restartDebug', "Restart"); export const STEP_OVER_LABEL = nls.localize2('stepOverDebug', "Step Over"); export const STEP_INTO_LABEL = nls.localize2('stepIntoDebug', "Step Into"); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 170f997e285..50a0b86f6b0 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -36,8 +36,7 @@ class ToggleBreakpointAction extends Action2 { super({ id: 'editor.debug.action.toggleBreakpoint', title: { - value: nls.localize('toggleBreakpointAction', "Debug: Toggle Breakpoint"), - original: 'Debug: Toggle Breakpoint', + ...nls.localize2('toggleBreakpointAction', "Debug: Toggle Breakpoint"), mnemonicTitle: nls.localize({ key: 'miToggleBreakpoint', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breakpoint"), }, precondition: CONTEXT_DEBUGGERS_AVAILABLE, @@ -234,9 +233,8 @@ class OpenDisassemblyViewAction extends Action2 { super({ id: OpenDisassemblyViewAction.ID, title: { - value: nls.localize('openDisassemblyView', "Open Disassembly View"), - original: 'Open Disassembly View', - mnemonicTitle: nls.localize({ key: 'miDisassemblyView', comment: ['&& denotes a mnemonic'] }, "&&DisassemblyView") + ...nls.localize2('openDisassemblyView', "Open Disassembly View"), + mnemonicTitle: nls.localize({ key: 'miDisassemblyView', comment: ['&& denotes a mnemonic'] }, "&&DisassemblyView"), }, precondition: CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE, menu: [ @@ -275,9 +273,8 @@ class ToggleDisassemblyViewSourceCodeAction extends Action2 { super({ id: ToggleDisassemblyViewSourceCodeAction.ID, title: { - value: nls.localize('toggleDisassemblyViewSourceCode', "Toggle Source Code in Disassembly View"), - original: 'Toggle Source Code in Disassembly View', - mnemonicTitle: nls.localize({ key: 'mitogglesource', comment: ['&& denotes a mnemonic'] }, "&&ToggleSource") + ...nls.localize2('toggleDisassemblyViewSourceCode', "Toggle Source Code in Disassembly View"), + mnemonicTitle: nls.localize({ key: 'mitogglesource', comment: ['&& denotes a mnemonic'] }, "&&ToggleSource"), }, f1: true, }); diff --git a/src/vs/workbench/contrib/editSessions/common/editSessions.ts b/src/vs/workbench/contrib/editSessions/common/editSessions.ts index 6484748e057..950973a04d1 100644 --- a/src/vs/workbench/contrib/editSessions/common/editSessions.ts +++ b/src/vs/workbench/contrib/editSessions/common/editSessions.ts @@ -16,10 +16,7 @@ import { Event } from 'vs/base/common/event'; import { StringSHA1 } from 'vs/base/common/hash'; import { EditSessionsStoreClient } from 'vs/workbench/contrib/editSessions/common/editSessionsStorageClient'; -export const EDIT_SESSION_SYNC_CATEGORY: ILocalizedString = { - original: 'Cloud Changes', - value: localize('cloud changes', 'Cloud Changes') -}; +export const EDIT_SESSION_SYNC_CATEGORY = localize2('cloud changes', 'Cloud Changes'); export type SyncResource = 'editSessions' | 'workspaceState'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index ba14cde4134..aeb392dccf7 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -1337,7 +1337,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: ToggleAutoUpdateForExtensionAction.ID, - title: { value: ToggleAutoUpdateForExtensionAction.LABEL, original: 'Auto Update' }, + title: ToggleAutoUpdateForExtensionAction.LABEL, category: ExtensionsLocalizedLabel, menu: { id: MenuId.ExtensionContext, @@ -1502,7 +1502,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.toggleApplyToAllProfiles', - title: { value: localize('workbench.extensions.action.toggleApplyToAllProfiles', "Apply Extension to all Profiles"), original: `Apply Extension to all Profiles` }, + title: localize2('workbench.extensions.action.toggleApplyToAllProfiles', "Apply Extension to all Profiles"), toggled: ContextKeyExpr.has('isApplicationScopedExtension'), menu: { id: MenuId.ExtensionContext, @@ -1520,7 +1520,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: TOGGLE_IGNORE_EXTENSION_ACTION_ID, - title: { value: localize('workbench.extensions.action.toggleIgnoreExtension', "Sync This Extension"), original: `Sync This Extension` }, + title: localize2('workbench.extensions.action.toggleIgnoreExtension', "Sync This Extension"), menu: { id: MenuId.ExtensionContext, group: '2_configure', @@ -1537,7 +1537,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.ignoreRecommendation', - title: { value: localize('workbench.extensions.action.ignoreRecommendation', "Ignore Recommendation"), original: `Ignore Recommendation` }, + title: localize2('workbench.extensions.action.ignoreRecommendation', "Ignore Recommendation"), menu: { id: MenuId.ExtensionContext, group: '3_recommendations', @@ -1549,7 +1549,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.undoIgnoredRecommendation', - title: { value: localize('workbench.extensions.action.undoIgnoredRecommendation', "Undo Ignored Recommendation"), original: `Undo Ignored Recommendation` }, + title: localize2('workbench.extensions.action.undoIgnoredRecommendation', "Undo Ignored Recommendation"), menu: { id: MenuId.ExtensionContext, group: '3_recommendations', @@ -1561,7 +1561,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.addExtensionToWorkspaceRecommendations', - title: { value: localize('workbench.extensions.action.addExtensionToWorkspaceRecommendations', "Add to Workspace Recommendations"), original: `Add to Workspace Recommendations` }, + title: localize2('workbench.extensions.action.addExtensionToWorkspaceRecommendations', "Add to Workspace Recommendations"), menu: { id: MenuId.ExtensionContext, group: '3_recommendations', @@ -1573,7 +1573,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.removeExtensionFromWorkspaceRecommendations', - title: { value: localize('workbench.extensions.action.removeExtensionFromWorkspaceRecommendations', "Remove from Workspace Recommendations"), original: `Remove from Workspace Recommendations` }, + title: localize2('workbench.extensions.action.removeExtensionFromWorkspaceRecommendations', "Remove from Workspace Recommendations"), menu: { id: MenuId.ExtensionContext, group: '3_recommendations', @@ -1585,7 +1585,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.addToWorkspaceRecommendations', - title: { value: localize('workbench.extensions.action.addToWorkspaceRecommendations', "Add Extension to Workspace Recommendations"), original: `Add Extension to Workspace Recommendations` }, + title: localize2('workbench.extensions.action.addToWorkspaceRecommendations', "Add Extension to Workspace Recommendations"), category: localize('extensions', "Extensions"), menu: { id: MenuId.CommandPalette, @@ -1608,7 +1608,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.addToWorkspaceFolderRecommendations', - title: { value: localize('workbench.extensions.action.addToWorkspaceFolderRecommendations', "Add Extension to Workspace Folder Recommendations"), original: `Add Extension to Workspace Folder Recommendations` }, + title: localize2('workbench.extensions.action.addToWorkspaceFolderRecommendations', "Add Extension to Workspace Folder Recommendations"), category: localize('extensions', "Extensions"), menu: { id: MenuId.CommandPalette, @@ -1619,7 +1619,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.addToWorkspaceIgnoredRecommendations', - title: { value: localize('workbench.extensions.action.addToWorkspaceIgnoredRecommendations', "Add Extension to Workspace Ignored Recommendations"), original: `Add Extension to Workspace Ignored Recommendations` }, + title: localize2('workbench.extensions.action.addToWorkspaceIgnoredRecommendations', "Add Extension to Workspace Ignored Recommendations"), category: localize('extensions', "Extensions"), menu: { id: MenuId.CommandPalette, @@ -1642,7 +1642,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.addToWorkspaceFolderIgnoredRecommendations', - title: { value: localize('workbench.extensions.action.addToWorkspaceFolderIgnoredRecommendations', "Add Extension to Workspace Folder Ignored Recommendations"), original: `Add Extension to Workspace Folder Ignored Recommendations` }, + title: localize2('workbench.extensions.action.addToWorkspaceFolderIgnoredRecommendations', "Add Extension to Workspace Folder Ignored Recommendations"), category: localize('extensions', "Extensions"), menu: { id: MenuId.CommandPalette, diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index d6d13ae2850..af3bd7fe006 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -850,7 +850,7 @@ export class UpdateAction extends AbstractUpdateAction { export class ToggleAutoUpdateForExtensionAction extends ExtensionAction { static readonly ID = 'workbench.extensions.action.toggleAutoUpdateForExtension'; - static readonly LABEL = localize('enableAutoUpdateLabel', "Auto Update"); + static readonly LABEL = localize2('enableAutoUpdateLabel', "Auto Update"); private static readonly EnabledClass = `${ExtensionAction.EXTENSION_ACTION_CLASS} auto-update`; private static readonly DisabledClass = `${ToggleAutoUpdateForExtensionAction.EnabledClass} hide`; @@ -862,7 +862,7 @@ export class ToggleAutoUpdateForExtensionAction extends ExtensionAction { @IConfigurationService configurationService: IConfigurationService, ) { - super(ToggleAutoUpdateForExtensionAction.ID, ToggleAutoUpdateForExtensionAction.LABEL, ToggleAutoUpdateForExtensionAction.DisabledClass); + super(ToggleAutoUpdateForExtensionAction.ID, ToggleAutoUpdateForExtensionAction.LABEL.value, ToggleAutoUpdateForExtensionAction.DisabledClass); this._register(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(AutoUpdateConfigurationKey)) { this.update(); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 55b42b4583c..f62d7182243 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -84,7 +84,7 @@ const SearchDeprecatedExtensionsContext = new RawContextKey('searchDepr export const RecommendedExtensionsContext = new RawContextKey('recommendedExtensions', false); const SortByUpdateDateContext = new RawContextKey('sortByUpdateDate', false); -const REMOTE_CATEGORY: ILocalizedString = { value: localize({ key: 'remote', comment: ['Remote as in remote machine'] }, "Remote"), original: 'Remote' }; +const REMOTE_CATEGORY: ILocalizedString = localize2({ key: 'remote', comment: ['Remote as in remote machine'] }, "Remote"); export class ExtensionsViewletViewsContribution implements IWorkbenchContribution { diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index a6b15081da2..7115d8fc698 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -824,8 +824,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { command: { id: toggleEditorGroupLayoutId, title: { - original: 'Flip Layout', - value: nls.localize('miToggleEditorLayoutWithoutMnemonic', "Flip Layout"), + ...nls.localize2('miToggleEditorLayoutWithoutMnemonic', "Flip Layout"), mnemonicTitle: nls.localize({ key: 'miToggleEditorLayout', comment: ['&& denotes a mnemonic'] }, "Flip &&Layout") } }, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts index e13d8fc2a84..690a05c19fb 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { localize, localize2 } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -122,10 +122,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.toggleLayoutTroubleshoot', - title: { - value: localize('workbench.notebook.toggleLayoutTroubleshoot', "Toggle Layout Troubleshoot"), - original: 'Toggle Notebook Layout Troubleshoot' - }, + title: localize2('workbench.notebook.toggleLayoutTroubleshoot', "Toggle Layout Troubleshoot"), category: Categories.Developer, f1: true }); @@ -173,10 +170,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.clearNotebookEdtitorTypeCache', - title: { - value: localize('workbench.notebook.clearNotebookEdtitorTypeCache', "Clear Notebook Editor Type Cache"), - original: 'Clear Notebook Editor Cache' - }, + title: localize2('workbench.notebook.clearNotebookEdtitorTypeCache', "Clear Notebook Editor Type Cache"), category: Categories.Developer, f1: true }); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/foldingController.ts b/src/vs/workbench/contrib/notebook/browser/controller/foldingController.ts index 2d7c6dee573..29e49248846 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/foldingController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/foldingController.ts @@ -151,7 +151,7 @@ registerNotebookContribution(FoldingController.id, FoldingController); const NOTEBOOK_FOLD_COMMAND_LABEL = localize('fold.cell', "Fold Cell"); -const NOTEBOOK_UNFOLD_COMMAND_LABEL = localize('unfold.cell', "Unfold Cell"); +const NOTEBOOK_UNFOLD_COMMAND_LABEL = localize2('unfold.cell', "Unfold Cell"); const FOLDING_COMMAND_ARGS: Pick = { args: [{ @@ -253,7 +253,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.unfold', - title: { value: NOTEBOOK_UNFOLD_COMMAND_LABEL, original: 'Unfold Cell' }, + title: NOTEBOOK_UNFOLD_COMMAND_LABEL, category: NOTEBOOK_ACTIONS_CATEGORY, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts index a353217d214..575a5ddedeb 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts @@ -213,10 +213,7 @@ registerAction2(class extends NotebookCellAction { super( { id: 'notebook.cell.chat.stop', - title: { - value: localize('notebook.cell.chat.stop', "Stop Request"), - original: 'Make Request' - }, + title: localize2('notebook.cell.chat.stop', "Stop Request"), icon: Codicon.debugStop, menu: { id: MENU_CELL_CHAT_INPUT, diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts index c614ea2ef46..a02e1bb2010 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { EventType as TouchEventType } from 'vs/base/browser/touch'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -33,9 +33,8 @@ export class ToggleNotebookStickyScroll extends Action2 { super({ id: 'notebook.action.toggleNotebookStickyScroll', title: { - value: localize('toggleStickyScroll', "Toggle Notebook Sticky Scroll"), + ...localize2('toggleStickyScroll', "Toggle Notebook Sticky Scroll"), mnemonicTitle: localize({ key: 'mitoggleStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Toggle Notebook Sticky Scroll"), - original: 'Toggle Notebook Sticky Scroll', }, category: Categories.View, toggled: { diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index e42c77e4ef9..65d8161b020 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -194,9 +194,8 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon super({ id: SETTINGS_COMMAND_OPEN_SETTINGS, title: { - value: nls.localize('settings', "Settings"), + ...nls.localize2('settings', "Settings"), mnemonicTitle: nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&Settings"), - original: 'Settings' }, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -411,7 +410,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: '_workbench.action.openFolderSettings', - title: nls.localize2('openFolderSettings', "Open Folder Settings"), + title: nls.localize('openFolderSettings', "Open Folder Settings"), category, menu: { id: MenuId.ExplorerContext, @@ -482,12 +481,11 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon .then(() => { const remoteAuthority = this.environmentService.remoteAuthority; const hostLabel = this.labelService.getHostLabel(Schemas.vscodeRemote, remoteAuthority) || remoteAuthority; - const label = nls.localize('openRemoteSettings', "Open Remote Settings ({0})", hostLabel); registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.openRemoteSettings', - title: { value: label, original: `Open Remote Settings (${hostLabel})` }, + title: nls.localize2('openRemoteSettings', "Open Remote Settings ({0})", hostLabel), category, menu: { id: MenuId.CommandPalette, @@ -500,12 +498,11 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon return accessor.get(IPreferencesService).openRemoteSettings(args); } }); - const jsonLabel = nls.localize('openRemoteSettingsJSON', "Open Remote Settings (JSON) ({0})", hostLabel); registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.openRemoteSettingsFile', - title: { value: jsonLabel, original: `Open Remote Settings (JSON) (${hostLabel})` }, + title: nls.localize2('openRemoteSettingsJSON', "Open Remote Settings (JSON) ({0})", hostLabel), category, menu: { id: MenuId.CommandPalette, @@ -832,7 +829,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, - title: { value: nls.localize('showDefaultKeybindings', "Show System Keybindings"), original: 'Show System Keyboard Shortcuts' }, + title: nls.localize2('showDefaultKeybindings', "Show System Keybindings"), menu: [ { id: MenuId.EditorTitle, @@ -853,7 +850,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: KEYBINDINGS_EDITOR_SHOW_EXTENSION_KEYBINDINGS, - title: { value: nls.localize('showExtensionKeybindings', "Show Extension Keybindings"), original: 'Show Extension Keyboard Shortcuts' }, + title: nls.localize2('showExtensionKeybindings', "Show Extension Keybindings"), menu: [ { id: MenuId.EditorTitle, @@ -874,7 +871,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, - title: { value: nls.localize('showUserKeybindings', "Show User Keybindings"), original: 'Show User Keyboard Shortcuts' }, + title: nls.localize2('showUserKeybindings', "Show User Keybindings"), menu: [ { id: MenuId.EditorTitle, diff --git a/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts b/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts index a1123af8777..305f9bae1c5 100644 --- a/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts +++ b/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts @@ -10,8 +10,7 @@ import { ITunnelApplicationConfig } from 'vs/base/common/product'; import { joinPath } from 'vs/base/common/resources'; import { isNumber, isObject, isString } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { localize } from 'vs/nls'; -import { ILocalizedString } from 'vs/platform/action/common/action'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -37,10 +36,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { IOutputService } from 'vs/workbench/services/output/common/output'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -export const REMOTE_TUNNEL_CATEGORY: ILocalizedString = { - original: 'Remote-Tunnels', - value: localize('remoteTunnel.category', 'Remote Tunnels') -}; +export const REMOTE_TUNNEL_CATEGORY = localize2('remoteTunnel.category', 'Remote Tunnels'); type CONTEXT_KEY_STATES = 'connected' | 'connecting' | 'disconnected'; diff --git a/src/vs/workbench/contrib/search/browser/searchActionsFind.ts b/src/vs/workbench/contrib/search/browser/searchActionsFind.ts index 85307857461..c2e313827bf 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsFind.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsFind.ts @@ -160,9 +160,8 @@ registerAction2(class FindInFilesAction extends Action2 { super({ id: Constants.SearchCommandIds.FindInFilesActionId, title: { - value: nls.localize('findInFiles', "Find in Files"), + ...nls.localize2('findInFiles', "Find in Files"), mnemonicTitle: nls.localize({ key: 'miFindInFiles', comment: ['&& denotes a mnemonic'] }, "Find &&in Files"), - original: 'Find in Files' }, metadata: { description: nls.localize('findInFiles.description', "Open a workspace search"), diff --git a/src/vs/workbench/contrib/search/browser/searchActionsSymbol.ts b/src/vs/workbench/contrib/search/browser/searchActionsSymbol.ts index d9c74cc18e7..68c527ade4a 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsSymbol.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsSymbol.ts @@ -23,9 +23,8 @@ registerAction2(class ShowAllSymbolsAction extends Action2 { super({ id: Constants.SearchCommandIds.ShowAllSymbolsActionId, title: { - value: nls.localize('showTriggerActions', "Go to Symbol in Workspace..."), - original: 'Go to Symbol in Workspace...', - mnemonicTitle: nls.localize({ key: 'miGotoSymbolInWorkspace', comment: ['&& denotes a mnemonic'] }, "Go to Symbol in &&Workspace...") + ...nls.localize2('showTriggerActions', "Go to Symbol in Workspace..."), + mnemonicTitle: nls.localize({ key: 'miGotoSymbolInWorkspace', comment: ['&& denotes a mnemonic'] }, "Go to Symbol in &&Workspace..."), }, f1: true, keybinding: { diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index fdb1811ac3f..6fcc094c3d2 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -458,7 +458,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: ToggleSearchEditorRegexCommandId, - title: { value: localize('searchEditor.action.toggleSearchEditorRegex', "Toggle Use Regular Expression"), original: 'Toggle Use Regular Expression"' }, + title: localize2('searchEditor.action.toggleSearchEditorRegex', "Toggle Use Regular Expression"), category, f1: true, precondition: SearchEditorConstants.InSearchEditor, @@ -477,7 +477,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: SearchEditorConstants.ToggleSearchEditorContextLinesCommandId, - title: { value: localize('searchEditor.action.toggleSearchEditorContextLines', "Toggle Context Lines"), original: 'Toggle Context Lines"' }, + title: localize2('searchEditor.action.toggleSearchEditorContextLines', "Toggle Context Lines"), category, f1: true, precondition: SearchEditorConstants.InSearchEditor, diff --git a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts index f80793442e3..77dad6efa4a 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts @@ -227,9 +227,8 @@ export class ConfigureSnippetsAction extends SnippetsAction { id: 'workbench.action.openSnippets', title: nls.localize2('openSnippet.label', "Configure User Snippets"), shortTitle: { - value: nls.localize('userSnippets', "User Snippets"), + ...nls.localize2('userSnippets', "User Snippets"), mnemonicTitle: nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets"), - original: 'User Snippets' }, f1: true, menu: [ diff --git a/src/vs/workbench/contrib/terminal/common/terminalStrings.ts b/src/vs/workbench/contrib/terminal/common/terminalStrings.ts index 2bf1534e685..a8602f7d2f6 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalStrings.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalStrings.ts @@ -20,8 +20,7 @@ export const terminalStrings = { focus: localize2('workbench.action.terminal.focus', "Focus Terminal"), focusAndHideAccessibleBuffer: localize2('workbench.action.terminal.focusAndHideAccessibleBuffer', "Focus Terminal and Hide Accessible Buffer"), kill: { - value: localize('killTerminal', "Kill Terminal"), - original: 'Kill Terminal', + ...localize2('killTerminal', "Kill Terminal"), short: localize('killTerminal.short', "Kill"), }, moveToEditor: localize2('moveToEditor', "Move Terminal into Editor Area"), @@ -30,8 +29,7 @@ export const terminalStrings = { changeIcon: localize2('workbench.action.terminal.changeIcon', "Change Icon..."), changeColor: localize2('workbench.action.terminal.changeColor', "Change Color..."), split: { - value: localize('splitTerminal', "Split Terminal"), - original: 'Split Terminal', + ...localize2('splitTerminal', "Split Terminal"), short: localize('splitTerminal.short', "Split"), }, unsplit: localize2('unsplitTerminal', "Unsplit Terminal"), diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index 85f92722620..f6f451bcd0d 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -77,7 +77,7 @@ const hasAnyTestProvider = ContextKeyGreaterExpr.create(TestingContextKeys.provi const LABEL_RUN_TESTS = localize2('runSelectedTests', "Run Tests"); const LABEL_DEBUG_TESTS = localize2('debugSelectedTests', "Debug Tests"); -const LABEL_COVERAGE_TESTS = { value: localize('coverageSelectedTests', 'Run Tests with Coverage'), original: 'Run Tests withCoverage' }; +const LABEL_COVERAGE_TESTS = localize2('coverageSelectedTests', "Run Tests with Coverage"); export class HideTestAction extends Action2 { constructor() { @@ -480,7 +480,7 @@ class StartContinuousRunAction extends Action2 { constructor() { super({ id: TestCommandId.StartContinousRun, - title: { value: localize('testing.startContinuous', "Start Continuous Run"), original: 'Enable Continuous Run' }, + title: localize2('testing.startContinuous', "Start Continuous Run"), category, icon: icons.testingTurnContinuousRunOn, menu: continuousMenus(false), diff --git a/src/vs/workbench/contrib/update/browser/update.contribution.ts b/src/vs/workbench/contrib/update/browser/update.contribution.ts index 0885b877466..46b1d76365a 100644 --- a/src/vs/workbench/contrib/update/browser/update.contribution.ts +++ b/src/vs/workbench/contrib/update/browser/update.contribution.ts @@ -38,9 +38,8 @@ export class ShowCurrentReleaseNotesAction extends Action2 { super({ id: ShowCurrentReleaseNotesActionId, title: { - value: localize('showReleaseNotes', "Show Release Notes"), + ...localize2('showReleaseNotes', "Show Release Notes"), mnemonicTitle: localize({ key: 'mshowReleaseNotes', comment: ['&& denotes a mnemonic'] }, "Show &&Release Notes"), - original: 'Show Release Notes' }, category: { value: product.nameShort, original: product.nameShort }, f1: true, @@ -148,10 +147,7 @@ class DownloadAction extends Action2 { constructor() { super({ id: DownloadAction.ID, - title: { - value: localize('openDownloadPage', "Download {0}", product.nameLong), - original: `Download ${product.downloadUrl}` - }, + title: localize2('openDownloadPage', "Download {0}", product.nameLong), precondition: ContextKeyExpr.and(IsWebContext, DOWNLOAD_URL), // Only show when running in a web browser and a download url is available f1: true, menu: [{ diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index b849286fa54..e86a7c07e96 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -191,10 +191,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements const when = ContextKeyExpr.and(ContextKeyExpr.notEquals(CURRENT_PROFILE_CONTEXT.key, that.userDataProfilesService.defaultProfile.id), IS_CURRENT_PROFILE_TRANSIENT_CONTEXT.toNegated()); super({ id: `workbench.profiles.actions.editCurrentProfile`, - title: { - value: localize('edit profile', "Edit Profile..."), - original: `Edit Profile...` - }, + title: localize2('edit profile', "Edit Profile..."), precondition: when, f1: true, menu: [ @@ -219,10 +216,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements constructor() { super({ id, - title: { - value: localize('show profile contents', "Show Profile Contents"), - original: `Show Profile Contents` - }, + title: localize2('show profile contents', "Show Profile Contents"), category: PROFILES_CATEGORY, menu: [ { @@ -251,10 +245,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements constructor() { super({ id, - title: { - value: localize('export profile', "Export Profile..."), - original: `Export Profile (${that.userDataProfileService.currentProfile.name})...` - }, + title: localize2('export profile', "Export Profile..."), category: PROFILES_CATEGORY, precondition: IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT.toNegated(), menu: [ @@ -277,10 +268,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements disposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarShare, { command: { id, - title: { - value: localize('export profile in share', "Export Profile ({0})...", that.userDataProfileService.currentProfile.name), - original: `Export Profile (${that.userDataProfileService.currentProfile.name})...` - }, + title: localize2('export profile in share', "Export Profile ({0})...", that.userDataProfileService.currentProfile.name), precondition: PROFILES_ENABLEMENT_CONTEXT, }, })); @@ -383,10 +371,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements disposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarShare, { command: { id, - title: { - value: localize('import profile share', "Import Profile...",), - original: 'Import Profile...' - }, + title: localize2('import profile share', "Import Profile..."), precondition: PROFILES_ENABLEMENT_CONTEXT, }, })); diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index 2d49534034c..6fe958a95b0 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -37,9 +37,8 @@ export class CloseWindowAction extends Action2 { super({ id: CloseWindowAction.ID, title: { - value: localize('closeWindow', "Close Window"), + ...localize2('closeWindow', "Close Window"), mnemonicTitle: localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Clos&&e Window"), - original: 'Close Window' }, f1: true, keybinding: { @@ -123,9 +122,8 @@ export class ZoomInAction extends BaseZoomAction { super({ id: 'workbench.action.zoomIn', title: { - value: localize('zoomIn', "Zoom In"), + ...localize2('zoomIn', "Zoom In"), mnemonicTitle: localize({ key: 'miZoomIn', comment: ['&& denotes a mnemonic'] }, "&&Zoom In"), - original: 'Zoom In' }, category: Categories.View, f1: true, @@ -153,9 +151,8 @@ export class ZoomOutAction extends BaseZoomAction { super({ id: 'workbench.action.zoomOut', title: { - value: localize('zoomOut', "Zoom Out"), + ...localize2('zoomOut', "Zoom Out"), mnemonicTitle: localize({ key: 'miZoomOut', comment: ['&& denotes a mnemonic'] }, "&&Zoom Out"), - original: 'Zoom Out' }, category: Categories.View, f1: true, @@ -187,9 +184,8 @@ export class ZoomResetAction extends BaseZoomAction { super({ id: 'workbench.action.zoomReset', title: { - value: localize('zoomReset', "Reset Zoom"), + ...localize2('zoomReset', "Reset Zoom"), mnemonicTitle: localize({ key: 'miZoomReset', comment: ['&& denotes a mnemonic'] }, "&&Reset Zoom"), - original: 'Reset Zoom' }, category: Categories.View, f1: true, diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 16e775ba028..f144494cb3f 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -18,7 +18,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { getViewsStateStorageId, ViewContainerModel } from 'vs/workbench/services/views/common/viewContainerModel'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IStringDictionary } from 'vs/base/common/collections'; import { ILogger, ILoggerService } from 'vs/platform/log/common/log'; @@ -839,10 +839,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor constructor() { super({ id: `${viewContainer.id}.resetViewContainerLocation`, - title: { - original: 'Reset Location', - value: localize('resetViewLocation', "Reset Location") - }, + title: localize2('resetViewLocation', "Reset Location"), menu: [{ id: MenuId.ViewContainerTitleContext, when: ContextKeyExpr.or( diff --git a/src/vs/workbench/services/views/browser/viewsService.ts b/src/vs/workbench/services/views/browser/viewsService.ts index 26cad586603..7f036faf807 100644 --- a/src/vs/workbench/services/views/browser/viewsService.ts +++ b/src/vs/workbench/services/views/browser/viewsService.ts @@ -12,7 +12,7 @@ import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from ' import { Event, Emitter } from 'vs/base/common/event'; import { isString } from 'vs/base/common/types'; import { MenuId, registerAction2, Action2, MenuRegistry } from 'vs/platform/actions/common/actions'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; @@ -550,10 +550,7 @@ export class ViewsService extends Disposable implements IViewsService { constructor() { super({ id: `${viewDescriptor.id}.resetViewLocation`, - title: { - original: 'Reset Location', - value: localize('resetViewLocation', "Reset Location") - }, + title: localize2('resetViewLocation', "Reset Location"), menu: [{ id: MenuId.ViewTitleContext, when: ContextKeyExpr.or( From c1c6e6a77b435d69e9b959125bd0ab7aaa786699 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 31 Jan 2024 20:06:43 +0100 Subject: [PATCH 0800/1897] Update panel and auxiliary bar action titles --- src/vs/workbench/browser/parts/panel/panelActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 5ce8e43ffb2..23f5ce98a1b 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -318,7 +318,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.closePanel', - title: localize2('closePanel', 'Close Panel'), + title: localize2('closePanel', 'Hide Panel'), category: Categories.View, icon: closeIcon, menu: [{ @@ -340,7 +340,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.closeAuxiliaryBar', - title: localize2('closeSecondarySideBar', 'Close Secondary Side Bar'), + title: localize2('closeSecondarySideBar', 'Hide Secondary Side Bar'), category: Categories.View, icon: closeIcon, menu: [{ From 3ad2e082c5f5e7adedee5a4ce17653bf99b339f8 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 31 Jan 2024 11:22:50 -0800 Subject: [PATCH 0801/1897] eng: group cli build vs publish steps (#203920) Fixes #203871 --- .../alpine/cli-build-alpine.yml | 14 ++++++++-- ...ompile-and-publish.yml => cli-compile.yml} | 16 ----------- build/azure-pipelines/cli/cli-publish.yml | 28 +++++++++++++++++++ .../darwin/cli-build-darwin.yml | 14 ++++++++-- .../azure-pipelines/linux/cli-build-linux.yml | 21 ++++++++++++-- .../azure-pipelines/win32/cli-build-win32.yml | 14 ++++++++-- 6 files changed, 82 insertions(+), 25 deletions(-) rename build/azure-pipelines/cli/{cli-compile-and-publish.yml => cli-compile.yml} (90%) create mode 100644 build/azure-pipelines/cli/cli-publish.yml diff --git a/build/azure-pipelines/alpine/cli-build-alpine.yml b/build/azure-pipelines/alpine/cli-build-alpine.yml index c13d2a5c44b..f808fe67112 100644 --- a/build/azure-pipelines/alpine/cli-build-alpine.yml +++ b/build/azure-pipelines/alpine/cli-build-alpine.yml @@ -64,7 +64,7 @@ steps: - x86_64-unknown-linux-musl - ${{ if eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true) }}: - - template: ../cli/cli-compile-and-publish.yml + - template: ../cli/cli-compile.yml parameters: VSCODE_CLI_TARGET: aarch64-unknown-linux-musl VSCODE_CLI_ARTIFACT: vscode_cli_alpine_arm64_cli @@ -77,7 +77,7 @@ steps: OPENSSL_STATIC: "1" - ${{ if eq(parameters.VSCODE_BUILD_ALPINE, true) }}: - - template: ../cli/cli-compile-and-publish.yml + - template: ../cli/cli-compile.yml parameters: VSCODE_CLI_TARGET: x86_64-unknown-linux-musl VSCODE_CLI_ARTIFACT: vscode_cli_alpine_x64_cli @@ -88,3 +88,13 @@ steps: OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux-musl/lib OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux-musl/include OPENSSL_STATIC: "1" + + - ${{ if eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true) }}: + - template: ../cli/cli-publish.yml + parameters: + VSCODE_CLI_ARTIFACT: vscode_cli_alpine_arm64_cli + + - ${{ if eq(parameters.VSCODE_BUILD_ALPINE, true) }}: + - template: ../cli/cli-publish.yml + parameters: + VSCODE_CLI_ARTIFACT: vscode_cli_alpine_x64_cli diff --git a/build/azure-pipelines/cli/cli-compile-and-publish.yml b/build/azure-pipelines/cli/cli-compile.yml similarity index 90% rename from build/azure-pipelines/cli/cli-compile-and-publish.yml rename to build/azure-pipelines/cli/cli-compile.yml index a9aaac73254..eaa222dbd1d 100644 --- a/build/azure-pipelines/cli/cli-compile-and-publish.yml +++ b/build/azure-pipelines/cli/cli-compile.yml @@ -98,10 +98,6 @@ steps: archiveType: zip archiveFile: $(Build.ArtifactStagingDirectory)/${{ parameters.VSCODE_CLI_ARTIFACT }}.zip - - publish: $(Build.ArtifactStagingDirectory)/${{ parameters.VSCODE_CLI_ARTIFACT }}.zip - artifact: ${{ parameters.VSCODE_CLI_ARTIFACT }} - displayName: Publish ${{ parameters.VSCODE_CLI_ARTIFACT }} artifact - - ${{ else }}: - script: | set -e @@ -120,10 +116,6 @@ steps: archiveType: zip archiveFile: $(Build.ArtifactStagingDirectory)/${{ parameters.VSCODE_CLI_ARTIFACT }}.zip - - publish: $(Build.ArtifactStagingDirectory)/${{ parameters.VSCODE_CLI_ARTIFACT }}.zip - artifact: ${{ parameters.VSCODE_CLI_ARTIFACT }} - displayName: Publish ${{ parameters.VSCODE_CLI_ARTIFACT }} artifact - - ${{ else }}: - task: ArchiveFiles@2 displayName: Archive CLI @@ -134,10 +126,6 @@ steps: tarCompression: gz archiveFile: $(Build.ArtifactStagingDirectory)/${{ parameters.VSCODE_CLI_ARTIFACT }}.tar.gz - - publish: $(Build.ArtifactStagingDirectory)/${{ parameters.VSCODE_CLI_ARTIFACT }}.tar.gz - artifact: ${{ parameters.VSCODE_CLI_ARTIFACT }} - displayName: Publish ${{ parameters.VSCODE_CLI_ARTIFACT }} artifact - # Make a folder for the SBOM for the specific artifact - ${{ if contains(parameters.VSCODE_CLI_TARGET, '-windows-') }}: - powershell: mkdir $(Build.ArtifactStagingDirectory)/sbom_${{ parameters.VSCODE_CLI_ARTIFACT }} @@ -156,7 +144,3 @@ steps: BuildComponentPath: $(Build.SourcesDirectory)/cli BuildDropPath: $(Build.ArtifactStagingDirectory)/sbom_${{ parameters.VSCODE_CLI_ARTIFACT }} PackageName: Visual Studio Code CLI - - - publish: $(Build.ArtifactStagingDirectory)/sbom_${{ parameters.VSCODE_CLI_ARTIFACT }}/_manifest - displayName: Publish SBOM - artifact: sbom_${{ parameters.VSCODE_CLI_ARTIFACT }} diff --git a/build/azure-pipelines/cli/cli-publish.yml b/build/azure-pipelines/cli/cli-publish.yml new file mode 100644 index 00000000000..fa3eacd0f96 --- /dev/null +++ b/build/azure-pipelines/cli/cli-publish.yml @@ -0,0 +1,28 @@ +parameters: + - name: VSCODE_CLI_ARTIFACT + type: string + - name: VSCODE_CHECK_ONLY + type: boolean + default: false + +steps: + - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: + - ${{ if contains(parameters.VSCODE_CLI_ARTIFACT, 'win32') }}: + - publish: $(Build.ArtifactStagingDirectory)/${{ parameters.VSCODE_CLI_ARTIFACT }}.zip + artifact: ${{ parameters.VSCODE_CLI_ARTIFACT }} + displayName: Publish ${{ parameters.VSCODE_CLI_ARTIFACT }} artifact + + - ${{ else }}: + - ${{ if contains(parameters.VSCODE_CLI_ARTIFACT, 'darwin') }}: + - publish: $(Build.ArtifactStagingDirectory)/${{ parameters.VSCODE_CLI_ARTIFACT }}.zip + artifact: ${{ parameters.VSCODE_CLI_ARTIFACT }} + displayName: Publish ${{ parameters.VSCODE_CLI_ARTIFACT }} artifact + + - ${{ else }}: + - publish: $(Build.ArtifactStagingDirectory)/${{ parameters.VSCODE_CLI_ARTIFACT }}.tar.gz + artifact: ${{ parameters.VSCODE_CLI_ARTIFACT }} + displayName: Publish ${{ parameters.VSCODE_CLI_ARTIFACT }} artifact + + - publish: $(Build.ArtifactStagingDirectory)/sbom_${{ parameters.VSCODE_CLI_ARTIFACT }}/_manifest + displayName: Publish SBOM + artifact: sbom_${{ parameters.VSCODE_CLI_ARTIFACT }} diff --git a/build/azure-pipelines/darwin/cli-build-darwin.yml b/build/azure-pipelines/darwin/cli-build-darwin.yml index 5d2abaf037d..c7b0053eb50 100644 --- a/build/azure-pipelines/darwin/cli-build-darwin.yml +++ b/build/azure-pipelines/darwin/cli-build-darwin.yml @@ -45,7 +45,7 @@ steps: - aarch64-apple-darwin - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - template: ../cli/cli-compile-and-publish.yml + - template: ../cli/cli-compile.yml parameters: VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} VSCODE_CLI_TARGET: x86_64-apple-darwin @@ -56,7 +56,7 @@ steps: OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-osx/include - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: - - template: ../cli/cli-compile-and-publish.yml + - template: ../cli/cli-compile.yml parameters: VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} VSCODE_CLI_TARGET: aarch64-apple-darwin @@ -65,3 +65,13 @@ steps: VSCODE_CLI_ENV: OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-osx/lib OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-osx/include + + - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: + - template: ../cli/cli-publish.yml + parameters: + VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_darwin_x64_cli + + - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: + - template: ../cli/cli-publish.yml + parameters: + VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_darwin_arm64_cli diff --git a/build/azure-pipelines/linux/cli-build-linux.yml b/build/azure-pipelines/linux/cli-build-linux.yml index a820c588e61..3fa2da61154 100644 --- a/build/azure-pipelines/linux/cli-build-linux.yml +++ b/build/azure-pipelines/linux/cli-build-linux.yml @@ -90,7 +90,7 @@ steps: - armv7-unknown-linux-gnueabihf - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARM64, true) }}: - - template: ../cli/cli-compile-and-publish.yml + - template: ../cli/cli-compile.yml parameters: VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} VSCODE_CLI_TARGET: aarch64-unknown-linux-gnu @@ -102,7 +102,7 @@ steps: SYSROOT_ARCH: arm64 - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: - - template: ../cli/cli-compile-and-publish.yml + - template: ../cli/cli-compile.yml parameters: VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} VSCODE_CLI_TARGET: x86_64-unknown-linux-gnu @@ -114,7 +114,7 @@ steps: SYSROOT_ARCH: amd64 - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true) }}: - - template: ../cli/cli-compile-and-publish.yml + - template: ../cli/cli-compile.yml parameters: VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} VSCODE_CLI_TARGET: armv7-unknown-linux-gnueabihf @@ -124,3 +124,18 @@ steps: OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm-linux/lib OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm-linux/include SYSROOT_ARCH: armhf + + - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true) }}: + - template: ../cli/cli-publish.yml + parameters: + VSCODE_CLI_ARTIFACT: vscode_cli_linux_armhf_cli + + - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: + - template: ../cli/cli-publish.yml + parameters: + VSCODE_CLI_ARTIFACT: vscode_cli_linux_x64_cli + + - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARM64, true) }}: + - template: ../cli/cli-publish.yml + parameters: + VSCODE_CLI_ARTIFACT: vscode_cli_linux_arm64_cli diff --git a/build/azure-pipelines/win32/cli-build-win32.yml b/build/azure-pipelines/win32/cli-build-win32.yml index ab6c5f34eef..5e1baa80603 100644 --- a/build/azure-pipelines/win32/cli-build-win32.yml +++ b/build/azure-pipelines/win32/cli-build-win32.yml @@ -44,7 +44,7 @@ steps: - aarch64-pc-windows-msvc - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - - template: ../cli/cli-compile-and-publish.yml + - template: ../cli/cli-compile.yml parameters: VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} VSCODE_CLI_TARGET: x86_64-pc-windows-msvc @@ -56,7 +56,7 @@ steps: RUSTFLAGS: "-C target-feature=+crt-static" - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: - - template: ../cli/cli-compile-and-publish.yml + - template: ../cli/cli-compile.yml parameters: VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} VSCODE_CLI_TARGET: aarch64-pc-windows-msvc @@ -66,3 +66,13 @@ steps: OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-windows-static/lib OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-windows-static/include RUSTFLAGS: "-C target-feature=+crt-static" + + - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: + - template: ../cli/cli-publish.yml + parameters: + VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_win32_arm64_cli + + - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: + - template: ../cli/cli-publish.yml + parameters: + VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_win32_x64_cli From 790e20c0f088b4f05d69c7afb1ea97981beaa320 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 31 Jan 2024 16:41:08 -0300 Subject: [PATCH 0802/1897] Get rid of 'slash command' from API (#203924) Also don't send an empty string for 'subCommand' when there is no slash command selected --- .../api/common/extHostChatAgents2.ts | 42 +++++-------------- .../api/common/extHostTypeConverters.ts | 3 +- .../contrib/chat/common/chatServiceImpl.ts | 2 +- .../vscode.proposed.chatAgents2.d.ts | 21 ++++++---- 4 files changed, 27 insertions(+), 41 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 0a835a965e1..846e790ab47 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -9,7 +9,6 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; import { StopWatch } from 'vs/base/common/stopwatch'; -import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -72,16 +71,12 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { token.onCancellationRequested(() => commandExecution.complete()); this._extHostChatProvider.allowListExtensionWhile(agent.extension.identifier, commandExecution.p); - const slashCommand = request.command - ? await agent.validateSlashCommand(request.command) - : undefined; - const stopWatch = StopWatch.create(false); let firstProgress: number | undefined; try { const convertedHistory = await this.prepareHistory(agent, request, context); const task = agent.invoke( - typeConvert.ChatAgentRequest.to(request, slashCommand), + typeConvert.ChatAgentRequest.to(request), { history: convertedHistory }, new Progress(progress => { throwIfDone(); @@ -141,7 +136,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const result = request.agentId === h.request.agentId && this._resultsBySessionAndRequestId.get(request.sessionId)?.get(h.request.requestId) || h.result; return { - request: typeConvert.ChatAgentRequest.to(h.request, undefined), + request: typeConvert.ChatAgentRequest.to(h.request), response: coalesce(h.response.map(r => typeConvert.ChatResponseProgress.toProgressContent(r))), result } satisfies vscode.ChatAgentHistoryEntry; @@ -159,7 +154,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { // this is OK, the agent might have disposed while the request was in flight return []; } - return agent.provideSlashCommand(token); + return agent.provideSlashCommands(token); } $provideFollowups(handle: number, sessionId: string, token: CancellationToken): Promise { @@ -227,8 +222,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { class ExtHostChatAgent { - private _slashCommandProvider: vscode.ChatAgentSubCommandProvider | undefined; - private _lastSlashCommands: vscode.ChatAgentSubCommand[] | undefined; + private _subCommandProvider: vscode.ChatAgentSubCommandProvider | undefined; private _followupProvider: vscode.FollowupProvider | undefined; private _description: string | undefined; private _fullName: string | undefined; @@ -267,28 +261,14 @@ class ExtHostChatAgent { return await this._agentVariableProvider.provider.provideCompletionItems(query, token) ?? []; } - async validateSlashCommand(command: string) { - if (!this._lastSlashCommands) { - await this.provideSlashCommand(CancellationToken.None); - assertType(this._lastSlashCommands); - } - const result = this._lastSlashCommands.find(candidate => candidate.name === command); - if (!result) { - throw new Error(`Unknown slashCommand: ${command}`); - - } - return result; - } - - async provideSlashCommand(token: CancellationToken): Promise { - if (!this._slashCommandProvider) { + async provideSlashCommands(token: CancellationToken): Promise { + if (!this._subCommandProvider) { return []; } - const result = await this._slashCommandProvider.provideSubCommands(token); + const result = await this._subCommandProvider.provideSubCommands(token); if (!result) { return []; } - this._lastSlashCommands = result; return result .map(c => ({ name: c.name, @@ -333,7 +313,7 @@ class ExtHostChatAgent { 'dark' in this._iconPath ? this._iconPath.dark : undefined, themeIcon: this._iconPath instanceof extHostTypes.ThemeIcon ? this._iconPath : undefined, - hasSlashCommands: this._slashCommandProvider !== undefined, + hasSlashCommands: this._subCommandProvider !== undefined, hasFollowups: this._followupProvider !== undefined, isDefault: this._isDefault, isSecondary: this._isSecondary, @@ -373,10 +353,10 @@ class ExtHostChatAgent { updateMetadataSoon(); }, get subCommandProvider() { - return that._slashCommandProvider; + return that._subCommandProvider; }, set subCommandProvider(v) { - that._slashCommandProvider = v; + that._subCommandProvider = v; updateMetadataSoon(); }, get followupProvider() { @@ -470,7 +450,7 @@ class ExtHostChatAgent { , dispose() { disposed = true; - that._slashCommandProvider = undefined; + that._subCommandProvider = undefined; that._followupProvider = undefined; that._onDidReceiveFeedback.dispose(); that._proxy.$unregisterAgent(that._handle); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 1afcc8102e8..e3f2810e3a5 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2432,11 +2432,10 @@ export namespace ChatResponseProgress { } export namespace ChatAgentRequest { - export function to(request: IChatAgentRequest, slashCommand: vscode.ChatAgentSubCommand | undefined): vscode.ChatAgentRequest { + export function to(request: IChatAgentRequest): vscode.ChatAgentRequest { return { prompt: request.message, variables: ChatVariable.objectTo(request.variables), - slashCommand, subCommand: request.command, agentId: request.agentId, }; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index b2f83d74ec6..8eaa7771901 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -542,7 +542,7 @@ export class ChatService extends Disposable implements IChatService { agentId: agent.id, message: variableData.message, variables: variableData.variables, - command: agentSlashCommandPart?.command.name ?? '', + command: agentSlashCommandPart?.command.name, }; const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index e303c682925..50451707b22 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -5,9 +5,23 @@ declare module 'vscode' { + /** + * One request/response pair in chat history. + */ export interface ChatAgentHistoryEntry { + /** + * The request that was sent to the chat agent. + */ request: ChatAgentRequest; + + /** + * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. + */ response: ChatAgentContentProgress[]; + + /** + * The result that was received from the chat agent. + */ result: ChatAgentResult2; } @@ -252,13 +266,6 @@ declare module 'vscode' { */ agentId: string; - /** - * The {@link ChatAgentSubCommand subCommand} that was selected for this request. It is guaranteed that the passed subCommand - * is an instance that was previously returned from the {@link ChatAgentSubCommandProvider.provideSubCommands subCommand provider}. - * @deprecated this will be replaced by `subCommand` - */ - slashCommand?: ChatAgentSubCommand; - /** * The name of the {@link ChatAgentSubCommand subCommand} that was selected for this request. */ From 5c0d2f47a11fbc6f98ccc1701446d5aafd6ecb84 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 31 Jan 2024 17:14:47 -0300 Subject: [PATCH 0803/1897] Incomplete markdown patching for codeblocks with >3 backticks (#203925) * Fix incomplete markdown handling of links with codespans * Incomplete markdown patching for codeblocks with >3 backticks --- src/vs/base/browser/markdownRenderer.ts | 18 +++++--- .../test/browser/markdownRenderer.test.ts | 42 +++++++++++++++++++ 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 951a60ff113..af2f3d1a0fc 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -605,7 +605,9 @@ function completeSingleLinePattern(token: marked.Tokens.ListItem | marked.Tokens return completeLinkTargetArg(token); } return completeLinkTarget(token); - } else if (lastLine.match(/(^|\s)\[\w/)) { + } else if (hasStartOfLinkTarget(lastLine)) { + return completeLinkTarget(token); + } else if (lastLine.match(/(^|\s)\[\w/) && !token.tokens.slice(i + 1).some(t => hasStartOfLinkTarget(t.raw))) { return completeLinkText(token); } } @@ -614,6 +616,10 @@ function completeSingleLinePattern(token: marked.Tokens.ListItem | marked.Tokens return undefined; } +function hasStartOfLinkTarget(str: string): boolean { + return !!str.match(/^[^\[]*\]\([^\)]*$/); +} + // function completeListItemPattern(token: marked.Tokens.List): marked.Tokens.List | undefined { // // Patch up this one list item // const lastItem = token.items[token.items.length - 1]; @@ -639,9 +645,11 @@ export function fillInIncompleteTokens(tokens: marked.TokensList): marked.Tokens let newTokens: marked.Token[] | undefined; for (i = 0; i < tokens.length; i++) { const token = tokens[i]; - if (token.type === 'paragraph' && token.raw.match(/(\n|^)```/)) { + let codeblockStart: RegExpMatchArray | null; + if (token.type === 'paragraph' && (codeblockStart = token.raw.match(/(\n|^)(````*)/))) { + const codeblockLead = codeblockStart[2]; // If the code block was complete, it would be in a type='code' - newTokens = completeCodeBlock(tokens.slice(i)); + newTokens = completeCodeBlock(tokens.slice(i), codeblockLead); break; } @@ -680,9 +688,9 @@ export function fillInIncompleteTokens(tokens: marked.TokensList): marked.Tokens return tokens; } -function completeCodeBlock(tokens: marked.Token[]): marked.Token[] { +function completeCodeBlock(tokens: marked.Token[], leader: string): marked.Token[] { const mergedRawText = mergeRawTokenText(tokens); - return marked.lexer(mergedRawText + '\n```'); + return marked.lexer(mergedRawText + `\n${leader}`); } function completeCodespan(token: marked.Token): marked.Token { diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts index 01803b7434d..289b3974df8 100644 --- a/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -513,6 +513,30 @@ suite('MarkdownRenderer', () => { const completeCodeblockTokens = marked.lexer(incompleteCodeblock + '\n```'); assert.deepStrictEqual(newTokens, completeCodeblockTokens); }); + + test('code block header with more backticks', () => { + const incompleteCodeblock = 'some text\n`````js\nconst'; + const tokens = marked.lexer(incompleteCodeblock); + const newTokens = fillInIncompleteTokens(tokens); + + const completeCodeblockTokens = marked.lexer(incompleteCodeblock + '\n`````'); + assert.deepStrictEqual(newTokens, completeCodeblockTokens); + }); + + test('code block header containing codeblock', () => { + const incompleteCodeblock = `some text +\`\`\`\`\`js +const x = 1; +\`\`\` +const y = 2; +\`\`\` +// foo`; + const tokens = marked.lexer(incompleteCodeblock); + const newTokens = fillInIncompleteTokens(tokens); + + const completeCodeblockTokens = marked.lexer(incompleteCodeblock + '\n`````'); + assert.deepStrictEqual(newTokens, completeCodeblockTokens); + }); }); function simpleMarkdownTestSuite(name: string, delimiter: string): void { @@ -687,6 +711,24 @@ suite('MarkdownRenderer', () => { assert.deepStrictEqual(newTokens, completeTokens); }); + test('incomplete link target with extra stuff', () => { + const incomplete = '[before `text` after](http://microsoft.com'; + const tokens = marked.lexer(incomplete); + const newTokens = fillInIncompleteTokens(tokens); + + const completeTokens = marked.lexer(incomplete + ')'); + assert.deepStrictEqual(newTokens, completeTokens); + }); + + test('incomplete link target with extra stuff and arg', () => { + const incomplete = '[before `text` after](http://microsoft.com "more text '; + const tokens = marked.lexer(incomplete); + const newTokens = fillInIncompleteTokens(tokens); + + const completeTokens = marked.lexer(incomplete + ')'); + assert.deepStrictEqual(newTokens, completeTokens); + }); + test('incomplete link target with arg', () => { const incomplete = 'foo [text](http://microsoft.com "more text here '; const tokens = marked.lexer(incomplete); From 7e2981e8a6729f33a950a6b18dac03a14d1ad24a Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 31 Jan 2024 12:25:26 -0800 Subject: [PATCH 0804/1897] eng: fix missing VSCODE_CHECK_ONLY in CLI publish step (#203926) --- build/azure-pipelines/alpine/cli-build-alpine.yml | 2 ++ build/azure-pipelines/darwin/cli-build-darwin.yml | 2 ++ build/azure-pipelines/linux/cli-build-linux.yml | 3 +++ build/azure-pipelines/win32/cli-build-win32.yml | 2 ++ 4 files changed, 9 insertions(+) diff --git a/build/azure-pipelines/alpine/cli-build-alpine.yml b/build/azure-pipelines/alpine/cli-build-alpine.yml index f808fe67112..0d179b82db6 100644 --- a/build/azure-pipelines/alpine/cli-build-alpine.yml +++ b/build/azure-pipelines/alpine/cli-build-alpine.yml @@ -93,8 +93,10 @@ steps: - template: ../cli/cli-publish.yml parameters: VSCODE_CLI_ARTIFACT: vscode_cli_alpine_arm64_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} - ${{ if eq(parameters.VSCODE_BUILD_ALPINE, true) }}: - template: ../cli/cli-publish.yml parameters: VSCODE_CLI_ARTIFACT: vscode_cli_alpine_x64_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} diff --git a/build/azure-pipelines/darwin/cli-build-darwin.yml b/build/azure-pipelines/darwin/cli-build-darwin.yml index c7b0053eb50..ac5adaec175 100644 --- a/build/azure-pipelines/darwin/cli-build-darwin.yml +++ b/build/azure-pipelines/darwin/cli-build-darwin.yml @@ -70,8 +70,10 @@ steps: - template: ../cli/cli-publish.yml parameters: VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_darwin_x64_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: - template: ../cli/cli-publish.yml parameters: VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_darwin_arm64_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} diff --git a/build/azure-pipelines/linux/cli-build-linux.yml b/build/azure-pipelines/linux/cli-build-linux.yml index 3fa2da61154..ff851c63c96 100644 --- a/build/azure-pipelines/linux/cli-build-linux.yml +++ b/build/azure-pipelines/linux/cli-build-linux.yml @@ -129,13 +129,16 @@ steps: - template: ../cli/cli-publish.yml parameters: VSCODE_CLI_ARTIFACT: vscode_cli_linux_armhf_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: - template: ../cli/cli-publish.yml parameters: VSCODE_CLI_ARTIFACT: vscode_cli_linux_x64_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARM64, true) }}: - template: ../cli/cli-publish.yml parameters: VSCODE_CLI_ARTIFACT: vscode_cli_linux_arm64_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} diff --git a/build/azure-pipelines/win32/cli-build-win32.yml b/build/azure-pipelines/win32/cli-build-win32.yml index 5e1baa80603..1210b1555c0 100644 --- a/build/azure-pipelines/win32/cli-build-win32.yml +++ b/build/azure-pipelines/win32/cli-build-win32.yml @@ -71,8 +71,10 @@ steps: - template: ../cli/cli-publish.yml parameters: VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_win32_arm64_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - template: ../cli/cli-publish.yml parameters: VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_win32_x64_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} From fac9ec9c1bf9231113e127e13bfb3d5ff1027f4c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:57:25 -0800 Subject: [PATCH 0805/1897] Don't recreate TerminalLinkManager after registered by ext Fixes #203930 --- .../browser/terminal.links.contribution.ts | 13 +++---- .../links/browser/terminalLinkManager.ts | 35 +++++++++++-------- .../test/browser/terminalLinkManager.test.ts | 4 +-- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts index a8d46af2d62..ce04c3aa002 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts @@ -63,21 +63,16 @@ class TerminalLinkContribution extends DisposableStore implements ITerminalContr linkManager.setWidgetManager(this._widgetManager); } - // Attach the link provider(s) to the instance and listen for changes + // Attach the external link provider to the instance and listen for changes if (!isDetachedTerminalInstance(this._instance)) { for (const linkProvider of this._terminalLinkProviderService.linkProviders) { - linkManager.registerExternalLinkProvider(linkProvider.provideLinks.bind(linkProvider, this._instance)); + linkManager.externalProvideLinksCb = linkProvider.provideLinks.bind(linkProvider, this._instance); } linkManager.add(this._terminalLinkProviderService.onDidAddLinkProvider(e => { - linkManager.registerExternalLinkProvider(e.provideLinks.bind(e, this._instance as ITerminalInstance)); + linkManager.externalProvideLinksCb = e.provideLinks.bind(e, this._instance as ITerminalInstance); })); } - - // TODO: Currently only a single link provider is supported; the one registered by the ext host - linkManager.add(this._terminalLinkProviderService.onDidRemoveLinkProvider(e => { - linkManager.dispose(); - this.xtermReady(xterm); - })); + linkManager.add(this._terminalLinkProviderService.onDidRemoveLinkProvider(() => linkManager.externalProvideLinksCb = undefined)); } async showLinkQuickpick(extended?: boolean): Promise { diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts index bfb1a5391ba..8d981fb62d2 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts @@ -45,6 +45,8 @@ export class TerminalLinkManager extends DisposableStore { private readonly _externalLinkProviders: IDisposable[] = []; private readonly _openers: Map = new Map(); + externalProvideLinksCb?: OmitFirstArg; + constructor( private readonly _xterm: Terminal, private readonly _processInfo: ITerminalProcessInfo, @@ -349,25 +351,30 @@ export class TerminalLinkManager extends DisposableStore { } private _registerStandardLinkProviders(): void { + // Forward any external link provider requests to the registered provider if it exists. This + // helps maintain the relative priority of the link providers as it's defined by the order + // in which they're registered in xterm.js. + // + /** + * There's a bit going on here but here's another view: + * - {@link externalProvideLinksCb} The external callback that gives the links (eg. from + * exthost) + * - {@link proxyLinkProvider} A proxy that forwards the call over to + * {@link externalProvideLinksCb} + * - {@link wrappedLinkProvider} Wraps the above in an `TerminalLinkDetectorAdapter` + */ + const proxyLinkProvider: OmitFirstArg = async (bufferLineNumber) => { + return this.externalProvideLinksCb?.(bufferLineNumber); + }; + const detectorId = `extension-${this._externalLinkProviders.length}`; + const wrappedLinkProvider = this._setupLinkDetector(detectorId, new TerminalExternalLinkDetector(detectorId, this._xterm, proxyLinkProvider), true); + this._linkProvidersDisposables.push(this._xterm.registerLinkProvider(wrappedLinkProvider)); + for (const p of this._standardLinkProviders.values()) { this._linkProvidersDisposables.push(this._xterm.registerLinkProvider(p)); } } - registerExternalLinkProvider(provideLinks: OmitFirstArg): void { - // Avoid any leaks in case this is already disposed - if (this.isDisposed) { - return; - } - // Clear and re-register the standard link providers so they are a lower priority than the new one - this._clearLinkProviders(); - const detectorId = `extension-${this._externalLinkProviders.length}`; - const wrappedLinkProvider = this._setupLinkDetector(detectorId, new TerminalExternalLinkDetector(detectorId, this._xterm, provideLinks), true); - const newLinkProvider = this._xterm.registerLinkProvider(wrappedLinkProvider); - this._externalLinkProviders.push(newLinkProvider); - this._registerStandardLinkProviders(); - } - protected _isLinkActivationModifierDown(event: MouseEvent): boolean { const editorConf = this._configurationService.getValue<{ multiCursorModifier: 'ctrlCmd' | 'alt' }>('editor'); if (editorConf.multiCursorModifier === 'ctrlCmd') { diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts index 11abe172871..7bd364483fa 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts @@ -101,9 +101,9 @@ suite('TerminalLinkManager', () => { suite('registerExternalLinkProvider', () => { test('should not leak disposables if the link manager is already disposed', () => { - linkManager.registerExternalLinkProvider(async () => undefined); + linkManager.externalProvideLinksCb = async () => undefined; linkManager.dispose(); - linkManager.registerExternalLinkProvider(async () => undefined); + linkManager.externalProvideLinksCb = async () => undefined; }); }); From 32ebbed9ef9f7d583f95dda2bec5582847e6d337 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:58:39 -0800 Subject: [PATCH 0806/1897] Bind proc ready event to link manager, not contrib --- .../links/browser/terminal.links.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts index ce04c3aa002..0327aa48168 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts @@ -55,7 +55,7 @@ class TerminalLinkContribution extends DisposableStore implements ITerminalContr // Set widget manager if (isTerminalProcessManager(this._processManager)) { - const disposable = this.add(Event.once(this._processManager.onProcessReady)(() => { + const disposable = linkManager.add(Event.once(this._processManager.onProcessReady)(() => { linkManager.setWidgetManager(this._widgetManager); this.delete(disposable); })); From 5e6ec068b2ceff37597ff357a49b8265315fb97a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 31 Jan 2024 14:46:39 -0800 Subject: [PATCH 0807/1897] Split TS' AI-backed code actions into separate entries (#201140) * Split TS' AI-backed code actions into separate entries Lets the user decide whether to add AI to their code action, which shows intent, which is good for us to learn whether people actually want this. Related: this should be unflagged for insiders. To do this, do I just delete the flags? * Stop appending a duplicate message in missingFunctionDeclaration * Fix: quickfix was still showing Copilot-only It's a workaround--I'm not sure of the right way to do this. * Update to use `isAI` * Put AI code actions after others. * Add isAI to rest of code actions * Remove flags for TS AI code actions * Check for copilot-chat instead of copilot It's possible to have copilot installed without copilot-chat. * Fix file casing --------- Co-authored-by: Matt Bierner --- .../typescript-language-features/package.json | 56 +---------- .../package.nls.json | 10 -- .../src/languageFeatures/quickFix.ts | 92 +++++++++++-------- .../src/languageFeatures/refactor.ts | 40 ++++---- .../tsconfig.json | 5 +- 5 files changed, 84 insertions(+), 119 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 880c8dc462b..9480d471455 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -10,7 +10,8 @@ "enabledApiProposals": [ "workspaceTrust", "multiDocumentHighlightProvider", - "mappedEditsProvider" + "mappedEditsProvider", + "codeActionAI" ], "capabilities": { "virtualWorkspaces": { @@ -147,59 +148,6 @@ "title": "%configuration.typescript%", "order": 20, "properties": { - "typescript.experimental.aiCodeActions": { - "type": "object", - "default": {}, - "description": "%typescript.experimental.aiCodeActions%", - "scope": "resource", - "properties": { - "classIncorrectlyImplementsInterface": { - "type": "boolean", - "default": false, - "description": "%typescript.experimental.aiCodeActions.classIncorrectlyImplementsInterface%" - }, - "classDoesntImplementInheritedAbstractMember": { - "type": "boolean", - "default": false, - "description": "%typescript.experimental.aiCodeActions.classDoesntImplementInheritedAbstractMember%" - }, - "missingFunctionDeclaration": { - "type": "boolean", - "default": false, - "description": "%typescript.experimental.aiCodeActions.missingFunctionDeclaration%" - }, - "inferAndAddTypes": { - "type": "boolean", - "default": false, - "description": "%typescript.experimental.aiCodeActions.inferAndAddTypes%" - }, - "addNameToNamelessParameter": { - "type": "boolean", - "default": false, - "description": "%typescript.experimental.aiCodeActions.addNameToNamelessParameter%" - }, - "extractConstant": { - "type": "boolean", - "default": false, - "description": "%typescript.experimental.aiCodeActions.extractConstant%" - }, - "extractFunction": { - "type": "boolean", - "default": false, - "description": "%typescript.experimental.aiCodeActions.extractFunction%" - }, - "extractType": { - "type": "boolean", - "default": false, - "description": "%typescript.experimental.aiCodeActions.extractType%" - }, - "extractInterface": { - "type": "boolean", - "default": false, - "description": "%typescript.experimental.aiCodeActions.extractInterface%" - } - } - }, "typescript.tsdk": { "type": "string", "markdownDescription": "%typescript.tsdk.desc%", diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 0bd09aa8d55..796b524a48c 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -8,16 +8,6 @@ "configuration.suggest.completeFunctionCalls": "Complete functions with their parameter signature.", "configuration.suggest.includeAutomaticOptionalChainCompletions": "Enable/disable showing completions on potentially undefined values that insert an optional chain call. Requires strict null checks to be enabled.", "configuration.suggest.includeCompletionsForImportStatements": "Enable/disable auto-import-style completions on partially-typed import statements.", - "typescript.experimental.aiCodeActions": "Enable/disable AI-assisted code actions. Requires an extension providing AI chat functionality.", - "typescript.experimental.aiCodeActions.classIncorrectlyImplementsInterface": "Enable/disable AI assistance for Class Incorrectly Implements Interface quickfix. Requires an extension providing AI chat functionality.", - "typescript.experimental.aiCodeActions.classDoesntImplementInheritedAbstractMember": "Enable/disable AI assistance for Class Doesn't Implement Inherited Abstract Member quickfix. Requires an extension providing AI chat functionality.", - "typescript.experimental.aiCodeActions.missingFunctionDeclaration": "Enable/disable AI assistance for Missing Function Declaration quickfix. Requires an extension providing AI chat functionality.", - "typescript.experimental.aiCodeActions.inferAndAddTypes": "Enable/disable AI assistance for Infer and Add Types refactor. Requires an extension providing AI chat functionality.", - "typescript.experimental.aiCodeActions.addNameToNamelessParameter": "Enable/disable AI assistance for Add Name to Nameless Parameter quickfix. Requires an extension providing AI chat functionality.", - "typescript.experimental.aiCodeActions.extractConstant": "Enable/disable AI assistance for Extract Constant refactor. Requires an extension providing AI chat functionality.", - "typescript.experimental.aiCodeActions.extractFunction": "Enable/disable AI assistance for Extract Function refactor. Requires an extension providing AI chat functionality.", - "typescript.experimental.aiCodeActions.extractType": "Enable/disable AI assistance for Extract Type refactor. Requires an extension providing AI chat functionality.", - "typescript.experimental.aiCodeActions.extractInterface": "Enable/disable AI assistance for Extract Interface refactor. Requires an extension providing AI chat functionality.", "typescript.tsdk.desc": "Specifies the folder path to the tsserver and `lib*.d.ts` files under a TypeScript install to use for IntelliSense, for example: `./node_modules/typescript/lib`.\n\n- When specified as a user setting, the TypeScript version from `typescript.tsdk` automatically replaces the built-in TypeScript version.\n- When specified as a workspace setting, `typescript.tsdk` allows you to switch to use that workspace version of TypeScript for IntelliSense with the `TypeScript: Select TypeScript version` command.\n\nSee the [TypeScript documentation](https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-newer-typescript-versions) for more detail about managing TypeScript versions.", "typescript.disableAutomaticTypeAcquisition": "Disables [automatic type acquisition](https://code.visualstudio.com/docs/nodejs/working-with-javascript#_typings-and-automatic-type-acquisition). Automatic type acquisition fetches `@types` packages from npm to improve IntelliSense for external libraries.", "typescript.enablePromptUseWorkspaceTsdk": "Enables prompting of users to use the TypeScript version configured in the workspace for Intellisense.", diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index 9f82bc759e3..26a658d9116 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -147,12 +147,19 @@ class VsCodeFixAllCodeAction extends VsCodeCodeAction { class CodeActionSet { private readonly _actions = new Set(); private readonly _fixAllActions = new Map<{}, VsCodeCodeAction>(); + private readonly _aiActions = new Set(); - public get values(): Iterable { - return this._actions; + public *values(): Iterable { + yield* this._actions; + yield* this._aiActions; } public addAction(action: VsCodeCodeAction) { + if (action.isAI) { + // there are no separate fixAllActions for AI, and no duplicates, so return immediately + this._aiActions.add(action); + return; + } for (const existing of this._actions) { if (action.tsAction.fixName === existing.tsAction.fixName && equals(action.edit, existing.edit)) { this._actions.delete(existing); @@ -261,7 +268,7 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider change.textChanges.map(textChange => textChange.newText).join('')).join(''); title = 'Add meaningful parameter name with Copilot'; message = `Rename the parameter ${newText} with a more meaningful name.`; @@ -365,32 +384,33 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider vscode.Command) | undefined; - if (vscode.workspace.getConfiguration('typescript', null).get('experimental.aiCodeActions')) { - if (Extract_Constant.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractConstant') - || Extract_Function.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractFunction') - || Extract_Type.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractType') - || Extract_Interface.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractInterface') + codeActions.push(new InlinedCodeAction(this.client, document, refactor, action, rangeOrSelection, undefined)); + const copilot = vscode.extensions.getExtension('github.copilot-chat'); + if (copilot?.isActive) { + if (Extract_Constant.matches(action) + || Extract_Function.matches(action) + || Extract_Type.matches(action) + || Extract_Interface.matches(action) ) { const newName = Extract_Constant.matches(action) ? 'newLocal' : Extract_Function.matches(action) ? 'newFunction' : Extract_Type.matches(action) ? 'NewType' : Extract_Interface.matches(action) ? 'NewInterface' : ''; - copilotRename = info => ({ + const copilotRename: ((info: Proto.RefactorEditInfo) => vscode.Command) = info => ({ title: '', command: EditorChatFollowUp.ID, arguments: [{ @@ -658,14 +664,14 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider Date: Wed, 31 Jan 2024 15:58:00 -0800 Subject: [PATCH 0808/1897] more copy SVG fixes (#203937) * re-add check for image for context menu, add selector for jupyter rendered SVGs * consisent command name --- extensions/ipynb/package.json | 2 +- .../browser/controller/cellOutputActions.ts | 15 +++++++++------ .../browser/view/renderers/webviewPreloads.ts | 4 +++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index 5703a575dc1..f29ed650162 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -113,7 +113,7 @@ "webview/context": [ { "command": "notebook.cellOutput.copy", - "when": "webviewId == 'notebook.output'" + "when": "webviewId == 'notebook.output' && webviewSection == 'image'" } ] } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/cellOutputActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/cellOutputActions.ts index 1469d991522..685e2754c47 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/cellOutputActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/cellOutputActions.ts @@ -23,7 +23,7 @@ registerAction2(class CopyCellOutputAction extends Action2 { constructor() { super({ id: COPY_OUTPUT_COMMAND_ID, - title: localize('notebookActions.copyOutput', "Copy Output"), + title: localize('notebookActions.copyOutput', "Copy Cell Output"), menu: { id: MenuId.NotebookOutputToolbar, when: NOTEBOOK_CELL_HAS_OUTPUTS @@ -42,7 +42,14 @@ registerAction2(class CopyCellOutputAction extends Action2 { } let outputViewModel: ICellOutputViewModel | undefined; - if (!outputContext) { + if (outputContext && 'outputId' in outputContext && typeof outputContext.outputId === 'string') { + outputViewModel = getOutputViewModelFromId(outputContext.outputId, notebookEditor); + } else if (outputContext && 'outputViewModel' in outputContext) { + outputViewModel = outputContext.outputViewModel; + } + + if (!outputViewModel) { + // not able to find the output from the provided context, use the active cell const activeCell = notebookEditor.getActiveCell(); if (!activeCell) { return; @@ -55,10 +62,6 @@ registerAction2(class CopyCellOutputAction extends Action2 { } else { outputViewModel = activeCell.outputsViewModels.find(output => output.pickedMimeType?.isTrusted); } - } else if ('outputId' in outputContext && typeof outputContext.outputId === 'string') { - outputViewModel = getOutputViewModelFromId(outputContext.outputId, notebookEditor); - } else { - outputViewModel = outputContext.outputViewModel; } if (!outputViewModel) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 0670f60c5da..16e0276f97a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -1400,7 +1400,9 @@ async function webviewPreloads(ctx: PreloadContext) { let image = outputElement?.querySelector('img'); if (!image) { - const svgImage = outputElement?.querySelector('svg.output-image'); + const svgImage = outputElement?.querySelector('svg.output-image') ?? + outputElement?.querySelector('div.svgContainerStyle > svg'); + if (svgImage) { image = new Image(); image.src = 'data:image/svg+xml,' + encodeURIComponent(svgImage.outerHTML); From 8182fd55db546ec005b5fa02624b98c7e3b60fc7 Mon Sep 17 00:00:00 2001 From: Lucas Towers Date: Wed, 31 Jan 2024 16:54:32 -0800 Subject: [PATCH 0809/1897] Fix markdown light and dark mode when using high contrast themes (#203690) --- extensions/github/markdown.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/github/markdown.css b/extensions/github/markdown.css index a8cb5840ccc..84441f42b16 100644 --- a/extensions/github/markdown.css +++ b/extensions/github/markdown.css @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ .vscode-dark img[src$=\#gh-light-mode-only], -.vscode-light img[src$=\#gh-dark-mode-only] { +.vscode-light img[src$=\#gh-dark-mode-only], +.vscode-high-contrast img[src$=\#gh-light-mode-only], +.vscode-high-contrast-light img[src$=\#gh-dark-mode-only] { display: none; } From 3969ef5b50e6b8ef4261012a5ee8c3d8b24662f5 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 1 Feb 2024 06:00:00 +0100 Subject: [PATCH 0810/1897] perf - track `ellapsedWorkbenchContributions` --- .../contrib/performance/browser/perfviewEditor.ts | 1 + .../workbench/services/timer/browser/timerService.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts index a06483bd1cf..846f995024c 100644 --- a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts @@ -215,6 +215,7 @@ class PerfModelContentProvider implements ITextModelContentProvider { table.push(['restore viewlet', metrics.timers.ellapsedViewletRestore, '[renderer]', metrics.viewletId]); table.push(['restore panel', metrics.timers.ellapsedPanelRestore, '[renderer]', metrics.panelId]); table.push(['restore & resolve visible editors', metrics.timers.ellapsedEditorRestore, '[renderer]', `${metrics.editorIds.length}: ${metrics.editorIds.join(', ')}`]); + table.push(['create workbench contributions', metrics.timers.ellapsedWorkbenchContributions, '[renderer]']); table.push(['overall workbench load', metrics.timers.ellapsedWorkbench, '[renderer]', undefined]); table.push(['workbench ready', metrics.ellapsed, '[main->renderer]', undefined]); table.push(['renderer ready', metrics.timers.ellapsedRenderer, '[renderer]', undefined]); diff --git a/src/vs/workbench/services/timer/browser/timerService.ts b/src/vs/workbench/services/timer/browser/timerService.ts index 28d247f2871..eb94174c972 100644 --- a/src/vs/workbench/services/timer/browser/timerService.ts +++ b/src/vs/workbench/services/timer/browser/timerService.ts @@ -66,6 +66,7 @@ export interface IMemoryInfo { "timers.ellapsedViewletRestore" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedPanelRestore" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedEditorRestore" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "timers.ellapsedWorkbenchContributions" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedWorkbench" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "platform" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, "release" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, @@ -364,6 +365,16 @@ export interface IStartupMetrics { */ readonly ellapsedEditorRestore: number; + /** + * The time it took to create all workbench contributions on the starting and ready + * lifecycle phase, thus blocking `ellapsedWorkbench`. + * + * * Happens in the renderer-process + * * Measured with the `willCreateWorkbenchContributions/1` and `didCreateWorkbenchContributions/2` performance marks. + * + */ + readonly ellapsedWorkbenchContributions: number; + /** * The time it took to create the workbench. * @@ -686,6 +697,7 @@ export abstract class AbstractTimerService implements ITimerService { ellapsedEditorRestore: this._marks.getDuration('code/willRestoreEditors', 'code/didRestoreEditors'), ellapsedViewletRestore: this._marks.getDuration('code/willRestoreViewlet', 'code/didRestoreViewlet'), ellapsedPanelRestore: this._marks.getDuration('code/willRestorePanel', 'code/didRestorePanel'), + ellapsedWorkbenchContributions: this._marks.getDuration('code/willCreateWorkbenchContributions/1', 'code/didCreateWorkbenchContributions/2'), ellapsedWorkbench: this._marks.getDuration('code/willStartWorkbench', 'code/didStartWorkbench'), ellapsedExtensionsReady: this._marks.getDuration(startMark, 'code/didLoadExtensions'), ellapsedRenderer: this._marks.getDuration('code/didStartRenderer', 'code/didStartWorkbench') From c93c6b2d17113cdb20640db335aa5a10c8e7b8c6 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 1 Feb 2024 08:06:29 +0100 Subject: [PATCH 0811/1897] perf - simplify registering workbench contributions --- .../api/browser/extensionHost.contribution.ts | 5 ++- .../api/browser/viewsExtensionPoint.ts | 5 ++- .../browser/actions/textInputActions.ts | 5 ++- .../actions/widgetNavigationCommands.ts | 6 ++-- .../parts/dialogs/dialog.web.contribution.ts | 6 ++-- .../parts/editor/editor.contribution.ts | 10 +++--- .../browser/workbench.contribution.ts | 4 +-- src/vs/workbench/common/contributions.ts | 36 ++++++++++--------- .../browser/accessibility.contribution.ts | 16 ++++----- .../browser/preview/bulkEdit.contribution.ts | 4 +-- .../contrib/chat/browser/chat.contribution.ts | 4 +-- .../browser/chatContributionServiceImpl.ts | 6 ++-- .../codeEditor/browser/editorFeatures.ts | 6 ++-- .../codeEditor/browser/toggleWordWrap.ts | 6 ++-- .../electron-sandbox/selectionClipboard.ts | 5 ++- .../browser/customEditor.contribution.ts | 5 ++- .../files/browser/files.contribution.ts | 14 ++++---- .../browser/interactive.contribution.ts | 11 +++--- .../contrib/list/browser/list.contribution.ts | 5 ++- .../browser/localHistory.contribution.ts | 5 ++- .../browser/mergeEditor.contribution.ts | 6 ++-- .../browser/multiDiffEditor.contribution.ts | 8 ++--- .../contrib/clipboard/notebookClipboard.ts | 6 ++-- .../browser/contrib/marker/markerProvider.ts | 7 ++-- .../contrib/profile/notebookProfile.ts | 7 ++-- .../contrib/undoRedo/notebookUndoRedo.ts | 6 ++-- .../notebook/browser/notebook.contribution.ts | 16 ++++----- .../browser/performance.contribution.ts | 4 +-- .../performance/browser/perfviewEditor.ts | 6 ++-- .../browser/keyboardLayoutPicker.ts | 6 ++-- .../browser/preferences.contribution.ts | 6 ++-- .../remote/browser/remote.contribution.ts | 8 ++--- .../remote/common/remote.contribution.ts | 6 ++-- .../electron-sandbox/remote.contribution.ts | 8 ++--- .../search/browser/replaceContributions.ts | 5 ++- .../search/browser/search.contribution.ts | 4 +-- .../browser/searchEditor.contribution.ts | 7 ++-- .../splash/browser/splash.contribution.ts | 5 ++- .../electron-sandbox/splash.contribution.ts | 5 ++- .../electron-sandbox/terminal.contribution.ts | 4 +-- .../contrib/url/browser/url.contribution.ts | 6 ++-- .../browser/userDataProfile.contribution.ts | 4 +-- .../userDataSync.contribution.ts | 6 ++-- .../browser/webviewPanel.contribution.ts | 5 ++- .../browser/walkThrough.contribution.ts | 5 ++- .../browser/workspace.contribution.ts | 4 +-- .../parts/dialogs/dialog.contribution.ts | 6 ++-- .../electron-sandbox/accessibilityService.ts | 5 ++- .../browser/configurationService.ts | 4 +-- .../extensions/browser/extensionUrlHandler.ts | 6 ++-- .../remote/browser/remoteAgentService.ts | 6 ++-- .../electron-sandbox/remoteAgentService.ts | 6 ++-- ...extMateTokenizationFeature.contribution.ts | 6 ++-- .../browser/workingCopyBackupService.ts | 5 ++- .../workingCopyBackupService.ts | 5 ++- 55 files changed, 158 insertions(+), 215 deletions(-) diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index a814d781c79..b04a4d3279a 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; // --- other interested parties @@ -107,4 +106,4 @@ export class ExtensionPoints implements IWorkbenchContribution { } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(ExtensionPoints.ID, ExtensionPoints, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(ExtensionPoints.ID, ExtensionPoints, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index cdbc3d7d432..03ab97c9c97 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -17,7 +17,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { Extensions as ViewletExtensions, PaneCompositeRegistry } from 'vs/workbench/browser/panecomposite'; import { CustomTreeView, RawCustomTreeViewContextKey, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { Extensions as ViewContainerExtensions, ICustomViewDescriptor, IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ResolvableTreeItem, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug'; import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files'; @@ -666,5 +666,4 @@ class ViewsExtensionHandler implements IWorkbenchContribution { } } -const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution2(ViewsExtensionHandler.ID, ViewsExtensionHandler, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(ViewsExtensionHandler.ID, ViewsExtensionHandler, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/browser/actions/textInputActions.ts b/src/vs/workbench/browser/actions/textInputActions.ts index 9c8939abf75..c3c85845aea 100644 --- a/src/vs/workbench/browser/actions/textInputActions.ts +++ b/src/vs/workbench/browser/actions/textInputActions.ts @@ -9,8 +9,7 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Disposable } from 'vs/base/common/lifecycle'; import { EventHelper, addDisposableListener, getActiveDocument, getWindow } from 'vs/base/browser/dom'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { isNative } from 'vs/base/common/platform'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -106,4 +105,4 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(TextInputActionsProvider.ID, TextInputActionsProvider, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(TextInputActionsProvider.ID, TextInputActionsProvider, WorkbenchContributionInstantiation.BlockRestore); diff --git a/src/vs/workbench/browser/actions/widgetNavigationCommands.ts b/src/vs/workbench/browser/actions/widgetNavigationCommands.ts index c98008e966e..59b2113bdcc 100644 --- a/src/vs/workbench/browser/actions/widgetNavigationCommands.ts +++ b/src/vs/workbench/browser/actions/widgetNavigationCommands.ts @@ -9,8 +9,7 @@ import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/co import { WorkbenchListFocusContextKey, WorkbenchListScrollAtBottomContextKey, WorkbenchListScrollAtTopContextKey } from 'vs/platform/list/browser/listService'; import { Event } from 'vs/base/common/event'; import { combinedDisposable, toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; /** INavigableContainer represents a logical container composed of widgets that can be navigated back and forth with key shortcuts */ @@ -113,8 +112,7 @@ export function registerNavigableContainer(container: INavigableContainer): IDis return NavigableContainerManager.register(container); } -Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution2(NavigableContainerManager.ID, NavigableContainerManager, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(NavigableContainerManager.ID, NavigableContainerManager, WorkbenchContributionInstantiation.BlockStartup); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'widgetNavigation.focusPrevious', diff --git a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts index fd3512321c1..b69c8db3b30 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts @@ -9,8 +9,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IDialogsModel, IDialogViewItem } from 'vs/workbench/common/dialogs'; import { BrowserDialogHandler } from 'vs/workbench/browser/parts/dialogs/dialogHandler'; import { DialogService } from 'vs/workbench/services/dialogs/common/dialogService'; @@ -78,5 +77,4 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC } } -const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution2(DialogHandlerContribution.ID, DialogHandlerContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(DialogHandlerContribution.ID, DialogHandlerContribution, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index f240fb9aac5..4a9bbd32ed7 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -59,7 +59,7 @@ import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/com import { isMacintosh } from 'vs/base/common/platform'; import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { FloatingEditorClickMenu } from 'vs/workbench/browser/codeeditor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorAutoSave } from 'vs/workbench/browser/parts/editor/editorAutoSave'; import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess, AllEditorsByAppearanceQuickAccess, AllEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; @@ -127,10 +127,10 @@ Registry.as(EditorExtensions.EditorFactory).registerEdit //#region Workbench Contributions -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(EditorAutoSave.ID, EditorAutoSave, WorkbenchContributionInstantiation.BlockRestore); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(EditorStatusContribution.ID, EditorStatusContribution, WorkbenchContributionInstantiation.BlockRestore); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(UntitledTextEditorWorkingCopyEditorHandler.ID, UntitledTextEditorWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(DynamicEditorConfigurations.ID, DynamicEditorConfigurations, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(EditorAutoSave.ID, EditorAutoSave, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(EditorStatusContribution.ID, EditorStatusContribution, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(UntitledTextEditorWorkingCopyEditorHandler.ID, UntitledTextEditorWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(DynamicEditorConfigurations.ID, DynamicEditorConfigurations, WorkbenchContributionInstantiation.BlockRestore); registerEditorContribution(FloatingEditorClickMenu.ID, FloatingEditorClickMenu, EditorContributionInstantiation.AfterFirstRender); //#endregion diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 8a0edf0c26e..3693d849422 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -9,7 +9,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform'; import { ConfigurationMigrationWorkbenchContribution, DynamicWorkbenchConfigurationWorkbenchContribution, IConfigurationMigrationRegistry, workbenchConfigurationNodeBase, Extensions, ConfigurationKeyValuePairs, problemsConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { isStandalone } from 'vs/base/browser/browser'; -import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ActivityBarPosition, EditorActionsLocation, EditorTabsMode, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; @@ -22,7 +22,7 @@ const registry = Registry.as(ConfigurationExtensions.Con Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ConfigurationMigrationWorkbenchContribution, LifecyclePhase.Eventually); // Dynamic Configuration - Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(DynamicWorkbenchConfigurationWorkbenchContribution.ID, DynamicWorkbenchConfigurationWorkbenchContribution, WorkbenchContributionInstantiation.BlockRestore); + registerWorkbenchContribution2(DynamicWorkbenchConfigurationWorkbenchContribution.ID, DynamicWorkbenchConfigurationWorkbenchContribution, WorkbenchContributionInstantiation.BlockRestore); // Workbench registry.registerConfiguration({ diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index 45481aff1ac..986fd296744 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -96,22 +96,6 @@ export interface IWorkbenchContributionsRegistry { */ registerWorkbenchContribution(contribution: IWorkbenchContributionSignature, phase: LifecyclePhase.Restored | LifecyclePhase.Eventually): void; - /** - * Register a workbench contribution that will be instantiated - * based on the `instantiation` property. - */ - registerWorkbenchContribution2(id: string, ctor: IWorkbenchContributionSignature, instantiation: WorkbenchContributionInstantiation): void; - - /** - * Provides access to a workbench contribution with a specific identifier. - * The contribution is created if not yet done. - * - * Note: will throw an error if - * - called too early before the registry has started - * - no contribution is known for the given identifier - */ - getWorkbenchContribution(id: string): T; - /** * Starts the registry by providing the required services. */ @@ -137,6 +121,8 @@ interface IWorkbenchContributionRegistration { export class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry { + static readonly INSTANCE = new WorkbenchContributionsRegistry(); + private static readonly BLOCK_BEFORE_RESTORE_WARN_THRESHOLD = 20; private static readonly BLOCK_AFTER_RESTORE_WARN_THRESHOLD = 100; @@ -368,4 +354,20 @@ export class WorkbenchContributionsRegistry implements IWorkbenchContributionsRe } } -Registry.add(Extensions.Workbench, new WorkbenchContributionsRegistry()); +/** + * Register a workbench contribution that will be instantiated + * based on the `instantiation` property. + */ +export const registerWorkbenchContribution2 = WorkbenchContributionsRegistry.INSTANCE.registerWorkbenchContribution2.bind(WorkbenchContributionsRegistry.INSTANCE) as { (id: string, ctor: IWorkbenchContributionSignature, instantiation: WorkbenchContributionInstantiation): void }; + +/** + * Provides access to a workbench contribution with a specific identifier. + * The contribution is created if not yet done. + * + * Note: will throw an error if + * - called too early before the registry has started + * - no contribution is known for the given identifier + */ +export const getWorkbenchContribution = WorkbenchContributionsRegistry.INSTANCE.getWorkbenchContribution.bind(WorkbenchContributionsRegistry.INSTANCE); + +Registry.add(Extensions.Workbench, WorkbenchContributionsRegistry.INSTANCE); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 884163dd8e2..963efd8617f 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -5,7 +5,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { DynamicSpeechAccessibilityConfiguration, registerAccessibilityConfiguration } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { IAccessibleViewService, AccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; @@ -24,10 +24,10 @@ workbenchRegistry.registerWorkbenchContribution(EditorAccessibilityHelpContribut workbenchRegistry.registerWorkbenchContribution(CommentsAccessibilityHelpContribution, LifecyclePhase.Eventually); workbenchRegistry.registerWorkbenchContribution(UnfocusedViewDimmingContribution, LifecyclePhase.Restored); -const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(HoverAccessibleViewContribution, LifecyclePhase.Eventually); -workbenchContributionsRegistry.registerWorkbenchContribution(NotificationAccessibleViewContribution, LifecyclePhase.Eventually); -workbenchContributionsRegistry.registerWorkbenchContribution(InlineCompletionsAccessibleViewContribution, LifecyclePhase.Eventually); -workbenchContributionsRegistry.registerWorkbenchContribution2(AccessibilityStatus.ID, AccessibilityStatus, WorkbenchContributionInstantiation.BlockRestore); -workbenchContributionsRegistry.registerWorkbenchContribution2(SaveAudioCueContribution.ID, SaveAudioCueContribution, WorkbenchContributionInstantiation.BlockRestore); -workbenchContributionsRegistry.registerWorkbenchContribution2(DynamicSpeechAccessibilityConfiguration.ID, DynamicSpeechAccessibilityConfiguration, WorkbenchContributionInstantiation.BlockRestore); +workbenchRegistry.registerWorkbenchContribution(HoverAccessibleViewContribution, LifecyclePhase.Eventually); +workbenchRegistry.registerWorkbenchContribution(NotificationAccessibleViewContribution, LifecyclePhase.Eventually); +workbenchRegistry.registerWorkbenchContribution(InlineCompletionsAccessibleViewContribution, LifecyclePhase.Eventually); + +registerWorkbenchContribution2(AccessibilityStatus.ID, AccessibilityStatus, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(SaveAudioCueContribution.ID, SaveAudioCueContribution, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(DynamicSpeechAccessibilityConfiguration.ID, DynamicSpeechAccessibilityConfiguration, WorkbenchContributionInstantiation.BlockRestore); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts index a20120d6900..6665ee79708 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IBulkEditService, ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { BulkEditPane } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane'; import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views'; @@ -320,7 +320,7 @@ registerAction2(class ToggleGrouping extends Action2 { } }); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2( +registerWorkbenchContribution2( BulkEditPreviewContribution.ID, BulkEditPreviewContribution, WorkbenchContributionInstantiation.BlockRestore ); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index e21da867b90..c4ebb7a5ff2 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -13,7 +13,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { registerChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { registerChatCodeBlockActions } from 'vs/workbench/contrib/chat/browser/actions/chatCodeblockActions'; @@ -284,7 +284,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution2(ChatResolverContribution.ID, ChatResolverContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(ChatResolverContribution.ID, ChatResolverContribution, WorkbenchContributionInstantiation.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(ChatAccessibleViewContribution, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(ChatSlashStaticSlashCommandsContribution, LifecyclePhase.Eventually); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(ChatEditorInput.TypeID, ChatEditorInputSerializer); diff --git a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts index bcaecb8d6d0..8a9df3934e7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts @@ -11,7 +11,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation, Extensions as ViewExtensions } from 'vs/workbench/common/views'; import { getHistoryAction, getOpenChatEditorAction } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { getNewChatAction } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; @@ -154,9 +154,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { } } -const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchContributionInstantiation.BlockStartup); - +registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchContributionInstantiation.BlockStartup); export class ChatContributionService implements IChatContributionService { declare _serviceBrand: undefined; diff --git a/src/vs/workbench/contrib/codeEditor/browser/editorFeatures.ts b/src/vs/workbench/contrib/codeEditor/browser/editorFeatures.ts index 534473115c3..20fae232e41 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/editorFeatures.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/editorFeatures.ts @@ -8,8 +8,7 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { getEditorFeatures } from 'vs/editor/common/editorFeatures'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; class EditorFeaturesInstantiator extends Disposable implements IWorkbenchContribution { @@ -51,5 +50,4 @@ class EditorFeaturesInstantiator extends Disposable implements IWorkbenchContrib } } -const workbenchRegistry = Registry.as(Extensions.Workbench); -workbenchRegistry.registerWorkbenchContribution2(EditorFeaturesInstantiator.ID, EditorFeaturesInstantiator, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(EditorFeaturesInstantiator.ID, EditorFeaturesInstantiator, WorkbenchContributionInstantiation.BlockRestore); diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index f1945b9bae8..09ab7d7cc5a 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -17,8 +17,7 @@ import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from ' import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { Codicon } from 'vs/base/common/codicons'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Event } from 'vs/base/common/event'; import { addDisposableListener, onDidRegisterWindow } from 'vs/base/browser/dom'; @@ -315,8 +314,7 @@ class EditorWordWrapContextKeyTracker extends Disposable implements IWorkbenchCo } } -const workbenchRegistry = Registry.as(Extensions.Workbench); -workbenchRegistry.registerWorkbenchContribution2(EditorWordWrapContextKeyTracker.ID, EditorWordWrapContextKeyTracker, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(EditorWordWrapContextKeyTracker.ID, EditorWordWrapContextKeyTracker, WorkbenchContributionInstantiation.BlockRestore); registerEditorContribution(ToggleWordWrapController.ID, ToggleWordWrapController, EditorContributionInstantiation.Eager); // eager because it needs to change the editor word wrap configuration registerDiffEditorContribution(DiffToggleWordWrapController.ID, DiffToggleWordWrapController); diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts index 4b8c89ae88a..9d914998632 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts @@ -16,8 +16,7 @@ import { IEditorContribution, Handler } from 'vs/editor/common/editorCommon'; import { EndOfLinePreference } from 'vs/editor/common/model'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { mainWindow } from 'vs/base/browser/window'; @@ -144,7 +143,7 @@ class PasteSelectionClipboardAction extends EditorAction { } registerEditorContribution(SelectionClipboardContributionID, SelectionClipboard, EditorContributionInstantiation.Eager); // eager because it needs to listen to selection change events -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(SelectionClipboardPastePreventer.ID, SelectionClipboardPastePreventer, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(SelectionClipboardPastePreventer.ID, SelectionClipboardPastePreventer, WorkbenchContributionInstantiation.BlockRestore); if (platform.isLinux) { registerEditorAction(PasteSelectionClipboardAction); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts b/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts index 1a68794ce2c..9caf2213c67 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts @@ -7,7 +7,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { ComplexCustomWorkingCopyEditorHandler as ComplexCustomWorkingCopyEditorHandler, CustomEditorInputSerializer } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory'; import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; @@ -30,5 +30,4 @@ Registry.as(EditorExtensions.EditorPane) Registry.as(EditorExtensions.EditorFactory) .registerEditorSerializer(CustomEditorInputSerializer.ID, CustomEditorInputSerializer); -Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution2(ComplexCustomWorkingCopyEditorHandler.ID, ComplexCustomWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(ComplexCustomWorkingCopyEditorHandler.ID, ComplexCustomWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index f1090570d8a..22ade982cbe 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { sep } from 'vs/base/common/path'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IFileEditorInput, IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { AutoSaveConfiguration, HotExitConfiguration, FILES_EXCLUDE_CONFIG, FILES_ASSOCIATIONS_CONFIG, FILES_READONLY_INCLUDE_CONFIG, FILES_READONLY_EXCLUDE_CONFIG, FILES_READONLY_FROM_PERMISSIONS_CONFIG } from 'vs/platform/files/common/files'; import { SortOrder, LexicographicOptions, FILE_EDITOR_INPUT_ID, BINARY_TEXT_FILE_MODE, UndoConfirmLevel, IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; @@ -98,25 +98,25 @@ Registry.as(EditorExtensions.EditorFactory).registerFile // Register Editor Input Serializer & Handler Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(FILE_EDITOR_INPUT_ID, FileEditorInputSerializer); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(FileEditorWorkingCopyEditorHandler.ID, FileEditorWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(FileEditorWorkingCopyEditorHandler.ID, FileEditorWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); // Register Explorer views -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(ExplorerViewletViewsContribution.ID, ExplorerViewletViewsContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(ExplorerViewletViewsContribution.ID, ExplorerViewletViewsContribution, WorkbenchContributionInstantiation.BlockStartup); // Register Text File Editor Tracker -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(TextFileEditorTracker.ID, TextFileEditorTracker, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(TextFileEditorTracker.ID, TextFileEditorTracker, WorkbenchContributionInstantiation.BlockStartup); // Register Text File Save Error Handler -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(TextFileSaveErrorHandler.ID, TextFileSaveErrorHandler, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(TextFileSaveErrorHandler.ID, TextFileSaveErrorHandler, WorkbenchContributionInstantiation.BlockStartup); // Register uri display for file uris -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(FileUriLabelContribution.ID, FileUriLabelContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(FileUriLabelContribution.ID, FileUriLabelContribution, WorkbenchContributionInstantiation.BlockStartup); // Register Workspace Watcher Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceWatcher, LifecyclePhase.Restored); // Register Dirty Files Indicator -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(DirtyFilesIndicator.ID, DirtyFilesIndicator, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(DirtyFilesIndicator.ID, DirtyFilesIndicator, WorkbenchContributionInstantiation.BlockStartup); // Configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index e28c6fd1798..c84512a71b5 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -36,7 +36,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { Registry } from 'vs/platform/registry/common/platform'; import { contrastBorder, ifDefinedThenElse, listInactiveSelectionBackground, registerColor } from 'vs/platform/theme/common/colorRegistry'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, EditorsOrder, IEditorFactoryRegistry, IEditorSerializer, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { PANEL_BORDER } from 'vs/workbench/common/theme'; @@ -254,12 +254,9 @@ class InteractiveWindowWorkingCopyEditorHandler extends Disposable implements IW } } - - -const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution2(InteractiveDocumentContribution.ID, InteractiveDocumentContribution, WorkbenchContributionInstantiation.BlockRestore); -workbenchContributionsRegistry.registerWorkbenchContribution2(InteractiveInputContentProvider.ID, InteractiveInputContentProvider, WorkbenchContributionInstantiation.BlockRestore); -workbenchContributionsRegistry.registerWorkbenchContribution2(InteractiveWindowWorkingCopyEditorHandler.ID, InteractiveWindowWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(InteractiveDocumentContribution.ID, InteractiveDocumentContribution, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(InteractiveInputContentProvider.ID, InteractiveInputContentProvider, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(InteractiveWindowWorkingCopyEditorHandler.ID, InteractiveWindowWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); type interactiveEditorInputData = { resource: URI; inputResource: URI; name: string; language: string }; diff --git a/src/vs/workbench/contrib/list/browser/list.contribution.ts b/src/vs/workbench/contrib/list/browser/list.contribution.ts index 9526e40671f..4bd577d21c6 100644 --- a/src/vs/workbench/contrib/list/browser/list.contribution.ts +++ b/src/vs/workbench/contrib/list/browser/list.contribution.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; export class ListContext implements IWorkbenchContribution { @@ -21,4 +20,4 @@ export class ListContext implements IWorkbenchContribution { } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(ListContext.ID, ListContext, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(ListContext.ID, ListContext, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/contrib/localHistory/browser/localHistory.contribution.ts b/src/vs/workbench/contrib/localHistory/browser/localHistory.contribution.ts index bb3efa3ecb4..08bc1675469 100644 --- a/src/vs/workbench/contrib/localHistory/browser/localHistory.contribution.ts +++ b/src/vs/workbench/contrib/localHistory/browser/localHistory.contribution.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/workbench/contrib/localHistory/browser/localHistoryCommands'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { LocalHistoryTimeline } from 'vs/workbench/contrib/localHistory/browser/localHistoryTimeline'; // Register Local History Timeline -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(LocalHistoryTimeline.ID, LocalHistoryTimeline, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(LocalHistoryTimeline.ID, LocalHistoryTimeline, WorkbenchContributionInstantiation.BlockRestore); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts index 74f55531541..acd58fea48b 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts @@ -9,7 +9,7 @@ import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/co import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { AcceptAllInput1, AcceptAllInput2, AcceptMerge, CompareInput1WithBaseCommand, @@ -94,6 +94,4 @@ Registry .as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(MergeEditorOpenHandlerContribution, LifecyclePhase.Restored); -Registry - .as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution2(MergeEditorResolverContribution.ID, MergeEditorResolverContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(MergeEditorResolverContribution.ID, MergeEditorResolverContribution, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts index dcdca8a46cc..99af4f8e13a 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts @@ -9,7 +9,7 @@ import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/co import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { MultiDiffEditor } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor'; import { MultiDiffEditorInput, MultiDiffEditorResolverContribution, MultiDiffEditorSerializer } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput'; @@ -36,8 +36,7 @@ Registry.as(Extensions.Configuration) registerSingleton(IMultiDiffSourceResolverService, MultiDiffSourceResolverService, InstantiationType.Delayed); // Editor Integration -Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution2(MultiDiffEditorResolverContribution.ID, MultiDiffEditorResolverContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(MultiDiffEditorResolverContribution.ID, MultiDiffEditorResolverContribution, WorkbenchContributionInstantiation.BlockStartup); Registry.as(EditorExtensions.EditorPane) .registerEditorPane( @@ -50,5 +49,4 @@ Registry.as(EditorExtensions.EditorFactory) // SCM integration registerAction2(OpenScmGroupAction); -Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution2(ScmMultiDiffSourceResolverContribution.ID, ScmMultiDiffSourceResolverContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(ScmMultiDiffSourceResolverContribution.ID, ScmMultiDiffSourceResolverContribution, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts index ee531fe588e..09c8d7c4aaf 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts @@ -5,8 +5,7 @@ import { localize, localize2 } from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { NOTEBOOK_CELL_EDITABLE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { cellRangeToViewCells, expandCellRangesWithHiddenCells, getNotebookEditorFromEditorPane, ICellViewModel, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -406,8 +405,7 @@ export class NotebookClipboardContribution extends Disposable { } } -const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution2(NotebookClipboardContribution.ID, NotebookClipboardContribution, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(NotebookClipboardContribution.ID, NotebookClipboardContribution, WorkbenchContributionInstantiation.BlockRestore); const COPY_CELL_COMMAND_ID = 'notebook.cell.copy'; const CUT_CELL_COMMAND_ID = 'notebook.cell.cut'; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider.ts b/src/vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider.ts index f7e3c8c3b2d..3ae73e42ceb 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IMarkerListProvider, MarkerList, IMarkerNavigationService } from 'vs/editor/contrib/gotoError/browser/markerNavigationService'; import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; @@ -98,8 +97,6 @@ class NotebookMarkerDecorationContribution extends Disposable implements INotebo } } -Registry - .as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution2(MarkerListProvider.ID, MarkerListProvider, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(MarkerListProvider.ID, MarkerListProvider, WorkbenchContributionInstantiation.BlockRestore); registerNotebookContribution(NotebookMarkerDecorationContribution.id, NotebookMarkerDecorationContribution); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts b/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts index 2bfb3ffc06e..e5a0d065ac1 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts @@ -4,14 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { Registry } from 'vs/platform/registry/common/platform'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; export enum NotebookProfileType { default = 'default', @@ -126,6 +125,4 @@ export class NotebookProfileContribution extends Disposable { } } -const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution2(NotebookProfileContribution.ID, NotebookProfileContribution, WorkbenchContributionInstantiation.BlockRestore); - +registerWorkbenchContribution2(NotebookProfileContribution.ID, NotebookProfileContribution, WorkbenchContributionInstantiation.BlockRestore); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts b/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts index 8d7f17c2b75..5591817b999 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CellEditState, getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -65,5 +64,4 @@ class NotebookUndoRedoContribution extends Disposable { } } -const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution2(NotebookUndoRedoContribution.ID, NotebookUndoRedoContribution, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(NotebookUndoRedoContribution.ID, NotebookUndoRedoContribution, WorkbenchContributionInstantiation.BlockRestore); diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 79ddeebfc89..72f9ffe60e2 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -22,7 +22,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IEditorSerializer, IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; @@ -730,13 +730,13 @@ class NotebookAccessibleViewContribution extends Disposable { } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution2(NotebookContribution.ID, NotebookContribution, WorkbenchContributionInstantiation.BlockStartup); -workbenchContributionsRegistry.registerWorkbenchContribution2(CellContentProvider.ID, CellContentProvider, WorkbenchContributionInstantiation.BlockStartup); -workbenchContributionsRegistry.registerWorkbenchContribution2(CellInfoContentProvider.ID, CellInfoContentProvider, WorkbenchContributionInstantiation.BlockStartup); -workbenchContributionsRegistry.registerWorkbenchContribution2(RegisterSchemasContribution.ID, RegisterSchemasContribution, WorkbenchContributionInstantiation.BlockStartup); -workbenchContributionsRegistry.registerWorkbenchContribution2(NotebookEditorManager.ID, NotebookEditorManager, WorkbenchContributionInstantiation.BlockRestore); -workbenchContributionsRegistry.registerWorkbenchContribution2(NotebookLanguageSelectorScoreRefine.ID, NotebookLanguageSelectorScoreRefine, WorkbenchContributionInstantiation.BlockRestore); -workbenchContributionsRegistry.registerWorkbenchContribution2(SimpleNotebookWorkingCopyEditorHandler.ID, SimpleNotebookWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(NotebookContribution.ID, NotebookContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(CellContentProvider.ID, CellContentProvider, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(CellInfoContentProvider.ID, CellInfoContentProvider, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(RegisterSchemasContribution.ID, RegisterSchemasContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(NotebookEditorManager.ID, NotebookEditorManager, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(NotebookLanguageSelectorScoreRefine.ID, NotebookLanguageSelectorScoreRefine, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(SimpleNotebookWorkingCopyEditorHandler.ID, SimpleNotebookWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookAccessibilityHelpContribution, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookAccessibleViewContribution, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookVariables, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts index cee7bb59dfa..79e97bf85c9 100644 --- a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts +++ b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts @@ -9,7 +9,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { Extensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { Extensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorSerializer, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { PerfviewContrib, PerfviewInput } from 'vs/workbench/contrib/performance/browser/perfviewEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -19,7 +19,7 @@ import { InputLatencyContrib } from 'vs/workbench/contrib/performance/browser/in // -- startup performance view -Registry.as(Extensions.Workbench).registerWorkbenchContribution2( +registerWorkbenchContribution2( PerfviewContrib.ID, PerfviewContrib, WorkbenchContributionInstantiation.Lazy diff --git a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts index 846f995024c..fd6d7ed9da4 100644 --- a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts @@ -29,14 +29,12 @@ import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal import * as perf from 'vs/base/common/performance'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, getWorkbenchContribution } from 'vs/workbench/common/contributions'; export class PerfviewContrib { static get() { - return Registry. - as(WorkbenchExtensions.Workbench) - .getWorkbenchContribution(PerfviewContrib.ID); + return getWorkbenchContribution(PerfviewContrib.ID); } static readonly ID = 'workbench.contrib.perfview'; diff --git a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts index ed9a0c87e30..6d1349aa2ba 100644 --- a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts +++ b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts @@ -7,8 +7,7 @@ import * as nls from 'vs/nls'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/browser/statusbar'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { parseKeyboardLayoutDescription, areKeyboardLayoutsEqual, getKeyboardLayoutId, IKeyboardLayoutService, IKeyboardLayoutInfo } from 'vs/platform/keyboardLayout/common/keyboardLayout'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { KEYBOARD_LAYOUT_OPEN_PICKER } from 'vs/workbench/contrib/preferences/common/preferences'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { QuickPickInput, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; @@ -81,8 +80,7 @@ export class KeyboardLayoutPickerContribution extends Disposable implements IWor } } -const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution2(KeyboardLayoutPickerContribution.ID, KeyboardLayoutPickerContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(KeyboardLayoutPickerContribution.ID, KeyboardLayoutPickerContribution, WorkbenchContributionInstantiation.BlockStartup); interface LayoutQuickPickItem extends IQuickPickItem { layout: IKeyboardLayoutInfo; diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 65d8161b020..03b17a52c85 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -24,7 +24,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry, IEditorSerializer } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ResourceContextKey, RemoteNameContext, WorkbenchStateContext } from 'vs/workbench/common/contextkeys'; @@ -1287,8 +1287,8 @@ class SettingsEditorTitleContribution extends Disposable implements IWorkbenchCo } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution2(PreferencesActionsContribution.ID, PreferencesActionsContribution, WorkbenchContributionInstantiation.BlockStartup); -workbenchContributionsRegistry.registerWorkbenchContribution2(PreferencesContribution.ID, PreferencesContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(PreferencesActionsContribution.ID, PreferencesActionsContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(PreferencesContribution.ID, PreferencesContribution, WorkbenchContributionInstantiation.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(SettingsEditorTitleContribution, LifecyclePhase.Restored); registerEditorContribution(SettingsEditorContribution.ID, SettingsEditorContribution, EditorContributionInstantiation.AfterFirstRender); diff --git a/src/vs/workbench/contrib/remote/browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/browser/remote.contribution.ts index 195277a6f9a..2d1367296c8 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { ShowCandidateContribution } from 'vs/workbench/contrib/remote/browser/showCandidate'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -13,10 +13,10 @@ import { RemoteStatusIndicator } from 'vs/workbench/contrib/remote/browser/remot import { AutomaticPortForwarding, ForwardedPortsView, PortRestore } from 'vs/workbench/contrib/remote/browser/remoteExplorer'; const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution2(ShowCandidateContribution.ID, ShowCandidateContribution, WorkbenchContributionInstantiation.BlockRestore); -workbenchContributionsRegistry.registerWorkbenchContribution2(TunnelFactoryContribution.ID, TunnelFactoryContribution, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(ShowCandidateContribution.ID, ShowCandidateContribution, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(TunnelFactoryContribution.ID, TunnelFactoryContribution, WorkbenchContributionInstantiation.BlockRestore); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually); -workbenchContributionsRegistry.registerWorkbenchContribution2(RemoteStatusIndicator.ID, RemoteStatusIndicator, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(RemoteStatusIndicator.ID, RemoteStatusIndicator, WorkbenchContributionInstantiation.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(ForwardedPortsView, LifecyclePhase.Restored); workbenchContributionsRegistry.registerWorkbenchContribution(PortRestore, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(AutomaticPortForwarding, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 4b83aa17a35..797d50d8293 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILabelService, ResourceLabelFormatting } from 'vs/platform/label/common/label'; @@ -234,9 +234,9 @@ class InitialRemoteConnectionHealthContribution implements IWorkbenchContributio } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution2(LabelContribution.ID, LabelContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(LabelContribution.ID, LabelContribution, WorkbenchContributionInstantiation.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteChannelsContribution, LifecyclePhase.Restored); -workbenchContributionsRegistry.registerWorkbenchContribution2(RemoteInvalidWorkspaceDetector.ID, RemoteInvalidWorkspaceDetector, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(RemoteInvalidWorkspaceDetector.ID, RemoteInvalidWorkspaceDetector, WorkbenchContributionInstantiation.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(InitialRemoteConnectionHealthContribution, LifecyclePhase.Restored); const enableDiagnostics = true; diff --git a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts index b9d5e5a6a02..303173b1172 100644 --- a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts @@ -10,7 +10,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchContributionsExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILabelService } from 'vs/platform/label/common/label'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -186,10 +186,10 @@ class WSLContextKeyInitializer extends Disposable implements IWorkbenchContribut const workbenchContributionsRegistry = Registry.as(WorkbenchContributionsExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentDiagnosticListener, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteExtensionHostEnvironmentUpdater, LifecyclePhase.Eventually); -workbenchContributionsRegistry.registerWorkbenchContribution2(RemoteTelemetryEnablementUpdater.ID, RemoteTelemetryEnablementUpdater, WorkbenchContributionInstantiation.BlockRestore); -workbenchContributionsRegistry.registerWorkbenchContribution2(RemoteEmptyWorkbenchPresentation.ID, RemoteEmptyWorkbenchPresentation, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(RemoteTelemetryEnablementUpdater.ID, RemoteTelemetryEnablementUpdater, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(RemoteEmptyWorkbenchPresentation.ID, RemoteEmptyWorkbenchPresentation, WorkbenchContributionInstantiation.BlockRestore); if (isWindows) { - workbenchContributionsRegistry.registerWorkbenchContribution2(WSLContextKeyInitializer.ID, WSLContextKeyInitializer, WorkbenchContributionInstantiation.BlockRestore); + registerWorkbenchContribution2(WSLContextKeyInitializer.ID, WSLContextKeyInitializer, WorkbenchContributionInstantiation.BlockRestore); } Registry.as(ConfigurationExtensions.Configuration) diff --git a/src/vs/workbench/contrib/search/browser/replaceContributions.ts b/src/vs/workbench/contrib/search/browser/replaceContributions.ts index 55f97965231..a65ff6b0927 100644 --- a/src/vs/workbench/contrib/search/browser/replaceContributions.ts +++ b/src/vs/workbench/contrib/search/browser/replaceContributions.ts @@ -5,10 +5,9 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; import { ReplaceService, ReplacePreviewContentProvider } from 'vs/workbench/contrib/search/browser/replaceService'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; export function registerContributions(): void { registerSingleton(IReplaceService, ReplaceService, InstantiationType.Delayed); - Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(ReplacePreviewContentProvider.ID, ReplacePreviewContentProvider, WorkbenchContributionInstantiation.BlockStartup); + registerWorkbenchContribution2(ReplacePreviewContentProvider.ID, ReplacePreviewContentProvider, WorkbenchContributionInstantiation.BlockStartup); } diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index b926098f8c3..412bc161f3f 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -16,7 +16,7 @@ import { Extensions as QuickAccessExtensions, IQuickAccessRegistry } from 'vs/pl import { Registry } from 'vs/platform/registry/common/platform'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { defaultQuickAccessContextKeyValue } from 'vs/workbench/browser/quickaccess'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { Extensions as ViewExtensions, IViewContainersRegistry, IViewDescriptor, IViewDescriptorService, IViewsRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { GotoSymbolQuickAccessProvider } from 'vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess'; import { AnythingQuickAccessProvider } from 'vs/workbench/contrib/search/browser/anythingQuickAccess'; @@ -101,7 +101,7 @@ class RegisterSearchViewContribution implements IWorkbenchContribution { .registerConfigurationMigrations([{ key: 'search.location', migrateFn: (value: any) => ({ value: undefined }) }]); } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(RegisterSearchViewContribution.ID, RegisterSearchViewContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(RegisterSearchViewContribution.ID, RegisterSearchViewContribution, WorkbenchContributionInstantiation.BlockStartup); // Register Quick Access Handler const quickAccessRegistry = Registry.as(QuickAccessExtensions.Quickaccess); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 6fcc094c3d2..b4c7ebc14a1 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -18,7 +18,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IEditorSerializer, IEditorFactoryRegistry, EditorExtensions, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; @@ -100,8 +100,7 @@ class SearchEditorContribution implements IWorkbenchContribution { } } -const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution2(SearchEditorContribution.ID, SearchEditorContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(SearchEditorContribution.ID, SearchEditorContribution, WorkbenchContributionInstantiation.BlockStartup); //#endregion //#region Input Serializer @@ -601,5 +600,5 @@ class SearchEditorWorkingCopyEditorHandler extends Disposable implements IWorkbe } } -workbenchContributionsRegistry.registerWorkbenchContribution2(SearchEditorWorkingCopyEditorHandler.ID, SearchEditorWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(SearchEditorWorkingCopyEditorHandler.ID, SearchEditorWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); //#endregion diff --git a/src/vs/workbench/contrib/splash/browser/splash.contribution.ts b/src/vs/workbench/contrib/splash/browser/splash.contribution.ts index 171f6b0db71..052c1bc0f3a 100644 --- a/src/vs/workbench/contrib/splash/browser/splash.contribution.ts +++ b/src/vs/workbench/contrib/splash/browser/splash.contribution.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ISplashStorageService } from 'vs/workbench/contrib/splash/browser/splash'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { PartsSplash } from 'vs/workbench/contrib/splash/browser/partsSplash'; @@ -19,7 +18,7 @@ registerSingleton(ISplashStorageService, class SplashStorageService implements I } }, InstantiationType.Delayed); -Registry.as(Extensions.Workbench).registerWorkbenchContribution2( +registerWorkbenchContribution2( PartsSplash.ID, PartsSplash, WorkbenchContributionInstantiation.BlockStartup diff --git a/src/vs/workbench/contrib/splash/electron-sandbox/splash.contribution.ts b/src/vs/workbench/contrib/splash/electron-sandbox/splash.contribution.ts index a28b65f081c..903b14be46b 100644 --- a/src/vs/workbench/contrib/splash/electron-sandbox/splash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-sandbox/splash.contribution.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ISplashStorageService } from 'vs/workbench/contrib/splash/browser/splash'; import { INativeHostService } from 'vs/platform/native/common/native'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -22,7 +21,7 @@ class SplashStorageService implements ISplashStorageService { registerSingleton(ISplashStorageService, SplashStorageService, InstantiationType.Delayed); -Registry.as(Extensions.Workbench).registerWorkbenchContribution2( +registerWorkbenchContribution2( PartsSplash.ID, PartsSplash, WorkbenchContributionInstantiation.BlockStartup diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts index dac77a57959..4d66e0016ec 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts @@ -7,7 +7,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; import { Registry } from 'vs/platform/registry/common/platform'; import { ILocalPtyService, TerminalIpcChannels } from 'vs/platform/terminal/common/terminal'; -import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalNativeContribution } from 'vs/workbench/contrib/terminal/electron-sandbox/terminalNativeContribution'; import { ElectronTerminalProfileResolverService } from 'vs/workbench/contrib/terminal/electron-sandbox/terminalProfileResolverService'; @@ -23,5 +23,5 @@ const workbenchRegistry = Registry.as(Workbench // This contribution needs to be active during the Startup phase to be available when a remote resolver tries to open a local // terminal while connecting to the remote. -workbenchRegistry.registerWorkbenchContribution2(LocalTerminalBackendContribution.ID, LocalTerminalBackendContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(LocalTerminalBackendContribution.ID, LocalTerminalBackendContribution, WorkbenchContributionInstantiation.BlockStartup); workbenchRegistry.registerWorkbenchContribution(TerminalNativeContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/url/browser/url.contribution.ts b/src/vs/workbench/contrib/url/browser/url.contribution.ts index c9d31afa63d..d0484bd566c 100644 --- a/src/vs/workbench/contrib/url/browser/url.contribution.ts +++ b/src/vs/workbench/contrib/url/browser/url.contribution.ts @@ -11,7 +11,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { IURLService } from 'vs/platform/url/common/url'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ExternalUriResolverContribution } from 'vs/workbench/contrib/url/browser/externalUriResolver'; import { manageTrustedDomainSettingsCommand } from 'vs/workbench/contrib/url/browser/trustedDomains'; import { TrustedDomainsFileSystemProvider } from 'vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider'; @@ -66,12 +66,12 @@ Registry.as(WorkbenchExtensions.Workbench).regi OpenerValidatorContributions, LifecyclePhase.Restored ); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2( +registerWorkbenchContribution2( TrustedDomainsFileSystemProvider.ID, TrustedDomainsFileSystemProvider, WorkbenchContributionInstantiation.BlockRestore ); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2( +registerWorkbenchContribution2( ExternalUriResolverContribution.ID, ExternalUriResolverContribution, WorkbenchContributionInstantiation.BlockRestore diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts index a606a322302..9bd37764021 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, Extensions, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { UserDataProfilesWorkbenchContribution } from 'vs/workbench/contrib/userDataProfile/browser/userDataProfile'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import './userDataProfileActions'; import { UserDataProfilePreviewContribution } from 'vs/workbench/contrib/userDataProfile/browser/userDataProfilePreview'; const workbenchRegistry = Registry.as(Extensions.Workbench); -workbenchRegistry.registerWorkbenchContribution2(UserDataProfilesWorkbenchContribution.ID, UserDataProfilesWorkbenchContribution, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(UserDataProfilesWorkbenchContribution.ID, UserDataProfilesWorkbenchContribution, WorkbenchContributionInstantiation.BlockRestore); workbenchRegistry.registerWorkbenchContribution(UserDataProfilePreviewContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts index ed103f415a3..adc6fc4a54a 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IUserDataSyncUtilService, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync'; -import { Registry } from 'vs/platform/registry/common/platform'; import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; import { localize, localize2 } from 'vs/nls'; @@ -32,8 +31,7 @@ class UserDataSyncServicesContribution extends Disposable implements IWorkbenchC } } -const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution2(UserDataSyncServicesContribution.ID, UserDataSyncServicesContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(UserDataSyncServicesContribution.ID, UserDataSyncServicesContribution, WorkbenchContributionInstantiation.BlockStartup); registerAction2(class OpenSyncBackupsFolder extends Action2 { constructor() { diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts index 2c5caf3070f..702f7215239 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts @@ -11,7 +11,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -82,8 +82,7 @@ class WebviewPanelContribution extends Disposable implements IWorkbenchContribut } } -const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution2(WebviewPanelContribution.ID, WebviewPanelContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(WebviewPanelContribution.ID, WebviewPanelContribution, WorkbenchContributionInstantiation.BlockStartup); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer( WebviewEditorInputSerializer.ID, diff --git a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.ts b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.ts index 0057bd498d7..d7445bb3674 100644 --- a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.ts +++ b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.ts @@ -13,7 +13,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { MenuRegistry, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; -import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IEditorPaneRegistry, EditorPaneDescriptor } from 'vs/workbench/browser/editor'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -29,8 +29,7 @@ registerAction2(EditorWalkThroughAction); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(EditorWalkThroughInputSerializer.ID, EditorWalkThroughInputSerializer); -Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution2(WalkThroughSnippetContentProvider.ID, WalkThroughSnippetContentProvider, WorkbenchContributionInstantiation.BlockRestore /* cannot be on a later phase because an editor might need this on startup */); +registerWorkbenchContribution2(WalkThroughSnippetContentProvider.ID, WalkThroughSnippetContentProvider, WorkbenchContributionInstantiation.BlockRestore /* cannot be on a later phase because an editor might need this on startup */); KeybindingsRegistry.registerCommandAndKeybindingRule(WalkThroughArrowUp); diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index d8fd9800144..74f18663d3a 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -14,7 +14,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { Severity } from 'vs/platform/notification/common/notification'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService, IWorkspaceTrustRequestService, workspaceTrustToString, WorkspaceTrustUriResponse } from 'vs/platform/workspace/common/workspaceTrust'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Codicon } from 'vs/base/common/codicons'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -636,7 +636,7 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon //#endregion } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(WorkspaceTrustRequestHandler.ID, WorkspaceTrustRequestHandler, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(WorkspaceTrustRequestHandler.ID, WorkspaceTrustRequestHandler, WorkbenchContributionInstantiation.BlockRestore); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceTrustUXHandler, LifecyclePhase.Restored); diff --git a/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts b/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts index 95faf4d10fe..2eb7806421f 100644 --- a/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts +++ b/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts @@ -11,8 +11,7 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILogService } from 'vs/platform/log/common/log'; import { INativeHostService } from 'vs/platform/native/common/native'; import { IProductService } from 'vs/platform/product/common/productService'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IDialogsModel, IDialogViewItem } from 'vs/workbench/common/dialogs'; import { BrowserDialogHandler } from 'vs/workbench/browser/parts/dialogs/dialogHandler'; import { NativeDialogHandler } from 'vs/workbench/electron-sandbox/parts/dialogs/dialogHandler'; @@ -108,5 +107,4 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC } } -const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution2(DialogHandlerContribution.ID, DialogHandlerContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(DialogHandlerContribution.ID, DialogHandlerContribution, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts b/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts index 130c5561855..6fcab421496 100644 --- a/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts @@ -8,12 +8,11 @@ import { isWindows, isLinux } from 'vs/base/common/platform'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Registry } from 'vs/platform/registry/common/platform'; import { AccessibilityService } from 'vs/platform/accessibility/browser/accessibilityService'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { INativeHostService } from 'vs/platform/native/common/native'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; @@ -89,5 +88,5 @@ class LinuxAccessibilityContribution implements IWorkbenchContribution { } if (isLinux) { - Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(LinuxAccessibilityContribution.ID, LinuxAccessibilityContribution, WorkbenchContributionInstantiation.BlockRestore); + registerWorkbenchContribution2(LinuxAccessibilityContribution.ID, LinuxAccessibilityContribution, WorkbenchContributionInstantiation.BlockRestore); } diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 5a8c05e7eca..2d4136bdc0e 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -27,7 +27,7 @@ import { mark } from 'vs/base/common/performance'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -1367,7 +1367,7 @@ class UpdateExperimentalSettingsDefaults extends Disposable implements IWorkbenc const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(RegisterConfigurationSchemasContribution, LifecyclePhase.Restored); workbenchContributionsRegistry.registerWorkbenchContribution(ResetConfigurationDefaultsOverridesCache, LifecyclePhase.Eventually); -workbenchContributionsRegistry.registerWorkbenchContribution2(UpdateExperimentalSettingsDefaults.ID, UpdateExperimentalSettingsDefaults, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(UpdateExperimentalSettingsDefaults.ID, UpdateExperimentalSettingsDefaults, WorkbenchContributionInstantiation.BlockRestore); const configurationRegistry = Registry.as(Extensions.Configuration); configurationRegistry.registerConfiguration({ diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index 3889556e786..11e368bfaba 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -19,8 +19,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ActivationKind, IExtensionService, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; @@ -426,8 +425,7 @@ class ExtensionUrlBootstrapHandler implements IWorkbenchContribution, IURLHandle } } -const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution2(ExtensionUrlBootstrapHandler.ID, ExtensionUrlBootstrapHandler, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(ExtensionUrlBootstrapHandler.ID, ExtensionUrlBootstrapHandler, WorkbenchContributionInstantiation.BlockRestore); class ManageAuthorizedExtensionURIsAction extends Action2 { diff --git a/src/vs/workbench/services/remote/browser/remoteAgentService.ts b/src/vs/workbench/services/remote/browser/remoteAgentService.ts index 68e0f1ab397..343e989c599 100644 --- a/src/vs/workbench/services/remote/browser/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/browser/remoteAgentService.ts @@ -13,8 +13,7 @@ import { ISignService } from 'vs/platform/sign/common/sign'; import { ILogService } from 'vs/platform/log/common/log'; import { Severity } from 'vs/platform/notification/common/notification'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; @@ -68,5 +67,4 @@ class RemoteConnectionFailureNotificationContribution implements IWorkbenchContr } -const workbenchRegistry = Registry.as(Extensions.Workbench); -workbenchRegistry.registerWorkbenchContribution2(RemoteConnectionFailureNotificationContribution.ID, RemoteConnectionFailureNotificationContribution, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(RemoteConnectionFailureNotificationContribution.ID, RemoteConnectionFailureNotificationContribution, WorkbenchContributionInstantiation.BlockRestore); diff --git a/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts b/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts index 0fd96b56dc2..e88f59ef2d0 100644 --- a/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts @@ -12,8 +12,7 @@ import { ISignService } from 'vs/platform/sign/common/sign'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INativeHostService } from 'vs/platform/native/common/native'; import { URI } from 'vs/base/common/uri'; @@ -93,5 +92,4 @@ class RemoteConnectionFailureNotificationContribution implements IWorkbenchContr } -const workbenchRegistry = Registry.as(Extensions.Workbench); -workbenchRegistry.registerWorkbenchContribution2(RemoteConnectionFailureNotificationContribution.ID, RemoteConnectionFailureNotificationContribution, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(RemoteConnectionFailureNotificationContribution.ID, RemoteConnectionFailureNotificationContribution, WorkbenchContributionInstantiation.BlockRestore); diff --git a/src/vs/workbench/services/textMate/browser/textMateTokenizationFeature.contribution.ts b/src/vs/workbench/services/textMate/browser/textMateTokenizationFeature.contribution.ts index d1cb9d068fb..2920489e6e8 100644 --- a/src/vs/workbench/services/textMate/browser/textMateTokenizationFeature.contribution.ts +++ b/src/vs/workbench/services/textMate/browser/textMateTokenizationFeature.contribution.ts @@ -6,8 +6,7 @@ import { registerSingleton, InstantiationType } from 'vs/platform/instantiation/common/extensions'; import { ITextMateTokenizationService } from 'vs/workbench/services/textMate/browser/textMateTokenizationFeature'; import { TextMateTokenizationFeature } from 'vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions, WorkbenchContributionInstantiation } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; /** * Makes sure the ITextMateTokenizationService is instantiated @@ -23,5 +22,4 @@ class TextMateTokenizationInstantiator implements IWorkbenchContribution { registerSingleton(ITextMateTokenizationService, TextMateTokenizationFeature, InstantiationType.Eager); -const workbenchRegistry = Registry.as(Extensions.Workbench); -workbenchRegistry.registerWorkbenchContribution2(TextMateTokenizationInstantiator.ID, TextMateTokenizationInstantiator, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(TextMateTokenizationInstantiator.ID, TextMateTokenizationInstantiator, WorkbenchContributionInstantiation.BlockRestore); diff --git a/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts index 057a70bb3b4..d2b8085e932 100644 --- a/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts @@ -11,8 +11,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { joinPath } from 'vs/base/common/resources'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { BrowserWorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/browser/workingCopyBackupTracker'; export class BrowserWorkingCopyBackupService extends WorkingCopyBackupService { @@ -31,4 +30,4 @@ export class BrowserWorkingCopyBackupService extends WorkingCopyBackupService { registerSingleton(IWorkingCopyBackupService, BrowserWorkingCopyBackupService, InstantiationType.Eager); // Register Backup Tracker -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(BrowserWorkingCopyBackupTracker.ID, BrowserWorkingCopyBackupTracker, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(BrowserWorkingCopyBackupTracker.ID, BrowserWorkingCopyBackupTracker, WorkbenchContributionInstantiation.BlockStartup); diff --git a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts index ea764cf379c..90ec57c0376 100644 --- a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts @@ -11,8 +11,7 @@ import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/com import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { NativeWorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker'; @@ -42,4 +41,4 @@ export class NativeWorkingCopyBackupService extends WorkingCopyBackupService { registerSingleton(IWorkingCopyBackupService, NativeWorkingCopyBackupService, InstantiationType.Eager); // Register Backup Tracker -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution2(NativeWorkingCopyBackupTracker.ID, NativeWorkingCopyBackupTracker, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(NativeWorkingCopyBackupTracker.ID, NativeWorkingCopyBackupTracker, WorkbenchContributionInstantiation.BlockStartup); From 442c133fe92b5e2606c8242caae4e68938faccc7 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 1 Feb 2024 09:43:05 +0100 Subject: [PATCH 0812/1897] Try to fix build pipeline (#203951) --- build/azure-pipelines/alpine/cli-build-alpine.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build/azure-pipelines/alpine/cli-build-alpine.yml b/build/azure-pipelines/alpine/cli-build-alpine.yml index 0d179b82db6..5d4e79424d2 100644 --- a/build/azure-pipelines/alpine/cli-build-alpine.yml +++ b/build/azure-pipelines/alpine/cli-build-alpine.yml @@ -7,6 +7,9 @@ parameters: default: false - name: VSCODE_QUALITY type: string + - name: VSCODE_CHECK_ONLY + type: boolean + default: false steps: - task: NodeTool@0 From 11617766f0dde3050c6f5f958a2977db1cd1a308 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 1 Feb 2024 09:48:38 +0100 Subject: [PATCH 0813/1897] placing the for loop inside of the suite directly --- .../test/browser/singleTextEdit.test.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts index 5cede383e1d..f2a9ac4e052 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts @@ -12,9 +12,8 @@ import { generateRandomDisjointEdits, generateRandomMultilineString as randomMul suite('Single Text Edit', () => { ensureNoDisposablesAreLeakedInTestSuite(); - test('testing getNewRanges', () => { - - function testInverseEdits() { + for (let i = 0; i < 10; i++) { + test(`testing getNewRanges ${i}`, () => { const randomText = randomMultilineString(10); const model = createTextModel(randomText); @@ -26,10 +25,7 @@ suite('Single Text Edit', () => { assert.deepStrictEqual(model.getValue(), randomText); model.dispose(); - } + }); + } - for (let i = 0; i < 1; i++) { - testInverseEdits(); - } - }); }); From 8ba046adbeecbbabac874d1a9b6b1a41f8cedf19 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 1 Feb 2024 11:37:19 +0100 Subject: [PATCH 0814/1897] print heap stats instead of memory usage (#203959) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * print heap stats instead of memory usage * ensure no hanging on to service --------- Co-authored-by: João Moreno --- build/lib/mangle/index.js | 15 +++++++-------- build/lib/mangle/index.ts | 13 +++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/build/lib/mangle/index.js b/build/lib/mangle/index.js index 14137c0db31..bb6b414e845 100644 --- a/build/lib/mangle/index.js +++ b/build/lib/mangle/index.js @@ -5,6 +5,7 @@ *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.Mangler = void 0; +const v8 = require("node:v8"); const fs = require("fs"); const path = require("path"); const process_1 = require("process"); @@ -293,19 +294,17 @@ const skippedExportMangledSymbols = [ class DeclarationData { fileName; node; - service; replacementName; - constructor(fileName, node, service, fileIdents) { + constructor(fileName, node, fileIdents) { this.fileName = fileName; this.node = node; - this.service = service; // Todo: generate replacement names based on usage count, with more used names getting shorter identifiers this.replacementName = fileIdents.next(); } - get locations() { + getLocations(service) { if (ts.isVariableDeclaration(this.node)) { // If the const aliases any types, we need to rename those too - const definitionResult = this.service.getDefinitionAndBoundSpan(this.fileName, this.node.name.getStart()); + const definitionResult = service.getDefinitionAndBoundSpan(this.fileName, this.node.name.getStart()); if (definitionResult?.definitions && definitionResult.definitions.length > 1) { return definitionResult.definitions.map(x => ({ fileName: x.fileName, offset: x.textSpan.start })); } @@ -404,7 +403,7 @@ class Mangler { if (isInAmbientContext(node)) { return; } - this.allExportedSymbols.add(new DeclarationData(node.getSourceFile().fileName, node, service, fileIdents)); + this.allExportedSymbols.add(new DeclarationData(node.getSourceFile().fileName, node, fileIdents)); } } ts.forEachChild(node, visit); @@ -528,7 +527,7 @@ class Mangler { continue; } const newText = data.replacementName; - for (const { fileName, offset } of data.locations) { + for (const { fileName, offset } of data.getLocations(service)) { queueRename(fileName, offset, newText); } } @@ -615,7 +614,7 @@ class Mangler { } service.dispose(); this.renameWorkerPool.terminate(); - this.log(`Done: ${savedBytes / 1000}kb saved, memory-usage: ${JSON.stringify(process.memoryUsage())}`); + this.log(`Done: ${savedBytes / 1000}kb saved, memory-usage: ${JSON.stringify(v8.getHeapStatistics())}`); return result; } } diff --git a/build/lib/mangle/index.ts b/build/lib/mangle/index.ts index ec023535975..4a7544f162b 100644 --- a/build/lib/mangle/index.ts +++ b/build/lib/mangle/index.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as v8 from 'node:v8'; import * as fs from 'fs'; import * as path from 'path'; import { argv } from 'process'; @@ -338,17 +339,16 @@ class DeclarationData { constructor( 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(); } - get locations(): Iterable<{ fileName: string; offset: number }> { + getLocations(service: ts.LanguageService): Iterable<{ fileName: string; offset: number }> { if (ts.isVariableDeclaration(this.node)) { // If the const aliases any types, we need to rename those too - const definitionResult = this.service.getDefinitionAndBoundSpan(this.fileName, this.node.name.getStart()); + const definitionResult = service.getDefinitionAndBoundSpan(this.fileName, this.node.name.getStart()); if (definitionResult?.definitions && definitionResult.definitions.length > 1) { return definitionResult.definitions.map(x => ({ fileName: x.fileName, offset: x.textSpan.start })); } @@ -471,7 +471,7 @@ export class Mangler { return; } - this.allExportedSymbols.add(new DeclarationData(node.getSourceFile().fileName, node, service, fileIdents)); + this.allExportedSymbols.add(new DeclarationData(node.getSourceFile().fileName, node, fileIdents)); } } @@ -620,7 +620,7 @@ export class Mangler { } const newText = data.replacementName; - for (const { fileName, offset } of data.locations) { + for (const { fileName, offset } of data.getLocations(service)) { queueRename(fileName, offset, newText); } } @@ -723,7 +723,8 @@ export class Mangler { service.dispose(); this.renameWorkerPool.terminate(); - this.log(`Done: ${savedBytes / 1000}kb saved, memory-usage: ${JSON.stringify(process.memoryUsage())}`); + + this.log(`Done: ${savedBytes / 1000}kb saved, memory-usage: ${JSON.stringify(v8.getHeapStatistics())}`); return result; } } From f0e9f6e5830af3ca5d805a9d77cda675110724c7 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 1 Feb 2024 11:51:54 +0100 Subject: [PATCH 0815/1897] refatcoring the code --- .../browser/inlineCompletionsModel.ts | 4 +- .../browser/singleTextEdit.ts | 58 +----------------- .../inlineCompletions/browser/utils.ts | 59 +++++++++++++++++++ .../{singleTextEdit.test.ts => utils.test.ts} | 14 +++-- .../inlineCompletions/test/browser/utils.ts | 38 ------------ .../combineTextEditInfos.test.ts | 6 +- 6 files changed, 73 insertions(+), 106 deletions(-) rename src/vs/editor/contrib/inlineCompletions/test/browser/{singleTextEdit.test.ts => utils.test.ts} (64%) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 3bab5e1947d..2c8d91924bd 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -20,9 +20,9 @@ import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { IFeatureDebounceInformation } from 'vs/editor/common/services/languageFeatureDebounce'; import { GhostText, GhostTextOrReplacement, ghostTextOrReplacementEquals } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; import { InlineCompletionWithUpdatedRange, InlineCompletionsSource } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource'; -import { SingleTextEdit, getNewRanges } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; +import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; import { SuggestItemInfo } from 'vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider'; -import { addPositions, lengthOfText } from 'vs/editor/contrib/inlineCompletions/browser/utils'; +import { addPositions, getNewRanges, lengthOfText } from 'vs/editor/contrib/inlineCompletions/browser/utils'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts index e605ba136b7..c958fcdb605 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts @@ -4,11 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { IDiffChange, LcsDiff } from 'vs/base/common/diff/diff'; -import { commonPrefixLength, getLeadingWhitespace, splitLines } from 'vs/base/common/strings'; +import { commonPrefixLength, getLeadingWhitespace } from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; -import { TextModel } from 'vs/editor/common/model/textModel'; import { GhostText, GhostTextPart } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; import { addPositions, lengthOfText } from 'vs/editor/contrib/inlineCompletions/browser/utils'; @@ -247,58 +246,3 @@ function smartDiff(originalValue: string, newValue: string, smartBracketMatching return new LcsDiff({ getElements: () => elements1 }, { getElements: () => elements2 }).ComputeDiff(false).changes; } - -/** - * Given some text edits, this function finds the new ranges of the editted text post application of all edits. - * Assumes that the edit ranges are disjoint - * @param edits edits applied - * @returns new ranges post edits for every edit - */ -export function getNewRanges(edits: SingleTextEdit[]): Range[] { - - if (edits.length === 0) { - return []; - } - - const sortIndices = Array.from(edits.keys()).sort((a, b) => - Range.compareRangesUsingStarts(edits[a].range, edits[b].range) - ); - const ranges: Range[] = []; - let previousEditEndLineNumber = 0; - let positionOffset = new Position(0, 0); - - for (const index of sortIndices) { - const edit = edits[index]; - const splitText = splitLines(edit.text!); - const rangeStart = edit.range.getStartPosition().delta( - positionOffset.lineNumber, - edit.range.startLineNumber === previousEditEndLineNumber ? positionOffset.column : 0 - ); - const rangeEnd = addPositions( - rangeStart, - lengthOfText(edit.text) - ); - ranges.push(Range.fromPositions(rangeStart, rangeEnd)); - previousEditEndLineNumber = edit.range.endLineNumber; - positionOffset = positionOffset.delta( - splitText.length - edit.range.endLineNumber + edit.range.startLineNumber - 1, - rangeEnd.column - edit.range.endColumn - positionOffset.column - ); - } - return ranges.map((_, index) => ranges[sortIndices.indexOf(index)]); -} - -/** - * Given a text model and edits, this function finds the inverse text edits - * @param model model on which to apply the edits - * @param edits edits applied - * @returns inverse edits - */ -export function inverseEdits(model: TextModel, edits: SingleTextEdit[]): SingleTextEdit[] { - const newRanges = getNewRanges(edits); - const inverseEdits: SingleTextEdit[] = []; - for (let i = 0; i < edits.length; i++) { - inverseEdits.push(new SingleTextEdit(newRanges[i], model.getValueInRange(edits[i].range))); - } - return inverseEdits; -} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts index a1876c8a6fc..6e2fa18b8c8 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts @@ -6,10 +6,13 @@ import { BugIndicatingError } from 'vs/base/common/errors'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IObservable, autorunOpts } from 'vs/base/common/observable'; +import { splitLines } from 'vs/base/common/strings'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; +import { TextModel } from 'vs/editor/common/model/textModel'; export function applyEdits(text: string, edits: { range: IRange; text: string }[]): string { const transformer = new PositionOffsetTransformer(text); @@ -106,3 +109,59 @@ export function lengthOfText(text: string): Position { } return new Position(line, column); } + +/** + * Given some text edits, this function finds the new ranges of the editted text post application of all edits. + * Assumes that the edit ranges are disjoint + * @param edits edits applied + * @returns new ranges post edits for every edit + */ +export function getNewRanges(edits: ISingleEditOperation[]): Range[] { + + if (edits.length === 0) { + return []; + } + + const sortIndices = Array.from(edits.keys()).sort((a, b) => + Range.compareRangesUsingStarts(edits[a].range, edits[b].range) + ); + const ranges: Range[] = []; + let previousEditEndLineNumber = 0; + let positionOffset = new Position(0, 0); + + for (const index of sortIndices) { + const edit = edits[index]; + const text = edit.text ?? ''; + const rangeStart = Position.lift({ + lineNumber: edit.range.startLineNumber + positionOffset.lineNumber, + column: edit.range.startColumn + (edit.range.startLineNumber === previousEditEndLineNumber ? positionOffset.column : 0) + }); + const rangeEnd = addPositions( + rangeStart, + lengthOfText(text) + ); + ranges.push(Range.fromPositions(rangeStart, rangeEnd)); + const splitText = splitLines(text); + previousEditEndLineNumber = edit.range.endLineNumber; + positionOffset = positionOffset.delta( + splitText.length - edit.range.endLineNumber + edit.range.startLineNumber - 1, + rangeEnd.column - edit.range.endColumn - positionOffset.column + ); + } + return ranges.map((_, index) => ranges[sortIndices.indexOf(index)]); +} + +/** + * Given a text model and edits, this function finds the inverse text edits + * @param model model on which to apply the edits + * @param edits edits applied + * @returns inverse edits + */ +export function inverseEdits(model: TextModel, edits: ISingleEditOperation[]): ISingleEditOperation[] { + const newRanges = getNewRanges(edits); + const inverseEdits: ISingleEditOperation[] = []; + for (let i = 0; i < edits.length; i++) { + inverseEdits.push({ range: newRanges[i], text: model.getValueInRange(edits[i].range) }); + } + return inverseEdits; +} diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts similarity index 64% rename from src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts rename to src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts index f2a9ac4e052..680d61ff32f 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/singleTextEdit.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts @@ -5,19 +5,21 @@ import * as assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { inverseEdits } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; -import { generateRandomDisjointEdits, generateRandomMultilineString as randomMultilineString } from 'vs/editor/contrib/inlineCompletions/test/browser/utils'; +import { generateRandomMultilineString as randomMultilineString } from 'vs/editor/contrib/inlineCompletions/test/browser/utils'; +import { MersenneTwister, getRandomEditInfos, toEdit, } from 'vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test'; +import { inverseEdits } from 'vs/editor/contrib/inlineCompletions/browser/utils'; -suite('Single Text Edit', () => { +suite('getNewRanges', () => { ensureNoDisposablesAreLeakedInTestSuite(); - for (let i = 0; i < 10; i++) { - test(`testing getNewRanges ${i}`, () => { + for (let seed = 0; seed < 20; seed++) { + test(`test ${seed}`, () => { const randomText = randomMultilineString(10); const model = createTextModel(randomText); - const edits = generateRandomDisjointEdits(model, 3); + const rng = new MersenneTwister(seed); + const edits = getRandomEditInfos(model, rng.nextIntRange(1, 4), rng).map(e => toEdit(e)); const invEdits = inverseEdits(model, edits); model.applyEdits(edits); diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts index 806b7ce5cb7..30f1e7c0cc6 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts @@ -13,9 +13,6 @@ import { InlineCompletion, InlineCompletionContext, InlineCompletionsProvider } import { ITestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel'; import { autorun } from 'vs/base/common/observable'; -import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; -import { Range } from 'vs/editor/common/core/range'; -import { TextModel } from 'vs/editor/common/model/textModel'; export class MockInlineCompletionsProvider implements InlineCompletionsProvider { private returnValue: InlineCompletion[] = []; @@ -153,38 +150,3 @@ export function generateRandomMultilineString(numberOfLines: number, maximumLeng } return randomText; } - -function generateRandomIntegers(numberOfIntegers: number, minimum: number, maximum: number, unique: boolean = false): number[] { - if (maximum - minimum + 1 < numberOfIntegers) { - throw new Error('Too many integers requested'); - } - const integers = []; - while (integers.length < numberOfIntegers) { - const integer = Math.floor(Math.random() * (maximum - minimum + 1)) + minimum; - if (unique) { - if (integers.indexOf(integer) === -1) { - integers.push(integer); - } - } else { - integers.push(integer); - } - } - return integers; -} - -export function generateRandomDisjointEdits(model: TextModel, numberOfEdits: number) { - const numberOfLines = model.getLineCount(); - if (2 * numberOfEdits > numberOfLines) { - throw new Error('Too many edits specified'); - } - const edits = []; - // Generate unique offsets, sort them and convert them to range boundary positions - const offsetBoundaries = generateRandomIntegers(2 * numberOfEdits, 0, model.getValueLength() - 1, true).sort((a, b) => a - b); - for (let i = 0; i < numberOfEdits; i++) { - const startPosition = model.getPositionAt(offsetBoundaries[2 * i]); - const endPosition = model.getPositionAt(offsetBoundaries[2 * i + 1]); - const numberOfLinesInText = generateRandomIntegers(1, 0, 3)[0]; - edits.push(new SingleTextEdit(Range.fromPositions(startPosition, endPosition), generateRandomMultilineString(numberOfLinesInText, 10))); - } - return edits; -} diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts index 5ab26b65dff..8ce6413ceda 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts @@ -58,7 +58,7 @@ function runTest(seed: number) { textModelS2.dispose(); } -function getRandomEditInfos(textModel: TextModel, count: number, rng: MersenneTwister): TextEditInfo[] { +export function getRandomEditInfos(textModel: TextModel, count: number, rng: MersenneTwister): TextEditInfo[] { const edits: TextEditInfo[] = []; let i = 0; for (let j = 0; j < count; j++) { @@ -79,7 +79,7 @@ function getRandomEdit(textModel: TextModel, rangeOffsetStart: number, rng: Mers return new TextEditInfo(positionToLength(textModel.getPositionAt(offsetStart)), positionToLength(textModel.getPositionAt(offsetEnd)), toLength(lineCount, columnCount)); } -function toEdit(editInfo: TextEditInfo): ISingleEditOperation { +export function toEdit(editInfo: TextEditInfo): ISingleEditOperation { const l = lengthToObj(editInfo.newLength); let text = ''; @@ -100,7 +100,7 @@ function toEdit(editInfo: TextEditInfo): ISingleEditOperation { } // Generated by copilot -class MersenneTwister { +export class MersenneTwister { private readonly mt = new Array(624); private index = 0; From 62dbeb60638166e4875311a11ac6c1accd8f28d1 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 1 Feb 2024 12:05:56 +0100 Subject: [PATCH 0816/1897] rewriting the function --- .../contrib/inlineCompletions/browser/utils.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts index 6e2fa18b8c8..d2bff394b20 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts @@ -127,14 +127,15 @@ export function getNewRanges(edits: ISingleEditOperation[]): Range[] { ); const ranges: Range[] = []; let previousEditEndLineNumber = 0; - let positionOffset = new Position(0, 0); + let lineOffset = 0; + let columnOffset = 0; for (const index of sortIndices) { const edit = edits[index]; const text = edit.text ?? ''; const rangeStart = Position.lift({ - lineNumber: edit.range.startLineNumber + positionOffset.lineNumber, - column: edit.range.startColumn + (edit.range.startLineNumber === previousEditEndLineNumber ? positionOffset.column : 0) + lineNumber: edit.range.startLineNumber + lineOffset, + column: edit.range.startColumn + (edit.range.startLineNumber === previousEditEndLineNumber ? columnOffset : 0) }); const rangeEnd = addPositions( rangeStart, @@ -142,11 +143,9 @@ export function getNewRanges(edits: ISingleEditOperation[]): Range[] { ); ranges.push(Range.fromPositions(rangeStart, rangeEnd)); const splitText = splitLines(text); + lineOffset += splitText.length - edit.range.endLineNumber + edit.range.startLineNumber - 1; + columnOffset = rangeEnd.column - edit.range.endColumn; previousEditEndLineNumber = edit.range.endLineNumber; - positionOffset = positionOffset.delta( - splitText.length - edit.range.endLineNumber + edit.range.startLineNumber - 1, - rangeEnd.column - edit.range.endColumn - positionOffset.column - ); } return ranges.map((_, index) => ranges[sortIndices.indexOf(index)]); } From 6b0281842f88b0aebac259876b8035fd47f81cc4 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 1 Feb 2024 12:42:44 +0100 Subject: [PATCH 0817/1897] using the mersenne twister class --- .../inlineCompletions/test/browser/utils.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts index 30f1e7c0cc6..659ddda61ee 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts @@ -13,6 +13,7 @@ import { InlineCompletion, InlineCompletionContext, InlineCompletionsProvider } import { ITestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel'; import { autorun } from 'vs/base/common/observable'; +import { MersenneTwister } from 'vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test'; export class MockInlineCompletionsProvider implements InlineCompletionsProvider { private returnValue: InlineCompletion[] = []; @@ -132,21 +133,24 @@ export class GhostTextContext extends Disposable { } } -function generateRandomSimpleString(stringLength: number): string { - let randomText: string = ''; +function generateRandomSimpleString(seed: number, stringLength: number): string { + const rng = new MersenneTwister(seed); const possibleCharacters: string = ' abcdefghijklmnopqrstuvwxyz0123456789'; + let randomText: string = ''; for (let i = 0; i < stringLength; i++) { - randomText += possibleCharacters.charAt(Math.floor(Math.random() * possibleCharacters.length)); + const characterIndex = rng.nextIntRange(0, possibleCharacters.length + 1); + randomText += possibleCharacters.charAt(characterIndex); } return randomText; } -export function generateRandomMultilineString(numberOfLines: number, maximumLengthOfLines: number = 20): string { +export function generateRandomMultilineString(seed: number, numberOfLines: number, maximumLengthOfLines: number = 20): string { + const rng = new MersenneTwister(seed); let randomText: string = ''; for (let i = 0; i < numberOfLines; i++) { - const lengthOfLine = Math.floor(Math.random() * maximumLengthOfLines); - randomText += generateRandomSimpleString(lengthOfLine) + '\n'; + const lengthOfLine = rng.nextIntRange(0, maximumLengthOfLines + 1); + randomText += generateRandomSimpleString(seed, lengthOfLine) + '\n'; } return randomText; } From 6347918fb3ce52ddd58efb69123e4b49e849d838 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 1 Feb 2024 12:43:17 +0100 Subject: [PATCH 0818/1897] removing one otherwise there would be indexing error --- src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts index 659ddda61ee..63f49835b5f 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts @@ -138,7 +138,7 @@ function generateRandomSimpleString(seed: number, stringLength: number): string const possibleCharacters: string = ' abcdefghijklmnopqrstuvwxyz0123456789'; let randomText: string = ''; for (let i = 0; i < stringLength; i++) { - const characterIndex = rng.nextIntRange(0, possibleCharacters.length + 1); + const characterIndex = rng.nextIntRange(0, possibleCharacters.length); randomText += possibleCharacters.charAt(characterIndex); } From 9554c7af48012a641b7d924428363120824bf9e5 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 1 Feb 2024 14:16:26 +0100 Subject: [PATCH 0819/1897] wip --- .../browser/inlineCompletionsModel.ts | 6 ++-- .../inlineCompletions/browser/utils.ts | 36 +++++++++++++++---- .../test/browser/utils.test.ts | 2 +- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 2c8d91924bd..52d26629ed5 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -456,8 +456,10 @@ export class InlineCompletionsModel extends Disposable { return new SingleTextEdit(range, secondaryEditText); }) ]; - const newRanges = getNewRanges(edits); - const editorSelections = newRanges.map(range => Selection.fromPositions(range.getEndPosition())); + const sortPerm = Permutation.createSortPermutation(edits); + const sortedRanges = getNewRanges(sortPerm.applyInPlace(edits)); + const ranges = sortPerm.inverse().applyInPlace(sortedRanges); + const editorSelections = ranges.map(range => Selection.fromPositions(range.getEndPosition())); return { edits, diff --git a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts index d2bff394b20..0ac489eb897 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts @@ -112,7 +112,7 @@ export function lengthOfText(text: string): Position { /** * Given some text edits, this function finds the new ranges of the editted text post application of all edits. - * Assumes that the edit ranges are disjoint + * Assumes that the edit ranges are disjoint and they are sorted in the order of the ranges * @param edits edits applied * @returns new ranges post edits for every edit */ @@ -121,17 +121,22 @@ export function getNewRanges(edits: ISingleEditOperation[]): Range[] { if (edits.length === 0) { return []; } + // Check if the edits ranges are sorted and disjoint + for (let i = 1; i < edits.length; i++) { + if (Position.isBeforeOrEqual( + Range.lift(edits[i].range).getStartPosition(), + Range.lift(edits[i - 1].range).getEndPosition()) + ) { + throw new Error('Edits are not sorted and disjoint.'); + } + } - const sortIndices = Array.from(edits.keys()).sort((a, b) => - Range.compareRangesUsingStarts(edits[a].range, edits[b].range) - ); const ranges: Range[] = []; let previousEditEndLineNumber = 0; let lineOffset = 0; let columnOffset = 0; - for (const index of sortIndices) { - const edit = edits[index]; + for (const edit of edits) { const text = edit.text ?? ''; const rangeStart = Position.lift({ lineNumber: edit.range.startLineNumber + lineOffset, @@ -147,7 +152,7 @@ export function getNewRanges(edits: ISingleEditOperation[]): Range[] { columnOffset = rangeEnd.column - edit.range.endColumn; previousEditEndLineNumber = edit.range.endLineNumber; } - return ranges.map((_, index) => ranges[sortIndices.indexOf(index)]); + return ranges; } /** @@ -164,3 +169,20 @@ export function inverseEdits(model: TextModel, edits: ISingleEditOperation[]): I } return inverseEdits; } + +/** + * Utility class which can be used to find the sort permutation of an array + */ +export class Permutation { + + constructor(public readonly indexMap: number[]) { } + + public static createSortPermutation(arr: readonly T[], compareFn: (a: T, b: T) => number): Permutation { + const sortIndices = Array.from(arr.keys()).sort((index1, index2) => compareFn(arr[index1], arr[index2])); + return new Permutation(sortIndices); + } + + applyInPlace(arr: T[]): T[] { } + + inverse(): Permutation { } +} diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts index 680d61ff32f..49d6478a7f8 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts @@ -15,7 +15,7 @@ suite('getNewRanges', () => { for (let seed = 0; seed < 20; seed++) { test(`test ${seed}`, () => { - const randomText = randomMultilineString(10); + const randomText = randomMultilineString(seed, 10); const model = createTextModel(randomText); const rng = new MersenneTwister(seed); From 69684cd43cfe8007883e0a039c49ee2ed222896f Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 1 Feb 2024 14:44:49 +0100 Subject: [PATCH 0820/1897] Bullet point renders in front of checkbox in comments view (#203968) Fixes #203194 --- .../widget/markdownRenderer/browser/renderedMarkdown.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/editor/browser/widget/markdownRenderer/browser/renderedMarkdown.css b/src/vs/editor/browser/widget/markdownRenderer/browser/renderedMarkdown.css index d784524a2f1..25962ee714e 100644 --- a/src/vs/editor/browser/widget/markdownRenderer/browser/renderedMarkdown.css +++ b/src/vs/editor/browser/widget/markdownRenderer/browser/renderedMarkdown.css @@ -15,3 +15,7 @@ vertical-align: middle; padding: 1px 3px; } + +.rendered-markdown li:has(input[type=checkbox]) { + list-style-type: none; +} From dcea438acaa16369d9d53505fd242df7d6285c28 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 1 Feb 2024 15:21:07 +0100 Subject: [PATCH 0821/1897] fix #203457 (#203972) --- .../abstractExtensionManagementService.ts | 301 ++++++++---------- .../common/extensionManagementCLI.ts | 162 +++++----- 2 files changed, 216 insertions(+), 247 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index f3d243e816f..ced410cb52e 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -18,7 +18,7 @@ import { IExtensionsControlManifest, StatisticType, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode, InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService, InstallExtensionInfo, EXTENSION_INSTALL_DEP_PACK_CONTEXT, ExtensionGalleryError } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { adoptToGalleryExtensionId, areSameExtensions, ExtensionKey, getGalleryExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { areSameExtensions, ExtensionKey, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, IExtensionManifest, isApplicationScopedExtension, TargetPlatform } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -204,198 +204,167 @@ export abstract class AbstractExtensionManagementService extends Disposable impl protected async installExtensions(extensions: InstallableExtension[]): Promise { const results: InstallExtensionResult[] = []; - await Promise.allSettled(extensions.map(async e => { - try { - const result = await this.installExtension(e, (taskToWait: IInstallExtensionTask, taskToWaitFor: IInstallExtensionTask): boolean => { - if (extensions.some(e => adoptToGalleryExtensionId(taskToWaitFor.identifier.id) === getGalleryExtensionId(e.manifest.publisher, e.manifest.name))) { - return false; - } - return this.canWaitForTask(taskToWait, taskToWaitFor); - }); - results.push(...result); - } catch (error) { - results.push({ identifier: { id: getGalleryExtensionId(e.manifest.publisher, e.manifest.name) }, operation: InstallOperation.Install, source: e.extension, error }); + + const installingExtensionsMap = new Map(); + const alreadyRequestedInstallations: Promise[] = []; + const successResults: (InstallExtensionResult & { local: ILocalExtension; profileLocation: URI })[] = []; + + const getInstallExtensionTaskKey = (extension: IGalleryExtension, profileLocation: URI) => `${ExtensionKey.create(extension).toString()}-${profileLocation.toString()}`; + const createInstallExtensionTask = (manifest: IExtensionManifest, extension: IGalleryExtension | URI, options: InstallExtensionTaskOptions): void => { + const installExtensionTask = this.createInstallExtensionTask(manifest, extension, options); + const key = URI.isUri(extension) ? extension.path : `${extension.identifier.id.toLowerCase()}-${options.profileLocation.toString()}`; + installingExtensionsMap.set(key, { task: installExtensionTask, manifest }); + this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension, profileLocation: options.profileLocation }); + this.logService.info('Installing extension:', installExtensionTask.identifier.id); + // only cache gallery extensions tasks + if (!URI.isUri(extension)) { + this.installingExtensions.set(getInstallExtensionTaskKey(extension, options.profileLocation), { task: installExtensionTask, waitingTasks: [] }); } - })); - this._onDidInstallExtensions.fire(results); - return results; - } - - private async installExtension({ manifest, extension, options }: InstallableExtension, shouldWait: (taskToWait: IInstallExtensionTask, taskToWaitFor: IInstallExtensionTask) => boolean): Promise { - - const isApplicationScoped = options.isApplicationScoped || options.isBuiltin || isApplicationScopedExtension(manifest); - const installExtensionTaskOptions: InstallExtensionTaskOptions = { - ...options, - installOnlyNewlyAddedFromExtensionPack: URI.isUri(extension) ? options.installOnlyNewlyAddedFromExtensionPack : true, /* always true for gallery extensions */ - isApplicationScoped, - profileLocation: isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation() }; - const getInstallExtensionTaskKey = (extension: IGalleryExtension) => `${ExtensionKey.create(extension).toString()}${installExtensionTaskOptions.profileLocation ? `-${installExtensionTaskOptions.profileLocation.toString()}` : ''}`; - - // only cache gallery extensions tasks - if (!URI.isUri(extension)) { - const installingExtension = this.installingExtensions.get(getInstallExtensionTaskKey(extension)); - if (installingExtension) { - this.logService.info('Extensions is already requested to install', extension.identifier.id); - await installingExtension.task.waitUntilTaskIsFinished(); - return []; - } - } - - const allInstallExtensionTasks: { task: IInstallExtensionTask; manifest: IExtensionManifest }[] = []; - const alreadyRequestedInstallations: Promise[] = []; - const installResults: (InstallExtensionResult & { local: ILocalExtension })[] = []; - const installExtensionTask = this.createInstallExtensionTask(manifest, extension, installExtensionTaskOptions); - if (!URI.isUri(extension)) { - this.installingExtensions.set(getInstallExtensionTaskKey(extension), { task: installExtensionTask, waitingTasks: [] }); - } - this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension, profileLocation: installExtensionTaskOptions.profileLocation }); - this.logService.info('Installing extension:', installExtensionTask.identifier.id); - allInstallExtensionTasks.push({ task: installExtensionTask, manifest }); - let installExtensionHasDependents: boolean = false; try { - if (installExtensionTaskOptions.donotIncludePackAndDependencies) { - this.logService.info('Installing the extension without checking dependencies and pack', installExtensionTask.identifier.id); - } else { - try { - const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensions(installExtensionTask.identifier, manifest, !!installExtensionTaskOptions.installOnlyNewlyAddedFromExtensionPack, !!installExtensionTaskOptions.installPreReleaseVersion, installExtensionTaskOptions.profileLocation); - const installed = await this.getInstalled(undefined, installExtensionTaskOptions.profileLocation); - const options: InstallExtensionTaskOptions = { ...installExtensionTaskOptions, donotIncludePackAndDependencies: true, context: { ...installExtensionTaskOptions.context, [EXTENSION_INSTALL_DEP_PACK_CONTEXT]: true } }; - for (const { gallery, manifest } of distinct(allDepsAndPackExtensionsToInstall, ({ gallery }) => gallery.identifier.id)) { - installExtensionHasDependents = installExtensionHasDependents || !!manifest.extensionDependencies?.some(id => areSameExtensions({ id }, installExtensionTask.identifier)); - const key = getInstallExtensionTaskKey(gallery); - const existingInstallingExtension = this.installingExtensions.get(key); - if (existingInstallingExtension) { - if (shouldWait(installExtensionTask, existingInstallingExtension.task)) { - const identifier = existingInstallingExtension.task.identifier; - this.logService.info('Waiting for already requested installing extension', identifier.id, installExtensionTask.identifier.id); - existingInstallingExtension.waitingTasks.push(installExtensionTask); - // add promise that waits until the extension is completely installed, ie., onDidInstallExtensions event is triggered for this extension - alreadyRequestedInstallations.push( - Event.toPromise( - Event.filter(this.onDidInstallExtensions, results => results.some(result => areSameExtensions(result.identifier, identifier))) - ).then(results => { - this.logService.info('Finished waiting for already requested installing extension', identifier.id, installExtensionTask.identifier.id); - const result = results.find(result => areSameExtensions(result.identifier, identifier)); - if (!result?.local) { - // Extension failed to install - throw new Error(`Extension ${identifier.id} is not installed`); - } - })); - } - } else if (!installed.some(({ identifier }) => areSameExtensions(identifier, gallery.identifier))) { - const task = this.createInstallExtensionTask(manifest, gallery, options); - this.installingExtensions.set(key, { task, waitingTasks: [installExtensionTask] }); - this._onInstallExtension.fire({ identifier: task.identifier, source: gallery, profileLocation: installExtensionTaskOptions.profileLocation }); - this.logService.info('Installing extension:', task.identifier.id, installExtensionTask.identifier.id); - allInstallExtensionTasks.push({ task, manifest }); - } - } - } catch (error) { - // Installing through VSIX - if (URI.isUri(installExtensionTask.source)) { - // Ignore installing dependencies and packs - if (isNonEmptyArray(manifest.extensionDependencies)) { - this.logService.warn(`Cannot install dependencies of extension:`, installExtensionTask.identifier.id, error.message); - } - if (isNonEmptyArray(manifest.extensionPack)) { - this.logService.warn(`Cannot install packed extensions of extension:`, installExtensionTask.identifier.id, error.message); - } - } else { - this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', installExtensionTask.identifier.id); - throw error; - } - } - } + // Start installing extensions + for (const { manifest, extension, options } of extensions) { + const isApplicationScoped = options.isApplicationScoped || options.isBuiltin || isApplicationScopedExtension(manifest); + const installExtensionTaskOptions: InstallExtensionTaskOptions = { + ...options, + installOnlyNewlyAddedFromExtensionPack: URI.isUri(extension) ? options.installOnlyNewlyAddedFromExtensionPack : true, /* always true for gallery extensions */ + isApplicationScoped, + profileLocation: isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation() + }; - const extensionsToInstallMap = allInstallExtensionTasks.reduce((result, { task, manifest }) => { - result.set(task.identifier.id.toLowerCase(), { task, manifest }); - return result; - }, new Map()); - - while (extensionsToInstallMap.size) { - let extensionsToInstall; - const extensionsWithoutDepsToInstall = [...extensionsToInstallMap.values()].filter(({ manifest }) => !manifest.extensionDependencies?.some(id => extensionsToInstallMap.has(id.toLowerCase()))); - if (extensionsWithoutDepsToInstall.length) { - extensionsToInstall = extensionsToInstallMap.size === 1 ? extensionsWithoutDepsToInstall - /* If the main extension has no dependents remove it and install it at the end */ - : extensionsWithoutDepsToInstall.filter(({ task }) => !(task === installExtensionTask && !installExtensionHasDependents)); + const existingInstallExtensionTask = !URI.isUri(extension) ? this.installingExtensions.get(getInstallExtensionTaskKey(extension, installExtensionTaskOptions.profileLocation)) : undefined; + if (existingInstallExtensionTask) { + this.logService.info('Extension is already requested to install', existingInstallExtensionTask.task.identifier.id); + alreadyRequestedInstallations.push(existingInstallExtensionTask.task.waitUntilTaskIsFinished()); } else { - this.logService.info('Found extensions with circular dependencies', extensionsWithoutDepsToInstall.map(({ task }) => task.identifier.id)); - extensionsToInstall = [...extensionsToInstallMap.values()]; + createInstallExtensionTask(manifest, extension, installExtensionTaskOptions); } + } - // Install extensions in parallel and wait until all extensions are installed / failed - await this.joinAllSettled(extensionsToInstall.map(async ({ task }) => { - const startTime = new Date().getTime(); + // collect and start installing all dependencies and pack extensions + await Promise.all([...installingExtensionsMap.values()].map(async ({ task, manifest }) => { + if (task.options.donotIncludePackAndDependencies) { + this.logService.info('Installing the extension without checking dependencies and pack', task.identifier.id); + } else { try { - const local = await task.run(); - await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, installExtensionTaskOptions, CancellationToken.None))); - if (!URI.isUri(task.source)) { - const isUpdate = task.operation === InstallOperation.Update; - const durationSinceUpdate = isUpdate ? undefined : (new Date().getTime() - task.source.lastUpdated) / 1000; - reportTelemetry(this.telemetryService, isUpdate ? 'extensionGallery:update' : 'extensionGallery:install', { - extensionData: getGalleryExtensionTelemetryData(task.source), - verificationStatus: task.verificationStatus, - duration: new Date().getTime() - startTime, - durationSinceUpdate - }); - // In web, report extension install statistics explicitly. In Desktop, statistics are automatically updated while downloading the VSIX. - if (isWeb && task.operation !== InstallOperation.Update) { - try { - await this.galleryService.reportStatistic(local.manifest.publisher, local.manifest.name, local.manifest.version, StatisticType.Install); - } catch (error) { /* ignore */ } + const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensions(task.identifier, manifest, !!task.options.installOnlyNewlyAddedFromExtensionPack, !!task.options.installPreReleaseVersion, task.options.profileLocation); + const installed = await this.getInstalled(undefined, task.options.profileLocation); + const options: InstallExtensionTaskOptions = { ...task.options, donotIncludePackAndDependencies: true, context: { ...task.options.context, [EXTENSION_INSTALL_DEP_PACK_CONTEXT]: true } }; + for (const { gallery, manifest } of distinct(allDepsAndPackExtensionsToInstall, ({ gallery }) => gallery.identifier.id)) { + if (installingExtensionsMap.has(`${gallery.identifier.id.toLowerCase()}-${options.profileLocation.toString()}`)) { + continue; + } + const existingInstallingExtension = this.installingExtensions.get(getInstallExtensionTaskKey(gallery, options.profileLocation)); + if (existingInstallingExtension) { + if (this.canWaitForTask(task, existingInstallingExtension.task)) { + const identifier = existingInstallingExtension.task.identifier; + this.logService.info('Waiting for already requested installing extension', identifier.id, task.identifier.id); + existingInstallingExtension.waitingTasks.push(task); + // add promise that waits until the extension is completely installed, ie., onDidInstallExtensions event is triggered for this extension + alreadyRequestedInstallations.push( + Event.toPromise( + Event.filter(this.onDidInstallExtensions, results => results.some(result => areSameExtensions(result.identifier, identifier))) + ).then(results => { + this.logService.info('Finished waiting for already requested installing extension', identifier.id, task.identifier.id); + const result = results.find(result => areSameExtensions(result.identifier, identifier)); + if (!result?.local) { + // Extension failed to install + throw new Error(`Extension ${identifier.id} is not installed`); + } + })); + } + } else if (!installed.some(({ identifier }) => areSameExtensions(identifier, gallery.identifier))) { + createInstallExtensionTask(manifest, gallery, options); } } - - installResults.push({ local, identifier: task.identifier, operation: task.operation, source: task.source, context: task.options.context, profileLocation: task.profileLocation, applicationScoped: local.isApplicationScoped }); } catch (error) { - this.logService.error('Error while installing the extension', task.identifier.id, getErrorMessage(error)); - throw error; - } finally { extensionsToInstallMap.delete(task.identifier.id.toLowerCase()); } - })); - } + // Installing through VSIX + if (URI.isUri(task.source)) { + // Ignore installing dependencies and packs + if (isNonEmptyArray(manifest.extensionDependencies)) { + this.logService.warn(`Cannot install dependencies of extension:`, task.identifier.id, error.message); + } + if (isNonEmptyArray(manifest.extensionPack)) { + this.logService.warn(`Cannot install packed extensions of extension:`, task.identifier.id, error.message); + } + } else { + this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', task.identifier.id); + throw error; + } + } + } + })); + + // Install extensions in parallel and wait until all extensions are installed / failed + await this.joinAllSettled([...installingExtensionsMap.values()].map(async ({ task }) => { + const startTime = new Date().getTime(); + try { + const local = await task.run(); + await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, task.options, CancellationToken.None))); + if (!URI.isUri(task.source)) { + const isUpdate = task.operation === InstallOperation.Update; + const durationSinceUpdate = isUpdate ? undefined : (new Date().getTime() - task.source.lastUpdated) / 1000; + reportTelemetry(this.telemetryService, isUpdate ? 'extensionGallery:update' : 'extensionGallery:install', { + extensionData: getGalleryExtensionTelemetryData(task.source), + verificationStatus: task.verificationStatus, + duration: new Date().getTime() - startTime, + durationSinceUpdate + }); + // In web, report extension install statistics explicitly. In Desktop, statistics are automatically updated while downloading the VSIX. + if (isWeb && task.operation !== InstallOperation.Update) { + try { + await this.galleryService.reportStatistic(local.manifest.publisher, local.manifest.name, local.manifest.version, StatisticType.Install); + } catch (error) { /* ignore */ } + } + } + + successResults.push({ local, identifier: task.identifier, operation: task.operation, source: task.source, context: task.options.context, profileLocation: task.profileLocation, applicationScoped: local.isApplicationScoped }); + } catch (error) { + this.logService.error('Error while installing the extension', task.identifier.id, getErrorMessage(error)); + throw error; + } + })); if (alreadyRequestedInstallations.length) { await this.joinAllSettled(alreadyRequestedInstallations); } - installResults.forEach(({ identifier }) => this.logService.info(`Extension installed successfully:`, identifier.id)); - return installResults; - + for (const result of successResults) { + this.logService.info(`Extension installed successfully:`, result.identifier.id); + results.push(result); + } + return results; } catch (error) { - - // cancel all tasks - allInstallExtensionTasks.forEach(({ task }) => task.cancel()); - // rollback installed extensions - if (installResults.length) { - try { - const result = await Promise.allSettled(installResults.map(({ local }) => this.createUninstallExtensionTask(local, { versionOnly: true, profileLocation: installExtensionTaskOptions.profileLocation }).run())); - for (let index = 0; index < result.length; index++) { - const r = result[index]; - const { identifier } = installResults[index]; - if (r.status === 'fulfilled') { - this.logService.info('Rollback: Uninstalled extension', identifier.id); - } else { - this.logService.warn('Rollback: Error while uninstalling extension', identifier.id, getErrorMessage(r.reason)); - } + if (successResults.length) { + await Promise.allSettled(successResults.map(async ({ local, profileLocation }) => { + try { + await this.createUninstallExtensionTask(local, { versionOnly: true, profileLocation }).run(); + this.logService.info('Rollback: Uninstalled extension', local.identifier.id); + } catch (error) { + this.logService.warn('Rollback: Error while uninstalling extension', local.identifier.id, getErrorMessage(error)); } - } catch (error) { - // ignore error - this.logService.warn('Error while rolling back extensions', getErrorMessage(error), installResults.map(({ identifier }) => identifier.id)); - } + })); } - return allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source, context: installExtensionTaskOptions.context, profileLocation: installExtensionTaskOptions.profileLocation, error })); + // cancel all tasks and collect error results + for (const { task } of installingExtensionsMap.values()) { + task.cancel(); + results.push({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source, context: task.options.context, profileLocation: task.profileLocation, error }); + } + + throw error; } finally { // Finally, remove all the tasks from the cache - for (const { task } of allInstallExtensionTasks) { + for (const { task } of installingExtensionsMap.values()) { if (task.source && !URI.isUri(task.source)) { - this.installingExtensions.delete(getInstallExtensionTaskKey(task.source)); + this.installingExtensions.delete(getInstallExtensionTaskKey(task.source, task.profileLocation)); } } + if (results.length) { + this._onDidInstallExtensions.fire(results); + } } } diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts index b1d8e01ecda..05ee97927fe 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts @@ -20,7 +20,7 @@ const notFound = (id: string) => localize('notFound', "Extension '{0}' not found const useId = localize('useId', "Make sure you use the full extension ID, including the publisher, e.g.: {0}", 'ms-dotnettools.csharp'); type InstallVSIXInfo = { vsix: URI; installOptions: InstallOptions }; -type CLIInstallExtensionInfo = { id: string; version?: string; installOptions: InstallOptions }; +type InstallGalleryExtensionInfo = { id: string; version?: string; installOptions: InstallOptions }; export class ExtensionManagementCLI { @@ -74,13 +74,12 @@ export class ExtensionManagementCLI { const failed: string[] = []; try { - const installedExtensionsManifests: IExtensionManifest[] = []; if (extensions.length) { this.logger.info(this.location ? localize('installingExtensionsOnLocation', "Installing extensions on {0}...", this.location) : localize('installingExtensions', "Installing extensions...")); } const installVSIXInfos: InstallVSIXInfo[] = []; - let installExtensionInfos: CLIInstallExtensionInfo[] = []; + const installExtensionInfos: InstallGalleryExtensionInfo[] = []; const addInstallExtensionInfo = (id: string, version: string | undefined, isBuiltin: boolean) => { installExtensionInfos.push({ id, version: version !== 'prerelease' ? version : undefined, installOptions: { ...installOptions, isBuiltin, installPreReleaseVersion: version === 'prerelease' || installOptions.installPreReleaseVersion } }); }; @@ -106,10 +105,7 @@ export class ExtensionManagementCLI { if (installVSIXInfos.length) { await Promise.all(installVSIXInfos.map(async ({ vsix, installOptions }) => { try { - const manifest = await this.installVSIX(vsix, installOptions, force, installed); - if (manifest) { - installedExtensionsManifests.push(manifest); - } + await this.installVSIX(vsix, installOptions, force, installed); } catch (err) { this.logger.error(err); failed.push(vsix.toString()); @@ -118,40 +114,8 @@ export class ExtensionManagementCLI { } if (installExtensionInfos.length) { - installExtensionInfos = installExtensionInfos.filter(({ id, version }) => { - const installedExtension = installed.find(i => areSameExtensions(i.identifier, { id })); - if (installedExtension) { - if (!force && (!version || (version === 'prerelease' && installedExtension.preRelease))) { - this.logger.info(localize('alreadyInstalled-checkAndUpdate', "Extension '{0}' v{1} is already installed. Use '--force' option to update to latest version or provide '@' to install a specific version, for example: '{2}@1.2.3'.", id, installedExtension.manifest.version, id)); - return false; - } - if (version && installedExtension.manifest.version === version) { - this.logger.info(localize('alreadyInstalled', "Extension '{0}' is already installed.", `${id}@${version}`)); - return false; - } - } - return true; - }); - if (installExtensionInfos.length) { - const galleryExtensions = await this.getGalleryExtensions(installExtensionInfos); - await Promise.all(installExtensionInfos.map(async extensionInfo => { - const gallery = galleryExtensions.get(extensionInfo.id.toLowerCase()); - if (gallery) { - try { - const manifest = await this.installFromGallery(extensionInfo, gallery, installed); - if (manifest) { - installedExtensionsManifests.push(manifest); - } - } catch (err) { - this.logger.error(err.message || err.stack || err); - failed.push(extensionInfo.id); - } - } else { - this.logger.error(`${notFound(extensionInfo.version ? `${extensionInfo.id}@${extensionInfo.version}` : extensionInfo.id)}\n${useId}`); - failed.push(extensionInfo.id); - } - })); - } + const failedGalleryExtensions = await this.installGalleryExtensions(installExtensionInfos, installed, force); + failed.push(...failedGalleryExtensions); } } catch (error) { this.logger.error(localize('error while installing extensions', "Error while installing extensions: {0}", getErrorMessage(error))); @@ -205,7 +169,81 @@ export class ExtensionManagementCLI { } } - private async installVSIX(vsix: URI, installOptions: InstallOptions, force: boolean, installedExtensions: ILocalExtension[]): Promise { + private async installGalleryExtensions(installExtensionInfos: InstallGalleryExtensionInfo[], installed: ILocalExtension[], force: boolean): Promise { + installExtensionInfos = installExtensionInfos.filter(({ id, version }) => { + const installedExtension = installed.find(i => areSameExtensions(i.identifier, { id })); + if (installedExtension) { + if (!force && (!version || (version === 'prerelease' && installedExtension.preRelease))) { + this.logger.info(localize('alreadyInstalled-checkAndUpdate', "Extension '{0}' v{1} is already installed. Use '--force' option to update to latest version or provide '@' to install a specific version, for example: '{2}@1.2.3'.", id, installedExtension.manifest.version, id)); + return false; + } + if (version && installedExtension.manifest.version === version) { + this.logger.info(localize('alreadyInstalled', "Extension '{0}' is already installed.", `${id}@${version}`)); + return false; + } + } + return true; + }); + + if (!installExtensionInfos.length) { + return []; + } + + const failed: string[] = []; + const extensionsToInstall: InstallExtensionInfo[] = []; + const galleryExtensions = await this.getGalleryExtensions(installExtensionInfos); + await Promise.all(installExtensionInfos.map(async ({ id, version, installOptions }) => { + const gallery = galleryExtensions.get(id.toLowerCase()); + if (!gallery) { + this.logger.error(`${notFound(version ? `${id}@${version}` : id)}\n${useId}`); + failed.push(id); + return; + } + try { + const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None); + if (manifest && !this.validateExtensionKind(manifest)) { + return; + } + } catch (err) { + this.logger.error(err.message || err.stack || err); + failed.push(id); + return; + } + const installedExtension = installed.find(e => areSameExtensions(e.identifier, gallery.identifier)); + if (installedExtension) { + if (gallery.version === installedExtension.manifest.version) { + this.logger.info(localize('alreadyInstalled', "Extension '{0}' is already installed.", version ? `${id}@${version}` : id)); + return; + } + this.logger.info(localize('updateMessage', "Updating the extension '{0}' to the version {1}", id, gallery.version)); + } + if (installOptions.isBuiltin) { + this.logger.info(version ? localize('installing builtin with version', "Installing builtin extension '{0}' v{1}...", id, version) : localize('installing builtin ', "Installing builtin extension '{0}'...", id)); + } else { + this.logger.info(version ? localize('installing with version', "Installing extension '{0}' v{1}...", id, version) : localize('installing', "Installing extension '{0}'...", id)); + } + extensionsToInstall.push({ + extension: gallery, + options: { ...installOptions, installGivenVersion: !!version }, + }); + })); + + if (extensionsToInstall.length) { + const installationResult = await this.extensionManagementService.installGalleryExtensions(extensionsToInstall); + for (const extensionResult of installationResult) { + if (extensionResult.error) { + this.logger.error(localize('errorInstallingExtension', "Error while installing extension {0}: {1}", extensionResult.identifier.id, getErrorMessage(extensionResult.error))); + failed.push(extensionResult.identifier.id); + } else { + this.logger.info(localize('successInstall', "Extension '{0}' v{1} was successfully installed.", extensionResult.identifier.id, extensionResult.local?.manifest.version)); + } + } + } + + return failed; + } + + private async installVSIX(vsix: URI, installOptions: InstallOptions, force: boolean, installedExtensions: ILocalExtension[]): Promise { const manifest = await this.extensionManagementService.getManifest(vsix); if (!manifest) { @@ -217,20 +255,17 @@ export class ExtensionManagementCLI { try { await this.extensionManagementService.install(vsix, installOptions); this.logger.info(localize('successVsixInstall', "Extension '{0}' was successfully installed.", basename(vsix))); - return manifest; } catch (error) { if (isCancellationError(error)) { this.logger.info(localize('cancelVsixInstall', "Cancelled installing extension '{0}'.", basename(vsix))); - return null; } else { throw error; } } } - return null; } - private async getGalleryExtensions(extensions: CLIInstallExtensionInfo[]): Promise> { + private async getGalleryExtensions(extensions: InstallGalleryExtensionInfo[]): Promise> { const galleryExtensions = new Map(); const preRelease = extensions.some(e => e.installOptions.installPreReleaseVersion); const targetPlatform = await this.extensionManagementService.getTargetPlatform(); @@ -249,41 +284,6 @@ export class ExtensionManagementCLI { return galleryExtensions; } - private async installFromGallery({ id, version, installOptions }: CLIInstallExtensionInfo, galleryExtension: IGalleryExtension, installed: ILocalExtension[]): Promise { - const manifest = await this.extensionGalleryService.getManifest(galleryExtension, CancellationToken.None); - if (manifest && !this.validateExtensionKind(manifest)) { - return null; - } - - const installedExtension = installed.find(e => areSameExtensions(e.identifier, galleryExtension.identifier)); - if (installedExtension) { - if (galleryExtension.version === installedExtension.manifest.version) { - this.logger.info(localize('alreadyInstalled', "Extension '{0}' is already installed.", version ? `${id}@${version}` : id)); - return null; - } - this.logger.info(localize('updateMessage', "Updating the extension '{0}' to the version {1}", id, galleryExtension.version)); - } - - try { - if (installOptions.isBuiltin) { - this.logger.info(version ? localize('installing builtin with version', "Installing builtin extension '{0}' v{1}...", id, version) : localize('installing builtin ', "Installing builtin extension '{0}'...", id)); - } else { - this.logger.info(version ? localize('installing with version', "Installing extension '{0}' v{1}...", id, version) : localize('installing', "Installing extension '{0}'...", id)); - } - - const local = await this.extensionManagementService.installFromGallery(galleryExtension, { ...installOptions, installGivenVersion: !!version }); - this.logger.info(localize('successInstall', "Extension '{0}' v{1} was successfully installed.", id, local.manifest.version)); - return manifest; - } catch (error) { - if (isCancellationError(error)) { - this.logger.info(localize('cancelInstall', "Cancelled installing extension '{0}'.", id)); - return null; - } else { - throw error; - } - } - } - protected validateExtensionKind(_manifest: IExtensionManifest): boolean { return true; } From 9270d93d880b1544946d392c0ef7dbd744a2b68c Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 1 Feb 2024 16:32:10 +0100 Subject: [PATCH 0822/1897] tests pass --- .../browser/inlineCompletionsModel.ts | 8 ++++---- .../contrib/inlineCompletions/browser/utils.ts | 15 +++++++++++---- .../inlineCompletions/test/browser/utils.test.ts | 2 +- .../combineTextEditInfos.test.ts | 4 ++-- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 52d26629ed5..c4b1efdf201 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -22,7 +22,7 @@ import { GhostText, GhostTextOrReplacement, ghostTextOrReplacementEquals } from import { InlineCompletionWithUpdatedRange, InlineCompletionsSource } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource'; import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; import { SuggestItemInfo } from 'vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider'; -import { addPositions, getNewRanges, lengthOfText } from 'vs/editor/contrib/inlineCompletions/browser/utils'; +import { Permutation, addPositions, getNewRanges, lengthOfText } from 'vs/editor/contrib/inlineCompletions/browser/utils'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -456,9 +456,9 @@ export class InlineCompletionsModel extends Disposable { return new SingleTextEdit(range, secondaryEditText); }) ]; - const sortPerm = Permutation.createSortPermutation(edits); - const sortedRanges = getNewRanges(sortPerm.applyInPlace(edits)); - const ranges = sortPerm.inverse().applyInPlace(sortedRanges); + const sortPerm = Permutation.createSortPermutation(edits, (edit1, edit2) => Range.compareRangesUsingStarts(edit1.range, edit2.range)); + const sortedRanges = getNewRanges(sortPerm.apply(edits)); + const ranges = sortPerm.inverse().apply(sortedRanges); const editorSelections = ranges.map(range => Selection.fromPositions(range.getEndPosition())); return { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts index 0ac489eb897..174191a4f9c 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts @@ -162,10 +162,12 @@ export function getNewRanges(edits: ISingleEditOperation[]): Range[] { * @returns inverse edits */ export function inverseEdits(model: TextModel, edits: ISingleEditOperation[]): ISingleEditOperation[] { - const newRanges = getNewRanges(edits); + const sortPerm = Permutation.createSortPermutation(edits, (edit1, edit2) => Range.compareRangesUsingStarts(edit1.range, edit2.range)); + const sortedRanges = getNewRanges(sortPerm.apply(edits)); + const ranges = sortPerm.inverse().apply(sortedRanges); const inverseEdits: ISingleEditOperation[] = []; for (let i = 0; i < edits.length; i++) { - inverseEdits.push({ range: newRanges[i], text: model.getValueInRange(edits[i].range) }); + inverseEdits.push({ range: ranges[i], text: model.getValueInRange(edits[i].range) }); } return inverseEdits; } @@ -182,7 +184,12 @@ export class Permutation { return new Permutation(sortIndices); } - applyInPlace(arr: T[]): T[] { } + apply(arr: T[]): T[] { + return arr.map((_, index) => arr[this.indexMap[index]]); + } - inverse(): Permutation { } + inverse(): Permutation { + const inverseSortIndices = Array.from(this.indexMap.keys()).sort((index1, index2) => index1 - index2); + return new Permutation(inverseSortIndices); + } } diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts index 49d6478a7f8..150205afe94 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts @@ -19,7 +19,7 @@ suite('getNewRanges', () => { const model = createTextModel(randomText); const rng = new MersenneTwister(seed); - const edits = getRandomEditInfos(model, rng.nextIntRange(1, 4), rng).map(e => toEdit(e)); + const edits = getRandomEditInfos(model, rng.nextIntRange(1, 4), rng, true).map(e => toEdit(e)); const invEdits = inverseEdits(model, edits); model.applyEdits(edits); diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts index 8ce6413ceda..611ba267d5b 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts @@ -58,12 +58,12 @@ function runTest(seed: number) { textModelS2.dispose(); } -export function getRandomEditInfos(textModel: TextModel, count: number, rng: MersenneTwister): TextEditInfo[] { +export function getRandomEditInfos(textModel: TextModel, count: number, rng: MersenneTwister, disjoint: boolean = false): TextEditInfo[] { const edits: TextEditInfo[] = []; let i = 0; for (let j = 0; j < count; j++) { edits.push(getRandomEdit(textModel, i, rng)); - i = textModel.getOffsetAt(lengthToPosition(edits[j].endOffset)); + i = textModel.getOffsetAt(lengthToPosition(edits[j].endOffset)) + (disjoint ? 1 : 0); } return edits; } From dbd391ebba2827eb12707abf499c1912d2aac31e Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 1 Feb 2024 12:33:38 -0300 Subject: [PATCH 0823/1897] Don't allow picking the same variable multiple times (#203975) * Cache agent subCommands, make some async code sync * Fix lastSlashCommands * Cache parsed chat query in one place so we don't have to parse the same thing over and over * Don't allow picking the same variable multiple times Fix microsoft/vscode-copilot-release#733 * Fix tests --- .../api/browser/mainThreadChatAgents2.ts | 11 ++- src/vs/workbench/contrib/chat/browser/chat.ts | 2 + .../contrib/chat/browser/chatWidget.ts | 16 +++- .../browser/contrib/chatInputEditorContrib.ts | 89 ++++++++----------- .../contrib/chat/common/chatAgents.ts | 12 +-- .../contrib/chat/common/chatRequestParser.ts | 12 ++- .../contrib/chat/common/chatServiceImpl.ts | 4 +- ..._agent_and_subcommand_after_newline.0.snap | 8 +- ..._subcommand_with_leading_whitespace.0.snap | 8 +- ...uestParser_agent_with_question_mark.0.snap | 8 +- ...er_agent_with_subcommand_after_text.0.snap | 8 +- ...hatRequestParser_agents__subCommand.0.snap | 8 +- ..._agents_and_variables_and_multiline.0.snap | 8 +- ..._and_variables_and_multiline__part2.0.snap | 8 +- .../__snapshots__/Chat_can_serialize.1.snap | 2 +- .../test/common/chatRequestParser.test.ts | 54 +++++------ .../chat/test/common/chatService.test.ts | 2 - 17 files changed, 154 insertions(+), 106 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index 7be5e2e6ea0..8ed8650fffc 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -18,7 +18,7 @@ import { ExtHostChatAgentsShape2, ExtHostContext, IChatProgressDto, IExtensionCh import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { AddDynamicVariableAction, IAddDynamicVariableContext } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables'; -import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentCommand, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestAgentPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatFollowup, IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; @@ -75,6 +75,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA } $registerAgent(handle: number, name: string, metadata: IExtensionChatAgentMetadata): void { + let lastSlashCommands: IChatAgentCommand[] | undefined; const d = this._chatAgentService.registerAgent({ id: name, metadata: revive(metadata), @@ -93,11 +94,15 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA return this._proxy.$provideFollowups(handle, sessionId, token); }, + get lastSlashCommands() { + return lastSlashCommands; + }, provideSlashCommands: async (token) => { if (!this._agents.get(handle)?.hasSlashCommands) { return []; // save an IPC call } - return this._proxy.$provideSlashCommands(handle, token); + lastSlashCommands = await this._proxy.$provideSlashCommands(handle, token); + return lastSlashCommands; } }); this._agents.set(handle, { @@ -141,7 +146,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA return; } - const parsedRequest = (await this._instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue())).parts; + const parsedRequest = this._instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue()).parts; const agentPart = parsedRequest.find((part): part is ChatRequestAgentPart => part instanceof ChatRequestAgentPart); const thisAgentName = this._agents.get(handle)?.name; if (agentPart?.agent.id !== thisAgentName) { diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index 17d1875bfc4..ff95527a982 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -9,6 +9,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Selection } from 'vs/editor/common/core/selection'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatWidgetContrib } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, IChatWelcomeMessageViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; export const IChatWidgetService = createDecorator('chatWidgetService'); @@ -105,6 +106,7 @@ export interface IChatWidget { readonly inputEditor: ICodeEditor; readonly providerId: string; readonly supportsFileReferences: boolean; + readonly parsedInput: IParsedChatRequest; getContrib(id: string): T | undefined; reveal(item: ChatTreeItem): void; diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 7487035e490..e667ddf78b2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -33,6 +33,8 @@ import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common import { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; const $ = dom.$; @@ -127,6 +129,15 @@ export class ChatWidget extends Disposable implements IChatWidget { return this._viewModel; } + private parsedChatRequest: IParsedChatRequest | undefined; + get parsedInput() { + if (this.parsedChatRequest === undefined) { + this.parsedChatRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(this.viewModel!.sessionId, this.getInput()); + } + + return this.parsedChatRequest; + } + constructor( readonly viewContext: IChatWidgetViewContext, private readonly viewOptions: IChatWidgetViewOptions, @@ -436,6 +447,9 @@ export class ChatWidget extends Disposable implements IChatWidget { }); })); this._register(this.inputPart.onDidChangeHeight(() => this.bodyDimension && this.layout(this.bodyDimension.height, this.bodyDimension.width))); + this._register(this.inputEditor.onDidChangeModelContent(() => { + this.parsedChatRequest = undefined; + })); } private onDidStyleChange(): void { @@ -553,8 +567,6 @@ export class ChatWidget extends Disposable implements IChatWidget { const lastResponse = responses?.[responses.length - 1]; this._chatAccessibilityService.acceptResponse(lastResponse, requestId); }); - } else { - this._chatAccessibilityService.acceptResponse(undefined, requestId); } } } diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 503d7c98258..84956526adb 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -15,7 +15,6 @@ import { CompletionContext, CompletionItem, CompletionItemKind, CompletionList } import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { localize } from 'vs/nls'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; @@ -33,7 +32,6 @@ import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestP import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; -import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; const decorationDescription = 'chat'; @@ -55,7 +53,6 @@ class InputEditorDecorations extends Disposable { constructor( private readonly widget: IChatWidget, - @IInstantiationService private readonly instantiationService: IInstantiationService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, @IThemeService private readonly themeService: IThemeService, @IChatService private readonly chatService: IChatService, @@ -154,7 +151,7 @@ class InputEditorDecorations extends Disposable { return; } - const parsedRequest = (await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(viewModel.sessionId, inputValue)).parts; + const parsedRequest = this.widget.parsedInput.parts; let placeholderDecoration: IDecorationOptions[] | undefined; const agentPart = parsedRequest.find((p): p is ChatRequestAgentPart => p instanceof ChatRequestAgentPart); @@ -294,7 +291,6 @@ class SlashCommandCompletions extends Disposable { constructor( @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, - @IInstantiationService private readonly instantiationService: IInstantiationService, @IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService ) { super(); @@ -313,7 +309,7 @@ class SlashCommandCompletions extends Disposable { return null; } - const parsedRequest = (await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue())).parts; + const parsedRequest = widget.parsedInput.parts; const usedAgent = parsedRequest.find(p => p instanceof ChatRequestAgentPart); if (usedAgent) { // No (classic) global slash commands when an agent is used @@ -351,7 +347,6 @@ class AgentCompletions extends Disposable { @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, @IChatAgentService private readonly chatAgentService: IChatAgentService, - @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); @@ -364,7 +359,7 @@ class AgentCompletions extends Disposable { return null; } - const parsedRequest = (await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue())).parts; + const parsedRequest = widget.parsedInput.parts; const usedAgent = parsedRequest.find(p => p instanceof ChatRequestAgentPart); if (usedAgent && !Range.containsPosition(usedAgent.editorRange, position)) { // Only one agent allowed @@ -407,7 +402,7 @@ class AgentCompletions extends Disposable { return null; } - const parsedRequest = (await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue())).parts; + const parsedRequest = widget.parsedInput.parts; const usedAgentIdx = parsedRequest.findIndex((p): p is ChatRequestAgentPart => p instanceof ChatRequestAgentPart); if (usedAgentIdx < 0) { return; @@ -428,7 +423,7 @@ class AgentCompletions extends Disposable { } const usedAgent = parsedRequest[usedAgentIdx] as ChatRequestAgentPart; - const commands = await usedAgent.agent.provideSlashCommands(token); + const commands = await usedAgent.agent.provideSlashCommands(token); // Refresh the cache here return { suggestions: commands.map((c, i) => { @@ -576,7 +571,6 @@ class VariableCompletions extends Disposable { @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, @IChatVariablesService private readonly chatVariablesService: IChatVariablesService, - @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(); @@ -595,33 +589,24 @@ class VariableCompletions extends Disposable { return null; } - const history = widget.viewModel!.getItems() - .filter(isResponseVM); - - // TODO@roblourens work out a real API for this- maybe it can be part of the two-step flow that @file will probably use - const historyVariablesEnabled = this.configurationService.getValue('chat.experimental.historyVariables'); - const historyItems = historyVariablesEnabled ? history.map((h, i): CompletionItem => ({ - label: `${chatVariableLeader}response:${i + 1}`, - detail: h.response.asString(), - insertText: `${chatVariableLeader}response:${String(i + 1).padStart(String(history.length).length, '0')} `, - kind: CompletionItemKind.Text, - range, - })) : []; - - const variableItems = Array.from(this.chatVariablesService.getVariables()).map(v => { - const withLeader = `${chatVariableLeader}${v.name}`; - return { - label: withLeader, - range, - insertText: withLeader + ' ', - detail: v.description, - kind: CompletionItemKind.Text, // The icons are disabled here anyway - sortText: 'z' - }; - }); + const usedVariables = widget.parsedInput.parts.filter((p): p is ChatRequestVariablePart => p instanceof ChatRequestVariablePart); + const variableItems = Array.from(this.chatVariablesService.getVariables()) + // This doesn't look at dynamic variables like `file`, where multiple makes sense. + .filter(v => !usedVariables.some(usedVar => usedVar.variableName === v.name)) + .map(v => { + const withLeader = `${chatVariableLeader}${v.name}`; + return { + label: withLeader, + range, + insertText: withLeader + ' ', + detail: v.description, + kind: CompletionItemKind.Text, // The icons are disabled here anyway + sortText: 'z' + }; + }); return { - suggestions: [...variableItems, ...historyItems] + suggestions: variableItems }; } })); @@ -655,22 +640,22 @@ class ChatTokenDeleter extends Disposable { // If this was a simple delete, try to find out whether it was inside a token if (!change.text && this.widget.viewModel) { - parser.parseChatRequest(this.widget.viewModel.sessionId, previousInputValue).then(previousParsedValue => { - // For dynamic variables, this has to happen in ChatDynamicVariableModel with the other bookkeeping - const deletableTokens = previousParsedValue.parts.filter(p => p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart || p instanceof ChatRequestSlashCommandPart || p instanceof ChatRequestVariablePart); - deletableTokens.forEach(token => { - const deletedRangeOfToken = Range.intersectRanges(token.editorRange, change.range); - // Part of this token was deleted, and the deletion range doesn't go off the front of the token, for simpler math - if ((deletedRangeOfToken && !deletedRangeOfToken.isEmpty()) && Range.compareRangesUsingStarts(token.editorRange, change.range) < 0) { - // Assume single line tokens - const length = deletedRangeOfToken.endColumn - deletedRangeOfToken.startColumn; - const rangeToDelete = new Range(token.editorRange.startLineNumber, token.editorRange.startColumn, token.editorRange.endLineNumber, token.editorRange.endColumn - length); - this.widget.inputEditor.executeEdits(this.id, [{ - range: rangeToDelete, - text: '', - }]); - } - }); + const previousParsedValue = parser.parseChatRequest(this.widget.viewModel.sessionId, previousInputValue); + + // For dynamic variables, this has to happen in ChatDynamicVariableModel with the other bookkeeping + const deletableTokens = previousParsedValue.parts.filter(p => p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart || p instanceof ChatRequestSlashCommandPart || p instanceof ChatRequestVariablePart); + deletableTokens.forEach(token => { + const deletedRangeOfToken = Range.intersectRanges(token.editorRange, change.range); + // Part of this token was deleted, and the deletion range doesn't go off the front of the token, for simpler math + if ((deletedRangeOfToken && !deletedRangeOfToken.isEmpty()) && Range.compareRangesUsingStarts(token.editorRange, change.range) < 0) { + // Assume single line tokens + const length = deletedRangeOfToken.endColumn - deletedRangeOfToken.startColumn; + const rangeToDelete = new Range(token.editorRange.startLineNumber, token.editorRange.startColumn, token.editorRange.endLineNumber, token.editorRange.endColumn - length); + this.widget.inputEditor.executeEdits(this.id, [{ + range: rangeToDelete, + text: '', + }]); + } }); } diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index bab428dd38c..1bdaf8ad424 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -17,20 +17,21 @@ import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chat //#region agent service, commands etc -export interface IChatAgentData { - id: string; - metadata: IChatAgentMetadata; -} - export interface IChatAgentHistoryEntry { request: IChatAgentRequest; response: ReadonlyArray; result: IChatAgentResult; } +export interface IChatAgentData { + id: string; + metadata: IChatAgentMetadata; +} + export interface IChatAgent extends IChatAgentData { invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; provideFollowups?(sessionId: string, token: CancellationToken): Promise; + lastSlashCommands?: IChatAgentCommand[]; provideSlashCommands(token: CancellationToken): Promise; } @@ -146,6 +147,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService { throw new Error(`No agent with id ${id} registered`); } data.agent.metadata = { ...data.agent.metadata, ...updateMetadata }; + data.agent.provideSlashCommands(CancellationToken.None); // Update the cached slash commands this._onDidChangeAgents.fire(); } diff --git a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts index cdc91180453..007b8c8a191 100644 --- a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts +++ b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken } from 'vs/base/common/cancellation'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -23,7 +22,7 @@ export class ChatRequestParser { @IChatSlashCommandService private readonly slashCommandService: IChatSlashCommandService ) { } - async parseChatRequest(sessionId: string, message: string): Promise { + parseChatRequest(sessionId: string, message: string): IParsedChatRequest { const parts: IParsedChatRequestPart[] = []; const references = this.variableService.getDynamicVariables(sessionId); // must access this list before any async calls @@ -39,8 +38,7 @@ export class ChatRequestParser { } else if (char === chatAgentLeader) { newPart = this.tryToParseAgent(message.slice(i), message, i, new Position(lineNumber, column), parts); } else if (char === chatSubcommandLeader) { - // TODO try to make this sync - newPart = await this.tryToParseSlashCommand(sessionId, message.slice(i), message, i, new Position(lineNumber, column), parts); + newPart = this.tryToParseSlashCommand(message.slice(i), message, i, new Position(lineNumber, column), parts); } if (!newPart) { @@ -140,7 +138,7 @@ export class ChatRequestParser { return; } - private async tryToParseSlashCommand(sessionId: string, remainingMessage: string, fullMessage: string, offset: number, position: IPosition, parts: ReadonlyArray): Promise { + private tryToParseSlashCommand(remainingMessage: string, fullMessage: string, offset: number, position: IPosition, parts: ReadonlyArray): ChatRequestSlashCommandPart | ChatRequestAgentSubcommandPart | undefined { const nextSlashMatch = remainingMessage.match(slashReg); if (!nextSlashMatch) { return; @@ -169,8 +167,8 @@ export class ChatRequestParser { return; } - const subCommands = await usedAgent.agent.provideSlashCommands(CancellationToken.None); - const subCommand = subCommands.find(c => c.name === command); + const subCommands = usedAgent.agent.lastSlashCommands; + const subCommand = subCommands?.find(c => c.name === command); if (subCommand) { // Valid agent subcommand return new ChatRequestAgentSubcommandPart(slashRange, slashEditorRange, subCommand); diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 8eaa7771901..9574afe19d6 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -459,7 +459,7 @@ export class ChatService extends Disposable implements IChatService { } private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, message: string): Promise { - const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message); + const parsedRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message); let request: ChatRequestModel; const agentPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart); @@ -656,7 +656,7 @@ export class ChatService extends Disposable implements IChatService { await model.waitForInitialization(); const parsedRequest = typeof message === 'string' ? - await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message) : + this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message) : message; const request = model.addRequest(parsedRequest, variableData || { message: parsedRequest.text, variables: {} }); if (typeof response.message === 'string') { diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap index 7a73d008baa..cc7ecaf508d 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap @@ -28,7 +28,13 @@ agent: { id: "agent", metadata: { description: "" }, - provideSlashCommands: [Function provideSlashCommands] + provideSlashCommands: [Function provideSlashCommands], + lastSlashCommands: [ + { + name: "subCommand", + description: "" + } + ] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap index ccd7eb870e0..d46c197f633 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap @@ -28,7 +28,13 @@ agent: { id: "agent", metadata: { description: "" }, - provideSlashCommands: [Function provideSlashCommands] + provideSlashCommands: [Function provideSlashCommands], + lastSlashCommands: [ + { + name: "subCommand", + description: "" + } + ] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap index 65e2aa78ac0..4891a73e641 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap @@ -14,7 +14,13 @@ agent: { id: "agent", metadata: { description: "" }, - provideSlashCommands: [Function provideSlashCommands] + provideSlashCommands: [Function provideSlashCommands], + lastSlashCommands: [ + { + name: "subCommand", + description: "" + } + ] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap index b1954f78a47..d7889981915 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap @@ -14,7 +14,13 @@ agent: { id: "agent", metadata: { description: "" }, - provideSlashCommands: [Function provideSlashCommands] + provideSlashCommands: [Function provideSlashCommands], + lastSlashCommands: [ + { + name: "subCommand", + description: "" + } + ] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap index ca9a0569fcd..df42f889d05 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap @@ -14,7 +14,13 @@ agent: { id: "agent", metadata: { description: "" }, - provideSlashCommands: [Function provideSlashCommands] + provideSlashCommands: [Function provideSlashCommands], + lastSlashCommands: [ + { + name: "subCommand", + description: "" + } + ] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap index 750f1bc39f6..d3f091a95e8 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap @@ -14,7 +14,13 @@ agent: { id: "agent", metadata: { description: "" }, - provideSlashCommands: [Function provideSlashCommands] + provideSlashCommands: [Function provideSlashCommands], + lastSlashCommands: [ + { + name: "subCommand", + description: "" + } + ] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap index 310f36005b3..c4b86b46fff 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap @@ -14,7 +14,13 @@ agent: { id: "agent", metadata: { description: "" }, - provideSlashCommands: [Function provideSlashCommands] + provideSlashCommands: [Function provideSlashCommands], + lastSlashCommands: [ + { + name: "subCommand", + description: "" + } + ] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap index a332c9b3af7..0c270a4d7f3 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap @@ -50,7 +50,7 @@ }, response: [ ], responseErrorDetails: undefined, - followups: [ ], + followups: undefined, isCanceled: false, vote: undefined, agent: { diff --git a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index dfdb6cf1fa5..5585c30afa8 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -9,7 +9,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { ChatAgentService, IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatAgentService, IChatAgent, IChatAgentCommand, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -37,14 +37,14 @@ suite('ChatRequestParser', () => { test('plain text', async () => { parser = instantiationService.createInstance(ChatRequestParser); - const result = await parser.parseChatRequest('1', 'test'); + const result = parser.parseChatRequest('1', 'test'); await assertSnapshot(result); }); test('plain text with newlines', async () => { parser = instantiationService.createInstance(ChatRequestParser); const text = 'line 1\nline 2\r\nline 3'; - const result = await parser.parseChatRequest('1', text); + const result = parser.parseChatRequest('1', text); await assertSnapshot(result); }); @@ -55,7 +55,7 @@ suite('ChatRequestParser', () => { parser = instantiationService.createInstance(ChatRequestParser); const text = '/fix this'; - const result = await parser.parseChatRequest('1', text); + const result = parser.parseChatRequest('1', text); await assertSnapshot(result); }); @@ -66,7 +66,7 @@ suite('ChatRequestParser', () => { parser = instantiationService.createInstance(ChatRequestParser); const text = '/explain this'; - const result = await parser.parseChatRequest('1', text); + const result = parser.parseChatRequest('1', text); await assertSnapshot(result); }); @@ -77,7 +77,7 @@ suite('ChatRequestParser', () => { parser = instantiationService.createInstance(ChatRequestParser); const text = '/fix /fix'; - const result = await parser.parseChatRequest('1', text); + const result = parser.parseChatRequest('1', text); await assertSnapshot(result); }); @@ -86,7 +86,7 @@ suite('ChatRequestParser', () => { parser = instantiationService.createInstance(ChatRequestParser); const text = 'What does #selection mean?'; - const result = await parser.parseChatRequest('1', text); + const result = parser.parseChatRequest('1', text); await assertSnapshot(result); }); @@ -95,7 +95,7 @@ suite('ChatRequestParser', () => { parser = instantiationService.createInstance(ChatRequestParser); const text = 'What is #selection?'; - const result = await parser.parseChatRequest('1', text); + const result = parser.parseChatRequest('1', text); await assertSnapshot(result); }); @@ -104,91 +104,95 @@ suite('ChatRequestParser', () => { parser = instantiationService.createInstance(ChatRequestParser); const text = 'What does #selection mean?'; - const result = await parser.parseChatRequest('1', text); + const result = parser.parseChatRequest('1', text); await assertSnapshot(result); }); + const getAgentWithSlashcommands = (slashCommands: IChatAgentCommand[]) => { + return >{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => [], lastSlashCommands: slashCommands }; + }; + test('agent with subcommand after text', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(>{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => { return [{ name: 'subCommand', description: '' }]; } }); + agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); - const result = await parser.parseChatRequest('1', '@agent Please do /subCommand thanks'); + const result = parser.parseChatRequest('1', '@agent Please do /subCommand thanks'); await assertSnapshot(result); }); test('agents, subCommand', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(>{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => { return [{ name: 'subCommand', description: '' }]; } }); + agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); - const result = await parser.parseChatRequest('1', '@agent /subCommand Please do thanks'); + const result = parser.parseChatRequest('1', '@agent /subCommand Please do thanks'); await assertSnapshot(result); }); test('agent with question mark', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(>{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => { return [{ name: 'subCommand', description: '' }]; } }); + agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); - const result = await parser.parseChatRequest('1', '@agent? Are you there'); + const result = parser.parseChatRequest('1', '@agent? Are you there'); await assertSnapshot(result); }); test('agent and subcommand with leading whitespace', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(>{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => { return [{ name: 'subCommand', description: '' }]; } }); + agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); - const result = await parser.parseChatRequest('1', ' \r\n\t @agent \r\n\t /subCommand Thanks'); + const result = parser.parseChatRequest('1', ' \r\n\t @agent \r\n\t /subCommand Thanks'); await assertSnapshot(result); }); test('agent and subcommand after newline', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(>{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => { return [{ name: 'subCommand', description: '' }]; } }); + agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); - const result = await parser.parseChatRequest('1', ' \n@agent\n/subCommand Thanks'); + const result = parser.parseChatRequest('1', ' \n@agent\n/subCommand Thanks'); await assertSnapshot(result); }); test('agent not first', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(>{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => { return [{ name: 'subCommand', description: '' }]; } }); + agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); - const result = await parser.parseChatRequest('1', 'Hello Mr. @agent'); + const result = parser.parseChatRequest('1', 'Hello Mr. @agent'); await assertSnapshot(result); }); test('agents and variables and multiline', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(>{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => { return [{ name: 'subCommand', description: '' }]; } }); + agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); varService.hasVariable.returns(true); parser = instantiationService.createInstance(ChatRequestParser); - const result = await parser.parseChatRequest('1', '@agent /subCommand \nPlease do with #selection\nand #debugConsole'); + const result = parser.parseChatRequest('1', '@agent /subCommand \nPlease do with #selection\nand #debugConsole'); await assertSnapshot(result); }); test('agents and variables and multiline, part2', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(>{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => { return [{ name: 'subCommand', description: '' }]; } }); + agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); varService.hasVariable.returns(true); parser = instantiationService.createInstance(ChatRequestParser); - const result = await parser.parseChatRequest('1', '@agent Please \ndo /subCommand with #selection\nand #debugConsole'); + const result = parser.parseChatRequest('1', '@agent Please \ndo /subCommand with #selection\nand #debugConsole'); await assertSnapshot(result); }); }); 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 227df61de91..c885bf9444d 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -227,8 +227,6 @@ suite('Chat', () => { const response = await testService.sendRequest(model.sessionId, `@${chatAgentWithUsedContextId} test request`); assert(response); - await response.responseCompletePromise; - assert.strictEqual(model.getRequests().length, 1); await assertSnapshot(model.toExport()); From a3d4451154bb2d6462221650f4472a3c4d563e31 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 1 Feb 2024 12:48:58 -0300 Subject: [PATCH 0824/1897] Always allow ctrl+up to focus the chat list on windows (#203982) --- .../chat/browser/actions/chatActions.ts | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index dd8f210d6e3..02c86b79c0f 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -34,6 +34,7 @@ import { IChatDetail, IChatService } from 'vs/workbench/contrib/chat/common/chat import { IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IsLinuxContext, IsWindowsContext } from 'vs/platform/contextkey/common/contextkeys'; export const CHAT_CATEGORY = localize2('chat.category', 'Chat'); export const CHAT_OPEN_ACTION_ID = 'workbench.action.chat.open'; @@ -192,13 +193,22 @@ export function registerChatActions() { super({ id: 'chat.action.focus', title: localize2('actions.interactiveSession.focus', 'Focus Chat List'), - precondition: ContextKeyExpr.and(CONTEXT_IN_CHAT_INPUT, CONTEXT_CHAT_INPUT_CURSOR_AT_TOP), + precondition: CONTEXT_IN_CHAT_INPUT, category: CHAT_CATEGORY, - keybinding: { - when: EditorContextKeys.textInputFocus, - primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - weight: KeybindingWeight.EditorContrib - } + keybinding: [ + // On mac, require that the cursor is at the top of the input, to avoid stealing cmd+up to move the cursor to the top + { + when: CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + weight: KeybindingWeight.EditorContrib, + }, + // On win/linux, ctrl+up can always focus the chat list + { + when: ContextKeyExpr.or(IsWindowsContext, IsLinuxContext), + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + weight: KeybindingWeight.EditorContrib, + } + ] }); } From 54488a73dd7506ed97bf24c9ee6a82e9edf6ffb0 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 1 Feb 2024 16:49:14 +0100 Subject: [PATCH 0825/1897] perf - handle `workbench.contrib.textInputActionsProvider` (#203947) (#203958) perf - handle `workbench.contrib.textInputActionsProvider` --- .../browser/actions/textInputActions.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/browser/actions/textInputActions.ts b/src/vs/workbench/browser/actions/textInputActions.ts index c3c85845aea..96cdb5f4307 100644 --- a/src/vs/workbench/browser/actions/textInputActions.ts +++ b/src/vs/workbench/browser/actions/textInputActions.ts @@ -14,12 +14,13 @@ import { isNative } from 'vs/base/common/platform'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { Event as BaseEvent } from 'vs/base/common/event'; +import { Lazy } from 'vs/base/common/lazy'; export class TextInputActionsProvider extends Disposable implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.textInputActionsProvider'; - private textInputActions: IAction[] = []; + private readonly textInputActions = new Lazy(() => this.createActions()); constructor( @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @@ -28,13 +29,11 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo ) { super(); - this.createActions(); - this.registerListeners(); } - private createActions(): void { - this.textInputActions.push( + private createActions(): IAction[] { + return [ // Undo/Redo new Action('undo', localize('undo', "Undo"), undefined, true, async () => getActiveDocument().execCommand('undo')), @@ -72,7 +71,7 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo // Select All new Action('editor.action.selectAll', localize('selectAll', "Select All"), undefined, true, async () => getActiveDocument().execCommand('selectAll')) - ); + ]; } private registerListeners(): void { @@ -99,10 +98,14 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo this.contextMenuService.showContextMenu({ getAnchor: () => event, - getActions: () => this.textInputActions, + getActions: () => this.textInputActions.value, getActionsContext: () => target, }); } } -registerWorkbenchContribution2(TextInputActionsProvider.ID, TextInputActionsProvider, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2( + TextInputActionsProvider.ID, + TextInputActionsProvider, + WorkbenchContributionInstantiation.BlockRestore // Block to allow right-click into input fields before restore finished +); From db3ebe6121ad51d646c22b11e7487beb3ff5782c Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Thu, 1 Feb 2024 16:36:21 +0100 Subject: [PATCH 0826/1897] Update to @vscode/proxy-agent 0.19.0 (#203847) --- package.json | 2 +- remote/package.json | 2 +- remote/yarn.lock | 8 ++++---- yarn.lock | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 7471fc6d041..f977e0d80a0 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.1.4", - "@vscode/proxy-agent": "^0.18.2", + "@vscode/proxy-agent": "^0.19.0", "@vscode/ripgrep": "^1.15.9", "@vscode/spdlog": "^0.14.0", "@vscode/sqlite3": "5.1.6-vscode", diff --git a/remote/package.json b/remote/package.json index 7a6c7145324..82d2d26b77b 100644 --- a/remote/package.json +++ b/remote/package.json @@ -7,7 +7,7 @@ "@microsoft/1ds-post-js": "^3.2.13", "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/proxy-agent": "^0.18.2", + "@vscode/proxy-agent": "^0.19.0", "@vscode/ripgrep": "^1.15.9", "@vscode/spdlog": "^0.14.0", "@vscode/vscode-languagedetection": "1.0.21", diff --git a/remote/yarn.lock b/remote/yarn.lock index 26ae24f2518..a1017c13e6b 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -58,10 +58,10 @@ resolved "https://registry.yarnpkg.com/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz#d2f1e0664ee6036408f9743fee264ea0699b0e48" integrity sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg== -"@vscode/proxy-agent@^0.18.2": - version "0.18.2" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.18.2.tgz#8b2872849b6c6779cd655ce369a4556c6804a9f7" - integrity sha512-yDj/JI9otEEUVU36dKOWNitGk4tBcriIeE6uvVGEdvKbpVD7L9UMNvL/wCoHCOqBnf2g5jUhWSURWDVyAhJn2Q== +"@vscode/proxy-agent@^0.19.0": + version "0.19.0" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.19.0.tgz#ea6571854abb8691ca24a95ba81a6850c54da5df" + integrity sha512-VKCEELuv/BFt1h9wAQE9zuKZM2UAJlJzucXFlvyUYrrPRG66Mgm2JQmClxkE5VRa0gCDRzUClZBT8Fptx8Ce7A== dependencies: "@tootallnate/once" "^3.0.0" agent-base "^7.0.1" diff --git a/yarn.lock b/yarn.lock index 9c0f656aad4..2bb41b4fc24 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1386,10 +1386,10 @@ bindings "^1.5.0" node-addon-api "^6.0.0" -"@vscode/proxy-agent@^0.18.2": - version "0.18.2" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.18.2.tgz#8b2872849b6c6779cd655ce369a4556c6804a9f7" - integrity sha512-yDj/JI9otEEUVU36dKOWNitGk4tBcriIeE6uvVGEdvKbpVD7L9UMNvL/wCoHCOqBnf2g5jUhWSURWDVyAhJn2Q== +"@vscode/proxy-agent@^0.19.0": + version "0.19.0" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.19.0.tgz#ea6571854abb8691ca24a95ba81a6850c54da5df" + integrity sha512-VKCEELuv/BFt1h9wAQE9zuKZM2UAJlJzucXFlvyUYrrPRG66Mgm2JQmClxkE5VRa0gCDRzUClZBT8Fptx8Ce7A== dependencies: "@tootallnate/once" "^3.0.0" agent-base "^7.0.1" From 65685b14c7e0ab60d2c694e85182bb5aa2ed8926 Mon Sep 17 00:00:00 2001 From: Aleksandr Kondrashov <116561995+aramikuto@users.noreply.github.com> Date: Fri, 2 Feb 2024 01:12:31 +0900 Subject: [PATCH 0827/1897] Do not use respectMultiSelection for upload and paste (#201145) --- src/vs/workbench/contrib/files/browser/fileActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index fba86d0dbbd..2f3dc318c25 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -1061,7 +1061,7 @@ const uploadFileHandler = async (accessor: ServicesAccessor) => { const notificationService = accessor.get(INotificationService); const instantiationService = accessor.get(IInstantiationService); - const context = explorerService.getContext(true); + const context = explorerService.getContext(false); const element = context.length ? context[0] : explorerService.roots[0]; try { @@ -1092,7 +1092,7 @@ export const pasteFileHandler = async (accessor: ServicesAccessor, fileList?: Fi const uriIdentityService = accessor.get(IUriIdentityService); const dialogService = accessor.get(IDialogService); - const context = explorerService.getContext(true); + const context = explorerService.getContext(false); const hasNativeFilesToPaste = fileList && fileList.length > 0; const confirmPasteNative = hasNativeFilesToPaste && configurationService.getValue('explorer.confirmPasteNative'); From 09f3bd5c0b5aa1ec0d3955199fbf796f699b5b18 Mon Sep 17 00:00:00 2001 From: Charly Date: Thu, 1 Feb 2024 17:24:18 +0100 Subject: [PATCH 0828/1897] Fix typo in configurationEditingMain.ts (#203970) Removed the extra `an` in `The path where an an extension is installed`. --- .../configuration-editing/src/configurationEditingMain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/configuration-editing/src/configurationEditingMain.ts b/extensions/configuration-editing/src/configurationEditingMain.ts index 30e7fd45370..f791557a705 100644 --- a/extensions/configuration-editing/src/configurationEditingMain.ts +++ b/extensions/configuration-editing/src/configurationEditingMain.ts @@ -67,7 +67,7 @@ function registerVariableCompletions(pattern: string): vscode.Disposable { { label: 'fileBasenameNoExtension', detail: vscode.l10n.t("The current opened file's basename with no file extension") }, { label: 'defaultBuildTask', detail: vscode.l10n.t("The name of the default build task. If there is not a single default build task then a quick pick is shown to choose the build task.") }, { label: 'pathSeparator', detail: vscode.l10n.t("The character used by the operating system to separate components in file paths. Is also aliased to '/'.") }, - { label: 'extensionInstallFolder', detail: vscode.l10n.t("The path where an an extension is installed."), param: 'publisher.extension' }, + { label: 'extensionInstallFolder', detail: vscode.l10n.t("The path where an extension is installed."), param: 'publisher.extension' }, ].map(variable => ({ label: `\${${variable.label}}`, range, From 3513974b537be83b6460972a32badfe408d10ee0 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 1 Feb 2024 17:30:03 +0100 Subject: [PATCH 0829/1897] [Accessibility] Take out redundant prefix from comment thread for better readability (#203953) Fixes #203952 --- src/vs/workbench/contrib/comments/browser/commentsView.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index ef8d51a1313..385ac16e1dc 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -318,7 +318,7 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { if (element instanceof CommentNode) { if (element.range) { return nls.localize('resourceWithCommentLabel', - "Comment from ${0} at line {1} column {2} in {3}, source: {4}", + "${0} at line {1} column {2} in {3}, source: {4}", element.comment.userName, element.range.startLineNumber, element.range.startColumn, @@ -327,7 +327,7 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { ); } else { return nls.localize('resourceWithCommentLabelFile', - "Comment from ${0} in {1}, source: {2}", + "${0} in {1}, source: {2}", element.comment.userName, basename(element.resource), (typeof element.comment.body === 'string') ? element.comment.body : element.comment.body.value From 5f22badb3297ad80229371d5a1997ac12c690174 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 1 Feb 2024 10:33:09 -0600 Subject: [PATCH 0830/1897] log info -> trace --- src/vs/platform/terminal/node/terminalProfiles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/terminal/node/terminalProfiles.ts b/src/vs/platform/terminal/node/terminalProfiles.ts index e6bb70145b4..eef82654029 100644 --- a/src/vs/platform/terminal/node/terminalProfiles.ts +++ b/src/vs/platform/terminal/node/terminalProfiles.ts @@ -154,7 +154,7 @@ async function detectAvailableWindowsProfiles( } } catch (e) { if (logIfWslNotInstalled) { - logService?.info('WSL is not installed, so could not detect WSL profiles'); + logService?.trace('WSL is not installed, so could not detect WSL profiles'); logIfWslNotInstalled = false; } } From 44fd438df521b26bb360fca7d4fee512415a5ddb Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 1 Feb 2024 17:35:00 +0100 Subject: [PATCH 0831/1897] window - mitigate macOS fullscreen issue with custom title (#203986) --- src/vs/platform/windows/electron-main/windowImpl.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index c63139f8668..6a237f67d77 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -408,7 +408,14 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { this.transientIsNativeFullScreen = undefined; this.joinNativeFullScreenTransition = undefined; - if (!transitioned && fullscreen && fromRestore) { + // There is one interesting gotcha on macOS: when you are opening a new + // window from a fullscreen window, that new window will immediately + // open fullscreen and emit the `enter-full-screen` event even before we + // reach this method. In that case, we actually will timeout after 10s + // for detecting the transition and as such it is important that we only + // signal to leave fullscreen if the window reports as not being in fullscreen. + + if (!transitioned && fullscreen && fromRestore && this.win && !this.win.isFullScreen()) { // We have seen requests for fullscreen failing eventually after some // time, for example when an OS update was performed and windows restore. From b8ac113876c556d5c950fdf1d5829677862d0ef6 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 1 Feb 2024 17:42:09 +0100 Subject: [PATCH 0832/1897] themes: Increase contrast of inputs placeholder text --- extensions/theme-defaults/themes/dark_modern.json | 2 +- extensions/theme-defaults/themes/light_modern.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/theme-defaults/themes/dark_modern.json b/extensions/theme-defaults/themes/dark_modern.json index cdb5c1f5f8f..3a17bc9a32b 100644 --- a/extensions/theme-defaults/themes/dark_modern.json +++ b/extensions/theme-defaults/themes/dark_modern.json @@ -49,7 +49,7 @@ "input.background": "#313131", "input.border": "#3C3C3C", "input.foreground": "#CCCCCC", - "input.placeholderForeground": "#818181", + "input.placeholderForeground": "#989898", "inputOption.activeBackground": "#2489DB82", "inputOption.activeBorder": "#2488DB", "keybindingLabel.foreground": "#CCCCCC", diff --git a/extensions/theme-defaults/themes/light_modern.json b/extensions/theme-defaults/themes/light_modern.json index b37c6275c24..6114d34d96e 100644 --- a/extensions/theme-defaults/themes/light_modern.json +++ b/extensions/theme-defaults/themes/light_modern.json @@ -51,7 +51,7 @@ "input.background": "#FFFFFF", "input.border": "#CECECE", "input.foreground": "#3B3B3B", - "input.placeholderForeground": "#868686", + "input.placeholderForeground": "#767676", "inputOption.activeBackground": "#BED6ED", "inputOption.activeBorder": "#005FB8", "inputOption.activeForeground": "#000000", From 4e67ba102b8adf31171195d9810b7e18f8e0975d Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Thu, 1 Feb 2024 09:14:18 -0800 Subject: [PATCH 0833/1897] dispose actions after selection/application of code action (#203936) dispose actions after selection and application of code action --- .../codeAction/browser/codeActionController.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index 3fbf1bf476b..21f1faa46a0 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -112,7 +112,12 @@ export class CodeActionController extends Disposable implements IEditorContribut command.arguments[0] = { ...command.arguments[0], autoSend: false }; } } - await this._applyCodeAction(actionItem, false, false, ApplyCodeActionReason.FromAILightbulb); + try { + this._lightBulbWidget.value?.hide(); + await this._applyCodeAction(actionItem, false, false, ApplyCodeActionReason.FromAILightbulb); + } finally { + actions.dispose(); + } return; } await this.showCodeActionList(actions, at, { includeDisabledActions: false, fromLightbulb: true }); @@ -279,7 +284,11 @@ export class CodeActionController extends Disposable implements IEditorContribut const delegate: IActionListDelegate = { onSelect: async (action: CodeActionItem, preview?: boolean) => { - this._applyCodeAction(action, /* retrigger */ true, !!preview, ApplyCodeActionReason.FromCodeActions); + try { + await this._applyCodeAction(action, /* retrigger */ true, !!preview, ApplyCodeActionReason.FromCodeActions); + } finally { + actions.dispose(); + } this._actionWidgetService.hide(); currentDecorations.clear(); }, From 17324f8db39c2e71b99e4e707cca0d9e732d349f Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 1 Feb 2024 18:40:32 +0100 Subject: [PATCH 0834/1897] polishing the code --- .../browser/inlineCompletionsModel.ts | 5 +--- .../inlineCompletions/browser/utils.ts | 29 +++++-------------- .../test/browser/utils.test.ts | 4 +-- .../inlineCompletions/test/browser/utils.ts | 8 ++--- 4 files changed, 13 insertions(+), 33 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index c4b1efdf201..cf6cce08787 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -408,10 +408,7 @@ export class InlineCompletionsModel extends Disposable { try { editor.pushUndoStop(); const replaceRange = Range.fromPositions(completion.range.getStartPosition(), position); - const newText = completion.insertText.substring( - 0, - firstPart.column - completion.range.startColumn + acceptUntilIndexExclusive - ); + const newText = completion.insertText.substring(0, firstPart.column - completion.range.startColumn + acceptUntilIndexExclusive); const singleTextEdit = new SingleTextEdit(replaceRange, newText); const edits = this._getEdits(editor, singleTextEdit); editor.executeEdits('inlineSuggestion.accept', edits.edits.map(edit => EditOperation.replaceMove(edit.range, edit.text))); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts index 174191a4f9c..5cc6aeee97c 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts @@ -117,20 +117,6 @@ export function lengthOfText(text: string): Position { * @returns new ranges post edits for every edit */ export function getNewRanges(edits: ISingleEditOperation[]): Range[] { - - if (edits.length === 0) { - return []; - } - // Check if the edits ranges are sorted and disjoint - for (let i = 1; i < edits.length; i++) { - if (Position.isBeforeOrEqual( - Range.lift(edits[i].range).getStartPosition(), - Range.lift(edits[i - 1].range).getEndPosition()) - ) { - throw new Error('Edits are not sorted and disjoint.'); - } - } - const ranges: Range[] = []; let previousEditEndLineNumber = 0; let lineOffset = 0; @@ -172,12 +158,8 @@ export function inverseEdits(model: TextModel, edits: ISingleEditOperation[]): I return inverseEdits; } -/** - * Utility class which can be used to find the sort permutation of an array - */ export class Permutation { - - constructor(public readonly indexMap: number[]) { } + constructor(private readonly _indexMap: number[]) { } public static createSortPermutation(arr: readonly T[], compareFn: (a: T, b: T) => number): Permutation { const sortIndices = Array.from(arr.keys()).sort((index1, index2) => compareFn(arr[index1], arr[index2])); @@ -185,11 +167,14 @@ export class Permutation { } apply(arr: T[]): T[] { - return arr.map((_, index) => arr[this.indexMap[index]]); + return arr.map((_, index) => arr[this._indexMap[index]]); } inverse(): Permutation { - const inverseSortIndices = Array.from(this.indexMap.keys()).sort((index1, index2) => index1 - index2); - return new Permutation(inverseSortIndices); + const inverseIndexMap = this._indexMap.slice(); + for (let i = 0; i < this._indexMap.length; i++) { + inverseIndexMap[this._indexMap[i]] = i; + } + return new Permutation(inverseIndexMap); } } diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts index 150205afe94..dff43547e29 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts @@ -15,10 +15,10 @@ suite('getNewRanges', () => { for (let seed = 0; seed < 20; seed++) { test(`test ${seed}`, () => { - const randomText = randomMultilineString(seed, 10); + const rng = new MersenneTwister(seed); + const randomText = randomMultilineString(rng, 10); const model = createTextModel(randomText); - const rng = new MersenneTwister(seed); const edits = getRandomEditInfos(model, rng.nextIntRange(1, 4), rng, true).map(e => toEdit(e)); const invEdits = inverseEdits(model, edits); diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts index 63f49835b5f..75bda0f3780 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts @@ -133,8 +133,7 @@ export class GhostTextContext extends Disposable { } } -function generateRandomSimpleString(seed: number, stringLength: number): string { - const rng = new MersenneTwister(seed); +function generateRandomSimpleString(rng: MersenneTwister, stringLength: number): string { const possibleCharacters: string = ' abcdefghijklmnopqrstuvwxyz0123456789'; let randomText: string = ''; for (let i = 0; i < stringLength; i++) { @@ -145,12 +144,11 @@ function generateRandomSimpleString(seed: number, stringLength: number): string return randomText; } -export function generateRandomMultilineString(seed: number, numberOfLines: number, maximumLengthOfLines: number = 20): string { - const rng = new MersenneTwister(seed); +export function generateRandomMultilineString(rng: MersenneTwister, numberOfLines: number, maximumLengthOfLines: number = 20): string { let randomText: string = ''; for (let i = 0; i < numberOfLines; i++) { const lengthOfLine = rng.nextIntRange(0, maximumLengthOfLines + 1); - randomText += generateRandomSimpleString(seed, lengthOfLine) + '\n'; + randomText += generateRandomSimpleString(rng, lengthOfLine) + '\n'; } return randomText; } From 05be5599c43bde07c8a88b84ef10e14bc564840d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 1 Feb 2024 15:07:21 -0300 Subject: [PATCH 0835/1897] Clean up unused element (#203994) Leftover from progress refactoring, and leading to extra space in the chat row --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index ba054389137..ba0918f7875 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -74,7 +74,6 @@ interface IChatListItemTemplate { readonly agentAvatarContainer: HTMLElement; readonly username: HTMLElement; readonly detail: HTMLElement; - readonly progressSteps: HTMLElement; readonly value: HTMLElement; readonly referencesListContainer: HTMLElement; readonly contextKeyService: IContextKeyService; @@ -241,7 +240,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer Date: Thu, 1 Feb 2024 10:07:46 -0800 Subject: [PATCH 0836/1897] cli: update dev tunnels (#203997) Fixes #201417 --- cli/Cargo.lock | 2 +- cli/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/Cargo.lock b/cli/Cargo.lock index c102791a69b..553df3f8f53 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -2526,7 +2526,7 @@ dependencies = [ [[package]] name = "tunnels" version = "0.1.0" -source = "git+https://github.com/microsoft/dev-tunnels?rev=c1bf1424846ce3a1297ed3be7b8401cef6904bee#c1bf1424846ce3a1297ed3be7b8401cef6904bee" +source = "git+https://github.com/microsoft/dev-tunnels?rev=4de1ff7979b5758c69218a3f45f6d9784b165072#4de1ff7979b5758c69218a3f45f6d9784b165072" dependencies = [ "async-trait", "chrono", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 334ac37d73c..f51f31e9fb5 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -34,7 +34,7 @@ serde_bytes = "0.11.9" chrono = { version = "0.4.26", features = ["serde", "std", "clock"], default-features = false } gethostname = "0.4.3" libc = "0.2.144" -tunnels = { git = "https://github.com/microsoft/dev-tunnels", rev = "c1bf1424846ce3a1297ed3be7b8401cef6904bee", default-features = false, features = ["connections"] } +tunnels = { git = "https://github.com/microsoft/dev-tunnels", rev = "4de1ff7979b5758c69218a3f45f6d9784b165072", default-features = false, features = ["connections"] } keyring = { version = "2.0.3", default-features = false, features = ["linux-secret-service-rt-tokio-crypto-openssl"] } dialoguer = "0.10.4" hyper = { version = "0.14.26", features = ["server", "http1", "runtime"] } From d7adba43fcd84ab89ec11042a8a6b600f5aa50e3 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 1 Feb 2024 10:21:24 -0800 Subject: [PATCH 0837/1897] Fix find widget top border fallback (#203991) fix #203981. find widget border fallback --- src/vs/editor/contrib/find/browser/findWidget.css | 1 - src/vs/editor/contrib/find/browser/findWidget.ts | 7 ++++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/find/browser/findWidget.css b/src/vs/editor/contrib/find/browser/findWidget.css index 782c19a53b2..5d3ef3277d7 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.css +++ b/src/vs/editor/contrib/find/browser/findWidget.css @@ -15,7 +15,6 @@ box-sizing: border-box; transform: translateY(calc(-100% - 10px)); /* shadow (10px) */ box-shadow: 0 0 8px 2px var(--vscode-widget-shadow); - border: 1px solid var(--vscode-contrastBorder); color: var(--vscode-editorWidget-foreground); border-left: 1px solid var(--vscode-widget-border); border-right: 1px solid var(--vscode-widget-border); diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index d278f2133a1..f89e3d81837 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -35,7 +35,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { asCssVariable, editorFindMatchHighlightBorder, editorFindRangeHighlightBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry'; +import { asCssVariable, contrastBorder, editorFindMatchHighlightBorder, editorFindRangeHighlightBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerIcon, widgetClose } from 'vs/platform/theme/common/iconRegistry'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ThemeIcon } from 'vs/base/common/themables'; @@ -1384,4 +1384,9 @@ registerThemingParticipant((theme, collector) => { if (findRangeHighlightBorder) { collector.addRule(`.monaco-editor .findScope { border: 1px ${isHighContrast(theme.type) ? 'dashed' : 'solid'} ${findRangeHighlightBorder}; }`); } + + const hcBorder = theme.getColor(contrastBorder); + if (hcBorder) { + collector.addRule(`.monaco-editor .find-widget { border: 1px solid ${hcBorder}; }`); + } }); From 13e49a698cf441f82984b357f09ed095779751b8 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 1 Feb 2024 10:22:29 -0800 Subject: [PATCH 0838/1897] testing: rename coverage executionCount to executed, allow bool (#204004) coverage.py does not provide execution count, only a boolean indicating whether any given data was executed at all. Allow extensions to provide boolean values for executions, and rename the properties to `executed` rather than `executionCount`, so that we can handle this properly in the UI and e.g. avoid showing incorrect count badges when the real count is unknown. --- .../api/common/extHostTypeConverters.ts | 6 +-- src/vs/workbench/api/common/extHostTypes.ts | 24 ++++++++--- .../browser/codeCoverageDecorations.ts | 6 +-- .../testing/browser/testCoverageView.ts | 8 ++-- .../contrib/testing/common/testTypes.ts | 12 +++--- .../vscode.proposed.testCoverage.d.ts | 40 +++++++++++-------- 6 files changed, 58 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index e3f2810e3a5..89adbab9dcd 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2003,18 +2003,18 @@ export namespace TestCoverage { export function fromDetailed(coverage: vscode.DetailedCoverage): CoverageDetails.Serialized { if ('branches' in coverage) { return { - count: coverage.executionCount, + count: coverage.executed, location: fromLocation(coverage.location), type: DetailType.Statement, branches: coverage.branches.length - ? coverage.branches.map(b => ({ count: b.executionCount, location: b.location && fromLocation(b.location), label: b.label })) + ? coverage.branches.map(b => ({ count: b.executed, location: b.location && fromLocation(b.location), label: b.label })) : undefined, }; } else { return { type: DetailType.Function, name: coverage.name, - count: coverage.executionCount, + count: coverage.executed, location: fromLocation(coverage.location), }; } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 2b4dd7f530d..2f46615b5a1 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3975,15 +3975,15 @@ export class FileCoverage implements vscode.FileCoverage { for (const detail of details) { if ('branches' in detail) { statements.total += 1; - statements.covered += detail.executionCount > 0 ? 1 : 0; + statements.covered += detail.executed ? 1 : 0; for (const branch of detail.branches) { branches.total += 1; - branches.covered += branch.executionCount > 0 ? 1 : 0; + branches.covered += branch.executed ? 1 : 0; } } else { fn.total += 1; - fn.covered += detail.executionCount > 0 ? 1 : 0; + fn.covered += detail.executed ? 1 : 0; } } @@ -4014,25 +4014,37 @@ export class FileCoverage implements vscode.FileCoverage { } export class StatementCoverage implements vscode.StatementCoverage { + // back compat until finalization: + get executionCount() { return +this.executed; } + set executionCount(n: number) { this.executed = n; } + constructor( - public executionCount: number, + public executed: number | boolean, public location: Position | Range, public branches: vscode.BranchCoverage[] = [], ) { } } export class BranchCoverage implements vscode.BranchCoverage { + // back compat until finalization: + get executionCount() { return +this.executed; } + set executionCount(n: number) { this.executed = n; } + constructor( - public executionCount: number, + public executed: number | boolean, public location: Position | Range, public label?: string, ) { } } export class FunctionCoverage implements vscode.FunctionCoverage { + // back compat until finalization: + get executionCount() { return +this.executed; } + set executionCount(n: number) { this.executed = n; } + constructor( public readonly name: string, - public executionCount: number, + public executed: number | boolean, public location: Position | Range, ) { } } diff --git a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts index da87e3c5f36..0c556853d8c 100644 --- a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts @@ -264,7 +264,7 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri }; } else { target.className = `coverage-deco-inline ${cls}`; - if (primary) { + if (primary && typeof hits === 'number') { target.before = countBadge(hits); } } @@ -286,7 +286,7 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri const applyHoverOptions = (target: IModelDecorationOptions) => { target.className = `coverage-deco-inline ${cls}`; target.hoverMessage = description; - if (primary) { + if (primary && typeof detail.count === 'number') { target.before = countBadge(detail.count); } }; @@ -451,7 +451,7 @@ export class CoverageDetailsModel { const text = wrapName(model.getValueInRange(tidyLocation(detail.location)).trim() || ``); const str = new MarkdownString(); if (detail.branches?.length) { - const covered = detail.branches.filter(b => b.count > 0).length; + const covered = detail.branches.filter(b => !!b.count).length; str.appendMarkdown(localize('coverage.branches', '{0} of {1} of branches in {2} were covered.', covered, detail.branches.length, text)); } else { str.appendMarkdown(localize('coverage.codeExecutedCount', '{0} was executed {1} time(s).', text, detail.count)); diff --git a/src/vs/workbench/contrib/testing/browser/testCoverageView.ts b/src/vs/workbench/contrib/testing/browser/testCoverageView.ts index ecf22d64e7e..5b583bf8322 100644 --- a/src/vs/workbench/contrib/testing/browser/testCoverageView.ts +++ b/src/vs/workbench/contrib/testing/browser/testCoverageView.ts @@ -158,11 +158,11 @@ class FunctionCoverageNode { continue; } - statement.covered += detail.count > 0 ? 1 : 0; + statement.covered += detail.count ? 1 : 0; statement.total++; if (detail.branches) { for (const { count } of detail.branches) { - branch.covered += count > 0 ? 1 : 0; + branch.covered += count ? 1 : 0; branch.total++; } } @@ -402,7 +402,7 @@ class Sorter implements ITreeSorter { const attrA = a.tpc; const attrB = b.tpc; return (attrA !== undefined && attrB !== undefined && attrB - attrA) - || (b.hits - a.hits) + || (+b.hits - +a.hits) || a.label.localeCompare(b.label); } } @@ -522,7 +522,7 @@ class FunctionCoverageRenderer implements ICompressibleTreeRenderer 0; + const covered = !!element.hits; const icon = covered ? testingWasCovered : testingStatesToIcons.get(TestResultState.Unset); templateData.container.classList.toggle('not-covered', !covered); templateData.icon.className = `computed-state ${ThemeIcon.asClassName(icon!)}`; diff --git a/src/vs/workbench/contrib/testing/common/testTypes.ts b/src/vs/workbench/contrib/testing/common/testTypes.ts index 31eaf041f11..ed4ff1c93ea 100644 --- a/src/vs/workbench/contrib/testing/common/testTypes.ts +++ b/src/vs/workbench/contrib/testing/common/testTypes.ts @@ -614,14 +614,14 @@ export namespace CoverageDetails { } export interface IBranchCoverage { - count: number; + count: number | boolean; label?: string; location?: Range | Position; } export namespace IBranchCoverage { export interface Serialized { - count: number; + count: number | boolean; label?: string; location?: IRange | IPosition; } @@ -633,7 +633,7 @@ export namespace IBranchCoverage { export interface IFunctionCoverage { type: DetailType.Function; name: string; - count: number; + count: number | boolean; location: Range | Position; } @@ -641,7 +641,7 @@ export namespace IFunctionCoverage { export interface Serialized { type: DetailType.Function; name: string; - count: number; + count: number | boolean; location: IRange | IPosition; } @@ -651,7 +651,7 @@ export namespace IFunctionCoverage { export interface IStatementCoverage { type: DetailType.Statement; - count: number; + count: number | boolean; location: Range | Position; branches?: IBranchCoverage[]; } @@ -659,7 +659,7 @@ export interface IStatementCoverage { export namespace IStatementCoverage { export interface Serialized { type: DetailType.Statement; - count: number; + count: number | boolean; location: IRange | IPosition; branches?: IBranchCoverage.Serialized[]; } diff --git a/src/vscode-dts/vscode.proposed.testCoverage.d.ts b/src/vscode-dts/vscode.proposed.testCoverage.d.ts index b7eadce83cf..aa0b1fee9ac 100644 --- a/src/vscode-dts/vscode.proposed.testCoverage.d.ts +++ b/src/vscode-dts/vscode.proposed.testCoverage.d.ts @@ -129,10 +129,11 @@ declare module 'vscode' { */ export class StatementCoverage { /** - * The number of times this statement was executed. If zero, the - * statement will be marked as un-covered. + * The number of times this statement was executed, or a boolean indicating + * whether it was executed if the exact count is unknown. If zero or false, + * the statement will be marked as un-covered. */ - executionCount: number; + executed: number | boolean; /** * Statement location. @@ -147,12 +148,13 @@ declare module 'vscode' { /** * @param location The statement position. - * @param executionCount The number of times this statement was - * executed. If zero, the statement will be marked as un-covered. + * @param executed The number of times this statement was executed, or a + * boolean indicating whether it was executed if the exact count is + * unknown. If zero or false, the statement will be marked as un-covered. * @param branches Coverage from branches of this line. If it's not a * conditional, this should be omitted. */ - constructor(executionCount: number, location: Position | Range, branches?: BranchCoverage[]); + constructor(executed: number | boolean, location: Position | Range, branches?: BranchCoverage[]); } /** @@ -160,10 +162,11 @@ declare module 'vscode' { */ export class BranchCoverage { /** - * The number of times this branch was executed. If zero, the - * branch will be marked as un-covered. + * The number of times this branch was executed, or a boolean indicating + * whether it was executed if the exact count is unknown. If zero or false, + * the branch will be marked as un-covered. */ - executionCount: number; + executed: number | boolean; /** * Branch location. @@ -177,10 +180,12 @@ declare module 'vscode' { label?: string; /** - * @param executionCount The number of times this branch was executed. + * @param executed The number of times this branch was executed, or a + * boolean indicating whether it was executed if the exact count is + * unknown. If zero or false, the branch will be marked as un-covered. * @param location The branch position. */ - constructor(executionCount: number, location?: Position | Range, label?: string); + constructor(executed: number | boolean, location?: Position | Range, label?: string); } /** @@ -193,10 +198,11 @@ declare module 'vscode' { name: string; /** - * The number of times this function was executed. If zero, the - * function will be marked as un-covered. + * The number of times this function was executed, or a boolean indicating + * whether it was executed if the exact count is unknown. If zero or false, + * the function will be marked as un-covered. */ - executionCount: number; + executed: number | boolean; /** * Function location. @@ -204,10 +210,12 @@ declare module 'vscode' { location: Position | Range; /** - * @param executionCount The number of times this function was executed. + * @param executed The number of times this function was executed, or a + * boolean indicating whether it was executed if the exact count is + * unknown. If zero or false, the function will be marked as un-covered. * @param location The function position. */ - constructor(name: string, executionCount: number, location: Position | Range); + constructor(name: string, executed: number | boolean, location: Position | Range); } export type DetailedCoverage = StatementCoverage | FunctionCoverage; From 4d97c320b10d4bbcf8cba2006dfe831e3a350f54 Mon Sep 17 00:00:00 2001 From: emilan <60216+emilan@users.noreply.github.com> Date: Thu, 1 Feb 2024 19:16:21 +0100 Subject: [PATCH 0839/1897] Fix for automatic folderOpen tasks with Remote SSH extension In some cases (very often for me), the task configuration is loaded after the RunAutomaticTasks class has searched through the configured tasks to see if there are any automatic tasks to run. Since this search is a one time action, it means that the automatic tasks aren't always run. This fix works around this race condition by exposing an event (onDidChangeTaskConfig) on the ITaskService interface and letting the AbstractTaskService implementation fire this event when it reloads the task configuration. If the RunAutomaticTasks class encounters zero automatic tasks, it waits for up to 10 seconds for this new event to fire before giving up. If the task configuration is updated during this time, RunAutomaticTasks looks for automatic tasks once more in the updated task configuration. --- .../tasks/browser/abstractTaskService.ts | 7 ++-- .../tasks/browser/runAutomaticTasks.ts | 34 +++++++++++++++---- .../contrib/tasks/common/taskService.ts | 1 + 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 0da433a0065..27f92a65ad3 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -231,6 +231,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer public onDidChangeTaskSystemInfo: Event = this._onDidChangeTaskSystemInfo.event; private _onDidReconnectToTasks: Emitter = new Emitter(); public onDidReconnectToTasks: Event = this._onDidReconnectToTasks.event; + private _onDidChangeTaskConfig: Emitter = new Emitter(); + public onDidChangeTaskConfig: Event = this._onDidChangeTaskConfig.event; public get isReconnected(): boolean { return this._tasksReconnected; } constructor( @@ -289,7 +291,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._updateSetup(folderSetup); return this._updateWorkspaceTasks(TaskRunSource.FolderOpen); })); - this._register(this._configurationService.onDidChangeConfiguration((e) => { + this._register(this._configurationService.onDidChangeConfiguration(async (e) => { if (!e.affectsConfiguration('tasks') || (!this._taskSystem && !this._workspaceTasksPromise)) { return; } @@ -306,7 +308,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } this._setTaskLRUCacheLimit(); - return this._updateWorkspaceTasks(TaskRunSource.ConfigurationChange); + await this._updateWorkspaceTasks(TaskRunSource.ConfigurationChange); + this._onDidChangeTaskConfig.fire(); })); this._taskRunningState = TASK_RUNNING_STATE.bindTo(_contextKeyService); this._onDidStateChange = this._register(new Emitter()); diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts index a4b4239b43c..086b48bf096 100644 --- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts +++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts @@ -50,9 +50,34 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut this._logService.trace('RunAutomaticTasks: Awaiting task system info.'); await Event.toPromise(Event.once(this._taskService.onDidChangeTaskSystemInfo)); } - const workspaceTasks = await this._taskService.getWorkspaceTasks(TaskRunSource.FolderOpen); + let workspaceTasks = await this._taskService.getWorkspaceTasks(TaskRunSource.FolderOpen); this._logService.trace(`RunAutomaticTasks: Found ${workspaceTasks.size} automatic tasks`); - await this._runWithPermission(this._taskService, this._configurationService, workspaceTasks); + + let autoTasks = this._findAutoTasks(this._taskService, workspaceTasks); + this._logService.trace(`RunAutomaticTasks: taskNames=${JSON.stringify(autoTasks.taskNames)}`); + + // As seen in some cases with the Remote SSH extension, the tasks configuration is loaded after we have come + // to this point. Let's give it some extra time. + if (autoTasks.taskNames.length === 0) { + const updatedWithinTimeout = await Promise.race([ + new Promise((resolve) => { + Event.toPromise(Event.once(this._taskService.onDidChangeTaskConfig)).then(() => resolve(true)); + }), + new Promise((resolve) => { + const timer = setTimeout(() => { clearTimeout(timer); resolve(false); }, 10000); + })]); + + if (!updatedWithinTimeout) { + this._logService.trace(`RunAutomaticTasks: waited some extra time, but no update of tasks configuration`); + return; + } + + workspaceTasks = await this._taskService.getWorkspaceTasks(TaskRunSource.FolderOpen); + autoTasks = this._findAutoTasks(this._taskService, workspaceTasks); + this._logService.trace(`RunAutomaticTasks: updated taskNames=${JSON.stringify(autoTasks.taskNames)}`); + } + + this._runWithPermission(this._taskService, this._configurationService, autoTasks.tasks, autoTasks.taskNames); } private _runTasks(taskService: ITaskService, tasks: Array>) { @@ -124,10 +149,7 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut return { tasks, taskNames, locations }; } - private async _runWithPermission(taskService: ITaskService, configurationService: IConfigurationService, workspaceTaskResult: Map) { - - const { tasks, taskNames } = this._findAutoTasks(taskService, workspaceTaskResult); - + private async _runWithPermission(taskService: ITaskService, configurationService: IConfigurationService, tasks: (Task | Promise)[], taskNames: string[]) { if (taskNames.length === 0) { return; } diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts index 5799f23fd49..3ef60f6e047 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -94,6 +94,7 @@ export interface ITaskService { registerTaskSystem(scheme: string, taskSystemInfo: ITaskSystemInfo): void; onDidChangeTaskSystemInfo: Event; + onDidChangeTaskConfig: Event; readonly hasTaskSystemInfo: boolean; registerSupportedExecutions(custom?: boolean, shell?: boolean, process?: boolean): void; From 4493cd71c9397548643987d74fa9658a13eeec8a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 1 Feb 2024 15:32:38 -0300 Subject: [PATCH 0840/1897] Avoid using 'deltaDecorations' directly in `setDecorationsByType` (#203902) * Avoid using 'deltaDecorations' directly in `setDecorationsByType` Fix #202374 * Update --- .../editor/browser/widget/codeEditorWidget.ts | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 1950771ff41..1255cd1a598 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -5,8 +5,6 @@ import 'vs/editor/browser/services/markerDecorations'; -import 'vs/css!./media/editor'; -import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; @@ -14,52 +12,54 @@ import { Color } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, EmitterOptions, Event, EventDeliveryQueue, createEventDeliveryQueue } from 'vs/base/common/event'; import { hash } from 'vs/base/common/hash'; -import { Disposable, IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; +import 'vs/css!./media/editor'; +import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; import { EditorConfiguration, IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; +import { TabFocus } from 'vs/editor/browser/config/tabFocus'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { ICommandDelegate } from 'vs/editor/browser/view/viewController'; import { IContentWidgetData, IGlyphMarginWidgetData, IOverlayWidgetData, View } from 'vs/editor/browser/view'; +import { DOMLineBreaksComputerFactory } from 'vs/editor/browser/view/domLineBreaksComputer'; +import { ICommandDelegate } from 'vs/editor/browser/view/viewController'; import { ViewUserInputEvents } from 'vs/editor/browser/view/viewUserInputEvents'; -import { ConfigurationChangedEvent, EditorLayoutInfo, IEditorOptions, EditorOption, IComputedEditorOptions, FindComputedEditorOptionValueById, filterValidationDecorations } from 'vs/editor/common/config/editorOptions'; +import { CodeEditorContributions } from 'vs/editor/browser/widget/codeEditorContributions'; +import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration'; +import { ConfigurationChangedEvent, EditorLayoutInfo, EditorOption, FindComputedEditorOptionValueById, IComputedEditorOptions, IEditorOptions, filterValidationDecorations } from 'vs/editor/common/config/editorOptions'; import { CursorColumns } from 'vs/editor/common/core/cursorColumns'; -import { CursorChangeReason, ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/editor/common/cursorEvents'; +import { IDimension } from 'vs/editor/common/core/dimension'; +import { editorUnnecessaryCodeOpacity } from 'vs/editor/common/core/editorColorRegistry'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; +import { IWordAtPosition } from 'vs/editor/common/core/wordHelper'; +import { WordOperations } from 'vs/editor/common/cursor/cursorWordOperations'; +import { CursorChangeReason, ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/editor/common/cursorEvents'; import { InternalEditorAction } from 'vs/editor/common/editorAction'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { EndOfLinePreference, IIdentifiedSingleEditOperation, IModelDecoration, IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel, ICursorStateComputer, IAttachedView } from 'vs/editor/common/model'; -import { IWordAtPosition } from 'vs/editor/common/core/wordHelper'; +import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { EndOfLinePreference, IAttachedView, ICursorStateComputer, IIdentifiedSingleEditOperation, IModelDecoration, IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { ClassName } from 'vs/editor/common/model/intervalTree'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent } from 'vs/editor/common/textModelEvents'; -import { editorUnnecessaryCodeOpacity } from 'vs/editor/common/core/editorColorRegistry'; -import { editorErrorForeground, editorHintForeground, editorInfoForeground, editorWarningForeground } from 'vs/platform/theme/common/colorRegistry'; import { VerticalRevealType } from 'vs/editor/common/viewEvents'; +import { IEditorWhitespace, IViewModel } from 'vs/editor/common/viewModel'; +import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; +import { OutgoingViewModelEventKind } from 'vs/editor/common/viewModelEventDispatcher'; +import * as nls from 'vs/nls'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyValue, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { editorErrorForeground, editorHintForeground, editorInfoForeground, editorWarningForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; -import { DOMLineBreaksComputerFactory } from 'vs/editor/browser/view/domLineBreaksComputer'; -import { WordOperations } from 'vs/editor/common/cursor/cursorWordOperations'; -import { IEditorWhitespace, IViewModel } from 'vs/editor/common/viewModel'; -import { OutgoingViewModelEventKind } from 'vs/editor/common/viewModelEventDispatcher'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; -import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; -import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration'; -import { IDimension } from 'vs/editor/common/core/dimension'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { CodeEditorContributions } from 'vs/editor/browser/widget/codeEditorContributions'; -import { TabFocus } from 'vs/editor/browser/config/tabFocus'; let EDITOR_ID = 0; @@ -1347,7 +1347,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE // update all decorations const oldDecorationsIds = this._decorationTypeKeysToIds[decorationTypeKey] || []; - this._decorationTypeKeysToIds[decorationTypeKey] = this.deltaDecorations(oldDecorationsIds, newModelDecorations); + this.changeDecorations(accessor => this._decorationTypeKeysToIds[decorationTypeKey] = accessor.deltaDecorations(oldDecorationsIds, newModelDecorations)); } public setDecorationsByTypeFast(decorationTypeKey: string, ranges: IRange[]): void { @@ -1367,14 +1367,14 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE // update all decorations const oldDecorationsIds = this._decorationTypeKeysToIds[decorationTypeKey] || []; - this._decorationTypeKeysToIds[decorationTypeKey] = this.deltaDecorations(oldDecorationsIds, newModelDecorations); + this.changeDecorations(accessor => this._decorationTypeKeysToIds[decorationTypeKey] = accessor.deltaDecorations(oldDecorationsIds, newModelDecorations)); } public removeDecorationsByType(decorationTypeKey: string): void { // remove decorations for type and sub type const oldDecorationsIds = this._decorationTypeKeysToIds[decorationTypeKey]; if (oldDecorationsIds) { - this.deltaDecorations(oldDecorationsIds, []); + this.changeDecorations(accessor => accessor.deltaDecorations(oldDecorationsIds, [])); } if (this._decorationTypeKeysToIds.hasOwnProperty(decorationTypeKey)) { delete this._decorationTypeKeysToIds[decorationTypeKey]; From f6c91c26c6f030c57c699bd0d697499eb489d355 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 1 Feb 2024 17:01:16 -0300 Subject: [PATCH 0841/1897] Fix highlighting agents after reloading the window (#204012) Invalidate cached parsed query when agents are loaded --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index e667ddf78b2..6d421c1974e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -35,6 +35,7 @@ import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWel import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; const $ = dom.$; @@ -145,6 +146,7 @@ export class ChatWidget extends Disposable implements IChatWidget { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IChatService private readonly chatService: IChatService, + @IChatAgentService private readonly chatAgentService: IChatAgentService, @IChatWidgetService chatWidgetService: IChatWidgetService, @IContextMenuService private readonly contextMenuService: IContextMenuService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, @@ -447,9 +449,8 @@ export class ChatWidget extends Disposable implements IChatWidget { }); })); this._register(this.inputPart.onDidChangeHeight(() => this.bodyDimension && this.layout(this.bodyDimension.height, this.bodyDimension.width))); - this._register(this.inputEditor.onDidChangeModelContent(() => { - this.parsedChatRequest = undefined; - })); + this._register(this.inputEditor.onDidChangeModelContent(() => this.parsedChatRequest = undefined)); + this._register(this.chatAgentService.onDidChangeAgents(() => this.parsedChatRequest = undefined)); } private onDidStyleChange(): void { From 240772aaac06dd4d5a41fad57404fd54082d2fc5 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 1 Feb 2024 20:11:13 +0000 Subject: [PATCH 0842/1897] Inline promiseWithResolvers for webviewPreload (#204022) Fixes #204020 --- .../browser/view/renderers/webviewPreloads.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 16e0276f97a..65660694027 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import type * as DOM from 'vs/base/browser/window'; -import { promiseWithResolvers } from 'vs/base/common/async'; import type { Event } from 'vs/base/common/event'; import type { IDisposable } from 'vs/base/common/lifecycle'; import type * as webviewMessages from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages'; @@ -97,6 +96,16 @@ async function webviewPreloads(ctx: PreloadContext) { const textEncoder = new TextEncoder(); const textDecoder = new TextDecoder(); + function promiseWithResolvers(): { promise: Promise; resolve: (value: T | PromiseLike) => void; reject: (err?: any) => void } { + let resolve: (value: T | PromiseLike) => void; + let reject: (reason?: any) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve: resolve!, reject: reject! }; + } + let currentOptions = ctx.options; const isWorkspaceTrusted = ctx.isWorkspaceTrusted; let currentRenderOptions = ctx.renderOptions; From fc5c9bef7a8c5d6e3dcfca2b2f0a72f78ffda076 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Thu, 1 Feb 2024 12:32:59 -0800 Subject: [PATCH 0843/1897] flattens openIssueReporterCommand and debt (#204024) flatten command, cleanup --- .../issue/issueReporterService.ts | 61 ++++++++++--------- src/vs/platform/issue/common/issue.ts | 14 ++--- .../browser/workbench.contribution.ts | 2 - .../issue/electron-sandbox/issueService.ts | 3 +- 4 files changed, 37 insertions(+), 43 deletions(-) diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index ebd76a1d4d5..82de2be993f 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -135,7 +135,7 @@ export class IssueReporter extends Disposable { this.updateUnsupportedMode(configuration.data.isUnsupported); // Handle case where extension is pre-selected through the command - if (configuration.data.command && targetExtension) { + if ((configuration.data.data || configuration.data.uri) && targetExtension) { this.updateExtensionStatus(targetExtension); } } @@ -249,8 +249,8 @@ export class IssueReporter extends Disposable { private async updateIssueReporterUri(extension: IssueReporterExtensionData): Promise { try { - if (extension.command?.uri) { - const uri = URI.revive(extension.command.uri); + if (extension.uri) { + const uri = URI.revive(extension.uri); extension.bugsUrl = uri.toString(); } else { const uri = await this.issueMainService.$getIssueReporterUri(extension.id); @@ -813,8 +813,8 @@ export class IssueReporter extends Disposable { show(extensionDataBlock); } - if (fileOnExtension && selectedExtension?.command?.data) { - const data = selectedExtension?.command?.data; + if (fileOnExtension && selectedExtension?.data) { + const data = selectedExtension?.data; (extensionDataTextArea as HTMLElement).innerText = data.toString(); (extensionDataTextArea as HTMLTextAreaElement).readOnly = true; show(extensionDataBlock); @@ -959,8 +959,8 @@ export class IssueReporter extends Disposable { return false; } - if (selectedExtension?.command?.uri) { - const uri = URI.revive(selectedExtension.command.uri); + if (selectedExtension?.uri) { + const uri = URI.revive(selectedExtension.uri); issueUrl = uri.toString(); } @@ -1174,7 +1174,8 @@ export class IssueReporter extends Disposable { this.issueReporterModel.update({ selectedExtension: matches[0] }); const selectedExtension = this.issueReporterModel.getData().selectedExtension; if (selectedExtension) { - selectedExtension.command = undefined; + selectedExtension.data = undefined; + selectedExtension.uri = undefined; } this.updateExtensionStatus(matches[0]); } else { @@ -1192,33 +1193,33 @@ export class IssueReporter extends Disposable { private clearExtensionData(): void { this.issueReporterModel.update({ extensionData: undefined }); - this.configuration.data.command = undefined; + this.configuration.data.data = undefined; + this.configuration.data.uri = undefined; } private async updateExtensionStatus(extension: IssueReporterExtensionData) { this.issueReporterModel.update({ selectedExtension: extension }); - if (this.configuration.data.command) { - const template = this.configuration.data.command.template; - if (template) { - const descriptionTextArea = this.getElementById('description')!; - const descriptionText = (descriptionTextArea as HTMLTextAreaElement).value; - if (descriptionText === '' || !descriptionText.includes(template.toString())) { - const fullTextArea = descriptionText + (descriptionText === '' ? '' : '\n') + template.toString(); - (descriptionTextArea as HTMLTextAreaElement).value = fullTextArea; - this.issueReporterModel.update({ issueDescription: fullTextArea }); - } - } - const data = this.configuration.data.command.data; - if (data) { - const extensionDataBlock = mainWindow.document.querySelector('.block-extension-data')!; - show(extensionDataBlock); - this.issueReporterModel.update({ extensionData: data }); + const template = this.configuration.data.issueBody; + if (template) { + const descriptionTextArea = this.getElementById('description')!; + const descriptionText = (descriptionTextArea as HTMLTextAreaElement).value; + if (descriptionText === '' || !descriptionText.includes(template.toString())) { + const fullTextArea = descriptionText + (descriptionText === '' ? '' : '\n') + template.toString(); + (descriptionTextArea as HTMLTextAreaElement).value = fullTextArea; + this.issueReporterModel.update({ issueDescription: fullTextArea }); } + } - const uri = this.configuration.data.command.uri; - if (uri) { - this.updateIssueReporterUri(extension); - } + const data = this.configuration.data.data; + if (data) { + const extensionDataBlock = mainWindow.document.querySelector('.block-extension-data')!; + show(extensionDataBlock); + this.issueReporterModel.update({ extensionData: data }); + } + + const uri = this.configuration.data.uri; + if (uri) { + this.updateIssueReporterUri(extension); } // if extension does not have provider/handles, will check for either. If extension is already active, IPC will return [false, false] and will proceed as normal. @@ -1274,7 +1275,7 @@ export class IssueReporter extends Disposable { this.removeLoading(iconElement); } else { this.validateSelectedExtension(); - this.issueReporterModel.update({ extensionData: extension.command?.data ?? undefined }); + this.issueReporterModel.update({ extensionData: extension.data ?? undefined }); const title = (this.getElementById('issue-title')).value; this.searchExtensionIssues(title); } diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index b4e911f1c02..76da6c0fee4 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -57,11 +57,8 @@ export interface IssueReporterExtensionData { extensionTemplate?: string; hasIssueUriRequestHandler?: boolean; hasIssueDataProviders?: boolean; - command?: { - data?: string; - template?: string; - uri?: UriComponents; - }; + data?: string; + uri?: UriComponents; } export interface IssueReporterData extends WindowData { @@ -75,11 +72,8 @@ export interface IssueReporterData extends WindowData { githubAccessToken: string; readonly issueTitle?: string; readonly issueBody?: string; - command?: { - data?: string; - template?: string; - uri?: UriComponents; - }; + data?: string; + uri?: UriComponents; } export interface ISettingSearchResult { diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 3693d849422..c0f395206f7 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -743,10 +743,8 @@ const registry = Registry.as(ConfigurationExtensions.Con 'problems.visibility': { 'type': 'boolean', 'default': true, - 'tags': ['experimental'], 'description': localize('problems.visibility', "Controls whether the problems are visible throughout the editor and workbench."), }, - // TODO: Add additional properties for problems (fine tuning in outline, marker file decorations, etc) } }); diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts index ebc4b66e1dc..8578bc63d35 100644 --- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts @@ -104,7 +104,8 @@ export class NativeIssueService implements IWorkbenchIssueService { hasIssueDataProviders: this._providers.has(extension.identifier.id.toLowerCase()), displayName: manifest.displayName, id: extension.identifier.id, - command: dataOverrides.command, + data: dataOverrides.data, + uri: dataOverrides.uri, isTheme, isBuiltin, extensionData: 'Extensions data loading', From 98715d77b1e1afeb9687941b92f8d2cc53a9ab91 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 1 Feb 2024 14:33:57 -0600 Subject: [PATCH 0844/1897] use on or off for alert setting --- .../audioCues/browser/audioCueService.ts | 18 +- .../browser/accessibility.contribution.ts | 347 +++++++++++++++++ .../browser/audioCues.contribution.ts | 352 +----------------- 3 files changed, 360 insertions(+), 357 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index c8f74e4324d..95e7354ca5a 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -61,13 +61,19 @@ export class AudioCueService extends Disposable implements IAudioCueService { this.configurationService.updateValue('signals.debouncePositionChanges', this.configurationService.getValue('audioCues.debouncePositionChanges')); const config = AudioCue.allAudioCues; for (const c of config) { - const alertValue = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; - const audioCueValue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; + const alertConfig = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; + const audioCue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; const newSettingsKey = c.settingsKey.replace('audioCues.', 'signals.'); - if (alertValue && audioCueValue) { - this.configurationService.updateValue(newSettingsKey, { alert: alertValue, audioCue: audioCueValue }); - } else if (audioCueValue) { - this.configurationService.updateValue(newSettingsKey + '.audioCue', audioCueValue); + if (alertConfig !== undefined && audioCue !== undefined) { + let alert; + if (typeof alertConfig === 'string') { + alert = alertConfig; + } else { + alert = alertConfig === true && this.accessibilityService.isScreenReaderOptimized() ? 'on' : 'off'; + } + this.configurationService.updateValue(newSettingsKey, { alert, audioCue }); + } else if (!c.alertSettingsKey && audioCue !== undefined) { + this.configurationService.updateValue(newSettingsKey + '.audioCue', audioCue); } } } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 884163dd8e2..1999a608d2b 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -15,6 +15,9 @@ import { AccessibilityStatus } from 'vs/workbench/contrib/accessibility/browser/ import { EditorAccessibilityHelpContribution } from 'vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp'; import { SaveAudioCueContribution } from 'vs/workbench/contrib/accessibility/browser/saveAudioCue'; import { CommentsAccessibilityHelpContribution } from 'vs/workbench/contrib/comments/browser/commentsAccessibility'; +import { audioCueFeatureBase } from 'vs/workbench/contrib/audioCues/browser/audioCues.contribution'; +import { Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { localize } from 'vs/nls'; registerAccessibilityConfiguration(); registerSingleton(IAccessibleViewService, AccessibleViewService, InstantiationType.Delayed); @@ -31,3 +34,347 @@ workbenchContributionsRegistry.registerWorkbenchContribution(InlineCompletionsAc workbenchContributionsRegistry.registerWorkbenchContribution2(AccessibilityStatus.ID, AccessibilityStatus, WorkbenchContributionInstantiation.BlockRestore); workbenchContributionsRegistry.registerWorkbenchContribution2(SaveAudioCueContribution.ID, SaveAudioCueContribution, WorkbenchContributionInstantiation.BlockRestore); workbenchContributionsRegistry.registerWorkbenchContribution2(DynamicSpeechAccessibilityConfiguration.ID, DynamicSpeechAccessibilityConfiguration, WorkbenchContributionInstantiation.BlockRestore); + +const signalFeatureBase: IConfigurationPropertySchema = { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: false, + default: { + audioCue: 'auto', + alert: 'on' + } +}; + +export const alertFeatureBase: IConfigurationPropertySchema = { + 'type': 'string', + 'enum': ['on', 'off'], + 'default': 'on', + 'enumDescriptions': [ + localize('audioCues.enabled.on', "Enable alert, will only apply when in screen reader optimized mode."), + localize('audioCues.enabled.off', "Disable alert.") + ], + tags: ['accessibility'], +}; + +Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + id: 'signals', + order: 100, + title: localize('signals', "Signals"), + type: 'object', + 'properties': { + 'signals.debouncePositionChanges': { + 'description': localize('signals.debouncePositionChanges', "Whether or not position changes should be debounced"), + 'type': 'boolean', + 'default': false, + tags: ['accessibility'] + }, + 'signals.lineHasBreakpoint': { + ...signalFeatureBase, + 'description': localize('signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), + ...alertFeatureBase + }, + }, + }, + 'signals.lineHasInlineSuggestion.audioCue': { + 'description': localize('signals.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), + ...audioCueFeatureBase + }, + 'signals.lineHasError': { + ...signalFeatureBase, + 'description': localize('signals.lineHasError', "Plays a signal when the active line has an error."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasError.alert', "Alerts when the active line has an error."), + ...alertFeatureBase + }, + }, + }, + 'signals.lineHasFoldedArea': { + ...signalFeatureBase, + 'description': localize('signals.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasFoldedArea.alert', "Alerts when the active line has a folded area that can be unfolded."), + ...alertFeatureBase + }, + } + }, + 'signals.lineHasWarning': { + ...signalFeatureBase, + 'description': localize('signals.lineHasWarning', "Plays a signal when the active line has a warning."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasWarning.alert', "Alerts when the active line has a warning."), + ...alertFeatureBase + }, + }, + }, + 'signals.onDebugBreak': { + ...signalFeatureBase, + 'description': localize('signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), + 'properties': { + 'audioCue': { + 'description': localize('signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.onDebugBreak.alert', "Alerts when the debugger stopped on a breakpoint."), + ...alertFeatureBase + }, + } + }, + 'signals.noInlayHints': { + ...signalFeatureBase, + 'description': localize('signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), + 'properties': { + 'audioCue': { + 'description': localize('signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.noInlayHints.alert', "Alerts when trying to read a line with inlay hints that has no inlay hints."), + ...alertFeatureBase + }, + } + }, + 'signals.taskCompleted': { + ...signalFeatureBase, + 'description': localize('signals.taskCompleted', "Plays a signal when a task is completed."), + 'properties': { + 'audioCue': { + 'description': localize('signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.taskCompleted.alert', "Alerts when a task is completed."), + ...alertFeatureBase + }, + } + }, + 'signals.taskFailed': { + ...signalFeatureBase, + 'description': localize('signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), + 'properties': { + 'audioCue': { + 'description': localize('signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.taskFailed.alert', "Alerts when a task fails (non-zero exit code)."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalCommandFailed': { + ...signalFeatureBase, + 'description': localize('signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalCommandFailed.alert', "Alerts when a terminal command fails (non-zero exit code)."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalQuickFix': { + ...signalFeatureBase, + 'description': localize('signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalQuickFix.alert', "Alerts when terminal Quick Fixes are available."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalBell': { + ...signalFeatureBase, + 'description': localize('signals.terminalBell', "Plays a signal when the terminal bell is ringing."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalBell.alert', "Alerts when the terminal bell is ringing."), + ...alertFeatureBase + }, + } + }, + 'signals.diffLineInserted.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + }, + 'signals.diffLineDeleted.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), + }, + 'signals.diffLineModified.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + }, + 'signals.notebookCellCompleted': { + ...signalFeatureBase, + 'description': localize('signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), + 'properties': { + 'audioCue': { + 'description': localize('signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.notebookCellCompleted.alert', "Alerts when a notebook cell execution is successfully completed."), + ...alertFeatureBase + }, + } + }, + 'signals.notebookCellFailed': { + ...signalFeatureBase, + 'description': localize('signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), + 'properties': { + 'audioCue': { + 'description': localize('signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.notebookCellFailed.alert', "Alerts when a notebook cell execution fails."), + ...alertFeatureBase + }, + } + }, + 'signals.chatRequestSent': { + ...signalFeatureBase, + 'description': localize('signals.chatRequestSent', "Plays a signal when a chat request is made."), + 'properties': { + 'audioCue': { + 'description': localize('signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.chatRequestSent.alert', "Alerts when a chat request is made."), + ...alertFeatureBase + }, + } + }, + 'signals.chatResponsePending': { + ...signalFeatureBase, + 'description': localize('signals.chatResponsePending', "Plays a signal on loop while the response is pending."), + 'properties': { + 'audioCue': { + 'description': localize('signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), + ...alertFeatureBase + }, + }, + }, + 'signals.chatResponseReceived.audioCue': { + 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), + ...audioCueFeatureBase + }, + 'signals.clear': { + ...signalFeatureBase, + 'description': localize('signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), + 'properties': { + 'audioCue': { + 'description': localize('signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.clear.alert', "Alerts when a feature is cleared."), + ...alertFeatureBase + }, + }, + }, + 'signals.save': { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, + 'markdownDescription': localize('signals.save', "Plays a signal when a file is saved."), + 'properties': { + 'audioCue': { + 'description': localize('signals.save.audioCue', "Plays an audio cue when a file is saved."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), + localize('signals.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), + localize('signals.save.audioCue.never', "Never plays the audio cue.") + ], + }, + 'alert': { + 'description': localize('signals.save.alert', "Alerts when a file is saved."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), + localize('signals.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), + localize('signals.save.alert.never', "Never plays the audio cue.") + ], + }, + }, + default: { + 'audioCue': 'never', + 'alert': 'never' + } + }, + 'signals.format': { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, + 'markdownDescription': localize('signals.format', "Plays a signal when a file or notebook is formatted."), + 'properties': { + 'audioCue': { + 'description': localize('signals.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), + localize('signals.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('signals.format.never', "Never plays the audio cue.") + ], + }, + 'alert': { + 'description': localize('signals.format.alert', "Alerts when a file or notebook is formatted."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), + localize('signals.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('signals.format.alert.never', "Never plays the alert.") + ], + }, + } + }, + } +}); diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index b066ce9a2ba..9059db0b31f 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -20,7 +20,7 @@ registerSingleton(IAudioCueService, AudioCueService, InstantiationType.Delayed); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AudioCueLineFeatureContribution, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AudioCueLineDebuggerContribution, LifecyclePhase.Restored); -const audioCueFeatureBase: IConfigurationPropertySchema = { +export const audioCueFeatureBase: IConfigurationPropertySchema = { 'type': 'string', 'enum': ['auto', 'on', 'off'], 'default': 'auto', @@ -37,356 +37,6 @@ const audioCueDeprecatedFeatureBase: IConfigurationPropertySchema = { markdownDeprecationMessage }; -const alertFeatureBase: IConfigurationPropertySchema = { - 'type': 'boolean', - 'default': true, - 'tags': ['accessibility'], - required: ['audioCue'] -}; - -const signalFeatureBase: IConfigurationPropertySchema = { - 'type': 'object', - 'tags': ['accessibility'], - additionalProperties: false, - default: { - audioCue: 'auto', - alert: true - } -}; - -Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ - id: 'signals', - order: 100, - title: localize('signals', "Signals"), - type: 'object', - 'properties': { - 'signals.debouncePositionChanges': { - 'description': localize('signals.debouncePositionChanges', "Whether or not position changes should be debounced"), - 'type': 'boolean', - 'default': false, - tags: ['accessibility'] - }, - 'signals.lineHasBreakpoint': { - ...signalFeatureBase, - 'description': localize('signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), - ...alertFeatureBase - }, - }, - }, - 'signals.lineHasInlineSuggestion': { - ...signalFeatureBase, - 'description': localize('signals.lineHasInlineSuggestion', "Plays a signal when the active line has an inline suggestion."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasInlineSuggestion.alert', "Alerts when the active line has an inline suggestion."), - ...alertFeatureBase - }, - } - }, - 'signals.lineHasError': { - ...signalFeatureBase, - 'description': localize('signals.lineHasError', "Plays a signal when the active line has an error."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasError.alert', "Alerts when the active line has an error."), - ...alertFeatureBase - }, - }, - }, - 'signals.lineHasFoldedArea': { - ...signalFeatureBase, - 'description': localize('signals.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasFoldedArea.alert', "Alerts when the active line has a folded area that can be unfolded."), - ...alertFeatureBase - }, - } - }, - 'signals.lineHasWarning': { - ...signalFeatureBase, - 'description': localize('signals.lineHasWarning', "Plays a signal when the active line has a warning."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasWarning.alert', "Alerts when the active line has a warning."), - ...alertFeatureBase - }, - }, - }, - 'signals.onDebugBreak': { - ...signalFeatureBase, - 'description': localize('signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), - 'properties': { - 'audioCue': { - 'description': localize('signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.onDebugBreak.alert', "Alerts when the debugger stopped on a breakpoint."), - ...alertFeatureBase - }, - } - }, - 'signals.noInlayHints': { - ...signalFeatureBase, - 'description': localize('signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), - 'properties': { - 'audioCue': { - 'description': localize('signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.noInlayHints.alert', "Alerts when trying to read a line with inlay hints that has no inlay hints."), - ...alertFeatureBase - }, - } - }, - 'signals.taskCompleted': { - ...signalFeatureBase, - 'description': localize('signals.taskCompleted', "Plays a signal when a task is completed."), - 'properties': { - 'audioCue': { - 'description': localize('signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.taskCompleted.alert', "Alerts when a task is completed."), - ...alertFeatureBase - }, - } - }, - 'signals.taskFailed': { - ...signalFeatureBase, - 'description': localize('signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), - 'properties': { - 'audioCue': { - 'description': localize('signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.taskFailed.alert', "Alerts when a task fails (non-zero exit code)."), - ...alertFeatureBase - }, - } - }, - 'signals.terminalCommandFailed': { - ...signalFeatureBase, - 'description': localize('signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), - 'properties': { - 'audioCue': { - 'description': localize('signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.terminalCommandFailed.alert', "Alerts when a terminal command fails (non-zero exit code)."), - ...alertFeatureBase - }, - } - }, - 'signals.terminalQuickFix': { - ...signalFeatureBase, - 'description': localize('signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), - 'properties': { - 'audioCue': { - 'description': localize('signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.terminalQuickFix.alert', "Alerts when terminal Quick Fixes are available."), - ...alertFeatureBase - }, - } - }, - 'signals.terminalBell': { - ...signalFeatureBase, - 'description': localize('signals.terminalBell', "Plays a signal when the terminal bell is ringing."), - 'properties': { - 'audioCue': { - 'description': localize('signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.terminalBell.alert', "Alerts when the terminal bell is ringing."), - ...alertFeatureBase - }, - } - }, - 'signals.diffLineInserted.audioCue': { - ...audioCueFeatureBase, - 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - }, - 'signals.diffLineDeleted.audioCue': { - ...audioCueFeatureBase, - 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), - }, - 'signals.diffLineModified.audioCue': { - ...audioCueFeatureBase, - 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - }, - 'signals.notebookCellCompleted': { - ...signalFeatureBase, - 'description': localize('signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), - 'properties': { - 'audioCue': { - 'description': localize('signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.notebookCellCompleted.alert', "Alerts when a notebook cell execution is successfully completed."), - ...alertFeatureBase - }, - } - }, - 'signals.notebookCellFailed': { - ...signalFeatureBase, - 'description': localize('signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), - 'properties': { - 'audioCue': { - 'description': localize('signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.notebookCellFailed.alert', "Alerts when a notebook cell execution fails."), - ...alertFeatureBase - }, - } - }, - 'signals.chatRequestSent': { - ...signalFeatureBase, - 'description': localize('signals.chatRequestSent', "Plays a signal when a chat request is made."), - 'properties': { - 'audioCue': { - 'description': localize('signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.chatRequestSent.alert', "Alerts when a chat request is made."), - ...alertFeatureBase - }, - } - }, - 'signals.chatResponsePending': { - ...signalFeatureBase, - 'description': localize('signals.chatResponsePending', "Plays a signal on loop while the response is pending."), - 'properties': { - 'audioCue': { - 'description': localize('signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), - ...alertFeatureBase - }, - }, - }, - 'signals.chatResponseReceived.audioCue': { - 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), - ...audioCueFeatureBase - }, - 'signals.clear': { - ...signalFeatureBase, - 'description': localize('signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), - 'properties': { - 'audioCue': { - 'description': localize('signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.clear.alert', "Alerts when a feature is cleared."), - ...alertFeatureBase - }, - }, - }, - 'signals.save': { - 'type': 'object', - 'tags': ['accessibility'], - additionalProperties: true, - 'markdownDescription': localize('signals.save', "Plays a signal when a file is saved."), - 'properties': { - 'audioCue': { - 'description': localize('signals.save.audioCue', "Plays an audio cue when a file is saved."), - 'type': 'string', - 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', - 'enumDescriptions': [ - localize('signals.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), - localize('signals.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), - localize('signals.save.audioCue.never', "Never plays the audio cue.") - ], - }, - 'alert': { - 'description': localize('signals.save.alert', "Alerts when a file is saved."), - 'type': 'string', - 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', - 'enumDescriptions': [ - localize('signals.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), - localize('signals.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), - localize('signals.save.alert.never', "Never plays the audio cue.") - ], - }, - }, - default: { - 'audioCue': 'never', - 'alert': 'never' - } - }, - 'signals.format': { - 'type': 'object', - 'tags': ['accessibility'], - additionalProperties: true, - 'markdownDescription': localize('signals.format', "Plays a signal when a file or notebook is formatted."), - 'properties': { - 'audioCue': { - 'description': localize('signals.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), - 'type': 'string', - 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', - 'enumDescriptions': [ - localize('signals.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), - localize('signals.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('signals.format.never', "Never plays the audio cue.") - ], - }, - 'alert': { - 'description': localize('signals.format.alert', "Alerts when a file or notebook is formatted."), - 'type': 'string', - 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', - 'enumDescriptions': [ - localize('signals.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), - localize('signals.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('signals.format.alert.never', "Never plays the alert.") - ], - }, - } - }, - } -}); - Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ 'properties': { 'audioCues.enabled': { From 4c30c08b665f8e61d8118145c30a2fbc0f7bd097 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 1 Feb 2024 14:48:33 -0600 Subject: [PATCH 0845/1897] fix issue --- src/vs/platform/audioCues/browser/audioCueService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 95e7354ca5a..0ad2662570f 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -61,8 +61,8 @@ export class AudioCueService extends Disposable implements IAudioCueService { this.configurationService.updateValue('signals.debouncePositionChanges', this.configurationService.getValue('audioCues.debouncePositionChanges')); const config = AudioCue.allAudioCues; for (const c of config) { - const alertConfig = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; - const audioCue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; + const alertConfig = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; + const audioCue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; const newSettingsKey = c.settingsKey.replace('audioCues.', 'signals.'); if (alertConfig !== undefined && audioCue !== undefined) { let alert; From 8c8dbd3b7f094bb328746a8eabedf9057564b9de Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 1 Feb 2024 17:56:46 -0300 Subject: [PATCH 0846/1897] Don't suggest agents after text in the chat input (#204028) Fix microsoft/vscode-copilot-release#816 --- .../contrib/chat/browser/contrib/chatInputEditorContrib.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 84956526adb..beeb2bdeeab 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -380,7 +380,7 @@ class AgentCompletions extends Disposable { label: withAt, insertText: `${withAt} `, detail: c.metadata.description, - range, + range: new Range(1, 1, 1, 1), kind: CompletionItemKind.Text, // The icons are disabled here anyway }; }) From 916c5ee556ec473f1792e382a3462c43804e2e46 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 1 Feb 2024 15:13:36 -0600 Subject: [PATCH 0847/1897] auto, off as options --- src/vs/platform/audioCues/browser/audioCueService.ts | 2 +- .../accessibility/browser/accessibility.contribution.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 0ad2662570f..668ebf9ed83 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -69,7 +69,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { if (typeof alertConfig === 'string') { alert = alertConfig; } else { - alert = alertConfig === true && this.accessibilityService.isScreenReaderOptimized() ? 'on' : 'off'; + alert = alertConfig === false ? 'off' : 'auto'; } this.configurationService.updateValue(newSettingsKey, { alert, audioCue }); } else if (!c.alertSettingsKey && audioCue !== undefined) { diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 1999a608d2b..3fc5ccd2918 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -41,16 +41,16 @@ const signalFeatureBase: IConfigurationPropertySchema = { additionalProperties: false, default: { audioCue: 'auto', - alert: 'on' + alert: 'auto' } }; export const alertFeatureBase: IConfigurationPropertySchema = { 'type': 'string', - 'enum': ['on', 'off'], - 'default': 'on', + 'enum': ['auto', 'off'], + 'default': 'auto', 'enumDescriptions': [ - localize('audioCues.enabled.on', "Enable alert, will only apply when in screen reader optimized mode."), + localize('audioCues.enabled.auto', "Enable alert, will only play when in screen reader optimized mode."), localize('audioCues.enabled.off', "Disable alert.") ], tags: ['accessibility'], From b65b69e5dd59ff9528fdbc6df11ae893a69d78a4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 1 Feb 2024 15:19:51 -0600 Subject: [PATCH 0848/1897] move to within accessibility configuration --- .../browser/accessibility.contribution.ts | 347 ------------------ .../browser/accessibilityConfiguration.ts | 338 ++++++++++++++++- 2 files changed, 337 insertions(+), 348 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 3fc5ccd2918..884163dd8e2 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -15,9 +15,6 @@ import { AccessibilityStatus } from 'vs/workbench/contrib/accessibility/browser/ import { EditorAccessibilityHelpContribution } from 'vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp'; import { SaveAudioCueContribution } from 'vs/workbench/contrib/accessibility/browser/saveAudioCue'; import { CommentsAccessibilityHelpContribution } from 'vs/workbench/contrib/comments/browser/commentsAccessibility'; -import { audioCueFeatureBase } from 'vs/workbench/contrib/audioCues/browser/audioCues.contribution'; -import { Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { localize } from 'vs/nls'; registerAccessibilityConfiguration(); registerSingleton(IAccessibleViewService, AccessibleViewService, InstantiationType.Delayed); @@ -34,347 +31,3 @@ workbenchContributionsRegistry.registerWorkbenchContribution(InlineCompletionsAc workbenchContributionsRegistry.registerWorkbenchContribution2(AccessibilityStatus.ID, AccessibilityStatus, WorkbenchContributionInstantiation.BlockRestore); workbenchContributionsRegistry.registerWorkbenchContribution2(SaveAudioCueContribution.ID, SaveAudioCueContribution, WorkbenchContributionInstantiation.BlockRestore); workbenchContributionsRegistry.registerWorkbenchContribution2(DynamicSpeechAccessibilityConfiguration.ID, DynamicSpeechAccessibilityConfiguration, WorkbenchContributionInstantiation.BlockRestore); - -const signalFeatureBase: IConfigurationPropertySchema = { - 'type': 'object', - 'tags': ['accessibility'], - additionalProperties: false, - default: { - audioCue: 'auto', - alert: 'auto' - } -}; - -export const alertFeatureBase: IConfigurationPropertySchema = { - 'type': 'string', - 'enum': ['auto', 'off'], - 'default': 'auto', - 'enumDescriptions': [ - localize('audioCues.enabled.auto', "Enable alert, will only play when in screen reader optimized mode."), - localize('audioCues.enabled.off', "Disable alert.") - ], - tags: ['accessibility'], -}; - -Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ - id: 'signals', - order: 100, - title: localize('signals', "Signals"), - type: 'object', - 'properties': { - 'signals.debouncePositionChanges': { - 'description': localize('signals.debouncePositionChanges', "Whether or not position changes should be debounced"), - 'type': 'boolean', - 'default': false, - tags: ['accessibility'] - }, - 'signals.lineHasBreakpoint': { - ...signalFeatureBase, - 'description': localize('signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), - ...alertFeatureBase - }, - }, - }, - 'signals.lineHasInlineSuggestion.audioCue': { - 'description': localize('signals.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), - ...audioCueFeatureBase - }, - 'signals.lineHasError': { - ...signalFeatureBase, - 'description': localize('signals.lineHasError', "Plays a signal when the active line has an error."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasError.alert', "Alerts when the active line has an error."), - ...alertFeatureBase - }, - }, - }, - 'signals.lineHasFoldedArea': { - ...signalFeatureBase, - 'description': localize('signals.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasFoldedArea.alert', "Alerts when the active line has a folded area that can be unfolded."), - ...alertFeatureBase - }, - } - }, - 'signals.lineHasWarning': { - ...signalFeatureBase, - 'description': localize('signals.lineHasWarning', "Plays a signal when the active line has a warning."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasWarning.alert', "Alerts when the active line has a warning."), - ...alertFeatureBase - }, - }, - }, - 'signals.onDebugBreak': { - ...signalFeatureBase, - 'description': localize('signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), - 'properties': { - 'audioCue': { - 'description': localize('signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.onDebugBreak.alert', "Alerts when the debugger stopped on a breakpoint."), - ...alertFeatureBase - }, - } - }, - 'signals.noInlayHints': { - ...signalFeatureBase, - 'description': localize('signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), - 'properties': { - 'audioCue': { - 'description': localize('signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.noInlayHints.alert', "Alerts when trying to read a line with inlay hints that has no inlay hints."), - ...alertFeatureBase - }, - } - }, - 'signals.taskCompleted': { - ...signalFeatureBase, - 'description': localize('signals.taskCompleted', "Plays a signal when a task is completed."), - 'properties': { - 'audioCue': { - 'description': localize('signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.taskCompleted.alert', "Alerts when a task is completed."), - ...alertFeatureBase - }, - } - }, - 'signals.taskFailed': { - ...signalFeatureBase, - 'description': localize('signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), - 'properties': { - 'audioCue': { - 'description': localize('signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.taskFailed.alert', "Alerts when a task fails (non-zero exit code)."), - ...alertFeatureBase - }, - } - }, - 'signals.terminalCommandFailed': { - ...signalFeatureBase, - 'description': localize('signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), - 'properties': { - 'audioCue': { - 'description': localize('signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.terminalCommandFailed.alert', "Alerts when a terminal command fails (non-zero exit code)."), - ...alertFeatureBase - }, - } - }, - 'signals.terminalQuickFix': { - ...signalFeatureBase, - 'description': localize('signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), - 'properties': { - 'audioCue': { - 'description': localize('signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.terminalQuickFix.alert', "Alerts when terminal Quick Fixes are available."), - ...alertFeatureBase - }, - } - }, - 'signals.terminalBell': { - ...signalFeatureBase, - 'description': localize('signals.terminalBell', "Plays a signal when the terminal bell is ringing."), - 'properties': { - 'audioCue': { - 'description': localize('signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.terminalBell.alert', "Alerts when the terminal bell is ringing."), - ...alertFeatureBase - }, - } - }, - 'signals.diffLineInserted.audioCue': { - ...audioCueFeatureBase, - 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - }, - 'signals.diffLineDeleted.audioCue': { - ...audioCueFeatureBase, - 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), - }, - 'signals.diffLineModified.audioCue': { - ...audioCueFeatureBase, - 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - }, - 'signals.notebookCellCompleted': { - ...signalFeatureBase, - 'description': localize('signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), - 'properties': { - 'audioCue': { - 'description': localize('signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.notebookCellCompleted.alert', "Alerts when a notebook cell execution is successfully completed."), - ...alertFeatureBase - }, - } - }, - 'signals.notebookCellFailed': { - ...signalFeatureBase, - 'description': localize('signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), - 'properties': { - 'audioCue': { - 'description': localize('signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.notebookCellFailed.alert', "Alerts when a notebook cell execution fails."), - ...alertFeatureBase - }, - } - }, - 'signals.chatRequestSent': { - ...signalFeatureBase, - 'description': localize('signals.chatRequestSent', "Plays a signal when a chat request is made."), - 'properties': { - 'audioCue': { - 'description': localize('signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.chatRequestSent.alert', "Alerts when a chat request is made."), - ...alertFeatureBase - }, - } - }, - 'signals.chatResponsePending': { - ...signalFeatureBase, - 'description': localize('signals.chatResponsePending', "Plays a signal on loop while the response is pending."), - 'properties': { - 'audioCue': { - 'description': localize('signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), - ...alertFeatureBase - }, - }, - }, - 'signals.chatResponseReceived.audioCue': { - 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), - ...audioCueFeatureBase - }, - 'signals.clear': { - ...signalFeatureBase, - 'description': localize('signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), - 'properties': { - 'audioCue': { - 'description': localize('signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.clear.alert', "Alerts when a feature is cleared."), - ...alertFeatureBase - }, - }, - }, - 'signals.save': { - 'type': 'object', - 'tags': ['accessibility'], - additionalProperties: true, - 'markdownDescription': localize('signals.save', "Plays a signal when a file is saved."), - 'properties': { - 'audioCue': { - 'description': localize('signals.save.audioCue', "Plays an audio cue when a file is saved."), - 'type': 'string', - 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', - 'enumDescriptions': [ - localize('signals.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), - localize('signals.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), - localize('signals.save.audioCue.never', "Never plays the audio cue.") - ], - }, - 'alert': { - 'description': localize('signals.save.alert', "Alerts when a file is saved."), - 'type': 'string', - 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', - 'enumDescriptions': [ - localize('signals.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), - localize('signals.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), - localize('signals.save.alert.never', "Never plays the audio cue.") - ], - }, - }, - default: { - 'audioCue': 'never', - 'alert': 'never' - } - }, - 'signals.format': { - 'type': 'object', - 'tags': ['accessibility'], - additionalProperties: true, - 'markdownDescription': localize('signals.format', "Plays a signal when a file or notebook is formatted."), - 'properties': { - 'audioCue': { - 'description': localize('signals.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), - 'type': 'string', - 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', - 'enumDescriptions': [ - localize('signals.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), - localize('signals.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('signals.format.never', "Never plays the audio cue.") - ], - }, - 'alert': { - 'description': localize('signals.format.alert', "Alerts when a file or notebook is formatted."), - 'type': 'string', - 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', - 'enumDescriptions': [ - localize('signals.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), - localize('signals.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('signals.format.alert.never', "Never plays the alert.") - ], - }, - } - }, - } -}); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index a73686b7b48..79a8445191a 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -13,6 +13,7 @@ import { ISpeechService } from 'vs/workbench/contrib/speech/common/speechService import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Event } from 'vs/base/common/event'; +import { audioCueFeatureBase } from 'vs/workbench/contrib/audioCues/browser/audioCues.contribution'; export const accessibilityHelpIsShown = new RawContextKey('accessibilityHelpIsShown', false, true); export const accessibleViewIsShown = new RawContextKey('accessibleViewIsShown', false, true); @@ -83,13 +84,34 @@ const baseAlertProperty: IConfigurationPropertySchema = { markdownDeprecationMessage }; - export const accessibilityConfigurationNodeBase = Object.freeze({ id: 'accessibility', title: localize('accessibilityConfigurationTitle', "Accessibility"), type: 'object' }); + +const signalFeatureBase: IConfigurationPropertySchema = { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: false, + default: { + audioCue: 'auto', + alert: 'auto' + } +}; + +export const alertFeatureBase: IConfigurationPropertySchema = { + 'type': 'string', + 'enum': ['auto', 'off'], + 'default': 'auto', + 'enumDescriptions': [ + localize('audioCues.enabled.auto', "Enable alert, will only play when in screen reader optimized mode."), + localize('audioCues.enabled.off', "Disable alert.") + ], + tags: ['accessibility'], +}; + const configuration: IConfigurationNode = { ...accessibilityConfigurationNodeBase, properties: { @@ -235,6 +257,320 @@ const configuration: IConfigurationNode = { type: 'boolean', default: true }, + 'signals.debouncePositionChanges': { + 'description': localize('signals.debouncePositionChanges', "Whether or not position changes should be debounced"), + 'type': 'boolean', + 'default': false, + tags: ['accessibility'] + }, + 'signals.lineHasBreakpoint': { + ...signalFeatureBase, + 'description': localize('signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), + ...alertFeatureBase + }, + }, + }, + 'signals.lineHasInlineSuggestion.audioCue': { + 'description': localize('signals.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), + ...audioCueFeatureBase + }, + 'signals.lineHasError': { + ...signalFeatureBase, + 'description': localize('signals.lineHasError', "Plays a signal when the active line has an error."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasError.alert', "Alerts when the active line has an error."), + ...alertFeatureBase + }, + }, + }, + 'signals.lineHasFoldedArea': { + ...signalFeatureBase, + 'description': localize('signals.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasFoldedArea.alert', "Alerts when the active line has a folded area that can be unfolded."), + ...alertFeatureBase + }, + } + }, + 'signals.lineHasWarning': { + ...signalFeatureBase, + 'description': localize('signals.lineHasWarning', "Plays a signal when the active line has a warning."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasWarning.alert', "Alerts when the active line has a warning."), + ...alertFeatureBase + }, + }, + }, + 'signals.onDebugBreak': { + ...signalFeatureBase, + 'description': localize('signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), + 'properties': { + 'audioCue': { + 'description': localize('signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.onDebugBreak.alert', "Alerts when the debugger stopped on a breakpoint."), + ...alertFeatureBase + }, + } + }, + 'signals.noInlayHints': { + ...signalFeatureBase, + 'description': localize('signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), + 'properties': { + 'audioCue': { + 'description': localize('signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.noInlayHints.alert', "Alerts when trying to read a line with inlay hints that has no inlay hints."), + ...alertFeatureBase + }, + } + }, + 'signals.taskCompleted': { + ...signalFeatureBase, + 'description': localize('signals.taskCompleted', "Plays a signal when a task is completed."), + 'properties': { + 'audioCue': { + 'description': localize('signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.taskCompleted.alert', "Alerts when a task is completed."), + ...alertFeatureBase + }, + } + }, + 'signals.taskFailed': { + ...signalFeatureBase, + 'description': localize('signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), + 'properties': { + 'audioCue': { + 'description': localize('signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.taskFailed.alert', "Alerts when a task fails (non-zero exit code)."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalCommandFailed': { + ...signalFeatureBase, + 'description': localize('signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalCommandFailed.alert', "Alerts when a terminal command fails (non-zero exit code)."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalQuickFix': { + ...signalFeatureBase, + 'description': localize('signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalQuickFix.alert', "Alerts when terminal Quick Fixes are available."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalBell': { + ...signalFeatureBase, + 'description': localize('signals.terminalBell', "Plays a signal when the terminal bell is ringing."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalBell.alert', "Alerts when the terminal bell is ringing."), + ...alertFeatureBase + }, + } + }, + 'signals.diffLineInserted.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + }, + 'signals.diffLineDeleted.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), + }, + 'signals.diffLineModified.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + }, + 'signals.notebookCellCompleted': { + ...signalFeatureBase, + 'description': localize('signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), + 'properties': { + 'audioCue': { + 'description': localize('signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.notebookCellCompleted.alert', "Alerts when a notebook cell execution is successfully completed."), + ...alertFeatureBase + }, + } + }, + 'signals.notebookCellFailed': { + ...signalFeatureBase, + 'description': localize('signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), + 'properties': { + 'audioCue': { + 'description': localize('signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.notebookCellFailed.alert', "Alerts when a notebook cell execution fails."), + ...alertFeatureBase + }, + } + }, + 'signals.chatRequestSent': { + ...signalFeatureBase, + 'description': localize('signals.chatRequestSent', "Plays a signal when a chat request is made."), + 'properties': { + 'audioCue': { + 'description': localize('signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.chatRequestSent.alert', "Alerts when a chat request is made."), + ...alertFeatureBase + }, + } + }, + 'signals.chatResponsePending': { + ...signalFeatureBase, + 'description': localize('signals.chatResponsePending', "Plays a signal on loop while the response is pending."), + 'properties': { + 'audioCue': { + 'description': localize('signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), + ...alertFeatureBase + }, + }, + }, + 'signals.chatResponseReceived.audioCue': { + 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), + ...audioCueFeatureBase + }, + 'signals.clear': { + ...signalFeatureBase, + 'description': localize('signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), + 'properties': { + 'audioCue': { + 'description': localize('signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.clear.alert', "Alerts when a feature is cleared."), + ...alertFeatureBase + }, + }, + }, + 'signals.save': { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, + 'markdownDescription': localize('signals.save', "Plays a signal when a file is saved."), + 'properties': { + 'audioCue': { + 'description': localize('signals.save.audioCue', "Plays an audio cue when a file is saved."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), + localize('signals.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), + localize('signals.save.audioCue.never', "Never plays the audio cue.") + ], + }, + 'alert': { + 'description': localize('signals.save.alert', "Alerts when a file is saved."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), + localize('signals.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), + localize('signals.save.alert.never', "Never plays the audio cue.") + ], + }, + }, + default: { + 'audioCue': 'never', + 'alert': 'never' + } + }, + 'signals.format': { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, + 'markdownDescription': localize('signals.format', "Plays a signal when a file or notebook is formatted."), + 'properties': { + 'audioCue': { + 'description': localize('signals.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), + localize('signals.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('signals.format.never', "Never plays the audio cue.") + ], + }, + 'alert': { + 'description': localize('signals.format.alert', "Alerts when a file or notebook is formatted."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), + localize('signals.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('signals.format.alert.never', "Never plays the alert.") + ], + }, + } + }, } }; From f81165f9ff7bc7a7ba9850145ce5487e86086d86 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 1 Feb 2024 15:23:08 -0600 Subject: [PATCH 0849/1897] signals -> accessibility.statusIndicators --- .../browser/accessibilityConfiguration.ts | 192 +++++++++--------- 1 file changed, 96 insertions(+), 96 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 79a8445191a..6f1d07717ed 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -257,282 +257,282 @@ const configuration: IConfigurationNode = { type: 'boolean', default: true }, - 'signals.debouncePositionChanges': { - 'description': localize('signals.debouncePositionChanges', "Whether or not position changes should be debounced"), + 'accessibility.statusIndicators.debouncePositionChanges': { + 'description': localize('accessibility.statusIndicators.debouncePositionChanges', "Whether or not position changes should be debounced"), 'type': 'boolean', 'default': false, tags: ['accessibility'] }, - 'signals.lineHasBreakpoint': { + 'accessibility.statusIndicators.lineHasBreakpoint': { ...signalFeatureBase, - 'description': localize('signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), + 'description': localize('accessibility.statusIndicators.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), 'properties': { 'audioCue': { - 'description': localize('signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), + 'description': localize('accessibility.statusIndicators.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), + 'description': localize('accessibility.statusIndicators.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), ...alertFeatureBase }, }, }, - 'signals.lineHasInlineSuggestion.audioCue': { - 'description': localize('signals.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), + 'accessibility.statusIndicators.lineHasInlineSuggestion.audioCue': { + 'description': localize('accessibility.statusIndicators.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), ...audioCueFeatureBase }, - 'signals.lineHasError': { + 'accessibility.statusIndicators.lineHasError': { ...signalFeatureBase, - 'description': localize('signals.lineHasError', "Plays a signal when the active line has an error."), + 'description': localize('accessibility.statusIndicators.lineHasError', "Plays a signal when the active line has an error."), 'properties': { 'audioCue': { - 'description': localize('signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), + 'description': localize('accessibility.statusIndicators.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.lineHasError.alert', "Alerts when the active line has an error."), + 'description': localize('accessibility.statusIndicators.lineHasError.alert', "Alerts when the active line has an error."), ...alertFeatureBase }, }, }, - 'signals.lineHasFoldedArea': { + 'accessibility.statusIndicators.lineHasFoldedArea': { ...signalFeatureBase, - 'description': localize('signals.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.statusIndicators.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), 'properties': { 'audioCue': { - 'description': localize('signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.statusIndicators.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.lineHasFoldedArea.alert', "Alerts when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.statusIndicators.lineHasFoldedArea.alert', "Alerts when the active line has a folded area that can be unfolded."), ...alertFeatureBase }, } }, - 'signals.lineHasWarning': { + 'accessibility.statusIndicators.lineHasWarning': { ...signalFeatureBase, - 'description': localize('signals.lineHasWarning', "Plays a signal when the active line has a warning."), + 'description': localize('accessibility.statusIndicators.lineHasWarning', "Plays a signal when the active line has a warning."), 'properties': { 'audioCue': { - 'description': localize('signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), + 'description': localize('accessibility.statusIndicators.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.lineHasWarning.alert', "Alerts when the active line has a warning."), + 'description': localize('accessibility.statusIndicators.lineHasWarning.alert', "Alerts when the active line has a warning."), ...alertFeatureBase }, }, }, - 'signals.onDebugBreak': { + 'accessibility.statusIndicators.onDebugBreak': { ...signalFeatureBase, - 'description': localize('signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), + 'description': localize('accessibility.statusIndicators.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), 'properties': { 'audioCue': { - 'description': localize('signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), + 'description': localize('accessibility.statusIndicators.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.onDebugBreak.alert', "Alerts when the debugger stopped on a breakpoint."), + 'description': localize('accessibility.statusIndicators.onDebugBreak.alert', "Alerts when the debugger stopped on a breakpoint."), ...alertFeatureBase }, } }, - 'signals.noInlayHints': { + 'accessibility.statusIndicators.noInlayHints': { ...signalFeatureBase, - 'description': localize('signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), + 'description': localize('accessibility.statusIndicators.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), 'properties': { 'audioCue': { - 'description': localize('signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), + 'description': localize('accessibility.statusIndicators.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.noInlayHints.alert', "Alerts when trying to read a line with inlay hints that has no inlay hints."), + 'description': localize('accessibility.statusIndicators.noInlayHints.alert', "Alerts when trying to read a line with inlay hints that has no inlay hints."), ...alertFeatureBase }, } }, - 'signals.taskCompleted': { + 'accessibility.statusIndicators.taskCompleted': { ...signalFeatureBase, - 'description': localize('signals.taskCompleted', "Plays a signal when a task is completed."), + 'description': localize('accessibility.statusIndicators.taskCompleted', "Plays a signal when a task is completed."), 'properties': { 'audioCue': { - 'description': localize('signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), + 'description': localize('accessibility.statusIndicators.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.taskCompleted.alert', "Alerts when a task is completed."), + 'description': localize('accessibility.statusIndicators.taskCompleted.alert', "Alerts when a task is completed."), ...alertFeatureBase }, } }, - 'signals.taskFailed': { + 'accessibility.statusIndicators.taskFailed': { ...signalFeatureBase, - 'description': localize('signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), + 'description': localize('accessibility.statusIndicators.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), 'properties': { 'audioCue': { - 'description': localize('signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), + 'description': localize('accessibility.statusIndicators.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.taskFailed.alert', "Alerts when a task fails (non-zero exit code)."), + 'description': localize('accessibility.statusIndicators.taskFailed.alert', "Alerts when a task fails (non-zero exit code)."), ...alertFeatureBase }, } }, - 'signals.terminalCommandFailed': { + 'accessibility.statusIndicators.terminalCommandFailed': { ...signalFeatureBase, - 'description': localize('signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), + 'description': localize('accessibility.statusIndicators.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), 'properties': { 'audioCue': { - 'description': localize('signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), + 'description': localize('accessibility.statusIndicators.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.terminalCommandFailed.alert', "Alerts when a terminal command fails (non-zero exit code)."), + 'description': localize('accessibility.statusIndicators.terminalCommandFailed.alert', "Alerts when a terminal command fails (non-zero exit code)."), ...alertFeatureBase }, } }, - 'signals.terminalQuickFix': { + 'accessibility.statusIndicators.terminalQuickFix': { ...signalFeatureBase, - 'description': localize('signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), + 'description': localize('accessibility.statusIndicators.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), 'properties': { 'audioCue': { - 'description': localize('signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), + 'description': localize('accessibility.statusIndicators.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.terminalQuickFix.alert', "Alerts when terminal Quick Fixes are available."), + 'description': localize('accessibility.statusIndicators.terminalQuickFix.alert', "Alerts when terminal Quick Fixes are available."), ...alertFeatureBase }, } }, - 'signals.terminalBell': { + 'accessibility.statusIndicators.terminalBell': { ...signalFeatureBase, - 'description': localize('signals.terminalBell', "Plays a signal when the terminal bell is ringing."), + 'description': localize('accessibility.statusIndicators.terminalBell', "Plays a signal when the terminal bell is ringing."), 'properties': { 'audioCue': { - 'description': localize('signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), + 'description': localize('accessibility.statusIndicators.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.terminalBell.alert', "Alerts when the terminal bell is ringing."), + 'description': localize('accessibility.statusIndicators.terminalBell.alert', "Alerts when the terminal bell is ringing."), ...alertFeatureBase }, } }, - 'signals.diffLineInserted.audioCue': { + 'accessibility.statusIndicators.diffLineInserted.audioCue': { ...audioCueFeatureBase, - 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.statusIndicators.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), }, - 'signals.diffLineDeleted.audioCue': { + 'accessibility.statusIndicators.diffLineDeleted.audioCue': { ...audioCueFeatureBase, - 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.statusIndicators.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), }, - 'signals.diffLineModified.audioCue': { + 'accessibility.statusIndicators.diffLineModified.audioCue': { ...audioCueFeatureBase, - 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.statusIndicators.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), }, - 'signals.notebookCellCompleted': { + 'accessibility.statusIndicators.notebookCellCompleted': { ...signalFeatureBase, - 'description': localize('signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), + 'description': localize('accessibility.statusIndicators.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), 'properties': { 'audioCue': { - 'description': localize('signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), + 'description': localize('accessibility.statusIndicators.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.notebookCellCompleted.alert', "Alerts when a notebook cell execution is successfully completed."), + 'description': localize('accessibility.statusIndicators.notebookCellCompleted.alert', "Alerts when a notebook cell execution is successfully completed."), ...alertFeatureBase }, } }, - 'signals.notebookCellFailed': { + 'accessibility.statusIndicators.notebookCellFailed': { ...signalFeatureBase, - 'description': localize('signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), + 'description': localize('accessibility.statusIndicators.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), 'properties': { 'audioCue': { - 'description': localize('signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), + 'description': localize('accessibility.statusIndicators.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.notebookCellFailed.alert', "Alerts when a notebook cell execution fails."), + 'description': localize('accessibility.statusIndicators.notebookCellFailed.alert', "Alerts when a notebook cell execution fails."), ...alertFeatureBase }, } }, - 'signals.chatRequestSent': { + 'accessibility.statusIndicators.chatRequestSent': { ...signalFeatureBase, - 'description': localize('signals.chatRequestSent', "Plays a signal when a chat request is made."), + 'description': localize('accessibility.statusIndicators.chatRequestSent', "Plays a signal when a chat request is made."), 'properties': { 'audioCue': { - 'description': localize('signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), + 'description': localize('accessibility.statusIndicators.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.chatRequestSent.alert', "Alerts when a chat request is made."), + 'description': localize('accessibility.statusIndicators.chatRequestSent.alert', "Alerts when a chat request is made."), ...alertFeatureBase }, } }, - 'signals.chatResponsePending': { + 'accessibility.statusIndicators.chatResponsePending': { ...signalFeatureBase, - 'description': localize('signals.chatResponsePending', "Plays a signal on loop while the response is pending."), + 'description': localize('accessibility.statusIndicators.chatResponsePending', "Plays a signal on loop while the response is pending."), 'properties': { 'audioCue': { - 'description': localize('signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), + 'description': localize('accessibility.statusIndicators.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), + 'description': localize('accessibility.statusIndicators.chatResponsePending.alert', "Alerts on loop while the response is pending."), ...alertFeatureBase }, }, }, - 'signals.chatResponseReceived.audioCue': { - 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), + 'accessibility.statusIndicators.chatResponseReceived.audioCue': { + 'description': localize('accessibility.statusIndicators.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), ...audioCueFeatureBase }, - 'signals.clear': { + 'accessibility.statusIndicators.clear': { ...signalFeatureBase, - 'description': localize('signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), + 'description': localize('accessibility.statusIndicators.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), 'properties': { 'audioCue': { - 'description': localize('signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), + 'description': localize('accessibility.statusIndicators.clear.audioCue', "Plays an audio cue when a feature is cleared."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.clear.alert', "Alerts when a feature is cleared."), + 'description': localize('accessibility.statusIndicators.clear.alert', "Alerts when a feature is cleared."), ...alertFeatureBase }, }, }, - 'signals.save': { + 'accessibility.statusIndicators.save': { 'type': 'object', 'tags': ['accessibility'], additionalProperties: true, - 'markdownDescription': localize('signals.save', "Plays a signal when a file is saved."), + 'markdownDescription': localize('accessibility.statusIndicators.save', "Plays a signal when a file is saved."), 'properties': { 'audioCue': { - 'description': localize('signals.save.audioCue', "Plays an audio cue when a file is saved."), + 'description': localize('accessibility.statusIndicators.save.audioCue', "Plays an audio cue when a file is saved."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('signals.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), - localize('signals.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), - localize('signals.save.audioCue.never', "Never plays the audio cue.") + localize('accessibility.statusIndicators.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), + localize('accessibility.statusIndicators.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), + localize('accessibility.statusIndicators.save.audioCue.never', "Never plays the audio cue.") ], }, 'alert': { - 'description': localize('signals.save.alert', "Alerts when a file is saved."), + 'description': localize('accessibility.statusIndicators.save.alert', "Alerts when a file is saved."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('signals.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), - localize('signals.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), - localize('signals.save.alert.never', "Never plays the audio cue.") + localize('accessibility.statusIndicators.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), + localize('accessibility.statusIndicators.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), + localize('accessibility.statusIndicators.save.alert.never', "Never plays the audio cue.") ], }, }, @@ -541,32 +541,32 @@ const configuration: IConfigurationNode = { 'alert': 'never' } }, - 'signals.format': { + 'accessibility.statusIndicators.format': { 'type': 'object', 'tags': ['accessibility'], additionalProperties: true, - 'markdownDescription': localize('signals.format', "Plays a signal when a file or notebook is formatted."), + 'markdownDescription': localize('accessibility.statusIndicators.format', "Plays a signal when a file or notebook is formatted."), 'properties': { 'audioCue': { - 'description': localize('signals.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), + 'description': localize('accessibility.statusIndicators.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('signals.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), - localize('signals.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('signals.format.never', "Never plays the audio cue.") + localize('accessibility.statusIndicators.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), + localize('accessibility.statusIndicators.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('accessibility.statusIndicators.format.never', "Never plays the audio cue.") ], }, 'alert': { - 'description': localize('signals.format.alert', "Alerts when a file or notebook is formatted."), + 'description': localize('accessibility.statusIndicators.format.alert', "Alerts when a file or notebook is formatted."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('signals.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), - localize('signals.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('signals.format.alert.never', "Never plays the alert.") + localize('accessibility.statusIndicators.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), + localize('accessibility.statusIndicators.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('accessibility.statusIndicators.format.alert.never', "Never plays the alert.") ], }, } From 7dcb9b754e58cd2fdfec5190b1d26c4549f02bdd Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 1 Feb 2024 15:26:55 -0600 Subject: [PATCH 0850/1897] tweak more words --- .../audioCues/browser/audioCueService.ts | 36 ++++----- .../browser/accessibilityConfiguration.ts | 80 +++++++++---------- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 668ebf9ed83..c231512c5dc 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -408,28 +408,28 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.lineHasError', alertSettingsKey: AccessibilityAlertSettingId.Error, - alertMessage: localize('audioCues.lineHasError.alertMessage', 'Error') + alertMessage: localize('accessibility.statusIndicators.lineHasError', 'Error') }); public static readonly warning = AudioCue.register({ name: localize('audioCues.lineHasWarning.name', 'Warning on Line'), sound: Sound.warning, settingsKey: 'audioCues.lineHasWarning', alertSettingsKey: AccessibilityAlertSettingId.Warning, - alertMessage: localize('audioCues.lineHasWarning.alertMessage', 'Warning') + alertMessage: localize('accessibility.statusIndicators.lineHasWarning', 'Warning') }); public static readonly foldedArea = AudioCue.register({ name: localize('audioCues.lineHasFoldedArea.name', 'Folded Area on Line'), sound: Sound.foldedArea, settingsKey: 'audioCues.lineHasFoldedArea', alertSettingsKey: AccessibilityAlertSettingId.FoldedArea, - alertMessage: localize('audioCues.lineHasFoldedArea.alertMessage', 'Folded') + alertMessage: localize('accessibility.statusIndicators.lineHasFoldedArea', 'Folded') }); public static readonly break = AudioCue.register({ name: localize('audioCues.lineHasBreakpoint.name', 'Breakpoint on Line'), sound: Sound.break, settingsKey: 'audioCues.lineHasBreakpoint', alertSettingsKey: AccessibilityAlertSettingId.Breakpoint, - alertMessage: localize('audioCues.lineHasBreakpoint.alertMessage', 'Breakpoint') + alertMessage: localize('accessibility.statusIndicators.lineHasBreakpoint', 'Breakpoint') }); public static readonly inlineSuggestion = AudioCue.register({ name: localize('audioCues.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), @@ -442,7 +442,7 @@ export class AudioCue { sound: Sound.quickFixes, settingsKey: 'audioCues.terminalQuickFix', alertSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, - alertMessage: localize('audioCues.terminalQuickFix.alertMessage', 'Quick Fix') + alertMessage: localize('accessibility.statusIndicators.terminalQuickFix', 'Quick Fix') }); public static readonly onDebugBreak = AudioCue.register({ @@ -450,7 +450,7 @@ export class AudioCue { sound: Sound.break, settingsKey: 'audioCues.onDebugBreak', alertSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, - alertMessage: localize('audioCues.onDebugBreak.alertMessage', 'Breakpoint') + alertMessage: localize('accessibility.statusIndicators.onDebugBreak', 'Breakpoint') }); public static readonly noInlayHints = AudioCue.register({ @@ -458,7 +458,7 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.noInlayHints', alertSettingsKey: AccessibilityAlertSettingId.NoInlayHints, - alertMessage: localize('audioCues.noInlayHints.alertMessage', 'No Inlay Hints') + alertMessage: localize('accessibility.statusIndicators.noInlayHints', 'No Inlay Hints') }); public static readonly taskCompleted = AudioCue.register({ @@ -466,7 +466,7 @@ export class AudioCue { sound: Sound.taskCompleted, settingsKey: 'audioCues.taskCompleted', alertSettingsKey: AccessibilityAlertSettingId.TaskCompleted, - alertMessage: localize('audioCues.taskCompleted.alertMessage', 'Task Completed') + alertMessage: localize('accessibility.statusIndicators.taskCompleted', 'Task Completed') }); public static readonly taskFailed = AudioCue.register({ @@ -474,7 +474,7 @@ export class AudioCue { sound: Sound.taskFailed, settingsKey: 'audioCues.taskFailed', alertSettingsKey: AccessibilityAlertSettingId.TaskFailed, - alertMessage: localize('audioCues.taskFailed.alertMessage', 'Task Failed') + alertMessage: localize('accessibility.statusIndicators.taskFailed', 'Task Failed') }); public static readonly terminalCommandFailed = AudioCue.register({ @@ -482,7 +482,7 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.terminalCommandFailed', alertSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, - alertMessage: localize('audioCues.terminalCommandFailed.alertMessage', 'Command Failed') + alertMessage: localize('accessibility.statusIndicators.terminalCommandFailed', 'Command Failed') }); public static readonly terminalBell = AudioCue.register({ @@ -490,7 +490,7 @@ export class AudioCue { sound: Sound.terminalBell, settingsKey: 'audioCues.terminalBell', alertSettingsKey: AccessibilityAlertSettingId.TerminalBell, - alertMessage: localize('audioCues.terminalBell.alertMessage', 'Terminal Bell') + alertMessage: localize('accessibility.statusIndicators.terminalBell', 'Terminal Bell') }); public static readonly notebookCellCompleted = AudioCue.register({ @@ -498,7 +498,7 @@ export class AudioCue { sound: Sound.taskCompleted, settingsKey: 'audioCues.notebookCellCompleted', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, - alertMessage: localize('audioCues.notebookCellCompleted.alertMessage', 'Notebook Cell Completed') + alertMessage: localize('accessibility.statusIndicators.notebookCellCompleted', 'Notebook Cell Completed') }); public static readonly notebookCellFailed = AudioCue.register({ @@ -506,7 +506,7 @@ export class AudioCue { sound: Sound.taskFailed, settingsKey: 'audioCues.notebookCellFailed', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, - alertMessage: localize('audioCues.notebookCellFailed.alertMessage', 'Notebook Cell Failed') + alertMessage: localize('accessibility.statusIndicators.notebookCellFailed', 'Notebook Cell Failed') }); public static readonly diffLineInserted = AudioCue.register({ @@ -532,7 +532,7 @@ export class AudioCue { sound: Sound.chatRequestSent, settingsKey: 'audioCues.chatRequestSent', alertSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, - alertMessage: localize('audioCues.chatRequestSent.alertMessage', 'Chat Request Sent') + alertMessage: localize('accessibility.statusIndicators.chatRequestSent', 'Chat Request Sent') }); public static readonly chatResponseReceived = AudioCue.register({ @@ -553,7 +553,7 @@ export class AudioCue { sound: Sound.chatResponsePending, settingsKey: 'audioCues.chatResponsePending', alertSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, - alertMessage: localize('audioCues.chatResponsePending.alertMessage', 'Chat Response Pending') + alertMessage: localize('accessibility.statusIndicators.chatResponsePending', 'Chat Response Pending') }); public static readonly clear = AudioCue.register({ @@ -561,7 +561,7 @@ export class AudioCue { sound: Sound.clear, settingsKey: 'audioCues.clear', alertSettingsKey: AccessibilityAlertSettingId.Clear, - alertMessage: localize('audioCues.clear.alertMessage', 'Clear') + alertMessage: localize('accessibility.statusIndicators.clear', 'Clear') }); public static readonly save = AudioCue.register({ @@ -569,7 +569,7 @@ export class AudioCue { sound: Sound.save, settingsKey: 'audioCues.save', alertSettingsKey: AccessibilityAlertSettingId.Save, - alertMessage: localize('audioCues.save.alertMessage', 'Save') + alertMessage: localize('accessibility.statusIndicators.save', 'Save') }); public static readonly format = AudioCue.register({ @@ -577,7 +577,7 @@ export class AudioCue { sound: Sound.format, settingsKey: 'audioCues.format', alertSettingsKey: AccessibilityAlertSettingId.Format, - alertMessage: localize('audioCues.format.alertMessage', 'Format') + alertMessage: localize('accessibility.statusIndicators.format', 'Format') }); private constructor( diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 6f1d07717ed..a1b8884d31a 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -160,96 +160,96 @@ const configuration: IConfigurationNode = { ...baseVerbosityProperty }, [AccessibilityAlertSettingId.Save]: { - 'markdownDescription': localize('alert.save', "Alerts when a file is saved. Also see {0}.", '`#audioCues.save#`'), + 'markdownDescription': localize('alert.save', "Indicates when a file is saved. Also see {0}.", '`#audioCues.save#`'), 'enum': ['userGesture', 'always', 'never'], 'default': 'always', 'enumDescriptions': [ - localize('alert.save.userGesture', "Alerts when a file is saved via user gesture."), - localize('alert.save.always', "Alerts whenever is a file is saved, including auto save."), + localize('alert.save.userGesture', "Indicates when a file is saved via user gesture."), + localize('alert.save.always', "Indicates whenever is a file is saved, including auto save."), localize('alert.save.never', "Never alerts.") ], tags: ['accessibility'], markdownDeprecationMessage }, [AccessibilityAlertSettingId.Clear]: { - 'markdownDescription': localize('alert.clear', "Alerts when a feature is cleared (for example, the terminal, Debug Console, or Output channel). Also see {0}.", '`#audioCues.clear#`'), + 'markdownDescription': localize('alert.clear', "Indicates when a feature is cleared (for example, the terminal, Debug Console, or Output channel). Also see {0}.", '`#audioCues.clear#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.Format]: { - 'markdownDescription': localize('alert.format', "Alerts when a file or notebook cell is formatted. Also see {0}.", '`#audioCues.format#`'), + 'markdownDescription': localize('alert.format', "Indicates when a file or notebook cell is formatted. Also see {0}.", '`#audioCues.format#`'), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'always', 'enumDescriptions': [ - localize('alert.format.userGesture', "Alerts when a file is formatted via user gesture."), - localize('alert.format.always', "Alerts whenever is a file is formatted, including auto save, on cell execution, and more."), + localize('alert.format.userGesture', "Indicates when a file is formatted via user gesture."), + localize('alert.format.always', "Indicates whenever is a file is formatted, including auto save, on cell execution, and more."), localize('alert.format.never', "Never alerts.") ], tags: ['accessibility'], markdownDeprecationMessage }, [AccessibilityAlertSettingId.Breakpoint]: { - 'markdownDescription': localize('alert.breakpoint', "Alerts when the active line has a breakpoint. Also see {0}.", '`#audioCues.onDebugBreak#`'), + 'markdownDescription': localize('alert.breakpoint', "Indicates when the active line has a breakpoint. Also see {0}.", '`#audioCues.onDebugBreak#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.Error]: { - 'markdownDescription': localize('alert.error', "Alerts when the active line has an error. Also see {0}.", '`#audioCues.lineHasError#`'), + 'markdownDescription': localize('alert.error', "Indicates when the active line has an error. Also see {0}.", '`#audioCues.lineHasError#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.Warning]: { - 'markdownDescription': localize('alert.warning', "Alerts when the active line has a warning. Also see {0}.", '`#audioCues.lineHasWarning#`'), + 'markdownDescription': localize('alert.warning', "Indicates when the active line has a warning. Also see {0}.", '`#audioCues.lineHasWarning#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.FoldedArea]: { - 'markdownDescription': localize('alert.foldedArea', "Alerts when the active line has a folded area that can be unfolded. Also see {0}.", '`#audioCues.lineHasFoldedArea#`'), + 'markdownDescription': localize('alert.foldedArea', "Indicates when the active line has a folded area that can be unfolded. Also see {0}.", '`#audioCues.lineHasFoldedArea#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalQuickFix]: { - 'markdownDescription': localize('alert.terminalQuickFix', "Alerts when there is an available terminal quick fix. Also see {0}.", '`#audioCues.terminalQuickFix#`'), + 'markdownDescription': localize('alert.terminalQuickFix', "Indicates when there is an available terminal quick fix. Also see {0}.", '`#audioCues.terminalQuickFix#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalBell]: { - 'markdownDescription': localize('alert.terminalBell', "Alerts when the terminal bell is activated."), + 'markdownDescription': localize('alert.terminalBell', "Indicates when the terminal bell is activated."), ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalCommandFailed]: { - 'markdownDescription': localize('alert.terminalCommandFailed', "Alerts when a terminal command fails (non-zero exit code). Also see {0}.", '`#audioCues.terminalCommandFailed#`'), + 'markdownDescription': localize('alert.terminalCommandFailed', "Indicates when a terminal command fails (non-zero exit code). Also see {0}.", '`#audioCues.terminalCommandFailed#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.TaskFailed]: { - 'markdownDescription': localize('alert.taskFailed', "Alerts when a task fails (non-zero exit code). Also see {0}.", '`#audioCues.taskFailed#`'), + 'markdownDescription': localize('alert.taskFailed', "Indicates when a task fails (non-zero exit code). Also see {0}.", '`#audioCues.taskFailed#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.TaskCompleted]: { - 'markdownDescription': localize('alert.taskCompleted', "Alerts when a task completes successfully (zero exit code). Also see {0}.", '`#audioCues.taskCompleted#`'), + 'markdownDescription': localize('alert.taskCompleted', "Indicates when a task completes successfully (zero exit code). Also see {0}.", '`#audioCues.taskCompleted#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.ChatRequestSent]: { - 'markdownDescription': localize('alert.chatRequestSent', "Alerts when a chat request is sent. Also see {0}.", '`#audioCues.chatRequestSent#`'), + 'markdownDescription': localize('alert.chatRequestSent', "Indicates when a chat request is sent. Also see {0}.", '`#audioCues.chatRequestSent#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.ChatResponsePending]: { - 'markdownDescription': localize('alert.chatResponsePending', "Alerts when a chat response is pending. Also see {0}.", '`#audioCues.chatResponsePending#`'), + 'markdownDescription': localize('alert.chatResponsePending', "Indicates when a chat response is pending. Also see {0}.", '`#audioCues.chatResponsePending#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.NoInlayHints]: { - 'markdownDescription': localize('alert.noInlayHints', "Alerts when there are no inlay hints. Also see {0}.", '`#audioCues.noInlayHints#`'), + 'markdownDescription': localize('alert.noInlayHints', "Indicates when there are no inlay hints. Also see {0}.", '`#audioCues.noInlayHints#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.LineHasBreakpoint]: { - 'markdownDescription': localize('alert.lineHasBreakpoint', "Alerts when on a line with a breakpoint. Also see {0}.", '`#audioCues.lineHasBreakpoint#`'), + 'markdownDescription': localize('alert.lineHasBreakpoint', "Indicates when on a line with a breakpoint. Also see {0}.", '`#audioCues.lineHasBreakpoint#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.NotebookCellCompleted]: { - 'markdownDescription': localize('alert.notebookCellCompleted', "Alerts when a notebook cell completes successfully. Also see {0}.", '`#audioCues.notebookCellCompleted#`'), + 'markdownDescription': localize('alert.notebookCellCompleted', "Indicates when a notebook cell completes successfully. Also see {0}.", '`#audioCues.notebookCellCompleted#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.NotebookCellFailed]: { - 'markdownDescription': localize('alert.notebookCellFailed', "Alerts when a notebook cell fails. Also see {0}.", '`#audioCues.notebookCellFailed#`'), + 'markdownDescription': localize('alert.notebookCellFailed', "Indicates when a notebook cell fails. Also see {0}.", '`#audioCues.notebookCellFailed#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.OnDebugBreak]: { - 'markdownDescription': localize('alert.onDebugBreak', "Alerts when the debugger breaks. Also see {0}.", '`#audioCues.onDebugBreak#`'), + 'markdownDescription': localize('alert.onDebugBreak', "Indicates when the debugger breaks. Also see {0}.", '`#audioCues.onDebugBreak#`'), ...baseAlertProperty }, [AccessibilityWorkbenchSettingId.AccessibleViewCloseOnKeyPress]: { @@ -272,7 +272,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), + 'description': localize('accessibility.statusIndicators.lineHasBreakpoint.alert', "Indicates when the active line has a breakpoint."), ...alertFeatureBase }, }, @@ -290,7 +290,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.lineHasError.alert', "Alerts when the active line has an error."), + 'description': localize('accessibility.statusIndicators.lineHasError.alert', "Indicates when the active line has an error."), ...alertFeatureBase }, }, @@ -304,7 +304,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.lineHasFoldedArea.alert', "Alerts when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.statusIndicators.lineHasFoldedArea.alert', "Indicates when the active line has a folded area that can be unfolded."), ...alertFeatureBase }, } @@ -318,7 +318,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.lineHasWarning.alert', "Alerts when the active line has a warning."), + 'description': localize('accessibility.statusIndicators.lineHasWarning.alert', "Indicates when the active line has a warning."), ...alertFeatureBase }, }, @@ -332,7 +332,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.onDebugBreak.alert', "Alerts when the debugger stopped on a breakpoint."), + 'description': localize('accessibility.statusIndicators.onDebugBreak.alert', "Indicates when the debugger stopped on a breakpoint."), ...alertFeatureBase }, } @@ -346,7 +346,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.noInlayHints.alert', "Alerts when trying to read a line with inlay hints that has no inlay hints."), + 'description': localize('accessibility.statusIndicators.noInlayHints.alert', "Indicates when trying to read a line with inlay hints that has no inlay hints."), ...alertFeatureBase }, } @@ -360,7 +360,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.taskCompleted.alert', "Alerts when a task is completed."), + 'description': localize('accessibility.statusIndicators.taskCompleted.alert', "Indicates when a task is completed."), ...alertFeatureBase }, } @@ -374,7 +374,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.taskFailed.alert', "Alerts when a task fails (non-zero exit code)."), + 'description': localize('accessibility.statusIndicators.taskFailed.alert', "Indicates when a task fails (non-zero exit code)."), ...alertFeatureBase }, } @@ -388,7 +388,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.terminalCommandFailed.alert', "Alerts when a terminal command fails (non-zero exit code)."), + 'description': localize('accessibility.statusIndicators.terminalCommandFailed.alert', "Indicates when a terminal command fails (non-zero exit code)."), ...alertFeatureBase }, } @@ -402,7 +402,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.terminalQuickFix.alert', "Alerts when terminal Quick Fixes are available."), + 'description': localize('accessibility.statusIndicators.terminalQuickFix.alert', "Indicates when terminal Quick Fixes are available."), ...alertFeatureBase }, } @@ -416,7 +416,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.terminalBell.alert', "Alerts when the terminal bell is ringing."), + 'description': localize('accessibility.statusIndicators.terminalBell.alert', "Indicates when the terminal bell is ringing."), ...alertFeatureBase }, } @@ -442,7 +442,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.notebookCellCompleted.alert', "Alerts when a notebook cell execution is successfully completed."), + 'description': localize('accessibility.statusIndicators.notebookCellCompleted.alert', "Indicates when a notebook cell execution is successfully completed."), ...alertFeatureBase }, } @@ -456,7 +456,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.notebookCellFailed.alert', "Alerts when a notebook cell execution fails."), + 'description': localize('accessibility.statusIndicators.notebookCellFailed.alert', "Indicates when a notebook cell execution fails."), ...alertFeatureBase }, } @@ -470,7 +470,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.chatRequestSent.alert', "Alerts when a chat request is made."), + 'description': localize('accessibility.statusIndicators.chatRequestSent.alert', "Indicates when a chat request is made."), ...alertFeatureBase }, } @@ -502,7 +502,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.clear.alert', "Alerts when a feature is cleared."), + 'description': localize('accessibility.statusIndicators.clear.alert', "Indicates when a feature is cleared."), ...alertFeatureBase }, }, @@ -525,7 +525,7 @@ const configuration: IConfigurationNode = { ], }, 'alert': { - 'description': localize('accessibility.statusIndicators.save.alert', "Alerts when a file is saved."), + 'description': localize('accessibility.statusIndicators.save.alert', "Indicates when a file is saved."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', @@ -559,7 +559,7 @@ const configuration: IConfigurationNode = { ], }, 'alert': { - 'description': localize('accessibility.statusIndicators.format.alert', "Alerts when a file or notebook is formatted."), + 'description': localize('accessibility.statusIndicators.format.alert', "Indicates when a file or notebook is formatted."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', From b642d20936aa17fa1d316eb83b807d67c5266169 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 1 Feb 2024 15:34:52 -0600 Subject: [PATCH 0851/1897] fix issues --- .../workbench/contrib/preferences/browser/settingsLayout.ts | 6 +++--- .../services/configuration/common/configurationEditing.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index f10aaea48eb..97a13dca2f9 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -227,9 +227,9 @@ export const tocData: ITOCEntry = { settings: ['audioCues.*'] }, { - id: 'features/signals', - label: localize('signals', 'Signals'), - settings: ['signals.*'] + id: 'features/accessibilityStatusIndicators', + label: localize('accessibility.statusIndicators', 'Accessibility Status Indicators'), + settings: ['accessibility.statusIndicators.*'] }, { id: 'features/mergeEditor', diff --git a/src/vs/workbench/services/configuration/common/configurationEditing.ts b/src/vs/workbench/services/configuration/common/configurationEditing.ts index 0c88c9fc11e..721f789dadb 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditing.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditing.ts @@ -471,7 +471,7 @@ export class ConfigurationEditing { } return this.textModelResolverService.createModelReference(resource); } - // + private hasParseErrors(content: string, operation: IConfigurationEditOperation): boolean { // If we write to a workspace standalone file and replace the entire contents (no key provided) // we can return here because any parse errors can safely be ignored since all contents are replaced From a780b235dde376ff32118386f32e6b2e9128fae1 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 1 Feb 2024 16:35:40 -0600 Subject: [PATCH 0852/1897] Fix another bug --- .../audioCues/browser/audioCueService.ts | 77 ++++++++++++------- .../browser/accessibilityConfiguration.ts | 73 ++++++++++++++---- .../contrib/audioCues/browser/commands.ts | 35 ++++++--- 3 files changed, 130 insertions(+), 55 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index c231512c5dc..69e3551c778 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -58,12 +58,12 @@ export class AudioCueService extends Disposable implements IAudioCueService { } private _migrateSettings(): void { - this.configurationService.updateValue('signals.debouncePositionChanges', this.configurationService.getValue('audioCues.debouncePositionChanges')); + this.configurationService.updateValue('accessibility.statusIndicators.debouncePositionChanges', this.configurationService.getValue('audioCues.debouncePositionChanges')); const config = AudioCue.allAudioCues; for (const c of config) { const alertConfig = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; const audioCue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; - const newSettingsKey = c.settingsKey.replace('audioCues.', 'signals.'); + const newSettingsKey = c.settingsKey.replace('audioCues.', 'accessibility.statusIndicators.'); if (alertConfig !== undefined && audioCue !== undefined) { let alert; if (typeof alertConfig === 'string') { @@ -200,9 +200,9 @@ export class AudioCueService extends Disposable implements IAudioCueService { private readonly isCueEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.settingsKey) + e.affectsConfiguration(event.cue.settingsKey) || e.affectsConfiguration(event.cue.accessibilityStatusIndicatorSettingsKey) ), - () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.cue.settingsKey) + () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.cue.accessibilityStatusIndicatorSettingsKey + '.audioCue') ); return derived(reader => { /** @description audio cue enabled */ @@ -231,9 +231,9 @@ export class AudioCueService extends Disposable implements IAudioCueService { private readonly isAlertEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.alertSettingsKey!) + e.affectsConfiguration(event.cue.alertSettingsKey!) || e.affectsConfiguration(event.cue.accessibilityStatusIndicatorSettingsKey) ), - () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.alertSettingsKey) : false + () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.accessibilityStatusIndicatorSettingsKey + '.alert') : false ); return derived(reader => { /** @description alert enabled */ @@ -390,11 +390,12 @@ export class AudioCue { randomOneOf: Sound[]; }; settingsKey: string; + accessibilityStatusIndicatorSettingsKey: string; alertSettingsKey?: AccessibilityAlertSettingId; alertMessage?: string; }): AudioCue { const soundSource = new SoundSource('randomOneOf' in options.sound ? options.sound.randomOneOf : [options.sound]); - const audioCue = new AudioCue(soundSource, options.name, options.settingsKey, options.alertSettingsKey, options.alertMessage); + const audioCue = new AudioCue(soundSource, options.name, options.settingsKey, options.accessibilityStatusIndicatorSettingsKey, options.alertSettingsKey, options.alertMessage); AudioCue._audioCues.add(audioCue); return audioCue; } @@ -408,33 +409,38 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.lineHasError', alertSettingsKey: AccessibilityAlertSettingId.Error, - alertMessage: localize('accessibility.statusIndicators.lineHasError', 'Error') + alertMessage: localize('accessibility.statusIndicators.lineHasError', 'Error'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasError' }); public static readonly warning = AudioCue.register({ name: localize('audioCues.lineHasWarning.name', 'Warning on Line'), sound: Sound.warning, settingsKey: 'audioCues.lineHasWarning', alertSettingsKey: AccessibilityAlertSettingId.Warning, - alertMessage: localize('accessibility.statusIndicators.lineHasWarning', 'Warning') + alertMessage: localize('accessibility.statusIndicators.lineHasWarning', 'Warning'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasWarning' }); public static readonly foldedArea = AudioCue.register({ name: localize('audioCues.lineHasFoldedArea.name', 'Folded Area on Line'), sound: Sound.foldedArea, settingsKey: 'audioCues.lineHasFoldedArea', alertSettingsKey: AccessibilityAlertSettingId.FoldedArea, - alertMessage: localize('accessibility.statusIndicators.lineHasFoldedArea', 'Folded') + alertMessage: localize('accessibility.statusIndicators.lineHasFoldedArea', 'Folded'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasFoldedArea' }); public static readonly break = AudioCue.register({ name: localize('audioCues.lineHasBreakpoint.name', 'Breakpoint on Line'), sound: Sound.break, settingsKey: 'audioCues.lineHasBreakpoint', alertSettingsKey: AccessibilityAlertSettingId.Breakpoint, - alertMessage: localize('accessibility.statusIndicators.lineHasBreakpoint', 'Breakpoint') + alertMessage: localize('accessibility.statusIndicators.lineHasBreakpoint', 'Breakpoint'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasBreakpoint' }); public static readonly inlineSuggestion = AudioCue.register({ name: localize('audioCues.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), sound: Sound.quickFixes, settingsKey: 'audioCues.lineHasInlineSuggestion', + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasInlineSuggestion' }); public static readonly terminalQuickFix = AudioCue.register({ @@ -442,7 +448,8 @@ export class AudioCue { sound: Sound.quickFixes, settingsKey: 'audioCues.terminalQuickFix', alertSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, - alertMessage: localize('accessibility.statusIndicators.terminalQuickFix', 'Quick Fix') + alertMessage: localize('accessibility.statusIndicators.terminalQuickFix', 'Quick Fix'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.terminalQuickFix' }); public static readonly onDebugBreak = AudioCue.register({ @@ -450,7 +457,8 @@ export class AudioCue { sound: Sound.break, settingsKey: 'audioCues.onDebugBreak', alertSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, - alertMessage: localize('accessibility.statusIndicators.onDebugBreak', 'Breakpoint') + alertMessage: localize('accessibility.statusIndicators.onDebugBreak', 'Breakpoint'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.onDebugBreak' }); public static readonly noInlayHints = AudioCue.register({ @@ -458,7 +466,8 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.noInlayHints', alertSettingsKey: AccessibilityAlertSettingId.NoInlayHints, - alertMessage: localize('accessibility.statusIndicators.noInlayHints', 'No Inlay Hints') + alertMessage: localize('accessibility.statusIndicators.noInlayHints', 'No Inlay Hints'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.noInlayHints' }); public static readonly taskCompleted = AudioCue.register({ @@ -466,7 +475,8 @@ export class AudioCue { sound: Sound.taskCompleted, settingsKey: 'audioCues.taskCompleted', alertSettingsKey: AccessibilityAlertSettingId.TaskCompleted, - alertMessage: localize('accessibility.statusIndicators.taskCompleted', 'Task Completed') + alertMessage: localize('accessibility.statusIndicators.taskCompleted', 'Task Completed'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.taskCompleted' }); public static readonly taskFailed = AudioCue.register({ @@ -474,7 +484,8 @@ export class AudioCue { sound: Sound.taskFailed, settingsKey: 'audioCues.taskFailed', alertSettingsKey: AccessibilityAlertSettingId.TaskFailed, - alertMessage: localize('accessibility.statusIndicators.taskFailed', 'Task Failed') + alertMessage: localize('accessibility.statusIndicators.taskFailed', 'Task Failed'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.taskFailed' }); public static readonly terminalCommandFailed = AudioCue.register({ @@ -482,7 +493,8 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.terminalCommandFailed', alertSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, - alertMessage: localize('accessibility.statusIndicators.terminalCommandFailed', 'Command Failed') + alertMessage: localize('accessibility.statusIndicators.terminalCommandFailed', 'Command Failed'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.terminalCommandFailed' }); public static readonly terminalBell = AudioCue.register({ @@ -490,7 +502,8 @@ export class AudioCue { sound: Sound.terminalBell, settingsKey: 'audioCues.terminalBell', alertSettingsKey: AccessibilityAlertSettingId.TerminalBell, - alertMessage: localize('accessibility.statusIndicators.terminalBell', 'Terminal Bell') + alertMessage: localize('accessibility.statusIndicators.terminalBell', 'Terminal Bell'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.terminalBell' }); public static readonly notebookCellCompleted = AudioCue.register({ @@ -498,7 +511,8 @@ export class AudioCue { sound: Sound.taskCompleted, settingsKey: 'audioCues.notebookCellCompleted', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, - alertMessage: localize('accessibility.statusIndicators.notebookCellCompleted', 'Notebook Cell Completed') + alertMessage: localize('accessibility.statusIndicators.notebookCellCompleted', 'Notebook Cell Completed'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.notebookCellCompleted' }); public static readonly notebookCellFailed = AudioCue.register({ @@ -506,25 +520,29 @@ export class AudioCue { sound: Sound.taskFailed, settingsKey: 'audioCues.notebookCellFailed', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, - alertMessage: localize('accessibility.statusIndicators.notebookCellFailed', 'Notebook Cell Failed') + alertMessage: localize('accessibility.statusIndicators.notebookCellFailed', 'Notebook Cell Failed'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.notebookCellFailed' }); public static readonly diffLineInserted = AudioCue.register({ name: localize('audioCues.diffLineInserted', 'Diff Line Inserted'), sound: Sound.diffLineInserted, settingsKey: 'audioCues.diffLineInserted', + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.diffLineInserted' }); public static readonly diffLineDeleted = AudioCue.register({ name: localize('audioCues.diffLineDeleted', 'Diff Line Deleted'), sound: Sound.diffLineDeleted, settingsKey: 'audioCues.diffLineDeleted', + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.diffLineDeleted' }); public static readonly diffLineModified = AudioCue.register({ name: localize('audioCues.diffLineModified', 'Diff Line Modified'), sound: Sound.diffLineModified, settingsKey: 'audioCues.diffLineModified', + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.diffLineModified' }); public static readonly chatRequestSent = AudioCue.register({ @@ -532,7 +550,8 @@ export class AudioCue { sound: Sound.chatRequestSent, settingsKey: 'audioCues.chatRequestSent', alertSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, - alertMessage: localize('accessibility.statusIndicators.chatRequestSent', 'Chat Request Sent') + alertMessage: localize('accessibility.statusIndicators.chatRequestSent', 'Chat Request Sent'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.chatRequestSent' }); public static readonly chatResponseReceived = AudioCue.register({ @@ -546,6 +565,7 @@ export class AudioCue { Sound.chatResponseReceived4 ] }, + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.chatResponseReceived' }); public static readonly chatResponsePending = AudioCue.register({ @@ -553,7 +573,8 @@ export class AudioCue { sound: Sound.chatResponsePending, settingsKey: 'audioCues.chatResponsePending', alertSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, - alertMessage: localize('accessibility.statusIndicators.chatResponsePending', 'Chat Response Pending') + alertMessage: localize('accessibility.statusIndicators.chatResponsePending', 'Chat Response Pending'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.chatResponsePending' }); public static readonly clear = AudioCue.register({ @@ -561,7 +582,8 @@ export class AudioCue { sound: Sound.clear, settingsKey: 'audioCues.clear', alertSettingsKey: AccessibilityAlertSettingId.Clear, - alertMessage: localize('accessibility.statusIndicators.clear', 'Clear') + alertMessage: localize('accessibility.statusIndicators.clear', 'Clear'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.clear' }); public static readonly save = AudioCue.register({ @@ -569,7 +591,8 @@ export class AudioCue { sound: Sound.save, settingsKey: 'audioCues.save', alertSettingsKey: AccessibilityAlertSettingId.Save, - alertMessage: localize('accessibility.statusIndicators.save', 'Save') + alertMessage: localize('accessibility.statusIndicators.save', 'Save'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.save' }); public static readonly format = AudioCue.register({ @@ -577,14 +600,16 @@ export class AudioCue { sound: Sound.format, settingsKey: 'audioCues.format', alertSettingsKey: AccessibilityAlertSettingId.Format, - alertMessage: localize('accessibility.statusIndicators.format', 'Format') + alertMessage: localize('accessibility.statusIndicators.format', 'Format'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.format' }); private constructor( public readonly sound: SoundSource, public readonly name: string, public readonly settingsKey: string, + public readonly accessibilityStatusIndicatorSettingsKey: string, public readonly alertSettingsKey?: string, - public readonly alertMessage?: string + public readonly alertMessage?: string, ) { } } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index a1b8884d31a..ca6fb5c8d2e 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -112,6 +112,15 @@ export const alertFeatureBase: IConfigurationPropertySchema = { tags: ['accessibility'], }; +const defaultNoAlert: IConfigurationPropertySchema = { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, + 'default': { + 'audioCue': 'auto', + } +}; + const configuration: IConfigurationNode = { ...accessibilityConfigurationNodeBase, properties: { @@ -277,13 +286,19 @@ const configuration: IConfigurationNode = { }, }, }, - 'accessibility.statusIndicators.lineHasInlineSuggestion.audioCue': { - 'description': localize('accessibility.statusIndicators.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), - ...audioCueFeatureBase + 'accessibility.statusIndicators.lineHasInlineSuggestion': { + ...defaultNoAlert, + 'description': localize('accessibility.statusIndicators.lineHasInlineSuggestion', "Indicates when the active line has an inline suggestion."), + 'properties': { + 'audioCue': { + 'description': localize('accessibility.statusIndicators.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), + ...audioCueFeatureBase + } + } }, 'accessibility.statusIndicators.lineHasError': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.lineHasError', "Plays a signal when the active line has an error."), + 'description': localize('accessibility.statusIndicators.lineHasError', "Indicates when the active line has an error."), 'properties': { 'audioCue': { 'description': localize('accessibility.statusIndicators.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), @@ -297,7 +312,7 @@ const configuration: IConfigurationNode = { }, 'accessibility.statusIndicators.lineHasFoldedArea': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.statusIndicators.lineHasFoldedArea', "Indicates when the active line has a folded area that can be unfolded."), 'properties': { 'audioCue': { 'description': localize('accessibility.statusIndicators.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), @@ -421,17 +436,35 @@ const configuration: IConfigurationNode = { }, } }, - 'accessibility.statusIndicators.diffLineInserted.audioCue': { - ...audioCueFeatureBase, - 'description': localize('accessibility.statusIndicators.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + 'accessibility.statusIndicators.diffLineInserted': { + ...defaultNoAlert, + 'description': localize('accessibility.statusIndicators.diffLineInserted', "Indicates when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + 'properties': { + 'audioCue': { + 'description': localize('accessibility.statusIndicators.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + ...audioCueFeatureBase + } + } }, - 'accessibility.statusIndicators.diffLineDeleted.audioCue': { - ...audioCueFeatureBase, - 'description': localize('accessibility.statusIndicators.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), + 'accessibility.statusIndicators.diffLineModified': { + ...defaultNoAlert, + 'description': localize('accessibility.statusIndicators.diffLineModified', "Indicates when the focus moves to an modified line in Accessible Diff Viewer mode or to the next/previous change."), + 'properties': { + 'audioCue': { + 'description': localize('accessibility.statusIndicators.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + ...audioCueFeatureBase + } + } }, - 'accessibility.statusIndicators.diffLineModified.audioCue': { - ...audioCueFeatureBase, - 'description': localize('accessibility.statusIndicators.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + 'accessibility.statusIndicators.diffLineDeleted': { + ...defaultNoAlert, + 'description': localize('accessibility.statusIndicators.diffLineDeleted', "Indicates when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), + 'properties': { + 'audioCue': { + 'description': localize('accessibility.statusIndicators.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), + ...audioCueFeatureBase + } + } }, 'accessibility.statusIndicators.notebookCellCompleted': { ...signalFeatureBase, @@ -489,9 +522,15 @@ const configuration: IConfigurationNode = { }, }, }, - 'accessibility.statusIndicators.chatResponseReceived.audioCue': { - 'description': localize('accessibility.statusIndicators.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), - ...audioCueFeatureBase + 'accessibility.statusIndicators.chatResponseReceived': { + ...defaultNoAlert, + 'description': localize('accessibility.statusIndicators.chatResponseReceived', "Indicates when the response has been received."), + 'properties': { + 'audioCue': { + 'description': localize('accessibility.statusIndicators.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), + ...audioCueFeatureBase + }, + } }, 'accessibility.statusIndicators.clear': { ...signalFeatureBase, diff --git a/src/vs/workbench/contrib/audioCues/browser/commands.ts b/src/vs/workbench/contrib/audioCues/browser/commands.ts index dbaac656df6..6a2e3e52786 100644 --- a/src/vs/workbench/contrib/audioCues/browser/commands.ts +++ b/src/vs/workbench/contrib/audioCues/browser/commands.ts @@ -31,7 +31,7 @@ export class ShowAudioCueHelp extends Action2 { const accessibilityService = accessor.get(IAccessibilityService); const userGestureCues = [AudioCue.save, AudioCue.format]; const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.map((cue, idx) => ({ - label: userGestureCues.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.settingsKey)})` : cue.name, + label: userGestureCues.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.accessibilityStatusIndicatorSettingsKey + '.audioCue')})` : cue.name, audioCue: cue, buttons: userGestureCues.includes(cue) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), @@ -47,14 +47,22 @@ export class ShowAudioCueHelp extends Action2 { const disabledCues = AudioCue.allAudioCues.filter(cue => !enabledCues.includes(cue)); for (const cue of enabledCues) { if (!userGestureCues.includes(cue)) { - configurationService.updateValue(cue.settingsKey, accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'); + let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.accessibilityStatusIndicatorSettingsKey); + audioCue = accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'; + if (alert) { + configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue, alert }); + } else { + configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue }); + } } } for (const cue of disabledCues) { - if (userGestureCues.includes(cue)) { - configurationService.updateValue(cue.settingsKey, 'never'); + const alert = cue.alertMessage ? configurationService.getValue(cue.accessibilityStatusIndicatorSettingsKey + '.alert') : undefined; + const audioCue = userGestureCues.includes(cue) ? 'never' : 'off'; + if (alert) { + configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue, alert }); } else { - configurationService.updateValue(cue.settingsKey, 'off'); + configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue }); } } qp.hide(); @@ -83,9 +91,10 @@ export class ShowAccessibilityAlertHelp extends Action2 { const audioCueService = accessor.get(IAudioCueService); const quickInputService = accessor.get(IQuickInputService); const configurationService = accessor.get(IConfigurationService); + const accessibilityService = accessor.get(IAccessibilityService); const userGestureAlerts = [AudioCue.save, AudioCue.format]; const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.filter(c => c.alertSettingsKey).map((cue, idx) => ({ - label: userGestureAlerts.includes(cue) && cue.alertSettingsKey ? `${cue.name} (${configurationService.getValue(cue.alertSettingsKey)})` : cue.name, + label: userGestureAlerts.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.accessibilityStatusIndicatorSettingsKey + '.alert')})` : cue.name, audioCue: cue, buttons: userGestureAlerts.includes(cue) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), @@ -101,15 +110,17 @@ export class ShowAccessibilityAlertHelp extends Action2 { const disabledAlerts = AudioCue.allAudioCues.filter(cue => !enabledAlerts.includes(cue)); for (const cue of enabledAlerts) { if (!userGestureAlerts.includes(cue)) { - configurationService.updateValue(cue.alertSettingsKey!, true); + let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.accessibilityStatusIndicatorSettingsKey); + alert = cue.alertMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; + if (alert) { + configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue, alert }); + } } } for (const cue of disabledAlerts) { - if (userGestureAlerts.includes(cue)) { - configurationService.updateValue(cue.alertSettingsKey!, 'never'); - } else { - configurationService.updateValue(cue.alertSettingsKey!, false); - } + const alert = userGestureAlerts.includes(cue) ? 'never' : 'off'; + const audioCue = configurationService.getValue(cue.accessibilityStatusIndicatorSettingsKey + '.audioCue'); + configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue, alert }); } qp.hide(); }); From 8e55c76a60fb81ab659fe24f626bd4ca813a3603 Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 1 Feb 2024 16:16:34 -0800 Subject: [PATCH 0853/1897] Experimental notebook view zone --- src/vs/base/browser/ui/list/listView.ts | 14 +- src/vs/base/browser/ui/list/rangeMap.ts | 12 +- .../notebook/browser/notebookBrowser.ts | 17 + .../notebook/browser/notebookEditorWidget.ts | 8 +- .../notebook/browser/view/notebookCellList.ts | 61 +- .../browser/view/notebookCellListView.ts | 214 +++++++ .../browser/view/notebookRenderingCommon.ts | 24 +- .../browser/viewParts/notebookViewZones.ts | 172 ++++++ .../test/browser/notebookViewZones.test.ts | 525 ++++++++++++++++++ 9 files changed, 1025 insertions(+), 22 deletions(-) create mode 100644 src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts create mode 100644 src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 2166d25e02b..9edc5aa8c90 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -18,7 +18,7 @@ import { IRange, Range } from 'vs/base/common/range'; import { INewScrollDimensions, Scrollable, ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; import { ISpliceable } from 'vs/base/common/sequence'; import { IListDragAndDrop, IListDragEvent, IListGestureEvent, IListMouseEvent, IListRenderer, IListTouchEvent, IListVirtualDelegate, ListDragOverEffectPosition, ListDragOverEffectType } from 'vs/base/browser/ui/list/list'; -import { RangeMap, shift } from 'vs/base/browser/ui/list/rangeMap'; +import { IRangeMap, RangeMap, shift } from 'vs/base/browser/ui/list/rangeMap'; import { IRow, RowCache } from 'vs/base/browser/ui/list/rowCache'; import { IObservableValue } from 'vs/base/common/observableValue'; import { BugIndicatingError } from 'vs/base/common/errors'; @@ -291,7 +291,7 @@ export class ListView implements IListView { private items: IItem[]; private itemId: number; - private rangeMap: RangeMap; + protected rangeMap: IRangeMap; private cache: RowCache; private renderers = new Map>(); private lastRenderTop: number; @@ -377,7 +377,7 @@ export class ListView implements IListView { this.items = []; this.itemId = 0; - this.rangeMap = new RangeMap(options.paddingTop ?? 0); + this.rangeMap = this.createRangeMap(options.paddingTop ?? 0); for (const renderer of renderers) { this.renderers.set(renderer.templateId, renderer); @@ -561,6 +561,10 @@ export class ListView implements IListView { } } + protected createRangeMap(paddingTop: number): IRangeMap { + return new RangeMap(paddingTop); + } + splice(start: number, deleteCount: number, elements: readonly T[] = []): T[] { if (this.splicing) { throw new Error('Can\'t run recursive splices.'); @@ -631,7 +635,7 @@ export class ListView implements IListView { // TODO@joao: improve this optimization to catch even more cases if (start === 0 && deleteCount >= this.items.length) { - this.rangeMap = new RangeMap(this.rangeMap.paddingTop); + this.rangeMap = this.createRangeMap(this.rangeMap.paddingTop); this.rangeMap.splice(0, 0, inserted); deleted = this.items; this.items = inserted; @@ -686,7 +690,7 @@ export class ListView implements IListView { return deleted.map(i => i.element); } - private eventuallyUpdateScrollDimensions(): void { + protected eventuallyUpdateScrollDimensions(): void { this._scrollHeight = this.contentHeight; this.rowsContainer.style.height = `${this._scrollHeight}px`; diff --git a/src/vs/base/browser/ui/list/rangeMap.ts b/src/vs/base/browser/ui/list/rangeMap.ts index 91f435cd822..79ea7de024d 100644 --- a/src/vs/base/browser/ui/list/rangeMap.ts +++ b/src/vs/base/browser/ui/list/rangeMap.ts @@ -87,7 +87,17 @@ function concat(...groups: IRangedGroup[][]): IRangedGroup[] { return consolidate(groups.reduce((r, g) => r.concat(g), [])); } -export class RangeMap { +export interface IRangeMap { + readonly size: number; + readonly count: number; + paddingTop: number; + splice(index: number, deleteCount: number, items?: IItem[]): void; + indexAt(position: number): number; + indexAfter(position: number): number; + positionAt(index: number): number; +} + +export class RangeMap implements IRangeMap { private groups: IRangedGroup[] = []; private _size = 0; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index fe53a8c5a0a..6d0331e96d0 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -416,6 +416,21 @@ export interface IModelDecorationsChangeAccessor { deltaDecorations(oldDecorations: ICellModelDecorations[], newDecorations: ICellModelDeltaDecorations[]): ICellModelDecorations[]; } +export interface INotebookViewZone { + /** + * Use 0 to place a view zone before the first cell + */ + afterModelPosition: number; + domNode: HTMLElement; + + heightInPx: number; +} + +export interface INotebookViewZoneChangeAccessor { + addZone(zone: INotebookViewZone): string; + removeZone(id: string): void; + layoutZone(id: string): void; +} export type NotebookViewCellsSplice = [ number /* start */, @@ -678,6 +693,8 @@ export interface INotebookEditor { */ changeModelDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null; + changeViewZones(callback: (accessor: INotebookViewZoneChangeAccessor) => void): void; + /** * Get a contribution of this editor. * @id Unique identifier of the contribution. diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 2de78f318de..edf84021d7f 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -52,7 +52,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { contrastBorder, errorForeground, focusBorder, foreground, listInactiveSelectionBackground, registerColor, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, transparent } from 'vs/platform/theme/common/colorRegistry'; import { EDITOR_PANE_BACKGROUND, PANEL_BORDER, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors'; -import { CellEditState, CellFindMatchWithIndex, CellFocusMode, CellLayoutContext, CellRevealRangeType, CellRevealType, IActiveNotebookEditorDelegate, IBaseCellEditorOptions, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IInsetRenderOutput, IModelDecorationsChangeAccessor, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorDelegate, INotebookEditorMouseEvent, INotebookEditorOptions, INotebookEditorViewState, INotebookViewCellsUpdateEvent, INotebookWebviewMessage, RenderOutputType, ScrollToRevealBehavior } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFindMatchWithIndex, CellFocusMode, CellLayoutContext, CellRevealRangeType, CellRevealType, IActiveNotebookEditorDelegate, IBaseCellEditorOptions, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IInsetRenderOutput, IModelDecorationsChangeAccessor, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorDelegate, INotebookEditorMouseEvent, INotebookEditorOptions, INotebookEditorViewState, INotebookViewCellsUpdateEvent, INotebookViewZoneChangeAccessor, INotebookWebviewMessage, RenderOutputType, ScrollToRevealBehavior } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { notebookDebug } from 'vs/workbench/contrib/notebook/browser/notebookLogger'; @@ -2208,6 +2208,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD //#endregion + //#region View Zones + changeViewZones(callback: (accessor: INotebookViewZoneChangeAccessor) => void): void { + this._list.changeViewZones(callback); + } + //#endregion + //#region Kernel/Execution private async _loadKernelPreloads(): Promise { diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index e0bdf102eee..c839e091f88 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -18,7 +18,7 @@ import { PrefixSumComputer } from 'vs/editor/common/model/prefixSumComputer'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IListService, IWorkbenchListOptions, WorkbenchList } from 'vs/platform/list/browser/listService'; -import { CursorAtBoundary, ICellViewModel, CellEditState, CellFocusMode, ICellOutputViewModel, CellRevealType, CellRevealRangeType, CursorAtLineBoundary } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CursorAtBoundary, ICellViewModel, CellEditState, CellFocusMode, ICellOutputViewModel, CellRevealType, CellRevealRangeType, CursorAtLineBoundary, INotebookViewZoneChangeAccessor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; import { diff, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, CellKind, SelectionStateType, NOTEBOOK_EDITOR_CURSOR_LINE_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICellRange, cellRangesToIndexes, reduceCellRanges, cellRangesEqual } from 'vs/workbench/contrib/notebook/common/notebookRange'; @@ -34,6 +34,7 @@ import { NotebookCellListView } from 'vs/workbench/contrib/notebook/browser/view import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { NotebookCellAnchor } from 'vs/workbench/contrib/notebook/browser/view/notebookCellAnchor'; +import { NotebookViewZones } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones'; const enum CellRevealPosition { Top, @@ -76,6 +77,7 @@ function validateWebviewBoundary(element: HTMLElement) { export class NotebookCellList extends WorkbenchList implements IDisposable, IStyleController, INotebookCellList { protected override readonly view!: NotebookCellListView; + private viewZones!: NotebookViewZones; get onWillScroll(): Event { return this.view.onWillScroll; } get rowsContainer(): HTMLElement { @@ -299,7 +301,9 @@ export class NotebookCellList extends WorkbenchList implements ID } protected override createListView(container: HTMLElement, virtualDelegate: IListVirtualDelegate, renderers: IListRenderer[], viewOptions: IListViewOptions): IListView { - return new NotebookCellListView(container, virtualDelegate, renderers, viewOptions); + const listView = new NotebookCellListView(container, virtualDelegate, renderers, viewOptions); + this.viewZones = new NotebookViewZones(listView, this); + return listView; } attachWebview(element: HTMLElement) { @@ -341,6 +345,9 @@ export class NotebookCellList extends WorkbenchList implements ID return; } + // update whitespaces which are anchored to the model indexes + this.viewZones.onCellsChanged(e); + const currentRanges = this._hiddenRangeIds.map(id => this._viewModel!.getTrackedRange(id)).filter(range => range !== null) as ICellRange[]; const newVisibleViewCells: CellViewModel[] = getVisibleCells(this._viewModel!.viewCells as CellViewModel[], currentRanges); @@ -446,6 +453,8 @@ export class NotebookCellList extends WorkbenchList implements ID if (!hasDifference) { // they call 'setHiddenAreas' for a reason, even if the ranges are still the same, it's possible that the hiddenRangeSum is not update to date this._updateHiddenRangePrefixSum(newRanges); + this.viewZones.onHiddenRangesChange(); + this.viewZones.layout(); return false; } } @@ -457,11 +466,14 @@ export class NotebookCellList extends WorkbenchList implements ID // set hidden ranges prefix sum this._updateHiddenRangePrefixSum(newRanges); + // Update view zone positions after hidden ranges change + this.viewZones.onHiddenRangesChange(); if (triggerViewUpdate) { this.updateHiddenAreasInView(oldRanges, newRanges); } + this.viewZones.layout(); return true; } @@ -534,6 +546,8 @@ export class NotebookCellList extends WorkbenchList implements ID // after splice, the selected cells are deleted this._viewModel!.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }); } + + this.viewZones.layout(); } getModelIndex(cell: CellViewModel): number | undefined { @@ -573,6 +587,31 @@ export class NotebookCellList extends WorkbenchList implements ID } } + convertModelIndexToViewIndex(modelIndex: number): number { + if (!this.hiddenRangesPrefixSum) { + return modelIndex; + } + + return this.hiddenRangesPrefixSum.getIndexOf(modelIndex).index; + } + + modelIndexIsVisible(modelIndex: number): boolean { + if (!this.hiddenRangesPrefixSum) { + return true; + } + + const viewIndexInfo = this.hiddenRangesPrefixSum.getIndexOf(modelIndex); + if (viewIndexInfo.remainder !== 0) { + if (modelIndex >= this.hiddenRangesPrefixSum.getTotalSum()) { + // it's already after the last hidden range + return true; + } + return false; + } else { + return true; + } + } + private _getVisibleRangesFromIndex(topViewIndex: number, topModelIndex: number, bottomViewIndex: number, bottomModelIndex: number) { const stack: number[] = []; const ranges: ICellRange[] = []; @@ -1185,11 +1224,14 @@ export class NotebookCellList extends WorkbenchList implements ID }); } this.view.updateElementHeight(index, size, anchorElementIndex); + this.viewZones.layout(); return; } if (anchorElementIndex !== null) { - return this.view.updateElementHeight(index, size, anchorElementIndex); + this.view.updateElementHeight(index, size, anchorElementIndex); + this.viewZones.layout(); + return; } const focused = this.getFocus(); @@ -1200,11 +1242,19 @@ export class NotebookCellList extends WorkbenchList implements ID const heightDelta = size - this.view.elementHeight(index); if (this._notebookCellAnchor.shouldAnchor(this.view, focus, heightDelta, this.element(index))) { - return this.view.updateElementHeight(index, size, focus); + this.view.updateElementHeight(index, size, focus); + this.viewZones.layout(); + return; } } - return this.view.updateElementHeight(index, size, null); + this.view.updateElementHeight(index, size, null); + this.viewZones.layout(); + return; + } + + changeViewZones(callback: (accessor: INotebookViewZoneChangeAccessor) => void): void { + this.viewZones.changeViewZones(callback); } // override @@ -1368,6 +1418,7 @@ export class NotebookCellList extends WorkbenchList implements ID this._viewModelStore.dispose(); this._localDisposableStore.dispose(); this._notebookCellAnchor.dispose(); + this.viewZones.dispose(); super.dispose(); // un-ref diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts index 93e8b93b116..c618dd8cb78 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts @@ -5,14 +5,203 @@ import { IRange } from 'vs/base/common/range'; import { ListView } from 'vs/base/browser/ui/list/listView'; +import { IItem, IRangeMap } from 'vs/base/browser/ui/list/rangeMap'; +import { ConstantTimePrefixSumComputer } from 'vs/editor/common/model/prefixSumComputer'; + +export interface IWhitespace { + id: string; + /** + * To insert whitespace before the first item, use afterPosition 0. + * In other cases, afterPosition is 1-based. + */ + afterPosition: number; + size: number; +} +export class NotebookCellsLayout implements IRangeMap { + private _items: IItem[] = []; + private _whitespace: IWhitespace[] = []; + protected _prefixSumComputer: ConstantTimePrefixSumComputer = new ConstantTimePrefixSumComputer([]); + private _size = 0; + private _paddingTop = 0; + + get paddingTop() { + return this._paddingTop; + } + + set paddingTop(paddingTop: number) { + this._size = this._size + paddingTop - this._paddingTop; + this._paddingTop = paddingTop; + } + + get count(): number { + return this._items.length; + } + + /** + * Returns the sum of the sizes of all items in the range map. + */ + get size(): number { + return this._size; + } + + constructor(topPadding?: number) { + this._paddingTop = topPadding ?? 0; + this._size = this._paddingTop; + } + + /** + */ + splice(index: number, deleteCount: number, items?: IItem[] | undefined): void { + const inserts = items ?? []; + // Perform the splice operation on the items array. + this._items.splice(index, deleteCount, ...inserts); + + this._size = this._paddingTop + this._items.reduce((total, item) => total + item.size, 0) + this._whitespace.reduce((total, ws) => total + ws.size, 0); + this._prefixSumComputer.removeValues(index, deleteCount); + + // inserts should also include whitespaces + const newSizes = []; + for (let i = 0; i < inserts.length; i++) { + const insertIndex = i + index; + const existingWhitespace = this._whitespace.find(ws => ws.afterPosition === insertIndex + 1); + + if (existingWhitespace) { + newSizes.push(inserts[i].size + existingWhitespace.size); + } else { + newSizes.push(inserts[i].size); + } + } + this._prefixSumComputer.insertValues(index, newSizes); + + // Now that the items array has been updated, and the whitespaces are updated elsewhere, if an item is removed/inserted, the accumlated size of the items are all updated. + // Loop through all items from the index where the splice started, to the end + for (let i = index; i < this._items.length; i++) { + const existingWhitespace = this._whitespace.find(ws => ws.afterPosition === i + 1); + if (existingWhitespace) { + this._prefixSumComputer.setValue(i, this._items[i].size + existingWhitespace.size); + } else { + this._prefixSumComputer.setValue(i, this._items[i].size); + } + } + } + + insertWhitespace(id: string, afterPosition: number, size: number): void { + const existingWhitespace = this._whitespace.find(ws => ws.afterPosition === afterPosition); + if (existingWhitespace) { + throw new Error('Whitespace already exists at the specified position'); + } + + this._whitespace.push({ id, afterPosition: afterPosition, size }); + this._size += size; // Update the total size to include the whitespace + this._whitespace.sort((a, b) => a.afterPosition - b.afterPosition); // Keep the whitespace sorted by index + + // find item size of index + if (afterPosition > 0) { + const index = afterPosition - 1; + const itemSize = this._items[index].size; + const accSize = itemSize + size; + this._prefixSumComputer.setValue(index, accSize); + } + } + + changeOneWhitespace(id: string, afterPosition: number, size: number): void { + const whitespaceIndex = this._whitespace.findIndex(ws => ws.id === id); + if (whitespaceIndex !== -1) { + const whitespace = this._whitespace[whitespaceIndex]; + const oldAfterPosition = whitespace.afterPosition; + whitespace.afterPosition = afterPosition; + const oldSize = whitespace.size; + const delta = size - oldSize; + whitespace.size = size; + this._size += delta; + + if (oldAfterPosition > 0 && oldAfterPosition <= this._items.length) { + const index = oldAfterPosition - 1; + const itemSize = this._items[index].size; + const accSize = itemSize; + this._prefixSumComputer.setValue(index, accSize); + } + + if (afterPosition > 0 && afterPosition <= this._items.length) { + const index = afterPosition - 1; + const itemSize = this._items[index].size; + const accSize = itemSize + size; + this._prefixSumComputer.setValue(index, accSize); + } + } + } + + removeWhitespace(id: string): void { + const whitespaceIndex = this._whitespace.findIndex(ws => ws.id === id); + if (whitespaceIndex !== -1) { + const whitespace = this._whitespace[whitespaceIndex]; + this._whitespace.splice(whitespaceIndex, 1); + this._size -= whitespace.size; // Reduce the total size by the size of the removed whitespace + + if (whitespace.afterPosition > 0) { + const index = whitespace.afterPosition - 1; + const itemSize = this._items[index].size; + const accSize = itemSize; + this._prefixSumComputer.setValue(index, accSize); + } + } + } + + indexAt(position: number): number { + if (position < 0) { + return -1; + } + + const whitespaceBeforeFirstItem = this._whitespace.length > 0 && this._whitespace[0].afterPosition === 0 ? this._whitespace[0].size : 0; + + const offset = position - (this._paddingTop + whitespaceBeforeFirstItem); + if (offset <= 0) { + return 0; + } + + if (offset >= this._size) { + return this.count; + } + + return this._prefixSumComputer.getIndexOf(offset).index; + } + + indexAfter(position: number): number { + const index = this.indexAt(position); + return Math.min(index + 1, this._items.length); + } + + positionAt(index: number): number { + if (index < 0) { + return -1; + } + + if (this.count === 0) { + return -1; + } + + // index is zero based, if index+1 > this.count, then it points to the fictitious element after the last element of this array. + if (index >= this.count) { + return -1; + } + + const whitespaceBeforeFirstItem = this._whitespace.length > 0 && this._whitespace[0].afterPosition === 0 ? this._whitespace[0].size : 0; + return this._prefixSumComputer.getPrefixSum(index/** count */) + this._paddingTop + whitespaceBeforeFirstItem; + } +} export class NotebookCellListView extends ListView { + private _lastWhitespaceId: number = 0; private _renderingStack = 0; get inRenderingTransaction(): boolean { return this._renderingStack > 0; } + get notebookRangeMap(): NotebookCellsLayout { + return this.rangeMap as NotebookCellsLayout; + } + protected override render(previousRenderRange: IRange, renderTop: number, renderHeight: number, renderLeft: number | undefined, scrollWidth: number | undefined, updateItemsInDOM?: boolean): void { this._renderingStack++; super.render(previousRenderRange, renderTop, renderHeight, renderLeft, scrollWidth, updateItemsInDOM); @@ -24,4 +213,29 @@ export class NotebookCellListView extends ListView { super._rerender(renderTop, renderHeight, inSmoothScrolling); this._renderingStack--; } + + protected override createRangeMap(paddingTop: number): IRangeMap { + return new NotebookCellsLayout(paddingTop); + } + + insertWhitespace(afterPosition: number, size: number): string { + const scrollTop = this.scrollTop; + const id = `${++this._lastWhitespaceId}`; + this.notebookRangeMap.insertWhitespace(id, afterPosition, size); + + this._rerender(scrollTop, this.renderHeight, false); + this.eventuallyUpdateScrollDimensions(); + + return id; + } + + changeOneWhitespace(id: string, newAfterPosition: number, newSize: number) { + this.notebookRangeMap.changeOneWhitespace(id, newAfterPosition, newSize); + this.eventuallyUpdateScrollDimensions(); + } + + removeWhitespace(id: string): void { + this.notebookRangeMap.removeWhitespace(id); + this.eventuallyUpdateScrollDimensions(); + } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts index 29923c35ec2..01e4f343458 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts @@ -16,13 +16,13 @@ import { Selection } from 'vs/editor/common/core/selection'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchListOptionsUpdate } from 'vs/platform/list/browser/listService'; -import { CellRevealRangeType, CellRevealType, ICellOutputViewModel, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellRevealRangeType, CellRevealType, ICellOutputViewModel, ICellViewModel, INotebookViewZoneChangeAccessor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellPartsCollection } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; -export interface INotebookCellList { +export interface INotebookCellList extends ICoordinatesConverter { isDisposed: boolean; inRenderingTransaction: boolean; viewModel: NotebookViewModel | null; @@ -54,13 +54,6 @@ export interface INotebookCellList { attachViewModel(viewModel: NotebookViewModel): void; attachWebview(element: HTMLElement): void; clear(): void; - getCellViewScrollTop(cell: ICellViewModel): number; - getCellViewScrollBottom(cell: ICellViewModel): number; - getViewIndex(cell: ICellViewModel): number | undefined; - getViewIndex2(modelIndex: number): number | undefined; - getModelIndex(cell: CellViewModel): number | undefined; - getModelIndex2(viewIndex: number): number | undefined; - getVisibleRangesPlusViewportAboveAndBelow(): ICellRange[]; focusElement(element: ICellViewModel): void; selectElements(elements: ICellViewModel[]): void; getFocusedElements(): ICellViewModel[]; @@ -71,6 +64,7 @@ export interface INotebookCellList { revealRangeInCell(cell: ICellViewModel, range: Selection | Range, revealType: CellRevealRangeType): Promise; revealCellOffsetInCenter(element: ICellViewModel, offset: number): void; setHiddenAreas(_ranges: ICellRange[], triggerViewUpdate: boolean): boolean; + changeViewZones(callback: (accessor: INotebookViewZoneChangeAccessor) => void): void; domElementOfElement(element: ICellViewModel): HTMLElement | null; focusView(): void; triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent): void; @@ -114,4 +108,14 @@ export interface CodeCellRenderTemplate extends BaseCellRenderTemplate { editor: ICodeEditor; } - +export interface ICoordinatesConverter { + getCellViewScrollTop(cell: ICellViewModel): number; + getCellViewScrollBottom(cell: ICellViewModel): number; + getViewIndex(cell: ICellViewModel): number | undefined; + getViewIndex2(modelIndex: number): number | undefined; + getModelIndex(cell: CellViewModel): number | undefined; + getModelIndex2(viewIndex: number): number | undefined; + getVisibleRangesPlusViewportAboveAndBelow(): ICellRange[]; + modelIndexIsVisible(modelIndex: number): boolean; + convertModelIndexToViewIndex(modelIndex: number): number; +} diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts new file mode 100644 index 00000000000..bf00d8b7a68 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts @@ -0,0 +1,172 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { INotebookViewCellsUpdateEvent, INotebookViewZone, INotebookViewZoneChangeAccessor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookCellListView } from 'vs/workbench/contrib/notebook/browser/view/notebookCellListView'; +import { ICoordinatesConverter } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; +import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; + +interface IZoneWidget { + whitespaceId: string; + isInHiddenArea: boolean; + zone: INotebookViewZone; + domNode: FastDomNode; +} + +export class NotebookViewZones extends Disposable { + private _zones: { [key: string]: IZoneWidget }; + public domNode: FastDomNode; + + constructor(private readonly listView: NotebookCellListView, private readonly coordinator: ICoordinatesConverter) { + super(); + this.domNode = createFastDomNode(document.createElement('div')); + this.domNode.setClassName('view-zones'); + this.domNode.setPosition('absolute'); + this.domNode.setAttribute('role', 'presentation'); + this.domNode.setAttribute('aria-hidden', 'true'); + this._zones = {}; + + this.listView.containerDomNode.appendChild(this.domNode.domNode); + } + + changeViewZones(callback: (changeAccessor: INotebookViewZoneChangeAccessor) => void): void { + const changeAccessor: INotebookViewZoneChangeAccessor = { + addZone: (zone: INotebookViewZone): string => { + return this._addZone(zone); + }, + removeZone: (id: string): void => { + this._removeZone(id); + }, + layoutZone: (id: string): void => { + this._layoutZone(id); + } + }; + + safeInvoke1Arg(callback, changeAccessor); + } + + onCellsChanged(e: INotebookViewCellsUpdateEvent): void { + const splices = e.splices.slice().reverse(); + splices.forEach(splice => { + const [start, deleted, newCells] = splice; + const fromIndex = start; + const toIndex = start + deleted; + + // 1, 2, 0 + // delete cell index 1 and 2 + // from index 1, to index 3 (exclusive): [1, 3) + // if we have whitespace afterModelPosition 3, which is after cell index 2 + + for (const id in this._zones) { + const zone = this._zones[id].zone; + + const cellBeforeWhitespaceIndex = zone.afterModelPosition - 1; + + if (cellBeforeWhitespaceIndex >= fromIndex && cellBeforeWhitespaceIndex < toIndex) { + // The cell this whitespace was after has been deleted + // => move whitespace to before first deleted cell + zone.afterModelPosition = fromIndex; + this._updateWhitespace(this._zones[id]); + } else if (cellBeforeWhitespaceIndex >= toIndex) { + // adjust afterModelPosition for all other cells + const insertLength = newCells.length; + const offset = insertLength - deleted; + zone.afterModelPosition += offset; + this._updateWhitespace(this._zones[id]); + } + } + }); + } + + onHiddenRangesChange() { + for (const id in this._zones) { + this._updateWhitespace(this._zones[id]); + } + } + + private _updateWhitespace(zone: IZoneWidget) { + const whitespaceId = zone.whitespaceId; + const viewPosition = this.coordinator.convertModelIndexToViewIndex(zone.zone.afterModelPosition); + const isInHiddenArea = this._isInHiddenRanges(zone.zone); + zone.isInHiddenArea = isInHiddenArea; + this.listView.changeOneWhitespace(whitespaceId, viewPosition, isInHiddenArea ? 0 : zone.zone.heightInPx); + } + + layout() { + for (const id in this._zones) { + this._layoutZone(id); + } + } + + private _addZone(zone: INotebookViewZone): string { + const whitespaceId = this.listView.insertWhitespace(zone.afterModelPosition, zone.heightInPx); + const isInHiddenArea = this._isInHiddenRanges(zone); + const myZone: IZoneWidget = { + whitespaceId: whitespaceId, + zone: zone, + domNode: createFastDomNode(zone.domNode), + isInHiddenArea: isInHiddenArea + }; + + this._zones[whitespaceId] = myZone; + myZone.domNode.setPosition('absolute'); + myZone.domNode.domNode.style.width = '100%'; + myZone.domNode.setDisplay('none'); + myZone.domNode.setAttribute('notebook-view-zone', whitespaceId); + this.domNode.appendChild(myZone.domNode); + return whitespaceId; + } + + private _removeZone(id: string): void { + this.listView.removeWhitespace(id); + delete this._zones[id]; + } + + private _layoutZone(id: string): void { + const zoneWidget = this._zones[id]; + if (!zoneWidget) { + return; + } + + const isInHiddenArea = this._isInHiddenRanges(zoneWidget.zone); + + if (isInHiddenArea) { + zoneWidget.domNode.setDisplay('none'); + } else { + const afterPosition = zoneWidget.zone.afterModelPosition; + const index = afterPosition - 1; + const viewIndex = this.coordinator.convertModelIndexToViewIndex(index); + const top = this.listView.elementTop(viewIndex); + const height = this.listView.elementHeight(viewIndex); + zoneWidget.domNode.setTop(top + height); + zoneWidget.domNode.setDisplay('block'); + } + } + + private _isInHiddenRanges(zone: INotebookViewZone) { + // The view zone is between two cells (zone.afterModelPosition - 1, zone.afterModelPosition) + const afterIndex = zone.afterModelPosition; + + // In notebook, the first cell (markdown cell) in a folding range is always visible, so we need to check the cell after the notebook view zone + return !this.coordinator.modelIndexIsVisible(afterIndex); + + } + + override dispose(): void { + super.dispose(); + this._zones = {}; + } +} + +function safeInvoke1Arg(func: Function, arg1: any): any { + try { + return func(arg1); + } catch (e) { + onUnexpectedError(e); + } +} diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts new file mode 100644 index 00000000000..bba2e3adfe0 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts @@ -0,0 +1,525 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +import * as assert from 'assert'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { NotebookCellsLayout } from 'vs/workbench/contrib/notebook/browser/view/notebookCellListView'; +import { FoldingModel } from 'vs/workbench/contrib/notebook/browser/viewModel/foldingModel'; +import { CellEditType, CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { createNotebookCellList, setupInstantiationService, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; + +suite('NotebookRangeMap', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('empty', () => { + const rangeMap = new NotebookCellsLayout(); + assert.strictEqual(rangeMap.size, 0); + assert.strictEqual(rangeMap.count, 0); + }); + + const one = { size: 1 }; + const two = { size: 2 }; + const three = { size: 3 }; + const five = { size: 5 }; + const ten = { size: 10 }; + + test('length & count', () => { + const rangeMap = new NotebookCellsLayout(); + rangeMap.splice(0, 0, [one]); + assert.strictEqual(rangeMap.size, 1); + assert.strictEqual(rangeMap.count, 1); + }); + + test('length & count #2', () => { + const rangeMap = new NotebookCellsLayout(); + rangeMap.splice(0, 0, [one, one, one, one, one]); + assert.strictEqual(rangeMap.size, 5); + assert.strictEqual(rangeMap.count, 5); + }); + + test('length & count #3', () => { + const rangeMap = new NotebookCellsLayout(); + rangeMap.splice(0, 0, [five]); + assert.strictEqual(rangeMap.size, 5); + assert.strictEqual(rangeMap.count, 1); + }); + + test('length & count #4', () => { + const rangeMap = new NotebookCellsLayout(); + rangeMap.splice(0, 0, [five, five, five, five, five]); + assert.strictEqual(rangeMap.size, 25); + assert.strictEqual(rangeMap.count, 5); + }); + + test('insert', () => { + const rangeMap = new NotebookCellsLayout(); + rangeMap.splice(0, 0, [five, five, five, five, five]); + assert.strictEqual(rangeMap.size, 25); + assert.strictEqual(rangeMap.count, 5); + + rangeMap.splice(0, 0, [five, five, five, five, five]); + assert.strictEqual(rangeMap.size, 50); + assert.strictEqual(rangeMap.count, 10); + + rangeMap.splice(5, 0, [ten, ten]); + assert.strictEqual(rangeMap.size, 70); + assert.strictEqual(rangeMap.count, 12); + + rangeMap.splice(12, 0, [{ size: 200 }]); + assert.strictEqual(rangeMap.size, 270); + assert.strictEqual(rangeMap.count, 13); + }); + + test('delete', () => { + const rangeMap = new NotebookCellsLayout(); + rangeMap.splice(0, 0, [five, five, five, five, five, + five, five, five, five, five, + five, five, five, five, five, + five, five, five, five, five]); + assert.strictEqual(rangeMap.size, 100); + assert.strictEqual(rangeMap.count, 20); + + rangeMap.splice(10, 5); + assert.strictEqual(rangeMap.size, 75); + assert.strictEqual(rangeMap.count, 15); + + rangeMap.splice(0, 1); + assert.strictEqual(rangeMap.size, 70); + assert.strictEqual(rangeMap.count, 14); + + rangeMap.splice(1, 13); + assert.strictEqual(rangeMap.size, 5); + assert.strictEqual(rangeMap.count, 1); + + rangeMap.splice(1, 1); + assert.strictEqual(rangeMap.size, 5); + assert.strictEqual(rangeMap.count, 1); + }); + + test('insert & delete', () => { + const rangeMap = new NotebookCellsLayout(); + assert.strictEqual(rangeMap.size, 0); + assert.strictEqual(rangeMap.count, 0); + + rangeMap.splice(0, 0, [one]); + assert.strictEqual(rangeMap.size, 1); + assert.strictEqual(rangeMap.count, 1); + + rangeMap.splice(0, 1); + assert.strictEqual(rangeMap.size, 0); + assert.strictEqual(rangeMap.count, 0); + }); + + test('insert & delete #2', () => { + const rangeMap = new NotebookCellsLayout(); + rangeMap.splice(0, 0, [one, one, one, one, one, + one, one, one, one, one]); + rangeMap.splice(2, 6); + assert.strictEqual(rangeMap.count, 4); + assert.strictEqual(rangeMap.size, 4); + }); + + test('insert & delete #3', () => { + const rangeMap = new NotebookCellsLayout(); + rangeMap.splice(0, 0, [one, one, one, one, one, + one, one, one, one, one, + two, two, two, two, two, + two, two, two, two, two]); + rangeMap.splice(8, 4); + assert.strictEqual(rangeMap.count, 16); + assert.strictEqual(rangeMap.size, 24); + }); + + test('insert & delete #4', () => { + const rangeMap = new NotebookCellsLayout(); + rangeMap.splice(0, 0, [one, one, one, one, one, + one, one, one, one, one, + two, two, two, two, two, + two, two, two, two, two]); + rangeMap.splice(5, 0, [three, three, three, three, three]); + assert.strictEqual(rangeMap.count, 25); + assert.strictEqual(rangeMap.size, 45); + + rangeMap.splice(4, 7); + assert.strictEqual(rangeMap.count, 18); + assert.strictEqual(rangeMap.size, 28); + }); + + suite('indexAt, positionAt', () => { + test('empty', () => { + const rangeMap = new NotebookCellsLayout(); + assert.strictEqual(rangeMap.indexAt(0), 0); + assert.strictEqual(rangeMap.indexAt(10), 0); + assert.strictEqual(rangeMap.indexAt(-1), -1); + assert.strictEqual(rangeMap.positionAt(0), -1); + assert.strictEqual(rangeMap.positionAt(10), -1); + assert.strictEqual(rangeMap.positionAt(-1), -1); + }); + + test('simple', () => { + const rangeMap = new NotebookCellsLayout(); + rangeMap.splice(0, 0, [one]); + assert.strictEqual(rangeMap.indexAt(0), 0); + assert.strictEqual(rangeMap.indexAt(1), 1); + assert.strictEqual(rangeMap.positionAt(0), 0); + assert.strictEqual(rangeMap.positionAt(1), -1); + }); + + test('simple #2', () => { + const rangeMap = new NotebookCellsLayout(); + rangeMap.splice(0, 0, [ten]); + assert.strictEqual(rangeMap.indexAt(0), 0); + assert.strictEqual(rangeMap.indexAt(5), 0); + assert.strictEqual(rangeMap.indexAt(9), 0); + assert.strictEqual(rangeMap.indexAt(10), 1); + assert.strictEqual(rangeMap.positionAt(0), 0); + assert.strictEqual(rangeMap.positionAt(1), -1); + }); + + test('insert', () => { + const rangeMap = new NotebookCellsLayout(); + rangeMap.splice(0, 0, [one, one, one, one, one, one, one, one, one, one]); + assert.strictEqual(rangeMap.indexAt(0), 0); + assert.strictEqual(rangeMap.indexAt(1), 1); + assert.strictEqual(rangeMap.indexAt(5), 5); + assert.strictEqual(rangeMap.indexAt(9), 9); + assert.strictEqual(rangeMap.indexAt(10), 10); + assert.strictEqual(rangeMap.indexAt(11), 10); + + rangeMap.splice(10, 0, [one, one, one, one, one, one, one, one, one, one]); + assert.strictEqual(rangeMap.indexAt(10), 10); + assert.strictEqual(rangeMap.indexAt(19), 19); + assert.strictEqual(rangeMap.indexAt(20), 20); + assert.strictEqual(rangeMap.indexAt(21), 20); + assert.strictEqual(rangeMap.positionAt(0), 0); + assert.strictEqual(rangeMap.positionAt(1), 1); + assert.strictEqual(rangeMap.positionAt(19), 19); + assert.strictEqual(rangeMap.positionAt(20), -1); + }); + + test('delete', () => { + const rangeMap = new NotebookCellsLayout(); + rangeMap.splice(0, 0, [one, one, one, one, one, one, one, one, one, one]); + rangeMap.splice(2, 6); + + assert.strictEqual(rangeMap.indexAt(0), 0); + assert.strictEqual(rangeMap.indexAt(1), 1); + assert.strictEqual(rangeMap.indexAt(3), 3); + assert.strictEqual(rangeMap.indexAt(4), 4); + assert.strictEqual(rangeMap.indexAt(5), 4); + assert.strictEqual(rangeMap.positionAt(0), 0); + assert.strictEqual(rangeMap.positionAt(1), 1); + assert.strictEqual(rangeMap.positionAt(3), 3); + assert.strictEqual(rangeMap.positionAt(4), -1); + }); + + test('delete #2', () => { + const rangeMap = new NotebookCellsLayout(); + rangeMap.splice(0, 0, [ten, ten, ten, ten, ten, ten, ten, ten, ten, ten]); + rangeMap.splice(2, 6); + + assert.strictEqual(rangeMap.indexAt(0), 0); + assert.strictEqual(rangeMap.indexAt(1), 0); + assert.strictEqual(rangeMap.indexAt(30), 3); + assert.strictEqual(rangeMap.indexAt(40), 4); + assert.strictEqual(rangeMap.indexAt(50), 4); + assert.strictEqual(rangeMap.positionAt(0), 0); + assert.strictEqual(rangeMap.positionAt(1), 10); + assert.strictEqual(rangeMap.positionAt(2), 20); + assert.strictEqual(rangeMap.positionAt(3), 30); + assert.strictEqual(rangeMap.positionAt(4), -1); + }); + }); +}); + +suite('NotebookRangeMap with top padding', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('empty', () => { + const rangeMap = new NotebookCellsLayout(10); + assert.strictEqual(rangeMap.size, 10); + assert.strictEqual(rangeMap.count, 0); + }); + + const one = { size: 1 }; + const five = { size: 5 }; + const ten = { size: 10 }; + + test('length & count', () => { + const rangeMap = new NotebookCellsLayout(10); + rangeMap.splice(0, 0, [one]); + assert.strictEqual(rangeMap.size, 11); + assert.strictEqual(rangeMap.count, 1); + }); + + test('length & count #2', () => { + const rangeMap = new NotebookCellsLayout(10); + rangeMap.splice(0, 0, [one, one, one, one, one]); + assert.strictEqual(rangeMap.size, 15); + assert.strictEqual(rangeMap.count, 5); + }); + + test('length & count #3', () => { + const rangeMap = new NotebookCellsLayout(10); + rangeMap.splice(0, 0, [five]); + assert.strictEqual(rangeMap.size, 15); + assert.strictEqual(rangeMap.count, 1); + }); + + test('length & count #4', () => { + const rangeMap = new NotebookCellsLayout(10); + rangeMap.splice(0, 0, [five, five, five, five, five]); + assert.strictEqual(rangeMap.size, 35); + assert.strictEqual(rangeMap.count, 5); + }); + + test('insert', () => { + const rangeMap = new NotebookCellsLayout(10); + rangeMap.splice(0, 0, [five, five, five, five, five]); + assert.strictEqual(rangeMap.size, 35); + assert.strictEqual(rangeMap.count, 5); + + rangeMap.splice(0, 0, [five, five, five, five, five]); + assert.strictEqual(rangeMap.size, 60); + assert.strictEqual(rangeMap.count, 10); + + rangeMap.splice(5, 0, [ten, ten]); + assert.strictEqual(rangeMap.size, 80); + assert.strictEqual(rangeMap.count, 12); + + rangeMap.splice(12, 0, [{ size: 200 }]); + assert.strictEqual(rangeMap.size, 280); + assert.strictEqual(rangeMap.count, 13); + }); +}); + +suite('NotebookRangeMap with whitesspaces', () => { + let testDisposables: DisposableStore; + let instantiationService: TestInstantiationService; + let config: TestConfigurationService; + + teardown(() => { + testDisposables.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + setup(() => { + testDisposables = new DisposableStore(); + instantiationService = setupInstantiationService(testDisposables); + config = new TestConfigurationService({ + [NotebookSetting.anchorToFocusedCell]: 'auto' + }); + + instantiationService.stub(IConfigurationService, config); + }); + + test('Whitespace with editing', async function () { + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markup, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markup, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['# header c', 'markdown', CellKind.Markup, [], {}] + ], + async (editor, viewModel, disposables) => { + viewModel.restoreEditorViewState({ + editingCells: [false, false, false, false, false], + cellLineNumberStates: {}, + editorViewStates: [null, null, null, null, null], + cellTotalHeights: [50, 100, 50, 100, 50], + collapsedInputCells: {}, + collapsedOutputCells: {}, + }); + + const cellList = createNotebookCellList(instantiationService, disposables); + disposables.add(cellList); + cellList.attachViewModel(viewModel); + + // render height 210, it can render 3 full cells and 1 partial cell + cellList.layout(210, 100); + assert.strictEqual(cellList.scrollHeight, 350); + + cellList.changeViewZones(accessor => { + const id = accessor.addZone({ + afterModelPosition: 1, + heightInPx: 20, + domNode: document.createElement('div') + }); + + accessor.layoutZone(id); + assert.strictEqual(cellList.scrollHeight, 370); + + assert.strictEqual(cellList.getElementTop(0), 0); + assert.strictEqual(cellList.getElementTop(1), 70); + assert.strictEqual(cellList.getElementTop(2), 170); + + const textModel = editor.textModel; + textModel.applyEdits([ + { editType: CellEditType.Replace, index: 0, count: 1, cells: [] }, + ], true, undefined, () => undefined, undefined, true); + + assert.strictEqual(cellList.getElementTop(0), 20); + assert.strictEqual(cellList.getElementTop(1), 120); + assert.strictEqual(cellList.getElementTop(2), 170); + + accessor.removeZone(id); + }); + }); + }); + + test('Whitespace with folding support', async function () { + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markup, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markup, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['# header c', 'markdown', CellKind.Markup, [], {}] + ], + async (editor, viewModel, disposables) => { + viewModel.restoreEditorViewState({ + editingCells: [false, false, false, false, false], + cellLineNumberStates: {}, + editorViewStates: [null, null, null, null, null], + cellTotalHeights: [50, 100, 50, 100, 50], + collapsedInputCells: {}, + collapsedOutputCells: {}, + }); + + const cellList = createNotebookCellList(instantiationService, disposables); + disposables.add(cellList); + cellList.attachViewModel(viewModel); + + // render height 210, it can render 3 full cells and 1 partial cell + cellList.layout(210, 100); + assert.strictEqual(cellList.scrollHeight, 350); + + cellList.changeViewZones(accessor => { + const id = accessor.addZone({ + afterModelPosition: 1, + heightInPx: 20, + domNode: document.createElement('div') + }); + + accessor.layoutZone(id); + assert.strictEqual(cellList.scrollHeight, 370); + + assert.strictEqual(cellList.getElementTop(0), 0); + assert.strictEqual(cellList.getElementTop(1), 70); + assert.strictEqual(cellList.getElementTop(2), 170); + assert.strictEqual(cellList.getElementTop(3), 220); + assert.strictEqual(cellList.getElementTop(4), 320); + + accessor.removeZone(id); + assert.strictEqual(cellList.scrollHeight, 350); + }); + + // Whitespace should be hidden if it's after the header in a folding region + cellList.changeViewZones(accessor => { + const id = accessor.addZone({ + afterModelPosition: 3, + heightInPx: 20, + domNode: document.createElement('div') + }); + + accessor.layoutZone(id); + assert.strictEqual(cellList.scrollHeight, 370); + + const foldingModel = disposables.add(new FoldingModel()); + foldingModel.attachViewModel(viewModel); + foldingModel.applyMemento([{ start: 2, end: 3 }]); + viewModel.updateFoldingRanges(foldingModel.regions); + assert.deepStrictEqual(viewModel.getHiddenRanges(), [ + { start: 3, end: 3 } + ]); + cellList.setHiddenAreas(viewModel.getHiddenRanges(), true); + assert.strictEqual(cellList.scrollHeight, 250); + + assert.strictEqual(cellList.getElementTop(0), 0); + assert.strictEqual(cellList.getElementTop(1), 50); + assert.strictEqual(cellList.getElementTop(2), 150); + assert.strictEqual(cellList.getElementTop(3), 200); + + cellList.setHiddenAreas([], true); + assert.strictEqual(cellList.scrollHeight, 370); + accessor.removeZone(id); + assert.strictEqual(cellList.scrollHeight, 350); + }); + + // Whitespace should not be hidden if it's after the last cell in a folding region + cellList.changeViewZones(accessor => { + const id = accessor.addZone({ + afterModelPosition: 4, + heightInPx: 20, + domNode: document.createElement('div') + }); + + accessor.layoutZone(id); + assert.strictEqual(cellList.scrollHeight, 370); + + const foldingModel = disposables.add(new FoldingModel()); + foldingModel.attachViewModel(viewModel); + foldingModel.applyMemento([{ start: 2, end: 3 }]); + viewModel.updateFoldingRanges(foldingModel.regions); + assert.deepStrictEqual(viewModel.getHiddenRanges(), [ + { start: 3, end: 3 } + ]); + cellList.setHiddenAreas(viewModel.getHiddenRanges(), true); + assert.strictEqual(cellList.scrollHeight, 270); + + assert.strictEqual(cellList.getElementTop(0), 0); + assert.strictEqual(cellList.getElementTop(1), 50); + assert.strictEqual(cellList.getElementTop(2), 150); + assert.strictEqual(cellList.getElementTop(3), 220); + + cellList.setHiddenAreas([], true); + assert.strictEqual(cellList.scrollHeight, 370); + accessor.removeZone(id); + assert.strictEqual(cellList.scrollHeight, 350); + }); + + // Whitespace move when previous folding regions fold + cellList.changeViewZones(accessor => { + const id = accessor.addZone({ + afterModelPosition: 4, + heightInPx: 20, + domNode: document.createElement('div') + }); + + accessor.layoutZone(id); + assert.strictEqual(cellList.scrollHeight, 370); + + const foldingModel = disposables.add(new FoldingModel()); + foldingModel.attachViewModel(viewModel); + foldingModel.applyMemento([{ start: 0, end: 1 }]); + viewModel.updateFoldingRanges(foldingModel.regions); + assert.deepStrictEqual(viewModel.getHiddenRanges(), [ + { start: 1, end: 1 } + ]); + cellList.setHiddenAreas(viewModel.getHiddenRanges(), true); + assert.strictEqual(cellList.scrollHeight, 270); + + assert.strictEqual(cellList.getElementTop(0), 0); + assert.strictEqual(cellList.getElementTop(1), 50); + assert.strictEqual(cellList.getElementTop(2), 100); + assert.strictEqual(cellList.getElementTop(3), 220); + + cellList.setHiddenAreas([], true); + assert.strictEqual(cellList.scrollHeight, 370); + accessor.removeZone(id); + assert.strictEqual(cellList.scrollHeight, 350); + }); + }); + }); +}); From d4dcb7741427c59582f8e22fbb29f38abe196711 Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 1 Feb 2024 16:22:03 -0800 Subject: [PATCH 0854/1897] align rangemap testing --- .../browser/view/notebookCellListView.ts | 2 +- .../test/browser/notebookViewZones.test.ts | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts index c618dd8cb78..c26ca53ac85 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts @@ -159,7 +159,7 @@ export class NotebookCellsLayout implements IRangeMap { return 0; } - if (offset >= this._size) { + if (offset >= (this._size - this._paddingTop - whitespaceBeforeFirstItem)) { return this.count; } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts index bba2e3adfe0..059704d8176 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts @@ -300,6 +300,29 @@ suite('NotebookRangeMap with top padding', () => { assert.strictEqual(rangeMap.size, 280); assert.strictEqual(rangeMap.count, 13); }); + + suite('indexAt, positionAt', () => { + test('empty', () => { + const rangeMap = new NotebookCellsLayout(10); + assert.strictEqual(rangeMap.indexAt(0), 0); + assert.strictEqual(rangeMap.indexAt(10), 0); + assert.strictEqual(rangeMap.indexAt(-1), -1); + assert.strictEqual(rangeMap.positionAt(0), -1); + assert.strictEqual(rangeMap.positionAt(10), -1); + assert.strictEqual(rangeMap.positionAt(-1), -1); + }); + + test('simple', () => { + const rangeMap = new NotebookCellsLayout(10); + rangeMap.splice(0, 0, [one]); + assert.strictEqual(rangeMap.indexAt(0), 0); + assert.strictEqual(rangeMap.indexAt(1), 0); + assert.strictEqual(rangeMap.indexAt(10), 0); + assert.strictEqual(rangeMap.indexAt(11), 1); + assert.strictEqual(rangeMap.positionAt(0), 10); + assert.strictEqual(rangeMap.positionAt(1), -1); + }); + }); }); suite('NotebookRangeMap with whitesspaces', () => { From 7923151da4b3567636c1346df9e8ba682744bbf3 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 1 Feb 2024 16:36:35 -0800 Subject: [PATCH 0855/1897] Adopt localize2 in more spots (#204030) Another pass adopting localize2 --- .../browser/link/goToDefinitionAtPosition.ts | 2 +- src/vs/nls.ts | 11 ++++- .../browser/actions/chatQuickInputActions.ts | 2 +- .../test/browser/commentsView.test.ts | 3 +- .../debug/browser/debug.contribution.ts | 3 +- .../browser/editSessions.contribution.ts | 8 ++-- .../extensions/browser/extensionsViewlet.ts | 5 +-- .../editors/textFileSaveErrorHandler.ts | 2 +- .../files/browser/fileActions.contribution.ts | 12 ++--- .../contrib/files/browser/fileActions.ts | 44 +++++++++---------- .../contrib/files/browser/fileConstants.ts | 10 ++--- .../files/browser/views/openEditorsView.ts | 2 +- .../fileActions.contribution.ts | 8 ++-- .../inlineChat/browser/inlineChatActions.ts | 4 +- .../issue/common/issue.contribution.ts | 7 +-- .../electron-sandbox/issue.contribution.ts | 2 +- .../electron-sandbox/localHistoryCommands.ts | 7 +-- .../common/localizationsActions.ts | 4 +- .../markers/browser/markers.contribution.ts | 6 +-- .../contrib/markers/browser/messages.ts | 2 +- .../share/browser/share.contribution.ts | 6 +-- .../tasks/browser/abstractTaskService.ts | 6 +-- .../tasks/browser/task.contribution.ts | 2 +- .../update/browser/update.contribution.ts | 12 ++--- .../contrib/url/browser/trustedDomains.ts | 4 +- .../contrib/url/browser/url.contribution.ts | 5 +-- .../userDataSync/browser/userDataSync.ts | 2 +- .../webviewPanel/browser/webviewCommands.ts | 4 +- .../actions/installActions.ts | 10 +---- .../services/views/browser/viewsService.ts | 6 +-- .../views/common/viewContainerModel.ts | 3 +- 31 files changed, 98 insertions(+), 106 deletions(-) diff --git a/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts b/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts index f5a08f94c1e..8a16c7f7a9f 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts @@ -304,7 +304,7 @@ export class GotoDefinitionAtPositionEditorContribution implements IEditorContri this.editor.setPosition(position); return this.editor.invokeWithinContext((accessor) => { const canPeek = !openToSide && this.editor.getOption(EditorOption.definitionLinkOpensInPeek) && !this.isInPeekEditor(accessor); - const action = new DefinitionAction({ openToSide, openInPeek: canPeek, muteMessage: true }, { title: { value: '', original: '' }, id: '', precondition: undefined }); + const action = new DefinitionAction({ openToSide, openInPeek: canPeek, muteMessage: true }, { title: nls.unlocalized2(''), id: '', precondition: undefined }); return action.run(accessor); }); } diff --git a/src/vs/nls.ts b/src/vs/nls.ts index 233840e65ab..47a5dcdf6d8 100644 --- a/src/vs/nls.ts +++ b/src/vs/nls.ts @@ -202,7 +202,16 @@ export function localize2(data: ILocalizeInfo | string, message: string, ...args return { value: original, original - }; + } as ILocalizedString; +} + +/** + * Create a `ILocalizedString` object that represents an unlocalized string. + * + * This lets you explicitly pass in unlocalized strings to places that normally expect localized strings. + */ +export function unlocalized2(str: string): ILocalizedString { + return { value: str, original: str } as ILocalizedString; } /** diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts index 9381aa94208..4df9285764e 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts @@ -171,7 +171,7 @@ export function getQuickChatActionForProvider(id: string, label: string) { super({ id: `workbench.action.openQuickChat.${id}`, category: CHAT_CATEGORY, - title: { value: localize('interactiveSession.open', "Open Quick Chat ({0})", label), original: `Open Quick Chat (${label})` }, + title: localize2('interactiveSession.open', "Open Quick Chat ({0})", label), f1: true }); } diff --git a/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts b/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts index de511db52f3..e6e274ed91d 100644 --- a/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts +++ b/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts @@ -19,6 +19,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CancellationToken } from 'vs/base/common/cancellation'; import { URI, UriComponents } from 'vs/base/common/uri'; +import { unlocalized2 } from 'vs/nls'; class TestCommentThread implements CommentThread { isDocumentCommentThread(): this is CommentThread { @@ -82,7 +83,7 @@ export class TestViewDescriptorService implements Partial { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 8fcb0937fd9..ed1ea24a08a 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -215,13 +215,13 @@ appendToCommandPalette({ appendToCommandPalette({ id: SAVE_FILE_COMMAND_ID, - title: { value: SAVE_FILE_LABEL, original: 'Save' }, + title: SAVE_FILE_LABEL, category: Categories.File }); appendToCommandPalette({ id: SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, - title: { value: SAVE_FILE_WITHOUT_FORMATTING_LABEL, original: 'Save without Formatting' }, + title: SAVE_FILE_WITHOUT_FORMATTING_LABEL, category: Categories.File }); @@ -251,26 +251,26 @@ appendToCommandPalette({ appendToCommandPalette({ id: SAVE_FILE_AS_COMMAND_ID, - title: { value: SAVE_FILE_AS_LABEL, original: 'Save As...' }, + title: SAVE_FILE_AS_LABEL, category: Categories.File }); appendToCommandPalette({ id: NEW_FILE_COMMAND_ID, - title: { value: NEW_FILE_LABEL, original: 'New File' }, + title: NEW_FILE_LABEL, category: Categories.File }, WorkspaceFolderCountContext.notEqualsTo('0')); appendToCommandPalette({ id: NEW_FOLDER_COMMAND_ID, - title: { value: NEW_FOLDER_LABEL, original: 'New Folder' }, + title: NEW_FOLDER_LABEL, category: Categories.File, metadata: { description: nls.localize2('newFolderDescription', "Create a new folder or directory") } }, WorkspaceFolderCountContext.notEqualsTo('0')); appendToCommandPalette({ id: NEW_UNTITLED_FILE_COMMAND_ID, - title: { value: NEW_UNTITLED_FILE_LABEL, original: 'New Untitled Text File' }, + title: NEW_UNTITLED_FILE_LABEL, category: Categories.File }); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 2f3dc318c25..cb64ae02fb4 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -61,9 +61,9 @@ import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { ILocalizedString } from 'vs/platform/action/common/action'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; -export const NEW_FILE_LABEL = nls.localize('newFile', "New File..."); +export const NEW_FILE_LABEL = nls.localize2('newFile', "New File..."); export const NEW_FOLDER_COMMAND_ID = 'explorer.newFolder'; -export const NEW_FOLDER_LABEL = nls.localize('newFolder', "New Folder..."); +export const NEW_FOLDER_LABEL = nls.localize2('newFolder', "New Folder..."); export const TRIGGER_RENAME_LABEL = nls.localize('rename', "Rename..."); export const MOVE_FILE_TO_TRASH_LABEL = nls.localize('delete', "Delete"); export const COPY_FILE_LABEL = nls.localize('copyFile', "Copy"); @@ -481,12 +481,12 @@ async function askForOverwrite(fileService: IFileService, dialogService: IDialog export class GlobalCompareResourcesAction extends Action2 { static readonly ID = 'workbench.files.action.compareFileWith'; - static readonly LABEL = nls.localize('globalCompareFile', "Compare Active File With..."); + static readonly LABEL = nls.localize2('globalCompareFile', "Compare Active File With..."); constructor() { super({ id: GlobalCompareResourcesAction.ID, - title: { value: GlobalCompareResourcesAction.LABEL, original: 'Compare Active File With...' }, + title: GlobalCompareResourcesAction.LABEL, f1: true, category: Categories.File, precondition: ActiveEditorContext @@ -609,12 +609,12 @@ export class CloseGroupAction extends Action { export class FocusFilesExplorer extends Action2 { static readonly ID = 'workbench.files.action.focusFilesExplorer'; - static readonly LABEL = nls.localize('focusFilesExplorer', "Focus on Files Explorer"); + static readonly LABEL = nls.localize2('focusFilesExplorer', "Focus on Files Explorer"); constructor() { super({ id: FocusFilesExplorer.ID, - title: { value: FocusFilesExplorer.LABEL, original: 'Focus on Files Explorer' }, + title: FocusFilesExplorer.LABEL, f1: true, category: Categories.File }); @@ -629,12 +629,12 @@ export class FocusFilesExplorer extends Action2 { export class ShowActiveFileInExplorer extends Action2 { static readonly ID = 'workbench.files.action.showActiveFileInExplorer'; - static readonly LABEL = nls.localize('showInExplorer', "Reveal Active File in Explorer View"); + static readonly LABEL = nls.localize2('showInExplorer', "Reveal Active File in Explorer View"); constructor() { super({ id: ShowActiveFileInExplorer.ID, - title: { value: ShowActiveFileInExplorer.LABEL, original: 'Reveal Active File in Explorer View' }, + title: ShowActiveFileInExplorer.LABEL, f1: true, category: Categories.File }); @@ -653,13 +653,13 @@ export class ShowActiveFileInExplorer extends Action2 { export class OpenActiveFileInEmptyWorkspace extends Action2 { static readonly ID = 'workbench.action.files.showOpenedFileInNewWindow'; - static readonly LABEL = nls.localize('openFileInEmptyWorkspace', "Open Active File in New Empty Workspace"); + static readonly LABEL = nls.localize2('openFileInEmptyWorkspace', "Open Active File in New Empty Workspace"); constructor( ) { super({ id: OpenActiveFileInEmptyWorkspace.ID, - title: { value: OpenActiveFileInEmptyWorkspace.LABEL, original: 'Open Active File in New Empty Workspace' }, + title: OpenActiveFileInEmptyWorkspace.LABEL, f1: true, category: Categories.File, precondition: EmptyWorkspaceSupportContext @@ -763,12 +763,12 @@ function getWellFormedFileName(filename: string): string { export class CompareNewUntitledTextFilesAction extends Action2 { static readonly ID = 'workbench.files.action.compareNewUntitledTextFiles'; - static readonly LABEL = nls.localize('compareNewUntitledTextFiles', "Compare New Untitled Text Files"); + static readonly LABEL = nls.localize2('compareNewUntitledTextFiles', "Compare New Untitled Text Files"); constructor() { super({ id: CompareNewUntitledTextFilesAction.ID, - title: { value: CompareNewUntitledTextFilesAction.LABEL, original: 'Compare New Untitled Text Files' }, + title: CompareNewUntitledTextFilesAction.LABEL, f1: true, category: Categories.File }); @@ -788,7 +788,7 @@ export class CompareNewUntitledTextFilesAction extends Action2 { export class CompareWithClipboardAction extends Action2 { static readonly ID = 'workbench.files.action.compareWithClipboard'; - static readonly LABEL = nls.localize('compareWithClipboard', "Compare Active File with Clipboard"); + static readonly LABEL = nls.localize2('compareWithClipboard', "Compare Active File with Clipboard"); private registrationDisposal: IDisposable | undefined; private static SCHEME_COUNTER = 0; @@ -796,7 +796,7 @@ export class CompareWithClipboardAction extends Action2 { constructor() { super({ id: CompareWithClipboardAction.ID, - title: { value: CompareWithClipboardAction.LABEL, original: 'Compare Active File with Clipboard' }, + title: CompareWithClipboardAction.LABEL, f1: true, category: Categories.File, keybinding: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyC), weight: KeybindingWeight.WorkbenchContrib } @@ -1261,12 +1261,12 @@ class BaseSetActiveEditorReadonlyInSession extends Action2 { export class SetActiveEditorReadonlyInSession extends BaseSetActiveEditorReadonlyInSession { static readonly ID = 'workbench.action.files.setActiveEditorReadonlyInSession'; - static readonly LABEL = nls.localize('setActiveEditorReadonlyInSession', "Set Active Editor Read-only in Session"); + static readonly LABEL = nls.localize2('setActiveEditorReadonlyInSession', "Set Active Editor Read-only in Session"); constructor() { super( SetActiveEditorReadonlyInSession.ID, - { value: SetActiveEditorReadonlyInSession.LABEL, original: 'Set Active Editor Readonly in Session' }, + SetActiveEditorReadonlyInSession.LABEL, true ); } @@ -1275,12 +1275,12 @@ export class SetActiveEditorReadonlyInSession extends BaseSetActiveEditorReadonl export class SetActiveEditorWriteableInSession extends BaseSetActiveEditorReadonlyInSession { static readonly ID = 'workbench.action.files.setActiveEditorWriteableInSession'; - static readonly LABEL = nls.localize('setActiveEditorWriteableInSession', "Set Active Editor Writeable in Session"); + static readonly LABEL = nls.localize2('setActiveEditorWriteableInSession', "Set Active Editor Writeable in Session"); constructor() { super( SetActiveEditorWriteableInSession.ID, - { value: SetActiveEditorWriteableInSession.LABEL, original: 'Set Active Editor Writeable in Session' }, + SetActiveEditorWriteableInSession.LABEL, false ); } @@ -1289,12 +1289,12 @@ export class SetActiveEditorWriteableInSession extends BaseSetActiveEditorReadon export class ToggleActiveEditorReadonlyInSession extends BaseSetActiveEditorReadonlyInSession { static readonly ID = 'workbench.action.files.toggleActiveEditorReadonlyInSession'; - static readonly LABEL = nls.localize('toggleActiveEditorReadonlyInSession', "Toggle Active Editor Read-only in Session"); + static readonly LABEL = nls.localize2('toggleActiveEditorReadonlyInSession', "Toggle Active Editor Read-only in Session"); constructor() { super( ToggleActiveEditorReadonlyInSession.ID, - { value: ToggleActiveEditorReadonlyInSession.LABEL, original: 'Toggle Active Editor Readonly in Session' }, + ToggleActiveEditorReadonlyInSession.LABEL, 'toggle' ); } @@ -1303,12 +1303,12 @@ export class ToggleActiveEditorReadonlyInSession extends BaseSetActiveEditorRead export class ResetActiveEditorReadonlyInSession extends BaseSetActiveEditorReadonlyInSession { static readonly ID = 'workbench.action.files.resetActiveEditorReadonlyInSession'; - static readonly LABEL = nls.localize('resetActiveEditorReadonlyInSession', "Reset Active Editor Read-only in Session"); + static readonly LABEL = nls.localize2('resetActiveEditorReadonlyInSession', "Reset Active Editor Read-only in Session"); constructor() { super( ResetActiveEditorReadonlyInSession.ID, - { value: ResetActiveEditorReadonlyInSession.LABEL, original: 'Reset Active Editor Readonly in Session' }, + ResetActiveEditorReadonlyInSession.LABEL, 'reset' ); } diff --git a/src/vs/workbench/contrib/files/browser/fileConstants.ts b/src/vs/workbench/contrib/files/browser/fileConstants.ts index 6736003acd1..c38f9987d22 100644 --- a/src/vs/workbench/contrib/files/browser/fileConstants.ts +++ b/src/vs/workbench/contrib/files/browser/fileConstants.ts @@ -19,14 +19,14 @@ export const COPY_PATH_COMMAND_ID = 'copyFilePath'; export const COPY_RELATIVE_PATH_COMMAND_ID = 'copyRelativeFilePath'; export const SAVE_FILE_AS_COMMAND_ID = 'workbench.action.files.saveAs'; -export const SAVE_FILE_AS_LABEL = nls.localize('saveAs', "Save As..."); +export const SAVE_FILE_AS_LABEL = nls.localize2('saveAs', "Save As..."); export const SAVE_FILE_COMMAND_ID = 'workbench.action.files.save'; -export const SAVE_FILE_LABEL = nls.localize('save', "Save"); +export const SAVE_FILE_LABEL = nls.localize2('save', "Save"); export const SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID = 'workbench.action.files.saveWithoutFormatting'; -export const SAVE_FILE_WITHOUT_FORMATTING_LABEL = nls.localize('saveWithoutFormatting', "Save without Formatting"); +export const SAVE_FILE_WITHOUT_FORMATTING_LABEL = nls.localize2('saveWithoutFormatting', "Save without Formatting"); export const SAVE_ALL_COMMAND_ID = 'saveAll'; -export const SAVE_ALL_LABEL = nls.localize('saveAll', "Save All"); +export const SAVE_ALL_LABEL = nls.localize2('saveAll', "Save All"); export const SAVE_ALL_IN_GROUP_COMMAND_ID = 'workbench.files.action.saveAllInGroup'; @@ -45,5 +45,5 @@ export const NEXT_COMPRESSED_FOLDER = 'nextCompressedFolder'; export const FIRST_COMPRESSED_FOLDER = 'firstCompressedFolder'; export const LAST_COMPRESSED_FOLDER = 'lastCompressedFolder'; export const NEW_UNTITLED_FILE_COMMAND_ID = 'workbench.action.files.newUntitledFile'; -export const NEW_UNTITLED_FILE_LABEL = nls.localize('newUntitledFile', "New Untitled Text File"); +export const NEW_UNTITLED_FILE_LABEL = nls.localize2('newUntitledFile', "New Untitled Text File"); export const NEW_FILE_COMMAND_ID = 'workbench.action.files.newFile'; diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 7115d8fc698..9f21e5014ba 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -835,7 +835,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.files.saveAll', - title: { value: SAVE_ALL_LABEL, original: 'Save All' }, + title: SAVE_ALL_LABEL, f1: true, icon: Codicon.saveAll, menu: { diff --git a/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts b/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts index 92a340e8175..de8140c144b 100644 --- a/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts @@ -24,7 +24,7 @@ import { SideBySideEditor, EditorResourceAccessor } from 'vs/workbench/common/ed import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; const REVEAL_IN_OS_COMMAND_ID = 'revealFileInOS'; -const REVEAL_IN_OS_LABEL = isWindows ? nls.localize('revealInWindows', "Reveal in File Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder"); +const REVEAL_IN_OS_LABEL = isWindows ? nls.localize2('revealInWindows', "Reveal in File Explorer") : isMacintosh ? nls.localize2('revealInMac', "Reveal in Finder") : nls.localize2('openContainer', "Open Containing Folder"); const REVEAL_IN_OS_WHEN_CONTEXT = ContextKeyExpr.or(ResourceContextKey.Scheme.isEqualTo(Schemas.file), ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeUserData)); KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -57,13 +57,13 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, REVEAL_IN_OS_WHEN_CONTEXT, '2_files', 0); +appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL.value, REVEAL_IN_OS_WHEN_CONTEXT, '2_files', 0); // Menu registration - open editors const revealInOsCommand = { id: REVEAL_IN_OS_COMMAND_ID, - title: REVEAL_IN_OS_LABEL + title: REVEAL_IN_OS_LABEL.value }; MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: 'navigation', @@ -92,6 +92,6 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { const category = nls.localize2('filesCategory', "File"); appendToCommandPalette({ id: REVEAL_IN_OS_COMMAND_ID, - title: { value: REVEAL_IN_OS_LABEL, original: isWindows ? 'Reveal in File Explorer' : isMacintosh ? 'Reveal in Finder' : 'Open Containing Folder' }, + title: REVEAL_IN_OS_LABEL, category: category }, REVEAL_IN_OS_WHEN_CONTEXT); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 57e2376c569..85033c64166 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -34,7 +34,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); -export const LOCALIZED_START_INLINE_CHAT_STRING = localize('run', 'Start Inline Chat'); +export const LOCALIZED_START_INLINE_CHAT_STRING = localize2('run', 'Start Inline Chat'); export const START_INLINE_CHAT = registerIcon('start-inline-chat', Codicon.sparkle, localize('startInlineChat', 'Icon which spawns the inline chat from the editor toolbar.')); // some gymnastics to enable hold for speech without moving the StartSessionAction into the electron-layer @@ -52,7 +52,7 @@ export class StartSessionAction extends EditorAction2 { constructor() { super({ id: 'inlineChat.start', - title: { value: LOCALIZED_START_INLINE_CHAT_STRING, original: 'Start Inline Chat' }, + title: LOCALIZED_START_INLINE_CHAT_STRING, category: AbstractInlineChatAction.category, f1: true, precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_PROVIDER, EditorContextKeys.writable), diff --git a/src/vs/workbench/contrib/issue/common/issue.contribution.ts b/src/vs/workbench/contrib/issue/common/issue.contribution.ts index ece002871bb..05518d0f4aa 100644 --- a/src/vs/workbench/contrib/issue/common/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/common/issue.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ICommandAction } from 'vs/platform/action/common/action'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; @@ -97,10 +97,7 @@ export class BaseIssueContribution implements IWorkbenchContribution { const reportIssue: ICommandAction = { id: OpenIssueReporterActionId, - title: { - value: localize({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue..."), - original: 'Report Issue...' - }, + title: localize2({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue..."), category: Categories.Help }; diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts index 3f446e23dba..05bc0632624 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts @@ -43,7 +43,7 @@ class ReportPerformanceIssueUsingReporterAction extends Action2 { constructor() { super({ id: ReportPerformanceIssueUsingReporterAction.ID, - title: { value: localize({ key: 'reportPerformanceIssue', comment: [`Here, 'issue' means problem or bug`] }, "Report Performance Issue..."), original: 'Report Performance Issue' }, + title: localize2({ key: 'reportPerformanceIssue', comment: [`Here, 'issue' means problem or bug`] }, "Report Performance Issue..."), category: Categories.Help, f1: true }); diff --git a/src/vs/workbench/contrib/localHistory/electron-sandbox/localHistoryCommands.ts b/src/vs/workbench/contrib/localHistory/electron-sandbox/localHistoryCommands.ts index 20d20356e6e..76ac171e9af 100644 --- a/src/vs/workbench/contrib/localHistory/electron-sandbox/localHistoryCommands.ts +++ b/src/vs/workbench/contrib/localHistory/electron-sandbox/localHistoryCommands.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { IWorkingCopyHistoryService } from 'vs/workbench/services/workingCopy/common/workingCopyHistory'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; @@ -21,10 +21,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.localHistory.revealInOS', - title: { - value: isWindows ? localize('revealInWindows', "Reveal in File Explorer") : isMacintosh ? localize('revealInMac', "Reveal in Finder") : localize('openContainer', "Open Containing Folder"), - original: isWindows ? 'Reveal in File Explorer' : isMacintosh ? 'Reveal in Finder' : 'Open Containing Folder' - }, + title: isWindows ? localize2('revealInWindows', "Reveal in File Explorer") : isMacintosh ? localize2('revealInMac', "Reveal in Finder") : localize2('openContainer', "Open Containing Folder"), menu: { id: MenuId.TimelineItemContext, group: '4_reveal', diff --git a/src/vs/workbench/contrib/localization/common/localizationsActions.ts b/src/vs/workbench/contrib/localization/common/localizationsActions.ts index 97c275ac765..d9ad34f1c1b 100644 --- a/src/vs/workbench/contrib/localization/common/localizationsActions.ts +++ b/src/vs/workbench/contrib/localization/common/localizationsActions.ts @@ -100,12 +100,12 @@ export class ConfigureDisplayLanguageAction extends Action2 { export class ClearDisplayLanguageAction extends Action2 { public static readonly ID = 'workbench.action.clearLocalePreference'; - public static readonly LABEL = localize('clearDisplayLanguage', "Clear Display Language Preference"); + public static readonly LABEL = localize2('clearDisplayLanguage', "Clear Display Language Preference"); constructor() { super({ id: ClearDisplayLanguageAction.ID, - title: { original: 'Clear Display Language Preference', value: ClearDisplayLanguageAction.LABEL }, + title: ClearDisplayLanguageAction.LABEL, menu: { id: MenuId.CommandPalette } diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 2466217b62c..aa02cce1cb8 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -311,7 +311,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.problems.focus', - title: { value: Messages.MARKERS_PANEL_SHOW_LABEL, original: 'Focus Problems (Errors, Warnings, Infos)' }, + title: Messages.MARKERS_PANEL_SHOW_LABEL, category: Categories.View, f1: true, }); @@ -446,7 +446,7 @@ registerAction2(class extends ViewAction { constructor() { super({ id: Markers.MARKERS_VIEW_SHOW_MULTILINE_MESSAGE, - title: { value: localize('show multiline', "Show message in multiple lines"), original: 'Problems: Show message in multiple lines' }, + title: localize2('show multiline', "Show message in multiple lines"), category: localize('problems', "Problems"), menu: { id: MenuId.CommandPalette, @@ -464,7 +464,7 @@ registerAction2(class extends ViewAction { constructor() { super({ id: Markers.MARKERS_VIEW_SHOW_SINGLELINE_MESSAGE, - title: { value: localize('show singleline', "Show message in single line"), original: 'Problems: Show message in single line' }, + title: localize2('show singleline', "Show message in single line"), category: localize('problems', "Problems"), menu: { id: MenuId.CommandPalette, diff --git a/src/vs/workbench/contrib/markers/browser/messages.ts b/src/vs/workbench/contrib/markers/browser/messages.ts index e6a387f0ce2..29c07828fc6 100644 --- a/src/vs/workbench/contrib/markers/browser/messages.ts +++ b/src/vs/workbench/contrib/markers/browser/messages.ts @@ -12,7 +12,7 @@ import { Marker } from './markersModel'; export default class Messages { public static MARKERS_PANEL_TOGGLE_LABEL: string = nls.localize('problems.view.toggle.label', "Toggle Problems (Errors, Warnings, Infos)"); - public static MARKERS_PANEL_SHOW_LABEL: string = nls.localize('problems.view.focus.label', "Focus Problems (Errors, Warnings, Infos)"); + public static MARKERS_PANEL_SHOW_LABEL = nls.localize2('problems.view.focus.label', "Focus Problems (Errors, Warnings, Infos)"); public static PROBLEMS_PANEL_CONFIGURATION_TITLE: string = nls.localize('problems.panel.configuration.title', "Problems View"); public static PROBLEMS_PANEL_CONFIGURATION_AUTO_REVEAL: string = nls.localize('problems.panel.configuration.autoreveal', "Controls whether Problems view should automatically reveal files when opening them."); diff --git a/src/vs/workbench/contrib/share/browser/share.contribution.ts b/src/vs/workbench/contrib/share/browser/share.contribution.ts index 2eb7e5595d5..ae071141610 100644 --- a/src/vs/workbench/contrib/share/browser/share.contribution.ts +++ b/src/vs/workbench/contrib/share/browser/share.contribution.ts @@ -8,7 +8,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -77,12 +77,12 @@ class ShareWorkbenchContribution { this._disposables.add( registerAction2(class ShareAction extends Action2 { static readonly ID = 'workbench.action.share'; - static readonly LABEL = localize('share', 'Share...'); + static readonly LABEL = localize2('share', 'Share...'); constructor() { super({ id: ShareAction.ID, - title: { value: ShareAction.LABEL, original: 'Share...' }, + title: ShareAction.LABEL, f1: true, icon: Codicon.linkExternal, precondition: ContextKeyExpr.and(ShareProviderCountContext.notEqualsTo(0), WorkspaceFolderCountContext.notEqualsTo(0)), diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 27f92a65ad3..43246667cfe 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -90,7 +90,7 @@ const USE_SLOW_PICKER = 'task.quickOpen.showAll'; export namespace ConfigureTaskAction { export const ID = 'workbench.action.tasks.configureTaskRunner'; - export const TEXT = nls.localize('ConfigureTaskRunnerAction.label', "Configure Task"); + export const TEXT = nls.localize2('ConfigureTaskRunnerAction.label', "Configure Task"); } export type TaskQuickPickEntryType = (IQuickPickItem & { task: Task }) | (IQuickPickItem & { folder: IWorkspaceFolder }) | (IQuickPickItem & { settingType: string }); @@ -2563,7 +2563,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const thisCapture: AbstractTaskService = this; return new class extends Action { constructor() { - super(ConfigureTaskAction.ID, ConfigureTaskAction.TEXT, undefined, true, () => { thisCapture._runConfigureTasks(); return Promise.resolve(undefined); }); + super(ConfigureTaskAction.ID, ConfigureTaskAction.TEXT.value, undefined, true, () => { thisCapture._runConfigureTasks(); return Promise.resolve(undefined); }); } }; } @@ -2576,7 +2576,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const needsTerminate = buildError.code === TaskErrors.RunningTask; if (needsConfig || needsTerminate) { this._notificationService.prompt(buildError.severity, buildError.message, [{ - label: needsConfig ? ConfigureTaskAction.TEXT : nls.localize('TerminateAction.label', "Terminate Task"), + label: needsConfig ? ConfigureTaskAction.TEXT.value : nls.localize('TerminateAction.label', "Terminate Task"), run: () => { if (needsConfig) { this._runConfigureTasks(); diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index d92f58923b0..a0d2bb523f6 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -246,7 +246,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: ConfigureTaskAction.ID, - title: { value: ConfigureTaskAction.TEXT, original: 'Configure Task' }, + title: ConfigureTaskAction.TEXT, category: TASKS_CATEGORY }, when: TaskExecutionSupportedContext diff --git a/src/vs/workbench/contrib/update/browser/update.contribution.ts b/src/vs/workbench/contrib/update/browser/update.contribution.ts index 46b1d76365a..1fc44c7d8f6 100644 --- a/src/vs/workbench/contrib/update/browser/update.contribution.ts +++ b/src/vs/workbench/contrib/update/browser/update.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/platform/update/common/update.config.contribution'; -import { localize, localize2 } from 'vs/nls'; +import { localize, localize2, unlocalized2 } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; @@ -41,7 +41,7 @@ export class ShowCurrentReleaseNotesAction extends Action2 { ...localize2('showReleaseNotes', "Show Release Notes"), mnemonicTitle: localize({ key: 'mshowReleaseNotes', comment: ['&& denotes a mnemonic'] }, "Show &&Release Notes"), }, - category: { value: product.nameShort, original: product.nameShort }, + category: unlocalized2(product.nameShort), f1: true, precondition: RELEASE_NOTES_URL, menu: [{ @@ -80,7 +80,7 @@ export class CheckForUpdateAction extends Action2 { super({ id: 'update.checkForUpdate', title: localize2('checkForUpdates', 'Check for Updates...'), - category: { value: product.nameShort, original: product.nameShort }, + category: unlocalized2(product.nameShort), f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Idle), }); @@ -97,7 +97,7 @@ class DownloadUpdateAction extends Action2 { super({ id: 'update.downloadUpdate', title: localize2('downloadUpdate', 'Download Update'), - category: { value: product.nameShort, original: product.nameShort }, + category: unlocalized2(product.nameShort), f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.AvailableForDownload) }); @@ -113,7 +113,7 @@ class InstallUpdateAction extends Action2 { super({ id: 'update.installUpdate', title: localize2('installUpdate', 'Install Update'), - category: { value: product.nameShort, original: product.nameShort }, + category: unlocalized2(product.nameShort), f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloaded) }); @@ -129,7 +129,7 @@ class RestartToUpdateAction extends Action2 { super({ id: 'update.restartToUpdate', title: localize2('restartToUpdate', 'Restart to Update'), - category: { value: product.nameShort, original: product.nameShort }, + category: unlocalized2(product.nameShort), f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready) }); diff --git a/src/vs/workbench/contrib/url/browser/trustedDomains.ts b/src/vs/workbench/contrib/url/browser/trustedDomains.ts index 2019b04e521..82c9559dad4 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomains.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomains.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; @@ -25,7 +25,7 @@ export const TRUSTED_DOMAINS_CONTENT_STORAGE_KEY = 'http.linkProtectionTrustedDo export const manageTrustedDomainSettingsCommand = { id: 'workbench.action.manageTrustedDomain', description: { - description: localize('trustedDomain.manageTrustedDomain', 'Manage Trusted Domains'), + description: localize2('trustedDomain.manageTrustedDomain', 'Manage Trusted Domains'), args: [] }, handler: async (accessor: ServicesAccessor) => { diff --git a/src/vs/workbench/contrib/url/browser/url.contribution.ts b/src/vs/workbench/contrib/url/browser/url.contribution.ts index d0484bd566c..92e2b6b9611 100644 --- a/src/vs/workbench/contrib/url/browser/url.contribution.ts +++ b/src/vs/workbench/contrib/url/browser/url.contribution.ts @@ -55,10 +55,7 @@ CommandsRegistry.registerCommand(manageTrustedDomainSettingsCommand); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: manageTrustedDomainSettingsCommand.id, - title: { - value: manageTrustedDomainSettingsCommand.description.description, - original: 'Manage Trusted Domains' - } + title: manageTrustedDomainSettingsCommand.description.description } }); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 5a716a0880e..0787368edba 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -835,7 +835,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private getShowConflictsTitle(): ILocalizedString { - return { value: localize('resolveConflicts_global', "Show Conflicts ({0})", this.getConflictsCount()), original: `Show Conflicts (${this.getConflictsCount()})` }; + return localize2('resolveConflicts_global', "Show Conflicts ({0})", this.getConflictsCount()); } private conflictsActionDisposable = this._register(new MutableDisposable()); diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts index f973ce7ef5b..356a852efdb 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts @@ -104,12 +104,12 @@ export class WebViewEditorFindPreviousCommand extends Action2 { export class ReloadWebviewAction extends Action2 { static readonly ID = 'workbench.action.webview.reloadWebviewAction'; - static readonly LABEL = nls.localize('refreshWebviewLabel', "Reload Webviews"); + static readonly LABEL = nls.localize2('refreshWebviewLabel', "Reload Webviews"); public constructor() { super({ id: ReloadWebviewAction.ID, - title: { value: ReloadWebviewAction.LABEL, original: 'Reload Webviews' }, + title: ReloadWebviewAction.LABEL, category: Categories.Developer, menu: [{ id: MenuId.CommandPalette diff --git a/src/vs/workbench/electron-sandbox/actions/installActions.ts b/src/vs/workbench/electron-sandbox/actions/installActions.ts index 952883aec23..d9332bb4855 100644 --- a/src/vs/workbench/electron-sandbox/actions/installActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/installActions.ts @@ -21,10 +21,7 @@ export class InstallShellScriptAction extends Action2 { constructor() { super({ id: 'workbench.action.installCommandLine', - title: { - value: localize('install', "Install '{0}' command in PATH", product.applicationName), - original: `Install \'${product.applicationName}\' command in PATH` - }, + title: localize2('install', "Install '{0}' command in PATH", product.applicationName), category: shellCommandCategory, f1: true }); @@ -54,10 +51,7 @@ export class UninstallShellScriptAction extends Action2 { constructor() { super({ id: 'workbench.action.uninstallCommandLine', - title: { - value: localize('uninstall', "Uninstall '{0}' command from PATH", product.applicationName), - original: `Uninstall \'${product.applicationName}\' command from PATH` - }, + title: localize2('uninstall', "Uninstall '{0}' command from PATH", product.applicationName), category: shellCommandCategory, f1: true }); diff --git a/src/vs/workbench/services/views/browser/viewsService.ts b/src/vs/workbench/services/views/browser/viewsService.ts index 7f036faf807..49643efd588 100644 --- a/src/vs/workbench/services/views/browser/viewsService.ts +++ b/src/vs/workbench/services/views/browser/viewsService.ts @@ -501,10 +501,10 @@ export class ViewsService extends Disposable implements IViewsService { private registerFocusViewAction(viewDescriptor: IViewDescriptor, category?: string | ILocalizedString): IDisposable { return registerAction2(class FocusViewAction extends Action2 { constructor() { - const title = localize({ key: 'focus view', comment: ['{0} indicates the name of the view to be focused.'] }, "Focus on {0} View", viewDescriptor.name.value); + const title = localize2({ key: 'focus view', comment: ['{0} indicates the name of the view to be focused.'] }, "Focus on {0} View", viewDescriptor.name.value); super({ id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`, - title: { original: `Focus on ${viewDescriptor.name.original} View`, value: title }, + title, category, menu: [{ id: MenuId.CommandPalette, @@ -520,7 +520,7 @@ export class ViewsService extends Disposable implements IViewsService { win: viewDescriptor.focusCommand?.keybindings?.win }, metadata: { - description: title, + description: title.value, args: [ { name: 'focusOptions', diff --git a/src/vs/workbench/services/views/common/viewContainerModel.ts b/src/vs/workbench/services/views/common/viewContainerModel.ts index d7c9f7149ab..60009d0ff79 100644 --- a/src/vs/workbench/services/views/common/viewContainerModel.ts +++ b/src/vs/workbench/services/views/common/viewContainerModel.ts @@ -21,12 +21,13 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IOutputService } from 'vs/workbench/services/output/common/output'; import { CounterSet } from 'vs/base/common/map'; +import { localize2 } from 'vs/nls'; registerAction2(class extends Action2 { constructor() { super({ id: '_workbench.output.showViewsLog', - title: { value: 'Show Views Log', original: 'Show Views Log' }, + title: localize2('showViewsLog', "Show Views Log"), category: Categories.Developer, f1: true }); From 9794c5e919eb3a44c8159a72d6db90bf1bee9c38 Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 2 Feb 2024 13:11:04 +0900 Subject: [PATCH 0856/1897] chore: bump node-pty@1.1.0-beta6 (#204058) --- package.json | 2 +- remote/package.json | 2 +- remote/yarn.lock | 15 ++++++++++----- yarn.lock | 15 ++++++++++----- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index f977e0d80a0..58b2fe6ea18 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "native-is-elevated": "0.7.0", "native-keymap": "^3.3.4", "native-watchdog": "^1.4.1", - "node-pty": "1.1.0-beta5", + "node-pty": "1.1.0-beta6", "tas-client-umd": "0.1.8", "v8-inspect-profiler": "^0.1.0", "vscode-oniguruma": "1.7.0", diff --git a/remote/package.json b/remote/package.json index 82d2d26b77b..955b8ceb43a 100644 --- a/remote/package.json +++ b/remote/package.json @@ -29,7 +29,7 @@ "kerberos": "^2.0.1", "minimist": "^1.2.6", "native-watchdog": "^1.4.1", - "node-pty": "1.1.0-beta5", + "node-pty": "1.1.0-beta6", "tas-client-umd": "0.1.8", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", diff --git a/remote/yarn.lock b/remote/yarn.lock index a1017c13e6b..75c61909b84 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -426,17 +426,22 @@ node-addon-api@^4.3.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== +node-addon-api@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.0.tgz#71f609369379c08e251c558527a107107b5e0fdb" + integrity sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g== + node-gyp-build@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== -node-pty@1.1.0-beta5: - version "1.1.0-beta5" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta5.tgz#364386b7058a93070234064f13164ec1ef914993" - integrity sha512-j3QdgFHnLY0JWxztrvM3g67RaQLOGvytv+C6mFu0PqD+JILlzqfwuoyqRqVxdZZjoOTUXPfSRj1qPVCaCH+eOw== +node-pty@1.1.0-beta6: + version "1.1.0-beta6" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta6.tgz#8b27ce40268e313868925e1b46f2af98cc677881" + integrity sha512-ZcuPz5wIbfF4rebVv8sl+nf2Cn5dVMqlEl9PtabCt4uIffGDnovOpmwh16Oh/MThrwSmeJL6gBwu6lIbBtW7DQ== dependencies: - nan "^2.17.0" + node-addon-api "^7.1.0" once@^1.3.1, once@^1.4.0: version "1.4.0" diff --git a/yarn.lock b/yarn.lock index 2bb41b4fc24..a09d82d1052 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6827,6 +6827,11 @@ node-addon-api@^6.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.0.0.tgz#cfb3574e6df708ff71a30db6c4762d9e06e11c27" integrity sha512-GyHvgPvUXBvAkXa0YvYnhilSB1A+FRYMpIVggKzPZqdaZfevZOuzfWzyvgzOwRLHBeo/MMswmJFsrNF4Nw1pmA== +node-addon-api@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.0.tgz#71f609369379c08e251c558527a107107b5e0fdb" + integrity sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g== + node-fetch@2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.8.tgz#a68d30b162bc1d8fd71a367e81b997e1f4d4937e" @@ -6846,12 +6851,12 @@ node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== -node-pty@1.1.0-beta5: - version "1.1.0-beta5" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta5.tgz#364386b7058a93070234064f13164ec1ef914993" - integrity sha512-j3QdgFHnLY0JWxztrvM3g67RaQLOGvytv+C6mFu0PqD+JILlzqfwuoyqRqVxdZZjoOTUXPfSRj1qPVCaCH+eOw== +node-pty@1.1.0-beta6: + version "1.1.0-beta6" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta6.tgz#8b27ce40268e313868925e1b46f2af98cc677881" + integrity sha512-ZcuPz5wIbfF4rebVv8sl+nf2Cn5dVMqlEl9PtabCt4uIffGDnovOpmwh16Oh/MThrwSmeJL6gBwu6lIbBtW7DQ== dependencies: - nan "^2.17.0" + node-addon-api "^7.1.0" node-releases@^1.1.71: version "1.1.72" From 9f705f937640b1960f7da60987ccf9394e1b9b8b Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 2 Feb 2024 16:25:12 +0900 Subject: [PATCH 0857/1897] chore: bump spdlog@0.15.0 (#204072) --- package.json | 2 +- remote/package.json | 2 +- remote/yarn.lock | 20 ++++++++++---------- yarn.lock | 20 ++++++++++---------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 58b2fe6ea18..dfad0e69518 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@vscode/policy-watcher": "^1.1.4", "@vscode/proxy-agent": "^0.19.0", "@vscode/ripgrep": "^1.15.9", - "@vscode/spdlog": "^0.14.0", + "@vscode/spdlog": "^0.15.0", "@vscode/sqlite3": "5.1.6-vscode", "@vscode/sudo-prompt": "9.3.1", "@vscode/vscode-languagedetection": "1.0.21", diff --git a/remote/package.json b/remote/package.json index 955b8ceb43a..bbdea84afdb 100644 --- a/remote/package.json +++ b/remote/package.json @@ -9,7 +9,7 @@ "@vscode/iconv-lite-umd": "0.7.0", "@vscode/proxy-agent": "^0.19.0", "@vscode/ripgrep": "^1.15.9", - "@vscode/spdlog": "^0.14.0", + "@vscode/spdlog": "^0.15.0", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.5.0", "@vscode/windows-registry": "^1.1.0", diff --git a/remote/yarn.lock b/remote/yarn.lock index 75c61909b84..62b37bc15c2 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -81,14 +81,14 @@ proxy-from-env "^1.1.0" yauzl "^2.9.2" -"@vscode/spdlog@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@vscode/spdlog/-/spdlog-0.14.0.tgz#3cdf589ca59b9ce792ef58b5f773e29a732a360f" - integrity sha512-mpblZa3v6AGEJC1qTwIkpgTc6NItdiiuTxufGxr6XD14srXCvVAUXFIYNqszxC6RY57qDQMX1d9Wd4/oZDxuUQ== +"@vscode/spdlog@^0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@vscode/spdlog/-/spdlog-0.15.0.tgz#37896541410ff036dc01c54e16bf131c522a057e" + integrity sha512-5UFcQXM/G6bTRF49zJJJH3A3+47nxaXuKzT26vhTXVIiMFoV1oI9559mWOzapLEmvrntAdYtjE7Jh74lSAuMcA== dependencies: bindings "^1.5.0" mkdirp "^1.0.4" - nan "^2.17.0" + node-addon-api "7.1.0" "@vscode/vscode-languagedetection@1.0.21": version "1.0.21" @@ -411,6 +411,11 @@ node-abi@^3.3.0: dependencies: semver "^7.3.5" +node-addon-api@7.1.0, node-addon-api@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.0.tgz#71f609369379c08e251c558527a107107b5e0fdb" + integrity sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g== + node-addon-api@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239" @@ -426,11 +431,6 @@ node-addon-api@^4.3.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== -node-addon-api@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.0.tgz#71f609369379c08e251c558527a107107b5e0fdb" - integrity sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g== - node-gyp-build@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" diff --git a/yarn.lock b/yarn.lock index a09d82d1052..90051e98b10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1409,14 +1409,14 @@ proxy-from-env "^1.1.0" yauzl "^2.9.2" -"@vscode/spdlog@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@vscode/spdlog/-/spdlog-0.14.0.tgz#3cdf589ca59b9ce792ef58b5f773e29a732a360f" - integrity sha512-mpblZa3v6AGEJC1qTwIkpgTc6NItdiiuTxufGxr6XD14srXCvVAUXFIYNqszxC6RY57qDQMX1d9Wd4/oZDxuUQ== +"@vscode/spdlog@^0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@vscode/spdlog/-/spdlog-0.15.0.tgz#37896541410ff036dc01c54e16bf131c522a057e" + integrity sha512-5UFcQXM/G6bTRF49zJJJH3A3+47nxaXuKzT26vhTXVIiMFoV1oI9559mWOzapLEmvrntAdYtjE7Jh74lSAuMcA== dependencies: bindings "^1.5.0" mkdirp "^1.0.4" - nan "^2.17.0" + node-addon-api "7.1.0" "@vscode/sqlite3@5.1.6-vscode": version "5.1.6-vscode" @@ -6802,6 +6802,11 @@ node-abi@^3.3.0: dependencies: semver "^7.3.5" +node-addon-api@7.1.0, node-addon-api@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.0.tgz#71f609369379c08e251c558527a107107b5e0fdb" + integrity sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g== + node-addon-api@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239" @@ -6827,11 +6832,6 @@ node-addon-api@^6.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.0.0.tgz#cfb3574e6df708ff71a30db6c4762d9e06e11c27" integrity sha512-GyHvgPvUXBvAkXa0YvYnhilSB1A+FRYMpIVggKzPZqdaZfevZOuzfWzyvgzOwRLHBeo/MMswmJFsrNF4Nw1pmA== -node-addon-api@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.0.tgz#71f609369379c08e251c558527a107107b5e0fdb" - integrity sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g== - node-fetch@2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.8.tgz#a68d30b162bc1d8fd71a367e81b997e1f4d4937e" From 56ed836ea9bae1602cf83bc286c7480b1370a99f Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 2 Feb 2024 17:23:51 +0900 Subject: [PATCH 0858/1897] chore: bump windows-process-tree@0.6.0 (#204079) --- package.json | 2 +- remote/package.json | 2 +- remote/yarn.lock | 15 +++++---------- yarn.lock | 10 +++++----- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index dfad0e69518..eefc68ea019 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "@vscode/sudo-prompt": "9.3.1", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-mutex": "^0.4.4", - "@vscode/windows-process-tree": "^0.5.0", + "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", "@xterm/addon-canvas": "0.6.0-beta.30", "@xterm/addon-image": "0.7.0-beta.28", diff --git a/remote/package.json b/remote/package.json index bbdea84afdb..d2b3fb7fb8b 100644 --- a/remote/package.json +++ b/remote/package.json @@ -11,7 +11,7 @@ "@vscode/ripgrep": "^1.15.9", "@vscode/spdlog": "^0.15.0", "@vscode/vscode-languagedetection": "1.0.21", - "@vscode/windows-process-tree": "^0.5.0", + "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", "@xterm/addon-canvas": "0.6.0-beta.30", "@xterm/addon-image": "0.7.0-beta.28", diff --git a/remote/yarn.lock b/remote/yarn.lock index 62b37bc15c2..f901b1a2c07 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -102,12 +102,12 @@ dependencies: node-addon-api "^3.0.2" -"@vscode/windows-process-tree@^0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@vscode/windows-process-tree/-/windows-process-tree-0.5.0.tgz#b8205b862c75a1e0ad8b7bf4350dc85036ee3a2c" - integrity sha512-y8Oliel/rBSYh9f1T4F0zQjJNPeJRgYRhEKZsjas7JXKLf46FpE3Ux8e9+7HelUD8dXFH7C7N6895nU0WhrMlg== +"@vscode/windows-process-tree@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@vscode/windows-process-tree/-/windows-process-tree-0.6.0.tgz#a62400c689b27688fd689e8cac71d63c6d1956da" + integrity sha512-7/DjBKKUtlmKNiAet2GRbdvfjgMKmfBeWVClIgONv8aqxGnaKca5N85eIDxh6rLMy2hKvFqIIsqgxs1Q26TWwg== dependencies: - nan "^2.17.0" + node-addon-api "7.1.0" "@vscode/windows-registry@^1.1.0": version "1.1.0" @@ -389,11 +389,6 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -nan@^2.17.0: - version "2.17.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" - integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== - napi-build-utils@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" diff --git a/yarn.lock b/yarn.lock index 90051e98b10..e3dca0e6830 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1520,12 +1520,12 @@ bindings "^1.2.1" nan "^2.17.0" -"@vscode/windows-process-tree@^0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@vscode/windows-process-tree/-/windows-process-tree-0.5.0.tgz#b8205b862c75a1e0ad8b7bf4350dc85036ee3a2c" - integrity sha512-y8Oliel/rBSYh9f1T4F0zQjJNPeJRgYRhEKZsjas7JXKLf46FpE3Ux8e9+7HelUD8dXFH7C7N6895nU0WhrMlg== +"@vscode/windows-process-tree@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@vscode/windows-process-tree/-/windows-process-tree-0.6.0.tgz#a62400c689b27688fd689e8cac71d63c6d1956da" + integrity sha512-7/DjBKKUtlmKNiAet2GRbdvfjgMKmfBeWVClIgONv8aqxGnaKca5N85eIDxh6rLMy2hKvFqIIsqgxs1Q26TWwg== dependencies: - nan "^2.17.0" + node-addon-api "7.1.0" "@vscode/windows-registry@^1.1.0": version "1.1.0" From 8fc7b1d9c0af41304dbf9900898be9bdf094f38f Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 2 Feb 2024 09:50:50 +0100 Subject: [PATCH 0859/1897] extracting length of text instead --- src/vs/editor/contrib/inlineCompletions/browser/utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts index 5cc6aeee97c..6dc56e0b082 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts @@ -124,17 +124,17 @@ export function getNewRanges(edits: ISingleEditOperation[]): Range[] { for (const edit of edits) { const text = edit.text ?? ''; + const textLength = lengthOfText(text); const rangeStart = Position.lift({ lineNumber: edit.range.startLineNumber + lineOffset, column: edit.range.startColumn + (edit.range.startLineNumber === previousEditEndLineNumber ? columnOffset : 0) }); const rangeEnd = addPositions( rangeStart, - lengthOfText(text) + textLength ); ranges.push(Range.fromPositions(rangeStart, rangeEnd)); - const splitText = splitLines(text); - lineOffset += splitText.length - edit.range.endLineNumber + edit.range.startLineNumber - 1; + lineOffset += textLength.lineNumber - edit.range.endLineNumber + edit.range.startLineNumber - 1; columnOffset = rangeEnd.column - edit.range.endColumn; previousEditEndLineNumber = edit.range.endLineNumber; } From 19e6fdeefce5955684b0b3b00aaaf1a0fe87bb80 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 2 Feb 2024 09:54:54 +0100 Subject: [PATCH 0860/1897] removing usless import --- src/vs/editor/contrib/inlineCompletions/browser/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts index 6dc56e0b082..a35bae19507 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts @@ -6,7 +6,6 @@ import { BugIndicatingError } from 'vs/base/common/errors'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IObservable, autorunOpts } from 'vs/base/common/observable'; -import { splitLines } from 'vs/base/common/strings'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; From 5baa693de861851c02b9811d273d51c664e1885d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 2 Feb 2024 09:59:59 +0100 Subject: [PATCH 0861/1897] Revert "Adopt localize2 in more spots" (#204087) Revert "Adopt localize2 in more spots (#204030)" This reverts commit 7923151da4b3567636c1346df9e8ba682744bbf3. --- .../browser/link/goToDefinitionAtPosition.ts | 2 +- src/vs/nls.ts | 11 +---- .../browser/actions/chatQuickInputActions.ts | 2 +- .../test/browser/commentsView.test.ts | 3 +- .../debug/browser/debug.contribution.ts | 3 +- .../browser/editSessions.contribution.ts | 8 ++-- .../extensions/browser/extensionsViewlet.ts | 5 ++- .../editors/textFileSaveErrorHandler.ts | 2 +- .../files/browser/fileActions.contribution.ts | 12 ++--- .../contrib/files/browser/fileActions.ts | 44 +++++++++---------- .../contrib/files/browser/fileConstants.ts | 10 ++--- .../files/browser/views/openEditorsView.ts | 2 +- .../fileActions.contribution.ts | 8 ++-- .../inlineChat/browser/inlineChatActions.ts | 4 +- .../issue/common/issue.contribution.ts | 7 ++- .../electron-sandbox/issue.contribution.ts | 2 +- .../electron-sandbox/localHistoryCommands.ts | 7 ++- .../common/localizationsActions.ts | 4 +- .../markers/browser/markers.contribution.ts | 6 +-- .../contrib/markers/browser/messages.ts | 2 +- .../share/browser/share.contribution.ts | 6 +-- .../tasks/browser/abstractTaskService.ts | 6 +-- .../tasks/browser/task.contribution.ts | 2 +- .../update/browser/update.contribution.ts | 12 ++--- .../contrib/url/browser/trustedDomains.ts | 4 +- .../contrib/url/browser/url.contribution.ts | 5 ++- .../userDataSync/browser/userDataSync.ts | 2 +- .../webviewPanel/browser/webviewCommands.ts | 4 +- .../actions/installActions.ts | 10 ++++- .../services/views/browser/viewsService.ts | 6 +-- .../views/common/viewContainerModel.ts | 3 +- 31 files changed, 106 insertions(+), 98 deletions(-) diff --git a/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts b/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts index 8a16c7f7a9f..f5a08f94c1e 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts @@ -304,7 +304,7 @@ export class GotoDefinitionAtPositionEditorContribution implements IEditorContri this.editor.setPosition(position); return this.editor.invokeWithinContext((accessor) => { const canPeek = !openToSide && this.editor.getOption(EditorOption.definitionLinkOpensInPeek) && !this.isInPeekEditor(accessor); - const action = new DefinitionAction({ openToSide, openInPeek: canPeek, muteMessage: true }, { title: nls.unlocalized2(''), id: '', precondition: undefined }); + const action = new DefinitionAction({ openToSide, openInPeek: canPeek, muteMessage: true }, { title: { value: '', original: '' }, id: '', precondition: undefined }); return action.run(accessor); }); } diff --git a/src/vs/nls.ts b/src/vs/nls.ts index 47a5dcdf6d8..233840e65ab 100644 --- a/src/vs/nls.ts +++ b/src/vs/nls.ts @@ -202,16 +202,7 @@ export function localize2(data: ILocalizeInfo | string, message: string, ...args return { value: original, original - } as ILocalizedString; -} - -/** - * Create a `ILocalizedString` object that represents an unlocalized string. - * - * This lets you explicitly pass in unlocalized strings to places that normally expect localized strings. - */ -export function unlocalized2(str: string): ILocalizedString { - return { value: str, original: str } as ILocalizedString; + }; } /** diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts index 4df9285764e..9381aa94208 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts @@ -171,7 +171,7 @@ export function getQuickChatActionForProvider(id: string, label: string) { super({ id: `workbench.action.openQuickChat.${id}`, category: CHAT_CATEGORY, - title: localize2('interactiveSession.open', "Open Quick Chat ({0})", label), + title: { value: localize('interactiveSession.open', "Open Quick Chat ({0})", label), original: `Open Quick Chat (${label})` }, f1: true }); } diff --git a/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts b/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts index e6e274ed91d..de511db52f3 100644 --- a/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts +++ b/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts @@ -19,7 +19,6 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CancellationToken } from 'vs/base/common/cancellation'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { unlocalized2 } from 'vs/nls'; class TestCommentThread implements CommentThread { isDocumentCommentThread(): this is CommentThread { @@ -83,7 +82,7 @@ export class TestViewDescriptorService implements Partial { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index ed1ea24a08a..8fcb0937fd9 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -215,13 +215,13 @@ appendToCommandPalette({ appendToCommandPalette({ id: SAVE_FILE_COMMAND_ID, - title: SAVE_FILE_LABEL, + title: { value: SAVE_FILE_LABEL, original: 'Save' }, category: Categories.File }); appendToCommandPalette({ id: SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, - title: SAVE_FILE_WITHOUT_FORMATTING_LABEL, + title: { value: SAVE_FILE_WITHOUT_FORMATTING_LABEL, original: 'Save without Formatting' }, category: Categories.File }); @@ -251,26 +251,26 @@ appendToCommandPalette({ appendToCommandPalette({ id: SAVE_FILE_AS_COMMAND_ID, - title: SAVE_FILE_AS_LABEL, + title: { value: SAVE_FILE_AS_LABEL, original: 'Save As...' }, category: Categories.File }); appendToCommandPalette({ id: NEW_FILE_COMMAND_ID, - title: NEW_FILE_LABEL, + title: { value: NEW_FILE_LABEL, original: 'New File' }, category: Categories.File }, WorkspaceFolderCountContext.notEqualsTo('0')); appendToCommandPalette({ id: NEW_FOLDER_COMMAND_ID, - title: NEW_FOLDER_LABEL, + title: { value: NEW_FOLDER_LABEL, original: 'New Folder' }, category: Categories.File, metadata: { description: nls.localize2('newFolderDescription', "Create a new folder or directory") } }, WorkspaceFolderCountContext.notEqualsTo('0')); appendToCommandPalette({ id: NEW_UNTITLED_FILE_COMMAND_ID, - title: NEW_UNTITLED_FILE_LABEL, + title: { value: NEW_UNTITLED_FILE_LABEL, original: 'New Untitled Text File' }, category: Categories.File }); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index cb64ae02fb4..2f3dc318c25 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -61,9 +61,9 @@ import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { ILocalizedString } from 'vs/platform/action/common/action'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; -export const NEW_FILE_LABEL = nls.localize2('newFile', "New File..."); +export const NEW_FILE_LABEL = nls.localize('newFile', "New File..."); export const NEW_FOLDER_COMMAND_ID = 'explorer.newFolder'; -export const NEW_FOLDER_LABEL = nls.localize2('newFolder', "New Folder..."); +export const NEW_FOLDER_LABEL = nls.localize('newFolder', "New Folder..."); export const TRIGGER_RENAME_LABEL = nls.localize('rename', "Rename..."); export const MOVE_FILE_TO_TRASH_LABEL = nls.localize('delete', "Delete"); export const COPY_FILE_LABEL = nls.localize('copyFile', "Copy"); @@ -481,12 +481,12 @@ async function askForOverwrite(fileService: IFileService, dialogService: IDialog export class GlobalCompareResourcesAction extends Action2 { static readonly ID = 'workbench.files.action.compareFileWith'; - static readonly LABEL = nls.localize2('globalCompareFile', "Compare Active File With..."); + static readonly LABEL = nls.localize('globalCompareFile', "Compare Active File With..."); constructor() { super({ id: GlobalCompareResourcesAction.ID, - title: GlobalCompareResourcesAction.LABEL, + title: { value: GlobalCompareResourcesAction.LABEL, original: 'Compare Active File With...' }, f1: true, category: Categories.File, precondition: ActiveEditorContext @@ -609,12 +609,12 @@ export class CloseGroupAction extends Action { export class FocusFilesExplorer extends Action2 { static readonly ID = 'workbench.files.action.focusFilesExplorer'; - static readonly LABEL = nls.localize2('focusFilesExplorer', "Focus on Files Explorer"); + static readonly LABEL = nls.localize('focusFilesExplorer', "Focus on Files Explorer"); constructor() { super({ id: FocusFilesExplorer.ID, - title: FocusFilesExplorer.LABEL, + title: { value: FocusFilesExplorer.LABEL, original: 'Focus on Files Explorer' }, f1: true, category: Categories.File }); @@ -629,12 +629,12 @@ export class FocusFilesExplorer extends Action2 { export class ShowActiveFileInExplorer extends Action2 { static readonly ID = 'workbench.files.action.showActiveFileInExplorer'; - static readonly LABEL = nls.localize2('showInExplorer', "Reveal Active File in Explorer View"); + static readonly LABEL = nls.localize('showInExplorer', "Reveal Active File in Explorer View"); constructor() { super({ id: ShowActiveFileInExplorer.ID, - title: ShowActiveFileInExplorer.LABEL, + title: { value: ShowActiveFileInExplorer.LABEL, original: 'Reveal Active File in Explorer View' }, f1: true, category: Categories.File }); @@ -653,13 +653,13 @@ export class ShowActiveFileInExplorer extends Action2 { export class OpenActiveFileInEmptyWorkspace extends Action2 { static readonly ID = 'workbench.action.files.showOpenedFileInNewWindow'; - static readonly LABEL = nls.localize2('openFileInEmptyWorkspace', "Open Active File in New Empty Workspace"); + static readonly LABEL = nls.localize('openFileInEmptyWorkspace', "Open Active File in New Empty Workspace"); constructor( ) { super({ id: OpenActiveFileInEmptyWorkspace.ID, - title: OpenActiveFileInEmptyWorkspace.LABEL, + title: { value: OpenActiveFileInEmptyWorkspace.LABEL, original: 'Open Active File in New Empty Workspace' }, f1: true, category: Categories.File, precondition: EmptyWorkspaceSupportContext @@ -763,12 +763,12 @@ function getWellFormedFileName(filename: string): string { export class CompareNewUntitledTextFilesAction extends Action2 { static readonly ID = 'workbench.files.action.compareNewUntitledTextFiles'; - static readonly LABEL = nls.localize2('compareNewUntitledTextFiles', "Compare New Untitled Text Files"); + static readonly LABEL = nls.localize('compareNewUntitledTextFiles', "Compare New Untitled Text Files"); constructor() { super({ id: CompareNewUntitledTextFilesAction.ID, - title: CompareNewUntitledTextFilesAction.LABEL, + title: { value: CompareNewUntitledTextFilesAction.LABEL, original: 'Compare New Untitled Text Files' }, f1: true, category: Categories.File }); @@ -788,7 +788,7 @@ export class CompareNewUntitledTextFilesAction extends Action2 { export class CompareWithClipboardAction extends Action2 { static readonly ID = 'workbench.files.action.compareWithClipboard'; - static readonly LABEL = nls.localize2('compareWithClipboard', "Compare Active File with Clipboard"); + static readonly LABEL = nls.localize('compareWithClipboard', "Compare Active File with Clipboard"); private registrationDisposal: IDisposable | undefined; private static SCHEME_COUNTER = 0; @@ -796,7 +796,7 @@ export class CompareWithClipboardAction extends Action2 { constructor() { super({ id: CompareWithClipboardAction.ID, - title: CompareWithClipboardAction.LABEL, + title: { value: CompareWithClipboardAction.LABEL, original: 'Compare Active File with Clipboard' }, f1: true, category: Categories.File, keybinding: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyC), weight: KeybindingWeight.WorkbenchContrib } @@ -1261,12 +1261,12 @@ class BaseSetActiveEditorReadonlyInSession extends Action2 { export class SetActiveEditorReadonlyInSession extends BaseSetActiveEditorReadonlyInSession { static readonly ID = 'workbench.action.files.setActiveEditorReadonlyInSession'; - static readonly LABEL = nls.localize2('setActiveEditorReadonlyInSession', "Set Active Editor Read-only in Session"); + static readonly LABEL = nls.localize('setActiveEditorReadonlyInSession', "Set Active Editor Read-only in Session"); constructor() { super( SetActiveEditorReadonlyInSession.ID, - SetActiveEditorReadonlyInSession.LABEL, + { value: SetActiveEditorReadonlyInSession.LABEL, original: 'Set Active Editor Readonly in Session' }, true ); } @@ -1275,12 +1275,12 @@ export class SetActiveEditorReadonlyInSession extends BaseSetActiveEditorReadonl export class SetActiveEditorWriteableInSession extends BaseSetActiveEditorReadonlyInSession { static readonly ID = 'workbench.action.files.setActiveEditorWriteableInSession'; - static readonly LABEL = nls.localize2('setActiveEditorWriteableInSession', "Set Active Editor Writeable in Session"); + static readonly LABEL = nls.localize('setActiveEditorWriteableInSession', "Set Active Editor Writeable in Session"); constructor() { super( SetActiveEditorWriteableInSession.ID, - SetActiveEditorWriteableInSession.LABEL, + { value: SetActiveEditorWriteableInSession.LABEL, original: 'Set Active Editor Writeable in Session' }, false ); } @@ -1289,12 +1289,12 @@ export class SetActiveEditorWriteableInSession extends BaseSetActiveEditorReadon export class ToggleActiveEditorReadonlyInSession extends BaseSetActiveEditorReadonlyInSession { static readonly ID = 'workbench.action.files.toggleActiveEditorReadonlyInSession'; - static readonly LABEL = nls.localize2('toggleActiveEditorReadonlyInSession', "Toggle Active Editor Read-only in Session"); + static readonly LABEL = nls.localize('toggleActiveEditorReadonlyInSession', "Toggle Active Editor Read-only in Session"); constructor() { super( ToggleActiveEditorReadonlyInSession.ID, - ToggleActiveEditorReadonlyInSession.LABEL, + { value: ToggleActiveEditorReadonlyInSession.LABEL, original: 'Toggle Active Editor Readonly in Session' }, 'toggle' ); } @@ -1303,12 +1303,12 @@ export class ToggleActiveEditorReadonlyInSession extends BaseSetActiveEditorRead export class ResetActiveEditorReadonlyInSession extends BaseSetActiveEditorReadonlyInSession { static readonly ID = 'workbench.action.files.resetActiveEditorReadonlyInSession'; - static readonly LABEL = nls.localize2('resetActiveEditorReadonlyInSession', "Reset Active Editor Read-only in Session"); + static readonly LABEL = nls.localize('resetActiveEditorReadonlyInSession', "Reset Active Editor Read-only in Session"); constructor() { super( ResetActiveEditorReadonlyInSession.ID, - ResetActiveEditorReadonlyInSession.LABEL, + { value: ResetActiveEditorReadonlyInSession.LABEL, original: 'Reset Active Editor Readonly in Session' }, 'reset' ); } diff --git a/src/vs/workbench/contrib/files/browser/fileConstants.ts b/src/vs/workbench/contrib/files/browser/fileConstants.ts index c38f9987d22..6736003acd1 100644 --- a/src/vs/workbench/contrib/files/browser/fileConstants.ts +++ b/src/vs/workbench/contrib/files/browser/fileConstants.ts @@ -19,14 +19,14 @@ export const COPY_PATH_COMMAND_ID = 'copyFilePath'; export const COPY_RELATIVE_PATH_COMMAND_ID = 'copyRelativeFilePath'; export const SAVE_FILE_AS_COMMAND_ID = 'workbench.action.files.saveAs'; -export const SAVE_FILE_AS_LABEL = nls.localize2('saveAs', "Save As..."); +export const SAVE_FILE_AS_LABEL = nls.localize('saveAs', "Save As..."); export const SAVE_FILE_COMMAND_ID = 'workbench.action.files.save'; -export const SAVE_FILE_LABEL = nls.localize2('save', "Save"); +export const SAVE_FILE_LABEL = nls.localize('save', "Save"); export const SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID = 'workbench.action.files.saveWithoutFormatting'; -export const SAVE_FILE_WITHOUT_FORMATTING_LABEL = nls.localize2('saveWithoutFormatting', "Save without Formatting"); +export const SAVE_FILE_WITHOUT_FORMATTING_LABEL = nls.localize('saveWithoutFormatting', "Save without Formatting"); export const SAVE_ALL_COMMAND_ID = 'saveAll'; -export const SAVE_ALL_LABEL = nls.localize2('saveAll', "Save All"); +export const SAVE_ALL_LABEL = nls.localize('saveAll', "Save All"); export const SAVE_ALL_IN_GROUP_COMMAND_ID = 'workbench.files.action.saveAllInGroup'; @@ -45,5 +45,5 @@ export const NEXT_COMPRESSED_FOLDER = 'nextCompressedFolder'; export const FIRST_COMPRESSED_FOLDER = 'firstCompressedFolder'; export const LAST_COMPRESSED_FOLDER = 'lastCompressedFolder'; export const NEW_UNTITLED_FILE_COMMAND_ID = 'workbench.action.files.newUntitledFile'; -export const NEW_UNTITLED_FILE_LABEL = nls.localize2('newUntitledFile', "New Untitled Text File"); +export const NEW_UNTITLED_FILE_LABEL = nls.localize('newUntitledFile', "New Untitled Text File"); export const NEW_FILE_COMMAND_ID = 'workbench.action.files.newFile'; diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 9f21e5014ba..7115d8fc698 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -835,7 +835,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.files.saveAll', - title: SAVE_ALL_LABEL, + title: { value: SAVE_ALL_LABEL, original: 'Save All' }, f1: true, icon: Codicon.saveAll, menu: { diff --git a/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts b/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts index de8140c144b..92a340e8175 100644 --- a/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts @@ -24,7 +24,7 @@ import { SideBySideEditor, EditorResourceAccessor } from 'vs/workbench/common/ed import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; const REVEAL_IN_OS_COMMAND_ID = 'revealFileInOS'; -const REVEAL_IN_OS_LABEL = isWindows ? nls.localize2('revealInWindows', "Reveal in File Explorer") : isMacintosh ? nls.localize2('revealInMac', "Reveal in Finder") : nls.localize2('openContainer', "Open Containing Folder"); +const REVEAL_IN_OS_LABEL = isWindows ? nls.localize('revealInWindows', "Reveal in File Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder"); const REVEAL_IN_OS_WHEN_CONTEXT = ContextKeyExpr.or(ResourceContextKey.Scheme.isEqualTo(Schemas.file), ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeUserData)); KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -57,13 +57,13 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL.value, REVEAL_IN_OS_WHEN_CONTEXT, '2_files', 0); +appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, REVEAL_IN_OS_WHEN_CONTEXT, '2_files', 0); // Menu registration - open editors const revealInOsCommand = { id: REVEAL_IN_OS_COMMAND_ID, - title: REVEAL_IN_OS_LABEL.value + title: REVEAL_IN_OS_LABEL }; MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: 'navigation', @@ -92,6 +92,6 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { const category = nls.localize2('filesCategory', "File"); appendToCommandPalette({ id: REVEAL_IN_OS_COMMAND_ID, - title: REVEAL_IN_OS_LABEL, + title: { value: REVEAL_IN_OS_LABEL, original: isWindows ? 'Reveal in File Explorer' : isMacintosh ? 'Reveal in Finder' : 'Open Containing Folder' }, category: category }, REVEAL_IN_OS_WHEN_CONTEXT); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 85033c64166..57e2376c569 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -34,7 +34,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); -export const LOCALIZED_START_INLINE_CHAT_STRING = localize2('run', 'Start Inline Chat'); +export const LOCALIZED_START_INLINE_CHAT_STRING = localize('run', 'Start Inline Chat'); export const START_INLINE_CHAT = registerIcon('start-inline-chat', Codicon.sparkle, localize('startInlineChat', 'Icon which spawns the inline chat from the editor toolbar.')); // some gymnastics to enable hold for speech without moving the StartSessionAction into the electron-layer @@ -52,7 +52,7 @@ export class StartSessionAction extends EditorAction2 { constructor() { super({ id: 'inlineChat.start', - title: LOCALIZED_START_INLINE_CHAT_STRING, + title: { value: LOCALIZED_START_INLINE_CHAT_STRING, original: 'Start Inline Chat' }, category: AbstractInlineChatAction.category, f1: true, precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_PROVIDER, EditorContextKeys.writable), diff --git a/src/vs/workbench/contrib/issue/common/issue.contribution.ts b/src/vs/workbench/contrib/issue/common/issue.contribution.ts index 05518d0f4aa..ece002871bb 100644 --- a/src/vs/workbench/contrib/issue/common/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/common/issue.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize, localize2 } from 'vs/nls'; +import { localize } from 'vs/nls'; import { ICommandAction } from 'vs/platform/action/common/action'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; @@ -97,7 +97,10 @@ export class BaseIssueContribution implements IWorkbenchContribution { const reportIssue: ICommandAction = { id: OpenIssueReporterActionId, - title: localize2({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue..."), + title: { + value: localize({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue..."), + original: 'Report Issue...' + }, category: Categories.Help }; diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts index 05bc0632624..3f446e23dba 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts @@ -43,7 +43,7 @@ class ReportPerformanceIssueUsingReporterAction extends Action2 { constructor() { super({ id: ReportPerformanceIssueUsingReporterAction.ID, - title: localize2({ key: 'reportPerformanceIssue', comment: [`Here, 'issue' means problem or bug`] }, "Report Performance Issue..."), + title: { value: localize({ key: 'reportPerformanceIssue', comment: [`Here, 'issue' means problem or bug`] }, "Report Performance Issue..."), original: 'Report Performance Issue' }, category: Categories.Help, f1: true }); diff --git a/src/vs/workbench/contrib/localHistory/electron-sandbox/localHistoryCommands.ts b/src/vs/workbench/contrib/localHistory/electron-sandbox/localHistoryCommands.ts index 76ac171e9af..20d20356e6e 100644 --- a/src/vs/workbench/contrib/localHistory/electron-sandbox/localHistoryCommands.ts +++ b/src/vs/workbench/contrib/localHistory/electron-sandbox/localHistoryCommands.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize2 } from 'vs/nls'; +import { localize } from 'vs/nls'; import { IWorkingCopyHistoryService } from 'vs/workbench/services/workingCopy/common/workingCopyHistory'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; @@ -21,7 +21,10 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.localHistory.revealInOS', - title: isWindows ? localize2('revealInWindows', "Reveal in File Explorer") : isMacintosh ? localize2('revealInMac', "Reveal in Finder") : localize2('openContainer', "Open Containing Folder"), + title: { + value: isWindows ? localize('revealInWindows', "Reveal in File Explorer") : isMacintosh ? localize('revealInMac', "Reveal in Finder") : localize('openContainer', "Open Containing Folder"), + original: isWindows ? 'Reveal in File Explorer' : isMacintosh ? 'Reveal in Finder' : 'Open Containing Folder' + }, menu: { id: MenuId.TimelineItemContext, group: '4_reveal', diff --git a/src/vs/workbench/contrib/localization/common/localizationsActions.ts b/src/vs/workbench/contrib/localization/common/localizationsActions.ts index d9ad34f1c1b..97c275ac765 100644 --- a/src/vs/workbench/contrib/localization/common/localizationsActions.ts +++ b/src/vs/workbench/contrib/localization/common/localizationsActions.ts @@ -100,12 +100,12 @@ export class ConfigureDisplayLanguageAction extends Action2 { export class ClearDisplayLanguageAction extends Action2 { public static readonly ID = 'workbench.action.clearLocalePreference'; - public static readonly LABEL = localize2('clearDisplayLanguage', "Clear Display Language Preference"); + public static readonly LABEL = localize('clearDisplayLanguage', "Clear Display Language Preference"); constructor() { super({ id: ClearDisplayLanguageAction.ID, - title: ClearDisplayLanguageAction.LABEL, + title: { original: 'Clear Display Language Preference', value: ClearDisplayLanguageAction.LABEL }, menu: { id: MenuId.CommandPalette } diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index aa02cce1cb8..2466217b62c 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -311,7 +311,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.problems.focus', - title: Messages.MARKERS_PANEL_SHOW_LABEL, + title: { value: Messages.MARKERS_PANEL_SHOW_LABEL, original: 'Focus Problems (Errors, Warnings, Infos)' }, category: Categories.View, f1: true, }); @@ -446,7 +446,7 @@ registerAction2(class extends ViewAction { constructor() { super({ id: Markers.MARKERS_VIEW_SHOW_MULTILINE_MESSAGE, - title: localize2('show multiline', "Show message in multiple lines"), + title: { value: localize('show multiline', "Show message in multiple lines"), original: 'Problems: Show message in multiple lines' }, category: localize('problems', "Problems"), menu: { id: MenuId.CommandPalette, @@ -464,7 +464,7 @@ registerAction2(class extends ViewAction { constructor() { super({ id: Markers.MARKERS_VIEW_SHOW_SINGLELINE_MESSAGE, - title: localize2('show singleline', "Show message in single line"), + title: { value: localize('show singleline', "Show message in single line"), original: 'Problems: Show message in single line' }, category: localize('problems', "Problems"), menu: { id: MenuId.CommandPalette, diff --git a/src/vs/workbench/contrib/markers/browser/messages.ts b/src/vs/workbench/contrib/markers/browser/messages.ts index 29c07828fc6..e6a387f0ce2 100644 --- a/src/vs/workbench/contrib/markers/browser/messages.ts +++ b/src/vs/workbench/contrib/markers/browser/messages.ts @@ -12,7 +12,7 @@ import { Marker } from './markersModel'; export default class Messages { public static MARKERS_PANEL_TOGGLE_LABEL: string = nls.localize('problems.view.toggle.label', "Toggle Problems (Errors, Warnings, Infos)"); - public static MARKERS_PANEL_SHOW_LABEL = nls.localize2('problems.view.focus.label', "Focus Problems (Errors, Warnings, Infos)"); + public static MARKERS_PANEL_SHOW_LABEL: string = nls.localize('problems.view.focus.label', "Focus Problems (Errors, Warnings, Infos)"); public static PROBLEMS_PANEL_CONFIGURATION_TITLE: string = nls.localize('problems.panel.configuration.title', "Problems View"); public static PROBLEMS_PANEL_CONFIGURATION_AUTO_REVEAL: string = nls.localize('problems.panel.configuration.autoreveal', "Controls whether Problems view should automatically reveal files when opening them."); diff --git a/src/vs/workbench/contrib/share/browser/share.contribution.ts b/src/vs/workbench/contrib/share/browser/share.contribution.ts index ae071141610..2eb7e5595d5 100644 --- a/src/vs/workbench/contrib/share/browser/share.contribution.ts +++ b/src/vs/workbench/contrib/share/browser/share.contribution.ts @@ -8,7 +8,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { localize, localize2 } from 'vs/nls'; +import { localize } from 'vs/nls'; import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -77,12 +77,12 @@ class ShareWorkbenchContribution { this._disposables.add( registerAction2(class ShareAction extends Action2 { static readonly ID = 'workbench.action.share'; - static readonly LABEL = localize2('share', 'Share...'); + static readonly LABEL = localize('share', 'Share...'); constructor() { super({ id: ShareAction.ID, - title: ShareAction.LABEL, + title: { value: ShareAction.LABEL, original: 'Share...' }, f1: true, icon: Codicon.linkExternal, precondition: ContextKeyExpr.and(ShareProviderCountContext.notEqualsTo(0), WorkspaceFolderCountContext.notEqualsTo(0)), diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 43246667cfe..27f92a65ad3 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -90,7 +90,7 @@ const USE_SLOW_PICKER = 'task.quickOpen.showAll'; export namespace ConfigureTaskAction { export const ID = 'workbench.action.tasks.configureTaskRunner'; - export const TEXT = nls.localize2('ConfigureTaskRunnerAction.label', "Configure Task"); + export const TEXT = nls.localize('ConfigureTaskRunnerAction.label', "Configure Task"); } export type TaskQuickPickEntryType = (IQuickPickItem & { task: Task }) | (IQuickPickItem & { folder: IWorkspaceFolder }) | (IQuickPickItem & { settingType: string }); @@ -2563,7 +2563,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const thisCapture: AbstractTaskService = this; return new class extends Action { constructor() { - super(ConfigureTaskAction.ID, ConfigureTaskAction.TEXT.value, undefined, true, () => { thisCapture._runConfigureTasks(); return Promise.resolve(undefined); }); + super(ConfigureTaskAction.ID, ConfigureTaskAction.TEXT, undefined, true, () => { thisCapture._runConfigureTasks(); return Promise.resolve(undefined); }); } }; } @@ -2576,7 +2576,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const needsTerminate = buildError.code === TaskErrors.RunningTask; if (needsConfig || needsTerminate) { this._notificationService.prompt(buildError.severity, buildError.message, [{ - label: needsConfig ? ConfigureTaskAction.TEXT.value : nls.localize('TerminateAction.label', "Terminate Task"), + label: needsConfig ? ConfigureTaskAction.TEXT : nls.localize('TerminateAction.label', "Terminate Task"), run: () => { if (needsConfig) { this._runConfigureTasks(); diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index a0d2bb523f6..d92f58923b0 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -246,7 +246,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: ConfigureTaskAction.ID, - title: ConfigureTaskAction.TEXT, + title: { value: ConfigureTaskAction.TEXT, original: 'Configure Task' }, category: TASKS_CATEGORY }, when: TaskExecutionSupportedContext diff --git a/src/vs/workbench/contrib/update/browser/update.contribution.ts b/src/vs/workbench/contrib/update/browser/update.contribution.ts index 1fc44c7d8f6..46b1d76365a 100644 --- a/src/vs/workbench/contrib/update/browser/update.contribution.ts +++ b/src/vs/workbench/contrib/update/browser/update.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/platform/update/common/update.config.contribution'; -import { localize, localize2, unlocalized2 } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; @@ -41,7 +41,7 @@ export class ShowCurrentReleaseNotesAction extends Action2 { ...localize2('showReleaseNotes', "Show Release Notes"), mnemonicTitle: localize({ key: 'mshowReleaseNotes', comment: ['&& denotes a mnemonic'] }, "Show &&Release Notes"), }, - category: unlocalized2(product.nameShort), + category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: RELEASE_NOTES_URL, menu: [{ @@ -80,7 +80,7 @@ export class CheckForUpdateAction extends Action2 { super({ id: 'update.checkForUpdate', title: localize2('checkForUpdates', 'Check for Updates...'), - category: unlocalized2(product.nameShort), + category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Idle), }); @@ -97,7 +97,7 @@ class DownloadUpdateAction extends Action2 { super({ id: 'update.downloadUpdate', title: localize2('downloadUpdate', 'Download Update'), - category: unlocalized2(product.nameShort), + category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.AvailableForDownload) }); @@ -113,7 +113,7 @@ class InstallUpdateAction extends Action2 { super({ id: 'update.installUpdate', title: localize2('installUpdate', 'Install Update'), - category: unlocalized2(product.nameShort), + category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloaded) }); @@ -129,7 +129,7 @@ class RestartToUpdateAction extends Action2 { super({ id: 'update.restartToUpdate', title: localize2('restartToUpdate', 'Restart to Update'), - category: unlocalized2(product.nameShort), + category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready) }); diff --git a/src/vs/workbench/contrib/url/browser/trustedDomains.ts b/src/vs/workbench/contrib/url/browser/trustedDomains.ts index 82c9559dad4..2019b04e521 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomains.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomains.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { localize, localize2 } from 'vs/nls'; +import { localize } from 'vs/nls'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; @@ -25,7 +25,7 @@ export const TRUSTED_DOMAINS_CONTENT_STORAGE_KEY = 'http.linkProtectionTrustedDo export const manageTrustedDomainSettingsCommand = { id: 'workbench.action.manageTrustedDomain', description: { - description: localize2('trustedDomain.manageTrustedDomain', 'Manage Trusted Domains'), + description: localize('trustedDomain.manageTrustedDomain', 'Manage Trusted Domains'), args: [] }, handler: async (accessor: ServicesAccessor) => { diff --git a/src/vs/workbench/contrib/url/browser/url.contribution.ts b/src/vs/workbench/contrib/url/browser/url.contribution.ts index 92e2b6b9611..d0484bd566c 100644 --- a/src/vs/workbench/contrib/url/browser/url.contribution.ts +++ b/src/vs/workbench/contrib/url/browser/url.contribution.ts @@ -55,7 +55,10 @@ CommandsRegistry.registerCommand(manageTrustedDomainSettingsCommand); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: manageTrustedDomainSettingsCommand.id, - title: manageTrustedDomainSettingsCommand.description.description + title: { + value: manageTrustedDomainSettingsCommand.description.description, + original: 'Manage Trusted Domains' + } } }); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 0787368edba..5a716a0880e 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -835,7 +835,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private getShowConflictsTitle(): ILocalizedString { - return localize2('resolveConflicts_global', "Show Conflicts ({0})", this.getConflictsCount()); + return { value: localize('resolveConflicts_global', "Show Conflicts ({0})", this.getConflictsCount()), original: `Show Conflicts (${this.getConflictsCount()})` }; } private conflictsActionDisposable = this._register(new MutableDisposable()); diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts index 356a852efdb..f973ce7ef5b 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts @@ -104,12 +104,12 @@ export class WebViewEditorFindPreviousCommand extends Action2 { export class ReloadWebviewAction extends Action2 { static readonly ID = 'workbench.action.webview.reloadWebviewAction'; - static readonly LABEL = nls.localize2('refreshWebviewLabel', "Reload Webviews"); + static readonly LABEL = nls.localize('refreshWebviewLabel', "Reload Webviews"); public constructor() { super({ id: ReloadWebviewAction.ID, - title: ReloadWebviewAction.LABEL, + title: { value: ReloadWebviewAction.LABEL, original: 'Reload Webviews' }, category: Categories.Developer, menu: [{ id: MenuId.CommandPalette diff --git a/src/vs/workbench/electron-sandbox/actions/installActions.ts b/src/vs/workbench/electron-sandbox/actions/installActions.ts index d9332bb4855..952883aec23 100644 --- a/src/vs/workbench/electron-sandbox/actions/installActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/installActions.ts @@ -21,7 +21,10 @@ export class InstallShellScriptAction extends Action2 { constructor() { super({ id: 'workbench.action.installCommandLine', - title: localize2('install', "Install '{0}' command in PATH", product.applicationName), + title: { + value: localize('install', "Install '{0}' command in PATH", product.applicationName), + original: `Install \'${product.applicationName}\' command in PATH` + }, category: shellCommandCategory, f1: true }); @@ -51,7 +54,10 @@ export class UninstallShellScriptAction extends Action2 { constructor() { super({ id: 'workbench.action.uninstallCommandLine', - title: localize2('uninstall', "Uninstall '{0}' command from PATH", product.applicationName), + title: { + value: localize('uninstall', "Uninstall '{0}' command from PATH", product.applicationName), + original: `Uninstall \'${product.applicationName}\' command from PATH` + }, category: shellCommandCategory, f1: true }); diff --git a/src/vs/workbench/services/views/browser/viewsService.ts b/src/vs/workbench/services/views/browser/viewsService.ts index 49643efd588..7f036faf807 100644 --- a/src/vs/workbench/services/views/browser/viewsService.ts +++ b/src/vs/workbench/services/views/browser/viewsService.ts @@ -501,10 +501,10 @@ export class ViewsService extends Disposable implements IViewsService { private registerFocusViewAction(viewDescriptor: IViewDescriptor, category?: string | ILocalizedString): IDisposable { return registerAction2(class FocusViewAction extends Action2 { constructor() { - const title = localize2({ key: 'focus view', comment: ['{0} indicates the name of the view to be focused.'] }, "Focus on {0} View", viewDescriptor.name.value); + const title = localize({ key: 'focus view', comment: ['{0} indicates the name of the view to be focused.'] }, "Focus on {0} View", viewDescriptor.name.value); super({ id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`, - title, + title: { original: `Focus on ${viewDescriptor.name.original} View`, value: title }, category, menu: [{ id: MenuId.CommandPalette, @@ -520,7 +520,7 @@ export class ViewsService extends Disposable implements IViewsService { win: viewDescriptor.focusCommand?.keybindings?.win }, metadata: { - description: title.value, + description: title, args: [ { name: 'focusOptions', diff --git a/src/vs/workbench/services/views/common/viewContainerModel.ts b/src/vs/workbench/services/views/common/viewContainerModel.ts index 60009d0ff79..d7c9f7149ab 100644 --- a/src/vs/workbench/services/views/common/viewContainerModel.ts +++ b/src/vs/workbench/services/views/common/viewContainerModel.ts @@ -21,13 +21,12 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IOutputService } from 'vs/workbench/services/output/common/output'; import { CounterSet } from 'vs/base/common/map'; -import { localize2 } from 'vs/nls'; registerAction2(class extends Action2 { constructor() { super({ id: '_workbench.output.showViewsLog', - title: localize2('showViewsLog', "Show Views Log"), + title: { value: 'Show Views Log', original: 'Show Views Log' }, category: Categories.Developer, f1: true }); From 0d0ddc5a7b355a1341f5314ccb6a4e5d5a96626c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 2 Feb 2024 10:08:55 +0100 Subject: [PATCH 0862/1897] Zoom level indicator always reset to level 1 (#204019) (#204082) --- src/vs/workbench/electron-sandbox/actions/windowActions.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index 6fe958a95b0..0be3e3918c7 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -83,7 +83,7 @@ abstract class BaseZoomAction extends Action2 { let level: number; if (typeof levelOrReset === 'number') { - level = levelOrReset; + level = Math.round(levelOrReset); // prevent fractional zoom levels } else { // reset to 0 when we apply to all windows @@ -102,8 +102,6 @@ abstract class BaseZoomAction extends Action2 { } } - level = Math.round(level); // when reaching smallest zoom, prevent fractional zoom levels - if (level > MAX_ZOOM_LEVEL || level < MIN_ZOOM_LEVEL) { return; // https://github.com/microsoft/vscode/issues/48357 } From 6ac03b980dab16b94cbebe1fd8442cc65cb078f5 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 2 Feb 2024 11:23:41 +0100 Subject: [PATCH 0863/1897] aux window - always send a target window ID for native ops (#204095) * aux window - always send a target window ID for native ops * aux window - always send a target window ID for native ops --- src/vs/platform/native/common/native.ts | 38 +++++----- .../electron-main/nativeHostMainService.ts | 73 +++++++------------ .../platform/windows/electron-main/windows.ts | 22 ------ .../runtimeExtensionsEditor.ts | 18 +++-- .../actions/developerActions.ts | 3 +- .../parts/dialogs/dialogHandler.ts | 10 ++- .../electron-sandbox/fileDialogService.ts | 13 ++-- .../electron-sandbox/workbenchTestServices.ts | 10 +-- 8 files changed, 79 insertions(+), 108 deletions(-) diff --git a/src/vs/platform/native/common/native.ts b/src/vs/platform/native/common/native.ts index 239dcad9322..94ae20b32f8 100644 --- a/src/vs/platform/native/common/native.ts +++ b/src/vs/platform/native/common/native.ts @@ -33,7 +33,7 @@ export interface IOSStatistics { loadavg: number[]; } -export interface INativeOptions { +export interface INativeHostOptions { readonly targetWindowId?: number; } @@ -77,25 +77,25 @@ export interface ICommonNativeHostService { openWindow(options?: IOpenEmptyWindowOptions): Promise; openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise; - toggleFullScreen(options?: INativeOptions): Promise; + toggleFullScreen(options?: INativeHostOptions): Promise; - handleTitleDoubleClick(options?: INativeOptions): Promise; + handleTitleDoubleClick(options?: INativeHostOptions): Promise; getCursorScreenPoint(): Promise<{ readonly point: IPoint; readonly display: IRectangle }>; - isMaximized(options?: INativeOptions): Promise; - maximizeWindow(options?: INativeOptions): Promise; - unmaximizeWindow(options?: INativeOptions): Promise; - minimizeWindow(options?: INativeOptions): Promise; - moveWindowTop(options?: INativeOptions): Promise; - positionWindow(position: IRectangle, options?: INativeOptions): Promise; + isMaximized(options?: INativeHostOptions): Promise; + maximizeWindow(options?: INativeHostOptions): Promise; + unmaximizeWindow(options?: INativeHostOptions): Promise; + minimizeWindow(options?: INativeHostOptions): Promise; + moveWindowTop(options?: INativeHostOptions): Promise; + positionWindow(position: IRectangle, options?: INativeHostOptions): Promise; /** * Only supported on Windows and macOS. Updates the window controls to match the title bar size. * * @param options `backgroundColor` and `foregroundColor` are only supported on Windows */ - updateWindowControls(options: INativeOptions & { height?: number; backgroundColor?: string; foregroundColor?: string }): Promise; + updateWindowControls(options: INativeHostOptions & { height?: number; backgroundColor?: string; foregroundColor?: string }): Promise; setMinimumSize(width: number | undefined, height: number | undefined): Promise; @@ -109,12 +109,12 @@ export interface ICommonNativeHostService { * should only be used if it is necessary to steal focus from the current * focused application which may not be VSCode. */ - focusWindow(options?: INativeOptions & { force?: boolean }): Promise; + focusWindow(options?: INativeHostOptions & { force?: boolean }): Promise; // Dialogs - showMessageBox(options: MessageBoxOptions): Promise; - showSaveDialog(options: SaveDialogOptions): Promise; - showOpenDialog(options: OpenDialogOptions): Promise; + showMessageBox(options: MessageBoxOptions & INativeHostOptions): Promise; + showSaveDialog(options: SaveDialogOptions & INativeHostOptions): Promise; + showOpenDialog(options: OpenDialogOptions & INativeHostOptions): Promise; pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise; pickFileAndOpen(options: INativeOpenDialogOptions): Promise; @@ -123,8 +123,8 @@ export interface ICommonNativeHostService { // OS showItemInFolder(path: string): Promise; - setRepresentedFilename(path: string, options?: INativeOptions): Promise; - setDocumentEdited(edited: boolean, options?: INativeOptions): Promise; + setRepresentedFilename(path: string, options?: INativeHostOptions): Promise; + setDocumentEdited(edited: boolean, options?: INativeHostOptions): Promise; openExternal(url: string): Promise; moveItemToTrash(fullPath: string): Promise; @@ -169,13 +169,13 @@ export interface ICommonNativeHostService { notifyReady(): Promise; relaunch(options?: { addArgs?: string[]; removeArgs?: string[] }): Promise; reload(options?: { disableExtensions?: boolean }): Promise; - closeWindow(options?: INativeOptions): Promise; + closeWindow(options?: INativeHostOptions): Promise; quit(): Promise; exit(code: number): Promise; // Development - openDevTools(options?: OpenDevToolsOptions): Promise; - toggleDevTools(): Promise; + openDevTools(options?: Partial & INativeHostOptions): Promise; + toggleDevTools(options?: INativeHostOptions): Promise; // Perf Introspection profileRenderer(session: string, duration: number): Promise; diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index a9f4b9ed735..fa4087a411f 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -24,16 +24,16 @@ import { ISerializableCommandAction } from 'vs/platform/action/common/action'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; -import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleMainService, IRelaunchOptions } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { ILogService } from 'vs/platform/log/common/log'; -import { ICommonNativeHostService, INativeOptions, IOSProperties, IOSStatistics } from 'vs/platform/native/common/native'; +import { ICommonNativeHostService, INativeHostOptions, IOSProperties, IOSStatistics } from 'vs/platform/native/common/native'; import { IProductService } from 'vs/platform/product/common/productService'; import { IPartsSplash } from 'vs/platform/theme/common/themeService'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { ICodeWindow } from 'vs/platform/window/electron-main/window'; import { IColorScheme, IOpenedAuxiliaryWindow, IOpenedMainWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IPoint, IRectangle, IWindowOpenable } from 'vs/platform/window/common/window'; -import { getFocusedOrLastActiveWindow, IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; +import { IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { isWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { VSBuffer } from 'vs/base/common/buffer'; @@ -61,8 +61,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain @ILogService private readonly logService: ILogService, @IProductService private readonly productService: IProductService, @IThemeMainService private readonly themeMainService: IThemeMainService, - @IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService, - @IInstantiationService private readonly instantiationService: IInstantiationService + @IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService ) { super(); } @@ -213,12 +212,12 @@ export class NativeHostMainService extends Disposable implements INativeHostMain }, options); } - async toggleFullScreen(windowId: number | undefined, options?: INativeOptions): Promise { + async toggleFullScreen(windowId: number | undefined, options?: INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); window?.toggleFullScreen(); } - async handleTitleDoubleClick(windowId: number | undefined, options?: INativeOptions): Promise { + async handleTitleDoubleClick(windowId: number | undefined, options?: INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); window?.handleTitleDoubleClick(); } @@ -230,32 +229,32 @@ export class NativeHostMainService extends Disposable implements INativeHostMain return { point, display: display.bounds }; } - async isMaximized(windowId: number | undefined, options?: INativeOptions): Promise { + async isMaximized(windowId: number | undefined, options?: INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); return window?.win?.isMaximized() ?? false; } - async maximizeWindow(windowId: number | undefined, options?: INativeOptions): Promise { + async maximizeWindow(windowId: number | undefined, options?: INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); window?.win?.maximize(); } - async unmaximizeWindow(windowId: number | undefined, options?: INativeOptions): Promise { + async unmaximizeWindow(windowId: number | undefined, options?: INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); window?.win?.unmaximize(); } - async minimizeWindow(windowId: number | undefined, options?: INativeOptions): Promise { + async minimizeWindow(windowId: number | undefined, options?: INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); window?.win?.minimize(); } - async moveWindowTop(windowId: number | undefined, options?: INativeOptions): Promise { + async moveWindowTop(windowId: number | undefined, options?: INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); window?.win?.moveTop(); } - async positionWindow(windowId: number | undefined, position: IRectangle, options?: INativeOptions): Promise { + async positionWindow(windowId: number | undefined, position: IRectangle, options?: INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); if (window?.win) { if (window.win.isFullScreen()) { @@ -268,12 +267,12 @@ export class NativeHostMainService extends Disposable implements INativeHostMain } } - async updateWindowControls(windowId: number | undefined, options: INativeOptions & { height?: number; backgroundColor?: string; foregroundColor?: string }): Promise { + async updateWindowControls(windowId: number | undefined, options: INativeHostOptions & { height?: number; backgroundColor?: string; foregroundColor?: string }): Promise { const window = this.windowById(options?.targetWindowId, windowId); window?.updateWindowControls(options); } - async focusWindow(windowId: number | undefined, options?: INativeOptions & { force?: boolean }): Promise { + async focusWindow(windowId: number | undefined, options?: INativeHostOptions & { force?: boolean }): Promise { const window = this.windowById(options?.targetWindowId, windowId); window?.focus({ force: options?.force ?? false }); } @@ -408,21 +407,18 @@ export class NativeHostMainService extends Disposable implements INativeHostMain //#region Dialog - async showMessageBox(windowId: number | undefined, options: MessageBoxOptions): Promise { - const window = this.getTargetWindow(windowId); - + async showMessageBox(windowId: number | undefined, options: MessageBoxOptions & INativeHostOptions): Promise { + const window = this.windowById(options?.targetWindowId, windowId); return this.dialogMainService.showMessageBox(options, window?.win ?? undefined); } - async showSaveDialog(windowId: number | undefined, options: SaveDialogOptions): Promise { - const window = this.getTargetWindow(windowId); - + async showSaveDialog(windowId: number | undefined, options: SaveDialogOptions & INativeHostOptions): Promise { + const window = this.windowById(options?.targetWindowId, windowId); return this.dialogMainService.showSaveDialog(options, window?.win ?? undefined); } - async showOpenDialog(windowId: number | undefined, options: OpenDialogOptions): Promise { - const window = this.getTargetWindow(windowId); - + async showOpenDialog(windowId: number | undefined, options: OpenDialogOptions & INativeHostOptions): Promise { + const window = this.windowById(options?.targetWindowId, windowId); return this.dialogMainService.showOpenDialog(options, window?.win ?? undefined); } @@ -474,12 +470,12 @@ export class NativeHostMainService extends Disposable implements INativeHostMain shell.showItemInFolder(path); } - async setRepresentedFilename(windowId: number | undefined, path: string, options?: INativeOptions): Promise { + async setRepresentedFilename(windowId: number | undefined, path: string, options?: INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); window?.setRepresentedFilename(path); } - async setDocumentEdited(windowId: number | undefined, edited: boolean, options?: INativeOptions): Promise { + async setDocumentEdited(windowId: number | undefined, edited: boolean, options?: INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); window?.setDocumentEdited(edited); } @@ -604,13 +600,11 @@ export class NativeHostMainService extends Disposable implements INativeHostMain return this.themeMainService.getColorScheme(); } - // WSL async hasWSLFeatureInstalled(): Promise { return isWindows && hasWSLFeatureInstalled(); } - //#endregion @@ -731,7 +725,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain } } - async closeWindow(windowId: number | undefined, options?: INativeOptions): Promise { + async closeWindow(windowId: number | undefined, options?: INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); return window?.win?.close(); } @@ -781,15 +775,13 @@ export class NativeHostMainService extends Disposable implements INativeHostMain //#region Development - async openDevTools(windowId: number | undefined, options?: OpenDevToolsOptions): Promise { - const window = this.getTargetWindow(windowId); - - window?.win?.webContents.openDevTools(options); + async openDevTools(windowId: number | undefined, options?: Partial & INativeHostOptions): Promise { + const window = this.windowById(options?.targetWindowId, windowId); + window?.win?.webContents.openDevTools(options?.mode ? { mode: options.mode, activate: options.activate } : undefined); } - async toggleDevTools(windowId: number | undefined): Promise { - const window = this.getTargetWindow(windowId); - + async toggleDevTools(windowId: number | undefined, options?: INativeHostOptions): Promise { + const window = this.windowById(options?.targetWindowId, windowId); window?.win?.webContents.toggleDevTools(); } @@ -846,13 +838,4 @@ export class NativeHostMainService extends Disposable implements INativeHostMain return this.auxiliaryWindowsMainService.getWindowById(windowId); } - - private getTargetWindow(fallbackWindowId: number | undefined): ICodeWindow | IAuxiliaryWindow | undefined { - let window = this.instantiationService.invokeFunction(getFocusedOrLastActiveWindow); - if (!window) { - window = this.windowById(fallbackWindowId); - } - - return window; - } } diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 5985e2f9691..1a57c00cee3 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -17,7 +17,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { join } from 'vs/base/common/path'; import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; -import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; import { Color } from 'vs/base/common/color'; export const IWindowsMainService = createDecorator('windowsMainService'); @@ -200,27 +199,6 @@ export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowSt return options; } -export function getFocusedOrLastActiveWindow(accessor: ServicesAccessor): ICodeWindow | IAuxiliaryWindow | undefined { - const windowsMainService = accessor.get(IWindowsMainService); - const auxiliaryWindowsMainService = accessor.get(IAuxiliaryWindowsMainService); - - // By: Electron focused window - const focusedWindow = windowsMainService.getFocusedWindow() ?? auxiliaryWindowsMainService.getFocusedWindow(); - if (focusedWindow) { - return focusedWindow; - } - - // By: Last active window - const mainLastActiveWindow = windowsMainService.getLastActiveWindow(); - const auxiliaryLastActiveWindow = auxiliaryWindowsMainService.getLastActiveWindow(); - - if (mainLastActiveWindow && auxiliaryLastActiveWindow) { - return mainLastActiveWindow.lastFocusTime < auxiliaryLastActiveWindow.lastFocusTime ? auxiliaryLastActiveWindow : mainLastActiveWindow; - } - - return mainLastActiveWindow ?? auxiliaryLastActiveWindow; -} - export function getLastFocused(windows: ICodeWindow[]): ICodeWindow | undefined; export function getLastFocused(windows: IAuxiliaryWindow[]): IAuxiliaryWindow | undefined; export function getLastFocused(windows: ICodeWindow[] | IAuxiliaryWindow[]): ICodeWindow | IAuxiliaryWindow | undefined { diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts index 38517c53dba..ad8794fb8c6 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts @@ -24,9 +24,11 @@ import { AbstractRuntimeExtensionsEditor, IRuntimeExtension } from 'vs/workbench import { VSBuffer } from 'vs/base/common/buffer'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; -import { INativeHostService } from 'vs/platform/native/common/native'; import { IV8Profile, Utils } from 'vs/platform/profiling/common/profiling'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { Schemas } from 'vs/base/common/network'; +import { joinPath } from 'vs/base/common/resources'; export const IExtensionHostProfileService = createDecorator('extensionHostProfileService'); export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey('profileSessionState', 'none'); @@ -170,10 +172,10 @@ export class SaveExtensionHostProfileAction extends Action { constructor( id: string = SaveExtensionHostProfileAction.ID, label: string = SaveExtensionHostProfileAction.LABEL, - @INativeHostService private readonly _nativeHostService: INativeHostService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService, - @IFileService private readonly _fileService: IFileService + @IFileService private readonly _fileService: IFileService, + @IFileDialogService private readonly _fileDialogService: IFileDialogService, ) { super(id, label, undefined, false); this._extensionHostProfileService.onDidChangeLastProfile(() => { @@ -186,24 +188,24 @@ export class SaveExtensionHostProfileAction extends Action { } private async _asyncRun(): Promise { - const picked = await this._nativeHostService.showSaveDialog({ + const picked = await this._fileDialogService.showSaveDialog({ title: nls.localize('saveprofile.dialogTitle', "Save Extension Host Profile"), - buttonLabel: nls.localize('saveprofile.saveButton', "Save"), - defaultPath: `CPU-${new Date().toISOString().replace(/[\-:]/g, '')}.cpuprofile`, + availableFileSystems: [Schemas.file], + defaultUri: joinPath(await this._fileDialogService.defaultFilePath(), `CPU-${new Date().toISOString().replace(/[\-:]/g, '')}.cpuprofile`), filters: [{ name: 'CPU Profiles', extensions: ['cpuprofile', 'txt'] }] }); - if (!picked || !picked.filePath || picked.canceled) { + if (!picked) { return; } const profileInfo = this._extensionHostProfileService.lastProfile; let dataToWrite: object = profileInfo ? profileInfo.data : {}; - let savePath = picked.filePath; + let savePath = picked.fsPath; if (this._environmentService.isBuilt) { // when running from a not-development-build we remove diff --git a/src/vs/workbench/electron-sandbox/actions/developerActions.ts b/src/vs/workbench/electron-sandbox/actions/developerActions.ts index 25df5605d17..a15b238b11a 100644 --- a/src/vs/workbench/electron-sandbox/actions/developerActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/developerActions.ts @@ -16,6 +16,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { IFileService } from 'vs/platform/files/common/files'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { URI } from 'vs/base/common/uri'; +import { getActiveWindow } from 'vs/base/browser/dom'; export class ToggleDevToolsAction extends Action2 { @@ -42,7 +43,7 @@ export class ToggleDevToolsAction extends Action2 { async run(accessor: ServicesAccessor): Promise { const nativeHostService = accessor.get(INativeHostService); - return nativeHostService.toggleDevTools(); + return nativeHostService.toggleDevTools({ targetWindowId: getActiveWindow().vscodeWindowId }); } } diff --git a/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts b/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts index 3940ab68e35..f2cc82ab8c5 100644 --- a/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts @@ -12,6 +12,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INativeHostService } from 'vs/platform/native/common/native'; import { IProductService } from 'vs/platform/product/common/productService'; import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { getActiveWindow } from 'vs/base/browser/dom'; export class NativeDialogHandler extends AbstractDialogHandler { @@ -37,7 +38,8 @@ export class NativeDialogHandler extends AbstractDialogHandler { buttons, cancelId: prompt.cancelButton ? buttons.length - 1 : -1 /* Disabled */, checkboxLabel: prompt.checkbox?.label, - checkboxChecked: prompt.checkbox?.checked + checkboxChecked: prompt.checkbox?.checked, + targetWindowId: getActiveWindow().vscodeWindowId }); return this.getPromptResult(prompt, response, checkboxChecked); @@ -56,7 +58,8 @@ export class NativeDialogHandler extends AbstractDialogHandler { buttons, cancelId: buttons.length - 1, checkboxLabel: confirmation.checkbox?.label, - checkboxChecked: confirmation.checkbox?.checked + checkboxChecked: confirmation.checkbox?.checked, + targetWindowId: getActiveWindow().vscodeWindowId }); return { confirmed: response === 0, checkboxChecked }; @@ -101,7 +104,8 @@ export class NativeDialogHandler extends AbstractDialogHandler { buttons: [ localize({ key: 'copy', comment: ['&& denotes a mnemonic'] }, "&&Copy"), localize('okButton', "OK") - ] + ], + targetWindowId: getActiveWindow().vscodeWindowId }); if (response === 0) { diff --git a/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts index 86fa6872ad6..fb588d918f0 100644 --- a/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts @@ -15,7 +15,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { INativeHostService } from 'vs/platform/native/common/native'; +import { INativeHostOptions, INativeHostService } from 'vs/platform/native/common/native'; import { AbstractFileDialogService } from 'vs/workbench/services/dialogs/browser/abstractFileDialogService'; import { Schemas } from 'vs/base/common/network'; import { ILanguageService } from 'vs/editor/common/languages/language'; @@ -26,6 +26,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ILogService } from 'vs/platform/log/common/log'; +import { getActiveWindow } from 'vs/base/browser/dom'; export class FileDialogService extends AbstractFileDialogService implements IFileDialogService { @@ -143,13 +144,14 @@ export class FileDialogService extends AbstractFileDialogService implements IFil return; } - private toNativeSaveDialogOptions(options: ISaveDialogOptions): SaveDialogOptions { + private toNativeSaveDialogOptions(options: ISaveDialogOptions): SaveDialogOptions & INativeHostOptions { options.defaultUri = options.defaultUri ? URI.file(options.defaultUri.path) : undefined; return { defaultPath: options.defaultUri?.fsPath, buttonLabel: options.saveLabel, filters: options.filters, - title: options.title + title: options.title, + targetWindowId: getActiveWindow().vscodeWindowId }; } @@ -173,12 +175,13 @@ export class FileDialogService extends AbstractFileDialogService implements IFil return this.showOpenDialogSimplified(schema, options); } - const newOptions: OpenDialogOptions & { properties: string[] } = { + const newOptions: OpenDialogOptions & { properties: string[] } & INativeHostOptions = { title: options.title, defaultPath: options.defaultUri?.fsPath, buttonLabel: options.openLabel, filters: options.filters, - properties: [] + properties: [], + targetWindowId: getActiveWindow().vscodeWindowId }; newOptions.properties.push('createDirectory'); diff --git a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts index e992583f9d6..d323ca935ae 100644 --- a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService, TestEncodingOracle, TestEnvironmentService, TestFileDialogService, TestFilesConfigurationService, TestFileService, TestLifecycleService, TestTextFileService } from 'vs/workbench/test/browser/workbenchTestServices'; import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; -import { INativeHostService, INativeOptions, IOSProperties, IOSStatistics } from 'vs/platform/native/common/native'; +import { INativeHostService, INativeHostOptions, IOSProperties, IOSStatistics } from 'vs/platform/native/common/native'; import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -96,13 +96,13 @@ export class TestNativeHostService implements INativeHostService { async maximizeWindow(): Promise { } async unmaximizeWindow(): Promise { } async minimizeWindow(): Promise { } - async moveWindowTop(options?: INativeOptions): Promise { } + async moveWindowTop(options?: INativeHostOptions): Promise { } getCursorScreenPoint(): Promise<{ readonly point: IPoint; readonly display: IRectangle }> { throw new Error('Method not implemented.'); } - async positionWindow(position: IRectangle, options?: INativeOptions): Promise { } + async positionWindow(position: IRectangle, options?: INativeHostOptions): Promise { } async updateWindowControls(options: { height?: number; backgroundColor?: string; foregroundColor?: string }): Promise { } async setMinimumSize(width: number | undefined, height: number | undefined): Promise { } async saveWindowSplash(value: IPartsSplash): Promise { } - async focusWindow(options?: INativeOptions): Promise { } + async focusWindow(options?: INativeHostOptions): Promise { } async showMessageBox(options: Electron.MessageBoxOptions): Promise { throw new Error('Method not implemented.'); } async showSaveDialog(options: Electron.SaveDialogOptions): Promise { throw new Error('Method not implemented.'); } async showOpenDialog(options: Electron.OpenDialogOptions): Promise { throw new Error('Method not implemented.'); } @@ -139,7 +139,7 @@ export class TestNativeHostService implements INativeHostService { async closeWindow(): Promise { } async quit(): Promise { } async exit(code: number): Promise { } - async openDevTools(options?: Electron.OpenDevToolsOptions | undefined): Promise { } + async openDevTools(options?: Partial & INativeHostOptions | undefined): Promise { } async toggleDevTools(): Promise { } async resolveProxy(url: string): Promise { return undefined; } async loadCertificates(): Promise { return []; } From df7f05bec75b3df228c9f4e1c7676e09a9c07736 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 2 Feb 2024 11:36:11 +0100 Subject: [PATCH 0864/1897] adding review changes --- .../contrib/inlineCompletions/browser/utils.ts | 3 ++- .../test/browser/utils.test.ts | 4 ++-- .../inlineCompletions/test/browser/utils.ts | 17 +++++++++-------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts index a35bae19507..5f4118eaf80 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { compareBy } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IObservable, autorunOpts } from 'vs/base/common/observable'; @@ -147,7 +148,7 @@ export function getNewRanges(edits: ISingleEditOperation[]): Range[] { * @returns inverse edits */ export function inverseEdits(model: TextModel, edits: ISingleEditOperation[]): ISingleEditOperation[] { - const sortPerm = Permutation.createSortPermutation(edits, (edit1, edit2) => Range.compareRangesUsingStarts(edit1.range, edit2.range)); + const sortPerm = Permutation.createSortPermutation(edits, compareBy(e => e.range, Range.compareRangesUsingStarts)); const sortedRanges = getNewRanges(sortPerm.apply(edits)); const ranges = sortPerm.inverse().apply(sortedRanges); const inverseEdits: ISingleEditOperation[] = []; diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts index dff43547e29..16c918fbe18 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts @@ -6,9 +6,9 @@ import * as assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; -import { generateRandomMultilineString as randomMultilineString } from 'vs/editor/contrib/inlineCompletions/test/browser/utils'; import { MersenneTwister, getRandomEditInfos, toEdit, } from 'vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test'; import { inverseEdits } from 'vs/editor/contrib/inlineCompletions/browser/utils'; +import { generateRandomMultilineString } from 'vs/editor/contrib/inlineCompletions/test/browser/utils'; suite('getNewRanges', () => { ensureNoDisposablesAreLeakedInTestSuite(); @@ -16,7 +16,7 @@ suite('getNewRanges', () => { for (let seed = 0; seed < 20; seed++) { test(`test ${seed}`, () => { const rng = new MersenneTwister(seed); - const randomText = randomMultilineString(rng, 10); + const randomText = generateRandomMultilineString(rng, 10); const model = createTextModel(randomText); const edits = getRandomEditInfos(model, rng.nextIntRange(1, 4), rng, true).map(e => toEdit(e)); diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts index 75bda0f3780..2770ea2aa33 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts @@ -133,6 +133,15 @@ export class GhostTextContext extends Disposable { } } +export function generateRandomMultilineString(rng: MersenneTwister, numberOfLines: number, maximumLengthOfLines: number = 20): string { + let randomText: string = ''; + for (let i = 0; i < numberOfLines; i++) { + const lengthOfLine = rng.nextIntRange(0, maximumLengthOfLines + 1); + randomText += generateRandomSimpleString(rng, lengthOfLine) + '\n'; + } + return randomText; +} + function generateRandomSimpleString(rng: MersenneTwister, stringLength: number): string { const possibleCharacters: string = ' abcdefghijklmnopqrstuvwxyz0123456789'; let randomText: string = ''; @@ -144,11 +153,3 @@ function generateRandomSimpleString(rng: MersenneTwister, stringLength: number): return randomText; } -export function generateRandomMultilineString(rng: MersenneTwister, numberOfLines: number, maximumLengthOfLines: number = 20): string { - let randomText: string = ''; - for (let i = 0; i < numberOfLines; i++) { - const lengthOfLine = rng.nextIntRange(0, maximumLengthOfLines + 1); - randomText += generateRandomSimpleString(rng, lengthOfLine) + '\n'; - } - return randomText; -} From e76bbf6236024b6589498e4c9f08bf0c9cf0a1a2 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 2 Feb 2024 11:39:33 +0100 Subject: [PATCH 0865/1897] adding enw lines in some places --- .../inlineCompletions/browser/inlineCompletionsModel.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index cf6cce08787..d212cde7175 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -307,7 +307,10 @@ export class InlineCompletionsModel extends Disposable { SnippetController2.get(editor)?.insert(completion.snippetInfo.snippet, { undoStopBefore: false }); } else { const edits = this._getEdits(editor, completion.toSingleTextEdit()); - editor.executeEdits('inlineSuggestion.accept', [...edits.edits.map(edit => EditOperation.replaceMove(edit.range, edit.text)), ...completion.additionalTextEdits]); + editor.executeEdits('inlineSuggestion.accept', [ + ...edits.edits.map(edit => EditOperation.replaceMove(edit.range, edit.text)), + ...completion.additionalTextEdits + ]); editor.setSelections(edits.editorSelections, 'inlineCompletionAccept'); } @@ -408,7 +411,9 @@ export class InlineCompletionsModel extends Disposable { try { editor.pushUndoStop(); const replaceRange = Range.fromPositions(completion.range.getStartPosition(), position); - const newText = completion.insertText.substring(0, firstPart.column - completion.range.startColumn + acceptUntilIndexExclusive); + const newText = completion.insertText.substring( + 0, + firstPart.column - completion.range.startColumn + acceptUntilIndexExclusive); const singleTextEdit = new SingleTextEdit(replaceRange, newText); const edits = this._getEdits(editor, singleTextEdit); editor.executeEdits('inlineSuggestion.accept', edits.edits.map(edit => EditOperation.replaceMove(edit.range, edit.text))); From 6959f5e4d7fb2f3df4d725e8ba9c48a600a0e2d1 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 2 Feb 2024 11:43:57 +0100 Subject: [PATCH 0866/1897] renamed the variables --- .../browser/inlineCompletionsModel.ts | 6 +++--- .../contrib/inlineCompletions/browser/utils.ts | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index d212cde7175..c031bdf51fc 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -459,9 +459,9 @@ export class InlineCompletionsModel extends Disposable { }) ]; const sortPerm = Permutation.createSortPermutation(edits, (edit1, edit2) => Range.compareRangesUsingStarts(edit1.range, edit2.range)); - const sortedRanges = getNewRanges(sortPerm.apply(edits)); - const ranges = sortPerm.inverse().apply(sortedRanges); - const editorSelections = ranges.map(range => Selection.fromPositions(range.getEndPosition())); + const sortedNewRanges = getNewRanges(sortPerm.apply(edits)); + const newRanges = sortPerm.inverse().apply(sortedNewRanges); + const editorSelections = newRanges.map(range => Selection.fromPositions(range.getEndPosition())); return { edits, diff --git a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts index 5f4118eaf80..cc24afd0eff 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts @@ -117,7 +117,7 @@ export function lengthOfText(text: string): Position { * @returns new ranges post edits for every edit */ export function getNewRanges(edits: ISingleEditOperation[]): Range[] { - const ranges: Range[] = []; + const newRanges: Range[] = []; let previousEditEndLineNumber = 0; let lineOffset = 0; let columnOffset = 0; @@ -125,20 +125,20 @@ export function getNewRanges(edits: ISingleEditOperation[]): Range[] { for (const edit of edits) { const text = edit.text ?? ''; const textLength = lengthOfText(text); - const rangeStart = Position.lift({ + const newRangeStart = Position.lift({ lineNumber: edit.range.startLineNumber + lineOffset, column: edit.range.startColumn + (edit.range.startLineNumber === previousEditEndLineNumber ? columnOffset : 0) }); - const rangeEnd = addPositions( - rangeStart, + const newRangeEnd = addPositions( + newRangeStart, textLength ); - ranges.push(Range.fromPositions(rangeStart, rangeEnd)); + newRanges.push(Range.fromPositions(newRangeStart, newRangeEnd)); lineOffset += textLength.lineNumber - edit.range.endLineNumber + edit.range.startLineNumber - 1; - columnOffset = rangeEnd.column - edit.range.endColumn; + columnOffset = newRangeEnd.column - edit.range.endColumn; previousEditEndLineNumber = edit.range.endLineNumber; } - return ranges; + return newRanges; } /** @@ -150,10 +150,10 @@ export function getNewRanges(edits: ISingleEditOperation[]): Range[] { export function inverseEdits(model: TextModel, edits: ISingleEditOperation[]): ISingleEditOperation[] { const sortPerm = Permutation.createSortPermutation(edits, compareBy(e => e.range, Range.compareRangesUsingStarts)); const sortedRanges = getNewRanges(sortPerm.apply(edits)); - const ranges = sortPerm.inverse().apply(sortedRanges); + const newRanges = sortPerm.inverse().apply(sortedRanges); const inverseEdits: ISingleEditOperation[] = []; for (let i = 0; i < edits.length; i++) { - inverseEdits.push({ range: ranges[i], text: model.getValueInRange(edits[i].range) }); + inverseEdits.push({ range: newRanges[i], text: model.getValueInRange(edits[i].range) }); } return inverseEdits; } From a54c7531d0beb18281d2dbc59f20f6ad54477fab Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 2 Feb 2024 11:47:17 +0100 Subject: [PATCH 0867/1897] :up: `esbuild@0.20.0` (#203626) --- build/package.json | 2 +- build/yarn.lock | 234 ++++++++++++++++++++-------------------- extensions/package.json | 2 +- extensions/yarn.lock | 234 ++++++++++++++++++++-------------------- 4 files changed, 242 insertions(+), 230 deletions(-) diff --git a/build/package.json b/build/package.json index 54c0bc8c586..b4ea8d06993 100644 --- a/build/package.json +++ b/build/package.json @@ -42,7 +42,7 @@ "commander": "^7.0.0", "debug": "^4.3.2", "electron-osx-sign": "^0.4.16", - "esbuild": "0.17.14", + "esbuild": "0.20.0", "extract-zip": "^2.0.1", "gulp-merge-json": "^2.1.1", "gulp-shell": "^0.8.0", diff --git a/build/yarn.lock b/build/yarn.lock index 4d2e6e75730..a0aca2e9595 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -238,115 +238,120 @@ optionalDependencies: global-agent "^3.0.0" -"@esbuild/android-arm64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.14.tgz#4624cea3c8941c91f9e9c1228f550d23f1cef037" - integrity sha512-eLOpPO1RvtsP71afiFTvS7tVFShJBCT0txiv/xjFBo5a7R7Gjw7X0IgIaFoLKhqXYAXhahoXm7qAmRXhY4guJg== +"@esbuild/aix-ppc64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.0.tgz#509621cca4e67caf0d18561a0c56f8b70237472f" + integrity sha512-fGFDEctNh0CcSwsiRPxiaqX0P5rq+AqE0SRhYGZ4PX46Lg1FNR6oCxJghf8YgY0WQEgQuh3lErUFE4KxLeRmmw== -"@esbuild/android-arm@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.14.tgz#74fae60fcab34c3f0e15cb56473a6091ba2b53a6" - integrity sha512-0CnlwnjDU8cks0yJLXfkaU/uoLyRf9VZJs4p1PskBr2AlAHeEsFEwJEo0of/Z3g+ilw5mpyDwThlxzNEIxOE4g== +"@esbuild/android-arm64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.0.tgz#109a6fdc4a2783fc26193d2687827045d8fef5ab" + integrity sha512-aVpnM4lURNkp0D3qPoAzSG92VXStYmoVPOgXveAUoQBWRSuQzt51yvSju29J6AHPmwY1BjH49uR29oyfH1ra8Q== -"@esbuild/android-x64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.14.tgz#f002fbc08d5e939d8314bd23bcfb1e95d029491f" - integrity sha512-nrfQYWBfLGfSGLvRVlt6xi63B5IbfHm3tZCdu/82zuFPQ7zez4XjmRtF/wIRYbJQ/DsZrxJdEvYFE67avYXyng== +"@esbuild/android-arm@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.0.tgz#1397a2c54c476c4799f9b9073550ede496c94ba5" + integrity sha512-3bMAfInvByLHfJwYPJRlpTeaQA75n8C/QKpEaiS4HrFWFiJlNI0vzq/zCjBrhAYcPyVPG7Eo9dMrcQXuqmNk5g== -"@esbuild/darwin-arm64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.14.tgz#b8dcd79a1dd19564950b4ca51d62999011e2e168" - integrity sha512-eoSjEuDsU1ROwgBH/c+fZzuSyJUVXQTOIN9xuLs9dE/9HbV/A5IqdXHU1p2OfIMwBwOYJ9SFVGGldxeRCUJFyw== +"@esbuild/android-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.0.tgz#2b615abefb50dc0a70ac313971102f4ce2fdb3ca" + integrity sha512-uK7wAnlRvjkCPzh8jJ+QejFyrP8ObKuR5cBIsQZ+qbMunwR8sbd8krmMbxTLSrDhiPZaJYKQAU5Y3iMDcZPhyQ== -"@esbuild/darwin-x64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.14.tgz#4b49f195d9473625efc3c773fc757018f2c0d979" - integrity sha512-zN0U8RWfrDttdFNkHqFYZtOH8hdi22z0pFm0aIJPsNC4QQZv7je8DWCX5iA4Zx6tRhS0CCc0XC2m7wKsbWEo5g== +"@esbuild/darwin-arm64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.0.tgz#5c122ed799eb0c35b9d571097f77254964c276a2" + integrity sha512-AjEcivGAlPs3UAcJedMa9qYg9eSfU6FnGHJjT8s346HSKkrcWlYezGE8VaO2xKfvvlZkgAhyvl06OJOxiMgOYQ== -"@esbuild/freebsd-arm64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.14.tgz#480923fd38f644c6342c55e916cc7c231a85eeb7" - integrity sha512-z0VcD4ibeZWVQCW1O7szaLxGsx54gcCnajEJMdYoYjLiq4g1jrP2lMq6pk71dbS5+7op/L2Aod+erw+EUr28/A== +"@esbuild/darwin-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.0.tgz#9561d277002ba8caf1524f209de2b22e93d170c1" + integrity sha512-bsgTPoyYDnPv8ER0HqnJggXK6RyFy4PH4rtsId0V7Efa90u2+EifxytE9pZnsDgExgkARy24WUQGv9irVbTvIw== -"@esbuild/freebsd-x64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.14.tgz#a6b6b01954ad8562461cb8a5e40e8a860af69cbe" - integrity sha512-hd9mPcxfTgJlolrPlcXkQk9BMwNBvNBsVaUe5eNUqXut6weDQH8whcNaKNF2RO8NbpT6GY8rHOK2A9y++s+ehw== +"@esbuild/freebsd-arm64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.0.tgz#84178986a3138e8500d17cc380044868176dd821" + integrity sha512-kQ7jYdlKS335mpGbMW5tEe3IrQFIok9r84EM3PXB8qBFJPSc6dpWfrtsC/y1pyrz82xfUIn5ZrnSHQQsd6jebQ== -"@esbuild/linux-arm64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.14.tgz#1fe2f39f78183b59f75a4ad9c48d079916d92418" - integrity sha512-FhAMNYOq3Iblcj9i+K0l1Fp/MHt+zBeRu/Qkf0LtrcFu3T45jcwB6A1iMsemQ42vR3GBhjNZJZTaCe3VFPbn9g== +"@esbuild/freebsd-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.0.tgz#3f9ce53344af2f08d178551cd475629147324a83" + integrity sha512-uG8B0WSepMRsBNVXAQcHf9+Ko/Tr+XqmK7Ptel9HVmnykupXdS4J7ovSQUIi0tQGIndhbqWLaIL/qO/cWhXKyQ== -"@esbuild/linux-arm@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.14.tgz#18d594a49b64e4a3a05022c005cb384a58056a2a" - integrity sha512-BNTl+wSJ1omsH8s3TkQmIIIQHwvwJrU9u1ggb9XU2KTVM4TmthRIVyxSp2qxROJHhZuW/r8fht46/QE8hU8Qvg== +"@esbuild/linux-arm64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.0.tgz#24efa685515689df4ecbc13031fa0a9dda910a11" + integrity sha512-uTtyYAP5veqi2z9b6Gr0NUoNv9F/rOzI8tOD5jKcCvRUn7T60Bb+42NDBCWNhMjkQzI0qqwXkQGo1SY41G52nw== -"@esbuild/linux-ia32@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.14.tgz#f7f0182a9cfc0159e0922ed66c805c9c6ef1b654" - integrity sha512-91OK/lQ5y2v7AsmnFT+0EyxdPTNhov3y2CWMdizyMfxSxRqHazXdzgBKtlmkU2KYIc+9ZK3Vwp2KyXogEATYxQ== +"@esbuild/linux-arm@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.0.tgz#6b586a488e02e9b073a75a957f2952b3b6e87b4c" + integrity sha512-2ezuhdiZw8vuHf1HKSf4TIk80naTbP9At7sOqZmdVwvvMyuoDiZB49YZKLsLOfKIr77+I40dWpHVeY5JHpIEIg== -"@esbuild/linux-loong64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.14.tgz#5f5305fdffe2d71dd9a97aa77d0c99c99409066f" - integrity sha512-vp15H+5NR6hubNgMluqqKza85HcGJgq7t6rMH7O3Y6ApiOWPkvW2AJfNojUQimfTp6OUrACUXfR4hmpcENXoMQ== +"@esbuild/linux-ia32@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.0.tgz#84ce7864f762708dcebc1b123898a397dea13624" + integrity sha512-c88wwtfs8tTffPaoJ+SQn3y+lKtgTzyjkD8NgsyCtCmtoIC8RDL7PrJU05an/e9VuAke6eJqGkoMhJK1RY6z4w== -"@esbuild/linux-mips64el@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.14.tgz#a602e85c51b2f71d2aedfe7f4143b2f92f97f3f5" - integrity sha512-90TOdFV7N+fgi6c2+GO9ochEkmm9kBAKnuD5e08GQMgMINOdOFHuYLPQ91RYVrnWwQ5683sJKuLi9l4SsbJ7Hg== +"@esbuild/linux-loong64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.0.tgz#1922f571f4cae1958e3ad29439c563f7d4fd9037" + integrity sha512-lR2rr/128/6svngnVta6JN4gxSXle/yZEZL3o4XZ6esOqhyR4wsKyfu6qXAL04S4S5CgGfG+GYZnjFd4YiG3Aw== -"@esbuild/linux-ppc64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.14.tgz#32d918d782105cbd9345dbfba14ee018b9c7afdf" - integrity sha512-NnBGeoqKkTugpBOBZZoktQQ1Yqb7aHKmHxsw43NddPB2YWLAlpb7THZIzsRsTr0Xw3nqiPxbA1H31ZMOG+VVPQ== +"@esbuild/linux-mips64el@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.0.tgz#7ca1bd9df3f874d18dbf46af009aebdb881188fe" + integrity sha512-9Sycc+1uUsDnJCelDf6ZNqgZQoK1mJvFtqf2MUz4ujTxGhvCWw+4chYfDLPepMEvVL9PDwn6HrXad5yOrNzIsQ== -"@esbuild/linux-riscv64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.14.tgz#38612e7b6c037dff7022c33f49ca17f85c5dec58" - integrity sha512-0qdlKScLXA8MGVy21JUKvMzCYWovctuP8KKqhtE5A6IVPq4onxXhSuhwDd2g5sRCzNDlDjitc5sX31BzDoL5Fw== +"@esbuild/linux-ppc64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.0.tgz#8f95baf05f9486343bceeb683703875d698708a4" + integrity sha512-CoWSaaAXOZd+CjbUTdXIJE/t7Oz+4g90A3VBCHLbfuc5yUQU/nFDLOzQsN0cdxgXd97lYW/psIIBdjzQIwTBGw== -"@esbuild/linux-s390x@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.14.tgz#4397dff354f899e72fd035d72af59a700c465ccb" - integrity sha512-Hdm2Jo1yaaOro4v3+6/zJk6ygCqIZuSDJHdHaf8nVH/tfOuoEX5Riv03Ka15LmQBYJObUTNS1UdyoMk0WUn9Ww== +"@esbuild/linux-riscv64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.0.tgz#ca63b921d5fe315e28610deb0c195e79b1a262ca" + integrity sha512-mlb1hg/eYRJUpv8h/x+4ShgoNLL8wgZ64SUr26KwglTYnwAWjkhR2GpoKftDbPOCnodA9t4Y/b68H4J9XmmPzA== -"@esbuild/linux-x64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.14.tgz#6c5cb99891b6c3e0c08369da3ef465e8038ad9c2" - integrity sha512-8KHF17OstlK4DuzeF/KmSgzrTWQrkWj5boluiiq7kvJCiQVzUrmSkaBvcLB2UgHpKENO2i6BthPkmUhNDaJsVw== +"@esbuild/linux-s390x@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.0.tgz#cb3d069f47dc202f785c997175f2307531371ef8" + integrity sha512-fgf9ubb53xSnOBqyvWEY6ukBNRl1mVX1srPNu06B6mNsNK20JfH6xV6jECzrQ69/VMiTLvHMicQR/PgTOgqJUQ== -"@esbuild/netbsd-x64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.14.tgz#5fa5255a64e9bf3947c1b3bef5e458b50b211994" - integrity sha512-nVwpqvb3yyXztxIT2+VsxJhB5GCgzPdk1n0HHSnchRAcxqKO6ghXwHhJnr0j/B+5FSyEqSxF4q03rbA2fKXtUQ== +"@esbuild/linux-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.0.tgz#ac617e0dc14e9758d3d7efd70288c14122557dc7" + integrity sha512-H9Eu6MGse++204XZcYsse1yFHmRXEWgadk2N58O/xd50P9EvFMLJTQLg+lB4E1cF2xhLZU5luSWtGTb0l9UeSg== -"@esbuild/openbsd-x64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.14.tgz#74d14c79dcb6faf446878cc64284aa4e02f5ca6f" - integrity sha512-1RZ7uQQ9zcy/GSAJL1xPdN7NDdOOtNEGiJalg/MOzeakZeTrgH/DoCkbq7TaPDiPhWqnDF+4bnydxRqQD7il6g== +"@esbuild/netbsd-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.0.tgz#6cc778567f1513da6e08060e0aeb41f82eb0f53c" + integrity sha512-lCT675rTN1v8Fo+RGrE5KjSnfY0x9Og4RN7t7lVrN3vMSjy34/+3na0q7RIfWDAj0e0rCh0OL+P88lu3Rt21MQ== -"@esbuild/sunos-x64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.14.tgz#5c7d1c7203781d86c2a9b2ff77bd2f8036d24cfa" - integrity sha512-nqMjDsFwv7vp7msrwWRysnM38Sd44PKmW8EzV01YzDBTcTWUpczQg6mGao9VLicXSgW/iookNK6AxeogNVNDZA== +"@esbuild/openbsd-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.0.tgz#76848bcf76b4372574fb4d06cd0ed1fb29ec0fbe" + integrity sha512-HKoUGXz/TOVXKQ+67NhxyHv+aDSZf44QpWLa3I1lLvAwGq8x1k0T+e2HHSRvxWhfJrFxaaqre1+YyzQ99KixoA== -"@esbuild/win32-arm64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.14.tgz#dc36ed84f1390e73b6019ccf0566c80045e5ca3d" - integrity sha512-xrD0mccTKRBBIotrITV7WVQAwNJ5+1va6L0H9zN92v2yEdjfAN7864cUaZwJS7JPEs53bDTzKFbfqVlG2HhyKQ== +"@esbuild/sunos-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.0.tgz#ea4cd0639bf294ad51bc08ffbb2dac297e9b4706" + integrity sha512-GDwAqgHQm1mVoPppGsoq4WJwT3vhnz/2N62CzhvApFD1eJyTroob30FPpOZabN+FgCjhG+AgcZyOPIkR8dfD7g== -"@esbuild/win32-ia32@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.14.tgz#0802a107afa9193c13e35de15a94fe347c588767" - integrity sha512-nXpkz9bbJrLLyUTYtRotSS3t5b+FOuljg8LgLdINWFs3FfqZMtbnBCZFUmBzQPyxqU87F8Av+3Nco/M3hEcu1w== +"@esbuild/win32-arm64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.0.tgz#a5c171e4a7f7e4e8be0e9947a65812c1535a7cf0" + integrity sha512-0vYsP8aC4TvMlOQYozoksiaxjlvUcQrac+muDqj1Fxy6jh9l9CZJzj7zmh8JGfiV49cYLTorFLxg7593pGldwQ== -"@esbuild/win32-x64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.14.tgz#e81fb49de05fed91bf74251c9ca0343f4fc77d31" - integrity sha512-gPQmsi2DKTaEgG14hc3CHXHp62k8g6qr0Pas+I4lUxRMugGSATh/Bi8Dgusoz9IQ0IfdrvLpco6kujEIBoaogA== +"@esbuild/win32-ia32@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.0.tgz#f8ac5650c412d33ea62d7551e0caf82da52b7f85" + integrity sha512-p98u4rIgfh4gdpV00IqknBD5pC84LCub+4a3MO+zjqvU5MVXOc3hqR2UgT2jI2nh3h8s9EQxmOsVI3tyzv1iFg== + +"@esbuild/win32-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.0.tgz#2efddf82828aac85e64cef62482af61c29561bee" + integrity sha512-NgJnesu1RtWihtTtXGFMU5YSE6JyyHPMxCwBZK7a6/8d31GuSo9l0Ss7w1Jw5QnKUawG6UEehs883kcXf5fYwg== "@malept/cross-spawn-promise@^1.1.0": version "1.1.1" @@ -1302,33 +1307,34 @@ es6-error@^4.1.1: resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== -esbuild@0.17.14: - version "0.17.14" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.14.tgz#d61a22de751a3133f3c6c7f9c1c3e231e91a3245" - integrity sha512-vOO5XhmVj/1XQR9NQ1UPq6qvMYL7QFJU57J5fKBKBKxp17uDt5PgxFDb4A2nEiXhr1qQs4x0F5+66hVVw4ruNw== +esbuild@0.20.0: + version "0.20.0" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.0.tgz#a7170b63447286cd2ff1f01579f09970e6965da4" + integrity sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA== optionalDependencies: - "@esbuild/android-arm" "0.17.14" - "@esbuild/android-arm64" "0.17.14" - "@esbuild/android-x64" "0.17.14" - "@esbuild/darwin-arm64" "0.17.14" - "@esbuild/darwin-x64" "0.17.14" - "@esbuild/freebsd-arm64" "0.17.14" - "@esbuild/freebsd-x64" "0.17.14" - "@esbuild/linux-arm" "0.17.14" - "@esbuild/linux-arm64" "0.17.14" - "@esbuild/linux-ia32" "0.17.14" - "@esbuild/linux-loong64" "0.17.14" - "@esbuild/linux-mips64el" "0.17.14" - "@esbuild/linux-ppc64" "0.17.14" - "@esbuild/linux-riscv64" "0.17.14" - "@esbuild/linux-s390x" "0.17.14" - "@esbuild/linux-x64" "0.17.14" - "@esbuild/netbsd-x64" "0.17.14" - "@esbuild/openbsd-x64" "0.17.14" - "@esbuild/sunos-x64" "0.17.14" - "@esbuild/win32-arm64" "0.17.14" - "@esbuild/win32-ia32" "0.17.14" - "@esbuild/win32-x64" "0.17.14" + "@esbuild/aix-ppc64" "0.20.0" + "@esbuild/android-arm" "0.20.0" + "@esbuild/android-arm64" "0.20.0" + "@esbuild/android-x64" "0.20.0" + "@esbuild/darwin-arm64" "0.20.0" + "@esbuild/darwin-x64" "0.20.0" + "@esbuild/freebsd-arm64" "0.20.0" + "@esbuild/freebsd-x64" "0.20.0" + "@esbuild/linux-arm" "0.20.0" + "@esbuild/linux-arm64" "0.20.0" + "@esbuild/linux-ia32" "0.20.0" + "@esbuild/linux-loong64" "0.20.0" + "@esbuild/linux-mips64el" "0.20.0" + "@esbuild/linux-ppc64" "0.20.0" + "@esbuild/linux-riscv64" "0.20.0" + "@esbuild/linux-s390x" "0.20.0" + "@esbuild/linux-x64" "0.20.0" + "@esbuild/netbsd-x64" "0.20.0" + "@esbuild/openbsd-x64" "0.20.0" + "@esbuild/sunos-x64" "0.20.0" + "@esbuild/win32-arm64" "0.20.0" + "@esbuild/win32-ia32" "0.20.0" + "@esbuild/win32-x64" "0.20.0" escape-string-regexp@^1.0.5: version "1.0.5" diff --git a/extensions/package.json b/extensions/package.json index 8bd2f6d07ce..4365c20acc1 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -11,7 +11,7 @@ }, "devDependencies": { "@parcel/watcher": "2.1.0", - "esbuild": "0.17.14", + "esbuild": "0.20.0", "vscode-grammar-updater": "^1.1.0" } } diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 8f166d8e04e..f4543f6a1c9 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,115 +2,120 @@ # yarn lockfile v1 -"@esbuild/android-arm64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.14.tgz#4624cea3c8941c91f9e9c1228f550d23f1cef037" - integrity sha512-eLOpPO1RvtsP71afiFTvS7tVFShJBCT0txiv/xjFBo5a7R7Gjw7X0IgIaFoLKhqXYAXhahoXm7qAmRXhY4guJg== +"@esbuild/aix-ppc64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.0.tgz#509621cca4e67caf0d18561a0c56f8b70237472f" + integrity sha512-fGFDEctNh0CcSwsiRPxiaqX0P5rq+AqE0SRhYGZ4PX46Lg1FNR6oCxJghf8YgY0WQEgQuh3lErUFE4KxLeRmmw== -"@esbuild/android-arm@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.14.tgz#74fae60fcab34c3f0e15cb56473a6091ba2b53a6" - integrity sha512-0CnlwnjDU8cks0yJLXfkaU/uoLyRf9VZJs4p1PskBr2AlAHeEsFEwJEo0of/Z3g+ilw5mpyDwThlxzNEIxOE4g== +"@esbuild/android-arm64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.0.tgz#109a6fdc4a2783fc26193d2687827045d8fef5ab" + integrity sha512-aVpnM4lURNkp0D3qPoAzSG92VXStYmoVPOgXveAUoQBWRSuQzt51yvSju29J6AHPmwY1BjH49uR29oyfH1ra8Q== -"@esbuild/android-x64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.14.tgz#f002fbc08d5e939d8314bd23bcfb1e95d029491f" - integrity sha512-nrfQYWBfLGfSGLvRVlt6xi63B5IbfHm3tZCdu/82zuFPQ7zez4XjmRtF/wIRYbJQ/DsZrxJdEvYFE67avYXyng== +"@esbuild/android-arm@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.0.tgz#1397a2c54c476c4799f9b9073550ede496c94ba5" + integrity sha512-3bMAfInvByLHfJwYPJRlpTeaQA75n8C/QKpEaiS4HrFWFiJlNI0vzq/zCjBrhAYcPyVPG7Eo9dMrcQXuqmNk5g== -"@esbuild/darwin-arm64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.14.tgz#b8dcd79a1dd19564950b4ca51d62999011e2e168" - integrity sha512-eoSjEuDsU1ROwgBH/c+fZzuSyJUVXQTOIN9xuLs9dE/9HbV/A5IqdXHU1p2OfIMwBwOYJ9SFVGGldxeRCUJFyw== +"@esbuild/android-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.0.tgz#2b615abefb50dc0a70ac313971102f4ce2fdb3ca" + integrity sha512-uK7wAnlRvjkCPzh8jJ+QejFyrP8ObKuR5cBIsQZ+qbMunwR8sbd8krmMbxTLSrDhiPZaJYKQAU5Y3iMDcZPhyQ== -"@esbuild/darwin-x64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.14.tgz#4b49f195d9473625efc3c773fc757018f2c0d979" - integrity sha512-zN0U8RWfrDttdFNkHqFYZtOH8hdi22z0pFm0aIJPsNC4QQZv7je8DWCX5iA4Zx6tRhS0CCc0XC2m7wKsbWEo5g== +"@esbuild/darwin-arm64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.0.tgz#5c122ed799eb0c35b9d571097f77254964c276a2" + integrity sha512-AjEcivGAlPs3UAcJedMa9qYg9eSfU6FnGHJjT8s346HSKkrcWlYezGE8VaO2xKfvvlZkgAhyvl06OJOxiMgOYQ== -"@esbuild/freebsd-arm64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.14.tgz#480923fd38f644c6342c55e916cc7c231a85eeb7" - integrity sha512-z0VcD4ibeZWVQCW1O7szaLxGsx54gcCnajEJMdYoYjLiq4g1jrP2lMq6pk71dbS5+7op/L2Aod+erw+EUr28/A== +"@esbuild/darwin-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.0.tgz#9561d277002ba8caf1524f209de2b22e93d170c1" + integrity sha512-bsgTPoyYDnPv8ER0HqnJggXK6RyFy4PH4rtsId0V7Efa90u2+EifxytE9pZnsDgExgkARy24WUQGv9irVbTvIw== -"@esbuild/freebsd-x64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.14.tgz#a6b6b01954ad8562461cb8a5e40e8a860af69cbe" - integrity sha512-hd9mPcxfTgJlolrPlcXkQk9BMwNBvNBsVaUe5eNUqXut6weDQH8whcNaKNF2RO8NbpT6GY8rHOK2A9y++s+ehw== +"@esbuild/freebsd-arm64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.0.tgz#84178986a3138e8500d17cc380044868176dd821" + integrity sha512-kQ7jYdlKS335mpGbMW5tEe3IrQFIok9r84EM3PXB8qBFJPSc6dpWfrtsC/y1pyrz82xfUIn5ZrnSHQQsd6jebQ== -"@esbuild/linux-arm64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.14.tgz#1fe2f39f78183b59f75a4ad9c48d079916d92418" - integrity sha512-FhAMNYOq3Iblcj9i+K0l1Fp/MHt+zBeRu/Qkf0LtrcFu3T45jcwB6A1iMsemQ42vR3GBhjNZJZTaCe3VFPbn9g== +"@esbuild/freebsd-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.0.tgz#3f9ce53344af2f08d178551cd475629147324a83" + integrity sha512-uG8B0WSepMRsBNVXAQcHf9+Ko/Tr+XqmK7Ptel9HVmnykupXdS4J7ovSQUIi0tQGIndhbqWLaIL/qO/cWhXKyQ== -"@esbuild/linux-arm@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.14.tgz#18d594a49b64e4a3a05022c005cb384a58056a2a" - integrity sha512-BNTl+wSJ1omsH8s3TkQmIIIQHwvwJrU9u1ggb9XU2KTVM4TmthRIVyxSp2qxROJHhZuW/r8fht46/QE8hU8Qvg== +"@esbuild/linux-arm64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.0.tgz#24efa685515689df4ecbc13031fa0a9dda910a11" + integrity sha512-uTtyYAP5veqi2z9b6Gr0NUoNv9F/rOzI8tOD5jKcCvRUn7T60Bb+42NDBCWNhMjkQzI0qqwXkQGo1SY41G52nw== -"@esbuild/linux-ia32@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.14.tgz#f7f0182a9cfc0159e0922ed66c805c9c6ef1b654" - integrity sha512-91OK/lQ5y2v7AsmnFT+0EyxdPTNhov3y2CWMdizyMfxSxRqHazXdzgBKtlmkU2KYIc+9ZK3Vwp2KyXogEATYxQ== +"@esbuild/linux-arm@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.0.tgz#6b586a488e02e9b073a75a957f2952b3b6e87b4c" + integrity sha512-2ezuhdiZw8vuHf1HKSf4TIk80naTbP9At7sOqZmdVwvvMyuoDiZB49YZKLsLOfKIr77+I40dWpHVeY5JHpIEIg== -"@esbuild/linux-loong64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.14.tgz#5f5305fdffe2d71dd9a97aa77d0c99c99409066f" - integrity sha512-vp15H+5NR6hubNgMluqqKza85HcGJgq7t6rMH7O3Y6ApiOWPkvW2AJfNojUQimfTp6OUrACUXfR4hmpcENXoMQ== +"@esbuild/linux-ia32@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.0.tgz#84ce7864f762708dcebc1b123898a397dea13624" + integrity sha512-c88wwtfs8tTffPaoJ+SQn3y+lKtgTzyjkD8NgsyCtCmtoIC8RDL7PrJU05an/e9VuAke6eJqGkoMhJK1RY6z4w== -"@esbuild/linux-mips64el@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.14.tgz#a602e85c51b2f71d2aedfe7f4143b2f92f97f3f5" - integrity sha512-90TOdFV7N+fgi6c2+GO9ochEkmm9kBAKnuD5e08GQMgMINOdOFHuYLPQ91RYVrnWwQ5683sJKuLi9l4SsbJ7Hg== +"@esbuild/linux-loong64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.0.tgz#1922f571f4cae1958e3ad29439c563f7d4fd9037" + integrity sha512-lR2rr/128/6svngnVta6JN4gxSXle/yZEZL3o4XZ6esOqhyR4wsKyfu6qXAL04S4S5CgGfG+GYZnjFd4YiG3Aw== -"@esbuild/linux-ppc64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.14.tgz#32d918d782105cbd9345dbfba14ee018b9c7afdf" - integrity sha512-NnBGeoqKkTugpBOBZZoktQQ1Yqb7aHKmHxsw43NddPB2YWLAlpb7THZIzsRsTr0Xw3nqiPxbA1H31ZMOG+VVPQ== +"@esbuild/linux-mips64el@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.0.tgz#7ca1bd9df3f874d18dbf46af009aebdb881188fe" + integrity sha512-9Sycc+1uUsDnJCelDf6ZNqgZQoK1mJvFtqf2MUz4ujTxGhvCWw+4chYfDLPepMEvVL9PDwn6HrXad5yOrNzIsQ== -"@esbuild/linux-riscv64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.14.tgz#38612e7b6c037dff7022c33f49ca17f85c5dec58" - integrity sha512-0qdlKScLXA8MGVy21JUKvMzCYWovctuP8KKqhtE5A6IVPq4onxXhSuhwDd2g5sRCzNDlDjitc5sX31BzDoL5Fw== +"@esbuild/linux-ppc64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.0.tgz#8f95baf05f9486343bceeb683703875d698708a4" + integrity sha512-CoWSaaAXOZd+CjbUTdXIJE/t7Oz+4g90A3VBCHLbfuc5yUQU/nFDLOzQsN0cdxgXd97lYW/psIIBdjzQIwTBGw== -"@esbuild/linux-s390x@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.14.tgz#4397dff354f899e72fd035d72af59a700c465ccb" - integrity sha512-Hdm2Jo1yaaOro4v3+6/zJk6ygCqIZuSDJHdHaf8nVH/tfOuoEX5Riv03Ka15LmQBYJObUTNS1UdyoMk0WUn9Ww== +"@esbuild/linux-riscv64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.0.tgz#ca63b921d5fe315e28610deb0c195e79b1a262ca" + integrity sha512-mlb1hg/eYRJUpv8h/x+4ShgoNLL8wgZ64SUr26KwglTYnwAWjkhR2GpoKftDbPOCnodA9t4Y/b68H4J9XmmPzA== -"@esbuild/linux-x64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.14.tgz#6c5cb99891b6c3e0c08369da3ef465e8038ad9c2" - integrity sha512-8KHF17OstlK4DuzeF/KmSgzrTWQrkWj5boluiiq7kvJCiQVzUrmSkaBvcLB2UgHpKENO2i6BthPkmUhNDaJsVw== +"@esbuild/linux-s390x@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.0.tgz#cb3d069f47dc202f785c997175f2307531371ef8" + integrity sha512-fgf9ubb53xSnOBqyvWEY6ukBNRl1mVX1srPNu06B6mNsNK20JfH6xV6jECzrQ69/VMiTLvHMicQR/PgTOgqJUQ== -"@esbuild/netbsd-x64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.14.tgz#5fa5255a64e9bf3947c1b3bef5e458b50b211994" - integrity sha512-nVwpqvb3yyXztxIT2+VsxJhB5GCgzPdk1n0HHSnchRAcxqKO6ghXwHhJnr0j/B+5FSyEqSxF4q03rbA2fKXtUQ== +"@esbuild/linux-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.0.tgz#ac617e0dc14e9758d3d7efd70288c14122557dc7" + integrity sha512-H9Eu6MGse++204XZcYsse1yFHmRXEWgadk2N58O/xd50P9EvFMLJTQLg+lB4E1cF2xhLZU5luSWtGTb0l9UeSg== -"@esbuild/openbsd-x64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.14.tgz#74d14c79dcb6faf446878cc64284aa4e02f5ca6f" - integrity sha512-1RZ7uQQ9zcy/GSAJL1xPdN7NDdOOtNEGiJalg/MOzeakZeTrgH/DoCkbq7TaPDiPhWqnDF+4bnydxRqQD7il6g== +"@esbuild/netbsd-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.0.tgz#6cc778567f1513da6e08060e0aeb41f82eb0f53c" + integrity sha512-lCT675rTN1v8Fo+RGrE5KjSnfY0x9Og4RN7t7lVrN3vMSjy34/+3na0q7RIfWDAj0e0rCh0OL+P88lu3Rt21MQ== -"@esbuild/sunos-x64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.14.tgz#5c7d1c7203781d86c2a9b2ff77bd2f8036d24cfa" - integrity sha512-nqMjDsFwv7vp7msrwWRysnM38Sd44PKmW8EzV01YzDBTcTWUpczQg6mGao9VLicXSgW/iookNK6AxeogNVNDZA== +"@esbuild/openbsd-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.0.tgz#76848bcf76b4372574fb4d06cd0ed1fb29ec0fbe" + integrity sha512-HKoUGXz/TOVXKQ+67NhxyHv+aDSZf44QpWLa3I1lLvAwGq8x1k0T+e2HHSRvxWhfJrFxaaqre1+YyzQ99KixoA== -"@esbuild/win32-arm64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.14.tgz#dc36ed84f1390e73b6019ccf0566c80045e5ca3d" - integrity sha512-xrD0mccTKRBBIotrITV7WVQAwNJ5+1va6L0H9zN92v2yEdjfAN7864cUaZwJS7JPEs53bDTzKFbfqVlG2HhyKQ== +"@esbuild/sunos-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.0.tgz#ea4cd0639bf294ad51bc08ffbb2dac297e9b4706" + integrity sha512-GDwAqgHQm1mVoPppGsoq4WJwT3vhnz/2N62CzhvApFD1eJyTroob30FPpOZabN+FgCjhG+AgcZyOPIkR8dfD7g== -"@esbuild/win32-ia32@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.14.tgz#0802a107afa9193c13e35de15a94fe347c588767" - integrity sha512-nXpkz9bbJrLLyUTYtRotSS3t5b+FOuljg8LgLdINWFs3FfqZMtbnBCZFUmBzQPyxqU87F8Av+3Nco/M3hEcu1w== +"@esbuild/win32-arm64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.0.tgz#a5c171e4a7f7e4e8be0e9947a65812c1535a7cf0" + integrity sha512-0vYsP8aC4TvMlOQYozoksiaxjlvUcQrac+muDqj1Fxy6jh9l9CZJzj7zmh8JGfiV49cYLTorFLxg7593pGldwQ== -"@esbuild/win32-x64@0.17.14": - version "0.17.14" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.14.tgz#e81fb49de05fed91bf74251c9ca0343f4fc77d31" - integrity sha512-gPQmsi2DKTaEgG14hc3CHXHp62k8g6qr0Pas+I4lUxRMugGSATh/Bi8Dgusoz9IQ0IfdrvLpco6kujEIBoaogA== +"@esbuild/win32-ia32@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.0.tgz#f8ac5650c412d33ea62d7551e0caf82da52b7f85" + integrity sha512-p98u4rIgfh4gdpV00IqknBD5pC84LCub+4a3MO+zjqvU5MVXOc3hqR2UgT2jI2nh3h8s9EQxmOsVI3tyzv1iFg== + +"@esbuild/win32-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.0.tgz#2efddf82828aac85e64cef62482af61c29561bee" + integrity sha512-NgJnesu1RtWihtTtXGFMU5YSE6JyyHPMxCwBZK7a6/8d31GuSo9l0Ss7w1Jw5QnKUawG6UEehs883kcXf5fYwg== "@parcel/watcher@2.1.0": version "2.1.0" @@ -141,33 +146,34 @@ cson-parser@^4.0.9: dependencies: coffeescript "1.12.7" -esbuild@0.17.14: - version "0.17.14" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.14.tgz#d61a22de751a3133f3c6c7f9c1c3e231e91a3245" - integrity sha512-vOO5XhmVj/1XQR9NQ1UPq6qvMYL7QFJU57J5fKBKBKxp17uDt5PgxFDb4A2nEiXhr1qQs4x0F5+66hVVw4ruNw== +esbuild@0.20.0: + version "0.20.0" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.0.tgz#a7170b63447286cd2ff1f01579f09970e6965da4" + integrity sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA== optionalDependencies: - "@esbuild/android-arm" "0.17.14" - "@esbuild/android-arm64" "0.17.14" - "@esbuild/android-x64" "0.17.14" - "@esbuild/darwin-arm64" "0.17.14" - "@esbuild/darwin-x64" "0.17.14" - "@esbuild/freebsd-arm64" "0.17.14" - "@esbuild/freebsd-x64" "0.17.14" - "@esbuild/linux-arm" "0.17.14" - "@esbuild/linux-arm64" "0.17.14" - "@esbuild/linux-ia32" "0.17.14" - "@esbuild/linux-loong64" "0.17.14" - "@esbuild/linux-mips64el" "0.17.14" - "@esbuild/linux-ppc64" "0.17.14" - "@esbuild/linux-riscv64" "0.17.14" - "@esbuild/linux-s390x" "0.17.14" - "@esbuild/linux-x64" "0.17.14" - "@esbuild/netbsd-x64" "0.17.14" - "@esbuild/openbsd-x64" "0.17.14" - "@esbuild/sunos-x64" "0.17.14" - "@esbuild/win32-arm64" "0.17.14" - "@esbuild/win32-ia32" "0.17.14" - "@esbuild/win32-x64" "0.17.14" + "@esbuild/aix-ppc64" "0.20.0" + "@esbuild/android-arm" "0.20.0" + "@esbuild/android-arm64" "0.20.0" + "@esbuild/android-x64" "0.20.0" + "@esbuild/darwin-arm64" "0.20.0" + "@esbuild/darwin-x64" "0.20.0" + "@esbuild/freebsd-arm64" "0.20.0" + "@esbuild/freebsd-x64" "0.20.0" + "@esbuild/linux-arm" "0.20.0" + "@esbuild/linux-arm64" "0.20.0" + "@esbuild/linux-ia32" "0.20.0" + "@esbuild/linux-loong64" "0.20.0" + "@esbuild/linux-mips64el" "0.20.0" + "@esbuild/linux-ppc64" "0.20.0" + "@esbuild/linux-riscv64" "0.20.0" + "@esbuild/linux-s390x" "0.20.0" + "@esbuild/linux-x64" "0.20.0" + "@esbuild/netbsd-x64" "0.20.0" + "@esbuild/openbsd-x64" "0.20.0" + "@esbuild/sunos-x64" "0.20.0" + "@esbuild/win32-arm64" "0.20.0" + "@esbuild/win32-ia32" "0.20.0" + "@esbuild/win32-x64" "0.20.0" fast-plist@0.1.2: version "0.1.2" From 942ed9acd7826135e3e0e42be6a9c827d4a2e1c1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 2 Feb 2024 12:32:38 +0100 Subject: [PATCH 0868/1897] dialogs contribution (#203947) (#204118) --- .../parts/dialogs/dialog.web.contribution.ts | 19 +++++++----- .../parts/dialogs/dialog.contribution.ts | 29 +++++++++++-------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts index b69c8db3b30..a5794373124 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts @@ -15,13 +15,14 @@ import { BrowserDialogHandler } from 'vs/workbench/browser/parts/dialogs/dialogH import { DialogService } from 'vs/workbench/services/dialogs/common/dialogService'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { Lazy } from 'vs/base/common/lazy'; export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.dialogHandler'; private readonly model: IDialogsModel; - private readonly impl: IDialogHandler; + private readonly impl: Lazy; private currentDialog: IDialogViewItem | undefined; @@ -36,7 +37,7 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC ) { super(); - this.impl = new BrowserDialogHandler(logService, layoutService, keybindingService, instantiationService, productService, clipboardService); + this.impl = new Lazy(() => new BrowserDialogHandler(logService, layoutService, keybindingService, instantiationService, productService, clipboardService)); this.model = (this.dialogService as DialogService).model; @@ -57,15 +58,15 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC try { if (this.currentDialog.args.confirmArgs) { const args = this.currentDialog.args.confirmArgs; - result = await this.impl.confirm(args.confirmation); + result = await this.impl.value.confirm(args.confirmation); } else if (this.currentDialog.args.inputArgs) { const args = this.currentDialog.args.inputArgs; - result = await this.impl.input(args.input); + result = await this.impl.value.input(args.input); } else if (this.currentDialog.args.promptArgs) { const args = this.currentDialog.args.promptArgs; - result = await this.impl.prompt(args.prompt); + result = await this.impl.value.prompt(args.prompt); } else { - await this.impl.about(); + await this.impl.value.about(); } } catch (error) { result = error; @@ -77,4 +78,8 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC } } -registerWorkbenchContribution2(DialogHandlerContribution.ID, DialogHandlerContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2( + DialogHandlerContribution.ID, + DialogHandlerContribution, + WorkbenchContributionInstantiation.BlockStartup // Block to allow for dialogs to show before restore finished +); diff --git a/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts b/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts index 2eb7806421f..ddbe230324c 100644 --- a/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts +++ b/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts @@ -18,13 +18,14 @@ import { NativeDialogHandler } from 'vs/workbench/electron-sandbox/parts/dialogs import { DialogService } from 'vs/workbench/services/dialogs/common/dialogService'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { Lazy } from 'vs/base/common/lazy'; export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.dialogHandler'; - private nativeImpl: IDialogHandler; - private browserImpl: IDialogHandler; + private nativeImpl: Lazy; + private browserImpl: Lazy; private model: IDialogsModel; private currentDialog: IDialogViewItem | undefined; @@ -42,8 +43,8 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC ) { super(); - this.browserImpl = new BrowserDialogHandler(logService, layoutService, keybindingService, instantiationService, productService, clipboardService); - this.nativeImpl = new NativeDialogHandler(logService, nativeHostService, productService, clipboardService); + this.browserImpl = new Lazy(() => new BrowserDialogHandler(logService, layoutService, keybindingService, instantiationService, productService, clipboardService)); + this.nativeImpl = new Lazy(() => new NativeDialogHandler(logService, nativeHostService, productService, clipboardService)); this.model = (this.dialogService as DialogService).model; @@ -67,30 +68,30 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC if (this.currentDialog.args.confirmArgs) { const args = this.currentDialog.args.confirmArgs; result = (this.useCustomDialog || args?.confirmation.custom) ? - await this.browserImpl.confirm(args.confirmation) : - await this.nativeImpl.confirm(args.confirmation); + await this.browserImpl.value.confirm(args.confirmation) : + await this.nativeImpl.value.confirm(args.confirmation); } // Input (custom only) else if (this.currentDialog.args.inputArgs) { const args = this.currentDialog.args.inputArgs; - result = await this.browserImpl.input(args.input); + result = await this.browserImpl.value.input(args.input); } // Prompt else if (this.currentDialog.args.promptArgs) { const args = this.currentDialog.args.promptArgs; result = (this.useCustomDialog || args?.prompt.custom) ? - await this.browserImpl.prompt(args.prompt) : - await this.nativeImpl.prompt(args.prompt); + await this.browserImpl.value.prompt(args.prompt) : + await this.nativeImpl.value.prompt(args.prompt); } // About else { if (this.useCustomDialog) { - await this.browserImpl.about(); + await this.browserImpl.value.about(); } else { - await this.nativeImpl.about(); + await this.nativeImpl.value.about(); } } } catch (error) { @@ -107,4 +108,8 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC } } -registerWorkbenchContribution2(DialogHandlerContribution.ID, DialogHandlerContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2( + DialogHandlerContribution.ID, + DialogHandlerContribution, + WorkbenchContributionInstantiation.BlockStartup // Block to allow for dialogs to show before restore finished +); From 22a578f53b2f8edb4ab07078d32d142bc543d651 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 2 Feb 2024 12:27:15 +0100 Subject: [PATCH 0869/1897] Uses status bar to indicate hot reloading --- scripts/debugger-scripts-api.d.ts | 21 ++- scripts/hot-reload-injected-script.js | 258 +++++++++++++++++++++++--- src/vs/base/common/hotReload.ts | 24 ++- 3 files changed, 267 insertions(+), 36 deletions(-) diff --git a/scripts/debugger-scripts-api.d.ts b/scripts/debugger-scripts-api.d.ts index 0a06ad925e0..b101855f4d0 100644 --- a/scripts/debugger-scripts-api.d.ts +++ b/scripts/debugger-scripts-api.d.ts @@ -3,20 +3,33 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -type RunFunction = ((debugSession: IDebugSession) => IDisposable) | ((debugSession: IDebugSession) => Promise); +type RunFunction = + | ((debugSession: IDebugSession, context: Context) => IDisposable) + | ((debugSession: IDebugSession, context: Context) => Promise); interface IDebugSession { name: string; - eval(expression: string): Promise; - evalJs(bodyFn: (...args: T) => void, ...args: T): Promise; + eval(expression: string): Promise; + evalJs( + bodyFn: (...args: T) => TResult, + ...args: T + ): Promise; +} + +interface Context { + vscode: typeof import('vscode'); } interface IDisposable { dispose(): void; } +interface HotReloadConfig { + mode?: 'patch-prototype' | undefined; +} + interface GlobalThisAddition { - $hotReload_applyNewExports?(args: { oldExports: Record; newSrc: string }): AcceptNewExportsFn | undefined; + $hotReload_applyNewExports?(args: { oldExports: Record; newSrc: string; config?: HotReloadConfig }): AcceptNewExportsFn | undefined; } type AcceptNewExportsFn = (newExports: Record) => boolean; diff --git a/scripts/hot-reload-injected-script.js b/scripts/hot-reload-injected-script.js index 8d082f1a139..431f11b6a66 100644 --- a/scripts/hot-reload-injected-script.js +++ b/scripts/hot-reload-injected-script.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // @ts-check +/// /// const path = require('path'); @@ -12,28 +13,198 @@ const parcelWatcher = require('@parcel/watcher'); // This file is loaded by the vscode-diagnostic-tools extension and injected into the debugger. -/** @type {RunFunction} */ -module.exports.run = async function (debugSession) { - const watcher = await DirWatcher.watchRecursively(path.join(__dirname, '../out/')); - const sub = watcher.onDidChange(changes => { - const supportedChanges = changes.filter(c => c.path.endsWith('.js') || c.path.endsWith('.css')); - debugSession.evalJs(function (changes, debugSessionName) { +/** + * Represents a lazy evaluation container. + * @template T + * @template TArg + */ +class Lazy { + /** + * Creates a new instance of the Lazy class. + * @param {(arg: TArg) => T} _fn - The function to be lazily evaluated. + */ + constructor(_fn) { + this._fn = _fn; + this._value = undefined; + } + + /** + * Gets the lazily evaluated value. + * @param {TArg} arg - The argument passed in to the evaluation function. + * @return {T} + */ + getValue(arg) { + if (!this._value) { + this._value = this._fn(arg); + } + return this._value; + } +} + +/** + * @param {Context['vscode']} vscode + */ +function setupGlobals(vscode) { + /** @type {DisposableStore} */ + const store = globalThis['hot-reload-injected-script-disposables'] ?? (globalThis['hot-reload-injected-script-disposables'] = new DisposableStore()); + store.clear(); + + function getConfig() { + const config = vscode.workspace.getConfiguration('vscode-diagnostic-tools').get('debuggerScriptsConfig', { + 'hotReload.sources': {} + }); + if (!config['hotReload.sources']) { + config['hotReload.sources'] = {}; + } + return config; + } + + /** + * @type {Map void>>} + */ + const enabledRelativePaths = new Map(); + const api = { + /** + * @param {string} relativePath + * @param {() => void} forceReloadFn + */ + reloadFailed: (relativePath, forceReloadFn) => { + const set = enabledRelativePaths.get(relativePath) ?? new Set(); + set.add(forceReloadFn); + enabledRelativePaths.set(relativePath, set); + + update(); + }, + + /** + * @param {string} relativePath + * @returns {HotReloadConfig} + */ + getConfig: (relativePath) => { + const config = getConfig(); + return { mode: config['hotReload.sources'][relativePath] === 'patch-prototype' ? 'patch-prototype' : undefined }; + } + }; + + const item = store.add(vscode.window.createStatusBarItem(undefined, 10000)); + + function update() { + item.hide(); + const e = vscode.window.activeTextEditor; + if (!e) { return; } + + const part = e.document.fileName.replace(/\\/g, '/').replace(/\.ts/, '.js').split('/src/')[1]; + if (!part) { return; } + + const isEnabled = api.getConfig(part)?.mode === 'patch-prototype'; + + if (!enabledRelativePaths.has(part) && !isEnabled) { + return; + } + + if (!isEnabled) { + item.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground'); + item.text = '$(sync-ignored) hot reload disabled'; + } else { + item.backgroundColor = undefined; + item.text = '$(sync) hot reload enabled'; + } + + item.command = { + command: 'vscode-diagnostic-tools.hotReload.toggle', + title: 'Toggle hot reload', + arguments: [part], + tooltip: 'Toggle hot reload' + }; + item.tooltip = 'Toggle hot reload'; + item.show(); + } + + store.add(vscode.window.onDidChangeActiveTextEditor(e => { + update(); + })); + + store.add(vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('vscode-diagnostic-tools.debuggerScriptsConfig')) { + update(); + } + })); + + update(); + + store.add(vscode.commands.registerCommand('vscode-diagnostic-tools.hotReload.toggle', async (relativePath) => { + let config = getConfig(); + const current = config['hotReload.sources'][relativePath]; + const newValue = current === 'patch-prototype' ? undefined : 'patch-prototype'; + config = { ...config, 'hotReload.sources': { ...config['hotReload.sources'], [relativePath]: newValue } }; + + await vscode.workspace.getConfiguration('vscode-diagnostic-tools').update('debuggerScriptsConfig', config, vscode.ConfigurationTarget.Global); + + if (newValue === 'patch-prototype') { + const reloadFns = enabledRelativePaths.get(relativePath); + console.log(reloadFns); + if (reloadFns) { + for (const fn of reloadFns) { + fn(); + } + } + } + })); + + return api; +} + +const g = new Lazy(setupGlobals); + +/** @type {RunFunction} */ +module.exports.run = async function (debugSession, ctx) { + const store = new DisposableStore(); + + const global = ctx.vscode ? g.getValue(ctx.vscode) : undefined; + + const watcher = store.add(await DirWatcher.watchRecursively(path.join(__dirname, '../out/'))); + + /** + * So that the same file always gets the same reload fn. + * @type {Map void>} + */ + const reloadFns = new Map(); + + store.add(watcher.onDidChange(async changes => { + const supportedChanges = changes + .filter(c => c.path.endsWith('.js') || c.path.endsWith('.css')) + .map(c => { + const relativePath = c.path.replace(/\\/g, '/').split('/out/')[1]; + return { ...c, relativePath, config: global?.getConfig(relativePath) }; + }); + + const result = await debugSession.evalJs(function (changes, debugSessionName) { // This function is stringified and injected into the debuggee. /** @type {{ count: number; originalWindowTitle: any; timeout: any; shouldReload: boolean }} */ const hotReloadData = globalThis.$hotReloadData || (globalThis.$hotReloadData = { count: 0, messageHideTimeout: undefined, shouldReload: false }); + /** @type {{ relativePath: string, path: string }[]} */ + const reloadFailedJsFiles = []; + + for (const change of changes) { + handleChange(change.relativePath, change.path, change.newContent, change.config); + } + + return { reloadFailedJsFiles }; + /** + * @param {string} relativePath * @param {string} path * @param {string} newSrc + * @param {HotReloadConfig | undefined} config */ - function handleChange(path, newSrc) { - const relativePath = path.replace(/\\/g, '/').split('/out/')[1]; + function handleChange(relativePath, path, newSrc, config) { if (relativePath.endsWith('.css')) { handleCssChange(relativePath); } else if (relativePath.endsWith('.js')) { - handleJsChange(relativePath, newSrc); + handleJsChange(relativePath, path, newSrc, config); } } @@ -60,8 +231,9 @@ module.exports.run = async function (debugSession) { /** * @param {string} relativePath * @param {string} newSrc + * @param {HotReloadConfig | undefined} config */ - function handleJsChange(relativePath, newSrc) { + function handleJsChange(relativePath, path, newSrc, config) { const moduleIdStr = trimEnd(relativePath, '.js'); /** @type {any} */ @@ -85,11 +257,14 @@ module.exports.run = async function (debugSession) { // A frozen copy of the previous exports const oldExports = Object.freeze({ ...oldModule.exports }); - const reloadFn = g.$hotReload_applyNewExports?.({ oldExports, newSrc }); + const reloadFn = g.$hotReload_applyNewExports?.({ oldExports, newSrc, config }); if (!reloadFn) { console.log(debugSessionName, 'ignoring js change, as module does not support hot-reload', relativePath); hotReloadData.shouldReload = true; + + reloadFailedJsFiles.push({ relativePath, path }); + setMessage(`hot reload not supported for ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`); return; } @@ -178,19 +353,19 @@ module.exports.run = async function (debugSession) { return str; } - for (const change of changes) { - handleChange(change.path, change.newContent); - } - }, supportedChanges, debugSession.name.substring(0, 25)); - }); - return { - dispose() { - sub.dispose(); - watcher.dispose(); + for (const failedFile of result.reloadFailedJsFiles) { + const reloadFn = reloadFns.get(failedFile.relativePath) ?? (() => { + console.log('force change'); + watcher.forceChange(failedFile.path); + }); + reloadFns.set(failedFile.relativePath, reloadFn); + global?.reloadFailed(failedFile.relativePath, reloadFn); } - }; + })); + + return store; }; class DirWatcher { @@ -237,16 +412,23 @@ class DirWatcher { } }); const result = await r; - return new DirWatcher(event, () => result.unsubscribe()); + return new DirWatcher(event, () => result.unsubscribe(), path => { + const content = fileContents.get(path); + if (content !== undefined) { + listeners.forEach(l => l([{ path: path, newContent: content }])); + } + }); } /** * @param {(handler: (changes: { path: string, newContent: string }[]) => void) => IDisposable} onDidChange * @param {() => void} unsub + * @param {(path: string) => void} forceChange */ - constructor(onDidChange, unsub) { + constructor(onDidChange, unsub, forceChange) { this.onDidChange = onDidChange; this.unsub = unsub; + this.forceChange = forceChange; } dispose() { @@ -269,3 +451,33 @@ function debounce(fn, delay = 50) { }; } +class DisposableStore { + constructor() { + this._toDispose = new Set(); + this._isDisposed = false; + } + + + /** + * Adds an item to the collection. + * + * @template T + * @param {T} t - The item to add. + * @returns {T} The added item. + */ + add(t) { + this._toDispose.add(t); + return t; + } + dispose() { + if (this._isDisposed) { + return; + } + this._isDisposed = true; + this.clear(); + } + clear() { + this._toDispose.forEach(item => item.dispose()); + this._toDispose.clear(); + } +} diff --git a/src/vs/base/common/hotReload.ts b/src/vs/base/common/hotReload.ts index 194e60da372..94dec8e9b2f 100644 --- a/src/vs/base/common/hotReload.ts +++ b/src/vs/base/common/hotReload.ts @@ -27,8 +27,9 @@ export function registerHotReloadHandler(handler: HotReloadHandler): IDisposable * * If no handler can apply the new exports, the module will not be reloaded. */ -export type HotReloadHandler = (args: { oldExports: Record; newSrc: string }) => AcceptNewExportsHandler | undefined; +export type HotReloadHandler = (args: { oldExports: Record; newSrc: string; config: IHotReloadConfig }) => AcceptNewExportsHandler | undefined; export type AcceptNewExportsHandler = (newExports: Record) => boolean; +export type IHotReloadConfig = HotReloadConfig; function registerGlobalHotReloadHandler() { if (!hotReloadHandlers) { @@ -37,9 +38,11 @@ function registerGlobalHotReloadHandler() { const g = globalThis as unknown as GlobalThisAddition; if (!g.$hotReload_applyNewExports) { - g.$hotReload_applyNewExports = oldExports => { + g.$hotReload_applyNewExports = args => { + const args2 = { config: { mode: undefined }, ...args }; + for (const h of hotReloadHandlers!) { - const result = h(oldExports); + const result = h(args2); if (result) { return result; } } return undefined; @@ -49,22 +52,25 @@ function registerGlobalHotReloadHandler() { return hotReloadHandlers; } -let hotReloadHandlers: Set<(args: { oldExports: Record; newSrc: string }) => AcceptNewExportsFn | undefined> | undefined = undefined; +let hotReloadHandlers: Set<(args: { oldExports: Record; newSrc: string; config: HotReloadConfig }) => AcceptNewExportsFn | undefined> | undefined = undefined; -interface GlobalThisAddition { - $hotReload_applyNewExports?(args: { oldExports: Record; newSrc: string }): AcceptNewExportsFn | undefined; +interface HotReloadConfig { + mode?: 'patch-prototype' | undefined; } +interface GlobalThisAddition { + $hotReload_applyNewExports?(args: { oldExports: Record; newSrc: string; config?: HotReloadConfig }): AcceptNewExportsFn | undefined; +} type AcceptNewExportsFn = (newExports: Record) => boolean; if (isHotReloadEnabled()) { // This code does not run in production. - registerHotReloadHandler(({ oldExports, newSrc }) => { - // Don't match its own source code - if (newSrc.indexOf('/* ' + 'hot-reload:patch-prototype-methods */') === -1) { + registerHotReloadHandler(({ oldExports, newSrc, config }) => { + if (config.mode !== 'patch-prototype') { return undefined; } + return newExports => { for (const key in newExports) { const exportedItem = newExports[key]; From 069b28029cbe1b73acb2864fa45efc93c3424e4e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 2 Feb 2024 13:08:14 +0100 Subject: [PATCH 0870/1897] push out dynamic speech settings contribution (#203947) (#204122) --- src/vs/workbench/browser/parts/editor/editor.contribution.ts | 1 + .../contrib/accessibility/browser/accessibility.contribution.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 4a9bbd32ed7..8ab9dcce884 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -133,6 +133,7 @@ registerWorkbenchContribution2(UntitledTextEditorWorkingCopyEditorHandler.ID, Un registerWorkbenchContribution2(DynamicEditorConfigurations.ID, DynamicEditorConfigurations, WorkbenchContributionInstantiation.BlockRestore); registerEditorContribution(FloatingEditorClickMenu.ID, FloatingEditorClickMenu, EditorContributionInstantiation.AfterFirstRender); + //#endregion //#region Quick Access diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 963efd8617f..b002e6ba681 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -30,4 +30,4 @@ workbenchRegistry.registerWorkbenchContribution(InlineCompletionsAccessibleViewC registerWorkbenchContribution2(AccessibilityStatus.ID, AccessibilityStatus, WorkbenchContributionInstantiation.BlockRestore); registerWorkbenchContribution2(SaveAudioCueContribution.ID, SaveAudioCueContribution, WorkbenchContributionInstantiation.BlockRestore); -registerWorkbenchContribution2(DynamicSpeechAccessibilityConfiguration.ID, DynamicSpeechAccessibilityConfiguration, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(DynamicSpeechAccessibilityConfiguration.ID, DynamicSpeechAccessibilityConfiguration, WorkbenchContributionInstantiation.AfterRestored); From 933a728d0e36050ed08f562a7048efada1bfdec3 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 2 Feb 2024 13:28:25 +0100 Subject: [PATCH 0871/1897] end inline chat session when its provider goes, undo changes and close chat UI (#204125) --- .../browser/inlineChatController.ts | 10 +++++++ .../browser/inlineChatSessionService.ts | 6 +++- .../browser/inlineChatSessionServiceImpl.ts | 30 ++++++++++++++----- .../contrib/inlineChat/common/inlineChat.ts | 7 ++++- .../common/inlineChatServiceImpl.ts | 13 ++++---- 5 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 11b8f8de857..da0282f46be 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -170,6 +170,16 @@ export class InlineChatController implements IEditorContribution { await this.run({ existingSession }); })); + this._store.add(this._inlineChatSessionService.onDidEndSession(e => { + if (e.session === this._session && e.endedByExternalCause) { + this._log('session ENDED by external cause'); + this._session = undefined; + this._strategy?.cancel(); + this._resetWidget(); + this.cancelSession(); + } + })); + this._store.add(this._inlineChatSessionService.onDidMoveSession(async e => { if (e.editor === this._editor) { this._log('session RESUMING after move', e); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts index 0f2f0fb4142..638f0858903 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts @@ -31,13 +31,17 @@ export interface IInlineChatSessionEvent { readonly session: Session; } +export interface IInlineChatSessionEndEvent extends IInlineChatSessionEvent { + readonly endedByExternalCause: boolean; +} + export interface IInlineChatSessionService { _serviceBrand: undefined; onWillStartSession: Event; onDidMoveSession: Event; onDidStashSession: Event; - onDidEndSession: Event; + onDidEndSession: Event; createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: IRange }, token: CancellationToken): Promise; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index 59ac9bf22ca..ad54eae0667 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -16,7 +16,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Iterable } from 'vs/base/common/iterator'; import { raceCancellation } from 'vs/base/common/async'; -import { Recording, IInlineChatSessionService, ISessionKeyComputer, IInlineChatSessionEvent } from './inlineChatSessionService'; +import { Recording, IInlineChatSessionService, ISessionKeyComputer, IInlineChatSessionEvent, IInlineChatSessionEndEvent } from './inlineChatSessionService'; import { HunkData, Session, SessionWholeRange, StashedSession, TelemetryData, TelemetryDataClassification } from './inlineChatSession'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { ITextModel, IValidEditOperation } from 'vs/editor/common/model'; @@ -43,8 +43,8 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { private readonly _onDidMoveSession = new Emitter(); readonly onDidMoveSession: Event = this._onDidMoveSession.event; - private readonly _onDidEndSession = new Emitter(); - readonly onDidEndSession: Event = this._onDidEndSession.event; + private readonly _onDidEndSession = new Emitter(); + readonly onDidEndSession: Event = this._onDidEndSession.event; private readonly _onDidStashSession = new Emitter(); readonly onDidStashSession: Event = this._onDidStashSession.event; @@ -100,9 +100,16 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { } this._logService.trace('[IE] NEW session', provider.debugName); - this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${provider.debugName}`); + this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${provider.debugName}`); const store = new DisposableStore(); + store.add(this._inlineChatService.onDidChangeProviders(e => { + if (e.removed === provider) { + this._logService.trace(`[IE] provider GONE for ${editor.getId()}, ${provider.debugName}`); + this._releaseSession(session, true); + } + })); + const id = generateUuid(); const targetUri = textModel.uri; @@ -131,7 +138,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { if (targetUri.scheme === Schemas.untitled) { store.add(this._editorService.onDidCloseEditor(() => { if (!this._editorService.isOpened({ resource: targetUri, typeId: UntitledTextEditorInput.ID, editorId: DEFAULT_EDITOR_ASSOCIATION.id })) { - this.releaseSession(session); + this._releaseSession(session, true); } })); } @@ -141,6 +148,11 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { wholeRange = rawSession.wholeRange ? Range.lift(rawSession.wholeRange) : editor.getSelection(); } + if (token.isCancellationRequested) { + store.dispose(); + return undefined; + } + const session = new Session( options.editMode, targetUri, @@ -190,6 +202,10 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { } releaseSession(session: Session): void { + this._releaseSession(session, false); + } + + private _releaseSession(session: Session, byServer: boolean): void { let tuple: [string, SessionData] | undefined; @@ -211,11 +227,11 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { this._telemetryService.publicLog2('interactiveEditor/session', session.asTelemetryData()); const [key, value] = tuple; - value.store.dispose(); this._sessions.delete(key); this._logService.trace(`[IE] did RELEASED session for ${value.editor.getId()}, ${session.provider.debugName}`); - this._onDidEndSession.fire({ editor: value.editor, session }); + this._onDidEndSession.fire({ editor: value.editor, session, endedByExternalCause: byServer }); + value.store.dispose(); } stashSession(session: Session, editor: ICodeEditor, undoCancelEdits: IValidEditOperation[]): StashedSession { diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index bf8c26d3a7d..0794a0f8a15 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -115,10 +115,15 @@ export interface IInlineChatSessionProvider { export const IInlineChatService = createDecorator('IInlineChatService'); +export interface InlineChatProviderChangeEvent { + readonly added?: IInlineChatSessionProvider; + readonly removed?: IInlineChatSessionProvider; +} + export interface IInlineChatService { _serviceBrand: undefined; - onDidChangeProviders: Event; + onDidChangeProviders: Event; addProvider(provider: IInlineChatSessionProvider): IDisposable; getAllProvider(): Iterable; } diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl.ts index 63b079cb0c2..ddff44a1b7f 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl.ts @@ -7,20 +7,17 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; import { LinkedList } from 'vs/base/common/linkedList'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IInlineChatService, IInlineChatSessionProvider, CTX_INLINE_CHAT_HAS_PROVIDER } from './inlineChat'; +import { IInlineChatService, IInlineChatSessionProvider, CTX_INLINE_CHAT_HAS_PROVIDER, InlineChatProviderChangeEvent } from './inlineChat'; export class InlineChatServiceImpl implements IInlineChatService { declare _serviceBrand: undefined; + private readonly _onDidChangeProviders = new Emitter(); private readonly _entries = new LinkedList(); - private readonly _ctxHasProvider: IContextKey; - private readonly _onDidChangeProviders = new Emitter(); - public get onDidChangeProviders() { - return this._onDidChangeProviders.event; - } + readonly onDidChangeProviders = this._onDidChangeProviders.event; constructor(@IContextKeyService contextKeyService: IContextKeyService) { this._ctxHasProvider = CTX_INLINE_CHAT_HAS_PROVIDER.bindTo(contextKeyService); @@ -30,12 +27,12 @@ export class InlineChatServiceImpl implements IInlineChatService { const rm = this._entries.push(provider); this._ctxHasProvider.set(true); - this._onDidChangeProviders.fire(); + this._onDidChangeProviders.fire({ added: provider }); return toDisposable(() => { rm(); this._ctxHasProvider.set(this._entries.size > 0); - this._onDidChangeProviders.fire(); + this._onDidChangeProviders.fire({ removed: provider }); }); } From 929edab63984e65f4ab15db6ed2f351c9b965bd3 Mon Sep 17 00:00:00 2001 From: Mahmood Ali Date: Fri, 2 Feb 2024 08:57:59 -0500 Subject: [PATCH 0872/1897] vscode server: cope with multiple libc/libc++ installations (#204032) cope with multiple libc/libc++ installations --- resources/server/bin/helpers/check-requirements-linux.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/server/bin/helpers/check-requirements-linux.sh b/resources/server/bin/helpers/check-requirements-linux.sh index b2ad871fa11..44240f7e536 100644 --- a/resources/server/bin/helpers/check-requirements-linux.sh +++ b/resources/server/bin/helpers/check-requirements-linux.sh @@ -56,7 +56,7 @@ elif [ -f /sbin/ldconfig ]; then libstdcpp_paths=$(/sbin/ldconfig -p | grep 'libstdc++.so.6') if [ "$(echo "$libstdcpp_paths" | wc -l)" -gt 1 ]; then - libstdcpp_path=$(echo "$libstdcpp_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}') + libstdcpp_path=$(echo "$libstdcpp_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}' | head -n1) else libstdcpp_path=$(echo "$libstdcpp_paths" | awk '{print $NF}') fi @@ -90,7 +90,7 @@ if [ -z "$(ldd --version 2>&1 | grep 'musl libc')" ]; then libc_paths=$(/sbin/ldconfig -p | grep 'libc.so.6') if [ "$(echo "$libc_paths" | wc -l)" -gt 1 ]; then - libc_path=$(echo "$libc_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}') + libc_path=$(echo "$libc_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}' | head -n1) else libc_path=$(echo "$libc_paths" | awk '{print $NF}') fi From 5f25b9a24074a4f739cb618c4ac95c2876e6bc7b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 2 Feb 2024 15:05:01 +0100 Subject: [PATCH 0873/1897] debt - ensure to include error in error message for extension (#204141) --- src/vs/workbench/api/common/extHost.api.impl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 18cef92d5fb..5a882c5f017 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -241,7 +241,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I try { listener.call(thisArgs, e); } catch (err) { - errors.onUnexpectedExternalError(new Error(`[ExtensionListenerError] Extension '${extension.identifier.value}' FAILED to handle event`, { cause: err })); + errors.onUnexpectedExternalError(new Error(`[ExtensionListenerError] Extension '${extension.identifier.value}' FAILED to handle event: ${err.toString()}`, { cause: err })); extHostTelemetry.onExtensionError(extension.identifier, err); } }); From 1899b51806a3531a7f79c1c7b4c261434adfcb2b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 2 Feb 2024 15:08:40 +0100 Subject: [PATCH 0874/1897] set clipboard paste preventer for linux only (#203947) (#204142) --- .../electron-sandbox/selectionClipboard.ts | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts index 9d914998632..ae407a26a24 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts @@ -90,30 +90,28 @@ export class SelectionClipboard extends Disposable implements IEditorContributio } } -class SelectionClipboardPastePreventer extends Disposable implements IWorkbenchContribution { +class LinuxSelectionClipboardPastePreventer extends Disposable implements IWorkbenchContribution { - static readonly ID = 'workbench.contrib.selectionClipboardPastePreventer'; + static readonly ID = 'workbench.contrib.linuxSelectionClipboardPastePreventer'; constructor( @IConfigurationService configurationService: IConfigurationService ) { super(); - if (platform.isLinux) { - this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => { - disposables.add(addDisposableListener(window.document, 'mouseup', e => { - if (e.button === 1) { - // middle button - const config = configurationService.getValue<{ selectionClipboard: boolean }>('editor'); - if (!config.selectionClipboard) { - // selection clipboard is disabled - // try to stop the upcoming paste - e.preventDefault(); - } + this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => { + disposables.add(addDisposableListener(window.document, 'mouseup', e => { + if (e.button === 1) { + // middle button + const config = configurationService.getValue<{ selectionClipboard: boolean }>('editor'); + if (!config.selectionClipboard) { + // selection clipboard is disabled + // try to stop the upcoming paste + e.preventDefault(); } - })); - }, { window: mainWindow, disposables: this._store })); - } + } + })); + }, { window: mainWindow, disposables: this._store })); } } @@ -143,7 +141,7 @@ class PasteSelectionClipboardAction extends EditorAction { } registerEditorContribution(SelectionClipboardContributionID, SelectionClipboard, EditorContributionInstantiation.Eager); // eager because it needs to listen to selection change events -registerWorkbenchContribution2(SelectionClipboardPastePreventer.ID, SelectionClipboardPastePreventer, WorkbenchContributionInstantiation.BlockRestore); if (platform.isLinux) { + registerWorkbenchContribution2(LinuxSelectionClipboardPastePreventer.ID, LinuxSelectionClipboardPastePreventer, WorkbenchContributionInstantiation.BlockRestore); // eager because it listens to mouse-up events globally registerEditorAction(PasteSelectionClipboardAction); } From d04b073780a845a8942c765eaa598446ff0e2b29 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 2 Feb 2024 15:35:50 +0100 Subject: [PATCH 0875/1897] set walkthrough snippets to lazy instantiation (#203947) (#204145) --- .../welcomeWalkthrough/browser/walkThrough.contribution.ts | 2 +- .../contrib/welcomeWalkthrough/browser/walkThroughInput.ts | 6 +++--- .../welcomeWalkthrough/common/walkThroughContentProvider.ts | 4 +++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.ts b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.ts index d7445bb3674..65bf37a893b 100644 --- a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.ts +++ b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.ts @@ -29,7 +29,7 @@ registerAction2(EditorWalkThroughAction); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(EditorWalkThroughInputSerializer.ID, EditorWalkThroughInputSerializer); -registerWorkbenchContribution2(WalkThroughSnippetContentProvider.ID, WalkThroughSnippetContentProvider, WorkbenchContributionInstantiation.BlockRestore /* cannot be on a later phase because an editor might need this on startup */); +registerWorkbenchContribution2(WalkThroughSnippetContentProvider.ID, WalkThroughSnippetContentProvider, WorkbenchContributionInstantiation.Lazy); KeybindingsRegistry.registerCommandAndKeybindingRule(WalkThroughArrowUp); diff --git a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughInput.ts b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughInput.ts index bd8aa418627..f217828b6ec 100644 --- a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughInput.ts +++ b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughInput.ts @@ -9,12 +9,12 @@ import { URI } from 'vs/base/common/uri'; import { DisposableStore, IReference } from 'vs/base/common/lifecycle'; import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { marked } from 'vs/base/common/marked/marked'; -import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; -import { requireToContent } from 'vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider'; +import { WalkThroughSnippetContentProvider, requireToContent } from 'vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider'; import { Dimension } from 'vs/base/browser/dom'; import { EditorInputCapabilities, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { getWorkbenchContribution } from 'vs/workbench/common/contributions'; class WalkThroughModel extends EditorModel { @@ -118,7 +118,7 @@ export class WalkThroughInput extends EditorInput { const renderer = new marked.Renderer(); renderer.code = (code, lang) => { i++; - const resource = this.options.resource.with({ scheme: Schemas.walkThroughSnippet, fragment: `${i}.${lang}` }); + const resource = this.options.resource.with({ scheme: getWorkbenchContribution(WalkThroughSnippetContentProvider.ID).scheme, fragment: `${i}.${lang}` }); snippets.push(this.textModelResolverService.createModelReference(resource)); return `
`; }; diff --git a/src/vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider.ts b/src/vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider.ts index 24764f1e31a..745e7cfd4f0 100644 --- a/src/vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider.ts +++ b/src/vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider.ts @@ -45,13 +45,15 @@ export class WalkThroughSnippetContentProvider implements ITextModelContentProvi private loads = new Map>(); + readonly scheme = Schemas.walkThroughSnippet; + constructor( @ITextModelService private readonly textModelResolverService: ITextModelService, @ILanguageService private readonly languageService: ILanguageService, @IModelService private readonly modelService: IModelService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { - this.textModelResolverService.registerTextModelContentProvider(Schemas.walkThroughSnippet, this); + this.textModelResolverService.registerTextModelContentProvider(this.scheme, this); } private async textBufferFactoryFromResource(resource: URI): Promise { From 52ea3341bcb182691440e2bcca0be778fb769520 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 2 Feb 2024 15:43:33 +0100 Subject: [PATCH 0876/1897] Fixes #203792 --- .../widget/multiDiffEditorWidget/diffEditorItemTemplate.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts index 186fdb35761..56bd728f01a 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts @@ -19,6 +19,7 @@ import { MenuId } from 'vs/platform/actions/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IObjectData, IPooledObject } from './objectPool'; import { ActionRunnerWithContext } from './utils'; +import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; export class TemplateData implements IObjectData { constructor( @@ -145,6 +146,7 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< shouldForwardArgs: true, }, toolbarOptions: { primaryGroup: g => g.startsWith('navigation') }, + actionViewItemProvider: action => createActionViewItem(_instantiationService, action), })); } From 9f6d06d231d4c45fe431b68b04f6e3cad57c557f Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 2 Feb 2024 15:44:46 +0100 Subject: [PATCH 0877/1897] Remove stylesheet itself --- src/vs/base/browser/dom.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index c00faddd1db..82459f97178 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -960,7 +960,7 @@ class WrappedStyleElement { public dispose(): void { if (this._styleSheet) { - clearNode(this._styleSheet); + this._styleSheet.remove(); this._styleSheet = undefined; } } From 9cf254d483b8188d009fa18a293baa84212a62be Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 2 Feb 2024 16:41:25 +0100 Subject: [PATCH 0878/1897] support to invoke inline chat controller with a prompt/response pair (#204154) --- .../browser/inlineChatController.ts | 26 +++++++-- .../test/browser/inlineChatController.test.ts | 54 ++++++++++++++++++- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index da0282f46be..d22229e3528 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -23,7 +23,7 @@ import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { TextEdit } from 'vs/editor/common/languages'; +import { ProviderResult, TextEdit } from 'vs/editor/common/languages'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; import { localize } from 'vs/nls'; @@ -78,18 +78,22 @@ export abstract class InlineChatRunOptions { message?: string; autoSend?: boolean; existingSession?: Session; + existingExchange?: { prompt: string; response: IInlineChatResponse }; isUnstashed?: boolean; position?: IPosition; withIntentDetection?: boolean; static isInteractiveEditorOptions(options: any): options is InlineChatRunOptions { - const { initialSelection, initialRange, message, autoSend, position } = options; + const { initialSelection, initialRange, message, autoSend, position, existingExchange, existingSession } = options; if ( typeof message !== 'undefined' && typeof message !== 'string' || typeof autoSend !== 'undefined' && typeof autoSend !== 'boolean' || typeof initialRange !== 'undefined' && !Range.isIRange(initialRange) || typeof initialSelection !== 'undefined' && !Selection.isISelection(initialSelection) - || typeof position !== 'undefined' && !Position.isIPosition(position)) { + || typeof position !== 'undefined' && !Position.isIPosition(position) + || typeof existingSession !== 'undefined' && !(existingSession instanceof Session) + || typeof existingExchange !== 'undefined' && typeof existingExchange !== 'object' + ) { return false; } return true; @@ -449,6 +453,11 @@ export class InlineChatController implements IEditorContribution { this._updatePlaceholder(); + if (options.existingExchange) { + options.message = options.existingExchange.prompt; + options.autoSend = true; + } + if (options.message) { this.updateInput(options.message); aria.alert(options.message); @@ -648,8 +657,15 @@ export class InlineChatController implements IEditorContribution { const a11yVerboseInlineChat = this._configurationService.getValue('accessibility.verbosity.inlineChat') === true; const requestId = this._chatAccessibilityService.acceptRequest(); - const task = this._session.provider.provideResponse(this._session.session, request, progress, requestCts.token); - this._log('request started', this._session.provider.debugName, this._session.session, request); + let task: ProviderResult; + if (options.existingExchange) { + task = options.existingExchange.response; + delete options.existingExchange; + this._log('using READY-response', this._session.provider.debugName, this._session.session); + } else { + task = this._session.provider.provideResponse(this._session.session, request, progress, requestCts.token); + this._log('request started', this._session.provider.debugName, this._session.session, request); + } let response: ReplyResponse | ErrorResponse | EmptyResponse; let reply: IInlineChatResponse | null | undefined; diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index cb1260e7a0f..932cb5056b1 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -36,13 +36,14 @@ import { IInlineChatSavingService } from '../../browser/inlineChatSavingService' import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionServiceImpl'; import { IInlineChatSessionService } from '../../browser/inlineChatSessionService'; -import { CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatRequest, IInlineChatService, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatEditResponse, IInlineChatRequest, IInlineChatService, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { TestWorkerService } from './testWorkerService'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { Schemas } from 'vs/base/common/network'; +import { MarkdownString } from 'vs/base/common/htmlContent'; suite('InteractiveChatController', function () { class TestController extends InlineChatController { @@ -524,4 +525,55 @@ suite('InteractiveChatController', function () { assert.strictEqual(requests[2].previewDocument.scheme, Schemas.vscode); // preview assert.strictEqual(requests[2].previewDocument.authority, 'inline-chat'); }); + + test('start with existing exchange', async function () { + + // don't call this provider + let providerCalled = 0; + store.add(inlineChatService.addProvider({ + debugName: 'Unit Test', + label: 'Unit Test', + prepareInlineChatSession() { + return { + id: Math.random() + }; + }, + provideResponse(_session, request) { + providerCalled++; + return undefined; + } + })); + + // use precooked response + const response = { + id: 1, + type: InlineChatResponseType.EditorEdit, + message: new MarkdownString('MD-message'), + edits: [{ + text: 'Precooked Response\n', + range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } + }] + + } satisfies IInlineChatEditResponse; + + configurationService.setUserConfiguration('inlineChat', { mode: EditMode.Live }); + + ctrl = instaService.createInstance(TestController, editor); + const p = ctrl.waitFor(TestController.INIT_SEQUENCE_AUTO_SEND); + ctrl.run({ existingExchange: { prompt: 'Hello', response } }); + + await p; + + assert.strictEqual(providerCalled, 0); + assert.ok(ctrl.getWidgetPosition() !== undefined); + + assert.ok(ctrl.getMessage() === 'MD-message'); + assert.equal(model.getLineContent(1), 'Precooked Response'); + + await ctrl.cancelSession(); + await ctrl.joinCurrentRun(); + + assert.ok(ctrl.getWidgetPosition() === undefined); + + }); }); From bf16cceb73db57ae378956468d2b22b13c315113 Mon Sep 17 00:00:00 2001 From: Robo Date: Sat, 3 Feb 2024 00:52:39 +0900 Subject: [PATCH 0879/1897] chore: bump windows-mutex@0.5.0 (#204150) --- package.json | 2 +- yarn.lock | 22 ++++++---------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index eefc68ea019..9bebe721127 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "@vscode/sqlite3": "5.1.6-vscode", "@vscode/sudo-prompt": "9.3.1", "@vscode/vscode-languagedetection": "1.0.21", - "@vscode/windows-mutex": "^0.4.4", + "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", "@xterm/addon-canvas": "0.6.0-beta.30", diff --git a/yarn.lock b/yarn.lock index e3dca0e6830..a8dae3e7e4d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1512,13 +1512,13 @@ dependencies: node-addon-api "^3.0.2" -"@vscode/windows-mutex@^0.4.4": - version "0.4.4" - resolved "https://registry.yarnpkg.com/@vscode/windows-mutex/-/windows-mutex-0.4.4.tgz#260eb900721a71122f37127c5250f6931bb99ad4" - integrity sha512-zPPbXuRJj35iv73+J7oZPfWZHht8A2qF96E6t/aCebI7d7RsNJuflLtDVuZqks3ZtgIdy77ht/lNrMxoTQMAPA== +"@vscode/windows-mutex@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@vscode/windows-mutex/-/windows-mutex-0.5.0.tgz#509f961704c6615bfab1a081c522bf1beb15bb3f" + integrity sha512-iD29L9AUscpn07aAvhP2QuhrXzuKc1iQpPF6u7ybtvRbR+o+RotfbuKqqF1RDlDDrJZkL+3AZTy4D01U4nEe5A== dependencies: - bindings "^1.2.1" - nan "^2.17.0" + bindings "^1.5.0" + node-addon-api "7.1.0" "@vscode/windows-process-tree@^0.6.0": version "0.6.0" @@ -2234,11 +2234,6 @@ binaryextensions@~1.0.0: resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-1.0.1.tgz#1e637488b35b58bda5f4774bf96a5212a8c90755" integrity sha1-HmN0iLNbWL2l9HdL+WpSEqjJB1U= -bindings@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7" - integrity sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw== - bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" @@ -6697,11 +6692,6 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== -nan@^2.17.0: - version "2.17.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" - integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== - nanoid@3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" From 645e218f9567c99b049dc89e382876fb07685a0f Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Sat, 3 Feb 2024 02:58:48 +1100 Subject: [PATCH 0880/1897] Cache the API object created (#204064) * Cache the API object created * Update src/vs/workbench/api/common/extHostDebugService.ts Co-authored-by: Connor Peet --------- Co-authored-by: Connor Peet --- src/vs/workbench/api/common/extHostDebugService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index 1fc8e403272..ced402e29f8 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -1004,7 +1004,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E } export class ExtHostDebugSession { - + private apiSession?: vscode.DebugSession; constructor( private _debugServiceProxy: MainThreadDebugServiceShape, private _id: DebugSessionUUID, @@ -1017,7 +1017,7 @@ export class ExtHostDebugSession { public get api(): vscode.DebugSession { const that = this; - return Object.freeze({ + return this.apiSession ??= Object.freeze({ id: that._id, type: that._type, get name() { From 7b3282b42e8e3e3048fe43d611d41ce31c35b3c7 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 2 Feb 2024 17:02:16 +0100 Subject: [PATCH 0881/1897] Remove children of stale tree nodes (#203783) Possible fix for #192422 --- src/vs/base/browser/ui/tree/asyncDataTree.ts | 1 + .../browser/ui/tree/asyncDataTree.test.ts | 121 ++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 77a72d50343..ac004a3b6e4 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -827,6 +827,7 @@ export class AsyncDataTree implements IDisposable if (treeNode.collapsed) { node.hasChildren = !!this.dataSource.hasChildren(node.element); node.stale = true; + this.setChildren(node, [], recursive, viewStateContext); return; } } diff --git a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts index 24640e6cf65..c92a6273336 100644 --- a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts +++ b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts @@ -269,6 +269,127 @@ suite('AsyncDataTree', function () { assert(tree.getNode(model.get('a')).collapsed); }); + test('issue #192422 - resolved collapsed nodes with changed children don\'t show old children', async () => { + const container = document.createElement('div'); + let hasGottenAChildren = false; + const dataSource = new class implements IAsyncDataSource { + hasChildren(element: Element): boolean { + return !!element.children && element.children.length > 0; + } + async getChildren(element: Element): Promise { + if (element.id === 'a') { + if (!hasGottenAChildren) { + hasGottenAChildren = true; + } else { + return [{ id: 'c' }]; + } + } + return element.children || []; + } + }; + + const model = new Model({ + id: 'root', + children: [{ + id: 'a', children: [{ id: 'b' }] + }] + }); + + const tree = store.add(new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() })); + tree.layout(200); + + await tree.setInput(model.root); + const a = model.get('a'); + const aNode = tree.getNode(a); + assert(aNode.collapsed); + await tree.expand(a); + assert(!aNode.collapsed); + assert.equal(aNode.children.length, 1); + assert.equal(aNode.children[0].element.id, 'b'); + const bChild = container.querySelector('.monaco-list-row:nth-child(2)') as HTMLElement | undefined; + assert.equal(bChild?.textContent, 'b'); + tree.collapse(a); + assert(aNode.collapsed); + + await tree.updateChildren(a); + const aUpdated1 = model.get('a'); + const aNodeUpdated1 = tree.getNode(a); + assert(aNodeUpdated1.collapsed); + assert.equal(aNodeUpdated1.children.length, 0); + let didCheckNoChildren = false; + const event = tree.onDidChangeCollapseState(e => { + const child = container.querySelector('.monaco-list-row:nth-child(2)') as HTMLElement | undefined; + assert.equal(child, undefined); + didCheckNoChildren = true; + }); + await tree.expand(aUpdated1); + event.dispose(); + assert(didCheckNoChildren); + + const aNodeUpdated2 = tree.getNode(a); + assert(!aNodeUpdated2.collapsed); + assert.equal(aNodeUpdated2.children.length, 1); + assert.equal(aNodeUpdated2.children[0].element.id, 'c'); + const child = container.querySelector('.monaco-list-row:nth-child(2)') as HTMLElement | undefined; + assert.equal(child?.textContent, 'c'); + }); + + test('issue #192422 - resolved collapsed nodes with unchanged children immediately show children', async () => { + const container = document.createElement('div'); + const dataSource = new class implements IAsyncDataSource { + hasChildren(element: Element): boolean { + return !!element.children && element.children.length > 0; + } + async getChildren(element: Element): Promise { + return element.children || []; + } + }; + + const model = new Model({ + id: 'root', + children: [{ + id: 'a', children: [{ id: 'b' }] + }] + }); + + const tree = store.add(new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() })); + tree.layout(200); + + await tree.setInput(model.root); + const a = model.get('a'); + const aNode = tree.getNode(a); + assert(aNode.collapsed); + await tree.expand(a); + assert(!aNode.collapsed); + assert.equal(aNode.children.length, 1); + assert.equal(aNode.children[0].element.id, 'b'); + const bChild = container.querySelector('.monaco-list-row:nth-child(2)') as HTMLElement | undefined; + assert.equal(bChild?.textContent, 'b'); + tree.collapse(a); + assert(aNode.collapsed); + + const aUpdated1 = model.get('a'); + const aNodeUpdated1 = tree.getNode(a); + assert(aNodeUpdated1.collapsed); + assert.equal(aNodeUpdated1.children.length, 1); + let didCheckSameChildren = false; + const event = tree.onDidChangeCollapseState(e => { + const child = container.querySelector('.monaco-list-row:nth-child(2)') as HTMLElement | undefined; + assert.equal(child?.textContent, 'b'); + didCheckSameChildren = true; + }); + await tree.expand(aUpdated1); + event.dispose(); + assert(didCheckSameChildren); + + const aNodeUpdated2 = tree.getNode(a); + assert(!aNodeUpdated2.collapsed); + assert.equal(aNodeUpdated2.children.length, 1); + assert.equal(aNodeUpdated2.children[0].element.id, 'b'); + const child = container.querySelector('.monaco-list-row:nth-child(2)') as HTMLElement | undefined; + assert.equal(child?.textContent, 'b'); + }); + test('support default collapse state per element', async () => { const container = document.createElement('div'); From 7513cee97bacb59a6940b4da118fd5875ce3e5dc Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Fri, 2 Feb 2024 17:24:13 +0100 Subject: [PATCH 0882/1897] multiDiffEditor: Card style + update colors (#203499) * multiDiffEditor: Card style + update colors --------- Co-authored-by: Henning Dieterichs --- .../lib/stylelint/vscode-known-variables.json | 5 +- .../widget/multiDiffEditorWidget/colors.ts | 4 +- .../diffEditorItemTemplate.ts | 20 +- .../multiDiffEditorWidgetImpl.ts | 2 +- .../widget/multiDiffEditorWidget/style.css | 198 ++++++++++-------- 5 files changed, 126 insertions(+), 103 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 3fbbf010d86..9c9786bcf8c 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -747,7 +747,8 @@ "--vscode-widget-border", "--vscode-widget-shadow", "--vscode-window-activeBorder", - "--vscode-window-inactiveBorder" + "--vscode-window-inactiveBorder", + "--vscode-multiDiffEditor-headerBackground" ], "others": [ "--background-dark", @@ -824,4 +825,4 @@ "--zoom-factor", "--test-bar-width" ] -} \ No newline at end of file +} diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/colors.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/colors.ts index b07e5338e80..d58781aabfe 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/colors.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/colors.ts @@ -8,13 +8,13 @@ import { registerColor } from 'vs/platform/theme/common/colorRegistry'; export const multiDiffEditorHeaderBackground = registerColor( 'multiDiffEditor.headerBackground', - { dark: '#808080', light: '#b4b4b4', hcDark: '#808080', hcLight: '#b4b4b4', }, + { dark: '#262626', light: 'tab.inactiveBackground', hcDark: 'tab.inactiveBackground', hcLight: 'tab.inactiveBackground', }, localize('multiDiffEditor.headerBackground', 'The background color of the diff editor\'s header') ); export const multiDiffEditorBackground = registerColor( 'multiDiffEditor.background', - { dark: '#000000', light: '#e5e5e5', hcDark: '#000000', hcLight: '#e5e5e5', }, + { dark: 'editorBackground', light: 'editorBackground', hcDark: 'editorBackground', hcLight: 'editorBackground', }, localize('multiDiffEditor.background', 'The background color of the multi file diff editor') ); diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts index 56bd728f01a..da71ca719f7 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts @@ -60,13 +60,15 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< private readonly _elements = h('div.multiDiffEntry', [ h('div.header@header', [ - h('div.collapse-button@collapseButton'), - h('div.file-path', [ - h('div.title.modified.show-file-icons@primaryPath', [] as any), - h('div.status.deleted@status', ['R']), - h('div.title.original.show-file-icons@secondaryPath', [] as any), + h('div.header-content', [ + h('div.collapse-button@collapseButton'), + h('div.file-path', [ + h('div.title.modified.show-file-icons@primaryPath', [] as any), + h('div.status.deleted@status', ['R']), + h('div.title.original.show-file-icons@secondaryPath', [] as any), + ]), + h('div.actions@actions'), ]), - h('div.actions@actions'), ]), h('div.editorParent', [ @@ -138,7 +140,7 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< })); this._container.appendChild(this._elements.root); - this._outerEditorHeight = 38; + this._outerEditorHeight = this._headerHeight; this._register(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.actions, MenuId.MultiDiffEditorFileToolbar, { actionRunner: this._register(new ActionRunnerWithContext(() => (this._viewModel.get()?.modifiedUri))), @@ -218,7 +220,7 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< }); } - private readonly _headerHeight = /*this._elements.header.clientHeight*/ 38; + private readonly _headerHeight = /*this._elements.header.clientHeight*/ 48; public render(verticalRange: OffsetRange, width: number, editorScroll: number, viewPort: OffsetRange): void { this._elements.root.style.visibility = 'visible'; @@ -233,7 +235,7 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< globalTransaction(tx => { this.editor.layout({ - width: width, + width: width - 2 * 8 - 2 * 1, height: verticalRange.length - this._outerEditorHeight, }); }); diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 9bfbae0c9ac..129d91151b1 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -85,7 +85,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } ); - private readonly _spaceBetweenPx = 10; + private readonly _spaceBetweenPx = 0; private readonly _totalHeight = this._viewItems.map(this, (items, reader) => items.reduce((r, i) => r + i.contentHeight.read(reader) + this._spaceBetweenPx, 0)); public readonly activeDiffItem = derived(this, reader => this._viewItems.read(reader).find(i => i.template.read(reader)?.isFocused.read(reader))); diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css b/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css index 9071d1f841b..ca41df09a04 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css @@ -6,93 +6,113 @@ .monaco-component.multiDiffEditor { background: var(--vscode-multiDiffEditor-background); overflow-y: hidden; -} - -.monaco-component .multiDiffEntry { - display: flex; - flex-direction: column; - flex: 1; - overflow: hidden; - - border-bottom: 1px solid var(--vscode-multiDiffEditor-border); -} - -.monaco-component .multiDiffEntry .collapse-button { - margin: 0 5px; - cursor: pointer; -} - -.monaco-component .multiDiffEntry .collapse-button a { - display: block; -} - -.monaco-component .multiDiffEntry .header { - display: flex; - align-items: center; - padding: 8px 5px; - color: var(--vscode-foreground); - background: var(--vscode-editor-background); - - z-index: 1000; - - border-top: 1px solid var(--vscode-multiDiffEditor-border); - - border-bottom: 1px solid var(--vscode-sideBarSectionHeader-border); -} - -.monaco-component .multiDiffEntry .header.shadow { - box-shadow: var(--vscode-scrollbar-shadow) 0px 6px 6px -6px; -} - -.monaco-component .multiDiffEntry .header .file-path { - display: flex; - flex: 1; - min-width: 0; -} - -.monaco-component .multiDiffEntry .header .file-path .title { - font-size: 14px; - line-height: 22px; -} - -.monaco-component .multiDiffEntry .header .file-path .status { - - font-weight: 600; - opacity: 0.75; - margin: 0px 10px; - line-height: 22px; -} -/* -TODO@hediet: move colors from git extension to core! - -.monaco-component .multiDiffEntry .header .file-path .status.renamed { - color: va r(--vscode-gitDecoration-renamedResourceForeground); -} - -.monaco-component .multiDiffEntry .header .file-path .status.deleted { - color: va r(--vscode-gitDecoration-deletedResourceForeground); -} - -.monaco-component .multiDiffEntry .header .file-path .status.added { - color: va r(--vscode-gitDecoration-addedResourceForeground); -} -*/ -.monaco-component .multiDiffEntry .header .file-path .title.original { - flex: 1; - min-width: 0; - text-overflow: ellipsis; -} - -.monaco-component .multiDiffEntry .header .actions { - padding: 0 8px; -} - -.monaco-component .multiDiffEntry .editorParent { - flex: 1; - display: flex; - flex-direction: column; -} - -.monaco-component .multiDiffEntry .editorContainer { - flex: 1; + + .multiDiffEntry { + display: flex; + flex-direction: column; + flex: 1; + overflow: hidden; + + + .collapse-button { + margin: 0 5px; + cursor: pointer; + + a { + display: block; + } + } + + .header { + z-index: 1000; + background: var(--vscode-editor-background); + + .header-content { + margin: 8px 8px 0px 8px; + padding: 8px 5px; + + border-top: 1px solid var(--vscode-multiDiffEditor-border); + border-right: 1px solid var(--vscode-multiDiffEditor-border); + border-left: 1px solid var(--vscode-multiDiffEditor-border); + border-top-left-radius: 2px; + border-top-right-radius: 2px; + + display: flex; + align-items: center; + + color: var(--vscode-foreground); + background: var(--vscode-multiDiffEditor-headerBackground); + + border-bottom: 1px solid var(--vscode-sideBarSectionHeader-border); + + &.shadow { + box-shadow: var(--vscode-scrollbar-shadow) 0px 6px 6px -6px; + } + + .file-path { + display: flex; + flex: 1; + min-width: 0; + + .title { + font-size: 14px; + line-height: 22px; + + &.original { + flex: 1; + min-width: 0; + text-overflow: ellipsis; + } + } + + .status { + font-weight: 600; + opacity: 0.75; + margin: 0px 10px; + line-height: 22px; + + /* + TODO@hediet: move colors from git extension to core! + &.renamed { + color: v ar(--vscode-gitDecoration-renamedResourceForeground); + } + + &.deleted { + color: v ar(--vscode-gitDecoration-deletedResourceForeground); + } + + &.added { + color: v ar(--vscode-gitDecoration-addedResourceForeground); + } + */ + } + } + + .actions { + padding: 0 8px; + } + } + + + } + + .editorParent { + flex: 1; + display: flex; + flex-direction: column; + + margin-right: 8px; + margin-left: 8px; + + border-right: 1px solid var(--vscode-multiDiffEditor-border); + border-left: 1px solid var(--vscode-multiDiffEditor-border); + border-bottom: 1px solid var(--vscode-multiDiffEditor-border); + border-radius: 2px; + overflow: hidden; + } + + .editorContainer { + flex: 1; + } + } } From 8019c10a01dc5781ffe5b803f404e7a3b797642a Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 2 Feb 2024 18:00:27 +0100 Subject: [PATCH 0883/1897] Bump distro (#204164) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9bebe721127..f34e85772c4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "6c14240015f9a76b34c29b4212fb8df761106443", + "distro": "ddced7c51be56108bf9f4bc3e3481a8673aa5031", "author": { "name": "Microsoft Corporation" }, From c535f3dc1fe9ed1cd624c727526b50f98a1f6d22 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 2 Feb 2024 09:20:07 -0800 Subject: [PATCH 0884/1897] updat endgame notebook milestone (#204165) --- .vscode/notebooks/endgame.github-issues | 2 +- .vscode/notebooks/my-endgame.github-issues | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index 5e2c7f9a815..ee1084be56d 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\r\n\r\n$MILESTONE=milestone:\"December / January 2024\"" + "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"February 2024\"" }, { "kind": 1, diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index 4d3d34a8c92..a286082c738 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\r\n\r\n$MILESTONE=milestone:\"December / January 2024\"\r\n\r\n$MINE=assignee:@me" + "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"February 2024\"\n\n$MINE=assignee:@me" }, { "kind": 1, @@ -147,7 +147,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:triage-needed -label:verification-found -label:*not-reproducible" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:triage-needed -label:verification-found -label:*not-reproducible" }, { "kind": 1, @@ -187,6 +187,6 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:on-release-notes\r\n$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:engineering -label:on-release-notes\r\n$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:plan-item -label:on-release-notes" + "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:on-release-notes\n$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:engineering -label:on-release-notes\n$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:plan-item -label:on-release-notes" } ] \ No newline at end of file From 4c753626d401b6cace9a27eb795325cbff10b07b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 2 Feb 2024 09:31:58 -0800 Subject: [PATCH 0885/1897] Use CHERE_INVOKING in msys2 profile Fixes #204167 --- src/vs/platform/terminal/node/terminalProfiles.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/platform/terminal/node/terminalProfiles.ts b/src/vs/platform/terminal/node/terminalProfiles.ts index eef82654029..e289fbc34fc 100644 --- a/src/vs/platform/terminal/node/terminalProfiles.ts +++ b/src/vs/platform/terminal/node/terminalProfiles.ts @@ -127,6 +127,8 @@ async function detectAvailableWindowsProfiles( { path: `${process.env['HOMEDRIVE']}\\msys64\\usr\\bin\\bash.exe`, isUnsafe: true }, ], args: ['--login', '-i'], + // CHERE_INVOKING retains current working directory + env: { CHERE_INVOKING: '1' }, icon: Codicon.terminalBash, isAutoDetected: true }); From 5e3a912d7f08afb0eaebe88b4f4b1193cb52fd24 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 2 Feb 2024 14:41:13 -0300 Subject: [PATCH 0886/1897] Move welcome message to chat agent (#204163) * Move welcome message provider to default agent * Remove welcome message/sample questions from interactive provider * Remove provider display name and icon * Add default agent for tests * And proposal --- extensions/vscode-api-tests/package.json | 1 + .../src/singlefolder-tests/chat.test.ts | 1 + .../workbench/api/browser/mainThreadChat.ts | 7 --- .../api/browser/mainThreadChatAgents2.ts | 6 ++ .../workbench/api/common/extHost.protocol.ts | 4 +- src/vs/workbench/api/common/extHostChat.ts | 46 -------------- .../api/common/extHostChatAgents2.ts | 60 +++++++++++++++++- .../contrib/chat/common/chatAgents.ts | 5 +- .../chat/common/chatContributionService.ts | 3 - .../contrib/chat/common/chatService.ts | 5 -- .../contrib/chat/common/chatServiceImpl.ts | 10 ++- .../chat/test/common/chatService.test.ts | 2 - .../browser/commandsQuickAccess.ts | 62 +++++++++---------- .../vscode.proposed.chatAgents2.d.ts | 4 +- .../vscode.proposed.defaultChatAgent.d.ts | 9 +++ .../vscode.proposed.interactive.d.ts | 2 - 16 files changed, 121 insertions(+), 106 deletions(-) diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index abb85f8f902..baf38d7c6fb 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -7,6 +7,7 @@ "enabledApiProposals": [ "authSession", "chatAgents2", + "defaultChatAgent", "contribViewsRemote", "contribStatusBarItems", "createFileSystemWatcher", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index 38be992edae..fd23f14d507 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -36,6 +36,7 @@ suite('chat', () => { deferred.complete(request); return null; }); + agent.isDefault = true; agent.subCommandProvider = { provideSubCommands: (_token) => { return [{ name: 'hello', description: 'Hello' }]; diff --git a/src/vs/workbench/api/browser/mainThreadChat.ts b/src/vs/workbench/api/browser/mainThreadChat.ts index a67f5d4b250..c7155e716a9 100644 --- a/src/vs/workbench/api/browser/mainThreadChat.ts +++ b/src/vs/workbench/api/browser/mainThreadChat.ts @@ -49,7 +49,6 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { const unreg = this._chatService.registerProvider({ id, - displayName: registration.label, prepareSession: async (token) => { const session = await this._proxy.$prepareChat(handle, token); if (!session) { @@ -75,12 +74,6 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { } }; }, - provideWelcomeMessage: (token) => { - return this._proxy.$provideWelcomeMessage(handle, token); - }, - provideSampleQuestions: (token) => { - return this._proxy.$provideSampleQuestions(handle, token); - }, }); this._providerRegistrations.set(handle, unreg); diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index 8ed8650fffc..0f1263149b9 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -103,6 +103,12 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA } lastSlashCommands = await this._proxy.$provideSlashCommands(handle, token); return lastSlashCommands; + }, + provideWelcomeMessage: (token: CancellationToken) => { + return this._proxy.$provideWelcomeMessage(handle, token); + }, + provideSampleQuestions: (token: CancellationToken) => { + return this._proxy.$provideSampleQuestions(handle, token); } }); this._agents.set(handle, { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b4605fd2111..8dec15bdd3a 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1222,6 +1222,8 @@ export interface ExtHostChatAgentsShape2 { $acceptFeedback(handle: number, sessionId: string, requestId: string, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void; $acceptAction(handle: number, sessionId: string, requestId: string, action: IChatUserActionEvent): void; $invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise; + $provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined>; + $provideSampleQuestions(handle: number, token: CancellationToken): Promise; $releaseSession(sessionId: string): void; } @@ -1305,8 +1307,6 @@ export interface MainThreadChatShape extends IDisposable { export interface ExtHostChatShape { $prepareChat(handle: number, token: CancellationToken): Promise; - $provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IMarkdownString | IChatReplyFollowup[])[] | undefined>; - $provideSampleQuestions(handle: number, token: CancellationToken): Promise; $releaseSession(sessionId: number): void; } diff --git a/src/vs/workbench/api/common/extHostChat.ts b/src/vs/workbench/api/common/extHostChat.ts index 74935c0e51f..d125a8b8f79 100644 --- a/src/vs/workbench/api/common/extHostChat.ts +++ b/src/vs/workbench/api/common/extHostChat.ts @@ -4,13 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Iterable } from 'vs/base/common/iterator'; import { toDisposable } from 'vs/base/common/lifecycle'; import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtHostChatShape, IChatDto, IMainContext, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; -import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { IChatReplyFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import type * as vscode from 'vscode'; class ChatProviderWrapper { @@ -89,49 +86,6 @@ export class ExtHostChat implements ExtHostChatShape { }; } - async $provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IMarkdownString | IChatReplyFollowup[])[] | undefined> { - const entry = this._chatProvider.get(handle); - if (!entry) { - return undefined; - } - - if (!entry.provider.provideWelcomeMessage) { - return undefined; - } - - const content = await entry.provider.provideWelcomeMessage(token); - if (!content) { - return undefined; - } - return content.map(item => { - if (typeof item === 'string') { - return item; - } else if (Array.isArray(item)) { - return item.map(f => typeConvert.ChatReplyFollowup.from(f)); - } else { - return typeConvert.MarkdownString.from(item); - } - }); - } - - async $provideSampleQuestions(handle: number, token: CancellationToken): Promise { - const entry = this._chatProvider.get(handle); - if (!entry) { - return undefined; - } - - if (!entry.provider.provideSampleQuestions) { - return undefined; - } - - const rawFollowups = await entry.provider.provideSampleQuestions(token); - if (!rawFollowups) { - return undefined; - } - - return rawFollowups?.map(f => typeConvert.ChatReplyFollowup.from(f)); - } - $releaseSession(sessionId: number) { this._chatSessions.delete(sessionId); } diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 846e790ab47..8079affd1db 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -8,6 +8,7 @@ import { DeferredPromise, raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; @@ -19,7 +20,7 @@ import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { IChatAgentCommand, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatFollowup, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatReplyFollowup, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import type * as vscode from 'vscode'; @@ -218,12 +219,30 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const items = await agent.invokeCompletionProvider(query, token); return items.map(typeConvert.ChatAgentCompletionItem.from); } + + async $provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined> { + const agent = this._agents.get(handle); + if (!agent) { + return; + } + + return await agent.provideWelcomeMessage(token); + } + + async $provideSampleQuestions(handle: number, token: CancellationToken): Promise { + const agent = this._agents.get(handle); + if (!agent) { + return; + } + + return await agent.provideSampleQuestions(token); + } } class ExtHostChatAgent { private _subCommandProvider: vscode.ChatAgentSubCommandProvider | undefined; - private _followupProvider: vscode.FollowupProvider | undefined; + private _followupProvider: vscode.ChatAgentFollowupProvider | undefined; private _description: string | undefined; private _fullName: string | undefined; private _iconPath: vscode.Uri | { light: vscode.Uri; dark: vscode.Uri } | vscode.ThemeIcon | undefined; @@ -236,6 +255,7 @@ class ExtHostChatAgent { private _onDidPerformAction = new Emitter(); private _supportIssueReporting: boolean | undefined; private _agentVariableProvider?: { provider: vscode.ChatAgentCompletionItemProvider; triggerCharacters: string[] }; + private _welcomeMessageProvider?: vscode.ChatAgentWelcomeMessageProvider | undefined; constructor( public readonly extension: IExtensionDescription, @@ -290,6 +310,35 @@ class ExtHostChatAgent { return followups.map(f => typeConvert.ChatFollowup.from(f)); } + async provideWelcomeMessage(token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined> { + if (!this._welcomeMessageProvider) { + return []; + } + const content = await this._welcomeMessageProvider.provideWelcomeMessage(token); + if (!content) { + return []; + } + return content.map(item => { + if (typeof item === 'string') { + return item; + } else { + return typeConvert.MarkdownString.from(item); + } + }); + } + + async provideSampleQuestions(token: CancellationToken): Promise { + if (!this._welcomeMessageProvider || !this._welcomeMessageProvider.provideSampleQuestions) { + return []; + } + const content = await this._welcomeMessageProvider.provideSampleQuestions(token); + if (!content) { + return []; + } + + return content?.map(f => typeConvert.ChatReplyFollowup.from(f)); + } + get apiAgent(): vscode.ChatAgent2 { let disposed = false; let updateScheduled = false; @@ -444,6 +493,13 @@ class ExtHostChatAgent { get agentVariableProvider() { return that._agentVariableProvider; }, + set welcomeMessageProvider(v) { + that._welcomeMessageProvider = v; + updateMetadataSoon(); + }, + get welcomeMessageProvider() { + return that._welcomeMessageProvider; + }, onDidPerformAction: !isProposedApiEnabled(this.extension, 'chatAgents2Additions') ? undefined! : this._onDidPerformAction.event diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 1bdaf8ad424..6b71960d119 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -10,9 +10,10 @@ import { Iterable } from 'vs/base/common/iterator'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; +import { ProviderResult } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; //#region agent service, commands etc @@ -33,6 +34,8 @@ export interface IChatAgent extends IChatAgentData { provideFollowups?(sessionId: string, token: CancellationToken): Promise; lastSlashCommands?: IChatAgentCommand[]; provideSlashCommands(token: CancellationToken): Promise; + provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>; + provideSampleQuestions?(token: CancellationToken): ProviderResult; } export interface IChatAgentCommand { diff --git a/src/vs/workbench/contrib/chat/common/chatContributionService.ts b/src/vs/workbench/contrib/chat/common/chatContributionService.ts index 24720409f21..2c43c2af703 100644 --- a/src/vs/workbench/contrib/chat/common/chatContributionService.ts +++ b/src/vs/workbench/contrib/chat/common/chatContributionService.ts @@ -3,13 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export interface IChatProviderContribution { id: string; - label: string; - extensionIcon?: URI; when?: string; } diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 52f6bcceb6d..9a9c1196e72 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -156,11 +156,7 @@ export type IChatProgress = export interface IChatProvider { readonly id: string; - readonly displayName: string; - readonly iconUrl?: string; prepareSession(token: CancellationToken): ProviderResult; - provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString | IChatReplyFollowup[])[] | undefined>; - provideSampleQuestions?(token: CancellationToken): ProviderResult; } export interface IChatReplyFollowup { @@ -269,7 +265,6 @@ export interface IChatDetail { export interface IChatProviderInfo { id: string; - displayName: string; } export interface IChatTransferredSessionData { diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 9574afe19d6..c55c438f228 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -384,11 +384,16 @@ export class ChatService extends Disposable implements IChatService { this.trace('startSession', `Provider returned session`); - const welcomeMessage = model.welcomeMessage ? undefined : await provider.provideWelcomeMessage?.(token) ?? undefined; + const defaultAgent = this.chatAgentService.getDefaultAgent(); + if (!defaultAgent) { + throw new Error('No default agent'); + } + + const welcomeMessage = model.welcomeMessage ? undefined : await defaultAgent.provideWelcomeMessage?.(token) ?? undefined; const welcomeModel = welcomeMessage && new ChatWelcomeMessageModel( model, welcomeMessage.map(item => typeof item === 'string' ? new MarkdownString(item) : item), - await provider.provideSampleQuestions?.(token) ?? [] + await defaultAgent.provideSampleQuestions?.(token) ?? [] ); model.initialize(session, welcomeModel); @@ -733,7 +738,6 @@ export class ChatService extends Disposable implements IChatService { return Array.from(this._providers.values()).map(provider => { return { id: provider.id, - displayName: provider.displayName }; }); } 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 c885bf9444d..4bd2322fc6e 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -172,7 +172,6 @@ suite('Chat', () => { const id = 'testProvider'; testDisposables.add(testService.registerProvider({ id, - displayName: 'Test', prepareSession: function (token: CancellationToken): ProviderResult { throw new Error('Function not implemented.'); } @@ -181,7 +180,6 @@ suite('Chat', () => { assert.throws(() => { testDisposables.add(testService.registerProvider({ id, - displayName: 'Test', prepareSession: function (token: CancellationToken): ProviderResult { throw new Error('Function not implemented.'); } diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index ac98c4c386f..3d492fd2e02 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -3,41 +3,41 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize, localize2 } from 'vs/nls'; -import { ICommandQuickPick, CommandsHistory } from 'vs/platform/quickinput/browser/commandsQuickAccess'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IMenuService, MenuId, MenuItemAction, SubmenuItemAction, Action2 } from 'vs/platform/actions/common/actions'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { isFirefox } from 'vs/base/browser/browser'; import { raceTimeout, timeout } from 'vs/base/common/async'; -import { AbstractEditorCommandsQuickAccessProvider } from 'vs/editor/contrib/quickAccess/browser/commandsQuickAccess'; -import { IEditor } from 'vs/editor/common/editorCommon'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Codicon } from 'vs/base/common/codicons'; +import { stripIcons } from 'vs/base/common/iconLabels'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Language } from 'vs/base/common/platform'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { IEditor } from 'vs/editor/common/editorCommon'; +import { AbstractEditorCommandsQuickAccessProvider } from 'vs/editor/contrib/quickAccess/browser/commandsQuickAccess'; +import { localize, localize2 } from 'vs/nls'; +import { isLocalizedString } from 'vs/platform/action/common/action'; +import { Action2, IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; 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'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { CommandsHistory, ICommandQuickPick } from 'vs/platform/quickinput/browser/commandsQuickAccess'; +import { TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { DefaultQuickAccessFilterValue } from 'vs/platform/quickinput/common/quickAccess'; -import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkbenchQuickAccessConfiguration } from 'vs/workbench/browser/quickaccess'; -import { Codicon } from 'vs/base/common/codicons'; -import { ThemeIcon } from 'vs/base/common/themables'; import { IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; -import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { stripIcons } from 'vs/base/common/iconLabels'; -import { isFirefox } from 'vs/base/browser/browser'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { ASK_QUICK_QUESTION_ACTION_ID } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions'; -import { CommandInformationResult, IAiRelatedInformationService, RelatedInformationType } from 'vs/workbench/services/aiRelatedInformation/common/aiRelatedInformation'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IWorkbenchQuickAccessConfiguration } from 'vs/workbench/browser/quickaccess'; import { CHAT_OPEN_ACTION_ID } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; -import { isLocalizedString } from 'vs/platform/action/common/action'; +import { ASK_QUICK_QUESTION_ACTION_ID } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { CommandInformationResult, IAiRelatedInformationService, RelatedInformationType } from 'vs/workbench/services/aiRelatedInformation/common/aiRelatedInformation'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { @@ -77,7 +77,7 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce @IPreferencesService private readonly preferencesService: IPreferencesService, @IProductService private readonly productService: IProductService, @IAiRelatedInformationService private readonly aiRelatedInformationService: IAiRelatedInformationService, - @IChatService private readonly chatService: IChatService + @IChatAgentService private readonly chatAgentService: IChatAgentService, ) { super({ showAlias: !Language.isDefaultVariant(), @@ -172,10 +172,10 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce }); } - const info = this.chatService.getProviderInfos()[0]; - if (info) { + const defaultAgent = this.chatAgentService.getDefaultAgent(); + if (defaultAgent) { additionalPicks.push({ - label: localize('askXInChat', "Ask {0}: {1}", info.displayName, filter), + label: localize('askXInChat', "Ask {0}: {1}", defaultAgent.metadata.fullName, filter), commandId: this.configuration.experimental.askChatLocation === 'quickChat' ? ASK_QUICK_QUESTION_ACTION_ID : CHAT_OPEN_ACTION_ID, args: [filter] }); diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 50451707b22..d23923fea04 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -181,7 +181,7 @@ declare module 'vscode' { /** * Will be invoked once after each request to get suggested followup questions to show the user. The user can click the followup to send it to the chat. */ - export interface FollowupProvider { + export interface ChatAgentFollowupProvider { /** * * @param result The same instance of the result object that was returned by the chat agent, and it can be extended with arbitrary properties if needed. @@ -229,7 +229,7 @@ declare module 'vscode' { /** * This provider will be called once after each request to retrieve suggested followup questions. */ - followupProvider?: FollowupProvider; + followupProvider?: ChatAgentFollowupProvider; /** * When the user clicks this agent in `/help`, this text will be submitted to this subCommand diff --git a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts b/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts index cc671da4e69..333783f844a 100644 --- a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts +++ b/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts @@ -5,6 +5,13 @@ declare module 'vscode' { + export type ChatAgentWelcomeMessageContent = string | MarkdownString; + + export interface ChatAgentWelcomeMessageProvider { + provideWelcomeMessage(token: CancellationToken): ProviderResult; + provideSampleQuestions?(token: CancellationToken): ProviderResult; + } + export interface ChatAgent2 { /** * When true, this agent is invoked by default when no other agent is being invoked @@ -26,5 +33,7 @@ declare module 'vscode' { * A string that will be appended after the listing of chat agents in `/help`. */ helpTextPostfix?: string | MarkdownString; + + welcomeMessageProvider?: ChatAgentWelcomeMessageProvider; } } diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index 3b3e6e53c31..fd8dc51d014 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -124,8 +124,6 @@ declare module 'vscode' { export type InteractiveWelcomeMessageContent = string | MarkdownString | ChatAgentReplyFollowup[]; export interface InteractiveSessionProvider { - provideWelcomeMessage?(token: CancellationToken): ProviderResult; - provideSampleQuestions?(token: CancellationToken): ProviderResult; prepareSession(token: CancellationToken): ProviderResult; } From 327c89ae896ee3011930784e2d8857363a1ace32 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 2 Feb 2024 10:12:52 -0800 Subject: [PATCH 0887/1897] exthost: preserve worker name when creating nested workers (#204169) This allows js-debug to identify which workers should be debugged Fixes https://github.com/microsoft/vscode/issues/204114, along with corresponding js-debug tweaks --- src/vs/workbench/api/worker/extensionHostWorker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/worker/extensionHostWorker.ts b/src/vs/workbench/api/worker/extensionHostWorker.ts index 6e6f8845da5..86136a080e6 100644 --- a/src/vs/workbench/api/worker/extensionHostWorker.ts +++ b/src/vs/workbench/api/worker/extensionHostWorker.ts @@ -21,7 +21,7 @@ import { URI } from 'vs/base/common/uri'; //#region --- Define, capture, and override some globals declare function postMessage(data: any, transferables?: Transferable[]): void; - +declare const name: string; // https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope/name declare type _Fetch = typeof fetch; declare namespace self { @@ -137,7 +137,7 @@ if ((self).Worker) { const js = `(${bootstrapFnSource}('${stringUrl}'))`; options = options || {}; - options.name = options.name || path.basename(stringUrl.toString()); + options.name = `${name} -> ${options.name || path.basename(stringUrl.toString())}`; const blob = new Blob([js], { type: 'application/javascript' }); const blobUrl = URL.createObjectURL(blob); return new _Worker(blobUrl, options); From 9f0b82ec5f446c6fb00d79e05348675274a6e508 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 2 Feb 2024 11:22:50 -0700 Subject: [PATCH 0888/1897] Disable exp in extension tests (#204175) --- src/vs/platform/assignment/common/assignmentService.ts | 6 ++++-- .../services/assignment/common/assignmentService.ts | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/assignment/common/assignmentService.ts b/src/vs/platform/assignment/common/assignmentService.ts index 67e34826627..c573b727e0b 100644 --- a/src/vs/platform/assignment/common/assignmentService.ts +++ b/src/vs/platform/assignment/common/assignmentService.ts @@ -10,6 +10,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { getTelemetryLevel } from 'vs/platform/telemetry/common/telemetryUtils'; import { AssignmentFilterProvider, ASSIGNMENT_REFETCH_INTERVAL, ASSIGNMENT_STORAGE_KEY, IAssignmentService, TargetPopulation } from 'vs/platform/assignment/common/assignment'; import { importAMDNodeModule } from 'vs/amdX'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export abstract class BaseAssignmentService implements IAssignmentService { _serviceBrand: undefined; @@ -25,11 +26,12 @@ export abstract class BaseAssignmentService implements IAssignmentService { private readonly machineId: string, protected readonly configurationService: IConfigurationService, protected readonly productService: IProductService, + protected readonly environmentService: IEnvironmentService, protected telemetry: IExperimentationTelemetry, private keyValueStorage?: IKeyValueStorage ) { - - if (productService.tasConfig && this.experimentsEnabled && getTelemetryLevel(this.configurationService) === TelemetryLevel.USAGE) { + const isTesting = environmentService.extensionTestsLocationURI !== undefined; + if (!isTesting && productService.tasConfig && this.experimentsEnabled && getTelemetryLevel(this.configurationService) === TelemetryLevel.USAGE) { this.tasClient = this.setupTASClient(); } diff --git a/src/vs/workbench/services/assignment/common/assignmentService.ts b/src/vs/workbench/services/assignment/common/assignmentService.ts index 0d0b424d22e..50c2ddb702c 100644 --- a/src/vs/workbench/services/assignment/common/assignmentService.ts +++ b/src/vs/workbench/services/assignment/common/assignmentService.ts @@ -18,6 +18,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { BaseAssignmentService } from 'vs/platform/assignment/common/assignmentService'; import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export const IWorkbenchAssignmentService = createDecorator('WorkbenchAssignmentService'); @@ -84,13 +85,15 @@ export class WorkbenchAssignmentService extends BaseAssignmentService { @ITelemetryService private telemetryService: ITelemetryService, @IStorageService storageService: IStorageService, @IConfigurationService configurationService: IConfigurationService, - @IProductService productService: IProductService + @IProductService productService: IProductService, + @IEnvironmentService environmentService: IEnvironmentService ) { super( telemetryService.machineId, configurationService, productService, + environmentService, new WorkbenchAssignmentServiceTelemetry(telemetryService, productService), new MementoKeyValueStorage(new Memento('experiment.service.memento', storageService)) ); From 17e46e241423b073f018a1aec89d1f8213d25bc9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 2 Feb 2024 20:01:35 +0100 Subject: [PATCH 0889/1897] move dynamic security settings contrib to after restore (#203947) (#204111) --- .../browser/workbench.contribution.ts | 9 +- src/vs/workbench/common/configuration.ts | 84 +++++++++++-------- src/vs/workbench/electron-sandbox/window.ts | 5 +- 3 files changed, 58 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index c0f395206f7..a74859f5e3c 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -7,10 +7,9 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { localize } from 'vs/nls'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform'; -import { ConfigurationMigrationWorkbenchContribution, DynamicWorkbenchConfigurationWorkbenchContribution, IConfigurationMigrationRegistry, workbenchConfigurationNodeBase, Extensions, ConfigurationKeyValuePairs, problemsConfigurationNodeBase } from 'vs/workbench/common/configuration'; +import { ConfigurationMigrationWorkbenchContribution, DynamicWorkbenchSecurityConfiguration, IConfigurationMigrationRegistry, workbenchConfigurationNodeBase, Extensions, ConfigurationKeyValuePairs, problemsConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { isStandalone } from 'vs/base/browser/browser'; -import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ActivityBarPosition, EditorActionsLocation, EditorTabsMode, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; const registry = Registry.as(ConfigurationExtensions.Configuration); @@ -19,10 +18,10 @@ const registry = Registry.as(ConfigurationExtensions.Con (function registerConfiguration(): void { // Migration support - Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ConfigurationMigrationWorkbenchContribution, LifecyclePhase.Eventually); + registerWorkbenchContribution2(ConfigurationMigrationWorkbenchContribution.ID, ConfigurationMigrationWorkbenchContribution, WorkbenchContributionInstantiation.Eventually); // Dynamic Configuration - registerWorkbenchContribution2(DynamicWorkbenchConfigurationWorkbenchContribution.ID, DynamicWorkbenchConfigurationWorkbenchContribution, WorkbenchContributionInstantiation.BlockRestore); + registerWorkbenchContribution2(DynamicWorkbenchSecurityConfiguration.ID, DynamicWorkbenchSecurityConfiguration, WorkbenchContributionInstantiation.AfterRestored); // Workbench registry.registerConfiguration({ diff --git a/src/vs/workbench/common/configuration.ts b/src/vs/workbench/common/configuration.ts index 67d90393d59..4e15bd458be 100644 --- a/src/vs/workbench/common/configuration.ts +++ b/src/vs/workbench/common/configuration.ts @@ -15,6 +15,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { OperatingSystem, isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { equals } from 'vs/base/common/objects'; +import { DeferredPromise } from 'vs/base/common/async'; export const applicationConfigurationNodeBase = Object.freeze({ 'id': 'application', @@ -76,6 +77,8 @@ Registry.add(Extensions.ConfigurationMigration, configurationMigrationRegistry); export class ConfigurationMigrationWorkbenchContribution extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.configurationMigration'; + constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, @@ -165,47 +168,60 @@ export class ConfigurationMigrationWorkbenchContribution extends Disposable impl } } -export class DynamicWorkbenchConfigurationWorkbenchContribution extends Disposable implements IWorkbenchContribution { +export class DynamicWorkbenchSecurityConfiguration extends Disposable implements IWorkbenchContribution { - static readonly ID = 'workbench.contrib.dynamicWorkbenchConfiguration'; + static readonly ID = 'workbench.contrib.dynamicWorkbenchSecurityConfiguration'; + + private readonly _ready = new DeferredPromise(); + readonly ready = this._ready.p; constructor( - @IRemoteAgentService remoteAgentService: IRemoteAgentService + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService ) { super(); - (async () => { - if (!isWindows) { - const remoteEnvironment = await remoteAgentService.getEnvironment(); - if (remoteEnvironment?.os !== OperatingSystem.Windows) { - return; + this.create(); + } + + private async create(): Promise { + try { + await this.doCreate(); + } finally { + this._ready.complete(); + } + } + + private async doCreate(): Promise { + if (!isWindows) { + const remoteEnvironment = await this.remoteAgentService.getEnvironment(); + if (remoteEnvironment?.os !== OperatingSystem.Windows) { + return; + } + } + + // Windows: UNC allow list security configuration + const registry = Registry.as(ConfigurationExtensions.Configuration); + registry.registerConfiguration({ + ...securityConfigurationNodeBase, + 'properties': { + 'security.allowedUNCHosts': { + 'type': 'array', + 'items': { + 'type': 'string', + 'pattern': '^[^\\\\]+$', + 'patternErrorMessage': localize('security.allowedUNCHosts.patternErrorMessage', 'UNC host names must not contain backslashes.') + }, + 'default': [], + 'markdownDescription': localize('security.allowedUNCHosts', 'A set of UNC host names (without leading or trailing backslash, for example `192.168.0.1` or `my-server`) to allow without user confirmation. If a UNC host is being accessed that is not allowed via this setting or has not been acknowledged via user confirmation, an error will occur and the operation stopped. A restart is required when changing this setting. Find out more about this setting at https://aka.ms/vscode-windows-unc.'), + 'scope': ConfigurationScope.MACHINE + }, + 'security.restrictUNCAccess': { + 'type': 'boolean', + 'default': true, + 'markdownDescription': localize('security.restrictUNCAccess', 'If enabled, only allows access to UNC host names that are allowed by the `#security.allowedUNCHosts#` setting or after user confirmation. Find out more about this setting at https://aka.ms/vscode-windows-unc.'), + 'scope': ConfigurationScope.MACHINE } } - - // Windows: UNC allow list security configuration - const registry = Registry.as(ConfigurationExtensions.Configuration); - registry.registerConfiguration({ - ...securityConfigurationNodeBase, - 'properties': { - 'security.allowedUNCHosts': { - 'type': 'array', - 'items': { - 'type': 'string', - 'pattern': '^[^\\\\]+$', - 'patternErrorMessage': localize('security.allowedUNCHosts.patternErrorMessage', 'UNC host names must not contain backslashes.') - }, - 'default': [], - 'markdownDescription': localize('security.allowedUNCHosts', 'A set of UNC host names (without leading or trailing backslash, for example `192.168.0.1` or `my-server`) to allow without user confirmation. If a UNC host is being accessed that is not allowed via this setting or has not been acknowledged via user confirmation, an error will occur and the operation stopped. A restart is required when changing this setting. Find out more about this setting at https://aka.ms/vscode-windows-unc.'), - 'scope': ConfigurationScope.MACHINE - }, - 'security.restrictUNCAccess': { - 'type': 'boolean', - 'default': true, - 'markdownDescription': localize('security.restrictUNCAccess', 'If enabled, only allows access to UNC host names that are allowed by the `#security.allowedUNCHosts#` setting or after user confirmation. Find out more about this setting at https://aka.ms/vscode-windows-unc.'), - 'scope': ConfigurationScope.MACHINE - } - } - }); - })(); + }); } } diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 46fd0515a1f..35136a73c5d 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -77,6 +77,8 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IStatusbarService, ShowTooltipCommand, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { ThemeIcon } from 'vs/base/common/themables'; +import { getWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { DynamicWorkbenchSecurityConfiguration } from 'vs/workbench/common/configuration'; export class NativeWindow extends BaseWindow { @@ -301,7 +303,7 @@ export class NativeWindow extends BaseWindow { }); // Allow to update security settings around allowed UNC Host - ipcRenderer.on('vscode:configureAllowedUNCHost', (event: unknown, host: string) => { + ipcRenderer.on('vscode:configureAllowedUNCHost', async (event: unknown, host: string) => { if (!isWindows) { return; // only supported on Windows } @@ -320,6 +322,7 @@ export class NativeWindow extends BaseWindow { if (!allowedUncHosts.has(host)) { allowedUncHosts.add(host); + await getWorkbenchContribution(DynamicWorkbenchSecurityConfiguration.ID).ready; // ensure this setting is registered this.configurationService.updateValue('security.allowedUNCHosts', [...allowedUncHosts.values()], ConfigurationTarget.USER); } }); From dbdf5813d70f6368530a517de9b8d87508c6e9c5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 2 Feb 2024 11:15:55 -0800 Subject: [PATCH 0890/1897] Update xterm.js Fixes #204104 --- package.json | 16 +++++------ remote/package.json | 16 +++++------ remote/web/package.json | 14 ++++----- remote/web/yarn.lock | 56 ++++++++++++++++++------------------ remote/yarn.lock | 64 ++++++++++++++++++++--------------------- yarn.lock | 64 ++++++++++++++++++++--------------------- 6 files changed, 115 insertions(+), 115 deletions(-) diff --git a/package.json b/package.json index f34e85772c4..e5949c93c22 100644 --- a/package.json +++ b/package.json @@ -80,14 +80,14 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-canvas": "0.6.0-beta.30", - "@xterm/addon-image": "0.7.0-beta.28", - "@xterm/addon-search": "0.14.0-beta.30", - "@xterm/addon-serialize": "0.12.0-beta.30", - "@xterm/addon-unicode11": "0.7.0-beta.30", - "@xterm/addon-webgl": "0.17.0-beta.30", - "@xterm/headless": "5.4.0-beta.30", - "@xterm/xterm": "5.4.0-beta.30", + "@xterm/addon-canvas": "0.6.0-beta.31", + "@xterm/addon-image": "0.7.0-beta.29", + "@xterm/addon-search": "0.14.0-beta.31", + "@xterm/addon-serialize": "0.12.0-beta.31", + "@xterm/addon-unicode11": "0.7.0-beta.31", + "@xterm/addon-webgl": "0.17.0-beta.31", + "@xterm/headless": "5.4.0-beta.31", + "@xterm/xterm": "5.4.0-beta.31", "graceful-fs": "4.2.11", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", diff --git a/remote/package.json b/remote/package.json index d2b3fb7fb8b..1d341b00a20 100644 --- a/remote/package.json +++ b/remote/package.json @@ -13,14 +13,14 @@ "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-canvas": "0.6.0-beta.30", - "@xterm/addon-image": "0.7.0-beta.28", - "@xterm/addon-search": "0.14.0-beta.30", - "@xterm/addon-serialize": "0.12.0-beta.30", - "@xterm/addon-unicode11": "0.7.0-beta.30", - "@xterm/addon-webgl": "0.17.0-beta.30", - "@xterm/headless": "5.4.0-beta.30", - "@xterm/xterm": "5.4.0-beta.30", + "@xterm/addon-canvas": "0.6.0-beta.31", + "@xterm/addon-image": "0.7.0-beta.29", + "@xterm/addon-search": "0.14.0-beta.31", + "@xterm/addon-serialize": "0.12.0-beta.31", + "@xterm/addon-unicode11": "0.7.0-beta.31", + "@xterm/addon-webgl": "0.17.0-beta.31", + "@xterm/headless": "5.4.0-beta.31", + "@xterm/xterm": "5.4.0-beta.31", "cookie": "^0.4.0", "graceful-fs": "4.2.11", "http-proxy-agent": "^7.0.0", diff --git a/remote/web/package.json b/remote/web/package.json index c24d8c77df8..e891be054dd 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -7,13 +7,13 @@ "@microsoft/1ds-post-js": "^3.2.13", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-canvas": "0.6.0-beta.30", - "@xterm/addon-image": "0.7.0-beta.28", - "@xterm/addon-search": "0.14.0-beta.30", - "@xterm/addon-serialize": "0.12.0-beta.30", - "@xterm/addon-unicode11": "0.7.0-beta.30", - "@xterm/addon-webgl": "0.17.0-beta.30", - "@xterm/xterm": "5.4.0-beta.30", + "@xterm/addon-canvas": "0.6.0-beta.31", + "@xterm/addon-image": "0.7.0-beta.29", + "@xterm/addon-search": "0.14.0-beta.31", + "@xterm/addon-serialize": "0.12.0-beta.31", + "@xterm/addon-unicode11": "0.7.0-beta.31", + "@xterm/addon-webgl": "0.17.0-beta.31", + "@xterm/xterm": "5.4.0-beta.31", "jschardet": "3.0.0", "tas-client-umd": "0.1.8", "vscode-oniguruma": "1.7.0", diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index e6f9e15bd03..1af0f2be779 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -48,40 +48,40 @@ resolved "https://registry.yarnpkg.com/@vscode/vscode-languagedetection/-/vscode-languagedetection-1.0.21.tgz#89b48f293f6aa3341bb888c1118d16ff13b032d3" integrity sha512-zSUH9HYCw5qsCtd7b31yqkpaCU6jhtkKLkvOOA8yTrIRfBSOFb8PPhgmMicD7B/m+t4PwOJXzU1XDtrM9Fd3/g== -"@xterm/addon-canvas@0.6.0-beta.30": - version "0.6.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.30.tgz#71139253d6ba037a2a6f26895c6e45363d9f496f" - integrity sha512-rDnIgtxOrRxwDiTHec3k0+K9PVeGR79RM7kjuOd39JEHRiH9JqPjwLHLAuN4RkCDh3fHbK/rTMYe73uWj5FvBw== +"@xterm/addon-canvas@0.6.0-beta.31": + version "0.6.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.31.tgz#17cc7d9968ede411fb23db11813b495435c068a0" + integrity sha512-jm/7FWZOgnAGG7MXjr0W4SnuIzsag+oVpyf6wAD9UlCgq5HBuk/3kJ5mYGiGR7CpdTxqXmzyBk3OhQe8npZ1aQ== -"@xterm/addon-image@0.7.0-beta.28": - version "0.7.0-beta.28" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.28.tgz#c06fe0c581e5c32b71a56b8c31b16580a596ae50" - integrity sha512-EIw+8vQNrl5i9rN+crc4DmYLcN66zdED1pEppShAFyQjwT+yLigbcF4QQa00eeq8xzG4NEnQbRb5R+4ZR1/G1Q== +"@xterm/addon-image@0.7.0-beta.29": + version "0.7.0-beta.29" + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.29.tgz#276b56007c9009e7a59605dc3809c280e7d637ed" + integrity sha512-Z5JCuhl0AcwQA+DE/kQMeSSHZbfwJVLUUBodDeujVItQrcpc9vA8mxf/qIwS3XTA/tPbFihfc/CE9zL7OFdbaw== -"@xterm/addon-search@0.14.0-beta.30": - version "0.14.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.30.tgz#313a44258121ed30205e18170a2865e0a432f725" - integrity sha512-x4bjV++cWWHg+HZ+WyOw76AjOe+lvh/rQF7XF1wO40ROLDSR9jBUH1oN9uR4Yr4XxxFgXLEdpswMhRYKrSJG/A== +"@xterm/addon-search@0.14.0-beta.31": + version "0.14.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.31.tgz#e6edcd257f5a66bca7e92e62684132b604fb817d" + integrity sha512-SS4CdgciLT98Uc4Dq0IjJegHcGIjGaASTcMtVkNBx9dOat9xt6lCXmtgUUj5w0KlB8nUfKrcy5T6fHgzrOzvrw== -"@xterm/addon-serialize@0.12.0-beta.30": - version "0.12.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.30.tgz#3472fa8cbf7d43603da558fe5b95bbeee7f7349f" - integrity sha512-AQC3Yf0Ld5uURJkMak/pJGsuSaajt1hdKabFPz7MYtnCy5KHsY97yIGFkUPGYKqNg6nT7gOCS2P/tCGjDaKOqA== +"@xterm/addon-serialize@0.12.0-beta.31": + version "0.12.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.31.tgz#daec32b94d45afcd662351d7689cb1b19eb24db7" + integrity sha512-MZ24pw33qOJrHdA6tlvwE4dSSpmIp/H9ZKtbiWZvuxVsY/hfYYPOluBQiCsOiYT7bZ8gQub2OOBX3jyMoZVxnQ== -"@xterm/addon-unicode11@0.7.0-beta.30": - version "0.7.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.30.tgz#49e0caebed508f8d684363c48c09da5cb1d06619" - integrity sha512-FzN+L2rvXAwsjGs+UyySPz8e6EvgZNQImX63XqUOIvPnpLWkFLc+ket4n8Bu0MwmZg99atqxepEGNpXVDRPY9A== +"@xterm/addon-unicode11@0.7.0-beta.31": + version "0.7.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.31.tgz#e1a6e965638ee6cb59b8b0777387037c42582d4b" + integrity sha512-wrZLt2s6Yjmpe4nh0Sp6DKji0EoHod7V6ABfWBf8krjmEGSleE+GSb+ZwDOMsNzLJLmxoq1e6glHcVixG1z7WQ== -"@xterm/addon-webgl@0.17.0-beta.30": - version "0.17.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.30.tgz#159fa6bfa8e68f37c54a9a3fbf505d389f6c9374" - integrity sha512-n/4ncwUXc6rRyYA+KDVv5e/VnF/OCo4bFqycWf6wNFSEvD1AJavHm/CHi//p9rPC5gLzulKuTJdOj4jQ5St+Yg== +"@xterm/addon-webgl@0.17.0-beta.31": + version "0.17.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.31.tgz#15dfea4583ff9b65f1a442e5cdba1d1638adb05f" + integrity sha512-wqbBDDppwQ4R8o0YgnyFL8Pai2mVZqHb3E097vkFLB5Fw2hNx2dys3MgiXriSGXaUABKM3usVdZyouL6QgWdxQ== -"@xterm/xterm@5.4.0-beta.30": - version "5.4.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.30.tgz#79d913f0ca0c2cd357e5f3f938d48a99d0ee4e5c" - integrity sha512-ShLgCpnA9WH4EMcasJYuWoUCGu5iHp0aZNsu5L/dw16CJZ7S1lkPg2dcVu3m/agOUZDJkaHiSG7j6V+P9ejvQQ== +"@xterm/xterm@5.4.0-beta.31": + version "5.4.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.31.tgz#ff0bb3af9b00b0dfc73e84075f4218440c9886be" + integrity sha512-EpCtaYqMhJSyZrGY2sJVZeRCIRrANKtv1GGTj+IQPvk6hTiJHGrFHLM0tZ0dj0l3z65tLoOdj6EzJnjzX3Pqjw== jschardet@3.0.0: version "3.0.0" diff --git a/remote/yarn.lock b/remote/yarn.lock index f901b1a2c07..e660692a806 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -114,45 +114,45 @@ resolved "https://registry.yarnpkg.com/@vscode/windows-registry/-/windows-registry-1.1.0.tgz#03dace7c29c46f658588b9885b9580e453ad21f9" integrity sha512-5AZzuWJpGscyiMOed0IuyEwt6iKmV5Us7zuwCDCFYMIq7tsvooO9BUiciywsvuthGz6UG4LSpeDeCxvgMVhnIw== -"@xterm/addon-canvas@0.6.0-beta.30": - version "0.6.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.30.tgz#71139253d6ba037a2a6f26895c6e45363d9f496f" - integrity sha512-rDnIgtxOrRxwDiTHec3k0+K9PVeGR79RM7kjuOd39JEHRiH9JqPjwLHLAuN4RkCDh3fHbK/rTMYe73uWj5FvBw== +"@xterm/addon-canvas@0.6.0-beta.31": + version "0.6.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.31.tgz#17cc7d9968ede411fb23db11813b495435c068a0" + integrity sha512-jm/7FWZOgnAGG7MXjr0W4SnuIzsag+oVpyf6wAD9UlCgq5HBuk/3kJ5mYGiGR7CpdTxqXmzyBk3OhQe8npZ1aQ== -"@xterm/addon-image@0.7.0-beta.28": - version "0.7.0-beta.28" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.28.tgz#c06fe0c581e5c32b71a56b8c31b16580a596ae50" - integrity sha512-EIw+8vQNrl5i9rN+crc4DmYLcN66zdED1pEppShAFyQjwT+yLigbcF4QQa00eeq8xzG4NEnQbRb5R+4ZR1/G1Q== +"@xterm/addon-image@0.7.0-beta.29": + version "0.7.0-beta.29" + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.29.tgz#276b56007c9009e7a59605dc3809c280e7d637ed" + integrity sha512-Z5JCuhl0AcwQA+DE/kQMeSSHZbfwJVLUUBodDeujVItQrcpc9vA8mxf/qIwS3XTA/tPbFihfc/CE9zL7OFdbaw== -"@xterm/addon-search@0.14.0-beta.30": - version "0.14.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.30.tgz#313a44258121ed30205e18170a2865e0a432f725" - integrity sha512-x4bjV++cWWHg+HZ+WyOw76AjOe+lvh/rQF7XF1wO40ROLDSR9jBUH1oN9uR4Yr4XxxFgXLEdpswMhRYKrSJG/A== +"@xterm/addon-search@0.14.0-beta.31": + version "0.14.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.31.tgz#e6edcd257f5a66bca7e92e62684132b604fb817d" + integrity sha512-SS4CdgciLT98Uc4Dq0IjJegHcGIjGaASTcMtVkNBx9dOat9xt6lCXmtgUUj5w0KlB8nUfKrcy5T6fHgzrOzvrw== -"@xterm/addon-serialize@0.12.0-beta.30": - version "0.12.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.30.tgz#3472fa8cbf7d43603da558fe5b95bbeee7f7349f" - integrity sha512-AQC3Yf0Ld5uURJkMak/pJGsuSaajt1hdKabFPz7MYtnCy5KHsY97yIGFkUPGYKqNg6nT7gOCS2P/tCGjDaKOqA== +"@xterm/addon-serialize@0.12.0-beta.31": + version "0.12.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.31.tgz#daec32b94d45afcd662351d7689cb1b19eb24db7" + integrity sha512-MZ24pw33qOJrHdA6tlvwE4dSSpmIp/H9ZKtbiWZvuxVsY/hfYYPOluBQiCsOiYT7bZ8gQub2OOBX3jyMoZVxnQ== -"@xterm/addon-unicode11@0.7.0-beta.30": - version "0.7.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.30.tgz#49e0caebed508f8d684363c48c09da5cb1d06619" - integrity sha512-FzN+L2rvXAwsjGs+UyySPz8e6EvgZNQImX63XqUOIvPnpLWkFLc+ket4n8Bu0MwmZg99atqxepEGNpXVDRPY9A== +"@xterm/addon-unicode11@0.7.0-beta.31": + version "0.7.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.31.tgz#e1a6e965638ee6cb59b8b0777387037c42582d4b" + integrity sha512-wrZLt2s6Yjmpe4nh0Sp6DKji0EoHod7V6ABfWBf8krjmEGSleE+GSb+ZwDOMsNzLJLmxoq1e6glHcVixG1z7WQ== -"@xterm/addon-webgl@0.17.0-beta.30": - version "0.17.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.30.tgz#159fa6bfa8e68f37c54a9a3fbf505d389f6c9374" - integrity sha512-n/4ncwUXc6rRyYA+KDVv5e/VnF/OCo4bFqycWf6wNFSEvD1AJavHm/CHi//p9rPC5gLzulKuTJdOj4jQ5St+Yg== +"@xterm/addon-webgl@0.17.0-beta.31": + version "0.17.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.31.tgz#15dfea4583ff9b65f1a442e5cdba1d1638adb05f" + integrity sha512-wqbBDDppwQ4R8o0YgnyFL8Pai2mVZqHb3E097vkFLB5Fw2hNx2dys3MgiXriSGXaUABKM3usVdZyouL6QgWdxQ== -"@xterm/headless@5.4.0-beta.30": - version "5.4.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.30.tgz#be819f4253083bf104aa6fb0d21d57dd9ef6adb4" - integrity sha512-2gHdJ5NUsFZyFS8H8X+98I7NUw9cYR2+ey7lLsFvfhd1gv8eqiCPGamiWDN1sZ/fbZf+cx39h/c+AaWlLlGuBA== +"@xterm/headless@5.4.0-beta.31": + version "5.4.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.31.tgz#7727c5c79d3b1b8e59526cf51c75148e13f61694" + integrity sha512-AIMP0ZZozxtvilVTKqquNPYDE5RuKINTsYjOcWzYvjpg7sS75/Tn/RBx20KfZN8Z2oCCwVgj+1mudrV0W4JmMw== -"@xterm/xterm@5.4.0-beta.30": - version "5.4.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.30.tgz#79d913f0ca0c2cd357e5f3f938d48a99d0ee4e5c" - integrity sha512-ShLgCpnA9WH4EMcasJYuWoUCGu5iHp0aZNsu5L/dw16CJZ7S1lkPg2dcVu3m/agOUZDJkaHiSG7j6V+P9ejvQQ== +"@xterm/xterm@5.4.0-beta.31": + version "5.4.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.31.tgz#ff0bb3af9b00b0dfc73e84075f4218440c9886be" + integrity sha512-EpCtaYqMhJSyZrGY2sJVZeRCIRrANKtv1GGTj+IQPvk6hTiJHGrFHLM0tZ0dj0l3z65tLoOdj6EzJnjzX3Pqjw== agent-base@^7.0.1, agent-base@^7.0.2, agent-base@^7.1.0: version "7.1.0" diff --git a/yarn.lock b/yarn.lock index a8dae3e7e4d..9ed6f456cb8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1668,45 +1668,45 @@ resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.1.tgz#34bdc31727a1889198855913db2f270ace6d7bf8" integrity sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw== -"@xterm/addon-canvas@0.6.0-beta.30": - version "0.6.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.30.tgz#71139253d6ba037a2a6f26895c6e45363d9f496f" - integrity sha512-rDnIgtxOrRxwDiTHec3k0+K9PVeGR79RM7kjuOd39JEHRiH9JqPjwLHLAuN4RkCDh3fHbK/rTMYe73uWj5FvBw== +"@xterm/addon-canvas@0.6.0-beta.31": + version "0.6.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.31.tgz#17cc7d9968ede411fb23db11813b495435c068a0" + integrity sha512-jm/7FWZOgnAGG7MXjr0W4SnuIzsag+oVpyf6wAD9UlCgq5HBuk/3kJ5mYGiGR7CpdTxqXmzyBk3OhQe8npZ1aQ== -"@xterm/addon-image@0.7.0-beta.28": - version "0.7.0-beta.28" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.28.tgz#c06fe0c581e5c32b71a56b8c31b16580a596ae50" - integrity sha512-EIw+8vQNrl5i9rN+crc4DmYLcN66zdED1pEppShAFyQjwT+yLigbcF4QQa00eeq8xzG4NEnQbRb5R+4ZR1/G1Q== +"@xterm/addon-image@0.7.0-beta.29": + version "0.7.0-beta.29" + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.29.tgz#276b56007c9009e7a59605dc3809c280e7d637ed" + integrity sha512-Z5JCuhl0AcwQA+DE/kQMeSSHZbfwJVLUUBodDeujVItQrcpc9vA8mxf/qIwS3XTA/tPbFihfc/CE9zL7OFdbaw== -"@xterm/addon-search@0.14.0-beta.30": - version "0.14.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.30.tgz#313a44258121ed30205e18170a2865e0a432f725" - integrity sha512-x4bjV++cWWHg+HZ+WyOw76AjOe+lvh/rQF7XF1wO40ROLDSR9jBUH1oN9uR4Yr4XxxFgXLEdpswMhRYKrSJG/A== +"@xterm/addon-search@0.14.0-beta.31": + version "0.14.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.31.tgz#e6edcd257f5a66bca7e92e62684132b604fb817d" + integrity sha512-SS4CdgciLT98Uc4Dq0IjJegHcGIjGaASTcMtVkNBx9dOat9xt6lCXmtgUUj5w0KlB8nUfKrcy5T6fHgzrOzvrw== -"@xterm/addon-serialize@0.12.0-beta.30": - version "0.12.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.30.tgz#3472fa8cbf7d43603da558fe5b95bbeee7f7349f" - integrity sha512-AQC3Yf0Ld5uURJkMak/pJGsuSaajt1hdKabFPz7MYtnCy5KHsY97yIGFkUPGYKqNg6nT7gOCS2P/tCGjDaKOqA== +"@xterm/addon-serialize@0.12.0-beta.31": + version "0.12.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.31.tgz#daec32b94d45afcd662351d7689cb1b19eb24db7" + integrity sha512-MZ24pw33qOJrHdA6tlvwE4dSSpmIp/H9ZKtbiWZvuxVsY/hfYYPOluBQiCsOiYT7bZ8gQub2OOBX3jyMoZVxnQ== -"@xterm/addon-unicode11@0.7.0-beta.30": - version "0.7.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.30.tgz#49e0caebed508f8d684363c48c09da5cb1d06619" - integrity sha512-FzN+L2rvXAwsjGs+UyySPz8e6EvgZNQImX63XqUOIvPnpLWkFLc+ket4n8Bu0MwmZg99atqxepEGNpXVDRPY9A== +"@xterm/addon-unicode11@0.7.0-beta.31": + version "0.7.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.31.tgz#e1a6e965638ee6cb59b8b0777387037c42582d4b" + integrity sha512-wrZLt2s6Yjmpe4nh0Sp6DKji0EoHod7V6ABfWBf8krjmEGSleE+GSb+ZwDOMsNzLJLmxoq1e6glHcVixG1z7WQ== -"@xterm/addon-webgl@0.17.0-beta.30": - version "0.17.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.30.tgz#159fa6bfa8e68f37c54a9a3fbf505d389f6c9374" - integrity sha512-n/4ncwUXc6rRyYA+KDVv5e/VnF/OCo4bFqycWf6wNFSEvD1AJavHm/CHi//p9rPC5gLzulKuTJdOj4jQ5St+Yg== +"@xterm/addon-webgl@0.17.0-beta.31": + version "0.17.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.31.tgz#15dfea4583ff9b65f1a442e5cdba1d1638adb05f" + integrity sha512-wqbBDDppwQ4R8o0YgnyFL8Pai2mVZqHb3E097vkFLB5Fw2hNx2dys3MgiXriSGXaUABKM3usVdZyouL6QgWdxQ== -"@xterm/headless@5.4.0-beta.30": - version "5.4.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.30.tgz#be819f4253083bf104aa6fb0d21d57dd9ef6adb4" - integrity sha512-2gHdJ5NUsFZyFS8H8X+98I7NUw9cYR2+ey7lLsFvfhd1gv8eqiCPGamiWDN1sZ/fbZf+cx39h/c+AaWlLlGuBA== +"@xterm/headless@5.4.0-beta.31": + version "5.4.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.31.tgz#7727c5c79d3b1b8e59526cf51c75148e13f61694" + integrity sha512-AIMP0ZZozxtvilVTKqquNPYDE5RuKINTsYjOcWzYvjpg7sS75/Tn/RBx20KfZN8Z2oCCwVgj+1mudrV0W4JmMw== -"@xterm/xterm@5.4.0-beta.30": - version "5.4.0-beta.30" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.30.tgz#79d913f0ca0c2cd357e5f3f938d48a99d0ee4e5c" - integrity sha512-ShLgCpnA9WH4EMcasJYuWoUCGu5iHp0aZNsu5L/dw16CJZ7S1lkPg2dcVu3m/agOUZDJkaHiSG7j6V+P9ejvQQ== +"@xterm/xterm@5.4.0-beta.31": + version "5.4.0-beta.31" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.31.tgz#ff0bb3af9b00b0dfc73e84075f4218440c9886be" + integrity sha512-EpCtaYqMhJSyZrGY2sJVZeRCIRrANKtv1GGTj+IQPvk6hTiJHGrFHLM0tZ0dj0l3z65tLoOdj6EzJnjzX3Pqjw== "@xtuc/ieee754@^1.2.0": version "1.2.0" From 246a3371b39aebb88ec211c725bf796804839933 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 2 Feb 2024 11:17:56 -0800 Subject: [PATCH 0891/1897] Move more strings to localize2 (take 2) (#204180) * Revert "Revert "Adopt localize2 in more spots" (#204087)" This reverts commit 5baa693de861851c02b9811d273d51c664e1885d. * Keep only the localize2 changes * revert accidental change to launch.json --- .../browser/actions/chatQuickInputActions.ts | 2 +- .../debug/browser/debug.contribution.ts | 3 +- .../browser/editSessions.contribution.ts | 2 +- .../extensions/browser/extensionsViewlet.ts | 5 +-- .../editors/textFileSaveErrorHandler.ts | 2 +- .../files/browser/fileActions.contribution.ts | 12 ++--- .../contrib/files/browser/fileActions.ts | 44 +++++++++---------- .../contrib/files/browser/fileConstants.ts | 10 ++--- .../files/browser/views/openEditorsView.ts | 2 +- .../fileActions.contribution.ts | 8 ++-- .../inlineChat/browser/inlineChatActions.ts | 4 +- .../issue/common/issue.contribution.ts | 7 +-- .../electron-sandbox/issue.contribution.ts | 2 +- .../electron-sandbox/localHistoryCommands.ts | 7 +-- .../common/localizationsActions.ts | 4 +- .../markers/browser/markers.contribution.ts | 6 +-- .../contrib/markers/browser/messages.ts | 2 +- .../share/browser/share.contribution.ts | 6 +-- .../tasks/browser/abstractTaskService.ts | 6 +-- .../tasks/browser/task.contribution.ts | 2 +- .../contrib/url/browser/trustedDomains.ts | 4 +- .../contrib/url/browser/url.contribution.ts | 5 +-- .../userDataSync/browser/userDataSync.ts | 2 +- .../webviewPanel/browser/webviewCommands.ts | 4 +- .../actions/installActions.ts | 10 +---- .../services/views/browser/viewsService.ts | 6 +-- .../views/common/viewContainerModel.ts | 3 +- 27 files changed, 76 insertions(+), 94 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts index 9381aa94208..4df9285764e 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts @@ -171,7 +171,7 @@ export function getQuickChatActionForProvider(id: string, label: string) { super({ id: `workbench.action.openQuickChat.${id}`, category: CHAT_CATEGORY, - title: { value: localize('interactiveSession.open', "Open Quick Chat ({0})", label), original: `Open Quick Chat (${label})` }, + title: localize2('interactiveSession.open', "Open Quick Chat ({0})", label), f1: true }); } diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 76ca42eeadd..8cd702486b8 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -232,8 +232,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { submenu: MenuId.EditorTitleRun MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, { submenu: MenuId.MenubarDebugMenu, title: { - value: 'Run', - original: 'Run', + ...nls.localize2('runMenu', "Run"), mnemonicTitle: nls.localize({ key: 'mRun', comment: ['&& denotes a mnemonic'] }, "&&Run") }, order: 6 diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index e984c264281..5baf0bb721d 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -899,7 +899,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo } private generateStandaloneOptionCommand(commandId: string, qualifiedName: string, category: string | ILocalizedString | undefined, when: ContextKeyExpression | undefined, remoteGroup: string | undefined) { - const command = { + const command: IAction2Options = { id: `${continueWorkingOnCommand.id}.${commandId}`, title: { original: qualifiedName, value: qualifiedName }, category: typeof category === 'string' ? { original: category, value: category } : category, diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index f62d7182243..53819a2eb53 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -177,10 +177,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio super({ id: 'workbench.extensions.installLocalExtensions', get title() { - return { - value: localize('select and install local extensions', "Install Local Extensions in '{0}'...", server.label), - original: `Install Local Extensions in '${server.label}'...`, - }; + return localize2('select and install local extensions', "Install Local Extensions in '{0}'...", server.label); }, category: REMOTE_CATEGORY, icon: installLocalInRemoteIcon, diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts index 1b0efaca20f..7d04ebe6d5d 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts @@ -324,7 +324,7 @@ class SaveModelAsAction extends Action { private model: ITextFileEditorModel, @IEditorService private editorService: IEditorService ) { - super('workbench.files.action.saveModelAs', SAVE_FILE_AS_LABEL); + super('workbench.files.action.saveModelAs', SAVE_FILE_AS_LABEL.value); } override async run(): Promise { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 8fcb0937fd9..ed1ea24a08a 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -215,13 +215,13 @@ appendToCommandPalette({ appendToCommandPalette({ id: SAVE_FILE_COMMAND_ID, - title: { value: SAVE_FILE_LABEL, original: 'Save' }, + title: SAVE_FILE_LABEL, category: Categories.File }); appendToCommandPalette({ id: SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, - title: { value: SAVE_FILE_WITHOUT_FORMATTING_LABEL, original: 'Save without Formatting' }, + title: SAVE_FILE_WITHOUT_FORMATTING_LABEL, category: Categories.File }); @@ -251,26 +251,26 @@ appendToCommandPalette({ appendToCommandPalette({ id: SAVE_FILE_AS_COMMAND_ID, - title: { value: SAVE_FILE_AS_LABEL, original: 'Save As...' }, + title: SAVE_FILE_AS_LABEL, category: Categories.File }); appendToCommandPalette({ id: NEW_FILE_COMMAND_ID, - title: { value: NEW_FILE_LABEL, original: 'New File' }, + title: NEW_FILE_LABEL, category: Categories.File }, WorkspaceFolderCountContext.notEqualsTo('0')); appendToCommandPalette({ id: NEW_FOLDER_COMMAND_ID, - title: { value: NEW_FOLDER_LABEL, original: 'New Folder' }, + title: NEW_FOLDER_LABEL, category: Categories.File, metadata: { description: nls.localize2('newFolderDescription', "Create a new folder or directory") } }, WorkspaceFolderCountContext.notEqualsTo('0')); appendToCommandPalette({ id: NEW_UNTITLED_FILE_COMMAND_ID, - title: { value: NEW_UNTITLED_FILE_LABEL, original: 'New Untitled Text File' }, + title: NEW_UNTITLED_FILE_LABEL, category: Categories.File }); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 2f3dc318c25..cb64ae02fb4 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -61,9 +61,9 @@ import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { ILocalizedString } from 'vs/platform/action/common/action'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; -export const NEW_FILE_LABEL = nls.localize('newFile', "New File..."); +export const NEW_FILE_LABEL = nls.localize2('newFile', "New File..."); export const NEW_FOLDER_COMMAND_ID = 'explorer.newFolder'; -export const NEW_FOLDER_LABEL = nls.localize('newFolder', "New Folder..."); +export const NEW_FOLDER_LABEL = nls.localize2('newFolder', "New Folder..."); export const TRIGGER_RENAME_LABEL = nls.localize('rename', "Rename..."); export const MOVE_FILE_TO_TRASH_LABEL = nls.localize('delete', "Delete"); export const COPY_FILE_LABEL = nls.localize('copyFile', "Copy"); @@ -481,12 +481,12 @@ async function askForOverwrite(fileService: IFileService, dialogService: IDialog export class GlobalCompareResourcesAction extends Action2 { static readonly ID = 'workbench.files.action.compareFileWith'; - static readonly LABEL = nls.localize('globalCompareFile', "Compare Active File With..."); + static readonly LABEL = nls.localize2('globalCompareFile', "Compare Active File With..."); constructor() { super({ id: GlobalCompareResourcesAction.ID, - title: { value: GlobalCompareResourcesAction.LABEL, original: 'Compare Active File With...' }, + title: GlobalCompareResourcesAction.LABEL, f1: true, category: Categories.File, precondition: ActiveEditorContext @@ -609,12 +609,12 @@ export class CloseGroupAction extends Action { export class FocusFilesExplorer extends Action2 { static readonly ID = 'workbench.files.action.focusFilesExplorer'; - static readonly LABEL = nls.localize('focusFilesExplorer', "Focus on Files Explorer"); + static readonly LABEL = nls.localize2('focusFilesExplorer', "Focus on Files Explorer"); constructor() { super({ id: FocusFilesExplorer.ID, - title: { value: FocusFilesExplorer.LABEL, original: 'Focus on Files Explorer' }, + title: FocusFilesExplorer.LABEL, f1: true, category: Categories.File }); @@ -629,12 +629,12 @@ export class FocusFilesExplorer extends Action2 { export class ShowActiveFileInExplorer extends Action2 { static readonly ID = 'workbench.files.action.showActiveFileInExplorer'; - static readonly LABEL = nls.localize('showInExplorer', "Reveal Active File in Explorer View"); + static readonly LABEL = nls.localize2('showInExplorer', "Reveal Active File in Explorer View"); constructor() { super({ id: ShowActiveFileInExplorer.ID, - title: { value: ShowActiveFileInExplorer.LABEL, original: 'Reveal Active File in Explorer View' }, + title: ShowActiveFileInExplorer.LABEL, f1: true, category: Categories.File }); @@ -653,13 +653,13 @@ export class ShowActiveFileInExplorer extends Action2 { export class OpenActiveFileInEmptyWorkspace extends Action2 { static readonly ID = 'workbench.action.files.showOpenedFileInNewWindow'; - static readonly LABEL = nls.localize('openFileInEmptyWorkspace', "Open Active File in New Empty Workspace"); + static readonly LABEL = nls.localize2('openFileInEmptyWorkspace', "Open Active File in New Empty Workspace"); constructor( ) { super({ id: OpenActiveFileInEmptyWorkspace.ID, - title: { value: OpenActiveFileInEmptyWorkspace.LABEL, original: 'Open Active File in New Empty Workspace' }, + title: OpenActiveFileInEmptyWorkspace.LABEL, f1: true, category: Categories.File, precondition: EmptyWorkspaceSupportContext @@ -763,12 +763,12 @@ function getWellFormedFileName(filename: string): string { export class CompareNewUntitledTextFilesAction extends Action2 { static readonly ID = 'workbench.files.action.compareNewUntitledTextFiles'; - static readonly LABEL = nls.localize('compareNewUntitledTextFiles', "Compare New Untitled Text Files"); + static readonly LABEL = nls.localize2('compareNewUntitledTextFiles', "Compare New Untitled Text Files"); constructor() { super({ id: CompareNewUntitledTextFilesAction.ID, - title: { value: CompareNewUntitledTextFilesAction.LABEL, original: 'Compare New Untitled Text Files' }, + title: CompareNewUntitledTextFilesAction.LABEL, f1: true, category: Categories.File }); @@ -788,7 +788,7 @@ export class CompareNewUntitledTextFilesAction extends Action2 { export class CompareWithClipboardAction extends Action2 { static readonly ID = 'workbench.files.action.compareWithClipboard'; - static readonly LABEL = nls.localize('compareWithClipboard', "Compare Active File with Clipboard"); + static readonly LABEL = nls.localize2('compareWithClipboard', "Compare Active File with Clipboard"); private registrationDisposal: IDisposable | undefined; private static SCHEME_COUNTER = 0; @@ -796,7 +796,7 @@ export class CompareWithClipboardAction extends Action2 { constructor() { super({ id: CompareWithClipboardAction.ID, - title: { value: CompareWithClipboardAction.LABEL, original: 'Compare Active File with Clipboard' }, + title: CompareWithClipboardAction.LABEL, f1: true, category: Categories.File, keybinding: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyC), weight: KeybindingWeight.WorkbenchContrib } @@ -1261,12 +1261,12 @@ class BaseSetActiveEditorReadonlyInSession extends Action2 { export class SetActiveEditorReadonlyInSession extends BaseSetActiveEditorReadonlyInSession { static readonly ID = 'workbench.action.files.setActiveEditorReadonlyInSession'; - static readonly LABEL = nls.localize('setActiveEditorReadonlyInSession', "Set Active Editor Read-only in Session"); + static readonly LABEL = nls.localize2('setActiveEditorReadonlyInSession', "Set Active Editor Read-only in Session"); constructor() { super( SetActiveEditorReadonlyInSession.ID, - { value: SetActiveEditorReadonlyInSession.LABEL, original: 'Set Active Editor Readonly in Session' }, + SetActiveEditorReadonlyInSession.LABEL, true ); } @@ -1275,12 +1275,12 @@ export class SetActiveEditorReadonlyInSession extends BaseSetActiveEditorReadonl export class SetActiveEditorWriteableInSession extends BaseSetActiveEditorReadonlyInSession { static readonly ID = 'workbench.action.files.setActiveEditorWriteableInSession'; - static readonly LABEL = nls.localize('setActiveEditorWriteableInSession', "Set Active Editor Writeable in Session"); + static readonly LABEL = nls.localize2('setActiveEditorWriteableInSession', "Set Active Editor Writeable in Session"); constructor() { super( SetActiveEditorWriteableInSession.ID, - { value: SetActiveEditorWriteableInSession.LABEL, original: 'Set Active Editor Writeable in Session' }, + SetActiveEditorWriteableInSession.LABEL, false ); } @@ -1289,12 +1289,12 @@ export class SetActiveEditorWriteableInSession extends BaseSetActiveEditorReadon export class ToggleActiveEditorReadonlyInSession extends BaseSetActiveEditorReadonlyInSession { static readonly ID = 'workbench.action.files.toggleActiveEditorReadonlyInSession'; - static readonly LABEL = nls.localize('toggleActiveEditorReadonlyInSession', "Toggle Active Editor Read-only in Session"); + static readonly LABEL = nls.localize2('toggleActiveEditorReadonlyInSession', "Toggle Active Editor Read-only in Session"); constructor() { super( ToggleActiveEditorReadonlyInSession.ID, - { value: ToggleActiveEditorReadonlyInSession.LABEL, original: 'Toggle Active Editor Readonly in Session' }, + ToggleActiveEditorReadonlyInSession.LABEL, 'toggle' ); } @@ -1303,12 +1303,12 @@ export class ToggleActiveEditorReadonlyInSession extends BaseSetActiveEditorRead export class ResetActiveEditorReadonlyInSession extends BaseSetActiveEditorReadonlyInSession { static readonly ID = 'workbench.action.files.resetActiveEditorReadonlyInSession'; - static readonly LABEL = nls.localize('resetActiveEditorReadonlyInSession', "Reset Active Editor Read-only in Session"); + static readonly LABEL = nls.localize2('resetActiveEditorReadonlyInSession', "Reset Active Editor Read-only in Session"); constructor() { super( ResetActiveEditorReadonlyInSession.ID, - { value: ResetActiveEditorReadonlyInSession.LABEL, original: 'Reset Active Editor Readonly in Session' }, + ResetActiveEditorReadonlyInSession.LABEL, 'reset' ); } diff --git a/src/vs/workbench/contrib/files/browser/fileConstants.ts b/src/vs/workbench/contrib/files/browser/fileConstants.ts index 6736003acd1..c38f9987d22 100644 --- a/src/vs/workbench/contrib/files/browser/fileConstants.ts +++ b/src/vs/workbench/contrib/files/browser/fileConstants.ts @@ -19,14 +19,14 @@ export const COPY_PATH_COMMAND_ID = 'copyFilePath'; export const COPY_RELATIVE_PATH_COMMAND_ID = 'copyRelativeFilePath'; export const SAVE_FILE_AS_COMMAND_ID = 'workbench.action.files.saveAs'; -export const SAVE_FILE_AS_LABEL = nls.localize('saveAs', "Save As..."); +export const SAVE_FILE_AS_LABEL = nls.localize2('saveAs', "Save As..."); export const SAVE_FILE_COMMAND_ID = 'workbench.action.files.save'; -export const SAVE_FILE_LABEL = nls.localize('save', "Save"); +export const SAVE_FILE_LABEL = nls.localize2('save', "Save"); export const SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID = 'workbench.action.files.saveWithoutFormatting'; -export const SAVE_FILE_WITHOUT_FORMATTING_LABEL = nls.localize('saveWithoutFormatting', "Save without Formatting"); +export const SAVE_FILE_WITHOUT_FORMATTING_LABEL = nls.localize2('saveWithoutFormatting', "Save without Formatting"); export const SAVE_ALL_COMMAND_ID = 'saveAll'; -export const SAVE_ALL_LABEL = nls.localize('saveAll', "Save All"); +export const SAVE_ALL_LABEL = nls.localize2('saveAll', "Save All"); export const SAVE_ALL_IN_GROUP_COMMAND_ID = 'workbench.files.action.saveAllInGroup'; @@ -45,5 +45,5 @@ export const NEXT_COMPRESSED_FOLDER = 'nextCompressedFolder'; export const FIRST_COMPRESSED_FOLDER = 'firstCompressedFolder'; export const LAST_COMPRESSED_FOLDER = 'lastCompressedFolder'; export const NEW_UNTITLED_FILE_COMMAND_ID = 'workbench.action.files.newUntitledFile'; -export const NEW_UNTITLED_FILE_LABEL = nls.localize('newUntitledFile', "New Untitled Text File"); +export const NEW_UNTITLED_FILE_LABEL = nls.localize2('newUntitledFile', "New Untitled Text File"); export const NEW_FILE_COMMAND_ID = 'workbench.action.files.newFile'; diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 7115d8fc698..9f21e5014ba 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -835,7 +835,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.files.saveAll', - title: { value: SAVE_ALL_LABEL, original: 'Save All' }, + title: SAVE_ALL_LABEL, f1: true, icon: Codicon.saveAll, menu: { diff --git a/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts b/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts index 92a340e8175..de8140c144b 100644 --- a/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts @@ -24,7 +24,7 @@ import { SideBySideEditor, EditorResourceAccessor } from 'vs/workbench/common/ed import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; const REVEAL_IN_OS_COMMAND_ID = 'revealFileInOS'; -const REVEAL_IN_OS_LABEL = isWindows ? nls.localize('revealInWindows', "Reveal in File Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder"); +const REVEAL_IN_OS_LABEL = isWindows ? nls.localize2('revealInWindows', "Reveal in File Explorer") : isMacintosh ? nls.localize2('revealInMac', "Reveal in Finder") : nls.localize2('openContainer', "Open Containing Folder"); const REVEAL_IN_OS_WHEN_CONTEXT = ContextKeyExpr.or(ResourceContextKey.Scheme.isEqualTo(Schemas.file), ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeUserData)); KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -57,13 +57,13 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, REVEAL_IN_OS_WHEN_CONTEXT, '2_files', 0); +appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL.value, REVEAL_IN_OS_WHEN_CONTEXT, '2_files', 0); // Menu registration - open editors const revealInOsCommand = { id: REVEAL_IN_OS_COMMAND_ID, - title: REVEAL_IN_OS_LABEL + title: REVEAL_IN_OS_LABEL.value }; MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: 'navigation', @@ -92,6 +92,6 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { const category = nls.localize2('filesCategory', "File"); appendToCommandPalette({ id: REVEAL_IN_OS_COMMAND_ID, - title: { value: REVEAL_IN_OS_LABEL, original: isWindows ? 'Reveal in File Explorer' : isMacintosh ? 'Reveal in Finder' : 'Open Containing Folder' }, + title: REVEAL_IN_OS_LABEL, category: category }, REVEAL_IN_OS_WHEN_CONTEXT); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 57e2376c569..85033c64166 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -34,7 +34,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); -export const LOCALIZED_START_INLINE_CHAT_STRING = localize('run', 'Start Inline Chat'); +export const LOCALIZED_START_INLINE_CHAT_STRING = localize2('run', 'Start Inline Chat'); export const START_INLINE_CHAT = registerIcon('start-inline-chat', Codicon.sparkle, localize('startInlineChat', 'Icon which spawns the inline chat from the editor toolbar.')); // some gymnastics to enable hold for speech without moving the StartSessionAction into the electron-layer @@ -52,7 +52,7 @@ export class StartSessionAction extends EditorAction2 { constructor() { super({ id: 'inlineChat.start', - title: { value: LOCALIZED_START_INLINE_CHAT_STRING, original: 'Start Inline Chat' }, + title: LOCALIZED_START_INLINE_CHAT_STRING, category: AbstractInlineChatAction.category, f1: true, precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_PROVIDER, EditorContextKeys.writable), diff --git a/src/vs/workbench/contrib/issue/common/issue.contribution.ts b/src/vs/workbench/contrib/issue/common/issue.contribution.ts index ece002871bb..05518d0f4aa 100644 --- a/src/vs/workbench/contrib/issue/common/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/common/issue.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ICommandAction } from 'vs/platform/action/common/action'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; @@ -97,10 +97,7 @@ export class BaseIssueContribution implements IWorkbenchContribution { const reportIssue: ICommandAction = { id: OpenIssueReporterActionId, - title: { - value: localize({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue..."), - original: 'Report Issue...' - }, + title: localize2({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue..."), category: Categories.Help }; diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts index 3f446e23dba..05bc0632624 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts @@ -43,7 +43,7 @@ class ReportPerformanceIssueUsingReporterAction extends Action2 { constructor() { super({ id: ReportPerformanceIssueUsingReporterAction.ID, - title: { value: localize({ key: 'reportPerformanceIssue', comment: [`Here, 'issue' means problem or bug`] }, "Report Performance Issue..."), original: 'Report Performance Issue' }, + title: localize2({ key: 'reportPerformanceIssue', comment: [`Here, 'issue' means problem or bug`] }, "Report Performance Issue..."), category: Categories.Help, f1: true }); diff --git a/src/vs/workbench/contrib/localHistory/electron-sandbox/localHistoryCommands.ts b/src/vs/workbench/contrib/localHistory/electron-sandbox/localHistoryCommands.ts index 20d20356e6e..76ac171e9af 100644 --- a/src/vs/workbench/contrib/localHistory/electron-sandbox/localHistoryCommands.ts +++ b/src/vs/workbench/contrib/localHistory/electron-sandbox/localHistoryCommands.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { IWorkingCopyHistoryService } from 'vs/workbench/services/workingCopy/common/workingCopyHistory'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; @@ -21,10 +21,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.localHistory.revealInOS', - title: { - value: isWindows ? localize('revealInWindows', "Reveal in File Explorer") : isMacintosh ? localize('revealInMac', "Reveal in Finder") : localize('openContainer', "Open Containing Folder"), - original: isWindows ? 'Reveal in File Explorer' : isMacintosh ? 'Reveal in Finder' : 'Open Containing Folder' - }, + title: isWindows ? localize2('revealInWindows', "Reveal in File Explorer") : isMacintosh ? localize2('revealInMac', "Reveal in Finder") : localize2('openContainer', "Open Containing Folder"), menu: { id: MenuId.TimelineItemContext, group: '4_reveal', diff --git a/src/vs/workbench/contrib/localization/common/localizationsActions.ts b/src/vs/workbench/contrib/localization/common/localizationsActions.ts index 97c275ac765..d9ad34f1c1b 100644 --- a/src/vs/workbench/contrib/localization/common/localizationsActions.ts +++ b/src/vs/workbench/contrib/localization/common/localizationsActions.ts @@ -100,12 +100,12 @@ export class ConfigureDisplayLanguageAction extends Action2 { export class ClearDisplayLanguageAction extends Action2 { public static readonly ID = 'workbench.action.clearLocalePreference'; - public static readonly LABEL = localize('clearDisplayLanguage', "Clear Display Language Preference"); + public static readonly LABEL = localize2('clearDisplayLanguage', "Clear Display Language Preference"); constructor() { super({ id: ClearDisplayLanguageAction.ID, - title: { original: 'Clear Display Language Preference', value: ClearDisplayLanguageAction.LABEL }, + title: ClearDisplayLanguageAction.LABEL, menu: { id: MenuId.CommandPalette } diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 2466217b62c..aa02cce1cb8 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -311,7 +311,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.problems.focus', - title: { value: Messages.MARKERS_PANEL_SHOW_LABEL, original: 'Focus Problems (Errors, Warnings, Infos)' }, + title: Messages.MARKERS_PANEL_SHOW_LABEL, category: Categories.View, f1: true, }); @@ -446,7 +446,7 @@ registerAction2(class extends ViewAction { constructor() { super({ id: Markers.MARKERS_VIEW_SHOW_MULTILINE_MESSAGE, - title: { value: localize('show multiline', "Show message in multiple lines"), original: 'Problems: Show message in multiple lines' }, + title: localize2('show multiline', "Show message in multiple lines"), category: localize('problems', "Problems"), menu: { id: MenuId.CommandPalette, @@ -464,7 +464,7 @@ registerAction2(class extends ViewAction { constructor() { super({ id: Markers.MARKERS_VIEW_SHOW_SINGLELINE_MESSAGE, - title: { value: localize('show singleline', "Show message in single line"), original: 'Problems: Show message in single line' }, + title: localize2('show singleline', "Show message in single line"), category: localize('problems', "Problems"), menu: { id: MenuId.CommandPalette, diff --git a/src/vs/workbench/contrib/markers/browser/messages.ts b/src/vs/workbench/contrib/markers/browser/messages.ts index e6a387f0ce2..29c07828fc6 100644 --- a/src/vs/workbench/contrib/markers/browser/messages.ts +++ b/src/vs/workbench/contrib/markers/browser/messages.ts @@ -12,7 +12,7 @@ import { Marker } from './markersModel'; export default class Messages { public static MARKERS_PANEL_TOGGLE_LABEL: string = nls.localize('problems.view.toggle.label', "Toggle Problems (Errors, Warnings, Infos)"); - public static MARKERS_PANEL_SHOW_LABEL: string = nls.localize('problems.view.focus.label', "Focus Problems (Errors, Warnings, Infos)"); + public static MARKERS_PANEL_SHOW_LABEL = nls.localize2('problems.view.focus.label', "Focus Problems (Errors, Warnings, Infos)"); public static PROBLEMS_PANEL_CONFIGURATION_TITLE: string = nls.localize('problems.panel.configuration.title', "Problems View"); public static PROBLEMS_PANEL_CONFIGURATION_AUTO_REVEAL: string = nls.localize('problems.panel.configuration.autoreveal', "Controls whether Problems view should automatically reveal files when opening them."); diff --git a/src/vs/workbench/contrib/share/browser/share.contribution.ts b/src/vs/workbench/contrib/share/browser/share.contribution.ts index 2eb7e5595d5..ae071141610 100644 --- a/src/vs/workbench/contrib/share/browser/share.contribution.ts +++ b/src/vs/workbench/contrib/share/browser/share.contribution.ts @@ -8,7 +8,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -77,12 +77,12 @@ class ShareWorkbenchContribution { this._disposables.add( registerAction2(class ShareAction extends Action2 { static readonly ID = 'workbench.action.share'; - static readonly LABEL = localize('share', 'Share...'); + static readonly LABEL = localize2('share', 'Share...'); constructor() { super({ id: ShareAction.ID, - title: { value: ShareAction.LABEL, original: 'Share...' }, + title: ShareAction.LABEL, f1: true, icon: Codicon.linkExternal, precondition: ContextKeyExpr.and(ShareProviderCountContext.notEqualsTo(0), WorkspaceFolderCountContext.notEqualsTo(0)), diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 27f92a65ad3..43246667cfe 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -90,7 +90,7 @@ const USE_SLOW_PICKER = 'task.quickOpen.showAll'; export namespace ConfigureTaskAction { export const ID = 'workbench.action.tasks.configureTaskRunner'; - export const TEXT = nls.localize('ConfigureTaskRunnerAction.label', "Configure Task"); + export const TEXT = nls.localize2('ConfigureTaskRunnerAction.label', "Configure Task"); } export type TaskQuickPickEntryType = (IQuickPickItem & { task: Task }) | (IQuickPickItem & { folder: IWorkspaceFolder }) | (IQuickPickItem & { settingType: string }); @@ -2563,7 +2563,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const thisCapture: AbstractTaskService = this; return new class extends Action { constructor() { - super(ConfigureTaskAction.ID, ConfigureTaskAction.TEXT, undefined, true, () => { thisCapture._runConfigureTasks(); return Promise.resolve(undefined); }); + super(ConfigureTaskAction.ID, ConfigureTaskAction.TEXT.value, undefined, true, () => { thisCapture._runConfigureTasks(); return Promise.resolve(undefined); }); } }; } @@ -2576,7 +2576,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const needsTerminate = buildError.code === TaskErrors.RunningTask; if (needsConfig || needsTerminate) { this._notificationService.prompt(buildError.severity, buildError.message, [{ - label: needsConfig ? ConfigureTaskAction.TEXT : nls.localize('TerminateAction.label', "Terminate Task"), + label: needsConfig ? ConfigureTaskAction.TEXT.value : nls.localize('TerminateAction.label', "Terminate Task"), run: () => { if (needsConfig) { this._runConfigureTasks(); diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index d92f58923b0..a0d2bb523f6 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -246,7 +246,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: ConfigureTaskAction.ID, - title: { value: ConfigureTaskAction.TEXT, original: 'Configure Task' }, + title: ConfigureTaskAction.TEXT, category: TASKS_CATEGORY }, when: TaskExecutionSupportedContext diff --git a/src/vs/workbench/contrib/url/browser/trustedDomains.ts b/src/vs/workbench/contrib/url/browser/trustedDomains.ts index 2019b04e521..82c9559dad4 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomains.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomains.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; @@ -25,7 +25,7 @@ export const TRUSTED_DOMAINS_CONTENT_STORAGE_KEY = 'http.linkProtectionTrustedDo export const manageTrustedDomainSettingsCommand = { id: 'workbench.action.manageTrustedDomain', description: { - description: localize('trustedDomain.manageTrustedDomain', 'Manage Trusted Domains'), + description: localize2('trustedDomain.manageTrustedDomain', 'Manage Trusted Domains'), args: [] }, handler: async (accessor: ServicesAccessor) => { diff --git a/src/vs/workbench/contrib/url/browser/url.contribution.ts b/src/vs/workbench/contrib/url/browser/url.contribution.ts index d0484bd566c..92e2b6b9611 100644 --- a/src/vs/workbench/contrib/url/browser/url.contribution.ts +++ b/src/vs/workbench/contrib/url/browser/url.contribution.ts @@ -55,10 +55,7 @@ CommandsRegistry.registerCommand(manageTrustedDomainSettingsCommand); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: manageTrustedDomainSettingsCommand.id, - title: { - value: manageTrustedDomainSettingsCommand.description.description, - original: 'Manage Trusted Domains' - } + title: manageTrustedDomainSettingsCommand.description.description } }); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 5a716a0880e..0787368edba 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -835,7 +835,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private getShowConflictsTitle(): ILocalizedString { - return { value: localize('resolveConflicts_global', "Show Conflicts ({0})", this.getConflictsCount()), original: `Show Conflicts (${this.getConflictsCount()})` }; + return localize2('resolveConflicts_global', "Show Conflicts ({0})", this.getConflictsCount()); } private conflictsActionDisposable = this._register(new MutableDisposable()); diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts index f973ce7ef5b..356a852efdb 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts @@ -104,12 +104,12 @@ export class WebViewEditorFindPreviousCommand extends Action2 { export class ReloadWebviewAction extends Action2 { static readonly ID = 'workbench.action.webview.reloadWebviewAction'; - static readonly LABEL = nls.localize('refreshWebviewLabel', "Reload Webviews"); + static readonly LABEL = nls.localize2('refreshWebviewLabel', "Reload Webviews"); public constructor() { super({ id: ReloadWebviewAction.ID, - title: { value: ReloadWebviewAction.LABEL, original: 'Reload Webviews' }, + title: ReloadWebviewAction.LABEL, category: Categories.Developer, menu: [{ id: MenuId.CommandPalette diff --git a/src/vs/workbench/electron-sandbox/actions/installActions.ts b/src/vs/workbench/electron-sandbox/actions/installActions.ts index 952883aec23..d9332bb4855 100644 --- a/src/vs/workbench/electron-sandbox/actions/installActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/installActions.ts @@ -21,10 +21,7 @@ export class InstallShellScriptAction extends Action2 { constructor() { super({ id: 'workbench.action.installCommandLine', - title: { - value: localize('install', "Install '{0}' command in PATH", product.applicationName), - original: `Install \'${product.applicationName}\' command in PATH` - }, + title: localize2('install', "Install '{0}' command in PATH", product.applicationName), category: shellCommandCategory, f1: true }); @@ -54,10 +51,7 @@ export class UninstallShellScriptAction extends Action2 { constructor() { super({ id: 'workbench.action.uninstallCommandLine', - title: { - value: localize('uninstall', "Uninstall '{0}' command from PATH", product.applicationName), - original: `Uninstall \'${product.applicationName}\' command from PATH` - }, + title: localize2('uninstall', "Uninstall '{0}' command from PATH", product.applicationName), category: shellCommandCategory, f1: true }); diff --git a/src/vs/workbench/services/views/browser/viewsService.ts b/src/vs/workbench/services/views/browser/viewsService.ts index 7f036faf807..49643efd588 100644 --- a/src/vs/workbench/services/views/browser/viewsService.ts +++ b/src/vs/workbench/services/views/browser/viewsService.ts @@ -501,10 +501,10 @@ export class ViewsService extends Disposable implements IViewsService { private registerFocusViewAction(viewDescriptor: IViewDescriptor, category?: string | ILocalizedString): IDisposable { return registerAction2(class FocusViewAction extends Action2 { constructor() { - const title = localize({ key: 'focus view', comment: ['{0} indicates the name of the view to be focused.'] }, "Focus on {0} View", viewDescriptor.name.value); + const title = localize2({ key: 'focus view', comment: ['{0} indicates the name of the view to be focused.'] }, "Focus on {0} View", viewDescriptor.name.value); super({ id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`, - title: { original: `Focus on ${viewDescriptor.name.original} View`, value: title }, + title, category, menu: [{ id: MenuId.CommandPalette, @@ -520,7 +520,7 @@ export class ViewsService extends Disposable implements IViewsService { win: viewDescriptor.focusCommand?.keybindings?.win }, metadata: { - description: title, + description: title.value, args: [ { name: 'focusOptions', diff --git a/src/vs/workbench/services/views/common/viewContainerModel.ts b/src/vs/workbench/services/views/common/viewContainerModel.ts index d7c9f7149ab..60009d0ff79 100644 --- a/src/vs/workbench/services/views/common/viewContainerModel.ts +++ b/src/vs/workbench/services/views/common/viewContainerModel.ts @@ -21,12 +21,13 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IOutputService } from 'vs/workbench/services/output/common/output'; import { CounterSet } from 'vs/base/common/map'; +import { localize2 } from 'vs/nls'; registerAction2(class extends Action2 { constructor() { super({ id: '_workbench.output.showViewsLog', - title: { value: 'Show Views Log', original: 'Show Views Log' }, + title: localize2('showViewsLog', "Show Views Log"), category: Categories.Developer, f1: true }); From 453f8fba116357e5fd4a9cb3f9c81ac7b2f63986 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 2 Feb 2024 11:31:46 -0800 Subject: [PATCH 0892/1897] Add commands to zoom in/out/reset for terminal Fixes #204156 --- .../contrib/terminal/common/terminal.ts | 3 + .../terminal/common/terminalConfiguration.ts | 4 +- .../contrib/terminal/terminal.all.ts | 2 +- .../browser/terminal.zoom.contribution.ts} | 61 +++++++++++++++---- 4 files changed, 56 insertions(+), 14 deletions(-) rename src/vs/workbench/contrib/terminalContrib/{mouseWheelZoom/browser/terminal.mouseWheelZoom.contribution.ts => zoom/browser/terminal.zoom.contribution.ts} (70%) diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 80c91d51992..f77b9f4ae5e 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -497,6 +497,9 @@ export const enum TerminalCommandId { ToggleStickyScroll = 'workbench.action.terminal.toggleStickyScroll', StartVoice = 'workbench.action.startTerminalVoice', StopVoice = 'workbench.action.stopTerminalVoice', + FontZoomIn = 'workbench.action.terminal.fontZoomIn', + FontZoomOut = 'workbench.action.terminal.fontZoomOut', + FontZoomReset = 'workbench.action.terminal.fontZoomReset', // Developer commands diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index bb9975e6338..ac0903b8ae3 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -30,6 +30,8 @@ terminalTitle += terminalDescriptors; let terminalDescription = localize('terminalDescription', "Controls the terminal description, which appears to the right of the title. Variables are substituted based on the context:"); terminalDescription += terminalDescriptors; +export const defaultTerminalFontSize = isMacintosh ? 12 : 14; + const terminalConfiguration: IConfigurationNode = { id: 'terminal', order: 100, @@ -176,7 +178,7 @@ const terminalConfiguration: IConfigurationNode = { [TerminalSettingId.FontSize]: { description: localize('terminal.integrated.fontSize', "Controls the font size in pixels of the terminal."), type: 'number', - default: isMacintosh ? 12 : 14, + default: defaultTerminalFontSize, minimum: 6, maximum: 100 }, diff --git a/src/vs/workbench/contrib/terminal/terminal.all.ts b/src/vs/workbench/contrib/terminal/terminal.all.ts index 5484ab7adeb..b49aa829f7b 100644 --- a/src/vs/workbench/contrib/terminal/terminal.all.ts +++ b/src/vs/workbench/contrib/terminal/terminal.all.ts @@ -19,7 +19,7 @@ import 'vs/workbench/contrib/terminalContrib/environmentChanges/browser/terminal import 'vs/workbench/contrib/terminalContrib/find/browser/terminal.find.contribution'; import 'vs/workbench/contrib/terminalContrib/highlight/browser/terminal.highlight.contribution'; import 'vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution'; -import 'vs/workbench/contrib/terminalContrib/mouseWheelZoom/browser/terminal.mouseWheelZoom.contribution'; +import 'vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution'; import 'vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminal.stickyScroll.contribution'; import 'vs/workbench/contrib/terminalContrib/quickFix/browser/terminal.quickFix.contribution'; import 'vs/workbench/contrib/terminalContrib/typeAhead/browser/terminal.typeAhead.contribution'; diff --git a/src/vs/workbench/contrib/terminalContrib/mouseWheelZoom/browser/terminal.mouseWheelZoom.contribution.ts b/src/vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution.ts similarity index 70% rename from src/vs/workbench/contrib/terminalContrib/mouseWheelZoom/browser/terminal.mouseWheelZoom.contribution.ts rename to src/vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution.ts index e2080bb75bd..0f4d1ad09f3 100644 --- a/src/vs/workbench/contrib/terminalContrib/mouseWheelZoom/browser/terminal.mouseWheelZoom.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution.ts @@ -13,8 +13,12 @@ import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ITerminalProcessInfo, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalProcessInfo, ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; +import { registerTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; +import { localize2 } from 'vs/nls'; +import { isNumber } from 'vs/base/common/types'; +import { defaultTerminalFontSize } from 'vs/workbench/contrib/terminal/common/terminalConfiguration'; class TerminalMouseWheelZoomContribution extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.mouseWheelZoom'; @@ -69,7 +73,7 @@ class TerminalMouseWheelZoomContribution extends Disposable implements ITerminal raw.attachCustomWheelEventHandler((e: WheelEvent) => { const browserEvent = e as any as IMouseWheelEvent; if (classifier.isPhysicalMouseWheel()) { - if (hasMouseWheelZoomModifiers(browserEvent)) { + if (this._hasMouseWheelZoomModifiers(browserEvent)) { const delta = browserEvent.deltaY > 0 ? -1 : 1; this._configurationService.updateValue(TerminalSettingId.FontSize, this._getConfigFontSize() + delta); // EditorZoom.setZoomLevel(zoomLevel + delta); @@ -84,7 +88,7 @@ class TerminalMouseWheelZoomContribution extends Disposable implements ITerminal if (Date.now() - prevMouseWheelTime > 50) { // reset if more than 50ms have passed gestureStartFontSize = this._getConfigFontSize(); - gestureHasZoomModifiers = hasMouseWheelZoomModifiers(browserEvent); + gestureHasZoomModifiers = this._hasMouseWheelZoomModifiers(browserEvent); gestureAccumulatedDelta = 0; } @@ -106,16 +110,49 @@ class TerminalMouseWheelZoomContribution extends Disposable implements ITerminal }); this._listener.value = toDisposable(() => raw.attachCustomWheelEventHandler(() => true)); } + + private _hasMouseWheelZoomModifiers(browserEvent: IMouseWheelEvent): boolean { + return ( + isMacintosh + // on macOS we support cmd + two fingers scroll (`metaKey` set) + // and also the two fingers pinch gesture (`ctrKey` set) + ? ((browserEvent.metaKey || browserEvent.ctrlKey) && !browserEvent.shiftKey && !browserEvent.altKey) + : (browserEvent.ctrlKey && !browserEvent.metaKey && !browserEvent.shiftKey && !browserEvent.altKey) + ); + } } registerTerminalContribution(TerminalMouseWheelZoomContribution.ID, TerminalMouseWheelZoomContribution, true); -function hasMouseWheelZoomModifiers(browserEvent: IMouseWheelEvent): boolean { - return ( - isMacintosh - // on macOS we support cmd + two fingers scroll (`metaKey` set) - // and also the two fingers pinch gesture (`ctrKey` set) - ? ((browserEvent.metaKey || browserEvent.ctrlKey) && !browserEvent.shiftKey && !browserEvent.altKey) - : (browserEvent.ctrlKey && !browserEvent.metaKey && !browserEvent.shiftKey && !browserEvent.altKey) - ); -} +registerTerminalAction({ + id: TerminalCommandId.FontZoomIn, + title: localize2('fontZoomIn', 'Increase Font Size'), + run: async (c, accessor) => { + const configurationService = accessor.get(IConfigurationService); + const value = configurationService.getValue(TerminalSettingId.FontSize); + if (isNumber(value)) { + await configurationService.updateValue(TerminalSettingId.FontSize, value + 1); + } + } +}); + +registerTerminalAction({ + id: TerminalCommandId.FontZoomOut, + title: localize2('fontZoomOut', 'Decrease Font Size'), + run: async (c, accessor) => { + const configurationService = accessor.get(IConfigurationService); + const value = configurationService.getValue(TerminalSettingId.FontSize); + if (isNumber(value)) { + await configurationService.updateValue(TerminalSettingId.FontSize, value - 1); + } + } +}); + +registerTerminalAction({ + id: TerminalCommandId.FontZoomReset, + title: localize2('fontZoomReset', 'Reset Font Size'), + run: async (c, accessor) => { + const configurationService = accessor.get(IConfigurationService); + await configurationService.updateValue(TerminalSettingId.FontSize, defaultTerminalFontSize); + } +}); From 7cd1f0d73ac1982ded6050b12011e8340a385489 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 2 Feb 2024 13:31:40 -0800 Subject: [PATCH 0893/1897] Fix URL link detection when a path is in query string Fixes #204195 --- .../terminalContrib/links/browser/terminalLinkParsing.ts | 2 +- .../links/test/browser/terminalLinkParsing.test.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts index fcef437eac0..8328315b957 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts @@ -315,7 +315,7 @@ enum RegexPathConstants { // '":; are allowed in paths but they are often separators so ignore them // Also disallow \\ to prevent a catastropic backtracking case #24795 ExcludedPathCharactersClause = '[^\\0<>\\?\\s!`&*()\'":;\\\\]', - ExcludedStartPathCharactersClause = '[^\\0<>\\s!`&*()\\[\\]\'":;\\\\]', + ExcludedStartPathCharactersClause = '[^\\0<>\\?\\s!`&*()\\[\\]\'":;\\\\]', WinOtherPathPrefix = '\\.\\.?|\\~', WinPathSeparatorClause = '(?:\\\\|\\/)', diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts index 1bd0f31e57b..71d641d0739 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts @@ -561,6 +561,14 @@ suite('TerminalLinkParsing', () => { ] as IParsedLink[] ); }); + test('should not detect links starting with ? within query strings that contain posix-style paths (#204195)', () => { + // ? appended to the cwd will exist since it's just the cwd + strictEqual(detectLinks(`http://foo.com/?bar=/a/b&baz=c`, os).some(e => e.path.text.startsWith('?')), false); + }); + test('should not detect links starting with ? within query strings that contain Windows-style paths (#204195)', () => { + // ? appended to the cwd will exist since it's just the cwd + strictEqual(detectLinks(`http://foo.com/?bar=a:\\b&baz=c`, os).some(e => e.path.text.startsWith('?')), false); + }); } }); From c18d2c03425ba963b5b5ff532aeae1c169f0c231 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 2 Feb 2024 16:17:16 -0600 Subject: [PATCH 0894/1897] tweak terminal voice command ids --- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 4 ++-- src/vs/workbench/contrib/terminal/common/terminal.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 8decd44525f..50a7fc56a29 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -1654,7 +1654,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.StartVoice, - title: localize2('workbench.action.startTerminalVoice', "Start Terminal Voice"), + title: localize2('workbench.action.terminal.startVoice', "Start Terminal Voice"), precondition: ContextKeyExpr.and(HasSpeechProvider, sharedWhenClause.terminalAvailable), f1: true, run: (activeInstance, c, accessor) => { @@ -1665,7 +1665,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.StopVoice, - title: localize2('workbench.action.stopTerminalVoice', "Stop Terminal Voice"), + title: localize2('workbench.action.terminal.stopVoice', "Stop Terminal Voice"), precondition: ContextKeyExpr.and(HasSpeechProvider, sharedWhenClause.terminalAvailable), f1: true, run: (activeInstance, c, accessor) => { diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 80c91d51992..ad4719a203a 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -495,8 +495,8 @@ export const enum TerminalCommandId { FocusHover = 'workbench.action.terminal.focusHover', ShowEnvironmentContributions = 'workbench.action.terminal.showEnvironmentContributions', ToggleStickyScroll = 'workbench.action.terminal.toggleStickyScroll', - StartVoice = 'workbench.action.startTerminalVoice', - StopVoice = 'workbench.action.stopTerminalVoice', + StartVoice = 'workbench.action.terminal.startVoice', + StopVoice = 'workbench.action.terminal.stopVoice', // Developer commands From 446a88c987cd33ff5b0137f15a50c25c65a8cbe6 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Fri, 2 Feb 2024 14:42:21 -0800 Subject: [PATCH 0895/1897] Remove featured extension service and other minor fixes (#204198) --- .../browser/featuredExtensionService.ts | 197 ------------------ .../browser/gettingStarted.contribution.ts | 6 + .../browser/gettingStarted.ts | 108 ++-------- .../browser/media/gettingStarted.css | 14 -- 4 files changed, 20 insertions(+), 305 deletions(-) delete mode 100644 src/vs/workbench/contrib/welcomeGettingStarted/browser/featuredExtensionService.ts diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/featuredExtensionService.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/featuredExtensionService.ts deleted file mode 100644 index d36253420a8..00000000000 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/featuredExtensionService.ts +++ /dev/null @@ -1,197 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { IFeaturedExtension } from 'vs/base/common/product'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { localize } from 'vs/nls'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; - -type FeaturedExtensionStorageData = { title: string; description: string; imagePath: string; date: number }; - -export const IFeaturedExtensionsService = createDecorator('featuredExtensionsService'); - -export interface IFeaturedExtensionsService { - _serviceBrand: undefined; - - getExtensions(): Promise; - title: string; -} - -const enum FeaturedExtensionMetadataType { - Title, - Description, - ImagePath -} - -export class FeaturedExtensionsService extends Disposable implements IFeaturedExtensionsService { - declare readonly _serviceBrand: undefined; - - private ignoredExtensions: Set = new Set(); - private _isInitialized: boolean = false; - - private static readonly STORAGE_KEY = 'workbench.welcomePage.extensionMetadata'; - - constructor( - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IExtensionService private readonly extensionService: IExtensionService, - @IStorageService private readonly storageService: IStorageService, - @IProductService private readonly productService: IProductService, - @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, - ) { - super(); - this.title = localize('gettingStarted.featuredTitle', 'Recommended'); - } - - title: string; - - async getExtensions(): Promise { - - await this._init(); - - const featuredExtensions: IFeaturedExtension[] = []; - for (const extension of this.productService.featuredExtensions?.filter(e => !this.ignoredExtensions.has(e.id)) ?? []) { - const resolvedExtension = await this.resolveExtension(extension); - if (resolvedExtension) { - featuredExtensions.push(resolvedExtension); - } - } - - return featuredExtensions; - } - - private async _init(): Promise { - - if (this._isInitialized) { - return; - } - - const featuredExtensions = this.productService.featuredExtensions; - if (!featuredExtensions) { - this._isInitialized = true; - return; - } - - await this.extensionService.whenInstalledExtensionsRegistered(); - const installed = await this.extensionManagementService.getInstalled(); - for (const extension of featuredExtensions) { - if (installed.some(e => ExtensionIdentifier.equals(e.identifier.id, extension.id))) { - this.ignoredExtensions.add(extension.id); - } - else { - let galleryExtension: IGalleryExtension | undefined; - try { - galleryExtension = (await this.galleryService.getExtensions([{ id: extension.id }], CancellationToken.None))[0]; - } catch (err) { - continue; - } - if (!await this.extensionManagementService.canInstall(galleryExtension)) { - this.ignoredExtensions.add(extension.id); - } - } - } - this._isInitialized = true; - } - - private async resolveExtension(productMetadata: IFeaturedExtension): Promise { - - const title = productMetadata.title ?? await this.getMetadata(productMetadata.id, FeaturedExtensionMetadataType.Title); - const description = productMetadata.description ?? await this.getMetadata(productMetadata.id, FeaturedExtensionMetadataType.Description); - const imagePath = productMetadata.imagePath ?? await this.getMetadata(productMetadata.id, FeaturedExtensionMetadataType.ImagePath); - - if (title && description && imagePath) { - return { - id: productMetadata.id, - title: title, - description: description, - imagePath: imagePath, - }; - } - return undefined; - } - - private async getMetadata(extensionId: string, key: FeaturedExtensionMetadataType): Promise { - - const storageMetadata = this.getStorageData(extensionId); - if (storageMetadata) { - switch (key) { - case FeaturedExtensionMetadataType.Title: { - return storageMetadata.title; - } - case FeaturedExtensionMetadataType.Description: { - return storageMetadata.description; - } - case FeaturedExtensionMetadataType.ImagePath: { - return storageMetadata.imagePath; - } - default: - return undefined; - } - } - - return await this.getGalleryMetadata(extensionId, key); - } - - private getStorageData(extensionId: string): FeaturedExtensionStorageData | undefined { - const metadata = this.storageService.get(FeaturedExtensionsService.STORAGE_KEY + '.' + extensionId, StorageScope.APPLICATION); - if (metadata) { - const value = JSON.parse(metadata) as FeaturedExtensionStorageData; - const lastUpdateDate = new Date().getTime() - value.date; - if (lastUpdateDate < 1000 * 60 * 60 * 24 * 7) { - return value; - } - } - return undefined; - } - - private async getGalleryMetadata(extensionId: string, key: FeaturedExtensionMetadataType): Promise { - - const storageKey = FeaturedExtensionsService.STORAGE_KEY + '.' + extensionId; - this.storageService.remove(storageKey, StorageScope.APPLICATION); - let metadata: string | undefined; - - let galleryExtension: IGalleryExtension | undefined; - try { - galleryExtension = (await this.galleryService.getExtensions([{ id: extensionId }], CancellationToken.None))[0]; - } catch (err) { - } - - if (!galleryExtension) { - return metadata; - } - - switch (key) { - case FeaturedExtensionMetadataType.Title: { - metadata = galleryExtension.displayName; - break; - } - case FeaturedExtensionMetadataType.Description: { - metadata = galleryExtension.description; - break; - } - case FeaturedExtensionMetadataType.ImagePath: { - metadata = galleryExtension.assets.icon?.uri; - break; - } - } - - this.storageService.store(storageKey, JSON.stringify({ - title: galleryExtension.displayName, - description: galleryExtension.description, - imagePath: galleryExtension.assets.icon?.uri, - date: new Date().getTime() - }), StorageScope.APPLICATION, StorageTarget.MACHINE); - - return metadata; - } -} - -registerSingleton(IFeaturedExtensionsService, FeaturedExtensionsService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts index a804657f8fd..5c7e2068615 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts @@ -63,6 +63,12 @@ registerAction2(class extends Action2 { const selectedCategory = typeof walkthroughID === 'string' ? walkthroughID : walkthroughID.category; const selectedStep = typeof walkthroughID === 'string' ? undefined : walkthroughID.step; + // We're trying to open the welcome page from the Help menu + if (!selectedCategory || !selectedStep) { + editorService.openEditor({ resource: GettingStartedInput.RESOURCE }); + return; + } + // Try first to select the walkthrough on an active welcome page with no selected walkthrough for (const group of editorGroupsService.groups) { if (group.activeEditor instanceof GettingStartedInput) { diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 8ff5cabc4cb..2abe8f8ebee 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -22,7 +22,6 @@ import { ILink, LinkedText } from 'vs/base/common/linkedText'; import { parse } from 'vs/base/common/marshalling'; import { Schemas, matchesScheme } from 'vs/base/common/network'; import { isMacintosh } from 'vs/base/common/platform'; -import { IFeaturedExtension } from 'vs/base/common/product'; import { ThemeIcon } from 'vs/base/common/themables'; import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -36,8 +35,6 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, ContextKeyExpression, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; -import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -61,7 +58,6 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { WorkbenchStateContext } from 'vs/workbench/common/contextkeys'; import { IEditorOpenContext, IEditorSerializer } from 'vs/workbench/common/editor'; import { IWebviewElement, IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; -import { IFeaturedExtensionsService } from 'vs/workbench/contrib/welcomeGettingStarted/browser/featuredExtensionService'; import 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedColors'; import { GettingStartedDetailsRenderer } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer'; import { gettingStartedCheckedCodicon, gettingStartedUncheckedCodicon } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedIcons'; @@ -134,7 +130,6 @@ export class GettingStartedPage extends EditorPane { // Currently initialized before use in buildCategoriesSlide and scrollToCategory private recentlyOpened!: Promise; private gettingStartedCategories!: IResolvedWalkthrough[]; - private featuredExtensions!: Promise; private currentWalkthrough: IResolvedWalkthrough | undefined; @@ -153,7 +148,6 @@ export class GettingStartedPage extends EditorPane { private recentlyOpenedList?: GettingStartedIndexList; private startList?: GettingStartedIndexList; private gettingStartedList?: GettingStartedIndexList; - private featuredExtensionsList?: GettingStartedIndexList; private stepsSlide!: HTMLElement; private categoriesSlide!: HTMLElement; @@ -172,7 +166,6 @@ export class GettingStartedPage extends EditorPane { @IProductService private readonly productService: IProductService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IWalkthroughsService private readonly gettingStartedService: IWalkthroughsService, - @IFeaturedExtensionsService private readonly featuredExtensionService: IFeaturedExtensionsService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITelemetryService telemetryService: ITelemetryService, @ILanguageService private readonly languageService: ILanguageService, @@ -191,8 +184,7 @@ export class GettingStartedPage extends EditorPane { @IHostService private readonly hostService: IHostService, @IWebviewService private readonly webviewService: IWebviewService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IAccessibilityService private readonly accessibilityService: IAccessibilityService, - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService) { + @IAccessibilityService private readonly accessibilityService: IAccessibilityService) { super(GettingStartedPage.ID, telemetryService, themeService, storageService); @@ -213,14 +205,12 @@ export class GettingStartedPage extends EditorPane { inWelcomeContext.bindTo(this.contextService).set(true); this.gettingStartedCategories = this.gettingStartedService.getWalkthroughs(); - this.featuredExtensions = this.featuredExtensionService.getExtensions(); this._register(this.dispatchListeners); this.buildSlideThrottle = new Throttler(); const rerender = () => { this.gettingStartedCategories = this.gettingStartedService.getWalkthroughs(); - this.featuredExtensions = this.featuredExtensionService.getExtensions(); if (this.currentWalkthrough) { const existingSteps = this.currentWalkthrough.steps.map(step => step.id); const newCategory = this.gettingStartedCategories.find(category => this.currentWalkthrough?.id === category.id); @@ -235,15 +225,6 @@ export class GettingStartedPage extends EditorPane { } }; - this._register(this.extensionManagementService.onDidInstallExtensions(async (result) => { - for (const e of result) { - const installedFeaturedExtension = (await this.featuredExtensions)?.find(ext => ExtensionIdentifier.equals(ext.id, e.identifier.id)); - if (installedFeaturedExtension) { - this.hideExtension(e.identifier.id); - } - } - })); - this._register(this.gettingStartedService.onDidAddWalkthrough(rerender)); this._register(this.gettingStartedService.onDidRemoveWalkthrough(rerender)); @@ -452,7 +433,6 @@ export class GettingStartedPage extends EditorPane { private hideExtension(extensionId: string) { this.setHiddenCategories([...this.getHiddenCategories().add(extensionId)]); - this.featuredExtensionsList?.rerender(); this.registerDispatchListeners(); } @@ -798,7 +778,6 @@ export class GettingStartedPage extends EditorPane { const startList = this.buildStartList(); const recentList = this.buildRecentlyOpenedList(); - const featuredExtensionList = this.buildFeaturedExtensionsList(); const gettingStartedList = this.buildGettingStartedWalkthroughsList(); const footer = $('.footer', {}, @@ -810,24 +789,12 @@ export class GettingStartedPage extends EditorPane { const layoutLists = () => { if (gettingStartedList.itemCount) { this.container.classList.remove('noWalkthroughs'); - reset(rightColumn, featuredExtensionList.getDomElement(), gettingStartedList.getDomElement()); + reset(rightColumn, gettingStartedList.getDomElement()); } else { this.container.classList.add('noWalkthroughs'); - reset(rightColumn, featuredExtensionList.getDomElement()); - } - setTimeout(() => this.categoriesPageScrollbar?.scanDomNode(), 50); - layoutRecentList(); - }; + reset(rightColumn); - const layoutFeaturedExtension = () => { - if (featuredExtensionList.itemCount) { - this.container.classList.remove('noExtensions'); - reset(rightColumn, featuredExtensionList.getDomElement(), gettingStartedList.getDomElement()); - } - else { - this.container.classList.add('noExtensions'); - reset(rightColumn, gettingStartedList.getDomElement()); } setTimeout(() => this.categoriesPageScrollbar?.scanDomNode(), 50); layoutRecentList(); @@ -844,8 +811,6 @@ export class GettingStartedPage extends EditorPane { } }; - featuredExtensionList.onDidChange(layoutFeaturedExtension); - layoutFeaturedExtension(); gettingStartedList.onDidChange(layoutLists); layoutLists(); @@ -867,6 +832,11 @@ export class GettingStartedPage extends EditorPane { return; } } + else { + this.buildCategorySlide(this.editorInput.selectedCategory, this.editorInput.selectedStep); + this.setSlide('details'); + return; + } } const someStepsComplete = this.gettingStartedCategories.some(category => category.steps.find(s => s.done)); @@ -1091,61 +1061,6 @@ export class GettingStartedPage extends EditorPane { return gettingStartedList; } - private buildFeaturedExtensionsList(): GettingStartedIndexList { - - const renderFeaturedExtensions = (entry: IFeaturedExtension): HTMLElement => { - - const descriptionContent = $('.featured-description-content', {},); - - reset(descriptionContent, ...renderLabelWithIcons(entry.description)); - - const titleContent = $('h3.category-title.max-lines-3', { 'x-category-title-for': entry.id }); - reset(titleContent, ...renderLabelWithIcons(entry.title)); - - return $('button.getting-started-category', - { - 'x-dispatch': 'openExtensionPage:' + entry.id, - 'title': entry.description - }, - $('.main-content', {}, - $('img.featured-icon.icon-widget', { src: entry.imagePath }), - titleContent, - $('a.codicon.codicon-close.hide-category-button', { - 'tabindex': 0, - 'x-dispatch': 'hideExtension:' + entry.id, - 'title': localize('close', "Hide"), - 'role': 'button', - 'aria-label': localize('closeAriaLabel', "Hide"), - }), - ), - descriptionContent); - }; - - if (this.featuredExtensionsList) { - this.featuredExtensionsList.dispose(); - } - - const featuredExtensionsList = this.featuredExtensionsList = new GettingStartedIndexList( - { - title: this.featuredExtensionService.title, - klass: 'featured-extensions', - limit: 5, - renderElement: renderFeaturedExtensions, - rankElement: (extension) => { if (this.getHiddenCategories().has(extension.id)) { return null; } return 0; }, - contextService: this.contextService, - }); - - this.featuredExtensions?.then(extensions => { - featuredExtensionsList.setEntries(extensions); - }); - - this.featuredExtensionsList?.onDidChange(() => { - this.registerDispatchListeners(); - }); - - return featuredExtensionsList; - } - layout(size: Dimension) { this.detailsScrollbar?.scanDomNode(); @@ -1154,7 +1069,6 @@ export class GettingStartedPage extends EditorPane { this.startList?.layout(size); this.gettingStartedList?.layout(size); - this.featuredExtensionsList?.layout(size); this.recentlyOpenedList?.layout(size); if (this.editorInput?.selectedStep && this.currentMediaType) { @@ -1546,6 +1460,12 @@ export class GettingStartedPage extends EditorPane { this.editorInput.selectedStep = undefined; this.editorInput.showTelemetryNotice = false; + if (this.gettingStartedCategories.length !== this.gettingStartedList?.itemCount) { + // extensions may have changed in the time since we last displayed the walkthrough list + // rebuild the list + this.buildCategoriesSlide(); + } + this.selectStep(undefined); this.setSlide('categories'); this.container.focus(); diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css b/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css index 2a31a8ba681..a3be91ad85e 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css @@ -290,7 +290,6 @@ width: 100%; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .getting-started-category .featured-description-content, .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .getting-started-category .description-content { text-align: left; margin-left: 28px; @@ -305,15 +304,6 @@ margin-bottom: 8px; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .getting-started-category .featured-description-content:not(:empty){ - margin-bottom: 8px; - margin-left: 32px; - overflow: hidden; - -webkit-line-clamp: 2; - display: -webkit-box; - -webkit-box-orient: vertical; -} - .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .getting-started-category .new-badge { justify-self: flex-end; align-self: flex-start; @@ -627,10 +617,6 @@ display: inline; } -.monaco-workbench .part.editor > .content .gettingStartedContainer.noExtensions .index-list.featured-extensions { - display: none; -} - .monaco-workbench .part.editor > .content .gettingStartedContainer.noWalkthroughs .index-list.getting-started { display: none; } From 600e2b638348efeb16d0436ed074dfa75068254b Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 2 Feb 2024 15:52:22 -0800 Subject: [PATCH 0896/1897] handle updates while outputs are collapsed (#204201) * handle updates while outputs are collapsed * naming, comments --- .../notebook/browser/notebookEditorWidget.ts | 2 +- .../browser/view/cellParts/cellOutput.ts | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index edf84021d7f..e32a1abfd7a 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2830,7 +2830,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD async updateOutput(cell: CodeCellViewModel, output: IInsetRenderOutput, offset: number): Promise { this._insetModifyQueueByOutputId.queue(output.source.model.outputId, async () => { - if (this._isDisposed || !this._webview) { + if (this._isDisposed || !this._webview || cell.isOutputCollapsed) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts index 3ec60d0ce1d..024db0c8919 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts @@ -183,6 +183,7 @@ class CellOutputElement extends Disposable { const index = this.viewCell.outputsViewModels.indexOf(this.output); if (this.viewCell.isOutputCollapsed || !this.notebookEditor.hasModel()) { + this.cellOutputContainer.flagAsStale(); return undefined; } @@ -477,6 +478,7 @@ const enum CellOutputUpdateContext { export class CellOutputContainer extends CellContentPart { private _outputEntries: OutputEntryViewHandler[] = []; + private _hasStaleOutputs: boolean = false; get renderedOutputEntries() { return this._outputEntries; @@ -534,6 +536,13 @@ export class CellOutputContainer extends CellContentPart { } } + /** + * Notify that an output may have been swapped out without the model getting rendered. + */ + flagAsStale() { + this._hasStaleOutputs = true; + } + private _doRender() { if (this.viewCell.outputsViewModels.length > 0) { if (this.viewCell.layoutInfo.outputTotalHeight !== 0) { @@ -569,6 +578,13 @@ export class CellOutputContainer extends CellContentPart { } viewUpdateShowOutputs(initRendering: boolean): void { + if (this._hasStaleOutputs) { + this._hasStaleOutputs = false; + this._outputEntries.forEach(entry => { + entry.element.rerender(); + }); + } + for (let index = 0; index < this._outputEntries.length; index++) { const viewHandler = this._outputEntries[index]; const outputEntry = viewHandler.element; From 559f5befe57875bff4e5bb4f2cf8b89aba19a820 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Fri, 2 Feb 2024 23:36:52 -0800 Subject: [PATCH 0897/1897] Support walkthrough restore from empty workspace (#204218) --- .../browser/gettingStarted.ts | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 2abe8f8ebee..9f171757fe7 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -44,7 +44,7 @@ import { Link } from 'vs/platform/opener/browser/link'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { ITelemetryService, TelemetryLevel, firstSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; import { getTelemetryLevel } from 'vs/platform/telemetry/common/telemetryUtils'; import { defaultButtonStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; @@ -282,6 +282,27 @@ export class GettingStartedPage extends EditorPane { } this.updateCategoryProgress(); })); + + this._register(this.storageService.onWillSaveState((e) => { + if (e.reason !== WillSaveStateReason.SHUTDOWN) { + return; + } + + if (this.workspaceContextService.getWorkspace().folders.length !== 0) { + return; + } + + if (!this.editorInput || !this.currentWalkthrough || !this.editorInput.selectedCategory || !this.editorInput.selectedStep) { + return; + } + + // Save the state of the walkthrough so we can restore it on reload + const restoreData: RestoreWalkthroughsConfigurationValue = { folder: UNKNOWN_EMPTY_WINDOW_WORKSPACE.id, category: this.editorInput.selectedCategory, step: this.editorInput.selectedStep }; + this.storageService.store( + restoreWalkthroughsConfigurationKey, + JSON.stringify(restoreData), + StorageScope.PROFILE, StorageTarget.MACHINE); + })); } // remove when 'workbench.welcomePage.preferReducedMotion' deprecated From 7cc042767b50448d36367e6061f43eea2049c685 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 3 Feb 2024 12:36:28 +0100 Subject: [PATCH 0898/1897] [Accessibility] Subsequent "Hey Code" does not work in the Chat view (#204137) (#204224) --- .../actions/voiceChatActions.ts | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 8083e797911..ad9c273ba2a 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -44,11 +44,9 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { ICodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); @@ -76,15 +74,6 @@ interface IVoiceChatSessionController { clearInputPlaceholder(): void; } -function getFocusedCodeEditor(editorService: IEditorService, codeEditorService: ICodeEditorService): ICodeEditor | null { - const codeEditor = getCodeEditor(codeEditorService.getFocusedCodeEditor()); - if (codeEditor && !(codeEditor instanceof EmbeddedCodeEditorWidget)) { - return codeEditor; - } - - return getCodeEditor(editorService.activeTextEditorControl); -} - class VoiceChatSessionControllerFactory { static create(accessor: ServicesAccessor, context: 'inline'): Promise; @@ -96,7 +85,6 @@ class VoiceChatSessionControllerFactory { const chatService = accessor.get(IChatService); const viewsService = accessor.get(IViewsService); const chatContributionService = accessor.get(IChatContributionService); - const codeEditorService = accessor.get(ICodeEditorService); const quickChatService = accessor.get(IQuickChatService); const layoutService = accessor.get(IWorkbenchLayoutService); const editorService = accessor.get(IEditorService); @@ -127,7 +115,7 @@ class VoiceChatSessionControllerFactory { } // Try with the inline chat - const activeCodeEditor = getFocusedCodeEditor(editorService, codeEditorService); + const activeCodeEditor = getCodeEditor(editorService.activeTextEditorControl); if (activeCodeEditor) { const inlineChat = InlineChatController.get(activeCodeEditor); if (inlineChat?.hasFocus()) { @@ -149,7 +137,7 @@ class VoiceChatSessionControllerFactory { // Inline Chat if (context === 'inline') { - const activeCodeEditor = getFocusedCodeEditor(editorService, codeEditorService); + const activeCodeEditor = getCodeEditor(editorService.activeTextEditorControl); if (activeCodeEditor) { const inlineChat = InlineChatController.get(activeCodeEditor); if (inlineChat) { @@ -764,7 +752,6 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe @ICommandService private readonly commandService: ICommandService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IInstantiationService instantiationService: IInstantiationService, - @ICodeEditorService private readonly codeEditorService: ICodeEditorService, @IEditorService private readonly editorService: IEditorService, @IHostService private readonly hostService: IHostService, @IChatService private readonly chatService: IChatService @@ -820,7 +807,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe localize('voice.keywordActivation.off', "Keyword activation is disabled."), localize('voice.keywordActivation.chatInView', "Keyword activation is enabled and listening for 'Hey Code' to start a voice chat session in the chat view."), localize('voice.keywordActivation.quickChat', "Keyword activation is enabled and listening for 'Hey Code' to start a voice chat session in the quick chat."), - localize('voice.keywordActivation.inlineChat', "Keyword activation is enabled and listening for 'Hey Code' to start a voice chat session in the active editor."), + localize('voice.keywordActivation.inlineChat', "Keyword activation is enabled and listening for 'Hey Code' to start a voice chat session in the active editor if possible."), localize('voice.keywordActivation.chatInContext', "Keyword activation is enabled and listening for 'Hey Code' to start a voice chat session in the active editor or view depending on keyboard focus.") ], 'description': localize('voice.keywordActivation', "Controls whether the keyword phrase 'Hey Code' is recognized to start a voice chat session. Enabling this will start recording from the microphone but the audio is processed locally and never sent to a server."), @@ -882,10 +869,12 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe return InlineVoiceChatAction.ID; case KeywordActivationContribution.SETTINGS_VALUE.QUICK_CHAT: return QuickVoiceChatAction.ID; - case KeywordActivationContribution.SETTINGS_VALUE.CHAT_IN_CONTEXT: - if (getFocusedCodeEditor(this.editorService, this.codeEditorService)) { + case KeywordActivationContribution.SETTINGS_VALUE.CHAT_IN_CONTEXT: { + const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorControl); + if (activeCodeEditor?.hasWidgetFocus()) { return InlineVoiceChatAction.ID; } + } default: return VoiceChatInChatViewAction.ID; } From 0cb10e9724abe82ff9d02365a61dd1bbe8a76e66 Mon Sep 17 00:00:00 2001 From: timotheeMM <143833750+timotheeMM@users.noreply.github.com> Date: Sat, 3 Feb 2024 18:43:24 +0100 Subject: [PATCH 0899/1897] Fix a typo in src/vs/platform/terminal/common/terminal.ts accomodate -> accommodate --- src/vs/platform/terminal/common/terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 54942de0d4d..ffe0e56a189 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -425,7 +425,7 @@ export enum HeartbeatConstants { BeatInterval = 5000, /** * The duration of the first heartbeat while the pty host is starting up. This is much larger - * than the regular BeatInterval to accomodate slow machines, we still want to warn about the + * than the regular BeatInterval to accommodate slow machines, we still want to warn about the * pty host's unresponsiveness eventually though. */ ConnectingBeatInterval = 20000, From ec291c126878742ad640055ce604a58129cd088c Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Sat, 3 Feb 2024 16:40:50 -0800 Subject: [PATCH 0900/1897] Fix opening welcome page from Help menu (#204215) --- .../browser/gettingStarted.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts index 5c7e2068615..a184caff2e7 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts @@ -64,7 +64,7 @@ registerAction2(class extends Action2 { const selectedStep = typeof walkthroughID === 'string' ? undefined : walkthroughID.step; // We're trying to open the welcome page from the Help menu - if (!selectedCategory || !selectedStep) { + if (!selectedCategory && !selectedStep) { editorService.openEditor({ resource: GettingStartedInput.RESOURCE }); return; } From 3154b5f948de18462d34394aef216e585e25e7ba Mon Sep 17 00:00:00 2001 From: Anthony Stewart <150152+a-stewart@users.noreply.github.com> Date: Mon, 5 Feb 2024 09:12:23 +0100 Subject: [PATCH 0901/1897] Stop the cursor from jumping when changing prefix in QuickAccess (#198821) --- src/vs/base/browser/ui/inputbox/inputBox.ts | 12 +++++++++++ .../quickinput/browser/quickAccess.ts | 8 ++++++++ .../platform/quickinput/browser/quickInput.ts | 20 +++++++++++++++++-- .../quickinput/browser/quickInputBox.ts | 4 ++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index e4c89dd3aff..0a79ae4f813 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -305,6 +305,18 @@ export class InputBox extends Widget { return this.input.selectionEnd === this.input.value.length && this.input.selectionStart === this.input.selectionEnd; } + public getSelection(): IRange | null { + const selectionStart = this.input.selectionStart; + if (selectionStart === null) { + return null; + } + const selectionEnd = this.input.selectionEnd ?? selectionStart; + return { + start: selectionStart, + end: selectionEnd, + }; + } + public enable(): void { this.input.removeAttribute('disabled'); } diff --git a/src/vs/platform/quickinput/browser/quickAccess.ts b/src/vs/platform/quickinput/browser/quickAccess.ts index cb35e451aa1..7b4e601436f 100644 --- a/src/vs/platform/quickinput/browser/quickAccess.ts +++ b/src/vs/platform/quickinput/browser/quickAccess.ts @@ -92,6 +92,9 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon } } + // Store the existing selection if there was one. + const visibleSelection = visibleQuickAccess?.picker?.valueSelection; + // Create a picker for the provider to use with the initial value // and adjust the filtering to exclude the prefix from filtering const disposables = new DisposableStore(); @@ -148,6 +151,11 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon // on the onDidHide event. picker.show(); + // If the previous picker had a selection, we should set that in the new picker. + if (visibleSelection) { + picker.valueSelection = visibleSelection; + } + // Pick mode: return with promise if (pick) { return pickPromise?.p; diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 16b52782c26..ed28f8c0c1f 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -725,7 +725,15 @@ export class QuickPick extends QuickInput implements I return this.ui.keyMods; } - set valueSelection(valueSelection: Readonly<[number, number]>) { + get valueSelection() { + const selection = this.ui.inputBox.getSelection(); + if (!selection) { + return undefined; + } + return [selection.start, selection.end]; + } + + set valueSelection(valueSelection: Readonly<[number, number]> | undefined) { this._valueSelection = valueSelection; this.valueSelectionUpdated = true; this.update(); @@ -1154,7 +1162,15 @@ export class InputBox extends QuickInput implements IInputBox { this.update(); } - set valueSelection(valueSelection: Readonly<[number, number]>) { + get valueSelection() { + const selection = this.ui.inputBox.getSelection(); + if (!selection) { + return undefined; + } + return [selection.start, selection.end]; + } + + set valueSelection(valueSelection: Readonly<[number, number]> | undefined) { this._valueSelection = valueSelection; this.valueSelectionUpdated = true; this.update(); diff --git a/src/vs/platform/quickinput/browser/quickInputBox.ts b/src/vs/platform/quickinput/browser/quickInputBox.ts index b3c6694387c..9ee1440a91a 100644 --- a/src/vs/platform/quickinput/browser/quickInputBox.ts +++ b/src/vs/platform/quickinput/browser/quickInputBox.ts @@ -59,6 +59,10 @@ export class QuickInputBox extends Disposable { this.findInput.inputBox.select(range); } + getSelection(): IRange | null { + return this.findInput.inputBox.getSelection(); + } + isSelectionAtEnd(): boolean { return this.findInput.inputBox.isSelectionAtEnd(); } From 1ade6d25cb230c85b7e9fb0408e1af4af8c828bf Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 5 Feb 2024 09:22:39 +0100 Subject: [PATCH 0902/1897] aux window - fix layout of quick pick (#204331) --- .../browser/quickInputController.ts | 19 ++++++++++++------- .../quickinput/browser/quickInputService.ts | 7 ++++++- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index d93c12cdcb1..d5a23ae66e8 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -41,7 +41,9 @@ export class QuickInputController extends Disposable { private controller: IQuickInput | null = null; - private parentElement: HTMLElement; + private _container: HTMLElement; + get container() { return this._container; } + private styles: IQuickInputStyles; private onShowEmitter = this._register(new Emitter()); @@ -54,10 +56,11 @@ export class QuickInputController extends Disposable { constructor(private options: IQuickInputOptions, private readonly themeService: IThemeService, - private readonly layoutService: ILayoutService) { + private readonly layoutService: ILayoutService + ) { super(); this.idPrefix = options.idPrefix; - this.parentElement = options.container; + this._container = options.container; this.styles = options.styles; this._register(Event.runAndSubscribe(dom.onDidRegisterWindow, ({ window, disposables }) => this.registerKeyModsListeners(window, disposables), { window: mainWindow, disposables: this._store })); this._register(dom.onWillUnregisterWindow(window => { @@ -67,6 +70,7 @@ export class QuickInputController extends Disposable { // existing parent to not loose functionality. // (https://github.com/microsoft/vscode/issues/195870) this.reparentUI(this.layoutService.mainContainer); + this.layout(this.layoutService.mainContainerDimension, this.layoutService.mainContainerOffset.quickPickTop); } })); } @@ -87,15 +91,16 @@ export class QuickInputController extends Disposable { // In order to support aux windows, re-parent the controller // if the original event is from a different document if (showInActiveContainer) { - if (this.parentElement.ownerDocument !== this.layoutService.activeContainer.ownerDocument) { + if (dom.getWindow(this._container) !== dom.getWindow(this.layoutService.activeContainer)) { this.reparentUI(this.layoutService.activeContainer); + this.layout(this.layoutService.activeContainerDimension, this.layoutService.activeContainerOffset.quickPickTop); } } return this.ui; } - const container = dom.append(this.parentElement, $('.quick-input-widget.show-file-icons')); + const container = dom.append(this._container, $('.quick-input-widget.show-file-icons')); container.tabIndex = -1; container.style.display = 'none'; @@ -322,8 +327,8 @@ export class QuickInputController extends Disposable { private reparentUI(container: HTMLElement): void { if (this.ui) { - this.parentElement = container; - dom.append(this.parentElement, this.ui.container); + this._container = container; + dom.append(this._container, this.ui.container); } } diff --git a/src/vs/platform/quickinput/browser/quickInputService.ts b/src/vs/platform/quickinput/browser/quickInputService.ts index db4284cba8c..c36470d2a95 100644 --- a/src/vs/platform/quickinput/browser/quickInputService.ts +++ b/src/vs/platform/quickinput/browser/quickInputService.ts @@ -22,6 +22,7 @@ import { IQuickInputOptions, IQuickInputStyles, QuickInputHoverDelegate } from ' import { QuickInputController, IQuickInputControllerHost } from 'vs/platform/quickinput/browser/quickInputController'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { getWindow } from 'vs/base/browser/dom'; export class QuickInputService extends Themable implements IQuickInputService { @@ -105,7 +106,11 @@ export class QuickInputService extends Themable implements IQuickInputService { controller.layout(host.activeContainerDimension, host.activeContainerOffset.quickPickTop); // Layout changes - this._register(host.onDidLayoutActiveContainer(dimension => controller.layout(dimension, host.activeContainerOffset.quickPickTop))); + this._register(host.onDidLayoutActiveContainer(dimension => { + if (getWindow(host.activeContainer) === getWindow(controller.container)) { + controller.layout(dimension, host.activeContainerOffset.quickPickTop); + } + })); this._register(host.onDidChangeActiveContainer(() => { if (controller.isVisible()) { return; From a8653cf1e66c4dc168cde4e9ad93b03ae5b31bcb Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 5 Feb 2024 09:29:52 +0100 Subject: [PATCH 0903/1897] contrib - towards more instantiation kinds (#204229) --- .../api/browser/extensionHost.contribution.ts | 4 +- .../api/browser/viewsExtensionPoint.ts | 4 +- .../browser/actions/textInputActions.ts | 4 +- .../actions/widgetNavigationCommands.ts | 4 +- src/vs/workbench/browser/editor.ts | 18 +- .../parts/dialogs/dialog.web.contribution.ts | 4 +- .../parts/editor/editor.contribution.ts | 10 +- .../browser/workbench.contribution.ts | 6 +- src/vs/workbench/common/contributions.ts | 172 ++++++++++++++---- src/vs/workbench/common/editor.ts | 8 + .../browser/accessibility.contribution.ts | 8 +- .../browser/preview/bulkEdit.contribution.ts | 4 +- .../contrib/chat/browser/chat.contribution.ts | 4 +- .../browser/chatContributionServiceImpl.ts | 4 +- .../codeEditor/browser/editorFeatures.ts | 4 +- .../codeEditor/browser/toggleWordWrap.ts | 4 +- .../electron-sandbox/selectionClipboard.ts | 4 +- .../browser/customEditor.contribution.ts | 4 +- .../files/browser/files.contribution.ts | 14 +- .../browser/interactive.contribution.ts | 8 +- .../contrib/list/browser/list.contribution.ts | 4 +- .../browser/localHistory.contribution.ts | 4 +- .../browser/mergeEditor.contribution.ts | 4 +- .../browser/multiDiffEditor.contribution.ts | 6 +- .../contrib/clipboard/notebookClipboard.ts | 4 +- .../browser/contrib/marker/markerProvider.ts | 4 +- .../contrib/profile/notebookProfile.ts | 4 +- .../contrib/undoRedo/notebookUndoRedo.ts | 4 +- .../notebook/browser/notebook.contribution.ts | 16 +- .../browser/performance.contribution.ts | 4 +- .../browser/keyboardLayoutPicker.ts | 4 +- .../browser/preferences.contribution.ts | 6 +- .../remote/browser/remote.contribution.ts | 8 +- .../remote/common/remote.contribution.ts | 6 +- .../electron-sandbox/remote.contribution.ts | 8 +- .../search/browser/replaceContributions.ts | 4 +- .../search/browser/search.contribution.ts | 4 +- .../browser/searchEditor.contribution.ts | 6 +- .../splash/browser/splash.contribution.ts | 4 +- .../electron-sandbox/splash.contribution.ts | 4 +- .../electron-sandbox/terminal.contribution.ts | 4 +- .../contrib/url/browser/url.contribution.ts | 6 +- .../browser/userDataProfile.contribution.ts | 4 +- .../userDataSync.contribution.ts | 4 +- .../browser/webviewPanel.contribution.ts | 4 +- .../browser/walkThrough.contribution.ts | 4 +- .../browser/walkThroughInput.ts | 6 +- .../common/walkThroughContentProvider.ts | 4 +- .../browser/workspace.contribution.ts | 4 +- .../parts/dialogs/dialog.contribution.ts | 4 +- .../electron-sandbox/accessibilityService.ts | 4 +- .../browser/configurationService.ts | 4 +- .../editor/browser/editorPaneService.ts | 21 +++ .../editor/common/editorPaneService.ts | 25 +++ .../editor/test/browser/editorService.test.ts | 106 ++++++----- .../extensions/browser/extensionUrlHandler.ts | 4 +- .../remote/browser/remoteAgentService.ts | 4 +- .../electron-sandbox/remoteAgentService.ts | 4 +- ...extMateTokenizationFeature.contribution.ts | 4 +- .../browser/workingCopyBackupService.ts | 4 +- .../workingCopyBackupService.ts | 4 +- .../test/browser/contributions.test.ts | 118 ++++++++++-- .../test/browser/workbenchTestServices.ts | 13 +- src/vs/workbench/workbench.common.main.ts | 1 + 64 files changed, 511 insertions(+), 249 deletions(-) create mode 100644 src/vs/workbench/services/editor/browser/editorPaneService.ts create mode 100644 src/vs/workbench/services/editor/common/editorPaneService.ts diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index b04a4d3279a..c986c67b5e9 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; // --- other interested parties @@ -106,4 +106,4 @@ export class ExtensionPoints implements IWorkbenchContribution { } } -registerWorkbenchContribution2(ExtensionPoints.ID, ExtensionPoints, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(ExtensionPoints.ID, ExtensionPoints, WorkbenchPhase.BlockStartup); diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 03ab97c9c97..a022c6755b6 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -17,7 +17,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { Extensions as ViewletExtensions, PaneCompositeRegistry } from 'vs/workbench/browser/panecomposite'; import { CustomTreeView, RawCustomTreeViewContextKey, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { Extensions as ViewContainerExtensions, ICustomViewDescriptor, IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ResolvableTreeItem, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug'; import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files'; @@ -666,4 +666,4 @@ class ViewsExtensionHandler implements IWorkbenchContribution { } } -registerWorkbenchContribution2(ViewsExtensionHandler.ID, ViewsExtensionHandler, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(ViewsExtensionHandler.ID, ViewsExtensionHandler, WorkbenchPhase.BlockStartup); diff --git a/src/vs/workbench/browser/actions/textInputActions.ts b/src/vs/workbench/browser/actions/textInputActions.ts index 96cdb5f4307..dae9d15bd89 100644 --- a/src/vs/workbench/browser/actions/textInputActions.ts +++ b/src/vs/workbench/browser/actions/textInputActions.ts @@ -9,7 +9,7 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Disposable } from 'vs/base/common/lifecycle'; import { EventHelper, addDisposableListener, getActiveDocument, getWindow } from 'vs/base/browser/dom'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { isNative } from 'vs/base/common/platform'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -107,5 +107,5 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo registerWorkbenchContribution2( TextInputActionsProvider.ID, TextInputActionsProvider, - WorkbenchContributionInstantiation.BlockRestore // Block to allow right-click into input fields before restore finished + WorkbenchPhase.BlockRestore // Block to allow right-click into input fields before restore finished ); diff --git a/src/vs/workbench/browser/actions/widgetNavigationCommands.ts b/src/vs/workbench/browser/actions/widgetNavigationCommands.ts index 59b2113bdcc..bc2cb028dd2 100644 --- a/src/vs/workbench/browser/actions/widgetNavigationCommands.ts +++ b/src/vs/workbench/browser/actions/widgetNavigationCommands.ts @@ -9,7 +9,7 @@ import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/co import { WorkbenchListFocusContextKey, WorkbenchListScrollAtBottomContextKey, WorkbenchListScrollAtTopContextKey } from 'vs/platform/list/browser/listService'; import { Event } from 'vs/base/common/event'; import { combinedDisposable, toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; /** INavigableContainer represents a logical container composed of widgets that can be navigated back and forth with key shortcuts */ @@ -112,7 +112,7 @@ export function registerNavigableContainer(container: INavigableContainer): IDis return NavigableContainerManager.register(container); } -registerWorkbenchContribution2(NavigableContainerManager.ID, NavigableContainerManager, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(NavigableContainerManager.ID, NavigableContainerManager, WorkbenchPhase.BlockStartup); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'widgetNavigation.focusPrevious', diff --git a/src/vs/workbench/browser/editor.ts b/src/vs/workbench/browser/editor.ts index 158f451a6c3..7d0b333bece 100644 --- a/src/vs/workbench/browser/editor.ts +++ b/src/vs/workbench/browser/editor.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { EditorResourceAccessor, EditorExtensions, SideBySideEditor, IEditorDescriptor as ICommonEditorDescriptor, EditorCloseContext } from 'vs/workbench/common/editor'; +import { EditorResourceAccessor, EditorExtensions, SideBySideEditor, IEditorDescriptor as ICommonEditorDescriptor, EditorCloseContext, IWillInstantiateEditorPaneEvent } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -19,6 +19,7 @@ import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { Iterable } from 'vs/base/common/iterator'; +import { Emitter } from 'vs/base/common/event'; //#region Editor Pane Registry @@ -49,6 +50,14 @@ export interface IEditorPaneRegistry { */ export class EditorPaneDescriptor implements IEditorPaneDescriptor { + private static readonly instantiatedEditorPanes = new Set(); + static didInstantiateEditorPane(typeId: string): boolean { + return EditorPaneDescriptor.instantiatedEditorPanes.has(typeId); + } + + private static readonly _onWillInstantiateEditorPane = new Emitter(); + static readonly onWillInstantiateEditorPane = EditorPaneDescriptor._onWillInstantiateEditorPane.event; + static create( ctor: { new(...services: Services): EditorPane }, typeId: string, @@ -64,7 +73,12 @@ export class EditorPaneDescriptor implements IEditorPaneDescriptor { ) { } instantiate(instantiationService: IInstantiationService): EditorPane { - return instantiationService.createInstance(this.ctor); + EditorPaneDescriptor._onWillInstantiateEditorPane.fire({ typeId: this.typeId }); + + const pane = instantiationService.createInstance(this.ctor); + EditorPaneDescriptor.instantiatedEditorPanes.add(this.typeId); + + return pane; } describes(editorPane: EditorPane): boolean { diff --git a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts index a5794373124..a97e57d2cbe 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts @@ -9,7 +9,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IDialogsModel, IDialogViewItem } from 'vs/workbench/common/dialogs'; import { BrowserDialogHandler } from 'vs/workbench/browser/parts/dialogs/dialogHandler'; import { DialogService } from 'vs/workbench/services/dialogs/common/dialogService'; @@ -81,5 +81,5 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC registerWorkbenchContribution2( DialogHandlerContribution.ID, DialogHandlerContribution, - WorkbenchContributionInstantiation.BlockStartup // Block to allow for dialogs to show before restore finished + WorkbenchPhase.BlockStartup // Block to allow for dialogs to show before restore finished ); diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 8ab9dcce884..ca21ad13864 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -59,7 +59,7 @@ import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/com import { isMacintosh } from 'vs/base/common/platform'; import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { FloatingEditorClickMenu } from 'vs/workbench/browser/codeeditor'; -import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorAutoSave } from 'vs/workbench/browser/parts/editor/editorAutoSave'; import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess, AllEditorsByAppearanceQuickAccess, AllEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; @@ -127,10 +127,10 @@ Registry.as(EditorExtensions.EditorFactory).registerEdit //#region Workbench Contributions -registerWorkbenchContribution2(EditorAutoSave.ID, EditorAutoSave, WorkbenchContributionInstantiation.BlockRestore); -registerWorkbenchContribution2(EditorStatusContribution.ID, EditorStatusContribution, WorkbenchContributionInstantiation.BlockRestore); -registerWorkbenchContribution2(UntitledTextEditorWorkingCopyEditorHandler.ID, UntitledTextEditorWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); -registerWorkbenchContribution2(DynamicEditorConfigurations.ID, DynamicEditorConfigurations, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(EditorAutoSave.ID, EditorAutoSave, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(EditorStatusContribution.ID, EditorStatusContribution, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(UntitledTextEditorWorkingCopyEditorHandler.ID, UntitledTextEditorWorkingCopyEditorHandler, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(DynamicEditorConfigurations.ID, DynamicEditorConfigurations, WorkbenchPhase.BlockRestore); registerEditorContribution(FloatingEditorClickMenu.ID, FloatingEditorClickMenu, EditorContributionInstantiation.AfterFirstRender); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index a74859f5e3c..d9cfbe24654 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -9,7 +9,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform'; import { ConfigurationMigrationWorkbenchContribution, DynamicWorkbenchSecurityConfiguration, IConfigurationMigrationRegistry, workbenchConfigurationNodeBase, Extensions, ConfigurationKeyValuePairs, problemsConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { isStandalone } from 'vs/base/browser/browser'; -import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ActivityBarPosition, EditorActionsLocation, EditorTabsMode, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; const registry = Registry.as(ConfigurationExtensions.Configuration); @@ -18,10 +18,10 @@ const registry = Registry.as(ConfigurationExtensions.Con (function registerConfiguration(): void { // Migration support - registerWorkbenchContribution2(ConfigurationMigrationWorkbenchContribution.ID, ConfigurationMigrationWorkbenchContribution, WorkbenchContributionInstantiation.Eventually); + registerWorkbenchContribution2(ConfigurationMigrationWorkbenchContribution.ID, ConfigurationMigrationWorkbenchContribution, WorkbenchPhase.Eventually); // Dynamic Configuration - registerWorkbenchContribution2(DynamicWorkbenchSecurityConfiguration.ID, DynamicWorkbenchSecurityConfiguration, WorkbenchContributionInstantiation.AfterRestored); + registerWorkbenchContribution2(DynamicWorkbenchSecurityConfiguration.ID, DynamicWorkbenchSecurityConfiguration, WorkbenchPhase.AfterRestored); // Workbench registry.registerConfiguration({ diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index 986fd296744..49801022a18 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -10,6 +10,10 @@ import { IdleDeadline, DeferredPromise, runWhenGlobalIdle } from 'vs/base/common import { mark } from 'vs/base/common/performance'; import { ILogService } from 'vs/platform/log/common/log'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { getOrSet } from 'vs/base/common/map'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IEditorPaneService } from 'vs/workbench/services/editor/common/editorPaneService'; /** * A workbench contribution that will be loaded when the workbench starts and disposed when the workbench shuts down. @@ -22,7 +26,7 @@ export namespace Extensions { export const Workbench = 'workbench.contributions.kind'; } -export const enum WorkbenchContributionInstantiation { +export const enum WorkbenchPhase { /** * The first phase signals that we are about to startup getting ready. @@ -52,37 +56,63 @@ export const enum WorkbenchContributionInstantiation { * The last phase after views, panels and editors have restored and * some time has passed (2-5 seconds). */ - Eventually = LifecyclePhase.Eventually, - - /** - * The contribution is created only when explicitly requested via - * `getContribution()`. - */ - Lazy = LifecyclePhase.Eventually + 1 + Eventually = LifecyclePhase.Eventually } -function toInstantiation(phase: LifecyclePhase): WorkbenchContributionInstantiation { +/** + * A workbenchch contribution that will only be instantiated + * when calling `getWorkbenchContribution`. + */ +export interface ILazyWorkbenchContributionInstantiation { + readonly lazy: true; +} + +/** + * A workbench contribution that will be instantiated when the + * corresponding file system scheme is used. + */ +export interface IOnFilesystemWorkbenchContributionInstantiation { + readonly scheme: string; +} + +/** + * A workbench contribution that will be instantiated when the + * corresponding editor is being created. + */ +export interface IOnEditorWorkbenchContributionInstantiation { + readonly editorTypeId: string; +} + +function isOnFilesystemWorkbenchContributionInstantiation(obj: unknown): obj is IOnFilesystemWorkbenchContributionInstantiation { + const candidate = obj as IOnFilesystemWorkbenchContributionInstantiation | undefined; + return !!candidate && typeof candidate.scheme === 'string'; +} + +function isOnEditorWorkbenchContributionInstantiation(obj: unknown): obj is IOnEditorWorkbenchContributionInstantiation { + const candidate = obj as IOnEditorWorkbenchContributionInstantiation | undefined; + return !!candidate && typeof candidate.editorTypeId === 'string'; +} + +export type WorkbenchContributionInstantiation = WorkbenchPhase | ILazyWorkbenchContributionInstantiation | IOnFilesystemWorkbenchContributionInstantiation | IOnEditorWorkbenchContributionInstantiation; + +function toWorkbenchPhase(phase: LifecyclePhase.Restored | LifecyclePhase.Eventually): WorkbenchPhase.AfterRestored | WorkbenchPhase.Eventually { switch (phase) { - case LifecyclePhase.Starting: - return WorkbenchContributionInstantiation.BlockStartup; - case LifecyclePhase.Ready: - return WorkbenchContributionInstantiation.BlockRestore; case LifecyclePhase.Restored: - return WorkbenchContributionInstantiation.AfterRestored; + return WorkbenchPhase.AfterRestored; case LifecyclePhase.Eventually: - return WorkbenchContributionInstantiation.Eventually; + return WorkbenchPhase.Eventually; } } -function toPhase(instantiation: WorkbenchContributionInstantiation.BlockStartup | WorkbenchContributionInstantiation.BlockRestore | WorkbenchContributionInstantiation.AfterRestored | WorkbenchContributionInstantiation.Eventually): LifecyclePhase { +function toLifecyclePhase(instantiation: WorkbenchPhase): LifecyclePhase { switch (instantiation) { - case WorkbenchContributionInstantiation.BlockStartup: + case WorkbenchPhase.BlockStartup: return LifecyclePhase.Starting; - case WorkbenchContributionInstantiation.BlockRestore: + case WorkbenchPhase.BlockRestore: return LifecyclePhase.Ready; - case WorkbenchContributionInstantiation.AfterRestored: + case WorkbenchPhase.AfterRestored: return LifecyclePhase.Restored; - case WorkbenchContributionInstantiation.Eventually: + case WorkbenchPhase.Eventually: return LifecyclePhase.Eventually; } } @@ -119,7 +149,7 @@ interface IWorkbenchContributionRegistration { readonly ctor: IConstructorSignature; } -export class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry { +export class WorkbenchContributionsRegistry extends Disposable implements IWorkbenchContributionsRegistry { static readonly INSTANCE = new WorkbenchContributionsRegistry(); @@ -130,8 +160,12 @@ export class WorkbenchContributionsRegistry implements IWorkbenchContributionsRe private lifecycleService: ILifecycleService | undefined; private logService: ILogService | undefined; private environmentService: IEnvironmentService | undefined; + private fileService: IFileService | undefined; + private editorPaneService: IEditorPaneService | undefined; private readonly contributionsByPhase = new Map(); + private readonly contributionsByFilesystem = new Map(); + private readonly contributionsByEditor = new Map(); private readonly contributionsById = new Map(); private readonly instancesById = new Map(); @@ -142,42 +176,58 @@ export class WorkbenchContributionsRegistry implements IWorkbenchContributionsRe private readonly pendingRestoredContributions = new DeferredPromise(); readonly whenRestored = this.pendingRestoredContributions.p; + registerWorkbenchContribution2(id: string, ctor: IConstructorSignature, phase: WorkbenchPhase.BlockStartup | WorkbenchPhase.BlockRestore): void; + registerWorkbenchContribution2(id: string | undefined, ctor: IConstructorSignature, phase: WorkbenchPhase.AfterRestored | WorkbenchPhase.Eventually): void; + registerWorkbenchContribution2(id: string, ctor: IConstructorSignature, lazy: ILazyWorkbenchContributionInstantiation): void; + registerWorkbenchContribution2(id: string, ctor: IConstructorSignature, onFileSystem: IOnFilesystemWorkbenchContributionInstantiation): void; + registerWorkbenchContribution2(id: string, ctor: IConstructorSignature, onEditor: IOnEditorWorkbenchContributionInstantiation): void; registerWorkbenchContribution2(id: string | undefined, ctor: IConstructorSignature, instantiation: WorkbenchContributionInstantiation): void { const contribution: IWorkbenchContributionRegistration = { id, ctor }; - // Instantiate directly if we are already matching the provided phase - if (instantiation !== WorkbenchContributionInstantiation.Lazy && this.instantiationService && this.lifecycleService && this.logService && this.environmentService && this.lifecycleService.phase >= instantiation) { - this.safeCreateContribution(this.instantiationService, this.logService, this.environmentService, contribution, toPhase(instantiation)); + // Instantiate directly if we already have a matching instantiation condition + if ( + this.instantiationService && this.lifecycleService && this.logService && this.environmentService && this.fileService && this.editorPaneService && + ( + (typeof instantiation === 'number' && this.lifecycleService.phase >= instantiation) || + (typeof id === 'string' && isOnFilesystemWorkbenchContributionInstantiation(instantiation) && this.fileService.getProvider(instantiation.scheme)) || + (typeof id === 'string' && isOnEditorWorkbenchContributionInstantiation(instantiation) && this.editorPaneService.didInstantiateEditorPane(instantiation.editorTypeId)) + ) + ) { + this.safeCreateContribution(this.instantiationService, this.logService, this.environmentService, contribution, typeof instantiation === 'number' ? toLifecyclePhase(instantiation) : this.lifecycleService.phase); } // Otherwise keep contributions by instantiation kind for later instantiation else { // by phase - if (instantiation !== WorkbenchContributionInstantiation.Lazy) { - const phase = toPhase(instantiation); - let contributionsForPhase = this.contributionsByPhase.get(phase); - if (!contributionsForPhase) { - contributionsForPhase = []; - this.contributionsByPhase.set(phase, contributionsForPhase); - } - - contributionsForPhase.push(contribution); + if (typeof instantiation === 'number') { + getOrSet(this.contributionsByPhase, toLifecyclePhase(instantiation), []).push(contribution); } - // by id if (typeof id === 'string') { + + // by id if (!this.contributionsById.has(id)) { this.contributionsById.set(id, contribution); } else { console.error(`IWorkbenchContributionsRegistry#registerWorkbenchContribution(): Can't register multiple contributions with same id '${id}'`); } + + // by filesystem + if (isOnFilesystemWorkbenchContributionInstantiation(instantiation)) { + getOrSet(this.contributionsByFilesystem, instantiation.scheme, []).push(contribution); + } + + // by editor + if (isOnEditorWorkbenchContributionInstantiation(instantiation)) { + getOrSet(this.contributionsByEditor, instantiation.editorTypeId, []).push(contribution); + } } } } - registerWorkbenchContribution(ctor: IConstructorSignature, phase: LifecyclePhase): void { - this.registerWorkbenchContribution2(undefined, ctor, toInstantiation(phase)); + registerWorkbenchContribution(ctor: IConstructorSignature, phase: LifecyclePhase.Restored | LifecyclePhase.Eventually): void { + this.registerWorkbenchContribution2(undefined, ctor, toWorkbenchPhase(phase)); } getWorkbenchContribution(id: string): T { @@ -198,9 +248,8 @@ export class WorkbenchContributionsRegistry implements IWorkbenchContributionsRe throw new Error(`IWorkbenchContributionsRegistry#getContribution('${id}'): contribution with that identifier is unknown.`); } - const phase = lifecycleService.phase; - if (phase < LifecyclePhase.Restored) { - logService.warn(`IWorkbenchContributionsRegistry#getContribution('${id}'): lazy contribution instantiated before LifecyclePhase.Restored!`); + if (lifecycleService.phase < LifecyclePhase.Restored) { + logService.warn(`IWorkbenchContributionsRegistry#getContribution('${id}'): contribution instantiated before LifecyclePhase.Restored!`); } this.safeCreateContribution(instantiationService, logService, environmentService, contribution, lifecycleService.phase); @@ -218,10 +267,51 @@ export class WorkbenchContributionsRegistry implements IWorkbenchContributionsRe const lifecycleService = this.lifecycleService = accessor.get(ILifecycleService); const logService = this.logService = accessor.get(ILogService); const environmentService = this.environmentService = accessor.get(IEnvironmentService); + const fileService = this.fileService = accessor.get(IFileService); + const editorPaneService = this.editorPaneService = accessor.get(IEditorPaneService); + // Instantiate contributions by phase when they are ready for (const phase of [LifecyclePhase.Starting, LifecyclePhase.Ready, LifecyclePhase.Restored, LifecyclePhase.Eventually]) { this.instantiateByPhase(instantiationService, lifecycleService, logService, environmentService, phase); } + + // Instantiate contributions by filesystem when they are activated or ready + for (const scheme of this.contributionsByFilesystem.keys()) { + if (fileService.getProvider(scheme)) { + this.onFilesystem(scheme, instantiationService, lifecycleService, logService, environmentService); + } + } + this._register(fileService.onWillActivateFileSystemProvider(e => this.onFilesystem(e.scheme, instantiationService, lifecycleService, logService, environmentService))); + + // Instantiate contributions by editor when they are created or have been + for (const editorTypeId of this.contributionsByEditor.keys()) { + if (editorPaneService.didInstantiateEditorPane(editorTypeId)) { + this.onEditor(editorTypeId, instantiationService, lifecycleService, logService, environmentService); + } + } + this._register(editorPaneService.onWillInstantiateEditorPane(e => this.onEditor(e.typeId, instantiationService, lifecycleService, logService, environmentService))); + } + + private onFilesystem(scheme: string, instantiationService: IInstantiationService, lifecycleService: ILifecycleService, logService: ILogService, environmentService: IEnvironmentService): void { + const contributions = this.contributionsByFilesystem.get(scheme); + if (contributions) { + this.contributionsByFilesystem.delete(scheme); + + for (const contribution of contributions) { + this.safeCreateContribution(instantiationService, logService, environmentService, contribution, lifecycleService.phase); + } + } + } + + private onEditor(editorTypeId: string, instantiationService: IInstantiationService, lifecycleService: ILifecycleService, logService: ILogService, environmentService: IEnvironmentService): void { + const contributions = this.contributionsByEditor.get(editorTypeId); + if (contributions) { + this.contributionsByEditor.delete(editorTypeId); + + for (const contribution of contributions) { + this.safeCreateContribution(instantiationService, logService, environmentService, contribution, lifecycleService.phase); + } + } } private instantiateByPhase(instantiationService: IInstantiationService, lifecycleService: ILifecycleService, logService: ILogService, environmentService: IEnvironmentService, phase: LifecyclePhase): void { @@ -358,7 +448,9 @@ export class WorkbenchContributionsRegistry implements IWorkbenchContributionsRe * Register a workbench contribution that will be instantiated * based on the `instantiation` property. */ -export const registerWorkbenchContribution2 = WorkbenchContributionsRegistry.INSTANCE.registerWorkbenchContribution2.bind(WorkbenchContributionsRegistry.INSTANCE) as { (id: string, ctor: IWorkbenchContributionSignature, instantiation: WorkbenchContributionInstantiation): void }; +export const registerWorkbenchContribution2 = WorkbenchContributionsRegistry.INSTANCE.registerWorkbenchContribution2.bind(WorkbenchContributionsRegistry.INSTANCE) as { + (id: string, ctor: IWorkbenchContributionSignature, instantiation: WorkbenchContributionInstantiation): void; +}; /** * Provides access to a workbench contribution with a specific identifier. diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index b90fb940fe6..bf6785d9675 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1102,6 +1102,14 @@ export interface IEditorWillMoveEvent extends IEditorIdentifier { export interface IEditorWillOpenEvent extends IEditorIdentifier { } +export interface IWillInstantiateEditorPaneEvent { + + /** + * @see {@link IEditorDescriptor.typeId} + */ + readonly typeId: string; +} + export type GroupIdentifier = number; export const enum GroupModelChangeKind { diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index b002e6ba681..4ec8d2e9b16 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -5,7 +5,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { DynamicSpeechAccessibilityConfiguration, registerAccessibilityConfiguration } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, WorkbenchPhase, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { IAccessibleViewService, AccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; @@ -28,6 +28,6 @@ workbenchRegistry.registerWorkbenchContribution(HoverAccessibleViewContribution, workbenchRegistry.registerWorkbenchContribution(NotificationAccessibleViewContribution, LifecyclePhase.Eventually); workbenchRegistry.registerWorkbenchContribution(InlineCompletionsAccessibleViewContribution, LifecyclePhase.Eventually); -registerWorkbenchContribution2(AccessibilityStatus.ID, AccessibilityStatus, WorkbenchContributionInstantiation.BlockRestore); -registerWorkbenchContribution2(SaveAudioCueContribution.ID, SaveAudioCueContribution, WorkbenchContributionInstantiation.BlockRestore); -registerWorkbenchContribution2(DynamicSpeechAccessibilityConfiguration.ID, DynamicSpeechAccessibilityConfiguration, WorkbenchContributionInstantiation.AfterRestored); +registerWorkbenchContribution2(AccessibilityStatus.ID, AccessibilityStatus, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(SaveAudioCueContribution.ID, SaveAudioCueContribution, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(DynamicSpeechAccessibilityConfiguration.ID, DynamicSpeechAccessibilityConfiguration, WorkbenchPhase.AfterRestored); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts index 6665ee79708..1d7f842bebc 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Registry } from 'vs/platform/registry/common/platform'; -import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IBulkEditService, ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { BulkEditPane } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane'; import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views'; @@ -321,7 +321,7 @@ registerAction2(class ToggleGrouping extends Action2 { }); registerWorkbenchContribution2( - BulkEditPreviewContribution.ID, BulkEditPreviewContribution, WorkbenchContributionInstantiation.BlockRestore + BulkEditPreviewContribution.ID, BulkEditPreviewContribution, WorkbenchPhase.BlockRestore ); const refactorPreviewViewIcon = registerIcon('refactor-preview-view-icon', Codicon.lightbulb, localize('refactorPreviewViewIcon', 'View icon of the refactor preview view.')); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index c4ebb7a5ff2..5a9d5ef5cf8 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -13,7 +13,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, WorkbenchPhase, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { registerChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { registerChatCodeBlockActions } from 'vs/workbench/contrib/chat/browser/actions/chatCodeblockActions'; @@ -284,7 +284,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -registerWorkbenchContribution2(ChatResolverContribution.ID, ChatResolverContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(ChatResolverContribution.ID, ChatResolverContribution, WorkbenchPhase.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(ChatAccessibleViewContribution, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(ChatSlashStaticSlashCommandsContribution, LifecyclePhase.Eventually); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(ChatEditorInput.TypeID, ChatEditorInputSerializer); diff --git a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts index 8a9df3934e7..8498292395a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts @@ -11,7 +11,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation, Extensions as ViewExtensions } from 'vs/workbench/common/views'; import { getHistoryAction, getOpenChatEditorAction } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { getNewChatAction } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; @@ -154,7 +154,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { } } -registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchPhase.BlockStartup); export class ChatContributionService implements IChatContributionService { declare _serviceBrand: undefined; diff --git a/src/vs/workbench/contrib/codeEditor/browser/editorFeatures.ts b/src/vs/workbench/contrib/codeEditor/browser/editorFeatures.ts index 20fae232e41..1d511d4bb0e 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/editorFeatures.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/editorFeatures.ts @@ -8,7 +8,7 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { getEditorFeatures } from 'vs/editor/common/editorFeatures'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; class EditorFeaturesInstantiator extends Disposable implements IWorkbenchContribution { @@ -50,4 +50,4 @@ class EditorFeaturesInstantiator extends Disposable implements IWorkbenchContrib } } -registerWorkbenchContribution2(EditorFeaturesInstantiator.ID, EditorFeaturesInstantiator, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(EditorFeaturesInstantiator.ID, EditorFeaturesInstantiator, WorkbenchPhase.BlockRestore); diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index 09ab7d7cc5a..c83ebc45f77 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -17,7 +17,7 @@ import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from ' import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { Codicon } from 'vs/base/common/codicons'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Event } from 'vs/base/common/event'; import { addDisposableListener, onDidRegisterWindow } from 'vs/base/browser/dom'; @@ -314,7 +314,7 @@ class EditorWordWrapContextKeyTracker extends Disposable implements IWorkbenchCo } } -registerWorkbenchContribution2(EditorWordWrapContextKeyTracker.ID, EditorWordWrapContextKeyTracker, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(EditorWordWrapContextKeyTracker.ID, EditorWordWrapContextKeyTracker, WorkbenchPhase.BlockRestore); registerEditorContribution(ToggleWordWrapController.ID, ToggleWordWrapController, EditorContributionInstantiation.Eager); // eager because it needs to change the editor word wrap configuration registerDiffEditorContribution(DiffToggleWordWrapController.ID, DiffToggleWordWrapController); diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts index ae407a26a24..46b835cd96d 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts @@ -16,7 +16,7 @@ import { IEditorContribution, Handler } from 'vs/editor/common/editorCommon'; import { EndOfLinePreference } from 'vs/editor/common/model'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { mainWindow } from 'vs/base/browser/window'; @@ -142,6 +142,6 @@ class PasteSelectionClipboardAction extends EditorAction { registerEditorContribution(SelectionClipboardContributionID, SelectionClipboard, EditorContributionInstantiation.Eager); // eager because it needs to listen to selection change events if (platform.isLinux) { - registerWorkbenchContribution2(LinuxSelectionClipboardPastePreventer.ID, LinuxSelectionClipboardPastePreventer, WorkbenchContributionInstantiation.BlockRestore); // eager because it listens to mouse-up events globally + registerWorkbenchContribution2(LinuxSelectionClipboardPastePreventer.ID, LinuxSelectionClipboardPastePreventer, WorkbenchPhase.BlockRestore); // eager because it listens to mouse-up events globally registerEditorAction(PasteSelectionClipboardAction); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts b/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts index 9caf2213c67..86e623975c0 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts @@ -7,7 +7,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { ComplexCustomWorkingCopyEditorHandler as ComplexCustomWorkingCopyEditorHandler, CustomEditorInputSerializer } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory'; import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; @@ -30,4 +30,4 @@ Registry.as(EditorExtensions.EditorPane) Registry.as(EditorExtensions.EditorFactory) .registerEditorSerializer(CustomEditorInputSerializer.ID, CustomEditorInputSerializer); -registerWorkbenchContribution2(ComplexCustomWorkingCopyEditorHandler.ID, ComplexCustomWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(ComplexCustomWorkingCopyEditorHandler.ID, ComplexCustomWorkingCopyEditorHandler, WorkbenchPhase.BlockStartup); diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 22ade982cbe..80162ffd007 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { sep } from 'vs/base/common/path'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IFileEditorInput, IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { AutoSaveConfiguration, HotExitConfiguration, FILES_EXCLUDE_CONFIG, FILES_ASSOCIATIONS_CONFIG, FILES_READONLY_INCLUDE_CONFIG, FILES_READONLY_EXCLUDE_CONFIG, FILES_READONLY_FROM_PERMISSIONS_CONFIG } from 'vs/platform/files/common/files'; import { SortOrder, LexicographicOptions, FILE_EDITOR_INPUT_ID, BINARY_TEXT_FILE_MODE, UndoConfirmLevel, IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; @@ -98,25 +98,25 @@ Registry.as(EditorExtensions.EditorFactory).registerFile // Register Editor Input Serializer & Handler Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(FILE_EDITOR_INPUT_ID, FileEditorInputSerializer); -registerWorkbenchContribution2(FileEditorWorkingCopyEditorHandler.ID, FileEditorWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(FileEditorWorkingCopyEditorHandler.ID, FileEditorWorkingCopyEditorHandler, WorkbenchPhase.BlockRestore); // Register Explorer views -registerWorkbenchContribution2(ExplorerViewletViewsContribution.ID, ExplorerViewletViewsContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(ExplorerViewletViewsContribution.ID, ExplorerViewletViewsContribution, WorkbenchPhase.BlockStartup); // Register Text File Editor Tracker -registerWorkbenchContribution2(TextFileEditorTracker.ID, TextFileEditorTracker, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(TextFileEditorTracker.ID, TextFileEditorTracker, WorkbenchPhase.BlockStartup); // Register Text File Save Error Handler -registerWorkbenchContribution2(TextFileSaveErrorHandler.ID, TextFileSaveErrorHandler, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(TextFileSaveErrorHandler.ID, TextFileSaveErrorHandler, WorkbenchPhase.BlockStartup); // Register uri display for file uris -registerWorkbenchContribution2(FileUriLabelContribution.ID, FileUriLabelContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(FileUriLabelContribution.ID, FileUriLabelContribution, WorkbenchPhase.BlockStartup); // Register Workspace Watcher Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceWatcher, LifecyclePhase.Restored); // Register Dirty Files Indicator -registerWorkbenchContribution2(DirtyFilesIndicator.ID, DirtyFilesIndicator, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(DirtyFilesIndicator.ID, DirtyFilesIndicator, WorkbenchPhase.BlockStartup); // Configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index c84512a71b5..3ba9491111e 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -36,7 +36,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { Registry } from 'vs/platform/registry/common/platform'; import { contrastBorder, ifDefinedThenElse, listInactiveSelectionBackground, registerColor } from 'vs/platform/theme/common/colorRegistry'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, EditorsOrder, IEditorFactoryRegistry, IEditorSerializer, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { PANEL_BORDER } from 'vs/workbench/common/theme'; @@ -254,9 +254,9 @@ class InteractiveWindowWorkingCopyEditorHandler extends Disposable implements IW } } -registerWorkbenchContribution2(InteractiveDocumentContribution.ID, InteractiveDocumentContribution, WorkbenchContributionInstantiation.BlockRestore); -registerWorkbenchContribution2(InteractiveInputContentProvider.ID, InteractiveInputContentProvider, WorkbenchContributionInstantiation.BlockRestore); -registerWorkbenchContribution2(InteractiveWindowWorkingCopyEditorHandler.ID, InteractiveWindowWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(InteractiveDocumentContribution.ID, InteractiveDocumentContribution, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(InteractiveInputContentProvider.ID, InteractiveInputContentProvider, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(InteractiveWindowWorkingCopyEditorHandler.ID, InteractiveWindowWorkingCopyEditorHandler, WorkbenchPhase.BlockRestore); type interactiveEditorInputData = { resource: URI; inputResource: URI; name: string; language: string }; diff --git a/src/vs/workbench/contrib/list/browser/list.contribution.ts b/src/vs/workbench/contrib/list/browser/list.contribution.ts index 4bd577d21c6..dffda536058 100644 --- a/src/vs/workbench/contrib/list/browser/list.contribution.ts +++ b/src/vs/workbench/contrib/list/browser/list.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; export class ListContext implements IWorkbenchContribution { @@ -20,4 +20,4 @@ export class ListContext implements IWorkbenchContribution { } } -registerWorkbenchContribution2(ListContext.ID, ListContext, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(ListContext.ID, ListContext, WorkbenchPhase.BlockStartup); diff --git a/src/vs/workbench/contrib/localHistory/browser/localHistory.contribution.ts b/src/vs/workbench/contrib/localHistory/browser/localHistory.contribution.ts index 08bc1675469..065f081d9a6 100644 --- a/src/vs/workbench/contrib/localHistory/browser/localHistory.contribution.ts +++ b/src/vs/workbench/contrib/localHistory/browser/localHistory.contribution.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/workbench/contrib/localHistory/browser/localHistoryCommands'; -import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { LocalHistoryTimeline } from 'vs/workbench/contrib/localHistory/browser/localHistoryTimeline'; // Register Local History Timeline -registerWorkbenchContribution2(LocalHistoryTimeline.ID, LocalHistoryTimeline, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(LocalHistoryTimeline.ID, LocalHistoryTimeline, WorkbenchPhase.BlockRestore); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts index acd58fea48b..a1600ccbdd2 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts @@ -9,7 +9,7 @@ import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/co import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { AcceptAllInput1, AcceptAllInput2, AcceptMerge, CompareInput1WithBaseCommand, @@ -94,4 +94,4 @@ Registry .as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(MergeEditorOpenHandlerContribution, LifecyclePhase.Restored); -registerWorkbenchContribution2(MergeEditorResolverContribution.ID, MergeEditorResolverContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(MergeEditorResolverContribution.ID, MergeEditorResolverContribution, WorkbenchPhase.BlockStartup); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts index 99af4f8e13a..ca7400fb4fa 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts @@ -9,7 +9,7 @@ import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/co import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { MultiDiffEditor } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor'; import { MultiDiffEditorInput, MultiDiffEditorResolverContribution, MultiDiffEditorSerializer } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput'; @@ -36,7 +36,7 @@ Registry.as(Extensions.Configuration) registerSingleton(IMultiDiffSourceResolverService, MultiDiffSourceResolverService, InstantiationType.Delayed); // Editor Integration -registerWorkbenchContribution2(MultiDiffEditorResolverContribution.ID, MultiDiffEditorResolverContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(MultiDiffEditorResolverContribution.ID, MultiDiffEditorResolverContribution, WorkbenchPhase.BlockStartup); Registry.as(EditorExtensions.EditorPane) .registerEditorPane( @@ -49,4 +49,4 @@ Registry.as(EditorExtensions.EditorFactory) // SCM integration registerAction2(OpenScmGroupAction); -registerWorkbenchContribution2(ScmMultiDiffSourceResolverContribution.ID, ScmMultiDiffSourceResolverContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(ScmMultiDiffSourceResolverContribution.ID, ScmMultiDiffSourceResolverContribution, WorkbenchPhase.BlockStartup); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts index 09c8d7c4aaf..73f0201827b 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts @@ -5,7 +5,7 @@ import { localize, localize2 } from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; -import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { NOTEBOOK_CELL_EDITABLE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { cellRangeToViewCells, expandCellRangesWithHiddenCells, getNotebookEditorFromEditorPane, ICellViewModel, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -405,7 +405,7 @@ export class NotebookClipboardContribution extends Disposable { } } -registerWorkbenchContribution2(NotebookClipboardContribution.ID, NotebookClipboardContribution, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(NotebookClipboardContribution.ID, NotebookClipboardContribution, WorkbenchPhase.BlockRestore); const COPY_CELL_COMMAND_ID = 'notebook.cell.copy'; const CUT_CELL_COMMAND_ID = 'notebook.cell.cut'; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider.ts b/src/vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider.ts index 3ae73e42ceb..760263fada1 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IMarkerListProvider, MarkerList, IMarkerNavigationService } from 'vs/editor/contrib/gotoError/browser/markerNavigationService'; import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; @@ -97,6 +97,6 @@ class NotebookMarkerDecorationContribution extends Disposable implements INotebo } } -registerWorkbenchContribution2(MarkerListProvider.ID, MarkerListProvider, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(MarkerListProvider.ID, MarkerListProvider, WorkbenchPhase.BlockRestore); registerNotebookContribution(NotebookMarkerDecorationContribution.id, NotebookMarkerDecorationContribution); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts b/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts index e5a0d065ac1..ae330d22aac 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts @@ -10,7 +10,7 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; -import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; export enum NotebookProfileType { default = 'default', @@ -125,4 +125,4 @@ export class NotebookProfileContribution extends Disposable { } } -registerWorkbenchContribution2(NotebookProfileContribution.ID, NotebookProfileContribution, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(NotebookProfileContribution.ID, NotebookProfileContribution, WorkbenchPhase.BlockRestore); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts b/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts index 5591817b999..ebe2244af75 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CellEditState, getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -64,4 +64,4 @@ class NotebookUndoRedoContribution extends Disposable { } } -registerWorkbenchContribution2(NotebookUndoRedoContribution.ID, NotebookUndoRedoContribution, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(NotebookUndoRedoContribution.ID, NotebookUndoRedoContribution, WorkbenchPhase.BlockRestore); diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 72f9ffe60e2..bcc27365df5 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -22,7 +22,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IEditorSerializer, IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; @@ -730,13 +730,13 @@ class NotebookAccessibleViewContribution extends Disposable { } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -registerWorkbenchContribution2(NotebookContribution.ID, NotebookContribution, WorkbenchContributionInstantiation.BlockStartup); -registerWorkbenchContribution2(CellContentProvider.ID, CellContentProvider, WorkbenchContributionInstantiation.BlockStartup); -registerWorkbenchContribution2(CellInfoContentProvider.ID, CellInfoContentProvider, WorkbenchContributionInstantiation.BlockStartup); -registerWorkbenchContribution2(RegisterSchemasContribution.ID, RegisterSchemasContribution, WorkbenchContributionInstantiation.BlockStartup); -registerWorkbenchContribution2(NotebookEditorManager.ID, NotebookEditorManager, WorkbenchContributionInstantiation.BlockRestore); -registerWorkbenchContribution2(NotebookLanguageSelectorScoreRefine.ID, NotebookLanguageSelectorScoreRefine, WorkbenchContributionInstantiation.BlockRestore); -registerWorkbenchContribution2(SimpleNotebookWorkingCopyEditorHandler.ID, SimpleNotebookWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(NotebookContribution.ID, NotebookContribution, WorkbenchPhase.BlockStartup); +registerWorkbenchContribution2(CellContentProvider.ID, CellContentProvider, WorkbenchPhase.BlockStartup); +registerWorkbenchContribution2(CellInfoContentProvider.ID, CellInfoContentProvider, WorkbenchPhase.BlockStartup); +registerWorkbenchContribution2(RegisterSchemasContribution.ID, RegisterSchemasContribution, WorkbenchPhase.BlockStartup); +registerWorkbenchContribution2(NotebookEditorManager.ID, NotebookEditorManager, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(NotebookLanguageSelectorScoreRefine.ID, NotebookLanguageSelectorScoreRefine, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(SimpleNotebookWorkingCopyEditorHandler.ID, SimpleNotebookWorkingCopyEditorHandler, WorkbenchPhase.BlockRestore); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookAccessibilityHelpContribution, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookAccessibleViewContribution, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookVariables, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts index 79e97bf85c9..18f8d85b4cc 100644 --- a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts +++ b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts @@ -9,7 +9,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { Extensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { Extensions, IWorkbenchContributionsRegistry, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorSerializer, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { PerfviewContrib, PerfviewInput } from 'vs/workbench/contrib/performance/browser/perfviewEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -22,7 +22,7 @@ import { InputLatencyContrib } from 'vs/workbench/contrib/performance/browser/in registerWorkbenchContribution2( PerfviewContrib.ID, PerfviewContrib, - WorkbenchContributionInstantiation.Lazy + { lazy: true } ); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer( diff --git a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts index 6d1349aa2ba..50ed5aaa3fd 100644 --- a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts +++ b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/browser/statusbar'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { parseKeyboardLayoutDescription, areKeyboardLayoutsEqual, getKeyboardLayoutId, IKeyboardLayoutService, IKeyboardLayoutInfo } from 'vs/platform/keyboardLayout/common/keyboardLayout'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { KEYBOARD_LAYOUT_OPEN_PICKER } from 'vs/workbench/contrib/preferences/common/preferences'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { QuickPickInput, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; @@ -80,7 +80,7 @@ export class KeyboardLayoutPickerContribution extends Disposable implements IWor } } -registerWorkbenchContribution2(KeyboardLayoutPickerContribution.ID, KeyboardLayoutPickerContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(KeyboardLayoutPickerContribution.ID, KeyboardLayoutPickerContribution, WorkbenchPhase.BlockStartup); interface LayoutQuickPickItem extends IQuickPickItem { layout: IKeyboardLayoutInfo; diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 03b17a52c85..f843df64aa3 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -24,7 +24,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry, IEditorSerializer } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ResourceContextKey, RemoteNameContext, WorkbenchStateContext } from 'vs/workbench/common/contextkeys'; @@ -1287,8 +1287,8 @@ class SettingsEditorTitleContribution extends Disposable implements IWorkbenchCo } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -registerWorkbenchContribution2(PreferencesActionsContribution.ID, PreferencesActionsContribution, WorkbenchContributionInstantiation.BlockStartup); -registerWorkbenchContribution2(PreferencesContribution.ID, PreferencesContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(PreferencesActionsContribution.ID, PreferencesActionsContribution, WorkbenchPhase.BlockStartup); +registerWorkbenchContribution2(PreferencesContribution.ID, PreferencesContribution, WorkbenchPhase.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(SettingsEditorTitleContribution, LifecyclePhase.Restored); registerEditorContribution(SettingsEditorContribution.ID, SettingsEditorContribution, EditorContributionInstantiation.AfterFirstRender); diff --git a/src/vs/workbench/contrib/remote/browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/browser/remote.contribution.ts index 2d1367296c8..83aeb5466d5 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, WorkbenchPhase, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { ShowCandidateContribution } from 'vs/workbench/contrib/remote/browser/showCandidate'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -13,10 +13,10 @@ import { RemoteStatusIndicator } from 'vs/workbench/contrib/remote/browser/remot import { AutomaticPortForwarding, ForwardedPortsView, PortRestore } from 'vs/workbench/contrib/remote/browser/remoteExplorer'; const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -registerWorkbenchContribution2(ShowCandidateContribution.ID, ShowCandidateContribution, WorkbenchContributionInstantiation.BlockRestore); -registerWorkbenchContribution2(TunnelFactoryContribution.ID, TunnelFactoryContribution, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(ShowCandidateContribution.ID, ShowCandidateContribution, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(TunnelFactoryContribution.ID, TunnelFactoryContribution, WorkbenchPhase.BlockRestore); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually); -registerWorkbenchContribution2(RemoteStatusIndicator.ID, RemoteStatusIndicator, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(RemoteStatusIndicator.ID, RemoteStatusIndicator, WorkbenchPhase.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(ForwardedPortsView, LifecyclePhase.Restored); workbenchContributionsRegistry.registerWorkbenchContribution(PortRestore, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(AutomaticPortForwarding, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 797d50d8293..1b76477c4b7 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchPhase, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILabelService, ResourceLabelFormatting } from 'vs/platform/label/common/label'; @@ -234,9 +234,9 @@ class InitialRemoteConnectionHealthContribution implements IWorkbenchContributio } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -registerWorkbenchContribution2(LabelContribution.ID, LabelContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(LabelContribution.ID, LabelContribution, WorkbenchPhase.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteChannelsContribution, LifecyclePhase.Restored); -registerWorkbenchContribution2(RemoteInvalidWorkspaceDetector.ID, RemoteInvalidWorkspaceDetector, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(RemoteInvalidWorkspaceDetector.ID, RemoteInvalidWorkspaceDetector, WorkbenchPhase.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(InitialRemoteConnectionHealthContribution, LifecyclePhase.Restored); const enableDiagnostics = true; diff --git a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts index 303173b1172..735d5d3b5f9 100644 --- a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts @@ -10,7 +10,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchContributionsExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchPhase, Extensions as WorkbenchContributionsExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILabelService } from 'vs/platform/label/common/label'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -186,10 +186,10 @@ class WSLContextKeyInitializer extends Disposable implements IWorkbenchContribut const workbenchContributionsRegistry = Registry.as(WorkbenchContributionsExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentDiagnosticListener, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteExtensionHostEnvironmentUpdater, LifecyclePhase.Eventually); -registerWorkbenchContribution2(RemoteTelemetryEnablementUpdater.ID, RemoteTelemetryEnablementUpdater, WorkbenchContributionInstantiation.BlockRestore); -registerWorkbenchContribution2(RemoteEmptyWorkbenchPresentation.ID, RemoteEmptyWorkbenchPresentation, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(RemoteTelemetryEnablementUpdater.ID, RemoteTelemetryEnablementUpdater, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(RemoteEmptyWorkbenchPresentation.ID, RemoteEmptyWorkbenchPresentation, WorkbenchPhase.BlockRestore); if (isWindows) { - registerWorkbenchContribution2(WSLContextKeyInitializer.ID, WSLContextKeyInitializer, WorkbenchContributionInstantiation.BlockRestore); + registerWorkbenchContribution2(WSLContextKeyInitializer.ID, WSLContextKeyInitializer, WorkbenchPhase.BlockRestore); } Registry.as(ConfigurationExtensions.Configuration) diff --git a/src/vs/workbench/contrib/search/browser/replaceContributions.ts b/src/vs/workbench/contrib/search/browser/replaceContributions.ts index a65ff6b0927..aa6cada9eb0 100644 --- a/src/vs/workbench/contrib/search/browser/replaceContributions.ts +++ b/src/vs/workbench/contrib/search/browser/replaceContributions.ts @@ -5,9 +5,9 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; import { ReplaceService, ReplacePreviewContentProvider } from 'vs/workbench/contrib/search/browser/replaceService'; -import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; export function registerContributions(): void { registerSingleton(IReplaceService, ReplaceService, InstantiationType.Delayed); - registerWorkbenchContribution2(ReplacePreviewContentProvider.ID, ReplacePreviewContentProvider, WorkbenchContributionInstantiation.BlockStartup); + registerWorkbenchContribution2(ReplacePreviewContentProvider.ID, ReplacePreviewContentProvider, WorkbenchPhase.BlockStartup); } diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 412bc161f3f..545df09074c 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -16,7 +16,7 @@ import { Extensions as QuickAccessExtensions, IQuickAccessRegistry } from 'vs/pl import { Registry } from 'vs/platform/registry/common/platform'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { defaultQuickAccessContextKeyValue } from 'vs/workbench/browser/quickaccess'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { Extensions as ViewExtensions, IViewContainersRegistry, IViewDescriptor, IViewDescriptorService, IViewsRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { GotoSymbolQuickAccessProvider } from 'vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess'; import { AnythingQuickAccessProvider } from 'vs/workbench/contrib/search/browser/anythingQuickAccess'; @@ -101,7 +101,7 @@ class RegisterSearchViewContribution implements IWorkbenchContribution { .registerConfigurationMigrations([{ key: 'search.location', migrateFn: (value: any) => ({ value: undefined }) }]); } } -registerWorkbenchContribution2(RegisterSearchViewContribution.ID, RegisterSearchViewContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(RegisterSearchViewContribution.ID, RegisterSearchViewContribution, WorkbenchPhase.BlockStartup); // Register Quick Access Handler const quickAccessRegistry = Registry.as(QuickAccessExtensions.Quickaccess); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index b4c7ebc14a1..020ae109fd0 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -18,7 +18,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IEditorSerializer, IEditorFactoryRegistry, EditorExtensions, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; @@ -100,7 +100,7 @@ class SearchEditorContribution implements IWorkbenchContribution { } } -registerWorkbenchContribution2(SearchEditorContribution.ID, SearchEditorContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(SearchEditorContribution.ID, SearchEditorContribution, WorkbenchPhase.BlockStartup); //#endregion //#region Input Serializer @@ -600,5 +600,5 @@ class SearchEditorWorkingCopyEditorHandler extends Disposable implements IWorkbe } } -registerWorkbenchContribution2(SearchEditorWorkingCopyEditorHandler.ID, SearchEditorWorkingCopyEditorHandler, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(SearchEditorWorkingCopyEditorHandler.ID, SearchEditorWorkingCopyEditorHandler, WorkbenchPhase.BlockRestore); //#endregion diff --git a/src/vs/workbench/contrib/splash/browser/splash.contribution.ts b/src/vs/workbench/contrib/splash/browser/splash.contribution.ts index 052c1bc0f3a..93d2428edee 100644 --- a/src/vs/workbench/contrib/splash/browser/splash.contribution.ts +++ b/src/vs/workbench/contrib/splash/browser/splash.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ISplashStorageService } from 'vs/workbench/contrib/splash/browser/splash'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { PartsSplash } from 'vs/workbench/contrib/splash/browser/partsSplash'; @@ -21,5 +21,5 @@ registerSingleton(ISplashStorageService, class SplashStorageService implements I registerWorkbenchContribution2( PartsSplash.ID, PartsSplash, - WorkbenchContributionInstantiation.BlockStartup + WorkbenchPhase.BlockStartup ); diff --git a/src/vs/workbench/contrib/splash/electron-sandbox/splash.contribution.ts b/src/vs/workbench/contrib/splash/electron-sandbox/splash.contribution.ts index 903b14be46b..c8fa8a6a604 100644 --- a/src/vs/workbench/contrib/splash/electron-sandbox/splash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-sandbox/splash.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ISplashStorageService } from 'vs/workbench/contrib/splash/browser/splash'; import { INativeHostService } from 'vs/platform/native/common/native'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -24,5 +24,5 @@ registerSingleton(ISplashStorageService, SplashStorageService, InstantiationType registerWorkbenchContribution2( PartsSplash.ID, PartsSplash, - WorkbenchContributionInstantiation.BlockStartup + WorkbenchPhase.BlockStartup ); diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts index 4d66e0016ec..4bdad738e60 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts @@ -7,7 +7,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; import { Registry } from 'vs/platform/registry/common/platform'; import { ILocalPtyService, TerminalIpcChannels } from 'vs/platform/terminal/common/terminal'; -import { IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, WorkbenchPhase, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalNativeContribution } from 'vs/workbench/contrib/terminal/electron-sandbox/terminalNativeContribution'; import { ElectronTerminalProfileResolverService } from 'vs/workbench/contrib/terminal/electron-sandbox/terminalProfileResolverService'; @@ -23,5 +23,5 @@ const workbenchRegistry = Registry.as(Workbench // This contribution needs to be active during the Startup phase to be available when a remote resolver tries to open a local // terminal while connecting to the remote. -registerWorkbenchContribution2(LocalTerminalBackendContribution.ID, LocalTerminalBackendContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(LocalTerminalBackendContribution.ID, LocalTerminalBackendContribution, WorkbenchPhase.BlockStartup); workbenchRegistry.registerWorkbenchContribution(TerminalNativeContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/url/browser/url.contribution.ts b/src/vs/workbench/contrib/url/browser/url.contribution.ts index 92e2b6b9611..34a226ceae6 100644 --- a/src/vs/workbench/contrib/url/browser/url.contribution.ts +++ b/src/vs/workbench/contrib/url/browser/url.contribution.ts @@ -11,7 +11,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { IURLService } from 'vs/platform/url/common/url'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ExternalUriResolverContribution } from 'vs/workbench/contrib/url/browser/externalUriResolver'; import { manageTrustedDomainSettingsCommand } from 'vs/workbench/contrib/url/browser/trustedDomains'; import { TrustedDomainsFileSystemProvider } from 'vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider'; @@ -66,12 +66,12 @@ Registry.as(WorkbenchExtensions.Workbench).regi registerWorkbenchContribution2( TrustedDomainsFileSystemProvider.ID, TrustedDomainsFileSystemProvider, - WorkbenchContributionInstantiation.BlockRestore + WorkbenchPhase.BlockRestore ); registerWorkbenchContribution2( ExternalUriResolverContribution.ID, ExternalUriResolverContribution, - WorkbenchContributionInstantiation.BlockRestore + WorkbenchPhase.BlockRestore ); diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts index 9bd37764021..d08a579c6a2 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, Extensions, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { UserDataProfilesWorkbenchContribution } from 'vs/workbench/contrib/userDataProfile/browser/userDataProfile'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import './userDataProfileActions'; import { UserDataProfilePreviewContribution } from 'vs/workbench/contrib/userDataProfile/browser/userDataProfilePreview'; const workbenchRegistry = Registry.as(Extensions.Workbench); -registerWorkbenchContribution2(UserDataProfilesWorkbenchContribution.ID, UserDataProfilesWorkbenchContribution, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(UserDataProfilesWorkbenchContribution.ID, UserDataProfilesWorkbenchContribution, WorkbenchPhase.BlockRestore); workbenchRegistry.registerWorkbenchContribution(UserDataProfilePreviewContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts index adc6fc4a54a..441b1dc8f55 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IUserDataSyncUtilService, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync'; import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; @@ -31,7 +31,7 @@ class UserDataSyncServicesContribution extends Disposable implements IWorkbenchC } } -registerWorkbenchContribution2(UserDataSyncServicesContribution.ID, UserDataSyncServicesContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(UserDataSyncServicesContribution.ID, UserDataSyncServicesContribution, WorkbenchPhase.BlockStartup); registerAction2(class OpenSyncBackupsFolder extends Action2 { constructor() { diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts index 702f7215239..02c9b112c5a 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts @@ -11,7 +11,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -82,7 +82,7 @@ class WebviewPanelContribution extends Disposable implements IWorkbenchContribut } } -registerWorkbenchContribution2(WebviewPanelContribution.ID, WebviewPanelContribution, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(WebviewPanelContribution.ID, WebviewPanelContribution, WorkbenchPhase.BlockStartup); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer( WebviewEditorInputSerializer.ID, diff --git a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.ts b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.ts index 65bf37a893b..81029fdd75a 100644 --- a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.ts +++ b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution.ts @@ -13,7 +13,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { MenuRegistry, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; -import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IEditorPaneRegistry, EditorPaneDescriptor } from 'vs/workbench/browser/editor'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -29,7 +29,7 @@ registerAction2(EditorWalkThroughAction); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(EditorWalkThroughInputSerializer.ID, EditorWalkThroughInputSerializer); -registerWorkbenchContribution2(WalkThroughSnippetContentProvider.ID, WalkThroughSnippetContentProvider, WorkbenchContributionInstantiation.Lazy); +registerWorkbenchContribution2(WalkThroughSnippetContentProvider.ID, WalkThroughSnippetContentProvider, { editorTypeId: WalkThroughPart.ID }); KeybindingsRegistry.registerCommandAndKeybindingRule(WalkThroughArrowUp); diff --git a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughInput.ts b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughInput.ts index f217828b6ec..1d6d5c184fe 100644 --- a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughInput.ts +++ b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughInput.ts @@ -10,11 +10,11 @@ import { DisposableStore, IReference } from 'vs/base/common/lifecycle'; import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { marked } from 'vs/base/common/marked/marked'; import { isEqual } from 'vs/base/common/resources'; -import { WalkThroughSnippetContentProvider, requireToContent } from 'vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider'; +import { requireToContent } from 'vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider'; import { Dimension } from 'vs/base/browser/dom'; import { EditorInputCapabilities, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { getWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { Schemas } from 'vs/base/common/network'; class WalkThroughModel extends EditorModel { @@ -118,7 +118,7 @@ export class WalkThroughInput extends EditorInput { const renderer = new marked.Renderer(); renderer.code = (code, lang) => { i++; - const resource = this.options.resource.with({ scheme: getWorkbenchContribution(WalkThroughSnippetContentProvider.ID).scheme, fragment: `${i}.${lang}` }); + const resource = this.options.resource.with({ scheme: Schemas.walkThroughSnippet, fragment: `${i}.${lang}` }); snippets.push(this.textModelResolverService.createModelReference(resource)); return `
`; }; diff --git a/src/vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider.ts b/src/vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider.ts index 745e7cfd4f0..24764f1e31a 100644 --- a/src/vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider.ts +++ b/src/vs/workbench/contrib/welcomeWalkthrough/common/walkThroughContentProvider.ts @@ -45,15 +45,13 @@ export class WalkThroughSnippetContentProvider implements ITextModelContentProvi private loads = new Map>(); - readonly scheme = Schemas.walkThroughSnippet; - constructor( @ITextModelService private readonly textModelResolverService: ITextModelService, @ILanguageService private readonly languageService: ILanguageService, @IModelService private readonly modelService: IModelService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { - this.textModelResolverService.registerTextModelContentProvider(this.scheme, this); + this.textModelResolverService.registerTextModelContentProvider(Schemas.walkThroughSnippet, this); } private async textBufferFactoryFromResource(resource: URI): Promise { diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index 74f18663d3a..9bad5ae61a3 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -14,7 +14,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { Severity } from 'vs/platform/notification/common/notification'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService, IWorkspaceTrustRequestService, workspaceTrustToString, WorkspaceTrustUriResponse } from 'vs/platform/workspace/common/workspaceTrust'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Codicon } from 'vs/base/common/codicons'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -636,7 +636,7 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon //#endregion } -registerWorkbenchContribution2(WorkspaceTrustRequestHandler.ID, WorkspaceTrustRequestHandler, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(WorkspaceTrustRequestHandler.ID, WorkspaceTrustRequestHandler, WorkbenchPhase.BlockRestore); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceTrustUXHandler, LifecyclePhase.Restored); diff --git a/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts b/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts index ddbe230324c..d9493a6453d 100644 --- a/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts +++ b/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts @@ -11,7 +11,7 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILogService } from 'vs/platform/log/common/log'; import { INativeHostService } from 'vs/platform/native/common/native'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IDialogsModel, IDialogViewItem } from 'vs/workbench/common/dialogs'; import { BrowserDialogHandler } from 'vs/workbench/browser/parts/dialogs/dialogHandler'; import { NativeDialogHandler } from 'vs/workbench/electron-sandbox/parts/dialogs/dialogHandler'; @@ -111,5 +111,5 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC registerWorkbenchContribution2( DialogHandlerContribution.ID, DialogHandlerContribution, - WorkbenchContributionInstantiation.BlockStartup // Block to allow for dialogs to show before restore finished + WorkbenchPhase.BlockStartup // Block to allow for dialogs to show before restore finished ); diff --git a/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts b/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts index 6fcab421496..77844dcfee0 100644 --- a/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts @@ -12,7 +12,7 @@ import { AccessibilityService } from 'vs/platform/accessibility/browser/accessib import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { INativeHostService } from 'vs/platform/native/common/native'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; @@ -88,5 +88,5 @@ class LinuxAccessibilityContribution implements IWorkbenchContribution { } if (isLinux) { - registerWorkbenchContribution2(LinuxAccessibilityContribution.ID, LinuxAccessibilityContribution, WorkbenchContributionInstantiation.BlockRestore); + registerWorkbenchContribution2(LinuxAccessibilityContribution.ID, LinuxAccessibilityContribution, WorkbenchPhase.BlockRestore); } diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 2d4136bdc0e..e5a67abac87 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -27,7 +27,7 @@ import { mark } from 'vs/base/common/performance'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchContributionInstantiation, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, WorkbenchPhase, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -1367,7 +1367,7 @@ class UpdateExperimentalSettingsDefaults extends Disposable implements IWorkbenc const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(RegisterConfigurationSchemasContribution, LifecyclePhase.Restored); workbenchContributionsRegistry.registerWorkbenchContribution(ResetConfigurationDefaultsOverridesCache, LifecyclePhase.Eventually); -registerWorkbenchContribution2(UpdateExperimentalSettingsDefaults.ID, UpdateExperimentalSettingsDefaults, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(UpdateExperimentalSettingsDefaults.ID, UpdateExperimentalSettingsDefaults, WorkbenchPhase.BlockRestore); const configurationRegistry = Registry.as(Extensions.Configuration); configurationRegistry.registerConfiguration({ diff --git a/src/vs/workbench/services/editor/browser/editorPaneService.ts b/src/vs/workbench/services/editor/browser/editorPaneService.ts new file mode 100644 index 00000000000..9e1342fce28 --- /dev/null +++ b/src/vs/workbench/services/editor/browser/editorPaneService.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IEditorPaneService } from 'vs/workbench/services/editor/common/editorPaneService'; +import { EditorPaneDescriptor } from 'vs/workbench/browser/editor'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class EditorPaneService implements IEditorPaneService { + + declare readonly _serviceBrand: undefined; + + readonly onWillInstantiateEditorPane = EditorPaneDescriptor.onWillInstantiateEditorPane; + + didInstantiateEditorPane(typeId: string): boolean { + return EditorPaneDescriptor.didInstantiateEditorPane(typeId); + } +} + +registerSingleton(IEditorPaneService, EditorPaneService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/editor/common/editorPaneService.ts b/src/vs/workbench/services/editor/common/editorPaneService.ts new file mode 100644 index 00000000000..28d3b0f1876 --- /dev/null +++ b/src/vs/workbench/services/editor/common/editorPaneService.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. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IWillInstantiateEditorPaneEvent } from 'vs/workbench/common/editor'; +import { Event } from 'vs/base/common/event'; + +export const IEditorPaneService = createDecorator('editorPaneService'); + +export interface IEditorPaneService { + + readonly _serviceBrand: undefined; + + /** + * Emitted when an editor pane is about to be instantiated. + */ + readonly onWillInstantiateEditorPane: Event; + + /** + * Returns whether a editor pane with the given type id has been instantiated. + */ + didInstantiateEditorPane(typeId: string): boolean; +} diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index 37bcce6f2e5..c0457be3192 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -28,6 +28,7 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { IEditorPaneService } from 'vs/workbench/services/editor/common/editorPaneService'; suite('EditorService', () => { @@ -70,115 +71,124 @@ suite('EditorService', () => { } test('openEditor() - basics', async () => { - const [, service] = await createEditorService(); + const [, service, accessor] = await createEditorService(); - await testOpenBasics(service); + await testOpenBasics(service, accessor.editorPaneService); }); test('openEditor() - basics (scoped)', async () => { - const [part, service] = await createEditorService(); + const [part, service, accessor] = await createEditorService(); const scoped = service.createScoped('main', disposables); await part.whenReady; - await testOpenBasics(scoped); + await testOpenBasics(scoped, accessor.editorPaneService); }); - async function testOpenBasics(service: IEditorService) { + async function testOpenBasics(editorService: IEditorService, editorPaneService: IEditorPaneService) { let input = createTestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); let otherInput = createTestFileEditorInput(URI.parse('my://resource2-basics'), TEST_EDITOR_INPUT_ID); let activeEditorChangeEventCounter = 0; - disposables.add(service.onDidActiveEditorChange(() => { + disposables.add(editorService.onDidActiveEditorChange(() => { activeEditorChangeEventCounter++; })); let visibleEditorChangeEventCounter = 0; - disposables.add(service.onDidVisibleEditorsChange(() => { + disposables.add(editorService.onDidVisibleEditorsChange(() => { visibleEditorChangeEventCounter++; })); let didCloseEditorListenerCounter = 0; - disposables.add(service.onDidCloseEditor(() => { + disposables.add(editorService.onDidCloseEditor(() => { didCloseEditorListenerCounter++; })); + let willInstantiateEditorPaneListenerCounter = 0; + disposables.add(editorPaneService.onWillInstantiateEditorPane(e => { + if (e.typeId === TEST_EDITOR_ID) { + willInstantiateEditorPaneListenerCounter++; + } + })); + // Open input - let editor = await service.openEditor(input, { pinned: true }); + let editor = await editorService.openEditor(input, { pinned: true }); assert.strictEqual(editor?.getId(), TEST_EDITOR_ID); - assert.strictEqual(editor, service.activeEditorPane); - assert.strictEqual(1, service.count); - assert.strictEqual(input, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].editor); - assert.strictEqual(input, service.getEditors(EditorsOrder.SEQUENTIAL)[0].editor); - assert.strictEqual(input, service.activeEditor); - assert.strictEqual(service.visibleEditorPanes.length, 1); - assert.strictEqual(service.visibleEditorPanes[0], editor); - assert.ok(!service.activeTextEditorControl); - assert.ok(!service.activeTextEditorLanguageId); - assert.strictEqual(service.visibleTextEditorControls.length, 0); - assert.strictEqual(service.isOpened(input), true); - assert.strictEqual(service.isOpened({ resource: input.resource, typeId: input.typeId, editorId: input.editorId }), true); - assert.strictEqual(service.isOpened({ resource: input.resource, typeId: input.typeId, editorId: 'unknownTypeId' }), false); - assert.strictEqual(service.isOpened({ resource: input.resource, typeId: 'unknownTypeId', editorId: input.editorId }), false); - assert.strictEqual(service.isOpened({ resource: input.resource, typeId: 'unknownTypeId', editorId: 'unknownTypeId' }), false); - assert.strictEqual(service.isVisible(input), true); - assert.strictEqual(service.isVisible(otherInput), false); + assert.strictEqual(editor, editorService.activeEditorPane); + assert.strictEqual(1, editorService.count); + assert.strictEqual(input, editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].editor); + assert.strictEqual(input, editorService.getEditors(EditorsOrder.SEQUENTIAL)[0].editor); + assert.strictEqual(input, editorService.activeEditor); + assert.strictEqual(editorService.visibleEditorPanes.length, 1); + assert.strictEqual(editorService.visibleEditorPanes[0], editor); + assert.ok(!editorService.activeTextEditorControl); + assert.ok(!editorService.activeTextEditorLanguageId); + assert.strictEqual(editorService.visibleTextEditorControls.length, 0); + assert.strictEqual(editorService.isOpened(input), true); + assert.strictEqual(editorService.isOpened({ resource: input.resource, typeId: input.typeId, editorId: input.editorId }), true); + assert.strictEqual(editorService.isOpened({ resource: input.resource, typeId: input.typeId, editorId: 'unknownTypeId' }), false); + assert.strictEqual(editorService.isOpened({ resource: input.resource, typeId: 'unknownTypeId', editorId: input.editorId }), false); + assert.strictEqual(editorService.isOpened({ resource: input.resource, typeId: 'unknownTypeId', editorId: 'unknownTypeId' }), false); + assert.strictEqual(editorService.isVisible(input), true); + assert.strictEqual(editorService.isVisible(otherInput), false); assert.strictEqual(activeEditorChangeEventCounter, 1); assert.strictEqual(visibleEditorChangeEventCounter, 1); + assert.ok(editorPaneService.didInstantiateEditorPane(TEST_EDITOR_ID)); + assert.strictEqual(willInstantiateEditorPaneListenerCounter, 1); // Close input await editor?.group?.closeEditor(input); - assert.strictEqual(0, service.count); - assert.strictEqual(0, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length); - assert.strictEqual(0, service.getEditors(EditorsOrder.SEQUENTIAL).length); + assert.strictEqual(0, editorService.count); + assert.strictEqual(0, editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length); + assert.strictEqual(0, editorService.getEditors(EditorsOrder.SEQUENTIAL).length); assert.strictEqual(didCloseEditorListenerCounter, 1); assert.strictEqual(activeEditorChangeEventCounter, 2); assert.strictEqual(visibleEditorChangeEventCounter, 2); assert.ok(input.gotDisposed); // Open again 2 inputs (disposed editors are ignored!) - await service.openEditor(input, { pinned: true }); - assert.strictEqual(0, service.count); + await editorService.openEditor(input, { pinned: true }); + assert.strictEqual(0, editorService.count); // Open again 2 inputs (recreate because disposed) input = createTestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); otherInput = createTestFileEditorInput(URI.parse('my://resource2-basics'), TEST_EDITOR_INPUT_ID); - await service.openEditor(input, { pinned: true }); - editor = await service.openEditor(otherInput, { pinned: true }); + await editorService.openEditor(input, { pinned: true }); + editor = await editorService.openEditor(otherInput, { pinned: true }); - assert.strictEqual(2, service.count); - assert.strictEqual(otherInput, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].editor); - assert.strictEqual(input, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[1].editor); - assert.strictEqual(input, service.getEditors(EditorsOrder.SEQUENTIAL)[0].editor); - assert.strictEqual(otherInput, service.getEditors(EditorsOrder.SEQUENTIAL)[1].editor); - assert.strictEqual(service.visibleEditorPanes.length, 1); - assert.strictEqual(service.isOpened(input), true); - assert.strictEqual(service.isOpened({ resource: input.resource, typeId: input.typeId, editorId: input.editorId }), true); - assert.strictEqual(service.isOpened(otherInput), true); - assert.strictEqual(service.isOpened({ resource: otherInput.resource, typeId: otherInput.typeId, editorId: otherInput.editorId }), true); + assert.strictEqual(2, editorService.count); + assert.strictEqual(otherInput, editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].editor); + assert.strictEqual(input, editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[1].editor); + assert.strictEqual(input, editorService.getEditors(EditorsOrder.SEQUENTIAL)[0].editor); + assert.strictEqual(otherInput, editorService.getEditors(EditorsOrder.SEQUENTIAL)[1].editor); + assert.strictEqual(editorService.visibleEditorPanes.length, 1); + assert.strictEqual(editorService.isOpened(input), true); + assert.strictEqual(editorService.isOpened({ resource: input.resource, typeId: input.typeId, editorId: input.editorId }), true); + assert.strictEqual(editorService.isOpened(otherInput), true); + assert.strictEqual(editorService.isOpened({ resource: otherInput.resource, typeId: otherInput.typeId, editorId: otherInput.editorId }), true); assert.strictEqual(activeEditorChangeEventCounter, 4); assert.strictEqual(visibleEditorChangeEventCounter, 4); const stickyInput = createTestFileEditorInput(URI.parse('my://resource3-basics'), TEST_EDITOR_INPUT_ID); - await service.openEditor(stickyInput, { sticky: true }); + await editorService.openEditor(stickyInput, { sticky: true }); - assert.strictEqual(3, service.count); + assert.strictEqual(3, editorService.count); - const allSequentialEditors = service.getEditors(EditorsOrder.SEQUENTIAL); + const allSequentialEditors = editorService.getEditors(EditorsOrder.SEQUENTIAL); assert.strictEqual(allSequentialEditors.length, 3); assert.strictEqual(stickyInput, allSequentialEditors[0].editor); assert.strictEqual(input, allSequentialEditors[1].editor); assert.strictEqual(otherInput, allSequentialEditors[2].editor); - const sequentialEditorsExcludingSticky = service.getEditors(EditorsOrder.SEQUENTIAL, { excludeSticky: true }); + const sequentialEditorsExcludingSticky = editorService.getEditors(EditorsOrder.SEQUENTIAL, { excludeSticky: true }); assert.strictEqual(sequentialEditorsExcludingSticky.length, 2); assert.strictEqual(input, sequentialEditorsExcludingSticky[0].editor); assert.strictEqual(otherInput, sequentialEditorsExcludingSticky[1].editor); - const mruEditorsExcludingSticky = service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, { excludeSticky: true }); + const mruEditorsExcludingSticky = editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, { excludeSticky: true }); assert.strictEqual(mruEditorsExcludingSticky.length, 2); assert.strictEqual(input, sequentialEditorsExcludingSticky[0].editor); assert.strictEqual(otherInput, sequentialEditorsExcludingSticky[1].editor); diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index 11e368bfaba..5201436eaf8 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -19,7 +19,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ActivationKind, IExtensionService, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; @@ -425,7 +425,7 @@ class ExtensionUrlBootstrapHandler implements IWorkbenchContribution, IURLHandle } } -registerWorkbenchContribution2(ExtensionUrlBootstrapHandler.ID, ExtensionUrlBootstrapHandler, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(ExtensionUrlBootstrapHandler.ID, ExtensionUrlBootstrapHandler, WorkbenchPhase.BlockRestore); class ManageAuthorizedExtensionURIsAction extends Action2 { diff --git a/src/vs/workbench/services/remote/browser/remoteAgentService.ts b/src/vs/workbench/services/remote/browser/remoteAgentService.ts index 343e989c599..ee12dd5b070 100644 --- a/src/vs/workbench/services/remote/browser/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/browser/remoteAgentService.ts @@ -13,7 +13,7 @@ import { ISignService } from 'vs/platform/sign/common/sign'; import { ILogService } from 'vs/platform/log/common/log'; import { Severity } from 'vs/platform/notification/common/notification'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; @@ -67,4 +67,4 @@ class RemoteConnectionFailureNotificationContribution implements IWorkbenchContr } -registerWorkbenchContribution2(RemoteConnectionFailureNotificationContribution.ID, RemoteConnectionFailureNotificationContribution, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(RemoteConnectionFailureNotificationContribution.ID, RemoteConnectionFailureNotificationContribution, WorkbenchPhase.BlockRestore); diff --git a/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts b/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts index e88f59ef2d0..fdc38631ea9 100644 --- a/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts @@ -12,7 +12,7 @@ import { ISignService } from 'vs/platform/sign/common/sign'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INativeHostService } from 'vs/platform/native/common/native'; import { URI } from 'vs/base/common/uri'; @@ -92,4 +92,4 @@ class RemoteConnectionFailureNotificationContribution implements IWorkbenchContr } -registerWorkbenchContribution2(RemoteConnectionFailureNotificationContribution.ID, RemoteConnectionFailureNotificationContribution, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(RemoteConnectionFailureNotificationContribution.ID, RemoteConnectionFailureNotificationContribution, WorkbenchPhase.BlockRestore); diff --git a/src/vs/workbench/services/textMate/browser/textMateTokenizationFeature.contribution.ts b/src/vs/workbench/services/textMate/browser/textMateTokenizationFeature.contribution.ts index 2920489e6e8..7003e08847c 100644 --- a/src/vs/workbench/services/textMate/browser/textMateTokenizationFeature.contribution.ts +++ b/src/vs/workbench/services/textMate/browser/textMateTokenizationFeature.contribution.ts @@ -6,7 +6,7 @@ import { registerSingleton, InstantiationType } from 'vs/platform/instantiation/common/extensions'; import { ITextMateTokenizationService } from 'vs/workbench/services/textMate/browser/textMateTokenizationFeature'; import { TextMateTokenizationFeature } from 'vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl'; -import { IWorkbenchContribution, WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; /** * Makes sure the ITextMateTokenizationService is instantiated @@ -22,4 +22,4 @@ class TextMateTokenizationInstantiator implements IWorkbenchContribution { registerSingleton(ITextMateTokenizationService, TextMateTokenizationFeature, InstantiationType.Eager); -registerWorkbenchContribution2(TextMateTokenizationInstantiator.ID, TextMateTokenizationInstantiator, WorkbenchContributionInstantiation.BlockRestore); +registerWorkbenchContribution2(TextMateTokenizationInstantiator.ID, TextMateTokenizationInstantiator, WorkbenchPhase.BlockRestore); diff --git a/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts index d2b8085e932..7854b8de8f1 100644 --- a/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts @@ -11,7 +11,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { joinPath } from 'vs/base/common/resources'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { BrowserWorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/browser/workingCopyBackupTracker'; export class BrowserWorkingCopyBackupService extends WorkingCopyBackupService { @@ -30,4 +30,4 @@ export class BrowserWorkingCopyBackupService extends WorkingCopyBackupService { registerSingleton(IWorkingCopyBackupService, BrowserWorkingCopyBackupService, InstantiationType.Eager); // Register Backup Tracker -registerWorkbenchContribution2(BrowserWorkingCopyBackupTracker.ID, BrowserWorkingCopyBackupTracker, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(BrowserWorkingCopyBackupTracker.ID, BrowserWorkingCopyBackupTracker, WorkbenchPhase.BlockStartup); diff --git a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts index 90ec57c0376..6a5d80f09fd 100644 --- a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts @@ -11,7 +11,7 @@ import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/com import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; -import { WorkbenchContributionInstantiation, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { NativeWorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker'; @@ -41,4 +41,4 @@ export class NativeWorkingCopyBackupService extends WorkingCopyBackupService { registerSingleton(IWorkingCopyBackupService, NativeWorkingCopyBackupService, InstantiationType.Eager); // Register Backup Tracker -registerWorkbenchContribution2(NativeWorkingCopyBackupTracker.ID, NativeWorkingCopyBackupTracker, WorkbenchContributionInstantiation.BlockStartup); +registerWorkbenchContribution2(NativeWorkingCopyBackupTracker.ID, NativeWorkingCopyBackupTracker, WorkbenchPhase.BlockStartup); diff --git a/src/vs/workbench/test/browser/contributions.test.ts b/src/vs/workbench/test/browser/contributions.test.ts index 1e97fb9d121..64129a9ea99 100644 --- a/src/vs/workbench/test/browser/contributions.test.ts +++ b/src/vs/workbench/test/browser/contributions.test.ts @@ -7,10 +7,17 @@ import * as assert from 'assert'; import { DeferredPromise } from 'vs/base/common/async'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isCI } from 'vs/base/common/platform'; +import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { WorkbenchContributionInstantiation, WorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { WorkbenchPhase, WorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { ITestInstantiationService, TestFileEditorInput, TestInMemoryFileSystemProvider, TestServiceAccessor, TestSingletonFileEditorInput, createEditorPart, registerTestEditor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; suite('Contributions', () => { const disposables = new DisposableStore(); @@ -21,15 +28,30 @@ suite('Contributions', () => { let bCreated: boolean; let bCreatedPromise: DeferredPromise; + const TEST_EDITOR_ID = 'MyTestEditorForContributions'; + const TEST_EDITOR_INPUT_ID = 'testEditorInputForContributions'; + + async function createEditorService(instantiationService: ITestInstantiationService = workbenchInstantiationService(undefined, disposables)): Promise<[EditorPart, EditorService]> { + const part = await createEditorPart(instantiationService, disposables); + instantiationService.stub(IEditorGroupsService, part); + + const editorService = disposables.add(instantiationService.createInstance(EditorService, undefined)); + instantiationService.stub(IEditorService, editorService); + + return [part, editorService]; + } + setup(() => { aCreated = false; aCreatedPromise = new DeferredPromise(); bCreated = false; bCreatedPromise = new DeferredPromise(); + + disposables.add(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput), new SyncDescriptor(TestSingletonFileEditorInput)], TEST_EDITOR_INPUT_ID)); }); - teardown(() => { + teardown(async () => { disposables.clear(); }); @@ -52,15 +74,15 @@ suite('Contributions', () => { } test('getWorkbenchContribution() - with lazy contributions', () => { - const registry = new WorkbenchContributionsRegistry(); + const registry = disposables.add(new WorkbenchContributionsRegistry()); assert.throws(() => registry.getWorkbenchContribution('a')); - registry.registerWorkbenchContribution2('a', TestContributionA, WorkbenchContributionInstantiation.Lazy); + registry.registerWorkbenchContribution2('a', TestContributionA, { lazy: true }); assert.throws(() => registry.getWorkbenchContribution('a')); - registry.registerWorkbenchContribution2('b', TestContributionB, WorkbenchContributionInstantiation.Lazy); - registry.registerWorkbenchContribution2('c', TestContributionError, WorkbenchContributionInstantiation.Lazy); + registry.registerWorkbenchContribution2('b', TestContributionB, { lazy: true }); + registry.registerWorkbenchContribution2('c', TestContributionError, { lazy: true }); const instantiationService = workbenchInstantiationService(undefined, disposables); registry.start(instantiationService); @@ -77,7 +99,7 @@ suite('Contributions', () => { }); test('getWorkbenchContribution() - with non-lazy contributions', async () => { - const registry = new WorkbenchContributionsRegistry(); + const registry = disposables.add(new WorkbenchContributionsRegistry()); const instantiationService = workbenchInstantiationService(undefined, disposables); const accessor = instantiationService.createInstance(TestServiceAccessor); @@ -86,7 +108,7 @@ suite('Contributions', () => { assert.throws(() => registry.getWorkbenchContribution('a')); - registry.registerWorkbenchContribution2('a', TestContributionA, WorkbenchContributionInstantiation.BlockRestore); + registry.registerWorkbenchContribution2('a', TestContributionA, WorkbenchPhase.BlockRestore); const instanceA = registry.getWorkbenchContribution('a'); assert.ok(instanceA instanceof TestContributionA); @@ -99,13 +121,13 @@ suite('Contributions', () => { }); test('lifecycle phase instantiation works when phase changes', async () => { - const registry = new WorkbenchContributionsRegistry(); + const registry = disposables.add(new WorkbenchContributionsRegistry()); const instantiationService = workbenchInstantiationService(undefined, disposables); const accessor = instantiationService.createInstance(TestServiceAccessor); registry.start(instantiationService); - registry.registerWorkbenchContribution2('a', TestContributionA, WorkbenchContributionInstantiation.BlockRestore); + registry.registerWorkbenchContribution2('a', TestContributionA, WorkbenchPhase.BlockRestore); assert.ok(!aCreated); accessor.lifecycleService.phase = LifecyclePhase.Ready; @@ -114,14 +136,14 @@ suite('Contributions', () => { }); test('lifecycle phase instantiation works when phase was already met', async () => { - const registry = new WorkbenchContributionsRegistry(); + const registry = disposables.add(new WorkbenchContributionsRegistry()); const instantiationService = workbenchInstantiationService(undefined, disposables); const accessor = instantiationService.createInstance(TestServiceAccessor); accessor.lifecycleService.usePhases = true; accessor.lifecycleService.phase = LifecyclePhase.Restored; - registry.registerWorkbenchContribution2('a', TestContributionA, WorkbenchContributionInstantiation.BlockRestore); + registry.registerWorkbenchContribution2('a', TestContributionA, WorkbenchPhase.BlockRestore); registry.start(instantiationService); await aCreatedPromise.p; @@ -129,15 +151,15 @@ suite('Contributions', () => { }); (isCI ? test.skip /* runWhenIdle seems flaky in CI on Windows */ : test)('lifecycle phase instantiation works for late phases', async () => { - const registry = new WorkbenchContributionsRegistry(); + const registry = disposables.add(new WorkbenchContributionsRegistry()); const instantiationService = workbenchInstantiationService(undefined, disposables); const accessor = instantiationService.createInstance(TestServiceAccessor); accessor.lifecycleService.usePhases = true; registry.start(instantiationService); - registry.registerWorkbenchContribution2('a', TestContributionA, WorkbenchContributionInstantiation.AfterRestored); - registry.registerWorkbenchContribution2('b', TestContributionB, WorkbenchContributionInstantiation.Eventually); + registry.registerWorkbenchContribution2('a', TestContributionA, WorkbenchPhase.AfterRestored); + registry.registerWorkbenchContribution2('b', TestContributionB, WorkbenchPhase.Eventually); assert.ok(!aCreated); assert.ok(!bCreated); @@ -152,5 +174,69 @@ suite('Contributions', () => { assert.ok(bCreated); }); + test('contribution on file system', async function () { + const registry = disposables.add(new WorkbenchContributionsRegistry()); + + const instantiationService = workbenchInstantiationService(undefined, disposables); + const accessor = instantiationService.createInstance(TestServiceAccessor); + disposables.add(accessor.fileService.registerProvider('testBefore', disposables.add(new TestInMemoryFileSystemProvider()))); + + registry.registerWorkbenchContribution2('a', TestContributionA, { scheme: 'testBefore' }); + registry.start(instantiationService); + + await aCreatedPromise.p; + assert.ok(aCreated); + + registry.registerWorkbenchContribution2('b', TestContributionB, { scheme: 'testAfter' }); + + accessor.fileService.activateProvider('testAfter'); + + await bCreatedPromise.p; + assert.ok(bCreated); + }); + + test('contribution on editor - editor exists before start', async function () { + const registry = disposables.add(new WorkbenchContributionsRegistry()); + + const instantiationService = workbenchInstantiationService(undefined, disposables); + + const [, editorService] = await createEditorService(instantiationService); + + const input = disposables.add(new TestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID)); + await editorService.openEditor(input, { pinned: true }); + + registry.registerWorkbenchContribution2('a', TestContributionA, { editorTypeId: TEST_EDITOR_ID }); + registry.start(instantiationService.createChild(new ServiceCollection([IEditorService, editorService]))); + + await aCreatedPromise.p; + assert.ok(aCreated); + + registry.registerWorkbenchContribution2('b', TestContributionB, { editorTypeId: TEST_EDITOR_ID }); + + const input2 = disposables.add(new TestFileEditorInput(URI.parse('my://resource-basics2'), TEST_EDITOR_INPUT_ID)); + await editorService.openEditor(input2, { pinned: true }, SIDE_GROUP); + + await bCreatedPromise.p; + assert.ok(bCreated); + }); + + test('contribution on editor - editor does not exist before start', async function () { + const registry = disposables.add(new WorkbenchContributionsRegistry()); + + const instantiationService = workbenchInstantiationService(undefined, disposables); + + const [, editorService] = await createEditorService(instantiationService); + + const input = disposables.add(new TestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID)); + + registry.registerWorkbenchContribution2('a', TestContributionA, { editorTypeId: TEST_EDITOR_ID }); + registry.start(instantiationService.createChild(new ServiceCollection([IEditorService, editorService]))); + + await editorService.openEditor(input, { pinned: true }); + + await aCreatedPromise.p; + assert.ok(aCreated); + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 8a7b4e3e9c1..dfdca368834 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -23,7 +23,7 @@ import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbe import { IWorkspaceContextService, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { ILifecycleService, ShutdownReason, StartupKind, LifecyclePhase, WillShutdownEvent, BeforeShutdownErrorEvent, InternalBeforeShutdownEvent, IWillShutdownEventJoiner } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { FileOperationEvent, IFileService, IFileStat, IFileStatResult, FileChangesEvent, IResolveFileOptions, ICreateFileOptions, IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, IFileDeleteOptions, IFileOverwriteOptions, IFileWriteOptions, IFileOpenOptions, IFileStatWithMetadata, IResolveMetadataFileOptions, IWriteFileOptions, IReadFileOptions, IFileContent, IFileStreamContent, FileOperationError, IFileSystemProviderWithFileReadStreamCapability, IFileReadStreamOptions, IReadFileStreamOptions, IFileSystemProviderCapabilitiesChangeEvent, IFileStatWithPartialMetadata, IFileSystemWatcher, IWatchOptionsWithCorrelation } from 'vs/platform/files/common/files'; +import { FileOperationEvent, IFileService, IFileStat, IFileStatResult, FileChangesEvent, IResolveFileOptions, ICreateFileOptions, IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, IFileDeleteOptions, IFileOverwriteOptions, IFileWriteOptions, IFileOpenOptions, IFileStatWithMetadata, IResolveMetadataFileOptions, IWriteFileOptions, IReadFileOptions, IFileContent, IFileStreamContent, FileOperationError, IFileSystemProviderWithFileReadStreamCapability, IFileReadStreamOptions, IReadFileStreamOptions, IFileSystemProviderCapabilitiesChangeEvent, IFileStatWithPartialMetadata, IFileSystemWatcher, IWatchOptionsWithCorrelation, IFileSystemProviderActivationEvent } from 'vs/platform/files/common/files'; import { IModelService } from 'vs/editor/common/services/model'; import { LanguageService } from 'vs/editor/common/services/languageService'; import { ModelService } from 'vs/editor/common/services/modelService'; @@ -171,6 +171,8 @@ import { mainWindow } from 'vs/base/browser/window'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IEditorPaneService } from 'vs/workbench/services/editor/common/editorPaneService'; +import { EditorPaneService } from 'vs/workbench/services/editor/browser/editorPaneService'; export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined, undefined); @@ -321,6 +323,7 @@ export function workbenchInstantiationService( instantiationService.stub(ILabelService, disposables.add(instantiationService.createInstance(LabelService))); const editorService = overrides?.editorService ? overrides.editorService(instantiationService) : disposables.add(new TestEditorService(editorGroupService)); instantiationService.stub(IEditorService, editorService); + instantiationService.stub(IEditorPaneService, new EditorPaneService()); instantiationService.stub(IWorkingCopyEditorService, disposables.add(instantiationService.createInstance(WorkingCopyEditorService))); instantiationService.stub(IEditorResolverService, disposables.add(instantiationService.createInstance(EditorResolverService))); const textEditorService = overrides?.textEditorService ? overrides.textEditorService(instantiationService) : disposables.add(instantiationService.createInstance(TextEditorService)); @@ -354,6 +357,7 @@ export class TestServiceAccessor { @IDialogService public dialogService: TestDialogService, @IWorkingCopyService public workingCopyService: TestWorkingCopyService, @IEditorService public editorService: TestEditorService, + @IEditorPaneService public editorPaneService: IEditorPaneService, @IWorkbenchEnvironmentService public environmentService: IWorkbenchEnvironmentService, @IPathService public pathService: IPathService, @IEditorGroupsService public editorGroupService: IEditorGroupsService, @@ -1074,7 +1078,8 @@ export class TestFileService implements IFileService { get onDidChangeFileSystemProviderCapabilities(): Event { return this._onDidChangeFileSystemProviderCapabilities.event; } fireFileSystemProviderCapabilitiesChangeEvent(event: IFileSystemProviderCapabilitiesChangeEvent): void { this._onDidChangeFileSystemProviderCapabilities.fire(event); } - readonly onWillActivateFileSystemProvider = Event.None; + private _onWillActivateFileSystemProvider = new Emitter(); + readonly onWillActivateFileSystemProvider = this._onWillActivateFileSystemProvider.event; readonly onDidWatchError = Event.None; private content = 'Hello Html'; @@ -1166,7 +1171,9 @@ export class TestFileService implements IFileService { return this.providers.get(scheme); } - async activateProvider(_scheme: string): Promise { return; } + async activateProvider(_scheme: string): Promise { + this._onWillActivateFileSystemProvider.fire({ scheme: _scheme, join: () => { } }); + } async canHandleResource(resource: URI): Promise { return this.hasProvider(resource); } hasProvider(resource: URI): boolean { return resource.scheme === Schemas.file || this.providers.has(resource.scheme); } listCapabilities() { diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 09cd28cf86a..98c8d09ad6d 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -114,6 +114,7 @@ import 'vs/workbench/services/textMate/browser/textMateTokenizationFeature.contr import 'vs/workbench/services/userActivity/common/userActivityService'; import 'vs/workbench/services/userActivity/browser/userActivityBrowser'; import 'vs/workbench/services/issue/browser/issueTroubleshoot'; +import 'vs/workbench/services/editor/browser/editorPaneService'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; From 981343c94fe0c4098a864a97983ffbc4ce62366d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 5 Feb 2024 09:52:28 +0100 Subject: [PATCH 0904/1897] perf editor - :lipstick: workbench contrib display (#204335) --- .../workbench/contrib/performance/browser/perfviewEditor.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts index fd6d7ed9da4..259ce2da160 100644 --- a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts @@ -192,6 +192,8 @@ class PerfModelContentProvider implements ITextModelContentProvider { private _addSummaryTable(md: MarkdownBuilder, stats?: LoaderStats): void { const metrics = this._timerService.startupMetrics; + const contribTimings = Registry.as(WorkbenchExtensions.Workbench).timings; + const table: Array> = []; table.push(['start => app.isReady', metrics.timers.ellapsedAppReady, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['nls:start => nls:end', metrics.timers.ellapsedNlsGeneration, '[main]', `initial startup: ${metrics.initialStartup}`]); @@ -213,7 +215,7 @@ class PerfModelContentProvider implements ITextModelContentProvider { table.push(['restore viewlet', metrics.timers.ellapsedViewletRestore, '[renderer]', metrics.viewletId]); table.push(['restore panel', metrics.timers.ellapsedPanelRestore, '[renderer]', metrics.panelId]); table.push(['restore & resolve visible editors', metrics.timers.ellapsedEditorRestore, '[renderer]', `${metrics.editorIds.length}: ${metrics.editorIds.join(', ')}`]); - table.push(['create workbench contributions', metrics.timers.ellapsedWorkbenchContributions, '[renderer]']); + table.push(['create workbench contributions', metrics.timers.ellapsedWorkbenchContributions, '[renderer]', `${(contribTimings.get(LifecyclePhase.Starting)?.length ?? 0) + (contribTimings.get(LifecyclePhase.Starting)?.length ?? 0)} blocking startup`]); table.push(['overall workbench load', metrics.timers.ellapsedWorkbench, '[renderer]', undefined]); table.push(['workbench ready', metrics.ellapsed, '[main->renderer]', undefined]); table.push(['renderer ready', metrics.timers.ellapsedRenderer, '[renderer]', undefined]); From 6c224510f55da03137dc4baea3b3d16802bdc5c1 Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 5 Feb 2024 12:33:35 +0100 Subject: [PATCH 0905/1897] Add `ChatAgentResponseStream` as a more explict alternative to `Progess` --- .../api/common/extHostChatAgents2.ts | 167 ++++++++++++++---- .../api/common/extHostTypeConverters.ts | 8 + .../vscode.proposed.chatAgents2.d.ts | 36 +++- .../vscode.proposed.chatAgents2Additions.d.ts | 4 +- 4 files changed, 176 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 8079affd1db..93142c7028b 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -8,22 +8,143 @@ import { DeferredPromise, raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; -import { Progress } from 'vs/platform/progress/common/progress'; import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { IChatAgentCommand, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatFollowup, IChatReplyFollowup, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatProgress, IChatReplyFollowup, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import type * as vscode from 'vscode'; +class ChatAgentResponseStream { + + private _stopWatch = StopWatch.create(false); + private _isClosed: boolean = false; + private _firstProgress: number | undefined; + private _apiObject: vscode.ChatAgentExtendedResponseStream | undefined; + + constructor( + private readonly _extension: IExtensionDescription, + private readonly _request: IChatAgentRequest, + private readonly _proxy: MainThreadChatAgentsShape2, + @ILogService private readonly _logService: ILogService, + ) { } + + close() { + this._isClosed = true; + } + + get timings() { + return { + firstProgress: this._firstProgress, + totalElapsed: this._stopWatch.elapsed() + }; + } + + get apiObject() { + + if (!this._apiObject) { + + const that = this; + this._stopWatch.reset(); + + function throwIfDone(source: Function | undefined) { + if (that._isClosed) { + const err = new Error('Response stream has been closed'); + Error.captureStackTrace(err, source); + throw err; + } + } + + const _report = (progress: Dto) => { + // Measure the time to the first progress update with real markdown content + if (typeof this._firstProgress === 'undefined' && 'content' in progress) { + this._firstProgress = this._stopWatch.elapsed(); + } + this._proxy.$handleProgressChunk(this._request.requestId, progress); + }; + + this._apiObject = { + markdown(value) { + throwIfDone(this.markdown); + _report({ + kind: 'markdownContent', + content: typeConvert.MarkdownString.from(value) + }); + return this; + }, + text(value) { + throwIfDone(this.text); + this.markdown(new MarkdownString().appendText(value)); + return this; + }, + files(value) { + throwIfDone(this.files); + _report({ + kind: 'treeData', + treeData: value + }); + return this; + }, + anchor(value) { + throwIfDone(this.anchor); + _report({ + kind: 'inlineReference', + inlineReference: !URI.isUri(value) ? typeConvert.Location.from(value) : value + }); + return this; + }, + progress(value) { + throwIfDone(this.progress); + _report({ + kind: 'progressMessage', + content: new MarkdownString(value) + }); + return this; + }, + reference(value) { + throwIfDone(this.reference); + _report({ + kind: 'reference', + reference: !URI.isUri(value) ? typeConvert.Location.from(value) : value + }); + return this; + }, + // annotation(value) { + // _report(value); + // return this; + // }, + report(progress) { + throwIfDone(this.report); + if ('placeholder' in progress && 'resolvedContent' in progress) { + // Ignore for now, this is the deleted Task type + return; + } + + const value = typeConvert.ChatResponseProgress.from(that._extension, progress); + if (!value) { + that._logService.error('Unknown progress type: ' + JSON.stringify(progress)); + return; + } + + _report(value); + return this; + } + }; + } + + return this._apiObject; + } +} + export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { private static _idPool = 0; @@ -61,44 +182,17 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); } - let done = false; - function throwIfDone() { - if (done) { - throw new Error('Only valid while executing the command'); - } - } - const commandExecution = new DeferredPromise(); token.onCancellationRequested(() => commandExecution.complete()); this._extHostChatProvider.allowListExtensionWhile(agent.extension.identifier, commandExecution.p); - const stopWatch = StopWatch.create(false); - let firstProgress: number | undefined; + const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService); try { const convertedHistory = await this.prepareHistory(agent, request, context); const task = agent.invoke( typeConvert.ChatAgentRequest.to(request), { history: convertedHistory }, - new Progress(progress => { - throwIfDone(); - - // Measure the time to the first progress update with real markdown content - if (typeof firstProgress === 'undefined' && 'content' in progress) { - firstProgress = stopWatch.elapsed(); - } - - const convertedProgress = typeConvert.ChatResponseProgress.from(agent.extension, progress); - if (!convertedProgress) { - this._logService.error('Unknown progress type: ' + JSON.stringify(progress)); - return; - } - - if ('placeholder' in progress && 'resolvedContent' in progress) { - // Ignore for now, this is the deleted Task type - } else { - this._proxy.$handleProgressChunk(request.requestId, convertedProgress); - } - }), + stream.apiObject, token ); @@ -112,8 +206,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } sessionResults.set(request.requestId, result); - const timings = { firstProgress: firstProgress, totalElapsed: stopWatch.elapsed() }; - return { errorDetails: result.errorDetails, timings }; + return { errorDetails: result.errorDetails, timings: stream.timings }; } else { this._previousResultMap.delete(request.sessionId); } @@ -126,7 +219,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { return { errorDetails: { message: localize('errorResponse', "Error from provider: {0}", toErrorMessage(e)), responseIsIncomplete: true } }; } finally { - done = true; + stream.close(); commandExecution.complete(); } } @@ -514,7 +607,7 @@ class ExtHostChatAgent { } satisfies vscode.ChatAgent2; } - invoke(request: vscode.ChatAgentRequest, context: vscode.ChatAgentContext, progress: Progress, token: CancellationToken): vscode.ProviderResult { - return this._callback(request, context, progress, token); + invoke(request: vscode.ChatAgentRequest, context: vscode.ChatAgentContext, response: vscode.ChatAgentExtendedResponseStream, token: CancellationToken): vscode.ProviderResult { + return this._callback(request, context, response, token); } } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 89adbab9dcd..380ac7a61f3 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -123,6 +123,14 @@ export namespace Range { } export namespace Location { + + export function from(location: vscode.Location): Dto { + return { + uri: location.uri, + range: Range.from(location.range) + }; + } + export function to(location: Dto): vscode.Location { return new types.Location(URI.revive(location.uri), Range.to(location.range)); } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index d23923fea04..17408691467 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -274,16 +274,50 @@ declare module 'vscode' { variables: Record; } + export interface ChatAgentResponseStream { + + // RENDERED + markdown(value: string | MarkdownString): ChatAgentResponseStream; + text(value: string): ChatAgentResponseStream; + files(value: ChatAgentFileTreeData): ChatAgentResponseStream; + // TODO@jrieken is this sugar for markdown syntax? should we have more like codeblock, bulletlist etc? + anchor(value: Uri | Location, attributes?: { title?: string }): ChatAgentResponseStream; + + // META + // TODO@API this influences the rendering, it inserts new lines which is likely a bug + progress(value: string): ChatAgentResponseStream; + + // TODO@API support non-file uris, like http://example.com + reference(value: Uri | Location): ChatAgentResponseStream; + + // TODO@API define support annotations + // annotation(value: string | MarkdownString | ChatXYZAnnotation, references?: string): ChatAgentResponseStream; + + /** + * @deprecated use above methods instread + */ + report(value: ChatAgentProgress): void; + } + + /** + * @deprecated use ChatAgentResponseStream instead + */ export type ChatAgentContentProgress = | ChatAgentContent | ChatAgentFileTree | ChatAgentInlineContentReference; + /** + * @deprecated use ChatAgentResponseStream instead + */ export type ChatAgentMetadataProgress = | ChatAgentUsedContext | ChatAgentContentReference | ChatAgentProgressMessage; + /** + * @deprecated use ChatAgentResponseStream instead + */ export type ChatAgentProgress = ChatAgentContentProgress | ChatAgentMetadataProgress; /** @@ -380,7 +414,7 @@ declare module 'vscode' { documents: ChatAgentDocumentContext[]; } - export type ChatAgentHandler = (request: ChatAgentRequest, context: ChatAgentContext, progress: Progress, token: CancellationToken) => ProviderResult; + export type ChatAgentHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentResponseStream, token: CancellationToken) => ProviderResult; export namespace chat { diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index 0f9de5a05bd..c56dfc45c89 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -43,6 +43,8 @@ declare module 'vscode' { | ChatAgentMarkdownContent | ChatAgentDetectedAgent; + export type ChatAgentExtendedResponseStream = ChatAgentResponseStream & Progress; + export interface ChatAgent2 { /** * Provide a set of variables that can only be used with this agent. @@ -64,7 +66,7 @@ declare module 'vscode' { constructor(label: string | CompletionItemLabel, values: ChatVariableValue[]); } - export type ChatAgentExtendedHandler = (request: ChatAgentRequest, context: ChatAgentContext, progress: Progress, token: CancellationToken) => ProviderResult; + export type ChatAgentExtendedHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentExtendedResponseStream, token: CancellationToken) => ProviderResult; export namespace chat { /** From f837742345df295cada781f5eb322e64022dbe14 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 5 Feb 2024 12:36:38 +0100 Subject: [PATCH 0906/1897] Root folders drag and drop (#204153) --- .../files/browser/views/explorerViewer.ts | 75 +++++++++++++++---- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 907fc93ce83..f4d93f91e8d 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -1103,7 +1103,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { const iconLabelName = getIconLabelNameFromHTMLElement(originalEvent.target); if (iconLabelName && iconLabelName.index < iconLabelName.count - 1) { - const result = this.handleDragOver(data, compressedTarget, targetIndex, originalEvent); + const result = this.handleDragOver(data, compressedTarget, targetIndex, targetSector, originalEvent); if (result) { if (iconLabelName.element !== this.compressedDragOverElement) { @@ -1127,10 +1127,10 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } this.compressedDropTargetDisposable.dispose(); - return this.handleDragOver(data, target, targetIndex, originalEvent); + return this.handleDragOver(data, target, targetIndex, targetSector, originalEvent); } - private handleDragOver(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { + private handleDragOver(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { const isCopy = originalEvent && ((originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh)); const isNative = data instanceof NativeDragAndDropData; const effectType = (isNative || isCopy) ? ListDragOverEffectType.Copy : ListDragOverEffectType.Move; @@ -1151,6 +1151,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // In-Explorer DND else { const items = FileDragAndDrop.getStatsFromDragAndDropData(data as ElementsDragAndDropData); + const isRootsReorder = items.every(item => item.isRoot); if (!target) { // Dropping onto the empty area. Do not accept if items dragged are already @@ -1159,6 +1160,11 @@ export class FileDragAndDrop implements ITreeDragAndDrop { return false; } + // root is added after last root folder when hovering on empty background + if (isRootsReorder) { + return { accept: true, effect: { type: ListDragOverEffectType.Move, position: ListDragOverEffectPosition.After } }; + } + return { accept: true, bubble: TreeDragOverBubble.Down, effect, autoExpand: false }; } @@ -1171,17 +1177,12 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } if (items.some((source) => { - if (source.isRoot && target instanceof ExplorerItem && !target.isRoot) { - return true; // Root folder can not be moved to a non root file stat. + if (source.isRoot) { + return false; // Root folders are handled seperately } if (this.uriIdentityService.extUri.isEqual(source.resource, target.resource)) { - return true; // Can not move anything onto itself - } - - if (source.isRoot && target instanceof ExplorerItem && target.isRoot) { - // Disable moving workspace roots in one another - return false; + return true; // Can not move anything onto itself excpet for root folders } if (!isCopy && this.uriIdentityService.extUri.isEqual(dirname(source.resource), target.resource)) { @@ -1196,6 +1197,24 @@ export class FileDragAndDrop implements ITreeDragAndDrop { })) { return false; } + + // reordering roots + if (isRootsReorder) { + if (!target.isRoot) { + return false; + } + + let dropEffectPosition: ListDragOverEffectPosition | undefined = undefined; + switch (targetSector) { + case ListViewTargetSector.TOP: + case ListViewTargetSector.CENTER_TOP: + dropEffectPosition = ListDragOverEffectPosition.Before; break; + case ListViewTargetSector.CENTER_BOTTOM: + case ListViewTargetSector.BOTTOM: + dropEffectPosition = ListDragOverEffectPosition.After; break; + } + return { accept: true, effect: { type: ListDragOverEffectType.Move, position: dropEffectPosition } }; + } } // All (target = model) @@ -1268,6 +1287,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Find parent to add to if (!target) { target = this.explorerService.roots[this.explorerService.roots.length - 1]; + targetSector = ListViewTargetSector.BOTTOM; } if (!target.isDirectory && target.parent) { target = target.parent; @@ -1298,14 +1318,14 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // In-Explorer DND (Move/Copy file) else { - await this.handleExplorerDrop(data as ElementsDragAndDropData, resolvedTarget, originalEvent); + await this.handleExplorerDrop(data as ElementsDragAndDropData, resolvedTarget, targetIndex, targetSector, originalEvent); } } catch (error) { this.dialogService.error(toErrorMessage(error)); } } - private async handleExplorerDrop(data: ElementsDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise { + private async handleExplorerDrop(data: ElementsDragAndDropData, target: ExplorerItem, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): Promise { const elementsData = FileDragAndDrop.getStatsFromDragAndDropData(data); const distinctItems = new Map(elementsData.map(element => [element, this.isCollapsed(element)])); @@ -1353,7 +1373,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } } - await this.doHandleRootDrop(items.filter(s => s.isRoot), target); + await this.doHandleRootDrop(items.filter(s => s.isRoot), target, targetSector); const sources = items.filter(s => !s.isRoot); if (isCopy) { @@ -1363,13 +1383,14 @@ export class FileDragAndDrop implements ITreeDragAndDrop { return this.doHandleExplorerDropOnMove(sources, target); } - private async doHandleRootDrop(roots: ExplorerItem[], target: ExplorerItem): Promise { + private async doHandleRootDrop(roots: ExplorerItem[], target: ExplorerItem, targetSector: ListViewTargetSector | undefined): Promise { if (roots.length === 0) { return; } const folders = this.contextService.getWorkspace().folders; let targetIndex: number | undefined; + const sourceIndices: number[] = []; const workspaceCreationData: IWorkspaceFolderCreationData[] = []; const rootsToMove: IWorkspaceFolderCreationData[] = []; @@ -1378,10 +1399,20 @@ export class FileDragAndDrop implements ITreeDragAndDrop { uri: folders[index].uri, name: folders[index].name }; + + // Is current target if (target instanceof ExplorerItem && this.uriIdentityService.extUri.isEqual(folders[index].uri, target.resource)) { targetIndex = index; } + // Is current source + for (const root of roots) { + if (this.uriIdentityService.extUri.isEqual(folders[index].uri, root.resource)) { + sourceIndices.push(index); + break; + } + } + if (roots.every(r => r.resource.toString() !== folders[index].uri.toString())) { workspaceCreationData.push(data); } else { @@ -1390,6 +1421,20 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } if (targetIndex === undefined) { targetIndex = workspaceCreationData.length; + } else { + switch (targetSector) { + case ListViewTargetSector.BOTTOM: + case ListViewTargetSector.CENTER_BOTTOM: + targetIndex++; + break; + } + // Adjust target index if source was located before target. + // The move will cause the index to change + for (const sourceIndex of sourceIndices) { + if (sourceIndex < targetIndex) { + targetIndex--; + } + } } workspaceCreationData.splice(targetIndex, 0, ...rootsToMove); From 8d6318a6227ce88b43cdaa5bb55740cb181b1af4 Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 5 Feb 2024 15:23:17 +0100 Subject: [PATCH 0907/1897] add `ChatAgentResponseItemMetadata` to stream builder methods, gives place for future annotations --- .../api/common/extHostChatAgents2.ts | 15 +++++------- .../vscode.proposed.chatAgents2.d.ts | 23 ++++++++++++------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 93142c7028b..e977d16fe91 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -73,7 +73,7 @@ class ChatAgentResponseStream { }; this._apiObject = { - markdown(value) { + markdown(value, meta) { throwIfDone(this.markdown); _report({ kind: 'markdownContent', @@ -81,12 +81,12 @@ class ChatAgentResponseStream { }); return this; }, - text(value) { + text(value, meta) { throwIfDone(this.text); - this.markdown(new MarkdownString().appendText(value)); + this.markdown(new MarkdownString().appendText(value), meta); return this; }, - files(value) { + files(value, meta) { throwIfDone(this.files); _report({ kind: 'treeData', @@ -94,10 +94,11 @@ class ChatAgentResponseStream { }); return this; }, - anchor(value) { + anchor(value, meta) { throwIfDone(this.anchor); _report({ kind: 'inlineReference', + name: meta?.title, inlineReference: !URI.isUri(value) ? typeConvert.Location.from(value) : value }); return this; @@ -118,10 +119,6 @@ class ChatAgentResponseStream { }); return this; }, - // annotation(value) { - // _report(value); - // return this; - // }, report(progress) { throwIfDone(this.report); if ('placeholder' in progress && 'resolvedContent' in progress) { diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 17408691467..c784e32dd3e 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -274,25 +274,32 @@ declare module 'vscode' { variables: Record; } + export interface ChatAgentResponseItemMetadata { + title: string; + // annotations: any[]; // future OffsetbasedAnnotation and Annotation + } + export interface ChatAgentResponseStream { // RENDERED - markdown(value: string | MarkdownString): ChatAgentResponseStream; - text(value: string): ChatAgentResponseStream; - files(value: ChatAgentFileTreeData): ChatAgentResponseStream; - // TODO@jrieken is this sugar for markdown syntax? should we have more like codeblock, bulletlist etc? - anchor(value: Uri | Location, attributes?: { title?: string }): ChatAgentResponseStream; + + text(value: string, meta?: ChatAgentResponseItemMetadata): ChatAgentResponseStream; + + markdown(value: string | MarkdownString, meta?: ChatAgentResponseItemMetadata): ChatAgentResponseStream; + + files(value: ChatAgentFileTreeData, meta?: ChatAgentResponseItemMetadata): ChatAgentResponseStream; + + anchor(value: Uri | Location, meta?: ChatAgentResponseItemMetadata): ChatAgentResponseStream; // META + // TODO@API this influences the rendering, it inserts new lines which is likely a bug progress(value: string): ChatAgentResponseStream; // TODO@API support non-file uris, like http://example.com + // TODO@API support mapped edits reference(value: Uri | Location): ChatAgentResponseStream; - // TODO@API define support annotations - // annotation(value: string | MarkdownString | ChatXYZAnnotation, references?: string): ChatAgentResponseStream; - /** * @deprecated use above methods instread */ From 125c21d0b4b437925f615f417e002aad3ad8287e Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 5 Feb 2024 15:24:15 +0100 Subject: [PATCH 0908/1897] Update log grammar (#204360) --- extensions/log/cgmanifest.json | 4 ++-- extensions/log/syntaxes/log.tmLanguage.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/log/cgmanifest.json b/extensions/log/cgmanifest.json index 7d7c9466d70..3f480ad6f64 100644 --- a/extensions/log/cgmanifest.json +++ b/extensions/log/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "vscode-logfile-highlighter", "repositoryUrl": "https://github.com/emilast/vscode-logfile-highlighter", - "commitHash": "8acba9307254d1887ac770057767698c82d926c6" + "commitHash": "eb50e785c27b4b4f7dbf6c0e801c58fe91baef5d" } }, "license": "MIT", - "version": "2.15.0" + "version": "2.17.0" } ], "version": 1 diff --git a/extensions/log/syntaxes/log.tmLanguage.json b/extensions/log/syntaxes/log.tmLanguage.json index 2bda9c9a1f2..f3a8fa9f655 100644 --- a/extensions/log/syntaxes/log.tmLanguage.json +++ b/extensions/log/syntaxes/log.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/emilast/vscode-logfile-highlighter/commit/8acba9307254d1887ac770057767698c82d926c6", + "version": "https://github.com/emilast/vscode-logfile-highlighter/commit/eb50e785c27b4b4f7dbf6c0e801c58fe91baef5d", "name": "Log file", "scopeName": "text.log", "patterns": [ @@ -113,7 +113,7 @@ "name": "string.regexp, emphasis log.exceptiontype" }, { - "begin": "^[\\t ]*at", + "begin": "^[\\t ]*at[\\t ]", "end": "$", "name": "string.key, emphasis log.exception" }, From 212e24f4dbddca9d08ec35a1d2a6ce8005c8ddab Mon Sep 17 00:00:00 2001 From: RedCMD <33529441+RedCMD@users.noreply.github.com> Date: Tue, 6 Feb 2024 03:56:16 +1300 Subject: [PATCH 0909/1897] Enable json language support for `code-snippets` files (#204090) * Enable json language support for `code-snippets` files * fix * snippets as a known language to the json language server --------- Co-authored-by: Martin Aeschlimann --- .../json-language-features/client/src/languageParticipants.ts | 2 ++ extensions/json-language-features/package.json | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions/json-language-features/client/src/languageParticipants.ts b/extensions/json-language-features/client/src/languageParticipants.ts index 6bd1a086e0a..7748d42589b 100644 --- a/extensions/json-language-features/client/src/languageParticipants.ts +++ b/extensions/json-language-features/client/src/languageParticipants.ts @@ -40,8 +40,10 @@ export function getLanguageParticipants(): LanguageParticipants { languages = new Set(); languages.add('json'); languages.add('jsonc'); + languages.add('snippets'); comments = new Set(); comments.add('jsonc'); + comments.add('snippets'); for (const extension of extensions.allAcrossExtensionHosts) { const jsonLanguageParticipants = extension.packageJSON?.contributes?.jsonLanguageParticipants as LanguageParticipantContribution[]; diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 33d2bbb09bd..541106ae1a0 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -15,7 +15,8 @@ "icon": "icons/json.png", "activationEvents": [ "onLanguage:json", - "onLanguage:jsonc" + "onLanguage:jsonc", + "onLanguage:snippets" ], "main": "./client/out/node/jsonClientMain", "browser": "./client/dist/browser/jsonClientMain", From 35aa2b1409f890517f2bc5520bcca6467c122747 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 5 Feb 2024 16:21:59 +0100 Subject: [PATCH 0910/1897] perf - work through workbench contributions (#203947) (#204350) --- .../workbench/browser/parts/editor/editor.ts | 4 +- src/vs/workbench/common/contributions.ts | 47 +------------------ .../browser/localHistory.contribution.ts | 2 +- .../browser/mergeEditor.contribution.ts | 2 +- .../browser/multiDiffEditor.contribution.ts | 4 +- .../search/browser/replaceContributions.ts | 2 +- .../contrib/url/browser/url.contribution.ts | 4 +- .../browser/webviewPanel.contribution.ts | 24 ++++------ .../services/editor/browser/editorService.ts | 9 +++- .../editor/common/editorGroupsService.ts | 8 +--- .../services/editor/common/editorService.ts | 7 ++- .../editor/test/browser/editorService.test.ts | 7 +++ .../extensions/browser/extensionUrlHandler.ts | 2 +- .../test/browser/contributions.test.ts | 23 +-------- .../test/browser/workbenchTestServices.ts | 1 + 15 files changed, 45 insertions(+), 101 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 8accd2f29dc..b118e9f193b 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { GroupIdentifier, IWorkbenchEditorConfiguration, IEditorIdentifier, IEditorCloseEvent, IEditorPartOptions, IEditorPartOptionsChangeEvent, SideBySideEditor, EditorCloseContext, IEditorPane, IEditorPartLimitOptions, IEditorPartDecorationOptions } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IWorkbenchEditorConfiguration, IEditorIdentifier, IEditorCloseEvent, IEditorPartOptions, IEditorPartOptionsChangeEvent, SideBySideEditor, EditorCloseContext, IEditorPane, IEditorPartLimitOptions, IEditorPartDecorationOptions, IEditorWillOpenEvent } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IEditorGroup, GroupDirection, IMergeGroupOptions, GroupsOrder, GroupsArrangement, IAuxiliaryEditorPart, IEditorPart } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -244,7 +244,9 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito readonly onDidFocus: Event; + readonly onWillOpenEditor: Event; readonly onDidOpenEditorFail: Event; + readonly onDidCloseEditor: Event; readonly groupsView: IEditorGroupsView; diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index 49801022a18..be5d2a78808 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -10,7 +10,6 @@ import { IdleDeadline, DeferredPromise, runWhenGlobalIdle } from 'vs/base/common import { mark } from 'vs/base/common/performance'; import { ILogService } from 'vs/platform/log/common/log'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IFileService } from 'vs/platform/files/common/files'; import { getOrSet } from 'vs/base/common/map'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEditorPaneService } from 'vs/workbench/services/editor/common/editorPaneService'; @@ -67,14 +66,6 @@ export interface ILazyWorkbenchContributionInstantiation { readonly lazy: true; } -/** - * A workbench contribution that will be instantiated when the - * corresponding file system scheme is used. - */ -export interface IOnFilesystemWorkbenchContributionInstantiation { - readonly scheme: string; -} - /** * A workbench contribution that will be instantiated when the * corresponding editor is being created. @@ -83,17 +74,12 @@ export interface IOnEditorWorkbenchContributionInstantiation { readonly editorTypeId: string; } -function isOnFilesystemWorkbenchContributionInstantiation(obj: unknown): obj is IOnFilesystemWorkbenchContributionInstantiation { - const candidate = obj as IOnFilesystemWorkbenchContributionInstantiation | undefined; - return !!candidate && typeof candidate.scheme === 'string'; -} - function isOnEditorWorkbenchContributionInstantiation(obj: unknown): obj is IOnEditorWorkbenchContributionInstantiation { const candidate = obj as IOnEditorWorkbenchContributionInstantiation | undefined; return !!candidate && typeof candidate.editorTypeId === 'string'; } -export type WorkbenchContributionInstantiation = WorkbenchPhase | ILazyWorkbenchContributionInstantiation | IOnFilesystemWorkbenchContributionInstantiation | IOnEditorWorkbenchContributionInstantiation; +export type WorkbenchContributionInstantiation = WorkbenchPhase | ILazyWorkbenchContributionInstantiation | IOnEditorWorkbenchContributionInstantiation; function toWorkbenchPhase(phase: LifecyclePhase.Restored | LifecyclePhase.Eventually): WorkbenchPhase.AfterRestored | WorkbenchPhase.Eventually { switch (phase) { @@ -160,11 +146,9 @@ export class WorkbenchContributionsRegistry extends Disposable implements IWorkb private lifecycleService: ILifecycleService | undefined; private logService: ILogService | undefined; private environmentService: IEnvironmentService | undefined; - private fileService: IFileService | undefined; private editorPaneService: IEditorPaneService | undefined; private readonly contributionsByPhase = new Map(); - private readonly contributionsByFilesystem = new Map(); private readonly contributionsByEditor = new Map(); private readonly contributionsById = new Map(); @@ -179,17 +163,15 @@ export class WorkbenchContributionsRegistry extends Disposable implements IWorkb registerWorkbenchContribution2(id: string, ctor: IConstructorSignature, phase: WorkbenchPhase.BlockStartup | WorkbenchPhase.BlockRestore): void; registerWorkbenchContribution2(id: string | undefined, ctor: IConstructorSignature, phase: WorkbenchPhase.AfterRestored | WorkbenchPhase.Eventually): void; registerWorkbenchContribution2(id: string, ctor: IConstructorSignature, lazy: ILazyWorkbenchContributionInstantiation): void; - registerWorkbenchContribution2(id: string, ctor: IConstructorSignature, onFileSystem: IOnFilesystemWorkbenchContributionInstantiation): void; registerWorkbenchContribution2(id: string, ctor: IConstructorSignature, onEditor: IOnEditorWorkbenchContributionInstantiation): void; registerWorkbenchContribution2(id: string | undefined, ctor: IConstructorSignature, instantiation: WorkbenchContributionInstantiation): void { const contribution: IWorkbenchContributionRegistration = { id, ctor }; // Instantiate directly if we already have a matching instantiation condition if ( - this.instantiationService && this.lifecycleService && this.logService && this.environmentService && this.fileService && this.editorPaneService && + this.instantiationService && this.lifecycleService && this.logService && this.environmentService && this.editorPaneService && ( (typeof instantiation === 'number' && this.lifecycleService.phase >= instantiation) || - (typeof id === 'string' && isOnFilesystemWorkbenchContributionInstantiation(instantiation) && this.fileService.getProvider(instantiation.scheme)) || (typeof id === 'string' && isOnEditorWorkbenchContributionInstantiation(instantiation) && this.editorPaneService.didInstantiateEditorPane(instantiation.editorTypeId)) ) ) { @@ -213,11 +195,6 @@ export class WorkbenchContributionsRegistry extends Disposable implements IWorkb console.error(`IWorkbenchContributionsRegistry#registerWorkbenchContribution(): Can't register multiple contributions with same id '${id}'`); } - // by filesystem - if (isOnFilesystemWorkbenchContributionInstantiation(instantiation)) { - getOrSet(this.contributionsByFilesystem, instantiation.scheme, []).push(contribution); - } - // by editor if (isOnEditorWorkbenchContributionInstantiation(instantiation)) { getOrSet(this.contributionsByEditor, instantiation.editorTypeId, []).push(contribution); @@ -267,7 +244,6 @@ export class WorkbenchContributionsRegistry extends Disposable implements IWorkb const lifecycleService = this.lifecycleService = accessor.get(ILifecycleService); const logService = this.logService = accessor.get(ILogService); const environmentService = this.environmentService = accessor.get(IEnvironmentService); - const fileService = this.fileService = accessor.get(IFileService); const editorPaneService = this.editorPaneService = accessor.get(IEditorPaneService); // Instantiate contributions by phase when they are ready @@ -275,14 +251,6 @@ export class WorkbenchContributionsRegistry extends Disposable implements IWorkb this.instantiateByPhase(instantiationService, lifecycleService, logService, environmentService, phase); } - // Instantiate contributions by filesystem when they are activated or ready - for (const scheme of this.contributionsByFilesystem.keys()) { - if (fileService.getProvider(scheme)) { - this.onFilesystem(scheme, instantiationService, lifecycleService, logService, environmentService); - } - } - this._register(fileService.onWillActivateFileSystemProvider(e => this.onFilesystem(e.scheme, instantiationService, lifecycleService, logService, environmentService))); - // Instantiate contributions by editor when they are created or have been for (const editorTypeId of this.contributionsByEditor.keys()) { if (editorPaneService.didInstantiateEditorPane(editorTypeId)) { @@ -292,17 +260,6 @@ export class WorkbenchContributionsRegistry extends Disposable implements IWorkb this._register(editorPaneService.onWillInstantiateEditorPane(e => this.onEditor(e.typeId, instantiationService, lifecycleService, logService, environmentService))); } - private onFilesystem(scheme: string, instantiationService: IInstantiationService, lifecycleService: ILifecycleService, logService: ILogService, environmentService: IEnvironmentService): void { - const contributions = this.contributionsByFilesystem.get(scheme); - if (contributions) { - this.contributionsByFilesystem.delete(scheme); - - for (const contribution of contributions) { - this.safeCreateContribution(instantiationService, logService, environmentService, contribution, lifecycleService.phase); - } - } - } - private onEditor(editorTypeId: string, instantiationService: IInstantiationService, lifecycleService: ILifecycleService, logService: ILogService, environmentService: IEnvironmentService): void { const contributions = this.contributionsByEditor.get(editorTypeId); if (contributions) { diff --git a/src/vs/workbench/contrib/localHistory/browser/localHistory.contribution.ts b/src/vs/workbench/contrib/localHistory/browser/localHistory.contribution.ts index 065f081d9a6..f43fa6eb26b 100644 --- a/src/vs/workbench/contrib/localHistory/browser/localHistory.contribution.ts +++ b/src/vs/workbench/contrib/localHistory/browser/localHistory.contribution.ts @@ -8,4 +8,4 @@ import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/com import { LocalHistoryTimeline } from 'vs/workbench/contrib/localHistory/browser/localHistoryTimeline'; // Register Local History Timeline -registerWorkbenchContribution2(LocalHistoryTimeline.ID, LocalHistoryTimeline, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(LocalHistoryTimeline.ID, LocalHistoryTimeline, WorkbenchPhase.BlockRestore /* registrations only */); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts index a1600ccbdd2..75a7ee7cc77 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts @@ -94,4 +94,4 @@ Registry .as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(MergeEditorOpenHandlerContribution, LifecyclePhase.Restored); -registerWorkbenchContribution2(MergeEditorResolverContribution.ID, MergeEditorResolverContribution, WorkbenchPhase.BlockStartup); +registerWorkbenchContribution2(MergeEditorResolverContribution.ID, MergeEditorResolverContribution, WorkbenchPhase.BlockStartup /* only registers an editor resolver */); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts index ca7400fb4fa..84d37362cb7 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts @@ -36,7 +36,7 @@ Registry.as(Extensions.Configuration) registerSingleton(IMultiDiffSourceResolverService, MultiDiffSourceResolverService, InstantiationType.Delayed); // Editor Integration -registerWorkbenchContribution2(MultiDiffEditorResolverContribution.ID, MultiDiffEditorResolverContribution, WorkbenchPhase.BlockStartup); +registerWorkbenchContribution2(MultiDiffEditorResolverContribution.ID, MultiDiffEditorResolverContribution, WorkbenchPhase.BlockStartup /* only registering an editor resolver */); Registry.as(EditorExtensions.EditorPane) .registerEditorPane( @@ -49,4 +49,4 @@ Registry.as(EditorExtensions.EditorFactory) // SCM integration registerAction2(OpenScmGroupAction); -registerWorkbenchContribution2(ScmMultiDiffSourceResolverContribution.ID, ScmMultiDiffSourceResolverContribution, WorkbenchPhase.BlockStartup); +registerWorkbenchContribution2(ScmMultiDiffSourceResolverContribution.ID, ScmMultiDiffSourceResolverContribution, WorkbenchPhase.BlockStartup /* only registering an editor resolver */); diff --git a/src/vs/workbench/contrib/search/browser/replaceContributions.ts b/src/vs/workbench/contrib/search/browser/replaceContributions.ts index aa6cada9eb0..f129a5bfc47 100644 --- a/src/vs/workbench/contrib/search/browser/replaceContributions.ts +++ b/src/vs/workbench/contrib/search/browser/replaceContributions.ts @@ -9,5 +9,5 @@ import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/com export function registerContributions(): void { registerSingleton(IReplaceService, ReplaceService, InstantiationType.Delayed); - registerWorkbenchContribution2(ReplacePreviewContentProvider.ID, ReplacePreviewContentProvider, WorkbenchPhase.BlockStartup); + registerWorkbenchContribution2(ReplacePreviewContentProvider.ID, ReplacePreviewContentProvider, WorkbenchPhase.BlockStartup /* registration only */); } diff --git a/src/vs/workbench/contrib/url/browser/url.contribution.ts b/src/vs/workbench/contrib/url/browser/url.contribution.ts index 34a226ceae6..29d148de83a 100644 --- a/src/vs/workbench/contrib/url/browser/url.contribution.ts +++ b/src/vs/workbench/contrib/url/browser/url.contribution.ts @@ -66,12 +66,12 @@ Registry.as(WorkbenchExtensions.Workbench).regi registerWorkbenchContribution2( TrustedDomainsFileSystemProvider.ID, TrustedDomainsFileSystemProvider, - WorkbenchPhase.BlockRestore + WorkbenchPhase.BlockRestore // registration only ); registerWorkbenchContribution2( ExternalUriResolverContribution.ID, ExternalUriResolverContribution, - WorkbenchPhase.BlockRestore + WorkbenchPhase.BlockRestore // registration only ); diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts index 02c9b112c5a..517f55d9dcc 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; @@ -20,6 +19,7 @@ import { WebviewEditor } from './webviewEditor'; import { WebviewInput } from './webviewEditorInput'; import { WebviewEditorInputSerializer } from './webviewEditorInputSerializer'; import { IWebviewWorkbenchService, WebviewEditorService } from './webviewWorkbenchService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; (Registry.as(EditorExtensions.EditorPane)).registerEditorPane(EditorPaneDescriptor.create( WebviewEditor, @@ -32,25 +32,17 @@ class WebviewPanelContribution extends Disposable implements IWorkbenchContribut static readonly ID = 'workbench.contrib.webviewPanel'; constructor( - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService, + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService ) { super(); - // Add all the initial groups to be listened to - this.editorGroupService.whenReady.then(() => this.editorGroupService.groups.forEach(group => { - this.registerGroupListener(group); + this._register(editorService.onWillOpenEditor(e => { + const group = editorGroupService.getGroup(e.groupId); + if (group) { + this.onEditorOpening(e.editor, group); + } })); - - // Additional groups added should also be listened to - this._register(this.editorGroupService.onDidAddGroup(group => this.registerGroupListener(group))); - } - - private registerGroupListener(group: IEditorGroup): void { - const listener = group.onWillOpenEditor(e => this.onEditorOpening(e.editor, group)); - - Event.once(group.onWillDispose)(() => { - listener.dispose(); - }); } private onEditorOpening( diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 06675aace81..6b0dc0e8e2d 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -5,7 +5,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IResourceEditorInput, IEditorOptions, EditorActivation, IResourceEditorInputIdentifier, ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; -import { SideBySideEditor, IEditorPane, GroupIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, EditorInputWithOptions, isEditorInputWithOptions, IEditorIdentifier, IEditorCloseEvent, ITextDiffEditorPane, IRevertOptions, SaveReason, EditorsOrder, IWorkbenchEditorConfiguration, EditorResourceAccessor, IVisibleEditorPane, EditorInputCapabilities, isResourceDiffEditorInput, IUntypedEditorInput, isResourceEditorInput, isEditorInput, isEditorInputWithOptionsAndGroup, IFindEditorOptions, isResourceMergeEditorInput } from 'vs/workbench/common/editor'; +import { SideBySideEditor, IEditorPane, GroupIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, EditorInputWithOptions, isEditorInputWithOptions, IEditorIdentifier, IEditorCloseEvent, ITextDiffEditorPane, IRevertOptions, SaveReason, EditorsOrder, IWorkbenchEditorConfiguration, EditorResourceAccessor, IVisibleEditorPane, EditorInputCapabilities, isResourceDiffEditorInput, IUntypedEditorInput, isResourceEditorInput, isEditorInput, isEditorInputWithOptionsAndGroup, IFindEditorOptions, isResourceMergeEditorInput, IEditorWillOpenEvent } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { ResourceMap, ResourceSet } from 'vs/base/common/map'; @@ -50,6 +50,9 @@ export class EditorService extends Disposable implements EditorServiceImpl { private readonly _onDidEditorsChange = this._register(new Emitter()); readonly onDidEditorsChange = this._onDidEditorsChange.event; + private readonly _onWillOpenEditor = this._register(new Emitter()); + readonly onWillOpenEditor = this._onWillOpenEditor.event; + private readonly _onDidCloseEditor = this._register(new Emitter()); readonly onDidCloseEditor = this._onDidCloseEditor.event; @@ -169,6 +172,10 @@ export class EditorService extends Disposable implements EditorServiceImpl { this._onDidVisibleEditorsChange.fire(); })); + groupDisposables.add(group.onWillOpenEditor(e => { + this._onWillOpenEditor.fire(e); + })); + groupDisposables.add(group.onDidCloseEditor(e => { this._onDidCloseEditor.fire(e); })); diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 0dd568153f3..650df06217d 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorPane, GroupIdentifier, EditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent, IUntypedEditorInput, isEditorInput, IEditorWillMoveEvent, IEditorWillOpenEvent, IMatchEditorOptions, IActiveEditorChangeEvent, IFindEditorOptions, IToolbarActions } from 'vs/workbench/common/editor'; +import { IEditorPane, GroupIdentifier, EditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent, IUntypedEditorInput, isEditorInput, IEditorWillMoveEvent, IMatchEditorOptions, IActiveEditorChangeEvent, IFindEditorOptions, IToolbarActions } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -577,12 +577,6 @@ export interface IEditorGroup { */ readonly onWillMoveEditor: Event; - /** - * An event that is fired when an editor is about to be opened - * in the group. - */ - readonly onWillOpenEditor: Event; - /** * A unique identifier of this group that remains identical even if the * group is moved to different locations. diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 1092fd870bc..1be2060f98c 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -5,7 +5,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IResourceEditorInput, IEditorOptions, IResourceEditorInputIdentifier, ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; -import { IEditorPane, GroupIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, ITextDiffEditorPane, IEditorIdentifier, ISaveOptions, IRevertOptions, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent, IUntypedEditorInput, IFindEditorOptions } from 'vs/workbench/common/editor'; +import { IEditorPane, GroupIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, ITextDiffEditorPane, IEditorIdentifier, ISaveOptions, IRevertOptions, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent, IUntypedEditorInput, IFindEditorOptions, IEditorWillOpenEvent } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { Event } from 'vs/base/common/event'; import { IEditor, IDiffEditor } from 'vs/editor/common/editorCommon'; @@ -151,6 +151,11 @@ export interface IEditorService { */ readonly onDidEditorsChange: Event; + /** + * Emitted when an editor is about to open. + */ + readonly onWillOpenEditor: Event; + /** * Emitted when an editor is closed. */ diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index c0457be3192..a940bd1a309 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -98,6 +98,11 @@ suite('EditorService', () => { visibleEditorChangeEventCounter++; })); + let willOpenEditorListenerCounter = 0; + disposables.add(editorService.onWillOpenEditor(() => { + willOpenEditorListenerCounter++; + })); + let didCloseEditorListenerCounter = 0; disposables.add(editorService.onDidCloseEditor(() => { didCloseEditorListenerCounter++; @@ -131,6 +136,7 @@ suite('EditorService', () => { assert.strictEqual(editorService.isOpened({ resource: input.resource, typeId: 'unknownTypeId', editorId: 'unknownTypeId' }), false); assert.strictEqual(editorService.isVisible(input), true); assert.strictEqual(editorService.isVisible(otherInput), false); + assert.strictEqual(willOpenEditorListenerCounter, 1); assert.strictEqual(activeEditorChangeEventCounter, 1); assert.strictEqual(visibleEditorChangeEventCounter, 1); assert.ok(editorPaneService.didInstantiateEditorPane(TEST_EDITOR_ID)); @@ -170,6 +176,7 @@ suite('EditorService', () => { assert.strictEqual(editorService.isOpened({ resource: otherInput.resource, typeId: otherInput.typeId, editorId: otherInput.editorId }), true); assert.strictEqual(activeEditorChangeEventCounter, 4); + assert.strictEqual(willOpenEditorListenerCounter, 3); assert.strictEqual(visibleEditorChangeEventCounter, 4); const stickyInput = createTestFileEditorInput(URI.parse('my://resource3-basics'), TEST_EDITOR_INPUT_ID); diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index 5201436eaf8..d019d14c4fb 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -425,7 +425,7 @@ class ExtensionUrlBootstrapHandler implements IWorkbenchContribution, IURLHandle } } -registerWorkbenchContribution2(ExtensionUrlBootstrapHandler.ID, ExtensionUrlBootstrapHandler, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(ExtensionUrlBootstrapHandler.ID, ExtensionUrlBootstrapHandler, WorkbenchPhase.BlockRestore /* registration only */); class ManageAuthorizedExtensionURIsAction extends Action2 { diff --git a/src/vs/workbench/test/browser/contributions.test.ts b/src/vs/workbench/test/browser/contributions.test.ts index 64129a9ea99..8f878d6ab53 100644 --- a/src/vs/workbench/test/browser/contributions.test.ts +++ b/src/vs/workbench/test/browser/contributions.test.ts @@ -17,7 +17,7 @@ import { EditorService } from 'vs/workbench/services/editor/browser/editorServic import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { ITestInstantiationService, TestFileEditorInput, TestInMemoryFileSystemProvider, TestServiceAccessor, TestSingletonFileEditorInput, createEditorPart, registerTestEditor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { ITestInstantiationService, TestFileEditorInput, TestServiceAccessor, TestSingletonFileEditorInput, createEditorPart, registerTestEditor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; suite('Contributions', () => { const disposables = new DisposableStore(); @@ -174,27 +174,6 @@ suite('Contributions', () => { assert.ok(bCreated); }); - test('contribution on file system', async function () { - const registry = disposables.add(new WorkbenchContributionsRegistry()); - - const instantiationService = workbenchInstantiationService(undefined, disposables); - const accessor = instantiationService.createInstance(TestServiceAccessor); - disposables.add(accessor.fileService.registerProvider('testBefore', disposables.add(new TestInMemoryFileSystemProvider()))); - - registry.registerWorkbenchContribution2('a', TestContributionA, { scheme: 'testBefore' }); - registry.start(instantiationService); - - await aCreatedPromise.p; - assert.ok(aCreated); - - registry.registerWorkbenchContribution2('b', TestContributionB, { scheme: 'testAfter' }); - - accessor.fileService.activateProvider('testAfter'); - - await bCreatedPromise.p; - assert.ok(bCreated); - }); - test('contribution on editor - editor exists before start', async function () { const registry = disposables.add(new WorkbenchContributionsRegistry()); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index dfdca368834..d9b0f8df766 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1004,6 +1004,7 @@ export class TestEditorService extends Disposable implements EditorServiceImpl { onDidActiveEditorChange: Event = Event.None; onDidVisibleEditorsChange: Event = Event.None; onDidEditorsChange: Event = Event.None; + onWillOpenEditor: Event = Event.None; onDidCloseEditor: Event = Event.None; onDidOpenEditorFail: Event = Event.None; onDidMostRecentlyActiveEditorsChange: Event = Event.None; From b288546d3963d6b48ad6870296302d30410063f1 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 5 Feb 2024 13:21:51 -0300 Subject: [PATCH 0911/1897] Escape '?' in debug terminal (#204378) Fix #204302 --- src/vs/workbench/contrib/debug/node/terminals.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index dab6a2962d5..c3f3cd92928 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -161,7 +161,7 @@ export function prepareCommand(shell: string, args: string[], argsCanBeInterpret case ShellType.bash: { quote = (s: string) => { - s = s.replace(/(["'\\\$!><#()\[\]*&^| ;{}`])/g, '\\$1'); + s = s.replace(/(["'\\\$!><#()\[\]*&^| ;{}?`])/g, '\\$1'); return s.length === 0 ? `""` : s; }; From f65e51669309ddc3e5786fc560255233e4220871 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 5 Feb 2024 08:37:41 -0800 Subject: [PATCH 0912/1897] Migrate top insert toolbar to notebook view zone (#204185) * Migrate top insert toolbar to notebook view zone * remove test command * Update docs. --- .../notebook/browser/notebookEditorWidget.ts | 20 +-- .../notebook/browser/view/notebookCellList.ts | 4 +- .../browser/view/notebookCellListView.ts | 29 ++++ .../viewParts/notebookTopCellToolbar.ts | 143 +++++++++++++----- .../browser/viewParts/notebookViewZones.ts | 27 +++- .../test/browser/notebookViewZones.test.ts | 85 +++++++++++ 6 files changed, 250 insertions(+), 58 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index e32a1abfd7a..8ac1e14d8e2 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -754,10 +754,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD // between cell insert toolbar if (insertToolbarPosition === 'betweenCells' || insertToolbarPosition === 'both') { styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container { display: flex; }`); - styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .cell-list-top-cell-toolbar-container { display: flex; }`); + styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .view-zones .cell-list-top-cell-toolbar-container { display: flex; }`); } else { styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container { display: none; }`); - styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .cell-list-top-cell-toolbar-container { display: none; }`); + styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .view-zones .cell-list-top-cell-toolbar-container { display: none; }`); } if (insertToolbarAlignment === 'left') { @@ -844,7 +844,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD `); styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container .monaco-toolbar { height: ${bottomToolbarHeight}px }`); - styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .cell-list-top-cell-toolbar-container .monaco-toolbar { height: ${bottomToolbarHeight}px }`); + styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .view-zones .cell-list-top-cell-toolbar-container .monaco-toolbar { height: ${bottomToolbarHeight}px }`); // cell toolbar styleSheets.push(`.monaco-workbench .notebookOverlay.cell-title-toolbar-right > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-title-toolbar { @@ -923,7 +923,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD multipleSelectionSupport: true, selectionNavigation: true, typeNavigationEnabled: true, - paddingTop: this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType), + paddingTop: 0, paddingBottom: 0, transformOptimization: false, //(isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native', initialSize: this._dimension, @@ -973,7 +973,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._register(combinedDisposable(...renderers)); // top cell toolbar - this._listTopCellToolbar = this._register(this.instantiationService.createInstance(ListTopCellToolbar, this, this.scopedContextKeyService, this._list.rowsContainer)); + this._listTopCellToolbar = this._register(this.instantiationService.createInstance(ListTopCellToolbar, this, this.notebookOptions)); // transparent cover this._webviewTransparentCover = DOM.append(this._list.rowsContainer, $('.webview-cover')); @@ -1131,15 +1131,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD async setModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined, perf?: NotebookPerfMarks): Promise { if (this.viewModel === undefined || !this.viewModel.equal(textModel)) { - const oldTopInsertToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType); const oldBottomToolbarDimensions = this._notebookOptions.computeBottomToolbarDimensions(this.viewModel?.viewType); this._detachModel(); await this._attachModel(textModel, viewState, perf); - const newTopInsertToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType); const newBottomToolbarDimensions = this._notebookOptions.computeBottomToolbarDimensions(this.viewModel?.viewType); - if (oldTopInsertToolbarHeight !== newTopInsertToolbarHeight - || oldBottomToolbarDimensions.bottomToolbarGap !== newBottomToolbarDimensions.bottomToolbarGap + if (oldBottomToolbarDimensions.bottomToolbarGap !== newBottomToolbarDimensions.bottomToolbarGap || oldBottomToolbarDimensions.bottomToolbarHeight !== newBottomToolbarDimensions.bottomToolbarHeight) { this._styleElement?.remove(); this._createLayoutStyles(); @@ -1848,16 +1845,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const newBodyHeight = this.getBodyHeight(dimension.height) - this.getLayoutInfo().stickyHeight; DOM.size(this._body, dimension.width, newBodyHeight); - const topInserToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType); const newCellListHeight = newBodyHeight; if (this._list.getRenderHeight() < newCellListHeight) { // the new dimension is larger than the list viewport, update its additional height first, otherwise the list view will move down a bit (as the `scrollBottom` will move down) - this._list.updateOptions({ paddingBottom: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : 0, paddingTop: topInserToolbarHeight }); + this._list.updateOptions({ paddingBottom: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : 0, paddingTop: 0 }); this._list.layout(newCellListHeight, dimension.width); } else { // the new dimension is smaller than the list viewport, if we update the additional height, the `scrollBottom` will move up, which moves the whole list view upwards a bit. So we run a layout first. this._list.layout(newCellListHeight, dimension.width); - this._list.updateOptions({ paddingBottom: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : 0, paddingTop: topInserToolbarHeight }); + this._list.updateOptions({ paddingBottom: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : 0, paddingTop: 0 }); } this._overlayContainer.style.visibility = 'visible'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index c839e091f88..62cf6f3b766 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -1254,7 +1254,9 @@ export class NotebookCellList extends WorkbenchList implements ID } changeViewZones(callback: (accessor: INotebookViewZoneChangeAccessor) => void): void { - this.viewZones.changeViewZones(callback); + if (this.viewZones.changeViewZones(callback)) { + this.viewZones.layout(); + } } // override diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts index c26ca53ac85..c2752fe8103 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts @@ -147,6 +147,31 @@ export class NotebookCellsLayout implements IRangeMap { } } + /** + * find position of whitespace + * @param id: id of the whitespace + * @returns: position in the list view + */ + getWhitespacePosition(id: string): number { + const whitespace = this._whitespace.find(ws => ws.id === id); + if (!whitespace) { + throw new Error('Whitespace not found'); + } + + const afterPosition = whitespace.afterPosition; + if (afterPosition === 0) { + return this.paddingTop; + } + + const whitespaceBeforeFirstItem = this._whitespace.length > 0 && this._whitespace[0].afterPosition === 0 ? this._whitespace[0].size : 0; + + // previous item index + const index = afterPosition - 1; + const previousItemPosition = this._prefixSumComputer.getPrefixSum(index); + const previousItemSize = this._items[index].size; + return previousItemPosition + previousItemSize + whitespaceBeforeFirstItem + this.paddingTop; + } + indexAt(position: number): number { if (position < 0) { return -1; @@ -238,4 +263,8 @@ export class NotebookCellListView extends ListView { this.notebookRangeMap.removeWhitespace(id); this.eventuallyUpdateScrollDimensions(); } + + getWhitespacePosition(id: string): number { + return this.notebookRangeMap.getWhitespacePosition(id); + } } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts index 73db1590837..9a558d0dd28 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts @@ -4,69 +4,136 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotebookActionContext } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView'; export class ListTopCellToolbar extends Disposable { + private readonly topCellToolbarContainer: HTMLElement; private topCellToolbar: HTMLElement; - private toolbar: MenuWorkbenchToolBar; + private viewZone: MutableDisposable = this._register(new MutableDisposable()); private readonly _modelDisposables = this._register(new DisposableStore()); constructor( protected readonly notebookEditor: INotebookEditorDelegate, - - contextKeyService: IContextKeyService, - insertionIndicatorContainer: HTMLElement, + private readonly notebookOptions: NotebookOptions, @IInstantiationService protected readonly instantiationService: IInstantiationService, @IContextMenuService protected readonly contextMenuService: IContextMenuService, @IMenuService protected readonly menuService: IMenuService ) { super(); - this.topCellToolbar = DOM.append(insertionIndicatorContainer, DOM.$('.cell-list-top-cell-toolbar-container')); + this.topCellToolbarContainer = DOM.$('div'); + this.topCellToolbar = DOM.$('.cell-list-top-cell-toolbar-container'); + this.topCellToolbarContainer.appendChild(this.topCellToolbar); - this.toolbar = this._register(instantiationService.createInstance(MenuWorkbenchToolBar, this.topCellToolbar, this.notebookEditor.creationOptions.menuIds.cellTopInsertToolbar, { - actionViewItemProvider: action => { - if (action instanceof MenuItemAction) { - const item = this.instantiationService.createInstance(CodiconActionViewItem, action, undefined); - return item; - } - - return undefined; - }, - menuOptions: { - shouldForwardArgs: true - }, - toolbarOptions: { - primaryGroup: (g: string) => /^inline/.test(g), - }, - hiddenItemStrategy: HiddenItemStrategy.Ignore, + this._register(this.notebookEditor.onDidAttachViewModel(() => { + this.updateTopToolbar(); })); - this.toolbar.context = { - notebookEditor - }; - - // update toolbar container css based on cell list length - this._register(this.notebookEditor.onDidChangeModel(() => { - this._modelDisposables.clear(); - - if (this.notebookEditor.hasModel()) { - this._modelDisposables.add(this.notebookEditor.onDidChangeViewCells(() => { - this.updateClass(); - })); - - this.updateClass(); + this._register(this.notebookOptions.onDidChangeOptions(e => { + if (e.insertToolbarAlignment || e.insertToolbarPosition || e.cellToolbarLocation) { + this.updateTopToolbar(); } })); + } - this.updateClass(); + private updateTopToolbar() { + const layoutInfo = this.notebookOptions.getLayoutConfiguration(); + this.viewZone.value = new DisposableStore(); + + if (layoutInfo.insertToolbarPosition === 'hidden' || layoutInfo.insertToolbarPosition === 'notebookToolbar') { + const height = this.notebookOptions.computeTopInsertToolbarHeight(this.notebookEditor.textModel?.viewType); + + if (height !== 0) { + // reserve whitespace to avoid overlap with cell toolbar + this.notebookEditor.changeViewZones(accessor => { + const id = accessor.addZone({ + afterModelPosition: 0, + heightInPx: height, + domNode: DOM.$('div') + }); + accessor.layoutZone(id); + this.viewZone.value?.add({ + dispose: () => { + if (!this.notebookEditor.isDisposed) { + this.notebookEditor.changeViewZones(accessor => { + accessor.removeZone(id); + }); + } + } + }); + }); + } + return; + } + + + this.notebookEditor.changeViewZones(accessor => { + const height = this.notebookOptions.computeTopInsertToolbarHeight(this.notebookEditor.textModel?.viewType); + const id = accessor.addZone({ + afterModelPosition: 0, + heightInPx: height, + domNode: this.topCellToolbarContainer + }); + accessor.layoutZone(id); + + this.viewZone.value?.add({ + dispose: () => { + if (!this.notebookEditor.isDisposed) { + this.notebookEditor.changeViewZones(accessor => { + accessor.removeZone(id); + }); + } + } + }); + + DOM.clearNode(this.topCellToolbar); + + const toolbar = this.instantiationService.createInstance(MenuWorkbenchToolBar, this.topCellToolbar, this.notebookEditor.creationOptions.menuIds.cellTopInsertToolbar, { + actionViewItemProvider: action => { + if (action instanceof MenuItemAction) { + const item = this.instantiationService.createInstance(CodiconActionViewItem, action, undefined); + return item; + } + + return undefined; + }, + menuOptions: { + shouldForwardArgs: true + }, + toolbarOptions: { + primaryGroup: (g: string) => /^inline/.test(g), + }, + hiddenItemStrategy: HiddenItemStrategy.Ignore, + }); + + toolbar.context = { + notebookEditor: this.notebookEditor + }; + + this.viewZone.value?.add(toolbar); + + // update toolbar container css based on cell list length + this.viewZone.value?.add(this.notebookEditor.onDidChangeModel(() => { + this._modelDisposables.clear(); + + if (this.notebookEditor.hasModel()) { + this._modelDisposables.add(this.notebookEditor.onDidChangeViewCells(() => { + this.updateClass(); + })); + + this.updateClass(); + } + })); + + this.updateClass(); + }); } private updateClass() { diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts index bf00d8b7a68..a56dd538155 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts @@ -11,6 +11,8 @@ import { NotebookCellListView } from 'vs/workbench/contrib/notebook/browser/view import { ICoordinatesConverter } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; +const invalidFunc = () => { throw new Error(`Invalid notebook view zone change accessor`); }; + interface IZoneWidget { whitespaceId: string; isInHiddenArea: boolean; @@ -29,25 +31,39 @@ export class NotebookViewZones extends Disposable { this.domNode.setPosition('absolute'); this.domNode.setAttribute('role', 'presentation'); this.domNode.setAttribute('aria-hidden', 'true'); + this.domNode.setWidth('100%'); this._zones = {}; this.listView.containerDomNode.appendChild(this.domNode.domNode); } - changeViewZones(callback: (changeAccessor: INotebookViewZoneChangeAccessor) => void): void { + changeViewZones(callback: (changeAccessor: INotebookViewZoneChangeAccessor) => void): boolean { + let zonesHaveChanged = false; const changeAccessor: INotebookViewZoneChangeAccessor = { addZone: (zone: INotebookViewZone): string => { + zonesHaveChanged = true; return this._addZone(zone); }, removeZone: (id: string): void => { + zonesHaveChanged = true; + // TODO: validate if zones have changed layout this._removeZone(id); }, layoutZone: (id: string): void => { + zonesHaveChanged = true; + // TODO: validate if zones have changed layout this._layoutZone(id); } }; safeInvoke1Arg(callback, changeAccessor); + + // Invalidate changeAccessor + changeAccessor.addZone = invalidFunc; + changeAccessor.removeZone = invalidFunc; + changeAccessor.layoutZone = invalidFunc; + + return zonesHaveChanged; } onCellsChanged(e: INotebookViewCellsUpdateEvent): void { @@ -138,13 +154,10 @@ export class NotebookViewZones extends Disposable { if (isInHiddenArea) { zoneWidget.domNode.setDisplay('none'); } else { - const afterPosition = zoneWidget.zone.afterModelPosition; - const index = afterPosition - 1; - const viewIndex = this.coordinator.convertModelIndexToViewIndex(index); - const top = this.listView.elementTop(viewIndex); - const height = this.listView.elementHeight(viewIndex); - zoneWidget.domNode.setTop(top + height); + const top = this.listView.getWhitespacePosition(zoneWidget.whitespaceId); + zoneWidget.domNode.setTop(top); zoneWidget.domNode.setDisplay('block'); + zoneWidget.domNode.setHeight(zoneWidget.zone.heightInPx); } } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts index 059704d8176..fb8562f0a1e 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts @@ -401,6 +401,71 @@ suite('NotebookRangeMap with whitesspaces', () => { }); }); + test('Multiple Whitespaces', async function () { + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markup, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markup, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['# header c', 'markdown', CellKind.Markup, [], {}] + ], + async (editor, viewModel, disposables) => { + viewModel.restoreEditorViewState({ + editingCells: [false, false, false, false, false], + cellLineNumberStates: {}, + editorViewStates: [null, null, null, null, null], + cellTotalHeights: [50, 100, 50, 100, 50], + collapsedInputCells: {}, + collapsedOutputCells: {}, + }); + + const cellList = createNotebookCellList(instantiationService, disposables); + disposables.add(cellList); + cellList.attachViewModel(viewModel); + + // render height 210, it can render 3 full cells and 1 partial cell + cellList.layout(210, 100); + assert.strictEqual(cellList.scrollHeight, 350); + + cellList.changeViewZones(accessor => { + const first = accessor.addZone({ + afterModelPosition: 0, + heightInPx: 20, + domNode: document.createElement('div') + }); + accessor.layoutZone(first); + + const second = accessor.addZone({ + afterModelPosition: 3, + heightInPx: 20, + domNode: document.createElement('div') + }); + accessor.layoutZone(second); + + assert.strictEqual(cellList.scrollHeight, 390); + + assert.strictEqual(cellList.getElementTop(0), 20); + assert.strictEqual(cellList.getElementTop(1), 70); + assert.strictEqual(cellList.getElementTop(2), 170); + assert.strictEqual(cellList.getElementTop(3), 240); + + accessor.removeZone(first); + + assert.strictEqual(cellList.scrollHeight, 370); + assert.strictEqual(cellList.getElementTop(0), 0); + assert.strictEqual(cellList.getElementTop(1), 50); + assert.strictEqual(cellList.getElementTop(2), 150); + assert.strictEqual(cellList.getElementTop(3), 220); + + accessor.removeZone(second); + + assert.strictEqual(cellList.scrollHeight, 350); + assert.strictEqual(cellList.getElementTop(3), 200); + }); + }); + }); + test('Whitespace with folding support', async function () { await withTestNotebook( [ @@ -428,6 +493,26 @@ suite('NotebookRangeMap with whitesspaces', () => { cellList.layout(210, 100); assert.strictEqual(cellList.scrollHeight, 350); + cellList.changeViewZones(accessor => { + const id = accessor.addZone({ + afterModelPosition: 0, + heightInPx: 20, + domNode: document.createElement('div') + }); + + accessor.layoutZone(id); + assert.strictEqual(cellList.scrollHeight, 370); + + assert.strictEqual(cellList.getElementTop(0), 20); + assert.strictEqual(cellList.getElementTop(1), 70); + assert.strictEqual(cellList.getElementTop(2), 170); + assert.strictEqual(cellList.getElementTop(3), 220); + assert.strictEqual(cellList.getElementTop(4), 320); + + accessor.removeZone(id); + assert.strictEqual(cellList.scrollHeight, 350); + }); + cellList.changeViewZones(accessor => { const id = accessor.addZone({ afterModelPosition: 1, From 857bfa63e495fd769574819d0075393b289426c1 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 5 Feb 2024 09:15:18 -0800 Subject: [PATCH 0913/1897] Enable `typescript.enablePromptUseWorkspaceTsdk` in repo (#204381) This will prompt users to switch to the workspace version on first open of the workspace --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index f200b221b37..ba7c0f4dd2b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -171,4 +171,5 @@ "css.format.spaceAroundSelectorSeparator": true, "inlineChat.mode": "live", "testing.defaultGutterClickAction": "contextMenu", + "typescript.enablePromptUseWorkspaceTsdk": true, } From 1e0fba6d7ffeddbcd20cf5ef79c8e7a132dd8ca2 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 5 Feb 2024 20:07:26 +0100 Subject: [PATCH 0914/1897] updates api notes (#204386) --- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 3 +++ src/vscode-dts/vscode.proposed.chatProvider.d.ts | 2 ++ src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts | 10 ++++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index c784e32dd3e..bed707cf559 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -64,6 +64,9 @@ declare module 'vscode' { * If the request resulted in an error, this property defines the error details. */ errorDetails?: ChatAgentErrorDetails; + + // TODO@API + // add CATCH-all signature [name:string]: string|boolean|number instead of `T extends...` } /** diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index 259ee594bf8..3ff89cca683 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -10,6 +10,8 @@ declare module 'vscode' { part: string; } + // @API extension ship a d.ts files for their options + /** * Represents a large language model that accepts ChatML messages and produces a streaming response */ diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts index f696289c062..b7bdef94fb1 100644 --- a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts +++ b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts @@ -65,13 +65,15 @@ declare module 'vscode' { * Whether the access to chat has been revoked. This happens when the condition that allowed for * chat access doesn't hold anymore, e.g a user interaction has ended. */ - isRevoked: boolean; + readonly isRevoked: boolean; + + // @API do we need an event for revocation /** * The name of the model that is used for this chat access. It is expected that the model name can * be used to lookup properties like token limits and ChatML support */ - model: string; + readonly model: string; /** * Make a chat request. @@ -101,5 +103,9 @@ declare module 'vscode' { * @param id The id of the chat provider, e.g `copilot` */ export function requestChatAccess(id: string): Thenable; + + //@API add those + // export const chatAccesses: string[]; + // export const onDidChangeChatAccesses: Event; } } From bd06f6c3d84c2cb860d919cce14a77950009d4c4 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Mon, 5 Feb 2024 11:41:13 -0800 Subject: [PATCH 0915/1897] Support walkthrough open toSide (#204220) --- .../browser/gettingStarted.contribution.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts index a184caff2e7..683831a49f2 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts @@ -10,7 +10,7 @@ import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/ed import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode } from 'vs/base/common/keyCodes'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; @@ -65,7 +65,10 @@ registerAction2(class extends Action2 { // We're trying to open the welcome page from the Help menu if (!selectedCategory && !selectedStep) { - editorService.openEditor({ resource: GettingStartedInput.RESOURCE }); + editorService.openEditor({ + resource: GettingStartedInput.RESOURCE, + options: { preserveFocus: toSide ?? false } + }, toSide ? SIDE_GROUP : undefined); return; } @@ -110,13 +113,16 @@ registerAction2(class extends Action2 { editorService.openEditor({ resource: GettingStartedInput.RESOURCE, options: { selectedCategory: selectedCategory, selectedStep: selectedStep, preserveFocus: toSide ?? false } - }).then((editor) => { + }, toSide ? SIDE_GROUP : undefined).then((editor) => { (editor as GettingStartedPage)?.makeCategoryVisibleWhenAvailable(selectedCategory, selectedStep); }); } } else { - editorService.openEditor({ resource: GettingStartedInput.RESOURCE }); + editorService.openEditor({ + resource: GettingStartedInput.RESOURCE, + options: { preserveFocus: toSide ?? false } + }, toSide ? SIDE_GROUP : undefined); } } }); From 4a318a5df90491a4862f409f352e334ded4d0220 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 5 Feb 2024 20:53:46 +0100 Subject: [PATCH 0916/1897] Aux window: validate window bounds are on display bounds (fix #204346) (#204399) --- src/vs/code/electron-main/app.ts | 10 +- .../electron-main/auxiliaryWindows.ts | 4 +- .../auxiliaryWindowsMainService.ts | 34 +++- .../windows/electron-main/windowImpl.ts | 146 +---------------- .../platform/windows/electron-main/windows.ts | 150 +++++++++++++++++- .../url/browser/externalUriResolver.ts | 4 +- .../browser/webviewPanel.contribution.ts | 6 +- 7 files changed, 192 insertions(+), 162 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 6f02aa6fc22..1dfa17a817c 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -407,23 +407,23 @@ export class CodeApplication extends Disposable { // All Windows: only allow about:blank auxiliary windows to open // For all other URLs, delegate to the OS. - contents.setWindowOpenHandler(handler => { + contents.setWindowOpenHandler(details => { // about:blank windows can open as window witho our default options - if (handler.url === 'about:blank') { + if (details.url === 'about:blank') { this.logService.trace('[aux window] webContents#setWindowOpenHandler: Allowing auxiliary window to open on about:blank'); return { action: 'allow', - overrideBrowserWindowOptions: this.auxiliaryWindowsMainService?.createWindow() + overrideBrowserWindowOptions: this.auxiliaryWindowsMainService?.createWindow(details) }; } // Any other URL: delegate to OS else { - this.logService.trace(`webContents#setWindowOpenHandler: Prevented opening window with URL ${handler.url}}`); + this.logService.trace(`webContents#setWindowOpenHandler: Prevented opening window with URL ${details.url}}`); - this.nativeHostMainService?.openExternal(undefined, handler.url); + this.nativeHostMainService?.openExternal(undefined, details.url); return { action: 'deny' }; } diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts index d437cc7f6ba..699659d0c77 100644 --- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts +++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BrowserWindowConstructorOptions, WebContents } from 'electron'; +import { BrowserWindowConstructorOptions, HandlerDetails, WebContents } from 'electron'; import { Event } from 'vs/base/common/event'; import { Schemas, VSCODE_AUTHORITY } from 'vs/base/common/network'; import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; @@ -20,7 +20,7 @@ export interface IAuxiliaryWindowsMainService { readonly onDidChangeFullScreen: Event<{ window: IAuxiliaryWindow; fullscreen: boolean }>; readonly onDidTriggerSystemContextMenu: Event<{ readonly window: IAuxiliaryWindow; readonly x: number; readonly y: number }>; - createWindow(): BrowserWindowConstructorOptions; + createWindow(details: HandlerDetails): BrowserWindowConstructorOptions; registerWindow(webContents: WebContents): void; getWindowById(windowId: number): IAuxiliaryWindow | undefined; diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts index d0afe918cf4..786f2ef9639 100644 --- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts +++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BrowserWindow, BrowserWindowConstructorOptions, WebContents, app } from 'electron'; +import { BrowserWindow, BrowserWindowConstructorOptions, HandlerDetails, WebContents, app } from 'electron'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { FileAccess } from 'vs/base/common/network'; @@ -12,7 +12,8 @@ import { AuxiliaryWindow, IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/e import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { defaultBrowserWindowOptions, getLastFocused } from 'vs/platform/windows/electron-main/windows'; +import { IWindowState } from 'vs/platform/window/electron-main/window'; +import { WindowStateValidator, defaultBrowserWindowOptions, getLastFocused } from 'vs/platform/windows/electron-main/windows'; export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliaryWindowsMainService { @@ -70,14 +71,39 @@ export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliar }); } - createWindow(): BrowserWindowConstructorOptions { - return this.instantiationService.invokeFunction(defaultBrowserWindowOptions, undefined, { + createWindow(details: HandlerDetails): BrowserWindowConstructorOptions { + return this.instantiationService.invokeFunction(defaultBrowserWindowOptions, this.validateWindowState(details), { webPreferences: { preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-sandbox/preload-aux.js').fsPath } }); } + private validateWindowState(details: HandlerDetails): IWindowState | undefined { + const windowState: IWindowState = {}; + + const features = details.features.split(','); // for example: popup=yes,left=270,top=14.5,width=800,height=600 + for (const feature of features) { + const [key, value] = feature.split('='); + switch (key) { + case 'width': + windowState.width = parseInt(value, 10); + break; + case 'height': + windowState.height = parseInt(value, 10); + break; + case 'left': + windowState.x = parseInt(value, 10); + break; + case 'top': + windowState.y = parseInt(value, 10); + break; + } + } + + return WindowStateValidator.validateWindowState(this.logService, windowState); + } + registerWindow(webContents: WebContents): void { const disposables = new DisposableStore(); diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 6a237f67d77..3041b4e0d77 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -33,7 +33,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ThemeIcon } from 'vs/base/common/themables'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { getMenuBarVisibility, IFolderToOpen, INativeWindowConfiguration, IWindowSettings, IWorkspaceToOpen, MenuBarVisibility, hasNativeTitlebar, useNativeFullScreen, useWindowControlsOverlay } from 'vs/platform/window/common/window'; -import { defaultBrowserWindowOptions, IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; +import { defaultBrowserWindowOptions, IWindowsMainService, OpenContext, WindowStateValidator } from 'vs/platform/windows/electron-main/windows'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { IWindowState, ICodeWindow, ILoadEvent, WindowMode, WindowError, LoadReason, defaultWindowState, IBaseWindow } from 'vs/platform/window/electron-main/window'; @@ -1259,7 +1259,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { const displays = screen.getAllDisplays(); hasMultipleDisplays = displays.length > 1; - state = this.validateWindowState(state, displays); + state = WindowStateValidator.validateWindowState(this.logService, state, displays); } catch (err) { this.logService.warn(`Unexpected error validating window state: ${err}\n${err.stack}`); // somehow display API can be picky about the state to validate } @@ -1270,148 +1270,6 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { return [state || defaultWindowState(), hasMultipleDisplays]; } - private validateWindowState(state: IWindowState, displays: Display[]): IWindowState | undefined { - this.logService.trace(`window#validateWindowState: validating window state on ${displays.length} display(s)`, state); - - if ( - typeof state.x !== 'number' || - typeof state.y !== 'number' || - typeof state.width !== 'number' || - typeof state.height !== 'number' - ) { - this.logService.trace('window#validateWindowState: unexpected type of state values'); - - return undefined; - } - - if (state.width <= 0 || state.height <= 0) { - this.logService.trace('window#validateWindowState: unexpected negative values'); - - return undefined; - } - - // Single Monitor: be strict about x/y positioning - // macOS & Linux: these OS seem to be pretty good in ensuring that a window is never outside of it's bounds. - // Windows: it is possible to have a window with a size that makes it fall out of the window. our strategy - // is to try as much as possible to keep the window in the monitor bounds. we are not as strict as - // macOS and Linux and allow the window to exceed the monitor bounds as long as the window is still - // some pixels (128) visible on the screen for the user to drag it back. - if (displays.length === 1) { - const displayWorkingArea = this.getWorkingArea(displays[0]); - if (displayWorkingArea) { - this.logService.trace('window#validateWindowState: 1 monitor working area', displayWorkingArea); - - function ensureStateInDisplayWorkingArea(): void { - if (!state || typeof state.x !== 'number' || typeof state.y !== 'number' || !displayWorkingArea) { - return; - } - - if (state.x < displayWorkingArea.x) { - // prevent window from falling out of the screen to the left - state.x = displayWorkingArea.x; - } - - if (state.y < displayWorkingArea.y) { - // prevent window from falling out of the screen to the top - state.y = displayWorkingArea.y; - } - } - - // ensure state is not outside display working area (top, left) - ensureStateInDisplayWorkingArea(); - - if (state.width > displayWorkingArea.width) { - // prevent window from exceeding display bounds width - state.width = displayWorkingArea.width; - } - - if (state.height > displayWorkingArea.height) { - // prevent window from exceeding display bounds height - state.height = displayWorkingArea.height; - } - - if (state.x > (displayWorkingArea.x + displayWorkingArea.width - 128)) { - // prevent window from falling out of the screen to the right with - // 128px margin by positioning the window to the far right edge of - // the screen - state.x = displayWorkingArea.x + displayWorkingArea.width - state.width; - } - - if (state.y > (displayWorkingArea.y + displayWorkingArea.height - 128)) { - // prevent window from falling out of the screen to the bottom with - // 128px margin by positioning the window to the far bottom edge of - // the screen - state.y = displayWorkingArea.y + displayWorkingArea.height - state.height; - } - - // again ensure state is not outside display working area - // (it may have changed from the previous validation step) - ensureStateInDisplayWorkingArea(); - } - - return state; - } - - // Multi Montior (fullscreen): try to find the previously used display - if (state.display && state.mode === WindowMode.Fullscreen) { - const display = displays.find(d => d.id === state.display); - if (display && typeof display.bounds?.x === 'number' && typeof display.bounds?.y === 'number') { - this.logService.trace('window#validateWindowState: restoring fullscreen to previous display'); - - const defaults = defaultWindowState(WindowMode.Fullscreen); // make sure we have good values when the user restores the window - defaults.x = display.bounds.x; // carefull to use displays x/y position so that the window ends up on the correct monitor - defaults.y = display.bounds.y; - - return defaults; - } - } - - // Multi Monitor (non-fullscreen): ensure window is within display bounds - let display: Display | undefined; - let displayWorkingArea: Rectangle | undefined; - try { - display = screen.getDisplayMatching({ x: state.x, y: state.y, width: state.width, height: state.height }); - displayWorkingArea = this.getWorkingArea(display); - } catch (error) { - // Electron has weird conditions under which it throws errors - // e.g. https://github.com/microsoft/vscode/issues/100334 when - // large numbers are passed in - } - - if ( - display && // we have a display matching the desired bounds - displayWorkingArea && // we have valid working area bounds - state.x + state.width > displayWorkingArea.x && // prevent window from falling out of the screen to the left - state.y + state.height > displayWorkingArea.y && // prevent window from falling out of the screen to the top - state.x < displayWorkingArea.x + displayWorkingArea.width && // prevent window from falling out of the screen to the right - state.y < displayWorkingArea.y + displayWorkingArea.height // prevent window from falling out of the screen to the bottom - ) { - this.logService.trace('window#validateWindowState: multi-monitor working area', displayWorkingArea); - - return state; - } - - return undefined; - } - - private getWorkingArea(display: Display): Rectangle | undefined { - - // Prefer the working area of the display to account for taskbars on the - // desktop being positioned somewhere (https://github.com/microsoft/vscode/issues/50830). - // - // Linux X11 sessions sometimes report wrong display bounds, so we validate - // the reported sizes are positive. - if (display.workArea.width > 0 && display.workArea.height > 0) { - return display.workArea; - } - - if (display.bounds.width > 0 && display.bounds.height > 0) { - return display.bounds; - } - - return undefined; - } - getBounds(): Rectangle { const [x, y] = this._win.getPosition(); const [width, height] = this._win.getSize(); diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 1a57c00cee3..c5fd87557d7 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BrowserWindowConstructorOptions, WebContents } from 'electron'; +import { BrowserWindowConstructorOptions, Display, Rectangle, WebContents, screen } from 'electron'; import { Event } from 'vs/base/common/event'; import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ICodeWindow, IWindowState } from 'vs/platform/window/electron-main/window'; +import { ICodeWindow, IWindowState, WindowMode, defaultWindowState } from 'vs/platform/window/electron-main/window'; import { IOpenEmptyWindowOptions, IWindowOpenable, IWindowSettings, WindowMinimumSize, hasNativeTitlebar, useNativeFullScreen, useWindowControlsOverlay, zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -18,6 +18,7 @@ import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/e import { join } from 'vs/base/common/path'; import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; import { Color } from 'vs/base/common/color'; +import { ILogService } from 'vs/platform/log/common/log'; export const IWindowsMainService = createDecorator('windowsMainService'); @@ -214,3 +215,148 @@ export function getLastFocused(windows: ICodeWindow[] | IAuxiliaryWindow[]): ICo return lastFocusedWindow; } + +export namespace WindowStateValidator { + + export function validateWindowState(logService: ILogService, state: IWindowState, displays = screen.getAllDisplays()): IWindowState | undefined { + logService.trace(`window#validateWindowState: validating window state on ${displays.length} display(s)`, state); + + if ( + typeof state.x !== 'number' || + typeof state.y !== 'number' || + typeof state.width !== 'number' || + typeof state.height !== 'number' + ) { + logService.trace('window#validateWindowState: unexpected type of state values'); + + return undefined; + } + + if (state.width <= 0 || state.height <= 0) { + logService.trace('window#validateWindowState: unexpected negative values'); + + return undefined; + } + + // Single Monitor: be strict about x/y positioning + // macOS & Linux: these OS seem to be pretty good in ensuring that a window is never outside of it's bounds. + // Windows: it is possible to have a window with a size that makes it fall out of the window. our strategy + // is to try as much as possible to keep the window in the monitor bounds. we are not as strict as + // macOS and Linux and allow the window to exceed the monitor bounds as long as the window is still + // some pixels (128) visible on the screen for the user to drag it back. + if (displays.length === 1) { + const displayWorkingArea = getWorkingArea(displays[0]); + if (displayWorkingArea) { + logService.trace('window#validateWindowState: 1 monitor working area', displayWorkingArea); + + function ensureStateInDisplayWorkingArea(): void { + if (!state || typeof state.x !== 'number' || typeof state.y !== 'number' || !displayWorkingArea) { + return; + } + + if (state.x < displayWorkingArea.x) { + // prevent window from falling out of the screen to the left + state.x = displayWorkingArea.x; + } + + if (state.y < displayWorkingArea.y) { + // prevent window from falling out of the screen to the top + state.y = displayWorkingArea.y; + } + } + + // ensure state is not outside display working area (top, left) + ensureStateInDisplayWorkingArea(); + + if (state.width > displayWorkingArea.width) { + // prevent window from exceeding display bounds width + state.width = displayWorkingArea.width; + } + + if (state.height > displayWorkingArea.height) { + // prevent window from exceeding display bounds height + state.height = displayWorkingArea.height; + } + + if (state.x > (displayWorkingArea.x + displayWorkingArea.width - 128)) { + // prevent window from falling out of the screen to the right with + // 128px margin by positioning the window to the far right edge of + // the screen + state.x = displayWorkingArea.x + displayWorkingArea.width - state.width; + } + + if (state.y > (displayWorkingArea.y + displayWorkingArea.height - 128)) { + // prevent window from falling out of the screen to the bottom with + // 128px margin by positioning the window to the far bottom edge of + // the screen + state.y = displayWorkingArea.y + displayWorkingArea.height - state.height; + } + + // again ensure state is not outside display working area + // (it may have changed from the previous validation step) + ensureStateInDisplayWorkingArea(); + } + + return state; + } + + // Multi Montior (fullscreen): try to find the previously used display + if (state.display && state.mode === WindowMode.Fullscreen) { + const display = displays.find(d => d.id === state.display); + if (display && typeof display.bounds?.x === 'number' && typeof display.bounds?.y === 'number') { + logService.trace('window#validateWindowState: restoring fullscreen to previous display'); + + const defaults = defaultWindowState(WindowMode.Fullscreen); // make sure we have good values when the user restores the window + defaults.x = display.bounds.x; // carefull to use displays x/y position so that the window ends up on the correct monitor + defaults.y = display.bounds.y; + + return defaults; + } + } + + // Multi Monitor (non-fullscreen): ensure window is within display bounds + let display: Display | undefined; + let displayWorkingArea: Rectangle | undefined; + try { + display = screen.getDisplayMatching({ x: state.x, y: state.y, width: state.width, height: state.height }); + displayWorkingArea = getWorkingArea(display); + } catch (error) { + // Electron has weird conditions under which it throws errors + // e.g. https://github.com/microsoft/vscode/issues/100334 when + // large numbers are passed in + } + + if ( + display && // we have a display matching the desired bounds + displayWorkingArea && // we have valid working area bounds + state.x + state.width > displayWorkingArea.x && // prevent window from falling out of the screen to the left + state.y + state.height > displayWorkingArea.y && // prevent window from falling out of the screen to the top + state.x < displayWorkingArea.x + displayWorkingArea.width && // prevent window from falling out of the screen to the right + state.y < displayWorkingArea.y + displayWorkingArea.height // prevent window from falling out of the screen to the bottom + ) { + logService.trace('window#validateWindowState: multi-monitor working area', displayWorkingArea); + + return state; + } + + return undefined; + } + + function getWorkingArea(display: Display): Rectangle | undefined { + + // Prefer the working area of the display to account for taskbars on the + // desktop being positioned somewhere (https://github.com/microsoft/vscode/issues/50830). + // + // Linux X11 sessions sometimes report wrong display bounds, so we validate + // the reported sizes are positive. + if (display.workArea.width > 0 && display.workArea.height > 0) { + return display.workArea; + } + + if (display.bounds.width > 0 && display.bounds.height > 0) { + return display.bounds; + } + + return undefined; + } +} diff --git a/src/vs/workbench/contrib/url/browser/externalUriResolver.ts b/src/vs/workbench/contrib/url/browser/externalUriResolver.ts index 0e629b4714e..e579170fb4d 100644 --- a/src/vs/workbench/contrib/url/browser/externalUriResolver.ts +++ b/src/vs/workbench/contrib/url/browser/externalUriResolver.ts @@ -18,13 +18,13 @@ export class ExternalUriResolverContribution extends Disposable implements IWork ) { super(); - if (_workbenchEnvironmentService.options && _workbenchEnvironmentService.options.resolveExternalUri) { + if (_workbenchEnvironmentService.options?.resolveExternalUri) { this._register(_openerService.registerExternalUriResolver({ resolveExternalUri: async (resource) => { return { resolved: await _workbenchEnvironmentService.options!.resolveExternalUri!(resource), dispose: () => { - // TODO + // TODO@mjbvz - do we need to do anything here? } }; } diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts index 517f55d9dcc..2d9fd6c87c8 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts @@ -50,11 +50,11 @@ class WebviewPanelContribution extends Disposable implements IWorkbenchContribut group: IEditorGroup ): void { if (!(editor instanceof WebviewInput) || editor.typeId !== WebviewInput.typeId) { - return undefined; + return; } if (group.contains(editor)) { - return undefined; + return; } let previousGroup: IEditorGroup | undefined; @@ -67,7 +67,7 @@ class WebviewPanelContribution extends Disposable implements IWorkbenchContribut } if (!previousGroup) { - return undefined; + return; } previousGroup.closeEditor(editor); From 12904c651efbdaa1346bf38260a85320866d49d0 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 5 Feb 2024 22:02:02 +0100 Subject: [PATCH 0917/1897] Git - fix View Commit action from the timeline view (#204410) --- extensions/git/src/commands.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 049f38e44e0..a193a48ed78 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3859,11 +3859,11 @@ export class CommandCenter { const changes = await repository.diffBetween(commitParentId, commit.hash); const title = `${item.shortRef} - ${commit.message}`; - const multiDiffSourceUri = toGitUri(Uri.file(repository.root), item.ref, { scheme: 'git-commit' }); + const multiDiffSourceUri = toGitUri(Uri.file(repository.root), commit.hash, { scheme: 'git-commit' }); const resources: { originalUri: Uri | undefined; modifiedUri: Uri | undefined }[] = []; for (const change of changes) { - resources.push(toMultiFileDiffEditorUris(change, item.previousRef, item.ref)); + resources.push(toMultiFileDiffEditorUris(change, commitParentId, commit.hash)); } return { From 4d3db486c280bc24f7876fe77394a342a7b47c29 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 5 Feb 2024 13:34:45 -0800 Subject: [PATCH 0918/1897] Enable explorer pasting of files without paths (#204052) Fixes #204051 This enables pasting clipboard files that don't have a `.path`. This enables: - Pasting image data into the explorer - Pasting files into the explorer on web --- .../contrib/files/browser/fileActions.ts | 178 +++++++++++------- 1 file changed, 114 insertions(+), 64 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index cb64ae02fb4..0b92df85a34 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -59,6 +59,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { ILocalizedString } from 'vs/platform/action/common/action'; +import { VSBuffer } from 'vs/base/common/buffer'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize2('newFile', "New File..."); @@ -308,11 +309,11 @@ export async function findValidPasteFileTarget( fileService: IFileService, dialogService: IDialogService, targetFolder: ExplorerItem, - fileToPaste: { resource: URI; isDirectory?: boolean; allowOverwrite: boolean }, + fileToPaste: { resource: URI | string; isDirectory?: boolean; allowOverwrite: boolean }, incrementalNaming: 'simple' | 'smart' | 'disabled' ): Promise { - let name = resources.basenameOrAuthority(fileToPaste.resource); + let name = typeof fileToPaste.resource === 'string' ? fileToPaste.resource : resources.basenameOrAuthority(fileToPaste.resource); let candidate = resources.joinPath(targetFolder.resource, name); // In the disabled case we must ask if it's ok to overwrite the file if it exists @@ -1098,11 +1099,11 @@ export const pasteFileHandler = async (accessor: ServicesAccessor, fileList?: Fi const toPaste = await getFilesToPaste(fileList, clipboardService); - if (confirmPasteNative && toPaste?.length >= 1) { - const message = toPaste.length > 1 ? - nls.localize('confirmMultiPasteNative', "Are you sure you want to paste the following {0} items?", toPaste.length) : - nls.localize('confirmPasteNative', "Are you sure you want to paste '{0}'?", basename(toPaste[0].fsPath)); - const detail = toPaste.length > 1 ? getFileNamesMessage(toPaste) : undefined; + if (confirmPasteNative && toPaste.files.length >= 1) { + const message = toPaste.files.length > 1 ? + nls.localize('confirmMultiPasteNative', "Are you sure you want to paste the following {0} items?", toPaste.files.length) : + nls.localize('confirmPasteNative', "Are you sure you want to paste '{0}'?", basename(toPaste.type === 'paths' ? toPaste.files[0].fsPath : toPaste.files[0].name)); + const detail = toPaste.files.length > 1 ? getFileNamesMessage(toPaste.files.map(item => toPaste.type === 'paths' ? item.path : (item as File).name)) : undefined; const confirmation = await dialogService.confirm({ message, detail, @@ -1131,67 +1132,94 @@ export const pasteFileHandler = async (accessor: ServicesAccessor, fileList?: Fi } try { - // Check if target is ancestor of pasted folder - const sourceTargetPairs = coalesce(await Promise.all(toPaste.map(async fileToPaste => { + let targets: URI[] = []; - if (element.resource.toString() !== fileToPaste.toString() && resources.isEqualOrParent(element.resource, fileToPaste)) { - throw new Error(nls.localize('fileIsAncestor', "File to paste is an ancestor of the destination folder")); - } - const fileToPasteStat = await fileService.stat(fileToPaste); + if (toPaste.type === 'paths') { // Pasting from files on disk - // Find target - let target: ExplorerItem; - if (uriIdentityService.extUri.isEqual(element.resource, fileToPaste)) { - target = element.parent!; - } else { - target = element.isDirectory ? element : element.parent!; + // Check if target is ancestor of pasted folder + const sourceTargetPairs = coalesce(await Promise.all(toPaste.files.map(async fileToPaste => { + if (element.resource.toString() !== fileToPaste.toString() && resources.isEqualOrParent(element.resource, fileToPaste)) { + throw new Error(nls.localize('fileIsAncestor', "File to paste is an ancestor of the destination folder")); + } + const fileToPasteStat = await fileService.stat(fileToPaste); + + // Find target + let target: ExplorerItem; + if (uriIdentityService.extUri.isEqual(element.resource, fileToPaste)) { + target = element.parent!; + } else { + target = element.isDirectory ? element : element.parent!; + } + + const targetFile = await findValidPasteFileTarget( + explorerService, + fileService, + dialogService, + target, + { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove || incrementalNaming === 'disabled' }, + incrementalNaming + ); + + if (!targetFile) { + return undefined; + } + + return { source: fileToPaste, target: targetFile }; + }))); + + if (sourceTargetPairs.length >= 1) { + // Move/Copy File + if (pasteShouldMove) { + const resourceFileEdits = sourceTargetPairs.map(pair => new ResourceFileEdit(pair.source, pair.target, { overwrite: incrementalNaming === 'disabled' })); + const options = { + confirmBeforeUndo: configurationService.getValue().explorer.confirmUndo === UndoConfirmLevel.Verbose, + progressLabel: sourceTargetPairs.length > 1 ? nls.localize({ key: 'movingBulkEdit', comment: ['Placeholder will be replaced by the number of files being moved'] }, "Moving {0} files", sourceTargetPairs.length) + : nls.localize({ key: 'movingFileBulkEdit', comment: ['Placeholder will be replaced by the name of the file moved.'] }, "Moving {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)), + undoLabel: sourceTargetPairs.length > 1 ? nls.localize({ key: 'moveBulkEdit', comment: ['Placeholder will be replaced by the number of files being moved'] }, "Move {0} files", sourceTargetPairs.length) + : nls.localize({ key: 'moveFileBulkEdit', comment: ['Placeholder will be replaced by the name of the file moved.'] }, "Move {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)) + }; + await explorerService.applyBulkEdit(resourceFileEdits, options); + } else { + const resourceFileEdits = sourceTargetPairs.map(pair => new ResourceFileEdit(pair.source, pair.target, { copy: true, overwrite: incrementalNaming === 'disabled' })); + await applyCopyResourceEdit(sourceTargetPairs.map(pair => pair.target), resourceFileEdits); + } } - const targetFile = await findValidPasteFileTarget( - explorerService, - fileService, - dialogService, - target, - { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove || incrementalNaming === 'disabled' }, - incrementalNaming - ); + targets = sourceTargetPairs.map(pair => pair.target); - if (!targetFile) { - return undefined; - } + } else { // Pasting from file data + const targetAndEdits = coalesce(await Promise.all(toPaste.files.map(async file => { + const target = element.isDirectory ? element : element.parent!; - return { source: fileToPaste, target: targetFile }; - }))); - - if (sourceTargetPairs.length >= 1) { - // Move/Copy File - if (pasteShouldMove) { - const resourceFileEdits = sourceTargetPairs.map(pair => new ResourceFileEdit(pair.source, pair.target, { overwrite: incrementalNaming === 'disabled' })); - const options = { - confirmBeforeUndo: configurationService.getValue().explorer.confirmUndo === UndoConfirmLevel.Verbose, - progressLabel: sourceTargetPairs.length > 1 ? nls.localize({ key: 'movingBulkEdit', comment: ['Placeholder will be replaced by the number of files being moved'] }, "Moving {0} files", sourceTargetPairs.length) - : nls.localize({ key: 'movingFileBulkEdit', comment: ['Placeholder will be replaced by the name of the file moved.'] }, "Moving {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)), - undoLabel: sourceTargetPairs.length > 1 ? nls.localize({ key: 'moveBulkEdit', comment: ['Placeholder will be replaced by the number of files being moved'] }, "Move {0} files", sourceTargetPairs.length) - : nls.localize({ key: 'moveFileBulkEdit', comment: ['Placeholder will be replaced by the name of the file moved.'] }, "Move {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)) + const targetFile = await findValidPasteFileTarget( + explorerService, + fileService, + dialogService, + target, + { resource: file.name, isDirectory: false, allowOverwrite: pasteShouldMove || incrementalNaming === 'disabled' }, + incrementalNaming + ); + if (!targetFile) { + return; + } + return { + target: targetFile, + edit: new ResourceFileEdit(undefined, targetFile, { + overwrite: incrementalNaming === 'disabled', + contents: (async () => VSBuffer.wrap(new Uint8Array(await file.arrayBuffer())))(), + }) }; - await explorerService.applyBulkEdit(resourceFileEdits, options); - } else { - const resourceFileEdits = sourceTargetPairs.map(pair => new ResourceFileEdit(pair.source, pair.target, { copy: true, overwrite: incrementalNaming === 'disabled' })); - const undoLevel = configurationService.getValue().explorer.confirmUndo; - const options = { - confirmBeforeUndo: undoLevel === UndoConfirmLevel.Default || undoLevel === UndoConfirmLevel.Verbose, - progressLabel: sourceTargetPairs.length > 1 ? nls.localize({ key: 'copyingBulkEdit', comment: ['Placeholder will be replaced by the number of files being copied'] }, "Copying {0} files", sourceTargetPairs.length) - : nls.localize({ key: 'copyingFileBulkEdit', comment: ['Placeholder will be replaced by the name of the file copied.'] }, "Copying {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)), - undoLabel: sourceTargetPairs.length > 1 ? nls.localize({ key: 'copyBulkEdit', comment: ['Placeholder will be replaced by the number of files being copied'] }, "Paste {0} files", sourceTargetPairs.length) - : nls.localize({ key: 'copyFileBulkEdit', comment: ['Placeholder will be replaced by the name of the file copied.'] }, "Paste {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)) - }; - await explorerService.applyBulkEdit(resourceFileEdits, options); - } + }))); - const pair = sourceTargetPairs[0]; - await explorerService.select(pair.target); - if (sourceTargetPairs.length === 1) { - const item = explorerService.findClosest(pair.target); + await applyCopyResourceEdit(targetAndEdits.map(pair => pair.target), targetAndEdits.map(pair => pair.edit)); + targets = targetAndEdits.map(pair => pair.target); + } + + if (targets.length) { + const firstTarget = targets[0]; + await explorerService.select(firstTarget); + if (targets.length === 1) { + const item = explorerService.findClosest(firstTarget); if (item && !item.isDirectory) { await editorService.openEditor({ resource: item.resource, options: { pinned: true, preserveFocus: true } }); } @@ -1206,15 +1234,37 @@ export const pasteFileHandler = async (accessor: ServicesAccessor, fileList?: Fi pasteShouldMove = false; } } + + async function applyCopyResourceEdit(targets: readonly URI[], resourceFileEdits: ResourceFileEdit[]) { + const undoLevel = configurationService.getValue().explorer.confirmUndo; + const options = { + confirmBeforeUndo: undoLevel === UndoConfirmLevel.Default || undoLevel === UndoConfirmLevel.Verbose, + progressLabel: targets.length > 1 ? nls.localize({ key: 'copyingBulkEdit', comment: ['Placeholder will be replaced by the number of files being copied'] }, "Copying {0} files", targets.length) + : nls.localize({ key: 'copyingFileBulkEdit', comment: ['Placeholder will be replaced by the name of the file copied.'] }, "Copying {0}", resources.basenameOrAuthority(targets[0])), + undoLabel: targets.length > 1 ? nls.localize({ key: 'copyBulkEdit', comment: ['Placeholder will be replaced by the number of files being copied'] }, "Paste {0} files", targets.length) + : nls.localize({ key: 'copyFileBulkEdit', comment: ['Placeholder will be replaced by the name of the file copied.'] }, "Paste {0}", resources.basenameOrAuthority(targets[0])) + }; + await explorerService.applyBulkEdit(resourceFileEdits, options); + } }; -async function getFilesToPaste(fileList: FileList | undefined, clipboardService: IClipboardService): Promise { +type FilesToPaste = + | { type: 'paths'; files: URI[] } + | { type: 'data'; files: File[] }; + +async function getFilesToPaste(fileList: FileList | undefined, clipboardService: IClipboardService): Promise { if (fileList && fileList.length > 0) { - // with a `fileList` we support natively pasting files from clipboard - return [...fileList].filter(file => !!file.path && isAbsolute(file.path)).map(file => URI.file(file.path)); + // with a `fileList` we support natively pasting file from disk from clipboard + const resources = [...fileList].filter(file => !!file.path && isAbsolute(file.path)).map(file => URI.file(file.path)); + if (resources.length) { + return { type: 'paths', files: resources, }; + } + + // Support pasting files that we can't read from disk + return { type: 'data', files: [...fileList].filter(file => !file.path) }; } else { // otherwise we fallback to reading resources from our clipboard service - return resources.distinctParents(await clipboardService.readResources(), resource => resource); + return { type: 'paths', files: resources.distinctParents(await clipboardService.readResources(), resource => resource) }; } } From 1f507c54749a2fc2540bc2c5bb0ee385bf08a02c Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:02:29 -0800 Subject: [PATCH 0919/1897] adds handling for when we have ranges and diagnostics in quick fixes (#204421) adds handling for when we have ranges and diagnostics --- .../contrib/codeAction/browser/codeActionController.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index 21f1faa46a0..a29f4334e1f 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -308,7 +308,10 @@ export class CodeActionController extends Disposable implements IEditorContribut const diagnostics = action.action.diagnostics; currentDecorations.clear(); if (ranges && ranges.length > 0) { - const decorations: IModelDeltaDecoration[] = ranges.map(range => ({ range, options: CodeActionController.DECORATION })); + // Handles case for `fix all` where there are multiple diagnostics. + const decorations: IModelDeltaDecoration[] = (diagnostics && diagnostics?.length > 1) + ? diagnostics.map(diagnostic => ({ range: diagnostic, options: CodeActionController.DECORATION })) + : ranges.map(range => ({ range, options: CodeActionController.DECORATION })); currentDecorations.set(decorations); } else if (diagnostics && diagnostics.length > 0) { const decorations: IModelDeltaDecoration[] = diagnostics.map(diagnostic => ({ range: diagnostic, options: CodeActionController.DECORATION })); From 9db238ecdc907c215d263ab7d81c3f5958e1299c Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 5 Feb 2024 23:09:50 +0100 Subject: [PATCH 0920/1897] tomorrow night blue shows keyword color wrong (#204423) --- .../themes/tomorrow-night-blue-color-theme.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json index 28fb8cd7f28..89bc8acd36f 100644 --- a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json +++ b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json @@ -140,10 +140,7 @@ }, { "name": "Keyword, Storage", - "scope": [ - "keyword, storage, storage.type, entity.name.tag.css", - "entity.name.tag.less" - ], + "scope": "keyword, storage, storage.type, entity.name.tag.css, entity.name.tag.less", "settings": { "fontStyle": "", "foreground": "#EBBBFF" From 9f50c3dc2a67e8f54e88d3190c6266aebbe8ed17 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:13:18 -0800 Subject: [PATCH 0921/1897] groundwork for code action ranges for quick fix (#204191) * added in config * fix balance between diagnostic and range * cleanup --- extensions/typescript-language-features/package.json | 3 ++- .../src/languageFeatures/quickFix.ts | 5 +++++ extensions/typescript-language-features/tsconfig.json | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 9480d471455..9a5f8ff1f86 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -11,7 +11,8 @@ "workspaceTrust", "multiDocumentHighlightProvider", "mappedEditsProvider", - "codeActionAI" + "codeActionAI", + "codeActionRanges" ], "capabilities": { "virtualWorkspaces": { diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index 26a658d9116..afc489c0521 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -331,6 +331,7 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider Date: Mon, 5 Feb 2024 15:15:53 -0800 Subject: [PATCH 0922/1897] Fix $window usage in notebook editors (including diff and iw) (#204403) * fix #203672. * Make notebook options aware of target window. --- .../interactive/browser/interactiveEditor.ts | 2 +- .../browser/diff/notebookDiffEditor.ts | 5 +- .../notebook/browser/notebookEditorWidget.ts | 6 +- .../notebook/browser/notebookOptions.ts | 85 +++++++++++++------ .../browser/services/notebookServiceImpl.ts | 45 ---------- .../browser/viewModel/baseCellViewModel.ts | 4 +- .../test/browser/notebookViewModel.test.ts | 4 +- .../test/browser/testNotebookEditor.ts | 11 ++- 8 files changed, 82 insertions(+), 80 deletions(-) diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index 4ec8f8acc6d..ffd76aca945 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -160,7 +160,7 @@ export class InteractiveEditor extends EditorPane { this._editorOptions = this._computeEditorOptions(); } })); - this._notebookOptions = new NotebookOptions(configurationService, notebookExecutionStateService, true, { cellToolbarInteraction: 'hover', globalToolbar: true, stickyScrollEnabled: false, dragAndDropEnabled: false }); + this._notebookOptions = new NotebookOptions(DOM.getWindowById(this.group?.windowId, true).window ?? mainWindow, configurationService, notebookExecutionStateService, codeEditorService, true, { cellToolbarInteraction: 'hover', globalToolbar: true, stickyScrollEnabled: false, dragAndDropEnabled: false }); this._editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY); codeEditorService.registerDecorationType('interactive-decoration', DECORATION_KEY, {}); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts index 5e57e97606f..e2de1a22096 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts @@ -47,6 +47,8 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { cellIndexesToRanges, cellRangesToIndexes } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { NotebookDiffOverviewRuler } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler'; import { registerZIndex, ZIndex } from 'vs/platform/layout/browser/zIndexRegistry'; +import { mainWindow } from 'vs/base/browser/window'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; const $ = DOM.$; @@ -149,9 +151,10 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD @ITelemetryService telemetryService: ITelemetryService, @IStorageService storageService: IStorageService, @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService, + @ICodeEditorService codeEditorService: ICodeEditorService ) { super(NotebookTextDiffEditor.ID, telemetryService, themeService, storageService); - this._notebookOptions = new NotebookOptions(this.configurationService, notebookExecutionStateService, false); + this._notebookOptions = new NotebookOptions(DOM.getWindowById(this.group?.windowId, true).window ?? mainWindow, this.configurationService, notebookExecutionStateService, codeEditorService, false); this._register(this._notebookOptions); this._revealFirst = true; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 8ac1e14d8e2..0719ee2ca4f 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -102,6 +102,7 @@ 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'; import { PixelRatio } from 'vs/base/browser/pixelRatio'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; const $ = DOM.$; @@ -300,7 +301,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService, @IEditorProgressService private editorProgressService: IEditorProgressService, @INotebookLoggingService readonly logService: INotebookLoggingService, - @IKeybindingService readonly keybindingService: IKeybindingService + @IKeybindingService readonly keybindingService: IKeybindingService, + @ICodeEditorService codeEditorService: ICodeEditorService ) { super(); @@ -309,7 +311,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this.isEmbedded = creationOptions.isEmbedded ?? false; this._readOnly = creationOptions.isReadOnly ?? false; - this._notebookOptions = creationOptions.options ?? new NotebookOptions(this.configurationService, notebookExecutionStateService, this._readOnly); + this._notebookOptions = creationOptions.options ?? new NotebookOptions(this.creationOptions?.codeWindow ?? mainWindow, this.configurationService, notebookExecutionStateService, codeEditorService, this._readOnly); this._register(this._notebookOptions); this._viewContext = new ViewContext( this._notebookOptions, diff --git a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts index 2cdbce9fad3..c4356bd6ed0 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts @@ -4,11 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { PixelRatio } from 'vs/base/browser/pixelRatio'; -import { $window } from 'vs/base/browser/window'; +import { CodeWindow } from 'vs/base/browser/window'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { FontMeasurements } from 'vs/editor/browser/config/fontMeasurements'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { ConfigurationTarget, IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -17,20 +18,6 @@ import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/co const SCROLLABLE_ELEMENT_PADDING_TOP = 18; -let EDITOR_TOP_PADDING = 12; -const editorTopPaddingChangeEmitter = new Emitter(); - -const EditorTopPaddingChangeEvent = editorTopPaddingChangeEmitter.event; - -export function updateEditorTopPadding(top: number) { - EDITOR_TOP_PADDING = top; - editorTopPaddingChangeEmitter.fire(); -} - -export function getEditorTopPadding() { - return EDITOR_TOP_PADDING; -} - export const OutputInnerContainerTopPadding = 4; export interface NotebookDisplayOptions { @@ -137,10 +124,13 @@ export class NotebookOptions extends Disposable { private _layoutConfiguration: NotebookLayoutConfiguration & NotebookDisplayOptions; protected readonly _onDidChangeOptions = this._register(new Emitter()); readonly onDidChangeOptions = this._onDidChangeOptions.event; + private _editorTopPadding: number = 12; constructor( + readonly targetWindow: CodeWindow, private readonly configurationService: IConfigurationService, private readonly notebookExecutionStateService: INotebookExecutionStateService, + private readonly codeEditorService: ICodeEditorService, private isReadonly: boolean, private readonly overrides?: { cellToolbarInteraction: string; globalToolbar: boolean; stickyScrollEnabled: boolean; dragAndDropEnabled: boolean } ) { @@ -207,6 +197,8 @@ export class NotebookOptions extends Disposable { const outputLineLimit = this.configurationService.getValue(NotebookSetting.textOutputLineLimit) ?? 30; const linkifyFilePaths = this.configurationService.getValue(NotebookSetting.LinkifyOutputFilePaths) ?? true; + const editorTopPadding = this._computeEditorTopPadding(); + this._layoutConfiguration = { ...(compactView ? compactConfigConstants : defaultConfigConstants), cellTopMargin: 6, @@ -218,7 +210,7 @@ export class NotebookOptions extends Disposable { // bottomToolbarHeight: bottomToolbarHeight, // bottomToolbarGap: bottomToolbarGap, editorToolbarHeight: 0, - editorTopPadding: EDITOR_TOP_PADDING, + editorTopPadding: editorTopPadding, editorBottomPadding: 4, editorBottomPaddingWithoutStatusBar: 12, collapsedIndicatorHeight: 28, @@ -254,13 +246,6 @@ export class NotebookOptions extends Disposable { this._register(this.configurationService.onDidChangeConfiguration(e => { this._updateConfiguration(e); })); - - this._register(EditorTopPaddingChangeEvent(() => { - const configuration = Object.assign({}, this._layoutConfiguration); - configuration.editorTopPadding = getEditorTopPadding(); - this._layoutConfiguration = configuration; - this._onDidChangeOptions.fire({ editorTopPadding: true }); - })); } updateOptions(isReadonly: boolean) { @@ -278,6 +263,56 @@ export class NotebookOptions extends Disposable { } } + private _computeEditorTopPadding(): number { + let decorationTriggeredAdjustment = false; + + const updateEditorTopPadding = (top: number) => { + this._editorTopPadding = top; + const configuration = Object.assign({}, this._layoutConfiguration); + configuration.editorTopPadding = this._editorTopPadding; + this._layoutConfiguration = configuration; + this._onDidChangeOptions.fire({ editorTopPadding: true }); + }; + + const decorationCheckSet = new Set(); + const onDidAddDecorationType = (e: string) => { + if (decorationTriggeredAdjustment) { + return; + } + + if (decorationCheckSet.has(e)) { + return; + } + + const options = this.codeEditorService.resolveDecorationOptions(e, true); + if (options.afterContentClassName || options.beforeContentClassName) { + const cssRules = this.codeEditorService.resolveDecorationCSSRules(e); + if (cssRules !== null) { + for (let i = 0; i < cssRules.length; i++) { + // The following ways to index into the list are equivalent + if ( + ((cssRules[i] as CSSStyleRule).selectorText.endsWith('::after') || (cssRules[i] as CSSStyleRule).selectorText.endsWith('::after')) + && (cssRules[i] as CSSStyleRule).cssText.indexOf('top:') > -1 + ) { + // there is a `::before` or `::after` text decoration whose position is above or below current line + // we at least make sure that the editor top padding is at least one line + const editorOptions = this.configurationService.getValue('editor'); + updateEditorTopPadding(BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(this.targetWindow).value).lineHeight + 2); + decorationTriggeredAdjustment = true; + break; + } + } + } + } + + decorationCheckSet.add(e); + }; + this._register(this.codeEditorService.onDecorationTypeRegistered(onDidAddDecorationType)); + this.codeEditorService.listDecorationTypes().forEach(onDidAddDecorationType); + + return this._editorTopPadding; + } + private _migrateDeprecatedSetting(deprecatedKey: string, key: string): void { const deprecatedSetting = this.configurationService.inspect(deprecatedKey); @@ -318,7 +353,7 @@ export class NotebookOptions extends Disposable { if (lineHeight === 0) { // use editor line height const editorOptions = this.configurationService.getValue('editor'); - const fontInfo = FontMeasurements.readFontInfo($window, BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance($window).value)); + const fontInfo = FontMeasurements.readFontInfo(this.targetWindow, BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(this.targetWindow).value)); lineHeight = fontInfo.lineHeight; } else if (lineHeight < minimumLineHeight) { // Values too small to be line heights in pixels are in ems. @@ -698,7 +733,7 @@ export class NotebookOptions extends Disposable { computeEditorPadding(internalMetadata: NotebookCellInternalMetadata, cellUri: URI) { return { - top: getEditorTopPadding(), + top: this._editorTopPadding, bottom: this.statusBarIsVisible(internalMetadata, cellUri) ? this._layoutConfiguration.editorBottomPadding : this._layoutConfiguration.editorBottomPaddingWithoutStatusBar diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts index a86b7a9701a..93d8186424b 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts @@ -16,9 +16,6 @@ import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; import { isDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; @@ -34,7 +31,6 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellUri, NotebookSetting, INotebookContributionData, INotebookExclusiveDocumentFilter, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, MimeTypeDisplayOrder, NotebookData, NotebookEditorPriority, NotebookRendererMatch, NOTEBOOK_DISPLAY_ORDER, RENDERER_EQUIVALENT_EXTENSIONS, RENDERER_NOT_AVAILABLE, TransientOptions, NotebookExtensionDescription, INotebookStaticPreloadInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; -import { updateEditorTopPadding } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; import { NotebookOutputRendererInfo, NotebookStaticPreloadInfo as NotebookStaticPreloadInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { INotebookSerializer, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService'; @@ -44,8 +40,6 @@ import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/ext import { InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { INotebookDocument, INotebookDocumentService } from 'vs/workbench/services/notebook/common/notebookDocumentService'; -import { $window } from 'vs/base/browser/window'; -import { PixelRatio } from 'vs/base/browser/pixelRatio'; export class NotebookProviderInfoStore extends Disposable { @@ -490,8 +484,6 @@ export class NotebookService extends Disposable implements INotebookService { @IConfigurationService private readonly _configurationService: IConfigurationService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, - @IConfigurationService private readonly configurationService: IConfigurationService, @IStorageService private readonly _storageService: IStorageService, @INotebookDocumentService private readonly _notebookDocumentService: INotebookDocumentService ) { @@ -580,43 +572,6 @@ export class NotebookService extends Disposable implements INotebookService { updateOrder(); })); - let decorationTriggeredAdjustment = false; - const decorationCheckSet = new Set(); - const onDidAddDecorationType = (e: string) => { - if (decorationTriggeredAdjustment) { - return; - } - - if (decorationCheckSet.has(e)) { - return; - } - - const options = this._codeEditorService.resolveDecorationOptions(e, true); - if (options.afterContentClassName || options.beforeContentClassName) { - const cssRules = this._codeEditorService.resolveDecorationCSSRules(e); - if (cssRules !== null) { - for (let i = 0; i < cssRules.length; i++) { - // The following ways to index into the list are equivalent - if ( - ((cssRules[i] as CSSStyleRule).selectorText.endsWith('::after') || (cssRules[i] as CSSStyleRule).selectorText.endsWith('::after')) - && (cssRules[i] as CSSStyleRule).cssText.indexOf('top:') > -1 - ) { - // there is a `::before` or `::after` text decoration whose position is above or below current line - // we at least make sure that the editor top padding is at least one line - const editorOptions = this.configurationService.getValue('editor'); - updateEditorTopPadding(BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance($window).value).lineHeight + 2); - decorationTriggeredAdjustment = true; - break; - } - } - } - } - - decorationCheckSet.add(e); - }; - this._register(this._codeEditorService.onDecorationTypeRegistered(onDidAddDecorationType)); - this._codeEditorService.listDecorationTypes().forEach(onDidAddDecorationType); - this._memento = new Memento(NotebookService._storageNotebookViewTypeProvider, this._storageService); this._viewTypeCache = this._memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index a95fe0b23da..548a5a7c043 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -23,7 +23,7 @@ import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/bro import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind, INotebookCellStatusBarItem, INotebookSearchOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { getEditorTopPadding, NotebookOptionsChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; +import { NotebookOptionsChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; export abstract class BaseCellViewModel extends Disposable { @@ -246,7 +246,7 @@ export abstract class BaseCellViewModel extends Disposable { viewState: { scrollLeft: 0, firstPosition: { lineNumber: 1, column: 1 }, - firstPositionDeltaTop: getEditorTopPadding() + firstPositionDeltaTop: this._viewContext.notebookOptions.getLayoutConfiguration().editorTopPadding } }); } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts index 13dc30523c7..b785ef3fac6 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts @@ -29,6 +29,8 @@ import { NotebookEditorTestModel, setupInstantiationService, withTestNotebook } import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { IBaseCellEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { mainWindow } from 'vs/base/browser/window'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; suite('NotebookViewModel', () => { ensureNoDisposablesAreLeakedInTestSuite(); @@ -61,7 +63,7 @@ suite('NotebookViewModel', () => { test('ctor', function () { const notebook = new NotebookTextModel('notebook', URI.parse('test'), [], {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false, cellContentMetadata: {} }, undoRedoService, modelService, languageService); const model = new NotebookEditorTestModel(notebook); - const options = new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false); + const options = new NotebookOptions(mainWindow, instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), instantiationService.get(ICodeEditorService), false); const eventDispatcher = new NotebookEventDispatcher(); const viewContext = new ViewContext(options, eventDispatcher, () => ({} as IBaseCellEditorOptions)); const viewModel = new NotebookViewModel('notebook', model.notebook, viewContext, null, { isReadOnly: false }, instantiationService, bulkEditService, undoRedoService, textModelService, notebookExecutionStateService); diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index 74d22fc7bf2..dbcfcbe1a41 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -62,6 +62,9 @@ import { TestLayoutService } from 'vs/workbench/test/browser/workbenchTestServic import { TestStorageService, TestWorkspaceTrustRequestService } from 'vs/workbench/test/common/workbenchTestServices'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { EditorFontLigatures, EditorFontVariations } from 'vs/editor/common/config/editorOptions'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { mainWindow } from 'vs/base/browser/window'; +import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices'; export class TestCell extends NotebookCellTextModel { constructor( @@ -175,10 +178,11 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi export function setupInstantiationService(disposables: DisposableStore) { const instantiationService = disposables.add(new TestInstantiationService()); + const testThemeService = new TestThemeService(); instantiationService.stub(ILanguageService, disposables.add(new LanguageService())); instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); instantiationService.stub(IConfigurationService, new TestConfigurationService()); - instantiationService.stub(IThemeService, new TestThemeService()); + instantiationService.stub(IThemeService, testThemeService); instantiationService.stub(ILanguageConfigurationService, disposables.add(new TestLanguageConfigurationService())); instantiationService.stub(IModelService, disposables.add(instantiationService.createInstance(ModelService))); instantiationService.stub(ITextModelService, disposables.add(instantiationService.createInstance(TextModelResolverService))); @@ -192,6 +196,7 @@ export function setupInstantiationService(disposables: DisposableStore) { instantiationService.stub(INotebookExecutionStateService, new TestNotebookExecutionStateService()); instantiationService.stub(IKeybindingService, new MockKeybindingService()); instantiationService.stub(INotebookCellStatusBarService, disposables.add(new NotebookCellStatusBarService())); + instantiationService.stub(ICodeEditorService, disposables.add(new TestCodeEditorService(testThemeService))); return instantiationService; } @@ -211,7 +216,7 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic }), {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, cellContentMetadata: {}, transientOutputs: false })); const model = disposables.add(new NotebookEditorTestModel(notebook)); - const notebookOptions = disposables.add(new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false)); + const notebookOptions = disposables.add(new NotebookOptions(mainWindow, instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), instantiationService.get(ICodeEditorService), false)); const viewContext = new ViewContext(notebookOptions, disposables.add(new NotebookEventDispatcher()), () => ({} as IBaseCellEditorOptions)); const viewModel: NotebookViewModel = disposables.add(instantiationService.createInstance(NotebookViewModel, viewType, model.notebook, viewContext, null, { isReadOnly: false })); @@ -435,7 +440,7 @@ export function createNotebookCellList(instantiationService: TestInstantiationSe }; const notebookOptions = !!viewContext ? viewContext.notebookOptions - : disposables.add(new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false)); + : disposables.add(new NotebookOptions(mainWindow, instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), instantiationService.get(ICodeEditorService), false)); const cellList: NotebookCellList = disposables.add(instantiationService.createInstance( NotebookCellList, 'NotebookCellList', From e7b03742b53946200bf054c7ed0105247f432076 Mon Sep 17 00:00:00 2001 From: rzvc Date: Tue, 6 Feb 2024 05:52:06 +0200 Subject: [PATCH 0923/1897] Fix docblock expansion in TS, when asterisk not preceded by a space (#204400) Fixes issue #193263. --- extensions/typescript-basics/language-configuration.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/typescript-basics/language-configuration.json b/extensions/typescript-basics/language-configuration.json index 03f06fa04d5..5f1fb10041e 100644 --- a/extensions/typescript-basics/language-configuration.json +++ b/extensions/typescript-basics/language-configuration.json @@ -136,7 +136,7 @@ }, // e.g. * ...| or */| or *-----*/| "unIndentedLinePattern": { - "pattern": "^(\\t|[ ])*[ ]\\*[^/]*\\*/\\s*$|^(\\t|[ ])*[ ]\\*/\\s*$|^(\\t|[ ])*[ ]\\*([ ]([^\\*]|\\*(?!/))*)?$" + "pattern": "^(\\t|[ ])*[ ]\\*[^/]*\\*/\\s*$|^(\\t|[ ])*[ ]\\*/\\s*$|^(\\t|[ ])*\\*([ ]([^\\*]|\\*(?!/))*)?$" } }, "onEnterRules": [ @@ -166,7 +166,7 @@ { // e.g. * ...| "beforeText": { - "pattern": "^(\\t|[ ])*[ ]\\*([ ]([^\\*]|\\*(?!/))*)?$" + "pattern": "^(\\t|[ ])*\\*([ ]([^\\*]|\\*(?!/))*)?$" }, "previousLineText": { "pattern": "(?=^(\\s*(/\\*\\*|\\*)).*)(?=(?!(\\s*\\*/)))" From 0786b4d2fbe5e68ea282451669084332f43c2ef3 Mon Sep 17 00:00:00 2001 From: Yves Daaboul <98392882+perplexyves@users.noreply.github.com> Date: Tue, 6 Feb 2024 05:59:23 +0200 Subject: [PATCH 0924/1897] fix(193523): JSDoc optional parameters don't display in functions (#202963) * Issue193523: Brought back optional params to autocompletion preview * #193523: Hided end-of-list optional params * #193523: Added unit tests * #193523: Fixed integration tests * Update extensions/typescript-language-features/src/languageFeatures/util/snippetForFunctionCall.ts --------- Co-authored-by: Matt Bierner --- .../util/snippetForFunctionCall.ts | 12 ++++++++ .../src/test/unit/functionCallSnippet.test.ts | 30 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/extensions/typescript-language-features/src/languageFeatures/util/snippetForFunctionCall.ts b/extensions/typescript-language-features/src/languageFeatures/util/snippetForFunctionCall.ts index def0895703f..a00d04e7a55 100644 --- a/extensions/typescript-language-features/src/languageFeatures/util/snippetForFunctionCall.ts +++ b/extensions/typescript-language-features/src/languageFeatures/util/snippetForFunctionCall.ts @@ -50,6 +50,7 @@ function getParameterListParts( displayParts: ReadonlyArray ): ParamterListParts { const parts: Proto.SymbolDisplayPart[] = []; + let optionalParams: Proto.SymbolDisplayPart[] = []; let isInMethod = false; let hasOptionalParameters = false; let parenCount = 0; @@ -75,6 +76,17 @@ function getParameterListParts( const nameIsFollowedByOptionalIndicator = next && next.text === '?'; // Skip this parameter const nameIsThis = part.text === 'this'; + + /* Add optional param to temp array. Once a non-optional param is encountered, + this means that previous optional params were mid-list ones, thus they should + be displayed */ + if (nameIsFollowedByOptionalIndicator) { + optionalParams.push(part); + } else { + parts.push(...optionalParams); + optionalParams = []; + } + if (!nameIsFollowedByOptionalIndicator && !nameIsThis) { parts.push(part); } diff --git a/extensions/typescript-language-features/src/test/unit/functionCallSnippet.test.ts b/extensions/typescript-language-features/src/test/unit/functionCallSnippet.test.ts index ccda34396f3..00f006a05a4 100644 --- a/extensions/typescript-language-features/src/test/unit/functionCallSnippet.test.ts +++ b/extensions/typescript-language-features/src/test/unit/functionCallSnippet.test.ts @@ -134,4 +134,34 @@ suite('typescript function call snippets', () => { ).snippet.value, 'foobar(${1:param})$0'); }); + + test('Should not skip mid-list optional parameter', async () => { + assert.strictEqual( + snippetForFunctionCall( + { label: 'foobar', }, + [{ "text": "function", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "foobar", "kind": "functionName" }, { "text": "(", "kind": "punctuation" }, { "text": "alpha", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "string", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "beta", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "gamma", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "string", "kind": "keyword" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "void", "kind": "keyword" }] + ).snippet.value, + 'foobar(${1:alpha}, ${2:beta}, ${3:gamma}$4)$0'); + }); + + test('Should skip end-of-list optional parameters', async () => { + assert.strictEqual( + snippetForFunctionCall( + { label: 'foobar', }, + [{ "text": "function", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "foobar", "kind": "functionName" }, { "text": "(", "kind": "punctuation" }, { "text": "alpha", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "string", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "beta", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "gamma", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "void", "kind": "keyword" }] + ).snippet.value, + 'foobar(${1:alpha}$2)$0'); + }); + + // A more complex case + test('Should skip end-of-list optional params but should not skip start-of-list and mid-list ones', async () => { + assert.strictEqual( + snippetForFunctionCall( + { label: 'foobar', }, + [{ "text": "function", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "foobar", "kind": "functionName" }, { "text": "(", "kind": "punctuation" }, { "text": "a", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "b", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "c", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "string", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, + { "text": "d", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "e", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "f", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "string", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, + { "text": "g", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "h", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "void", "kind": "keyword" }] + ).snippet.value, + 'foobar(${1:a}, ${2:b}, ${3:c}, ${4:d}, ${5:e}, ${6:f}$7)$0'); + }); }); From 6b2a628bf1e8cbca0ecd0a274898f8bba3cfc2db Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 5 Feb 2024 22:32:05 -0600 Subject: [PATCH 0925/1897] fix #204257 in insider's --- .../accessibility/browser/accessibilityConfiguration.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 9c7366abeca..4e5b465ff71 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -167,19 +167,19 @@ const configuration: IConfigurationNode = { [AccessibilityAlertSettingId.Error]: { 'markdownDescription': localize('alert.error', "Alerts when the active line has an error. Also see {0}.", '`#audioCues.lineHasError#`'), 'type': 'boolean', - 'default': true, + 'default': false, tags: ['accessibility'] }, [AccessibilityAlertSettingId.Warning]: { 'markdownDescription': localize('alert.warning', "Alerts when the active line has a warning. Also see {0}.", '`#audioCues.lineHasWarning#`'), 'type': 'boolean', - 'default': true, + 'default': false, tags: ['accessibility'] }, [AccessibilityAlertSettingId.FoldedArea]: { 'markdownDescription': localize('alert.foldedArea', "Alerts when the active line has a folded area that can be unfolded. Also see {0}.", '`#audioCues.lineHasFoldedArea#`'), 'type': 'boolean', - 'default': true, + 'default': false, tags: ['accessibility'] }, [AccessibilityAlertSettingId.TerminalQuickFix]: { @@ -233,7 +233,7 @@ const configuration: IConfigurationNode = { [AccessibilityAlertSettingId.LineHasBreakpoint]: { 'markdownDescription': localize('alert.lineHasBreakpoint', "Alerts when on a line with a breakpoint. Also see {0}.", '`#audioCues.lineHasBreakpoint#`'), 'type': 'boolean', - 'default': true, + 'default': false, tags: ['accessibility'] }, [AccessibilityAlertSettingId.NotebookCellCompleted]: { From 273872582db894598336bd6380a6951dd0f89710 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 5 Feb 2024 21:12:44 -0800 Subject: [PATCH 0926/1897] testing: fix letter spacing in test results terminal (#204429) * testing: fix letter spacing in test results terminal Fixes #194156 * add comment --- .../testing/browser/testingOutputPeek.ts | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 8185982f284..a64fc0274f3 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -1068,11 +1068,11 @@ class TestResultsPeek extends PeekViewWidget { } export class TestResultsView extends ViewPane { - private readonly content = this._register(this.instantiationService.createInstance(TestResultsViewContent, undefined, { + private readonly content = new Lazy(() => this._register(this.instantiationService.createInstance(TestResultsViewContent, undefined, { historyVisible: staticObservableValue(true), showRevealLocationOnMessages: true, locationForProgress: Testing.ExplorerViewId, - })); + }))); constructor( options: IViewPaneOptions, @@ -1091,7 +1091,7 @@ export class TestResultsView extends ViewPane { } public get subject() { - return this.content.current; + return this.content.rawValue?.current; } public showLatestRun(preserveFocus = false) { @@ -1100,27 +1100,34 @@ export class TestResultsView extends ViewPane { return; } - this.content.reveal({ preserveFocus, subject: new TaskSubject(result, 0) }); - } - - public showMessage(result: ITestResult, test: TestResultItem, taskIndex: number, messageIndex: number) { - this.content.reveal({ preserveFocus: false, subject: new MessageSubject(result, test, taskIndex, messageIndex) }); + this.content.rawValue?.reveal({ preserveFocus, subject: new TaskSubject(result, 0) }); } protected override renderBody(container: HTMLElement): void { super.renderBody(container); - this.content.fillBody(container); - this.content.onDidRequestReveal(subject => this.content.reveal({ preserveFocus: true, subject })); - - const [lastResult] = this.resultService.results; - if (lastResult && lastResult.tasks.length) { - this.content.reveal({ preserveFocus: true, subject: new TaskSubject(lastResult, 0) }); + // Avoid rendering into the body until it's attached the DOM, as it can + // result in rendering issues in the terminal (#194156) + if (this.isBodyVisible()) { + this.renderContent(container); + } else { + this._register(Event.once(Event.filter(this.onDidChangeBodyVisibility, Boolean))(() => this.renderContent(container))); } } protected override layoutBody(height: number, width: number): void { super.layoutBody(height, width); - this.content.onLayoutBody(height, width); + this.content.rawValue?.onLayoutBody(height, width); + } + + private renderContent(container: HTMLElement) { + const content = this.content.value; + content.fillBody(container); + content.onDidRequestReveal(subject => content.reveal({ preserveFocus: true, subject })); + + const [lastResult] = this.resultService.results; + if (lastResult && lastResult.tasks.length) { + content.reveal({ preserveFocus: true, subject: new TaskSubject(lastResult, 0) }); + } } } From 8839e62ada712a958e76e9c5cd62a41176c51501 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 5 Feb 2024 23:58:54 -0800 Subject: [PATCH 0927/1897] sent string value in telemetry (#204457) --- .../contrib/telemetry/browser/telemetry.contribution.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index d52e62ee7a7..68275f3ea20 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -281,12 +281,12 @@ class ConfigurationTelemetryContribution extends Disposable implements IWorkbenc /** * Report value of a setting only if it is an enum, boolean, or number or an array of those. */ - private getValueToReport(key: string, target: ConfigurationTarget.USER_LOCAL | ConfigurationTarget.WORKSPACE): any { + private getValueToReport(key: string, target: ConfigurationTarget.USER_LOCAL | ConfigurationTarget.WORKSPACE): string | undefined { const schema = this.configurationRegistry.getConfigurationProperties()[key]; const inpsectData = this.configurationService.inspect(key); const value = target === ConfigurationTarget.USER_LOCAL ? inpsectData.user?.value : inpsectData.workspace?.value; if (isNumber(value) || isBoolean(value)) { - return value; + return value.toString(); } if (isString(value)) { if (schema?.enum?.includes(value)) { @@ -296,7 +296,7 @@ class ConfigurationTelemetryContribution extends Disposable implements IWorkbenc } if (Array.isArray(value)) { if (value.every(v => isNumber(v) || isBoolean(v) || (isString(v) && schema?.enum?.includes(v)))) { - return value; + return JSON.stringify(value); } } return undefined; @@ -304,7 +304,7 @@ class ConfigurationTelemetryContribution extends Disposable implements IWorkbenc private reportTelemetry(key: string, target: ConfigurationTarget.USER_LOCAL | ConfigurationTarget.WORKSPACE): void { type UpdatedSettingEvent = { - value: any; + value: string | undefined; source: string; }; const source = ConfigurationTargetToString(target); From 846090c9893272d1473525a2042ed26b64b9eb42 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 6 Feb 2024 09:26:02 +0100 Subject: [PATCH 0928/1897] fix watermark context keys (#204462) --- .../browser/parts/editor/editorGroupWatermark.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts b/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts index b0f71cdc5fc..3daa20701a7 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts @@ -35,8 +35,8 @@ const newUntitledFileMacOnly: WatermarkEntry = { text: localize('watermark.newUn const findInFiles: WatermarkEntry = { text: localize('watermark.findInFiles', "Find in Files"), id: 'workbench.action.findInFiles' }; const toggleTerminal: WatermarkEntry = { text: localize({ key: 'watermark.toggleTerminal', comment: ['toggle is a verb here'] }, "Toggle Terminal"), id: 'workbench.action.terminal.toggleTerminal', when: ContextKeyExpr.equals('terminalProcessSupported', true) }; const startDebugging: WatermarkEntry = { text: localize('watermark.startDebugging', "Start Debugging"), id: 'workbench.action.debug.start', when: ContextKeyExpr.equals('terminalProcessSupported', true) }; -const toggleFullscreen: WatermarkEntry = { text: localize({ key: 'watermark.toggleFullscreen', comment: ['toggle is a verb here'] }, "Toggle Full Screen"), id: 'workbench.action.toggleFullScreen', when: ContextKeyExpr.equals('terminalProcessSupported', true).negate() }; -const showSettings: WatermarkEntry = { text: localize('watermark.showSettings', "Show Settings"), id: 'workbench.action.openSettings', when: ContextKeyExpr.equals('terminalProcessSupported', true).negate() }; +const toggleFullscreen: WatermarkEntry = { text: localize({ key: 'watermark.toggleFullscreen', comment: ['toggle is a verb here'] }, "Toggle Full Screen"), id: 'workbench.action.toggleFullScreen' }; +const showSettings: WatermarkEntry = { text: localize('watermark.showSettings', "Show Settings"), id: 'workbench.action.openSettings' }; const noFolderEntries = [ showCommands, @@ -131,14 +131,15 @@ export class EditorGroupWatermark extends Disposable { const selected = (folder ? folderEntries : noFolderEntries) .filter(entry => !('when' in entry) || this.contextKeyService.contextMatchesRules(entry.when)) .filter(entry => !('mac' in entry) || entry.mac === (isMacintosh && !isWeb)) - .filter(entry => !!CommandsRegistry.getCommand(entry.id)); + .filter(entry => !!CommandsRegistry.getCommand(entry.id)) + .filter(entry => !!this.keybindingService.lookupKeybinding(entry.id)); const update = () => { clearNode(box); - selected.forEach(entry => { + for (const entry of selected) { const keys = this.keybindingService.lookupKeybinding(entry.id); if (!keys) { - return; + continue; } const dl = append(box, $('dl')); const dt = append(dl, $('dt')); @@ -146,7 +147,7 @@ export class EditorGroupWatermark extends Disposable { const dd = append(dl, $('dd')); const keybinding = new KeybindingLabel(dd, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles }); keybinding.set(keys); - }); + } }; update(); From 6a315f2d4317489f256b158650f5458ac6e6e57a Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Tue, 6 Feb 2024 01:25:50 -0800 Subject: [PATCH 0929/1897] Adopt registerWorkbenchContribution2 for welcome page (#204440) * Adopt registerWorkbenchContribution2 for welcome page * create GettingStartedInput editor before opening it * split resolver and runner * Set StartupPageRunnerContribution to instantiate after Restore * Update comment --------- Co-authored-by: Benjamin Pasero --- src/vs/workbench/common/contributions.ts | 3 ++ .../browser/gettingStarted.contribution.ts | 16 +++---- .../browser/startupPage.ts | 43 ++++++++++++------- 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index be5d2a78808..b96df678196 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -22,6 +22,9 @@ export interface IWorkbenchContribution { } export namespace Extensions { + /** + * @deprecated use `registerWorkbenchContribution2` instead. + */ export const Workbench = 'workbench.contributions.kind'; } diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts index 683831a49f2..fc6c589ca16 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts @@ -17,8 +17,7 @@ import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IWalkthroughsService } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService'; import { GettingStartedEditorOptions, GettingStartedInput } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedInput'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { registerWorkbenchContribution2, WorkbenchPhase } from 'vs/workbench/common/contributions'; import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -28,7 +27,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { isLinux, isMacintosh, isWindows, OperatingSystem as OS } from 'vs/base/common/platform'; import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { StartupPageContribution, } from 'vs/workbench/contrib/welcomeGettingStarted/browser/startupPage'; +import { StartupPageEditorResolverContribution, StartupPageRunnerContribution } from 'vs/workbench/contrib/welcomeGettingStarted/browser/startupPage'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; @@ -269,6 +268,9 @@ registerAction2(class extends Action2 { export const WorkspacePlatform = new RawContextKey<'mac' | 'linux' | 'windows' | 'webworker' | undefined>('workspacePlatform', undefined, localize('workspacePlatform', "The platform of the current workspace, which in remote or serverless contexts may be different from the platform of the UI")); class WorkspacePlatformContribution { + + static readonly ID = 'workbench.contrib.workspacePlatform'; + constructor( @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, @@ -301,9 +303,6 @@ class WorkspacePlatformContribution { } } -Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(WorkspacePlatformContribution, LifecyclePhase.Restored); - const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ ...workbenchConfigurationNodeBase, @@ -339,5 +338,6 @@ configurationRegistry.registerConfiguration({ } }); -Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(StartupPageContribution, LifecyclePhase.Restored); +registerWorkbenchContribution2(WorkspacePlatformContribution.ID, WorkspacePlatformContribution, WorkbenchPhase.AfterRestored); +registerWorkbenchContribution2(StartupPageEditorResolverContribution.ID, StartupPageEditorResolverContribution, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(StartupPageRunnerContribution.ID, StartupPageRunnerContribution, WorkbenchPhase.AfterRestored); diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/startupPage.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/startupPage.ts index da1a4f35e0f..a79946bb525 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/startupPage.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/startupPage.ts @@ -13,7 +13,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { IWorkspaceContextService, UNKNOWN_EMPTY_WINDOW_WORKSPACE, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; -import { ILifecycleService, StartupKind } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { ILifecycleService, LifecyclePhase, StartupKind } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IFileService } from 'vs/platform/files/common/files'; import { joinPath } from 'vs/base/common/resources'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; @@ -37,23 +37,12 @@ const configurationKey = 'workbench.startupEditor'; const oldConfigurationKey = 'workbench.welcome.enabled'; const telemetryOptOutStorageKey = 'workbench.telemetryOptOutShown'; -export class StartupPageContribution implements IWorkbenchContribution { +export class StartupPageEditorResolverContribution implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.startupPageEditorResolver'; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IEditorService private readonly editorService: IEditorService, - @IWorkingCopyBackupService private readonly workingCopyBackupService: IWorkingCopyBackupService, - @IFileService private readonly fileService: IFileService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IProductService private readonly productService: IProductService, - @ICommandService private readonly commandService: ICommandService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IStorageService private readonly storageService: IStorageService, - @ILogService private readonly logService: ILogService, - @INotificationService private readonly notificationService: INotificationService, @IEditorResolverService editorResolverService: IEditorResolverService ) { editorResolverService.registerEditor( @@ -79,12 +68,36 @@ export class StartupPageContribution implements IWorkbenchContribution { } } ); + } +} +export class StartupPageRunnerContribution implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.startupPageRunner'; + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IEditorService private readonly editorService: IEditorService, + @IWorkingCopyBackupService private readonly workingCopyBackupService: IWorkingCopyBackupService, + @IFileService private readonly fileService: IFileService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IProductService private readonly productService: IProductService, + @ICommandService private readonly commandService: ICommandService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IStorageService private readonly storageService: IStorageService, + @ILogService private readonly logService: ILogService, + @INotificationService private readonly notificationService: INotificationService + ) { this.run().then(undefined, onUnexpectedError); } private async run() { + // Wait for resolving startup editor until we are restored to reduce startup pressure + await this.lifecycleService.when(LifecyclePhase.Restored); + // Always open Welcome page for first-launch, no matter what is open or which startupEditor is set. if ( this.productService.enableTelemetry From cf7ddbb51d0eaaed6dc25d2df137594254f3b790 Mon Sep 17 00:00:00 2001 From: Robo Date: Tue, 6 Feb 2024 19:25:58 +0900 Subject: [PATCH 0930/1897] fix: use legacy server as default with additional warnings (#204377) * ci: switch to glibc 2.17 remote server * chore: signal user about unsupported connection * chore: address review comments * chore: update nodejs build * chore: bump distro * chore: lower the minimum requirements * fix: glibc version check * chore: remove explicit connection disposal --- build/.cachesalt | 2 +- build/azure-pipelines/linux/install.sh | 5 - .../linux/product-build-linux.yml | 58 ++---- .../linux/verify-glibc-requirements.sh | 44 +++++ build/gulpfile.reh.js | 9 +- build/npm/postinstall.js | 23 ++- cli/src/util/prereqs.rs | 4 +- package.json | 2 +- remote/.yarnrc | 2 +- .../bin/helpers/check-requirements-linux.sh | 2 +- .../remote/common/remoteAgentEnvironment.ts | 1 + .../server/node/remoteAgentEnvironmentImpl.ts | 12 +- .../browser/parts/banner/bannerPart.ts | 12 +- .../remote/browser/remote.contribution.ts | 2 + .../remote/browser/remoteConnectionHealth.ts | 183 ++++++++++++++++++ .../remote/common/remote.contribution.ts | 94 +-------- .../services/banner/browser/bannerService.ts | 1 + .../common/remoteAgentEnvironmentChannel.ts | 4 +- 18 files changed, 298 insertions(+), 162 deletions(-) create mode 100755 build/azure-pipelines/linux/verify-glibc-requirements.sh create mode 100644 src/vs/workbench/contrib/remote/browser/remoteConnectionHealth.ts diff --git a/build/.cachesalt b/build/.cachesalt index 4f3f91b0edf..89591977f28 100644 --- a/build/.cachesalt +++ b/build/.cachesalt @@ -1 +1 @@ -2024-01-29T19:26:27.993Z +2024-02-05T09:34:15.476Z diff --git a/build/azure-pipelines/linux/install.sh b/build/azure-pipelines/linux/install.sh index 16fefd2e754..57f58763cca 100755 --- a/build/azure-pipelines/linux/install.sh +++ b/build/azure-pipelines/linux/install.sh @@ -34,11 +34,6 @@ if [ "$npm_config_arch" == "x64" ]; then export CXX="$PWD/.build/CR_Clang/bin/clang++ --gcc-toolchain=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu" export CXXFLAGS="-nostdinc++ -D__NO_INLINE__ -I$PWD/.build/libcxx_headers -isystem$PWD/.build/libcxx_headers/include -isystem$PWD/.build/libcxxabi_headers/include -fPIC -flto=thin -fsplit-lto-unit -D_LIBCPP_ABI_NAMESPACE=Cr --sysroot=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot" export LDFLAGS="-stdlib=libc++ --sysroot=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot -fuse-ld=lld -flto=thin -L$PWD/.build/libcxx-objects -lc++abi -L$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot/usr/lib/x86_64-linux-gnu -L$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot/lib/x86_64-linux-gnu -Wl,--lto-O0" - # Set compiler toolchain for remote server - export VSCODE_REMOTE_CC=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/bin/x86_64-linux-gnu-gcc - export VSCODE_REMOTE_CXX=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/bin/x86_64-linux-gnu-g++ - export VSCODE_REMOTE_CXXFLAGS="--sysroot=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot" - export VSCODE_REMOTE_LDFLAGS="--sysroot=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot -L$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot/usr/lib/x86_64-linux-gnu -L$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot/lib/x86_64-linux-gnu" elif [ "$npm_config_arch" == "arm64" ]; then # Set compiler toolchain for client native modules and remote server export CC=$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 848c47fe823..e4b4fab899b 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -121,53 +121,23 @@ steps: ELECTRON_SKIP_BINARY_DOWNLOAD: 1 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 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')) }}: + VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME: vscodehub.azurecr.io/vscode-linux-build-agent:centos7-devtoolset8-$(VSCODE_ARCH) + ${{ if eq(parameters.VSCODE_ARCH, 'armhf') }}: + VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME: vscodehub.azurecr.io/vscode-linux-build-agent:bionic-arm32v7 displayName: Install dependencies (non-OSS) condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - script: | - set -e + - ${{ if or(eq(parameters.VSCODE_ARCH, 'x64'), eq(parameters.VSCODE_ARCH, 'arm64')) }}: + - script: | + set -e - TRIPLE="x86_64-linux-gnu" - if [ "$VSCODE_ARCH" == "arm64" ]; then - TRIPLE="aarch64-linux-gnu" - elif [ "$VSCODE_ARCH" == "armhf" ]; then - TRIPLE="arm-rpi-linux-gnueabihf" - fi - - # Get all files with .node extension from remote/node_modules folder - files=$(find remote/node_modules -name "*.node") - - # Check if any file has dependency of GLIBC > 2.28 or GLIBCXX > 3.4.25 - for file in $files; do - glibc_version="2.28" - glibcxx_version="3.4.25" - while IFS= read -r line; do - if [[ $line == *"GLIBC_"* ]]; then - version=$(echo "$line" | awk '{print $5}' | tr -d '()') - version=${version#*_} - if [[ $(printf "%s\n%s" "$version" "$glibc_version" | sort -V | tail -n1) == "$version" ]]; then - glibc_version=$version - fi - elif [[ $line == *"GLIBCXX_"* ]]; then - version=$(echo "$line" | awk '{print $5}' | tr -d '()') - version=${version#*_} - if [[ $(printf "%s\n%s" "$version" "$glibcxx_version" | sort -V | tail -n1) == "$version" ]]; then - glibcxx_version=$version - fi - fi - done < <("$PWD/.build/sysroots/$TRIPLE/$TRIPLE/bin/objdump" -T "$file") - - if [[ "$glibc_version" != "2.28" ]]; then - echo "Error: File $file has dependency on GLIBC > 2.28" - exit 1 - fi - if [[ "$glibcxx_version" != "3.4.25" ]]; then - echo "Error: File $file has dependency on GLIBCXX > 3.4.25" - exit 1 - fi - done - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Check GLIBC and GLIBCXX dependencies in remote/node_modules + EXPECTED_GLIBC_VERSION="2.17" \ + EXPECTED_GLIBCXX_VERSION="3.4.19" \ + ./build/azure-pipelines/linux/verify-glibc-requirements.sh + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Check GLIBC and GLIBCXX dependencies in remote/node_modules - script: node build/azure-pipelines/distro/mixin-npm condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) @@ -248,6 +218,7 @@ steps: - script: | set -e + export VSCODE_NODE_GLIBC='-glibc-2.17' yarn gulp vscode-reh-linux-$(VSCODE_ARCH)-min-ci mv ../vscode-reh-linux-$(VSCODE_ARCH) ../vscode-server-linux-$(VSCODE_ARCH) # TODO@joaomoreno ARCHIVE_PATH=".build/linux/server/vscode-server-linux-$(VSCODE_ARCH).tar.gz" @@ -260,6 +231,7 @@ steps: - script: | set -e + export VSCODE_NODE_GLIBC='-glibc-2.17' yarn gulp vscode-reh-web-linux-$(VSCODE_ARCH)-min-ci mv ../vscode-reh-web-linux-$(VSCODE_ARCH) ../vscode-server-linux-$(VSCODE_ARCH)-web # TODO@joaomoreno ARCHIVE_PATH=".build/linux/web/vscode-server-linux-$(VSCODE_ARCH)-web.tar.gz" diff --git a/build/azure-pipelines/linux/verify-glibc-requirements.sh b/build/azure-pipelines/linux/verify-glibc-requirements.sh new file mode 100755 index 00000000000..f07c0ba71b0 --- /dev/null +++ b/build/azure-pipelines/linux/verify-glibc-requirements.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +set -e + +TRIPLE="x86_64-linux-gnu" +if [ "$VSCODE_ARCH" == "arm64" ]; then + TRIPLE="aarch64-linux-gnu" +elif [ "$VSCODE_ARCH" == "armhf" ]; then + TRIPLE="arm-rpi-linux-gnueabihf" +fi + +# Get all files with .node extension from remote/node_modules folder +files=$(find remote/node_modules -name "*.node" -not -path "*prebuilds*") + +echo "Verifying requirements for files: $files" + +for file in $files; do + glibc_version="$EXPECTED_GLIBC_VERSION" + glibcxx_version="$EXPECTED_GLIBCXX_VERSION" + while IFS= read -r line; do + if [[ $line == *"GLIBC_"* ]]; then + version=$(echo "$line" | awk '{print $5}' | tr -d '()') + version=${version#*_} + if [[ $(printf "%s\n%s" "$version" "$glibc_version" | sort -V | tail -n1) == "$version" ]]; then + glibc_version=$version + fi + elif [[ $line == *"GLIBCXX_"* ]]; then + version=$(echo "$line" | awk '{print $5}' | tr -d '()') + version=${version#*_} + if [[ $(printf "%s\n%s" "$version" "$glibcxx_version" | sort -V | tail -n1) == "$version" ]]; then + glibcxx_version=$version + fi + fi + done < <("$PWD/.build/sysroots/$TRIPLE/$TRIPLE/bin/objdump" -T "$file") + + if [[ "$glibc_version" != "$EXPECTED_GLIBC_VERSION" ]]; then + echo "Error: File $file has dependency on GLIBC > $EXPECTED_GLIBC_VERSION" + exit 1 + fi + if [[ "$glibcxx_version" != "$EXPECTED_GLIBCXX_VERSION" ]]; then + echo "Error: File $file has dependency on GLIBCXX > $EXPECTED_GLIBCXX_VERSION" + exit 1 + fi +done diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index df18cf06aa9..595d0ce1f43 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -125,7 +125,7 @@ function getNodeVersion() { return { nodeVersion, internalNodeVersion }; } -function getNodeChecksum(nodeVersion, platform, arch) { +function getNodeChecksum(nodeVersion, platform, arch, glibcPrefix) { let expectedName; switch (platform) { case 'win32': @@ -135,7 +135,7 @@ function getNodeChecksum(nodeVersion, platform, arch) { case 'darwin': case 'alpine': case 'linux': - expectedName = `node-v${nodeVersion}-${platform}-${arch}.tar.gz`; + expectedName = `node-v${nodeVersion}${glibcPrefix}-${platform}-${arch}.tar.gz`; break; } @@ -193,7 +193,8 @@ function nodejs(platform, arch) { log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from ${product.nodejsRepository}...`); - const checksumSha256 = getNodeChecksum(nodeVersion, platform, arch); + const glibcPrefix = process.env['VSCODE_NODE_GLIBC'] ?? ''; + const checksumSha256 = getNodeChecksum(nodeVersion, platform, arch, glibcPrefix); if (checksumSha256) { log(`Using SHA256 checksum for checking integrity: ${checksumSha256}`); @@ -210,7 +211,7 @@ function nodejs(platform, arch) { case 'darwin': case 'linux': return (product.nodejsRepository !== 'https://nodejs.org' ? - fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: `node-v${nodeVersion}-${platform}-${arch}.tar.gz`, checksumSha256 }) : + fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: `node-v${nodeVersion}${glibcPrefix}-${platform}-${arch}.tar.gz`, checksumSha256 }) : fetchUrls(`/dist/v${nodeVersion}/node-v${nodeVersion}-${platform}-${arch}.tar.gz`, { base: 'https://nodejs.org', checksumSha256 }) ).pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar()))) .pipe(filter('**/node')) diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index cce0f460378..72dd74f8986 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -53,10 +53,14 @@ function yarnInstall(dir, opts) { console.log(`Installing dependencies in ${dir} inside container ${process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME']}...`); opts.cwd = root; - if (process.env['npm_config_arch'] === 'arm64') { + if (process.env['npm_config_arch'] === 'arm64' || process.env['npm_config_arch'] === 'arm') { run('sudo', ['docker', 'run', '--rm', '--privileged', 'multiarch/qemu-user-static', '--reset', '-p', 'yes'], opts); } - run('sudo', ['docker', 'run', '-e', 'GITHUB_TOKEN', '-e', 'npm_config_arch', '-v', `${process.env['VSCODE_HOST_MOUNT']}:/root/vscode`, '-v', `${process.env['VSCODE_HOST_MOUNT']}/.build/.netrc:/root/.netrc`, process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'], 'yarn', '--cwd', dir, ...args], opts); + if (process.env['npm_config_arch'] === 'arm') { + run('sudo', ['docker', 'run', '-e', 'GITHUB_TOKEN', '-e', 'npm_config_arch', '-v', `${process.env['VSCODE_HOST_MOUNT']}:/home/builduser`, '-v', `${process.env['VSCODE_HOST_MOUNT']}/.build/.netrc:/home/builduser/.netrc`, process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'], 'yarn', '--cwd', dir, ...args], opts); + } else { + run('sudo', ['docker', 'run', '-e', 'GITHUB_TOKEN', '-e', 'npm_config_arch', '-v', `${process.env['VSCODE_HOST_MOUNT']}:/root/vscode`, '-v', `${process.env['VSCODE_HOST_MOUNT']}/.build/.netrc:/root/.netrc`, process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'], 'yarn', '--cwd', dir, ...args], opts); + } run('sudo', ['chown', '-R', `${userinfo.uid}:${userinfo.gid}`, `${dir}/node_modules`], opts); } else { console.log(`Installing dependencies in ${dir}...`); @@ -102,8 +106,19 @@ for (let dir of dirs) { if (/^(.build\/distro\/npm\/)?remote$/.test(dir)) { // node modules used by vscode server const env = { ...process.env }; - if (process.env['VSCODE_REMOTE_CC']) { env['CC'] = process.env['VSCODE_REMOTE_CC']; } - if (process.env['VSCODE_REMOTE_CXX']) { env['CXX'] = process.env['VSCODE_REMOTE_CXX']; } + if (process.env['VSCODE_REMOTE_CC']) { + env['CC'] = process.env['VSCODE_REMOTE_CC']; + } else { + delete env['CC']; + } + if (process.env['VSCODE_REMOTE_CXX']) { + env['CXX'] = process.env['VSCODE_REMOTE_CXX']; + } else { + delete env['CXX']; + } + if (process.env['CXXFLAGS']) { delete env['CXXFLAGS']; } + if (process.env['CFLAGS']) { delete env['CFLAGS']; } + if (process.env['LDFLAGS']) { delete env['LDFLAGS']; } if (process.env['VSCODE_REMOTE_CXXFLAGS']) { env['CXXFLAGS'] = process.env['VSCODE_REMOTE_CXXFLAGS']; } if (process.env['VSCODE_REMOTE_LDFLAGS']) { env['LDFLAGS'] = process.env['VSCODE_REMOTE_LDFLAGS']; } if (process.env['VSCODE_REMOTE_NODE_GYP']) { env['npm_config_node_gyp'] = process.env['VSCODE_REMOTE_NODE_GYP']; } diff --git a/cli/src/util/prereqs.rs b/cli/src/util/prereqs.rs index 3bf2934e255..b22fd469fac 100644 --- a/cli/src/util/prereqs.rs +++ b/cli/src/util/prereqs.rs @@ -20,8 +20,8 @@ lazy_static! { static ref GENERIC_VERSION_RE: Regex = Regex::new(r"^([0-9]+)\.([0-9]+)$").unwrap(); static ref LIBSTD_CXX_VERSION_RE: BinRegex = BinRegex::new(r"GLIBCXX_([0-9]+)\.([0-9]+)(?:\.([0-9]+))?").unwrap(); - static ref MIN_CXX_VERSION: SimpleSemver = SimpleSemver::new(3, 4, 25); - static ref MIN_LDD_VERSION: SimpleSemver = SimpleSemver::new(2, 28, 0); + static ref MIN_CXX_VERSION: SimpleSemver = SimpleSemver::new(3, 4, 19); + static ref MIN_LDD_VERSION: SimpleSemver = SimpleSemver::new(2, 17, 0); } const NIXOS_TEST_PATH: &str = "/etc/NIXOS"; diff --git a/package.json b/package.json index e5949c93c22..ecf12d79edc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "ddced7c51be56108bf9f4bc3e3481a8673aa5031", + "distro": "205c07946b7d7629f85cf332acfcfc8a76f23f6d", "author": { "name": "Microsoft Corporation" }, diff --git a/remote/.yarnrc b/remote/.yarnrc index adbe2d2a5f4..cac528fd2dd 100644 --- a/remote/.yarnrc +++ b/remote/.yarnrc @@ -1,5 +1,5 @@ disturl "https://nodejs.org/dist" target "18.17.1" -ms_build_id "252256" +ms_build_id "255375" runtime "node" build_from_source "true" diff --git a/resources/server/bin/helpers/check-requirements-linux.sh b/resources/server/bin/helpers/check-requirements-linux.sh index 44240f7e536..1b9199fd039 100644 --- a/resources/server/bin/helpers/check-requirements-linux.sh +++ b/resources/server/bin/helpers/check-requirements-linux.sh @@ -126,5 +126,5 @@ fi if [ "$found_required_glibc" = "0" ] || [ "$found_required_glibcxx" = "0" ]; then echo "Error: Missing required dependencies. Please refer to our FAQ https://aka.ms/vscode-remote/faq/old-linux for additional information." # Custom exit code based on https://tldp.org/LDP/abs/html/exitcodes.html - exit 99 + #exit 99 fi diff --git a/src/vs/platform/remote/common/remoteAgentEnvironment.ts b/src/vs/platform/remote/common/remoteAgentEnvironment.ts index d83efeb9408..de9a7559ad1 100644 --- a/src/vs/platform/remote/common/remoteAgentEnvironment.ts +++ b/src/vs/platform/remote/common/remoteAgentEnvironment.ts @@ -27,6 +27,7 @@ export interface IRemoteAgentEnvironment { all: IUserDataProfile[]; home: URI; }; + isUnsupportedGlibc: boolean; } export interface RemoteAgentConnectionContext { diff --git a/src/vs/server/node/remoteAgentEnvironmentImpl.ts b/src/vs/server/node/remoteAgentEnvironmentImpl.ts index b5aa198663e..b9861444caf 100644 --- a/src/vs/server/node/remoteAgentEnvironmentImpl.ts +++ b/src/vs/server/node/remoteAgentEnvironmentImpl.ts @@ -96,6 +96,15 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel { if (profile && !this._userDataProfilesService.profiles.some(p => p.id === profile)) { await this._userDataProfilesService.createProfile(profile, profile); } + type ProcessWithGlibc = NodeJS.Process & { + glibcVersion?: string; + }; + let isUnsupportedGlibc = false; + if (process.platform === 'linux') { + const glibcVersion = (process as ProcessWithGlibc).glibcVersion; + const minorVersion = glibcVersion ? parseInt(glibcVersion.split('.')[1]) : 28; + isUnsupportedGlibc = (minorVersion <= 27); + } return { pid: process.pid, connectionToken: (this._connectionToken.type !== ServerConnectionTokenType.None ? this._connectionToken.value : ''), @@ -114,7 +123,8 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel { profiles: { home: this._userDataProfilesService.profilesHome, all: [...this._userDataProfilesService.profiles].map(profile => ({ ...profile })) - } + }, + isUnsupportedGlibc }; } diff --git a/src/vs/workbench/browser/parts/banner/bannerPart.ts b/src/vs/workbench/browser/parts/banner/bannerPart.ts index ccf73b4808e..3725e594c97 100644 --- a/src/vs/workbench/browser/parts/banner/bannerPart.ts +++ b/src/vs/workbench/browser/parts/banner/bannerPart.ts @@ -222,11 +222,13 @@ export class BannerPart extends Part implements IBannerService { } // Action - const actionBarContainer = append(this.element, $('div.action-container')); - this.actionBar = this._register(new ActionBar(actionBarContainer)); - const closeAction = this._register(new Action('banner.close', 'Close Banner', ThemeIcon.asClassName(widgetClose), true, () => this.close(item))); - this.actionBar.push(closeAction, { icon: true, label: false }); - this.actionBar.setFocusable(false); + if (!item.disableCloseAction) { + const actionBarContainer = append(this.element, $('div.action-container')); + this.actionBar = this._register(new ActionBar(actionBarContainer)); + const closeAction = this._register(new Action('banner.close', 'Close Banner', ThemeIcon.asClassName(widgetClose), true, () => this.close(item))); + this.actionBar.push(closeAction, { icon: true, label: false }); + this.actionBar.setFocusable(false); + } this.setVisibility(true); this.item = item; diff --git a/src/vs/workbench/contrib/remote/browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/browser/remote.contribution.ts index 83aeb5466d5..4689c816624 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.contribution.ts @@ -11,6 +11,7 @@ import { TunnelFactoryContribution } from 'vs/workbench/contrib/remote/browser/t import { RemoteAgentConnectionStatusListener, RemoteMarkers } from 'vs/workbench/contrib/remote/browser/remote'; import { RemoteStatusIndicator } from 'vs/workbench/contrib/remote/browser/remoteIndicator'; import { AutomaticPortForwarding, ForwardedPortsView, PortRestore } from 'vs/workbench/contrib/remote/browser/remoteExplorer'; +import { InitialRemoteConnectionHealthContribution } from 'vs/workbench/contrib/remote/browser/remoteConnectionHealth'; const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); registerWorkbenchContribution2(ShowCandidateContribution.ID, ShowCandidateContribution, WorkbenchPhase.BlockRestore); @@ -21,3 +22,4 @@ workbenchContributionsRegistry.registerWorkbenchContribution(ForwardedPortsView, workbenchContributionsRegistry.registerWorkbenchContribution(PortRestore, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(AutomaticPortForwarding, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteMarkers, LifecyclePhase.Eventually); +workbenchContributionsRegistry.registerWorkbenchContribution(InitialRemoteConnectionHealthContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/remote/browser/remoteConnectionHealth.ts b/src/vs/workbench/contrib/remote/browser/remoteConnectionHealth.ts new file mode 100644 index 00000000000..c75b9c543f7 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/remoteConnectionHealth.ts @@ -0,0 +1,183 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IRemoteAgentService, remoteConnectionLatencyMeasurer } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { localize } from 'vs/nls'; +import { isWeb } from 'vs/base/common/platform'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; +import { IBannerService } from 'vs/workbench/services/banner/browser/bannerService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { Codicon } from 'vs/base/common/codicons'; +import Severity from 'vs/base/common/severity'; + + +const REMOTE_UNSUPPORTED_CONNECTION_CHOICE_KEY = 'remote.unsupportedConnectionChoice'; + +export class InitialRemoteConnectionHealthContribution implements IWorkbenchContribution { + + constructor( + @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, + @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IBannerService private readonly bannerService: IBannerService, + @IDialogService private readonly dialogService: IDialogService, + @IOpenerService private readonly openerService: IOpenerService, + @IHostService private readonly hostService: IHostService, + @IStorageService private readonly storageService: IStorageService, + @IProductService private readonly productService: IProductService, + ) { + if (this._environmentService.remoteAuthority) { + this._checkInitialRemoteConnectionHealth(); + } + } + + private async _confirmConnection(): Promise { + const enum ConnectionChoice { + Allow = 1, + LearnMore = 2, + Cancel = 0 + } + + const { result, checkboxChecked } = await this.dialogService.prompt({ + type: Severity.Warning, + message: localize('unsupportedGlibcWarning', "You are about to connect to an OS that is unsupported by {0}", this.productService.nameLong), + buttons: [ + { + label: localize({ key: 'allow', comment: ['&& denotes a mnemonic'] }, "&&Allow"), + run: () => ConnectionChoice.Allow + }, + { + label: localize({ key: 'learnMore', comment: ['&& denotes a mnemonic'] }, "&&Learn More"), + run: async () => { await this.openerService.open('https://aka.ms/vscode-remote/faq/old-linux'); return ConnectionChoice.LearnMore; } + } + ], + cancelButton: { + run: () => ConnectionChoice.Cancel + }, + checkbox: { + label: localize('remember', "Do not ask me again"), + } + }); + + if (result === ConnectionChoice.LearnMore) { + return await this._confirmConnection(); + } + + const allowed = result === ConnectionChoice.Allow; + if (checkboxChecked) { + this.storageService.store(REMOTE_UNSUPPORTED_CONNECTION_CHOICE_KEY, allowed, StorageScope.PROFILE, StorageTarget.MACHINE); + } + + return allowed; + } + + private async _checkInitialRemoteConnectionHealth(): Promise { + try { + const environment = await this._remoteAgentService.getRawEnvironment(); + + if (environment && environment.isUnsupportedGlibc) { + let allowed = this.storageService.getBoolean(REMOTE_UNSUPPORTED_CONNECTION_CHOICE_KEY, StorageScope.PROFILE); + if (allowed === undefined) { + allowed = await this._confirmConnection(); + } + if (allowed) { + const actions = [ + { + label: localize('unsupportedGlibcBannerLearnMore', "Learn More"), + href: 'https://aka.ms/vscode-remote/faq/old-linux' + } + ]; + this.bannerService.show({ + id: 'unsupportedGlibcWarning.banner', + message: localize('unsupportedGlibcWarning.banner', "You are connected to an OS that is unsupported by {0}.", this.productService.nameLong), + actions, + icon: Codicon.warning, + disableCloseAction: true + }); + } else { + this.hostService.openWindow({ forceReuseWindow: true, remoteAuthority: null }); + return; + } + } + + type RemoteConnectionSuccessClassification = { + owner: 'alexdima'; + comment: 'The initial connection succeeded'; + web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Is web ui.' }; + connectionTimeMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time, in ms, until connected'; isMeasurement: true }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + }; + type RemoteConnectionSuccessEvent = { + web: boolean; + connectionTimeMs: number | undefined; + remoteName: string | undefined; + }; + this._telemetryService.publicLog2('remoteConnectionSuccess', { + web: isWeb, + connectionTimeMs: await this._remoteAgentService.getConnection()?.getInitialConnectionTimeMs(), + remoteName: getRemoteName(this._environmentService.remoteAuthority) + }); + + await this._measureExtHostLatency(); + + } catch (err) { + + type RemoteConnectionFailureClassification = { + owner: 'alexdima'; + comment: 'The initial connection failed'; + web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Is web ui.' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + connectionTimeMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time, in ms, until connection failure'; isMeasurement: true }; + message: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Error message' }; + }; + type RemoteConnectionFailureEvent = { + web: boolean; + remoteName: string | undefined; + connectionTimeMs: number | undefined; + message: string; + }; + this._telemetryService.publicLog2('remoteConnectionFailure', { + web: isWeb, + connectionTimeMs: await this._remoteAgentService.getConnection()?.getInitialConnectionTimeMs(), + remoteName: getRemoteName(this._environmentService.remoteAuthority), + message: err ? err.message : '' + }); + + } + } + + private async _measureExtHostLatency() { + const measurement = await remoteConnectionLatencyMeasurer.measure(this._remoteAgentService); + if (measurement === undefined) { + return; + } + + type RemoteConnectionLatencyClassification = { + owner: 'connor4312'; + comment: 'The latency to the remote extension host'; + web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Whether this is running on web' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Anonymized remote name' }; + latencyMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Latency to the remote, in milliseconds'; isMeasurement: true }; + }; + type RemoteConnectionLatencyEvent = { + web: boolean; + remoteName: string | undefined; + latencyMs: number; + }; + + this._telemetryService.publicLog2('remoteConnectionLatency', { + web: isWeb, + remoteName: getRemoteName(this._environmentService.remoteAuthority), + latencyMs: measurement.current + }); + } +} diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 1b76477c4b7..c66080cacec 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -9,7 +9,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { ILabelService, ResourceLabelFormatting } from 'vs/platform/label/common/label'; import { OperatingSystem, isWeb, OS } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; -import { IRemoteAgentService, remoteConnectionLatencyMeasurer } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ILoggerService } from 'vs/platform/log/common/log'; import { localize, localize2 } from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -24,8 +24,6 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { PersistentConnection } from 'vs/platform/remote/common/remoteAgentConnection'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; import { IDownloadService } from 'vs/platform/download/common/download'; import { DownloadServiceChannel } from 'vs/platform/download/common/downloadIpc'; import { RemoteLoggerChannelClient } from 'vs/platform/log/common/logIpc'; @@ -144,100 +142,10 @@ class RemoteInvalidWorkspaceDetector extends Disposable implements IWorkbenchCon } } -class InitialRemoteConnectionHealthContribution implements IWorkbenchContribution { - - constructor( - @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, - @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, - @ITelemetryService private readonly _telemetryService: ITelemetryService, - ) { - if (this._environmentService.remoteAuthority) { - this._checkInitialRemoteConnectionHealth(); - } - } - - private async _checkInitialRemoteConnectionHealth(): Promise { - try { - await this._remoteAgentService.getRawEnvironment(); - - type RemoteConnectionSuccessClassification = { - owner: 'alexdima'; - comment: 'The initial connection succeeded'; - web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Is web ui.' }; - connectionTimeMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time, in ms, until connected'; isMeasurement: true }; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; - }; - type RemoteConnectionSuccessEvent = { - web: boolean; - connectionTimeMs: number | undefined; - remoteName: string | undefined; - }; - this._telemetryService.publicLog2('remoteConnectionSuccess', { - web: isWeb, - connectionTimeMs: await this._remoteAgentService.getConnection()?.getInitialConnectionTimeMs(), - remoteName: getRemoteName(this._environmentService.remoteAuthority) - }); - - await this._measureExtHostLatency(); - - } catch (err) { - - type RemoteConnectionFailureClassification = { - owner: 'alexdima'; - comment: 'The initial connection failed'; - web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Is web ui.' }; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; - connectionTimeMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time, in ms, until connection failure'; isMeasurement: true }; - message: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Error message' }; - }; - type RemoteConnectionFailureEvent = { - web: boolean; - remoteName: string | undefined; - connectionTimeMs: number | undefined; - message: string; - }; - this._telemetryService.publicLog2('remoteConnectionFailure', { - web: isWeb, - connectionTimeMs: await this._remoteAgentService.getConnection()?.getInitialConnectionTimeMs(), - remoteName: getRemoteName(this._environmentService.remoteAuthority), - message: err ? err.message : '' - }); - - } - } - - private async _measureExtHostLatency() { - const measurement = await remoteConnectionLatencyMeasurer.measure(this._remoteAgentService); - if (measurement === undefined) { - return; - } - - type RemoteConnectionLatencyClassification = { - owner: 'connor4312'; - comment: 'The latency to the remote extension host'; - web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Whether this is running on web' }; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Anonymized remote name' }; - latencyMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Latency to the remote, in milliseconds'; isMeasurement: true }; - }; - type RemoteConnectionLatencyEvent = { - web: boolean; - remoteName: string | undefined; - latencyMs: number; - }; - - this._telemetryService.publicLog2('remoteConnectionLatency', { - web: isWeb, - remoteName: getRemoteName(this._environmentService.remoteAuthority), - latencyMs: measurement.current - }); - } -} - const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); registerWorkbenchContribution2(LabelContribution.ID, LabelContribution, WorkbenchPhase.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteChannelsContribution, LifecyclePhase.Restored); registerWorkbenchContribution2(RemoteInvalidWorkspaceDetector.ID, RemoteInvalidWorkspaceDetector, WorkbenchPhase.BlockStartup); -workbenchContributionsRegistry.registerWorkbenchContribution(InitialRemoteConnectionHealthContribution, LifecyclePhase.Restored); const enableDiagnostics = true; diff --git a/src/vs/workbench/services/banner/browser/bannerService.ts b/src/vs/workbench/services/banner/browser/bannerService.ts index 639b1b2ce81..d8560ce135e 100644 --- a/src/vs/workbench/services/banner/browser/bannerService.ts +++ b/src/vs/workbench/services/banner/browser/bannerService.ts @@ -16,6 +16,7 @@ export interface IBannerItem { readonly actions?: ILinkDescriptor[]; readonly ariaLabel?: string; readonly onClose?: () => void; + readonly disableCloseAction?: boolean; } export const IBannerService = createDecorator('bannerService'); diff --git a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts index c5d56b6dffe..01e7c293a8c 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts @@ -43,6 +43,7 @@ export interface IRemoteAgentEnvironmentDTO { all: UriDto; home: UriComponents; }; + isUnsupportedGlibc: boolean; } export class RemoteExtensionEnvironmentChannelClient { @@ -70,7 +71,8 @@ export class RemoteExtensionEnvironmentChannelClient { arch: data.arch, marks: data.marks, useHostProxy: data.useHostProxy, - profiles: revive(data.profiles) + profiles: revive(data.profiles), + isUnsupportedGlibc: data.isUnsupportedGlibc }; } From 63ebf06bb689dd488fa238bd5eaf95b161aa9aa8 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 6 Feb 2024 14:22:14 +0100 Subject: [PATCH 0931/1897] Git - add action to regenerate branch name (#204481) --- extensions/git/src/commands.ts | 108 ++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 41 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index a193a48ed78..979daf3d2ba 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ import * as os from 'os'; import * as path from 'path'; -import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl } from 'vscode'; +import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import { ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git'; @@ -2597,49 +2597,75 @@ export class CommandCenter { const branchPrefix = config.get('branchPrefix')!; const branchWhitespaceChar = config.get('branchWhitespaceChar')!; const branchValidationRegex = config.get('branchValidationRegex')!; + const branchRandomNameEnabled = config.get('branchRandomName.enable', false); - let rawBranchName = defaultName; - - if (!rawBranchName) { - // Branch name - if (!initialValue) { - const branchRandomNameEnabled = config.get('branchRandomName.enable', false); - const branchName = branchRandomNameEnabled ? await this.generateRandomBranchName(repository, branchWhitespaceChar) : ''; - - initialValue = `${branchPrefix}${branchName}`; - } - - // Branch name selection - const initialValueSelection: [number, number] | undefined = - initialValue.startsWith(branchPrefix) ? [branchPrefix.length, initialValue.length] : undefined; - - rawBranchName = await window.showInputBox({ - placeHolder: l10n.t('Branch name'), - prompt: l10n.t('Please provide a new branch name'), - value: initialValue, - valueSelection: initialValueSelection, - ignoreFocusOut: true, - validateInput: (name: string) => { - const validateName = new RegExp(branchValidationRegex); - const sanitizedName = sanitizeBranchName(name, branchWhitespaceChar); - if (validateName.test(sanitizedName)) { - // If the sanitized name that we will use is different than what is - // in the input box, show an info message to the user informing them - // the branch name that will be used. - return name === sanitizedName - ? null - : { - message: l10n.t('The new branch will be "{0}"', sanitizedName), - severity: InputBoxValidationSeverity.Info - }; - } - - return l10n.t('Branch name needs to match regex: {0}', branchValidationRegex); - } - }); + if (defaultName) { + return sanitizeBranchName(defaultName, branchWhitespaceChar); } - return sanitizeBranchName(rawBranchName || '', branchWhitespaceChar); + const getBranchName = async (): Promise => { + const branchName = branchRandomNameEnabled ? await this.generateRandomBranchName(repository, branchWhitespaceChar) : ''; + return `${branchPrefix}${branchName}`; + }; + + const getValueSelection = (value: string): [number, number] | undefined => { + return value.startsWith(branchPrefix) ? [branchPrefix.length, value.length] : undefined; + }; + + const getValidationMessage = (name: string): string | InputBoxValidationMessage | undefined => { + const validateName = new RegExp(branchValidationRegex); + const sanitizedName = sanitizeBranchName(name, branchWhitespaceChar); + if (validateName.test(sanitizedName)) { + // If the sanitized name that we will use is different than what is + // in the input box, show an info message to the user informing them + // the branch name that will be used. + return name === sanitizedName + ? undefined + : { + message: l10n.t('The new branch will be "{0}"', sanitizedName), + severity: InputBoxValidationSeverity.Info + }; + } + + return l10n.t('Branch name needs to match regex: {0}', branchValidationRegex); + }; + + const disposables: Disposable[] = []; + const inputBox = window.createInputBox(); + + inputBox.placeholder = l10n.t('Branch name'); + inputBox.prompt = l10n.t('Please provide a new branch name'); + + inputBox.buttons = branchRandomNameEnabled ? [ + { + iconPath: new ThemeIcon('refresh'), + tooltip: l10n.t('Regenerate Branch Name'), + } + ] : []; + + inputBox.value = initialValue ?? await getBranchName(); + inputBox.valueSelection = getValueSelection(inputBox.value); + inputBox.validationMessage = getValidationMessage(inputBox.value); + inputBox.ignoreFocusOut = true; + + inputBox.show(); + + const branchName = await new Promise((resolve) => { + disposables.push(inputBox.onDidHide(() => resolve(undefined))); + disposables.push(inputBox.onDidAccept(() => resolve(inputBox.value))); + disposables.push(inputBox.onDidChangeValue(value => { + inputBox.validationMessage = getValidationMessage(value); + })); + disposables.push(inputBox.onDidTriggerButton(async () => { + inputBox.value = await getBranchName(); + inputBox.valueSelection = getValueSelection(inputBox.value); + })); + }); + + dispose(disposables); + inputBox.dispose(); + + return sanitizeBranchName(branchName || '', branchWhitespaceChar); } private async _branch(repository: Repository, defaultName?: string, from = false): Promise { From 1e53f206318b6099927f03d27d4d2adfd08a0f55 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 6 Feb 2024 14:25:17 +0100 Subject: [PATCH 0932/1897] Add classes for chat response parts (#204483) * add classes for each content type that can be streamed, make history contains those * refine types, add converters --- .../workbench/api/common/extHost.api.impl.ts | 8 +- .../api/common/extHostChatAgents2.ts | 52 +++++----- .../api/common/extHostTypeConverters.ts | 98 ++++++++++++++++++- src/vs/workbench/api/common/extHostTypes.ts | 46 +++++++++ .../vscode.proposed.chatAgents2.d.ts | 60 +++++++++--- 5 files changed, 219 insertions(+), 45 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 5a882c5f017..7cffe74fd74 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1639,7 +1639,13 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ThreadFocus: extHostTypes.ThreadFocus, RelatedInformationType: extHostTypes.RelatedInformationType, SpeechToTextStatus: extHostTypes.SpeechToTextStatus, - KeywordRecognitionStatus: extHostTypes.KeywordRecognitionStatus + KeywordRecognitionStatus: extHostTypes.KeywordRecognitionStatus, + ChatResponseTextPart: extHostTypes.ChatResponseTextPart, + ChatResponseMarkdownPart: extHostTypes.ChatResponseMarkdownPart, + ChatResponseFilesPart: extHostTypes.ChatResponseFilesPart, + ChatResponseAnchorPart: extHostTypes.ChatResponseAnchorPart, + ChatResponseProgressPart: extHostTypes.ChatResponseProgressPart, + ChatResponseReferencePart: extHostTypes.ChatResponseReferencePart, }; }; } diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index e977d16fe91..8dce42e642e 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -73,50 +73,44 @@ class ChatAgentResponseStream { }; this._apiObject = { - markdown(value, meta) { - throwIfDone(this.markdown); - _report({ - kind: 'markdownContent', - content: typeConvert.MarkdownString.from(value) - }); - return this; - }, - text(value, meta) { + text(value) { throwIfDone(this.text); - this.markdown(new MarkdownString().appendText(value), meta); + this.markdown(new MarkdownString().appendText(value)); return this; }, - files(value, meta) { + markdown(value) { + throwIfDone(this.markdown); + const part = new extHostTypes.ChatResponseMarkdownPart(value); + const dto = typeConvert.ChatResponseMarkdownPart.to(part); + _report(dto); + return this; + }, + files(value) { throwIfDone(this.files); - _report({ - kind: 'treeData', - treeData: value - }); + const part = new extHostTypes.ChatResponseFilesPart(value); + const dto = typeConvert.ChatResponseFilesPart.to(part); + _report(dto); return this; }, - anchor(value, meta) { + anchor(value, title?: string) { throwIfDone(this.anchor); - _report({ - kind: 'inlineReference', - name: meta?.title, - inlineReference: !URI.isUri(value) ? typeConvert.Location.from(value) : value - }); + const part = new extHostTypes.ChatResponseAnchorPart(value, title); + const dto = typeConvert.ChatResponseAnchorPart.to(part); + _report(dto); return this; }, progress(value) { throwIfDone(this.progress); - _report({ - kind: 'progressMessage', - content: new MarkdownString(value) - }); + const part = new extHostTypes.ChatResponseProgressPart(value); + const dto = typeConvert.ChatResponseProgressPart.to(part); + _report(dto); return this; }, reference(value) { throwIfDone(this.reference); - _report({ - kind: 'reference', - reference: !URI.isUri(value) ? typeConvert.Location.from(value) : value - }); + const part = new extHostTypes.ChatResponseReferencePart(value); + const dto = typeConvert.ChatResponseReferencePart.to(part); + _report(dto); return this; }, report(progress) { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 380ac7a61f3..4345dee16d7 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -36,7 +36,7 @@ import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/edit import { IViewBadge } from 'vs/workbench/common/views'; import { IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents'; import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatFollowup, IChatReplyFollowup, IChatResponseCommandFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatReplyFollowup, IChatResponseCommandFollowup, IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; import { InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -2330,6 +2330,102 @@ export namespace InteractiveEditorResponseFeedbackKind { } } +export namespace ChatResponseTextPart { + export function to(part: vscode.ChatResponseTextPart): Dto { + return { + kind: 'markdownContent', + content: MarkdownString.from(new types.MarkdownString().appendText(part.value)) + }; + } + export function from(part: Dto): vscode.ChatResponseTextPart { + return new types.ChatResponseTextPart(part.content.value); + } +} + +export namespace ChatResponseMarkdownPart { + export function to(part: vscode.ChatResponseMarkdownPart): Dto { + return { + kind: 'markdownContent', + content: MarkdownString.from(part.value) + }; + } + export function from(part: Dto): vscode.ChatResponseMarkdownPart { + return new types.ChatResponseMarkdownPart(MarkdownString.to(part.content)); + } +} + +export namespace ChatResponseFilesPart { + export function to(part: vscode.ChatResponseFilesPart): IChatTreeData { + return { + kind: 'treeData', + treeData: part.value + }; + } + export function from(part: Dto): vscode.ChatResponseFilesPart { + const value = revive(part.treeData); + return new types.ChatResponseFilesPart(value.treeData); + } +} + +export namespace ChatResponseAnchorPart { + export function to(part: vscode.ChatResponseAnchorPart): Dto { + return { + kind: 'inlineReference', + name: part.title, + inlineReference: !URI.isUri(part.value) ? Location.from(part.value) : part.value + }; + } + + export function from(part: Dto): vscode.ChatResponseAnchorPart { + const value = revive(part); + return new types.ChatResponseAnchorPart( + URI.isUri(value.inlineReference) ? value.inlineReference : Location.to(value.inlineReference), + part.name + ); + } +} + +export namespace ChatResponseProgressPart { + export function to(part: vscode.ChatResponseProgressPart): Dto { + return { + kind: 'progressMessage', + content: MarkdownString.from(part.value) + }; + } + export function from(part: Dto): vscode.ChatResponseProgressPart { + return new types.ChatResponseProgressPart(part.content.value); + } +} + +export namespace ChatResponseReferencePart { + export function to(part: vscode.ChatResponseReferencePart): Dto { + return { + kind: 'reference', + reference: !URI.isUri(part.value) ? Location.from(part.value) : part.value + }; + } + export function from(part: Dto): vscode.ChatResponseReferencePart { + const value = revive(part); + return new types.ChatResponseReferencePart( + URI.isUri(value.reference) ? value.reference : Location.to(value.reference) + ); + } +} + +export namespace ChatResponsePart { + + export function to(part: extHostProtocol.IChatProgressDto): vscode.ChatResponsePart { + switch (part.kind) { + case 'markdownContent': return ChatResponseMarkdownPart.from(part); + case 'inlineReference': return ChatResponseAnchorPart.from(part); + case 'reference': return ChatResponseReferencePart.from(part); + case 'progressMessage': return ChatResponseProgressPart.from(part); + case 'treeData': return ChatResponseFilesPart.from(part); + } + return new types.ChatResponseTextPart(''); + } +} + export namespace ChatResponseProgress { export function from(extension: IExtensionDescription, progress: vscode.ChatAgentExtendedProgress): extHostProtocol.IChatProgressDto | undefined { if ('markdownContent' in progress) { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 2f46615b5a1..1deb244c2e4 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4203,6 +4203,52 @@ export enum ChatAgentResultFeedbackKind { Helpful = 1, } + +export class ChatResponseTextPart { + value: string; + constructor(value: string) { + this.value = value; + } +} + +export class ChatResponseMarkdownPart { + value: string | vscode.MarkdownString; + constructor(value: string | vscode.MarkdownString) { + this.value = value; + } +} + +export class ChatResponseFilesPart { + value: vscode.ChatAgentFileTreeData; + constructor(value: vscode.ChatAgentFileTreeData) { + this.value = value; + } +} + +export class ChatResponseAnchorPart { + value: vscode.Uri | vscode.Location | vscode.SymbolInformation; + title?: string; + constructor(value: vscode.Uri | vscode.Location | vscode.SymbolInformation, title?: string) { + this.value = value; + this.title = title; + } +} + +export class ChatResponseProgressPart { + value: string; + constructor(value: string) { + this.value = value; + } +} + +export class ChatResponseReferencePart { + value: vscode.Uri | vscode.Location; + constructor(value: vscode.Uri | vscode.Location) { + this.value = value; + } +} + + //#endregion //#region ai diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index bed707cf559..b7cda17484a 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -17,7 +17,7 @@ declare module 'vscode' { /** * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. */ - response: ChatAgentContentProgress[]; + response: (ChatAgentContentProgress | ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFilesPart | ChatResponseAnchorPart)[]; /** * The result that was received from the chat agent. @@ -277,24 +277,15 @@ declare module 'vscode' { variables: Record; } - export interface ChatAgentResponseItemMetadata { - title: string; - // annotations: any[]; // future OffsetbasedAnnotation and Annotation - } - export interface ChatAgentResponseStream { - // RENDERED + text(value: string): ChatAgentResponseStream; - text(value: string, meta?: ChatAgentResponseItemMetadata): ChatAgentResponseStream; + markdown(value: string | MarkdownString): ChatAgentResponseStream; - markdown(value: string | MarkdownString, meta?: ChatAgentResponseItemMetadata): ChatAgentResponseStream; + files(value: ChatAgentFileTreeData): ChatAgentResponseStream; - files(value: ChatAgentFileTreeData, meta?: ChatAgentResponseItemMetadata): ChatAgentResponseStream; - - anchor(value: Uri | Location, meta?: ChatAgentResponseItemMetadata): ChatAgentResponseStream; - - // META + anchor(value: Uri | Location, title?: string): ChatAgentResponseStream; // TODO@API this influences the rendering, it inserts new lines which is likely a bug progress(value: string): ChatAgentResponseStream; @@ -309,6 +300,47 @@ declare module 'vscode' { report(value: ChatAgentProgress): void; } + // TODO@API + // support ChatResponseCommandPart + // support ChatResponseTextEditPart + // support ChatResponseCodeReferencePart + + // TODO@API should the name suffix differentiate between rendered items (XYZPart) + // and metadata like XYZItem + export class ChatResponseTextPart { + value: string; + constructor(value: string); + } + + export class ChatResponseMarkdownPart { + value: string | MarkdownString; + constructor(value: string | MarkdownString); + } + + export class ChatResponseFilesPart { + value: ChatAgentFileTreeData; + constructor(value: ChatAgentFileTreeData); + } + + export class ChatResponseAnchorPart { + value: Uri | Location | SymbolInformation; + title?: string; + constructor(value: Uri | Location | SymbolInformation, title?: string); + } + + export class ChatResponseProgressPart { + value: string; + constructor(value: string); + } + + export class ChatResponseReferencePart { + value: Uri | Location; + constructor(value: Uri | Location); + } + + export type ChatResponsePart = ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFilesPart | ChatResponseAnchorPart + | ChatResponseProgressPart | ChatResponseReferencePart; + /** * @deprecated use ChatAgentResponseStream instead */ From e294d45711943b5975bdeb011e9c5f62133ddf09 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Tue, 6 Feb 2024 06:11:49 -0800 Subject: [PATCH 0933/1897] dont include icon in notebook symbol filter (#204170) * strip icons to filter, and create highlights around icons * parse once * remove unused import --- .../browser/quickaccess/gotoSymbolQuickAccess.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts index 2ac217dfdb1..6788e70b877 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts @@ -20,7 +20,7 @@ import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/act import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { prepareQuery } from 'vs/base/common/fuzzyScorer'; import { SymbolKind } from 'vs/editor/common/languages'; -import { fuzzyScore, createMatches } from 'vs/base/common/filters'; +import { fuzzyScore } from 'vs/base/common/filters'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -33,6 +33,7 @@ import { IOutlineModelService } from 'vs/editor/contrib/documentSymbols/browser/ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { accessibilityHelpIsShown, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { matchesFuzzyIconAware, parseLabelWithIcons } from 'vs/base/common/iconLabels'; export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { @@ -194,14 +195,22 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess item.highlights = undefined; return true; } - const score = fuzzyScore(picker.value, picker.value.toLowerCase(), 1 /*@-character*/, item.label, item.label.toLowerCase(), 0, { firstMatchCanBeWeak: true, boostFullMatch: true }); + + const trimmedQuery = picker.value.substring(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim(); + const parsedLabel = parseLabelWithIcons(item.label); + const score = fuzzyScore(trimmedQuery, trimmedQuery.toLowerCase(), 0, + parsedLabel.text, parsedLabel.text.toLowerCase(), 0, + { firstMatchCanBeWeak: true, boostFullMatch: true }); + if (!score) { return false; } + item.score = score[1]; - item.highlights = { label: createMatches(score) }; + item.highlights = { label: matchesFuzzyIconAware(trimmedQuery, parsedLabel) ?? undefined }; return true; }); + if (filteredItems.length === 0) { const label = localize('empty', 'No matching entries'); picker.items = [{ label, index: -1, kind: SymbolKind.String }]; From a226d35584b046e2762f11f73d4ee653794e4434 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 6 Feb 2024 15:19:13 +0100 Subject: [PATCH 0934/1897] prepare internals for API that list language model ids and have an event for when those change (#204492) * add ChatAccess#onDidChangeAccess * add `$updateAllowlist` to protocol * prepare internals for API that list language model ids and have an event for when those change --- .../api/browser/mainThreadChatProvider.ts | 7 ++- .../workbench/api/common/extHost.protocol.ts | 2 + .../api/common/extHostChatAgents2.ts | 8 +-- .../api/common/extHostChatProvider.ts | 60 ++++++++++++++++--- .../contrib/chat/common/chatProvider.ts | 24 +++++++- .../vscode.proposed.chatRequestAccess.d.ts | 13 +++- 6 files changed, 96 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index 5e8ee0bc50d..9a14e8ec0f7 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; -import { DisposableMap } from 'vs/base/common/lifecycle'; +import { DisposableMap, DisposableStore } 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'; @@ -16,6 +16,7 @@ import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/ext export class MainThreadChatProvider implements MainThreadChatProviderShape { private readonly _proxy: ExtHostChatProviderShape; + private readonly _store = new DisposableStore(); private readonly _providerRegistrations = new DisposableMap(); private readonly _pendingProgress = new Map>(); @@ -25,10 +26,14 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { @ILogService private readonly _logService: ILogService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatProvider); + + this._proxy.$updateProviderList({ added: _chatProviderService.getProviders() }); + this._store.add(_chatProviderService.onDidChangeProviders(this._proxy.$updateProviderList, this._proxy)); } dispose(): void { this._providerRegistrations.dispose(); + this._store.dispose(); } $registerProvider(handle: number, identifier: string, metadata: IChatResponseProviderMetadata): void { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 8dec15bdd3a..d24c57433a8 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1180,6 +1180,8 @@ export interface MainThreadChatProviderShape extends IDisposable { } export interface ExtHostChatProviderShape { + $updateProviderList(data: { added?: string[]; removed?: string[] }): void; + $updateAllowlist(data: { extension: ExtensionIdentifier; allowed: boolean }[]): void; $provideChatResponse(handle: number, requestId: number, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise; } diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 8dce42e642e..cb1333f212b 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { coalesce } from 'vs/base/common/arrays'; -import { DeferredPromise, raceCancellation } from 'vs/base/common/async'; +import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; @@ -173,9 +173,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); } - const commandExecution = new DeferredPromise(); - token.onCancellationRequested(() => commandExecution.complete()); - this._extHostChatProvider.allowListExtensionWhile(agent.extension.identifier, commandExecution.p); + this._extHostChatProvider.$updateAllowlist([{ extension: agent.extension.identifier, allowed: true }]); const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService); try { @@ -211,7 +209,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } finally { stream.close(); - commandExecution.complete(); + this._extHostChatProvider.$updateAllowlist([{ extension: agent.extension.identifier, allowed: false }]); } } diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index 68194296285..8980c9eb009 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -11,9 +11,9 @@ 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, ExtensionIdentifierMap } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet } from 'vs/platform/extensions/common/extensions'; import { AsyncIterableSource } from 'vs/base/common/async'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; type ProviderData = { readonly extension: ExtensionIdentifier; @@ -94,6 +94,8 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { private readonly _proxy: MainThreadChatProviderShape; private readonly _providers = new Map(); + private readonly _onDidChangeAccess = new Emitter(); + private readonly _onDidChangeProviders = new Emitter<{ added: string[]; removed: string[] }>(); constructor( mainContext: IMainContext, @@ -102,6 +104,11 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._proxy = mainContext.getProxy(MainContext.MainThreadChatProvider); } + dispose(): void { + this._onDidChangeAccess.dispose(); + this._onDidChangeProviders.dispose(); + } + registerProvider(extension: ExtensionIdentifier, identifier: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata): IDisposable { const handle = ExtHostChatProvider._idPool++; @@ -132,19 +139,51 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { //#region --- making request + + private readonly _accessAllowlist = new ExtensionIdentifierMap(); + private readonly _providerIds = new Set(); + private readonly _pendingRequest = new Map(); - private readonly _chatAccessAllowList = new ExtensionIdentifierMap>(); - allowListExtensionWhile(extension: ExtensionIdentifier, promise: Promise): void { - this._chatAccessAllowList.set(extension, promise); - promise.finally(() => this._chatAccessAllowList.delete(extension)); + $updateProviderList(data: { added?: string[] | undefined; removed?: string[] | undefined }): void { + const added: string[] = []; + const removed: string[] = []; + if (data.added) { + for (const id of data.added) { + this._providerIds.add(id); + added.push(id); + } + } + if (data.removed) { + for (const id of data.removed) { + this._providerIds.delete(id); + removed.push(id); + } + } + this._onDidChangeProviders.fire({ added, removed }); + } + + getProviderIds(): string[] { + return Array.from(this._providerIds); + } + + $updateAllowlist(data: { extension: ExtensionIdentifier; allowed: boolean }[]): void { + const updated = new ExtensionIdentifierSet(); + for (const { extension, allowed } of data) { + const oldValue = this._accessAllowlist.get(extension); + if (oldValue !== allowed) { + this._accessAllowlist.set(extension, allowed); + updated.add(extension); + } + } + this._onDidChangeAccess.fire(updated); } async requestChatResponseProvider(from: ExtensionIdentifier, identifier: string): Promise { // check if a UI command is running/active - if (!this._chatAccessAllowList.has(from)) { + if (!this._accessAllowlist.get(from)) { throw new Error('Extension is NOT allowed to make chat requests'); } @@ -160,11 +199,14 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { return metadata.model; }, get isRevoked() { - return !that._chatAccessAllowList.has(from); + return !that._accessAllowlist.get(from); + }, + get onDidChangeAccess() { + return Event.signal(Event.filter(that._onDidChangeAccess.event, set => set.has(from))); }, makeRequest(messages, options, token) { - if (!that._chatAccessAllowList.has(from)) { + if (!that._accessAllowlist.get(from)) { throw new Error('Access to chat has been revoked'); } diff --git a/src/vs/workbench/contrib/chat/common/chatProvider.ts b/src/vs/workbench/contrib/chat/common/chatProvider.ts index 468e31b58b1..6305d770e3d 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 { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -44,6 +45,10 @@ export interface IChatProviderService { readonly _serviceBrand: undefined; + onDidChangeProviders: Event<{ added?: string[]; removed?: string[] }>; + + getProviders(): string[]; + lookupChatResponseProvider(identifier: string): IChatResponseProviderMetadata | undefined; registerChatResponseProvider(identifier: string, provider: IChatResponseProvider): IDisposable; @@ -56,6 +61,18 @@ export class ChatProviderService implements IChatProviderService { private readonly _providers: Map = new Map(); + private readonly _onDidChangeProviders = new Emitter<{ added?: string[]; removed?: string[] }>(); + readonly onDidChangeProviders: Event<{ added?: string[]; removed?: string[] }> = this._onDidChangeProviders.event; + + dispose() { + this._onDidChangeProviders.dispose(); + this._providers.clear(); + } + + getProviders(): string[] { + return Array.from(this._providers.keys()); + } + lookupChatResponseProvider(identifier: string): IChatResponseProviderMetadata | undefined { return this._providers.get(identifier)?.metadata; } @@ -65,7 +82,12 @@ export class ChatProviderService implements IChatProviderService { throw new Error(`Chat response provider with identifier ${identifier} is already registered.`); } this._providers.set(identifier, provider); - return toDisposable(() => this._providers.delete(identifier)); + this._onDidChangeProviders.fire({ added: [identifier] }); + return toDisposable(() => { + if (this._providers.delete(identifier)) { + this._onDidChangeProviders.fire({ removed: [identifier] }); + } + }); } fetchChatResponse(identifier: string, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise { diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts index b7bdef94fb1..2fc36577447 100644 --- a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts +++ b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts @@ -5,6 +5,8 @@ declare module 'vscode' { + // TODO@API rename to LanguageModelAccess + export interface ChatResponseStream { /** @@ -55,6 +57,7 @@ declare module 'vscode' { cancel(): void; } + /** * Represents access to using a chat provider (LLM). Access is granted and temporary, usually only valid * for the duration of an user interaction or specific time frame. @@ -67,7 +70,10 @@ declare module 'vscode' { */ readonly isRevoked: boolean; - // @API do we need an event for revocation + /** + * An event that is fired when the access to chat has been revoked or re-granted. + */ + readonly onDidChangeAccess: Event; /** * The name of the model that is used for this chat access. It is expected that the model name can @@ -91,6 +97,9 @@ declare module 'vscode' { * @param options */ makeRequest(messages: ChatMessage[], options: { [name: string]: any }, token: CancellationToken): ChatRequest; + + // TODO@API disposable? + // dispose(): void; } export namespace chat { @@ -105,7 +114,7 @@ declare module 'vscode' { export function requestChatAccess(id: string): Thenable; //@API add those - // export const chatAccesses: string[]; + // export const chatAccesses: readonly string[]; // export const onDidChangeChatAccesses: Event; } } From 7495f24f506c6223c5de2dab0f85d9abbf9f5b8b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 6 Feb 2024 15:32:07 +0100 Subject: [PATCH 0935/1897] Consider to remove `RegisterSearchViewContribution` (fix #204347) (#204494) --- .../search/browser/search.contribution.ts | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 545df09074c..0531e3a3776 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -7,7 +7,6 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as platform from 'vs/base/common/platform'; import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess'; import * as nls from 'vs/nls'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -16,8 +15,7 @@ import { Extensions as QuickAccessExtensions, IQuickAccessRegistry } from 'vs/pl import { Registry } from 'vs/platform/registry/common/platform'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { defaultQuickAccessContextKeyValue } from 'vs/workbench/browser/quickaccess'; -import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; -import { Extensions as ViewExtensions, IViewContainersRegistry, IViewDescriptor, IViewDescriptorService, IViewsRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; +import { Extensions as ViewExtensions, IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { GotoSymbolQuickAccessProvider } from 'vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess'; import { AnythingQuickAccessProvider } from 'vs/workbench/contrib/search/browser/anythingQuickAccess'; import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions'; @@ -29,7 +27,6 @@ import { SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/ import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { ISearchViewModelWorkbenchService, SearchViewModelWorkbenchService } from 'vs/workbench/contrib/search/browser/searchModel'; import { SearchSortOrder, SEARCH_EXCLUDE_CONFIG, VIEWLET_ID, ViewMode, VIEW_ID } from 'vs/workbench/services/search/common/search'; -import { Extensions, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { assertType } from 'vs/base/common/types'; import { getWorkspaceSymbols, IWorkspaceSymbol } from 'vs/workbench/contrib/search/common/search'; @@ -84,25 +81,6 @@ const viewDescriptor: IViewDescriptor = { // Register search default location to sidebar Registry.as(ViewExtensions.ViewsRegistry).registerViews([viewDescriptor], viewContainer); -// Migrate search location setting to new model -class RegisterSearchViewContribution implements IWorkbenchContribution { - - static readonly ID = 'workbench.contrib.registerSearchView'; - - constructor( - @IConfigurationService configurationService: IConfigurationService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService - ) { - const data = configurationService.inspect('search.location'); - if (data.value === 'panel') { - viewDescriptorService.moveViewToLocation(viewDescriptor, ViewContainerLocation.Panel, 'search.location'); - } - Registry.as(Extensions.ConfigurationMigration) - .registerConfigurationMigrations([{ key: 'search.location', migrateFn: (value: any) => ({ value: undefined }) }]); - } -} -registerWorkbenchContribution2(RegisterSearchViewContribution.ID, RegisterSearchViewContribution, WorkbenchPhase.BlockStartup); - // Register Quick Access Handler const quickAccessRegistry = Registry.as(QuickAccessExtensions.Quickaccess); From 1df67c370cc127ec4b4a901c5a17e62147e70ba2 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 6 Feb 2024 16:20:52 +0100 Subject: [PATCH 0936/1897] Show keybindings in comment button hovers (#204496) * Show keybindings in comment button hovers * Show submit keybinding --- .../contrib/comments/browser/commentFormActions.ts | 12 +++++++++++- .../contrib/comments/browser/commentNode.ts | 8 +++++--- .../contrib/comments/browser/commentReply.ts | 8 +++++--- .../browser/commentThreadAdditionalActions.ts | 4 +++- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentFormActions.ts b/src/vs/workbench/contrib/comments/browser/commentFormActions.ts index 279ad45bfd5..8afebd30d18 100644 --- a/src/vs/workbench/contrib/comments/browser/commentFormActions.ts +++ b/src/vs/workbench/contrib/comments/browser/commentFormActions.ts @@ -7,7 +7,10 @@ import { Button } from 'vs/base/browser/ui/button/button'; import { IAction } from 'vs/base/common/actions'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IMenu } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { CommentCommandId } from 'vs/workbench/contrib/comments/common/commentCommandIds'; export class CommentFormActions implements IDisposable { private _buttonElements: HTMLElement[] = []; @@ -15,6 +18,8 @@ export class CommentFormActions implements IDisposable { private _actions: IAction[] = []; constructor( + private readonly keybindingService: IKeybindingService, + private readonly contextKeyService: IContextKeyService, private container: HTMLElement, private actionHandler: (action: IAction) => void, private readonly maxActions?: number @@ -33,7 +38,12 @@ export class CommentFormActions implements IDisposable { this._actions = actions; for (const action of actions) { - const button = new Button(this.container, { secondary: !isPrimary, ...defaultButtonStyles }); + let keybinding = this.keybindingService.lookupKeybinding(action.id, this.contextKeyService)?.getLabel(); + if (!keybinding && isPrimary) { + keybinding = this.keybindingService.lookupKeybinding(CommentCommandId.Submit, this.contextKeyService)?.getLabel(); + } + const title = keybinding ? `${action.label} (${keybinding})` : action.label; + const button = new Button(this.container, { secondary: !isPrimary, title, ...defaultButtonStyles }); isPrimary = false; this._buttonElements.push(button.element); diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index e5b1da295f4..7197063ec80 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -49,6 +49,7 @@ import { FileAccess } from 'vs/base/common/network'; import { COMMENTS_SECTION, ICommentsConfiguration } from 'vs/workbench/contrib/comments/common/commentsConfiguration'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; class CommentsActionRunner extends ActionRunner { protected override async runAction(action: IAction, context: any[]): Promise { @@ -114,7 +115,8 @@ export class CommentNode extends Disposable { @IContextMenuService private contextMenuService: IContextMenuService, @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService private configurationService: IConfigurationService, - @IAccessibilityService private accessibilityService: IAccessibilityService + @IAccessibilityService private accessibilityService: IAccessibilityService, + @IKeybindingService private keybindingService: IKeybindingService ) { super(); @@ -610,7 +612,7 @@ export class CommentNode extends Disposable { this._commentFormActions?.setActions(menu); })); - this._commentFormActions = new CommentFormActions(container, (action: IAction): void => { + this._commentFormActions = new CommentFormActions(this.keybindingService, this._contextKeyService, container, (action: IAction): void => { const text = this._commentEditor!.getValue(); action.run({ @@ -636,7 +638,7 @@ export class CommentNode extends Disposable { this._commentEditorActions?.setActions(menu); })); - this._commentEditorActions = new CommentFormActions(container, (action: IAction): void => { + this._commentEditorActions = new CommentFormActions(this.keybindingService, this._contextKeyService, container, (action: IAction): void => { const text = this._commentEditor!.getValue(); action.run({ diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts index d4a74b20f40..d2c54a4c9f9 100644 --- a/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -29,6 +29,7 @@ import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/comment import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { LayoutableEditor, MIN_EDITOR_HEIGHT, SimpleCommentEditor, calculateEditorHeight } from './simpleCommentEditor'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; const COMMENT_SCHEME = 'comment'; let INMEM_MODEL_ID = 0; @@ -63,7 +64,8 @@ export class CommentReply extends Disposable { @ILanguageService private languageService: ILanguageService, @IModelService private modelService: IModelService, @IThemeService private themeService: IThemeService, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, + @IKeybindingService private keybindingService: IKeybindingService ) { super(); @@ -283,7 +285,7 @@ export class CommentReply extends Disposable { this._commentFormActions.setActions(menu); })); - this._commentFormActions = new CommentFormActions(container, async (action: IAction) => { + this._commentFormActions = new CommentFormActions(this.keybindingService, this._contextKeyService, container, async (action: IAction) => { await this._actionRunDelegate?.(); await action.run({ @@ -306,7 +308,7 @@ export class CommentReply extends Disposable { this._commentEditorActions.setActions(editorMenu); })); - this._commentEditorActions = new CommentFormActions(container, async (action: IAction) => { + this._commentEditorActions = new CommentFormActions(this.keybindingService, this._contextKeyService, container, async (action: IAction) => { this._actionRunDelegate?.(); action.run({ diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadAdditionalActions.ts b/src/vs/workbench/contrib/comments/browser/commentThreadAdditionalActions.ts index 34366676669..8e561d6fad2 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadAdditionalActions.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadAdditionalActions.ts @@ -15,6 +15,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions'; import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; export class CommentThreadAdditionalActions extends Disposable { private _container: HTMLElement | null; @@ -27,6 +28,7 @@ export class CommentThreadAdditionalActions exten private _contextKeyService: IContextKeyService, private _commentMenus: CommentMenus, private _actionRunDelegate: (() => void) | null, + @IKeybindingService private _keybindingService: IKeybindingService, ) { super(); @@ -78,7 +80,7 @@ export class CommentThreadAdditionalActions exten this._enableDisableMenu(menu); })); - this._commentFormActions = new CommentFormActions(container, async (action: IAction) => { + this._commentFormActions = new CommentFormActions(this._keybindingService, this._contextKeyService, container, async (action: IAction) => { this._actionRunDelegate?.(); action.run({ From a190a264a91bfda39d5555856cce9cfe56a7eaf6 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 6 Feb 2024 16:54:33 +0100 Subject: [PATCH 0937/1897] Remove hot-reload:patch-prototype-methods statements --- .../common/model/bracketPairsTextModelPart/bracketPairsImpl.ts | 2 -- src/vs/editor/common/model/tokenizationTextModelPart.ts | 2 -- .../inlineCompletions/browser/inlineCompletionsSource.ts | 2 -- .../contrib/multiDiffEditor/browser/multiDiffEditorInput.ts | 1 - 4 files changed, 7 deletions(-) diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts index 4df6b83148d..470a16f009c 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts @@ -18,8 +18,6 @@ import { BracketInfo, BracketPairInfo, BracketPairWithMinIndentationInfo, IBrack import { IModelContentChangedEvent, IModelLanguageChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent } from 'vs/editor/common/textModelEvents'; import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; -/* hot-reload:patch-prototype-methods */ - export class BracketPairsTextModelPart extends Disposable implements IBracketPairsTextModelPart { private readonly bracketPairsTree = this._register(new MutableDisposable>()); diff --git a/src/vs/editor/common/model/tokenizationTextModelPart.ts b/src/vs/editor/common/model/tokenizationTextModelPart.ts index f03d0bc60a4..61490912068 100644 --- a/src/vs/editor/common/model/tokenizationTextModelPart.ts +++ b/src/vs/editor/common/model/tokenizationTextModelPart.ts @@ -32,8 +32,6 @@ import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; import { SparseMultilineTokens } from 'vs/editor/common/tokens/sparseMultilineTokens'; import { SparseTokensStore } from 'vs/editor/common/tokens/sparseTokensStore'; -/* hot-reload:patch-prototype-methods */ - export class TokenizationTextModelPart extends TextModelPart implements ITokenizationTextModelPart { private readonly _semanticTokens: SparseTokensStore = new SparseTokensStore(this._languageService.languageIdCodec); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts index c99767faab9..3e38e9e8161 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts @@ -17,8 +17,6 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { InlineCompletionItem, InlineCompletionProviderResult, provideInlineCompletions } from 'vs/editor/contrib/inlineCompletions/browser/provideInlineCompletions'; import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; -/* hot-reload:patch-prototype-methods */ - export class InlineCompletionsSource extends Disposable { private readonly _updateOperation = this._register(new MutableDisposable()); public readonly inlineCompletions = disposableObservableValue('inlineCompletions', undefined); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 64304bb13bb..907fd6e15fe 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -33,7 +33,6 @@ import { ObservableLazyStatefulPromise } from 'vs/workbench/contrib/multiDiffEdi import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { ILanguageSupport, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -/* hot-reload:patch-prototype-methods */ export class MultiDiffEditorInput extends EditorInput implements ILanguageSupport { public static fromResourceMultiDiffEditorInput(input: IResourceMultiDiffEditorInput, instantiationService: IInstantiationService): MultiDiffEditorInput { if (!input.multiDiffSource && !input.resources) { From 08eda834f933e05d5c3bac64b147da8e2cffcb5a Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Tue, 6 Feb 2024 19:19:50 +0200 Subject: [PATCH 0938/1897] Tunnel: Extend port mapping lookup also for querystring (#203908) When running az login, the URL has localhost in redirect_uri query param. This should trigger automatic port mapping. Improve localhost port mapping to cover this case as well. Fixes #203869 --- src/vs/platform/tunnel/common/tunnel.ts | 25 ++++++++--- .../tunnel/test/common/tunnel.test.ts | 44 +++++++++++++++++++ .../webview/common/webviewPortMapping.ts | 2 +- 3 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 src/vs/platform/tunnel/test/common/tunnel.test.ts diff --git a/src/vs/platform/tunnel/common/tunnel.ts b/src/vs/platform/tunnel/common/tunnel.ts index 62c9059d2f8..bff2feb7576 100644 --- a/src/vs/platform/tunnel/common/tunnel.ts +++ b/src/vs/platform/tunnel/common/tunnel.ts @@ -141,18 +141,31 @@ export interface ITunnelService { isPortPrivileged(port: number): boolean; } -export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string; port: number } | undefined { +export function extractLocalHostUriMetaDataForPortMapping(uri: URI, { checkQuery = true } = {}): { address: string; port: number } | undefined { if (uri.scheme !== 'http' && uri.scheme !== 'https') { return undefined; } const localhostMatch = /^(localhost|127\.0\.0\.1|0\.0\.0\.0):(\d+)$/.exec(uri.authority); - if (!localhostMatch) { + if (localhostMatch) { + return { + address: localhostMatch[1], + port: +localhostMatch[2], + }; + } + if (!uri.query || !checkQuery) { return undefined; } - return { - address: localhostMatch[1], - port: +localhostMatch[2], - }; + const keyvalues = uri.query.split('&'); + for (const keyvalue of keyvalues) { + const value = keyvalue.split('=')[1]; + if (/^https?:/.exec(value)) { + const result = extractLocalHostUriMetaDataForPortMapping(URI.parse(value)); + if (result) { + return result; + } + } + } + return undefined; } export const LOCALHOST_ADDRESSES = ['localhost', '127.0.0.1', '0:0:0:0:0:0:0:1', '::1']; diff --git a/src/vs/platform/tunnel/test/common/tunnel.test.ts b/src/vs/platform/tunnel/test/common/tunnel.test.ts new file mode 100644 index 00000000000..4c763765aa0 --- /dev/null +++ b/src/vs/platform/tunnel/test/common/tunnel.test.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as assert from 'assert'; +import { URI } from 'vs/base/common/uri'; +import { extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/tunnel/common/tunnel'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; + + +suite('Tunnel', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + function portMappingDoTest(res: { address: string; port: number } | undefined, expectedAddress?: string, expectedPort?: number) { + assert.strictEqual(!expectedAddress, !res); + assert.strictEqual(res?.address, expectedAddress); + assert.strictEqual(res?.port, expectedPort); + } + + function portMappingTest(uri: string, expectedAddress?: string, expectedPort?: number) { + for (const checkQuery of [true, false]) { + portMappingDoTest(extractLocalHostUriMetaDataForPortMapping(URI.parse(uri), { checkQuery }), expectedAddress, expectedPort); + } + } + + function portMappingTestQuery(uri: string, expectedAddress?: string, expectedPort?: number) { + portMappingDoTest(extractLocalHostUriMetaDataForPortMapping(URI.parse(uri)), expectedAddress, expectedPort); + portMappingDoTest(extractLocalHostUriMetaDataForPortMapping(URI.parse(uri), { checkQuery: false }), undefined, undefined); + } + + test('portMapping', () => { + portMappingTest('file:///foo.bar/baz'); + portMappingTest('http://foo.bar:1234'); + portMappingTest('http://localhost:8080', 'localhost', 8080); + portMappingTest('https://localhost:443', 'localhost', 443); + portMappingTest('http://127.0.0.1:3456', '127.0.0.1', 3456); + portMappingTest('http://0.0.0.0:7654', '0.0.0.0', 7654); + portMappingTest('http://localhost:8080/path?foo=bar', 'localhost', 8080); + portMappingTest('http://localhost:8080/path?foo=http%3A%2F%2Flocalhost%3A8081', 'localhost', 8080); + portMappingTestQuery('http://foo.bar/path?url=http%3A%2F%2Flocalhost%3A8081', 'localhost', 8081); + portMappingTestQuery('http://foo.bar/path?url=http%3A%2F%2Flocalhost%3A8081&url2=http%3A%2F%2Flocalhost%3A8082', 'localhost', 8081); + portMappingTestQuery('http://foo.bar/path?url=http%3A%2F%2Fmicrosoft.com%2Fbad&url2=http%3A%2F%2Flocalhost%3A8081', 'localhost', 8081); + }); +}); diff --git a/src/vs/platform/webview/common/webviewPortMapping.ts b/src/vs/platform/webview/common/webviewPortMapping.ts index b47339d28fa..1ebc8534a28 100644 --- a/src/vs/platform/webview/common/webviewPortMapping.ts +++ b/src/vs/platform/webview/common/webviewPortMapping.ts @@ -29,7 +29,7 @@ export class WebviewPortMappingManager implements IDisposable { public async getRedirect(resolveAuthority: IAddress | null | undefined, url: string): Promise { const uri = URI.parse(url); - const requestLocalHostInfo = extractLocalHostUriMetaDataForPortMapping(uri); + const requestLocalHostInfo = extractLocalHostUriMetaDataForPortMapping(uri, { checkQuery: false }); if (!requestLocalHostInfo) { return undefined; } From cda51f6ab4a708a83dd2522d2616963c60f4509a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 6 Feb 2024 14:37:22 -0300 Subject: [PATCH 0939/1897] Replace chat "command followups" with command button content (#204512) * Delete CommandFollowups and make inline chat use its own types for command followups * Add command button, render it properly * Manage the lifecycle of commands from chat command buttons * Handle stale session command in type converter * Fix --- .../lib/stylelint/vscode-known-variables.json | 1 + .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 8 +- .../api/common/extHostChatAgents2.ts | 35 ++++-- .../workbench/api/common/extHostInlineChat.ts | 15 ++- .../api/common/extHostTypeConverters.ts | 35 ++++-- .../contrib/chat/browser/chatFollowups.ts | 3 +- .../contrib/chat/browser/chatInputPart.ts | 6 +- .../contrib/chat/browser/chatListRenderer.ts | 82 ++++++------- .../contrib/chat/browser/chatWidget.ts | 4 +- .../contrib/chat/browser/media/chat.css | 30 ++--- .../contrib/chat/common/chatAgents.ts | 4 +- .../contrib/chat/common/chatModel.ts | 32 +++-- .../contrib/chat/common/chatService.ts | 26 ++-- .../contrib/chat/common/chatServiceImpl.ts | 4 +- .../contrib/chat/common/chatViewModel.ts | 22 ++-- .../inlineChat/browser/inlineChatWidget.ts | 111 +++++++++--------- .../contrib/inlineChat/common/inlineChat.ts | 22 +++- .../vscode.proposed.chatAgents2.d.ts | 25 ++-- .../vscode.proposed.chatAgents2Additions.d.ts | 2 +- .../vscode.proposed.defaultChatAgent.d.ts | 2 +- .../vscode.proposed.interactive.d.ts | 2 +- 22 files changed, 262 insertions(+), 211 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 9c9786bcf8c..f6d81111246 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -46,6 +46,7 @@ "--vscode-chat-requestBorder", "--vscode-chat-slashCommandBackground", "--vscode-chat-slashCommandForeground", + "--vscode-chat-list-background", "--vscode-checkbox-background", "--vscode-checkbox-border", "--vscode-checkbox-foreground", diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 7cffe74fd74..f41ba74a6e6 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -208,7 +208,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I 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 extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService)); + const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService, extHostCommands)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol)); const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index d24c57433a8..8abc5dca98d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -53,10 +53,10 @@ import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/cal import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatDynamicRequest, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatDynamicRequest, IChatProgress, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; import { DebugConfigurationProviderTriggerKind, MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext } from 'vs/workbench/contrib/debug/common/debug'; -import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatFollowup, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; @@ -1225,7 +1225,7 @@ export interface ExtHostChatAgentsShape2 { $acceptAction(handle: number, sessionId: string, requestId: string, action: IChatUserActionEvent): void; $invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise; $provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined>; - $provideSampleQuestions(handle: number, token: CancellationToken): Promise; + $provideSampleQuestions(handle: number, token: CancellationToken): Promise; $releaseSession(sessionId: string): void; } @@ -1251,7 +1251,7 @@ export type IInlineChatResponseDto = Dto; $provideResponse(handle: number, session: IInlineChatSession, request: IInlineChatRequest, token: CancellationToken): Promise; - $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise; + $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise; $handleFeedback(handle: number, sessionId: number, responseId: number, kind: InlineChatResponseFeedbackKind): void; $releaseSession(handle: number, sessionId: number): void; } diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index cb1333f212b..79b7cb1d63a 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -9,6 +9,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; @@ -16,10 +17,11 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; +import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { IChatAgentCommand, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatFollowup, IChatProgress, IChatReplyFollowup, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatProgress, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import type * as vscode from 'vscode'; @@ -35,7 +37,8 @@ class ChatAgentResponseStream { private readonly _extension: IExtensionDescription, private readonly _request: IChatAgentRequest, private readonly _proxy: MainThreadChatAgentsShape2, - @ILogService private readonly _logService: ILogService, + private readonly _logService: ILogService, + private readonly _commandsConverter: CommandsConverter, ) { } close() { @@ -99,6 +102,14 @@ class ChatAgentResponseStream { _report(dto); return this; }, + button(command) { + throwIfDone(this.anchor); + _report({ + kind: 'command', + command: that._commandsConverter.toInternal(command, new DisposableStore()) // ?? + }); + return this; + }, progress(value) { throwIfDone(this.progress); const part = new extHostTypes.ChatResponseProgressPart(value); @@ -150,6 +161,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { mainContext: IMainContext, private readonly _extHostChatProvider: ExtHostChatProvider, private readonly _logService: ILogService, + private readonly commands: ExtHostCommands, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); } @@ -175,7 +187,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { this._extHostChatProvider.$updateAllowlist([{ extension: agent.extension.identifier, allowed: true }]); - const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService); + const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService, this.commands.converter); try { const convertedHistory = await this.prepareHistory(agent, request, context); const task = agent.invoke( @@ -220,7 +232,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { || h.result; return { request: typeConvert.ChatAgentRequest.to(h.request), - response: coalesce(h.response.map(r => typeConvert.ChatResponseProgress.toProgressContent(r))), + response: coalesce(h.response.map(r => typeConvert.ChatResponseProgress.toProgressContent(r, this.commands.converter))), result } satisfies vscode.ChatAgentHistoryEntry; }))); @@ -289,7 +301,14 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { // handled by $acceptFeedback return; } - agent.acceptAction(Object.freeze({ action: action.action, result })); + + if (action.action.kind === 'command') { + const action2: vscode.ChatAgentCommandAction = { kind: 'command', commandButton: typeConvert.ChatResponseProgress.toProgressContent(action.action.commandButton, this.commands.converter) as vscode.ChatAgentCommandButton }; + agent.acceptAction(Object.freeze({ action: action2, result })); + return; + } else { + agent.acceptAction(Object.freeze({ action: action.action, result })); + } } async $invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise { @@ -311,7 +330,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { return await agent.provideWelcomeMessage(token); } - async $provideSampleQuestions(handle: number, token: CancellationToken): Promise { + async $provideSampleQuestions(handle: number, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { return; @@ -409,7 +428,7 @@ class ExtHostChatAgent { }); } - async provideSampleQuestions(token: CancellationToken): Promise { + async provideSampleQuestions(token: CancellationToken): Promise { if (!this._welcomeMessageProvider || !this._welcomeMessageProvider.provideSampleQuestions) { return []; } @@ -418,7 +437,7 @@ class ExtHostChatAgent { return []; } - return content?.map(f => typeConvert.ChatReplyFollowup.from(f)); + return content?.map(f => typeConvert.ChatFollowup.from(f)); } get apiAgent(): vscode.ChatAgent2 { diff --git a/src/vs/workbench/api/common/extHostInlineChat.ts b/src/vs/workbench/api/common/extHostInlineChat.ts index 47ec20ccea2..9e8478b98f3 100644 --- a/src/vs/workbench/api/common/extHostInlineChat.ts +++ b/src/vs/workbench/api/common/extHostInlineChat.ts @@ -3,23 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { toDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; +import { IPosition } from 'vs/editor/common/core/position'; +import { IRange } from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; -import { IInlineChatSession, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostInlineChatShape, IInlineChatResponseDto, IMainContext, MainContext, MainThreadInlineChatShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; +import { IInlineChatFollowup, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import type * as vscode from 'vscode'; -import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; -import { IRange } from 'vs/editor/common/core/range'; -import { IPosition } from 'vs/editor/common/core/position'; -import { raceCancellation } from 'vs/base/common/async'; -import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; class ProviderWrapper { @@ -228,14 +227,14 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { } } - async $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise { + async $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise { const entry = this._inputProvider.get(handle); const sessionData = this._inputSessions.get(sessionId); const response = sessionData?.responses[responseId]; if (entry && response && entry.provider.provideFollowups) { const task = Promise.resolve(entry.provider.provideFollowups(sessionData.session, response, token)); const followups = await raceCancellation(task, token); - return followups?.map(typeConvert.ChatFollowup.from); + return followups?.map(typeConvert.ChatInlineFollowup.from); } return undefined; } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 4345dee16d7..9ce83ae8888 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -36,9 +36,9 @@ import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/edit import { IViewBadge } from 'vs/workbench/common/views'; import { IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents'; import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatReplyFollowup, IChatResponseCommandFollowup, IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; -import { InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IInlineChatCommandFollowup, IInlineChatFollowup, IInlineChatReplyFollowup, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import * as search from 'vs/workbench/contrib/search/common/search'; @@ -2190,8 +2190,8 @@ export namespace DataTransfer { } } -export namespace ChatReplyFollowup { - export function from(followup: vscode.ChatAgentReplyFollowup | vscode.InteractiveEditorReplyFollowup): IChatReplyFollowup { +export namespace ChatFollowup { + export function from(followup: vscode.ChatAgentFollowup): IChatFollowup { return { kind: 'reply', message: followup.message, @@ -2201,21 +2201,25 @@ export namespace ChatReplyFollowup { } } -export namespace ChatFollowup { - export function from(followup: string | vscode.ChatAgentFollowup): IChatFollowup { - if (typeof followup === 'string') { - return { title: followup, message: followup, kind: 'reply' }; - } else if ('commandId' in followup) { - return { +export namespace ChatInlineFollowup { + export function from(followup: vscode.InteractiveEditorFollowup): IInlineChatFollowup { + if ('commandId' in followup) { + return { kind: 'command', title: followup.title ?? '', commandId: followup.commandId ?? '', when: followup.when ?? '', args: followup.args - }; + } satisfies IInlineChatCommandFollowup; } else { - return ChatReplyFollowup.from(followup); + return { + kind: 'reply', + message: followup.message, + title: followup.title, + tooltip: followup.tooltip, + } satisfies IInlineChatReplyFollowup; } + } } @@ -2513,7 +2517,7 @@ export namespace ChatResponseProgress { } } - export function toProgressContent(progress: extHostProtocol.IChatContentProgressDto): vscode.ChatAgentContentProgress | undefined { + export function toProgressContent(progress: extHostProtocol.IChatContentProgressDto, commandsConverter: Command.ICommandsConverter): vscode.ChatAgentContentProgress | undefined { switch (progress.kind) { case 'markdownContent': // For simplicity, don't sent back the 'extended' types, so downgrade markdown to just some text @@ -2528,6 +2532,11 @@ export namespace ChatResponseProgress { }; case 'treeData': return { treeData: revive(progress.treeData) }; + case 'command': + // If the command isn't in the converter, then this session may have been restored, and the command args don't exist anymore + return { + command: commandsConverter.fromInternal(progress.command) ?? { command: progress.command.id, title: progress.command.title }, + }; default: // Unknown type, eg something in history that was removed? Ignore return undefined; diff --git a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts index 6c6e0d8f6d7..70e92d3f5b1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts +++ b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts @@ -10,10 +10,11 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { IInlineChatFollowup } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; const $ = dom.$; -export class ChatFollowups extends Disposable { +export class ChatFollowups extends Disposable { constructor( container: HTMLElement, followups: T[], diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index ce8a6081bea..06a5e2bb2fd 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -37,7 +37,7 @@ import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_IN_CHAT_INPUT } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { chatAgentLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatReplyFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IChatHistoryEntry, IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService'; import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; @@ -64,7 +64,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private _onDidBlur = this._register(new Emitter()); readonly onDidBlur = this._onDidBlur.event; - private _onDidAcceptFollowup = this._register(new Emitter<{ followup: IChatReplyFollowup; response: IChatResponseViewModel | undefined }>()); + private _onDidAcceptFollowup = this._register(new Emitter<{ followup: IChatFollowup; response: IChatResponseViewModel | undefined }>()); readonly onDidAcceptFollowup = this._onDidAcceptFollowup.event; private inputEditorHeight = 0; @@ -338,7 +338,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } } - async renderFollowups(items: IChatReplyFollowup[] | undefined, response: IChatResponseViewModel | undefined): Promise { + async renderFollowups(items: IChatFollowup[] | undefined, response: IChatResponseViewModel | undefined): Promise { if (!this.options.renderFollowups) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index ba0918f7875..fc5738c28f3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -58,7 +58,7 @@ import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents' import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatContentReference, IChatProgressMessage, IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatCommandButton, IChatContentReference, IChatFollowup, IChatProgressMessage, IChatResponseProgressFileTreeData, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatProgressMessageRenderData, IChatRenderData, IChatResponseMarkdownRenderData, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView'; @@ -110,8 +110,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer()); - readonly onDidClickFollowup: Event = this._onDidClickFollowup.event; + protected readonly _onDidClickFollowup = this._register(new Emitter()); + readonly onDidClickFollowup: Event = this._onDidClickFollowup.event; protected readonly _onDidChangeItemHeight = this._register(new Emitter()); readonly onDidChangeItemHeight: Event = this._onDidChangeItemHeight.event; @@ -133,12 +133,11 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - this.chatService.notifyUserAction({ - providerId: element.providerId, - agentId: element.agent?.id, - sessionId: element.sessionId, - requestId: element.requestId, - action: { - kind: 'command', - command: followup, - } - }); - return this.commandService.executeCommand(followup.commandId, ...(followup.args ?? [])); - }, - templateData.contextKeyService)); - } - const newHeight = templateData.rowContainer.offsetHeight; const fireEvent = !element.currentRenderedHeight || element.currentRenderedHeight !== newHeight; element.currentRenderedHeight = newHeight; @@ -551,6 +529,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.commandService.executeCommand(commandButton.command.id, ...(commandButton.command.arguments ?? [])))); + return { + dispose() { + disposables.dispose(); + }, + element: container + }; + } + private renderMarkdown(markdown: IMarkdownString, element: ChatTreeItem, templateData: IChatListItemTemplate, fillInIncompleteTokens = false): IMarkdownRenderResult { const disposables = new DisposableStore(); let codeBlockIndex = 0; @@ -988,17 +987,6 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider followup.title).join(', ')); - } const fileTreeCount = element.response.value.filter((v) => !('value' in v))?.length ?? 0; let fileTreeCountHint = ''; switch (fileTreeCount) { @@ -1023,7 +1011,7 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider, i: number): boolean { return items.slice(i).every(isProgressMessage); } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 6d421c1974e..d0ee8506695 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -30,7 +30,7 @@ import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION, CONTEXT_RESPONSE_FILTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; @@ -292,7 +292,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } } - private async renderFollowups(items: IChatReplyFollowup[] | undefined, response?: IChatResponseViewModel): Promise { + private async renderFollowups(items: IChatFollowup[] | undefined, response?: IChatResponseViewModel): Promise { this.inputPart.renderFollowups(items, response); if (this.bodyDimension) { diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 7b8c230b92f..14361496659 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -418,20 +418,6 @@ align-items: start; } -.interactive-session-followups .monaco-button { - text-align: left; - width: initial; -} - -.interactive-session-followups .monaco-button .codicon { - margin-left: 0; - margin-top: 1px; -} - -.interactive-item-container .interactive-response-followups .monaco-button { - padding: 4px 8px; -} - .interactive-session .interactive-input-part .interactive-input-followups { margin: 0px 20px; } @@ -626,3 +612,19 @@ font-size: 14px; color: var(--vscode-debugIcon-startForeground) !important; } + +.interactive-item-container .chat-command-button { + display: flex; + margin-bottom: 16px; +} + +.interactive-item-container .chat-command-button .monaco-button { + text-align: left; + width: initial; + padding: 4px 8px; +} + +.interactive-item-container .chat-command-button .monaco-button .codicon { + margin-left: 0; + margin-top: 1px; +} diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 6b71960d119..cd4e3b1b489 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { ProviderResult } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; //#region agent service, commands etc @@ -35,7 +35,7 @@ export interface IChatAgent extends IChatAgentData { lastSlashCommands?: IChatAgentCommand[]; provideSlashCommands(token: CancellationToken): Promise; provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>; - provideSampleQuestions?(token: CancellationToken): ProviderResult; + provideSampleQuestions?(token: CancellationToken): ProviderResult; } export interface IChatAgentCommand { diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 235c547b2e4..cbf36af4e56 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -16,7 +16,7 @@ import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { ILogService } from 'vs/platform/log/common/log'; import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatContent, IChatContentInlineReference, IChatContentReference, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext, IChatCommandButton } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; export interface IChatRequestVariableData { @@ -33,7 +33,7 @@ export interface IChatRequestModel { readonly username: string; readonly avatarIconUri?: URI; readonly session: IChatModel; - readonly message: IParsedChatRequest | IChatReplyFollowup; + readonly message: IParsedChatRequest | IChatFollowup; readonly variableData: IChatRequestVariableData; readonly response?: IChatResponseModel; } @@ -43,7 +43,8 @@ export type IChatProgressResponseContent = | IChatAgentMarkdownContentWithVulnerability | IChatTreeData | IChatContentInlineReference - | IChatProgressMessage; + | IChatProgressMessage + | IChatCommandButton; export type IChatProgressRenderableResponseContent = Exclude; @@ -68,6 +69,8 @@ export interface IChatResponseModel { readonly response: IResponse; readonly isComplete: boolean; readonly isCanceled: boolean; + /** A stale response is one that has been persisted and rehydrated, so e.g. Commands that have their arguments stored in the EH are gone. */ + readonly isStale: boolean; readonly vote: InteractiveSessionVoteDirection | undefined; readonly followups?: IChatFollowup[] | undefined; readonly errorDetails?: IChatResponseErrorDetails; @@ -159,7 +162,7 @@ export class Response implements IResponse { } this._updateRepr(quiet); - } else if (progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage') { + } else { this._responseParts.push(progress); this._updateRepr(quiet); } @@ -171,6 +174,8 @@ export class Response implements IResponse { return ''; } else if (part.kind === 'inlineReference') { return basename('uri' in part.inlineReference ? part.inlineReference.uri : part.inlineReference); + } else if (part.kind === 'command') { + return part.command.title; } else { return part.content.value; } @@ -255,6 +260,11 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this._progressMessages; } + private _isStale: boolean = false; + public get isStale(): boolean { + return this._isStale; + } + constructor( _response: IMarkdownString | ReadonlyArray, public readonly session: ChatModel, @@ -268,6 +278,10 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel followups?: ReadonlyArray ) { super(); + + // If we are creating a response with some existing content, consider it stale + this._isStale = Array.isArray(_response) && (_response.length !== 0 || isMarkdownString(_response) && _response.value.length !== 0); + this._followups = followups ? [...followups] : undefined; this._response = new Response(_response); this._register(this._response.onDidChangeValue(() => this._onDidChange.fire())); @@ -368,7 +382,7 @@ export interface ISerializableChatRequestData { export interface IExportableChatData { providerId: string; - welcomeMessage: (string | IChatReplyFollowup[])[] | undefined; + welcomeMessage: (string | IChatFollowup[])[] | undefined; requests: ISerializableChatRequestData[]; requesterUsername: string; responderUsername: string; @@ -646,7 +660,7 @@ export class ChatModel extends Disposable implements IChatModel { if (progress.kind === 'vulnerability') { request.response.updateContent({ kind: 'markdownVuln', content: { value: progress.content }, vulnerabilities: progress.vulnerabilities }, quiet); - } else if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage') { + } else if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage' || progress.kind === 'command') { request.response.updateContent(progress, quiet); } else if (progress.kind === 'usedContext' || progress.kind === 'reference') { request.response.applyReference(progress); @@ -772,12 +786,12 @@ export class ChatModel extends Disposable implements IChatModel { } } -export type IChatWelcomeMessageContent = IMarkdownString | IChatReplyFollowup[]; +export type IChatWelcomeMessageContent = IMarkdownString | IChatFollowup[]; export interface IChatWelcomeMessageModel { readonly id: string; readonly content: IChatWelcomeMessageContent[]; - readonly sampleQuestions: IChatReplyFollowup[]; + readonly sampleQuestions: IChatFollowup[]; readonly username: string; readonly avatarIconUri?: URI; @@ -794,7 +808,7 @@ export class ChatWelcomeMessageModel implements IChatWelcomeMessageModel { constructor( private readonly session: ChatModel, public readonly content: IChatWelcomeMessageContent[], - public readonly sampleQuestions: IChatReplyFollowup[] + public readonly sampleQuestions: IChatFollowup[] ) { this._id = 'welcome_' + ChatWelcomeMessageModel.nextId++; } diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 9a9c1196e72..bf5c78b9ef2 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -9,7 +9,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { Location, ProviderResult } from 'vs/editor/common/languages'; +import { Command, Location, ProviderResult } from 'vs/editor/common/languages'; import { FileType } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; @@ -142,6 +142,11 @@ export interface IChatAgentMarkdownContentWithVulnerability { kind: 'markdownVuln'; } +export interface IChatCommandButton { + command: Command; + kind: 'command'; +} + export type IChatProgress = | IChatContent | IChatMarkdownContent @@ -152,30 +157,21 @@ export type IChatProgress = | IChatContentReference | IChatContentInlineReference | IChatAgentDetection - | IChatProgressMessage; + | IChatProgressMessage + | IChatCommandButton; export interface IChatProvider { readonly id: string; prepareSession(token: CancellationToken): ProviderResult; } -export interface IChatReplyFollowup { +export interface IChatFollowup { kind: 'reply'; message: string; title?: string; tooltip?: string; } -export interface IChatResponseCommandFollowup { - kind: 'command'; - commandId: string; - args?: any[]; - title: string; // supports codicon strings - when?: string; -} - -export type IChatFollowup = IChatReplyFollowup | IChatResponseCommandFollowup; - // Name has to match the one in vscode.d.ts for some reason export enum InteractiveSessionVoteDirection { Down = 0, @@ -218,12 +214,12 @@ export interface IChatTerminalAction { export interface IChatCommandAction { kind: 'command'; - command: IChatResponseCommandFollowup; + commandButton: IChatCommandButton; } export interface IChatFollowupAction { kind: 'followUp'; - followup: IChatReplyFollowup; + followup: IChatFollowup; } export interface IChatBugReportAction { diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index c55c438f228..5d52f07555b 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -235,8 +235,8 @@ export class ChatService extends Disposable implements IChatService { newFile: !!action.action.newFile }); } else if (action.action.kind === 'command') { - const command = CommandsRegistry.getCommand(action.action.command.commandId); - const commandId = command ? action.action.command.commandId : 'INVALID'; + const command = CommandsRegistry.getCommand(action.action.commandButton.command.id); + const commandId = command ? action.action.commandButton.command.id : 'INVALID'; this.telemetryService.publicLog2('interactiveSessionCommand', { providerId: action.providerId, commandId diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index e3712490d9a..d55a8b0c07f 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -11,7 +11,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModelInitState, IChatModel, IChatRequestModel, IChatResponseModel, IChatWelcomeMessageContent, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatContentReference, IChatProgressMessage, IChatReplyFollowup, IChatResponseCommandFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatContentReference, IChatProgressMessage, IChatFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection, IChatCommandButton } from 'vs/workbench/contrib/chat/common/chatService'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; export function isRequestVM(item: unknown): item is IChatRequestViewModel { @@ -60,7 +60,7 @@ export interface IChatRequestViewModel { readonly dataId: string; readonly username: string; readonly avatarIconUri?: URI; - readonly message: IParsedChatRequest | IChatReplyFollowup; + readonly message: IParsedChatRequest | IChatFollowup; readonly messageText: string; currentRenderedHeight: number | undefined; } @@ -88,7 +88,7 @@ export interface IChatProgressMessageRenderData { isLast: boolean; } -export type IChatRenderData = IChatResponseProgressFileTreeData | IChatResponseMarkdownRenderData | IChatProgressMessageRenderData; +export type IChatRenderData = IChatResponseProgressFileTreeData | IChatResponseMarkdownRenderData | IChatProgressMessageRenderData | IChatCommandButton; export interface IChatResponseRenderData { renderedParts: IChatRenderData[]; } @@ -118,9 +118,9 @@ export interface IChatResponseViewModel { readonly progressMessages: ReadonlyArray; readonly isComplete: boolean; readonly isCanceled: boolean; + readonly isStale: boolean; readonly vote: InteractiveSessionVoteDirection | undefined; - readonly replyFollowups?: IChatReplyFollowup[]; - readonly commandFollowups?: IChatResponseCommandFollowup[]; + readonly replyFollowups?: IChatFollowup[]; readonly errorDetails?: IChatResponseErrorDetails; readonly contentUpdateTimings?: IChatLiveUpdateData; renderData?: IChatResponseRenderData; @@ -331,11 +331,7 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi } get replyFollowups() { - return this._model.followups?.filter((f): f is IChatReplyFollowup => f.kind === 'reply'); - } - - get commandFollowups() { - return this._model.followups?.filter((f): f is IChatResponseCommandFollowup => f.kind === 'command'); + return this._model.followups?.filter((f): f is IChatFollowup => f.kind === 'reply'); } get errorDetails() { @@ -350,6 +346,10 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.requestId; } + get isStale() { + return this._model.isStale; + } + renderData: IChatResponseRenderData | undefined = undefined; agentAvatarHasBeenRendered?: boolean; currentRenderedHeight: number | undefined; @@ -436,6 +436,6 @@ export interface IChatWelcomeMessageViewModel { readonly username: string; readonly avatarIconUri?: URI; readonly content: IChatWelcomeMessageContent[]; - readonly sampleQuestions: IChatReplyFollowup[]; + readonly sampleQuestions: IChatFollowup[]; currentRenderedHeight?: number; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 36da2489a01..7ceff7252b2 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -3,66 +3,65 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./inlineChat'; -import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; -import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; -import { IRange, Range } from 'vs/editor/common/core/range'; -import { localize } from 'vs/nls'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET_STATUS, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, IInlineChatSlashCommand, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_RESPONSE_FOCUSED, ACTION_ACCEPT_CHANGES, MENU_INLINE_CHAT_WIDGET } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { Dimension, addDisposableListener, getActiveElement, getTotalHeight, getTotalWidth, h, reset } from 'vs/base/browser/dom'; -import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event'; -import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; -import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; -import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; -import { IModelService } from 'vs/editor/common/services/model'; -import { URI } from 'vs/base/common/uri'; -import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; -import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; -import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; -import { Position } from 'vs/editor/common/core/position'; -import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; -import { CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages'; -import { ResourceLabel } from 'vs/workbench/browser/labels'; -import { FileKind } from 'vs/platform/files/common/files'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { LanguageSelector } from 'vs/editor/common/languageSelector'; -import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; -import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; -import { LineRange } from 'vs/editor/common/core/lineRange'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { ExpansionState, HunkData } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; import * as aria from 'vs/base/browser/ui/aria/aria'; +import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; +import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event'; +import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { Lazy } from 'vs/base/common/lazy'; +import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { assertType } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import 'vs/css!./inlineChat'; +import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; +import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; +import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; +import { LineRange } from 'vs/editor/common/core/lineRange'; +import { Position } from 'vs/editor/common/core/position'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; +import { LanguageSelector } from 'vs/editor/common/languageSelector'; +import { CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages'; +import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; +import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; +import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; +import { localize } from 'vs/nls'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; -import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; +import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { FileKind } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ILogService } from 'vs/platform/log/common/log'; +import { editorBackground, editorForeground, inputBackground } from 'vs/platform/theme/common/colorRegistry'; +import { ResourceLabel } from 'vs/workbench/browser/labels'; +import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; -import { assertType } from 'vs/base/common/types'; -import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; -import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; -import { MenuId } from 'vs/platform/actions/common/actions'; -import { editorForeground, inputBackground, editorBackground } from 'vs/platform/theme/common/colorRegistry'; -import { Lazy } from 'vs/base/common/lazy'; -import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; -import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; -import { ILogService } from 'vs/platform/log/common/log'; -import { ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; -import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; -import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; +import { ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; +import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; +import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { ExpansionState, HunkData } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; +import { ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_VISIBLE, IInlineChatFollowup, IInlineChatSlashCommand, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_FEEDBACK, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -631,9 +630,9 @@ export class InlineChatWidget { return resultingAppender; } - updateFollowUps(items: IChatFollowup[], onFollowup: (followup: IChatFollowup) => void): void; + updateFollowUps(items: IInlineChatFollowup[], onFollowup: (followup: IInlineChatFollowup) => void): void; updateFollowUps(items: undefined): void; - updateFollowUps(items: IChatFollowup[] | undefined, onFollowup?: ((followup: IChatFollowup) => void)) { + updateFollowUps(items: IInlineChatFollowup[] | undefined, onFollowup?: ((followup: IInlineChatFollowup) => void)) { this._followUpDisposables.clear(); this._elements.followUps.classList.toggle('hidden', !items || items.length === 0); reset(this._elements.followUps); diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 0794a0f8a15..8b1d17efa8e 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; +import { Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IRange } from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; -import { Event } from 'vs/base/common/event'; import { ProviderResult, TextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; @@ -20,7 +20,6 @@ import { IProgress } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; import { diffInserted, diffRemoved, editorHoverHighlight, editorWidgetBackground, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { Extensions as ExtensionsMigration, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; -import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { URI } from 'vs/base/common/uri'; export interface IInlineChatSlashCommand { @@ -98,6 +97,23 @@ export const enum InlineChatResponseFeedbackKind { Bug = 4 } +export interface IInlineChatReplyFollowup { + kind: 'reply'; + message: string; + title?: string; + tooltip?: string; +} + +export interface IInlineChatCommandFollowup { + kind: 'command'; + commandId: string; + args?: any[]; + title: string; // supports codicon strings + when?: string; +} + +export type IInlineChatFollowup = IInlineChatReplyFollowup | IInlineChatCommandFollowup; + export interface IInlineChatSessionProvider { debugName: string; @@ -108,7 +124,7 @@ export interface IInlineChatSessionProvider { provideResponse(item: IInlineChatSession, request: IInlineChatRequest, progress: IProgress, token: CancellationToken): ProviderResult; - provideFollowups?(session: IInlineChatSession, response: IInlineChatResponse, token: CancellationToken): ProviderResult; + provideFollowups?(session: IInlineChatSession, response: IInlineChatResponse, token: CancellationToken): ProviderResult; handleInlineChatResponseFeedback?(session: IInlineChatSession, response: IInlineChatResponse, kind: InlineChatResponseFeedbackKind): void; } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index b7cda17484a..bfc83f254df 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -150,19 +150,10 @@ declare module 'vscode' { provideSubCommands(token: CancellationToken): ProviderResult; } - // TODO@API This should become a progress type, and use vscode.Command - // TODO@API what's the when-property for? how about not returning it in the first place? - export interface ChatAgentCommandFollowup { - commandId: string; - args?: any[]; - title: string; // supports codicon strings - when?: string; - } - /** * A followup question suggested by the model. */ - export interface ChatAgentReplyFollowup { + export interface ChatAgentFollowup { /** * The message to send to the chat. */ @@ -179,8 +170,6 @@ declare module 'vscode' { title?: string; } - export type ChatAgentFollowup = ChatAgentCommandFollowup | ChatAgentReplyFollowup; - /** * Will be invoked once after each request to get suggested followup questions to show the user. The user can click the followup to send it to the chat. */ @@ -287,6 +276,8 @@ declare module 'vscode' { anchor(value: Uri | Location, title?: string): ChatAgentResponseStream; + button(command: Command): ChatAgentResponseStream; + // TODO@API this influences the rendering, it inserts new lines which is likely a bug progress(value: string): ChatAgentResponseStream; @@ -347,7 +338,8 @@ declare module 'vscode' { export type ChatAgentContentProgress = | ChatAgentContent | ChatAgentFileTree - | ChatAgentInlineContentReference; + | ChatAgentInlineContentReference + | ChatAgentCommandButton; /** * @deprecated use ChatAgentResponseStream instead @@ -394,6 +386,13 @@ declare module 'vscode' { title?: string; } + /** + * Displays a {@link Command command} as a button in the chat response. + */ + export interface ChatAgentCommandButton { + command: Command; + } + /** * A piece of the chat response's content. Will be merged with other progress pieces as needed, and rendered as markdown. */ diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index c56dfc45c89..b2795ef4b3a 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -113,7 +113,7 @@ declare module 'vscode' { export interface ChatAgentCommandAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'command'; - command: ChatAgentCommandFollowup; + commandButton: ChatAgentCommandButton; } export interface ChatAgentSessionFollowupAction { diff --git a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts b/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts index 333783f844a..e40a66c044c 100644 --- a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts +++ b/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts @@ -9,7 +9,7 @@ declare module 'vscode' { export interface ChatAgentWelcomeMessageProvider { provideWelcomeMessage(token: CancellationToken): ProviderResult; - provideSampleQuestions?(token: CancellationToken): ProviderResult; + provideSampleQuestions?(token: CancellationToken): ProviderResult; } export interface ChatAgent2 { diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index fd8dc51d014..5cfa6c0741e 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -121,7 +121,7 @@ declare module 'vscode' { inputPlaceholder?: string; } - export type InteractiveWelcomeMessageContent = string | MarkdownString | ChatAgentReplyFollowup[]; + export type InteractiveWelcomeMessageContent = string | MarkdownString | ChatAgentFollowup[]; export interface InteractiveSessionProvider { prepareSession(token: CancellationToken): ProviderResult; From ebed3f2345cadd89179eb593154498450285e500 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 6 Feb 2024 09:46:57 -0800 Subject: [PATCH 0940/1897] testing: adopt coverage icons (#204514) --- src/vs/base/common/codicons.ts | 3 +++ src/vs/workbench/contrib/testing/browser/icons.ts | 8 +++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 09ee9693d23..c8ab637ebeb 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -591,6 +591,9 @@ export const Codicon = { gitStash: register('git-stash', 0xec26), gitStashApply: register('git-stash-apply', 0xec27), gitStashPop: register('git-stash-pop', 0xec28), + coverage: register('coverage', 0xec2e), + runAllCoverage: register('run-all-coverage', 0xec2d), + runCoverage: register('run-all-coverage', 0xec2c), // derived icons, that could become separate icons diff --git a/src/vs/workbench/contrib/testing/browser/icons.ts b/src/vs/workbench/contrib/testing/browser/icons.ts index e237f5afbaa..81b61817d3f 100644 --- a/src/vs/workbench/contrib/testing/browser/icons.ts +++ b/src/vs/workbench/contrib/testing/browser/icons.ts @@ -19,10 +19,8 @@ export const testingRunAllIcon = registerIcon('testing-run-all-icon', Codicon.ru // todo: https://github.com/microsoft/vscode-codicons/issues/72 export const testingDebugAllIcon = registerIcon('testing-debug-all-icon', Codicon.debugAltSmall, localize('testingDebugAllIcon', 'Icon of the "debug all tests" action.')); export const testingDebugIcon = registerIcon('testing-debug-icon', Codicon.debugAltSmall, localize('testingDebugIcon', 'Icon of the "debug test" action.')); -// todo: https://github.com/microsoft/vscode-codicons/issues/205 -export const testingCoverageIcon = registerIcon('testing-coverage-icon', Codicon.heartFilled, localize('testingCoverageIcon', 'Icon of the "run test with coverage" action.')); -// todo: https://github.com/microsoft/vscode-codicons/issues/205 -export const testingCoverageAllIcon = registerIcon('testing-coverage-all-icon', Codicon.heartFilled, localize('testingRunAllWithCoverageIcon', 'Icon of the "run all tests with coverage" action.')); +export const testingCoverageIcon = registerIcon('testing-coverage-icon', Codicon.runCoverage, localize('testingCoverageIcon', 'Icon of the "run test with coverage" action.')); +export const testingCoverageAllIcon = registerIcon('testing-coverage-all-icon', Codicon.runAllCoverage, localize('testingRunAllWithCoverageIcon', 'Icon of the "run all tests with coverage" action.')); export const testingCancelIcon = registerIcon('testing-cancel-icon', Codicon.debugStop, localize('testingCancelIcon', 'Icon to cancel ongoing test runs.')); export const testingFilterIcon = registerIcon('testing-filter', Codicon.filter, localize('filterIcon', 'Icon for the \'Filter\' action in the testing view.')); export const testingHiddenIcon = registerIcon('testing-hidden', Codicon.eyeClosed, localize('hiddenIcon', 'Icon shown beside hidden tests, when they\'ve been shown.')); @@ -37,7 +35,7 @@ export const testingTurnContinuousRunOff = registerIcon('testing-turn-continuous export const testingContinuousIsOn = registerIcon('testing-continuous-is-on', Codicon.eye, localize('testingTurnContinuousRunIsOn', 'Icon when continuous run is on for a test ite,.')); export const testingCancelRefreshTests = registerIcon('testing-cancel-refresh-tests', Codicon.stop, localize('testingCancelRefreshTests', 'Icon on the button to cancel refreshing tests.')); -export const testingCoverageReport = registerIcon('testing-coverage', Codicon.lightBulb, localize('testingCoverage', 'Icon representing test coverage')); +export const testingCoverageReport = registerIcon('testing-coverage', Codicon.coverage, localize('testingCoverage', 'Icon representing test coverage')); export const testingWasCovered = registerIcon('testing-was-covered', Codicon.check, localize('testingWasCovered', 'Icon representing that an element was covered')); export const testingCoverageMissingBranch = registerIcon('testing-missing-branch', Codicon.question, localize('testingMissingBranch', 'Icon representing a uncovered block without a range')); From df01567c35f397cf80049a2058e38a39c8bc9e36 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 6 Feb 2024 10:49:29 -0800 Subject: [PATCH 0941/1897] Add codeql comments (#204448) --- .../notebook/browser/view/renderers/webviewPreloads.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 65660694027..ebbf9d27032 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -2233,7 +2233,7 @@ async function webviewPreloads(ctx: PreloadContext) { return; } const trustedHtml = ttPolicy?.createHTML(html) ?? html; - el.innerHTML = trustedHtml as string; + el.innerHTML = trustedHtml as string; // CodeQL [SM03712] The rendered content comes from VS Code's tokenizer and is considered safe const root = el.getRootNode(); if (root instanceof ShadowRoot) { if (!root.adoptedStyleSheets.includes(tokenizationStyle)) { @@ -2707,8 +2707,8 @@ async function webviewPreloads(ctx: PreloadContext) { this._content = { preferredRendererId, preloadErrors }; if (content.type === 0 /* RenderOutputType.Html */) { - const trustedHtml = ttPolicy?.createHTML(content.htmlContent) ?? content.htmlContent; // CodeQL [SM03712] The content comes from renderer extensions, not from direct user input. - this.element.innerHTML = trustedHtml as string; + const trustedHtml = ttPolicy?.createHTML(content.htmlContent) ?? content.htmlContent; + this.element.innerHTML = trustedHtml as string; // CodeQL [SM03712] The content comes from renderer extensions, not from direct user input. } else if (preloadErrors.some(e => e instanceof Error)) { const errors = preloadErrors.filter((e): e is Error => e instanceof Error); showRenderError(`Error loading preloads`, this.element, errors); From e0ae0f30e3fab7e94eedb587c8192f7f1daf48db Mon Sep 17 00:00:00 2001 From: Anthony Stewart <150152+a-stewart@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:49:56 +0100 Subject: [PATCH 0942/1897] Prevent F1 from opening browser help in webviews (#204499) * Prevent F1 fro opening browser help in webviews * Also prevent F5 from refreshing * Update CSP hash --- .../webview/browser/pre/index-no-csp.html | 20 ++++++++++++++++- .../contrib/webview/browser/pre/index.html | 22 +++++++++++++++++-- 2 files changed, 39 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 70c85003ce2..8b22da14204 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 @@ -596,7 +596,7 @@ } else { return; // let the browser handle this } - } else if (!onElectron && (isCloseTab(e) || isNewWindow(e))) { + } else if (!onElectron && (isCloseTab(e) || isNewWindow(e) || isHelp(e) || isRefresh(e))) { // Prevent Ctrl+W closing window / Ctrl+N opening new window in PWA. // (No effect in a regular browser tab.) e.preventDefault(); @@ -701,6 +701,24 @@ return hasMeta && e.keyCode === 78; } + /** + * @param {KeyboardEvent} e + * @return {boolean} + */ + function isHelp(e) { + // 112: keyCode of "F1" + return e.keyCode === 112; + } + + /** + * @param {KeyboardEvent} e + * @return {boolean} + */ + function isRefresh(e) { + // 116: keyCode of "F5" + return e.keyCode === 116; + } + let isHandlingScroll = false; /** diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index 3d4b24036e0..277a619ddc7 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-MbYFw/X6HjRtVlnfFTL3ylPDt3RsDzWrYVjfrzKJbMA=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> Date: Tue, 6 Feb 2024 15:58:48 -0300 Subject: [PATCH 0943/1897] Ensure chat feedback buttons don't get pushed off screen (#204524) --- src/vs/workbench/contrib/chat/browser/media/chat.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 14361496659..d802cf60524 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -29,6 +29,7 @@ display: flex; align-items: center; justify-content: space-between; + position: relative; } .interactive-item-container .header.hidden { @@ -159,6 +160,12 @@ padding: 2px; } +.interactive-item-container .header .monaco-toolbar { + position: absolute; + right: 0px; + background-color: var(--vscode-chat-list-background); +} + .interactive-item-container .header .monaco-toolbar .checked.action-label, .interactive-item-container .header .monaco-toolbar .checked.action-label:hover { color: var(--vscode-inputOption-activeForeground) !important; From 384df5aad7ad2bccba18c1c6dba3c249d02faac6 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 6 Feb 2024 16:19:50 -0300 Subject: [PATCH 0944/1897] Filter out "command followups" from agents that haven't adopted new API (#204530) --- src/vs/workbench/api/common/extHostChatAgents2.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 79b7cb1d63a..30ae71c8d52 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -408,7 +408,10 @@ class ExtHostChatAgent { if (!followups) { return []; } - return followups.map(f => typeConvert.ChatFollowup.from(f)); + return followups + // Filter out "command followups" from older providers + .filter(f => !(f && 'commandId' in f)) + .map(f => typeConvert.ChatFollowup.from(f)); } async provideWelcomeMessage(token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined> { From 0f47ae79329b45d955a799834d81ba8bc9920b9d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 6 Feb 2024 11:39:50 -0800 Subject: [PATCH 0945/1897] Pick up latest TS for building VS Code (#204527) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ecf12d79edc..ccc76d3e3d8 100644 --- a/package.json +++ b/package.json @@ -209,7 +209,7 @@ "ts-loader": "^9.4.2", "ts-node": "^10.9.1", "tsec": "0.2.7", - "typescript": "^5.4.0-dev.20240130", + "typescript": "^5.4.0-dev.20240206", "typescript-formatter": "7.1.0", "util": "^0.12.4", "vscode-nls-dev": "^3.3.1", diff --git a/yarn.lock b/yarn.lock index 9ed6f456cb8..310d78e80d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9529,10 +9529,10 @@ typescript@^4.7.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== -typescript@^5.4.0-dev.20240130: - version "5.4.0-dev.20240130" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.0-dev.20240130.tgz#6716a8f43a0bf9feb21a850f73eb34443d9017dd" - integrity sha512-LelY/DlG9yPe807IRs7ZYvKp0eGgk4ehYr/9RvQsZg31E7sF3oYz0r44fsvnqxFWCsM11CWOrmY1r+MD5CDMfQ== +typescript@^5.4.0-dev.20240206: + version "5.4.0-dev.20240206" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.0-dev.20240206.tgz#75755acb115e1176958d511d11eb018694e74987" + integrity sha512-8P1XYxDbG/AyGE5tB8+JpeiQfS5ye1BTvIVDZaHhoK9nJuCn4nkB0L66lvfwYB+46hA4rLo3vE3WkIToSYtqQA== typical@^4.0.0: version "4.0.0" From 5700e15f44491e9523e099875d9d8acc0386552d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 6 Feb 2024 11:42:50 -0800 Subject: [PATCH 0946/1897] Prototype local file editors in chat (#202754) Adds support for rendering local file editors in chat These editors have full editor support and are loaded from the workspace instead of having their content provided in chat These editors can be created with a special markdown code block like: ~~~md ```vscode-local-file { "uri": uri_json, "range": optional_range_data } ``` --- .../contrib/chat/browser/chatListRenderer.ts | 69 ++-- .../contrib/chat/browser/chatWidget.ts | 9 +- .../contrib/chat/browser/codeBlockPart.ts | 314 +++++++++++++----- .../inlineChat/browser/inlineChatWidget.ts | 2 +- 4 files changed, 287 insertions(+), 107 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index fc5738c28f3..653c27fbaeb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -27,9 +27,11 @@ import { marked } from 'vs/base/common/marked/marked'; import { FileAccess } from 'vs/base/common/network'; import { clamp } from 'vs/base/common/numbers'; import { basename } from 'vs/base/common/path'; +import { equalsIgnoreCase } from 'vs/base/common/strings'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; +import { Range } from 'vs/editor/common/core/range'; import { localize } from 'vs/nls'; import { IMenuEntryActionViewItemOptions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; @@ -53,7 +55,7 @@ import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo } from 'vs/workbenc import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; import { ChatMarkdownDecorationsRenderer, annotateSpecialMarkdownContent, extractVulnerabilitiesFromText } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; -import { CodeBlockPart, ICodeBlockData, ICodeBlockPart } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; +import { ICodeBlockData, ICodeBlockPart, LocalFileCodeBlockPart, SimpleCodeBlockPart, localFileLanguageId, parseLocalFileData } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; @@ -130,6 +132,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { + for (const editor of this._editorPool.inUse()) { editor.layout(this._currentLayoutWidth); - }); + } } renderTemplate(container: HTMLElement): IChatListItemTemplate { @@ -837,11 +840,22 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - const vulns = extractVulnerabilitiesFromText(text); + let data: ICodeBlockData; + if (equalsIgnoreCase(languageId, localFileLanguageId)) { + try { + const parsedBody = parseLocalFileData(text); + data = { type: 'localFile', uri: parsedBody.uri, range: parsedBody.range && Range.lift(parsedBody.range), codeBlockIndex: codeBlockIndex++, element, hideToolbar: false, parentContextKeyService: templateData.contextKeyService }; + } catch (e) { + console.error(e); + return $('div'); + } + } else { + const vulns = extractVulnerabilitiesFromText(text); + const hideToolbar = isResponseVM(element) && element.errorDetails?.responseIsFiltered; + data = { type: 'code', languageId, text: vulns.newText, codeBlockIndex: codeBlockIndex++, element, hideToolbar, parentContextKeyService: templateData.contextKeyService, vulns: vulns.vulnerabilities }; + } - const hideToolbar = isResponseVM(element) && element.errorDetails?.responseIsFiltered; - const data = { languageId, text: vulns.newText, codeBlockIndex: codeBlockIndex++, element, hideToolbar, parentContextKeyService: templateData.contextKeyService, vulns: vulns.vulnerabilities }; - const ref = this.renderCodeBlock(data, disposables); + const ref = this.renderCodeBlock(data); // Attach this after updating text/layout of the editor, so it should only be fired when the size updates later (horizontal scrollbar, wrapping) // not during a renderElement OR a progressive render (when we will be firing this event anyway at the end of the render) @@ -859,8 +873,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.codeBlocksByEditorUri.delete(ref.object.textModel.uri))); + this.codeBlocksByEditorUri.set(ref.object.uri, info); + disposables.add(toDisposable(() => this.codeBlocksByEditorUri.delete(ref.object.uri))); } orderedDisposablesList.push(ref); return ref.object.element; @@ -885,8 +899,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - const ref = this._editorPool.get(); + private renderCodeBlock(data: ICodeBlockData): IDisposableReference { + const ref = this._editorPool.get(data); const editorInfo = ref.object; editorInfo.render(data, this._currentLayoutWidth); @@ -1022,35 +1036,42 @@ interface IDisposableReference extends IDisposable { } class EditorPool extends Disposable { - private _pool: ResourcePool; - public get inUse(): ReadonlySet { - return this._pool.inUse; + private readonly _simpleEditorPool: ResourcePool; + private readonly _localFileEditorPool: ResourcePool; + + public *inUse(): Iterable { + yield* this._simpleEditorPool.inUse; + yield* this._localFileEditorPool.inUse; } constructor( private readonly options: ChatEditorOptions, + overflowWidgetsDomNode: HTMLElement | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); - this._pool = this._register(new ResourcePool(() => this.editorFactory())); - - // TODO listen to changes on options + this._simpleEditorPool = this._register(new ResourcePool(() => { + return this.instantiationService.createInstance(SimpleCodeBlockPart, this.options, MenuId.ChatCodeBlock); + })); + this._localFileEditorPool = this._register(new ResourcePool(() => { + return this.instantiationService.createInstance(LocalFileCodeBlockPart, this.options, MenuId.ChatCodeBlock, overflowWidgetsDomNode); + })); } - private editorFactory(): ICodeBlockPart { - return this.instantiationService.createInstance(CodeBlockPart, this.options, MenuId.ChatCodeBlock); + get(data: ICodeBlockData): IDisposableReference { + return this.getFromPool(data.type === 'localFile' ? this._localFileEditorPool : this._simpleEditorPool); } - get(): IDisposableReference { - const object = this._pool.get(); + private getFromPool(pool: ResourcePool): IDisposableReference { + const object = pool.get(); let stale = false; return { object, isStale: () => stale, dispose: () => { stale = true; - this._pool.release(object); + pool.release(object); } }; } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index d0ee8506695..79d74e0ba11 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -322,11 +322,18 @@ export class ChatWidget extends Disposable implements IChatWidget { const rendererDelegate: IChatRendererDelegate = { getListLength: () => this.tree.getNode(null).visibleChildrenCount, }; + + // Create a dom element to hold UI from editor widgets embedded in chat messages + const overflowWidgetsContainer = document.createElement('div'); + overflowWidgetsContainer.classList.add('chat-overflow-widget-container', 'monaco-editor'); + listContainer.append(overflowWidgetsContainer); + this.renderer = this._register(scopedInstantiationService.createInstance( ChatListItemRenderer, this.editorOptions, options, - rendererDelegate + rendererDelegate, + overflowWidgetsContainer, )); this._register(this.renderer.onDidClickFollowup(item => { // is this used anymore? diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 7f55edcb93b..b397943928e 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -6,19 +6,21 @@ import 'vs/css!./codeBlockPart'; import * as dom from 'vs/base/browser/dom'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; - import { Button } from 'vs/base/browser/ui/button/button'; import { Codicon } from 'vs/base/common/codicons'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, IReference, MutableDisposable } from 'vs/base/common/lifecycle'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { Range } from 'vs/editor/common/core/range'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { ScrollType } from 'vs/editor/common/editorCommon'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; +import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { BracketMatchingController } from 'vs/editor/contrib/bracketMatching/browser/bracketMatching'; import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu'; import { ViewportSemanticTokensContribution } from 'vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens'; @@ -39,19 +41,70 @@ import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/ import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; +import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; const $ = dom.$; -export interface ICodeBlockData { - text: string; - languageId: string; +interface ICodeBlockDataCommon { codeBlockIndex: number; element: unknown; parentContextKeyService?: IContextKeyService; hideToolbar?: boolean; +} + +export interface ISimpleCodeBlockData extends ICodeBlockDataCommon { + type: 'code'; + text: string; + languageId: string; vulns?: IMarkdownVulnerability[]; } +export interface ILocalFileCodeBlockData extends ICodeBlockDataCommon { + type: 'localFile'; + uri: URI; + range?: Range; +} + +export type ICodeBlockData = ISimpleCodeBlockData | ILocalFileCodeBlockData; + +/** + * Special markdown code block language id used to render a local file. + * + * The text of the code path should be a {@link LocalFileCodeBlockData} json object. + */ +export const localFileLanguageId = 'vscode-local-file'; + + +export function parseLocalFileData(text: string) { + + interface RawLocalFileCodeBlockData { + readonly uri: UriComponents; + readonly range?: IRange; + } + + let data: RawLocalFileCodeBlockData; + try { + data = JSON.parse(text); + } catch (e) { + throw new Error('Could not parse code block local file data'); + } + + let uri: URI; + try { + uri = URI.revive(data?.uri); + } catch (e) { + throw new Error('Invalid code block local file data URI'); + } + + let range: IRange | undefined; + if (data.range) { + // Note that since this is coming from extensions, position are actually zero based and must be converted. + range = new Range(data.range.startLineNumber + 1, data.range.startColumn + 1, data.range.endLineNumber + 1, data.range.endColumn + 1); + } + + return { uri, range }; +} + export interface ICodeBlockActionContext { code: string; languageId: string; @@ -60,42 +113,37 @@ export interface ICodeBlockActionContext { } -export interface ICodeBlockPart { +export interface ICodeBlockPart { readonly onDidChangeContentHeight: Event; readonly element: HTMLElement; - readonly textModel: ITextModel; + readonly uri: URI; layout(width: number): void; - render(data: ICodeBlockData, width: number): void; + render(data: Data, width: number): Promise; focus(): void; dispose(): void; } const defaultCodeblockPadding = 10; - -export class CodeBlockPart extends Disposable implements ICodeBlockPart { - private readonly _onDidChangeContentHeight = this._register(new Emitter()); +abstract class BaseCodeBlockPart extends Disposable implements ICodeBlockPart { + protected readonly _onDidChangeContentHeight = this._register(new Emitter()); public readonly onDidChangeContentHeight = this._onDidChangeContentHeight.event; - private readonly editor: CodeEditorWidget; - private readonly toolbar: MenuWorkbenchToolBar; + protected readonly editor: CodeEditorWidget; + protected readonly toolbar: MenuWorkbenchToolBar; private readonly contextKeyService: IContextKeyService; - private readonly vulnsButton: Button; - private readonly vulnsListElement: HTMLElement; - - public readonly textModel: ITextModel; + abstract readonly uri: URI; public readonly element: HTMLElement; - private currentCodeBlockData: ICodeBlockData | undefined; private currentScrollWidth = 0; constructor( private readonly options: ChatEditorOptions, readonly menuId: MenuId, + overflowWidgetsDomNode: HTMLElement | undefined, @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, - @ILanguageService private readonly languageService: ILanguageService, - @IModelService private readonly modelService: IModelService, + @IModelService protected readonly modelService: IModelService, @IConfigurationService private readonly configurationService: IConfigurationService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService ) { @@ -105,7 +153,7 @@ export class CodeBlockPart extends Disposable implements ICodeBlockPart { this.contextKeyService = this._register(contextKeyService.createScoped(this.element)); const scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this.contextKeyService])); const editorElement = dom.append(this.element, $('.interactive-result-editor')); - this.editor = this._register(scopedInstantiationService.createInstance(CodeEditorWidget, editorElement, { + this.editor = this.createEditor(scopedInstantiationService, editorElement, { ...getSimpleEditorOptions(this.configurationService), readOnly: true, lineNumbers: 'off', @@ -119,20 +167,9 @@ export class CodeBlockPart extends Disposable implements ICodeBlockPart { alwaysConsumeMouseWheel: false }, ariaLabel: localize('chat.codeBlockHelp', 'Code block'), - ...this.getEditorOptionsFromConfig() - }, { - isSimpleWidget: true, - contributions: EditorExtensionsRegistry.getSomeEditorContributions([ - MenuPreventer.ID, - SelectionClipboardContributionID, - ContextMenuController.ID, - - WordHighlighterContribution.ID, - ViewportSemanticTokensContribution.ID, - BracketMatchingController.ID, - SmartSelectController.ID, - ]) - })); + overflowWidgetsDomNode, + ...this.getEditorOptionsFromConfig(), + }); const toolbarElement = dom.append(this.element, $('.interactive-result-code-block-toolbar')); const editorScopedService = this.editor.contextKeyService.createScoped(toolbarElement); @@ -143,31 +180,6 @@ export class CodeBlockPart extends Disposable implements ICodeBlockPart { } })); - const vulnsContainer = dom.append(this.element, $('.interactive-result-vulns')); - const vulnsHeaderElement = dom.append(vulnsContainer, $('.interactive-result-vulns-header', undefined)); - this.vulnsButton = new Button(vulnsHeaderElement, { - buttonBackground: undefined, - buttonBorder: undefined, - buttonForeground: undefined, - buttonHoverBackground: undefined, - buttonSecondaryBackground: undefined, - buttonSecondaryForeground: undefined, - buttonSecondaryHoverBackground: undefined, - buttonSeparator: undefined, - supportIcons: true - }); - - this.vulnsListElement = dom.append(vulnsContainer, $('ul.interactive-result-vulns-list')); - - this.vulnsButton.onDidClick(() => { - const element = this.currentCodeBlockData!.element as IChatResponseViewModel; - element.vulnerabilitiesListExpanded = !element.vulnerabilitiesListExpanded; - this.vulnsButton.label = this.getVulnerabilitiesLabel(); - this.element.classList.toggle('chat-vulnerabilities-collapsed', !element.vulnerabilitiesListExpanded); - this._onDidChangeContentHeight.fire(); - // this.updateAriaLabel(collapseButton.element, referencesLabel, element.usedReferencesExpanded); - }); - this._register(this.toolbar.onDidChangeDropdownVisibility(e => { toolbarElement.classList.toggle('force-visibility', e); })); @@ -200,11 +212,10 @@ export class CodeBlockPart extends Disposable implements ICodeBlockPart { this.element.classList.add('focused'); WordHighlighterContribution.get(this.editor)?.restoreViewState(true); })); - - this.textModel = this._register(this.modelService.createModel('', null, undefined, true)); - this.editor.setModel(this.textModel); } + protected abstract createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): CodeEditorWidget; + focus(): void { this.editor.focus(); } @@ -228,7 +239,6 @@ export class CodeBlockPart extends Disposable implements ICodeBlockPart { } else { toolbarElt.style.display = ''; } - } private getEditorOptionsFromConfig(): IEditorOptions { @@ -248,13 +258,12 @@ export class CodeBlockPart extends Disposable implements ICodeBlockPart { layout(width: number): void { const realContentHeight = this.editor.getContentHeight(); const editorBorder = 2; - this.editor.layout({ width: width - editorBorder, height: realContentHeight }); + // TODO: Min added for testing. Compute proper height based on range + this.editor.layout({ width: width - editorBorder, height: Math.min(realContentHeight, 100) }); this.updatePaddingForLayout(); } - - render(data: ICodeBlockData, width: number): void { - this.currentCodeBlockData = data; + async render(data: Data, width: number) { if (data.parentContextKeyService) { this.contextKeyService.updateParent(data.parentContextKeyService); } @@ -265,26 +274,93 @@ export class CodeBlockPart extends Disposable implements ICodeBlockPart { this.layout(width); } - const text = this.fixCodeText(data.text, data.languageId); - this.setText(text); - - const vscodeLanguageId = this.languageService.getLanguageIdByLanguageName(data.languageId) ?? undefined; - this.setLanguage(vscodeLanguageId); + await this.updateEditor(data); this.layout(width); this.editor.updateOptions({ ariaLabel: localize('chat.codeBlockLabel', "Code block {0}", data.codeBlockIndex + 1) }); - this.toolbar.context = { - code: data.text, - codeBlockIndex: data.codeBlockIndex, - element: data.element, - languageId: vscodeLanguageId - }; if (data.hideToolbar) { dom.hide(this.toolbar.getElement()); } else { dom.show(this.toolbar.getElement()); } + } + + protected abstract updateEditor(data: Data): void | Promise; +} + + +export class SimpleCodeBlockPart extends BaseCodeBlockPart { + + private readonly vulnsButton: Button; + private readonly vulnsListElement: HTMLElement; + + private currentCodeBlockData: ISimpleCodeBlockData | undefined; + + private readonly textModel: ITextModel; + constructor( + options: ChatEditorOptions, + menuId: MenuId, + @IInstantiationService instantiationService: IInstantiationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IModelService modelService: IModelService, + @IConfigurationService configurationService: IConfigurationService, + @IAccessibilityService accessibilityService: IAccessibilityService, + @ILanguageService private readonly languageService: ILanguageService, + ) { + super(options, menuId, undefined, instantiationService, contextKeyService, modelService, configurationService, accessibilityService); + + const vulnsContainer = dom.append(this.element, $('.interactive-result-vulns')); + const vulnsHeaderElement = dom.append(vulnsContainer, $('.interactive-result-vulns-header', undefined)); + this.vulnsButton = new Button(vulnsHeaderElement, { + buttonBackground: undefined, + buttonBorder: undefined, + buttonForeground: undefined, + buttonHoverBackground: undefined, + buttonSecondaryBackground: undefined, + buttonSecondaryForeground: undefined, + buttonSecondaryHoverBackground: undefined, + buttonSeparator: undefined, + supportIcons: true + }); + + this.textModel = this._register(this.modelService.createModel('', null, undefined, true)); + this.editor.setModel(this.textModel); + + this.vulnsListElement = dom.append(vulnsContainer, $('ul.interactive-result-vulns-list')); + + this.vulnsButton.onDidClick(() => { + const element = this.currentCodeBlockData!.element as IChatResponseViewModel; + element.vulnerabilitiesListExpanded = !element.vulnerabilitiesListExpanded; + this.vulnsButton.label = this.getVulnerabilitiesLabel(); + this.element.classList.toggle('chat-vulnerabilities-collapsed', !element.vulnerabilitiesListExpanded); + this._onDidChangeContentHeight.fire(); + // this.updateAriaLabel(collapseButton.element, referencesLabel, element.usedReferencesExpanded); + }); + } + + get uri(): URI { + return this.textModel.uri; + } + + protected override createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): CodeEditorWidget { + return this._register(instantiationService.createInstance(CodeEditorWidget, parent, options, { + isSimpleWidget: true, + contributions: EditorExtensionsRegistry.getSomeEditorContributions([ + MenuPreventer.ID, + SelectionClipboardContributionID, + ContextMenuController.ID, + + WordHighlighterContribution.ID, + ViewportSemanticTokensContribution.ID, + BracketMatchingController.ID, + SmartSelectController.ID, + ]) + })); + } + + override async render(data: ISimpleCodeBlockData, width: number): Promise { + await super.render(data, width); if (data.vulns?.length && isResponseVM(data.element)) { dom.clearNode(this.vulnsListElement); @@ -297,6 +373,23 @@ export class CodeBlockPart extends Disposable implements ICodeBlockPart { } } + protected override updateEditor(data: ISimpleCodeBlockData): void { + this.editor.setModel(this.textModel); + const text = this.fixCodeText(data.text, data.languageId); + this.setText(text); + + const vscodeLanguageId = this.languageService.getLanguageIdByLanguageName(data.languageId) ?? undefined; + this.setLanguage(vscodeLanguageId); + data.languageId = vscodeLanguageId ?? 'plaintext'; + + this.toolbar.context = { + code: data.text, + codeBlockIndex: data.codeBlockIndex, + element: data.element, + languageId: data.languageId + } satisfies ICodeBlockActionContext; + } + private getVulnerabilitiesLabel(): string { if (!this.currentCodeBlockData || !this.currentCodeBlockData.vulns) { return ''; @@ -340,3 +433,62 @@ export class CodeBlockPart extends Disposable implements ICodeBlockPart { this.textModel.setLanguage(vscodeLanguageId ?? PLAINTEXT_LANGUAGE_ID); } } + +export class LocalFileCodeBlockPart extends BaseCodeBlockPart { + + private readonly textModelReference = this._register(new MutableDisposable>()); + private currentCodeBlockData?: ILocalFileCodeBlockData; + + constructor( + options: ChatEditorOptions, + menuId: MenuId, + overflowWidgetsDomNode: HTMLElement | undefined, + @IInstantiationService instantiationService: IInstantiationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IModelService modelService: IModelService, + @ITextModelService private readonly textModelService: ITextModelService, + @IConfigurationService configurationService: IConfigurationService, + @IAccessibilityService accessibilityService: IAccessibilityService + ) { + super(options, menuId, overflowWidgetsDomNode, instantiationService, contextKeyService, modelService, configurationService, accessibilityService); + } + + get uri(): URI { + return this.currentCodeBlockData!.uri; + } + + protected override createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): CodeEditorWidget { + return this._register(instantiationService.createInstance(CodeEditorWidget, parent, { + ...options, + }, { + // TODO: be more selective about contributions + })); + } + + protected override async updateEditor(data: ILocalFileCodeBlockData): Promise { + let model: ITextModel; + if (this.currentCodeBlockData?.uri.toString() === data.uri.toString()) { + this.currentCodeBlockData = data; + model = this.editor.getModel()!; + } else { + this.currentCodeBlockData = data; + const result = await this.textModelService.createModelReference(data.uri); + model = result.object.textEditorModel; + this.textModelReference.value = result; + this.editor.setModel(model); + } + + + if (data.range) { + this.editor.setSelection(data.range); + this.editor.revealRangeInCenter(data.range, ScrollType.Immediate); + } + + this.toolbar.context = { + code: model.getTextBuffer().getValueInRange(data.range ?? model.getFullModelRange(), EndOfLinePreference.TextDefined), + codeBlockIndex: data.codeBlockIndex, + element: data.element, + languageId: model.getLanguageId() + } satisfies ICodeBlockActionContext; + } +} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 7ceff7252b2..15f98cccce7 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -596,7 +596,7 @@ export class InlineChatWidget { const viewModel = this._chatMessageDisposables.add(new ChatResponseViewModel(responseModel, this._logService)); const renderOptions: IChatListItemRendererOptions = { renderStyle: 'compact', noHeader: true, noPadding: true }; const chatRendererDelegate: IChatRendererDelegate = { getListLength() { return 1; } }; - const renderer = this._chatMessageDisposables.add(this._instantiationService.createInstance(ChatListItemRenderer, this._editorOptions, renderOptions, chatRendererDelegate)); + const renderer = this._chatMessageDisposables.add(this._instantiationService.createInstance(ChatListItemRenderer, this._editorOptions, renderOptions, chatRendererDelegate, undefined)); renderer.layout(this._elements.chatMessageContent.clientWidth - 4); // 2 for the padding used for the tab index border this._chatMessageDisposables.add(this._onDidChangeLayout.event(() => { renderer.layout(this._elements.chatMessageContent.clientWidth - 4); From ea16ecb7aca5cc3d7df8a80b8427244d280d4725 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 6 Feb 2024 12:09:45 -0800 Subject: [PATCH 0947/1897] Fix code block sizing (#204536) Missed this in previous PR --- .../contrib/chat/browser/codeBlockPart.ts | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index b397943928e..6e7f34f5ad6 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -13,7 +13,7 @@ import { Disposable, IReference, MutableDisposable } from 'vs/base/common/lifecy import { URI, UriComponents } from 'vs/base/common/uri'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { EDITOR_FONT_DEFAULTS, EditorOption, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { ILanguageService } from 'vs/editor/common/languages/language'; @@ -256,13 +256,16 @@ abstract class BaseCodeBlockPart extends Disposable } layout(width: number): void { - const realContentHeight = this.editor.getContentHeight(); + const contentHeight = this.getContentHeight(); const editorBorder = 2; - // TODO: Min added for testing. Compute proper height based on range - this.editor.layout({ width: width - editorBorder, height: Math.min(realContentHeight, 100) }); + this.editor.layout({ width: width - editorBorder, height: contentHeight }); this.updatePaddingForLayout(); } + protected getContentHeight() { + return this.editor.getContentHeight(); + } + async render(data: Data, width: number) { if (data.parentContextKeyService) { this.contextKeyService.updateParent(data.parentContextKeyService); @@ -323,8 +326,8 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart buttonSeparator: undefined, supportIcons: true }); - - this.textModel = this._register(this.modelService.createModel('', null, undefined, true)); + const x = URI.from({ scheme: 'ai', path: `chat-code-block-${Date.now()}.txt` }); + this.textModel = this._register(this.modelService.createModel('', null, x, true)); this.editor.setModel(this.textModel); this.vulnsListElement = dom.append(vulnsContainer, $('ul.interactive-result-vulns-list')); @@ -457,6 +460,15 @@ export class LocalFileCodeBlockPart extends BaseCodeBlockPart): CodeEditorWidget { return this._register(instantiationService.createInstance(CodeEditorWidget, parent, { ...options, From ad05897293964be52ad97505124830937d096953 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Tue, 6 Feb 2024 12:35:29 -0800 Subject: [PATCH 0948/1897] Transfer view state between the chat view pane and editor (#204534) --- .../contrib/chat/browser/actions/chatMoveActions.ts | 6 ++++-- src/vs/workbench/contrib/chat/browser/chatEditor.ts | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts index 23da3273ec1..20e2bc80bb8 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts @@ -65,9 +65,10 @@ export function getMoveToAction(viewId: string, providerId: string, moveTo: Move const editorService = accessor.get(IEditorService); const sessionId = viewModel.sessionId; + const viewState = view.widget.getViewState(); view.clear(); - await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { sessionId }, pinned: true } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); + await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { sessionId }, pinned: true, viewState: viewState } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); } }; } @@ -149,9 +150,10 @@ async function executeMoveToAction(accessor: ServicesAccessor, moveTo: MoveToNew const sessionId = viewModel.sessionId; const view = await viewService.openView(widget.viewContext.viewId) as ChatViewPane; + const viewState = view.widget.getViewState(); view.clear(); - await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { sessionId }, pinned: true } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); + await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { sessionId }, pinned: true, viewState: viewState } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); } async function moveToSidebar(accessor: ServicesAccessor): Promise { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index 4c2146167f9..8401da7b172 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -93,12 +93,12 @@ export class ChatEditor extends EditorPane { throw new Error('ChatEditor lifecycle issue: no editor widget'); } - this.updateModel(editorModel.model); + this.updateModel(editorModel.model, options.viewState); } - private updateModel(model: IChatModel): void { - this._memento = new Memento('interactive-session-editor-' + model.sessionId, this.storageService); - this._viewState = this._memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE) as IChatViewState; + private updateModel(model: IChatModel, viewState?: IChatViewState): void { + this._memento = new Memento('interactive-session-editor-' + model.providerId, this.storageService); + this._viewState = viewState ?? this._memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE) as IChatViewState; this.widget.setModel(model, { ...this._viewState }); } From 01a956086387e1bd909721c592cb3799e537c41e Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 6 Feb 2024 15:22:15 -0800 Subject: [PATCH 0949/1897] Revert model uri change (#204541) Only testing, not ready yet --- src/vs/workbench/contrib/chat/browser/codeBlockPart.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 6e7f34f5ad6..6663727301b 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -326,8 +326,7 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart buttonSeparator: undefined, supportIcons: true }); - const x = URI.from({ scheme: 'ai', path: `chat-code-block-${Date.now()}.txt` }); - this.textModel = this._register(this.modelService.createModel('', null, x, true)); + this.textModel = this._register(this.modelService.createModel('', null, undefined, true)); this.editor.setModel(this.textModel); this.vulnsListElement = dom.append(vulnsContainer, $('ul.interactive-result-vulns-list')); From 780192a30bb1a4b8424309d909051ae1ae27a84f Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 6 Feb 2024 20:54:17 -0300 Subject: [PATCH 0950/1897] Revert "Replace chat "command followups" with command button content (#204512)" (#204546) This reverts commit cda51f6ab4a708a83dd2522d2616963c60f4509a. Have to revert this because I can't get the vscode-copilot change to work. I might try again tomorrow in a back-compatible way. --- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 8 +- .../api/common/extHostChatAgents2.ts | 40 ++------ .../workbench/api/common/extHostInlineChat.ts | 15 +-- .../api/common/extHostTypeConverters.ts | 35 +++---- .../contrib/chat/browser/chatFollowups.ts | 3 +- .../contrib/chat/browser/chatInputPart.ts | 6 +- .../contrib/chat/browser/chatListRenderer.ts | 82 ++++++++-------- .../contrib/chat/browser/chatWidget.ts | 4 +- .../contrib/chat/browser/media/chat.css | 30 +++--- .../contrib/chat/common/chatAgents.ts | 4 +- .../contrib/chat/common/chatModel.ts | 32 ++----- .../contrib/chat/common/chatService.ts | 26 ++--- .../contrib/chat/common/chatServiceImpl.ts | 4 +- .../contrib/chat/common/chatViewModel.ts | 22 ++--- .../inlineChat/browser/inlineChatWidget.ts | 95 ++++++++++--------- .../contrib/inlineChat/common/inlineChat.ts | 22 +---- .../vscode.proposed.chatAgents2.d.ts | 25 ++--- .../vscode.proposed.chatAgents2Additions.d.ts | 2 +- .../vscode.proposed.defaultChatAgent.d.ts | 2 +- .../vscode.proposed.interactive.d.ts | 2 +- 21 files changed, 204 insertions(+), 257 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index f41ba74a6e6..7cffe74fd74 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -208,7 +208,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I 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 extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService, extHostCommands)); + const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol)); const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 8abc5dca98d..d24c57433a8 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -53,10 +53,10 @@ import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/cal import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatDynamicRequest, IChatProgress, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatDynamicRequest, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; import { DebugConfigurationProviderTriggerKind, MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext } from 'vs/workbench/contrib/debug/common/debug'; -import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatFollowup, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; @@ -1225,7 +1225,7 @@ export interface ExtHostChatAgentsShape2 { $acceptAction(handle: number, sessionId: string, requestId: string, action: IChatUserActionEvent): void; $invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise; $provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined>; - $provideSampleQuestions(handle: number, token: CancellationToken): Promise; + $provideSampleQuestions(handle: number, token: CancellationToken): Promise; $releaseSession(sessionId: string): void; } @@ -1251,7 +1251,7 @@ export type IInlineChatResponseDto = Dto; $provideResponse(handle: number, session: IInlineChatSession, request: IInlineChatRequest, token: CancellationToken): Promise; - $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise; + $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise; $handleFeedback(handle: number, sessionId: number, responseId: number, kind: InlineChatResponseFeedbackKind): void; $releaseSession(handle: number, sessionId: number): void; } diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 30ae71c8d52..cb1333f212b 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -9,7 +9,6 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; @@ -17,11 +16,10 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; -import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { IChatAgentCommand, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatFollowup, IChatProgress, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatProgress, IChatReplyFollowup, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import type * as vscode from 'vscode'; @@ -37,8 +35,7 @@ class ChatAgentResponseStream { private readonly _extension: IExtensionDescription, private readonly _request: IChatAgentRequest, private readonly _proxy: MainThreadChatAgentsShape2, - private readonly _logService: ILogService, - private readonly _commandsConverter: CommandsConverter, + @ILogService private readonly _logService: ILogService, ) { } close() { @@ -102,14 +99,6 @@ class ChatAgentResponseStream { _report(dto); return this; }, - button(command) { - throwIfDone(this.anchor); - _report({ - kind: 'command', - command: that._commandsConverter.toInternal(command, new DisposableStore()) // ?? - }); - return this; - }, progress(value) { throwIfDone(this.progress); const part = new extHostTypes.ChatResponseProgressPart(value); @@ -161,7 +150,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { mainContext: IMainContext, private readonly _extHostChatProvider: ExtHostChatProvider, private readonly _logService: ILogService, - private readonly commands: ExtHostCommands, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); } @@ -187,7 +175,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { this._extHostChatProvider.$updateAllowlist([{ extension: agent.extension.identifier, allowed: true }]); - const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService, this.commands.converter); + const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService); try { const convertedHistory = await this.prepareHistory(agent, request, context); const task = agent.invoke( @@ -232,7 +220,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { || h.result; return { request: typeConvert.ChatAgentRequest.to(h.request), - response: coalesce(h.response.map(r => typeConvert.ChatResponseProgress.toProgressContent(r, this.commands.converter))), + response: coalesce(h.response.map(r => typeConvert.ChatResponseProgress.toProgressContent(r))), result } satisfies vscode.ChatAgentHistoryEntry; }))); @@ -301,14 +289,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { // handled by $acceptFeedback return; } - - if (action.action.kind === 'command') { - const action2: vscode.ChatAgentCommandAction = { kind: 'command', commandButton: typeConvert.ChatResponseProgress.toProgressContent(action.action.commandButton, this.commands.converter) as vscode.ChatAgentCommandButton }; - agent.acceptAction(Object.freeze({ action: action2, result })); - return; - } else { - agent.acceptAction(Object.freeze({ action: action.action, result })); - } + agent.acceptAction(Object.freeze({ action: action.action, result })); } async $invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise { @@ -330,7 +311,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { return await agent.provideWelcomeMessage(token); } - async $provideSampleQuestions(handle: number, token: CancellationToken): Promise { + async $provideSampleQuestions(handle: number, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { return; @@ -408,10 +389,7 @@ class ExtHostChatAgent { if (!followups) { return []; } - return followups - // Filter out "command followups" from older providers - .filter(f => !(f && 'commandId' in f)) - .map(f => typeConvert.ChatFollowup.from(f)); + return followups.map(f => typeConvert.ChatFollowup.from(f)); } async provideWelcomeMessage(token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined> { @@ -431,7 +409,7 @@ class ExtHostChatAgent { }); } - async provideSampleQuestions(token: CancellationToken): Promise { + async provideSampleQuestions(token: CancellationToken): Promise { if (!this._welcomeMessageProvider || !this._welcomeMessageProvider.provideSampleQuestions) { return []; } @@ -440,7 +418,7 @@ class ExtHostChatAgent { return []; } - return content?.map(f => typeConvert.ChatFollowup.from(f)); + return content?.map(f => typeConvert.ChatReplyFollowup.from(f)); } get apiAgent(): vscode.ChatAgent2 { diff --git a/src/vs/workbench/api/common/extHostInlineChat.ts b/src/vs/workbench/api/common/extHostInlineChat.ts index 9e8478b98f3..47ec20ccea2 100644 --- a/src/vs/workbench/api/common/extHostInlineChat.ts +++ b/src/vs/workbench/api/common/extHostInlineChat.ts @@ -3,22 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { toDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { IPosition } from 'vs/editor/common/core/position'; -import { IRange } from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; +import { IInlineChatSession, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostInlineChatShape, IInlineChatResponseDto, IMainContext, MainContext, MainThreadInlineChatShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { IInlineChatFollowup, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import type * as vscode from 'vscode'; +import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { IRange } from 'vs/editor/common/core/range'; +import { IPosition } from 'vs/editor/common/core/position'; +import { raceCancellation } from 'vs/base/common/async'; +import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; class ProviderWrapper { @@ -227,14 +228,14 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { } } - async $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise { + async $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise { const entry = this._inputProvider.get(handle); const sessionData = this._inputSessions.get(sessionId); const response = sessionData?.responses[responseId]; if (entry && response && entry.provider.provideFollowups) { const task = Promise.resolve(entry.provider.provideFollowups(sessionData.session, response, token)); const followups = await raceCancellation(task, token); - return followups?.map(typeConvert.ChatInlineFollowup.from); + return followups?.map(typeConvert.ChatFollowup.from); } return undefined; } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 9ce83ae8888..4345dee16d7 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -36,9 +36,9 @@ import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/edit import { IViewBadge } from 'vs/workbench/common/views'; import { IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents'; import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatReplyFollowup, IChatResponseCommandFollowup, IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; -import { IInlineChatCommandFollowup, IInlineChatFollowup, IInlineChatReplyFollowup, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import * as search from 'vs/workbench/contrib/search/common/search'; @@ -2190,8 +2190,8 @@ export namespace DataTransfer { } } -export namespace ChatFollowup { - export function from(followup: vscode.ChatAgentFollowup): IChatFollowup { +export namespace ChatReplyFollowup { + export function from(followup: vscode.ChatAgentReplyFollowup | vscode.InteractiveEditorReplyFollowup): IChatReplyFollowup { return { kind: 'reply', message: followup.message, @@ -2201,25 +2201,21 @@ export namespace ChatFollowup { } } -export namespace ChatInlineFollowup { - export function from(followup: vscode.InteractiveEditorFollowup): IInlineChatFollowup { - if ('commandId' in followup) { - return { +export namespace ChatFollowup { + export function from(followup: string | vscode.ChatAgentFollowup): IChatFollowup { + if (typeof followup === 'string') { + return { title: followup, message: followup, kind: 'reply' }; + } else if ('commandId' in followup) { + return { kind: 'command', title: followup.title ?? '', commandId: followup.commandId ?? '', when: followup.when ?? '', args: followup.args - } satisfies IInlineChatCommandFollowup; + }; } else { - return { - kind: 'reply', - message: followup.message, - title: followup.title, - tooltip: followup.tooltip, - } satisfies IInlineChatReplyFollowup; + return ChatReplyFollowup.from(followup); } - } } @@ -2517,7 +2513,7 @@ export namespace ChatResponseProgress { } } - export function toProgressContent(progress: extHostProtocol.IChatContentProgressDto, commandsConverter: Command.ICommandsConverter): vscode.ChatAgentContentProgress | undefined { + export function toProgressContent(progress: extHostProtocol.IChatContentProgressDto): vscode.ChatAgentContentProgress | undefined { switch (progress.kind) { case 'markdownContent': // For simplicity, don't sent back the 'extended' types, so downgrade markdown to just some text @@ -2532,11 +2528,6 @@ export namespace ChatResponseProgress { }; case 'treeData': return { treeData: revive(progress.treeData) }; - case 'command': - // If the command isn't in the converter, then this session may have been restored, and the command args don't exist anymore - return { - command: commandsConverter.fromInternal(progress.command) ?? { command: progress.command.id, title: progress.command.title }, - }; default: // Unknown type, eg something in history that was removed? Ignore return undefined; diff --git a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts index 70e92d3f5b1..6c6e0d8f6d7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts +++ b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts @@ -10,11 +10,10 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; -import { IInlineChatFollowup } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; const $ = dom.$; -export class ChatFollowups extends Disposable { +export class ChatFollowups extends Disposable { constructor( container: HTMLElement, followups: T[], diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 06a5e2bb2fd..ce8a6081bea 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -37,7 +37,7 @@ import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_IN_CHAT_INPUT } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { chatAgentLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatReplyFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IChatHistoryEntry, IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService'; import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; @@ -64,7 +64,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private _onDidBlur = this._register(new Emitter()); readonly onDidBlur = this._onDidBlur.event; - private _onDidAcceptFollowup = this._register(new Emitter<{ followup: IChatFollowup; response: IChatResponseViewModel | undefined }>()); + private _onDidAcceptFollowup = this._register(new Emitter<{ followup: IChatReplyFollowup; response: IChatResponseViewModel | undefined }>()); readonly onDidAcceptFollowup = this._onDidAcceptFollowup.event; private inputEditorHeight = 0; @@ -338,7 +338,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } } - async renderFollowups(items: IChatFollowup[] | undefined, response: IChatResponseViewModel | undefined): Promise { + async renderFollowups(items: IChatReplyFollowup[] | undefined, response: IChatResponseViewModel | undefined): Promise { if (!this.options.renderFollowups) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 653c27fbaeb..6e30f1e51d4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -60,7 +60,7 @@ import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents' import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatCommandButton, IChatContentReference, IChatFollowup, IChatProgressMessage, IChatResponseProgressFileTreeData, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatContentReference, IChatProgressMessage, IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatProgressMessageRenderData, IChatRenderData, IChatResponseMarkdownRenderData, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView'; @@ -112,8 +112,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer()); - readonly onDidClickFollowup: Event = this._onDidClickFollowup.event; + protected readonly _onDidClickFollowup = this._register(new Emitter()); + readonly onDidClickFollowup: Event = this._onDidClickFollowup.event; protected readonly _onDidChangeItemHeight = this._register(new Emitter()); readonly onDidChangeItemHeight: Event = this._onDidChangeItemHeight.event; @@ -136,11 +136,12 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { + this.chatService.notifyUserAction({ + providerId: element.providerId, + agentId: element.agent?.id, + sessionId: element.sessionId, + requestId: element.requestId, + action: { + kind: 'command', + command: followup, + } + }); + return this.commandService.executeCommand(followup.commandId, ...(followup.args ?? [])); + }, + templateData.contextKeyService)); + } + const newHeight = templateData.rowContainer.offsetHeight; const fireEvent = !element.currentRenderedHeight || element.currentRenderedHeight !== newHeight; element.currentRenderedHeight = newHeight; @@ -532,8 +554,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.commandService.executeCommand(commandButton.command.id, ...(commandButton.command.arguments ?? [])))); - return { - dispose() { - disposables.dispose(); - }, - element: container - }; - } - private renderMarkdown(markdown: IMarkdownString, element: ChatTreeItem, templateData: IChatListItemTemplate, fillInIncompleteTokens = false): IMarkdownRenderResult { const disposables = new DisposableStore(); let codeBlockIndex = 0; @@ -1001,6 +1002,17 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider followup.title).join(', ')); + } const fileTreeCount = element.response.value.filter((v) => !('value' in v))?.length ?? 0; let fileTreeCountHint = ''; switch (fileTreeCount) { @@ -1025,7 +1037,7 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider, i: number): boolean { return items.slice(i).every(isProgressMessage); } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 79d74e0ba11..d0681b606f5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -30,7 +30,7 @@ import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION, CONTEXT_RESPONSE_FILTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; @@ -292,7 +292,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } } - private async renderFollowups(items: IChatFollowup[] | undefined, response?: IChatResponseViewModel): Promise { + private async renderFollowups(items: IChatReplyFollowup[] | undefined, response?: IChatResponseViewModel): Promise { this.inputPart.renderFollowups(items, response); if (this.bodyDimension) { diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index d802cf60524..fdbd39a5961 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -425,6 +425,20 @@ align-items: start; } +.interactive-session-followups .monaco-button { + text-align: left; + width: initial; +} + +.interactive-session-followups .monaco-button .codicon { + margin-left: 0; + margin-top: 1px; +} + +.interactive-item-container .interactive-response-followups .monaco-button { + padding: 4px 8px; +} + .interactive-session .interactive-input-part .interactive-input-followups { margin: 0px 20px; } @@ -619,19 +633,3 @@ font-size: 14px; color: var(--vscode-debugIcon-startForeground) !important; } - -.interactive-item-container .chat-command-button { - display: flex; - margin-bottom: 16px; -} - -.interactive-item-container .chat-command-button .monaco-button { - text-align: left; - width: initial; - padding: 4px 8px; -} - -.interactive-item-container .chat-command-button .monaco-button .codicon { - margin-left: 0; - margin-top: 1px; -} diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index cd4e3b1b489..6b71960d119 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { ProviderResult } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; //#region agent service, commands etc @@ -35,7 +35,7 @@ export interface IChatAgent extends IChatAgentData { lastSlashCommands?: IChatAgentCommand[]; provideSlashCommands(token: CancellationToken): Promise; provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>; - provideSampleQuestions?(token: CancellationToken): ProviderResult; + provideSampleQuestions?(token: CancellationToken): ProviderResult; } export interface IChatAgentCommand { diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index cbf36af4e56..235c547b2e4 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -16,7 +16,7 @@ import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { ILogService } from 'vs/platform/log/common/log'; import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatContent, IChatContentInlineReference, IChatContentReference, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext, IChatCommandButton } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; export interface IChatRequestVariableData { @@ -33,7 +33,7 @@ export interface IChatRequestModel { readonly username: string; readonly avatarIconUri?: URI; readonly session: IChatModel; - readonly message: IParsedChatRequest | IChatFollowup; + readonly message: IParsedChatRequest | IChatReplyFollowup; readonly variableData: IChatRequestVariableData; readonly response?: IChatResponseModel; } @@ -43,8 +43,7 @@ export type IChatProgressResponseContent = | IChatAgentMarkdownContentWithVulnerability | IChatTreeData | IChatContentInlineReference - | IChatProgressMessage - | IChatCommandButton; + | IChatProgressMessage; export type IChatProgressRenderableResponseContent = Exclude; @@ -69,8 +68,6 @@ export interface IChatResponseModel { readonly response: IResponse; readonly isComplete: boolean; readonly isCanceled: boolean; - /** A stale response is one that has been persisted and rehydrated, so e.g. Commands that have their arguments stored in the EH are gone. */ - readonly isStale: boolean; readonly vote: InteractiveSessionVoteDirection | undefined; readonly followups?: IChatFollowup[] | undefined; readonly errorDetails?: IChatResponseErrorDetails; @@ -162,7 +159,7 @@ export class Response implements IResponse { } this._updateRepr(quiet); - } else { + } else if (progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage') { this._responseParts.push(progress); this._updateRepr(quiet); } @@ -174,8 +171,6 @@ export class Response implements IResponse { return ''; } else if (part.kind === 'inlineReference') { return basename('uri' in part.inlineReference ? part.inlineReference.uri : part.inlineReference); - } else if (part.kind === 'command') { - return part.command.title; } else { return part.content.value; } @@ -260,11 +255,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this._progressMessages; } - private _isStale: boolean = false; - public get isStale(): boolean { - return this._isStale; - } - constructor( _response: IMarkdownString | ReadonlyArray, public readonly session: ChatModel, @@ -278,10 +268,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel followups?: ReadonlyArray ) { super(); - - // If we are creating a response with some existing content, consider it stale - this._isStale = Array.isArray(_response) && (_response.length !== 0 || isMarkdownString(_response) && _response.value.length !== 0); - this._followups = followups ? [...followups] : undefined; this._response = new Response(_response); this._register(this._response.onDidChangeValue(() => this._onDidChange.fire())); @@ -382,7 +368,7 @@ export interface ISerializableChatRequestData { export interface IExportableChatData { providerId: string; - welcomeMessage: (string | IChatFollowup[])[] | undefined; + welcomeMessage: (string | IChatReplyFollowup[])[] | undefined; requests: ISerializableChatRequestData[]; requesterUsername: string; responderUsername: string; @@ -660,7 +646,7 @@ export class ChatModel extends Disposable implements IChatModel { if (progress.kind === 'vulnerability') { request.response.updateContent({ kind: 'markdownVuln', content: { value: progress.content }, vulnerabilities: progress.vulnerabilities }, quiet); - } else if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage' || progress.kind === 'command') { + } else if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage') { request.response.updateContent(progress, quiet); } else if (progress.kind === 'usedContext' || progress.kind === 'reference') { request.response.applyReference(progress); @@ -786,12 +772,12 @@ export class ChatModel extends Disposable implements IChatModel { } } -export type IChatWelcomeMessageContent = IMarkdownString | IChatFollowup[]; +export type IChatWelcomeMessageContent = IMarkdownString | IChatReplyFollowup[]; export interface IChatWelcomeMessageModel { readonly id: string; readonly content: IChatWelcomeMessageContent[]; - readonly sampleQuestions: IChatFollowup[]; + readonly sampleQuestions: IChatReplyFollowup[]; readonly username: string; readonly avatarIconUri?: URI; @@ -808,7 +794,7 @@ export class ChatWelcomeMessageModel implements IChatWelcomeMessageModel { constructor( private readonly session: ChatModel, public readonly content: IChatWelcomeMessageContent[], - public readonly sampleQuestions: IChatFollowup[] + public readonly sampleQuestions: IChatReplyFollowup[] ) { this._id = 'welcome_' + ChatWelcomeMessageModel.nextId++; } diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index bf5c78b9ef2..9a9c1196e72 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -9,7 +9,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { Command, Location, ProviderResult } from 'vs/editor/common/languages'; +import { Location, ProviderResult } from 'vs/editor/common/languages'; import { FileType } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; @@ -142,11 +142,6 @@ export interface IChatAgentMarkdownContentWithVulnerability { kind: 'markdownVuln'; } -export interface IChatCommandButton { - command: Command; - kind: 'command'; -} - export type IChatProgress = | IChatContent | IChatMarkdownContent @@ -157,21 +152,30 @@ export type IChatProgress = | IChatContentReference | IChatContentInlineReference | IChatAgentDetection - | IChatProgressMessage - | IChatCommandButton; + | IChatProgressMessage; export interface IChatProvider { readonly id: string; prepareSession(token: CancellationToken): ProviderResult; } -export interface IChatFollowup { +export interface IChatReplyFollowup { kind: 'reply'; message: string; title?: string; tooltip?: string; } +export interface IChatResponseCommandFollowup { + kind: 'command'; + commandId: string; + args?: any[]; + title: string; // supports codicon strings + when?: string; +} + +export type IChatFollowup = IChatReplyFollowup | IChatResponseCommandFollowup; + // Name has to match the one in vscode.d.ts for some reason export enum InteractiveSessionVoteDirection { Down = 0, @@ -214,12 +218,12 @@ export interface IChatTerminalAction { export interface IChatCommandAction { kind: 'command'; - commandButton: IChatCommandButton; + command: IChatResponseCommandFollowup; } export interface IChatFollowupAction { kind: 'followUp'; - followup: IChatFollowup; + followup: IChatReplyFollowup; } export interface IChatBugReportAction { diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 5d52f07555b..c55c438f228 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -235,8 +235,8 @@ export class ChatService extends Disposable implements IChatService { newFile: !!action.action.newFile }); } else if (action.action.kind === 'command') { - const command = CommandsRegistry.getCommand(action.action.commandButton.command.id); - const commandId = command ? action.action.commandButton.command.id : 'INVALID'; + const command = CommandsRegistry.getCommand(action.action.command.commandId); + const commandId = command ? action.action.command.commandId : 'INVALID'; this.telemetryService.publicLog2('interactiveSessionCommand', { providerId: action.providerId, commandId diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index d55a8b0c07f..e3712490d9a 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -11,7 +11,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModelInitState, IChatModel, IChatRequestModel, IChatResponseModel, IChatWelcomeMessageContent, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatContentReference, IChatProgressMessage, IChatFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection, IChatCommandButton } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatContentReference, IChatProgressMessage, IChatReplyFollowup, IChatResponseCommandFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; export function isRequestVM(item: unknown): item is IChatRequestViewModel { @@ -60,7 +60,7 @@ export interface IChatRequestViewModel { readonly dataId: string; readonly username: string; readonly avatarIconUri?: URI; - readonly message: IParsedChatRequest | IChatFollowup; + readonly message: IParsedChatRequest | IChatReplyFollowup; readonly messageText: string; currentRenderedHeight: number | undefined; } @@ -88,7 +88,7 @@ export interface IChatProgressMessageRenderData { isLast: boolean; } -export type IChatRenderData = IChatResponseProgressFileTreeData | IChatResponseMarkdownRenderData | IChatProgressMessageRenderData | IChatCommandButton; +export type IChatRenderData = IChatResponseProgressFileTreeData | IChatResponseMarkdownRenderData | IChatProgressMessageRenderData; export interface IChatResponseRenderData { renderedParts: IChatRenderData[]; } @@ -118,9 +118,9 @@ export interface IChatResponseViewModel { readonly progressMessages: ReadonlyArray; readonly isComplete: boolean; readonly isCanceled: boolean; - readonly isStale: boolean; readonly vote: InteractiveSessionVoteDirection | undefined; - readonly replyFollowups?: IChatFollowup[]; + readonly replyFollowups?: IChatReplyFollowup[]; + readonly commandFollowups?: IChatResponseCommandFollowup[]; readonly errorDetails?: IChatResponseErrorDetails; readonly contentUpdateTimings?: IChatLiveUpdateData; renderData?: IChatResponseRenderData; @@ -331,7 +331,11 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi } get replyFollowups() { - return this._model.followups?.filter((f): f is IChatFollowup => f.kind === 'reply'); + return this._model.followups?.filter((f): f is IChatReplyFollowup => f.kind === 'reply'); + } + + get commandFollowups() { + return this._model.followups?.filter((f): f is IChatResponseCommandFollowup => f.kind === 'command'); } get errorDetails() { @@ -346,10 +350,6 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.requestId; } - get isStale() { - return this._model.isStale; - } - renderData: IChatResponseRenderData | undefined = undefined; agentAvatarHasBeenRendered?: boolean; currentRenderedHeight: number | undefined; @@ -436,6 +436,6 @@ export interface IChatWelcomeMessageViewModel { readonly username: string; readonly avatarIconUri?: URI; readonly content: IChatWelcomeMessageContent[]; - readonly sampleQuestions: IChatFollowup[]; + readonly sampleQuestions: IChatReplyFollowup[]; currentRenderedHeight?: number; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 15f98cccce7..8f730b52fe2 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -3,65 +3,66 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Dimension, addDisposableListener, getActiveElement, getTotalHeight, getTotalWidth, h, reset } from 'vs/base/browser/dom'; -import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; -import * as aria from 'vs/base/browser/ui/aria/aria'; -import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event'; -import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { Lazy } from 'vs/base/common/lazy'; -import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { assertType } from 'vs/base/common/types'; -import { URI } from 'vs/base/common/uri'; import 'vs/css!./inlineChat'; -import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; +import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; -import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; -import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; -import { LineRange } from 'vs/editor/common/core/lineRange'; -import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; -import { LanguageSelector } from 'vs/editor/common/languageSelector'; -import { CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages'; -import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { IModelService } from 'vs/editor/common/services/model'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; -import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; -import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; import { localize } from 'vs/nls'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; -import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; -import { MenuId } from 'vs/platform/actions/common/actions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { FileKind } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ILogService } from 'vs/platform/log/common/log'; -import { editorBackground, editorForeground, inputBackground } from 'vs/platform/theme/common/colorRegistry'; -import { ResourceLabel } from 'vs/workbench/browser/labels'; +import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET_STATUS, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, IInlineChatSlashCommand, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_RESPONSE_FOCUSED, ACTION_ACCEPT_CHANGES, MENU_INLINE_CHAT_WIDGET } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; +import { Dimension, addDisposableListener, getActiveElement, getTotalHeight, getTotalWidth, h, reset } from 'vs/base/browser/dom'; +import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event'; +import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; +import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; +import { IModelService } from 'vs/editor/common/services/model'; +import { URI } from 'vs/base/common/uri'; +import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; +import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; +import { Position } from 'vs/editor/common/core/position'; import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; +import { CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages'; +import { ResourceLabel } from 'vs/workbench/browser/labels'; +import { FileKind } from 'vs/platform/files/common/files'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { LanguageSelector } from 'vs/editor/common/languageSelector'; +import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; +import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; +import { LineRange } from 'vs/editor/common/core/lineRange'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { ExpansionState, HunkData } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import * as aria from 'vs/base/browser/ui/aria/aria'; +import { IWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; +import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; -import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; -import { ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; +import { assertType } from 'vs/base/common/types'; +import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; +import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; -import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; -import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { editorForeground, inputBackground, editorBackground } from 'vs/platform/theme/common/colorRegistry'; +import { Lazy } from 'vs/base/common/lazy'; import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; -import { ExpansionState, HunkData } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; -import { ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_VISIBLE, IInlineChatFollowup, IInlineChatSlashCommand, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_FEEDBACK, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -630,9 +631,9 @@ export class InlineChatWidget { return resultingAppender; } - updateFollowUps(items: IInlineChatFollowup[], onFollowup: (followup: IInlineChatFollowup) => void): void; + updateFollowUps(items: IChatFollowup[], onFollowup: (followup: IChatFollowup) => void): void; updateFollowUps(items: undefined): void; - updateFollowUps(items: IInlineChatFollowup[] | undefined, onFollowup?: ((followup: IInlineChatFollowup) => void)) { + updateFollowUps(items: IChatFollowup[] | undefined, onFollowup?: ((followup: IChatFollowup) => void)) { this._followUpDisposables.clear(); this._elements.followUps.classList.toggle('hidden', !items || items.length === 0); reset(this._elements.followUps); diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 8b1d17efa8e..0794a0f8a15 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; -import { Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IRange } from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; +import { Event } from 'vs/base/common/event'; import { ProviderResult, TextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; @@ -20,6 +20,7 @@ import { IProgress } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; import { diffInserted, diffRemoved, editorHoverHighlight, editorWidgetBackground, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { Extensions as ExtensionsMigration, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; +import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { URI } from 'vs/base/common/uri'; export interface IInlineChatSlashCommand { @@ -97,23 +98,6 @@ export const enum InlineChatResponseFeedbackKind { Bug = 4 } -export interface IInlineChatReplyFollowup { - kind: 'reply'; - message: string; - title?: string; - tooltip?: string; -} - -export interface IInlineChatCommandFollowup { - kind: 'command'; - commandId: string; - args?: any[]; - title: string; // supports codicon strings - when?: string; -} - -export type IInlineChatFollowup = IInlineChatReplyFollowup | IInlineChatCommandFollowup; - export interface IInlineChatSessionProvider { debugName: string; @@ -124,7 +108,7 @@ export interface IInlineChatSessionProvider { provideResponse(item: IInlineChatSession, request: IInlineChatRequest, progress: IProgress, token: CancellationToken): ProviderResult; - provideFollowups?(session: IInlineChatSession, response: IInlineChatResponse, token: CancellationToken): ProviderResult; + provideFollowups?(session: IInlineChatSession, response: IInlineChatResponse, token: CancellationToken): ProviderResult; handleInlineChatResponseFeedback?(session: IInlineChatSession, response: IInlineChatResponse, kind: InlineChatResponseFeedbackKind): void; } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index bfc83f254df..b7cda17484a 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -150,10 +150,19 @@ declare module 'vscode' { provideSubCommands(token: CancellationToken): ProviderResult; } + // TODO@API This should become a progress type, and use vscode.Command + // TODO@API what's the when-property for? how about not returning it in the first place? + export interface ChatAgentCommandFollowup { + commandId: string; + args?: any[]; + title: string; // supports codicon strings + when?: string; + } + /** * A followup question suggested by the model. */ - export interface ChatAgentFollowup { + export interface ChatAgentReplyFollowup { /** * The message to send to the chat. */ @@ -170,6 +179,8 @@ declare module 'vscode' { title?: string; } + export type ChatAgentFollowup = ChatAgentCommandFollowup | ChatAgentReplyFollowup; + /** * Will be invoked once after each request to get suggested followup questions to show the user. The user can click the followup to send it to the chat. */ @@ -276,8 +287,6 @@ declare module 'vscode' { anchor(value: Uri | Location, title?: string): ChatAgentResponseStream; - button(command: Command): ChatAgentResponseStream; - // TODO@API this influences the rendering, it inserts new lines which is likely a bug progress(value: string): ChatAgentResponseStream; @@ -338,8 +347,7 @@ declare module 'vscode' { export type ChatAgentContentProgress = | ChatAgentContent | ChatAgentFileTree - | ChatAgentInlineContentReference - | ChatAgentCommandButton; + | ChatAgentInlineContentReference; /** * @deprecated use ChatAgentResponseStream instead @@ -386,13 +394,6 @@ declare module 'vscode' { title?: string; } - /** - * Displays a {@link Command command} as a button in the chat response. - */ - export interface ChatAgentCommandButton { - command: Command; - } - /** * A piece of the chat response's content. Will be merged with other progress pieces as needed, and rendered as markdown. */ diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index b2795ef4b3a..c56dfc45c89 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -113,7 +113,7 @@ declare module 'vscode' { export interface ChatAgentCommandAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'command'; - commandButton: ChatAgentCommandButton; + command: ChatAgentCommandFollowup; } export interface ChatAgentSessionFollowupAction { diff --git a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts b/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts index e40a66c044c..333783f844a 100644 --- a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts +++ b/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts @@ -9,7 +9,7 @@ declare module 'vscode' { export interface ChatAgentWelcomeMessageProvider { provideWelcomeMessage(token: CancellationToken): ProviderResult; - provideSampleQuestions?(token: CancellationToken): ProviderResult; + provideSampleQuestions?(token: CancellationToken): ProviderResult; } export interface ChatAgent2 { diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index 5cfa6c0741e..fd8dc51d014 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -121,7 +121,7 @@ declare module 'vscode' { inputPlaceholder?: string; } - export type InteractiveWelcomeMessageContent = string | MarkdownString | ChatAgentFollowup[]; + export type InteractiveWelcomeMessageContent = string | MarkdownString | ChatAgentReplyFollowup[]; export interface InteractiveSessionProvider { prepareSession(token: CancellationToken): ProviderResult; From 36b41dc6db75780ef77bd9aa11913e64341eb5ac Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:20:28 -0800 Subject: [PATCH 0951/1897] Add Python Prompt detection hint (#204513) Add Python Prompt --- .../common/capabilities/commandDetectionCapability.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index 5ba9a37177d..df3cb4ce9ee 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -903,6 +903,15 @@ class WindowsPtyHeuristics extends Disposable { } } + // Python Prompt + const pythonPrompt = lineText.match(/^(?>>> )/g)?.groups?.prompt; + if (pythonPrompt) { + return { + prompt: pythonPrompt, + likelySingleLine: true + }; + } + // Command Prompt const cmdMatch = lineText.match(/^(?(\(.+\)\s)?(?:[A-Z]:\\.*>))/); return cmdMatch?.groups?.prompt ? { From 8a98afa4403bf7c2c4b06e79565fdf8b9cc9c7d5 Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 7 Feb 2024 10:14:41 +0900 Subject: [PATCH 0952/1897] chore: legacy server cleanups (#204501) * fix: revert glibc requirements for tunnel cli * chore: address feedback on wording * fix: only store state when user allows the prompt * chore: update checksums for sysroot * fix: store connection choice per distro * update rpm deps-list --------- Co-authored-by: Connor Peet --- build/azure-pipelines/cli/cli-compile.yml | 22 +++++++++++++++++++ build/checksums/vscode-sysroot.txt | 9 +++++--- build/linux/debian/install-sysroot.js | 9 ++++---- build/linux/debian/install-sysroot.ts | 9 ++++---- build/linux/rpm/dep-lists.js | 9 -------- build/linux/rpm/dep-lists.ts | 9 -------- .../remote/browser/remoteConnectionHealth.ts | 12 +++++----- 7 files changed, 44 insertions(+), 35 deletions(-) diff --git a/build/azure-pipelines/cli/cli-compile.yml b/build/azure-pipelines/cli/cli-compile.yml index eaa222dbd1d..ba0a70da645 100644 --- a/build/azure-pipelines/cli/cli-compile.yml +++ b/build/azure-pipelines/cli/cli-compile.yml @@ -48,17 +48,39 @@ steps: export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER="$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc" export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C link-arg=--sysroot=$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/aarch64-linux-gnu/sysroot" export CC_aarch64_unknown_linux_gnu="$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc --sysroot=$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/aarch64-linux-gnu/sysroot" + export OBJDUMP="$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/aarch64-linux-gnu/bin/objdump" elif [ "$SYSROOT_ARCH" == "amd64" ]; then export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER="$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/bin/x86_64-linux-gnu-gcc" export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C link-arg=--sysroot=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot -C link-arg=-L$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot/usr/lib/x86_64-linux-gnu" export CC_x86_64_unknown_linux_gnu="$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/bin/x86_64-linux-gnu-gcc --sysroot=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot" + export OBJDUMP="$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/bin/objdump" elif [ "$SYSROOT_ARCH" == "armhf" ]; then export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER="$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/bin/arm-rpi-linux-gnueabihf-gcc" export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_RUSTFLAGS="-C link-arg=--sysroot=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot" export CC_armv7_unknown_linux_gnueabihf="$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/bin/arm-rpi-linux-gnueabihf-gcc --sysroot=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot" + export OBJDUMP="$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/bin/objdump" fi fi + cargo build --release --target ${{ parameters.VSCODE_CLI_TARGET }} --bin=code + + # verify glibc requirement + if [ -n "$SYSROOT_ARCH" ]; then + glibc_version="2.17" + while IFS= read -r line; do + if [[ $line == *"GLIBC_"* ]]; then + version=$(echo "$line" | awk '{print $5}' | tr -d '()') + version=${version#*_} + if [[ $(printf "%s\n%s" "$version" "$glibc_version" | sort -V | tail -n1) == "$version" ]]; then + glibc_version=$version + fi + fi + done < <("$OBJDUMP" -T "$PWD/target/${{ parameters.VSCODE_CLI_TARGET }}/release/code") + if [[ "$glibc_version" != "2.17" ]]; then + echo "Error: binary has dependency on GLIBC > 2.17" + exit 1 + fi + fi displayName: Compile ${{ parameters.VSCODE_CLI_TARGET }} workingDirectory: $(Build.SourcesDirectory)/cli env: diff --git a/build/checksums/vscode-sysroot.txt b/build/checksums/vscode-sysroot.txt index 86da04db2ae..0b5f38c60ce 100644 --- a/build/checksums/vscode-sysroot.txt +++ b/build/checksums/vscode-sysroot.txt @@ -1,3 +1,6 @@ -a2d51dc505ed544c52757f90bcdab44920132295fc7a67166eff86b6e0e24aa8 aarch64-linux-gnu.tar.gz -032cf16bf8b965e1351305f10f3dedabf4f9868027ac6d0e8f52321ca0b70d4a arm-rpi-linux-gnueabihf.tar.gz -360475a764d0faf4d3743aa866347eff78072639d20660def83e1a03eadf534c x86_64-linux-gnu.tar.gz +68a17006021975ff271a1dd615f9db9eda7c25f2cc65e750c87980dc57a06c94 aarch64-linux-gnu-glibc-2.17.tar.gz +0de422a81683cf9e8cf875dbd1e0c27545ac3c775b2d53015daf3ca2b31d3f15 aarch64-linux-gnu-glibc-2.28.tar.gz +3ced48cb479f2cdba95aa649710fcb7778685551c745bbd76ac706c3c0ead9fb arm-rpi-linux-gnueabihf-glibc-2.17.tar.gz +7aea163f7fad8cc50000c86b5108be880121d35e2f55d016ef8c96bbe54129eb arm-rpi-linux-gnueabihf-glibc-2.28.tar.gz +5aae21115f1d284c3cdf32c83db15771b59bc80793f1423032abf5a823c0d658 x86_64-linux-gnu-glibc-2.17.tar.gz +dbb927408393041664a020661f2641c9785741be3d29b050b9dac58980967784 x86_64-linux-gnu-glibc-2.28.tar.gz diff --git a/build/linux/debian/install-sysroot.js b/build/linux/debian/install-sysroot.js index 40ca42efe78..f582558b331 100644 --- a/build/linux/debian/install-sysroot.js +++ b/build/linux/debian/install-sysroot.js @@ -67,7 +67,7 @@ async function fetchUrl(options, retries = 10, retryDelay = 1000) { try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 30 * 1000); - const version = '20231122-245579'; + const version = '20240129-253798'; try { const response = await fetch(`https://api.github.com/repos/Microsoft/vscode-linux-build-agent/releases/tags/v${version}`, { headers: ghApiHeaders, @@ -121,18 +121,19 @@ async function getVSCodeSysroot(arch) { let triple; switch (arch) { case 'amd64': - expectedName = `x86_64-linux-gnu.tar.gz`; + expectedName = `x86_64-linux-gnu-glibc-2.17.tar.gz`; triple = 'x86_64-linux-gnu'; break; case 'arm64': - expectedName = `aarch64-linux-gnu.tar.gz`; + expectedName = `aarch64-linux-gnu-glibc-2.17.tar.gz`; triple = 'aarch64-linux-gnu'; break; case 'armhf': - expectedName = `arm-rpi-linux-gnueabihf.tar.gz`; + expectedName = `arm-rpi-linux-gnueabihf-glibc-2.17.tar.gz`; triple = 'arm-rpi-linux-gnueabihf'; break; } + console.log(`Fetching ${expectedName} for ${triple}`); const checksumSha256 = getVSCodeSysrootChecksum(expectedName); if (!checksumSha256) { throw new Error(`Could not find checksum for ${expectedName}`); diff --git a/build/linux/debian/install-sysroot.ts b/build/linux/debian/install-sysroot.ts index 37ea883d9bc..b704393b655 100644 --- a/build/linux/debian/install-sysroot.ts +++ b/build/linux/debian/install-sysroot.ts @@ -80,7 +80,7 @@ async function fetchUrl(options: IFetchOptions, retries = 10, retryDelay = 1000) try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 30 * 1000); - const version = '20231122-245579'; + const version = '20240129-253798'; try { const response = await fetch(`https://api.github.com/repos/Microsoft/vscode-linux-build-agent/releases/tags/v${version}`, { headers: ghApiHeaders, @@ -139,18 +139,19 @@ export async function getVSCodeSysroot(arch: DebianArchString): Promise let triple: string; switch (arch) { case 'amd64': - expectedName = `x86_64-linux-gnu.tar.gz`; + expectedName = `x86_64-linux-gnu-glibc-2.17.tar.gz`; triple = 'x86_64-linux-gnu'; break; case 'arm64': - expectedName = `aarch64-linux-gnu.tar.gz`; + expectedName = `aarch64-linux-gnu-glibc-2.17.tar.gz`; triple = 'aarch64-linux-gnu'; break; case 'armhf': - expectedName = `arm-rpi-linux-gnueabihf.tar.gz`; + expectedName = `arm-rpi-linux-gnueabihf-glibc-2.17.tar.gz`; triple = 'arm-rpi-linux-gnueabihf'; break; } + console.log(`Fetching ${expectedName} for ${triple}`); const checksumSha256 = getVSCodeSysrootChecksum(expectedName); if (!checksumSha256) { throw new Error(`Could not find checksum for ${expectedName}`); diff --git a/build/linux/rpm/dep-lists.js b/build/linux/rpm/dep-lists.js index e824f19c177..b9a6e80d5f3 100644 --- a/build/linux/rpm/dep-lists.js +++ b/build/linux/rpm/dep-lists.js @@ -42,10 +42,7 @@ exports.referenceGeneratedDepsByArch = { 'libc.so.6(GLIBC_2.15)(64bit)', 'libc.so.6(GLIBC_2.16)(64bit)', 'libc.so.6(GLIBC_2.17)(64bit)', - 'libc.so.6(GLIBC_2.18)(64bit)', 'libc.so.6(GLIBC_2.2.5)(64bit)', - 'libc.so.6(GLIBC_2.25)(64bit)', - 'libc.so.6(GLIBC_2.27)(64bit)', 'libc.so.6(GLIBC_2.28)(64bit)', 'libc.so.6(GLIBC_2.3)(64bit)', 'libc.so.6(GLIBC_2.3.2)(64bit)', @@ -141,9 +138,6 @@ exports.referenceGeneratedDepsByArch = { 'libc.so.6(GLIBC_2.15)', 'libc.so.6(GLIBC_2.16)', 'libc.so.6(GLIBC_2.17)', - 'libc.so.6(GLIBC_2.18)', - 'libc.so.6(GLIBC_2.25)', - 'libc.so.6(GLIBC_2.27)', 'libc.so.6(GLIBC_2.28)', 'libc.so.6(GLIBC_2.4)', 'libc.so.6(GLIBC_2.6)', @@ -245,9 +239,6 @@ exports.referenceGeneratedDepsByArch = { 'libatspi.so.0()(64bit)', 'libc.so.6()(64bit)', 'libc.so.6(GLIBC_2.17)(64bit)', - 'libc.so.6(GLIBC_2.18)(64bit)', - 'libc.so.6(GLIBC_2.25)(64bit)', - 'libc.so.6(GLIBC_2.27)(64bit)', 'libc.so.6(GLIBC_2.28)(64bit)', 'libcairo.so.2()(64bit)', 'libcurl.so.4()(64bit)', diff --git a/build/linux/rpm/dep-lists.ts b/build/linux/rpm/dep-lists.ts index 1c38df695db..275d88b95a8 100644 --- a/build/linux/rpm/dep-lists.ts +++ b/build/linux/rpm/dep-lists.ts @@ -41,10 +41,7 @@ export const referenceGeneratedDepsByArch = { 'libc.so.6(GLIBC_2.15)(64bit)', 'libc.so.6(GLIBC_2.16)(64bit)', 'libc.so.6(GLIBC_2.17)(64bit)', - 'libc.so.6(GLIBC_2.18)(64bit)', 'libc.so.6(GLIBC_2.2.5)(64bit)', - 'libc.so.6(GLIBC_2.25)(64bit)', - 'libc.so.6(GLIBC_2.27)(64bit)', 'libc.so.6(GLIBC_2.28)(64bit)', 'libc.so.6(GLIBC_2.3)(64bit)', 'libc.so.6(GLIBC_2.3.2)(64bit)', @@ -140,9 +137,6 @@ export const referenceGeneratedDepsByArch = { 'libc.so.6(GLIBC_2.15)', 'libc.so.6(GLIBC_2.16)', 'libc.so.6(GLIBC_2.17)', - 'libc.so.6(GLIBC_2.18)', - 'libc.so.6(GLIBC_2.25)', - 'libc.so.6(GLIBC_2.27)', 'libc.so.6(GLIBC_2.28)', 'libc.so.6(GLIBC_2.4)', 'libc.so.6(GLIBC_2.6)', @@ -244,9 +238,6 @@ export const referenceGeneratedDepsByArch = { 'libatspi.so.0()(64bit)', 'libc.so.6()(64bit)', 'libc.so.6(GLIBC_2.17)(64bit)', - 'libc.so.6(GLIBC_2.18)(64bit)', - 'libc.so.6(GLIBC_2.25)(64bit)', - 'libc.so.6(GLIBC_2.27)(64bit)', 'libc.so.6(GLIBC_2.28)(64bit)', 'libcairo.so.2()(64bit)', 'libcurl.so.4()(64bit)', diff --git a/src/vs/workbench/contrib/remote/browser/remoteConnectionHealth.ts b/src/vs/workbench/contrib/remote/browser/remoteConnectionHealth.ts index c75b9c543f7..e75900e7fa7 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteConnectionHealth.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteConnectionHealth.ts @@ -49,7 +49,7 @@ export class InitialRemoteConnectionHealthContribution implements IWorkbenchCont const { result, checkboxChecked } = await this.dialogService.prompt({ type: Severity.Warning, - message: localize('unsupportedGlibcWarning', "You are about to connect to an OS that is unsupported by {0}", this.productService.nameLong), + message: localize('unsupportedGlibcWarning', "You are about to connect to an OS version that is unsupported by {0}.", this.productService.nameLong), buttons: [ { label: localize({ key: 'allow', comment: ['&& denotes a mnemonic'] }, "&&Allow"), @@ -64,7 +64,7 @@ export class InitialRemoteConnectionHealthContribution implements IWorkbenchCont run: () => ConnectionChoice.Cancel }, checkbox: { - label: localize('remember', "Do not ask me again"), + label: localize('remember', "Do not show again"), } }); @@ -73,8 +73,8 @@ export class InitialRemoteConnectionHealthContribution implements IWorkbenchCont } const allowed = result === ConnectionChoice.Allow; - if (checkboxChecked) { - this.storageService.store(REMOTE_UNSUPPORTED_CONNECTION_CHOICE_KEY, allowed, StorageScope.PROFILE, StorageTarget.MACHINE); + if (allowed && checkboxChecked) { + this.storageService.store(`${REMOTE_UNSUPPORTED_CONNECTION_CHOICE_KEY}.${this._environmentService.remoteAuthority}`, allowed, StorageScope.PROFILE, StorageTarget.MACHINE); } return allowed; @@ -85,7 +85,7 @@ export class InitialRemoteConnectionHealthContribution implements IWorkbenchCont const environment = await this._remoteAgentService.getRawEnvironment(); if (environment && environment.isUnsupportedGlibc) { - let allowed = this.storageService.getBoolean(REMOTE_UNSUPPORTED_CONNECTION_CHOICE_KEY, StorageScope.PROFILE); + let allowed = this.storageService.getBoolean(`${REMOTE_UNSUPPORTED_CONNECTION_CHOICE_KEY}.${this._environmentService.remoteAuthority}`, StorageScope.PROFILE); if (allowed === undefined) { allowed = await this._confirmConnection(); } @@ -98,7 +98,7 @@ export class InitialRemoteConnectionHealthContribution implements IWorkbenchCont ]; this.bannerService.show({ id: 'unsupportedGlibcWarning.banner', - message: localize('unsupportedGlibcWarning.banner', "You are connected to an OS that is unsupported by {0}.", this.productService.nameLong), + message: localize('unsupportedGlibcWarning.banner', "You are connected to an OS version that is unsupported by {0}.", this.productService.nameLong), actions, icon: Codicon.warning, disableCloseAction: true From 29e000b1e5c03e7c9a6fccf3e475f0b5a239e4d6 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Tue, 6 Feb 2024 18:06:06 -0800 Subject: [PATCH 0953/1897] Migrate user's `password-store` if they are `gnome` or `gnome-keyring` (#204553) * Migrate user's `password-store` if they are `gnome` or `gnome-keyring` True fix for https://github.com/microsoft/vscode/issues/204318 * add import --- src/main.js | 28 +++++------ .../encryption/common/encryptionService.ts | 2 - .../encryption.contribution.ts | 48 +++++++++++++++++++ src/vs/workbench/workbench.desktop.main.ts | 2 + 4 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 src/vs/workbench/contrib/encryption/electron-sandbox/encryption.contribution.ts diff --git a/src/main.js b/src/main.js index de5c7dfbcb8..90de17b278b 100644 --- a/src/main.js +++ b/src/main.js @@ -233,25 +233,25 @@ function configureCommandlineSwitchesSync(cliArgs) { // Append Electron flags to Electron if (SUPPORTED_ELECTRON_SWITCHES.indexOf(argvKey) !== -1) { - - if ( - // Color profile - argvKey === 'force-color-profile' || - // Password store - argvKey === 'password-store' - ) { - if (argvValue) { - app.commandLine.appendSwitch(argvKey, argvValue); - } - } - - // Others - else if (argvValue === true || argvValue === 'true') { + if (argvValue === true || argvValue === 'true') { if (argvKey === 'disable-hardware-acceleration') { app.disableHardwareAcceleration(); // needs to be called explicitly } else { app.commandLine.appendSwitch(argvKey); } + } else if (argvValue) { + if (argvKey === 'force-color-profile') { + // Color profile + app.commandLine.appendSwitch(argvKey, argvValue); + } else if (argvKey === 'password-store') { + // Password store + // TODO@TylerLeonhardt: Remove this migration in 3 months + let migratedArgvValue = argvValue; + if (argvValue === 'gnome' || argvValue === 'gnome-keyring') { + migratedArgvValue = 'gnome-libsecret'; + } + app.commandLine.appendSwitch(argvKey, migratedArgvValue); + } } } diff --git a/src/vs/platform/encryption/common/encryptionService.ts b/src/vs/platform/encryption/common/encryptionService.ts index c00a32a663e..b36ef5724a6 100644 --- a/src/vs/platform/encryption/common/encryptionService.ts +++ b/src/vs/platform/encryption/common/encryptionService.ts @@ -31,8 +31,6 @@ export interface ICommonEncryptionService { export const enum PasswordStoreCLIOption { kwallet = 'kwallet', kwallet5 = 'kwallet5', - gnome = 'gnome', - gnomeKeyring = 'gnome-keyring', gnomeLibsecret = 'gnome-libsecret', basic = 'basic' } diff --git a/src/vs/workbench/contrib/encryption/electron-sandbox/encryption.contribution.ts b/src/vs/workbench/contrib/encryption/electron-sandbox/encryption.contribution.ts new file mode 100644 index 00000000000..9969928a2b5 --- /dev/null +++ b/src/vs/workbench/contrib/encryption/electron-sandbox/encryption.contribution.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isLinux } from 'vs/base/common/platform'; +import { stripComments } from 'vs/base/common/stripComments'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; + +class EncryptionContribution implements IWorkbenchContribution { + constructor( + @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IFileService private readonly fileService: IFileService, + @IStorageService private readonly storageService: IStorageService + ) { + this.migrateToGnomeLibsecret(); + } + + /** + * Migrate the user from using the gnome or gnome-keyring password-store to gnome-libsecret. + * TODO@TylerLeonhardt: This migration can be removed in 3 months or so and then storage + * can be cleaned up. + */ + private async migrateToGnomeLibsecret(): Promise { + if (!isLinux || this.storageService.getBoolean('encryption.migratedToGnomeLibsecret', StorageScope.APPLICATION, false)) { + return; + } + try { + const content = await this.fileService.readFile(this.environmentService.argvResource); + const argv = JSON.parse(stripComments(content.value.toString())); + if (argv['password-store'] === 'gnome' || argv['password-store'] === 'gnome-keyring') { + this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['password-store'], value: 'gnome-libsecret' }], true); + } + this.storageService.store('encryption.migratedToGnomeLibsecret', true, StorageScope.APPLICATION, StorageTarget.USER); + } catch (error) { + console.error(error); + } + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EncryptionContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 4e56e19903c..bb6d118d460 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -173,6 +173,8 @@ import 'vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribu import 'vs/workbench/contrib/chat/electron-sandbox/chat.contribution'; import 'vs/workbench/contrib/inlineChat/electron-sandbox/inlineChat.contribution'; +// Encryption +import 'vs/workbench/contrib/encryption/electron-sandbox/encryption.contribution'; //#endregion From 95386afadd3d291a119f44f94d9bd7c71325d12c Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 6 Feb 2024 18:29:47 -0800 Subject: [PATCH 0954/1897] Expose chat code blocks as documents (#204551) Exposes code blocks as documents. Also: - Enables hovers in code blocks - Makes sure hover widgets are cleared when chat is cleared or scrolled --- src/vs/base/common/network.ts | 3 ++ .../contrib/chat/browser/chatListRenderer.ts | 16 +++++---- .../contrib/chat/browser/chatWidget.ts | 7 ++++ .../contrib/chat/browser/codeBlockPart.ts | 35 ++++++++++++++++--- 4 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 00ad27c3d55..3e0cc0071af 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -71,6 +71,9 @@ export namespace Schemas { export const vscodeTerminal = 'vscode-terminal'; + /** Scheme used for code blocks in chat. */ + export const vscodeChatCodeBlock = 'vscode-chat-code-block'; + /** Scheme used for the chat input editor. */ export const vscodeChatSesssion = 'vscode-chat-editor'; /** diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 6e30f1e51d4..b95b85a22d6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -92,6 +92,8 @@ const forceVerboseLayoutTracing = false; export interface IChatRendererDelegate { getListLength(): number; + + readonly onDidScroll?: Event; } export interface IChatListItemRendererOptions { @@ -146,7 +148,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - return this.instantiationService.createInstance(SimpleCodeBlockPart, this.options, MenuId.ChatCodeBlock); + return this.instantiationService.createInstance(SimpleCodeBlockPart, this.options, MenuId.ChatCodeBlock, delegate, overflowWidgetsDomNode); })); this._localFileEditorPool = this._register(new ResourcePool(() => { - return this.instantiationService.createInstance(LocalFileCodeBlockPart, this.options, MenuId.ChatCodeBlock, overflowWidgetsDomNode); + return this.instantiationService.createInstance(LocalFileCodeBlockPart, this.options, MenuId.ChatCodeBlock, delegate, overflowWidgetsDomNode); })); } @@ -1076,14 +1079,15 @@ class EditorPool extends Disposable { } private getFromPool(pool: ResourcePool): IDisposableReference { - const object = pool.get(); + const codeBlock = pool.get(); let stale = false; return { - object, + object: codeBlock, isStale: () => stale, dispose: () => { + codeBlock.reset(); stale = true; - pool.release(object); + pool.release(codeBlock); } }; } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index d0681b606f5..ed99c1c6409 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -79,6 +79,9 @@ export class ChatWidget extends Disposable implements IChatWidget { private _onDidChangeViewModel = this._register(new Emitter()); readonly onDidChangeViewModel = this._onDidChangeViewModel.event; + private _onDidScroll = this._register(new Emitter()); + readonly onDidScroll = this._onDidScroll.event; + private _onDidClear = this._register(new Emitter()); readonly onDidClear = this._onDidClear.event; @@ -321,6 +324,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const delegate = scopedInstantiationService.createInstance(ChatListDelegate); const rendererDelegate: IChatRendererDelegate = { getListLength: () => this.tree.getNode(null).visibleChildrenCount, + onDidScroll: this.onDidScroll, }; // Create a dom element to hold UI from editor widgets embedded in chat messages @@ -381,6 +385,9 @@ export class ChatWidget extends Disposable implements IChatWidget { this._register(this.tree.onDidFocus(() => { this._onDidFocus.fire(); })); + this._register(this.tree.onDidScroll(() => { + this._onDidScroll.fire(); + })); } private onContextMenu(e: ITreeContextMenuEvent): void { diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 6663727301b..5fb9f1e41f0 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -10,7 +10,10 @@ import { Button } from 'vs/base/browser/ui/button/button'; import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IReference, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; import { URI, UriComponents } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { EDITOR_FONT_DEFAULTS, EditorOption, IEditorOptions } from 'vs/editor/common/config/editorOptions'; @@ -23,6 +26,7 @@ import { IModelService } from 'vs/editor/common/services/model'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { BracketMatchingController } from 'vs/editor/contrib/bracketMatching/browser/bracketMatching'; import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu'; +import { HoverController } from 'vs/editor/contrib/hover/browser/hover'; import { ViewportSemanticTokensContribution } from 'vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens'; import { SmartSelectController } from 'vs/editor/contrib/smartSelect/browser/smartSelect'; import { WordHighlighterContribution } from 'vs/editor/contrib/wordHighlighter/browser/wordHighlighter'; @@ -35,13 +39,13 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; import { IMarkdownVulnerability } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; -import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; const $ = dom.$; @@ -120,6 +124,7 @@ export interface ICodeBlockPart { layout(width: number): void; render(data: Data, width: number): Promise; focus(): void; + reset(): unknown; dispose(): void; } @@ -140,6 +145,7 @@ abstract class BaseCodeBlockPart extends Disposable constructor( private readonly options: ChatEditorOptions, readonly menuId: MenuId, + delegate: IChatRendererDelegate, overflowWidgetsDomNode: HTMLElement | undefined, @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @@ -207,11 +213,19 @@ abstract class BaseCodeBlockPart extends Disposable this._register(this.editor.onDidBlurEditorWidget(() => { this.element.classList.remove('focused'); WordHighlighterContribution.get(this.editor)?.stopHighlighting(); + this.clearWidgets(); })); this._register(this.editor.onDidFocusEditorWidget(() => { this.element.classList.add('focused'); WordHighlighterContribution.get(this.editor)?.restoreViewState(true); })); + + // Parent list scrolled + if (delegate.onDidScroll) { + this._register(delegate.onDidScroll(e => { + this.clearWidgets(); + })); + } } protected abstract createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): CodeEditorWidget; @@ -290,6 +304,14 @@ abstract class BaseCodeBlockPart extends Disposable } protected abstract updateEditor(data: Data): void | Promise; + + reset() { + this.clearWidgets(); + } + + private clearWidgets() { + HoverController.get(this.editor)?.hideContentHover(); + } } @@ -304,6 +326,8 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart constructor( options: ChatEditorOptions, menuId: MenuId, + delegate: IChatRendererDelegate, + overflowWidgetsDomNode: HTMLElement | undefined, @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @IModelService modelService: IModelService, @@ -311,7 +335,7 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart @IAccessibilityService accessibilityService: IAccessibilityService, @ILanguageService private readonly languageService: ILanguageService, ) { - super(options, menuId, undefined, instantiationService, contextKeyService, modelService, configurationService, accessibilityService); + super(options, menuId, delegate, overflowWidgetsDomNode, instantiationService, contextKeyService, modelService, configurationService, accessibilityService); const vulnsContainer = dom.append(this.element, $('.interactive-result-vulns')); const vulnsHeaderElement = dom.append(vulnsContainer, $('.interactive-result-vulns-header', undefined)); @@ -326,7 +350,8 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart buttonSeparator: undefined, supportIcons: true }); - this.textModel = this._register(this.modelService.createModel('', null, undefined, true)); + const modelUri = URI.from({ scheme: Schemas.vscodeChatCodeBlock, path: generateUuid() }); + this.textModel = this._register(this.modelService.createModel('', null, modelUri, false)); this.editor.setModel(this.textModel); this.vulnsListElement = dom.append(vulnsContainer, $('ul.interactive-result-vulns-list')); @@ -357,6 +382,7 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart ViewportSemanticTokensContribution.ID, BracketMatchingController.ID, SmartSelectController.ID, + HoverController.ID, ]) })); } @@ -444,6 +470,7 @@ export class LocalFileCodeBlockPart extends BaseCodeBlockPart Date: Wed, 7 Feb 2024 13:50:23 +0900 Subject: [PATCH 0955/1897] fix: sysroot used for client (#204559) --- build/azure-pipelines/cli/cli-compile.yml | 1 + build/linux/debian/install-sysroot.js | 7 ++++--- build/linux/debian/install-sysroot.ts | 7 ++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/build/azure-pipelines/cli/cli-compile.yml b/build/azure-pipelines/cli/cli-compile.yml index ba0a70da645..8d8b313253e 100644 --- a/build/azure-pipelines/cli/cli-compile.yml +++ b/build/azure-pipelines/cli/cli-compile.yml @@ -42,6 +42,7 @@ steps: - script: | set -e if [ -n "$SYSROOT_ARCH" ]; then + export VSCODE_SYSROOT_PREFIX='-glibc-2.17' export VSCODE_SYSROOT_DIR=$(Build.SourcesDirectory)/.build/sysroots node -e '(async () => { const { getVSCodeSysroot } = require("../build/linux/debian/install-sysroot.js"); await getVSCodeSysroot(process.env["SYSROOT_ARCH"]); })()' if [ "$SYSROOT_ARCH" == "arm64" ]; then diff --git a/build/linux/debian/install-sysroot.js b/build/linux/debian/install-sysroot.js index f582558b331..d637fce3ca6 100644 --- a/build/linux/debian/install-sysroot.js +++ b/build/linux/debian/install-sysroot.js @@ -119,17 +119,18 @@ async function fetchUrl(options, retries = 10, retryDelay = 1000) { async function getVSCodeSysroot(arch) { let expectedName; let triple; + const prefix = process.env['VSCODE_SYSROOT_PREFIX'] ?? '-glibc-2.28'; switch (arch) { case 'amd64': - expectedName = `x86_64-linux-gnu-glibc-2.17.tar.gz`; + expectedName = `x86_64-linux-gnu${prefix}.tar.gz`; triple = 'x86_64-linux-gnu'; break; case 'arm64': - expectedName = `aarch64-linux-gnu-glibc-2.17.tar.gz`; + expectedName = `aarch64-linux-gnu${prefix}.tar.gz`; triple = 'aarch64-linux-gnu'; break; case 'armhf': - expectedName = `arm-rpi-linux-gnueabihf-glibc-2.17.tar.gz`; + expectedName = `arm-rpi-linux-gnueabihf${prefix}.tar.gz`; triple = 'arm-rpi-linux-gnueabihf'; break; } diff --git a/build/linux/debian/install-sysroot.ts b/build/linux/debian/install-sysroot.ts index b704393b655..aa417dcc722 100644 --- a/build/linux/debian/install-sysroot.ts +++ b/build/linux/debian/install-sysroot.ts @@ -137,17 +137,18 @@ type SysrootDictEntry = { export async function getVSCodeSysroot(arch: DebianArchString): Promise { let expectedName: string; let triple: string; + const prefix = process.env['VSCODE_SYSROOT_PREFIX'] ?? '-glibc-2.28'; switch (arch) { case 'amd64': - expectedName = `x86_64-linux-gnu-glibc-2.17.tar.gz`; + expectedName = `x86_64-linux-gnu${prefix}.tar.gz`; triple = 'x86_64-linux-gnu'; break; case 'arm64': - expectedName = `aarch64-linux-gnu-glibc-2.17.tar.gz`; + expectedName = `aarch64-linux-gnu${prefix}.tar.gz`; triple = 'aarch64-linux-gnu'; break; case 'armhf': - expectedName = `arm-rpi-linux-gnueabihf-glibc-2.17.tar.gz`; + expectedName = `arm-rpi-linux-gnueabihf${prefix}.tar.gz`; triple = 'arm-rpi-linux-gnueabihf'; break; } From f8546bc73f498d0c46f2ac22603d4d28406a28b7 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 7 Feb 2024 08:59:24 +0100 Subject: [PATCH 0956/1897] API to find the active comment thread (#204486) * API to find the active comment thread Fixes #204484 * Add issue ref * Add activeComment proposal to api tests * Add settimeout to blur event --- extensions/vscode-api-tests/package.json | 1 + .../api/browser/mainThreadComments.ts | 20 ++++++---- .../workbench/api/common/extHost.protocol.ts | 1 + .../workbench/api/common/extHostComments.ts | 38 +++++++++++++++++++ .../contrib/comments/browser/commentNode.ts | 20 ++++++++-- .../contrib/comments/browser/commentReply.ts | 21 +++++++--- .../comments/browser/commentService.ts | 24 +++++++++--- .../comments/browser/commentThreadBody.ts | 2 +- .../test/browser/commentsView.test.ts | 3 ++ .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.activeComment.d.ts | 22 +++++++++++ 11 files changed, 129 insertions(+), 24 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.activeComment.d.ts diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index baf38d7c6fb..2ba38dfdf9e 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -5,6 +5,7 @@ "publisher": "vscode", "license": "MIT", "enabledApiProposals": [ + "activeComment", "authSession", "chatAgents2", "defaultChatAgent", diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 98a479f4b9a..538c754e1b3 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -242,7 +242,7 @@ export class MainThreadCommentController implements ICommentController { } private readonly _threads: Map> = new Map>(); - public activeCommentThread?: MainThreadCommentThread; + public activeEditingCommentThread?: MainThreadCommentThread; get features(): CommentProviderFeatures { return this._features; @@ -258,6 +258,10 @@ export class MainThreadCommentController implements ICommentController { private _features: CommentProviderFeatures ) { } + async setActiveCommentAndThread(commentInfo: { thread: languages.CommentThread; comment?: languages.Comment } | undefined) { + return this._proxy.$setActiveComment(this._handle, commentInfo ? { commentThreadHandle: commentInfo.thread.commentThreadHandle, uniqueIdInThread: commentInfo.comment?.uniqueIdInThread } : undefined); + } + updateFeatures(features: CommentProviderFeatures) { this._features = features; } @@ -357,7 +361,7 @@ export class MainThreadCommentController implements ICommentController { } updateInput(input: string) { - const thread = this.activeCommentThread; + const thread = this.activeEditingCommentThread; if (thread && thread.input) { const commentInput = thread.input; @@ -477,8 +481,8 @@ export class MainThreadComments extends Disposable implements MainThreadComments private _handlers = new Map(); private _commentControllers = new Map(); - private _activeCommentThread?: MainThreadCommentThread; - private readonly _activeCommentThreadDisposables = this._register(new DisposableStore()); + private _activeEditingCommentThread?: MainThreadCommentThread; + private readonly _activeEditingCommentThreadDisposables = this._register(new DisposableStore()); private _openViewListener: IDisposable | null = null; @@ -493,7 +497,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostComments); this._commentService.unregisterCommentController(); - this._register(this._commentService.onDidChangeActiveCommentThread(async thread => { + this._register(this._commentService.onDidChangeActiveEditingCommentThread(async thread => { const handle = (thread as MainThreadCommentThread).controllerHandle; const controller = this._commentControllers.get(handle); @@ -501,9 +505,9 @@ export class MainThreadComments extends Disposable implements MainThreadComments return; } - this._activeCommentThreadDisposables.clear(); - this._activeCommentThread = thread as MainThreadCommentThread; - controller.activeCommentThread = this._activeCommentThread; + this._activeEditingCommentThreadDisposables.clear(); + this._activeEditingCommentThread = thread as MainThreadCommentThread; + controller.activeEditingCommentThread = this._activeEditingCommentThread; })); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index d24c57433a8..78eda6ea257 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2403,6 +2403,7 @@ export interface ExtHostCommentsShape { $deleteCommentThread(commentControllerHandle: number, commentThreadHandle: number): void; $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise<{ ranges: IRange[]; fileComments: boolean } | undefined>; $toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: languages.Comment, reaction: languages.CommentReaction): Promise; + $setActiveComment(controllerHandle: number, commentInfo: { commentThreadHandle: number; uniqueIdInThread?: number } | undefined): Promise; } export interface INotebookSelectionChangeEvent { diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 91e14ee9a07..95db9c67fee 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -168,6 +168,16 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo commentController.$createCommentThreadTemplate(uriComponents, range); } + async $setActiveComment(controllerHandle: number, commentInfo: { commentThreadHandle: number; uniqueIdInThread?: number }): Promise { + const commentController = this._commentControllers.get(controllerHandle); + + if (!commentController) { + return; + } + + commentController.$setActiveComment(commentInfo ?? undefined); + } + async $updateCommentThreadTemplate(commentControllerHandle: number, threadHandle: number, range: IRange) { const commentController = this._commentControllers.get(commentControllerHandle); @@ -582,6 +592,19 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo proxy.$updateCommentControllerFeatures(this.handle, { options: this._options }); } + private _activeComment: vscode.Comment | undefined; + + get activeComment(): vscode.Comment | undefined { + checkProposedApiEnabled(this._extension, 'activeComment'); + return this._activeComment; + } + + private _activeThread: vscode.CommentThread2 | undefined; + + get activeThread(): vscode.CommentThread2 | undefined { + checkProposedApiEnabled(this._extension, 'activeComment'); + return this._activeThread; + } private _localDisposables: types.Disposable[]; readonly value: vscode.CommentController; @@ -604,6 +627,8 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo set commentingRangeProvider(commentingRangeProvider: vscode.CommentingRangeProvider | undefined) { that.commentingRangeProvider = commentingRangeProvider; }, get reactionHandler(): ReactionHandler | undefined { return that.reactionHandler; }, set reactionHandler(handler: ReactionHandler | undefined) { that.reactionHandler = handler; }, + // get activeComment(): vscode.Comment | undefined { return that.activeComment; }, + get activeThread(): vscode.CommentThread2 | undefined { return that.activeThread; }, createCommentThread(uri: vscode.Uri, range: vscode.Range | undefined, comments: vscode.Comment[]): vscode.CommentThread | vscode.CommentThread2 { return that.createCommentThread(uri, range, comments).value; }, @@ -627,6 +652,19 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo return commentThread; } + $setActiveComment(commentInfo: { commentThreadHandle: number; uniqueIdInThread?: number } | undefined) { + if (!commentInfo) { + this._activeComment = undefined; + this._activeThread = undefined; + return; + } + const thread = this._threads.get(commentInfo.commentThreadHandle); + if (thread) { + this._activeComment = commentInfo.uniqueIdInThread ? thread.getCommentByUniqueId(commentInfo.uniqueIdInThread) : undefined; + this._activeThread = thread; + } + } + $createCommentThreadTemplate(uriComponents: UriComponents, range: IRange | undefined): ExtHostCommentThread { const commentThread = new ExtHostCommentThread(this.id, this.handle, undefined, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), [], this._extension, true); commentThread.collapsibleState = languages.CommentThreadCollapsibleState.Expanded; diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 7197063ec80..13ddcb48f1e 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -166,6 +166,17 @@ export class CommentNode extends Disposable { this._register(this.accessibilityService.onDidChangeScreenReaderOptimized(() => { this.toggleToolbarHidden(true); })); + + this.activeCommentListeners(); + } + + private activeCommentListeners() { + this._register(dom.addDisposableListener(this._domNode, dom.EventType.FOCUS_IN, () => { + this.commentService.setActiveCommentAndThread(this.owner, { thread: this.commentThread, comment: this.comment }); + }, true)); + this._register(dom.addDisposableListener(this._domNode, dom.EventType.FOCUS_OUT, () => { + this.commentService.setActiveCommentAndThread(this.owner, undefined); + }, true)); } private createScroll(container: HTMLElement, body: HTMLElement) { @@ -502,14 +513,16 @@ export class CommentNode extends Disposable { uri: this._commentEditor.getModel()!.uri, value: this.commentBodyValue }; - this.commentService.setActiveCommentThread(commentThread); + this.commentService.setActiveEditingCommentThread(commentThread); + this.commentService.setActiveCommentAndThread(this.owner, { thread: commentThread, comment: this.comment }); this._commentEditorDisposables.push(this._commentEditor.onDidFocusEditorWidget(() => { commentThread.input = { uri: this._commentEditor!.getModel()!.uri, value: this.commentBodyValue }; - this.commentService.setActiveCommentThread(commentThread); + this.commentService.setActiveEditingCommentThread(commentThread); + this.commentService.setActiveCommentAndThread(this.owner, { thread: commentThread, comment: this.comment }); })); this._commentEditorDisposables.push(this._commentEditor.onDidChangeModelContent(e => { @@ -519,7 +532,8 @@ export class CommentNode extends Disposable { const input = commentThread.input; input.value = newVal; commentThread.input = input; - this.commentService.setActiveCommentThread(commentThread); + this.commentService.setActiveEditingCommentThread(commentThread); + this.commentService.setActiveCommentAndThread(this.owner, { thread: commentThread, comment: this.comment }); } } })); diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts index d2c54a4c9f9..e4d45918616 100644 --- a/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -233,11 +233,20 @@ export class CommentReply extends Disposable { private createTextModelListener(commentEditor: ICodeEditor, commentForm: HTMLElement) { this._commentThreadDisposables.push(commentEditor.onDidFocusEditorWidget(() => { - this._commentThread.input = { - uri: commentEditor.getModel()!.uri, - value: commentEditor.getValue() - }; - this.commentService.setActiveCommentThread(this._commentThread); + // Add a setTimeout so that the blur event doesn't fire before the focus event + // https://github.com/microsoft/vscode/blob/f6d945edbdc1b2e8a176624fdf612bb61468944f/src/vs/base/browser/dom.ts#L1322-L1328 + setTimeout(() => { + this._commentThread.input = { + uri: commentEditor.getModel()!.uri, + value: commentEditor.getValue() + }; + this.commentService.setActiveEditingCommentThread(this._commentThread); + this.commentService.setActiveCommentAndThread(this.owner, { thread: this._commentThread }); + }, 0); + })); + + this._commentThreadDisposables.push(commentEditor.onDidBlurEditorWidget(() => { + this.commentService.setActiveCommentAndThread(this.owner, undefined); })); this._commentThreadDisposables.push(commentEditor.getModel()!.onDidChangeContent(() => { @@ -247,7 +256,7 @@ export class CommentReply extends Disposable { newInput.value = modelContent; this._commentThread.input = newInput; } - this.commentService.setActiveCommentThread(this._commentThread); + this.commentService.setActiveEditingCommentThread(this._commentThread); })); this._commentThreadDisposables.push(this._commentThread.onDidChangeInput(input => { diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index de319272c0e..2f72753c28c 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -67,6 +67,7 @@ export interface ICommentController { toggleReaction(uri: URI, thread: CommentThread, comment: Comment, reaction: CommentReaction, token: CancellationToken): Promise; getDocumentComments(resource: URI, token: CancellationToken): Promise; getNotebookComments(resource: URI, token: CancellationToken): Promise; + setActiveCommentAndThread(commentInfo: { thread: CommentThread; comment?: Comment } | undefined): Promise; } export interface IContinueOnCommentProvider { @@ -79,7 +80,7 @@ export interface ICommentService { readonly onDidSetAllCommentThreads: Event; readonly onDidUpdateCommentThreads: Event; readonly onDidUpdateNotebookCommentThreads: Event; - readonly onDidChangeActiveCommentThread: Event; + readonly onDidChangeActiveEditingCommentThread: Event; readonly onDidChangeCurrentCommentThread: Event; readonly onDidUpdateCommentingRanges: Event<{ owner: string }>; readonly onDidChangeActiveCommentingRange: Event<{ range: Range; commentingRangesInfo: CommentingRanges }>; @@ -105,8 +106,9 @@ export interface ICommentService { updateCommentingRanges(ownerId: string): void; hasReactionHandler(owner: string): boolean; toggleReaction(owner: string, resource: URI, thread: CommentThread, comment: Comment, reaction: CommentReaction): Promise; - setActiveCommentThread(commentThread: CommentThread | null): void; + setActiveEditingCommentThread(commentThread: CommentThread | null): void; setCurrentCommentThread(commentThread: CommentThread | undefined): void; + setActiveCommentAndThread(owner: string, commentInfo: { thread: CommentThread; comment?: Comment } | undefined): Promise; enableCommenting(enable: boolean): void; registerContinueOnCommentProvider(provider: IContinueOnCommentProvider): IDisposable; removeContinueOnComment(pendingComment: { range: IRange | undefined; uri: URI; owner: string; isReply?: boolean }): PendingCommentThread | undefined; @@ -138,8 +140,8 @@ export class CommentService extends Disposable implements ICommentService { private readonly _onDidUpdateCommentingRanges: Emitter<{ owner: string }> = this._register(new Emitter<{ owner: string }>()); readonly onDidUpdateCommentingRanges: Event<{ owner: string }> = this._onDidUpdateCommentingRanges.event; - private readonly _onDidChangeActiveCommentThread = this._register(new Emitter()); - readonly onDidChangeActiveCommentThread = this._onDidChangeActiveCommentThread.event; + private readonly _onDidChangeActiveEditingCommentThread = this._register(new Emitter()); + readonly onDidChangeActiveEditingCommentThread = this._onDidChangeActiveEditingCommentThread.event; private readonly _onDidChangeCurrentCommentThread = this._register(new Emitter()); readonly onDidChangeCurrentCommentThread = this._onDidChangeCurrentCommentThread.event; @@ -266,8 +268,18 @@ export class CommentService extends Disposable implements ICommentService { * The active comment thread is the the thread that is currently being edited. * @param commentThread */ - setActiveCommentThread(commentThread: CommentThread | null) { - this._onDidChangeActiveCommentThread.fire(commentThread); + setActiveEditingCommentThread(commentThread: CommentThread | null) { + this._onDidChangeActiveEditingCommentThread.fire(commentThread); + } + + async setActiveCommentAndThread(owner: string, commentInfo: { thread: CommentThread; comment?: Comment } | undefined) { + const commentController = this._commentControls.get(owner); + + if (!commentController) { + return; + } + + return commentController.setActiveCommentAndThread(commentInfo); } setDocumentComments(resource: URI, commentInfos: ICommentInfo[]): void { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts index 035f9057c37..62656e6f97a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts @@ -60,7 +60,7 @@ export class CommentThreadBody extends D this._register(dom.addDisposableListener(container, dom.EventType.FOCUS_IN, e => { // TODO @rebornix, limit T to IRange | ICellRange - this.commentService.setActiveCommentThread(this._commentThread); + this.commentService.setActiveEditingCommentThread(this._commentThread); })); this._markdownRenderer = this._register(new MarkdownRenderer(this._options, this.languageService, this.openerService)); diff --git a/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts b/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts index de511db52f3..a3e171b9d40 100644 --- a/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts +++ b/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts @@ -68,6 +68,9 @@ class TestCommentController implements ICommentController { getNotebookComments(resource: URI, token: CancellationToken): Promise { throw new Error('Method not implemented.'); } + setActiveCommentAndThread(commentInfo: { thread: CommentThread; comment: Comment } | undefined): Promise { + throw new Error('Method not implemented.'); + } } diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 0a1f29e9d7a..47486e60790 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -6,6 +6,7 @@ // THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY. export const allApiProposals = Object.freeze({ + activeComment: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.activeComment.d.ts', aiRelatedInformation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiRelatedInformation.d.ts', 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', diff --git a/src/vscode-dts/vscode.proposed.activeComment.d.ts b/src/vscode-dts/vscode.proposed.activeComment.d.ts new file mode 100644 index 00000000000..f3e023c5bc5 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.activeComment.d.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. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + // @alexr00 https://github.com/microsoft/vscode/issues/204484 + + export interface CommentController { + /** + * The currently active comment or `undefined`. The active comment is the one + * that currently has focus or, when none has focus, undefined. + */ + // readonly activeComment: Comment | undefined; + + /** + * The currently active comment thread or `undefined`. The active comment thread is the one + * that currently has focus or, when none has focus, undefined. + */ + readonly activeThread: CommentThread | undefined; + } +} From 64cd658cac536066feaa473aec53bf8d03a3d0be Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 7 Feb 2024 00:05:51 -0800 Subject: [PATCH 0957/1897] Disable some share context menus in editor playground (#204566) --- extensions/github/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/github/package.json b/extensions/github/package.json index 1e5e886cb42..5596a2c0557 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -98,14 +98,14 @@ "editor/context/share": [ { "command": "github.copyVscodeDevLink", - "when": "github.hasGitHubRepo && resourceScheme != untitled && remoteName != 'codespaces'", + "when": "github.hasGitHubRepo && resourceScheme != untitled && !isInEmbeddedEditor && remoteName != 'codespaces'", "group": "0_vscode@0" } ], "explorer/context/share": [ { "command": "github.copyVscodeDevLinkWithoutRange", - "when": "github.hasGitHubRepo && resourceScheme != untitled && remoteName != 'codespaces'", + "when": "github.hasGitHubRepo && resourceScheme != untitled && !isInEmbeddedEditor && remoteName != 'codespaces'", "group": "0_vscode@0" } ], From fd2db103459c82eb77d36bb968d3b98137cda5b4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 7 Feb 2024 09:06:16 +0100 Subject: [PATCH 0958/1897] "workbench.action.compareEditor.swapSides" may create Untitled files unnecessarily (fix #204560) (#204565) --- .../browser/parts/editor/editorCommands.ts | 4 ++-- .../common/editor/diffEditorInput.ts | 6 ++--- src/vs/workbench/common/editor/editorInput.ts | 24 ++++++++++++++----- .../common/editor/sideBySideEditorInput.ts | 4 ++-- .../files/browser/editors/fileEditorInput.ts | 4 ++-- .../common/untitledTextEditorInput.ts | 8 +++---- .../test/browser/untitledTextEditor.test.ts | 4 ++++ 7 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 895db330ab8..68cd3a6b328 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -475,7 +475,7 @@ function registerDiffEditorCommands(): void { return; } - const untypedDiffInput = diffInput.toUntyped({ preserveViewState: activeGroup.id }); + const untypedDiffInput = diffInput.toUntyped({ preserveViewState: activeGroup.id, preserveResource: true }); if (!untypedDiffInput) { return; } @@ -484,7 +484,7 @@ function registerDiffEditorCommands(): void { // sure to first open the modified side if it is not // yet opened. This ensures that the swapping is not // bringing up a confirmation dialog to save. - if (diffInput.modified.isModified() && !editorService.isOpened({ resource: diffInput.modified.resource, typeId: diffInput.modified.typeId, editorId: diffInput.modified.editorId })) { + if (diffInput.modified.isModified() && editorService.findEditors({ resource: diffInput.modified.resource, typeId: diffInput.modified.typeId, editorId: diffInput.modified.editorId }).length === 0) { await editorService.openEditor({ ...untypedDiffInput.modified, options: { diff --git a/src/vs/workbench/common/editor/diffEditorInput.ts b/src/vs/workbench/common/editor/diffEditorInput.ts index 1680cdd3d8c..1d6e6118751 100644 --- a/src/vs/workbench/common/editor/diffEditorInput.ts +++ b/src/vs/workbench/common/editor/diffEditorInput.ts @@ -5,9 +5,9 @@ import { localize } from 'vs/nls'; import { AbstractSideBySideEditorInputSerializer, SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; -import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { EditorInput, IUntypedEditorOptions } from 'vs/workbench/common/editor/editorInput'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; -import { TEXT_DIFF_EDITOR_ID, BINARY_DIFF_EDITOR_ID, Verbosity, IEditorDescriptor, IEditorPane, GroupIdentifier, IResourceDiffEditorInput, IUntypedEditorInput, isResourceDiffEditorInput, IDiffEditorInput, IResourceSideBySideEditorInput, EditorInputCapabilities } from 'vs/workbench/common/editor'; +import { TEXT_DIFF_EDITOR_ID, BINARY_DIFF_EDITOR_ID, Verbosity, IEditorDescriptor, IEditorPane, IResourceDiffEditorInput, IUntypedEditorInput, isResourceDiffEditorInput, IDiffEditorInput, IResourceSideBySideEditorInput, EditorInputCapabilities } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { DiffEditorModel } from 'vs/workbench/common/editor/diffEditorModel'; import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorModel'; @@ -210,7 +210,7 @@ export class DiffEditorInput extends SideBySideEditorInput implements IDiffEdito return new DiffEditorModel(isResolvedEditorModel(originalEditorModel) ? originalEditorModel : undefined, isResolvedEditorModel(modifiedEditorModel) ? modifiedEditorModel : undefined); } - override toUntyped(options?: { preserveViewState: GroupIdentifier }): (IResourceDiffEditorInput & IResourceSideBySideEditorInput) | undefined { + override toUntyped(options?: IUntypedEditorOptions): (IResourceDiffEditorInput & IResourceSideBySideEditorInput) | undefined { const untyped = super.toUntyped(options); if (untyped) { return { diff --git a/src/vs/workbench/common/editor/editorInput.ts b/src/vs/workbench/common/editor/editorInput.ts index 3aa427413f9..9bce607e387 100644 --- a/src/vs/workbench/common/editor/editorInput.ts +++ b/src/vs/workbench/common/editor/editorInput.ts @@ -39,6 +39,23 @@ export interface IEditorCloseHandler { confirm(editors: ReadonlyArray): Promise; } +export interface IUntypedEditorOptions { + + /** + * Implementations should try to preserve as much + * view state as possible from the typed input based + * on the group the editor is opened. + */ + readonly preserveViewState?: GroupIdentifier; + + /** + * Implementations should preserve the original + * resource of the typed input and not alter + * it. + */ + readonly preserveResource?: boolean; +} + /** * Editor inputs are lightweight objects that can be passed to the workbench API to open inside the editor part. * Each editor input is mapped to an editor that is capable of opening it through the Platform facade. @@ -310,13 +327,8 @@ export abstract class EditorInput extends AbstractEditorInput { * editor input into a form that it can be restored. * * May return `undefined` if an untyped representation is not supported. - * - * @param options additional configuration for the expected return type. - * When `preserveViewState` is provided, implementations should try to - * preserve as much view state as possible from the typed input based on - * the group the editor is opened. */ - toUntyped(options?: { preserveViewState: GroupIdentifier }): IUntypedEditorInput | undefined { + toUntyped(options?: IUntypedEditorOptions): IUntypedEditorInput | undefined { return undefined; } diff --git a/src/vs/workbench/common/editor/sideBySideEditorInput.ts b/src/vs/workbench/common/editor/sideBySideEditorInput.ts index eab54171e89..88585cb807c 100644 --- a/src/vs/workbench/common/editor/sideBySideEditorInput.ts +++ b/src/vs/workbench/common/editor/sideBySideEditorInput.ts @@ -10,7 +10,7 @@ import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorInputCapabilities, GroupIdentifier, ISaveOptions, IRevertOptions, EditorExtensions, IEditorFactoryRegistry, IEditorSerializer, ISideBySideEditorInput, IUntypedEditorInput, isResourceSideBySideEditorInput, isDiffEditorInput, isResourceDiffEditorInput, IResourceSideBySideEditorInput, findViewStateForEditor, IMoveResult, isEditorInput, isResourceEditorInput, Verbosity, isResourceMergeEditorInput, isResourceDiffListEditorInput } from 'vs/workbench/common/editor'; -import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { EditorInput, IUntypedEditorOptions } from 'vs/workbench/common/editor/editorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; /** @@ -271,7 +271,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi return this.primary.isReadonly(); } - override toUntyped(options?: { preserveViewState: GroupIdentifier }): IResourceSideBySideEditorInput | undefined { + override toUntyped(options?: IUntypedEditorOptions): IResourceSideBySideEditorInput | undefined { const primaryResourceEditorInput = this.primary.toUntyped(options); const secondaryResourceEditorInput = this.secondary.toUntyped(options); diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts index 51684e02b56..506bf005000 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { IFileEditorInput, Verbosity, GroupIdentifier, IMoveResult, EditorInputCapabilities, IEditorDescriptor, IEditorPane, IUntypedEditorInput, DEFAULT_EDITOR_ASSOCIATION, IUntypedFileEditorInput, findViewStateForEditor, isResourceEditorInput, IFileEditorInputOptions } from 'vs/workbench/common/editor'; -import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { EditorInput, IUntypedEditorOptions } from 'vs/workbench/common/editor/editorInput'; import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; @@ -418,7 +418,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements }; } - override toUntyped(options?: { preserveViewState: GroupIdentifier }): ITextResourceEditorInput { + override toUntyped(options?: IUntypedEditorOptions): ITextResourceEditorInput { const untypedInput: IUntypedFileEditorInput = { resource: this.preferredResource, forceFile: true, diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts index 1d67b04088d..369a6994ed5 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { DEFAULT_EDITOR_ASSOCIATION, findViewStateForEditor, GroupIdentifier, isUntitledResourceEditorInput, IUntitledTextResourceEditorInput, IUntypedEditorInput, Verbosity } from 'vs/workbench/common/editor'; -import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { DEFAULT_EDITOR_ASSOCIATION, findViewStateForEditor, isUntitledResourceEditorInput, IUntitledTextResourceEditorInput, IUntypedEditorInput, Verbosity } from 'vs/workbench/common/editor'; +import { EditorInput, IUntypedEditorOptions } from 'vs/workbench/common/editor/editorInput'; import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { EncodingMode, IEncodingSupport, ILanguageSupport, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -165,7 +165,7 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp return this.model; } - override toUntyped(options?: { preserveViewState: GroupIdentifier }): IUntitledTextResourceEditorInput { + override toUntyped(options?: IUntypedEditorOptions): IUntitledTextResourceEditorInput { const untypedInput: IUntitledTextResourceEditorInput & { resource: URI | undefined; options: ITextEditorOptions } = { resource: this.model.hasAssociatedFilePath ? toLocalResource(this.model.resource, this.environmentService.remoteAuthority, this.pathService.defaultUriScheme) : this.resource, forceUntitled: true, @@ -180,7 +180,7 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp untypedInput.contents = this.model.isModified() ? this.model.textEditorModel?.getValue() : undefined; untypedInput.options.viewState = findViewStateForEditor(this, options.preserveViewState, this.editorService); - if (typeof untypedInput.contents === 'string' && !this.model.hasAssociatedFilePath) { + if (typeof untypedInput.contents === 'string' && !this.model.hasAssociatedFilePath && !options.preserveResource) { // Given how generic untitled resources in the system are, we // need to be careful not to set our resource into the untyped // editor if we want to transport contents too, because of diff --git a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts index 133ee3f3298..9ccf8fa049a 100644 --- a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts @@ -111,6 +111,10 @@ suite('Untitled text editors', () => { assert.strictEqual(dirtyUntypedInput.contents, 'foo bar'); assert.strictEqual(dirtyUntypedInput.resource, undefined); + const dirtyUntypedInputWithResource = input2.toUntyped({ preserveViewState: 0, preserveResource: true }); + assert.strictEqual(dirtyUntypedInputWithResource.contents, 'foo bar'); + assert.strictEqual(dirtyUntypedInputWithResource?.resource?.toString(), input2.resource.toString()); + const dirtyUntypedInputWithoutContent = input2.toUntyped(); assert.strictEqual(dirtyUntypedInputWithoutContent.resource?.toString(), input2.resource.toString()); assert.strictEqual(dirtyUntypedInputWithoutContent.contents, undefined); From 23c84573fbfa9858b67721dd619e4de3ef0b15de Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 7 Feb 2024 10:46:47 +0100 Subject: [PATCH 0959/1897] List - ensure DOM order in lists (#204574) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: ensure DOM order in lists Co-authored-by: João Moreno --- src/vs/base/browser/ui/list/listView.ts | 64 ++++++++----------------- 1 file changed, 20 insertions(+), 44 deletions(-) diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 9edc5aa8c90..546f0bdb1f4 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -39,6 +39,7 @@ interface IItem { dropTarget: boolean; dragStartDisposable: IDisposable; checkedDisposable: IDisposable; + stale: boolean; } const StaticDND = { @@ -610,6 +611,7 @@ export class ListView implements IListView { } item.row = null; + item.stale = true; } const previousRestRange: IRange = { start: start + deleteCount, end: this.items.length }; @@ -628,7 +630,8 @@ export class ListView implements IListView { uri: undefined, dropTarget: false, dragStartDisposable: Disposable.None, - checkedDisposable: Disposable.None + checkedDisposable: Disposable.None, + stale: false })); let deleted: IItem[]; @@ -663,15 +666,14 @@ export class ListView implements IListView { const unrenderedRestRanges = previousUnrenderedRestRanges.map(r => shift(r, delta)); const elementsRange = { start, end: start + elements.length }; - const insertRanges = [elementsRange, ...unrenderedRestRanges].map(r => Range.intersect(renderRange, r)); - const beforeElement = this.getNextToLastElement(insertRanges); + const insertRanges = [elementsRange, ...unrenderedRestRanges].map(r => Range.intersect(renderRange, r)).reverse(); for (const range of insertRanges) { - for (let i = range.start; i < range.end; i++) { + for (let i = range.end - 1; i >= range.start; i--) { const item = this.items[i]; const rows = rowsToDispose.get(item.templateId); const row = rows?.pop(); - this.insertItemInDOM(i, beforeElement, row); + this.insertItemInDOM(i, row); } } @@ -852,9 +854,8 @@ export class ListView implements IListView { protected render(previousRenderRange: IRange, renderTop: number, renderHeight: number, renderLeft: number | undefined, scrollWidth: number | undefined, updateItemsInDOM: boolean = false): void { const renderRange = this.getRenderRange(renderTop, renderHeight); - const rangesToInsert = Range.relativeComplement(renderRange, previousRenderRange); + const rangesToInsert = Range.relativeComplement(renderRange, previousRenderRange).reverse(); const rangesToRemove = Range.relativeComplement(previousRenderRange, renderRange); - const beforeElement = this.getNextToLastElement(rangesToInsert); if (updateItemsInDOM) { const rangesToUpdate = Range.intersect(previousRenderRange, renderRange); @@ -872,8 +873,8 @@ export class ListView implements IListView { } for (const range of rangesToInsert) { - for (let i = range.start; i < range.end; i++) { - this.insertItemInDOM(i, beforeElement); + for (let i = range.end - 1; i >= range.start; i--) { + this.insertItemInDOM(i); } } }); @@ -894,17 +895,17 @@ export class ListView implements IListView { // DOM operations - private insertItemInDOM(index: number, beforeElement: HTMLElement | null, row?: IRow): void { + private insertItemInDOM(index: number, row?: IRow): void { const item = this.items[index]; - let isStale = false; if (!item.row) { if (row) { item.row = row; + item.stale = true; } else { const result = this.cache.alloc(item.templateId); item.row = result.row; - isStale = result.isReusingConnectedDomNode; + item.stale ||= result.isReusingConnectedDomNode; } } @@ -921,12 +922,10 @@ export class ListView implements IListView { item.checkedDisposable = checked.onDidChange(update); } - if (isStale || !item.row.domNode.parentElement) { - if (beforeElement) { - this.rowsContainer.insertBefore(item.row.domNode, beforeElement); - } else { - this.rowsContainer.appendChild(item.row.domNode); - } + if (item.stale || !item.row.domNode.parentElement) { + const referenceNode = this.items.at(index + 1)?.row?.domNode ?? null; + this.rowsContainer.insertBefore(item.row.domNode, referenceNode); + item.stale = false; } this.updateItemInDOM(item, index); @@ -1461,14 +1460,11 @@ export class ListView implements IListView { } } - const renderRanges = Range.relativeComplement(renderRange, previousRenderRange); + const renderRanges = Range.relativeComplement(renderRange, previousRenderRange).reverse(); for (const range of renderRanges) { - for (let i = range.start; i < range.end; i++) { - const afterIndex = i + 1; - const beforeRow = afterIndex < this.items.length ? this.items[afterIndex].row : null; - const beforeElement = beforeRow ? beforeRow.domNode : null; - this.insertItemInDOM(i, beforeElement); + for (let i = range.end - 1; i >= range.start; i--) { + this.insertItemInDOM(i); } } @@ -1548,26 +1544,6 @@ export class ListView implements IListView { return item.size - size; } - private getNextToLastElement(ranges: IRange[]): HTMLElement | null { - const lastRange = ranges[ranges.length - 1]; - - if (!lastRange) { - return null; - } - - const nextToLastItem = this.items[lastRange.end]; - - if (!nextToLastItem) { - return null; - } - - if (!nextToLastItem.row) { - return null; - } - - return nextToLastItem.row.domNode; - } - getElementDomId(index: number): string { return `${this.domId}_${index}`; } From dd7f36ffa85611b42eb56c810b1b37f5f77fd8b8 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 7 Feb 2024 11:19:43 +0100 Subject: [PATCH 0960/1897] refine `ChatResponseMarkdownPart`, properly deprecate `ChatAgentExtendedResponseStream`, refine `InteractiveEditorRequest` (#204579) --- src/vs/workbench/api/common/extHostTypes.ts | 4 ++-- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 3 ++- src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts | 7 ++++++- src/vscode-dts/vscode.proposed.interactive.d.ts | 6 +++++- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 1deb244c2e4..266793aa00d 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4212,9 +4212,9 @@ export class ChatResponseTextPart { } export class ChatResponseMarkdownPart { - value: string | vscode.MarkdownString; + value: vscode.MarkdownString; constructor(value: string | vscode.MarkdownString) { - this.value = value; + this.value = typeof value === 'string' ? new MarkdownString(value) : value; } } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index b7cda17484a..f8f0c2caed3 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -67,6 +67,7 @@ declare module 'vscode' { // TODO@API // add CATCH-all signature [name:string]: string|boolean|number instead of `T extends...` + // readonly metadata: { readonly [key: string]: any }; } /** @@ -313,7 +314,7 @@ declare module 'vscode' { } export class ChatResponseMarkdownPart { - value: string | MarkdownString; + value: MarkdownString; constructor(value: string | MarkdownString); } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index c56dfc45c89..5d0a7b1afdd 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -43,7 +43,12 @@ declare module 'vscode' { | ChatAgentMarkdownContent | ChatAgentDetectedAgent; - export type ChatAgentExtendedResponseStream = ChatAgentResponseStream & Progress; + export type ChatAgentExtendedResponseStream = ChatAgentResponseStream & { + /** + * @deprecated + */ + report(value: ChatAgentExtendedProgress): void; + }; export interface ChatAgent2 { /** diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index fd8dc51d014..f331fd88343 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -32,8 +32,12 @@ declare module 'vscode' { selection: Selection; wholeRange: Range; attempt: number; + + /** + * @deprecated, use previewDocument + */ live: boolean; - previewDocument: TextDocument | undefined; + previewDocument: TextDocument; withIntentDetection: boolean; } From ef78ec4e6f6715c444bd54ff6bab576a43c8b257 Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 7 Feb 2024 19:54:49 +0900 Subject: [PATCH 0961/1897] fix: avoid DPR factor in mouse wheel event for newer versions (#204582) --- src/vs/base/browser/mouseEvent.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/mouseEvent.ts b/src/vs/base/browser/mouseEvent.ts index 6e414822d73..51de0152e26 100644 --- a/src/vs/base/browser/mouseEvent.ts +++ b/src/vs/base/browser/mouseEvent.ts @@ -136,6 +136,15 @@ export class StandardWheelEvent { this.deltaY = deltaY; this.deltaX = deltaX; + let shouldFactorDPR: boolean = false; + if (browser.isChrome) { + // Chrome version >= 123 contains the fix to factor devicePixelRatio into the wheel event. + // See https://chromium.googlesource.com/chromium/src.git/+/be51b448441ff0c9d1f17e0f25c4bf1ab3f11f61 + const chromeVersionMatch = navigator.userAgent.match(/Chrome\/(\d+)/); + const chromeMajorVersion = chromeVersionMatch ? parseInt(chromeVersionMatch[1]) : 123; + shouldFactorDPR = chromeMajorVersion <= 122; + } + if (e) { // Old (deprecated) wheel events const e1 = e; @@ -144,7 +153,7 @@ export class StandardWheelEvent { // vertical delta scroll if (typeof e1.wheelDeltaY !== 'undefined') { - if (browser.isChrome) { + if (shouldFactorDPR) { // Refs https://github.com/microsoft/vscode/issues/146403#issuecomment-1854538928 this.deltaY = e1.wheelDeltaY / (120 * devicePixelRatio); } else { @@ -173,7 +182,7 @@ export class StandardWheelEvent { if (typeof e1.wheelDeltaX !== 'undefined') { if (browser.isSafari && platform.isWindows) { this.deltaX = - (e1.wheelDeltaX / 120); - } else if (browser.isChrome) { + } else if (shouldFactorDPR) { // Refs https://github.com/microsoft/vscode/issues/146403#issuecomment-1854538928 this.deltaX = e1.wheelDeltaX / (120 * devicePixelRatio); } else { @@ -200,7 +209,7 @@ export class StandardWheelEvent { // Assume a vertical scroll if nothing else worked if (this.deltaY === 0 && this.deltaX === 0 && e.wheelDelta) { - if (browser.isChrome) { + if (shouldFactorDPR) { // Refs https://github.com/microsoft/vscode/issues/146403#issuecomment-1854538928 this.deltaY = e.wheelDelta / (120 * devicePixelRatio); } else { From 926d4e5fd1d6c0ff1d2a9002e01e3b4b6946d7a7 Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 7 Feb 2024 20:08:06 +0900 Subject: [PATCH 0962/1897] chore: update electron@27.3.1 (#204580) * chore: update electron@27.3.1 * chore: bump distro --- .yarnrc | 4 +- build/checksums/electron.txt | 150 +++++++++++++++++------------------ cgmanifest.json | 4 +- package.json | 4 +- yarn.lock | 8 +- 5 files changed, 85 insertions(+), 85 deletions(-) diff --git a/.yarnrc b/.yarnrc index c95a4be2afa..e7aba7e6b76 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,5 +1,5 @@ disturl "https://electronjs.org/headers" -target "27.2.3" -ms_build_id "26495564" +target "27.3.1" +ms_build_id "26731440" runtime "electron" build_from_source "true" diff --git a/build/checksums/electron.txt b/build/checksums/electron.txt index bcd4bfe0da6..2eaf08f2c00 100644 --- a/build/checksums/electron.txt +++ b/build/checksums/electron.txt @@ -1,75 +1,75 @@ -5e12800023944ba203820eac76382b57ccd5dad371f70650889bd77decbc372b *chromedriver-v27.2.3-darwin-arm64.zip -d271649b18bbbe7aa2c8fe8633bbb335611d018c95ddb6b4f54e6cd037163219 *chromedriver-v27.2.3-darwin-x64.zip -1c94c33c01cb23b1e9bf6ab34093abd1a8b8cfa8b6021a70c8142022b6d8f0a0 *chromedriver-v27.2.3-linux-arm64.zip -4491b78e7fc45f27388abc022841aeecb48fd2907f7aed2be42caf3db3149284 *chromedriver-v27.2.3-linux-armv7l.zip -1ec4ddb99209017df0f3252799e87bd04f2ae6d14b1ed3dc638e8bfbc512441b *chromedriver-v27.2.3-linux-x64.zip -a9384b769f0b47eab221a0576c6eb5b6f8a390943621dac0b4b03c6c11b4db9b *chromedriver-v27.2.3-mas-arm64.zip -2dc6bb386adb60426ed9902cae118073d289bea9d2ad739162aa3534464637b0 *chromedriver-v27.2.3-mas-x64.zip -23587604cdc9aa9b434879a8615f96d023cb5f7bc1bbb1d672b232bda21f3c48 *chromedriver-v27.2.3-win32-arm64.zip -aceae2b417ae6a83bc6ddd3a762bd99e48455765b9db4d65fcfbc1fe17182bae *chromedriver-v27.2.3-win32-ia32.zip -a3797c5d58fb73b22b8d06b58250c57be94673f2c7e6862095a0bd768feee278 *chromedriver-v27.2.3-win32-x64.zip -39ec5429329af2990470a0c46ba865851863610ff46b47c4a25f7055670f1604 *electron-api.json -6cd6e53a0fdccbf3e89da8634d6fda7f04e0e521ffd3f494dfb4e9f7087c5ff2 *electron-v27.2.3-darwin-arm64-dsym-snapshot.zip -cc13dbdb4f319cbc31c8ef76560a92b695d19e1f88709dda5f2f26d87fc04469 *electron-v27.2.3-darwin-arm64-dsym.zip -949cfa456931a3f9190504a7b135a726eb75d223fd769d38c5c0258ab2328db0 *electron-v27.2.3-darwin-arm64-symbols.zip -20d6d486f6e0e428182dbbafd293f3007fc91760d37bbc3559e813d55ee15d85 *electron-v27.2.3-darwin-arm64.zip -b1b4e7d7212336fe28e8a461b920e7ca0a358a313000592d160adbf335c7e65b *electron-v27.2.3-darwin-x64-dsym-snapshot.zip -55f88e9491c237675cb1f7e3d055d404db70e4682e906ede79ff433d8794679f *electron-v27.2.3-darwin-x64-dsym.zip -75262139cb49ce5070501f6e74dade18bf9f0eb3b968d848765a937560f6338b *electron-v27.2.3-darwin-x64-symbols.zip -602b89308fb114631d90bda4bbef611aa571c57588fbb1bfcec781e22d584c22 *electron-v27.2.3-darwin-x64.zip -94989909484abff147b4334a100ace00571d810da94b717e54c82cb76d7acf11 *electron-v27.2.3-linux-arm64-debug.zip -9e57241fa5df5e09c70a99aff5099350f5b7126895f7a4ab8ca401352b4bd99a *electron-v27.2.3-linux-arm64-symbols.zip -86c98c25f1a7e02f3c8e3fc837e8785e1cd88518c4e5f005c59e039bb6e79054 *electron-v27.2.3-linux-arm64.zip -94989909484abff147b4334a100ace00571d810da94b717e54c82cb76d7acf11 *electron-v27.2.3-linux-armv7l-debug.zip -43a89997cb8acd88f37d69649ef4e8f6bbb9ffce187004e09604c410e396e788 *electron-v27.2.3-linux-armv7l-symbols.zip -7a581252c59514696f920f21af0e97f997e01c9df44d6788c457825bf398c0ea *electron-v27.2.3-linux-armv7l.zip -8a27e845f4405a44fb16297362782531aff03219a002a3e23b3b439fd610b6dd *electron-v27.2.3-linux-x64-debug.zip -b8ddc3f92f23d5351c096287b76ff106c53560bf44cdbc3ba33ed2bd9609bb63 *electron-v27.2.3-linux-x64-symbols.zip -3347f4ff220fa63b286d91fec129d7f8d636ce883dba3c53c50b7e5834c940e1 *electron-v27.2.3-linux-x64.zip -b474af6f1681e204e09906f92d26e1474bc84b35510e4454a3f6bbaaf4df387b *electron-v27.2.3-mas-arm64-dsym-snapshot.zip -3c829ad8fafd12a3ee5c633bee0ae12137a9d780b7bc003785fd1badfafe3d97 *electron-v27.2.3-mas-arm64-dsym.zip -1a0ff4e32b980aabbcc21b533b17c16dbe1eb431408e9cc61dccddbcb673c695 *electron-v27.2.3-mas-arm64-symbols.zip -dd59ca093811646c953c71aaa12bf30c773ed7a77ea1344029d87af40281a114 *electron-v27.2.3-mas-arm64.zip -70b04af2ef42f99becdf0de53efaa4cf419151ceeed1b2ed77759c1f7c1332cc *electron-v27.2.3-mas-x64-dsym-snapshot.zip -89c379f6648a1864250adb420e09bcd1ac7422958a4daf6c830c0b15cfb2babc *electron-v27.2.3-mas-x64-dsym.zip -b0215c7b1496352b92547543c2a107fcf4c70023c10043a5a6777bdf82b22287 *electron-v27.2.3-mas-x64-symbols.zip -781f618ec0b16fdee5c0a76430b617f66152aa2e02ccf953fae1c81e57fab950 *electron-v27.2.3-mas-x64.zip -d7deac3628b145c006ab935d90694a9b009fbfdce2e73f4ccc861ce739452af7 *electron-v27.2.3-win32-arm64-pdb.zip -4b767cd84c8fccdd0350fa5b82a3424779994bfcd4e3c4f864f003ea3ec4fd33 *electron-v27.2.3-win32-arm64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.2.3-win32-arm64-toolchain-profile.zip -407f86a40574152ee89e4f01878e43d389876bbf23058352b77d5d3363ab98d3 *electron-v27.2.3-win32-arm64.zip -b5de17188f154b5041978b90f0471089a94d552d2c96e1f953154484f65bfe31 *electron-v27.2.3-win32-ia32-pdb.zip -f7ac401c08cdfe3d1f60d3e61c1de995f20a476f4762e3a21057a5132ff42735 *electron-v27.2.3-win32-ia32-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.2.3-win32-ia32-toolchain-profile.zip -f637d01467cdb808530391508ac268b397675eaa620b71d9d2f0d71d25cafe53 *electron-v27.2.3-win32-ia32.zip -a7463a6993a6d938c110dfa61c4af7c751081aac581f81d803efa9af063f4530 *electron-v27.2.3-win32-x64-pdb.zip -32fd37c76cfe8ac40738f0c9775f991971ec6a787bd81216ae2822ade8695e7a *electron-v27.2.3-win32-x64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.2.3-win32-x64-toolchain-profile.zip -36b75f90dde9013e13afd47576192fba1ee1ddf6790cca285e78fd58828d5961 *electron-v27.2.3-win32-x64.zip -57900d1631e5f15f976d8aac2ce11dab56127d83358bbbc09ae8db4aa5698516 *electron.d.ts -a6863fa69d352b46e28966dc0f7ca2af08d91cc5e894421d9c8b21d9a552cf1c *ffmpeg-v27.2.3-darwin-arm64.zip -e35fa1f8f5884dc3dca29b6e7b03f30509dc20012abffcdac027ddda4d2e1602 *ffmpeg-v27.2.3-darwin-x64.zip -be517ba93c5b5f36d2e55af148213f4c2fc43177563825601bee6f88dd4f7b05 *ffmpeg-v27.2.3-linux-arm64.zip -926d0da25ffcea3d05a6cbcae15e5d7729d93bc43394ae4439747669d2210e1d *ffmpeg-v27.2.3-linux-armv7l.zip -6f9c0ef52af14828ad547a80b17f8c63cac51a18b8d5769a2f33e4fa6cccfc7e *ffmpeg-v27.2.3-linux-x64.zip -e2e099bba965b13050d65980705aca2fb26698c9b0bcf1e66725896e6a3702cf *ffmpeg-v27.2.3-mas-arm64.zip -6104f719783d7d910a520dde756555ee77a284e29c3b14d8c2e9c53d409e8efa *ffmpeg-v27.2.3-mas-x64.zip -c7c574fe9afc3a42f207b805543b56b36e3f4fe65b48a5c6a5a4dc03353a6d41 *ffmpeg-v27.2.3-win32-arm64.zip -165bb1e9b42cc8f1bc233f26e823e1f53ae7f1e278fac1ec864273ceb3bfe5eb *ffmpeg-v27.2.3-win32-ia32.zip -1ccc98816757a19f01c838bd67c73fa53e11495bf075fbc101754aeb38a09d8d *ffmpeg-v27.2.3-win32-x64.zip -27490d54e045651a7c2c3d220bb23cf76a98fd9f7566737f5b06e75809e7959e *hunspell_dictionaries.zip -b49f4a784414819807de510a3d8ace198118997c11693cd693807c31260ac47d *libcxx-objects-v27.2.3-linux-arm64.zip -c62630afb544f42749bd2b9f7d0b54d8d44e109fb115f88822a5d7102f61088c *libcxx-objects-v27.2.3-linux-armv7l.zip -f4f8c72bc6b545959a18ba2caf4db05f05ce8c70b234509f95d2826dd54273e2 *libcxx-objects-v27.2.3-linux-x64.zip -f26fd6d3216afb9e9c16be9f88d27d82bb13500eac4957ae2d83c90682a201fc *libcxx_headers.zip -ba6952490ded27c344150527543dee9693e5906ca419a6975a38cb61da6be794 *libcxxabi_headers.zip -fd1e8aed99530f94bec125a60f5b6ea34f2586deb8034f5f437e95f58c951340 *mksnapshot-v27.2.3-darwin-arm64.zip -8508fbbd71c1654b79a02336cdd042101f9596ebf4e1666d2d55ab8d3f363c8f *mksnapshot-v27.2.3-darwin-x64.zip -9c0ecb392aab08f59cddd54b0f3116f3a19442549ec3d3523208ac0807e3d504 *mksnapshot-v27.2.3-linux-arm64-x64.zip -ccc26dd636dbfc9c99c8e76fa55535dc66154c19a7de18d2f462ee4e1269ae95 *mksnapshot-v27.2.3-linux-armv7l-x64.zip -d7ceffae1aeb4022660fd2354bb4b8e3f62fd4531c1f77989039a3e47be17303 *mksnapshot-v27.2.3-linux-x64.zip -efc12f63c71b290c8f07b337098bcd0f54aa9dfa92a1ed16603db5e9f232d100 *mksnapshot-v27.2.3-mas-arm64.zip -65f9fc4c3a639d4b5279fd80b57be79099815a04bcdc504631170a15f8a7efdb *mksnapshot-v27.2.3-mas-x64.zip -b46cc5f28cfb49a2a2b5f4c66cb8d0cfc530877201d4df85b7568c1ad8d5da1c *mksnapshot-v27.2.3-win32-arm64-x64.zip -39878ef70de05b000b86c2e593a62f9d76703d46d1bd5bfdb525e2826ea85d9f *mksnapshot-v27.2.3-win32-ia32.zip -c19a90b8149d0ba9a623d1134c058f514234975dce32ca37e7e97bdf474eb9fd *mksnapshot-v27.2.3-win32-x64.zip +27fcfc6982b3b1b56cbb611c5060bdbee35fe64aab6c8a24e19ac133d1bbaf05 *chromedriver-v27.3.1-darwin-arm64.zip +56ddf9df850b3612e8aa61204e6334400a89502bb8acabcc9873218317831d0d *chromedriver-v27.3.1-darwin-x64.zip +c070bc178589426112c4ff740712ad3ce5da672f717a93ef74c3d53f23173e08 *chromedriver-v27.3.1-linux-arm64.zip +84f072cca93660bc5eb2f261d71c6db207e7a979ecede92c0f0c5d64cbdfc907 *chromedriver-v27.3.1-linux-armv7l.zip +a722b30273d94d370434604949c4bccb38f3c725e14429d9f6dc9f33455414bf *chromedriver-v27.3.1-linux-x64.zip +8ca0fe4e3daeef42002213c2bf957539c77010e16a5772a0a79e167cf803bb85 *chromedriver-v27.3.1-mas-arm64.zip +52bba4265e0e18f6958de225b9726b262efafebdfee846749f4d83c400046a74 *chromedriver-v27.3.1-mas-x64.zip +b1b9dfd2d5c962b87827b0f4f72ab955cdee14358e9914476b30de603b8834ce *chromedriver-v27.3.1-win32-arm64.zip +a74ae6f90b8d0ae0e29a53a254d25a9ca09eaadeb0bc546fea0aff838345a494 *chromedriver-v27.3.1-win32-ia32.zip +bf37f6947e2e0dc19a99320a39aa23cf644dcf075d7d9ba2394364681aaafb60 *chromedriver-v27.3.1-win32-x64.zip +b25fb8ae02721a89614aeb4db621a797fca385340c8b6be64724785a2e734d23 *electron-api.json +e8394d2c23c878b4bd774f85a1f468547f4c9e4669e14b19850f685ca7ab3c76 *electron-v27.3.1-darwin-arm64-dsym-snapshot.zip +f346aeb98ef1c059b6fc035f837ba997e98d2a2b9e06076ec882df877ae1d6be *electron-v27.3.1-darwin-arm64-dsym.zip +a66a6656acc7217a3f8627a384d9ca5ba3d3cb1b7e144e25bb82344eb211612b *electron-v27.3.1-darwin-arm64-symbols.zip +0c8b1c3bf1fcb52be3c0517603fe69329050a2efb1c61ab5e5988a54aa693ebd *electron-v27.3.1-darwin-arm64.zip +92086b977a81d5e4787bdcff9e4b684807edbf71093cdc54c1d2169b96c0165d *electron-v27.3.1-darwin-x64-dsym-snapshot.zip +57c0d9dd591d2abfa56865323745e411351f639049eefa4506116cbe421baca5 *electron-v27.3.1-darwin-x64-dsym.zip +069c76522f1c4316065932a90067f02b7f6e8f144133c2ba09da8d97a06c4c0f *electron-v27.3.1-darwin-x64-symbols.zip +e2360124f0fdb3ff5e6a644d0e139a9546947ee2f69e51f3d310c3f2cbcd19ee *electron-v27.3.1-darwin-x64.zip +e6fdce1f521511b13ec4425d8a7e51077e83faa4bbfa18026598037c6688965c *electron-v27.3.1-linux-arm64-debug.zip +3304fcfbf8aa45f3a174d404174c34123b8f777259eae7fe26a9d82104068a89 *electron-v27.3.1-linux-arm64-symbols.zip +9337cfe3f9256fecc806554c9fac3be780fd20c868efe621aaca2c1455c5ff64 *electron-v27.3.1-linux-arm64.zip +e6fdce1f521511b13ec4425d8a7e51077e83faa4bbfa18026598037c6688965c *electron-v27.3.1-linux-armv7l-debug.zip +7bb138d548e621b62ece75f43b26c5e287127b720a2fcf1b24fde75178000848 *electron-v27.3.1-linux-armv7l-symbols.zip +4956df2e23ae6bdebda8a1fbbee40828ca1170ce61b8d4504f1e30ed1102052a *electron-v27.3.1-linux-armv7l.zip +b8e7cd591db6ffad32f7dac90e05404a675d4f2a5e1e375dfce7b4a5c3b0064b *electron-v27.3.1-linux-x64-debug.zip +5c692dce572cf1b8ad57a0633280ee61096256b7291701a1c3e357f48479fb7b *electron-v27.3.1-linux-x64-symbols.zip +9daf5c5dc2050b9f37a5ec6d91d586ac156824bfe9a07ca53872c1b293280ca1 *electron-v27.3.1-linux-x64.zip +959529b81b9517df24baed089fb92fd1730b4eefaddcd37c864da6b05f63ecbe *electron-v27.3.1-mas-arm64-dsym-snapshot.zip +603c4955dddd4f8211c6fab3e633242255c1bf408326bf66567bfa00bed21493 *electron-v27.3.1-mas-arm64-dsym.zip +8181e85363bcc89863c0de76b29d47b68beb4f53d79583875a74178f5e12ef1d *electron-v27.3.1-mas-arm64-symbols.zip +c577088087c137e60884d857335a6720547c14eac894884d9daa07f1662a43a5 *electron-v27.3.1-mas-arm64.zip +6ce1f58f7bfa0b1aafd1aea2bfbad83df3a86a7b922b5d57f00689c7dd573f42 *electron-v27.3.1-mas-x64-dsym-snapshot.zip +1efe461ecca71b7efd96a34187b4810969c2d2bece7d02db2ad2821acd44e130 *electron-v27.3.1-mas-x64-dsym.zip +eabfbd4ee150f60b7cf10c126343cc078a3df2b8e63938f5795766fa8d837c93 *electron-v27.3.1-mas-x64-symbols.zip +2e0f6a468d388ca47eba3ae8cd79f5f422d3a36813b46d09fc7a0ae93bcbb68c *electron-v27.3.1-mas-x64.zip +c086cee2cfe73567d35e3b373c39f562e9169dc1f234caa6d006a878ff43b2e4 *electron-v27.3.1-win32-arm64-pdb.zip +c064dd5aa63b1506cca2cc6cb2fda6478839c194f62b3b90782b3dc8246bd55c *electron-v27.3.1-win32-arm64-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.1-win32-arm64-toolchain-profile.zip +09caae1d93087cebcefec8bfc01496213e8db95d4563151d46aec3f05f31ec30 *electron-v27.3.1-win32-arm64.zip +c1dbf42222a5bfd86850fec114436e24960275547cf529e4f5ff2729ce680801 *electron-v27.3.1-win32-ia32-pdb.zip +8d3db6570266cc571823e7b2498e694e428134ecf2a11211dbd8f1b1d9ddfe02 *electron-v27.3.1-win32-ia32-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.1-win32-ia32-toolchain-profile.zip +686daa2e3cd9751d411b781af88ff84b91b23ddcf4879bfa5f4417e2e45ca4eb *electron-v27.3.1-win32-ia32.zip +7c1ec060a7ca71c3018b18c66607aad3f648257bdc055f4672341abcf275f1d8 *electron-v27.3.1-win32-x64-pdb.zip +7ab64686ed233b2aef2443d72997a36a2eb67cbdc1321a419111d573292956bc *electron-v27.3.1-win32-x64-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.1-win32-x64-toolchain-profile.zip +20d54521540d1d9fa441d1e977555dbec4958bcd76de57f23b4534077361a865 *electron-v27.3.1-win32-x64.zip +482a6452d0d2ccda8afbfc19326fe0d59118491c216938a192bfa1ad7d168493 *electron.d.ts +ac54ec270dacdd7ca2f806b3c75047875535e1722e251ec81b4f3ab9c574470b *ffmpeg-v27.3.1-darwin-arm64.zip +091699a1fe0506f54b2b990c3e85058dae0fffffb818d22b41ffc2496e155fa4 *ffmpeg-v27.3.1-darwin-x64.zip +be517ba93c5b5f36d2e55af148213f4c2fc43177563825601bee6f88dd4f7b05 *ffmpeg-v27.3.1-linux-arm64.zip +926d0da25ffcea3d05a6cbcae15e5d7729d93bc43394ae4439747669d2210e1d *ffmpeg-v27.3.1-linux-armv7l.zip +6f9c0ef52af14828ad547a80b17f8c63cac51a18b8d5769a2f33e4fa6cccfc7e *ffmpeg-v27.3.1-linux-x64.zip +e26c3c7097642012acf1178367c6815cf90ac588864e99206d89230409f67c01 *ffmpeg-v27.3.1-mas-arm64.zip +8dc1775e034c1a2c25a150690c384d3479d26f020a0ed533e1691a01a62ab0a5 *ffmpeg-v27.3.1-mas-x64.zip +94832b30166038225703810fec94b2f854fab7ca3fc7f5531219d2de5b9fa4e2 *ffmpeg-v27.3.1-win32-arm64.zip +7e5d9c0b4db4e1370219a74c4484f9d15d18c0c54501003280eb61f8dc3b84b2 *ffmpeg-v27.3.1-win32-ia32.zip +f45d1f9cf5e5e7bff93406d70b2bee58ae5b3836f1950fb47abb6520d0547aac *ffmpeg-v27.3.1-win32-x64.zip +ea4a6308be24bfc818526f1830f084961b5bd9277bbac16e5753fa4d60aad01e *hunspell_dictionaries.zip +9c05221181a2ae7d6f437ec356878ab44b71a958995f4f104667fef20a8076ef *libcxx-objects-v27.3.1-linux-arm64.zip +bef8d7930d31993196f7b0916eece6fac1d84fe9ed2b0a63fb484778949c1ee3 *libcxx-objects-v27.3.1-linux-armv7l.zip +17b1e3d3d9e5abdcedc3c22ce879becdff316c2e2a89369c6f2657e9051da6a6 *libcxx-objects-v27.3.1-linux-x64.zip +46313e591349d7a3a8390d96560d7799afad1f3774d588488a6ee80756172f7d *libcxx_headers.zip +44e6410776454067a546bf5d2525dd33baacd919481dc43708f327009f8b52bd *libcxxabi_headers.zip +870072bdd1956b404265ecfebcbe1a4fbaa2219fd61dd881c817f4f4f8130684 *mksnapshot-v27.3.1-darwin-arm64.zip +d76067c1f28fa4baebed723cc9f6374bd31d4baf0d6e7e19c36bfff6d214dd91 *mksnapshot-v27.3.1-darwin-x64.zip +e403e621c2af0a468301297c11790b370a77a0ba6f7f3fa4af731f005b6ffb96 *mksnapshot-v27.3.1-linux-arm64-x64.zip +6f7ef9ce138ff5c6d4c7df449ac717613e3001756b765f0e4e7662af1387c754 *mksnapshot-v27.3.1-linux-armv7l-x64.zip +a794b49b20677cd68b8616229180687adec3f46c425cc4c805c4cdb9c4b6ec72 *mksnapshot-v27.3.1-linux-x64.zip +6a61292fd8e3fb86c36c364ea32be2e78b8947e9abf22a8a7749bad40e75a5f5 *mksnapshot-v27.3.1-mas-arm64.zip +d3b1ae83b6244a4ce83e6eab16f8cd121c187ee21a5e37cac4253cfd4e53f6b4 *mksnapshot-v27.3.1-mas-x64.zip +6916414f51fa30cce966e49ea252a73facf24ebac86f068daa2f37930c48d58b *mksnapshot-v27.3.1-win32-arm64-x64.zip +ef37d3c0d9ef36038adef1b81ffbf302c3b819456ffea705c05c3def09710723 *mksnapshot-v27.3.1-win32-ia32.zip +95a222425bb72a8b1a42f37161eb90ff6869268f539387b47b54904df27f2f68 *mksnapshot-v27.3.1-win32-x64.zip diff --git a/cgmanifest.json b/cgmanifest.json index 1a676f2ac1e..c546a3e27e5 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -528,12 +528,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "b3ca1a1f96bae5352f420ef1f77cc9d7fd5957c6" + "commitHash": "f6af8959c36eca437f38076c46ab13e910cbfbdb" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "27.2.3" + "version": "27.3.1" }, { "component": { diff --git a/package.json b/package.json index ccc76d3e3d8..46cb4f726f9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "205c07946b7d7629f85cf332acfcfc8a76f23f6d", + "distro": "3cea8384a7a351accd0153af4199d204dc9ec202", "author": { "name": "Microsoft Corporation" }, @@ -149,7 +149,7 @@ "cssnano": "^6.0.3", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "27.2.3", + "electron": "27.3.1", "eslint": "8.36.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^46.5.0", diff --git a/yarn.lock b/yarn.lock index 310d78e80d8..1f21b0273ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3509,10 +3509,10 @@ electron-to-chromium@^1.4.648: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.648.tgz#c7b46c9010752c37bb4322739d6d2dd82354fbe4" integrity sha512-EmFMarXeqJp9cUKu/QEciEApn0S/xRcpZWuAm32U7NgoZCimjsilKXHRO9saeEW55eHZagIDg6XTUOv32w9pjg== -electron@27.2.3: - version "27.2.3" - resolved "https://registry.yarnpkg.com/electron/-/electron-27.2.3.tgz#ef8c7a33175bb9fc91ae5508c560aea719b49232" - integrity sha512-AbDL5WhoyN56Nb6Ox26eFNXo3PcoxPQGUfBMBT8WXY19uD8kI+l6XF8mI09ezMejo3JjKu4kh3zZUPcgskzIng== +electron@27.3.1: + version "27.3.1" + resolved "https://registry.yarnpkg.com/electron/-/electron-27.3.1.tgz#b64ae70410b657c911d8c7665605c02695722fd3" + integrity sha512-hDAeaTJvXHu3vCmR6l6/muPGaKRlEp6v471yyqOju9xhLKUaGOAdMN46F0V+SCABZq2nWr9DTwqHsJ0b4hUZLQ== dependencies: "@electron/get" "^2.0.0" "@types/node" "^18.11.18" From 614c5437cb9e13f1f2586bda813e961f9916a884 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 7 Feb 2024 12:40:56 +0100 Subject: [PATCH 0963/1897] let `prepareHistory` use new response part classes (#204584) --- src/vs/workbench/api/common/extHostChatAgents2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index cb1333f212b..68657eeacb8 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -220,7 +220,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { || h.result; return { request: typeConvert.ChatAgentRequest.to(h.request), - response: coalesce(h.response.map(r => typeConvert.ChatResponseProgress.toProgressContent(r))), + response: coalesce(h.response.map(r => typeConvert.ChatResponsePart.to(r))), result } satisfies vscode.ChatAgentHistoryEntry; }))); From 0679fa72d53471fd9f368676030aca020755cde3 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 7 Feb 2024 12:43:16 +0100 Subject: [PATCH 0964/1897] Git - adjust branch base computation (#204585) --- extensions/git/src/historyProvider.ts | 7 +++++++ extensions/git/src/repository.ts | 5 ----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 82c43be52fb..0063f45cad3 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -182,6 +182,13 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec private async resolveHistoryItemGroupBase(historyItemId: string): Promise { try { + // Upstream + const branch = await this.repository.getBranch(historyItemId); + if (branch.upstream) { + return branch.upstream; + } + + // Base (config -> reflog -> default) const remoteBranch = await this.repository.getBranchBase(historyItemId); if (!remoteBranch?.remote || !remoteBranch?.name || !remoteBranch?.commit || remoteBranch?.type !== RefType.RemoteHead) { return undefined; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 7fa410c615c..c71069fd3c8 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1484,11 +1484,6 @@ export class Repository implements Disposable { async getBranchBase(ref: string): Promise { const branch = await this.getBranch(ref); - const branchUpstream = await this.getUpstreamBranch(branch); - - if (branchUpstream) { - return branchUpstream; - } // Git config const mergeBaseConfigKey = `branch.${branch.name}.vscode-merge-base`; From d9a95cc50934986a6442ee385402eacffdefec1b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 7 Feb 2024 12:53:30 +0100 Subject: [PATCH 0965/1897] fix surround with naming for the command palette (#204587) https://github.com/microsoft/vscode/issues/204506 --- .../contrib/snippets/browser/commands/surroundWithSnippet.ts | 2 +- .../contrib/snippets/browser/snippetCodeActionProvider.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts index 3b4a46f65df..8486ac3dad6 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts @@ -31,7 +31,7 @@ export class SurroundWithSnippetEditorAction extends SnippetEditorAction { static readonly options = { id: 'editor.action.surroundWithSnippet', - title: localize2('label', "More...") + title: localize2('label', "Surround with Snippet...") }; constructor() { diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCodeActionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCodeActionProvider.ts index b99af9f5749..a061cca5593 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCodeActionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCodeActionProvider.ts @@ -25,7 +25,7 @@ class SurroundWithSnippetCodeActionProvider implements CodeActionProvider { private static readonly _overflowCommandCodeAction: CodeAction = { kind: CodeActionKind.SurroundWith.value, - title: SurroundWithSnippetEditorAction.options.title.value, + title: localize('more', "More..."), command: { id: SurroundWithSnippetEditorAction.options.id, title: SurroundWithSnippetEditorAction.options.title.value, From 2c28a908d4380c9b81a07cd314d0b827303dd9c4 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 7 Feb 2024 12:55:21 +0100 Subject: [PATCH 0966/1897] API feedback for activeCommentThread (#204588) Part of #204484 --- .../workbench/api/common/extHostComments.ts | 4 ++-- .../contrib/comments/browser/commentNode.ts | 3 --- .../contrib/comments/browser/commentReply.ts | 20 ++++++------------- .../comments/browser/commentService.ts | 8 +++++--- .../vscode.proposed.activeComment.d.ts | 5 +++-- 5 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 95db9c67fee..0f04b3f2455 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -601,7 +601,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo private _activeThread: vscode.CommentThread2 | undefined; - get activeThread(): vscode.CommentThread2 | undefined { + get activeCommentThread(): vscode.CommentThread2 | undefined { checkProposedApiEnabled(this._extension, 'activeComment'); return this._activeThread; } @@ -628,7 +628,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo get reactionHandler(): ReactionHandler | undefined { return that.reactionHandler; }, set reactionHandler(handler: ReactionHandler | undefined) { that.reactionHandler = handler; }, // get activeComment(): vscode.Comment | undefined { return that.activeComment; }, - get activeThread(): vscode.CommentThread2 | undefined { return that.activeThread; }, + get activeCommentThread(): vscode.CommentThread2 | undefined { return that.activeCommentThread; }, createCommentThread(uri: vscode.Uri, range: vscode.Range | undefined, comments: vscode.Comment[]): vscode.CommentThread | vscode.CommentThread2 { return that.createCommentThread(uri, range, comments).value; }, diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 13ddcb48f1e..3f72b63dad9 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -174,9 +174,6 @@ export class CommentNode extends Disposable { this._register(dom.addDisposableListener(this._domNode, dom.EventType.FOCUS_IN, () => { this.commentService.setActiveCommentAndThread(this.owner, { thread: this.commentThread, comment: this.comment }); }, true)); - this._register(dom.addDisposableListener(this._domNode, dom.EventType.FOCUS_OUT, () => { - this.commentService.setActiveCommentAndThread(this.owner, undefined); - }, true)); } private createScroll(container: HTMLElement, body: HTMLElement) { diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts index e4d45918616..4bf059c4267 100644 --- a/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -233,20 +233,12 @@ export class CommentReply extends Disposable { private createTextModelListener(commentEditor: ICodeEditor, commentForm: HTMLElement) { this._commentThreadDisposables.push(commentEditor.onDidFocusEditorWidget(() => { - // Add a setTimeout so that the blur event doesn't fire before the focus event - // https://github.com/microsoft/vscode/blob/f6d945edbdc1b2e8a176624fdf612bb61468944f/src/vs/base/browser/dom.ts#L1322-L1328 - setTimeout(() => { - this._commentThread.input = { - uri: commentEditor.getModel()!.uri, - value: commentEditor.getValue() - }; - this.commentService.setActiveEditingCommentThread(this._commentThread); - this.commentService.setActiveCommentAndThread(this.owner, { thread: this._commentThread }); - }, 0); - })); - - this._commentThreadDisposables.push(commentEditor.onDidBlurEditorWidget(() => { - this.commentService.setActiveCommentAndThread(this.owner, undefined); + this._commentThread.input = { + uri: commentEditor.getModel()!.uri, + value: commentEditor.getValue() + }; + this.commentService.setActiveEditingCommentThread(this._commentThread); + this.commentService.setActiveCommentAndThread(this.owner, { thread: this._commentThread }); })); this._commentThreadDisposables.push(commentEditor.getModel()!.onDidChangeContent(() => { diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index 2f72753c28c..1072a60aedf 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -272,6 +272,7 @@ export class CommentService extends Disposable implements ICommentService { this._onDidChangeActiveEditingCommentThread.fire(commentThread); } + private _lastActiveCommentController: ICommentController | undefined; async setActiveCommentAndThread(owner: string, commentInfo: { thread: CommentThread; comment?: Comment } | undefined) { const commentController = this._commentControls.get(owner); @@ -279,13 +280,14 @@ export class CommentService extends Disposable implements ICommentService { return; } + if (commentController !== this._lastActiveCommentController) { + await this._lastActiveCommentController?.setActiveCommentAndThread(undefined); + } + this._lastActiveCommentController = commentController; return commentController.setActiveCommentAndThread(commentInfo); } setDocumentComments(resource: URI, commentInfos: ICommentInfo[]): void { - if (commentInfos.length) { - this._workspaceHasCommenting.set(true); - } this._onDidSetResourceCommentInfos.fire({ resource, commentInfos }); } diff --git a/src/vscode-dts/vscode.proposed.activeComment.d.ts b/src/vscode-dts/vscode.proposed.activeComment.d.ts index f3e023c5bc5..deef101dff5 100644 --- a/src/vscode-dts/vscode.proposed.activeComment.d.ts +++ b/src/vscode-dts/vscode.proposed.activeComment.d.ts @@ -15,8 +15,9 @@ declare module 'vscode' { /** * The currently active comment thread or `undefined`. The active comment thread is the one - * that currently has focus or, when none has focus, undefined. + * in the CommentController that most recently had focus or, when a different CommentController's + * thread has most recently had focus, undefined. */ - readonly activeThread: CommentThread | undefined; + readonly activeCommentThread: CommentThread | undefined; } } From 57f5f81edd2478c868c144ffb0af7b182400956c Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 7 Feb 2024 13:39:34 +0100 Subject: [PATCH 0967/1897] api - rename: ChatAccess -> LanguageModelAccess (#204583) * rename - ChatAccess -> LanguageModelAccess * fix tests --- extensions/vscode-api-tests/package.json | 3 +- .../api/browser/mainThreadChatProvider.ts | 8 +- .../workbench/api/common/extHost.api.impl.ts | 14 ++- .../workbench/api/common/extHost.protocol.ts | 6 +- .../api/common/extHostChatProvider.ts | 95 +++++++++++-------- .../vscode.proposed.chatRequestAccess.d.ts | 89 ++++++++++------- 6 files changed, 131 insertions(+), 84 deletions(-) diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 2ba38dfdf9e..97de1b94d05 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -5,9 +5,10 @@ "publisher": "vscode", "license": "MIT", "enabledApiProposals": [ - "activeComment", + "activeComment", "authSession", "chatAgents2", + "chatRequestAccess", "defaultChatAgent", "contribViewsRemote", "contribStatusBarItems", diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index 9a14e8ec0f7..7a2324d9e43 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -27,8 +27,8 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatProvider); - this._proxy.$updateProviderList({ added: _chatProviderService.getProviders() }); - this._store.add(_chatProviderService.onDidChangeProviders(this._proxy.$updateProviderList, this._proxy)); + this._proxy.$updateLanguageModels({ added: _chatProviderService.getProviders() }); + this._store.add(_chatProviderService.onDidChangeProviders(this._proxy.$updateLanguageModels, this._proxy)); } dispose(): void { @@ -43,7 +43,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { const requestId = (Math.random() * 1e6) | 0; this._pendingProgress.set(requestId, progress); try { - await this._proxy.$provideChatResponse(handle, requestId, messages, options, token); + await this._proxy.$provideLanguageModelResponse(handle, requestId, messages, options, token); } finally { this._pendingProgress.delete(requestId); } @@ -60,7 +60,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { this._providerRegistrations.deleteAndDispose(handle); } - async $prepareChatAccess(providerId: string): Promise { + async $prepareChatAccess(providerId: string, justification?: string): Promise { return this._chatProviderService.lookupChatResponseProvider(providerId); } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 7cffe74fd74..0f7ca46c046 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1387,11 +1387,19 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I 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); + return extHostChatProvider.registerLanguageModel(extension.identifier, id, provider, metadata); }, - requestChatAccess(id: string) { + requestLanguageModelAccess(id, options) { checkProposedApiEnabled(extension, 'chatRequestAccess'); - return extHostChatProvider.requestChatResponseProvider(extension.identifier, id); + return extHostChatProvider.requestLanguageModelAccess(extension.identifier, id, options); + }, + get languageModels() { + checkProposedApiEnabled(extension, 'chatRequestAccess'); + return extHostChatProvider.getLanguageModelIds(); + }, + onDidChangeLanguageModels: (listener, thisArgs?, disposables?) => { + checkProposedApiEnabled(extension, 'chatRequestAccess'); + return extHostChatProvider.onDidChangeProviders(listener, thisArgs, disposables); }, registerVariable(name: string, description: string, resolver: vscode.ChatVariableResolver) { checkProposedApiEnabled(extension, 'chatAgents2'); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 78eda6ea257..b15a1540eb6 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1175,14 +1175,14 @@ export interface MainThreadChatProviderShape extends IDisposable { $unregisterProvider(handle: number): void; $handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise; - $prepareChatAccess(providerId: string): Promise; + $prepareChatAccess(providerId: string, justification?: string): Promise; $fetchResponse(extension: ExtensionIdentifier, provider: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise; } export interface ExtHostChatProviderShape { - $updateProviderList(data: { added?: string[]; removed?: string[] }): void; + $updateLanguageModels(data: { added?: string[]; removed?: string[] }): void; $updateAllowlist(data: { extension: ExtensionIdentifier; allowed: boolean }[]): void; - $provideChatResponse(handle: number, requestId: number, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; + $provideLanguageModelResponse(handle: number, requestId: number, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise; } diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index 8980c9eb009..4497039f34b 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -15,14 +15,14 @@ import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet } f import { AsyncIterableSource } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; -type ProviderData = { +type LanguageModelData = { readonly extension: ExtensionIdentifier; readonly provider: vscode.ChatResponseProvider; }; -class ChatResponseStream { +class LanguageModelResponseStream { - readonly apiObj: vscode.ChatResponseStream; + readonly apiObj: vscode.LanguageModelResponseStream; readonly stream = new AsyncIterableSource(); constructor(option: number, stream?: AsyncIterableSource) { @@ -35,18 +35,18 @@ class ChatResponseStream { } } -class ChatRequest { +class LanguageModelRequest { - readonly apiObject: vscode.ChatRequest; + readonly apiObject: vscode.LanguageModelRequest; - private readonly _onDidStart = new Emitter(); - private readonly _responseStreams = new Map(); + private readonly _onDidStart = new Emitter(); + private readonly _responseStreams = new Map(); private readonly _defaultStream = new AsyncIterableSource(); private _isDone: boolean = false; constructor( promise: Promise, - cts: CancellationTokenSource + readonly cts: CancellationTokenSource ) { const that = this; this.apiObject = { @@ -76,9 +76,9 @@ class ChatRequest { if (!res) { if (this._responseStreams.size === 0) { // the first response claims the default response - res = new ChatResponseStream(fragment.index, this._defaultStream); + res = new LanguageModelResponseStream(fragment.index, this._defaultStream); } else { - res = new ChatResponseStream(fragment.index); + res = new LanguageModelResponseStream(fragment.index); } this._responseStreams.set(fragment.index, res); this._onDidStart.fire(res.apiObj); @@ -93,9 +93,15 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { private static _idPool = 1; private readonly _proxy: MainThreadChatProviderShape; - private readonly _providers = new Map(); private readonly _onDidChangeAccess = new Emitter(); - private readonly _onDidChangeProviders = new Emitter<{ added: string[]; removed: string[] }>(); + private readonly _onDidChangeProviders = new Emitter(); + readonly onDidChangeProviders = this._onDidChangeProviders.event; + + private readonly _languageModels = new Map(); + private readonly _languageModelIds = new Set(); // these are ALL models, not just the one in this EH + private readonly _accessAllowlist = new ExtensionIdentifierMap(); + private readonly _pendingRequest = new Map(); + constructor( mainContext: IMainContext, @@ -109,20 +115,20 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._onDidChangeProviders.dispose(); } - registerProvider(extension: ExtensionIdentifier, identifier: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata): IDisposable { + registerLanguageModel(extension: ExtensionIdentifier, identifier: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata): IDisposable { const handle = ExtHostChatProvider._idPool++; - this._providers.set(handle, { extension, provider }); + this._languageModels.set(handle, { extension, provider }); this._proxy.$registerProvider(handle, identifier, { extension, model: metadata.name ?? '' }); return toDisposable(() => { + this._languageModels.delete(handle); 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); + async $provideLanguageModelResponse(handle: number, requestId: number, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise { + const data = this._languageModels.get(handle); if (!data) { return; } @@ -140,32 +146,41 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { //#region --- making request - private readonly _accessAllowlist = new ExtensionIdentifierMap(); - private readonly _providerIds = new Set(); - - private readonly _pendingRequest = new Map(); - $updateProviderList(data: { added?: string[] | undefined; removed?: string[] | undefined }): void { + $updateLanguageModels(data: { added?: string[] | undefined; removed?: string[] | undefined }): void { const added: string[] = []; const removed: string[] = []; if (data.added) { for (const id of data.added) { - this._providerIds.add(id); + this._languageModelIds.add(id); added.push(id); } } if (data.removed) { for (const id of data.removed) { - this._providerIds.delete(id); + // clean up + this._languageModelIds.delete(id); removed.push(id); + + // cancel pending requests for this model + for (const [key, value] of this._pendingRequest) { + if (value.languageModelId === id) { + value.res.cts.cancel(); + this._pendingRequest.delete(key); + } + } } } - this._onDidChangeProviders.fire({ added, removed }); + + this._onDidChangeProviders.fire(Object.freeze({ + added: Object.freeze(added), + removed: Object.freeze(removed) + })); } - getProviderIds(): string[] { - return Array.from(this._providerIds); + getLanguageModelIds(): string[] { + return Array.from(this._languageModelIds); } $updateAllowlist(data: { extension: ExtensionIdentifier; allowed: boolean }[]): void { @@ -180,16 +195,14 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._onDidChangeAccess.fire(updated); } - async requestChatResponseProvider(from: ExtensionIdentifier, identifier: string): Promise { - // check if a UI command is running/active - + async requestLanguageModelAccess(from: ExtensionIdentifier, languageModelId: string, options?: vscode.LanguageModelAccessOptions): Promise { if (!this._accessAllowlist.get(from)) { throw new Error('Extension is NOT allowed to make chat requests'); } - const metadata = await this._proxy.$prepareChatAccess(identifier); + const metadata = await this._proxy.$prepareChatAccess(languageModelId, options?.justification); if (!metadata) { - throw new Error(`ChatAccess '${identifier}' NOT found`); + throw new Error(`Language model '${languageModelId}' NOT found`); } const that = this; @@ -199,25 +212,29 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { return metadata.model; }, get isRevoked() { - return !that._accessAllowlist.get(from); + return !that._accessAllowlist.get(from) || !that._languageModelIds.has(languageModelId); }, get onDidChangeAccess() { - return Event.signal(Event.filter(that._onDidChangeAccess.event, set => set.has(from))); + const onDidChangeAccess = Event.filter(that._onDidChangeAccess.event, set => set.has(from)); + const onDidRemoveLM = Event.filter(that._onDidChangeProviders.event, e => e.removed.includes(languageModelId)); + return Event.signal(Event.any(onDidChangeAccess, onDidRemoveLM)); }, makeRequest(messages, options, token) { - if (!that._accessAllowlist.get(from)) { throw new Error('Access to chat has been revoked'); } - + if (!that._languageModelIds.has(languageModelId)) { + throw new Error('Language Model has been removed'); + } const cts = new CancellationTokenSource(token); const requestId = (Math.random() * 1e6) | 0; - const requestPromise = that._proxy.$fetchResponse(from, identifier, requestId, messages.map(typeConvert.ChatMessage.from), options ?? {}, cts.token); - const res = new ChatRequest(requestPromise, cts); - that._pendingRequest.set(requestId, { res }); + const requestPromise = that._proxy.$fetchResponse(from, languageModelId, requestId, messages.map(typeConvert.ChatMessage.from), options ?? {}, cts.token); + const res = new LanguageModelRequest(requestPromise, cts); + that._pendingRequest.set(requestId, { languageModelId, res }); requestPromise.finally(() => { that._pendingRequest.delete(requestId); + cts.dispose(); }); return res.apiObject; diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts index 2fc36577447..733e985dd98 100644 --- a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts +++ b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts @@ -5,9 +5,7 @@ declare module 'vscode' { - // TODO@API rename to LanguageModelAccess - - export interface ChatResponseStream { + export interface LanguageModelResponseStream { /** * The response stream. @@ -21,12 +19,14 @@ declare module 'vscode' { readonly option: number; } - export interface ChatRequest { + // TODO@API NAME: LanguageModelResponse (depends on having cancel or not) + export interface LanguageModelRequest { /** * The overall result of the request which represents failure or success * but _not_ the actual response or responses */ + // TODO@API define this type! result: Thenable; /** @@ -36,19 +36,20 @@ declare module 'vscode' { * Usually there is only one response option and this stream is more convienient to use * than the {@link onDidStartResponseStream `onDidStartResponseStream`} event. */ + // TODO@API NAME: responseStream response: AsyncIterable; /** * An event that fires whenever a new response option is available. The response * itself is a stream of the actual response. * - * *Note* that the first time this event fires, the {@link ChatResponseStream.response response stream} + * *Note* that the first time this event fires, the {@link LanguageModelResponseStream.response response stream} * is the same as the {@link response `default response stream`}. * * *Note* that unless requested there is only one response option, so this event will only fire * once. */ - onDidStartResponseStream: Event; + onDidStartResponseStream: Event; /** * Cancel this request. @@ -57,64 +58,84 @@ declare module 'vscode' { cancel(): void; } - /** - * Represents access to using a chat provider (LLM). Access is granted and temporary, usually only valid - * for the duration of an user interaction or specific time frame. + * Represents access to using a language model. Access can be revoked at any time and extension + * must check if the access is {@link LanguageModelAccess.isRevoked still valid} before using it. */ - export interface ChatAccess { + export interface LanguageModelAccess { /** - * Whether the access to chat has been revoked. This happens when the condition that allowed for - * chat access doesn't hold anymore, e.g a user interaction has ended. + * Whether the access to the language model has been revoked. */ readonly isRevoked: boolean; /** - * An event that is fired when the access to chat has been revoked or re-granted. + * An event that is fired when the access the language model has has been revoked or re-granted. */ readonly onDidChangeAccess: Event; /** - * The name of the model that is used for this chat access. It is expected that the model name can - * be used to lookup properties like token limits and ChatML support + * The name of the model. + * + * It is expected that the model name can be used to lookup properties like token limits or what + * `options` are available. */ readonly model: string; /** - * Make a chat request. - * - * The actual response will be reported back via the `progress` callback. The promise returned by this function - * returns a overall result which represents failure or success of the request. - * - * Chat can be asked for multiple response options. In that case the `progress` callback will be called multiple - * time with different `ChatResponseStream` objects. Each object represents a different response option and the actual - * response will be reported back via their `stream` property. + * Make a request to the language model. * * *Note:* This will throw an error if access has been revoked. * * @param messages * @param options */ - makeRequest(messages: ChatMessage[], options: { [name: string]: any }, token: CancellationToken): ChatRequest; - - // TODO@API disposable? - // dispose(): void; + makeRequest(messages: ChatMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelRequest; } + export interface LanguageModelAccessOptions { + /** + * A human-readable message that explains why access to a language model is needed and what feature is enabled by it. + */ + justification?: string; + } + + /** + * An event describing the change in the set of available language models. + */ + export interface LanguageModelChangeEvent { + /** + * Added language models. + */ + readonly added: readonly string[]; + /** + * Removed language models. + */ + readonly removed: readonly string[]; + } + + //@API DEFINE the namespace for this: env, lm, ai? export namespace chat { /** - * Request access to chat. + * Request access to a language model. * - * *Note* that this function will throw an error unless an user interaction is currently active. + * *Note* that this function will throw an error when the user didn't grant access * - * @param id The id of the chat provider, e.g `copilot` + * @param id The id of the language model, e.g `copilot` + * @returns A thenable that resolves to a language model access object, rejects is access wasn't granted */ - export function requestChatAccess(id: string): Thenable; + export function requestLanguageModelAccess(id: string, options?: LanguageModelAccessOptions): Thenable; - //@API add those - // export const chatAccesses: readonly string[]; - // export const onDidChangeChatAccesses: Event; + /** + * The identifiers of all language models that are currently available. + */ + export const languageModels: readonly string[]; + + /** + * An event that is fired when the set of available language models changes. + */ + //@API is this really needed? + export const onDidChangeLanguageModels: Event; } } From 7c16d665f9be688f6e7528b3c1a066a09b86e11f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 7 Feb 2024 13:51:18 +0100 Subject: [PATCH 0968/1897] api - refine filetree-part (name and structure), add push method to stream (#204595) https://github.com/microsoft/vscode/issues/199908 --- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../api/common/extHostChatAgents2.ts | 14 +++- .../api/common/extHostTypeConverters.ts | 58 ++++++++++++-- src/vs/workbench/api/common/extHostTypes.ts | 6 +- .../vscode.proposed.chatAgents2.d.ts | 77 +++++++++++++++++-- 5 files changed, 137 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 0f7ca46c046..dda8eaba39a 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1650,7 +1650,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I KeywordRecognitionStatus: extHostTypes.KeywordRecognitionStatus, ChatResponseTextPart: extHostTypes.ChatResponseTextPart, ChatResponseMarkdownPart: extHostTypes.ChatResponseMarkdownPart, - ChatResponseFilesPart: extHostTypes.ChatResponseFilesPart, + ChatResponseFileTreePart: extHostTypes.ChatResponseFilesPart, ChatResponseAnchorPart: extHostTypes.ChatResponseAnchorPart, ChatResponseProgressPart: extHostTypes.ChatResponseProgressPart, ChatResponseReferencePart: extHostTypes.ChatResponseReferencePart, diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 68657eeacb8..82fc72aae16 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -85,9 +85,9 @@ class ChatAgentResponseStream { _report(dto); return this; }, - files(value) { - throwIfDone(this.files); - const part = new extHostTypes.ChatResponseFilesPart(value); + filetree(value, baseUri) { + throwIfDone(this.filetree); + const part = new extHostTypes.ChatResponseFilesPart(value, baseUri); const dto = typeConvert.ChatResponseFilesPart.to(part); _report(dto); return this; @@ -113,6 +113,12 @@ class ChatAgentResponseStream { _report(dto); return this; }, + push(part) { + throwIfDone(this.push); + const dto = typeConvert.ChatResponsePart.to(part); + _report(dto); + return this; + }, report(progress) { throwIfDone(this.report); if ('placeholder' in progress && 'resolvedContent' in progress) { @@ -220,7 +226,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { || h.result; return { request: typeConvert.ChatAgentRequest.to(h.request), - response: coalesce(h.response.map(r => typeConvert.ChatResponsePart.to(r))), + response: coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r))), result } satisfies vscode.ChatAgentHistoryEntry; }))); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 4345dee16d7..dba9b9f3a50 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -50,6 +50,7 @@ import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import type * as vscode from 'vscode'; import * as types from './extHostTypes'; +import { basename } from 'vs/base/common/resources'; export namespace Command { @@ -2355,15 +2356,41 @@ export namespace ChatResponseMarkdownPart { } export namespace ChatResponseFilesPart { - export function to(part: vscode.ChatResponseFilesPart): IChatTreeData { + export function to(part: vscode.ChatResponseFileTreePart): IChatTreeData { + const { value, baseUri } = part; + function convert(items: vscode.ChatResponseFileTree[], baseUri: URI): extHostProtocol.IChatResponseProgressFileTreeData[] { + return items.map(item => { + const myUri = URI.joinPath(baseUri, item.name); + return { + label: item.name, + uri: myUri, + children: item.children && convert(item.children, myUri) + }; + }); + } return { kind: 'treeData', - treeData: part.value + treeData: { + label: basename(baseUri), + uri: baseUri, + children: convert(value, baseUri) + } }; } - export function from(part: Dto): vscode.ChatResponseFilesPart { - const value = revive(part.treeData); - return new types.ChatResponseFilesPart(value.treeData); + export function from(part: Dto): vscode.ChatResponseFileTreePart { + const { treeData } = revive(part.treeData); + function convert(items: extHostProtocol.IChatResponseProgressFileTreeData[]): vscode.ChatResponseFileTree[] { + return items.map(item => { + return { + name: item.label, + children: item.children && convert(item.children) + }; + }); + } + + const baseUri = treeData.uri; + const items = treeData.children ? convert(treeData.children) : []; + return new types.ChatResponseFilesPart(items, baseUri); } } @@ -2414,7 +2441,26 @@ export namespace ChatResponseReferencePart { export namespace ChatResponsePart { - export function to(part: extHostProtocol.IChatProgressDto): vscode.ChatResponsePart { + export function to(part: vscode.ChatResponsePart): extHostProtocol.IChatProgressDto { + if (part instanceof types.ChatResponseMarkdownPart) { + return ChatResponseMarkdownPart.to(part); + } else if (part instanceof types.ChatResponseAnchorPart) { + return ChatResponseAnchorPart.to(part); + } else if (part instanceof types.ChatResponseReferencePart) { + return ChatResponseReferencePart.to(part); + } else if (part instanceof types.ChatResponseProgressPart) { + return ChatResponseProgressPart.to(part); + } else if (part instanceof types.ChatResponseFilesPart) { + return ChatResponseFilesPart.to(part); + } + return { + kind: 'content', + content: '' + }; + + } + + export function from(part: extHostProtocol.IChatProgressDto): vscode.ChatResponsePart { switch (part.kind) { case 'markdownContent': return ChatResponseMarkdownPart.from(part); case 'inlineReference': return ChatResponseAnchorPart.from(part); diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 266793aa00d..3a52a722e00 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4219,9 +4219,11 @@ export class ChatResponseMarkdownPart { } export class ChatResponseFilesPart { - value: vscode.ChatAgentFileTreeData; - constructor(value: vscode.ChatAgentFileTreeData) { + value: vscode.ChatResponseFileTree[]; + baseUri: vscode.Uri; + constructor(value: vscode.ChatResponseFileTree[], baseUri: vscode.Uri) { this.value = value; + this.baseUri = baseUri; } } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index f8f0c2caed3..9864b23c7e2 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -17,7 +17,7 @@ declare module 'vscode' { /** * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. */ - response: (ChatAgentContentProgress | ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFilesPart | ChatResponseAnchorPart)[]; + response: (ChatAgentContentProgress | ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart)[]; /** * The result that was received from the chat agent. @@ -280,21 +280,78 @@ declare module 'vscode' { export interface ChatAgentResponseStream { + /** + * Push a text part to this stream. Short-hand for + * `push(new ChatResponseTextPart(value))`. + * + * @see {@link ChatAgentResponseStream.push} + * @param value A plain text value. + * @returns This stream. + */ text(value: string): ChatAgentResponseStream; + /** + * Push a markdown part to this stream. Short-hand for + * `push(new ChatResponseMarkdownPart(value))`. + * + * @see {@link ChatAgentResponseStream.push} + * @param value A markdown string or a string that should be interpreted as markdown. + * @returns This stream. + */ markdown(value: string | MarkdownString): ChatAgentResponseStream; - files(value: ChatAgentFileTreeData): ChatAgentResponseStream; - + /** + * Push an anchor part to this stream. Short-hand for + * `push(new ChatResponseAnchorPart(value, title))`. + * + * @param value A uri or location + * @param title An optional title that is rendered with value + * @returns This stream. + */ anchor(value: Uri | Location, title?: string): ChatAgentResponseStream; + /** + * Push a filetree part to this stream. Short-hand for + * `push(new ChatResponseFileTreePart(value))`. + * + * @param value File tree data. + * @param baseUri The base uri to which this file tree is relative to. + * @returns This stream. + */ + filetree(value: ChatResponseFileTree[], baseUri: Uri): ChatAgentResponseStream; + + /** + * Push a progress part to this stream. Short-hand for + * `push(new ChatResponseProgressPart(value))`. + * + * @param value + * @returns This stream. + */ + // TODO@API is this always inline or not + // TODO@API is this markdown or string? // TODO@API this influences the rendering, it inserts new lines which is likely a bug progress(value: string): ChatAgentResponseStream; + /** + * Push a reference to this stream. Short-hand for + * `push(new ChatResponseReferencePart(value))`. + * + * *Note* that the reference is not rendered inline with the response. + * + * @param value A uri or location + * @returns This stream. + */ // TODO@API support non-file uris, like http://example.com // TODO@API support mapped edits reference(value: Uri | Location): ChatAgentResponseStream; + /** + * Pushes a part to this stream. + * + * @param part A response part, rendered or metadata + */ + push(part: ChatResponsePart): ChatAgentResponseStream; + /** * @deprecated use above methods instread */ @@ -318,9 +375,15 @@ declare module 'vscode' { constructor(value: string | MarkdownString); } - export class ChatResponseFilesPart { - value: ChatAgentFileTreeData; - constructor(value: ChatAgentFileTreeData); + export interface ChatResponseFileTree { + name: string; + children?: ChatResponseFileTree[]; + } + + export class ChatResponseFileTreePart { + value: ChatResponseFileTree[]; + baseUri: Uri; + constructor(value: ChatResponseFileTree[], baseUri: Uri); } export class ChatResponseAnchorPart { @@ -339,7 +402,7 @@ declare module 'vscode' { constructor(value: Uri | Location); } - export type ChatResponsePart = ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFilesPart | ChatResponseAnchorPart + export type ChatResponsePart = ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseProgressPart | ChatResponseReferencePart; /** From fc771c597f7e15500921e7965f999338f2dccbd1 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 7 Feb 2024 14:33:31 +0100 Subject: [PATCH 0969/1897] Update Go grammar (#204590) --- extensions/go/cgmanifest.json | 4 +- extensions/go/syntaxes/go.tmLanguage.json | 441 ++++++++++++---------- 2 files changed, 239 insertions(+), 206 deletions(-) diff --git a/extensions/go/cgmanifest.json b/extensions/go/cgmanifest.json index dd832ef5145..2b837e80a2f 100644 --- a/extensions/go/cgmanifest.json +++ b/extensions/go/cgmanifest.json @@ -6,12 +6,12 @@ "git": { "name": "go-syntax", "repositoryUrl": "https://github.com/worlpaker/go-syntax", - "commitHash": "80a9e153c018b6c3b9c52766b39d7be9d915f68b" + "commitHash": "de0edabe11035e7035155c68eddc5817d5ec4af9" } }, "license": "MIT", "description": "The file syntaxes/go.tmLanguage.json is from https://github.com/worlpaker/go-syntax, which in turn was derived from https://github.com/jeff-hykin/better-go-syntax.", - "version": "0.5.5" + "version": "0.5.6" } ], "version": 1 diff --git a/extensions/go/syntaxes/go.tmLanguage.json b/extensions/go/syntaxes/go.tmLanguage.json index 5917568d4cc..3641d3edf99 100644 --- a/extensions/go/syntaxes/go.tmLanguage.json +++ b/extensions/go/syntaxes/go.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/worlpaker/go-syntax/commit/80a9e153c018b6c3b9c52766b39d7be9d915f68b", + "version": "https://github.com/worlpaker/go-syntax/commit/de0edabe11035e7035155c68eddc5817d5ec4af9", "name": "Go", "scopeName": "source.go", "patterns": [ @@ -126,7 +126,7 @@ "include": "#comments" }, { - "include": "#map_other_types" + "include": "#map_types" }, { "include": "#brackets" @@ -170,7 +170,7 @@ "include": "#comments" }, { - "include": "#map_other_types" + "include": "#map_types" }, { "include": "#delimiters" @@ -297,62 +297,79 @@ } ] }, - "map_other_types": { + "map_types": { + "comment": "map types", + "begin": "(?:(\\bmap\\b)(\\[))", + "beginCaptures": { + "1": { + "name": "keyword.map.go" + }, + "2": { + "name": "punctuation.definition.begin.bracket.square.go" + } + }, + "end": "(?:(\\])((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?!(?:[\\[\\]\\*]+)?\\b(?:func|struct|map)\\b)[\\w\\.\\[\\]\\*]+)?)", + "endCaptures": { + "1": { + "name": "punctuation.definition.end.bracket.square.go" + }, + "2": { + "patterns": [ + { + "include": "#type-declarations-without-brackets" + }, + { + "match": "\\[", + "name": "punctuation.definition.begin.bracket.square.go" + }, + { + "match": "\\]", + "name": "punctuation.definition.end.bracket.square.go" + }, + { + "match": "\\w+", + "name": "entity.name.type.go" + } + ] + } + }, "patterns": [ { - "comment": "Other map type names (struct/interface)", - "match": "(?:(\\bmap\\b)(\\[(?:.*)\\])?((?!\\bfunc\\b|\\bstruct\\b)[\\w\\.\\*]+)?)", - "captures": { - "1": { - "name": "keyword.map.go" - }, - "2": { - "patterns": [ - { - "include": "#type-declarations-without-brackets" - }, - { - "match": "\\[", - "name": "punctuation.definition.begin.bracket.square.go" - }, - { - "match": "\\]", - "name": "punctuation.definition.end.bracket.square.go" - }, - { - "match": "\\{", - "name": "punctuation.definition.begin.bracket.curly.go" - }, - { - "match": "\\}", - "name": "punctuation.definition.end.bracket.curly.go" - }, - { - "match": "\\(", - "name": "punctuation.definition.begin.bracket.round.go" - }, - { - "match": "\\)", - "name": "punctuation.definition.end.bracket.round.go" - }, - { - "match": "\\w+", - "name": "entity.name.type.go" - } - ] - }, - "3": { - "patterns": [ - { - "include": "#type-declarations" - }, - { - "match": "\\w+", - "name": "entity.name.type.go" - } - ] - } - } + "include": "#type-declarations-without-brackets" + }, + { + "include": "#generic_types" + }, + { + "include": "#functions" + }, + { + "match": "\\[", + "name": "punctuation.definition.begin.bracket.square.go" + }, + { + "match": "\\]", + "name": "punctuation.definition.end.bracket.square.go" + }, + { + "match": "\\{", + "name": "punctuation.definition.begin.bracket.curly.go" + }, + { + "match": "\\}", + "name": "punctuation.definition.end.bracket.curly.go" + }, + { + "match": "\\(", + "name": "punctuation.definition.begin.bracket.round.go" + }, + { + "match": "\\)", + "name": "punctuation.definition.end.bracket.round.go" + }, + { + "match": "\\w+", + "name": "entity.name.type.go" } ] }, @@ -1496,7 +1513,7 @@ }, "functions_inline": { "comment": "functions in-line with multi return types", - "match": "(?:(\\bfunc\\b)((?:\\((?:.*)\\))(?:\\s+)(?:\\((?:.*)\\)))(?:\\s+)(?=\\{))", + "match": "(?:(\\bfunc\\b)((?:\\((?:[^/]*)\\))(?:\\s+)(?:\\((?:[^/]*)\\)))(?:\\s+)(?=\\{))", "captures": { "1": { "name": "keyword.function.go" @@ -1554,7 +1571,7 @@ }, "support_functions": { "comment": "Support Functions", - "match": "(?:(?:((?<=\\.)\\w+)|(\\w+))(\\[(?:.*)?\\])?(?=\\())", + "match": "(?:(?:((?<=\\.)\\w+)|(\\w+))(\\[(?:(?:[\\w\\.\\*\\[\\]\\{\\}\"\\']+)(?:(?:\\,\\s*(?:[\\w\\.\\*\\[\\]\\{\\}]+))*))?\\])?(?=\\())", "captures": { "1": { "name": "entity.name.function.support.go" @@ -1588,7 +1605,15 @@ "name": "punctuation.definition.end.bracket.square.go" }, { - "match": "(?:\\w+)", + "match": "\\{", + "name": "punctuation.definition.begin.bracket.curly.go" + }, + { + "match": "\\}", + "name": "punctuation.definition.end.bracket.curly.go" + }, + { + "match": "\\w+", "name": "entity.name.type.go" } ] @@ -1599,6 +1624,7 @@ "comment": "struct and interface expression in-line (before curly bracket)", "patterns": [ { + "comment": "after control variables must be added exactly here, do not move it! (changing may not affect tests, so be careful!)", "include": "#after_control_variables" }, { @@ -1947,7 +1973,7 @@ "patterns": [ { "comment": "single type declaration", - "match": "(?:(?:^|\\s+)(\\btype\\b)(?:\\s*)([\\w\\.\\*]+)(\\[(?:.*)\\])?(?:\\s+)((?!(?:[\\[\\]\\*]+)?(?:\\bstruct\\b|\\binterface\\b))([\\s\\S]+)))", + "match": "(?:(?:^\\s*)(\\btype\\b)(?:\\s*)([\\w\\.\\*]+)(?:\\s+)(?!(?:[\\[\\]\\*]+)?\\b(?:struct|interface)\\b)([\\s\\S]+))", "captures": { "1": { "name": "keyword.type.go" @@ -1958,38 +1984,12 @@ "include": "#type-declarations" }, { - "match": "(?:\\w+)", + "match": "\\w+", "name": "entity.name.type.go" } ] }, "3": { - "patterns": [ - { - "begin": "\\[", - "beginCaptures": { - "0": { - "name": "punctuation.definition.begin.bracket.square.go" - } - }, - "end": "\\]", - "endCaptures": { - "0": { - "name": "punctuation.definition.end.bracket.square.go" - } - }, - "patterns": [ - { - "include": "#generic_param_types" - }, - { - "include": "$self" - } - ] - } - ] - }, - "4": { "patterns": [ { "begin": "\\(", @@ -2020,52 +2020,15 @@ "include": "#generic_types" }, { - "match": "(?:\\w+)", + "match": "\\w+", "name": "entity.name.type.go" } ] } } - } - ] - }, - "multi_types": { - "patterns": [ - { - "comment": "multi type declaration", - "begin": "(\\btype\\b)\\s*(\\()", - "beginCaptures": { - "1": { - "name": "keyword.type.go" - }, - "2": { - "name": "punctuation.definition.begin.bracket.round.go" - } - }, - "patterns": [ - { - "include": "#struct_variables_types" - }, - { - "include": "#type-declarations-without-brackets" - }, - { - "include": "#parameter-variable-types" - }, - { - "match": "(?:\\w+)", - "name": "entity.name.type.go" - } - ], - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.definition.end.bracket.round.go" - } - } }, { - "comment": "single type declaration with multi-lines generics", + "comment": "single type declaration with generics", "begin": "(?:(?:^|\\s+)(\\btype\\b)(?:\\s*)([\\w\\.\\*]+)(?=\\[))", "beginCaptures": { "1": { @@ -2077,7 +2040,7 @@ "include": "#type-declarations-without-brackets" }, { - "match": "(?:\\w+)", + "match": "\\w+", "name": "entity.name.type.go" } ] @@ -2085,42 +2048,6 @@ }, "end": "(?:(?<=\\])((?:\\s+)(?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:(?!(?:[\\[\\]\\*]+)?(?:\\bstruct\\b|\\binterface\\b|\\bfunc\\b))[\\w\\.\\-\\*\\[\\]]+))?)", "endCaptures": { - "1": { - "patterns": [ - { - "include": "#type-declarations-without-brackets" - }, - { - "match": "(?:\\w+)", - "name": "entity.name.type.go" - } - ] - } - }, - "patterns": [ - { - "include": "#struct_variables_types" - }, - { - "include": "#type-declarations-without-brackets" - }, - { - "include": "#parameter-variable-types" - }, - { - "match": "(?:\\w+)", - "name": "entity.name.type.go" - } - ] - } - ] - }, - "after_control_variables": { - "patterns": [ - { - "comment": "After control variables, to not highlight as a struct/interface", - "match": "(?:(?<=\\brange\\b|\\bswitch\\b|\\;|\\bif\\b|\\bfor\\b|\\<|\\>|\\<\\=|\\>\\=|\\=\\=|\\!\\=|\\w(?:\\+|/|\\-|\\*|\\%)|\\w(?:\\+|/|\\-|\\*|\\%)\\=|\\|\\||\\&\\&)(?:\\s*)([[:alnum:]\\-\\_\\!\\.\\[\\]\\<\\>\\=\\*/\\+\\%\\:]+)(?:\\s*)(?=\\{))", - "captures": { "1": { "patterns": [ { @@ -2135,15 +2062,112 @@ "name": "punctuation.definition.end.bracket.square.go" }, { - "match": "(?:\\w+)", - "name": "variable.other.go" + "match": "\\w+", + "name": "entity.name.type.go" } ] } - } + }, + "patterns": [ + { + "include": "#struct_variables_types" + }, + { + "include": "#type-declarations-without-brackets" + }, + { + "include": "#parameter-variable-types" + }, + { + "match": "\\[", + "name": "punctuation.definition.begin.bracket.square.go" + }, + { + "match": "\\]", + "name": "punctuation.definition.end.bracket.square.go" + }, + { + "match": "\\{", + "name": "punctuation.definition.begin.bracket.curly.go" + }, + { + "match": "\\}", + "name": "punctuation.definition.end.bracket.curly.go" + }, + { + "match": "\\(", + "name": "punctuation.definition.begin.bracket.round.go" + }, + { + "match": "\\)", + "name": "punctuation.definition.end.bracket.round.go" + }, + { + "match": "\\w+", + "name": "entity.name.type.go" + } + ] } ] }, + "multi_types": { + "comment": "multi type declaration", + "begin": "(\\btype\\b)\\s*(\\()", + "beginCaptures": { + "1": { + "name": "keyword.type.go" + }, + "2": { + "name": "punctuation.definition.begin.bracket.round.go" + } + }, + "patterns": [ + { + "include": "#struct_variables_types" + }, + { + "include": "#type-declarations-without-brackets" + }, + { + "include": "#parameter-variable-types" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ], + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.round.go" + } + } + }, + "after_control_variables": { + "comment": "After control variables, to not highlight as a struct/interface (before formatting with gofmt)", + "match": "(?:(?<=\\brange\\b|\\bswitch\\b|\\;|\\bif\\b|\\bfor\\b|\\<|\\>|\\<\\=|\\>\\=|\\=\\=|\\!\\=|\\w(?:\\+|/|\\-|\\*|\\%)|\\w(?:\\+|/|\\-|\\*|\\%)\\=|\\|\\||\\&\\&)(?:\\s*)([[:alnum:]\\-\\_\\!\\.\\[\\]\\<\\>\\=\\*/\\+\\%\\:]+)(?:\\s*)(?=\\{))", + "captures": { + "1": { + "patterns": [ + { + "include": "#type-declarations-without-brackets" + }, + { + "match": "\\[", + "name": "punctuation.definition.begin.bracket.square.go" + }, + { + "match": "\\]", + "name": "punctuation.definition.end.bracket.square.go" + }, + { + "match": "(?:\\w+)", + "name": "variable.other.go" + } + ] + } + } + }, "syntax_errors": { "patterns": [ { @@ -2210,7 +2234,7 @@ }, { "comment": "make keyword", - "match": "(?:(\\bmake\\b)(?:(\\()((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?:[\\w\\.\\*\\[\\]\\{\\}]+)(?:\\[(?:[^\\]]+)?\\])?(?:[\\w\\.\\*\\[\\]\\{\\}]+)?)?((?:\\,\\s*[\\w\\.\\(\\)]+)+)?(\\))))", + "match": "(?:(\\bmake\\b)(?:(\\()((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+(?:\\([^\\)]+\\))?)?(?:[\\w\\.\\*\\[\\]\\{\\}]+)?(?:\\[(?:[^\\]]+)?\\])?(?:[\\w\\.\\*\\[\\]\\{\\}]+)?)?((?:\\,\\s*(?:[\\w\\.\\(\\)]+)?)+)?(\\))))", "captures": { "1": { "name": "entity.name.function.support.builtin.go" @@ -2247,45 +2271,22 @@ ] }, "struct_interface_declaration": { - "comment": "struct, interface type declarations", - "match": "(?:(?:^|\\s+)(\\btype\\b)(?:\\s+)([\\w\\.]+)(\\[(?:.*)\\])?)", + "comment": "struct, interface type declarations (related to: struct_variables_types, interface_variables_types)", + "match": "(?:(?:^\\s*)(\\btype\\b)(?:\\s*)([\\w\\.]+))", "captures": { "1": { "name": "keyword.type.go" }, "2": { "patterns": [ + { + "include": "#type-declarations" + }, { "match": "\\w+", "name": "entity.name.type.go" } ] - }, - "3": { - "patterns": [ - { - "begin": "\\[", - "beginCaptures": { - "0": { - "name": "punctuation.definition.begin.bracket.square.go" - } - }, - "end": "\\]", - "endCaptures": { - "0": { - "name": "punctuation.definition.end.bracket.square.go" - } - }, - "patterns": [ - { - "include": "#generic_param_types" - }, - { - "include": "$self" - } - ] - } - ] } } }, @@ -2384,7 +2385,7 @@ "patterns": [ { "comment": "var and const with single type assignment", - "match": "(?:(?<=\\bvar\\b|\\bconst\\b)(?:\\s*)([\\w\\.]+(?:(?:\\,\\s*[\\w\\.]+)+)?)(?:\\s*)(?:((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?!(?:(?:[\\[\\]\\*]+)?\\bstruct\\b\\s*\\{)|(?:(?:[\\[\\]\\*]+)?\\bfunc\\b))(?:(?:(?:[\\w\\.\\*]+)?(?:\\[(?:.*)\\])?(?:[\\w\\.\\*]+)?))?\\s*(?:\\=)?))?)", + "match": "(?:(?<=\\bvar\\b|\\bconst\\b)(?:\\s*)(\\b[\\w\\.]+(?:\\,\\s*[\\w\\.]+)*)(?:\\s*)((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+(?:\\([^\\)]+\\))?)?(?!(?:[\\[\\]\\*]+)?\\b(?:struct|func|map)\\b)(?:[\\w\\.\\[\\]\\*]+(?:\\,\\s*[\\w\\.\\[\\]\\*]+)*)?(?:\\s*)(?:\\=)?)?)", "captures": { "1": { "patterns": [ @@ -2392,7 +2393,7 @@ "include": "#delimiters" }, { - "match": "(?:\\w+)", + "match": "\\w+", "name": "variable.other.assignment.go" } ] @@ -2400,13 +2401,29 @@ "2": { "patterns": [ { - "include": "#type-declarations" + "include": "#type-declarations-without-brackets" }, { "include": "#generic_types" }, { - "match": "(?:\\w+)", + "match": "\\(", + "name": "punctuation.definition.begin.bracket.round.go" + }, + { + "match": "\\)", + "name": "punctuation.definition.end.bracket.round.go" + }, + { + "match": "\\[", + "name": "punctuation.definition.begin.bracket.square.go" + }, + { + "match": "\\]", + "name": "punctuation.definition.end.bracket.square.go" + }, + { + "match": "\\w+", "name": "entity.name.type.go" } ] @@ -2429,7 +2446,7 @@ }, "patterns": [ { - "match": "(?:(?:^\\s+)([\\w\\.]+(?:(?:\\,\\s*[\\w\\.]+)+)?)(?:\\s*)((?:((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?!(?:(?:[\\[\\]\\*]+)?\\bstruct\\b\\s*\\{)|(?:(?:[\\[\\]\\*]+)?\\bfunc\\b))(?:(?:(?:[\\w\\.\\*]+)?(?:\\[(?:.*)\\])?(?:[\\w\\.\\*]+)?))?\\s*(?:\\=)?))?))", + "match": "(?:(?:^\\s*)(\\b[\\w\\.]+(?:\\,\\s*[\\w\\.]+)*)(?:\\s*)((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+(?:\\([^\\)]+\\))?)?(?!(?:[\\[\\]\\*]+)?\\b(?:struct|func|map)\\b)(?:[\\w\\.\\[\\]\\*]+(?:\\,\\s*[\\w\\.\\[\\]\\*]+)*)?(?:\\s*)(?:\\=)?)?)", "captures": { "1": { "patterns": [ @@ -2437,7 +2454,7 @@ "include": "#delimiters" }, { - "match": "(?:\\w+)", + "match": "\\w+", "name": "variable.other.assignment.go" } ] @@ -2445,13 +2462,29 @@ "2": { "patterns": [ { - "include": "#type-declarations" + "include": "#type-declarations-without-brackets" }, { "include": "#generic_types" }, { - "match": "(?:\\w+)", + "match": "\\(", + "name": "punctuation.definition.begin.bracket.round.go" + }, + { + "match": "\\)", + "name": "punctuation.definition.end.bracket.round.go" + }, + { + "match": "\\[", + "name": "punctuation.definition.begin.bracket.square.go" + }, + { + "match": "\\]", + "name": "punctuation.definition.end.bracket.square.go" + }, + { + "match": "\\w+", "name": "entity.name.type.go" } ] From a81f19bd7143f6eedc6c03e30d0af604f86ca9ef Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 7 Feb 2024 14:46:47 +0100 Subject: [PATCH 0970/1897] Bump distro (#204602) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 46cb4f726f9..c94ab6966c4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "3cea8384a7a351accd0153af4199d204dc9ec202", + "distro": "0a14a7cde028e801c7aea8415afac7ddf3c9a0bd", "author": { "name": "Microsoft Corporation" }, From d5ec4bd197720093bdd655ebb9eca8374c983cf1 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 7 Feb 2024 14:52:21 +0100 Subject: [PATCH 0971/1897] Monokai Dimmed theme: LESS syntax highlighting colors very different in v1.86.0 (#204605) --- .../themes/dimmed-monokai-color-theme.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json index 00ba8b3ace6..796cd5b95c4 100644 --- a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json +++ b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json @@ -366,7 +366,7 @@ }, { "name": "CSS ID", - "scope": "meta.selector.css entity.other.attribute-name.id", + "scope": "meta.selector entity.other.attribute-name.id", "settings": { "fontStyle": "", "foreground": "#9872A2" @@ -374,7 +374,7 @@ }, { "name": "CSS Property Name", - "scope": "support.type.property-name.css", + "scope": "support.type.property-name", "settings": { "fontStyle": "", "foreground": "#676867" @@ -382,7 +382,7 @@ }, { "name": "CSS Property Value", - "scope": "meta.property-group support.constant.property-value.css, meta.property-value support.constant.property-value.css", + "scope": "meta.property-group support.constant.property-value, meta.property-value support.constant.property-value", "settings": { "fontStyle": "", "foreground": "#C7444A" From c9235bf40be8e3db1807c2a06775d25d94a3b22d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 7 Feb 2024 06:41:47 -0800 Subject: [PATCH 0972/1897] Introuduce extension features (#204607) Introduce features with access control --- .../api/browser/mainThreadChatProvider.ts | 15 +- .../api/browser/viewsExtensionPoint.ts | 116 ++- .../api/common/configurationExtensionPoint.ts | 60 +- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 4 +- .../api/common/extHostChatAgents2.ts | 5 - .../api/common/extHostChatProvider.ts | 23 +- .../common/jsonValidationExtensionPoint.ts | 50 ++ .../contrib/chat/browser/chat.contribution.ts | 71 +- .../contrib/chat/common/chatService.ts | 1 + .../common/codeActionsExtensionPoint.ts | 59 ++ .../customEditor/common/extensionPoint.ts | 54 ++ .../contrib/debug/common/debugSchemas.ts | 51 ++ .../extensions/browser/extensionEditor.ts | 793 +----------------- .../browser/extensionFeaturesTab.ts | 426 ++++++++++ .../browser/extensions.contribution.ts | 10 +- .../extensions/browser/extensionsActions.ts | 21 +- .../extensions/browser/extensionsWidgets.ts | 4 +- .../browser/extensionsWorkbenchService.ts | 6 +- .../browser/media/extensionEditor.css | 122 ++- .../contrib/extensions/common/extensions.ts | 3 +- .../extensions/common/extensionsInput.ts | 1 + .../common/localization.contribution.ts | 53 ++ .../browser/notebookExtensionPoint.ts | 99 +++ .../actions/common/menusExtensionPoint.ts | 116 ++- .../browser/authenticationService.ts | 51 ++ .../common/extensionFeatures.ts | 110 +++ .../extensionFeaturesManagemetService.ts | 231 +++++ .../common/abstractExtensionService.ts | 95 ++- .../language/common/languageService.ts | 94 ++- .../themes/common/colorExtensionPoint.ts | 52 ++ .../themes/common/themeExtensionPoints.ts | 50 ++ src/vs/workbench/workbench.common.main.ts | 1 + 33 files changed, 2030 insertions(+), 819 deletions(-) create mode 100644 src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts create mode 100644 src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts create mode 100644 src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index 7a2324d9e43..96f65a744b8 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -10,6 +10,8 @@ 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'; +import { CHAT_FEATURE_ID } from 'vs/workbench/contrib/chat/common/chatService'; +import { IExtensionFeaturesManagementService } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; @extHostNamedCustomer(MainContext.MainThreadChatProvider) @@ -23,12 +25,19 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { constructor( extHostContext: IExtHostContext, @IChatProviderService private readonly _chatProviderService: IChatProviderService, + @IExtensionFeaturesManagementService private readonly _extensionFeaturesManagementService: IExtensionFeaturesManagementService, @ILogService private readonly _logService: ILogService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatProvider); this._proxy.$updateLanguageModels({ added: _chatProviderService.getProviders() }); + this._proxy.$updateAccesslist(_extensionFeaturesManagementService.getEnablementData(CHAT_FEATURE_ID)); this._store.add(_chatProviderService.onDidChangeProviders(this._proxy.$updateLanguageModels, this._proxy)); + this._store.add(_extensionFeaturesManagementService.onDidChangeEnablement(e => { + if (e.featureId === CHAT_FEATURE_ID) { + this._proxy.$updateAccesslist(_extensionFeaturesManagementService.getEnablementData(CHAT_FEATURE_ID)); + } + })); } dispose(): void { @@ -60,7 +69,11 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { this._providerRegistrations.deleteAndDispose(handle); } - async $prepareChatAccess(providerId: string, justification?: string): Promise { + async $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise { + const access = await this._extensionFeaturesManagementService.getAccess(extension, CHAT_FEATURE_ID); + if (!access) { + return undefined; + } return this._chatProviderService.lookupChatResponseProvider(providerId); } diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index a022c6755b6..b67c8eb616e 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -9,7 +9,7 @@ import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { ExtensionIdentifier, ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionIdentifierSet, IExtensionDescription, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -35,6 +35,8 @@ import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { ILogService } from 'vs/platform/log/common/log'; +import { IExtensionFeatureTableRenderer, IRenderedData, ITableData, IRowData, IExtensionFeaturesRegistry, Extensions as ExtensionFeaturesRegistryExtensions } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { Disposable } from 'vs/base/common/lifecycle'; export interface IUserFriendlyViewsContainerDescriptor { id: string; @@ -666,4 +668,116 @@ class ViewsExtensionHandler implements IWorkbenchContribution { } } +class ViewContainersDataRenderer extends Disposable implements IExtensionFeatureTableRenderer { + + readonly type = 'table'; + + shouldRender(manifest: IExtensionManifest): boolean { + return !!manifest.contributes?.viewsContainers; + } + + render(manifest: IExtensionManifest): IRenderedData { + const contrib = manifest.contributes?.viewsContainers || {}; + + const viewContainers = Object.keys(contrib).reduce((result, location) => { + const viewContainersForLocation = contrib[location]; + result.push(...viewContainersForLocation.map(viewContainer => ({ ...viewContainer, location }))); + return result; + }, [] as Array<{ id: string; title: string; location: string }>); + + if (!viewContainers.length) { + return { data: { headers: [], rows: [] }, dispose: () => { } }; + } + + const headers = [ + localize('view container id', "ID"), + localize('view container title', "Title"), + localize('view container location', "Where"), + ]; + + const rows: IRowData[][] = viewContainers + .sort((a, b) => a.id.localeCompare(b.id)) + .map(viewContainer => { + return [ + viewContainer.id, + viewContainer.title, + viewContainer.location + ]; + }); + + return { + data: { + headers, + rows + }, + dispose: () => { } + }; + } +} + +class ViewsDataRenderer extends Disposable implements IExtensionFeatureTableRenderer { + + readonly type = 'table'; + + shouldRender(manifest: IExtensionManifest): boolean { + return !!manifest.contributes?.views; + } + + render(manifest: IExtensionManifest): IRenderedData { + const contrib = manifest.contributes?.views || {}; + + const views = Object.keys(contrib).reduce((result, location) => { + const viewsForLocation = contrib[location]; + result.push(...viewsForLocation.map(view => ({ ...view, location }))); + return result; + }, [] as Array<{ id: string; name: string; location: string }>); + + if (!views.length) { + return { data: { headers: [], rows: [] }, dispose: () => { } }; + } + + const headers = [ + localize('view id', "ID"), + localize('view name title', "Name"), + localize('view container location', "Where"), + ]; + + const rows: IRowData[][] = views + .sort((a, b) => a.id.localeCompare(b.id)) + .map(view => { + return [ + view.id, + view.name, + view.location + ]; + }); + + return { + data: { + headers, + rows + }, + dispose: () => { } + }; + } +} + +Registry.as(ExtensionFeaturesRegistryExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'viewsContainers', + label: localize('viewsContainers', "View Containers"), + enablement: { + canToggle: false + }, + renderer: new SyncDescriptor(ViewContainersDataRenderer), +}); + +Registry.as(ExtensionFeaturesRegistryExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'views', + label: localize('views', "Views"), + enablement: { + canToggle: false + }, + renderer: new SyncDescriptor(ViewsDataRenderer), +}); + registerWorkbenchContribution2(ViewsExtensionHandler.ID, ViewsExtensionHandler, WorkbenchPhase.BlockStartup); diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index fbf4f3ef6d3..db7cec83a6a 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -8,12 +8,16 @@ import * as objects from 'vs/base/common/objects'; import { Registry } from 'vs/platform/registry/common/platform'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IConfigurationNode, IConfigurationRegistry, Extensions, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_REGEX, IConfigurationDefaults, configurationDefaultsSchemaId, IConfigurationDelta } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationNode, IConfigurationRegistry, Extensions, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_REGEX, IConfigurationDefaults, configurationDefaultsSchemaId, IConfigurationDelta, getDefaultValue } from 'vs/platform/configuration/common/configurationRegistry'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { workspaceSettingsSchemaId, launchSchemaId, tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; -import { isObject } from 'vs/base/common/types'; -import { ExtensionIdentifierMap } from 'vs/platform/extensions/common/extensions'; +import { isObject, isUndefined } from 'vs/base/common/types'; +import { ExtensionIdentifierMap, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { IStringDictionary } from 'vs/base/common/collections'; +import { Extensions as ExtensionFeaturesExtensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, IRenderedData, IRowData, ITableData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { MarkdownString } from 'vs/base/common/htmlContent'; const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); const configurationRegistry = Registry.as(Extensions.Configuration); @@ -385,3 +389,53 @@ jsonRegistry.registerSchema('vscode://schemas/workspaceConfig', { }, errorMessage: nls.localize('unknownWorkspaceProperty', "Unknown workspace configuration property") }); + + +class SettingsTableRenderer extends Disposable implements IExtensionFeatureTableRenderer { + + readonly type = 'table'; + + shouldRender(manifest: IExtensionManifest): boolean { + return !!manifest.contributes?.configuration; + } + + render(manifest: IExtensionManifest): IRenderedData { + const configuration = manifest.contributes?.configuration; + let properties: any = {}; + if (Array.isArray(configuration)) { + configuration.forEach(config => { + properties = { ...properties, ...config.properties }; + }); + } else if (configuration) { + properties = configuration.properties; + } + + const contrib = properties ? Object.keys(properties) : []; + const headers = [nls.localize('setting name', "ID"), nls.localize('description', "Description"), nls.localize('default', "Default")]; + const rows: IRowData[][] = contrib.sort((a, b) => a.localeCompare(b)) + .map(key => { + return [ + { data: key, type: 'code' }, + properties[key].markdownDescription ? new MarkdownString(properties[key].markdownDescription, false) : properties[key].description ?? '', + { data: `${isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : JSON.stringify(properties[key].default)}`, type: 'code' } + ]; + }); + + return { + data: { + headers, + rows + }, + dispose: () => { } + }; + } +} + +Registry.as(ExtensionFeaturesExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'configuration', + label: nls.localize('settings', "Settings"), + enablement: { + canToggle: false + }, + renderer: new SyncDescriptor(SettingsTableRenderer), +}); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index dda8eaba39a..08d86933303 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -208,7 +208,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I 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 extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService)); + const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostLogService)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol)); const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b15a1540eb6..8826cef8433 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1175,13 +1175,13 @@ export interface MainThreadChatProviderShape extends IDisposable { $unregisterProvider(handle: number): void; $handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise; - $prepareChatAccess(providerId: string, justification?: string): Promise; + $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise; $fetchResponse(extension: ExtensionIdentifier, provider: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise; } export interface ExtHostChatProviderShape { $updateLanguageModels(data: { added?: string[]; removed?: string[] }): void; - $updateAllowlist(data: { extension: ExtensionIdentifier; allowed: boolean }[]): void; + $updateAccesslist(data: { extension: ExtensionIdentifier; enabled: boolean }[]): void; $provideLanguageModelResponse(handle: number, requestId: number, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise; } diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 82fc72aae16..b0c5f4405e7 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -15,7 +15,6 @@ import { localize } from 'vs/nls'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { IChatAgentCommand, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; @@ -154,7 +153,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { constructor( mainContext: IMainContext, - private readonly _extHostChatProvider: ExtHostChatProvider, private readonly _logService: ILogService, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); @@ -179,8 +177,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); } - this._extHostChatProvider.$updateAllowlist([{ extension: agent.extension.identifier, allowed: true }]); - const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService); try { const convertedHistory = await this.prepareHistory(agent, request, context); @@ -215,7 +211,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } finally { stream.close(); - this._extHostChatProvider.$updateAllowlist([{ extension: agent.extension.identifier, allowed: false }]); } } diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index 4497039f34b..5493be6a2b5 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -99,7 +99,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { private readonly _languageModels = new Map(); private readonly _languageModelIds = new Set(); // these are ALL models, not just the one in this EH - private readonly _accessAllowlist = new ExtensionIdentifierMap(); + private readonly _accesslist = new ExtensionIdentifierMap(); private readonly _pendingRequest = new Map(); @@ -183,12 +183,12 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { return Array.from(this._languageModelIds); } - $updateAllowlist(data: { extension: ExtensionIdentifier; allowed: boolean }[]): void { + $updateAccesslist(data: { extension: ExtensionIdentifier; enabled: boolean }[]): void { const updated = new ExtensionIdentifierSet(); - for (const { extension, allowed } of data) { - const oldValue = this._accessAllowlist.get(extension); + for (const { extension, enabled: allowed } of data) { + const oldValue = this._accesslist.get(extension); if (oldValue !== allowed) { - this._accessAllowlist.set(extension, allowed); + this._accesslist.set(extension, allowed); updated.add(extension); } } @@ -196,12 +196,17 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { } async requestLanguageModelAccess(from: ExtensionIdentifier, languageModelId: string, options?: vscode.LanguageModelAccessOptions): Promise { - if (!this._accessAllowlist.get(from)) { + // check if the extension is in the access list and allowed to make chat requests + if (this._accesslist.get(from) === false) { throw new Error('Extension is NOT allowed to make chat requests'); } - const metadata = await this._proxy.$prepareChatAccess(languageModelId, options?.justification); + const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, options?.justification); + if (!metadata) { + if (!this._accesslist.get(from)) { + throw new Error('Extension is NOT allowed to make chat requests'); + } throw new Error(`Language model '${languageModelId}' NOT found`); } @@ -212,7 +217,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { return metadata.model; }, get isRevoked() { - return !that._accessAllowlist.get(from) || !that._languageModelIds.has(languageModelId); + return !that._accesslist.get(from) || !that._languageModelIds.has(languageModelId); }, get onDidChangeAccess() { const onDidChangeAccess = Event.filter(that._onDidChangeAccess.event, set => set.has(from)); @@ -220,7 +225,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { return Event.signal(Event.any(onDidChangeAccess, onDidRemoveLM)); }, makeRequest(messages, options, token) { - if (!that._accessAllowlist.get(from)) { + if (!that._accesslist.get(from)) { throw new Error('Access to chat has been revoked'); } if (!that._languageModelIds.has(languageModelId)) { diff --git a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts index f95aa03c0d4..f4edaebde1e 100644 --- a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts +++ b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts @@ -7,6 +7,11 @@ import * as nls from 'vs/nls'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import * as resources from 'vs/base/common/resources'; import { isString } from 'vs/base/common/types'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Extensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, IRenderedData, IRowData, ITableData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; interface IJSONValidationExtensionPoint { fileMatch: string | string[]; @@ -82,3 +87,48 @@ export class JSONValidationExtensionPoint { } } + +class JSONValidationDataRenderer extends Disposable implements IExtensionFeatureTableRenderer { + + readonly type = 'table'; + + shouldRender(manifest: IExtensionManifest): boolean { + return !!manifest.contributes?.jsonValidation; + } + + render(manifest: IExtensionManifest): IRenderedData { + const contrib = manifest.contributes?.jsonValidation || []; + if (!contrib.length) { + return { data: { headers: [], rows: [] }, dispose: () => { } }; + } + + const headers = [ + nls.localize('fileMatch', "File Match"), + nls.localize('schema', "Schema"), + ]; + + const rows: IRowData[][] = contrib.map(v => { + return [ + { data: Array.isArray(v.fileMatch) ? v.fileMatch.join(', ') : v.fileMatch, type: 'code' }, + v.url, + ]; + }); + + return { + data: { + headers, + rows + }, + dispose: () => { } + }; + } +} + +Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'jsonValidation', + label: nls.localize('jsonValidation', "JSON Validation"), + enablement: { + canToggle: false + }, + renderer: new SyncDescriptor(JSONValidationDataRenderer), +}); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 5a9d5ef5cf8..b7df6c82ceb 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isMacintosh } from 'vs/base/common/platform'; +import { Emitter } from 'vs/base/common/event'; import * as nls from 'vs/nls'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -30,7 +31,7 @@ import { ChatWidgetService } from 'vs/workbench/contrib/chat/browser/chatWidget' import 'vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib'; import 'vs/workbench/contrib/chat/browser/contrib/chatHistoryVariables'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; -import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { CHAT_FEATURE_ID, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; import { ChatWidgetHistoryService, IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService'; import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; @@ -58,6 +59,9 @@ import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/c import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IExtensionFeatureMarkdownRenderer, Extensions as ExtensionFeaturesExtensions, IRenderedData, IExtensionFeaturesRegistry, IExtensionFeaturesManagementService } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { ExtensionIdentifier, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -283,6 +287,69 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { } } +class ChatFeatureMarkdowneRenderer extends Disposable implements IExtensionFeatureMarkdownRenderer { + + readonly type = 'markdown'; + + constructor( + @IExtensionFeaturesManagementService private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService, + ) { + super(); + } + + shouldRender(manifest: IExtensionManifest): boolean { + const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); + if (!this.extensionFeaturesManagementService.isEnabled(extensionId, CHAT_FEATURE_ID)) { + return true; + } + const accessData = this.extensionFeaturesManagementService.getAccessData(extensionId, CHAT_FEATURE_ID); + if (accessData?.totalCount) { + return true; + } + return false; + } + + render(manifest: IExtensionManifest): IRenderedData { + const disposables = new DisposableStore(); + const emitter = disposables.add(new Emitter()); + const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); + disposables.add(this.extensionFeaturesManagementService.onDidChangeAccessData(e => { + if (ExtensionIdentifier.equals(e.extension, extensionId) && e.featureId === CHAT_FEATURE_ID) { + emitter.fire(this.getMarkdownData(extensionId)); + } + })); + return { + data: this.getMarkdownData(extensionId), + onDidChange: emitter.event, + dispose: () => { disposables.dispose(); } + }; + } + + private getMarkdownData(extensionId: ExtensionIdentifier): IMarkdownString { + const markdown = new MarkdownString(); + const accessData = this.extensionFeaturesManagementService.getAccessData(extensionId, CHAT_FEATURE_ID); + if (accessData && accessData.totalCount) { + if (accessData.current) { + markdown.appendMarkdown(nls.localize('requests count session', "Requests (Session) : `{0}`", accessData.current.count)); + markdown.appendText('\n'); + } + markdown.appendMarkdown(nls.localize('requests count total', "Requests (Overall): `{0}`", accessData.totalCount)); + } + return markdown; + } +} + +Registry.as(ExtensionFeaturesExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: CHAT_FEATURE_ID, + label: nls.localize('chat', "Chat"), + description: nls.localize('chatFeatureDescription', "Allows the extension to make requests to the Large Language Model (LLM)."), + enablement: { + canToggle: true, + requireUserConsent: true, + }, + renderer: new SyncDescriptor(ChatFeatureMarkdowneRenderer), +}); + const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); registerWorkbenchContribution2(ChatResolverContribution.ID, ChatResolverContribution, WorkbenchPhase.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(ChatAccessibleViewContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 9a9c1196e72..5b4e36308a7 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -311,3 +311,4 @@ export interface IChatService { } export const KEYWORD_ACTIVIATION_SETTING_ID = 'accessibility.voice.keywordActivation'; +export const CHAT_FEATURE_ID = 'chat'; diff --git a/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts b/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts index 76a39acf65a..a1d1c4688bb 100644 --- a/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts @@ -6,6 +6,11 @@ import * as nls from 'vs/nls'; import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { languagesExtPoint } from 'vs/workbench/services/language/common/languageService'; +import { Extensions as ExtensionFeaturesExtensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, IRenderedData, IRowData, ITableData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { Registry } from 'vs/platform/registry/common/platform'; enum CodeActionExtensionPointFields { languages = 'languages', @@ -65,3 +70,57 @@ export const codeActionsExtensionPointDescriptor = { deps: [languagesExtPoint], jsonSchema: codeActionsExtensionPointSchema }; + +class CodeActionsTableRenderer extends Disposable implements IExtensionFeatureTableRenderer { + + readonly type = 'table'; + + shouldRender(manifest: IExtensionManifest): boolean { + return !!manifest.contributes?.codeActions; + } + + render(manifest: IExtensionManifest): IRenderedData { + const codeActions = manifest.contributes?.codeActions || []; + if (!codeActions.length) { + return { data: { headers: [], rows: [] }, dispose: () => { } }; + } + + const flatActions = + codeActions.map(contribution => + contribution.actions.map(action => ({ ...action, languages: contribution.languages }))).flat(); + + const headers = [ + nls.localize('codeActions.title', "Title"), + nls.localize('codeActions.kind', "Kind"), + nls.localize('codeActions.description', "Description"), + nls.localize('codeActions.languages', "Languages") + ]; + + const rows: IRowData[][] = flatActions.sort((a, b) => a.title.localeCompare(b.title)) + .map(action => { + return [ + action.title, + { data: action.kind, type: 'code' }, + action.description ?? '', + { data: [...action.languages], type: 'code' }, + ]; + }); + + return { + data: { + headers, + rows + }, + dispose: () => { } + }; + } +} + +Registry.as(ExtensionFeaturesExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'codeActions', + label: nls.localize('codeactions', "Code Actions"), + enablement: { + canToggle: false, + }, + renderer: new SyncDescriptor(CodeActionsTableRenderer), +}); diff --git a/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts b/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts index 7c6fac24e1f..22719e22732 100644 --- a/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts +++ b/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts @@ -3,9 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { coalesce } from 'vs/base/common/arrays'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { Disposable } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; +import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { Registry } from 'vs/platform/registry/common/platform'; import { CustomEditorPriority, CustomEditorSelector } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { Extensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, IRenderedData, IRowData, ITableData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { languagesExtPoint } from 'vs/workbench/services/language/common/languageService'; @@ -99,3 +105,51 @@ export const customEditorsExtensionPoint = ExtensionsRegistry.registerExtensionP } }, }); + +class CustomEditorsDataRenderer extends Disposable implements IExtensionFeatureTableRenderer { + + readonly type = 'table'; + + shouldRender(manifest: IExtensionManifest): boolean { + return !!manifest.contributes?.customEditors; + } + + render(manifest: IExtensionManifest): IRenderedData { + const customEditors = manifest.contributes?.customEditors || []; + if (!customEditors.length) { + return { data: { headers: [], rows: [] }, dispose: () => { } }; + } + + const headers = [ + nls.localize('customEditors view type', "View Type"), + nls.localize('customEditors priority', "Priority"), + nls.localize('customEditors filenamePattern', "Filename Pattern"), + ]; + + const rows: IRowData[][] = customEditors + .map(customEditor => { + return [ + customEditor.viewType, + customEditor.priority ?? '', + coalesce(customEditor.selector.map(x => x.filenamePattern)).join(', ') + ]; + }); + + return { + data: { + headers, + rows + }, + dispose: () => { } + }; + } +} + +Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'customEditors', + label: nls.localize('customEditors', "Custom Editors"), + enablement: { + canToggle: false + }, + renderer: new SyncDescriptor(CustomEditorsDataRenderer), +}); diff --git a/src/vs/workbench/contrib/debug/common/debugSchemas.ts b/src/vs/workbench/contrib/debug/common/debugSchemas.ts index ac868c2e5ca..bf5341f8cd2 100644 --- a/src/vs/workbench/contrib/debug/common/debugSchemas.ts +++ b/src/vs/workbench/contrib/debug/common/debugSchemas.ts @@ -9,6 +9,11 @@ import { IDebuggerContribution, ICompound, IBreakpointContribution } from 'vs/wo import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { inputsSchema } from 'vs/workbench/services/configurationResolver/common/configurationResolverSchema'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Extensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, IRenderedData, IRowData, ITableData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { Registry } from 'vs/platform/registry/common/platform'; // debuggers extension point export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ @@ -261,3 +266,49 @@ export const launchSchema: IJSONSchema = { inputs: inputsSchema.definitions!.inputs } }; + +class DebuggersDataRenderer extends Disposable implements IExtensionFeatureTableRenderer { + + readonly type = 'table'; + + shouldRender(manifest: IExtensionManifest): boolean { + return !!manifest.contributes?.debuggers; + } + + render(manifest: IExtensionManifest): IRenderedData { + const contrib = manifest.contributes?.debuggers || []; + if (!contrib.length) { + return { data: { headers: [], rows: [] }, dispose: () => { } }; + } + + const headers = [ + nls.localize('debugger name', "Name"), + nls.localize('debugger type', "Type"), + ]; + + const rows: IRowData[][] = contrib.map(d => { + return [ + d.label ?? '', + d.type + ]; + }); + + return { + data: { + headers, + rows + }, + dispose: () => { } + }; + } +} + +Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'debuggers', + label: nls.localize('debuggers', "Debuggers"), + enablement: { + canToggle: false + }, + renderer: new SyncDescriptor(DebuggersDataRenderer), +}); + diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 8b49ea2903f..45f5217c9f5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -3,27 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, Dimension, addDisposableListener, append, join, reset, setParentFlowTo } from 'vs/base/browser/dom'; -import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; +import { $, Dimension, addDisposableListener, append, setParentFlowTo } from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { CheckboxActionViewItem } from 'vs/base/browser/ui/toggle/toggle'; import { Action, IAction } from 'vs/base/common/actions'; import * as arrays from 'vs/base/common/arrays'; import { Cache, CacheResult } from 'vs/base/common/cache'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { Color } from 'vs/base/common/color'; -import { getErrorMessage, isCancellationError, onUnexpectedError } from 'vs/base/common/errors'; +import { isCancellationError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { Disposable, DisposableStore, MutableDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { Schemas, matchesScheme } from 'vs/base/common/network'; -import { OS, language } from 'vs/base/common/platform'; -import { platform } from 'vs/base/common/process'; +import { language } from 'vs/base/common/platform'; import * as semver from 'vs/base/common/semver/semver'; -import { ThemeIcon } from 'vs/base/common/themables'; import { isUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; @@ -34,25 +28,24 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { generateTokensCSSForColorMap } from 'vs/editor/common/languages/supports/tokenization'; import { localize } from 'vs/nls'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; -import { getDefaultValue } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr, IContextKey, IContextKeyService, IScopedContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IExtensionGalleryService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { ExtensionIdentifier, IExtensionManifest, IKeyBinding, IView, IViewContainer } from 'vs/platform/extensions/common/extensions'; +import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { defaultCheckboxStyles, defaultKeybindingLabelStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultCheckboxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { buttonForeground, buttonHoverBackground, editorBackground, textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorOpenContext } from 'vs/workbench/common/editor'; import { ViewContainerLocation } from 'vs/workbench/common/views'; +import { ExtensionFeaturesTab } from 'vs/workbench/contrib/extensions/browser/extensionFeaturesTab'; import { ActionWithDropDownAction, ClearLanguageAction, @@ -78,7 +71,6 @@ import { WebInstallAction, TogglePreReleaseExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; -import { errorIcon, infoIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { Delegate } from 'vs/workbench/contrib/extensions/browser/extensionsList'; import { ExtensionData, ExtensionsGridView, ExtensionsTree, getExtensions } from 'vs/workbench/contrib/extensions/browser/extensionsViewer'; import { ExtensionRecommendationWidget, ExtensionStatusWidget, ExtensionWidget, InstallCountWidget, RatingsWidget, RemoteBadgeWidget, SponsorWidget, VerifiedPublisherWidget, onClick } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; @@ -239,7 +231,6 @@ export class ExtensionEditor extends EditorPane { @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IThemeService themeService: IThemeService, - @IKeybindingService private readonly keybindingService: IKeybindingService, @INotificationService private readonly notificationService: INotificationService, @IOpenerService private readonly openerService: IOpenerService, @IExtensionRecommendationsService private readonly extensionRecommendationsService: IExtensionRecommendationsService, @@ -576,8 +567,8 @@ export class ExtensionEditor extends EditorPane { } template.navbar.push(ExtensionEditorTab.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file")); - if (manifest && manifest.contributes) { - template.navbar.push(ExtensionEditorTab.Contributions, localize('contributions', "Feature Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension")); + if (manifest) { + template.navbar.push(ExtensionEditorTab.Features, localize('features', "Features"), localize('featurestooltip', "Lists features contributed by this extension")); } if (extension.hasChangelog()) { template.navbar.push(ExtensionEditorTab.Changelog, localize('changelog', "Changelog"), localize('changelogtooltip', "Extension update history, rendered from the extension's 'CHANGELOG.md' file")); @@ -589,18 +580,9 @@ export class ExtensionEditor extends EditorPane { template.navbar.push(ExtensionEditorTab.ExtensionPack, localize('extensionpack', "Extension Pack"), localize('extensionpacktooltip', "Lists extensions those will be installed together with this extension")); } - const addRuntimeStatusSection = () => template.navbar.push(ExtensionEditorTab.RuntimeStatus, localize('runtimeStatus', "Runtime Status"), localize('runtimeStatus description', "Extension runtime status")); - if (this.extensionsWorkbenchService.getExtensionStatus(extension)) { - addRuntimeStatusSection(); - } else { - const disposable = this.extensionService.onDidChangeExtensionsStatus(e => { - if (e.some(extensionIdentifier => areSameExtensions({ id: extensionIdentifier.value }, extension.identifier))) { - addRuntimeStatusSection(); - disposable.dispose(); - } - }, this, this.transientDisposables); + if ((this.options).tab) { + template.navbar.switch((this.options).tab!); } - if (template.navbar.currentId) { this.onNavbarChange(extension, { id: template.navbar.currentId, focus: !preserveFocus }, template); } @@ -657,11 +639,10 @@ export class ExtensionEditor extends EditorPane { private open(id: string, extension: IExtension, template: IExtensionEditorTemplate, token: CancellationToken): Promise { switch (id) { case ExtensionEditorTab.Readme: return this.openDetails(extension, template, token); - case ExtensionEditorTab.Contributions: return this.openContributions(template, token); + case ExtensionEditorTab.Features: return this.openFeatures(template, token); case ExtensionEditorTab.Changelog: return this.openChangelog(template, token); case ExtensionEditorTab.Dependencies: return this.openExtensionDependencies(extension, template, token); case ExtensionEditorTab.ExtensionPack: return this.openExtensionPack(extension, template, token); - case ExtensionEditorTab.RuntimeStatus: return this.openRuntimeStatus(extension, template, token); } return Promise.resolve(null); } @@ -976,65 +957,22 @@ export class ExtensionEditor extends EditorPane { return this.openMarkdown(this.extensionChangelog!.get(), localize('noChangelog', "No Changelog available."), template.content, WebviewIndex.Changelog, localize('Changelog title', "Changelog"), token); } - private openContributions(template: IExtensionEditorTemplate, token: CancellationToken): Promise { - const content = $('div.subcontent.feature-contributions', { tabindex: '0' }); - return this.loadContents(() => this.extensionManifest!.get(), template.content) - .then(manifest => { - if (token.isCancellationRequested) { - return null; - } + private async openFeatures(template: IExtensionEditorTemplate, token: CancellationToken): Promise { + const manifest = await this.loadContents(() => this.extensionManifest!.get(), template.content); + if (token.isCancellationRequested) { + return null; + } + if (!manifest) { + return null; + } - if (!manifest) { - return content; - } - - const scrollableContent = new DomScrollableElement(content, {}); - - const layout = () => scrollableContent.scanDomNode(); - const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout }); - this.contentDisposables.add(toDisposable(removeLayoutParticipant)); - - const renders = [ - this.renderSettings(content, manifest, layout), - this.renderCommands(content, manifest, layout), - this.renderCodeActions(content, manifest, layout), - this.renderLanguages(content, manifest, layout), - this.renderColorThemes(content, manifest, layout), - this.renderIconThemes(content, manifest, layout), - this.renderProductIconThemes(content, manifest, layout), - this.renderColors(content, manifest, layout), - this.renderJSONValidation(content, manifest, layout), - this.renderDebuggers(content, manifest, layout), - this.renderViewContainers(content, manifest, layout), - this.renderViews(content, manifest, layout), - this.renderLocalizations(content, manifest, layout), - this.renderCustomEditors(content, manifest, layout), - this.renderNotebooks(content, manifest, layout), - this.renderNotebookRenderers(content, manifest, layout), - this.renderAuthentication(content, manifest, layout), - this.renderActivationEvents(content, manifest, layout), - ]; - - scrollableContent.scanDomNode(); - - const isEmpty = !renders.some(x => x); - if (isEmpty) { - append(content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions"); - append(template.content, content); - } else { - append(template.content, scrollableContent.getDomNode()); - this.contentDisposables.add(scrollableContent); - } - return content; - }, () => { - if (token.isCancellationRequested) { - return null; - } - - append(content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions"); - append(template.content, content); - return content; - }); + const extensionFeaturesTab = this.contentDisposables.add(this.instantiationService.createInstance(ExtensionFeaturesTab, manifest, (this.options).feature)); + const layout = () => extensionFeaturesTab.layout(template.content.clientHeight, template.content.clientWidth); + const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout }); + this.contentDisposables.add(toDisposable(removeLayoutParticipant)); + append(template.content, extensionFeaturesTab.domNode); + layout(); + return extensionFeaturesTab.domNode; } private openExtensionDependencies(extension: IExtension, template: IExtensionEditorTemplate, token: CancellationToken): Promise { @@ -1084,91 +1022,6 @@ export class ExtensionEditor extends EditorPane { return this.renderExtensionPack(manifest, template.content, token); } - private async openRuntimeStatus(extension: IExtension, template: IExtensionEditorTemplate, token: CancellationToken): Promise { - const content = $('div', { class: 'subcontent', tabindex: '0' }); - - const scrollableContent = new DomScrollableElement(content, {}); - const layout = () => scrollableContent.scanDomNode(); - const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout }); - this.contentDisposables.add(toDisposable(removeLayoutParticipant)); - - const updateContent = () => { - scrollableContent.scanDomNode(); - reset(content, this.renderRuntimeStatus(extension, layout)); - }; - - updateContent(); - this.extensionService.onDidChangeExtensionsStatus(e => { - if (e.some(extensionIdentifier => areSameExtensions({ id: extensionIdentifier.value }, extension.identifier))) { - updateContent(); - } - }, this, this.contentDisposables); - - this.contentDisposables.add(scrollableContent); - append(template.content, scrollableContent.getDomNode()); - return content; - } - - private renderRuntimeStatus(extension: IExtension, onDetailsToggle: Function): HTMLElement { - const extensionStatus = this.extensionsWorkbenchService.getExtensionStatus(extension); - const element = $('.runtime-status'); - - if (extensionStatus?.activationTimes) { - const activationTime = extensionStatus.activationTimes.codeLoadingTime + extensionStatus.activationTimes.activateCallTime; - const activationElement = append(element, $('div.activation-details')); - - const activationReasonElement = append(activationElement, $('div.activation-element-entry')); - append(activationReasonElement, $('span.activation-message-title', undefined, localize('activation reason', "Activation Event:"))); - append(activationReasonElement, $('code', undefined, extensionStatus.activationTimes.activationReason.startup ? localize('startup', "Startup") : extensionStatus.activationTimes.activationReason.activationEvent)); - - const activationTimeElement = append(activationElement, $('div.activation-element-entry')); - append(activationTimeElement, $('span.activation-message-title', undefined, localize('activation time', "Activation Time:"))); - append(activationTimeElement, $('code', undefined, `${activationTime}ms`)); - - - if (ExtensionIdentifier.toKey(extensionStatus.activationTimes.activationReason.extensionId) !== ExtensionIdentifier.toKey(extension.identifier.id)) { - const activatedByElement = append(activationElement, $('div.activation-element-entry')); - append(activatedByElement, $('span.activation-message-title', undefined, localize('activatedBy', "Activated By:"))); - append(activatedByElement, $('span', undefined, extensionStatus.activationTimes.activationReason.extensionId.value)); - } - } - - else if (extension.local && (extension.local.manifest.main || extension.local.manifest.browser)) { - append(element, $('div.activation-message', undefined, localize('not yet activated', "Not yet activated."))); - } - - if (extensionStatus?.runtimeErrors.length) { - append(element, $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('uncaught errors', "Uncaught Errors ({0})", extensionStatus.runtimeErrors.length)), - $('div', undefined, - ...extensionStatus.runtimeErrors.map(error => $('div.message-entry', undefined, - $(`span${ThemeIcon.asCSSSelector(errorIcon)}`, undefined), - $('span', undefined, getErrorMessage(error)), - )) - ), - )); - } - - if (extensionStatus?.messages.length) { - append(element, $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('messages', "Messages ({0})", extensionStatus?.messages.length)), - $('div', undefined, - ...extensionStatus.messages.sort((a, b) => b.type - a.type) - .map(message => $('div.message-entry', undefined, - $(`span${ThemeIcon.asCSSSelector(message.type === Severity.Error ? errorIcon : message.type === Severity.Warning ? warningIcon : infoIcon)}`, undefined), - $('span', undefined, message.message) - )) - ), - )); - } - - if (element.children.length === 0) { - append(element, $('div.no-status-message')).textContent = localize('noStatus', "No status available."); - } - - return element; - } - private async renderExtensionPack(manifest: IExtensionManifest, parent: HTMLElement, token: CancellationToken): Promise { if (token.isCancellationRequested) { return null; @@ -1190,598 +1043,6 @@ export class ExtensionEditor extends EditorPane { return content; } - private renderSettings(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const configuration = manifest.contributes?.configuration; - let properties: any = {}; - if (Array.isArray(configuration)) { - configuration.forEach(config => { - properties = { ...properties, ...config.properties }; - }); - } else if (configuration) { - properties = configuration.properties; - } - - let contrib = properties ? Object.keys(properties) : []; - - // filter deprecated settings - contrib = contrib.filter(key => { - const config = properties[key]; - return !config.deprecationMessage && !config.markdownDeprecationMessage; - }); - - if (!contrib.length) { - return false; - } - - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('settings', "Settings ({0})", contrib.length)), - $('table', undefined, - $('tr', undefined, - $('th', undefined, localize('setting name', "ID")), - $('th', undefined, localize('description', "Description")), - $('th', undefined, localize('default', "Default")) - ), - ...contrib - .sort((a, b) => a.localeCompare(b)) - .map(key => { - let description: (Node | string) = properties[key].description || ''; - if (properties[key].markdownDescription) { - const { element, dispose } = renderMarkdown({ value: properties[key].markdownDescription }, { actionHandler: { callback: (content) => this.openerService.open(content).catch(onUnexpectedError), disposables: this.contentDisposables } }); - description = element; - this.contentDisposables.add(toDisposable(dispose)); - } - return $('tr', undefined, - $('td', undefined, $('code', undefined, key)), - $('td', undefined, description), - $('td', undefined, $('code', undefined, `${isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : JSON.stringify(properties[key].default)}`))); - }) - ) - ); - - append(container, details); - return true; - } - - private renderDebuggers(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contrib = manifest.contributes?.debuggers || []; - if (!contrib.length) { - return false; - } - - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('debuggers', "Debuggers ({0})", contrib.length)), - $('table', undefined, - $('tr', undefined, - $('th', undefined, localize('debugger name', "Name")), - $('th', undefined, localize('debugger type', "Type")), - ), - ...contrib - .sort((a, b) => a.label!.localeCompare(b.label!)) - .map(d => $('tr', undefined, - $('td', undefined, d.label!), - $('td', undefined, d.type))) - ) - ); - - append(container, details); - return true; - } - - private renderViewContainers(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contrib = manifest.contributes?.viewsContainers || {}; - - const viewContainers = Object.keys(contrib).reduce((result, location) => { - const viewContainersForLocation: IViewContainer[] = contrib[location]; - result.push(...viewContainersForLocation.map(viewContainer => ({ ...viewContainer, location }))); - return result; - }, [] as Array<{ id: string; title: string; location: string }>); - - if (!viewContainers.length) { - return false; - } - - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('viewContainers', "View Containers ({0})", viewContainers.length)), - $('table', undefined, - $('tr', undefined, $('th', undefined, localize('view container id', "ID")), $('th', undefined, localize('view container title', "Title")), $('th', undefined, localize('view container location', "Where"))), - ...viewContainers - .sort((a, b) => a.id.localeCompare(b.id)) - .map(viewContainer => $('tr', undefined, $('td', undefined, viewContainer.id), $('td', undefined, viewContainer.title), $('td', undefined, viewContainer.location))) - ) - ); - - append(container, details); - return true; - } - - private renderViews(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contrib = manifest.contributes?.views || {}; - - const views = Object.keys(contrib).reduce((result, location) => { - const viewsForLocation: IView[] = contrib[location]; - result.push(...viewsForLocation.map(view => ({ ...view, location }))); - return result; - }, [] as Array<{ id: string; name: string; location: string }>); - - if (!views.length) { - return false; - } - - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('views', "Views ({0})", views.length)), - $('table', undefined, - $('tr', undefined, $('th', undefined, localize('view id', "ID")), $('th', undefined, localize('view name', "Name")), $('th', undefined, localize('view location', "Where"))), - ...views - .sort((a, b) => a.id.localeCompare(b.id)) - .map(view => $('tr', undefined, $('td', undefined, view.id), $('td', undefined, view.name), $('td', undefined, view.location))) - ) - ); - - append(container, details); - return true; - } - - private renderLocalizations(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const localizations = manifest.contributes?.localizations || []; - if (!localizations.length) { - return false; - } - - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('localizations', "Localizations ({0})", localizations.length)), - $('table', undefined, - $('tr', undefined, $('th', undefined, localize('localizations language id', "Language ID")), $('th', undefined, localize('localizations language name', "Language Name")), $('th', undefined, localize('localizations localized language name', "Language Name (Localized)"))), - ...localizations - .sort((a, b) => a.languageId.localeCompare(b.languageId)) - .map(localization => $('tr', undefined, $('td', undefined, localization.languageId), $('td', undefined, localization.languageName || ''), $('td', undefined, localization.localizedLanguageName || ''))) - ) - ); - - append(container, details); - return true; - } - - private renderCustomEditors(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const webviewEditors = manifest.contributes?.customEditors || []; - if (!webviewEditors.length) { - return false; - } - const renderEditors = Array.from(webviewEditors).sort((a, b) => a.viewType.localeCompare(b.viewType)); - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('customEditors', "Custom Editors ({0})", renderEditors.length)), - $('table', undefined, - $('tr', undefined, - $('th', undefined, localize('customEditors view type', "View Type")), - $('th', undefined, localize('customEditors priority', "Priority")), - $('th', undefined, localize('customEditors filenamePattern', "Filename Pattern"))), - ...renderEditors.map(webviewEditor => - $('tr', undefined, - $('td', undefined, webviewEditor.viewType), - $('td', undefined, webviewEditor.priority), - $('td', undefined, arrays.coalesce(webviewEditor.selector.map(x => x.filenamePattern)).join(', ')))) - ) - ); - - append(container, details); - return true; - } - - private renderCodeActions(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const codeActions = manifest.contributes?.codeActions || []; - if (!codeActions.length) { - return false; - } - - const flatActions = arrays.flatten( - codeActions.map(contribution => - contribution.actions.map(action => ({ ...action, languages: contribution.languages })))); - - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('codeActions', "Code Actions ({0})", flatActions.length)), - $('table', undefined, - $('tr', undefined, - $('th', undefined, localize('codeActions.title', "Title")), - $('th', undefined, localize('codeActions.kind', "Kind")), - $('th', undefined, localize('codeActions.description', "Description")), - $('th', undefined, localize('codeActions.languages', "Languages"))), - ...flatActions - .sort((a, b) => a.title.localeCompare(b.title)) - .map(action => - $('tr', undefined, - $('td', undefined, action.title), - $('td', undefined, $('code', undefined, action.kind)), - $('td', undefined, action.description ?? ''), - $('td', undefined, ...action.languages.map(language => $('code', undefined, language))))) - ) - ); - - append(container, details); - return true; - } - - private renderAuthentication(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const authentication = manifest.contributes?.authentication || []; - if (!authentication.length) { - return false; - } - - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('authentication', "Authentication ({0})", authentication.length)), - $('table', undefined, - $('tr', undefined, - $('th', undefined, localize('authentication.label', "Label")), - $('th', undefined, localize('authentication.id', "ID")) - ), - ...authentication - .sort((a, b) => a.label.localeCompare(b.label)) - .map(action => - $('tr', undefined, - $('td', undefined, action.label), - $('td', undefined, action.id) - ) - ) - ) - ); - - append(container, details); - return true; - } - - private renderColorThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contrib = manifest.contributes?.themes || []; - if (!contrib.length) { - return false; - } - - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('colorThemes', "Color Themes ({0})", contrib.length)), - $('ul', undefined, - ...contrib - .sort((a, b) => a.label.localeCompare(b.label)) - .map(theme => $('li', undefined, theme.label))) - ); - - append(container, details); - return true; - } - - private renderIconThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contrib = manifest.contributes?.iconThemes || []; - if (!contrib.length) { - return false; - } - - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('iconThemes', "File Icon Themes ({0})", contrib.length)), - $('ul', undefined, - ...contrib - .sort((a, b) => a.label.localeCompare(b.label)) - .map(theme => $('li', undefined, theme.label))) - ); - - append(container, details); - return true; - } - - private renderProductIconThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contrib = manifest.contributes?.productIconThemes || []; - if (!contrib.length) { - return false; - } - - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('productThemes', "Product Icon Themes ({0})", contrib.length)), - $('ul', undefined, - ...contrib - .sort((a, b) => a.label.localeCompare(b.label)) - .map(theme => $('li', undefined, theme.label))) - ); - - append(container, details); - return true; - } - - private renderColors(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const colors = manifest.contributes?.colors || []; - if (!colors.length) { - return false; - } - - function colorPreview(colorReference: string): Node[] { - const result: Node[] = []; - if (colorReference && colorReference[0] === '#') { - const color = Color.fromHex(colorReference); - if (color) { - result.push($('span', { class: 'colorBox', style: 'background-color: ' + Color.Format.CSS.format(color) }, '')); - } - } - result.push($('code', undefined, colorReference)); - return result; - } - - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('colors', "Colors ({0})", colors.length)), - $('table', undefined, - $('tr', undefined, - $('th', undefined, localize('colorId', "ID")), - $('th', undefined, localize('description', "Description")), - $('th', undefined, localize('defaultDark', "Dark Default")), - $('th', undefined, localize('defaultLight', "Light Default")), - $('th', undefined, localize('defaultHC', "High Contrast Default")) - ), - ...colors - .sort((a, b) => a.id.localeCompare(b.id)) - .map(color => $('tr', undefined, - $('td', undefined, $('code', undefined, color.id)), - $('td', undefined, color.description), - $('td', undefined, ...colorPreview(color.defaults.dark)), - $('td', undefined, ...colorPreview(color.defaults.light)), - $('td', undefined, ...colorPreview(color.defaults.highContrast)) - )) - ) - ); - - append(container, details); - return true; - } - - - private renderJSONValidation(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contrib = manifest.contributes?.jsonValidation || []; - if (!contrib.length) { - return false; - } - - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('JSON Validation', "JSON Validation ({0})", contrib.length)), - $('table', undefined, - $('tr', undefined, - $('th', undefined, localize('fileMatch', "File Match")), - $('th', undefined, localize('schema', "Schema")) - ), - ...contrib.map(v => $('tr', undefined, - $('td', undefined, $('code', undefined, Array.isArray(v.fileMatch) ? v.fileMatch.join(', ') : v.fileMatch)), - $('td', undefined, v.url) - )))); - - append(container, details); - return true; - } - - private renderCommands(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const rawCommands = manifest.contributes?.commands || []; - const commands = rawCommands.map(c => ({ - id: c.command, - title: c.title, - keybindings: [] as ResolvedKeybinding[], - menus: [] as string[] - })); - - const byId = arrays.index(commands, c => c.id); - - const menus = manifest.contributes?.menus || {}; - - for (const context in menus) { - for (const menu of menus[context]) { - if (menu.command) { - let command = byId[menu.command]; - if (command) { - command.menus.push(context); - } else { - command = { id: menu.command, title: '', keybindings: [], menus: [context] }; - byId[command.id] = command; - commands.push(command); - } - } - } - } - - const rawKeybindings = manifest.contributes?.keybindings ? (Array.isArray(manifest.contributes.keybindings) ? manifest.contributes.keybindings : [manifest.contributes.keybindings]) : []; - - rawKeybindings.forEach(rawKeybinding => { - const keybinding = this.resolveKeybinding(rawKeybinding); - - if (!keybinding) { - return; - } - - let command = byId[rawKeybinding.command]; - - if (command) { - command.keybindings.push(keybinding); - } else { - command = { id: rawKeybinding.command, title: '', keybindings: [keybinding], menus: [] }; - byId[command.id] = command; - commands.push(command); - } - }); - - if (!commands.length) { - return false; - } - - const renderKeybinding = (keybinding: ResolvedKeybinding): HTMLElement => { - const element = $(''); - const kbl = new KeybindingLabel(element, OS, defaultKeybindingLabelStyles); - kbl.set(keybinding); - return element; - }; - - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('commands', "Commands ({0})", commands.length)), - $('table', undefined, - $('tr', undefined, - $('th', undefined, localize('command name', "ID")), - $('th', undefined, localize('command title', "Title")), - $('th', undefined, localize('keyboard shortcuts', "Keyboard Shortcuts")), - $('th', undefined, localize('menuContexts', "Menu Contexts")) - ), - ...commands - .sort((a, b) => a.id.localeCompare(b.id)) - .map(c => $('tr', undefined, - $('td', undefined, $('code', undefined, c.id)), - $('td', undefined, typeof c.title === 'string' ? c.title : c.title.value), - $('td', undefined, ...c.keybindings.map(keybinding => renderKeybinding(keybinding))), - $('td', undefined, ...c.menus.map(context => $('code', undefined, context))) - )) - ) - ); - - append(container, details); - return true; - } - - private renderLanguages(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const rawLanguages = contributes?.languages || []; - const languages = rawLanguages.map(l => ({ - id: l.id, - name: (l.aliases || [])[0] || l.id, - extensions: l.extensions || [], - hasGrammar: false, - hasSnippets: false - })); - - const byId = arrays.index(languages, l => l.id); - - const grammars = contributes?.grammars || []; - grammars.forEach(grammar => { - let language = byId[grammar.language]; - - if (language) { - language.hasGrammar = true; - } else { - language = { id: grammar.language, name: grammar.language, extensions: [], hasGrammar: true, hasSnippets: false }; - byId[language.id] = language; - languages.push(language); - } - }); - - const snippets = contributes?.snippets || []; - snippets.forEach(snippet => { - let language = byId[snippet.language]; - - if (language) { - language.hasSnippets = true; - } else { - language = { id: snippet.language, name: snippet.language, extensions: [], hasGrammar: false, hasSnippets: true }; - byId[language.id] = language; - languages.push(language); - } - }); - - if (!languages.length) { - return false; - } - - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('languages', "Languages ({0})", languages.length)), - $('table', undefined, - $('tr', undefined, - $('th', undefined, localize('language id', "ID")), - $('th', undefined, localize('language name', "Name")), - $('th', undefined, localize('file extensions', "File Extensions")), - $('th', undefined, localize('grammar', "Grammar")), - $('th', undefined, localize('snippets', "Snippets")) - ), - ...languages - .sort((a, b) => a.id.localeCompare(b.id)) - .map(l => $('tr', undefined, - $('td', undefined, l.id), - $('td', undefined, l.name), - $('td', undefined, ...join(l.extensions.map(ext => $('code', undefined, ext)), ' ')), - $('td', undefined, document.createTextNode(l.hasGrammar ? '✔︎' : '\u2014')), - $('td', undefined, document.createTextNode(l.hasSnippets ? '✔︎' : '\u2014')) - )) - ) - ); - - append(container, details); - return true; - } - - private renderActivationEvents(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const activationEvents = manifest.activationEvents || []; - if (!activationEvents.length) { - return false; - } - - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('activation events', "Activation Events ({0})", activationEvents.length)), - $('ul', undefined, - ...activationEvents - .sort((a, b) => a.localeCompare(b)) - .map(activationEvent => $('li', undefined, $('code', undefined, activationEvent)))) - ); - - append(container, details); - return true; - } - - private renderNotebooks(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contrib = manifest.contributes?.notebooks || []; - - if (!contrib.length) { - return false; - } - - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('Notebooks', "Notebooks ({0})", contrib.length)), - $('table', undefined, - $('tr', undefined, - $('th', undefined, localize('Notebook id', "ID")), - $('th', undefined, localize('Notebook name', "Name")), - ), - ...contrib - .sort((a, b) => a.type.localeCompare(b.type)) - .map(d => $('tr', undefined, - $('td', undefined, d.type), - $('td', undefined, d.displayName))) - ) - ); - - append(container, details); - return true; - } - - private renderNotebookRenderers(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contrib = manifest.contributes?.notebookRenderer || []; - - if (!contrib.length) { - return false; - } - - const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('NotebookRenderers', "Notebook Renderers ({0})", contrib.length)), - $('table', undefined, - $('tr', undefined, - $('th', undefined, localize('Notebook renderer name', "Name")), - $('th', undefined, localize('Notebook mimetypes', "Mimetypes")), - ), - ...contrib - .sort((a, b) => a.displayName.localeCompare(b.displayName)) - .map(d => $('tr', undefined, - $('td', undefined, d.displayName), - $('td', undefined, d.mimeTypes.join(',')))) - ) - ); - - append(container, details); - return true; - } - - private resolveKeybinding(rawKeyBinding: IKeyBinding): ResolvedKeybinding | null { - let key: string | undefined; - - switch (platform) { - case 'win32': key = rawKeyBinding.win; break; - case 'linux': key = rawKeyBinding.linux; break; - case 'darwin': key = rawKeyBinding.mac; break; - } - - return this.keybindingService.resolveUserBinding(key || rawKeyBinding.key)[0]; - } - private loadContents(loadingTask: () => CacheResult, container: HTMLElement): Promise { container.classList.add('loading'); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts new file mode 100644 index 00000000000..fd893b1bc02 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts @@ -0,0 +1,426 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { $, append, clearNode } from 'vs/base/browser/dom'; +import { Event } from 'vs/base/common/event'; +import { ExtensionIdentifier, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { Orientation, Sizing, SplitView } from 'vs/base/browser/ui/splitview/splitview'; +import { IExtensionFeatureDescriptor, Extensions, IExtensionFeaturesRegistry, IExtensionFeatureRenderer, IExtensionFeaturesManagementService, IExtensionFeatureTableRenderer, IExtensionFeatureMarkdownRenderer, ITableData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { localize } from 'vs/nls'; +import { WorkbenchList } from 'vs/platform/list/browser/listService'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { Button } from 'vs/base/browser/ui/button/button'; +import { defaultButtonStyles, defaultKeybindingLabelStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { PANEL_SECTION_BORDER } from 'vs/workbench/common/theme'; +import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; +import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { ThemeIcon } from 'vs/base/common/themables'; +import Severity from 'vs/base/common/severity'; +import { errorIcon, infoIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; +import { SeverityIcon } from 'vs/platform/severityIcon/browser/severityIcon'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; +import { OS } from 'vs/base/common/platform'; +import { IMarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; +import { Color } from 'vs/base/common/color'; + +interface ILayoutParticipant { + layout(height?: number, width?: number): void; +} + +export class ExtensionFeaturesTab extends Themable { + + readonly domNode: HTMLElement; + + private readonly featureView = this._register(new MutableDisposable()); + private featureViewDimension?: { height?: number; width?: number }; + + private readonly layoutParticipants: ILayoutParticipant[] = []; + private readonly extensionId: ExtensionIdentifier; + + constructor( + private readonly manifest: IExtensionManifest, + private readonly feature: string | undefined, + @IThemeService themeService: IThemeService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(themeService); + + this.extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); + this.domNode = $('div.subcontent.feature-contributions'); + this.create(); + } + + layout(height?: number, width?: number): void { + this.layoutParticipants.forEach(participant => participant.layout(height, width)); + } + + private create(): void { + const features = this.getFeatures(); + if (features.length === 0) { + append($('.no-features'), this.domNode).textContent = localize('noFeatures', "No features contributed."); + return; + } + + const splitView = new SplitView(this.domNode, { + orientation: Orientation.HORIZONTAL, + proportionalLayout: true + }); + this.layoutParticipants.push({ + layout: (height: number, width: number) => { + splitView.el.style.height = `${height - 14}px`; + splitView.layout(width); + } + }); + + const featuresListContainer = $('.features-list-container'); + const list = this.createFeaturesList(featuresListContainer); + list.splice(0, list.length, features); + + const featureViewContainer = $('.feature-view-container'); + this._register(list.onDidChangeSelection(e => { + const feature = e.elements[0]; + if (feature) { + this.showFeatureView(feature, featureViewContainer); + } + })); + + const index = this.feature ? features.findIndex(f => f.id === this.feature) : 0; + list.setSelection([index === -1 ? 0 : index]); + + splitView.addView({ + onDidChange: Event.None, + element: featuresListContainer, + minimumSize: 100, + maximumSize: Number.POSITIVE_INFINITY, + layout: (width, _, height) => { + featuresListContainer.style.width = `${width}px`; + list.layout(height, width); + } + }, 200, undefined, true); + + splitView.addView({ + onDidChange: Event.None, + element: featureViewContainer, + minimumSize: 500, + maximumSize: Number.POSITIVE_INFINITY, + layout: (width, _, height) => { + featureViewContainer.style.width = `${width}px`; + this.featureViewDimension = { height, width }; + this.layoutFeatureView(); + } + }, Sizing.Distribute, undefined, true); + + splitView.style({ + separatorBorder: this.theme.getColor(PANEL_SECTION_BORDER)! + }); + } + + private createFeaturesList(container: HTMLElement): WorkbenchList { + const renderer = this.instantiationService.createInstance(ExtensionFeatureItemRenderer, this.extensionId); + const delegate = new ExtensionFeatureItemDelegate(); + const list = this.instantiationService.createInstance(WorkbenchList, 'ExtensionFeaturesList', append(container, $('.features-list-wrapper')), delegate, [renderer], { + multipleSelectionSupport: false, + setRowLineHeight: false, + horizontalScrolling: false, + accessibilityProvider: >{ + getAriaLabel(extensionFeature: IExtensionFeatureDescriptor | null): string { + return extensionFeature?.label ?? ''; + }, + getWidgetAriaLabel(): string { + return localize('extension features list', "Extension Features"); + } + }, + openOnSingleClick: true + }) as WorkbenchList; + return list; + } + + private layoutFeatureView(): void { + this.featureView.value?.layout(this.featureViewDimension?.height, this.featureViewDimension?.width); + } + + private showFeatureView(feature: IExtensionFeatureDescriptor, container: HTMLElement): void { + if (this.featureView.value?.feature.id === feature.id) { + return; + } + clearNode(container); + this.featureView.value = this.instantiationService.createInstance(ExtensionFeatureView, this.extensionId, this.manifest, feature); + container.appendChild(this.featureView.value.domNode); + this.layoutFeatureView(); + } + + private getFeatures(): IExtensionFeatureDescriptor[] { + const features = Registry.as(Extensions.ExtensionFeaturesRegistry).getExtensionFeatures(); + return features.filter(feature => { + const renderer = this.getRenderer(feature); + const shouldRender = renderer.shouldRender(this.manifest); + renderer.dispose(); + return shouldRender; + }).sort((a, b) => a.label.localeCompare(b.label)); + } + + private getRenderer(feature: IExtensionFeatureDescriptor): IExtensionFeatureRenderer { + return this.instantiationService.createInstance(feature.renderer); + } + +} + +interface IExtensionFeatureItemTemplateData { + readonly label: HTMLElement; + readonly disabledElement: HTMLElement; + readonly statusElement: HTMLElement; + readonly disposables: DisposableStore; +} + +class ExtensionFeatureItemDelegate implements IListVirtualDelegate { + getHeight() { return 22; } + getTemplateId() { return 'extensionFeatureDescriptor'; } +} + +class ExtensionFeatureItemRenderer implements IListRenderer { + + readonly templateId = 'extensionFeatureDescriptor'; + + constructor( + private readonly extensionId: ExtensionIdentifier, + @IExtensionFeaturesManagementService private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService + ) { } + + renderTemplate(container: HTMLElement): IExtensionFeatureItemTemplateData { + container.classList.add('extension-feature-list-item'); + const label = append(container, $('.extension-feature-label')); + const disabledElement = append(container, $('.extension-feature-disabled-label')); + disabledElement.textContent = localize('revoked', "No Access"); + const statusElement = append(container, $('.extension-feature-status')); + return { label, disabledElement, statusElement, disposables: new DisposableStore() }; + } + + renderElement(element: IExtensionFeatureDescriptor, index: number, templateData: IExtensionFeatureItemTemplateData) { + templateData.disposables.clear(); + templateData.label.textContent = element.label; + templateData.disabledElement.style.display = this.extensionFeaturesManagementService.isEnabled(this.extensionId, element.id) ? 'none' : 'inherit'; + + templateData.disposables.add(this.extensionFeaturesManagementService.onDidChangeEnablement(({ extension, featureId, enabled }) => { + if (ExtensionIdentifier.equals(extension, this.extensionId) && featureId === element.id) { + templateData.disabledElement.style.display = enabled ? 'none' : 'inherit'; + } + })); + + const statusElementClassName = templateData.statusElement.className; + const updateStatus = () => { + const accessData = this.extensionFeaturesManagementService.getAccessData(this.extensionId, element.id); + if (accessData?.current?.status) { + templateData.statusElement.style.display = 'inherit'; + templateData.statusElement.className = `${statusElementClassName} ${SeverityIcon.className(accessData.current.status.severity)}`; + } else { + templateData.statusElement.style.display = 'none'; + } + }; + updateStatus(); + templateData.disposables.add(this.extensionFeaturesManagementService.onDidChangeAccessData(({ extension, featureId }) => { + if (ExtensionIdentifier.equals(extension, this.extensionId) && featureId === element.id) { + updateStatus(); + } + })); + } + + disposeElement(element: IExtensionFeatureDescriptor, index: number, templateData: IExtensionFeatureItemTemplateData, height: number | undefined): void { + templateData.disposables.dispose(); + } + + disposeTemplate(templateData: IExtensionFeatureItemTemplateData) { + templateData.disposables.dispose(); + } + +} + +class ExtensionFeatureView extends Disposable { + + readonly domNode: HTMLElement; + private readonly layoutParticipants: ILayoutParticipant[] = []; + + constructor( + private readonly extensionId: ExtensionIdentifier, + private readonly manifest: IExtensionManifest, + readonly feature: IExtensionFeatureDescriptor, + @IOpenerService private readonly openerService: IOpenerService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IExtensionFeaturesManagementService private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService, + @IDialogService private readonly dialogService: IDialogService, + @IKeybindingService private readonly keybindingService: IKeybindingService, + ) { + super(); + + this.domNode = $('.extension-feature-content'); + this.create(this.domNode); + } + + private create(content: HTMLElement): void { + const header = append(content, $('.feature-header')); + const title = append(header, $('.feature-title')); + title.textContent = this.feature.label; + + if (this.feature.enablement.canToggle) { + const actionsContainer = append(header, $('.feature-actions')); + const button = new Button(actionsContainer, defaultButtonStyles); + this.updateButtonLabel(button); + this._register(this.extensionFeaturesManagementService.onDidChangeEnablement(({ extension, featureId }) => { + if (ExtensionIdentifier.equals(extension, this.extensionId) && featureId === this.feature.id) { + this.updateButtonLabel(button); + } + })); + this._register(button.onDidClick(async () => { + const enabled = this.extensionFeaturesManagementService.isEnabled(this.extensionId, this.feature.id); + const confirmationResult = await this.dialogService.confirm({ + title: localize('accessExtensionFeature', "Enable '{0}' Feature", this.feature.label), + message: enabled + ? localize('disableAccessExtensionFeatureMessage', "Would you like to revoke '{0}' extension to access '{1}' feature?", this.manifest.displayName ?? this.extensionId.value, this.feature.label) + : localize('enableAccessExtensionFeatureMessage', "Would you like to allow '{0}' extension to access '{1}' feature?", this.manifest.displayName ?? this.extensionId.value, this.feature.label), + custom: true, + primaryButton: enabled ? localize('revoke', "Revoke Access") : localize('grant', "Allow Access"), + cancelButton: localize('cancel', "Cancel"), + }); + if (confirmationResult.confirmed) { + this.extensionFeaturesManagementService.setEnablement(this.extensionId, this.feature.id, !enabled); + } + })); + } + + const body = append(content, $('.feature-body')); + + const bodyContent = $('.feature-body-content'); + const scrollableContent = this._register(new DomScrollableElement(bodyContent, {})); + append(body, scrollableContent.getDomNode()); + this.layoutParticipants.push({ layout: () => scrollableContent.scanDomNode() }); + scrollableContent.scanDomNode(); + + if (this.feature.description) { + const description = append(bodyContent, $('.feature-description')); + description.textContent = this.feature.description; + } + + const accessData = this.extensionFeaturesManagementService.getAccessData(this.extensionId, this.feature.id); + if (accessData?.current?.status) { + append(bodyContent, $('.feature-status', undefined, + $(`span${ThemeIcon.asCSSSelector(accessData.current.status.severity === Severity.Error ? errorIcon : accessData.current.status.severity === Severity.Warning ? warningIcon : infoIcon)}`, undefined), + $('span', undefined, accessData.current.status.message))); + } + + const featureContentElement = append(bodyContent, $('.feature-content')); + const renderer = this.instantiationService.createInstance(this.feature.renderer); + if (renderer.type === 'table') { + this.renderTableData(featureContentElement, renderer); + } else if (renderer.type === 'markdown') { + this.renderMarkdownData(featureContentElement, renderer); + } + } + + private updateButtonLabel(button: Button): void { + button.label = this.extensionFeaturesManagementService.isEnabled(this.extensionId, this.feature.id) ? localize('revoke', "Revoke Access") : localize('enable', "Allow Access"); + } + + private renderTableData(container: HTMLElement, renderer: IExtensionFeatureTableRenderer): void { + const tableData = this._register(renderer.render(this.manifest)); + if (tableData.onDidChange) { + this._register(tableData.onDidChange(data => { + clearNode(container); + this.renderTable(data, container); + })); + } + this.renderTable(tableData.data, container); + } + + private renderTable(tableData: ITableData, container: HTMLElement): void { + append(container, + $('table', undefined, + $('tr', undefined, + ...tableData.headers.map(header => $('th', undefined, header)) + ), + ...tableData.rows + .map(row => { + return $('tr', undefined, + ...row.map(rowData => { + if (typeof rowData === 'string') { + return $('td', undefined, rowData); + } + if (isMarkdownString(rowData)) { + const element = $('td', undefined); + this.renderMarkdown(rowData, element); + return element; + } + const data = Array.isArray(rowData.data) ? rowData.data : [rowData.data]; + if (rowData.type === 'code') { + return $('td', undefined, ...data.map(c => $('code', undefined, c))); + } else if (rowData.type === 'keybinding') { + return $('td', undefined, ...data.map(keybinding => { + const element = $(''); + const kbl = new KeybindingLabel(element, OS, defaultKeybindingLabelStyles); + kbl.set(this.keybindingService.resolveUserBinding(keybinding)[0]); + return element; + })); + } else if (rowData.type === 'color') { + return $('td', undefined, ...data.map(colorReference => { + const result: Node[] = []; + if (colorReference && colorReference[0] === '#') { + const color = Color.fromHex(colorReference); + if (color) { + result.push($('span', { class: 'colorBox', style: 'background-color: ' + Color.Format.CSS.format(color) }, '')); + } + } + result.push($('code', undefined, colorReference)); + return result; + }).flat()); + } else { + return $('td', undefined, rowData.data[0]); + } + }) + ); + }))); + } + + private renderMarkdownData(container: HTMLElement, renderer: IExtensionFeatureMarkdownRenderer): void { + container.classList.add('markdown'); + const markdownData = this._register(renderer.render(this.manifest)); + if (markdownData.onDidChange) { + this._register(markdownData.onDidChange(data => { + clearNode(container); + this.renderMarkdown(data, container); + })); + } + this.renderMarkdown(markdownData.data, container); + } + + private renderMarkdown(markdown: IMarkdownString, container: HTMLElement): void { + const { element, dispose } = renderMarkdown( + { + value: markdown.value, + isTrusted: markdown.isTrusted, + supportThemeIcons: true + }, + { + actionHandler: { + callback: (content) => this.openerService.open(content, { allowCommands: !!markdown.isTrusted }).catch(onUnexpectedError), + disposables: this._store + }, + }); + this._register(toDisposable(dispose)); + append(container, element); + } + + layout(height?: number, width?: number): void { + this.layoutParticipants.forEach(p => p.layout(height, width)); + } + +} diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index aeb392dccf7..c2747ac3285 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -263,26 +263,26 @@ const jsonRegistry = Registr jsonRegistry.registerSchema(ExtensionsConfigurationSchemaId, ExtensionsConfigurationSchema); // Register Commands -CommandsRegistry.registerCommand('_extensions.manage', (accessor: ServicesAccessor, extensionId: string, tab?: ExtensionEditorTab, preserveFocus?: boolean) => { +CommandsRegistry.registerCommand('_extensions.manage', (accessor: ServicesAccessor, extensionId: string, tab?: ExtensionEditorTab, preserveFocus?: boolean, feature?: string) => { const extensionService = accessor.get(IExtensionsWorkbenchService); const extension = extensionService.local.find(e => areSameExtensions(e.identifier, { id: extensionId })); if (extension) { - extensionService.open(extension, { tab, preserveFocus }); + extensionService.open(extension, { tab, preserveFocus, feature }); } else { throw new Error(localize('notFound', "Extension '{0}' not found.", extensionId)); } }); -CommandsRegistry.registerCommand('extension.open', async (accessor: ServicesAccessor, extensionId: string, tab?: ExtensionEditorTab, preserveFocus?: boolean) => { +CommandsRegistry.registerCommand('extension.open', async (accessor: ServicesAccessor, extensionId: string, tab?: ExtensionEditorTab, preserveFocus?: boolean, feature?: string) => { const extensionService = accessor.get(IExtensionsWorkbenchService); const commandService = accessor.get(ICommandService); const [extension] = await extensionService.getExtensions([{ id: extensionId }], CancellationToken.None); if (extension) { - return extensionService.open(extension, { tab, preserveFocus }); + return extensionService.open(extension, { tab, preserveFocus, feature }); } - return commandService.executeCommand('_extensions.manage', extensionId, tab, preserveFocus); + return commandService.executeCommand('_extensions.manage', extensionId, tab, preserveFocus, feature); }); CommandsRegistry.registerCommand({ diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index af3bd7fe006..1fc58a9e0f5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -12,7 +12,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import * as json from 'vs/base/common/json'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { disposeIfDisposable } from 'vs/base/common/lifecycle'; -import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, UPDATE_ACTIONS_GROUP, AutoUpdateConfigurationKey, AutoUpdateConfigurationValue } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, UPDATE_ACTIONS_GROUP, AutoUpdateConfigurationKey, AutoUpdateConfigurationValue, ExtensionEditorTab } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, TargetPlatformToString, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -71,6 +71,8 @@ import { ILocaleService } from 'vs/workbench/services/localization/common/locale import { isString } from 'vs/base/common/types'; import { showWindowLogActionId } from 'vs/workbench/services/log/common/logConstants'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { Registry } from 'vs/platform/registry/common/platform'; export class PromptExtensionInstallFailureAction extends Action { @@ -2284,10 +2286,12 @@ export class ExtensionStatusAction extends ExtensionAction { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IProductService private readonly productService: IProductService, @IWorkbenchExtensionEnablementService private readonly workbenchExtensionEnablementService: IWorkbenchExtensionEnablementService, + @IExtensionFeaturesManagementService private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService, ) { super('extensions.status', '', `${ExtensionStatusAction.CLASS} hide`, false); this._register(this.labelService.onDidChangeFormatters(() => this.update(), this)); this._register(this.extensionService.onDidChangeExtensions(() => this.update())); + this._register(this.extensionFeaturesManagementService.onDidChangeAccessData(() => this.update())); this.update(); } @@ -2435,6 +2439,21 @@ export class ExtensionStatusAction extends ExtensionAction { } } + const extensionId = new ExtensionIdentifier(this.extension.identifier.id); + const features = Registry.as(Extensions.ExtensionFeaturesRegistry).getExtensionFeatures(); + for (const feature of features) { + const status = this.extensionFeaturesManagementService.getAccessData(extensionId, feature.id)?.current?.status; + const manageAccessLink = `[${localize('manage access', 'Manage Access')}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id, ExtensionEditorTab.Features, false, feature.id]))}`)})`; + if (status?.severity === Severity.Error) { + this.updateStatus({ icon: errorIcon, message: new MarkdownString().appendText(status.message).appendMarkdown(` ${manageAccessLink}`) }, true); + return; + } + if (status?.severity === Severity.Warning) { + this.updateStatus({ icon: warningIcon, message: new MarkdownString().appendText(status.message).appendMarkdown(` ${manageAccessLink}`) }, true); + return; + } + } + // Remote Workspace if (this.extensionManagementServerService.remoteExtensionManagementServer) { if (isLanguagePackExtension(this.extension.local.manifest)) { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 64639cd23de..a4a887e596a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -621,8 +621,8 @@ export class ExtensionHoverWidget extends ExtensionWidget { if (extensionRuntimeStatus.runtimeErrors.length || extensionRuntimeStatus.messages.length) { const hasErrors = extensionRuntimeStatus.runtimeErrors.length || extensionRuntimeStatus.messages.some(message => message.type === Severity.Error); const hasWarnings = extensionRuntimeStatus.messages.some(message => message.type === Severity.Warning); - const errorsLink = extensionRuntimeStatus.runtimeErrors.length ? `[${extensionRuntimeStatus.runtimeErrors.length === 1 ? localize('uncaught error', '1 uncaught error') : localize('uncaught errors', '{0} uncaught errors', extensionRuntimeStatus.runtimeErrors.length)}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id, ExtensionEditorTab.RuntimeStatus]))}`)})` : undefined; - const messageLink = extensionRuntimeStatus.messages.length ? `[${extensionRuntimeStatus.messages.length === 1 ? localize('message', '1 message') : localize('messages', '{0} messages', extensionRuntimeStatus.messages.length)}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id, ExtensionEditorTab.RuntimeStatus]))}`)})` : undefined; + const errorsLink = extensionRuntimeStatus.runtimeErrors.length ? `[${extensionRuntimeStatus.runtimeErrors.length === 1 ? localize('uncaught error', '1 uncaught error') : localize('uncaught errors', '{0} uncaught errors', extensionRuntimeStatus.runtimeErrors.length)}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id, ExtensionEditorTab.Features]))}`)})` : undefined; + const messageLink = extensionRuntimeStatus.messages.length ? `[${extensionRuntimeStatus.messages.length === 1 ? localize('message', '1 message') : localize('messages', '{0} messages', extensionRuntimeStatus.messages.length)}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id, ExtensionEditorTab.Features]))}`)})` : undefined; markdown.appendMarkdown(`$(${hasErrors ? errorIcon.id : hasWarnings ? warningIcon.id : infoIcon.id}) This extension has reported `); if (errorsLink && messageLink) { markdown.appendMarkdown(`${errorsLink} and ${messageLink}`); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 7787578f02a..fa11076ab65 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -44,7 +44,6 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { isBoolean, isString, isUndefined } from 'vs/base/common/types'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { IExtensionService, IExtensionsStatus, toExtension, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; -import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor'; import { isWeb, language } from 'vs/base/common/platform'; import { getLocale } from 'vs/platform/languagePacks/common/languagePacks'; import { ILocaleService } from 'vs/workbench/services/localization/common/locale'; @@ -1087,10 +1086,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (!extension) { throw new Error(`Extension not found. ${extension}`); } - const editor = await this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), options, options?.sideByside ? SIDE_GROUP : ACTIVE_GROUP); - if (options?.tab && editor instanceof ExtensionEditor) { - await editor.openTab(options.tab); - } + await this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), options, options?.sideByside ? SIDE_GROUP : ACTIVE_GROUP); } getExtensionStatus(extension: IExtension): IExtensionsStatus | undefined { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 2409552fb99..a54bd50bf16 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -200,15 +200,18 @@ } .extension-editor > .header > .details > .actions-status-container > .monaco-action-bar > .actions-container > .action-item .extension-action { - margin-bottom: 2px; /* margin for outline */ + margin-bottom: 2px; + /* margin for outline */ } .extension-editor > .header > .details > .actions-status-container > .monaco-action-bar > .actions-container > .action-item > .extension-action:not(.icon) { - margin-left: 2px; /* margin for outline */ + margin-left: 2px; + /* margin for outline */ } .extension-editor > .header > .details > .actions-status-container > .monaco-action-bar > .actions-container > .action-item.action-dropdown-item > .monaco-dropdown .extension-action.action-dropdown { - margin-right: 2px; /* margin for outline */ + margin-right: 2px; + /* margin for outline */ } .extension-editor > .header > .details > .actions-status-container > .monaco-action-bar > .actions-container > .action-item:not(.checkbox-action-item) .extension-action:not(.icon) { @@ -271,7 +274,7 @@ border-radius: 2px 0 0 2px; } -.extension-editor > .header > .details > .actions-status-container > .monaco-action-bar > .actions-container > .action-item.action-dropdown-item:not(.empty) > .monaco-dropdown .extension-action.label { +.extension-editor > .header > .details > .actions-status-container > .monaco-action-bar > .actions-container > .action-item.action-dropdown-item:not(.empty) > .monaco-dropdown .extension-action.label { border-left-width: 0; border-radius: 0 2px 2px 0; } @@ -326,7 +329,7 @@ color: var(--vscode-textLink-activeForeground) } -.extension-editor > .header > .details > .pre-release-text:not(:empty){ +.extension-editor > .header > .details > .pre-release-text:not(:empty) { margin-top: 5px; display: flex; font-size: 90%; @@ -489,25 +492,25 @@ text-decoration: underline; } -.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry { +.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry { font-size: 90%; display: flex; } -.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry :first-child { +.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry :first-child { width: 40%; } -.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry :last-child { +.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry :last-child { width: 60%; } -.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry code { +.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry code { background-color: transparent; padding: 0px; } -.extension-editor > .body > .content > .details > .readme-container > .extension-pack-readme { +.extension-editor > .body > .content > .details > .readme-container > .extension-pack-readme { height: 100%; } @@ -780,3 +783,102 @@ .monaco-workbench .extension-editor > .header > .details > .recommendation .codicon { color: var(--vscode-extensionButton-prominentBackground); } + +/* Features Tab */ + +.extension-editor .subcontent.feature-contributions { + margin-top: 14px; +} + +.extension-editor .subcontent.feature-contributions .features-list-container { + height: 100%; +} + +.extension-editor .subcontent.feature-contributions .features-list-container > .features-list-wrapper { + height: 100%; + padding-left: 24px; +} + +.extension-editor .subcontent.feature-contributions .features-list-container > .features-list-wrapper .monaco-list-row.extension-feature-list-item { + padding-left: 10px; + padding-right: 10px; + display: flex; + align-items: center; +} + +.extension-editor .subcontent.feature-contributions .features-list-container > .features-list-wrapper .monaco-list-row.extension-feature-list-item .extension-feature-label { + flex: 1; +} + +.extension-editor .subcontent.feature-contributions .features-list-container > .features-list-wrapper .monaco-list-row.extension-feature-list-item .extension-feature-disabled-label { + opacity: 0.8; + font-size: 12px; +} + +.extension-editor .subcontent.feature-contributions .features-list-container > .features-list-wrapper .monaco-list-row.extension-feature-list-item .extension-feature-status { + padding-left: 5px; +} + +.extension-editor .subcontent.feature-contributions .feature-view-container { + height: 100%; +} + +.extension-editor .subcontent.feature-contributions .extension-feature-content .feature-body-content .feature-description { + margin-bottom: 10px; +} + +.extension-editor .subcontent.feature-contributions .extension-feature-content { + padding-left: 24px; + height: 100%; + box-sizing: border-box; +} + +.extension-editor .subcontent.feature-contributions .extension-feature-content .feature-header { + margin: 0 10px 10px 0; + display: flex; + line-height: 20px; + align-items: center; +} + +.extension-editor .subcontent.feature-contributions .extension-feature-content .feature-header > .feature-title { + font-size: 26px; + display: inline-block; + margin: 0px; + font-weight: 600; + height: 100%; + box-sizing: border-box; + padding: 10px; + padding-left: 0px; + flex: 1; + position: relative; + overflow: hidden; + text-overflow: ellipsis; +} + +.extension-editor .subcontent.feature-contributions .feature-view-container .extension-feature-content .feature-body { + height: calc(100% - 50px); +} + +.extension-editor .subcontent.feature-contributions .feature-view-container .extension-feature-content .monaco-scrollable-element { + height: 100%; +} + +.extension-editor .subcontent.feature-contributions .feature-view-container .extension-feature-content .feature-body .feature-body-content { + height: 100%; + box-sizing: border-box; + overflow-y: scroll; +} + +.extension-editor .subcontent.feature-contributions .extension-feature-content .feature-body .feature-body-content .feature-status { + display: flex; + align-items: center; +} + +.extension-editor .subcontent.feature-contributions .extension-feature-content .feature-body .feature-body-content pre { + white-space: pre-wrap; + word-wrap: break-word; +} + +.extension-editor .subcontent.feature-contributions .extension-feature-content .feature-body .feature-body-content .feature-content.markdown .codicon { + vertical-align: bottom; +} diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index cd7375688b3..3e5d85b7bf2 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -133,11 +133,10 @@ export interface IExtensionsWorkbenchService { export const enum ExtensionEditorTab { Readme = 'readme', - Contributions = 'contributions', + Features = 'features', Changelog = 'changelog', Dependencies = 'dependencies', ExtensionPack = 'extensionPack', - RuntimeStatus = 'runtimeStatus', } export const ConfigurationKey = 'extensions'; diff --git a/src/vs/workbench/contrib/extensions/common/extensionsInput.ts b/src/vs/workbench/contrib/extensions/common/extensionsInput.ts index df291d7bd39..c8d1a1b1861 100644 --- a/src/vs/workbench/contrib/extensions/common/extensionsInput.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionsInput.ts @@ -21,6 +21,7 @@ const ExtensionEditorIcon = registerIcon('extensions-editor-label-icon', Codicon export interface IExtensionEditorOptions extends IEditorOptions { showPreReleaseVersion?: boolean; tab?: ExtensionEditorTab; + feature?: string; sideByside?: boolean; } diff --git a/src/vs/workbench/contrib/localization/common/localization.contribution.ts b/src/vs/workbench/contrib/localization/common/localization.contribution.ts index 568c39fbdc1..d4a05b38d86 100644 --- a/src/vs/workbench/contrib/localization/common/localization.contribution.ts +++ b/src/vs/workbench/contrib/localization/common/localization.contribution.ts @@ -6,8 +6,12 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ClearDisplayLanguageAction, ConfigureDisplayLanguageAction } from 'vs/workbench/contrib/localization/common/localizationsActions'; +import { IExtensionFeatureTableRenderer, IRenderedData, ITableData, IRowData, IExtensionFeaturesRegistry, Extensions } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; export class BaseLocalizationWorkbenchContribution extends Disposable implements IWorkbenchContribution { @@ -70,3 +74,52 @@ export class BaseLocalizationWorkbenchContribution extends Disposable implements }); } } + +class LocalizationsDataRenderer extends Disposable implements IExtensionFeatureTableRenderer { + + readonly type = 'table'; + + shouldRender(manifest: IExtensionManifest): boolean { + return !!manifest.contributes?.localizations; + } + + render(manifest: IExtensionManifest): IRenderedData { + const localizations = manifest.contributes?.localizations || []; + if (!localizations.length) { + return { data: { headers: [], rows: [] }, dispose: () => { } }; + } + + const headers = [ + localize('language id', "Language ID"), + localize('localizations language name', "Language Name"), + localize('localizations localized language name', "Language Name (Localized)"), + ]; + + const rows: IRowData[][] = localizations + .sort((a, b) => a.languageId.localeCompare(b.languageId)) + .map(localization => { + return [ + localization.languageId, + localization.languageName ?? '', + localization.localizedLanguageName ?? '' + ]; + }); + + return { + data: { + headers, + rows + }, + dispose: () => { } + }; + } +} + +Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'localizations', + label: localize('localizations', "Langauage Packs"), + enablement: { + canToggle: false + }, + renderer: new SyncDescriptor(LocalizationsDataRenderer), +}); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts b/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts index 026f2bb14b2..b620ce43e53 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts @@ -7,6 +7,11 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as nls from 'vs/nls'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { NotebookEditorPriority, ContributedNotebookRendererEntrypoint, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IExtensionFeatureTableRenderer, IRenderedData, ITableData, IRowData, IExtensionFeaturesRegistry, Extensions } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { Registry } from 'vs/platform/registry/common/platform'; const NotebookEditorContribution = Object.freeze({ type: 'type', @@ -265,3 +270,97 @@ export const notebookPreloadExtensionPoint = ExtensionsRegistry.registerExtensio extensionPoint: 'notebookPreload', jsonSchema: notebookPreloadContribution, }); + +class NotebooksDataRenderer extends Disposable implements IExtensionFeatureTableRenderer { + + readonly type = 'table'; + + shouldRender(manifest: IExtensionManifest): boolean { + return !!manifest.contributes?.notebooks; + } + + render(manifest: IExtensionManifest): IRenderedData { + const contrib = manifest.contributes?.notebooks || []; + if (!contrib.length) { + return { data: { headers: [], rows: [] }, dispose: () => { } }; + } + + const headers = [ + nls.localize('Notebook id', "ID"), + nls.localize('Notebook name', "Name"), + ]; + + const rows: IRowData[][] = contrib + .sort((a, b) => a.type.localeCompare(b.type)) + .map(notebook => { + return [ + notebook.type, + notebook.displayName + ]; + }); + + return { + data: { + headers, + rows + }, + dispose: () => { } + }; + } +} + +class NotebookRenderersDataRenderer extends Disposable implements IExtensionFeatureTableRenderer { + + readonly type = 'table'; + + shouldRender(manifest: IExtensionManifest): boolean { + return !!manifest.contributes?.notebookRenderer; + } + + render(manifest: IExtensionManifest): IRenderedData { + const contrib = manifest.contributes?.notebookRenderer || []; + if (!contrib.length) { + return { data: { headers: [], rows: [] }, dispose: () => { } }; + } + + const headers = [ + nls.localize('Notebook renderer name', "Name"), + nls.localize('Notebook mimetypes', "Mimetypes"), + ]; + + const rows: IRowData[][] = contrib + .sort((a, b) => a.displayName.localeCompare(b.displayName)) + .map(notebookRenderer => { + return [ + notebookRenderer.displayName, + notebookRenderer.mimeTypes.join(',') + ]; + }); + + return { + data: { + headers, + rows + }, + dispose: () => { } + }; + } +} + +Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'notebooks', + label: nls.localize('notebooks', "Notebooks"), + enablement: { + canToggle: false + }, + renderer: new SyncDescriptor(NotebooksDataRenderer), +}); + +Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'notebookRenderer', + label: nls.localize('notebookRenderer', "Notebook Renderers"), + enablement: { + canToggle: false + }, + renderer: new SyncDescriptor(NotebookRenderersDataRenderer), +}); diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 3a9ff88e5dd..f6fdb36e554 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -11,12 +11,17 @@ import { IExtensionPointUser, ExtensionMessageCollector, ExtensionsRegistry } fr import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { MenuId, MenuRegistry, IMenuItem, ISubmenuItem } from 'vs/platform/actions/common/actions'; import { URI } from 'vs/base/common/uri'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import { index } from 'vs/base/common/arrays'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { ApiProposalName } from 'vs/workbench/services/extensions/common/extensionsApiProposals'; import { ILocalizedString } from 'vs/platform/action/common/action'; +import { IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, IRenderedData, IRowData, ITableData, Extensions as ExtensionFeaturesExtensions } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { IExtensionManifest, IKeyBinding } from 'vs/platform/extensions/common/extensions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { platform } from 'vs/base/common/process'; interface IAPIMenu { readonly key: string; @@ -981,3 +986,112 @@ menusExtensionPoint.setHandler(extensions => { } } }); + +class CommandsTableRenderer extends Disposable implements IExtensionFeatureTableRenderer { + + readonly type = 'table'; + + shouldRender(manifest: IExtensionManifest): boolean { + return !!manifest.contributes?.commands; + } + + render(manifest: IExtensionManifest): IRenderedData { + const rawCommands = manifest.contributes?.commands || []; + const commands = rawCommands.map(c => ({ + id: c.command, + title: c.title, + keybindings: [] as string[], + menus: [] as string[] + })); + + const byId = index(commands, c => c.id); + + const menus = manifest.contributes?.menus || {}; + + for (const context in menus) { + for (const menu of menus[context]) { + if (menu.command) { + let command = byId[menu.command]; + if (command) { + command.menus.push(context); + } else { + command = { id: menu.command, title: '', keybindings: [], menus: [context] }; + byId[command.id] = command; + commands.push(command); + } + } + } + } + + const rawKeybindings = manifest.contributes?.keybindings ? (Array.isArray(manifest.contributes.keybindings) ? manifest.contributes.keybindings : [manifest.contributes.keybindings]) : []; + + rawKeybindings.forEach(rawKeybinding => { + const keybinding = this.resolveKeybinding(rawKeybinding); + + if (!keybinding) { + return; + } + + let command = byId[rawKeybinding.command]; + + if (command) { + command.keybindings.push(keybinding); + } else { + command = { id: rawKeybinding.command, title: '', keybindings: [keybinding], menus: [] }; + byId[command.id] = command; + commands.push(command); + } + }); + + if (!commands.length) { + return { data: { headers: [], rows: [] }, dispose: () => { } }; + } + + const headers = [ + localize('command name', "ID"), + localize('command title', "Title"), + localize('keyboard shortcuts', "Keyboard Shortcuts"), + localize('menuContexts', "Menu Contexts") + ]; + + const rows: IRowData[][] = commands.sort((a, b) => a.id.localeCompare(b.id)) + .map(command => { + return [ + { data: command.id, type: 'code' }, + typeof command.title === 'string' ? command.title : command.title.value, + { data: command.keybindings, type: 'keybinding' }, + { data: command.menus, type: 'code' }, + ]; + }); + + return { + data: { + headers, + rows + }, + dispose: () => { } + }; + } + + private resolveKeybinding(rawKeyBinding: IKeyBinding): string | undefined { + let key: string | undefined; + + switch (platform) { + case 'win32': key = rawKeyBinding.win; break; + case 'linux': key = rawKeyBinding.linux; break; + case 'darwin': key = rawKeyBinding.mac; break; + } + + return key ?? rawKeyBinding.key; + } + +} + +Registry.as(ExtensionFeaturesExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'commands', + label: localize('commands', "Commands"), + enablement: { + canToggle: false, + }, + renderer: new SyncDescriptor(CommandsTableRenderer), +}); diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index ab9c5697cfb..df4b8d65679 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -14,15 +14,19 @@ import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Severity } from 'vs/platform/notification/common/notification'; import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { Registry } from 'vs/platform/registry/common/platform'; import { ISecretStorageService } from 'vs/platform/secrets/common/secrets'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IAuthenticationCreateSessionOptions, AuthenticationProviderInformation, AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; +import { IExtensionFeatureTableRenderer, IRenderedData, ITableData, IRowData, IExtensionFeaturesRegistry, Extensions } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -169,6 +173,53 @@ const authenticationExtPoint = ExtensionsRegistry.registerExtensionPoint { + const authentication = manifest.contributes?.authentication || []; + if (!authentication.length) { + return { data: { headers: [], rows: [] }, dispose: () => { } }; + } + + const headers = [ + nls.localize('authenticationlabel', "Label"), + nls.localize('authenticationid', "ID"), + ]; + + const rows: IRowData[][] = authentication + .sort((a, b) => a.label.localeCompare(b.label)) + .map(auth => { + return [ + auth.label, + auth.id, + ]; + }); + + return { + data: { + headers, + rows + }, + dispose: () => { } + }; + } +} + +Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'authentication', + label: nls.localize('authentication', "Authentication"), + enablement: { + canToggle: false + }, + renderer: new SyncDescriptor(AuthenticationDataRenderer), +}); + let placeholderMenuItem: IDisposable | undefined = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { command: { id: 'noAuthenticationProviders', diff --git a/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts b/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts new file mode 100644 index 00000000000..724f4d189a0 --- /dev/null +++ b/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { Event } from 'vs/base/common/event'; +import { ExtensionIdentifier, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import Severity from 'vs/base/common/severity'; + +export namespace Extensions { + export const ExtensionFeaturesRegistry = 'workbench.registry.extensionFeatures'; +} + +export interface IExtensionFeatureRenderer extends IDisposable { + type: string; + shouldRender(manifest: IExtensionManifest): boolean; + render(manifest: IExtensionManifest): IDisposable; +} + +export interface IRenderedData extends IDisposable { + readonly data: T; + readonly onDidChange?: Event; +} + +export interface IExtensionFeatureMarkdownRenderer extends IExtensionFeatureRenderer { + type: 'markdown'; + render(manifest: IExtensionManifest): IRenderedData; +} + +export type IRowData = string | IMarkdownString | { readonly data: string | string[]; readonly type: 'code' | 'keybinding' | 'color' }; + +export interface ITableData { + headers: string[]; + rows: IRowData[][]; +} + +export interface IExtensionFeatureTableRenderer extends IExtensionFeatureRenderer { + type: 'table'; + render(manifest: IExtensionManifest): IRenderedData; +} + +export interface IExtensionFeatureDescriptor { + readonly id: string; + readonly label: string; + readonly description?: string; + readonly enablement: { + readonly canToggle?: boolean; + readonly requireUserConsent?: boolean; + }; + readonly renderer: SyncDescriptor; +} + +export interface IExtensionFeaturesRegistry { + + registerExtensionFeature(descriptor: IExtensionFeatureDescriptor): void; + getExtensionFeature(id: string): IExtensionFeatureDescriptor | undefined; + getExtensionFeatures(): ReadonlyArray; +} + +export interface IExtensionFeatureAccessData { + readonly current?: { + readonly count: number; + readonly lastAccessed: number; + readonly status?: { readonly severity: Severity; readonly message: string }; + }; + readonly totalCount: number; +} + +export const IExtensionFeaturesManagementService = createDecorator('IExtensionFeaturesManagementService'); +export interface IExtensionFeaturesManagementService { + readonly _serviceBrand: undefined; + + readonly onDidChangeEnablement: Event<{ readonly extension: ExtensionIdentifier; readonly featureId: string; readonly enabled: boolean }>; + isEnabled(extension: ExtensionIdentifier, featureId: string): boolean; + setEnablement(extension: ExtensionIdentifier, featureId: string, enabled: boolean): void; + getEnablementData(featureId: string): { readonly extension: ExtensionIdentifier; readonly enabled: boolean }[]; + + getAccess(extension: ExtensionIdentifier, featureId: string): Promise; + + readonly onDidChangeAccessData: Event<{ readonly extension: ExtensionIdentifier; readonly featureId: string; readonly accessData: IExtensionFeatureAccessData }>; + getAccessData(extension: ExtensionIdentifier, featureId: string): IExtensionFeatureAccessData | undefined; + setStatus(extension: ExtensionIdentifier, featureId: string, status: { readonly severity: Severity; readonly message: string } | undefined): void; +} + +class ExtensionFeaturesRegistry implements IExtensionFeaturesRegistry { + + private readonly extensionFeatures = new Map(); + + registerExtensionFeature(descriptor: IExtensionFeatureDescriptor): void { + if (this.extensionFeatures.has(descriptor.id)) { + throw new Error(`Extension feature with id '${descriptor.id}' already exists`); + } + this.extensionFeatures.set(descriptor.id, descriptor); + } + + getExtensionFeature(id: string): IExtensionFeatureDescriptor | undefined { + return this.extensionFeatures.get(id); + } + + getExtensionFeatures(): ReadonlyArray { + return Array.from(this.extensionFeatures.values()); + } +} + +Registry.add(Extensions.ExtensionFeaturesRegistry, new ExtensionFeaturesRegistry()); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts b/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts new file mode 100644 index 00000000000..5cdaa860c57 --- /dev/null +++ b/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts @@ -0,0 +1,231 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { Disposable } from 'vs/base/common/lifecycle'; +import Severity from 'vs/base/common/severity'; +import { Extensions, IExtensionFeatureAccessData, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { Mutable } from 'vs/base/common/types'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { localize } from 'vs/nls'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IStorageChangeEvent } from 'vs/base/parts/storage/common/storage'; +import { distinct } from 'vs/base/common/arrays'; +import { equals } from 'vs/base/common/objects'; + +interface IExtensionFeatureState { + disabled?: boolean; + accessData: Mutable; +} + +const FEATURES_STATE_KEY = 'extension.features.state'; + +class ExtensionFeaturesManagementService extends Disposable implements IExtensionFeaturesManagementService { + declare readonly _serviceBrand: undefined; + + private readonly _onDidChangeEnablement = this._register(new Emitter<{ extension: ExtensionIdentifier; featureId: string; enabled: boolean }>()); + readonly onDidChangeEnablement = this._onDidChangeEnablement.event; + + private readonly _onDidChangeAccessData = this._register(new Emitter<{ extension: ExtensionIdentifier; featureId: string; accessData: IExtensionFeatureAccessData }>()); + readonly onDidChangeAccessData = this._onDidChangeAccessData.event; + + private readonly registry: IExtensionFeaturesRegistry; + private extensionFeaturesState = new Map>(); + + constructor( + @IStorageService private readonly storageService: IStorageService, + @IDialogService private readonly dialogService: IDialogService, + @IExtensionService private readonly extensionService: IExtensionService, + ) { + super(); + this.registry = Registry.as(Extensions.ExtensionFeaturesRegistry); + this.extensionFeaturesState = this.loadState(); + this._register(storageService.onDidChangeValue(StorageScope.PROFILE, FEATURES_STATE_KEY, this._store)(e => this.onDidStorageChange(e))); + } + + isEnabled(extension: ExtensionIdentifier, featureId: string): boolean { + const feature = this.registry.getExtensionFeature(featureId); + if (!feature) { + return false; + } + return !(this.getExtensionFeatureState(extension, featureId)?.disabled ?? false); + } + + setEnablement(extension: ExtensionIdentifier, featureId: string, enabled: boolean): void { + const feature = this.registry.getExtensionFeature(featureId); + if (!feature) { + throw new Error(`No feature with id '${featureId}'`); + } + const featureState = this.getAndSetIfNotExistsExtensionFeatureState(extension, featureId); + if (featureState.disabled !== !enabled) { + featureState.disabled = !enabled; + this._onDidChangeEnablement.fire({ extension, featureId, enabled }); + this.saveState(); + } + } + + getEnablementData(featureId: string): { readonly extension: ExtensionIdentifier; readonly enabled: boolean }[] { + const result: { readonly extension: ExtensionIdentifier; readonly enabled: boolean }[] = []; + const feature = this.registry.getExtensionFeature(featureId); + if (feature) { + for (const [extension, featuresStateMap] of this.extensionFeaturesState) { + const featureState = featuresStateMap.get(featureId); + if (featureState?.disabled !== undefined) { + result.push({ extension: new ExtensionIdentifier(extension), enabled: !featureState.disabled }); + } + } + } + return result; + } + + async getAccess(extension: ExtensionIdentifier, featureId: string): Promise { + const feature = this.registry.getExtensionFeature(featureId); + if (!feature) { + return false; + } + const featureState = this.getAndSetIfNotExistsExtensionFeatureState(extension, featureId); + if (featureState.disabled) { + return false; + } + + if (featureState.disabled === undefined) { + const extensionDescription = this.extensionService.extensions.find(e => ExtensionIdentifier.equals(e.identifier, extension)); + const confirmationResult = await this.dialogService.confirm({ + title: localize('accessExtensionFeature', "Access '{0}' Feature", feature.label), + message: localize('accessExtensionFeatureMessage', "'{0}' extension would like to access the '{1}' feature.", extensionDescription?.displayName ?? extension.value, feature.label), + detail: feature.description, + custom: true, + primaryButton: localize('allow', "Allow"), + cancelButton: localize('disallow', "Don't Allow"), + }); + this.setEnablement(extension, featureId, confirmationResult.confirmed); + if (!confirmationResult.confirmed) { + return false; + } + } + + featureState.accessData.current = { + count: featureState.accessData.current?.count ? featureState.accessData.current?.count + 1 : 1, + lastAccessed: Date.now(), + status: featureState.accessData.current?.status + }; + featureState.accessData.totalCount = featureState.accessData.totalCount + 1; + this.saveState(); + this._onDidChangeAccessData.fire({ extension, featureId, accessData: featureState.accessData }); + return true; + } + + getAccessData(extension: ExtensionIdentifier, featureId: string): IExtensionFeatureAccessData | undefined { + const feature = this.registry.getExtensionFeature(featureId); + if (!feature) { + return; + } + return this.getExtensionFeatureState(extension, featureId)?.accessData; + } + + setStatus(extension: ExtensionIdentifier, featureId: string, status: { readonly severity: Severity; readonly message: string } | undefined): void { + const feature = this.registry.getExtensionFeature(featureId); + if (!feature) { + throw new Error(`No feature with id '${featureId}'`); + } + const featureState = this.getAndSetIfNotExistsExtensionFeatureState(extension, featureId); + featureState.accessData.current = { + count: featureState.accessData.current?.count ?? 0, + lastAccessed: featureState.accessData.current?.lastAccessed ?? 0, + status + }; + this._onDidChangeAccessData.fire({ extension, featureId, accessData: this.getAccessData(extension, featureId)! }); + } + + private getExtensionFeatureState(extension: ExtensionIdentifier, featureId: string): IExtensionFeatureState | undefined { + return this.extensionFeaturesState.get(extension.value)?.get(featureId); + } + + private getAndSetIfNotExistsExtensionFeatureState(extension: ExtensionIdentifier, featureId: string): Mutable { + let extensionState = this.extensionFeaturesState.get(extension.value); + if (!extensionState) { + extensionState = new Map(); + this.extensionFeaturesState.set(extension.value, extensionState); + } + let featureState = extensionState.get(featureId); + if (!featureState) { + featureState = { accessData: { totalCount: 0 } }; + extensionState.set(featureId, featureState); + } + return featureState; + } + + private onDidStorageChange(e: IStorageChangeEvent): void { + if (e.external) { + const oldState = this.extensionFeaturesState; + this.extensionFeaturesState = this.loadState(); + for (const extensionId of distinct([...oldState.keys(), ...this.extensionFeaturesState.keys()])) { + const extension = new ExtensionIdentifier(extensionId); + const oldExtensionFeaturesState = oldState.get(extensionId); + const newExtensionFeaturesState = this.extensionFeaturesState.get(extensionId); + for (const featureId of distinct([...oldExtensionFeaturesState?.keys() ?? [], ...newExtensionFeaturesState?.keys() ?? []])) { + const isEnabled = this.isEnabled(extension, featureId); + const wasEnabled = !oldExtensionFeaturesState?.get(featureId)?.disabled; + if (isEnabled !== wasEnabled) { + this._onDidChangeEnablement.fire({ extension, featureId, enabled: isEnabled }); + } + const newAccessData = this.getAccessData(extension, featureId); + const oldAccessData = oldExtensionFeaturesState?.get(featureId)?.accessData; + if (!equals(newAccessData, oldAccessData)) { + this._onDidChangeAccessData.fire({ extension, featureId, accessData: newAccessData ?? { totalCount: 0 } }); + } + } + } + } + } + + private loadState(): Map> { + let data: IStringDictionary> = {}; + const raw = this.storageService.get(FEATURES_STATE_KEY, StorageScope.PROFILE, '{}'); + try { + data = JSON.parse(raw); + } catch (e) { + // ignore + } + const result = new Map>(); + for (const extensionId in data) { + const extensionFeatureState = new Map(); + const extensionFeatures = data[extensionId]; + for (const featureId in extensionFeatures) { + const extensionFeature = extensionFeatures[featureId]; + extensionFeatureState.set(featureId, { + disabled: extensionFeature.disabled, + accessData: { + totalCount: extensionFeature.accessCount + } + }); + } + result.set(extensionId, extensionFeatureState); + } + return result; + } + + private saveState(): void { + const data: IStringDictionary> = {}; + this.extensionFeaturesState.forEach((extensionState, extensionId) => { + const extensionFeatures: IStringDictionary<{ disabled?: boolean; accessCount: number }> = {}; + extensionState.forEach((featureState, featureId) => { + extensionFeatures[featureId] = { + disabled: featureState.disabled, + accessCount: featureState.accessData.totalCount + }; + }); + data[extensionId] = extensionFeatures; + }); + this.storageService.store(FEATURES_STATE_KEY, JSON.stringify(data), StorageScope.PROFILE, StorageTarget.USER); + } +} + +registerSingleton(IExtensionFeaturesManagementService, ExtensionFeaturesManagementService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 4b9a9581b0d..e8f7f3a5eab 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -4,9 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { Barrier } from 'vs/base/common/async'; +import { Codicon } from 'vs/base/common/codicons'; import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { getErrorMessage } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import * as perf from 'vs/base/common/performance'; import { isCI } from 'vs/base/common/platform'; @@ -17,19 +20,23 @@ import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ImplicitActivationEvents } from 'vs/platform/extensionManagement/common/implicitActivationEvents'; -import { ExtensionIdentifier, ExtensionIdentifierMap, IExtension, IExtensionContributions, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionIdentifierMap, IExtension, IExtensionContributions, IExtensionDescription, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { handleVetos } from 'vs/platform/lifecycle/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IProductService } from 'vs/platform/product/common/productService'; +import { Registry } from 'vs/platform/registry/common/platform'; import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IExtensionFeaturesRegistry, Extensions as ExtensionFeaturesExtensions, IExtensionFeatureMarkdownRenderer, IRenderedData, } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionDescriptionRegistryLock, ExtensionDescriptionRegistrySnapshot, IActivationEventsReader, LockableExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; @@ -1342,3 +1349,87 @@ export class ImplicitActivationAwareReader implements IActivationEventsReader { return ImplicitActivationEvents.readActivationEvents(extensionDescription); } } + +class ActivationFeatureMarkdowneRenderer extends Disposable implements IExtensionFeatureMarkdownRenderer { + + readonly type = 'markdown'; + + constructor( + @IExtensionService private readonly _extensionService: IExtensionService + ) { + super(); + } + + shouldRender(manifest: IExtensionManifest): boolean { + const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); + if (this._extensionService.extensions.some(e => ExtensionIdentifier.equals(e.identifier, extensionId))) { + return !!manifest.main || !!manifest.browser; + } + return !!manifest.activationEvents; + } + + render(manifest: IExtensionManifest): IRenderedData { + const disposables = new DisposableStore(); + const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); + const emitter = disposables.add(new Emitter()); + this._extensionService.onDidChangeExtensionsStatus(e => { + if (e.some(extension => ExtensionIdentifier.equals(extension, extensionId))) { + emitter.fire(this.getActivationData(manifest)); + } + }); + return { + onDidChange: emitter.event, + data: this.getActivationData(manifest), + dispose: () => disposables.dispose() + }; + } + + private getActivationData(manifest: IExtensionManifest): IMarkdownString { + const data = new MarkdownString(); + const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); + const status = this._extensionService.getExtensionsStatus()[extensionId.value]; + if (this._extensionService.extensions.some(extension => ExtensionIdentifier.equals(extension.identifier, extensionId))) { + if (status.activationTimes) { + if (status.activationTimes.activationReason.startup) { + data.appendText('Activated on startup in `') + .appendText(`${status.activationTimes.activateCallTime}ms`) + .appendText('`'); + } else { + data.appendMarkdown('Activated in `' + status.activationTimes.activateCallTime + 'ms` by `' + status.activationTimes.activationReason.activationEvent + '` event.'); + } + } else { + data.appendMarkdown('Not yet activated'); + } + if (status.runtimeErrors.length) { + data.appendMarkdown(`\n ### ${nls.localize('uncaught errors', "Uncaught Errors ({0})", status.runtimeErrors.length)}\n`); + for (const error of status.runtimeErrors) { + data.appendMarkdown(`$(${Codicon.error.id}) ${getErrorMessage(error)}\n\n`); + } + } + if (status.messages.length) { + data.appendMarkdown(`\n ### ${nls.localize('messaages', "Messages ({0})", status.messages.length)}\n`); + for (const message of status.messages) { + data.appendMarkdown(`$(${(message.type === Severity.Error ? Codicon.error : message.type === Severity.Warning ? Codicon.warning : Codicon.info).id}) ${message.message}\n\n`); + } + } + } else { + const activationEvents = manifest.activationEvents || []; + if (activationEvents.length) { + data.appendMarkdown(`### ${nls.localize('activation events', "Activation Events")}\n\n`); + for (const activationEvent of activationEvents) { + data.appendMarkdown(`- \`${activationEvent}\`\n`); + } + } + } + return data; + } +} + +Registry.as(ExtensionFeaturesExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'activation', + label: nls.localize('activation', "Activation"), + enablement: { + canToggle: false + }, + renderer: new SyncDescriptor(ActivationFeatureMarkdowneRenderer), +}); diff --git a/src/vs/workbench/services/language/common/languageService.ts b/src/vs/workbench/services/language/common/languageService.ts index efe2c8dd2c0..b7b66c60a1e 100644 --- a/src/vs/workbench/services/language/common/languageService.ts +++ b/src/vs/workbench/services/language/common/languageService.ts @@ -15,8 +15,13 @@ import { FILES_ASSOCIATIONS_CONFIG, IFilesConfiguration } from 'vs/platform/file import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IExtensionDescription, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Extensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, IRenderedData, IRowData, ITableData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { index } from 'vs/base/common/arrays'; export interface IRawLanguageExtensionPoint { id: string; @@ -114,6 +119,93 @@ export const languagesExtPoint: IExtensionPoint = } }); +class LanguageTableRenderer extends Disposable implements IExtensionFeatureTableRenderer { + + readonly type = 'table'; + + shouldRender(manifest: IExtensionManifest): boolean { + return !!manifest.contributes?.languages; + } + + render(manifest: IExtensionManifest): IRenderedData { + const contributes = manifest.contributes; + const rawLanguages = contributes?.languages || []; + const languages = rawLanguages.map(l => ({ + id: l.id, + name: (l.aliases || [])[0] || l.id, + extensions: l.extensions || [], + hasGrammar: false, + hasSnippets: false + })); + + const byId = index(languages, l => l.id); + + const grammars = contributes?.grammars || []; + grammars.forEach(grammar => { + let language = byId[grammar.language]; + + if (language) { + language.hasGrammar = true; + } else { + language = { id: grammar.language, name: grammar.language, extensions: [], hasGrammar: true, hasSnippets: false }; + byId[language.id] = language; + languages.push(language); + } + }); + + const snippets = contributes?.snippets || []; + snippets.forEach(snippet => { + let language = byId[snippet.language]; + + if (language) { + language.hasSnippets = true; + } else { + language = { id: snippet.language, name: snippet.language, extensions: [], hasGrammar: false, hasSnippets: true }; + byId[language.id] = language; + languages.push(language); + } + }); + + if (!languages.length) { + return { data: { headers: [], rows: [] }, dispose: () => { } }; + } + + const headers = [ + localize('language id', "ID"), + localize('language name', "Name"), + localize('file extensions', "File Extensions"), + localize('grammar', "Grammar"), + localize('snippets', "Snippets") + ]; + const rows: IRowData[][] = languages.sort((a, b) => a.id.localeCompare(b.id)) + .map(l => { + return [ + l.id, l.name, + { data: l.extensions, type: 'code' }, + l.hasGrammar ? '✔︎' : '\u2014', + l.hasSnippets ? '✔︎' : '\u2014' + ]; + }); + + return { + data: { + headers, + rows + }, + dispose: () => { } + }; + } +} + +Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'languages', + label: localize('languages', "Programming Languages"), + enablement: { + canToggle: false + }, + renderer: new SyncDescriptor(LanguageTableRenderer), +}); + export class WorkbenchLanguageService extends LanguageService { private _configurationService: IConfigurationService; private _extensionService: IExtensionService; diff --git a/src/vs/workbench/services/themes/common/colorExtensionPoint.ts b/src/vs/workbench/services/themes/common/colorExtensionPoint.ts index bda39a526a2..9997259fd94 100644 --- a/src/vs/workbench/services/themes/common/colorExtensionPoint.ts +++ b/src/vs/workbench/services/themes/common/colorExtensionPoint.ts @@ -8,6 +8,10 @@ import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/exte import { IColorRegistry, Extensions as ColorRegistryExtensions } from 'vs/platform/theme/common/colorRegistry'; import { Color } from 'vs/base/common/color'; import { Registry } from 'vs/platform/registry/common/platform'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Extensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, IRenderedData, IRowData, ITableData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; interface IColorExtensionPoint { id: string; @@ -150,5 +154,53 @@ export class ColorExtensionPoint { } } +class ColorDataRenderer extends Disposable implements IExtensionFeatureTableRenderer { + readonly type = 'table'; + shouldRender(manifest: IExtensionManifest): boolean { + return !!manifest.contributes?.colors; + } + + render(manifest: IExtensionManifest): IRenderedData { + const colors = manifest.contributes?.colors || []; + if (!colors.length) { + return { data: { headers: [], rows: [] }, dispose: () => { } }; + } + + const headers = [ + nls.localize('id', "ID"), + nls.localize('description', "Description"), + nls.localize('defaultDark', "Dark Default"), + nls.localize('defaultLight', "Light Default"), + nls.localize('defaultHC', "High Contrast Default"), + ]; + const rows: IRowData[][] = colors.sort((a, b) => a.id.localeCompare(b.id)) + .map(color => { + return [ + { data: color.id, type: 'code' }, + color.description, + { data: color.defaults.dark, type: 'color' }, + { data: color.defaults.light, type: 'color' }, + { data: color.defaults.highContrast, type: 'color' }, + ]; + }); + + return { + data: { + headers, + rows + }, + dispose: () => { } + }; + } +} + +Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'colors', + label: nls.localize('colors', "Colors"), + enablement: { + canToggle: false + }, + renderer: new SyncDescriptor(ColorDataRenderer), +}); diff --git a/src/vs/workbench/services/themes/common/themeExtensionPoints.ts b/src/vs/workbench/services/themes/common/themeExtensionPoints.ts index 8862328cba9..5fdf01d4a15 100644 --- a/src/vs/workbench/services/themes/common/themeExtensionPoints.ts +++ b/src/vs/workbench/services/themes/common/themeExtensionPoints.ts @@ -12,6 +12,12 @@ import { ExtensionData, IThemeExtensionPoint, VS_LIGHT_THEME, VS_DARK_THEME, VS_ import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Extensions, IExtensionFeatureMarkdownRenderer, IExtensionFeaturesRegistry, IRenderedData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; export function registerColorThemeExtensionPoint() { return ExtensionsRegistry.registerExtensionPoint({ @@ -103,6 +109,50 @@ export function registerProductIconThemeExtensionPoint() { }); } +class ThemeDataRenderer extends Disposable implements IExtensionFeatureMarkdownRenderer { + + readonly type = 'markdown'; + + shouldRender(manifest: IExtensionManifest): boolean { + return !!manifest.contributes?.themes || !!manifest.contributes?.iconThemes || !!manifest.contributes?.productIconThemes; + } + + render(manifest: IExtensionManifest): IRenderedData { + const markdown = new MarkdownString(); + if (manifest.contributes?.themes) { + markdown.appendMarkdown(`### ${nls.localize('color themes', "Color Themes")}\n\n`); + for (const theme of manifest.contributes.themes) { + markdown.appendMarkdown(`- ${theme.label}\n`); + } + } + if (manifest.contributes?.iconThemes) { + markdown.appendMarkdown(`### ${nls.localize('file icon themes', "File Icon Themes")}\n\n`); + for (const theme of manifest.contributes.iconThemes) { + markdown.appendMarkdown(`- ${theme.label}\n`); + } + } + if (manifest.contributes?.productIconThemes) { + markdown.appendMarkdown(`### ${nls.localize('product icon themes', "Product Icon Themes")}\n\n`); + for (const theme of manifest.contributes.productIconThemes) { + markdown.appendMarkdown(`- ${theme.label}\n`); + } + } + return { + data: markdown, + dispose: () => { /* noop */ } + }; + } +} + +Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'themes', + label: nls.localize('themes', "Themes"), + enablement: { + canToggle: false + }, + renderer: new SyncDescriptor(ThemeDataRenderer), +}); + export interface ThemeChangeEvent { themes: T[]; added: T[]; diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 98c8d09ad6d..6aa1bf45c0d 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -86,6 +86,7 @@ import 'vs/workbench/services/extensionManagement/browser/extensionEnablementSer import 'vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService'; import 'vs/workbench/services/extensionRecommendations/common/extensionIgnoredRecommendationsService'; import 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; +import 'vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService'; import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/userDataSync/common/userDataSyncUtil'; import 'vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService'; From 015d675fb66d7b13f6be2bc4d49b174faf47783d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 7 Feb 2024 15:44:56 +0100 Subject: [PATCH 0973/1897] Voice: show mic icon in chat boxes even when no extension is installed (fix #203896) (#204532) * Voice: show mic icon in chat boxes even when no extension is installed (fix #203896) * implement * . * . --- .../actions/voiceChatActions.ts | 70 ++++++++++++++++++- .../electron-sandbox/chat.contribution.ts | 3 +- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index ad9c273ba2a..bb711aea86f 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/voiceChatActions'; import { Event } from 'vs/base/common/event'; import { firstOrDefault } from 'vs/base/common/arrays'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; @@ -47,6 +47,10 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IHostService } from 'vs/workbench/services/host/browser/host'; import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); @@ -502,6 +506,70 @@ export class StartVoiceChatAction extends Action2 { } } +export class InstallVoiceChatAction extends Action2 { + + static readonly ID = 'workbench.action.chat.installVoiceChat'; + + private static readonly SPEECH_EXTENSION_ID = 'ms-vscode.vscode-speech'; + + constructor() { + super({ + id: InstallVoiceChatAction.ID, + title: localize2('workbench.action.chat.startVoiceChat.label', "Use Microphone"), + f1: false, + category: CHAT_CATEGORY, + icon: Codicon.mic, + menu: [{ + id: MenuId.ChatExecute, + when: HasSpeechProvider.negate(), + group: 'navigation', + order: -1 + }, { + id: MENU_INLINE_CHAT_INPUT, + when: HasSpeechProvider.negate(), + group: 'main', + order: -1 + }] + }); + } + + async run(accessor: ServicesAccessor): Promise { + const commandService = accessor.get(ICommandService); + const extensionService = accessor.get(IExtensionService); + const dialogService = accessor.get(IDialogService); + const extensionManagementService = accessor.get(IExtensionsWorkbenchService); + + const extension = firstOrDefault((await extensionManagementService.getExtensions([{ id: InstallVoiceChatAction.SPEECH_EXTENSION_ID }], CancellationToken.None))); + if (!extension) { + return; + } + + if (extension.state === ExtensionState.Installed) { + await dialogService.info( + localize('enableExtensionMessage', "Microphone support requires an extension. Pleas enable it."), + localize('enableExtensionDetail', "Extension '{0}' is currently disabled.", InstallVoiceChatAction.SPEECH_EXTENSION_ID), + ); + + return extensionManagementService.open(extension); + } + + const { confirmed } = await dialogService.confirm({ + message: localize('confirmInstallMessage', "Microphone support requires an extension. Would you like to install it now?"), + detail: localize('confirmInstallDetail', "This will install the '{0}' extension.", InstallVoiceChatAction.SPEECH_EXTENSION_ID), + primaryButton: localize({ key: 'installButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Install") + }); + + if (!confirmed) { + return; + } + + const extensionRunning = Event.toPromise(Event.filter(extensionService.onDidChangeExtensionsStatus, e => e.some(e => e.value === InstallVoiceChatAction.SPEECH_EXTENSION_ID))); + await extensionManagementService.install(extension, undefined, ProgressLocation.Notification); + await extensionRunning; + commandService.executeCommand(StartVoiceChatAction.ID); + } +} + export class StopListeningAction extends Action2 { static readonly ID = 'workbench.action.chat.stopListening'; diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts index 4d260fce917..127d13be18b 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction, KeywordActivationContribution } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; +import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction, KeywordActivationContribution, InstallVoiceChatAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; import { registerAction2 } from 'vs/platform/actions/common/actions'; 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'; registerAction2(StartVoiceChatAction); +registerAction2(InstallVoiceChatAction); registerAction2(VoiceChatInChatViewAction); registerAction2(QuickVoiceChatAction); From d96a8b66cec5062667ab854c2c8861e05c332b57 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 7 Feb 2024 15:54:49 +0100 Subject: [PATCH 0974/1897] SCM - Use Publish action when the local branch does not have an upstream (#204617) --- extensions/git/package.json | 15 +++++- src/vs/workbench/contrib/scm/browser/menus.ts | 48 +++++++++---------- .../contrib/scm/browser/scmViewPane.ts | 10 ++-- .../workbench/contrib/scm/common/history.ts | 6 +-- 4 files changed, 41 insertions(+), 38 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 874588dd9aa..88553d91126 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -629,6 +629,7 @@ "command": "git.publish", "title": "%command.publish%", "category": "Git", + "icon": "$(cloud-upload)", "enablement": "!operationInProgress" }, { @@ -1887,14 +1888,24 @@ { "command": "git.pushRef", "group": "navigation", - "when": "scmProvider == git" + "when": "scmProvider == git && scmHistoryItemGroupHasUpstream" + }, + { + "command": "git.publish", + "group": "navigation", + "when": "scmProvider == git && !scmHistoryItemGroupHasUpstream" } ], "scm/outgoingChanges/context": [ { "command": "git.pushRef", "group": "1_modification@1", - "when": "scmProvider == git" + "when": "scmProvider == git && scmHistoryItemGroupHasUpstream" + }, + { + "command": "git.publish", + "group": "1_modification@1", + "when": "scmProvider == git && !scmHistoryItemGroupHasUpstream" } ], "scm/outgoingChanges/allChanges/context": [ diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index f79b6050a70..39ee31783bd 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -14,7 +14,7 @@ import { IMenu, IMenuService, MenuId, MenuRegistry } from 'vs/platform/actions/c import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { ISCMHistoryProviderMenus, SCMHistoryItemTreeElement } from 'vs/workbench/contrib/scm/common/history'; +import { ISCMHistoryProviderMenus, SCMHistoryItemGroupTreeElement, SCMHistoryItemTreeElement } from 'vs/workbench/contrib/scm/common/history'; import { ISCMMenus, ISCMProvider, ISCMRepository, ISCMRepositoryMenus, ISCMResource, ISCMResourceGroup, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; function actionEquals(a: IAction, b: IAction): boolean { @@ -258,38 +258,26 @@ export class SCMHistoryProviderMenus implements ISCMHistoryProviderMenus, IDispo private readonly historyItemMenus = new Map(); private readonly disposables = new DisposableStore(); - private _incomingHistoryItemGroupMenu: IMenu; - get incomingHistoryItemGroupMenu(): IMenu { return this._incomingHistoryItemGroupMenu; } - - private _incomingHistoryItemGroupContextMenu: IMenu; - get incomingHistoryItemGroupContextMenu(): IMenu { return this._incomingHistoryItemGroupContextMenu; } - - private _outgoingHistoryItemGroupMenu: IMenu; - get outgoingHistoryItemGroupMenu(): IMenu { return this._outgoingHistoryItemGroupMenu; } - - private _outgoingHistoryItemGroupContextMenu: IMenu; - get outgoingHistoryItemGroupContextMenu(): IMenu { return this._outgoingHistoryItemGroupContextMenu; } - constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IMenuService private readonly menuService: IMenuService) { - this._incomingHistoryItemGroupMenu = this.menuService.createMenu(MenuId.SCMIncomingChanges, this.contextKeyService); - this.disposables.add(this._incomingHistoryItemGroupMenu); - - this._incomingHistoryItemGroupContextMenu = this.menuService.createMenu(MenuId.SCMIncomingChangesContext, this.contextKeyService); - this.disposables.add(this._incomingHistoryItemGroupContextMenu); - - this._outgoingHistoryItemGroupMenu = this.menuService.createMenu(MenuId.SCMOutgoingChanges, this.contextKeyService); - this.disposables.add(this._outgoingHistoryItemGroupMenu); - - this._outgoingHistoryItemGroupContextMenu = this.menuService.createMenu(MenuId.SCMOutgoingChangesContext, this.contextKeyService); - this.disposables.add(this._outgoingHistoryItemGroupContextMenu); - } + @IMenuService private readonly menuService: IMenuService) { } getHistoryItemMenu(historyItem: SCMHistoryItemTreeElement): IMenu { return this.getOrCreateHistoryItemMenu(historyItem); } + getHistoryItemGroupMenu(historyItemGroup: SCMHistoryItemGroupTreeElement): IMenu { + return historyItemGroup.direction === 'incoming' ? + this.menuService.createMenu(MenuId.SCMIncomingChanges, this.contextKeyService) : + this.getOutgoingHistoryItemGroupMenu(MenuId.SCMOutgoingChanges, historyItemGroup); + } + + getHistoryItemGroupContextMenu(historyItemGroup: SCMHistoryItemGroupTreeElement): IMenu { + return historyItemGroup.direction === 'incoming' ? + this.menuService.createMenu(MenuId.SCMIncomingChangesContext, this.contextKeyService) : + this.getOutgoingHistoryItemGroupMenu(MenuId.SCMOutgoingChangesContext, historyItemGroup); + } + private getOrCreateHistoryItemMenu(historyItem: SCMHistoryItemTreeElement): IMenu { let result = this.historyItemMenus.get(historyItem); @@ -316,6 +304,14 @@ export class SCMHistoryProviderMenus implements ISCMHistoryProviderMenus, IDispo return result; } + private getOutgoingHistoryItemGroupMenu(menuId: MenuId, historyItemGroup: SCMHistoryItemGroupTreeElement): IMenu { + const contextKeyService = this.contextKeyService.createOverlay([ + ['scmHistoryItemGroupHasUpstream', !!historyItemGroup.repository.provider.historyProvider?.currentHistoryItemGroup?.base], + ]); + + return this.menuService.createMenu(menuId, contextKeyService); + } + dispose(): void { this.disposables.dispose(); } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index e64b8ce1809..57f3428b3dc 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -831,11 +831,11 @@ class HistoryItemGroupRenderer implements ICompressibleTreeRenderer { - templateData.toolBar.setActions(primary, secondary, [menuId]); + templateData.toolBar.setActions(primary, secondary, [resetMenuId]); })); templateData.toolBar.context = historyItemGroup; @@ -3037,9 +3037,7 @@ export class SCMViewPane extends ViewPane { } } else if (isSCMHistoryItemGroupTreeElement(element)) { const menus = this.scmViewService.menus.getRepositoryMenus(element.repository.provider); - const menu = element.direction === 'incoming' ? - menus.historyProviderMenu?.incomingHistoryItemGroupContextMenu : - menus.historyProviderMenu?.outgoingHistoryItemGroupContextMenu; + const menu = menus.historyProviderMenu?.getHistoryItemGroupContextMenu(element); if (menu) { actionRunner = new HistoryItemGroupActionRunner(); diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index ff86e3693fc..70ab5a8f8bb 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -10,10 +10,8 @@ import { IMenu } from 'vs/platform/actions/common/actions'; import { ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; export interface ISCMHistoryProviderMenus { - readonly incomingHistoryItemGroupMenu: IMenu; - readonly incomingHistoryItemGroupContextMenu: IMenu; - readonly outgoingHistoryItemGroupMenu: IMenu; - readonly outgoingHistoryItemGroupContextMenu: IMenu; + getHistoryItemGroupMenu(historyItemGroup: SCMHistoryItemGroupTreeElement): IMenu; + getHistoryItemGroupContextMenu(historyItemGroup: SCMHistoryItemGroupTreeElement): IMenu; getHistoryItemMenu(historyItem: SCMHistoryItemTreeElement): IMenu; } From eef0e11f849d3457d5a6633086032e53b0ee2e17 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 7 Feb 2024 12:30:04 -0300 Subject: [PATCH 0975/1897] Don't double-up @workspace (#204625) Fix #204624 --- .../contrib/chat/browser/actions/chatActions.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 02c86b79c0f..929fb17898c 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -120,7 +120,16 @@ export class ChatSubmitSecondaryAgentEditorAction extends EditorAction2 { } const widgetService = accessor.get(IChatWidgetService); - widgetService.getWidgetByInputUri(editorUri)?.acceptInputWithPrefix(`${chatAgentLeader}${secondaryAgent.id}`); + const widget = widgetService.getWidgetByInputUri(editorUri); + if (!widget) { + return; + } + + if (widget.getInput().match(/^\s*@/)) { + widget.acceptInput(); + } else { + widget.acceptInputWithPrefix(`${chatAgentLeader}${secondaryAgent.id}`); + } } } } From 8fb476b6020d3294eefa608f49bbe867d0d134aa Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 7 Feb 2024 17:06:28 +0100 Subject: [PATCH 0976/1897] chat - keep toolbar lean when hiding items (#204631) --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 3 ++- .../workbench/contrib/inlineChat/browser/inlineChatWidget.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index ce8a6081bea..eb906af265f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -20,7 +20,7 @@ import { IModelService } from 'vs/editor/common/services/model'; import { HoverController } from 'vs/editor/contrib/hover/browser/hover'; import { localize } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -302,6 +302,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge menuOptions: { shouldForwardArgs: true }, + hiddenItemStrategy: HiddenItemStrategy.Ignore, // keep it lean when hiding items and avoid a "..." overflow menu actionViewItemProvider: (action, options) => { if (action.id === SubmitAction.ID) { return this.instantiationService.createInstance(SubmitButtonActionViewItem, { widget } satisfies IChatExecuteActionContext, action, options); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 8f730b52fe2..b631e16714d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -371,7 +371,8 @@ export class InlineChatWidget { this._store.add(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.editorToolbar, _options.menuId, { telemetrySource: 'interactiveEditorWidget-toolbar', - toolbarOptions: { primaryGroup: 'main' } + toolbarOptions: { primaryGroup: 'main' }, + hiddenItemStrategy: HiddenItemStrategy.Ignore // keep it lean when hiding items and avoid a "..." overflow menu })); this._progressBar = new ProgressBar(this._elements.progress); From 9b129a0cf3a3b3cc4a603b26d60061ce833be772 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 7 Feb 2024 08:07:26 -0800 Subject: [PATCH 0977/1897] polish (#204627) --- src/vs/workbench/api/common/extHostChatProvider.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index 5493be6a2b5..c76f419515b 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -185,10 +185,10 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { $updateAccesslist(data: { extension: ExtensionIdentifier; enabled: boolean }[]): void { const updated = new ExtensionIdentifierSet(); - for (const { extension, enabled: allowed } of data) { + for (const { extension, enabled } of data) { const oldValue = this._accesslist.get(extension); - if (oldValue !== allowed) { - this._accesslist.set(extension, allowed); + if (oldValue !== enabled) { + this._accesslist.set(extension, enabled); updated.add(extension); } } From d9337a1721cf75a74b84edace86447b089fa9413 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 7 Feb 2024 08:11:57 -0800 Subject: [PATCH 0978/1897] - default extension access list (#204633) - justify message to get access --- .../api/browser/mainThreadChatProvider.ts | 2 +- .../workbench/api/browser/viewsExtensionPoint.ts | 4 ++-- .../api/common/configurationExtensionPoint.ts | 2 +- .../api/common/jsonValidationExtensionPoint.ts | 2 +- .../contrib/chat/browser/chat.contribution.ts | 2 +- .../common/codeActionsExtensionPoint.ts | 2 +- .../customEditor/common/extensionPoint.ts | 2 +- .../contrib/debug/common/debugSchemas.ts | 2 +- .../extensions/browser/extensionFeaturesTab.ts | 2 +- .../common/localization.contribution.ts | 2 +- .../notebook/browser/notebookExtensionPoint.ts | 4 ++-- .../actions/common/menusExtensionPoint.ts | 2 +- .../browser/authenticationService.ts | 2 +- .../common/extensionFeatures.ts | 7 +++++-- .../common/extensionFeaturesManagemetService.ts | 16 ++++++++++++---- .../common/abstractExtensionService.ts | 2 +- .../services/language/common/languageService.ts | 2 +- .../themes/common/colorExtensionPoint.ts | 2 +- .../themes/common/themeExtensionPoints.ts | 2 +- 19 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index 96f65a744b8..24ce8d39797 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -70,7 +70,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { } async $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise { - const access = await this._extensionFeaturesManagementService.getAccess(extension, CHAT_FEATURE_ID); + const access = await this._extensionFeaturesManagementService.getAccess(extension, CHAT_FEATURE_ID, justification); if (!access) { return undefined; } diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index b67c8eb616e..fe77f8325b3 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -765,7 +765,7 @@ class ViewsDataRenderer extends Disposable implements IExtensionFeatureTableRend Registry.as(ExtensionFeaturesRegistryExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: 'viewsContainers', label: localize('viewsContainers', "View Containers"), - enablement: { + access: { canToggle: false }, renderer: new SyncDescriptor(ViewContainersDataRenderer), @@ -774,7 +774,7 @@ Registry.as(ExtensionFeaturesRegistryExtensions.Exte Registry.as(ExtensionFeaturesRegistryExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: 'views', label: localize('views', "Views"), - enablement: { + access: { canToggle: false }, renderer: new SyncDescriptor(ViewsDataRenderer), diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index db7cec83a6a..57fa4cef735 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -434,7 +434,7 @@ class SettingsTableRenderer extends Disposable implements IExtensionFeatureTable Registry.as(ExtensionFeaturesExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: 'configuration', label: nls.localize('settings', "Settings"), - enablement: { + access: { canToggle: false }, renderer: new SyncDescriptor(SettingsTableRenderer), diff --git a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts index f4edaebde1e..94a2f00ff58 100644 --- a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts +++ b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts @@ -127,7 +127,7 @@ class JSONValidationDataRenderer extends Disposable implements IExtensionFeature Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: 'jsonValidation', label: nls.localize('jsonValidation', "JSON Validation"), - enablement: { + access: { canToggle: false }, renderer: new SyncDescriptor(JSONValidationDataRenderer), diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index b7df6c82ceb..de1f9258da0 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -343,7 +343,7 @@ Registry.as(ExtensionFeaturesExtensions.ExtensionFea id: CHAT_FEATURE_ID, label: nls.localize('chat', "Chat"), description: nls.localize('chatFeatureDescription', "Allows the extension to make requests to the Large Language Model (LLM)."), - enablement: { + access: { canToggle: true, requireUserConsent: true, }, diff --git a/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts b/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts index a1d1c4688bb..4e119df579a 100644 --- a/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts @@ -119,7 +119,7 @@ class CodeActionsTableRenderer extends Disposable implements IExtensionFeatureTa Registry.as(ExtensionFeaturesExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: 'codeActions', label: nls.localize('codeactions', "Code Actions"), - enablement: { + access: { canToggle: false, }, renderer: new SyncDescriptor(CodeActionsTableRenderer), diff --git a/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts b/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts index 22719e22732..4be82b83e13 100644 --- a/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts +++ b/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts @@ -148,7 +148,7 @@ class CustomEditorsDataRenderer extends Disposable implements IExtensionFeatureT Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: 'customEditors', label: nls.localize('customEditors', "Custom Editors"), - enablement: { + access: { canToggle: false }, renderer: new SyncDescriptor(CustomEditorsDataRenderer), diff --git a/src/vs/workbench/contrib/debug/common/debugSchemas.ts b/src/vs/workbench/contrib/debug/common/debugSchemas.ts index bf5341f8cd2..31ec9fc410a 100644 --- a/src/vs/workbench/contrib/debug/common/debugSchemas.ts +++ b/src/vs/workbench/contrib/debug/common/debugSchemas.ts @@ -306,7 +306,7 @@ class DebuggersDataRenderer extends Disposable implements IExtensionFeatureTable Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: 'debuggers', label: nls.localize('debuggers', "Debuggers"), - enablement: { + access: { canToggle: false }, renderer: new SyncDescriptor(DebuggersDataRenderer), diff --git a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts index fd893b1bc02..71a1018af4d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts @@ -272,7 +272,7 @@ class ExtensionFeatureView extends Disposable { const title = append(header, $('.feature-title')); title.textContent = this.feature.label; - if (this.feature.enablement.canToggle) { + if (this.feature.access.canToggle) { const actionsContainer = append(header, $('.feature-actions')); const button = new Button(actionsContainer, defaultButtonStyles); this.updateButtonLabel(button); diff --git a/src/vs/workbench/contrib/localization/common/localization.contribution.ts b/src/vs/workbench/contrib/localization/common/localization.contribution.ts index d4a05b38d86..4b8d4c29554 100644 --- a/src/vs/workbench/contrib/localization/common/localization.contribution.ts +++ b/src/vs/workbench/contrib/localization/common/localization.contribution.ts @@ -118,7 +118,7 @@ class LocalizationsDataRenderer extends Disposable implements IExtensionFeatureT Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: 'localizations', label: localize('localizations', "Langauage Packs"), - enablement: { + access: { canToggle: false }, renderer: new SyncDescriptor(LocalizationsDataRenderer), diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts b/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts index b620ce43e53..267d93ba91c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts @@ -350,7 +350,7 @@ class NotebookRenderersDataRenderer extends Disposable implements IExtensionFeat Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: 'notebooks', label: nls.localize('notebooks', "Notebooks"), - enablement: { + access: { canToggle: false }, renderer: new SyncDescriptor(NotebooksDataRenderer), @@ -359,7 +359,7 @@ Registry.as(Extensions.ExtensionFeaturesRegistry).re Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: 'notebookRenderer', label: nls.localize('notebookRenderer', "Notebook Renderers"), - enablement: { + access: { canToggle: false }, renderer: new SyncDescriptor(NotebookRenderersDataRenderer), diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index f6fdb36e554..413b9db0e95 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -1090,7 +1090,7 @@ class CommandsTableRenderer extends Disposable implements IExtensionFeatureTable Registry.as(ExtensionFeaturesExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: 'commands', label: localize('commands', "Commands"), - enablement: { + access: { canToggle: false, }, renderer: new SyncDescriptor(CommandsTableRenderer), diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index df4b8d65679..401fb69b0e7 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -214,7 +214,7 @@ class AuthenticationDataRenderer extends Disposable implements IExtensionFeature Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: 'authentication', label: nls.localize('authentication', "Authentication"), - enablement: { + access: { canToggle: false }, renderer: new SyncDescriptor(AuthenticationDataRenderer), diff --git a/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts b/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts index 724f4d189a0..d4d4e85cd06 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts @@ -11,6 +11,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IDisposable } from 'vs/base/common/lifecycle'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import Severity from 'vs/base/common/severity'; +import { IStringDictionary } from 'vs/base/common/collections'; export namespace Extensions { export const ExtensionFeaturesRegistry = 'workbench.registry.extensionFeatures'; @@ -48,9 +49,11 @@ export interface IExtensionFeatureDescriptor { readonly id: string; readonly label: string; readonly description?: string; - readonly enablement: { + readonly access: { readonly canToggle?: boolean; readonly requireUserConsent?: boolean; + readonly default?: boolean; + readonly extensionsList?: IStringDictionary; }; readonly renderer: SyncDescriptor; } @@ -80,7 +83,7 @@ export interface IExtensionFeaturesManagementService { setEnablement(extension: ExtensionIdentifier, featureId: string, enabled: boolean): void; getEnablementData(featureId: string): { readonly extension: ExtensionIdentifier; readonly enabled: boolean }[]; - getAccess(extension: ExtensionIdentifier, featureId: string): Promise; + getAccess(extension: ExtensionIdentifier, featureId: string, justification?: string): Promise; readonly onDidChangeAccessData: Event<{ readonly extension: ExtensionIdentifier; readonly featureId: string; readonly accessData: IExtensionFeatureAccessData }>; getAccessData(extension: ExtensionIdentifier, featureId: string): IExtensionFeatureAccessData | undefined; diff --git a/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts b/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts index 5cdaa860c57..615b61ce206 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts @@ -12,7 +12,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; import { IStringDictionary } from 'vs/base/common/collections'; -import { Mutable } from 'vs/base/common/types'; +import { Mutable, isBoolean } from 'vs/base/common/types'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { localize } from 'vs/nls'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -55,7 +55,15 @@ class ExtensionFeaturesManagementService extends Disposable implements IExtensio if (!feature) { return false; } - return !(this.getExtensionFeatureState(extension, featureId)?.disabled ?? false); + const isDisabled = this.getExtensionFeatureState(extension, featureId)?.disabled; + if (isBoolean(isDisabled)) { + return !isDisabled; + } + const defaultExtensionAccess = feature.access.extensionsList?.[extension.value]; + if (isBoolean(defaultExtensionAccess)) { + return defaultExtensionAccess; + } + return !!feature.access.default; } setEnablement(extension: ExtensionIdentifier, featureId: string, enabled: boolean): void { @@ -85,7 +93,7 @@ class ExtensionFeaturesManagementService extends Disposable implements IExtensio return result; } - async getAccess(extension: ExtensionIdentifier, featureId: string): Promise { + async getAccess(extension: ExtensionIdentifier, featureId: string, justification?: string): Promise { const feature = this.registry.getExtensionFeature(featureId); if (!feature) { return false; @@ -100,7 +108,7 @@ class ExtensionFeaturesManagementService extends Disposable implements IExtensio const confirmationResult = await this.dialogService.confirm({ title: localize('accessExtensionFeature', "Access '{0}' Feature", feature.label), message: localize('accessExtensionFeatureMessage', "'{0}' extension would like to access the '{1}' feature.", extensionDescription?.displayName ?? extension.value, feature.label), - detail: feature.description, + detail: justification ?? feature.description, custom: true, primaryButton: localize('allow', "Allow"), cancelButton: localize('disallow', "Don't Allow"), diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index e8f7f3a5eab..c44dc4d24d9 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -1428,7 +1428,7 @@ class ActivationFeatureMarkdowneRenderer extends Disposable implements IExtensio Registry.as(ExtensionFeaturesExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: 'activation', label: nls.localize('activation', "Activation"), - enablement: { + access: { canToggle: false }, renderer: new SyncDescriptor(ActivationFeatureMarkdowneRenderer), diff --git a/src/vs/workbench/services/language/common/languageService.ts b/src/vs/workbench/services/language/common/languageService.ts index b7b66c60a1e..c7e93a09c44 100644 --- a/src/vs/workbench/services/language/common/languageService.ts +++ b/src/vs/workbench/services/language/common/languageService.ts @@ -200,7 +200,7 @@ class LanguageTableRenderer extends Disposable implements IExtensionFeatureTable Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: 'languages', label: localize('languages', "Programming Languages"), - enablement: { + access: { canToggle: false }, renderer: new SyncDescriptor(LanguageTableRenderer), diff --git a/src/vs/workbench/services/themes/common/colorExtensionPoint.ts b/src/vs/workbench/services/themes/common/colorExtensionPoint.ts index 9997259fd94..30f8cd24987 100644 --- a/src/vs/workbench/services/themes/common/colorExtensionPoint.ts +++ b/src/vs/workbench/services/themes/common/colorExtensionPoint.ts @@ -199,7 +199,7 @@ class ColorDataRenderer extends Disposable implements IExtensionFeatureTableRend Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: 'colors', label: nls.localize('colors', "Colors"), - enablement: { + access: { canToggle: false }, renderer: new SyncDescriptor(ColorDataRenderer), diff --git a/src/vs/workbench/services/themes/common/themeExtensionPoints.ts b/src/vs/workbench/services/themes/common/themeExtensionPoints.ts index 5fdf01d4a15..2f6cdb23b15 100644 --- a/src/vs/workbench/services/themes/common/themeExtensionPoints.ts +++ b/src/vs/workbench/services/themes/common/themeExtensionPoints.ts @@ -147,7 +147,7 @@ class ThemeDataRenderer extends Disposable implements IExtensionFeatureMarkdownR Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: 'themes', label: nls.localize('themes', "Themes"), - enablement: { + access: { canToggle: false }, renderer: new SyncDescriptor(ThemeDataRenderer), From b92e9b2b73c280f47d993f68b6a581d51b7cfa32 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 7 Feb 2024 10:13:02 -0600 Subject: [PATCH 0979/1897] statusIndicators->signals --- .../audioCues/browser/audioCueService.ts | 60 +++--- .../browser/accessibilityConfiguration.ts | 202 +++++++++--------- .../contrib/audioCues/browser/commands.ts | 24 +-- 3 files changed, 143 insertions(+), 143 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 69e3551c778..b9a6d92c5c1 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -200,9 +200,9 @@ export class AudioCueService extends Disposable implements IAudioCueService { private readonly isCueEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.settingsKey) || e.affectsConfiguration(event.cue.accessibilityStatusIndicatorSettingsKey) + e.affectsConfiguration(event.cue.settingsKey) || e.affectsConfiguration(event.cue.signalSettingsKey) ), - () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.cue.accessibilityStatusIndicatorSettingsKey + '.audioCue') + () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.cue.signalSettingsKey + '.audioCue') ); return derived(reader => { /** @description audio cue enabled */ @@ -231,9 +231,9 @@ export class AudioCueService extends Disposable implements IAudioCueService { private readonly isAlertEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.alertSettingsKey!) || e.affectsConfiguration(event.cue.accessibilityStatusIndicatorSettingsKey) + e.affectsConfiguration(event.cue.alertSettingsKey!) || e.affectsConfiguration(event.cue.signalSettingsKey) ), - () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.accessibilityStatusIndicatorSettingsKey + '.alert') : false + () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.signalSettingsKey + '.alert') : false ); return derived(reader => { /** @description alert enabled */ @@ -390,12 +390,12 @@ export class AudioCue { randomOneOf: Sound[]; }; settingsKey: string; - accessibilityStatusIndicatorSettingsKey: string; + signalSettingsKey: string; alertSettingsKey?: AccessibilityAlertSettingId; alertMessage?: string; }): AudioCue { const soundSource = new SoundSource('randomOneOf' in options.sound ? options.sound.randomOneOf : [options.sound]); - const audioCue = new AudioCue(soundSource, options.name, options.settingsKey, options.accessibilityStatusIndicatorSettingsKey, options.alertSettingsKey, options.alertMessage); + const audioCue = new AudioCue(soundSource, options.name, options.settingsKey, options.signalSettingsKey, options.alertSettingsKey, options.alertMessage); AudioCue._audioCues.add(audioCue); return audioCue; } @@ -410,7 +410,7 @@ export class AudioCue { settingsKey: 'audioCues.lineHasError', alertSettingsKey: AccessibilityAlertSettingId.Error, alertMessage: localize('accessibility.statusIndicators.lineHasError', 'Error'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasError' + signalSettingsKey: 'accessibility.statusIndicators.lineHasError' }); public static readonly warning = AudioCue.register({ name: localize('audioCues.lineHasWarning.name', 'Warning on Line'), @@ -418,7 +418,7 @@ export class AudioCue { settingsKey: 'audioCues.lineHasWarning', alertSettingsKey: AccessibilityAlertSettingId.Warning, alertMessage: localize('accessibility.statusIndicators.lineHasWarning', 'Warning'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasWarning' + signalSettingsKey: 'accessibility.statusIndicators.lineHasWarning' }); public static readonly foldedArea = AudioCue.register({ name: localize('audioCues.lineHasFoldedArea.name', 'Folded Area on Line'), @@ -426,7 +426,7 @@ export class AudioCue { settingsKey: 'audioCues.lineHasFoldedArea', alertSettingsKey: AccessibilityAlertSettingId.FoldedArea, alertMessage: localize('accessibility.statusIndicators.lineHasFoldedArea', 'Folded'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasFoldedArea' + signalSettingsKey: 'accessibility.statusIndicators.lineHasFoldedArea' }); public static readonly break = AudioCue.register({ name: localize('audioCues.lineHasBreakpoint.name', 'Breakpoint on Line'), @@ -434,13 +434,13 @@ export class AudioCue { settingsKey: 'audioCues.lineHasBreakpoint', alertSettingsKey: AccessibilityAlertSettingId.Breakpoint, alertMessage: localize('accessibility.statusIndicators.lineHasBreakpoint', 'Breakpoint'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasBreakpoint' + signalSettingsKey: 'accessibility.statusIndicators.lineHasBreakpoint' }); public static readonly inlineSuggestion = AudioCue.register({ name: localize('audioCues.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), sound: Sound.quickFixes, settingsKey: 'audioCues.lineHasInlineSuggestion', - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasInlineSuggestion' + signalSettingsKey: 'accessibility.statusIndicators.lineHasInlineSuggestion' }); public static readonly terminalQuickFix = AudioCue.register({ @@ -449,7 +449,7 @@ export class AudioCue { settingsKey: 'audioCues.terminalQuickFix', alertSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, alertMessage: localize('accessibility.statusIndicators.terminalQuickFix', 'Quick Fix'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.terminalQuickFix' + signalSettingsKey: 'accessibility.statusIndicators.terminalQuickFix' }); public static readonly onDebugBreak = AudioCue.register({ @@ -458,7 +458,7 @@ export class AudioCue { settingsKey: 'audioCues.onDebugBreak', alertSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, alertMessage: localize('accessibility.statusIndicators.onDebugBreak', 'Breakpoint'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.onDebugBreak' + signalSettingsKey: 'accessibility.statusIndicators.onDebugBreak' }); public static readonly noInlayHints = AudioCue.register({ @@ -467,7 +467,7 @@ export class AudioCue { settingsKey: 'audioCues.noInlayHints', alertSettingsKey: AccessibilityAlertSettingId.NoInlayHints, alertMessage: localize('accessibility.statusIndicators.noInlayHints', 'No Inlay Hints'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.noInlayHints' + signalSettingsKey: 'accessibility.statusIndicators.noInlayHints' }); public static readonly taskCompleted = AudioCue.register({ @@ -476,7 +476,7 @@ export class AudioCue { settingsKey: 'audioCues.taskCompleted', alertSettingsKey: AccessibilityAlertSettingId.TaskCompleted, alertMessage: localize('accessibility.statusIndicators.taskCompleted', 'Task Completed'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.taskCompleted' + signalSettingsKey: 'accessibility.statusIndicators.taskCompleted' }); public static readonly taskFailed = AudioCue.register({ @@ -485,7 +485,7 @@ export class AudioCue { settingsKey: 'audioCues.taskFailed', alertSettingsKey: AccessibilityAlertSettingId.TaskFailed, alertMessage: localize('accessibility.statusIndicators.taskFailed', 'Task Failed'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.taskFailed' + signalSettingsKey: 'accessibility.statusIndicators.taskFailed' }); public static readonly terminalCommandFailed = AudioCue.register({ @@ -494,7 +494,7 @@ export class AudioCue { settingsKey: 'audioCues.terminalCommandFailed', alertSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, alertMessage: localize('accessibility.statusIndicators.terminalCommandFailed', 'Command Failed'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.terminalCommandFailed' + signalSettingsKey: 'accessibility.statusIndicators.terminalCommandFailed' }); public static readonly terminalBell = AudioCue.register({ @@ -503,7 +503,7 @@ export class AudioCue { settingsKey: 'audioCues.terminalBell', alertSettingsKey: AccessibilityAlertSettingId.TerminalBell, alertMessage: localize('accessibility.statusIndicators.terminalBell', 'Terminal Bell'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.terminalBell' + signalSettingsKey: 'accessibility.statusIndicators.terminalBell' }); public static readonly notebookCellCompleted = AudioCue.register({ @@ -512,7 +512,7 @@ export class AudioCue { settingsKey: 'audioCues.notebookCellCompleted', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, alertMessage: localize('accessibility.statusIndicators.notebookCellCompleted', 'Notebook Cell Completed'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.notebookCellCompleted' + signalSettingsKey: 'accessibility.statusIndicators.notebookCellCompleted' }); public static readonly notebookCellFailed = AudioCue.register({ @@ -521,28 +521,28 @@ export class AudioCue { settingsKey: 'audioCues.notebookCellFailed', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, alertMessage: localize('accessibility.statusIndicators.notebookCellFailed', 'Notebook Cell Failed'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.notebookCellFailed' + signalSettingsKey: 'accessibility.statusIndicators.notebookCellFailed' }); public static readonly diffLineInserted = AudioCue.register({ name: localize('audioCues.diffLineInserted', 'Diff Line Inserted'), sound: Sound.diffLineInserted, settingsKey: 'audioCues.diffLineInserted', - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.diffLineInserted' + signalSettingsKey: 'accessibility.statusIndicators.diffLineInserted' }); public static readonly diffLineDeleted = AudioCue.register({ name: localize('audioCues.diffLineDeleted', 'Diff Line Deleted'), sound: Sound.diffLineDeleted, settingsKey: 'audioCues.diffLineDeleted', - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.diffLineDeleted' + signalSettingsKey: 'accessibility.statusIndicators.diffLineDeleted' }); public static readonly diffLineModified = AudioCue.register({ name: localize('audioCues.diffLineModified', 'Diff Line Modified'), sound: Sound.diffLineModified, settingsKey: 'audioCues.diffLineModified', - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.diffLineModified' + signalSettingsKey: 'accessibility.statusIndicators.diffLineModified' }); public static readonly chatRequestSent = AudioCue.register({ @@ -551,7 +551,7 @@ export class AudioCue { settingsKey: 'audioCues.chatRequestSent', alertSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, alertMessage: localize('accessibility.statusIndicators.chatRequestSent', 'Chat Request Sent'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.chatRequestSent' + signalSettingsKey: 'accessibility.statusIndicators.chatRequestSent' }); public static readonly chatResponseReceived = AudioCue.register({ @@ -565,7 +565,7 @@ export class AudioCue { Sound.chatResponseReceived4 ] }, - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.chatResponseReceived' + signalSettingsKey: 'accessibility.statusIndicators.chatResponseReceived' }); public static readonly chatResponsePending = AudioCue.register({ @@ -574,7 +574,7 @@ export class AudioCue { settingsKey: 'audioCues.chatResponsePending', alertSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, alertMessage: localize('accessibility.statusIndicators.chatResponsePending', 'Chat Response Pending'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.chatResponsePending' + signalSettingsKey: 'accessibility.statusIndicators.chatResponsePending' }); public static readonly clear = AudioCue.register({ @@ -583,7 +583,7 @@ export class AudioCue { settingsKey: 'audioCues.clear', alertSettingsKey: AccessibilityAlertSettingId.Clear, alertMessage: localize('accessibility.statusIndicators.clear', 'Clear'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.clear' + signalSettingsKey: 'accessibility.statusIndicators.clear' }); public static readonly save = AudioCue.register({ @@ -592,7 +592,7 @@ export class AudioCue { settingsKey: 'audioCues.save', alertSettingsKey: AccessibilityAlertSettingId.Save, alertMessage: localize('accessibility.statusIndicators.save', 'Save'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.save' + signalSettingsKey: 'accessibility.statusIndicators.save' }); public static readonly format = AudioCue.register({ @@ -601,14 +601,14 @@ export class AudioCue { settingsKey: 'audioCues.format', alertSettingsKey: AccessibilityAlertSettingId.Format, alertMessage: localize('accessibility.statusIndicators.format', 'Format'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.format' + signalSettingsKey: 'accessibility.statusIndicators.format' }); private constructor( public readonly sound: SoundSource, public readonly name: string, public readonly settingsKey: string, - public readonly accessibilityStatusIndicatorSettingsKey: string, + public readonly signalSettingsKey: string, public readonly alertSettingsKey?: string, public readonly alertMessage?: string, ) { } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index ca6fb5c8d2e..7a16cca7739 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -266,312 +266,312 @@ const configuration: IConfigurationNode = { type: 'boolean', default: true }, - 'accessibility.statusIndicators.debouncePositionChanges': { - 'description': localize('accessibility.statusIndicators.debouncePositionChanges', "Whether or not position changes should be debounced"), + 'accessibility.signals.debouncePositionChanges': { + 'description': localize('accessibility.signals.debouncePositionChanges', "Whether or not position changes should be debounced"), 'type': 'boolean', 'default': false, tags: ['accessibility'] }, - 'accessibility.statusIndicators.lineHasBreakpoint': { + 'accessibility.signals.lineHasBreakpoint': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), + 'description': localize('accessibility.signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), + 'description': localize('accessibility.signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.lineHasBreakpoint.alert', "Indicates when the active line has a breakpoint."), + 'description': localize('accessibility.signals.lineHasBreakpoint.alert', "Indicates when the active line has a breakpoint."), ...alertFeatureBase }, }, }, - 'accessibility.statusIndicators.lineHasInlineSuggestion': { + 'accessibility.signals.lineHasInlineSuggestion': { ...defaultNoAlert, - 'description': localize('accessibility.statusIndicators.lineHasInlineSuggestion', "Indicates when the active line has an inline suggestion."), + 'description': localize('accessibility.signals.lineHasInlineSuggestion', "Indicates when the active line has an inline suggestion."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), + 'description': localize('accessibility.signals.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), ...audioCueFeatureBase } } }, - 'accessibility.statusIndicators.lineHasError': { + 'accessibility.signals.lineHasError': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.lineHasError', "Indicates when the active line has an error."), + 'description': localize('accessibility.signals.lineHasError', "Indicates when the active line has an error."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), + 'description': localize('accessibility.signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.lineHasError.alert', "Indicates when the active line has an error."), + 'description': localize('accessibility.signals.lineHasError.alert', "Indicates when the active line has an error."), ...alertFeatureBase }, }, }, - 'accessibility.statusIndicators.lineHasFoldedArea': { + 'accessibility.signals.lineHasFoldedArea': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.lineHasFoldedArea', "Indicates when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.signals.lineHasFoldedArea', "Indicates when the active line has a folded area that can be unfolded."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.lineHasFoldedArea.alert', "Indicates when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.signals.lineHasFoldedArea.alert', "Indicates when the active line has a folded area that can be unfolded."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.lineHasWarning': { + 'accessibility.signals.lineHasWarning': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.lineHasWarning', "Plays a signal when the active line has a warning."), + 'description': localize('accessibility.signals.lineHasWarning', "Plays a signal when the active line has a warning."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), + 'description': localize('accessibility.signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.lineHasWarning.alert', "Indicates when the active line has a warning."), + 'description': localize('accessibility.signals.lineHasWarning.alert', "Indicates when the active line has a warning."), ...alertFeatureBase }, }, }, - 'accessibility.statusIndicators.onDebugBreak': { + 'accessibility.signals.onDebugBreak': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), + 'description': localize('accessibility.signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), + 'description': localize('accessibility.signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.onDebugBreak.alert', "Indicates when the debugger stopped on a breakpoint."), + 'description': localize('accessibility.signals.onDebugBreak.alert', "Indicates when the debugger stopped on a breakpoint."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.noInlayHints': { + 'accessibility.signals.noInlayHints': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), + 'description': localize('accessibility.signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), + 'description': localize('accessibility.signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.noInlayHints.alert', "Indicates when trying to read a line with inlay hints that has no inlay hints."), + 'description': localize('accessibility.signals.noInlayHints.alert', "Indicates when trying to read a line with inlay hints that has no inlay hints."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.taskCompleted': { + 'accessibility.signals.taskCompleted': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.taskCompleted', "Plays a signal when a task is completed."), + 'description': localize('accessibility.signals.taskCompleted', "Plays a signal when a task is completed."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), + 'description': localize('accessibility.signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.taskCompleted.alert', "Indicates when a task is completed."), + 'description': localize('accessibility.signals.taskCompleted.alert', "Indicates when a task is completed."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.taskFailed': { + 'accessibility.signals.taskFailed': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), + 'description': localize('accessibility.signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), + 'description': localize('accessibility.signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.taskFailed.alert', "Indicates when a task fails (non-zero exit code)."), + 'description': localize('accessibility.signals.taskFailed.alert', "Indicates when a task fails (non-zero exit code)."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.terminalCommandFailed': { + 'accessibility.signals.terminalCommandFailed': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), + 'description': localize('accessibility.signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), + 'description': localize('accessibility.signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.terminalCommandFailed.alert', "Indicates when a terminal command fails (non-zero exit code)."), + 'description': localize('accessibility.signals.terminalCommandFailed.alert', "Indicates when a terminal command fails (non-zero exit code)."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.terminalQuickFix': { + 'accessibility.signals.terminalQuickFix': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), + 'description': localize('accessibility.signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), + 'description': localize('accessibility.signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.terminalQuickFix.alert', "Indicates when terminal Quick Fixes are available."), + 'description': localize('accessibility.signals.terminalQuickFix.alert', "Indicates when terminal Quick Fixes are available."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.terminalBell': { + 'accessibility.signals.terminalBell': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.terminalBell', "Plays a signal when the terminal bell is ringing."), + 'description': localize('accessibility.signals.terminalBell', "Plays a signal when the terminal bell is ringing."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), + 'description': localize('accessibility.signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.terminalBell.alert', "Indicates when the terminal bell is ringing."), + 'description': localize('accessibility.signals.terminalBell.alert', "Indicates when the terminal bell is ringing."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.diffLineInserted': { + 'accessibility.signals.diffLineInserted': { ...defaultNoAlert, - 'description': localize('accessibility.statusIndicators.diffLineInserted', "Indicates when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.signals.diffLineInserted', "Indicates when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.signals.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), ...audioCueFeatureBase } } }, - 'accessibility.statusIndicators.diffLineModified': { + 'accessibility.signals.diffLineModified': { ...defaultNoAlert, - 'description': localize('accessibility.statusIndicators.diffLineModified', "Indicates when the focus moves to an modified line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.signals.diffLineModified', "Indicates when the focus moves to an modified line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), ...audioCueFeatureBase } } }, - 'accessibility.statusIndicators.diffLineDeleted': { + 'accessibility.signals.diffLineDeleted': { ...defaultNoAlert, - 'description': localize('accessibility.statusIndicators.diffLineDeleted', "Indicates when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.signals.diffLineDeleted', "Indicates when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), ...audioCueFeatureBase } } }, - 'accessibility.statusIndicators.notebookCellCompleted': { + 'accessibility.signals.notebookCellCompleted': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), + 'description': localize('accessibility.signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), + 'description': localize('accessibility.signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.notebookCellCompleted.alert', "Indicates when a notebook cell execution is successfully completed."), + 'description': localize('accessibility.signals.notebookCellCompleted.alert', "Indicates when a notebook cell execution is successfully completed."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.notebookCellFailed': { + 'accessibility.signals.notebookCellFailed': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), + 'description': localize('accessibility.signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), + 'description': localize('accessibility.signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.notebookCellFailed.alert', "Indicates when a notebook cell execution fails."), + 'description': localize('accessibility.signals.notebookCellFailed.alert', "Indicates when a notebook cell execution fails."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.chatRequestSent': { + 'accessibility.signals.chatRequestSent': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.chatRequestSent', "Plays a signal when a chat request is made."), + 'description': localize('accessibility.signals.chatRequestSent', "Plays a signal when a chat request is made."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), + 'description': localize('accessibility.signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.chatRequestSent.alert', "Indicates when a chat request is made."), + 'description': localize('accessibility.signals.chatRequestSent.alert', "Indicates when a chat request is made."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.chatResponsePending': { + 'accessibility.signals.chatResponsePending': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.chatResponsePending', "Plays a signal on loop while the response is pending."), + 'description': localize('accessibility.signals.chatResponsePending', "Plays a signal on loop while the response is pending."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), + 'description': localize('accessibility.signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.chatResponsePending.alert', "Alerts on loop while the response is pending."), + 'description': localize('accessibility.signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), ...alertFeatureBase }, }, }, - 'accessibility.statusIndicators.chatResponseReceived': { + 'accessibility.signals.chatResponseReceived': { ...defaultNoAlert, - 'description': localize('accessibility.statusIndicators.chatResponseReceived', "Indicates when the response has been received."), + 'description': localize('accessibility.signals.chatResponseReceived', "Indicates when the response has been received."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), + 'description': localize('accessibility.signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), ...audioCueFeatureBase }, } }, - 'accessibility.statusIndicators.clear': { + 'accessibility.signals.clear': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), + 'description': localize('accessibility.signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.clear.audioCue', "Plays an audio cue when a feature is cleared."), + 'description': localize('accessibility.signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.clear.alert', "Indicates when a feature is cleared."), + 'description': localize('accessibility.signals.clear.alert', "Indicates when a feature is cleared."), ...alertFeatureBase }, }, }, - 'accessibility.statusIndicators.save': { + 'accessibility.signals.save': { 'type': 'object', 'tags': ['accessibility'], additionalProperties: true, - 'markdownDescription': localize('accessibility.statusIndicators.save', "Plays a signal when a file is saved."), + 'markdownDescription': localize('accessibility.signals.save', "Plays a signal when a file is saved."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.save.audioCue', "Plays an audio cue when a file is saved."), + 'description': localize('accessibility.signals.save.audioCue', "Plays an audio cue when a file is saved."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('accessibility.statusIndicators.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), - localize('accessibility.statusIndicators.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), - localize('accessibility.statusIndicators.save.audioCue.never', "Never plays the audio cue.") + localize('accessibility.signals.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), + localize('accessibility.signals.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), + localize('accessibility.signals.save.audioCue.never', "Never plays the audio cue.") ], }, 'alert': { - 'description': localize('accessibility.statusIndicators.save.alert', "Indicates when a file is saved."), + 'description': localize('accessibility.signals.save.alert', "Indicates when a file is saved."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('accessibility.statusIndicators.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), - localize('accessibility.statusIndicators.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), - localize('accessibility.statusIndicators.save.alert.never', "Never plays the audio cue.") + localize('accessibility.signals.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), + localize('accessibility.signals.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), + localize('accessibility.signals.save.alert.never', "Never plays the audio cue.") ], }, }, @@ -580,32 +580,32 @@ const configuration: IConfigurationNode = { 'alert': 'never' } }, - 'accessibility.statusIndicators.format': { + 'accessibility.signals.format': { 'type': 'object', 'tags': ['accessibility'], additionalProperties: true, - 'markdownDescription': localize('accessibility.statusIndicators.format', "Plays a signal when a file or notebook is formatted."), + 'markdownDescription': localize('accessibility.signals.format', "Plays a signal when a file or notebook is formatted."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), + 'description': localize('accessibility.signals.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('accessibility.statusIndicators.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), - localize('accessibility.statusIndicators.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('accessibility.statusIndicators.format.never', "Never plays the audio cue.") + localize('accessibility.signals.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), + localize('accessibility.signals.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('accessibility.signals.format.never', "Never plays the audio cue.") ], }, 'alert': { - 'description': localize('accessibility.statusIndicators.format.alert', "Indicates when a file or notebook is formatted."), + 'description': localize('accessibility.signals.format.alert', "Indicates when a file or notebook is formatted."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('accessibility.statusIndicators.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), - localize('accessibility.statusIndicators.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('accessibility.statusIndicators.format.alert.never', "Never plays the alert.") + localize('accessibility.signals.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), + localize('accessibility.signals.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('accessibility.signals.format.alert.never', "Never plays the alert.") ], }, } diff --git a/src/vs/workbench/contrib/audioCues/browser/commands.ts b/src/vs/workbench/contrib/audioCues/browser/commands.ts index 6a2e3e52786..624c67985db 100644 --- a/src/vs/workbench/contrib/audioCues/browser/commands.ts +++ b/src/vs/workbench/contrib/audioCues/browser/commands.ts @@ -31,7 +31,7 @@ export class ShowAudioCueHelp extends Action2 { const accessibilityService = accessor.get(IAccessibilityService); const userGestureCues = [AudioCue.save, AudioCue.format]; const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.map((cue, idx) => ({ - label: userGestureCues.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.accessibilityStatusIndicatorSettingsKey + '.audioCue')})` : cue.name, + label: userGestureCues.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.signalSettingsKey + '.audioCue')})` : cue.name, audioCue: cue, buttons: userGestureCues.includes(cue) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), @@ -47,22 +47,22 @@ export class ShowAudioCueHelp extends Action2 { const disabledCues = AudioCue.allAudioCues.filter(cue => !enabledCues.includes(cue)); for (const cue of enabledCues) { if (!userGestureCues.includes(cue)) { - let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.accessibilityStatusIndicatorSettingsKey); + let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.signalSettingsKey); audioCue = accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'; if (alert) { - configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue, alert }); + configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); } else { - configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue }); + configurationService.updateValue(cue.signalSettingsKey, { audioCue }); } } } for (const cue of disabledCues) { - const alert = cue.alertMessage ? configurationService.getValue(cue.accessibilityStatusIndicatorSettingsKey + '.alert') : undefined; + const alert = cue.alertMessage ? configurationService.getValue(cue.signalSettingsKey + '.alert') : undefined; const audioCue = userGestureCues.includes(cue) ? 'never' : 'off'; if (alert) { - configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue, alert }); + configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); } else { - configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue }); + configurationService.updateValue(cue.signalSettingsKey, { audioCue }); } } qp.hide(); @@ -94,7 +94,7 @@ export class ShowAccessibilityAlertHelp extends Action2 { const accessibilityService = accessor.get(IAccessibilityService); const userGestureAlerts = [AudioCue.save, AudioCue.format]; const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.filter(c => c.alertSettingsKey).map((cue, idx) => ({ - label: userGestureAlerts.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.accessibilityStatusIndicatorSettingsKey + '.alert')})` : cue.name, + label: userGestureAlerts.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.signalSettingsKey + '.alert')})` : cue.name, audioCue: cue, buttons: userGestureAlerts.includes(cue) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), @@ -110,17 +110,17 @@ export class ShowAccessibilityAlertHelp extends Action2 { const disabledAlerts = AudioCue.allAudioCues.filter(cue => !enabledAlerts.includes(cue)); for (const cue of enabledAlerts) { if (!userGestureAlerts.includes(cue)) { - let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.accessibilityStatusIndicatorSettingsKey); + let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.signalSettingsKey); alert = cue.alertMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; if (alert) { - configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue, alert }); + configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); } } } for (const cue of disabledAlerts) { const alert = userGestureAlerts.includes(cue) ? 'never' : 'off'; - const audioCue = configurationService.getValue(cue.accessibilityStatusIndicatorSettingsKey + '.audioCue'); - configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue, alert }); + const audioCue = configurationService.getValue(cue.signalSettingsKey + '.audioCue'); + configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); } qp.hide(); }); From 7e3804fda94e417729cadb75dfca66cb84b7f0ab Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Wed, 7 Feb 2024 08:50:23 -0800 Subject: [PATCH 0980/1897] Add first item to context menu for notebook variables (#204549) * first context menu action * stick with notebook term for now * cleanup --- src/vs/platform/actions/common/actions.ts | 1 + .../debug/browser/debug.contribution.ts | 3 ++ .../notebookVariableCommands.ts | 32 +++++++++++++++++++ .../notebookVariablesView.ts | 32 +++++++++++++++++++ .../actions/common/menusExtensionPoint.ts | 5 +++ 5 files changed, 73 insertions(+) create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 78a18824a45..376627398ac 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -52,6 +52,7 @@ export class MenuId { static readonly DebugCallStackContext = new MenuId('DebugCallStackContext'); static readonly DebugConsoleContext = new MenuId('DebugConsoleContext'); static readonly DebugVariablesContext = new MenuId('DebugVariablesContext'); + static readonly NotebookVariablesContext = new MenuId('NotebookVariablesContext'); static readonly DebugHoverContext = new MenuId('DebugHoverContext'); static readonly DebugWatchContext = new MenuId('DebugWatchContext'); static readonly DebugToolBar = new MenuId('DebugToolBar'); diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 8cd702486b8..a30c4d62dd8 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -53,6 +53,7 @@ import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugCon import { DebugLifecycle } from 'vs/workbench/contrib/debug/common/debugLifecycle'; import { DebugVisualizerService, IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; +import { COPY_NOTEBOOK_VARIABLE_VALUE_ID, COPY_NOTEBOOK_VARIABLE_VALUE_LABEL } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands'; import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -196,6 +197,8 @@ registerDebugViewMenuItem(MenuId.DebugWatchContext, VIEW_MEMORY_ID, nls.localize registerDebugViewMenuItem(MenuId.DebugWatchContext, REMOVE_EXPRESSION_COMMAND_ID, nls.localize('removeWatchExpression', "Remove Expression"), 20, CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression'), undefined, 'inline', icons.watchExpressionRemove); registerDebugViewMenuItem(MenuId.DebugWatchContext, REMOVE_WATCH_EXPRESSIONS_COMMAND_ID, REMOVE_WATCH_EXPRESSIONS_LABEL, 20, undefined, undefined, 'z_commands'); +registerDebugViewMenuItem(MenuId.NotebookVariablesContext, COPY_NOTEBOOK_VARIABLE_VALUE_ID, COPY_NOTEBOOK_VARIABLE_VALUE_LABEL, 20); + // Touch Bar if (isMacintosh) { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts new file mode 100644 index 00000000000..1e955f90bb2 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { contextMenuArg } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView'; + +export const COPY_NOTEBOOK_VARIABLE_VALUE_ID = 'workbench.debug.viewlet.action.copyWorkspaceVariableValue'; +export const COPY_NOTEBOOK_VARIABLE_VALUE_LABEL = localize('copyWorkspaceVariableValue', "Copy Value"); +registerAction2(class extends Action2 { + constructor() { + super({ + id: COPY_NOTEBOOK_VARIABLE_VALUE_ID, + title: COPY_NOTEBOOK_VARIABLE_VALUE_LABEL, + f1: false, + precondition: ContextKeyExpr.has('value') + }); + } + + run(accessor: ServicesAccessor, context: contextMenuArg): void { + const clipboardService = accessor.get(IClipboardService); + + if (context.value) { + clipboardService.writeText(context.value); + } + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index d96d4933328..d7c5246450c 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -3,14 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; +import { IAction } from 'vs/base/common/actions'; import { RunOnceScheduler } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { ILocalizedString } from 'vs/platform/action/common/action'; +import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; + import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; @@ -28,6 +33,8 @@ import { ICellExecutionStateChangedEvent, IExecutionStateChangedEvent, INotebook import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +export type contextMenuArg = { source?: string; type?: string; value?: string }; + export class NotebookVariablesView extends ViewPane { static readonly ID = 'notebookVariablesView'; @@ -35,6 +42,7 @@ export class NotebookVariablesView extends ViewPane { private tree: WorkbenchAsyncDataTree | undefined; private activeNotebook: NotebookTextModel | undefined; + private readonly menu: IMenu; private readonly dataSource: NotebookVariableDataSource; private updateScheduler: RunOnceScheduler; @@ -55,6 +63,7 @@ export class NotebookVariablesView extends ViewPane { @ICommandService protected commandService: ICommandService, @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, + @IMenuService menuService: IMenuService ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); @@ -64,6 +73,7 @@ export class NotebookVariablesView extends ViewPane { this.setActiveNotebook(); + this.menu = menuService.createMenu(MenuId.NotebookVariablesContext, contextKeyService); this.dataSource = new NotebookVariableDataSource(this.notebookKernelService); this.updateScheduler = new RunOnceScheduler(() => this.tree?.updateChildren(), 100); } @@ -87,6 +97,28 @@ export class NotebookVariablesView extends ViewPane { if (this.activeNotebook) { this.tree.setInput({ kind: 'root', notebook: this.activeNotebook }); } + + this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); + } + + private onContextMenu(e: ITreeContextMenuEvent): any { + const element = e.element; + + const context = { + type: element?.type + }; + const arg: contextMenuArg = { + source: element?.notebook.uri.toString(), + value: element?.value, + ...context + }; + const actions: IAction[] = []; + createAndFillInContextMenuActions(this.menu, { arg, shouldForwardArgs: true }, actions); + this.contextMenuService.showContextMenu({ + getAnchor: () => e.anchor, + getActions: () => actions, + getActionsContext: () => context, + }); } protected override layoutBody(height: number, width: number): void { diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 413b9db0e95..981c5a0ed62 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -107,6 +107,11 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.DebugToolBar, description: localize('menus.debugToolBar', "The debug toolbar menu") }, + { + key: 'notebook/variables/context', + id: MenuId.NotebookVariablesContext, + description: localize('menus.notebookVariablesContext', "The notebook variables view context menu") + }, { key: 'menuBar/home', id: MenuId.MenubarHomeMenu, From 26b5aec03655a338a606b6ca6cce175d9fe4f13e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 7 Feb 2024 08:51:00 -0800 Subject: [PATCH 0981/1897] some fixes (#204636) --- .../workbench/contrib/chat/browser/chat.contribution.ts | 8 +------- .../extensionManagement/common/extensionFeatures.ts | 1 - .../common/extensionFeaturesManagemetService.ts | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index de1f9258da0..f334bfc59fa 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -299,14 +299,8 @@ class ChatFeatureMarkdowneRenderer extends Disposable implements IExtensionFeatu shouldRender(manifest: IExtensionManifest): boolean { const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); - if (!this.extensionFeaturesManagementService.isEnabled(extensionId, CHAT_FEATURE_ID)) { - return true; - } const accessData = this.extensionFeaturesManagementService.getAccessData(extensionId, CHAT_FEATURE_ID); - if (accessData?.totalCount) { - return true; - } - return false; + return !!accessData; } render(manifest: IExtensionManifest): IRenderedData { diff --git a/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts b/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts index d4d4e85cd06..becb8e21f37 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts @@ -52,7 +52,6 @@ export interface IExtensionFeatureDescriptor { readonly access: { readonly canToggle?: boolean; readonly requireUserConsent?: boolean; - readonly default?: boolean; readonly extensionsList?: IStringDictionary; }; readonly renderer: SyncDescriptor; diff --git a/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts b/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts index 615b61ce206..460eaafc515 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts @@ -63,7 +63,7 @@ class ExtensionFeaturesManagementService extends Disposable implements IExtensio if (isBoolean(defaultExtensionAccess)) { return defaultExtensionAccess; } - return !!feature.access.default; + return !feature.access.requireUserConsent; } setEnablement(extension: ExtensionIdentifier, featureId: string, enabled: boolean): void { From ee29464b843eb6b4bd6db804c6710505380cf238 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 7 Feb 2024 17:53:00 +0100 Subject: [PATCH 0982/1897] voice - :lipstick: install experience (#204634) * voice - allow chat in notebooks and :lipstick: install experience * . --- .../actions/voiceChatActions.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index bb711aea86f..3b605a096d5 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -48,7 +48,6 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ProgressLocation } from 'vs/platform/progress/common/progress'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; @@ -506,6 +505,8 @@ export class StartVoiceChatAction extends Action2 { } } +const InstallingSpeechProvider = new RawContextKey('installingSpeechProvider', false, true); + export class InstallVoiceChatAction extends Action2 { static readonly ID = 'workbench.action.chat.installVoiceChat'; @@ -519,6 +520,7 @@ export class InstallVoiceChatAction extends Action2 { f1: false, category: CHAT_CATEGORY, icon: Codicon.mic, + precondition: InstallingSpeechProvider.negate(), menu: [{ id: MenuId.ChatExecute, when: HasSpeechProvider.negate(), @@ -534,8 +536,7 @@ export class InstallVoiceChatAction extends Action2 { } async run(accessor: ServicesAccessor): Promise { - const commandService = accessor.get(ICommandService); - const extensionService = accessor.get(IExtensionService); + const contextKeyService = accessor.get(IContextKeyService); const dialogService = accessor.get(IDialogService); const extensionManagementService = accessor.get(IExtensionsWorkbenchService); @@ -546,7 +547,7 @@ export class InstallVoiceChatAction extends Action2 { if (extension.state === ExtensionState.Installed) { await dialogService.info( - localize('enableExtensionMessage', "Microphone support requires an extension. Pleas enable it."), + localize('enableExtensionMessage', "Microphone support requires an extension. Please enable it."), localize('enableExtensionDetail', "Extension '{0}' is currently disabled.", InstallVoiceChatAction.SPEECH_EXTENSION_ID), ); @@ -563,10 +564,12 @@ export class InstallVoiceChatAction extends Action2 { return; } - const extensionRunning = Event.toPromise(Event.filter(extensionService.onDidChangeExtensionsStatus, e => e.some(e => e.value === InstallVoiceChatAction.SPEECH_EXTENSION_ID))); - await extensionManagementService.install(extension, undefined, ProgressLocation.Notification); - await extensionRunning; - commandService.executeCommand(StartVoiceChatAction.ID); + try { + InstallingSpeechProvider.bindTo(contextKeyService).set(true); + await extensionManagementService.install(extension, undefined, ProgressLocation.Notification); + } finally { + InstallingSpeechProvider.bindTo(contextKeyService).set(false); + } } } From c6be170d981b81267cbc8e11bbc64303ef23f69c Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 7 Feb 2024 17:53:48 +0100 Subject: [PATCH 0983/1897] api - polish `ChatResponseFileTreePart` (#204630) https://github.com/microsoft/vscode/issues/199908 --- src/vs/workbench/api/common/extHost.api.impl.ts | 2 +- src/vs/workbench/api/common/extHostChatAgents2.ts | 2 +- src/vs/workbench/api/common/extHostTypeConverters.ts | 4 ++-- src/vs/workbench/api/common/extHostTypes.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 08d86933303..dd4a2772606 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1650,7 +1650,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I KeywordRecognitionStatus: extHostTypes.KeywordRecognitionStatus, ChatResponseTextPart: extHostTypes.ChatResponseTextPart, ChatResponseMarkdownPart: extHostTypes.ChatResponseMarkdownPart, - ChatResponseFileTreePart: extHostTypes.ChatResponseFilesPart, + ChatResponseFileTreePart: extHostTypes.ChatResponseFileTreePart, ChatResponseAnchorPart: extHostTypes.ChatResponseAnchorPart, ChatResponseProgressPart: extHostTypes.ChatResponseProgressPart, ChatResponseReferencePart: extHostTypes.ChatResponseReferencePart, diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index b0c5f4405e7..dbcce3f64d7 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -86,7 +86,7 @@ class ChatAgentResponseStream { }, filetree(value, baseUri) { throwIfDone(this.filetree); - const part = new extHostTypes.ChatResponseFilesPart(value, baseUri); + const part = new extHostTypes.ChatResponseFileTreePart(value, baseUri); const dto = typeConvert.ChatResponseFilesPart.to(part); _report(dto); return this; diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index dba9b9f3a50..62a0c7e6c1a 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2390,7 +2390,7 @@ export namespace ChatResponseFilesPart { const baseUri = treeData.uri; const items = treeData.children ? convert(treeData.children) : []; - return new types.ChatResponseFilesPart(items, baseUri); + return new types.ChatResponseFileTreePart(items, baseUri); } } @@ -2450,7 +2450,7 @@ export namespace ChatResponsePart { return ChatResponseReferencePart.to(part); } else if (part instanceof types.ChatResponseProgressPart) { return ChatResponseProgressPart.to(part); - } else if (part instanceof types.ChatResponseFilesPart) { + } else if (part instanceof types.ChatResponseFileTreePart) { return ChatResponseFilesPart.to(part); } return { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 3a52a722e00..bd09d9992b8 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4218,7 +4218,7 @@ export class ChatResponseMarkdownPart { } } -export class ChatResponseFilesPart { +export class ChatResponseFileTreePart { value: vscode.ChatResponseFileTree[]; baseUri: vscode.Uri; constructor(value: vscode.ChatResponseFileTree[], baseUri: vscode.Uri) { From cc2934a43e6a2be2db324402e2238895d4189785 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 7 Feb 2024 18:03:06 +0100 Subject: [PATCH 0984/1897] remove `ChatMessageRole#Function` (#204637) https://github.com/microsoft/vscode/issues/199908 --- src/vs/workbench/api/common/extHostTypeConverters.ts | 2 -- src/vs/workbench/api/common/extHostTypes.ts | 1 - src/vs/workbench/contrib/chat/common/chatProvider.ts | 1 - src/vscode-dts/vscode.proposed.chat.d.ts | 1 - 4 files changed, 5 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 62a0c7e6c1a..42a57e47c13 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2245,7 +2245,6 @@ export namespace ChatMessageRole { 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; } } @@ -2253,7 +2252,6 @@ export namespace 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/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index bd09d9992b8..b0269443d64 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4183,7 +4183,6 @@ export enum ChatMessageRole { System = 0, User = 1, Assistant = 2, - Function = 3, } export class ChatMessage implements vscode.ChatMessage { diff --git a/src/vs/workbench/contrib/chat/common/chatProvider.ts b/src/vs/workbench/contrib/chat/common/chatProvider.ts index 6305d770e3d..88349952e4e 100644 --- a/src/vs/workbench/contrib/chat/common/chatProvider.ts +++ b/src/vs/workbench/contrib/chat/common/chatProvider.ts @@ -14,7 +14,6 @@ export const enum ChatMessageRole { System, User, Assistant, - Function, } export interface IChatMessage { diff --git a/src/vscode-dts/vscode.proposed.chat.d.ts b/src/vscode-dts/vscode.proposed.chat.d.ts index 8585cb31205..170a995e56d 100644 --- a/src/vscode-dts/vscode.proposed.chat.d.ts +++ b/src/vscode-dts/vscode.proposed.chat.d.ts @@ -10,7 +10,6 @@ declare module 'vscode' { System = 0, User = 1, Assistant = 2, - Function = 3, } // ChatML From f97da26259ee6f7fec61cfc36249baaa55b54af9 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 7 Feb 2024 11:29:35 -0600 Subject: [PATCH 0985/1897] set default to false for inline alerts --- .../browser/accessibilityConfiguration.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index e8270539962..7f7aac2224a 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -282,7 +282,8 @@ const configuration: IConfigurationNode = { }, 'alert': { 'description': localize('accessibility.signals.lineHasBreakpoint.alert', "Indicates when the active line has a breakpoint."), - ...alertFeatureBase + ...alertFeatureBase, + default: 'off' }, }, }, @@ -306,7 +307,8 @@ const configuration: IConfigurationNode = { }, 'alert': { 'description': localize('accessibility.signals.lineHasError.alert', "Indicates when the active line has an error."), - ...alertFeatureBase + ...alertFeatureBase, + default: 'off' }, }, }, @@ -316,7 +318,8 @@ const configuration: IConfigurationNode = { 'properties': { 'audioCue': { 'description': localize('accessibility.signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), - ...audioCueFeatureBase + ...audioCueFeatureBase, + default: 'off' }, 'alert': { 'description': localize('accessibility.signals.lineHasFoldedArea.alert', "Indicates when the active line has a folded area that can be unfolded."), @@ -334,7 +337,8 @@ const configuration: IConfigurationNode = { }, 'alert': { 'description': localize('accessibility.signals.lineHasWarning.alert', "Indicates when the active line has a warning."), - ...alertFeatureBase + ...alertFeatureBase, + default: 'off' }, }, }, From 373768ecdfc97397167caaeccd287773b7db6a42 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 7 Feb 2024 11:35:40 -0600 Subject: [PATCH 0986/1897] fix issue --- .../audioCues/browser/audioCueService.ts | 88 +++++++++---------- .../preferences/browser/settingsLayout.ts | 6 +- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index b9a6d92c5c1..0da53fe2963 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -58,12 +58,12 @@ export class AudioCueService extends Disposable implements IAudioCueService { } private _migrateSettings(): void { - this.configurationService.updateValue('accessibility.statusIndicators.debouncePositionChanges', this.configurationService.getValue('audioCues.debouncePositionChanges')); + this.configurationService.updateValue('accessibility.signals.debouncePositionChanges', this.configurationService.getValue('audioCues.debouncePositionChanges')); const config = AudioCue.allAudioCues; for (const c of config) { const alertConfig = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; const audioCue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; - const newSettingsKey = c.settingsKey.replace('audioCues.', 'accessibility.statusIndicators.'); + const newSettingsKey = c.settingsKey.replace('audioCues.', 'accessibility.signals.'); if (alertConfig !== undefined && audioCue !== undefined) { let alert; if (typeof alertConfig === 'string') { @@ -73,7 +73,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { } this.configurationService.updateValue(newSettingsKey, { alert, audioCue }); } else if (!c.alertSettingsKey && audioCue !== undefined) { - this.configurationService.updateValue(newSettingsKey + '.audioCue', audioCue); + this.configurationService.updateValue(newSettingsKey, { audioCue }); } } } @@ -409,38 +409,38 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.lineHasError', alertSettingsKey: AccessibilityAlertSettingId.Error, - alertMessage: localize('accessibility.statusIndicators.lineHasError', 'Error'), - signalSettingsKey: 'accessibility.statusIndicators.lineHasError' + alertMessage: localize('accessibility.signals.lineHasError', 'Error'), + signalSettingsKey: 'accessibility.signals.lineHasError' }); public static readonly warning = AudioCue.register({ name: localize('audioCues.lineHasWarning.name', 'Warning on Line'), sound: Sound.warning, settingsKey: 'audioCues.lineHasWarning', alertSettingsKey: AccessibilityAlertSettingId.Warning, - alertMessage: localize('accessibility.statusIndicators.lineHasWarning', 'Warning'), - signalSettingsKey: 'accessibility.statusIndicators.lineHasWarning' + alertMessage: localize('accessibility.signals.lineHasWarning', 'Warning'), + signalSettingsKey: 'accessibility.signals.lineHasWarning' }); public static readonly foldedArea = AudioCue.register({ name: localize('audioCues.lineHasFoldedArea.name', 'Folded Area on Line'), sound: Sound.foldedArea, settingsKey: 'audioCues.lineHasFoldedArea', alertSettingsKey: AccessibilityAlertSettingId.FoldedArea, - alertMessage: localize('accessibility.statusIndicators.lineHasFoldedArea', 'Folded'), - signalSettingsKey: 'accessibility.statusIndicators.lineHasFoldedArea' + alertMessage: localize('accessibility.signals.lineHasFoldedArea', 'Folded'), + signalSettingsKey: 'accessibility.signals.lineHasFoldedArea' }); public static readonly break = AudioCue.register({ name: localize('audioCues.lineHasBreakpoint.name', 'Breakpoint on Line'), sound: Sound.break, settingsKey: 'audioCues.lineHasBreakpoint', alertSettingsKey: AccessibilityAlertSettingId.Breakpoint, - alertMessage: localize('accessibility.statusIndicators.lineHasBreakpoint', 'Breakpoint'), - signalSettingsKey: 'accessibility.statusIndicators.lineHasBreakpoint' + alertMessage: localize('accessibility.signals.lineHasBreakpoint', 'Breakpoint'), + signalSettingsKey: 'accessibility.signals.lineHasBreakpoint' }); public static readonly inlineSuggestion = AudioCue.register({ name: localize('audioCues.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), sound: Sound.quickFixes, settingsKey: 'audioCues.lineHasInlineSuggestion', - signalSettingsKey: 'accessibility.statusIndicators.lineHasInlineSuggestion' + signalSettingsKey: 'accessibility.signals.lineHasInlineSuggestion' }); public static readonly terminalQuickFix = AudioCue.register({ @@ -448,8 +448,8 @@ export class AudioCue { sound: Sound.quickFixes, settingsKey: 'audioCues.terminalQuickFix', alertSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, - alertMessage: localize('accessibility.statusIndicators.terminalQuickFix', 'Quick Fix'), - signalSettingsKey: 'accessibility.statusIndicators.terminalQuickFix' + alertMessage: localize('accessibility.signals.terminalQuickFix', 'Quick Fix'), + signalSettingsKey: 'accessibility.signals.terminalQuickFix' }); public static readonly onDebugBreak = AudioCue.register({ @@ -457,8 +457,8 @@ export class AudioCue { sound: Sound.break, settingsKey: 'audioCues.onDebugBreak', alertSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, - alertMessage: localize('accessibility.statusIndicators.onDebugBreak', 'Breakpoint'), - signalSettingsKey: 'accessibility.statusIndicators.onDebugBreak' + alertMessage: localize('accessibility.signals.onDebugBreak', 'Breakpoint'), + signalSettingsKey: 'accessibility.signals.onDebugBreak' }); public static readonly noInlayHints = AudioCue.register({ @@ -466,8 +466,8 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.noInlayHints', alertSettingsKey: AccessibilityAlertSettingId.NoInlayHints, - alertMessage: localize('accessibility.statusIndicators.noInlayHints', 'No Inlay Hints'), - signalSettingsKey: 'accessibility.statusIndicators.noInlayHints' + alertMessage: localize('accessibility.signals.noInlayHints', 'No Inlay Hints'), + signalSettingsKey: 'accessibility.signals.noInlayHints' }); public static readonly taskCompleted = AudioCue.register({ @@ -475,8 +475,8 @@ export class AudioCue { sound: Sound.taskCompleted, settingsKey: 'audioCues.taskCompleted', alertSettingsKey: AccessibilityAlertSettingId.TaskCompleted, - alertMessage: localize('accessibility.statusIndicators.taskCompleted', 'Task Completed'), - signalSettingsKey: 'accessibility.statusIndicators.taskCompleted' + alertMessage: localize('accessibility.signals.taskCompleted', 'Task Completed'), + signalSettingsKey: 'accessibility.signals.taskCompleted' }); public static readonly taskFailed = AudioCue.register({ @@ -484,8 +484,8 @@ export class AudioCue { sound: Sound.taskFailed, settingsKey: 'audioCues.taskFailed', alertSettingsKey: AccessibilityAlertSettingId.TaskFailed, - alertMessage: localize('accessibility.statusIndicators.taskFailed', 'Task Failed'), - signalSettingsKey: 'accessibility.statusIndicators.taskFailed' + alertMessage: localize('accessibility.signals.taskFailed', 'Task Failed'), + signalSettingsKey: 'accessibility.signals.taskFailed' }); public static readonly terminalCommandFailed = AudioCue.register({ @@ -493,8 +493,8 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.terminalCommandFailed', alertSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, - alertMessage: localize('accessibility.statusIndicators.terminalCommandFailed', 'Command Failed'), - signalSettingsKey: 'accessibility.statusIndicators.terminalCommandFailed' + alertMessage: localize('accessibility.signals.terminalCommandFailed', 'Command Failed'), + signalSettingsKey: 'accessibility.signals.terminalCommandFailed' }); public static readonly terminalBell = AudioCue.register({ @@ -502,8 +502,8 @@ export class AudioCue { sound: Sound.terminalBell, settingsKey: 'audioCues.terminalBell', alertSettingsKey: AccessibilityAlertSettingId.TerminalBell, - alertMessage: localize('accessibility.statusIndicators.terminalBell', 'Terminal Bell'), - signalSettingsKey: 'accessibility.statusIndicators.terminalBell' + alertMessage: localize('accessibility.signals.terminalBell', 'Terminal Bell'), + signalSettingsKey: 'accessibility.signals.terminalBell' }); public static readonly notebookCellCompleted = AudioCue.register({ @@ -511,8 +511,8 @@ export class AudioCue { sound: Sound.taskCompleted, settingsKey: 'audioCues.notebookCellCompleted', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, - alertMessage: localize('accessibility.statusIndicators.notebookCellCompleted', 'Notebook Cell Completed'), - signalSettingsKey: 'accessibility.statusIndicators.notebookCellCompleted' + alertMessage: localize('accessibility.signals.notebookCellCompleted', 'Notebook Cell Completed'), + signalSettingsKey: 'accessibility.signals.notebookCellCompleted' }); public static readonly notebookCellFailed = AudioCue.register({ @@ -520,29 +520,29 @@ export class AudioCue { sound: Sound.taskFailed, settingsKey: 'audioCues.notebookCellFailed', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, - alertMessage: localize('accessibility.statusIndicators.notebookCellFailed', 'Notebook Cell Failed'), - signalSettingsKey: 'accessibility.statusIndicators.notebookCellFailed' + alertMessage: localize('accessibility.signals.notebookCellFailed', 'Notebook Cell Failed'), + signalSettingsKey: 'accessibility.signals.notebookCellFailed' }); public static readonly diffLineInserted = AudioCue.register({ name: localize('audioCues.diffLineInserted', 'Diff Line Inserted'), sound: Sound.diffLineInserted, settingsKey: 'audioCues.diffLineInserted', - signalSettingsKey: 'accessibility.statusIndicators.diffLineInserted' + signalSettingsKey: 'accessibility.signals.diffLineInserted' }); public static readonly diffLineDeleted = AudioCue.register({ name: localize('audioCues.diffLineDeleted', 'Diff Line Deleted'), sound: Sound.diffLineDeleted, settingsKey: 'audioCues.diffLineDeleted', - signalSettingsKey: 'accessibility.statusIndicators.diffLineDeleted' + signalSettingsKey: 'accessibility.signals.diffLineDeleted' }); public static readonly diffLineModified = AudioCue.register({ name: localize('audioCues.diffLineModified', 'Diff Line Modified'), sound: Sound.diffLineModified, settingsKey: 'audioCues.diffLineModified', - signalSettingsKey: 'accessibility.statusIndicators.diffLineModified' + signalSettingsKey: 'accessibility.signals.diffLineModified' }); public static readonly chatRequestSent = AudioCue.register({ @@ -550,8 +550,8 @@ export class AudioCue { sound: Sound.chatRequestSent, settingsKey: 'audioCues.chatRequestSent', alertSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, - alertMessage: localize('accessibility.statusIndicators.chatRequestSent', 'Chat Request Sent'), - signalSettingsKey: 'accessibility.statusIndicators.chatRequestSent' + alertMessage: localize('accessibility.signals.chatRequestSent', 'Chat Request Sent'), + signalSettingsKey: 'accessibility.signals.chatRequestSent' }); public static readonly chatResponseReceived = AudioCue.register({ @@ -565,7 +565,7 @@ export class AudioCue { Sound.chatResponseReceived4 ] }, - signalSettingsKey: 'accessibility.statusIndicators.chatResponseReceived' + signalSettingsKey: 'accessibility.signals.chatResponseReceived' }); public static readonly chatResponsePending = AudioCue.register({ @@ -573,8 +573,8 @@ export class AudioCue { sound: Sound.chatResponsePending, settingsKey: 'audioCues.chatResponsePending', alertSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, - alertMessage: localize('accessibility.statusIndicators.chatResponsePending', 'Chat Response Pending'), - signalSettingsKey: 'accessibility.statusIndicators.chatResponsePending' + alertMessage: localize('accessibility.signals.chatResponsePending', 'Chat Response Pending'), + signalSettingsKey: 'accessibility.signals.chatResponsePending' }); public static readonly clear = AudioCue.register({ @@ -582,8 +582,8 @@ export class AudioCue { sound: Sound.clear, settingsKey: 'audioCues.clear', alertSettingsKey: AccessibilityAlertSettingId.Clear, - alertMessage: localize('accessibility.statusIndicators.clear', 'Clear'), - signalSettingsKey: 'accessibility.statusIndicators.clear' + alertMessage: localize('accessibility.signals.clear', 'Clear'), + signalSettingsKey: 'accessibility.signals.clear' }); public static readonly save = AudioCue.register({ @@ -591,8 +591,8 @@ export class AudioCue { sound: Sound.save, settingsKey: 'audioCues.save', alertSettingsKey: AccessibilityAlertSettingId.Save, - alertMessage: localize('accessibility.statusIndicators.save', 'Save'), - signalSettingsKey: 'accessibility.statusIndicators.save' + alertMessage: localize('accessibility.signals.save', 'Save'), + signalSettingsKey: 'accessibility.signals.save' }); public static readonly format = AudioCue.register({ @@ -600,8 +600,8 @@ export class AudioCue { sound: Sound.format, settingsKey: 'audioCues.format', alertSettingsKey: AccessibilityAlertSettingId.Format, - alertMessage: localize('accessibility.statusIndicators.format', 'Format'), - signalSettingsKey: 'accessibility.statusIndicators.format' + alertMessage: localize('accessibility.signals.format', 'Format'), + signalSettingsKey: 'accessibility.signals.format' }); private constructor( diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index 97a13dca2f9..54ac57ce224 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -227,9 +227,9 @@ export const tocData: ITOCEntry = { settings: ['audioCues.*'] }, { - id: 'features/accessibilityStatusIndicators', - label: localize('accessibility.statusIndicators', 'Accessibility Status Indicators'), - settings: ['accessibility.statusIndicators.*'] + id: 'features/accessibilitySignals', + label: localize('accessibility.signals', 'Accessibility Signals'), + settings: ['accessibility.signals.*'] }, { id: 'features/mergeEditor', From cfa5001723394bbe633c8fa14d539334a1111be7 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 7 Feb 2024 10:00:30 -0800 Subject: [PATCH 0987/1897] Allow type imports only in webview preloads (#204639) --- .eslintplugin/code-no-runtime-import.ts | 66 +++++++++++++++++++++++++ .eslintrc.json | 13 +++++ 2 files changed, 79 insertions(+) create mode 100644 .eslintplugin/code-no-runtime-import.ts diff --git a/.eslintplugin/code-no-runtime-import.ts b/.eslintplugin/code-no-runtime-import.ts new file mode 100644 index 00000000000..61597236e0c --- /dev/null +++ b/.eslintplugin/code-no-runtime-import.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import * as eslint from 'eslint'; +import { dirname, join, relative } from 'path'; +import minimatch from 'minimatch'; +import { createImportRuleListener } from './utils'; + +export = new class implements eslint.Rule.RuleModule { + + readonly meta: eslint.Rule.RuleMetaData = { + messages: { + layerbreaker: 'You are only allowed to import {{import}} from here using `import type ...`.' + }, + schema: { + type: "array", + items: { + type: "object", + additionalProperties: { + type: "array", + items: { + type: "string" + } + } + } + } + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + let fileRelativePath = relative(dirname(__dirname), context.getFilename()); + if (!fileRelativePath.endsWith('/')) { + fileRelativePath += '/'; + } + const ruleArgs = >context.options[0]; + + const matchingKey = Object.keys(ruleArgs).find(key => fileRelativePath.startsWith(key) || minimatch(fileRelativePath, key)); + if (!matchingKey) { + // nothing + return {}; + } + + const restrictedImports = ruleArgs[matchingKey]; + return createImportRuleListener((node, path) => { + if (path[0] === '.') { + path = join(dirname(context.getFilename()), path); + } + + if (( + restrictedImports.includes(path) || restrictedImports.some(restriction => minimatch(path, restriction)) + ) && !( + (node.parent?.type === TSESTree.AST_NODE_TYPES.ImportDeclaration && node.parent.importKind === 'type') || + (node.parent && 'exportKind' in node.parent && node.parent.exportKind === 'type'))) { // the export could be multiple types + context.report({ + loc: node.parent!.loc, + messageId: 'layerbreaker', + data: { + import: path + } + }); + } + }); + } +}; diff --git a/.eslintrc.json b/.eslintrc.json index 13cd7274b5e..0922babfe92 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1087,6 +1087,19 @@ } ] } + }, + { + "files": [ + "src/vs/workbench/contrib/notebook/browser/view/renderers/*.ts" + ], + "rules": { + "local/code-no-runtime-import": [ + "error", + { + "src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts": ["**/*"] + } + ] + } } ] } From bc056d2978262aeab95998388eeb33d1009545fe Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 7 Feb 2024 10:03:28 -0800 Subject: [PATCH 0988/1897] Enable go to definition in chat code blocks (#204555) * Enable go to definition in chat code blocks Hooks up code blocks to the `ITextModelService` so that link decorations work as well * Register ref --- .../contrib/chat/browser/chatListRenderer.ts | 33 +++++++++- .../contrib/chat/browser/codeBlockPart.ts | 64 ++++++++++++++----- 2 files changed, 78 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index b95b85a22d6..9983b28dd4e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -24,12 +24,14 @@ import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; import { marked } from 'vs/base/common/marked/marked'; -import { FileAccess } from 'vs/base/common/network'; +import { FileAccess, Schemas } from 'vs/base/common/network'; import { clamp } from 'vs/base/common/numbers'; import { basename } from 'vs/base/common/path'; import { equalsIgnoreCase } from 'vs/base/common/strings'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { Range } from 'vs/editor/common/core/range'; import { localize } from 'vs/nls'; @@ -39,6 +41,7 @@ import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { FileKind, FileType } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -55,7 +58,7 @@ import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo } from 'vs/workbenc import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; import { ChatMarkdownDecorationsRenderer, annotateSpecialMarkdownContent, extractVulnerabilitiesFromText } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; -import { ICodeBlockData, ICodeBlockPart, LocalFileCodeBlockPart, SimpleCodeBlockPart, localFileLanguageId, parseLocalFileData } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; +import { ChatCodeBlockContentProvider, ICodeBlockData, ICodeBlockPart, LocalFileCodeBlockPart, SimpleCodeBlockPart, localFileLanguageId, parseLocalFileData } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; @@ -143,6 +146,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer => { + if (input.resource.scheme !== Schemas.vscodeChatCodeBlock) { + return null; + } + const block = this._editorPool.find(input.resource); + if (!block) { + return null; + } + if (input.options?.selection) { + block.editor.setSelection({ + startLineNumber: input.options.selection.startLineNumber, + startColumn: input.options.selection.startColumn, + endLineNumber: input.options.selection.startLineNumber ?? input.options.selection.endLineNumber, + endColumn: input.options.selection.startColumn ?? input.options.selection.endColumn + }); + } + return block.editor; + })); + this._usedReferencesEnabled = configService.getValue('chat.experimental.usedReferences') ?? true; this._register(configService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('chat.experimental.usedReferences')) { @@ -1078,6 +1103,10 @@ class EditorPool extends Disposable { return this.getFromPool(data.type === 'localFile' ? this._localFileEditorPool : this._simpleEditorPool); } + find(resource: URI): SimpleCodeBlockPart | undefined { + return Array.from(this._simpleEditorPool.inUse).find(part => part.uri?.toString() === resource.toString()); + } + private getFromPool(pool: ResourcePool): IDisposableReference { const codeBlock = pool.get(); let stale = false; diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 5fb9f1e41f0..a68207d86c8 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -23,9 +23,10 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; -import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import { BracketMatchingController } from 'vs/editor/contrib/bracketMatching/browser/bracketMatching'; import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu'; +import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition'; import { HoverController } from 'vs/editor/contrib/hover/browser/hover'; import { ViewportSemanticTokensContribution } from 'vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens'; import { SmartSelectController } from 'vs/editor/contrib/smartSelect/browser/smartSelect'; @@ -133,7 +134,7 @@ abstract class BaseCodeBlockPart extends Disposable protected readonly _onDidChangeContentHeight = this._register(new Emitter()); public readonly onDidChangeContentHeight = this._onDidChangeContentHeight.event; - protected readonly editor: CodeEditorWidget; + public readonly editor: CodeEditorWidget; protected readonly toolbar: MenuWorkbenchToolBar; private readonly contextKeyService: IContextKeyService; @@ -322,7 +323,10 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart private currentCodeBlockData: ISimpleCodeBlockData | undefined; - private readonly textModel: ITextModel; + private readonly textModel: Promise; + + private readonly _uri: URI; + constructor( options: ChatEditorOptions, menuId: MenuId, @@ -331,6 +335,7 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @IModelService modelService: IModelService, + @ITextModelService textModelService: ITextModelService, @IConfigurationService configurationService: IConfigurationService, @IAccessibilityService accessibilityService: IAccessibilityService, @ILanguageService private readonly languageService: ILanguageService, @@ -350,9 +355,12 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart buttonSeparator: undefined, supportIcons: true }); - const modelUri = URI.from({ scheme: Schemas.vscodeChatCodeBlock, path: generateUuid() }); - this.textModel = this._register(this.modelService.createModel('', null, modelUri, false)); - this.editor.setModel(this.textModel); + this._uri = URI.from({ scheme: Schemas.vscodeChatCodeBlock, path: generateUuid() }); + this.textModel = textModelService.createModelReference(this._uri).then(ref => { + this.editor.setModel(ref.object.textEditorModel); + this._register(ref); + return ref.object.textEditorModel; + }); this.vulnsListElement = dom.append(vulnsContainer, $('ul.interactive-result-vulns-list')); @@ -367,7 +375,7 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart } get uri(): URI { - return this.textModel.uri; + return this._uri; } protected override createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): CodeEditorWidget { @@ -383,6 +391,7 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart BracketMatchingController.ID, SmartSelectController.ID, HoverController.ID, + GotoDefinitionAtPositionEditorContribution.ID, ]) })); } @@ -401,8 +410,8 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart } } - protected override updateEditor(data: ISimpleCodeBlockData): void { - this.editor.setModel(this.textModel); + protected override async updateEditor(data: ISimpleCodeBlockData): Promise { + this.editor.setModel(await this.textModel); const text = this.fixCodeText(data.text, data.languageId); this.setText(text); @@ -440,25 +449,26 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart return text; } - private setText(newText: string): void { - const currentText = this.textModel.getValue(EndOfLinePreference.LF); + private async setText(newText: string): Promise { + const model = await this.textModel; + const currentText = model.getValue(EndOfLinePreference.LF); if (newText === currentText) { return; } if (newText.startsWith(currentText)) { const text = newText.slice(currentText.length); - const lastLine = this.textModel.getLineCount(); - const lastCol = this.textModel.getLineMaxColumn(lastLine); - this.textModel.applyEdits([{ range: new Range(lastLine, lastCol, lastLine, lastCol), text }]); + const lastLine = model.getLineCount(); + const lastCol = model.getLineMaxColumn(lastLine); + model.applyEdits([{ range: new Range(lastLine, lastCol, lastLine, lastCol), text }]); } else { // console.log(`Failed to optimize setText`); - this.textModel.setValue(newText); + model.setValue(newText); } } - private setLanguage(vscodeLanguageId: string | undefined): void { - this.textModel.setLanguage(vscodeLanguageId ?? PLAINTEXT_LANGUAGE_ID); + private async setLanguage(vscodeLanguageId: string | undefined): Promise { + (await this.textModel).setLanguage(vscodeLanguageId ?? PLAINTEXT_LANGUAGE_ID); } } @@ -530,3 +540,23 @@ export class LocalFileCodeBlockPart extends BaseCodeBlockPart { + const existing = this._modelService.getModel(resource); + if (existing) { + return existing; + } + return this._modelService.createModel('', null, resource); + } +} From 348e88dc0099fcedf9578013692ce6ca761f3501 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 7 Feb 2024 10:06:45 -0800 Subject: [PATCH 0989/1897] Revert "Stop the cursor from jumping when changing prefix in QuickAccess (#198821)" (#204638) This reverts commit 3154b5f948de18462d34394aef216e585e25e7ba. --- src/vs/base/browser/ui/inputbox/inputBox.ts | 12 ----------- .../quickinput/browser/quickAccess.ts | 8 -------- .../platform/quickinput/browser/quickInput.ts | 20 ++----------------- .../quickinput/browser/quickInputBox.ts | 4 ---- 4 files changed, 2 insertions(+), 42 deletions(-) diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index 0a79ae4f813..e4c89dd3aff 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -305,18 +305,6 @@ export class InputBox extends Widget { return this.input.selectionEnd === this.input.value.length && this.input.selectionStart === this.input.selectionEnd; } - public getSelection(): IRange | null { - const selectionStart = this.input.selectionStart; - if (selectionStart === null) { - return null; - } - const selectionEnd = this.input.selectionEnd ?? selectionStart; - return { - start: selectionStart, - end: selectionEnd, - }; - } - public enable(): void { this.input.removeAttribute('disabled'); } diff --git a/src/vs/platform/quickinput/browser/quickAccess.ts b/src/vs/platform/quickinput/browser/quickAccess.ts index 7b4e601436f..cb35e451aa1 100644 --- a/src/vs/platform/quickinput/browser/quickAccess.ts +++ b/src/vs/platform/quickinput/browser/quickAccess.ts @@ -92,9 +92,6 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon } } - // Store the existing selection if there was one. - const visibleSelection = visibleQuickAccess?.picker?.valueSelection; - // Create a picker for the provider to use with the initial value // and adjust the filtering to exclude the prefix from filtering const disposables = new DisposableStore(); @@ -151,11 +148,6 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon // on the onDidHide event. picker.show(); - // If the previous picker had a selection, we should set that in the new picker. - if (visibleSelection) { - picker.valueSelection = visibleSelection; - } - // Pick mode: return with promise if (pick) { return pickPromise?.p; diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index ed28f8c0c1f..16b52782c26 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -725,15 +725,7 @@ export class QuickPick extends QuickInput implements I return this.ui.keyMods; } - get valueSelection() { - const selection = this.ui.inputBox.getSelection(); - if (!selection) { - return undefined; - } - return [selection.start, selection.end]; - } - - set valueSelection(valueSelection: Readonly<[number, number]> | undefined) { + set valueSelection(valueSelection: Readonly<[number, number]>) { this._valueSelection = valueSelection; this.valueSelectionUpdated = true; this.update(); @@ -1162,15 +1154,7 @@ export class InputBox extends QuickInput implements IInputBox { this.update(); } - get valueSelection() { - const selection = this.ui.inputBox.getSelection(); - if (!selection) { - return undefined; - } - return [selection.start, selection.end]; - } - - set valueSelection(valueSelection: Readonly<[number, number]> | undefined) { + set valueSelection(valueSelection: Readonly<[number, number]>) { this._valueSelection = valueSelection; this.valueSelectionUpdated = true; this.update(); diff --git a/src/vs/platform/quickinput/browser/quickInputBox.ts b/src/vs/platform/quickinput/browser/quickInputBox.ts index 9ee1440a91a..b3c6694387c 100644 --- a/src/vs/platform/quickinput/browser/quickInputBox.ts +++ b/src/vs/platform/quickinput/browser/quickInputBox.ts @@ -59,10 +59,6 @@ export class QuickInputBox extends Disposable { this.findInput.inputBox.select(range); } - getSelection(): IRange | null { - return this.findInput.inputBox.getSelection(); - } - isSelectionAtEnd(): boolean { return this.findInput.inputBox.isSelectionAtEnd(); } From e3de24d808d56666dd93b7796cb54bf4fd2fe5af Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 7 Feb 2024 12:13:14 -0600 Subject: [PATCH 0990/1897] try to migrate a different way --- .../audioCues/browser/audioCueService.ts | 22 ---------- .../browser/accessibilityConfiguration.ts | 44 ++++++++++++++++++- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 0da53fe2963..7c7dfbe0567 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -54,28 +54,6 @@ export class AudioCueService extends Disposable implements IAudioCueService { @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); - this._migrateSettings(); - } - - private _migrateSettings(): void { - this.configurationService.updateValue('accessibility.signals.debouncePositionChanges', this.configurationService.getValue('audioCues.debouncePositionChanges')); - const config = AudioCue.allAudioCues; - for (const c of config) { - const alertConfig = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; - const audioCue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; - const newSettingsKey = c.settingsKey.replace('audioCues.', 'accessibility.signals.'); - if (alertConfig !== undefined && audioCue !== undefined) { - let alert; - if (typeof alertConfig === 'string') { - alert = alertConfig; - } else { - alert = alertConfig === false ? 'off' : 'auto'; - } - this.configurationService.updateValue(newSettingsKey, { alert, audioCue }); - } else if (!c.alertSettingsKey && audioCue !== undefined) { - this.configurationService.updateValue(newSettingsKey, { audioCue }); - } - } } public async playAudioCue(cue: AudioCue, options: IAudioCueOptions = {}): Promise { diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 7f7aac2224a..0f9f0990ab1 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -7,8 +7,8 @@ import { localize } from 'vs/nls'; import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; -import { AccessibilityAlertSettingId } from 'vs/platform/audioCues/browser/audioCueService'; +import { workbenchConfigurationNodeBase, Extensions as WorkbenchExtensions, IConfigurationMigrationRegistry, ConfigurationKeyValuePairs } from 'vs/workbench/common/configuration'; +import { AccessibilityAlertSettingId, AudioCue } from 'vs/platform/audioCues/browser/audioCueService'; import { ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -687,3 +687,43 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen }); } } + + +Registry.as(WorkbenchExtensions.ConfigurationMigration) + .registerConfigurationMigrations([{ + key: 'audioCues.debouncePositionChanges', + migrateFn: (value, accessor) => { + return [ + ['accessibility.signals.debouncePositionChanges', { value }], + ['audioCues.debouncePositionChangess', { value: undefined }] + ]; + } + }]); + + +Registry.as(WorkbenchExtensions.ConfigurationMigration) + .registerConfigurationMigrations(AudioCue.allAudioCues.map(item => ({ + key: item.settingsKey, + migrateFn: (value: 'on' | 'off' | 'auto', accessor) => { + const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; + configurationKeyValuePairs.push([`${item.signalSettingsKey}.audioCue`, { value }]); + return configurationKeyValuePairs; + } + }))); + +Registry.as(WorkbenchExtensions.ConfigurationMigration) + .registerConfigurationMigrations(AudioCue.allAudioCues.filter(c => c.alertSettingsKey).map(item => ({ + key: item.alertSettingsKey!, + migrateFn: (value, accessor) => { + const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; + if (typeof value !== 'string') { + if (value === true) { + value = 'auto'; + } else { + value = 'off'; + } + } + configurationKeyValuePairs.push([`${item.signalSettingsKey}.alert`, { value }]); + return configurationKeyValuePairs; + } + }))); From 533da382079efc3a565cae64519421dacd3b6090 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 7 Feb 2024 10:32:23 -0800 Subject: [PATCH 0991/1897] Use `InEmbeddedEditor` context for chat code blocks (#204641) This als: - Renames the variable to match the context key name - Uses this context key to disable peek call hierarchy - Enable the normal go to def commands in these contexts (not the peek versions) --- .../editor/browser/widget/codeEditorWidget.ts | 8 ++--- src/vs/editor/common/editorContextKeys.ts | 2 +- .../gotoSymbol/browser/goToCommands.ts | 30 ++++++++----------- .../browser/callHierarchy.contribution.ts | 3 +- 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 1255cd1a598..63be27baefb 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -2095,7 +2095,7 @@ export class EditorModeContext extends Disposable { private readonly _hasMultipleDocumentSelectionFormattingProvider: IContextKey; private readonly _hasSignatureHelpProvider: IContextKey; private readonly _hasInlayHintsProvider: IContextKey; - private readonly _isInWalkThrough: IContextKey; + private readonly _isInEmbeddedEditor: IContextKey; constructor( private readonly _editor: CodeEditorWidget, @@ -2123,7 +2123,7 @@ export class EditorModeContext extends Disposable { this._hasDocumentSelectionFormattingProvider = EditorContextKeys.hasDocumentSelectionFormattingProvider.bindTo(_contextKeyService); this._hasMultipleDocumentFormattingProvider = EditorContextKeys.hasMultipleDocumentFormattingProvider.bindTo(_contextKeyService); this._hasMultipleDocumentSelectionFormattingProvider = EditorContextKeys.hasMultipleDocumentSelectionFormattingProvider.bindTo(_contextKeyService); - this._isInWalkThrough = EditorContextKeys.isInWalkThroughSnippet.bindTo(_contextKeyService); + this._isInEmbeddedEditor = EditorContextKeys.isInEmbeddedEditor.bindTo(_contextKeyService); const update = () => this._update(); @@ -2174,7 +2174,7 @@ export class EditorModeContext extends Disposable { this._hasDocumentFormattingProvider.reset(); this._hasDocumentSelectionFormattingProvider.reset(); this._hasSignatureHelpProvider.reset(); - this._isInWalkThrough.reset(); + this._isInEmbeddedEditor.reset(); }); } @@ -2204,7 +2204,7 @@ export class EditorModeContext extends Disposable { this._hasDocumentSelectionFormattingProvider.set(this._languageFeaturesService.documentRangeFormattingEditProvider.has(model)); this._hasMultipleDocumentFormattingProvider.set(this._languageFeaturesService.documentFormattingEditProvider.all(model).length + this._languageFeaturesService.documentRangeFormattingEditProvider.all(model).length > 1); this._hasMultipleDocumentSelectionFormattingProvider.set(this._languageFeaturesService.documentRangeFormattingEditProvider.all(model).length > 1); - this._isInWalkThrough.set(model.uri.scheme === Schemas.walkThroughSnippet); + this._isInEmbeddedEditor.set(model.uri.scheme === Schemas.walkThroughSnippet || model.uri.scheme === Schemas.vscodeChatCodeBlock); }); } } diff --git a/src/vs/editor/common/editorContextKeys.ts b/src/vs/editor/common/editorContextKeys.ts index 4cc1b78c7e4..a38c21c4717 100644 --- a/src/vs/editor/common/editorContextKeys.ts +++ b/src/vs/editor/common/editorContextKeys.ts @@ -42,7 +42,7 @@ export namespace EditorContextKeys { export const hasSingleSelection = hasMultipleSelections.toNegated(); export const tabMovesFocus = new RawContextKey('editorTabMovesFocus', false, nls.localize('editorTabMovesFocus', "Whether `Tab` will move focus out of the editor")); export const tabDoesNotMoveFocus = tabMovesFocus.toNegated(); - export const isInWalkThroughSnippet = new RawContextKey('isInEmbeddedEditor', false, true); + export const isInEmbeddedEditor = new RawContextKey('isInEmbeddedEditor', false, true); export const canUndo = new RawContextKey('canUndo', false, true); export const canRedo = new RawContextKey('canRedo', false, true); diff --git a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts index c579e25acc4..b968f248904 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts @@ -286,9 +286,7 @@ registerAction2(class GoToDefinitionAction extends DefinitionAction { ...nls.localize2('actions.goToDecl.label', "Go to Definition"), mnemonicTitle: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition"), }, - precondition: ContextKeyExpr.and( - EditorContextKeys.hasDefinitionProvider, - EditorContextKeys.isInWalkThroughSnippet.toNegated()), + precondition: EditorContextKeys.hasDefinitionProvider, keybinding: [{ when: EditorContextKeys.editorTextFocus, primary: KeyCode.F12, @@ -327,7 +325,7 @@ registerAction2(class OpenDefinitionToSideAction extends DefinitionAction { title: nls.localize2('actions.goToDeclToSide.label', "Open Definition to the Side"), precondition: ContextKeyExpr.and( EditorContextKeys.hasDefinitionProvider, - EditorContextKeys.isInWalkThroughSnippet.toNegated()), + EditorContextKeys.isInEmbeddedEditor.toNegated()), keybinding: [{ when: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.F12), @@ -357,7 +355,7 @@ registerAction2(class PeekDefinitionAction extends DefinitionAction { precondition: ContextKeyExpr.and( EditorContextKeys.hasDefinitionProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInWalkThroughSnippet.toNegated() + EditorContextKeys.isInEmbeddedEditor.toNegated() ), keybinding: { when: EditorContextKeys.editorTextFocus, @@ -417,7 +415,7 @@ registerAction2(class GoToDeclarationAction extends DeclarationAction { }, precondition: ContextKeyExpr.and( EditorContextKeys.hasDeclarationProvider, - EditorContextKeys.isInWalkThroughSnippet.toNegated() + EditorContextKeys.isInEmbeddedEditor.toNegated() ), menu: [{ id: MenuId.EditorContext, @@ -451,7 +449,7 @@ registerAction2(class PeekDeclarationAction extends DeclarationAction { precondition: ContextKeyExpr.and( EditorContextKeys.hasDeclarationProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInWalkThroughSnippet.toNegated() + EditorContextKeys.isInEmbeddedEditor.toNegated() ), menu: { id: MenuId.EditorContextPeek, @@ -502,9 +500,7 @@ registerAction2(class GoToTypeDefinitionAction extends TypeDefinitionAction { ...nls.localize2('actions.goToTypeDefinition.label', "Go to Type Definition"), mnemonicTitle: nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definition"), }, - precondition: ContextKeyExpr.and( - EditorContextKeys.hasTypeDefinitionProvider, - EditorContextKeys.isInWalkThroughSnippet.toNegated()), + precondition: EditorContextKeys.hasTypeDefinitionProvider, keybinding: { when: EditorContextKeys.editorTextFocus, primary: 0, @@ -539,7 +535,7 @@ registerAction2(class PeekTypeDefinitionAction extends TypeDefinitionAction { precondition: ContextKeyExpr.and( EditorContextKeys.hasTypeDefinitionProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInWalkThroughSnippet.toNegated() + EditorContextKeys.isInEmbeddedEditor.toNegated() ), menu: { id: MenuId.EditorContextPeek, @@ -590,9 +586,7 @@ registerAction2(class GoToImplementationAction extends ImplementationAction { ...nls.localize2('actions.goToImplementation.label', "Go to Implementations"), mnemonicTitle: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementations"), }, - precondition: ContextKeyExpr.and( - EditorContextKeys.hasImplementationProvider, - EditorContextKeys.isInWalkThroughSnippet.toNegated()), + precondition: EditorContextKeys.hasImplementationProvider, keybinding: { when: EditorContextKeys.editorTextFocus, primary: KeyMod.CtrlCmd | KeyCode.F12, @@ -627,7 +621,7 @@ registerAction2(class PeekImplementationAction extends ImplementationAction { precondition: ContextKeyExpr.and( EditorContextKeys.hasImplementationProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInWalkThroughSnippet.toNegated() + EditorContextKeys.isInEmbeddedEditor.toNegated() ), keybinding: { when: EditorContextKeys.editorTextFocus, @@ -680,7 +674,7 @@ registerAction2(class GoToReferencesAction extends ReferencesAction { precondition: ContextKeyExpr.and( EditorContextKeys.hasReferenceProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInWalkThroughSnippet.toNegated() + EditorContextKeys.isInEmbeddedEditor.toNegated() ), keybinding: { when: EditorContextKeys.editorTextFocus, @@ -718,7 +712,7 @@ registerAction2(class PeekReferencesAction extends ReferencesAction { precondition: ContextKeyExpr.and( EditorContextKeys.hasReferenceProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInWalkThroughSnippet.toNegated() + EditorContextKeys.isInEmbeddedEditor.toNegated() ), menu: { id: MenuId.EditorContextPeek, @@ -750,7 +744,7 @@ class GenericGoToLocationAction extends SymbolNavigationAction { title: nls.localize2('label.generic', "Go to Any Symbol"), precondition: ContextKeyExpr.and( PeekContext.notInPeekEditor, - EditorContextKeys.isInWalkThroughSnippet.toNegated() + EditorContextKeys.isInEmbeddedEditor.toNegated() ), }); } diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts index 8dcb48da8a2..a68eaeed3c3 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts @@ -186,7 +186,8 @@ registerAction2(class PeekCallHierarchyAction extends EditorAction2 { order: 1000, when: ContextKeyExpr.and( _ctxHasCallHierarchyProvider, - PeekContext.notInPeekEditor + PeekContext.notInPeekEditor, + EditorContextKeys.isInEmbeddedEditor.toNegated(), ), }, keybinding: { From 08beef7828374d84c956efc51477845a22149162 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 7 Feb 2024 13:06:54 -0600 Subject: [PATCH 0992/1897] use accessor --- .../browser/accessibilityConfiguration.ts | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 0f9f0990ab1..43abaabadef 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -700,30 +700,20 @@ Registry.as(WorkbenchExtensions.ConfigurationMi } }]); - Registry.as(WorkbenchExtensions.ConfigurationMigration) .registerConfigurationMigrations(AudioCue.allAudioCues.map(item => ({ key: item.settingsKey, - migrateFn: (value: 'on' | 'off' | 'auto', accessor) => { + migrateFn: (audioCue, accessor) => { const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; - configurationKeyValuePairs.push([`${item.signalSettingsKey}.audioCue`, { value }]); - return configurationKeyValuePairs; - } - }))); - -Registry.as(WorkbenchExtensions.ConfigurationMigration) - .registerConfigurationMigrations(AudioCue.allAudioCues.filter(c => c.alertSettingsKey).map(item => ({ - key: item.alertSettingsKey!, - migrateFn: (value, accessor) => { - const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; - if (typeof value !== 'string') { - if (value === true) { - value = 'auto'; - } else { - value = 'off'; + const alertSettingsKey = item.alertSettingsKey; + let alert: string | undefined; + if (alertSettingsKey) { + alert = accessor(alertSettingsKey) ?? undefined; + if (typeof alert !== 'string') { + alert = alert ? 'auto' : 'off'; } } - configurationKeyValuePairs.push([`${item.signalSettingsKey}.alert`, { value }]); + configurationKeyValuePairs.push([`${item.signalSettingsKey}`, alert ? { alert, audioCue } as any : { audioCue }]); return configurationKeyValuePairs; } }))); From fe6db4073fdf00c683b7738e9790b9dd83e66207 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Wed, 7 Feb 2024 11:08:03 -0800 Subject: [PATCH 0993/1897] allow time to scroll (#204646) --- .../singlefolder-tests/interactiveWindow.test.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts index c85fdcb4c81..a3d4036e5a2 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts @@ -99,8 +99,13 @@ async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { } // Verify visible range has the last cell - assert.strictEqual(notebookEditor.visibleRanges[notebookEditor.visibleRanges.length - 1].end, notebookEditor.notebook.cellCount, `Last cell is not visible`); - + if (!lastCellIsVisible(notebookEditor)) { + // scroll happens async, so give it some time to scroll + await new Promise((resolve) => setTimeout(() => { + assert.ok(lastCellIsVisible(notebookEditor), `Last cell is not visible`); + resolve(); + }, 1000)); + } }); test('Interactive window has the correct kernel', async () => { @@ -120,3 +125,8 @@ async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { }); }); + +function lastCellIsVisible(notebookEditor: vscode.NotebookEditor) { + const lastVisibleCell = notebookEditor.visibleRanges[notebookEditor.visibleRanges.length - 1].end; + return notebookEditor.notebook.cellCount === lastVisibleCell; +} From 5551a8e28c3718c1d419fe49114739d40cb4e510 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 7 Feb 2024 13:09:43 -0600 Subject: [PATCH 0994/1897] finally migrate settings correctly --- .../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 43abaabadef..3f601b3b408 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -713,7 +713,7 @@ Registry.as(WorkbenchExtensions.ConfigurationMi alert = alert ? 'auto' : 'off'; } } - configurationKeyValuePairs.push([`${item.signalSettingsKey}`, alert ? { alert, audioCue } as any : { audioCue }]); + configurationKeyValuePairs.push([`${item.signalSettingsKey}`, { value: alert ? { alert, audioCue } : { audioCue } }]); return configurationKeyValuePairs; } }))); From 45a573ed9999db7d4637c6ead2628723d4e2ae98 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 7 Feb 2024 16:11:03 -0300 Subject: [PATCH 0995/1897] Don't wait for variable resolvers before rendering request (#204650) Fix #204409 --- src/vs/workbench/contrib/chat/common/chatModel.ts | 13 +++++++++++-- .../contrib/chat/common/chatParserTypes.ts | 5 +++++ .../contrib/chat/common/chatServiceImpl.ts | 7 +++++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 235c547b2e4..951b8a19001 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -92,10 +92,18 @@ export class ChatRequestModel implements IChatRequestModel { return this.session.requesterAvatarIconUri; } + public get variableData(): IChatRequestVariableData { + return this._variableData; + } + + public set variableData(v: IChatRequestVariableData) { + this._variableData = v; + } + constructor( public readonly session: ChatModel, public readonly message: IParsedChatRequest, - public readonly variableData: IChatRequestVariableData) { + private _variableData: IChatRequestVariableData) { this._id = 'request_' + ChatRequestModel.nextId++; } } @@ -353,7 +361,8 @@ export type ISerializableChatAgentData = UriDto; export interface ISerializableChatRequestData { message: string | IParsedChatRequest; // string => old format - variableData: IChatRequestVariableData; // make optional + /** Is really like "prompt data". This is the message in the format in which the agent gets it + variable values. */ + variableData: IChatRequestVariableData; response: ReadonlyArray | undefined; agent?: ISerializableChatAgentData; slashCommand?: IChatAgentCommand; diff --git a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts index 7363f5cffcb..65e4cf2a8fb 100644 --- a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts +++ b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts @@ -22,9 +22,14 @@ export interface IParsedChatRequestPart { readonly range: IOffsetRange; readonly editorRange: IRange; readonly text: string; + /** How this part is represented in the prompt going to the agent */ readonly promptText: string; } +export function getPromptText(request: ReadonlyArray): string { + return request.map(r => r.promptText).join(''); +} + export class ChatRequestTextPart implements IParsedChatRequestPart { static readonly Kind = 'text'; readonly kind = ChatRequestTextPart.Kind; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index c55c438f228..caab172b0f4 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -23,7 +23,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatResponse, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; @@ -539,8 +539,11 @@ export class ChatService extends Disposable implements IChatService { history.push({ request: historyRequest, response: request.response.response.value, result: { errorDetails: request.response.errorDetails } }); } + const initVariableData: IChatRequestVariableData = { message: getPromptText(parsedRequest.parts), variables: {} }; + request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); - request = model.addRequest(parsedRequest, variableData, agent, agentSlashCommandPart?.command); + request.variableData = variableData; + const requestProps: IChatAgentRequest = { sessionId, requestId: request.id, From c4a1ed80ec72e5001b1b5ee14d2ee24a1ca6db97 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 7 Feb 2024 16:27:33 -0300 Subject: [PATCH 0996/1897] Delete agent, slash command, variable more easily (#204651) --- .../contrib/chat/browser/contrib/chatInputEditorContrib.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index beeb2bdeeab..e5e44863dfd 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -646,8 +646,8 @@ class ChatTokenDeleter extends Disposable { const deletableTokens = previousParsedValue.parts.filter(p => p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart || p instanceof ChatRequestSlashCommandPart || p instanceof ChatRequestVariablePart); deletableTokens.forEach(token => { const deletedRangeOfToken = Range.intersectRanges(token.editorRange, change.range); - // Part of this token was deleted, and the deletion range doesn't go off the front of the token, for simpler math - if ((deletedRangeOfToken && !deletedRangeOfToken.isEmpty()) && Range.compareRangesUsingStarts(token.editorRange, change.range) < 0) { + // Part of this token was deleted, or the space after it was deleted, and the deletion range doesn't go off the front of the token, for simpler math + if (deletedRangeOfToken && Range.compareRangesUsingStarts(token.editorRange, change.range) < 0) { // Assume single line tokens const length = deletedRangeOfToken.endColumn - deletedRangeOfToken.startColumn; const rangeToDelete = new Range(token.editorRange.startLineNumber, token.editorRange.startColumn, token.editorRange.endLineNumber, token.editorRange.endColumn - length); From b05778eb907c25ed7546dd73511f2aa14683af84 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 7 Feb 2024 21:41:17 +0100 Subject: [PATCH 0997/1897] Workbench - ability to contribute window title variables (#204538) --------- Co-authored-by: Benjamin Pasero --- .../src/settingsDocumentHelper.ts | 2 + .../browser/parts/titlebar/titlebarPart.ts | 36 ++++++++++++++ .../browser/parts/titlebar/windowTitle.ts | 34 ++++++++++++- .../browser/workbench.contribution.ts | 2 + .../workbench/contrib/scm/browser/activity.ts | 49 ++++++++++++++++--- 5 files changed, 115 insertions(+), 8 deletions(-) diff --git a/extensions/configuration-editing/src/settingsDocumentHelper.ts b/extensions/configuration-editing/src/settingsDocumentHelper.ts index 110494fdb3e..bbf77e6017e 100644 --- a/extensions/configuration-editing/src/settingsDocumentHelper.ts +++ b/extensions/configuration-editing/src/settingsDocumentHelper.ts @@ -120,6 +120,8 @@ export class SettingsDocument { completions.push(this.newSimpleCompletionItem(getText('remoteName'), range, vscode.l10n.t("e.g. SSH"))); completions.push(this.newSimpleCompletionItem(getText('dirty'), range, vscode.l10n.t("an indicator for when the active editor has unsaved changes"))); completions.push(this.newSimpleCompletionItem(getText('separator'), range, vscode.l10n.t("a conditional separator (' - ') that only shows when surrounded by variables with values"))); + completions.push(this.newSimpleCompletionItem(getText('activeRepositoryName'), range, vscode.l10n.t("the name of the active repository (e.g. vscode)"))); + completions.push(this.newSimpleCompletionItem(getText('activeRepositoryBranchName'), range, vscode.l10n.t("the name of the active branch in the active repository (e.g. main)"))); return completions; } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 7e481cf928d..cbe98d2e852 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -55,6 +55,11 @@ import { mainWindow } from 'vs/base/browser/window'; import { ACCOUNTS_ACTIVITY_TILE_ACTION, GLOBAL_ACTIVITY_TITLE_ACTION } from 'vs/workbench/browser/parts/titlebar/titlebarActions'; import { IView } from 'vs/base/browser/ui/grid/grid'; +export interface ITitleVariable { + readonly name: string; + readonly contextKey: string; +} + export interface ITitleProperties { isPure?: boolean; isAdmin?: boolean; @@ -72,6 +77,11 @@ export interface ITitlebarPart extends IDisposable { * Update some environmental title properties. */ updateProperties(properties: ITitleProperties): void; + + /** + * Adds variables to be supported in the window title. + */ + registerVariables(variables: ITitleVariable[]): void; } export class BrowserTitleService extends MultiWindowParts implements ITitleService { @@ -134,6 +144,14 @@ export class BrowserTitleService extends MultiWindowParts i disposables.add(Event.runAndSubscribe(titlebarPart.onDidChange, () => titlebarPartContainer.style.height = `${titlebarPart.height}px`)); titlebarPart.create(titlebarPartContainer); + if (this.properties) { + titlebarPart.updateProperties(this.properties); + } + + if (this.variables.length) { + titlebarPart.registerVariables(this.variables); + } + Event.once(titlebarPart.onWillDispose)(() => disposables.dispose()); return titlebarPart; @@ -150,12 +168,26 @@ export class BrowserTitleService extends MultiWindowParts i readonly onMenubarVisibilityChange = this.mainPart.onMenubarVisibilityChange; + private properties: ITitleProperties | undefined = undefined; + updateProperties(properties: ITitleProperties): void { + this.properties = properties; + for (const part of this.parts) { part.updateProperties(properties); } } + private variables: ITitleVariable[] = []; + + registerVariables(variables: ITitleVariable[]): void { + this.variables.push(...variables); + + for (const part of this.parts) { + part.registerVariables(variables); + } + } + //#endregion } @@ -379,6 +411,10 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.windowTitle.updateProperties(properties); } + registerVariables(variables: ITitleVariable[]): void { + this.windowTitle.registerVariables(variables); + } + protected override createContentArea(parent: HTMLElement): HTMLElement { this.element = parent; this.rootContainer = append(parent, $('.titlebar-container')); diff --git a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts index fffceb67baf..0628c153307 100644 --- a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts +++ b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { dirname, basename } from 'vs/base/common/resources'; -import { ITitleProperties } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; +import { ITitleProperties, ITitleVariable } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -26,6 +26,7 @@ import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtua import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; const enum WindowSettingNames { titleSeparator = 'window.titleSeparator', @@ -39,6 +40,8 @@ export class WindowTitle extends Disposable { private static readonly TITLE_DIRTY = '\u25cf '; private readonly properties: ITitleProperties = { isPure: true, isAdmin: false, prefix: undefined }; + private readonly variables = new Map(); + private readonly activeEditorListeners = this._register(new DisposableStore()); private readonly titleUpdater = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0)); @@ -66,6 +69,7 @@ export class WindowTitle extends Disposable { private readonly targetWindow: Window, editorGroupsContainer: IEditorGroupsContainer | 'main', @IConfigurationService protected readonly configurationService: IConfigurationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, @IEditorService editorService: IEditorService, @IBrowserWorkbenchEnvironmentService protected readonly environmentService: IBrowserWorkbenchEnvironmentService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @@ -95,6 +99,11 @@ export class WindowTitle extends Disposable { this.titleUpdater.schedule(); } })); + this._register(this.contextKeyService.onDidChangeContext(e => { + if (e.affectsSome(this.variables)) { + this.titleUpdater.schedule(); + } + })); } private onConfigurationChanged(event: IConfigurationChangeEvent): void { @@ -223,6 +232,22 @@ export class WindowTitle extends Disposable { } } + registerVariables(variables: ITitleVariable[]): void { + let changed = false; + + for (const { name, contextKey } of variables) { + if (!this.variables.has(contextKey)) { + this.variables.set(contextKey, name); + + changed = true; + } + } + + if (changed) { + this.titleUpdater.schedule(); + } + } + /** * Possible template values: * @@ -303,6 +328,12 @@ export class WindowTitle extends Disposable { const titleTemplate = this.configurationService.getValue(WindowSettingNames.title); const focusedView: string = this.viewsService.getFocusedViewName(); + // Variables (contributed) + const contributedVariables: { [key: string]: string } = {}; + for (const [contextKey, name] of this.variables) { + contributedVariables[name] = this.contextKeyService.getContextKeyValue(contextKey) ?? ''; + } + return template(titleTemplate, { activeEditorShort, activeEditorLong, @@ -320,6 +351,7 @@ export class WindowTitle extends Disposable { remoteName, profileName, focusedView, + ...contributedVariables, separator: { label: separator } }); } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index d9cfbe24654..f2888621120 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -611,6 +611,8 @@ const registry = Registry.as(ConfigurationExtensions.Con localize('remoteName', "`${remoteName}`: e.g. SSH"), localize('dirty', "`${dirty}`: an indicator for when the active editor has unsaved changes."), localize('focusedView', "`${focusedView}`: the name of the view that is currently focused."), + localize('activeRepositoryName', "`${activeRepositoryName}`: the name of the active repository (e.g. vscode)."), + localize('activeRepositoryBranchName', "`${activeRepositoryBranchName}`: the name of the active branch in the active repository (e.g. main)."), localize('separator', "`${separator}`: a conditional separator (\" - \") that only shows when surrounded by variables with values or static text.") ].join('\n- '); // intentionally concatenated to not produce a string that is too long for translations diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index 68a280a56bc..402df518c99 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -10,7 +10,7 @@ import { Event } from 'vs/base/common/event'; import { VIEW_PANE_ID, ISCMService, ISCMRepository, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IStatusbarEntry, IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -18,6 +18,7 @@ import { EditorResourceAccessor } from 'vs/workbench/common/editor'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { Schemas } from 'vs/base/common/network'; import { Iterable } from 'vs/base/common/iterator'; +import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; function getCount(repository: ISCMRepository): number { if (typeof repository.provider.count === 'number') { @@ -27,10 +28,18 @@ function getCount(repository: ISCMRepository): number { } } +const ContextKeys = { + ActiveRepositoryName: new RawContextKey('scmActiveRepositoryName', ''), + ActiveRepositoryBranchName: new RawContextKey('scmActiveRepositoryBranchName', ''), +}; + export class SCMStatusController implements IWorkbenchContribution { + private activeRepositoryNameContextKey: IContextKey; + private activeRepositoryBranchNameContextKey: IContextKey; + private statusBarDisposable: IDisposable = Disposable.None; - private focusDisposable: IDisposable = Disposable.None; + private focusDisposables = new DisposableStore(); private focusedRepository: ISCMRepository | undefined = undefined; private readonly badgeDisposable = new MutableDisposable(); private readonly disposables = new DisposableStore(); @@ -43,7 +52,9 @@ export class SCMStatusController implements IWorkbenchContribution { @IActivityService private readonly activityService: IActivityService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IContextKeyService contextKeyService: IContextKeyService, + @ITitleService titleService: ITitleService ) { this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); @@ -55,6 +66,14 @@ export class SCMStatusController implements IWorkbenchContribution { this.onDidAddRepository(repository); } + this.activeRepositoryNameContextKey = ContextKeys.ActiveRepositoryName.bindTo(contextKeyService); + this.activeRepositoryBranchNameContextKey = ContextKeys.ActiveRepositoryBranchName.bindTo(contextKeyService); + + titleService.registerVariables([ + { name: 'activeRepositoryName', contextKey: ContextKeys.ActiveRepositoryName.key }, + { name: 'activeRepositoryBranchName', contextKey: ContextKeys.ActiveRepositoryBranchName.key, } + ]); + this.scmViewService.onDidFocusRepository(this.focusRepository, this, this.disposables); this.focusRepository(this.scmViewService.focusedRepository); @@ -125,17 +144,33 @@ export class SCMStatusController implements IWorkbenchContribution { return; } - this.focusDisposable.dispose(); + this.focusDisposables.clear(); this.focusedRepository = repository; - if (repository && repository.provider.onDidChangeStatusBarCommands) { - this.focusDisposable = repository.provider.onDidChangeStatusBarCommands(() => this.renderStatusBar(repository)); + if (repository) { + if (repository.provider.onDidChangeStatusBarCommands) { + this.focusDisposables.add(repository.provider.onDidChangeStatusBarCommands(() => this.renderStatusBar(repository))); + } + + this.focusDisposables.add(repository.provider.onDidChangeHistoryProvider(() => { + if (repository.provider.historyProvider) { + this.focusDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => this.updateContextKeys(repository))); + } + + this.updateContextKeys(repository); + })); } + this.updateContextKeys(repository); this.renderStatusBar(repository); this.renderActivityCount(); } + private updateContextKeys(repository: ISCMRepository | undefined): void { + this.activeRepositoryNameContextKey.set(repository?.provider.name ?? ''); + this.activeRepositoryBranchNameContextKey.set(repository?.provider.historyProvider?.currentHistoryItemGroup?.label ?? ''); + } + private renderStatusBar(repository: ISCMRepository | undefined): void { this.statusBarDisposable.dispose(); @@ -204,7 +239,7 @@ export class SCMStatusController implements IWorkbenchContribution { } dispose(): void { - this.focusDisposable.dispose(); + this.focusDisposables.dispose(); this.statusBarDisposable.dispose(); this.badgeDisposable.dispose(); this.disposables.dispose(); From 6c7362fe4f46ca0335b0dc82d331be9b63c31134 Mon Sep 17 00:00:00 2001 From: John Murray Date: Wed, 7 Feb 2024 20:42:45 +0000 Subject: [PATCH 0998/1897] Reinstate command items when filtering checkout quickpick (fix #202870) (#204107) --------- Co-authored-by: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> --- extensions/git/src/commands.ts | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 979daf3d2ba..3f46f698ce2 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -23,7 +23,7 @@ import { RemoteSourceAction } from './api/git-base'; abstract class CheckoutCommandItem implements QuickPickItem { abstract get label(): string; get description(): string { return ''; } - get alwaysShow(): boolean { return false; } + get alwaysShow(): boolean { return true; } } class CreateBranchItem extends CheckoutCommandItem { @@ -2461,9 +2461,10 @@ export class CommandCenter { const createBranchFrom = new CreateBranchFromItem(); const checkoutDetached = new CheckoutDetachedItem(); const picks: QuickPickItem[] = []; + const commands: QuickPickItem[] = []; if (!opts?.detached) { - picks.push(createBranch, createBranchFrom, checkoutDetached); + commands.push(createBranch, createBranchFrom, checkoutDetached); } const disposables: Disposable[] = []; @@ -2477,7 +2478,7 @@ export class CommandCenter { quickPick.show(); picks.push(... await createCheckoutItems(repository, opts?.detached)); - quickPick.items = picks; + quickPick.items = [...commands, ...picks]; quickPick.busy = false; const choice = await new Promise(c => { @@ -2492,6 +2493,22 @@ export class CommandCenter { c(undefined); }))); + disposables.push(quickPick.onDidChangeValue(value => { + switch (true) { + case value === '': + quickPick.items = [...commands, ...picks]; + break; + case commands.length === 0: + quickPick.items = picks; + break; + case picks.length === 0: + quickPick.items = commands; + break; + default: + quickPick.items = [...picks, { label: '', kind: QuickPickItemKind.Separator }, ...commands]; + break; + } + })); }); dispose(disposables); From 20d18171b31d408986f527f3fadecfd9841539b4 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Wed, 7 Feb 2024 16:23:46 -0600 Subject: [PATCH 0999/1897] introduce findFiles2 API (#203844) * introduce first version of FindFiles2 API --- extensions/vscode-api-tests/package.json | 1 + .../src/singlefolder-tests/workspace.test.ts | 39 ++++ .../api/browser/mainThreadWorkspace.ts | 15 +- .../workbench/api/common/extHost.api.impl.ts | 3 + .../workbench/api/common/extHost.protocol.ts | 4 +- .../workbench/api/common/extHostWorkspace.ts | 68 ++++++- .../api/test/browser/extHostWorkspace.test.ts | 189 ++++++++++++++++-- .../test/browser/mainThreadWorkspace.test.ts | 32 ++- .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.findFiles2.d.ts | 81 ++++++++ 10 files changed, 390 insertions(+), 43 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.findFiles2.d.ts diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 97de1b94d05..a35e4bc08cc 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -22,6 +22,7 @@ "extensionsAny", "externalUriOpener", "fileSearchProvider", + "findFiles2", "findTextInFiles", "fsChunks", "interactive", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index e69eecff5d1..b867766d027 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -597,6 +597,45 @@ suite('vscode API - workspace', () => { }); }); + test('`findFiles2`', () => { + return vscode.workspace.findFiles2('*image.png').then((res) => { + assert.strictEqual(res.length, 4); + // TODO: see why this is fuzzy matching + }); + }); + + test('findFiles2 - null exclude', async () => { + await vscode.workspace.findFiles2('**/file.txt', { useDefaultExcludes: true, useDefaultSearchExcludes: false }).then((res) => { + // file.exclude folder is still searched, search.exclude folder is not + assert.strictEqual(res.length, 1); + assert.strictEqual(basename(vscode.workspace.asRelativePath(res[0])), 'file.txt'); + }); + + await vscode.workspace.findFiles2('**/file.txt', { useDefaultExcludes: false, useDefaultSearchExcludes: false }).then((res) => { + // search.exclude and files.exclude folders are both searched + assert.strictEqual(res.length, 2); + assert.strictEqual(basename(vscode.workspace.asRelativePath(res[0])), 'file.txt'); + }); + }); + + test('findFiles2, exclude', () => { + return vscode.workspace.findFiles2('*image.png', { exclude: '**/sub/**' }).then((res) => { + assert.strictEqual(res.length, 3); + // TODO: see why this is fuzzy matching + }); + }); + + test('findFiles2, cancellation', () => { + + const source = new vscode.CancellationTokenSource(); + const token = source.token; // just to get an instance first + source.cancel(); + + return vscode.workspace.findFiles2('*.js', {}, token).then((res) => { + assert.deepStrictEqual(res, []); + }); + }); + test('findTextInFiles', async () => { const options: vscode.FindTextInFilesOptions = { include: '*.ts', diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index e04ee2837d9..180932b6d6d 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -19,7 +19,7 @@ import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorksp import { IWorkspace, IWorkspaceContextService, WorkbenchState, isUntitledWorkspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { checkGlobFileExists } from 'vs/workbench/services/extensions/common/workspaceContains'; -import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder'; +import { IFileQueryBuilderOptions, ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder'; import { IEditorService, ISaveEditorsResult } from 'vs/workbench/services/editor/common/editorService'; import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; @@ -140,21 +140,14 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { // --- search --- - $startFileSearch(includePattern: string | null, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false | null, maxResults: number | null, token: CancellationToken): Promise { + $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { const includeFolder = URI.revive(_includeFolder); const workspace = this._contextService.getWorkspace(); const query = this._queryBuilder.file( includeFolder ? [includeFolder] : workspace.folders, - { - maxResults: maxResults ?? undefined, - disregardExcludeSettings: (excludePatternOrDisregardExcludes === false) || undefined, - disregardSearchExcludeSettings: true, - disregardIgnoreFiles: true, - includePattern: includePattern ?? undefined, - excludePattern: typeof excludePatternOrDisregardExcludes === 'string' ? excludePatternOrDisregardExcludes : undefined, - _reason: 'startFileSearch' - }); + options + ); return this._searchService.fileSearch(query, token).then(result => { return result.results.map(m => m.resource); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index dd4a2772606..5b7786330ac 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -938,6 +938,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // Note, undefined/null have different meanings on "exclude" return extHostWorkspace.findFiles(include, exclude, maxResults, extension.identifier, token); }, + findFiles2: (filePattern, options?, token?) => { + return extHostWorkspace.findFiles2(filePattern, options, extension.identifier, token); + }, findTextInFiles: (query: vscode.TextSearchQuery, optionsOrCallback: vscode.FindTextInFilesOptions | ((result: vscode.TextSearchResult) => void), callbackOrToken?: vscode.CancellationToken | ((result: vscode.TextSearchResult) => void), token?: vscode.CancellationToken) => { checkProposedApiEnabled(extension, 'findTextInFiles'); let options: vscode.FindTextInFilesOptions; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 8826cef8433..676cf530dcf 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -78,7 +78,7 @@ import { Dto, IRPCProtocol, SerializableObjectWithBuffers, createProxyIdentifier 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/tunnelModel'; -import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; +import { IFileQueryBuilderOptions, 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'; @@ -1341,7 +1341,7 @@ export interface ITextSearchComplete { } export interface MainThreadWorkspaceShape extends IDisposable { - $startFileSearch(includePattern: string | null, includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false | null, maxResults: number | null, token: CancellationToken): Promise; + $startFileSearch(includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise; $startTextSearch(query: search.IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise; $checkExists(folders: readonly UriComponents[], includes: string[], token: CancellationToken): Promise; $save(uri: UriComponents, options: { saveAs: boolean }): Promise; diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 95a60bcba8b..dcb32598916 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -28,7 +28,7 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { GlobPattern } from 'vs/workbench/api/common/extHostTypeConverters'; import { Range } from 'vs/workbench/api/common/extHostTypes'; import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; -import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; +import { IFileQueryBuilderOptions, ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; import { IRawFileMatch2, ITextSearchResult, resultIsMatch } from 'vs/workbench/services/search/common/search'; import * as vscode from 'vscode'; import { ExtHostWorkspaceShape, IRelativePatternDto, IWorkspaceData, MainContext, MainThreadMessageOptions, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol'; @@ -446,27 +446,73 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac findFiles(include: vscode.GlobPattern | undefined, exclude: vscode.GlobPattern | null | undefined, maxResults: number | undefined, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { this._logService.trace(`extHostWorkspace#findFiles: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles`); - let excludePatternOrDisregardExcludes: string | false | undefined = undefined; + let excludeString: string = ''; + let useFileExcludes = true; if (exclude === null) { - excludePatternOrDisregardExcludes = false; - } else if (exclude) { + useFileExcludes = false; + } else if (exclude !== undefined) { if (typeof exclude === 'string') { - excludePatternOrDisregardExcludes = exclude; + excludeString = exclude; } else { - excludePatternOrDisregardExcludes = exclude.pattern; + excludeString = exclude.pattern; } } + return this._findFilesImpl(include, undefined, { + exclude: excludeString, + maxResults, + useDefaultExcludes: useFileExcludes, + useDefaultSearchExcludes: false, + useIgnoreFiles: true + }, token); + } + findFiles2(filePattern: vscode.GlobPattern | undefined, + options: vscode.FindFiles2Options = {}, + extensionId: ExtensionIdentifier, + token: vscode.CancellationToken = CancellationToken.None): Promise { + this._logService.trace(`extHostWorkspace#findFiles2: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles2`); + return this._findFilesImpl(undefined, filePattern, options, token); + } + + private async _findFilesImpl( + // the old `findFiles` used `include` to query, but the new `findFiles2` uses `filePattern` to query. + // `filePattern` is the proper way to handle this, since it takes less precedence than the ignore files. + include: vscode.GlobPattern | undefined, + filePattern: vscode.GlobPattern | undefined, + options: vscode.FindFiles2Options, + token: vscode.CancellationToken = CancellationToken.None): Promise { if (token && token.isCancellationRequested) { return Promise.resolve([]); } - const { includePattern, folder } = parseSearchInclude(GlobPattern.from(include)); + const excludePattern = (typeof options.exclude === 'string') ? options.exclude : + options.exclude ? options.exclude.pattern : undefined; + + const fileQueries = { + ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined, + disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined, + disregardGlobalIgnoreFiles: typeof options.useGlobalIgnoreFiles === 'boolean' ? !options.useGlobalIgnoreFiles : undefined, + disregardParentIgnoreFiles: typeof options.useParentIgnoreFiles === 'boolean' ? !options.useParentIgnoreFiles : undefined, + disregardExcludeSettings: typeof options.useDefaultExcludes === 'boolean' ? !options.useDefaultExcludes : false, + disregardSearchExcludeSettings: typeof options.useDefaultSearchExcludes === 'boolean' ? !options.useDefaultSearchExcludes : false, + maxResults: options.maxResults, + excludePattern: excludePattern, + _reason: 'startFileSearch' + }; + let folderToUse: URI | undefined; + if (include) { + const { includePattern, folder } = parseSearchInclude(GlobPattern.from(include)); + folderToUse = folder; + fileQueries.includePattern = includePattern; + } else { + const { includePattern, folder } = parseSearchInclude(GlobPattern.from(filePattern)); + folderToUse = folder; + fileQueries.filePattern = includePattern; + } + return this._proxy.$startFileSearch( - includePattern ?? null, - folder ?? null, - excludePatternOrDisregardExcludes ?? null, - maxResults ?? null, + folderToUse ?? null, + fileQueries, token ) .then(data => Array.isArray(data) ? data.map(d => URI.revive(d)) : []); diff --git a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts index da33adc7de7..6abca46d3ff 100644 --- a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts +++ b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts @@ -18,7 +18,7 @@ import { mock } from 'vs/base/test/common/mock'; import { TestRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; import { ExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; -import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; +import { IFileQueryBuilderOptions, ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; import { IPatternInfo } from 'vs/workbench/services/search/common/search'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; @@ -583,12 +583,13 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { mainThreadCalled = true; - assert.strictEqual(includePattern, 'foo'); + assert.strictEqual(options.includePattern, 'foo'); assert.strictEqual(_includeFolder, null); - assert.strictEqual(excludePatternOrDisregardExcludes, null); - assert.strictEqual(maxResults, 10); + assert.strictEqual(options.excludePattern, ''); + assert.strictEqual(options.disregardExcludeSettings, false); + assert.strictEqual(options.maxResults, 10); return Promise.resolve(null); } }); @@ -605,11 +606,12 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { mainThreadCalled = true; - assert.strictEqual(includePattern, 'glob/**'); + assert.strictEqual(options.includePattern, 'glob/**'); assert.deepStrictEqual(_includeFolder ? URI.from(_includeFolder).toJSON() : null, URI.file('/other/folder').toJSON()); - assert.strictEqual(excludePatternOrDisregardExcludes, null); + assert.strictEqual(options.excludePattern, ''); + assert.strictEqual(options.disregardExcludeSettings, false); return Promise.resolve(null); } }); @@ -634,11 +636,12 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { mainThreadCalled = true; - assert.strictEqual(includePattern, 'glob/**'); + assert.strictEqual(options.includePattern, 'glob/**'); assert.deepStrictEqual(URI.revive(_includeFolder!).toString(), URI.file('/other/folder').toString()); - assert.strictEqual(excludePatternOrDisregardExcludes, false); + assert.strictEqual(options.excludePattern, ''); + assert.strictEqual(options.disregardExcludeSettings, true); return Promise.resolve(null); } }); @@ -655,7 +658,7 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { mainThreadCalled = true; return Promise.resolve(null); } @@ -675,9 +678,10 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { mainThreadCalled = true; - assert(excludePatternOrDisregardExcludes, 'glob/**'); // Note that the base portion is ignored, see #52651 + assert.strictEqual(options.disregardExcludeSettings, false); + assert.strictEqual(options.excludePattern, 'glob/**'); // Note that the base portion is ignored, see #52651 return Promise.resolve(null); } }); @@ -687,6 +691,163 @@ suite('ExtHostWorkspace', function () { assert(mainThreadCalled, 'mainThreadCalled'); }); }); + test('findFiles2 - string include', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.strictEqual(options.filePattern, 'foo'); + assert.strictEqual(options.includePattern, undefined); + assert.strictEqual(_includeFolder, null); + assert.strictEqual(options.excludePattern, undefined); + assert.strictEqual(options.disregardExcludeSettings, false); + assert.strictEqual(options.maxResults, 10); + return Promise.resolve(null); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + return ws.findFiles2('foo', { maxResults: 10, useDefaultExcludes: true }, new ExtensionIdentifier('test')).then(() => { + assert(mainThreadCalled, 'mainThreadCalled'); + }); + }); + + function testFindFiles2Include(pattern: RelativePattern) { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.strictEqual(options.filePattern, 'glob/**'); + assert.strictEqual(options.includePattern, undefined); + assert.deepStrictEqual(_includeFolder ? URI.from(_includeFolder).toJSON() : null, URI.file('/other/folder').toJSON()); + assert.strictEqual(options.excludePattern, undefined); + assert.strictEqual(options.disregardExcludeSettings, false); + return Promise.resolve(null); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + return ws.findFiles2(pattern, { maxResults: 10 }, new ExtensionIdentifier('test')).then(() => { + assert(mainThreadCalled, 'mainThreadCalled'); + }); + } + + test('findFiles2 - RelativePattern include (string)', () => { + return testFindFiles2Include(new RelativePattern('/other/folder', 'glob/**')); + }); + + test('findFiles2 - RelativePattern include (URI)', () => { + return testFindFiles2Include(new RelativePattern(URI.file('/other/folder'), 'glob/**')); + }); + + test('findFiles2 - no excludes', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.strictEqual(options.filePattern, 'glob/**'); + assert.strictEqual(options.includePattern, undefined); + assert.deepStrictEqual(URI.revive(_includeFolder!).toString(), URI.file('/other/folder').toString()); + assert.strictEqual(options.excludePattern, undefined); + assert.strictEqual(options.disregardExcludeSettings, false); + return Promise.resolve(null); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + return ws.findFiles2(new RelativePattern('/other/folder', 'glob/**'), {}, new ExtensionIdentifier('test')).then(() => { + assert(mainThreadCalled, 'mainThreadCalled'); + }); + }); + + test('findFiles2 - with cancelled token', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { + mainThreadCalled = true; + return Promise.resolve(null); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + + const token = CancellationToken.Cancelled; + return ws.findFiles2(new RelativePattern('/other/folder', 'glob/**'), {}, new ExtensionIdentifier('test'), token).then(() => { + assert(!mainThreadCalled, '!mainThreadCalled'); + }); + }); + + test('findFiles2 - RelativePattern exclude', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.strictEqual(options.disregardExcludeSettings, false); + assert.strictEqual(options.excludePattern, 'glob/**'); // Note that the base portion is ignored, see #52651 + return Promise.resolve(null); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + return ws.findFiles2('', { exclude: new RelativePattern(root, 'glob/**') }, new ExtensionIdentifier('test')).then(() => { + assert(mainThreadCalled, 'mainThreadCalled'); + }); + }); + test('findFiles2 - useIgnoreFiles', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.strictEqual(options.disregardExcludeSettings, false); + assert.strictEqual(options.disregardIgnoreFiles, false); + assert.strictEqual(options.disregardGlobalIgnoreFiles, false); + assert.strictEqual(options.disregardParentIgnoreFiles, false); + return Promise.resolve(null); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + return ws.findFiles2('', { useIgnoreFiles: true, useParentIgnoreFiles: true, useGlobalIgnoreFiles: true }, new ExtensionIdentifier('test')).then(() => { + assert(mainThreadCalled, 'mainThreadCalled'); + }); + }); + + test('findFiles2 - use symlinks', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.strictEqual(options.ignoreSymlinks, false); + return Promise.resolve(null); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + return ws.findFiles2('', { followSymlinks: true }, new ExtensionIdentifier('test')).then(() => { + assert(mainThreadCalled, 'mainThreadCalled'); + }); + }); test('findTextInFiles - no include', async () => { const root = '/project/foo'; diff --git a/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts b/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts index 7000d9edb5b..2cc5a637a6a 100644 --- a/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts @@ -41,7 +41,7 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch('foo', null, null, 10, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: 'foo', disregardSearchExcludeSettings: true }, new CancellationTokenSource().token); }); test('exclude defaults', () => { @@ -63,7 +63,7 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch('', null, null, 10, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardSearchExcludeSettings: true }, new CancellationTokenSource().token); }); test('disregard excludes', () => { @@ -76,7 +76,7 @@ suite('MainThreadWorkspace', () => { instantiationService.stub(ISearchService, { fileSearch(query: IFileQuery) { - assert.strictEqual(query.folderQueries[0].excludePattern, undefined); + assert.deepStrictEqual(query.folderQueries[0].excludePattern, undefined); assert.deepStrictEqual(query.excludePattern, undefined); return Promise.resolve({ results: [], messages: [] }); @@ -84,7 +84,29 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch('', null, false, 10, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardSearchExcludeSettings: true, disregardExcludeSettings: true }, new CancellationTokenSource().token); + }); + + test('do not disregard anything if disregardExcludeSettings is true', () => { + configService.setUserConfiguration('search', { + 'exclude': { 'searchExclude': true } + }); + configService.setUserConfiguration('files', { + 'exclude': { 'filesExclude': true } + }); + + instantiationService.stub(ISearchService, { + fileSearch(query: IFileQuery) { + assert.strictEqual(query.folderQueries.length, 1); + assert.strictEqual(query.folderQueries[0].disregardIgnoreFiles, true); + assert.deepStrictEqual(query.folderQueries[0].excludePattern, undefined); + + return Promise.resolve({ results: [], messages: [] }); + } + }); + + const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardExcludeSettings: true, disregardSearchExcludeSettings: false }, new CancellationTokenSource().token); }); test('exclude string', () => { @@ -98,6 +120,6 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch('', null, 'exclude/**', 10, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', excludePattern: 'exclude/**', disregardSearchExcludeSettings: true }, new CancellationTokenSource().token); }); }); diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 47486e60790..15df9887466 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -58,6 +58,7 @@ export const allApiProposals = Object.freeze({ externalUriOpener: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.externalUriOpener.d.ts', fileComments: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileComments.d.ts', fileSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileSearchProvider.d.ts', + findFiles2: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findFiles2.d.ts', findTextInFiles: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts', fsChunks: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fsChunks.d.ts', handleIssueUri: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.handleIssueUri.d.ts', diff --git a/src/vscode-dts/vscode.proposed.findFiles2.d.ts b/src/vscode-dts/vscode.proposed.findFiles2.d.ts new file mode 100644 index 00000000000..45d734153a2 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.findFiles2.d.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * 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 FindFiles2Options { + // note: this is just FindTextInFilesOptions without select properties (include, previewOptions, beforeContext, afterContext) + + /** + * A {@link GlobPattern glob pattern} that defines files and folders to exclude. The glob pattern + * will be matched against the file paths of resulting matches relative to their workspace. When `undefined`, default excludes will + * apply. + */ + exclude?: GlobPattern; + + /** + * Whether to use the values for files.exclude. Defaults to true. + */ + useDefaultExcludes?: boolean; + + /** + * Whether to use the values for search.exclude. Defaults to true. Will not be followed if `useDefaultExcludes` is set to `false`. + */ + useDefaultSearchExcludes?: boolean; + + /** + * The maximum number of results to search for + */ + maxResults?: number; + + /** + * Whether external files that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useIgnoreFiles"`. + */ + useIgnoreFiles?: boolean; + + /** + * Whether global files that exclude files, like .gitignore, should be respected. + * Must set `useIgnoreFiles` to `true` to use this. + * See the vscode setting `"search.useGlobalIgnoreFiles"`. + */ + useGlobalIgnoreFiles?: boolean; + + /** + * Whether files in parent directories that exclude files, like .gitignore, should be respected. + * Must set `useIgnoreFiles` to `true` to use this. + * See the vscode setting `"search.useParentIgnoreFiles"`. + */ + useParentIgnoreFiles?: boolean; + + /** + * Whether symlinks should be followed while searching. + * See the vscode setting `"search.followSymlinks"`. + */ + followSymlinks?: boolean; + } + + /** + * Represents a session of a currently logged in user. + */ + export namespace workspace { + /** + * Find files across all {@link workspace.workspaceFolders workspace folders} in the workspace. + * + * @example + * findFiles('**​/*.js', {exclude: '**​/out/**', useIgnoreFiles: true}, 10) + * + * @param filePattern A {@link GlobPattern glob pattern} that defines the files to search for. The glob pattern + * will be matched against the file paths of resulting matches relative to their workspace. Use a {@link RelativePattern relative pattern} + * to restrict the search results to a {@link WorkspaceFolder workspace folder}. + * @param options A set of {@link FindFiles2Options FindFiles2Options} that defines where and how to search (e.g. exclude settings). + * If enabled, any ignore files will take prescendence over any files found in the `filePattern` parameter. + * @param token A token that can be used to signal cancellation to the underlying search engine. + * @returns A thenable that resolves to an array of resource identifiers. Will return no results if no + * {@link workspace.workspaceFolders workspace folders} are opened. + */ + export function findFiles2(filePattern: GlobPattern, options?: FindFiles2Options, token?: CancellationToken): Thenable; + } +} From a1fb0dcd2e2c90916d3754adbeb881312f78cdaa Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 7 Feb 2024 14:47:44 -0800 Subject: [PATCH 1000/1897] Put all chat code blocks in same implicit JS/TS project (#204655) --- .../src/configuration/fileSchemes.ts | 1 + .../src/tsServer/bufferSyncSupport.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/extensions/typescript-language-features/src/configuration/fileSchemes.ts b/extensions/typescript-language-features/src/configuration/fileSchemes.ts index 9fb572c7175..07dd38a5379 100644 --- a/extensions/typescript-language-features/src/configuration/fileSchemes.ts +++ b/extensions/typescript-language-features/src/configuration/fileSchemes.ts @@ -19,6 +19,7 @@ export const vscodeNotebookCell = 'vscode-notebook-cell'; export const memFs = 'memfs'; export const vscodeVfs = 'vscode-vfs'; export const officeScript = 'office-script'; +export const chatCodeBlock = 'vscode-chat-code-block'; export function getSemanticSupportedSchemes() { if (isWeb() && vscode.workspace.workspaceFolders) { diff --git a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts index 90151ea6a08..c95b2e473e3 100644 --- a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts +++ b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { officeScript, vscodeNotebookCell } from '../configuration/fileSchemes'; +import * as fileSchemes from '../configuration/fileSchemes'; import * as languageModeIds from '../configuration/languageIds'; import * as typeConverters from '../typeConverters'; import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; @@ -227,7 +227,7 @@ class SyncedBuffer { return tsRoot?.startsWith(inMemoryResourcePrefix) ? undefined : tsRoot; } - return resource.scheme === officeScript ? '/' : undefined; + return resource.scheme === fileSchemes.officeScript || resource.scheme === fileSchemes.chatCodeBlock ? '/' : undefined; } public get resource(): vscode.Uri { @@ -395,7 +395,7 @@ class TabResourceTracker extends Disposable { } public has(resource: vscode.Uri): boolean { - if (resource.scheme === vscodeNotebookCell) { + if (resource.scheme === fileSchemes.vscodeNotebookCell) { const notebook = vscode.workspace.notebookDocuments.find(doc => doc.getCells().some(cell => cell.document.uri.toString() === resource.toString())); From fdbf304519ed209b0fabbcae67ac90c02117d356 Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 8 Feb 2024 08:19:53 +0900 Subject: [PATCH 1001/1897] fix: requirements detection for alpine (#204660) --- .../bin/helpers/check-requirements-linux.sh | 88 +++++++++++-------- 1 file changed, 50 insertions(+), 38 deletions(-) diff --git a/resources/server/bin/helpers/check-requirements-linux.sh b/resources/server/bin/helpers/check-requirements-linux.sh index 1b9199fd039..8c7d1523e21 100644 --- a/resources/server/bin/helpers/check-requirements-linux.sh +++ b/resources/server/bin/helpers/check-requirements-linux.sh @@ -19,16 +19,17 @@ if [ -f "/tmp/vscode-skip-server-requirements-check" ]; then exit 0 fi -BITNESS=$(getconf LONG_BIT) ARCH=$(uname -m) found_required_glibc=0 found_required_glibcxx=0 # Extract the ID value from /etc/os-release -OS_ID="$(cat /etc/os-release | grep -Eo 'ID=([^"]+)' | sed -n '1s/ID=//p')" -if [ "$OS_ID" = "nixos" ]; then - echo "Warning: NixOS detected, skipping GLIBC check" - exit 0 +if [ -f /etc/os-release ]; then + OS_ID="$(cat /etc/os-release | grep -Eo 'ID=([^"]+)' | sed -n '1s/ID=//p')" + if [ "$OS_ID" = "nixos" ]; then + echo "Warning: NixOS detected, skipping GLIBC check" + exit 0 + fi fi # Based on https://github.com/bminor/glibc/blob/520b1df08de68a3de328b65a25b86300a7ddf512/elf/cache.c#L162-L245 @@ -36,6 +37,7 @@ case $ARCH in x86_64) LDCONFIG_ARCH="x86-64";; armv7l | armv8l) LDCONFIG_ARCH="hard-float";; arm64 | aarch64) + BITNESS=$(getconf LONG_BIT) if [ "$BITNESS" = "32" ]; then # Can have 32-bit userland on 64-bit kernel LDCONFIG_ARCH="hard-float" @@ -45,23 +47,29 @@ case $ARCH in ;; esac -if [ -f /usr/lib64/libstdc++.so.6 ]; then - # Typical path - libstdcpp_path='/usr/lib64/libstdc++.so.6' -elif [ -f /usr/lib/libstdc++.so.6 ]; then - # Typical path - libstdcpp_path='/usr/lib/libstdc++.so.6' -elif [ -f /sbin/ldconfig ]; then - # Look up path - libstdcpp_paths=$(/sbin/ldconfig -p | grep 'libstdc++.so.6') +if [ "$OS_ID" != "alpine" ]; then + if [ -f /sbin/ldconfig ]; then + # Look up path + libstdcpp_paths=$(/sbin/ldconfig -p | grep 'libstdc++.so.6') - if [ "$(echo "$libstdcpp_paths" | wc -l)" -gt 1 ]; then - libstdcpp_path=$(echo "$libstdcpp_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}' | head -n1) - else - libstdcpp_path=$(echo "$libstdcpp_paths" | awk '{print $NF}') + if [ "$(echo "$libstdcpp_paths" | wc -l)" -gt 1 ]; then + libstdcpp_path=$(echo "$libstdcpp_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}' | head -n1) + else + libstdcpp_path=$(echo "$libstdcpp_paths" | awk '{print $NF}') + fi + fi +fi + +if [ -z "$libstdcpp_path" ]; then + if [ -f /usr/lib/libstdc++.so.6 ]; then + # Typical path + libstdcpp_path='/usr/lib/libstdc++.so.6' + elif [ -f /usr/lib64/libstdc++.so.6 ]; then + # Typical path + libstdcpp_path='/usr/lib64/libstdc++.so.6' + else + echo "Warning: Can't find libstdc++.so or ldconfig, can't verify libstdc++ version" fi -else - echo "Warning: Can't find libstdc++.so or ldconfig, can't verify libstdc++ version" fi if [ -n "$libstdcpp_path" ]; then @@ -78,14 +86,21 @@ if [ -n "$libstdcpp_path" ]; then fi fi -if [ -z "$(ldd --version 2>&1 | grep 'musl libc')" ]; then - if [ -f /usr/lib64/libc.so.6 ]; then - # Typical path - libc_path='/usr/lib64/libc.so.6' - elif [ -f /usr/lib/libc.so.6 ]; then - # Typical path - libc_path='/usr/lib/libc.so.6' - elif [ -f /sbin/ldconfig ]; then +if [ "$OS_ID" = "alpine" ]; then + MUSL_RTLDLIST="/lib/ld-musl-aarch64.so.1 /lib/ld-musl-x86_64.so.1" + for rtld in ${MUSL_RTLDLIST}; do + if [ -x $rtld ]; then + musl_version=$("$rtld" --version 2>&1 | grep "Version" | awk '{print $NF}') + break + fi + done + if [ "$(printf '%s\n' "1.2.3" "$musl_version" | sort -V | head -n1)" != "1.2.3" ]; then + echo "Error: Unsupported alpine distribution. Please refer to our supported distro section https://aka.ms/vscode-remote/linux for additional information." + exit 99 + fi + found_required_glibc=1 +elif [ -z "$(ldd --version 2>&1 | grep 'musl libc')" ]; then + if [ -f /sbin/ldconfig ]; then # Look up path libc_paths=$(/sbin/ldconfig -p | grep 'libc.so.6') @@ -94,6 +109,12 @@ if [ -z "$(ldd --version 2>&1 | grep 'musl libc')" ]; then else libc_path=$(echo "$libc_paths" | awk '{print $NF}') fi + elif [ -f /usr/lib/libc.so.6 ]; then + # Typical path + libc_path='/usr/lib/libc.so.6' + elif [ -f /usr/lib64/libc.so.6 ]; then + # Typical path + libc_path='/usr/lib64/libc.so.6' else echo "Warning: Can't find libc.so or ldconfig, can't verify libc version" fi @@ -110,16 +131,7 @@ if [ -z "$(ldd --version 2>&1 | grep 'musl libc')" ]; then fi fi else - if [ "$OS_ID" = "alpine" ]; then - musl_version=$(ldd --version 2>&1 | grep "Version" | awk '{print $NF}') - if [ "$(printf '%s\n' "1.2.3" "$musl_version" | sort -V | head -n1)" != "1.2.3" ]; then - echo "Error: Unsupported alpine distribution. Please refer to our supported distro section https://aka.ms/vscode-remote/linux for additional information." - exit 99 - fi - else - echo "Warning: musl detected, skipping GLIBC check" - fi - + echo "Warning: musl detected, skipping GLIBC check" found_required_glibc=1 fi From 428dd56479f4f9987fe0b0a444c11c46e84b2525 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 7 Feb 2024 15:21:42 -0800 Subject: [PATCH 1002/1897] Try to observe TS usage of insert/replace (#204661) Fixes #204037 - Replace span on item == use for both insert and replace - Optional replacement span == use only for replace --- .../src/languageFeatures/completions.ts | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/completions.ts b/extensions/typescript-language-features/src/languageFeatures/completions.ts index 817f614a6c5..a46835aa907 100644 --- a/extensions/typescript-language-features/src/languageFeatures/completions.ts +++ b/extensions/typescript-language-features/src/languageFeatures/completions.ts @@ -39,6 +39,7 @@ interface CompletionContext { readonly wordRange: vscode.Range | undefined; readonly line: string; + readonly optionalReplacementRange: vscode.Range | undefined; } type ResolvedCompletionItem = { @@ -363,18 +364,25 @@ class MyCompletionItem extends vscode.CompletionItem { private getRangeFromReplacementSpan(tsEntry: Proto.CompletionEntry, completionContext: CompletionContext) { if (!tsEntry.replacementSpan) { - return; + if (completionContext.optionalReplacementRange) { + return { + inserting: new vscode.Range(completionContext.optionalReplacementRange.start, this.position), + replacing: completionContext.optionalReplacementRange, + }; + } + + return undefined; } - let replaceRange = typeConverters.Range.fromTextSpan(tsEntry.replacementSpan); + // If TS returns an explicit replacement range on this item, we should use it for both types of completion + // Make sure we only replace a single line at most + let replaceRange = typeConverters.Range.fromTextSpan(tsEntry.replacementSpan); if (!replaceRange.isSingleLine) { replaceRange = new vscode.Range(replaceRange.start.line, replaceRange.start.character, replaceRange.start.line, completionContext.line.length); } - - // If TS returns an explicit replacement range, we should use it for both types of completion return { - inserting: new vscode.Range(replaceRange.start, this.position), + inserting: replaceRange, replacing: replaceRange, }; } @@ -735,6 +743,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< let metadata: any | undefined; let response: ServerResponse.Response | undefined; let duration: number | undefined; + let optionalReplacementRange: vscode.Range | undefined; if (this.client.apiVersion.gte(API.v300)) { const startTime = Date.now(); try { @@ -762,9 +771,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< metadata = response.metadata; if (response.body.optionalReplacementSpan) { - for (const entry of entries) { - entry.replacementSpan ??= response.body.optionalReplacementSpan; - } + optionalReplacementRange = typeConverters.Range.fromTextSpan(response.body.optionalReplacementSpan); } } else { const response = await this.client.interruptGetErr(() => this.client.execute('completions', args, token)); @@ -784,6 +791,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< wordRange, line: line.text, completeFunctionCalls: completionConfiguration.completeFunctionCalls, + optionalReplacementRange, }; let includesPackageJsonImport = false; From 06eee91ac73111061c40dc3496ed645cc08d4968 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 7 Feb 2024 15:37:58 -0800 Subject: [PATCH 1003/1897] eng: update CLI and a fix for extension test coverage (#204662) --- build/gulpfile.extensions.js | 3 +- package.json | 2 +- yarn.lock | 128 +++++++++++++++++++++++++++++++++-- 3 files changed, 125 insertions(+), 8 deletions(-) diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index bcdb206606b..579a62dbbbb 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -134,7 +134,8 @@ const tasks = compilations.map(function (tsconfigFile) { sourceMappingURL: !build ? null : f => `${baseUrl}/${f.relative}.map`, addComment: !!build, includeContent: !!build, - sourceRoot: '../src' + // note: trailing slash is important, else the source URLs in V8's file coverage are incorrect + sourceRoot: '../src/', })) .pipe(tsFilter.restore) .pipe(build ? nlsDev.bundleMetaDataFiles(headerId, headerOut) : es.through()) diff --git a/package.json b/package.json index c94ab6966c4..51d5b93310a 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "@vscode/gulp-electron": "^1.36.0", "@vscode/l10n-dev": "0.0.30", "@vscode/telemetry-extractor": "^1.10.2", - "@vscode/test-cli": "^0.0.3", + "@vscode/test-cli": "^0.0.6", "@vscode/test-electron": "^2.3.8", "@vscode/test-web": "^0.0.50", "@vscode/v8-heap-parser": "^0.1.0", diff --git a/yarn.lock b/yarn.lock index 1f21b0273ba..49eb5f91f6e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -370,6 +370,11 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -498,6 +503,11 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== +"@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -548,6 +558,14 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/trace-mapping@^0.3.12": + version "0.3.22" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz#72a621e5de59f5f1ef792d0793a82ee20f645e4c" + integrity sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@jridgewell/trace-mapping@^0.3.17": version "0.3.19" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" @@ -1062,6 +1080,11 @@ dependencies: "@types/node" "*" +"@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + "@types/json-schema@*": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" @@ -1440,13 +1463,15 @@ command-line-args "^5.2.1" ts-morph "^19.0.0" -"@vscode/test-cli@^0.0.3": - version "0.0.3" - resolved "https://registry.yarnpkg.com/@vscode/test-cli/-/test-cli-0.0.3.tgz#9b02943713652e84a675894ffa4a6fe5375496ab" - integrity sha512-Gk2Vo5OOoJ3bFChW+THN5/gVz7qsGfZUsTgMgQtpx39Z2NqyddONM4MDVGM83Hgjlr+4rCP9RUS5C0WL3ERtdw== +"@vscode/test-cli@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@vscode/test-cli/-/test-cli-0.0.6.tgz#13fe86902b2e8af3f9b67ff7cf66453ea0c24999" + integrity sha512-4i61OUv5PQr3GxhHOuUgHdgBDfIO/kXTPCsEyFiMaY4SOqQTgkTmyZLagHehjOgCfsXdcrJa3zgQ7zoc+Dh6hQ== dependencies: "@types/mocha" "^10.0.2" + c8 "^9.1.0" chokidar "^3.5.3" + enhanced-resolve "^5.15.0" glob "^10.3.10" minimatch "^9.0.3" mocha "^10.2.0" @@ -2381,6 +2406,23 @@ bytes@^3.0.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== +c8@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/c8/-/c8-9.1.0.tgz#0e57ba3ab9e5960ab1d650b4a86f71e53cb68112" + integrity sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@istanbuljs/schema" "^0.1.3" + find-up "^5.0.0" + foreground-child "^3.1.1" + istanbul-lib-coverage "^3.2.0" + istanbul-lib-report "^3.0.1" + istanbul-reports "^3.1.6" + test-exclude "^6.0.0" + v8-to-istanbul "^9.0.0" + yargs "^17.7.2" + yargs-parser "^21.1.1" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -2902,6 +2944,11 @@ convert-source-map@^1.0.0, convert-source-map@^1.5.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + cookie@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" @@ -3566,6 +3613,14 @@ enhanced-resolve@^5.10.0: graceful-fs "^4.2.4" tapable "^2.2.0" +enhanced-resolve@^5.15.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + entities@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" @@ -4340,7 +4395,7 @@ for-own@^1.0.0: dependencies: for-in "^1.0.1" -foreground-child@^3.1.0: +foreground-child@^3.1.0, foreground-child@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== @@ -4627,6 +4682,18 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-agent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6" @@ -5716,6 +5783,15 @@ istanbul-lib-report@^3.0.0: make-dir "^3.0.0" supports-color "^7.1.0" +istanbul-lib-report@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + istanbul-lib-source-maps@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" @@ -5733,6 +5809,14 @@ istanbul-reports@^3.1.5: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +istanbul-reports@^3.1.6: + version "3.1.6" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a" + integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + istextorbinary@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-1.0.2.tgz#ace19354d1a9a0173efeb1084ce0f87b0ad7decf" @@ -6248,6 +6332,13 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" @@ -6502,7 +6593,7 @@ mimic-response@^3.1.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== -"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.2: +"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -8411,6 +8502,13 @@ semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semve dependencies: lru-cache "^6.0.0" +semver@^7.5.3: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + serialize-error@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" @@ -9215,6 +9313,15 @@ terser@^5.7.0: source-map "~0.7.2" source-map-support "~0.5.19" +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -9690,6 +9797,15 @@ v8-inspect-profiler@^0.1.0: dependencies: chrome-remote-interface "0.28.2" +v8-to-istanbul@^9.0.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" + integrity sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + v8flags@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.2.0.tgz#b243e3b4dfd731fa774e7492128109a0fe66d656" From f6a5654dbf9811b94dd8848571a84b73a7a34813 Mon Sep 17 00:00:00 2001 From: Dennis Date: Wed, 7 Feb 2024 16:11:14 -0800 Subject: [PATCH 1004/1897] Pass the check if any one of the library (of the arch) satisfies the requirement. (#204221) * Update check-requirements-linux.sh Pass the check if one of the library (of the arch) satisfies the requirement. * Update resources/server/bin/helpers/check-requirements-linux.sh Co-authored-by: Robo * Update resources/server/bin/helpers/check-requirements-linux.sh Co-authored-by: Robo * Update resources/server/bin/helpers/check-requirements-linux.sh Co-authored-by: Robo * Update resources/server/bin/helpers/check-requirements-linux.sh Co-authored-by: Robo * Update resources/server/bin/helpers/check-requirements-linux.sh Co-authored-by: Robo * Update resources/server/bin/helpers/check-requirements-linux.sh Co-authored-by: Robo --------- Co-authored-by: Robo --- .../bin/helpers/check-requirements-linux.sh | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/resources/server/bin/helpers/check-requirements-linux.sh b/resources/server/bin/helpers/check-requirements-linux.sh index 8c7d1523e21..9888bbe0831 100644 --- a/resources/server/bin/helpers/check-requirements-linux.sh +++ b/resources/server/bin/helpers/check-requirements-linux.sh @@ -53,7 +53,7 @@ if [ "$OS_ID" != "alpine" ]; then libstdcpp_paths=$(/sbin/ldconfig -p | grep 'libstdc++.so.6') if [ "$(echo "$libstdcpp_paths" | wc -l)" -gt 1 ]; then - libstdcpp_path=$(echo "$libstdcpp_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}' | head -n1) + libstdcpp_path=$(echo "$libstdcpp_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}') else libstdcpp_path=$(echo "$libstdcpp_paths" | awk '{print $NF}') fi @@ -72,18 +72,22 @@ if [ -z "$libstdcpp_path" ]; then fi fi -if [ -n "$libstdcpp_path" ]; then +while [ -n "$libstdcpp_path" ]; do # Extracts the version number from the path, e.g. libstdc++.so.6.0.22 -> 6.0.22 # which is then compared based on the fact that release versioning and symbol versioning # are aligned for libstdc++. Refs https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html # (i-e) GLIBCXX_3.4. is provided by libstdc++.so.6.y. - libstdcpp_real_path=$(readlink -f "$libstdcpp_path") + libstdcpp_path_line=$(echo "$libstdcpp_path" | head -n1) + libstdcpp_real_path=$(readlink -f "$libstdcpp_path_line") libstdcpp_version=$(echo "$libstdcpp_real_path" | awk -F'\\.so\\.' '{print $NF}') if [ "$(printf '%s\n' "6.0.25" "$libstdcpp_version" | sort -V | head -n1)" = "6.0.25" ]; then found_required_glibcxx=1 - else - echo "Warning: Missing GLIBCXX >= 3.4.25! from $libstdcpp_real_path" + break fi + libstdcpp_path=$(echo "$libstdcpp_path" | tail -n +2) # remove first line +done +if [ "$found_required_glibcxx" = "0" ]; then + echo "Warning: Missing GLIBCXX >= 3.4.25! from $libstdcpp_real_path" fi if [ "$OS_ID" = "alpine" ]; then @@ -105,7 +109,7 @@ elif [ -z "$(ldd --version 2>&1 | grep 'musl libc')" ]; then libc_paths=$(/sbin/ldconfig -p | grep 'libc.so.6') if [ "$(echo "$libc_paths" | wc -l)" -gt 1 ]; then - libc_path=$(echo "$libc_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}' | head -n1) + libc_path=$(echo "$libc_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}') else libc_path=$(echo "$libc_paths" | awk '{print $NF}') fi @@ -119,16 +123,20 @@ elif [ -z "$(ldd --version 2>&1 | grep 'musl libc')" ]; then echo "Warning: Can't find libc.so or ldconfig, can't verify libc version" fi - if [ -n "$libc_path" ]; then + while [ -n "$libc_path" ]; do # Rather than trusting the output of ldd --version (which is not always accurate) # we instead use the version of the cached libc.so.6 file itself. - libc_real_path=$(readlink -f "$libc_path") + libc_path_line=$(echo "$libc_path" | head -n1) + libc_real_path=$(readlink -f "$libc_path_line") libc_version=$(cat "$libc_real_path" | sed -n 's/.*release version \([0-9]\+\.[0-9]\+\).*/\1/p') if [ "$(printf '%s\n' "2.28" "$libc_version" | sort -V | head -n1)" = "2.28" ]; then found_required_glibc=1 - else - echo "Warning: Missing GLIBC >= 2.28! from $libc_real_path" + break fi + libc_path=$(echo "$libc_path" | tail -n +2) # remove first line + done + if [ "$found_required_glibc" = "0" ]; then + echo "Warning: Missing GLIBC >= 2.28! from $libc_real_path" fi else echo "Warning: musl detected, skipping GLIBC check" From 2eb66826bdec26623ddc88aecc2043993db8878e Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 7 Feb 2024 16:24:33 -0800 Subject: [PATCH 1005/1897] testing: rename FunctionCoverage -> DeclarationCoverage for finalization (#204667) --- .../workbench/api/common/extHost.api.impl.ts | 4 +- .../api/common/extHostTypeConverters.ts | 4 +- src/vs/workbench/api/common/extHostTypes.ts | 14 ++-- .../browser/codeCoverageDecorations.ts | 6 +- .../testing/browser/testCoverageBars.ts | 10 +-- .../testing/browser/testCoverageView.ts | 76 +++++++++---------- .../contrib/testing/common/testCoverage.ts | 8 +- .../contrib/testing/common/testTypes.ts | 30 ++++---- .../vscode.proposed.testCoverage.d.ts | 37 +++++---- 9 files changed, 94 insertions(+), 95 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 5b7786330ac..f86fd099ef4 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1615,7 +1615,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I TestResultState: extHostTypes.TestResultState, TestRunRequest: extHostTypes.TestRunRequest, TestMessage: extHostTypes.TestMessage, - TestMessage2: extHostTypes.TestMessage, // back compat for Oct 2023 TestTag: extHostTypes.TestTag, TestRunProfileKind: extHostTypes.TestRunProfileKind, TextSearchCompleteMessageType: TextSearchCompleteMessageType, @@ -1625,7 +1624,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I FileCoverage: extHostTypes.FileCoverage, StatementCoverage: extHostTypes.StatementCoverage, BranchCoverage: extHostTypes.BranchCoverage, - FunctionCoverage: extHostTypes.FunctionCoverage, + DeclarationCoverage: extHostTypes.DeclarationCoverage, + FunctionCoverage: extHostTypes.DeclarationCoverage, // back compat for Feb 2024 WorkspaceTrustState: extHostTypes.WorkspaceTrustState, LanguageStatusSeverity: extHostTypes.LanguageStatusSeverity, QuickPickItemKind: extHostTypes.QuickPickItemKind, diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 42a57e47c13..6237bc44823 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2021,7 +2021,7 @@ export namespace TestCoverage { }; } else { return { - type: DetailType.Function, + type: DetailType.Declaration, name: coverage.name, count: coverage.executed, location: fromLocation(coverage.location), @@ -2034,7 +2034,7 @@ export namespace TestCoverage { uri: coverage.uri, statement: fromCoveredCount(coverage.statementCoverage), branch: coverage.branchCoverage && fromCoveredCount(coverage.branchCoverage), - function: coverage.functionCoverage && fromCoveredCount(coverage.functionCoverage), + declaration: coverage.declarationCoverage && fromCoveredCount(coverage.declarationCoverage), details: coverage.detailedCoverage?.map(fromDetailed), }; } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index b0269443d64..300a1a4c549 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3970,7 +3970,7 @@ export class FileCoverage implements vscode.FileCoverage { public static fromDetails(uri: vscode.Uri, details: vscode.DetailedCoverage[]): vscode.FileCoverage { const statements = new CoveredCount(0, 0); const branches = new CoveredCount(0, 0); - const fn = new CoveredCount(0, 0); + const decl = new CoveredCount(0, 0); for (const detail of details) { if ('branches' in detail) { @@ -3982,8 +3982,8 @@ export class FileCoverage implements vscode.FileCoverage { branches.covered += branch.executed ? 1 : 0; } } else { - fn.total += 1; - fn.covered += detail.executed ? 1 : 0; + decl.total += 1; + decl.covered += detail.executed ? 1 : 0; } } @@ -3991,7 +3991,7 @@ export class FileCoverage implements vscode.FileCoverage { uri, statements, branches.total > 0 ? branches : undefined, - fn.total > 0 ? fn : undefined, + decl.total > 0 ? decl : undefined, ); coverage.detailedCoverage = details; @@ -4005,11 +4005,11 @@ export class FileCoverage implements vscode.FileCoverage { public readonly uri: vscode.Uri, public statementCoverage: vscode.CoveredCount, public branchCoverage?: vscode.CoveredCount, - public functionCoverage?: vscode.CoveredCount, + public declarationCoverage?: vscode.CoveredCount, ) { validateCC(statementCoverage); validateCC(branchCoverage); - validateCC(functionCoverage); + validateCC(declarationCoverage); } } @@ -4037,7 +4037,7 @@ export class BranchCoverage implements vscode.BranchCoverage { ) { } } -export class FunctionCoverage implements vscode.FunctionCoverage { +export class DeclarationCoverage implements vscode.DeclarationCoverage { // back compat until finalization: get executionCount() { return +this.executed; } set executionCount(n: number) { this.executed = n; } diff --git a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts index 0c556853d8c..d96710fbd0d 100644 --- a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts @@ -366,7 +366,7 @@ export class CoverageDetailsModel { //#region decoration generation // Coverage from a provider can have a range that contains smaller ranges, - // such as a function declarationt that has nested statements. In this we + // such as a function declaration that has nested statements. In this we // make sequential, non-overlapping ranges for each detail for display in // the editor without ugly overlaps. const detailRanges: DetailRange[] = details.map(detail => ({ @@ -445,8 +445,8 @@ export class CoverageDetailsModel { /** Gets the markdown description for the given detail */ public describe(detail: CoverageDetailsWithBranch, model: ITextModel): IMarkdownString | undefined { - if (detail.type === DetailType.Function) { - return new MarkdownString().appendMarkdown(localize('coverage.fnExecutedCount', 'Function `{0}` was executed {1} time(s).', detail.name, detail.count)); + if (detail.type === DetailType.Declaration) { + return new MarkdownString().appendMarkdown(localize('coverage.declExecutedCount', '`{0}` was executed {1} time(s).', detail.name, detail.count)); } else if (detail.type === DetailType.Statement) { const text = wrapName(model.getValueInRange(tidyLocation(detail.location)).trim() || ``); const str = new MarkdownString(); diff --git a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts index 9a1c2987b9d..08cb528de32 100644 --- a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts +++ b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts @@ -35,7 +35,7 @@ export interface TestCoverageBarsOptions { } /** Type that can be used to render coverage bars */ -export type CoverageBarSource = Pick; +export type CoverageBarSource = Pick; export class ManagedTestCoverageBars extends Disposable { private _coverage?: CoverageBarSource; @@ -142,7 +142,7 @@ export class ManagedTestCoverageBars extends Disposable { renderBar(el.tpcBar, overallStat, false, thresholds); } else { renderBar(el.statement, percent(coverage.statement), coverage.statement.total === 0, thresholds); - renderBar(el.function, coverage.function && percent(coverage.function), coverage.function?.total === 0, thresholds); + renderBar(el.function, coverage.declaration && percent(coverage.declaration), coverage.declaration?.total === 0, thresholds); renderBar(el.branch, coverage.branch && percent(coverage.branch), coverage.branch?.total === 0, thresholds); } } @@ -196,11 +196,11 @@ const calculateDisplayedStat = (coverage: CoverageBarSource, method: TestingDisp case TestingDisplayedCoveragePercent.Minimum: { let value = percent(coverage.statement); if (coverage.branch) { value = Math.min(value, percent(coverage.branch)); } - if (coverage.function) { value = Math.min(value, percent(coverage.function)); } + if (coverage.declaration) { value = Math.min(value, percent(coverage.declaration)); } return value; } case TestingDisplayedCoveragePercent.TotalCoverage: - return getTotalCoveragePercent(coverage.statement, coverage.branch, coverage.function); + return getTotalCoveragePercent(coverage.statement, coverage.branch, coverage.declaration); default: assertNever(method); } @@ -219,7 +219,7 @@ const displayPercent = (value: number, precision = 2) => { }; const stmtCoverageText = (coverage: CoverageBarSource) => localize('statementCoverage', '{0}/{1} statements covered ({2})', coverage.statement.covered, coverage.statement.total, displayPercent(percent(coverage.statement))); -const fnCoverageText = (coverage: CoverageBarSource) => coverage.function && localize('functionCoverage', '{0}/{1} functions covered ({2})', coverage.function.covered, coverage.function.total, displayPercent(percent(coverage.function))); +const fnCoverageText = (coverage: CoverageBarSource) => coverage.declaration && localize('functionCoverage', '{0}/{1} functions covered ({2})', coverage.declaration.covered, coverage.declaration.total, displayPercent(percent(coverage.declaration))); const branchCoverageText = (coverage: CoverageBarSource) => coverage.branch && localize('branchCoverage', '{0}/{1} branches covered ({2})', coverage.branch.covered, coverage.branch.total, displayPercent(percent(coverage.branch))); const getOverallHoverText = (coverage: CoverageBarSource) => new MarkdownString([ diff --git a/src/vs/workbench/contrib/testing/browser/testCoverageView.ts b/src/vs/workbench/contrib/testing/browser/testCoverageView.ts index 5b583bf8322..2451613400f 100644 --- a/src/vs/workbench/contrib/testing/browser/testCoverageView.ts +++ b/src/vs/workbench/contrib/testing/browser/testCoverageView.ts @@ -44,7 +44,7 @@ import { CoverageBarSource, ManagedTestCoverageBars } from 'vs/workbench/contrib import { TestCommandId, Testing } from 'vs/workbench/contrib/testing/common/constants'; import { ComputedFileCoverage, FileCoverage, TestCoverage, getTotalCoveragePercent } from 'vs/workbench/contrib/testing/common/testCoverage'; import { ITestCoverageService } from 'vs/workbench/contrib/testing/common/testCoverageService'; -import { CoverageDetails, DetailType, ICoveredCount, IFunctionCoverage, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes'; +import { CoverageDetails, DetailType, ICoveredCount, IDeclarationCoverage, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; const enum CoverageSortOrder { @@ -97,10 +97,10 @@ export class TestCoverageView extends ViewPane { let fnNodeId = 0; -class FunctionCoverageNode { +class DeclarationCoverageNode { public readonly id = String(fnNodeId++); public readonly containedDetails = new Set(); - public readonly children: FunctionCoverageNode[] = []; + public readonly children: DeclarationCoverageNode[] = []; public get hits() { return this.data.count; @@ -121,7 +121,7 @@ class FunctionCoverageNode { constructor( public readonly uri: URI, - private readonly data: IFunctionCoverage, + private readonly data: IDeclarationCoverage, details: readonly CoverageDetails[], ) { if (data.location instanceof Range) { @@ -172,11 +172,11 @@ class FunctionCoverageNode { } } -class RevealUncoveredFunctions { +class RevealUncoveredDeclarations { public readonly id = String(fnNodeId++); public get label() { - return localize('functionsWithoutCoverage', "{0} functions without coverage...", this.n); + return localize('functionsWithoutCoverage', "{0} declarations without coverage...", this.n); } constructor(public readonly n: number) { } @@ -189,12 +189,12 @@ class LoadingDetails { /** Type of nodes returned from {@link TestCoverage}. Note: value is *always* defined. */ type TestCoverageFileNode = IPrefixTreeNode; -type CoverageTreeElement = TestCoverageFileNode | FunctionCoverageNode | LoadingDetails | RevealUncoveredFunctions; +type CoverageTreeElement = TestCoverageFileNode | DeclarationCoverageNode | LoadingDetails | RevealUncoveredDeclarations; const isFileCoverage = (c: CoverageTreeElement): c is TestCoverageFileNode => typeof c === 'object' && 'value' in c; -const isFunctionCoverage = (c: CoverageTreeElement): c is FunctionCoverageNode => c instanceof FunctionCoverageNode; -const shouldShowFunctionDetailsOnExpand = (c: CoverageTreeElement): c is IPrefixTreeNode => - isFileCoverage(c) && c.value instanceof FileCoverage && !!c.value.function?.total; +const isDeclarationCoverage = (c: CoverageTreeElement): c is DeclarationCoverageNode => c instanceof DeclarationCoverageNode; +const shouldShowDeclDetailsOnExpand = (c: CoverageTreeElement): c is IPrefixTreeNode => + isFileCoverage(c) && c.value instanceof FileCoverage && !!c.value.declaration?.total; class TestCoverageTree extends Disposable { private readonly tree: WorkbenchCompressibleObjectTree; @@ -215,7 +215,7 @@ class TestCoverageTree extends Disposable { new TestCoverageTreeListDelegate(), [ instantiationService.createInstance(FileCoverageRenderer, labels), - instantiationService.createInstance(FunctionCoverageRenderer), + instantiationService.createInstance(DeclarationCoverageRenderer), instantiationService.createInstance(BasicRenderer), ], { @@ -256,7 +256,7 @@ class TestCoverageTree extends Disposable { this._register(this.tree); this._register(this.tree.onDidChangeCollapseState(e => { const el = e.node.element; - if (!e.node.collapsed && !e.node.children.length && el && shouldShowFunctionDetailsOnExpand(el)) { + if (!e.node.collapsed && !e.node.children.length && el && shouldShowDeclDetailsOnExpand(el)) { if (el.value!.hasSynchronousDetails) { this.tree.setChildren(el, [{ element: new LoadingDetails(), incompressible: true }]); } @@ -270,7 +270,7 @@ class TestCoverageTree extends Disposable { if (e.element) { if (isFileCoverage(e.element) && !e.element.children?.size) { resource = e.element.value!.uri; - } else if (isFunctionCoverage(e.element)) { + } else if (isDeclarationCoverage(e.element)) { resource = e.element.uri; selection = e.element.location; } @@ -310,7 +310,7 @@ class TestCoverageTree extends Disposable { incompressible: isFile, collapsed: isFile, // directories can be expanded, and items with function info can be expanded - collapsible: !isFile || !!file.value?.function?.total, + collapsible: !isFile || !!file.value?.declaration?.total, children: file.children && Iterable.map(file.children?.values(), toChild) }; }; @@ -327,13 +327,13 @@ class TestCoverageTree extends Disposable { return; // avoid any issues if the tree changes in the meanwhile } - const functions: FunctionCoverageNode[] = []; + const decl: DeclarationCoverageNode[] = []; for (const fn of details) { - if (fn.type !== DetailType.Function) { + if (fn.type !== DetailType.Declaration) { continue; } - let arr = functions; + let arr = decl; while (true) { const parent = arr.find(p => p.containedDetails.has(fn)); if (parent) { @@ -343,10 +343,10 @@ class TestCoverageTree extends Disposable { } } - arr.push(new FunctionCoverageNode(el.value!.uri, fn, details)); + arr.push(new DeclarationCoverageNode(el.value!.uri, fn, details)); } - const makeChild = (fn: FunctionCoverageNode): ICompressedTreeElement => ({ + const makeChild = (fn: DeclarationCoverageNode): ICompressedTreeElement => ({ element: fn, incompressible: true, collapsed: true, @@ -354,7 +354,7 @@ class TestCoverageTree extends Disposable { children: fn.children.map(makeChild) }); - this.tree.setChildren(el, functions.map(makeChild)); + this.tree.setChildren(el, decl.map(makeChild)); } } @@ -367,10 +367,10 @@ class TestCoverageTreeListDelegate implements IListVirtualDelegate { case CoverageSortOrder.Coverage: return b.value!.tpc - a.value!.tpc; } - } else if (isFunctionCoverage(a) && isFunctionCoverage(b)) { + } else if (isDeclarationCoverage(a) && isDeclarationCoverage(b)) { switch (order) { case CoverageSortOrder.Location: return Position.compare( @@ -474,7 +474,7 @@ class FileCoverageRenderer implements ICompressibleTreeRenderer { +class DeclarationCoverageRenderer implements ICompressibleTreeRenderer { public static readonly ID = 'N'; - public readonly templateId = FunctionCoverageRenderer.ID; + public readonly templateId = DeclarationCoverageRenderer.ID; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, ) { } /** @inheritdoc */ - public renderTemplate(container: HTMLElement): FunctionTemplateData { + public renderTemplate(container: HTMLElement): DeclarationTemplateData { const templateDisposables = new DisposableStore(); container.classList.add('test-coverage-list-item'); const icon = dom.append(container, dom.$('.state')); @@ -507,21 +507,21 @@ class FunctionCoverageRenderer implements ICompressibleTreeRenderer, _index: number, templateData: FunctionTemplateData): void { - this.doRender(node.element as FunctionCoverageNode, templateData, node.filterData); + public renderElement(node: ITreeNode, _index: number, templateData: DeclarationTemplateData): void { + this.doRender(node.element as DeclarationCoverageNode, templateData, node.filterData); } /** @inheritdoc */ - public renderCompressedElements(node: ITreeNode, FuzzyScore>, _index: number, templateData: FunctionTemplateData): void { - this.doRender(node.element.elements[node.element.elements.length - 1] as FunctionCoverageNode, templateData, node.filterData); + public renderCompressedElements(node: ITreeNode, FuzzyScore>, _index: number, templateData: DeclarationTemplateData): void { + this.doRender(node.element.elements[node.element.elements.length - 1] as DeclarationCoverageNode, templateData, node.filterData); } - public disposeTemplate(templateData: FunctionTemplateData) { + public disposeTemplate(templateData: DeclarationTemplateData) { templateData.templateDisposables.dispose(); } /** @inheritdoc */ - private doRender(element: FunctionCoverageNode, templateData: FunctionTemplateData, _filterData: FuzzyScore | undefined) { + private doRender(element: DeclarationCoverageNode, templateData: DeclarationTemplateData, _filterData: FuzzyScore | undefined) { const covered = !!element.hits; const icon = covered ? testingWasCovered : testingStatesToIcons.get(TestResultState.Unset); templateData.container.classList.toggle('not-covered', !covered); @@ -552,7 +552,7 @@ class BasicRenderer implements ICompressibleTreeRenderer()); const items: Item[] = [ - { label: localize('testing.coverageSortByLocation', 'Sort by Location'), value: CoverageSortOrder.Location, description: localize('testing.coverageSortByLocationDescription', 'Files are sorted alphabetically, functions are sorted by position') }, - { label: localize('testing.coverageSortByCoverage', 'Sort by Coverage'), value: CoverageSortOrder.Coverage, description: localize('testing.coverageSortByCoverageDescription', 'Files and functions are sorted by total coverage') }, - { label: localize('testing.coverageSortByName', 'Sort by Name'), value: CoverageSortOrder.Name, description: localize('testing.coverageSortByNameDescription', 'Files and functions are sorted alphabetically') }, + { label: localize('testing.coverageSortByLocation', 'Sort by Location'), value: CoverageSortOrder.Location, description: localize('testing.coverageSortByLocationDescription', 'Files are sorted alphabetically, declarations are sorted by position') }, + { label: localize('testing.coverageSortByCoverage', 'Sort by Coverage'), value: CoverageSortOrder.Coverage, description: localize('testing.coverageSortByCoverageDescription', 'Files and declarations are sorted by total coverage') }, + { label: localize('testing.coverageSortByName', 'Sort by Name'), value: CoverageSortOrder.Name, description: localize('testing.coverageSortByNameDescription', 'Files and declarations are sorted alphabetically') }, ]; quickInput.placeholder = localize('testing.coverageSortPlaceholder', 'Sort the Test Coverage view...'); diff --git a/src/vs/workbench/contrib/testing/common/testCoverage.ts b/src/vs/workbench/contrib/testing/common/testCoverage.ts index 7545c7c3da6..05d532263da 100644 --- a/src/vs/workbench/contrib/testing/common/testCoverage.ts +++ b/src/vs/workbench/contrib/testing/common/testCoverage.ts @@ -98,7 +98,7 @@ export class TestCoverage { ICoveredCount.sum(fileCoverage.statement, v.statement); if (v.branch) { ICoveredCount.sum(fileCoverage.branch ??= ICoveredCount.empty(), v.branch); } - if (v.function) { ICoveredCount.sum(fileCoverage.function ??= ICoveredCount.empty(), v.function); } + if (v.declaration) { ICoveredCount.sum(fileCoverage.declaration ??= ICoveredCount.empty(), v.declaration); } } } @@ -146,21 +146,21 @@ export abstract class AbstractFileCoverage { public readonly uri: URI; public readonly statement: ICoveredCount; public readonly branch?: ICoveredCount; - public readonly function?: ICoveredCount; + public readonly declaration?: ICoveredCount; /** * Gets the total coverage percent based on information provided. * This is based on the Clover total coverage formula */ public get tpc() { - return getTotalCoveragePercent(this.statement, this.branch, this.function); + return getTotalCoveragePercent(this.statement, this.branch, this.declaration); } constructor(coverage: IFileCoverage) { this.uri = coverage.uri; this.statement = coverage.statement; this.branch = coverage.branch; - this.function = coverage.function; + this.declaration = coverage.declaration; } } diff --git a/src/vs/workbench/contrib/testing/common/testTypes.ts b/src/vs/workbench/contrib/testing/common/testTypes.ts index ed4ff1c93ea..7737ef78020 100644 --- a/src/vs/workbench/contrib/testing/common/testTypes.ts +++ b/src/vs/workbench/contrib/testing/common/testTypes.ts @@ -549,7 +549,7 @@ export interface IFileCoverage { uri: URI; statement: ICoveredCount; branch?: ICoveredCount; - function?: ICoveredCount; + declaration?: ICoveredCount; details?: CoverageDetails[]; } @@ -559,14 +559,14 @@ export namespace IFileCoverage { uri: UriComponents; statement: ICoveredCount; branch?: ICoveredCount; - function?: ICoveredCount; + declaration?: ICoveredCount; details?: CoverageDetails.Serialized[]; } export const serialize = (original: Readonly): Serialized => ({ statement: original.statement, branch: original.branch, - function: original.function, + declaration: original.declaration, details: original.details?.map(CoverageDetails.serialize), uri: original.uri.toJSON(), }); @@ -574,7 +574,7 @@ export namespace IFileCoverage { export const deserialize = (uriIdentity: ITestUriCanonicalizer, serialized: Serialized): IFileCoverage => ({ statement: serialized.statement, branch: serialized.branch, - function: serialized.function, + declaration: serialized.declaration, details: serialized.details?.map(CoverageDetails.deserialize), uri: uriIdentity.asCanonicalUri(URI.revive(serialized.uri)), }); @@ -596,21 +596,21 @@ function deserializeThingWithLocation): Serialized => - original.type === DetailType.Function ? IFunctionCoverage.serialize(original) : IStatementCoverage.serialize(original); + original.type === DetailType.Declaration ? IDeclarationCoverage.serialize(original) : IStatementCoverage.serialize(original); export const deserialize = (serialized: Serialized): CoverageDetails => - serialized.type === DetailType.Function ? IFunctionCoverage.deserialize(serialized) : IStatementCoverage.deserialize(serialized); + serialized.type === DetailType.Declaration ? IDeclarationCoverage.deserialize(serialized) : IStatementCoverage.deserialize(serialized); } export interface IBranchCoverage { @@ -630,23 +630,23 @@ export namespace IBranchCoverage { export const deserialize: (original: Serialized) => IBranchCoverage = deserializeThingWithLocation; } -export interface IFunctionCoverage { - type: DetailType.Function; +export interface IDeclarationCoverage { + type: DetailType.Declaration; name: string; count: number | boolean; location: Range | Position; } -export namespace IFunctionCoverage { +export namespace IDeclarationCoverage { export interface Serialized { - type: DetailType.Function; + type: DetailType.Declaration; name: string; count: number | boolean; location: IRange | IPosition; } - export const serialize: (original: IFunctionCoverage) => Serialized = serializeThingWithLocation; - export const deserialize: (original: Serialized) => IFunctionCoverage = deserializeThingWithLocation; + export const serialize: (original: IDeclarationCoverage) => Serialized = serializeThingWithLocation; + export const deserialize: (original: Serialized) => IDeclarationCoverage = deserializeThingWithLocation; } export interface IStatementCoverage { diff --git a/src/vscode-dts/vscode.proposed.testCoverage.d.ts b/src/vscode-dts/vscode.proposed.testCoverage.d.ts index aa0b1fee9ac..614792e0d9c 100644 --- a/src/vscode-dts/vscode.proposed.testCoverage.d.ts +++ b/src/vscode-dts/vscode.proposed.testCoverage.d.ts @@ -47,7 +47,7 @@ declare module 'vscode' { /** * A class that contains information about a covered resource. A count can - * be give for lines, branches, and functions in a file. + * be give for lines, branches, and declarations in a file. */ export class CoveredCount { /** @@ -87,9 +87,10 @@ declare module 'vscode' { branchCoverage?: CoveredCount; /** - * Function coverage information. + * Declaration coverage information. Depending on the reporter and + * language, this may be types such as functions, methods, or namespaces. */ - functionCoverage?: CoveredCount; + declarationCoverage?: CoveredCount; /** * Detailed, per-statement coverage. If this is undefined, the editor will @@ -111,19 +112,16 @@ declare module 'vscode' { * does not provide statement coverage information, this can instead be * used to represent line coverage. * @param branchCoverage Branch coverage information - * @param functionCoverage Function coverage information + * @param declarationCoverage Declaration coverage information */ constructor( uri: Uri, statementCoverage: CoveredCount, branchCoverage?: CoveredCount, - functionCoverage?: CoveredCount, + declarationCoverage?: CoveredCount, ); } - // @API are StatementCoverage and BranchCoverage etc really needed - // or is a generic type with a kind-property enough - /** * Contains coverage information for a single statement or line. */ @@ -189,35 +187,36 @@ declare module 'vscode' { } /** - * Contains coverage information for a function or method. + * Contains coverage information for a declaration. Depending on the reporter + * and language, this may be types such as functions, methods, or namespaces. */ - export class FunctionCoverage { + export class DeclarationCoverage { /** - * Name of the function or method. + * Name of the declaration. */ name: string; /** - * The number of times this function was executed, or a boolean indicating - * whether it was executed if the exact count is unknown. If zero or false, - * the function will be marked as un-covered. + * The number of times this declaration was executed, or a boolean + * indicating whether it was executed if the exact count is unknown. If + * zero or false, the declaration will be marked as un-covered. */ executed: number | boolean; /** - * Function location. + * Declaration location. */ location: Position | Range; /** - * @param executed The number of times this function was executed, or a + * @param executed The number of times this declaration was executed, or a * boolean indicating whether it was executed if the exact count is - * unknown. If zero or false, the function will be marked as un-covered. - * @param location The function position. + * unknown. If zero or false, the declaration will be marked as un-covered. + * @param location The declaration position. */ constructor(name: string, executed: number | boolean, location: Position | Range); } - export type DetailedCoverage = StatementCoverage | FunctionCoverage; + export type DetailedCoverage = StatementCoverage | DeclarationCoverage; } From da36e0eba18babc8eb3e18f5a9b30e1dd7e083d4 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 7 Feb 2024 18:01:32 -0800 Subject: [PATCH 1006/1897] Disable url smart paste in autolinks (#204673) Fixes #188859 --- .../src/languageFeatures/copyFiles/pasteUrlProvider.ts | 1 + .../markdown-language-features/src/test/pasteUrl.test.ts | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts index 4968f3cb227..a57f0d39005 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts @@ -89,6 +89,7 @@ const smartPasteLineRegexes = [ { regex: /\$\$[\s\S]*?\$\$/gm }, // In a fenced math block { regex: /`[^`]*`/g }, // In inline code { regex: /\$[^$]*\$/g }, // In inline math + { regex: /<[^<>\s]*>/g }, // Autolink { regex: /^[ ]{0,3}\[\w+\]:\s.*$/g, isWholeLine: true }, // Block link definition (needed as tokens are not generated for these) ]; diff --git a/extensions/markdown-language-features/src/test/pasteUrl.test.ts b/extensions/markdown-language-features/src/test/pasteUrl.test.ts index 88b996de37d..df863a25652 100644 --- a/extensions/markdown-language-features/src/test/pasteUrl.test.ts +++ b/extensions/markdown-language-features/src/test/pasteUrl.test.ts @@ -300,5 +300,11 @@ suite('createEditAddingLinksForUriList', () => { await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc(' \r\n\r\n'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 7)], noopToken), false); }); + + test('Smart should be disabled inside of autolinks', async () => { + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('<>'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 1, 0, 1)], noopToken), + false); + }); }); }); From 3b5844353f73230abaa22b6c87755bddbb0aeb78 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 8 Feb 2024 08:50:47 +0100 Subject: [PATCH 1007/1897] window - guard against invalid title settings (#204683) --- .../browser/parts/titlebar/windowTitle.ts | 38 ++++++++++++++----- .../browser/workbench.contribution.ts | 18 ++------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts index 0628c153307..3ec39eeafc2 100644 --- a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts +++ b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts @@ -12,7 +12,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { EditorResourceAccessor, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { isWindows, isWeb, isMacintosh } from 'vs/base/common/platform'; +import { isWindows, isWeb, isMacintosh, isNative } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { trim } from 'vs/base/common/strings'; import { IEditorGroupsContainer } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -30,9 +30,23 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; const enum WindowSettingNames { titleSeparator = 'window.titleSeparator', - title = 'window.title', + title = 'window.title' } +export const defaultWindowTitle = (() => { + if (isMacintosh && isNative) { + return '${activeEditorShort}${separator}${rootName}${separator}${profileName}'; // macOS has native dirty indicator + } + + const base = '${dirty}${activeEditorShort}${separator}${rootName}${separator}${profileName}${separator}${appName}'; + if (isWeb) { + return base + '${separator}${remoteName}'; // Web: always show remote name + } + + return base; +})(); +export const defaultWindowTitleSeparator = isMacintosh ? ' \u2014 ' : ' - '; + export class WindowTitle extends Disposable { private static readonly NLS_USER_IS_ADMIN = isWindows ? localize('userIsAdmin', "[Administrator]") : localize('userIsSudo', "[Superuser]"); @@ -324,17 +338,24 @@ export class WindowTitle extends Disposable { const dirty = editor?.isDirty() && !editor.isSaving() ? WindowTitle.TITLE_DIRTY : ''; const appName = this.productService.nameLong; const profileName = this.userDataProfileService.currentProfile.isDefault ? '' : this.userDataProfileService.currentProfile.name; - const separator = this.configurationService.getValue(WindowSettingNames.titleSeparator); - const titleTemplate = this.configurationService.getValue(WindowSettingNames.title); const focusedView: string = this.viewsService.getFocusedViewName(); - - // Variables (contributed) - const contributedVariables: { [key: string]: string } = {}; + const variables: Record = {}; for (const [contextKey, name] of this.variables) { - contributedVariables[name] = this.contextKeyService.getContextKeyValue(contextKey) ?? ''; + variables[name] = this.contextKeyService.getContextKeyValue(contextKey) ?? ''; + } + + let titleTemplate = this.configurationService.getValue(WindowSettingNames.title); + if (typeof titleTemplate !== 'string') { + titleTemplate = defaultWindowTitle; + } + + let separator = this.configurationService.getValue(WindowSettingNames.titleSeparator); + if (typeof separator !== 'string') { + separator = defaultWindowTitleSeparator; } return template(titleTemplate, { + ...variables, activeEditorShort, activeEditorLong, activeEditorMedium, @@ -351,7 +372,6 @@ export class WindowTitle extends Disposable { remoteName, profileName, focusedView, - ...contributedVariables, separator: { label: separator } }); } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index f2888621120..b715436c0d4 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -6,11 +6,12 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { localize } from 'vs/nls'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform'; +import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform'; import { ConfigurationMigrationWorkbenchContribution, DynamicWorkbenchSecurityConfiguration, IConfigurationMigrationRegistry, workbenchConfigurationNodeBase, Extensions, ConfigurationKeyValuePairs, problemsConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { isStandalone } from 'vs/base/browser/browser'; import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ActivityBarPosition, EditorActionsLocation, EditorTabsMode, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; +import { defaultWindowTitle, defaultWindowTitleSeparator } from 'vs/workbench/browser/parts/titlebar/windowTitle'; const registry = Registry.as(ConfigurationExtensions.Configuration); @@ -624,23 +625,12 @@ const registry = Registry.as(ConfigurationExtensions.Con 'properties': { 'window.title': { 'type': 'string', - 'default': (() => { - if (isMacintosh && isNative) { - return '${activeEditorShort}${separator}${rootName}${separator}${profileName}'; // macOS has native dirty indicator - } - - const base = '${dirty}${activeEditorShort}${separator}${rootName}${separator}${profileName}${separator}${appName}'; - if (isWeb) { - return base + '${separator}${remoteName}'; // Web: always show remote name - } - - return base; - })(), + 'default': defaultWindowTitle, 'markdownDescription': windowTitleDescription }, 'window.titleSeparator': { 'type': 'string', - 'default': isMacintosh ? ' \u2014 ' : ' - ', + 'default': defaultWindowTitleSeparator, 'markdownDescription': localize("window.titleSeparator", "Separator used by {0}.", '`#window.title#`') }, [LayoutSettings.COMMAND_CENTER]: { From d25c50303e44695a5e37738c2330b5cebdee5e88 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 8 Feb 2024 09:02:47 +0100 Subject: [PATCH 1008/1897] Git - Add more telemetry to history provider to investigate issue (#204689) --- extensions/git/src/historyProvider.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 0063f45cad3..dbb7b8d8a36 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -34,19 +34,24 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec private disposables: Disposable[] = []; constructor(protected readonly repository: Repository, private readonly logger: LogOutputChannel) { - this.disposables.push(repository.onDidRunGitStatus(this.onDidRunGitStatus, this)); - this.disposables.push(filterEvent(repository.onDidRunOperation, e => e.operation === Operation.Refresh)(() => this._onDidChangeCurrentHistoryItemGroup.fire())); + this.disposables.push(repository.onDidRunGitStatus(() => this.onDidRunGitStatus(), this)); + this.disposables.push(filterEvent(repository.onDidRunOperation, e => e.operation === Operation.Refresh)(() => this.onDidRunGitStatus(true), this)); this.disposables.push(window.registerFileDecorationProvider(this)); } - private async onDidRunGitStatus(): Promise { + private async onDidRunGitStatus(force = false): Promise { + this.logger.trace('GitHistoryProvider:onDidRunGitStatus - HEAD:', JSON.stringify(this._HEAD)); + this.logger.trace('GitHistoryProvider:onDidRunGitStatus - repository.HEAD:', JSON.stringify(this.repository.HEAD)); + // Check if HEAD has changed - if (this._HEAD?.name === this.repository.HEAD?.name && + if (!force && + this._HEAD?.name === this.repository.HEAD?.name && this._HEAD?.commit === this.repository.HEAD?.commit && this._HEAD?.upstream?.name === this.repository.HEAD?.upstream?.name && this._HEAD?.upstream?.remote === this.repository.HEAD?.upstream?.remote && this._HEAD?.upstream?.commit === this.repository.HEAD?.upstream?.commit) { + this.logger.trace('GitHistoryProvider:onDidRunGitStatus - HEAD has not changed'); return; } @@ -55,6 +60,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec // Check if HEAD does not support incoming/outgoing (detached commit, tag) if (!this._HEAD?.name || !this._HEAD?.commit || this._HEAD.type === RefType.Tag) { this.currentHistoryItemGroup = undefined; + this.logger.trace('GitHistoryProvider:onDidRunGitStatus - HEAD does not support incoming/outgoing'); return; } @@ -67,6 +73,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec label: `${this._HEAD.upstream.remote}/${this._HEAD.upstream.name}`, } : undefined }; + + this.logger.trace('GitHistoryProvider:onDidRunGitStatus - currentHistoryItemGroup:', JSON.stringify(this.currentHistoryItemGroup)); } async provideHistoryItems(historyItemGroupId: string, options: SourceControlHistoryOptions): Promise { From 768c8e3e93aee95c4fd47711cfc1e213771d6833 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 8 Feb 2024 10:09:42 +0100 Subject: [PATCH 1009/1897] add some API todos (#204695) --- src/vscode-dts/vscode.proposed.chat.d.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vscode-dts/vscode.proposed.chat.d.ts b/src/vscode-dts/vscode.proposed.chat.d.ts index 170a995e56d..1e27e2e5ab9 100644 --- a/src/vscode-dts/vscode.proposed.chat.d.ts +++ b/src/vscode-dts/vscode.proposed.chat.d.ts @@ -9,6 +9,7 @@ declare module 'vscode' { export enum ChatMessageRole { System = 0, User = 1, + // TODO@API name: align with ChatAgent (or whatever we'll rename that to) Assistant = 2, } @@ -16,6 +17,8 @@ declare module 'vscode' { export class ChatMessage { role: ChatMessageRole; content: string; + + // TODO@API is this a leftover from Role.Function? Should message just support a catch-all signature? name?: string; constructor(role: ChatMessageRole, content: string); From 3a00525d5c24d3439c351deaaa883af33b9e9264 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 8 Feb 2024 01:24:20 -0800 Subject: [PATCH 1010/1897] remove user consent for chat (#204698) --- .../contrib/chat/browser/chat.contribution.ts | 3 +-- .../extensionFeaturesManagemetService.ts | 25 ++++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index f334bfc59fa..cc1983fb53b 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -338,8 +338,7 @@ Registry.as(ExtensionFeaturesExtensions.ExtensionFea label: nls.localize('chat', "Chat"), description: nls.localize('chatFeatureDescription', "Allows the extension to make requests to the Large Language Model (LLM)."), access: { - canToggle: true, - requireUserConsent: true, + canToggle: false, }, renderer: new SyncDescriptor(ChatFeatureMarkdowneRenderer), }); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts b/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts index 460eaafc515..fe6bee617fb 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts @@ -104,19 +104,20 @@ class ExtensionFeaturesManagementService extends Disposable implements IExtensio } if (featureState.disabled === undefined) { - const extensionDescription = this.extensionService.extensions.find(e => ExtensionIdentifier.equals(e.identifier, extension)); - const confirmationResult = await this.dialogService.confirm({ - title: localize('accessExtensionFeature', "Access '{0}' Feature", feature.label), - message: localize('accessExtensionFeatureMessage', "'{0}' extension would like to access the '{1}' feature.", extensionDescription?.displayName ?? extension.value, feature.label), - detail: justification ?? feature.description, - custom: true, - primaryButton: localize('allow', "Allow"), - cancelButton: localize('disallow', "Don't Allow"), - }); - this.setEnablement(extension, featureId, confirmationResult.confirmed); - if (!confirmationResult.confirmed) { - return false; + let enabled = true; + if (feature.access.requireUserConsent) { + const extensionDescription = this.extensionService.extensions.find(e => ExtensionIdentifier.equals(e.identifier, extension)); + const confirmationResult = await this.dialogService.confirm({ + title: localize('accessExtensionFeature', "Access '{0}' Feature", feature.label), + message: localize('accessExtensionFeatureMessage', "'{0}' extension would like to access the '{1}' feature.", extensionDescription?.displayName ?? extension.value, feature.label), + detail: justification ?? feature.description, + custom: true, + primaryButton: localize('allow', "Allow"), + cancelButton: localize('disallow', "Don't Allow"), + }); + enabled = confirmationResult.confirmed; } + this.setEnablement(extension, featureId, enabled); } featureState.accessData.current = { From cccd228808c9fee13286d7158441861d7b324457 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 8 Feb 2024 10:47:54 +0100 Subject: [PATCH 1011/1897] disable extension mangling, (#204700) workaround for https://github.com/microsoft/vscode/issues/204692 --- extensions/mangle-loader.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extensions/mangle-loader.js b/extensions/mangle-loader.js index b6b22ce3f1a..016d0f69033 100644 --- a/extensions/mangle-loader.js +++ b/extensions/mangle-loader.js @@ -41,6 +41,10 @@ module.exports = async function (source, sourceMap, meta) { // Only enable mangling in production builds return source; } + if (true) { + // disable mangling for now, SEE https://github.com/microsoft/vscode/issues/204692 + return source; + } const options = this.getOptions(); if (options.disabled) { // Dynamically disabled From 7b0e5303dd6ed7865921e0783c775ba9467dc6aa Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 8 Feb 2024 11:41:49 +0100 Subject: [PATCH 1012/1897] add proposed API check (#204705) fyi @andreamah --- src/vs/workbench/api/common/extHost.api.impl.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index f86fd099ef4..c0569540c2e 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -939,6 +939,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostWorkspace.findFiles(include, exclude, maxResults, extension.identifier, token); }, findFiles2: (filePattern, options?, token?) => { + checkProposedApiEnabled(extension, 'findFiles2'); return extHostWorkspace.findFiles2(filePattern, options, extension.identifier, token); }, findTextInFiles: (query: vscode.TextSearchQuery, optionsOrCallback: vscode.FindTextInFilesOptions | ((result: vscode.TextSearchResult) => void), callbackOrToken?: vscode.CancellationToken | ((result: vscode.TextSearchResult) => void), token?: vscode.CancellationToken) => { From 9d7e3d9cc150625c2e24def0058d7e3030b426a3 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 8 Feb 2024 11:58:32 +0100 Subject: [PATCH 1013/1897] make `live` the default mode for inline chat, remove the `livePreview` setting but not (yet) the implementation (#204706) https://github.com/microsoft/vscode/issues/204368 --- .../contrib/inlineChat/browser/inlineChatController.ts | 8 ++++---- .../workbench/contrib/inlineChat/common/inlineChat.ts | 10 +++++----- .../browser/view/cellParts/chat/cellChatController.ts | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index d22229e3528..781e4477dc9 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -347,16 +347,16 @@ export class InlineChatController implements IEditorContribution { // create a new strategy switch (session.editMode) { - case EditMode.Live: - this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._zone.value); - break; case EditMode.Preview: this._strategy = this._instaService.createInstance(PreviewStrategy, session, this._editor, this._zone.value); break; case EditMode.LivePreview: - default: this._strategy = this._instaService.createInstance(LivePreviewStrategy, session, this._editor, this._zone.value); break; + case EditMode.Live: + default: + this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._zone.value); + break; } this._session = session; diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 0794a0f8a15..4a4717a88ba 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -193,8 +193,9 @@ export const overviewRulerInlineChatDiffRemoved = registerColor('editorOverviewR export const enum EditMode { Live = 'live', + Preview = 'preview', + /** @deprecated */ LivePreview = 'livePreview', - Preview = 'preview' } Registry.as(ExtensionsMigration.ConfigurationMigration).registerConfigurationMigrations( @@ -217,13 +218,12 @@ Registry.as(Extensions.Configuration).registerConfigurat properties: { [InlineChatConfigKeys.Mode]: { description: localize('mode', "Configure if changes crafted with inline chat are applied directly to the document or are previewed first."), - default: EditMode.LivePreview, + default: EditMode.Live, type: 'string', - enum: [EditMode.LivePreview, EditMode.Preview, EditMode.Live], + enum: [EditMode.Live, EditMode.Preview], markdownEnumDescriptions: [ - localize('mode.livePreview', "Changes are applied directly to the document and are highlighted visually via inline or side-by-side diffs. Ending a session will keep the changes."), - localize('mode.preview', "Changes are previewed only and need to be accepted via the apply button. Ending a session will discard the changes."), localize('mode.live', "Changes are applied directly to the document, can be highlighted via inline diffs, and accepted/discarded by hunks. Ending a session will keep the changes."), + localize('mode.preview', "Changes are previewed only and need to be accepted via the apply button. Ending a session will discard the changes."), ], tags: ['experimental'] }, diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts index 829d4d0c4a5..0e05e8cae14 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts @@ -270,7 +270,7 @@ export class NotebookCellChatController extends Disposable { const session = await this._inlineChatSessionService.createSession( editor, - { editMode: EditMode.LivePreview }, + { editMode: EditMode.Live }, token ); From 14770d1197cc70b4ebca0885d147bcf0aaacc24b Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 5 Feb 2024 16:06:50 +0100 Subject: [PATCH 1014/1897] feat: add proposed API `newSymbolNamesProvider` that allows extensions to suggest new names for symbols that are being renamed --- src/vs/editor/common/languages.ts | 4 ++ .../common/services/languageFeatures.ts | 4 +- .../services/languageFeaturesService.ts | 3 +- src/vs/monaco.d.ts | 4 ++ .../api/browser/mainThreadLanguageFeatures.ts | 10 +++- .../workbench/api/common/extHost.api.impl.ts | 4 ++ .../workbench/api/common/extHost.protocol.ts | 2 + .../api/common/extHostLanguageFeatures.ts | 50 ++++++++++++++++++- .../common/extensionsApiProposals.ts | 1 + ...scode.proposed.newSymbolNamesProvider.d.ts | 25 ++++++++++ 10 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 43d17fb0609..1f0a242b31f 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1652,6 +1652,10 @@ export interface RenameProvider { resolveRenameLocation?(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult; } +export interface NewSymbolNamesProvider { + provideNewSymbolNames(model: model.ITextModel, range: IRange, token: CancellationToken): ProviderResult; +} + export interface Command { id: string; title: string; diff --git a/src/vs/editor/common/services/languageFeatures.ts b/src/vs/editor/common/services/languageFeatures.ts index 760e6e6e22a..92f7e271f1a 100644 --- a/src/vs/editor/common/services/languageFeatures.ts +++ b/src/vs/editor/common/services/languageFeatures.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { LanguageFeatureRegistry, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry'; -import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentPasteEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, MappedEditsProvider, MultiDocumentHighlightProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; +import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentPasteEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, MappedEditsProvider, MultiDocumentHighlightProvider, NewSymbolNamesProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export const ILanguageFeaturesService = createDecorator('ILanguageFeaturesService'); @@ -29,6 +29,8 @@ export interface ILanguageFeaturesService { readonly renameProvider: LanguageFeatureRegistry; + readonly newSymbolNamesProvider: LanguageFeatureRegistry; + readonly documentFormattingEditProvider: LanguageFeatureRegistry; readonly documentRangeFormattingEditProvider: LanguageFeatureRegistry; diff --git a/src/vs/editor/common/services/languageFeaturesService.ts b/src/vs/editor/common/services/languageFeaturesService.ts index c6504614ce3..4e414b34b36 100644 --- a/src/vs/editor/common/services/languageFeaturesService.ts +++ b/src/vs/editor/common/services/languageFeaturesService.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { LanguageFeatureRegistry, NotebookInfo, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry'; -import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DocumentPasteEditProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, MultiDocumentHighlightProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, MappedEditsProvider } from 'vs/editor/common/languages'; +import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DocumentPasteEditProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, MultiDocumentHighlightProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, MappedEditsProvider, NewSymbolNamesProvider } from 'vs/editor/common/languages'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -15,6 +15,7 @@ export class LanguageFeaturesService implements ILanguageFeaturesService { readonly referenceProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly renameProvider = new LanguageFeatureRegistry(this._score.bind(this)); + readonly newSymbolNamesProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly codeActionProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly definitionProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly typeDefinitionProvider = new LanguageFeatureRegistry(this._score.bind(this)); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index ed1b548900f..dc6e5fb74e9 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7766,6 +7766,10 @@ declare namespace monaco.languages { resolveRenameLocation?(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult; } + export interface NewSymbolNamesProvider { + provideNewSymbolNames(model: editor.ITextModel, range: IRange, token: CancellationToken): ProviderResult; + } + export interface Command { id: string; title: string; diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 497fc46663b..b9d86e64edf 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -479,7 +479,7 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread // --- rename $registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportResolveLocation: boolean): void { - this._registrations.set(handle, this._languageFeaturesService.renameProvider.register(selector, { + this._registrations.set(handle, this._languageFeaturesService.renameProvider.register(selector, { provideRenameEdits: (model: ITextModel, position: EditorPosition, newName: string, token: CancellationToken) => { return this._proxy.$provideRenameEdits(handle, model.uri, position, newName, token).then(data => reviveWorkspaceEditDto(data, this._uriIdentService)); }, @@ -489,6 +489,14 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread })); } + $registerNewSymbolNamesProvider(handle: number, selector: IDocumentFilterDto[]): void { + this._registrations.set(handle, this._languageFeaturesService.newSymbolNamesProvider.register(selector, { + provideNewSymbolNames: (model: ITextModel, range: IRange, token: CancellationToken): Promise => { + return this._proxy.$provideNewSymbolNames(handle, model.uri, range, token); + } + } satisfies languages.NewSymbolNamesProvider)); + } + // --- semantic tokens $registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: languages.SemanticTokensLegend, eventHandle: number | undefined): void { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index c0569540c2e..db680538d6b 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -582,6 +582,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerRenameProvider(selector: vscode.DocumentSelector, provider: vscode.RenameProvider): vscode.Disposable { return extHostLanguageFeatures.registerRenameProvider(extension, checkSelector(selector), provider); }, + registerNewSymbolNamesProvider(selector: vscode.DocumentSelector, provider: vscode.NewSymbolNamesProvider): vscode.Disposable { + checkProposedApiEnabled(extension, 'newSymbolNamesProvider'); + return extHostLanguageFeatures.registerNewSymbolNamesProvider(extension, checkSelector(selector), provider); + }, registerDocumentSymbolProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider, metadata?: vscode.DocumentSymbolProviderMetadata): vscode.Disposable { return extHostLanguageFeatures.registerDocumentSymbolProvider(extension, checkSelector(selector), provider, metadata); }, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 676cf530dcf..e8b75cbceba 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -416,6 +416,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; $registerNavigateTypeSupport(handle: number, supportsResolve: boolean): void; $registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportsResolveInitialValues: boolean): void; + $registerNewSymbolNamesProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: languages.SemanticTokensLegend, eventHandle: number | undefined): void; $emitDocumentSemanticTokensEvent(eventHandle: number): void; $registerDocumentRangeSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: languages.SemanticTokensLegend): void; @@ -2103,6 +2104,7 @@ export interface ExtHostLanguageFeaturesShape { $releaseWorkspaceSymbols(handle: number, id: number): void; $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise; $resolveRenameLocation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideNewSymbolNames(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise; $provideDocumentSemanticTokens(handle: number, resource: UriComponents, previousResultId: number, token: CancellationToken): Promise; $releaseDocumentSemanticTokens(handle: number, semanticColoringResultId: number): void; $provideDocumentRangeSemanticTokens(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index b0a8742c349..90bf9b20469 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -816,6 +816,44 @@ class RenameAdapter { } } +class NewSymbolNamesAdapter { + + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.NewSymbolNamesProvider, + private readonly _logService: ILogService + ) { } + + async provideNewSymbolNames(resource: URI, range: IRange, token: CancellationToken): Promise { + + const doc = this._documents.getDocument(resource); + const pos = typeConvert.Range.to(range); + + try { + const value = await this._provider.provideNewSymbolNames(doc, pos, token); + if (!value) { + return undefined; + } + return value; + + } catch (err: unknown) { + this._logService.error(NewSymbolNamesAdapter._asMessage(err) ?? JSON.stringify(err, null, '\t') /* @ulugbekna: assuming `err` doesn't have circular references that could result in an exception when converting to JSON */); + return undefined; + } + } + + // @ulugbekna: this method is also defined in RenameAdapter but seems OK to be duplicated + private static _asMessage(err: any): string | undefined { + if (typeof err === 'string') { + return err; + } else if (err instanceof Error && typeof err.message === 'string') { + return err.message; + } else { + return undefined; + } + } +} + class SemanticTokensPreviousResult { constructor( readonly resultId: string | undefined, @@ -1905,7 +1943,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter | InlineValuesAdapter | LinkedEditingRangeAdapter | InlayHintsAdapter | InlineCompletionAdapter - | DocumentOnDropEditAdapter | MappedEditsAdapter; + | DocumentOnDropEditAdapter | MappedEditsAdapter | NewSymbolNamesAdapter; class AdapterData { constructor( @@ -2287,6 +2325,16 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._withAdapter(handle, RenameAdapter, adapter => adapter.resolveRenameLocation(URI.revive(resource), position, token), undefined, token); } + registerNewSymbolNamesProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.NewSymbolNamesProvider): vscode.Disposable { + const handle = this._addNewAdapter(new NewSymbolNamesAdapter(this._documents, provider, this._logService), extension); + this._proxy.$registerNewSymbolNamesProvider(handle, this._transformDocumentSelector(selector, extension)); + return this._createDisposable(handle); + } + + $provideNewSymbolNames(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise { + return this._withAdapter(handle, NewSymbolNamesAdapter, adapter => adapter.provideNewSymbolNames(URI.revive(resource), range, token), undefined, token); + } + //#region semantic coloring registerDocumentSemanticTokensProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentSemanticTokensProvider, legend: vscode.SemanticTokensLegend): vscode.Disposable { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 15df9887466..0edc713a74c 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -70,6 +70,7 @@ export const allApiProposals = Object.freeze({ languageStatusText: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageStatusText.d.ts', mappedEditsProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts', multiDocumentHighlightProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.multiDocumentHighlightProvider.d.ts', + newSymbolNamesProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts', notebookCellExecutionState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts', notebookControllerAffinityHidden: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookControllerAffinityHidden.d.ts', notebookDeprecated: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookDeprecated.d.ts', diff --git a/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts b/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts new file mode 100644 index 00000000000..5f2c994d283 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.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. + *--------------------------------------------------------------------------------------------*/ + +// https://github.com/microsoft/vscode/issues/204345 @ulugbekna + +declare module 'vscode' { + + export interface NewSymbolNamesProvider { + /** + * Provide possible new names for the symbol at the given range. + * + * @param document The document in which the symbol is defined. + * @param range The range that spans the symbol being renamed. + * @param token A cancellation token. + * @return A list of new symbol names. + */ + provideNewSymbolNames(document: TextDocument, range: Range, token: CancellationToken): ProviderResult; + } + + export namespace languages { + export function registerNewSymbolNamesProvider(selector: DocumentSelector, provider: NewSymbolNamesProvider): Disposable; + } +} From bd1536407a815b0f0d715bb6728080f5772f6a11 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 5 Feb 2024 19:48:00 +0100 Subject: [PATCH 1015/1897] rename: show list of rename candidate names, allow tabbing through them and selecting one by pressing 'enter' --- .../editor/contrib/rename/browser/rename.ts | 9 ++- .../rename/browser/renameInputField.css | 13 ++++ .../rename/browser/renameInputField.ts | 76 +++++++++++++++++-- 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index cd88b21da7e..16229d323e9 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -208,6 +208,13 @@ class RenameController implements IEditorContribution { // part 2 - do rename at location const cts2 = new EditorStateCancellationTokenSource(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value, loc.range, this._cts.token); + const model = this.editor.getModel(); // @ulugbekna: assumes editor still has a model, otherwise, cts1 should've been cancelled + const newNameCandidates = Promise.all( + this._languageFeaturesService.newSymbolNamesProvider + .all(model) + .map(provider => provider.provideNewSymbolNames(model, loc.range, cts2.token)) // TODO@ulugbekna: make sure this works regardless if the result is then-able + ).then((candidates) => candidates.filter((c): c is string[] => !!c).flat()); + const selection = this.editor.getSelection(); let selectionStart = 0; let selectionEnd = loc.text.length; @@ -218,7 +225,7 @@ class RenameController implements IEditorContribution { } const supportPreview = this._bulkEditService.hasPreviewHandler() && this._configService.getValue(this.editor.getModel().uri, 'editor.rename.enablePreview'); - const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, cts2.token); + const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, newNameCandidates, cts2.token); // no result, only hint to focus the editor or not if (typeof inputFieldResult === 'boolean') { diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.css b/src/vs/editor/contrib/rename/browser/renameInputField.css index 1b5de07fab0..9f73a682823 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.css +++ b/src/vs/editor/contrib/rename/browser/renameInputField.css @@ -23,6 +23,19 @@ opacity: .8; } +.monaco-editor .rename-box .new-name-candidates-container { + margin-top: 4px; +} + +.monaco-editor .rename-box .new-name-candidate { + /* FIXME@ulugbekna: adapt colors to be nice */ + background-color: rgb(2, 96, 190); + color: white; + + margin: 2px; + padding: 2px; +} + .monaco-editor .rename-box.preview .rename-label { display: inherit; } diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index e00d68cd368..6e6b4b1b3e4 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as dom from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import 'vs/css!./renameInputField'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -30,6 +31,7 @@ export class RenameInputField implements IContentWidget { private _position?: Position; private _domNode?: HTMLElement; private _input?: HTMLInputElement; + private _newNameCandidates?: NewSymbolNameCandidates; private _label?: HTMLDivElement; private _visible?: boolean; private readonly _visibleContextKey: IContextKey; @@ -77,6 +79,11 @@ export class RenameInputField implements IContentWidget { this._input.setAttribute('aria-label', localize('renameAriaLabel', "Rename input. Type new name and press Enter to commit.")); this._domNode.appendChild(this._input); + // TODO@ulugbekna: add keyboard support for cycling through the candidates + this._newNameCandidates = new NewSymbolNameCandidates(); + this._domNode.appendChild(this._newNameCandidates!.domNode); + this._disposables.add(this._newNameCandidates); + this._label = document.createElement('div'); this._label.className = 'rename-label'; this._domNode.appendChild(this._label); @@ -108,7 +115,7 @@ export class RenameInputField implements IContentWidget { } private _updateFont(): void { - if (!this._input || !this._label) { + if (!this._input || !this._label || !this._newNameCandidates) { return; } @@ -117,6 +124,10 @@ export class RenameInputField implements IContentWidget { this._input.style.fontWeight = fontInfo.fontWeight; this._input.style.fontSize = `${fontInfo.fontSize}px`; + this._newNameCandidates.domNode.style.fontFamily = fontInfo.fontFamily; + this._newNameCandidates.domNode.style.fontWeight = fontInfo.fontWeight; + this._newNameCandidates.domNode.style.fontSize = `${fontInfo.fontSize}px`; + this._label.style.fontSize = `${fontInfo.fontSize * 0.8}px`; } @@ -155,7 +166,7 @@ export class RenameInputField implements IContentWidget { this._currentCancelInput?.(focusEditor); } - getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, token: CancellationToken): Promise { + getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, newNameCandidates: Promise, token: CancellationToken): Promise { this._domNode!.classList.toggle('preview', supportPreview); @@ -167,26 +178,39 @@ export class RenameInputField implements IContentWidget { const disposeOnDone = new DisposableStore(); + newNameCandidates.then(candidates => { + this._newNameCandidates!.setCandidates(candidates); + }); + return new Promise(resolve => { this._currentCancelInput = (focusEditor) => { this._currentAcceptInput = undefined; this._currentCancelInput = undefined; + this._newNameCandidates?.clearCandidates(); resolve(focusEditor); return true; }; this._currentAcceptInput = (wantsPreview) => { - if (this._input!.value.trim().length === 0 || this._input!.value === value) { + if (this._input!.value.trim().length === 0) { // empty or whitespace only or not changed this.cancelInput(true); return; } + const selectedCandidate = this._newNameCandidates?.selectedCandidate; + if ((selectedCandidate === undefined && this._input!.value === value) || selectedCandidate === value) { + this.cancelInput(true); + return; + } + this._currentAcceptInput = undefined; this._currentCancelInput = undefined; + this._newNameCandidates?.clearCandidates(); + resolve({ - newName: this._input!.value, + newName: selectedCandidate ?? this._input!.value, wantsPreview: supportPreview && wantsPreview }); }; @@ -222,3 +246,45 @@ export class RenameInputField implements IContentWidget { this._editor.layoutContentWidget(this); } } + +class NewSymbolNameCandidates implements IDisposable { + + public readonly domNode: HTMLDivElement; + + private _candidates: HTMLSpanElement[] = []; + private _disposables = new DisposableStore(); + + constructor() { + this.domNode = document.createElement('div'); + this.domNode.className = 'new-name-candidates-container'; + this.domNode.tabIndex = -1; // Make the div unfocusable + } + + get selectedCandidate(): string | undefined { + const activeDocument = dom.getActiveDocument(); + const activeElement = activeDocument.activeElement; + const index = this._candidates.indexOf(activeElement as HTMLSpanElement); + return index !== -1 ? this._candidates[index].innerText : undefined; + } + + setCandidates(candidates: string[]): void { + for (let i = 0; i < candidates.length; i++) { + const candidate = candidates[i]; + const candidateElt = document.createElement('span'); + candidateElt.className = 'new-name-candidate'; + candidateElt.innerText = candidate; + candidateElt.tabIndex = 0; + this.domNode.appendChild(candidateElt); + this._candidates.push(candidateElt); + } + } + + clearCandidates(): void { + this.domNode.innerText = ''; // TODO@ulugbekna: make sure this is the right way to clean up children + this._candidates = []; + } + + dispose(): void { + this._disposables.dispose(); + } +} From 9b2e567ee2a13ff5ccad58897343cef0aebfc1df Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Tue, 6 Feb 2024 16:53:44 +0100 Subject: [PATCH 1016/1897] rename: re-use Button class & styling --- src/vs/base/browser/ui/button/button.ts | 4 ++ .../rename/browser/renameInputField.css | 15 ++--- .../rename/browser/renameInputField.ts | 63 ++++++++++++------- 3 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index 2e845b684f6..3b1ad7915bb 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -78,6 +78,9 @@ export class Button extends Disposable implements IButton { private _onDidClick = this._register(new Emitter()); get onDidClick(): BaseEvent { return this._onDidClick.event; } + private _onDidEscape = this._register(new Emitter()); + get onDidEscape(): BaseEvent { return this._onDidEscape.event; } + private focusTracker: IFocusTracker; constructor(container: HTMLElement, options: IButtonOptions) { @@ -134,6 +137,7 @@ export class Button extends Disposable implements IButton { this._onDidClick.fire(e); eventHandled = true; } else if (event.equals(KeyCode.Escape)) { + this._onDidEscape.fire(e); this._element.blur(); eventHandled = true; } diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.css b/src/vs/editor/contrib/rename/browser/renameInputField.css index 9f73a682823..d02e7d5afca 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.css +++ b/src/vs/editor/contrib/rename/browser/renameInputField.css @@ -16,6 +16,7 @@ .monaco-editor .rename-box .rename-input { padding: 3px; border-radius: 2px; + width: calc(100% - 8px); /* 4px padding on each side */ } .monaco-editor .rename-box .rename-label { @@ -23,15 +24,15 @@ opacity: .8; } -.monaco-editor .rename-box .new-name-candidates-container { - margin-top: 4px; +.rename-box .new-name-candidates-container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin-top: 5px; } -.monaco-editor .rename-box .new-name-candidate { - /* FIXME@ulugbekna: adapt colors to be nice */ - background-color: rgb(2, 96, 190); - color: white; - +.rename-box .new-name-candidates-container > .monaco-text-button { + width: auto; margin: 2px; padding: 2px; } diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 6e6b4b1b3e4..0f8a2dc7b26 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from 'vs/base/browser/dom'; +import { Button } from 'vs/base/browser/ui/button/button'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Emitter } from 'vs/base/common/event'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { assertType } from 'vs/base/common/types'; import 'vs/css!./renameInputField'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -16,6 +18,7 @@ import { ScrollType } from 'vs/editor/common/editorCommon'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { editorWidgetBackground, inputBackground, inputBorder, inputForeground, widgetBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; @@ -79,10 +82,11 @@ export class RenameInputField implements IContentWidget { this._input.setAttribute('aria-label', localize('renameAriaLabel', "Rename input. Type new name and press Enter to commit.")); this._domNode.appendChild(this._input); - // TODO@ulugbekna: add keyboard support for cycling through the candidates + // TODO@ulugbekna: support accept/escape corresponding to the keybindings this._newNameCandidates = new NewSymbolNameCandidates(); + this._newNameCandidates.onAccept(() => this.acceptInput(false)); // FIXME@ulugbekna: need to handle preview + this._newNameCandidates.onEscape(() => this._input!.focus()); this._domNode.appendChild(this._newNameCandidates!.domNode); - this._disposables.add(this._newNameCandidates); this._label = document.createElement('div'); this._label.className = 'rename-label'; @@ -144,6 +148,10 @@ export class RenameInputField implements IContentWidget { beforeRender(): IDimension | null { const [accept, preview] = this._acceptKeybindings; this._label!.innerText = localize({ key: 'label', comment: ['placeholders are keybindings, e.g "F2 to Rename, Shift+F2 to Preview"'] }, "{0} to Rename, {1} to Preview", this._keybindingService.lookupKeybinding(accept)?.getLabel(), this._keybindingService.lookupKeybinding(preview)?.getLabel()); + // TODO@ulugbekna: elements larger than maxWidth shouldn't overflow + const maxWidth = Math.ceil(this._editor.getLayoutInfo().contentWidth / 4); + this._domNode!.style.maxWidth = `${maxWidth}px`; + this._domNode!.style.minWidth = `250px`; // to prevent from widening when candidates come in return null; } @@ -179,7 +187,9 @@ export class RenameInputField implements IContentWidget { const disposeOnDone = new DisposableStore(); newNameCandidates.then(candidates => { - this._newNameCandidates!.setCandidates(candidates); + if (!token.isCancellationRequested) { // TODO@ulugbekna: make sure this's the correct token to check + this._newNameCandidates!.setCandidates(candidates); + } }); return new Promise(resolve => { @@ -247,44 +257,49 @@ export class RenameInputField implements IContentWidget { } } -class NewSymbolNameCandidates implements IDisposable { +export class NewSymbolNameCandidates { - public readonly domNode: HTMLDivElement; + public readonly domNode: HTMLElement; - private _candidates: HTMLSpanElement[] = []; - private _disposables = new DisposableStore(); + private _onAcceptEmitter = new Emitter(); + public readonly onAccept = this._onAcceptEmitter.event; + private _onEscapeEmitter = new Emitter(); + public readonly onEscape = this._onEscapeEmitter.event; + private _candidates: Button[] = []; + + private _candidateDisposables: DisposableStore | undefined; + + // TODO@ulugbekna: pressing escape when focus is on a candidate should return the focus to the input field constructor() { this.domNode = document.createElement('div'); - this.domNode.className = 'new-name-candidates-container'; + this.domNode.className = 'rename-box new-name-candidates-container'; this.domNode.tabIndex = -1; // Make the div unfocusable } get selectedCandidate(): string | undefined { - const activeDocument = dom.getActiveDocument(); - const activeElement = activeDocument.activeElement; - const index = this._candidates.indexOf(activeElement as HTMLSpanElement); - return index !== -1 ? this._candidates[index].innerText : undefined; + const selected = this._candidates.find(c => c.hasFocus()); + return selected === undefined ? undefined : ( + assertType(typeof selected.label === 'string', 'string'), + selected.label + ); } setCandidates(candidates: string[]): void { + this._candidateDisposables = new DisposableStore(); for (let i = 0; i < candidates.length; i++) { const candidate = candidates[i]; - const candidateElt = document.createElement('span'); - candidateElt.className = 'new-name-candidate'; - candidateElt.innerText = candidate; - candidateElt.tabIndex = 0; - this.domNode.appendChild(candidateElt); + const candidateElt = new Button(this.domNode, defaultButtonStyles); + this._candidateDisposables.add(candidateElt.onDidClick(() => this._onAcceptEmitter.fire(candidate))); + this._candidateDisposables.add(candidateElt.onDidEscape(() => this._onEscapeEmitter.fire())); + candidateElt.label = candidate; this._candidates.push(candidateElt); } } clearCandidates(): void { - this.domNode.innerText = ''; // TODO@ulugbekna: make sure this is the right way to clean up children + this._candidateDisposables?.dispose(); + this.domNode.innerText = ''; this._candidates = []; } - - dispose(): void { - this._disposables.dispose(); - } } From a12d9f44fc5f89cb5db5d3e50a5869f2ba5515f0 Mon Sep 17 00:00:00 2001 From: Yifei Yang Date: Thu, 8 Feb 2024 19:43:01 +0800 Subject: [PATCH 1017/1897] Fix: GLIBCXX version detection bug in check-requirements-linux.sh (issue #204186) (#204635) * Fix: Fixed glibc version detection bug in check-requirements-linux.sh - Existing detection scripts simply use the `awk` command to record the version number in the `libstdc++.so` filename, - but in some specific versions of glibc++, the detailed version number is not indicated in the filename, - so we need to use a script to read the current version of GLIBCXX in the environment to see if it meets the expectations. Co-authored-by: chengy-sysu <939416532@qq.com> * Update check-requirements-linux.sh fix Indent * fix: Using grep and sed to replace strings command Since some Linux distributions do not come with GNU binutils pre-installed, the `strings` command does not fit on all platforms, so we use `grep` and `sed` instead of `strings`. Co-authored-by: chengy-sysu <939416532@qq.com> * fix: boundary case of check-requirements-linux.sh Co-authored-by: chengy-sysu <939416532@qq.com> * fix: Using grep and sed to replace strings command Since some Linux distributions do not come with GNU binutils pre-installed, the `strings` command does not fit on all platforms, so we use `grep` and `sed` instead of `strings`. Co-authored-by: chengy-sysu <939416532@qq.com> * fix: skip glibcxx check on alpine --------- Co-authored-by: chengy-sysu <939416532@qq.com> Co-authored-by: deepak1556 --- .../bin/helpers/check-requirements-linux.sh | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/resources/server/bin/helpers/check-requirements-linux.sh b/resources/server/bin/helpers/check-requirements-linux.sh index 9888bbe0831..be5207c2f05 100644 --- a/resources/server/bin/helpers/check-requirements-linux.sh +++ b/resources/server/bin/helpers/check-requirements-linux.sh @@ -57,11 +57,7 @@ if [ "$OS_ID" != "alpine" ]; then else libstdcpp_path=$(echo "$libstdcpp_paths" | awk '{print $NF}') fi - fi -fi - -if [ -z "$libstdcpp_path" ]; then - if [ -f /usr/lib/libstdc++.so.6 ]; then + elif [ -f /usr/lib/libstdc++.so.6 ]; then # Typical path libstdcpp_path='/usr/lib/libstdc++.so.6' elif [ -f /usr/lib64/libstdc++.so.6 ]; then @@ -70,22 +66,26 @@ if [ -z "$libstdcpp_path" ]; then else echo "Warning: Can't find libstdc++.so or ldconfig, can't verify libstdc++ version" fi -fi -while [ -n "$libstdcpp_path" ]; do - # Extracts the version number from the path, e.g. libstdc++.so.6.0.22 -> 6.0.22 - # which is then compared based on the fact that release versioning and symbol versioning - # are aligned for libstdc++. Refs https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html - # (i-e) GLIBCXX_3.4. is provided by libstdc++.so.6.y. - libstdcpp_path_line=$(echo "$libstdcpp_path" | head -n1) - libstdcpp_real_path=$(readlink -f "$libstdcpp_path_line") - libstdcpp_version=$(echo "$libstdcpp_real_path" | awk -F'\\.so\\.' '{print $NF}') - if [ "$(printf '%s\n' "6.0.25" "$libstdcpp_version" | sort -V | head -n1)" = "6.0.25" ]; then - found_required_glibcxx=1 - break - fi - libstdcpp_path=$(echo "$libstdcpp_path" | tail -n +2) # remove first line -done + while [ -n "$libstdcpp_path" ]; do + # Extracts the version number from the path, e.g. libstdc++.so.6.0.22 -> 6.0.22 + # which is then compared based on the fact that release versioning and symbol versioning + # are aligned for libstdc++. Refs https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html + # (i-e) GLIBCXX_3.4. is provided by libstdc++.so.6.y. + libstdcpp_path_line=$(echo "$libstdcpp_path" | head -n1) + libstdcpp_real_path=$(readlink -f "$libstdcpp_path_line") + libstdcpp_version=$(grep -ao 'GLIBCXX_[0-9]*\.[0-9]*\.[0-9]*' "$libstdcpp_real_path" | sort -V | tail -1) + libstdcpp_version_number=$(echo "$libstdcpp_version" | sed 's/GLIBCXX_//') + if [ "$(printf '%s\n' "3.4.24" "$libstdcpp_version_number" | sort -V | head -n1)" = "3.4.24" ]; then + found_required_glibcxx=1 + break + fi + libstdcpp_path=$(echo "$libstdcpp_path" | tail -n +2) # remove first line + done +else + echo "Warning: alpine distro detected, skipping GLIBCXX check" + found_required_glibcxx=1 +fi if [ "$found_required_glibcxx" = "0" ]; then echo "Warning: Missing GLIBCXX >= 3.4.25! from $libstdcpp_real_path" fi From e33834f54d5aff7146c36f632e9422d141f7d7d0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 8 Feb 2024 03:58:58 -0800 Subject: [PATCH 1018/1897] return if not enabled (#204714) --- .../common/extensionFeaturesManagemetService.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts b/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts index fe6bee617fb..0e60b30cd13 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts @@ -118,6 +118,9 @@ class ExtensionFeaturesManagementService extends Disposable implements IExtensio enabled = confirmationResult.confirmed; } this.setEnablement(extension, featureId, enabled); + if (!enabled) { + return false; + } } featureState.accessData.current = { From 11b38898343ad3fd53328fc3caadef942ba43d1f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 8 Feb 2024 14:33:41 +0100 Subject: [PATCH 1019/1897] Revisit need for workbench contribution that block editor restore (#203947) (#204710) --- .../contrib/accessibility/browser/accessibility.contribution.ts | 2 +- src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 4ec8d2e9b16..fb097ebecaa 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -29,5 +29,5 @@ workbenchRegistry.registerWorkbenchContribution(NotificationAccessibleViewContri workbenchRegistry.registerWorkbenchContribution(InlineCompletionsAccessibleViewContribution, LifecyclePhase.Eventually); registerWorkbenchContribution2(AccessibilityStatus.ID, AccessibilityStatus, WorkbenchPhase.BlockRestore); -registerWorkbenchContribution2(SaveAudioCueContribution.ID, SaveAudioCueContribution, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(SaveAudioCueContribution.ID, SaveAudioCueContribution, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(DynamicSpeechAccessibilityConfiguration.ID, DynamicSpeechAccessibilityConfiguration, WorkbenchPhase.AfterRestored); diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index c83ebc45f77..7cdd7d910ad 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -314,7 +314,7 @@ class EditorWordWrapContextKeyTracker extends Disposable implements IWorkbenchCo } } -registerWorkbenchContribution2(EditorWordWrapContextKeyTracker.ID, EditorWordWrapContextKeyTracker, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(EditorWordWrapContextKeyTracker.ID, EditorWordWrapContextKeyTracker, WorkbenchPhase.AfterRestored); registerEditorContribution(ToggleWordWrapController.ID, ToggleWordWrapController, EditorContributionInstantiation.Eager); // eager because it needs to change the editor word wrap configuration registerDiffEditorContribution(DiffToggleWordWrapController.ID, DiffToggleWordWrapController); From 867f35c00dc0bcd34bbbdcc8fdf6472b460fc37a Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 8 Feb 2024 17:03:56 +0100 Subject: [PATCH 1020/1897] wip --- extensions/git/src/commands.ts | 1 + .../standalone/browser/standaloneEditor.ts | 1 + src/vs/workbench/api/common/extHostSCM.ts | 1 + .../bulkEdit/browser/preview/bulkEditPane.ts | 44 ++++++++++++++++--- .../browser/preview/bulkEditPreview.ts | 1 + 5 files changed, 41 insertions(+), 7 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 3f46f698ce2..9c6c2e1991b 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3780,6 +3780,7 @@ export class CommandCenter { resources.push(toMultiFileDiffEditorUris(change, stashFirstParentCommit, modifiedUriRef)); } + // Command which is executed in order to open the multi diff editor, uring the passed in URI, the given title and the resources which are the modified resource and the original resource commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); } diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 059a4928862..10a75a44904 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -100,6 +100,7 @@ export function createDiffEditor(domElement: HTMLElement, options?: IStandaloneD return instantiationService.createInstance(StandaloneDiffEditor2, domElement, options); } +// There is also a function which creates a multi file diff editor, but maybe we do not need it export function createMultiFileDiffEditor(domElement: HTMLElement, override?: IEditorOverrideServices) { const instantiationService = StandaloneServices.initialize(override || {}); return new MultiDiffEditorWidget(domElement, {}, instantiationService); diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index ee2be0b1bcc..16c8268848b 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -416,6 +416,7 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG private _sourceControlHandle: number, private _id: string, private _label: string, + // The following appears to be adding multi diff editor support public readonly multiDiffEditorEnableViewChanges: boolean, private readonly _extension: IExtensionDescription, ) { } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 4a18e62f256..4cfaa3c07ee 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -38,6 +38,7 @@ import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; +import { ICommandService } from 'vs/platform/commands/common/commands'; const enum State { Data = 'data', @@ -79,6 +80,7 @@ export class BulkEditPane extends ViewPane { @IDialogService private readonly _dialogService: IDialogService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IStorageService private readonly _storageService: IStorageService, + @ICommandService private readonly commandService: ICommandService, @IContextKeyService contextKeyService: IContextKeyService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IKeybindingService keybindingService: IKeybindingService, @@ -223,6 +225,7 @@ export class BulkEditPane extends ViewPane { return Boolean(this._currentInput); } + // Presumably the method where we set the data to show in the tree refactors view private async _setTreeInput(input: BulkFileOperations) { const viewState = this._treeViewStates.get(this._treeDataSource.groupByFile); @@ -316,6 +319,7 @@ export class BulkEditPane extends ViewPane { } } + // In this function, we actually open the element as an editor, and this is where we could open a multi file diff editor private async _openElementAsEditor(e: IOpenEvent): Promise { const options: Mutable = { ...e.editorOptions }; @@ -333,9 +337,15 @@ export class BulkEditPane extends ViewPane { return; } + console.log('options : ', JSON.stringify(options)); + const previewUri = this._currentProvider!.asPreviewUri(fileElement.edit.uri); if (fileElement.edit.type & BulkFileOperationType.Delete) { + + console.log('fileElement.edit : ', fileElement.edit); + console.log('previewUri : ', JSON.stringify(previewUri)); + // delete -> show single editor this._editorService.openEditor({ label: localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)), @@ -343,7 +353,17 @@ export class BulkEditPane extends ViewPane { options }); + // const label = localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)); + // const uri = fileElement.edit.newUri ?? fileElement.edit.uri; + // const resources: { originalUri: Uri | undefined; modifiedUri: Uri | undefined }[] = [{ + // originalUri: fileElement.edit.uri, + // modifiedUri: fileElement.edit.newUri + // }]; + // await commands.executeCommand('_workbench.openMultiDiffEditor', { uri, label, resources }); + } else { + console.log('fileElement.edit ; ', fileElement.edit); + // rename, create, edits -> show diff editr let leftResource: URI | undefined; try { @@ -367,13 +387,23 @@ export class BulkEditPane extends ViewPane { label = localize('edt.title.1', "{0} (refactor preview)", basename(fileElement.edit.uri)); } - this._editorService.openEditor({ - original: { resource: leftResource }, - modified: { resource: previewUri }, - label, - description: this._labelService.getUriLabel(dirname(leftResource), { relative: true }), - options - }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + console.log('leftResource : ', JSON.stringify(leftResource)); + console.log('previewUri : ', JSON.stringify(previewUri)); + + // this._editorService.openEditor({ + // original: { resource: leftResource }, + // modified: { resource: previewUri }, + // label, + // description: this._labelService.getUriLabel(dirname(leftResource), { relative: true }), + // options + // }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + + const uri = previewUri; + const resources: { originalUri: URI | undefined; modifiedUri: URI | undefined }[] = [{ + originalUri: leftResource, + modifiedUri: previewUri + }]; + this.commandService.executeCommand('_workbench.openMultiDiffEditor', { uri, label, resources }); } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts index d9e8a1112a0..c649d96c78c 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts @@ -307,6 +307,7 @@ export class BulkFileOperations { return result; } + // Getting the edits for a specific file getFileEdits(uri: URI): ISingleEditOperation[] { for (const file of this.fileOperations) { From 366ff2c156ffc3627a7422cc72530589ba213929 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 8 Feb 2024 17:10:19 +0100 Subject: [PATCH 1021/1897] SCM - extract title context keys into its own workbench contribution (#204736) --- .../workbench/contrib/scm/browser/activity.ts | 118 ++++++++++++------ .../contrib/scm/browser/scm.contribution.ts | 5 +- 2 files changed, 82 insertions(+), 41 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index 402df518c99..efd752e0f53 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -28,18 +28,10 @@ function getCount(repository: ISCMRepository): number { } } -const ContextKeys = { - ActiveRepositoryName: new RawContextKey('scmActiveRepositoryName', ''), - ActiveRepositoryBranchName: new RawContextKey('scmActiveRepositoryBranchName', ''), -}; - export class SCMStatusController implements IWorkbenchContribution { - private activeRepositoryNameContextKey: IContextKey; - private activeRepositoryBranchNameContextKey: IContextKey; - private statusBarDisposable: IDisposable = Disposable.None; - private focusDisposables = new DisposableStore(); + private focusDisposable: IDisposable = Disposable.None; private focusedRepository: ISCMRepository | undefined = undefined; private readonly badgeDisposable = new MutableDisposable(); private readonly disposables = new DisposableStore(); @@ -52,9 +44,7 @@ export class SCMStatusController implements IWorkbenchContribution { @IActivityService private readonly activityService: IActivityService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - @IContextKeyService contextKeyService: IContextKeyService, - @ITitleService titleService: ITitleService + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService ) { this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); @@ -66,14 +56,6 @@ export class SCMStatusController implements IWorkbenchContribution { this.onDidAddRepository(repository); } - this.activeRepositoryNameContextKey = ContextKeys.ActiveRepositoryName.bindTo(contextKeyService); - this.activeRepositoryBranchNameContextKey = ContextKeys.ActiveRepositoryBranchName.bindTo(contextKeyService); - - titleService.registerVariables([ - { name: 'activeRepositoryName', contextKey: ContextKeys.ActiveRepositoryName.key }, - { name: 'activeRepositoryBranchName', contextKey: ContextKeys.ActiveRepositoryBranchName.key, } - ]); - this.scmViewService.onDidFocusRepository(this.focusRepository, this, this.disposables); this.focusRepository(this.scmViewService.focusedRepository); @@ -144,33 +126,17 @@ export class SCMStatusController implements IWorkbenchContribution { return; } - this.focusDisposables.clear(); + this.focusDisposable.dispose(); this.focusedRepository = repository; - if (repository) { - if (repository.provider.onDidChangeStatusBarCommands) { - this.focusDisposables.add(repository.provider.onDidChangeStatusBarCommands(() => this.renderStatusBar(repository))); - } - - this.focusDisposables.add(repository.provider.onDidChangeHistoryProvider(() => { - if (repository.provider.historyProvider) { - this.focusDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => this.updateContextKeys(repository))); - } - - this.updateContextKeys(repository); - })); + if (repository && repository.provider.onDidChangeStatusBarCommands) { + this.focusDisposable = repository.provider.onDidChangeStatusBarCommands(() => this.renderStatusBar(repository)); } - this.updateContextKeys(repository); this.renderStatusBar(repository); this.renderActivityCount(); } - private updateContextKeys(repository: ISCMRepository | undefined): void { - this.activeRepositoryNameContextKey.set(repository?.provider.name ?? ''); - this.activeRepositoryBranchNameContextKey.set(repository?.provider.historyProvider?.currentHistoryItemGroup?.label ?? ''); - } - private renderStatusBar(repository: ISCMRepository | undefined): void { this.statusBarDisposable.dispose(); @@ -239,7 +205,7 @@ export class SCMStatusController implements IWorkbenchContribution { } dispose(): void { - this.focusDisposables.dispose(); + this.focusDisposable.dispose(); this.statusBarDisposable.dispose(); this.badgeDisposable.dispose(); this.disposables.dispose(); @@ -248,6 +214,78 @@ export class SCMStatusController implements IWorkbenchContribution { } } +const ActiveRepositoryContextKeys = { + ActiveRepositoryName: new RawContextKey('scmActiveRepositoryName', ''), + ActiveRepositoryBranchName: new RawContextKey('scmActiveRepositoryBranchName', ''), +}; + +export class SCMActiveRepositoryContextKeyController implements IWorkbenchContribution { + + private activeRepositoryNameContextKey: IContextKey; + private activeRepositoryBranchNameContextKey: IContextKey; + + private readonly disposables = new DisposableStore(); + private readonly focusedRepositoryDisposables = new DisposableStore(); + + constructor( + @IContextKeyService contextKeyService: IContextKeyService, + @IEditorService private readonly editorService: IEditorService, + @ISCMViewService private readonly scmViewService: ISCMViewService, + @ITitleService titleService: ITitleService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService + ) { + this.activeRepositoryNameContextKey = ActiveRepositoryContextKeys.ActiveRepositoryName.bindTo(contextKeyService); + this.activeRepositoryBranchNameContextKey = ActiveRepositoryContextKeys.ActiveRepositoryBranchName.bindTo(contextKeyService); + + titleService.registerVariables([ + { name: 'activeRepositoryName', contextKey: ActiveRepositoryContextKeys.ActiveRepositoryName.key }, + { name: 'activeRepositoryBranchName', contextKey: ActiveRepositoryContextKeys.ActiveRepositoryBranchName.key, } + ]); + + editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.disposables); + scmViewService.onDidFocusRepository(this.onDidFocusRepository, this, this.disposables); + this.onDidFocusRepository(scmViewService.focusedRepository); + } + + private onDidActiveEditorChange(): void { + const activeResource = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor); + + if (activeResource?.scheme !== Schemas.file && activeResource?.scheme !== Schemas.vscodeRemote) { + return; + } + + const activeResourceRepository = Iterable.find( + this.scmViewService.visibleRepositories, + r => Boolean(r.provider.rootUri && this.uriIdentityService.extUri.isEqualOrParent(activeResource, r.provider.rootUri)) + ); + + this.onDidFocusRepository(activeResourceRepository); + } + + private onDidFocusRepository(repository: ISCMRepository | undefined): void { + this.focusedRepositoryDisposables.clear(); + + if (!repository) { + return; + } + + this.focusedRepositoryDisposables.add(repository.provider.onDidChangeHistoryProvider(() => { + if (repository.provider.historyProvider) { + this.focusedRepositoryDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => this.updateContextKeys(repository))); + } + + this.updateContextKeys(repository); + })); + + this.updateContextKeys(repository); + } + + private updateContextKeys(repository: ISCMRepository | undefined): void { + this.activeRepositoryNameContextKey.set(repository?.provider.name ?? ''); + this.activeRepositoryBranchNameContextKey.set(repository?.provider.historyProvider?.currentHistoryItemGroup?.label ?? ''); + } +} + export class SCMActiveResourceContextKeyController implements IWorkbenchContribution { private activeResourceHasChangesContextKey: IContextKey; diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 8fae0d843b8..9cd68687897 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -10,7 +10,7 @@ import { DirtyDiffWorkbenchController } from './dirtydiffDecorator'; import { VIEWLET_ID, ISCMService, VIEW_PANE_ID, ISCMProvider, ISCMViewService, REPOSITORIES_VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { SCMActiveResourceContextKeyController, SCMStatusController } from './activity'; +import { SCMActiveRepositoryContextKeyController, SCMActiveResourceContextKeyController, SCMStatusController } from './activity'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -113,6 +113,9 @@ viewsRegistry.registerViews([{ Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(SCMActiveResourceContextKeyController, LifecyclePhase.Restored); +Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(SCMActiveRepositoryContextKeyController, LifecyclePhase.Restored); + Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(SCMStatusController, LifecyclePhase.Restored); From 3fa90909560116c71afd8b8eeda3947c85d67e5e Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 8 Feb 2024 17:23:47 +0100 Subject: [PATCH 1022/1897] adding support so that the multi file diff editor shows for all of the bulk operations at the same time --- src/vs/platform/list/browser/listService.ts | 1 + .../bulkEdit/browser/preview/bulkEditPane.ts | 128 ++++++++++++------ 2 files changed, 87 insertions(+), 42 deletions(-) diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 6857531cb66..317af0fdf3a 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -770,6 +770,7 @@ abstract class ResourceNavigator extends Disposable { this._open(element, preserveFocus, pinned, sideBySide, browserEvent); } + // We want to actually retrieve all of the elements, not just the element at hand private _open(element: T | undefined, preserveFocus: boolean, pinned: boolean, sideBySide: boolean, browserEvent?: UIEvent): void { if (!element) { return; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 4cfaa3c07ee..437721a69fc 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -145,6 +145,7 @@ export class BulkEditPane extends ViewPane { ); this._disposables.add(this._tree.onContextMenu(this._onContextMenu, this)); + // Thing is when the tree is clicked, we want actually to show all of the files inside of the multi diff editor this._disposables.add(this._tree.onDidOpen(e => this._openElementAsEditor(e))); // buttons @@ -339,56 +340,53 @@ export class BulkEditPane extends ViewPane { console.log('options : ', JSON.stringify(options)); - const previewUri = this._currentProvider!.asPreviewUri(fileElement.edit.uri); - if (fileElement.edit.type & BulkFileOperationType.Delete) { + const previewUri = this._currentProvider!.asPreviewUri(fileElement.edit.uri); console.log('fileElement.edit : ', fileElement.edit); console.log('previewUri : ', JSON.stringify(previewUri)); // delete -> show single editor - this._editorService.openEditor({ - label: localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)), - resource: previewUri, - options - }); + // this._editorService.openEditor({ + // label: localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)), + // resource: previewUri, + // options + // }); - // const label = localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)); - // const uri = fileElement.edit.newUri ?? fileElement.edit.uri; - // const resources: { originalUri: Uri | undefined; modifiedUri: Uri | undefined }[] = [{ - // originalUri: fileElement.edit.uri, - // modifiedUri: fileElement.edit.newUri - // }]; - // await commands.executeCommand('_workbench.openMultiDiffEditor', { uri, label, resources }); + const uri = previewUri; + + const label = localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)); + const resources: { originalUri: URI | undefined; modifiedUri: URI | undefined }[] = [{ + originalUri: undefined, + modifiedUri: previewUri + }]; + this.commandService.executeCommand('_workbench.openMultiDiffEditor', { uri, label, resources }); } else { console.log('fileElement.edit ; ', fileElement.edit); // rename, create, edits -> show diff editr - let leftResource: URI | undefined; - try { - (await this._textModelService.createModelReference(fileElement.edit.uri)).dispose(); - leftResource = fileElement.edit.uri; - } catch { - leftResource = BulkEditPreviewProvider.emptyPreview; - } + // let leftResource: URI | undefined; + // try { + // (await this._textModelService.createModelReference(fileElement.edit.uri)).dispose(); + // leftResource = fileElement.edit.uri; + // } catch { + // leftResource = BulkEditPreviewProvider.emptyPreview; + // } - let typeLabel: string | undefined; - if (fileElement.edit.type & BulkFileOperationType.Rename) { - typeLabel = localize('rename', "rename"); - } else if (fileElement.edit.type & BulkFileOperationType.Create) { - typeLabel = localize('create', "create"); - } + // let typeLabel: string | undefined; + // if (fileElement.edit.type & BulkFileOperationType.Rename) { + // typeLabel = localize('rename', "rename"); + // } else if (fileElement.edit.type & BulkFileOperationType.Create) { + // typeLabel = localize('create', "create"); + // } - let label: string; - if (typeLabel) { - label = localize('edt.title.2', "{0} ({1}, refactor preview)", basename(fileElement.edit.uri), typeLabel); - } else { - label = localize('edt.title.1', "{0} (refactor preview)", basename(fileElement.edit.uri)); - } - - console.log('leftResource : ', JSON.stringify(leftResource)); - console.log('previewUri : ', JSON.stringify(previewUri)); + // let label: string; + // if (typeLabel) { + // label = localize('edt.title.2', "{0} ({1}, refactor preview)", basename(fileElement.edit.uri), typeLabel); + // } else { + // label = localize('edt.title.1', "{0} (refactor preview)", basename(fileElement.edit.uri)); + // } // this._editorService.openEditor({ // original: { resource: leftResource }, @@ -398,12 +396,58 @@ export class BulkEditPane extends ViewPane { // options // }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); - const uri = previewUri; - const resources: { originalUri: URI | undefined; modifiedUri: URI | undefined }[] = [{ - originalUri: leftResource, - modifiedUri: previewUri - }]; - this.commandService.executeCommand('_workbench.openMultiDiffEditor', { uri, label, resources }); + // Perhaps a better way to access the parent instead of accessing through the parent of the file element + + let bulkFileOperations: BulkFileOperations | undefined = undefined; + let currentParent = fileElement.parent; + while (true) { + if (currentParent instanceof BulkFileOperations) { + bulkFileOperations = currentParent; + break; + } + currentParent = currentParent.parent; + } + const allBulkFileOperations = bulkFileOperations.fileOperations; + console.log('allBulkFileOperations : ', allBulkFileOperations); + + const resources: { originalUri: URI | undefined; modifiedUri: URI | undefined }[] = []; + + for (const operation of allBulkFileOperations) { + const previewUri = this._currentProvider!.asPreviewUri(operation.textEdits[0].textEdit.resource); + + let leftResource: URI | undefined; + try { + (await this._textModelService.createModelReference(fileElement.edit.uri)).dispose(); + leftResource = fileElement.edit.uri; + } catch { + leftResource = BulkEditPreviewProvider.emptyPreview; + } + + let typeLabel: string | undefined; + if (fileElement.edit.type & BulkFileOperationType.Rename) { + typeLabel = localize('rename', "rename"); + } else if (fileElement.edit.type & BulkFileOperationType.Create) { + typeLabel = localize('create', "create"); + } + + let label: string; + if (typeLabel) { + label = localize('edt.title.2', "{0} ({1}, refactor preview)", basename(fileElement.edit.uri), typeLabel); + } else { + label = localize('edt.title.1', "{0} (refactor preview)", basename(fileElement.edit.uri)); + } + + console.log('leftResource : ', JSON.stringify(leftResource)); + console.log('previewUri : ', JSON.stringify(previewUri)); + + resources.push({ + originalUri: leftResource, + modifiedUri: previewUri + }); + } + + const refactorSourceUri = URI.from({ scheme: 'refactor-preview' }); + this.commandService.executeCommand('_workbench.openMultiDiffEditor', { refactorSourceUri, label: 'Refactor Preview', resources }); } } From d40fd3baa2b280c3efc9432ebf07c57fb4aa1a84 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 8 Feb 2024 17:32:06 +0100 Subject: [PATCH 1023/1897] adding notes --- .../bulkEdit/browser/preview/bulkEditPane.ts | 44 ++----------------- 1 file changed, 4 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 437721a69fc..22966278726 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -346,13 +346,6 @@ export class BulkEditPane extends ViewPane { console.log('fileElement.edit : ', fileElement.edit); console.log('previewUri : ', JSON.stringify(previewUri)); - // delete -> show single editor - // this._editorService.openEditor({ - // label: localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)), - // resource: previewUri, - // options - // }); - const uri = previewUri; const label = localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)); @@ -365,39 +358,6 @@ export class BulkEditPane extends ViewPane { } else { console.log('fileElement.edit ; ', fileElement.edit); - // rename, create, edits -> show diff editr - // let leftResource: URI | undefined; - // try { - // (await this._textModelService.createModelReference(fileElement.edit.uri)).dispose(); - // leftResource = fileElement.edit.uri; - // } catch { - // leftResource = BulkEditPreviewProvider.emptyPreview; - // } - - // let typeLabel: string | undefined; - // if (fileElement.edit.type & BulkFileOperationType.Rename) { - // typeLabel = localize('rename', "rename"); - // } else if (fileElement.edit.type & BulkFileOperationType.Create) { - // typeLabel = localize('create', "create"); - // } - - // let label: string; - // if (typeLabel) { - // label = localize('edt.title.2', "{0} ({1}, refactor preview)", basename(fileElement.edit.uri), typeLabel); - // } else { - // label = localize('edt.title.1', "{0} (refactor preview)", basename(fileElement.edit.uri)); - // } - - // this._editorService.openEditor({ - // original: { resource: leftResource }, - // modified: { resource: previewUri }, - // label, - // description: this._labelService.getUriLabel(dirname(leftResource), { relative: true }), - // options - // }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); - - // Perhaps a better way to access the parent instead of accessing through the parent of the file element - let bulkFileOperations: BulkFileOperations | undefined = undefined; let currentParent = fileElement.parent; while (true) { @@ -446,6 +406,10 @@ export class BulkEditPane extends ViewPane { }); } + // Issues with current implementation + // 1. Each time, this creates a new multi diff editor, we want it to reshow the same multi diff editor if there is one + // 2. The file naming does not look correct in the multi diff editor, there is a bug somewhere + // 3. Currently I am accessing the parent of the file element and showing all of the files, but we want to jump to the correct location when clicking on the multi diff editor const refactorSourceUri = URI.from({ scheme: 'refactor-preview' }); this.commandService.executeCommand('_workbench.openMultiDiffEditor', { refactorSourceUri, label: 'Refactor Preview', resources }); } From fa3500ffc7ff75207a9bdd2aa5ab2429f9065664 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 8 Feb 2024 08:43:41 -0800 Subject: [PATCH 1024/1897] Remove used notebook profile exp (#204739) --- .../contrib/profile/notebookProfile.ts | 69 +++++++++---------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts b/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts index ae330d22aac..ec03486e587 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts @@ -3,14 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; -import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; export enum NotebookProfileType { default = 'default', @@ -89,40 +86,40 @@ function isSetProfileArgs(args: unknown): args is ISetProfileArgs { setProfileArgs.profile === NotebookProfileType.jupyter; } -export class NotebookProfileContribution extends Disposable { +// export class NotebookProfileContribution extends Disposable { - static readonly ID = 'workbench.contrib.notebookProfile'; +// static readonly ID = 'workbench.contrib.notebookProfile'; - constructor(@IConfigurationService configService: IConfigurationService, @IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService) { - super(); +// constructor(@IConfigurationService configService: IConfigurationService, @IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService) { +// super(); - if (this.experimentService) { - this.experimentService.getTreatment('notebookprofile').then(treatment => { - if (treatment === undefined) { - return; - } else { - // check if settings are already modified - const focusIndicator = configService.getValue(NotebookSetting.focusIndicator); - const insertToolbarPosition = configService.getValue(NotebookSetting.insertToolbarLocation); - const globalToolbar = configService.getValue(NotebookSetting.globalToolbar); - // const cellToolbarLocation = configService.getValue(NotebookSetting.cellToolbarLocation); - const compactView = configService.getValue(NotebookSetting.compactView); - const showCellStatusBar = configService.getValue(NotebookSetting.showCellStatusBar); - const consolidatedRunButton = configService.getValue(NotebookSetting.consolidatedRunButton); - if (focusIndicator === 'border' - && insertToolbarPosition === 'both' - && globalToolbar === false - // && cellToolbarLocation === undefined - && compactView === true - && showCellStatusBar === 'visible' - && consolidatedRunButton === true - ) { - applyProfile(configService, profiles[treatment] ?? profiles[NotebookProfileType.default]); - } - } - }); - } - } -} +// if (this.experimentService) { +// this.experimentService.getTreatment('notebookprofile').then(treatment => { +// if (treatment === undefined) { +// return; +// } else { +// // check if settings are already modified +// const focusIndicator = configService.getValue(NotebookSetting.focusIndicator); +// const insertToolbarPosition = configService.getValue(NotebookSetting.insertToolbarLocation); +// const globalToolbar = configService.getValue(NotebookSetting.globalToolbar); +// // const cellToolbarLocation = configService.getValue(NotebookSetting.cellToolbarLocation); +// const compactView = configService.getValue(NotebookSetting.compactView); +// const showCellStatusBar = configService.getValue(NotebookSetting.showCellStatusBar); +// const consolidatedRunButton = configService.getValue(NotebookSetting.consolidatedRunButton); +// if (focusIndicator === 'border' +// && insertToolbarPosition === 'both' +// && globalToolbar === false +// // && cellToolbarLocation === undefined +// && compactView === true +// && showCellStatusBar === 'visible' +// && consolidatedRunButton === true +// ) { +// applyProfile(configService, profiles[treatment] ?? profiles[NotebookProfileType.default]); +// } +// } +// }); +// } +// } +// } -registerWorkbenchContribution2(NotebookProfileContribution.ID, NotebookProfileContribution, WorkbenchPhase.BlockRestore); +// registerWorkbenchContribution2(NotebookProfileContribution.ID, NotebookProfileContribution, WorkbenchPhase.BlockRestore); From 44653961b25376dc581990ee1a65a6d72b8c17c7 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 8 Feb 2024 18:12:06 +0100 Subject: [PATCH 1025/1897] Adding preview of multi-cursor inline completions (#204146) * Adding preview of multi-cursor inline completions --------- Co-authored-by: Henning Dieterichs --- .../inlineCompletions/browser/ghostText.ts | 5 + .../browser/hoverParticipant.ts | 2 +- .../browser/inlineCompletionContextKeys.ts | 6 +- .../browser/inlineCompletionsController.ts | 67 +++++++--- .../browser/inlineCompletionsHintsWidget.ts | 2 +- .../browser/inlineCompletionsModel.ts | 119 ++++++++++-------- .../browser/singleTextEdit.ts | 5 + .../inlineCompletions/test/browser/utils.ts | 2 +- .../browser/accessibilityContributions.ts | 4 +- 9 files changed, 135 insertions(+), 77 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts b/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts index 68f913238a7..42d0404984f 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { equals } from 'vs/base/common/arrays'; import { splitLines } from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; import { ColumnRange, applyEdits } from 'vs/editor/contrib/inlineCompletions/browser/utils'; @@ -135,6 +136,10 @@ export class GhostTextReplacement { export type GhostTextOrReplacement = GhostText | GhostTextReplacement; +export function ghostTextsOrReplacementsEqual(a: readonly GhostTextOrReplacement[] | undefined, b: readonly GhostTextOrReplacement[] | undefined): boolean { + return equals(a, b, ghostTextOrReplacementEquals); +} + export function ghostTextOrReplacementEquals(a: GhostTextOrReplacement | undefined, b: GhostTextOrReplacement | undefined): boolean { if (a === b) { return true; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts b/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts index 373eece44c2..96b27ee7bf5 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts @@ -142,7 +142,7 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan disposableStore.add(autorun(reader => { /** @description update hover */ - const ghostText = part.controller.model.read(reader)?.ghostText.read(reader); + const ghostText = part.controller.model.read(reader)?.primaryGhostText.read(reader); if (ghostText) { const lineText = this._editor.getModel()!.getLineContent(ghostText.lineNumber); render(ghostText.renderForScreenReader(lineText)); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys.ts index 1e9a7fbbbd0..fcbcd62120f 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys.ts @@ -33,10 +33,10 @@ export class InlineCompletionContextKeys extends Disposable { const model = this.model.read(reader); const state = model?.state.read(reader); - const isInlineCompletionVisible = !!state?.inlineCompletion && state?.ghostText !== undefined && !state?.ghostText.isEmpty(); + const isInlineCompletionVisible = !!state?.inlineCompletion && state?.primaryGhostText !== undefined && !state?.primaryGhostText.isEmpty(); this.inlineCompletionVisible.set(isInlineCompletionVisible); - if (state?.ghostText && state?.inlineCompletion) { + if (state?.primaryGhostText && state?.inlineCompletion) { this.suppressSuggestions.set(state.inlineCompletion.inlineCompletion.source.inlineCompletions.suppressSuggestions); } })); @@ -48,7 +48,7 @@ export class InlineCompletionContextKeys extends Disposable { let startsWithIndentation = false; let startsWithIndentationLessThanTabSize = true; - const ghostText = model?.ghostText.read(reader); + const ghostText = model?.primaryGhostText.read(reader); if (!!model?.selectedSuggestItem && ghostText && ghostText.parts.length > 0) { const { column, lines } = ghostText.parts[0]; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts index 4f38487a6ff..17f6edfee9a 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts @@ -5,8 +5,8 @@ import { createStyleSheet2 } from 'vs/base/browser/dom'; import { alert } from 'vs/base/browser/ui/aria/aria'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ITransaction, autorun, autorunHandleChanges, constObservable, derived, disposableObservableValue, observableFromEvent, observableSignal, observableValue, transaction } from 'vs/base/common/observable'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { IObservable, ITransaction, autorun, autorunHandleChanges, constObservable, derived, disposableObservableValue, observableFromEvent, observableSignal, observableValue, transaction } from 'vs/base/common/observable'; import { CoreEditingCommands } from 'vs/editor/browser/coreCommands'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -29,6 +29,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { mapObservableArrayCached } from 'vs/base/common/observableInternal/utils'; +import { ISettableObservable } from 'vs/base/common/observableInternal/base'; export class InlineCompletionsController extends Disposable { static ID = 'editor.contrib.inlineCompletionsController'; @@ -37,9 +39,9 @@ export class InlineCompletionsController extends Disposable { return editor.getContribution(InlineCompletionsController.ID); } - public readonly model = disposableObservableValue('inlineCompletionModel', undefined); + public readonly model = this._register(disposableObservableValue('inlineCompletionModel', undefined)); private readonly _textModelVersionId = observableValue(this, -1); - private readonly _cursorPosition = observableValue(this, new Position(1, 1)); + private readonly _positions = observableValue(this, [new Position(1, 1)]); private readonly _suggestWidgetAdaptor = this._register(new SuggestWidgetAdaptor( this.editor, () => this.model.get()?.selectedInlineCompletion.get()?.toSingleTextEdit(undefined), @@ -55,11 +57,20 @@ export class InlineCompletionsController extends Disposable { private readonly _enabled = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).enabled); private readonly _fontFamily = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).fontFamily); - private _ghostTextWidget = this._register(this._instantiationService.createInstance(GhostTextWidget, this.editor, { - ghostText: this.model.map((v, reader) => /** ghostText */ v?.ghostText.read(reader)), - minReservedLineCount: constObservable(0), - targetTextModel: this.model.map(v => v?.textModel), - })); + private readonly _ghostTexts = derived(this, (reader) => { + const model = this.model.read(reader); + return model?.ghostTexts.read(reader) ?? []; + }); + + private readonly _stablizedGhostTexts = convertItemsToStableObservables(this._ghostTexts, this._store); + + private readonly _ghostTextWidgets = mapObservableArrayCached(this, this._stablizedGhostTexts, (ghostText, store) => { + return store.add(this._instantiationService.createInstance(GhostTextWidget, this.editor, { + ghostText: ghostText, + minReservedLineCount: constObservable(0), + targetTextModel: this.model.map(v => v?.textModel), + })); + }).recomputeInitiallyAndOnChange(this._store); private readonly _debounceValue = this._debounceService.for( this._languageFeaturesService.inlineCompletionsProvider, @@ -101,8 +112,8 @@ export class InlineCompletionsController extends Disposable { InlineCompletionsModel, textModel, this._suggestWidgetAdaptor.selectedItem, - this._cursorPosition, this._textModelVersionId, + this._positions, this._debounceValue, observableFromEvent(editor.onDidChangeConfiguration, () => editor.getOption(EditorOption.suggest).preview), observableFromEvent(editor.onDidChangeConfiguration, () => editor.getOption(EditorOption.suggest).previewMode), @@ -188,7 +199,7 @@ export class InlineCompletionsController extends Disposable { /** @description InlineCompletionsController.forceRenderingAbove */ const state = this.model.read(reader)?.state.read(reader); if (state?.suggestItem) { - if (state.ghostText.lineCount >= 2) { + if (state.primaryGhostText.lineCount >= 2) { this._suggestWidgetAdaptor.forceRenderingAbove(); } } else { @@ -220,10 +231,10 @@ export class InlineCompletionsController extends Disposable { if (state.inlineCompletion.semanticId !== lastInlineCompletionId) { lastInlineCompletionId = state.inlineCompletion.semanticId; - const lineText = model.textModel.getLineContent(state.ghostText.lineNumber); + const lineText = model.textModel.getLineContent(state.primaryGhostText.lineNumber); this._audioCueService.playAudioCue(AudioCue.inlineSuggestion).then(() => { if (this.editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) { - this.provideScreenReaderUpdate(state.ghostText.renderForScreenReader(lineText)); + this.provideScreenReaderUpdate(state.primaryGhostText.renderForScreenReader(lineText)); } }); } @@ -260,11 +271,11 @@ export class InlineCompletionsController extends Disposable { private updateObservables(tx: ITransaction, changeReason: VersionIdChangeReason): void { const newModel = this.editor.getModel(); this._textModelVersionId.set(newModel?.getVersionId() ?? -1, tx, changeReason); - this._cursorPosition.set(this.editor.getPosition() ?? new Position(1, 1), tx); + this._positions.set(this.editor.getSelections()?.map(selection => selection.getPosition()) ?? [new Position(1, 1)], tx); } public shouldShowHoverAt(range: Range) { - const ghostText = this.model.get()?.ghostText.get(); + const ghostText = this.model.get()?.primaryGhostText.get(); if (ghostText) { return ghostText.parts.some(p => range.containsPosition(new Position(ghostText.lineNumber, p.column))); } @@ -272,7 +283,7 @@ export class InlineCompletionsController extends Disposable { } public shouldShowHoverAtViewZone(viewZoneId: string): boolean { - return this._ghostTextWidget.ownsViewZone(viewZoneId); + return this._ghostTextWidgets.get()[0]?.ownsViewZone(viewZoneId) ?? false; } public hide() { @@ -281,3 +292,27 @@ export class InlineCompletionsController extends Disposable { }); } } + +function convertItemsToStableObservables(items: IObservable, store: DisposableStore): IObservable[]> { + const result = observableValue[]>('result', []); + const innerObservables: ISettableObservable[] = []; + + store.add(autorun(reader => { + const itemsValue = items.read(reader); + + transaction(tx => { + if (itemsValue.length !== innerObservables.length) { + innerObservables.length = itemsValue.length; + for (let i = 0; i < innerObservables.length; i++) { + if (!innerObservables[i]) { + innerObservables[i] = observableValue('item', itemsValue[i]); + } + } + result.set([...innerObservables], tx); + } + innerObservables.forEach((o, i) => o.set(itemsValue[i], tx)); + }); + })); + + return result; +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget.ts index 5229bc11df2..30f622e01fc 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget.ts @@ -40,7 +40,7 @@ export class InlineCompletionsHintsWidget extends Disposable { private sessionPosition: Position | undefined = undefined; private readonly position = derived(this, reader => { - const ghostText = this.model.read(reader)?.ghostText.read(reader); + const ghostText = this.model.read(reader)?.primaryGhostText.read(reader); if (!this.alwaysShowToolbar.read(reader) || !ghostText || ghostText.parts.length === 0) { this.sessionPosition = undefined; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index c031bdf51fc..dcd0dec6cac 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -18,7 +18,7 @@ import { InlineCompletionContext, InlineCompletionTriggerKind } from 'vs/editor/ import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { IFeatureDebounceInformation } from 'vs/editor/common/services/languageFeatureDebounce'; -import { GhostText, GhostTextOrReplacement, ghostTextOrReplacementEquals } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; +import { GhostText, GhostTextOrReplacement, ghostTextOrReplacementEquals, ghostTextsOrReplacementsEqual } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; import { InlineCompletionWithUpdatedRange, InlineCompletionsSource } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource'; import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; import { SuggestItemInfo } from 'vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider'; @@ -41,6 +41,7 @@ export class InlineCompletionsModel extends Disposable { // We use a semantic id to keep the same inline completion selected even if the provider reorders the completions. private readonly _selectedInlineCompletionId = observableValue(this, undefined); + private readonly _primaryPosition = derived(this, reader => this._positions.read(reader)[0] ?? new Position(1, 1)); private _isAcceptingPartially = false; public get isAcceptingPartially() { return this._isAcceptingPartially; } @@ -48,8 +49,8 @@ export class InlineCompletionsModel extends Disposable { constructor( public readonly textModel: ITextModel, public readonly selectedSuggestItem: IObservable, - public readonly cursorPosition: IObservable, public readonly textModelVersionId: IObservable, + private readonly _positions: IObservable, private readonly _debounceValue: IFeatureDebounceInformation, private readonly _suggestPreviewEnabled: IObservable, private readonly _suggestPreviewMode: IObservable<'prefix' | 'subword' | 'subwordSmart'>, @@ -126,7 +127,7 @@ export class InlineCompletionsModel extends Disposable { }); } - const cursorPosition = this.cursorPosition.read(reader); + const cursorPosition = this._primaryPosition.read(reader); const context: InlineCompletionContext = { triggerKind: changeSummary.inlineCompletionTriggerKind, selectedSuggestionInfo: suggestItem?.toSelectedSuggestionInfo(), @@ -157,7 +158,7 @@ export class InlineCompletionsModel extends Disposable { private readonly _filteredInlineCompletionItems = derived(this, reader => { const c = this._source.inlineCompletions.read(reader); if (!c) { return []; } - const cursorPosition = this.cursorPosition.read(reader); + const cursorPosition = this._primaryPosition.read(reader); const filteredCompletions = c.inlineCompletions.filter(c => c.isVisible(this.textModel, cursorPosition, reader)); return filteredCompletions; }); @@ -195,12 +196,14 @@ export class InlineCompletionsModel extends Disposable { public readonly state = derivedOpts<{ suggestItem: SuggestItemInfo | undefined; inlineCompletion: InlineCompletionWithUpdatedRange | undefined; - ghostText: GhostTextOrReplacement; + primaryGhostText: GhostTextOrReplacement; + ghostTexts: readonly GhostTextOrReplacement[]; + edits: SingleTextEdit[]; } | undefined>({ owner: this, equalityComparer: (a, b) => { if (!a || !b) { return a === b; } - return ghostTextOrReplacementEquals(a.ghostText, b.ghostText) + return ghostTextsOrReplacementsEqual(a.ghostTexts, b.ghostTexts) && a.inlineCompletion === b.inlineCompletion && a.suggestItem === b.suggestItem; } @@ -219,12 +222,13 @@ export class InlineCompletionsModel extends Disposable { const editPreviewLength = augmentedCompletion ? augmentedCompletion.edit.text.length - suggestCompletion.text.length : 0; const mode = this._suggestPreviewMode.read(reader); - const cursor = this.cursorPosition.read(reader); - const newGhostText = edit.computeGhostText(model, mode, cursor, editPreviewLength); - - // Show an invisible ghost text to reserve space - const ghostText = newGhostText ?? new GhostText(edit.range.endLineNumber, []); - return { ghostText, inlineCompletion: augmentedCompletion?.completion, suggestItem }; + const positions = this._positions.read(reader); + const edits = [edit, ...this._getSecondaryEdits(this.textModel, positions, edit)]; + const ghostTexts = edits + .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], editPreviewLength)) + .filter(isDefined); + const primaryGhostText = ghostTexts[0] ?? new GhostText(edit.range.endLineNumber, []); + return { ghostTexts, primaryGhostText, inlineCompletion: augmentedCompletion?.completion, suggestItem, edits }; } else { if (!this._isActive.read(reader)) { return undefined; } const item = this.selectedInlineCompletion.read(reader); @@ -232,9 +236,13 @@ export class InlineCompletionsModel extends Disposable { const replacement = item.toSingleTextEdit(reader); const mode = this._inlineSuggestMode.read(reader); - const cursor = this.cursorPosition.read(reader); - const ghostText = replacement.computeGhostText(model, mode, cursor); - return ghostText ? { ghostText, inlineCompletion: item, suggestItem: undefined } : undefined; + const positions = this._positions.read(reader); + const edits = [replacement, ...this._getSecondaryEdits(this.textModel, positions, replacement)]; + const ghostTexts = edits + .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], 0)) + .filter(isDefined); + if (!ghostTexts[0]) { return undefined; } + return { ghostTexts, primaryGhostText: ghostTexts[0], inlineCompletion: item, suggestItem: undefined, edits }; } }); @@ -254,13 +262,22 @@ export class InlineCompletionsModel extends Disposable { return augmentedCompletion; } - public readonly ghostText = derivedOpts({ + public readonly ghostTexts = derivedOpts({ + owner: this, + equalityComparer: ghostTextsOrReplacementsEqual + }, reader => { + const v = this.state.read(reader); + if (!v) { return undefined; } + return v.ghostTexts; + }); + + public readonly primaryGhostText = derivedOpts({ owner: this, equalityComparer: ghostTextOrReplacementEquals }, reader => { const v = this.state.read(reader); if (!v) { return undefined; } - return v.ghostText; + return v?.primaryGhostText; }); private async _deltaSelectedInlineCompletionIndex(delta: 1 | -1): Promise { @@ -289,7 +306,7 @@ export class InlineCompletionsModel extends Disposable { } const state = this.state.get(); - if (!state || state.ghostText.isEmpty() || !state.inlineCompletion) { + if (!state || state.primaryGhostText.isEmpty() || !state.inlineCompletion) { return; } const completion = state.inlineCompletion.toInlineCompletion(undefined); @@ -306,12 +323,13 @@ export class InlineCompletionsModel extends Disposable { editor.setPosition(completion.snippetInfo.range.getStartPosition(), 'inlineCompletionAccept'); SnippetController2.get(editor)?.insert(completion.snippetInfo.snippet, { undoStopBefore: false }); } else { - const edits = this._getEdits(editor, completion.toSingleTextEdit()); + const edits = state.edits; + const selections = getEndPositionsAfterApplying(edits).map(p => Selection.fromPositions(p)); editor.executeEdits('inlineSuggestion.accept', [ - ...edits.edits.map(edit => EditOperation.replaceMove(edit.range, edit.text)), + ...edits.map(edit => EditOperation.replaceMove(edit.range, edit.text)), ...completion.additionalTextEdits ]); - editor.setSelections(edits.editorSelections, 'inlineCompletionAccept'); + editor.setSelections(selections, 'inlineCompletionAccept'); } if (completion.command) { @@ -380,10 +398,10 @@ export class InlineCompletionsModel extends Disposable { } const state = this.state.get(); - if (!state || state.ghostText.isEmpty() || !state.inlineCompletion) { + if (!state || state.primaryGhostText.isEmpty() || !state.inlineCompletion) { return; } - const ghostText = state.ghostText; + const ghostText = state.primaryGhostText; const completion = state.inlineCompletion.toInlineCompletion(undefined); if (completion.snippetInfo || completion.filterText !== completion.insertText) { @@ -415,9 +433,11 @@ export class InlineCompletionsModel extends Disposable { 0, firstPart.column - completion.range.startColumn + acceptUntilIndexExclusive); const singleTextEdit = new SingleTextEdit(replaceRange, newText); - const edits = this._getEdits(editor, singleTextEdit); - editor.executeEdits('inlineSuggestion.accept', edits.edits.map(edit => EditOperation.replaceMove(edit.range, edit.text))); - editor.setSelections(edits.editorSelections, 'inlineCompletionPartialAccept'); + const positions = this._positions.get(); + const edits = [singleTextEdit, ...this._getSecondaryEdits(this.textModel, positions, singleTextEdit)]; + const selections = getEndPositionsAfterApplying(edits).map(p => Selection.fromPositions(p)); + editor.executeEdits('inlineSuggestion.accept', edits.map(edit => EditOperation.replaceMove(edit.range, edit.text))); + editor.setSelections(selections, 'inlineCompletionPartialAccept'); } finally { this._isAcceptingPartially = false; } @@ -437,36 +457,22 @@ export class InlineCompletionsModel extends Disposable { } } - private _getEdits(editor: ICodeEditor, completion: SingleTextEdit): { edits: SingleTextEdit[]; editorSelections: Selection[] } { + private _getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { - const selections = editor.getSelections() ?? []; - const secondaryPositions = selections.slice(1).map(selection => selection.getPosition()); - const primaryPosition = selections[0].getPosition(); - const textModel = editor.getModel()!; + const primaryPosition = positions[0]; + const secondaryPositions = positions.slice(1); const replacedTextAfterPrimaryCursor = textModel .getLineContent(primaryPosition.lineNumber) - .substring(primaryPosition.column - 1, completion.range.endColumn - 1); - const secondaryEditText = completion.text.substring(primaryPosition.column - completion.range.startColumn); - const edits = [ - new SingleTextEdit(completion.range, completion.text), - ...secondaryPositions.map(pos => { - const textAfterSecondaryCursor = this.textModel - .getLineContent(pos.lineNumber) - .substring(pos.column - 1); - const l = commonPrefixLength(replacedTextAfterPrimaryCursor, textAfterSecondaryCursor); - const range = Range.fromPositions(pos, pos.delta(0, l)); - return new SingleTextEdit(range, secondaryEditText); - }) - ]; - const sortPerm = Permutation.createSortPermutation(edits, (edit1, edit2) => Range.compareRangesUsingStarts(edit1.range, edit2.range)); - const sortedNewRanges = getNewRanges(sortPerm.apply(edits)); - const newRanges = sortPerm.inverse().apply(sortedNewRanges); - const editorSelections = newRanges.map(range => Selection.fromPositions(range.getEndPosition())); - - return { - edits, - editorSelections - }; + .substring(primaryPosition.column - 1, primaryEdit.range.endColumn - 1); + const secondaryEditText = primaryEdit.text.substring(primaryPosition.column - primaryEdit.range.startColumn); + return secondaryPositions.map(pos => { + const textAfterSecondaryCursor = this.textModel + .getLineContent(pos.lineNumber) + .substring(pos.column - 1); + const l = commonPrefixLength(replacedTextAfterPrimaryCursor, textAfterSecondaryCursor); + const range = Range.fromPositions(pos, pos.delta(0, l)); + return new SingleTextEdit(range, secondaryEditText); + }); } public handleSuggestAccepted(item: SuggestItemInfo) { @@ -482,3 +488,10 @@ export class InlineCompletionsModel extends Disposable { ); } } + +function getEndPositionsAfterApplying(edits: SingleTextEdit[]): Position[] { + const sortPerm = Permutation.createSortPermutation(edits, (edit1, edit2) => Range.compareRangesUsingStarts(edit1.range, edit2.range)); + const sortedNewRanges = getNewRanges(sortPerm.apply(edits)); + const newRanges = sortPerm.inverse().apply(sortedNewRanges); + return newRanges.map(range => range.getEndPosition()); +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts index c958fcdb605..8517f24ec85 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts @@ -18,6 +18,11 @@ export class SingleTextEdit { ) { } + static equals(first: SingleTextEdit, second: SingleTextEdit) { + return first.range.equalsRange(second.range) && first.text === second.text; + + } + removeCommonPrefix(model: ITextModel, validModelRange?: Range): SingleTextEdit { const modelRange = validModelRange ? this.range.intersectRanges(validModelRange) : this.range; if (!modelRange) { diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts index 2770ea2aa33..49e09d4e4a7 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts @@ -83,7 +83,7 @@ export class GhostTextContext extends Disposable { this._register(autorun(reader => { /** @description update */ - const ghostText = model.ghostText.read(reader); + const ghostText = model.primaryGhostText.read(reader); let view: string | undefined; if (ghostText) { view = ghostText.render(this.editor.getValue(), true); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts index 815ac45fe1d..065bf265a06 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts @@ -242,8 +242,8 @@ export class InlineCompletionsAccessibleViewContribution extends Disposable { if (!model || !state) { return false; } - const lineText = model.textModel.getLineContent(state.ghostText.lineNumber); - const ghostText = state.ghostText.renderForScreenReader(lineText); + const lineText = model.textModel.getLineContent(state.primaryGhostText.lineNumber); + const ghostText = state.primaryGhostText.renderForScreenReader(lineText); if (!ghostText) { return false; } From 31a7befa311836b14adc6348293ad22f2257998a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 8 Feb 2024 18:12:46 +0100 Subject: [PATCH 1026/1897] api - some notes on chat agent request, history, and potential for notify (#204740) * api - language model access - rename LanguageModelRequest -> LanguageModelResponse - remove cancel-method - remove event of streams which isn't needed at first * api - some notes on chat agent request, history, and potential for notify --- .../api/common/extHostChatProvider.ts | 20 +++----- .../vscode.proposed.chatAgents2.d.ts | 28 +++++++++++ .../vscode.proposed.chatRequestAccess.d.ts | 49 ++----------------- 3 files changed, 39 insertions(+), 58 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index c76f419515b..d9159a7350a 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -22,24 +22,20 @@ type LanguageModelData = { class LanguageModelResponseStream { - readonly apiObj: vscode.LanguageModelResponseStream; readonly stream = new AsyncIterableSource(); - constructor(option: number, stream?: AsyncIterableSource) { + constructor( + readonly option: number, + stream?: AsyncIterableSource + ) { this.stream = stream ?? new AsyncIterableSource(); - const that = this; - this.apiObj = { - option: option, - response: that.stream.asyncIterable - }; } } class LanguageModelRequest { - readonly apiObject: vscode.LanguageModelRequest; + readonly apiObject: vscode.LanguageModelResponse; - private readonly _onDidStart = new Emitter(); private readonly _responseStreams = new Map(); private readonly _defaultStream = new AsyncIterableSource(); private _isDone: boolean = false; @@ -51,9 +47,8 @@ class LanguageModelRequest { const that = this; this.apiObject = { result: promise, - response: that._defaultStream.asyncIterable, - onDidStartResponseStream: that._onDidStart.event, - cancel() { cts.cancel(); }, + stream: that._defaultStream.asyncIterable, + // responses: AsyncIterable[] // FUTURE responses per N }; promise.finally(() => { @@ -81,7 +76,6 @@ class LanguageModelRequest { res = new LanguageModelResponseStream(fragment.index); } this._responseStreams.set(fragment.index, res); - this._onDidStart.fire(res.apiObj); } res.stream.emitOne(fragment.part); } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 9864b23c7e2..253347776e4 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -12,6 +12,7 @@ declare module 'vscode' { /** * The request that was sent to the chat agent. */ + // TODO@API make this optional? Allow for response without request? request: ChatAgentRequest; /** @@ -25,11 +26,27 @@ declare module 'vscode' { result: ChatAgentResult2; } + // TODO@API class + // export interface ChatAgentResponse { + // /** + // * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. + // */ + // response: (ChatAgentContentProgress | ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart)[]; + + // /** + // * The result that was received from the chat agent. + // */ + // result: ChatAgentResult2; + // } + export interface ChatAgentContext { /** * All of the chat messages so far in the current chat session. */ history: ChatAgentHistoryEntry[]; + + // TODO@API have "turns" + // history2: (ChatAgentRequest | ChatAgentResponse)[]; } /** @@ -235,6 +252,14 @@ declare module 'vscode' { */ followupProvider?: ChatAgentFollowupProvider; + + // TODO@ + // notify(request: ChatResponsePart, reference: string): boolean; + + // TODO@API + // clear NEVER happens + // onDidClearResult(value: TResult): void; + /** * When the user clicks this agent in `/help`, this text will be submitted to this subCommand */ @@ -276,6 +301,9 @@ declare module 'vscode' { subCommand?: string; variables: Record; + + // TODO@API argumented prompt, reverse order! + // variables2: { start:number, length:number, values: ChatVariableValue[]}[] } export interface ChatAgentResponseStream { diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts index 733e985dd98..1134553728c 100644 --- a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts +++ b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts @@ -5,57 +5,16 @@ declare module 'vscode' { - export interface LanguageModelResponseStream { - - /** - * The response stream. - */ - readonly response: AsyncIterable; - - /** - * The variant of multiple responses. This is used to disambiguate between multiple - * response streams when having asked for multiple response options - */ - readonly option: number; - } - - // TODO@API NAME: LanguageModelResponse (depends on having cancel or not) - export interface LanguageModelRequest { + export interface LanguageModelResponse { /** * The overall result of the request which represents failure or success * but _not_ the actual response or responses */ // TODO@API define this type! - result: Thenable; + result: Thenable; - /** - * The _default response_ stream. This is the stream of the first response option - * receiving data. - * - * Usually there is only one response option and this stream is more convienient to use - * than the {@link onDidStartResponseStream `onDidStartResponseStream`} event. - */ - // TODO@API NAME: responseStream - response: AsyncIterable; - - /** - * An event that fires whenever a new response option is available. The response - * itself is a stream of the actual response. - * - * *Note* that the first time this event fires, the {@link LanguageModelResponseStream.response response stream} - * is the same as the {@link response `default response stream`}. - * - * *Note* that unless requested there is only one response option, so this event will only fire - * once. - */ - onDidStartResponseStream: Event; - - /** - * Cancel this request. - */ - // TODO@API remove this? We pass a token to makeRequest call already - cancel(): void; + stream: AsyncIterable; } /** @@ -90,7 +49,7 @@ declare module 'vscode' { * @param messages * @param options */ - makeRequest(messages: ChatMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelRequest; + makeRequest(messages: ChatMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelResponse; } export interface LanguageModelAccessOptions { From f618bf3befda9b9c5bade12bc77cb33dff8d1284 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 8 Feb 2024 18:16:28 +0100 Subject: [PATCH 1027/1897] Custom hover for editor tabs (#204742) --- .../browser/parts/editor/editorTabsControl.ts | 46 ++++++++++++++++++- .../parts/editor/multiEditorTabsControl.ts | 15 +++--- .../parts/editor/singleEditorTabsControl.ts | 9 +--- 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index 3d202d8d48b..058d0a91a92 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -25,7 +25,7 @@ import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillEditorsDragData, isWindowDraggedOver } from 'vs/workbench/browser/dnd'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorGroupsView, IEditorGroupView, IEditorPartsView, IInternalEditorOpenOptions } from 'vs/workbench/browser/parts/editor/editor'; -import { IEditorCommandsContext, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, EditorsOrder, EditorInputCapabilities, IToolbarActions, GroupIdentifier } from 'vs/workbench/common/editor'; +import { IEditorCommandsContext, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, EditorsOrder, EditorInputCapabilities, IToolbarActions, GroupIdentifier, Verbosity } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ResourceContextKey, ActiveEditorPinnedContext, ActiveEditorStickyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, ActiveEditorFirstInGroupContext, ActiveEditorAvailableEditorIdsContext, applyAvailableEditorIds, ActiveEditorLastInGroupContext } from 'vs/workbench/common/contextkeys'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; @@ -44,6 +44,11 @@ import { IAuxiliaryEditorPart, MergeGroupMode } from 'vs/workbench/services/edit import { isMacintosh } from 'vs/base/common/platform'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { MarkdownString, MarkdownStringTextNewlineStyle } from 'vs/base/common/htmlContent'; +import { ITooltipMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { IDecorationsService } from 'vs/workbench/services/decorations/common/decorations'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; export class EditorCommandsContextActionRunner extends ActionRunner { @@ -135,7 +140,9 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC @IQuickInputService protected quickInputService: IQuickInputService, @IThemeService themeService: IThemeService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService, - @IHostService private readonly hostService: IHostService + @IHostService private readonly hostService: IHostService, + @IDecorationsService private readonly decorationsService: IDecorationsService, + @IHoverService private readonly hoverService: IHoverService ) { super(themeService); @@ -444,6 +451,41 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC return this.groupsView.partOptions.tabHeight !== 'compact' ? EditorTabsControl.EDITOR_TAB_HEIGHT.normal : EditorTabsControl.EDITOR_TAB_HEIGHT.compact; } + protected getHoverTitle(editor: EditorInput): ITooltipMarkdownString { + const title = editor.getTitle(Verbosity.LONG); + const markdown = new MarkdownString(title); + + if (editor.resource) { + const decoration = this.decorationsService.getDecoration(editor.resource, false); + if (decoration) { + const decorations = decoration.tooltip.split('• '); + const decorationString = `• ${decorations.join('\n• ')}`; + + markdown.appendText('\n', MarkdownStringTextNewlineStyle.Paragraph); + markdown.appendText(decorationString, MarkdownStringTextNewlineStyle.Break); + } + } + + return { + markdown, + markdownNotSupportedFallback: title + }; + } + + protected getHoverDelegate(): IHoverDelegate { + return { + delay: 500, + showHover: options => { + return this.hoverService.showHover({ + ...options, + persistence: { + hideOnHover: true + } + }); + } + }; + } + protected updateTabHeight(): void { this.parent.style.setProperty('--editor-group-tab-height', `${this.tabHeight}px`); } diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 4372fd674a4..d79a5be26c3 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -56,6 +56,8 @@ import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor import { StickyEditorGroupModel, UnstickyEditorGroupModel } from 'vs/workbench/common/editor/filteredEditorGroupModel'; import { IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { IDecorationsService } from 'vs/workbench/services/decorations/common/decorations'; interface IEditorInputLabel { readonly editor: EditorInput; @@ -149,9 +151,11 @@ export class MultiEditorTabsControl extends EditorTabsControl { @IPathService private readonly pathService: IPathService, @ITreeViewsDnDService private readonly treeViewsDragAndDropService: ITreeViewsDnDService, @IEditorResolverService editorResolverService: IEditorResolverService, - @IHostService hostService: IHostService + @IHostService hostService: IHostService, + @IDecorationsService decorationsService: IDecorationsService, + @IHoverService hoverService: IHoverService, ) { - super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService, hostService); + super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService, hostService, decorationsService, hoverService); // Resolve the correct path library for the OS we are on // If we are connected to remote, this accounts for the @@ -793,7 +797,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { tabContainer.appendChild(tabBorderTopContainer); // Tab Editor Label - const editorLabel = this.tabResourceLabels.create(tabContainer); + const editorLabel = this.tabResourceLabels.create(tabContainer, { hoverDelegate: this.getHoverDelegate() }); // Tab Actions const tabActionsContainer = document.createElement('div'); @@ -1469,14 +1473,11 @@ export class MultiEditorTabsControl extends EditorTabsControl { tabContainer.setAttribute('aria-description', ''); } - const title = tabLabel.title || ''; - tabContainer.title = title; - // Label tabLabelWidget.setResource( { name, description, resource: EditorResourceAccessor.getOriginalUri(editor, { supportSideBySide: SideBySideEditor.BOTH }) }, { - title, + title: this.getHoverTitle(editor), extraClasses: coalesce(['tab-label', fileDecorationBadges ? 'tab-label-has-badge' : undefined].concat(editor.getLabelExtraClasses())), italic: !this.tabsModel.isPinned(editor), forceLabel, diff --git a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts index 810dbc73083..c33305205c2 100644 --- a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts @@ -51,7 +51,7 @@ export class SingleEditorTabsControl extends EditorTabsControl { titleContainer.appendChild(labelContainer); // Editor Label - this.editorLabel = this._register(this.instantiationService.createInstance(ResourceLabel, labelContainer, undefined)).element; + this.editorLabel = this._register(this.instantiationService.createInstance(ResourceLabel, labelContainer, { hoverDelegate: this.getHoverDelegate() })).element; this._register(addDisposableListener(this.editorLabel.element, EventType.CLICK, e => this.onTitleLabelClick(e))); // Breadcrumbs @@ -304,11 +304,6 @@ export class SingleEditorTabsControl extends EditorTabsControl { description = editor.getDescription(this.getVerbosity(labelFormat)) || ''; } - let title = editor.getTitle(Verbosity.LONG); - if (description === title) { - title = ''; // dont repeat what is already shown - } - editorLabel.setResource( { resource: EditorResourceAccessor.getOriginalUri(editor, { supportSideBySide: SideBySideEditor.BOTH }), @@ -316,7 +311,7 @@ export class SingleEditorTabsControl extends EditorTabsControl { description }, { - title, + title: this.getHoverTitle(editor), italic: !isEditorPinned, extraClasses: ['single-tab', 'title-label'].concat(editor.getLabelExtraClasses()), fileDecorations: { From 884acabd703fee0ef844794210798567f85425d1 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 8 Feb 2024 10:28:40 -0700 Subject: [PATCH 1028/1897] When receiving an unexpected status code also add information surrounding the headers and body (#204741) * When receiving an unexpected status code also add information surrounding the headers and body * Add compiled file --- build/azure-pipelines/common/publish.js | 2 +- build/azure-pipelines/common/publish.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/common/publish.js b/build/azure-pipelines/common/publish.js index d035de8bb84..bc7d500d450 100644 --- a/build/azure-pipelines/common/publish.js +++ b/build/azure-pipelines/common/publish.js @@ -79,7 +79,7 @@ class ProvisionService { }; const res = await fetch(`https://dsprovisionapi.microsoft.com${url}`, opts); if (!res.ok || res.status < 200 || res.status >= 500) { - throw new Error(`Unexpected status code: ${res.status}`); + throw new Error(`Unexpected status code: ${res.status}\nResponse Headers: ${JSON.stringify(res.headers)}\nBody Text: ${await res.text()}`); } return await res.json(); } diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index 6b20492c87c..6048c19f7a0 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -113,7 +113,7 @@ class ProvisionService { const res = await fetch(`https://dsprovisionapi.microsoft.com${url}`, opts); if (!res.ok || res.status < 200 || res.status >= 500) { - throw new Error(`Unexpected status code: ${res.status}`); + throw new Error(`Unexpected status code: ${res.status}\nResponse Headers: ${JSON.stringify(res.headers)}\nBody Text: ${await res.text()}`); } return await res.json(); From 49001e5237910cfdae20dde5219b3c6572bd99f5 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 8 Feb 2024 18:43:24 +0100 Subject: [PATCH 1029/1897] know for whom a LM request is made (#204744) --- .../workbench/api/browser/mainThreadChatProvider.ts | 6 +++--- src/vs/workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostChatProvider.ts | 11 ++++++----- src/vs/workbench/contrib/chat/common/chatProvider.ts | 8 ++++---- src/vscode-dts/vscode.proposed.chatProvider.d.ts | 2 ++ 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index 24ce8d39797..7c6534079e2 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -48,11 +48,11 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { $registerProvider(handle: number, identifier: string, metadata: IChatResponseProviderMetadata): void { const registration = this._chatProviderService.registerChatResponseProvider(identifier, { metadata, - provideChatResponse: async (messages, options, progress, token) => { + provideChatResponse: async (messages, from, options, progress, token) => { const requestId = (Math.random() * 1e6) | 0; this._pendingProgress.set(requestId, progress); try { - await this._proxy.$provideLanguageModelResponse(handle, requestId, messages, options, token); + await this._proxy.$provideLanguageModelResponse(handle, requestId, from, messages, options, token); } finally { this._pendingProgress.delete(requestId); } @@ -80,7 +80,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { 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 => { + const task = this._chatProviderService.fetchChatResponse(providerId, extension, 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 e8b75cbceba..a34f7a83238 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1183,7 +1183,7 @@ export interface MainThreadChatProviderShape extends IDisposable { export interface ExtHostChatProviderShape { $updateLanguageModels(data: { added?: string[]; removed?: string[] }): void; $updateAccesslist(data: { extension: ExtensionIdentifier; enabled: boolean }[]): void; - $provideLanguageModelResponse(handle: number, requestId: number, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; + $provideLanguageModelResponse(handle: number, requestId: number, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise; } diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index d9159a7350a..d47cf519006 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -121,7 +121,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { }); } - async $provideLanguageModelResponse(handle: number, requestId: number, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise { + async $provideLanguageModelResponse(handle: number, requestId: number, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise { const data = this._languageModels.get(handle); if (!data) { return; @@ -134,14 +134,15 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._proxy.$handleProgressChunk(requestId, { index: fragment.index, part: fragment.part }); }); - return data.provider.provideChatResponse(messages.map(typeConvert.ChatMessage.to), options, progress, token); + if (data.provider.provideLanguageModelResponse) { + return data.provider.provideLanguageModelResponse(messages.map(typeConvert.ChatMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); + } else { + return data.provider.provideChatResponse(messages.map(typeConvert.ChatMessage.to), options, progress, token); + } } //#region --- making request - - - $updateLanguageModels(data: { added?: string[] | undefined; removed?: string[] | undefined }): void { const added: string[] = []; const removed: string[] = []; diff --git a/src/vs/workbench/contrib/chat/common/chatProvider.ts b/src/vs/workbench/contrib/chat/common/chatProvider.ts index 88349952e4e..38be72eb51e 100644 --- a/src/vs/workbench/contrib/chat/common/chatProvider.ts +++ b/src/vs/workbench/contrib/chat/common/chatProvider.ts @@ -35,7 +35,7 @@ export interface IChatResponseProviderMetadata { export interface IChatResponseProvider { metadata: IChatResponseProviderMetadata; - provideChatResponse(messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; + provideChatResponse(messages: IChatMessage[], from: ExtensionIdentifier, options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; } export const IChatProviderService = createDecorator('chatProviderService'); @@ -52,7 +52,7 @@ export interface IChatProviderService { registerChatResponseProvider(identifier: string, provider: IChatResponseProvider): IDisposable; - fetchChatResponse(identifier: string, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; + fetchChatResponse(identifier: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; } export class ChatProviderService implements IChatProviderService { @@ -89,11 +89,11 @@ export class ChatProviderService implements IChatProviderService { }); } - fetchChatResponse(identifier: string, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise { + fetchChatResponse(identifier: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise { const provider = this._providers.get(identifier); if (!provider) { throw new Error(`Chat response provider with identifier ${identifier} is not registered.`); } - return provider.provideChatResponse(messages, options, progress, token); + return provider.provideChatResponse(messages, from, options, progress, token); } } diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index 3ff89cca683..1d218bc8e00 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -17,6 +17,8 @@ declare module 'vscode' { */ export interface ChatResponseProvider { provideChatResponse(messages: ChatMessage[], options: { [name: string]: any }, progress: Progress, token: CancellationToken): Thenable; + + provideLanguageModelResponse?(messages: ChatMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; } export interface ChatResponseProviderMetadata { From 249a9514f273478950cdfd514a94f06f9fe0c3d9 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 8 Feb 2024 18:59:55 +0100 Subject: [PATCH 1030/1897] add ownership file for vscode-dts (#204746) --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..8da51487c84 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# ensure the API police is aware of changes to the vscode-dts file +# this is only about the final API, not about proposed API changes +src/vscode-dts/vscode.d.ts @jrieken @mjbvz From cfb737085554c4d588f51e5f5f4010784b4595ac Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 9 Feb 2024 03:02:19 +0900 Subject: [PATCH 1031/1897] fix: inheriting NODE_OPTIONS on macOS with integrated terminal (#204682) --- resources/darwin/bin/code.sh | 4 ++++ .../terminal/common/terminalEnvironment.ts | 22 ++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/resources/darwin/bin/code.sh b/resources/darwin/bin/code.sh index 1d66515c2c7..de5c3bfcab0 100755 --- a/resources/darwin/bin/code.sh +++ b/resources/darwin/bin/code.sh @@ -31,5 +31,9 @@ fi CONTENTS="$APP_PATH/Contents" ELECTRON="$CONTENTS/MacOS/Electron" CLI="$CONTENTS/Resources/app/out/cli.js" +export VSCODE_NODE_OPTIONS=$NODE_OPTIONS +export VSCODE_NODE_REPL_EXTERNAL_MODULE=$NODE_REPL_EXTERNAL_MODULE +unset NODE_OPTIONS +unset NODE_REPL_EXTERNAL_MODULE ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" "$@" exit $? diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index 22aedd0758c..27ccb9cb61f 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -13,7 +13,7 @@ import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspac import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; 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 { IProcessEnvironment, isWindows, isMacintosh, language, OperatingSystem } from 'vs/base/common/platform'; import { escapeNonWindowsPath, sanitizeCwd } from 'vs/platform/terminal/common/terminalEnvironment'; import { isString } from 'vs/base/common/types'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; @@ -269,6 +269,26 @@ export async function createTerminalEnvironment( } } + // Workaround for https://github.com/microsoft/vscode/issues/204005 + // We should restore the following environment variables when a user + // launches the application using the CLI so that integrated terminal + // can still inherit these variables. + // We are not bypassing the restrictions implied in https://github.com/electron/electron/pull/40770 + // since this only affects integrated terminal and not the application itself. + if (isMacintosh) { + // Restore NODE_OPTIONS if it was set + if (env['VSCODE_NODE_OPTIONS']) { + env['NODE_OPTIONS'] = env['VSCODE_NODE_OPTIONS']; + delete env['VSCODE_NODE_OPTIONS']; + } + + // Restore NODE_REPL_EXTERNAL_MODULE if it was set + if (env['VSCODE_NODE_REPL_EXTERNAL_MODULE']) { + env['NODE_REPL_EXTERNAL_MODULE'] = env['VSCODE_NODE_REPL_EXTERNAL_MODULE']; + delete env['VSCODE_NODE_REPL_EXTERNAL_MODULE']; + } + } + // Sanitize the environment, removing any undesirable VS Code and Electron environment // variables sanitizeProcessEnvironment(env, 'VSCODE_IPC_HOOK_CLI'); From 4bc4f1adaf04dfeb757d3c897e2a14e4f1874b0b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 8 Feb 2024 10:48:55 -0800 Subject: [PATCH 1032/1897] Add zsh and fish as valid shell code block languages This will make the run in terminal button move out of the overflow menu --- .../contrib/chat/browser/actions/chatCodeblockActions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 2d616c7be68..0076b4eb1f8 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -382,8 +382,10 @@ export function registerChatCodeBlockActions() { }); const shellLangIds = [ + 'fish', 'powershell', - 'shellscript' + 'shellscript', + 'zsh' ]; registerAction2(class RunInTerminalAction extends ChatCodeBlockAction { constructor() { From f1f5d07d14472f954553049209e8fdb0afe0683e Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 8 Feb 2024 11:00:23 -0800 Subject: [PATCH 1033/1897] debug: restore default notification priority for progress (#204750) Fixes #204334 --- src/vs/workbench/contrib/debug/browser/debugProgress.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugProgress.ts b/src/vs/workbench/contrib/debug/browser/debugProgress.ts index acf23c97080..c87b19974f5 100644 --- a/src/vs/workbench/contrib/debug/browser/debugProgress.ts +++ b/src/vs/workbench/contrib/debug/browser/debugProgress.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IDebugService, VIEWLET_ID, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IDebugService, IDebugSession, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; -import { NotificationPriority } from 'vs/platform/notification/common/notification'; export class DebugProgressContribution implements IWorkbenchContribution { @@ -45,7 +44,6 @@ export class DebugProgressContribution implements IWorkbenchContribution { location: ProgressLocation.Notification, title: progressStartEvent.body.title, cancellable: progressStartEvent.body.cancellable, - priority: NotificationPriority.SILENT, source, delay: 500 }, progressStep => { From 2033eae5af6a253b580753f9860c9b7242f2954b Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 8 Feb 2024 12:00:44 -0700 Subject: [PATCH 1034/1897] Allow publishing retry (#204758) * Allow publishing retry * Update build/azure-pipelines/common/publish.ts Co-authored-by: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> * Update build/azure-pipelines/common/publish.ts Co-authored-by: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> * Compile --------- Co-authored-by: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> --- build/azure-pipelines/common/publish.js | 11 ++++++++++- build/azure-pipelines/common/publish.ts | 14 +++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/common/publish.js b/build/azure-pipelines/common/publish.js index bc7d500d450..c7c0efe9ba0 100644 --- a/build/azure-pipelines/common/publish.js +++ b/build/azure-pipelines/common/publish.js @@ -41,6 +41,9 @@ class Temp { } } } +function isCreateProvisionedFilesErrorResponse(response) { + return response?.ErrorDetails !== undefined; +} class ProvisionService { log; accessToken; @@ -63,6 +66,10 @@ class ProvisionService { }); this.log(`Provisioning ${fileName} (releaseId: ${releaseId}, fileId: ${fileId})...`); const res = await (0, retry_1.retry)(() => this.request('POST', '/api/v2/ProvisionedFiles/CreateProvisionedFiles', { body })); + if (isCreateProvisionedFilesErrorResponse(res) && res.ErrorDetails.Code === 'FriendlyFileNameAlreadyProvisioned') { + this.log(`File already provisioned (most likley due to a re-run), skipping: ${fileName}`); + return; + } if (!res.IsSuccess) { throw new Error(`Failed to submit provisioning request: ${JSON.stringify(res.ErrorDetails)}`); } @@ -78,7 +85,9 @@ class ProvisionService { } }; const res = await fetch(`https://dsprovisionapi.microsoft.com${url}`, opts); - if (!res.ok || res.status < 200 || res.status >= 500) { + // 400 normally means the request is bad or something is already provisioned, so we will return as retries are useless + // Otherwise log the text body and headers. We do text because some responses are not JSON. + if ((!res.ok || res.status < 200 || res.status >= 500) && res.status !== 400) { throw new Error(`Unexpected status code: ${res.status}\nResponse Headers: ${JSON.stringify(res.headers)}\nBody Text: ${await res.text()}`); } return await res.json(); diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index 6048c19f7a0..90da4fd5235 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -69,6 +69,10 @@ interface CreateProvisionedFilesErrorResponse { type CreateProvisionedFilesResponse = CreateProvisionedFilesSuccessResponse | CreateProvisionedFilesErrorResponse; +function isCreateProvisionedFilesErrorResponse(response: unknown): response is CreateProvisionedFilesErrorResponse { + return (response as CreateProvisionedFilesErrorResponse)?.ErrorDetails !== undefined; +} + class ProvisionService { constructor( @@ -93,6 +97,11 @@ class ProvisionService { this.log(`Provisioning ${fileName} (releaseId: ${releaseId}, fileId: ${fileId})...`); const res = await retry(() => this.request('POST', '/api/v2/ProvisionedFiles/CreateProvisionedFiles', { body })); + if (isCreateProvisionedFilesErrorResponse(res) && res.ErrorDetails.Code === 'FriendlyFileNameAlreadyProvisioned') { + this.log(`File already provisioned (most likley due to a re-run), skipping: ${fileName}`); + return; + } + if (!res.IsSuccess) { throw new Error(`Failed to submit provisioning request: ${JSON.stringify(res.ErrorDetails)}`); } @@ -112,7 +121,10 @@ class ProvisionService { const res = await fetch(`https://dsprovisionapi.microsoft.com${url}`, opts); - if (!res.ok || res.status < 200 || res.status >= 500) { + + // 400 normally means the request is bad or something is already provisioned, so we will return as retries are useless + // Otherwise log the text body and headers. We do text because some responses are not JSON. + if ((!res.ok || res.status < 200 || res.status >= 500) && res.status !== 400) { throw new Error(`Unexpected status code: ${res.status}\nResponse Headers: ${JSON.stringify(res.headers)}\nBody Text: ${await res.text()}`); } From 53031979503c9e97552f2bb5bf185d41bb977bc3 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 8 Feb 2024 12:37:39 -0700 Subject: [PATCH 1035/1897] Update type guard (#204763) --- build/azure-pipelines/common/publish.js | 2 +- build/azure-pipelines/common/publish.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/common/publish.js b/build/azure-pipelines/common/publish.js index c7c0efe9ba0..e6b24921ac1 100644 --- a/build/azure-pipelines/common/publish.js +++ b/build/azure-pipelines/common/publish.js @@ -42,7 +42,7 @@ class Temp { } } function isCreateProvisionedFilesErrorResponse(response) { - return response?.ErrorDetails !== undefined; + return response?.ErrorDetails?.Code !== undefined; } class ProvisionService { log; diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index 90da4fd5235..f144a7be793 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -70,7 +70,7 @@ interface CreateProvisionedFilesErrorResponse { type CreateProvisionedFilesResponse = CreateProvisionedFilesSuccessResponse | CreateProvisionedFilesErrorResponse; function isCreateProvisionedFilesErrorResponse(response: unknown): response is CreateProvisionedFilesErrorResponse { - return (response as CreateProvisionedFilesErrorResponse)?.ErrorDetails !== undefined; + return (response as CreateProvisionedFilesErrorResponse)?.ErrorDetails?.Code !== undefined; } class ProvisionService { From fcaef74d2ef94479f4924d1f5aa7647d4281815f Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 8 Feb 2024 20:43:28 +0100 Subject: [PATCH 1036/1897] Fix smoke tests (#204764) fix smoke tests --- test/automation/src/extensions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/automation/src/extensions.ts b/test/automation/src/extensions.ts index aa59f7cd7cf..1d0f97dadf3 100644 --- a/test/automation/src/extensions.ts +++ b/test/automation/src/extensions.ts @@ -41,7 +41,7 @@ export class Extensions extends Viewlet { } async closeExtension(title: string): Promise { - await this.code.waitAndClick(`.tabs-container div.tab[title="Extension: ${title}"] div.tab-actions a.action-label.codicon.codicon-close`); + await this.code.waitAndClick(`.tabs-container div.tab[aria-label="Extension: ${title}"] div.tab-actions a.action-label.codicon.codicon-close`); } async installExtension(id: string, waitUntilEnabled: boolean): Promise { From 56f9e01216f191f7d6b8e32d32b62c816e5a6f77 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 8 Feb 2024 12:05:19 -0800 Subject: [PATCH 1037/1897] Improvements to extensions features * - show all runtime information in runtime status feature - show runtime feature information in runtime extensions editor - filter extensions by feature in view --- .../api/browser/mainThreadChatProvider.ts | 27 ++-- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../api/common/extHostChatAgents2.ts | 5 + .../contrib/chat/browser/chat.contribution.ts | 64 +------- .../contrib/chat/common/chatService.ts | 1 - .../abstractRuntimeExtensionsEditor.ts | 25 ++- .../browser/extensionFeaturesTab.ts | 153 +++++++++++++++--- .../extensions/browser/extensionsViews.ts | 38 ++++- .../browser/media/extensionEditor.css | 2 +- .../runtimeExtensionsEditor.ts | 4 +- .../common/extensionFeatures.ts | 9 +- .../common/abstractExtensionService.ts | 77 ++------- 12 files changed, 236 insertions(+), 171 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index 7c6534079e2..76001d04960 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -5,13 +5,14 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; 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 { Registry } from 'vs/platform/registry/common/platform'; import { ExtHostChatProviderShape, ExtHostContext, MainContext, MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol'; import { IChatResponseProviderMetadata, IChatResponseFragment, IChatProviderService, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { CHAT_FEATURE_ID } from 'vs/workbench/contrib/chat/common/chatService'; -import { IExtensionFeaturesManagementService } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; @extHostNamedCustomer(MainContext.MainThreadChatProvider) @@ -31,13 +32,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatProvider); this._proxy.$updateLanguageModels({ added: _chatProviderService.getProviders() }); - this._proxy.$updateAccesslist(_extensionFeaturesManagementService.getEnablementData(CHAT_FEATURE_ID)); this._store.add(_chatProviderService.onDidChangeProviders(this._proxy.$updateLanguageModels, this._proxy)); - this._store.add(_extensionFeaturesManagementService.onDidChangeEnablement(e => { - if (e.featureId === CHAT_FEATURE_ID) { - this._proxy.$updateAccesslist(_extensionFeaturesManagementService.getEnablementData(CHAT_FEATURE_ID)); - } - })); } dispose(): void { @@ -46,7 +41,8 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { } $registerProvider(handle: number, identifier: string, metadata: IChatResponseProviderMetadata): void { - const registration = this._chatProviderService.registerChatResponseProvider(identifier, { + const dipsosables = new DisposableStore(); + dipsosables.add(this._chatProviderService.registerChatResponseProvider(identifier, { metadata, provideChatResponse: async (messages, from, options, progress, token) => { const requestId = (Math.random() * 1e6) | 0; @@ -57,8 +53,15 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { this._pendingProgress.delete(requestId); } } - }); - this._providerRegistrations.set(handle, registration); + })); + dipsosables.add(Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: `lm-${identifier}`, + label: localize('languageModels', "Language Model ({0})", `${identifier}-${metadata.model}`), + access: { + canToggle: false, + }, + })); + this._providerRegistrations.set(handle, dipsosables); } async $handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise { @@ -70,7 +73,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { } async $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise { - const access = await this._extensionFeaturesManagementService.getAccess(extension, CHAT_FEATURE_ID, justification); + const access = await this._extensionFeaturesManagementService.getAccess(extension, `lm-${providerId}`, justification); if (!access) { return undefined; } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index db680538d6b..5b672c3356a 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -208,7 +208,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I 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 extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostLogService)); + const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol)); const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol)); diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index dbcce3f64d7..d83e50c8442 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -15,6 +15,7 @@ import { localize } from 'vs/nls'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { IChatAgentCommand, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; @@ -153,6 +154,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { constructor( mainContext: IMainContext, + private readonly _extHostChatProvider: ExtHostChatProvider, private readonly _logService: ILogService, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); @@ -177,6 +179,8 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); } + this._extHostChatProvider.$updateAccesslist([{ extension: agent.extension.identifier, enabled: true }]); + const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService); try { const convertedHistory = await this.prepareHistory(agent, request, context); @@ -211,6 +215,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } finally { stream.close(); + this._extHostChatProvider.$updateAccesslist([{ extension: agent.extension.identifier, enabled: false }]); } } diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index cc1983fb53b..5a9d5ef5cf8 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isMacintosh } from 'vs/base/common/platform'; -import { Emitter } from 'vs/base/common/event'; import * as nls from 'vs/nls'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -31,7 +30,7 @@ import { ChatWidgetService } from 'vs/workbench/contrib/chat/browser/chatWidget' import 'vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib'; import 'vs/workbench/contrib/chat/browser/contrib/chatHistoryVariables'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; -import { CHAT_FEATURE_ID, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; import { ChatWidgetHistoryService, IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService'; import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; @@ -59,9 +58,6 @@ import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/c import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IExtensionFeatureMarkdownRenderer, Extensions as ExtensionFeaturesExtensions, IRenderedData, IExtensionFeaturesRegistry, IExtensionFeaturesManagementService } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; -import { ExtensionIdentifier, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; -import { getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -287,62 +283,6 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { } } -class ChatFeatureMarkdowneRenderer extends Disposable implements IExtensionFeatureMarkdownRenderer { - - readonly type = 'markdown'; - - constructor( - @IExtensionFeaturesManagementService private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService, - ) { - super(); - } - - shouldRender(manifest: IExtensionManifest): boolean { - const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); - const accessData = this.extensionFeaturesManagementService.getAccessData(extensionId, CHAT_FEATURE_ID); - return !!accessData; - } - - render(manifest: IExtensionManifest): IRenderedData { - const disposables = new DisposableStore(); - const emitter = disposables.add(new Emitter()); - const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); - disposables.add(this.extensionFeaturesManagementService.onDidChangeAccessData(e => { - if (ExtensionIdentifier.equals(e.extension, extensionId) && e.featureId === CHAT_FEATURE_ID) { - emitter.fire(this.getMarkdownData(extensionId)); - } - })); - return { - data: this.getMarkdownData(extensionId), - onDidChange: emitter.event, - dispose: () => { disposables.dispose(); } - }; - } - - private getMarkdownData(extensionId: ExtensionIdentifier): IMarkdownString { - const markdown = new MarkdownString(); - const accessData = this.extensionFeaturesManagementService.getAccessData(extensionId, CHAT_FEATURE_ID); - if (accessData && accessData.totalCount) { - if (accessData.current) { - markdown.appendMarkdown(nls.localize('requests count session', "Requests (Session) : `{0}`", accessData.current.count)); - markdown.appendText('\n'); - } - markdown.appendMarkdown(nls.localize('requests count total', "Requests (Overall): `{0}`", accessData.totalCount)); - } - return markdown; - } -} - -Registry.as(ExtensionFeaturesExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ - id: CHAT_FEATURE_ID, - label: nls.localize('chat', "Chat"), - description: nls.localize('chatFeatureDescription', "Allows the extension to make requests to the Large Language Model (LLM)."), - access: { - canToggle: false, - }, - renderer: new SyncDescriptor(ChatFeatureMarkdowneRenderer), -}); - const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); registerWorkbenchContribution2(ChatResolverContribution.ID, ChatResolverContribution, WorkbenchPhase.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(ChatAccessibleViewContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 5b4e36308a7..9a9c1196e72 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -311,4 +311,3 @@ export interface IChatService { } export const KEYWORD_ACTIVIATION_SETTING_ID = 'accessibility.voice.keywordActivation'; -export const CHAT_FEATURE_ID = 'chat'; diff --git a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index fc0f45125f8..413da693a6a 100644 --- a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -11,6 +11,7 @@ import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { Action, IAction, Separator } from 'vs/base/common/actions'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; +import { fromNow } from 'vs/base/common/date'; import { memoize } from 'vs/base/common/decorators'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; @@ -25,16 +26,19 @@ import { ExtensionIdentifier, ExtensionIdentifierMap, IExtensionDescription } fr import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { Registry } from 'vs/platform/registry/common/platform'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; +import { errorIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { DefaultIconPath, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation'; import { IExtensionHostProfile, IExtensionService, IExtensionsStatus } from 'vs/workbench/services/extensions/common/extensions'; @@ -83,6 +87,7 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { @ILabelService private readonly _labelService: ILabelService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @IClipboardService private readonly _clipboardService: IClipboardService, + @IExtensionFeaturesManagementService private readonly _extensionFeaturesManagementService: IExtensionFeaturesManagementService, ) { super(AbstractRuntimeExtensionsEditor.ID, telemetryService, themeService, storageService); @@ -91,6 +96,7 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { this._updateSoon = this._register(new RunOnceScheduler(() => this._updateExtensions(), 200)); this._register(this._extensionService.onDidChangeExtensionsStatus(() => this._updateSoon.schedule())); + this._register(this._extensionFeaturesManagementService.onDidChangeAccessData(() => this._updateSoon.schedule())); this._updateExtensions(); } @@ -400,6 +406,23 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { data.msgContainer.appendChild(el); } + const features = Registry.as(Extensions.ExtensionFeaturesRegistry).getExtensionFeatures(); + for (const feature of features) { + const accessData = this._extensionFeaturesManagementService.getAccessData(element.description.identifier, feature.id); + if (accessData) { + const status = accessData?.current?.status; + if (status) { + data.msgContainer.appendChild($('span', undefined, `${feature.label}: `)); + data.msgContainer.appendChild($('span', undefined, ...renderLabelWithIcons(`$(${status.severity === Severity.Error ? errorIcon.id : warningIcon.id}) ${status.message}`))); + } + if (accessData?.current) { + const element = $('span', undefined, nls.localize('requests count', "{0} Requests: {1} (Session)", feature.label, accessData.current.count)); + element.title = nls.localize('requests count title', "Last request was {0}. Overall Requests: {1}", fromNow(accessData.current.lastAccessed, true, true), accessData.totalCount); + data.msgContainer.appendChild(element); + } + } + } + if (element.profileInfo) { data.profileTime.textContent = `Profile: ${(element.profileInfo.totalTime / 1000).toFixed(2)}ms`; } else { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts index 71a1018af4d..a778ba34bdb 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts @@ -5,10 +5,10 @@ import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { $, append, clearNode } from 'vs/base/browser/dom'; -import { Event } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { ExtensionIdentifier, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { Orientation, Sizing, SplitView } from 'vs/base/browser/ui/splitview/splitview'; -import { IExtensionFeatureDescriptor, Extensions, IExtensionFeaturesRegistry, IExtensionFeatureRenderer, IExtensionFeaturesManagementService, IExtensionFeatureTableRenderer, IExtensionFeatureMarkdownRenderer, ITableData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { IExtensionFeatureDescriptor, Extensions, IExtensionFeaturesRegistry, IExtensionFeatureRenderer, IExtensionFeaturesManagementService, IExtensionFeatureTableRenderer, IExtensionFeatureMarkdownRenderer, ITableData, IRenderedData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { Registry } from 'vs/platform/registry/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { localize } from 'vs/nls'; @@ -19,7 +19,7 @@ import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/lis import { Button } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles, defaultKeybindingLabelStyles } from 'vs/platform/theme/browser/defaultStyles'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; -import { onUnexpectedError } from 'vs/base/common/errors'; +import { getErrorMessage, onUnexpectedError } from 'vs/base/common/errors'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { PANEL_SECTION_BORDER } from 'vs/workbench/common/theme'; import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; @@ -32,13 +32,119 @@ import { SeverityIcon } from 'vs/platform/severityIcon/browser/severityIcon'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { OS } from 'vs/base/common/platform'; -import { IMarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; +import { IMarkdownString, MarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; import { Color } from 'vs/base/common/color'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { Codicon } from 'vs/base/common/codicons'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { fromNow } from 'vs/base/common/date'; + +class RuntimeStatusMarkdownRenderer extends Disposable implements IExtensionFeatureMarkdownRenderer { + + static readonly ID = 'runtimeStatus'; + readonly type = 'markdown'; + + constructor( + @IExtensionService private readonly extensionService: IExtensionService, + @IExtensionFeaturesManagementService private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService, + ) { + super(); + } + + shouldRender(manifest: IExtensionManifest): boolean { + const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); + if (this.extensionService.extensions.some(e => ExtensionIdentifier.equals(e.identifier, extensionId))) { + return !!manifest.main || !!manifest.browser; + } + return !!manifest.activationEvents; + } + + render(manifest: IExtensionManifest): IRenderedData { + const disposables = new DisposableStore(); + const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); + const emitter = disposables.add(new Emitter()); + disposables.add(this.extensionService.onDidChangeExtensionsStatus(e => { + if (e.some(extension => ExtensionIdentifier.equals(extension, extensionId))) { + emitter.fire(this.getActivationData(manifest)); + } + })); + disposables.add(this.extensionFeaturesManagementService.onDidChangeAccessData(e => emitter.fire(this.getActivationData(manifest)))); + return { + onDidChange: emitter.event, + data: this.getActivationData(manifest), + dispose: () => disposables.dispose() + }; + } + + private getActivationData(manifest: IExtensionManifest): IMarkdownString { + const data = new MarkdownString(); + const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); + const status = this.extensionService.getExtensionsStatus()[extensionId.value]; + if (this.extensionService.extensions.some(extension => ExtensionIdentifier.equals(extension.identifier, extensionId))) { + data.appendMarkdown(`### ${localize('activation', "Activation")}\n\n`); + if (status.activationTimes) { + if (status.activationTimes.activationReason.startup) { + data.appendMarkdown(`Activated on Startup: \`${status.activationTimes.activateCallTime}ms\``); + } else { + data.appendMarkdown(`Activated by \`${status.activationTimes.activationReason.activationEvent}\` event: \`${status.activationTimes.activateCallTime}ms\``); + } + } else { + data.appendMarkdown('Not yet activated'); + } + if (status.runtimeErrors.length) { + data.appendMarkdown(`\n ### ${localize('uncaught errors', "Uncaught Errors ({0})", status.runtimeErrors.length)}\n`); + for (const error of status.runtimeErrors) { + data.appendMarkdown(`$(${Codicon.error.id}) ${getErrorMessage(error)}\n\n`); + } + } + if (status.messages.length) { + data.appendMarkdown(`\n ### ${localize('messaages', "Messages ({0})", status.messages.length)}\n`); + for (const message of status.messages) { + data.appendMarkdown(`$(${(message.type === Severity.Error ? Codicon.error : message.type === Severity.Warning ? Codicon.warning : Codicon.info).id}) ${message.message}\n\n`); + } + } + } + const features = Registry.as(Extensions.ExtensionFeaturesRegistry).getExtensionFeatures(); + for (const feature of features) { + const accessData = this.extensionFeaturesManagementService.getAccessData(extensionId, feature.id); + if (accessData) { + data.appendMarkdown(`\n ### ${feature.label}\n\n`); + const status = accessData?.current?.status; + if (status) { + if (status?.severity === Severity.Error) { + data.appendMarkdown(`$(${errorIcon.id}) ${status.message}\n\n`); + } + if (status?.severity === Severity.Warning) { + data.appendMarkdown(`$(${warningIcon.id}) ${status.message}\n\n`); + } + } + if (accessData?.totalCount) { + if (accessData.current) { + data.appendMarkdown(`${localize('last request', "Last Request: `{0}`", fromNow(accessData.current.lastAccessed, true, true))}\n\n`); + data.appendMarkdown(`${localize('requests count session', "Requests (Session) : `{0}`", accessData.current.count)}\n\n`); + } + data.appendMarkdown(`${localize('requests count total', "Requests (Overall): `{0}`", accessData.totalCount)}\n\n`); + } + } + } + return data; + } +} + interface ILayoutParticipant { layout(height?: number, width?: number): void; } +const runtimeStatusFeature = { + id: RuntimeStatusMarkdownRenderer.ID, + label: localize('runtime', "Runtime Status"), + access: { + canToggle: false + }, + renderer: new SyncDescriptor(RuntimeStatusMarkdownRenderer), +}; + export class ExtensionFeaturesTab extends Themable { readonly domNode: HTMLElement; @@ -162,17 +268,24 @@ export class ExtensionFeaturesTab extends Themable { } private getFeatures(): IExtensionFeatureDescriptor[] { - const features = Registry.as(Extensions.ExtensionFeaturesRegistry).getExtensionFeatures(); - return features.filter(feature => { - const renderer = this.getRenderer(feature); - const shouldRender = renderer.shouldRender(this.manifest); - renderer.dispose(); - return shouldRender; - }).sort((a, b) => a.label.localeCompare(b.label)); + const features = Registry.as(Extensions.ExtensionFeaturesRegistry) + .getExtensionFeatures().filter(feature => { + const renderer = this.getRenderer(feature); + const shouldRender = renderer?.shouldRender(this.manifest); + renderer?.dispose(); + return shouldRender; + }).sort((a, b) => a.label.localeCompare(b.label)); + + const renderer = this.getRenderer(runtimeStatusFeature); + if (renderer?.shouldRender(this.manifest)) { + features.splice(0, 0, runtimeStatusFeature); + } + renderer?.dispose(); + return features; } - private getRenderer(feature: IExtensionFeatureDescriptor): IExtensionFeatureRenderer { - return this.instantiationService.createInstance(feature.renderer); + private getRenderer(feature: IExtensionFeatureDescriptor): IExtensionFeatureRenderer | undefined { + return feature.renderer ? this.instantiationService.createInstance(feature.renderer) : undefined; } } @@ -210,7 +323,7 @@ class ExtensionFeatureItemRenderer implements IListRenderer { if (ExtensionIdentifier.equals(extension, this.extensionId) && featureId === element.id) { @@ -319,11 +432,13 @@ class ExtensionFeatureView extends Disposable { } const featureContentElement = append(bodyContent, $('.feature-content')); - const renderer = this.instantiationService.createInstance(this.feature.renderer); - if (renderer.type === 'table') { - this.renderTableData(featureContentElement, renderer); - } else if (renderer.type === 'markdown') { - this.renderMarkdownData(featureContentElement, renderer); + if (this.feature.renderer) { + const renderer = this.instantiationService.createInstance(this.feature.renderer); + if (renderer.type === 'table') { + this.renderTableData(featureContentElement, renderer); + } else if (renderer.type === 'markdown') { + this.renderMarkdownData(featureContentElement, renderer); + } } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index cd8a1b647cd..adcd04b081f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -36,7 +36,7 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IAction, Action, Separator, ActionRunner } from 'vs/base/common/actions'; -import { ExtensionIdentifierMap, ExtensionUntrustedWorkspaceSupportType, ExtensionVirtualWorkspaceSupportType, IExtensionDescription, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionUntrustedWorkspaceSupportType, ExtensionVirtualWorkspaceSupportType, IExtensionDescription, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs/base/common/async'; import { IProductService } from 'vs/platform/product/common/productService'; import { SeverityIcon } from 'vs/platform/severityIcon/browser/severityIcon'; @@ -55,6 +55,8 @@ import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { ILogService } from 'vs/platform/log/common/log'; import { isOfflineError } from 'vs/base/parts/request/common/request'; import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions, IExtensionFeatureRenderer, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; export const NONE_CATEGORY = 'none'; @@ -145,6 +147,7 @@ export class ExtensionsListView extends ViewPane { @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IExtensionFeaturesManagementService private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService, @ILogService private readonly logService: ILogService ) { super({ @@ -442,6 +445,10 @@ export class ExtensionsListView extends ViewPane { extensions = this.filterRecentlyUpdatedExtensions(local, query, options); } + else if (/@feature:/i.test(query.value)) { + extensions = this.filterExtensionsByFeature(local, query, options); + } + return { extensions, canIncludeInstalledExtensions }; } @@ -677,6 +684,23 @@ export class ExtensionsListView extends ViewPane { return this.sortExtensions(result, options); } + private filterExtensionsByFeature(local: IExtension[], query: Query, options: IQueryOptions): IExtension[] { + const value = query.value.replace(/@feature:/g, '').trim().toLowerCase(); + const featureId = value.split(' ')[0]; + const feature = Registry.as(Extensions.ExtensionFeaturesRegistry).getExtensionFeature(featureId); + if (!feature) { + return []; + } + const renderer = feature.renderer ? this.instantiationService.createInstance(feature.renderer) : undefined; + const result = local.filter(e => { + if (!e.local) { + return false; + } + return renderer?.shouldRender(e.local.manifest) || this.extensionFeaturesManagementService.getAccessData(new ExtensionIdentifier(e.identifier.id), featureId); + }); + return this.sortExtensions(result, options); + } + private mergeAddedExtensions(extensions: IExtension[], newExtensions: IExtension[]): IExtension[] | undefined { const oldExtensions = [...extensions]; const findPreviousExtensionIndex = (from: number): number => { @@ -1074,7 +1098,8 @@ export class ExtensionsListView extends ViewPane { || this.isSearchWorkspaceUnsupportedExtensionsQuery(query) || this.isSearchRecentlyUpdatedQuery(query) || this.isSearchExtensionUpdatesQuery(query) - || this.isSortInstalledExtensionsQuery(query, sortBy); + || this.isSortInstalledExtensionsQuery(query, sortBy) + || this.isFeatureExtensionsQuery(query); } static isSearchBuiltInExtensionsQuery(query: string): boolean { @@ -1098,7 +1123,7 @@ export class ExtensionsListView extends ViewPane { } static isSearchInstalledExtensionsQuery(query: string): boolean { - return /@installed\s./i.test(query); + return /@installed\s./i.test(query) || this.isFeatureExtensionsQuery(query); } static isOutdatedExtensionsQuery(query: string): boolean { @@ -1169,6 +1194,10 @@ export class ExtensionsListView extends ViewPane { return /@sort:updateDate/i.test(query); } + static isFeatureExtensionsQuery(query: string): boolean { + return /@feature:/i.test(query); + } + override focus(): void { super.focus(); if (!this.list) { @@ -1283,12 +1312,13 @@ export class StaticQueryExtensionsView extends ExtensionsListView { @IWorkspaceTrustManagementService workspaceTrustManagementService: IWorkspaceTrustManagementService, @IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IExtensionFeaturesManagementService extensionFeaturesManagementService: IExtensionFeaturesManagementService, @ILogService logService: ILogService ) { super(options, viewletViewOptions, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, extensionRecommendationsService, telemetryService, configurationService, contextService, extensionManagementServerService, extensionManifestPropertiesService, extensionManagementService, workspaceService, productService, contextKeyService, viewDescriptorService, openerService, - preferencesService, storageService, workspaceTrustManagementService, extensionEnablementService, layoutService, logService); + preferencesService, storageService, workspaceTrustManagementService, extensionEnablementService, layoutService, extensionFeaturesManagementService, logService); } override show(): Promise> { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index a54bd50bf16..93b10e5bb6e 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -880,5 +880,5 @@ } .extension-editor .subcontent.feature-contributions .extension-feature-content .feature-body .feature-body-content .feature-content.markdown .codicon { - vertical-align: bottom; + vertical-align: sub; } diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts index ad8794fb8c6..11dc035fb5b 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts @@ -29,6 +29,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { Schemas } from 'vs/base/common/network'; import { joinPath } from 'vs/base/common/resources'; +import { IExtensionFeaturesManagementService } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; export const IExtensionHostProfileService = createDecorator('extensionHostProfileService'); export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey('profileSessionState', 'none'); @@ -77,8 +78,9 @@ export class RuntimeExtensionsEditor extends AbstractRuntimeExtensionsEditor { @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IClipboardService clipboardService: IClipboardService, @IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService, + @IExtensionFeaturesManagementService extensionFeaturesManagementService: IExtensionFeaturesManagementService, ) { - super(telemetryService, themeService, contextKeyService, extensionsWorkbenchService, extensionService, notificationService, contextMenuService, instantiationService, storageService, labelService, environmentService, clipboardService); + super(telemetryService, themeService, contextKeyService, extensionsWorkbenchService, extensionService, notificationService, contextMenuService, instantiationService, storageService, labelService, environmentService, clipboardService, extensionFeaturesManagementService); this._profileInfo = this._extensionHostProfileService.lastProfile; this._extensionsHostRecorded = CONTEXT_EXTENSION_HOST_PROFILE_RECORDED.bindTo(contextKeyService); this._profileSessionState = CONTEXT_PROFILE_SESSION_STATE.bindTo(contextKeyService); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts b/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts index becb8e21f37..e72d5eee735 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts @@ -54,12 +54,12 @@ export interface IExtensionFeatureDescriptor { readonly requireUserConsent?: boolean; readonly extensionsList?: IStringDictionary; }; - readonly renderer: SyncDescriptor; + readonly renderer?: SyncDescriptor; } export interface IExtensionFeaturesRegistry { - registerExtensionFeature(descriptor: IExtensionFeatureDescriptor): void; + registerExtensionFeature(descriptor: IExtensionFeatureDescriptor): IDisposable; getExtensionFeature(id: string): IExtensionFeatureDescriptor | undefined; getExtensionFeatures(): ReadonlyArray; } @@ -93,11 +93,14 @@ class ExtensionFeaturesRegistry implements IExtensionFeaturesRegistry { private readonly extensionFeatures = new Map(); - registerExtensionFeature(descriptor: IExtensionFeatureDescriptor): void { + registerExtensionFeature(descriptor: IExtensionFeatureDescriptor): IDisposable { if (this.extensionFeatures.has(descriptor.id)) { throw new Error(`Extension feature with id '${descriptor.id}' already exists`); } this.extensionFeatures.set(descriptor.id, descriptor); + return { + dispose: () => this.extensionFeatures.delete(descriptor.id) + }; } getExtensionFeature(id: string): IExtensionFeatureDescriptor | undefined { diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index c44dc4d24d9..2d93e41d0f0 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -4,12 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Barrier } from 'vs/base/common/async'; -import { Codicon } from 'vs/base/common/codicons'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { getErrorMessage } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import * as perf from 'vs/base/common/performance'; import { isCI } from 'vs/base/common/platform'; @@ -20,7 +18,6 @@ import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ImplicitActivationEvents } from 'vs/platform/extensionManagement/common/implicitActivationEvents'; import { ExtensionIdentifier, ExtensionIdentifierMap, IExtension, IExtensionContributions, IExtensionDescription, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; @@ -1354,80 +1351,28 @@ class ActivationFeatureMarkdowneRenderer extends Disposable implements IExtensio readonly type = 'markdown'; - constructor( - @IExtensionService private readonly _extensionService: IExtensionService - ) { - super(); - } - shouldRender(manifest: IExtensionManifest): boolean { - const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); - if (this._extensionService.extensions.some(e => ExtensionIdentifier.equals(e.identifier, extensionId))) { - return !!manifest.main || !!manifest.browser; - } return !!manifest.activationEvents; } render(manifest: IExtensionManifest): IRenderedData { - const disposables = new DisposableStore(); - const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); - const emitter = disposables.add(new Emitter()); - this._extensionService.onDidChangeExtensionsStatus(e => { - if (e.some(extension => ExtensionIdentifier.equals(extension, extensionId))) { - emitter.fire(this.getActivationData(manifest)); - } - }); - return { - onDidChange: emitter.event, - data: this.getActivationData(manifest), - dispose: () => disposables.dispose() - }; - } - - private getActivationData(manifest: IExtensionManifest): IMarkdownString { + const activationEvents = manifest.activationEvents || []; const data = new MarkdownString(); - const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); - const status = this._extensionService.getExtensionsStatus()[extensionId.value]; - if (this._extensionService.extensions.some(extension => ExtensionIdentifier.equals(extension.identifier, extensionId))) { - if (status.activationTimes) { - if (status.activationTimes.activationReason.startup) { - data.appendText('Activated on startup in `') - .appendText(`${status.activationTimes.activateCallTime}ms`) - .appendText('`'); - } else { - data.appendMarkdown('Activated in `' + status.activationTimes.activateCallTime + 'ms` by `' + status.activationTimes.activationReason.activationEvent + '` event.'); - } - } else { - data.appendMarkdown('Not yet activated'); - } - if (status.runtimeErrors.length) { - data.appendMarkdown(`\n ### ${nls.localize('uncaught errors', "Uncaught Errors ({0})", status.runtimeErrors.length)}\n`); - for (const error of status.runtimeErrors) { - data.appendMarkdown(`$(${Codicon.error.id}) ${getErrorMessage(error)}\n\n`); - } - } - if (status.messages.length) { - data.appendMarkdown(`\n ### ${nls.localize('messaages', "Messages ({0})", status.messages.length)}\n`); - for (const message of status.messages) { - data.appendMarkdown(`$(${(message.type === Severity.Error ? Codicon.error : message.type === Severity.Warning ? Codicon.warning : Codicon.info).id}) ${message.message}\n\n`); - } - } - } else { - const activationEvents = manifest.activationEvents || []; - if (activationEvents.length) { - data.appendMarkdown(`### ${nls.localize('activation events', "Activation Events")}\n\n`); - for (const activationEvent of activationEvents) { - data.appendMarkdown(`- \`${activationEvent}\`\n`); - } + if (activationEvents.length) { + for (const activationEvent of activationEvents) { + data.appendMarkdown(`- \`${activationEvent}\`\n`); } } - return data; + return { + data, + dispose: () => { } + }; } } Registry.as(ExtensionFeaturesExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ - id: 'activation', - label: nls.localize('activation', "Activation"), + id: 'activationEvents', + label: nls.localize('activation', "Activation Events"), access: { canToggle: false }, From 7dd84ca58f2bb0a81b70851495d430a0c50aca18 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 8 Feb 2024 22:00:08 +0100 Subject: [PATCH 1038/1897] Breadcrumb hover (#204765) breadcrumbs hover --- .../parts/editor/breadcrumbsControl.ts | 24 ++++++++++++++++--- .../browser/outline/documentSymbolsTree.ts | 21 ++++++++++++++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 0eb27fcfcb6..b8b74e5aec1 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -40,6 +40,8 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { Codicon } from 'vs/base/common/codicons'; import { defaultBreadcrumbsWidgetStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Emitter } from 'vs/base/common/event'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverOptions, IHoverService } from 'vs/platform/hover/browser/hover'; class OutlineItem extends BreadcrumbsItem { @@ -108,7 +110,8 @@ class FileItem extends BreadcrumbsItem { readonly model: BreadcrumbsModel, readonly element: FileElement, readonly options: IBreadcrumbsControlOptions, - private readonly _labels: ResourceLabels + private readonly _labels: ResourceLabels, + private readonly _hoverDelegate: IHoverDelegate ) { super(); } @@ -129,7 +132,7 @@ class FileItem extends BreadcrumbsItem { render(container: HTMLElement): void { // file/folder - const label = this._labels.create(container); + const label = this._labels.create(container, { hoverDelegate: this._hoverDelegate }); label.setFile(this.element.uri, { hidePath: true, hideIcon: this.element.kind === FileKind.FOLDER || !this.options.showFileIcons, @@ -186,6 +189,8 @@ export class BreadcrumbsControl { private _breadcrumbsPickerShowing = false; private _breadcrumbsPickerIgnoreOnceItem: BreadcrumbsItem | undefined; + private readonly _hoverDelegate: IHoverDelegate; + private readonly _onDidVisibilityChange = this._disposables.add(new Emitter()); get onDidVisibilityChange() { return this._onDidVisibilityChange.event; } @@ -202,6 +207,7 @@ export class BreadcrumbsControl { @ILabelService private readonly _labelService: ILabelService, @IConfigurationService configurationService: IConfigurationService, @IBreadcrumbsService breadcrumbsService: IBreadcrumbsService, + @IHoverService private readonly hoverService: IHoverService ) { this.domNode = document.createElement('div'); this.domNode.classList.add('breadcrumbs-control'); @@ -224,6 +230,18 @@ export class BreadcrumbsControl { this._ckBreadcrumbsVisible = BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo(this._contextKeyService); this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService); + this._hoverDelegate = { + delay: 500, + showHover: (options: IHoverOptions) => { + return this.hoverService.showHover({ + ...options, + persistence: { + hideOnHover: true + } + }); + } + }; + this._disposables.add(breadcrumbsService.register(this._editorGroup.id, this._widget)); this.hide(); } @@ -321,7 +339,7 @@ export class BreadcrumbsControl { showFileIcons: this._options.showFileIcons && showIcons, showSymbolIcons: this._options.showSymbolIcons && showIcons }; - const items = model.getElements().map(element => element instanceof FileElement ? new FileItem(model, element, options, this._labels) : new OutlineItem(model, element, options)); + const items = model.getElements().map(element => element instanceof FileElement ? new FileItem(model, element, options, this._labels, this._hoverDelegate) : new OutlineItem(model, element, options)); if (items.length === 0) { this._widget.setEnabled(false); this._widget.setItems([new class extends BreadcrumbsItem { diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index 437a260752e..8434fa5acf3 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -24,6 +24,8 @@ import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IOutlineComparator, OutlineConfigKeys } from 'vs/workbench/services/outline/browser/outline'; import { ThemeIcon } from 'vs/base/common/themables'; import { mainWindow } from 'vs/base/browser/window'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverOptions, IHoverService } from 'vs/platform/hover/browser/hover'; export type DocumentSymbolItem = OutlineGroup | OutlineElement; @@ -115,15 +117,30 @@ export class DocumentSymbolRenderer implements ITreeRenderer { + return hoverService.showHover({ + ...options, + persistence: { + hideOnHover: true + } + }); + } + }; + } renderTemplate(container: HTMLElement): DocumentSymbolTemplate { container.classList.add('outline-element'); - const iconLabel = new IconLabel(container, { supportHighlights: true }); + const iconLabel = new IconLabel(container, { supportHighlights: true, hoverDelegate: this._hoverDelegate }); const iconClass = dom.$('.outline-element-icon'); const decoration = dom.$('.outline-element-decoration'); container.prepend(iconClass); From 94799a61c93dbe183eee1af84d9e15b056199ddb Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 8 Feb 2024 22:00:48 +0100 Subject: [PATCH 1039/1897] Editor tabs Align hover functionality (#204767) align hover --- .../browser/parts/editor/editorTabsControl.ts | 25 ++----------------- .../parts/editor/multiEditorTabsControl.ts | 2 +- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index 058d0a91a92..651a9b3ff16 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -44,9 +44,6 @@ import { IAuxiliaryEditorPart, MergeGroupMode } from 'vs/workbench/services/edit import { isMacintosh } from 'vs/base/common/platform'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { MarkdownString, MarkdownStringTextNewlineStyle } from 'vs/base/common/htmlContent'; -import { ITooltipMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { IDecorationsService } from 'vs/workbench/services/decorations/common/decorations'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { IHoverService } from 'vs/platform/hover/browser/hover'; @@ -141,7 +138,6 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC @IThemeService themeService: IThemeService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService, @IHostService private readonly hostService: IHostService, - @IDecorationsService private readonly decorationsService: IDecorationsService, @IHoverService private readonly hoverService: IHoverService ) { super(themeService); @@ -451,25 +447,8 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC return this.groupsView.partOptions.tabHeight !== 'compact' ? EditorTabsControl.EDITOR_TAB_HEIGHT.normal : EditorTabsControl.EDITOR_TAB_HEIGHT.compact; } - protected getHoverTitle(editor: EditorInput): ITooltipMarkdownString { - const title = editor.getTitle(Verbosity.LONG); - const markdown = new MarkdownString(title); - - if (editor.resource) { - const decoration = this.decorationsService.getDecoration(editor.resource, false); - if (decoration) { - const decorations = decoration.tooltip.split('• '); - const decorationString = `• ${decorations.join('\n• ')}`; - - markdown.appendText('\n', MarkdownStringTextNewlineStyle.Paragraph); - markdown.appendText(decorationString, MarkdownStringTextNewlineStyle.Break); - } - } - - return { - markdown, - markdownNotSupportedFallback: title - }; + protected getHoverTitle(editor: EditorInput): string { + return editor.getTitle(Verbosity.LONG); } protected getHoverDelegate(): IHoverDelegate { diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index d79a5be26c3..2f613acd93f 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -155,7 +155,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { @IDecorationsService decorationsService: IDecorationsService, @IHoverService hoverService: IHoverService, ) { - super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService, hostService, decorationsService, hoverService); + super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService, hostService, hoverService); // Resolve the correct path library for the OS we are on // If we are connected to remote, this accounts for the From 73058072bf1b761b5e0ed0e551b2969d33093dbe Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 8 Feb 2024 13:10:06 -0800 Subject: [PATCH 1040/1897] track when LM request is made (#204773) --- src/vs/workbench/api/browser/mainThreadChatProvider.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index 76001d04960..dcf7e7ab3e0 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -73,14 +73,12 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { } async $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise { - const access = await this._extensionFeaturesManagementService.getAccess(extension, `lm-${providerId}`, justification); - if (!access) { - return undefined; - } return this._chatProviderService.lookupChatResponseProvider(providerId); } async $fetchResponse(extension: ExtensionIdentifier, providerId: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise { + await this._extensionFeaturesManagementService.getAccess(extension, `lm-${providerId}`); + this._logService.debug('[CHAT] extension request STARTED', extension.value, requestId); const task = this._chatProviderService.fetchChatResponse(providerId, extension, messages, options, new Progress(value => { From bcf9b4ff0ff453a72d01f4bceaea05e166f347c6 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 8 Feb 2024 14:14:55 -0700 Subject: [PATCH 1041/1897] Skip extension smoketest (#204775) --- test/smoke/src/areas/extensions/extensions.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/smoke/src/areas/extensions/extensions.test.ts b/test/smoke/src/areas/extensions/extensions.test.ts index c78cbe87089..8a7522c6ea7 100644 --- a/test/smoke/src/areas/extensions/extensions.test.ts +++ b/test/smoke/src/areas/extensions/extensions.test.ts @@ -12,7 +12,7 @@ export function setup(logger: Logger) { // Shared before/after handling installAllHandlers(logger); - it('install and enable vscode-smoketest-check extension', async function () { + it.skip('install and enable vscode-smoketest-check extension', async function () { const app = this.app as Application; await app.workbench.extensions.installExtension('ms-vscode.vscode-smoketest-check', true); From 90cebfaeb260263075f88dc2a8b97658942a09d7 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:17:58 -0600 Subject: [PATCH 1042/1897] Fix fuzzy searching for findFiles2 (#204768) * progress on making fuzzy option * finish connection to findfiles API --- .../src/singlefolder-tests/workspace.test.ts | 10 ++++------ src/vs/workbench/api/common/extHostWorkspace.ts | 1 + .../workbench/services/search/common/queryBuilder.ts | 2 ++ src/vs/workbench/services/search/common/search.ts | 12 ++++++++++-- src/vs/workbench/services/search/node/fileSearch.ts | 4 +++- src/vscode-dts/vscode.proposed.findFiles2.d.ts | 6 ++++++ 6 files changed, 26 insertions(+), 9 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index b867766d027..11caa87618d 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -598,9 +598,8 @@ suite('vscode API - workspace', () => { }); test('`findFiles2`', () => { - return vscode.workspace.findFiles2('*image.png').then((res) => { - assert.strictEqual(res.length, 4); - // TODO: see why this is fuzzy matching + return vscode.workspace.findFiles2('**/image.png').then((res) => { + assert.strictEqual(res.length, 2); }); }); @@ -619,9 +618,8 @@ suite('vscode API - workspace', () => { }); test('findFiles2, exclude', () => { - return vscode.workspace.findFiles2('*image.png', { exclude: '**/sub/**' }).then((res) => { - assert.strictEqual(res.length, 3); - // TODO: see why this is fuzzy matching + return vscode.workspace.findFiles2('**/image.png', { exclude: '**/sub/**' }).then((res) => { + assert.strictEqual(res.length, 1); }); }); diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index dcb32598916..65cee0c4faa 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -497,6 +497,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac disregardSearchExcludeSettings: typeof options.useDefaultSearchExcludes === 'boolean' ? !options.useDefaultSearchExcludes : false, maxResults: options.maxResults, excludePattern: excludePattern, + shouldGlobSearch: typeof options.fuzzy === 'boolean' ? !options.fuzzy : true, _reason: 'startFileSearch' }; let folderToUse: URI | undefined; diff --git a/src/vs/workbench/services/search/common/queryBuilder.ts b/src/vs/workbench/services/search/common/queryBuilder.ts index 78b4d0c9227..9124249957a 100644 --- a/src/vs/workbench/services/search/common/queryBuilder.ts +++ b/src/vs/workbench/services/search/common/queryBuilder.ts @@ -72,6 +72,7 @@ export interface IFileQueryBuilderOptions extends ICommonQueryBuilderOptions { exists?: boolean; sortByScore?: boolean; cacheKey?: string; + shouldGlobSearch?: boolean; } export interface ITextQueryBuilderOptions extends ICommonQueryBuilderOptions { @@ -188,6 +189,7 @@ export class QueryBuilder { exists: options.exists, sortByScore: options.sortByScore, cacheKey: options.cacheKey, + shouldGlobMatchFilePattern: options.shouldGlobSearch }; } diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 7803e26cb47..ca93adc467b 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -81,6 +81,8 @@ export interface ICommonQueryProps { _reason?: string; folderQueries: IFolderQuery[]; + // The include pattern for files that gets passed into ripgrep. + // Note that this will override any ignore files if applicable. includePattern?: glob.IExpression; excludePattern?: glob.IExpression; extraFileResources?: U[]; @@ -95,6 +97,10 @@ export interface IFileQueryProps extends ICommonQueryPr type: QueryType.File; filePattern?: string; + // when walking through the tree to find the result, don't use the filePattern to fuzzy match. + // Instead, should use glob matching. + shouldGlobMatchFilePattern?: boolean; + /** * If true no results will be returned. Instead `limitHit` will indicate if at least one result exists or not. * Currently does not work with queries including a 'siblings clause'. @@ -586,9 +592,11 @@ export function isSerializedFileMatch(arg: ISerializedSearchProgressItem): arg i return !!(arg).path; } -export function isFilePatternMatch(candidate: IRawFileMatch, normalizedFilePatternLowercase: string): boolean { +export function isFilePatternMatch(candidate: IRawFileMatch, filePatternToUse: string, fuzzy = true): boolean { const pathToMatch = candidate.searchPath ? candidate.searchPath : candidate.relativePath; - return fuzzyContains(pathToMatch, normalizedFilePatternLowercase); + return fuzzy ? + fuzzyContains(pathToMatch, filePatternToUse) : + glob.match(filePatternToUse, pathToMatch); } export interface ISerializedFileMatch { diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index 3abfea8d4f3..9b372b8dedb 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -75,7 +75,7 @@ export class FileWalker { this.errors = []; if (this.filePattern) { - this.normalizedFilePatternLowercase = prepareQuery(this.filePattern).normalizedLowercase; + this.normalizedFilePatternLowercase = config.shouldGlobMatchFilePattern ? null : prepareQuery(this.filePattern).normalizedLowercase; } this.globalExcludePattern = config.excludePattern && glob.parse(config.excludePattern); @@ -579,6 +579,8 @@ export class FileWalker { if (this.normalizedFilePatternLowercase) { return isFilePatternMatch(candidate, this.normalizedFilePatternLowercase); + } else if (this.filePattern) { + return isFilePatternMatch(candidate, this.filePattern, false); } } diff --git a/src/vscode-dts/vscode.proposed.findFiles2.d.ts b/src/vscode-dts/vscode.proposed.findFiles2.d.ts index 45d734153a2..37743a897da 100644 --- a/src/vscode-dts/vscode.proposed.findFiles2.d.ts +++ b/src/vscode-dts/vscode.proposed.findFiles2.d.ts @@ -55,6 +55,12 @@ declare module 'vscode' { * See the vscode setting `"search.followSymlinks"`. */ followSymlinks?: boolean; + + /** + * If set to true, the `filePattern` arg will be fuzzy-searched instead of glob-searched. + * If `filePattern` is a `GlobPattern`, then the fuzzy search will act on the `pattern` of the `RelativePattern` + */ + fuzzy?: boolean; } /** From 4217d852aeb1f6fbb920063257ee351de125d92c Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 8 Feb 2024 15:11:30 -0800 Subject: [PATCH 1043/1897] Make chat code blocks non-simple (#204780) This enables some features we don't want but we want to test it in insiders to see the impact --- src/vs/workbench/contrib/chat/browser/codeBlockPart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index a68207d86c8..17b169bc4f1 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -380,7 +380,7 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart protected override createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): CodeEditorWidget { return this._register(instantiationService.createInstance(CodeEditorWidget, parent, options, { - isSimpleWidget: true, + isSimpleWidget: false, contributions: EditorExtensionsRegistry.getSomeEditorContributions([ MenuPreventer.ID, SelectionClipboardContributionID, From ba3f30b358e0fbeeb3625d1d90ada81252a97000 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 8 Feb 2024 16:29:02 -0800 Subject: [PATCH 1044/1897] Display trusted auth extensions (#204785) * Display trusted auth extensions To do this, the product.json entry takes a new shape... specifying which extensions use which auth providers. * fix bug * fix tests --- src/vs/base/common/product.ts | 2 +- .../extHostAuthentication.integrationTest.ts | 4 +- .../browser/authenticationService.ts | 181 +++++++++++++----- 3 files changed, 132 insertions(+), 55 deletions(-) diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index cbd573fdc72..3a57612651d 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -113,7 +113,7 @@ export interface IProductConfiguration { readonly webExtensionTips?: readonly string[]; readonly languageExtensionTips?: readonly string[]; readonly trustedExtensionUrlPublicKeys?: IStringDictionary; - readonly trustedExtensionAuthAccess?: readonly string[]; + readonly trustedExtensionAuthAccess?: string[] | IStringDictionary; readonly trustedExtensionProtocolHandlers?: readonly string[]; readonly commandPaletteSuggestedCommandIds?: string[]; diff --git a/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts b/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts index 25455816095..5e4e8ed1510 100644 --- a/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts +++ b/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts @@ -24,9 +24,10 @@ import { IExtensionService, nullExtensionDescription as extensionDescription } f import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { TestRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; import { TestEnvironmentService, TestQuickInputService, TestRemoteAgentService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { TestActivityService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; +import { TestActivityService, TestExtensionService, TestProductService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; import type { AuthenticationProvider, AuthenticationSession } from 'vscode'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; +import { IProductService } from 'vs/platform/product/common/productService'; class AuthQuickPick { private listener: ((e: IQuickPickDidAcceptEvent) => any) | undefined; @@ -111,6 +112,7 @@ suite('ExtHostAuthentication', () => { instantiationService.stub(INotificationService, new TestNotificationService()); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IBrowserWorkbenchEnvironmentService, TestEnvironmentService); + instantiationService.stub(IProductService, TestProductService); const rpcProtocol = new TestRPCProtocol(); instantiationService.stub(IAuthenticationService, instantiationService.createInstance(AuthenticationService)); diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 401fb69b0e7..78963a374a1 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -19,7 +19,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Severity } from 'vs/platform/notification/common/notification'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { ISecretStorageService } from 'vs/platform/secrets/common/secrets'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; @@ -107,22 +107,13 @@ export async function getCurrentAuthenticationSessionInfo( return undefined; } -export interface AllowedExtension { +interface AllowedExtension { id: string; name: string; allowed?: boolean; -} - -function readAllowedExtensions(storageService: IStorageService, providerId: string, accountName: string): AllowedExtension[] { - let trustedExtensions: AllowedExtension[] = []; - try { - const trustedExtensionSrc = storageService.get(`${providerId}-${accountName}`, StorageScope.APPLICATION); - if (trustedExtensionSrc) { - trustedExtensions = JSON.parse(trustedExtensionSrc); - } - } catch (err) { } - - return trustedExtensions; + lastUsed?: number; + // If true, this comes from the product.json + trusted?: boolean; } // OAuth2 spec prohibits space in a scope, so use that to join them. @@ -439,24 +430,26 @@ export class AuthenticationService extends Disposable implements IAuthentication * if they haven't made a choice yet */ isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean | undefined { - const allowList = readAllowedExtensions(this.storageService, providerId, accountName); - const extensionData = allowList.find(extension => extension.id === extensionId); - if (extensionData) { - // This property didn't exist on this data previously, inclusion in the list at all indicates allowance - return extensionData.allowed !== undefined - ? extensionData.allowed - : true; - } - - if (this.productService.trustedExtensionAuthAccess?.includes(extensionId)) { + const trustedExtensionAuthAccess = this.productService.trustedExtensionAuthAccess; + if (Array.isArray(trustedExtensionAuthAccess)) { + return trustedExtensionAuthAccess.includes(extensionId) ?? undefined; + } else if (trustedExtensionAuthAccess?.[providerId]?.includes(extensionId)) { return true; } - return undefined; + const allowList = this.readAllowedExtensions(providerId, accountName); + const extensionData = allowList.find(extension => extension.id === extensionId); + if (!extensionData) { + return undefined; + } + // This property didn't exist on this data previously, inclusion in the list at all indicates allowance + return extensionData.allowed !== undefined + ? extensionData.allowed + : true; } updateAllowedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string, isAllowed: boolean): void { - const allowList = readAllowedExtensions(this.storageService, providerId, accountName); + const allowList = this.readAllowedExtensions(providerId, accountName); const index = allowList.findIndex(extension => extension.id === extensionId); if (index === -1) { allowList.push({ id: extensionId, name: extensionName, allowed: isAllowed }); @@ -823,67 +816,149 @@ export class AuthenticationService extends Disposable implements IAuthentication } } + private readAllowedExtensions(providerId: string, accountName: string): AllowedExtension[] { + let trustedExtensions: AllowedExtension[] = []; + try { + const trustedExtensionSrc = this.storageService.get(`${providerId}-${accountName}`, StorageScope.APPLICATION); + if (trustedExtensionSrc) { + trustedExtensions = JSON.parse(trustedExtensionSrc); + } + } catch (err) { } + + return trustedExtensions; + } + + // TODO: pull this out into an Action in a contribution async manageTrustedExtensionsForAccount(id: string, accountName: string): Promise { const authProvider = this._authenticationProviders.get(id); if (!authProvider) { throw new Error(`No authentication provider '${id}' is currently registered.`); } - const allowedExtensions = readAllowedExtensions(this.storageService, authProvider.id, accountName); + + const allowedExtensions = this.readAllowedExtensions(authProvider.id, accountName); + const trustedExtensionAuthAccess = this.productService.trustedExtensionAuthAccess; + const trustedExtensionIds = + // Case 1: trustedExtensionAuthAccess is an array + Array.isArray(trustedExtensionAuthAccess) + ? trustedExtensionAuthAccess + // Case 2: trustedExtensionAuthAccess is an object + : typeof trustedExtensionAuthAccess === 'object' + ? trustedExtensionAuthAccess[authProvider.id] ?? [] + : []; + for (const extensionId of trustedExtensionIds) { + const allowedExtension = allowedExtensions.find(ext => ext.id === extensionId); + if (!allowedExtension) { + // Add the extension to the allowedExtensions list + const extension = await this.extensionService.getExtension(extensionId); + if (extension) { + allowedExtensions.push({ + id: extensionId, + name: extension.displayName || extension.name, + allowed: true, + trusted: true + }); + } + } else { + // Update the extension to be allowed + allowedExtension.allowed = true; + allowedExtension.trusted = true; + } + } if (!allowedExtensions.length) { this.dialogService.info(nls.localize('noTrustedExtensions', "This account has not been used by any extensions.")); return; } - type TrustedExtensionsQuickPickItem = { - label: string; - description: string; + interface TrustedExtensionsQuickPickItem extends IQuickPickItem { extension: AllowedExtension; - }; - const quickPick = this.quickInputService.createQuickPick(); + lastUsed?: number; + } + + const disposableStore = new DisposableStore(); + const quickPick = disposableStore.add(this.quickInputService.createQuickPick()); quickPick.canSelectMany = true; quickPick.customButton = true; quickPick.customLabel = nls.localize('manageTrustedExtensions.cancel', 'Cancel'); const usages = readAccountUsages(this.storageService, authProvider.id, accountName); - const items = allowedExtensions.map(extension => { + const trustedExtensions = []; + const otherExtensions = []; + for (const extension of allowedExtensions) { const usage = usages.find(usage => extension.id === usage.extensionId); + extension.lastUsed = usage?.lastUsed; + if (extension.trusted) { + trustedExtensions.push(extension); + } else { + otherExtensions.push(extension); + } + } + + const sortByLastUsed = (a: AllowedExtension, b: AllowedExtension) => (b.lastUsed || 0) - (a.lastUsed || 0); + const toQuickPickItem = function (extension: AllowedExtension) { + const lastUsed = extension.lastUsed; + const description = lastUsed + ? nls.localize({ key: 'accountLastUsedDate', comment: ['The placeholder {0} is a string with time information, such as "3 days ago"'] }, "Last used this account {0}", fromNow(lastUsed, true)) + : nls.localize('notUsed', "Has not used this account"); + let tooltip: string | undefined; + if (extension.trusted) { + tooltip = nls.localize('trustedExtensionTooltip', "This extension is trusted by Microsoft and has access to this account"); + } return { label: extension.name, - description: usage - ? nls.localize({ key: 'accountLastUsedDate', comment: ['The placeholder {0} is a string with time information, such as "3 days ago"'] }, "Last used this account {0}", fromNow(usage.lastUsed, true)) - : nls.localize('notUsed', "Has not used this account"), - extension + extension, + description, + tooltip }; - }); + }; + const items: Array = [ + ...otherExtensions.sort(sortByLastUsed).map(toQuickPickItem), + { type: 'separator', label: nls.localize('trustedExtensions', "Trusted by Microsoft") }, + ...trustedExtensions.sort(sortByLastUsed).map(toQuickPickItem) + ]; quickPick.items = items; - quickPick.selectedItems = items.filter(item => item.extension.allowed === undefined || item.extension.allowed); + quickPick.selectedItems = items.filter((item): item is TrustedExtensionsQuickPickItem => item.type !== 'separator' && (item.extension.allowed === undefined || item.extension.allowed)); quickPick.title = nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"); quickPick.placeholder = nls.localize('manageExtensions', "Choose which extensions can access this account"); - quickPick.onDidAccept(() => { - const updatedAllowedList = quickPick.items.map(i => (i as TrustedExtensionsQuickPickItem).extension); + disposableStore.add(quickPick.onDidAccept(() => { + const updatedAllowedList = quickPick.items + .filter((item): item is TrustedExtensionsQuickPickItem => item.type !== 'separator') + .map(i => i.extension); this.storageService.store(`${authProvider.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.APPLICATION, StorageTarget.USER); - quickPick.dispose(); - }); + quickPick.hide(); + })); - quickPick.onDidChangeSelection((changed) => { + disposableStore.add(quickPick.onDidChangeSelection((changed) => { + const trustedItems = new Set(); quickPick.items.forEach(item => { - if ((item as TrustedExtensionsQuickPickItem).extension) { - (item as TrustedExtensionsQuickPickItem).extension.allowed = false; + const trustItem = item as TrustedExtensionsQuickPickItem; + if (trustItem.extension) { + if (trustItem.extension.trusted) { + trustedItems.add(trustItem); + } else { + trustItem.extension.allowed = false; + } } }); + changed.forEach((item) => { + item.extension.allowed = true; + trustedItems.delete(item); + }); - changed.forEach((item) => item.extension.allowed = true); - }); + // reselect trusted items if a user tried to unselect one since quick pick doesn't support forcing selection + if (trustedItems.size) { + quickPick.selectedItems = [...changed, ...trustedItems]; + } + })); - quickPick.onDidHide(() => { - quickPick.dispose(); - }); + disposableStore.add(quickPick.onDidHide(() => { + disposableStore.dispose(); + })); - quickPick.onDidCustom(() => { + disposableStore.add(quickPick.onDidCustom(() => { quickPick.hide(); - }); + })); quickPick.show(); } From 820bfcb5251e322c67d31a07ef086bcae940fe51 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 8 Feb 2024 22:41:59 -0300 Subject: [PATCH 1045/1897] Fix converting tree data (#204791) --- src/vs/workbench/api/common/extHostTypeConverters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 6237bc44823..faafa59f8f0 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2376,7 +2376,7 @@ export namespace ChatResponseFilesPart { }; } export function from(part: Dto): vscode.ChatResponseFileTreePart { - const { treeData } = revive(part.treeData); + const treeData = revive(part.treeData); function convert(items: extHostProtocol.IChatResponseProgressFileTreeData[]): vscode.ChatResponseFileTree[] { return items.map(item => { return { From 8fa84589eef3538dbc763ff98dc7d5a8a0c56374 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Thu, 8 Feb 2024 18:57:55 -0800 Subject: [PATCH 1046/1897] show variable view only when applicable (#204789) * only initialize view if there is a variable provider available * use context key to show/hide the view --- .../notebookVariableContextKeys.ts | 8 +++ .../notebookVariables/notebookVariables.ts | 63 +++++++++++++++---- .../notebookVariablesDataSource.ts | 6 +- 3 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableContextKeys.ts diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableContextKeys.ts new file mode 100644 index 00000000000..b90769ef43c --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableContextKeys.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; + +export const NOTEBOOK_VARIABLE_VIEW_ENABLED = new RawContextKey('notebookVariableViewEnabled', false); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts index 962f144cc70..0da62de50b4 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts @@ -9,40 +9,75 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions, IViewContainersRegistry, IViewsRegistry } from 'vs/workbench/common/views'; import { VIEWLET_ID as debugContainerId } from 'vs/workbench/contrib/debug/common/debug'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { NotebookVariablesView } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView'; -import { NOTEBOOK_KERNEL } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { variablesViewIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { NOTEBOOK_VARIABLE_VIEW_ENABLED } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableContextKeys'; export class NotebookVariables extends Disposable implements IWorkbenchContribution { private listeners: IDisposable[] = []; + private configListener: IDisposable; + private initialized = false; + + private viewEnabled: IContextKey; constructor( + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, - @IConfigurationService configurationService: IConfigurationService, - @INotebookExecutionStateService private readonly notebookExecutionStateService: INotebookExecutionStateService + @INotebookExecutionStateService private readonly notebookExecutionStateService: INotebookExecutionStateService, + @INotebookKernelService private readonly notebookKernelService: INotebookKernelService ) { super(); - this.listeners.push(this.editorService.onDidEditorsChange(() => this.handleInitEvent(configurationService))); - this.listeners.push(this.notebookExecutionStateService.onDidChangeExecution(() => this.handleInitEvent(configurationService))); + this.viewEnabled = NOTEBOOK_VARIABLE_VIEW_ENABLED.bindTo(contextKeyService); + + this.listeners.push(this.editorService.onDidEditorsChange(() => this.handleInitEvent())); + this.listeners.push(this.notebookExecutionStateService.onDidChangeExecution(() => this.handleInitEvent())); + + this.configListener = configurationService.onDidChangeConfiguration((e) => this.handleConfigChange(e)); } - private handleInitEvent(configurationService: IConfigurationService) { - if (configurationService.getValue(NotebookSetting.notebookVariablesView) + private handleConfigChange(e: IConfigurationChangeEvent) { + if (e.affectsConfiguration(NotebookSetting.notebookVariablesView)) { + if (!this.configurationService.getValue(NotebookSetting.notebookVariablesView)) { + this.viewEnabled.set(false); + } else if (this.initialized) { + this.viewEnabled.set(true); + } else { + this.handleInitEvent(); + } + } + } + + private handleInitEvent() { + if (this.configurationService.getValue(NotebookSetting.notebookVariablesView) && this.editorService.activeEditorPane?.getId() === 'workbench.editor.notebook') { - if (this.initializeView()) { + + if (this.hasVariableProvider()) { + this.viewEnabled.set(true); + } + + if (!this.initialized && this.initializeView()) { + this.initialized = true; this.listeners.forEach(listener => listener.dispose()); } } } + private hasVariableProvider() { + const notebookDocument = getNotebookEditorFromEditorPane(this.editorService.activeEditorPane)?.getViewModel()?.notebookDocument; + return notebookDocument && this.notebookKernelService.getMatchingKernel(notebookDocument).selected?.hasVariableProvider; + } + private initializeView() { const debugViewContainer = Registry.as('workbench.registry.view.containers').get(debugContainerId); @@ -51,7 +86,7 @@ export class NotebookVariables extends Disposable implements IWorkbenchContribut const viewDescriptor = { id: 'NOTEBOOK_VARIABLES', name: nls.localize2('notebookVariables', "Notebook Variables"), containerIcon: variablesViewIcon, ctorDescriptor: new SyncDescriptor(NotebookVariablesView), - order: 50, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: ContextKeyExpr.notEquals(NOTEBOOK_KERNEL.key, ''), + order: 50, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: NOTEBOOK_VARIABLE_VIEW_ENABLED, }; viewsRegistry.registerViews([viewDescriptor], debugViewContainer); @@ -61,4 +96,10 @@ export class NotebookVariables extends Disposable implements IWorkbenchContribut return false; } + override dispose(): void { + super.dispose(); + this.listeners.forEach(listener => listener.dispose()); + this.configListener.dispose(); + } + } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts index 035e5b0ee7f..2ca03e7798e 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts @@ -53,7 +53,7 @@ export class NotebookVariableDataSource implements IAsyncDataSource { + private async getVariables(parent: INotebookVariableElement): Promise { const selectedKernel = this.notebookKernelService.getMatchingKernel(parent.notebook).selected; if (selectedKernel && selectedKernel.hasVariableProvider) { @@ -75,7 +75,7 @@ export class NotebookVariableDataSource implements IAsyncDataSource variablePageSize) { @@ -128,7 +128,7 @@ export class NotebookVariableDataSource implements IAsyncDataSource { + private async getRootVariables(notebook: NotebookTextModel): Promise { const selectedKernel = this.notebookKernelService.getMatchingKernel(notebook).selected; if (selectedKernel && selectedKernel.hasVariableProvider) { const variables = selectedKernel.provideVariables(notebook.uri, undefined, 'named', 0, this.cancellationTokenSource.token); From d1dc4e46ff8b42bb2d86980034f7b61f1edbc961 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 07:01:36 +0100 Subject: [PATCH 1047/1897] Voice: support prompts, like `@workSpace` or `/explain` (fix #201220) --- .../contrib/chat/browser/chat.contribution.ts | 2 + .../contrib/chat/common/voiceChat.ts | 108 ++++++++++++++++++ .../actions/voiceChatActions.ts | 5 +- .../electron-sandbox/inlineChatQuickVoice.ts | 7 +- 4 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/common/voiceChat.ts diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 5a9d5ef5cf8..52871880c7c 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -58,6 +58,7 @@ import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/c import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IVoiceChatService, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -310,3 +311,4 @@ registerSingleton(IChatProviderService, ChatProviderService, InstantiationType.D registerSingleton(IChatSlashCommandService, ChatSlashCommandService, InstantiationType.Delayed); registerSingleton(IChatAgentService, ChatAgentService, InstantiationType.Delayed); registerSingleton(IChatVariablesService, ChatVariablesService, InstantiationType.Delayed); +registerSingleton(IVoiceChatService, VoiceChatService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts new file mode 100644 index 00000000000..e68335616fa --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -0,0 +1,108 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Emitter } from 'vs/base/common/event'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { startsWithIgnoreCase } from 'vs/base/common/strings'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ISpeechService, ISpeechToTextEvent, ISpeechToTextSession, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; + +export const IVoiceChatService = createDecorator('voiceChatService'); + +export interface IVoiceChatService { + + readonly _serviceBrand: undefined; + + createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession; +} + +export class VoiceChatService extends Disposable implements IVoiceChatService { + + readonly _serviceBrand: undefined; + + private static readonly AGENT_PREFIX = chatAgentLeader; + private static readonly COMMAND_PREFIX = chatSubcommandLeader; + + private static readonly PHRASES = { + [VoiceChatService.AGENT_PREFIX]: 'at ', + [VoiceChatService.COMMAND_PREFIX]: 'slash ' + }; + + private static readonly CHAT_AGENT_ALIAS = new Map([['vscode', 'code']]); + + constructor( + @ISpeechService private readonly speechService: ISpeechService, + @IChatAgentService private readonly chatAgentService: IChatAgentService + ) { + super(); + } + + createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { + const phrases = new Map(); + + for (const agent of this.chatAgentService.getAgents()) { + const agentPhrase = `${VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX]}${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase(); + const agentResult = `${VoiceChatService.AGENT_PREFIX}${agent.id}`; + phrases.set(agentPhrase, agentResult); + + if (agent.lastSlashCommands) { + for (const slashCommand of agent.lastSlashCommands) { + const slashCommandPhrase = `${agentPhrase} ${VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]}${slashCommand.name}`.toLowerCase(); + const slashCommandResult = `${agentResult} ${VoiceChatService.COMMAND_PREFIX}${slashCommand.name}`; + phrases.set(slashCommandPhrase, slashCommandResult); + } + } + } + + const session = this.speechService.createSpeechToTextSession(token); + + const disposables = new DisposableStore(); + + const emitter = disposables.add(new Emitter()); + disposables.add(session.onDidChange(e => { + switch (e.status) { + case SpeechToTextStatus.Recognizing: + case SpeechToTextStatus.Recognized: + if (e.text && startsWithIgnoreCase(e.text, VoiceChatService.AGENT_PREFIX)) { + let words = e.text.split(' '); + + // Check for slash command + if (words.length >= 4) { + const slashCommandResult = phrases.get(words.slice(0, 4).join(' ').toLowerCase()); + if (slashCommandResult) { + words = [slashCommandResult, ...words.slice(4)]; + } + } + + // Check for agent + if (words.length >= 2) { + const agentResult = phrases.get(words.slice(0, 2).join(' ').toLowerCase()); + if (agentResult) { + words = [agentResult, ...words.slice(2)]; + } + } + + emitter.fire({ + status: e.status, + text: words.join(' ') + }); + + break; + } + default: + emitter.fire(e); + break; + } + })); + + return { + onDidChange: emitter.event, + dispose: () => disposables.dispose() + }; + } +} diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 3b605a096d5..88fcb52e533 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -50,6 +50,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { ProgressLocation } from 'vs/platform/progress/common/progress'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IVoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); @@ -249,7 +250,7 @@ class VoiceChatSessions { constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, - @ISpeechService private readonly speechService: ISpeechService, + @IVoiceChatService private readonly voiceChatService: IVoiceChatService, @IConfigurationService private readonly configurationService: IConfigurationService ) { } @@ -273,7 +274,7 @@ class VoiceChatSessions { this.voiceChatGettingReadyKey.set(true); - const speechToTextSession = session.disposables.add(this.speechService.createSpeechToTextSession(cts.token)); + const speechToTextSession = session.disposables.add(this.voiceChatService.createSpeechToTextSession(cts.token)); let inputValue = controller.getInput(); diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index 948093ae666..aedef0cc8bd 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -14,7 +14,7 @@ import { localize, localize2 } from 'vs/nls'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { HasSpeechProvider, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import * as dom from 'vs/base/browser/dom'; @@ -25,6 +25,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { IVoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; const CTX_QUICK_CHAT_IN_PROGRESS = new RawContextKey('inlineChat.quickChatInProgress', false); @@ -215,7 +216,7 @@ export class InlineChatQuickVoice implements IEditorContribution { constructor( private readonly _editor: ICodeEditor, - @ISpeechService private readonly _speechService: ISpeechService, + @IVoiceChatService private readonly _voiceChatService: IVoiceChatService, @IContextKeyService contextKeyService: IContextKeyService, ) { this._widget = this._store.add(new QuickVoiceWidget(this._editor)); @@ -239,7 +240,7 @@ export class InlineChatQuickVoice implements IEditorContribution { let message: string | undefined; let preview: string | undefined; - const session = this._speechService.createSpeechToTextSession(cts.token); + const session = this._voiceChatService.createSpeechToTextSession(cts.token); const listener = session.onDidChange(e => { if (cts.token.isCancellationRequested) { From 77c16ff1f3df3d66cd831e76b5cc30624f571f73 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 09:26:19 +0100 Subject: [PATCH 1048/1897] . --- .../contrib/chat/common/voiceChat.ts | 52 +++++++++++++------ .../actions/voiceChatActions.ts | 6 +-- .../electron-sandbox/inlineChatQuickVoice.ts | 2 +- 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index e68335616fa..6598cfe4bf8 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; -import { Emitter } from 'vs/base/common/event'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { startsWithIgnoreCase } from 'vs/base/common/strings'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { ISpeechService, ISpeechToTextEvent, ISpeechToTextSession, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { ISpeechService, ISpeechToTextEvent, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; export const IVoiceChatService = createDecorator('voiceChatService'); @@ -18,7 +18,21 @@ export interface IVoiceChatService { readonly _serviceBrand: undefined; - createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession; + createVoiceChatSession(token: CancellationToken): IVoiceChatSession; +} + +export interface IVoiceChatTextEvent extends ISpeechToTextEvent { + + /** + * This property will be `true` when the text recognized + * so far only consists of agent prefixes (`@workspace`) + * and/or command prefixes (`@workspace /fix`). + */ + readonly waitingForInput?: boolean; +} + +export interface IVoiceChatSession extends IDisposable { + readonly onDidChange: Event; } export class VoiceChatService extends Disposable implements IVoiceChatService { @@ -42,7 +56,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { super(); } - createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { + createVoiceChatSession(token: CancellationToken): IVoiceChatSession { const phrases = new Map(); for (const agent of this.chatAgentService.getAgents()) { @@ -63,33 +77,41 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { const disposables = new DisposableStore(); - const emitter = disposables.add(new Emitter()); + const emitter = disposables.add(new Emitter()); disposables.add(session.onDidChange(e => { switch (e.status) { case SpeechToTextStatus.Recognizing: case SpeechToTextStatus.Recognized: - if (e.text && startsWithIgnoreCase(e.text, VoiceChatService.AGENT_PREFIX)) { - let words = e.text.split(' '); + if (e.text && startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX].trim())) { + const originalWords = e.text.split(' '); + let transformedWords: string[] | undefined; + + let waitingForInput = false; // Check for slash command - if (words.length >= 4) { - const slashCommandResult = phrases.get(words.slice(0, 4).join(' ').toLowerCase()); + if (originalWords.length >= 4) { + const slashCommandResult = phrases.get(originalWords.slice(0, 4).join(' ').toLowerCase()); if (slashCommandResult) { - words = [slashCommandResult, ...words.slice(4)]; + transformedWords = [slashCommandResult, ...originalWords.slice(4)]; + + waitingForInput = originalWords.length === 4; } } // Check for agent - if (words.length >= 2) { - const agentResult = phrases.get(words.slice(0, 2).join(' ').toLowerCase()); + if (!transformedWords && originalWords.length >= 2) { + const agentResult = phrases.get(originalWords.slice(0, 2).join(' ').toLowerCase()); if (agentResult) { - words = [agentResult, ...words.slice(2)]; + transformedWords = [agentResult, ...originalWords.slice(2)]; + + waitingForInput = originalWords.length === 2; } } emitter.fire({ status: e.status, - text: words.join(' ') + text: (transformedWords ?? originalWords).join(' '), + waitingForInput }); break; diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 88fcb52e533..d433ccfecad 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -274,7 +274,7 @@ class VoiceChatSessions { this.voiceChatGettingReadyKey.set(true); - const speechToTextSession = session.disposables.add(this.voiceChatService.createSpeechToTextSession(cts.token)); + const voiceChatSession = session.disposables.add(this.voiceChatService.createVoiceChatSession(cts.token)); let inputValue = controller.getInput(); @@ -284,7 +284,7 @@ class VoiceChatSessions { } const acceptTranscriptionScheduler = session.disposables.add(new RunOnceScheduler(() => session.controller.acceptInput(), voiceChatTimeout)); - session.disposables.add(speechToTextSession.onDidChange(({ status, text }) => { + session.disposables.add(voiceChatSession.onDidChange(({ status, text, waitingForInput }) => { if (cts.token.isCancellationRequested) { return; } @@ -305,7 +305,7 @@ class VoiceChatSessions { if (text) { inputValue = [inputValue, text].join(' '); session.controller.updateInput(inputValue); - if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true) { + if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true && !waitingForInput) { acceptTranscriptionScheduler.schedule(); } } diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index aedef0cc8bd..985ca40703b 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -240,7 +240,7 @@ export class InlineChatQuickVoice implements IEditorContribution { let message: string | undefined; let preview: string | undefined; - const session = this._voiceChatService.createSpeechToTextSession(cts.token); + const session = this._voiceChatService.createVoiceChatSession(cts.token); const listener = session.onDidChange(e => { if (cts.token.isCancellationRequested) { From da4271a18278cfab0e7564081220d400fe3bbdd8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 09:44:35 +0100 Subject: [PATCH 1049/1897] . --- .../contrib/chat/common/voiceChat.ts | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index 6598cfe4bf8..93481ed63c3 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -18,6 +18,12 @@ export interface IVoiceChatService { readonly _serviceBrand: undefined; + /** + * Similar to `ISpeechService.createSpeechToTextSession`, but with + * support for agent prefixes and command prefixes. For example, + * if the user says "at workspace slash fix this problem", the result + * will be "@workspace /fix this problem". + */ createVoiceChatSession(token: CancellationToken): IVoiceChatSession; } @@ -49,30 +55,42 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { private static readonly CHAT_AGENT_ALIAS = new Map([['vscode', 'code']]); + private phrases = this.createPhrases(); + constructor( @ISpeechService private readonly speechService: ISpeechService, @IChatAgentService private readonly chatAgentService: IChatAgentService ) { super(); + + this.registerListeners(); } - createVoiceChatSession(token: CancellationToken): IVoiceChatSession { + private registerListeners(): void { + this._register(this.chatAgentService.onDidChangeAgents(() => this.phrases = this.createPhrases())); + } + + private createPhrases(): Map { const phrases = new Map(); for (const agent of this.chatAgentService.getAgents()) { const agentPhrase = `${VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX]}${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase(); const agentResult = `${VoiceChatService.AGENT_PREFIX}${agent.id}`; - phrases.set(agentPhrase, agentResult); + this.phrases.set(agentPhrase, agentResult); if (agent.lastSlashCommands) { for (const slashCommand of agent.lastSlashCommands) { const slashCommandPhrase = `${agentPhrase} ${VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]}${slashCommand.name}`.toLowerCase(); const slashCommandResult = `${agentResult} ${VoiceChatService.COMMAND_PREFIX}${slashCommand.name}`; - phrases.set(slashCommandPhrase, slashCommandResult); + this.phrases.set(slashCommandPhrase, slashCommandResult); } } } + return phrases; + } + + createVoiceChatSession(token: CancellationToken): IVoiceChatSession { const session = this.speechService.createSpeechToTextSession(token); const disposables = new DisposableStore(); @@ -90,7 +108,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { // Check for slash command if (originalWords.length >= 4) { - const slashCommandResult = phrases.get(originalWords.slice(0, 4).join(' ').toLowerCase()); + const slashCommandResult = this.phrases.get(originalWords.slice(0, 4).join(' ').toLowerCase()); if (slashCommandResult) { transformedWords = [slashCommandResult, ...originalWords.slice(4)]; @@ -100,7 +118,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { // Check for agent if (!transformedWords && originalWords.length >= 2) { - const agentResult = phrases.get(originalWords.slice(0, 2).join(' ').toLowerCase()); + const agentResult = this.phrases.get(originalWords.slice(0, 2).join(' ').toLowerCase()); if (agentResult) { transformedWords = [agentResult, ...originalWords.slice(2)]; From 54ffc0e8a349da4ebbcfaa9c392cd5e3dd81fc4c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 10:06:02 +0100 Subject: [PATCH 1050/1897] . --- src/vs/workbench/contrib/chat/common/voiceChat.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index 93481ed63c3..cec3df65409 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -91,16 +91,21 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { } createVoiceChatSession(token: CancellationToken): IVoiceChatSession { - const session = this.speechService.createSpeechToTextSession(token); - const disposables = new DisposableStore(); + let finishedPhraseDetection = false; + const emitter = disposables.add(new Emitter()); + const session = disposables.add(this.speechService.createSpeechToTextSession(token)); disposables.add(session.onDidChange(e => { switch (e.status) { case SpeechToTextStatus.Recognizing: case SpeechToTextStatus.Recognized: - if (e.text && startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX].trim())) { + if ( + !finishedPhraseDetection && // only if we have not yet attempted phrase detection + e.text && + startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX].trim()) + ) { const originalWords = e.text.split(' '); let transformedWords: string[] | undefined; @@ -114,6 +119,8 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { waitingForInput = originalWords.length === 4; } + + finishedPhraseDetection = true; // only detect phrases in the beginning of the session } // Check for agent From 5e537b389ed2a30afd0beea99e019adbc317ae74 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 9 Feb 2024 10:32:23 +0100 Subject: [PATCH 1051/1897] polishing the code --- extensions/git/src/commands.ts | 1 - .../standalone/browser/standaloneEditor.ts | 1 - src/vs/platform/list/browser/listService.ts | 1 - src/vs/workbench/api/common/extHostSCM.ts | 1 - .../bulkEdit/browser/preview/bulkEditPane.ts | 151 ++++++++---------- .../browser/preview/bulkEditPreview.ts | 1 - 6 files changed, 69 insertions(+), 87 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 9c6c2e1991b..3f46f698ce2 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3780,7 +3780,6 @@ export class CommandCenter { resources.push(toMultiFileDiffEditorUris(change, stashFirstParentCommit, modifiedUriRef)); } - // Command which is executed in order to open the multi diff editor, uring the passed in URI, the given title and the resources which are the modified resource and the original resource commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); } diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 10a75a44904..059a4928862 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -100,7 +100,6 @@ export function createDiffEditor(domElement: HTMLElement, options?: IStandaloneD return instantiationService.createInstance(StandaloneDiffEditor2, domElement, options); } -// There is also a function which creates a multi file diff editor, but maybe we do not need it export function createMultiFileDiffEditor(domElement: HTMLElement, override?: IEditorOverrideServices) { const instantiationService = StandaloneServices.initialize(override || {}); return new MultiDiffEditorWidget(domElement, {}, instantiationService); diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 317af0fdf3a..6857531cb66 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -770,7 +770,6 @@ abstract class ResourceNavigator extends Disposable { this._open(element, preserveFocus, pinned, sideBySide, browserEvent); } - // We want to actually retrieve all of the elements, not just the element at hand private _open(element: T | undefined, preserveFocus: boolean, pinned: boolean, sideBySide: boolean, browserEvent?: UIEvent): void { if (!element) { return; diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 16c8268848b..ee2be0b1bcc 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -416,7 +416,6 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG private _sourceControlHandle: number, private _id: string, private _label: string, - // The following appears to be adding multi diff editor support public readonly multiDiffEditorEnableViewChanges: boolean, private readonly _extension: IExtensionDescription, ) { } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 22966278726..5716aa67659 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -11,7 +11,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IThemeService } from 'vs/platform/theme/common/themeService'; import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { BulkEditPreviewProvider, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -24,7 +23,7 @@ import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/cont import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { ResourceLabels, IResourceLabelsContainer } from 'vs/workbench/browser/labels'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { basename, dirname } from 'vs/base/common/resources'; +import { basename } from 'vs/base/common/resources'; import { MenuId } from 'vs/platform/actions/common/actions'; import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -38,7 +37,7 @@ import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; const enum State { Data = 'data', @@ -80,7 +79,6 @@ export class BulkEditPane extends ViewPane { @IDialogService private readonly _dialogService: IDialogService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IStorageService private readonly _storageService: IStorageService, - @ICommandService private readonly commandService: ICommandService, @IContextKeyService contextKeyService: IContextKeyService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IKeybindingService keybindingService: IKeybindingService, @@ -145,8 +143,7 @@ export class BulkEditPane extends ViewPane { ); this._disposables.add(this._tree.onContextMenu(this._onContextMenu, this)); - // Thing is when the tree is clicked, we want actually to show all of the files inside of the multi diff editor - this._disposables.add(this._tree.onDidOpen(e => this._openElementAsEditor(e))); + this._disposables.add(this._tree.onDidOpen(e => this._openElementInMultiDiffEditor(e))); // buttons const buttonsContainer = document.createElement('div'); @@ -226,7 +223,6 @@ export class BulkEditPane extends ViewPane { return Boolean(this._currentInput); } - // Presumably the method where we set the data to show in the tree refactors view private async _setTreeInput(input: BulkFileOperations) { const viewState = this._treeViewStates.get(this._treeDataSource.groupByFile); @@ -320,8 +316,11 @@ export class BulkEditPane extends ViewPane { } } - // In this function, we actually open the element as an editor, and this is where we could open a multi file diff editor - private async _openElementAsEditor(e: IOpenEvent): Promise { + // Issues with current implementation + // 1. Each time, this creates a new multi diff editor, we want it to reshow the same multi diff editor if there is one + // 2. The file naming does not look correct in the multi diff editor, there is a bug somewhere + // 3. Currently I am accessing the parent of the file element and showing all of the files, but we want to jump to the correct location when clicking on the multi diff editor + private async _openElementInMultiDiffEditor(e: IOpenEvent): Promise { const options: Mutable = { ...e.editorOptions }; let fileElement: FileElement; @@ -338,81 +337,69 @@ export class BulkEditPane extends ViewPane { return; } - console.log('options : ', JSON.stringify(options)); - - if (fileElement.edit.type & BulkFileOperationType.Delete) { - const previewUri = this._currentProvider!.asPreviewUri(fileElement.edit.uri); - - console.log('fileElement.edit : ', fileElement.edit); - console.log('previewUri : ', JSON.stringify(previewUri)); - - const uri = previewUri; - - const label = localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)); - const resources: { originalUri: URI | undefined; modifiedUri: URI | undefined }[] = [{ - originalUri: undefined, - modifiedUri: previewUri - }]; - this.commandService.executeCommand('_workbench.openMultiDiffEditor', { uri, label, resources }); - - } else { - console.log('fileElement.edit ; ', fileElement.edit); - - let bulkFileOperations: BulkFileOperations | undefined = undefined; - let currentParent = fileElement.parent; - while (true) { - if (currentParent instanceof BulkFileOperations) { - bulkFileOperations = currentParent; - break; - } - currentParent = currentParent.parent; + let bulkFileOperations: BulkFileOperations | undefined = undefined; + let currentParent = fileElement.parent; + while (true) { + if (currentParent instanceof BulkFileOperations) { + bulkFileOperations = currentParent; + break; } - const allBulkFileOperations = bulkFileOperations.fileOperations; - console.log('allBulkFileOperations : ', allBulkFileOperations); - - const resources: { originalUri: URI | undefined; modifiedUri: URI | undefined }[] = []; - - for (const operation of allBulkFileOperations) { - const previewUri = this._currentProvider!.asPreviewUri(operation.textEdits[0].textEdit.resource); - - let leftResource: URI | undefined; - try { - (await this._textModelService.createModelReference(fileElement.edit.uri)).dispose(); - leftResource = fileElement.edit.uri; - } catch { - leftResource = BulkEditPreviewProvider.emptyPreview; - } - - let typeLabel: string | undefined; - if (fileElement.edit.type & BulkFileOperationType.Rename) { - typeLabel = localize('rename', "rename"); - } else if (fileElement.edit.type & BulkFileOperationType.Create) { - typeLabel = localize('create', "create"); - } - - let label: string; - if (typeLabel) { - label = localize('edt.title.2', "{0} ({1}, refactor preview)", basename(fileElement.edit.uri), typeLabel); - } else { - label = localize('edt.title.1', "{0} (refactor preview)", basename(fileElement.edit.uri)); - } - - console.log('leftResource : ', JSON.stringify(leftResource)); - console.log('previewUri : ', JSON.stringify(previewUri)); - - resources.push({ - originalUri: leftResource, - modifiedUri: previewUri - }); - } - - // Issues with current implementation - // 1. Each time, this creates a new multi diff editor, we want it to reshow the same multi diff editor if there is one - // 2. The file naming does not look correct in the multi diff editor, there is a bug somewhere - // 3. Currently I am accessing the parent of the file element and showing all of the files, but we want to jump to the correct location when clicking on the multi diff editor - const refactorSourceUri = URI.from({ scheme: 'refactor-preview' }); - this.commandService.executeCommand('_workbench.openMultiDiffEditor', { refactorSourceUri, label: 'Refactor Preview', resources }); + currentParent = currentParent.parent; } + const fileOperations = bulkFileOperations.fileOperations; + + const resources = []; + for (const operation of fileOperations) { + const previewUri = this._currentProvider!.asPreviewUri(operation.textEdits[0].textEdit.resource); + + let leftResource: URI | undefined; + try { + (await this._textModelService.createModelReference(fileElement.edit.uri)).dispose(); + leftResource = fileElement.edit.uri; + } catch { + leftResource = BulkEditPreviewProvider.emptyPreview; + } + resources.push({ + originalUri: leftResource, + modifiedUri: previewUri + }); + + console.log('leftResource : ', JSON.stringify(leftResource)); + console.log('previewUri : ', JSON.stringify(previewUri)); + } + + let typeLabel: string | undefined; + if (fileElement.edit.type & BulkFileOperationType.Rename) { + typeLabel = localize('rename', "rename"); + } else if (fileElement.edit.type & BulkFileOperationType.Create) { + typeLabel = localize('create', "create"); + } + + let label: string; + if (typeLabel) { + label = localize('edt.title.2', "{0} ({1}, refactor preview)", basename(fileElement.edit.uri), typeLabel); + } else { + label = localize('edt.title.1', "{0} (refactor preview)", basename(fileElement.edit.uri)); + } + + const multiDiffSource = URI.from({ scheme: 'refactor-preview', path: JSON.stringify(fileElement.edit.uri) }); + const description = 'Refactor Preview'; + + console.log('options : ', JSON.stringify(options)); + console.log('fileOperations : ', fileOperations); + console.log('fileElement.edit ; ', fileElement.edit); + console.log('multiDiffSource : ', multiDiffSource); + console.log('resources : ', resources); + console.log('label : ', label); + console.log('description : ', description); + + this._editorService.openEditor({ + multiDiffSource: multiDiffSource ? URI.revive(multiDiffSource) : undefined, + resources: resources?.map(r => ({ original: { resource: URI.revive(r.originalUri) }, modified: { resource: URI.revive(r.modifiedUri) } })), + label: label, + description: description, + options: options, + }); } private _onContextMenu(e: ITreeContextMenuEvent): void { diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts index c649d96c78c..d9e8a1112a0 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts @@ -307,7 +307,6 @@ export class BulkFileOperations { return result; } - // Getting the edits for a specific file getFileEdits(uri: URI): ISingleEditOperation[] { for (const file of this.fileOperations) { From 914f33d15bbdfef34291083f49cf4c2f04c2eef7 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 9 Feb 2024 10:43:31 +0100 Subject: [PATCH 1052/1897] updating correctly the right resource too --- .../contrib/bulkEdit/browser/preview/bulkEditPane.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 5716aa67659..9f7077d24ac 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -350,22 +350,22 @@ export class BulkEditPane extends ViewPane { const resources = []; for (const operation of fileOperations) { - const previewUri = this._currentProvider!.asPreviewUri(operation.textEdits[0].textEdit.resource); - let leftResource: URI | undefined; + let leftResource: URI | undefined = operation.textEdits[0].textEdit.resource; + let rightResource: URI | undefined = undefined; try { - (await this._textModelService.createModelReference(fileElement.edit.uri)).dispose(); - leftResource = fileElement.edit.uri; + (await this._textModelService.createModelReference(leftResource)).dispose(); + rightResource = this._currentProvider!.asPreviewUri(leftResource); } catch { leftResource = BulkEditPreviewProvider.emptyPreview; } resources.push({ originalUri: leftResource, - modifiedUri: previewUri + modifiedUri: rightResource }); console.log('leftResource : ', JSON.stringify(leftResource)); - console.log('previewUri : ', JSON.stringify(previewUri)); + console.log('rightResource : ', JSON.stringify(rightResource)); } let typeLabel: string | undefined; From fbb7175b4c8d0da79c74abb3bd256e7bc27c602b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 9 Feb 2024 13:05:22 +0100 Subject: [PATCH 1053/1897] api - rename makeRequest to makeChatRequest, updated todos, jsodc (#204819) --- src/vs/workbench/api/common/extHostChatProvider.ts | 2 +- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 12 +++++++++++- .../vscode.proposed.chatRequestAccess.d.ts | 13 ++++++++----- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index d47cf519006..be14e5557ec 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -219,7 +219,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { const onDidRemoveLM = Event.filter(that._onDidChangeProviders.event, e => e.removed.includes(languageModelId)); return Event.signal(Event.any(onDidChangeAccess, onDidRemoveLM)); }, - makeRequest(messages, options, token) { + makeChatRequest(messages, options, token) { if (!that._accesslist.get(from)) { throw new Error('Access to chat has been revoked'); } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 253347776e4..bbdfe82206a 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -33,6 +33,8 @@ declare module 'vscode' { // */ // response: (ChatAgentContentProgress | ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart)[]; + // agentId: string + // /** // * The result that was received from the chat agent. // */ @@ -45,6 +47,8 @@ declare module 'vscode' { */ history: ChatAgentHistoryEntry[]; + // location: + // TODO@API have "turns" // history2: (ChatAgentRequest | ChatAgentResponse)[]; } @@ -153,6 +157,7 @@ declare module 'vscode' { readonly followupPlaceholder?: string; } + // TODO@API NAME: w/o Sub just `ChatAgentCommand` etc pp export interface ChatAgentSubCommandProvider { /** @@ -253,8 +258,10 @@ declare module 'vscode' { followupProvider?: ChatAgentFollowupProvider; - // TODO@ + // TODO@API // notify(request: ChatResponsePart, reference: string): boolean; + // BETTER + // requestResponseStream(callback: (stream: ChatAgentResponseStream) => void, why?: string): void; // TODO@API // clear NEVER happens @@ -548,6 +555,8 @@ declare module 'vscode' { documents: ChatAgentDocumentContext[]; } + // TODO@API Remove a different type of `request` so that they can + // evolve at their own pace export type ChatAgentHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentResponseStream, token: CancellationToken) => ProviderResult; export namespace chat { @@ -573,6 +582,7 @@ declare module 'vscode' { /** * The detail level of this chat variable value. */ + // TODO@API maybe for round2 export enum ChatVariableLevel { Short = 1, Medium = 2, diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts index 1134553728c..e1a151e77f4 100644 --- a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts +++ b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts @@ -49,7 +49,7 @@ declare module 'vscode' { * @param messages * @param options */ - makeRequest(messages: ChatMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelResponse; + makeChatRequest(messages: ChatMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelResponse; } export interface LanguageModelAccessOptions { @@ -79,10 +79,14 @@ declare module 'vscode' { /** * Request access to a language model. * - * *Note* that this function will throw an error when the user didn't grant access + * - *Note 1:* This function will throw an error when the user didn't grant access or when the + * requested language model is not available. * - * @param id The id of the language model, e.g `copilot` - * @returns A thenable that resolves to a language model access object, rejects is access wasn't granted + * - *Note 2:* It is OK to hold on to the returned access object and use it later, but extensions + * should check {@link LanguageModelAccess.isRevoked} before using it. + * + * @param id The id of the language model, see {@link languageModels} for valid values. + * @returns A thenable that resolves to a language model access object, rejects if access wasn't granted */ export function requestLanguageModelAccess(id: string, options?: LanguageModelAccessOptions): Thenable; @@ -94,7 +98,6 @@ declare module 'vscode' { /** * An event that is fired when the set of available language models changes. */ - //@API is this really needed? export const onDidChangeLanguageModels: Event; } } From 46054bc4377beb80277466c2a915e67e2138363b Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 9 Feb 2024 13:16:31 +0100 Subject: [PATCH 1054/1897] setting the height --- .../multiDiffEditorWidget.ts | 14 ++++++++- .../multiDiffEditorWidgetImpl.ts | 19 +++++++++++- .../bulkEdit/browser/preview/bulkEditPane.ts | 30 +++++++++++++++---- .../browser/multiDiffEditor.ts | 14 ++++++++- 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index 938edfad4b3..349c26b5ee3 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { derived, derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; -import { IMultiDiffEditorViewState, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorViewState, MultiDiffEditorWidgetImpl, VirtualizedViewItem } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import './colors'; @@ -44,6 +44,18 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } + public setScrollState(scrollState: { top?: number; left?: number }): void { + this._widgetImpl.get().setScrollState(scrollState); + } + + public getTopOfElement(index: number): number { + return this._widgetImpl.get().getTopOfElement(index); + } + + public viewItems(): readonly VirtualizedViewItem[] { + return this._widgetImpl.get().viewItems(); + } + public createViewModel(model: IMultiDiffEditorModel): MultiDiffEditorViewModel { return new MultiDiffEditorViewModel(model, this._instantiationService); } diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 129d91151b1..54818d55c35 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -186,6 +186,21 @@ export class MultiDiffEditorWidgetImpl extends Disposable { this._scrollableElement.setScrollPosition({ scrollLeft: scrollState.left, scrollTop: scrollState.top }); } + public getTopOfElement(index: number): number { + console.log('index : ', index); + const viewItems = this._viewItems.get(); + console.log('viewItems : ', viewItems); + let top = 0; + for (let i = 0; i < index - 1; i++) { + top += viewItems[i].contentHeight.get() + this._spaceBetweenPx; + } + return top; + } + + public viewItems(): readonly VirtualizedViewItem[] { + return this._viewItems.get(); + } + public getViewState(): IMultiDiffEditorViewState { return { scrollState: { @@ -277,9 +292,10 @@ export interface IMultiDiffEditorViewState { interface IMultiDiffDocState { collapsed: boolean; selections?: ISelection[]; + uri?: URI; } -class VirtualizedViewItem extends Disposable { +export class VirtualizedViewItem extends Disposable { private readonly _templateRef = this._register(disposableObservableValue | undefined>(this, undefined)); public readonly contentHeight = derived(this, reader => @@ -336,6 +352,7 @@ class VirtualizedViewItem extends Disposable { return { collapsed: this.viewModel.collapsed.get(), selections: this.viewModel.lastTemplateData.get().selections, + uri: this.viewModel.originalUri }; } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 9f7077d24ac..7a8faba7ea4 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -11,7 +11,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IThemeService } from 'vs/platform/theme/common/themeService'; import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { BulkEditPreviewProvider, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; +import { BulkEditPreviewProvider, BulkFileOperation, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { URI } from 'vs/base/common/uri'; @@ -38,6 +38,7 @@ import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { MultiDiffEditor } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor'; const enum State { Data = 'data', @@ -68,7 +69,8 @@ export class BulkEditPane extends ViewPane { private _currentResolve?: (edit?: ResourceEdit[]) => void; private _currentInput?: BulkFileOperations; private _currentProvider?: BulkEditPreviewProvider; - + private _multiDiffEditor?: MultiDiffEditor; + private _fileOperations?: BulkFileOperation[]; constructor( options: IViewletViewOptions, @@ -317,9 +319,9 @@ export class BulkEditPane extends ViewPane { } // Issues with current implementation - // 1. Each time, this creates a new multi diff editor, we want it to reshow the same multi diff editor if there is one - // 2. The file naming does not look correct in the multi diff editor, there is a bug somewhere // 3. Currently I am accessing the parent of the file element and showing all of the files, but we want to jump to the correct location when clicking on the multi diff editor + // Maybe we should somehow return the file operations directly instead of iterating upwards, the way that we currently do + // 4. Should jump instead to the part of the multi diff editor of interest, so no need to show it again, and can just scroll private async _openElementInMultiDiffEditor(e: IOpenEvent): Promise { const options: Mutable = { ...e.editorOptions }; @@ -348,6 +350,22 @@ export class BulkEditPane extends ViewPane { } const fileOperations = bulkFileOperations.fileOperations; + if (this._fileOperations === fileOperations) { + // Multi diff editor already open + const viewItems = this._multiDiffEditor?.viewItems(); + if (viewItems) { + const item = viewItems?.find(item => item.viewModel.originalUri?.toString() === fileElement.edit.uri.toString()); + if (item) { + const index = viewItems.indexOf(item); + const top = this._multiDiffEditor?.getTopOfElement(index); + console.log('top : ', top); + this._multiDiffEditor?.setScrollState({ top }); + } + } + return; + } + this._fileOperations = fileOperations; + const resources = []; for (const operation of fileOperations) { @@ -393,13 +411,13 @@ export class BulkEditPane extends ViewPane { console.log('label : ', label); console.log('description : ', description); - this._editorService.openEditor({ + this._multiDiffEditor = await this._editorService.openEditor({ multiDiffSource: multiDiffSource ? URI.revive(multiDiffSource) : undefined, resources: resources?.map(r => ({ original: { resource: URI.revive(r.originalUri) }, modified: { resource: URI.revive(r.modifiedUri) } })), label: label, description: description, options: options, - }); + }) as MultiDiffEditor; } private _onContextMenu(e: ITreeContextMenuEvent): void { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 26366866759..9361ee7e0ef 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -24,7 +24,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { URI } from 'vs/base/common/uri'; import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; -import { IMultiDiffEditorViewState } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorViewState, VirtualizedViewItem } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; @@ -72,6 +72,18 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { await super.setInput(input, options, context, token); this._viewModel = await input.getViewModel(); From c7b85d9d7aa5c2185528ed359a4a8b25ab1d5db4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 9 Feb 2024 04:28:44 -0800 Subject: [PATCH 1055/1897] polish feature renderers (#204821) --- .../api/common/configurationExtensionPoint.ts | 4 +- .../common/jsonValidationExtensionPoint.ts | 3 +- .../common/codeActionsExtensionPoint.ts | 5 +- .../browser/extensionFeaturesTab.ts | 47 +++++++------------ .../browser/media/extensionEditor.css | 5 -- .../actions/common/menusExtensionPoint.ts | 19 +++++--- .../common/extensionFeatures.ts | 4 +- .../language/common/languageService.ts | 3 +- .../themes/common/colorExtensionPoint.ts | 12 +++-- 9 files changed, 50 insertions(+), 52 deletions(-) diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 57fa4cef735..496651fe769 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -415,9 +415,9 @@ class SettingsTableRenderer extends Disposable implements IExtensionFeatureTable const rows: IRowData[][] = contrib.sort((a, b) => a.localeCompare(b)) .map(key => { return [ - { data: key, type: 'code' }, + new MarkdownString().appendMarkdown(`\`${key}\``), properties[key].markdownDescription ? new MarkdownString(properties[key].markdownDescription, false) : properties[key].description ?? '', - { data: `${isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : JSON.stringify(properties[key].default)}`, type: 'code' } + new MarkdownString().appendCodeblock('json', JSON.stringify(isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : properties[key].default, null, 2)), ]; }); diff --git a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts index 94a2f00ff58..1b82d305f19 100644 --- a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts +++ b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts @@ -12,6 +12,7 @@ import { Extensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { MarkdownString } from 'vs/base/common/htmlContent'; interface IJSONValidationExtensionPoint { fileMatch: string | string[]; @@ -109,7 +110,7 @@ class JSONValidationDataRenderer extends Disposable implements IExtensionFeature const rows: IRowData[][] = contrib.map(v => { return [ - { data: Array.isArray(v.fileMatch) ? v.fileMatch.join(', ') : v.fileMatch, type: 'code' }, + new MarkdownString().appendMarkdown(`\`${Array.isArray(v.fileMatch) ? v.fileMatch.join(', ') : v.fileMatch}\``), v.url, ]; }); diff --git a/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts b/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts index 4e119df579a..d8581f7d67c 100644 --- a/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts @@ -11,6 +11,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; +import { MarkdownString } from 'vs/base/common/htmlContent'; enum CodeActionExtensionPointFields { languages = 'languages', @@ -100,9 +101,9 @@ class CodeActionsTableRenderer extends Disposable implements IExtensionFeatureTa .map(action => { return [ action.title, - { data: action.kind, type: 'code' }, + new MarkdownString().appendMarkdown(`\`${action.kind}\``), action.description ?? '', - { data: [...action.languages], type: 'code' }, + new MarkdownString().appendMarkdown(`${action.languages.map(lang => `\`${lang}\``).join(' ')}`), ]; }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts index a778ba34bdb..d09f329de49 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts @@ -29,7 +29,6 @@ import { ThemeIcon } from 'vs/base/common/themables'; import Severity from 'vs/base/common/severity'; import { errorIcon, infoIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { SeverityIcon } from 'vs/platform/severityIcon/browser/severityIcon'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { OS } from 'vs/base/common/platform'; import { IMarkdownString, MarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; @@ -38,6 +37,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { Codicon } from 'vs/base/common/codicons'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { fromNow } from 'vs/base/common/date'; +import { ResolvedKeybinding } from 'vs/base/common/keybindings'; class RuntimeStatusMarkdownRenderer extends Disposable implements IExtensionFeatureMarkdownRenderer { @@ -372,7 +372,6 @@ class ExtensionFeatureView extends Disposable { @IInstantiationService private readonly instantiationService: IInstantiationService, @IExtensionFeaturesManagementService private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService, @IDialogService private readonly dialogService: IDialogService, - @IKeybindingService private readonly keybindingService: IKeybindingService, ) { super(); @@ -470,36 +469,24 @@ class ExtensionFeatureView extends Disposable { if (typeof rowData === 'string') { return $('td', undefined, rowData); } - if (isMarkdownString(rowData)) { - const element = $('td', undefined); - this.renderMarkdown(rowData, element); - return element; - } - const data = Array.isArray(rowData.data) ? rowData.data : [rowData.data]; - if (rowData.type === 'code') { - return $('td', undefined, ...data.map(c => $('code', undefined, c))); - } else if (rowData.type === 'keybinding') { - return $('td', undefined, ...data.map(keybinding => { + const data = Array.isArray(rowData) ? rowData : [rowData]; + return $('td', undefined, ...data.map(item => { + const result: Node[] = []; + if (isMarkdownString(rowData)) { + const element = $('td', undefined); + this.renderMarkdown(rowData, element); + result.push(element); + } else if (item instanceof ResolvedKeybinding) { const element = $(''); const kbl = new KeybindingLabel(element, OS, defaultKeybindingLabelStyles); - kbl.set(this.keybindingService.resolveUserBinding(keybinding)[0]); - return element; - })); - } else if (rowData.type === 'color') { - return $('td', undefined, ...data.map(colorReference => { - const result: Node[] = []; - if (colorReference && colorReference[0] === '#') { - const color = Color.fromHex(colorReference); - if (color) { - result.push($('span', { class: 'colorBox', style: 'background-color: ' + Color.Format.CSS.format(color) }, '')); - } - } - result.push($('code', undefined, colorReference)); - return result; - }).flat()); - } else { - return $('td', undefined, rowData.data[0]); - } + kbl.set(item); + result.push(element); + } else if (item instanceof Color) { + result.push($('span', { class: 'colorBox', style: 'background-color: ' + Color.Format.CSS.format(item) }, '')); + result.push($('code', undefined, Color.Format.CSS.formatHex(item))); + } + return result; + }).flat()); }) ); }))); diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 93b10e5bb6e..4243f5ebd44 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -874,11 +874,6 @@ align-items: center; } -.extension-editor .subcontent.feature-contributions .extension-feature-content .feature-body .feature-body-content pre { - white-space: pre-wrap; - word-wrap: break-word; -} - .extension-editor .subcontent.feature-contributions .extension-feature-content .feature-body .feature-body-content .feature-content.markdown .codicon { vertical-align: sub; } diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 981c5a0ed62..a81d30aee5e 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -22,6 +22,9 @@ import { IExtensionManifest, IKeyBinding } from 'vs/platform/extensions/common/e import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { platform } from 'vs/base/common/process'; +import { MarkdownString } from 'vs/base/common/htmlContent'; +import { ResolvedKeybinding } from 'vs/base/common/keybindings'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; interface IAPIMenu { readonly key: string; @@ -996,6 +999,10 @@ class CommandsTableRenderer extends Disposable implements IExtensionFeatureTable readonly type = 'table'; + constructor( + @IKeybindingService private readonly _keybindingService: IKeybindingService + ) { super(); } + shouldRender(manifest: IExtensionManifest): boolean { return !!manifest.contributes?.commands; } @@ -1005,7 +1012,7 @@ class CommandsTableRenderer extends Disposable implements IExtensionFeatureTable const commands = rawCommands.map(c => ({ id: c.command, title: c.title, - keybindings: [] as string[], + keybindings: [] as ResolvedKeybinding[], menus: [] as string[] })); @@ -1062,10 +1069,10 @@ class CommandsTableRenderer extends Disposable implements IExtensionFeatureTable const rows: IRowData[][] = commands.sort((a, b) => a.id.localeCompare(b.id)) .map(command => { return [ - { data: command.id, type: 'code' }, + new MarkdownString().appendMarkdown(`\`${command.id}\``), typeof command.title === 'string' ? command.title : command.title.value, - { data: command.keybindings, type: 'keybinding' }, - { data: command.menus, type: 'code' }, + command.keybindings, + new MarkdownString().appendMarkdown(`${command.menus.map(menu => `\`${menu}\``).join(' ')}`), ]; }); @@ -1078,7 +1085,7 @@ class CommandsTableRenderer extends Disposable implements IExtensionFeatureTable }; } - private resolveKeybinding(rawKeyBinding: IKeyBinding): string | undefined { + private resolveKeybinding(rawKeyBinding: IKeyBinding): ResolvedKeybinding | undefined { let key: string | undefined; switch (platform) { @@ -1087,7 +1094,7 @@ class CommandsTableRenderer extends Disposable implements IExtensionFeatureTable case 'darwin': key = rawKeyBinding.mac; break; } - return key ?? rawKeyBinding.key; + return this._keybindingService.resolveUserBinding(key ?? rawKeyBinding.key)[0]; } } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts b/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts index e72d5eee735..2e02427e40f 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts @@ -12,6 +12,8 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import Severity from 'vs/base/common/severity'; import { IStringDictionary } from 'vs/base/common/collections'; +import { ResolvedKeybinding } from 'vs/base/common/keybindings'; +import { Color } from 'vs/base/common/color'; export namespace Extensions { export const ExtensionFeaturesRegistry = 'workbench.registry.extensionFeatures'; @@ -33,7 +35,7 @@ export interface IExtensionFeatureMarkdownRenderer extends IExtensionFeatureRend render(manifest: IExtensionManifest): IRenderedData; } -export type IRowData = string | IMarkdownString | { readonly data: string | string[]; readonly type: 'code' | 'keybinding' | 'color' }; +export type IRowData = string | IMarkdownString | ResolvedKeybinding | Color | ReadonlyArray; export interface ITableData { headers: string[]; diff --git a/src/vs/workbench/services/language/common/languageService.ts b/src/vs/workbench/services/language/common/languageService.ts index c7e93a09c44..c788168597a 100644 --- a/src/vs/workbench/services/language/common/languageService.ts +++ b/src/vs/workbench/services/language/common/languageService.ts @@ -22,6 +22,7 @@ import { Extensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { index } from 'vs/base/common/arrays'; +import { MarkdownString } from 'vs/base/common/htmlContent'; export interface IRawLanguageExtensionPoint { id: string; @@ -181,7 +182,7 @@ class LanguageTableRenderer extends Disposable implements IExtensionFeatureTable .map(l => { return [ l.id, l.name, - { data: l.extensions, type: 'code' }, + new MarkdownString().appendMarkdown(`${l.extensions.map(e => `\`${e}\``).join(' ')}`), l.hasGrammar ? '✔︎' : '\u2014', l.hasSnippets ? '✔︎' : '\u2014' ]; diff --git a/src/vs/workbench/services/themes/common/colorExtensionPoint.ts b/src/vs/workbench/services/themes/common/colorExtensionPoint.ts index 30f8cd24987..9a7bf4aa503 100644 --- a/src/vs/workbench/services/themes/common/colorExtensionPoint.ts +++ b/src/vs/workbench/services/themes/common/colorExtensionPoint.ts @@ -12,6 +12,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Extensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, IRenderedData, IRowData, ITableData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { MarkdownString } from 'vs/base/common/htmlContent'; interface IColorExtensionPoint { id: string; @@ -175,14 +176,17 @@ class ColorDataRenderer extends Disposable implements IExtensionFeatureTableRend nls.localize('defaultLight', "Light Default"), nls.localize('defaultHC', "High Contrast Default"), ]; + + const toColor = (colorReference: string): Color | undefined => colorReference[0] === '#' ? Color.fromHex(colorReference) : undefined; + const rows: IRowData[][] = colors.sort((a, b) => a.id.localeCompare(b.id)) .map(color => { return [ - { data: color.id, type: 'code' }, + new MarkdownString().appendMarkdown(`\`${color.id}\``), color.description, - { data: color.defaults.dark, type: 'color' }, - { data: color.defaults.light, type: 'color' }, - { data: color.defaults.highContrast, type: 'color' }, + toColor(color.defaults.dark) ?? new MarkdownString().appendMarkdown(`\`${color.defaults.dark}\``), + toColor(color.defaults.light) ?? new MarkdownString().appendMarkdown(`\`${color.defaults.light}\``), + toColor(color.defaults.highContrast) ?? new MarkdownString().appendMarkdown(`\`${color.defaults.highContrast}\``), ]; }); From 20c0f83f1b6e436f4db6888e621e2fff625f8b8c Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 9 Feb 2024 14:19:30 +0100 Subject: [PATCH 1056/1897] Git - experimental input validation using diagnostics (#204822) * Initial implementation * Add setting + code actions --- extensions/git/package.json | 6 ++ extensions/git/src/diagnostics.ts | 159 ++++++++++++++++++++++++++++++ extensions/git/src/main.ts | 7 ++ extensions/git/tsconfig.json | 1 + 4 files changed, 173 insertions(+) create mode 100644 extensions/git/src/diagnostics.ts diff --git a/extensions/git/package.json b/extensions/git/package.json index 88553d91126..bee5fc15c87 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -28,6 +28,7 @@ "scmHistoryProvider", "scmMultiDiffEditor", "scmSelectedProvider", + "scmTextDocument", "scmValidation", "tabInputTextMerge", "timeline" @@ -2748,6 +2749,11 @@ "default": 50, "markdownDescription": "%config.inputValidationSubjectLength%" }, + "git.experimental.inputValidation": { + "type": "boolean", + "default": false, + "description": "%config.inputValidation%" + }, "git.detectSubmodules": { "type": "boolean", "scope": "resource", diff --git a/extensions/git/src/diagnostics.ts b/extensions/git/src/diagnostics.ts new file mode 100644 index 00000000000..f9b45a9b23e --- /dev/null +++ b/extensions/git/src/diagnostics.ts @@ -0,0 +1,159 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CodeAction, CodeActionKind, CodeActionProvider, Diagnostic, DiagnosticCollection, DiagnosticSeverity, Disposable, Range, Selection, TextDocument, Uri, WorkspaceEdit, l10n, languages, workspace } from 'vscode'; +import { mapEvent, filterEvent, dispose } from './util'; + +export enum DiagnosticCodes { + empty_message = 'empty_message', + line_length = 'line_length' +} + +export class GitCommitInputBoxDiagnosticsManager { + + private readonly diagnostics: DiagnosticCollection; + private readonly severity = DiagnosticSeverity.Warning; + private readonly disposables: Disposable[] = []; + + constructor() { + this.diagnostics = languages.createDiagnosticCollection(); + mapEvent(filterEvent(workspace.onDidChangeTextDocument, e => e.document.uri.scheme === 'vscode-scm'), e => e.document)(this.validateTextDocument, this, this.disposables); + } + + public getDiagnostics(uri: Uri): ReadonlyArray { + return this.diagnostics.get(uri) ?? []; + } + + private validateTextDocument(document: TextDocument): void { + this.diagnostics.delete(document.uri); + + const config = workspace.getConfiguration('git'); + const inputValidation = config.get('experimental.inputValidation', false) === true; + if (!inputValidation) { + return; + } + + const diagnostics: Diagnostic[] = []; + + if (/^\s+$/.test(document.getText())) { + const documentRange = new Range(document.lineAt(0).range.start, document.lineAt(document.lineCount - 1).range.end); + const diagnostic = new Diagnostic(documentRange, l10n.t('Current commit message only contains whitespace characters'), this.severity); + diagnostic.code = DiagnosticCodes.empty_message; + + diagnostics.push(diagnostic); + this.diagnostics.set(document.uri, diagnostics); + + return; + } + + const inputValidationLength = config.get('inputValidationLength', 50); + const inputValidationSubjectLength = config.get('inputValidationSubjectLength', undefined); + + for (let index = 0; index < document.lineCount; index++) { + const line = document.lineAt(index); + const threshold = index === 0 ? inputValidationSubjectLength ?? inputValidationLength : inputValidationLength; + + if (line.text.length > threshold) { + const diagnostic = new Diagnostic(line.range, l10n.t('{0} characters over {1} in current line', line.text.length - threshold, threshold), this.severity); + diagnostic.code = DiagnosticCodes.line_length; + + diagnostics.push(diagnostic); + } + } + + this.diagnostics.set(document.uri, diagnostics); + } + + dispose() { + dispose(this.disposables); + } +} + +export class GitCommitInputBoxCodeActionsProvider implements CodeActionProvider { + + private readonly disposables: Disposable[] = []; + + constructor(private readonly diagnosticsManager: GitCommitInputBoxDiagnosticsManager) { + this.disposables.push(languages.registerCodeActionsProvider({ scheme: 'vscode-scm' }, this)); + } + + provideCodeActions(document: TextDocument, range: Range | Selection): CodeAction[] { + const codeActions: CodeAction[] = []; + const diagnostics = this.diagnosticsManager.getDiagnostics(document.uri); + + for (const diagnostic of diagnostics) { + if (!diagnostic.range.contains(range)) { + continue; + } + + switch (diagnostic.code) { + case DiagnosticCodes.empty_message: { + const workspaceEdit = new WorkspaceEdit(); + workspaceEdit.delete(document.uri, diagnostic.range); + + const codeAction = new CodeAction(l10n.t('Remove empty characters'), CodeActionKind.QuickFix); + codeAction.diagnostics = [diagnostic]; + codeAction.edit = workspaceEdit; + + codeActions.push(codeAction); + break; + } + case DiagnosticCodes.line_length: { + const workspaceEdit = this.getWrapLineWorkspaceEdit(document, diagnostic.range); + const codeAction = new CodeAction(l10n.t('Hard wrap line'), CodeActionKind.QuickFix); + codeAction.diagnostics = [diagnostic]; + codeAction.edit = workspaceEdit; + + codeActions.push(codeAction); + break; + } + } + } + + return codeActions; + } + + private getWrapLineWorkspaceEdit(document: TextDocument, range: Range): WorkspaceEdit { + const config = workspace.getConfiguration('git'); + const inputValidationLength = config.get('inputValidationLength', 50); + const inputValidationSubjectLength = config.get('inputValidationSubjectLength', undefined); + const lineLengthThreshold = range.start.line === 0 ? inputValidationSubjectLength ?? inputValidationLength : inputValidationLength; + + const lineSegments: string[] = []; + const lineText = document.lineAt(range.start.line).text; + + let position = 0; + while (lineText.length - position > lineLengthThreshold) { + const lastSpaceBeforeThreshold = lineText.lastIndexOf(' ', position + lineLengthThreshold); + + if (lastSpaceBeforeThreshold !== -1 && lastSpaceBeforeThreshold > position) { + lineSegments.push(lineText.substring(position, lastSpaceBeforeThreshold)); + position = lastSpaceBeforeThreshold + 1; + } else { + // Find first space after threshold + const firstSpaceAfterThreshold = lineText.indexOf(' ', position + lineLengthThreshold); + if (firstSpaceAfterThreshold !== -1) { + lineSegments.push(lineText.substring(position, firstSpaceAfterThreshold)); + position = firstSpaceAfterThreshold + 1; + } else { + lineSegments.push(lineText.substring(position)); + position = lineText.length; + } + } + } + if (position < lineText.length) { + lineSegments.push(lineText.substring(position)); + } + + const workspaceEdit = new WorkspaceEdit(); + workspaceEdit.replace(document.uri, range, lineSegments.join('\n')); + + return workspaceEdit; + } + + dispose() { + dispose(this.disposables); + } +} diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 5440795cce9..c40aedaec69 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -25,6 +25,7 @@ import { createIPCServer, IPCServer } from './ipc/ipcServer'; import { GitEditor } from './gitEditor'; import { GitPostCommitCommandsProvider } from './postCommitCommands'; import { GitEditSessionIdentityProvider } from './editSessionIdentityProvider'; +import { GitCommitInputBoxCodeActionsProvider, GitCommitInputBoxDiagnosticsManager } from './diagnostics'; const deactivateTasks: { (): Promise }[] = []; @@ -118,6 +119,12 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, const postCommitCommandsProvider = new GitPostCommitCommandsProvider(); model.registerPostCommitCommandsProvider(postCommitCommandsProvider); + const diagnosticsManager = new GitCommitInputBoxDiagnosticsManager(); + disposables.push(diagnosticsManager); + + const codeActionsProvider = new GitCommitInputBoxCodeActionsProvider(diagnosticsManager); + disposables.push(codeActionsProvider); + checkGitVersion(info); commands.executeCommand('setContext', 'gitVersion2.35', git.compareGitVersionTo('2.35') >= 0); diff --git a/extensions/git/tsconfig.json b/extensions/git/tsconfig.json index 48b9d4227f0..d485d904215 100644 --- a/extensions/git/tsconfig.json +++ b/extensions/git/tsconfig.json @@ -17,6 +17,7 @@ "../../src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts", "../../src/vscode-dts/vscode.proposed.scmValidation.d.ts", "../../src/vscode-dts/vscode.proposed.scmMultiDiffEditor.d.ts", + "../../src/vscode-dts/vscode.proposed.scmTextDocument.d.ts", "../../src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts", "../../src/vscode-dts/vscode.proposed.timeline.d.ts", "../types/lib.textEncoder.d.ts" From 808582dad06e722860e5169f0a43ff193b5625fa Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 9 Feb 2024 10:32:27 -0300 Subject: [PATCH 1057/1897] Bring back command buttons, remove command followups (#204548) * Revert "Revert "Replace chat "command followups" with command button content (#204512)"" This reverts commit e822ae92ea05e56540b0535e436cb043c01ffbbb. * Also add ChatResponseCommandButtonPart class * Fix * dts comments --- .../workbench/api/common/extHost.api.impl.ts | 3 +- .../workbench/api/common/extHost.protocol.ts | 8 +- .../api/common/extHostChatAgents2.ts | 49 ++++++-- .../workbench/api/common/extHostInlineChat.ts | 15 ++- .../api/common/extHostTypeConverters.ts | 54 ++++++--- src/vs/workbench/api/common/extHostTypes.ts | 7 ++ .../contrib/chat/browser/chatFollowups.ts | 3 +- .../contrib/chat/browser/chatInputPart.ts | 6 +- .../contrib/chat/browser/chatListRenderer.ts | 82 ++++++------- .../contrib/chat/browser/chatWidget.ts | 4 +- .../contrib/chat/browser/media/chat.css | 16 +++ .../contrib/chat/common/chatAgents.ts | 4 +- .../contrib/chat/common/chatModel.ts | 32 +++-- .../contrib/chat/common/chatService.ts | 26 ++-- .../contrib/chat/common/chatServiceImpl.ts | 4 +- .../contrib/chat/common/chatViewModel.ts | 22 ++-- .../inlineChat/browser/inlineChatWidget.ts | 111 +++++++++--------- .../contrib/inlineChat/common/inlineChat.ts | 22 +++- .../vscode.proposed.chatAgents2.d.ts | 41 ++++--- .../vscode.proposed.chatAgents2Additions.d.ts | 2 +- .../vscode.proposed.defaultChatAgent.d.ts | 2 +- .../vscode.proposed.interactive.d.ts | 2 +- 22 files changed, 314 insertions(+), 201 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 5b672c3356a..d899ada547e 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -208,7 +208,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I 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 extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService)); + const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService, extHostCommands)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol)); const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol)); @@ -1662,6 +1662,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ChatResponseAnchorPart: extHostTypes.ChatResponseAnchorPart, ChatResponseProgressPart: extHostTypes.ChatResponseProgressPart, ChatResponseReferencePart: extHostTypes.ChatResponseReferencePart, + ChatResponseCommandButtonPart: extHostTypes.ChatResponseCommandButtonPart, }; }; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index a34f7a83238..b7944086d30 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -53,10 +53,10 @@ import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/cal import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatDynamicRequest, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatDynamicRequest, IChatProgress, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; import { DebugConfigurationProviderTriggerKind, MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext } from 'vs/workbench/contrib/debug/common/debug'; -import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatFollowup, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; @@ -1226,7 +1226,7 @@ export interface ExtHostChatAgentsShape2 { $acceptAction(handle: number, sessionId: string, requestId: string, action: IChatUserActionEvent): void; $invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise; $provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined>; - $provideSampleQuestions(handle: number, token: CancellationToken): Promise; + $provideSampleQuestions(handle: number, token: CancellationToken): Promise; $releaseSession(sessionId: string): void; } @@ -1252,7 +1252,7 @@ export type IInlineChatResponseDto = Dto; $provideResponse(handle: number, session: IInlineChatSession, request: IInlineChatRequest, token: CancellationToken): Promise; - $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise; + $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise; $handleFeedback(handle: number, sessionId: number, responseId: number, kind: InlineChatResponseFeedbackKind): void; $releaseSession(handle: number, sessionId: number): void; } diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index d83e50c8442..191941e87d6 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -9,6 +9,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; @@ -16,10 +17,11 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; +import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { IChatAgentCommand, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatFollowup, IChatProgress, IChatReplyFollowup, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatProgress, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import type * as vscode from 'vscode'; @@ -35,7 +37,9 @@ class ChatAgentResponseStream { private readonly _extension: IExtensionDescription, private readonly _request: IChatAgentRequest, private readonly _proxy: MainThreadChatAgentsShape2, - @ILogService private readonly _logService: ILogService, + private readonly _logService: ILogService, + private readonly _commandsConverter: CommandsConverter, + private readonly _sessionDisposables: DisposableStore ) { } close() { @@ -99,6 +103,13 @@ class ChatAgentResponseStream { _report(dto); return this; }, + button(value) { + throwIfDone(this.anchor); + const part = new extHostTypes.ChatResponseCommandButtonPart(value); + const dto = typeConvert.ChatResponseCommandButtonPart.to(part, that._commandsConverter, that._sessionDisposables); + _report(dto); + return this; + }, progress(value) { throwIfDone(this.progress); const part = new extHostTypes.ChatResponseProgressPart(value); @@ -151,11 +162,13 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { private readonly _previousResultMap: Map = new Map(); private readonly _resultsBySessionAndRequestId: Map> = new Map(); + private readonly _sessionDisposables: DisposableMap = new DisposableMap(); constructor( mainContext: IMainContext, private readonly _extHostChatProvider: ExtHostChatProvider, private readonly _logService: ILogService, + private readonly commands: ExtHostCommands, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); } @@ -181,7 +194,14 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { this._extHostChatProvider.$updateAccesslist([{ extension: agent.extension.identifier, enabled: true }]); - const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService); + // Init session disposables + let sessionDisposables = this._sessionDisposables.get(request.sessionId); + if (!sessionDisposables) { + sessionDisposables = new DisposableStore(); + this._sessionDisposables.set(request.sessionId, sessionDisposables); + } + + const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService, this.commands.converter, sessionDisposables); try { const convertedHistory = await this.prepareHistory(agent, request, context); const task = agent.invoke( @@ -226,7 +246,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { || h.result; return { request: typeConvert.ChatAgentRequest.to(h.request), - response: coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r))), + response: coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))), result } satisfies vscode.ChatAgentHistoryEntry; }))); @@ -235,6 +255,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { $releaseSession(sessionId: string): void { this._previousResultMap.delete(sessionId); this._resultsBySessionAndRequestId.delete(sessionId); + this._sessionDisposables.deleteAndDispose(sessionId); } async $provideSlashCommands(handle: number, token: CancellationToken): Promise { @@ -295,7 +316,14 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { // handled by $acceptFeedback return; } - agent.acceptAction(Object.freeze({ action: action.action, result })); + + if (action.action.kind === 'command') { + const commandAction: vscode.ChatAgentCommandAction = { kind: 'command', commandButton: typeConvert.ChatResponseProgress.toProgressContent(action.action.commandButton, this.commands.converter) as vscode.ChatAgentCommandButton }; + agent.acceptAction(Object.freeze({ action: commandAction, result })); + return; + } else { + agent.acceptAction(Object.freeze({ action: action.action, result })); + } } async $invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise { @@ -317,7 +345,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { return await agent.provideWelcomeMessage(token); } - async $provideSampleQuestions(handle: number, token: CancellationToken): Promise { + async $provideSampleQuestions(handle: number, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { return; @@ -395,7 +423,10 @@ class ExtHostChatAgent { if (!followups) { return []; } - return followups.map(f => typeConvert.ChatFollowup.from(f)); + return followups + // Filter out "command followups" from older providers + .filter(f => !(f && 'commandId' in f)) + .map(f => typeConvert.ChatFollowup.from(f)); } async provideWelcomeMessage(token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined> { @@ -415,7 +446,7 @@ class ExtHostChatAgent { }); } - async provideSampleQuestions(token: CancellationToken): Promise { + async provideSampleQuestions(token: CancellationToken): Promise { if (!this._welcomeMessageProvider || !this._welcomeMessageProvider.provideSampleQuestions) { return []; } @@ -424,7 +455,7 @@ class ExtHostChatAgent { return []; } - return content?.map(f => typeConvert.ChatReplyFollowup.from(f)); + return content?.map(f => typeConvert.ChatFollowup.from(f)); } get apiAgent(): vscode.ChatAgent2 { diff --git a/src/vs/workbench/api/common/extHostInlineChat.ts b/src/vs/workbench/api/common/extHostInlineChat.ts index 47ec20ccea2..9e8478b98f3 100644 --- a/src/vs/workbench/api/common/extHostInlineChat.ts +++ b/src/vs/workbench/api/common/extHostInlineChat.ts @@ -3,23 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { toDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; +import { IPosition } from 'vs/editor/common/core/position'; +import { IRange } from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; -import { IInlineChatSession, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostInlineChatShape, IInlineChatResponseDto, IMainContext, MainContext, MainThreadInlineChatShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; +import { IInlineChatFollowup, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import type * as vscode from 'vscode'; -import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; -import { IRange } from 'vs/editor/common/core/range'; -import { IPosition } from 'vs/editor/common/core/position'; -import { raceCancellation } from 'vs/base/common/async'; -import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; class ProviderWrapper { @@ -228,14 +227,14 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { } } - async $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise { + async $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise { const entry = this._inputProvider.get(handle); const sessionData = this._inputSessions.get(sessionId); const response = sessionData?.responses[responseId]; if (entry && response && entry.provider.provideFollowups) { const task = Promise.resolve(entry.provider.provideFollowups(sessionData.session, response, token)); const followups = await raceCancellation(task, token); - return followups?.map(typeConvert.ChatFollowup.from); + return followups?.map(typeConvert.ChatInlineFollowup.from); } return undefined; } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index faafa59f8f0..6e0cc73d248 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -36,9 +36,9 @@ import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/edit import { IViewBadge } from 'vs/workbench/common/views'; import { IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents'; import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatReplyFollowup, IChatResponseCommandFollowup, IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatCommandButton, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; -import { InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IInlineChatCommandFollowup, IInlineChatFollowup, IInlineChatReplyFollowup, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import * as search from 'vs/workbench/contrib/search/common/search'; @@ -50,6 +50,7 @@ import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import type * as vscode from 'vscode'; import * as types from './extHostTypes'; +import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { basename } from 'vs/base/common/resources'; export namespace Command { @@ -2191,8 +2192,8 @@ export namespace DataTransfer { } } -export namespace ChatReplyFollowup { - export function from(followup: vscode.ChatAgentReplyFollowup | vscode.InteractiveEditorReplyFollowup): IChatReplyFollowup { +export namespace ChatFollowup { + export function from(followup: vscode.ChatAgentFollowup): IChatFollowup { return { kind: 'reply', message: followup.message, @@ -2202,21 +2203,25 @@ export namespace ChatReplyFollowup { } } -export namespace ChatFollowup { - export function from(followup: string | vscode.ChatAgentFollowup): IChatFollowup { - if (typeof followup === 'string') { - return { title: followup, message: followup, kind: 'reply' }; - } else if ('commandId' in followup) { - return { +export namespace ChatInlineFollowup { + export function from(followup: vscode.InteractiveEditorFollowup): IInlineChatFollowup { + if ('commandId' in followup) { + return { kind: 'command', title: followup.title ?? '', commandId: followup.commandId ?? '', when: followup.when ?? '', args: followup.args - }; + } satisfies IInlineChatCommandFollowup; } else { - return ChatReplyFollowup.from(followup); + return { + kind: 'reply', + message: followup.message, + title: followup.title, + tooltip: followup.tooltip, + } satisfies IInlineChatReplyFollowup; } + } } @@ -2422,6 +2427,21 @@ export namespace ChatResponseProgressPart { } } +export namespace ChatResponseCommandButtonPart { + export function to(part: vscode.ChatResponseCommandButtonPart, commandsConverter: CommandsConverter, commandDisposables: DisposableStore): Dto { + // If the command isn't in the converter, then this session may have been restored, and the command args don't exist anymore + const command = commandsConverter.toInternal(part.value, commandDisposables) ?? { command: part.value.command, title: part.value.title }; + return { + kind: 'command', + command + }; + } + export function from(part: Dto, commandsConverter: CommandsConverter): vscode.ChatResponseCommandButtonPart { + // If the command isn't in the converter, then this session may have been restored, and the command args don't exist anymore + return new types.ChatResponseCommandButtonPart(commandsConverter.fromInternal(part.command) ?? { command: part.command.id, title: part.command.title }); + } +} + export namespace ChatResponseReferencePart { export function to(part: vscode.ChatResponseReferencePart): Dto { return { @@ -2458,13 +2478,14 @@ export namespace ChatResponsePart { } - export function from(part: extHostProtocol.IChatProgressDto): vscode.ChatResponsePart { + export function from(part: extHostProtocol.IChatProgressDto, commandsConverter: CommandsConverter): vscode.ChatResponsePart { switch (part.kind) { case 'markdownContent': return ChatResponseMarkdownPart.from(part); case 'inlineReference': return ChatResponseAnchorPart.from(part); case 'reference': return ChatResponseReferencePart.from(part); case 'progressMessage': return ChatResponseProgressPart.from(part); case 'treeData': return ChatResponseFilesPart.from(part); + case 'command': return ChatResponseCommandButtonPart.from(part, commandsConverter); } return new types.ChatResponseTextPart(''); } @@ -2557,7 +2578,7 @@ export namespace ChatResponseProgress { } } - export function toProgressContent(progress: extHostProtocol.IChatContentProgressDto): vscode.ChatAgentContentProgress | undefined { + export function toProgressContent(progress: extHostProtocol.IChatContentProgressDto, commandsConverter: Command.ICommandsConverter): vscode.ChatAgentContentProgress | undefined { switch (progress.kind) { case 'markdownContent': // For simplicity, don't sent back the 'extended' types, so downgrade markdown to just some text @@ -2572,6 +2593,11 @@ export namespace ChatResponseProgress { }; case 'treeData': return { treeData: revive(progress.treeData) }; + case 'command': + // If the command isn't in the converter, then this session may have been restored, and the command args don't exist anymore + return { + command: commandsConverter.fromInternal(progress.command) ?? { command: progress.command.id, title: progress.command.title }, + }; default: // Unknown type, eg something in history that was removed? Ignore return undefined; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 300a1a4c549..9a392ffc2e3 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4242,6 +4242,13 @@ export class ChatResponseProgressPart { } } +export class ChatResponseCommandButtonPart { + value: vscode.Command; + constructor(value: vscode.Command) { + this.value = value; + } +} + export class ChatResponseReferencePart { value: vscode.Uri | vscode.Location; constructor(value: vscode.Uri | vscode.Location) { diff --git a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts index 6c6e0d8f6d7..70e92d3f5b1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts +++ b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts @@ -10,10 +10,11 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { IInlineChatFollowup } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; const $ = dom.$; -export class ChatFollowups extends Disposable { +export class ChatFollowups extends Disposable { constructor( container: HTMLElement, followups: T[], diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index eb906af265f..b69f086eb58 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -37,7 +37,7 @@ import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_IN_CHAT_INPUT } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { chatAgentLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatReplyFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IChatHistoryEntry, IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService'; import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; @@ -64,7 +64,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private _onDidBlur = this._register(new Emitter()); readonly onDidBlur = this._onDidBlur.event; - private _onDidAcceptFollowup = this._register(new Emitter<{ followup: IChatReplyFollowup; response: IChatResponseViewModel | undefined }>()); + private _onDidAcceptFollowup = this._register(new Emitter<{ followup: IChatFollowup; response: IChatResponseViewModel | undefined }>()); readonly onDidAcceptFollowup = this._onDidAcceptFollowup.event; private inputEditorHeight = 0; @@ -339,7 +339,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } } - async renderFollowups(items: IChatReplyFollowup[] | undefined, response: IChatResponseViewModel | undefined): Promise { + async renderFollowups(items: IChatFollowup[] | undefined, response: IChatResponseViewModel | undefined): Promise { if (!this.options.renderFollowups) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 9983b28dd4e..972fe2d9aeb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -63,7 +63,7 @@ import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents' import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatContentReference, IChatProgressMessage, IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatCommandButton, IChatContentReference, IChatFollowup, IChatProgressMessage, IChatResponseProgressFileTreeData, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatProgressMessageRenderData, IChatRenderData, IChatResponseMarkdownRenderData, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView'; @@ -117,8 +117,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer()); - readonly onDidClickFollowup: Event = this._onDidClickFollowup.event; + protected readonly _onDidClickFollowup = this._register(new Emitter()); + readonly onDidClickFollowup: Event = this._onDidClickFollowup.event; protected readonly _onDidChangeItemHeight = this._register(new Emitter()); readonly onDidChangeItemHeight: Event = this._onDidChangeItemHeight.event; @@ -141,13 +141,12 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - this.chatService.notifyUserAction({ - providerId: element.providerId, - agentId: element.agent?.id, - sessionId: element.sessionId, - requestId: element.requestId, - action: { - kind: 'command', - command: followup, - } - }); - return this.commandService.executeCommand(followup.commandId, ...(followup.args ?? [])); - }, - templateData.contextKeyService)); - } - const newHeight = templateData.rowContainer.offsetHeight; const fireEvent = !element.currentRenderedHeight || element.currentRenderedHeight !== newHeight; element.currentRenderedHeight = newHeight; @@ -581,6 +559,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.commandService.executeCommand(commandButton.command.id, ...(commandButton.command.arguments ?? [])))); + return { + dispose() { + disposables.dispose(); + }, + element: container + }; + } + private renderMarkdown(markdown: IMarkdownString, element: ChatTreeItem, templateData: IChatListItemTemplate, fillInIncompleteTokens = false): IMarkdownRenderResult { const disposables = new DisposableStore(); let codeBlockIndex = 0; @@ -1029,17 +1028,6 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider followup.title).join(', ')); - } const fileTreeCount = element.response.value.filter((v) => !('value' in v))?.length ?? 0; let fileTreeCountHint = ''; switch (fileTreeCount) { @@ -1064,7 +1052,7 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider, i: number): boolean { return items.slice(i).every(isProgressMessage); } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index ed99c1c6409..2e4fffac465 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -30,7 +30,7 @@ import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION, CONTEXT_RESPONSE_FILTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; @@ -295,7 +295,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } } - private async renderFollowups(items: IChatReplyFollowup[] | undefined, response?: IChatResponseViewModel): Promise { + private async renderFollowups(items: IChatFollowup[] | undefined, response?: IChatResponseViewModel): Promise { this.inputPart.renderFollowups(items, response); if (this.bodyDimension) { diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index fdbd39a5961..52180a60652 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -633,3 +633,19 @@ font-size: 14px; color: var(--vscode-debugIcon-startForeground) !important; } + +.interactive-item-container .chat-command-button { + display: flex; + margin-bottom: 16px; +} + +.interactive-item-container .chat-command-button .monaco-button { + text-align: left; + width: initial; + padding: 4px 8px; +} + +.interactive-item-container .chat-command-button .monaco-button .codicon { + margin-left: 0; + margin-top: 1px; +} diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 6b71960d119..cd4e3b1b489 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { ProviderResult } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; //#region agent service, commands etc @@ -35,7 +35,7 @@ export interface IChatAgent extends IChatAgentData { lastSlashCommands?: IChatAgentCommand[]; provideSlashCommands(token: CancellationToken): Promise; provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>; - provideSampleQuestions?(token: CancellationToken): ProviderResult; + provideSampleQuestions?(token: CancellationToken): ProviderResult; } export interface IChatAgentCommand { diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 951b8a19001..5b09beef63b 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -16,7 +16,7 @@ import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { ILogService } from 'vs/platform/log/common/log'; import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatContent, IChatContentInlineReference, IChatContentReference, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext, IChatCommandButton } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; export interface IChatRequestVariableData { @@ -33,7 +33,7 @@ export interface IChatRequestModel { readonly username: string; readonly avatarIconUri?: URI; readonly session: IChatModel; - readonly message: IParsedChatRequest | IChatReplyFollowup; + readonly message: IParsedChatRequest | IChatFollowup; readonly variableData: IChatRequestVariableData; readonly response?: IChatResponseModel; } @@ -43,7 +43,8 @@ export type IChatProgressResponseContent = | IChatAgentMarkdownContentWithVulnerability | IChatTreeData | IChatContentInlineReference - | IChatProgressMessage; + | IChatProgressMessage + | IChatCommandButton; export type IChatProgressRenderableResponseContent = Exclude; @@ -68,6 +69,8 @@ export interface IChatResponseModel { readonly response: IResponse; readonly isComplete: boolean; readonly isCanceled: boolean; + /** A stale response is one that has been persisted and rehydrated, so e.g. Commands that have their arguments stored in the EH are gone. */ + readonly isStale: boolean; readonly vote: InteractiveSessionVoteDirection | undefined; readonly followups?: IChatFollowup[] | undefined; readonly errorDetails?: IChatResponseErrorDetails; @@ -167,7 +170,7 @@ export class Response implements IResponse { } this._updateRepr(quiet); - } else if (progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage') { + } else { this._responseParts.push(progress); this._updateRepr(quiet); } @@ -179,6 +182,8 @@ export class Response implements IResponse { return ''; } else if (part.kind === 'inlineReference') { return basename('uri' in part.inlineReference ? part.inlineReference.uri : part.inlineReference); + } else if (part.kind === 'command') { + return part.command.title; } else { return part.content.value; } @@ -263,6 +268,11 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this._progressMessages; } + private _isStale: boolean = false; + public get isStale(): boolean { + return this._isStale; + } + constructor( _response: IMarkdownString | ReadonlyArray, public readonly session: ChatModel, @@ -276,6 +286,10 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel followups?: ReadonlyArray ) { super(); + + // If we are creating a response with some existing content, consider it stale + this._isStale = Array.isArray(_response) && (_response.length !== 0 || isMarkdownString(_response) && _response.value.length !== 0); + this._followups = followups ? [...followups] : undefined; this._response = new Response(_response); this._register(this._response.onDidChangeValue(() => this._onDidChange.fire())); @@ -377,7 +391,7 @@ export interface ISerializableChatRequestData { export interface IExportableChatData { providerId: string; - welcomeMessage: (string | IChatReplyFollowup[])[] | undefined; + welcomeMessage: (string | IChatFollowup[])[] | undefined; requests: ISerializableChatRequestData[]; requesterUsername: string; responderUsername: string; @@ -655,7 +669,7 @@ export class ChatModel extends Disposable implements IChatModel { if (progress.kind === 'vulnerability') { request.response.updateContent({ kind: 'markdownVuln', content: { value: progress.content }, vulnerabilities: progress.vulnerabilities }, quiet); - } else if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage') { + } else if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage' || progress.kind === 'command') { request.response.updateContent(progress, quiet); } else if (progress.kind === 'usedContext' || progress.kind === 'reference') { request.response.applyReference(progress); @@ -781,12 +795,12 @@ export class ChatModel extends Disposable implements IChatModel { } } -export type IChatWelcomeMessageContent = IMarkdownString | IChatReplyFollowup[]; +export type IChatWelcomeMessageContent = IMarkdownString | IChatFollowup[]; export interface IChatWelcomeMessageModel { readonly id: string; readonly content: IChatWelcomeMessageContent[]; - readonly sampleQuestions: IChatReplyFollowup[]; + readonly sampleQuestions: IChatFollowup[]; readonly username: string; readonly avatarIconUri?: URI; @@ -803,7 +817,7 @@ export class ChatWelcomeMessageModel implements IChatWelcomeMessageModel { constructor( private readonly session: ChatModel, public readonly content: IChatWelcomeMessageContent[], - public readonly sampleQuestions: IChatReplyFollowup[] + public readonly sampleQuestions: IChatFollowup[] ) { this._id = 'welcome_' + ChatWelcomeMessageModel.nextId++; } diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 9a9c1196e72..bf5c78b9ef2 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -9,7 +9,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { Location, ProviderResult } from 'vs/editor/common/languages'; +import { Command, Location, ProviderResult } from 'vs/editor/common/languages'; import { FileType } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; @@ -142,6 +142,11 @@ export interface IChatAgentMarkdownContentWithVulnerability { kind: 'markdownVuln'; } +export interface IChatCommandButton { + command: Command; + kind: 'command'; +} + export type IChatProgress = | IChatContent | IChatMarkdownContent @@ -152,30 +157,21 @@ export type IChatProgress = | IChatContentReference | IChatContentInlineReference | IChatAgentDetection - | IChatProgressMessage; + | IChatProgressMessage + | IChatCommandButton; export interface IChatProvider { readonly id: string; prepareSession(token: CancellationToken): ProviderResult; } -export interface IChatReplyFollowup { +export interface IChatFollowup { kind: 'reply'; message: string; title?: string; tooltip?: string; } -export interface IChatResponseCommandFollowup { - kind: 'command'; - commandId: string; - args?: any[]; - title: string; // supports codicon strings - when?: string; -} - -export type IChatFollowup = IChatReplyFollowup | IChatResponseCommandFollowup; - // Name has to match the one in vscode.d.ts for some reason export enum InteractiveSessionVoteDirection { Down = 0, @@ -218,12 +214,12 @@ export interface IChatTerminalAction { export interface IChatCommandAction { kind: 'command'; - command: IChatResponseCommandFollowup; + commandButton: IChatCommandButton; } export interface IChatFollowupAction { kind: 'followUp'; - followup: IChatReplyFollowup; + followup: IChatFollowup; } export interface IChatBugReportAction { diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index caab172b0f4..eae790915c1 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -235,8 +235,8 @@ export class ChatService extends Disposable implements IChatService { newFile: !!action.action.newFile }); } else if (action.action.kind === 'command') { - const command = CommandsRegistry.getCommand(action.action.command.commandId); - const commandId = command ? action.action.command.commandId : 'INVALID'; + const command = CommandsRegistry.getCommand(action.action.commandButton.command.id); + const commandId = command ? action.action.commandButton.command.id : 'INVALID'; this.telemetryService.publicLog2('interactiveSessionCommand', { providerId: action.providerId, commandId diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index e3712490d9a..d55a8b0c07f 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -11,7 +11,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModelInitState, IChatModel, IChatRequestModel, IChatResponseModel, IChatWelcomeMessageContent, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatContentReference, IChatProgressMessage, IChatReplyFollowup, IChatResponseCommandFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatContentReference, IChatProgressMessage, IChatFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection, IChatCommandButton } from 'vs/workbench/contrib/chat/common/chatService'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; export function isRequestVM(item: unknown): item is IChatRequestViewModel { @@ -60,7 +60,7 @@ export interface IChatRequestViewModel { readonly dataId: string; readonly username: string; readonly avatarIconUri?: URI; - readonly message: IParsedChatRequest | IChatReplyFollowup; + readonly message: IParsedChatRequest | IChatFollowup; readonly messageText: string; currentRenderedHeight: number | undefined; } @@ -88,7 +88,7 @@ export interface IChatProgressMessageRenderData { isLast: boolean; } -export type IChatRenderData = IChatResponseProgressFileTreeData | IChatResponseMarkdownRenderData | IChatProgressMessageRenderData; +export type IChatRenderData = IChatResponseProgressFileTreeData | IChatResponseMarkdownRenderData | IChatProgressMessageRenderData | IChatCommandButton; export interface IChatResponseRenderData { renderedParts: IChatRenderData[]; } @@ -118,9 +118,9 @@ export interface IChatResponseViewModel { readonly progressMessages: ReadonlyArray; readonly isComplete: boolean; readonly isCanceled: boolean; + readonly isStale: boolean; readonly vote: InteractiveSessionVoteDirection | undefined; - readonly replyFollowups?: IChatReplyFollowup[]; - readonly commandFollowups?: IChatResponseCommandFollowup[]; + readonly replyFollowups?: IChatFollowup[]; readonly errorDetails?: IChatResponseErrorDetails; readonly contentUpdateTimings?: IChatLiveUpdateData; renderData?: IChatResponseRenderData; @@ -331,11 +331,7 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi } get replyFollowups() { - return this._model.followups?.filter((f): f is IChatReplyFollowup => f.kind === 'reply'); - } - - get commandFollowups() { - return this._model.followups?.filter((f): f is IChatResponseCommandFollowup => f.kind === 'command'); + return this._model.followups?.filter((f): f is IChatFollowup => f.kind === 'reply'); } get errorDetails() { @@ -350,6 +346,10 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.requestId; } + get isStale() { + return this._model.isStale; + } + renderData: IChatResponseRenderData | undefined = undefined; agentAvatarHasBeenRendered?: boolean; currentRenderedHeight: number | undefined; @@ -436,6 +436,6 @@ export interface IChatWelcomeMessageViewModel { readonly username: string; readonly avatarIconUri?: URI; readonly content: IChatWelcomeMessageContent[]; - readonly sampleQuestions: IChatReplyFollowup[]; + readonly sampleQuestions: IChatFollowup[]; currentRenderedHeight?: number; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index b631e16714d..aee5dace743 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -3,66 +3,65 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./inlineChat'; -import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; -import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; -import { IRange, Range } from 'vs/editor/common/core/range'; -import { localize } from 'vs/nls'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET_STATUS, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, IInlineChatSlashCommand, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_RESPONSE_FOCUSED, ACTION_ACCEPT_CHANGES, MENU_INLINE_CHAT_WIDGET } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { Dimension, addDisposableListener, getActiveElement, getTotalHeight, getTotalWidth, h, reset } from 'vs/base/browser/dom'; -import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event'; -import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; -import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; -import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; -import { IModelService } from 'vs/editor/common/services/model'; -import { URI } from 'vs/base/common/uri'; -import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; -import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; -import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; -import { Position } from 'vs/editor/common/core/position'; -import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; -import { CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages'; -import { ResourceLabel } from 'vs/workbench/browser/labels'; -import { FileKind } from 'vs/platform/files/common/files'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { LanguageSelector } from 'vs/editor/common/languageSelector'; -import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; -import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; -import { LineRange } from 'vs/editor/common/core/lineRange'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { ExpansionState, HunkData } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; import * as aria from 'vs/base/browser/ui/aria/aria'; +import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; +import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event'; +import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { Lazy } from 'vs/base/common/lazy'; +import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { assertType } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import 'vs/css!./inlineChat'; +import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; +import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; +import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; +import { LineRange } from 'vs/editor/common/core/lineRange'; +import { Position } from 'vs/editor/common/core/position'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; +import { LanguageSelector } from 'vs/editor/common/languageSelector'; +import { CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages'; +import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; +import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; +import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; +import { localize } from 'vs/nls'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; -import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; +import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { FileKind } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ILogService } from 'vs/platform/log/common/log'; +import { editorBackground, editorForeground, inputBackground } from 'vs/platform/theme/common/colorRegistry'; +import { ResourceLabel } from 'vs/workbench/browser/labels'; +import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; -import { assertType } from 'vs/base/common/types'; -import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; -import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; -import { MenuId } from 'vs/platform/actions/common/actions'; -import { editorForeground, inputBackground, editorBackground } from 'vs/platform/theme/common/colorRegistry'; -import { Lazy } from 'vs/base/common/lazy'; -import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; -import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; -import { ILogService } from 'vs/platform/log/common/log'; -import { ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; -import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; -import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; +import { ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; +import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; +import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { ExpansionState, HunkData } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; +import { ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_VISIBLE, IInlineChatFollowup, IInlineChatSlashCommand, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_FEEDBACK, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -632,9 +631,9 @@ export class InlineChatWidget { return resultingAppender; } - updateFollowUps(items: IChatFollowup[], onFollowup: (followup: IChatFollowup) => void): void; + updateFollowUps(items: IInlineChatFollowup[], onFollowup: (followup: IInlineChatFollowup) => void): void; updateFollowUps(items: undefined): void; - updateFollowUps(items: IChatFollowup[] | undefined, onFollowup?: ((followup: IChatFollowup) => void)) { + updateFollowUps(items: IInlineChatFollowup[] | undefined, onFollowup?: ((followup: IInlineChatFollowup) => void)) { this._followUpDisposables.clear(); this._elements.followUps.classList.toggle('hidden', !items || items.length === 0); reset(this._elements.followUps); diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 4a4717a88ba..d339e9f5b07 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; +import { Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IRange } from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; -import { Event } from 'vs/base/common/event'; import { ProviderResult, TextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; @@ -20,7 +20,6 @@ import { IProgress } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; import { diffInserted, diffRemoved, editorHoverHighlight, editorWidgetBackground, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { Extensions as ExtensionsMigration, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; -import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { URI } from 'vs/base/common/uri'; export interface IInlineChatSlashCommand { @@ -98,6 +97,23 @@ export const enum InlineChatResponseFeedbackKind { Bug = 4 } +export interface IInlineChatReplyFollowup { + kind: 'reply'; + message: string; + title?: string; + tooltip?: string; +} + +export interface IInlineChatCommandFollowup { + kind: 'command'; + commandId: string; + args?: any[]; + title: string; // supports codicon strings + when?: string; +} + +export type IInlineChatFollowup = IInlineChatReplyFollowup | IInlineChatCommandFollowup; + export interface IInlineChatSessionProvider { debugName: string; @@ -108,7 +124,7 @@ export interface IInlineChatSessionProvider { provideResponse(item: IInlineChatSession, request: IInlineChatRequest, progress: IProgress, token: CancellationToken): ProviderResult; - provideFollowups?(session: IInlineChatSession, response: IInlineChatResponse, token: CancellationToken): ProviderResult; + provideFollowups?(session: IInlineChatSession, response: IInlineChatResponse, token: CancellationToken): ProviderResult; handleInlineChatResponseFeedback?(session: IInlineChatSession, response: IInlineChatResponse, kind: InlineChatResponseFeedbackKind): void; } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index bbdfe82206a..cacd23e912f 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -18,7 +18,7 @@ declare module 'vscode' { /** * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. */ - response: (ChatAgentContentProgress | ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart)[]; + response: (ChatAgentContentProgress | ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart)[]; /** * The result that was received from the chat agent. @@ -173,19 +173,10 @@ declare module 'vscode' { provideSubCommands(token: CancellationToken): ProviderResult; } - // TODO@API This should become a progress type, and use vscode.Command - // TODO@API what's the when-property for? how about not returning it in the first place? - export interface ChatAgentCommandFollowup { - commandId: string; - args?: any[]; - title: string; // supports codicon strings - when?: string; - } - /** * A followup question suggested by the model. */ - export interface ChatAgentReplyFollowup { + export interface ChatAgentFollowup { /** * The message to send to the chat. */ @@ -202,8 +193,6 @@ declare module 'vscode' { title?: string; } - export type ChatAgentFollowup = ChatAgentCommandFollowup | ChatAgentReplyFollowup; - /** * Will be invoked once after each request to get suggested followup questions to show the user. The user can click the followup to send it to the chat. */ @@ -345,6 +334,15 @@ declare module 'vscode' { */ anchor(value: Uri | Location, title?: string): ChatAgentResponseStream; + /** + * Push a command button part to this stream. Short-hand for + * `push(new ChatResponseCommandButtonPart(value, title))`. + * + * @param command A Command that will be executed when the button is clicked. + * @returns This stream. + */ + button(command: Command): ChatAgentResponseStream; + /** * Push a filetree part to this stream. Short-hand for * `push(new ChatResponseFileTreePart(value))`. @@ -437,8 +435,13 @@ declare module 'vscode' { constructor(value: Uri | Location); } + export class ChatResponseCommandButtonPart { + value: Command; + constructor(value: Command); + } + export type ChatResponsePart = ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart - | ChatResponseProgressPart | ChatResponseReferencePart; + | ChatResponseProgressPart | ChatResponseReferencePart | ChatResponseCommandButtonPart; /** * @deprecated use ChatAgentResponseStream instead @@ -446,7 +449,8 @@ declare module 'vscode' { export type ChatAgentContentProgress = | ChatAgentContent | ChatAgentFileTree - | ChatAgentInlineContentReference; + | ChatAgentInlineContentReference + | ChatAgentCommandButton; /** * @deprecated use ChatAgentResponseStream instead @@ -493,6 +497,13 @@ declare module 'vscode' { title?: string; } + /** + * Displays a {@link Command command} as a button in the chat response. + */ + export interface ChatAgentCommandButton { + command: Command; + } + /** * A piece of the chat response's content. Will be merged with other progress pieces as needed, and rendered as markdown. */ diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index 5d0a7b1afdd..0b0b59c7c55 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -118,7 +118,7 @@ declare module 'vscode' { export interface ChatAgentCommandAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'command'; - command: ChatAgentCommandFollowup; + commandButton: ChatAgentCommandButton; } export interface ChatAgentSessionFollowupAction { diff --git a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts b/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts index 333783f844a..e40a66c044c 100644 --- a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts +++ b/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts @@ -9,7 +9,7 @@ declare module 'vscode' { export interface ChatAgentWelcomeMessageProvider { provideWelcomeMessage(token: CancellationToken): ProviderResult; - provideSampleQuestions?(token: CancellationToken): ProviderResult; + provideSampleQuestions?(token: CancellationToken): ProviderResult; } export interface ChatAgent2 { diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index f331fd88343..bd580590d68 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -125,7 +125,7 @@ declare module 'vscode' { inputPlaceholder?: string; } - export type InteractiveWelcomeMessageContent = string | MarkdownString | ChatAgentReplyFollowup[]; + export type InteractiveWelcomeMessageContent = string | MarkdownString | ChatAgentFollowup[]; export interface InteractiveSessionProvider { prepareSession(token: CancellationToken): ProviderResult; From 2c40be183e31f3d2d159673ecda11a4f22dbf78a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 14:56:31 +0100 Subject: [PATCH 1058/1897] . --- src/vs/workbench/contrib/chat/common/voiceChat.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index cec3df65409..72fe5af468e 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -76,13 +76,13 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { for (const agent of this.chatAgentService.getAgents()) { const agentPhrase = `${VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX]}${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase(); const agentResult = `${VoiceChatService.AGENT_PREFIX}${agent.id}`; - this.phrases.set(agentPhrase, agentResult); + phrases.set(agentPhrase, agentResult); if (agent.lastSlashCommands) { for (const slashCommand of agent.lastSlashCommands) { const slashCommandPhrase = `${agentPhrase} ${VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]}${slashCommand.name}`.toLowerCase(); const slashCommandResult = `${agentResult} ${VoiceChatService.COMMAND_PREFIX}${slashCommand.name}`; - this.phrases.set(slashCommandPhrase, slashCommandResult); + phrases.set(slashCommandPhrase, slashCommandResult); } } } @@ -120,7 +120,9 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { waitingForInput = originalWords.length === 4; } - finishedPhraseDetection = true; // only detect phrases in the beginning of the session + if (e.status === SpeechToTextStatus.Recognized) { + finishedPhraseDetection = true; // only detect phrases in the beginning of the session + } } // Check for agent From de731a945992dae885190077ef337c1131650036 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 15:08:13 +0100 Subject: [PATCH 1059/1897] . --- src/vs/workbench/contrib/chat/common/voiceChat.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index 72fe5af468e..84a511d42e5 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -55,7 +55,14 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { private static readonly CHAT_AGENT_ALIAS = new Map([['vscode', 'code']]); - private phrases = this.createPhrases(); + private _phrases: Map | undefined = undefined; + private get phrases(): Map { + if (!this._phrases) { + this._phrases = this.createPhrases(); + } + + return this._phrases; + } constructor( @ISpeechService private readonly speechService: ISpeechService, @@ -67,7 +74,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { } private registerListeners(): void { - this._register(this.chatAgentService.onDidChangeAgents(() => this.phrases = this.createPhrases())); + this._register(this.chatAgentService.onDidChangeAgents(() => this._phrases = undefined)); } private createPhrases(): Map { From c4455a4d5cdea06e1f532370811f3399de090778 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 9 Feb 2024 12:19:42 +0100 Subject: [PATCH 1060/1897] Code cleanup --- .../browser/inlineCompletionsModel.ts | 65 +++++++++---------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index dcd0dec6cac..6657b08d18a 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -194,11 +194,11 @@ export class InlineCompletionsModel extends Disposable { }); public readonly state = derivedOpts<{ - suggestItem: SuggestItemInfo | undefined; - inlineCompletion: InlineCompletionWithUpdatedRange | undefined; + edits: readonly SingleTextEdit[]; primaryGhostText: GhostTextOrReplacement; ghostTexts: readonly GhostTextOrReplacement[]; - edits: SingleTextEdit[]; + suggestItem: SuggestItemInfo | undefined; + inlineCompletion: InlineCompletionWithUpdatedRange | undefined; } | undefined>({ owner: this, equalityComparer: (a, b) => { @@ -212,29 +212,29 @@ export class InlineCompletionsModel extends Disposable { const suggestItem = this.selectedSuggestItem.read(reader); if (suggestItem) { - const suggestCompletion = suggestItem.toSingleTextEdit().removeCommonPrefix(model); - const augmentedCompletion = this._computeAugmentedCompletion(suggestCompletion, reader); + const suggestCompletionEdit = suggestItem.toSingleTextEdit().removeCommonPrefix(model); + const augmentation = this._computeAugmentation(suggestCompletionEdit, reader); const isSuggestionPreviewEnabled = this._suggestPreviewEnabled.read(reader); - if (!isSuggestionPreviewEnabled && !augmentedCompletion) { return undefined; } + if (!isSuggestionPreviewEnabled && !augmentation) { return undefined; } - const edit = augmentedCompletion?.edit ?? suggestCompletion; - const editPreviewLength = augmentedCompletion ? augmentedCompletion.edit.text.length - suggestCompletion.text.length : 0; + const fullEdit = augmentation?.edit ?? suggestCompletionEdit; + const fullEditPreviewLength = augmentation ? augmentation.edit.text.length - suggestCompletionEdit.text.length : 0; const mode = this._suggestPreviewMode.read(reader); const positions = this._positions.read(reader); - const edits = [edit, ...this._getSecondaryEdits(this.textModel, positions, edit)]; + const edits = [fullEdit, ...this._getSecondaryEdits(this.textModel, positions, fullEdit)]; const ghostTexts = edits - .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], editPreviewLength)) + .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], fullEditPreviewLength)) .filter(isDefined); - const primaryGhostText = ghostTexts[0] ?? new GhostText(edit.range.endLineNumber, []); - return { ghostTexts, primaryGhostText, inlineCompletion: augmentedCompletion?.completion, suggestItem, edits }; + const primaryGhostText = ghostTexts[0] ?? new GhostText(fullEdit.range.endLineNumber, []); + return { edits, primaryGhostText, ghostTexts, inlineCompletion: augmentation?.completion, suggestItem }; } else { if (!this._isActive.read(reader)) { return undefined; } - const item = this.selectedInlineCompletion.read(reader); - if (!item) { return undefined; } + const inlineCompletion = this.selectedInlineCompletion.read(reader); + if (!inlineCompletion) { return undefined; } - const replacement = item.toSingleTextEdit(reader); + const replacement = inlineCompletion.toSingleTextEdit(reader); const mode = this._inlineSuggestMode.read(reader); const positions = this._positions.read(reader); const edits = [replacement, ...this._getSecondaryEdits(this.textModel, positions, replacement)]; @@ -242,11 +242,11 @@ export class InlineCompletionsModel extends Disposable { .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], 0)) .filter(isDefined); if (!ghostTexts[0]) { return undefined; } - return { ghostTexts, primaryGhostText: ghostTexts[0], inlineCompletion: item, suggestItem: undefined, edits }; + return { edits, primaryGhostText: ghostTexts[0], ghostTexts, inlineCompletion, suggestItem: undefined }; } }); - private _computeAugmentedCompletion(suggestCompletion: SingleTextEdit, reader: IReader | undefined) { + private _computeAugmentation(suggestCompletion: SingleTextEdit, reader: IReader | undefined) { const model = this.textModel; const suggestWidgetInlineCompletions = this._source.suggestWidgetInlineCompletions.read(reader); const candidateInlineCompletions = suggestWidgetInlineCompletions @@ -256,7 +256,7 @@ export class InlineCompletionsModel extends Disposable { const augmentedCompletion = mapFindFirst(candidateInlineCompletions, completion => { let r = completion.toSingleTextEdit(reader); r = r.removeCommonPrefix(model, Range.fromPositions(r.range.getStartPosition(), suggestCompletion.range.getEndPosition())); - return r.augments(suggestCompletion) ? { edit: r, completion } : undefined; + return r.augments(suggestCompletion) ? { completion, edit: r } : undefined; }); return augmentedCompletion; @@ -411,16 +411,17 @@ export class InlineCompletionsModel extends Disposable { } const firstPart = ghostText.parts[0]; - const position = new Position(ghostText.lineNumber, firstPart.column); - const text = firstPart.text; - const acceptUntilIndexExclusive = getAcceptUntilIndex(position, text); - - if (acceptUntilIndexExclusive === text.length && ghostText.parts.length === 1) { + const ghostTextPos = new Position(ghostText.lineNumber, firstPart.column); + const ghostTextVal = firstPart.text; + const acceptUntilIndexExclusive = getAcceptUntilIndex(ghostTextPos, ghostTextVal); + if (acceptUntilIndexExclusive === ghostTextVal.length && ghostText.parts.length === 1) { this.accept(editor); return; } + const partialGhostTextVal = ghostTextVal.substring(0, acceptUntilIndexExclusive); - const partialText = text.substring(0, acceptUntilIndexExclusive); + const positions = this._positions.get(); + const cursorPosition = positions[0]; // Executing the edit might free the completion, so we have to hold a reference on it. completion.source.addRef(); @@ -428,13 +429,10 @@ export class InlineCompletionsModel extends Disposable { this._isAcceptingPartially = true; try { editor.pushUndoStop(); - const replaceRange = Range.fromPositions(completion.range.getStartPosition(), position); - const newText = completion.insertText.substring( - 0, - firstPart.column - completion.range.startColumn + acceptUntilIndexExclusive); - const singleTextEdit = new SingleTextEdit(replaceRange, newText); - const positions = this._positions.get(); - const edits = [singleTextEdit, ...this._getSecondaryEdits(this.textModel, positions, singleTextEdit)]; + const replaceRange = Range.fromPositions(cursorPosition, ghostTextPos); + const newText = editor.getModel()!.getValueInRange(replaceRange) + partialGhostTextVal; + const primaryEdit = new SingleTextEdit(replaceRange, newText); + const edits = [primaryEdit, ...this._getSecondaryEdits(this.textModel, positions, primaryEdit)]; const selections = getEndPositionsAfterApplying(edits).map(p => Selection.fromPositions(p)); editor.executeEdits('inlineSuggestion.accept', edits.map(edit => EditOperation.replaceMove(edit.range, edit.text))); editor.setSelections(selections, 'inlineCompletionPartialAccept'); @@ -443,7 +441,7 @@ export class InlineCompletionsModel extends Disposable { } if (completion.source.provider.handlePartialAccept) { - const acceptedRange = Range.fromPositions(completion.range.getStartPosition(), addPositions(position, lengthOfText(partialText))); + const acceptedRange = Range.fromPositions(completion.range.getStartPosition(), addPositions(ghostTextPos, lengthOfText(partialGhostTextVal))); // This assumes that the inline completion and the model use the same EOL style. const text = editor.getModel()!.getValueInRange(acceptedRange, EndOfLinePreference.LF); completion.source.provider.handlePartialAccept( @@ -458,7 +456,6 @@ export class InlineCompletionsModel extends Disposable { } private _getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { - const primaryPosition = positions[0]; const secondaryPositions = positions.slice(1); const replacedTextAfterPrimaryCursor = textModel @@ -477,7 +474,7 @@ export class InlineCompletionsModel extends Disposable { public handleSuggestAccepted(item: SuggestItemInfo) { const itemEdit = item.toSingleTextEdit().removeCommonPrefix(this.textModel); - const augmentedCompletion = this._computeAugmentedCompletion(itemEdit, undefined); + const augmentedCompletion = this._computeAugmentation(itemEdit, undefined); if (!augmentedCompletion) { return; } const inlineCompletion = augmentedCompletion.completion.inlineCompletion; From 80ef615f0b7f8d405d6774149a2e3052463076e6 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 9 Feb 2024 14:54:29 +0100 Subject: [PATCH 1061/1897] adding readonly --- .../contrib/inlineCompletions/browser/inlineCompletionsModel.ts | 2 +- src/vs/editor/contrib/inlineCompletions/browser/utils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 6657b08d18a..2faf2a0f8a9 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -486,7 +486,7 @@ export class InlineCompletionsModel extends Disposable { } } -function getEndPositionsAfterApplying(edits: SingleTextEdit[]): Position[] { +function getEndPositionsAfterApplying(edits: readonly SingleTextEdit[]): Position[] { const sortPerm = Permutation.createSortPermutation(edits, (edit1, edit2) => Range.compareRangesUsingStarts(edit1.range, edit2.range)); const sortedNewRanges = getNewRanges(sortPerm.apply(edits)); const newRanges = sortPerm.inverse().apply(sortedNewRanges); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts index cc24afd0eff..805de9a781a 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts @@ -166,7 +166,7 @@ export class Permutation { return new Permutation(sortIndices); } - apply(arr: T[]): T[] { + apply(arr: readonly T[]): T[] { return arr.map((_, index) => arr[this._indexMap[index]]); } From c8d9a52cc67cb9592b30ff03b3ac4cbe5a4513ca Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 15:26:44 +0100 Subject: [PATCH 1062/1897] . --- .../contrib/chat/common/voiceChat.ts | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index 84a511d42e5..b3219d405a7 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -6,7 +6,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { startsWithIgnoreCase } from 'vs/base/common/strings'; +import { rtrim, startsWithIgnoreCase } from 'vs/base/common/strings'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; @@ -100,7 +100,8 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { createVoiceChatSession(token: CancellationToken): IVoiceChatSession { const disposables = new DisposableStore(); - let finishedPhraseDetection = false; + let detectedAgent = false; + let detectedSlashCommand = false; const emitter = disposables.add(new Emitter()); const session = disposables.add(this.speechService.createSpeechToTextSession(token)); @@ -109,7 +110,6 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { case SpeechToTextStatus.Recognizing: case SpeechToTextStatus.Recognized: if ( - !finishedPhraseDetection && // only if we have not yet attempted phrase detection e.text && startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX].trim()) ) { @@ -119,26 +119,31 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { let waitingForInput = false; // Check for slash command - if (originalWords.length >= 4) { - const slashCommandResult = this.phrases.get(originalWords.slice(0, 4).join(' ').toLowerCase()); + if (!detectedSlashCommand && originalWords.length >= 4) { + const slashCommandResult = this.phrases.get(originalWords.slice(0, 4).map(word => this.normalizeWord(word)).join(' ')); if (slashCommandResult) { transformedWords = [slashCommandResult, ...originalWords.slice(4)]; waitingForInput = originalWords.length === 4; - } - if (e.status === SpeechToTextStatus.Recognized) { - finishedPhraseDetection = true; // only detect phrases in the beginning of the session + if (e.status === SpeechToTextStatus.Recognized) { + detectedAgent = true; + detectedSlashCommand = true; + } } } - // Check for agent - if (!transformedWords && originalWords.length >= 2) { - const agentResult = this.phrases.get(originalWords.slice(0, 2).join(' ').toLowerCase()); + // Check for agent (if not done already) + if (!detectedAgent && !transformedWords && originalWords.length >= 2) { + const agentResult = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); if (agentResult) { transformedWords = [agentResult, ...originalWords.slice(2)]; waitingForInput = originalWords.length === 2; + + if (e.status === SpeechToTextStatus.Recognized) { + detectedAgent = true; + } } } @@ -161,4 +166,12 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { dispose: () => disposables.dispose() }; } + + private normalizeWord(word: string): string { + word = rtrim(word, '.'); + word = rtrim(word, ','); + word = rtrim(word, '?'); + + return word.toLowerCase(); + } } From bac6edbb5559f24268a84faa6261406741db3045 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 16:37:25 +0100 Subject: [PATCH 1063/1897] . --- .../chat/test/common/voiceChat.test.ts | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts new file mode 100644 index 00000000000..0d7504e25c0 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -0,0 +1,196 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { ProviderResult } from 'vs/editor/common/languages'; +import { IChatAgent, IChatAgentCommand, IChatAgentHistoryEntry, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatProgress, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; +import { ISpeechProvider, ISpeechService, ISpeechToTextEvent, ISpeechToTextSession, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; + +suite('VoiceChat', () => { + + class TestChatAgentCommand implements IChatAgentCommand { + constructor(readonly name: string, readonly description: string) { } + } + + class TestChatAgent implements IChatAgent { + constructor(readonly id: string, readonly lastSlashCommands: IChatAgentCommand[]) { } + invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error('Method not implemented.'); } + provideSlashCommands(token: CancellationToken): Promise { throw new Error('Method not implemented.'); } + provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined> { throw new Error('Method not implemented.'); } + metadata = {}; + } + + const agents: IChatAgent[] = [ + new TestChatAgent('workspace', [ + new TestChatAgentCommand('fix', 'fix'), + new TestChatAgentCommand('explain', 'explain') + ]), + new TestChatAgent('vscode', [ + new TestChatAgentCommand('search', 'search') + ]), + ]; + + class TestChatAgentService implements IChatAgentService { + _serviceBrand: undefined; + readonly onDidChangeAgents = Event.None; + registerAgent(agent: IChatAgent): IDisposable { throw new Error(); } + invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error(); } + getFollowups(id: string, sessionId: string, token: CancellationToken): Promise { throw new Error(); } + getAgents(): Array { return agents; } + getAgent(id: string): IChatAgent | undefined { throw new Error(); } + getDefaultAgent(): IChatAgent | undefined { throw new Error(); } + getSecondaryAgent(): IChatAgent | undefined { throw new Error(); } + hasAgent(id: string): boolean { throw new Error(); } + updateAgent(id: string, updateMetadata: IChatAgentMetadata): void { throw new Error(); } + } + + class TestSpeechService implements ISpeechService { + _serviceBrand: undefined; + + onDidRegisterSpeechProvider = Event.None; + onDidUnregisterSpeechProvider = Event.None; + + readonly hasSpeechProvider = true; + readonly hasActiveSpeechToTextSession = false; + readonly hasActiveKeywordRecognition = false; + + registerSpeechProvider(identifier: string, provider: ISpeechProvider): IDisposable { throw new Error('Method not implemented.'); } + onDidStartSpeechToTextSession = Event.None; + onDidEndSpeechToTextSession = Event.None; + + createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { + return { + onDidChange: emitter.event, + dispose: () => { } + }; + } + + onDidStartKeywordRecognition = Event.None; + onDidEndKeywordRecognition = Event.None; + recognizeKeyword(token: CancellationToken): Promise { throw new Error('Method not implemented.'); } + } + + const disposables = new DisposableStore(); + const emitter = disposables.add(new Emitter()); + + teardown(() => { + disposables.clear(); + }); + + test('Agent and slash command detection', async () => { + const service = disposables.add(new VoiceChatService(new TestSpeechService(), new TestChatAgentService())); + + let event: ISpeechToTextEvent | undefined; + let session: ISpeechToTextSession | undefined; + + function createSession() { + session?.dispose(); + + session = disposables.add(service.createVoiceChatSession(CancellationToken.None)); + disposables.add(session.onDidChange(e => { + event = e; + })); + } + + // Nothing to detect + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Started }); + assert.strictEqual(event?.status, SpeechToTextStatus.Started); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'Hello' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, 'Hello'); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'Hello World' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, 'Hello World'); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'Hello World' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, 'Hello World'); + + // Simple detection: Agent + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, 'At'); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@workspace'); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@workspace help'); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@workspace help'); + + // Simple detection: Agent with punctuation + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace, help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@workspace help'); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace, help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@workspace help'); + + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At Workspace. help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@workspace help'); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At Workspace. help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@workspace help'); + + // Simple detection: Slash Command + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code slash search help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@vscode /search help'); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code slash search help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@vscode /search help'); + + // Simple detection: Slash Command with punctuation + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code, slash search, help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@vscode /search help'); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code, slash search, help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@vscode /search help'); + + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code. slash, search help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@vscode /search help'); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code. slash search, help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@vscode /search help'); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); +}); From d1f8fbceee80824ffa96abfaabfc0908253159d0 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 16:40:33 +0100 Subject: [PATCH 1064/1897] . --- .../chat/test/common/voiceChat.test.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index 0d7504e25c0..69391ca4df6 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -119,7 +119,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, 'Hello World'); - // Simple detection: Agent + // Agent createSession(); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At' }); @@ -138,7 +138,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@workspace help'); - // Simple detection: Agent with punctuation + // Agent with punctuation createSession(); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace, help' }); @@ -159,7 +159,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@workspace help'); - // Simple detection: Slash Command + // Slash Command createSession(); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code slash search help' }); @@ -170,7 +170,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@vscode /search help'); - // Simple detection: Slash Command with punctuation + // Slash Command with punctuation createSession(); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code, slash search, help' }); @@ -190,6 +190,17 @@ suite('VoiceChat', () => { emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code. slash search, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@vscode /search help'); + + // Agent not detected twice + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace, for at workspace' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@workspace for at workspace'); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace, for at workspace' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@workspace for at workspace'); }); ensureNoDisposablesAreLeakedInTestSuite(); From 5c3082d584778ce6b695cee29b2d81fe8b9fe8d6 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 9 Feb 2024 16:40:42 +0100 Subject: [PATCH 1065/1897] remove N-1 compatibility trick (#204833) --- src/vs/workbench/api/common/extHostChatProvider.ts | 6 +----- src/vscode-dts/vscode.proposed.chatProvider.d.ts | 4 +--- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index be14e5557ec..b7a91c72e35 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -134,11 +134,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._proxy.$handleProgressChunk(requestId, { index: fragment.index, part: fragment.part }); }); - if (data.provider.provideLanguageModelResponse) { - return data.provider.provideLanguageModelResponse(messages.map(typeConvert.ChatMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); - } else { - return data.provider.provideChatResponse(messages.map(typeConvert.ChatMessage.to), options, progress, token); - } + return data.provider.provideLanguageModelResponse(messages.map(typeConvert.ChatMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); } //#region --- making request diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index 1d218bc8e00..226416480c0 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -16,9 +16,7 @@ declare module 'vscode' { * 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; - - provideLanguageModelResponse?(messages: ChatMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; + provideLanguageModelResponse(messages: ChatMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; } export interface ChatResponseProviderMetadata { From 5b8fe0ed3e34df8075a0e619fad72087cd6b6ddf Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 9 Feb 2024 16:47:22 +0100 Subject: [PATCH 1066/1897] Fix smoke test (#204836) fix smoke test --- test/automation/src/extensions.ts | 7 ++++++- test/smoke/src/areas/extensions/extensions.test.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/test/automation/src/extensions.ts b/test/automation/src/extensions.ts index 1d0f97dadf3..41dc79d442d 100644 --- a/test/automation/src/extensions.ts +++ b/test/automation/src/extensions.ts @@ -41,7 +41,12 @@ export class Extensions extends Viewlet { } async closeExtension(title: string): Promise { - await this.code.waitAndClick(`.tabs-container div.tab[aria-label="Extension: ${title}"] div.tab-actions a.action-label.codicon.codicon-close`); + try { + await this.code.waitAndClick(`.tabs-container div.tab[aria-label="Extension: ${title}, preview"] div.tab-actions a.action-label.codicon.codicon-close`); + } catch (e) { + this.code.logger.log(`Extension '${title}' not opened as preview. Trying without 'preview'.`); + await this.code.waitAndClick(`.tabs-container div.tab[aria-label="Extension: ${title}"] div.tab-actions a.action-label.codicon.codicon-close`); + } } async installExtension(id: string, waitUntilEnabled: boolean): Promise { diff --git a/test/smoke/src/areas/extensions/extensions.test.ts b/test/smoke/src/areas/extensions/extensions.test.ts index 8a7522c6ea7..c78cbe87089 100644 --- a/test/smoke/src/areas/extensions/extensions.test.ts +++ b/test/smoke/src/areas/extensions/extensions.test.ts @@ -12,7 +12,7 @@ export function setup(logger: Logger) { // Shared before/after handling installAllHandlers(logger); - it.skip('install and enable vscode-smoketest-check extension', async function () { + it('install and enable vscode-smoketest-check extension', async function () { const app = this.app as Application; await app.workbench.extensions.installExtension('ms-vscode.vscode-smoketest-check', true); From b59e74c076d1f59cfadabe2d5eab6b52fa4553a4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 16:47:48 +0100 Subject: [PATCH 1067/1897] . --- .../chat/test/common/voiceChat.test.ts | 83 +++++++++++++++---- 1 file changed, 68 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index 69391ca4df6..d844be3c5c8 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -12,7 +12,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti import { ProviderResult } from 'vs/editor/common/languages'; import { IChatAgent, IChatAgentCommand, IChatAgentHistoryEntry, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgress, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; -import { VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; +import { IVoiceChatTextEvent, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; import { ISpeechProvider, ISpeechService, ISpeechToTextEvent, ISpeechToTextSession, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; suite('VoiceChat', () => { @@ -80,26 +80,32 @@ suite('VoiceChat', () => { } const disposables = new DisposableStore(); - const emitter = disposables.add(new Emitter()); + let emitter: Emitter; + + let service: VoiceChatService; + let event: IVoiceChatTextEvent | undefined; + let session: ISpeechToTextSession | undefined; + + function createSession() { + session?.dispose(); + + session = disposables.add(service.createVoiceChatSession(CancellationToken.None)); + disposables.add(session.onDidChange(e => { + event = e; + })); + } + + + setup(() => { + emitter = disposables.add(new Emitter()); + service = disposables.add(new VoiceChatService(new TestSpeechService(), new TestChatAgentService())); + }); teardown(() => { disposables.clear(); }); test('Agent and slash command detection', async () => { - const service = disposables.add(new VoiceChatService(new TestSpeechService(), new TestChatAgentService())); - - let event: ISpeechToTextEvent | undefined; - let session: ISpeechToTextSession | undefined; - - function createSession() { - session?.dispose(); - - session = disposables.add(service.createVoiceChatSession(CancellationToken.None)); - disposables.add(session.onDidChange(e => { - event = e; - })); - } // Nothing to detect createSession(); @@ -110,14 +116,17 @@ suite('VoiceChat', () => { emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'Hello' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, 'Hello'); + assert.strictEqual(event?.waitingForInput, undefined); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'Hello World' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, 'Hello World'); + assert.strictEqual(event?.waitingForInput, undefined); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'Hello World' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, 'Hello World'); + assert.strictEqual(event?.waitingForInput, undefined); // Agent createSession(); @@ -129,14 +138,17 @@ suite('VoiceChat', () => { emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@workspace'); + assert.strictEqual(event?.waitingForInput, true); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.waitingForInput, false); // Agent with punctuation createSession(); @@ -144,20 +156,24 @@ suite('VoiceChat', () => { emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.waitingForInput, false); createSession(); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At Workspace. help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At Workspace. help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.waitingForInput, false); // Slash Command createSession(); @@ -165,10 +181,12 @@ suite('VoiceChat', () => { emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code slash search help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code slash search help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.waitingForInput, false); // Slash Command with punctuation createSession(); @@ -176,20 +194,24 @@ suite('VoiceChat', () => { emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code, slash search, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code, slash search, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.waitingForInput, false); createSession(); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code. slash, search help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code. slash search, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.waitingForInput, false); // Agent not detected twice createSession(); @@ -197,10 +219,41 @@ suite('VoiceChat', () => { emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace, for at workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@workspace for at workspace'); + assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace, for at workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@workspace for at workspace'); + assert.strictEqual(event?.waitingForInput, false); + }); + + test('waiting for input', async () => { + + // Agent + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@workspace'); + assert.strictEqual(event.waitingForInput, true); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@workspace'); + assert.strictEqual(event.waitingForInput, true); + + // Slash Command + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace slash explain' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@workspace /explain'); + assert.strictEqual(event.waitingForInput, true); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace slash explain' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@workspace /explain'); + assert.strictEqual(event.waitingForInput, true); }); ensureNoDisposablesAreLeakedInTestSuite(); From 7a54628f38cb9cf30c2e325a34765332c52ef6ac Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 9 Feb 2024 17:00:54 +0100 Subject: [PATCH 1068/1897] Move old content API types out (#204839) * api - move old content/progress API types out https://github.com/microsoft/vscode/issues/199908 * update docs --- .../vscode.proposed.chatAgents2.d.ts | 129 +----------------- .../vscode.proposed.chatAgents2Additions.d.ts | 108 ++++++++++++++- 2 files changed, 106 insertions(+), 131 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index cacd23e912f..616f85ecc17 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -18,7 +18,7 @@ declare module 'vscode' { /** * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. */ - response: (ChatAgentContentProgress | ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart)[]; + response: (ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart)[]; /** * The result that was received from the chat agent. @@ -384,11 +384,6 @@ declare module 'vscode' { * @param part A response part, rendered or metadata */ push(part: ChatResponsePart): ChatAgentResponseStream; - - /** - * @deprecated use above methods instread - */ - report(value: ChatAgentProgress): void; } // TODO@API @@ -443,128 +438,6 @@ declare module 'vscode' { export type ChatResponsePart = ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseProgressPart | ChatResponseReferencePart | ChatResponseCommandButtonPart; - /** - * @deprecated use ChatAgentResponseStream instead - */ - export type ChatAgentContentProgress = - | ChatAgentContent - | ChatAgentFileTree - | ChatAgentInlineContentReference - | ChatAgentCommandButton; - - /** - * @deprecated use ChatAgentResponseStream instead - */ - export type ChatAgentMetadataProgress = - | ChatAgentUsedContext - | ChatAgentContentReference - | ChatAgentProgressMessage; - - /** - * @deprecated use ChatAgentResponseStream instead - */ - export type ChatAgentProgress = ChatAgentContentProgress | ChatAgentMetadataProgress; - - /** - * Is displayed in the UI to communicate steps of progress to the user. Should be used when the agent may be slow to respond, e.g. due to doing extra work before sending the actual request to the LLM. - */ - export interface ChatAgentProgressMessage { - message: string; - } - - /** - * Indicates a piece of content that was used by the chat agent while processing the request. Will be displayed to the user. - */ - export interface ChatAgentContentReference { - /** - * The resource that was referenced. - */ - reference: Uri | Location; - } - - /** - * A reference to a piece of content that will be rendered inline with the markdown content. - */ - export interface ChatAgentInlineContentReference { - /** - * The resource being referenced. - */ - inlineReference: Uri | Location; - - /** - * An alternate title for the resource. - */ - title?: string; - } - - /** - * Displays a {@link Command command} as a button in the chat response. - */ - export interface ChatAgentCommandButton { - command: Command; - } - - /** - * A piece of the chat response's content. Will be merged with other progress pieces as needed, and rendered as markdown. - */ - export interface ChatAgentContent { - /** - * The content as a string of markdown source. - */ - content: string; - } - - /** - * Represents a tree, such as a file and directory structure, rendered in the chat response. - */ - export interface ChatAgentFileTree { - /** - * The root node of the tree. - */ - treeData: ChatAgentFileTreeData; - } - - /** - * Represents a node in a chat response tree. - */ - export interface ChatAgentFileTreeData { - /** - * A human-readable string describing this node. - */ - label: string; - - /** - * A Uri for this node, opened when it's clicked. - */ - // TODO@API why label and uri. Can the former be derived from the latter? - // TODO@API don't use uri but just names? This API allows to to build nonsense trees where the data structure doesn't match the uris - // path-structure. - uri: Uri; - - /** - * The type of this node. Defaults to {@link FileType.Directory} if it has {@link ChatAgentFileTreeData.children children}. - */ - // TODO@API cross API usage - type?: FileType; - - /** - * The children of this node. - */ - children?: ChatAgentFileTreeData[]; - } - - export interface ChatAgentDocumentContext { - uri: Uri; - version: number; - ranges: Range[]; - } - - /** - * Document references that should be used by the MappedEditsProvider. - */ - export interface ChatAgentUsedContext { - documents: ChatAgentDocumentContext[]; - } // TODO@API Remove a different type of `request` so that they can // evolve at their own pace diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index 0b0b59c7c55..49924b8ff0c 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -17,28 +17,130 @@ declare module 'vscode' { responseIsRedacted?: boolean; } - /** - * This is temporary until inline references are fully supported and adopted - */ + /** @deprecated */ export interface ChatAgentMarkdownContent { markdownContent: MarkdownString; } + // TODO@API fit this into the stream export interface ChatAgentDetectedAgent { agentName: string; command?: ChatAgentSubCommand; } + // TODO@API fit this into the stream export interface ChatAgentVulnerability { title: string; description: string; // id: string; // Later we will need to be able to link these across multiple content chunks. } + // TODO@API fit this into the stream export interface ChatAgentContent { vulnerabilities?: ChatAgentVulnerability[]; } + /** + * @deprecated use ChatAgentResponseStream instead + */ + export type ChatAgentContentProgress = + | ChatAgentContent + | ChatAgentFileTree + | ChatAgentInlineContentReference + | ChatAgentCommandButton; + + /** + * @deprecated use ChatAgentResponseStream instead + */ + export type ChatAgentMetadataProgress = + | ChatAgentUsedContext + | ChatAgentContentReference + | ChatAgentProgressMessage; + + /** + * @deprecated use ChatAgentResponseStream instead + */ + export type ChatAgentProgress = ChatAgentContentProgress | ChatAgentMetadataProgress; + + /** @deprecated */ + export interface ChatAgentProgressMessage { + message: string; + } + + /** @deprecated */ + + export interface ChatAgentContentReference { + /** + * The resource that was referenced. + */ + reference: Uri | Location; + } + + /** + * A reference to a piece of content that will be rendered inline with the markdown content. + */ + export interface ChatAgentInlineContentReference { + /** + * The resource being referenced. + */ + inlineReference: Uri | Location; + + /** + * An alternate title for the resource. + */ + title?: string; + } + + /** + * Displays a {@link Command command} as a button in the chat response. + */ + export interface ChatAgentCommandButton { + command: Command; + } + + /** + * A piece of the chat response's content. Will be merged with other progress pieces as needed, and rendered as markdown. + */ + export interface ChatAgentContent { + /** + * The content as a string of markdown source. + */ + content: string; + } + + /** @deprecated */ + export interface ChatAgentFileTree { + treeData: ChatAgentFileTreeData; + } + + /** @deprecated */ + export interface ChatAgentFileTreeData { + label: string; + uri: Uri; + type?: FileType; + children?: ChatAgentFileTreeData[]; + } + + + export interface ChatAgentDocumentContext { + uri: Uri; + version: number; + ranges: Range[]; + } + + // TODO@API fit this into the stream + export interface ChatAgentUsedContext { + documents: ChatAgentDocumentContext[]; + } + + export interface ChatAgentResponseStream { + /** + * @deprecated use above methods instread + */ + report(value: ChatAgentProgress): void; + } + + /** @deprecated */ export type ChatAgentExtendedProgress = ChatAgentProgress | ChatAgentMarkdownContent | ChatAgentDetectedAgent; From dad5b331eaee2d4e7f87061b412097c21e98ef81 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 9 Feb 2024 10:07:17 -0600 Subject: [PATCH 1069/1897] add basics --- src/vs/platform/terminal/common/terminal.ts | 4 + .../terminal/browser/media/terminal.css | 11 ++ .../contrib/terminal/common/terminal.ts | 4 + .../terminal/common/terminalContextKey.ts | 17 ++ .../contrib/terminal/terminal.all.ts | 1 + .../browser/terminal.chat.contribution.ts | 160 ++++++++++++++++++ .../chat/browser/terminalChatWidget.ts | 76 +++++++++ 7 files changed, 273 insertions(+) create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index ffe0e56a189..56ebf9ddae5 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -121,6 +121,10 @@ export const enum TerminalSettingId { StickyScrollEnabled = 'terminal.integrated.stickyScroll.enabled', StickyScrollMaxLineCount = 'terminal.integrated.stickyScroll.maxLineCount', MouseWheelZoom = 'terminal.integrated.mouseWheelZoom', + FocusChat = 'workbench.action.terminal.focusChat', + HideChat = 'workbench.action.terminal.hideChat', + SubmitChat = 'workbench.action.terminal.submitChat', + CancelChat = 'workbench.action.terminal.cancelChat', // Debug settings that are hidden from user diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 488815cf258..a6ea80d22d6 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -565,3 +565,14 @@ .monaco-workbench .xterm.terminal.hide { visibility: hidden; } + +.monaco-workbench .terminal-chat-widget { + z-index: 33 !important; + position: absolute; + bottom: 30px; + left: 10px; +} + +.monaco-workbench .terminal-chat-widget.hide { + visibility: hidden; +} diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index fc925b646bb..0579628b521 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -500,6 +500,10 @@ export const enum TerminalCommandId { FontZoomIn = 'workbench.action.terminal.fontZoomIn', FontZoomOut = 'workbench.action.terminal.fontZoomOut', FontZoomReset = 'workbench.action.terminal.fontZoomReset', + FocusChat = 'workbench.action.terminal.focusChat', + HideChat = 'workbench.action.terminal.hideChat', + SubmitChat = 'workbench.action.terminal.submitChat', + CancelChat = 'workbench.action.terminal.cancelChat', // Developer commands diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 72335480aee..1de53d61930 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -39,6 +39,10 @@ export const enum TerminalContextKeyStrings { ShellType = 'terminalShellType', InTerminalRunCommandPicker = 'inTerminalRunCommandPicker', TerminalShellIntegrationEnabled = 'terminalShellIntegrationEnabled', + ChatFocus = 'terminalChatFocus', + ChatVisible = 'terminalChatVisible', + ChatSessionInProgress = 'terminalChatSessionInProgress', + ChatInputHasText = 'terminalChatInputHasText', } export namespace TerminalContextKeys { @@ -158,4 +162,17 @@ export namespace TerminalContextKeys { ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActions}`, 'always') ) ); + + + /** Whether the chat widget is focused */ + export const chatFocused = new RawContextKey(TerminalContextKeyStrings.ChatFocus, false, localize('chatFocusedContextKey', "Whether the chat view is focused.")); + + /** Whether the chat widget is visible */ + export const chatVisible = new RawContextKey(TerminalContextKeyStrings.ChatVisible, false, localize('chatVisibleContextKey', "Whether the chat view is visible.")); + + /** Whether a chat session is in progress */ + export const chatSessionInProgress = new RawContextKey(TerminalContextKeyStrings.ChatSessionInProgress, false, localize('chatSessionInProgressContextKey', "Whether a chat session is in progress.")); + + /** Whether the chat input has text */ + export const chatInputHasText = new RawContextKey(TerminalContextKeyStrings.ChatInputHasText, false, localize('chatInputHasTextContextKey', "Whether the chat input has text.")); } diff --git a/src/vs/workbench/contrib/terminal/terminal.all.ts b/src/vs/workbench/contrib/terminal/terminal.all.ts index b49aa829f7b..e9cdc253212 100644 --- a/src/vs/workbench/contrib/terminal/terminal.all.ts +++ b/src/vs/workbench/contrib/terminal/terminal.all.ts @@ -17,6 +17,7 @@ import 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.acce import 'vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution'; import 'vs/workbench/contrib/terminalContrib/environmentChanges/browser/terminal.environmentChanges.contribution'; import 'vs/workbench/contrib/terminalContrib/find/browser/terminal.find.contribution'; +import 'vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution'; import 'vs/workbench/contrib/terminalContrib/highlight/browser/terminal.highlight.contribution'; import 'vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution'; import 'vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution'; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts new file mode 100644 index 00000000000..bb9cbd24540 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -0,0 +1,160 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDimension } from 'vs/base/browser/dom'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { Lazy } from 'vs/base/common/lazy'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { localize2 } from 'vs/nls'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; +import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; +import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; +import { ITerminalProcessInfo, ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; +import { Codicon } from 'vs/base/common/codicons'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; + +export class TerminalChatContribution extends Disposable implements ITerminalContribution { + static readonly ID = 'terminal.Chat'; + + /** + * Currently focused Chat widget. This is used to track action context since + * 'active terminals' are only tracked for non-detached terminal instanecs. + */ + static activeChatWidget?: TerminalChatContribution; + + static get(instance: ITerminalInstance | IDetachedTerminalInstance): TerminalChatContribution | null { + return instance.getContribution(TerminalChatContribution.ID); + } + + private _chatWidget: Lazy | undefined; + private _lastLayoutDimensions: IDimension | undefined; + + get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } + + constructor( + private readonly _instance: ITerminalInstance | IDetachedTerminalInstance, + processManager: ITerminalProcessManager | ITerminalProcessInfo, + widgetManager: TerminalWidgetManager, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ITerminalService private readonly _terminalService: ITerminalService + ) { + super(); + } + + layout(_xterm: IXtermTerminal & { raw: RawXtermTerminal }, dimension: IDimension): void { + this._lastLayoutDimensions = dimension; + this._chatWidget?.rawValue?.layout(dimension.width); + } + + xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { + this._chatWidget = new Lazy(() => { + const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); + + // Track focus and set state so we can force the scroll bar to be visible + chatWidget.onDidFocus(() => { + TerminalChatContribution.activeChatWidget = this; + this._instance.forceScrollbarVisibility(); + if (!isDetachedTerminalInstance(this._instance)) { + this._terminalService.setActiveInstance(this._instance); + } + }); + // chatWidget.onDidBlur(() => { + // TerminalChatContribution.activeChatWidget = undefined; + // this._instance.resetScrollbarVisibility(); + // }); + + if (!this._instance.domElement) { + throw new Error('FindWidget expected terminal DOM to be initialized'); + } + + // this._instance.domElement?.appendChild(chatWidget.getDomNode()); + if (this._lastLayoutDimensions) { + chatWidget.layout(this._lastLayoutDimensions.width); + } + + return chatWidget; + }); + } + + override dispose() { + if (TerminalChatContribution.activeChatWidget === this) { + TerminalChatContribution.activeChatWidget = undefined; + } + super.dispose(); + this._chatWidget?.rawValue?.dispose(); + } +} +registerTerminalContribution(TerminalChatContribution.ID, TerminalChatContribution, true); + +registerActiveXtermAction({ + id: TerminalCommandId.FocusChat, + title: localize2('workbench.action.terminal.focusChat', 'Terminal: Focus Chat'), + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.KeyI, + when: ContextKeyExpr.or(TerminalContextKeys.chatFocused, TerminalContextKeys.focusInAny), + weight: KeybindingWeight.WorkbenchContrib + }, + f1: true, + precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + run: (_xterm, _accessor, activeInstance) => { + const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + contr?.chatWidget?.reveal(); + } +}); + +registerActiveXtermAction({ + id: TerminalCommandId.HideChat, + title: localize2('workbench.action.terminal.hideChat', 'Terminal: Hide Chat'), + keybinding: { + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape], + when: ContextKeyExpr.and(TerminalContextKeys.chatFocused, TerminalContextKeys.chatVisible), + weight: KeybindingWeight.WorkbenchContrib + }, + f1: true, + precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + run: (_xterm, _accessor, activeInstance) => { + const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + contr?.chatWidget?.hide(); + } +}); + +registerActiveXtermAction({ + id: TerminalCommandId.SubmitChat, + title: localize2('workbench.action.terminal.submitChat', 'Terminal: Submit Chat'), + precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.chatInputHasText), + icon: Codicon.send, + menu: { + id: MenuId.ChatExecute, + when: TerminalContextKeys.chatSessionInProgress.negate(), + group: 'navigation', + }, + run: (_xterm, _accessor, activeInstance) => { + const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + contr?.chatWidget?.acceptInput(); + } +}); + +registerActiveXtermAction({ + id: TerminalCommandId.CancelChat, + title: localize2('workbench.action.terminal.cancelChat', 'Cancel Chat'), + precondition: TerminalContextKeys.chatSessionInProgress, + icon: Codicon.debugStop, + menu: { + id: MenuId.ChatExecute, + group: 'navigation', + }, + run: (_xterm, _accessor, activeInstance) => { + const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + contr?.chatWidget?.cancel(); + } +}); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts new file mode 100644 index 00000000000..5b44b0a0cf7 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { editorBackground, editorForeground, inputBackground } from 'vs/platform/theme/common/colorRegistry'; +import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { IDetachedTerminalInstance, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; + +export class TerminalChatWidget extends Disposable { + private _widget: ChatWidget | undefined; + private _scopedInstantiationService: IInstantiationService; + private readonly _onDidFocus = this._register(new Emitter()); + readonly onDidFocus = this._onDidFocus.event; + private _widgetContainer: HTMLElement | undefined; + private _chatWidgetFocused: IContextKey; + private _chatWidgetVisible: IContextKey; + constructor( + private readonly _container: HTMLElement, + private readonly _instance: ITerminalInstance | IDetachedTerminalInstance, + + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService) { + super(); + const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); + this._scopedInstantiationService = this._instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); + this._chatWidgetFocused = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); + this._chatWidgetVisible = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); + } + reveal(): void { + this._widgetContainer = document.createElement('div'); + this._widgetContainer.classList.add('terminal-chat-widget'); + this._widget = this._register(this._scopedInstantiationService.createInstance( + ChatWidget, + { viewId: 'terminal' }, + { supportsFileReferences: false, renderStyle: 'compact' }, + { + listForeground: editorForeground, + listBackground: editorBackground, + inputEditorBackground: inputBackground, + resultEditorBackground: editorBackground + })); + this._widget.render(this._widgetContainer); + this._container.appendChild(this._widgetContainer); + this._register(this._widget.onDidFocus(() => { + this._onDidFocus.fire(); + this._chatWidgetFocused.set(true); + })); + this._widget.setVisible(true); + this._chatWidgetVisible.set(true); + this._widget.setInput('@terminal'); + this._widget.setInputPlaceholder('Request a terminal command'); + this._widget.focusInput(); + } + hide(): void { + if (this._widgetContainer) { + this._container.removeChild(this._widgetContainer); + } + this._chatWidgetVisible.set(false); + } + cancel(): void { + this._widget?.clear(); + } + acceptInput(): void { + this._widget?.acceptInput(); + } + layout(width: number): void { + this._widget?.layout(40, width); + } +} From 4f219d7a161c595d66dce6e376aa77f6703a5158 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 9 Feb 2024 10:11:24 -0600 Subject: [PATCH 1070/1897] fix some issues --- .../browser/terminal.chat.contribution.ts | 36 +++---------------- .../chat/browser/terminalChatWidget.ts | 5 +-- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index bb9cbd24540..0f803200129 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -11,7 +11,7 @@ import { localize2 } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; @@ -25,12 +25,6 @@ import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/br export class TerminalChatContribution extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; - /** - * Currently focused Chat widget. This is used to track action context since - * 'active terminals' are only tracked for non-detached terminal instanecs. - */ - static activeChatWidget?: TerminalChatContribution; - static get(instance: ITerminalInstance | IDetachedTerminalInstance): TerminalChatContribution | null { return instance.getContribution(TerminalChatContribution.ID); } @@ -59,19 +53,6 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon this._chatWidget = new Lazy(() => { const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); - // Track focus and set state so we can force the scroll bar to be visible - chatWidget.onDidFocus(() => { - TerminalChatContribution.activeChatWidget = this; - this._instance.forceScrollbarVisibility(); - if (!isDetachedTerminalInstance(this._instance)) { - this._terminalService.setActiveInstance(this._instance); - } - }); - // chatWidget.onDidBlur(() => { - // TerminalChatContribution.activeChatWidget = undefined; - // this._instance.resetScrollbarVisibility(); - // }); - if (!this._instance.domElement) { throw new Error('FindWidget expected terminal DOM to be initialized'); } @@ -86,9 +67,6 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon } override dispose() { - if (TerminalChatContribution.activeChatWidget === this) { - TerminalChatContribution.activeChatWidget = undefined; - } super.dispose(); this._chatWidget?.rawValue?.dispose(); } @@ -106,8 +84,7 @@ registerActiveXtermAction({ f1: true, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), run: (_xterm, _accessor, activeInstance) => { - const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); - contr?.chatWidget?.reveal(); + TerminalChatContribution.get(activeInstance)?.chatWidget?.reveal(); } }); @@ -123,8 +100,7 @@ registerActiveXtermAction({ f1: true, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), run: (_xterm, _accessor, activeInstance) => { - const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); - contr?.chatWidget?.hide(); + TerminalChatContribution.get(activeInstance)?.chatWidget?.hide(); } }); @@ -139,8 +115,7 @@ registerActiveXtermAction({ group: 'navigation', }, run: (_xterm, _accessor, activeInstance) => { - const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); - contr?.chatWidget?.acceptInput(); + TerminalChatContribution.get(activeInstance)?.chatWidget?.acceptInput(); } }); @@ -154,7 +129,6 @@ registerActiveXtermAction({ group: 'navigation', }, run: (_xterm, _accessor, activeInstance) => { - const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); - contr?.chatWidget?.cancel(); + TerminalChatContribution.get(activeInstance)?.chatWidget?.cancel(); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 5b44b0a0cf7..b6dbcfa67ee 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -16,8 +15,6 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin export class TerminalChatWidget extends Disposable { private _widget: ChatWidget | undefined; private _scopedInstantiationService: IInstantiationService; - private readonly _onDidFocus = this._register(new Emitter()); - readonly onDidFocus = this._onDidFocus.event; private _widgetContainer: HTMLElement | undefined; private _chatWidgetFocused: IContextKey; private _chatWidgetVisible: IContextKey; @@ -49,7 +46,6 @@ export class TerminalChatWidget extends Disposable { this._widget.render(this._widgetContainer); this._container.appendChild(this._widgetContainer); this._register(this._widget.onDidFocus(() => { - this._onDidFocus.fire(); this._chatWidgetFocused.set(true); })); this._widget.setVisible(true); @@ -63,6 +59,7 @@ export class TerminalChatWidget extends Disposable { this._container.removeChild(this._widgetContainer); } this._chatWidgetVisible.set(false); + this._instance.focus(); } cancel(): void { this._widget?.clear(); From 7bde02eb590c0f2cb4c6146f2c152315f55a8aa4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 17:31:29 +0100 Subject: [PATCH 1071/1897] . --- .../contrib/chat/common/voiceChat.ts | 89 +++++++++++-------- .../chat/test/common/voiceChat.test.ts | 15 +++- 2 files changed, 68 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index b3219d405a7..9b974c89b55 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -87,9 +87,13 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { if (agent.lastSlashCommands) { for (const slashCommand of agent.lastSlashCommands) { - const slashCommandPhrase = `${agentPhrase} ${VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]}${slashCommand.name}`.toLowerCase(); - const slashCommandResult = `${agentResult} ${VoiceChatService.COMMAND_PREFIX}${slashCommand.name}`; + const slashCommandPhrase = `${VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]}${slashCommand.name}`.toLowerCase(); + const slashCommandResult = `${VoiceChatService.COMMAND_PREFIX}${slashCommand.name}`; phrases.set(slashCommandPhrase, slashCommandResult); + + const agentSlashCommandPhrase = `${agentPhrase} ${slashCommandPhrase}`.toLowerCase(); + const agentSlashCommandResult = `${agentResult} ${slashCommandResult}`; + phrases.set(agentSlashCommandPhrase, agentSlashCommandResult); } } } @@ -109,51 +113,66 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { switch (e.status) { case SpeechToTextStatus.Recognizing: case SpeechToTextStatus.Recognized: - if ( - e.text && - startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX].trim()) - ) { - const originalWords = e.text.split(' '); - let transformedWords: string[] | undefined; + if (e.text) { + const startsWithAgent = startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX].trim()); + const startsWithSlashCommand = startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX].trim()); + if (startsWithAgent || startsWithSlashCommand) { + const originalWords = e.text.split(' '); + let transformedWords: string[] | undefined; - let waitingForInput = false; + let waitingForInput = false; - // Check for slash command - if (!detectedSlashCommand && originalWords.length >= 4) { - const slashCommandResult = this.phrases.get(originalWords.slice(0, 4).map(word => this.normalizeWord(word)).join(' ')); - if (slashCommandResult) { - transformedWords = [slashCommandResult, ...originalWords.slice(4)]; + // Check for agent + slash command + if (startsWithAgent && !detectedAgent && !detectedSlashCommand && originalWords.length >= 4) { + const slashCommandResult = this.phrases.get(originalWords.slice(0, 4).map(word => this.normalizeWord(word)).join(' ')); + if (slashCommandResult) { + transformedWords = [slashCommandResult, ...originalWords.slice(4)]; - waitingForInput = originalWords.length === 4; + waitingForInput = originalWords.length === 4; - if (e.status === SpeechToTextStatus.Recognized) { - detectedAgent = true; - detectedSlashCommand = true; + if (e.status === SpeechToTextStatus.Recognized) { + detectedAgent = true; + detectedSlashCommand = true; + } } } - } - // Check for agent (if not done already) - if (!detectedAgent && !transformedWords && originalWords.length >= 2) { - const agentResult = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); - if (agentResult) { - transformedWords = [agentResult, ...originalWords.slice(2)]; + // Check for agent (if not done already) + if (startsWithAgent && !detectedAgent && !transformedWords && originalWords.length >= 2) { + const agentResult = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); + if (agentResult) { + transformedWords = [agentResult, ...originalWords.slice(2)]; - waitingForInput = originalWords.length === 2; + waitingForInput = originalWords.length === 2; - if (e.status === SpeechToTextStatus.Recognized) { - detectedAgent = true; + if (e.status === SpeechToTextStatus.Recognized) { + detectedAgent = true; + } } } + + // Check for slash command (if not done already) + if (startsWithSlashCommand && !detectedSlashCommand && !transformedWords && originalWords.length >= 2) { + const slashCommandResult = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); + if (slashCommandResult) { + transformedWords = [slashCommandResult, ...originalWords.slice(2)]; + + waitingForInput = originalWords.length === 2; + + if (e.status === SpeechToTextStatus.Recognized) { + detectedSlashCommand = true; + } + } + } + + emitter.fire({ + status: e.status, + text: (transformedWords ?? originalWords).join(' '), + waitingForInput + }); + + break; } - - emitter.fire({ - status: e.status, - text: (transformedWords ?? originalWords).join(' '), - waitingForInput - }); - - break; } default: emitter.fire(e); diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index d844be3c5c8..2f87f56857d 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -178,6 +178,19 @@ suite('VoiceChat', () => { // Slash Command createSession(); + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'Slash fix' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '/fix'); + assert.strictEqual(event?.waitingForInput, true); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'Slash fix' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '/fix'); + assert.strictEqual(event?.waitingForInput, true); + + // Agent + Slash Command + createSession(); + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code slash search help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@vscode /search help'); @@ -188,7 +201,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.text, '@vscode /search help'); assert.strictEqual(event?.waitingForInput, false); - // Slash Command with punctuation + // Agent + Slash Command with punctuation createSession(); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code, slash search, help' }); From ab4a6959f7298fbd463daf364309350e16c40812 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 9 Feb 2024 10:44:29 -0600 Subject: [PATCH 1072/1897] add hold for speech --- .../contrib/inlineChat/electron-sandbox/inlineChatActions.ts | 5 +++-- .../chat/browser/terminal.chat.contribution.ts | 4 ++-- .../terminalContrib/chat/browser/terminalChatWidget.ts | 4 +--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts index d30d230a2b7..ec8c7cd1c47 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts @@ -20,16 +20,17 @@ import { HasSpeechProvider, ISpeechService } from 'vs/workbench/contrib/speech/c import { localize2 } from 'vs/nls'; import { Action2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; export class HoldToSpeak extends AbstractInlineChatAction { constructor() { super({ id: 'inlineChat.holdForSpeech', - precondition: ContextKeyExpr.and(HasSpeechProvider, CTX_INLINE_CHAT_VISIBLE), + precondition: ContextKeyExpr.and(HasSpeechProvider, ContextKeyExpr.or(CTX_INLINE_CHAT_VISIBLE, TerminalContextKeys.chatVisible)), title: localize2('holdForSpeech', "Hold for Speech"), keybinding: { - when: EditorContextKeys.textInputFocus, + when: ContextKeyExpr.or(EditorContextKeys.textInputFocus, TerminalContextKeys.chatFocused), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.KeyI, }, diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index 0f803200129..46bef94963c 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -39,7 +39,7 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon processManager: ITerminalProcessManager | ITerminalProcessInfo, widgetManager: TerminalWidgetManager, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @ITerminalService private readonly _terminalService: ITerminalService + @ITerminalService terminalService: ITerminalService ) { super(); } @@ -78,7 +78,7 @@ registerActiveXtermAction({ title: localize2('workbench.action.terminal.focusChat', 'Terminal: Focus Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, - when: ContextKeyExpr.or(TerminalContextKeys.chatFocused, TerminalContextKeys.focusInAny), + when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), weight: KeybindingWeight.WorkbenchContrib }, f1: true, diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index b6dbcfa67ee..cb2db4bb1be 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -45,9 +45,7 @@ export class TerminalChatWidget extends Disposable { })); this._widget.render(this._widgetContainer); this._container.appendChild(this._widgetContainer); - this._register(this._widget.onDidFocus(() => { - this._chatWidgetFocused.set(true); - })); + this._register(this._widget.onDidFocus(() => this._chatWidgetFocused.set(true))); this._widget.setVisible(true); this._chatWidgetVisible.set(true); this._widget.setInput('@terminal'); From dc4ebd856d49a1ae73b93214f635d755a1f0ad58 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 9 Feb 2024 11:04:23 -0600 Subject: [PATCH 1073/1897] fix some issues --- .../terminal/browser/media/terminal.css | 3 +-- .../chat/browser/terminalChatWidget.ts | 20 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index a6ea80d22d6..8fc9e6be0eb 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -569,8 +569,7 @@ .monaco-workbench .terminal-chat-widget { z-index: 33 !important; position: absolute; - bottom: 30px; - left: 10px; + top: 10px; } .monaco-workbench .terminal-chat-widget.hide { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index cb2db4bb1be..4f0c787fc6b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -13,9 +13,9 @@ import { IDetachedTerminalInstance, ITerminalInstance } from 'vs/workbench/contr import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; export class TerminalChatWidget extends Disposable { - private _widget: ChatWidget | undefined; + private _widget: ChatWidget; private _scopedInstantiationService: IInstantiationService; - private _widgetContainer: HTMLElement | undefined; + private _widgetContainer: HTMLElement; private _chatWidgetFocused: IContextKey; private _chatWidgetVisible: IContextKey; constructor( @@ -29,10 +29,9 @@ export class TerminalChatWidget extends Disposable { this._scopedInstantiationService = this._instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); this._chatWidgetFocused = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); this._chatWidgetVisible = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); - } - reveal(): void { this._widgetContainer = document.createElement('div'); this._widgetContainer.classList.add('terminal-chat-widget'); + this._container.appendChild(this._widgetContainer); this._widget = this._register(this._scopedInstantiationService.createInstance( ChatWidget, { viewId: 'terminal' }, @@ -44,19 +43,22 @@ export class TerminalChatWidget extends Disposable { resultEditorBackground: editorBackground })); this._widget.render(this._widgetContainer); - this._container.appendChild(this._widgetContainer); this._register(this._widget.onDidFocus(() => this._chatWidgetFocused.set(true))); + } + reveal(): void { + this._widgetContainer.classList.remove('hide'); this._widget.setVisible(true); + this._chatWidgetFocused.set(true); this._chatWidgetVisible.set(true); this._widget.setInput('@terminal'); this._widget.setInputPlaceholder('Request a terminal command'); this._widget.focusInput(); } hide(): void { - if (this._widgetContainer) { - this._container.removeChild(this._widgetContainer); - } + this._widgetContainer.classList.add('hide'); + this._chatWidgetFocused.set(false); this._chatWidgetVisible.set(false); + this._widget.clear(); this._instance.focus(); } cancel(): void { @@ -66,6 +68,6 @@ export class TerminalChatWidget extends Disposable { this._widget?.acceptInput(); } layout(width: number): void { - this._widget?.layout(40, width); + this._widget?.layout(100, width < 300 ? 300 : width); } } From d06f94f18e302ff3e301a5fc81fcd1c429f4afdc Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 18:06:07 +0100 Subject: [PATCH 1074/1897] Async queue timeout (fix #204772) (#204842) --- src/vs/base/test/common/async.test.ts | 36 ++++++++++++++------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index 4c2e74ce39b..f5bb0232a7f 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -651,26 +651,28 @@ suite('Async', () => { }); test('order is kept', function () { - const queue = new async.Queue(); + return runWithFakedTimers({}, () => { + const queue = new async.Queue(); - const res: number[] = []; + const res: number[] = []; - const f1 = () => Promise.resolve(true).then(() => res.push(1)); - const f2 = () => async.timeout(10).then(() => res.push(2)); - const f3 = () => Promise.resolve(true).then(() => res.push(3)); - const f4 = () => async.timeout(20).then(() => res.push(4)); - const f5 = () => async.timeout(0).then(() => res.push(5)); + const f1 = () => Promise.resolve(true).then(() => res.push(1)); + const f2 = () => async.timeout(10).then(() => res.push(2)); + const f3 = () => Promise.resolve(true).then(() => res.push(3)); + const f4 = () => async.timeout(20).then(() => res.push(4)); + const f5 = () => async.timeout(0).then(() => res.push(5)); - queue.queue(f1); - queue.queue(f2); - queue.queue(f3); - queue.queue(f4); - return queue.queue(f5).then(() => { - assert.strictEqual(res[0], 1); - assert.strictEqual(res[1], 2); - assert.strictEqual(res[2], 3); - assert.strictEqual(res[3], 4); - assert.strictEqual(res[4], 5); + queue.queue(f1); + queue.queue(f2); + queue.queue(f3); + queue.queue(f4); + return queue.queue(f5).then(() => { + assert.strictEqual(res[0], 1); + assert.strictEqual(res[1], 2); + assert.strictEqual(res[2], 3); + assert.strictEqual(res[3], 4); + assert.strictEqual(res[4], 5); + }); }); }); From eedd668787628d52ff190f60a52a6f49522500b6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 09:23:24 -0800 Subject: [PATCH 1075/1897] Add setting to disable term inline chat by default --- src/vs/platform/terminal/common/terminal.ts | 5 +-- .../terminal/common/terminalConfiguration.ts | 5 +++ .../browser/terminal.chat.contribution.ts | 33 ++++++++++++++++--- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 56ebf9ddae5..6d291481c7f 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -121,10 +121,7 @@ export const enum TerminalSettingId { StickyScrollEnabled = 'terminal.integrated.stickyScroll.enabled', StickyScrollMaxLineCount = 'terminal.integrated.stickyScroll.maxLineCount', MouseWheelZoom = 'terminal.integrated.mouseWheelZoom', - FocusChat = 'workbench.action.terminal.focusChat', - HideChat = 'workbench.action.terminal.hideChat', - SubmitChat = 'workbench.action.terminal.submitChat', - CancelChat = 'workbench.action.terminal.cancelChat', + ExperimentalInlineChat = 'workbench.action.terminal.experimentalInlineChat', // Debug settings that are hidden from user diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index ac0903b8ae3..397ff3bc97a 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -653,6 +653,11 @@ const terminalConfiguration: IConfigurationNode = { type: 'boolean', default: false }, + [TerminalSettingId.ExperimentalInlineChat]: { + markdownDescription: localize('terminal.integrated.experimentalInlineChat', "Whether to enable the upcoming experimental inline terminal chat UI."), + type: 'boolean', + default: false + } } }; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index 46bef94963c..c0ee3d62f1b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -21,6 +21,8 @@ import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; import { Codicon } from 'vs/base/common/codicons'; import { MenuId } from 'vs/platform/actions/common/actions'; import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class TerminalChatContribution extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; @@ -39,17 +41,27 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon processManager: ITerminalProcessManager | ITerminalProcessInfo, widgetManager: TerminalWidgetManager, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IConfigurationService private _configurationService: IConfigurationService, @ITerminalService terminalService: ITerminalService ) { super(); + if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { + return; + } } layout(_xterm: IXtermTerminal & { raw: RawXtermTerminal }, dimension: IDimension): void { + if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { + return; + } this._lastLayoutDimensions = dimension; this._chatWidget?.rawValue?.layout(dimension.width); } xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { + if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { + return; + } this._chatWidget = new Lazy(() => { const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); @@ -82,7 +94,10 @@ registerActiveXtermAction({ weight: KeybindingWeight.WorkbenchContrib }, f1: true, - precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + ), run: (_xterm, _accessor, activeInstance) => { TerminalChatContribution.get(activeInstance)?.chatWidget?.reveal(); } @@ -98,7 +113,10 @@ registerActiveXtermAction({ weight: KeybindingWeight.WorkbenchContrib }, f1: true, - precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + ), run: (_xterm, _accessor, activeInstance) => { TerminalChatContribution.get(activeInstance)?.chatWidget?.hide(); } @@ -107,7 +125,11 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalCommandId.SubmitChat, title: localize2('workbench.action.terminal.submitChat', 'Terminal: Submit Chat'), - precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.chatInputHasText), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + TerminalContextKeys.chatInputHasText + ), icon: Codicon.send, menu: { id: MenuId.ChatExecute, @@ -122,7 +144,10 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalCommandId.CancelChat, title: localize2('workbench.action.terminal.cancelChat', 'Cancel Chat'), - precondition: TerminalContextKeys.chatSessionInProgress, + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalContextKeys.chatSessionInProgress, + ), icon: Codicon.debugStop, menu: { id: MenuId.ChatExecute, From 8220518134e8fa144941f5c0ee19cd30a6504acf Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 9 Feb 2024 18:35:14 +0100 Subject: [PATCH 1076/1897] fixing bug in get edits generation --- .../browser/inlineCompletionsModel.ts | 109 ++++++++++++++---- .../browser/inlineCompletionsModel.test.ts | 63 ++++++++++ 2 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 2faf2a0f8a9..b23c5718822 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -7,7 +7,7 @@ import { mapFindFirst } from 'vs/base/common/arraysFind'; import { BugIndicatingError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; import { IObservable, IReader, ITransaction, autorun, derived, derivedHandleChanges, derivedOpts, recomputeInitiallyAndOnChange, observableSignal, observableValue, subtransaction, transaction } from 'vs/base/common/observable'; -import { commonPrefixLength } from 'vs/base/common/strings'; +import { commonPrefixLength, splitLines } from 'vs/base/common/strings'; import { isDefined } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditOperation } from 'vs/editor/common/core/editOperation'; @@ -212,6 +212,7 @@ export class InlineCompletionsModel extends Disposable { const suggestItem = this.selectedSuggestItem.read(reader); if (suggestItem) { + console.log('first if statement'); const suggestCompletionEdit = suggestItem.toSingleTextEdit().removeCommonPrefix(model); const augmentation = this._computeAugmentation(suggestCompletionEdit, reader); @@ -219,29 +220,37 @@ export class InlineCompletionsModel extends Disposable { if (!isSuggestionPreviewEnabled && !augmentation) { return undefined; } const fullEdit = augmentation?.edit ?? suggestCompletionEdit; + console.log('fullEdit : ', fullEdit); const fullEditPreviewLength = augmentation ? augmentation.edit.text.length - suggestCompletionEdit.text.length : 0; const mode = this._suggestPreviewMode.read(reader); const positions = this._positions.read(reader); - const edits = [fullEdit, ...this._getSecondaryEdits(this.textModel, positions, fullEdit)]; + const edits = [fullEdit, ...getSecondaryEdits(this.textModel, positions, fullEdit)]; + console.log('edits : ', edits); const ghostTexts = edits .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], fullEditPreviewLength)) .filter(isDefined); const primaryGhostText = ghostTexts[0] ?? new GhostText(fullEdit.range.endLineNumber, []); + console.log('primaryGhostText : ', primaryGhostText); + console.log('ghostTexts : ', ghostTexts); return { edits, primaryGhostText, ghostTexts, inlineCompletion: augmentation?.completion, suggestItem }; } else { + console.log('second else statement'); if (!this._isActive.read(reader)) { return undefined; } const inlineCompletion = this.selectedInlineCompletion.read(reader); + console.log('inlineCompletion : ', inlineCompletion); if (!inlineCompletion) { return undefined; } const replacement = inlineCompletion.toSingleTextEdit(reader); const mode = this._inlineSuggestMode.read(reader); const positions = this._positions.read(reader); - const edits = [replacement, ...this._getSecondaryEdits(this.textModel, positions, replacement)]; + const edits = [replacement, ...getSecondaryEdits(this.textModel, positions, replacement)]; const ghostTexts = edits .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], 0)) .filter(isDefined); if (!ghostTexts[0]) { return undefined; } + console.log('primaryGhostText : ', ghostTexts[0]); + console.log('ghostTexts : ', ghostTexts); return { edits, primaryGhostText: ghostTexts[0], ghostTexts, inlineCompletion, suggestItem: undefined }; } }); @@ -402,6 +411,7 @@ export class InlineCompletionsModel extends Disposable { return; } const ghostText = state.primaryGhostText; + console.log('ghostText : ', ghostText); const completion = state.inlineCompletion.toInlineCompletion(undefined); if (completion.snippetInfo || completion.filterText !== completion.insertText) { @@ -413,13 +423,16 @@ export class InlineCompletionsModel extends Disposable { const firstPart = ghostText.parts[0]; const ghostTextPos = new Position(ghostText.lineNumber, firstPart.column); const ghostTextVal = firstPart.text; + console.log('ghostTextPos : ', ghostTextPos); + console.log('ghostTextVal : ', ghostTextVal); const acceptUntilIndexExclusive = getAcceptUntilIndex(ghostTextPos, ghostTextVal); + console.log('acceptUntilIndexExclusive : ', acceptUntilIndexExclusive); if (acceptUntilIndexExclusive === ghostTextVal.length && ghostText.parts.length === 1) { this.accept(editor); return; } const partialGhostTextVal = ghostTextVal.substring(0, acceptUntilIndexExclusive); - + console.log('partialGhostTextVal : ', partialGhostTextVal); const positions = this._positions.get(); const cursorPosition = positions[0]; @@ -430,9 +443,12 @@ export class InlineCompletionsModel extends Disposable { try { editor.pushUndoStop(); const replaceRange = Range.fromPositions(cursorPosition, ghostTextPos); + console.log('replaceRange : ', replaceRange); const newText = editor.getModel()!.getValueInRange(replaceRange) + partialGhostTextVal; + console.log('newText : ', newText); const primaryEdit = new SingleTextEdit(replaceRange, newText); - const edits = [primaryEdit, ...this._getSecondaryEdits(this.textModel, positions, primaryEdit)]; + console.log('primaryEdit : ', primaryEdit); + const edits = [primaryEdit, ...getSecondaryEdits(this.textModel, positions, primaryEdit)]; const selections = getEndPositionsAfterApplying(edits).map(p => Selection.fromPositions(p)); editor.executeEdits('inlineSuggestion.accept', edits.map(edit => EditOperation.replaceMove(edit.range, edit.text))); editor.setSelections(selections, 'inlineCompletionPartialAccept'); @@ -455,23 +471,6 @@ export class InlineCompletionsModel extends Disposable { } } - private _getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { - const primaryPosition = positions[0]; - const secondaryPositions = positions.slice(1); - const replacedTextAfterPrimaryCursor = textModel - .getLineContent(primaryPosition.lineNumber) - .substring(primaryPosition.column - 1, primaryEdit.range.endColumn - 1); - const secondaryEditText = primaryEdit.text.substring(primaryPosition.column - primaryEdit.range.startColumn); - return secondaryPositions.map(pos => { - const textAfterSecondaryCursor = this.textModel - .getLineContent(pos.lineNumber) - .substring(pos.column - 1); - const l = commonPrefixLength(replacedTextAfterPrimaryCursor, textAfterSecondaryCursor); - const range = Range.fromPositions(pos, pos.delta(0, l)); - return new SingleTextEdit(range, secondaryEditText); - }); - } - public handleSuggestAccepted(item: SuggestItemInfo) { const itemEdit = item.toSingleTextEdit().removeCommonPrefix(this.textModel); const augmentedCompletion = this._computeAugmentation(itemEdit, undefined); @@ -492,3 +491,69 @@ function getEndPositionsAfterApplying(edits: readonly SingleTextEdit[]): Positio const newRanges = sortPerm.inverse().apply(sortedNewRanges); return newRanges.map(range => range.getEndPosition()); } + +export function getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { + const primaryPosition = positions[0]; + const secondaryPositions = positions.slice(1); + console.log('positions : ', JSON.stringify(positions)); + // replaced text is not calculated correctly + // only works if primary edit line number + // Need to take all the that is replaced in the primary edit range + + const replacedTextAfterPrimaryCursor = textModel.getValueInRange(Range.fromPositions(primaryPosition, primaryEdit.range.getEndPosition())); + console.log('replacedTextAfterPrimaryCursor : ', JSON.stringify(replacedTextAfterPrimaryCursor)); + console.log('primaryEdit : ', JSON.stringify(primaryEdit)); + + // There is an error below too, the secondary edit text is the text after the cursor to the right of it, that needs to be added + // in the test case we would want to add ') {\n\treturn 0;\n}' because we already have fib( written. + // Before it worked because we would have the cursor at the end of function fib(, now the cursor is on the line below it + // Or at the very least, we should insert 'return 0;\n}' because this is to the right of the cursor at the primary cursor position + // So need to find the primary position within the edit, and find all the text to the right of it. The primary position will not necessarily be on the first + // line of the edit text. + // We suppose that the primaryEdit.range always touches the primaryPosition in some manner + + // could find the offset of primary position, the offset of the primary edit start and find thus the secondary edit text + // const _offsetPrimaryPosition = textModel.getOffsetAt(primaryPosition); + // const _offsetPrimaryEditStart = textModel.getOffsetAt(primaryEdit.range.getStartPosition()); + // console.log('_offsetPrimaryPosition : ', _offsetPrimaryPosition); + // console.log('_offsetPrimaryEditStart : ', _offsetPrimaryEditStart); + + // Find offset in a different way + // Split the lines of the text, place it in the context of the whole text + // Find the position in the text where the initial position would be, exactly as is, find the offset, and take the substring + + const newCol = primaryPosition.lineNumber - primaryEdit.range.startLineNumber; + const newLine = newCol === 0 ? primaryPosition.column - primaryEdit.range.startColumn : primaryPosition.column; + + let text = ''; + const _splitLines = splitLines(primaryEdit.text); + for (let i = newLine; i < _splitLines.length; i++) { + if (i === newLine) { + text += _splitLines[i].substring(newCol) + '\n'; + } else { + text += _splitLines[i] + '\n'; + } + } + console.log('text : ', text); + + const secondaryEditText = text; + // primaryEdit.text.substring(primaryPosition.column - primaryEdit.range.startColumn); + // console.log('secondaryEditText : ', JSON.stringify(secondaryEditText)); + return secondaryPositions.map(pos => { + console.log('pos : ', JSON.stringify(pos)); + // Maybe taking the substring on the line content specifically is not enough, so we need to actually take it until the range end, because that is the text we would replace + // the range end is not necessarily on the end of that line either + // const textAfterSecondaryCursor = textModel + // .getLineContent(pos.lineNumber) + // .substring(pos.column - 1); + + const textAfterSecondaryCursor = textModel.getValueInRange(Range.fromPositions(pos, primaryEdit.range.getEndPosition())); + console.log('textAfterSecondaryCursor : ', textAfterSecondaryCursor); + const l = commonPrefixLength(replacedTextAfterPrimaryCursor, textAfterSecondaryCursor); + console.log('l : ', l); + const range = Range.fromPositions(pos, pos.delta(0, l)); + console.log('range : ', JSON.stringify(range)); + return new SingleTextEdit(range, secondaryEditText); + }); +} + diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts new file mode 100644 index 00000000000..22c3a0c1987 --- /dev/null +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as assert from 'assert'; +import { Position } from 'vs/editor/common/core/position'; +import { getSecondaryEdits } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel'; +import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; +import { createTextModel } from 'vs/editor/test/common/testTextModel'; +import { Range } from 'vs/editor/common/core/range'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; + +suite('inlineCompletionModel', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('getSecondaryEdits - basic', async function () { + + const textModel = createTextModel([ + 'function fib(', + 'function fib(' + ].join('\n')); + const positions = [ + new Position(1, 14), + new Position(2, 14) + ]; + const primaryEdit = new SingleTextEdit(new Range(1, 1, 1, 14), 'function fib() {'); + const secondaryEdits = getSecondaryEdits(textModel, positions, primaryEdit); + assert.deepStrictEqual(secondaryEdits, [new SingleTextEdit( + new Range(2, 14, 2, 14), + ') {' + )]); + textModel.dispose(); + }); + + test('getSecondaryEdits - cursor not on same line as primary edit', async function () { + + const textModel = createTextModel([ + 'function fib(', + '', + 'function fib(', + '' + ].join('\n')); + const positions = [ + new Position(2, 1), + new Position(4, 1) + ]; + const primaryEdit = new SingleTextEdit(new Range(1, 1, 2, 1), [ + 'function fib() {', + ' return 0;', + '}' + ].join('\n')); + const secondaryEdits = getSecondaryEdits(textModel, positions, primaryEdit); + assert.deepStrictEqual(secondaryEdits, [new SingleTextEdit( + new Range(4, 1, 4, 1), [ + ' return 0;', + '}' + ].join('\n') + )]); + console.log('secondaryEdits : ', JSON.stringify(secondaryEdits)); + textModel.dispose(); + }); +}); From bb78bb1d393e0acb32c509871732117e8e737003 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 9 Feb 2024 18:37:19 +0100 Subject: [PATCH 1077/1897] removing some logs --- .../browser/inlineCompletionsModel.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index b23c5718822..e4d474d8238 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -212,7 +212,6 @@ export class InlineCompletionsModel extends Disposable { const suggestItem = this.selectedSuggestItem.read(reader); if (suggestItem) { - console.log('first if statement'); const suggestCompletionEdit = suggestItem.toSingleTextEdit().removeCommonPrefix(model); const augmentation = this._computeAugmentation(suggestCompletionEdit, reader); @@ -220,25 +219,19 @@ export class InlineCompletionsModel extends Disposable { if (!isSuggestionPreviewEnabled && !augmentation) { return undefined; } const fullEdit = augmentation?.edit ?? suggestCompletionEdit; - console.log('fullEdit : ', fullEdit); const fullEditPreviewLength = augmentation ? augmentation.edit.text.length - suggestCompletionEdit.text.length : 0; const mode = this._suggestPreviewMode.read(reader); const positions = this._positions.read(reader); const edits = [fullEdit, ...getSecondaryEdits(this.textModel, positions, fullEdit)]; - console.log('edits : ', edits); const ghostTexts = edits .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], fullEditPreviewLength)) .filter(isDefined); const primaryGhostText = ghostTexts[0] ?? new GhostText(fullEdit.range.endLineNumber, []); - console.log('primaryGhostText : ', primaryGhostText); - console.log('ghostTexts : ', ghostTexts); return { edits, primaryGhostText, ghostTexts, inlineCompletion: augmentation?.completion, suggestItem }; } else { - console.log('second else statement'); if (!this._isActive.read(reader)) { return undefined; } const inlineCompletion = this.selectedInlineCompletion.read(reader); - console.log('inlineCompletion : ', inlineCompletion); if (!inlineCompletion) { return undefined; } const replacement = inlineCompletion.toSingleTextEdit(reader); @@ -249,8 +242,6 @@ export class InlineCompletionsModel extends Disposable { .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], 0)) .filter(isDefined); if (!ghostTexts[0]) { return undefined; } - console.log('primaryGhostText : ', ghostTexts[0]); - console.log('ghostTexts : ', ghostTexts); return { edits, primaryGhostText: ghostTexts[0], ghostTexts, inlineCompletion, suggestItem: undefined }; } }); @@ -411,7 +402,6 @@ export class InlineCompletionsModel extends Disposable { return; } const ghostText = state.primaryGhostText; - console.log('ghostText : ', ghostText); const completion = state.inlineCompletion.toInlineCompletion(undefined); if (completion.snippetInfo || completion.filterText !== completion.insertText) { @@ -423,16 +413,13 @@ export class InlineCompletionsModel extends Disposable { const firstPart = ghostText.parts[0]; const ghostTextPos = new Position(ghostText.lineNumber, firstPart.column); const ghostTextVal = firstPart.text; - console.log('ghostTextPos : ', ghostTextPos); - console.log('ghostTextVal : ', ghostTextVal); const acceptUntilIndexExclusive = getAcceptUntilIndex(ghostTextPos, ghostTextVal); - console.log('acceptUntilIndexExclusive : ', acceptUntilIndexExclusive); if (acceptUntilIndexExclusive === ghostTextVal.length && ghostText.parts.length === 1) { this.accept(editor); return; } const partialGhostTextVal = ghostTextVal.substring(0, acceptUntilIndexExclusive); - console.log('partialGhostTextVal : ', partialGhostTextVal); + const positions = this._positions.get(); const cursorPosition = positions[0]; @@ -443,11 +430,8 @@ export class InlineCompletionsModel extends Disposable { try { editor.pushUndoStop(); const replaceRange = Range.fromPositions(cursorPosition, ghostTextPos); - console.log('replaceRange : ', replaceRange); const newText = editor.getModel()!.getValueInRange(replaceRange) + partialGhostTextVal; - console.log('newText : ', newText); const primaryEdit = new SingleTextEdit(replaceRange, newText); - console.log('primaryEdit : ', primaryEdit); const edits = [primaryEdit, ...getSecondaryEdits(this.textModel, positions, primaryEdit)]; const selections = getEndPositionsAfterApplying(edits).map(p => Selection.fromPositions(p)); editor.executeEdits('inlineSuggestion.accept', edits.map(edit => EditOperation.replaceMove(edit.range, edit.text))); From 92143ea2ddfba4a0aea73f19b8a4374e18da1992 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 9 Feb 2024 18:40:01 +0100 Subject: [PATCH 1078/1897] adding one more test --- .../browser/inlineCompletionsModel.test.ts | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts index 22c3a0c1987..c6bc78de063 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts @@ -33,7 +33,7 @@ suite('inlineCompletionModel', () => { textModel.dispose(); }); - test('getSecondaryEdits - cursor not on same line as primary edit', async function () { + test('getSecondaryEdits - cursor not on same line as primary edit 1', async function () { const textModel = createTextModel([ 'function fib(', @@ -60,4 +60,35 @@ suite('inlineCompletionModel', () => { console.log('secondaryEdits : ', JSON.stringify(secondaryEdits)); textModel.dispose(); }); + + test('getSecondaryEdits - cursor not on same line as primary edit 2', async function () { + + const textModel = createTextModel([ + 'class A {', + '', + 'class B {', + '', + 'function f() {}' + ].join('\n')); + const positions = [ + new Position(2, 1), + new Position(4, 1) + ]; + const primaryEdit = new SingleTextEdit(new Range(1, 1, 2, 1), [ + 'class A {', + ' public x: number = 0;', + ' public y: number = 0;', + '}' + ].join('\n')); + const secondaryEdits = getSecondaryEdits(textModel, positions, primaryEdit); + assert.deepStrictEqual(secondaryEdits, [new SingleTextEdit( + new Range(4, 1, 4, 1), [ + ' public x: number = 0;', + ' public y: number = 0;', + '}' + ].join('\n') + )]); + console.log('secondaryEdits : ', JSON.stringify(secondaryEdits)); + textModel.dispose(); + }); }); From 0d202eff3d87ae7cb3e943fb82fa9a98f6aeffcd Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 9 Feb 2024 11:46:48 -0600 Subject: [PATCH 1079/1897] widget per terminal --- .../browser/terminal.chat.contribution.ts | 33 ++++++++++++++----- .../chat/browser/terminalChatWidget.ts | 6 ++++ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index c0ee3d62f1b..b9fab74a3a2 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -11,7 +11,7 @@ import { localize2 } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; @@ -30,7 +30,11 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon static get(instance: ITerminalInstance | IDetachedTerminalInstance): TerminalChatContribution | null { return instance.getContribution(TerminalChatContribution.ID); } - + /** + * Currently focused chat widget. This is used to track action context since + * 'active terminals' are only tracked for non-detached terminal instanecs. + */ + static activeChatWidget?: TerminalChatContribution; private _chatWidget: Lazy | undefined; private _lastLayoutDimensions: IDimension | undefined; @@ -42,7 +46,7 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon widgetManager: TerminalWidgetManager, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IConfigurationService private _configurationService: IConfigurationService, - @ITerminalService terminalService: ITerminalService + @ITerminalService private readonly _terminalService: ITerminalService ) { super(); if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { @@ -64,7 +68,16 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon } this._chatWidget = new Lazy(() => { const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); - + chatWidget.focusTracker.onDidFocus(() => { + TerminalChatContribution.activeChatWidget = this; + if (!isDetachedTerminalInstance(this._instance)) { + this._terminalService.setActiveInstance(this._instance); + } + }); + chatWidget.focusTracker.onDidBlur(() => { + TerminalChatContribution.activeChatWidget = undefined; + this._instance.resetScrollbarVisibility(); + }); if (!this._instance.domElement) { throw new Error('FindWidget expected terminal DOM to be initialized'); } @@ -99,7 +112,8 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), ), run: (_xterm, _accessor, activeInstance) => { - TerminalChatContribution.get(activeInstance)?.chatWidget?.reveal(); + const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + contr?.chatWidget?.reveal(); } }); @@ -118,7 +132,8 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), ), run: (_xterm, _accessor, activeInstance) => { - TerminalChatContribution.get(activeInstance)?.chatWidget?.hide(); + const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + contr?.chatWidget?.hide(); } }); @@ -137,7 +152,8 @@ registerActiveXtermAction({ group: 'navigation', }, run: (_xterm, _accessor, activeInstance) => { - TerminalChatContribution.get(activeInstance)?.chatWidget?.acceptInput(); + const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + contr?.chatWidget?.acceptInput(); } }); @@ -154,6 +170,7 @@ registerActiveXtermAction({ group: 'navigation', }, run: (_xterm, _accessor, activeInstance) => { - TerminalChatContribution.get(activeInstance)?.chatWidget?.cancel(); + const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + contr?.chatWidget?.cancel(); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 4f0c787fc6b..de48c32fcf3 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IFocusTracker, trackFocus } from 'vs/base/browser/dom'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -18,6 +19,7 @@ export class TerminalChatWidget extends Disposable { private _widgetContainer: HTMLElement; private _chatWidgetFocused: IContextKey; private _chatWidgetVisible: IContextKey; + private readonly _focusTracker: IFocusTracker; constructor( private readonly _container: HTMLElement, private readonly _instance: ITerminalInstance | IDetachedTerminalInstance, @@ -44,6 +46,7 @@ export class TerminalChatWidget extends Disposable { })); this._widget.render(this._widgetContainer); this._register(this._widget.onDidFocus(() => this._chatWidgetFocused.set(true))); + this._focusTracker = this._register(trackFocus(this._widgetContainer)); } reveal(): void { this._widgetContainer.classList.remove('hide'); @@ -70,4 +73,7 @@ export class TerminalChatWidget extends Disposable { layout(width: number): void { this._widget?.layout(100, width < 300 ? 300 : width); } + public get focusTracker(): IFocusTracker { + return this._focusTracker; + } } From a2f2e5e2c392b6598c03545e93163541381b5c2a Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:51:56 -0600 Subject: [PATCH 1080/1897] fix findFiles useIgnoreFiles (#204845) --- src/vs/workbench/api/common/extHostWorkspace.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 65cee0c4faa..d240127a4d4 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -462,7 +462,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac maxResults, useDefaultExcludes: useFileExcludes, useDefaultSearchExcludes: false, - useIgnoreFiles: true + useIgnoreFiles: false }, token); } From 10e7518fbe0d4e9da230165470ed87558a70b86d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 09:57:23 -0800 Subject: [PATCH 1081/1897] WIP inline chat widget integration --- .../chat/browser/media/chat.css | 13 ++++ .../browser/terminal.chat.contribution.ts | 1 + .../chat/browser/terminalChatWidget.ts | 72 +++++++++++++------ 3 files changed, 65 insertions(+), 21 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/media/chat.css diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/chat.css new file mode 100644 index 00000000000..e81dc6b4752 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/chat.css @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.terminal-inline-chat { + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: 0; + z-index: 100; +} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index c0ee3d62f1b..8881c8f664a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/chat'; import { IDimension } from 'vs/base/browser/dom'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 4f0c787fc6b..76482c5c987 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -4,61 +4,91 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { editorBackground, editorForeground, inputBackground } from 'vs/platform/theme/common/colorRegistry'; import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; +import { MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; import { IDetachedTerminalInstance, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; export class TerminalChatWidget extends Disposable { - private _widget: ChatWidget; + private _widget!: ChatWidget; private _scopedInstantiationService: IInstantiationService; private _widgetContainer: HTMLElement; private _chatWidgetFocused: IContextKey; private _chatWidgetVisible: IContextKey; + + private readonly _inlineChatWidget: InlineChatWidget; + constructor( private readonly _container: HTMLElement, private readonly _instance: ITerminalInstance | IDetachedTerminalInstance, - @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService) { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); - this._scopedInstantiationService = this._instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); + this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); this._chatWidgetFocused = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); this._chatWidgetVisible = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); this._widgetContainer = document.createElement('div'); - this._widgetContainer.classList.add('terminal-chat-widget'); + this._widgetContainer.classList.add('terminal-inline-chat'); this._container.appendChild(this._widgetContainer); - this._widget = this._register(this._scopedInstantiationService.createInstance( - ChatWidget, - { viewId: 'terminal' }, - { supportsFileReferences: false, renderStyle: 'compact' }, + // this._widget = this._register(this._scopedInstantiationService.createInstance( + // ChatWidget, + // { viewId: 'terminal' }, + // { supportsFileReferences: false, renderStyle: 'compact' }, + // { + // listForeground: editorForeground, + // listBackground: editorBackground, + // inputEditorBackground: inputBackground, + // resultEditorBackground: editorBackground + // })); + // this._widget.render(this._widgetContainer); + // this._register(this._widget.onDidFocus(() => this._chatWidgetFocused.set(true))); + + const fakeParentEditorElement = document.createElement('div'); + + // const editorConstructionOptions = this.inputEditorOptions.getEditorConstructionOptions(); + // this.setPlaceholderFontStyles(editorConstructionOptions.fontFamily!, editorConstructionOptions.fontSize!, editorConstructionOptions.lineHeight!); + + const fakeParentEditor = this._scopedInstantiationService.createInstance( + CodeEditorWidget, + fakeParentEditorElement, + {}, + { isSimpleWidget: true } + ); + + this._inlineChatWidget = this._scopedInstantiationService.createInstance( + InlineChatWidget, + fakeParentEditor, { - listForeground: editorForeground, - listBackground: editorBackground, - inputEditorBackground: inputBackground, - resultEditorBackground: editorBackground - })); - this._widget.render(this._widgetContainer); - this._register(this._widget.onDidFocus(() => this._chatWidgetFocused.set(true))); + menuId: MENU_CELL_CHAT_INPUT, + widgetMenuId: MENU_CELL_CHAT_WIDGET, + statusMenuId: MENU_CELL_CHAT_WIDGET_STATUS, + feedbackMenuId: MENU_CELL_CHAT_WIDGET_FEEDBACK + } + ); + + this._widgetContainer.appendChild(this._inlineChatWidget.domNode); } reveal(): void { this._widgetContainer.classList.remove('hide'); - this._widget.setVisible(true); + // this._widget.setVisible(true); this._chatWidgetFocused.set(true); this._chatWidgetVisible.set(true); - this._widget.setInput('@terminal'); - this._widget.setInputPlaceholder('Request a terminal command'); - this._widget.focusInput(); + // this._widget.setInput('@terminal'); + // this._widget.setInputPlaceholder('Request a terminal command'); + // this._widget.focusInput(); } hide(): void { this._widgetContainer.classList.add('hide'); this._chatWidgetFocused.set(false); this._chatWidgetVisible.set(false); - this._widget.clear(); + // this._widget.clear(); this._instance.focus(); } cancel(): void { From 73e7e46e8056b36e74118d23304d65c8c959819a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:17:35 -0800 Subject: [PATCH 1082/1897] More styled inline chat --- .../contrib/inlineChat/browser/inlineChat.css | 150 +++++++++--------- .../{chat.css => terminalChatWidget.css} | 5 +- .../browser/terminal.chat.contribution.ts | 1 - .../chat/browser/terminalChatWidget.ts | 13 +- 4 files changed, 85 insertions(+), 84 deletions(-) rename src/vs/workbench/contrib/terminalContrib/chat/browser/media/{chat.css => terminalChatWidget.css} (86%) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css index ee46f6df25e..862f0183354 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css @@ -11,7 +11,7 @@ background-color: var(--vscode-inlineChat-regionHighlight); } -.monaco-editor .inline-chat { +.inline-chat { color: inherit; padding: 6px; margin-top: 6px; @@ -23,11 +23,11 @@ /* body */ -.monaco-editor .inline-chat .body { +.inline-chat .body { display: flex; } -.monaco-editor .inline-chat .body .content { +.inline-chat .body .content { display: flex; box-sizing: border-box; outline: 1px solid var(--vscode-inlineChatInput-border); @@ -35,11 +35,11 @@ border-radius: 2px; } -.monaco-editor .inline-chat .body .content.synthetic-focus { +.inline-chat .body .content.synthetic-focus { outline: 1px solid var(--vscode-inlineChatInput-focusBorder); } -.monaco-editor .inline-chat .body .content .input { +.inline-chat .body .content .input { display: flex; align-items: center; justify-content: space-between; @@ -48,11 +48,11 @@ cursor: text; } -.monaco-editor .inline-chat .body .content .input .monaco-editor-background { +.inline-chat .body .content .input .monaco-editor-background { background-color: var(--vscode-inlineChatInput-background); } -.monaco-editor .inline-chat .body .content .input .editor-placeholder { +.inline-chat .body .content .input .editor-placeholder { position: absolute; z-index: 1; color: var(--vscode-inlineChatInput-placeholderForeground); @@ -61,14 +61,14 @@ text-overflow: ellipsis; } -.monaco-editor .inline-chat .body .content .input .editor-placeholder.hidden { +.inline-chat .body .content .input .editor-placeholder.hidden { display: none; } -.monaco-editor .inline-chat .body .content .input .editor-container { +.inline-chat .body .content .input .editor-container { vertical-align: middle; } -.monaco-editor .inline-chat .body .toolbar { +.inline-chat .body .toolbar { display: flex; flex-direction: column; align-self: stretch; @@ -78,47 +78,47 @@ background: var(--vscode-inlineChatInput-background); } -.monaco-editor .inline-chat .body .toolbar .actions-container { +.inline-chat .body .toolbar .actions-container { display: flex; flex-direction: row; gap: 4px; } -.monaco-editor .inline-chat .body > .widget-toolbar { +.inline-chat .body > .widget-toolbar { padding-left: 4px; } /* progress bit */ -.monaco-editor .inline-chat .progress { +.inline-chat .progress { position: relative; width: calc(100% - 18px); left: 19px; } /* UGLY - fighting against workbench styles */ -.monaco-workbench .part.editor > .content .monaco-editor .inline-chat .progress .monaco-progress-container { +.monaco-workbench .part.editor > .content .inline-chat .progress .monaco-progress-container { top: 0; } /* status */ -.monaco-editor .inline-chat .status { +.inline-chat .status { margin-top: 4px; display: flex; justify-content: space-between; align-items: center; } -.monaco-editor .inline-chat .status.actions { +.inline-chat .status.actions { margin-top: 4px; } -.monaco-editor .inline-chat .status .actions.hidden { +.inline-chat .status .actions.hidden { display: none; } -.monaco-editor .inline-chat .status .label { +.inline-chat .status .label { overflow: hidden; color: var(--vscode-descriptionForeground); font-size: 11px; @@ -126,60 +126,60 @@ display: inline-flex; } -.monaco-editor .inline-chat .status .label.hidden { +.inline-chat .status .label.hidden { display: none; } -.monaco-editor .inline-chat .status .label.info { +.inline-chat .status .label.info { margin-right: auto; padding-left: 2px; } -.monaco-editor .inline-chat .status .label.info > .codicon { +.inline-chat .status .label.info > .codicon { padding: 0 5px; font-size: 12px; line-height: 18px; } -.monaco-editor .inline-chat .status .label.status { +.inline-chat .status .label.status { padding-left: 10px; padding-right: 4px; margin-left: auto; } -.monaco-editor .inline-chat .status .label .slash-command-pill CODE { +.inline-chat .status .label .slash-command-pill CODE { border-radius: 3px; padding: 0 1px; background-color: var(--vscode-chat-slashCommandBackground); color: var(--vscode-chat-slashCommandForeground); } -.monaco-editor .inline-chat .detectedIntent { +.inline-chat .detectedIntent { color: var(--vscode-descriptionForeground); padding: 5px 0px 5px 5px; } -.monaco-editor .inline-chat .detectedIntent.hidden { +.inline-chat .detectedIntent.hidden { display: none; } -.monaco-editor .inline-chat .detectedIntent .slash-command-pill CODE { +.inline-chat .detectedIntent .slash-command-pill CODE { border-radius: 3px; padding: 0 1px; background-color: var(--vscode-chat-slashCommandBackground); color: var(--vscode-chat-slashCommandForeground); } -.monaco-editor .inline-chat .detectedIntent .slash-command-pill a { +.inline-chat .detectedIntent .slash-command-pill a { color: var(--vscode-textLink-foreground); cursor: pointer; } -/* .monaco-editor .inline-chat .markdownMessage .message * { +/* .inline-chat .markdownMessage .message * { margin: unset; } -.monaco-editor .inline-chat .markdownMessage .message code { +.inline-chat .markdownMessage .message code { font-family: var(--monaco-monospace-font); font-size: 12px; color: var(--vscode-textPreformat-foreground); @@ -189,7 +189,7 @@ } */ -.monaco-editor .inline-chat .chatMessage .chatMessageContent .value { +.inline-chat .chatMessage .chatMessageContent .value { -webkit-line-clamp: initial; -webkit-box-orient: vertical; overflow: hidden; @@ -198,130 +198,130 @@ user-select: text; } -.monaco-editor .inline-chat .chatMessage .chatMessageContent[state="cropped"] .value { +.inline-chat .chatMessage .chatMessageContent[state="cropped"] .value { -webkit-line-clamp: var(--vscode-inline-chat-cropped, 3); } -.monaco-editor .inline-chat .chatMessage .chatMessageContent[state="expanded"] .value { +.inline-chat .chatMessage .chatMessageContent[state="expanded"] .value { -webkit-line-clamp: var(--vscode-inline-chat-expanded, 10); } -.monaco-editor .inline-chat .followUps { +.inline-chat .followUps { padding: 5px 5px; } -.monaco-editor .inline-chat .followUps .interactive-session-followups .monaco-button { +.inline-chat .followUps .interactive-session-followups .monaco-button { display: block; color: var(--vscode-textLink-foreground); font-size: 12px; } -.monaco-editor .inline-chat .followUps.hidden { +.inline-chat .followUps.hidden { display: none; } -.monaco-editor .inline-chat .chatMessage { +.inline-chat .chatMessage { padding: 8px 3px; } -.monaco-editor .inline-chat .chatMessage .chatMessageContent { +.inline-chat .chatMessage .chatMessageContent { padding: 2px 2px; } -.monaco-editor .inline-chat .chatMessage.hidden { +.inline-chat .chatMessage.hidden { display: none; } -.monaco-editor .inline-chat .status .label A { +.inline-chat .status .label A { color: var(--vscode-textLink-foreground); cursor: pointer; } -.monaco-editor .inline-chat .status .label.error { +.inline-chat .status .label.error { color: var(--vscode-errorForeground); } -.monaco-editor .inline-chat .status .label.warn { +.inline-chat .status .label.warn { color: var(--vscode-editorWarning-foreground); } -.monaco-editor .inline-chat .status .actions { +.inline-chat .status .actions { display: flex; } -.monaco-editor .inline-chat .status .actions > .monaco-button, -.monaco-editor .inline-chat .status .actions > .monaco-button-dropdown { +.inline-chat .status .actions > .monaco-button, +.inline-chat .status .actions > .monaco-button-dropdown { margin-right: 6px; } -.monaco-editor .inline-chat .status .actions > .monaco-button-dropdown > .monaco-dropdown-button { +.inline-chat .status .actions > .monaco-button-dropdown > .monaco-dropdown-button { display: flex; align-items: center; padding: 0 4px; } -.monaco-editor .inline-chat .status .actions > .monaco-button.codicon { +.inline-chat .status .actions > .monaco-button.codicon { display: flex; } -.monaco-editor .inline-chat .status .actions > .monaco-button.codicon::before { +.inline-chat .status .actions > .monaco-button.codicon::before { align-self: center; } -.monaco-editor .inline-chat .status .actions .monaco-text-button { +.inline-chat .status .actions .monaco-text-button { padding: 2px 4px; white-space: nowrap; } -.monaco-editor .inline-chat .status .monaco-toolbar .action-item { +.inline-chat .status .monaco-toolbar .action-item { padding: 0 2px; } /* TODO@jrieken not needed? */ -.monaco-editor .inline-chat .status .monaco-toolbar .action-label.checked { +.inline-chat .status .monaco-toolbar .action-label.checked { color: var(--vscode-inputOption-activeForeground); background-color: var(--vscode-inputOption-activeBackground); outline: 1px solid var(--vscode-inputOption-activeBorder); } -.monaco-editor .inline-chat .status .monaco-toolbar .action-item.button-item .action-label:is(:hover, :focus) { +.inline-chat .status .monaco-toolbar .action-item.button-item .action-label:is(:hover, :focus) { background-color: var(--vscode-button-hoverBackground); } /* preview */ -.monaco-editor .inline-chat .preview { +.inline-chat .preview { display: none; } -.monaco-editor .inline-chat .previewDiff, -.monaco-editor .inline-chat .previewCreate { +.inline-chat .previewDiff, +.inline-chat .previewCreate { display: inherit; border: 1px solid var(--vscode-inlineChat-border); border-radius: 2px; margin: 6px 0px; } -.monaco-editor .inline-chat .previewCreateTitle { +.inline-chat .previewCreateTitle { padding-top: 6px; } -.monaco-editor .inline-chat .previewDiff.hidden, -.monaco-editor .inline-chat .previewCreate.hidden, -.monaco-editor .inline-chat .previewCreateTitle.hidden { +.inline-chat .previewDiff.hidden, +.inline-chat .previewCreate.hidden, +.inline-chat .previewCreateTitle.hidden { display: none; } -.monaco-editor .inline-chat-toolbar { +.inline-chat-toolbar { display: flex; } -.monaco-editor .inline-chat-toolbar > .monaco-button{ +.inline-chat-toolbar > .monaco-button{ margin-right: 6px; } -.monaco-editor .inline-chat-toolbar .action-label.checked { +.inline-chat-toolbar .action-label.checked { color: var(--vscode-inputOption-activeForeground); background-color: var(--vscode-inputOption-activeBackground); outline: 1px solid var(--vscode-inputOption-activeBorder); @@ -329,65 +329,65 @@ /* decoration styles */ -.monaco-editor .inline-chat-inserted-range { +.inline-chat-inserted-range { background-color: var(--vscode-inlineChatDiff-inserted); } -.monaco-editor .inline-chat-inserted-range-linehighlight { +.inline-chat-inserted-range-linehighlight { background-color: var(--vscode-diffEditor-insertedLineBackground); } -.monaco-editor .inline-chat-original-zone2 { +.inline-chat-original-zone2 { background-color: var(--vscode-diffEditor-removedLineBackground); opacity: 0.8; } -.monaco-editor .inline-chat-lines-inserted-range { +.inline-chat-lines-inserted-range { background-color: var(--vscode-diffEditor-insertedTextBackground); } -.monaco-editor .inline-chat-block-selection { +.inline-chat-block-selection { background-color: var(--vscode-inlineChat-regionHighlight); } -.monaco-editor .inline-chat-slash-command { +.inline-chat-slash-command { opacity: 0; } -.monaco-editor .inline-chat-slash-command-detail { +.inline-chat-slash-command-detail { opacity: 0.5; } /* diff zone */ -.monaco-editor .inline-chat-diff-widget .monaco-diff-editor .monaco-editor-background, -.monaco-editor .inline-chat-diff-widget .monaco-diff-editor .monaco-editor .margin-view-overlays { +.inline-chat-diff-widget .monaco-diff-editor .monaco-editor-background, +.inline-chat-diff-widget .monaco-diff-editor .monaco-editor .margin-view-overlays { background-color: var(--vscode-inlineChat-regionHighlight); } /* create zone */ -.monaco-editor .inline-chat-newfile-widget { +.inline-chat-newfile-widget { background-color: var(--vscode-inlineChat-regionHighlight); } -.monaco-editor .inline-chat-newfile-widget .title { +.inline-chat-newfile-widget .title { display: flex; align-items: center; justify-content: space-between; } -.monaco-editor .inline-chat-newfile-widget .title .detail { +.inline-chat-newfile-widget .title .detail { margin-left: 4px; } -.monaco-editor .inline-chat-newfile-widget .buttonbar-widget { +.inline-chat-newfile-widget .buttonbar-widget { display: flex; margin-left: auto; margin-right: 8px; } -.monaco-editor .inline-chat-newfile-widget .buttonbar-widget > .monaco-button { +.inline-chat-newfile-widget .buttonbar-widget > .monaco-button { display: inline-flex; white-space: nowrap; margin-left: 4px; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css similarity index 86% rename from src/vs/workbench/contrib/terminalContrib/chat/browser/media/chat.css rename to src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css index e81dc6b4752..5c8676bbe6e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css @@ -6,8 +6,9 @@ .terminal-inline-chat { position: absolute; left: 0; - top: 0; bottom: 0; - right: 0; z-index: 100; } +/* .terminal-inline-chat .inline-chat .body { + display: flex; +} */ diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index c210f1c4494..b9fab74a3a2 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/chat'; import { IDimension } from 'vs/base/browser/dom'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index d8c1d5180c7..7c67b161aba 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -3,20 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IFocusTracker, trackFocus } from 'vs/base/browser/dom'; +import 'vs/css!./media/terminalChatWidget'; +import { Dimension, IFocusTracker, trackFocus } from 'vs/base/browser/dom'; import { Disposable } from 'vs/base/common/lifecycle'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; import { IDetachedTerminalInstance, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; export class TerminalChatWidget extends Disposable { - private _widget!: ChatWidget; private _scopedInstantiationService: IInstantiationService; private _widgetContainer: HTMLElement; private _chatWidgetFocused: IContextKey; @@ -81,6 +80,8 @@ export class TerminalChatWidget extends Disposable { this._focusTracker = this._register(trackFocus(this._widgetContainer)); } reveal(): void { + this._inlineChatWidget.layout(new Dimension(400, 150)); + this._widgetContainer.classList.remove('hide'); // this._widget.setVisible(true); this._chatWidgetFocused.set(true); @@ -97,13 +98,13 @@ export class TerminalChatWidget extends Disposable { this._instance.focus(); } cancel(): void { - this._widget?.clear(); + // this._widget?.clear(); } acceptInput(): void { - this._widget?.acceptInput(); + // this._widget?.acceptInput(); } layout(width: number): void { - this._widget?.layout(100, width < 300 ? 300 : width); + // this._widget?.layout(100, width < 300 ? 300 : width); } public get focusTracker(): IFocusTracker { return this._focusTracker; From 22670a1031a0af3df9cc13f267eaa3dd16626bb2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:21:09 -0800 Subject: [PATCH 1083/1897] Add temp placeholders --- .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 7c67b161aba..041ec3b9573 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -14,6 +14,7 @@ import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inline import { MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; import { IDetachedTerminalInstance, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { localize } from 'vs/nls'; export class TerminalChatWidget extends Disposable { private _scopedInstantiationService: IInstantiationService; @@ -74,6 +75,8 @@ export class TerminalChatWidget extends Disposable { feedbackMenuId: MENU_CELL_CHAT_WIDGET_FEEDBACK } ); + this._inlineChatWidget.placeholder = localize('default.placeholder', "Ask how to do something in the terminal"); + this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); this._widgetContainer.appendChild(this._inlineChatWidget.domNode); From 792fc7a419483cf0b1bd6a496e4b68307574d479 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 9 Feb 2024 12:24:13 -0600 Subject: [PATCH 1084/1897] fix focus issues --- src/vs/workbench/contrib/terminal/browser/media/terminal.css | 2 +- .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 8fc9e6be0eb..8fcb3c71172 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -572,6 +572,6 @@ top: 10px; } -.monaco-workbench .terminal-chat-widget.hide { +.monaco-workbench .terminal-inline-chat.hide { visibility: hidden; } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 041ec3b9573..89c2518dd8e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -92,6 +92,7 @@ export class TerminalChatWidget extends Disposable { // this._widget.setInput('@terminal'); // this._widget.setInputPlaceholder('Request a terminal command'); // this._widget.focusInput(); + this._inlineChatWidget.focus(); } hide(): void { this._widgetContainer.classList.add('hide'); From dfed5c7b09b6ff0dd6d91de17875c8278e66e3d2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:31:03 -0800 Subject: [PATCH 1085/1897] Remove detached terminal support --- .../browser/terminal.chat.contribution.ts | 34 +++++++++++++------ .../chat/browser/terminalChatWidget.ts | 14 ++++---- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index b9fab74a3a2..e54914120bd 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -3,31 +3,31 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; import { IDimension } from 'vs/base/browser/dom'; +import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; import { localize2 } from 'vs/nls'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; -import { ITerminalProcessInfo, ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; -import { Codicon } from 'vs/base/common/codicons'; -import { MenuId } from 'vs/platform/actions/common/actions'; import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; -import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class TerminalChatContribution extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; - static get(instance: ITerminalInstance | IDetachedTerminalInstance): TerminalChatContribution | null { + static get(instance: ITerminalInstance): TerminalChatContribution | null { return instance.getContribution(TerminalChatContribution.ID); } /** @@ -41,8 +41,8 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } constructor( - private readonly _instance: ITerminalInstance | IDetachedTerminalInstance, - processManager: ITerminalProcessManager | ITerminalProcessInfo, + private readonly _instance: ITerminalInstance, + processManager: ITerminalProcessManager, widgetManager: TerminalWidgetManager, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IConfigurationService private _configurationService: IConfigurationService, @@ -96,7 +96,7 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon this._chatWidget?.rawValue?.dispose(); } } -registerTerminalContribution(TerminalChatContribution.ID, TerminalChatContribution, true); +registerTerminalContribution(TerminalChatContribution.ID, TerminalChatContribution, false); registerActiveXtermAction({ id: TerminalCommandId.FocusChat, @@ -112,6 +112,9 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), ), run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); contr?.chatWidget?.reveal(); } @@ -132,6 +135,9 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), ), run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); contr?.chatWidget?.hide(); } @@ -152,6 +158,9 @@ registerActiveXtermAction({ group: 'navigation', }, run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); contr?.chatWidget?.acceptInput(); } @@ -170,6 +179,9 @@ registerActiveXtermAction({ group: 'navigation', }, run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); contr?.chatWidget?.cancel(); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 89c2518dd8e..3a99cd5ebef 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -12,7 +12,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; -import { IDetachedTerminalInstance, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { localize } from 'vs/nls'; @@ -28,10 +28,10 @@ export class TerminalChatWidget extends Disposable { constructor( private readonly _container: HTMLElement, - private readonly _instance: ITerminalInstance | IDetachedTerminalInstance, - + private readonly _instance: ITerminalInstance, @IInstantiationService instantiationService: IInstantiationService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService) { + @IContextKeyService private readonly _contextKeyService: IContextKeyService + ) { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); @@ -61,7 +61,9 @@ export class TerminalChatWidget extends Disposable { const fakeParentEditor = this._scopedInstantiationService.createInstance( CodeEditorWidget, fakeParentEditorElement, - {}, + { + + }, { isSimpleWidget: true } ); @@ -76,7 +78,7 @@ export class TerminalChatWidget extends Disposable { } ); this._inlineChatWidget.placeholder = localize('default.placeholder', "Ask how to do something in the terminal"); - this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); + this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated commands may be incorrect")); this._widgetContainer.appendChild(this._inlineChatWidget.domNode); From a6f0b5cda38e244b3517bdf095f4e49d1404e144 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Fri, 9 Feb 2024 10:46:38 -0800 Subject: [PATCH 1086/1897] Getting Started Accessibility fixes (#204848) --- .../contrib/welcomeGettingStarted/browser/gettingStarted.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 9f171757fe7..7c23216426b 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -269,11 +269,13 @@ export class GettingStartedPage extends EditorPane { const badgeelements = assertIsDefined(getWindow(this.container).document.querySelectorAll(`[data-done-step-id="${step.id}"]`)); badgeelements.forEach(badgeelement => { if (step.done) { + badgeelement.setAttribute('aria-checked', 'true'); badgeelement.parentElement?.setAttribute('aria-checked', 'true'); badgeelement.classList.remove(...ThemeIcon.asClassNameArray(gettingStartedUncheckedCodicon)); badgeelement.classList.add('complete', ...ThemeIcon.asClassNameArray(gettingStartedCheckedCodicon)); } else { + badgeelement.setAttribute('aria-checked', 'false'); badgeelement.parentElement?.setAttribute('aria-checked', 'false'); badgeelement.classList.remove('complete', ...ThemeIcon.asClassNameArray(gettingStartedCheckedCodicon)); badgeelement.classList.add(...ThemeIcon.asClassNameArray(gettingStartedUncheckedCodicon)); @@ -1400,7 +1402,7 @@ export class GettingStartedPage extends EditorPane { 'x-dispatch': 'selectTask:' + step.id, 'data-step-id': step.id, 'aria-expanded': 'false', - 'aria-checked': '' + step.done, + 'aria-checked': step.done ? 'true' : 'false', 'role': 'button', }, codicon, From cfeb85db2be149ab3c0e5a6e466f02876acf5330 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:49:57 -0800 Subject: [PATCH 1087/1897] Anchor term chat to bottom --- .../terminalContrib/chat/browser/media/terminalChatWidget.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css index 5c8676bbe6e..633b6778dfe 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css @@ -8,6 +8,7 @@ left: 0; bottom: 0; z-index: 100; + height: auto !important; } /* .terminal-inline-chat .inline-chat .body { display: flex; From 0a8994cf3ac712a7ee9e5fa1ddbf82a70c8ce891 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 9 Feb 2024 12:53:06 -0600 Subject: [PATCH 1088/1897] use terminal menu ID --- src/vs/platform/actions/common/actions.ts | 1 + .../actions/voiceChatActions.ts | 6 ++++++ .../chat/browser/terminalChatWidget.ts | 20 +++++++++---------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 376627398ac..ec2810e91fb 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -208,6 +208,7 @@ export class MenuId { static readonly ChatCodeBlock = new MenuId('ChatCodeblock'); static readonly ChatMessageTitle = new MenuId('ChatMessageTitle'); static readonly ChatExecute = new MenuId('ChatExecute'); + static readonly TerminalChat = new MenuId('TerminalChat'); static readonly ChatInputSide = new MenuId('ChatInputSide'); static readonly AccessibleView = new MenuId('AccessibleView'); static readonly MultiDiffEditorFileToolbar = new MenuId('MultiDiffEditorFileToolbar'); diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 3b605a096d5..75f3ab3c832 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -475,6 +475,12 @@ export class StartVoiceChatAction extends Action2 { when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.negate()), group: 'main', order: -1 + }, + { + id: MenuId.TerminalChat, + when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.negate()), + group: 'main', + order: -1 }] }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 3a99cd5ebef..de24d36312d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -11,10 +11,11 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; -import { MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; -import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IDetachedTerminalInstance, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { localize } from 'vs/nls'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_STATUS, MENU_CELL_CHAT_WIDGET_FEEDBACK } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; export class TerminalChatWidget extends Disposable { private _scopedInstantiationService: IInstantiationService; @@ -28,10 +29,10 @@ export class TerminalChatWidget extends Disposable { constructor( private readonly _container: HTMLElement, - private readonly _instance: ITerminalInstance, + private readonly _instance: ITerminalInstance | IDetachedTerminalInstance, + @IInstantiationService instantiationService: IInstantiationService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService - ) { + @IContextKeyService private readonly _contextKeyService: IContextKeyService) { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); @@ -61,9 +62,7 @@ export class TerminalChatWidget extends Disposable { const fakeParentEditor = this._scopedInstantiationService.createInstance( CodeEditorWidget, fakeParentEditorElement, - { - - }, + {}, { isSimpleWidget: true } ); @@ -71,15 +70,14 @@ export class TerminalChatWidget extends Disposable { InlineChatWidget, fakeParentEditor, { - menuId: MENU_CELL_CHAT_INPUT, + menuId: MenuId.TerminalChat, widgetMenuId: MENU_CELL_CHAT_WIDGET, statusMenuId: MENU_CELL_CHAT_WIDGET_STATUS, feedbackMenuId: MENU_CELL_CHAT_WIDGET_FEEDBACK } ); this._inlineChatWidget.placeholder = localize('default.placeholder', "Ask how to do something in the terminal"); - this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated commands may be incorrect")); - + this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); this._widgetContainer.appendChild(this._inlineChatWidget.domNode); this._focusTracker = this._register(trackFocus(this._widgetContainer)); From 9e07b3ae1e411647d86531822b31178e044a4df5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:54:57 -0800 Subject: [PATCH 1089/1897] Remove detached terminal support --- .../terminalContrib/chat/browser/terminalChatWidget.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index de24d36312d..bef5d94f3d8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -11,7 +11,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; -import { IDetachedTerminalInstance, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; @@ -29,7 +29,7 @@ export class TerminalChatWidget extends Disposable { constructor( private readonly _container: HTMLElement, - private readonly _instance: ITerminalInstance | IDetachedTerminalInstance, + private readonly _instance: ITerminalInstance, @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService) { From 5e3bd936bcb55db758af7de1346549eff0d3f2f5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:59:31 -0800 Subject: [PATCH 1090/1897] Fix panel background overriding input's --- src/vs/workbench/browser/parts/panel/media/panelpart.css | 6 +++--- .../terminalContrib/chat/browser/terminalChatWidget.ts | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 4ccb53b0c80..7b17465b2d9 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -35,9 +35,9 @@ border-right-width: 0; /* no border when main editor area is hiden */ } -.monaco-workbench .part.panel > .content .monaco-editor, -.monaco-workbench .part.panel > .content .monaco-editor .margin, -.monaco-workbench .part.panel > .content .monaco-editor .monaco-editor-background { +.monaco-workbench .part.panel > .content .monaco-editor:not(.ignore-panel-bg), +.monaco-workbench .part.panel > .content .monaco-editor:not(.ignore-panel-bg) .margin, +.monaco-workbench .part.panel > .content .monaco-editor:not(.ignore-panel-bg) .monaco-editor-background { background-color: var(--vscode-panel-background); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index bef5d94f3d8..fc85c9a5a50 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -62,7 +62,9 @@ export class TerminalChatWidget extends Disposable { const fakeParentEditor = this._scopedInstantiationService.createInstance( CodeEditorWidget, fakeParentEditorElement, - {}, + { + extraEditorClassName: 'ignore-panel-bg' + }, { isSimpleWidget: true } ); From 2611151ba8966ba0c52c27ea0885a6cadd39c99a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:10:10 -0800 Subject: [PATCH 1091/1897] Add wip make request button --- .../contrib/terminal/common/terminal.ts | 2 +- .../browser/terminal.chat.contribution.ts | 22 ++++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 0579628b521..9a11ee3252c 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -502,7 +502,7 @@ export const enum TerminalCommandId { FontZoomReset = 'workbench.action.terminal.fontZoomReset', FocusChat = 'workbench.action.terminal.focusChat', HideChat = 'workbench.action.terminal.hideChat', - SubmitChat = 'workbench.action.terminal.submitChat', + MakeChatRequest = 'workbench.action.terminal.submitChat', CancelChat = 'workbench.action.terminal.cancelChat', // Developer commands diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index e54914120bd..6c44b62619c 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -100,7 +100,7 @@ registerTerminalContribution(TerminalChatContribution.ID, TerminalChatContributi registerActiveXtermAction({ id: TerminalCommandId.FocusChat, - title: localize2('workbench.action.terminal.focusChat', 'Terminal: Focus Chat'), + title: localize2('workbench.action.terminal.focusChat', 'Focus Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), @@ -122,7 +122,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalCommandId.HideChat, - title: localize2('workbench.action.terminal.hideChat', 'Terminal: Hide Chat'), + title: localize2('workbench.action.terminal.hideChat', 'Hide Chat'), keybinding: { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], @@ -144,18 +144,28 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.SubmitChat, - title: localize2('workbench.action.terminal.submitChat', 'Terminal: Submit Chat'), + id: TerminalCommandId.MakeChatRequest, + title: localize2('workbench.action.terminal.submitChat', 'Make Chat Request'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.chatInputHasText ), icon: Codicon.send, + keybinding: { + when: TerminalContextKeys.chatSessionInProgress, + // TODO: + // when: CTX_INLINE_CHAT_FOCUSED, + weight: KeybindingWeight.EditorCore + 7, + primary: KeyCode.Enter + }, menu: { - id: MenuId.ChatExecute, + id: MenuId.TerminalChat, + group: 'main', + order: 1, when: TerminalContextKeys.chatSessionInProgress.negate(), - group: 'navigation', + // TODO: + // when: CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.isEqualTo(false) }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { From a8cda8716fb80b41170aeb251b99a26b276e2bfc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:10:25 -0800 Subject: [PATCH 1092/1897] Fix command name --- src/vs/workbench/contrib/terminal/common/terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 9a11ee3252c..5ed9e674af2 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -502,7 +502,7 @@ export const enum TerminalCommandId { FontZoomReset = 'workbench.action.terminal.fontZoomReset', FocusChat = 'workbench.action.terminal.focusChat', HideChat = 'workbench.action.terminal.hideChat', - MakeChatRequest = 'workbench.action.terminal.submitChat', + MakeChatRequest = 'workbench.action.terminal.makeChatRequest', CancelChat = 'workbench.action.terminal.cancelChat', // Developer commands From f192601cda71416d2b4a03a2e4e6de4a2e1c9d95 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 9 Feb 2024 11:21:18 -0800 Subject: [PATCH 1093/1897] testing: fix single new child not appearing (#204850) * testing: fix single new child not appearing Fixes #204805 * add test case --- .../browser/explorerProjections/index.ts | 10 ++++- .../explorerProjections/treeProjection.ts | 11 ++++-- .../hierarchalByLocation.test.ts | 38 +++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts index e3e327ee8c0..29f03a52d11 100644 --- a/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts +++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts @@ -142,7 +142,15 @@ export type TestExplorerTreeElement = TestItemTreeElement | TestTreeErrorMessage export const testIdentityProvider: IIdentityProvider = { getId(element) { - return element.treeId + '\0' + (element instanceof TestTreeErrorMessage ? 'error' : element.test.expand); + // For "not expandable" elements, whether they have children is part of the + // ID so they're rerendered if that changes (#204805) + const expandComponent = element instanceof TestTreeErrorMessage + ? 'error' + : element.test.expand === TestItemExpandState.NotExpandable + ? !!element.children.size + : element.test.expand; + + return element.treeId + '\0' + expandComponent; } }; diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/treeProjection.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/treeProjection.ts index 2e0374be9cd..d7f385457ae 100644 --- a/src/vs/workbench/contrib/testing/browser/explorerProjections/treeProjection.ts +++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/treeProjection.ts @@ -302,9 +302,14 @@ export class TreeProjection extends Disposable implements ITestTreeProjection { treeElement.parent?.children.add(treeElement); this.items.set(treeElement.test.item.extId, treeElement); - // The first element will cause the root to be shown - const affectsRootElement = treeElement.depth === 1 && treeElement.parent?.children.size === 1; - this.changedParents.add(affectsRootElement ? null : treeElement.parent); + // The first element will cause the root to be shown. The first element of + // a parent may need to re-render it for #204805. + const affectsParent = treeElement.parent?.children.size === 1; + const affectedParent = affectsParent ? treeElement.parent.parent : treeElement.parent; + this.changedParents.add(affectedParent); + if (affectedParent?.depth === 0) { + this.changedParents.add(null); + } if (treeElement.depth === 0 || isCollapsedInSerializedTestTree(this.lastState, treeElement.test.item.extId) === false) { this.expandElement(treeElement, 0); diff --git a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts index 97b6ef85520..cebf9e01a5d 100644 --- a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts +++ b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts @@ -229,5 +229,43 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => { }); + test('fixes #204805', async () => { + harness.flush(); + harness.pushDiff({ + op: TestDiffOpType.Remove, + itemId: 'ctrlId', + }, { + op: TestDiffOpType.Add, + item: { controllerId: 'ctrlId', expand: TestItemExpandState.NotExpandable, item: new TestTestItem(new TestId(['ctrlId']), 'ctrl').toTestItem() }, + }, { + op: TestDiffOpType.Add, + item: { controllerId: 'ctrlId', expand: TestItemExpandState.NotExpandable, item: new TestTestItem(new TestId(['ctrlId', 'a']), 'a').toTestItem() }, + }); + + assert.deepStrictEqual(harness.flush(), [ + { e: 'a' } + ]); + + harness.pushDiff({ + op: TestDiffOpType.Add, + item: { controllerId: 'ctrlId', expand: TestItemExpandState.NotExpandable, item: new TestTestItem(new TestId(['ctrlId', 'a', 'b']), 'b').toTestItem() }, + }); + harness.flush(); + harness.tree.expandAll(); + assert.deepStrictEqual(harness.tree.getRendered(), [ + { e: 'a', children: [{ e: 'b' }] } + ]); + + harness.pushDiff({ + op: TestDiffOpType.Add, + item: { controllerId: 'ctrlId', expand: TestItemExpandState.NotExpandable, item: new TestTestItem(new TestId(['ctrlId', 'a', 'b', 'c']), 'c').toTestItem() }, + }); + harness.flush(); + harness.tree.expandAll(); + assert.deepStrictEqual(harness.tree.getRendered(), [ + { e: 'a', children: [{ e: 'b', children: [{ e: 'c' }] }] } + ]); + }); + }); From 2697c604f3ccf73757fe16441fde8f9838d59a3b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 20:34:21 +0100 Subject: [PATCH 1094/1897] voice - improve phrase detection (#204855) --- .../contrib/chat/common/voiceChat.ts | 85 ++++++++++++------- .../actions/voiceChatActions.ts | 2 +- .../chat/test/common/voiceChat.test.ts | 77 +++++++++-------- .../electron-sandbox/inlineChatQuickVoice.ts | 2 +- 4 files changed, 100 insertions(+), 66 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index 9b974c89b55..8d277ba9931 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -14,6 +14,10 @@ import { ISpeechService, ISpeechToTextEvent, SpeechToTextStatus } from 'vs/workb export const IVoiceChatService = createDecorator('voiceChatService'); +export interface IVoiceChatSessionOptions { + readonly usesAgents?: boolean; +} + export interface IVoiceChatService { readonly _serviceBrand: undefined; @@ -24,7 +28,7 @@ export interface IVoiceChatService { * if the user says "at workspace slash fix this problem", the result * will be "@workspace /fix this problem". */ - createVoiceChatSession(token: CancellationToken): IVoiceChatSession; + createVoiceChatSession(token: CancellationToken, options: IVoiceChatSessionOptions): IVoiceChatSession; } export interface IVoiceChatTextEvent extends ISpeechToTextEvent { @@ -41,6 +45,17 @@ export interface IVoiceChatSession extends IDisposable { readonly onDidChange: Event; } +interface IPhraseValue { + readonly agent: string; + readonly command?: string; +} + +enum PhraseTextType { + AGENT = 1, + COMMAND = 2, + AGENT_AND_COMMAND = 3 +} + export class VoiceChatService extends Disposable implements IVoiceChatService { readonly _serviceBrand: undefined; @@ -49,14 +64,14 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { private static readonly COMMAND_PREFIX = chatSubcommandLeader; private static readonly PHRASES = { - [VoiceChatService.AGENT_PREFIX]: 'at ', - [VoiceChatService.COMMAND_PREFIX]: 'slash ' + [VoiceChatService.AGENT_PREFIX]: 'at', + [VoiceChatService.COMMAND_PREFIX]: 'slash' }; private static readonly CHAT_AGENT_ALIAS = new Map([['vscode', 'code']]); - private _phrases: Map | undefined = undefined; - private get phrases(): Map { + private _phrases: Map | undefined = undefined; + private get phrases(): Map { if (!this._phrases) { this._phrases = this.createPhrases(); } @@ -77,23 +92,20 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { this._register(this.chatAgentService.onDidChangeAgents(() => this._phrases = undefined)); } - private createPhrases(): Map { - const phrases = new Map(); + private createPhrases(): Map { + const phrases = new Map(); for (const agent of this.chatAgentService.getAgents()) { - const agentPhrase = `${VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX]}${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase(); - const agentResult = `${VoiceChatService.AGENT_PREFIX}${agent.id}`; - phrases.set(agentPhrase, agentResult); + const agentPhrase = `${VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX]} ${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase(); + phrases.set(agentPhrase, { agent: agent.id }); if (agent.lastSlashCommands) { for (const slashCommand of agent.lastSlashCommands) { - const slashCommandPhrase = `${VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]}${slashCommand.name}`.toLowerCase(); - const slashCommandResult = `${VoiceChatService.COMMAND_PREFIX}${slashCommand.name}`; - phrases.set(slashCommandPhrase, slashCommandResult); + const slashCommandPhrase = `${VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]} ${slashCommand.name}`.toLowerCase(); + phrases.set(slashCommandPhrase, { agent: agent.id, command: slashCommand.name }); const agentSlashCommandPhrase = `${agentPhrase} ${slashCommandPhrase}`.toLowerCase(); - const agentSlashCommandResult = `${agentResult} ${slashCommandResult}`; - phrases.set(agentSlashCommandPhrase, agentSlashCommandResult); + phrases.set(agentSlashCommandPhrase, { agent: agent.id, command: slashCommand.name }); } } } @@ -101,7 +113,22 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { return phrases; } - createVoiceChatSession(token: CancellationToken): IVoiceChatSession { + private toText(value: IPhraseValue, type: PhraseTextType, options: IVoiceChatSessionOptions): string { + if (type === PhraseTextType.COMMAND && options.usesAgents) { + type = PhraseTextType.AGENT_AND_COMMAND; // rewrite `/fix` to `@workspace /foo` in this case + } + + switch (type) { + case PhraseTextType.AGENT: + return `${VoiceChatService.AGENT_PREFIX}${value.agent}`; + case PhraseTextType.COMMAND: + return `${VoiceChatService.COMMAND_PREFIX}${value.command}`; + case PhraseTextType.AGENT_AND_COMMAND: + return `${VoiceChatService.AGENT_PREFIX}${value.agent} ${VoiceChatService.COMMAND_PREFIX}${value.command}`; + } + } + + createVoiceChatSession(token: CancellationToken, options: IVoiceChatSessionOptions): IVoiceChatSession { const disposables = new DisposableStore(); let detectedAgent = false; @@ -114,8 +141,8 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { case SpeechToTextStatus.Recognizing: case SpeechToTextStatus.Recognized: if (e.text) { - const startsWithAgent = startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX].trim()); - const startsWithSlashCommand = startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX].trim()); + const startsWithAgent = startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX]); + const startsWithSlashCommand = startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]); if (startsWithAgent || startsWithSlashCommand) { const originalWords = e.text.split(' '); let transformedWords: string[] | undefined; @@ -123,10 +150,10 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { let waitingForInput = false; // Check for agent + slash command - if (startsWithAgent && !detectedAgent && !detectedSlashCommand && originalWords.length >= 4) { - const slashCommandResult = this.phrases.get(originalWords.slice(0, 4).map(word => this.normalizeWord(word)).join(' ')); - if (slashCommandResult) { - transformedWords = [slashCommandResult, ...originalWords.slice(4)]; + if (options.usesAgents && startsWithAgent && !detectedAgent && !detectedSlashCommand && originalWords.length >= 4) { + const phrase = this.phrases.get(originalWords.slice(0, 4).map(word => this.normalizeWord(word)).join(' ')); + if (phrase) { + transformedWords = [this.toText(phrase, PhraseTextType.AGENT_AND_COMMAND, options), ...originalWords.slice(4)]; waitingForInput = originalWords.length === 4; @@ -138,10 +165,10 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { } // Check for agent (if not done already) - if (startsWithAgent && !detectedAgent && !transformedWords && originalWords.length >= 2) { - const agentResult = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); - if (agentResult) { - transformedWords = [agentResult, ...originalWords.slice(2)]; + if (options.usesAgents && startsWithAgent && !detectedAgent && !transformedWords && originalWords.length >= 2) { + const phrase = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); + if (phrase) { + transformedWords = [this.toText(phrase, PhraseTextType.AGENT, options), ...originalWords.slice(2)]; waitingForInput = originalWords.length === 2; @@ -153,9 +180,9 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { // Check for slash command (if not done already) if (startsWithSlashCommand && !detectedSlashCommand && !transformedWords && originalWords.length >= 2) { - const slashCommandResult = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); - if (slashCommandResult) { - transformedWords = [slashCommandResult, ...originalWords.slice(2)]; + const phrase = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); + if (phrase) { + transformedWords = [this.toText(phrase, PhraseTextType.COMMAND, options), ...originalWords.slice(2)]; waitingForInput = originalWords.length === 2; diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index d433ccfecad..3d826bcaba8 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -274,7 +274,7 @@ class VoiceChatSessions { this.voiceChatGettingReadyKey.set(true); - const voiceChatSession = session.disposables.add(this.voiceChatService.createVoiceChatSession(cts.token)); + const voiceChatSession = session.disposables.add(this.voiceChatService.createVoiceChatSession(cts.token, { usesAgents: controller.context !== 'inline' })); let inputValue = controller.getInput(); diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index 2f87f56857d..d90f5b4e3ec 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -12,7 +12,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti import { ProviderResult } from 'vs/editor/common/languages'; import { IChatAgent, IChatAgentCommand, IChatAgentHistoryEntry, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgress, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; -import { IVoiceChatTextEvent, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; +import { IVoiceChatSessionOptions, IVoiceChatTextEvent, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; import { ISpeechProvider, ISpeechService, ISpeechToTextEvent, ISpeechToTextSession, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; suite('VoiceChat', () => { @@ -86,16 +86,15 @@ suite('VoiceChat', () => { let event: IVoiceChatTextEvent | undefined; let session: ISpeechToTextSession | undefined; - function createSession() { + function createSession(options: IVoiceChatSessionOptions) { session?.dispose(); - session = disposables.add(service.createVoiceChatSession(CancellationToken.None)); + session = disposables.add(service.createVoiceChatSession(CancellationToken.None, options)); disposables.add(session.onDidChange(e => { event = e; })); } - setup(() => { emitter = disposables.add(new Emitter()); service = disposables.add(new VoiceChatService(new TestSpeechService(), new TestChatAgentService())); @@ -105,10 +104,18 @@ suite('VoiceChat', () => { disposables.clear(); }); - test('Agent and slash command detection', async () => { + test('Agent and slash command detection (useAgents: false)', async () => { + testAgentsAndSlashCommandsDetection({ usesAgents: false }); + }); + + test('Agent and slash command detection (useAgents: true)', async () => { + testAgentsAndSlashCommandsDetection({ usesAgents: true }); + }); + + function testAgentsAndSlashCommandsDetection(options: IVoiceChatSessionOptions) { // Nothing to detect - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Started }); assert.strictEqual(event?.status, SpeechToTextStatus.Started); @@ -129,7 +136,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.waitingForInput, undefined); // Agent - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); @@ -137,113 +144,113 @@ suite('VoiceChat', () => { emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '@workspace'); - assert.strictEqual(event?.waitingForInput, true); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace' : 'At workspace'); + assert.strictEqual(event?.waitingForInput, options.usesAgents); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace help' : 'At workspace help'); assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); - assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace help' : 'At workspace help'); assert.strictEqual(event?.waitingForInput, false); // Agent with punctuation - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace help' : 'At workspace, help'); assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); - assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace help' : 'At workspace, help'); assert.strictEqual(event?.waitingForInput, false); - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At Workspace. help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace help' : 'At Workspace. help'); assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At Workspace. help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); - assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace help' : 'At Workspace. help'); assert.strictEqual(event?.waitingForInput, false); // Slash Command - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'Slash fix' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '/fix'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace /fix' : '/fix'); assert.strictEqual(event?.waitingForInput, true); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'Slash fix' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); - assert.strictEqual(event?.text, '/fix'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace /fix' : '/fix'); assert.strictEqual(event?.waitingForInput, true); // Agent + Slash Command - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code slash search help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.text, options.usesAgents ? '@vscode /search help' : 'At code slash search help'); assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code slash search help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); - assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.text, options.usesAgents ? '@vscode /search help' : 'At code slash search help'); assert.strictEqual(event?.waitingForInput, false); // Agent + Slash Command with punctuation - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code, slash search, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.text, options.usesAgents ? '@vscode /search help' : 'At code, slash search, help'); assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code, slash search, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); - assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.text, options.usesAgents ? '@vscode /search help' : 'At code, slash search, help'); assert.strictEqual(event?.waitingForInput, false); - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code. slash, search help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.text, options.usesAgents ? '@vscode /search help' : 'At code. slash, search help'); assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code. slash search, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); - assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.text, options.usesAgents ? '@vscode /search help' : 'At code. slash search, help'); assert.strictEqual(event?.waitingForInput, false); // Agent not detected twice - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace, for at workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '@workspace for at workspace'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace for at workspace' : 'At workspace, for at workspace'); assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace, for at workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); - assert.strictEqual(event?.text, '@workspace for at workspace'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace for at workspace' : 'At workspace, for at workspace'); assert.strictEqual(event?.waitingForInput, false); - }); + } test('waiting for input', async () => { // Agent - createSession(); + createSession({ usesAgents: true }); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); @@ -256,7 +263,7 @@ suite('VoiceChat', () => { assert.strictEqual(event.waitingForInput, true); // Slash Command - createSession(); + createSession({ usesAgents: true }); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace slash explain' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index 985ca40703b..60fea0a352c 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -240,7 +240,7 @@ export class InlineChatQuickVoice implements IEditorContribution { let message: string | undefined; let preview: string | undefined; - const session = this._voiceChatService.createVoiceChatSession(cts.token); + const session = this._voiceChatService.createVoiceChatSession(cts.token, { usesAgents: false }); const listener = session.onDidChange(e => { if (cts.token.isCancellationRequested) { From c9215c87ba4f6cd579dff6bbbe541cc97881cf30 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 9 Feb 2024 21:02:24 +0100 Subject: [PATCH 1095/1897] Git - fix commit action button when detached/rebase (#204857) --- extensions/git/src/actionButton.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/git/src/actionButton.ts b/extensions/git/src/actionButton.ts index d29bfcb2322..494972276ac 100644 --- a/extensions/git/src/actionButton.ts +++ b/extensions/git/src/actionButton.ts @@ -128,7 +128,7 @@ export class ActionButton { command: 'git.commit', title: l10n.t('{0} Continue', '$(check)'), tooltip: this.state.isCommitInProgress ? l10n.t('Continuing Rebase...') : l10n.t('Continue Rebase'), - arguments: [this.repository.sourceControl, ''] + arguments: [this.repository.sourceControl, null] }; } @@ -138,7 +138,7 @@ export class ActionButton { command: 'git.commit', title: l10n.t('{0} Commit', '$(check)'), tooltip: this.state.isCommitInProgress ? l10n.t('Committing Changes...') : l10n.t('Commit Changes'), - arguments: [this.repository.sourceControl, ''] + arguments: [this.repository.sourceControl, null] }; } From f10f059a541ab999c6863e755c66ea7af1468086 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 9 Feb 2024 12:19:16 -0800 Subject: [PATCH 1096/1897] Leverage AuthenticationProviders for gating Language Model Access (#204859) * Leverage AuthenticationProviders for gating Language Model Access Since we already have the Auth Stack which has a concept of "Managing Trusted Extensions" we can initially use that for gating Language Model Access so that when an extension asks for Language Model Access, they have to see a dialog first. * Support multiple models and create AuthProviders on Core side --- .../api/browser/mainThreadChatProvider.ts | 104 +++++++++++++++++- .../workbench/api/common/extHost.api.impl.ts | 4 +- .../api/common/extHostAuthentication.ts | 6 +- .../api/common/extHostChatProvider.ts | 31 +++++- .../browser/authenticationService.ts | 18 ++- .../authentication/common/authentication.ts | 21 ++++ 6 files changed, 166 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index dcf7e7ab3e0..1a1d046357a 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; -import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; @@ -12,8 +13,10 @@ import { IProgress, Progress } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; import { ExtHostChatProviderShape, ExtHostContext, MainContext, MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol'; import { IChatResponseProviderMetadata, IChatResponseFragment, IChatProviderService, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationProviderCreateSessionOptions, IAuthenticationService, INTERNAL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication'; import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @extHostNamedCustomer(MainContext.MainThreadChatProvider) export class MainThreadChatProvider implements MainThreadChatProviderShape { @@ -28,6 +31,8 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { @IChatProviderService private readonly _chatProviderService: IChatProviderService, @IExtensionFeaturesManagementService private readonly _extensionFeaturesManagementService: IExtensionFeaturesManagementService, @ILogService private readonly _logService: ILogService, + @IAuthenticationService private readonly _authenticationService: IAuthenticationService, + @IExtensionService private readonly _extensionService: IExtensionService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatProvider); @@ -54,6 +59,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { } } })); + dipsosables.add(this._registerAuthenticationProvider(identifier)); dipsosables.add(Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: `lm-${identifier}`, label: localize('languageModels', "Language Model ({0})", `${identifier}-${metadata.model}`), @@ -93,4 +99,100 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { return task; } + + private _registerAuthenticationProvider(identifier: string): IDisposable { + const disposables = new DisposableStore(); + // This needs to be done in both MainThread & ExtHost ChatProvider + const authProviderId = INTERNAL_AUTH_PROVIDER_PREFIX + identifier; + // This is what will be displayed in the UI and the account used for managing access via Auth UI + const authAccountId = identifier; + this._authenticationService.registerAuthenticationProvider(authProviderId, new LanguageModelAccessAuthProvider(authProviderId, authAccountId)); + disposables.add(toDisposable(() => { + this._authenticationService.unregisterAuthenticationProvider(authProviderId); + })); + disposables.add(this._authenticationService.onDidChangeSessions(async (e) => { + if (e.providerId === authProviderId) { + if (e.event.removed?.length) { + const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, authAccountId); + const extensionsToUpdateAccess = []; + for (const allowed of allowedExtensions) { + const extension = await this._extensionService.getExtension(allowed.id); + this._authenticationService.updateAllowedExtension(authProviderId, authAccountId, allowed.id, allowed.name, false); + if (extension) { + extensionsToUpdateAccess.push({ + extension: extension.identifier, + enabled: false + }); + } + } + this._proxy.$updateAccesslist(extensionsToUpdateAccess); + } + } + })); + disposables.add(this._authenticationService.onDidChangeExtensionSessionAccess(async (e) => { + const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, authAccountId); + const accessList = []; + for (const allowedExtension of allowedExtensions) { + const extension = await this._extensionService.getExtension(allowedExtension.id); + if (extension) { + accessList.push({ + extension: extension.identifier, + enabled: allowedExtension.allowed ?? true + }); + } + } + this._proxy.$updateAccesslist(accessList); + })); + return disposables; + } +} + +// The fake AuthenticationProvider that will be used to gate access to the Language Model. There will be one per provider. +class LanguageModelAccessAuthProvider implements IAuthenticationProvider { + supportsMultipleAccounts = false; + label = 'Language Model'; + + // Important for updating the UI + private _onDidChangeSessions: Emitter = new Emitter(); + onDidChangeSessions: Event = this._onDidChangeSessions.event; + + private _session: AuthenticationSession | undefined; + + constructor(readonly id: string, private readonly accountName: string) { } + + async getSessions(scopes?: string[] | undefined): Promise { + // If there are no scopes and no session that means no extension has requested a session yet + // and the user is simply opening the Account menu. In that case, we should not return any "sessions". + if (scopes === undefined && !this._session) { + return []; + } + if (this._session) { + return [this._session]; + } + return [await this.createSession(scopes || [], {})]; + } + async createSession(scopes: string[], options: IAuthenticationProviderCreateSessionOptions): Promise { + this._session = this._createFakeSession(scopes); + this._onDidChangeSessions.fire({ added: [this._session], changed: [], removed: [] }); + return this._session; + } + removeSession(sessionId: string): Promise { + if (this._session) { + this._onDidChangeSessions.fire({ added: [], changed: [], removed: [this._session!] }); + this._session = undefined; + } + return Promise.resolve(); + } + + private _createFakeSession(scopes: string[]): AuthenticationSession { + return { + id: 'fake-session', + account: { + id: this.id, + label: this.accountName, + }, + accessToken: 'fake-access-token', + scopes, + }; + } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index d899ada547e..a820c667796 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -207,7 +207,7 @@ 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 extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostChatProvider(rpcProtocol, extHostLogService, extHostAuthentication)); const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService, extHostCommands)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol)); @@ -1399,7 +1399,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, requestLanguageModelAccess(id, options) { checkProposedApiEnabled(extension, 'chatRequestAccess'); - return extHostChatProvider.requestLanguageModelAccess(extension.identifier, id, options); + return extHostChatProvider.requestLanguageModelAccess(extension, id, options); }, get languageModels() { checkProposedApiEnabled(extension, 'chatRequestAccess'); diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index ca07cbfef64..84ddbd6fa55 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -8,6 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IMainContext, MainContext, MainThreadAuthenticationShape, ExtHostAuthenticationShape } from 'vs/workbench/api/common/extHost.protocol'; import { Disposable } from 'vs/workbench/api/common/extHostTypes'; import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { INTERNAL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication'; interface ProviderWithMetadata { label: string; @@ -106,7 +107,10 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { } $onDidChangeAuthenticationSessions(id: string, label: string) { - this._onDidChangeSessions.fire({ provider: { id, label } }); + // Don't fire events for the internal auth providers + if (!id.startsWith(INTERNAL_AUTH_PROVIDER_PREFIX)) { + this._onDidChangeSessions.fire({ provider: { id, label } }); + } return Promise.resolve(); } } diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index b7a91c72e35..eae0f221ffa 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -11,9 +11,12 @@ 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, ExtensionIdentifierMap, ExtensionIdentifierSet } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { AsyncIterableSource } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; +import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; +import { localize } from 'vs/nls'; +import { INTERNAL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication'; type LanguageModelData = { readonly extension: ExtensionIdentifier; @@ -100,6 +103,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { constructor( mainContext: IMainContext, private readonly _logService: ILogService, + private readonly _extHostAuthentication: ExtHostAuthentication, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadChatProvider); } @@ -186,13 +190,15 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._onDidChangeAccess.fire(updated); } - async requestLanguageModelAccess(from: ExtensionIdentifier, languageModelId: string, options?: vscode.LanguageModelAccessOptions): Promise { + async requestLanguageModelAccess(extension: Readonly, languageModelId: string, options?: vscode.LanguageModelAccessOptions): Promise { + const from = extension.identifier; // check if the extension is in the access list and allowed to make chat requests if (this._accesslist.get(from) === false) { throw new Error('Extension is NOT allowed to make chat requests'); } - const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, options?.justification); + const justification = options?.justification; + const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, justification); if (!metadata) { if (!this._accesslist.get(from)) { @@ -200,6 +206,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { } throw new Error(`Language model '${languageModelId}' NOT found`); } + await this._checkAuthAccess(extension, languageModelId, justification); const that = this; @@ -244,4 +251,22 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { data.res.handleFragment(chunk); } } + + // BIG HACK: Using AuthenticationProviders to check access to Language Models + private async _checkAuthAccess(from: Readonly, languageModelId: string, detail?: string): Promise { + // This needs to be done in both MainThread & ExtHost ChatProvider + const providerId = INTERNAL_AUTH_PROVIDER_PREFIX + languageModelId; + const session = await this._extHostAuthentication.getSession(from, providerId, [], { silent: true }); + if (!session) { + try { + await this._extHostAuthentication.getSession(from, providerId, [], { + forceNewSession: { + detail: detail ?? localize('chatAccess', "To allow access to the '{0}' language model", languageModelId), + } + }); + } catch (err) { + throw new Error('Access to language model has not been granted'); + } + } + } } diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 78963a374a1..78e2a0c4137 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -24,7 +24,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { ISecretStorageService } from 'vs/platform/secrets/common/secrets'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; -import { IAuthenticationCreateSessionOptions, AuthenticationProviderInformation, AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; +import { IAuthenticationCreateSessionOptions, AuthenticationProviderInformation, AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationService, AllowedExtension } from 'vs/workbench/services/authentication/common/authentication'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IExtensionFeatureTableRenderer, IRenderedData, ITableData, IRowData, IExtensionFeaturesRegistry, Extensions } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -107,15 +107,6 @@ export async function getCurrentAuthenticationSessionInfo( return undefined; } -interface AllowedExtension { - id: string; - name: string; - allowed?: boolean; - lastUsed?: number; - // If true, this comes from the product.json - trusted?: boolean; -} - // OAuth2 spec prohibits space in a scope, so use that to join them. const SCOPESLIST_SEPARATOR = ' '; @@ -245,6 +236,9 @@ export class AuthenticationService extends Disposable implements IAuthentication private _onDidChangeDeclaredProviders: Emitter = this._register(new Emitter()); readonly onDidChangeDeclaredProviders: Event = this._onDidChangeDeclaredProviders.event; + private _onDidChangeExtensionSessionAccess: Emitter<{ providerId: string; accountName: string }> = this._register(new Emitter<{ providerId: string; accountName: string }>()); + readonly onDidChangeExtensionSessionAccess: Event<{ providerId: string; accountName: string }> = this._onDidChangeExtensionSessionAccess.event; + constructor( @IActivityService private readonly activityService: IActivityService, @IExtensionService private readonly extensionService: IExtensionService, @@ -816,7 +810,8 @@ export class AuthenticationService extends Disposable implements IAuthentication } } - private readAllowedExtensions(providerId: string, accountName: string): AllowedExtension[] { + // TODO: pull this stuff out into its own service + readAllowedExtensions(providerId: string, accountName: string): AllowedExtension[] { let trustedExtensions: AllowedExtension[] = []; try { const trustedExtensionSrc = this.storageService.get(`${providerId}-${accountName}`, StorageScope.APPLICATION); @@ -926,6 +921,7 @@ export class AuthenticationService extends Disposable implements IAuthentication .filter((item): item is TrustedExtensionsQuickPickItem => item.type !== 'separator') .map(i => i.extension); this.storageService.store(`${authProvider.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.APPLICATION, StorageTarget.USER); + this._onDidChangeExtensionSessionAccess.fire({ providerId: authProvider.id, accountName }); quickPick.hide(); })); diff --git a/src/vs/workbench/services/authentication/common/authentication.ts b/src/vs/workbench/services/authentication/common/authentication.ts index 0ff2179bfe2..eaeaffc56ac 100644 --- a/src/vs/workbench/services/authentication/common/authentication.ts +++ b/src/vs/workbench/services/authentication/common/authentication.ts @@ -5,6 +5,11 @@ import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +/** + * Use this if you don't want the onDidChangeSessions event to fire in the extension host + */ +export const INTERNAL_AUTH_PROVIDER_PREFIX = '__'; + export interface AuthenticationSessionAccount { label: string; id: string; @@ -34,6 +39,20 @@ export interface IAuthenticationCreateSessionOptions { activateImmediate?: boolean; } +export interface AllowedExtension { + id: string; + name: string; + /** + * If true or undefined, the extension is allowed to use the account + * If false, the extension is not allowed to use the account + * TODO: undefined shouldn't be a valid value, but it is for now + */ + allowed?: boolean; + lastUsed?: number; + // If true, this comes from the product.json + trusted?: boolean; +} + export const IAuthenticationService = createDecorator('IAuthenticationService'); export interface IAuthenticationService { @@ -58,6 +77,7 @@ export interface IAuthenticationService { readonly onDidUnregisterAuthenticationProvider: Event; readonly onDidChangeSessions: Event<{ providerId: string; label: string; event: AuthenticationSessionsChangeEvent }>; + readonly onDidChangeExtensionSessionAccess: Event<{ providerId: string; accountName: string }>; // TODO completely remove this property declaredProviders: AuthenticationProviderInformation[]; @@ -70,6 +90,7 @@ export interface IAuthenticationService { removeSession(providerId: string, sessionId: string): Promise; manageTrustedExtensionsForAccount(providerId: string, accountName: string): Promise; + readAllowedExtensions(providerId: string, accountName: string): AllowedExtension[]; removeAccountSessions(providerId: string, accountName: string, sessions: AuthenticationSession[]): Promise; } From 3883134f5355a8b84f7c1604c37bd6f58aaf6f5e Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 9 Feb 2024 12:58:02 -0800 Subject: [PATCH 1097/1897] correctly assign context to the tree view items (#204856) --- .../debug/browser/debug.contribution.ts | 4 +-- .../workbench/contrib/debug/common/debug.ts | 1 + .../notebookVariableCommands.ts | 2 +- .../notebookVariablesDataSource.ts | 1 + .../notebookVariablesView.ts | 36 +++++++++++-------- ...ode.proposed.notebookVariableProvider.d.ts | 3 ++ 6 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index a30c4d62dd8..d16e8095044 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -48,7 +48,7 @@ import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statu import { ADD_TO_WATCH_ID, BREAK_WHEN_VALUE_CHANGES_ID, BREAK_WHEN_VALUE_IS_ACCESSED_ID, BREAK_WHEN_VALUE_IS_READ_ID, COPY_EVALUATE_PATH_ID, COPY_VALUE_ID, SET_VARIABLE_ID, VIEW_MEMORY_ID, VariablesView } from 'vs/workbench/contrib/debug/browser/variablesView'; import { ADD_WATCH_ID, ADD_WATCH_LABEL, REMOVE_WATCH_EXPRESSIONS_COMMAND_ID, REMOVE_WATCH_EXPRESSIONS_LABEL, WatchExpressionsView } from 'vs/workbench/contrib/debug/browser/watchExpressionsView'; import { WelcomeView } from 'vs/workbench/contrib/debug/browser/welcomeView'; -import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CALLSTACK_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_UX, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_HAS_DEBUGGED, CONTEXT_IN_DEBUG_MODE, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_WATCH_ITEM_TYPE, DEBUG_PANEL_ID, DISASSEMBLY_VIEW_ID, EDITOR_CONTRIBUTION_ID, IDebugService, INTERNAL_CONSOLE_OPTIONS_SCHEMA, LOADED_SCRIPTS_VIEW_ID, REPL_VIEW_ID, State, VARIABLES_VIEW_ID, VIEWLET_ID, WATCH_VIEW_ID, getStateLabel } from 'vs/workbench/contrib/debug/common/debug'; +import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CALLSTACK_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_UX, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_HAS_DEBUGGED, CONTEXT_IN_DEBUG_MODE, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_VARIABLE_VALUE, CONTEXT_WATCH_ITEM_TYPE, DEBUG_PANEL_ID, DISASSEMBLY_VIEW_ID, EDITOR_CONTRIBUTION_ID, IDebugService, INTERNAL_CONSOLE_OPTIONS_SCHEMA, LOADED_SCRIPTS_VIEW_ID, REPL_VIEW_ID, State, VARIABLES_VIEW_ID, VIEWLET_ID, WATCH_VIEW_ID, getStateLabel } from 'vs/workbench/contrib/debug/common/debug'; import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; import { DebugLifecycle } from 'vs/workbench/contrib/debug/common/debugLifecycle'; import { DebugVisualizerService, IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; @@ -197,7 +197,7 @@ registerDebugViewMenuItem(MenuId.DebugWatchContext, VIEW_MEMORY_ID, nls.localize registerDebugViewMenuItem(MenuId.DebugWatchContext, REMOVE_EXPRESSION_COMMAND_ID, nls.localize('removeWatchExpression', "Remove Expression"), 20, CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression'), undefined, 'inline', icons.watchExpressionRemove); registerDebugViewMenuItem(MenuId.DebugWatchContext, REMOVE_WATCH_EXPRESSIONS_COMMAND_ID, REMOVE_WATCH_EXPRESSIONS_LABEL, 20, undefined, undefined, 'z_commands'); -registerDebugViewMenuItem(MenuId.NotebookVariablesContext, COPY_NOTEBOOK_VARIABLE_VALUE_ID, COPY_NOTEBOOK_VARIABLE_VALUE_LABEL, 20); +registerDebugViewMenuItem(MenuId.NotebookVariablesContext, COPY_NOTEBOOK_VARIABLE_VALUE_ID, COPY_NOTEBOOK_VARIABLE_VALUE_LABEL, 20, CONTEXT_VARIABLE_VALUE); // Touch Bar if (isMacintosh) { diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 06d374cbb80..c59762d498b 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -87,6 +87,7 @@ export const CONTEXT_VARIABLE_IS_READONLY = new RawContextKey('variable export const CONTEXT_VARIABLE_VALUE = new RawContextKey('variableValue', false, { type: 'string', description: nls.localize('variableValue', "Value of the variable, present for debug visualization clauses.") }); export const CONTEXT_VARIABLE_TYPE = new RawContextKey('variableType', false, { type: 'string', description: nls.localize('variableType', "Type of the variable, present for debug visualization clauses.") }); export const CONTEXT_VARIABLE_NAME = new RawContextKey('variableName', false, { type: 'string', description: nls.localize('variableName', "Name of the variable, present for debug visualization clauses.") }); +export const CONTEXT_VARIABLE_LANGUAGE = new RawContextKey('variableLanguage', false, { type: 'string', description: nls.localize('variableLanguage', "Language of the variable source, present for debug visualization clauses.") }); export const CONTEXT_EXCEPTION_WIDGET_VISIBLE = new RawContextKey('exceptionWidgetVisible', false, { type: 'boolean', description: nls.localize('exceptionWidgetVisible', "True when the exception widget is visible.") }); export const CONTEXT_MULTI_SESSION_REPL = new RawContextKey('multiSessionRepl', false, { type: 'boolean', description: nls.localize('multiSessionRepl', "True when there is more than 1 debug console.") }); export const CONTEXT_MULTI_SESSION_DEBUG = new RawContextKey('multiSessionDebug', false, { type: 'boolean', description: nls.localize('multiSessionDebug', "True when there is more than 1 active debug session.") }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts index 1e955f90bb2..168480825c3 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts @@ -18,7 +18,7 @@ registerAction2(class extends Action2 { id: COPY_NOTEBOOK_VARIABLE_VALUE_ID, title: COPY_NOTEBOOK_VARIABLE_VALUE_LABEL, f1: false, - precondition: ContextKeyExpr.has('value') + precondition: ContextKeyExpr.has('value'), }); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts index 2ca03e7798e..7fdb7141760 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts @@ -21,6 +21,7 @@ export interface INotebookVariableElement { readonly name: string; readonly value: string; readonly type?: string; + readonly language?: string; readonly indexedChildrenCount: number; readonly indexStart?: number; readonly hasNamedChildren: boolean; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index d7c5246450c..f0fc23bfa4f 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -25,6 +25,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { CONTEXT_VARIABLE_LANGUAGE, CONTEXT_VARIABLE_NAME, CONTEXT_VARIABLE_TYPE, CONTEXT_VARIABLE_VALUE } from 'vs/workbench/contrib/debug/common/debug'; import { INotebookScope, INotebookVariableElement, NotebookVariableDataSource } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource'; import { NotebookVariableAccessibilityProvider, NotebookVariableRenderer, NotebookVariablesDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree'; import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -33,7 +34,7 @@ import { ICellExecutionStateChangedEvent, IExecutionStateChangedEvent, INotebook import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -export type contextMenuArg = { source?: string; type?: string; value?: string }; +export type contextMenuArg = { source?: string; type?: string; value?: string; language?: string }; export class NotebookVariablesView extends ViewPane { @@ -42,7 +43,6 @@ export class NotebookVariablesView extends ViewPane { private tree: WorkbenchAsyncDataTree | undefined; private activeNotebook: NotebookTextModel | undefined; - private readonly menu: IMenu; private readonly dataSource: NotebookVariableDataSource; private updateScheduler: RunOnceScheduler; @@ -63,7 +63,7 @@ export class NotebookVariablesView extends ViewPane { @ICommandService protected commandService: ICommandService, @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, - @IMenuService menuService: IMenuService + @IMenuService private readonly menuService: IMenuService ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); @@ -73,7 +73,6 @@ export class NotebookVariablesView extends ViewPane { this.setActiveNotebook(); - this.menu = menuService.createMenu(MenuId.NotebookVariablesContext, contextKeyService); this.dataSource = new NotebookVariableDataSource(this.notebookKernelService); this.updateScheduler = new RunOnceScheduler(() => this.tree?.updateChildren(), 100); } @@ -102,22 +101,31 @@ export class NotebookVariablesView extends ViewPane { } private onContextMenu(e: ITreeContextMenuEvent): any { + if (!e.element) { + return; + } const element = e.element; - const context = { - type: element?.type - }; const arg: contextMenuArg = { - source: element?.notebook.uri.toString(), - value: element?.value, - ...context + source: element.notebook.uri.toString(), + value: element.value, + type: element.type, + language: element.language }; const actions: IAction[] = []; - createAndFillInContextMenuActions(this.menu, { arg, shouldForwardArgs: true }, actions); + + const overlayedContext = this.contextKeyService.createOverlay([ + [CONTEXT_VARIABLE_NAME.key, element.name], + [CONTEXT_VARIABLE_VALUE.key, element.value], + [CONTEXT_VARIABLE_TYPE.key, element.type], + [CONTEXT_VARIABLE_LANGUAGE.key, element.language] + ]); + const menu = this.menuService.createMenu(MenuId.NotebookVariablesContext, overlayedContext); + createAndFillInContextMenuActions(menu, { arg, shouldForwardArgs: true }, actions); + menu.dispose(); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, - getActions: () => actions, - getActionsContext: () => context, + getActions: () => actions }); } diff --git a/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts b/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts index 46258e0432c..515818d3a1b 100644 --- a/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts @@ -41,6 +41,9 @@ declare module 'vscode' { /** The type of the variable's value */ type?: string; + + /** The language of the variable's value */ + language?: string; } } From e06f8b6d5ef68475c98c759b51ce70a2dd68cde1 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 9 Feb 2024 14:19:26 -0800 Subject: [PATCH 1098/1897] Fix #204820 (#204867) This shouldn't have returned undefined. Fixes #204820 --- .../services/authentication/browser/authenticationService.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 78e2a0c4137..bc8d6860538 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -426,7 +426,9 @@ export class AuthenticationService extends Disposable implements IAuthentication isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean | undefined { const trustedExtensionAuthAccess = this.productService.trustedExtensionAuthAccess; if (Array.isArray(trustedExtensionAuthAccess)) { - return trustedExtensionAuthAccess.includes(extensionId) ?? undefined; + if (trustedExtensionAuthAccess.includes(extensionId)) { + return true; + } } else if (trustedExtensionAuthAccess?.[providerId]?.includes(extensionId)) { return true; } From 2fa5c6538ef9f464a7ef07a916214cacfe1d3036 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 9 Feb 2024 14:29:21 -0800 Subject: [PATCH 1099/1897] Bump distro (#204868) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 51d5b93310a..6dc97a406e5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "0a14a7cde028e801c7aea8415afac7ddf3c9a0bd", + "distro": "848d09154925ef27343af358097ed5435450160e", "author": { "name": "Microsoft Corporation" }, From ca858e1da32ad0f47b05abd9c48e98258498db6b Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 9 Feb 2024 15:26:23 -0800 Subject: [PATCH 1100/1897] Check for newline instead of an arbitrary length for showing hover hint (#204873) This should show the hint less in scenarios where we don't need it. --- src/vs/platform/quickinput/browser/quickInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 16b52782c26..79e8b3d6aa1 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -1283,7 +1283,7 @@ export class QuickInputHoverDelegate implements IHoverDelegate { : typeof options.content === 'string' ? options.content : options.content.value - ).length > 20; + ).includes('\n'); return this.hoverService.showHover({ ...options, persistence: { From 559785c7111cb2104d76b1ad507a1d91f58579ac Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 9 Feb 2024 15:40:36 -0800 Subject: [PATCH 1101/1897] Better wording of Trusted Extension tooltip (#204875) ref https://github.com/microsoft/vscode/pull/204785#issuecomment-1935612459 --- .../services/authentication/browser/authenticationService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index bc8d6860538..524b7402f04 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -898,7 +898,7 @@ export class AuthenticationService extends Disposable implements IAuthentication : nls.localize('notUsed', "Has not used this account"); let tooltip: string | undefined; if (extension.trusted) { - tooltip = nls.localize('trustedExtensionTooltip', "This extension is trusted by Microsoft and has access to this account"); + tooltip = nls.localize('trustedExtensionTooltip', "This extension is trusted by Microsoft and\nalways has access to this account"); } return { label: extension.name, From f140bf63099aa782a08f983c95065f5ae92eb0bb Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 9 Feb 2024 16:24:20 -0800 Subject: [PATCH 1102/1897] Render icons in the default description & detail hovers (#204882) Fixes https://github.com/microsoft/vscode/issues/204818 --- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 2 +- .../quickinput/browser/quickInputList.ts | 30 +++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 0bb344bfbb6..f95a11c9c44 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -22,7 +22,7 @@ export interface IIconLabelCreationOptions { export interface IIconLabelValueOptions { title?: string | ITooltipMarkdownString; - descriptionTitle?: string; + descriptionTitle?: string | ITooltipMarkdownString; suffix?: string; hideIcon?: boolean; extraClasses?: readonly string[]; diff --git a/src/vs/platform/quickinput/browser/quickInputList.ts b/src/vs/platform/quickinput/browser/quickInputList.ts index d8871456aee..de219a800bb 100644 --- a/src/vs/platform/quickinput/browser/quickInputList.ts +++ b/src/vs/platform/quickinput/browser/quickInputList.ts @@ -35,6 +35,7 @@ import { Lazy } from 'vs/base/common/lazy'; import { URI } from 'vs/base/common/uri'; import { isDark } from 'vs/platform/theme/common/theme'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ITooltipMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; const $ = dom.$; @@ -314,10 +315,23 @@ class ListElementRenderer implements IListRenderer Date: Fri, 9 Feb 2024 16:47:29 -0800 Subject: [PATCH 1103/1897] Adopt view zone api for notebook cell chat --- .../chat/cellChatActions.ts | 174 +--- .../chat/notebookChatController.ts} | 802 ++++++++++-------- .../view/cellParts/chat/cellChatPart.ts | 25 +- 3 files changed, 482 insertions(+), 519 deletions(-) rename src/vs/workbench/contrib/notebook/browser/{view/cellParts => controller}/chat/cellChatActions.ts (77%) rename src/vs/workbench/contrib/notebook/browser/{view/cellParts/chat/cellChatController.ts => controller/chat/notebookChatController.ts} (54%) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts similarity index 77% rename from src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts rename to src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 575a5ddedeb..8d6325c6b26 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -6,7 +6,6 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ILanguageService } from 'vs/editor/common/languages/language'; import { localize, localize2 } from 'vs/nls'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -15,11 +14,9 @@ import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkey import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS, NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController'; import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, getEditorFromArgsOrActivePane } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { insertNewCell } from 'vs/workbench/contrib/notebook/browser/controller/insertCellActions'; -import { CellEditState, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS, NotebookCellChatController } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; +import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; @@ -46,12 +43,7 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const ctrl = NotebookCellChatController.get(context.cell); - if (!ctrl) { - return; - } - - ctrl.acceptInput(); + NotebookChatController.get(context.notebookEditor)?.acceptInput(); } }); @@ -115,9 +107,7 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const editor = context.notebookEditor; - const activeCell = context.cell; - await editor.focusNotebookCell(activeCell, 'editor'); + await NotebookChatController.get(context.notebookEditor)?.focusNext(); } }); @@ -146,14 +136,8 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const activeCell = context.cell; - // Navigate to cell chat widget if it exists - const controller = NotebookCellChatController.get(activeCell); - if (controller && controller.isWidgetVisible()) { - controller.focusWidget(); - return; - } - + const index = context.notebookEditor.getCellIndex(context.cell); + await NotebookChatController.get(context.notebookEditor)?.focusNearestWidget(index, 'above'); } }); @@ -182,29 +166,8 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const editor = context.notebookEditor; - const activeCell = context.cell; - - const idx = editor.getCellIndex(activeCell); - if (typeof idx !== 'number') { - return; - } - - if (idx >= editor.getLength() - 1) { - // last one - return; - } - - const targetCell = editor.cellAt(idx + 1); - - if (targetCell) { - // Navigate to cell chat widget if it exists - const controller = NotebookCellChatController.get(targetCell); - if (controller && controller.isWidgetVisible()) { - controller.focusWidget(); - return; - } - } + const index = context.notebookEditor.getCellIndex(context.cell); + await NotebookChatController.get(context.notebookEditor)?.focusNearestWidget(index, 'below'); } }); @@ -225,12 +188,7 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const ctrl = NotebookCellChatController.get(context.cell); - if (!ctrl) { - return; - } - - ctrl.cancelCurrentRequest(false); + NotebookChatController.get(context.notebookEditor)?.cancelCurrentRequest(false); } }); @@ -250,12 +208,7 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const ctrl = NotebookCellChatController.get(context.cell); - if (!ctrl) { - return; - } - - ctrl.dismiss(false); + NotebookChatController.get(context.notebookEditor)?.dismiss(); } }); @@ -285,12 +238,7 @@ registerAction2(class extends NotebookAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const ctrl = NotebookCellChatController.get(context.cell); - if (!ctrl) { - return; - } - - ctrl.acceptSession(); + NotebookChatController.get(context.notebookEditor)?.acceptSession(); } }); @@ -315,15 +263,7 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const ctrl = NotebookCellChatController.get(context.cell); - if (!ctrl) { - return; - } - - // todo discard - ctrl.dismiss(true); - // focus on the cell editor container - context.notebookEditor.focusNotebookCell(context.cell, 'container'); + NotebookChatController.get(context.notebookEditor)?.discard(); } }); @@ -343,12 +283,7 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const ctrl = NotebookCellChatController.get(context.cell); - if (!ctrl) { - return; - } - - ctrl.feedbackLast(InlineChatResponseFeedbackKind.Helpful); + NotebookChatController.get(context.notebookEditor)?.feedbackLast(InlineChatResponseFeedbackKind.Helpful); } }); @@ -368,12 +303,7 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const ctrl = NotebookCellChatController.get(context.cell); - if (!ctrl) { - return; - } - - ctrl.feedbackLast(InlineChatResponseFeedbackKind.Unhelpful); + NotebookChatController.get(context.notebookEditor)?.feedbackLast(InlineChatResponseFeedbackKind.Unhelpful); } }); @@ -393,12 +323,7 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const ctrl = NotebookCellChatController.get(context.cell); - if (!ctrl) { - return; - } - - ctrl.feedbackLast(InlineChatResponseFeedbackKind.Bug); + NotebookChatController.get(context.notebookEditor)?.feedbackLast(InlineChatResponseFeedbackKind.Bug); } }); @@ -458,7 +383,22 @@ registerAction2(class extends NotebookAction { override getEditorContextFromArgsOrActive(accessor: ServicesAccessor, ...args: any[]): IInsertCellWithChatArgs | undefined { const [firstArg] = args; if (!firstArg) { - return undefined; + const notebookEditor = getEditorFromArgsOrActivePane(accessor); + if (!notebookEditor) { + return undefined; + } + + const activeCell = notebookEditor.getActiveCell(); + if (!activeCell) { + return undefined; + } + + return { + cell: activeCell, + notebookEditor, + input: undefined, + autoSend: undefined + }; } if (typeof firstArg !== 'object' || typeof firstArg.index !== 'number') { @@ -481,33 +421,9 @@ registerAction2(class extends NotebookAction { } async runWithContext(accessor: ServicesAccessor, context: IInsertCellWithChatArgs) { - let newCell: ICellViewModel | null = null; - if (!context.cell) { - // insert at the top - const languageService = accessor.get(ILanguageService); - newCell = insertCell(languageService, context.notebookEditor, 0, CellKind.Code, 'above', undefined, true); - } else { - newCell = insertNewCell(accessor, context, CellKind.Code, 'below', true); - } - - if (!newCell) { - return; - } - - await context.notebookEditor.focusNotebookCell(newCell, 'container'); - const ctrl = NotebookCellChatController.get(newCell); - if (!ctrl) { - return; - } - - context.notebookEditor.getCellsInRange().forEach(cell => { - const cellCtrl = NotebookCellChatController.get(cell); - if (cellCtrl) { - cellCtrl.dismiss(false); - } - }); - - ctrl.show(context.input, context.autoSend); + const index = Math.max(0, context.cell ? context.notebookEditor.getCellIndex(context.cell) + 1 : 0); + context.notebookEditor.focusContainer(); + NotebookChatController.get(context.notebookEditor)?.run(index, context.input, context.autoSend); } }); @@ -537,26 +453,8 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const languageService = accessor.get(ILanguageService); - const newCell = insertCell(languageService, context.notebookEditor, 0, CellKind.Code, 'above', undefined, true); - - if (!newCell) { - return; - } - await context.notebookEditor.focusNotebookCell(newCell, 'container'); - const ctrl = NotebookCellChatController.get(newCell); - if (!ctrl) { - return; - } - - context.notebookEditor.getCellsInRange().forEach(cell => { - const cellCtrl = NotebookCellChatController.get(cell); - if (cellCtrl) { - cellCtrl.dismiss(false); - } - }); - - ctrl.show(); + context.notebookEditor.focusContainer(); + NotebookChatController.get(context.notebookEditor)?.run(0, '', false); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts similarity index 54% rename from src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts rename to src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 0e05e8cae14..86aadb52b8d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -3,25 +3,29 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Dimension, WindowIntervalTimer } from 'vs/base/browser/dom'; -import { CancelablePromise, Queue, createCancelablePromise, raceCancellationError } from 'vs/base/common/async'; +import { Dimension, WindowIntervalTimer, getWindow, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { CancelablePromise, Queue, createCancelablePromise, disposableTimeout, raceCancellationError } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; import { MovingAverage } from 'vs/base/common/numbers'; import { StopWatch } from 'vs/base/common/stopwatch'; import { assertType } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; import { TextEdit } from 'vs/editor/common/languages'; -import { ICursorStateComputer } from 'vs/editor/common/model'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { ICursorStateComputer, ITextModel } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; +import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; -import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -29,16 +33,21 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { AsyncProgress } from 'vs/platform/progress/common/progress'; import { SaveReason } from 'vs/workbench/common/editor'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; -import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingService'; import { EmptyResponse, ErrorResponse, ReplyResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; import { ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; -import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; -import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_VISIBLE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingService'; +import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/utils'; +import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { insertCell, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; +import { INotebookEditor, INotebookEditorContribution, INotebookViewZone, ScrollToRevealBehavior } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; +import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + + +import 'vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions'; export const CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused")); export const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request")); @@ -48,219 +57,374 @@ export const MENU_CELL_CHAT_WIDGET_STATUS = MenuId.for('cellChatWidget.status'); export const MENU_CELL_CHAT_WIDGET_FEEDBACK = MenuId.for('cellChatWidget.feedback'); export const MENU_CELL_CHAT_WIDGET_TOOLBAR = MenuId.for('cellChatWidget.toolbar'); -interface ICellChatPart { - activeCell: ICellViewModel | undefined; -} +const WIDGET_MARGIN_BOTTOM = 16; -export class NotebookCellChatController extends Disposable { - private static _cellChatControllers = new WeakMap(); +class NotebookChatWidget extends Disposable implements INotebookViewZone { + private _afterModelPosition: number; - static get(cell: ICellViewModel): NotebookCellChatController | undefined { - return NotebookCellChatController._cellChatControllers.get(cell); + set afterModelPosition(afterModelPosition: number) { + this._afterModelPosition = afterModelPosition; } - private _sessionCtor: CancelablePromise | undefined; - private _activeSession?: Session; - private readonly _ctxHasActiveRequest: IContextKey; - private _isVisible: boolean = false; - private _strategy: EditStrategy | undefined; + get afterModelPosition(): number { + return this._afterModelPosition; + } + + private _heightInPx: number; + + set heightInPx(heightInPx: number) { + this._heightInPx = heightInPx; + } + + get heightInPx(): number { + return this._heightInPx; + } + + private _editingCell: CellViewModel | null = null; - private _inlineChatListener: IDisposable | undefined; - private _widget: InlineChatWidget | undefined; - private _toolbar: MenuWorkbenchToolBar | undefined; - private readonly _ctxVisible: IContextKey; - private readonly _ctxCellWidgetFocused: IContextKey; - private readonly _ctxLastResponseType: IContextKey; - private _widgetDisposableStore: DisposableStore = this._register(new DisposableStore()); constructor( - private readonly _notebookEditor: INotebookEditorDelegate, - private readonly _chatPart: ICellChatPart, - private readonly _cell: ICellViewModel, - private readonly _partContainer: HTMLElement, - @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, - @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService, - @ICommandService private readonly _commandService: ICommandService, - @IInlineChatSavingService private readonly _inlineChatSavingService: IInlineChatSavingService, + private readonly _notebookEditor: INotebookEditor, + readonly id: string, + readonly domNode: HTMLElement, + readonly widgetContainer: HTMLElement, + readonly inlineChatWidget: InlineChatWidget, + readonly parentEditor: CodeEditorWidget, + afterModelPosition: number, + heightInPx: number, + private readonly _languageService: ILanguageService, ) { super(); - NotebookCellChatController._cellChatControllers.set(this._cell, this); - this._ctxHasActiveRequest = CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST.bindTo(this._contextKeyService); - this._ctxVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(_contextKeyService); - this._ctxCellWidgetFocused = CTX_NOTEBOOK_CELL_CHAT_FOCUSED.bindTo(this._contextKeyService); - this._ctxLastResponseType = CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.bindTo(this._contextKeyService); + this._afterModelPosition = afterModelPosition; + this._heightInPx = heightInPx; - this._register(this._cell.onDidChangeEditorAttachState(() => { - const editor = this._getCellEditor(); - this._inlineChatListener?.dispose(); - - if (!editor) { - return; - } - - if (!this._widget && this._isVisible) { - this._initialize(editor); - } - - const inlineChatController = InlineChatController.get(editor); - if (inlineChatController) { - this._inlineChatListener = inlineChatController.onWillStartSession(() => { - this.dismiss(false); - }); - } + this._register(inlineChatWidget.onDidChangeHeight(() => { + this.heightInPx = inlineChatWidget.getHeight() + WIDGET_MARGIN_BOTTOM; + this._notebookEditor.changeViewZones(accessor => { + accessor.layoutZone(id); + }); + this._layoutWidget(inlineChatWidget, widgetContainer); })); + + this._layoutWidget(inlineChatWidget, widgetContainer); } - private _initialize(editor: IActiveCodeEditor) { - this._widget = this._instantiationService.createInstance(InlineChatWidget, editor, { - menuId: MENU_CELL_CHAT_INPUT, - widgetMenuId: MENU_CELL_CHAT_WIDGET, - statusMenuId: MENU_CELL_CHAT_WIDGET_STATUS, - feedbackMenuId: MENU_CELL_CHAT_WIDGET_FEEDBACK + focus() { + this.inlineChatWidget.focus(); + } + + async getEditingCellEditor() { + if (this._editingCell) { + await this._notebookEditor.focusNotebookCell(this._editingCell, 'editor'); + return this._notebookEditor.activeCodeEditor; + } + + if (!this._notebookEditor.hasModel()) { + return undefined; + } + + this._editingCell = insertCell(this._languageService, this._notebookEditor, this._afterModelPosition, CellKind.Code, 'above'); + + if (!this._editingCell) { + return undefined; + } + + await this._notebookEditor.focusNotebookCell(this._editingCell, 'editor', { revealBehavior: ScrollToRevealBehavior.firstLine }); + return this._notebookEditor.activeCodeEditor; + } + + async discardChange() { + if (this._notebookEditor.hasModel() && this._editingCell) { + // remove the cell from the notebook + runDeleteAction(this._notebookEditor, this._editingCell); + } + } + + private _layoutWidget(inlineChatWidget: InlineChatWidget, widgetContainer: HTMLElement) { + const layoutConfiguration = this._notebookEditor.notebookOptions.getLayoutConfiguration(); + const rightMargin = layoutConfiguration.cellRightMargin; + const leftMargin = this._notebookEditor.notebookOptions.getCellEditorContainerLeftMargin(); + const maxWidth = !inlineChatWidget.showsAnyPreview() ? 640 : Number.MAX_SAFE_INTEGER; + const width = Math.min(maxWidth, this._notebookEditor.getLayoutInfo().width - leftMargin - rightMargin); + + inlineChatWidget.layout(new Dimension(width, 80 + WIDGET_MARGIN_BOTTOM)); + inlineChatWidget.domNode.style.width = `${width}px`; + widgetContainer.style.left = `${leftMargin}px`; + } + + override dispose() { + this._notebookEditor.changeViewZones(accessor => { + accessor.removeZone(this.id); }); - - this._widgetDisposableStore.add(this._widget.onDidChangeHeight(() => { - this._updateHeight(); - })); - - this._widgetDisposableStore.add(this._notebookExecutionStateService.onDidChangeExecution(e => { - if (e.notebook.toString() !== this._notebookEditor.textModel?.uri.toString()) { - return; - } - - if (e.type === NotebookExecutionType.cell && e.affectsCell(this._cell.uri) && e.changed === undefined /** complete */) { - // check if execution is successfull - const { lastRunSuccess } = this._cell.internalMetadata; - if (lastRunSuccess) { - this._strategy?.createSnapshot(); - } - } - })); - - - this._partContainer.appendChild(this._widget.domNode); - } - - public override dispose(): void { - if (this._isVisible) { - // detach the chat widget - this._widget?.reset(); - this._sessionCtor?.cancel(); - this._sessionCtor = undefined; - } - - try { - if (this._widget) { - this._partContainer.removeChild(this._widget.domNode); - } - - } catch (_ex) { - // might not be attached - } - - // dismiss since we can't restore the widget properly now - this.dismiss(false); - this._widget?.dispose(); - this._inlineChatListener?.dispose(); - this._toolbar?.dispose(); - this._inlineChatListener = undefined; - this._ctxHasActiveRequest.reset(); - this._ctxVisible.reset(); - NotebookCellChatController._cellChatControllers.delete(this._cell); + this.domNode.remove(); super.dispose(); } +} - isWidgetVisible() { - return this._isVisible; +export class NotebookChatController extends Disposable implements INotebookEditorContribution { + static id: string = 'workbench.notebook.chatController'; + static counter: number = 0; + + public static get(editor: INotebookEditor): NotebookChatController | null { + return editor.getContribution(NotebookChatController.id); + } + private _strategy: EditStrategy | undefined; + private _sessionCtor: CancelablePromise | undefined; + private _activeSession?: Session; + private readonly _ctxHasActiveRequest: IContextKey; + private readonly _ctxCellWidgetFocused: IContextKey; + private readonly _ctxLastResponseType: IContextKey; + private _widget: NotebookChatWidget | undefined; + constructor( + private readonly _notebookEditor: INotebookEditor, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @ICommandService private readonly _commandService: ICommandService, + @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, + @IInlineChatSavingService private readonly _inlineChatSavingService: IInlineChatSavingService, + @IModelService private readonly _modelService: IModelService, + @ILanguageService private readonly _languageService: ILanguageService, + + ) { + super(); + this._ctxHasActiveRequest = CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST.bindTo(this._contextKeyService); + this._ctxCellWidgetFocused = CTX_NOTEBOOK_CELL_CHAT_FOCUSED.bindTo(this._contextKeyService); + this._ctxLastResponseType = CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.bindTo(this._contextKeyService); } - layout() { - if (this._isVisible && this._widget) { - const width = this._notebookEditor.getLayoutInfo().width - (/** margin */ 16 + 6) - (/** padding */ 6 * 2); - const height = this._widget.getHeight(); - this._widget.layout(new Dimension(width, height)); - } - } + run(index: number, input: string | undefined, autoSend: boolean | undefined): void { + if (this._widget) { + if (this._widget.afterModelPosition === index) { + // this._chatZone + // chatZone focus + } else { + const window = getWindow(this._widget.domNode); + this._widget.dispose(); + this._widget = undefined; - private _updateHeight() { - const surrounding = 6 * 2 /** padding */ + 6 /** cell chat widget margin bottom */ + 2 /** border */; - const heightWithPadding = this._isVisible && this._widget - ? (this._widget.getHeight() - 8 /** shadow */ - 18 /** padding */ - 6 /** widget's internal margin top */ + surrounding) - : 0; + scheduleAtNextAnimationFrame(window, () => { + this._createWidget(index, input, autoSend); + }); + } - if (this._cell.chatHeight === heightWithPadding) { return; } - this._cell.chatHeight = heightWithPadding; - this._partContainer.style.height = `${heightWithPadding - surrounding}px`; + this._createWidget(index, input, autoSend); + // TODO: reveal widget to the center if it's out of the viewport } - async show(input?: string, autoSend?: boolean) { - this._isVisible = true; - if (!this._widget) { - const editor = this._getCellEditor(); - if (editor) { - this._initialize(editor); + private _createWidget(index: number, input: string | undefined, autoSend: boolean | undefined) { + const viewZoneContainer = document.createElement('div'); + viewZoneContainer.classList.add('monaco-editor'); + const widgetContainer = document.createElement('div'); + widgetContainer.style.position = 'absolute'; + viewZoneContainer.appendChild(widgetContainer); + + const fakeParentEditorElement = document.createElement('div'); + + const fakeParentEditor = this._instantiationService.createInstance( + CodeEditorWidget, + fakeParentEditorElement, + { + }, + { isSimpleWidget: true } + ); + + const inputBoxPath = `/notebook-chat-input0-${NotebookChatController.counter++}`; + const inputUri = URI.from({ scheme: Schemas.untitled, path: inputBoxPath }); + const result: ITextModel = this._modelService.createModel('', null, inputUri, false); + fakeParentEditor.setModel(result); + + const inlineChatWidget = this._instantiationService.createInstance( + InlineChatWidget, + fakeParentEditor, + { + menuId: MENU_CELL_CHAT_INPUT, + widgetMenuId: MENU_CELL_CHAT_WIDGET, + statusMenuId: MENU_CELL_CHAT_WIDGET_STATUS, + feedbackMenuId: MENU_CELL_CHAT_WIDGET_FEEDBACK } - } + ); + inlineChatWidget.placeholder = localize('default.placeholder', "Ask a question"); + inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); + widgetContainer.appendChild(inlineChatWidget.domNode); - this._partContainer.style.display = 'flex'; - this._widget?.focus(); - this._widget?.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); - this._ctxVisible.set(true); - this._ctxCellWidgetFocused.set(true); - this._updateHeight(); + this._notebookEditor.changeViewZones(accessor => { + const id = accessor.addZone({ + afterModelPosition: index, + heightInPx: 80 + WIDGET_MARGIN_BOTTOM, + domNode: viewZoneContainer + }); - this._sessionCtor = createCancelablePromise(async token => { - if (this._cell.editorAttached) { - const editor = this._getCellEditor(); - if (editor) { - await this._startSession(editor, token); + this._widget = new NotebookChatWidget( + this._notebookEditor, + id, + viewZoneContainer, + widgetContainer, + inlineChatWidget, + fakeParentEditor, + index, + 80 + WIDGET_MARGIN_BOTTOM, + this._languageService + ); + + disposableTimeout(() => { + this._ctxCellWidgetFocused.set(true); + this._widget?.focus(); + }, 0, this._store); + + this._sessionCtor = createCancelablePromise(async token => { + + if (fakeParentEditor.hasModel()) { + this._startSession(fakeParentEditor, token); + + if (this._widget) { + this._widget.inlineChatWidget.placeholder = this._activeSession?.session.placeholder ?? localize('default.placeholder', "Ask a question"); + this._widget.inlineChatWidget.updateInfo(this._activeSession?.session.message ?? localize('welcome.1', "AI-generated code may be incorrect")); + this._widget.focus(); + } + + if (this._widget && input) { + this._widget.inlineChatWidget.value = input; + + if (autoSend) { + this.acceptInput(); + } + } } - } else { - await Event.toPromise(Event.once(this._cell.onDidChangeEditorAttachState)); - if (token.isCancellationRequested) { - return; - } - - const editor = this._getCellEditor(); - if (editor) { - await this._startSession(editor, token); - } - } - - if (this._widget) { - this._widget.placeholder = this._activeSession?.session.placeholder ?? localize('default.placeholder', "Ask a question"); - this._widget.updateInfo(this._activeSession?.session.message ?? localize('welcome.1', "AI-generated code may be incorrect")); - this._widget.focus(); - } - - if (this._widget && input) { - this._widget.value = input; - - if (autoSend) { - this.acceptInput(); - } - } + }); }); } - async focusWidget() { - this._widget?.focus(); - } + async acceptInput() { + assertType(this._activeSession); + assertType(this._widget); + this._activeSession.addInput(new SessionPrompt(this._widget.inlineChatWidget.value)); - private _getCellEditor() { - const editors = this._notebookEditor.codeEditors.find(editor => editor[0] === this._chatPart.activeCell); - if (!editors || !editors[1].hasModel()) { + assertType(this._activeSession.lastInput); + const value = this._activeSession.lastInput.value; + const editor = this._widget.parentEditor; + const model = editor.getModel(); + + if (!editor.hasModel() || !model) { return; } - const editor = editors[1]; - return editor; + this._ctxHasActiveRequest.set(true); + this._widget?.inlineChatWidget.updateProgress(true); + + const request: IInlineChatRequest = { + requestId: generateUuid(), + prompt: value, + attempt: 0, + selection: { selectionStartLineNumber: 1, selectionStartColumn: 1, positionLineNumber: 1, positionColumn: 1 }, + wholeRange: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, + live: true, + previewDocument: model.uri, + withIntentDetection: true, // TODO: don't hard code but allow in corresponding UI to run without intent detection? + }; + + //TODO: update progress in a newly inserted cell below the widget instead of the fake editor + + const requestCts = new CancellationTokenSource(); + const progressEdits: TextEdit[][] = []; + const progressiveEditsQueue = new Queue(); + const progressiveEditsClock = StopWatch.create(); + const progressiveEditsAvgDuration = new MovingAverage(); + const progressiveEditsCts = new CancellationTokenSource(requestCts.token); + const progress = new AsyncProgress(async data => { + // console.log('received chunk', data, request); + + if (requestCts.token.isCancellationRequested) { + return; + } + + if (data.message) { + this._widget?.inlineChatWidget.updateToolbar(false); + this._widget?.inlineChatWidget.updateInfo(data.message); + } + + if (data.edits?.length) { + if (!request.live) { + throw new Error('Progress in NOT supported in non-live mode'); + } + progressEdits.push(data.edits); + progressiveEditsAvgDuration.update(progressiveEditsClock.elapsed()); + progressiveEditsClock.reset(); + + progressiveEditsQueue.queue(async () => { + // making changes goes into a queue because otherwise the async-progress time will + // influence the time it takes to receive the changes and progressive typing will + // become infinitely fast + await this._makeChanges(data.edits!, data.editsShouldBeInstant + ? undefined + : { duration: progressiveEditsAvgDuration.value, token: progressiveEditsCts.token } + ); + }); + } + }); + + const task = this._activeSession.provider.provideResponse(this._activeSession.session, request, progress, requestCts.token); + let response: ReplyResponse | ErrorResponse | EmptyResponse; + + try { + this._widget?.inlineChatWidget.updateChatMessage(undefined); + this._widget?.inlineChatWidget.updateFollowUps(undefined); + this._widget?.inlineChatWidget.updateProgress(true); + this._widget?.inlineChatWidget.updateInfo(!this._activeSession.lastExchange ? localize('thinking', "Thinking\u2026") : ''); + this._ctxHasActiveRequest.set(true); + + const reply = await raceCancellationError(Promise.resolve(task), requestCts.token); + if (progressiveEditsQueue.size > 0) { + // we must wait for all edits that came in via progress to complete + await Event.toPromise(progressiveEditsQueue.onDrained); + } + await progress.drain(); + + if (!reply) { + response = new EmptyResponse(); + } else { + const markdownContents = new MarkdownString('', { supportThemeIcons: true, supportHtml: true, isTrusted: false }); + const replyResponse = response = this._instantiationService.createInstance(ReplyResponse, reply, markdownContents, this._activeSession.textModelN.uri, this._activeSession.textModelN.getAlternativeVersionId(), progressEdits, request.requestId); + for (let i = progressEdits.length; i < replyResponse.allLocalEdits.length; i++) { + await this._makeChanges(replyResponse.allLocalEdits[i], undefined); + } + + if (this._activeSession?.provider.provideFollowups) { + const followupCts = new CancellationTokenSource(); + const followups = await this._activeSession.provider.provideFollowups(this._activeSession.session, replyResponse.raw, followupCts.token); + if (followups && this._widget) { + const widget = this._widget; + widget.inlineChatWidget.updateFollowUps(followups, async followup => { + if (followup.kind === 'reply') { + widget.inlineChatWidget.value = followup.message; + this.acceptInput(); + } else { + await this.acceptSession(); + this._commandService.executeCommand(followup.commandId, ...(followup.args ?? [])); + } + }); + } + } + } + } catch (e) { + response = new ErrorResponse(e); + } finally { + this._ctxHasActiveRequest.set(false); + this._widget?.inlineChatWidget.updateProgress(false); + this._widget?.inlineChatWidget.updateInfo(''); + this._widget?.inlineChatWidget.updateToolbar(true); + } + + this._ctxHasActiveRequest.set(false); + this._widget?.inlineChatWidget.updateProgress(false); + this._widget?.inlineChatWidget.updateInfo(''); + this._widget?.inlineChatWidget.updateToolbar(true); + + this._activeSession.addExchange(new SessionExchange(this._activeSession.lastInput, response)); + this._ctxLastResponseType.set(response instanceof ReplyResponse ? response.raw.type : undefined); } private async _startSession(editor: IActiveCodeEditor, token: CancellationToken) { @@ -282,184 +446,18 @@ export class NotebookCellChatController extends Disposable { this._strategy = new EditStrategy(session); } - async acceptInput() { + private async _makeChanges(edits: TextEdit[], opts: ProgressingEditsOptions | undefined) { assertType(this._activeSession); + assertType(this._strategy); assertType(this._widget); - this._activeSession.addInput(new SessionPrompt(this._widget.value)); - assertType(this._activeSession.lastInput); + const editor = await this._widget.getEditingCellEditor(); - const value = this._activeSession.lastInput.value; - const editors = this._notebookEditor.codeEditors.find(editor => editor[0] === this._chatPart.activeCell); - if (!editors || !editors[1].hasModel()) { + if (!editor || !editor.hasModel()) { return; } - const editor = editors[1]; - - this._ctxHasActiveRequest.set(true); - this._widget?.updateProgress(true); - - const request: IInlineChatRequest = { - requestId: generateUuid(), - prompt: value, - attempt: 0, - selection: { selectionStartLineNumber: 1, selectionStartColumn: 1, positionLineNumber: 1, positionColumn: 1 }, - wholeRange: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, - live: true, - previewDocument: editor.getModel().uri, - withIntentDetection: true, // TODO: don't hard code but allow in corresponding UI to run without intent detection? - }; - - const requestCts = new CancellationTokenSource(); - const progressEdits: TextEdit[][] = []; - const progressiveEditsQueue = new Queue(); - const progressiveEditsClock = StopWatch.create(); - const progressiveEditsAvgDuration = new MovingAverage(); - const progressiveEditsCts = new CancellationTokenSource(requestCts.token); - const progress = new AsyncProgress(async data => { - // console.log('received chunk', data, request); - - if (requestCts.token.isCancellationRequested) { - return; - } - - if (data.message) { - this._widget?.updateToolbar(false); - this._widget?.updateInfo(data.message); - } - - if (data.edits?.length) { - if (!request.live) { - throw new Error('Progress in NOT supported in non-live mode'); - } - progressEdits.push(data.edits); - progressiveEditsAvgDuration.update(progressiveEditsClock.elapsed()); - progressiveEditsClock.reset(); - - progressiveEditsQueue.queue(async () => { - // making changes goes into a queue because otherwise the async-progress time will - // influence the time it takes to receive the changes and progressive typing will - // become infinitely fast - await this._makeChanges(editor, data.edits!, data.editsShouldBeInstant - ? undefined - : { duration: progressiveEditsAvgDuration.value, token: progressiveEditsCts.token } - ); - }); - } - }); - - const task = this._activeSession.provider.provideResponse(this._activeSession.session, request, progress, requestCts.token); - let response: ReplyResponse | ErrorResponse | EmptyResponse; - - try { - this._widget?.updateChatMessage(undefined); - this._widget?.updateFollowUps(undefined); - this._widget?.updateProgress(true); - this._widget?.updateInfo(!this._activeSession.lastExchange ? localize('thinking', "Thinking\u2026") : ''); - this._ctxHasActiveRequest.set(true); - - const reply = await raceCancellationError(Promise.resolve(task), requestCts.token); - if (progressiveEditsQueue.size > 0) { - // we must wait for all edits that came in via progress to complete - await Event.toPromise(progressiveEditsQueue.onDrained); - } - await progress.drain(); - - if (!reply) { - response = new EmptyResponse(); - } else { - const markdownContents = new MarkdownString('', { supportThemeIcons: true, supportHtml: true, isTrusted: false }); - const replyResponse = response = this._instantiationService.createInstance(ReplyResponse, reply, markdownContents, this._activeSession.textModelN.uri, this._activeSession.textModelN.getAlternativeVersionId(), progressEdits, request.requestId); - for (let i = progressEdits.length; i < replyResponse.allLocalEdits.length; i++) { - await this._makeChanges(editor, replyResponse.allLocalEdits[i], undefined); - } - - if (this._activeSession?.provider.provideFollowups) { - const followupCts = new CancellationTokenSource(); - const followups = await this._activeSession.provider.provideFollowups(this._activeSession.session, replyResponse.raw, followupCts.token); - if (followups && this._widget) { - const widget = this._widget; - widget.updateFollowUps(followups, async followup => { - if (followup.kind === 'reply') { - widget.value = followup.message; - this.acceptInput(); - } else { - await this.acceptSession(); - this._commandService.executeCommand(followup.commandId, ...(followup.args ?? [])); - } - }); - } - } - } - } catch (e) { - response = new ErrorResponse(e); - } finally { - this._ctxHasActiveRequest.set(false); - this._widget?.updateProgress(false); - this._widget?.updateInfo(''); - this._widget?.updateToolbar(true); - } - - this._ctxHasActiveRequest.set(false); - this._widget?.updateProgress(false); - this._widget?.updateInfo(''); - this._widget?.updateToolbar(true); - - this._activeSession.addExchange(new SessionExchange(this._activeSession.lastInput, response)); - this._ctxLastResponseType.set(response instanceof ReplyResponse ? response.raw.type : undefined); - } - - async cancelCurrentRequest(discard: boolean) { - if (discard) { - this._strategy?.cancel(); - } - - if (this._activeSession) { - this._inlineChatSessionService.releaseSession(this._activeSession); - } - - this._activeSession = undefined; - } - - async acceptSession() { - assertType(this._activeSession); - assertType(this._strategy); - - const editor = this._getCellEditor(); - assertType(editor); - - try { - await this._strategy.apply(editor); - } catch (_err) { } - - this._inlineChatSessionService.releaseSession(this._activeSession); - this.dismiss(false); - } - - async dismiss(discard: boolean) { - this._isVisible = false; - this._partContainer.style.display = 'none'; - this.cancelCurrentRequest(discard); - this._ctxCellWidgetFocused.set(false); - this._ctxVisible.set(false); - this._ctxLastResponseType.reset(); - this._widget?.reset(); - this._updateHeight(); - } - - async feedbackLast(kind: InlineChatResponseFeedbackKind) { - if (this._activeSession?.lastExchange && this._activeSession.lastExchange.response instanceof ReplyResponse) { - this._activeSession.provider.handleInlineChatResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, kind); - this._widget?.updateStatus('Thank you for your feedback!', { resetAfter: 1250 }); - } - } - - private async _makeChanges(editor: IActiveCodeEditor, edits: TextEdit[], opts: ProgressingEditsOptions | undefined) { - assertType(this._activeSession); - assertType(this._strategy); - - const moreMinimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(this._activeSession.textModelN.uri, edits); + const moreMinimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(editor.getModel().uri, edits); // this._log('edits from PROVIDER and after making them MORE MINIMAL', this._activeSession.provider.debugName, edits, moreMinimalEdits); if (moreMinimalEdits?.length === 0) { @@ -484,9 +482,91 @@ export class NotebookCellChatController extends Disposable { // this._ignoreModelContentChanged = false; } } + + async acceptSession() { + assertType(this._activeSession); + assertType(this._strategy); + + const editor = this._widget?.parentEditor; + if (!editor?.hasModel()) { + return; + } + + try { + await this._strategy.apply(editor); + } catch (_err) { } + + this._inlineChatSessionService.releaseSession(this._activeSession); + this.dismiss(); + } + + async focusNext() { + if (!this._widget) { + return; + } + + const index = this._widget.afterModelPosition; + const cell = this._notebookEditor.cellAt(index); + if (!cell) { + return; + } + + await this._notebookEditor.focusNotebookCell(cell, 'editor'); + } + + focusNearestWidget(index: number, direction: 'above' | 'below') { + switch (direction) { + case 'above': + if (this._widget?.afterModelPosition === index) { + this._widget.focus(); + } + break; + case 'below': + if (this._widget?.afterModelPosition === index + 1) { + this._widget.focus(); + } + break; + default: + break; + } + } + + + async cancelCurrentRequest(discard: boolean) { + if (discard) { + this._strategy?.cancel(); + } + + if (this._activeSession) { + this._inlineChatSessionService.releaseSession(this._activeSession); + } + + this._activeSession = undefined; + } + + discard() { + this._strategy?.cancel(); + this._widget?.discardChange(); + } + + async feedbackLast(kind: InlineChatResponseFeedbackKind) { + if (this._activeSession?.lastExchange && this._activeSession.lastExchange.response instanceof ReplyResponse) { + this._activeSession.provider.handleInlineChatResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, kind); + this._widget?.inlineChatWidget.updateStatus('Thank you for your feedback!', { resetAfter: 1250 }); + } + } + + + dismiss() { + this._ctxCellWidgetFocused.set(false); + this._sessionCtor?.cancel(); + this._sessionCtor = undefined; + this._widget?.dispose(); + this._widget = undefined; + } } -class EditStrategy { +export class EditStrategy { private _editCount: number = 0; constructor( @@ -558,3 +638,7 @@ class EditStrategy { } } } + + +registerNotebookContribution(NotebookChatController.id, NotebookChatController); + diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatPart.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatPart.ts index 461dc85c664..059f5ab7b14 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatPart.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatPart.ts @@ -3,54 +3,35 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; -import { NotebookCellChatController } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; - -import 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export class CellChatPart extends CellContentPart { - private _controller: NotebookCellChatController | undefined; + // private _controller: NotebookCellChatController | undefined; get activeCell() { return this.currentCell; } constructor( - private readonly _notebookEditor: INotebookEditorDelegate, - private readonly _partContainer: HTMLElement, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IConfigurationService private readonly _configurationService: IConfigurationService, + _notebookEditor: INotebookEditorDelegate, + _partContainer: HTMLElement, ) { super(); } override didRenderCell(element: ICellViewModel): void { - this._controller?.dispose(); - const enabled = this._configurationService.getValue(NotebookSetting.cellChat); - if (enabled) { - this._controller = this._instantiationService.createInstance(NotebookCellChatController, this._notebookEditor, this, element, this._partContainer); - } - super.didRenderCell(element); } override unrenderCell(element: ICellViewModel): void { - this._controller?.dispose(); - this._controller = undefined; super.unrenderCell(element); } override updateInternalLayoutNow(element: ICellViewModel): void { - this._controller?.layout(); } override dispose() { - this._controller?.dispose(); - this._controller = undefined; super.dispose(); } } From 75b82a187f428406d6f0d4210ec9c7a58a5240b5 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 9 Feb 2024 17:35:00 -0800 Subject: [PATCH 1104/1897] Handle all chat actions with the new floating widget. --- .../controller/chat/cellChatActions.ts | 33 ++++++++++--------- .../chat/notebook.chat.contribution.ts | 6 ++++ .../controller/chat/notebookChatContext.ts | 16 +++++++++ .../controller/chat/notebookChatController.ts | 13 ++------ .../notebook/browser/notebook.contribution.ts | 1 + .../notebook/browser/view/notebookCellList.ts | 5 +++ .../browser/view/notebookCellListView.ts | 19 ++++++++++- 7 files changed, 65 insertions(+), 28 deletions(-) create mode 100644 src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 8d6325c6b26..5959e33fcd7 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -14,7 +14,8 @@ import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkey import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS, NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; +import { NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController'; import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, getEditorFromArgsOrActivePane } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -87,7 +88,7 @@ registerAction2(class extends NotebookCellAction { } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super( { @@ -106,7 +107,7 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { await NotebookChatController.get(context.notebookEditor)?.focusNext(); } }); @@ -171,7 +172,7 @@ registerAction2(class extends NotebookCellAction { } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super( { @@ -187,12 +188,12 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { NotebookChatController.get(context.notebookEditor)?.cancelCurrentRequest(false); } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super( { @@ -207,7 +208,7 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { NotebookChatController.get(context.notebookEditor)?.dismiss(); } }); @@ -237,12 +238,12 @@ registerAction2(class extends NotebookAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { NotebookChatController.get(context.notebookEditor)?.acceptSession(); } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super( { @@ -262,12 +263,12 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { NotebookChatController.get(context.notebookEditor)?.discard(); } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super({ id: 'notebook.cell.feedbackHelpful', @@ -282,12 +283,12 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { NotebookChatController.get(context.notebookEditor)?.feedbackLast(InlineChatResponseFeedbackKind.Helpful); } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super({ id: 'notebook.cell.feedbackUnhelpful', @@ -302,12 +303,12 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { NotebookChatController.get(context.notebookEditor)?.feedbackLast(InlineChatResponseFeedbackKind.Unhelpful); } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super({ id: 'notebook.cell.reportIssueForBug', @@ -322,7 +323,7 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { NotebookChatController.get(context.notebookEditor)?.feedbackLast(InlineChatResponseFeedbackKind.Bug); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts new file mode 100644 index 00000000000..995340fb191 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions'; diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts new file mode 100644 index 00000000000..af75c65626d --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; + +export const CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused")); +export const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request")); +export const MENU_CELL_CHAT_INPUT = MenuId.for('cellChatInput'); +export const MENU_CELL_CHAT_WIDGET = MenuId.for('cellChatWidget'); +export const MENU_CELL_CHAT_WIDGET_STATUS = MenuId.for('cellChatWidget.status'); +export const MENU_CELL_CHAT_WIDGET_FEEDBACK = MenuId.for('cellChatWidget.feedback'); +export const MENU_CELL_CHAT_WIDGET_TOOLBAR = MenuId.for('cellChatWidget.toolbar'); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 86aadb52b8d..99b982368a2 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -26,9 +26,8 @@ import { ICursorStateComputer, ITextModel } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; -import { MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { AsyncProgress } from 'vs/platform/progress/common/progress'; import { SaveReason } from 'vs/workbench/common/editor'; @@ -41,21 +40,13 @@ import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inline import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; import { INotebookEditor, INotebookEditorContribution, INotebookViewZone, ScrollToRevealBehavior } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import 'vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions'; - -export const CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused")); -export const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request")); -export const MENU_CELL_CHAT_INPUT = MenuId.for('cellChatInput'); -export const MENU_CELL_CHAT_WIDGET = MenuId.for('cellChatWidget'); -export const MENU_CELL_CHAT_WIDGET_STATUS = MenuId.for('cellChatWidget.status'); -export const MENU_CELL_CHAT_WIDGET_FEEDBACK = MenuId.for('cellChatWidget.feedback'); -export const MENU_CELL_CHAT_WIDGET_TOOLBAR = MenuId.for('cellChatWidget.toolbar'); const WIDGET_MARGIN_BOTTOM = 16; diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index bcc27365df5..cf5cf217587 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -67,6 +67,7 @@ import 'vs/workbench/contrib/notebook/browser/controller/editActions'; import 'vs/workbench/contrib/notebook/browser/controller/cellOutputActions'; import 'vs/workbench/contrib/notebook/browser/controller/apiActions'; import 'vs/workbench/contrib/notebook/browser/controller/foldingController'; +import 'vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution'; // Editor Contribution import 'vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 62cf6f3b766..e613e8af3e6 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -592,6 +592,11 @@ export class NotebookCellList extends WorkbenchList implements ID return modelIndex; } + if (modelIndex >= this.hiddenRangesPrefixSum.getTotalSum()) { + // it's already after the last hidden range + return this.hiddenRangesPrefixSum.getTotalSum(); + } + return this.hiddenRangesPrefixSum.getIndexOf(modelIndex).index; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts index c2752fe8103..8ff1afea0c1 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts @@ -49,6 +49,15 @@ export class NotebookCellsLayout implements IRangeMap { this._size = this._paddingTop; } + getWhitespaces(): IWhitespace[] { + return this._whitespace; + } + + restoreWhitespace(items: IWhitespace[]) { + this._whitespace = items; + this._size = this._paddingTop + this._items.reduce((total, item) => total + item.size, 0) + this._whitespace.reduce((total, ws) => total + ws.size, 0); + } + /** */ splice(index: number, deleteCount: number, items?: IItem[] | undefined): void { @@ -240,7 +249,15 @@ export class NotebookCellListView extends ListView { } protected override createRangeMap(paddingTop: number): IRangeMap { - return new NotebookCellsLayout(paddingTop); + const existingMap = this.rangeMap as NotebookCellsLayout | undefined; + if (existingMap) { + const layout = new NotebookCellsLayout(paddingTop); + layout.restoreWhitespace(existingMap.getWhitespaces()); + return layout; + } else { + return new NotebookCellsLayout(paddingTop); + } + } insertWhitespace(afterPosition: number, size: number): string { From 4be946d5d9a7a6d45d6657712b42697e96a30b3f Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 9 Feb 2024 17:56:44 -0800 Subject: [PATCH 1105/1897] Update slash commands --- .../notebook/browser/controller/chat/notebookChatController.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 99b982368a2..a336ad2c6ea 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -274,6 +274,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito if (this._widget) { this._widget.inlineChatWidget.placeholder = this._activeSession?.session.placeholder ?? localize('default.placeholder', "Ask a question"); this._widget.inlineChatWidget.updateInfo(this._activeSession?.session.message ?? localize('welcome.1', "AI-generated code may be incorrect")); + this._widget.inlineChatWidget.updateSlashCommands(this._activeSession?.session.slashCommands ?? []); this._widget.focus(); } @@ -304,6 +305,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito } this._ctxHasActiveRequest.set(true); + this._widget.inlineChatWidget.updateSlashCommands(this._activeSession.session.slashCommands ?? []); this._widget?.inlineChatWidget.updateProgress(true); const request: IInlineChatRequest = { From 56c54e7fef7f168e9d6eab921301fc91479b5b19 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 9 Feb 2024 19:46:02 -0800 Subject: [PATCH 1106/1897] Trim camelCase filter instead of returning null (#204885) Fixes https://github.com/microsoft/vscode/issues/204757 --- src/vs/base/common/filters.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index f7f0a43972f..03cb85813cc 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -280,8 +280,9 @@ export function matchesCamelCase(word: string, camelCaseWord: string): IMatch[] return null; } + // TODO: Consider removing this check if (camelCaseWord.length > 60) { - return null; + camelCaseWord = camelCaseWord.substring(0, 60); } const analysis = analyzeCamelCaseWord(camelCaseWord); From b49c1c1170bb947bc6a36c0aacd3a9e4db0c518a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sat, 10 Feb 2024 10:40:01 -0300 Subject: [PATCH 1107/1897] Add ChatAgentResult2#metadata (#204851) * Support serializable metadata on 'result' object from chat agent * Fix 'errorDetails' on the VM * Fix acceptAction, get rid of generic parameter on ChatAgent * Use result metadata for followups * Use serialized result for history * Don't share metadata between agents * Add a test for result metadata * Fix build --- .../src/singlefolder-tests/chat.test.ts | 35 ++++++- .../api/browser/mainThreadChatAgents2.ts | 8 +- .../workbench/api/common/extHost.protocol.ts | 6 +- .../api/common/extHostChatAgents2.ts | 93 ++++++++----------- .../api/common/extHostTypeConverters.ts | 11 ++- .../browser/actions/chatCodeblockActions.ts | 5 + .../chat/browser/actions/chatTitleActions.ts | 3 + .../contrib/chat/browser/chatQuick.ts | 4 +- .../contrib/chat/browser/chatWidget.ts | 3 +- .../contrib/chat/common/chatAgents.ts | 12 +-- .../contrib/chat/common/chatModel.ts | 48 ++++++---- .../contrib/chat/common/chatParserTypes.ts | 15 +++ .../contrib/chat/common/chatService.ts | 14 +-- .../contrib/chat/common/chatServiceImpl.ts | 46 ++++----- .../contrib/chat/common/chatViewModel.ts | 13 ++- .../__snapshots__/Chat_can_deserialize.0.snap | 12 +-- .../__snapshots__/Chat_can_serialize.1.snap | 10 +- .../chat/test/common/chatService.test.ts | 9 +- .../chat/test/common/voiceChat.test.ts | 2 +- .../vscode.proposed.chatAgents2.d.ts | 23 ++--- .../vscode.proposed.chatAgents2Additions.d.ts | 6 +- .../vscode.proposed.defaultChatAgent.d.ts | 2 +- 22 files changed, 215 insertions(+), 165 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index fd23f14d507..cdb80bbb50e 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import 'mocha'; -import { CancellationToken, chat, ChatAgentRequest, ChatVariableLevel, Disposable, interactive, InteractiveSession, ProviderResult } from 'vscode'; +import { CancellationToken, chat, ChatAgentRequest, ChatAgentResult2, ChatVariableLevel, Disposable, interactive, InteractiveSession, ProviderResult } from 'vscode'; import { assertNoRpc, closeAllEditors, DeferredPromise, disposeAll } from '../utils'; suite('chat', () => { @@ -67,4 +67,37 @@ suite('chat', () => { assert.strictEqual(request.prompt, 'hi [#myVar](values:myVar)'); assert.strictEqual(request.variables['myVar'][0].value, 'myValue'); }); + + test('result metadata is returned to the followup provider', async () => { + disposables.push(interactive.registerInteractiveSessionProvider('provider', { + prepareSession: (_token: CancellationToken): ProviderResult => { + return { + requester: { name: 'test' }, + responder: { name: 'test' }, + }; + }, + })); + + const deferred = new DeferredPromise(); + const agent = chat.createChatAgent('agent', (_request, _context, _progress, _token) => { + return { metadata: { key: 'value' } }; + }); + agent.isDefault = true; + agent.subCommandProvider = { + provideSubCommands: (_token) => { + return [{ name: 'hello', description: 'Hello' }]; + } + }; + agent.followupProvider = { + provideFollowups(result, _token) { + deferred.complete(result); + return []; + }, + }; + disposables.push(agent); + + interactive.sendInteractiveRequestToProvider('provider', { message: '@agent /hello friend' }); + const result = await deferred.p; + assert.deepStrictEqual(result.metadata, { key: 'value' }); + }); }); diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index 0f1263149b9..cdd867d0160 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -59,9 +59,9 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA for (const [handle, agent] of this._agents) { if (agent.name === e.agentId) { if (e.action.kind === 'vote') { - this._proxy.$acceptFeedback(handle, e.sessionId, e.requestId, e.action.direction); + this._proxy.$acceptFeedback(handle, e.result ?? {}, e.action.direction); } else { - this._proxy.$acceptAction(handle, e.sessionId, e.requestId, e); + this._proxy.$acceptAction(handle, e.result || {}, e); } break; } @@ -87,12 +87,12 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA this._pendingProgress.delete(request.requestId); } }, - provideFollowups: async (sessionId, token): Promise => { + provideFollowups: async (result, token): Promise => { if (!this._agents.get(handle)?.hasFollowups) { return []; } - return this._proxy.$provideFollowups(handle, sessionId, token); + return this._proxy.$provideFollowups(handle, result, token); }, get lastSlashCommands() { return lastSlashCommands; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b7944086d30..4b49c05c2d7 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1221,9 +1221,9 @@ export type IChatAgentHistoryEntryDto = { export interface ExtHostChatAgentsShape2 { $invokeAgent(handle: number, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; $provideSlashCommands(handle: number, token: CancellationToken): Promise; - $provideFollowups(handle: number, sessionId: string, token: CancellationToken): Promise; - $acceptFeedback(handle: number, sessionId: string, requestId: string, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void; - $acceptAction(handle: number, sessionId: string, requestId: string, action: IChatUserActionEvent): void; + $provideFollowups(handle: number, result: IChatAgentResult, token: CancellationToken): Promise; + $acceptFeedback(handle: number, result: IChatAgentResult, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void; + $acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void; $invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise; $provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined>; $provideSampleQuestions(handle: number, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 191941e87d6..284caf0032b 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -157,11 +157,9 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { private static _idPool = 0; - private readonly _agents = new Map>(); + private readonly _agents = new Map(); private readonly _proxy: MainThreadChatAgentsShape2; - private readonly _previousResultMap: Map = new Map(); - private readonly _resultsBySessionAndRequestId: Map> = new Map(); private readonly _sessionDisposables: DisposableMap = new DisposableMap(); constructor( @@ -173,9 +171,9 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); } - createChatAgent(extension: IExtensionDescription, name: string, handler: vscode.ChatAgentExtendedHandler): vscode.ChatAgent2 { + createChatAgent(extension: IExtensionDescription, name: string, handler: vscode.ChatAgentExtendedHandler): vscode.ChatAgent2 { const handle = ExtHostChatAgents2._idPool++; - const agent = new ExtHostChatAgent(extension, name, this._proxy, handle, handler); + const agent = new ExtHostChatAgent(extension, name, this._proxy, handle, handler); this._agents.set(handle, agent); this._proxy.$registerAgent(handle, name, {}); @@ -183,10 +181,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } async $invokeAgent(handle: number, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise { - // Clear the previous result so that $acceptFeedback or $acceptAction during a request will be ignored. - // We may want to support sending those during a request. - this._previousResultMap.delete(request.sessionId); - const agent = this._agents.get(handle); if (!agent) { throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); @@ -203,7 +197,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService, this.commands.converter, sessionDisposables); try { - const convertedHistory = await this.prepareHistory(agent, request, context); + const convertedHistory = await this.prepareHistory(request, context); const task = agent.invoke( typeConvert.ChatAgentRequest.to(request), { history: convertedHistory }, @@ -212,21 +206,16 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { ); return await raceCancellation(Promise.resolve(task).then((result) => { - if (result) { - this._previousResultMap.set(request.sessionId, result); - let sessionResults = this._resultsBySessionAndRequestId.get(request.sessionId); - if (!sessionResults) { - sessionResults = new Map(); - this._resultsBySessionAndRequestId.set(request.sessionId, sessionResults); + if (result?.metadata) { + try { + JSON.stringify(result.metadata); + } catch (err) { + const msg = `result.metadata MUST be JSON.stringify-able. Got error: ${err.message}`; + this._logService.error(`[${agent.extension.identifier.value}] [@${agent.id}] ${msg}`, agent.extension); + return { errorDetails: { message: msg }, timings: stream.timings }; } - sessionResults.set(request.requestId, result); - - return { errorDetails: result.errorDetails, timings: stream.timings }; - } else { - this._previousResultMap.delete(request.sessionId); } - - return undefined; + return { errorDetails: result?.errorDetails, timings: stream.timings, metadata: result?.metadata }; }), token); } catch (e) { @@ -239,22 +228,22 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } } - private async prepareHistory(agent: ExtHostChatAgent, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise { + private async prepareHistory(request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise { return coalesce(await Promise.all(context.history .map(async h => { - const result = request.agentId === h.request.agentId && this._resultsBySessionAndRequestId.get(request.sessionId)?.get(h.request.requestId) - || h.result; + const ehResult = typeConvert.ChatAgentResult.to(h.result); + const result: vscode.ChatAgentResult2 = request.agentId === h.request.agentId ? + ehResult : + { ...ehResult, metadata: undefined }; return { request: typeConvert.ChatAgentRequest.to(h.request), response: coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))), - result + result, } satisfies vscode.ChatAgentHistoryEntry; }))); } $releaseSession(sessionId: string): void { - this._previousResultMap.delete(sessionId); - this._resultsBySessionAndRequestId.delete(sessionId); this._sessionDisposables.deleteAndDispose(sessionId); } @@ -267,30 +256,23 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { return agent.provideSlashCommands(token); } - $provideFollowups(handle: number, sessionId: string, token: CancellationToken): Promise { + $provideFollowups(handle: number, result: IChatAgentResult, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { return Promise.resolve([]); } - const result = this._previousResultMap.get(sessionId); - if (!result) { - return Promise.resolve([]); - } - - return agent.provideFollowups(result, token); + const ehResult = typeConvert.ChatAgentResult.to(result); + return agent.provideFollowups(ehResult, token); } - $acceptFeedback(handle: number, sessionId: string, requestId: string, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void { + $acceptFeedback(handle: number, result: IChatAgentResult, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void { const agent = this._agents.get(handle); if (!agent) { return; } - const result = this._resultsBySessionAndRequestId.get(sessionId)?.get(requestId); - if (!result) { - return; - } + const ehResult = typeConvert.ChatAgentResult.to(result); let kind: extHostTypes.ChatAgentResultFeedbackKind; switch (vote) { case InteractiveSessionVoteDirection.Down: @@ -300,29 +282,28 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { kind = extHostTypes.ChatAgentResultFeedbackKind.Helpful; break; } - agent.acceptFeedback(reportIssue ? Object.freeze({ result, kind, reportIssue }) : Object.freeze({ result, kind })); + agent.acceptFeedback(reportIssue ? + Object.freeze({ result: ehResult, kind, reportIssue }) : + Object.freeze({ result: ehResult, kind })); } - $acceptAction(handle: number, sessionId: string, requestId: string, action: IChatUserActionEvent): void { + $acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void { const agent = this._agents.get(handle); if (!agent) { return; } - const result = this._resultsBySessionAndRequestId.get(sessionId)?.get(requestId); - if (!result) { - return; - } if (action.action.kind === 'vote') { // handled by $acceptFeedback return; } + const ehResult = typeConvert.ChatAgentResult.to(result); if (action.action.kind === 'command') { const commandAction: vscode.ChatAgentCommandAction = { kind: 'command', commandButton: typeConvert.ChatResponseProgress.toProgressContent(action.action.commandButton, this.commands.converter) as vscode.ChatAgentCommandButton }; - agent.acceptAction(Object.freeze({ action: commandAction, result })); + agent.acceptAction(Object.freeze({ action: commandAction, result: ehResult })); return; } else { - agent.acceptAction(Object.freeze({ action: action.action, result })); + agent.acceptAction(Object.freeze({ action: action.action, result: ehResult })); } } @@ -355,10 +336,10 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } } -class ExtHostChatAgent { +class ExtHostChatAgent { private _subCommandProvider: vscode.ChatAgentSubCommandProvider | undefined; - private _followupProvider: vscode.ChatAgentFollowupProvider | undefined; + private _followupProvider: vscode.ChatAgentFollowupProvider | undefined; private _description: string | undefined; private _fullName: string | undefined; private _iconPath: vscode.Uri | { light: vscode.Uri; dark: vscode.Uri } | vscode.ThemeIcon | undefined; @@ -367,7 +348,7 @@ class ExtHostChatAgent { private _helpTextPostfix: string | vscode.MarkdownString | undefined; private _sampleRequest?: string; private _isSecondary: boolean | undefined; - private _onDidReceiveFeedback = new Emitter>(); + private _onDidReceiveFeedback = new Emitter(); private _onDidPerformAction = new Emitter(); private _supportIssueReporting: boolean | undefined; private _agentVariableProvider?: { provider: vscode.ChatAgentCompletionItemProvider; triggerCharacters: string[] }; @@ -381,7 +362,7 @@ class ExtHostChatAgent { private readonly _callback: vscode.ChatAgentExtendedHandler, ) { } - acceptFeedback(feedback: vscode.ChatAgentResult2Feedback) { + acceptFeedback(feedback: vscode.ChatAgentResult2Feedback) { this._onDidReceiveFeedback.fire(feedback); } @@ -415,7 +396,7 @@ class ExtHostChatAgent { })); } - async provideFollowups(result: TResult, token: CancellationToken): Promise { + async provideFollowups(result: vscode.ChatAgentResult2, token: CancellationToken): Promise { if (!this._followupProvider) { return []; } @@ -458,7 +439,7 @@ class ExtHostChatAgent { return content?.map(f => typeConvert.ChatFollowup.from(f)); } - get apiAgent(): vscode.ChatAgent2 { + get apiAgent(): vscode.ChatAgent2 { let disposed = false; let updateScheduled = false; const updateMetadataSoon = () => { @@ -630,7 +611,7 @@ class ExtHostChatAgent { that._onDidReceiveFeedback.dispose(); that._proxy.$unregisterAgent(that._handle); }, - } satisfies vscode.ChatAgent2; + } satisfies vscode.ChatAgent2; } invoke(request: vscode.ChatAgentRequest, context: vscode.ChatAgentContext, response: vscode.ChatAgentExtendedResponseStream, token: CancellationToken): vscode.ProviderResult { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 6e0cc73d248..5886cc9bfc3 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -34,7 +34,7 @@ import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; import { getPrivateApiFor } from 'vs/workbench/api/common/extHostTestingPrivateApi'; import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/editor'; import { IViewBadge } from 'vs/workbench/common/views'; -import { IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; import { IChatCommandButton, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -2628,6 +2628,15 @@ export namespace ChatAgentCompletionItem { } } +export namespace ChatAgentResult { + export function to(result: IChatAgentResult): vscode.ChatAgentResult2 { + return { + errorDetails: result.errorDetails, + metadata: result.metadata, + }; + } +} + export namespace TerminalQuickFix { export function from(quickFix: vscode.TerminalQuickFixTerminalCommand | vscode.TerminalQuickFixOpener | vscode.Command, converter: Command.ICommandsConverter, disposables: DisposableStore): extHostProtocol.ITerminalQuickFixTerminalCommandDto | extHostProtocol.ITerminalQuickFixOpenerDto | extHostProtocol.ICommandDto | undefined { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 0076b4eb1f8..592ee91e256 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -108,6 +108,7 @@ export function registerChatCodeBlockActions() { agentId: context.element.agent?.id, sessionId: context.element.sessionId, requestId: context.element.requestId, + result: context.element.result, action: { kind: 'copy', codeBlockIndex: context.codeBlockIndex, @@ -151,6 +152,7 @@ export function registerChatCodeBlockActions() { agentId: context.element.agent?.id, sessionId: context.element.sessionId, requestId: context.element.requestId, + result: context.element.result, action: { kind: 'copy', codeBlockIndex: context.codeBlockIndex, @@ -325,6 +327,7 @@ export function registerChatCodeBlockActions() { agentId: context.element.agent?.id, sessionId: context.element.sessionId, requestId: context.element.requestId, + result: context.element.result, action: { kind: 'insert', codeBlockIndex: context.codeBlockIndex, @@ -370,6 +373,7 @@ export function registerChatCodeBlockActions() { agentId: context.element.agent?.id, sessionId: context.element.sessionId, requestId: context.element.requestId, + result: context.element.result, action: { kind: 'insert', codeBlockIndex: context.codeBlockIndex, @@ -464,6 +468,7 @@ export function registerChatCodeBlockActions() { agentId: context.element.agent?.id, sessionId: context.element.sessionId, requestId: context.element.requestId, + result: context.element.result, action: { kind: 'runInTerminal', codeBlockIndex: context.codeBlockIndex, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index f98c6fc6524..8204a71dea1 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -54,6 +54,7 @@ export function registerChatTitleActions() { agentId: item.agent?.id, sessionId: item.sessionId, requestId: item.requestId, + result: item.result, action: { kind: 'vote', direction: InteractiveSessionVoteDirection.Up, @@ -93,6 +94,7 @@ export function registerChatTitleActions() { agentId: item.agent?.id, sessionId: item.sessionId, requestId: item.requestId, + result: item.result, action: { kind: 'vote', direction: InteractiveSessionVoteDirection.Down, @@ -131,6 +133,7 @@ export function registerChatTitleActions() { agentId: item.agent?.id, sessionId: item.sessionId, requestId: item.requestId, + result: item.result, action: { kind: 'bug' } diff --git a/src/vs/workbench/contrib/chat/browser/chatQuick.ts b/src/vs/workbench/contrib/chat/browser/chatQuick.ts index a23257610dc..6e06936d15d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatQuick.ts +++ b/src/vs/workbench/contrib/chat/browser/chatQuick.ts @@ -289,13 +289,13 @@ class QuickChat extends Disposable { } for (const request of this.model.getRequests()) { - if (request.response?.response.value || request.response?.errorDetails) { + if (request.response?.response.value || request.response?.result) { this.chatService.addCompleteRequest(widget.viewModel.sessionId, request.message as IParsedChatRequest, request.variableData, { message: request.response.response.value, - errorDetails: request.response.errorDetails, + result: request.response.result, followups: request.response.followups }); } else if (request.message) { diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 2e4fffac465..13bd6f3d772 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -455,7 +455,8 @@ export class ChatWidget extends Disposable implements IChatWidget { providerId: this.viewModel.providerId, sessionId: this.viewModel.sessionId, requestId: e.response.requestId, - agentId: e.response?.agent?.id, + agentId: e.response.agent?.id, + result: e.response.result, action: { kind: 'followUp', followup: e.followup diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index cd4e3b1b489..2da3567a20e 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -31,7 +31,7 @@ export interface IChatAgentData { export interface IChatAgent extends IChatAgentData { invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; - provideFollowups?(sessionId: string, token: CancellationToken): Promise; + provideFollowups?(result: IChatAgentResult, token: CancellationToken): Promise; lastSlashCommands?: IChatAgentCommand[]; provideSlashCommands(token: CancellationToken): Promise; provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>; @@ -89,13 +89,13 @@ export interface IChatAgentRequest { } export interface IChatAgentResult { - // delete, keep while people are still using the previous API - followUp?: IChatFollowup[]; errorDetails?: IChatResponseErrorDetails; timings?: { firstProgress?: number; totalElapsed: number; }; + /** Extra properties that the agent can use to identify a result */ + readonly metadata?: { readonly [key: string]: any }; } export const IChatAgentService = createDecorator('chatAgentService'); @@ -105,7 +105,7 @@ export interface IChatAgentService { readonly onDidChangeAgents: Event; registerAgent(agent: IChatAgent): IDisposable; invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; - getFollowups(id: string, sessionId: string, token: CancellationToken): Promise; + getFollowups(id: string, result: IChatAgentResult, token: CancellationToken): Promise; getAgents(): Array; getAgent(id: string): IChatAgent | undefined; getDefaultAgent(): IChatAgent | undefined; @@ -184,7 +184,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService { return await data.agent.invoke(request, progress, history, token); } - async getFollowups(id: string, sessionId: string, token: CancellationToken): Promise { + async getFollowups(id: string, result: IChatAgentResult, token: CancellationToken): Promise { const data = this._agents.get(id); if (!data) { throw new Error(`No agent with id ${id}`); @@ -194,6 +194,6 @@ export class ChatAgentService extends Disposable implements IChatAgentService { return []; } - return data.agent.provideFollowups(sessionId, token); + return data.agent.provideFollowups(result, token); } } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 5b09beef63b..68b5b843ee3 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -14,9 +14,9 @@ import { URI, UriComponents, UriDto } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { ILogService } from 'vs/platform/log/common/log'; -import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatContent, IChatContentInlineReference, IChatContentReference, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext, IChatCommandButton } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatCommandButton, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; export interface IChatRequestVariableData { @@ -73,7 +73,7 @@ export interface IChatResponseModel { readonly isStale: boolean; readonly vote: InteractiveSessionVoteDirection | undefined; readonly followups?: IChatFollowup[] | undefined; - readonly errorDetails?: IChatResponseErrorDetails; + readonly result?: IChatAgentResult; setVote(vote: InteractiveSessionVoteDirection): void; } @@ -227,8 +227,8 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this._response; } - public get errorDetails(): IChatResponseErrorDetails | undefined { - return this._errorDetails; + public get result(): IChatAgentResult | undefined { + return this._result; } public get providerId(): string { @@ -282,7 +282,7 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel private _isComplete: boolean = false, private _isCanceled = false, private _vote?: InteractiveSessionVoteDirection, - private _errorDetails?: IChatResponseErrorDetails, + private _result?: IChatAgentResult, followups?: ReadonlyArray ) { super(); @@ -321,13 +321,13 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel this._onDidChange.fire(); } - setErrorDetails(errorDetails?: IChatResponseErrorDetails): void { - this._errorDetails = errorDetails; + setResult(result: IChatAgentResult): void { + this._result = result; this._onDidChange.fire(); } - complete(errorDetails?: IChatResponseErrorDetails): void { - if (errorDetails?.responseIsRedacted) { + complete(): void { + if (this._result?.errorDetails?.responseIsRedacted) { this._response.clear(); } @@ -380,7 +380,8 @@ export interface ISerializableChatRequestData { response: ReadonlyArray | undefined; agent?: ISerializableChatAgentData; slashCommand?: IChatAgentCommand; - responseErrorDetails: IChatResponseErrorDetails | undefined; + // responseErrorDetails: IChatResponseErrorDetails | undefined; + result?: IChatAgentResult; // Optional for backcompat followups: ReadonlyArray | undefined; isCanceled: boolean | undefined; vote: InteractiveSessionVoteDirection | undefined; @@ -561,13 +562,18 @@ export class ChatModel extends Disposable implements IChatModel { typeof raw.message === 'string' ? this.getParsedRequestFromString(raw.message) : reviveParsedChatRequest(raw.message); + // Only old messages don't have variableData const variableData: IChatRequestVariableData = raw.variableData ?? { message: parsedRequest.text, variables: {} }; const request = new ChatRequestModel(this, parsedRequest, variableData); - if (raw.response || raw.responseErrorDetails) { + if (raw.response || raw.result || (raw as any).responseErrorDetails) { const agent = (raw.agent && 'metadata' in raw.agent) ? // Check for the new format, ignore entries in the old format revive(raw.agent) : undefined; - request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, raw.slashCommand, request.id, true, raw.isCanceled, raw.vote, raw.responseErrorDetails, raw.followups); + + // Port entries from old format + const result = 'responseErrorDetails' in raw ? + { errorDetails: raw.responseErrorDetails } as IChatAgentResult : raw.result; + request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, raw.slashCommand, request.id, true, raw.isCanceled, raw.vote, result, raw.followups); if (raw.usedContext) { // @ulugbekna: if this's a new vscode sessions, doc versions are incorrect anyway? request.response.applyReference(revive(raw.usedContext)); } @@ -700,7 +706,7 @@ export class ChatModel extends Disposable implements IChatModel { } } - setResponse(request: ChatRequestModel, rawResponse: IChatResponse): void { + setResponse(request: ChatRequestModel, result: IChatAgentResult): void { if (!this._session) { throw new Error('completeResponse: No session'); } @@ -709,15 +715,15 @@ export class ChatModel extends Disposable implements IChatModel { request.response = new ChatResponseModel([], this, undefined, undefined, request.id); } - request.response.setErrorDetails(rawResponse.errorDetails); + request.response.setResult(result); } - completeResponse(request: ChatRequestModel, errorDetails: IChatResponseErrorDetails | undefined): void { + completeResponse(request: ChatRequestModel): void { if (!request.response) { throw new Error('Call setResponse before completeResponse'); } - request.response.complete(errorDetails); + request.response.complete(); } setFollowups(request: ChatRequestModel, followups: IChatFollowup[] | undefined): void { @@ -748,8 +754,12 @@ export class ChatModel extends Disposable implements IChatModel { } }), requests: this._requests.map((r): ISerializableChatRequestData => { + const message = { + ...r.message, + parts: r.message.parts.map(p => p && 'toJSON' in p ? (p.toJSON as Function)() : p) + }; return { - message: r.message, + message, variableData: r.variableData, response: r.response ? r.response.response.value.map(item => { @@ -763,7 +773,7 @@ export class ChatModel extends Disposable implements IChatModel { } }) : undefined, - responseErrorDetails: r.response?.errorDetails, + result: r.response?.result, followups: r.response?.followups, isCanceled: r.response?.isCanceled, vote: r.response?.vote, diff --git a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts index 65e4cf2a8fb..f65c12199b8 100644 --- a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts +++ b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts @@ -78,6 +78,21 @@ export class ChatRequestAgentPart implements IParsedChatRequestPart { get promptText(): string { return ''; } + + /** + * Don't stringify all the agent methods, just data. + */ + toJSON(): any { + return { + kind: this.kind, + range: this.range, + editorRange: this.editorRange, + agent: { + id: this.agent.id, + metadata: this.agent.metadata + } + }; + } } /** diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index bf5c78b9ef2..665ecb9eb29 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -12,7 +12,7 @@ import { IRange, Range } from 'vs/editor/common/core/range'; import { Command, Location, ProviderResult } from 'vs/editor/common/languages'; import { FileType } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentCommand, IChatAgentData, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModel, IChatModel, IChatRequestVariableData, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -40,15 +40,6 @@ export interface IChatResponseErrorDetails { responseIsRedacted?: boolean; } -export interface IChatResponse { - session: IChat; - errorDetails?: IChatResponseErrorDetails; - timings?: { - firstProgress?: number; - totalElapsed: number; - }; -} - export interface IChatResponseProgressFileTreeData { label: string; uri: URI; @@ -234,6 +225,7 @@ export interface IChatUserActionEvent { agentId: string | undefined; sessionId: string; requestId: string; + result: IChatAgentResult | undefined; } export interface IChatDynamicRequest { @@ -250,7 +242,7 @@ export interface IChatDynamicRequest { export interface IChatCompleteResponse { message: string | ReadonlyArray; - errorDetails?: IChatResponseErrorDetails; + result?: IChatAgentResult; followups?: IChatFollowup[]; } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index eae790915c1..1957228f12c 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -20,13 +20,13 @@ 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 { IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatResponse, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -516,7 +516,7 @@ export class ChatService extends Disposable implements IChatService { this._onDidSubmitAgent.fire({ agent: agentPart.agent, slashCommand: agentSlashCommandPart.command, sessionId: model.sessionId }); } - let rawResponse: IChatResponse | null | undefined; + let rawResult: IChatAgentResult | null | undefined; let agentOrCommandFollowups: Promise | undefined = undefined; const defaultAgent = this.chatAgentService.getDefaultAgent(); @@ -536,7 +536,7 @@ export class ChatService extends Disposable implements IChatService { variables: request.variableData.variables, command: request.response.slashCommand?.name }; - history.push({ request: historyRequest, response: request.response.response.value, result: { errorDetails: request.response.errorDetails } }); + history.push({ request: historyRequest, response: request.response.response.value, result: request.response.result ?? {} }); } const initVariableData: IChatRequestVariableData = { message: getPromptText(parsedRequest.parts), variables: {} }; @@ -554,13 +554,8 @@ export class ChatService extends Disposable implements IChatService { }; const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); - rawResponse = { - session: model.session!, - errorDetails: agentResult.errorDetails, - timings: agentResult.timings - }; - agentOrCommandFollowups = agentResult?.followUp ? Promise.resolve(agentResult.followUp) : - this.chatAgentService.getFollowups(agent.id, sessionId, CancellationToken.None); + rawResult = agentResult; + agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, agentResult, CancellationToken.None); } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { request = model.addRequest(parsedRequest, { message, variables: {} }); // contributed slash commands @@ -577,7 +572,7 @@ export class ChatService extends Disposable implements IChatService { progressCallback(p); }), history, token); agentOrCommandFollowups = Promise.resolve(commandResult?.followUp); - rawResponse = { session: model.session! }; + rawResult = {}; } else { throw new Error(`Cannot handle request`); @@ -586,36 +581,36 @@ export class ChatService extends Disposable implements IChatService { if (token.isCancellationRequested) { return; } else { - if (!rawResponse) { + if (!rawResult) { this.trace('sendRequest', `Provider returned no response for session ${model.sessionId}`); - rawResponse = { session: model.session!, errorDetails: { message: localize('emptyResponse', "Provider returned null response") } }; + rawResult = { errorDetails: { message: localize('emptyResponse', "Provider returned null response") } }; } - const result = rawResponse.errorDetails?.responseIsFiltered ? 'filtered' : - rawResponse.errorDetails && gotProgress ? 'errorWithOutput' : - rawResponse.errorDetails ? 'error' : + const result = rawResult.errorDetails?.responseIsFiltered ? 'filtered' : + rawResult.errorDetails && gotProgress ? 'errorWithOutput' : + rawResult.errorDetails ? 'error' : 'success'; this.telemetryService.publicLog2('interactiveSessionProviderInvoked', { providerId: provider.id, - timeToFirstProgress: rawResponse.timings?.firstProgress, - totalTime: rawResponse.timings?.totalElapsed, + timeToFirstProgress: rawResult.timings?.firstProgress, + totalTime: rawResult.timings?.totalElapsed, result, requestType, agent: agentPart?.agent.id ?? '', slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command, chatSessionId: model.sessionId }); - model.setResponse(request, rawResponse); + model.setResponse(request, rawResult); this.trace('sendRequest', `Provider returned response for session ${model.sessionId}`); // TODO refactor this or rethink the API https://github.com/microsoft/vscode-copilot/issues/593 if (agentOrCommandFollowups) { agentOrCommandFollowups.then(followups => { model.setFollowups(request, followups); - model.completeResponse(request, rawResponse?.errorDetails); + model.completeResponse(request); }); } else { - model.completeResponse(request, rawResponse?.errorDetails); + model.completeResponse(request); } } } finally { @@ -674,14 +669,11 @@ export class ChatService extends Disposable implements IChatService { model.acceptResponseProgress(request, part, true); } } - model.setResponse(request, { - session: model.session!, - errorDetails: response.errorDetails, - }); + model.setResponse(request, response.result || {}); if (response.followups !== undefined) { model.setFollowups(request, response.followups); } - model.completeResponse(request, response.errorDetails); + model.completeResponse(request); } cancelCurrentRequestForSession(sessionId: string): void { diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index d55a8b0c07f..bd2c325d728 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentCommand, IChatAgentData, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModelInitState, IChatModel, IChatRequestModel, IChatResponseModel, IChatWelcomeMessageContent, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatContentReference, IChatProgressMessage, IChatFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection, IChatCommandButton } from 'vs/workbench/contrib/chat/common/chatService'; @@ -122,6 +122,7 @@ export interface IChatResponseViewModel { readonly vote: InteractiveSessionVoteDirection | undefined; readonly replyFollowups?: IChatFollowup[]; readonly errorDetails?: IChatResponseErrorDetails; + readonly result?: IChatAgentResult; readonly contentUpdateTimings?: IChatLiveUpdateData; renderData?: IChatResponseRenderData; agentAvatarHasBeenRendered?: boolean; @@ -203,7 +204,7 @@ export class ChatViewModel extends Disposable implements IChatViewModel { if (typeof responseIdx === 'number' && responseIdx >= 0) { const items = this._items.splice(responseIdx, 1); const item = items[0]; - if (isResponseVM(item)) { + if (item instanceof ChatResponseViewModel) { item.dispose(); } } @@ -334,8 +335,12 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.followups?.filter((f): f is IChatFollowup => f.kind === 'reply'); } - get errorDetails() { - return this._model.errorDetails; + get result() { + return this._model.result; + } + + get errorDetails(): IChatResponseErrorDetails | undefined { + return this.result?.errorDetails; } get vote() { diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap index e9ee90ae021..cd6fa9b8139 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap @@ -10,6 +10,7 @@ text: "@ChatProviderWithUsedContext test request", parts: [ { + kind: "agent", range: { start: 0, endExclusive: 28 @@ -22,11 +23,8 @@ }, agent: { id: "ChatProviderWithUsedContext", - metadata: { }, - provideSlashCommands: [Function provideSlashCommands], - invoke: [Function invoke] - }, - kind: "agent" + metadata: { } + } }, { range: { @@ -49,8 +47,8 @@ variables: { } }, response: [ ], - responseErrorDetails: undefined, - followups: [ ], + result: { metadata: { metadataKey: "value" } }, + followups: undefined, isCanceled: false, vote: undefined, agent: { diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap index 0c270a4d7f3..8420b35111e 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap @@ -9,6 +9,7 @@ message: { parts: [ { + kind: "agent", range: { start: 0, endExclusive: 28 @@ -21,11 +22,8 @@ }, agent: { id: "ChatProviderWithUsedContext", - metadata: { }, - provideSlashCommands: [Function provideSlashCommands], - invoke: [Function invoke] - }, - kind: "agent" + metadata: { } + } }, { range: { @@ -49,7 +47,7 @@ variables: { } }, response: [ ], - responseErrorDetails: undefined, + result: { metadata: { metadataKey: "value" } }, followups: undefined, isCanceled: false, vote: undefined, 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 4bd2322fc6e..37ef4f4b4bd 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -75,7 +75,10 @@ const chatAgentWithUsedContext: IChatAgent = { kind: 'usedContext' }); - return {}; + return { metadata: { metadataKey: 'value' } }; + }, + async provideFollowups(sessionId, token) { + return [{ kind: 'reply', message: 'Something else', tooltip: 'a tooltip' }]; }, }; @@ -110,6 +113,9 @@ suite('Chat', () => { async invoke(request, progress, history, token) { return {}; }, + async provideSlashCommands(token) { + return []; + }, } as IChatAgent; testDisposables.add(chatAgentService.registerAgent(agent)); }); @@ -224,6 +230,7 @@ suite('Chat', () => { const response = await testService.sendRequest(model.sessionId, `@${chatAgentWithUsedContextId} test request`); assert(response); + await response.responseCompletePromise; assert.strictEqual(model.getRequests().length, 1); diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index d90f5b4e3ec..b986779094a 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -44,7 +44,7 @@ suite('VoiceChat', () => { readonly onDidChangeAgents = Event.None; registerAgent(agent: IChatAgent): IDisposable { throw new Error(); } invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error(); } - getFollowups(id: string, sessionId: string, token: CancellationToken): Promise { throw new Error(); } + getFollowups(id: string, result: IChatAgentResult, token: CancellationToken): Promise { throw new Error(); } getAgents(): Array { return agents; } getAgent(id: string): IChatAgent | undefined { throw new Error(); } getDefaultAgent(): IChatAgent | undefined { throw new Error(); } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 616f85ecc17..467256275ba 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -86,9 +86,10 @@ declare module 'vscode' { */ errorDetails?: ChatAgentErrorDetails; - // TODO@API - // add CATCH-all signature [name:string]: string|boolean|number instead of `T extends...` - // readonly metadata: { readonly [key: string]: any }; + /** + * Arbitrary metadata for this result. Can be anything but must be JSON-stringifyable. + */ + readonly metadata?: { readonly [key: string]: any }; } /** @@ -109,12 +110,12 @@ declare module 'vscode' { /** * Represents user feedback for a result. */ - export interface ChatAgentResult2Feedback { + export interface ChatAgentResult2Feedback { /** * This instance of ChatAgentResult2 is the same instance that was returned from the chat agent, * and it can be extended with arbitrary properties if needed. */ - readonly result: TResult; + readonly result: ChatAgentResult2; /** * The kind of feedback that was received. @@ -196,16 +197,16 @@ declare module 'vscode' { /** * Will be invoked once after each request to get suggested followup questions to show the user. The user can click the followup to send it to the chat. */ - export interface ChatAgentFollowupProvider { + export interface ChatAgentFollowupProvider { /** * * @param result The same instance of the result object that was returned by the chat agent, and it can be extended with arbitrary properties if needed. * @param token A cancellation token. */ - provideFollowups(result: TResult, token: CancellationToken): ProviderResult; + provideFollowups(result: ChatAgentResult2, token: CancellationToken): ProviderResult; } - export interface ChatAgent2 { + export interface ChatAgent2 { /** * The short name by which this agent is referred to in the UI, e.g `workspace`. @@ -244,7 +245,7 @@ declare module 'vscode' { /** * This provider will be called once after each request to retrieve suggested followup questions. */ - followupProvider?: ChatAgentFollowupProvider; + followupProvider?: ChatAgentFollowupProvider; // TODO@API @@ -268,7 +269,7 @@ declare module 'vscode' { * The passed {@link ChatAgentResult2Feedback.result result} is guaranteed to be the same instance that was * previously returned from this chat agent. */ - onDidReceiveFeedback: Event>; + onDidReceiveFeedback: Event; /** * Dispose this agent and free resources @@ -452,7 +453,7 @@ declare module 'vscode' { * @param handler The reply-handler of the agent. * @returns A new chat agent */ - export function createChatAgent(name: string, handler: ChatAgentHandler): ChatAgent2; + export function createChatAgent(name: string, handler: ChatAgentHandler): ChatAgent2; /** * Register a variable which can be used in a chat request to any agent. diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index 49924b8ff0c..ad7a4d3c796 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -5,7 +5,7 @@ declare module 'vscode' { - export interface ChatAgent2 { + export interface ChatAgent2 { onDidPerformAction: Event; supportIssueReporting?: boolean; } @@ -152,7 +152,7 @@ declare module 'vscode' { report(value: ChatAgentExtendedProgress): void; }; - export interface ChatAgent2 { + export interface ChatAgent2 { /** * Provide a set of variables that can only be used with this agent. */ @@ -179,7 +179,7 @@ declare module 'vscode' { /** * Create a chat agent with the extended progress type */ - export function createChatAgent(name: string, handler: ChatAgentExtendedHandler): ChatAgent2; + export function createChatAgent(name: string, handler: ChatAgentExtendedHandler): ChatAgent2; } /* diff --git a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts b/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts index e40a66c044c..3a4c675a96e 100644 --- a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts +++ b/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts @@ -12,7 +12,7 @@ declare module 'vscode' { provideSampleQuestions?(token: CancellationToken): ProviderResult; } - export interface ChatAgent2 { + export interface ChatAgent2 { /** * When true, this agent is invoked by default when no other agent is being invoked */ From 28cc5986c2727c09c8f65cc9a3ca1cdeca0e82e8 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sat, 10 Feb 2024 16:47:40 -0300 Subject: [PATCH 1108/1897] Properly cancel provideFollowups request (#204909) And don't block the request on followups completing --- .../contrib/chat/common/chatServiceImpl.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 1957228f12c..4e1222c1a0c 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -158,6 +158,8 @@ export class ChatService extends Disposable implements IChatService { private readonly _onDidUnregisterProvider = this._register(new Emitter<{ providerId: string }>()); public readonly onDidUnregisterProvider = this._onDidUnregisterProvider.event; + private readonly _sessionFollowupCancelTokens = this._register(new DisposableMap()); + constructor( @IStorageService private readonly storageService: IStorageService, @ILogService private readonly logService: ILogService, @@ -463,7 +465,16 @@ export class ChatService extends Disposable implements IChatService { return { responseCompletePromise: this._sendRequestAsync(model, sessionId, provider, request) }; } + private refreshFollowupsCancellationToken(sessionId: string): CancellationToken { + this._sessionFollowupCancelTokens.get(sessionId)?.cancel(); + const newTokenSource = new CancellationTokenSource(); + this._sessionFollowupCancelTokens.set(sessionId, newTokenSource); + + return newTokenSource.token; + } + private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, message: string): Promise { + const followupsCancelToken = this.refreshFollowupsCancellationToken(sessionId); const parsedRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message); let request: ChatRequestModel; @@ -555,7 +566,7 @@ export class ChatService extends Disposable implements IChatService { const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); rawResult = agentResult; - agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, agentResult, CancellationToken.None); + agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, agentResult, followupsCancelToken); } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { request = model.addRequest(parsedRequest, { message, variables: {} }); // contributed slash commands @@ -603,14 +614,11 @@ export class ChatService extends Disposable implements IChatService { model.setResponse(request, rawResult); this.trace('sendRequest', `Provider returned response for session ${model.sessionId}`); - // TODO refactor this or rethink the API https://github.com/microsoft/vscode-copilot/issues/593 + model.completeResponse(request); if (agentOrCommandFollowups) { agentOrCommandFollowups.then(followups => { model.setFollowups(request, followups); - model.completeResponse(request); }); - } else { - model.completeResponse(request); } } } finally { From b2f1748501a9924b68bd00ce582f1dfb97fddfed Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sat, 10 Feb 2024 18:58:18 -0300 Subject: [PATCH 1109/1897] Rename "subcommand" to "command" (#204911) * Rename "subcommand" to "command" * Fix build --- .../src/singlefolder-tests/chat.test.ts | 10 +++--- .../api/common/extHostChatAgents2.ts | 18 +++++----- .../api/common/extHostTypeConverters.ts | 2 +- .../vscode.proposed.chatAgents2.d.ts | 35 +++++++++---------- .../vscode.proposed.chatAgents2Additions.d.ts | 2 +- 5 files changed, 33 insertions(+), 34 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index cdb80bbb50e..871fb8572ff 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -37,8 +37,8 @@ suite('chat', () => { return null; }); agent.isDefault = true; - agent.subCommandProvider = { - provideSubCommands: (_token) => { + agent.commandProvider = { + provideCommands: (_token) => { return [{ name: 'hello', description: 'Hello' }]; } }; @@ -50,7 +50,7 @@ suite('chat', () => { const deferred = getDeferredForRequest(); interactive.sendInteractiveRequestToProvider('provider', { message: '@agent /hello friend' }); const request = await deferred.p; - assert.deepStrictEqual(request.subCommand, 'hello'); + assert.deepStrictEqual(request.command, 'hello'); assert.strictEqual(request.prompt, 'friend'); }); @@ -83,8 +83,8 @@ suite('chat', () => { return { metadata: { key: 'value' } }; }); agent.isDefault = true; - agent.subCommandProvider = { - provideSubCommands: (_token) => { + agent.commandProvider = { + provideCommands: (_token) => { return [{ name: 'hello', description: 'Hello' }]; } }; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 284caf0032b..558061ddbe8 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -338,7 +338,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { class ExtHostChatAgent { - private _subCommandProvider: vscode.ChatAgentSubCommandProvider | undefined; + private _commandProvider: vscode.ChatAgentCommandProvider | undefined; private _followupProvider: vscode.ChatAgentFollowupProvider | undefined; private _description: string | undefined; private _fullName: string | undefined; @@ -379,10 +379,10 @@ class ExtHostChatAgent { } async provideSlashCommands(token: CancellationToken): Promise { - if (!this._subCommandProvider) { + if (!this._commandProvider) { return []; } - const result = await this._subCommandProvider.provideSubCommands(token); + const result = await this._commandProvider.provideCommands(token); if (!result) { return []; } @@ -462,7 +462,7 @@ class ExtHostChatAgent { 'dark' in this._iconPath ? this._iconPath.dark : undefined, themeIcon: this._iconPath instanceof extHostTypes.ThemeIcon ? this._iconPath : undefined, - hasSlashCommands: this._subCommandProvider !== undefined, + hasSlashCommands: this._commandProvider !== undefined, hasFollowups: this._followupProvider !== undefined, isDefault: this._isDefault, isSecondary: this._isSecondary, @@ -501,11 +501,11 @@ class ExtHostChatAgent { that._iconPath = v; updateMetadataSoon(); }, - get subCommandProvider() { - return that._subCommandProvider; + get commandProvider() { + return that._commandProvider; }, - set subCommandProvider(v) { - that._subCommandProvider = v; + set commandProvider(v) { + that._commandProvider = v; updateMetadataSoon(); }, get followupProvider() { @@ -606,7 +606,7 @@ class ExtHostChatAgent { , dispose() { disposed = true; - that._subCommandProvider = undefined; + that._commandProvider = undefined; that._followupProvider = undefined; that._onDidReceiveFeedback.dispose(); that._proxy.$unregisterAgent(that._handle); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 5886cc9bfc3..e1e05919528 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2610,7 +2610,7 @@ export namespace ChatAgentRequest { return { prompt: request.message, variables: ChatVariable.objectTo(request.variables), - subCommand: request.command, + command: request.command, agentId: request.agentId, }; } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 467256275ba..5c12b9d4fd5 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -123,12 +123,12 @@ declare module 'vscode' { readonly kind: ChatAgentResultFeedbackKind; } - export interface ChatAgentSubCommand { + export interface ChatAgentCommand { /** * A short name by which this command is referred to in the UI, e.g. `fix` or * `explain` for commands that fix an issue or explain code. * - * **Note**: The name should be unique among the subCommands provided by this agent. + * **Note**: The name should be unique among the commands provided by this agent. */ readonly name: string; @@ -138,40 +138,39 @@ declare module 'vscode' { readonly description: string; /** - * When the user clicks this subCommand in `/help`, this text will be submitted to this subCommand + * When the user clicks this command in `/help`, this text will be submitted to this command */ readonly sampleRequest?: string; /** * Whether executing the command puts the * chat into a persistent mode, where the - * subCommand is prepended to the chat input. + * command is prepended to the chat input. */ readonly shouldRepopulate?: boolean; /** * Placeholder text to render in the chat input - * when the subCommand has been repopulated. + * when the command has been repopulated. * Has no effect if `shouldRepopulate` is `false`. */ // TODO@API merge this with shouldRepopulate? so that invalid state cannot be represented? readonly followupPlaceholder?: string; } - // TODO@API NAME: w/o Sub just `ChatAgentCommand` etc pp - export interface ChatAgentSubCommandProvider { + export interface ChatAgentCommandProvider { /** - * Returns a list of subCommands that its agent is capable of handling. A subCommand + * Returns a list of commands that its agent is capable of handling. A command * can be selected by the user and will then be passed to the {@link ChatAgentHandler handler} - * via the {@link ChatAgentRequest.subCommand subCommand} property. + * via the {@link ChatAgentRequest.command command} property. * * * @param token A cancellation token. - * @returns A list of subCommands. The lack of a result can be signaled by returning `undefined`, `null`, or + * @returns A list of commands. The lack of a result can be signaled by returning `undefined`, `null`, or * an empty array. */ - provideSubCommands(token: CancellationToken): ProviderResult; + provideCommands(token: CancellationToken): ProviderResult; } /** @@ -238,9 +237,9 @@ declare module 'vscode' { } | ThemeIcon; /** - * This provider will be called to retrieve the agent's subCommands. + * This provider will be called to retrieve the agent's commands. */ - subCommandProvider?: ChatAgentSubCommandProvider; + commandProvider?: ChatAgentCommandProvider; /** * This provider will be called once after each request to retrieve suggested followup questions. @@ -258,7 +257,7 @@ declare module 'vscode' { // onDidClearResult(value: TResult): void; /** - * When the user clicks this agent in `/help`, this text will be submitted to this subCommand + * When the user clicks this agent in `/help`, this text will be submitted to this command */ sampleRequest?: string; @@ -280,10 +279,10 @@ declare module 'vscode' { export interface ChatAgentRequest { /** - * The prompt entered by the user. The {@link ChatAgent2.name name} of the agent or the {@link ChatAgentSubCommand.name subCommand} + * The prompt entered by the user. The {@link ChatAgent2.name name} of the agent or the {@link ChatAgentCommand.name command} * are not part of the prompt. * - * @see {@link ChatAgentRequest.subCommand} + * @see {@link ChatAgentRequest.command} */ prompt: string; @@ -293,9 +292,9 @@ declare module 'vscode' { agentId: string; /** - * The name of the {@link ChatAgentSubCommand subCommand} that was selected for this request. + * The name of the {@link ChatAgentCommand command} that was selected for this request. */ - subCommand?: string; + command?: string; variables: Record; diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index ad7a4d3c796..22bf5f7dbd8 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -25,7 +25,7 @@ declare module 'vscode' { // TODO@API fit this into the stream export interface ChatAgentDetectedAgent { agentName: string; - command?: ChatAgentSubCommand; + command?: ChatAgentCommand; } // TODO@API fit this into the stream From 2aae82a102da66e566842ff9177bceeb99873970 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Sat, 10 Feb 2024 14:00:35 -0800 Subject: [PATCH 1110/1897] Include extension ID in variable item context (#204879) * remove broken precondition * include extension ID in context --- src/vs/workbench/api/common/extHost.protocol.ts | 1 + src/vs/workbench/api/common/extHostNotebookKernels.ts | 3 ++- src/vs/workbench/contrib/debug/common/debug.ts | 1 + .../notebookVariables/notebookVariableCommands.ts | 2 -- .../notebookVariables/notebookVariablesDataSource.ts | 1 + .../contrib/notebookVariables/notebookVariablesView.ts | 10 ++++++---- 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 4b49c05c2d7..7626fb4364f 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1121,6 +1121,7 @@ export interface VariablesResult { value: string; hasNamedChildren: boolean; indexedChildrenCount: number; + extensionId: string; } export interface MainThreadNotebookKernelsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index 265b29c33d3..e73b409421a 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -458,7 +458,8 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { value: result.variable.value, type: result.variable.type, hasNamedChildren: result.hasNamedChildren, - indexedChildrenCount: result.indexedChildrenCount + indexedChildrenCount: result.indexedChildrenCount, + extensionId: obj.extensionId.value, }; this.variableStore[variable.id] = result.variable; this._proxy.$receiveVariable(requestId, variable); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index c59762d498b..d16c7c8e0cd 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -88,6 +88,7 @@ export const CONTEXT_VARIABLE_VALUE = new RawContextKey('variableValue' export const CONTEXT_VARIABLE_TYPE = new RawContextKey('variableType', false, { type: 'string', description: nls.localize('variableType', "Type of the variable, present for debug visualization clauses.") }); export const CONTEXT_VARIABLE_NAME = new RawContextKey('variableName', false, { type: 'string', description: nls.localize('variableName', "Name of the variable, present for debug visualization clauses.") }); export const CONTEXT_VARIABLE_LANGUAGE = new RawContextKey('variableLanguage', false, { type: 'string', description: nls.localize('variableLanguage', "Language of the variable source, present for debug visualization clauses.") }); +export const CONTEXT_VARIABLE_EXTENSIONID = new RawContextKey('variableExtensionId', false, { type: 'string', description: nls.localize('variableExtensionId', "Extension ID of the variable source, present for debug visualization clauses.") }); export const CONTEXT_EXCEPTION_WIDGET_VISIBLE = new RawContextKey('exceptionWidgetVisible', false, { type: 'boolean', description: nls.localize('exceptionWidgetVisible', "True when the exception widget is visible.") }); export const CONTEXT_MULTI_SESSION_REPL = new RawContextKey('multiSessionRepl', false, { type: 'boolean', description: nls.localize('multiSessionRepl', "True when there is more than 1 debug console.") }); export const CONTEXT_MULTI_SESSION_DEBUG = new RawContextKey('multiSessionDebug', false, { type: 'boolean', description: nls.localize('multiSessionDebug', "True when there is more than 1 active debug session.") }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts index 168480825c3..fcdd865e65e 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts @@ -6,7 +6,6 @@ import { localize } from 'vs/nls'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { contextMenuArg } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView'; @@ -18,7 +17,6 @@ registerAction2(class extends Action2 { id: COPY_NOTEBOOK_VARIABLE_VALUE_ID, title: COPY_NOTEBOOK_VARIABLE_VALUE_LABEL, f1: false, - precondition: ContextKeyExpr.has('value'), }); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts index 7fdb7141760..c233e02a8a0 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts @@ -26,6 +26,7 @@ export interface INotebookVariableElement { readonly indexStart?: number; readonly hasNamedChildren: boolean; readonly notebook: NotebookTextModel; + readonly extensionId?: string; } export class NotebookVariableDataSource implements IAsyncDataSource { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index f0fc23bfa4f..3e4cf3cf7fc 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -25,7 +25,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewDescriptorService } from 'vs/workbench/common/views'; -import { CONTEXT_VARIABLE_LANGUAGE, CONTEXT_VARIABLE_NAME, CONTEXT_VARIABLE_TYPE, CONTEXT_VARIABLE_VALUE } from 'vs/workbench/contrib/debug/common/debug'; +import { CONTEXT_VARIABLE_EXTENSIONID, CONTEXT_VARIABLE_LANGUAGE, CONTEXT_VARIABLE_NAME, CONTEXT_VARIABLE_TYPE, CONTEXT_VARIABLE_VALUE } from 'vs/workbench/contrib/debug/common/debug'; import { INotebookScope, INotebookVariableElement, NotebookVariableDataSource } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource'; import { NotebookVariableAccessibilityProvider, NotebookVariableRenderer, NotebookVariablesDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree'; import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -34,7 +34,7 @@ import { ICellExecutionStateChangedEvent, IExecutionStateChangedEvent, INotebook import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -export type contextMenuArg = { source?: string; type?: string; value?: string; language?: string }; +export type contextMenuArg = { source?: string; type?: string; value?: string; language?: string; extensionId?: string }; export class NotebookVariablesView extends ViewPane { @@ -110,7 +110,8 @@ export class NotebookVariablesView extends ViewPane { source: element.notebook.uri.toString(), value: element.value, type: element.type, - language: element.language + language: element.language, + extensionId: element.extensionId }; const actions: IAction[] = []; @@ -118,7 +119,8 @@ export class NotebookVariablesView extends ViewPane { [CONTEXT_VARIABLE_NAME.key, element.name], [CONTEXT_VARIABLE_VALUE.key, element.value], [CONTEXT_VARIABLE_TYPE.key, element.type], - [CONTEXT_VARIABLE_LANGUAGE.key, element.language] + [CONTEXT_VARIABLE_LANGUAGE.key, element.language], + [CONTEXT_VARIABLE_EXTENSIONID.key, element.extensionId] ]); const menu = this.menuService.createMenu(MenuId.NotebookVariablesContext, overlayedContext); createAndFillInContextMenuActions(menu, { arg, shouldForwardArgs: true }, actions); From c19383a66d9d69de67d60e0d5774cd9212179240 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Sun, 11 Feb 2024 07:39:43 +0100 Subject: [PATCH 1111/1897] Git - add file decoration provider for incoming changes (#204919) * Initial implementation of a file decoration provider and quick diff provider * Refactor file decoration provider * Add incomingChanges to history provider * Move decoration provider * Move things around * Add separate color for renamed incoming change * Remove include that is not needed --- extensions/git/package.json | 40 +++++++++ extensions/git/package.nls.json | 4 + extensions/git/src/decorationProvider.ts | 108 +++++++++++++++++++++-- extensions/git/src/historyProvider.ts | 39 +++++--- 4 files changed, 173 insertions(+), 18 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index bee5fc15c87..a36b08c0baa 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -3176,6 +3176,46 @@ "highContrast": "#8db9e2", "highContrastLight": "#1258a7" } + }, + { + "id": "gitDecoration.incomingAddedForegroundColor", + "description": "%colors.incomingAdded%", + "defaults": { + "light": "#587c0c", + "dark": "#81b88b", + "highContrast": "#1b5225", + "highContrastLight": "#374e06" + } + }, + { + "id": "gitDecoration.incomingDeletedForegroundColor", + "description": "%colors.incomingDeleted%", + "defaults": { + "light": "#ad0707", + "dark": "#c74e39", + "highContrast": "#c74e39", + "highContrastLight": "#ad0707" + } + }, + { + "id": "gitDecoration.incomingRenamedForegroundColor", + "description": "%colors.incomingRenamed%", + "defaults": { + "light": "#007100", + "dark": "#73C991", + "highContrast": "#73C991", + "highContrastLight": "#007100" + } + }, + { + "id": "gitDecoration.incomingModifiedForegroundColor", + "description": "%colors.incomingModified%", + "defaults": { + "light": "#895503", + "dark": "#E2C08D", + "highContrast": "#E2C08D", + "highContrastLight": "#895503" + } } ], "configurationDefaults": { diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 5cc838d25c3..388d5387261 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -286,6 +286,10 @@ "colors.ignored": "Color for ignored resources.", "colors.conflict": "Color for resources with conflicts.", "colors.submodule": "Color for submodule resources.", + "colors.incomingAdded": "Color for added incoming resource.", + "colors.incomingDeleted": "Color for deleted incoming resource.", + "colors.incomingRenamed": "Color for renamed incoming resource.", + "colors.incomingModified": "Color for modified incoming resource.", "view.workbench.scm.missing.windows": { "message": "[Download Git for Windows](https://git-scm.com/download/win)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).", "comment": [ diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index c630f00c712..943af377eb7 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { window, workspace, Uri, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, ThemeColor } from 'vscode'; +import { window, workspace, Uri, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, ThemeColor, l10n } from 'vscode'; import * as path from 'path'; import { Repository, GitResourceGroup } from './repository'; import { Model } from './model'; import { debounce } from './decorators'; -import { filterEvent, dispose, anyEvent, fireEvent, PromiseSource } from './util'; -import { GitErrorCodes, Status } from './api/git'; +import { filterEvent, dispose, anyEvent, fireEvent, PromiseSource, combinedDisposable } from './util'; +import { Change, GitErrorCodes, Status } from './api/git'; class GitIgnoreDecorationProvider implements FileDecorationProvider { @@ -153,6 +153,100 @@ class GitDecorationProvider implements FileDecorationProvider { } } +class GitIncomingChangesFileDecorationProvider implements FileDecorationProvider { + + private readonly _onDidChangeDecorations = new EventEmitter(); + readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; + + private decorations = new Map(); + private readonly disposables: Disposable[] = []; + + constructor(private readonly repository: Repository) { + this.disposables.push(window.registerFileDecorationProvider(this)); + repository.historyProvider.onDidChangeCurrentHistoryItemGroupBase(this.onDidChangeCurrentHistoryItemGroupBase, this, this.disposables); + } + + private async onDidChangeCurrentHistoryItemGroupBase(): Promise { + const newDecorations = new Map(); + await this.collectIncomingChangesFileDecorations(newDecorations); + const uris = new Set([...this.decorations.keys()].concat([...newDecorations.keys()])); + + this.decorations = newDecorations; + this._onDidChangeDecorations.fire([...uris.values()].map(value => Uri.parse(value, true))); + } + + private async collectIncomingChangesFileDecorations(bucket: Map): Promise { + for (const change of await this.getIncomingChanges()) { + switch (change.status) { + case Status.INDEX_ADDED: + bucket.set(change.uri.toString(), { + badge: '↓A', + color: new ThemeColor('gitDecoration.incomingAddedForegroundColor'), + tooltip: l10n.t('Incoming Changes (added)'), + }); + break; + case Status.DELETED: + bucket.set(change.uri.toString(), { + badge: '↓D', + color: new ThemeColor('gitDecoration.incomingDeletedForegroundColor'), + tooltip: l10n.t('Incoming Changes (deleted)'), + }); + break; + case Status.INDEX_RENAMED: + bucket.set(change.originalUri.toString(), { + badge: '↓R', + color: new ThemeColor('gitDecoration.incomingRenamedForegroundColor'), + tooltip: l10n.t('Incoming Changes (renamed)'), + }); + break; + case Status.MODIFIED: + bucket.set(change.uri.toString(), { + badge: '↓M', + color: new ThemeColor('gitDecoration.incomingModifiedForegroundColor'), + tooltip: l10n.t('Incoming Changes (modified)'), + }); + break; + default: { + bucket.set(change.uri.toString(), { + badge: '↓~', + color: new ThemeColor('gitDecoration.incomingModifiedForegroundColor'), + tooltip: l10n.t('Incoming Changes'), + }); + break; + } + } + } + } + + private async getIncomingChanges(): Promise { + try { + const historyProvider = this.repository.historyProvider; + const currentHistoryItemGroup = historyProvider.currentHistoryItemGroup; + + if (!currentHistoryItemGroup?.base) { + return []; + } + + const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(currentHistoryItemGroup.id, currentHistoryItemGroup.base.id); + if (!ancestor) { + return []; + } + + const changes = await this.repository.diffBetween(ancestor.id, currentHistoryItemGroup.base.id); + return changes; + } catch (err) { + return []; + } + } + + provideFileDecoration(uri: Uri): FileDecoration | undefined { + return this.decorations.get(uri.toString()); + } + + dispose(): void { + dispose(this.disposables); + } +} export class GitDecorations { @@ -191,8 +285,12 @@ export class GitDecorations { } private onDidOpenRepository(repository: Repository): void { - const provider = new GitDecorationProvider(repository); - this.providers.set(repository, provider); + const providers = combinedDisposable([ + new GitDecorationProvider(repository), + new GitIncomingChangesFileDecorationProvider(repository) + ]); + + this.providers.set(repository, providers); } private onDidCloseRepository(repository: Repository): void { diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index dbb7b8d8a36..80fbbe52799 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -6,7 +6,7 @@ import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel } from 'vscode'; import { Repository, Resource } from './repository'; -import { IDisposable, filterEvent } from './util'; +import { IDisposable, dispose, filterEvent } from './util'; import { toGitUri } from './uri'; import { Branch, RefType, UpstreamRef } from './api/git'; import { emojify, ensureEmojis } from './emoji'; @@ -17,16 +17,20 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec private readonly _onDidChangeCurrentHistoryItemGroup = new EventEmitter(); readonly onDidChangeCurrentHistoryItemGroup: Event = this._onDidChangeCurrentHistoryItemGroup.event; + private readonly _onDidChangeCurrentHistoryItemGroupBase = new EventEmitter(); + readonly onDidChangeCurrentHistoryItemGroupBase: Event = this._onDidChangeCurrentHistoryItemGroupBase.event; + private readonly _onDidChangeDecorations = new EventEmitter(); readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; private _HEAD: Branch | undefined; private _currentHistoryItemGroup: SourceControlHistoryItemGroup | undefined; - get currentHistoryItemGroup(): SourceControlHistoryItemGroup | undefined { return this._currentHistoryItemGroup; } set currentHistoryItemGroup(value: SourceControlHistoryItemGroup | undefined) { this._currentHistoryItemGroup = value; this._onDidChangeCurrentHistoryItemGroup.fire(); + + this.logger.trace('GitHistoryProvider:onDidRunGitStatus - currentHistoryItemGroup:', JSON.stringify(value)); } private historyItemDecorations = new Map(); @@ -55,26 +59,35 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return; } - this._HEAD = this.repository.HEAD; - // Check if HEAD does not support incoming/outgoing (detached commit, tag) - if (!this._HEAD?.name || !this._HEAD?.commit || this._HEAD.type === RefType.Tag) { - this.currentHistoryItemGroup = undefined; + if (!this.repository.HEAD?.name || !this.repository.HEAD?.commit || this.repository.HEAD.type === RefType.Tag) { this.logger.trace('GitHistoryProvider:onDidRunGitStatus - HEAD does not support incoming/outgoing'); + + this.currentHistoryItemGroup = undefined; + this._HEAD = this.repository.HEAD; return; } this.currentHistoryItemGroup = { - id: `refs/heads/${this._HEAD.name ?? ''}`, - label: this._HEAD.name ?? '', - base: this._HEAD.upstream ? + id: `refs/heads/${this.repository.HEAD.name ?? ''}`, + label: this.repository.HEAD.name ?? '', + base: this.repository.HEAD.upstream ? { - id: `refs/remotes/${this._HEAD.upstream.remote}/${this._HEAD.upstream.name}`, - label: `${this._HEAD.upstream.remote}/${this._HEAD.upstream.name}`, + id: `refs/remotes/${this.repository.HEAD.upstream.remote}/${this.repository.HEAD.upstream.name}`, + label: `${this.repository.HEAD.upstream.remote}/${this.repository.HEAD.upstream.name}`, } : undefined }; - this.logger.trace('GitHistoryProvider:onDidRunGitStatus - currentHistoryItemGroup:', JSON.stringify(this.currentHistoryItemGroup)); + // Check if Upstream has changed + if (force || + this._HEAD?.upstream?.name !== this.repository.HEAD?.upstream?.name || + this._HEAD?.upstream?.remote === this.repository.HEAD?.upstream?.remote || + this._HEAD?.upstream?.commit === this.repository.HEAD?.upstream?.commit) { + this.logger.trace('GitHistoryProvider:onDidRunGitStatus - Upstream has changed'); + this._onDidChangeCurrentHistoryItemGroupBase.fire(); + } + + this._HEAD = this.repository.HEAD; } async provideHistoryItems(historyItemGroupId: string, options: SourceControlHistoryOptions): Promise { @@ -216,6 +229,6 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec } dispose(): void { - this.disposables.forEach(d => d.dispose()); + dispose(this.disposables); } } From a36fce7bd78ec12b55b96cc97b4f0d6d46b19dd8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 10 Feb 2024 18:04:30 +0100 Subject: [PATCH 1112/1897] voice - use `startsWith` --- .../workbench/contrib/chat/common/voiceChat.ts | 17 +++++++++++------ .../contrib/chat/test/common/voiceChat.test.ts | 5 +++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index 8d277ba9931..ff8679fa3ea 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -6,7 +6,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { rtrim, startsWithIgnoreCase } from 'vs/base/common/strings'; +import { rtrim } from 'vs/base/common/strings'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; @@ -63,11 +63,16 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { private static readonly AGENT_PREFIX = chatAgentLeader; private static readonly COMMAND_PREFIX = chatSubcommandLeader; - private static readonly PHRASES = { + private static readonly PHRASES_LOWER = { [VoiceChatService.AGENT_PREFIX]: 'at', [VoiceChatService.COMMAND_PREFIX]: 'slash' }; + private static readonly PHRASES_UPPER = { + [VoiceChatService.AGENT_PREFIX]: 'At', + [VoiceChatService.COMMAND_PREFIX]: 'Slash' + }; + private static readonly CHAT_AGENT_ALIAS = new Map([['vscode', 'code']]); private _phrases: Map | undefined = undefined; @@ -96,12 +101,12 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { const phrases = new Map(); for (const agent of this.chatAgentService.getAgents()) { - const agentPhrase = `${VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX]} ${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase(); + const agentPhrase = `${VoiceChatService.PHRASES_LOWER[VoiceChatService.AGENT_PREFIX]} ${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase(); phrases.set(agentPhrase, { agent: agent.id }); if (agent.lastSlashCommands) { for (const slashCommand of agent.lastSlashCommands) { - const slashCommandPhrase = `${VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]} ${slashCommand.name}`.toLowerCase(); + const slashCommandPhrase = `${VoiceChatService.PHRASES_LOWER[VoiceChatService.COMMAND_PREFIX]} ${slashCommand.name}`.toLowerCase(); phrases.set(slashCommandPhrase, { agent: agent.id, command: slashCommand.name }); const agentSlashCommandPhrase = `${agentPhrase} ${slashCommandPhrase}`.toLowerCase(); @@ -141,8 +146,8 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { case SpeechToTextStatus.Recognizing: case SpeechToTextStatus.Recognized: if (e.text) { - const startsWithAgent = startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX]); - const startsWithSlashCommand = startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]); + const startsWithAgent = e.text.startsWith(VoiceChatService.PHRASES_UPPER[VoiceChatService.AGENT_PREFIX]) || e.text.startsWith(VoiceChatService.PHRASES_LOWER[VoiceChatService.AGENT_PREFIX]); + const startsWithSlashCommand = e.text.startsWith(VoiceChatService.PHRASES_UPPER[VoiceChatService.COMMAND_PREFIX]) || e.text.startsWith(VoiceChatService.PHRASES_LOWER[VoiceChatService.COMMAND_PREFIX]); if (startsWithAgent || startsWithSlashCommand) { const originalWords = e.text.split(' '); let transformedWords: string[] | undefined; diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index b986779094a..d40cdccffc8 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -147,6 +147,11 @@ suite('VoiceChat', () => { assert.strictEqual(event?.text, options.usesAgents ? '@workspace' : 'At workspace'); assert.strictEqual(event?.waitingForInput, options.usesAgents); + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'at workspace' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace' : 'at workspace'); + assert.strictEqual(event?.waitingForInput, options.usesAgents); + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, options.usesAgents ? '@workspace help' : 'At workspace help'); From 01f46bb5357baa8f3b9da690e1e34eb78e09a72b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 10 Feb 2024 18:12:04 +0100 Subject: [PATCH 1113/1897] Go Back and Go Forward don't work sometimes (fix #204359) --- .../workbench/services/history/browser/historyService.ts | 5 +++++ .../services/history/test/browser/historyService.test.ts | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts index 18062624a78..1c73d334210 100644 --- a/src/vs/workbench/services/history/browser/historyService.ts +++ b/src/vs/workbench/services/history/browser/historyService.ts @@ -1692,6 +1692,7 @@ ${entryLabels.join('\n')} } remove(arg1: EditorInput | FileChangesEvent | FileOperationEvent | GroupIdentifier): void { + const previousStackSize = this.stack.length; // Remove all stack entries that match `arg1` this.stack = this.stack.filter(entry => { @@ -1705,6 +1706,10 @@ ${entryLabels.join('\n')} return !matches; }); + if (previousStackSize === this.stack.length) { + return; // nothing removed + } + // Given we just removed entries, we need to make sure // to remove entries that are now identical and next // to each other to prevent no-op navigations. diff --git a/src/vs/workbench/services/history/test/browser/historyService.test.ts b/src/vs/workbench/services/history/test/browser/historyService.test.ts index cb861fbc783..124a79c0203 100644 --- a/src/vs/workbench/services/history/test/browser/historyService.test.ts +++ b/src/vs/workbench/services/history/test/browser/historyService.test.ts @@ -427,6 +427,7 @@ suite('HistoryService', function () { const resource: URI = toResource.call(this, '/path/index.txt'); const otherResource: URI = toResource.call(this, '/path/index.html'); + const unrelatedResource: URI = toResource.call(this, '/path/unrelated.html'); const pane = await editorService.openEditor({ resource, options: { pinned: true } }); stack.notifyNavigation(pane); @@ -444,7 +445,14 @@ suite('HistoryService', function () { await editorService.openEditor({ resource: otherResource, options: { pinned: true } }); stack.notifyNavigation(pane); + // Remove unrelated resource does not cause any harm (via internal event) + await stack.goBack(); + assert.strictEqual(stack.canGoForward(), true); + stack.remove(new FileOperationEvent(unrelatedResource, FileOperation.DELETE)); + assert.strictEqual(stack.canGoForward(), true); + // Remove (via internal event) + await stack.goForward(); assert.strictEqual(stack.canGoBack(), true); stack.remove(new FileOperationEvent(resource, FileOperation.DELETE)); assert.strictEqual(stack.canGoBack(), false); From fea743c7cf1dfebdbf1fd095ac8a17a0a465cff5 Mon Sep 17 00:00:00 2001 From: rebornix Date: Sun, 11 Feb 2024 21:12:01 -0800 Subject: [PATCH 1114/1897] Handle chat widget resize properly and handle follow up index. --- .../controller/chat/notebookChatController.ts | 49 ++++++++++++------- .../browser/viewParts/notebookViewZones.ts | 2 + 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index a336ad2c6ea..307c202e33c 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -51,44 +51,40 @@ import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; const WIDGET_MARGIN_BOTTOM = 16; class NotebookChatWidget extends Disposable implements INotebookViewZone { - private _afterModelPosition: number; - set afterModelPosition(afterModelPosition: number) { - this._afterModelPosition = afterModelPosition; + this.notebookViewZone.afterModelPosition = afterModelPosition; } get afterModelPosition(): number { - return this._afterModelPosition; + return this.notebookViewZone.afterModelPosition; } - private _heightInPx: number; - set heightInPx(heightInPx: number) { - this._heightInPx = heightInPx; + this.notebookViewZone.heightInPx = heightInPx; } get heightInPx(): number { - return this._heightInPx; + return this.notebookViewZone.heightInPx; } private _editingCell: CellViewModel | null = null; + get editingCell() { + return this._editingCell; + } + constructor( private readonly _notebookEditor: INotebookEditor, readonly id: string, + readonly notebookViewZone: INotebookViewZone, readonly domNode: HTMLElement, readonly widgetContainer: HTMLElement, readonly inlineChatWidget: InlineChatWidget, readonly parentEditor: CodeEditorWidget, - afterModelPosition: number, - heightInPx: number, private readonly _languageService: ILanguageService, ) { super(); - this._afterModelPosition = afterModelPosition; - this._heightInPx = heightInPx; - this._register(inlineChatWidget.onDidChangeHeight(() => { this.heightInPx = inlineChatWidget.getHeight() + WIDGET_MARGIN_BOTTOM; this._notebookEditor.changeViewZones(accessor => { @@ -114,7 +110,7 @@ class NotebookChatWidget extends Disposable implements INotebookViewZone { return undefined; } - this._editingCell = insertCell(this._languageService, this._notebookEditor, this._afterModelPosition, CellKind.Code, 'above'); + this._editingCell = insertCell(this._languageService, this._notebookEditor, this.afterModelPosition, CellKind.Code, 'above'); if (!this._editingCell) { return undefined; @@ -223,7 +219,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito { isSimpleWidget: true } ); - const inputBoxPath = `/notebook-chat-input0-${NotebookChatController.counter++}`; + const inputBoxPath = `/notebook-chat-input-${NotebookChatController.counter++}`; const inputUri = URI.from({ scheme: Schemas.untitled, path: inputBoxPath }); const result: ITextModel = this._modelService.createModel('', null, inputUri, false); fakeParentEditor.setModel(result); @@ -243,21 +239,22 @@ export class NotebookChatController extends Disposable implements INotebookEdito widgetContainer.appendChild(inlineChatWidget.domNode); this._notebookEditor.changeViewZones(accessor => { - const id = accessor.addZone({ + const notebookViewZone = { afterModelPosition: index, heightInPx: 80 + WIDGET_MARGIN_BOTTOM, domNode: viewZoneContainer - }); + }; + + const id = accessor.addZone(notebookViewZone); this._widget = new NotebookChatWidget( this._notebookEditor, id, + notebookViewZone, viewZoneContainer, widgetContainer, inlineChatWidget, fakeParentEditor, - index, - 80 + WIDGET_MARGIN_BOTTOM, this._languageService ); @@ -304,6 +301,20 @@ export class NotebookChatController extends Disposable implements INotebookEdito return; } + const editingCellIndex = this._widget.editingCell ? this._notebookEditor.getCellIndex(this._widget.editingCell) : undefined; + if (editingCellIndex !== undefined) { + this._notebookEditor.setSelections([{ + start: editingCellIndex, + end: editingCellIndex + 1 + }]); + } else { + // Update selection to the widget index + this._notebookEditor.setSelections([{ + start: this._widget.afterModelPosition, + end: this._widget.afterModelPosition + }]); + } + this._ctxHasActiveRequest.set(true); this._widget.inlineChatWidget.updateSlashCommands(this._activeSession.session.slashCommands ?? []); this._widget?.inlineChatWidget.updateProgress(true); diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts index a56dd538155..bc7384c7654 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts @@ -149,6 +149,8 @@ export class NotebookViewZones extends Disposable { return; } + this._updateWhitespace(this._zones[id]); + const isInHiddenArea = this._isInHiddenRanges(zoneWidget.zone); if (isInHiddenArea) { From deadef1f3c0c3117903304c42dae9ce165f98713 Mon Sep 17 00:00:00 2001 From: rebornix Date: Sun, 11 Feb 2024 21:15:00 -0800 Subject: [PATCH 1115/1897] Fix accept execption blocking dismiss --- .../notebook/browser/controller/chat/notebookChatController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 307c202e33c..a2fde377709 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -498,9 +498,9 @@ export class NotebookChatController extends Disposable implements INotebookEdito try { await this._strategy.apply(editor); + this._inlineChatSessionService.releaseSession(this._activeSession); } catch (_err) { } - this._inlineChatSessionService.releaseSession(this._activeSession); this.dismiss(); } From 983a3bc0aecfc0772dd7b37919d7da36c89a6f2d Mon Sep 17 00:00:00 2001 From: Robo Date: Mon, 12 Feb 2024 14:42:48 +0900 Subject: [PATCH 1116/1897] chore: bump electron@27.3.2 (#204960) * chore: bump electron@27.3.2 * chore: bump distro --- .yarnrc | 4 +- build/checksums/electron.txt | 150 +++++++++++++++++------------------ cgmanifest.json | 4 +- package.json | 4 +- yarn.lock | 8 +- 5 files changed, 85 insertions(+), 85 deletions(-) diff --git a/.yarnrc b/.yarnrc index e7aba7e6b76..19c5cb1eb8f 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,5 +1,5 @@ disturl "https://electronjs.org/headers" -target "27.3.1" -ms_build_id "26731440" +target "27.3.2" +ms_build_id "26836302" runtime "electron" build_from_source "true" diff --git a/build/checksums/electron.txt b/build/checksums/electron.txt index 2eaf08f2c00..a774dffc830 100644 --- a/build/checksums/electron.txt +++ b/build/checksums/electron.txt @@ -1,75 +1,75 @@ -27fcfc6982b3b1b56cbb611c5060bdbee35fe64aab6c8a24e19ac133d1bbaf05 *chromedriver-v27.3.1-darwin-arm64.zip -56ddf9df850b3612e8aa61204e6334400a89502bb8acabcc9873218317831d0d *chromedriver-v27.3.1-darwin-x64.zip -c070bc178589426112c4ff740712ad3ce5da672f717a93ef74c3d53f23173e08 *chromedriver-v27.3.1-linux-arm64.zip -84f072cca93660bc5eb2f261d71c6db207e7a979ecede92c0f0c5d64cbdfc907 *chromedriver-v27.3.1-linux-armv7l.zip -a722b30273d94d370434604949c4bccb38f3c725e14429d9f6dc9f33455414bf *chromedriver-v27.3.1-linux-x64.zip -8ca0fe4e3daeef42002213c2bf957539c77010e16a5772a0a79e167cf803bb85 *chromedriver-v27.3.1-mas-arm64.zip -52bba4265e0e18f6958de225b9726b262efafebdfee846749f4d83c400046a74 *chromedriver-v27.3.1-mas-x64.zip -b1b9dfd2d5c962b87827b0f4f72ab955cdee14358e9914476b30de603b8834ce *chromedriver-v27.3.1-win32-arm64.zip -a74ae6f90b8d0ae0e29a53a254d25a9ca09eaadeb0bc546fea0aff838345a494 *chromedriver-v27.3.1-win32-ia32.zip -bf37f6947e2e0dc19a99320a39aa23cf644dcf075d7d9ba2394364681aaafb60 *chromedriver-v27.3.1-win32-x64.zip -b25fb8ae02721a89614aeb4db621a797fca385340c8b6be64724785a2e734d23 *electron-api.json -e8394d2c23c878b4bd774f85a1f468547f4c9e4669e14b19850f685ca7ab3c76 *electron-v27.3.1-darwin-arm64-dsym-snapshot.zip -f346aeb98ef1c059b6fc035f837ba997e98d2a2b9e06076ec882df877ae1d6be *electron-v27.3.1-darwin-arm64-dsym.zip -a66a6656acc7217a3f8627a384d9ca5ba3d3cb1b7e144e25bb82344eb211612b *electron-v27.3.1-darwin-arm64-symbols.zip -0c8b1c3bf1fcb52be3c0517603fe69329050a2efb1c61ab5e5988a54aa693ebd *electron-v27.3.1-darwin-arm64.zip -92086b977a81d5e4787bdcff9e4b684807edbf71093cdc54c1d2169b96c0165d *electron-v27.3.1-darwin-x64-dsym-snapshot.zip -57c0d9dd591d2abfa56865323745e411351f639049eefa4506116cbe421baca5 *electron-v27.3.1-darwin-x64-dsym.zip -069c76522f1c4316065932a90067f02b7f6e8f144133c2ba09da8d97a06c4c0f *electron-v27.3.1-darwin-x64-symbols.zip -e2360124f0fdb3ff5e6a644d0e139a9546947ee2f69e51f3d310c3f2cbcd19ee *electron-v27.3.1-darwin-x64.zip -e6fdce1f521511b13ec4425d8a7e51077e83faa4bbfa18026598037c6688965c *electron-v27.3.1-linux-arm64-debug.zip -3304fcfbf8aa45f3a174d404174c34123b8f777259eae7fe26a9d82104068a89 *electron-v27.3.1-linux-arm64-symbols.zip -9337cfe3f9256fecc806554c9fac3be780fd20c868efe621aaca2c1455c5ff64 *electron-v27.3.1-linux-arm64.zip -e6fdce1f521511b13ec4425d8a7e51077e83faa4bbfa18026598037c6688965c *electron-v27.3.1-linux-armv7l-debug.zip -7bb138d548e621b62ece75f43b26c5e287127b720a2fcf1b24fde75178000848 *electron-v27.3.1-linux-armv7l-symbols.zip -4956df2e23ae6bdebda8a1fbbee40828ca1170ce61b8d4504f1e30ed1102052a *electron-v27.3.1-linux-armv7l.zip -b8e7cd591db6ffad32f7dac90e05404a675d4f2a5e1e375dfce7b4a5c3b0064b *electron-v27.3.1-linux-x64-debug.zip -5c692dce572cf1b8ad57a0633280ee61096256b7291701a1c3e357f48479fb7b *electron-v27.3.1-linux-x64-symbols.zip -9daf5c5dc2050b9f37a5ec6d91d586ac156824bfe9a07ca53872c1b293280ca1 *electron-v27.3.1-linux-x64.zip -959529b81b9517df24baed089fb92fd1730b4eefaddcd37c864da6b05f63ecbe *electron-v27.3.1-mas-arm64-dsym-snapshot.zip -603c4955dddd4f8211c6fab3e633242255c1bf408326bf66567bfa00bed21493 *electron-v27.3.1-mas-arm64-dsym.zip -8181e85363bcc89863c0de76b29d47b68beb4f53d79583875a74178f5e12ef1d *electron-v27.3.1-mas-arm64-symbols.zip -c577088087c137e60884d857335a6720547c14eac894884d9daa07f1662a43a5 *electron-v27.3.1-mas-arm64.zip -6ce1f58f7bfa0b1aafd1aea2bfbad83df3a86a7b922b5d57f00689c7dd573f42 *electron-v27.3.1-mas-x64-dsym-snapshot.zip -1efe461ecca71b7efd96a34187b4810969c2d2bece7d02db2ad2821acd44e130 *electron-v27.3.1-mas-x64-dsym.zip -eabfbd4ee150f60b7cf10c126343cc078a3df2b8e63938f5795766fa8d837c93 *electron-v27.3.1-mas-x64-symbols.zip -2e0f6a468d388ca47eba3ae8cd79f5f422d3a36813b46d09fc7a0ae93bcbb68c *electron-v27.3.1-mas-x64.zip -c086cee2cfe73567d35e3b373c39f562e9169dc1f234caa6d006a878ff43b2e4 *electron-v27.3.1-win32-arm64-pdb.zip -c064dd5aa63b1506cca2cc6cb2fda6478839c194f62b3b90782b3dc8246bd55c *electron-v27.3.1-win32-arm64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.1-win32-arm64-toolchain-profile.zip -09caae1d93087cebcefec8bfc01496213e8db95d4563151d46aec3f05f31ec30 *electron-v27.3.1-win32-arm64.zip -c1dbf42222a5bfd86850fec114436e24960275547cf529e4f5ff2729ce680801 *electron-v27.3.1-win32-ia32-pdb.zip -8d3db6570266cc571823e7b2498e694e428134ecf2a11211dbd8f1b1d9ddfe02 *electron-v27.3.1-win32-ia32-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.1-win32-ia32-toolchain-profile.zip -686daa2e3cd9751d411b781af88ff84b91b23ddcf4879bfa5f4417e2e45ca4eb *electron-v27.3.1-win32-ia32.zip -7c1ec060a7ca71c3018b18c66607aad3f648257bdc055f4672341abcf275f1d8 *electron-v27.3.1-win32-x64-pdb.zip -7ab64686ed233b2aef2443d72997a36a2eb67cbdc1321a419111d573292956bc *electron-v27.3.1-win32-x64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.1-win32-x64-toolchain-profile.zip -20d54521540d1d9fa441d1e977555dbec4958bcd76de57f23b4534077361a865 *electron-v27.3.1-win32-x64.zip -482a6452d0d2ccda8afbfc19326fe0d59118491c216938a192bfa1ad7d168493 *electron.d.ts -ac54ec270dacdd7ca2f806b3c75047875535e1722e251ec81b4f3ab9c574470b *ffmpeg-v27.3.1-darwin-arm64.zip -091699a1fe0506f54b2b990c3e85058dae0fffffb818d22b41ffc2496e155fa4 *ffmpeg-v27.3.1-darwin-x64.zip -be517ba93c5b5f36d2e55af148213f4c2fc43177563825601bee6f88dd4f7b05 *ffmpeg-v27.3.1-linux-arm64.zip -926d0da25ffcea3d05a6cbcae15e5d7729d93bc43394ae4439747669d2210e1d *ffmpeg-v27.3.1-linux-armv7l.zip -6f9c0ef52af14828ad547a80b17f8c63cac51a18b8d5769a2f33e4fa6cccfc7e *ffmpeg-v27.3.1-linux-x64.zip -e26c3c7097642012acf1178367c6815cf90ac588864e99206d89230409f67c01 *ffmpeg-v27.3.1-mas-arm64.zip -8dc1775e034c1a2c25a150690c384d3479d26f020a0ed533e1691a01a62ab0a5 *ffmpeg-v27.3.1-mas-x64.zip -94832b30166038225703810fec94b2f854fab7ca3fc7f5531219d2de5b9fa4e2 *ffmpeg-v27.3.1-win32-arm64.zip -7e5d9c0b4db4e1370219a74c4484f9d15d18c0c54501003280eb61f8dc3b84b2 *ffmpeg-v27.3.1-win32-ia32.zip -f45d1f9cf5e5e7bff93406d70b2bee58ae5b3836f1950fb47abb6520d0547aac *ffmpeg-v27.3.1-win32-x64.zip -ea4a6308be24bfc818526f1830f084961b5bd9277bbac16e5753fa4d60aad01e *hunspell_dictionaries.zip -9c05221181a2ae7d6f437ec356878ab44b71a958995f4f104667fef20a8076ef *libcxx-objects-v27.3.1-linux-arm64.zip -bef8d7930d31993196f7b0916eece6fac1d84fe9ed2b0a63fb484778949c1ee3 *libcxx-objects-v27.3.1-linux-armv7l.zip -17b1e3d3d9e5abdcedc3c22ce879becdff316c2e2a89369c6f2657e9051da6a6 *libcxx-objects-v27.3.1-linux-x64.zip -46313e591349d7a3a8390d96560d7799afad1f3774d588488a6ee80756172f7d *libcxx_headers.zip -44e6410776454067a546bf5d2525dd33baacd919481dc43708f327009f8b52bd *libcxxabi_headers.zip -870072bdd1956b404265ecfebcbe1a4fbaa2219fd61dd881c817f4f4f8130684 *mksnapshot-v27.3.1-darwin-arm64.zip -d76067c1f28fa4baebed723cc9f6374bd31d4baf0d6e7e19c36bfff6d214dd91 *mksnapshot-v27.3.1-darwin-x64.zip -e403e621c2af0a468301297c11790b370a77a0ba6f7f3fa4af731f005b6ffb96 *mksnapshot-v27.3.1-linux-arm64-x64.zip -6f7ef9ce138ff5c6d4c7df449ac717613e3001756b765f0e4e7662af1387c754 *mksnapshot-v27.3.1-linux-armv7l-x64.zip -a794b49b20677cd68b8616229180687adec3f46c425cc4c805c4cdb9c4b6ec72 *mksnapshot-v27.3.1-linux-x64.zip -6a61292fd8e3fb86c36c364ea32be2e78b8947e9abf22a8a7749bad40e75a5f5 *mksnapshot-v27.3.1-mas-arm64.zip -d3b1ae83b6244a4ce83e6eab16f8cd121c187ee21a5e37cac4253cfd4e53f6b4 *mksnapshot-v27.3.1-mas-x64.zip -6916414f51fa30cce966e49ea252a73facf24ebac86f068daa2f37930c48d58b *mksnapshot-v27.3.1-win32-arm64-x64.zip -ef37d3c0d9ef36038adef1b81ffbf302c3b819456ffea705c05c3def09710723 *mksnapshot-v27.3.1-win32-ia32.zip -95a222425bb72a8b1a42f37161eb90ff6869268f539387b47b54904df27f2f68 *mksnapshot-v27.3.1-win32-x64.zip +032e54843700736bf3566518ff88717b2dc70be41bdc43840993fcb4cd9c82e8 *chromedriver-v27.3.2-darwin-arm64.zip +7d693267bacc510b724b97db23e21e22983e9f500605a132ab519303ec2e4d94 *chromedriver-v27.3.2-darwin-x64.zip +5f3f417986667e4c82c492b30c14892b0fef3a6dcf07860e74f7d7ba29f0ca41 *chromedriver-v27.3.2-linux-arm64.zip +84364d9c1fc53ce6f29e41d08d12351a2a4a208646acf02551c6f9aa6029c163 *chromedriver-v27.3.2-linux-armv7l.zip +7d3965a5ca3217e16739153d2817fc292e7cb16f55034fde76f26bdc916e60d1 *chromedriver-v27.3.2-linux-x64.zip +068adc1ea9e1d21dcfef1468b2b789714c93465c1874dbd3bf2872a695a1279f *chromedriver-v27.3.2-mas-arm64.zip +0d4d4bb8971260cbc0058cab2a7972e556b83a19d6ea062ea226e7a8555bc369 *chromedriver-v27.3.2-mas-x64.zip +83ffc61b6b524ee0caa0e5cd02dcd00adcd166ba1e03e7fc50206a299a6fca11 *chromedriver-v27.3.2-win32-arm64.zip +df4e9f20681b3e7b65c41dd1df3aa8cb9bc0a061a24ddcffbe44a9191aa01e0c *chromedriver-v27.3.2-win32-ia32.zip +1ef67b7c06061e691176df5e3463f4d5f5f258946dac24ae62e3cc250b8b95d1 *chromedriver-v27.3.2-win32-x64.zip +f3c52d205572da71a23f436b4708dc89c721a74f0e0c5c51093e3e331b1dff67 *electron-api.json +1489dca88c89f6fef05bdc2c08b9623bb46eb8d0f43020985776daef08642061 *electron-v27.3.2-darwin-arm64-dsym-snapshot.zip +7ee895e81d695c1ed65378ff4514d4fc9c4015a1c3c67691765f92c08c8e0855 *electron-v27.3.2-darwin-arm64-dsym.zip +cbc1c9973b2a895aa2ebecdbd92b3fe8964590b12141a658a6d03ed97339fae6 *electron-v27.3.2-darwin-arm64-symbols.zip +0d4efeff14ac16744eef3d461b95fb59abd2c3affbf638af169698135db73e1f *electron-v27.3.2-darwin-arm64.zip +a77b52509213e67ae1e24172256479831ecbff55d1f49dc0e8bfd4818a5f393e *electron-v27.3.2-darwin-x64-dsym-snapshot.zip +9006386321c50aa7e0e02cd9bd9daef4b8c3ec0e9735912524802f31d02399ef *electron-v27.3.2-darwin-x64-dsym.zip +14fa8e76e519e1fb9e166e134d03f3df1ae1951c14dfd76db8a033a9627c0f13 *electron-v27.3.2-darwin-x64-symbols.zip +5105acce7d832a606fd11b0551d1ef00e0c49fc8b4cff4b53712c9efdddc27a2 *electron-v27.3.2-darwin-x64.zip +3bc20fb4f1d5effb2d882e7b587a337f910026aa50c22e7bc92522daa13f389c *electron-v27.3.2-linux-arm64-debug.zip +0d5d97a93938fa62d2659e2053dcc8d1cabc967878992b248bfec4dcc7763b8c *electron-v27.3.2-linux-arm64-symbols.zip +db9320d9ec6309145347fbba369ab7634139e80f15fff452be9b0171b2bd1823 *electron-v27.3.2-linux-arm64.zip +3bc20fb4f1d5effb2d882e7b587a337f910026aa50c22e7bc92522daa13f389c *electron-v27.3.2-linux-armv7l-debug.zip +6b9117419568c72542ab671301df05d46a662deab0bc37787b3dc9a907e68f8c *electron-v27.3.2-linux-armv7l-symbols.zip +72fd10c666dd810e9f961c2727ae44f5f6cf964cedb6860c1f09da7152e29a29 *electron-v27.3.2-linux-armv7l.zip +354209d48be01785d286eb80d691cdff476479db2d8cdbc6b6bd30652f5539fa *electron-v27.3.2-linux-x64-debug.zip +5f45a4b42f3b35ecea8a623338a6add35bb5220cb0ed02e3489b6d77fbe102ef *electron-v27.3.2-linux-x64-symbols.zip +2261aa0a5a293cf963487c050e9f6d05124da1f946f99bd1115f616f8730f286 *electron-v27.3.2-linux-x64.zip +54a4ad6e75e5a0001c32de18dbfec17f5edc17693663078076456ded525d65da *electron-v27.3.2-mas-arm64-dsym-snapshot.zip +5a5c85833ad7a6ef04337ed8acd131e5cf383a49638789dfd84e07c855b33ccc *electron-v27.3.2-mas-arm64-dsym.zip +16da4cc5a19a953c839093698f0532854e4d3fc839496a5c2b2405fd63c707f4 *electron-v27.3.2-mas-arm64-symbols.zip +8455b79826fe195124bee3f0661e08c14ca50858d376b09d03c79aace0082ea5 *electron-v27.3.2-mas-arm64.zip +00731db08a1bb66e51af0d26d03f8510221f4f6f92282c7baa0cd1c130e0cce6 *electron-v27.3.2-mas-x64-dsym-snapshot.zip +446f98f2d957e4ae487a6307b18be7b11edff35187b71143def4d00325943e42 *electron-v27.3.2-mas-x64-dsym.zip +d3455394eff02d463fdf89aabeee9c05d4980207ecf75a5eac27b35fb2aef874 *electron-v27.3.2-mas-x64-symbols.zip +dae434f52ff9b1055703aaf74b17ff3d93351646e9271a3b10e14b49969d4218 *electron-v27.3.2-mas-x64.zip +a598fcd1e20dcef9e7dccf7676ba276cd95ec7ff6799834fd090800fb15a6507 *electron-v27.3.2-win32-arm64-pdb.zip +7ba64940321ddff307910cc49077aa36c430d4b0797097975cb797cc0ab2b39d *electron-v27.3.2-win32-arm64-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.2-win32-arm64-toolchain-profile.zip +692f264e9d13478ad9a42d06e2eead0ed67ab1b52fc3693ba536a6a441fd9010 *electron-v27.3.2-win32-arm64.zip +a74eee739ff26681f6696f7959ab8e8603bb57f8fcb7ddab305220f71d2c69f3 *electron-v27.3.2-win32-ia32-pdb.zip +c10b90b51d0292129dc5bba5e012c7e07c78d6c70b0980c36676d6abf8eef12f *electron-v27.3.2-win32-ia32-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.2-win32-ia32-toolchain-profile.zip +63e477332608d31afb965a4054b5d78165df1da65d57477ac1dbddf8ede0f1b9 *electron-v27.3.2-win32-ia32.zip +3d795150c0afd48f585c7d32685f726618825262cb76f4014567be9e3de88732 *electron-v27.3.2-win32-x64-pdb.zip +d5463f797d1eb9a57ac9b20caa6419c15c5f3b378a3cb2b45d338040d7124411 *electron-v27.3.2-win32-x64-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.2-win32-x64-toolchain-profile.zip +e701b3023d4929f86736ae8a7ff6409134455da99b3fbdcea8d58555acbd9d46 *electron-v27.3.2-win32-x64.zip +3383cd44951cf763ddd36ba3ec91c930c9e8d33a175adfcb6dce4f667d60bc34 *electron.d.ts +db6df7bd0264c859009247276b35eda4ef20f22a7b2f41c2335a4609f5653cb7 *ffmpeg-v27.3.2-darwin-arm64.zip +3c0bb9740d6b95ff476ff7a5d4b442ccef7ec98e0fa3f2bad8d0e6a51329b511 *ffmpeg-v27.3.2-darwin-x64.zip +6fea38ce22bae4d23fb6b143e946c1c3d214ccecabf841883a2cb1b621161113 *ffmpeg-v27.3.2-linux-arm64.zip +926d0da25ffcea3d05a6cbcae15e5d7729d93bc43394ae4439747669d2210e1d *ffmpeg-v27.3.2-linux-armv7l.zip +6f9c0ef52af14828ad547a80b17f8c63cac51a18b8d5769a2f33e4fa6cccfc7e *ffmpeg-v27.3.2-linux-x64.zip +c75f62fc08d6c5e49fd1a805ca00b4191d5f04d26469448e3d4af48fb409b3a7 *ffmpeg-v27.3.2-mas-arm64.zip +acb8154c113ecbafb91aef5a294dc2c2bce61cbc4a261696681b723d292a5cb3 *ffmpeg-v27.3.2-mas-x64.zip +1665bdac6aa7264a6eb5f00a93110b718c7231010389bdda5ec7bf8275aab953 *ffmpeg-v27.3.2-win32-arm64.zip +3972d89c60a77f7955d7e8520adeae0c9f449a5ae3730cacf202f2baf2bae079 *ffmpeg-v27.3.2-win32-ia32.zip +37d2da723c2f2148c1c8f2ccf354b6dd933148c49dfc7f32aa57ecbd7063ffaf *ffmpeg-v27.3.2-win32-x64.zip +8828099c931c56981865fb9ff6fca85012dd05702a125858d6377c793760db1f *hunspell_dictionaries.zip +9e2126db472f66d3dde2d77eec63364e7071358f5591fc3c4dfb53d191ab5da8 *libcxx-objects-v27.3.2-linux-arm64.zip +530c3a92c4cd721e49e62d4fd97090c4e4d1b00c3ba821fd4f42c5f9186c98e7 *libcxx-objects-v27.3.2-linux-armv7l.zip +5b67f5e2a268bd1980a13b794013d4ac96e7ee40c4878d96f7c27da2c3f94923 *libcxx-objects-v27.3.2-linux-x64.zip +0d3086ccf9a050a88251a4382349f436f99d3d2b1842d87d854ea80667f6c423 *libcxx_headers.zip +ac02262548cb396051c683ad35fcbbed61b9a6f935c2a2bd3d568b209ce9e5a4 *libcxxabi_headers.zip +ba3b63a297b8be954a0ca1b8b83c3c856abaae85d17e6337d2b34e1c14f0d4b2 *mksnapshot-v27.3.2-darwin-arm64.zip +cb09a9e9e1fee567bf9e697eef30d143bd30627c0b189d0271cf84a72a03042e *mksnapshot-v27.3.2-darwin-x64.zip +014c5b621bbbc497bdc40dac47fac20143013fa1e905c0570b5cf92a51826354 *mksnapshot-v27.3.2-linux-arm64-x64.zip +f71407b9cc5c727de243a9e9e7fb56d2a0880e02187fa79982478853432ed5b7 *mksnapshot-v27.3.2-linux-armv7l-x64.zip +e5caa81f467d071756a4209f05f360055be7625a71a0dd9b2a8c95296c8415b5 *mksnapshot-v27.3.2-linux-x64.zip +fc33ec02a17fb58d48625c7b68517705dcd95b5a12e731d0072711a084dc65bd *mksnapshot-v27.3.2-mas-arm64.zip +961af5fc0ef80243d0e94036fb31b90f7e8458e392dd0e49613c11be89cb723f *mksnapshot-v27.3.2-mas-x64.zip +844a70ccef160921e0baeaefe9038d564db9a9476d98fab1eebb5c122ba9c22c *mksnapshot-v27.3.2-win32-arm64-x64.zip +3e723ca42794d43e16656599fbfec73880b964264f5057e38b865688c83ac905 *mksnapshot-v27.3.2-win32-ia32.zip +3e6fc056fa8cfb9940b26c4f066a9c9343056f053bcc53e1eada464bf5bc0d42 *mksnapshot-v27.3.2-win32-x64.zip diff --git a/cgmanifest.json b/cgmanifest.json index c546a3e27e5..2673931fdb6 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -528,12 +528,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "f6af8959c36eca437f38076c46ab13e910cbfbdb" + "commitHash": "077c4addd5faa3ad1d1c9e598284368394a97fdd" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "27.3.1" + "version": "27.3.2" }, { "component": { diff --git a/package.json b/package.json index 6dc97a406e5..d27c2f3b8fc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "848d09154925ef27343af358097ed5435450160e", + "distro": "d944d3f8f3d96c19da85e25c45e65317f8d795ac", "author": { "name": "Microsoft Corporation" }, @@ -149,7 +149,7 @@ "cssnano": "^6.0.3", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "27.3.1", + "electron": "27.3.2", "eslint": "8.36.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^46.5.0", diff --git a/yarn.lock b/yarn.lock index 49eb5f91f6e..fa86aaa2893 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3556,10 +3556,10 @@ electron-to-chromium@^1.4.648: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.648.tgz#c7b46c9010752c37bb4322739d6d2dd82354fbe4" integrity sha512-EmFMarXeqJp9cUKu/QEciEApn0S/xRcpZWuAm32U7NgoZCimjsilKXHRO9saeEW55eHZagIDg6XTUOv32w9pjg== -electron@27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/electron/-/electron-27.3.1.tgz#b64ae70410b657c911d8c7665605c02695722fd3" - integrity sha512-hDAeaTJvXHu3vCmR6l6/muPGaKRlEp6v471yyqOju9xhLKUaGOAdMN46F0V+SCABZq2nWr9DTwqHsJ0b4hUZLQ== +electron@27.3.2: + version "27.3.2" + resolved "https://registry.yarnpkg.com/electron/-/electron-27.3.2.tgz#ff2caa6d09e013ec32bae3185c790d712cd02f54" + integrity sha512-FoLdHj2ON0jE8S0YntgNT4ABaHgK4oR4dqXixPQXnTK0JvXgrrrW5W7ls+c7oiFBGN/f9bm0Mabq8iKH+7wMYQ== dependencies: "@electron/get" "^2.0.0" "@types/node" "^18.11.18" From c7335f45ec29c41eaed5e98da5163040815c0fdb Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 12 Feb 2024 09:31:33 +0100 Subject: [PATCH 1117/1897] fixing failing tests --- .../browser/inlineCompletionsModel.ts | 49 ++----------------- .../browser/inlineCompletionsModel.test.ts | 2 - 2 files changed, 4 insertions(+), 47 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index e4d474d8238..87fa7080180 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -479,64 +479,23 @@ function getEndPositionsAfterApplying(edits: readonly SingleTextEdit[]): Positio export function getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { const primaryPosition = positions[0]; const secondaryPositions = positions.slice(1); - console.log('positions : ', JSON.stringify(positions)); - // replaced text is not calculated correctly - // only works if primary edit line number - // Need to take all the that is replaced in the primary edit range - const replacedTextAfterPrimaryCursor = textModel.getValueInRange(Range.fromPositions(primaryPosition, primaryEdit.range.getEndPosition())); - console.log('replacedTextAfterPrimaryCursor : ', JSON.stringify(replacedTextAfterPrimaryCursor)); - console.log('primaryEdit : ', JSON.stringify(primaryEdit)); - - // There is an error below too, the secondary edit text is the text after the cursor to the right of it, that needs to be added - // in the test case we would want to add ') {\n\treturn 0;\n}' because we already have fib( written. - // Before it worked because we would have the cursor at the end of function fib(, now the cursor is on the line below it - // Or at the very least, we should insert 'return 0;\n}' because this is to the right of the cursor at the primary cursor position - // So need to find the primary position within the edit, and find all the text to the right of it. The primary position will not necessarily be on the first - // line of the edit text. - // We suppose that the primaryEdit.range always touches the primaryPosition in some manner - - // could find the offset of primary position, the offset of the primary edit start and find thus the secondary edit text - // const _offsetPrimaryPosition = textModel.getOffsetAt(primaryPosition); - // const _offsetPrimaryEditStart = textModel.getOffsetAt(primaryEdit.range.getStartPosition()); - // console.log('_offsetPrimaryPosition : ', _offsetPrimaryPosition); - // console.log('_offsetPrimaryEditStart : ', _offsetPrimaryEditStart); - - // Find offset in a different way - // Split the lines of the text, place it in the context of the whole text - // Find the position in the text where the initial position would be, exactly as is, find the offset, and take the substring - - const newCol = primaryPosition.lineNumber - primaryEdit.range.startLineNumber; - const newLine = newCol === 0 ? primaryPosition.column - primaryEdit.range.startColumn : primaryPosition.column; - + const newLine = primaryPosition.lineNumber - primaryEdit.range.startLineNumber; + const newCol = newLine === 0 ? primaryPosition.column - primaryEdit.range.startColumn + 1 : primaryPosition.column; let text = ''; const _splitLines = splitLines(primaryEdit.text); for (let i = newLine; i < _splitLines.length; i++) { if (i === newLine) { - text += _splitLines[i].substring(newCol) + '\n'; + text += _splitLines[i].substring(newCol - 1) + (i === _splitLines.length - 1 ? '' : '\n'); } else { - text += _splitLines[i] + '\n'; + text += _splitLines[i] + (i === _splitLines.length - 1 ? '' : '\n'); } } - console.log('text : ', text); - const secondaryEditText = text; - // primaryEdit.text.substring(primaryPosition.column - primaryEdit.range.startColumn); - // console.log('secondaryEditText : ', JSON.stringify(secondaryEditText)); return secondaryPositions.map(pos => { - console.log('pos : ', JSON.stringify(pos)); - // Maybe taking the substring on the line content specifically is not enough, so we need to actually take it until the range end, because that is the text we would replace - // the range end is not necessarily on the end of that line either - // const textAfterSecondaryCursor = textModel - // .getLineContent(pos.lineNumber) - // .substring(pos.column - 1); - const textAfterSecondaryCursor = textModel.getValueInRange(Range.fromPositions(pos, primaryEdit.range.getEndPosition())); - console.log('textAfterSecondaryCursor : ', textAfterSecondaryCursor); const l = commonPrefixLength(replacedTextAfterPrimaryCursor, textAfterSecondaryCursor); - console.log('l : ', l); const range = Range.fromPositions(pos, pos.delta(0, l)); - console.log('range : ', JSON.stringify(range)); return new SingleTextEdit(range, secondaryEditText); }); } diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts index c6bc78de063..67c56bb3945 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts @@ -57,7 +57,6 @@ suite('inlineCompletionModel', () => { '}' ].join('\n') )]); - console.log('secondaryEdits : ', JSON.stringify(secondaryEdits)); textModel.dispose(); }); @@ -88,7 +87,6 @@ suite('inlineCompletionModel', () => { '}' ].join('\n') )]); - console.log('secondaryEdits : ', JSON.stringify(secondaryEdits)); textModel.dispose(); }); }); From 420099cb092f0994ecb1e7af4c679dc0ffe57fa3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Feb 2024 10:55:48 +0100 Subject: [PATCH 1118/1897] code :lipstick: (#204967) --- .../workbench/browser/parts/compositeBarActions.ts | 10 +++++----- src/vs/workbench/browser/parts/globalCompositeBar.ts | 4 ++-- src/vs/workbench/browser/parts/panel/panelActions.ts | 2 +- .../browser/parts/titlebar/menubarControl.ts | 8 ++++---- .../browser/parts/views/viewPaneContainer.ts | 2 +- .../workbench/common/editor/sideBySideEditorInput.ts | 4 ++-- .../contrib/timeline/browser/timelinePane.ts | 12 ++++++------ .../parts/titlebar/menubarControl.ts | 2 +- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index e1945a1cbc2..fb06c28c67d 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -144,7 +144,7 @@ export interface ICompositeBarActionViewItemOptions extends IActionViewItemOptio readonly compact?: boolean; } -export class CompoisteBarActionViewItem extends BaseActionViewItem { +export class CompositeBarActionViewItem extends BaseActionViewItem { private static hoverLeaveTime = 0; @@ -394,7 +394,7 @@ export class CompoisteBarActionViewItem extends BaseActionViewItem { this.hoverDisposables.add(addDisposableListener(this.container, EventType.MOUSE_OVER, () => { if (!this.showHoverScheduler.isScheduled()) { - if (Date.now() - CompoisteBarActionViewItem.hoverLeaveTime < 200) { + if (Date.now() - CompositeBarActionViewItem.hoverLeaveTime < 200) { this.showHover(true); } else { this.showHoverScheduler.schedule(this.configurationService.getValue('workbench.hover.delay')); @@ -404,7 +404,7 @@ export class CompoisteBarActionViewItem extends BaseActionViewItem { this.hoverDisposables.add(addDisposableListener(this.container, EventType.MOUSE_LEAVE, e => { if (e.target === this.container) { - CompoisteBarActionViewItem.hoverLeaveTime = Date.now(); + CompositeBarActionViewItem.hoverLeaveTime = Date.now(); this.hoverService.hideHover(); this.showHoverScheduler.cancel(); } @@ -467,7 +467,7 @@ export class CompositeOverflowActivityAction extends CompositeBarAction { } } -export class CompositeOverflowActivityActionViewItem extends CompoisteBarActionViewItem { +export class CompositeOverflowActivityActionViewItem extends CompositeBarActionViewItem { constructor( action: CompositeBarAction, @@ -529,7 +529,7 @@ class ManageExtensionAction extends Action { } } -export class CompositeActionViewItem extends CompoisteBarActionViewItem { +export class CompositeActionViewItem extends CompositeBarActionViewItem { private static manageExtensionAction: ManageExtensionAction; diff --git a/src/vs/workbench/browser/parts/globalCompositeBar.ts b/src/vs/workbench/browser/parts/globalCompositeBar.ts index 50a63bb9e00..263ef8d9616 100644 --- a/src/vs/workbench/browser/parts/globalCompositeBar.ts +++ b/src/vs/workbench/browser/parts/globalCompositeBar.ts @@ -12,7 +12,7 @@ import { DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { CompoisteBarActionViewItem, CompositeBarAction, IActivityHoverOptions, ICompositeBarActionViewItemOptions, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions'; +import { CompositeBarActionViewItem, CompositeBarAction, IActivityHoverOptions, ICompositeBarActionViewItemOptions, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; @@ -153,7 +153,7 @@ export class GlobalCompositeBar extends Disposable { } } -abstract class AbstractGlobalActivityActionViewItem extends CompoisteBarActionViewItem { +abstract class AbstractGlobalActivityActionViewItem extends CompositeBarActionViewItem { constructor( private readonly menuId: MenuId, diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 23f5ce98a1b..416cc1d4921 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -199,7 +199,7 @@ AlignPanelActionConfigs.forEach(alignPanelAction => { constructor() { super({ id, - title: title, + title, category: Categories.View, toggled: when.negate(), f1: true diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index eeec3dbcc5e..a3d09742994 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -665,7 +665,7 @@ export class CustomMenubarControl extends MenubarControl { if (!this.focusInsideMenubar) { const actions: IAction[] = []; updateActions(this.toActionsArray(menu), actions, title); - this.menubar?.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); + this.menubar?.updateMenu({ actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); } })); @@ -675,7 +675,7 @@ export class CustomMenubarControl extends MenubarControl { if (!this.focusInsideMenubar) { const actions: IAction[] = []; updateActions(this.toActionsArray(menu), actions, title); - this.menubar?.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); + this.menubar?.updateMenu({ actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); } })); } @@ -688,9 +688,9 @@ export class CustomMenubarControl extends MenubarControl { if (this.menubar) { if (!firstTime) { - this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); + this.menubar.updateMenu({ actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); } else { - this.menubar.push({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); + this.menubar.push({ actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); } } } diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 3021fe51eee..1c7201e246b 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -658,7 +658,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { addPanes(panes: { pane: ViewPane; size: number; index?: number; disposable: IDisposable }[]): void { const wasMerged = this.isViewMergedWithContainer(); - for (const { pane: pane, size, index, disposable } of panes) { + for (const { pane, size, index, disposable } of panes) { this.addPane(pane, size, disposable, index); } diff --git a/src/vs/workbench/common/editor/sideBySideEditorInput.ts b/src/vs/workbench/common/editor/sideBySideEditorInput.ts index 88585cb807c..7228b47ae71 100644 --- a/src/vs/workbench/common/editor/sideBySideEditorInput.ts +++ b/src/vs/workbench/common/editor/sideBySideEditorInput.ts @@ -362,8 +362,8 @@ export abstract class AbstractSideBySideEditorInputSerializer implements IEditor const serializedEditorInput: ISerializedSideBySideEditorInput = { name: input.getPreferredName(), description: input.getPreferredDescription(), - primarySerialized: primarySerialized, - secondarySerialized: secondarySerialized, + primarySerialized, + secondarySerialized, primaryTypeId: input.primary.typeId, secondaryTypeId: input.secondary.typeId }; diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index d20b31e0641..d919f15af47 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -747,7 +747,7 @@ export class TimelinePane extends ViewPane { } const iterator = timeline.items[Symbol.iterator](); - sources.push({ timeline: timeline, iterator: iterator, nextItem: iterator.next() }); + sources.push({ timeline, iterator, nextItem: iterator.next() }); } this._visibleItemCount = hasAnyItems ? 1 : 0; @@ -1046,7 +1046,7 @@ export class TimelinePane extends ViewPane { this.tree.domFocus(); } }, - getActionsContext: (): TimelineActionContext => ({ uri: this.uri, item: item }), + getActionsContext: (): TimelineActionContext => ({ uri: this.uri, item }), actionRunner: new TimelineActionRunner() }); } @@ -1068,13 +1068,13 @@ class TimelineElementTemplate implements IDisposable { container.classList.add('custom-view-tree-node-item'); this.icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon')); - this.iconLabel = new IconLabel(container, { supportHighlights: true, supportIcons: true, hoverDelegate: hoverDelegate }); + this.iconLabel = new IconLabel(container, { supportHighlights: true, supportIcons: true, hoverDelegate }); const timestampContainer = DOM.append(this.iconLabel.element, DOM.$('.timeline-timestamp-container')); this.timestamp = DOM.append(timestampContainer, DOM.$('span.timeline-timestamp')); const actionsContainer = DOM.append(this.iconLabel.element, DOM.$('.actions')); - this.actionBar = new ActionBar(actionsContainer, { actionViewItemProvider: actionViewItemProvider }); + this.actionBar = new ActionBar(actionsContainer, { actionViewItemProvider }); } dispose() { @@ -1109,7 +1109,7 @@ class TimelineActionRunner extends ActionRunner { $mid: MarshalledId.TimelineActionContext, handle: item.handle, source: item.source, - uri: uri + uri }, uri, item.source, @@ -1212,7 +1212,7 @@ class TimelineTreeRenderer implements ITreeRenderer Date: Mon, 12 Feb 2024 11:34:21 +0100 Subject: [PATCH 1119/1897] adding function splitLinesIncludeSeparators --- src/vs/base/common/strings.ts | 8 ++++++ .../browser/inlineCompletionsModel.ts | 28 +++++++++---------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 230e6eb7bdd..a68f48e76f9 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -254,6 +254,14 @@ export function splitLines(str: string): string[] { return str.split(/\r\n|\r|\n/); } +export function splitLinesIncludeSeparators(str: string): { lines: string[]; separators: string[] } { + const lines: string[] = []; + const separators: string[] = []; + const splitLinesAndSeparators = str.split(/(\r\n|\r|\n)/); + splitLinesAndSeparators.forEach((el, idx) => (idx % 2 === 0 ? lines : separators).push(el)); + return { lines, separators }; +} + /** * Returns first index of the string that is not whitespace. * If string is empty or contains only whitespaces, returns -1 diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 87fa7080180..15fd1e0e856 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -7,7 +7,7 @@ import { mapFindFirst } from 'vs/base/common/arraysFind'; import { BugIndicatingError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; import { IObservable, IReader, ITransaction, autorun, derived, derivedHandleChanges, derivedOpts, recomputeInitiallyAndOnChange, observableSignal, observableValue, subtransaction, transaction } from 'vs/base/common/observable'; -import { commonPrefixLength, splitLines } from 'vs/base/common/strings'; +import { commonPrefixLength, splitLinesIncludeSeparators } from 'vs/base/common/strings'; import { isDefined } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditOperation } from 'vs/editor/common/core/editOperation'; @@ -479,21 +479,21 @@ function getEndPositionsAfterApplying(edits: readonly SingleTextEdit[]): Positio export function getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { const primaryPosition = positions[0]; const secondaryPositions = positions.slice(1); - const replacedTextAfterPrimaryCursor = textModel.getValueInRange(Range.fromPositions(primaryPosition, primaryEdit.range.getEndPosition())); - const newLine = primaryPosition.lineNumber - primaryEdit.range.startLineNumber; - const newCol = newLine === 0 ? primaryPosition.column - primaryEdit.range.startColumn + 1 : primaryPosition.column; - let text = ''; - const _splitLines = splitLines(primaryEdit.text); - for (let i = newLine; i < _splitLines.length; i++) { - if (i === newLine) { - text += _splitLines[i].substring(newCol - 1) + (i === _splitLines.length - 1 ? '' : '\n'); - } else { - text += _splitLines[i] + (i === _splitLines.length - 1 ? '' : '\n'); - } + const primaryEditEndPosition = primaryEdit.range.getEndPosition(); + const replacedTextAfterPrimaryCursor = textModel.getValueInRange( + Range.fromPositions(primaryPosition, primaryEditEndPosition) + ); + const lineNumberWithinEditText = primaryPosition.lineNumber - primaryEdit.range.startLineNumber; + const columnWithinEditText = lineNumberWithinEditText === 0 ? primaryPosition.column - primaryEdit.range.startColumn : primaryPosition.column - 1; + let secondaryEditText = ''; + const { lines, separators } = splitLinesIncludeSeparators(primaryEdit.text); + for (let i = lineNumberWithinEditText; i < lines.length; i++) { + secondaryEditText += lines[i].substring(i === lineNumberWithinEditText ? columnWithinEditText : 0) + (separators[i] ?? ''); } - const secondaryEditText = text; return secondaryPositions.map(pos => { - const textAfterSecondaryCursor = textModel.getValueInRange(Range.fromPositions(pos, primaryEdit.range.getEndPosition())); + const textAfterSecondaryCursor = textModel.getValueInRange( + Range.fromPositions(pos, primaryEditEndPosition) + ); const l = commonPrefixLength(replacedTextAfterPrimaryCursor, textAfterSecondaryCursor); const range = Range.fromPositions(pos, pos.delta(0, l)); return new SingleTextEdit(range, secondaryEditText); From 3f52e5efd6eee98a355a221044f92b9620d24238 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Feb 2024 12:31:57 +0100 Subject: [PATCH 1120/1897] Multi diff editor: implement `focus` and `hasFocus` (#204076) (#204970) --- .../multiDiffEditorWidget/multiDiffEditorWidget.ts | 3 ++- .../contrib/multiDiffEditor/browser/multiDiffEditor.ts | 10 ++++++++++ .../browser/scmMultiDiffSourceResolver.ts | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index 938edfad4b3..ef78607a7d5 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -18,6 +18,7 @@ import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; export class MultiDiffEditorWidget extends Disposable { private readonly _dimension = observableValue(this, undefined); @@ -58,7 +59,7 @@ export class MultiDiffEditorWidget extends Disposable { private readonly _activeControl = derived(this, (reader) => this._widgetImpl.read(reader).activeControl.read(reader)); - public getActiveControl(): any | undefined { + public getActiveControl(): DiffEditorWidget | undefined { return this._activeControl.get(); } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 26366866759..a879f7b85c4 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -96,6 +96,16 @@ export class MultiDiffEditor extends AbstractEditorWithViewState Date: Mon, 12 Feb 2024 12:33:15 +0100 Subject: [PATCH 1121/1897] api - refine `ChatAgentRequest` to have the prompt as-is and include metadata about variables (#204980) --- .../api/common/extHostTypeConverters.ts | 13 +++++++++ .../contrib/chat/common/chatAgents.ts | 4 ++- .../contrib/chat/common/chatModel.ts | 7 ++++- .../contrib/chat/common/chatServiceImpl.ts | 28 +++++++++++++++++-- .../vscode.proposed.chatAgents2.d.ts | 23 +++++++++++++-- 5 files changed, 68 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index e1e05919528..0b639e933b5 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -52,6 +52,7 @@ import type * as vscode from 'vscode'; import * as types from './extHostTypes'; import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { basename } from 'vs/base/common/resources'; +import { IOffsetRange } from 'vs/editor/common/core/offsetRange'; export namespace Command { @@ -2609,9 +2610,21 @@ export namespace ChatAgentRequest { export function to(request: IChatAgentRequest): vscode.ChatAgentRequest { return { prompt: request.message, + prompt2: request.variables2.message, variables: ChatVariable.objectTo(request.variables), command: request.command, agentId: request.agentId, + variables2: request.variables2.variables.map(ChatAgentResolvedVariable.to) + }; + } +} + +export namespace ChatAgentResolvedVariable { + export function to(request: { name: string; range: IOffsetRange; values: IChatRequestVariableValue[] }): vscode.ChatAgentResolvedVariable { + return { + name: request.name, + range: [request.range.start, request.range.endExclusive], + values: request.values.map(ChatVariable.to) }; } } diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 2da3567a20e..e5d20e0db45 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -12,7 +12,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { ProviderResult } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatProgressResponseContent, IChatRequestVariableData2 } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -79,6 +79,7 @@ export interface IChatAgentMetadata { supportIssueReporting?: boolean; } + export interface IChatAgentRequest { sessionId: string; requestId: string; @@ -86,6 +87,7 @@ export interface IChatAgentRequest { command?: string; message: string; variables: Record; + variables2: IChatRequestVariableData2; } export interface IChatAgentResult { diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 68b5b843ee3..81bcad7a26c 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -12,7 +12,7 @@ import { revive } from 'vs/base/common/marshalling'; import { basename } from 'vs/base/common/resources'; import { URI, UriComponents, UriDto } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import { OffsetRange } from 'vs/editor/common/core/offsetRange'; +import { IOffsetRange, OffsetRange } from 'vs/editor/common/core/offsetRange'; import { ILogService } from 'vs/platform/log/common/log'; import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; @@ -28,6 +28,11 @@ export interface IChatRequestVariableData { variables: Record; } +export interface IChatRequestVariableData2 { + message: string; + variables: { name: string; range: IOffsetRange; values: IChatRequestVariableValue[] }[]; +} + export interface IChatRequestModel { readonly id: string; readonly username: string; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 4e1222c1a0c..8c363983bb1 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -22,8 +22,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, IChatRequestVariableData2, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestVariablePart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; @@ -545,7 +545,8 @@ export class ChatService extends Disposable implements IChatService { agentId: request.response.agent?.id ?? '', message: request.variableData.message, variables: request.variableData.variables, - command: request.response.slashCommand?.name + command: request.response.slashCommand?.name, + variables2: asVariablesData2(request.message, request.variableData) }; history.push({ request: historyRequest, response: request.response.response.value, result: request.response.result ?? {} }); } @@ -562,6 +563,7 @@ export class ChatService extends Disposable implements IChatService { message: variableData.message, variables: variableData.variables, command: agentSlashCommandPart?.command.name, + variables2: asVariablesData2(parsedRequest, variableData) }; const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); @@ -763,3 +765,23 @@ export class ChatService extends Disposable implements IChatService { this.trace('transferChatSession', `Transferred session ${model.sessionId} to workspace ${toWorkspace.toString()}`); } } + +function asVariablesData2(parsedRequest: IParsedChatRequest, variableData: IChatRequestVariableData): IChatRequestVariableData2 { + + const res: IChatRequestVariableData2 = { + message: getPromptText(parsedRequest.parts), + variables: [] + }; + + for (const part of parsedRequest.parts) { + if (part instanceof ChatRequestVariablePart) { + const values = variableData.variables[part.variableName]; + res.variables.push({ name: part.variableName, range: part.range, values }); + } + } + + // "reverse", high index first so that replacement is simple + res.variables.sort((a, b) => b.range.start - a.range.start); + + return res; +} diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 5c12b9d4fd5..403265ad3f2 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -276,6 +276,12 @@ declare module 'vscode' { dispose(): void; } + export interface ChatAgentResolvedVariable { + name: string; + range: [start: number, end: number]; + values: ChatVariableValue[]; + } + export interface ChatAgentRequest { /** @@ -286,6 +292,16 @@ declare module 'vscode' { */ prompt: string; + /** + * The prompt as entered by the user. + * + * Information about variables used in this request are is stored in {@link ChatAgentRequest.variables2}. + * + * *Note* that the {@link ChatAgent2.name name} of the agent and the {@link ChatAgentCommand.name command} + * are not part of the prompt. + */ + prompt2: string; + /** * The ID of the chat agent to which this request was directed. */ @@ -296,10 +312,13 @@ declare module 'vscode' { */ command?: string; + /** @deprecated */ variables: Record; - // TODO@API argumented prompt, reverse order! - // variables2: { start:number, length:number, values: ChatVariableValue[]}[] + /** + * + */ + variables2: ChatAgentResolvedVariable[]; } export interface ChatAgentResponseStream { From e37a23a9e4b6dddbb289c43e18134b674203f6d0 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 12 Feb 2024 12:56:23 +0100 Subject: [PATCH 1122/1897] defining now the subtract positions method and using it --- .../browser/inlineCompletionsModel.ts | 33 ++++++++++--------- .../inlineCompletions/browser/utils.ts | 4 +++ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 15fd1e0e856..0773f8224eb 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -22,7 +22,7 @@ import { GhostText, GhostTextOrReplacement, ghostTextOrReplacementEquals, ghostT import { InlineCompletionWithUpdatedRange, InlineCompletionsSource } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource'; import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; import { SuggestItemInfo } from 'vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider'; -import { Permutation, addPositions, getNewRanges, lengthOfText } from 'vs/editor/contrib/inlineCompletions/browser/utils'; +import { Permutation, addPositions, getNewRanges, lengthOfText, subtractPositions } from 'vs/editor/contrib/inlineCompletions/browser/utils'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -469,13 +469,6 @@ export class InlineCompletionsModel extends Disposable { } } -function getEndPositionsAfterApplying(edits: readonly SingleTextEdit[]): Position[] { - const sortPerm = Permutation.createSortPermutation(edits, (edit1, edit2) => Range.compareRangesUsingStarts(edit1.range, edit2.range)); - const sortedNewRanges = getNewRanges(sortPerm.apply(edits)); - const newRanges = sortPerm.inverse().apply(sortedNewRanges); - return newRanges.map(range => range.getEndPosition()); -} - export function getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { const primaryPosition = positions[0]; const secondaryPositions = positions.slice(1); @@ -483,13 +476,8 @@ export function getSecondaryEdits(textModel: ITextModel, positions: readonly Pos const replacedTextAfterPrimaryCursor = textModel.getValueInRange( Range.fromPositions(primaryPosition, primaryEditEndPosition) ); - const lineNumberWithinEditText = primaryPosition.lineNumber - primaryEdit.range.startLineNumber; - const columnWithinEditText = lineNumberWithinEditText === 0 ? primaryPosition.column - primaryEdit.range.startColumn : primaryPosition.column - 1; - let secondaryEditText = ''; - const { lines, separators } = splitLinesIncludeSeparators(primaryEdit.text); - for (let i = lineNumberWithinEditText; i < lines.length; i++) { - secondaryEditText += lines[i].substring(i === lineNumberWithinEditText ? columnWithinEditText : 0) + (separators[i] ?? ''); - } + const positionWithinTextEdit = subtractPositions(primaryPosition, primaryEdit.range.getStartPosition()); + const secondaryEditText = getTextFromPosition(primaryEdit.text, positionWithinTextEdit); return secondaryPositions.map(pos => { const textAfterSecondaryCursor = textModel.getValueInRange( Range.fromPositions(pos, primaryEditEndPosition) @@ -500,3 +488,18 @@ export function getSecondaryEdits(textModel: ITextModel, positions: readonly Pos }); } +function getTextFromPosition(text: string, pos: Position): string { + let subtext = ''; + const { lines, separators } = splitLinesIncludeSeparators(text); + for (let i = pos.lineNumber - 1; i < lines.length; i++) { + subtext += lines[i].substring(i === pos.lineNumber - 1 ? pos.column - 1 : 0) + (separators[i] ?? ''); + } + return subtext; +} + +function getEndPositionsAfterApplying(edits: readonly SingleTextEdit[]): Position[] { + const sortPerm = Permutation.createSortPermutation(edits, (edit1, edit2) => Range.compareRangesUsingStarts(edit1.range, edit2.range)); + const sortedNewRanges = getNewRanges(sortPerm.apply(edits)); + const newRanges = sortPerm.inverse().apply(sortedNewRanges); + return newRanges.map(range => range.getEndPosition()); +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts index 805de9a781a..4a9e78238b6 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts @@ -96,6 +96,10 @@ export function addPositions(pos1: Position, pos2: Position): Position { return new Position(pos1.lineNumber + pos2.lineNumber - 1, pos2.lineNumber === 1 ? pos1.column + pos2.column - 1 : pos2.column); } +export function subtractPositions(pos1: Position, pos2: Position): Position { + return new Position(pos1.lineNumber - pos2.lineNumber + 1, pos1.lineNumber - pos2.lineNumber === 0 ? pos1.column - pos2.column + 1 : pos1.column); +} + export function lengthOfText(text: string): Position { let line = 1; let column = 1; From 55b3ca6757904e56147ed8acbdd1a162fe310117 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 12 Feb 2024 13:09:59 +0100 Subject: [PATCH 1123/1897] adding check that the marker controller is defined before ppending an action --- .../hover/browser/markerHoverParticipant.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/markerHoverParticipant.ts b/src/vs/editor/contrib/hover/browser/markerHoverParticipant.ts index d56720dcebd..ffdc5ccf50f 100644 --- a/src/vs/editor/contrib/hover/browser/markerHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/browser/markerHoverParticipant.ts @@ -170,15 +170,18 @@ export class MarkerHoverParticipant implements IEditorHoverParticipant { - context.hide(); - MarkerController.get(this._editor)?.showAtMarker(markerHover.marker); - this._editor.focus(); - } - }); + const markerController = MarkerController.get(this._editor); + if (markerController) { + context.statusBar.addAction({ + label: nls.localize('view problem', "View Problem"), + commandId: NextMarkerAction.ID, + run: () => { + context.hide(); + markerController.showAtMarker(markerHover.marker); + this._editor.focus(); + } + }); + } } if (!this._editor.getOption(EditorOption.readOnly)) { From 488005693067ba8d6ac73d32bac3892fe4a445c9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Feb 2024 14:53:36 +0100 Subject: [PATCH 1124/1897] aux window - reduce $window usage (#204991) --- src/vs/platform/sign/browser/signService.ts | 4 +- .../services/notebookKernelServiceImpl.ts | 5 +- .../browser/view/renderers/webviewPreloads.ts | 118 +++++++++--------- 3 files changed, 65 insertions(+), 62 deletions(-) diff --git a/src/vs/platform/sign/browser/signService.ts b/src/vs/platform/sign/browser/signService.ts index dadf7a0f418..a9d699bf3b2 100644 --- a/src/vs/platform/sign/browser/signService.ts +++ b/src/vs/platform/sign/browser/signService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { WindowIntervalTimer } from 'vs/base/browser/dom'; -import { $window } from 'vs/base/browser/window'; +import { mainWindow } from 'vs/base/browser/window'; import { memoize } from 'vs/base/common/decorators'; import { FileAccess } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -70,7 +70,7 @@ export class SignService extends AbstractSignService implements ISignService { if (typeof vsda_web !== 'undefined') { resolve(); } - }, 50, $window); + }, 50, mainWindow); }).finally(() => checkInterval.dispose()), ]); diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts index d86b1b7f928..572a833e173 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts @@ -16,8 +16,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAction } from 'vs/base/common/actions'; import { MarshalledId } from 'vs/base/common/marshallingIds'; import { Schemas } from 'vs/base/common/network'; -import { $window } from 'vs/base/browser/window'; -import { runWhenWindowIdle } from 'vs/base/browser/dom'; +import { getActiveWindow, runWhenWindowIdle } from 'vs/base/browser/dom'; class KernelInfo { @@ -171,7 +170,7 @@ export class NotebookKernelService extends Disposable implements INotebookKernel private _persistMementos(): void { this._persistSoonHandle?.dispose(); - this._persistSoonHandle = runWhenWindowIdle($window, () => { + this._persistSoonHandle = runWhenWindowIdle(getActiveWindow(), () => { this._storageService.store(NotebookKernelService._storageNotebookBinding, JSON.stringify(this._notebookBindings), StorageScope.WORKSPACE, StorageTarget.MACHINE); }, 100); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index ebbf9d27032..c08eb70c296 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type * as DOM from 'vs/base/browser/window'; import type { Event } from 'vs/base/common/event'; import type { IDisposable } from 'vs/base/common/lifecycle'; import type * as webviewMessages from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages'; @@ -89,8 +88,13 @@ declare function cancelIdleCallback(handle: number): void; declare function __import(path: string): Promise; async function webviewPreloads(ctx: PreloadContext) { - // eslint-disable-next-line no-restricted-globals - const $window = window as typeof DOM.$window; + + /* eslint-disable no-restricted-globals */ + + // The use of global `window` should be fine in this context, even + // with aux windows. This code is running from within an `iframe` + // where there is only one `window` object anyway. + const userAgent = navigator.userAgent; const isChrome = (userAgent.indexOf('Chrome') >= 0); const textEncoder = new TextEncoder(); @@ -159,7 +163,7 @@ async function webviewPreloads(ctx: PreloadContext) { // check if an input element is focused within the output element const checkOutputInputFocus = () => { - const activeElement = $window.document.activeElement; + const activeElement = window.document.activeElement; if (!activeElement) { return; } @@ -271,8 +275,8 @@ async function webviewPreloads(ctx: PreloadContext) { } }; - $window.document.body.addEventListener('click', handleInnerClick); - $window.document.body.addEventListener('focusin', checkOutputInputFocus); + window.document.body.addEventListener('click', handleInnerClick); + window.document.body.addEventListener('focusin', checkOutputInputFocus); interface RendererContext extends rendererApi.RendererContext { readonly onDidChangeSettings: Event; @@ -373,7 +377,7 @@ async function webviewPreloads(ctx: PreloadContext) { constructor() { this._observer = new ResizeObserver(entries => { for (const entry of entries) { - if (!$window.document.body.contains(entry.target)) { + if (!window.document.body.contains(entry.target)) { continue; } @@ -405,7 +409,7 @@ async function webviewPreloads(ctx: PreloadContext) { if (shouldUpdatePadding) { // Do not update dimension in resize observer - $window.requestAnimationFrame(() => { + window.requestAnimationFrame(() => { if (newHeight !== 0) { entry.target.style.padding = `${ctx.style.outputNodePadding}px ${ctx.style.outputNodePadding}px ${ctx.style.outputNodePadding}px ${ctx.style.outputNodeLeftPadding}px`; } else { @@ -473,7 +477,7 @@ async function webviewPreloads(ctx: PreloadContext) { } // if the node is not scrollable, we can continue. We don't check the computed style always as it's expensive - if ($window.getComputedStyle(node).overflowY === 'hidden' || $window.getComputedStyle(node).overflowY === 'visible') { + if (window.getComputedStyle(node).overflowY === 'hidden' || window.getComputedStyle(node).overflowY === 'visible') { continue; } @@ -495,9 +499,9 @@ async function webviewPreloads(ctx: PreloadContext) { deltaY: event.deltaY, deltaZ: event.deltaZ, // Refs https://github.com/microsoft/vscode/issues/146403#issuecomment-1854538928 - wheelDelta: event.wheelDelta && isChrome ? (event.wheelDelta / $window.devicePixelRatio) : event.wheelDelta, - wheelDeltaX: event.wheelDeltaX && isChrome ? (event.wheelDeltaX / $window.devicePixelRatio) : event.wheelDeltaX, - wheelDeltaY: event.wheelDeltaY && isChrome ? (event.wheelDeltaY / $window.devicePixelRatio) : event.wheelDeltaY, + wheelDelta: event.wheelDelta && isChrome ? (event.wheelDelta / window.devicePixelRatio) : event.wheelDelta, + wheelDeltaX: event.wheelDeltaX && isChrome ? (event.wheelDeltaX / window.devicePixelRatio) : event.wheelDeltaX, + wheelDeltaY: event.wheelDeltaY && isChrome ? (event.wheelDeltaY / window.devicePixelRatio) : event.wheelDeltaY, detail: event.detail, shiftKey: event.shiftKey, type: event.type @@ -506,10 +510,10 @@ async function webviewPreloads(ctx: PreloadContext) { }; function focusFirstFocusableOrContainerInOutput(cellOrOutputId: string, alternateId?: string) { - const cellOutputContainer = $window.document.getElementById(cellOrOutputId) ?? - (alternateId ? $window.document.getElementById(alternateId) : undefined); + const cellOutputContainer = window.document.getElementById(cellOrOutputId) ?? + (alternateId ? window.document.getElementById(alternateId) : undefined); if (cellOutputContainer) { - if (cellOutputContainer.contains($window.document.activeElement)) { + if (cellOutputContainer.contains(window.document.activeElement)) { return; } @@ -688,7 +692,7 @@ async function webviewPreloads(ctx: PreloadContext) { } function selectRange(_range: ICommonRange) { - const sel = $window.getSelection(); + const sel = window.getSelection(); if (sel) { try { sel.removeAllRanges(); @@ -721,8 +725,8 @@ async function webviewPreloads(ctx: PreloadContext) { } }; } else { - $window.document.execCommand('hiliteColor', false, matchColor); - const cloneRange = $window.getSelection()!.getRangeAt(0).cloneRange(); + window.document.execCommand('hiliteColor', false, matchColor); + const cloneRange = window.getSelection()!.getRangeAt(0).cloneRange(); const _range = { collapsed: cloneRange.collapsed, commonAncestorContainer: cloneRange.commonAncestorContainer, @@ -737,9 +741,9 @@ async function webviewPreloads(ctx: PreloadContext) { selectRange(_range); try { document.designMode = 'On'; - $window.document.execCommand('removeFormat', false, undefined); + window.document.execCommand('removeFormat', false, undefined); document.designMode = 'Off'; - $window.getSelection()?.removeAllRanges(); + window.getSelection()?.removeAllRanges(); } catch (e) { console.log(e); } @@ -748,10 +752,10 @@ async function webviewPreloads(ctx: PreloadContext) { selectRange(_range); try { document.designMode = 'On'; - $window.document.execCommand('removeFormat', false, undefined); - $window.document.execCommand('hiliteColor', false, color); + window.document.execCommand('removeFormat', false, undefined); + window.document.execCommand('hiliteColor', false, color); document.designMode = 'Off'; - $window.getSelection()?.removeAllRanges(); + window.getSelection()?.removeAllRanges(); } catch (e) { console.log(e); } @@ -922,12 +926,12 @@ async function webviewPreloads(ctx: PreloadContext) { const onDidReceiveKernelMessage = createEmitter(); - const ttPolicy = $window.trustedTypes?.createPolicy('notebookRenderer', { + const ttPolicy = window.trustedTypes?.createPolicy('notebookRenderer', { createHTML: value => value, // CodeQL [SM03712] The rendered content is provided by renderer extensions, which are responsible for sanitizing their content themselves. The notebook webview is also sandboxed. createScript: value => value, // CodeQL [SM03712] The rendered content is provided by renderer extensions, which are responsible for sanitizing their content themselves. The notebook webview is also sandboxed. }); - $window.addEventListener('wheel', handleWheel); + window.addEventListener('wheel', handleWheel); interface IFindMatch { type: 'preview' | 'output'; @@ -961,8 +965,8 @@ async function webviewPreloads(ctx: PreloadContext) { currentMatchIndex: number; } - const matchColor = $window.getComputedStyle($window.document.getElementById('_defaultColorPalatte')!).color; - const currentMatchColor = $window.getComputedStyle($window.document.getElementById('_defaultColorPalatte')!).backgroundColor; + const matchColor = window.getComputedStyle(window.document.getElementById('_defaultColorPalatte')!).color; + const currentMatchColor = window.getComputedStyle(window.document.getElementById('_defaultColorPalatte')!).backgroundColor; class JSHighlighter implements IHighlighter { private _activeHighlightInfo: Map; @@ -1008,11 +1012,11 @@ async function webviewPreloads(ctx: PreloadContext) { const match = highlightInfo.matches[index]; highlightInfo.currentMatchIndex = index; - const sel = $window.getSelection(); + const sel = window.getSelection(); if (!!match && !!sel && match.highlightResult) { let offset = 0; try { - const outputOffset = $window.document.getElementById(match.id)!.getBoundingClientRect().top; + const outputOffset = window.document.getElementById(match.id)!.getBoundingClientRect().top; const tempRange = document.createRange(); tempRange.selectNode(match.highlightResult.range.startContainer); @@ -1028,7 +1032,7 @@ async function webviewPreloads(ctx: PreloadContext) { match.highlightResult?.update(currentMatchColor, match.isShadow ? undefined : 'current-find-match'); - $window.document.getSelection()?.removeAllRanges(); + window.document.getSelection()?.removeAllRanges(); postNotebookMessage('didFindHighlightCurrent', { offset }); @@ -1047,7 +1051,7 @@ async function webviewPreloads(ctx: PreloadContext) { } dispose() { - $window.document.getSelection()?.removeAllRanges(); + window.document.getSelection()?.removeAllRanges(); this._activeHighlightInfo.forEach(highlightInfo => { highlightInfo.matches.forEach(match => { match.highlightResult?.dispose(); @@ -1122,7 +1126,7 @@ async function webviewPreloads(ctx: PreloadContext) { if (match) { let offset = 0; try { - const outputOffset = $window.document.getElementById(match.id)!.getBoundingClientRect().top; + const outputOffset = window.document.getElementById(match.id)!.getBoundingClientRect().top; match.originalRange.startContainer.parentElement?.scrollIntoView({ behavior: 'auto', block: 'end', inline: 'nearest' }); const rangeOffset = match.originalRange.getBoundingClientRect().top; offset = rangeOffset - outputOffset; @@ -1151,7 +1155,7 @@ async function webviewPreloads(ctx: PreloadContext) { } dispose(): void { - $window.document.getSelection()?.removeAllRanges(); + window.document.getSelection()?.removeAllRanges(); this._currentMatchesHighlight.clear(); this._matchesHighlight.clear(); } @@ -1252,8 +1256,8 @@ async function webviewPreloads(ctx: PreloadContext) { const matches: IFindMatch[] = []; const range = document.createRange(); - range.selectNodeContents($window.document.getElementById('findStart')!); - const sel = $window.getSelection(); + range.selectNodeContents(window.document.getElementById('findStart')!); + const sel = window.getSelection(); sel?.removeAllRanges(); sel?.addRange(range); @@ -1263,7 +1267,7 @@ async function webviewPreloads(ctx: PreloadContext) { document.designMode = 'On'; while (find && matches.length < 500) { - find = ($window as any).find(query, /* caseSensitive*/ !!options.caseSensitive, + find = (window as any).find(query, /* caseSensitive*/ !!options.caseSensitive, /* backwards*/ false, /* wrapAround*/ false, /* wholeWord */ !!options.wholeWord, @@ -1271,7 +1275,7 @@ async function webviewPreloads(ctx: PreloadContext) { false); if (find) { - const selection = $window.getSelection(); + const selection = window.getSelection(); if (!selection) { console.log('no selection'); break; @@ -1360,7 +1364,7 @@ async function webviewPreloads(ctx: PreloadContext) { break; } - if (node.id === 'container' || node === $window.document.body) { + if (node.id === 'container' || node === window.document.body) { break; } } @@ -1376,7 +1380,7 @@ async function webviewPreloads(ctx: PreloadContext) { } _highlighter.addHighlights(matches, options.ownerID); - $window.document.getSelection()?.removeAllRanges(); + window.document.getSelection()?.removeAllRanges(); viewModel.toggleDragDropEnabled(currentOptions.dragAndDropEnabled); @@ -1394,7 +1398,7 @@ async function webviewPreloads(ctx: PreloadContext) { }; const copyOutputImage = async (outputId: string, altOutputId: string, retries = 5) => { - if (!$window.document.hasFocus() && retries > 0) { + if (!window.document.hasFocus() && retries > 0) { // copyImage can be called from outside of the webview, which means this function may be running whilst the webview is gaining focus. // Since navigator.clipboard.write requires the document to be focused, we need to wait for focus. // We cannot use a listener, as there is a high chance the focus is gained during the setup of the listener resulting in us missing it. @@ -1403,8 +1407,8 @@ async function webviewPreloads(ctx: PreloadContext) { } try { - const outputElement = $window.document.getElementById(outputId) - ?? $window.document.getElementById(altOutputId); + const outputElement = window.document.getElementById(outputId) + ?? window.document.getElementById(altOutputId); let image = outputElement?.querySelector('img'); @@ -1446,7 +1450,7 @@ async function webviewPreloads(ctx: PreloadContext) { } }; - $window.addEventListener('message', async rawEvent => { + window.addEventListener('message', async rawEvent => { const event = rawEvent as ({ data: webviewMessages.ToWebviewMessage }); switch (event.data.type) { @@ -1520,7 +1524,7 @@ async function webviewPreloads(ctx: PreloadContext) { case 'clear': renderers.clearAll(); viewModel.clearAll(); - $window.document.getElementById('container')!.innerText = ''; + window.document.getElementById('container')!.innerText = ''; break; case 'clearOutput': { @@ -1572,10 +1576,10 @@ async function webviewPreloads(ctx: PreloadContext) { focusFirstFocusableOrContainerInOutput(event.data.cellOrOutputId, event.data.alternateId); break; case 'decorations': { - let outputContainer = $window.document.getElementById(event.data.cellId); + let outputContainer = window.document.getElementById(event.data.cellId); if (!outputContainer) { viewModel.ensureOutputCell(event.data.cellId, -100000, true); - outputContainer = $window.document.getElementById(event.data.cellId); + outputContainer = window.document.getElementById(event.data.cellId); } outputContainer?.classList.add(...event.data.addedClassNames); outputContainer?.classList.remove(...event.data.removedClassNames); @@ -1588,7 +1592,7 @@ async function webviewPreloads(ctx: PreloadContext) { renderers.getRenderer(event.data.rendererId)?.receiveMessage(event.data.message); break; case 'notebookStyles': { - const documentStyle = $window.document.documentElement.style; + const documentStyle = window.document.documentElement.style; for (let i = documentStyle.length - 1; i >= 0; i--) { const property = documentStyle[i]; @@ -1969,7 +1973,7 @@ async function webviewPreloads(ctx: PreloadContext) { public async render(item: ExtendedOutputItem, preferredRendererId: string | undefined, element: HTMLElement, signal: AbortSignal): Promise { const primaryRenderer = this.findRenderer(preferredRendererId, item); if (!primaryRenderer) { - const errorMessage = ($window.document.documentElement.style.getPropertyValue('--notebook-cell-renderer-not-found-error') || '').replace('$0', () => item.mime); + const errorMessage = (window.document.documentElement.style.getPropertyValue('--notebook-cell-renderer-not-found-error') || '').replace('$0', () => item.mime); this.showRenderError(item, element, errorMessage); return; } @@ -2001,7 +2005,7 @@ async function webviewPreloads(ctx: PreloadContext) { } // All renderers have failed and there is nothing left to fallback to - const errorMessage = ($window.document.documentElement.style.getPropertyValue('--notebook-cell-renderer-fallbacks-exhausted') || '').replace('$0', () => item.mime); + const errorMessage = (window.document.documentElement.style.getPropertyValue('--notebook-cell-renderer-fallbacks-exhausted') || '').replace('$0', () => item.mime); this.showRenderError(item, element, errorMessage); } @@ -2318,7 +2322,7 @@ async function webviewPreloads(ctx: PreloadContext) { }] }); - const root = $window.document.getElementById('container')!; + const root = window.document.getElementById('container')!; const markupCell = document.createElement('div'); markupCell.className = 'markup'; markupCell.style.position = 'absolute'; @@ -2486,7 +2490,7 @@ async function webviewPreloads(ctx: PreloadContext) { private readonly outputElements = new Map(); constructor(cellId: string) { - const container = $window.document.getElementById('container')!; + const container = window.document.getElementById('container')!; const upperWrapperElement = createFocusSink(cellId); container.appendChild(upperWrapperElement); @@ -2779,12 +2783,12 @@ async function webviewPreloads(ctx: PreloadContext) { private dragOverlay?: HTMLElement; constructor() { - $window.document.addEventListener('dragover', e => { + window.document.addEventListener('dragover', e => { // Allow dropping dragged markup cells e.preventDefault(); }); - $window.document.addEventListener('drop', e => { + window.document.addEventListener('drop', e => { e.preventDefault(); const drag = this.currentDrag; @@ -2823,7 +2827,7 @@ async function webviewPreloads(ctx: PreloadContext) { this.dragOverlay.style.width = '100%'; this.dragOverlay.style.height = '100%'; this.dragOverlay.style.background = 'transparent'; - $window.document.body.appendChild(this.dragOverlay); + window.document.body.appendChild(this.dragOverlay); } (e.target as HTMLElement).style.zIndex = `${overlayZIndex + 1}`; (e.target as HTMLElement).classList.add('dragging'); @@ -2844,9 +2848,9 @@ async function webviewPreloads(ctx: PreloadContext) { cellId: cellId, dragOffsetY: this.currentDrag.clientY, }); - $window.requestAnimationFrame(trySendDragUpdate); + window.requestAnimationFrame(trySendDragUpdate); }; - $window.requestAnimationFrame(trySendDragUpdate); + window.requestAnimationFrame(trySendDragUpdate); } updateDrag(e: DragEvent, cellId: string) { @@ -2865,7 +2869,7 @@ async function webviewPreloads(ctx: PreloadContext) { }); if (this.dragOverlay) { - $window.document.body.removeChild(this.dragOverlay); + window.document.body.removeChild(this.dragOverlay); this.dragOverlay = undefined; } From b31cef9e2c98143aad50d8ccbf7ec203cffa925c Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 12 Feb 2024 14:55:26 +0100 Subject: [PATCH 1125/1897] placing separators with the lines themselves --- src/vs/base/common/strings.ts | 11 ++++++----- .../browser/inlineCompletionsModel.ts | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index a68f48e76f9..6ec11f03919 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -254,12 +254,13 @@ export function splitLines(str: string): string[] { return str.split(/\r\n|\r|\n/); } -export function splitLinesIncludeSeparators(str: string): { lines: string[]; separators: string[] } { - const lines: string[] = []; - const separators: string[] = []; +export function splitLinesIncludeSeparators(str: string): string[] { + const linesWithSeparators: string[] = []; const splitLinesAndSeparators = str.split(/(\r\n|\r|\n)/); - splitLinesAndSeparators.forEach((el, idx) => (idx % 2 === 0 ? lines : separators).push(el)); - return { lines, separators }; + for (let i = 0; i < Math.ceil(splitLinesAndSeparators.length / 2); i++) { + linesWithSeparators.push(splitLinesAndSeparators[2 * i] + (splitLinesAndSeparators[2 * i + 1] ?? '')); + } + return linesWithSeparators; } /** diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 0773f8224eb..e385a0d3c65 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -490,9 +490,9 @@ export function getSecondaryEdits(textModel: ITextModel, positions: readonly Pos function getTextFromPosition(text: string, pos: Position): string { let subtext = ''; - const { lines, separators } = splitLinesIncludeSeparators(text); + const lines = splitLinesIncludeSeparators(text); for (let i = pos.lineNumber - 1; i < lines.length; i++) { - subtext += lines[i].substring(i === pos.lineNumber - 1 ? pos.column - 1 : 0) + (separators[i] ?? ''); + subtext += lines[i].substring(i === pos.lineNumber - 1 ? pos.column - 1 : 0); } return subtext; } From 4eb07edec2fde1a1de1886086a947e737ff3d8e4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 12 Feb 2024 15:20:58 +0100 Subject: [PATCH 1126/1897] api - change history into turns (#204992) https://github.com/microsoft/vscode/issues/199908 --- .../workbench/api/common/extHost.api.impl.ts | 2 + .../api/common/extHostChatAgents2.ts | 24 ++++++- .../api/common/extHostTypeConverters.ts | 4 +- src/vs/workbench/api/common/extHostTypes.ts | 17 +++++ .../vscode.proposed.chatAgents2.d.ts | 72 ++++++++++++++----- 5 files changed, 99 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index a820c667796..071e935c051 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1663,6 +1663,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ChatResponseProgressPart: extHostTypes.ChatResponseProgressPart, ChatResponseReferencePart: extHostTypes.ChatResponseReferencePart, ChatResponseCommandButtonPart: extHostTypes.ChatResponseCommandButtonPart, + ChatAgentRequestTurn: extHostTypes.ChatAgentRequestTurn, + ChatAgentResponseTurn: extHostTypes.ChatAgentResponseTurn, }; }; } diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 558061ddbe8..65a95d5374e 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -198,9 +198,10 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService, this.commands.converter, sessionDisposables); try { const convertedHistory = await this.prepareHistory(request, context); + const convertedHistory2 = await this.prepareHistoryTurns(request, context); const task = agent.invoke( typeConvert.ChatAgentRequest.to(request), - { history: convertedHistory }, + { history: convertedHistory, history2: convertedHistory2 }, stream.apiObject, token ); @@ -243,6 +244,27 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { }))); } + private async prepareHistoryTurns(request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise<(vscode.ChatAgentRequestTurn | vscode.ChatAgentResponseTurn)[]> { + + const res: (vscode.ChatAgentRequestTurn | vscode.ChatAgentResponseTurn)[] = []; + + for (const h of context.history) { + const ehResult = typeConvert.ChatAgentResult.to(h.result); + const result: vscode.ChatAgentResult2 = request.agentId === h.request.agentId ? + ehResult : + { ...ehResult, metadata: undefined }; + + // REQUEST turn + res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.agentId, h.request.command, h.request.variables2.variables.map(typeConvert.ChatAgentResolvedVariable.to))); + + // RESPONSE turn + const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))); + res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, h.request.agentId)); + } + + return res; + } + $releaseSession(sessionId: string): void { this._sessionDisposables.deleteAndDispose(sessionId); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 0b639e933b5..588ab94a8de 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2479,7 +2479,7 @@ export namespace ChatResponsePart { } - export function from(part: extHostProtocol.IChatProgressDto, commandsConverter: CommandsConverter): vscode.ChatResponsePart { + export function from(part: extHostProtocol.IChatProgressDto, commandsConverter: CommandsConverter): vscode.ChatResponsePart | undefined { switch (part.kind) { case 'markdownContent': return ChatResponseMarkdownPart.from(part); case 'inlineReference': return ChatResponseAnchorPart.from(part); @@ -2488,7 +2488,7 @@ export namespace ChatResponsePart { case 'treeData': return ChatResponseFilesPart.from(part); case 'command': return ChatResponseCommandButtonPart.from(part, commandsConverter); } - return new types.ChatResponseTextPart(''); + return undefined; } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 9a392ffc2e3..492cf921558 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4257,6 +4257,23 @@ export class ChatResponseReferencePart { } +export class ChatAgentRequestTurn implements vscode.ChatAgentRequestTurn { + constructor( + readonly prompt: string, + readonly agentId: string, + readonly command: string | undefined, + readonly variables: vscode.ChatAgentResolvedVariable[], + ) { } +} + +export class ChatAgentResponseTurn implements vscode.ChatAgentResponseTurn { + constructor( + readonly response: ReadonlyArray, + readonly result: vscode.ChatAgentResult2, + readonly agentId: string, + ) { } +} + //#endregion //#region ai diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 403265ad3f2..9f0f46769f1 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -12,13 +12,12 @@ declare module 'vscode' { /** * The request that was sent to the chat agent. */ - // TODO@API make this optional? Allow for response without request? request: ChatAgentRequest; /** * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. */ - response: (ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart)[]; + response: ReadonlyArray; /** * The result that was received from the chat agent. @@ -26,31 +25,69 @@ declare module 'vscode' { result: ChatAgentResult2; } - // TODO@API class - // export interface ChatAgentResponse { - // /** - // * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. - // */ - // response: (ChatAgentContentProgress | ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart)[]; + // TODO@API name: Turn? + export class ChatAgentRequestTurn { - // agentId: string + /** + * The prompt as entered by the user. + * + * Information about variables used in this request are is stored in {@link ChatAgentRequest.variables2}. + * + * *Note* that the {@link ChatAgent2.name name} of the agent and the {@link ChatAgentCommand.name command} + * are not part of the prompt. + */ + readonly prompt: string; - // /** - // * The result that was received from the chat agent. - // */ - // result: ChatAgentResult2; - // } + /** + * The ID of the chat agent to which this request was directed. + */ + readonly agentId: string; + + /** + * The name of the {@link ChatAgentCommand command} that was selected for this request. + */ + readonly command: string | undefined; + + /** + * + */ + // TODO@API is this needed? + readonly variables: ChatAgentResolvedVariable[]; + + private constructor(prompt: string, agentId: string, command: string | undefined, variables: ChatAgentResolvedVariable[],); + } + + // TODO@API name: Turn? + export class ChatAgentResponseTurn { + + /** + * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. + */ + readonly response: ReadonlyArray; + + /** + * The result that was received from the chat agent. + */ + readonly result: ChatAgentResult2; + + readonly agentId: string; + + private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: string); + } export interface ChatAgentContext { /** - * All of the chat messages so far in the current chat session. + * @deprecated */ history: ChatAgentHistoryEntry[]; // location: - // TODO@API have "turns" - // history2: (ChatAgentRequest | ChatAgentResponse)[]; + /** + * All of the chat messages so far in the current chat session. + */ + // TODO@API name: histroy + readonly history2: ReadonlyArray; } /** @@ -479,6 +516,7 @@ declare module 'vscode' { * @param description A description of the variable for the chat input suggest widget. * @param resolver Will be called to provide the chat variable's value when it is used. */ + // TODO@API NAME: registerChatVariable, registerChatVariableResolver export function registerVariable(name: string, description: string, resolver: ChatVariableResolver): Disposable; } From b2d46084e9900bc9c4818e5824288ebdc4bc4a23 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 12 Feb 2024 15:32:14 +0100 Subject: [PATCH 1127/1897] api - remove `vscode.ChatMessage#name` (#204993) https://github.com/microsoft/vscode/issues/199908 --- src/vs/workbench/api/common/extHostTypeConverters.ts | 6 +----- src/vs/workbench/contrib/chat/common/chatProvider.ts | 1 - src/vscode-dts/vscode.proposed.chat.d.ts | 3 --- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 588ab94a8de..fda37eef309 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2228,17 +2228,13 @@ export namespace ChatInlineFollowup { 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; + return new types.ChatMessage(ChatMessageRole.to(message.role), message.content); } - export function from(message: vscode.ChatMessage): chatProvider.IChatMessage { return { role: ChatMessageRole.from(message.role), content: message.content, - name: message.name }; } } diff --git a/src/vs/workbench/contrib/chat/common/chatProvider.ts b/src/vs/workbench/contrib/chat/common/chatProvider.ts index 38be72eb51e..c4655e35665 100644 --- a/src/vs/workbench/contrib/chat/common/chatProvider.ts +++ b/src/vs/workbench/contrib/chat/common/chatProvider.ts @@ -19,7 +19,6 @@ export const enum ChatMessageRole { export interface IChatMessage { readonly role: ChatMessageRole; readonly content: string; - readonly name?: string; } export interface IChatResponseFragment { diff --git a/src/vscode-dts/vscode.proposed.chat.d.ts b/src/vscode-dts/vscode.proposed.chat.d.ts index 1e27e2e5ab9..5dc2704b176 100644 --- a/src/vscode-dts/vscode.proposed.chat.d.ts +++ b/src/vscode-dts/vscode.proposed.chat.d.ts @@ -18,9 +18,6 @@ declare module 'vscode' { role: ChatMessageRole; content: string; - // TODO@API is this a leftover from Role.Function? Should message just support a catch-all signature? - name?: string; - constructor(role: ChatMessageRole, content: string); } From 5cfd0d5a41d47fb72a3625800407c9b24673e94e Mon Sep 17 00:00:00 2001 From: Xavier Decoster Date: Mon, 12 Feb 2024 15:46:32 +0100 Subject: [PATCH 1128/1897] Update comment in extensionGalleryService.ts See GH issue #205003 --- .../extensionManagement/common/extensionGalleryService.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 6afa575b1a0..07195962b1c 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -167,9 +167,8 @@ enum Flags { IncludeLatestVersionOnly = 0x200, /** - * This flag switches the asset uri to use GetAssetByName instead of CDN - * When this is used, values of base asset uri and base asset uri fallback are switched - * When this is used, source of asset files are pointed to Gallery service always even if CDN is available + * The Unpublished extension flag indicates that the extension can't be installed/downloaded. + * Users who have installed such an extension can continue to use the extension. */ Unpublished = 0x1000, From 23704c7fa53d5fd08d163de682a1423db89fc0a1 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 12 Feb 2024 15:51:28 +0100 Subject: [PATCH 1129/1897] using instead scrollTo method --- .../multiDiffEditorWidget.ts | 14 ++------ .../multiDiffEditorWidgetImpl.ts | 21 ++++-------- .../bulkEdit/browser/preview/bulkEditPane.ts | 33 ++++--------------- .../browser/multiDiffEditor.ts | 14 ++------ 4 files changed, 19 insertions(+), 63 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index 349c26b5ee3..9a19e5e5669 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { derived, derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; -import { IMultiDiffEditorViewState, MultiDiffEditorWidgetImpl, VirtualizedViewItem } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorViewState, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import './colors'; @@ -44,16 +44,8 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public setScrollState(scrollState: { top?: number; left?: number }): void { - this._widgetImpl.get().setScrollState(scrollState); - } - - public getTopOfElement(index: number): number { - return this._widgetImpl.get().getTopOfElement(index); - } - - public viewItems(): readonly VirtualizedViewItem[] { - return this._widgetImpl.get().viewItems(); + public scrollTo(uri: URI): void { + this._widgetImpl.get().scrollTo(uri); } public createViewModel(model: IMultiDiffEditorModel): MultiDiffEditorViewModel { diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 54818d55c35..5619b08b2cd 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -186,19 +186,14 @@ export class MultiDiffEditorWidgetImpl extends Disposable { this._scrollableElement.setScrollPosition({ scrollLeft: scrollState.left, scrollTop: scrollState.top }); } - public getTopOfElement(index: number): number { - console.log('index : ', index); + public scrollTo(originalUri: URI): void { const viewItems = this._viewItems.get(); - console.log('viewItems : ', viewItems); - let top = 0; - for (let i = 0; i < index - 1; i++) { - top += viewItems[i].contentHeight.get() + this._spaceBetweenPx; + const index = viewItems?.findIndex(item => item.viewModel.originalUri?.toString() === originalUri.toString()); + let scrollTop = 0; + for (let i = 0; i < index; i++) { + scrollTop += viewItems[i].contentHeight.get() + this._spaceBetweenPx; } - return top; - } - - public viewItems(): readonly VirtualizedViewItem[] { - return this._viewItems.get(); + this._scrollableElement.setScrollPosition({ scrollTop }); } public getViewState(): IMultiDiffEditorViewState { @@ -292,10 +287,9 @@ export interface IMultiDiffEditorViewState { interface IMultiDiffDocState { collapsed: boolean; selections?: ISelection[]; - uri?: URI; } -export class VirtualizedViewItem extends Disposable { +class VirtualizedViewItem extends Disposable { private readonly _templateRef = this._register(disposableObservableValue | undefined>(this, undefined)); public readonly contentHeight = derived(this, reader => @@ -352,7 +346,6 @@ export class VirtualizedViewItem extends Disposable { return { collapsed: this.viewModel.collapsed.get(), selections: this.viewModel.lastTemplateData.get().selections, - uri: this.viewModel.originalUri }; } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 7a8faba7ea4..b072acb88e0 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -318,10 +318,6 @@ export class BulkEditPane extends ViewPane { } } - // Issues with current implementation - // 3. Currently I am accessing the parent of the file element and showing all of the files, but we want to jump to the correct location when clicking on the multi diff editor - // Maybe we should somehow return the file operations directly instead of iterating upwards, the way that we currently do - // 4. Should jump instead to the part of the multi diff editor of interest, so no need to show it again, and can just scroll private async _openElementInMultiDiffEditor(e: IOpenEvent): Promise { const options: Mutable = { ...e.editorOptions }; @@ -352,16 +348,7 @@ export class BulkEditPane extends ViewPane { if (this._fileOperations === fileOperations) { // Multi diff editor already open - const viewItems = this._multiDiffEditor?.viewItems(); - if (viewItems) { - const item = viewItems?.find(item => item.viewModel.originalUri?.toString() === fileElement.edit.uri.toString()); - if (item) { - const index = viewItems.indexOf(item); - const top = this._multiDiffEditor?.getTopOfElement(index); - console.log('top : ', top); - this._multiDiffEditor?.setScrollState({ top }); - } - } + this._multiDiffEditor?.scrollTo(fileElement.edit.uri); return; } this._fileOperations = fileOperations; @@ -381,9 +368,6 @@ export class BulkEditPane extends ViewPane { originalUri: leftResource, modifiedUri: rightResource }); - - console.log('leftResource : ', JSON.stringify(leftResource)); - console.log('rightResource : ', JSON.stringify(rightResource)); } let typeLabel: string | undefined; @@ -403,21 +387,16 @@ export class BulkEditPane extends ViewPane { const multiDiffSource = URI.from({ scheme: 'refactor-preview', path: JSON.stringify(fileElement.edit.uri) }); const description = 'Refactor Preview'; - console.log('options : ', JSON.stringify(options)); - console.log('fileOperations : ', fileOperations); - console.log('fileElement.edit ; ', fileElement.edit); - console.log('multiDiffSource : ', multiDiffSource); - console.log('resources : ', resources); - console.log('label : ', label); - console.log('description : ', description); - - this._multiDiffEditor = await this._editorService.openEditor({ + console.log('before open editor'); + this._multiDiffEditor = this._disposables.add(await this._editorService.openEditor({ multiDiffSource: multiDiffSource ? URI.revive(multiDiffSource) : undefined, resources: resources?.map(r => ({ original: { resource: URI.revive(r.originalUri) }, modified: { resource: URI.revive(r.modifiedUri) } })), label: label, description: description, options: options, - }) as MultiDiffEditor; + }) as MultiDiffEditor); + + } private _onContextMenu(e: ITreeContextMenuEvent): void { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 9361ee7e0ef..21b1d64a6fa 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -24,7 +24,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { URI } from 'vs/base/common/uri'; import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; -import { IMultiDiffEditorViewState, VirtualizedViewItem } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorViewState } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; @@ -72,16 +72,8 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { From 7932adc68cff4093d79068890beda2aec0f33501 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 12 Feb 2024 15:56:02 +0100 Subject: [PATCH 1130/1897] api - remove old ChatAgentFileTree and ChatAgentFileTreeData (#205002) --- .../workbench/api/common/extHostTypeConverters.ts | 4 ---- .../vscode.proposed.chatAgents2Additions.d.ts | 15 --------------- 2 files changed, 19 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index fda37eef309..f41b939a927 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2536,8 +2536,6 @@ export namespace ChatResponseProgress { } else if ('agentName' in progress) { checkProposedApiEnabled(extension, 'chatAgents2Additions'); return { agentName: progress.agentName, command: progress.command, kind: 'agentDetection' }; - } else if ('treeData' in progress) { - return { treeData: progress.treeData, kind: 'treeData' }; } else if ('message' in progress) { return { content: MarkdownString.from(progress.message), kind: 'progressMessage' }; } else { @@ -2588,8 +2586,6 @@ export namespace ChatResponseProgress { Location.to(progress.inlineReference), title: progress.name }; - case 'treeData': - return { treeData: revive(progress.treeData) }; case 'command': // If the command isn't in the converter, then this session may have been restored, and the command args don't exist anymore return { diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index 22bf5f7dbd8..712d31b296b 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -45,7 +45,6 @@ declare module 'vscode' { */ export type ChatAgentContentProgress = | ChatAgentContent - | ChatAgentFileTree | ChatAgentInlineContentReference | ChatAgentCommandButton; @@ -108,20 +107,6 @@ declare module 'vscode' { content: string; } - /** @deprecated */ - export interface ChatAgentFileTree { - treeData: ChatAgentFileTreeData; - } - - /** @deprecated */ - export interface ChatAgentFileTreeData { - label: string; - uri: Uri; - type?: FileType; - children?: ChatAgentFileTreeData[]; - } - - export interface ChatAgentDocumentContext { uri: Uri; version: number; From b3fc17d24a46428d665a03e34a0d95294dc27ddc Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 12 Feb 2024 15:34:40 +0100 Subject: [PATCH 1131/1897] update vscode-distro hash --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d27c2f3b8fc..488e3cd2c58 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "d944d3f8f3d96c19da85e25c45e65317f8d795ac", + "distro": "cee7e51a2bc638aa4fe4bc62668aec6d46ac94dc", "author": { "name": "Microsoft Corporation" }, From 9cc555d28b959627b0160cfaac09264bbcf9ea77 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 12 Feb 2024 16:02:20 +0100 Subject: [PATCH 1132/1897] renaming method --- .../inlineCompletions/browser/inlineCompletionsModel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index e385a0d3c65..0b8bc138bf3 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -477,7 +477,7 @@ export function getSecondaryEdits(textModel: ITextModel, positions: readonly Pos Range.fromPositions(primaryPosition, primaryEditEndPosition) ); const positionWithinTextEdit = subtractPositions(primaryPosition, primaryEdit.range.getStartPosition()); - const secondaryEditText = getTextFromPosition(primaryEdit.text, positionWithinTextEdit); + const secondaryEditText = substringPos(primaryEdit.text, positionWithinTextEdit); return secondaryPositions.map(pos => { const textAfterSecondaryCursor = textModel.getValueInRange( Range.fromPositions(pos, primaryEditEndPosition) @@ -488,7 +488,7 @@ export function getSecondaryEdits(textModel: ITextModel, positions: readonly Pos }); } -function getTextFromPosition(text: string, pos: Position): string { +function substringPos(text: string, pos: Position): string { let subtext = ''; const lines = splitLinesIncludeSeparators(text); for (let i = pos.lineNumber - 1; i < lines.length; i++) { From 258e22ea12e795cc101df2c006b452a83b0b1d0b Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 12 Feb 2024 09:59:47 -0600 Subject: [PATCH 1133/1897] audio cue setting migration (#203827) --- .../audioCues/browser/audioCueService.ts | 73 ++- .../browser/accessibilityConfiguration.ts | 567 +++++++++++++++--- .../browser/audioCues.contribution.ts | 61 +- .../contrib/audioCues/browser/commands.ts | 35 +- .../preferences/browser/settingsLayout.ts | 5 + 5 files changed, 588 insertions(+), 153 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 0e35e7f9e3c..7c7dfbe0567 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -178,9 +178,9 @@ export class AudioCueService extends Disposable implements IAudioCueService { private readonly isCueEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.settingsKey) + e.affectsConfiguration(event.cue.settingsKey) || e.affectsConfiguration(event.cue.signalSettingsKey) ), - () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.cue.settingsKey) + () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.cue.signalSettingsKey + '.audioCue') ); return derived(reader => { /** @description audio cue enabled */ @@ -209,9 +209,9 @@ export class AudioCueService extends Disposable implements IAudioCueService { private readonly isAlertEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.alertSettingsKey!) + e.affectsConfiguration(event.cue.alertSettingsKey!) || e.affectsConfiguration(event.cue.signalSettingsKey) ), - () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.alertSettingsKey) : false + () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.signalSettingsKey + '.alert') : false ); return derived(reader => { /** @description alert enabled */ @@ -368,11 +368,12 @@ export class AudioCue { randomOneOf: Sound[]; }; settingsKey: string; + signalSettingsKey: string; alertSettingsKey?: AccessibilityAlertSettingId; alertMessage?: string; }): AudioCue { const soundSource = new SoundSource('randomOneOf' in options.sound ? options.sound.randomOneOf : [options.sound]); - const audioCue = new AudioCue(soundSource, options.name, options.settingsKey, options.alertSettingsKey, options.alertMessage); + const audioCue = new AudioCue(soundSource, options.name, options.settingsKey, options.signalSettingsKey, options.alertSettingsKey, options.alertMessage); AudioCue._audioCues.add(audioCue); return audioCue; } @@ -386,33 +387,38 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.lineHasError', alertSettingsKey: AccessibilityAlertSettingId.Error, - alertMessage: localize('audioCues.lineHasError.alertMessage', 'Error') + alertMessage: localize('accessibility.signals.lineHasError', 'Error'), + signalSettingsKey: 'accessibility.signals.lineHasError' }); public static readonly warning = AudioCue.register({ name: localize('audioCues.lineHasWarning.name', 'Warning on Line'), sound: Sound.warning, settingsKey: 'audioCues.lineHasWarning', alertSettingsKey: AccessibilityAlertSettingId.Warning, - alertMessage: localize('audioCues.lineHasWarning.alertMessage', 'Warning') + alertMessage: localize('accessibility.signals.lineHasWarning', 'Warning'), + signalSettingsKey: 'accessibility.signals.lineHasWarning' }); public static readonly foldedArea = AudioCue.register({ name: localize('audioCues.lineHasFoldedArea.name', 'Folded Area on Line'), sound: Sound.foldedArea, settingsKey: 'audioCues.lineHasFoldedArea', alertSettingsKey: AccessibilityAlertSettingId.FoldedArea, - alertMessage: localize('audioCues.lineHasFoldedArea.alertMessage', 'Folded') + alertMessage: localize('accessibility.signals.lineHasFoldedArea', 'Folded'), + signalSettingsKey: 'accessibility.signals.lineHasFoldedArea' }); public static readonly break = AudioCue.register({ name: localize('audioCues.lineHasBreakpoint.name', 'Breakpoint on Line'), sound: Sound.break, settingsKey: 'audioCues.lineHasBreakpoint', alertSettingsKey: AccessibilityAlertSettingId.Breakpoint, - alertMessage: localize('audioCues.lineHasBreakpoint.alertMessage', 'Breakpoint') + alertMessage: localize('accessibility.signals.lineHasBreakpoint', 'Breakpoint'), + signalSettingsKey: 'accessibility.signals.lineHasBreakpoint' }); public static readonly inlineSuggestion = AudioCue.register({ name: localize('audioCues.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), sound: Sound.quickFixes, settingsKey: 'audioCues.lineHasInlineSuggestion', + signalSettingsKey: 'accessibility.signals.lineHasInlineSuggestion' }); public static readonly terminalQuickFix = AudioCue.register({ @@ -420,7 +426,8 @@ export class AudioCue { sound: Sound.quickFixes, settingsKey: 'audioCues.terminalQuickFix', alertSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, - alertMessage: localize('audioCues.terminalQuickFix.alertMessage', 'Quick Fix') + alertMessage: localize('accessibility.signals.terminalQuickFix', 'Quick Fix'), + signalSettingsKey: 'accessibility.signals.terminalQuickFix' }); public static readonly onDebugBreak = AudioCue.register({ @@ -428,7 +435,8 @@ export class AudioCue { sound: Sound.break, settingsKey: 'audioCues.onDebugBreak', alertSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, - alertMessage: localize('audioCues.onDebugBreak.alertMessage', 'Breakpoint') + alertMessage: localize('accessibility.signals.onDebugBreak', 'Breakpoint'), + signalSettingsKey: 'accessibility.signals.onDebugBreak' }); public static readonly noInlayHints = AudioCue.register({ @@ -436,7 +444,8 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.noInlayHints', alertSettingsKey: AccessibilityAlertSettingId.NoInlayHints, - alertMessage: localize('audioCues.noInlayHints.alertMessage', 'No Inlay Hints') + alertMessage: localize('accessibility.signals.noInlayHints', 'No Inlay Hints'), + signalSettingsKey: 'accessibility.signals.noInlayHints' }); public static readonly taskCompleted = AudioCue.register({ @@ -444,7 +453,8 @@ export class AudioCue { sound: Sound.taskCompleted, settingsKey: 'audioCues.taskCompleted', alertSettingsKey: AccessibilityAlertSettingId.TaskCompleted, - alertMessage: localize('audioCues.taskCompleted.alertMessage', 'Task Completed') + alertMessage: localize('accessibility.signals.taskCompleted', 'Task Completed'), + signalSettingsKey: 'accessibility.signals.taskCompleted' }); public static readonly taskFailed = AudioCue.register({ @@ -452,7 +462,8 @@ export class AudioCue { sound: Sound.taskFailed, settingsKey: 'audioCues.taskFailed', alertSettingsKey: AccessibilityAlertSettingId.TaskFailed, - alertMessage: localize('audioCues.taskFailed.alertMessage', 'Task Failed') + alertMessage: localize('accessibility.signals.taskFailed', 'Task Failed'), + signalSettingsKey: 'accessibility.signals.taskFailed' }); public static readonly terminalCommandFailed = AudioCue.register({ @@ -460,7 +471,8 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.terminalCommandFailed', alertSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, - alertMessage: localize('audioCues.terminalCommandFailed.alertMessage', 'Command Failed') + alertMessage: localize('accessibility.signals.terminalCommandFailed', 'Command Failed'), + signalSettingsKey: 'accessibility.signals.terminalCommandFailed' }); public static readonly terminalBell = AudioCue.register({ @@ -468,7 +480,8 @@ export class AudioCue { sound: Sound.terminalBell, settingsKey: 'audioCues.terminalBell', alertSettingsKey: AccessibilityAlertSettingId.TerminalBell, - alertMessage: localize('audioCues.terminalBell.alertMessage', 'Terminal Bell') + alertMessage: localize('accessibility.signals.terminalBell', 'Terminal Bell'), + signalSettingsKey: 'accessibility.signals.terminalBell' }); public static readonly notebookCellCompleted = AudioCue.register({ @@ -476,7 +489,8 @@ export class AudioCue { sound: Sound.taskCompleted, settingsKey: 'audioCues.notebookCellCompleted', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, - alertMessage: localize('audioCues.notebookCellCompleted.alertMessage', 'Notebook Cell Completed') + alertMessage: localize('accessibility.signals.notebookCellCompleted', 'Notebook Cell Completed'), + signalSettingsKey: 'accessibility.signals.notebookCellCompleted' }); public static readonly notebookCellFailed = AudioCue.register({ @@ -484,25 +498,29 @@ export class AudioCue { sound: Sound.taskFailed, settingsKey: 'audioCues.notebookCellFailed', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, - alertMessage: localize('audioCues.notebookCellFailed.alertMessage', 'Notebook Cell Failed') + alertMessage: localize('accessibility.signals.notebookCellFailed', 'Notebook Cell Failed'), + signalSettingsKey: 'accessibility.signals.notebookCellFailed' }); public static readonly diffLineInserted = AudioCue.register({ name: localize('audioCues.diffLineInserted', 'Diff Line Inserted'), sound: Sound.diffLineInserted, settingsKey: 'audioCues.diffLineInserted', + signalSettingsKey: 'accessibility.signals.diffLineInserted' }); public static readonly diffLineDeleted = AudioCue.register({ name: localize('audioCues.diffLineDeleted', 'Diff Line Deleted'), sound: Sound.diffLineDeleted, settingsKey: 'audioCues.diffLineDeleted', + signalSettingsKey: 'accessibility.signals.diffLineDeleted' }); public static readonly diffLineModified = AudioCue.register({ name: localize('audioCues.diffLineModified', 'Diff Line Modified'), sound: Sound.diffLineModified, settingsKey: 'audioCues.diffLineModified', + signalSettingsKey: 'accessibility.signals.diffLineModified' }); public static readonly chatRequestSent = AudioCue.register({ @@ -510,7 +528,8 @@ export class AudioCue { sound: Sound.chatRequestSent, settingsKey: 'audioCues.chatRequestSent', alertSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, - alertMessage: localize('audioCues.chatRequestSent.alertMessage', 'Chat Request Sent') + alertMessage: localize('accessibility.signals.chatRequestSent', 'Chat Request Sent'), + signalSettingsKey: 'accessibility.signals.chatRequestSent' }); public static readonly chatResponseReceived = AudioCue.register({ @@ -524,6 +543,7 @@ export class AudioCue { Sound.chatResponseReceived4 ] }, + signalSettingsKey: 'accessibility.signals.chatResponseReceived' }); public static readonly chatResponsePending = AudioCue.register({ @@ -531,7 +551,8 @@ export class AudioCue { sound: Sound.chatResponsePending, settingsKey: 'audioCues.chatResponsePending', alertSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, - alertMessage: localize('audioCues.chatResponsePending.alertMessage', 'Chat Response Pending') + alertMessage: localize('accessibility.signals.chatResponsePending', 'Chat Response Pending'), + signalSettingsKey: 'accessibility.signals.chatResponsePending' }); public static readonly clear = AudioCue.register({ @@ -539,7 +560,8 @@ export class AudioCue { sound: Sound.clear, settingsKey: 'audioCues.clear', alertSettingsKey: AccessibilityAlertSettingId.Clear, - alertMessage: localize('audioCues.clear.alertMessage', 'Clear') + alertMessage: localize('accessibility.signals.clear', 'Clear'), + signalSettingsKey: 'accessibility.signals.clear' }); public static readonly save = AudioCue.register({ @@ -547,7 +569,8 @@ export class AudioCue { sound: Sound.save, settingsKey: 'audioCues.save', alertSettingsKey: AccessibilityAlertSettingId.Save, - alertMessage: localize('audioCues.save.alertMessage', 'Save') + alertMessage: localize('accessibility.signals.save', 'Save'), + signalSettingsKey: 'accessibility.signals.save' }); public static readonly format = AudioCue.register({ @@ -555,14 +578,16 @@ export class AudioCue { sound: Sound.format, settingsKey: 'audioCues.format', alertSettingsKey: AccessibilityAlertSettingId.Format, - alertMessage: localize('audioCues.format.alertMessage', 'Format') + alertMessage: localize('accessibility.signals.format', 'Format'), + signalSettingsKey: 'accessibility.signals.format' }); private constructor( public readonly sound: SoundSource, public readonly name: string, public readonly settingsKey: string, + public readonly signalSettingsKey: string, public readonly alertSettingsKey?: string, - public readonly alertMessage?: string + public readonly alertMessage?: string, ) { } } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 4e5b465ff71..3f601b3b408 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -4,15 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; -import { AccessibilityAlertSettingId } from 'vs/platform/audioCues/browser/audioCueService'; +import { workbenchConfigurationNodeBase, Extensions as WorkbenchExtensions, IConfigurationMigrationRegistry, ConfigurationKeyValuePairs } from 'vs/workbench/common/configuration'; +import { AccessibilityAlertSettingId, AudioCue } from 'vs/platform/audioCues/browser/audioCueService'; import { ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Event } from 'vs/base/common/event'; +import { audioCueFeatureBase } from 'vs/workbench/contrib/audioCues/browser/audioCues.contribution'; export const accessibilityHelpIsShown = new RawContextKey('accessibilityHelpIsShown', false, true); export const accessibleViewIsShown = new RawContextKey('accessibleViewIsShown', false, true); @@ -70,11 +71,18 @@ export const enum AccessibleViewProviderId { Comments = 'comments' } -const baseProperty: object = { +const baseVerbosityProperty: IConfigurationPropertySchema = { type: 'boolean', default: true, tags: ['accessibility'] }; +const markdownDeprecationMessage = localize('accessibility.alert.deprecationMessage', "This setting is deprecated. Use the `signals` settings instead."); +const baseAlertProperty: IConfigurationPropertySchema = { + type: 'boolean', + default: true, + tags: ['accessibility'], + markdownDeprecationMessage +}; export const accessibilityConfigurationNodeBase = Object.freeze({ id: 'accessibility', @@ -82,183 +90,530 @@ export const accessibilityConfigurationNodeBase = Object.freeze(WorkbenchExtensions.ConfigurationMigration) + .registerConfigurationMigrations([{ + key: 'audioCues.debouncePositionChanges', + migrateFn: (value, accessor) => { + return [ + ['accessibility.signals.debouncePositionChanges', { value }], + ['audioCues.debouncePositionChangess', { value: undefined }] + ]; + } + }]); + +Registry.as(WorkbenchExtensions.ConfigurationMigration) + .registerConfigurationMigrations(AudioCue.allAudioCues.map(item => ({ + key: item.settingsKey, + migrateFn: (audioCue, accessor) => { + const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; + const alertSettingsKey = item.alertSettingsKey; + let alert: string | undefined; + if (alertSettingsKey) { + alert = accessor(alertSettingsKey) ?? undefined; + if (typeof alert !== 'string') { + alert = alert ? 'auto' : 'off'; + } + } + configurationKeyValuePairs.push([`${item.signalSettingsKey}`, { value: alert ? { alert, audioCue } : { audioCue } }]); + return configurationKeyValuePairs; + } + }))); diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index 1fced335827..9059db0b31f 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -20,7 +20,7 @@ registerSingleton(IAudioCueService, AudioCueService, InstantiationType.Delayed); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AudioCueLineFeatureContribution, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AudioCueLineDebuggerContribution, LifecyclePhase.Restored); -const audioCueFeatureBase: IConfigurationPropertySchema = { +export const audioCueFeatureBase: IConfigurationPropertySchema = { 'type': 'string', 'enum': ['auto', 'on', 'off'], 'default': 'auto', @@ -29,7 +29,12 @@ const audioCueFeatureBase: IConfigurationPropertySchema = { localize('audioCues.enabled.on', "Enable audio cue."), localize('audioCues.enabled.off', "Disable audio cue.") ], - tags: ['accessibility'] + tags: ['accessibility'], +}; +const markdownDeprecationMessage = localize('audioCues.enabled.deprecated', "This setting is deprecated. Use `signals` settings instead."); +const audioCueDeprecatedFeatureBase: IConfigurationPropertySchema = { + ...audioCueFeatureBase, + markdownDeprecationMessage }; Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ @@ -50,96 +55,97 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'description': localize('audioCues.debouncePositionChanges', "Whether or not position changes should be debounced"), 'type': 'boolean', 'default': false, - tags: ['accessibility'] + tags: ['accessibility'], + 'markdownDeprecationMessage': localize('audioCues.debouncePositionChangesDeprecated', 'This setting is deprecated, instead use the `signals.debouncePositionChanges` setting.') }, 'audioCues.lineHasBreakpoint': { 'description': localize('audioCues.lineHasBreakpoint', "Plays a sound when the active line has a breakpoint."), - ...audioCueFeatureBase + ...audioCueDeprecatedFeatureBase }, 'audioCues.lineHasInlineSuggestion': { 'description': localize('audioCues.lineHasInlineSuggestion', "Plays a sound when the active line has an inline suggestion."), - ...audioCueFeatureBase + ...audioCueDeprecatedFeatureBase }, 'audioCues.lineHasError': { 'description': localize('audioCues.lineHasError', "Plays a sound when the active line has an error."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.lineHasFoldedArea': { 'description': localize('audioCues.lineHasFoldedArea', "Plays a sound when the active line has a folded area that can be unfolded."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.lineHasWarning': { 'description': localize('audioCues.lineHasWarning', "Plays a sound when the active line has a warning."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'off', }, 'audioCues.onDebugBreak': { 'description': localize('audioCues.onDebugBreak', "Plays a sound when the debugger stopped on a breakpoint."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.noInlayHints': { 'description': localize('audioCues.noInlayHints', "Plays a sound when trying to read a line with inlay hints that has no inlay hints."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.taskCompleted': { 'description': localize('audioCues.taskCompleted', "Plays a sound when a task is completed."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.taskFailed': { 'description': localize('audioCues.taskFailed', "Plays a sound when a task fails (non-zero exit code)."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.terminalCommandFailed': { 'description': localize('audioCues.terminalCommandFailed', "Plays a sound when a terminal command fails (non-zero exit code)."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.terminalQuickFix': { 'description': localize('audioCues.terminalQuickFix', "Plays a sound when terminal Quick Fixes are available."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.terminalBell': { 'description': localize('audioCues.terminalBell', "Plays a sound when the terminal bell is ringing."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'on' }, 'audioCues.diffLineInserted': { 'description': localize('audioCues.diffLineInserted', "Plays a sound when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.diffLineDeleted': { 'description': localize('audioCues.diffLineDeleted', "Plays a sound when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.diffLineModified': { 'description': localize('audioCues.diffLineModified', "Plays a sound when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.notebookCellCompleted': { 'description': localize('audioCues.notebookCellCompleted', "Plays a sound when a notebook cell execution is successfully completed."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.notebookCellFailed': { 'description': localize('audioCues.notebookCellFailed', "Plays a sound when a notebook cell execution fails."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.chatRequestSent': { 'description': localize('audioCues.chatRequestSent', "Plays a sound when a chat request is made."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'off' }, 'audioCues.chatResponsePending': { 'description': localize('audioCues.chatResponsePending', "Plays a sound on loop while the response is pending."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'auto' }, 'audioCues.chatResponseReceived': { 'description': localize('audioCues.chatResponseReceived', "Plays a sound on loop while the response has been received."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'off' }, 'audioCues.clear': { 'description': localize('audioCues.clear', "Plays a sound when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'off' }, 'audioCues.save': { @@ -152,7 +158,8 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('audioCues.save.always', "Plays the audio cue whenever a file is saved, including auto save."), localize('audioCues.save.never', "Never plays the audio cue.") ], - tags: ['accessibility'] + tags: ['accessibility'], + markdownDeprecationMessage }, 'audioCues.format': { 'markdownDescription': localize('audioCues.format', "Plays a sound when a file or notebook is formatted. Also see {0}", '`#accessibility.alert.format#`'), @@ -164,10 +171,12 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('audioCues.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), localize('audioCues.format.never', "Never plays the audio cue.") ], - tags: ['accessibility'] + tags: ['accessibility'], + markdownDeprecationMessage }, }, }); registerAction2(ShowAudioCueHelp); registerAction2(ShowAccessibilityAlertHelp); + diff --git a/src/vs/workbench/contrib/audioCues/browser/commands.ts b/src/vs/workbench/contrib/audioCues/browser/commands.ts index dbaac656df6..624c67985db 100644 --- a/src/vs/workbench/contrib/audioCues/browser/commands.ts +++ b/src/vs/workbench/contrib/audioCues/browser/commands.ts @@ -31,7 +31,7 @@ export class ShowAudioCueHelp extends Action2 { const accessibilityService = accessor.get(IAccessibilityService); const userGestureCues = [AudioCue.save, AudioCue.format]; const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.map((cue, idx) => ({ - label: userGestureCues.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.settingsKey)})` : cue.name, + label: userGestureCues.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.signalSettingsKey + '.audioCue')})` : cue.name, audioCue: cue, buttons: userGestureCues.includes(cue) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), @@ -47,14 +47,22 @@ export class ShowAudioCueHelp extends Action2 { const disabledCues = AudioCue.allAudioCues.filter(cue => !enabledCues.includes(cue)); for (const cue of enabledCues) { if (!userGestureCues.includes(cue)) { - configurationService.updateValue(cue.settingsKey, accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'); + let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.signalSettingsKey); + audioCue = accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'; + if (alert) { + configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); + } else { + configurationService.updateValue(cue.signalSettingsKey, { audioCue }); + } } } for (const cue of disabledCues) { - if (userGestureCues.includes(cue)) { - configurationService.updateValue(cue.settingsKey, 'never'); + const alert = cue.alertMessage ? configurationService.getValue(cue.signalSettingsKey + '.alert') : undefined; + const audioCue = userGestureCues.includes(cue) ? 'never' : 'off'; + if (alert) { + configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); } else { - configurationService.updateValue(cue.settingsKey, 'off'); + configurationService.updateValue(cue.signalSettingsKey, { audioCue }); } } qp.hide(); @@ -83,9 +91,10 @@ export class ShowAccessibilityAlertHelp extends Action2 { const audioCueService = accessor.get(IAudioCueService); const quickInputService = accessor.get(IQuickInputService); const configurationService = accessor.get(IConfigurationService); + const accessibilityService = accessor.get(IAccessibilityService); const userGestureAlerts = [AudioCue.save, AudioCue.format]; const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.filter(c => c.alertSettingsKey).map((cue, idx) => ({ - label: userGestureAlerts.includes(cue) && cue.alertSettingsKey ? `${cue.name} (${configurationService.getValue(cue.alertSettingsKey)})` : cue.name, + label: userGestureAlerts.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.signalSettingsKey + '.alert')})` : cue.name, audioCue: cue, buttons: userGestureAlerts.includes(cue) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), @@ -101,15 +110,17 @@ export class ShowAccessibilityAlertHelp extends Action2 { const disabledAlerts = AudioCue.allAudioCues.filter(cue => !enabledAlerts.includes(cue)); for (const cue of enabledAlerts) { if (!userGestureAlerts.includes(cue)) { - configurationService.updateValue(cue.alertSettingsKey!, true); + let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.signalSettingsKey); + alert = cue.alertMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; + if (alert) { + configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); + } } } for (const cue of disabledAlerts) { - if (userGestureAlerts.includes(cue)) { - configurationService.updateValue(cue.alertSettingsKey!, 'never'); - } else { - configurationService.updateValue(cue.alertSettingsKey!, false); - } + const alert = userGestureAlerts.includes(cue) ? 'never' : 'off'; + const audioCue = configurationService.getValue(cue.signalSettingsKey + '.audioCue'); + configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); } qp.hide(); }); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index b4b225d7bc8..54ac57ce224 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -226,6 +226,11 @@ export const tocData: ITOCEntry = { label: localize('audioCues', 'Audio Cues'), settings: ['audioCues.*'] }, + { + id: 'features/accessibilitySignals', + label: localize('accessibility.signals', 'Accessibility Signals'), + settings: ['accessibility.signals.*'] + }, { id: 'features/mergeEditor', label: localize('mergeEditor', 'Merge Editor'), From c2df354c9fde197a2fade2bb342da7f427fd390d Mon Sep 17 00:00:00 2001 From: "ermin.zem" Date: Tue, 13 Feb 2024 00:01:52 +0800 Subject: [PATCH 1134/1897] chore: update vscode known variables (#204568) Co-authored-by: ermin.zem --- build/lib/stylelint/vscode-known-variables.json | 1 + 1 file changed, 1 insertion(+) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index f6d81111246..0411ea42d75 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -693,6 +693,7 @@ "--vscode-terminalOverviewRuler-findMatchForeground", "--vscode-terminalStickyScroll-background", "--vscode-terminalStickyScrollHover-background", + "--vscode-testing-coverage-lineHeight", "--vscode-testing-coverCountBadgeBackground", "--vscode-testing-coverCountBadgeForeground", "--vscode-testing-coveredBackground", From 4f727cb9852caf9bb037344216a8e36d2cc0be66 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 12 Feb 2024 17:29:32 +0100 Subject: [PATCH 1135/1897] polishing the code --- .../bulkEdit/browser/preview/bulkEditPane.ts | 100 ++++++++---------- 1 file changed, 43 insertions(+), 57 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index b072acb88e0..49cef9c8c8a 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -11,7 +11,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IThemeService } from 'vs/platform/theme/common/themeService'; import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { BulkEditPreviewProvider, BulkFileOperation, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; +import { BulkEditPreviewProvider, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { URI } from 'vs/base/common/uri'; @@ -23,7 +23,6 @@ import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/cont import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { ResourceLabels, IResourceLabelsContainer } from 'vs/workbench/browser/labels'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { basename } from 'vs/base/common/resources'; import { MenuId } from 'vs/platform/actions/common/actions'; import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -39,6 +38,7 @@ import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { MultiDiffEditor } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor'; +import { IResourceDiffEditorInput } from 'vs/workbench/common/editor'; const enum State { Data = 'data', @@ -70,7 +70,6 @@ export class BulkEditPane extends ViewPane { private _currentInput?: BulkFileOperations; private _currentProvider?: BulkEditPreviewProvider; private _multiDiffEditor?: MultiDiffEditor; - private _fileOperations?: BulkFileOperation[]; constructor( options: IViewletViewOptions, @@ -104,6 +103,7 @@ export class BulkEditPane extends ViewPane { override dispose(): void { this._tree.dispose(); this._disposables.dispose(); + this._multiDiffEditor = undefined; super.dispose(); } @@ -268,6 +268,7 @@ export class BulkEditPane extends ViewPane { this._dialogService.warn(message).finally(() => this._done(false)); } + // Going through here to discard discard() { this._done(false); } @@ -320,6 +321,11 @@ export class BulkEditPane extends ViewPane { private async _openElementInMultiDiffEditor(e: IOpenEvent): Promise { + const fileOperations = this._currentInput?.fileOperations; + if (!fileOperations) { + return; + } + const options: Mutable = { ...e.editorOptions }; let fileElement: FileElement; if (e.element instanceof TextEditElement) { @@ -335,68 +341,48 @@ export class BulkEditPane extends ViewPane { return; } - let bulkFileOperations: BulkFileOperations | undefined = undefined; - let currentParent = fileElement.parent; - while (true) { - if (currentParent instanceof BulkFileOperations) { - bulkFileOperations = currentParent; - break; - } - currentParent = currentParent.parent; - } - const fileOperations = bulkFileOperations.fileOperations; - - if (this._fileOperations === fileOperations) { - // Multi diff editor already open - this._multiDiffEditor?.scrollTo(fileElement.edit.uri); + if (this._multiDiffEditor) { + // Multi diff editor already visible + this._multiDiffEditor.scrollTo(fileElement.edit.uri); return; } - this._fileOperations = fileOperations; - const resources = []; + const resources: IResourceDiffEditorInput[] = []; for (const operation of fileOperations) { + const operationUri = operation.uri; + const previewUri = this._currentProvider!.asPreviewUri(operationUri); + // delete -> show single editor + if (operation.type & BulkFileOperationType.Delete) { + resources.push({ + original: { resource: undefined }, + modified: { resource: URI.revive(previewUri) } + }); - let leftResource: URI | undefined = operation.textEdits[0].textEdit.resource; - let rightResource: URI | undefined = undefined; - try { - (await this._textModelService.createModelReference(leftResource)).dispose(); - rightResource = this._currentProvider!.asPreviewUri(leftResource); - } catch { - leftResource = BulkEditPreviewProvider.emptyPreview; + } else { + // rename, create, edits -> show diff editr + let leftResource: URI | undefined; + try { + (await this._textModelService.createModelReference(operationUri)).dispose(); + leftResource = operationUri; + } catch { + leftResource = BulkEditPreviewProvider.emptyPreview; + } + resources.push({ + original: { resource: URI.revive(leftResource) }, + modified: { resource: URI.revive(previewUri) } + }); } - resources.push({ - originalUri: leftResource, - modifiedUri: rightResource - }); } - - let typeLabel: string | undefined; - if (fileElement.edit.type & BulkFileOperationType.Rename) { - typeLabel = localize('rename', "rename"); - } else if (fileElement.edit.type & BulkFileOperationType.Create) { - typeLabel = localize('create', "create"); - } - - let label: string; - if (typeLabel) { - label = localize('edt.title.2', "{0} ({1}, refactor preview)", basename(fileElement.edit.uri), typeLabel); - } else { - label = localize('edt.title.1', "{0} (refactor preview)", basename(fileElement.edit.uri)); - } - const multiDiffSource = URI.from({ scheme: 'refactor-preview', path: JSON.stringify(fileElement.edit.uri) }); - const description = 'Refactor Preview'; - - console.log('before open editor'); - this._multiDiffEditor = this._disposables.add(await this._editorService.openEditor({ - multiDiffSource: multiDiffSource ? URI.revive(multiDiffSource) : undefined, - resources: resources?.map(r => ({ original: { resource: URI.revive(r.originalUri) }, modified: { resource: URI.revive(r.modifiedUri) } })), - label: label, - description: description, - options: options, - }) as MultiDiffEditor); - - + const label = 'Refactor Preview'; + this._multiDiffEditor = this._sessionDisposables.add( + await this._editorService.openEditor({ + multiDiffSource: multiDiffSource ? URI.revive(multiDiffSource) : undefined, + resources, + label: label, + description: label, + options: options, + }) as MultiDiffEditor); } private _onContextMenu(e: ITreeContextMenuEvent): void { From 5ff3ac10849efdc3c54c9d2c66ceb584eb4ccc4b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 10:34:25 -0600 Subject: [PATCH 1136/1897] first pass of renaming --- build/lib/i18n.resources.json | 2 +- .../components/accessibleDiffViewer.ts | 8 +- .../widget/diffEditor/diffEditorWidget.ts | 16 +- .../widget/embeddedCodeEditorWidget.ts | 6 +- .../editor/contrib/format/browser/format.ts | 10 +- .../contrib/format/browser/formatActions.ts | 6 +- .../browser/inlineCompletionsController.ts | 6 +- .../browser/inlineCompletionsProvider.test.ts | 4 +- .../test/browser/suggestWidgetModel.test.ts | 4 +- .../browser/standaloneCodeEditor.ts | 6 +- .../standalone/browser/standaloneServices.ts | 20 +- .../browser/accessibilitySignalService.ts | 593 ++++++++++++++++++ .../browser/media/break.mp3 | Bin .../browser/media/chatRequestSent.mp3 | Bin .../browser/media/chatResponsePending.mp3 | Bin .../browser/media/chatResponseReceived1.mp3 | Bin .../browser/media/chatResponseReceived2.mp3 | Bin .../browser/media/chatResponseReceived3.mp3 | Bin .../browser/media/chatResponseReceived4.mp3 | Bin .../browser/media/clear.mp3 | Bin .../browser/media/diffLineDeleted.mp3 | Bin .../browser/media/diffLineInserted.mp3 | Bin .../browser/media/diffLineModified.mp3 | Bin .../browser/media/error.mp3 | Bin .../browser/media/foldedAreas.mp3 | Bin .../browser/media/format.mp3 | Bin .../browser/media/quickFixes.mp3 | Bin .../browser/media/save.mp3 | Bin .../browser/media/taskCompleted.mp3 | Bin .../browser/media/taskFailed.mp3 | Bin .../browser/media/terminalBell.mp3 | Bin .../browser/media/warning.mp3 | Bin .../audioCues/browser/audioCueService.ts | 593 ------------------ .../notifications/notificationsCenter.ts | 6 +- .../notifications/notificationsCommands.ts | 6 +- .../browser/accessibilityConfiguration.ts | 147 ++--- .../browser/accessibilityContributions.ts | 10 +- .../accessibility/browser/saveAudioCue.ts | 6 +- .../accessibilitySignal.contribution.ts} | 16 +- ...ccessibilitySignalDebuggerContribution.ts} | 12 +- ...ssibilitySignalLineFeatureContribution.ts} | 56 +- .../browser/commands.ts | 74 +-- .../chat/browser/actions/chatClearActions.ts | 4 +- .../chat/browser/chatAccessibilityService.ts | 12 +- .../workbench/contrib/debug/browser/repl.ts | 6 +- .../files/test/browser/editorAutoSave.test.ts | 4 +- .../browser/inlayHintsAccessibilty.ts | 6 +- .../notebookExecutionStateServiceImpl.ts | 8 +- .../output/browser/output.contribution.ts | 6 +- .../preferences/browser/settingsLayout.ts | 6 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 18 +- .../contrib/search/browser/searchView.ts | 6 +- .../tasks/browser/taskTerminalStatus.ts | 12 +- .../tasks/electron-sandbox/taskService.ts | 4 +- .../test/browser/taskTerminalStatus.test.ts | 12 +- .../terminal/browser/terminalInstance.ts | 6 +- .../terminal/browser/xterm/decorationAddon.ts | 6 +- .../terminal/browser/xterm/xtermTerminal.ts | 6 +- .../test/browser/bufferContentTracker.test.ts | 4 +- .../quickFix/browser/quickFixAddon.ts | 6 +- .../test/browser/workbenchTestServices.ts | 4 +- src/vs/workbench/workbench.common.main.ts | 2 +- 62 files changed, 873 insertions(+), 872 deletions(-) create mode 100644 src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/break.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/chatRequestSent.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/chatResponsePending.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/chatResponseReceived1.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/chatResponseReceived2.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/chatResponseReceived3.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/chatResponseReceived4.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/clear.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/diffLineDeleted.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/diffLineInserted.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/diffLineModified.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/error.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/foldedAreas.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/format.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/quickFixes.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/save.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/taskCompleted.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/taskFailed.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/terminalBell.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/warning.mp3 (100%) delete mode 100644 src/vs/platform/audioCues/browser/audioCueService.ts rename src/vs/workbench/contrib/{audioCues/browser/audioCues.contribution.ts => accessibilitySignals/browser/accessibilitySignal.contribution.ts} (90%) rename src/vs/workbench/contrib/{audioCues/browser/audioCueDebuggerContribution.ts => accessibilitySignals/browser/accessibilitySignalDebuggerContribution.ts} (77%) rename src/vs/workbench/contrib/{audioCues/browser/audioCueLineFeatureContribution.ts => accessibilitySignals/browser/accessibilitySignalLineFeatureContribution.ts} (77%) rename src/vs/workbench/contrib/{audioCues => accessibilitySignals}/browser/commands.ts (55%) diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index cf74aac44be..6b8f0738b7a 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -327,7 +327,7 @@ "project": "vscode-workbench" }, { - "name": "vs/workbench/contrib/audioCues", + "name": "vs/workbench/contrib/accessibilitySignals", "project": "vscode-workbench" }, { diff --git a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts index 786a8c26192..aa3d6da5402 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts @@ -30,7 +30,7 @@ import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; import { RenderLineInput, renderViewLine2 } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { ViewLineRenderingData } from 'vs/editor/common/viewModel'; import { localize } from 'vs/nls'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import 'vs/css!./accessibleDiffViewer'; @@ -109,7 +109,7 @@ class ViewModel extends Disposable { private readonly _editors: DiffEditorEditors, private readonly _setVisible: (visible: boolean, tx: ITransaction | undefined) => void, public readonly canClose: IObservable, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, ) { super(); @@ -143,9 +143,9 @@ class ViewModel extends Disposable { /** @description play audio-cue for diff */ const currentViewItem = this.currentElement.read(reader); if (currentViewItem?.type === LineType.Deleted) { - this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, { source: 'accessibleDiffViewer.currentElementChanged' }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineDeleted, { source: 'accessibleDiffViewer.currentElementChanged' }); } else if (currentViewItem?.type === LineType.Added) { - this._audioCueService.playAudioCue(AudioCue.diffLineInserted, { source: 'accessibleDiffViewer.currentElementChanged' }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineInserted, { source: 'accessibleDiffViewer.currentElementChanged' }); } })); diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index 3fd6e543a61..acf848c25c4 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -35,7 +35,7 @@ import { DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/ra import { EditorType, IDiffEditorModel, IDiffEditorViewModel, IDiffEditorViewState } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -97,7 +97,7 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { @IContextKeyService private readonly _parentContextKeyService: IContextKeyService, @IInstantiationService private readonly _parentInstantiationService: IInstantiationService, @ICodeEditorService codeEditorService: ICodeEditorService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @IEditorProgressService private readonly _editorProgressService: IEditorProgressService, ) { super(); @@ -271,11 +271,11 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { if (e?.reason === CursorChangeReason.Explicit) { const diff = this._diffModel.get()?.diff.get()?.mappings.find(m => m.lineRangeMapping.modified.contains(e.position.lineNumber)); if (diff?.lineRangeMapping.modified.isEmpty) { - this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, { source: 'diffEditor.cursorPositionChanged' }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineDeleted, { source: 'diffEditor.cursorPositionChanged' }); } else if (diff?.lineRangeMapping.original.isEmpty) { - this._audioCueService.playAudioCue(AudioCue.diffLineInserted, { source: 'diffEditor.cursorPositionChanged' }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineInserted, { source: 'diffEditor.cursorPositionChanged' }); } else if (diff) { - this._audioCueService.playAudioCue(AudioCue.diffLineModified, { source: 'diffEditor.cursorPositionChanged' }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineModified, { source: 'diffEditor.cursorPositionChanged' }); } } })); @@ -528,11 +528,11 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { this._goTo(diff); if (diff.lineRangeMapping.modified.isEmpty) { - this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, { source: 'diffEditor.goToDiff' }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineDeleted, { source: 'diffEditor.goToDiff' }); } else if (diff.lineRangeMapping.original.isEmpty) { - this._audioCueService.playAudioCue(AudioCue.diffLineInserted, { source: 'diffEditor.goToDiff' }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineInserted, { source: 'diffEditor.goToDiff' }); } else if (diff) { - this._audioCueService.playAudioCue(AudioCue.diffLineModified, { source: 'diffEditor.goToDiff' }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineModified, { source: 'diffEditor.goToDiff' }); } } diff --git a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts index 8d63ee6f754..4a5dffa5347 100644 --- a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts +++ b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts @@ -12,7 +12,7 @@ import { ConfigurationChangedEvent, IDiffEditorOptions, IEditorOptions } from 'v import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -79,10 +79,10 @@ export class EmbeddedDiffEditorWidget extends DiffEditorWidget { @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService instantiationService: IInstantiationService, @ICodeEditorService codeEditorService: ICodeEditorService, - @IAudioCueService audioCueService: IAudioCueService, + @IAccessibilitySignalService accessibilitySignalService: IAccessibilitySignalService, @IEditorProgressService editorProgressService: IEditorProgressService, ) { - super(domElement, parentEditor.getRawOptions(), codeEditorWidgetOptions, contextKeyService, instantiationService, codeEditorService, audioCueService, editorProgressService); + super(domElement, parentEditor.getRawOptions(), codeEditorWidgetOptions, contextKeyService, instantiationService, codeEditorService, accessibilitySignalService, editorProgressService); this._parentEditor = parentEditor; this._overwriteOptions = options; diff --git a/src/vs/editor/contrib/format/browser/format.ts b/src/vs/editor/contrib/format/browser/format.ts index 742518daae3..dbf6ede9eae 100644 --- a/src/vs/editor/contrib/format/browser/format.ts +++ b/src/vs/editor/contrib/format/browser/format.ts @@ -30,7 +30,7 @@ import { IProgress } from 'vs/platform/progress/common/progress'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; import { ILogService } from 'vs/platform/log/common/log'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; export function getRealAndSyntheticDocumentFormattersOrdered( documentFormattingEditProvider: LanguageFeatureRegistry, @@ -135,7 +135,7 @@ export async function formatDocumentRangesWithProvider( ): Promise { const workerService = accessor.get(IEditorWorkerService); const logService = accessor.get(ILogService); - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); let model: ITextModel; let cts: CancellationTokenSource; @@ -279,7 +279,7 @@ export async function formatDocumentRangesWithProvider( return null; }); } - audioCueService.playAudioCue(AudioCue.format, { userGesture }); + accessibilitySignalService.playSignal(AccessibilitySignal.format, { userGesture }); return true; } @@ -312,7 +312,7 @@ export async function formatDocumentWithProvider( userGesture?: boolean ): Promise { const workerService = accessor.get(IEditorWorkerService); - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); let model: ITextModel; let cts: CancellationTokenSource; @@ -373,7 +373,7 @@ export async function formatDocumentWithProvider( return null; }); } - audioCueService.playAudioCue(AudioCue.format, { userGesture }); + accessibilitySignalService.playSignal(AccessibilitySignal.format, { userGesture }); return true; } diff --git a/src/vs/editor/contrib/format/browser/formatActions.ts b/src/vs/editor/contrib/format/browser/formatActions.ts index a9c4984e4c1..1f345ac5329 100644 --- a/src/vs/editor/contrib/format/browser/formatActions.ts +++ b/src/vs/editor/contrib/format/browser/formatActions.ts @@ -21,7 +21,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { formatDocumentRangesWithSelectedProvider, formatDocumentWithSelectedProvider, FormattingMode, getOnTypeFormattingEdits } from 'vs/editor/contrib/format/browser/format'; import { FormattingEdit } from 'vs/editor/contrib/format/browser/formattingEdit'; import * as nls from 'vs/nls'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -40,7 +40,7 @@ export class FormatOnType implements IEditorContribution { private readonly _editor: ICodeEditor, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @IEditorWorkerService private readonly _workerService: IEditorWorkerService, - @IAudioCueService private readonly _audioCueService: IAudioCueService + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService ) { this._disposables.add(_languageFeaturesService.onTypeFormattingEditProvider.onDidChange(this._update, this)); this._disposables.add(_editor.onDidChangeModel(() => this._update())); @@ -143,7 +143,7 @@ export class FormatOnType implements IEditorContribution { return; } if (isNonEmptyArray(edits)) { - this._audioCueService.playAudioCue(AudioCue.format, { userGesture: false }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.format, { userGesture: false }); FormattingEdit.execute(this._editor, edits, true); } }).finally(() => { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts index 4f38487a6ff..aad71f482db 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts @@ -23,7 +23,7 @@ import { InlineCompletionsHintsWidget, InlineSuggestionHintsContentWidget } from import { InlineCompletionsModel, VersionIdChangeReason } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel'; import { SuggestWidgetAdaptor } from 'vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider'; import { localize } from 'vs/nls'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -81,7 +81,7 @@ export class InlineCompletionsController extends Disposable { @ICommandService private readonly _commandService: ICommandService, @ILanguageFeatureDebounceService private readonly _debounceService: ILanguageFeatureDebounceService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @IKeybindingService private readonly _keybindingService: IKeybindingService, ) { super(); @@ -221,7 +221,7 @@ export class InlineCompletionsController extends Disposable { if (state.inlineCompletion.semanticId !== lastInlineCompletionId) { lastInlineCompletionId = state.inlineCompletion.semanticId; const lineText = model.textModel.getLineContent(state.ghostText.lineNumber); - this._audioCueService.playAudioCue(AudioCue.inlineSuggestion).then(() => { + this._accessibilitySignalService.playSignal(AccessibilitySignal.inlineSuggestion).then(() => { if (this.editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) { this.provideScreenReaderUpdate(state.ghostText.renderForScreenReader(lineText)); } diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts index 20dd10e2d67..aa5c7a4e4a6 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts @@ -19,7 +19,7 @@ import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/sing import { GhostTextContext, MockInlineCompletionsProvider } from 'vs/editor/contrib/inlineCompletions/test/browser/utils'; import { ITestCodeEditor, TestCodeEditorInstantiationOptions, withAsyncTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; -import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { Selection } from 'vs/editor/common/core/selection'; @@ -775,7 +775,7 @@ async function withAsyncTestCodeEditorAndInlineCompletionsModel( options.serviceCollection = new ServiceCollection(); } options.serviceCollection.set(ILanguageFeaturesService, languageFeaturesService); - options.serviceCollection.set(IAudioCueService, { + options.serviceCollection.set(IAccessibilitySignalService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; }, } as any); diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts index 06cc564f3d9..969ab2fec42 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts @@ -34,7 +34,7 @@ import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/brow import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; import { autorun } from 'vs/base/common/observable'; import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; -import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Suggest Widget Model', () => { @@ -160,7 +160,7 @@ async function withAsyncTestCodeEditorAndInlineCompletionsModel( }], [ILabelService, new class extends mock() { }], [IWorkspaceContextService, new class extends mock() { }], - [IAudioCueService, { + [IAccessibilitySignalService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; }, } as any] diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 60c16599bf3..739e76f97c2 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -37,7 +37,7 @@ import { ILanguageConfigurationService } from 'vs/editor/common/languages/langua import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; -import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { mainWindow } from 'vs/base/browser/window'; /** @@ -499,7 +499,7 @@ export class StandaloneDiffEditor2 extends DiffEditorWidget implements IStandalo @IContextMenuService contextMenuService: IContextMenuService, @IEditorProgressService editorProgressService: IEditorProgressService, @IClipboardService clipboardService: IClipboardService, - @IAudioCueService audioCueService: IAudioCueService, + @IAccessibilitySignalService accessibilitySignalService: IAccessibilitySignalService, ) { const options = { ..._options }; updateConfigurationService(configurationService, options, true); @@ -518,7 +518,7 @@ export class StandaloneDiffEditor2 extends DiffEditorWidget implements IStandalo contextKeyService, instantiationService, codeEditorService, - audioCueService, + accessibilitySignalService, editorProgressService, ); diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 97841a6012b..351f85537d8 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -88,7 +88,7 @@ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; import { DefaultConfiguration } from 'vs/platform/configuration/common/configurations'; import { WorkspaceEdit } from 'vs/editor/common/languages'; -import { AudioCue, IAudioCueService, Sound } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService, Sound } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { LogService } from 'vs/platform/log/common/logService'; import { getEditorFeatures } from 'vs/editor/common/editorFeatures'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -1057,33 +1057,33 @@ class StandaloneContextMenuService extends ContextMenuService { } } -class StandaloneAudioService implements IAudioCueService { +class StandaloneAccessbilitySignalService implements IAccessibilitySignalService { _serviceBrand: undefined; - async playAudioCue(cue: AudioCue, options: {}): Promise { + async playSignal(cue: AccessibilitySignal, options: {}): Promise { } - async playAudioCues(cues: AudioCue[]): Promise { + async playAccessibilitySignals(cues: AccessibilitySignal[]): Promise { } - isCueEnabled(cue: AudioCue): boolean { + isSoundEnabled(cue: AccessibilitySignal): boolean { return false; } - isAlertEnabled(cue: AudioCue): boolean { + isAnnouncementEnabled(cue: AccessibilitySignal): boolean { return false; } - onEnabledChanged(cue: AudioCue): Event { + onSoundEnabledChanged(cue: AccessibilitySignal): Event { return Event.None; } - onAlertEnabledChanged(cue: AudioCue): Event { + onAnnouncementEnabledChanged(cue: AccessibilitySignal): Event { return Event.None; } async playSound(cue: Sound, allowManyInParallel?: boolean | undefined): Promise { } - playAudioCueLoop(cue: AudioCue): IDisposable { + playSignalLoop(cue: AccessibilitySignal): IDisposable { return toDisposable(() => { }); } } @@ -1125,7 +1125,7 @@ registerSingleton(IOpenerService, OpenerService, InstantiationType.Eager); registerSingleton(IClipboardService, BrowserClipboardService, InstantiationType.Eager); registerSingleton(IContextMenuService, StandaloneContextMenuService, InstantiationType.Eager); registerSingleton(IMenuService, MenuService, InstantiationType.Eager); -registerSingleton(IAudioCueService, StandaloneAudioService, InstantiationType.Eager); +registerSingleton(IAccessibilitySignalService, StandaloneAccessbilitySignalService, InstantiationType.Eager); /** * We don't want to eagerly instantiate services because embedders get a one time chance diff --git a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts new file mode 100644 index 00000000000..9bbbf0b1db9 --- /dev/null +++ b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts @@ -0,0 +1,593 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { FileAccess } from 'vs/base/common/network'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Event } from 'vs/base/common/event'; +import { localize } from 'vs/nls'; +import { observableFromEvent, derived } from 'vs/base/common/observable'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; + +export const IAccessibilitySignalService = createDecorator('accessibilitySignalService'); + +export interface IAccessibilitySignalService { + readonly _serviceBrand: undefined; + playSignal(cue: AccessibilitySignal, options?: IAccessbilitySignalOptions): Promise; + playAccessibilitySignals(cues: (AccessibilitySignal | { cue: AccessibilitySignal; source: string })[]): Promise; + isSoundEnabled(cue: AccessibilitySignal): boolean; + isAnnouncementEnabled(cue: AccessibilitySignal): boolean; + onSoundEnabledChanged(cue: AccessibilitySignal): Event; + onAnnouncementEnabledChanged(cue: AccessibilitySignal): Event; + + playSound(cue: Sound, allowManyInParallel?: boolean): Promise; + playSignalLoop(cue: AccessibilitySignal, milliseconds: number): IDisposable; +} + +export interface IAccessbilitySignalOptions { + allowManyInParallel?: boolean; + source?: string; + /** + * For actions like save or format, depending on the + * configured value, we will only + * play the sound if the user triggered the action. + */ + userGesture?: boolean; +} + +export class AccessibilitySignalService extends Disposable implements IAccessibilitySignalService { + readonly _serviceBrand: undefined; + private readonly sounds: Map = new Map(); + private readonly screenReaderAttached = observableFromEvent( + this.accessibilityService.onDidChangeScreenReaderOptimized, + () => /** @description accessibilityService.onDidChangeScreenReaderOptimized */ this.accessibilityService.isScreenReaderOptimized() + ); + private readonly sentTelemetry = new Set(); + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + ) { + super(); + } + + public async playSignal(signal: AccessibilitySignal, options: IAccessbilitySignalOptions = {}): Promise { + const alertMessage = signal.announcementMessage; + if (this.isAnnouncementEnabled(signal, options.userGesture) && alertMessage) { + this.accessibilityService.status(alertMessage); + } + + if (this.isSoundEnabled(signal, options.userGesture)) { + this.sendAudioCueTelemetry(signal, options.source); + await this.playSound(signal.sound.getSound(), options.allowManyInParallel); + } + } + + public async playAccessibilitySignals(cues: (AccessibilitySignal | { cue: AccessibilitySignal; source: string })[]): Promise { + for (const cue of cues) { + this.sendAudioCueTelemetry('cue' in cue ? cue.cue : cue, 'source' in cue ? cue.source : undefined); + } + const cueArray = cues.map(c => 'cue' in c ? c.cue : c); + const alerts = cueArray.filter(cue => this.isAnnouncementEnabled(cue)).map(c => c.announcementMessage); + if (alerts.length) { + this.accessibilityService.status(alerts.join(', ')); + } + + // Some audio cues might reuse sounds. Don't play the same sound twice. + const sounds = new Set(cueArray.filter(cue => this.isSoundEnabled(cue)).map(cue => cue.sound.getSound())); + await Promise.all(Array.from(sounds).map(sound => this.playSound(sound, true))); + + } + + + private sendAudioCueTelemetry(cue: AccessibilitySignal, source: string | undefined): void { + const isScreenReaderOptimized = this.accessibilityService.isScreenReaderOptimized(); + const key = cue.name + (source ? `::${source}` : '') + (isScreenReaderOptimized ? '{screenReaderOptimized}' : ''); + // Only send once per user session + if (this.sentTelemetry.has(key) || this.getVolumeInPercent() === 0) { + return; + } + this.sentTelemetry.add(key); + + this.telemetryService.publicLog2<{ + audioCue: string; + source: string; + isScreenReaderOptimized: boolean; + }, { + owner: 'hediet'; + + audioCue: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The audio cue that was played.' }; + source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source that triggered the audio cue (e.g. "diffEditorNavigation").' }; + isScreenReaderOptimized: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user is using a screen reader' }; + + comment: 'This data is collected to understand how audio cues are used and if more audio cues should be added.'; + }>('audioCue.played', { + audioCue: cue.name, + source: source ?? '', + isScreenReaderOptimized, + }); + } + + private getVolumeInPercent(): number { + const volume = this.configurationService.getValue('accessibilitySignals.volume'); + if (typeof volume !== 'number') { + return 50; + } + + return Math.max(Math.min(volume, 100), 0); + } + + private readonly playingSounds = new Set(); + + public async playSound(sound: Sound, allowManyInParallel = false): Promise { + if (!allowManyInParallel && this.playingSounds.has(sound)) { + return; + } + this.playingSounds.add(sound); + const url = FileAccess.asBrowserUri(`vs/platform/accessibilitySignals/browser/media/${sound.fileName}`).toString(true); + + try { + const sound = this.sounds.get(url); + if (sound) { + sound.volume = this.getVolumeInPercent() / 100; + sound.currentTime = 0; + await sound.play(); + } else { + const playedSound = await playAudio(url, this.getVolumeInPercent() / 100); + this.sounds.set(url, playedSound); + } + } catch (e) { + if (!e.message.includes('play() can only be initiated by a user gesture')) { + // tracking this issue in #178642, no need to spam the console + console.error('Error while playing sound', e); + } + } finally { + this.playingSounds.delete(sound); + } + } + + public playSignalLoop(signal: AccessibilitySignal, milliseconds: number): IDisposable { + let playing = true; + const playSound = () => { + if (playing) { + this.playSignal(signal, { allowManyInParallel: true }).finally(() => { + setTimeout(() => { + if (playing) { + playSound(); + } + }, milliseconds); + }); + } + }; + playSound(); + return toDisposable(() => playing = false); + } + + private readonly obsoleteAccessibilitySignalsEnabled = observableFromEvent( + Event.filter(this.configurationService.onDidChangeConfiguration, (e) => + e.affectsConfiguration('accessibilitySignals.enabled') + ), + () => /** @description config: accessibilitySignals.enabled */ this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>('accessibilitySignals.enabled') + ); + + private readonly isSoundEnabledCache = new Cache((event: { readonly signal: AccessibilitySignal; readonly userGesture?: boolean }) => { + const settingObservable = observableFromEvent( + Event.filter(this.configurationService.onDidChangeConfiguration, (e) => + e.affectsConfiguration(event.signal.settingsKey) || e.affectsConfiguration(event.signal.signalSettingsKey) + ), + () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.signal.signalSettingsKey + '.audioCue') + ); + return derived(reader => { + /** @description audio cue enabled */ + const setting = settingObservable.read(reader); + if ( + setting === 'on' || + (setting === 'auto' && this.screenReaderAttached.read(reader)) + ) { + return true; + } else if (setting === 'always' || setting === 'userGesture' && event.userGesture) { + return true; + } + + const obsoleteSetting = this.obsoleteAccessibilitySignalsEnabled.read(reader); + if ( + obsoleteSetting === 'on' || + (obsoleteSetting === 'auto' && this.screenReaderAttached.read(reader)) + ) { + return true; + } + + return false; + }); + }, JSON.stringify); + + private readonly isAnnouncementEnabledCache = new Cache((event: { readonly signal: AccessibilitySignal; readonly userGesture?: boolean }) => { + const settingObservable = observableFromEvent( + Event.filter(this.configurationService.onDidChangeConfiguration, (e) => + e.affectsConfiguration(event.signal.alertSettingsKey!) || e.affectsConfiguration(event.signal.signalSettingsKey) + ), + () => event.signal.alertSettingsKey ? this.configurationService.getValue(event.signal.signalSettingsKey + '.alert') : false + ); + return derived(reader => { + /** @description alert enabled */ + const setting = settingObservable.read(reader); + if ( + !this.screenReaderAttached.read(reader) + ) { + return false; + } + return setting === true || setting === 'always' || setting === 'userGesture' && event.userGesture; + }); + }, JSON.stringify); + + public isAnnouncementEnabled(cue: AccessibilitySignal, userGesture?: boolean): boolean { + if (!cue.alertSettingsKey) { + return false; + } + return this.isAnnouncementEnabledCache.get({ signal: cue, userGesture }).get() ?? false; + } + + public isSoundEnabled(cue: AccessibilitySignal, userGesture?: boolean): boolean { + return this.isSoundEnabledCache.get({ signal: cue, userGesture }).get() ?? false; + } + + public onSoundEnabledChanged(cue: AccessibilitySignal): Event { + return Event.fromObservableLight(this.isSoundEnabledCache.get({ signal: cue })); + } + + public onAnnouncementEnabledChanged(cue: AccessibilitySignal): Event { + return Event.fromObservableLight(this.isAnnouncementEnabledCache.get({ signal: cue })); + } +} + + +/** + * Play the given audio url. + * @volume value between 0 and 1 + */ +function playAudio(url: string, volume: number): Promise { + return new Promise((resolve, reject) => { + const audio = new Audio(url); + audio.volume = volume; + audio.addEventListener('ended', () => { + resolve(audio); + }); + audio.addEventListener('error', (e) => { + // When the error event fires, ended might not be called + reject(e.error); + }); + audio.play().catch(e => { + // When play fails, the error event is not fired. + reject(e); + }); + }); +} + +class Cache { + private readonly map = new Map(); + constructor(private readonly getValue: (value: TArg) => TValue, private readonly getKey: (value: TArg) => unknown) { + } + + public get(arg: TArg): TValue { + if (this.map.has(arg)) { + return this.map.get(arg)!; + } + + const value = this.getValue(arg); + const key = this.getKey(arg); + this.map.set(key, value); + return value; + } +} + +/** + * Corresponds to the audio files in ./media. +*/ +export class Sound { + private static register(options: { fileName: string }): Sound { + const sound = new Sound(options.fileName); + return sound; + } + + public static readonly error = Sound.register({ fileName: 'error.mp3' }); + public static readonly warning = Sound.register({ fileName: 'warning.mp3' }); + public static readonly foldedArea = Sound.register({ fileName: 'foldedAreas.mp3' }); + public static readonly break = Sound.register({ fileName: 'break.mp3' }); + public static readonly quickFixes = Sound.register({ fileName: 'quickFixes.mp3' }); + public static readonly taskCompleted = Sound.register({ fileName: 'taskCompleted.mp3' }); + public static readonly taskFailed = Sound.register({ fileName: 'taskFailed.mp3' }); + public static readonly terminalBell = Sound.register({ fileName: 'terminalBell.mp3' }); + public static readonly diffLineInserted = Sound.register({ fileName: 'diffLineInserted.mp3' }); + public static readonly diffLineDeleted = Sound.register({ fileName: 'diffLineDeleted.mp3' }); + public static readonly diffLineModified = Sound.register({ fileName: 'diffLineModified.mp3' }); + public static readonly chatRequestSent = Sound.register({ fileName: 'chatRequestSent.mp3' }); + public static readonly chatResponsePending = Sound.register({ fileName: 'chatResponsePending.mp3' }); + public static readonly chatResponseReceived1 = Sound.register({ fileName: 'chatResponseReceived1.mp3' }); + public static readonly chatResponseReceived2 = Sound.register({ fileName: 'chatResponseReceived2.mp3' }); + public static readonly chatResponseReceived3 = Sound.register({ fileName: 'chatResponseReceived3.mp3' }); + public static readonly chatResponseReceived4 = Sound.register({ fileName: 'chatResponseReceived4.mp3' }); + public static readonly clear = Sound.register({ fileName: 'clear.mp3' }); + public static readonly save = Sound.register({ fileName: 'save.mp3' }); + public static readonly format = Sound.register({ fileName: 'format.mp3' }); + + private constructor(public readonly fileName: string) { } +} + +export class SoundSource { + constructor( + public readonly randomOneOf: Sound[] + ) { } + + public getSound(deterministic = false): Sound { + if (deterministic || this.randomOneOf.length === 1) { + return this.randomOneOf[0]; + } else { + const index = Math.floor(Math.random() * this.randomOneOf.length); + return this.randomOneOf[index]; + } + } +} + +export const enum AccessibilityAlertSettingId { + Save = 'accessibility.alert.save', + Format = 'accessibility.alert.format', + Clear = 'accessibility.alert.clear', + Breakpoint = 'accessibility.alert.breakpoint', + Error = 'accessibility.alert.error', + Warning = 'accessibility.alert.warning', + FoldedArea = 'accessibility.alert.foldedArea', + TerminalQuickFix = 'accessibility.alert.terminalQuickFix', + TerminalBell = 'accessibility.alert.terminalBell', + TerminalCommandFailed = 'accessibility.alert.terminalCommandFailed', + TaskCompleted = 'accessibility.alert.taskCompleted', + TaskFailed = 'accessibility.alert.taskFailed', + ChatRequestSent = 'accessibility.alert.chatRequestSent', + NotebookCellCompleted = 'accessibility.alert.notebookCellCompleted', + NotebookCellFailed = 'accessibility.alert.notebookCellFailed', + OnDebugBreak = 'accessibility.alert.onDebugBreak', + NoInlayHints = 'accessibility.alert.noInlayHints', + LineHasBreakpoint = 'accessibility.alert.lineHasBreakpoint', + ChatResponsePending = 'accessibility.alert.chatResponsePending' +} + + +export class AccessibilitySignal { + private static _signals = new Set(); + private static register(options: { + name: string; + sound: Sound | { + /** + * Gaming and other apps often play a sound variant when the same event happens again + * for an improved experience. This option enables audio cues to play a random sound. + */ + randomOneOf: Sound[]; + }; + legacyAudioCueSettingsKey: string; + settingsKey: string; + legacyAnnouncementSettingsKey?: AccessibilityAlertSettingId; + announcementMessage?: string; + }): AccessibilitySignal { + const soundSource = new SoundSource('randomOneOf' in options.sound ? options.sound.randomOneOf : [options.sound]); + const signal = new AccessibilitySignal(soundSource, options.name, options.legacyAudioCueSettingsKey, options.settingsKey, options.legacyAnnouncementSettingsKey, options.announcementMessage); + AccessibilitySignal._signals.add(signal); + return signal; + } + + public static get allAccessibilitySignals() { + return [...this._signals]; + } + + public static readonly error = AccessibilitySignal.register({ + name: localize('accessibilitySignals.lineHasError.name', 'Error on Line'), + sound: Sound.error, + legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasError', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Error, + announcementMessage: localize('accessibility.signals.lineHasError', 'Error'), + settingsKey: 'accessibility.signals.lineHasError' + }); + public static readonly warning = AccessibilitySignal.register({ + name: localize('accessibilitySignals.lineHasWarning.name', 'Warning on Line'), + sound: Sound.warning, + legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasWarning', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Warning, + announcementMessage: localize('accessibility.signals.lineHasWarning', 'Warning'), + settingsKey: 'accessibility.signals.lineHasWarning' + }); + public static readonly foldedArea = AccessibilitySignal.register({ + name: localize('accessibilitySignals.lineHasFoldedArea.name', 'Folded Area on Line'), + sound: Sound.foldedArea, + legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasFoldedArea', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.FoldedArea, + announcementMessage: localize('accessibility.signals.lineHasFoldedArea', 'Folded'), + settingsKey: 'accessibility.signals.lineHasFoldedArea' + }); + public static readonly break = AccessibilitySignal.register({ + name: localize('accessibilitySignals.lineHasBreakpoint.name', 'Breakpoint on Line'), + sound: Sound.break, + legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasBreakpoint', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Breakpoint, + announcementMessage: localize('accessibility.signals.lineHasBreakpoint', 'Breakpoint'), + settingsKey: 'accessibility.signals.lineHasBreakpoint' + }); + public static readonly inlineSuggestion = AccessibilitySignal.register({ + name: localize('accessibilitySignals.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), + sound: Sound.quickFixes, + legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasInlineSuggestion', + settingsKey: 'accessibility.signals.lineHasInlineSuggestion' + }); + + public static readonly terminalQuickFix = AccessibilitySignal.register({ + name: localize('accessibilitySignals.terminalQuickFix.name', 'Terminal Quick Fix'), + sound: Sound.quickFixes, + legacyAudioCueSettingsKey: 'accessibilitySignals.terminalQuickFix', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, + announcementMessage: localize('accessibility.signals.terminalQuickFix', 'Quick Fix'), + settingsKey: 'accessibility.signals.terminalQuickFix' + }); + + public static readonly onDebugBreak = AccessibilitySignal.register({ + name: localize('accessibilitySignals.onDebugBreak.name', 'Debugger Stopped on Breakpoint'), + sound: Sound.break, + legacyAudioCueSettingsKey: 'accessibilitySignals.onDebugBreak', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, + announcementMessage: localize('accessibility.signals.onDebugBreak', 'Breakpoint'), + settingsKey: 'accessibility.signals.onDebugBreak' + }); + + public static readonly noInlayHints = AccessibilitySignal.register({ + name: localize('accessibilitySignals.noInlayHints', 'No Inlay Hints on Line'), + sound: Sound.error, + legacyAudioCueSettingsKey: 'accessibilitySignals.noInlayHints', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NoInlayHints, + announcementMessage: localize('accessibility.signals.noInlayHints', 'No Inlay Hints'), + settingsKey: 'accessibility.signals.noInlayHints' + }); + + public static readonly taskCompleted = AccessibilitySignal.register({ + name: localize('accessibilitySignals.taskCompleted', 'Task Completed'), + sound: Sound.taskCompleted, + legacyAudioCueSettingsKey: 'accessibilitySignals.taskCompleted', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TaskCompleted, + announcementMessage: localize('accessibility.signals.taskCompleted', 'Task Completed'), + settingsKey: 'accessibility.signals.taskCompleted' + }); + + public static readonly taskFailed = AccessibilitySignal.register({ + name: localize('accessibilitySignals.taskFailed', 'Task Failed'), + sound: Sound.taskFailed, + legacyAudioCueSettingsKey: 'accessibilitySignals.taskFailed', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TaskFailed, + announcementMessage: localize('accessibility.signals.taskFailed', 'Task Failed'), + settingsKey: 'accessibility.signals.taskFailed' + }); + + public static readonly terminalCommandFailed = AccessibilitySignal.register({ + name: localize('accessibilitySignals.terminalCommandFailed', 'Terminal Command Failed'), + sound: Sound.error, + legacyAudioCueSettingsKey: 'accessibilitySignals.terminalCommandFailed', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, + announcementMessage: localize('accessibility.signals.terminalCommandFailed', 'Command Failed'), + settingsKey: 'accessibility.signals.terminalCommandFailed' + }); + + public static readonly terminalBell = AccessibilitySignal.register({ + name: localize('accessibilitySignals.terminalBell', 'Terminal Bell'), + sound: Sound.terminalBell, + legacyAudioCueSettingsKey: 'accessibilitySignals.terminalBell', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalBell, + announcementMessage: localize('accessibility.signals.terminalBell', 'Terminal Bell'), + settingsKey: 'accessibility.signals.terminalBell' + }); + + public static readonly notebookCellCompleted = AccessibilitySignal.register({ + name: localize('accessibilitySignals.notebookCellCompleted', 'Notebook Cell Completed'), + sound: Sound.taskCompleted, + legacyAudioCueSettingsKey: 'accessibilitySignals.notebookCellCompleted', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, + announcementMessage: localize('accessibility.signals.notebookCellCompleted', 'Notebook Cell Completed'), + settingsKey: 'accessibility.signals.notebookCellCompleted' + }); + + public static readonly notebookCellFailed = AccessibilitySignal.register({ + name: localize('accessibilitySignals.notebookCellFailed', 'Notebook Cell Failed'), + sound: Sound.taskFailed, + legacyAudioCueSettingsKey: 'accessibilitySignals.notebookCellFailed', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, + announcementMessage: localize('accessibility.signals.notebookCellFailed', 'Notebook Cell Failed'), + settingsKey: 'accessibility.signals.notebookCellFailed' + }); + + public static readonly diffLineInserted = AccessibilitySignal.register({ + name: localize('accessibilitySignals.diffLineInserted', 'Diff Line Inserted'), + sound: Sound.diffLineInserted, + legacyAudioCueSettingsKey: 'accessibilitySignals.diffLineInserted', + settingsKey: 'accessibility.signals.diffLineInserted' + }); + + public static readonly diffLineDeleted = AccessibilitySignal.register({ + name: localize('accessibilitySignals.diffLineDeleted', 'Diff Line Deleted'), + sound: Sound.diffLineDeleted, + legacyAudioCueSettingsKey: 'accessibilitySignals.diffLineDeleted', + settingsKey: 'accessibility.signals.diffLineDeleted' + }); + + public static readonly diffLineModified = AccessibilitySignal.register({ + name: localize('accessibilitySignals.diffLineModified', 'Diff Line Modified'), + sound: Sound.diffLineModified, + legacyAudioCueSettingsKey: 'accessibilitySignals.diffLineModified', + settingsKey: 'accessibility.signals.diffLineModified' + }); + + public static readonly chatRequestSent = AccessibilitySignal.register({ + name: localize('accessibilitySignals.chatRequestSent', 'Chat Request Sent'), + sound: Sound.chatRequestSent, + legacyAudioCueSettingsKey: 'accessibilitySignals.chatRequestSent', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, + announcementMessage: localize('accessibility.signals.chatRequestSent', 'Chat Request Sent'), + settingsKey: 'accessibility.signals.chatRequestSent' + }); + + public static readonly chatResponseReceived = AccessibilitySignal.register({ + name: localize('accessibilitySignals.chatResponseReceived', 'Chat Response Received'), + legacyAudioCueSettingsKey: 'accessibilitySignals.chatResponseReceived', + sound: { + randomOneOf: [ + Sound.chatResponseReceived1, + Sound.chatResponseReceived2, + Sound.chatResponseReceived3, + Sound.chatResponseReceived4 + ] + }, + settingsKey: 'accessibility.signals.chatResponseReceived' + }); + + public static readonly chatResponsePending = AccessibilitySignal.register({ + name: localize('accessibilitySignals.chatResponsePending', 'Chat Response Pending'), + sound: Sound.chatResponsePending, + legacyAudioCueSettingsKey: 'accessibilitySignals.chatResponsePending', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, + announcementMessage: localize('accessibility.signals.chatResponsePending', 'Chat Response Pending'), + settingsKey: 'accessibility.signals.chatResponsePending' + }); + + public static readonly clear = AccessibilitySignal.register({ + name: localize('accessibilitySignals.clear', 'Clear'), + sound: Sound.clear, + legacyAudioCueSettingsKey: 'accessibilitySignals.clear', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Clear, + announcementMessage: localize('accessibility.signals.clear', 'Clear'), + settingsKey: 'accessibility.signals.clear' + }); + + public static readonly save = AccessibilitySignal.register({ + name: localize('accessibilitySignals.save', 'Save'), + sound: Sound.save, + legacyAudioCueSettingsKey: 'accessibilitySignals.save', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Save, + announcementMessage: localize('accessibility.signals.save', 'Save'), + settingsKey: 'accessibility.signals.save' + }); + + public static readonly format = AccessibilitySignal.register({ + name: localize('accessibilitySignals.format', 'Format'), + sound: Sound.format, + legacyAudioCueSettingsKey: 'accessibilitySignals.format', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Format, + announcementMessage: localize('accessibility.signals.format', 'Format'), + settingsKey: 'accessibility.signals.format' + }); + + private constructor( + public readonly sound: SoundSource, + public readonly name: string, + public readonly settingsKey: string, + public readonly signalSettingsKey: string, + public readonly alertSettingsKey?: string, + public readonly announcementMessage?: string, + ) { } +} diff --git a/src/vs/platform/audioCues/browser/media/break.mp3 b/src/vs/platform/accessibilitySignal/browser/media/break.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/break.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/break.mp3 diff --git a/src/vs/platform/audioCues/browser/media/chatRequestSent.mp3 b/src/vs/platform/accessibilitySignal/browser/media/chatRequestSent.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/chatRequestSent.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/chatRequestSent.mp3 diff --git a/src/vs/platform/audioCues/browser/media/chatResponsePending.mp3 b/src/vs/platform/accessibilitySignal/browser/media/chatResponsePending.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/chatResponsePending.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/chatResponsePending.mp3 diff --git a/src/vs/platform/audioCues/browser/media/chatResponseReceived1.mp3 b/src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived1.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/chatResponseReceived1.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived1.mp3 diff --git a/src/vs/platform/audioCues/browser/media/chatResponseReceived2.mp3 b/src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived2.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/chatResponseReceived2.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived2.mp3 diff --git a/src/vs/platform/audioCues/browser/media/chatResponseReceived3.mp3 b/src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived3.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/chatResponseReceived3.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived3.mp3 diff --git a/src/vs/platform/audioCues/browser/media/chatResponseReceived4.mp3 b/src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived4.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/chatResponseReceived4.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived4.mp3 diff --git a/src/vs/platform/audioCues/browser/media/clear.mp3 b/src/vs/platform/accessibilitySignal/browser/media/clear.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/clear.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/clear.mp3 diff --git a/src/vs/platform/audioCues/browser/media/diffLineDeleted.mp3 b/src/vs/platform/accessibilitySignal/browser/media/diffLineDeleted.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/diffLineDeleted.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/diffLineDeleted.mp3 diff --git a/src/vs/platform/audioCues/browser/media/diffLineInserted.mp3 b/src/vs/platform/accessibilitySignal/browser/media/diffLineInserted.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/diffLineInserted.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/diffLineInserted.mp3 diff --git a/src/vs/platform/audioCues/browser/media/diffLineModified.mp3 b/src/vs/platform/accessibilitySignal/browser/media/diffLineModified.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/diffLineModified.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/diffLineModified.mp3 diff --git a/src/vs/platform/audioCues/browser/media/error.mp3 b/src/vs/platform/accessibilitySignal/browser/media/error.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/error.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/error.mp3 diff --git a/src/vs/platform/audioCues/browser/media/foldedAreas.mp3 b/src/vs/platform/accessibilitySignal/browser/media/foldedAreas.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/foldedAreas.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/foldedAreas.mp3 diff --git a/src/vs/platform/audioCues/browser/media/format.mp3 b/src/vs/platform/accessibilitySignal/browser/media/format.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/format.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/format.mp3 diff --git a/src/vs/platform/audioCues/browser/media/quickFixes.mp3 b/src/vs/platform/accessibilitySignal/browser/media/quickFixes.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/quickFixes.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/quickFixes.mp3 diff --git a/src/vs/platform/audioCues/browser/media/save.mp3 b/src/vs/platform/accessibilitySignal/browser/media/save.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/save.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/save.mp3 diff --git a/src/vs/platform/audioCues/browser/media/taskCompleted.mp3 b/src/vs/platform/accessibilitySignal/browser/media/taskCompleted.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/taskCompleted.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/taskCompleted.mp3 diff --git a/src/vs/platform/audioCues/browser/media/taskFailed.mp3 b/src/vs/platform/accessibilitySignal/browser/media/taskFailed.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/taskFailed.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/taskFailed.mp3 diff --git a/src/vs/platform/audioCues/browser/media/terminalBell.mp3 b/src/vs/platform/accessibilitySignal/browser/media/terminalBell.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/terminalBell.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/terminalBell.mp3 diff --git a/src/vs/platform/audioCues/browser/media/warning.mp3 b/src/vs/platform/accessibilitySignal/browser/media/warning.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/warning.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/warning.mp3 diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts deleted file mode 100644 index 7c7dfbe0567..00000000000 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ /dev/null @@ -1,593 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { FileAccess } from 'vs/base/common/network'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Event } from 'vs/base/common/event'; -import { localize } from 'vs/nls'; -import { observableFromEvent, derived } from 'vs/base/common/observable'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; - -export const IAudioCueService = createDecorator('audioCue'); - -export interface IAudioCueService { - readonly _serviceBrand: undefined; - playAudioCue(cue: AudioCue, options?: IAudioCueOptions): Promise; - playAudioCues(cues: (AudioCue | { cue: AudioCue; source: string })[]): Promise; - isCueEnabled(cue: AudioCue): boolean; - isAlertEnabled(cue: AudioCue): boolean; - onEnabledChanged(cue: AudioCue): Event; - onAlertEnabledChanged(cue: AudioCue): Event; - - playSound(cue: Sound, allowManyInParallel?: boolean): Promise; - playAudioCueLoop(cue: AudioCue, milliseconds: number): IDisposable; -} - -export interface IAudioCueOptions { - allowManyInParallel?: boolean; - source?: string; - /** - * For actions like save or format, depending on the - * configured value, we will only - * play the sound if the user triggered the action. - */ - userGesture?: boolean; -} - -export class AudioCueService extends Disposable implements IAudioCueService { - readonly _serviceBrand: undefined; - private readonly sounds: Map = new Map(); - private readonly screenReaderAttached = observableFromEvent( - this.accessibilityService.onDidChangeScreenReaderOptimized, - () => /** @description accessibilityService.onDidChangeScreenReaderOptimized */ this.accessibilityService.isScreenReaderOptimized() - ); - private readonly sentTelemetry = new Set(); - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, - @IAccessibilityService private readonly accessibilityService: IAccessibilityService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - ) { - super(); - } - - public async playAudioCue(cue: AudioCue, options: IAudioCueOptions = {}): Promise { - const alertMessage = cue.alertMessage; - if (this.isAlertEnabled(cue, options.userGesture) && alertMessage) { - this.accessibilityService.status(alertMessage); - } - - if (this.isCueEnabled(cue, options.userGesture)) { - this.sendAudioCueTelemetry(cue, options.source); - await this.playSound(cue.sound.getSound(), options.allowManyInParallel); - } - } - - public async playAudioCues(cues: (AudioCue | { cue: AudioCue; source: string })[]): Promise { - for (const cue of cues) { - this.sendAudioCueTelemetry('cue' in cue ? cue.cue : cue, 'source' in cue ? cue.source : undefined); - } - const cueArray = cues.map(c => 'cue' in c ? c.cue : c); - const alerts = cueArray.filter(cue => this.isAlertEnabled(cue)).map(c => c.alertMessage); - if (alerts.length) { - this.accessibilityService.status(alerts.join(', ')); - } - - // Some audio cues might reuse sounds. Don't play the same sound twice. - const sounds = new Set(cueArray.filter(cue => this.isCueEnabled(cue)).map(cue => cue.sound.getSound())); - await Promise.all(Array.from(sounds).map(sound => this.playSound(sound, true))); - - } - - - private sendAudioCueTelemetry(cue: AudioCue, source: string | undefined): void { - const isScreenReaderOptimized = this.accessibilityService.isScreenReaderOptimized(); - const key = cue.name + (source ? `::${source}` : '') + (isScreenReaderOptimized ? '{screenReaderOptimized}' : ''); - // Only send once per user session - if (this.sentTelemetry.has(key) || this.getVolumeInPercent() === 0) { - return; - } - this.sentTelemetry.add(key); - - this.telemetryService.publicLog2<{ - audioCue: string; - source: string; - isScreenReaderOptimized: boolean; - }, { - owner: 'hediet'; - - audioCue: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The audio cue that was played.' }; - source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source that triggered the audio cue (e.g. "diffEditorNavigation").' }; - isScreenReaderOptimized: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user is using a screen reader' }; - - comment: 'This data is collected to understand how audio cues are used and if more audio cues should be added.'; - }>('audioCue.played', { - audioCue: cue.name, - source: source ?? '', - isScreenReaderOptimized, - }); - } - - private getVolumeInPercent(): number { - const volume = this.configurationService.getValue('audioCues.volume'); - if (typeof volume !== 'number') { - return 50; - } - - return Math.max(Math.min(volume, 100), 0); - } - - private readonly playingSounds = new Set(); - - public async playSound(sound: Sound, allowManyInParallel = false): Promise { - if (!allowManyInParallel && this.playingSounds.has(sound)) { - return; - } - this.playingSounds.add(sound); - const url = FileAccess.asBrowserUri(`vs/platform/audioCues/browser/media/${sound.fileName}`).toString(true); - - try { - const sound = this.sounds.get(url); - if (sound) { - sound.volume = this.getVolumeInPercent() / 100; - sound.currentTime = 0; - await sound.play(); - } else { - const playedSound = await playAudio(url, this.getVolumeInPercent() / 100); - this.sounds.set(url, playedSound); - } - } catch (e) { - if (!e.message.includes('play() can only be initiated by a user gesture')) { - // tracking this issue in #178642, no need to spam the console - console.error('Error while playing sound', e); - } - } finally { - this.playingSounds.delete(sound); - } - } - - public playAudioCueLoop(cue: AudioCue, milliseconds: number): IDisposable { - let playing = true; - const playSound = () => { - if (playing) { - this.playAudioCue(cue, { allowManyInParallel: true }).finally(() => { - setTimeout(() => { - if (playing) { - playSound(); - } - }, milliseconds); - }); - } - }; - playSound(); - return toDisposable(() => playing = false); - } - - private readonly obsoleteAudioCuesEnabled = observableFromEvent( - Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration('audioCues.enabled') - ), - () => /** @description config: audioCues.enabled */ this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>('audioCues.enabled') - ); - - private readonly isCueEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { - const settingObservable = observableFromEvent( - Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.settingsKey) || e.affectsConfiguration(event.cue.signalSettingsKey) - ), - () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.cue.signalSettingsKey + '.audioCue') - ); - return derived(reader => { - /** @description audio cue enabled */ - const setting = settingObservable.read(reader); - if ( - setting === 'on' || - (setting === 'auto' && this.screenReaderAttached.read(reader)) - ) { - return true; - } else if (setting === 'always' || setting === 'userGesture' && event.userGesture) { - return true; - } - - const obsoleteSetting = this.obsoleteAudioCuesEnabled.read(reader); - if ( - obsoleteSetting === 'on' || - (obsoleteSetting === 'auto' && this.screenReaderAttached.read(reader)) - ) { - return true; - } - - return false; - }); - }, JSON.stringify); - - private readonly isAlertEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { - const settingObservable = observableFromEvent( - Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.alertSettingsKey!) || e.affectsConfiguration(event.cue.signalSettingsKey) - ), - () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.signalSettingsKey + '.alert') : false - ); - return derived(reader => { - /** @description alert enabled */ - const setting = settingObservable.read(reader); - if ( - !this.screenReaderAttached.read(reader) - ) { - return false; - } - return setting === true || setting === 'always' || setting === 'userGesture' && event.userGesture; - }); - }, JSON.stringify); - - public isAlertEnabled(cue: AudioCue, userGesture?: boolean): boolean { - if (!cue.alertSettingsKey) { - return false; - } - return this.isAlertEnabledCache.get({ cue, userGesture }).get() ?? false; - } - - public isCueEnabled(cue: AudioCue, userGesture?: boolean): boolean { - return this.isCueEnabledCache.get({ cue, userGesture }).get() ?? false; - } - - public onEnabledChanged(cue: AudioCue): Event { - return Event.fromObservableLight(this.isCueEnabledCache.get({ cue })); - } - - public onAlertEnabledChanged(cue: AudioCue): Event { - return Event.fromObservableLight(this.isAlertEnabledCache.get({ cue })); - } -} - - -/** - * Play the given audio url. - * @volume value between 0 and 1 - */ -function playAudio(url: string, volume: number): Promise { - return new Promise((resolve, reject) => { - const audio = new Audio(url); - audio.volume = volume; - audio.addEventListener('ended', () => { - resolve(audio); - }); - audio.addEventListener('error', (e) => { - // When the error event fires, ended might not be called - reject(e.error); - }); - audio.play().catch(e => { - // When play fails, the error event is not fired. - reject(e); - }); - }); -} - -class Cache { - private readonly map = new Map(); - constructor(private readonly getValue: (value: TArg) => TValue, private readonly getKey: (value: TArg) => unknown) { - } - - public get(arg: TArg): TValue { - if (this.map.has(arg)) { - return this.map.get(arg)!; - } - - const value = this.getValue(arg); - const key = this.getKey(arg); - this.map.set(key, value); - return value; - } -} - -/** - * Corresponds to the audio files in ./media. -*/ -export class Sound { - private static register(options: { fileName: string }): Sound { - const sound = new Sound(options.fileName); - return sound; - } - - public static readonly error = Sound.register({ fileName: 'error.mp3' }); - public static readonly warning = Sound.register({ fileName: 'warning.mp3' }); - public static readonly foldedArea = Sound.register({ fileName: 'foldedAreas.mp3' }); - public static readonly break = Sound.register({ fileName: 'break.mp3' }); - public static readonly quickFixes = Sound.register({ fileName: 'quickFixes.mp3' }); - public static readonly taskCompleted = Sound.register({ fileName: 'taskCompleted.mp3' }); - public static readonly taskFailed = Sound.register({ fileName: 'taskFailed.mp3' }); - public static readonly terminalBell = Sound.register({ fileName: 'terminalBell.mp3' }); - public static readonly diffLineInserted = Sound.register({ fileName: 'diffLineInserted.mp3' }); - public static readonly diffLineDeleted = Sound.register({ fileName: 'diffLineDeleted.mp3' }); - public static readonly diffLineModified = Sound.register({ fileName: 'diffLineModified.mp3' }); - public static readonly chatRequestSent = Sound.register({ fileName: 'chatRequestSent.mp3' }); - public static readonly chatResponsePending = Sound.register({ fileName: 'chatResponsePending.mp3' }); - public static readonly chatResponseReceived1 = Sound.register({ fileName: 'chatResponseReceived1.mp3' }); - public static readonly chatResponseReceived2 = Sound.register({ fileName: 'chatResponseReceived2.mp3' }); - public static readonly chatResponseReceived3 = Sound.register({ fileName: 'chatResponseReceived3.mp3' }); - public static readonly chatResponseReceived4 = Sound.register({ fileName: 'chatResponseReceived4.mp3' }); - public static readonly clear = Sound.register({ fileName: 'clear.mp3' }); - public static readonly save = Sound.register({ fileName: 'save.mp3' }); - public static readonly format = Sound.register({ fileName: 'format.mp3' }); - - private constructor(public readonly fileName: string) { } -} - -export class SoundSource { - constructor( - public readonly randomOneOf: Sound[] - ) { } - - public getSound(deterministic = false): Sound { - if (deterministic || this.randomOneOf.length === 1) { - return this.randomOneOf[0]; - } else { - const index = Math.floor(Math.random() * this.randomOneOf.length); - return this.randomOneOf[index]; - } - } -} - -export const enum AccessibilityAlertSettingId { - Save = 'accessibility.alert.save', - Format = 'accessibility.alert.format', - Clear = 'accessibility.alert.clear', - Breakpoint = 'accessibility.alert.breakpoint', - Error = 'accessibility.alert.error', - Warning = 'accessibility.alert.warning', - FoldedArea = 'accessibility.alert.foldedArea', - TerminalQuickFix = 'accessibility.alert.terminalQuickFix', - TerminalBell = 'accessibility.alert.terminalBell', - TerminalCommandFailed = 'accessibility.alert.terminalCommandFailed', - TaskCompleted = 'accessibility.alert.taskCompleted', - TaskFailed = 'accessibility.alert.taskFailed', - ChatRequestSent = 'accessibility.alert.chatRequestSent', - NotebookCellCompleted = 'accessibility.alert.notebookCellCompleted', - NotebookCellFailed = 'accessibility.alert.notebookCellFailed', - OnDebugBreak = 'accessibility.alert.onDebugBreak', - NoInlayHints = 'accessibility.alert.noInlayHints', - LineHasBreakpoint = 'accessibility.alert.lineHasBreakpoint', - ChatResponsePending = 'accessibility.alert.chatResponsePending' -} - - -export class AudioCue { - private static _audioCues = new Set(); - private static register(options: { - name: string; - sound: Sound | { - /** - * Gaming and other apps often play a sound variant when the same event happens again - * for an improved experience. This option enables audio cues to play a random sound. - */ - randomOneOf: Sound[]; - }; - settingsKey: string; - signalSettingsKey: string; - alertSettingsKey?: AccessibilityAlertSettingId; - alertMessage?: string; - }): AudioCue { - const soundSource = new SoundSource('randomOneOf' in options.sound ? options.sound.randomOneOf : [options.sound]); - const audioCue = new AudioCue(soundSource, options.name, options.settingsKey, options.signalSettingsKey, options.alertSettingsKey, options.alertMessage); - AudioCue._audioCues.add(audioCue); - return audioCue; - } - - public static get allAudioCues() { - return [...this._audioCues]; - } - - public static readonly error = AudioCue.register({ - name: localize('audioCues.lineHasError.name', 'Error on Line'), - sound: Sound.error, - settingsKey: 'audioCues.lineHasError', - alertSettingsKey: AccessibilityAlertSettingId.Error, - alertMessage: localize('accessibility.signals.lineHasError', 'Error'), - signalSettingsKey: 'accessibility.signals.lineHasError' - }); - public static readonly warning = AudioCue.register({ - name: localize('audioCues.lineHasWarning.name', 'Warning on Line'), - sound: Sound.warning, - settingsKey: 'audioCues.lineHasWarning', - alertSettingsKey: AccessibilityAlertSettingId.Warning, - alertMessage: localize('accessibility.signals.lineHasWarning', 'Warning'), - signalSettingsKey: 'accessibility.signals.lineHasWarning' - }); - public static readonly foldedArea = AudioCue.register({ - name: localize('audioCues.lineHasFoldedArea.name', 'Folded Area on Line'), - sound: Sound.foldedArea, - settingsKey: 'audioCues.lineHasFoldedArea', - alertSettingsKey: AccessibilityAlertSettingId.FoldedArea, - alertMessage: localize('accessibility.signals.lineHasFoldedArea', 'Folded'), - signalSettingsKey: 'accessibility.signals.lineHasFoldedArea' - }); - public static readonly break = AudioCue.register({ - name: localize('audioCues.lineHasBreakpoint.name', 'Breakpoint on Line'), - sound: Sound.break, - settingsKey: 'audioCues.lineHasBreakpoint', - alertSettingsKey: AccessibilityAlertSettingId.Breakpoint, - alertMessage: localize('accessibility.signals.lineHasBreakpoint', 'Breakpoint'), - signalSettingsKey: 'accessibility.signals.lineHasBreakpoint' - }); - public static readonly inlineSuggestion = AudioCue.register({ - name: localize('audioCues.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), - sound: Sound.quickFixes, - settingsKey: 'audioCues.lineHasInlineSuggestion', - signalSettingsKey: 'accessibility.signals.lineHasInlineSuggestion' - }); - - public static readonly terminalQuickFix = AudioCue.register({ - name: localize('audioCues.terminalQuickFix.name', 'Terminal Quick Fix'), - sound: Sound.quickFixes, - settingsKey: 'audioCues.terminalQuickFix', - alertSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, - alertMessage: localize('accessibility.signals.terminalQuickFix', 'Quick Fix'), - signalSettingsKey: 'accessibility.signals.terminalQuickFix' - }); - - public static readonly onDebugBreak = AudioCue.register({ - name: localize('audioCues.onDebugBreak.name', 'Debugger Stopped on Breakpoint'), - sound: Sound.break, - settingsKey: 'audioCues.onDebugBreak', - alertSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, - alertMessage: localize('accessibility.signals.onDebugBreak', 'Breakpoint'), - signalSettingsKey: 'accessibility.signals.onDebugBreak' - }); - - public static readonly noInlayHints = AudioCue.register({ - name: localize('audioCues.noInlayHints', 'No Inlay Hints on Line'), - sound: Sound.error, - settingsKey: 'audioCues.noInlayHints', - alertSettingsKey: AccessibilityAlertSettingId.NoInlayHints, - alertMessage: localize('accessibility.signals.noInlayHints', 'No Inlay Hints'), - signalSettingsKey: 'accessibility.signals.noInlayHints' - }); - - public static readonly taskCompleted = AudioCue.register({ - name: localize('audioCues.taskCompleted', 'Task Completed'), - sound: Sound.taskCompleted, - settingsKey: 'audioCues.taskCompleted', - alertSettingsKey: AccessibilityAlertSettingId.TaskCompleted, - alertMessage: localize('accessibility.signals.taskCompleted', 'Task Completed'), - signalSettingsKey: 'accessibility.signals.taskCompleted' - }); - - public static readonly taskFailed = AudioCue.register({ - name: localize('audioCues.taskFailed', 'Task Failed'), - sound: Sound.taskFailed, - settingsKey: 'audioCues.taskFailed', - alertSettingsKey: AccessibilityAlertSettingId.TaskFailed, - alertMessage: localize('accessibility.signals.taskFailed', 'Task Failed'), - signalSettingsKey: 'accessibility.signals.taskFailed' - }); - - public static readonly terminalCommandFailed = AudioCue.register({ - name: localize('audioCues.terminalCommandFailed', 'Terminal Command Failed'), - sound: Sound.error, - settingsKey: 'audioCues.terminalCommandFailed', - alertSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, - alertMessage: localize('accessibility.signals.terminalCommandFailed', 'Command Failed'), - signalSettingsKey: 'accessibility.signals.terminalCommandFailed' - }); - - public static readonly terminalBell = AudioCue.register({ - name: localize('audioCues.terminalBell', 'Terminal Bell'), - sound: Sound.terminalBell, - settingsKey: 'audioCues.terminalBell', - alertSettingsKey: AccessibilityAlertSettingId.TerminalBell, - alertMessage: localize('accessibility.signals.terminalBell', 'Terminal Bell'), - signalSettingsKey: 'accessibility.signals.terminalBell' - }); - - public static readonly notebookCellCompleted = AudioCue.register({ - name: localize('audioCues.notebookCellCompleted', 'Notebook Cell Completed'), - sound: Sound.taskCompleted, - settingsKey: 'audioCues.notebookCellCompleted', - alertSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, - alertMessage: localize('accessibility.signals.notebookCellCompleted', 'Notebook Cell Completed'), - signalSettingsKey: 'accessibility.signals.notebookCellCompleted' - }); - - public static readonly notebookCellFailed = AudioCue.register({ - name: localize('audioCues.notebookCellFailed', 'Notebook Cell Failed'), - sound: Sound.taskFailed, - settingsKey: 'audioCues.notebookCellFailed', - alertSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, - alertMessage: localize('accessibility.signals.notebookCellFailed', 'Notebook Cell Failed'), - signalSettingsKey: 'accessibility.signals.notebookCellFailed' - }); - - public static readonly diffLineInserted = AudioCue.register({ - name: localize('audioCues.diffLineInserted', 'Diff Line Inserted'), - sound: Sound.diffLineInserted, - settingsKey: 'audioCues.diffLineInserted', - signalSettingsKey: 'accessibility.signals.diffLineInserted' - }); - - public static readonly diffLineDeleted = AudioCue.register({ - name: localize('audioCues.diffLineDeleted', 'Diff Line Deleted'), - sound: Sound.diffLineDeleted, - settingsKey: 'audioCues.diffLineDeleted', - signalSettingsKey: 'accessibility.signals.diffLineDeleted' - }); - - public static readonly diffLineModified = AudioCue.register({ - name: localize('audioCues.diffLineModified', 'Diff Line Modified'), - sound: Sound.diffLineModified, - settingsKey: 'audioCues.diffLineModified', - signalSettingsKey: 'accessibility.signals.diffLineModified' - }); - - public static readonly chatRequestSent = AudioCue.register({ - name: localize('audioCues.chatRequestSent', 'Chat Request Sent'), - sound: Sound.chatRequestSent, - settingsKey: 'audioCues.chatRequestSent', - alertSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, - alertMessage: localize('accessibility.signals.chatRequestSent', 'Chat Request Sent'), - signalSettingsKey: 'accessibility.signals.chatRequestSent' - }); - - public static readonly chatResponseReceived = AudioCue.register({ - name: localize('audioCues.chatResponseReceived', 'Chat Response Received'), - settingsKey: 'audioCues.chatResponseReceived', - sound: { - randomOneOf: [ - Sound.chatResponseReceived1, - Sound.chatResponseReceived2, - Sound.chatResponseReceived3, - Sound.chatResponseReceived4 - ] - }, - signalSettingsKey: 'accessibility.signals.chatResponseReceived' - }); - - public static readonly chatResponsePending = AudioCue.register({ - name: localize('audioCues.chatResponsePending', 'Chat Response Pending'), - sound: Sound.chatResponsePending, - settingsKey: 'audioCues.chatResponsePending', - alertSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, - alertMessage: localize('accessibility.signals.chatResponsePending', 'Chat Response Pending'), - signalSettingsKey: 'accessibility.signals.chatResponsePending' - }); - - public static readonly clear = AudioCue.register({ - name: localize('audioCues.clear', 'Clear'), - sound: Sound.clear, - settingsKey: 'audioCues.clear', - alertSettingsKey: AccessibilityAlertSettingId.Clear, - alertMessage: localize('accessibility.signals.clear', 'Clear'), - signalSettingsKey: 'accessibility.signals.clear' - }); - - public static readonly save = AudioCue.register({ - name: localize('audioCues.save', 'Save'), - sound: Sound.save, - settingsKey: 'audioCues.save', - alertSettingsKey: AccessibilityAlertSettingId.Save, - alertMessage: localize('accessibility.signals.save', 'Save'), - signalSettingsKey: 'accessibility.signals.save' - }); - - public static readonly format = AudioCue.register({ - name: localize('audioCues.format', 'Format'), - sound: Sound.format, - settingsKey: 'audioCues.format', - alertSettingsKey: AccessibilityAlertSettingId.Format, - alertMessage: localize('accessibility.signals.format', 'Format'), - signalSettingsKey: 'accessibility.signals.format' - }); - - private constructor( - public readonly sound: SoundSource, - public readonly name: string, - public readonly settingsKey: string, - public readonly signalSettingsKey: string, - public readonly alertSettingsKey?: string, - public readonly alertMessage?: string, - ) { } -} diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index 8a88a9e5f4a..e4cbaff2272 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -28,7 +28,7 @@ import { INotificationService, NotificationsFilter } from 'vs/platform/notificat import { mainWindow } from 'vs/base/browser/window'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; export class NotificationsCenter extends Themable implements INotificationsCenterController { @@ -59,7 +59,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IKeybindingService private readonly keybindingService: IKeybindingService, @INotificationService private readonly notificationService: INotificationService, - @IAudioCueService private readonly audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly accessibilitySignalService: IAccessibilitySignalService, @IContextMenuService private readonly contextMenuService: IContextMenuService ) { super(themeService); @@ -383,7 +383,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente if (!notification.hasProgress) { notification.close(); } - this.audioCueService.playAudioCue(AudioCue.clear); + this.accessibilitySignalService.playSignal(AccessibilitySignal.clear); } } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index 5e8135b4fcc..6f205b9d81f 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -21,7 +21,7 @@ import { hash } from 'vs/base/common/hash'; import { firstOrDefault } from 'vs/base/common/arrays'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; // Center export const SHOW_NOTIFICATIONS_CENTER = 'notifications.showList'; @@ -142,11 +142,11 @@ export function registerNotificationCommands(center: INotificationsCenterControl primary: KeyMod.CtrlCmd | KeyCode.Backspace }, handler: (accessor, args?) => { - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); const notification = getNotificationFromContext(accessor.get(IListService), args); if (notification && !notification.hasProgress) { notification.close(); - audioCueService.playAudioCue(AudioCue.clear); + accessibilitySignalService.playSignal(AccessibilitySignal.clear); } } }); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 3f601b3b408..243068f3983 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -8,12 +8,12 @@ import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPrope import { Registry } from 'vs/platform/registry/common/platform'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { workbenchConfigurationNodeBase, Extensions as WorkbenchExtensions, IConfigurationMigrationRegistry, ConfigurationKeyValuePairs } from 'vs/workbench/common/configuration'; -import { AccessibilityAlertSettingId, AudioCue } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilityAlertSettingId, AccessibilitySignal } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Event } from 'vs/base/common/event'; -import { audioCueFeatureBase } from 'vs/workbench/contrib/audioCues/browser/audioCues.contribution'; +import { audioCueFeatureBase } from 'vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution'; export const accessibilityHelpIsShown = new RawContextKey('accessibilityHelpIsShown', false, true); export const accessibleViewIsShown = new RawContextKey('accessibleViewIsShown', false, true); @@ -97,22 +97,22 @@ const signalFeatureBase: IConfigurationPropertySchema = { additionalProperties: false, default: { audioCue: 'auto', - alert: 'auto' + announcement: 'auto' } }; -export const alertFeatureBase: IConfigurationPropertySchema = { +export const announcementFeatureBase: IConfigurationPropertySchema = { 'type': 'string', 'enum': ['auto', 'off'], 'default': 'auto', 'enumDescriptions': [ - localize('audioCues.enabled.auto', "Enable alert, will only play when in screen reader optimized mode."), - localize('audioCues.enabled.off', "Disable alert.") + localize('audioCues.enabled.auto', "Enable announcement, will only play when in screen reader optimized mode."), + localize('audioCues.enabled.off', "Disable announcement.") ], tags: ['accessibility'], }; -const defaultNoAlert: IConfigurationPropertySchema = { +const defaultNoAnnouncement: IConfigurationPropertySchema = { 'type': 'object', 'tags': ['accessibility'], additionalProperties: true, @@ -169,96 +169,96 @@ const configuration: IConfigurationNode = { ...baseVerbosityProperty }, [AccessibilityAlertSettingId.Save]: { - 'markdownDescription': localize('alert.save', "Indicates when a file is saved. Also see {0}.", '`#audioCues.save#`'), + 'markdownDescription': localize('announcement.save', "Indicates when a file is saved. Also see {0}.", '`#audioCues.save#`'), 'enum': ['userGesture', 'always', 'never'], 'default': 'always', 'enumDescriptions': [ - localize('alert.save.userGesture', "Indicates when a file is saved via user gesture."), - localize('alert.save.always', "Indicates whenever is a file is saved, including auto save."), - localize('alert.save.never', "Never alerts.") + localize('announcement.save.userGesture', "Indicates when a file is saved via user gesture."), + localize('announcement.save.always', "Indicates whenever is a file is saved, including auto save."), + localize('announcement.save.never', "Never alerts.") ], tags: ['accessibility'], markdownDeprecationMessage }, [AccessibilityAlertSettingId.Clear]: { - 'markdownDescription': localize('alert.clear', "Indicates when a feature is cleared (for example, the terminal, Debug Console, or Output channel). Also see {0}.", '`#audioCues.clear#`'), + 'markdownDescription': localize('announcement.clear', "Indicates when a feature is cleared (for example, the terminal, Debug Console, or Output channel). Also see {0}.", '`#audioCues.clear#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.Format]: { - 'markdownDescription': localize('alert.format', "Indicates when a file or notebook cell is formatted. Also see {0}.", '`#audioCues.format#`'), + 'markdownDescription': localize('announcement.format', "Indicates when a file or notebook cell is formatted. Also see {0}.", '`#audioCues.format#`'), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'always', 'enumDescriptions': [ - localize('alert.format.userGesture', "Indicates when a file is formatted via user gesture."), - localize('alert.format.always', "Indicates whenever is a file is formatted, including auto save, on cell execution, and more."), - localize('alert.format.never', "Never alerts.") + localize('announcement.format.userGesture', "Indicates when a file is formatted via user gesture."), + localize('announcement.format.always', "Indicates whenever is a file is formatted, including auto save, on cell execution, and more."), + localize('announcement.format.never', "Never alerts.") ], tags: ['accessibility'], markdownDeprecationMessage }, [AccessibilityAlertSettingId.Breakpoint]: { - 'markdownDescription': localize('alert.breakpoint', "Indicates when the debugger breaks. Also see {0}.", '`#audioCues.onDebugBreak#`'), + 'markdownDescription': localize('announcement.breakpoint', "Indicates when the debugger breaks. Also see {0}.", '`#audioCues.onDebugBreak#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.Error]: { - 'markdownDescription': localize('alert.error', "Indicates when the active line has an error. Also see {0}.", '`#audioCues.lineHasError#`'), + 'markdownDescription': localize('announcement.error', "Indicates when the active line has an error. Also see {0}.", '`#audioCues.lineHasError#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.Warning]: { - 'markdownDescription': localize('alert.warning', "Indicates when the active line has a warning. Also see {0}.", '`#audioCues.lineHasWarning#`'), + 'markdownDescription': localize('announcement.warning', "Indicates when the active line has a warning. Also see {0}.", '`#audioCues.lineHasWarning#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.FoldedArea]: { - 'markdownDescription': localize('alert.foldedArea', "Indicates when the active line has a folded area that can be unfolded. Also see {0}.", '`#audioCues.lineHasFoldedArea#`'), + 'markdownDescription': localize('announcement.foldedArea', "Indicates when the active line has a folded area that can be unfolded. Also see {0}.", '`#audioCues.lineHasFoldedArea#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalQuickFix]: { - 'markdownDescription': localize('alert.terminalQuickFix', "Indicates when there is an available terminal quick fix. Also see {0}.", '`#audioCues.terminalQuickFix#`'), + 'markdownDescription': localize('announcement.terminalQuickFix', "Indicates when there is an available terminal quick fix. Also see {0}.", '`#audioCues.terminalQuickFix#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalBell]: { - 'markdownDescription': localize('alert.terminalBell', "Indicates when the terminal bell is activated."), + 'markdownDescription': localize('announcement.terminalBell', "Indicates when the terminal bell is activated."), ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalCommandFailed]: { - 'markdownDescription': localize('alert.terminalCommandFailed', "Indicates when a terminal command fails (non-zero exit code). Also see {0}.", '`#audioCues.terminalCommandFailed#`'), + 'markdownDescription': localize('announcement.terminalCommandFailed', "Indicates when a terminal command fails (non-zero exit code). Also see {0}.", '`#audioCues.terminalCommandFailed#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.TaskFailed]: { - 'markdownDescription': localize('alert.taskFailed', "Indicates when a task fails (non-zero exit code). Also see {0}.", '`#audioCues.taskFailed#`'), + 'markdownDescription': localize('announcement.taskFailed', "Indicates when a task fails (non-zero exit code). Also see {0}.", '`#audioCues.taskFailed#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.TaskCompleted]: { - 'markdownDescription': localize('alert.taskCompleted', "Indicates when a task completes successfully (zero exit code). Also see {0}.", '`#audioCues.taskCompleted#`'), + 'markdownDescription': localize('announcement.taskCompleted', "Indicates when a task completes successfully (zero exit code). Also see {0}.", '`#audioCues.taskCompleted#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.ChatRequestSent]: { - 'markdownDescription': localize('alert.chatRequestSent', "Indicates when a chat request is sent. Also see {0}.", '`#audioCues.chatRequestSent#`'), + 'markdownDescription': localize('announcement.chatRequestSent', "Indicates when a chat request is sent. Also see {0}.", '`#audioCues.chatRequestSent#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.ChatResponsePending]: { - 'markdownDescription': localize('alert.chatResponsePending', "Indicates when a chat response is pending. Also see {0}.", '`#audioCues.chatResponsePending#`'), + 'markdownDescription': localize('announcement.chatResponsePending', "Indicates when a chat response is pending. Also see {0}.", '`#audioCues.chatResponsePending#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.NoInlayHints]: { - 'markdownDescription': localize('alert.noInlayHints', "Indicates when there are no inlay hints. Also see {0}.", '`#audioCues.noInlayHints#`'), + 'markdownDescription': localize('announcement.noInlayHints', "Indicates when there are no inlay hints. Also see {0}.", '`#audioCues.noInlayHints#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.LineHasBreakpoint]: { - 'markdownDescription': localize('alert.lineHasBreakpoint', "Indicates when on a line with a breakpoint. Also see {0}.", '`#audioCues.lineHasBreakpoint#`'), + 'markdownDescription': localize('announcement.lineHasBreakpoint', "Indicates when on a line with a breakpoint. Also see {0}.", '`#audioCues.lineHasBreakpoint#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.NotebookCellCompleted]: { - 'markdownDescription': localize('alert.notebookCellCompleted', "Indicates when a notebook cell completes successfully. Also see {0}.", '`#audioCues.notebookCellCompleted#`'), + 'markdownDescription': localize('announcement.notebookCellCompleted', "Indicates when a notebook cell completes successfully. Also see {0}.", '`#audioCues.notebookCellCompleted#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.NotebookCellFailed]: { - 'markdownDescription': localize('alert.notebookCellFailed', "Indicates when a notebook cell fails. Also see {0}.", '`#audioCues.notebookCellFailed#`'), + 'markdownDescription': localize('announcement.notebookCellFailed', "Indicates when a notebook cell fails. Also see {0}.", '`#audioCues.notebookCellFailed#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.OnDebugBreak]: { - 'markdownDescription': localize('alert.onDebugBreak', "Indicates when the debugger breaks. Also see {0}.", '`#audioCues.onDebugBreak#`'), + 'markdownDescription': localize('announcement.onDebugBreak', "Indicates when the debugger breaks. Also see {0}.", '`#audioCues.onDebugBreak#`'), ...baseAlertProperty }, [AccessibilityWorkbenchSettingId.AccessibleViewCloseOnKeyPress]: { @@ -280,15 +280,15 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.lineHasBreakpoint.alert', "Indicates when the active line has a breakpoint."), - ...alertFeatureBase, + ...announcementFeatureBase, default: 'off' }, }, }, 'accessibility.signals.lineHasInlineSuggestion': { - ...defaultNoAlert, + ...defaultNoAnnouncement, 'description': localize('accessibility.signals.lineHasInlineSuggestion', "Indicates when the active line has an inline suggestion."), 'properties': { 'audioCue': { @@ -305,9 +305,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.lineHasError.alert', "Indicates when the active line has an error."), - ...alertFeatureBase, + ...announcementFeatureBase, default: 'off' }, }, @@ -321,9 +321,9 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase, default: 'off' }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.lineHasFoldedArea.alert', "Indicates when the active line has a folded area that can be unfolded."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -335,9 +335,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.lineHasWarning.alert', "Indicates when the active line has a warning."), - ...alertFeatureBase, + ...announcementFeatureBase, default: 'off' }, }, @@ -350,9 +350,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.onDebugBreak.alert', "Indicates when the debugger stopped on a breakpoint."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -364,9 +364,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.noInlayHints.alert', "Indicates when trying to read a line with inlay hints that has no inlay hints."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -378,9 +378,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.taskCompleted.alert', "Indicates when a task is completed."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -392,9 +392,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.taskFailed.alert', "Indicates when a task fails (non-zero exit code)."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -406,9 +406,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.terminalCommandFailed.alert', "Indicates when a terminal command fails (non-zero exit code)."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -420,9 +420,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.terminalQuickFix.alert', "Indicates when terminal Quick Fixes are available."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -434,14 +434,14 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.terminalBell.alert', "Indicates when the terminal bell is ringing."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, 'accessibility.signals.diffLineInserted': { - ...defaultNoAlert, + ...defaultNoAnnouncement, 'description': localize('accessibility.signals.diffLineInserted', "Indicates when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { @@ -451,7 +451,7 @@ const configuration: IConfigurationNode = { } }, 'accessibility.signals.diffLineModified': { - ...defaultNoAlert, + ...defaultNoAnnouncement, 'description': localize('accessibility.signals.diffLineModified', "Indicates when the focus moves to an modified line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { @@ -461,7 +461,7 @@ const configuration: IConfigurationNode = { } }, 'accessibility.signals.diffLineDeleted': { - ...defaultNoAlert, + ...defaultNoAnnouncement, 'description': localize('accessibility.signals.diffLineDeleted', "Indicates when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { @@ -478,9 +478,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.notebookCellCompleted.alert', "Indicates when a notebook cell execution is successfully completed."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -492,9 +492,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.notebookCellFailed.alert', "Indicates when a notebook cell execution fails."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -506,9 +506,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.chatRequestSent.alert', "Indicates when a chat request is made."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -520,14 +520,15 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), - ...alertFeatureBase + ...announcementFeatureBase }, }, }, 'accessibility.signals.chatResponseReceived': { - ...defaultNoAlert, + ...defaultNoAnnouncement, + additionalProperties: false, 'description': localize('accessibility.signals.chatResponseReceived', "Indicates when the response has been received."), 'properties': { 'audioCue': { @@ -544,9 +545,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.clear.alert', "Indicates when a feature is cleared."), - ...alertFeatureBase + ...announcementFeatureBase }, }, }, @@ -567,7 +568,7 @@ const configuration: IConfigurationNode = { localize('accessibility.signals.save.audioCue.never', "Never plays the audio cue.") ], }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.save.alert', "Indicates when a file is saved."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], @@ -581,7 +582,7 @@ const configuration: IConfigurationNode = { }, default: { 'audioCue': 'never', - 'alert': 'never' + 'announcement': 'never' } }, 'accessibility.signals.format': { @@ -601,7 +602,7 @@ const configuration: IConfigurationNode = { localize('accessibility.signals.format.never', "Never plays the audio cue.") ], }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.format.alert', "Indicates when a file or notebook is formatted."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], @@ -701,7 +702,7 @@ Registry.as(WorkbenchExtensions.ConfigurationMi }]); Registry.as(WorkbenchExtensions.ConfigurationMigration) - .registerConfigurationMigrations(AudioCue.allAudioCues.map(item => ({ + .registerConfigurationMigrations(AccessibilitySignal.allAccessibilitySignals.map(item => ({ key: item.settingsKey, migrateFn: (audioCue, accessor) => { const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts index 815ac45fe1d..b3237138f60 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts @@ -32,7 +32,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; export function descriptionForCommand(commandId: string, msg: string, noKbMsg: string, keybindingService: IKeybindingService): string { const kb = keybindingService.lookupKeybinding(commandId); @@ -104,7 +104,7 @@ export class NotificationAccessibleViewContribution extends Disposable { const accessibleViewService = accessor.get(IAccessibleViewService); const listService = accessor.get(IListService); const commandService = accessor.get(ICommandService); - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); function renderAccessibleView(): boolean { const notification = getNotificationFromContext(listService); @@ -165,7 +165,7 @@ export class NotificationAccessibleViewContribution extends Disposable { }, verbositySettingKey: AccessibilityVerbositySettingId.Notification, options: { type: AccessibleViewType.View }, - actions: getActionsFromNotification(notification, audioCueService) + actions: getActionsFromNotification(notification, accessibilitySignalService) }); return true; } @@ -174,7 +174,7 @@ export class NotificationAccessibleViewContribution extends Disposable { } } -function getActionsFromNotification(notification: INotificationViewItem, audioCueService: IAudioCueService): IAction[] | undefined { +function getActionsFromNotification(notification: INotificationViewItem, accessibilitySignalService: IAccessibilitySignalService): IAction[] | undefined { let actions = undefined; if (notification.actions) { actions = []; @@ -203,7 +203,7 @@ function getActionsFromNotification(notification: INotificationViewItem, audioCu actions.push({ id: 'clearNotification', label: localize('clearNotification', "Clear Notification"), tooltip: localize('clearNotification', "Clear Notification"), run: () => { notification.close(); - audioCueService.playAudioCue(AudioCue.clear); + accessibilitySignalService.playSignal(AccessibilitySignal.clear); }, enabled: true, class: ThemeIcon.asClassName(Codicon.clearAll) }); } diff --git a/src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts b/src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts index 32ef2425e81..87abfd5cb8d 100644 --- a/src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts +++ b/src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { SaveReason } from 'vs/workbench/common/editor'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; @@ -14,12 +14,12 @@ export class SaveAudioCueContribution extends Disposable implements IWorkbenchCo static readonly ID = 'workbench.contrib.saveAudioCues'; constructor( - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, ) { super(); this._register(this._workingCopyService.onDidSave((e) => { - this._audioCueService.playAudioCue(AudioCue.save, { userGesture: e.reason === SaveReason.EXPLICIT }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.save, { userGesture: e.reason === SaveReason.EXPLICIT }); })); } } diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts similarity index 90% rename from src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts rename to src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts index 9059db0b31f..4f916fb4d5c 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ShowAccessibilityAlertHelp, ShowAudioCueHelp } from 'vs/workbench/contrib/audioCues/browser/commands'; +import { ShowAccessibilityAnnouncementHelp, ShowAudioCueHelp } from 'vs/workbench/contrib/accessibilitySignals/browser/commands'; import { localize } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; @@ -11,14 +11,14 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { IAudioCueService, AudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; -import { AudioCueLineDebuggerContribution } from 'vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution'; -import { AudioCueLineFeatureContribution } from 'vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution'; +import { IAccessibilitySignalService, AccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; +import { AccessibilitySignalLineDebuggerContribution } from 'vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalDebuggerContribution'; +import { SignalLineFeatureContribution } from 'vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalLineFeatureContribution'; -registerSingleton(IAudioCueService, AudioCueService, InstantiationType.Delayed); +registerSingleton(IAccessibilitySignalService, AccessibilitySignalService, InstantiationType.Delayed); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AudioCueLineFeatureContribution, LifecyclePhase.Restored); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AudioCueLineDebuggerContribution, LifecyclePhase.Restored); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SignalLineFeatureContribution, LifecyclePhase.Restored); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AccessibilitySignalLineDebuggerContribution, LifecyclePhase.Restored); export const audioCueFeatureBase: IConfigurationPropertySchema = { 'type': 'string', @@ -178,5 +178,5 @@ Registry.as(ConfigurationExtensions.Configuration).regis }); registerAction2(ShowAudioCueHelp); -registerAction2(ShowAccessibilityAlertHelp); +registerAction2(ShowAccessibilityAnnouncementHelp); diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalDebuggerContribution.ts similarity index 77% rename from src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts rename to src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalDebuggerContribution.ts index 6150308ccfa..45d5f2b3121 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalDebuggerContribution.ts @@ -5,23 +5,23 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { autorunWithStore, observableFromEvent } from 'vs/base/common/observable'; -import { IAudioCueService, AudioCue, AudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService, AccessibilitySignal, AccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; -export class AudioCueLineDebuggerContribution +export class AccessibilitySignalLineDebuggerContribution extends Disposable implements IWorkbenchContribution { constructor( @IDebugService debugService: IDebugService, - @IAudioCueService private readonly audioCueService: AudioCueService, + @IAccessibilitySignalService private readonly accessibilitySignalService: AccessibilitySignalService, ) { super(); const isEnabled = observableFromEvent( - audioCueService.onEnabledChanged(AudioCue.onDebugBreak), - () => audioCueService.isCueEnabled(AudioCue.onDebugBreak) + accessibilitySignalService.onSoundEnabledChanged(AccessibilitySignal.onDebugBreak), + () => accessibilitySignalService.isSoundEnabled(AccessibilitySignal.onDebugBreak) ); this._register(autorunWithStore((reader, store) => { /** @description subscribe to debug sessions */ @@ -60,7 +60,7 @@ export class AudioCueLineDebuggerContribution const stoppedDetails = session.getStoppedDetails(); const BREAKPOINT_STOP_REASON = 'breakpoint'; if (stoppedDetails && stoppedDetails.reason === BREAKPOINT_STOP_REASON) { - this.audioCueService.playAudioCue(AudioCue.onDebugBreak); + this.accessibilitySignalService.playSignal(AccessibilitySignal.onDebugBreak); } }); diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalLineFeatureContribution.ts similarity index 77% rename from src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts rename to src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalLineFeatureContribution.ts index e7bba6b3f35..bff29815a40 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalLineFeatureContribution.ts @@ -12,7 +12,7 @@ import { Position } from 'vs/editor/common/core/position'; import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; import { ITextModel } from 'vs/editor/common/model'; import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; @@ -20,39 +20,39 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -export class AudioCueLineFeatureContribution +export class SignalLineFeatureContribution extends Disposable implements IWorkbenchContribution { private readonly store = this._register(new DisposableStore()); private readonly features: LineFeature[] = [ - this.instantiationService.createInstance(MarkerLineFeature, AudioCue.error, MarkerSeverity.Error), - this.instantiationService.createInstance(MarkerLineFeature, AudioCue.warning, MarkerSeverity.Warning), + this.instantiationService.createInstance(MarkerLineFeature, AccessibilitySignal.error, MarkerSeverity.Error), + this.instantiationService.createInstance(MarkerLineFeature, AccessibilitySignal.warning, MarkerSeverity.Warning), this.instantiationService.createInstance(FoldedAreaLineFeature), this.instantiationService.createInstance(BreakpointLineFeature), ]; - private readonly isEnabledCache = new CachedFunction>((cue) => observableFromEvent( - this.audioCueService.onEnabledChanged(cue), - () => this.audioCueService.isCueEnabled(cue) + private readonly isSoundEnabledCache = new CachedFunction>((cue) => observableFromEvent( + this.accessibilitySignalService.onSoundEnabledChanged(cue), + () => this.accessibilitySignalService.isSoundEnabled(cue) )); - private readonly isAlertEnabledCache = new CachedFunction>((cue) => observableFromEvent( - this.audioCueService.onAlertEnabledChanged(cue), - () => this.audioCueService.isAlertEnabled(cue) + private readonly isAnnouncmentEnabledCahce = new CachedFunction>((cue) => observableFromEvent( + this.accessibilitySignalService.onAnnouncementEnabledChanged(cue), + () => this.accessibilitySignalService.isAnnouncementEnabled(cue) )); constructor( @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IAudioCueService private readonly audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly accessibilitySignalService: IAccessibilitySignalService, @IConfigurationService private readonly _configurationService: IConfigurationService ) { super(); - const someAudioCueFeatureIsEnabled = derived( - (reader) => /** @description someAudioCueFeatureIsEnabled */ this.features.some((feature) => - this.isEnabledCache.get(feature.audioCue).read(reader) || this.isAlertEnabledCache.get(feature.audioCue).read(reader) + const someAccessibilitySignalIsEnabled = derived( + (reader) => /** @description someAccessibilitySignalFeatureIsEnabled */ this.features.some((feature) => + this.isSoundEnabledCache.get(feature.signal).read(reader) || this.isAnnouncmentEnabledCahce.get(feature.signal).read(reader) ) ); @@ -74,22 +74,22 @@ export class AudioCueLineFeatureContribution this._register( autorun(reader => { - /** @description updateAudioCuesEnabled */ + /** @description updateSignalsEnabled */ this.store.clear(); - if (!someAudioCueFeatureIsEnabled.read(reader)) { + if (!someAccessibilitySignalIsEnabled.read(reader)) { return; } const activeEditor = activeEditorObservable.read(reader); if (activeEditor) { - this.registerAudioCuesForEditor(activeEditor.editor, activeEditor.model, this.store); + this.registerAccessibilitySignalsForEditor(activeEditor.editor, activeEditor.model, this.store); } }) ); } - private registerAudioCuesForEditor( + private registerAccessibilitySignalsForEditor( editor: ICodeEditor, editorModel: ITextModel, store: DisposableStore @@ -109,7 +109,7 @@ export class AudioCueLineFeatureContribution return editor.getPosition(); } ); - const debouncedPosition = debouncedObservable(curPosition, this._configurationService.getValue('audioCues.debouncePositionChanges') ? 300 : 0, store); + const debouncedPosition = debouncedObservable(curPosition, this._configurationService.getValue('accessibility.signals.debouncePositionChanges') ? 300 : 0, store); const isTyping = wasEventTriggeredRecently( editorModel.onDidChangeContent.bind(editorModel), 1000, @@ -119,9 +119,9 @@ export class AudioCueLineFeatureContribution const featureStates = this.features.map((feature) => { const lineFeatureState = feature.getObservableState(editor, editorModel); const isFeaturePresent = derivedOpts( - { debugName: `isPresentInLine:${feature.audioCue.name}` }, + { debugName: `isPresentInLine:${feature.signal.name}` }, (reader) => { - if (!this.isEnabledCache.get(feature.audioCue).read(reader) && !this.isAlertEnabledCache.get(feature.audioCue).read(reader)) { + if (!this.isSoundEnabledCache.get(feature.signal).read(reader) && !this.isAnnouncmentEnabledCahce.get(feature.signal).read(reader)) { return false; } const position = debouncedPosition.read(reader); @@ -132,7 +132,7 @@ export class AudioCueLineFeatureContribution } ); return derivedOpts( - { debugName: `typingDebouncedFeatureState:\n${feature.audioCue.name}` }, + { debugName: `typingDebouncedFeatureState:\n${feature.signal.name}` }, (reader) => feature.debounceWhileTyping && isTyping.read(reader) ? (debouncedPosition.read(reader), isFeaturePresent.get()) @@ -154,21 +154,21 @@ export class AudioCueLineFeatureContribution store.add( autorunDelta(state, ({ lastValue, newValue }) => { - /** @description Play Audio Cue */ + /** @description Play Accessibility Signal */ const newFeatures = this.features.filter( feature => newValue?.featureStates.get(feature) && (!lastValue?.featureStates?.get(feature) || newValue.lineNumber !== lastValue.lineNumber) ); - this.audioCueService.playAudioCues(newFeatures.map(f => f.audioCue)); + this.accessibilitySignalService.playAccessibilitySignals(newFeatures.map(f => f.signal)); }) ); } } interface LineFeature { - audioCue: AudioCue; + signal: AccessibilitySignal; debounceWhileTyping?: boolean; getObservableState( editor: ICodeEditor, @@ -184,7 +184,7 @@ class MarkerLineFeature implements LineFeature { public readonly debounceWhileTyping = true; private _previousLine: number = 0; constructor( - public readonly audioCue: AudioCue, + public readonly signal: AccessibilitySignal, private readonly severity: MarkerSeverity, @IMarkerService private readonly markerService: IMarkerService, @@ -214,7 +214,7 @@ class MarkerLineFeature implements LineFeature { } class FoldedAreaLineFeature implements LineFeature { - public readonly audioCue = AudioCue.foldedArea; + public readonly signal = AccessibilitySignal.foldedArea; getObservableState(editor: ICodeEditor, model: ITextModel): IObservable { const foldingController = FoldingController.get(editor); @@ -241,7 +241,7 @@ class FoldedAreaLineFeature implements LineFeature { } class BreakpointLineFeature implements LineFeature { - public readonly audioCue = AudioCue.break; + public readonly signal = AccessibilitySignal.break; constructor(@IDebugService private readonly debugService: IDebugService) { } diff --git a/src/vs/workbench/contrib/audioCues/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts similarity index 55% rename from src/vs/workbench/contrib/audioCues/browser/commands.ts rename to src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 624c67985db..1236f98f57a 100644 --- a/src/vs/workbench/contrib/audioCues/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -8,7 +8,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { localize, localize2 } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { Action2 } from 'vs/platform/actions/common/actions'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; @@ -25,42 +25,42 @@ export class ShowAudioCueHelp extends Action2 { } override async run(accessor: ServicesAccessor): Promise { - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); const quickInputService = accessor.get(IQuickInputService); const configurationService = accessor.get(IConfigurationService); const accessibilityService = accessor.get(IAccessibilityService); - const userGestureCues = [AudioCue.save, AudioCue.format]; - const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.map((cue, idx) => ({ - label: userGestureCues.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.signalSettingsKey + '.audioCue')})` : cue.name, - audioCue: cue, - buttons: userGestureCues.includes(cue) ? [{ + const userGestureSignals = [AccessibilitySignal.save, AccessibilitySignal.format]; + const items: (IQuickPickItem & { audioCue: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.map((signal, idx) => ({ + label: userGestureSignals.includes(signal) ? `${signal.name} (${configurationService.getValue(signal.signalSettingsKey + '.sound')})` : signal.name, + audioCue: signal, + buttons: userGestureSignals.includes(signal) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), - tooltip: localize('audioCues.help.settings', 'Enable/Disable Audio Cue'), + tooltip: localize('sounds.help.settings', 'Enable/Disable Sound'), alwaysVisible: true }] : [] })); - const qp = quickInputService.createQuickPick(); + const qp = quickInputService.createQuickPick(); qp.items = items; - qp.selectedItems = items.filter(i => audioCueService.isCueEnabled(i.audioCue)); + qp.selectedItems = items.filter(i => accessibilitySignalService.isSoundEnabled(i.audioCue)); qp.onDidAccept(() => { const enabledCues = qp.selectedItems.map(i => i.audioCue); - const disabledCues = AudioCue.allAudioCues.filter(cue => !enabledCues.includes(cue)); + const disabledCues = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledCues.includes(cue)); for (const cue of enabledCues) { - if (!userGestureCues.includes(cue)) { - let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.signalSettingsKey); + if (!userGestureSignals.includes(cue)) { + let { audioCue, announcement } = configurationService.getValue<{ audioCue: string; announcement?: string }>(cue.signalSettingsKey); audioCue = accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'; - if (alert) { - configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); + if (announcement) { + configurationService.updateValue(cue.signalSettingsKey, { audioCue, announcement }); } else { configurationService.updateValue(cue.signalSettingsKey, { audioCue }); } } } for (const cue of disabledCues) { - const alert = cue.alertMessage ? configurationService.getValue(cue.signalSettingsKey + '.alert') : undefined; - const audioCue = userGestureCues.includes(cue) ? 'never' : 'off'; - if (alert) { - configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); + const announcement = cue.announcementMessage ? configurationService.getValue(cue.signalSettingsKey + '.announcement') : undefined; + const audioCue = userGestureSignals.includes(cue) ? 'never' : 'off'; + if (announcement) { + configurationService.updateValue(cue.signalSettingsKey, { audioCue, announcement }); } else { configurationService.updateValue(cue.signalSettingsKey, { audioCue }); } @@ -68,7 +68,7 @@ export class ShowAudioCueHelp extends Action2 { qp.hide(); }); qp.onDidChangeActive(() => { - audioCueService.playSound(qp.activeItems[0].audioCue.sound.getSound(true), true); + accessibilitySignalService.playSound(qp.activeItems[0].audioCue.sound.getSound(true), true); }); qp.placeholder = localize('audioCues.help.placeholder', 'Select an audio cue to play and configure'); qp.canSelectMany = true; @@ -76,49 +76,49 @@ export class ShowAudioCueHelp extends Action2 { } } -export class ShowAccessibilityAlertHelp extends Action2 { - static readonly ID = 'accessibility.alert.help'; +export class ShowAccessibilityAnnouncementHelp extends Action2 { + static readonly ID = 'accessibility.announcement.help'; constructor() { super({ - id: ShowAccessibilityAlertHelp.ID, - title: localize2('accessibility.alert.help', "Help: List Alerts"), + id: ShowAccessibilityAnnouncementHelp.ID, + title: localize2('accessibility.announcement.help', "Help: List Announcements"), f1: true, }); } override async run(accessor: ServicesAccessor): Promise { - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); const quickInputService = accessor.get(IQuickInputService); const configurationService = accessor.get(IConfigurationService); const accessibilityService = accessor.get(IAccessibilityService); - const userGestureAlerts = [AudioCue.save, AudioCue.format]; - const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.filter(c => c.alertSettingsKey).map((cue, idx) => ({ - label: userGestureAlerts.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.signalSettingsKey + '.alert')})` : cue.name, - audioCue: cue, - buttons: userGestureAlerts.includes(cue) ? [{ + const userGestureSignals = [AccessibilitySignal.save, AccessibilitySignal.format]; + const items: (IQuickPickItem & { audioCue: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.filter(c => !!c.announcementMessage).map((signal, idx) => ({ + label: userGestureSignals.includes(signal) ? `${signal.name} (${configurationService.getValue(signal.signalSettingsKey + '.announcement')})` : signal.name, + audioCue: signal, + buttons: userGestureSignals.includes(signal) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), - tooltip: localize('alert.help.settings', 'Enable/Disable Alert'), + tooltip: localize('announcement.help.settings', 'Enable/Disable Announcement'), alwaysVisible: true }] : [] })); - const qp = quickInputService.createQuickPick(); + const qp = quickInputService.createQuickPick(); qp.items = items; - qp.selectedItems = items.filter(i => audioCueService.isAlertEnabled(i.audioCue)); + qp.selectedItems = items.filter(i => accessibilitySignalService.isAnnouncementEnabled(i.audioCue)); qp.onDidAccept(() => { const enabledAlerts = qp.selectedItems.map(i => i.audioCue); - const disabledAlerts = AudioCue.allAudioCues.filter(cue => !enabledAlerts.includes(cue)); + const disabledAlerts = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledAlerts.includes(cue)); for (const cue of enabledAlerts) { - if (!userGestureAlerts.includes(cue)) { + if (!userGestureSignals.includes(cue)) { let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.signalSettingsKey); - alert = cue.alertMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; + alert = cue.announcementMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; if (alert) { configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); } } } for (const cue of disabledAlerts) { - const alert = userGestureAlerts.includes(cue) ? 'never' : 'off'; + const alert = userGestureSignals.includes(cue) ? 'never' : 'off'; const audioCue = configurationService.getValue(cue.signalSettingsKey + '.audioCue'); configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index c3c1f31f1f1..2736da158c5 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -8,7 +8,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize2 } from 'vs/nls'; import { Action2, IAction2Options, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; @@ -117,5 +117,5 @@ export function getNewChatAction(viewId: string, providerId: string) { } function announceChatCleared(accessor: ServicesAccessor): void { - accessor.get(IAudioCueService).playAudioCue(AudioCue.clear); + accessor.get(IAccessibilitySignalService).playSignal(AccessibilitySignal.clear); } diff --git a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts index fa522294c26..b6c66797247 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts @@ -6,7 +6,7 @@ import { status } from 'vs/base/browser/ui/aria/aria'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Disposable, DisposableMap, IDisposable } from 'vs/base/common/lifecycle'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; @@ -19,12 +19,12 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi private _requestId: number = 0; - constructor(@IAudioCueService private readonly _audioCueService: IAudioCueService, @IInstantiationService private readonly _instantiationService: IInstantiationService) { + constructor(@IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @IInstantiationService private readonly _instantiationService: IInstantiationService) { super(); } acceptRequest(): number { this._requestId++; - this._audioCueService.playAudioCue(AudioCue.chatRequestSent, { allowManyInParallel: true }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.chatRequestSent, { allowManyInParallel: true }); this._pendingCueMap.set(this._requestId, this._instantiationService.createInstance(AudioCueScheduler)); return this._requestId; } @@ -32,7 +32,7 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi this._pendingCueMap.deleteAndDispose(requestId); const isPanelChat = typeof response !== 'string'; const responseContent = typeof response === 'string' ? response : response?.response.asString(); - this._audioCueService.playAudioCue(AudioCue.chatResponseReceived, { allowManyInParallel: true }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.chatResponseReceived, { allowManyInParallel: true }); if (!response) { return; } @@ -49,10 +49,10 @@ const CHAT_RESPONSE_PENDING_ALLOWANCE_MS = 4000; class AudioCueScheduler extends Disposable { private _scheduler: RunOnceScheduler; private _audioCueLoop: IDisposable | undefined; - constructor(@IAudioCueService private readonly _audioCueService: IAudioCueService) { + constructor(@IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService) { super(); this._scheduler = new RunOnceScheduler(() => { - this._audioCueLoop = this._audioCueService.playAudioCueLoop(AudioCue.chatResponsePending, CHAT_RESPONSE_PENDING_AUDIO_CUE_LOOP_MS); + this._audioCueLoop = this._accessibilitySignalService.playSignalLoop(AccessibilitySignal.chatResponsePending, CHAT_RESPONSE_PENDING_AUDIO_CUE_LOOP_MS); }, CHAT_RESPONSE_PENDING_ALLOWANCE_MS); this._scheduler.schedule(); } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index eaa382ce67d..4eef489c49f 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -70,7 +70,7 @@ import { Variable } from 'vs/workbench/contrib/debug/common/debugModel'; import { ReplEvaluationResult, ReplGroup } from 'vs/workbench/contrib/debug/common/replModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; const $ = dom.$; @@ -989,9 +989,9 @@ registerAction2(class extends ViewAction { } runInView(_accessor: ServicesAccessor, view: Repl): void { - const audioCueService = _accessor.get(IAudioCueService); + const accessibilitySignalService = _accessor.get(IAccessibilitySignalService); view.clearRepl(); - audioCueService.playAudioCue(AudioCue.clear); + accessibilitySignalService.playSignal(AccessibilitySignal.clear); } }); diff --git a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts index 27594f7cd7c..86fbc23d978 100644 --- a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts @@ -23,7 +23,7 @@ import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { TestContextService, TestMarkerService } from 'vs/workbench/test/common/workbenchTestServices'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; -import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; suite('EditorAutoSave', () => { @@ -43,7 +43,7 @@ suite('EditorAutoSave', () => { const configurationService = new TestConfigurationService(); configurationService.setUserConfiguration('files', autoSaveConfig); instantiationService.stub(IConfigurationService, configurationService); - instantiationService.stub(IAudioCueService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); + instantiationService.stub(IAccessibilitySignalService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); instantiationService.stub(IFilesConfigurationService, disposables.add(new TestFilesConfigurationService( instantiationService.createInstance(MockContextKeyService), configurationService, diff --git a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts index 16e3d189f00..bd8db57491b 100644 --- a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts +++ b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts @@ -15,7 +15,7 @@ import { InlayHintItem, asCommandLink } from 'vs/editor/contrib/inlayHints/brows import { InlayHintsController } from 'vs/editor/contrib/inlayHints/browser/inlayHintsController'; import { localize, localize2 } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -40,7 +40,7 @@ export class InlayHintsAccessibility implements IEditorContribution { constructor( private readonly _editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @IInstantiationService private readonly _instaService: IInstantiationService, ) { this._ariaElement = document.createElement('span'); @@ -156,7 +156,7 @@ export class InlayHintsAccessibility implements IEditorContribution { const line = this._editor.getPosition().lineNumber; const hints = InlayHintsController.get(this._editor)?.getInlayHintsForLine(line); if (!hints || hints.length === 0) { - this._audioCueService.playAudioCue(AudioCue.noInlayHints); + this._accessibilitySignalService.playSignal(AccessibilitySignal.noInlayHints); } else { this._read(line, hints); } diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts index a5736966b8a..dbc9e88e9a4 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts @@ -9,7 +9,7 @@ import { ResourceMap } from 'vs/base/common/map'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; @@ -38,7 +38,7 @@ export class NotebookExecutionStateService extends Disposable implements INotebo @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILogService private readonly _logService: ILogService, @INotebookService private readonly _notebookService: INotebookService, - @IAudioCueService private readonly _audioCueService: IAudioCueService + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService ) { super(); } @@ -112,11 +112,11 @@ export class NotebookExecutionStateService extends Disposable implements INotebo if (lastRunSuccess !== undefined) { if (lastRunSuccess) { if (this._executions.size === 0) { - this._audioCueService.playAudioCue(AudioCue.notebookCellCompleted); + this._accessibilitySignalService.playSignal(AccessibilitySignal.notebookCellCompleted); } this._clearLastFailedCell(notebookUri); } else { - this._audioCueService.playAudioCue(AudioCue.notebookCellFailed); + this._accessibilitySignalService.playSignal(AccessibilitySignal.notebookCellFailed); this._setLastFailedCell(notebookUri, cellHandle); } } diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 5d31bf65850..794a8e60d57 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -29,7 +29,7 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; // Register Service registerSingleton(IOutputService, OutputService, InstantiationType.Delayed); @@ -224,11 +224,11 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { } async run(accessor: ServicesAccessor): Promise { const outputService = accessor.get(IOutputService); - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); const activeChannel = outputService.getActiveChannel(); if (activeChannel) { activeChannel.clear(); - audioCueService.playAudioCue(AudioCue.clear); + accessibilitySignalService.playSignal(AccessibilitySignal.clear); } } })); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index 54ac57ce224..cf33ec25163 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -222,9 +222,9 @@ export const tocData: ITOCEntry = { settings: ['notebook.*', 'interactiveWindow.*'] }, { - id: 'features/audioCues', - label: localize('audioCues', 'Audio Cues'), - settings: ['audioCues.*'] + id: 'features/signals', + label: localize('signals', 'Signals'), + settings: ['signals.*'] }, { id: 'features/accessibilitySignals', diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index e99121e3d6d..f9963ff0340 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -54,7 +54,7 @@ import { IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; import { Color } from 'vs/base/common/color'; import { ResourceMap } from 'vs/base/common/map'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IQuickDiffService, QuickDiff } from 'vs/workbench/contrib/scm/common/quickDiff'; import { IQuickDiffSelectItem, SwitchQuickDiffBaseAction, SwitchQuickDiffViewItem } from 'vs/workbench/contrib/scm/browser/dirtyDiffSwitcher'; @@ -578,7 +578,7 @@ export class GotoPreviousChangeAction extends EditorAction { async run(accessor: ServicesAccessor): Promise { const outerEditor = getOuterEditorFromDiffEditor(accessor); - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); const accessibilityService = accessor.get(IAccessibilityService); const codeEditorService = accessor.get(ICodeEditorService); @@ -600,7 +600,7 @@ export class GotoPreviousChangeAction extends EditorAction { const index = model.findPreviousClosestChange(lineNumber, false); const change = model.changes[index]; - await playAudioCueForChange(change.change, audioCueService); + await playAudioCueForChange(change.change, accessibilitySignalService); setPositionAndSelection(change.change, outerEditor, accessibilityService, codeEditorService); } } @@ -619,7 +619,7 @@ export class GotoNextChangeAction extends EditorAction { } async run(accessor: ServicesAccessor): Promise { - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); const outerEditor = getOuterEditorFromDiffEditor(accessor); const accessibilityService = accessor.get(IAccessibilityService); const codeEditorService = accessor.get(ICodeEditorService); @@ -643,7 +643,7 @@ export class GotoNextChangeAction extends EditorAction { const index = model.findNextClosestChange(lineNumber, false); const change = model.changes[index].change; - await playAudioCueForChange(change, audioCueService); + await playAudioCueForChange(change, accessibilitySignalService); setPositionAndSelection(change, outerEditor, accessibilityService, codeEditorService); } } @@ -658,17 +658,17 @@ function setPositionAndSelection(change: IChange, editor: ICodeEditor, accessibi } } -async function playAudioCueForChange(change: IChange, audioCueService: IAudioCueService) { +async function playAudioCueForChange(change: IChange, accessibilitySignalService: IAccessibilitySignalService) { const changeType = getChangeType(change); switch (changeType) { case ChangeType.Add: - audioCueService.playAudioCue(AudioCue.diffLineInserted, { allowManyInParallel: true, source: 'dirtyDiffDecoration' }); + accessibilitySignalService.playSignal(AccessibilitySignal.diffLineInserted, { allowManyInParallel: true, source: 'dirtyDiffDecoration' }); break; case ChangeType.Delete: - audioCueService.playAudioCue(AudioCue.diffLineDeleted, { allowManyInParallel: true, source: 'dirtyDiffDecoration' }); + accessibilitySignalService.playSignal(AccessibilitySignal.diffLineDeleted, { allowManyInParallel: true, source: 'dirtyDiffDecoration' }); break; case ChangeType.Modify: - audioCueService.playAudioCue(AudioCue.diffLineModified, { allowManyInParallel: true, source: 'dirtyDiffDecoration' }); + accessibilitySignalService.playSignal(AccessibilitySignal.diffLineModified, { allowManyInParallel: true, source: 'dirtyDiffDecoration' }); break; } } diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index fc890846182..654ac8d1ed2 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -80,7 +80,7 @@ import { TextSearchCompleteMessage } from 'vs/workbench/services/search/common/s import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { ILogService } from 'vs/platform/log/common/log'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; const $ = dom.$; @@ -192,7 +192,7 @@ export class SearchView extends ViewPane { @ITelemetryService telemetryService: ITelemetryService, @INotebookService private readonly notebookService: INotebookService, @ILogService private readonly logService: ILogService, - @IAudioCueService private readonly audioCueService: IAudioCueService + @IAccessibilitySignalService private readonly accessibilitySignalService: IAccessibilitySignalService ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); @@ -1279,7 +1279,7 @@ export class SearchView extends ViewPane { this.viewModel.cancelSearch(); this.tree.ariaLabel = nls.localize('emptySearch', "Empty Search"); - this.audioCueService.playAudioCue(AudioCue.clear); + this.accessibilitySignalService.playSignal(AccessibilitySignal.clear); this.reLayout(); } diff --git a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts index df714767124..12767f599ae 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts @@ -14,7 +14,7 @@ import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/termina import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { spinningLoading } from 'vs/platform/theme/common/iconRegistry'; import { IMarker } from 'vs/platform/terminal/common/capabilities/capabilities'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ITerminalStatus } from 'vs/workbench/contrib/terminal/common/terminal'; interface ITerminalData { @@ -40,7 +40,7 @@ const INFO_INACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID export class TaskTerminalStatus extends Disposable { private terminalMap: Map = new Map(); private _marker: IMarker | undefined; - constructor(@ITaskService taskService: ITaskService, @IAudioCueService private readonly _audioCueService: IAudioCueService) { + constructor(@ITaskService taskService: ITaskService, @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService) { super(); this._register(taskService.onDidStateChange((event) => { switch (event.kind) { @@ -95,7 +95,7 @@ export class TaskTerminalStatus extends Disposable { terminalData.taskRunEnded = true; terminalData.terminal.statusList.remove(terminalData.status); if ((event.exitCode === 0) && (terminalData.problemMatcher.numberOfMatches === 0)) { - this._audioCueService.playAudioCue(AudioCue.taskCompleted); + this._accessibilitySignalService.playSignal(AccessibilitySignal.taskCompleted); if (terminalData.task.configurationProperties.isBackground) { for (const status of terminalData.terminal.statusList.statuses) { terminalData.terminal.statusList.remove(status); @@ -104,7 +104,7 @@ export class TaskTerminalStatus extends Disposable { terminalData.terminal.statusList.add(SUCCEEDED_TASK_STATUS); } } else if (event.exitCode || terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Error) { - this._audioCueService.playAudioCue(AudioCue.taskFailed); + this._accessibilitySignalService.playSignal(AccessibilitySignal.taskFailed); terminalData.terminal.statusList.add(FAILED_TASK_STATUS); } else if (terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Warning) { terminalData.terminal.statusList.add(WARNING_TASK_STATUS); @@ -120,10 +120,10 @@ export class TaskTerminalStatus extends Disposable { } terminalData.terminal.statusList.remove(terminalData.status); if (terminalData.problemMatcher.numberOfMatches === 0) { - this._audioCueService.playAudioCue(AudioCue.taskCompleted); + this._accessibilitySignalService.playSignal(AccessibilitySignal.taskCompleted); terminalData.terminal.statusList.add(SUCCEEDED_INACTIVE_TASK_STATUS); } else if (terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Error) { - this._audioCueService.playAudioCue(AudioCue.taskFailed); + this._accessibilitySignalService.playSignal(AccessibilitySignal.taskFailed); terminalData.terminal.statusList.add(FAILED_INACTIVE_TASK_STATUS); } else if (terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Warning) { terminalData.terminal.statusList.add(WARNING_INACTIVE_TASK_STATUS); diff --git a/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts b/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts index 5eeee32c264..9efbc2ac32d 100644 --- a/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts +++ b/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts @@ -47,7 +47,7 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; interface IWorkspaceFolderConfigurationResult { workspaceFolder: IWorkspaceFolder; @@ -92,7 +92,7 @@ export class TaskService extends AbstractTaskService { @IThemeService themeService: IThemeService, @IInstantiationService instantiationService: IInstantiationService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IAudioCueService audioCueService: IAudioCueService + @IAccessibilitySignalService accessibilitySignalService: IAccessibilitySignalService ) { super(configurationService, markerService, diff --git a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts index 5e413afc9c1..c3bf26252fd 100644 --- a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts +++ b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts @@ -7,7 +7,7 @@ import { ok } from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ACTIVE_TASK_STATUS, FAILED_TASK_STATUS, SUCCEEDED_TASK_STATUS, TaskTerminalStatus } from 'vs/workbench/contrib/tasks/browser/taskTerminalStatus'; @@ -28,8 +28,8 @@ class TestTaskService implements Partial { } } -class TestAudioCueService implements Partial { - async playAudioCue(cue: AudioCue): Promise { +class TestaccessibilitySignalService implements Partial { + async playSignal(cue: AccessibilitySignal): Promise { return; } } @@ -74,13 +74,13 @@ suite('Task Terminal Status', () => { let testTerminal: ITerminalInstance; let testTask: Task; let problemCollector: AbstractProblemCollector; - let audioCueService: TestAudioCueService; + let accessibilitySignalService: TestaccessibilitySignalService; const store = ensureNoDisposablesAreLeakedInTestSuite(); setup(() => { instantiationService = store.add(new TestInstantiationService()); taskService = new TestTaskService(); - audioCueService = new TestAudioCueService(); - taskTerminalStatus = store.add(new TaskTerminalStatus(taskService as any, audioCueService as any)); + accessibilitySignalService = new TestaccessibilitySignalService(); + taskTerminalStatus = store.add(new TaskTerminalStatus(taskService as any, accessibilitySignalService as any)); testTerminal = store.add(instantiationService.createInstance(TestTerminal) as any); testTask = instantiationService.createInstance(TestTask) as unknown as Task; problemCollector = store.add(instantiationService.createInstance(TestProblemCollector) as any); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 9bd13171c8f..e46e771f2a8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -26,7 +26,7 @@ import { URI } from 'vs/base/common/uri'; import { TabFocus } from 'vs/editor/browser/config/tabFocus'; import * as nls from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -363,7 +363,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { @ITelemetryService private readonly _telemetryService: ITelemetryService, @IOpenerService private readonly _openerService: IOpenerService, @ICommandService private readonly _commandService: ICommandService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService ) { super(); @@ -761,7 +761,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { tooltip: nls.localize('bellStatus', "Bell") }, this._configHelper.config.bellDuration); } - this._audioCueService.playAudioCue(AudioCue.terminalBell); + this._accessibilitySignalService.playSignal(AccessibilitySignal.terminalBell); })); }, 1000, this._store); this._register(xterm.raw.onSelectionChange(async () => this._onSelectionChange())); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index da0a7fc9d0c..9b025bd50b6 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -9,7 +9,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -52,7 +52,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { @ILifecycleService lifecycleService: ILifecycleService, @ICommandService private readonly _commandService: ICommandService, @IInstantiationService instantiationService: IInstantiationService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @INotificationService private readonly _notificationService: INotificationService ) { super(); @@ -219,7 +219,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { commandDetectionListeners.push(capability.onCommandFinished(command => { this.registerCommandDecoration(command); if (command.exitCode) { - this._audioCueService.playAudioCue(AudioCue.terminalCommandFailed); + this._accessibilitySignalService.playSignal(AccessibilitySignal.terminalCommandFailed); } })); // Command invalidated diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 6f9028565c5..0f2ef8f01f4 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -43,7 +43,7 @@ import { debounce } from 'vs/base/common/decorators'; import { MouseWheelClassifier } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { IMouseWheelEvent, StandardWheelEvent } from 'vs/base/browser/mouseEvent'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; const enum RenderConstants { /** @@ -205,7 +205,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach @ITelemetryService private readonly _telemetryService: ITelemetryService, @IClipboardService private readonly _clipboardService: IClipboardService, @IContextKeyService contextKeyService: IContextKeyService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @ILayoutService layoutService: ILayoutService ) { super(); @@ -584,7 +584,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach // the prompt being written this._capabilities.get(TerminalCapability.CommandDetection)?.handlePromptStart(); this._capabilities.get(TerminalCapability.CommandDetection)?.handleCommandStart(); - this._audioCueService.playAudioCue(AudioCue.clear); + this._accessibilitySignalService.playSignal(AccessibilitySignal.clear); } hasSelection(): boolean { diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts index 833db09d807..51fb47c8c6e 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts @@ -30,7 +30,7 @@ import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecy import { TestLayoutService, TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestLoggerService } from 'vs/workbench/test/common/workbenchTestServices'; import type { Terminal } from '@xterm/xterm'; -import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; const defaultTerminalConfig: Partial = { fontFamily: 'monospace', @@ -67,7 +67,7 @@ suite('Buffer Content Tracker', () => { instantiationService.stub(IContextMenuService, store.add(instantiationService.createInstance(ContextMenuService))); instantiationService.stub(ILifecycleService, store.add(new TestLifecycleService())); instantiationService.stub(IContextKeyService, store.add(new MockContextKeyService())); - instantiationService.stub(IAudioCueService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); + instantiationService.stub(IAccessibilitySignalService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); instantiationService.stub(ILayoutService, new TestLayoutService()); configHelper = store.add(instantiationService.createInstance(TerminalConfigHelper)); diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts index ea488b6fe0e..21c62777054 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts @@ -20,7 +20,7 @@ import type { IDecoration, Terminal } from '@xterm/xterm'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IActionWidgetService } from 'vs/platform/actionWidget/browser/actionWidget'; import { ActionSet } from 'vs/platform/actionWidget/common/actionWidget'; import { getLinesForCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; @@ -77,7 +77,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, @ITerminalQuickFixService private readonly _quickFixService: ITerminalQuickFixService, @ICommandService private readonly _commandService: ICommandService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @IOpenerService private readonly _openerService: IOpenerService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IExtensionService private readonly _extensionService: IExtensionService, @@ -284,7 +284,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, e.classList.add(...ThemeIcon.asClassNameArray(isExplainOnly ? Codicon.sparkle : Codicon.lightBulb)); updateLayout(this._configurationService, e); - this._audioCueService.playAudioCue(AudioCue.terminalQuickFix); + this._accessibilitySignalService.playSignal(AccessibilitySignal.terminalQuickFix); const parentElement = (e.closest('.xterm') as HTMLElement).parentElement; if (!parentElement) { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index d9b0f8df766..be911f2f4c5 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -170,7 +170,7 @@ import { EditorParts } from 'vs/workbench/browser/parts/editor/editorParts'; import { mainWindow } from 'vs/base/browser/window'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IEditorPaneService } from 'vs/workbench/services/editor/common/editorPaneService'; import { EditorPaneService } from 'vs/workbench/services/editor/browser/editorPaneService'; @@ -282,7 +282,7 @@ export function workbenchInstantiationService( instantiationService.stub(IDialogService, new TestDialogService()); const accessibilityService = new TestAccessibilityService(); instantiationService.stub(IAccessibilityService, accessibilityService); - instantiationService.stub(IAudioCueService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); + instantiationService.stub(IAccessibilitySignalService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); instantiationService.stub(IFileDialogService, instantiationService.createInstance(TestFileDialogService)); instantiationService.stub(ILanguageService, disposables.add(instantiationService.createInstance(LanguageService))); instantiationService.stub(ILanguageFeaturesService, new LanguageFeaturesService()); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 6aa1bf45c0d..b83c1fcd894 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -369,7 +369,7 @@ import 'vs/workbench/contrib/workspaces/browser/workspaces.contribution'; import 'vs/workbench/contrib/list/browser/list.contribution'; // Audio Cues -import 'vs/workbench/contrib/audioCues/browser/audioCues.contribution'; +import 'vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution'; // Deprecated Extension Migrator import 'vs/workbench/contrib/deprecatedExtensionMigrator/browser/deprecatedExtensionMigrator.contribution'; From 2f6b7e163c93b73564bb3f413b3f39e1d0028d0d Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:40:02 +0100 Subject: [PATCH 1137/1897] Git - Update showProgress value in DiffOperation (#205012) --- extensions/git/src/operation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/operation.ts b/extensions/git/src/operation.ts index ead345c0b06..223f1945b02 100644 --- a/extensions/git/src/operation.ts +++ b/extensions/git/src/operation.ts @@ -146,7 +146,7 @@ export const Operation = { DeleteRef: { kind: OperationKind.DeleteRef, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteRefOperation, DeleteRemoteTag: { kind: OperationKind.DeleteRemoteTag, blocking: false, readOnly: false, remote: true, retry: false, showProgress: true } as DeleteRemoteTagOperation, DeleteTag: { kind: OperationKind.DeleteTag, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteTagOperation, - Diff: { kind: OperationKind.Diff, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as DiffOperation, + Diff: { kind: OperationKind.Diff, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as DiffOperation, Fetch: (showProgress: boolean) => ({ kind: OperationKind.Fetch, blocking: false, readOnly: false, remote: true, retry: true, showProgress } as FetchOperation), FindTrackingBranches: { kind: OperationKind.FindTrackingBranches, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as FindTrackingBranchesOperation, GetBranch: { kind: OperationKind.GetBranch, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as GetBranchOperation, From c7648aaf427f31455d03c7021a8f963d8ec248cd Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 10:45:44 -0600 Subject: [PATCH 1138/1897] replace most instances of audio cues --- src/vs/editor/common/standaloneStrings.ts | 4 +- .../browser/accessibilitySignalService.ts | 74 +++++++++---------- .../browser/accessibilityConfiguration.ts | 21 +++++- .../browser/editorAccessibilityHelp.ts | 2 +- .../accessibilitySignal.contribution.ts | 10 +-- .../accessibilitySignals/browser/commands.ts | 10 +-- .../browser/actions/chatAccessibilityHelp.ts | 2 +- .../codeEditor/browser/diffEditorHelper.ts | 4 +- src/vs/workbench/workbench.common.main.ts | 2 +- 9 files changed, 71 insertions(+), 58 deletions(-) diff --git a/src/vs/editor/common/standaloneStrings.ts b/src/vs/editor/common/standaloneStrings.ts index c6472a81899..1bcfecfeb97 100644 --- a/src/vs/editor/common/standaloneStrings.ts +++ b/src/vs/editor/common/standaloneStrings.ts @@ -25,8 +25,8 @@ export namespace AccessibilityHelpNLS { export const tabFocusModeOffMsg = nls.localize("tabFocusModeOffMsg", "Pressing Tab in the current editor will insert the tab character. Toggle this behavior {0}."); export const tabFocusModeOffMsgNoKb = nls.localize("tabFocusModeOffMsgNoKb", "Pressing Tab in the current editor will insert the tab character. The command {0} is currently not triggerable by a keybinding."); export const showAccessibilityHelpAction = nls.localize("showAccessibilityHelpAction", "Show Accessibility Help"); - export const listAudioCues = nls.localize("listAudioCuesCommand", "Run the command: List Audio Cues for an overview of all audio cues and their current status."); - export const listAlerts = nls.localize("listAlertsCommand", "Run the command: List Alerts for an overview of alerts and their current status."); + export const listSignalSounds = nls.localize("listSignalSoundsCommand", "Run the command: List Signal Sounds for an overview of all sounds and their current status."); + export const listAlerts = nls.localize("listAnnouncementsCommand", "Run the command: List Signal Announcements for an overview of announcements and their current status."); export const quickChat = nls.localize("quickChatCommand", "Toggle quick chat ({0}) to open or close a chat session."); export const quickChatNoKb = nls.localize("quickChatCommandNoKb", "Toggle quick chat is not currently triggerable by a keybinding."); export const startInlineChat = nls.localize("startInlineChatCommand", "Start inline chat ({0}) to create an in editor chat session."); diff --git a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts index 9bbbf0b1db9..503a178b46d 100644 --- a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts +++ b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts @@ -63,14 +63,14 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi } if (this.isSoundEnabled(signal, options.userGesture)) { - this.sendAudioCueTelemetry(signal, options.source); + this.sendSignalTelemetry(signal, options.source); await this.playSound(signal.sound.getSound(), options.allowManyInParallel); } } public async playAccessibilitySignals(cues: (AccessibilitySignal | { cue: AccessibilitySignal; source: string })[]): Promise { for (const cue of cues) { - this.sendAudioCueTelemetry('cue' in cue ? cue.cue : cue, 'source' in cue ? cue.source : undefined); + this.sendSignalTelemetry('cue' in cue ? cue.cue : cue, 'source' in cue ? cue.source : undefined); } const cueArray = cues.map(c => 'cue' in c ? c.cue : c); const alerts = cueArray.filter(cue => this.isAnnouncementEnabled(cue)).map(c => c.announcementMessage); @@ -78,14 +78,14 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi this.accessibilityService.status(alerts.join(', ')); } - // Some audio cues might reuse sounds. Don't play the same sound twice. + // Some sounds are reused. Don't play the same sound twice. const sounds = new Set(cueArray.filter(cue => this.isSoundEnabled(cue)).map(cue => cue.sound.getSound())); await Promise.all(Array.from(sounds).map(sound => this.playSound(sound, true))); } - private sendAudioCueTelemetry(cue: AccessibilitySignal, source: string | undefined): void { + private sendSignalTelemetry(cue: AccessibilitySignal, source: string | undefined): void { const isScreenReaderOptimized = this.accessibilityService.isScreenReaderOptimized(); const key = cue.name + (source ? `::${source}` : '') + (isScreenReaderOptimized ? '{screenReaderOptimized}' : ''); // Only send once per user session @@ -95,19 +95,19 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi this.sentTelemetry.add(key); this.telemetryService.publicLog2<{ - audioCue: string; + signal: string; source: string; isScreenReaderOptimized: boolean; }, { owner: 'hediet'; - audioCue: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The audio cue that was played.' }; - source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source that triggered the audio cue (e.g. "diffEditorNavigation").' }; + signal: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The signal that was played.' }; + source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source that triggered the signal (e.g. "diffEditorNavigation").' }; isScreenReaderOptimized: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user is using a screen reader' }; - comment: 'This data is collected to understand how audio cues are used and if more audio cues should be added.'; - }>('audioCue.played', { - audioCue: cue.name, + comment: 'This data is collected to understand how signals are used and if more signals should be added.'; + }>('signal.played', { + signal: cue.name, source: source ?? '', isScreenReaderOptimized, }); @@ -183,7 +183,7 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.signal.signalSettingsKey + '.audioCue') ); return derived(reader => { - /** @description audio cue enabled */ + /** @description sound enabled */ const setting = settingObservable.read(reader); if ( setting === 'on' || @@ -363,17 +363,17 @@ export class AccessibilitySignal { sound: Sound | { /** * Gaming and other apps often play a sound variant when the same event happens again - * for an improved experience. This option enables audio cues to play a random sound. + * for an improved experience. This option enables playing a random sound. */ randomOneOf: Sound[]; }; - legacyAudioCueSettingsKey: string; + legacySoundSettingsKey: string; settingsKey: string; legacyAnnouncementSettingsKey?: AccessibilityAlertSettingId; announcementMessage?: string; }): AccessibilitySignal { const soundSource = new SoundSource('randomOneOf' in options.sound ? options.sound.randomOneOf : [options.sound]); - const signal = new AccessibilitySignal(soundSource, options.name, options.legacyAudioCueSettingsKey, options.settingsKey, options.legacyAnnouncementSettingsKey, options.announcementMessage); + const signal = new AccessibilitySignal(soundSource, options.name, options.legacySoundSettingsKey, options.settingsKey, options.legacyAnnouncementSettingsKey, options.announcementMessage); AccessibilitySignal._signals.add(signal); return signal; } @@ -385,7 +385,7 @@ export class AccessibilitySignal { public static readonly error = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasError.name', 'Error on Line'), sound: Sound.error, - legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasError', + legacySoundSettingsKey: 'accessibilitySignals.lineHasError', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Error, announcementMessage: localize('accessibility.signals.lineHasError', 'Error'), settingsKey: 'accessibility.signals.lineHasError' @@ -393,7 +393,7 @@ export class AccessibilitySignal { public static readonly warning = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasWarning.name', 'Warning on Line'), sound: Sound.warning, - legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasWarning', + legacySoundSettingsKey: 'accessibilitySignals.lineHasWarning', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Warning, announcementMessage: localize('accessibility.signals.lineHasWarning', 'Warning'), settingsKey: 'accessibility.signals.lineHasWarning' @@ -401,7 +401,7 @@ export class AccessibilitySignal { public static readonly foldedArea = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasFoldedArea.name', 'Folded Area on Line'), sound: Sound.foldedArea, - legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasFoldedArea', + legacySoundSettingsKey: 'accessibilitySignals.lineHasFoldedArea', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.FoldedArea, announcementMessage: localize('accessibility.signals.lineHasFoldedArea', 'Folded'), settingsKey: 'accessibility.signals.lineHasFoldedArea' @@ -409,7 +409,7 @@ export class AccessibilitySignal { public static readonly break = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasBreakpoint.name', 'Breakpoint on Line'), sound: Sound.break, - legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasBreakpoint', + legacySoundSettingsKey: 'accessibilitySignals.lineHasBreakpoint', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Breakpoint, announcementMessage: localize('accessibility.signals.lineHasBreakpoint', 'Breakpoint'), settingsKey: 'accessibility.signals.lineHasBreakpoint' @@ -417,14 +417,14 @@ export class AccessibilitySignal { public static readonly inlineSuggestion = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), sound: Sound.quickFixes, - legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasInlineSuggestion', + legacySoundSettingsKey: 'accessibilitySignals.lineHasInlineSuggestion', settingsKey: 'accessibility.signals.lineHasInlineSuggestion' }); public static readonly terminalQuickFix = AccessibilitySignal.register({ name: localize('accessibilitySignals.terminalQuickFix.name', 'Terminal Quick Fix'), sound: Sound.quickFixes, - legacyAudioCueSettingsKey: 'accessibilitySignals.terminalQuickFix', + legacySoundSettingsKey: 'accessibilitySignals.terminalQuickFix', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, announcementMessage: localize('accessibility.signals.terminalQuickFix', 'Quick Fix'), settingsKey: 'accessibility.signals.terminalQuickFix' @@ -433,7 +433,7 @@ export class AccessibilitySignal { public static readonly onDebugBreak = AccessibilitySignal.register({ name: localize('accessibilitySignals.onDebugBreak.name', 'Debugger Stopped on Breakpoint'), sound: Sound.break, - legacyAudioCueSettingsKey: 'accessibilitySignals.onDebugBreak', + legacySoundSettingsKey: 'accessibilitySignals.onDebugBreak', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, announcementMessage: localize('accessibility.signals.onDebugBreak', 'Breakpoint'), settingsKey: 'accessibility.signals.onDebugBreak' @@ -442,7 +442,7 @@ export class AccessibilitySignal { public static readonly noInlayHints = AccessibilitySignal.register({ name: localize('accessibilitySignals.noInlayHints', 'No Inlay Hints on Line'), sound: Sound.error, - legacyAudioCueSettingsKey: 'accessibilitySignals.noInlayHints', + legacySoundSettingsKey: 'accessibilitySignals.noInlayHints', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NoInlayHints, announcementMessage: localize('accessibility.signals.noInlayHints', 'No Inlay Hints'), settingsKey: 'accessibility.signals.noInlayHints' @@ -451,7 +451,7 @@ export class AccessibilitySignal { public static readonly taskCompleted = AccessibilitySignal.register({ name: localize('accessibilitySignals.taskCompleted', 'Task Completed'), sound: Sound.taskCompleted, - legacyAudioCueSettingsKey: 'accessibilitySignals.taskCompleted', + legacySoundSettingsKey: 'accessibilitySignals.taskCompleted', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TaskCompleted, announcementMessage: localize('accessibility.signals.taskCompleted', 'Task Completed'), settingsKey: 'accessibility.signals.taskCompleted' @@ -460,7 +460,7 @@ export class AccessibilitySignal { public static readonly taskFailed = AccessibilitySignal.register({ name: localize('accessibilitySignals.taskFailed', 'Task Failed'), sound: Sound.taskFailed, - legacyAudioCueSettingsKey: 'accessibilitySignals.taskFailed', + legacySoundSettingsKey: 'accessibilitySignals.taskFailed', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TaskFailed, announcementMessage: localize('accessibility.signals.taskFailed', 'Task Failed'), settingsKey: 'accessibility.signals.taskFailed' @@ -469,7 +469,7 @@ export class AccessibilitySignal { public static readonly terminalCommandFailed = AccessibilitySignal.register({ name: localize('accessibilitySignals.terminalCommandFailed', 'Terminal Command Failed'), sound: Sound.error, - legacyAudioCueSettingsKey: 'accessibilitySignals.terminalCommandFailed', + legacySoundSettingsKey: 'accessibilitySignals.terminalCommandFailed', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, announcementMessage: localize('accessibility.signals.terminalCommandFailed', 'Command Failed'), settingsKey: 'accessibility.signals.terminalCommandFailed' @@ -478,7 +478,7 @@ export class AccessibilitySignal { public static readonly terminalBell = AccessibilitySignal.register({ name: localize('accessibilitySignals.terminalBell', 'Terminal Bell'), sound: Sound.terminalBell, - legacyAudioCueSettingsKey: 'accessibilitySignals.terminalBell', + legacySoundSettingsKey: 'accessibilitySignals.terminalBell', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalBell, announcementMessage: localize('accessibility.signals.terminalBell', 'Terminal Bell'), settingsKey: 'accessibility.signals.terminalBell' @@ -487,7 +487,7 @@ export class AccessibilitySignal { public static readonly notebookCellCompleted = AccessibilitySignal.register({ name: localize('accessibilitySignals.notebookCellCompleted', 'Notebook Cell Completed'), sound: Sound.taskCompleted, - legacyAudioCueSettingsKey: 'accessibilitySignals.notebookCellCompleted', + legacySoundSettingsKey: 'accessibilitySignals.notebookCellCompleted', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, announcementMessage: localize('accessibility.signals.notebookCellCompleted', 'Notebook Cell Completed'), settingsKey: 'accessibility.signals.notebookCellCompleted' @@ -496,7 +496,7 @@ export class AccessibilitySignal { public static readonly notebookCellFailed = AccessibilitySignal.register({ name: localize('accessibilitySignals.notebookCellFailed', 'Notebook Cell Failed'), sound: Sound.taskFailed, - legacyAudioCueSettingsKey: 'accessibilitySignals.notebookCellFailed', + legacySoundSettingsKey: 'accessibilitySignals.notebookCellFailed', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, announcementMessage: localize('accessibility.signals.notebookCellFailed', 'Notebook Cell Failed'), settingsKey: 'accessibility.signals.notebookCellFailed' @@ -505,28 +505,28 @@ export class AccessibilitySignal { public static readonly diffLineInserted = AccessibilitySignal.register({ name: localize('accessibilitySignals.diffLineInserted', 'Diff Line Inserted'), sound: Sound.diffLineInserted, - legacyAudioCueSettingsKey: 'accessibilitySignals.diffLineInserted', + legacySoundSettingsKey: 'accessibilitySignals.diffLineInserted', settingsKey: 'accessibility.signals.diffLineInserted' }); public static readonly diffLineDeleted = AccessibilitySignal.register({ name: localize('accessibilitySignals.diffLineDeleted', 'Diff Line Deleted'), sound: Sound.diffLineDeleted, - legacyAudioCueSettingsKey: 'accessibilitySignals.diffLineDeleted', + legacySoundSettingsKey: 'accessibilitySignals.diffLineDeleted', settingsKey: 'accessibility.signals.diffLineDeleted' }); public static readonly diffLineModified = AccessibilitySignal.register({ name: localize('accessibilitySignals.diffLineModified', 'Diff Line Modified'), sound: Sound.diffLineModified, - legacyAudioCueSettingsKey: 'accessibilitySignals.diffLineModified', + legacySoundSettingsKey: 'accessibilitySignals.diffLineModified', settingsKey: 'accessibility.signals.diffLineModified' }); public static readonly chatRequestSent = AccessibilitySignal.register({ name: localize('accessibilitySignals.chatRequestSent', 'Chat Request Sent'), sound: Sound.chatRequestSent, - legacyAudioCueSettingsKey: 'accessibilitySignals.chatRequestSent', + legacySoundSettingsKey: 'accessibilitySignals.chatRequestSent', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, announcementMessage: localize('accessibility.signals.chatRequestSent', 'Chat Request Sent'), settingsKey: 'accessibility.signals.chatRequestSent' @@ -534,7 +534,7 @@ export class AccessibilitySignal { public static readonly chatResponseReceived = AccessibilitySignal.register({ name: localize('accessibilitySignals.chatResponseReceived', 'Chat Response Received'), - legacyAudioCueSettingsKey: 'accessibilitySignals.chatResponseReceived', + legacySoundSettingsKey: 'accessibilitySignals.chatResponseReceived', sound: { randomOneOf: [ Sound.chatResponseReceived1, @@ -549,7 +549,7 @@ export class AccessibilitySignal { public static readonly chatResponsePending = AccessibilitySignal.register({ name: localize('accessibilitySignals.chatResponsePending', 'Chat Response Pending'), sound: Sound.chatResponsePending, - legacyAudioCueSettingsKey: 'accessibilitySignals.chatResponsePending', + legacySoundSettingsKey: 'accessibilitySignals.chatResponsePending', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, announcementMessage: localize('accessibility.signals.chatResponsePending', 'Chat Response Pending'), settingsKey: 'accessibility.signals.chatResponsePending' @@ -558,7 +558,7 @@ export class AccessibilitySignal { public static readonly clear = AccessibilitySignal.register({ name: localize('accessibilitySignals.clear', 'Clear'), sound: Sound.clear, - legacyAudioCueSettingsKey: 'accessibilitySignals.clear', + legacySoundSettingsKey: 'accessibilitySignals.clear', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Clear, announcementMessage: localize('accessibility.signals.clear', 'Clear'), settingsKey: 'accessibility.signals.clear' @@ -567,7 +567,7 @@ export class AccessibilitySignal { public static readonly save = AccessibilitySignal.register({ name: localize('accessibilitySignals.save', 'Save'), sound: Sound.save, - legacyAudioCueSettingsKey: 'accessibilitySignals.save', + legacySoundSettingsKey: 'accessibilitySignals.save', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Save, announcementMessage: localize('accessibility.signals.save', 'Save'), settingsKey: 'accessibility.signals.save' @@ -576,7 +576,7 @@ export class AccessibilitySignal { public static readonly format = AccessibilitySignal.register({ name: localize('accessibilitySignals.format', 'Format'), sound: Sound.format, - legacyAudioCueSettingsKey: 'accessibilitySignals.format', + legacySoundSettingsKey: 'accessibilitySignals.format', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Format, announcementMessage: localize('accessibility.signals.format', 'Format'), settingsKey: 'accessibility.signals.format' diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 243068f3983..529a7f35bc7 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -266,6 +266,14 @@ const configuration: IConfigurationNode = { type: 'boolean', default: true }, + 'accessibility.signals.sounds.volume': { + 'description': localize('accessibility.signals.sounds.volume', "The volume of the sounds in percent (0-100)."), + 'type': 'number', + 'minimum': 0, + 'maximum': 100, + 'default': 70, + tags: ['accessibility'] + }, 'accessibility.signals.debouncePositionChanges': { 'description': localize('accessibility.signals.debouncePositionChanges', "Whether or not position changes should be debounced"), 'type': 'boolean', @@ -688,7 +696,16 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen }); } } - +Registry.as(WorkbenchExtensions.ConfigurationMigration) + .registerConfigurationMigrations([{ + key: 'audioCues.volume', + migrateFn: (value, accessor) => { + return [ + ['accessibility.signals.sounds.volume', { value }], + ['audioCues.volume', { value: undefined }] + ]; + } + }]); Registry.as(WorkbenchExtensions.ConfigurationMigration) .registerConfigurationMigrations([{ @@ -696,7 +713,7 @@ Registry.as(WorkbenchExtensions.ConfigurationMi migrateFn: (value, accessor) => { return [ ['accessibility.signals.debouncePositionChanges', { value }], - ['audioCues.debouncePositionChangess', { value: undefined }] + ['audioCues.debouncePositionChanges', { value: undefined }] ]; } }]); diff --git a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts index a084c6b378d..a28f965e588 100644 --- a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts @@ -75,7 +75,7 @@ class EditorAccessibilityHelpProvider implements IAccessibleContentProvider { } } - content.push(AccessibilityHelpNLS.listAudioCues); + content.push(AccessibilityHelpNLS.listSignalSounds); content.push(AccessibilityHelpNLS.listAlerts); const chatCommandInfo = getChatCommandInfo(this._keybindingService, this._contextKeyService); diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts index 4f916fb4d5c..e7ac5c35390 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ShowAccessibilityAnnouncementHelp, ShowAudioCueHelp } from 'vs/workbench/contrib/accessibilitySignals/browser/commands'; +import { ShowAccessibilityAnnouncementHelp, ShowSignalSoundHelp } from 'vs/workbench/contrib/accessibilitySignals/browser/commands'; import { localize } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; @@ -44,11 +44,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis tags: ['accessibility'] }, 'audioCues.volume': { - 'description': localize('audioCues.volume', "The volume of the audio cues in percent (0-100)."), - 'type': 'number', - 'minimum': 0, - 'maximum': 100, - 'default': 70, + markdownDeprecationMessage: 'Deprecated. Use `accessibility.signals.sounds.volume` instead.', tags: ['accessibility'] }, 'audioCues.debouncePositionChanges': { @@ -177,6 +173,6 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, }); -registerAction2(ShowAudioCueHelp); +registerAction2(ShowSignalSoundHelp); registerAction2(ShowAccessibilityAnnouncementHelp); diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 1236f98f57a..8f47611cfd1 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -13,13 +13,13 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -export class ShowAudioCueHelp extends Action2 { - static readonly ID = 'audioCues.help'; +export class ShowSignalSoundHelp extends Action2 { + static readonly ID = 'signals.sounds.help'; constructor() { super({ - id: ShowAudioCueHelp.ID, - title: localize2('audioCues.help', "Help: List Audio Cues"), + id: ShowSignalSoundHelp.ID, + title: localize2('signals.sound.help', "Help: List Signal Sounds"), f1: true, }); } @@ -82,7 +82,7 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { constructor() { super({ id: ShowAccessibilityAnnouncementHelp.ID, - title: localize2('accessibility.announcement.help', "Help: List Announcements"), + title: localize2('accessibility.announcement.help', "Help: List Signal Announcements"), f1: true, }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 6be43b0fcff..9fd67d69b88 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -45,7 +45,7 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'pane content.push(diffReviewKeybinding ? localize('inlineChat.diff', "Once in the diff editor, enter review mode with ({0}). Use up and down arrows to navigate lines with the proposed changes.", diffReviewKeybinding) : localize('inlineChat.diffNoKb', "Tab again to enter the Diff editor with the changes and enter review mode with the Go to Next Difference Command. Use Up/DownArrow to navigate lines with the proposed changes.")); content.push(localize('inlineChat.toolbar', "Use tab to reach conditional parts like commands, status, message responses and more.")); } - content.push(localize('chat.audioCues', "Audio cues can be changed via settings with a prefix of audioCues.chat. By default, if a request takes more than 4 seconds, you will hear an audio cue indicating that progress is still occurring.")); + content.push(localize('chat.signals', "Accessibility Signals can be changed via settings with a prefix of signals.chat. By default, if a request takes more than 4 seconds, you will hear a sound indicating that progress is still occurring.")); return content.join('\n\n'); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index cfbcd71273a..204ebdd1358 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -103,11 +103,11 @@ function createScreenReaderHelp(): IDisposable { return; } - const keys = ['audioCues.diffLineDeleted', 'audioCues.diffLineInserted', 'audioCues.diffLineModified']; + const keys = ['accessibility.signals.diffLineDeleted', 'accessibility.signals.diffLineInserted', 'accessibility.signals.diffLineModified']; const content = [ localize('msg1', "You are in a diff editor."), localize('msg2', "View the next ({0}) or previous ({1}) diff in diff review mode, which is optimized for screen readers.", next, previous), - localize('msg3', "To control which audio cues should be played, the following settings can be configured: {0}.", keys.join(', ')), + localize('msg3', "To control which accessibility signals should be played, the following settings can be configured: {0}.", keys.join(', ')), ]; const commentCommandInfo = getCommentCommandInfo(keybindingService, contextKeyService, codeEditor); if (commentCommandInfo) { diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index b83c1fcd894..719bd0bc24c 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -368,7 +368,7 @@ import 'vs/workbench/contrib/workspaces/browser/workspaces.contribution'; // List import 'vs/workbench/contrib/list/browser/list.contribution'; -// Audio Cues +// Accessibility Signals import 'vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution'; // Deprecated Extension Migrator From 6cbe80c310e810e2b1cd4b92e6e8f99ee1ea6000 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 10:46:41 -0600 Subject: [PATCH 1139/1897] alert -> announcement --- .../browser/accessibilityConfiguration.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 529a7f35bc7..7bdbe32f90e 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -724,14 +724,14 @@ Registry.as(WorkbenchExtensions.ConfigurationMi migrateFn: (audioCue, accessor) => { const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; const alertSettingsKey = item.alertSettingsKey; - let alert: string | undefined; + let announcement: string | undefined; if (alertSettingsKey) { - alert = accessor(alertSettingsKey) ?? undefined; - if (typeof alert !== 'string') { - alert = alert ? 'auto' : 'off'; + announcement = accessor(alertSettingsKey) ?? undefined; + if (typeof announcement !== 'string') { + announcement = announcement ? 'auto' : 'off'; } } - configurationKeyValuePairs.push([`${item.signalSettingsKey}`, { value: alert ? { alert, audioCue } : { audioCue } }]); + configurationKeyValuePairs.push([`${item.signalSettingsKey}`, { value: announcement ? { announcement, audioCue } : { audioCue } }]); return configurationKeyValuePairs; } }))); From c69e96e6bf08f4416fd15a84a67babcaac1e4cb5 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 10:52:43 -0600 Subject: [PATCH 1140/1897] audio cue -> sound --- .../browser/accessibilityConfiguration.ts | 150 +++++++++--------- .../accessibilitySignal.contribution.ts | 54 +++---- 2 files changed, 102 insertions(+), 102 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 7bdbe32f90e..8ed1e20369a 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -13,7 +13,7 @@ import { ISpeechService } from 'vs/workbench/contrib/speech/common/speechService import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Event } from 'vs/base/common/event'; -import { audioCueFeatureBase } from 'vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution'; +import { soundFeatureBase } from 'vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution'; export const accessibilityHelpIsShown = new RawContextKey('accessibilityHelpIsShown', false, true); export const accessibleViewIsShown = new RawContextKey('accessibleViewIsShown', false, true); @@ -106,8 +106,8 @@ export const announcementFeatureBase: IConfigurationPropertySchema = { 'enum': ['auto', 'off'], 'default': 'auto', 'enumDescriptions': [ - localize('audioCues.enabled.auto', "Enable announcement, will only play when in screen reader optimized mode."), - localize('audioCues.enabled.off', "Disable announcement.") + localize('announcement.enabled.auto', "Enable announcement, will only play when in screen reader optimized mode."), + localize('announcement.enabled.off', "Disable announcement.") ], tags: ['accessibility'], }; @@ -117,7 +117,7 @@ const defaultNoAnnouncement: IConfigurationPropertySchema = { 'tags': ['accessibility'], additionalProperties: true, 'default': { - 'audioCue': 'auto', + 'sound': 'auto', } }; @@ -284,9 +284,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.lineHasBreakpoint.sound', "Plays a sound when the active line has a breakpoint."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.lineHasBreakpoint.alert', "Indicates when the active line has a breakpoint."), @@ -299,9 +299,9 @@ const configuration: IConfigurationNode = { ...defaultNoAnnouncement, 'description': localize('accessibility.signals.lineHasInlineSuggestion', "Indicates when the active line has an inline suggestion."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.lineHasInlineSuggestion.sound', "Plays a sound when the active line has an inline suggestion."), + ...soundFeatureBase } } }, @@ -309,9 +309,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.lineHasError', "Indicates when the active line has an error."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.lineHasError.sound', "Plays a sound when the active line has an error."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.lineHasError.alert', "Indicates when the active line has an error."), @@ -324,9 +324,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.lineHasFoldedArea', "Indicates when the active line has a folded area that can be unfolded."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), - ...audioCueFeatureBase, + 'sound': { + 'description': localize('accessibility.signals.lineHasFoldedArea.sound', "Plays a sound when the active line has a folded area that can be unfolded."), + ...soundFeatureBase, default: 'off' }, 'announcement': { @@ -339,9 +339,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.lineHasWarning', "Plays a signal when the active line has a warning."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.lineHasWarning.sound', "Plays a sound when the active line has a warning."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.lineHasWarning.alert', "Indicates when the active line has a warning."), @@ -354,9 +354,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.onDebugBreak.sound', "Plays a sound when the debugger stopped on a breakpoint."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.onDebugBreak.alert', "Indicates when the debugger stopped on a breakpoint."), @@ -368,9 +368,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.noInlayHints.sound', "Plays a sound when trying to read a line with inlay hints that has no inlay hints."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.noInlayHints.alert', "Indicates when trying to read a line with inlay hints that has no inlay hints."), @@ -382,9 +382,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.taskCompleted', "Plays a signal when a task is completed."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.taskCompleted.sound', "Plays a sound when a task is completed."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.taskCompleted.alert', "Indicates when a task is completed."), @@ -396,9 +396,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.taskFailed.sound', "Plays a sound when a task fails (non-zero exit code)."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.taskFailed.alert', "Indicates when a task fails (non-zero exit code)."), @@ -410,9 +410,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.terminalCommandFailed.sound', "Plays a sound when a terminal command fails (non-zero exit code)."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.terminalCommandFailed.alert', "Indicates when a terminal command fails (non-zero exit code)."), @@ -424,9 +424,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.terminalQuickFix.sound', "Plays a sound when terminal Quick Fixes are available."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.terminalQuickFix.alert', "Indicates when terminal Quick Fixes are available."), @@ -438,9 +438,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.terminalBell', "Plays a signal when the terminal bell is ringing."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.terminalBell.sound', "Plays a sound when the terminal bell is ringing."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.terminalBell.alert', "Indicates when the terminal bell is ringing."), @@ -452,9 +452,9 @@ const configuration: IConfigurationNode = { ...defaultNoAnnouncement, 'description': localize('accessibility.signals.diffLineInserted', "Indicates when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.sound', "Plays a sound when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + ...soundFeatureBase } } }, @@ -462,9 +462,9 @@ const configuration: IConfigurationNode = { ...defaultNoAnnouncement, 'description': localize('accessibility.signals.diffLineModified', "Indicates when the focus moves to an modified line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.diffLineModified.sound', "Plays a sound when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + ...soundFeatureBase } } }, @@ -472,9 +472,9 @@ const configuration: IConfigurationNode = { ...defaultNoAnnouncement, 'description': localize('accessibility.signals.diffLineDeleted', "Indicates when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.diffLineDeleted.sound', "Plays a sound when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), + ...soundFeatureBase } } }, @@ -482,9 +482,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.notebookCellCompleted.sound', "Plays a sound when a notebook cell execution is successfully completed."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.notebookCellCompleted.alert', "Indicates when a notebook cell execution is successfully completed."), @@ -496,9 +496,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.notebookCellFailed.sound', "Plays a sound when a notebook cell execution fails."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.notebookCellFailed.alert', "Indicates when a notebook cell execution fails."), @@ -510,9 +510,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.chatRequestSent', "Plays a signal when a chat request is made."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.chatRequestSent.sound', "Plays a sound when a chat request is made."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.chatRequestSent.alert', "Indicates when a chat request is made."), @@ -524,9 +524,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.chatResponsePending', "Plays a signal on loop while the response is pending."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.chatResponsePending.sound', "Plays a sound on loop while the response is pending."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), @@ -539,9 +539,9 @@ const configuration: IConfigurationNode = { additionalProperties: false, 'description': localize('accessibility.signals.chatResponseReceived', "Indicates when the response has been received."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.chatResponseReceived.sound', "Plays a sound on loop while the response has been received."), + ...soundFeatureBase }, } }, @@ -549,9 +549,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.clear.sound', "Plays a sound when a feature is cleared."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.clear.alert', "Indicates when a feature is cleared."), @@ -565,15 +565,15 @@ const configuration: IConfigurationNode = { additionalProperties: true, 'markdownDescription': localize('accessibility.signals.save', "Plays a signal when a file is saved."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.save.audioCue', "Plays an audio cue when a file is saved."), + 'sound': { + 'description': localize('accessibility.signals.save.sound', "Plays a sound when a file is saved."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('accessibility.signals.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), - localize('accessibility.signals.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), - localize('accessibility.signals.save.audioCue.never', "Never plays the audio cue.") + localize('accessibility.signals.save.sound.userGesture', "Plays the audio cue when a user explicitly saves a file."), + localize('accessibility.signals.save.sound.always', "Plays the audio cue whenever a file is saved, including auto save."), + localize('accessibility.signals.save.sound.never', "Never plays the audio cue.") ], }, 'announcement': { @@ -589,7 +589,7 @@ const configuration: IConfigurationNode = { }, }, default: { - 'audioCue': 'never', + 'sound': 'never', 'announcement': 'never' } }, @@ -599,8 +599,8 @@ const configuration: IConfigurationNode = { additionalProperties: true, 'markdownDescription': localize('accessibility.signals.format', "Plays a signal when a file or notebook is formatted."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), + 'sound': { + 'description': localize('accessibility.signals.format.sound', "Plays a sound when a file or notebook is formatted."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts index e7ac5c35390..8469855da9f 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts @@ -20,20 +20,20 @@ registerSingleton(IAccessibilitySignalService, AccessibilitySignalService, Insta Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SignalLineFeatureContribution, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AccessibilitySignalLineDebuggerContribution, LifecyclePhase.Restored); -export const audioCueFeatureBase: IConfigurationPropertySchema = { +export const soundFeatureBase: IConfigurationPropertySchema = { 'type': 'string', 'enum': ['auto', 'on', 'off'], 'default': 'auto', 'enumDescriptions': [ - localize('audioCues.enabled.auto', "Enable audio cue when a screen reader is attached."), - localize('audioCues.enabled.on', "Enable audio cue."), - localize('audioCues.enabled.off', "Disable audio cue.") + localize('audioCues.enabled.auto', "Enable sound when a screen reader is attached."), + localize('audioCues.enabled.on', "Enable sound."), + localize('audioCues.enabled.off', "Disable sound.") ], tags: ['accessibility'], }; const markdownDeprecationMessage = localize('audioCues.enabled.deprecated', "This setting is deprecated. Use `signals` settings instead."); -const audioCueDeprecatedFeatureBase: IConfigurationPropertySchema = { - ...audioCueFeatureBase, +const soundDeprecatedFeatureBase: IConfigurationPropertySchema = { + ...soundFeatureBase, markdownDeprecationMessage }; @@ -56,92 +56,92 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, 'audioCues.lineHasBreakpoint': { 'description': localize('audioCues.lineHasBreakpoint', "Plays a sound when the active line has a breakpoint."), - ...audioCueDeprecatedFeatureBase + ...soundDeprecatedFeatureBase }, 'audioCues.lineHasInlineSuggestion': { 'description': localize('audioCues.lineHasInlineSuggestion', "Plays a sound when the active line has an inline suggestion."), - ...audioCueDeprecatedFeatureBase + ...soundDeprecatedFeatureBase }, 'audioCues.lineHasError': { 'description': localize('audioCues.lineHasError', "Plays a sound when the active line has an error."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.lineHasFoldedArea': { 'description': localize('audioCues.lineHasFoldedArea', "Plays a sound when the active line has a folded area that can be unfolded."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.lineHasWarning': { 'description': localize('audioCues.lineHasWarning', "Plays a sound when the active line has a warning."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, default: 'off', }, 'audioCues.onDebugBreak': { 'description': localize('audioCues.onDebugBreak', "Plays a sound when the debugger stopped on a breakpoint."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.noInlayHints': { 'description': localize('audioCues.noInlayHints', "Plays a sound when trying to read a line with inlay hints that has no inlay hints."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.taskCompleted': { 'description': localize('audioCues.taskCompleted', "Plays a sound when a task is completed."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.taskFailed': { 'description': localize('audioCues.taskFailed', "Plays a sound when a task fails (non-zero exit code)."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.terminalCommandFailed': { 'description': localize('audioCues.terminalCommandFailed', "Plays a sound when a terminal command fails (non-zero exit code)."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.terminalQuickFix': { 'description': localize('audioCues.terminalQuickFix', "Plays a sound when terminal Quick Fixes are available."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.terminalBell': { 'description': localize('audioCues.terminalBell', "Plays a sound when the terminal bell is ringing."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, default: 'on' }, 'audioCues.diffLineInserted': { 'description': localize('audioCues.diffLineInserted', "Plays a sound when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.diffLineDeleted': { 'description': localize('audioCues.diffLineDeleted', "Plays a sound when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.diffLineModified': { 'description': localize('audioCues.diffLineModified', "Plays a sound when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.notebookCellCompleted': { 'description': localize('audioCues.notebookCellCompleted', "Plays a sound when a notebook cell execution is successfully completed."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.notebookCellFailed': { 'description': localize('audioCues.notebookCellFailed', "Plays a sound when a notebook cell execution fails."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.chatRequestSent': { 'description': localize('audioCues.chatRequestSent', "Plays a sound when a chat request is made."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, default: 'off' }, 'audioCues.chatResponsePending': { 'description': localize('audioCues.chatResponsePending', "Plays a sound on loop while the response is pending."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, default: 'auto' }, 'audioCues.chatResponseReceived': { 'description': localize('audioCues.chatResponseReceived', "Plays a sound on loop while the response has been received."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, default: 'off' }, 'audioCues.clear': { 'description': localize('audioCues.clear', "Plays a sound when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, default: 'off' }, 'audioCues.save': { From 1a598ffe00c050dd484c1b39601fd863fb074755 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Mon, 12 Feb 2024 17:56:56 +0100 Subject: [PATCH 1141/1897] fix: memory leak in notebook editor widget (#204892) --- .../contrib/notebook/browser/notebookEditorWidget.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 0719ee2ca4f..c57af0eb8ad 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -313,16 +313,17 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._notebookOptions = creationOptions.options ?? new NotebookOptions(this.creationOptions?.codeWindow ?? mainWindow, this.configurationService, notebookExecutionStateService, codeEditorService, this._readOnly); this._register(this._notebookOptions); + const eventDispatcher = this._register(new NotebookEventDispatcher()); this._viewContext = new ViewContext( this._notebookOptions, - new NotebookEventDispatcher(), + eventDispatcher, language => this.getBaseCellEditorOptions(language)); this._register(this._viewContext.eventDispatcher.onDidChangeCellState(e => { this._onDidChangeCellState.fire(e); })); this._overlayContainer = document.createElement('div'); - this.scopedContextKeyService = contextKeyService.createScoped(this._overlayContainer); + this.scopedContextKeyService = this._register(contextKeyService.createScoped(this._overlayContainer)); this.instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])); this._register(_notebookService.onDidChangeOutputRenderers(() => { From 4e63128a7e9820f62c236913bcc0ac67e1da833c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chanchevrier?= Date: Mon, 12 Feb 2024 18:12:40 +0100 Subject: [PATCH 1142/1897] fix: inverted resize for direction.right --- src/vs/workbench/contrib/terminal/browser/terminalGroup.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index d993243770f..163b64068d2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -69,6 +69,7 @@ class SplitPaneContainer extends Disposable { // Resize the entire pane as a whole if ( (this.orientation === Orientation.HORIZONTAL && direction === Direction.Down) || + (this.orientation === Orientation.VERTICAL && direction === Direction.Right) || (part === Parts.SIDEBAR_PART && direction === Direction.Left) || (part === Parts.AUXILIARYBAR_PART && direction === Direction.Right) ) { From 18fde1e863ce5c56f50e90b36095b50281196c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chanchevrier?= Date: Mon, 12 Feb 2024 18:13:11 +0100 Subject: [PATCH 1143/1897] fix: inverted resize direction for left-positionned terminal panel --- .../workbench/contrib/terminal/browser/terminalGroup.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index 163b64068d2..7e944aa9753 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -577,11 +577,18 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { } const isHorizontal = (direction === Direction.Left || direction === Direction.Right); + + // Left-positionned panels have inverted controls + // see https://github.com/microsoft/vscode/issues/140873 + const shouldInvertHorizontalResize = (isHorizontal && this._panelPosition === Position.LEFT); + + const resizeDirection = shouldInvertHorizontalResize ? direction === Direction.Left ? Direction.Right : Direction.Left : direction; + const font = this._terminalService.configHelper.getFont(getWindow(this._groupElement)); // TODO: Support letter spacing and line height const charSize = (isHorizontal ? font.charWidth : font.charHeight); if (charSize) { - this._splitPaneContainer.resizePane(this._activeInstanceIndex, direction, charSize * Constants.ResizePartCellCount, getPartByLocation(this._terminalLocation)); + this._splitPaneContainer.resizePane(this._activeInstanceIndex, resizeDirection, charSize * Constants.ResizePartCellCount, getPartByLocation(this._terminalLocation)); } } From 8fe3c2dabbd716445e866dbd3e956fb564c21331 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 11:32:24 -0600 Subject: [PATCH 1144/1897] Get migration to work --- .../browser/accessibilitySignalService.ts | 60 +++++++------- .../browser/accessibilityConfiguration.ts | 83 +++++++++++-------- .../accessibilitySignals/browser/commands.ts | 66 +++++++-------- 3 files changed, 110 insertions(+), 99 deletions(-) diff --git a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts index 503a178b46d..1e1262ec668 100644 --- a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts +++ b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts @@ -178,9 +178,9 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi private readonly isSoundEnabledCache = new Cache((event: { readonly signal: AccessibilitySignal; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.signal.settingsKey) || e.affectsConfiguration(event.signal.signalSettingsKey) + e.affectsConfiguration(event.signal.settingsKey) || e.affectsConfiguration(event.signal.settingsKey) ), - () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.signal.signalSettingsKey + '.audioCue') + () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.signal.settingsKey + '.audioCue') ); return derived(reader => { /** @description sound enabled */ @@ -209,9 +209,9 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi private readonly isAnnouncementEnabledCache = new Cache((event: { readonly signal: AccessibilitySignal; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.signal.alertSettingsKey!) || e.affectsConfiguration(event.signal.signalSettingsKey) + e.affectsConfiguration(event.signal.legacyAnnouncementSettingsKey!) || e.affectsConfiguration(event.signal.settingsKey) ), - () => event.signal.alertSettingsKey ? this.configurationService.getValue(event.signal.signalSettingsKey + '.alert') : false + () => event.signal.announcementMessage ? this.configurationService.getValue(event.signal.settingsKey + '.announcement') : false ); return derived(reader => { /** @description alert enabled */ @@ -226,7 +226,7 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi }, JSON.stringify); public isAnnouncementEnabled(cue: AccessibilitySignal, userGesture?: boolean): boolean { - if (!cue.alertSettingsKey) { + if (!cue.announcementMessage) { return false; } return this.isAnnouncementEnabledCache.get({ signal: cue, userGesture }).get() ?? false; @@ -385,7 +385,7 @@ export class AccessibilitySignal { public static readonly error = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasError.name', 'Error on Line'), sound: Sound.error, - legacySoundSettingsKey: 'accessibilitySignals.lineHasError', + legacySoundSettingsKey: 'audioCues.lineHasError', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Error, announcementMessage: localize('accessibility.signals.lineHasError', 'Error'), settingsKey: 'accessibility.signals.lineHasError' @@ -393,7 +393,7 @@ export class AccessibilitySignal { public static readonly warning = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasWarning.name', 'Warning on Line'), sound: Sound.warning, - legacySoundSettingsKey: 'accessibilitySignals.lineHasWarning', + legacySoundSettingsKey: 'audioCues.lineHasWarning', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Warning, announcementMessage: localize('accessibility.signals.lineHasWarning', 'Warning'), settingsKey: 'accessibility.signals.lineHasWarning' @@ -401,7 +401,7 @@ export class AccessibilitySignal { public static readonly foldedArea = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasFoldedArea.name', 'Folded Area on Line'), sound: Sound.foldedArea, - legacySoundSettingsKey: 'accessibilitySignals.lineHasFoldedArea', + legacySoundSettingsKey: 'audioCues.lineHasFoldedArea', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.FoldedArea, announcementMessage: localize('accessibility.signals.lineHasFoldedArea', 'Folded'), settingsKey: 'accessibility.signals.lineHasFoldedArea' @@ -409,7 +409,7 @@ export class AccessibilitySignal { public static readonly break = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasBreakpoint.name', 'Breakpoint on Line'), sound: Sound.break, - legacySoundSettingsKey: 'accessibilitySignals.lineHasBreakpoint', + legacySoundSettingsKey: 'audioCues.lineHasBreakpoint', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Breakpoint, announcementMessage: localize('accessibility.signals.lineHasBreakpoint', 'Breakpoint'), settingsKey: 'accessibility.signals.lineHasBreakpoint' @@ -417,14 +417,14 @@ export class AccessibilitySignal { public static readonly inlineSuggestion = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), sound: Sound.quickFixes, - legacySoundSettingsKey: 'accessibilitySignals.lineHasInlineSuggestion', + legacySoundSettingsKey: 'audioCues.lineHasInlineSuggestion', settingsKey: 'accessibility.signals.lineHasInlineSuggestion' }); public static readonly terminalQuickFix = AccessibilitySignal.register({ name: localize('accessibilitySignals.terminalQuickFix.name', 'Terminal Quick Fix'), sound: Sound.quickFixes, - legacySoundSettingsKey: 'accessibilitySignals.terminalQuickFix', + legacySoundSettingsKey: 'audioCues.terminalQuickFix', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, announcementMessage: localize('accessibility.signals.terminalQuickFix', 'Quick Fix'), settingsKey: 'accessibility.signals.terminalQuickFix' @@ -433,7 +433,7 @@ export class AccessibilitySignal { public static readonly onDebugBreak = AccessibilitySignal.register({ name: localize('accessibilitySignals.onDebugBreak.name', 'Debugger Stopped on Breakpoint'), sound: Sound.break, - legacySoundSettingsKey: 'accessibilitySignals.onDebugBreak', + legacySoundSettingsKey: 'audioCues.onDebugBreak', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, announcementMessage: localize('accessibility.signals.onDebugBreak', 'Breakpoint'), settingsKey: 'accessibility.signals.onDebugBreak' @@ -442,7 +442,7 @@ export class AccessibilitySignal { public static readonly noInlayHints = AccessibilitySignal.register({ name: localize('accessibilitySignals.noInlayHints', 'No Inlay Hints on Line'), sound: Sound.error, - legacySoundSettingsKey: 'accessibilitySignals.noInlayHints', + legacySoundSettingsKey: 'audioCues.noInlayHints', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NoInlayHints, announcementMessage: localize('accessibility.signals.noInlayHints', 'No Inlay Hints'), settingsKey: 'accessibility.signals.noInlayHints' @@ -451,7 +451,7 @@ export class AccessibilitySignal { public static readonly taskCompleted = AccessibilitySignal.register({ name: localize('accessibilitySignals.taskCompleted', 'Task Completed'), sound: Sound.taskCompleted, - legacySoundSettingsKey: 'accessibilitySignals.taskCompleted', + legacySoundSettingsKey: 'audioCues.taskCompleted', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TaskCompleted, announcementMessage: localize('accessibility.signals.taskCompleted', 'Task Completed'), settingsKey: 'accessibility.signals.taskCompleted' @@ -460,7 +460,7 @@ export class AccessibilitySignal { public static readonly taskFailed = AccessibilitySignal.register({ name: localize('accessibilitySignals.taskFailed', 'Task Failed'), sound: Sound.taskFailed, - legacySoundSettingsKey: 'accessibilitySignals.taskFailed', + legacySoundSettingsKey: 'audioCues.taskFailed', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TaskFailed, announcementMessage: localize('accessibility.signals.taskFailed', 'Task Failed'), settingsKey: 'accessibility.signals.taskFailed' @@ -469,7 +469,7 @@ export class AccessibilitySignal { public static readonly terminalCommandFailed = AccessibilitySignal.register({ name: localize('accessibilitySignals.terminalCommandFailed', 'Terminal Command Failed'), sound: Sound.error, - legacySoundSettingsKey: 'accessibilitySignals.terminalCommandFailed', + legacySoundSettingsKey: 'audioCues.terminalCommandFailed', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, announcementMessage: localize('accessibility.signals.terminalCommandFailed', 'Command Failed'), settingsKey: 'accessibility.signals.terminalCommandFailed' @@ -478,7 +478,7 @@ export class AccessibilitySignal { public static readonly terminalBell = AccessibilitySignal.register({ name: localize('accessibilitySignals.terminalBell', 'Terminal Bell'), sound: Sound.terminalBell, - legacySoundSettingsKey: 'accessibilitySignals.terminalBell', + legacySoundSettingsKey: 'audioCues.terminalBell', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalBell, announcementMessage: localize('accessibility.signals.terminalBell', 'Terminal Bell'), settingsKey: 'accessibility.signals.terminalBell' @@ -487,7 +487,7 @@ export class AccessibilitySignal { public static readonly notebookCellCompleted = AccessibilitySignal.register({ name: localize('accessibilitySignals.notebookCellCompleted', 'Notebook Cell Completed'), sound: Sound.taskCompleted, - legacySoundSettingsKey: 'accessibilitySignals.notebookCellCompleted', + legacySoundSettingsKey: 'audioCues.notebookCellCompleted', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, announcementMessage: localize('accessibility.signals.notebookCellCompleted', 'Notebook Cell Completed'), settingsKey: 'accessibility.signals.notebookCellCompleted' @@ -496,7 +496,7 @@ export class AccessibilitySignal { public static readonly notebookCellFailed = AccessibilitySignal.register({ name: localize('accessibilitySignals.notebookCellFailed', 'Notebook Cell Failed'), sound: Sound.taskFailed, - legacySoundSettingsKey: 'accessibilitySignals.notebookCellFailed', + legacySoundSettingsKey: 'audioCues.notebookCellFailed', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, announcementMessage: localize('accessibility.signals.notebookCellFailed', 'Notebook Cell Failed'), settingsKey: 'accessibility.signals.notebookCellFailed' @@ -505,28 +505,28 @@ export class AccessibilitySignal { public static readonly diffLineInserted = AccessibilitySignal.register({ name: localize('accessibilitySignals.diffLineInserted', 'Diff Line Inserted'), sound: Sound.diffLineInserted, - legacySoundSettingsKey: 'accessibilitySignals.diffLineInserted', + legacySoundSettingsKey: 'audioCues.diffLineInserted', settingsKey: 'accessibility.signals.diffLineInserted' }); public static readonly diffLineDeleted = AccessibilitySignal.register({ name: localize('accessibilitySignals.diffLineDeleted', 'Diff Line Deleted'), sound: Sound.diffLineDeleted, - legacySoundSettingsKey: 'accessibilitySignals.diffLineDeleted', + legacySoundSettingsKey: 'audioCues.diffLineDeleted', settingsKey: 'accessibility.signals.diffLineDeleted' }); public static readonly diffLineModified = AccessibilitySignal.register({ name: localize('accessibilitySignals.diffLineModified', 'Diff Line Modified'), sound: Sound.diffLineModified, - legacySoundSettingsKey: 'accessibilitySignals.diffLineModified', + legacySoundSettingsKey: 'audioCues.diffLineModified', settingsKey: 'accessibility.signals.diffLineModified' }); public static readonly chatRequestSent = AccessibilitySignal.register({ name: localize('accessibilitySignals.chatRequestSent', 'Chat Request Sent'), sound: Sound.chatRequestSent, - legacySoundSettingsKey: 'accessibilitySignals.chatRequestSent', + legacySoundSettingsKey: 'audioCues.chatRequestSent', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, announcementMessage: localize('accessibility.signals.chatRequestSent', 'Chat Request Sent'), settingsKey: 'accessibility.signals.chatRequestSent' @@ -534,7 +534,7 @@ export class AccessibilitySignal { public static readonly chatResponseReceived = AccessibilitySignal.register({ name: localize('accessibilitySignals.chatResponseReceived', 'Chat Response Received'), - legacySoundSettingsKey: 'accessibilitySignals.chatResponseReceived', + legacySoundSettingsKey: 'audioCues.chatResponseReceived', sound: { randomOneOf: [ Sound.chatResponseReceived1, @@ -549,7 +549,7 @@ export class AccessibilitySignal { public static readonly chatResponsePending = AccessibilitySignal.register({ name: localize('accessibilitySignals.chatResponsePending', 'Chat Response Pending'), sound: Sound.chatResponsePending, - legacySoundSettingsKey: 'accessibilitySignals.chatResponsePending', + legacySoundSettingsKey: 'audioCues.chatResponsePending', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, announcementMessage: localize('accessibility.signals.chatResponsePending', 'Chat Response Pending'), settingsKey: 'accessibility.signals.chatResponsePending' @@ -558,7 +558,7 @@ export class AccessibilitySignal { public static readonly clear = AccessibilitySignal.register({ name: localize('accessibilitySignals.clear', 'Clear'), sound: Sound.clear, - legacySoundSettingsKey: 'accessibilitySignals.clear', + legacySoundSettingsKey: 'audioCues.clear', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Clear, announcementMessage: localize('accessibility.signals.clear', 'Clear'), settingsKey: 'accessibility.signals.clear' @@ -567,7 +567,7 @@ export class AccessibilitySignal { public static readonly save = AccessibilitySignal.register({ name: localize('accessibilitySignals.save', 'Save'), sound: Sound.save, - legacySoundSettingsKey: 'accessibilitySignals.save', + legacySoundSettingsKey: 'audioCues.save', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Save, announcementMessage: localize('accessibility.signals.save', 'Save'), settingsKey: 'accessibility.signals.save' @@ -576,7 +576,7 @@ export class AccessibilitySignal { public static readonly format = AccessibilitySignal.register({ name: localize('accessibilitySignals.format', 'Format'), sound: Sound.format, - legacySoundSettingsKey: 'accessibilitySignals.format', + legacySoundSettingsKey: 'audioCues.format', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Format, announcementMessage: localize('accessibility.signals.format', 'Format'), settingsKey: 'accessibility.signals.format' @@ -585,9 +585,9 @@ export class AccessibilitySignal { private constructor( public readonly sound: SoundSource, public readonly name: string, + public readonly legacySoundSettingsKey: string, public readonly settingsKey: string, - public readonly signalSettingsKey: string, - public readonly alertSettingsKey?: string, + public readonly legacyAnnouncementSettingsKey?: string, public readonly announcementMessage?: string, ) { } } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 8ed1e20369a..c322814c2ba 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -76,7 +76,7 @@ const baseVerbosityProperty: IConfigurationPropertySchema = { default: true, tags: ['accessibility'] }; -const markdownDeprecationMessage = localize('accessibility.alert.deprecationMessage', "This setting is deprecated. Use the `signals` settings instead."); +const markdownDeprecationMessage = localize('accessibility.announcement.deprecationMessage', "This setting is deprecated. Use the `signals` settings instead."); const baseAlertProperty: IConfigurationPropertySchema = { type: 'boolean', default: true, @@ -96,7 +96,7 @@ const signalFeatureBase: IConfigurationPropertySchema = { 'tags': ['accessibility'], additionalProperties: false, default: { - audioCue: 'auto', + sound: 'auto', announcement: 'auto' } }; @@ -289,9 +289,8 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.lineHasBreakpoint.alert', "Indicates when the active line has a breakpoint."), - ...announcementFeatureBase, - default: 'off' + 'description': localize('accessibility.signals.lineHasBreakpoint.announcement', "Indicates when the active line has a breakpoint."), + ...announcementFeatureBase }, }, }, @@ -301,7 +300,8 @@ const configuration: IConfigurationNode = { 'properties': { 'sound': { 'description': localize('accessibility.signals.lineHasInlineSuggestion.sound', "Plays a sound when the active line has an inline suggestion."), - ...soundFeatureBase + ...soundFeatureBase, + 'default': 'off' } } }, @@ -314,7 +314,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.lineHasError.alert', "Indicates when the active line has an error."), + 'description': localize('accessibility.signals.lineHasError.announcement', "Indicates when the active line has an error."), ...announcementFeatureBase, default: 'off' }, @@ -330,7 +330,7 @@ const configuration: IConfigurationNode = { default: 'off' }, 'announcement': { - 'description': localize('accessibility.signals.lineHasFoldedArea.alert', "Indicates when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.signals.lineHasFoldedArea.announcement', "Indicates when the active line has a folded area that can be unfolded."), ...announcementFeatureBase }, } @@ -344,7 +344,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.lineHasWarning.alert', "Indicates when the active line has a warning."), + 'description': localize('accessibility.signals.lineHasWarning.announcement', "Indicates when the active line has a warning."), ...announcementFeatureBase, default: 'off' }, @@ -359,7 +359,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.onDebugBreak.alert', "Indicates when the debugger stopped on a breakpoint."), + 'description': localize('accessibility.signals.onDebugBreak.announcement', "Indicates when the debugger stopped on a breakpoint."), ...announcementFeatureBase }, } @@ -373,7 +373,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.noInlayHints.alert', "Indicates when trying to read a line with inlay hints that has no inlay hints."), + 'description': localize('accessibility.signals.noInlayHints.announcement', "Indicates when trying to read a line with inlay hints that has no inlay hints."), ...announcementFeatureBase }, } @@ -387,7 +387,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.taskCompleted.alert', "Indicates when a task is completed."), + 'description': localize('accessibility.signals.taskCompleted.announcement', "Indicates when a task is completed."), ...announcementFeatureBase }, } @@ -401,7 +401,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.taskFailed.alert', "Indicates when a task fails (non-zero exit code)."), + 'description': localize('accessibility.signals.taskFailed.announcement', "Indicates when a task fails (non-zero exit code)."), ...announcementFeatureBase }, } @@ -415,7 +415,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.terminalCommandFailed.alert', "Indicates when a terminal command fails (non-zero exit code)."), + 'description': localize('accessibility.signals.terminalCommandFailed.announcement', "Indicates when a terminal command fails (non-zero exit code)."), ...announcementFeatureBase }, } @@ -429,7 +429,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.terminalQuickFix.alert', "Indicates when terminal Quick Fixes are available."), + 'description': localize('accessibility.signals.terminalQuickFix.announcement', "Indicates when terminal Quick Fixes are available."), ...announcementFeatureBase }, } @@ -443,7 +443,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.terminalBell.alert', "Indicates when the terminal bell is ringing."), + 'description': localize('accessibility.signals.terminalBell.announcement', "Indicates when the terminal bell is ringing."), ...announcementFeatureBase }, } @@ -487,7 +487,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.notebookCellCompleted.alert', "Indicates when a notebook cell execution is successfully completed."), + 'description': localize('accessibility.signals.notebookCellCompleted.announcement', "Indicates when a notebook cell execution is successfully completed."), ...announcementFeatureBase }, } @@ -501,7 +501,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.notebookCellFailed.alert', "Indicates when a notebook cell execution fails."), + 'description': localize('accessibility.signals.notebookCellFailed.announcement', "Indicates when a notebook cell execution fails."), ...announcementFeatureBase }, } @@ -515,7 +515,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.chatRequestSent.alert', "Indicates when a chat request is made."), + 'description': localize('accessibility.signals.chatRequestSent.announcement', "Indicates when a chat request is made."), ...announcementFeatureBase }, } @@ -529,7 +529,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), + 'description': localize('accessibility.signals.chatResponsePending.announcement', "Alerts on loop while the response is pending."), ...announcementFeatureBase }, }, @@ -554,7 +554,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.clear.alert', "Indicates when a feature is cleared."), + 'description': localize('accessibility.signals.clear.announcement', "Indicates when a feature is cleared."), ...announcementFeatureBase }, }, @@ -577,14 +577,14 @@ const configuration: IConfigurationNode = { ], }, 'announcement': { - 'description': localize('accessibility.signals.save.alert', "Indicates when a file is saved."), + 'description': localize('accessibility.signals.save.announcement', "Indicates when a file is saved."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('accessibility.signals.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), - localize('accessibility.signals.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), - localize('accessibility.signals.save.alert.never', "Never plays the audio cue.") + localize('accessibility.signals.save.announcement.userGesture', "Announces when a user explicitly saves a file."), + localize('accessibility.signals.save.announcement.always', "Announces whenever a file is saved, including auto save."), + localize('accessibility.signals.save.announcement.never', "Never plays the audio cue.") ], }, }, @@ -611,14 +611,14 @@ const configuration: IConfigurationNode = { ], }, 'announcement': { - 'description': localize('accessibility.signals.format.alert', "Indicates when a file or notebook is formatted."), + 'description': localize('accessibility.signals.format.announcement', "Indicates when a file or notebook is formatted."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('accessibility.signals.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), - localize('accessibility.signals.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('accessibility.signals.format.alert.never', "Never plays the alert.") + localize('accessibility.signals.format.announcement.userGesture', "Announceswhen a user explicitly formats a file."), + localize('accessibility.signals.format.announcement.always', "Announces whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('accessibility.signals.format.announcement.never', "Never announces.") ], }, } @@ -720,18 +720,29 @@ Registry.as(WorkbenchExtensions.ConfigurationMi Registry.as(WorkbenchExtensions.ConfigurationMigration) .registerConfigurationMigrations(AccessibilitySignal.allAccessibilitySignals.map(item => ({ - key: item.settingsKey, - migrateFn: (audioCue, accessor) => { + key: item.legacySoundSettingsKey, + migrateFn: (sound, accessor) => { const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; - const alertSettingsKey = item.alertSettingsKey; + const legacyAnnouncementSettingsKey = item.legacyAnnouncementSettingsKey; let announcement: string | undefined; - if (alertSettingsKey) { - announcement = accessor(alertSettingsKey) ?? undefined; - if (typeof announcement !== 'string') { + if (legacyAnnouncementSettingsKey) { + announcement = accessor(legacyAnnouncementSettingsKey) ?? undefined; + if (announcement !== undefined && typeof announcement !== 'string') { announcement = announcement ? 'auto' : 'off'; } } - configurationKeyValuePairs.push([`${item.signalSettingsKey}`, { value: announcement ? { announcement, audioCue } : { audioCue } }]); + configurationKeyValuePairs.push([`${item.settingsKey}`, { value: announcement ? { announcement, sound } : { sound } }]); + return configurationKeyValuePairs; + } + }))); + +Registry.as(WorkbenchExtensions.ConfigurationMigration) + .registerConfigurationMigrations(AccessibilitySignal.allAccessibilitySignals.filter(i => !!i.announcementMessage).map(item => ({ + key: item.legacyAnnouncementSettingsKey!, + migrateFn: (announcement, accessor) => { + const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; + const sound = accessor(item.legacySoundSettingsKey); + configurationKeyValuePairs.push([`${item.settingsKey}`, { value: announcement ? { announcement, sound } : { sound } }]); return configurationKeyValuePairs; } }))); diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 8f47611cfd1..2ec576a22e1 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -30,47 +30,47 @@ export class ShowSignalSoundHelp extends Action2 { const configurationService = accessor.get(IConfigurationService); const accessibilityService = accessor.get(IAccessibilityService); const userGestureSignals = [AccessibilitySignal.save, AccessibilitySignal.format]; - const items: (IQuickPickItem & { audioCue: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.map((signal, idx) => ({ - label: userGestureSignals.includes(signal) ? `${signal.name} (${configurationService.getValue(signal.signalSettingsKey + '.sound')})` : signal.name, - audioCue: signal, + const items: (IQuickPickItem & { signal: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.map((signal, idx) => ({ + label: userGestureSignals.includes(signal) ? `${signal.name} (${configurationService.getValue(signal.settingsKey + '.sound')})` : signal.name, + signal, buttons: userGestureSignals.includes(signal) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), tooltip: localize('sounds.help.settings', 'Enable/Disable Sound'), alwaysVisible: true }] : [] })); - const qp = quickInputService.createQuickPick(); + const qp = quickInputService.createQuickPick(); qp.items = items; - qp.selectedItems = items.filter(i => accessibilitySignalService.isSoundEnabled(i.audioCue)); + qp.selectedItems = items.filter(i => accessibilitySignalService.isSoundEnabled(i.signal)); qp.onDidAccept(() => { - const enabledCues = qp.selectedItems.map(i => i.audioCue); + const enabledCues = qp.selectedItems.map(i => i.signal); const disabledCues = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledCues.includes(cue)); for (const cue of enabledCues) { if (!userGestureSignals.includes(cue)) { - let { audioCue, announcement } = configurationService.getValue<{ audioCue: string; announcement?: string }>(cue.signalSettingsKey); - audioCue = accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'; + let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(cue.settingsKey); + sound = accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'; if (announcement) { - configurationService.updateValue(cue.signalSettingsKey, { audioCue, announcement }); + configurationService.updateValue(cue.settingsKey, { sound, announcement }); } else { - configurationService.updateValue(cue.signalSettingsKey, { audioCue }); + configurationService.updateValue(cue.settingsKey, { sound }); } } } for (const cue of disabledCues) { - const announcement = cue.announcementMessage ? configurationService.getValue(cue.signalSettingsKey + '.announcement') : undefined; - const audioCue = userGestureSignals.includes(cue) ? 'never' : 'off'; + const announcement = cue.announcementMessage ? configurationService.getValue(cue.settingsKey + '.announcement') : undefined; + const sound = userGestureSignals.includes(cue) ? 'never' : 'off'; if (announcement) { - configurationService.updateValue(cue.signalSettingsKey, { audioCue, announcement }); + configurationService.updateValue(cue.settingsKey, { sound, announcement }); } else { - configurationService.updateValue(cue.signalSettingsKey, { audioCue }); + configurationService.updateValue(cue.settingsKey, { sound }); } } qp.hide(); }); qp.onDidChangeActive(() => { - accessibilitySignalService.playSound(qp.activeItems[0].audioCue.sound.getSound(true), true); + accessibilitySignalService.playSound(qp.activeItems[0].signal.sound.getSound(true), true); }); - qp.placeholder = localize('audioCues.help.placeholder', 'Select an audio cue to play and configure'); + qp.placeholder = localize('audioCues.help.placeholder', 'Select a sound to play and configure'); qp.canSelectMany = true; await qp.show(); } @@ -93,38 +93,38 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { const configurationService = accessor.get(IConfigurationService); const accessibilityService = accessor.get(IAccessibilityService); const userGestureSignals = [AccessibilitySignal.save, AccessibilitySignal.format]; - const items: (IQuickPickItem & { audioCue: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.filter(c => !!c.announcementMessage).map((signal, idx) => ({ - label: userGestureSignals.includes(signal) ? `${signal.name} (${configurationService.getValue(signal.signalSettingsKey + '.announcement')})` : signal.name, - audioCue: signal, + const items: (IQuickPickItem & { signal: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.filter(c => !!c.announcementMessage).map((signal, idx) => ({ + label: userGestureSignals.includes(signal) ? `${signal.name} (${configurationService.getValue(signal.settingsKey + '.announcement')})` : signal.name, + signal, buttons: userGestureSignals.includes(signal) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), tooltip: localize('announcement.help.settings', 'Enable/Disable Announcement'), alwaysVisible: true }] : [] })); - const qp = quickInputService.createQuickPick(); + const qp = quickInputService.createQuickPick(); qp.items = items; - qp.selectedItems = items.filter(i => accessibilitySignalService.isAnnouncementEnabled(i.audioCue)); + qp.selectedItems = items.filter(i => accessibilitySignalService.isAnnouncementEnabled(i.signal)); qp.onDidAccept(() => { - const enabledAlerts = qp.selectedItems.map(i => i.audioCue); - const disabledAlerts = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledAlerts.includes(cue)); - for (const cue of enabledAlerts) { + const enabledAnnouncements = qp.selectedItems.map(i => i.signal); + const disabledAnnouncements = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledAnnouncements.includes(cue)); + for (const cue of enabledAnnouncements) { if (!userGestureSignals.includes(cue)) { - let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.signalSettingsKey); - alert = cue.announcementMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; - if (alert) { - configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); + let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(cue.settingsKey); + announcement = cue.announcementMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; + if (announcement) { + configurationService.updateValue(cue.settingsKey, { sound, announcement }); } } } - for (const cue of disabledAlerts) { - const alert = userGestureSignals.includes(cue) ? 'never' : 'off'; - const audioCue = configurationService.getValue(cue.signalSettingsKey + '.audioCue'); - configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); + for (const cue of disabledAnnouncements) { + const announcement = userGestureSignals.includes(cue) ? 'never' : 'off'; + const sound = configurationService.getValue(cue.settingsKey + '.sound'); + configurationService.updateValue(cue.settingsKey, { sound, announcement }); } qp.hide(); }); - qp.placeholder = localize('alert.help.placeholder', 'Select an alert to configure'); + qp.placeholder = localize('announcement.help.placeholder', 'Select an announcement to configure'); qp.canSelectMany = true; await qp.show(); } From c4bea7bd3d2133326760b5d5e5f9625d1da12b87 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Mon, 12 Feb 2024 09:32:56 -0800 Subject: [PATCH 1145/1897] Support Tabs vs Spaces in notebook editors (#204783) * settings override for notebook indentation + new statusbar entry * remove dead code for statusbar command. coming in dif PR * disposed checks --- .../browser/parts/editor/editorStatus.ts | 12 +++ .../editorStatusBar/editorStatusBar.ts | 82 ++++++++++++++++++- .../browser/controller/editActions.ts | 1 + .../notebook/browser/notebookOptions.ts | 2 +- .../view/cellParts/cellEditorOptions.ts | 64 ++++++++++++++- .../browser/view/cellParts/codeCell.ts | 38 +++++++-- .../browser/view/cellParts/markupCell.ts | 36 +++++++- 7 files changed, 220 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 0c08179077a..968668456a4 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -449,6 +449,12 @@ class EditorStatus extends Disposable { return; } + const editorURI = getCodeEditor(this.editorService.activeTextEditorControl)?.getModel()?.uri; + if (editorURI?.scheme === Schemas.vscodeNotebookCell) { + this.selectionElement.clear(); + return; + } + const props: IStatusbarEntry = { name: localize('status.editor.selection', "Editor Selection"), text, @@ -466,6 +472,12 @@ class EditorStatus extends Disposable { return; } + const editorURI = getCodeEditor(this.editorService.activeTextEditorControl)?.getModel()?.uri; + if (editorURI?.scheme === Schemas.vscodeNotebookCell) { + this.indentationElement.clear(); + return; + } + const props: IStatusbarEntry = { name: localize('status.editor.indentation', "Editor Indentation"), text, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index 9dd8e757117..68b5eba7d19 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -7,15 +7,17 @@ import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/ import { Schemas } from 'vs/base/common/network'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import * as nls from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, registerWorkbenchContribution2, WorkbenchPhase } from 'vs/workbench/common/contributions'; import { CENTER_ACTIVE_CELL } from 'vs/workbench/contrib/notebook/browser/contrib/navigation/arrow'; import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; +// import { SELECT_NOTEBOOK_INDENTATION_ID } from 'vs/workbench/contrib/notebook/browser/controller/editActions'; import { getNotebookEditorFromEditorPane, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookCellsChangeType, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -254,3 +256,79 @@ export class ActiveCellStatus extends Disposable implements IWorkbenchContributi } Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ActiveCellStatus, LifecyclePhase.Restored); + +export class NotebookIndentationStatus extends Disposable implements IWorkbenchContribution { + + private readonly _itemDisposables = this._register(new DisposableStore()); + private readonly _accessor = this._register(new MutableDisposable()); + + static readonly ID = 'selectNotebookIndentation'; + + constructor( + @IEditorService private readonly _editorService: IEditorService, + @IStatusbarService private readonly _statusbarService: IStatusbarService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + ) { + super(); + this._register(this._editorService.onDidActiveEditorChange(() => this._update())); + this._register(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('editor') || e.affectsConfiguration('notebook')) { + this._update(); + } + })); + } + + private _update() { + this._itemDisposables.clear(); + const activeEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); + if (activeEditor) { + this._show(activeEditor); + } else { + this._accessor.clear(); + } + } + + private _show(editor: INotebookEditor) { + if (!editor.hasModel()) { + this._accessor.clear(); + return; + } + + const cellEditorOverrides = this._configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations) as any; + + const indentSize = cellEditorOverrides['editor.indentSize'] ?? this._configurationService.getValue('editor.indentSize'); + const tabSize = cellEditorOverrides['editor.tabSize'] ?? this._configurationService.getValue('editor.tabSize'); + const insertSpaces = cellEditorOverrides['editor.insertSpaces'] ?? this._configurationService.getValue('editor.insertSpaces'); + + const width = typeof indentSize === 'number' ? indentSize : tabSize; + + const message = insertSpaces ? `Spaces: ${width}` : `Tab Size: ${width}`; + const newText = message; + if (!newText) { + this._accessor.clear(); + return; + } + + const entry: IStatusbarEntry = { + name: nls.localize('notebook.indentation', "Notebook Indentation"), + text: newText, + ariaLabel: newText, + // tooltip: nls.localize('selectNotebookIndentation', "Select Notebook Indentation"), + tooltip: nls.localize('selectNotebookIndentation', "Notebook Indentation"), + // command: SELECT_NOTEBOOK_INDENTATION_ID // TODO@Yoyokrazy -- finish hooking this up + }; + + if (!this._accessor.value) { + this._accessor.value = this._statusbarService.addEntry( + entry, + 'notebook.status.indentation', + StatusbarAlignment.RIGHT, + 100.4 + ); + } else { + this._accessor.value.update(entry); + } + } +} + +registerWorkbenchContribution2(NotebookIndentationStatus.ID, NotebookIndentationStatus, WorkbenchPhase.AfterRestored); // TODO@Yoyokrazy -- unsure on the phase diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index d43cc47b2c7..59f95276c9f 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -38,6 +38,7 @@ const CLEAR_ALL_CELLS_OUTPUTS_COMMAND_ID = 'notebook.clearAllCellsOutputs'; const EDIT_CELL_COMMAND_ID = 'notebook.cell.edit'; const DELETE_CELL_COMMAND_ID = 'notebook.cell.delete'; export const CLEAR_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.clearOutputs'; +export const SELECT_NOTEBOOK_INDENTATION_ID = 'notebook.selectIndentation'; registerAction2(class EditCellAction extends NotebookCellAction { constructor() { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts index c4356bd6ed0..36ed3544b98 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts @@ -20,7 +20,7 @@ const SCROLLABLE_ELEMENT_PADDING_TOP = 18; export const OutputInnerContainerTopPadding = 4; -export interface NotebookDisplayOptions { +export interface NotebookDisplayOptions { // TODO @Yoyokrazy rename to a more generic name, not display showCellStatusBar: ShowCellStatusBarType; cellToolbarLocation: string | { [key: string]: string }; cellToolbarInteraction: string; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts index 0804f29c3ac..fe000cac947 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts @@ -21,14 +21,56 @@ import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cell import { NotebookCellInternalMetadata, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; +import { ITextModelUpdateOptions } from 'vs/editor/common/model'; -export class CellEditorOptions extends CellContentPart { +//todo@Yoyokrazy implenets is needed or not? +export class CellEditorOptions extends CellContentPart implements ITextModelUpdateOptions { private _lineNumbers: 'on' | 'off' | 'inherit' = 'inherit'; + private _tabSize?: number; + private _indentSize?: number | 'tabSize'; + private _insertSpaces?: boolean; + + set tabSize(value: number | undefined) { + if (this._tabSize !== value) { + this._tabSize = value; + this._onDidChange.fire(); + } + } + + get tabSize() { + return this._tabSize; + } + + set indentSize(value: number | 'tabSize' | undefined) { + if (this._indentSize !== value) { + this._indentSize = value; + this._onDidChange.fire(); + } + } + + get indentSize() { + return this._indentSize; + } + + set insertSpaces(value: boolean | undefined) { + if (this._insertSpaces !== value) { + this._insertSpaces = value; + this._onDidChange.fire(); + } + } + + get insertSpaces() { + return this._insertSpaces; + } + private readonly _onDidChange = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; private _value: IEditorOptions; - constructor(private readonly base: IBaseCellEditorOptions, readonly notebookOptions: NotebookOptions, readonly configurationService: IConfigurationService) { + constructor( + private readonly base: IBaseCellEditorOptions, + readonly notebookOptions: NotebookOptions, + readonly configurationService: IConfigurationService) { super(); this._register(base.onDidChange(() => { @@ -50,7 +92,23 @@ export class CellEditorOptions extends CellContentPart { } private _computeEditorOptions() { - const value = this.base.value; + const value = this.base.value; // base IEditorOptions + + // TODO @Yoyokrazy find a different way to get the editor overrides, this is not the right way + const cellEditorOverridesRaw = this.notebookOptions.getDisplayOptions().editorOptionsCustomizations; + const indentSize = cellEditorOverridesRaw['editor.indentSize']; + if (indentSize !== undefined) { + this.indentSize = indentSize; + } + const insertSpaces = cellEditorOverridesRaw['editor.insertSpaces']; + if (insertSpaces !== undefined) { + this.insertSpaces = insertSpaces; + } + const tabSize = cellEditorOverridesRaw['editor.tabSize']; + if (tabSize !== undefined) { + this.tabSize = tabSize; + } + let cellRenderLineNumber = value.lineNumbers; switch (this._lineNumbers) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts index cdfd236913a..a74983b6b5f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts @@ -42,6 +42,7 @@ export class CodeCell extends Disposable { private readonly cellParts: CellPartsCollection; private _collapsedExecutionIcon: CollapsedCodeCellExecutionIcon; + private _cellEditorOptions: CellEditorOptions; constructor( private readonly notebookEditor: IActiveNotebookEditorDelegate, @@ -52,13 +53,13 @@ export class CodeCell extends Disposable { @IOpenerService openerService: IOpenerService, @ILanguageService private readonly languageService: ILanguageService, @IConfigurationService private configurationService: IConfigurationService, - @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService + @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService, ) { super(); - const cellEditorOptions = this._register(new CellEditorOptions(this.notebookEditor.getBaseCellEditorOptions(viewCell.language), this.notebookEditor.notebookOptions, this.configurationService)); + this._cellEditorOptions = this._register(new CellEditorOptions(this.notebookEditor.getBaseCellEditorOptions(viewCell.language), this.notebookEditor.notebookOptions, this.configurationService)); this._outputContainerRenderer = this.instantiationService.createInstance(CellOutputContainer, notebookEditor, viewCell, templateData, { limit: outputDisplayLimit }); - this.cellParts = this._register(templateData.cellParts.concatContentPart([cellEditorOptions, this._outputContainerRenderer], DOM.getWindow(notebookEditor.getDomNode()))); + this.cellParts = this._register(templateData.cellParts.concatContentPart([this._cellEditorOptions, this._outputContainerRenderer], DOM.getWindow(notebookEditor.getDomNode()))); // this.viewCell.layoutInfo.editorHeight or estimation when this.viewCell.layoutInfo.editorHeight === 0 const editorHeight = this.calculateInitEditorHeight(); @@ -135,9 +136,29 @@ export class CodeCell extends Disposable { this._register(Event.runAndSubscribe(viewCell.onDidChangeOutputs, this.updateForOutputs.bind(this))); this._register(Event.runAndSubscribe(viewCell.onDidChangeLayout, this.updateForLayout.bind(this))); - cellEditorOptions.setLineNumbers(this.viewCell.lineNumbers); - this._register(cellEditorOptions.onDidChange(() => templateData.editor.updateOptions(cellEditorOptions.getUpdatedValue(this.viewCell.internalMetadata, this.viewCell.uri)))); - templateData.editor.updateOptions(cellEditorOptions.getUpdatedValue(this.viewCell.internalMetadata, this.viewCell.uri)); + this._cellEditorOptions.setLineNumbers(this.viewCell.lineNumbers); + this._register(this._cellEditorOptions.onDidChange(() => this.updateCodeCellOptions(templateData))); + templateData.editor.updateOptions(this._cellEditorOptions.getUpdatedValue(this.viewCell.internalMetadata, this.viewCell.uri)); + } + + private updateCodeCellOptions(templateData: CodeCellRenderTemplate) { + templateData.editor.updateOptions(this._cellEditorOptions.getUpdatedValue(this.viewCell.internalMetadata, this.viewCell.uri)); + + const cts = new CancellationTokenSource(); + this._register({ dispose() { cts.dispose(true); } }); + raceCancellation(this.viewCell.resolveTextModel(), cts.token).then(model => { + if (this._isDisposed) { + return; + } + + if (model) { + model.updateOptions({ + indentSize: this._cellEditorOptions.indentSize, + tabSize: this._cellEditorOptions.tabSize, + insertSpaces: this._cellEditorOptions.insertSpaces, + }); + } + }); } private _pendingLayout: IDisposable | undefined; @@ -186,6 +207,11 @@ export class CodeCell extends Disposable { if (model && this.templateData.editor) { this._reigsterModelListeners(model); this.templateData.editor.setModel(model); + model.updateOptions({ + indentSize: this._cellEditorOptions.indentSize, + tabSize: this._cellEditorOptions.tabSize, + insertSpaces: this._cellEditorOptions.insertSpaces, + }); this.viewCell.attachTextEditor(this.templateData.editor, this.viewCell.layoutInfo.estimatedHasHorizontalScrolling); const focusEditorIfNeeded = () => { if ( diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts index 7636dbf004c..5806dd97b5f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts @@ -44,6 +44,7 @@ export class MarkupCell extends Disposable { private foldingState: CellFoldingState; private cellEditorOptions: CellEditorOptions; private editorOptions: IEditorOptions; + private _isDisposed: boolean = false; constructor( private readonly notebookEditor: IActiveNotebookEditorDelegate, @@ -174,9 +175,31 @@ export class MarkupCell extends Disposable { } })); - this._register(this.cellEditorOptions.onDidChange(() => { - this.updateEditorOptions(this.cellEditorOptions.getUpdatedValue(this.viewCell.internalMetadata, this.viewCell.uri)); - })); + this._register(this.cellEditorOptions.onDidChange(() => this.updateMarkupCellOptions())); + } + + private updateMarkupCellOptions(): any { + this.updateEditorOptions(this.cellEditorOptions.getUpdatedValue(this.viewCell.internalMetadata, this.viewCell.uri)); + + if (this.editor) { + this.editor.updateOptions(this.cellEditorOptions.getUpdatedValue(this.viewCell.internalMetadata, this.viewCell.uri)); + + const cts = new CancellationTokenSource(); + this._register({ dispose() { cts.dispose(true); } }); + raceCancellation(this.viewCell.resolveTextModel(), cts.token).then(model => { + if (this._isDisposed) { + return; + } + + if (model) { + model.updateOptions({ + indentSize: this.cellEditorOptions.indentSize, + tabSize: this.cellEditorOptions.tabSize, + insertSpaces: this.cellEditorOptions.insertSpaces, + }); + } + }); + } } private updateCollapsedState() { @@ -223,6 +246,8 @@ export class MarkupCell extends Disposable { } override dispose() { + this._isDisposed = true; + // move focus back to the cell list otherwise the focus goes to body if (this.notebookEditor.getActiveCell() === this.viewCell && this.viewCell.focusMode === CellFocusMode.Editor && (this.notebookEditor.hasEditorFocus() || this.notebookEditor.getDomNode().ownerDocument.activeElement === this.notebookEditor.getDomNode().ownerDocument.body)) { this.notebookEditor.focusContainer(); @@ -354,6 +379,11 @@ export class MarkupCell extends Disposable { } this.editor!.setModel(model); + model.updateOptions({ + indentSize: this.cellEditorOptions.indentSize, + tabSize: this.cellEditorOptions.tabSize, + insertSpaces: this.cellEditorOptions.insertSpaces, + }); const realContentHeight = this.editor!.getContentHeight(); if (realContentHeight !== editorHeight) { From 7d04ffa55d8bffcd4edcdeba556986e6f1b460b8 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Mon, 12 Feb 2024 09:57:12 -0800 Subject: [PATCH 1146/1897] fix: rerender empty editor hint if a file changes readonly status (#205016) --- .../browser/emptyTextEditorHint/emptyTextEditorHint.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts index c103f95c23a..fb540a708d1 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts @@ -83,6 +83,11 @@ export class EmptyTextEditorHintContribution implements IEditorContribution { this.toDispose.push(this.editor.onDidChangeModelContent(() => this.update())); this.toDispose.push(this.inlineChatService.onDidChangeProviders(() => this.update())); this.toDispose.push(this.editor.onDidChangeModelDecorations(() => this.update())); + this.toDispose.push(this.editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { + if (e.hasChanged(EditorOption.readOnly)) { + this.update(); + } + })); this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(emptyTextEditorHintSetting)) { this.update(); From f24957e5d21fec398564e6f1d33043fbb6f4a3f3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 12:02:26 -0600 Subject: [PATCH 1147/1897] Fix issues --- .../browser/accessibilitySignalService.ts | 22 ++++---- .../accessibilitySignals/browser/commands.ts | 54 +++++++++---------- 2 files changed, 36 insertions(+), 40 deletions(-) diff --git a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts index 1e1262ec668..915a61aba5b 100644 --- a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts +++ b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts @@ -178,9 +178,9 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi private readonly isSoundEnabledCache = new Cache((event: { readonly signal: AccessibilitySignal; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.signal.settingsKey) || e.affectsConfiguration(event.signal.settingsKey) + e.affectsConfiguration(event.signal.legacySoundSettingsKey) || e.affectsConfiguration(event.signal.settingsKey) ), - () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.signal.settingsKey + '.audioCue') + () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.signal.settingsKey + '.sound') ); return derived(reader => { /** @description sound enabled */ @@ -211,7 +211,7 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi Event.filter(this.configurationService.onDidChangeConfiguration, (e) => e.affectsConfiguration(event.signal.legacyAnnouncementSettingsKey!) || e.affectsConfiguration(event.signal.settingsKey) ), - () => event.signal.announcementMessage ? this.configurationService.getValue(event.signal.settingsKey + '.announcement') : false + () => event.signal.announcementMessage ? this.configurationService.getValue<'auto' | 'off' | 'userGesture' | 'always' | 'never'>(event.signal.settingsKey + '.announcement') : false ); return derived(reader => { /** @description alert enabled */ @@ -221,23 +221,23 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi ) { return false; } - return setting === true || setting === 'always' || setting === 'userGesture' && event.userGesture; + return setting === 'auto' || setting === 'always' || setting === 'userGesture' && event.userGesture; }); }, JSON.stringify); - public isAnnouncementEnabled(cue: AccessibilitySignal, userGesture?: boolean): boolean { - if (!cue.announcementMessage) { + public isAnnouncementEnabled(signal: AccessibilitySignal, userGesture?: boolean): boolean { + if (!signal.announcementMessage) { return false; } - return this.isAnnouncementEnabledCache.get({ signal: cue, userGesture }).get() ?? false; + return this.isAnnouncementEnabledCache.get({ signal, userGesture }).get() ?? false; } - public isSoundEnabled(cue: AccessibilitySignal, userGesture?: boolean): boolean { - return this.isSoundEnabledCache.get({ signal: cue, userGesture }).get() ?? false; + public isSoundEnabled(signal: AccessibilitySignal, userGesture?: boolean): boolean { + return this.isSoundEnabledCache.get({ signal, userGesture }).get() ?? false; } - public onSoundEnabledChanged(cue: AccessibilitySignal): Event { - return Event.fromObservableLight(this.isSoundEnabledCache.get({ signal: cue })); + public onSoundEnabledChanged(signal: AccessibilitySignal): Event { + return Event.fromObservableLight(this.isSoundEnabledCache.get({ signal })); } public onAnnouncementEnabledChanged(cue: AccessibilitySignal): Event { diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 2ec576a22e1..5a7bacde1c5 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -41,28 +41,26 @@ export class ShowSignalSoundHelp extends Action2 { })); const qp = quickInputService.createQuickPick(); qp.items = items; - qp.selectedItems = items.filter(i => accessibilitySignalService.isSoundEnabled(i.signal)); + qp.selectedItems = items.filter(i => accessibilitySignalService.isSoundEnabled(i.signal) || configurationService.getValue(i.signal.settingsKey + '.sound') !== 'never'); qp.onDidAccept(() => { - const enabledCues = qp.selectedItems.map(i => i.signal); - const disabledCues = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledCues.includes(cue)); - for (const cue of enabledCues) { - if (!userGestureSignals.includes(cue)) { - let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(cue.settingsKey); - sound = accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'; - if (announcement) { - configurationService.updateValue(cue.settingsKey, { sound, announcement }); - } else { - configurationService.updateValue(cue.settingsKey, { sound }); - } + const enabledSounds = qp.selectedItems.map(i => i.signal); + const disabledSounds = qp.items.map(i => (i as any).signal).filter(i => !enabledSounds.includes(i)); + for (const signal of enabledSounds) { + let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(signal.settingsKey); + sound = userGestureSignals.includes(signal) ? 'userGesture' : accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'; + if (announcement) { + configurationService.updateValue(signal.settingsKey, { sound, announcement }); + } else { + configurationService.updateValue(signal.settingsKey, { sound }); } } - for (const cue of disabledCues) { - const announcement = cue.announcementMessage ? configurationService.getValue(cue.settingsKey + '.announcement') : undefined; - const sound = userGestureSignals.includes(cue) ? 'never' : 'off'; + for (const signal of disabledSounds) { + let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(signal.settingsKey); + sound = userGestureSignals.includes(signal) ? 'never' : 'off'; if (announcement) { - configurationService.updateValue(cue.settingsKey, { sound, announcement }); + configurationService.updateValue(signal.settingsKey, { sound, announcement }); } else { - configurationService.updateValue(cue.settingsKey, { sound }); + configurationService.updateValue(signal.settingsKey, { sound }); } } qp.hide(); @@ -104,23 +102,21 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { })); const qp = quickInputService.createQuickPick(); qp.items = items; - qp.selectedItems = items.filter(i => accessibilitySignalService.isAnnouncementEnabled(i.signal)); + qp.selectedItems = items.filter(i => accessibilitySignalService.isAnnouncementEnabled(i.signal) || configurationService.getValue(i.signal.settingsKey + '.announcement') !== 'never'); qp.onDidAccept(() => { const enabledAnnouncements = qp.selectedItems.map(i => i.signal); const disabledAnnouncements = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledAnnouncements.includes(cue)); - for (const cue of enabledAnnouncements) { - if (!userGestureSignals.includes(cue)) { - let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(cue.settingsKey); - announcement = cue.announcementMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; - if (announcement) { - configurationService.updateValue(cue.settingsKey, { sound, announcement }); - } + for (const signal of enabledAnnouncements) { + let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(signal.settingsKey); + announcement = userGestureSignals.includes(signal) ? 'userGesture' : signal.announcementMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; + if (announcement) { + configurationService.updateValue(signal.settingsKey, { sound, announcement }); } } - for (const cue of disabledAnnouncements) { - const announcement = userGestureSignals.includes(cue) ? 'never' : 'off'; - const sound = configurationService.getValue(cue.settingsKey + '.sound'); - configurationService.updateValue(cue.settingsKey, { sound, announcement }); + for (const signal of disabledAnnouncements) { + const announcement = userGestureSignals.includes(signal) ? 'never' : 'off'; + const sound = configurationService.getValue(signal.settingsKey + '.sound'); + configurationService.updateValue(signal.settingsKey, { sound, announcement }); } qp.hide(); }); From 4e4cc4c257ff20ec09fdffdb65470c9f5788b60b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 12:12:37 -0600 Subject: [PATCH 1148/1897] fix other issue --- .../contrib/accessibilitySignals/browser/commands.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 5a7bacde1c5..50db5214d76 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -109,14 +109,12 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { for (const signal of enabledAnnouncements) { let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(signal.settingsKey); announcement = userGestureSignals.includes(signal) ? 'userGesture' : signal.announcementMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; - if (announcement) { - configurationService.updateValue(signal.settingsKey, { sound, announcement }); - } + configurationService.updateValue(signal.settingsKey, { sound, announcement }); } for (const signal of disabledAnnouncements) { const announcement = userGestureSignals.includes(signal) ? 'never' : 'off'; const sound = configurationService.getValue(signal.settingsKey + '.sound'); - configurationService.updateValue(signal.settingsKey, { sound, announcement }); + configurationService.updateValue(signal.settingsKey, announcement ? { sound, announcement } : { sound }); } qp.hide(); }); From d9c1ee54b775801f23d345a884bdf777d24d8f21 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 12 Feb 2024 15:20:12 -0300 Subject: [PATCH 1149/1897] Parameterize agentId and command in followups (#204905) * Parameterize agentId and command in followups For #199908 * Update * Safety * Fix build --- .../api/browser/mainThreadChatAgents2.ts | 4 +- .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostChatAgents2.ts | 31 ++++++++------- .../api/common/extHostTypeConverters.ts | 38 +++++++++++++++++-- .../contrib/chat/browser/chatFollowups.ts | 18 +++++++-- .../contrib/chat/browser/chatWidget.ts | 12 +++++- .../contrib/chat/common/chatAgents.ts | 8 ++-- .../contrib/chat/common/chatService.ts | 2 + .../contrib/chat/common/chatServiceImpl.ts | 2 +- .../chat/test/common/chatService.test.ts | 4 +- .../chat/test/common/voiceChat.test.ts | 2 +- .../vscode.proposed.chatAgents2.d.ts | 10 ++++- .../vscode.proposed.chatAgents2Additions.d.ts | 4 +- 13 files changed, 99 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index cdd867d0160..3229725e96a 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -87,12 +87,12 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA this._pendingProgress.delete(request.requestId); } }, - provideFollowups: async (result, token): Promise => { + provideFollowups: async (request, result, token): Promise => { if (!this._agents.get(handle)?.hasFollowups) { return []; } - return this._proxy.$provideFollowups(handle, result, token); + return this._proxy.$provideFollowups(request, handle, result, token); }, get lastSlashCommands() { return lastSlashCommands; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 7626fb4364f..964afc507dc 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1222,7 +1222,7 @@ export type IChatAgentHistoryEntryDto = { export interface ExtHostChatAgentsShape2 { $invokeAgent(handle: number, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; $provideSlashCommands(handle: number, token: CancellationToken): Promise; - $provideFollowups(handle: number, result: IChatAgentResult, token: CancellationToken): Promise; + $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, token: CancellationToken): Promise; $acceptFeedback(handle: number, result: IChatAgentResult, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void; $acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void; $invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 65a95d5374e..a807fca2d00 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -278,14 +278,15 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { return agent.provideSlashCommands(token); } - $provideFollowups(handle: number, result: IChatAgentResult, token: CancellationToken): Promise { + async $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { return Promise.resolve([]); } const ehResult = typeConvert.ChatAgentResult.to(result); - return agent.provideFollowups(ehResult, token); + return (await agent.provideFollowups(ehResult, token)) + .map(f => typeConvert.ChatFollowup.from(f, request)); } $acceptFeedback(handle: number, result: IChatAgentResult, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void { @@ -309,23 +310,19 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { Object.freeze({ result: ehResult, kind })); } - $acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void { + $acceptAction(handle: number, result: IChatAgentResult, event: IChatUserActionEvent): void { const agent = this._agents.get(handle); if (!agent) { return; } - if (action.action.kind === 'vote') { + if (event.action.kind === 'vote') { // handled by $acceptFeedback return; } - const ehResult = typeConvert.ChatAgentResult.to(result); - if (action.action.kind === 'command') { - const commandAction: vscode.ChatAgentCommandAction = { kind: 'command', commandButton: typeConvert.ChatResponseProgress.toProgressContent(action.action.commandButton, this.commands.converter) as vscode.ChatAgentCommandButton }; - agent.acceptAction(Object.freeze({ action: commandAction, result: ehResult })); - return; - } else { - agent.acceptAction(Object.freeze({ action: action.action, result: ehResult })); + const ehAction = typeConvert.ChatAgentUserActionEvent.to(result, event, this.commands.converter); + if (ehAction) { + agent.acceptAction(Object.freeze(ehAction)); } } @@ -354,7 +351,8 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { return; } - return await agent.provideSampleQuestions(token); + return (await agent.provideSampleQuestions(token)) + .map(f => typeConvert.ChatFollowup.from(f, undefined)); } } @@ -418,7 +416,7 @@ class ExtHostChatAgent { })); } - async provideFollowups(result: vscode.ChatAgentResult2, token: CancellationToken): Promise { + async provideFollowups(result: vscode.ChatAgentResult2, token: CancellationToken): Promise { if (!this._followupProvider) { return []; } @@ -429,7 +427,8 @@ class ExtHostChatAgent { return followups // Filter out "command followups" from older providers .filter(f => !(f && 'commandId' in f)) - .map(f => typeConvert.ChatFollowup.from(f)); + // Filter out followups from older providers before 'message' changed to 'prompt' + .filter(f => !(f && 'message' in f)); } async provideWelcomeMessage(token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined> { @@ -449,7 +448,7 @@ class ExtHostChatAgent { }); } - async provideSampleQuestions(token: CancellationToken): Promise { + async provideSampleQuestions(token: CancellationToken): Promise { if (!this._welcomeMessageProvider || !this._welcomeMessageProvider.provideSampleQuestions) { return []; } @@ -458,7 +457,7 @@ class ExtHostChatAgent { return []; } - return content?.map(f => typeConvert.ChatFollowup.from(f)); + return content; } get apiAgent(): vscode.ChatAgent2 { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index f41b939a927..2a3a71e9029 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -36,7 +36,7 @@ import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/edit import { IViewBadge } from 'vs/workbench/common/views'; import { IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatCommandButton, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatCommandButton, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTreeData, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IInlineChatCommandFollowup, IInlineChatFollowup, IInlineChatReplyFollowup, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -2194,14 +2194,26 @@ export namespace DataTransfer { } export namespace ChatFollowup { - export function from(followup: vscode.ChatAgentFollowup): IChatFollowup { + export function from(followup: vscode.ChatAgentFollowup, request: IChatAgentRequest | undefined): IChatFollowup { return { kind: 'reply', - message: followup.message, + agentId: followup.agentId ?? request?.agentId ?? '', + subCommand: followup.subCommand ?? request?.command, + message: followup.prompt, title: followup.title, tooltip: followup.tooltip, }; } + + export function to(followup: IChatFollowup): vscode.ChatAgentFollowup { + return { + prompt: followup.message, + title: followup.title, + agentId: followup.agentId, + subCommand: followup.subCommand, + tooltip: followup.tooltip, + }; + } } export namespace ChatInlineFollowup { @@ -2642,6 +2654,26 @@ export namespace ChatAgentResult { } } +export namespace ChatAgentUserActionEvent { + export function to(result: IChatAgentResult, event: IChatUserActionEvent, commandsConverter: CommandsConverter): vscode.ChatAgentUserActionEvent | undefined { + if (event.action.kind === 'vote') { + // Is the "feedback" type + return; + } + + const ehResult = ChatAgentResult.to(result); + if (event.action.kind === 'command') { + const commandAction: vscode.ChatAgentCommandAction = { kind: 'command', commandButton: ChatResponseProgress.toProgressContent(event.action.commandButton, commandsConverter) as vscode.ChatAgentCommandButton }; + return { action: commandAction, result: ehResult }; + } else if (event.action.kind === 'followUp') { + const followupAction: vscode.ChatAgentFollowupAction = { kind: 'followUp', followup: ChatFollowup.to(event.action.followup) }; + return { action: followupAction, result: ehResult }; + } else { + return { action: event.action, result: ehResult }; + } + } +} + export namespace TerminalQuickFix { export function from(quickFix: vscode.TerminalQuickFixTerminalCommand | vscode.TerminalQuickFixOpener | vscode.Command, converter: Command.ICommandsConverter, disposables: DisposableStore): extHostProtocol.ITerminalQuickFixTerminalCommandDto | extHostProtocol.ITerminalQuickFixOpenerDto | extHostProtocol.ICommandDto | undefined { diff --git a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts index 70e92d3f5b1..827de16655c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts +++ b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts @@ -9,6 +9,7 @@ import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IInlineChatFollowup } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -42,9 +43,20 @@ export class ChatFollowups extend button.element.classList.add('interactive-followup-command'); } button.element.ariaLabel = localize('followUpAriaLabel', "Follow up question: {0}", followup.title); - const label = followup.kind === 'reply' ? - '$(sparkle) ' + (followup.title || followup.message) : - followup.title; + let prefix = ''; + if ('agentId' in followup && followup.agentId) { + prefix += `${chatAgentLeader}${followup.agentId} `; + if ('subCommand' in followup && followup.subCommand) { + prefix += `${chatSubcommandLeader}${followup.subCommand} `; + } + } + + let label = ''; + if (followup.kind === 'reply') { + label = '$(sparkle) ' + (followup.title || (prefix + followup.message)); + } else { + label = followup.title; + } button.label = new MarkdownString(label, { supportThemeIcons: true }); this._register(button.onDidClick(() => this.clickHandler(followup))); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 13bd6f3d772..e243033f713 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -33,7 +33,7 @@ import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common import { IChatFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { IParsedChatRequest, chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; @@ -443,7 +443,15 @@ export class ChatWidget extends Disposable implements IChatWidget { return; } - this.acceptInput(e.followup.message); + let msg = ''; + if (e.followup.agentId !== this.chatAgentService.getDefaultAgent()?.id) { + msg = `${chatAgentLeader}${e.followup.agentId} `; + if (e.followup.subCommand) { + msg += `${chatSubcommandLeader}${e.followup.subCommand} `; + } + } + msg += e.followup.message; + this.acceptInput(msg); if (!e.response) { // Followups can be shown by the welcome message, then there is no response associated. diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index e5d20e0db45..8647b65ad43 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -31,7 +31,7 @@ export interface IChatAgentData { export interface IChatAgent extends IChatAgentData { invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; - provideFollowups?(result: IChatAgentResult, token: CancellationToken): Promise; + provideFollowups?(request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise; lastSlashCommands?: IChatAgentCommand[]; provideSlashCommands(token: CancellationToken): Promise; provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>; @@ -107,7 +107,7 @@ export interface IChatAgentService { readonly onDidChangeAgents: Event; registerAgent(agent: IChatAgent): IDisposable; invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; - getFollowups(id: string, result: IChatAgentResult, token: CancellationToken): Promise; + getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise; getAgents(): Array; getAgent(id: string): IChatAgent | undefined; getDefaultAgent(): IChatAgent | undefined; @@ -186,7 +186,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService { return await data.agent.invoke(request, progress, history, token); } - async getFollowups(id: string, result: IChatAgentResult, token: CancellationToken): Promise { + async getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise { const data = this._agents.get(id); if (!data) { throw new Error(`No agent with id ${id}`); @@ -196,6 +196,6 @@ export class ChatAgentService extends Disposable implements IChatAgentService { return []; } - return data.agent.provideFollowups(result, token); + return data.agent.provideFollowups(request, result, token); } } diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 665ecb9eb29..4764e9f23bd 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -159,6 +159,8 @@ export interface IChatProvider { export interface IChatFollowup { kind: 'reply'; message: string; + agentId: string; + subCommand?: string; title?: string; tooltip?: string; } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 8c363983bb1..1191afb209a 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -568,7 +568,7 @@ export class ChatService extends Disposable implements IChatService { const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); rawResult = agentResult; - agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, agentResult, followupsCancelToken); + agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, followupsCancelToken); } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { request = model.addRequest(parsedRequest, { message, variables: {} }); // contributed slash commands 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 37ef4f4b4bd..3358df8cd4d 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -24,7 +24,7 @@ import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ChatAgentService, IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChat, IChatProgress, IChatProvider, IChatRequest } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatFollowup, IChatProgress, IChatProvider, IChatRequest } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -78,7 +78,7 @@ const chatAgentWithUsedContext: IChatAgent = { return { metadata: { metadataKey: 'value' } }; }, async provideFollowups(sessionId, token) { - return [{ kind: 'reply', message: 'Something else', tooltip: 'a tooltip' }]; + return [{ kind: 'reply', message: 'Something else', agentId: '', tooltip: 'a tooltip' } satisfies IChatFollowup]; }, }; diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index d40cdccffc8..bfa4baf30e9 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -44,7 +44,7 @@ suite('VoiceChat', () => { readonly onDidChangeAgents = Event.None; registerAgent(agent: IChatAgent): IDisposable { throw new Error(); } invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error(); } - getFollowups(id: string, result: IChatAgentResult, token: CancellationToken): Promise { throw new Error(); } + getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise { throw new Error(); } getAgents(): Array { return agents; } getAgent(id: string): IChatAgent | undefined { throw new Error(); } getDefaultAgent(): IChatAgent | undefined { throw new Error(); } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 9f0f46769f1..4ec0f8a92fc 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -216,8 +216,16 @@ declare module 'vscode' { export interface ChatAgentFollowup { /** * The message to send to the chat. + * TODO@API is it ok for variables to resolved from the text of this prompt, using the `#` syntax? */ - message: string; + prompt: string; + + /** + * By default, the followup goes to the same agent/subCommand. But these properties can be set to override that. + */ + agentId?: string; + + subCommand?: string; /** * A tooltip to show when hovering over the followup. diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index 712d31b296b..f42f874d0dd 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -208,7 +208,7 @@ declare module 'vscode' { commandButton: ChatAgentCommandButton; } - export interface ChatAgentSessionFollowupAction { + export interface ChatAgentFollowupAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'followUp'; followup: ChatAgentFollowup; @@ -221,7 +221,7 @@ declare module 'vscode' { export interface ChatAgentUserActionEvent { readonly result: ChatAgentResult2; - readonly action: ChatAgentCopyAction | ChatAgentInsertAction | ChatAgentTerminalAction | ChatAgentCommandAction | ChatAgentSessionFollowupAction | ChatAgentBugReportAction; + readonly action: ChatAgentCopyAction | ChatAgentInsertAction | ChatAgentTerminalAction | ChatAgentCommandAction | ChatAgentFollowupAction | ChatAgentBugReportAction; } export interface ChatVariableValue { From 0e1ccb789954fde827f6c67602174a2eed3d2458 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 12:36:55 -0600 Subject: [PATCH 1150/1897] Fix test issue --- .../test/browser/inlineCompletionsProvider.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts index aa5c7a4e4a6..4c846025949 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts @@ -776,8 +776,8 @@ async function withAsyncTestCodeEditorAndInlineCompletionsModel( } options.serviceCollection.set(ILanguageFeaturesService, languageFeaturesService); options.serviceCollection.set(IAccessibilitySignalService, { - playAudioCue: async () => { }, - isEnabled(cue: unknown) { return false; }, + playSignal: async () => { }, + isSoundEnabled(signal: unknown) { return false; }, } as any); const d = languageFeaturesService.inlineCompletionsProvider.register({ pattern: '**' }, options.provider); disposableStore.add(d); From 7ff2572a3e1ac8a038045790dd14660441317417 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Mon, 12 Feb 2024 10:45:06 -0800 Subject: [PATCH 1151/1897] remove leftover console log in quickfix (#205022) removed leftover log --- .../src/languageFeatures/quickFix.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index afc489c0521..a627670c70a 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -363,7 +363,6 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider Date: Mon, 12 Feb 2024 19:46:00 +0100 Subject: [PATCH 1152/1897] Set settings directly from the release notes (#204832) * Set settings directly from the release notes Fixes #204338 * Fix build --- build/lib/i18n.resources.json | 4 + src/vs/base/common/network.ts | 5 + .../browser/markdownDocumentRenderer.ts | 9 +- .../browser/markdownSettingRenderer.ts | 187 ++++++++++++++++++ .../browser/markdownSettingRenderer.test.ts | 147 ++++++++++++++ .../update/browser/releaseNotesEditor.ts | 63 ++++-- 6 files changed, 399 insertions(+), 16 deletions(-) create mode 100644 src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts create mode 100644 src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index cf74aac44be..e4acd66ea52 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -62,6 +62,10 @@ "name": "vs/workbench/contrib/mappedEdits", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/markdown", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/comments", "project": "vscode-workbench" diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 3e0cc0071af..5dde8ca080b 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -111,6 +111,11 @@ export namespace Schemas { * Scheme used for the Source Control commit input's text document */ export const vscodeSourceControl = 'vscode-scm'; + + /** + * Scheme used for special rendering of settings in the release notes + */ + export const codeSetting = 'code-setting'; } export function matchesScheme(target: URI | string, scheme: string): boolean { diff --git a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts index fb9cf55df78..fce923cad92 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts @@ -13,6 +13,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { tokenizeToString } from 'vs/editor/common/languages/textToHtmlTokenizer'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { escape } from 'vs/base/common/strings'; +import { SimpleSettingRenderer } from 'vs/workbench/contrib/markdown/browser/markdownSettingRenderer'; export const DEFAULT_MARKDOWN_STYLES = ` body { @@ -195,6 +196,7 @@ export async function renderMarkdownDocument( shouldSanitize: boolean = true, allowUnknownProtocols: boolean = false, token?: CancellationToken, + settingRenderer?: SimpleSettingRenderer ): Promise { const highlight = (code: string, lang: string | undefined, callback: ((error: any, code: string) => void) | undefined): any => { @@ -220,8 +222,13 @@ export async function renderMarkdownDocument( return ''; }; + const renderer = new marked.Renderer(); + if (settingRenderer) { + renderer.html = settingRenderer.getHtmlRenderer(); + } + return new Promise((resolve, reject) => { - marked(text, { highlight }, (err, value) => err ? reject(err) : resolve(value)); + marked(text, { highlight, renderer }, (err, value) => err ? reject(err) : resolve(value)); }).then(raw => { if (shouldSanitize) { return sanitize(raw, allowUnknownProtocols); diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts new file mode 100644 index 00000000000..b593321746b --- /dev/null +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -0,0 +1,187 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ISetting, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; +import { settingKeyToDisplayFormat } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; +import { URI } from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { DefaultSettings } from 'vs/workbench/services/preferences/common/preferencesModels'; + +const codeSettingRegex = /^/; + +export class SimpleSettingRenderer { + private defaultSettings: DefaultSettings; + private updatedSettings = new Map(); // setting ID to user's original setting value + private encounteredSettings = new Map(); // setting ID to setting + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + this.defaultSettings = new DefaultSettings([], ConfigurationTarget.USER); + } + + getHtmlRenderer(): (html: string) => string { + return (html): string => { + const match = codeSettingRegex.exec(html); + if (match && match.length === 3) { + const settingId = match[1]; + const rendered = this.render(settingId, match[2]); + if (rendered) { + html = html.replace(codeSettingRegex, rendered); + } + } + return html; + }; + } + + private settingsGroups: ISettingsGroup[] | undefined = undefined; + private getSetting(settingId: string): ISetting | undefined { + if (!this.settingsGroups) { + this.settingsGroups = this.defaultSettings.getSettingsGroups(); + } + if (this.encounteredSettings.has(settingId)) { + return this.encounteredSettings.get(settingId); + } + for (const group of this.settingsGroups) { + for (const section of group.sections) { + for (const setting of section.settings) { + if (setting.key === settingId) { + this.encounteredSettings.set(settingId, setting); + return setting; + } + } + } + } + return undefined; + } + + parseValue(settingId: string, value: string): any { + if (value === 'undefined') { + return undefined; + } + const setting = this.getSetting(settingId); + if (!setting) { + return value; + } + + switch (setting.type) { + case 'boolean': + return value === 'true'; + case 'number': + return parseInt(value, 10); + case 'string': + default: + return value; + } + } + + private render(settingId: string, newValue: string): string | undefined { + const setting = this.getSetting(settingId); + if (!setting) { + return ''; + } + + return this.renderSetting(setting, newValue); + } + + private viewInSettings(settingId: string, alreadySet: boolean): string { + let message: string; + if (alreadySet) { + const displayName = settingKeyToDisplayFormat(settingId); + message = nls.localize('viewInSettingsDetailed', "View \"{0}: {1}\" in Settings", displayName.category, displayName.label); + } else { + message = nls.localize('viewInSettings', "View in Settings"); + } + return `${message}`; + } + + private renderRestorePreviousSetting(settingId: string): string { + const displayName = settingKeyToDisplayFormat(settingId); + const value = this.updatedSettings.get(settingId); + const message = nls.localize('restorePreviousValue', "Restore value of \"{0}: {1}\"", displayName.category, displayName.label); + return `${message}`; + } + + private renderBooleanSetting(setting: ISetting, value: string): string | undefined { + const booleanValue: boolean = value === 'true' ? true : false; + const currentValue = this.configurationService.getValue(setting.key); + if (currentValue === booleanValue || (currentValue === undefined && setting.value === booleanValue)) { + return undefined; + } + + const displayName = settingKeyToDisplayFormat(setting.key); + let message: string; + if (booleanValue) { + message = nls.localize('trueMessage', "Enable \"{0}: {1}\" now", displayName.category, displayName.label); + } else { + message = nls.localize('falseMessage', "Disable \"{0}: {1}\" now", displayName.category, displayName.label); + } + return `${message}`; + } + + private renderStringSetting(setting: ISetting, value: string): string | undefined { + const currentValue = this.configurationService.getValue(setting.key); + if (currentValue === value || (currentValue === undefined && setting.value === value)) { + return undefined; + } + + const displayName = settingKeyToDisplayFormat(setting.key); + const message = nls.localize('stringValue', "Set \"{0}: {1}\" to \"{2}\" now", displayName.category, displayName.label, value); + return `${message}`; + } + + private renderNumberSetting(setting: ISetting, value: string): string | undefined { + const numberValue: number = parseInt(value, 10); + const currentValue = this.configurationService.getValue(setting.key); + if (currentValue === numberValue || (currentValue === undefined && setting.value === numberValue)) { + return undefined; + } + + const displayName = settingKeyToDisplayFormat(setting.key); + const message = nls.localize('numberValue', "Set \"{0}: {1}\" to {2} now", displayName.category, displayName.label, numberValue); + return `${message}`; + + } + + private renderSetting(setting: ISetting, newValue: string | undefined): string | undefined { + let renderedSetting: string | undefined; + + if (newValue !== undefined) { + if (this.updatedSettings.has(setting.key)) { + renderedSetting = this.renderRestorePreviousSetting(setting.key); + } else if (setting.type === 'boolean') { + renderedSetting = this.renderBooleanSetting(setting, newValue); + } else if (setting.type === 'string') { + renderedSetting = this.renderStringSetting(setting, newValue); + } else if (setting.type === 'number') { + renderedSetting = this.renderNumberSetting(setting, newValue); + } + } + + if (!renderedSetting) { + return `(${this.viewInSettings(setting.key, true)})`; + } + + return nls.localize({ key: 'fullRenderedSetting', comment: ['A pair of already localized links. The first argument is a link to change a setting, the second is a link to view the setting.'] }, + "({0} | {1})", renderedSetting, this.viewInSettings(setting.key, false),); + } + + async updateSettingValue(uri: URI) { + if (uri.scheme !== Schemas.codeSetting) { + return; + } + const settingId = uri.authority; + const newSettingValue = this.parseValue(uri.authority, uri.path.substring(1)); + const oldSettingValue = this.configurationService.inspect(settingId).userValue; + if (newSettingValue === this.updatedSettings.get(settingId)) { + this.updatedSettings.delete(settingId); + } else { + this.updatedSettings.set(settingId, oldSettingValue); + } + await this.configurationService.updateValue(settingId, newSettingValue, ConfigurationTarget.USER); + } +} diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts new file mode 100644 index 00000000000..bc215847e49 --- /dev/null +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -0,0 +1,147 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as assert from 'assert'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { SimpleSettingRenderer } from 'vs/workbench/contrib/markdown/browser/markdownSettingRenderer'; + +const configuration: IConfigurationNode = { + 'id': 'examples', + 'title': 'Examples', + 'type': 'object', + 'properties': { + 'example.booleanSetting': { + 'type': 'boolean', + 'default': false, + 'scope': ConfigurationScope.APPLICATION + }, + 'example.booleanSetting2': { + 'type': 'boolean', + 'default': true, + 'scope': ConfigurationScope.APPLICATION + }, + 'example.stringSetting': { + 'type': 'string', + 'default': 'one', + 'scope': ConfigurationScope.APPLICATION + }, + 'example.numberSetting': { + 'type': 'number', + 'default': 3, + 'scope': ConfigurationScope.APPLICATION + } + } +}; + +class MarkdownConfigurationService extends TestConfigurationService { + override async updateValue(key: string, value: any): Promise { + const [section, setting] = key.split('.'); + return this.setUserConfiguration(section, { [setting]: value }); + } +} + +suite('Markdown Setting Renderer Test', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + let configurationService: TestConfigurationService; + let settingRenderer: SimpleSettingRenderer; + + suiteSetup(() => { + configurationService = new MarkdownConfigurationService(); + Registry.as(Extensions.Configuration).registerConfiguration(configuration); + settingRenderer = new SimpleSettingRenderer(configurationService); + }); + + suiteTeardown(() => { + Registry.as(Extensions.Configuration).deregisterConfigurations([configuration]); + }); + + test('render boolean setting', () => { + const htmlRenderer = settingRenderer.getHtmlRenderer(); + const htmlNoValue = ''; + const renderedHtmlNoValue = htmlRenderer(htmlNoValue); + assert.equal(renderedHtmlNoValue, + `(View "Example: Boolean Setting" in Settings)`); + + const htmlWithValue = ''; + const renderedHtmlWithValue = htmlRenderer(htmlWithValue); + assert.equal(renderedHtmlWithValue, + `(Enable "Example: Boolean Setting" now | View in Settings)`); + + const htmlWithValueSetToFalse = ''; + const renderedHtmlWithValueSetToFalse = htmlRenderer(htmlWithValueSetToFalse); + assert.equal(renderedHtmlWithValueSetToFalse, + `(Disable "Example: Boolean Setting2" now | View in Settings)`); + + const htmlSameValue = ''; + const renderedHtmlSameValue = htmlRenderer(htmlSameValue); + assert.equal(renderedHtmlSameValue, + `(View "Example: Boolean Setting" in Settings)`); + }); + + test('render string setting', () => { + const htmlRenderer = settingRenderer.getHtmlRenderer(); + const htmlNoValue = ''; + const renderedHtmlNoValue = htmlRenderer(htmlNoValue); + assert.equal(renderedHtmlNoValue, + `(View "Example: String Setting" in Settings)`); + + const htmlWithValue = ''; + const renderedHtmlWithValue = htmlRenderer(htmlWithValue); + assert.equal(renderedHtmlWithValue, + `(Set "Example: String Setting" to "two" now | View in Settings)`); + + const htmlSameValue = ''; + const renderedHtmlSameValue = htmlRenderer(htmlSameValue); + assert.equal(renderedHtmlSameValue, + `(View "Example: String Setting" in Settings)`); + }); + + test('render number setting', () => { + const htmlRenderer = settingRenderer.getHtmlRenderer(); + const htmlNoValue = ''; + const renderedHtmlNoValue = htmlRenderer(htmlNoValue); + assert.equal(renderedHtmlNoValue, + `(View "Example: Number Setting" in Settings)`); + + const htmlWithValue = ''; + const renderedHtmlWithValue = htmlRenderer(htmlWithValue); + assert.equal(renderedHtmlWithValue, + `(Set "Example: Number Setting" to 2 now | View in Settings)`); + + const htmlSameValue = ''; + const renderedHtmlSameValue = htmlRenderer(htmlSameValue); + assert.equal(renderedHtmlSameValue, + `(View "Example: Number Setting" in Settings)`); + }); + + test('updating and restoring the setting through the renderer changes what is rendered', async () => { + await configurationService.setUserConfiguration('example', { stringSetting: 'two' }); + const htmlRenderer = settingRenderer.getHtmlRenderer(); + const htmlWithValue = ''; + const renderedHtmlWithValue = htmlRenderer(htmlWithValue); + assert.equal(renderedHtmlWithValue, + `(Set "Example: String Setting" to "three" now | View in Settings)`); + assert.equal(configurationService.getValue('example.stringSetting'), 'two'); + + // Update the value + await settingRenderer.updateSettingValue(URI.parse(`${Schemas.codeSetting}://example.stringSetting/three`)); + assert.equal(configurationService.getValue('example.stringSetting'), 'three'); + const renderedHtmlWithValueAfterUpdate = htmlRenderer(htmlWithValue); + assert.equal(renderedHtmlWithValueAfterUpdate, + `(Restore value of "Example: String Setting" | View in Settings)`); + + // Restore the value + await settingRenderer.updateSettingValue(URI.parse(`${Schemas.codeSetting}://example.stringSetting/two`)); + assert.equal(configurationService.getValue('example.stringSetting'), 'two'); + const renderedHtmlWithValueAfterRestore = htmlRenderer(htmlWithValue); + assert.equal(renderedHtmlWithValueAfterRestore, + `(Set "Example: String Setting" to "three" now | View in Settings)`); + }); +}); diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 411c6105a73..27846ded77b 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -30,10 +30,14 @@ import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/comm import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { SimpleSettingRenderer } from 'vs/workbench/contrib/markdown/browser/markdownSettingRenderer'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { Schemas } from 'vs/base/common/network'; export class ReleaseNotesManager { - + private readonly _simpleSettingRenderer: SimpleSettingRenderer; private readonly _releaseNotesCache = new Map>(); + private scrollPosition: { x: number; y: number } | undefined; private _currentReleaseNotes: WebviewInput | undefined = undefined; private _lastText: string | undefined; @@ -50,20 +54,28 @@ export class ReleaseNotesManager { @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, @IExtensionService private readonly _extensionService: IExtensionService, - @IProductService private readonly _productService: IProductService + @IProductService private readonly _productService: IProductService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { - TokenizationRegistry.onDidChange(async () => { - if (!this._currentReleaseNotes || !this._lastText) { - return; - } - const html = await this.renderBody(this._lastText); - if (this._currentReleaseNotes) { - this._currentReleaseNotes.webview.setHtml(html); - } + TokenizationRegistry.onDidChange(() => { + return this.updateHtml(); }); _configurationService.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables); _webviewWorkbenchService.onDidChangeActiveWebviewEditor(this.onDidChangeActiveWebviewEditor, this, this.disposables); + this._simpleSettingRenderer = this._instantiationService.createInstance(SimpleSettingRenderer); + } + + private async updateHtml() { + if (!this._currentReleaseNotes || !this._lastText) { + return; + } + const captureScroll = this.scrollPosition; + const html = await this.renderBody(this._lastText); + if (this._currentReleaseNotes) { + this._currentReleaseNotes.webview.setHtml(html); + this._currentReleaseNotes.webview.postMessage({ type: 'setScroll', value: { scrollPosition: captureScroll } }); + } } public async show(version: string): Promise { @@ -102,6 +114,8 @@ export class ReleaseNotesManager { disposables.add(this._currentReleaseNotes.webview.onMessage(e => { if (e.message.type === 'showReleaseNotes') { this._configurationService.updateValue('update.showReleaseNotes', e.message.value); + } else if (e.message.type === 'scroll') { + this.scrollPosition = e.message.value.scrollPosition; } })); @@ -204,10 +218,15 @@ export class ReleaseNotesManager { return this._releaseNotesCache.get(version)!; } - private onDidClickLink(uri: URI) { - this.addGAParameters(uri, 'ReleaseNotes') - .then(updated => this._openerService.open(updated)) - .then(undefined, onUnexpectedError); + private async onDidClickLink(uri: URI) { + if (uri.scheme === Schemas.codeSetting) { + await this._simpleSettingRenderer.updateSettingValue(uri); + this.updateHtml(); + } else { + this.addGAParameters(uri, 'ReleaseNotes') + .then(updated => this._openerService.open(updated, { allowCommands: ['workbench.action.openSettings'] })) + .then(undefined, onUnexpectedError); + } } private async addGAParameters(uri: URI, origin: string, experiment = '1'): Promise { @@ -221,7 +240,7 @@ export class ReleaseNotesManager { private async renderBody(text: string) { const nonce = generateUuid(); - const content = await renderMarkdownDocument(text, this._extensionService, this._languageService, false); + const content = await renderMarkdownDocument(text, this._extensionService, this._languageService, false, undefined, undefined, this._simpleSettingRenderer); const colorMap = TokenizationRegistry.getColorMap(); const css = colorMap ? generateTokensCSSForColorMap(colorMap) : ''; const showReleaseNotes = Boolean(this._configurationService.getValue('update.showReleaseNotes')); @@ -267,9 +286,23 @@ export class ReleaseNotesManager { window.addEventListener('message', event => { if (event.data.type === 'showReleaseNotes') { input.checked = event.data.value; + } else if (event.data.type === 'setScroll') { + window.scrollTo(event.data.value.scrollPosition.x, event.data.value.scrollPosition.y); } }); + window.onscroll = () => { + vscode.postMessage({ + type: 'scroll', + value: { + scrollPosition: { + x: window.scrollX, + y: window.scrollY + } + } + }); + }; + input.addEventListener('change', event => { vscode.postMessage({ type: 'showReleaseNotes', value: input.checked }, '*'); }); From 66713a49cb6b8c044dfa7a7c64a4b86e41c6ec8f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Feb 2024 20:08:34 +0100 Subject: [PATCH 1153/1897] zoom - do not localise codicons (#205024) --- src/vs/workbench/electron-sandbox/window.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 35136a73c5d..6880e360e02 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -1076,9 +1076,9 @@ export class NativeWindow extends BaseWindow { let text: string | undefined = undefined; if (currentZoomLevel < this.configuredWindowZoomLevel) { - text = localize('zoomedOut', "$(zoom-out)"); + text = '$(zoom-out)'; } else if (currentZoomLevel > this.configuredWindowZoomLevel) { - text = localize('zoomedIn', "$(zoom-in)"); + text = '$(zoom-in)'; } entry.updateZoomEntry(text ?? false, targetWindowId); From 1dd2c349f234a6befc1bd5fa1d104e324090a371 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Feb 2024 20:28:47 +0100 Subject: [PATCH 1154/1897] voice - fix leading whitespace in transcription (#205026) --- .../contrib/chat/electron-sandbox/actions/voiceChatActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 3d826bcaba8..41083a4bb9b 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -295,7 +295,7 @@ class VoiceChatSessions { break; case SpeechToTextStatus.Recognizing: if (text) { - session.controller.updateInput([inputValue, text].join(' ')); + session.controller.updateInput(inputValue ? [inputValue, text].join(' ') : text); if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true) { acceptTranscriptionScheduler.cancel(); } @@ -303,7 +303,7 @@ class VoiceChatSessions { break; case SpeechToTextStatus.Recognized: if (text) { - inputValue = [inputValue, text].join(' '); + inputValue = inputValue ? [inputValue, text].join(' ') : text; session.controller.updateInput(inputValue); if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true && !waitingForInput) { acceptTranscriptionScheduler.schedule(); From fcb468f4aef585fefd3de735c38566f1c0476e33 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Mon, 12 Feb 2024 11:33:31 -0800 Subject: [PATCH 1155/1897] add class to re-use style from the rest of the debug views (#205030) --- .../browser/contrib/notebookVariables/notebookVariablesView.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index 3e4cf3cf7fc..82360512546 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -79,6 +79,7 @@ export class NotebookVariablesView extends ViewPane { protected override renderBody(container: HTMLElement): void { super.renderBody(container); + this.element.classList.add('debug-pane'); this.tree = >this.instantiationService.createInstance( WorkbenchAsyncDataTree, From 5451a4e69a305a8146808e1957f67252195dcd81 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 12 Feb 2024 11:49:24 -0800 Subject: [PATCH 1156/1897] Try use shell integration in debug terminals Fixes #204694 --- src/vs/platform/terminal/common/terminal.ts | 6 ++++++ src/vs/platform/terminal/node/terminalEnvironment.ts | 2 +- src/vs/workbench/api/browser/mainThreadTerminalService.ts | 1 + src/vs/workbench/api/common/extHost.protocol.ts | 1 + src/vs/workbench/api/common/extHostTerminalService.ts | 2 ++ src/vs/workbench/api/node/extHostDebugService.ts | 3 +++ 6 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index ffe0e56a189..4dea050e336 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -608,6 +608,12 @@ export interface IShellLaunchConfig { */ isTransient?: boolean; + /** + * Attempt to force shell integration to be enabled by bypassing the {@link isFeatureTerminal} + * equals false requirement. + */ + forceShellIntegration?: boolean; + /** * Create a terminal without shell integration even when it's enabled */ diff --git a/src/vs/platform/terminal/node/terminalEnvironment.ts b/src/vs/platform/terminal/node/terminalEnvironment.ts index 066ca8f6bc1..703e0fec623 100644 --- a/src/vs/platform/terminal/node/terminalEnvironment.ts +++ b/src/vs/platform/terminal/node/terminalEnvironment.ts @@ -117,7 +117,7 @@ export function getShellIntegrationInjection( // - There is no executable (not sure what script to run) // - The terminal is used by a feature like tasks or debugging const useWinpty = isWindows && (!options.windowsEnableConpty || getWindowsBuildNumber() < 18309); - if (!options.shellIntegration.enabled || !shellLaunchConfig.executable || shellLaunchConfig.isFeatureTerminal || shellLaunchConfig.hideFromUser || shellLaunchConfig.ignoreShellIntegration || useWinpty) { + if (!options.shellIntegration.enabled || !shellLaunchConfig.executable || (shellLaunchConfig.isFeatureTerminal && !shellLaunchConfig.forceShellIntegration) || shellLaunchConfig.hideFromUser || shellLaunchConfig.ignoreShellIntegration || useWinpty) { return undefined; } diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 2293708c63b..28bc27cbb12 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -152,6 +152,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape ? (id, cols, rows) => new TerminalProcessExtHostProxy(id, cols, rows, this._terminalService) : undefined, extHostTerminalId, + forceShellIntegration: launchConfig.forceShellIntegration, isFeatureTerminal: launchConfig.isFeatureTerminal, isExtensionOwnedTerminal: launchConfig.isExtensionOwnedTerminal, useShellEnvironment: launchConfig.useShellEnvironment, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 964afc507dc..4ed54e7b0e3 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -495,6 +495,7 @@ export interface TerminalLaunchConfig { strictEnv?: boolean; hideFromUser?: boolean; isExtensionCustomPtyTerminal?: boolean; + forceShellIntegration?: boolean; isFeatureTerminal?: boolean; isExtensionOwnedTerminal?: boolean; useShellEnvironment?: boolean; diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index e004acba809..cbe28de19ca 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -63,6 +63,7 @@ interface IEnvironmentVariableCollection extends vscode.EnvironmentVariableColle export interface ITerminalInternalOptions { cwd?: string | URI; isFeatureTerminal?: boolean; + forceShellIntegration?: boolean; useShellEnvironment?: boolean; resolvedExtHostIdentifier?: ExtHostTerminalIdentifier; /** @@ -165,6 +166,7 @@ export class ExtHostTerminal { initialText: options.message ?? undefined, strictEnv: options.strictEnv ?? undefined, hideFromUser: options.hideFromUser ?? undefined, + forceShellIntegration: internalOptions?.forceShellIntegration ?? undefined, isFeatureTerminal: internalOptions?.isFeatureTerminal ?? undefined, isExtensionOwnedTerminal: true, useShellEnvironment: internalOptions?.useShellEnvironment ?? undefined, diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 1ccfd2101da..90b977aa644 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -106,6 +106,9 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { giveShellTimeToInitialize = true; terminal = this._terminalService.createTerminalFromOptions(options, { isFeatureTerminal: true, + // Since debug termnials are REPLs, we want shell integration to be enabled. + // Ignore isFeatureTerminal when evaluating shell integration enablement. + forceShellIntegration: true, useShellEnvironment: true }); this._integratedTerminalInstances.insert(terminal, shellConfig); From b79d48c296a18057450205fe65029dabebb08ddc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 13:52:59 -0600 Subject: [PATCH 1157/1897] wip make request action --- .../chat/browser/terminal.chat.contribution.ts | 6 +++--- .../chat/browser/terminalChatWidget.ts | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index 6c44b62619c..c2bbe8aa74a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -149,11 +149,11 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalContextKeys.chatInputHasText + // TerminalContextKeys.chatInputHasText ), icon: Codicon.send, keybinding: { - when: TerminalContextKeys.chatSessionInProgress, + when: TerminalContextKeys.chatSessionInProgress.negate(), // TODO: // when: CTX_INLINE_CHAT_FOCUSED, weight: KeybindingWeight.EditorCore + 7, @@ -163,7 +163,7 @@ registerActiveXtermAction({ id: MenuId.TerminalChat, group: 'main', order: 1, - when: TerminalContextKeys.chatSessionInProgress.negate(), + // when: TerminalContextKeys.chatSessionInProgress.negate(), // TODO: // when: CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.isEqualTo(false) }, diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index fc85c9a5a50..1467a9a86ef 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -16,6 +16,9 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; import { MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_STATUS, MENU_CELL_CHAT_WIDGET_FEEDBACK } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; +import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { CancellationToken } from 'vs/base/common/cancellation'; export class TerminalChatWidget extends Disposable { private _scopedInstantiationService: IInstantiationService; @@ -27,12 +30,15 @@ export class TerminalChatWidget extends Disposable { private readonly _focusTracker: IFocusTracker; + private _chatModel: ChatModel | undefined; + constructor( private readonly _container: HTMLElement, private readonly _instance: ITerminalInstance, @IInstantiationService instantiationService: IInstantiationService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService) { + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IChatService private readonly _chatService: IChatService) { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); @@ -41,6 +47,7 @@ export class TerminalChatWidget extends Disposable { this._widgetContainer = document.createElement('div'); this._widgetContainer.classList.add('terminal-inline-chat'); this._container.appendChild(this._widgetContainer); + // this._widget = this._register(this._scopedInstantiationService.createInstance( // ChatWidget, // { viewId: 'terminal' }, @@ -108,6 +115,14 @@ export class TerminalChatWidget extends Disposable { } acceptInput(): void { // this._widget?.acceptInput(); + this._chatModel ??= this._chatService.startSession('terminal', CancellationToken.None); + + // if (!this._model) { + // throw new Error('Could not start chat session'); + // } + this._chatService?.sendRequest(this._chatModel?.sessionId!, this._inlineChatWidget.value); + this._inlineChatWidget.value = ''; + // this.widget.setModel(this.model, { inputValue: this._currentQuery }); } layout(width: number): void { // this._widget?.layout(100, width < 300 ? 300 : width); From f8818faa97a22fd0aa213c5fa3027f7b51b13916 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 12 Feb 2024 11:55:23 -0800 Subject: [PATCH 1158/1897] Add new shell language IDs to chatCodeblockActions.ts --- .../contrib/chat/browser/actions/chatCodeblockActions.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 592ee91e256..cee07851804 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -387,7 +387,10 @@ export function registerChatCodeBlockActions() { const shellLangIds = [ 'fish', + 'ps1', + 'pwsh', 'powershell', + 'sh', 'shellscript', 'zsh' ]; From d06c669a8daceb7169e5083b53ade71b1e81c401 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 13:57:18 -0600 Subject: [PATCH 1159/1897] Fix more issues --- .../inlineCompletions/browser/commands.ts | 2 +- .../browser/inlineCompletionsController.ts | 12 +- .../audioCues/browser/audioCueService.ts | 593 ------------------ .../files/test/browser/editorAutoSave.test.ts | 5 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 6 +- .../test/browser/bufferContentTracker.test.ts | 5 +- .../test/browser/workbenchTestServices.ts | 5 +- 7 files changed, 22 insertions(+), 606 deletions(-) delete mode 100644 src/vs/platform/audioCues/browser/audioCueService.ts diff --git a/src/vs/editor/contrib/inlineCompletions/browser/commands.ts b/src/vs/editor/contrib/inlineCompletions/browser/commands.ts index 9727bec2359..ddfde90862d 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/commands.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/commands.ts @@ -76,7 +76,7 @@ export class TriggerInlineSuggestionAction extends EditorAction { await asyncTransaction(async tx => { /** @description triggerExplicitly from command */ await controller?.model.get()?.triggerExplicitly(tx); - controller?.playAudioCue(tx); + controller?.playAccessibilitySignal(tx); }); } } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts index 14f427b5c70..320a8f17264 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts @@ -78,7 +78,7 @@ export class InlineCompletionsController extends Disposable { { min: 50, max: 50 } ); - private readonly _playAudioCueSignal = observableSignal(this); + private readonly _playAccessibilitySignal = observableSignal(this); private readonly _isReadonly = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.readOnly)); private readonly _textModel = observableFromEvent(this.editor.onDidChangeModel, () => this.editor.getModel()); @@ -213,14 +213,14 @@ export class InlineCompletionsController extends Disposable { let lastInlineCompletionId: string | undefined = undefined; this._register(autorunHandleChanges({ handleChange: (context, changeSummary) => { - if (context.didChange(this._playAudioCueSignal)) { + if (context.didChange(this._playAccessibilitySignal)) { lastInlineCompletionId = undefined; } return true; }, }, async reader => { - /** @description InlineCompletionsController.playAudioCueAndReadSuggestion */ - this._playAudioCueSignal.read(reader); + /** @description InlineCompletionsController.playAccessibilitySignalAndReadSuggestion */ + this._playAccessibilitySignal.read(reader); const model = this.model.read(reader); const state = model?.state.read(reader); @@ -249,8 +249,8 @@ export class InlineCompletionsController extends Disposable { this.editor.updateOptions({ inlineCompletionsAccessibilityVerbose: this._configurationService.getValue('accessibility.verbosity.inlineCompletions') }); } - public playAudioCue(tx: ITransaction) { - this._playAudioCueSignal.trigger(tx); + public playAccessibilitySignal(tx: ITransaction) { + this._playAccessibilitySignal.trigger(tx); } private provideScreenReaderUpdate(content: string): void { diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts deleted file mode 100644 index 7c7dfbe0567..00000000000 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ /dev/null @@ -1,593 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { FileAccess } from 'vs/base/common/network'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Event } from 'vs/base/common/event'; -import { localize } from 'vs/nls'; -import { observableFromEvent, derived } from 'vs/base/common/observable'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; - -export const IAudioCueService = createDecorator('audioCue'); - -export interface IAudioCueService { - readonly _serviceBrand: undefined; - playAudioCue(cue: AudioCue, options?: IAudioCueOptions): Promise; - playAudioCues(cues: (AudioCue | { cue: AudioCue; source: string })[]): Promise; - isCueEnabled(cue: AudioCue): boolean; - isAlertEnabled(cue: AudioCue): boolean; - onEnabledChanged(cue: AudioCue): Event; - onAlertEnabledChanged(cue: AudioCue): Event; - - playSound(cue: Sound, allowManyInParallel?: boolean): Promise; - playAudioCueLoop(cue: AudioCue, milliseconds: number): IDisposable; -} - -export interface IAudioCueOptions { - allowManyInParallel?: boolean; - source?: string; - /** - * For actions like save or format, depending on the - * configured value, we will only - * play the sound if the user triggered the action. - */ - userGesture?: boolean; -} - -export class AudioCueService extends Disposable implements IAudioCueService { - readonly _serviceBrand: undefined; - private readonly sounds: Map = new Map(); - private readonly screenReaderAttached = observableFromEvent( - this.accessibilityService.onDidChangeScreenReaderOptimized, - () => /** @description accessibilityService.onDidChangeScreenReaderOptimized */ this.accessibilityService.isScreenReaderOptimized() - ); - private readonly sentTelemetry = new Set(); - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, - @IAccessibilityService private readonly accessibilityService: IAccessibilityService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - ) { - super(); - } - - public async playAudioCue(cue: AudioCue, options: IAudioCueOptions = {}): Promise { - const alertMessage = cue.alertMessage; - if (this.isAlertEnabled(cue, options.userGesture) && alertMessage) { - this.accessibilityService.status(alertMessage); - } - - if (this.isCueEnabled(cue, options.userGesture)) { - this.sendAudioCueTelemetry(cue, options.source); - await this.playSound(cue.sound.getSound(), options.allowManyInParallel); - } - } - - public async playAudioCues(cues: (AudioCue | { cue: AudioCue; source: string })[]): Promise { - for (const cue of cues) { - this.sendAudioCueTelemetry('cue' in cue ? cue.cue : cue, 'source' in cue ? cue.source : undefined); - } - const cueArray = cues.map(c => 'cue' in c ? c.cue : c); - const alerts = cueArray.filter(cue => this.isAlertEnabled(cue)).map(c => c.alertMessage); - if (alerts.length) { - this.accessibilityService.status(alerts.join(', ')); - } - - // Some audio cues might reuse sounds. Don't play the same sound twice. - const sounds = new Set(cueArray.filter(cue => this.isCueEnabled(cue)).map(cue => cue.sound.getSound())); - await Promise.all(Array.from(sounds).map(sound => this.playSound(sound, true))); - - } - - - private sendAudioCueTelemetry(cue: AudioCue, source: string | undefined): void { - const isScreenReaderOptimized = this.accessibilityService.isScreenReaderOptimized(); - const key = cue.name + (source ? `::${source}` : '') + (isScreenReaderOptimized ? '{screenReaderOptimized}' : ''); - // Only send once per user session - if (this.sentTelemetry.has(key) || this.getVolumeInPercent() === 0) { - return; - } - this.sentTelemetry.add(key); - - this.telemetryService.publicLog2<{ - audioCue: string; - source: string; - isScreenReaderOptimized: boolean; - }, { - owner: 'hediet'; - - audioCue: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The audio cue that was played.' }; - source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source that triggered the audio cue (e.g. "diffEditorNavigation").' }; - isScreenReaderOptimized: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user is using a screen reader' }; - - comment: 'This data is collected to understand how audio cues are used and if more audio cues should be added.'; - }>('audioCue.played', { - audioCue: cue.name, - source: source ?? '', - isScreenReaderOptimized, - }); - } - - private getVolumeInPercent(): number { - const volume = this.configurationService.getValue('audioCues.volume'); - if (typeof volume !== 'number') { - return 50; - } - - return Math.max(Math.min(volume, 100), 0); - } - - private readonly playingSounds = new Set(); - - public async playSound(sound: Sound, allowManyInParallel = false): Promise { - if (!allowManyInParallel && this.playingSounds.has(sound)) { - return; - } - this.playingSounds.add(sound); - const url = FileAccess.asBrowserUri(`vs/platform/audioCues/browser/media/${sound.fileName}`).toString(true); - - try { - const sound = this.sounds.get(url); - if (sound) { - sound.volume = this.getVolumeInPercent() / 100; - sound.currentTime = 0; - await sound.play(); - } else { - const playedSound = await playAudio(url, this.getVolumeInPercent() / 100); - this.sounds.set(url, playedSound); - } - } catch (e) { - if (!e.message.includes('play() can only be initiated by a user gesture')) { - // tracking this issue in #178642, no need to spam the console - console.error('Error while playing sound', e); - } - } finally { - this.playingSounds.delete(sound); - } - } - - public playAudioCueLoop(cue: AudioCue, milliseconds: number): IDisposable { - let playing = true; - const playSound = () => { - if (playing) { - this.playAudioCue(cue, { allowManyInParallel: true }).finally(() => { - setTimeout(() => { - if (playing) { - playSound(); - } - }, milliseconds); - }); - } - }; - playSound(); - return toDisposable(() => playing = false); - } - - private readonly obsoleteAudioCuesEnabled = observableFromEvent( - Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration('audioCues.enabled') - ), - () => /** @description config: audioCues.enabled */ this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>('audioCues.enabled') - ); - - private readonly isCueEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { - const settingObservable = observableFromEvent( - Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.settingsKey) || e.affectsConfiguration(event.cue.signalSettingsKey) - ), - () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.cue.signalSettingsKey + '.audioCue') - ); - return derived(reader => { - /** @description audio cue enabled */ - const setting = settingObservable.read(reader); - if ( - setting === 'on' || - (setting === 'auto' && this.screenReaderAttached.read(reader)) - ) { - return true; - } else if (setting === 'always' || setting === 'userGesture' && event.userGesture) { - return true; - } - - const obsoleteSetting = this.obsoleteAudioCuesEnabled.read(reader); - if ( - obsoleteSetting === 'on' || - (obsoleteSetting === 'auto' && this.screenReaderAttached.read(reader)) - ) { - return true; - } - - return false; - }); - }, JSON.stringify); - - private readonly isAlertEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { - const settingObservable = observableFromEvent( - Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.alertSettingsKey!) || e.affectsConfiguration(event.cue.signalSettingsKey) - ), - () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.signalSettingsKey + '.alert') : false - ); - return derived(reader => { - /** @description alert enabled */ - const setting = settingObservable.read(reader); - if ( - !this.screenReaderAttached.read(reader) - ) { - return false; - } - return setting === true || setting === 'always' || setting === 'userGesture' && event.userGesture; - }); - }, JSON.stringify); - - public isAlertEnabled(cue: AudioCue, userGesture?: boolean): boolean { - if (!cue.alertSettingsKey) { - return false; - } - return this.isAlertEnabledCache.get({ cue, userGesture }).get() ?? false; - } - - public isCueEnabled(cue: AudioCue, userGesture?: boolean): boolean { - return this.isCueEnabledCache.get({ cue, userGesture }).get() ?? false; - } - - public onEnabledChanged(cue: AudioCue): Event { - return Event.fromObservableLight(this.isCueEnabledCache.get({ cue })); - } - - public onAlertEnabledChanged(cue: AudioCue): Event { - return Event.fromObservableLight(this.isAlertEnabledCache.get({ cue })); - } -} - - -/** - * Play the given audio url. - * @volume value between 0 and 1 - */ -function playAudio(url: string, volume: number): Promise { - return new Promise((resolve, reject) => { - const audio = new Audio(url); - audio.volume = volume; - audio.addEventListener('ended', () => { - resolve(audio); - }); - audio.addEventListener('error', (e) => { - // When the error event fires, ended might not be called - reject(e.error); - }); - audio.play().catch(e => { - // When play fails, the error event is not fired. - reject(e); - }); - }); -} - -class Cache { - private readonly map = new Map(); - constructor(private readonly getValue: (value: TArg) => TValue, private readonly getKey: (value: TArg) => unknown) { - } - - public get(arg: TArg): TValue { - if (this.map.has(arg)) { - return this.map.get(arg)!; - } - - const value = this.getValue(arg); - const key = this.getKey(arg); - this.map.set(key, value); - return value; - } -} - -/** - * Corresponds to the audio files in ./media. -*/ -export class Sound { - private static register(options: { fileName: string }): Sound { - const sound = new Sound(options.fileName); - return sound; - } - - public static readonly error = Sound.register({ fileName: 'error.mp3' }); - public static readonly warning = Sound.register({ fileName: 'warning.mp3' }); - public static readonly foldedArea = Sound.register({ fileName: 'foldedAreas.mp3' }); - public static readonly break = Sound.register({ fileName: 'break.mp3' }); - public static readonly quickFixes = Sound.register({ fileName: 'quickFixes.mp3' }); - public static readonly taskCompleted = Sound.register({ fileName: 'taskCompleted.mp3' }); - public static readonly taskFailed = Sound.register({ fileName: 'taskFailed.mp3' }); - public static readonly terminalBell = Sound.register({ fileName: 'terminalBell.mp3' }); - public static readonly diffLineInserted = Sound.register({ fileName: 'diffLineInserted.mp3' }); - public static readonly diffLineDeleted = Sound.register({ fileName: 'diffLineDeleted.mp3' }); - public static readonly diffLineModified = Sound.register({ fileName: 'diffLineModified.mp3' }); - public static readonly chatRequestSent = Sound.register({ fileName: 'chatRequestSent.mp3' }); - public static readonly chatResponsePending = Sound.register({ fileName: 'chatResponsePending.mp3' }); - public static readonly chatResponseReceived1 = Sound.register({ fileName: 'chatResponseReceived1.mp3' }); - public static readonly chatResponseReceived2 = Sound.register({ fileName: 'chatResponseReceived2.mp3' }); - public static readonly chatResponseReceived3 = Sound.register({ fileName: 'chatResponseReceived3.mp3' }); - public static readonly chatResponseReceived4 = Sound.register({ fileName: 'chatResponseReceived4.mp3' }); - public static readonly clear = Sound.register({ fileName: 'clear.mp3' }); - public static readonly save = Sound.register({ fileName: 'save.mp3' }); - public static readonly format = Sound.register({ fileName: 'format.mp3' }); - - private constructor(public readonly fileName: string) { } -} - -export class SoundSource { - constructor( - public readonly randomOneOf: Sound[] - ) { } - - public getSound(deterministic = false): Sound { - if (deterministic || this.randomOneOf.length === 1) { - return this.randomOneOf[0]; - } else { - const index = Math.floor(Math.random() * this.randomOneOf.length); - return this.randomOneOf[index]; - } - } -} - -export const enum AccessibilityAlertSettingId { - Save = 'accessibility.alert.save', - Format = 'accessibility.alert.format', - Clear = 'accessibility.alert.clear', - Breakpoint = 'accessibility.alert.breakpoint', - Error = 'accessibility.alert.error', - Warning = 'accessibility.alert.warning', - FoldedArea = 'accessibility.alert.foldedArea', - TerminalQuickFix = 'accessibility.alert.terminalQuickFix', - TerminalBell = 'accessibility.alert.terminalBell', - TerminalCommandFailed = 'accessibility.alert.terminalCommandFailed', - TaskCompleted = 'accessibility.alert.taskCompleted', - TaskFailed = 'accessibility.alert.taskFailed', - ChatRequestSent = 'accessibility.alert.chatRequestSent', - NotebookCellCompleted = 'accessibility.alert.notebookCellCompleted', - NotebookCellFailed = 'accessibility.alert.notebookCellFailed', - OnDebugBreak = 'accessibility.alert.onDebugBreak', - NoInlayHints = 'accessibility.alert.noInlayHints', - LineHasBreakpoint = 'accessibility.alert.lineHasBreakpoint', - ChatResponsePending = 'accessibility.alert.chatResponsePending' -} - - -export class AudioCue { - private static _audioCues = new Set(); - private static register(options: { - name: string; - sound: Sound | { - /** - * Gaming and other apps often play a sound variant when the same event happens again - * for an improved experience. This option enables audio cues to play a random sound. - */ - randomOneOf: Sound[]; - }; - settingsKey: string; - signalSettingsKey: string; - alertSettingsKey?: AccessibilityAlertSettingId; - alertMessage?: string; - }): AudioCue { - const soundSource = new SoundSource('randomOneOf' in options.sound ? options.sound.randomOneOf : [options.sound]); - const audioCue = new AudioCue(soundSource, options.name, options.settingsKey, options.signalSettingsKey, options.alertSettingsKey, options.alertMessage); - AudioCue._audioCues.add(audioCue); - return audioCue; - } - - public static get allAudioCues() { - return [...this._audioCues]; - } - - public static readonly error = AudioCue.register({ - name: localize('audioCues.lineHasError.name', 'Error on Line'), - sound: Sound.error, - settingsKey: 'audioCues.lineHasError', - alertSettingsKey: AccessibilityAlertSettingId.Error, - alertMessage: localize('accessibility.signals.lineHasError', 'Error'), - signalSettingsKey: 'accessibility.signals.lineHasError' - }); - public static readonly warning = AudioCue.register({ - name: localize('audioCues.lineHasWarning.name', 'Warning on Line'), - sound: Sound.warning, - settingsKey: 'audioCues.lineHasWarning', - alertSettingsKey: AccessibilityAlertSettingId.Warning, - alertMessage: localize('accessibility.signals.lineHasWarning', 'Warning'), - signalSettingsKey: 'accessibility.signals.lineHasWarning' - }); - public static readonly foldedArea = AudioCue.register({ - name: localize('audioCues.lineHasFoldedArea.name', 'Folded Area on Line'), - sound: Sound.foldedArea, - settingsKey: 'audioCues.lineHasFoldedArea', - alertSettingsKey: AccessibilityAlertSettingId.FoldedArea, - alertMessage: localize('accessibility.signals.lineHasFoldedArea', 'Folded'), - signalSettingsKey: 'accessibility.signals.lineHasFoldedArea' - }); - public static readonly break = AudioCue.register({ - name: localize('audioCues.lineHasBreakpoint.name', 'Breakpoint on Line'), - sound: Sound.break, - settingsKey: 'audioCues.lineHasBreakpoint', - alertSettingsKey: AccessibilityAlertSettingId.Breakpoint, - alertMessage: localize('accessibility.signals.lineHasBreakpoint', 'Breakpoint'), - signalSettingsKey: 'accessibility.signals.lineHasBreakpoint' - }); - public static readonly inlineSuggestion = AudioCue.register({ - name: localize('audioCues.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), - sound: Sound.quickFixes, - settingsKey: 'audioCues.lineHasInlineSuggestion', - signalSettingsKey: 'accessibility.signals.lineHasInlineSuggestion' - }); - - public static readonly terminalQuickFix = AudioCue.register({ - name: localize('audioCues.terminalQuickFix.name', 'Terminal Quick Fix'), - sound: Sound.quickFixes, - settingsKey: 'audioCues.terminalQuickFix', - alertSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, - alertMessage: localize('accessibility.signals.terminalQuickFix', 'Quick Fix'), - signalSettingsKey: 'accessibility.signals.terminalQuickFix' - }); - - public static readonly onDebugBreak = AudioCue.register({ - name: localize('audioCues.onDebugBreak.name', 'Debugger Stopped on Breakpoint'), - sound: Sound.break, - settingsKey: 'audioCues.onDebugBreak', - alertSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, - alertMessage: localize('accessibility.signals.onDebugBreak', 'Breakpoint'), - signalSettingsKey: 'accessibility.signals.onDebugBreak' - }); - - public static readonly noInlayHints = AudioCue.register({ - name: localize('audioCues.noInlayHints', 'No Inlay Hints on Line'), - sound: Sound.error, - settingsKey: 'audioCues.noInlayHints', - alertSettingsKey: AccessibilityAlertSettingId.NoInlayHints, - alertMessage: localize('accessibility.signals.noInlayHints', 'No Inlay Hints'), - signalSettingsKey: 'accessibility.signals.noInlayHints' - }); - - public static readonly taskCompleted = AudioCue.register({ - name: localize('audioCues.taskCompleted', 'Task Completed'), - sound: Sound.taskCompleted, - settingsKey: 'audioCues.taskCompleted', - alertSettingsKey: AccessibilityAlertSettingId.TaskCompleted, - alertMessage: localize('accessibility.signals.taskCompleted', 'Task Completed'), - signalSettingsKey: 'accessibility.signals.taskCompleted' - }); - - public static readonly taskFailed = AudioCue.register({ - name: localize('audioCues.taskFailed', 'Task Failed'), - sound: Sound.taskFailed, - settingsKey: 'audioCues.taskFailed', - alertSettingsKey: AccessibilityAlertSettingId.TaskFailed, - alertMessage: localize('accessibility.signals.taskFailed', 'Task Failed'), - signalSettingsKey: 'accessibility.signals.taskFailed' - }); - - public static readonly terminalCommandFailed = AudioCue.register({ - name: localize('audioCues.terminalCommandFailed', 'Terminal Command Failed'), - sound: Sound.error, - settingsKey: 'audioCues.terminalCommandFailed', - alertSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, - alertMessage: localize('accessibility.signals.terminalCommandFailed', 'Command Failed'), - signalSettingsKey: 'accessibility.signals.terminalCommandFailed' - }); - - public static readonly terminalBell = AudioCue.register({ - name: localize('audioCues.terminalBell', 'Terminal Bell'), - sound: Sound.terminalBell, - settingsKey: 'audioCues.terminalBell', - alertSettingsKey: AccessibilityAlertSettingId.TerminalBell, - alertMessage: localize('accessibility.signals.terminalBell', 'Terminal Bell'), - signalSettingsKey: 'accessibility.signals.terminalBell' - }); - - public static readonly notebookCellCompleted = AudioCue.register({ - name: localize('audioCues.notebookCellCompleted', 'Notebook Cell Completed'), - sound: Sound.taskCompleted, - settingsKey: 'audioCues.notebookCellCompleted', - alertSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, - alertMessage: localize('accessibility.signals.notebookCellCompleted', 'Notebook Cell Completed'), - signalSettingsKey: 'accessibility.signals.notebookCellCompleted' - }); - - public static readonly notebookCellFailed = AudioCue.register({ - name: localize('audioCues.notebookCellFailed', 'Notebook Cell Failed'), - sound: Sound.taskFailed, - settingsKey: 'audioCues.notebookCellFailed', - alertSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, - alertMessage: localize('accessibility.signals.notebookCellFailed', 'Notebook Cell Failed'), - signalSettingsKey: 'accessibility.signals.notebookCellFailed' - }); - - public static readonly diffLineInserted = AudioCue.register({ - name: localize('audioCues.diffLineInserted', 'Diff Line Inserted'), - sound: Sound.diffLineInserted, - settingsKey: 'audioCues.diffLineInserted', - signalSettingsKey: 'accessibility.signals.diffLineInserted' - }); - - public static readonly diffLineDeleted = AudioCue.register({ - name: localize('audioCues.diffLineDeleted', 'Diff Line Deleted'), - sound: Sound.diffLineDeleted, - settingsKey: 'audioCues.diffLineDeleted', - signalSettingsKey: 'accessibility.signals.diffLineDeleted' - }); - - public static readonly diffLineModified = AudioCue.register({ - name: localize('audioCues.diffLineModified', 'Diff Line Modified'), - sound: Sound.diffLineModified, - settingsKey: 'audioCues.diffLineModified', - signalSettingsKey: 'accessibility.signals.diffLineModified' - }); - - public static readonly chatRequestSent = AudioCue.register({ - name: localize('audioCues.chatRequestSent', 'Chat Request Sent'), - sound: Sound.chatRequestSent, - settingsKey: 'audioCues.chatRequestSent', - alertSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, - alertMessage: localize('accessibility.signals.chatRequestSent', 'Chat Request Sent'), - signalSettingsKey: 'accessibility.signals.chatRequestSent' - }); - - public static readonly chatResponseReceived = AudioCue.register({ - name: localize('audioCues.chatResponseReceived', 'Chat Response Received'), - settingsKey: 'audioCues.chatResponseReceived', - sound: { - randomOneOf: [ - Sound.chatResponseReceived1, - Sound.chatResponseReceived2, - Sound.chatResponseReceived3, - Sound.chatResponseReceived4 - ] - }, - signalSettingsKey: 'accessibility.signals.chatResponseReceived' - }); - - public static readonly chatResponsePending = AudioCue.register({ - name: localize('audioCues.chatResponsePending', 'Chat Response Pending'), - sound: Sound.chatResponsePending, - settingsKey: 'audioCues.chatResponsePending', - alertSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, - alertMessage: localize('accessibility.signals.chatResponsePending', 'Chat Response Pending'), - signalSettingsKey: 'accessibility.signals.chatResponsePending' - }); - - public static readonly clear = AudioCue.register({ - name: localize('audioCues.clear', 'Clear'), - sound: Sound.clear, - settingsKey: 'audioCues.clear', - alertSettingsKey: AccessibilityAlertSettingId.Clear, - alertMessage: localize('accessibility.signals.clear', 'Clear'), - signalSettingsKey: 'accessibility.signals.clear' - }); - - public static readonly save = AudioCue.register({ - name: localize('audioCues.save', 'Save'), - sound: Sound.save, - settingsKey: 'audioCues.save', - alertSettingsKey: AccessibilityAlertSettingId.Save, - alertMessage: localize('accessibility.signals.save', 'Save'), - signalSettingsKey: 'accessibility.signals.save' - }); - - public static readonly format = AudioCue.register({ - name: localize('audioCues.format', 'Format'), - sound: Sound.format, - settingsKey: 'audioCues.format', - alertSettingsKey: AccessibilityAlertSettingId.Format, - alertMessage: localize('accessibility.signals.format', 'Format'), - signalSettingsKey: 'accessibility.signals.format' - }); - - private constructor( - public readonly sound: SoundSource, - public readonly name: string, - public readonly settingsKey: string, - public readonly signalSettingsKey: string, - public readonly alertSettingsKey?: string, - public readonly alertMessage?: string, - ) { } -} diff --git a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts index 86fbc23d978..e1ecd72a649 100644 --- a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts @@ -43,7 +43,10 @@ suite('EditorAutoSave', () => { const configurationService = new TestConfigurationService(); configurationService.setUserConfiguration('files', autoSaveConfig); instantiationService.stub(IConfigurationService, configurationService); - instantiationService.stub(IAccessibilitySignalService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); + instantiationService.stub(IAccessibilitySignalService, { + playSignal: async () => { }, + isSoundEnabled(signal: unknown) { return false; }, + } as any); instantiationService.stub(IFilesConfigurationService, disposables.add(new TestFilesConfigurationService( instantiationService.createInstance(MockContextKeyService), configurationService, diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index f9963ff0340..d2fd3da2e03 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -600,7 +600,7 @@ export class GotoPreviousChangeAction extends EditorAction { const index = model.findPreviousClosestChange(lineNumber, false); const change = model.changes[index]; - await playAudioCueForChange(change.change, accessibilitySignalService); + await playAccessibilitySymbolForChange(change.change, accessibilitySignalService); setPositionAndSelection(change.change, outerEditor, accessibilityService, codeEditorService); } } @@ -643,7 +643,7 @@ export class GotoNextChangeAction extends EditorAction { const index = model.findNextClosestChange(lineNumber, false); const change = model.changes[index].change; - await playAudioCueForChange(change, accessibilitySignalService); + await playAccessibilitySymbolForChange(change, accessibilitySignalService); setPositionAndSelection(change, outerEditor, accessibilityService, codeEditorService); } } @@ -658,7 +658,7 @@ function setPositionAndSelection(change: IChange, editor: ICodeEditor, accessibi } } -async function playAudioCueForChange(change: IChange, accessibilitySignalService: IAccessibilitySignalService) { +async function playAccessibilitySymbolForChange(change: IChange, accessibilitySignalService: IAccessibilitySignalService) { const changeType = getChangeType(change); switch (changeType) { case ChangeType.Add: diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts index 51fb47c8c6e..c71c20dd406 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts @@ -67,7 +67,10 @@ suite('Buffer Content Tracker', () => { instantiationService.stub(IContextMenuService, store.add(instantiationService.createInstance(ContextMenuService))); instantiationService.stub(ILifecycleService, store.add(new TestLifecycleService())); instantiationService.stub(IContextKeyService, store.add(new MockContextKeyService())); - instantiationService.stub(IAccessibilitySignalService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); + instantiationService.stub(IAccessibilitySignalService, { + playSignal: async () => { }, + isSoundEnabled(signal: unknown) { return false; }, + } as any); instantiationService.stub(ILayoutService, new TestLayoutService()); configHelper = store.add(instantiationService.createInstance(TerminalConfigHelper)); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index be911f2f4c5..f6febaf3d2f 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -282,7 +282,10 @@ export function workbenchInstantiationService( instantiationService.stub(IDialogService, new TestDialogService()); const accessibilityService = new TestAccessibilityService(); instantiationService.stub(IAccessibilityService, accessibilityService); - instantiationService.stub(IAccessibilitySignalService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); + instantiationService.stub(IAccessibilitySignalService, { + playSignal: async () => { }, + isSoundEnabled(signal: unknown) { return false; }, + } as any); instantiationService.stub(IFileDialogService, instantiationService.createInstance(TestFileDialogService)); instantiationService.stub(ILanguageService, disposables.add(instantiationService.createInstance(LanguageService))); instantiationService.stub(ILanguageFeaturesService, new LanguageFeaturesService()); From d7d2bbbc2eeea3b4ae44d8199c35a4d193a90210 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 13:59:25 -0600 Subject: [PATCH 1160/1897] one more --- .../inlineCompletions/test/browser/suggestWidgetModel.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts index 969ab2fec42..354fa36b5af 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts @@ -161,8 +161,8 @@ async function withAsyncTestCodeEditorAndInlineCompletionsModel( [ILabelService, new class extends mock() { }], [IWorkspaceContextService, new class extends mock() { }], [IAccessibilitySignalService, { - playAudioCue: async () => { }, - isEnabled(cue: unknown) { return false; }, + playSignal: async () => { }, + isSoundEnabled(signal: unknown) { return false; }, } as any] ); From 05842e7e63c8550b0d256d93b710fcd23259df2f Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 12 Feb 2024 17:17:13 -0300 Subject: [PATCH 1161/1897] Support sticky agents (#204910) * Allow sticky chat agents Agents can be sticky and customize the repopulated placeholder. And normalize the API for sub commands See #199908 * Move code around to fix race condition and simplify * Fix build * Agents are sticky by default. Command sticky 'placeholder' is moved to chat agent additions * Rename repopulate to 'isSticky' * Rename --- .../api/common/extHostChatAgents2.ts | 20 +++-- src/vs/workbench/contrib/chat/browser/chat.ts | 2 + .../contrib/chat/browser/chatWidget.ts | 6 +- .../browser/contrib/chatInputEditorContrib.ts | 78 +++++++------------ .../contrib/chat/common/chatAgents.ts | 1 + .../contrib/chat/common/chatService.ts | 9 ++- .../contrib/chat/common/chatServiceImpl.ts | 30 +++---- .../vscode.proposed.chatAgents2.d.ts | 17 +--- .../vscode.proposed.chatAgents2Additions.d.ts | 14 ++++ 9 files changed, 88 insertions(+), 89 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index a807fca2d00..56518bd78e4 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -407,13 +407,19 @@ class ExtHostChatAgent { return []; } return result - .map(c => ({ - name: c.name, - description: c.description, - followupPlaceholder: c.followupPlaceholder, - shouldRepopulate: c.shouldRepopulate, - sampleRequest: c.sampleRequest - })); + .map(c => { + if ('repopulate2' in c) { + checkProposedApiEnabled(this.extension, 'chatAgents2Additions'); + } + + return { + name: c.name, + description: c.description, + followupPlaceholder: c.isSticky2?.placeholder, + shouldRepopulate: c.isSticky2?.isSticky ?? c.isSticky, + sampleRequest: c.sampleRequest + }; + }); } async provideFollowups(result: vscode.ChatAgentResult2, token: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index ff95527a982..daa5ac41eb1 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -9,6 +9,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Selection } from 'vs/editor/common/core/selection'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatWidgetContrib } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, IChatWelcomeMessageViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; @@ -101,6 +102,7 @@ export type IChatWidgetViewContext = IChatViewViewContext | IChatResourceViewCon export interface IChatWidget { readonly onDidChangeViewModel: Event; readonly onDidAcceptInput: Event; + readonly onDidSubmitAgent: Event<{ agent: IChatAgentData; slashCommand?: IChatAgentCommand }>; readonly viewContext: IChatWidgetViewContext; readonly viewModel: IChatViewModel | undefined; readonly inputEditor: ICodeEditor; diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index e243033f713..ab61b6bde4f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -35,7 +35,7 @@ import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWel import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IParsedChatRequest, chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; const $ = dom.$; @@ -73,6 +73,9 @@ export interface IChatWidgetContrib extends IDisposable { export class ChatWidget extends Disposable implements IChatWidget { public static readonly CONTRIBS: { new(...args: [IChatWidget, ...any]): IChatWidgetContrib }[] = []; + private readonly _onDidSubmitAgent = this._register(new Emitter<{ agent: IChatAgentData; slashCommand?: IChatAgentCommand }>()); + public readonly onDidSubmitAgent = this._onDidSubmitAgent.event; + private _onDidFocus = this._register(new Emitter()); readonly onDidFocus = this._onDidFocus.event; @@ -586,6 +589,7 @@ export class ChatWidget extends Disposable implements IChatWidget { if (result) { const inputState = this.collectInputState(); this.inputPart.acceptInput(isUserQuery ? input : undefined, isUserQuery ? inputState : undefined); + this._onDidSubmitAgent.fire({ agent: result.agent, slashCommand: result.slashCommand }); result.responseCompletePromise.then(async () => { const responses = this.viewModel?.getItems().filter(isResponseVM); const lastResponse = responses?.[responses.length - 1]; diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index e5e44863dfd..a6cf114ed33 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -29,7 +29,6 @@ import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workben import { chatSlashCommandBackground, chatSlashCommandForeground } from 'vs/workbench/contrib/chat/common/chatColors'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -39,8 +38,8 @@ const placeholderDecorationType = 'chat-session-detail'; const slashCommandTextDecorationType = 'chat-session-text'; const variableTextDecorationType = 'chat-variable-text'; -function agentAndCommandToKey(agent: string, subcommand: string): string { - return `${agent}__${subcommand}`; +function agentAndCommandToKey(agent: string, subcommand: string | undefined): string { + return subcommand ? `${agent}__${subcommand}` : agent; } class InputEditorDecorations extends Disposable { @@ -55,7 +54,6 @@ class InputEditorDecorations extends Disposable { private readonly widget: IChatWidget, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, @IThemeService private readonly themeService: IThemeService, - @IChatService private readonly chatService: IChatService, @IChatAgentService private readonly chatAgentService: IChatAgentService, ) { super(); @@ -72,10 +70,8 @@ class InputEditorDecorations extends Disposable { this.previouslyUsedAgents.clear(); this.updateInputEditorDecorations(); })); - this._register(this.chatService.onDidSubmitAgent((e) => { - if (e.sessionId === this.widget.viewModel?.sessionId) { - this.previouslyUsedAgents.add(agentAndCommandToKey(e.agent.id, e.slashCommand.name)); - } + this._register(this.widget.onDidSubmitAgent((e) => { + this.previouslyUsedAgents.add(agentAndCommandToKey(e.agent.id, e.slashCommand?.name)); })); this._register(this.chatAgentService.onDidChangeAgents(() => this.updateInputEditorDecorations())); @@ -168,20 +164,24 @@ class InputEditorDecorations extends Disposable { return nextPart && nextPart instanceof ChatRequestTextPart && nextPart.text === ' '; }; + const getRangeForPlaceholder = (part: IParsedChatRequestPart) => ({ + startLineNumber: part.editorRange.startLineNumber, + endLineNumber: part.editorRange.endLineNumber, + startColumn: part.editorRange.endColumn + 1, + endColumn: 1000 + }); + const onlyAgentAndWhitespace = agentPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestAgentPart); if (onlyAgentAndWhitespace) { // Agent reference with no other text - show the placeholder + const isFollowupSlashCommand = this.previouslyUsedAgents.has(agentAndCommandToKey(agentPart.agent.id, undefined)); + const shouldRenderFollowupPlaceholder = isFollowupSlashCommand && agentPart.agent.metadata.followupPlaceholder; if (agentPart.agent.metadata.description && exactlyOneSpaceAfterPart(agentPart)) { placeholderDecoration = [{ - range: { - startLineNumber: agentPart.editorRange.startLineNumber, - endLineNumber: agentPart.editorRange.endLineNumber, - startColumn: agentPart.editorRange.endColumn + 1, - endColumn: 1000 - }, + range: getRangeForPlaceholder(agentPart), renderOptions: { after: { - contentText: agentPart.agent.metadata.description, + contentText: shouldRenderFollowupPlaceholder ? agentPart.agent.metadata.followupPlaceholder : agentPart.agent.metadata.description, color: this.getPlaceholderColor(), } } @@ -196,12 +196,7 @@ class InputEditorDecorations extends Disposable { const shouldRenderFollowupPlaceholder = isFollowupSlashCommand && agentSubcommandPart.command.followupPlaceholder; if (agentSubcommandPart?.command.description && exactlyOneSpaceAfterPart(agentSubcommandPart)) { placeholderDecoration = [{ - range: { - startLineNumber: agentSubcommandPart.editorRange.startLineNumber, - endLineNumber: agentSubcommandPart.editorRange.endLineNumber, - startColumn: agentSubcommandPart.editorRange.endColumn + 1, - endColumn: 1000 - }, + range: getRangeForPlaceholder(agentSubcommandPart), renderOptions: { after: { contentText: shouldRenderFollowupPlaceholder ? agentSubcommandPart.command.followupPlaceholder : agentSubcommandPart.command.description, @@ -212,27 +207,6 @@ class InputEditorDecorations extends Disposable { } } - const onlySlashCommandAndWhitespace = slashCommandPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestSlashCommandPart); - if (onlySlashCommandAndWhitespace) { - // Command reference with no other text - show the placeholder - if (slashCommandPart.slashCommand.detail && exactlyOneSpaceAfterPart(slashCommandPart)) { - placeholderDecoration = [{ - range: { - startLineNumber: slashCommandPart.editorRange.startLineNumber, - endLineNumber: slashCommandPart.editorRange.endLineNumber, - startColumn: slashCommandPart.editorRange.endColumn + 1, - endColumn: 1000 - }, - renderOptions: { - after: { - contentText: slashCommandPart.slashCommand.detail, - color: this.getPlaceholderColor(), - } - } - }]; - } - } - this.widget.inputEditor.setDecorationsByType(decorationDescription, placeholderDecorationType, placeholderDecoration ?? []); const textDecorations: IDecorationOptions[] | undefined = []; @@ -264,21 +238,23 @@ class InputEditorSlashCommandMode extends Disposable { constructor( private readonly widget: IChatWidget, - @IChatService private readonly chatService: IChatService ) { super(); - this._register(this.chatService.onDidSubmitAgent(e => { - if (this.widget.viewModel?.sessionId !== e.sessionId) { - return; - } - + this._register(this.widget.onDidSubmitAgent(e => { this.repopulateAgentCommand(e.agent, e.slashCommand); })); } - private async repopulateAgentCommand(agent: IChatAgentData, slashCommand: IChatAgentCommand) { - if (slashCommand.shouldRepopulate) { - const value = `${chatAgentLeader}${agent.id} ${chatSubcommandLeader}${slashCommand.name} `; + private async repopulateAgentCommand(agent: IChatAgentData, slashCommand: IChatAgentCommand | undefined) { + let value: string | undefined; + if (slashCommand && slashCommand.shouldRepopulate) { + value = `${chatAgentLeader}${agent.id} ${chatSubcommandLeader}${slashCommand.name} `; + } else { + // Agents always repopulate, and slash commands fall back to the agent if they don't repopulate + value = `${chatAgentLeader}${agent.id} `; + } + + if (value) { this.widget.inputEditor.setValue(value); this.widget.inputEditor.setPosition({ lineNumber: 1, column: value.length + 1 }); } diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 8647b65ad43..289d3cb15b4 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -77,6 +77,7 @@ export interface IChatAgentMetadata { themeIcon?: ThemeIcon; sampleRequest?: string; supportIssueReporting?: boolean; + followupPlaceholder?: string; } diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 4764e9f23bd..769476c60a7 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -262,13 +262,18 @@ export interface IChatTransferredSessionData { inputValue: string; } +export interface IChatSendRequestData { + responseCompletePromise: Promise; + agent: IChatAgentData; + slashCommand?: IChatAgentCommand; +} + export const IChatService = createDecorator('IChatService'); export interface IChatService { _serviceBrand: undefined; transferredSessionData: IChatTransferredSessionData | undefined; - onDidSubmitAgent: Event<{ agent: IChatAgentData; slashCommand: IChatAgentCommand; sessionId: string }>; onDidRegisterProvider: Event<{ providerId: string }>; onDidUnregisterProvider: Event<{ providerId: string }>; registerProvider(provider: IChatProvider): IDisposable; @@ -283,7 +288,7 @@ export interface IChatService { /** * Returns whether the request was accepted. */ - sendRequest(sessionId: string, message: string): Promise<{ responseCompletePromise: Promise } | undefined>; + sendRequest(sessionId: string, message: string): Promise; removeRequest(sessionid: string, requestId: string): Promise; cancelCurrentRequestForSession(sessionId: string): void; clearSession(sessionId: string): void; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 1191afb209a..ebe25ec1b3a 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -20,13 +20,13 @@ 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 { IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, IChatRequestVariableData2, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestVariablePart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -146,9 +146,6 @@ export class ChatService extends Disposable implements IChatService { private readonly _onDidPerformUserAction = this._register(new Emitter()); public readonly onDidPerformUserAction: Event = this._onDidPerformUserAction.event; - private readonly _onDidSubmitAgent = this._register(new Emitter<{ agent: IChatAgentData; slashCommand: IChatAgentCommand; sessionId: string }>()); - public readonly onDidSubmitAgent = this._onDidSubmitAgent.event; - private readonly _onDidDisposeSession = this._register(new Emitter<{ sessionId: string; providerId: string; reason: 'initializationFailed' | 'cleared' }>()); public readonly onDidDisposeSession = this._onDidDisposeSession.event; @@ -438,7 +435,7 @@ export class ChatService extends Disposable implements IChatService { return this._startSession(data.providerId, data, CancellationToken.None); } - async sendRequest(sessionId: string, request: string): Promise<{ responseCompletePromise: Promise } | undefined> { + async sendRequest(sessionId: string, request: string): Promise { this.trace('sendRequest', `sessionId: ${sessionId}, message: ${request.substring(0, 20)}${request.length > 20 ? '[...]' : ''}}`); if (!request.trim()) { this.trace('sendRequest', 'Rejected empty message'); @@ -461,8 +458,16 @@ export class ChatService extends Disposable implements IChatService { return; } + const parsedRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, request); + const agent = parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart)?.agent ?? this.chatAgentService.getDefaultAgent()!; + const agentSlashCommandPart = parsedRequest.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart); + // This method is only returning whether the request was accepted - don't block on the actual request - return { responseCompletePromise: this._sendRequestAsync(model, sessionId, provider, request) }; + return { + responseCompletePromise: this._sendRequestAsync(model, sessionId, provider, parsedRequest), + agent, + slashCommand: agentSlashCommandPart?.command, + }; } private refreshFollowupsCancellationToken(sessionId: string): CancellationToken { @@ -473,10 +478,8 @@ export class ChatService extends Disposable implements IChatService { return newTokenSource.token; } - private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, message: string): Promise { + private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, parsedRequest: IParsedChatRequest): Promise { const followupsCancelToken = this.refreshFollowupsCancellationToken(sessionId); - const parsedRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message); - let request: ChatRequestModel; const agentPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart); const agentSlashCommandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart); @@ -523,10 +526,6 @@ export class ChatService extends Disposable implements IChatService { }); try { - if (agentPart && agentSlashCommandPart?.command) { - this._onDidSubmitAgent.fire({ agent: agentPart.agent, slashCommand: agentSlashCommandPart.command, sessionId: model.sessionId }); - } - let rawResult: IChatAgentResult | null | undefined; let agentOrCommandFollowups: Promise | undefined = undefined; @@ -570,7 +569,7 @@ export class ChatService extends Disposable implements IChatService { rawResult = agentResult; agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, followupsCancelToken); } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { - request = model.addRequest(parsedRequest, { message, variables: {} }); + request = model.addRequest(parsedRequest, { message: parsedRequest.text, variables: {} }); // contributed slash commands // TODO: spell this out in the UI const history: IChatMessage[] = []; @@ -581,6 +580,7 @@ export class ChatService extends Disposable implements IChatService { history.push({ role: ChatMessageRole.User, content: request.message.text }); history.push({ role: ChatMessageRole.Assistant, content: request.response.response.asString() }); } + const message = parsedRequest.text; const commandResult = await this.chatSlashCommandService.executeCommand(commandPart.slashCommand.command, message.substring(commandPart.slashCommand.command.length + 1).trimStart(), new Progress(p => { progressCallback(p); }), history, token); diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 4ec0f8a92fc..d2a76eb8736 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -180,19 +180,10 @@ declare module 'vscode' { readonly sampleRequest?: string; /** - * Whether executing the command puts the - * chat into a persistent mode, where the - * command is prepended to the chat input. + * Whether executing the command puts the chat into a persistent mode, where the command is automatically added to the chat input for the next message. + * If this is not set, the chat input will fall back to the agent after submitting this command. */ - readonly shouldRepopulate?: boolean; - - /** - * Placeholder text to render in the chat input - * when the command has been repopulated. - * Has no effect if `shouldRepopulate` is `false`. - */ - // TODO@API merge this with shouldRepopulate? so that invalid state cannot be represented? - readonly followupPlaceholder?: string; + readonly isSticky?: boolean; } export interface ChatAgentCommandProvider { @@ -221,7 +212,7 @@ declare module 'vscode' { prompt: string; /** - * By default, the followup goes to the same agent/subCommand. But these properties can be set to override that. + * By default, the followup goes to the same agent/command. But these properties can be set to override that. */ agentId?: string; diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index f42f874d0dd..b934eeea995 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -230,4 +230,18 @@ declare module 'vscode' { */ kind?: string; } + + export interface ChatAgentCommand { + readonly isSticky2?: { + /** + * Indicates that the command should be automatically repopulated. + */ + isSticky: true; + + /** + * This can be set to a string to use a different placeholder message in the input box when the command has been repopulated. + */ + placeholder?: string; + }; + } } From 71770417094a265b1ab1dd5aaefada0662fde544 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 12 Feb 2024 17:34:53 -0300 Subject: [PATCH 1162/1897] Fix title in chat history picker (#205029) --- src/vs/workbench/contrib/chat/common/chatModel.ts | 14 ++++++++++---- .../contrib/chat/common/chatServiceImpl.ts | 5 ++--- .../workbench/contrib/chat/common/chatViewModel.ts | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 81bcad7a26c..aac4dc60c4f 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -38,7 +38,7 @@ export interface IChatRequestModel { readonly username: string; readonly avatarIconUri?: URI; readonly session: IChatModel; - readonly message: IParsedChatRequest | IChatFollowup; + readonly message: IParsedChatRequest; readonly variableData: IChatRequestVariableData; readonly response?: IChatResponseModel; } @@ -458,6 +458,14 @@ export enum ChatModelInitState { } export class ChatModel extends Disposable implements IChatModel { + static getDefaultTitle(requests: (ISerializableChatRequestData | IChatRequestModel)[]): string { + const firstRequestMessage = firstOrDefault(requests)?.message ?? ''; + const message = typeof firstRequestMessage === 'string' ? + firstRequestMessage : + firstRequestMessage.text; + return message.split('\n')[0].substring(0, 50); + } + private readonly _onDidDispose = this._register(new Emitter()); readonly onDidDispose = this._onDidDispose.event; @@ -527,9 +535,7 @@ export class ChatModel extends Disposable implements IChatModel { } get title(): string { - const firstRequestMessage = firstOrDefault(this._requests)?.message; - const message = firstRequestMessage?.text ?? ''; - return message.split('\n')[0].substring(0, 50); + return ChatModel.getDefaultTitle(this._requests); } constructor( diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index ebe25ec1b3a..cdbcda18537 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -322,11 +322,10 @@ export class ChatService extends Disposable implements IChatService { .filter(session => !this._sessionModels.has(session.sessionId)) .filter(session => !session.isImported) .map(item => { - const firstRequestMessage = item.requests[0]?.message; + const title = ChatModel.getDefaultTitle(item.requests); return { sessionId: item.sessionId, - title: (typeof firstRequestMessage === 'string' ? firstRequestMessage : - firstRequestMessage?.text) ?? '', + title }; }); } diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index bd2c325d728..1a959494464 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -261,7 +261,7 @@ export class ChatRequestViewModel implements IChatRequestViewModel { } get messageText() { - return 'kind' in this.message ? this.message.message : this.message.text; + return this.message.text; } currentRenderedHeight: number | undefined; From a4611af71759f8a29ea71229c25e15927e650fc1 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 12 Feb 2024 21:43:25 +0100 Subject: [PATCH 1163/1897] SCM - add menu to configure incoming/outgoing (#205037) SCM - add menu to easily configure incoming/outgoing settings --- src/vs/platform/actions/common/actions.ts | 3 + .../contrib/scm/browser/media/scm.css | 9 + .../contrib/scm/browser/scmViewPane.ts | 154 +++++++++++++++++- 3 files changed, 162 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 376627398ac..0b3015ddf75 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -108,10 +108,13 @@ export class MenuId { static readonly OpenEditorsContextShare = new MenuId('OpenEditorsContextShare'); static readonly ProblemsPanelContext = new MenuId('ProblemsPanelContext'); static readonly SCMInputBox = new MenuId('SCMInputBox'); + static readonly SCMChangesSeparator = new MenuId('SCMChangesSeparator'); static readonly SCMIncomingChanges = new MenuId('SCMIncomingChanges'); static readonly SCMIncomingChangesContext = new MenuId('SCMIncomingChangesContext'); + static readonly SCMIncomingChangesSetting = new MenuId('SCMIncomingChangesSetting'); static readonly SCMOutgoingChanges = new MenuId('SCMOutgoingChanges'); static readonly SCMOutgoingChangesContext = new MenuId('SCMOutgoingChangesContext'); + static readonly SCMOutgoingChangesSetting = new MenuId('SCMOutgoingChangesSetting'); static readonly SCMIncomingChangesAllChangesContext = new MenuId('SCMIncomingChangesAllChangesContext'); static readonly SCMIncomingChangesHistoryItemContext = new MenuId('SCMIncomingChangesHistoryItemContext'); static readonly SCMOutgoingChangesAllChangesContext = new MenuId('SCMOutgoingChangesAllChangesContext'); diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 13eb48d7df4..c1c3290d585 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -210,6 +210,10 @@ border-top: 1px solid var(--vscode-sideBar-border); } +.scm-view .monaco-list-row .separator-container .actions { + padding-left: 6px; +} + .scm-view .monaco-list-row .history > .name, .scm-view .monaco-list-row .history-item-group > .name, .scm-view .monaco-list-row .resource-group > .name { @@ -255,6 +259,7 @@ .scm-view .monaco-list .monaco-list-row .resource-group > .actions, .scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions, +.scm-view .monaco-list .monaco-list-row .separator-container > .actions, .scm-view .monaco-list .monaco-list-row .history-item-group > .actions, .scm-view .monaco-list .monaco-list-row .history-item > .actions { display: none; @@ -268,6 +273,9 @@ .scm-view .monaco-list .monaco-list-row.selected .resource > .name > .monaco-icon-label > .actions, .scm-view .monaco-list .monaco-list-row.focused .resource > .name > .monaco-icon-label > .actions, .scm-view .monaco-list:not(.selection-multiple) .monaco-list-row .resource:hover > .actions, +.scm-view .monaco-list .monaco-list-row:hover .separator-container > .actions, +.scm-view .monaco-list .monaco-list-row.selected .separator-container > .actions, +.scm-view .monaco-list .monaco-list-row.focused .separator-container > .actions, .scm-view .monaco-list .monaco-list-row:hover .history-item-group > .actions, .scm-view .monaco-list .monaco-list-row.selected .history-item-group > .actions, .scm-view .monaco-list .monaco-list-row.focused .history-item-group > .actions, @@ -292,6 +300,7 @@ .scm-view.show-actions > .monaco-list .monaco-list-row .scm-input > .scm-editor > .actions, .scm-view.show-actions > .monaco-list .monaco-list-row .resource-group > .actions, .scm-view.show-actions > .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions, +.scm-view.show-actions > .monaco-list .monaco-list-row .separator-container > .actions, .scm-view.show-actions > .monaco-list .monaco-list-row .history-item-group > .actions, .scm-view.show-actions > .monaco-list .monaco-list-row .history-item > .actions { display: block; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 57f3428b3dc..ac5b68e12b2 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -100,7 +100,7 @@ import { EditOperation } from 'vs/editor/common/core/editOperation'; import { stripIcons } from 'vs/base/common/iconLabels'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { foreground, listActiveSelectionForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; -import { IMenuWorkbenchToolBarOptions, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { IMenuWorkbenchToolBarOptions, MenuWorkbenchToolBar, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem'; import { clamp } from 'vs/base/common/numbers'; @@ -1047,7 +1047,7 @@ class HistoryItemChangeRenderer implements ICompressibleTreeRenderer { @@ -1055,6 +1055,14 @@ class SeparatorRenderer implements ICompressibleTreeRenderer, index: number, templateData: SeparatorTemplate, height: number | undefined): void { templateData.label.setLabel(element.element.label, undefined, { title: element.element.ariaLabel }); @@ -1077,7 +1090,7 @@ class SeparatorRenderer implements ICompressibleTreeRenderer) { + super({ + ...desc, + f1: false, + toggled: ContextKeyExpr.equals(`config.${settingKey}`, settingValue), + }); + } + + override run(accessor: ServicesAccessor): void { + const configurationService = accessor.get(IConfigurationService); + configurationService.updateValue(this.settingKey, this.settingValue); + } +} + +MenuRegistry.appendMenuItem(MenuId.SCMChangesSeparator, { + title: localize('incomingChanges', "Show Incoming Changes"), + submenu: MenuId.SCMIncomingChangesSetting, + group: '1_incoming&outgoing', + order: 1 +}); + +registerAction2(class extends SCMChangesSettingAction { + constructor() { + super('scm.showIncomingChanges', 'always', + { + id: 'workbench.scm.action.showIncomingChanges.always', + title: localize('always', "Always"), + menu: { + id: MenuId.SCMIncomingChangesSetting, + + } + }); + } +}); + +registerAction2(class extends SCMChangesSettingAction { + constructor() { + super('scm.showIncomingChanges', 'auto', + { + id: 'workbench.scm.action.showIncomingChanges.auto', + title: localize('auto', "Auto"), + menu: { + id: MenuId.SCMIncomingChangesSetting, + } + }); + } +}); + +registerAction2(class extends SCMChangesSettingAction { + constructor() { + super('scm.showIncomingChanges', 'never', + { + id: 'workbench.scm.action.showIncomingChanges.never', + title: localize('never', "Never"), + menu: { + id: MenuId.SCMIncomingChangesSetting, + } + }); + } +}); + +MenuRegistry.appendMenuItem(MenuId.SCMChangesSeparator, { + title: localize('outgoingChanges', "Show Outgoing Changes"), + submenu: MenuId.SCMOutgoingChangesSetting, + group: '1_incoming&outgoing', + order: 2 +}); + +registerAction2(class extends SCMChangesSettingAction { + constructor() { + super('scm.showOutgoingChanges', 'always', + { + id: 'workbench.scm.action.showOutgoingChanges.always', + title: localize('always', "Always"), + menu: { + id: MenuId.SCMOutgoingChangesSetting, + + } + }); + } +}); + +registerAction2(class extends SCMChangesSettingAction { + constructor() { + super('scm.showOutgoingChanges', 'auto', + { + id: 'workbench.scm.action.showOutgoingChanges.auto', + title: localize('auto', "Auto"), + menu: { + id: MenuId.SCMOutgoingChangesSetting, + } + }); + } +}); + +registerAction2(class extends SCMChangesSettingAction { + constructor() { + super('scm.showOutgoingChanges', 'never', + { + id: 'workbench.scm.action.showOutgoingChanges.never', + title: localize('never', "Never"), + menu: { + id: MenuId.SCMOutgoingChangesSetting, + } + }); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.scm.action.scm.showChangesSummary', + title: localize('showChangesSummary', "Show Changes Summary"), + f1: false, + toggled: ContextKeyExpr.equals('config.scm.showChangesSummary', true), + menu: { + id: MenuId.SCMChangesSeparator, + order: 3 + } + }); + } + + override run(accessor: ServicesAccessor) { + const configurationService = accessor.get(IConfigurationService); + const configValue = configurationService.getValue('scm.showChangesSummary') === true; + configurationService.updateValue('scm.showChangesSummary', !configValue); + } +}); + class RepositoryVisibilityAction extends Action2 { private repository: ISCMRepository; From a0d97ae9449d8665a531d8dd1dd58c4fe24332fe Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 15:49:32 -0600 Subject: [PATCH 1164/1897] contrib => controller --- .../terminal/common/terminalContextKey.ts | 6 +- .../browser/terminal.chat.contribution.ts | 101 ++------------- .../chat/browser/terminalChatController.ts | 119 ++++++++++++++++++ .../chat/browser/terminalChatWidget.ts | 5 +- 4 files changed, 136 insertions(+), 95 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 1de53d61930..78fd7df6712 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -41,7 +41,7 @@ export const enum TerminalContextKeyStrings { TerminalShellIntegrationEnabled = 'terminalShellIntegrationEnabled', ChatFocus = 'terminalChatFocus', ChatVisible = 'terminalChatVisible', - ChatSessionInProgress = 'terminalChatSessionInProgress', + ChatActiveRequest = 'terminalChatActiveRequest', ChatInputHasText = 'terminalChatInputHasText', } @@ -170,8 +170,8 @@ export namespace TerminalContextKeys { /** Whether the chat widget is visible */ export const chatVisible = new RawContextKey(TerminalContextKeyStrings.ChatVisible, false, localize('chatVisibleContextKey', "Whether the chat view is visible.")); - /** Whether a chat session is in progress */ - export const chatSessionInProgress = new RawContextKey(TerminalContextKeyStrings.ChatSessionInProgress, false, localize('chatSessionInProgressContextKey', "Whether a chat session is in progress.")); + /** Whether there is an active chat request */ + export const chatRequestActive = new RawContextKey(TerminalContextKeyStrings.ChatActiveRequest, false, localize('chatRequestActiveContextKey', "Whether there is an active chat request.")); /** Whether the chat input has text */ export const chatInputHasText = new RawContextKey(TerminalContextKeyStrings.ChatInputHasText, false, localize('chatInputHasTextContextKey', "Whether the chat input has text.")); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index c2bbe8aa74a..1d909e6fc30 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -3,100 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; -import { IDimension } from 'vs/base/browser/dom'; import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { Lazy } from 'vs/base/common/lazy'; -import { Disposable } from 'vs/base/common/lifecycle'; import { localize2 } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; -import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; -import { ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; +import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; -export class TerminalChatContribution extends Disposable implements ITerminalContribution { - static readonly ID = 'terminal.Chat'; - - static get(instance: ITerminalInstance): TerminalChatContribution | null { - return instance.getContribution(TerminalChatContribution.ID); - } - /** - * Currently focused chat widget. This is used to track action context since - * 'active terminals' are only tracked for non-detached terminal instanecs. - */ - static activeChatWidget?: TerminalChatContribution; - private _chatWidget: Lazy | undefined; - private _lastLayoutDimensions: IDimension | undefined; - - get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } - - constructor( - private readonly _instance: ITerminalInstance, - processManager: ITerminalProcessManager, - widgetManager: TerminalWidgetManager, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IConfigurationService private _configurationService: IConfigurationService, - @ITerminalService private readonly _terminalService: ITerminalService - ) { - super(); - if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { - return; - } - } - - layout(_xterm: IXtermTerminal & { raw: RawXtermTerminal }, dimension: IDimension): void { - if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { - return; - } - this._lastLayoutDimensions = dimension; - this._chatWidget?.rawValue?.layout(dimension.width); - } - - xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { - if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { - return; - } - this._chatWidget = new Lazy(() => { - const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); - chatWidget.focusTracker.onDidFocus(() => { - TerminalChatContribution.activeChatWidget = this; - if (!isDetachedTerminalInstance(this._instance)) { - this._terminalService.setActiveInstance(this._instance); - } - }); - chatWidget.focusTracker.onDidBlur(() => { - TerminalChatContribution.activeChatWidget = undefined; - this._instance.resetScrollbarVisibility(); - }); - if (!this._instance.domElement) { - throw new Error('FindWidget expected terminal DOM to be initialized'); - } - - // this._instance.domElement?.appendChild(chatWidget.getDomNode()); - if (this._lastLayoutDimensions) { - chatWidget.layout(this._lastLayoutDimensions.width); - } - - return chatWidget; - }); - } - - override dispose() { - super.dispose(); - this._chatWidget?.rawValue?.dispose(); - } -} -registerTerminalContribution(TerminalChatContribution.ID, TerminalChatContribution, false); +registerTerminalContribution(TerminalChatController.ID, TerminalChatController, false); registerActiveXtermAction({ id: TerminalCommandId.FocusChat, @@ -115,7 +36,7 @@ registerActiveXtermAction({ if (isDetachedTerminalInstance(activeInstance)) { return; } - const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); contr?.chatWidget?.reveal(); } }); @@ -138,7 +59,7 @@ registerActiveXtermAction({ if (isDetachedTerminalInstance(activeInstance)) { return; } - const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); contr?.chatWidget?.hide(); } }); @@ -153,7 +74,7 @@ registerActiveXtermAction({ ), icon: Codicon.send, keybinding: { - when: TerminalContextKeys.chatSessionInProgress.negate(), + when: TerminalContextKeys.chatRequestActive.negate(), // TODO: // when: CTX_INLINE_CHAT_FOCUSED, weight: KeybindingWeight.EditorCore + 7, @@ -171,8 +92,8 @@ registerActiveXtermAction({ if (isDetachedTerminalInstance(activeInstance)) { return; } - const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); - contr?.chatWidget?.acceptInput(); + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.acceptInput(); } }); @@ -181,7 +102,7 @@ registerActiveXtermAction({ title: localize2('workbench.action.terminal.cancelChat', 'Cancel Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatSessionInProgress, + TerminalContextKeys.chatRequestActive, ), icon: Codicon.debugStop, menu: { @@ -192,7 +113,7 @@ registerActiveXtermAction({ if (isDetachedTerminalInstance(activeInstance)) { return; } - const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); contr?.chatWidget?.cancel(); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts new file mode 100644 index 00000000000..ae9ded44aec --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -0,0 +1,119 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; +import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IDimension } from 'vs/base/browser/dom'; +import { Lazy } from 'vs/base/common/lazy'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; +import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; +import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; +import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; + +export class TerminalChatController extends Disposable implements ITerminalContribution { + static readonly ID = 'terminal.Chat'; + + static get(instance: ITerminalInstance): TerminalChatController | null { + return instance.getContribution(TerminalChatController.ID); + } + /** + * Currently focused chat widget. This is used to track action context since + * 'active terminals' are only tracked for non-detached terminal instanecs. + */ + static activeChatWidget?: TerminalChatController; + private _chatWidget: Lazy | undefined; + private _lastLayoutDimensions: IDimension | undefined; + + get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } + + // private _sessionCtor: CancelablePromise | undefined; + private _activeSession?: Session; + // private readonly _ctxHasActiveRequest: IContextKey; + // private _isVisible: boolean = false; + // private _strategy: EditStrategy | undefined; + + // private _inlineChatListener: IDisposable | undefined; + // private _toolbar: MenuWorkbenchToolBar | undefined; + // private readonly _ctxLastResponseType: IContextKey; + // private _widgetDisposableStore: DisposableStore = this._register(new DisposableStore()); + + constructor( + private readonly _instance: ITerminalInstance, + processManager: ITerminalProcessManager, + widgetManager: TerminalWidgetManager, + @IConfigurationService private _configurationService: IConfigurationService, + @ITerminalService private readonly _terminalService: ITerminalService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, + // @IContextKeyService private readonly _contextKeyService: IContextKeyService, + // @IInstantiationService private readonly _instantiationService: IInstantiationService, + // @ICommandService private readonly _commandService: ICommandService, + // @IInlineChatSavingService private readonly _inlineChatSavingService: IInlineChatSavingService + ) { + super(); + if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { + return; + } + // this._ctxHasActiveRequest = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); + // this._ctxLastResponseType = CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.bindTo(this._contextKeyService); + } + + layout(_xterm: IXtermTerminal & { raw: RawXtermTerminal }, dimension: IDimension): void { + if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { + return; + } + this._lastLayoutDimensions = dimension; + this._chatWidget?.rawValue?.layout(dimension.width); + } + + xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { + if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { + return; + } + this._chatWidget = new Lazy(() => { + const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); + chatWidget.focusTracker.onDidFocus(() => { + TerminalChatController.activeChatWidget = this; + if (!isDetachedTerminalInstance(this._instance)) { + this._terminalService.setActiveInstance(this._instance); + } + }); + chatWidget.focusTracker.onDidBlur(() => { + TerminalChatController.activeChatWidget = undefined; + this._instance.resetScrollbarVisibility(); + }); + if (!this._instance.domElement) { + throw new Error('FindWidget expected terminal DOM to be initialized'); + } + + // this._instance.domElement?.appendChild(chatWidget.getDomNode()); + if (this._lastLayoutDimensions) { + chatWidget.layout(this._lastLayoutDimensions.width); + } + + return chatWidget; + }); + } + + async acceptInput(): Promise { + // TODO: create session, deal with response + this._chatWidget?.rawValue?.acceptInput(); + } + + reveal(): void { + this._chatWidget?.rawValue?.reveal(); + } + + override dispose() { + super.dispose(); + this._chatWidget?.rawValue?.dispose(); + } +} + diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 1467a9a86ef..14a6ca567b4 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -115,12 +115,13 @@ export class TerminalChatWidget extends Disposable { } acceptInput(): void { // this._widget?.acceptInput(); - this._chatModel ??= this._chatService.startSession('terminal', CancellationToken.None); + // this._chatModel ??= this._chatService.startSession('terminal', CancellationToken.None); // if (!this._model) { // throw new Error('Could not start chat session'); // } - this._chatService?.sendRequest(this._chatModel?.sessionId!, this._inlineChatWidget.value); + // this._chatService?.sendRequest(this._chatModel?.sessionId!, this._inlineChatWidget.value); + this._inlineChatWidget.value = ''; // this.widget.setModel(this.model, { inputValue: this._currentQuery }); } From adfe67dca6f7901e714e69242c4f8fcae3a1bcd1 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 12 Feb 2024 19:19:44 -0300 Subject: [PATCH 1165/1897] A few chat Followups fixes (#205038) * A few chat Followups fixes * Fix build --- .../workbench/api/common/extHostTypeConverters.ts | 4 ++-- .../contrib/chat/browser/chatFollowups.ts | 6 ++++-- .../contrib/chat/browser/chatInputPart.ts | 2 +- .../contrib/chat/browser/chatListRenderer.ts | 14 ++++++++------ .../chat/browser/contrib/chatInputEditorContrib.ts | 3 ++- .../contrib/inlineChat/browser/inlineChatWidget.ts | 3 ++- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 2 +- 7 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 2a3a71e9029..62f12e9aae7 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2198,7 +2198,7 @@ export namespace ChatFollowup { return { kind: 'reply', agentId: followup.agentId ?? request?.agentId ?? '', - subCommand: followup.subCommand ?? request?.command, + subCommand: followup.command ?? request?.command, message: followup.prompt, title: followup.title, tooltip: followup.tooltip, @@ -2210,7 +2210,7 @@ export namespace ChatFollowup { prompt: followup.message, title: followup.title, agentId: followup.agentId, - subCommand: followup.subCommand, + command: followup.subCommand, tooltip: followup.tooltip, }; } diff --git a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts index 827de16655c..87ab8978289 100644 --- a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts +++ b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts @@ -9,6 +9,7 @@ import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IInlineChatFollowup } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -21,7 +22,8 @@ export class ChatFollowups extend followups: T[], private readonly options: IButtonStyles | undefined, private readonly clickHandler: (followup: T) => void, - private readonly contextService: IContextKeyService, + @IContextKeyService private readonly contextService: IContextKeyService, + @IChatAgentService private readonly chatAgentService: IChatAgentService ) { super(); @@ -44,7 +46,7 @@ export class ChatFollowups extend } button.element.ariaLabel = localize('followUpAriaLabel', "Follow up question: {0}", followup.title); let prefix = ''; - if ('agentId' in followup && followup.agentId) { + if ('agentId' in followup && followup.agentId && followup.agentId !== this.chatAgentService.getDefaultAgent()?.id) { prefix += `${chatAgentLeader}${followup.agentId} `; if ('subCommand' in followup && followup.subCommand) { prefix += `${chatSubcommandLeader}${followup.subCommand} `; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index b69f086eb58..04b6ffc3810 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -347,7 +347,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge dom.clearNode(this.followupsContainer); if (items && items.length > 0) { - this.followupsDisposables.add(new ChatFollowups(this.followupsContainer, items, undefined, followup => this._onDidAcceptFollowup.fire({ followup, response }), this.contextKeyService)); + this.followupsDisposables.add(this.instantiationService.createInstance, ChatFollowups>(ChatFollowups, this.followupsContainer, items, undefined, followup => this._onDidAcceptFollowup.fire({ followup, response }))); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 972fe2d9aeb..194e012133d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -500,12 +500,14 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this._onDidClickFollowup.fire(followup), - templateData.contextKeyService)); + const scopedInstaService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, templateData.contextKeyService])); + templateData.elementDisposables.add( + scopedInstaService.createInstance, ChatFollowups>( + ChatFollowups, + templateData.value, + item, + undefined, + followup => this._onDidClickFollowup.fire(followup))); } else { const result = this.renderMarkdown(item as IMarkdownString, element, templateData); templateData.value.appendChild(result.element); diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index a6cf114ed33..0b1a1793ea2 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -238,6 +238,7 @@ class InputEditorSlashCommandMode extends Disposable { constructor( private readonly widget: IChatWidget, + @IChatAgentService private readonly _chatAgentService: IChatAgentService ) { super(); this._register(this.widget.onDidSubmitAgent(e => { @@ -249,7 +250,7 @@ class InputEditorSlashCommandMode extends Disposable { let value: string | undefined; if (slashCommand && slashCommand.shouldRepopulate) { value = `${chatAgentLeader}${agent.id} ${chatSubcommandLeader}${slashCommand.name} `; - } else { + } else if (agent.id !== this._chatAgentService.getDefaultAgent()?.id) { // Agents always repopulate, and slash commands fall back to the agent if they don't repopulate value = `${chatAgentLeader}${agent.id} `; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index aee5dace743..ca909ba98e0 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -638,7 +638,8 @@ export class InlineChatWidget { this._elements.followUps.classList.toggle('hidden', !items || items.length === 0); reset(this._elements.followUps); if (items && items.length > 0 && onFollowup) { - this._followUpDisposables.add(new ChatFollowups(this._elements.followUps, items, undefined, onFollowup, this._contextKeyService)); + this._followUpDisposables.add( + this._instantiationService.createInstance(ChatFollowups, this._elements.followUps, items, undefined, onFollowup)); } this._onDidChangeHeight.fire(); } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index d2a76eb8736..5a667140993 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -216,7 +216,7 @@ declare module 'vscode' { */ agentId?: string; - subCommand?: string; + command?: string; /** * A tooltip to show when hovering over the followup. From fd0784486582c2cff44a8c4bb28fa01b38904473 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 16:27:38 -0600 Subject: [PATCH 1166/1897] wip make request action --- .../chat/browser/terminalChatController.ts | 20 +++++++++- .../chat/browser/terminalChatWidget.ts | 39 ++++++++++++++++--- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index ae9ded44aec..5392e1787dd 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -14,8 +14,8 @@ import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/wid import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; export class TerminalChatController extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; @@ -51,7 +51,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IConfigurationService private _configurationService: IConfigurationService, @ITerminalService private readonly _terminalService: ITerminalService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, + @IChatAgentService private readonly _chatAgentService: IChatAgentService // @IContextKeyService private readonly _contextKeyService: IContextKeyService, // @IInstantiationService private readonly _instantiationService: IInstantiationService, // @ICommandService private readonly _commandService: ICommandService, @@ -104,6 +104,22 @@ export class TerminalChatController extends Disposable implements ITerminalContr async acceptInput(): Promise { // TODO: create session, deal with response + // this._activeSession = new Session(EditMode.Live, , this._instance); + // const initVariableData: IChatRequestVariableData = { message: getPromptText(parsedRequest.parts), variables: {} }; + // request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); + // const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); + // const requestProps: IChatAgentRequest = { + // sessionId: 'sessionId', + // requestId: 'fake', + // agentId: 'terminal', + // message: this._chatWidget?.rawValue?.getValue() || '', + // // variables: variableData.variables, + // // command: agentSlashCommandPart?.command.name, + // // variables2: asVariablesData2(parsedRequest, variableData) + // }; + // const agentResult = await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, undefined, token); + // const rawResult = agentResult; + // const agentOrCommandFollowups = this._chatAgentService.getFollowups('terminal', agentResult, followupsCancelToken); this._chatWidget?.rawValue?.acceptInput(); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 14a6ca567b4..ed78f6292e7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -16,8 +16,9 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; import { MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_STATUS, MENU_CELL_CHAT_WIDGET_FEEDBACK } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; -import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CancellationToken } from 'vs/base/common/cancellation'; export class TerminalChatWidget extends Disposable { @@ -30,7 +31,6 @@ export class TerminalChatWidget extends Disposable { private readonly _focusTracker: IFocusTracker; - private _chatModel: ChatModel | undefined; constructor( private readonly _container: HTMLElement, @@ -38,7 +38,7 @@ export class TerminalChatWidget extends Disposable { @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IChatService private readonly _chatService: IChatService) { + @IChatAgentService private readonly _chatAgentService: IChatAgentService) { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); @@ -113,7 +113,7 @@ export class TerminalChatWidget extends Disposable { cancel(): void { // this._widget?.clear(); } - acceptInput(): void { + async acceptInput(): Promise { // this._widget?.acceptInput(); // this._chatModel ??= this._chatService.startSession('terminal', CancellationToken.None); @@ -121,9 +121,36 @@ export class TerminalChatWidget extends Disposable { // throw new Error('Could not start chat session'); // } // this._chatService?.sendRequest(this._chatModel?.sessionId!, this._inlineChatWidget.value); + // this._activeSession = new Session(EditMode.Live, , this._instance); + // const initVariableData: IChatRequestVariableData = { message: getPromptText(parsedRequest.parts), variables: {} }; + // request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); + // const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); + const progressCallback = (progress: IChatProgress) => { + // if (token.isCancellationRequested) { + // return; + // } + console.log(progress); + // gotProgress = true; + if (progress.kind === 'content' || progress.kind === 'markdownContent') { + // this.trace('sendRequest', `Provider returned progress for session ${model.sessionId}, ${typeof progress.content === 'string' ? progress.content.length : progress.content.value.length} chars`); + } else { + // this.trace('sendRequest', `Provider returned progress: ${JSON.stringify(progress)}`); + } + + // model.acceptResponseProgress(request, progress); + }; + const requestProps: IChatAgentRequest = { + sessionId: generateUuid(), + requestId: generateUuid(), + agentId: 'terminal', + message: this._inlineChatWidget.value || '', + variables: new Map() as any, + variables2: {} as any + }; + const agentResult = await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], CancellationToken.None); + console.log(agentResult); this._inlineChatWidget.value = ''; - // this.widget.setModel(this.model, { inputValue: this._currentQuery }); } layout(width: number): void { // this._widget?.layout(100, width < 300 ? 300 : width); From ba50d3b212caf4ed5504442493d1fd7b3e5dc140 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Mon, 12 Feb 2024 18:18:04 -0600 Subject: [PATCH 1167/1897] Error in search input for spawn ripgrep ENOENT (#205045) * Error in search input for spawn ripgrep ENOENT Fixes #204011 * add message for ignoring notebook results. --- src/vs/workbench/api/common/extHostNotebook.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index f4a80653baa..6af6206b7fc 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -434,6 +434,17 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { } finalMatchedTargets.add(uri); }); + }).catch(err => { + // temporary fix for https://github.com/microsoft/vscode/issues/205044: don't show notebook results for remotehub repos. + if (err.code === 'ENOENT') { + console.warn(`Could not find notebook search results, ignoring notebook results.`); + return { + limitHit: false, + messages: [], + }; + } else { + throw err; + } }); })) )); From b2598b92d18ea1132e9cd35df776c8c22a7f9730 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 13 Feb 2024 00:04:24 -0300 Subject: [PATCH 1168/1897] Fix variables2 for #file (#205054) --- src/vs/workbench/contrib/chat/common/chatParserTypes.ts | 5 ++--- src/vs/workbench/contrib/chat/common/chatServiceImpl.ts | 5 ++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts index f65c12199b8..fd9630ab515 100644 --- a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts +++ b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts @@ -138,12 +138,11 @@ export class ChatRequestDynamicVariablePart implements IParsedChatRequestPart { constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly text: string, readonly data: IChatRequestVariableValue[]) { } get referenceText(): string { - return this.text; + return this.text.replace(chatVariableLeader, ''); } get promptText(): string { - // This needs to be dynamically generated for de-duping - return ``; + return this.text; } } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index cdbcda18537..5ec45665e6d 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -23,7 +23,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, IChatRequestVariableData2, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestVariablePart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicVariablePart, ChatRequestSlashCommandPart, ChatRequestVariablePart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; @@ -776,6 +776,9 @@ function asVariablesData2(parsedRequest: IParsedChatRequest, variableData: IChat if (part instanceof ChatRequestVariablePart) { const values = variableData.variables[part.variableName]; res.variables.push({ name: part.variableName, range: part.range, values }); + } else if (part instanceof ChatRequestDynamicVariablePart) { + // Need variable without `#` + res.variables.push({ name: part.referenceText, range: part.range, values: part.data }); } } From 6061711e35083e2114ba765bdabb39575b933b20 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 20:40:25 -0800 Subject: [PATCH 1169/1897] Allow focus to be between cells --- .../controller/chat/cellChatActions.ts | 4 ++-- .../controller/chat/notebookChatController.ts | 20 ++++++++++++++++--- .../notebook/browser/view/notebookCellList.ts | 9 +++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 5959e33fcd7..b4c797e0c36 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -22,7 +22,7 @@ import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NotebookSetting } from 'vs/w import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super( { @@ -43,7 +43,7 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { NotebookChatController.get(context.notebookEditor)?.acceptInput(); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index a2fde377709..33ad97dc0b6 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -272,7 +272,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._widget.inlineChatWidget.placeholder = this._activeSession?.session.placeholder ?? localize('default.placeholder', "Ask a question"); this._widget.inlineChatWidget.updateInfo(this._activeSession?.session.message ?? localize('welcome.1', "AI-generated code may be incorrect")); this._widget.inlineChatWidget.updateSlashCommands(this._activeSession?.session.slashCommands ?? []); - this._widget.focus(); + this.focusWidget(); } if (this._widget && input) { @@ -287,6 +287,20 @@ export class NotebookChatController extends Disposable implements INotebookEdito }); } + private focusWidget() { + if (!this._widget) { + return; + } + + this._notebookEditor.focusContainer(); + this._notebookEditor.setSelections([{ + start: this._widget.afterModelPosition, + end: this._widget.afterModelPosition + }]); + + this._widget.focus(); + } + async acceptInput() { assertType(this._activeSession); assertType(this._widget); @@ -522,12 +536,12 @@ export class NotebookChatController extends Disposable implements INotebookEdito switch (direction) { case 'above': if (this._widget?.afterModelPosition === index) { - this._widget.focus(); + this.focusWidget(); } break; case 'below': if (this._widget?.afterModelPosition === index + 1) { - this._widget.focus(); + this.focusWidget(); } break; default: diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index e613e8af3e6..9f57fec5385 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -1282,6 +1282,15 @@ export class NotebookCellList extends WorkbenchList implements ID } focusContainer() { + // allow focus to be between cells + this._viewModel?.updateSelectionsState({ + kind: SelectionStateType.Handle, + primary: null, + selections: [] + }, 'view'); + this.setFocus([], undefined, true); + this.setSelection([], undefined, true); + super.domFocus(); } From aca3aec01a9d517a61eca3e68be479867139b254 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 20:54:23 -0800 Subject: [PATCH 1170/1897] Update notebook selections when focus change --- .../controller/chat/notebookChatController.ts | 51 ++++++++++++++----- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 33ad97dc0b6..b2c6309f9ba 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Dimension, WindowIntervalTimer, getWindow, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { Dimension, IFocusTracker, WindowIntervalTimer, getWindow, scheduleAtNextAnimationFrame, trackFocus } from 'vs/base/browser/dom'; import { CancelablePromise, Queue, createCancelablePromise, disposableTimeout, raceCancellationError } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { MovingAverage } from 'vs/base/common/numbers'; import { StopWatch } from 'vs/base/common/stopwatch'; @@ -162,6 +162,8 @@ export class NotebookChatController extends Disposable implements INotebookEdito private readonly _ctxCellWidgetFocused: IContextKey; private readonly _ctxLastResponseType: IContextKey; private _widget: NotebookChatWidget | undefined; + private _widgetDisposableStore = this._register(new DisposableStore()); + private _focusTracker: IFocusTracker | undefined; constructor( private readonly _notebookEditor: INotebookEditor, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -189,6 +191,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito const window = getWindow(this._widget.domNode); this._widget.dispose(); this._widget = undefined; + this._widgetDisposableStore.clear(); scheduleAtNextAnimationFrame(window, () => { this._createWidget(index, input, autoSend); @@ -203,28 +206,36 @@ export class NotebookChatController extends Disposable implements INotebookEdito } private _createWidget(index: number, input: string | undefined, autoSend: boolean | undefined) { + // Clear the widget if it's already there + this._widgetDisposableStore.clear(); + const viewZoneContainer = document.createElement('div'); viewZoneContainer.classList.add('monaco-editor'); const widgetContainer = document.createElement('div'); widgetContainer.style.position = 'absolute'; viewZoneContainer.appendChild(widgetContainer); + this._focusTracker = this._widgetDisposableStore.add(trackFocus(viewZoneContainer)); + this._widgetDisposableStore.add(this._focusTracker.onDidFocus(() => { + this._updateNotebookEditorFocusNSelections(); + })); + const fakeParentEditorElement = document.createElement('div'); - const fakeParentEditor = this._instantiationService.createInstance( + const fakeParentEditor = this._widgetDisposableStore.add(this._instantiationService.createInstance( CodeEditorWidget, fakeParentEditorElement, { }, { isSimpleWidget: true } - ); + )); const inputBoxPath = `/notebook-chat-input-${NotebookChatController.counter++}`; const inputUri = URI.from({ scheme: Schemas.untitled, path: inputBoxPath }); const result: ITextModel = this._modelService.createModel('', null, inputUri, false); fakeParentEditor.setModel(result); - const inlineChatWidget = this._instantiationService.createInstance( + const inlineChatWidget = this._widgetDisposableStore.add(this._instantiationService.createInstance( InlineChatWidget, fakeParentEditor, { @@ -233,7 +244,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito statusMenuId: MENU_CELL_CHAT_WIDGET_STATUS, feedbackMenuId: MENU_CELL_CHAT_WIDGET_FEEDBACK } - ); + )); inlineChatWidget.placeholder = localize('default.placeholder', "Ask a question"); inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); widgetContainer.appendChild(inlineChatWidget.domNode); @@ -260,7 +271,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito disposableTimeout(() => { this._ctxCellWidgetFocused.set(true); - this._widget?.focus(); + this._focusWidget(); }, 0, this._store); this._sessionCtor = createCancelablePromise(async token => { @@ -272,7 +283,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._widget.inlineChatWidget.placeholder = this._activeSession?.session.placeholder ?? localize('default.placeholder', "Ask a question"); this._widget.inlineChatWidget.updateInfo(this._activeSession?.session.message ?? localize('welcome.1', "AI-generated code may be incorrect")); this._widget.inlineChatWidget.updateSlashCommands(this._activeSession?.session.slashCommands ?? []); - this.focusWidget(); + this._focusWidget(); } if (this._widget && input) { @@ -287,7 +298,16 @@ export class NotebookChatController extends Disposable implements INotebookEdito }); } - private focusWidget() { + private _focusWidget() { + if (!this._widget) { + return; + } + + this._updateNotebookEditorFocusNSelections(); + this._widget.focus(); + } + + private _updateNotebookEditorFocusNSelections() { if (!this._widget) { return; } @@ -297,8 +317,6 @@ export class NotebookChatController extends Disposable implements INotebookEdito start: this._widget.afterModelPosition, end: this._widget.afterModelPosition }]); - - this._widget.focus(); } async acceptInput() { @@ -536,12 +554,12 @@ export class NotebookChatController extends Disposable implements INotebookEdito switch (direction) { case 'above': if (this._widget?.afterModelPosition === index) { - this.focusWidget(); + this._focusWidget(); } break; case 'below': if (this._widget?.afterModelPosition === index + 1) { - this.focusWidget(); + this._focusWidget(); } break; default: @@ -581,6 +599,13 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._sessionCtor = undefined; this._widget?.dispose(); this._widget = undefined; + this._widgetDisposableStore.clear(); + } + + public override dispose(): void { + this.dismiss(); + + super.dispose(); } } From 590d1fde9d211677754d517e7b92690f6b033214 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 21:06:46 -0800 Subject: [PATCH 1171/1897] Update markdown fragment from requests --- .../controller/chat/notebookChatController.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index b2c6309f9ba..b686115da75 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -36,7 +36,7 @@ import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browse import { EmptyResponse, ErrorResponse, ReplyResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; import { ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; -import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; +import { IInlineChatMessageAppender, InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; @@ -354,7 +354,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito const request: IInlineChatRequest = { requestId: generateUuid(), prompt: value, - attempt: 0, + attempt: this._activeSession.lastInput.attempt, selection: { selectionStartLineNumber: 1, selectionStartColumn: 1, positionLineNumber: 1, positionColumn: 1 }, wholeRange: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, live: true, @@ -366,10 +366,12 @@ export class NotebookChatController extends Disposable implements INotebookEdito const requestCts = new CancellationTokenSource(); const progressEdits: TextEdit[][] = []; + const progressiveEditsQueue = new Queue(); const progressiveEditsClock = StopWatch.create(); const progressiveEditsAvgDuration = new MovingAverage(); const progressiveEditsCts = new CancellationTokenSource(requestCts.token); + let progressiveChatResponse: IInlineChatMessageAppender | undefined; const progress = new AsyncProgress(async data => { // console.log('received chunk', data, request); @@ -400,6 +402,19 @@ export class NotebookChatController extends Disposable implements INotebookEdito ); }); } + + if (data.markdownFragment) { + if (!progressiveChatResponse) { + const message = { + message: new MarkdownString(data.markdownFragment, { supportThemeIcons: true, supportHtml: true, isTrusted: false }), + providerId: this._activeSession!.provider.debugName, + requestId: request.requestId, + }; + progressiveChatResponse = this._widget?.inlineChatWidget.updateChatMessage(message, true); + } else { + progressiveChatResponse.appendContent(data.markdownFragment); + } + } }); const task = this._activeSession.provider.provideResponse(this._activeSession.session, request, progress, requestCts.token); From 27211bf19c3563a9dc9d6dd04ca6ed6e7d244c07 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 21:10:51 -0800 Subject: [PATCH 1172/1897] ensure init slash commands are registered --- .../notebook/browser/controller/chat/notebookChatController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index b686115da75..5eccad3da48 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -277,7 +277,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._sessionCtor = createCancelablePromise(async token => { if (fakeParentEditor.hasModel()) { - this._startSession(fakeParentEditor, token); + await this._startSession(fakeParentEditor, token); if (this._widget) { this._widget.inlineChatWidget.placeholder = this._activeSession?.session.placeholder ?? localize('default.placeholder', "Ask a question"); From cd89ea1ee64a0ef28618b9c97dd0a52b86c2fbe5 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 21:34:39 -0800 Subject: [PATCH 1173/1897] support multiple whitespaces at the same position --- .../browser/view/notebookCellListView.ts | 47 +++++---- .../test/browser/notebookViewZones.test.ts | 95 +++++++++++++++++++ 2 files changed, 125 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts index 8ff1afea0c1..bafaac004e0 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts @@ -16,6 +16,7 @@ export interface IWhitespace { */ afterPosition: number; size: number; + priority: number; } export class NotebookCellsLayout implements IRangeMap { private _items: IItem[] = []; @@ -72,10 +73,11 @@ export class NotebookCellsLayout implements IRangeMap { const newSizes = []; for (let i = 0; i < inserts.length; i++) { const insertIndex = i + index; - const existingWhitespace = this._whitespace.find(ws => ws.afterPosition === insertIndex + 1); + const existingWhitespaces = this._whitespace.filter(ws => ws.afterPosition === insertIndex + 1); - if (existingWhitespace) { - newSizes.push(inserts[i].size + existingWhitespace.size); + + if (existingWhitespaces.length > 0) { + newSizes.push(inserts[i].size + existingWhitespaces.reduce((acc, ws) => acc + ws.size, 0)); } else { newSizes.push(inserts[i].size); } @@ -85,9 +87,9 @@ export class NotebookCellsLayout implements IRangeMap { // Now that the items array has been updated, and the whitespaces are updated elsewhere, if an item is removed/inserted, the accumlated size of the items are all updated. // Loop through all items from the index where the splice started, to the end for (let i = index; i < this._items.length; i++) { - const existingWhitespace = this._whitespace.find(ws => ws.afterPosition === i + 1); - if (existingWhitespace) { - this._prefixSumComputer.setValue(i, this._items[i].size + existingWhitespace.size); + const existingWhitespaces = this._whitespace.filter(ws => ws.afterPosition === i + 1); + if (existingWhitespaces.length > 0) { + this._prefixSumComputer.setValue(i, this._items[i].size + existingWhitespaces.reduce((acc, ws) => acc + ws.size, 0)); } else { this._prefixSumComputer.setValue(i, this._items[i].size); } @@ -95,14 +97,20 @@ export class NotebookCellsLayout implements IRangeMap { } insertWhitespace(id: string, afterPosition: number, size: number): void { - const existingWhitespace = this._whitespace.find(ws => ws.afterPosition === afterPosition); - if (existingWhitespace) { - throw new Error('Whitespace already exists at the specified position'); + let priority = 0; + const existingWhitespaces = this._whitespace.filter(ws => ws.afterPosition === afterPosition); + if (existingWhitespaces.length > 0) { + priority = Math.max(...existingWhitespaces.map(ws => ws.priority)) + 1; } - this._whitespace.push({ id, afterPosition: afterPosition, size }); + this._whitespace.push({ id, afterPosition: afterPosition, size, priority }); this._size += size; // Update the total size to include the whitespace - this._whitespace.sort((a, b) => a.afterPosition - b.afterPosition); // Keep the whitespace sorted by index + this._whitespace.sort((a, b) => { + if (a.afterPosition === b.afterPosition) { + return a.priority - b.priority; + } + return a.afterPosition - b.afterPosition; + }); // find item size of index if (afterPosition > 0) { @@ -150,7 +158,8 @@ export class NotebookCellsLayout implements IRangeMap { if (whitespace.afterPosition > 0) { const index = whitespace.afterPosition - 1; const itemSize = this._items[index].size; - const accSize = itemSize; + const remainingWhitespaces = this._whitespace.filter(ws => ws.afterPosition === whitespace.afterPosition); + const accSize = itemSize + remainingWhitespaces.reduce((acc, ws) => acc + ws.size, 0); this._prefixSumComputer.setValue(index, accSize); } } @@ -169,16 +178,20 @@ export class NotebookCellsLayout implements IRangeMap { const afterPosition = whitespace.afterPosition; if (afterPosition === 0) { - return this.paddingTop; + // find all whitespaces at the same position but with higher priority (smaller number) + const whitespaces = this._whitespace.filter(ws => ws.afterPosition === afterPosition && ws.priority < whitespace.priority); + return whitespaces.reduce((acc, ws) => acc + ws.size, 0) + this.paddingTop; } - const whitespaceBeforeFirstItem = this._whitespace.length > 0 && this._whitespace[0].afterPosition === 0 ? this._whitespace[0].size : 0; + const whitespaceBeforeFirstItem = this._whitespace.filter(ws => ws.afterPosition === 0).reduce((acc, ws) => acc + ws.size, 0); // previous item index const index = afterPosition - 1; const previousItemPosition = this._prefixSumComputer.getPrefixSum(index); const previousItemSize = this._items[index].size; - return previousItemPosition + previousItemSize + whitespaceBeforeFirstItem + this.paddingTop; + const previousWhitespace = this._whitespace.filter(ws => ws.afterPosition === afterPosition - 1); + const whitespaceBefore = previousWhitespace.reduce((acc, ws) => acc + ws.size, 0); + return previousItemPosition + previousItemSize + whitespaceBeforeFirstItem + this.paddingTop + whitespaceBefore; } indexAt(position: number): number { @@ -186,7 +199,7 @@ export class NotebookCellsLayout implements IRangeMap { return -1; } - const whitespaceBeforeFirstItem = this._whitespace.length > 0 && this._whitespace[0].afterPosition === 0 ? this._whitespace[0].size : 0; + const whitespaceBeforeFirstItem = this._whitespace.filter(ws => ws.afterPosition === 0).reduce((acc, ws) => acc + ws.size, 0); const offset = position - (this._paddingTop + whitespaceBeforeFirstItem); if (offset <= 0) { @@ -219,7 +232,7 @@ export class NotebookCellsLayout implements IRangeMap { return -1; } - const whitespaceBeforeFirstItem = this._whitespace.length > 0 && this._whitespace[0].afterPosition === 0 ? this._whitespace[0].size : 0; + const whitespaceBeforeFirstItem = this._whitespace.filter(ws => ws.afterPosition === 0).reduce((acc, ws) => acc + ws.size, 0); return this._prefixSumComputer.getPrefixSum(index/** count */) + this._paddingTop + whitespaceBeforeFirstItem; } } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts index fb8562f0a1e..a858c9d7866 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts @@ -346,6 +346,41 @@ suite('NotebookRangeMap with whitesspaces', () => { instantiationService.stub(IConfigurationService, config); }); + test('Whitespace CRUD', async function () { + const twenty = { size: 20 }; + + const rangeMap = new NotebookCellsLayout(0); + rangeMap.splice(0, 0, [twenty, twenty, twenty]); + rangeMap.insertWhitespace('0', 0, 5); + rangeMap.insertWhitespace('1', 0, 5); + assert.strictEqual(rangeMap.indexAt(0), 0); + assert.strictEqual(rangeMap.indexAt(1), 0); + assert.strictEqual(rangeMap.indexAt(10), 0); + assert.strictEqual(rangeMap.indexAt(11), 0); + assert.strictEqual(rangeMap.indexAt(21), 0); + assert.strictEqual(rangeMap.indexAt(31), 1); + assert.strictEqual(rangeMap.positionAt(0), 10); + + assert.strictEqual(rangeMap.getWhitespacePosition('0'), 0); + assert.strictEqual(rangeMap.getWhitespacePosition('1'), 5); + + assert.strictEqual(rangeMap.positionAt(0), 10); + assert.strictEqual(rangeMap.positionAt(1), 30); + + rangeMap.changeOneWhitespace('0', 0, 10); + assert.strictEqual(rangeMap.getWhitespacePosition('0'), 0); + assert.strictEqual(rangeMap.getWhitespacePosition('1'), 10); + + assert.strictEqual(rangeMap.positionAt(0), 15); + assert.strictEqual(rangeMap.positionAt(1), 35); + + rangeMap.removeWhitespace('1'); + assert.strictEqual(rangeMap.getWhitespacePosition('0'), 0); + + assert.strictEqual(rangeMap.positionAt(0), 10); + assert.strictEqual(rangeMap.positionAt(1), 30); + }); + test('Whitespace with editing', async function () { await withTestNotebook( [ @@ -630,4 +665,64 @@ suite('NotebookRangeMap with whitesspaces', () => { }); }); }); + + test('Whitespace with multiple viewzones at same position', async function () { + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markup, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markup, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['# header c', 'markdown', CellKind.Markup, [], {}] + ], + async (editor, viewModel, disposables) => { + viewModel.restoreEditorViewState({ + editingCells: [false, false, false, false, false], + cellLineNumberStates: {}, + editorViewStates: [null, null, null, null, null], + cellTotalHeights: [50, 100, 50, 100, 50], + collapsedInputCells: {}, + collapsedOutputCells: {}, + }); + + const cellList = createNotebookCellList(instantiationService, disposables); + disposables.add(cellList); + cellList.attachViewModel(viewModel); + + // render height 210, it can render 3 full cells and 1 partial cell + cellList.layout(210, 100); + assert.strictEqual(cellList.scrollHeight, 350); + + cellList.changeViewZones(accessor => { + const first = accessor.addZone({ + afterModelPosition: 0, + heightInPx: 20, + domNode: document.createElement('div') + }); + + accessor.layoutZone(first); + assert.strictEqual(cellList.scrollHeight, 370); + + const second = accessor.addZone({ + afterModelPosition: 0, + heightInPx: 20, + domNode: document.createElement('div') + }); + accessor.layoutZone(second); + assert.strictEqual(cellList.scrollHeight, 390); + + assert.strictEqual(cellList.getElementTop(0), 40); + assert.strictEqual(cellList.getElementTop(1), 90); + assert.strictEqual(cellList.getElementTop(2), 190); + assert.strictEqual(cellList.getElementTop(3), 240); + assert.strictEqual(cellList.getElementTop(4), 340); + + + accessor.removeZone(first); + assert.strictEqual(cellList.scrollHeight, 370); + accessor.removeZone(second); + assert.strictEqual(cellList.scrollHeight, 350); + }); + }); + }); }); From a16faa30835e1185bf3d6017ad186650dc2fcc98 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 22:05:55 -0800 Subject: [PATCH 1174/1897] Reveal chat widget into view on creation. --- .../controller/chat/cellChatActions.ts | 4 ++-- .../controller/chat/notebookChatController.ts | 20 +++++++++++++++++++ .../notebook/browser/notebookBrowser.ts | 6 ++++++ .../notebook/browser/notebookEditorWidget.ts | 8 ++++++++ .../notebook/browser/view/notebookCellList.ts | 9 +++++++++ .../browser/view/notebookRenderingCommon.ts | 1 + 6 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index b4c797e0c36..3829e6e67c2 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -428,7 +428,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super( { @@ -453,7 +453,7 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { context.notebookEditor.focusContainer(); NotebookChatController.get(context.notebookEditor)?.run(0, '', false); } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 5eccad3da48..dd6529fe241 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -257,6 +257,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito }; const id = accessor.addZone(notebookViewZone); + this._scrollWidgetIntoView(index); this._widget = new NotebookChatWidget( this._notebookEditor, @@ -298,6 +299,25 @@ export class NotebookChatController extends Disposable implements INotebookEdito }); } + private _scrollWidgetIntoView(index: number) { + if (index === 0 || this._notebookEditor.getLength() === 0) { + // the cell is at the beginning of the notebook + this._notebookEditor.revealOffsetInCenterIfOutsideViewport(0); + } else if (index >= this._notebookEditor.getLength()) { + // the cell is at the end of the notebook + const cell = this._notebookEditor.cellAt(this._notebookEditor.getLength() - 1)!; + const cellTop = this._notebookEditor.getAbsoluteTopOfElement(cell); + const cellHeight = this._notebookEditor.getHeightOfElement(cell); + + this._notebookEditor.revealOffsetInCenterIfOutsideViewport(cellTop + cellHeight); + } else { + const cell = this._notebookEditor.cellAt(index); + if (cell) { + this._notebookEditor.revealInCenterIfOutsideViewport(cell); + } + } + } + private _focusWidget() { if (!this._widget) { return; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 6d0331e96d0..f54e31ebf96 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -663,6 +663,11 @@ export interface INotebookEditor { */ revealCellOffsetInCenter(cell: ICellViewModel, offset: number): void; + /** + * Reveal `offset` in the list view into viewport center if it is outside of the viewport. + */ + revealOffsetInCenterIfOutsideViewport(offset: number): void; + /** * Convert the view range to model range * @param startIndex Inclusive @@ -720,6 +725,7 @@ export interface INotebookEditor { hideProgress(): void; getAbsoluteTopOfElement(cell: ICellViewModel): number; + getHeightOfElement(cell: ICellViewModel): number; } export interface IActiveNotebookEditor extends INotebookEditor { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 0719ee2ca4f..64d26f612ce 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2086,6 +2086,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return this._list.getCellViewScrollTop(cell); } + getHeightOfElement(cell: ICellViewModel) { + return this._list.elementHeight(cell); + } + scrollToBottom() { this._list.scrollToBottom(); } @@ -2146,6 +2150,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return this._list.revealCellOffsetInCenter(cell, offset); } + revealOffsetInCenterIfOutsideViewport(offset: number) { + return this._list.revealOffsetInCenterIfOutsideViewport(offset); + } + getViewIndexByModelIndex(index: number): number { if (!this._listViewInfoAccessor) { return -1; diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 9f57fec5385..af4c103561b 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -1171,6 +1171,15 @@ export class NotebookCellList extends WorkbenchList implements ID } } + revealOffsetInCenterIfOutsideViewport(offset: number) { + const scrollTop = this.getViewScrollTop(); + const wrapperBottom = this.getViewScrollBottom(); + + if (offset < scrollTop || offset > wrapperBottom) { + this.view.setScrollTop(offset - this.view.renderHeight / 2); + } + } + private _revealInCenterIfOutsideViewport(viewIndex: number) { this._revealInternal(viewIndex, true, CellRevealPosition.Center); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts index 01e4f343458..fd10069c2b0 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts @@ -63,6 +63,7 @@ export interface INotebookCellList extends ICoordinatesConverter { revealCells(range: ICellRange): void; revealRangeInCell(cell: ICellViewModel, range: Selection | Range, revealType: CellRevealRangeType): Promise; revealCellOffsetInCenter(element: ICellViewModel, offset: number): void; + revealOffsetInCenterIfOutsideViewport(offset: number): void; setHiddenAreas(_ranges: ICellRange[], triggerViewUpdate: boolean): boolean; changeViewZones(callback: (accessor: INotebookViewZoneChangeAccessor) => void): void; domElementOfElement(element: ICellViewModel): HTMLElement | null; From 7db840ad973637b422ebabe7b54bbaef1769ce15 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 13 Feb 2024 07:07:19 +0100 Subject: [PATCH 1175/1897] eng - prefix error to hunt down shared process error (#205060) --- .../electron-main/userDataProfileStorageIpc.ts | 2 +- src/vs/platform/userDataSync/common/userDataSyncIpc.ts | 4 ++-- src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc.ts b/src/vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc.ts index e3ac3124a10..457dac8e1d4 100644 --- a/src/vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc.ts +++ b/src/vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc.ts @@ -97,7 +97,7 @@ export class ProfileStorageChangesListenerChannel extends Disposable implements switch (event) { case 'onDidChange': return this._onDidChange.event; } - throw new Error(`Event not found: ${event}`); + throw new Error(`[ProfileStorageChangesListenerChannel] Event not found: ${event}`); } async call(_: unknown, command: string): Promise { diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 74725542b10..ede978060d9 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -22,7 +22,7 @@ export class UserDataSyncAccountServiceChannel implements IServerChannel { case 'onDidChangeAccount': return this.service.onDidChangeAccount; case 'onTokenFailed': return this.service.onTokenFailed; } - throw new Error(`Event not found: ${event}`); + throw new Error(`[UserDataSyncAccountServiceChannel] Event not found: ${event}`); } call(context: any, command: string, args?: any): Promise { @@ -70,7 +70,7 @@ export class UserDataSyncStoreManagementServiceChannel implements IServerChannel switch (event) { case 'onDidChangeUserDataSyncStore': return this.service.onDidChangeUserDataSyncStore; } - throw new Error(`Event not found: ${event}`); + throw new Error(`[UserDataSyncStoreManagementServiceChannel] Event not found: ${event}`); } call(context: any, command: string, args?: any): Promise { diff --git a/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts index 9619fae438e..b052fe458c5 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts @@ -51,7 +51,7 @@ export class UserDataSyncServiceChannel implements IServerChannel { case 'manualSync/onSynchronizeResources': return this.onManualSynchronizeResources.event; } - throw new Error(`Event not found: ${event}`); + throw new Error(`[UserDataSyncServiceChannel] Event not found: ${event}`); } async call(context: any, command: string, args?: any): Promise { From 4ebc4f8b6447feb02e235cad8bd24c254e84dfc5 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 22:14:37 -0800 Subject: [PATCH 1176/1897] Small tweaks to widget reveal --- .../browser/controller/chat/notebookChatController.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index dd6529fe241..9fdd1bbf9fd 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -311,9 +311,12 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._notebookEditor.revealOffsetInCenterIfOutsideViewport(cellTop + cellHeight); } else { - const cell = this._notebookEditor.cellAt(index); + const cell = this._notebookEditor.cellAt(index - 1); if (cell) { - this._notebookEditor.revealInCenterIfOutsideViewport(cell); + const cellTop = this._notebookEditor.getAbsoluteTopOfElement(cell); + const cellHeight = this._notebookEditor.getHeightOfElement(cell); + + this._notebookEditor.revealOffsetInCenterIfOutsideViewport(cellTop + cellHeight); } } } From e2d8d6273f0d038ebd4183c5bcd1017fa988956f Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 22:22:56 -0800 Subject: [PATCH 1177/1897] :lipstick: --- .../controller/chat/notebookChatController.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 9fdd1bbf9fd..53e513ca6ee 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -303,18 +303,12 @@ export class NotebookChatController extends Disposable implements INotebookEdito if (index === 0 || this._notebookEditor.getLength() === 0) { // the cell is at the beginning of the notebook this._notebookEditor.revealOffsetInCenterIfOutsideViewport(0); - } else if (index >= this._notebookEditor.getLength()) { - // the cell is at the end of the notebook - const cell = this._notebookEditor.cellAt(this._notebookEditor.getLength() - 1)!; - const cellTop = this._notebookEditor.getAbsoluteTopOfElement(cell); - const cellHeight = this._notebookEditor.getHeightOfElement(cell); - - this._notebookEditor.revealOffsetInCenterIfOutsideViewport(cellTop + cellHeight); } else { - const cell = this._notebookEditor.cellAt(index - 1); - if (cell) { - const cellTop = this._notebookEditor.getAbsoluteTopOfElement(cell); - const cellHeight = this._notebookEditor.getHeightOfElement(cell); + // the cell is at the end of the notebook + const previousCell = this._notebookEditor.cellAt(Math.min(index - 1, this._notebookEditor.getLength() - 1)); + if (previousCell) { + const cellTop = this._notebookEditor.getAbsoluteTopOfElement(previousCell); + const cellHeight = this._notebookEditor.getHeightOfElement(previousCell); this._notebookEditor.revealOffsetInCenterIfOutsideViewport(cellTop + cellHeight); } From b1784433fc8eaf93fe230ca33de25bb531d682ad Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 23:11:18 -0800 Subject: [PATCH 1178/1897] Fix failing integration tests caused by notebook focus/selection change --- .../controller/chat/notebookChatController.ts | 2 +- .../notebook/browser/notebookBrowser.ts | 2 +- .../notebook/browser/notebookEditorWidget.ts | 4 ++-- .../notebook/browser/view/notebookCellList.ts | 20 ++++++++++--------- .../browser/view/notebookRenderingCommon.ts | 2 +- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 53e513ca6ee..6f0a0ad584a 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -329,7 +329,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito return; } - this._notebookEditor.focusContainer(); + this._notebookEditor.focusContainer(true); this._notebookEditor.setSelections([{ start: this._widget.afterModelPosition, end: this._widget.afterModelPosition diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index f54e31ebf96..ae7113667a3 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -521,7 +521,7 @@ export interface INotebookEditor { /** * Focus the notebook cell list container */ - focusContainer(): void; + focusContainer(clearSelection?: boolean): void; hasEditorFocus(): boolean; hasWebviewFocus(): boolean; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 64d26f612ce..60ad41b8d18 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1968,11 +1968,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } } - focusContainer() { + focusContainer(clearSelection: boolean = false) { if (this._webviewFocused) { this._webview?.focusWebview(); } else { - this._list.focusContainer(); + this._list.focusContainer(clearSelection); } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index af4c103561b..d9d1dab798d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -1290,15 +1290,17 @@ export class NotebookCellList extends WorkbenchList implements ID super.domFocus(); } - focusContainer() { - // allow focus to be between cells - this._viewModel?.updateSelectionsState({ - kind: SelectionStateType.Handle, - primary: null, - selections: [] - }, 'view'); - this.setFocus([], undefined, true); - this.setSelection([], undefined, true); + focusContainer(clearSelection: boolean) { + if (clearSelection) { + // allow focus to be between cells + this._viewModel?.updateSelectionsState({ + kind: SelectionStateType.Handle, + primary: null, + selections: [] + }, 'view'); + this.setFocus([], undefined, true); + this.setSelection([], undefined, true); + } super.domFocus(); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts index fd10069c2b0..f2700c72a21 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts @@ -71,7 +71,7 @@ export interface INotebookCellList extends ICoordinatesConverter { triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent): void; updateElementHeight2(element: ICellViewModel, size: number, anchorElementIndex?: number | null): void; domFocus(): void; - focusContainer(): void; + focusContainer(clearSelection: boolean): void; setCellEditorSelection(element: ICellViewModel, range: Range): void; style(styles: IListStyles): void; getRenderHeight(): number; From 2f785c99624573f441d445d7327fd48790f899e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chanchevrier?= Date: Tue, 13 Feb 2024 09:14:21 +0100 Subject: [PATCH 1179/1897] fix: resize direction for terminals in the sidebar --- .../workbench/contrib/terminal/browser/terminalGroup.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index 7e944aa9753..30a95315f33 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -70,7 +70,6 @@ class SplitPaneContainer extends Disposable { if ( (this.orientation === Orientation.HORIZONTAL && direction === Direction.Down) || (this.orientation === Orientation.VERTICAL && direction === Direction.Right) || - (part === Parts.SIDEBAR_PART && direction === Direction.Left) || (part === Parts.AUXILIARYBAR_PART && direction === Direction.Right) ) { amount *= -1; @@ -578,9 +577,13 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { const isHorizontal = (direction === Direction.Left || direction === Direction.Right); + const part = getPartByLocation(this._terminalLocation); + + const isTerminalLeft = this._panelPosition === Position.LEFT || part === Parts.SIDEBAR_PART; + // Left-positionned panels have inverted controls // see https://github.com/microsoft/vscode/issues/140873 - const shouldInvertHorizontalResize = (isHorizontal && this._panelPosition === Position.LEFT); + const shouldInvertHorizontalResize = (isHorizontal && isTerminalLeft); const resizeDirection = shouldInvertHorizontalResize ? direction === Direction.Left ? Direction.Right : Direction.Left : direction; @@ -588,7 +591,7 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { // TODO: Support letter spacing and line height const charSize = (isHorizontal ? font.charWidth : font.charHeight); if (charSize) { - this._splitPaneContainer.resizePane(this._activeInstanceIndex, resizeDirection, charSize * Constants.ResizePartCellCount, getPartByLocation(this._terminalLocation)); + this._splitPaneContainer.resizePane(this._activeInstanceIndex, resizeDirection, charSize * Constants.ResizePartCellCount, part); } } From 656e26c80f38ed670804c88457cea5cc7ac80c76 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Tue, 13 Feb 2024 00:27:11 -0800 Subject: [PATCH 1180/1897] One auth provider per-extension (#205049) This way we don't complicate the user's experience who doesn't need to know anything about models. --- .../api/browser/mainThreadChatProvider.ts | 55 +++++++++------- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 1 + .../api/common/extHostChatProvider.ts | 66 +++++++++++++++---- .../contrib/chat/common/chatProvider.ts | 4 ++ .../vscode.proposed.chatProvider.d.ts | 7 ++ 6 files changed, 99 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index 1a1d046357a..801b0139627 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -5,7 +5,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; @@ -59,7 +59,9 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { } } })); - dipsosables.add(this._registerAuthenticationProvider(identifier)); + if (metadata.auth) { + dipsosables.add(this._registerAuthenticationProvider(metadata.extension, metadata.auth)); + } dipsosables.add(Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: `lm-${identifier}`, label: localize('languageModels', "Language Model ({0})", `${identifier}-${metadata.model}`), @@ -100,48 +102,54 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { return task; } - private _registerAuthenticationProvider(identifier: string): IDisposable { - const disposables = new DisposableStore(); + private _registerAuthenticationProvider(extension: ExtensionIdentifier, auth: { providerLabel: string; accountLabel?: string | undefined }): IDisposable { // This needs to be done in both MainThread & ExtHost ChatProvider - const authProviderId = INTERNAL_AUTH_PROVIDER_PREFIX + identifier; - // This is what will be displayed in the UI and the account used for managing access via Auth UI - const authAccountId = identifier; - this._authenticationService.registerAuthenticationProvider(authProviderId, new LanguageModelAccessAuthProvider(authProviderId, authAccountId)); + const authProviderId = INTERNAL_AUTH_PROVIDER_PREFIX + extension.value; + + // Only register one auth provider per extension + if (this._authenticationService.getProviderIds().includes(authProviderId)) { + return Disposable.None; + } + + const disposables = new DisposableStore(); + this._authenticationService.registerAuthenticationProvider(authProviderId, new LanguageModelAccessAuthProvider(authProviderId, auth.providerLabel, auth.accountLabel)); disposables.add(toDisposable(() => { this._authenticationService.unregisterAuthenticationProvider(authProviderId); })); disposables.add(this._authenticationService.onDidChangeSessions(async (e) => { if (e.providerId === authProviderId) { if (e.event.removed?.length) { - const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, authAccountId); + const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, authProviderId); const extensionsToUpdateAccess = []; for (const allowed of allowedExtensions) { - const extension = await this._extensionService.getExtension(allowed.id); - this._authenticationService.updateAllowedExtension(authProviderId, authAccountId, allowed.id, allowed.name, false); - if (extension) { + const from = await this._extensionService.getExtension(allowed.id); + this._authenticationService.updateAllowedExtension(authProviderId, authProviderId, allowed.id, allowed.name, false); + if (from) { extensionsToUpdateAccess.push({ - extension: extension.identifier, + from: from.identifier, + to: extension, enabled: false }); } } - this._proxy.$updateAccesslist(extensionsToUpdateAccess); + this._proxy.$updateModelAccesslist(extensionsToUpdateAccess); } } })); disposables.add(this._authenticationService.onDidChangeExtensionSessionAccess(async (e) => { - const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, authAccountId); + const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, authProviderId); const accessList = []; for (const allowedExtension of allowedExtensions) { - const extension = await this._extensionService.getExtension(allowedExtension.id); - if (extension) { + const from = await this._extensionService.getExtension(allowedExtension.id); + if (from) { accessList.push({ - extension: extension.identifier, + from: from.identifier, + to: extension, enabled: allowedExtension.allowed ?? true }); } } - this._proxy.$updateAccesslist(accessList); + this._proxy.$updateModelAccesslist(accessList); })); return disposables; } @@ -150,7 +158,6 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { // The fake AuthenticationProvider that will be used to gate access to the Language Model. There will be one per provider. class LanguageModelAccessAuthProvider implements IAuthenticationProvider { supportsMultipleAccounts = false; - label = 'Language Model'; // Important for updating the UI private _onDidChangeSessions: Emitter = new Emitter(); @@ -158,7 +165,11 @@ class LanguageModelAccessAuthProvider implements IAuthenticationProvider { private _session: AuthenticationSession | undefined; - constructor(readonly id: string, private readonly accountName: string) { } + constructor( + readonly id: string, + readonly label: string, + private readonly _accountLabel: string = localize('languageModelsAccountId', 'Language Models') + ) { } async getSessions(scopes?: string[] | undefined): Promise { // If there are no scopes and no session that means no extension has requested a session yet @@ -189,7 +200,7 @@ class LanguageModelAccessAuthProvider implements IAuthenticationProvider { id: 'fake-session', account: { id: this.id, - label: this.accountName, + label: this._accountLabel, }, accessToken: 'fake-access-token', scopes, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 071e935c051..2b4bfec98a6 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1395,7 +1395,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const chat: typeof vscode.chat = { registerChatResponseProvider(id: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata) { checkProposedApiEnabled(extension, 'chatProvider'); - return extHostChatProvider.registerLanguageModel(extension.identifier, id, provider, metadata); + return extHostChatProvider.registerLanguageModel(extension, id, provider, metadata); }, requestLanguageModelAccess(id, options) { checkProposedApiEnabled(extension, 'chatRequestAccess'); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 964afc507dc..2f931bb83ef 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1184,6 +1184,7 @@ export interface MainThreadChatProviderShape extends IDisposable { export interface ExtHostChatProviderShape { $updateLanguageModels(data: { added?: string[]; removed?: string[] }): void; $updateAccesslist(data: { extension: ExtensionIdentifier; enabled: boolean }[]): void; + $updateModelAccesslist(data: { from: ExtensionIdentifier; to: ExtensionIdentifier; enabled: boolean }[]): void; $provideLanguageModelResponse(handle: number, requestId: number, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise; } diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index eae0f221ffa..108a893fb4d 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, ExtensionIdentifierMap, ExtensionIdentifierSet, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { AsyncIterableSource } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; @@ -91,12 +91,14 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { private readonly _proxy: MainThreadChatProviderShape; private readonly _onDidChangeAccess = new Emitter(); + private readonly _onDidChangeModelAccess = new Emitter<{ from: ExtensionIdentifier; to: ExtensionIdentifier }>(); private readonly _onDidChangeProviders = new Emitter(); readonly onDidChangeProviders = this._onDidChangeProviders.event; private readonly _languageModels = new Map(); private readonly _languageModelIds = new Set(); // these are ALL models, not just the one in this EH private readonly _accesslist = new ExtensionIdentifierMap(); + private readonly _modelAccessList = new ExtensionIdentifierMap(); private readonly _pendingRequest = new Map(); @@ -113,11 +115,22 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._onDidChangeProviders.dispose(); } - registerLanguageModel(extension: ExtensionIdentifier, identifier: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata): IDisposable { + registerLanguageModel(extension: IExtensionDescription, identifier: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata): IDisposable { const handle = ExtHostChatProvider._idPool++; - this._languageModels.set(handle, { extension, provider }); - this._proxy.$registerProvider(handle, identifier, { extension, model: metadata.name ?? '' }); + this._languageModels.set(handle, { extension: extension.identifier, provider }); + let auth; + if (metadata.auth) { + auth = { + providerLabel: extension.displayName || extension.name, + accountLabel: typeof metadata.auth === 'object' ? metadata.auth.label : undefined + }; + } + this._proxy.$registerProvider(handle, identifier, { + extension: extension.identifier, + model: metadata.name ?? '', + auth + }); return toDisposable(() => { this._languageModels.delete(handle); @@ -190,7 +203,26 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._onDidChangeAccess.fire(updated); } - async requestLanguageModelAccess(extension: Readonly, languageModelId: string, options?: vscode.LanguageModelAccessOptions): Promise { + $updateModelAccesslist(data: { from: ExtensionIdentifier; to: ExtensionIdentifier; enabled: boolean }[]): void { + const updated = new Array<{ from: ExtensionIdentifier; to: ExtensionIdentifier }>(); + for (const { from, to, enabled } of data) { + const set = this._modelAccessList.get(from) ?? new ExtensionIdentifierSet(); + const oldValue = set.has(to); + if (oldValue !== enabled) { + if (enabled) { + set.add(to); + } else { + set.delete(to); + } + this._modelAccessList.set(from, set); + const newItem = { from, to }; + updated.push(newItem); + this._onDidChangeModelAccess.fire(newItem); + } + } + } + + async requestLanguageModelAccess(extension: IExtensionDescription, languageModelId: string, options?: vscode.LanguageModelAccessOptions): Promise { const from = extension.identifier; // check if the extension is in the access list and allowed to make chat requests if (this._accesslist.get(from) === false) { @@ -206,7 +238,10 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { } throw new Error(`Language model '${languageModelId}' NOT found`); } - await this._checkAuthAccess(extension, languageModelId, justification); + + if (metadata.auth) { + await this._checkAuthAccess(extension, { identifier: metadata.extension, displayName: metadata.auth?.providerLabel }, justification); + } const that = this; @@ -215,15 +250,18 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { return metadata.model; }, get isRevoked() { - return !that._accesslist.get(from) || !that._languageModelIds.has(languageModelId); + return !that._accesslist.get(from) + || (metadata.auth && !that._modelAccessList.get(from)?.has(metadata.extension)) + || !that._languageModelIds.has(languageModelId); }, get onDidChangeAccess() { const onDidChangeAccess = Event.filter(that._onDidChangeAccess.event, set => set.has(from)); const onDidRemoveLM = Event.filter(that._onDidChangeProviders.event, e => e.removed.includes(languageModelId)); - return Event.signal(Event.any(onDidChangeAccess, onDidRemoveLM)); + const onDidChangeModelAccess = Event.filter(that._onDidChangeModelAccess.event, e => ExtensionIdentifier.equals(e.from, from) && ExtensionIdentifier.equals(e.to, metadata.extension)); + return Event.signal(Event.any(onDidChangeAccess, onDidRemoveLM, onDidChangeModelAccess)); }, makeChatRequest(messages, options, token) { - if (!that._accesslist.get(from)) { + if (!that._accesslist.get(from) || (metadata.auth && !that._modelAccessList.get(from)?.has(metadata.extension))) { throw new Error('Access to chat has been revoked'); } if (!that._languageModelIds.has(languageModelId)) { @@ -253,20 +291,22 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { } // BIG HACK: Using AuthenticationProviders to check access to Language Models - private async _checkAuthAccess(from: Readonly, languageModelId: string, detail?: string): Promise { + private async _checkAuthAccess(from: IExtensionDescription, to: { identifier: ExtensionIdentifier; displayName: string }, detail?: string): Promise { // This needs to be done in both MainThread & ExtHost ChatProvider - const providerId = INTERNAL_AUTH_PROVIDER_PREFIX + languageModelId; + const providerId = INTERNAL_AUTH_PROVIDER_PREFIX + to.identifier.value; const session = await this._extHostAuthentication.getSession(from, providerId, [], { silent: true }); if (!session) { try { await this._extHostAuthentication.getSession(from, providerId, [], { forceNewSession: { - detail: detail ?? localize('chatAccess', "To allow access to the '{0}' language model", languageModelId), + detail: detail ?? localize('chatAccess', "To allow access to the language models provided by {0}", to.displayName), } }); } catch (err) { - throw new Error('Access to language model has not been granted'); + throw new Error('Access to language models has not been granted'); } } + + this.$updateModelAccesslist([{ from: from.identifier, to: to.identifier, enabled: true }]); } } diff --git a/src/vs/workbench/contrib/chat/common/chatProvider.ts b/src/vs/workbench/contrib/chat/common/chatProvider.ts index c4655e35665..c393a73de98 100644 --- a/src/vs/workbench/contrib/chat/common/chatProvider.ts +++ b/src/vs/workbench/contrib/chat/common/chatProvider.ts @@ -30,6 +30,10 @@ export interface IChatResponseProviderMetadata { readonly extension: ExtensionIdentifier; readonly model: string; readonly description?: string; + readonly auth?: { + readonly providerLabel: string; + readonly accountLabel?: string; + }; } export interface IChatResponseProvider { diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index 226416480c0..e35fd5547d1 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -26,6 +26,13 @@ declare module 'vscode' { */ // TODO@API rename to model name: string; + + /** + * When present, this gates the use of `requestLanguageModelAccess` behind an authorization flow where + * the user must approve of another extension accessing the models contributed by this extension. + * Additionally, the extension can provide a label that will be shown in the UI. + */ + auth?: true | { label: string }; } export namespace chat { From 7b0e8858b37d05e44907378e5e61fe5f524a198d Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 13 Feb 2024 09:35:47 +0100 Subject: [PATCH 1181/1897] polishing code --- .../bulkEdit/browser/preview/bulkEditPane.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 49cef9c8c8a..3a9b9ebab13 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -98,6 +98,11 @@ export class BulkEditPane extends ViewPane { this._ctxHasCategories = BulkEditPane.ctxHasCategories.bindTo(contextKeyService); this._ctxGroupByFile = BulkEditPane.ctxGroupByFile.bindTo(contextKeyService); this._ctxHasCheckedChanges = BulkEditPane.ctxHasCheckedChanges.bindTo(contextKeyService); + this._disposables.add(this._editorService.onDidCloseEditor((e) => { + if (this._multiDiffEditor && e.editor === this._multiDiffEditor.input) { + this._multiDiffEditor = undefined; + } + })); } override dispose(): void { @@ -268,7 +273,6 @@ export class BulkEditPane extends ViewPane { this._dialogService.warn(message).finally(() => this._done(false)); } - // Going through here to discard discard() { this._done(false); } @@ -278,6 +282,10 @@ export class BulkEditPane extends ViewPane { this._currentInput = undefined; this._setState(State.Message); this._sessionDisposables.clear(); + if (this._multiDiffEditor && this._multiDiffEditor.input && this._multiDiffEditor.group) { + this._editorService.closeEditor({ editor: this._multiDiffEditor.input, groupId: this._multiDiffEditor.group.id }); + } + this._multiDiffEditor = undefined; } toggleChecked() { @@ -375,14 +383,14 @@ export class BulkEditPane extends ViewPane { } const multiDiffSource = URI.from({ scheme: 'refactor-preview', path: JSON.stringify(fileElement.edit.uri) }); const label = 'Refactor Preview'; - this._multiDiffEditor = this._sessionDisposables.add( + this._multiDiffEditor = await this._editorService.openEditor({ - multiDiffSource: multiDiffSource ? URI.revive(multiDiffSource) : undefined, + multiDiffSource: URI.revive(multiDiffSource), resources, label: label, description: label, options: options, - }) as MultiDiffEditor); + }) as MultiDiffEditor; } private _onContextMenu(e: ITreeContextMenuEvent): void { From 45e2e2072fac7eb15804477b725cf3081ecd7bb3 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:53:50 +0100 Subject: [PATCH 1182/1897] Git - fix upstream state management check (#205078) --- extensions/git/src/historyProvider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 80fbbe52799..db987344028 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -81,8 +81,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec // Check if Upstream has changed if (force || this._HEAD?.upstream?.name !== this.repository.HEAD?.upstream?.name || - this._HEAD?.upstream?.remote === this.repository.HEAD?.upstream?.remote || - this._HEAD?.upstream?.commit === this.repository.HEAD?.upstream?.commit) { + this._HEAD?.upstream?.remote !== this.repository.HEAD?.upstream?.remote || + this._HEAD?.upstream?.commit !== this.repository.HEAD?.upstream?.commit) { this.logger.trace('GitHistoryProvider:onDidRunGitStatus - Upstream has changed'); this._onDidChangeCurrentHistoryItemGroupBase.fire(); } From c58252ba7e7cb82f869df43df2c765a8655053c5 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:54:33 +0100 Subject: [PATCH 1183/1897] SCM - Add Incoming/Outgoing menu to the title menu (#205069) SCM Add Incoming/Outgoing menu to the title menu --- .../contrib/scm/browser/scmViewPane.ts | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index ac5b68e12b2..3a92ee61bdc 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1430,6 +1430,7 @@ const enum ViewSortKey { const Menus = { ViewSort: new MenuId('SCMViewSort'), Repositories: new MenuId('SCMRepositories'), + ChangesSettings: new MenuId('SCMChangesSettings'), }; const ContextKeys = { @@ -1451,7 +1452,16 @@ MenuRegistry.appendMenuItem(MenuId.SCMTitle, { title: localize('sortAction', "View & Sort"), submenu: Menus.ViewSort, when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0)), - group: '0_view&sort' + group: '0_view&sort', + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.SCMTitle, { + title: localize('scmChanges', "Incoming & Outgoing"), + submenu: Menus.ChangesSettings, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0)), + group: '0_view&sort', + order: 2 }); MenuRegistry.appendMenuItem(Menus.ViewSort, { @@ -1486,16 +1496,20 @@ MenuRegistry.appendMenuItem(MenuId.SCMChangesSeparator, { order: 1 }); +MenuRegistry.appendMenuItem(Menus.ChangesSettings, { + title: localize('incomingChanges', "Show Incoming Changes"), + submenu: MenuId.SCMIncomingChangesSetting, + group: '1_incoming&outgoing', + order: 1 +}); + registerAction2(class extends SCMChangesSettingAction { constructor() { super('scm.showIncomingChanges', 'always', { id: 'workbench.scm.action.showIncomingChanges.always', title: localize('always', "Always"), - menu: { - id: MenuId.SCMIncomingChangesSetting, - - } + menu: { id: MenuId.SCMIncomingChangesSetting }, }); } }); @@ -1533,6 +1547,13 @@ MenuRegistry.appendMenuItem(MenuId.SCMChangesSeparator, { order: 2 }); +MenuRegistry.appendMenuItem(Menus.ChangesSettings, { + title: localize('outgoingChanges', "Show Outgoing Changes"), + submenu: MenuId.SCMOutgoingChangesSetting, + group: '1_incoming&outgoing', + order: 2 +}); + registerAction2(class extends SCMChangesSettingAction { constructor() { super('scm.showOutgoingChanges', 'always', @@ -1580,10 +1601,10 @@ registerAction2(class extends Action2 { title: localize('showChangesSummary', "Show Changes Summary"), f1: false, toggled: ContextKeyExpr.equals('config.scm.showChangesSummary', true), - menu: { - id: MenuId.SCMChangesSeparator, - order: 3 - } + menu: [ + { id: MenuId.SCMChangesSeparator, order: 3 }, + { id: Menus.ChangesSettings, order: 3 }, + ] }); } From 038226e88363e69f59023c491ce40dfbcbffad55 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 13 Feb 2024 12:02:41 +0100 Subject: [PATCH 1184/1897] Support holding `Cmd+I` in panel chat to dictate (fix #203208) (#205065) --- .../actions/voiceChatActions.ts | 340 +++++++++--------- .../electron-sandbox/chat.contribution.ts | 7 +- .../extensions/browser/extensionEditor.ts | 4 +- .../electron-sandbox/inlineChatActions.ts | 6 +- 4 files changed, 177 insertions(+), 180 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 41083a4bb9b..fecd9449db4 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -11,25 +11,25 @@ import { Codicon } from 'vs/base/common/codicons'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize, localize2 } from 'vs/nls'; -import { Action2, MenuId } from 'vs/platform/actions/common/actions'; +import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { spinningLoading } from 'vs/platform/theme/common/iconRegistry'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidget, IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatService, KEYWORD_ACTIVIATION_SETTING_ID } from 'vs/workbench/contrib/chat/common/chatService'; -import { CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, MENU_INLINE_CHAT_INPUT } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, MENU_INLINE_CHAT_INPUT } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_INPUT, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { KeyCode } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { HasSpeechProvider, ISpeechService, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; -import { RunOnceScheduler } from 'vs/base/common/async'; +import { RunOnceScheduler, disposableTimeout } from 'vs/base/common/async'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ACTIVITY_BAR_BADGE_BACKGROUND } from 'vs/workbench/common/theme'; import { ColorScheme } from 'vs/platform/theme/common/theme'; @@ -51,6 +51,9 @@ import { ProgressLocation } from 'vs/platform/progress/common/progress'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { IVoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); @@ -60,6 +63,8 @@ const CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS = new RawContextKey('inline const CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS = new RawContextKey('voiceChatInViewInProgress', false, { type: 'boolean', description: localize('voiceChatInViewInProgress', "True when voice recording from microphone is in progress in the chat view.") }); const CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS = new RawContextKey('voiceChatInEditorInProgress', false, { type: 'boolean', description: localize('voiceChatInEditorInProgress', "True when voice recording from microphone is in progress in the chat editor.") }); +const CanVoiceChat = ContextKeyExpr.and(CONTEXT_PROVIDER_EXISTS, HasSpeechProvider); + type VoiceChatSessionContext = 'inline' | 'quick' | 'view' | 'editor'; interface IVoiceChatSessionController { @@ -84,6 +89,7 @@ class VoiceChatSessionControllerFactory { static create(accessor: ServicesAccessor, context: 'quick'): Promise; static create(accessor: ServicesAccessor, context: 'view'): Promise; static create(accessor: ServicesAccessor, context: 'focused'): Promise; + static create(accessor: ServicesAccessor, context: 'inline' | 'quick' | 'view' | 'focused'): Promise; static async create(accessor: ServicesAccessor, context: 'inline' | 'quick' | 'view' | 'focused'): Promise { const chatWidgetService = accessor.get(IChatWidgetService); const chatService = accessor.get(IChatService); @@ -129,7 +135,7 @@ class VoiceChatSessionControllerFactory { } // View Chat - if (context === 'view') { + if (context === 'view' || context === 'focused' /* fallback in case 'focused' was not successful */) { const provider = firstOrDefault(chatService.getProviderInfos()); if (provider) { const chatView = await chatWidgetService.revealViewForProvider(provider.id); @@ -220,7 +226,14 @@ class VoiceChatSessionControllerFactory { } } -interface ActiveVoiceChatSession { +interface IVoiceChatSession { + setTimeoutDisabled(disabled: boolean): void; + + accept(): void; + stop(): void; +} + +interface IActiveVoiceChatSession extends IVoiceChatSession { readonly id: number; readonly controller: IVoiceChatSessionController; readonly disposables: DisposableStore; @@ -245,7 +258,7 @@ class VoiceChatSessions { private voiceChatInViewInProgressKey = CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS.bindTo(this.contextKeyService); private voiceChatInEditorInProgressKey = CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS.bindTo(this.contextKeyService); - private currentVoiceChatSession: ActiveVoiceChatSession | undefined = undefined; + private currentVoiceChatSession: IActiveVoiceChatSession | undefined = undefined; private voiceChatSessionIds = 0; constructor( @@ -254,14 +267,19 @@ class VoiceChatSessions { @IConfigurationService private readonly configurationService: IConfigurationService ) { } - async start(controller: IVoiceChatSessionController, context?: IChatExecuteActionContext): Promise { + start(controller: IVoiceChatSessionController, context?: IChatExecuteActionContext): IVoiceChatSession { this.stop(); + let disableTimeout = false; + const sessionId = ++this.voiceChatSessionIds; - const session = this.currentVoiceChatSession = { + const session: IActiveVoiceChatSession = this.currentVoiceChatSession = { id: sessionId, controller, - disposables: new DisposableStore() + disposables: new DisposableStore(), + setTimeoutDisabled: (disabled: boolean) => { disableTimeout = disabled; }, + accept: () => session.controller.acceptInput(), + stop: () => this.stop(sessionId, controller.context) }; const cts = new CancellationTokenSource(); @@ -296,7 +314,7 @@ class VoiceChatSessions { case SpeechToTextStatus.Recognizing: if (text) { session.controller.updateInput(inputValue ? [inputValue, text].join(' ') : text); - if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true) { + if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true && !disableTimeout) { acceptTranscriptionScheduler.cancel(); } } @@ -305,7 +323,7 @@ class VoiceChatSessions { if (text) { inputValue = inputValue ? [inputValue, text].join(' ') : text; session.controller.updateInput(inputValue); - if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true && !waitingForInput) { + if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true && !waitingForInput && !disableTimeout) { acceptTranscriptionScheduler.schedule(); } } @@ -315,6 +333,8 @@ class VoiceChatSessions { break; } })); + + return session; } private onDidSpeechToTextSessionStart(controller: IVoiceChatSessionController, disposables: DisposableStore): void { @@ -383,31 +403,66 @@ class VoiceChatSessions { } } -export class VoiceChatInChatViewAction extends Action2 { +export const VOICE_KEY_HOLD_THRESHOLD = 500; + +async function awaitHoldAndAccept(session: IVoiceChatSession, holdMode?: Promise): Promise { + if (!holdMode) { + return; + } + + let acceptVoice = false; + const handle = disposableTimeout(() => { + acceptVoice = true; + session.setTimeoutDisabled(true); // disable accept on timeout when hold mode runs for 250ms + }, VOICE_KEY_HOLD_THRESHOLD); + + await holdMode; + handle.dispose(); + + if (acceptVoice) { + session.accept(); + } +} + +class VoiceChatWithHoldModeAction extends Action2 { + + constructor(desc: Readonly, private readonly target: 'inline' | 'quick' | 'view') { + super(desc); + } + + async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { + const instantiationService = accessor.get(IInstantiationService); + const keybindingService = accessor.get(IKeybindingService); + + const holdMode = keybindingService.enableKeybindingHoldMode(this.desc.id); + + const controller = await VoiceChatSessionControllerFactory.create(accessor, this.target); + if (!controller) { + return; + } + + const session = VoiceChatSessions.getInstance(instantiationService).start(controller, context); + + awaitHoldAndAccept(session, holdMode); + } +} + +export class VoiceChatInChatViewAction extends VoiceChatWithHoldModeAction { static readonly ID = 'workbench.action.chat.voiceChatInChatView'; constructor() { super({ id: VoiceChatInChatViewAction.ID, - title: localize2('workbench.action.chat.voiceChatInView.label', "Voice Chat in Chat View"), + title: localize2('workbench.action.chat.voiceChatInView.label', "Voice Chat in View"), category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_PROVIDER_EXISTS, CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), + precondition: ContextKeyExpr.and(CanVoiceChat, CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), f1: true - }); - } - - async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { - const instantiationService = accessor.get(IInstantiationService); - - const controller = await VoiceChatSessionControllerFactory.create(accessor, 'view'); - if (controller) { - VoiceChatSessions.getInstance(instantiationService).start(controller, context); - } + }, 'view'); } } -export class InlineVoiceChatAction extends Action2 { +export class InlineVoiceChatAction extends VoiceChatWithHoldModeAction { static readonly ID = 'workbench.action.chat.inlineVoiceChat'; @@ -416,22 +471,13 @@ export class InlineVoiceChatAction extends Action2 { id: InlineVoiceChatAction.ID, title: localize2('workbench.action.chat.inlineVoiceChat', "Inline Voice Chat"), category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_PROVIDER_EXISTS, ActiveEditorContext, CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), + precondition: ContextKeyExpr.and(CanVoiceChat, ActiveEditorContext, CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), f1: true - }); - } - - async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { - const instantiationService = accessor.get(IInstantiationService); - - const controller = await VoiceChatSessionControllerFactory.create(accessor, 'inline'); - if (controller) { - VoiceChatSessions.getInstance(instantiationService).start(controller, context); - } + }, 'inline'); } } -export class QuickVoiceChatAction extends Action2 { +export class QuickVoiceChatAction extends VoiceChatWithHoldModeAction { static readonly ID = 'workbench.action.chat.quickVoiceChat'; @@ -440,18 +486,9 @@ export class QuickVoiceChatAction extends Action2 { id: QuickVoiceChatAction.ID, title: localize2('workbench.action.chat.quickVoiceChat.label', "Quick Voice Chat"), category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_PROVIDER_EXISTS, CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), + precondition: ContextKeyExpr.and(CanVoiceChat, CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), f1: true - }); - } - - async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { - const instantiationService = accessor.get(IInstantiationService); - - const controller = await VoiceChatSessionControllerFactory.create(accessor, 'quick'); - if (controller) { - VoiceChatSessions.getInstance(instantiationService).start(controller, context); - } + }, 'quick'); } } @@ -462,10 +499,26 @@ export class StartVoiceChatAction extends Action2 { constructor() { super({ id: StartVoiceChatAction.ID, - title: localize2('workbench.action.chat.startVoiceChat.label', "Use Microphone"), + title: localize2('workbench.action.chat.startVoiceChat.label', "Start Voice Chat"), category: CHAT_CATEGORY, + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and( + CanVoiceChat, + EditorContextKeys.focus.toNegated(), // do not steal the inline-chat keybinding + CONTEXT_VOICE_CHAT_GETTING_READY.negate(), + CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), + CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.negate(), + CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS.negate(), + CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS.negate(), + CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS.negate(), + CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.negate() + ), + primary: KeyMod.CtrlCmd | KeyCode.KeyI + }, icon: Codicon.mic, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_GETTING_READY.negate(), CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.negate()), + precondition: ContextKeyExpr.and(CanVoiceChat, CONTEXT_VOICE_CHAT_GETTING_READY.negate(), CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.negate()), menu: [{ id: MenuId.ChatExecute, when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS.negate(), CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS.negate(), CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS.negate()), @@ -482,7 +535,9 @@ export class StartVoiceChatAction extends Action2 { async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { const instantiationService = accessor.get(IInstantiationService); - const commandService = accessor.get(ICommandService); + const keybindingService = accessor.get(IKeybindingService); + + const holdMode = keybindingService.enableKeybindingHoldMode(this.desc.id); const widget = context?.widget; if (widget) { @@ -497,12 +552,13 @@ export class StartVoiceChatAction extends Action2 { } const controller = await VoiceChatSessionControllerFactory.create(accessor, 'focused'); - if (controller) { - VoiceChatSessions.getInstance(instantiationService).start(controller, context); - } else { - // fallback to Quick Voice Chat command - commandService.executeCommand(QuickVoiceChatAction.ID, context); + if (!controller) { + return; } + + const session = VoiceChatSessions.getInstance(instantiationService).start(controller, context); + + awaitHoldAndAccept(session, holdMode); } } @@ -517,8 +573,7 @@ export class InstallVoiceChatAction extends Action2 { constructor() { super({ id: InstallVoiceChatAction.ID, - title: localize2('workbench.action.chat.startVoiceChat.label', "Use Microphone"), - f1: false, + title: localize2('workbench.action.chat.startVoiceChat.label', "Start Voice Chat"), category: CHAT_CATEGORY, icon: Codicon.mic, precondition: InstallingSpeechProvider.negate(), @@ -574,147 +629,81 @@ export class InstallVoiceChatAction extends Action2 { } } -export class StopListeningAction extends Action2 { +class BaseStopListeningAction extends Action2 { + + constructor( + desc: { id: string; icon?: ThemeIcon; f1?: boolean }, + private readonly target: 'inline' | 'quick' | 'view' | 'editor' | undefined, + context: RawContextKey, + menu: MenuId | undefined, + group: 'navigation' | 'main' = 'navigation' + ) { + super({ + ...desc, + title: localize2('workbench.action.chat.stopListening.label', "Stop Listening"), + category: CHAT_CATEGORY, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib + 100, + when: ContextKeyExpr.and(CanVoiceChat, context), + primary: KeyCode.Escape + }, + precondition: ContextKeyExpr.and(CanVoiceChat, context), + menu: menu ? [{ + id: menu, + when: ContextKeyExpr.and(CanVoiceChat, context), + group, + order: -1 + }] : undefined + }); + } + + async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { + VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).stop(undefined, this.target); + } +} + +export class StopListeningAction extends BaseStopListeningAction { static readonly ID = 'workbench.action.chat.stopListening'; constructor() { - super({ - id: StopListeningAction.ID, - title: localize2('workbench.action.chat.stopListening.label', "Stop Listening"), - category: CHAT_CATEGORY, - f1: true, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib + 100, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_PROGRESS), - primary: KeyCode.Escape - }, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_PROGRESS) - }); - } - - run(accessor: ServicesAccessor): void { - VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).stop(); + super({ id: StopListeningAction.ID, f1: true }, undefined, CONTEXT_VOICE_CHAT_IN_PROGRESS, undefined); } } -export class StopListeningInChatViewAction extends Action2 { +export class StopListeningInChatViewAction extends BaseStopListeningAction { static readonly ID = 'workbench.action.chat.stopListeningInChatView'; constructor() { - super({ - id: StopListeningInChatViewAction.ID, - title: localize2('workbench.action.chat.stopListeningInChatView.label', "Stop Listening"), - category: CHAT_CATEGORY, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib + 100, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS), - primary: KeyCode.Escape - }, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS), - icon: spinningLoading, - menu: [{ - id: MenuId.ChatExecute, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS), - group: 'navigation', - order: -1 - }] - }); - } - - run(accessor: ServicesAccessor): void { - VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).stop(undefined, 'view'); + super({ id: StopListeningInChatViewAction.ID, icon: spinningLoading }, 'view', CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS, MenuId.ChatExecute); } } -export class StopListeningInChatEditorAction extends Action2 { +export class StopListeningInChatEditorAction extends BaseStopListeningAction { static readonly ID = 'workbench.action.chat.stopListeningInChatEditor'; constructor() { - super({ - id: StopListeningInChatEditorAction.ID, - title: localize2('workbench.action.chat.stopListeningInChatEditor.label', "Stop Listening"), - category: CHAT_CATEGORY, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib + 100, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS), - primary: KeyCode.Escape - }, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS), - icon: spinningLoading, - menu: [{ - id: MenuId.ChatExecute, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS), - group: 'navigation', - order: -1 - }] - }); - } - - run(accessor: ServicesAccessor): void { - VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).stop(undefined, 'editor'); + super({ id: StopListeningInChatEditorAction.ID, icon: spinningLoading }, 'editor', CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS, MenuId.ChatExecute); } } -export class StopListeningInQuickChatAction extends Action2 { +export class StopListeningInQuickChatAction extends BaseStopListeningAction { static readonly ID = 'workbench.action.chat.stopListeningInQuickChat'; constructor() { - super({ - id: StopListeningInQuickChatAction.ID, - title: localize2('workbench.action.chat.stopListeningInQuickChat.label', "Stop Listening"), - category: CHAT_CATEGORY, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib + 100, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS), - primary: KeyCode.Escape - }, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS), - icon: spinningLoading, - menu: [{ - id: MenuId.ChatExecute, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS), - group: 'navigation', - order: -1 - }] - }); - } - - run(accessor: ServicesAccessor): void { - VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).stop(undefined, 'quick'); + super({ id: StopListeningInQuickChatAction.ID, icon: spinningLoading }, 'quick', CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS, MenuId.ChatExecute); } } -export class StopListeningInInlineChatAction extends Action2 { +export class StopListeningInInlineChatAction extends BaseStopListeningAction { static readonly ID = 'workbench.action.chat.stopListeningInInlineChat'; constructor() { - super({ - id: StopListeningInInlineChatAction.ID, - title: localize2('workbench.action.chat.stopListeningInInlineChat.label', "Stop Listening"), - category: CHAT_CATEGORY, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib + 100, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS), - primary: KeyCode.Escape - }, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS), - icon: spinningLoading, - menu: [{ - id: MENU_INLINE_CHAT_INPUT, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS), - group: 'main', - order: -1 - }] - }); - } - - run(accessor: ServicesAccessor): void { - VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).stop(undefined, 'inline'); + super({ id: StopListeningInInlineChatAction.ID, icon: spinningLoading }, 'inline', CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS, MENU_INLINE_CHAT_INPUT, 'main'); } } @@ -728,7 +717,16 @@ export class StopListeningAndSubmitAction extends Action2 { title: localize2('workbench.action.chat.stopListeningAndSubmit.label', "Stop Listening and Submit"), category: CHAT_CATEGORY, f1: true, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_PROGRESS) + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and( + CanVoiceChat, + ContextKeyExpr.or(CTX_INLINE_CHAT_FOCUSED, CONTEXT_IN_CHAT_INPUT), + CONTEXT_VOICE_CHAT_IN_PROGRESS + ), + primary: KeyMod.CtrlCmd | KeyCode.KeyI + }, + precondition: ContextKeyExpr.and(CanVoiceChat, CONTEXT_VOICE_CHAT_IN_PROGRESS) }); } @@ -808,6 +806,8 @@ function supportsKeywordActivation(configurationService: IConfigurationService, export class KeywordActivationContribution extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.keywordActivation'; + static SETTINGS_VALUE = { OFF: 'off', INLINE_CHAT: 'inlineChat', diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts index 127d13be18b..2f69e1b13f8 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts @@ -5,9 +5,7 @@ import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction, KeywordActivationContribution, InstallVoiceChatAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; import { registerAction2 } from 'vs/platform/actions/common/actions'; -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 { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; registerAction2(StartVoiceChatAction); registerAction2(InstallVoiceChatAction); @@ -24,5 +22,4 @@ registerAction2(StopListeningInChatEditorAction); registerAction2(StopListeningInQuickChatAction); registerAction2(StopListeningInInlineChatAction); -const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(KeywordActivationContribution, LifecyclePhase.Restored); +registerWorkbenchContribution2(KeywordActivationContribution.ID, KeywordActivationContribution, WorkbenchPhase.AfterRestored); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 45f5217c9f5..53f55fa804c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -580,7 +580,7 @@ export class ExtensionEditor extends EditorPane { template.navbar.push(ExtensionEditorTab.ExtensionPack, localize('extensionpack', "Extension Pack"), localize('extensionpacktooltip', "Lists extensions those will be installed together with this extension")); } - if ((this.options).tab) { + if ((this.options)?.tab) { template.navbar.switch((this.options).tab!); } if (template.navbar.currentId) { @@ -966,7 +966,7 @@ export class ExtensionEditor extends EditorPane { return null; } - const extensionFeaturesTab = this.contentDisposables.add(this.instantiationService.createInstance(ExtensionFeaturesTab, manifest, (this.options).feature)); + const extensionFeaturesTab = this.contentDisposables.add(this.instantiationService.createInstance(ExtensionFeaturesTab, manifest, (this.options)?.feature)); const layout = () => extensionFeaturesTab.layout(template.content.clientHeight, template.content.clientWidth); const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout }); this.contentDisposables.add(toDisposable(removeLayoutParticipant)); diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts index d30d230a2b7..99862b01d7b 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts @@ -13,7 +13,7 @@ import { disposableTimeout } from 'vs/base/common/async'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { StartVoiceChatAction, StopListeningAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; +import { StartVoiceChatAction, StopListeningAction, VOICE_KEY_HOLD_THRESHOLD } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; import { IChatExecuteActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; import { CTX_INLINE_CHAT_VISIBLE, InlineChatConfigKeys } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { HasSpeechProvider, ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; @@ -62,12 +62,12 @@ function holdForSpeech(accessor: ServicesAccessor, ctrl: InlineChatController, a // start VOICE input commandService.executeCommand(StartVoiceChatAction.ID, { voice: { disableTimeout: true } } satisfies IChatExecuteActionContext); listening = true; - }, 250); + }, VOICE_KEY_HOLD_THRESHOLD); holdMode.finally(() => { if (listening) { commandService.executeCommand(StopListeningAction.ID).finally(() => { - ctrl!.acceptInput(); + ctrl.acceptInput(); }); } handle.dispose(); From f6c0c5e5e26e76939eb9ec79f964073f8dc9da84 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 13 Feb 2024 12:24:29 +0100 Subject: [PATCH 1185/1897] Less text-y settings in release notes (#205081) --- .../browser/markdownSettingRenderer.ts | 190 ++++++++++++------ .../browser/markdownSettingRenderer.test.ts | 113 ++++------- .../update/browser/releaseNotesEditor.ts | 61 +++++- 3 files changed, 221 insertions(+), 143 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index b593321746b..89101b7134b 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -4,24 +4,29 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { ISetting, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; +import { IPreferencesService, ISetting, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; import { settingKeyToDisplayFormat } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { DefaultSettings } from 'vs/workbench/services/preferences/common/preferencesModels'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { IAction } from 'vs/base/common/actions'; const codeSettingRegex = /^/; export class SimpleSettingRenderer { - private defaultSettings: DefaultSettings; - private updatedSettings = new Map(); // setting ID to user's original setting value - private encounteredSettings = new Map(); // setting ID to setting + private _defaultSettings: DefaultSettings; + private _updatedSettings = new Map(); // setting ID to user's original setting value + private _encounteredSettings = new Map(); // setting ID to setting constructor( - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IPreferencesService private readonly _preferencesService: IPreferencesService ) { - this.defaultSettings = new DefaultSettings([], ConfigurationTarget.USER); + this._defaultSettings = new DefaultSettings([], ConfigurationTarget.USER); } getHtmlRenderer(): (html: string) => string { @@ -38,19 +43,23 @@ export class SimpleSettingRenderer { }; } + settingToUriString(settingId: string, value?: any): string { + return `${Schemas.codeSetting}://${settingId}${value ? `/${value}` : ''}`; + } + private settingsGroups: ISettingsGroup[] | undefined = undefined; private getSetting(settingId: string): ISetting | undefined { if (!this.settingsGroups) { - this.settingsGroups = this.defaultSettings.getSettingsGroups(); + this.settingsGroups = this._defaultSettings.getSettingsGroups(); } - if (this.encounteredSettings.has(settingId)) { - return this.encounteredSettings.get(settingId); + if (this._encounteredSettings.has(settingId)) { + return this._encounteredSettings.get(settingId); } for (const group of this.settingsGroups) { for (const section of group.sections) { for (const setting of section.settings) { if (setting.key === settingId) { - this.encounteredSettings.set(settingId, setting); + this._encounteredSettings.set(settingId, setting); return setting; } } @@ -88,100 +97,151 @@ export class SimpleSettingRenderer { return this.renderSetting(setting, newValue); } - private viewInSettings(settingId: string, alreadySet: boolean): string { - let message: string; - if (alreadySet) { - const displayName = settingKeyToDisplayFormat(settingId); - message = nls.localize('viewInSettingsDetailed', "View \"{0}: {1}\" in Settings", displayName.category, displayName.label); + private viewInSettingsMessage(settingId: string, alreadyDisplayed: boolean) { + if (alreadyDisplayed) { + return nls.localize('viewInSettings', "View in Settings"); } else { - message = nls.localize('viewInSettings', "View in Settings"); + const displayName = settingKeyToDisplayFormat(settingId); + return nls.localize('viewInSettingsDetailed', "View \"{0}: {1}\" in Settings", displayName.category, displayName.label); } - return `${message}`; } - private renderRestorePreviousSetting(settingId: string): string { + private restorePreviousSettingMessage(settingId: string): string { const displayName = settingKeyToDisplayFormat(settingId); - const value = this.updatedSettings.get(settingId); - const message = nls.localize('restorePreviousValue', "Restore value of \"{0}: {1}\"", displayName.category, displayName.label); - return `${message}`; + return nls.localize('restorePreviousValue', "Restore value of \"{0}: {1}\"", displayName.category, displayName.label); } - private renderBooleanSetting(setting: ISetting, value: string): string | undefined { - const booleanValue: boolean = value === 'true' ? true : false; - const currentValue = this.configurationService.getValue(setting.key); + private booleanSettingMessage(setting: ISetting, booleanValue: boolean): string | undefined { + const currentValue = this._configurationService.getValue(setting.key); if (currentValue === booleanValue || (currentValue === undefined && setting.value === booleanValue)) { return undefined; } const displayName = settingKeyToDisplayFormat(setting.key); - let message: string; if (booleanValue) { - message = nls.localize('trueMessage', "Enable \"{0}: {1}\" now", displayName.category, displayName.label); + return nls.localize('trueMessage', "Enable \"{0}: {1}\"", displayName.category, displayName.label); } else { - message = nls.localize('falseMessage', "Disable \"{0}: {1}\" now", displayName.category, displayName.label); + return nls.localize('falseMessage', "Disable \"{0}: {1}\"", displayName.category, displayName.label); } - return `${message}`; } - private renderStringSetting(setting: ISetting, value: string): string | undefined { - const currentValue = this.configurationService.getValue(setting.key); - if (currentValue === value || (currentValue === undefined && setting.value === value)) { + private stringSettingMessage(setting: ISetting, stringValue: string): string | undefined { + const currentValue = this._configurationService.getValue(setting.key); + if (currentValue === stringValue || (currentValue === undefined && setting.value === stringValue)) { return undefined; } const displayName = settingKeyToDisplayFormat(setting.key); - const message = nls.localize('stringValue', "Set \"{0}: {1}\" to \"{2}\" now", displayName.category, displayName.label, value); - return `${message}`; + return nls.localize('stringValue', "Set \"{0}: {1}\" to \"{2}\"", displayName.category, displayName.label, stringValue); } - private renderNumberSetting(setting: ISetting, value: string): string | undefined { - const numberValue: number = parseInt(value, 10); - const currentValue = this.configurationService.getValue(setting.key); + private numberSettingMessage(setting: ISetting, numberValue: number): string | undefined { + const currentValue = this._configurationService.getValue(setting.key); if (currentValue === numberValue || (currentValue === undefined && setting.value === numberValue)) { return undefined; } const displayName = settingKeyToDisplayFormat(setting.key); - const message = nls.localize('numberValue', "Set \"{0}: {1}\" to {2} now", displayName.category, displayName.label, numberValue); - return `${message}`; + return nls.localize('numberValue', "Set \"{0}: {1}\" to {2}", displayName.category, displayName.label, numberValue); } private renderSetting(setting: ISetting, newValue: string | undefined): string | undefined { - let renderedSetting: string | undefined; - - if (newValue !== undefined) { - if (this.updatedSettings.has(setting.key)) { - renderedSetting = this.renderRestorePreviousSetting(setting.key); - } else if (setting.type === 'boolean') { - renderedSetting = this.renderBooleanSetting(setting, newValue); - } else if (setting.type === 'string') { - renderedSetting = this.renderStringSetting(setting, newValue); - } else if (setting.type === 'number') { - renderedSetting = this.renderNumberSetting(setting, newValue); - } - } - - if (!renderedSetting) { - return `(${this.viewInSettings(setting.key, true)})`; - } - - return nls.localize({ key: 'fullRenderedSetting', comment: ['A pair of already localized links. The first argument is a link to change a setting, the second is a link to view the setting.'] }, - "({0} | {1})", renderedSetting, this.viewInSettings(setting.key, false),); + const href = this.settingToUriString(setting.key, newValue); + const title = nls.localize('changeSettingTitle', "Try feature"); + return ``; } - async updateSettingValue(uri: URI) { + private getSettingMessage(setting: ISetting, newValue: boolean | string | number): string | undefined { + if (setting.type === 'boolean') { + return this.booleanSettingMessage(setting, newValue as boolean); + } else if (setting.type === 'string') { + return this.stringSettingMessage(setting, newValue as string); + } else if (setting.type === 'number') { + return this.numberSettingMessage(setting, newValue as number); + } + return undefined; + } + + async restoreSetting(settingId: string): Promise { + const userOriginalSettingValue = this._updatedSettings.get(settingId); + this._updatedSettings.delete(settingId); + return this._configurationService.updateValue(settingId, userOriginalSettingValue, ConfigurationTarget.USER); + } + + async setSetting(settingId: string, currentSettingValue: any, newSettingValue: any): Promise { + this._updatedSettings.set(settingId, currentSettingValue); + return this._configurationService.updateValue(settingId, newSettingValue, ConfigurationTarget.USER); + } + + getActions(uri: URI) { if (uri.scheme !== Schemas.codeSetting) { return; } + + const actions: IAction[] = []; + const settingId = uri.authority; const newSettingValue = this.parseValue(uri.authority, uri.path.substring(1)); - const oldSettingValue = this.configurationService.inspect(settingId).userValue; - if (newSettingValue === this.updatedSettings.get(settingId)) { - this.updatedSettings.delete(settingId); - } else { - this.updatedSettings.set(settingId, oldSettingValue); + const currentSettingValue = this._configurationService.inspect(settingId).userValue; + + if (newSettingValue && newSettingValue === currentSettingValue && this._updatedSettings.has(settingId)) { + const restoreMessage = this.restorePreviousSettingMessage(settingId); + actions.push({ + class: undefined, + id: 'restoreSetting', + enabled: true, + tooltip: restoreMessage, + label: restoreMessage, + run: () => { + return this.restoreSetting(settingId); + } + }); + } else if (newSettingValue) { + const setting = this.getSetting(settingId); + const trySettingMessage = setting ? this.getSettingMessage(setting, newSettingValue) : undefined; + + if (setting && trySettingMessage) { + actions.push({ + class: undefined, + id: 'trySetting', + enabled: currentSettingValue !== newSettingValue, + tooltip: trySettingMessage, + label: trySettingMessage, + run: () => { + this.setSetting(settingId, currentSettingValue, newSettingValue); + } + }); + } } - await this.configurationService.updateValue(settingId, newSettingValue, ConfigurationTarget.USER); + + const viewInSettingsMessage = this.viewInSettingsMessage(settingId, actions.length > 0); + actions.push({ + class: undefined, + enabled: true, + id: 'viewInSettings', + tooltip: viewInSettingsMessage, + label: viewInSettingsMessage, + run: () => { + return this._preferencesService.openApplicationSettings({ query: `@id:${settingId}` }); + } + }); + + return actions; + } + + async updateSetting(uri: URI, x: number, y: number) { + const actions = this.getActions(uri); + if (!actions) { + return; + } + + this._contextMenuService.showContextMenu({ + getAnchor: () => ({ x, y }), + getActions: () => actions, + getActionViewItem: (action) => { + return new ActionViewItem(action, action, { label: true }); + }, + }); } } diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index bc215847e49..15db5eac979 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -3,13 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Schemas } from 'vs/base/common/network'; +import { IAction } from 'vs/base/common/actions'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Registry } from 'vs/platform/registry/common/platform'; import { SimpleSettingRenderer } from 'vs/workbench/contrib/markdown/browser/markdownSettingRenderer'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; const configuration: IConfigurationNode = { 'id': 'examples', @@ -50,98 +52,63 @@ suite('Markdown Setting Renderer Test', () => { ensureNoDisposablesAreLeakedInTestSuite(); let configurationService: TestConfigurationService; + let preferencesService: IPreferencesService; + let contextMenuService: IContextMenuService; let settingRenderer: SimpleSettingRenderer; suiteSetup(() => { configurationService = new MarkdownConfigurationService(); + preferencesService = {}; + contextMenuService = {}; Registry.as(Extensions.Configuration).registerConfiguration(configuration); - settingRenderer = new SimpleSettingRenderer(configurationService); + settingRenderer = new SimpleSettingRenderer(configurationService, contextMenuService, preferencesService); }); suiteTeardown(() => { Registry.as(Extensions.Configuration).deregisterConfigurations([configuration]); }); - test('render boolean setting', () => { + test('render code setting button with value', () => { const htmlRenderer = settingRenderer.getHtmlRenderer(); const htmlNoValue = ''; const renderedHtmlNoValue = htmlRenderer(htmlNoValue); - assert.equal(renderedHtmlNoValue, - `(View "Example: Boolean Setting" in Settings)`); - - const htmlWithValue = ''; - const renderedHtmlWithValue = htmlRenderer(htmlWithValue); - assert.equal(renderedHtmlWithValue, - `(Enable "Example: Boolean Setting" now | View in Settings)`); - - const htmlWithValueSetToFalse = ''; - const renderedHtmlWithValueSetToFalse = htmlRenderer(htmlWithValueSetToFalse); - assert.equal(renderedHtmlWithValueSetToFalse, - `(Disable "Example: Boolean Setting2" now | View in Settings)`); - - const htmlSameValue = ''; - const renderedHtmlSameValue = htmlRenderer(htmlSameValue); - assert.equal(renderedHtmlSameValue, - `(View "Example: Boolean Setting" in Settings)`); + assert.strictEqual(renderedHtmlNoValue, + ``); }); - test('render string setting', () => { - const htmlRenderer = settingRenderer.getHtmlRenderer(); - const htmlNoValue = ''; - const renderedHtmlNoValue = htmlRenderer(htmlNoValue); - assert.equal(renderedHtmlNoValue, - `(View "Example: String Setting" in Settings)`); - - const htmlWithValue = ''; - const renderedHtmlWithValue = htmlRenderer(htmlWithValue); - assert.equal(renderedHtmlWithValue, - `(Set "Example: String Setting" to "two" now | View in Settings)`); - - const htmlSameValue = ''; - const renderedHtmlSameValue = htmlRenderer(htmlSameValue); - assert.equal(renderedHtmlSameValue, - `(View "Example: String Setting" in Settings)`); + test('actions with no value', () => { + const uri = URI.parse(settingRenderer.settingToUriString('example.booleanSetting')); + const actions = settingRenderer.getActions(uri); + assert.strictEqual(actions?.length, 1); + assert.strictEqual(actions[0].label, 'View "Example: Boolean Setting" in Settings'); }); - test('render number setting', () => { - const htmlRenderer = settingRenderer.getHtmlRenderer(); - const htmlNoValue = ''; - const renderedHtmlNoValue = htmlRenderer(htmlNoValue); - assert.equal(renderedHtmlNoValue, - `(View "Example: Number Setting" in Settings)`); - - const htmlWithValue = ''; - const renderedHtmlWithValue = htmlRenderer(htmlWithValue); - assert.equal(renderedHtmlWithValue, - `(Set "Example: Number Setting" to 2 now | View in Settings)`); - - const htmlSameValue = ''; - const renderedHtmlSameValue = htmlRenderer(htmlSameValue); - assert.equal(renderedHtmlSameValue, - `(View "Example: Number Setting" in Settings)`); - }); - - test('updating and restoring the setting through the renderer changes what is rendered', async () => { + test('actions with value + updating and restoring', async () => { await configurationService.setUserConfiguration('example', { stringSetting: 'two' }); - const htmlRenderer = settingRenderer.getHtmlRenderer(); - const htmlWithValue = ''; - const renderedHtmlWithValue = htmlRenderer(htmlWithValue); - assert.equal(renderedHtmlWithValue, - `(Set "Example: String Setting" to "three" now | View in Settings)`); - assert.equal(configurationService.getValue('example.stringSetting'), 'two'); + const uri = URI.parse(settingRenderer.settingToUriString('example.stringSetting', 'three')); - // Update the value - await settingRenderer.updateSettingValue(URI.parse(`${Schemas.codeSetting}://example.stringSetting/three`)); - assert.equal(configurationService.getValue('example.stringSetting'), 'three'); - const renderedHtmlWithValueAfterUpdate = htmlRenderer(htmlWithValue); - assert.equal(renderedHtmlWithValueAfterUpdate, - `(Restore value of "Example: String Setting" | View in Settings)`); + const verifyOriginalState = (actions: IAction[] | undefined): actions is IAction[] => { + assert.strictEqual(actions?.length, 2); + assert.strictEqual(actions[0].label, 'Set "Example: String Setting" to "three"'); + assert.strictEqual(actions[1].label, 'View in Settings'); + assert.strictEqual(configurationService.getValue('example.stringSetting'), 'two'); + return true; + }; - // Restore the value - await settingRenderer.updateSettingValue(URI.parse(`${Schemas.codeSetting}://example.stringSetting/two`)); - assert.equal(configurationService.getValue('example.stringSetting'), 'two'); - const renderedHtmlWithValueAfterRestore = htmlRenderer(htmlWithValue); - assert.equal(renderedHtmlWithValueAfterRestore, - `(Set "Example: String Setting" to "three" now | View in Settings)`); + const actions = settingRenderer.getActions(uri); + if (verifyOriginalState(actions)) { + // Update the value + await actions[0].run(); + assert.strictEqual(configurationService.getValue('example.stringSetting'), 'three'); + const actionsUpdated = settingRenderer.getActions(uri); + assert.strictEqual(actionsUpdated?.length, 2); + assert.strictEqual(actionsUpdated[0].label, 'Restore value of "Example: String Setting"'); + assert.strictEqual(actions[1].label, 'View in Settings'); + assert.strictEqual(configurationService.getValue('example.stringSetting'), 'three'); + + // Restore the value + await actionsUpdated[0].run(); + verifyOriginalState(settingRenderer.getActions(uri)); + } }); }); diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 27846ded77b..f03e00087d3 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -116,6 +116,8 @@ export class ReleaseNotesManager { this._configurationService.updateValue('update.showReleaseNotes', e.message.value); } else if (e.message.type === 'scroll') { this.scrollPosition = e.message.value.scrollPosition; + } else if (e.message.type === 'clickSetting') { + this._simpleSettingRenderer.updateSetting(URI.parse(e.message.value.uri), e.message.value.x, e.message.value.y); } })); @@ -220,8 +222,7 @@ export class ReleaseNotesManager { private async onDidClickLink(uri: URI) { if (uri.scheme === Schemas.codeSetting) { - await this._simpleSettingRenderer.updateSettingValue(uri); - this.updateHtml(); + // handled in receive message } else { this.addGAParameters(uri, 'ReleaseNotes') .then(updated => this._openerService.open(updated, { allowCommands: ['workbench.action.openSettings'] })) @@ -254,6 +255,49 @@ export class ReleaseNotesManager { @@ -303,6 +347,13 @@ export class ReleaseNotesManager { }); }; + window.addEventListener('click', event => { + const href = event.target.href ?? event.target.parentElement.href ?? event.target.parentElement.parentElement?.href; + if (href && href.startsWith('${Schemas.codeSetting}')) { + vscode.postMessage({ type: 'clickSetting', value: { uri: href, x: event.screenX, y: event.screenY }}); + } + }); + input.addEventListener('change', event => { vscode.postMessage({ type: 'showReleaseNotes', value: input.checked }, '*'); }); @@ -313,17 +364,17 @@ export class ReleaseNotesManager { private onDidChangeConfiguration(e: IConfigurationChangeEvent): void { if (e.affectsConfiguration('update.showReleaseNotes')) { - this.updateWebview(); + this.updateCheckboxWebview(); } } private onDidChangeActiveWebviewEditor(input: WebviewInput | undefined): void { if (input && input === this._currentReleaseNotes) { - this.updateWebview(); + this.updateCheckboxWebview(); } } - private updateWebview() { + private updateCheckboxWebview() { if (this._currentReleaseNotes) { this._currentReleaseNotes.webview.postMessage({ type: 'showReleaseNotes', From f1b761fea526ae882891710a3c73bb3852f36ced Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 13 Feb 2024 12:18:54 +0100 Subject: [PATCH 1186/1897] voice - reuse `startVoiceChatWithHoldMode` --- .../actions/voiceChatActions.ts | 47 +++++++------------ 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index fecd9449db4..75867e30b4b 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -405,10 +405,11 @@ class VoiceChatSessions { export const VOICE_KEY_HOLD_THRESHOLD = 500; -async function awaitHoldAndAccept(session: IVoiceChatSession, holdMode?: Promise): Promise { - if (!holdMode) { - return; - } +async function startVoiceChatWithHoldMode(id: string, accessor: ServicesAccessor, target: 'inline' | 'quick' | 'view' | 'focused', context?: IChatExecuteActionContext): Promise { + const instantiationService = accessor.get(IInstantiationService); + const keybindingService = accessor.get(IKeybindingService); + + const holdMode = keybindingService.enableKeybindingHoldMode(id); let acceptVoice = false; const handle = disposableTimeout(() => { @@ -416,6 +417,14 @@ async function awaitHoldAndAccept(session: IVoiceChatSession, holdMode?: Promise session.setTimeoutDisabled(true); // disable accept on timeout when hold mode runs for 250ms }, VOICE_KEY_HOLD_THRESHOLD); + const controller = await VoiceChatSessionControllerFactory.create(accessor, target); + if (!controller) { + handle.dispose(); + return; + } + + const session = VoiceChatSessions.getInstance(instantiationService).start(controller, context); + await holdMode; handle.dispose(); @@ -430,20 +439,8 @@ class VoiceChatWithHoldModeAction extends Action2 { super(desc); } - async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { - const instantiationService = accessor.get(IInstantiationService); - const keybindingService = accessor.get(IKeybindingService); - - const holdMode = keybindingService.enableKeybindingHoldMode(this.desc.id); - - const controller = await VoiceChatSessionControllerFactory.create(accessor, this.target); - if (!controller) { - return; - } - - const session = VoiceChatSessions.getInstance(instantiationService).start(controller, context); - - awaitHoldAndAccept(session, holdMode); + run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { + return startVoiceChatWithHoldMode(this.desc.id, accessor, this.target, context); } } @@ -534,11 +531,6 @@ export class StartVoiceChatAction extends Action2 { } async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { - const instantiationService = accessor.get(IInstantiationService); - const keybindingService = accessor.get(IKeybindingService); - - const holdMode = keybindingService.enableKeybindingHoldMode(this.desc.id); - const widget = context?.widget; if (widget) { // if we already get a context when the action is executed @@ -551,14 +543,7 @@ export class StartVoiceChatAction extends Action2 { widget.focusInput(); } - const controller = await VoiceChatSessionControllerFactory.create(accessor, 'focused'); - if (!controller) { - return; - } - - const session = VoiceChatSessions.getInstance(instantiationService).start(controller, context); - - awaitHoldAndAccept(session, holdMode); + return startVoiceChatWithHoldMode(this.desc.id, accessor, 'focused', context); } } From f28c9e1cb6c8fe8e6bece5e6519c1a3e804925d9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 13 Feb 2024 12:27:34 +0100 Subject: [PATCH 1187/1897] voice - allow pause between agent and slash command --- .../contrib/chat/common/voiceChat.ts | 15 ++++---- .../chat/test/common/voiceChat.test.ts | 37 +++++++++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index ff8679fa3ea..b1d1d468ea1 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -118,11 +118,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { return phrases; } - private toText(value: IPhraseValue, type: PhraseTextType, options: IVoiceChatSessionOptions): string { - if (type === PhraseTextType.COMMAND && options.usesAgents) { - type = PhraseTextType.AGENT_AND_COMMAND; // rewrite `/fix` to `@workspace /foo` in this case - } - + private toText(value: IPhraseValue, type: PhraseTextType): string { switch (type) { case PhraseTextType.AGENT: return `${VoiceChatService.AGENT_PREFIX}${value.agent}`; @@ -158,7 +154,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { if (options.usesAgents && startsWithAgent && !detectedAgent && !detectedSlashCommand && originalWords.length >= 4) { const phrase = this.phrases.get(originalWords.slice(0, 4).map(word => this.normalizeWord(word)).join(' ')); if (phrase) { - transformedWords = [this.toText(phrase, PhraseTextType.AGENT_AND_COMMAND, options), ...originalWords.slice(4)]; + transformedWords = [this.toText(phrase, PhraseTextType.AGENT_AND_COMMAND), ...originalWords.slice(4)]; waitingForInput = originalWords.length === 4; @@ -173,7 +169,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { if (options.usesAgents && startsWithAgent && !detectedAgent && !transformedWords && originalWords.length >= 2) { const phrase = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); if (phrase) { - transformedWords = [this.toText(phrase, PhraseTextType.AGENT, options), ...originalWords.slice(2)]; + transformedWords = [this.toText(phrase, PhraseTextType.AGENT), ...originalWords.slice(2)]; waitingForInput = originalWords.length === 2; @@ -187,7 +183,10 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { if (startsWithSlashCommand && !detectedSlashCommand && !transformedWords && originalWords.length >= 2) { const phrase = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); if (phrase) { - transformedWords = [this.toText(phrase, PhraseTextType.COMMAND, options), ...originalWords.slice(2)]; + transformedWords = [this.toText(phrase, options.usesAgents && !detectedAgent ? + PhraseTextType.AGENT_AND_COMMAND : // rewrite `/fix` to `@workspace /foo` in this case + PhraseTextType.COMMAND // when we have not yet detected an agent before + ), ...originalWords.slice(2)]; waitingForInput = originalWords.length === 2; diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index bfa4baf30e9..6751d47ede4 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -250,6 +250,43 @@ suite('VoiceChat', () => { assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, options.usesAgents ? '@workspace for at workspace' : 'At workspace, for at workspace'); assert.strictEqual(event?.waitingForInput, false); + + // Slash command detected after agent recognized + if (options.usesAgents) { + createSession(options); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@workspace'); + assert.strictEqual(event?.waitingForInput, true); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'slash' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, 'slash'); + assert.strictEqual(event?.waitingForInput, false); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'slash fix' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '/fix'); + assert.strictEqual(event?.waitingForInput, true); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'slash fix' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '/fix'); + assert.strictEqual(event?.waitingForInput, true); + + createSession(options); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@workspace'); + assert.strictEqual(event?.waitingForInput, true); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'slash fix' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '/fix'); + assert.strictEqual(event?.waitingForInput, true); + } } test('waiting for input', async () => { From c5d0386f54cc012c76c914214aa02592d0899958 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 13 Feb 2024 12:54:37 +0100 Subject: [PATCH 1188/1897] some API todos (#205095) --- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 5a667140993..1a48b381952 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -41,6 +41,8 @@ declare module 'vscode' { /** * The ID of the chat agent to which this request was directed. */ + // TODO@API NAME agent + // TODO@API TYPE {agent:string, extension:string} readonly agentId: string; /** @@ -286,7 +288,7 @@ declare module 'vscode' { // TODO@API // notify(request: ChatResponsePart, reference: string): boolean; // BETTER - // requestResponseStream(callback: (stream: ChatAgentResponseStream) => void, why?: string): void; + // requestResponseStream(result: ChatAgentResult, callback: (stream: ChatAgentResponseStream) => void, why?: string): void; // TODO@API // clear NEVER happens @@ -367,6 +369,7 @@ declare module 'vscode' { * @param value A plain text value. * @returns This stream. */ + // TODO@API remove? text(value: string): ChatAgentResponseStream; /** @@ -377,6 +380,7 @@ declare module 'vscode' { * @param value A markdown string or a string that should be interpreted as markdown. * @returns This stream. */ + // TODO@API NAME: content markdown(value: string | MarkdownString): ChatAgentResponseStream; /** From 4d542ef5d144449f1c399dc5c06cbf296380b7fe Mon Sep 17 00:00:00 2001 From: gjsjohnmurray Date: Tue, 13 Feb 2024 13:55:49 +0000 Subject: [PATCH 1189/1897] Make channel log level settable from output view --- .../contrib/logs/common/logs.contribution.ts | 4 +- .../contrib/logs/common/logsActions.ts | 17 ++++++--- .../output/browser/output.contribution.ts | 37 ++++++++++++++++++- .../contrib/output/browser/outputServices.ts | 9 ++++- .../services/output/common/output.ts | 2 + 5 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index 19257411641..2b95a4341ce 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -36,8 +36,8 @@ registerAction2(class extends Action2 { f1: true }); } - run(servicesAccessor: ServicesAccessor): Promise { - return servicesAccessor.get(IInstantiationService).createInstance(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.TITLE.value).run(); + run(servicesAccessor: ServicesAccessor, channelId?: string): Promise { + return servicesAccessor.get(IInstantiationService).createInstance(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.TITLE.value).run(channelId); } }); diff --git a/src/vs/workbench/contrib/logs/common/logsActions.ts b/src/vs/workbench/contrib/logs/common/logsActions.ts index 519fab7f93a..f295d14a03c 100644 --- a/src/vs/workbench/contrib/logs/common/logsActions.ts +++ b/src/vs/workbench/contrib/logs/common/logsActions.ts @@ -12,7 +12,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { dirname, basename, isEqual } from 'vs/base/common/resources'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IOutputService } from 'vs/workbench/services/output/common/output'; +import { IOutputChannelDescriptor, IOutputService } from 'vs/workbench/services/output/common/output'; import { extensionTelemetryLogChannelId, telemetryLogId } from 'vs/platform/telemetry/common/telemetryUtils'; import { IDefaultLogLevelsService } from 'vs/workbench/contrib/logs/common/defaultLogLevels'; import { Codicon } from 'vs/base/common/codicons'; @@ -36,8 +36,8 @@ export class SetLogLevelAction extends Action { super(id, label); } - override async run(): Promise { - const logLevelOrChannel = await this.selectLogLevelOrChannel(); + override async run(channelId?: string): Promise { + const logLevelOrChannel = await this.getLogLevelOrChannel(channelId); if (logLevelOrChannel !== null) { if (isLogLevel(logLevelOrChannel)) { this.loggerService.setLogLevel(logLevelOrChannel); @@ -47,16 +47,19 @@ export class SetLogLevelAction extends Action { } } - private async selectLogLevelOrChannel(): Promise { + private async getLogLevelOrChannel(channelId?: string): Promise { const defaultLogLevels = await this.defaultLogLevelsService.getDefaultLogLevels(); const extensionLogs: LogChannelQuickPickItem[] = [], logs: LogChannelQuickPickItem[] = []; const logLevel = this.loggerService.getLogLevel(); for (const channel of this.outputService.getChannelDescriptors()) { - if (!channel.log || !channel.file || channel.id === telemetryLogId || channel.id === extensionTelemetryLogChannelId) { + if (!SetLogLevelAction.isLevelSettable(channel) || !channel.file) { continue; } const channelLogLevel = this.loggerService.getLogLevel(channel.file) ?? logLevel; const item: LogChannelQuickPickItem = { id: channel.id, resource: channel.file, label: channel.label, description: channelLogLevel !== logLevel ? this.getLabel(channelLogLevel) : undefined, extensionId: channel.extensionId }; + if (channelId && channel.id === channelId) { + return item; + } if (channel.extensionId) { extensionLogs.push(item); } else { @@ -96,6 +99,10 @@ export class SetLogLevelAction extends Action { }); } + static isLevelSettable(channel: IOutputChannelDescriptor): boolean { + return channel.log && channel.file !== undefined && channel.id !== telemetryLogId && channel.id !== extensionTelemetryLogChannelId; + } + private async setLogLevelForChannel(logChannel: LogChannelQuickPickItem): Promise { const defaultLogLevels = await this.defaultLogLevelsService.getDefaultLogLevels(); const defaultLogLevel = defaultLogLevels.extensions.find(e => e[0] === logChannel.extensionId?.toLowerCase())?.[1] ?? defaultLogLevels.default; diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 794a8e60d57..6ef9f9764ea 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { MenuId, registerAction2, Action2, MenuRegistry } from 'vs/platform/actions/common/actions'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { OutputService } from 'vs/workbench/contrib/output/browser/outputServices'; -import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_MODE_ID, LOG_MIME, CONTEXT_ACTIVE_FILE_OUTPUT, CONTEXT_OUTPUT_SCROLL_LOCK, IOutputChannelDescriptor, IFileOutputChannelDescriptor, ACTIVE_OUTPUT_CHANNEL_CONTEXT, IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output'; +import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_MODE_ID, LOG_MIME, CONTEXT_ACTIVE_FILE_OUTPUT, CONTEXT_OUTPUT_SCROLL_LOCK, IOutputChannelDescriptor, IFileOutputChannelDescriptor, ACTIVE_OUTPUT_CHANNEL_CONTEXT, CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE, IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output'; import { OutputViewPane } from 'vs/workbench/contrib/output/browser/outputView'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -30,6 +30,8 @@ import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { SetLogLevelAction } from 'vs/workbench/contrib/logs/common/logsActions'; // Register Service registerSingleton(IOutputService, OutputService, InstantiationType.Delayed); @@ -99,6 +101,7 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { this.registerOpenActiveOutputFileInAuxWindowAction(); this.registerShowLogsAction(); this.registerOpenLogFileAction(); + this.registerConfigureActiveOutputLogLevelAction(); } private registerSwitchOutputAction(): void { @@ -334,6 +337,38 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { return null; } + private registerConfigureActiveOutputLogLevelAction(): void { + const that = this; + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.action.configureActiveOutputLogLevel`, + title: nls.localize2('configureActiveOutputLogLevel', "Configure Log Level..."), + menu: [{ + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', OUTPUT_VIEW_ID), + group: 'navigation', + order: 6, + isHiddenByDefault: true + }], + icon: Codicon.gear, + precondition: CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE + }); + } + async run(accessor: ServicesAccessor): Promise { + const commandService = accessor.get(ICommandService); + that.configureActiveOutputLogLevel(commandService); + } + })); + } + + private async configureActiveOutputLogLevel(commandService: ICommandService): Promise { + const channel = this.outputService.getActiveChannel(); + if (channel) { + await commandService.executeCommand(SetLogLevelAction.ID, channel.id); + } + } + private registerShowLogsAction(): void { this._register(registerAction2(class extends Action2 { constructor() { diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index 26fed6ffc4e..e004aaa86bc 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -9,7 +9,7 @@ import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IOutputChannel, IOutputService, OUTPUT_VIEW_ID, OUTPUT_SCHEME, LOG_MIME, OUTPUT_MIME, OutputChannelUpdateMode, IOutputChannelDescriptor, Extensions, IOutputChannelRegistry, ACTIVE_OUTPUT_CHANNEL_CONTEXT, CONTEXT_ACTIVE_FILE_OUTPUT } from 'vs/workbench/services/output/common/output'; +import { IOutputChannel, IOutputService, OUTPUT_VIEW_ID, OUTPUT_SCHEME, LOG_MIME, OUTPUT_MIME, OutputChannelUpdateMode, IOutputChannelDescriptor, Extensions, IOutputChannelRegistry, ACTIVE_OUTPUT_CHANNEL_CONTEXT, CONTEXT_ACTIVE_FILE_OUTPUT, CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE } from 'vs/workbench/services/output/common/output'; import { OutputLinkProvider } from 'vs/workbench/contrib/output/browser/outputLinkProvider'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { ITextModel } from 'vs/editor/common/model'; @@ -21,6 +21,7 @@ import { OutputViewPane } from 'vs/workbench/contrib/output/browser/outputView'; import { IOutputChannelModelService } from 'vs/workbench/contrib/output/common/outputChannelModelService'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { SetLogLevelAction } from 'vs/workbench/contrib/logs/common/logsActions'; const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel'; @@ -74,6 +75,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo private readonly activeOutputChannelContext: IContextKey; private readonly activeFileOutputChannelContext: IContextKey; + private readonly activeOutputChannelLevelSettableContext: IContextKey; constructor( @IStorageService private readonly storageService: IStorageService, @@ -91,6 +93,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo this._register(this.onActiveOutputChannel(channel => this.activeOutputChannelContext.set(channel))); this.activeFileOutputChannelContext = CONTEXT_ACTIVE_FILE_OUTPUT.bindTo(contextKeyService); + this.activeOutputChannelLevelSettableContext = CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE.bindTo(contextKeyService); // Register as text model content provider for output textModelResolverService.registerTextModelContentProvider(OUTPUT_SCHEME, this); @@ -196,7 +199,9 @@ export class OutputService extends Disposable implements IOutputService, ITextMo private setActiveChannel(channel: OutputChannel | undefined): void { this.activeChannel = channel; - this.activeFileOutputChannelContext.set(!!channel?.outputChannelDescriptor?.file); + const descriptor = channel?.outputChannelDescriptor; + this.activeFileOutputChannelContext.set(!!descriptor?.file); + this.activeOutputChannelLevelSettableContext.set(descriptor !== undefined && SetLogLevelAction.isLevelSettable(descriptor)); if (this.activeChannel) { this.storageService.store(OUTPUT_ACTIVE_CHANNEL_KEY, this.activeChannel.id, StorageScope.WORKSPACE, StorageTarget.MACHINE); diff --git a/src/vs/workbench/services/output/common/output.ts b/src/vs/workbench/services/output/common/output.ts index 012855aaaee..716bb3bcfd6 100644 --- a/src/vs/workbench/services/output/common/output.ts +++ b/src/vs/workbench/services/output/common/output.ts @@ -43,6 +43,8 @@ export const CONTEXT_IN_OUTPUT = new RawContextKey('inOutput', false); export const CONTEXT_ACTIVE_FILE_OUTPUT = new RawContextKey('activeLogOutput', false); +export const CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE = new RawContextKey('activeLogOutput.levelSettable', false); + export const CONTEXT_OUTPUT_SCROLL_LOCK = new RawContextKey(`outputView.scrollLock`, false); export const IOutputService = createDecorator('outputService'); From 4194cbfdf8b5b5b83a7a525b5470bdddb39da2ea Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:56:22 +0100 Subject: [PATCH 1190/1897] =?UTF-8?q?Git=20-=20=F0=9F=92=84=20more=20histo?= =?UTF-8?q?ry=20provider=20logging=20(#205101)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Git - 💄 more history provider logging * Fix up more logging --- extensions/git/src/historyProvider.ts | 10 +++++++--- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index db987344028..64230ecf46b 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -83,7 +83,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec this._HEAD?.upstream?.name !== this.repository.HEAD?.upstream?.name || this._HEAD?.upstream?.remote !== this.repository.HEAD?.upstream?.remote || this._HEAD?.upstream?.commit !== this.repository.HEAD?.upstream?.commit) { - this.logger.trace('GitHistoryProvider:onDidRunGitStatus - Upstream has changed'); + this.logger.trace(`GitHistoryProvider:onDidRunGitStatus - Upstream has changed (${force})`); this._onDidChangeCurrentHistoryItemGroupBase.fire(); } @@ -176,6 +176,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec if (!historyItemId2) { const upstreamRef = await this.resolveHistoryItemGroupBase(historyItemId1); if (!upstreamRef) { + this.logger.info(`GitHistoryProvider:resolveHistoryItemGroupCommonAncestor - Failed to resolve history item group base for '${historyItemId1}'`); return undefined; } @@ -184,14 +185,16 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec const ancestor = await this.repository.getMergeBase(historyItemId1, historyItemId2); if (!ancestor) { + this.logger.info(`GitHistoryProvider:resolveHistoryItemGroupCommonAncestor - Failed to resolve common ancestor for '${historyItemId1}' and '${historyItemId2}'`); return undefined; } try { const commitCount = await this.repository.getCommitCount(`${historyItemId1}...${historyItemId2}`); + this.logger.trace(`GitHistoryProvider:resolveHistoryItemGroupCommonAncestor - Resolved common ancestor for '${historyItemId1}' and '${historyItemId2}': ${JSON.stringify({ id: ancestor, ahead: commitCount.ahead, behind: commitCount.behind })}`); return { id: ancestor, ahead: commitCount.ahead, behind: commitCount.behind }; } catch (err) { - this.logger.error(`Failed to get ahead/behind for '${historyItemId1}...${historyItemId2}': ${err.message}`); + this.logger.error(`GitHistoryProvider:resolveHistoryItemGroupCommonAncestor - Failed to get ahead/behind for '${historyItemId1}...${historyItemId2}': ${err.message}`); } return undefined; @@ -212,6 +215,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec // Base (config -> reflog -> default) const remoteBranch = await this.repository.getBranchBase(historyItemId); if (!remoteBranch?.remote || !remoteBranch?.name || !remoteBranch?.commit || remoteBranch?.type !== RefType.RemoteHead) { + this.logger.info(`GitHistoryProvider:resolveHistoryItemGroupBase - Failed to resolve history item group base for '${historyItemId}'`); return undefined; } @@ -222,7 +226,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec }; } catch (err) { - this.logger.error(`Failed to get branch base for '${historyItemId}': ${err.message}`); + this.logger.error(`GitHistoryProvider:resolveHistoryItemGroupBase - Failed to get branch base for '${historyItemId}': ${err.message}`); } return undefined; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 3a92ee61bdc..73ce611ad21 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -3559,7 +3559,7 @@ class SCMTreeDataSource implements IAsyncDataSource Date: Tue, 13 Feb 2024 14:50:06 +0100 Subject: [PATCH 1191/1897] activitybar: Use orange active highlight in dark HC theme --- .../browser/parts/activitybar/media/activityaction.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css index 99df125f3a8..a47bbe794a0 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css @@ -107,6 +107,11 @@ border-left: 2px solid; } +.monaco-workbench.hc-black .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before, +.monaco-workbench.hc-black .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .active-item-indicator:before { + border-color: var(--vscode-focusBorder); +} + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before { top: 0; height: 100%; From 0776d4f78f4ec9b974990d5414e332ebd82232ba Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 13 Feb 2024 15:38:07 +0100 Subject: [PATCH 1192/1897] polishing the code --- .../bulkEdit/browser/preview/bulkEditPane.ts | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 3a9b9ebab13..2d8031cbe77 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -99,7 +99,7 @@ export class BulkEditPane extends ViewPane { this._ctxGroupByFile = BulkEditPane.ctxGroupByFile.bindTo(contextKeyService); this._ctxHasCheckedChanges = BulkEditPane.ctxHasCheckedChanges.bindTo(contextKeyService); this._disposables.add(this._editorService.onDidCloseEditor((e) => { - if (this._multiDiffEditor && e.editor === this._multiDiffEditor.input) { + if (this._multiDiffEditor && e.editor === this._multiDiffEditor.input && e.groupId === this._multiDiffEditor.group?.id) { this._multiDiffEditor = undefined; } })); @@ -108,7 +108,6 @@ export class BulkEditPane extends ViewPane { override dispose(): void { this._tree.dispose(); this._disposables.dispose(); - this._multiDiffEditor = undefined; super.dispose(); } @@ -284,8 +283,8 @@ export class BulkEditPane extends ViewPane { this._sessionDisposables.clear(); if (this._multiDiffEditor && this._multiDiffEditor.input && this._multiDiffEditor.group) { this._editorService.closeEditor({ editor: this._multiDiffEditor.input, groupId: this._multiDiffEditor.group.id }); + this._multiDiffEditor = undefined; } - this._multiDiffEditor = undefined; } toggleChecked() { @@ -333,7 +332,6 @@ export class BulkEditPane extends ViewPane { if (!fileOperations) { return; } - const options: Mutable = { ...e.editorOptions }; let fileElement: FileElement; if (e.element instanceof TextEditElement) { @@ -381,16 +379,15 @@ export class BulkEditPane extends ViewPane { }); } } - const multiDiffSource = URI.from({ scheme: 'refactor-preview', path: JSON.stringify(fileElement.edit.uri) }); + const multiDiffSource = URI.from({ scheme: 'refactor-preview' }); const label = 'Refactor Preview'; - this._multiDiffEditor = - await this._editorService.openEditor({ - multiDiffSource: URI.revive(multiDiffSource), - resources, - label: label, - description: label, - options: options, - }) as MultiDiffEditor; + this._multiDiffEditor = await this._editorService.openEditor({ + multiDiffSource, + resources, + label, + description: label, + options, + }) as MultiDiffEditor; } private _onContextMenu(e: ITreeContextMenuEvent): void { From 72d1ad1be81ab1dbce885786dba4f5b21800c7a0 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 13 Feb 2024 15:42:19 +0100 Subject: [PATCH 1193/1897] remove ChatMessage and add types LanguageModelMessage types so that they can evolve at their own pace https://github.com/microsoft/vscode/issues/199908 --- .../workbench/api/common/extHost.api.impl.ts | 3 +++ .../api/common/extHostChatProvider.ts | 10 +++++-- .../api/common/extHostTypeConverters.ts | 26 +++++++++++++++---- src/vs/workbench/api/common/extHostTypes.ts | 26 +++++++++++++++++++ .../common/extensionsApiProposals.ts | 1 - src/vscode-dts/vscode.proposed.chat.d.ts | 24 ----------------- .../vscode.proposed.chatProvider.d.ts | 22 ++++++++++++++++ .../vscode.proposed.chatRequestAccess.d.ts | 23 +++++++++++++++- 8 files changed, 102 insertions(+), 33 deletions(-) delete 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 2b4bfec98a6..881bd742f46 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1665,6 +1665,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ChatResponseCommandButtonPart: extHostTypes.ChatResponseCommandButtonPart, ChatAgentRequestTurn: extHostTypes.ChatAgentRequestTurn, ChatAgentResponseTurn: extHostTypes.ChatAgentResponseTurn, + LanguageModelSystemMessage: extHostTypes.LanguageModelSystemMessage, + LanguageModelUserMessage: extHostTypes.LanguageModelUserMessage, + LanguageModelAssistantMessage: extHostTypes.LanguageModelAssistantMessage, }; }; } diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index 108a893fb4d..923a2db9eda 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -151,7 +151,13 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._proxy.$handleProgressChunk(requestId, { index: fragment.index, part: fragment.part }); }); - return data.provider.provideLanguageModelResponse(messages.map(typeConvert.ChatMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); + if (data.provider.provideLanguageModelResponse2) { + return data.provider.provideLanguageModelResponse2(messages.map(typeConvert.LanguageModelMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); + } else { + // TODO@jrieken remove + return data.provider.provideLanguageModelResponse(messages.map(typeConvert.ChatMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); + } + } //#region --- making request @@ -269,7 +275,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { } const cts = new CancellationTokenSource(token); const requestId = (Math.random() * 1e6) | 0; - const requestPromise = that._proxy.$fetchResponse(from, languageModelId, requestId, messages.map(typeConvert.ChatMessage.from), options ?? {}, cts.token); + const requestPromise = that._proxy.$fetchResponse(from, languageModelId, requestId, messages.map(typeConvert.LanguageModelMessage.from), options ?? {}, cts.token); const res = new LanguageModelRequest(requestPromise, cts); that._pendingRequest.set(requestId, { languageModelId, res }); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 62f12e9aae7..2c4bba8099f 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2242,12 +2242,28 @@ export namespace ChatMessage { export function to(message: chatProvider.IChatMessage): vscode.ChatMessage { return new types.ChatMessage(ChatMessageRole.to(message.role), message.content); } +} - export function from(message: vscode.ChatMessage): chatProvider.IChatMessage { - return { - role: ChatMessageRole.from(message.role), - content: message.content, - }; +export namespace LanguageModelMessage { + + export function to(message: chatProvider.IChatMessage): vscode.LanguageModelMessage { + switch (message.role) { + case chatProvider.ChatMessageRole.System: return new types.LanguageModelSystemMessage(message.content); + case chatProvider.ChatMessageRole.User: return new types.LanguageModelUserMessage(message.content); + case chatProvider.ChatMessageRole.Assistant: return new types.LanguageModelAssistantMessage(message.content); + } + } + + export function from(message: vscode.LanguageModelMessage): chatProvider.IChatMessage { + if (message instanceof types.LanguageModelSystemMessage) { + return { role: chatProvider.ChatMessageRole.System, content: message.content }; + } else if (message instanceof types.LanguageModelUserMessage) { + return { role: chatProvider.ChatMessageRole.User, content: message.content }; + } else if (message instanceof types.LanguageModelAssistantMessage) { + return { role: chatProvider.ChatMessageRole.Assistant, content: message.content }; + } else { + throw new Error('Invalid LanguageModelMessage'); + } } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 492cf921558..09327af7d1b 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4274,6 +4274,32 @@ export class ChatAgentResponseTurn implements vscode.ChatAgentResponseTurn { ) { } } +export class LanguageModelSystemMessage { + content: string; + + constructor(content: string) { + this.content = content; + } +} + +export class LanguageModelUserMessage { + content: string; + name: string | undefined; + + constructor(content: string, name?: string) { + this.content = content; + this.name = name; + } +} + +export class LanguageModelAssistantMessage { + content: string; + + constructor(content: string) { + this.content = content; + } +} + //#endregion //#region ai diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 0edc713a74c..879db067311 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -11,7 +11,6 @@ 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', chatAgents2: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents2.d.ts', chatAgents2Additions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts', chatProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts', diff --git a/src/vscode-dts/vscode.proposed.chat.d.ts b/src/vscode-dts/vscode.proposed.chat.d.ts deleted file mode 100644 index 5dc2704b176..00000000000 --- a/src/vscode-dts/vscode.proposed.chat.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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, - // TODO@API name: align with ChatAgent (or whatever we'll rename that to) - Assistant = 2, - } - - // ChatML - export class ChatMessage { - role: ChatMessageRole; - content: 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 e35fd5547d1..7ac3ddba1b9 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -5,6 +5,27 @@ declare module 'vscode' { + // TODO@API NAME: ChatMessageKind? + export enum ChatMessageRole { + System = 0, + User = 1, + // TODO@API name: align with ChatAgent (or whatever we'll rename that to) + Assistant = 2, + } + + /** + * A chat message that is used to make chat request against a language model. + */ + export class ChatMessage { + + readonly role: ChatMessageRole; + + readonly content: string; + + constructor(role: ChatMessageRole, content: string); + } + + export interface ChatResponseFragment { index: number; part: string; @@ -17,6 +38,7 @@ declare module 'vscode' { */ export interface ChatResponseProvider { provideLanguageModelResponse(messages: ChatMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; + provideLanguageModelResponse2?(messages: LanguageModelMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; } export interface ChatResponseProviderMetadata { diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts index e1a151e77f4..431f3c2d708 100644 --- a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts +++ b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts @@ -17,6 +17,27 @@ declare module 'vscode' { stream: AsyncIterable; } + //TODO@API see https://learn.microsoft.com/en-us/dotnet/api/azure.ai.openai.chatrequestmessage?view=azure-dotnet-preview + // this allows to grow message by type, e.g add more content types to User message to support multimodal language models + + export class LanguageModelSystemMessage { + content: string; + constructor(content: string); + } + + export class LanguageModelUserMessage { + content: string; + name: string | undefined; + constructor(content: string, name?: string); + } + + export class LanguageModelAssistantMessage { + content: string; + constructor(content: string); + } + + export type LanguageModelMessage = LanguageModelSystemMessage | LanguageModelUserMessage | LanguageModelAssistantMessage; + /** * Represents access to using a language model. Access can be revoked at any time and extension * must check if the access is {@link LanguageModelAccess.isRevoked still valid} before using it. @@ -49,7 +70,7 @@ declare module 'vscode' { * @param messages * @param options */ - makeChatRequest(messages: ChatMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelResponse; + makeChatRequest(messages: LanguageModelMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelResponse; } export interface LanguageModelAccessOptions { From 4531f1a2be1795084994f133b0dda300d189d274 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 13 Feb 2024 15:47:56 +0100 Subject: [PATCH 1194/1897] Fix cursor position after splitting window (#205107) The cursor is not in the correct position after splitting the window Fixes #203969 --- src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index d2fd3da2e03..742c76b7d37 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -280,9 +280,9 @@ class DirtyDiffWidget extends PeekViewWidget { this._actionbarWidget!.context = [diffEditorModel.modified.uri, providerSpecificChanges, contextIndex]; if (usePosition) { this.show(position, height); + this.editor.setPosition(position); + this.editor.focus(); } - this.editor.setPosition(position); - this.editor.focus(); } private renderTitle(label: string): void { From 2b7bef020bc73d8f9dac84eccda619e232c8c8f4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 13 Feb 2024 16:05:15 +0100 Subject: [PATCH 1195/1897] Chat Agent API updates (#205104) * update `ChatAgentContext#history` to be an array of turns https://github.com/microsoft/vscode/issues/199908 * add agent info (agent and extension id) to `ChatAgentResponseTurn` and `ChatAgentRequestTurn` https://github.com/microsoft/vscode/issues/199908 * update chat test snapshots --- .../api/browser/mainThreadChatAgents2.ts | 4 ++- .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostChatAgents2.ts | 26 ++++------------- src/vs/workbench/api/common/extHostTypes.ts | 16 +++++++--- .../contrib/chat/common/chatAgents.ts | 2 ++ .../contrib/chat/common/chatModel.ts | 2 +- .../__snapshots__/Chat_can_deserialize.0.snap | 4 +++ .../__snapshots__/Chat_can_serialize.1.snap | 4 +++ .../chat/test/common/chatService.test.ts | 4 ++- .../chat/test/common/voiceChat.test.ts | 5 ++++ .../vscode.proposed.chatAgents2.d.ts | 29 ++++++++++--------- 11 files changed, 56 insertions(+), 42 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index 3229725e96a..fa796bde948 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -13,6 +13,7 @@ import { getWordAtText } from 'vs/editor/common/core/wordHelper'; import { CompletionContext, CompletionItem, CompletionItemKind, CompletionList } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ExtHostChatAgentsShape2, ExtHostContext, IChatProgressDto, IExtensionChatAgentMetadata, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; @@ -74,10 +75,11 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA this._agents.deleteAndDispose(handle); } - $registerAgent(handle: number, name: string, metadata: IExtensionChatAgentMetadata): void { + $registerAgent(handle: number, extension: ExtensionIdentifier, name: string, metadata: IExtensionChatAgentMetadata): void { let lastSlashCommands: IChatAgentCommand[] | undefined; const d = this._chatAgentService.registerAgent({ id: name, + extensionId: extension, metadata: revive(metadata), invoke: async (request, progress, history, token) => { this._pendingProgress.set(request.requestId, progress); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 2f931bb83ef..c17e879971a 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1195,7 +1195,7 @@ export interface IExtensionChatAgentMetadata extends Dto { } export interface MainThreadChatAgentsShape2 extends IDisposable { - $registerAgent(handle: number, name: string, metadata: IExtensionChatAgentMetadata): void; + $registerAgent(handle: number, extension: ExtensionIdentifier, name: string, metadata: IExtensionChatAgentMetadata): void; $registerAgentCompletionsProvider(handle: number, triggerCharacters: string[]): void; $unregisterAgentCompletionsProvider(handle: number): void; $updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 56518bd78e4..e32d418c24a 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -176,7 +176,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const agent = new ExtHostChatAgent(extension, name, this._proxy, handle, handler); this._agents.set(handle, agent); - this._proxy.$registerAgent(handle, name, {}); + this._proxy.$registerAgent(handle, extension.identifier, name, {}); return agent.apiAgent; } @@ -197,11 +197,10 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService, this.commands.converter, sessionDisposables); try { - const convertedHistory = await this.prepareHistory(request, context); - const convertedHistory2 = await this.prepareHistoryTurns(request, context); + const convertedHistory = await this.prepareHistoryTurns(request, context); const task = agent.invoke( typeConvert.ChatAgentRequest.to(request), - { history: convertedHistory, history2: convertedHistory2 }, + { history: convertedHistory, history2: convertedHistory }, stream.apiObject, token ); @@ -229,21 +228,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } } - private async prepareHistory(request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise { - return coalesce(await Promise.all(context.history - .map(async h => { - const ehResult = typeConvert.ChatAgentResult.to(h.result); - const result: vscode.ChatAgentResult2 = request.agentId === h.request.agentId ? - ehResult : - { ...ehResult, metadata: undefined }; - return { - request: typeConvert.ChatAgentRequest.to(h.request), - response: coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))), - result, - } satisfies vscode.ChatAgentHistoryEntry; - }))); - } - private async prepareHistoryTurns(request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise<(vscode.ChatAgentRequestTurn | vscode.ChatAgentResponseTurn)[]> { const res: (vscode.ChatAgentRequestTurn | vscode.ChatAgentResponseTurn)[] = []; @@ -255,11 +239,11 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { { ...ehResult, metadata: undefined }; // REQUEST turn - res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.agentId, h.request.command, h.request.variables2.variables.map(typeConvert.ChatAgentResolvedVariable.to))); + res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.command, h.request.variables2.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', agentId: h.request.agentId })); // RESPONSE turn const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))); - res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, h.request.agentId)); + res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, { extensionId: '', agentId: h.request.agentId })); } return res; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 09327af7d1b..9122ad1b881 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4258,20 +4258,28 @@ export class ChatResponseReferencePart { export class ChatAgentRequestTurn implements vscode.ChatAgentRequestTurn { + agentId: string; constructor( readonly prompt: string, - readonly agentId: string, readonly command: string | undefined, readonly variables: vscode.ChatAgentResolvedVariable[], - ) { } + readonly agent: { extensionId: string; agentId: string }, + ) { + this.agentId = agent.agentId; + } } export class ChatAgentResponseTurn implements vscode.ChatAgentResponseTurn { + + agentId: string; + constructor( readonly response: ReadonlyArray, readonly result: vscode.ChatAgentResult2, - readonly agentId: string, - ) { } + readonly agent: { extensionId: string; agentId: string } + ) { + this.agentId = agent.agentId; + } } export class LanguageModelSystemMessage { diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 289d3cb15b4..adbaa77da2a 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -11,6 +11,7 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { ProviderResult } from 'vs/editor/common/languages'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatProgressResponseContent, IChatRequestVariableData2 } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; @@ -26,6 +27,7 @@ export interface IChatAgentHistoryEntry { export interface IChatAgentData { id: string; + extensionId: ExtensionIdentifier; metadata: IChatAgentMetadata; } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index aac4dc60c4f..56f75f06eda 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -788,7 +788,7 @@ export class ChatModel extends Disposable implements IChatModel { followups: r.response?.followups, isCanceled: r.response?.isCanceled, vote: r.response?.vote, - agent: r.response?.agent ? { id: r.response.agent.id, metadata: r.response.agent.metadata } : undefined, // May actually be the full IChatAgent instance, just take the data props + agent: r.response?.agent ? { id: r.response.agent.id, extensionId: r.response.agent.extensionId, metadata: r.response.agent.metadata } : undefined, // May actually be the full IChatAgent instance, just take the data props slashCommand: r.response?.slashCommand, usedContext: r.response?.usedContext, contentReferences: r.response?.contentReferences diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap index cd6fa9b8139..1e34cf65b08 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap @@ -53,6 +53,10 @@ vote: undefined, agent: { id: "ChatProviderWithUsedContext", + extensionId: { + value: "nullExtensionDescription", + _lower: "nullextensiondescription" + }, metadata: { } }, slashCommand: undefined, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap index 8420b35111e..16a53a88b07 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap @@ -53,6 +53,10 @@ vote: undefined, agent: { id: "ChatProviderWithUsedContext", + extensionId: { + value: "nullExtensionDescription", + _lower: "nullextensiondescription" + }, metadata: { } }, slashCommand: undefined, 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 3358df8cd4d..eface4d487d 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -29,7 +29,7 @@ import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { MockChatVariablesService } from 'vs/workbench/contrib/chat/test/common/mockChatVariables'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { TestContextService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; class SimpleTestProvider extends Disposable implements IChatProvider { @@ -57,6 +57,7 @@ class SimpleTestProvider extends Disposable implements IChatProvider { const chatAgentWithUsedContextId = 'ChatProviderWithUsedContext'; const chatAgentWithUsedContext: IChatAgent = { id: chatAgentWithUsedContextId, + extensionId: nullExtensionDescription.identifier, metadata: {}, async provideSlashCommands(token) { return []; @@ -109,6 +110,7 @@ suite('Chat', () => { const agent = { id: 'testAgent', + extensionId: nullExtensionDescription.identifier, metadata: { isDefault: true }, async invoke(request, progress, history, token) { return {}; diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index 6751d47ede4..fd4184ab8eb 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -10,10 +10,12 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ProviderResult } from 'vs/editor/common/languages'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IChatAgent, IChatAgentCommand, IChatAgentHistoryEntry, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgress, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IVoiceChatSessionOptions, IVoiceChatTextEvent, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; import { ISpeechProvider, ISpeechService, ISpeechToTextEvent, ISpeechToTextSession, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; suite('VoiceChat', () => { @@ -22,6 +24,9 @@ suite('VoiceChat', () => { } class TestChatAgent implements IChatAgent { + + extensionId: ExtensionIdentifier = nullExtensionDescription.identifier; + constructor(readonly id: string, readonly lastSlashCommands: IChatAgentCommand[]) { } invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error('Method not implemented.'); } provideSlashCommands(token: CancellationToken): Promise { throw new Error('Method not implemented.'); } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 1a48b381952..3ee255c32e1 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -38,12 +38,15 @@ declare module 'vscode' { */ readonly prompt: string; + // TODO@API NAME agent + // TODO@API TYPE {agent:string, extension:string} + /** @deprecated */ + readonly agentId: string; + /** * The ID of the chat agent to which this request was directed. */ - // TODO@API NAME agent - // TODO@API TYPE {agent:string, extension:string} - readonly agentId: string; + readonly agent: { extensionId: string; agentId: string }; /** * The name of the {@link ChatAgentCommand command} that was selected for this request. @@ -56,7 +59,7 @@ declare module 'vscode' { // TODO@API is this needed? readonly variables: ChatAgentResolvedVariable[]; - private constructor(prompt: string, agentId: string, command: string | undefined, variables: ChatAgentResolvedVariable[],); + private constructor(prompt: string, command: string | undefined, variables: ChatAgentResolvedVariable[], agent: { extensionId: string; agentId: string }); } // TODO@API name: Turn? @@ -72,23 +75,23 @@ declare module 'vscode' { */ readonly result: ChatAgentResult2; + /** @deprecated */ readonly agentId: string; - private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: string); + readonly agent: { extensionId: string; agentId: string }; + + private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: { extensionId: string; agentId: string }); } export interface ChatAgentContext { - /** - * @deprecated - */ - history: ChatAgentHistoryEntry[]; - - // location: - /** * All of the chat messages so far in the current chat session. */ - // TODO@API name: histroy + readonly history: ReadonlyArray; + + /** + * @deprecated, use histroy + */ readonly history2: ReadonlyArray; } From 80f1ca24b43780278fd48c2136afbc87c9a53967 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 09:44:08 -0600 Subject: [PATCH 1196/1897] fix #205108 --- .../browser/accessibilitySignalService.ts | 2 +- .../browser/accessibilityConfiguration.ts | 7 +++++-- .../accessibilitySignals/browser/commands.ts | 10 ++++++++-- .../preferences/browser/settingsLayout.ts | 20 +++++-------------- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts index 915a61aba5b..d2689b002d7 100644 --- a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts +++ b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts @@ -129,7 +129,7 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi return; } this.playingSounds.add(sound); - const url = FileAccess.asBrowserUri(`vs/platform/accessibilitySignals/browser/media/${sound.fileName}`).toString(true); + const url = FileAccess.asBrowserUri(`vs/platform/accessibilitySignal/browser/media/${sound.fileName}`).toString(true); try { const sound = this.sounds.get(url); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index c322814c2ba..afd920ac1f9 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -731,7 +731,7 @@ Registry.as(WorkbenchExtensions.ConfigurationMi announcement = announcement ? 'auto' : 'off'; } } - configurationKeyValuePairs.push([`${item.settingsKey}`, { value: announcement ? { announcement, sound } : { sound } }]); + configurationKeyValuePairs.push([`${item.settingsKey}`, { value: announcement !== undefined ? { announcement, sound } : { sound } }]); return configurationKeyValuePairs; } }))); @@ -742,7 +742,10 @@ Registry.as(WorkbenchExtensions.ConfigurationMi migrateFn: (announcement, accessor) => { const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; const sound = accessor(item.legacySoundSettingsKey); - configurationKeyValuePairs.push([`${item.settingsKey}`, { value: announcement ? { announcement, sound } : { sound } }]); + if (announcement !== undefined && typeof announcement !== 'string') { + announcement = announcement ? 'auto' : 'off'; + } + configurationKeyValuePairs.push([`${item.settingsKey}`, { value: announcement !== undefined ? { announcement, sound } : { sound } }]); return configurationKeyValuePairs; } }))); diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 50db5214d76..666516df128 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -21,6 +21,9 @@ export class ShowSignalSoundHelp extends Action2 { id: ShowSignalSoundHelp.ID, title: localize2('signals.sound.help', "Help: List Signal Sounds"), f1: true, + metadata: { + description: localize('accessibility.sound.help.description', "List all accessibility sounds / audio cues and configure their settings") + } }); } @@ -41,7 +44,7 @@ export class ShowSignalSoundHelp extends Action2 { })); const qp = quickInputService.createQuickPick(); qp.items = items; - qp.selectedItems = items.filter(i => accessibilitySignalService.isSoundEnabled(i.signal) || configurationService.getValue(i.signal.settingsKey + '.sound') !== 'never'); + qp.selectedItems = items.filter(i => accessibilitySignalService.isSoundEnabled(i.signal) || userGestureSignals.includes(i.signal) && configurationService.getValue(i.signal.settingsKey + '.sound') !== 'never'); qp.onDidAccept(() => { const enabledSounds = qp.selectedItems.map(i => i.signal); const disabledSounds = qp.items.map(i => (i as any).signal).filter(i => !enabledSounds.includes(i)); @@ -82,6 +85,9 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { id: ShowAccessibilityAnnouncementHelp.ID, title: localize2('accessibility.announcement.help', "Help: List Signal Announcements"), f1: true, + metadata: { + description: localize('accessibility.announcement.help.description', "List all accessibility announcements / alerts and configure their settings") + } }); } @@ -102,7 +108,7 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { })); const qp = quickInputService.createQuickPick(); qp.items = items; - qp.selectedItems = items.filter(i => accessibilitySignalService.isAnnouncementEnabled(i.signal) || configurationService.getValue(i.signal.settingsKey + '.announcement') !== 'never'); + qp.selectedItems = items.filter(i => accessibilitySignalService.isAnnouncementEnabled(i.signal) || userGestureSignals.includes(i.signal) && configurationService.getValue(i.signal.settingsKey + '.announcement') !== 'never'); qp.onDidAccept(() => { const enabledAnnouncements = qp.selectedItems.map(i => i.signal); const disabledAnnouncements = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledAnnouncements.includes(cue)); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index 6995c33c302..2edaff0b3b7 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -146,6 +146,11 @@ export const tocData: ITOCEntry = { id: 'features', label: localize('features', "Features"), children: [ + { + id: 'features/accessibilitySignals', + label: localize('accessibility.signals', 'Accessibility Signals'), + settings: ['accessibility.signals.*'] + }, { id: 'features/accessibility', label: localize('accessibility', "Accessibility"), @@ -221,21 +226,6 @@ export const tocData: ITOCEntry = { label: localize('notebook', 'Notebook'), settings: ['notebook.*', 'interactiveWindow.*'] }, - { - id: 'features/signals', - label: localize('signals', 'Signals'), - settings: ['signals.*'] - }, - { - id: 'features/accessibilitySignals', - label: localize('accessibility.signals', 'Accessibility Signals'), - settings: ['accessibility.signals.*'] - }, - { - id: 'features/accessibilitySignals', - label: localize('accessibility.signals', 'Accessibility Signals'), - settings: ['accessibility.signals.*'] - }, { id: 'features/mergeEditor', label: localize('mergeEditor', 'Merge Editor'), From 29a8551519b0a5eceb2979e8cb1ec1f02743f59e Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Tue, 13 Feb 2024 07:52:41 -0800 Subject: [PATCH 1197/1897] a new cell editor might already be in view (#205023) account for creating a editor cell already within viewable range - edit markdown --- .../notebook/browser/view/notebookCellList.ts | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index d9d1dab798d..94cbebb16b5 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -1023,19 +1023,19 @@ export class NotebookCellList extends WorkbenchList implements ID const element = this.view.element(viewIndex); if (element.editorAttached) { - this._revealRangeCommon(viewIndex, range, false, false); + this._revealRangeCommon(viewIndex, range); } else { const elementHeight = this.view.elementHeight(viewIndex); - let upwards = false; + let alignHint: 'top' | 'bottom' | undefined = undefined; if (elementTop + elementHeight <= scrollTop) { - // scroll downwards + // scroll up this.view.setScrollTop(elementTop); - upwards = false; + alignHint = 'top'; } else if (elementTop >= wrapperBottom) { - // scroll upwards + // scroll down this.view.setScrollTop(elementTop - this.view.renderHeight / 2); - upwards = true; + alignHint = 'bottom'; } const editorAttachedPromise = new Promise((resolve, reject) => { @@ -1045,7 +1045,7 @@ export class NotebookCellList extends WorkbenchList implements ID }); return editorAttachedPromise.then(() => { - this._revealRangeCommon(viewIndex, range, true, upwards); + this._revealRangeCommon(viewIndex, range, alignHint); }); } } @@ -1112,7 +1112,7 @@ export class NotebookCellList extends WorkbenchList implements ID } } - private _revealRangeCommon(viewIndex: number, range: Selection | Range, newlyCreated: boolean, alignToBottom: boolean) { + private _revealRangeCommon(viewIndex: number, range: Selection | Range, alignHint?: 'top' | 'bottom' | undefined) { const element = this.view.element(viewIndex); const scrollTop = this.getViewScrollTop(); const wrapperBottom = this.getViewScrollBottom(); @@ -1134,17 +1134,15 @@ export class NotebookCellList extends WorkbenchList implements ID this.view.setScrollTop(positionTop - 30); } else if (positionTop > wrapperBottom) { this.view.setScrollTop(scrollTop + positionTop - wrapperBottom + 30); - } else if (newlyCreated) { - // newly scrolled into view - if (alignToBottom) { - // align to the bottom - this.view.setScrollTop(scrollTop + positionTop - wrapperBottom + 30); - } else { - // align to to top - this.view.setScrollTop(positionTop - 30); - } + } else if (alignHint === 'bottom') { + // Scrolled into view from below + this.view.setScrollTop(scrollTop + positionTop - wrapperBottom + 30); + } else if (alignHint === 'top') { + // Scrolled into view from above + this.view.setScrollTop(positionTop - 30); } + element.revealRangeInCenter(range); } //#endregion From 760ee5b9976bc9ee647e5aa5ecfa26c42780fab4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 10:22:14 -0600 Subject: [PATCH 1198/1897] wip --- .../chat/browser/terminalChatController.ts | 65 ++++++++++++------- .../chat/browser/terminalChatWidget.ts | 64 ++++++------------ 2 files changed, 63 insertions(+), 66 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 5392e1787dd..8308105d9f5 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -14,8 +14,11 @@ import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/wid import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; +import { generateUuid } from 'vs/base/common/uuid'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; export class TerminalChatController extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; @@ -34,7 +37,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } // private _sessionCtor: CancelablePromise | undefined; - private _activeSession?: Session; + // private _activeSession?: Session; // private readonly _ctxHasActiveRequest: IContextKey; // private _isVisible: boolean = false; // private _strategy: EditStrategy | undefined; @@ -51,7 +54,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IConfigurationService private _configurationService: IConfigurationService, @ITerminalService private readonly _terminalService: ITerminalService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IChatAgentService private readonly _chatAgentService: IChatAgentService + @IChatAgentService private readonly _chatAgentService: IChatAgentService, // @IContextKeyService private readonly _contextKeyService: IContextKeyService, // @IInstantiationService private readonly _instantiationService: IInstantiationService, // @ICommandService private readonly _commandService: ICommandService, @@ -93,7 +96,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr throw new Error('FindWidget expected terminal DOM to be initialized'); } - // this._instance.domElement?.appendChild(chatWidget.getDomNode()); if (this._lastLayoutDimensions) { chatWidget.layout(this._lastLayoutDimensions.width); } @@ -103,24 +105,41 @@ export class TerminalChatController extends Disposable implements ITerminalContr } async acceptInput(): Promise { - // TODO: create session, deal with response - // this._activeSession = new Session(EditMode.Live, , this._instance); - // const initVariableData: IChatRequestVariableData = { message: getPromptText(parsedRequest.parts), variables: {} }; - // request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); - // const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); - // const requestProps: IChatAgentRequest = { - // sessionId: 'sessionId', - // requestId: 'fake', - // agentId: 'terminal', - // message: this._chatWidget?.rawValue?.getValue() || '', - // // variables: variableData.variables, - // // command: agentSlashCommandPart?.command.name, - // // variables2: asVariablesData2(parsedRequest, variableData) - // }; - // const agentResult = await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, undefined, token); - // const rawResult = agentResult; - // const agentOrCommandFollowups = this._chatAgentService.getFollowups('terminal', agentResult, followupsCancelToken); - this._chatWidget?.rawValue?.acceptInput(); + let message = ''; + const progressCallback = (progress: IChatProgress) => { + // if (token.isCancellationRequested) { + // return; + // } + + + // gotProgress = true; + + if (progress.kind === 'content' || progress.kind === 'markdownContent') { + // this.trace('sendRequest', `Provider returned progress for session ${model.sessionId}, ${typeof progress.content === 'string' ? progress.content.length : progress.content.value.length} chars`); + message += progress.content; + } else { + // this.trace('sendRequest', `Provider returned progress: ${JSON.stringify(progress)}`); + } + + // model.acceptResponseProgress(request, progress); + }; + const resolvedVariables: Record = {}; + + const requestProps: IChatAgentRequest = { + sessionId: generateUuid(), + requestId: generateUuid(), + agentId: 'terminal', + message: this._chatWidget?.rawValue?.input() || '', + variables: resolvedVariables, + variables2: { message: this._chatWidget?.rawValue?.input() || '', variables: [] } + }; + // TODO: use token + const agentResult = await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], CancellationToken.None); + console.log(agentResult); + console.log(message); + alert(message); + this._chatWidget?.rawValue?.setValue(); + // TODO: accessibility announcement, help dialog } reveal(): void { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index ed78f6292e7..ee0a778cc0f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -15,11 +15,8 @@ import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/termina import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; -import { MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_STATUS, MENU_CELL_CHAT_WIDGET_FEEDBACK } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; -import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; -import { generateUuid } from 'vs/base/common/uuid'; -import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; export class TerminalChatWidget extends Disposable { private _scopedInstantiationService: IInstantiationService; @@ -113,45 +110,26 @@ export class TerminalChatWidget extends Disposable { cancel(): void { // this._widget?.clear(); } - async acceptInput(): Promise { - // this._widget?.acceptInput(); - // this._chatModel ??= this._chatService.startSession('terminal', CancellationToken.None); - - // if (!this._model) { - // throw new Error('Could not start chat session'); - // } - // this._chatService?.sendRequest(this._chatModel?.sessionId!, this._inlineChatWidget.value); - // this._activeSession = new Session(EditMode.Live, , this._instance); - // const initVariableData: IChatRequestVariableData = { message: getPromptText(parsedRequest.parts), variables: {} }; - // request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); - // const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); - const progressCallback = (progress: IChatProgress) => { - // if (token.isCancellationRequested) { - // return; - // } - console.log(progress); - // gotProgress = true; - - if (progress.kind === 'content' || progress.kind === 'markdownContent') { - // this.trace('sendRequest', `Provider returned progress for session ${model.sessionId}, ${typeof progress.content === 'string' ? progress.content.length : progress.content.value.length} chars`); - } else { - // this.trace('sendRequest', `Provider returned progress: ${JSON.stringify(progress)}`); - } - - // model.acceptResponseProgress(request, progress); - }; - const requestProps: IChatAgentRequest = { - sessionId: generateUuid(), - requestId: generateUuid(), - agentId: 'terminal', - message: this._inlineChatWidget.value || '', - variables: new Map() as any, - variables2: {} as any - }; - const agentResult = await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], CancellationToken.None); - console.log(agentResult); - this._inlineChatWidget.value = ''; + input(): string { + return this._inlineChatWidget.value; } + setValue(value?: string) { + this._inlineChatWidget.value = value ?? ''; + } + // async acceptInput(): Promise { + // // this._widget?.acceptInput(); + // // this._chatModel ??= this._chatService.startSession('terminal', CancellationToken.None); + + // // if (!this._model) { + // // throw new Error('Could not start chat session'); + // // } + // // this._chatService?.sendRequest(this._chatModel?.sessionId!, this._inlineChatWidget.value); + // // this._activeSession = new Session(EditMode.Live, , this._instance); + // // const initVariableData: IChatRequestVariableData = { message: getPromptText(parsedRequest.parts), variables: {} }; + // // request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); + // // const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); + // this._inlineChatWidget.value = ''; + // } layout(width: number): void { // this._widget?.layout(100, width < 300 ? 300 : width); } From b998b6432bd6c080a26b59ccd8a03f69b06a0169 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 08:30:12 -0800 Subject: [PATCH 1199/1897] Add menus for terminal chat to the terminalContrib --- src/vs/platform/actions/common/actions.ts | 1 - .../chat/browser/terminal.chat.contribution.ts | 11 +++++++++-- .../chat/browser/terminalChat.ts | 12 ++++++++++++ .../chat/browser/terminalChatWidget.ts | 17 ++++++++--------- 4 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 3102938a3be..0b3015ddf75 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -211,7 +211,6 @@ export class MenuId { static readonly ChatCodeBlock = new MenuId('ChatCodeblock'); static readonly ChatMessageTitle = new MenuId('ChatMessageTitle'); static readonly ChatExecute = new MenuId('ChatExecute'); - static readonly TerminalChat = new MenuId('TerminalChat'); static readonly ChatInputSide = new MenuId('ChatInputSide'); static readonly AccessibleView = new MenuId('AccessibleView'); static readonly MultiDiffEditorFileToolbar = new MenuId('MultiDiffEditorFileToolbar'); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index 1d909e6fc30..fd88e4c0982 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -15,6 +15,7 @@ import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; registerTerminalContribution(TerminalChatController.ID, TerminalChatController, false); @@ -43,13 +44,19 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalCommandId.HideChat, - title: localize2('workbench.action.terminal.hideChat', 'Hide Chat'), + title: localize2('workbench.action.terminal.closeChat', 'Close Chat'), keybinding: { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], when: ContextKeyExpr.and(TerminalContextKeys.chatFocused, TerminalContextKeys.chatVisible), weight: KeybindingWeight.WorkbenchContrib }, + icon: Codicon.close, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET, + group: 'main', + order: 2 + }, f1: true, precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -81,7 +88,7 @@ registerActiveXtermAction({ primary: KeyCode.Enter }, menu: { - id: MenuId.TerminalChat, + id: MENU_TERMINAL_CHAT_INPUT, group: 'main', order: 1, // when: TerminalContextKeys.chatSessionInProgress.negate(), diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts new file mode 100644 index 00000000000..8b74aae7aeb --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.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. + *--------------------------------------------------------------------------------------------*/ + +import { MenuId } from 'vs/platform/actions/common/actions'; + +export const MENU_TERMINAL_CHAT_INPUT = MenuId.for('terminalChatInput'); +export const MENU_TERMINAL_CHAT_WIDGET = MenuId.for('terminalChatWidget'); +export const MENU_TERMINAL_CHAT_WIDGET_STATUS = MenuId.for('terminalChatWidget.status'); +export const MENU_TERMINAL_CHAT_WIDGET_FEEDBACK = MenuId.for('terminalChatWidget.feedback'); +export const MENU_TERMINAL_CHAT_WIDGET_TOOLBAR = MenuId.for('terminalChatWidget.toolbar'); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index ee0a778cc0f..7f30c3bca53 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -3,20 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/terminalChatWidget'; import { Dimension, IFocusTracker, trackFocus } from 'vs/base/browser/dom'; import { Disposable } from 'vs/base/common/lifecycle'; +import 'vs/css!./media/terminalChatWidget'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { localize } from 'vs/nls'; -import { MenuId } from 'vs/platform/actions/common/actions'; -import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; +import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; export class TerminalChatWidget extends Disposable { private _scopedInstantiationService: IInstantiationService; @@ -76,10 +75,10 @@ export class TerminalChatWidget extends Disposable { InlineChatWidget, fakeParentEditor, { - menuId: MenuId.TerminalChat, - widgetMenuId: MENU_CELL_CHAT_WIDGET, - statusMenuId: MENU_CELL_CHAT_WIDGET_STATUS, - feedbackMenuId: MENU_CELL_CHAT_WIDGET_FEEDBACK + menuId: MENU_TERMINAL_CHAT_INPUT, + widgetMenuId: MENU_TERMINAL_CHAT_WIDGET, + statusMenuId: MENU_TERMINAL_CHAT_WIDGET_STATUS, + feedbackMenuId: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK } ); this._inlineChatWidget.placeholder = localize('default.placeholder', "Ask how to do something in the terminal"); From e4e9562df987263f6315e4104a7e968bf45ef87a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 08:33:18 -0800 Subject: [PATCH 1200/1897] Clean up chat widget --- .../chat/browser/terminalChatWidget.ts | 30 ++++--------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 7f30c3bca53..330be0b35a5 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -31,10 +31,10 @@ export class TerminalChatWidget extends Disposable { constructor( private readonly _container: HTMLElement, private readonly _instance: ITerminalInstance, - @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IChatAgentService private readonly _chatAgentService: IChatAgentService) { + @IChatAgentService private readonly _chatAgentService: IChatAgentService + ) { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); @@ -44,24 +44,9 @@ export class TerminalChatWidget extends Disposable { this._widgetContainer.classList.add('terminal-inline-chat'); this._container.appendChild(this._widgetContainer); - // this._widget = this._register(this._scopedInstantiationService.createInstance( - // ChatWidget, - // { viewId: 'terminal' }, - // { supportsFileReferences: false, renderStyle: 'compact' }, - // { - // listForeground: editorForeground, - // listBackground: editorBackground, - // inputEditorBackground: inputBackground, - // resultEditorBackground: editorBackground - // })); - // this._widget.render(this._widgetContainer); - // this._register(this._widget.onDidFocus(() => this._chatWidgetFocused.set(true))); - + // The inline chat widget requires a parent editor that it bases the diff view on, since the + // terminal doesn't use that feature we can just pass in an unattached editor instance. const fakeParentEditorElement = document.createElement('div'); - - // const editorConstructionOptions = this.inputEditorOptions.getEditorConstructionOptions(); - // this.setPlaceholderFontStyles(editorConstructionOptions.fontFamily!, editorConstructionOptions.fontSize!, editorConstructionOptions.lineHeight!); - const fakeParentEditor = this._scopedInstantiationService.createInstance( CodeEditorWidget, fakeParentEditorElement, @@ -91,23 +76,18 @@ export class TerminalChatWidget extends Disposable { this._inlineChatWidget.layout(new Dimension(400, 150)); this._widgetContainer.classList.remove('hide'); - // this._widget.setVisible(true); this._chatWidgetFocused.set(true); this._chatWidgetVisible.set(true); - // this._widget.setInput('@terminal'); - // this._widget.setInputPlaceholder('Request a terminal command'); - // this._widget.focusInput(); this._inlineChatWidget.focus(); } hide(): void { this._widgetContainer.classList.add('hide'); this._chatWidgetFocused.set(false); this._chatWidgetVisible.set(false); - // this._widget.clear(); this._instance.focus(); } cancel(): void { - // this._widget?.clear(); + // TODO: Impl } input(): string { return this._inlineChatWidget.value; From 32b3e7ed0e0fb868f3b2ede89ffa94b898f7a4c1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 08:45:29 -0800 Subject: [PATCH 1201/1897] Standardize chat command ns, add feedback placeholders --- .../contrib/terminal/common/terminal.ts | 11 +-- .../browser/terminal.chat.contribution.ts | 70 +++++++++++++++++-- 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 5ed9e674af2..f2167db7f17 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -500,10 +500,13 @@ export const enum TerminalCommandId { FontZoomIn = 'workbench.action.terminal.fontZoomIn', FontZoomOut = 'workbench.action.terminal.fontZoomOut', FontZoomReset = 'workbench.action.terminal.fontZoomReset', - FocusChat = 'workbench.action.terminal.focusChat', - HideChat = 'workbench.action.terminal.hideChat', - MakeChatRequest = 'workbench.action.terminal.makeChatRequest', - CancelChat = 'workbench.action.terminal.cancelChat', + ChatFocus = 'workbench.action.terminal.chat.focus', + ChatHide = 'workbench.action.terminal.chat.close', + ChatMakeRequest = 'workbench.action.terminal.chat.makeRequest', + ChatCancel = 'workbench.action.terminal.chat.cancel', + ChatFeedbackHelpful = 'workbench.action.terminal.chat.feedbackHelpful', + ChatFeedbackUnhelpful = 'workbench.action.terminal.chat.feedbackUnhelpful', + ChatFeedbackReportIssue = 'workbench.action.terminal.chat.feedbackReportIssue', // Developer commands diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index fd88e4c0982..2972104787f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -15,13 +15,13 @@ import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; registerTerminalContribution(TerminalChatController.ID, TerminalChatController, false); registerActiveXtermAction({ - id: TerminalCommandId.FocusChat, + id: TerminalCommandId.ChatFocus, title: localize2('workbench.action.terminal.focusChat', 'Focus Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, @@ -43,7 +43,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.HideChat, + id: TerminalCommandId.ChatHide, title: localize2('workbench.action.terminal.closeChat', 'Close Chat'), keybinding: { primary: KeyCode.Escape, @@ -72,7 +72,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.MakeChatRequest, + id: TerminalCommandId.ChatMakeRequest, title: localize2('workbench.action.terminal.submitChat', 'Make Chat Request'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -105,7 +105,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.CancelChat, + id: TerminalCommandId.ChatCancel, title: localize2('workbench.action.terminal.cancelChat', 'Cancel Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -124,3 +124,63 @@ registerActiveXtermAction({ contr?.chatWidget?.cancel(); } }); + +registerActiveXtermAction({ + id: TerminalCommandId.ChatFeedbackHelpful, + title: localize2('feedbackHelpful', 'Helpful'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalContextKeys.chatRequestActive, + ), + icon: Codicon.thumbsup, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, + group: 'inline', + order: 1, + // TODO: Fill in ctx + // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), + }, + run: (_xterm, _accessor, activeInstance) => { + // TODO: Impl + } +}); + +registerActiveXtermAction({ + id: TerminalCommandId.ChatFeedbackUnhelpful, + title: localize2('feedbackUnhelpful', 'Helpful'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalContextKeys.chatRequestActive, + ), + icon: Codicon.thumbsup, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, + group: 'inline', + order: 2, + // TODO: Fill in ctx + // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), + }, + run: (_xterm, _accessor, activeInstance) => { + // TODO: Impl + } +}); + +registerActiveXtermAction({ + id: TerminalCommandId.ChatFeedbackReportIssue, + title: localize2('reportIssue', 'Report Issue'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalContextKeys.chatRequestActive, + ), + icon: Codicon.thumbsup, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, + group: 'inline', + order: 3, + // TODO: Fill in ctx + // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), + }, + run: (_xterm, _accessor, activeInstance) => { + // TODO: Impl + } +}); From 611fd5cf88efac77f8fee0aed18bbef8aaeef768 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 08:46:57 -0800 Subject: [PATCH 1202/1897] Move chat command IDs into contrib --- .../contrib/terminal/common/terminal.ts | 7 ------- .../chat/browser/terminal.chat.contribution.ts | 17 ++++++++--------- .../chat/browser/terminalChat.ts | 10 ++++++++++ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index f2167db7f17..fc925b646bb 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -500,13 +500,6 @@ export const enum TerminalCommandId { FontZoomIn = 'workbench.action.terminal.fontZoomIn', FontZoomOut = 'workbench.action.terminal.fontZoomOut', FontZoomReset = 'workbench.action.terminal.fontZoomReset', - ChatFocus = 'workbench.action.terminal.chat.focus', - ChatHide = 'workbench.action.terminal.chat.close', - ChatMakeRequest = 'workbench.action.terminal.chat.makeRequest', - ChatCancel = 'workbench.action.terminal.chat.cancel', - ChatFeedbackHelpful = 'workbench.action.terminal.chat.feedbackHelpful', - ChatFeedbackUnhelpful = 'workbench.action.terminal.chat.feedbackUnhelpful', - ChatFeedbackReportIssue = 'workbench.action.terminal.chat.feedbackReportIssue', // Developer commands diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index 2972104787f..f8a1dd4b111 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -13,15 +13,14 @@ import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; -import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; registerTerminalContribution(TerminalChatController.ID, TerminalChatController, false); registerActiveXtermAction({ - id: TerminalCommandId.ChatFocus, + id: TerminalChatCommandId.Focus, title: localize2('workbench.action.terminal.focusChat', 'Focus Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, @@ -43,7 +42,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.ChatHide, + id: TerminalChatCommandId.Hide, title: localize2('workbench.action.terminal.closeChat', 'Close Chat'), keybinding: { primary: KeyCode.Escape, @@ -72,7 +71,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.ChatMakeRequest, + id: TerminalChatCommandId.MakeRequest, title: localize2('workbench.action.terminal.submitChat', 'Make Chat Request'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -105,7 +104,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.ChatCancel, + id: TerminalChatCommandId.Cancel, title: localize2('workbench.action.terminal.cancelChat', 'Cancel Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -126,7 +125,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.ChatFeedbackHelpful, + id: TerminalChatCommandId.FeedbackHelpful, title: localize2('feedbackHelpful', 'Helpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -146,7 +145,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.ChatFeedbackUnhelpful, + id: TerminalChatCommandId.FeedbackUnhelpful, title: localize2('feedbackUnhelpful', 'Helpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -166,7 +165,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.ChatFeedbackReportIssue, + id: TerminalChatCommandId.FeedbackReportIssue, title: localize2('reportIssue', 'Report Issue'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index 8b74aae7aeb..431a0e9c715 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -5,6 +5,16 @@ import { MenuId } from 'vs/platform/actions/common/actions'; +export const enum TerminalChatCommandId { + Focus = 'workbench.action.terminal.chat.focus', + Hide = 'workbench.action.terminal.chat.close', + MakeRequest = 'workbench.action.terminal.chat.makeRequest', + Cancel = 'workbench.action.terminal.chat.cancel', + FeedbackHelpful = 'workbench.action.terminal.chat.feedbackHelpful', + FeedbackUnhelpful = 'workbench.action.terminal.chat.feedbackUnhelpful', + FeedbackReportIssue = 'workbench.action.terminal.chat.feedbackReportIssue', +} + export const MENU_TERMINAL_CHAT_INPUT = MenuId.for('terminalChatInput'); export const MENU_TERMINAL_CHAT_WIDGET = MenuId.for('terminalChatWidget'); export const MENU_TERMINAL_CHAT_WIDGET_STATUS = MenuId.for('terminalChatWidget.status'); From 430db3ac9e053dada843e609422050da6938cf45 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 08:49:30 -0800 Subject: [PATCH 1203/1897] Move term chat actions into own file --- .../browser/terminal.chat.contribution.ts | 176 +---------------- .../chat/browser/terminalChatActions.ts | 182 ++++++++++++++++++ 2 files changed, 183 insertions(+), 175 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index f8a1dd4b111..a3a7b55757a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -3,183 +3,9 @@ * 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 { localize2 } from 'vs/nls'; -import { MenuId } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; registerTerminalContribution(TerminalChatController.ID, TerminalChatController, false); -registerActiveXtermAction({ - id: TerminalChatCommandId.Focus, - title: localize2('workbench.action.terminal.focusChat', 'Focus Chat'), - keybinding: { - primary: KeyMod.CtrlCmd | KeyCode.KeyI, - when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), - weight: KeybindingWeight.WorkbenchContrib - }, - f1: true, - precondition: ContextKeyExpr.and( - ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - ), - run: (_xterm, _accessor, activeInstance) => { - if (isDetachedTerminalInstance(activeInstance)) { - return; - } - const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.chatWidget?.reveal(); - } -}); - -registerActiveXtermAction({ - id: TerminalChatCommandId.Hide, - title: localize2('workbench.action.terminal.closeChat', 'Close Chat'), - keybinding: { - primary: KeyCode.Escape, - secondary: [KeyMod.Shift | KeyCode.Escape], - when: ContextKeyExpr.and(TerminalContextKeys.chatFocused, TerminalContextKeys.chatVisible), - weight: KeybindingWeight.WorkbenchContrib - }, - icon: Codicon.close, - menu: { - id: MENU_TERMINAL_CHAT_WIDGET, - group: 'main', - order: 2 - }, - f1: true, - precondition: ContextKeyExpr.and( - ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - ), - run: (_xterm, _accessor, activeInstance) => { - if (isDetachedTerminalInstance(activeInstance)) { - return; - } - const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.chatWidget?.hide(); - } -}); - -registerActiveXtermAction({ - id: TerminalChatCommandId.MakeRequest, - title: localize2('workbench.action.terminal.submitChat', 'Make Chat Request'), - precondition: ContextKeyExpr.and( - ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - // TerminalContextKeys.chatInputHasText - ), - icon: Codicon.send, - keybinding: { - when: TerminalContextKeys.chatRequestActive.negate(), - // TODO: - // when: CTX_INLINE_CHAT_FOCUSED, - weight: KeybindingWeight.EditorCore + 7, - primary: KeyCode.Enter - }, - menu: { - id: MENU_TERMINAL_CHAT_INPUT, - group: 'main', - order: 1, - // when: TerminalContextKeys.chatSessionInProgress.negate(), - // TODO: - // when: CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.isEqualTo(false) - }, - run: (_xterm, _accessor, activeInstance) => { - if (isDetachedTerminalInstance(activeInstance)) { - return; - } - const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.acceptInput(); - } -}); - -registerActiveXtermAction({ - id: TerminalChatCommandId.Cancel, - title: localize2('workbench.action.terminal.cancelChat', 'Cancel Chat'), - precondition: ContextKeyExpr.and( - ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive, - ), - icon: Codicon.debugStop, - menu: { - id: MenuId.ChatExecute, - group: 'navigation', - }, - run: (_xterm, _accessor, activeInstance) => { - if (isDetachedTerminalInstance(activeInstance)) { - return; - } - const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.chatWidget?.cancel(); - } -}); - -registerActiveXtermAction({ - id: TerminalChatCommandId.FeedbackHelpful, - title: localize2('feedbackHelpful', 'Helpful'), - precondition: ContextKeyExpr.and( - ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive, - ), - icon: Codicon.thumbsup, - menu: { - id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, - group: 'inline', - order: 1, - // TODO: Fill in ctx - // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), - }, - run: (_xterm, _accessor, activeInstance) => { - // TODO: Impl - } -}); - -registerActiveXtermAction({ - id: TerminalChatCommandId.FeedbackUnhelpful, - title: localize2('feedbackUnhelpful', 'Helpful'), - precondition: ContextKeyExpr.and( - ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive, - ), - icon: Codicon.thumbsup, - menu: { - id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, - group: 'inline', - order: 2, - // TODO: Fill in ctx - // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), - }, - run: (_xterm, _accessor, activeInstance) => { - // TODO: Impl - } -}); - -registerActiveXtermAction({ - id: TerminalChatCommandId.FeedbackReportIssue, - title: localize2('reportIssue', 'Report Issue'), - precondition: ContextKeyExpr.and( - ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive, - ), - icon: Codicon.thumbsup, - menu: { - id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, - group: 'inline', - order: 3, - // TODO: Fill in ctx - // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), - }, - run: (_xterm, _accessor, activeInstance) => { - // TODO: Impl - } -}); +import 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions'; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts new file mode 100644 index 00000000000..161b8515478 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -0,0 +1,182 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { localize2 } from 'vs/nls'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; + +registerActiveXtermAction({ + id: TerminalChatCommandId.Focus, + title: localize2('workbench.action.terminal.focusChat', 'Focus Chat'), + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.KeyI, + when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), + weight: KeybindingWeight.WorkbenchContrib + }, + f1: true, + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + ), + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.chatWidget?.reveal(); + } +}); + +registerActiveXtermAction({ + id: TerminalChatCommandId.Hide, + title: localize2('workbench.action.terminal.closeChat', 'Close Chat'), + keybinding: { + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape], + when: ContextKeyExpr.and(TerminalContextKeys.chatFocused, TerminalContextKeys.chatVisible), + weight: KeybindingWeight.WorkbenchContrib + }, + icon: Codicon.close, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET, + group: 'main', + order: 2 + }, + f1: true, + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + ), + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.chatWidget?.hide(); + } +}); + +registerActiveXtermAction({ + id: TerminalChatCommandId.MakeRequest, + title: localize2('workbench.action.terminal.submitChat', 'Make Chat Request'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + // TerminalContextKeys.chatInputHasText + ), + icon: Codicon.send, + keybinding: { + when: TerminalContextKeys.chatRequestActive.negate(), + // TODO: + // when: CTX_INLINE_CHAT_FOCUSED, + weight: KeybindingWeight.EditorCore + 7, + primary: KeyCode.Enter + }, + menu: { + id: MENU_TERMINAL_CHAT_INPUT, + group: 'main', + order: 1, + // when: TerminalContextKeys.chatSessionInProgress.negate(), + // TODO: + // when: CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.isEqualTo(false) + }, + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.acceptInput(); + } +}); + +registerActiveXtermAction({ + id: TerminalChatCommandId.Cancel, + title: localize2('workbench.action.terminal.cancelChat', 'Cancel Chat'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalContextKeys.chatRequestActive, + ), + icon: Codicon.debugStop, + menu: { + id: MenuId.ChatExecute, + group: 'navigation', + }, + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.chatWidget?.cancel(); + } +}); + +registerActiveXtermAction({ + id: TerminalChatCommandId.FeedbackHelpful, + title: localize2('feedbackHelpful', 'Helpful'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalContextKeys.chatRequestActive, + ), + icon: Codicon.thumbsup, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, + group: 'inline', + order: 1, + // TODO: Fill in ctx + // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), + }, + run: (_xterm, _accessor, activeInstance) => { + // TODO: Impl + } +}); + +registerActiveXtermAction({ + id: TerminalChatCommandId.FeedbackUnhelpful, + title: localize2('feedbackUnhelpful', 'Helpful'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalContextKeys.chatRequestActive, + ), + icon: Codicon.thumbsup, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, + group: 'inline', + order: 2, + // TODO: Fill in ctx + // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), + }, + run: (_xterm, _accessor, activeInstance) => { + // TODO: Impl + } +}); + +registerActiveXtermAction({ + id: TerminalChatCommandId.FeedbackReportIssue, + title: localize2('reportIssue', 'Report Issue'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalContextKeys.chatRequestActive, + ), + icon: Codicon.thumbsup, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, + group: 'inline', + order: 3, + // TODO: Fill in ctx + // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), + }, + run: (_xterm, _accessor, activeInstance) => { + // TODO: Impl + } +}); From 5f33bf34ce74baeebfa231dfd3dc2f15a247ae5c Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 13 Feb 2024 17:51:04 +0100 Subject: [PATCH 1204/1897] wip --- .../multiDiffEditorWidget.ts | 4 +- .../multiDiffEditorWidgetImpl.ts | 32 ++++++++++-- src/vs/workbench/common/editor.ts | 5 ++ .../bulkEdit/browser/preview/bulkEditPane.ts | 49 +++++++++---------- .../browser/multiDiffEditor.ts | 4 +- .../services/editor/common/editorService.ts | 1 - 6 files changed, 61 insertions(+), 34 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index 9a19e5e5669..e2392cc8b4a 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -44,8 +44,8 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public scrollTo(uri: URI): void { - this._widgetImpl.get().scrollTo(uri); + public reveal(resource: { original: URI } | { modified: URI }, lineNumber: number): void { + this._widgetImpl.get().reveal(resource, lineNumber); } public createViewModel(model: IMultiDiffEditorModel): MultiDiffEditorViewModel { diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 5619b08b2cd..b8547c7d347 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -25,6 +25,7 @@ import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _elements = h('div.monaco-component.multiDiffEditor', [ @@ -104,6 +105,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _workbenchUIElementFactory: IWorkbenchUIElementFactory, @IContextKeyService private readonly _parentContextKeyService: IContextKeyService, @IInstantiationService private readonly _parentInstantiationService: IInstantiationService, + @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); @@ -186,10 +188,20 @@ export class MultiDiffEditorWidgetImpl extends Disposable { this._scrollableElement.setScrollPosition({ scrollLeft: scrollState.left, scrollTop: scrollState.top }); } - public scrollTo(originalUri: URI): void { + public reveal(resource: IMultiDiffResource, lineNumber: number): void { const viewItems = this._viewItems.get(); - const index = viewItems?.findIndex(item => item.viewModel.originalUri?.toString() === originalUri.toString()); - let scrollTop = 0; + let searchCallback: (item: VirtualizedViewItem) => boolean; + if (isMultiDiffOriginalResourceUri(resource)) { + searchCallback = (item) => item.viewModel.originalUri?.toString() === resource.original.toString(); + } else { + searchCallback = (item) => item.viewModel.modifiedUri?.toString() === resource.modified.toString(); + } + const index = viewItems.findIndex(searchCallback); + const scrollTopWithinItem = (lineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); + // todo@aiday-mar: need to find the actual scroll top given the line number specific to the original or modified uri + // following does not neccessarily correspond to the appropriate scroll top within the editor + const maxScroll = viewItems[index].template.get()?.maxScroll.get().maxScroll; + let scrollTop = (maxScroll && scrollTopWithinItem < maxScroll) ? scrollTopWithinItem : 0; for (let i = 0; i < index; i++) { scrollTop += viewItems[i].contentHeight.get() + this._spaceBetweenPx; } @@ -289,6 +301,20 @@ interface IMultiDiffDocState { selections?: ISelection[]; } +interface IMultiDiffOriginalResource { + original: URI; +} + +interface IMultiDiffModifiedResource { + modified: URI; +} + +function isMultiDiffOriginalResourceUri(obj: any): obj is IMultiDiffOriginalResource { + return 'original' in obj && obj.original instanceof URI; +} + +type IMultiDiffResource = IMultiDiffOriginalResource | IMultiDiffModifiedResource; + class VirtualizedViewItem extends Disposable { private readonly _templateRef = this._register(disposableObservableValue | undefined>(this, undefined)); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index bf6785d9675..322582ad33c 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -505,6 +505,11 @@ export interface IResourceMultiDiffEditorInput extends IBaseUntypedEditorInput { * If not set, the resources are dynamically derived from the {@link multiDiffSource}. */ readonly resources?: IResourceDiffEditorInput[]; + + /** + * Reveal the following resource on open + */ + readonly revealResource?: IResourceDiffEditorInput; } export type IResourceMergeEditorInputSide = (IResourceEditorInput | ITextResourceEditorInput) & { detail?: string }; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 2d8031cbe77..ff0cc489d28 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -11,7 +11,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IThemeService } from 'vs/platform/theme/common/themeService'; import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { BulkEditPreviewProvider, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; +import { BulkEditPreviewProvider, BulkFileOperation, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { URI } from 'vs/base/common/uri'; @@ -36,8 +36,7 @@ import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { MultiDiffEditor } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor'; +import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IResourceDiffEditorInput } from 'vs/workbench/common/editor'; const enum State { @@ -69,7 +68,8 @@ export class BulkEditPane extends ViewPane { private _currentResolve?: (edit?: ResourceEdit[]) => void; private _currentInput?: BulkFileOperations; private _currentProvider?: BulkEditPreviewProvider; - private _multiDiffEditor?: MultiDiffEditor; + private _fileOperations?: BulkFileOperation[]; + private _resources?: IResourceDiffEditorInput[]; constructor( options: IViewletViewOptions, @@ -98,11 +98,6 @@ export class BulkEditPane extends ViewPane { this._ctxHasCategories = BulkEditPane.ctxHasCategories.bindTo(contextKeyService); this._ctxGroupByFile = BulkEditPane.ctxGroupByFile.bindTo(contextKeyService); this._ctxHasCheckedChanges = BulkEditPane.ctxHasCheckedChanges.bindTo(contextKeyService); - this._disposables.add(this._editorService.onDidCloseEditor((e) => { - if (this._multiDiffEditor && e.editor === this._multiDiffEditor.input && e.groupId === this._multiDiffEditor.group?.id) { - this._multiDiffEditor = undefined; - } - })); } override dispose(): void { @@ -281,10 +276,6 @@ export class BulkEditPane extends ViewPane { this._currentInput = undefined; this._setState(State.Message); this._sessionDisposables.clear(); - if (this._multiDiffEditor && this._multiDiffEditor.input && this._multiDiffEditor.group) { - this._editorService.closeEditor({ editor: this._multiDiffEditor.input, groupId: this._multiDiffEditor.group.id }); - this._multiDiffEditor = undefined; - } } toggleChecked() { @@ -347,12 +338,26 @@ export class BulkEditPane extends ViewPane { return; } - if (this._multiDiffEditor) { - // Multi diff editor already visible - this._multiDiffEditor.scrollTo(fileElement.edit.uri); - return; + let resources: IResourceDiffEditorInput[]; + if (this._fileOperations === fileOperations && this._resources) { + resources = this._resources; + } else { + resources = await this._getResources(fileOperations); } + const revealResource = resources.find(r => r.original.resource!.toString() === fileElement.edit.uri.toString()); + const multiDiffSource = URI.from({ scheme: 'refactor-preview' }); + const label = 'Refactor Preview'; + this._editorService.openEditor({ + multiDiffSource, + revealResource, + resources, + label, + description: label, + options, + }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + } + private async _getResources(fileOperations: BulkFileOperation[]): Promise { const resources: IResourceDiffEditorInput[] = []; for (const operation of fileOperations) { const operationUri = operation.uri; @@ -379,15 +384,7 @@ export class BulkEditPane extends ViewPane { }); } } - const multiDiffSource = URI.from({ scheme: 'refactor-preview' }); - const label = 'Refactor Preview'; - this._multiDiffEditor = await this._editorService.openEditor({ - multiDiffSource, - resources, - label, - description: label, - options, - }) as MultiDiffEditor; + return resources; } private _onContextMenu(e: ITreeContextMenuEvent): void { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 21b1d64a6fa..be2fab93175 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -72,8 +72,8 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 1be2060f98c..19b51a652c7 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -236,7 +236,6 @@ export interface IEditorService { * Open an editor in an editor group. * * @param editor the editor to open - * @param options the options to use for the editor * @param group the target group. If unspecified, the editor will open in the currently * active group. Use `SIDE_GROUP` to open the editor in a new editor group to the side * of the currently active group. From ab50eb72b355613d6f028289e41fccb4fcd9dc91 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 09:01:19 -0800 Subject: [PATCH 1205/1897] Fix compile error, add todo for layer breaker --- .../contrib/chat/electron-sandbox/actions/voiceChatActions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index b41804eb8cc..74ec952ba1c 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -54,6 +54,7 @@ import { IVoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ThemeIcon } from 'vs/base/common/themables'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { MENU_TERMINAL_CHAT_INPUT } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); @@ -528,7 +529,8 @@ export class StartVoiceChatAction extends Action2 { order: -1 }, { - id: MenuId.TerminalChat, + // TODO: Fix layer breaker, chat can't depend on terminalContrib + id: MENU_TERMINAL_CHAT_INPUT, when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.negate()), group: 'main', order: -1 From 283b211fc8544baaa577021b2d507fb966ac1975 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 11:09:21 -0600 Subject: [PATCH 1206/1897] alert single code block --- .../chat/browser/terminalChatController.ts | 13 ++++++++----- .../chat/browser/terminalChatWidget.ts | 4 +--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 8308105d9f5..c8f3ec69958 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -19,6 +19,7 @@ import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; import { generateUuid } from 'vs/base/common/uuid'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { marked } from 'vs/base/common/marked/marked'; export class TerminalChatController extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; @@ -133,12 +134,14 @@ export class TerminalChatController extends Disposable implements ITerminalContr variables: resolvedVariables, variables2: { message: this._chatWidget?.rawValue?.input() || '', variables: [] } }; - // TODO: use token - const agentResult = await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], CancellationToken.None); - console.log(agentResult); - console.log(message); - alert(message); this._chatWidget?.rawValue?.setValue(); + + // TODO: use token + await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], CancellationToken.None); + const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); + if (codeBlock) { + alert(codeBlock); + } // TODO: accessibility announcement, help dialog } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 330be0b35a5..4a5dbb55994 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -11,7 +11,6 @@ import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; @@ -32,8 +31,7 @@ export class TerminalChatWidget extends Disposable { private readonly _container: HTMLElement, private readonly _instance: ITerminalInstance, @IInstantiationService instantiationService: IInstantiationService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IChatAgentService private readonly _chatAgentService: IChatAgentService + @IContextKeyService private readonly _contextKeyService: IContextKeyService ) { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); From 1f57cd8267e146bbedc1d7a00d9c09baadb6a4f1 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:19:53 +0100 Subject: [PATCH 1207/1897] SCM - fix issue related to event handler registration (#205120) --- .../contrib/scm/browser/scmViewPane.ts | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 73ce611ad21..500967073c5 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -104,6 +104,7 @@ import { IMenuWorkbenchToolBarOptions, MenuWorkbenchToolBar, WorkbenchToolBar } import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem'; import { clamp } from 'vs/base/common/numbers'; +import { ILogService } from 'vs/platform/log/common/log'; // type SCMResourceTreeNode = IResourceNode; // type SCMHistoryItemChangeResourceTreeNode = IResourceNode; @@ -2753,6 +2754,7 @@ export class SCMViewPane extends ViewPane { options: IViewPaneOptions, @ICommandService private readonly commandService: ICommandService, @IEditorService private readonly editorService: IEditorService, + @ILogService private readonly logService: ILogService, @IMenuService private readonly menuService: IMenuService, @ISCMService private readonly scmService: ISCMService, @ISCMViewService private readonly scmViewService: ISCMViewService, @@ -3117,9 +3119,22 @@ export class SCMViewPane extends ViewPane { repositoryDisposables.add(repository.input.onDidChangeVisibility(() => this.updateChildren(repository))); repositoryDisposables.add(repository.provider.onDidChangeResourceGroups(() => this.updateChildren(repository))); - if (repository.provider.historyProvider) { - repositoryDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => this.updateChildren(repository))); - } + const onDidChangeHistoryProvider = () => { + if (!repository.provider.historyProvider) { + this.logService.debug('SCMViewPane:onDidChangeVisibleRepositories - no history provider present'); + return; + } + + repositoryDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => { + this.updateChildren(repository); + this.logService.debug('SCMViewPane:onDidChangeCurrentHistoryItemGroup - update children'); + })); + + this.logService.debug('SCMViewPane:onDidChangeVisibleRepositories - onDidChangeCurrentHistoryItemGroup listener added'); + }; + + repositoryDisposables.add(repository.provider.onDidChangeHistoryProvider(onDidChangeHistoryProvider)); + onDidChangeHistoryProvider(); const resourceGroupDisposables = repositoryDisposables.add(new DisposableMap()); @@ -3411,6 +3426,7 @@ class SCMTreeDataSource implements IAsyncDataSource ViewMode, @IConfigurationService private readonly configurationService: IConfigurationService, + @ILogService private readonly logService: ILogService, @ISCMViewService private readonly scmViewService: ISCMViewService, @IUriIdentityService private uriIdentityService: IUriIdentityService, ) { @@ -3767,9 +3783,22 @@ class SCMTreeDataSource implements IAsyncDataSource this.historyProviderCache.delete(repository))); - } + const onDidChangeHistoryProvider = () => { + if (!repository.provider.historyProvider) { + this.logService.debug('SCMTreeDataSource:onDidChangeVisibleRepositories - no history provider present'); + return; + } + + repositoryDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => { + this.historyProviderCache.delete(repository); + this.logService.debug('SCMTreeDataSource:onDidChangeCurrentHistoryItemGroup - cache cleared'); + })); + + this.logService.debug('SCMTreeDataSource:onDidChangeVisibleRepositories - onDidChangeCurrentHistoryItemGroup listener added'); + }; + + repositoryDisposables.add(repository.provider.onDidChangeHistoryProvider(onDidChangeHistoryProvider)); + onDidChangeHistoryProvider(); this.repositoryDisposables.set(repository, repositoryDisposables); } From a2c91f1f288f66c15d1e5614478b21fbfa314964 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 13 Feb 2024 09:22:06 -0800 Subject: [PATCH 1208/1897] debug: update visualizers tree proposal, some initial implementation (#204877) --- .../api/browser/mainThreadDebugService.ts | 15 ++ .../workbench/api/common/extHost.api.impl.ts | 4 + .../workbench/api/common/extHost.protocol.ts | 8 +- .../api/common/extHostDebugService.ts | 121 ++++++++++++--- .../api/common/extHostTypeConverters.ts | 20 ++- .../contrib/debug/browser/baseDebugView.ts | 26 +++- .../contrib/debug/browser/debugHover.ts | 26 ++-- .../workbench/contrib/debug/browser/repl.ts | 2 +- .../contrib/debug/browser/variablesView.ts | 142 ++++++++++++++++-- .../debug/browser/watchExpressionsView.ts | 70 +++++---- .../workbench/contrib/debug/common/debug.ts | 35 ++++- .../contrib/debug/common/debugModel.ts | 32 +++- .../contrib/debug/common/debugViewModel.ts | 21 +++ .../contrib/debug/common/debugVisualizers.ts | 129 +++++++++++++++- .../vscode.proposed.debugVisualization.d.ts | 81 +++++++++- 15 files changed, 640 insertions(+), 92 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index ea668f70f3c..1b383b49d75 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -30,6 +30,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb private readonly _debugAdapterDescriptorFactories: Map; private readonly _extHostKnownSessions: Set; private readonly _visualizerHandles = new Map(); + private readonly _visualizerTreeHandles = new Map(); constructor( extHostContext: IExtHostContext, @@ -112,6 +113,20 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb this.sendBreakpointsAndListen(); } + $registerDebugVisualizerTree(treeId: string, canEdit: boolean): void { + this.visualizerService.registerTree(treeId, { + disposeItem: id => this._proxy.$disposeVisualizedTree(id), + getChildren: e => this._proxy.$getVisualizerTreeItemChildren(treeId, e), + getTreeItem: e => this._proxy.$getVisualizerTreeItem(treeId, e), + editItem: canEdit ? ((e, v) => this._proxy.$editVisualizerTreeItem(e, v)) : undefined + }); + } + + $unregisterDebugVisualizerTree(treeId: string): void { + this._visualizerTreeHandles.get(treeId)?.dispose(); + this._visualizerTreeHandles.delete(treeId); + } + $registerDebugVisualizer(extensionId: string, id: string): void { const handle = this.visualizerService.register({ extensionId: new ExtensionIdentifier(extensionId), diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 881bd742f46..ac17b86e67c 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1229,6 +1229,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'debugVisualization'); return extHostDebugService.registerDebugVisualizationProvider(extension, id, provider); }, + registerDebugVisualizationTreeProvider(id, provider) { + checkProposedApiEnabled(extension, 'debugVisualization'); + return extHostDebugService.registerDebugVisualizationTree(extension, id, provider); + }, onDidStartDebugSession(listener, thisArg?, disposables?) { return _asExtensionEvent(extHostDebugService.onDidStartDebugSession)(listener, thisArg, disposables); }, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c17e879971a..c3382650e3e 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -55,7 +55,7 @@ import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/c import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; import { IChatDynamicRequest, IChatProgress, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; -import { DebugConfigurationProviderTriggerKind, MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext } from 'vs/workbench/contrib/debug/common/debug'; +import { DebugConfigurationProviderTriggerKind, MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatFollowup, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; @@ -1565,6 +1565,8 @@ export interface MainThreadDebugServiceShape extends IDisposable { $unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[], dataBreakpointIds: string[]): Promise; $registerDebugVisualizer(extensionId: string, id: string): void; $unregisterDebugVisualizer(extensionId: string, id: string): void; + $registerDebugVisualizerTree(treeId: string, canEdit: boolean): void; + $unregisterDebugVisualizerTree(treeId: string): void; } export interface IOpenUriOptions { @@ -2360,6 +2362,10 @@ export interface ExtHostDebugServiceShape { $resolveDebugVisualizer(id: number, token: CancellationToken): Promise; $executeDebugVisualizerCommand(id: number): Promise; $disposeDebugVisualizers(ids: number[]): void; + $getVisualizerTreeItem(treeId: string, element: IDebugVisualizationContext): Promise; + $getVisualizerTreeItemChildren(treeId: string, element: number): Promise; + $editVisualizerTreeItem(element: number, value: string): Promise; + $disposeVisualizedTree(element: number): void; } diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index ced402e29f8..133ff216767 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -7,7 +7,7 @@ import { asPromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ISignService } from 'vs/platform/sign/common/sign'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -18,7 +18,7 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { Breakpoint, DataBreakpoint, DebugAdapterExecutable, DebugAdapterInlineImplementation, DebugAdapterNamedPipeServer, DebugAdapterServer, DebugConsoleMode, Disposable, FunctionBreakpoint, Location, Position, setBreakpointId, SourceBreakpoint, ThreadFocus, StackFrameFocus, ThemeIcon } from 'vs/workbench/api/common/extHostTypes'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; -import { MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugAdapter, IDebugAdapterExecutable, IDebugAdapterNamedPipeServer, IDebugAdapterServer, IDebugVisualization, IDebugVisualizationContext, IDebuggerContribution, DebugVisualizationType } from 'vs/workbench/contrib/debug/common/debug'; +import { MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugAdapter, IDebugAdapterExecutable, IDebugAdapterNamedPipeServer, IDebugAdapterServer, IDebugVisualization, IDebugVisualizationContext, IDebuggerContribution, DebugVisualizationType, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; import { convertToDAPaths, convertToVSCPaths, isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; @@ -28,6 +28,7 @@ import { IExtHostVariableResolverProvider } from './extHostVariableResolverServi import { toDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon as ThemeIconUtils } from 'vs/base/common/themables'; import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import * as Convert from 'vs/workbench/api/common/extHostTypeConverters'; export const IExtHostDebugService = createDecorator('IExtHostDebugService'); @@ -54,6 +55,7 @@ export interface IExtHostDebugService extends ExtHostDebugServiceShape { registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable; registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable; registerDebugVisualizationProvider(extension: IExtensionDescription, id: string, provider: vscode.DebugVisualizationProvider): vscode.Disposable; + registerDebugVisualizationTree(extension: IExtensionDescription, id: string, provider: vscode.DebugVisualizationTree): vscode.Disposable; asDebugSourceUri(source: vscode.DebugProtocolSource, session?: vscode.DebugSession): vscode.Uri; } @@ -100,11 +102,16 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E private _debugAdapters: Map; private _debugAdaptersTrackers: Map; + + private _debugVisualizationTreeItemIdsCounter = 0; private readonly _debugVisualizationProviders = new Map(); + private readonly _debugVisualizationTrees = new Map(); + private readonly _debugVisualizationTreeItemIds = new WeakMap(); + private readonly _debugVisualizationElements = new Map(); private _signService: ISignService | undefined; - private readonly _visualizers = new Map(); + private readonly _visualizers = new Map(); private _visualizerIdCounter = 0; constructor( @@ -151,6 +158,77 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E }); } + public async $getVisualizerTreeItem(treeId: string, element: IDebugVisualizationContext): Promise { + const context = this.hydrateVisualizationContext(element); + if (!context) { + return undefined; + } + + const item = await this._debugVisualizationTrees.get(treeId)?.getTreeItem?.(context); + return item ? this.convertVisualizerTreeItem(treeId, item) : undefined; + } + + public registerDebugVisualizationTree(manifest: Readonly, id: string, provider: vscode.DebugVisualizationTree): vscode.Disposable { + const extensionId = ExtensionIdentifier.toKey(manifest.identifier); + const key = this.extensionVisKey(extensionId, id); + if (this._debugVisualizationProviders.has(key)) { + throw new Error(`A debug visualization provider with id '${id}' is already registered`); + } + + this._debugVisualizationTrees.set(key, provider); + this._debugServiceProxy.$registerDebugVisualizerTree(key, !!provider.editItem); + return toDisposable(() => { + this._debugServiceProxy.$unregisterDebugVisualizerTree(key); + this._debugVisualizationTrees.delete(id); + }); + } + + public async $getVisualizerTreeItemChildren(treeId: string, element: number): Promise { + const item = this._debugVisualizationElements.get(element)?.item; + if (!item) { + return []; + } + + const children = await this._debugVisualizationTrees.get(treeId)?.getChildren?.(item); + return children?.map(i => this.convertVisualizerTreeItem(treeId, i)) || []; + } + + public async $editVisualizerTreeItem(element: number, value: string): Promise { + const e = this._debugVisualizationElements.get(element); + if (!e) { return undefined; } + + const r = await this._debugVisualizationTrees.get(e.provider)?.editItem?.(e.item, value); + return this.convertVisualizerTreeItem(e.provider, r || e.item); + } + + public $disposeVisualizedTree(element: number): void { + const root = this._debugVisualizationElements.get(element); + if (!root) { + return; + } + + const queue = [root.children]; + for (const children of queue) { + if (children) { + for (const child of children) { + queue.push(this._debugVisualizationElements.get(child)?.children); + this._debugVisualizationElements.delete(child); + } + } + } + } + + private convertVisualizerTreeItem(treeId: string, item: vscode.DebugTreeItem): IDebugVisualizationTreeItem { + let id = this._debugVisualizationTreeItemIds.get(item); + if (!id) { + id = this._debugVisualizationTreeItemIdsCounter++; + this._debugVisualizationTreeItemIds.set(item, id); + this._debugVisualizationElements.set(id, { provider: treeId, item }); + } + + return Convert.DebugTreeItem.from(item, id); + } + public asDebugSourceUri(src: vscode.DebugProtocolSource, session?: vscode.DebugSession): URI { const source = src; @@ -224,7 +302,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E throw new Error(`No debug visualizer found with id '${id}'`); } - let { v, provider } = visualizer; + let { v, provider, extensionId } = visualizer; if (!v.visualization) { v = await provider.resolveDebugVisualization?.(v, token) || v; visualizer.v = v; @@ -234,7 +312,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E throw new Error(`No visualization returned from resolveDebugVisualization in '${provider}'`); } - return this.serializeVisualization(v.visualization)!; + return this.serializeVisualization(extensionId, v.visualization)!; } public async $executeDebugVisualizerCommand(id: number): Promise { @@ -249,21 +327,26 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E } } - public async $provideDebugVisualizers(extensionId: string, id: string, context: IDebugVisualizationContext, token: CancellationToken): Promise { + private hydrateVisualizationContext(context: IDebugVisualizationContext): vscode.DebugVisualizationContext | undefined { const session = this._debugSessions.get(context.sessionId); - const key = this.extensionVisKey(extensionId, id); - const provider = this._debugVisualizationProviders.get(key); - if (!session || !provider) { - return []; // probably ended in the meantime - } - - const visualizations = await provider.provideDebugVisualization({ + return session && { session: session.api, variable: context.variable, containerId: context.containerId, frameId: context.frameId, threadId: context.threadId, - }, token); + }; + } + + public async $provideDebugVisualizers(extensionId: string, id: string, context: IDebugVisualizationContext, token: CancellationToken): Promise { + const contextHydrated = this.hydrateVisualizationContext(context); + const key = this.extensionVisKey(extensionId, id); + const provider = this._debugVisualizationProviders.get(key); + if (!contextHydrated || !provider) { + return []; // probably ended in the meantime + } + + const visualizations = await provider.provideDebugVisualization(contextHydrated, token); if (!visualizations) { return []; @@ -271,14 +354,14 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E return visualizations.map(v => { const id = ++this._visualizerIdCounter; - this._visualizers.set(id, { v, provider }); + this._visualizers.set(id, { v, provider, extensionId }); const icon = v.iconPath ? this.getIconPathOrClass(v.iconPath) : undefined; return { id, name: v.name, iconClass: icon?.iconClass, iconPath: icon?.iconPath, - visualization: this.serializeVisualization(v.visualization), + visualization: this.serializeVisualization(extensionId, v.visualization), }; }); } @@ -962,7 +1045,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E return `${extensionId}\0${id}`; } - private serializeVisualization(viz: vscode.DebugVisualization['visualization']): MainThreadDebugVisualization | undefined { + private serializeVisualization(extensionId: string, viz: vscode.DebugVisualization['visualization']): MainThreadDebugVisualization | undefined { if (!viz) { return undefined; } @@ -971,6 +1054,10 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E return { type: DebugVisualizationType.Command }; } + if ('treeId' in viz) { + return { type: DebugVisualizationType.Tree, id: `${extensionId}\0${viz.treeId}` }; + } + throw new Error('Unsupported debug visualization type'); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 2c4bba8099f..91f918b71cf 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -14,10 +14,12 @@ import { marked } from 'vs/base/common/marked/marked'; import { parse, revive } from 'vs/base/common/marshalling'; import { Mimes } from 'vs/base/common/mime'; import { cloneAndChange } from 'vs/base/common/objects'; +import { basename } from 'vs/base/common/resources'; import { isEmptyObject, isNumber, isString, isUndefinedOrNull } from 'vs/base/common/types'; import { URI, UriComponents, isUriComponents } from 'vs/base/common/uri'; import { IURITransformer } from 'vs/base/common/uriIpc'; import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; +import { IOffsetRange } from 'vs/editor/common/core/offsetRange'; import { IPosition } from 'vs/editor/common/core/position'; import * as editorRange from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; @@ -31,6 +33,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { IMarkerData, IRelatedInformation, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; import { ProgressLocation as MainProgressLocation } from 'vs/platform/progress/common/progress'; import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; +import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { getPrivateApiFor } from 'vs/workbench/api/common/extHostTestingPrivateApi'; import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/editor'; import { IViewBadge } from 'vs/workbench/common/views'; @@ -38,6 +41,7 @@ import { IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/c import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; import { IChatCommandButton, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTreeData, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { DebugTreeItemCollapsibleState, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; import { IInlineChatCommandFollowup, IInlineChatFollowup, IInlineChatReplyFollowup, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; @@ -50,9 +54,6 @@ import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import type * as vscode from 'vscode'; import * as types from './extHostTypes'; -import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; -import { basename } from 'vs/base/common/resources'; -import { IOffsetRange } from 'vs/editor/common/core/offsetRange'; export namespace Command { @@ -2702,3 +2703,16 @@ export namespace TerminalQuickFix { return converter.toInternal(quickFix, disposables); } } + +export namespace DebugTreeItem { + export function from(item: vscode.DebugTreeItem, id: number): IDebugVisualizationTreeItem { + return { + id, + label: item.label, + description: item.description, + canEdit: item.canEdit, + collapsibleState: (item.collapsibleState || DebugTreeItemCollapsibleState.None) as DebugTreeItemCollapsibleState, + contextValue: item.contextValue, + }; + } +} diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 4399464dbc0..d55c8cefd4d 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -8,7 +8,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; -import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { IAsyncDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; @@ -145,6 +145,30 @@ export interface IExpressionTemplateData { currentElement: IExpression | undefined; } +export abstract class AbstractExpressionDataSource implements IAsyncDataSource { + constructor(@IDebugService protected debugService: IDebugService) { } + + public abstract hasChildren(element: Input | Element): boolean; + + public getChildren(element: Input | Element): Promise { + const vm = this.debugService.getViewModel(); + return this.doGetChildren(element).then(r => { + let dup: Element[] | undefined; + for (let i = 0; i < r.length; i++) { + const visualized = vm.getVisualizedExpression(r[i] as IExpression); + if (visualized) { + dup ||= r.slice(); + dup[i] = visualized as Element; + } + } + + return dup || r; + }); + } + + protected abstract doGetChildren(element: Input | Element): Promise; +} + export abstract class AbstractExpressionsRenderer implements ITreeRenderer { constructor( diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 369949fb9f7..c0e1b530b3e 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -9,7 +9,7 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; -import { IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; +import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { coalesce } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -32,11 +32,11 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { ILogService } from 'vs/platform/log/common/log'; import { asCssVariable, editorHoverBackground, editorHoverBorder, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; -import { renderExpressionValue } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { AbstractExpressionDataSource, renderExpressionValue } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; -import { VariablesRenderer, openContextMenuForVariableTreeElement } from 'vs/workbench/contrib/debug/browser/variablesView'; +import { VariablesRenderer, VisualizedVariableRenderer, openContextMenuForVariableTreeElement } from 'vs/workbench/contrib/debug/browser/variablesView'; import { IDebugService, IDebugSession, IExpression, IExpressionContainer, IStackFrame } from 'vs/workbench/contrib/debug/common/debug'; -import { Expression, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Expression, Variable, VisualizedExpression } from 'vs/workbench/contrib/debug/common/debugModel'; import { getEvaluatableExpressionAtPosition } from 'vs/workbench/contrib/debug/common/debugUtils'; const $ = dom.$; @@ -127,9 +127,12 @@ export class DebugHoverWidget implements IContentWidget { this.treeContainer.setAttribute('role', 'tree'); const tip = dom.append(this.complexValueContainer, $('.tip')); tip.textContent = nls.localize({ key: 'quickTip', comment: ['"switch to editor language hover" means to show the programming language hover widget instead of the debug hover'] }, 'Hold {0} key to switch to editor language hover', isMacintosh ? 'Option' : 'Alt'); - const dataSource = new DebugHoverDataSource(); + const dataSource = new DebugHoverDataSource(this.debugService); const linkeDetector = this.instantiationService.createInstance(LinkDetector); - this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'DebugHover', this.treeContainer, new DebugHoverDelegate(), [this.instantiationService.createInstance(VariablesRenderer, linkeDetector)], + this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'DebugHover', this.treeContainer, new DebugHoverDelegate(), [ + this.instantiationService.createInstance(VariablesRenderer, linkeDetector), + this.instantiationService.createInstance(VisualizedVariableRenderer, linkeDetector), + ], dataSource, { accessibilityProvider: new DebugHoverAccessibilityProvider(), mouseSupport: false, @@ -141,6 +144,8 @@ export class DebugHoverWidget implements IContentWidget { } }); + this.toDispose.push(VisualizedVariableRenderer.rendererOnVisualizationRange(this.debugService.getViewModel(), this.tree)); + this.valueContainer = $('.value'); this.valueContainer.tabIndex = 0; this.valueContainer.setAttribute('role', 'tooltip'); @@ -403,13 +408,13 @@ class DebugHoverAccessibilityProvider implements IListAccessibilityProvider { +class DebugHoverDataSource extends AbstractExpressionDataSource { - hasChildren(element: IExpression): boolean { + public override hasChildren(element: IExpression): boolean { return element.hasChildren; } - getChildren(element: IExpression): Promise { + public override doGetChildren(element: IExpression): Promise { return element.getChildren(); } } @@ -420,6 +425,9 @@ class DebugHoverDelegate implements IListVirtualDelegate { } getTemplateId(element: IExpression): string { + if (element instanceof VisualizedExpression) { + return VisualizedVariableRenderer.ID; + } return VariablesRenderer.ID; } } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 4eef489c49f..ffcda601ff9 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -626,7 +626,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { new ReplRawObjectsRenderer(linkDetector), ], // https://github.com/microsoft/TypeScript/issues/32526 - new ReplDataSource() as IAsyncDataSource, + new ReplDataSource() satisfies IAsyncDataSource, { filter: this.filter, accessibilityProvider: new ReplAccessibilityProvider(), diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index df67a66260a..ef5e0cfb7d0 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -8,15 +8,15 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; -import { IAsyncDataSource, ITreeContextMenuEvent, ITreeMouseEvent, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { AsyncDataTree, IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; +import { ITreeContextMenuEvent, ITreeMouseEvent, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Action, IAction } from 'vs/base/common/actions'; import { coalesce } from 'vs/base/common/arrays'; import { RunOnceScheduler, timeout } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -37,11 +37,11 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ViewAction, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IViewDescriptorService } from 'vs/workbench/common/views'; -import { AbstractExpressionsRenderer, IExpressionTemplateData, IInputBoxOptions, renderVariable, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { AbstractExpressionDataSource, AbstractExpressionsRenderer, IExpressionTemplateData, IInputBoxOptions, renderExpressionValue, renderVariable, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; -import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_VARIABLES_FOCUSED, DebugVisualizationType, IDataBreakpointInfoResponse, IDebugService, IExpression, IScope, IStackFrame, VARIABLES_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_VARIABLES_FOCUSED, DebugVisualizationType, IDataBreakpointInfoResponse, IDebugService, IExpression, IScope, IStackFrame, IViewModel, VARIABLES_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; import { getContextForVariable } from 'vs/workbench/contrib/debug/common/debugContext'; -import { ErrorScope, Expression, Scope, StackFrame, Variable, getUriForDebugMemory } from 'vs/workbench/contrib/debug/common/debugModel'; +import { ErrorScope, Expression, Scope, StackFrame, Variable, VisualizedExpression, getUriForDebugMemory } from 'vs/workbench/contrib/debug/common/debugModel'; import { DebugVisualizer, IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -118,10 +118,15 @@ export class VariablesView extends ViewPane { this.element.classList.add('debug-pane'); container.classList.add('debug-variables'); const treeContainer = renderViewTree(container); - const linkeDetector = this.instantiationService.createInstance(LinkDetector); + const linkDetector = this.instantiationService.createInstance(LinkDetector); this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'VariablesView', treeContainer, new VariablesDelegate(), - [this.instantiationService.createInstance(VariablesRenderer, linkeDetector), new ScopesRenderer(), new ScopeErrorRenderer()], - new VariablesDataSource(), { + [ + this.instantiationService.createInstance(VariablesRenderer, linkDetector), + this.instantiationService.createInstance(VisualizedVariableRenderer, linkDetector), + new ScopesRenderer(), + new ScopeErrorRenderer(), + ], + new VariablesDataSource(this.debugService), { accessibilityProvider: new VariablesAccessibilityProvider(), identityProvider: { getId: (element: IExpression | IScope) => element.getId() }, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression | IScope) => e.name }, @@ -130,6 +135,7 @@ export class VariablesView extends ViewPane { } }); + this._register(VisualizedVariableRenderer.rendererOnVisualizationRange(this.debugService.getViewModel(), this.tree)); this.tree.setInput(this.debugService.getViewModel().focusedStackFrame ?? null); CONTEXT_VARIABLES_FOCUSED.bindTo(this.tree.contextKeyService); @@ -299,9 +305,9 @@ function isStackFrame(obj: any): obj is IStackFrame { return obj instanceof StackFrame; } -class VariablesDataSource implements IAsyncDataSource { +class VariablesDataSource extends AbstractExpressionDataSource { - hasChildren(element: IStackFrame | null | IExpression | IScope): boolean { + public override hasChildren(element: IStackFrame | null | IExpression | IScope): boolean { if (!element) { return false; } @@ -312,7 +318,7 @@ class VariablesDataSource implements IAsyncDataSource { + protected override doGetChildren(element: IStackFrame | IExpression | IScope): Promise<(IExpression | IScope)[]> { if (isStackFrame(element)) { return element.getScopes(); } @@ -341,6 +347,10 @@ class VariablesDelegate implements IListVirtualDelegate { return ScopesRenderer.ID; } + if (element instanceof VisualizedExpression) { + return VisualizedVariableRenderer.ID; + } + return VariablesRenderer.ID; } } @@ -396,6 +406,102 @@ class ScopeErrorRenderer implements ITreeRenderer): IDisposable { + return model.onDidChangeVisualization(({ original }) => { + if (!tree.hasElement(original)) { + return; + } + + const parent: IExpression = tree.getParentElement(original); + tree.updateChildren(parent, false, false); + }); + + } + + constructor( + private readonly linkDetector: LinkDetector, + @IDebugService debugService: IDebugService, + @IContextViewService contextViewService: IContextViewService, + @IMenuService private readonly menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + ) { + super(debugService, contextViewService); + } + + public override get templateId(): string { + return VisualizedVariableRenderer.ID; + } + + public override renderElement(node: ITreeNode, index: number, data: IExpressionTemplateData): void { + super.renderExpressionElement(node.element, node, data); + } + + protected override renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void { + const viz = expression as VisualizedExpression; + + let text = viz.name; + if (viz.value && typeof viz.name === 'string') { + text += ':'; + } + data.label.set(text, highlights, viz.name); + renderExpressionValue(viz, data.value, { + showChanged: false, + maxValueLength: 1024, + showHover: true, + colorize: true, + linkDetector: this.linkDetector + }); + } + + protected override getInputBoxOptions(expression: IExpression): IInputBoxOptions | undefined { + const variable = expression; + return { + initialValue: expression.value, + ariaLabel: localize('variableValueAriaLabel', "Type new variable value"), + validationOptions: { + validation: () => variable.errorMessage ? ({ content: variable.errorMessage }) : null + }, + onFinish: (value: string, success: boolean) => { + variable.errorMessage = undefined; + const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; + if (success && variable.value !== value && focusedStackFrame) { + variable.setVariable(value, focusedStackFrame) + // Need to force watch expressions and variables to update since a variable change can have an effect on both + .then(() => { + // Do not refresh scopes due to a node limitation #15520 + forgetScopes = false; + this.debugService.getViewModel().updateViews(); + }); + } + } + }; + } + + protected override renderActionBar(actionBar: ActionBar, expression: IExpression, _data: IExpressionTemplateData) { + const viz = expression as VisualizedExpression; + const contextKeyService = viz.original ? getContextForVariableMenuBase(this.contextKeyService, viz.original) : this.contextKeyService; + const menu = this.menuService.createMenu(MenuId.DebugVariablesContext, contextKeyService); + + const primary: IAction[] = []; + const context = viz.original ? getVariablesContext(viz.original) : undefined; + createAndFillInContextMenuActions(menu, { arg: context, shouldForwardArgs: false }, { primary, secondary: [] }, 'inline'); + + if (viz.original) { + primary.push(new Action('debugViz', localize('removeVisualizer', 'Remove Visualizer'), ThemeIcon.asClassName(Codicon.close), undefined, () => this.debugService.getViewModel().setVisualizedExpression(viz.original!, undefined))); + } + actionBar.clear(); + actionBar.context = context; + actionBar.push(primary, { icon: true, label: false }); + } +} + export class VariablesRenderer extends AbstractExpressionsRenderer { static readonly ID = 'variable'; @@ -466,13 +572,14 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { this.visualization.getApplicableFor(expression, cts.token).then(result => { data.elementDisposable.add(result); - const actions = result.object.map(v => new Action('debugViz', v.name, v.iconClass || 'debug-viz-icon', undefined, this.useVisualizer(v, cts.token))); + const originalExpression = (expression instanceof VisualizedExpression && expression.original) || expression; + const actions = result.object.map(v => new Action('debugViz', v.name, v.iconClass || 'debug-viz-icon', undefined, this.useVisualizer(v, originalExpression, cts.token))); if (actions.length === 0) { // no-op } else if (actions.length === 1) { actionBar.push(actions[0], { icon: true, label: false }); } else { - actionBar.push(new Action('debugViz', localize('useVisualizer', 'Visualize Variable...'), ThemeIcon.asClassName(Codicon.eye), undefined, () => this.pickVisualizer(actions, expression, data)), { icon: true, label: false }); + actionBar.push(new Action('debugViz', localize('useVisualizer', 'Visualize Variable...'), ThemeIcon.asClassName(Codicon.eye), undefined, () => this.pickVisualizer(actions, originalExpression, data)), { icon: true, label: false }); } }); } @@ -484,7 +591,7 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { }); } - private useVisualizer(viz: DebugVisualizer, token: CancellationToken) { + private useVisualizer(viz: DebugVisualizer, expression: IExpression, token: CancellationToken) { return async () => { const resolved = await viz.resolve(token); if (token.isCancellationRequested) { @@ -494,7 +601,10 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { if (resolved.type === DebugVisualizationType.Command) { viz.execute(); } else { - throw new Error('not implemented, yet'); + const replacement = await this.visualization.setVisualizedNodeFor(resolved.id, expression); + if (replacement) { + this.debugService.getViewModel().setVisualizedExpression(expression, replacement); + } } }; } diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 991c7712217..9bdf8801c54 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -3,38 +3,38 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IDragAndDropData } from 'vs/base/browser/dnd'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; +import { IListVirtualDelegate, ListDragOverEffectPosition, ListDragOverEffectType } from 'vs/base/browser/ui/list/list'; +import { ElementsDragAndDropData, ListViewTargetSector } from 'vs/base/browser/ui/list/listView'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, ITreeMouseEvent, ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { IAction } from 'vs/base/common/actions'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { IDebugService, IExpression, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, WATCH_VIEW_ID, CONTEXT_WATCH_EXPRESSIONS_EXIST, CONTEXT_WATCH_ITEM_TYPE, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_CAN_VIEW_MEMORY } from 'vs/workbench/contrib/debug/common/debug'; -import { Expression, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Codicon } from 'vs/base/common/codicons'; +import { FuzzyScore } from 'vs/base/common/filters'; +import { localize } from 'vs/nls'; +import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { Action2, IMenu, IMenuService, 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 { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IAction } from 'vs/base/common/actions'; -import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ViewPane, ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IListVirtualDelegate, ListDragOverEffectPosition, ListDragOverEffectType } from 'vs/base/browser/ui/list/list'; -import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; -import { IAsyncDataSource, ITreeMouseEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, ITreeNode } from 'vs/base/browser/ui/tree/tree'; -import { IDragAndDropData } from 'vs/base/browser/dnd'; -import { ElementsDragAndDropData, ListViewTargetSector } from 'vs/base/browser/ui/list/listView'; -import { FuzzyScore } from 'vs/base/common/filters'; -import { IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; -import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; -import { IContextKeyService, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { watchExpressionsRemoveAll, watchExpressionsAdd } from 'vs/workbench/contrib/debug/browser/debugIcons'; -import { registerAction2, MenuId, Action2, IMenuService, IMenu } from 'vs/platform/actions/common/actions'; -import { localize } from 'vs/nls'; -import { Codicon } from 'vs/base/common/codicons'; -import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ViewAction, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; +import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { AbstractExpressionDataSource, AbstractExpressionsRenderer, IExpressionTemplateData, IInputBoxOptions, renderExpressionValue, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { watchExpressionsAdd, watchExpressionsRemoveAll } from 'vs/workbench/contrib/debug/browser/debugIcons'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; +import { VariablesRenderer, VisualizedVariableRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; +import { CONTEXT_CAN_VIEW_MEMORY, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_WATCH_EXPRESSIONS_EXIST, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_WATCH_ITEM_TYPE, IDebugService, IExpression, WATCH_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { Expression, Variable, VisualizedExpression } from 'vs/workbench/contrib/debug/common/debugModel'; const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; let ignoreViewUpdates = false; @@ -86,9 +86,14 @@ export class WatchExpressionsView extends ViewPane { const treeContainer = renderViewTree(container); const expressionsRenderer = this.instantiationService.createInstance(WatchExpressionsRenderer); - const linkeDetector = this.instantiationService.createInstance(LinkDetector); - this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'WatchExpressions', treeContainer, new WatchExpressionsDelegate(), [expressionsRenderer, this.instantiationService.createInstance(VariablesRenderer, linkeDetector)], - new WatchExpressionsDataSource(), { + const linkDetector = this.instantiationService.createInstance(LinkDetector); + this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'WatchExpressions', treeContainer, new WatchExpressionsDelegate(), + [ + expressionsRenderer, + this.instantiationService.createInstance(VariablesRenderer, linkDetector), + this.instantiationService.createInstance(VisualizedVariableRenderer, linkDetector), + ], + new WatchExpressionsDataSource(this.debugService), { accessibilityProvider: new WatchExpressionsAccessibilityProvider(), identityProvider: { getId: (element: IExpression) => element.getId() }, keyboardNavigationLabelProvider: { @@ -109,6 +114,7 @@ export class WatchExpressionsView extends ViewPane { this.tree.setInput(this.debugService); CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(this.tree.contextKeyService); + this._register(VisualizedVariableRenderer.rendererOnVisualizationRange(this.debugService.getViewModel(), this.tree)); this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e))); this._register(this.debugService.getModel().onDidChangeWatchExpressions(async we => { @@ -234,6 +240,10 @@ class WatchExpressionsDelegate implements IListVirtualDelegate { return WatchExpressionsRenderer.ID; } + if (element instanceof VisualizedExpression) { + return VisualizedVariableRenderer.ID; + } + // Variable return VariablesRenderer.ID; } @@ -243,13 +253,13 @@ function isDebugService(element: any): element is IDebugService { return typeof element.getConfigurationManager === 'function'; } -class WatchExpressionsDataSource implements IAsyncDataSource { +class WatchExpressionsDataSource extends AbstractExpressionDataSource { - hasChildren(element: IExpression | IDebugService): boolean { + public override hasChildren(element: IExpression | IDebugService): boolean { return isDebugService(element) || element.hasChildren; } - getChildren(element: IDebugService | IExpression): Promise> { + public override doGetChildren(element: IDebugService | IExpression): Promise> { if (isDebugService(element)) { const debugService = element as IDebugService; const watchExpressions = debugService.getModel().getWatchExpressions(); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index d16c7c8e0cd..9dcab53e240 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -634,6 +634,8 @@ export interface IViewModel extends ITreeElement { */ readonly focusedStackFrame: IStackFrame | undefined; + setVisualizedExpression(original: IExpression, visualized: IExpression | undefined): void; + getVisualizedExpression(expression: IExpression): IExpression | undefined; getSelectedExpression(): { expression: IExpression; settingWatch: boolean } | undefined; setSelectedExpression(expression: IExpression | undefined, settingWatch: boolean): void; updateViews(): void; @@ -645,6 +647,11 @@ export interface IViewModel extends ITreeElement { onDidFocusStackFrame: Event<{ stackFrame: IStackFrame | undefined; explicit: boolean; session: IDebugSession | undefined }>; onDidSelectExpression: Event<{ expression: IExpression; settingWatch: boolean } | undefined>; onDidEvaluateLazyExpression: Event; + /** + * Fired when `setVisualizedExpression`, to migrate elements currently + * rendered as `original` to the `replacement`. + */ + onDidChangeVisualization: Event<{ original: IExpression; replacement: IExpression }>; onWillUpdateViews: Event; evaluateLazyExpression(expression: IExpressionContainer): void; @@ -1269,9 +1276,31 @@ export const enum DebugVisualizationType { Tree, } -export type MainThreadDebugVisualization = { - type: DebugVisualizationType.Command; -}; // todo: tree +export type MainThreadDebugVisualization = + | { type: DebugVisualizationType.Command } + | { type: DebugVisualizationType.Tree; id: string }; + + +export const enum DebugTreeItemCollapsibleState { + None = 0, + Collapsed = 1, + Expanded = 2 +} + +export interface IDebugVisualizationTreeItem { + id: number; + label: string; + description?: string; + collapsibleState: DebugTreeItemCollapsibleState; + contextValue?: string; + canEdit?: boolean; +} + +export namespace IDebugVisualizationTreeItem { + export type Serialized = IDebugVisualizationTreeItem; + export const deserialize = (v: Serialized): IDebugVisualizationTreeItem => v; + export const serialize = (item: IDebugVisualizationTreeItem): Serialized => item; +} export interface IDebugVisualization { id: number; diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 5a33aecfbeb..373ebfa9189 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -22,9 +22,10 @@ import * as nls from 'vs/nls'; import { ILogService } from 'vs/platform/log/common/log'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IEditorPane } from 'vs/workbench/common/editor'; -import { DEBUG_MEMORY_SCHEME, IBaseBreakpoint, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IBreakpointsChangeEvent, IDataBreakpoint, IDebugModel, IDebugSession, IEnablement, IExceptionBreakpoint, IExceptionInfo, IExpression, IExpressionContainer, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryInvalidationEvent, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IScope, IStackFrame, IThread, ITreeElement, MemoryRange, MemoryRangeType, State } from 'vs/workbench/contrib/debug/common/debug'; +import { DEBUG_MEMORY_SCHEME, DebugTreeItemCollapsibleState, IBaseBreakpoint, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IBreakpointsChangeEvent, IDataBreakpoint, IDebugModel, IDebugSession, IDebugVisualizationTreeItem, IEnablement, IExceptionBreakpoint, IExceptionInfo, IExpression, IExpressionContainer, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryInvalidationEvent, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IScope, IStackFrame, IThread, ITreeElement, MemoryRange, MemoryRangeType, State } from 'vs/workbench/contrib/debug/common/debug'; import { Source, UNKNOWN_SOURCE_LABEL, getUriFromSource } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage'; +import { IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -244,6 +245,35 @@ function handleSetResponse(expression: ExpressionContainer, response: DebugProto } } +export class VisualizedExpression implements IExpression { + public readonly name: string; + public readonly hasChildren: boolean; + public readonly value: string; + private readonly id = generateUuid(); + + evaluateLazy(): Promise { + return Promise.resolve(); + } + getChildren(): Promise { + return this.visualizer.getVisualizedChildren(this.treeId, this.treeItem.id); + } + + getId(): string { + return this.id; + } + + constructor( + private readonly visualizer: IDebugVisualizerService, + private readonly treeId: string, + public readonly treeItem: IDebugVisualizationTreeItem, + public readonly original?: Variable, + ) { + this.name = treeItem.label; + this.hasChildren = treeItem.collapsibleState !== DebugTreeItemCollapsibleState.None; + this.value = treeItem.description || ''; + } +} + export class Expression extends ExpressionContainer implements IExpression { static readonly DEFAULT_VALUE = nls.localize('notAvailable', "not available"); diff --git a/src/vs/workbench/contrib/debug/common/debugViewModel.ts b/src/vs/workbench/contrib/debug/common/debugViewModel.ts index 01bf3add5f8..9c4ee1217ab 100644 --- a/src/vs/workbench/contrib/debug/common/debugViewModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugViewModel.ts @@ -22,6 +22,8 @@ export class ViewModel implements IViewModel { private readonly _onDidSelectExpression = new Emitter<{ expression: IExpression; settingWatch: boolean } | undefined>(); private readonly _onDidEvaluateLazyExpression = new Emitter(); private readonly _onWillUpdateViews = new Emitter(); + private readonly _onDidChangeVisualization = new Emitter<{ original: IExpression; replacement: IExpression }>(); + private readonly visualized = new WeakMap(); private expressionSelectedContextKey!: IContextKey; private loadedScriptsSupportedContextKey!: IContextKey; private stepBackSupportedContextKey!: IContextKey; @@ -125,6 +127,10 @@ export class ViewModel implements IViewModel { return this._onDidFocusStackFrame.event; } + get onDidChangeVisualization() { + return this._onDidChangeVisualization.event; + } + getSelectedExpression(): { expression: IExpression; settingWatch: boolean } | undefined { return this.selectedExpression; } @@ -159,6 +165,21 @@ export class ViewModel implements IViewModel { this.multiSessionDebug.set(isMultiSessionView); } + setVisualizedExpression(original: IExpression, visualized: IExpression | undefined): void { + const current = this.visualized.get(original) || original; + + if (visualized) { + this.visualized.set(original, visualized); + } else { + this.visualized.delete(original); + } + this._onDidChangeVisualization.fire({ original: current, replacement: visualized || original }); + } + + getVisualizedExpression(expression: IExpression): IExpression | undefined { + return this.visualized.get(expression); + } + async evaluateLazyExpression(expression: IExpressionContainer): Promise { await expression.evaluateLazy(); this._onDidEvaluateLazyExpression.fire(expression); diff --git a/src/vs/workbench/contrib/debug/common/debugVisualizers.ts b/src/vs/workbench/contrib/debug/common/debugVisualizers.ts index 9a5cf6885c2..2b171d5f673 100644 --- a/src/vs/workbench/contrib/debug/common/debugVisualizers.ts +++ b/src/vs/workbench/contrib/debug/common/debugVisualizers.ts @@ -10,9 +10,9 @@ import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/pla import { ExtensionIdentifier, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { CONTEXT_VARIABLE_NAME, CONTEXT_VARIABLE_TYPE, CONTEXT_VARIABLE_VALUE, MainThreadDebugVisualization, IDebugVisualization, IDebugVisualizationContext, IExpression, IExpressionContainer } from 'vs/workbench/contrib/debug/common/debug'; +import { CONTEXT_VARIABLE_NAME, CONTEXT_VARIABLE_TYPE, CONTEXT_VARIABLE_VALUE, MainThreadDebugVisualization, IDebugVisualization, IDebugVisualizationContext, IExpression, IExpressionContainer, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; import { getContextForVariable } from 'vs/workbench/contrib/debug/common/debugContext'; -import { Scope, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Scope, Variable, VisualizedExpression } from 'vs/workbench/contrib/debug/common/debugModel'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -27,6 +27,13 @@ interface VisualizerHandle { disposeDebugVisualizers(ids: number[]): void; } +interface VisualizerTreeHandle { + getTreeItem(element: IDebugVisualizationContext): Promise; + getChildren(element: number): Promise; + disposeItem(element: number): void; + editItem?(item: number, value: string): Promise; +} + export class DebugVisualizer { public get name() { return this.viz.name; @@ -63,13 +70,38 @@ export interface IDebugVisualizerService { * Registers a new visualizer (called from the main thread debug service) */ register(handle: VisualizerHandle): IDisposable; + + /** + * Registers a new visualizer tree. + */ + registerTree(treeId: string, handle: VisualizerTreeHandle): IDisposable; + + /** + * Sets that a certa tree should be used for the visualized node + */ + setVisualizedNodeFor(treeId: string, expr: IExpression): Promise; + + /** + * Gets a visualized node for the given expression if the user has preferred + * to visualize it that way. + */ + getVisualizedNodeFor(expr: IExpression): Promise; + + /** + * Gets children for a visualized tree node. + */ + getVisualizedChildren(treeId: string, treeElementId: number): Promise; } +const emptyRef: IReference = { object: [], dispose: () => { } }; + export class DebugVisualizerService implements IDebugVisualizerService { declare public readonly _serviceBrand: undefined; private readonly handles = new Map(); + private readonly trees = new Map(); private readonly didActivate = new Map>(); + private readonly preferredTrees = new Map(); private registrations: { expr: ContextKeyExpression; id: string; extensionId: ExtensionIdentifier }[] = []; constructor( @@ -85,10 +117,13 @@ export class DebugVisualizerService implements IDebugVisualizerService { } /** @inheritdoc */ - public async getApplicableFor(variable: Variable, token: CancellationToken): Promise> { + public async getApplicableFor(variable: IExpression, token: CancellationToken): Promise> { + if (!(variable instanceof Variable)) { + return emptyRef; + } const threadId = variable.getThreadId(); if (threadId === undefined) { // an expression, not a variable - return { object: [], dispose: () => { } }; + return emptyRef; } const context: IDebugVisualizationContext = { @@ -163,6 +198,92 @@ export class DebugVisualizerService implements IDebugVisualizerService { return toDisposable(() => this.handles.delete(key)); } + /** @inheritdoc */ + public registerTree(treeId: string, handle: VisualizerTreeHandle): IDisposable { + this.trees.set(treeId, handle); + return toDisposable(() => this.trees.delete(treeId)); + } + + /** @inheritdoc */ + public async setVisualizedNodeFor(treeId: string, expr: IExpression): Promise { + return this.getOrSetNodeFor(expr, treeId); + } + + /** @inheritdoc */ + public async getVisualizedNodeFor(expr: IExpression): Promise { + return this.getOrSetNodeFor(expr); + } + + /** @inheritdoc */ + public async getVisualizedChildren(treeId: string, treeElementId: number): Promise { + const children = await this.trees.get(treeId)?.getChildren(treeElementId) || []; + return children.map(c => new VisualizedExpression(this, treeId, c, undefined)); + } + + private async getOrSetNodeFor(expr: IExpression, useTreeKey?: string): Promise { + if (!(expr instanceof Variable)) { + return; + } + + const threadId = expr.getThreadId(); + if (threadId === undefined) { + return; + } + + const exprPreferKey = useTreeKey || this.getPreferredTreeKey(expr); + const tree = exprPreferKey && this.trees.get(exprPreferKey); + if (!tree) { + return; + } + + const treeItem = await tree.getTreeItem(this.getVariableContext(threadId, expr)); + if (!treeItem) { + return; + } + + if (useTreeKey) { + this.preferredTrees.set(exprPreferKey, exprPreferKey); + } + + return new VisualizedExpression(this, exprPreferKey, treeItem, expr); + } + + private getPreferredTreeKey(expr: Variable) { + return JSON.stringify([ + expr.name, + expr.value, + expr.type, + !!expr.memoryReference, + ].join('\0')); + } + + private getVariableContext(threadId: number, variable: Variable) { + const context: IDebugVisualizationContext = { + sessionId: variable.getSession()?.getId() || '', + containerId: variable.parent.getId(), + threadId, + variable: { + name: variable.name, + value: variable.value, + type: variable.type, + evaluateName: variable.evaluateName, + variablesReference: variable.reference || 0, + indexedVariables: variable.indexedVariables, + memoryReference: variable.memoryReference, + namedVariables: variable.namedVariables, + presentationHint: variable.presentationHint, + } + }; + + for (let p: IExpressionContainer = variable; p instanceof Variable; p = p.parent) { + if (p.parent instanceof Scope) { + context.frameId = p.parent.stackFrame.frameId; + } + } + + return context; + } + private processExtensionRegistration(ext: Readonly) { const viz = ext.contributes?.debugVisualizers; if (!(viz instanceof Array)) { diff --git a/src/vscode-dts/vscode.proposed.debugVisualization.d.ts b/src/vscode-dts/vscode.proposed.debugVisualization.d.ts index d23bbfe2edd..0fc115f7619 100644 --- a/src/vscode-dts/vscode.proposed.debugVisualization.d.ts +++ b/src/vscode-dts/vscode.proposed.debugVisualization.d.ts @@ -16,6 +16,79 @@ declare module 'vscode' { id: string, provider: DebugVisualizationProvider ): Disposable; + + /** + * Registers a tree that can be referenced by {@link DebugVisualization.visualization}. + * @param id + * @param provider + */ + export function registerDebugVisualizationTreeProvider( + id: string, + provider: DebugVisualizationTree + ): Disposable; + } + + /** + * An item from the {@link DebugVisualizationTree} + */ + export interface DebugTreeItem { + /** + * A human-readable string describing this item. + */ + label: string; + + /** + * A human-readable string which is rendered less prominent. + */ + description?: string; + + /** + * {@link TreeItemCollapsibleState} of the tree item. + */ + collapsibleState?: TreeItemCollapsibleState; + + /** + * Context value of the tree item. This can be used to contribute item specific actions in the tree. + * For example, a tree item is given a context value as `folder`. When contributing actions to `view/item/context` + * using `menus` extension point, you can specify context value for key `viewItem` in `when` expression like `viewItem == folder`. + * ```json + * "contributes": { + * "menus": { + * "view/item/context": [ + * { + * "command": "extension.deleteFolder", + * "when": "viewItem == folder" + * } + * ] + * } + * } + * ``` + * This will show action `extension.deleteFolder` only for items with `contextValue` is `folder`. + */ + contextValue?: string; + + /** + * Whether this item can be edited by the user. + */ + canEdit?: boolean; + } + + /** + * Provides a tree that can be referenced in debug visualizations. + */ + export interface DebugVisualizationTree { + /** + * Gets the tree item for an element or the base context item. + */ + getTreeItem(context: DebugVisualizationContext): ProviderResult; + /** + * Gets children for the tree item or the best context item. + */ + getChildren(element: T): ProviderResult; + /** + * Handles the user editing an item. + */ + editItem?(item: T, value: string): ProviderResult; } export class DebugVisualization { @@ -32,13 +105,9 @@ declare module 'vscode' { /** * Visualization to use for the variable. This may be either: * - A command to run when the visualization is selected for a variable. - * - A {@link TreeDataProvider} which is used to display the data in-line - * where the variable is shown. If a single root item is returned from - * the data provider, it will replace the variable in its tree. - * Otherwise, the items will be shown as children of the variable. + * - A reference to a previously-registered {@link DebugVisualizationTree} */ - // @API don't return TreeDataProvider but a reference to it, like its ids - visualization?: Command | TreeDataProvider; + visualization?: Command | { treeId: string }; /** * Creates a new debug visualization object. From 582b0bea5a9e5f9090573fa3a2b4dd8587030100 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 11:29:51 -0600 Subject: [PATCH 1209/1897] add response editor --- .../terminal/browser/media/terminal.css | 4 ++ .../chat/browser/terminalChatController.ts | 6 +-- .../chat/browser/terminalChatWidget.ts | 42 ++++++++++++++++++- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 8fcb3c71172..9053a746f89 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -575,3 +575,7 @@ .monaco-workbench .terminal-inline-chat.hide { visibility: hidden; } + +.monaco-workbench .terminal-inline-chat-response.hide { + visibility: hidden; +} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index c8f3ec69958..05383e3e7f8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -34,7 +34,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr static activeChatWidget?: TerminalChatController; private _chatWidget: Lazy | undefined; private _lastLayoutDimensions: IDimension | undefined; - + private _requestId: number = 0; get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } // private _sessionCtor: CancelablePromise | undefined; @@ -140,9 +140,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], CancellationToken.None); const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); if (codeBlock) { - alert(codeBlock); + // TODO: check the SR experience + this._chatWidget?.rawValue?.renderResponse(codeBlock, this._requestId++); } - // TODO: accessibility announcement, help dialog } reveal(): void { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 4a5dbb55994..88f17fe5c58 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -5,12 +5,16 @@ import { Dimension, IFocusTracker, trackFocus } from 'vs/base/browser/dom'; import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/terminalChatWidget'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { ITextModel } from 'vs/editor/common/model'; +import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; @@ -23,7 +27,8 @@ export class TerminalChatWidget extends Disposable { private _chatWidgetVisible: IContextKey; private readonly _inlineChatWidget: InlineChatWidget; - + private _responseWidget: CodeEditorWidget | undefined; + private _responseContainer: HTMLElement | undefined; private readonly _focusTracker: IFocusTracker; @@ -31,7 +36,9 @@ export class TerminalChatWidget extends Disposable { private readonly _container: HTMLElement, private readonly _instance: ITerminalInstance, @IInstantiationService instantiationService: IInstantiationService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, + @IModelService private readonly _modelService: IModelService ) { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); @@ -70,6 +77,33 @@ export class TerminalChatWidget extends Disposable { this._focusTracker = this._register(trackFocus(this._widgetContainer)); } + renderResponse(codeBlock: string, requestId: number): void { + this._chatAccessibilityService.acceptResponse(codeBlock, requestId); + if (!this._responseWidget) { + this._responseContainer = document.createElement('div'); + this._responseContainer.classList.add('terminal-inline-chat-response'); + this._responseWidget = this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseContainer, {}, { isSimpleWidget: true }); + this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: codeBlock })).then((model) => { + if (!model || !this._responseWidget) { + return; + } + this._responseWidget.setModel(model); + this._responseWidget.layout(new Dimension(400, 150)); + this._widgetContainer.prepend(this._responseContainer!); + }); + } else { + this._responseWidget.setValue(codeBlock); + } + this._responseContainer?.classList.remove('hide'); + } + + private async _getTextModel(resource: URI): Promise { + const existing = this._modelService.getModel(resource); + if (existing && !existing.isDisposed()) { + return existing; + } + return this._modelService.createModel(resource.fragment, null, resource, false); + } reveal(): void { this._inlineChatWidget.layout(new Dimension(400, 150)); @@ -79,6 +113,7 @@ export class TerminalChatWidget extends Disposable { this._inlineChatWidget.focus(); } hide(): void { + this._responseContainer?.classList.add('hide'); this._widgetContainer.classList.add('hide'); this._chatWidgetFocused.set(false); this._chatWidgetVisible.set(false); @@ -92,6 +127,9 @@ export class TerminalChatWidget extends Disposable { } setValue(value?: string) { this._inlineChatWidget.value = value ?? ''; + if (!value) { + this._responseContainer?.classList.add('hide'); + } } // async acceptInput(): Promise { // // this._widget?.acceptInput(); From c4519ce005d7e3f1d52a280d022b1cab5df4aa00 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 11:44:13 -0600 Subject: [PATCH 1210/1897] wip render message when no code block comes through --- .../terminal/browser/media/terminal.css | 4 +++ .../chat/browser/terminalChatController.ts | 5 +++- .../chat/browser/terminalChatWidget.ts | 26 ++++++++++++------- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 9053a746f89..ab84f0258b1 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -579,3 +579,7 @@ .monaco-workbench .terminal-inline-chat-response.hide { visibility: hidden; } + +.monaco-workbench .terminal-inline-chat-response.message { + width: 400px !important; +} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 05383e3e7f8..c4755a08c47 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -139,9 +139,12 @@ export class TerminalChatController extends Disposable implements ITerminalContr // TODO: use token await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], CancellationToken.None); const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); + this._requestId++; if (codeBlock) { // TODO: check the SR experience - this._chatWidget?.rawValue?.renderResponse(codeBlock, this._requestId++); + this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._requestId); + } else { + this._chatWidget?.rawValue?.renderMessage(message, this._requestId); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 88f17fe5c58..476c808dce1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -28,7 +28,7 @@ export class TerminalChatWidget extends Disposable { private readonly _inlineChatWidget: InlineChatWidget; private _responseWidget: CodeEditorWidget | undefined; - private _responseContainer: HTMLElement | undefined; + private _responseElement: HTMLElement; private readonly _focusTracker: IFocusTracker; @@ -49,6 +49,10 @@ export class TerminalChatWidget extends Disposable { this._widgetContainer.classList.add('terminal-inline-chat'); this._container.appendChild(this._widgetContainer); + this._responseElement = document.createElement('div'); + this._responseElement.classList.add('terminal-inline-chat-response'); + this._widgetContainer.prepend(this._responseElement); + // The inline chat widget requires a parent editor that it bases the diff view on, since the // terminal doesn't use that feature we can just pass in an unattached editor instance. const fakeParentEditorElement = document.createElement('div'); @@ -77,24 +81,28 @@ export class TerminalChatWidget extends Disposable { this._focusTracker = this._register(trackFocus(this._widgetContainer)); } - renderResponse(codeBlock: string, requestId: number): void { + renderTerminalCommand(codeBlock: string, requestId: number): void { + this._responseElement.classList.remove('message', 'hide'); this._chatAccessibilityService.acceptResponse(codeBlock, requestId); if (!this._responseWidget) { - this._responseContainer = document.createElement('div'); - this._responseContainer.classList.add('terminal-inline-chat-response'); - this._responseWidget = this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseContainer, {}, { isSimpleWidget: true }); + this._responseWidget = this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, {}, { isSimpleWidget: true }); this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: codeBlock })).then((model) => { if (!model || !this._responseWidget) { return; } this._responseWidget.setModel(model); this._responseWidget.layout(new Dimension(400, 150)); - this._widgetContainer.prepend(this._responseContainer!); }); } else { this._responseWidget.setValue(codeBlock); } - this._responseContainer?.classList.remove('hide'); + } + + renderMessage(message: string, requestId: number): void { + this._responseElement?.classList.remove('hide'); + this._responseElement.classList.add('message'); + this._chatAccessibilityService.acceptResponse(message, requestId); + this._responseElement.textContent = message; } private async _getTextModel(resource: URI): Promise { @@ -113,7 +121,7 @@ export class TerminalChatWidget extends Disposable { this._inlineChatWidget.focus(); } hide(): void { - this._responseContainer?.classList.add('hide'); + this._responseElement?.classList.add('hide'); this._widgetContainer.classList.add('hide'); this._chatWidgetFocused.set(false); this._chatWidgetVisible.set(false); @@ -128,7 +136,7 @@ export class TerminalChatWidget extends Disposable { setValue(value?: string) { this._inlineChatWidget.value = value ?? ''; if (!value) { - this._responseContainer?.classList.add('hide'); + this._responseElement?.classList.add('hide'); } } // async acceptInput(): Promise { From e8be60a478ca6c351401e00fe068ad18ca210f29 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 13 Feb 2024 09:50:40 -0800 Subject: [PATCH 1211/1897] debug: fix launch using empty directory in external terminal (#205123) Fixes #204039 --- .../platform/externalTerminal/node/externalTerminalService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/externalTerminal/node/externalTerminalService.ts b/src/vs/platform/externalTerminal/node/externalTerminalService.ts index 71dbac899b3..9fd38929726 100644 --- a/src/vs/platform/externalTerminal/node/externalTerminalService.ts +++ b/src/vs/platform/externalTerminal/node/externalTerminalService.ts @@ -107,7 +107,7 @@ export class WindowsExternalTerminalService extends ExternalTerminalService impl // prefer to use the window terminal to spawn if it's available instead // of start, since that allows ctrl+c handling (#81322) spawnExec = wt; - cmdArgs = ['-d', dir, exec, '/c', command]; + cmdArgs = ['-d', dir || '.', exec, '/c', command]; // default dir fixes #204039 } else { spawnExec = WindowsExternalTerminalService.CMD; cmdArgs = ['/c', 'start', title, '/wait', exec, '/c', command]; From c9c98f010a12da31afa70b7b60b280260f911bac Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 11:56:08 -0600 Subject: [PATCH 1212/1897] get chat cancellation action to show up --- .../chat/browser/terminalChatActions.ts | 7 ++--- .../chat/browser/terminalChatController.ts | 29 +++++++++++++++---- .../chat/browser/terminalChatWidget.ts | 2 +- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 161b8515478..b9544ce5feb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -6,7 +6,6 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { localize2 } from 'vs/nls'; -import { MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; @@ -73,7 +72,7 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - // TerminalContextKeys.chatInputHasText + TerminalContextKeys.chatRequestActive.negate() ), icon: Codicon.send, keybinding: { @@ -109,8 +108,8 @@ registerActiveXtermAction({ ), icon: Codicon.debugStop, menu: { - id: MenuId.ChatExecute, - group: 'navigation', + id: MENU_TERMINAL_CHAT_INPUT, + group: 'main', }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index c4755a08c47..5465c3abed3 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -17,9 +17,12 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; import { generateUuid } from 'vs/base/common/uuid'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; import { marked } from 'vs/base/common/marked/marked'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; export class TerminalChatController extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; @@ -39,7 +42,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr // private _sessionCtor: CancelablePromise | undefined; // private _activeSession?: Session; - // private readonly _ctxHasActiveRequest: IContextKey; + private readonly _ctxHasActiveRequest!: IContextKey; + + private _cancellationTokenSource!: CancellationTokenSource; + // private _isVisible: boolean = false; // private _strategy: EditStrategy | undefined; @@ -56,7 +62,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr @ITerminalService private readonly _terminalService: ITerminalService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IChatAgentService private readonly _chatAgentService: IChatAgentService, - // @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService // @IInstantiationService private readonly _instantiationService: IInstantiationService, // @ICommandService private readonly _commandService: ICommandService, // @IInlineChatSavingService private readonly _inlineChatSavingService: IInlineChatSavingService @@ -65,7 +72,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { return; } - // this._ctxHasActiveRequest = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); + this._ctxHasActiveRequest = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); + this._cancellationTokenSource = new CancellationTokenSource(); // this._ctxLastResponseType = CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.bindTo(this._contextKeyService); } @@ -105,8 +113,15 @@ export class TerminalChatController extends Disposable implements ITerminalContr }); } + cancel(): void { + this._cancellationTokenSource.cancel(); + } + async acceptInput(): Promise { let message = ''; + this._chatAccessibilityService.acceptRequest(); + this._ctxHasActiveRequest.set(true); + const cancellationToken = this._cancellationTokenSource.token; const progressCallback = (progress: IChatProgress) => { // if (token.isCancellationRequested) { // return; @@ -137,15 +152,19 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.setValue(); // TODO: use token - await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], CancellationToken.None); + await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], cancellationToken); const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); this._requestId++; + if (cancellationToken.isCancellationRequested) { + return; + } if (codeBlock) { // TODO: check the SR experience this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._requestId); } else { this._chatWidget?.rawValue?.renderMessage(message, this._requestId); } + this._ctxHasActiveRequest.set(false); } reveal(): void { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 476c808dce1..4c84b3656bc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -31,7 +31,6 @@ export class TerminalChatWidget extends Disposable { private _responseElement: HTMLElement; private readonly _focusTracker: IFocusTracker; - constructor( private readonly _container: HTMLElement, private readonly _instance: ITerminalInstance, @@ -129,6 +128,7 @@ export class TerminalChatWidget extends Disposable { } cancel(): void { // TODO: Impl + this._inlineChatWidget.value = ''; } input(): string { return this._inlineChatWidget.value; From cf634dfa386325d041f05d86cf24a2654defc04a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 11:57:24 -0600 Subject: [PATCH 1213/1897] only show if active --- .../terminalContrib/chat/browser/terminalChatActions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index b9544ce5feb..57e3cee5de0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -86,7 +86,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_INPUT, group: 'main', order: 1, - // when: TerminalContextKeys.chatSessionInProgress.negate(), + when: TerminalContextKeys.chatRequestActive.negate(), // TODO: // when: CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.isEqualTo(false) }, @@ -110,6 +110,7 @@ registerActiveXtermAction({ menu: { id: MENU_TERMINAL_CHAT_INPUT, group: 'main', + when: TerminalContextKeys.chatRequestActive, }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { From 7d80e6d9396dc3bf91d8880cd65758a31e3cef81 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 12:02:49 -0600 Subject: [PATCH 1214/1897] clean up --- .../chat/browser/terminalChatActions.ts | 2 -- .../chat/browser/terminalChatController.ts | 19 +++++-------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 57e3cee5de0..bb1a012026a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -87,8 +87,6 @@ registerActiveXtermAction({ group: 'main', order: 1, when: TerminalContextKeys.chatRequestActive.negate(), - // TODO: - // when: CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.isEqualTo(false) }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 5465c3abed3..a9babf3a5de 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -123,21 +123,13 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._ctxHasActiveRequest.set(true); const cancellationToken = this._cancellationTokenSource.token; const progressCallback = (progress: IChatProgress) => { - // if (token.isCancellationRequested) { - // return; - // } - - - // gotProgress = true; - - if (progress.kind === 'content' || progress.kind === 'markdownContent') { - // this.trace('sendRequest', `Provider returned progress for session ${model.sessionId}, ${typeof progress.content === 'string' ? progress.content.length : progress.content.value.length} chars`); - message += progress.content; - } else { - // this.trace('sendRequest', `Provider returned progress: ${JSON.stringify(progress)}`); + if (cancellationToken.isCancellationRequested) { + return; } - // model.acceptResponseProgress(request, progress); + if (progress.kind === 'content' || progress.kind === 'markdownContent') { + message += progress.content; + } }; const resolvedVariables: Record = {}; @@ -151,7 +143,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr }; this._chatWidget?.rawValue?.setValue(); - // TODO: use token await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], cancellationToken); const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); this._requestId++; From 44d4700aae178618cbb9aa6f888e0b81db5e66f2 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 12:03:37 -0600 Subject: [PATCH 1215/1897] remove things --- .../chat/browser/terminalChatController.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index a9babf3a5de..22810165660 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -40,20 +40,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr private _requestId: number = 0; get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } - // private _sessionCtor: CancelablePromise | undefined; - // private _activeSession?: Session; private readonly _ctxHasActiveRequest!: IContextKey; private _cancellationTokenSource!: CancellationTokenSource; - // private _isVisible: boolean = false; - // private _strategy: EditStrategy | undefined; - - // private _inlineChatListener: IDisposable | undefined; - // private _toolbar: MenuWorkbenchToolBar | undefined; - // private readonly _ctxLastResponseType: IContextKey; - // private _widgetDisposableStore: DisposableStore = this._register(new DisposableStore()); - constructor( private readonly _instance: ITerminalInstance, processManager: ITerminalProcessManager, @@ -64,9 +54,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService - // @IInstantiationService private readonly _instantiationService: IInstantiationService, - // @ICommandService private readonly _commandService: ICommandService, - // @IInlineChatSavingService private readonly _inlineChatSavingService: IInlineChatSavingService ) { super(); if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { @@ -74,7 +61,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr } this._ctxHasActiveRequest = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); this._cancellationTokenSource = new CancellationTokenSource(); - // this._ctxLastResponseType = CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.bindTo(this._contextKeyService); } layout(_xterm: IXtermTerminal & { raw: RawXtermTerminal }, dimension: IDimension): void { From c4af34eae27344c9bf3030f7c84d2f2e0cf69d8d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 12:04:44 -0600 Subject: [PATCH 1216/1897] more clean up --- .../terminalContrib/chat/browser/terminalChatController.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 22810165660..85d7ba44296 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -108,6 +108,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatAccessibilityService.acceptRequest(); this._ctxHasActiveRequest.set(true); const cancellationToken = this._cancellationTokenSource.token; + const agentId = 'terminal'; const progressCallback = (progress: IChatProgress) => { if (cancellationToken.isCancellationRequested) { return; @@ -122,14 +123,14 @@ export class TerminalChatController extends Disposable implements ITerminalContr const requestProps: IChatAgentRequest = { sessionId: generateUuid(), requestId: generateUuid(), - agentId: 'terminal', + agentId, message: this._chatWidget?.rawValue?.input() || '', variables: resolvedVariables, variables2: { message: this._chatWidget?.rawValue?.input() || '', variables: [] } }; this._chatWidget?.rawValue?.setValue(); - await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], cancellationToken); + await this._chatAgentService.invokeAgent(agentId, requestProps, progressCallback, [], cancellationToken); const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); this._requestId++; if (cancellationToken.isCancellationRequested) { From a9cfba287544762341fa7a2e84fce90f568f4cbb Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 12:11:41 -0600 Subject: [PATCH 1217/1897] catch error --- .../chat/browser/terminalChatController.ts | 11 ++++++++++- .../chat/browser/terminalChatWidget.ts | 18 ++++-------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 85d7ba44296..432d5fd0cbf 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -117,6 +117,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (progress.kind === 'content' || progress.kind === 'markdownContent') { message += progress.content; } + this._chatWidget?.rawValue?.updateProgress(progress); }; const resolvedVariables: Record = {}; @@ -130,7 +131,14 @@ export class TerminalChatController extends Disposable implements ITerminalContr }; this._chatWidget?.rawValue?.setValue(); - await this._chatAgentService.invokeAgent(agentId, requestProps, progressCallback, [], cancellationToken); + try { + await this._chatAgentService.invokeAgent(agentId, requestProps, progressCallback, [], cancellationToken); + } catch (e) { + // Provider is not ready + this._ctxHasActiveRequest.set(false); + this._chatWidget?.rawValue?.updateProgress(); + return; + } const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); this._requestId++; if (cancellationToken.isCancellationRequested) { @@ -143,6 +151,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.renderMessage(message, this._requestId); } this._ctxHasActiveRequest.set(false); + this._chatWidget?.rawValue?.updateProgress(); } reveal(): void { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 4c84b3656bc..985fc071cb2 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -15,6 +15,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; +import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; @@ -139,20 +140,9 @@ export class TerminalChatWidget extends Disposable { this._responseElement?.classList.add('hide'); } } - // async acceptInput(): Promise { - // // this._widget?.acceptInput(); - // // this._chatModel ??= this._chatService.startSession('terminal', CancellationToken.None); - - // // if (!this._model) { - // // throw new Error('Could not start chat session'); - // // } - // // this._chatService?.sendRequest(this._chatModel?.sessionId!, this._inlineChatWidget.value); - // // this._activeSession = new Session(EditMode.Live, , this._instance); - // // const initVariableData: IChatRequestVariableData = { message: getPromptText(parsedRequest.parts), variables: {} }; - // // request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); - // // const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); - // this._inlineChatWidget.value = ''; - // } + updateProgress(progress?: IChatProgress): void { + this._inlineChatWidget.updateProgress(progress?.kind === 'content' || progress?.kind === 'markdownContent'); + } layout(width: number): void { // this._widget?.layout(100, width < 300 ? 300 : width); } From 6289704f0d96a9860d5303a545e13ddd847cc164 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 13 Feb 2024 10:22:10 -0800 Subject: [PATCH 1218/1897] debug: fix toolbar overlaps traffic lights (#205127) Fixes #204265 --- src/vs/workbench/contrib/debug/browser/debugToolBar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 888e0b12048..c46cd2d9a6f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -254,7 +254,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { const positionPercentage = isMainWindow ? Number(this.storageService.get(DEBUG_TOOLBAR_POSITION_KEY, StorageScope.PROFILE)) : this.auxWindowCoordinates.get(currentWindow)?.x; - x = positionPercentage !== undefined + x = positionPercentage !== undefined && !isNaN(positionPercentage) ? positionPercentage * currentWindow.innerWidth : (0.5 * currentWindow.innerWidth - 0.5 * widgetWidth); } From 1617ebe843cb56d7ff8fdd2c17c3fbaa2de9f7ec Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 12:24:10 -0600 Subject: [PATCH 1219/1897] ensure terminal agent is registered --- .../contrib/terminal/common/terminalContextKey.ts | 4 ++++ .../chat/browser/terminalChatActions.ts | 4 +++- .../chat/browser/terminalChatController.ts | 11 +++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 78fd7df6712..7f777f94f62 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -43,6 +43,7 @@ export const enum TerminalContextKeyStrings { ChatVisible = 'terminalChatVisible', ChatActiveRequest = 'terminalChatActiveRequest', ChatInputHasText = 'terminalChatInputHasText', + ChatAgentRegistered = 'terminalChatAgentRegistered', } export namespace TerminalContextKeys { @@ -175,4 +176,7 @@ export namespace TerminalContextKeys { /** Whether the chat input has text */ export const chatInputHasText = new RawContextKey(TerminalContextKeyStrings.ChatInputHasText, false, localize('chatInputHasTextContextKey', "Whether the chat input has text.")); + + /** Whether the terminal chat agent has been registered */ + export const chatAgentRegistered = new RawContextKey(TerminalContextKeyStrings.ChatAgentRegistered, false, localize('chatAgentRegisteredContextKey', "Whether the terminal chat agent has been registered.")); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index bb1a012026a..dcf2aec0617 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -72,7 +72,8 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalContextKeys.chatRequestActive.negate() + TerminalContextKeys.chatRequestActive.negate(), + TerminalContextKeys.chatAgentRegistered ), icon: Codicon.send, keybinding: { @@ -103,6 +104,7 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatRequestActive, + TerminalContextKeys.chatAgentRegistered ), icon: Codicon.debugStop, menu: { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 432d5fd0cbf..b3b9c002aad 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -41,6 +41,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } private readonly _ctxHasActiveRequest!: IContextKey; + private readonly _ctxHasTerminalAgent!: IContextKey; private _cancellationTokenSource!: CancellationTokenSource; @@ -60,6 +61,16 @@ export class TerminalChatController extends Disposable implements ITerminalContr return; } this._ctxHasActiveRequest = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); + this._ctxHasTerminalAgent = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); + if (!this._chatAgentService.hasAgent('terminal')) { + this._register(this._chatAgentService.onDidChangeAgents(() => { + if (this._chatAgentService.getAgent('terminal')) { + this._ctxHasTerminalAgent.set(true); + } + })); + } else { + this._ctxHasTerminalAgent.set(true); + } this._cancellationTokenSource = new CancellationTokenSource(); } From fe89a8a7209a38d806140e954e4b79325ef6853a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 13 Feb 2024 19:31:05 +0100 Subject: [PATCH 1220/1897] add some api todos (#205128) --- src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts index 431f3c2d708..fdd1cb7f5ef 100644 --- a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts +++ b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts @@ -14,6 +14,7 @@ declare module 'vscode' { // TODO@API define this type! result: Thenable; + // TODO@API doc what to expect here stream: AsyncIterable; } @@ -52,6 +53,7 @@ declare module 'vscode' { /** * An event that is fired when the access the language model has has been revoked or re-granted. */ + // TODO@API NAME? readonly onDidChangeAccess: Event; /** @@ -94,7 +96,7 @@ declare module 'vscode' { readonly removed: readonly string[]; } - //@API DEFINE the namespace for this: env, lm, ai? + //@API DEFINE the namespace for this: lm (languageModels), copilot, ai, env,? export namespace chat { /** From d5527a86b26e1586b487dfaadc43001c32b4bd64 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 12:56:50 -0600 Subject: [PATCH 1221/1897] add view in chat action even though it doesn't work atm --- .../inlineChat/browser/inlineChatActions.ts | 17 +++++++++++++---- .../terminal/browser/media/terminal.css | 2 +- .../chat/browser/terminalChatActions.ts | 8 ++++---- .../chat/browser/terminalChatController.ts | 18 +++++++++++------- .../chat/browser/terminalChatWidget.ts | 11 +++++------ 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 85033c64166..635233002c4 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -30,6 +30,9 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +// TODO: fix eslint-disable-next-line local/code-import-patterns +import { MENU_TERMINAL_CHAT_INPUT } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); @@ -682,13 +685,19 @@ export class ViewInChatAction extends AbstractInlineChatAction { id: ACTION_VIEW_IN_CHAT, title: localize('viewInChat', 'View in Chat'), icon: Codicon.commentDiscussion, - precondition: CTX_INLINE_CHAT_VISIBLE, - menu: { + precondition: ContextKeyExpr.or(TerminalContextKeys.chatVisible, CTX_INLINE_CHAT_VISIBLE), + menu: [{ id: MENU_INLINE_CHAT_WIDGET_STATUS, - when: CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages), + when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages), CTX_INLINE_CHAT_VISIBLE), group: '0_main', order: 1 - } + }, + { + id: MENU_TERMINAL_CHAT_INPUT, + when: ContextKeyExpr.and(TerminalContextKeys.chatVisible, CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages)), + group: 'inline', + order: 1 + }] }); } override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): void { diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index ab84f0258b1..1aaf1516c5e 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -580,6 +580,6 @@ visibility: hidden; } -.monaco-workbench .terminal-inline-chat-response.message { +.monaco-workbench .terminal-inline-chat .chatMessageContent { width: 400px !important; } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index dcf2aec0617..60032254028 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -9,6 +9,7 @@ import { localize2 } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; @@ -73,13 +74,12 @@ registerActiveXtermAction({ ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.chatRequestActive.negate(), - TerminalContextKeys.chatAgentRegistered + TerminalContextKeys.chatAgentRegistered, + CTX_INLINE_CHAT_EMPTY.negate() ), icon: Codicon.send, keybinding: { - when: TerminalContextKeys.chatRequestActive.negate(), - // TODO: - // when: CTX_INLINE_CHAT_FOCUSED, + when: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, TerminalContextKeys.chatRequestActive.negate()), weight: KeybindingWeight.EditorCore + 7, primary: KeyCode.Enter }, diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index b3b9c002aad..9312e56e19b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -23,6 +23,7 @@ import { marked } from 'vs/base/common/marked/marked'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; +import { CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; export class TerminalChatController extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; @@ -37,11 +38,12 @@ export class TerminalChatController extends Disposable implements ITerminalContr static activeChatWidget?: TerminalChatController; private _chatWidget: Lazy | undefined; private _lastLayoutDimensions: IDimension | undefined; - private _requestId: number = 0; + private _accessibilityRequestId: number = 0; get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } private readonly _ctxHasActiveRequest!: IContextKey; private readonly _ctxHasTerminalAgent!: IContextKey; + private readonly _ctxLastResponseType!: IContextKey; private _cancellationTokenSource!: CancellationTokenSource; @@ -54,7 +56,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IInstantiationService private readonly _instantiationService: IInstantiationService, @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService + @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, ) { super(); if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { @@ -62,6 +64,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } this._ctxHasActiveRequest = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); this._ctxHasTerminalAgent = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); + this._ctxLastResponseType = CTX_INLINE_CHAT_RESPONSE_TYPES.bindTo(this._contextKeyService); if (!this._chatAgentService.hasAgent('terminal')) { this._register(this._chatAgentService.onDidChangeAgents(() => { if (this._chatAgentService.getAgent('terminal')) { @@ -131,10 +134,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.updateProgress(progress); }; const resolvedVariables: Record = {}; - + const requestId = generateUuid(); const requestProps: IChatAgentRequest = { sessionId: generateUuid(), - requestId: generateUuid(), + requestId, agentId, message: this._chatWidget?.rawValue?.input() || '', variables: resolvedVariables, @@ -151,15 +154,16 @@ export class TerminalChatController extends Disposable implements ITerminalContr return; } const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); - this._requestId++; + this._accessibilityRequestId++; if (cancellationToken.isCancellationRequested) { return; } if (codeBlock) { // TODO: check the SR experience - this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._requestId); + this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._accessibilityRequestId); } else { - this._chatWidget?.rawValue?.renderMessage(message, this._requestId); + this._chatWidget?.rawValue?.renderMessage(message, this._accessibilityRequestId, requestId); + this._ctxLastResponseType.set(InlineChatResponseTypes.OnlyMessages); } this._ctxHasActiveRequest.set(false); this._chatWidget?.rawValue?.updateProgress(); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 985fc071cb2..761708aec35 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Dimension, IFocusTracker, trackFocus } from 'vs/base/browser/dom'; +import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/terminalChatWidget'; @@ -82,8 +83,8 @@ export class TerminalChatWidget extends Disposable { this._focusTracker = this._register(trackFocus(this._widgetContainer)); } renderTerminalCommand(codeBlock: string, requestId: number): void { - this._responseElement.classList.remove('message', 'hide'); this._chatAccessibilityService.acceptResponse(codeBlock, requestId); + this._responseElement.classList.remove('hide'); if (!this._responseWidget) { this._responseWidget = this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, {}, { isSimpleWidget: true }); this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: codeBlock })).then((model) => { @@ -98,11 +99,9 @@ export class TerminalChatWidget extends Disposable { } } - renderMessage(message: string, requestId: number): void { - this._responseElement?.classList.remove('hide'); - this._responseElement.classList.add('message'); - this._chatAccessibilityService.acceptResponse(message, requestId); - this._responseElement.textContent = message; + renderMessage(message: string, accessibilityRequestId: number, requestId: string): void { + this._inlineChatWidget.updateChatMessage({ message: new MarkdownString(message), requestId, providerId: 'terminal' }); + this._chatAccessibilityService.acceptResponse(message, accessibilityRequestId); } private async _getTextModel(resource: URI): Promise { From 9af101d1807a52d95714f19621c86425b342ba01 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 12:59:40 -0600 Subject: [PATCH 1222/1897] revert View in Chat action since it doesn't work --- .../inlineChat/browser/inlineChatActions.ts | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 635233002c4..85033c64166 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -30,9 +30,6 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -// TODO: fix eslint-disable-next-line local/code-import-patterns -import { MENU_TERMINAL_CHAT_INPUT } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); @@ -685,19 +682,13 @@ export class ViewInChatAction extends AbstractInlineChatAction { id: ACTION_VIEW_IN_CHAT, title: localize('viewInChat', 'View in Chat'), icon: Codicon.commentDiscussion, - precondition: ContextKeyExpr.or(TerminalContextKeys.chatVisible, CTX_INLINE_CHAT_VISIBLE), - menu: [{ + precondition: CTX_INLINE_CHAT_VISIBLE, + menu: { id: MENU_INLINE_CHAT_WIDGET_STATUS, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages), CTX_INLINE_CHAT_VISIBLE), + when: CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages), group: '0_main', order: 1 - }, - { - id: MENU_TERMINAL_CHAT_INPUT, - when: ContextKeyExpr.and(TerminalContextKeys.chatVisible, CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages)), - group: 'inline', - order: 1 - }] + } }); } override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): void { From 7ad358bdb5ee2688a7df65fbd1381fb8ebffb0e7 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 13:14:43 -0600 Subject: [PATCH 1223/1897] add accept command action --- .../chat/browser/terminalChat.ts | 1 + .../chat/browser/terminalChatActions.ts | 36 ++++++++++++++++++- .../chat/browser/terminalChatController.ts | 5 +++ .../chat/browser/terminalChatWidget.ts | 13 +++++-- 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index 431a0e9c715..882baf8c26b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -13,6 +13,7 @@ export const enum TerminalChatCommandId { FeedbackHelpful = 'workbench.action.terminal.chat.feedbackHelpful', FeedbackUnhelpful = 'workbench.action.terminal.chat.feedbackUnhelpful', FeedbackReportIssue = 'workbench.action.terminal.chat.feedbackReportIssue', + AcceptCommand = 'workbench.action.terminal.chat.acceptCommand', } export const MENU_TERMINAL_CHAT_INPUT = MenuId.for('terminalChatInput'); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 60032254028..73ebf9631a9 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -9,7 +9,7 @@ import { localize2 } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; @@ -67,6 +67,40 @@ registerActiveXtermAction({ } }); + + + +registerActiveXtermAction({ + id: TerminalChatCommandId.AcceptCommand, + title: localize2('workbench.action.terminal.acceptCommand', 'Accept Command'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + TerminalContextKeys.chatRequestActive.negate(), + TerminalContextKeys.chatAgentRegistered, + CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyEdits) + ), + icon: Codicon.check, + keybinding: { + when: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, TerminalContextKeys.chatRequestActive.negate()), + weight: KeybindingWeight.EditorCore + 7, + primary: KeyCode.Enter + }, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET, + group: 'main', + order: 0, + when: CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyEdits), + }, + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.acceptCommand(); + } +}); + registerActiveXtermAction({ id: TerminalChatCommandId.MakeRequest, title: localize2('workbench.action.terminal.submitChat', 'Make Chat Request'), diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 9312e56e19b..0a943b362fd 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -161,6 +161,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (codeBlock) { // TODO: check the SR experience this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._accessibilityRequestId); + this._ctxLastResponseType.set(InlineChatResponseTypes.OnlyEdits); } else { this._chatWidget?.rawValue?.renderMessage(message, this._accessibilityRequestId, requestId); this._ctxLastResponseType.set(InlineChatResponseTypes.OnlyMessages); @@ -169,6 +170,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.updateProgress(); } + acceptCommand(): void { + this._chatWidget?.rawValue?.acceptCommand(); + } + reveal(): void { this._chatWidget?.rawValue?.reveal(); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 761708aec35..0ccea8a61cd 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -91,8 +91,10 @@ export class TerminalChatWidget extends Disposable { if (!model || !this._responseWidget) { return; } + this._responseWidget.layout(new Dimension(400, 0)); this._responseWidget.setModel(model); - this._responseWidget.layout(new Dimension(400, 150)); + const height = this._responseWidget.getContentHeight(); + this._responseWidget.layout(new Dimension(400, height)); }); } else { this._responseWidget.setValue(codeBlock); @@ -113,7 +115,6 @@ export class TerminalChatWidget extends Disposable { } reveal(): void { this._inlineChatWidget.layout(new Dimension(400, 150)); - this._widgetContainer.classList.remove('hide'); this._chatWidgetFocused.set(true); this._chatWidgetVisible.set(true); @@ -139,6 +140,14 @@ export class TerminalChatWidget extends Disposable { this._responseElement?.classList.add('hide'); } } + acceptCommand(): void { + const value = this._responseWidget?.getValue(); + if (!value) { + return; + } + this._instance.sendText(value, false, true); + this.hide(); + } updateProgress(progress?: IChatProgress): void { this._inlineChatWidget.updateProgress(progress?.kind === 'content' || progress?.kind === 'markdownContent'); } From 9992d7da51e1d97858721263038a82e6c80149cd Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:18:10 -0800 Subject: [PATCH 1224/1897] Fix terminal voice menu ids --- .../chat/electron-sandbox/actions/voiceChatActions.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 74ec952ba1c..e41ae049b6e 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -54,7 +54,6 @@ import { IVoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ThemeIcon } from 'vs/base/common/themables'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { MENU_TERMINAL_CHAT_INPUT } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); @@ -529,8 +528,7 @@ export class StartVoiceChatAction extends Action2 { order: -1 }, { - // TODO: Fix layer breaker, chat can't depend on terminalContrib - id: MENU_TERMINAL_CHAT_INPUT, + id: MenuId.for('terminalChatInput'), when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.negate()), group: 'main', order: -1 @@ -580,6 +578,11 @@ export class InstallVoiceChatAction extends Action2 { when: HasSpeechProvider.negate(), group: 'main', order: -1 + }, { + id: MenuId.for('terminalChatInput'), + when: HasSpeechProvider.negate(), + group: 'main', + order: -1 }] }); } From da8f67171617edccb55461df9e2c6bb6e8aca4d4 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Tue, 13 Feb 2024 13:22:04 -0600 Subject: [PATCH 1225/1897] try to ignore history on editor open for quicksearch (#204880) * try to ignore history on editor open * correct history service change * fix history race condition * edit shouldIgnoreActiveEditorChange description --- .../quickTextSearch/textSearchQuickAccess.ts | 21 +++++++++++++---- .../history/browser/historyService.ts | 23 ++++++++++++++++--- .../services/history/common/history.ts | 1 + .../test/browser/workbenchTestServices.ts | 1 + .../test/common/workbenchTestServices.ts | 1 + 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index b1ba725a1b3..b5ad884d5d0 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -30,6 +30,8 @@ import { IPatternInfo, ISearchComplete, ITextQuery, VIEW_ID } from 'vs/workbench import { Event } from 'vs/base/common/event'; import { EditorViewState } from 'vs/workbench/browser/quickaccess'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { Sequencer } from 'vs/base/common/async'; export const TEXT_SEARCH_QUICK_ACCESS_PREFIX = '%'; @@ -48,6 +50,8 @@ interface ITextSearchQuickAccessItem extends IPickerQuickAccessItem { match?: Match; } export class TextSearchQuickAccess extends PickerQuickAccessProvider { + + private editorSequencer: Sequencer; private queryBuilder: QueryBuilder; private searchModel: SearchModel; private currentAsyncSearch: Promise = Promise.resolve({ @@ -80,13 +84,15 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { + // disable and re-enable history service so that we can ignore this history entry + this._historyService.shouldIgnoreActiveEditorChange = true; + await this._editorService.openEditor({ + resource: itemMatch.parent().resource, + options: { preserveFocus: true, revealIfOpened: true, ignoreError: true, selection: itemMatch.range() } + }); + this._historyService.shouldIgnoreActiveEditorChange = false; }); } })); diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts index 1c73d334210..3c580614b86 100644 --- a/src/vs/workbench/services/history/browser/historyService.ts +++ b/src/vs/workbench/services/history/browser/historyService.ts @@ -47,6 +47,10 @@ export class HistoryService extends Disposable implements IHistoryService { private readonly editorHelper = this.instantiationService.createInstance(EditorHelper); + // Can be set to temporarily ignore messages from the editor service that indicate a new active editor. + // Used for ignoring some editors for history. + public shouldIgnoreActiveEditorChange: boolean = false; + constructor( @IEditorService private readonly editorService: EditorServiceImpl, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @@ -77,10 +81,18 @@ export class HistoryService extends Disposable implements IHistoryService { this.registerMouseNavigationListener(); // Editor changes - this._register(this.editorService.onDidActiveEditorChange(() => this.onDidActiveEditorChange())); + this._register(this.editorService.onDidActiveEditorChange((e) => { + if (!this.shouldIgnoreActiveEditorChange) { + this.onDidActiveEditorChange(); + } + })); this._register(this.editorService.onDidOpenEditorFail(event => this.remove(event.editor))); this._register(this.editorService.onDidCloseEditor(event => this.onDidCloseEditor(event))); - this._register(this.editorService.onDidMostRecentlyActiveEditorsChange(() => this.handleEditorEventInRecentEditorsStack())); + this._register(this.editorService.onDidMostRecentlyActiveEditorsChange(() => { + if (!this.shouldIgnoreActiveEditorChange) { + this.handleEditorEventInRecentEditorsStack(); + } + })); // Editor group changes this._register(this.editorGroupService.onDidRemoveGroup(e => this.onDidRemoveGroup(e))); @@ -177,7 +189,12 @@ export class HistoryService extends Disposable implements IHistoryService { // Listen to selection changes if the editor pane // is having a selection concept. if (isEditorPaneWithSelection(activeEditorPane)) { - this.activeEditorListeners.add(activeEditorPane.onDidChangeSelection(e => this.handleActiveEditorSelectionChangeEvent(activeEditorGroup, activeEditorPane, e))); + this.activeEditorListeners.add(activeEditorPane.onDidChangeSelection(e => { + if (!this.shouldIgnoreActiveEditorChange) { + this.handleActiveEditorSelectionChangeEvent(activeEditorGroup, activeEditorPane, e); + } + } + )); } // Context keys diff --git a/src/vs/workbench/services/history/common/history.ts b/src/vs/workbench/services/history/common/history.ts index b5abcd2dad3..86d21f49dad 100644 --- a/src/vs/workbench/services/history/common/history.ts +++ b/src/vs/workbench/services/history/common/history.ts @@ -60,6 +60,7 @@ export const enum GoScope { export interface IHistoryService { readonly _serviceBrand: undefined; + shouldIgnoreActiveEditorChange: boolean; /** * Navigate forwards in editor navigation history. diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index f6febaf3d2f..10461c8e293 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -548,6 +548,7 @@ export class TestMenuService implements IMenuService { export class TestHistoryService implements IHistoryService { declare readonly _serviceBrand: undefined; + declare shouldIgnoreActiveEditorChange: boolean; constructor(private root?: URI) { } diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index 1a938d7dbd6..d1483ff15be 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -149,6 +149,7 @@ export class TestStorageService extends InMemoryStorageService { export class TestHistoryService implements IHistoryService { declare readonly _serviceBrand: undefined; + declare shouldIgnoreActiveEditorChange: boolean; constructor(private root?: URI) { } From 702535987ca16b9ba1ed8d86443a1d24a5400538 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 13:29:56 -0600 Subject: [PATCH 1226/1897] add accept command keybinding --- .../terminal/common/terminalContextKey.ts | 4 +++ .../chat/browser/terminalChatActions.ts | 8 ++--- .../chat/browser/terminalChatController.ts | 2 +- .../chat/browser/terminalChatWidget.ts | 35 ++++++++++++------- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 7f777f94f62..8ea1469a4b1 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -44,6 +44,7 @@ export const enum TerminalContextKeyStrings { ChatActiveRequest = 'terminalChatActiveRequest', ChatInputHasText = 'terminalChatInputHasText', ChatAgentRegistered = 'terminalChatAgentRegistered', + ChatResponseEditorFocused = 'terminalChatResponseEditorFocused', } export namespace TerminalContextKeys { @@ -179,4 +180,7 @@ export namespace TerminalContextKeys { /** Whether the terminal chat agent has been registered */ export const chatAgentRegistered = new RawContextKey(TerminalContextKeyStrings.ChatAgentRegistered, false, localize('chatAgentRegisteredContextKey', "Whether the terminal chat agent has been registered.")); + + /** Whether the chat response editor is focused */ + export const chatResponseEditorFocused = new RawContextKey(TerminalContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseEditorFocusedContextKey', "Whether the chat response editor is focused.")); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 73ebf9631a9..37cf45c77fc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -78,19 +78,19 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.chatRequestActive.negate(), TerminalContextKeys.chatAgentRegistered, - CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyEdits) + CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty) ), icon: Codicon.check, keybinding: { - when: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalContextKeys.chatResponseEditorFocused, TerminalContextKeys.chatRequestActive.negate()), weight: KeybindingWeight.EditorCore + 7, - primary: KeyCode.Enter + primary: KeyMod.CtrlCmd | KeyCode.Enter, }, menu: { id: MENU_TERMINAL_CHAT_WIDGET, group: 'main', order: 0, - when: CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyEdits), + when: CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 0a943b362fd..00b117d6661 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -65,6 +65,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._ctxHasActiveRequest = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); this._ctxHasTerminalAgent = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); this._ctxLastResponseType = CTX_INLINE_CHAT_RESPONSE_TYPES.bindTo(this._contextKeyService); + if (!this._chatAgentService.hasAgent('terminal')) { this._register(this._chatAgentService.onDidChangeAgents(() => { if (this._chatAgentService.getAgent('terminal')) { @@ -161,7 +162,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (codeBlock) { // TODO: check the SR experience this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._accessibilityRequestId); - this._ctxLastResponseType.set(InlineChatResponseTypes.OnlyEdits); } else { this._chatWidget?.rawValue?.renderMessage(message, this._accessibilityRequestId, requestId); this._ctxLastResponseType.set(InlineChatResponseTypes.OnlyMessages); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 0ccea8a61cd..41332e575fb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -23,15 +23,16 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; export class TerminalChatWidget extends Disposable { - private _scopedInstantiationService: IInstantiationService; - private _widgetContainer: HTMLElement; - private _chatWidgetFocused: IContextKey; - private _chatWidgetVisible: IContextKey; + private readonly _scopedInstantiationService: IInstantiationService; + private readonly _widgetContainer: HTMLElement; + private readonly _ctxChatWidgetFocused: IContextKey; + private readonly _ctxChatWidgetVisible: IContextKey; + private readonly _ctxResponseEditorFocused!: IContextKey; private readonly _inlineChatWidget: InlineChatWidget; - private _responseWidget: CodeEditorWidget | undefined; - private _responseElement: HTMLElement; + private readonly _responseElement: HTMLElement; private readonly _focusTracker: IFocusTracker; + private _responseWidget: CodeEditorWidget | undefined; constructor( private readonly _container: HTMLElement, @@ -44,8 +45,10 @@ export class TerminalChatWidget extends Disposable { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); - this._chatWidgetFocused = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); - this._chatWidgetVisible = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); + this._ctxChatWidgetFocused = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); + this._ctxChatWidgetVisible = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); + this._ctxResponseEditorFocused = TerminalContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); + this._widgetContainer = document.createElement('div'); this._widgetContainer.classList.add('terminal-inline-chat'); this._container.appendChild(this._widgetContainer); @@ -86,7 +89,7 @@ export class TerminalChatWidget extends Disposable { this._chatAccessibilityService.acceptResponse(codeBlock, requestId); this._responseElement.classList.remove('hide'); if (!this._responseWidget) { - this._responseWidget = this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, {}, { isSimpleWidget: true }); + this._responseWidget = this._register(this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, {}, { isSimpleWidget: true })); this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: codeBlock })).then((model) => { if (!model || !this._responseWidget) { return; @@ -96,6 +99,12 @@ export class TerminalChatWidget extends Disposable { const height = this._responseWidget.getContentHeight(); this._responseWidget.layout(new Dimension(400, height)); }); + this._register(this._responseWidget.onDidFocusEditorText(() => { + this._ctxResponseEditorFocused.set(true); + })); + this._register(this._responseWidget.onDidBlurEditorText(() => { + this._ctxResponseEditorFocused.set(false); + })); } else { this._responseWidget.setValue(codeBlock); } @@ -116,15 +125,15 @@ export class TerminalChatWidget extends Disposable { reveal(): void { this._inlineChatWidget.layout(new Dimension(400, 150)); this._widgetContainer.classList.remove('hide'); - this._chatWidgetFocused.set(true); - this._chatWidgetVisible.set(true); + this._ctxChatWidgetFocused.set(true); + this._ctxChatWidgetVisible.set(true); this._inlineChatWidget.focus(); } hide(): void { this._responseElement?.classList.add('hide'); this._widgetContainer.classList.add('hide'); - this._chatWidgetFocused.set(false); - this._chatWidgetVisible.set(false); + this._ctxChatWidgetFocused.set(false); + this._ctxChatWidgetVisible.set(false); this._instance.focus(); } cancel(): void { From c2316194e2306d239e8a6e2ea3049ed0087ed191 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 13 Feb 2024 16:33:21 -0300 Subject: [PATCH 1227/1897] Get rid of history2, variables2 (#205124) * Get rid of history2 * Remove prompt2 and variables2 * Clean up variables2/prompt2 * Fix tests * Fix ranges * Fix test --- .../src/singlefolder-tests/chat.test.ts | 4 +- .../api/common/extHostChatAgents2.ts | 4 +- .../api/common/extHostTypeConverters.ts | 5 +- .../contrib/chat/browser/chatVariables.ts | 36 +++++-------- .../contrib/chat/common/chatAgents.ts | 6 +-- .../contrib/chat/common/chatModel.ts | 16 ++---- .../contrib/chat/common/chatParserTypes.ts | 7 ++- .../contrib/chat/common/chatServiceImpl.ts | 51 ++++++++----------- .../chat/test/browser/chatVariables.test.ts | 34 ++++++------- .../__snapshots__/Chat_can_deserialize.0.snap | 5 +- .../__snapshots__/Chat_can_serialize.1.snap | 5 +- .../chat/test/common/chatModel.test.ts | 2 +- .../chat/test/common/chatService.test.ts | 4 +- .../chat/test/common/mockChatVariables.ts | 3 +- .../vscode.proposed.chatAgents2.d.ts | 31 ++--------- 15 files changed, 74 insertions(+), 139 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index 871fb8572ff..6c0406aadbb 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -64,8 +64,8 @@ suite('chat', () => { const deferred = getDeferredForRequest(); interactive.sendInteractiveRequestToProvider('provider', { message: '@agent hi #myVar' }); const request = await deferred.p; - assert.strictEqual(request.prompt, 'hi [#myVar](values:myVar)'); - assert.strictEqual(request.variables['myVar'][0].value, 'myValue'); + assert.strictEqual(request.prompt, 'hi #myVar'); + assert.strictEqual(request.variables[0].values[0].value, 'myValue'); }); test('result metadata is returned to the followup provider', async () => { diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index e32d418c24a..bd233330fb2 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -200,7 +200,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const convertedHistory = await this.prepareHistoryTurns(request, context); const task = agent.invoke( typeConvert.ChatAgentRequest.to(request), - { history: convertedHistory, history2: convertedHistory }, + { history: convertedHistory }, stream.apiObject, token ); @@ -239,7 +239,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { { ...ehResult, metadata: undefined }; // REQUEST turn - res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.command, h.request.variables2.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', agentId: h.request.agentId })); + res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', agentId: h.request.agentId })); // RESPONSE turn const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 91f918b71cf..4c403c04398 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2631,11 +2631,8 @@ export namespace ChatAgentRequest { export function to(request: IChatAgentRequest): vscode.ChatAgentRequest { return { prompt: request.message, - prompt2: request.variables2.message, - variables: ChatVariable.objectTo(request.variables), command: request.command, - agentId: request.agentId, - variables2: request.variables2.variables.map(ChatAgentResolvedVariable.to) + variables: request.variables.variables.map(ChatAgentResolvedVariable.to) }; } } diff --git a/src/vs/workbench/contrib/chat/browser/chatVariables.ts b/src/vs/workbench/contrib/chat/browser/chatVariables.ts index 3bb6fa6b7ca..95baa1d92da 100644 --- a/src/vs/workbench/contrib/chat/browser/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/chatVariables.ts @@ -3,10 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { coalesce } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; import { Iterable } from 'vs/base/common/iterator'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IOffsetRange } from 'vs/editor/common/core/offsetRange'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatDynamicVariableModel } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables'; import { IChatModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; @@ -29,51 +31,37 @@ export class ChatVariablesService implements IChatVariablesService { } async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise { - const resolvedVariables: Record = {}; + let resolvedVariables: { name: string; range: IOffsetRange; values: IChatRequestVariableValue[] }[] = []; const jobs: Promise[] = []; - const parsedPrompt: string[] = []; prompt.parts .forEach((part, i) => { if (part instanceof ChatRequestVariablePart) { const data = this._resolver.get(part.variableName.toLowerCase()); if (data) { - jobs.push(data.resolver(prompt.text, part.variableArg, model, token).then(value => { - if (value) { - resolvedVariables[part.variableName] = value; - parsedPrompt[i] = `[${part.text}](values:${part.variableName})`; - } else { - parsedPrompt[i] = part.promptText; + jobs.push(data.resolver(prompt.text, part.variableArg, model, token).then(values => { + if (values?.length) { + resolvedVariables[i] = { name: part.variableName, range: part.range, values }; } }).catch(onUnexpectedExternalError)); } } else if (part instanceof ChatRequestDynamicVariablePart) { - const referenceName = this.getUniqueReferenceName(part.referenceText, resolvedVariables); - resolvedVariables[referenceName] = part.data; - const safeText = part.text.replace(/[\[\]]/g, '_'); - const safeTarget = referenceName.replace(/[\(\)]/g, '_'); - parsedPrompt[i] = `[${safeText}](values:${safeTarget})`; - } else { - parsedPrompt[i] = part.promptText; + resolvedVariables[i] = { name: part.referenceText, range: part.range, values: part.data }; } }); await Promise.allSettled(jobs); + resolvedVariables = coalesce(resolvedVariables); + + // "reverse", high index first so that replacement is simple + resolvedVariables.sort((a, b) => b.range.start - a.range.start); + return { variables: resolvedVariables, - message: parsedPrompt.join('').trim() }; } - private getUniqueReferenceName(name: string, vars: Record): string { - let i = 1; - while (vars[name]) { - name = `${name}_${i++}`; - } - return name; - } - hasVariable(name: string): boolean { return this._resolver.has(name.toLowerCase()); } diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index adbaa77da2a..dbcabdd422d 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -13,9 +13,8 @@ import { URI } from 'vs/base/common/uri'; import { ProviderResult } from 'vs/editor/common/languages'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IChatProgressResponseContent, IChatRequestVariableData2 } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatProgressResponseContent, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; -import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; //#region agent service, commands etc @@ -89,8 +88,7 @@ export interface IChatAgentRequest { agentId: string; command?: string; message: string; - variables: Record; - variables2: IChatRequestVariableData2; + variables: IChatRequestVariableData; } export interface IChatAgentResult { diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 56f75f06eda..64b50701e84 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -20,16 +20,6 @@ import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatCommandButton, import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; export interface IChatRequestVariableData { - /** - * The user's message with variable references for extension API. - */ - message: string; - - variables: Record; -} - -export interface IChatRequestVariableData2 { - message: string; variables: { name: string; range: IOffsetRange; values: IChatRequestVariableValue[] }[]; } @@ -574,8 +564,10 @@ export class ChatModel extends Disposable implements IChatModel { ? this.getParsedRequestFromString(raw.message) : reviveParsedChatRequest(raw.message); - // Only old messages don't have variableData - const variableData: IChatRequestVariableData = raw.variableData ?? { message: parsedRequest.text, variables: {} }; + // Old messages don't have variableData, or have it in the wrong (non-array) shape + const variableData: IChatRequestVariableData = raw.variableData && Array.isArray(raw.variableData.variables) + ? raw.variableData : + { variables: [] }; const request = new ChatRequestModel(this, parsedRequest, variableData); if (raw.response || raw.result || (raw as any).responseErrorDetails) { const agent = (raw.agent && 'metadata' in raw.agent) ? // Check for the new format, ignore entries in the old format diff --git a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts index fd9630ab515..4f7654fd56a 100644 --- a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts +++ b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts @@ -26,8 +26,11 @@ export interface IParsedChatRequestPart { readonly promptText: string; } -export function getPromptText(request: ReadonlyArray): string { - return request.map(r => r.promptText).join(''); +export function getPromptText(request: IParsedChatRequest): { message: string; diff: number } { + const message = request.parts.map(r => r.promptText).join('').trimStart(); + const diff = request.text.length - message.length; + + return { message, diff }; } export class ChatRequestTextPart implements IParsedChatRequestPart { diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 5ec45665e6d..7197f521f06 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -22,8 +22,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, IChatRequestVariableData2, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicVariablePart, ChatRequestSlashCommandPart, ChatRequestVariablePart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; @@ -537,38 +537,38 @@ export class ChatService extends Disposable implements IChatService { continue; } + const promptTextResult = getPromptText(request.message); const historyRequest: IChatAgentRequest = { sessionId, requestId: request.id, agentId: request.response.agent?.id ?? '', - message: request.variableData.message, - variables: request.variableData.variables, + message: promptTextResult.message, command: request.response.slashCommand?.name, - variables2: asVariablesData2(request.message, request.variableData) + variables: updateRanges(request.variableData, promptTextResult.diff) // TODO bit of a hack }; history.push({ request: historyRequest, response: request.response.response.value, result: request.response.result ?? {} }); } - const initVariableData: IChatRequestVariableData = { message: getPromptText(parsedRequest.parts), variables: {} }; + const initVariableData: IChatRequestVariableData = { variables: [] }; request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); request.variableData = variableData; + const promptTextResult = getPromptText(request.message); const requestProps: IChatAgentRequest = { sessionId, requestId: request.id, agentId: agent.id, - message: variableData.message, - variables: variableData.variables, + message: promptTextResult.message, command: agentSlashCommandPart?.command.name, - variables2: asVariablesData2(parsedRequest, variableData) + variables: updateRanges(variableData, promptTextResult.diff) // TODO bit of a hack }; const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); rawResult = agentResult; agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, followupsCancelToken); } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { - request = model.addRequest(parsedRequest, { message: parsedRequest.text, variables: {} }); + request = model.addRequest(parsedRequest, { variables: [] }); // contributed slash commands // TODO: spell this out in the UI const history: IChatMessage[] = []; @@ -670,7 +670,7 @@ export class ChatService extends Disposable implements IChatService { const parsedRequest = typeof message === 'string' ? this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message) : message; - const request = model.addRequest(parsedRequest, variableData || { message: parsedRequest.text, variables: {} }); + const request = model.addRequest(parsedRequest, variableData || { variables: [] }); if (typeof response.message === 'string') { model.acceptResponseProgress(request, { content: response.message, kind: 'content' }); } else { @@ -765,25 +765,14 @@ export class ChatService extends Disposable implements IChatService { } } -function asVariablesData2(parsedRequest: IParsedChatRequest, variableData: IChatRequestVariableData): IChatRequestVariableData2 { - - const res: IChatRequestVariableData2 = { - message: getPromptText(parsedRequest.parts), - variables: [] +function updateRanges(variableData: IChatRequestVariableData, diff: number): IChatRequestVariableData { + return { + variables: variableData.variables.map(v => ({ + ...v, + range: { + start: v.range.start - diff, + endExclusive: v.range.endExclusive - diff + } + })) }; - - for (const part of parsedRequest.parts) { - if (part instanceof ChatRequestVariablePart) { - const values = variableData.variables[part.variableName]; - res.variables.push({ name: part.variableName, range: part.range, values }); - } else if (part instanceof ChatRequestDynamicVariablePart) { - // Need variable without `#` - res.variables.push({ name: part.referenceText, range: part.range, values: part.data }); - } - } - - // "reverse", high index first so that replacement is simple - res.variables.sort((a, b) => b.range.start - a.range.start); - - return res; } diff --git a/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts index bda3f40baac..7b9bbde63c7 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts @@ -41,48 +41,44 @@ suite('ChatVariables', function () { const parser = instantiationService.createInstance(ChatRequestParser); const resolveVariables = async (text: string) => { - const result = await parser.parseChatRequest('1', text); + const result = parser.parseChatRequest('1', text); return await service.resolveVariables(result, null!, CancellationToken.None); }; { const data = await resolveVariables('Hello #foo and#far'); - assert.strictEqual(Object.keys(data.variables).length, 1); - assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); - assert.strictEqual(data.message, 'Hello [#foo](values:foo) and#far'); + assert.strictEqual(data.variables.length, 1); + assert.deepEqual(data.variables.map(v => v.name), ['foo']); } { const data = await resolveVariables('#foo Hello'); - assert.strictEqual(Object.keys(data.variables).length, 1); - assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); - assert.strictEqual(data.message, '[#foo](values:foo) Hello'); + assert.strictEqual(data.variables.length, 1); + assert.deepEqual(data.variables.map(v => v.name), ['foo']); } { const data = await resolveVariables('Hello #foo'); - assert.strictEqual(Object.keys(data.variables).length, 1); - assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); + assert.strictEqual(data.variables.length, 1); + assert.deepEqual(data.variables.map(v => v.name), ['foo']); } { const data = await resolveVariables('Hello #foo?'); - assert.strictEqual(Object.keys(data.variables).length, 1); - assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); - assert.strictEqual(data.message, 'Hello [#foo](values:foo)?'); + assert.strictEqual(data.variables.length, 1); + assert.deepEqual(data.variables.map(v => v.name), ['foo']); } { const data = await resolveVariables('Hello #foo and#far #foo'); - assert.strictEqual(Object.keys(data.variables).length, 1); - assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); + assert.strictEqual(data.variables.length, 2); + assert.deepEqual(data.variables.map(v => v.name), ['foo', 'foo']); } { const data = await resolveVariables('Hello #foo and #far #foo'); - assert.strictEqual(Object.keys(data.variables).length, 2); - assert.deepEqual(Object.keys(data.variables).sort(), ['far', 'foo']); + assert.strictEqual(data.variables.length, 3); + assert.deepEqual(data.variables.map(v => v.name), ['foo', 'far', 'foo']); } { const data = await resolveVariables('Hello #foo and #far #foo #unknown'); - assert.strictEqual(Object.keys(data.variables).length, 2); - assert.deepEqual(Object.keys(data.variables).sort(), ['far', 'foo']); - assert.strictEqual(data.message, 'Hello [#foo](values:foo) and [#far](values:far) [#foo](values:foo) #unknown'); + assert.strictEqual(data.variables.length, 3); + assert.deepEqual(data.variables.map(v => v.name), ['foo', 'far', 'foo']); } v1.dispose(); diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap index 1e34cf65b08..d58da8b3744 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap @@ -42,10 +42,7 @@ } ] }, - variableData: { - message: "@ChatProviderWithUsedContext test request", - variables: { } - }, + variableData: { variables: [ ] }, response: [ ], result: { metadata: { metadataKey: "value" } }, followups: undefined, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap index 16a53a88b07..3a6b248a792 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap @@ -42,10 +42,7 @@ ], text: "@ChatProviderWithUsedContext test request" }, - variableData: { - message: "@ChatProviderWithUsedContext test request", - variables: { } - }, + variableData: { variables: [ ] }, response: [ ], result: { metadata: { metadataKey: "value" } }, followups: undefined, diff --git a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index ed18235fe4f..ffcda81f6de 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -111,7 +111,7 @@ suite('ChatModel', () => { model.startInitialize(); model.initialize({} as any, undefined); const text = 'hello'; - model.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }, { message: text, variables: {} }); + model.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }, { variables: [] }); const requests = model.getRequests(); assert.strictEqual(requests.length, 1); 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 eface4d487d..9f7e33e850a 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -131,11 +131,11 @@ suite('Chat', () => { const session1 = testDisposables.add(testService.startSession('provider1', CancellationToken.None)); await session1.waitForInitialization(); - session1.addRequest({ parts: [], text: 'request 1' }, { message: 'request 1', variables: {} }); + session1.addRequest({ parts: [], text: 'request 1' }, { variables: [] }); const session2 = testDisposables.add(testService.startSession('provider2', CancellationToken.None)); await session2.waitForInitialization(); - session2.addRequest({ parts: [], text: 'request 2' }, { message: 'request 2', variables: {} }); + session2.addRequest({ parts: [], text: 'request 2' }, { variables: [] }); storageService.flush(); const testService2 = testDisposables.add(instantiationService.createInstance(ChatService)); diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts b/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts index 73d64ca339b..75220254336 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts @@ -29,8 +29,7 @@ export class MockChatVariablesService implements IChatVariablesService { async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise { return { - message: prompt.text, - variables: {} + variables: [] }; } } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 3ee255c32e1..39b2360f360 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -31,7 +31,7 @@ declare module 'vscode' { /** * The prompt as entered by the user. * - * Information about variables used in this request are is stored in {@link ChatAgentRequest.variables2}. + * Information about variables used in this request are is stored in {@link ChatAgentRequest.variables}. * * *Note* that the {@link ChatAgent2.name name} of the agent and the {@link ChatAgentCommand.name command} * are not part of the prompt. @@ -88,11 +88,6 @@ declare module 'vscode' { * All of the chat messages so far in the current chat session. */ readonly history: ReadonlyArray; - - /** - * @deprecated, use histroy - */ - readonly history2: ReadonlyArray; } /** @@ -325,41 +320,25 @@ declare module 'vscode' { export interface ChatAgentRequest { - /** - * The prompt entered by the user. The {@link ChatAgent2.name name} of the agent or the {@link ChatAgentCommand.name command} - * are not part of the prompt. - * - * @see {@link ChatAgentRequest.command} - */ - prompt: string; - /** * The prompt as entered by the user. * - * Information about variables used in this request are is stored in {@link ChatAgentRequest.variables2}. + * Information about variables used in this request are is stored in {@link ChatAgentRequest.variables}. * * *Note* that the {@link ChatAgent2.name name} of the agent and the {@link ChatAgentCommand.name command} * are not part of the prompt. */ - prompt2: string; - - /** - * The ID of the chat agent to which this request was directed. - */ - agentId: string; + prompt: string; /** * The name of the {@link ChatAgentCommand command} that was selected for this request. */ command?: string; - /** @deprecated */ - variables: Record; - /** - * + * The list of variables that are referenced in the prompt. */ - variables2: ChatAgentResolvedVariable[]; + variables: ChatAgentResolvedVariable[]; } export interface ChatAgentResponseStream { From b93cd296b072da5ae170af15695e00d865c449f8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 12:15:54 -0800 Subject: [PATCH 1228/1897] Working terminal voice chat --- .../actions/media/voiceChatActions.css | 9 ++- .../actions/voiceChatActions.ts | 77 ++++++++++++++++--- .../electron-sandbox/chat.contribution.ts | 3 +- .../chat/browser/terminalChatController.ts | 52 +++++++++++++ .../chat/browser/terminalChatWidget.ts | 8 ++ 5 files changed, 136 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css b/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css index 7a43cef7d12..87b1d68f988 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css @@ -7,7 +7,8 @@ * Replace with "microphone" icon. */ .monaco-workbench .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, -.monaco-workbench .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { +.monaco-workbench .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, +.monaco-workbench .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { content: "\ec1c"; } @@ -15,7 +16,8 @@ * Clear animation styles when reduced motion is enabled. */ .monaco-workbench.reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), -.monaco-workbench.reduce-motion .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { +.monaco-workbench.reduce-motion .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), +.monaco-workbench.reduce-motion .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { animation: none; } @@ -23,6 +25,7 @@ * Replace with "stop" icon when reduced motion is enabled. */ .monaco-workbench.reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, -.monaco-workbench.reduce-motion .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { +.monaco-workbench.reduce-motion .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, +.monaco-workbench.reduce-motion .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { content: "\ead7"; } diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index e41ae049b6e..aeb85706fc6 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -54,18 +54,23 @@ import { IVoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ThemeIcon } from 'vs/base/common/themables'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +// TODO: The chat needs to move into contrib/terminal/ as we don't want anything importing from terminalContrib/ +// eslint-disable-next-line local/code-import-patterns +import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); const CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS = new RawContextKey('quickVoiceChatInProgress', false, { type: 'boolean', description: localize('quickVoiceChatInProgress', "True when voice recording from microphone is in progress for quick chat.") }); const CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS = new RawContextKey('inlineVoiceChatInProgress', false, { type: 'boolean', description: localize('inlineVoiceChatInProgress', "True when voice recording from microphone is in progress for inline chat.") }); +const CONTEXT_TERMINAL_VOICE_CHAT_IN_PROGRESS = new RawContextKey('terminalVoiceChatInProgress', false, { type: 'boolean', description: localize('terminalVoiceChatInProgress', "True when voice recording from microphone is in progress for terminal chat.") }); const CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS = new RawContextKey('voiceChatInViewInProgress', false, { type: 'boolean', description: localize('voiceChatInViewInProgress', "True when voice recording from microphone is in progress in the chat view.") }); const CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS = new RawContextKey('voiceChatInEditorInProgress', false, { type: 'boolean', description: localize('voiceChatInEditorInProgress', "True when voice recording from microphone is in progress in the chat editor.") }); const CanVoiceChat = ContextKeyExpr.and(CONTEXT_PROVIDER_EXISTS, HasSpeechProvider); -type VoiceChatSessionContext = 'inline' | 'quick' | 'view' | 'editor'; +type VoiceChatSessionContext = 'inline' | 'terminal' | 'quick' | 'view' | 'editor'; interface IVoiceChatSessionController { @@ -89,8 +94,9 @@ class VoiceChatSessionControllerFactory { static create(accessor: ServicesAccessor, context: 'quick'): Promise; static create(accessor: ServicesAccessor, context: 'view'): Promise; static create(accessor: ServicesAccessor, context: 'focused'): Promise; - static create(accessor: ServicesAccessor, context: 'inline' | 'quick' | 'view' | 'focused'): Promise; - static async create(accessor: ServicesAccessor, context: 'inline' | 'quick' | 'view' | 'focused'): Promise { + static create(accessor: ServicesAccessor, context: 'terminal'): Promise; + static create(accessor: ServicesAccessor, context: 'inline' | 'terminal' | 'quick' | 'view' | 'focused'): Promise; + static async create(accessor: ServicesAccessor, context: 'inline' | 'terminal' | 'quick' | 'view' | 'focused'): Promise { const chatWidgetService = accessor.get(IChatWidgetService); const chatService = accessor.get(IChatService); const viewsService = accessor.get(IViewsService); @@ -98,6 +104,7 @@ class VoiceChatSessionControllerFactory { const quickChatService = accessor.get(IQuickChatService); const layoutService = accessor.get(IWorkbenchLayoutService); const editorService = accessor.get(IEditorService); + const terminalService = accessor.get(ITerminalService); // Currently Focused Context if (context === 'focused') { @@ -132,6 +139,15 @@ class VoiceChatSessionControllerFactory { return VoiceChatSessionControllerFactory.doCreateForInlineChat(inlineChat); } } + + // Try with the terminal chat + const activeInstance = terminalService.activeInstance; + if (activeInstance) { + const terminalChat = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + if (terminalChat?.hasFocus()) { + return VoiceChatSessionControllerFactory.doCreateForTerminalChat(terminalChat); + } + } } // View Chat @@ -156,6 +172,17 @@ class VoiceChatSessionControllerFactory { } } + // Terminal Chat + if (context === 'terminal') { + const activeInstance = terminalService.activeInstance; + if (activeInstance) { + const terminalChat = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + if (terminalChat) { + return VoiceChatSessionControllerFactory.doCreateForTerminalChat(terminalChat); + } + } + } + // Quick Chat if (context === 'quick') { quickChatService.open(); @@ -224,6 +251,20 @@ class VoiceChatSessionControllerFactory { clearInputPlaceholder: () => inlineChat.resetPlaceholder() }; } + + private static doCreateForTerminalChat(terminalChat: TerminalChatController): IVoiceChatSessionController { + return { + context: 'terminal', + onDidAcceptInput: terminalChat.onDidAcceptInput, + onDidCancelInput: terminalChat.onDidCancelInput, + focusInput: () => terminalChat.focus(), + acceptInput: () => terminalChat.acceptInput(), + updateInput: text => terminalChat.updateInput(text, false), + getInput: () => terminalChat.getInput(), + setInputPlaceholder: text => terminalChat.setPlaceholder(text), + clearInputPlaceholder: () => terminalChat.resetPlaceholder() + }; + } } interface IVoiceChatSession { @@ -255,6 +296,7 @@ class VoiceChatSessions { private quickVoiceChatInProgressKey = CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS.bindTo(this.contextKeyService); private inlineVoiceChatInProgressKey = CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.bindTo(this.contextKeyService); + private terminalVoiceChatInProgressKey = CONTEXT_TERMINAL_VOICE_CHAT_IN_PROGRESS.bindTo(this.contextKeyService); private voiceChatInViewInProgressKey = CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS.bindTo(this.contextKeyService); private voiceChatInEditorInProgressKey = CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS.bindTo(this.contextKeyService); @@ -345,6 +387,9 @@ class VoiceChatSessions { case 'inline': this.inlineVoiceChatInProgressKey.set(true); break; + case 'terminal': + this.terminalVoiceChatInProgressKey.set(true); + break; case 'quick': this.quickVoiceChatInProgressKey.set(true); break; @@ -387,6 +432,7 @@ class VoiceChatSessions { this.quickVoiceChatInProgressKey.set(false); this.inlineVoiceChatInProgressKey.set(false); + this.terminalVoiceChatInProgressKey.set(false); this.voiceChatInViewInProgressKey.set(false); this.voiceChatInEditorInProgressKey.set(false); } @@ -510,7 +556,8 @@ export class StartVoiceChatAction extends Action2 { CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS.negate(), CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS.negate(), CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS.negate(), - CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.negate() + CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.negate(), + CONTEXT_TERMINAL_VOICE_CHAT_IN_PROGRESS.negate() ), primary: KeyMod.CtrlCmd | KeyCode.KeyI }, @@ -529,7 +576,7 @@ export class StartVoiceChatAction extends Action2 { }, { id: MenuId.for('terminalChatInput'), - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.negate()), + when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_TERMINAL_VOICE_CHAT_IN_PROGRESS.negate()), group: 'main', order: -1 }] @@ -629,7 +676,7 @@ class BaseStopListeningAction extends Action2 { constructor( desc: { id: string; icon?: ThemeIcon; f1?: boolean }, - private readonly target: 'inline' | 'quick' | 'view' | 'editor' | undefined, + private readonly target: 'inline' | 'terminal' | 'quick' | 'view' | 'editor' | undefined, context: RawContextKey, menu: MenuId | undefined, group: 'navigation' | 'main' = 'navigation' @@ -703,6 +750,15 @@ export class StopListeningInInlineChatAction extends BaseStopListeningAction { } } +export class StopListeningInTerminalChatAction extends BaseStopListeningAction { + + static readonly ID = 'workbench.action.chat.stopListeningInTerminalChat'; + + constructor() { + super({ id: StopListeningInTerminalChatAction.ID, icon: spinningLoading }, 'terminal', CONTEXT_TERMINAL_VOICE_CHAT_IN_PROGRESS, MenuId.for('terminalChatInput'), 'main'); + } +} + export class StopListeningAndSubmitAction extends Action2 { static readonly ID = 'workbench.action.chat.stopListeningAndSubmit'; @@ -745,7 +801,8 @@ registerThemingParticipant((theme, collector) => { // Show a "microphone" icon when recording is in progress that glows via outline. collector.addRule(` .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), - .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { + .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), + .monaco-workbench:not(.reduce-motion) .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { color: ${activeRecordingColor}; outline: 1px solid ${activeRecordingColor}; outline-offset: -1px; @@ -754,7 +811,8 @@ registerThemingParticipant((theme, collector) => { } .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, - .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { + .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, + .monaco-workbench:not(.reduce-motion) .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { position: absolute; outline: 1px solid ${activeRecordingColor}; outline-offset: 2px; @@ -764,7 +822,8 @@ registerThemingParticipant((theme, collector) => { } .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after, - .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after { + .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after, + .monaco-workbench:not(.reduce-motion) .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after { content: ''; position: absolute; outline: 1px solid ${activeRecordingDimmedColor}; diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts index 2f69e1b13f8..02383b6b3f3 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction, KeywordActivationContribution, InstallVoiceChatAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; +import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction, KeywordActivationContribution, InstallVoiceChatAction, StopListeningInTerminalChatAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; @@ -21,5 +21,6 @@ registerAction2(StopListeningInChatViewAction); registerAction2(StopListeningInChatEditorAction); registerAction2(StopListeningInQuickChatAction); registerAction2(StopListeningInInlineChatAction); +registerAction2(StopListeningInTerminalChatAction); registerWorkbenchContribution2(KeywordActivationContribution.ID, KeywordActivationContribution, WorkbenchPhase.AfterRestored); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 00b117d6661..86287257f8f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -24,6 +24,18 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; import { CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { Emitter, Event } from 'vs/base/common/event'; + +const enum Message { + NONE = 0, + ACCEPT_SESSION = 1 << 0, + CANCEL_SESSION = 1 << 1, + PAUSE_SESSION = 1 << 2, + CANCEL_REQUEST = 1 << 3, + CANCEL_INPUT = 1 << 4, + ACCEPT_INPUT = 1 << 5, + RERUN_INPUT = 1 << 6, +} export class TerminalChatController extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; @@ -47,6 +59,11 @@ export class TerminalChatController extends Disposable implements ITerminalContr private _cancellationTokenSource!: CancellationTokenSource; + private _messages = this._store.add(new Emitter()); + + readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store); + readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store); + constructor( private readonly _instance: ITerminalInstance, processManager: ITerminalProcessManager, @@ -118,6 +135,18 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._cancellationTokenSource.cancel(); } + setPlaceholder(text: string): void { + // TODO: Impl + // this._forcedPlaceholder = text; + // this._updatePlaceholder(); + } + + resetPlaceholder(): void { + // TODO: Impl + // this._forcedPlaceholder = undefined; + // this._updatePlaceholder(); + } + async acceptInput(): Promise { let message = ''; this._chatAccessibilityService.acceptRequest(); @@ -168,6 +197,29 @@ export class TerminalChatController extends Disposable implements ITerminalContr } this._ctxHasActiveRequest.set(false); this._chatWidget?.rawValue?.updateProgress(); + this._messages.fire(Message.ACCEPT_INPUT); + } + + updateInput(text: string, selectAll = true): void { + const widget = this._chatWidget?.rawValue?.inlineChatWidget; + if (widget) { + widget.value = text; + if (selectAll) { + widget.selectAll(); + } + } + } + + getInput(): string { + return this._chatWidget?.rawValue?.input() ?? ''; + } + + focus(): void { + this._chatWidget?.rawValue?.focus(); + } + + hasFocus(): boolean { + return !!this._chatWidget?.rawValue?.hasFocus(); } acceptCommand(): void { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 41332e575fb..6374e1cc991 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -34,6 +34,8 @@ export class TerminalChatWidget extends Disposable { private readonly _focusTracker: IFocusTracker; private _responseWidget: CodeEditorWidget | undefined; + public get inlineChatWidget(): InlineChatWidget { return this._inlineChatWidget; } + constructor( private readonly _container: HTMLElement, private readonly _instance: ITerminalInstance, @@ -140,6 +142,12 @@ export class TerminalChatWidget extends Disposable { // TODO: Impl this._inlineChatWidget.value = ''; } + focus(): void { + this._inlineChatWidget.focus(); + } + hasFocus(): boolean { + return this._inlineChatWidget.hasFocus(); + } input(): string { return this._inlineChatWidget.value; } From 3960db9ce3b00189765b88601fefa1913ffdd1e9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 12:19:10 -0800 Subject: [PATCH 1229/1897] Add voice placeholder --- .../chat/browser/terminalChatController.ts | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 86287257f8f..f3d7ef85326 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -135,16 +135,29 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._cancellationTokenSource.cancel(); } + private _forcedPlaceholder: string | undefined = undefined; + + private _updatePlaceholder(): void { + const inlineChatWidget = this._chatWidget?.rawValue?.inlineChatWidget; + if (inlineChatWidget) { + inlineChatWidget.placeholder = this._getPlaceholderText(); + } + } + + private _getPlaceholderText(): string { + return this._forcedPlaceholder ?? ''; + // TODO: Pass through session placeholder + // return this._forcedPlaceholder ?? this._session?.session.placeholder ?? ''; + } + setPlaceholder(text: string): void { - // TODO: Impl - // this._forcedPlaceholder = text; - // this._updatePlaceholder(); + this._forcedPlaceholder = text; + this._updatePlaceholder(); } resetPlaceholder(): void { - // TODO: Impl - // this._forcedPlaceholder = undefined; - // this._updatePlaceholder(); + this._forcedPlaceholder = undefined; + this._updatePlaceholder(); } async acceptInput(): Promise { From f174624716b48b490b3a27f33f844b79448f85ea Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 13 Feb 2024 17:20:00 -0300 Subject: [PATCH 1230/1897] Add tooltip to disabled command button (#205140) --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 194e012133d..8ab85eb1297 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -838,9 +838,13 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.commandService.executeCommand(commandButton.command.id, ...(commandButton.command.arguments ?? [])))); From 30faa5af8a5c46faf6aea73b0121dce4abb6fbec Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 13 Feb 2024 17:41:32 -0300 Subject: [PATCH 1231/1897] API notes (#205144) --- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 39b2360f360..8aa7e9c184f 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -54,9 +54,8 @@ declare module 'vscode' { readonly command: string | undefined; /** - * + * The variables that were referenced in this message. */ - // TODO@API is this needed? readonly variables: ChatAgentResolvedVariable[]; private constructor(prompt: string, command: string | undefined, variables: ChatAgentResolvedVariable[], agent: { extensionId: string; agentId: string }); @@ -207,15 +206,18 @@ declare module 'vscode' { export interface ChatAgentFollowup { /** * The message to send to the chat. - * TODO@API is it ok for variables to resolved from the text of this prompt, using the `#` syntax? */ prompt: string; /** - * By default, the followup goes to the same agent/command. But these properties can be set to override that. + * By default, the followup goes to the same agent/command. But this property can be set to invoke a different agent. + * TODO@API do extensions need to specify the extensionID of the agent here as well? */ agentId?: string; + /** + * By default, the followup goes to the same agent/command. But this property can be set to invoke a different command. + */ command?: string; /** From f205511a4154adb94ccdd95c92301ae8243007f7 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 13 Feb 2024 21:26:40 +0100 Subject: [PATCH 1232/1897] Improves observable test docs --- src/vs/base/test/common/observable.test.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/base/test/common/observable.test.ts b/src/vs/base/test/common/observable.test.ts index 9227c465579..63b4c9c48df 100644 --- a/src/vs/base/test/common/observable.test.ts +++ b/src/vs/base/test/common/observable.test.ts @@ -18,11 +18,15 @@ suite('observables', () => { suite('tutorial', () => { test('observable + autorun', () => { const log = new Log(); - // This creates a new observable value. The name is only used for debugging purposes. + // This creates a variable that stores a value and whose value changes can be observed. + // The name is only used for debugging purposes. // The second arg is the initial value. const myObservable = observableValue('myObservable', 0); - // This creates an autorun. The @description is only used for debugging purposes. + // This creates an autorun: It runs immediately and then again whenever any of the + // dependencies change. Dependencies are tracked by reading observables with the `reader` parameter. + // + // The @description is only used for debugging purposes. // The autorun has to be disposed! This is very important. ds.add(autorun(reader => { /** @description myAutorun */ @@ -31,7 +35,7 @@ suite('observables', () => { // Use the `reader` to read observable values and track the dependency to them. // If you use `observable.get()` instead of `observable.read(reader)`, you will just - // get the value and not track the dependency. + // get the value and not subscribe to it. log.log(`myAutorun.run(myObservable: ${myObservable.read(reader)})`); // Now that all dependencies are tracked, the autorun is re-run whenever any of the From bf1bb656259791cf84051759a1d264409b5239c0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 14:50:05 -0600 Subject: [PATCH 1233/1897] clear on hide --- .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 6374e1cc991..0e8b82043ea 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -134,6 +134,8 @@ export class TerminalChatWidget extends Disposable { hide(): void { this._responseElement?.classList.add('hide'); this._widgetContainer.classList.add('hide'); + this._inlineChatWidget.value = ''; + this._responseWidget?.setValue(''); this._ctxChatWidgetFocused.set(false); this._ctxChatWidgetVisible.set(false); this._instance.focus(); From 5ef40d1286de90fcc8641155058614632a524409 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 13 Feb 2024 21:32:05 +0100 Subject: [PATCH 1234/1897] Improves observable debug names --- .../base/common/observableInternal/autorun.ts | 81 +++++----- src/vs/base/common/observableInternal/base.ts | 102 +----------- .../common/observableInternal/debugName.ts | 145 ++++++++++++++++++ .../base/common/observableInternal/derived.ts | 62 +++++--- .../base/common/observableInternal/utils.ts | 9 +- 5 files changed, 239 insertions(+), 160 deletions(-) create mode 100644 src/vs/base/common/observableInternal/debugName.ts diff --git a/src/vs/base/common/observableInternal/autorun.ts b/src/vs/base/common/observableInternal/autorun.ts index 6e7fdcf6d52..6c14cb20c5b 100644 --- a/src/vs/base/common/observableInternal/autorun.ts +++ b/src/vs/base/common/observableInternal/autorun.ts @@ -5,7 +5,8 @@ import { assertFn } from 'vs/base/common/assert'; import { DisposableStore, IDisposable, markAsDisposed, toDisposable, trackDisposable } from 'vs/base/common/lifecycle'; -import { IReader, IObservable, IObserver, IChangeContext, getFunctionName } from 'vs/base/common/observableInternal/base'; +import { IReader, IObservable, IObserver, IChangeContext } from 'vs/base/common/observableInternal/base'; +import { DebugNameData, IDebugNameData } from 'vs/base/common/observableInternal/debugName'; import { getLogger } from 'vs/base/common/observableInternal/logging'; /** @@ -13,15 +14,25 @@ import { getLogger } from 'vs/base/common/observableInternal/logging'; * {@link fn} should start with a JS Doc using `@description` to name the autorun. */ export function autorun(fn: (reader: IReader) => void): IDisposable { - return new AutorunObserver(undefined, fn, undefined, undefined); + return new AutorunObserver( + new DebugNameData(undefined, undefined, fn), + fn, + undefined, + undefined + ); } /** * Runs immediately and whenever a transaction ends and an observed observable changed. * {@link fn} should start with a JS Doc using `@description` to name the autorun. */ -export function autorunOpts(options: { debugName?: string | (() => string | undefined) }, fn: (reader: IReader) => void): IDisposable { - return new AutorunObserver(options.debugName, fn, undefined, undefined); +export function autorunOpts(options: IDebugNameData & {}, fn: (reader: IReader) => void): IDisposable { + return new AutorunObserver( + new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? fn), + fn, + undefined, + undefined + ); } /** @@ -36,22 +47,25 @@ export function autorunOpts(options: { debugName?: string | (() => string | unde * @see autorun */ export function autorunHandleChanges( - options: { - debugName?: string | (() => string | undefined); + options: IDebugNameData & { createEmptyChangeSummary?: () => TChangeSummary; handleChange: (context: IChangeContext, changeSummary: TChangeSummary) => boolean; }, fn: (reader: IReader, changeSummary: TChangeSummary) => void ): IDisposable { - return new AutorunObserver(options.debugName, fn, options.createEmptyChangeSummary, options.handleChange); + return new AutorunObserver( + new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? fn), + fn, + options.createEmptyChangeSummary, + options.handleChange + ); } /** * @see autorunHandleChanges (but with a disposable store that is cleared before the next run or on dispose) */ export function autorunWithStoreHandleChanges( - options: { - debugName?: string | (() => string | undefined); + options: IDebugNameData & { createEmptyChangeSummary?: () => TChangeSummary; handleChange: (context: IChangeContext, changeSummary: TChangeSummary) => boolean; }, @@ -60,7 +74,9 @@ export function autorunWithStoreHandleChanges( const store = new DisposableStore(); const disposable = autorunHandleChanges( { - debugName: options.debugName ?? (() => getFunctionName(fn)), + owner: options.owner, + debugName: options.debugName, + debugReferenceFn: options.debugReferenceFn, createEmptyChangeSummary: options.createEmptyChangeSummary, handleChange: options.handleChange, }, @@ -82,7 +98,9 @@ export function autorunWithStore(fn: (reader: IReader, store: DisposableStore) = const store = new DisposableStore(); const disposable = autorunOpts( { - debugName: () => getFunctionName(fn) || '(anonymous)', + owner: undefined, + debugName: undefined, + debugReferenceFn: fn, }, reader => { store.clear(); @@ -95,6 +113,20 @@ export function autorunWithStore(fn: (reader: IReader, store: DisposableStore) = }); } +export function autorunDelta( + observable: IObservable, + handler: (args: { lastValue: T | undefined; newValue: T }) => void +): IDisposable { + let _lastValue: T | undefined; + return autorunOpts({ debugReferenceFn: handler }, (reader) => { + const newValue = observable.read(reader); + const lastValue = _lastValue; + _lastValue = newValue; + handler({ lastValue, newValue }); + }); +} + + const enum AutorunState { /** * A dependency could have changed. @@ -118,21 +150,11 @@ export class AutorunObserver implements IObserver, IReader private changeSummary: TChangeSummary | undefined; public get debugName(): string { - if (typeof this._debugName === 'string') { - return this._debugName; - } - if (typeof this._debugName === 'function') { - const name = this._debugName(); - if (name !== undefined) { return name; } - } - const name = getFunctionName(this._runFn); - if (name !== undefined) { return name; } - - return '(anonymous)'; + return this._debugNameData.getDebugName(this) ?? '(anonymous)'; } constructor( - private readonly _debugName: string | (() => string | undefined) | undefined, + private readonly _debugNameData: DebugNameData, public readonly _runFn: (reader: IReader, changeSummary: TChangeSummary) => void, private readonly createChangeSummary: (() => TChangeSummary) | undefined, private readonly _handleChange: ((context: IChangeContext, summary: TChangeSummary) => boolean) | undefined, @@ -257,16 +279,3 @@ export class AutorunObserver implements IObserver, IReader export namespace autorun { export const Observer = AutorunObserver; } - -export function autorunDelta( - observable: IObservable, - handler: (args: { lastValue: T | undefined; newValue: T }) => void -): IDisposable { - let _lastValue: T | undefined; - return autorunOpts({ debugName: () => getFunctionName(handler) }, (reader) => { - const newValue = observable.read(reader); - const lastValue = _lastValue; - _lastValue = newValue; - handler({ lastValue, newValue }); - }); -} diff --git a/src/vs/base/common/observableInternal/base.ts b/src/vs/base/common/observableInternal/base.ts index 45e6f0ef201..74b03df1438 100644 --- a/src/vs/base/common/observableInternal/base.ts +++ b/src/vs/base/common/observableInternal/base.ts @@ -5,6 +5,7 @@ import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { keepObserved, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; +import { DebugNameData, Owner, getFunctionName } from 'vs/base/common/observableInternal/debugName'; import type { derivedOpts } from 'vs/base/common/observableInternal/derived'; import { getLogger } from 'vs/base/common/observableInternal/logging'; @@ -354,105 +355,6 @@ export class TransactionImpl implements ITransaction { } } -/** - * The owner object of an observable. - * Is only used for debugging purposes, such as computing a name for the observable by iterating over the fields of the owner. - */ -export type Owner = object | undefined; -export type DebugNameFn = string | (() => string | undefined); - -const countPerName = new Map(); -const cachedDebugName = new WeakMap(); - -export function getDebugName(self: object, debugNameFn: DebugNameFn | undefined, fn: Function | undefined, owner: Owner): string | undefined { - const cached = cachedDebugName.get(self); - if (cached) { - return cached; - } - - const dbgName = computeDebugName(self, debugNameFn, fn, owner); - if (dbgName) { - let count = countPerName.get(dbgName) ?? 0; - count++; - countPerName.set(dbgName, count); - const result = count === 1 ? dbgName : `${dbgName}#${count}`; - cachedDebugName.set(self, result); - return result; - } - return undefined; -} - -function computeDebugName(self: object, debugNameFn: DebugNameFn | undefined, fn: Function | undefined, owner: Owner): string | undefined { - const cached = cachedDebugName.get(self); - if (cached) { - return cached; - } - - const ownerStr = owner ? formatOwner(owner) + `.` : ''; - - let result: string | undefined; - if (debugNameFn !== undefined) { - if (typeof debugNameFn === 'function') { - result = debugNameFn(); - if (result !== undefined) { - return ownerStr + result; - } - } else { - return ownerStr + debugNameFn; - } - } - - if (fn !== undefined) { - result = getFunctionName(fn); - if (result !== undefined) { - return ownerStr + result; - } - } - - if (owner !== undefined) { - for (const key in owner) { - if ((owner as any)[key] === self) { - return ownerStr + key; - } - } - } - return undefined; -} - -const countPerClassName = new Map(); -const ownerId = new WeakMap(); - -function formatOwner(owner: object): string { - const id = ownerId.get(owner); - if (id) { - return id; - } - const className = getClassName(owner); - let count = countPerClassName.get(className) ?? 0; - count++; - countPerClassName.set(className, count); - const result = count === 1 ? className : `${className}#${count}`; - ownerId.set(owner, result); - return result; -} - -function getClassName(obj: object): string { - const ctor = obj.constructor; - if (ctor) { - return ctor.name; - } - return 'Object'; -} - -export function getFunctionName(fn: Function): string | undefined { - const fnSrc = fn.toString(); - // Pattern: /** @description ... */ - const regexp = /\/\*\*\s*@description\s*([^*]*)\*\//; - const match = regexp.exec(fnSrc); - const result = match ? match[1] : undefined; - return result?.trim(); -} - /** * A settable observable. */ @@ -481,7 +383,7 @@ export class ObservableValue protected _value: T; get debugName() { - return getDebugName(this, this._debugName, undefined, this._owner) ?? 'ObservableValue'; + return new DebugNameData(this._owner, this._debugName, undefined).getDebugName(this) ?? 'ObservableValue'; } constructor( diff --git a/src/vs/base/common/observableInternal/debugName.ts b/src/vs/base/common/observableInternal/debugName.ts new file mode 100644 index 00000000000..481d24f0377 --- /dev/null +++ b/src/vs/base/common/observableInternal/debugName.ts @@ -0,0 +1,145 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface IDebugNameData { + /** + * The owner object of an observable. + * Used for debugging only, such as computing a name for the observable by iterating over the fields of the owner. + */ + readonly owner?: Owner | undefined; + + /** + * A string or function that returns a string that represents the name of the observable. + * Used for debugging only. + */ + readonly debugName?: DebugNameSource | undefined; + + /** + * A function that points to the defining function of the object. + * Used for debugging only. + */ + readonly debugReferenceFn?: Function | undefined; +} + +export class DebugNameData { + constructor( + public readonly owner: Owner | undefined, + public readonly debugNameSource: DebugNameSource | undefined, + public readonly referenceFn: Function | undefined, + ) { } + + public getDebugName(target: object): string | undefined { + return getDebugName(target, this); + } +} + +/** + * The owner object of an observable. + * Is only used for debugging purposes, such as computing a name for the observable by iterating over the fields of the owner. + */ +export type Owner = object | undefined; +export type DebugNameSource = string | (() => string | undefined); + +const countPerName = new Map(); +const cachedDebugName = new WeakMap(); + +export function getDebugName(target: object, data: DebugNameData): string | undefined { + const cached = cachedDebugName.get(target); + if (cached) { + return cached; + } + + const dbgName = computeDebugName(target, data); + if (dbgName) { + let count = countPerName.get(dbgName) ?? 0; + count++; + countPerName.set(dbgName, count); + const result = count === 1 ? dbgName : `${dbgName}#${count}`; + cachedDebugName.set(target, result); + return result; + } + return undefined; +} + +function computeDebugName(self: object, data: DebugNameData): string | undefined { + const cached = cachedDebugName.get(self); + if (cached) { + return cached; + } + + const ownerStr = data.owner ? formatOwner(data.owner) + `.` : ''; + + let result: string | undefined; + const debugNameSource = data.debugNameSource; + if (debugNameSource !== undefined) { + if (typeof debugNameSource === 'function') { + result = debugNameSource(); + if (result !== undefined) { + return ownerStr + result; + } + } else { + return ownerStr + debugNameSource; + } + } + + const referenceFn = data.referenceFn; + if (referenceFn !== undefined) { + result = getFunctionName(referenceFn); + if (result !== undefined) { + return ownerStr + result; + } + } + + if (data.owner !== undefined) { + const key = findKey(data.owner, self); + if (key !== undefined) { + return ownerStr + key; + } + } + return undefined; +} + +function findKey(obj: object, value: object): string | undefined { + for (const key in obj) { + if ((obj as any)[key] === value) { + return key; + } + } + return undefined; +} + +const countPerClassName = new Map(); +const ownerId = new WeakMap(); + +function formatOwner(owner: object): string { + const id = ownerId.get(owner); + if (id) { + return id; + } + const className = getClassName(owner); + let count = countPerClassName.get(className) ?? 0; + count++; + countPerClassName.set(className, count); + const result = count === 1 ? className : `${className}#${count}`; + ownerId.set(owner, result); + return result; +} + +function getClassName(obj: object): string { + const ctor = obj.constructor; + if (ctor) { + return ctor.name; + } + return 'Object'; +} + +export function getFunctionName(fn: Function): string | undefined { + const fnSrc = fn.toString(); + // Pattern: /** @description ... */ + const regexp = /\/\*\*\s*@description\s*([^*]*)\*\//; + const match = regexp.exec(fnSrc); + const result = match ? match[1] : undefined; + return result?.trim(); +} diff --git a/src/vs/base/common/observableInternal/derived.ts b/src/vs/base/common/observableInternal/derived.ts index 573b5ad5258..cab2c7c4bc1 100644 --- a/src/vs/base/common/observableInternal/derived.ts +++ b/src/vs/base/common/observableInternal/derived.ts @@ -5,7 +5,8 @@ import { assertFn } from 'vs/base/common/assert'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { BaseObservable, DebugNameFn, IChangeContext, IObservable, IObserver, IReader, Owner, _setDerivedOpts, getDebugName, getFunctionName } from 'vs/base/common/observableInternal/base'; +import { BaseObservable, IChangeContext, IObservable, IObserver, IReader, _setDerivedOpts } from 'vs/base/common/observableInternal/base'; +import { DebugNameData, IDebugNameData, Owner } from 'vs/base/common/observableInternal/debugName'; import { getLogger } from 'vs/base/common/observableInternal/logging'; export type EqualityComparer = (a: T, b: T) => boolean; @@ -21,23 +22,44 @@ export function derived(computeFn: (reader: IReader) => T): IObservable; export function derived(owner: object, computeFn: (reader: IReader) => T): IObservable; export function derived(computeFnOrOwner: ((reader: IReader) => T) | object, computeFn?: ((reader: IReader) => T) | undefined): IObservable { if (computeFn !== undefined) { - return new Derived(computeFnOrOwner, undefined, computeFn, undefined, undefined, undefined, defaultEqualityComparer); + return new Derived( + new DebugNameData(computeFnOrOwner, undefined, computeFn), + computeFn, + undefined, + undefined, + undefined, + defaultEqualityComparer + ); } - return new Derived(undefined, undefined, computeFnOrOwner as any, undefined, undefined, undefined, defaultEqualityComparer); + return new Derived( + new DebugNameData(undefined, undefined, computeFnOrOwner as any), + computeFnOrOwner as any, + undefined, + undefined, + undefined, + defaultEqualityComparer + ); } export function derivedOpts( - options: { - owner?: object; - debugName?: DebugNameFn; + options: IDebugNameData & { equalityComparer?: EqualityComparer; onLastObserverRemoved?: (() => void); }, computeFn: (reader: IReader) => T ): IObservable { - return new Derived(options.owner, options.debugName, computeFn, undefined, undefined, options.onLastObserverRemoved, options.equalityComparer ?? defaultEqualityComparer); + return new Derived( + new DebugNameData(options.owner, options.debugName, options.debugReferenceFn), + computeFn, + undefined, + undefined, + options.onLastObserverRemoved, + options.equalityComparer ?? defaultEqualityComparer + ); } +_setDerivedOpts(derivedOpts); + /** * Represents an observable that is derived from other observables. * The value is only recomputed when absolutely needed. @@ -52,16 +74,21 @@ export function derivedOpts( * @see derived */ export function derivedHandleChanges( - options: { - owner?: object; - debugName?: string | (() => string); + options: IDebugNameData & { createEmptyChangeSummary: () => TChangeSummary; handleChange: (context: IChangeContext, changeSummary: TChangeSummary) => boolean; equalityComparer?: EqualityComparer; }, computeFn: (reader: IReader, changeSummary: TChangeSummary) => T ): IObservable { - return new Derived(options.owner, options.debugName, computeFn, options.createEmptyChangeSummary, options.handleChange, undefined, options.equalityComparer ?? defaultEqualityComparer); + return new Derived( + new DebugNameData(options.owner, options.debugName, undefined), + computeFn, + options.createEmptyChangeSummary, + options.handleChange, + undefined, + options.equalityComparer ?? defaultEqualityComparer + ); } export function derivedWithStore(computeFn: (reader: IReader, store: DisposableStore) => T): IObservable; @@ -79,8 +106,7 @@ export function derivedWithStore(computeFnOrOwner: ((reader: IReader, store: const store = new DisposableStore(); return new Derived( - owner, - (() => getFunctionName(computeFn) ?? '(anonymous)'), + new DebugNameData(owner, undefined, computeFn), r => { store.clear(); return computeFn(r, store); @@ -106,8 +132,7 @@ export function derivedDisposable(computeFnOr const store = new DisposableStore(); return new Derived( - owner, - (() => getFunctionName(computeFn) ?? '(anonymous)'), + new DebugNameData(owner, undefined, computeFn), r => { store.clear(); const result = computeFn(r); @@ -122,8 +147,6 @@ export function derivedDisposable(computeFnOr ); } -_setDerivedOpts(derivedOpts); - const enum DerivedState { /** Initial state, no previous value, recomputation needed */ initial = 0, @@ -155,12 +178,11 @@ export class Derived extends BaseObservable im private changeSummary: TChangeSummary | undefined = undefined; public override get debugName(): string { - return getDebugName(this, this._debugName, this._computeFn, this._owner) ?? '(anonymous)'; + return this._debugNameData.getDebugName(this) ?? '(anonymous)'; } constructor( - private readonly _owner: Owner, - private readonly _debugName: DebugNameFn | undefined, + private readonly _debugNameData: DebugNameData, public readonly _computeFn: (reader: IReader, changeSummary: TChangeSummary) => T, private readonly createChangeSummary: (() => TChangeSummary) | undefined, private readonly _handleChange: ((context: IChangeContext, summary: TChangeSummary) => boolean) | undefined, diff --git a/src/vs/base/common/observableInternal/utils.ts b/src/vs/base/common/observableInternal/utils.ts index 830785aab40..a16feb1f65c 100644 --- a/src/vs/base/common/observableInternal/utils.ts +++ b/src/vs/base/common/observableInternal/utils.ts @@ -6,7 +6,8 @@ import { Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { autorun } from 'vs/base/common/observableInternal/autorun'; -import { BaseObservable, ConvenientObservable, IObservable, IObserver, IReader, ITransaction, Owner, _setKeepObserved, _setRecomputeInitiallyAndOnChange, getDebugName, getFunctionName, observableValue, subtransaction, transaction } from 'vs/base/common/observableInternal/base'; +import { BaseObservable, ConvenientObservable, IObservable, IObserver, IReader, ITransaction, _setKeepObserved, _setRecomputeInitiallyAndOnChange, observableValue, subtransaction, transaction } from 'vs/base/common/observableInternal/base'; +import { DebugNameData, Owner, getFunctionName } from 'vs/base/common/observableInternal/debugName'; import { derived, derivedOpts } from 'vs/base/common/observableInternal/derived'; import { getLogger } from 'vs/base/common/observableInternal/logging'; @@ -249,7 +250,7 @@ export interface IObservableSignal extends IObservable { class ObservableSignal extends BaseObservable implements IObservableSignal { public get debugName() { - return getDebugName(this, this._debugName, undefined, this._owner) ?? 'Observable Signal'; + return new DebugNameData(this._owner, this._debugName, undefined).getDebugName(this) ?? 'Observable Signal'; } constructor( @@ -352,7 +353,7 @@ export function recomputeInitiallyAndOnChange(observable: IObservable, han _setRecomputeInitiallyAndOnChange(recomputeInitiallyAndOnChange); -class KeepAliveObserver implements IObserver { +export class KeepAliveObserver implements IObserver { private _counter = 0; constructor( @@ -415,7 +416,7 @@ export function derivedObservableWithWritableCache(owner: object, computeFn: export function mapObservableArrayCached(owner: Owner, items: IObservable, map: (input: TIn, store: DisposableStore) => TOut, keySelector?: (input: TIn) => TKey): IObservable { let m = new ArrayMap(map, keySelector); const self = derivedOpts({ - debugName: () => getDebugName(m, undefined, map, owner), + debugReferenceFn: map, owner, onLastObserverRemoved: () => { m.dispose(); From 83f8ee26b6c832c68f7ba37f7b3f083bc2b76292 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 14:53:59 -0600 Subject: [PATCH 1235/1897] rm unused --- .../chat/browser/terminalChatController.ts | 14 -------------- .../chat/browser/terminalChatWidget.ts | 11 ++--------- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index f3d7ef85326..83adeff265d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -6,7 +6,6 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { IDimension } from 'vs/base/browser/dom'; import { Lazy } from 'vs/base/common/lazy'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; @@ -49,7 +48,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr */ static activeChatWidget?: TerminalChatController; private _chatWidget: Lazy | undefined; - private _lastLayoutDimensions: IDimension | undefined; private _accessibilityRequestId: number = 0; get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } @@ -95,14 +93,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._cancellationTokenSource = new CancellationTokenSource(); } - layout(_xterm: IXtermTerminal & { raw: RawXtermTerminal }, dimension: IDimension): void { - if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { - return; - } - this._lastLayoutDimensions = dimension; - this._chatWidget?.rawValue?.layout(dimension.width); - } - xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { return; @@ -123,10 +113,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr throw new Error('FindWidget expected terminal DOM to be initialized'); } - if (this._lastLayoutDimensions) { - chatWidget.layout(this._lastLayoutDimensions.width); - } - return chatWidget; }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 0e8b82043ea..61aa2c4a409 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -92,6 +92,8 @@ export class TerminalChatWidget extends Disposable { this._responseElement.classList.remove('hide'); if (!this._responseWidget) { this._responseWidget = this._register(this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, {}, { isSimpleWidget: true })); + this._register(this._responseWidget.onDidFocusEditorText(() => this._ctxResponseEditorFocused.set(true))); + this._register(this._responseWidget.onDidBlurEditorText(() => this._ctxResponseEditorFocused.set(false))); this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: codeBlock })).then((model) => { if (!model || !this._responseWidget) { return; @@ -101,12 +103,6 @@ export class TerminalChatWidget extends Disposable { const height = this._responseWidget.getContentHeight(); this._responseWidget.layout(new Dimension(400, height)); }); - this._register(this._responseWidget.onDidFocusEditorText(() => { - this._ctxResponseEditorFocused.set(true); - })); - this._register(this._responseWidget.onDidBlurEditorText(() => { - this._ctxResponseEditorFocused.set(false); - })); } else { this._responseWidget.setValue(codeBlock); } @@ -170,9 +166,6 @@ export class TerminalChatWidget extends Disposable { updateProgress(progress?: IChatProgress): void { this._inlineChatWidget.updateProgress(progress?.kind === 'content' || progress?.kind === 'markdownContent'); } - layout(width: number): void { - // this._widget?.layout(100, width < 300 ? 300 : width); - } public get focusTracker(): IFocusTracker { return this._focusTracker; } From 71bd033d631e32a97e384f2d7123717354ab7d42 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Tue, 13 Feb 2024 15:02:37 -0600 Subject: [PATCH 1236/1897] Quick Search leaves preview editors open (#205141) Fixes #203236 --- src/vs/workbench/browser/quickaccess.ts | 9 ++++++++- .../browser/quickTextSearch/textSearchQuickAccess.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/quickaccess.ts b/src/vs/workbench/browser/quickaccess.ts index a73c3d7072c..b0a88a28142 100644 --- a/src/vs/workbench/browser/quickaccess.ts +++ b/src/vs/workbench/browser/quickaccess.ts @@ -75,12 +75,19 @@ export class EditorViewState { } } - async restore(): Promise { + async restore(shouldCloseCurrEditor = false): Promise { if (this._editorViewState) { const options: IEditorOptions = { viewState: this._editorViewState.state, preserveFocus: true /* import to not close the picker as a result */ }; + if (shouldCloseCurrEditor) { + const activeEditorPane = this.editorService.activeEditorPane; + const currEditor = activeEditorPane?.input; + if (currEditor && currEditor !== this._editorViewState.editor) { + await activeEditorPane.group.closeEditor(currEditor); + } + } await this._editorViewState.group.openEditor(this._editorViewState.editor, options); } diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index b5ad884d5d0..15a07cb0675 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -141,7 +141,7 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider Date: Tue, 13 Feb 2024 22:04:29 +0100 Subject: [PATCH 1237/1897] Git - Add button/setting to always replace local tags in case of a conflict during the pull operation (#205148) --- extensions/git/package.json | 6 ++++++ extensions/git/package.nls.json | 1 + extensions/git/src/repository.ts | 22 ++++++++++++++++------ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index a36b08c0baa..f35aa7989ad 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -2645,6 +2645,12 @@ "default": false, "description": "%config.followTagsWhenSync%" }, + "git.replaceTagsWhenPull": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%config.replaceTagsWhenPull%" + }, "git.promptToSaveFilesBeforeStash": { "type": "string", "enum": [ diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 388d5387261..28ae736038f 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -176,6 +176,7 @@ "config.decorations.enabled": "Controls whether Git contributes colors and badges to the Explorer and the Open Editors view.", "config.enableStatusBarSync": "Controls whether the Git Sync command appears in the status bar.", "config.followTagsWhenSync": "Push all annotated tags when running the sync command.", + "config.replaceTagsWhenPull": "Automatically replace the local tags with the remote tags in case of a conflict when running the pull command.", "config.promptToSaveFilesBeforeStash": "Controls whether Git should check for unsaved files before stashing changes.", "config.promptToSaveFilesBeforeStash.always": "Check for any unsaved files.", "config.promptToSaveFilesBeforeStash.staged": "Check only for unsaved staged files.", diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index c71069fd3c8..cadb3c717f7 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -2630,13 +2630,23 @@ export class Repository implements Disposable { throw new Error(`Unable to extract tag names from error message: ${raw}`); } - // Notification - const replaceLocalTags = l10n.t('Replace Local Tag(s)'); - const message = l10n.t('Unable to pull from remote repository due to conflicting tag(s): {0}. Would you like to resolve the conflict by replacing the local tag(s)?', tags.join(', ')); - const choice = await window.showErrorMessage(message, { modal: true }, replaceLocalTags); + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const replaceTagsWhenPull = config.get('replaceTagsWhenPull', false) === true; - if (choice !== replaceLocalTags) { - return false; + if (!replaceTagsWhenPull) { + // Notification + const replaceLocalTags = l10n.t('Replace Local Tag(s)'); + const replaceLocalTagsAlways = l10n.t('Always Replace Local Tag(s)'); + const message = l10n.t('Unable to pull from remote repository due to conflicting tag(s): {0}. Would you like to resolve the conflict by replacing the local tag(s)?', tags.join(', ')); + const choice = await window.showErrorMessage(message, { modal: true }, replaceLocalTags, replaceLocalTagsAlways); + + if (choice !== replaceLocalTags && choice !== replaceLocalTagsAlways) { + return false; + } + + if (choice === replaceLocalTagsAlways) { + await config.update('replaceTagsWhenPull', true, true); + } } // Force fetch tags From 1d262b3146aa7f3c254d28fc0144f9ae08bf9717 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 15:05:59 -0600 Subject: [PATCH 1238/1897] update variables after rob's change --- .../terminalContrib/chat/browser/terminalChatController.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 83adeff265d..9c9cb0a2ff8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -17,7 +17,6 @@ import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/ import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; import { generateUuid } from 'vs/base/common/uuid'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; import { marked } from 'vs/base/common/marked/marked'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -162,15 +161,13 @@ export class TerminalChatController extends Disposable implements ITerminalContr } this._chatWidget?.rawValue?.updateProgress(progress); }; - const resolvedVariables: Record = {}; const requestId = generateUuid(); const requestProps: IChatAgentRequest = { sessionId: generateUuid(), requestId, agentId, message: this._chatWidget?.rawValue?.input() || '', - variables: resolvedVariables, - variables2: { message: this._chatWidget?.rawValue?.input() || '', variables: [] } + variables: { variables: [] }, }; this._chatWidget?.rawValue?.setValue(); From cd261754e852064d15516f6321a65ac17c906a3f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 13:06:30 -0800 Subject: [PATCH 1239/1897] Simplify response editor presentation --- .../chat/browser/terminalChatWidget.ts | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 61aa2c4a409..0c174ed3555 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -91,7 +91,41 @@ export class TerminalChatWidget extends Disposable { this._chatAccessibilityService.acceptResponse(codeBlock, requestId); this._responseElement.classList.remove('hide'); if (!this._responseWidget) { - this._responseWidget = this._register(this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, {}, { isSimpleWidget: true })); + this._responseWidget = this._register(this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, { + padding: { top: 2, bottom: 2 }, + overviewRulerLanes: 0, + glyphMargin: false, + lineNumbers: 'off', + folding: false, + hideCursorInOverviewRuler: true, + selectOnLineNumbers: false, + selectionHighlight: false, + scrollbar: { + useShadows: false, + vertical: 'hidden', + horizontal: 'auto', + alwaysConsumeMouseWheel: false + }, + lineDecorationsWidth: 0, + overviewRulerBorder: false, + scrollBeyondLastLine: false, + renderLineHighlight: 'none', + fixedOverflowWidgets: true, + dragAndDrop: false, + revealHorizontalRightPadding: 5, + minimap: { enabled: false }, + guides: { indentation: false }, + rulers: [], + renderWhitespace: 'none', + dropIntoEditor: { enabled: true }, + quickSuggestions: false, + suggest: { + showIcons: false, + showSnippets: false, + showWords: true, + showStatusBar: false, + }, + }, { isSimpleWidget: true })); this._register(this._responseWidget.onDidFocusEditorText(() => this._ctxResponseEditorFocused.set(true))); this._register(this._responseWidget.onDidBlurEditorText(() => this._ctxResponseEditorFocused.set(false))); this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: codeBlock })).then((model) => { From 43088f274753b2c6d22f54899028cef88a41514c Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Tue, 13 Feb 2024 13:09:35 -0800 Subject: [PATCH 1240/1897] variable view initialization (#205149) * intitialization fixes * use more appropriate event --- .../contrib/notebookVariables/notebookVariables.ts | 10 +++------- .../contrib/notebookVariables/notebookVariablesView.ts | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts index 0da62de50b4..9e7435fce88 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts @@ -21,7 +21,6 @@ import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/not import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { NOTEBOOK_VARIABLE_VIEW_ENABLED } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableContextKeys'; - export class NotebookVariables extends Disposable implements IWorkbenchContribution { private listeners: IDisposable[] = []; private configListener: IDisposable; @@ -40,8 +39,8 @@ export class NotebookVariables extends Disposable implements IWorkbenchContribut this.viewEnabled = NOTEBOOK_VARIABLE_VIEW_ENABLED.bindTo(contextKeyService); - this.listeners.push(this.editorService.onDidEditorsChange(() => this.handleInitEvent())); - this.listeners.push(this.notebookExecutionStateService.onDidChangeExecution(() => this.handleInitEvent())); + this.listeners.push(this.editorService.onDidActiveEditorChange(() => this.handleInitEvent())); + this.listeners.push(this.notebookExecutionStateService.onDidChangeExecution((e) => this.handleInitEvent())); this.configListener = configurationService.onDidChangeConfiguration((e) => this.handleConfigChange(e)); } @@ -62,11 +61,8 @@ export class NotebookVariables extends Disposable implements IWorkbenchContribut if (this.configurationService.getValue(NotebookSetting.notebookVariablesView) && this.editorService.activeEditorPane?.getId() === 'workbench.editor.notebook') { - if (this.hasVariableProvider()) { + if (this.hasVariableProvider() && !this.initialized && this.initializeView()) { this.viewEnabled.set(true); - } - - if (!this.initialized && this.initializeView()) { this.initialized = true; this.listeners.forEach(listener => listener.dispose()); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index 82360512546..515a43f8cf5 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -140,7 +140,7 @@ export class NotebookVariablesView extends ViewPane { private setActiveNotebook() { const current = this.activeNotebook; const activeEditorPane = this.editorService.activeEditorPane; - if (activeEditorPane && activeEditorPane.getId() === 'workbench.editor.notebook') { + if (activeEditorPane?.getId() === 'workbench.editor.notebook') { const notebookDocument = getNotebookEditorFromEditorPane(activeEditorPane)?.getViewModel()?.notebookDocument; this.activeNotebook = notebookDocument; } From 84fb0821a9347a1b8c563f29259962e758ca6e87 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 15:56:53 -0600 Subject: [PATCH 1241/1897] extract code content, language --- .../terminalContrib/chat/browser/terminalChatController.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 9c9cb0a2ff8..f7edc4b5fd6 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -179,7 +179,12 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.updateProgress(); return; } - const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); + const firstCodeBlockContent = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw; + const regex = /```(?\w+)\n(?[\s\S]*?)```/g; + const match = regex.exec(firstCodeBlockContent); + const codeBlock = match?.groups?.content; + // TODO: map to editor known language, set editor language + // const language = match?.groups?.language; this._accessibilityRequestId++; if (cancellationToken.isCancellationRequested) { return; From fa111adf63ac35b6b8921db9ece8cb7880a7831c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 16:08:25 -0600 Subject: [PATCH 1242/1897] add language support --- .../chat/browser/terminalChatController.ts | 5 ++--- .../chat/browser/terminalChatWidget.ts | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index f7edc4b5fd6..221c010600b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -183,15 +183,14 @@ export class TerminalChatController extends Disposable implements ITerminalContr const regex = /```(?\w+)\n(?[\s\S]*?)```/g; const match = regex.exec(firstCodeBlockContent); const codeBlock = match?.groups?.content; - // TODO: map to editor known language, set editor language - // const language = match?.groups?.language; + const shellType = match?.groups?.language; this._accessibilityRequestId++; if (cancellationToken.isCancellationRequested) { return; } if (codeBlock) { // TODO: check the SR experience - this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._accessibilityRequestId); + this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._accessibilityRequestId, shellType); } else { this._chatWidget?.rawValue?.renderMessage(message, this._accessibilityRequestId, requestId); this._ctxLastResponseType.set(InlineChatResponseTypes.OnlyMessages); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 0c174ed3555..abb8807cdd9 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -87,7 +87,7 @@ export class TerminalChatWidget extends Disposable { this._focusTracker = this._register(trackFocus(this._widgetContainer)); } - renderTerminalCommand(codeBlock: string, requestId: number): void { + renderTerminalCommand(codeBlock: string, requestId: number, shellType?: string): void { this._chatAccessibilityService.acceptResponse(codeBlock, requestId); this._responseElement.classList.remove('hide'); if (!this._responseWidget) { @@ -140,6 +140,20 @@ export class TerminalChatWidget extends Disposable { } else { this._responseWidget.setValue(codeBlock); } + this._responseWidget.getModel()?.setLanguage(this._getLanguageFromShell(shellType)); + } + + private _getLanguageFromShell(shell?: string): string { + switch (shell) { + case 'sh': + case 'bash': + case 'zsh': + return 'shellscript'; + case 'pwsh': + return 'powershell'; + default: + return 'plaintext'; + } } renderMessage(message: string, accessibilityRequestId: number, requestId: string): void { From 07446f62116e478a317fa432b142810f5002181e Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Tue, 13 Feb 2024 22:51:08 +0100 Subject: [PATCH 1243/1897] chat: Use alternating request/response color --- build/lib/stylelint/vscode-known-variables.json | 1 + src/vs/workbench/contrib/chat/browser/media/chat.css | 7 +++++++ src/vs/workbench/contrib/chat/common/chatColors.ts | 8 +++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 0411ea42d75..5e0c031b144 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -44,6 +44,7 @@ "--vscode-chat-avatarBackground", "--vscode-chat-avatarForeground", "--vscode-chat-requestBorder", + "--vscode-chat-requestBackground", "--vscode-chat-slashCommandBackground", "--vscode-chat-slashCommandForeground", "--vscode-chat-list-background", diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 52180a60652..140e418f1e0 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -221,6 +221,13 @@ .interactive-request { border-bottom: 1px solid var(--vscode-chat-requestBorder); border-top: 1px solid var(--vscode-chat-requestBorder); + background-color: var(--vscode-chat-requestBackground); +} + +.hc-black .interactive-request, +.hc-light .interactive-request { + border-left: 3px solid var(--vscode-chat-requestBorder); + border-right: 3px solid var(--vscode-chat-requestBorder); } .interactive-item-container .value { diff --git a/src/vs/workbench/contrib/chat/common/chatColors.ts b/src/vs/workbench/contrib/chat/common/chatColors.ts index 97c8bc85f5a..a11134cac9d 100644 --- a/src/vs/workbench/contrib/chat/common/chatColors.ts +++ b/src/vs/workbench/contrib/chat/common/chatColors.ts @@ -5,7 +5,7 @@ import { Color, RGBA } from 'vs/base/common/color'; import { localize } from 'vs/nls'; -import { badgeBackground, badgeForeground, contrastBorder, foreground, registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { badgeBackground, badgeForeground, contrastBorder, editorBackground, editorWidgetBackground, foreground, registerColor } from 'vs/platform/theme/common/colorRegistry'; export const chatRequestBorder = registerColor( 'chat.requestBorder', @@ -13,6 +13,12 @@ export const chatRequestBorder = registerColor( localize('chat.requestBorder', 'The border color of a chat request.') ); +export const chatRequestBackground = registerColor( + 'chat.requestBackground', + { dark: editorBackground, light: editorBackground, hcDark: editorWidgetBackground, hcLight: null }, + localize('chat.requestBackground', 'The background color of a chat request.') +); + export const chatSlashCommandBackground = registerColor( 'chat.slashCommandBackground', { dark: '#34414B', light: '#D2ECFF', hcDark: Color.white, hcLight: badgeBackground }, From 16f1f4bfc1cb918a397c5c4cd4a1904d88ec983b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:29:44 -0800 Subject: [PATCH 1244/1897] Add shell language id fallback --- .../terminalContrib/chat/browser/terminalChatWidget.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index abb8807cdd9..c3efebdc5ca 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -9,6 +9,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/terminalChatWidget'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; @@ -42,6 +43,7 @@ export class TerminalChatWidget extends Disposable { @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, + @ILanguageService private readonly _languageService: ILanguageService, @IModelService private readonly _modelService: IModelService ) { super(); @@ -145,9 +147,13 @@ export class TerminalChatWidget extends Disposable { private _getLanguageFromShell(shell?: string): string { switch (shell) { - case 'sh': - case 'bash': + case 'fish': + return this._languageService.isRegisteredLanguageId('fish') ? 'fish' : 'shellscript'; case 'zsh': + return this._languageService.isRegisteredLanguageId('zsh') ? 'zsh' : 'shellscript'; + case 'bash': + return this._languageService.isRegisteredLanguageId('bash') ? 'bash' : 'shellscript'; + case 'sh': return 'shellscript'; case 'pwsh': return 'powershell'; From 8112339b211fcdb807e4e7f9b57f39142fbe289b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:49:37 -0800 Subject: [PATCH 1245/1897] Improve progress feedback --- .../chat/browser/terminalChatController.ts | 17 +++++++++++++---- .../chat/browser/terminalChatWidget.ts | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 221c010600b..36db8ad2ab9 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -23,6 +23,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; import { CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { Emitter, Event } from 'vs/base/common/event'; +import { localize } from 'vs/nls'; const enum Message { NONE = 0, @@ -172,12 +173,20 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.setValue(); try { - await this._chatAgentService.invokeAgent(agentId, requestProps, progressCallback, [], cancellationToken); + const task = this._chatAgentService.invokeAgent(agentId, requestProps, progressCallback, [], cancellationToken); + this._chatWidget?.rawValue?.inlineChatWidget.updateChatMessage(undefined); + this._chatWidget?.rawValue?.inlineChatWidget.updateFollowUps(undefined); + this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(true); + this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(localize('thinking', "Thinking\u2026")); + // TODO: this._zone.value.widget.updateInfo(!this._session.lastExchange ? localize('thinking', "Thinking\u2026") : ''); + await task; } catch (e) { - // Provider is not ready + + } finally { this._ctxHasActiveRequest.set(false); - this._chatWidget?.rawValue?.updateProgress(); - return; + this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(false); + this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(''); + this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); } const firstCodeBlockContent = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw; const regex = /```(?\w+)\n(?[\s\S]*?)```/g; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index c3efebdc5ca..402f8638558 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -84,7 +84,7 @@ export class TerminalChatWidget extends Disposable { } ); this._inlineChatWidget.placeholder = localize('default.placeholder', "Ask how to do something in the terminal"); - this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); + this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated commands may be incorrect")); this._widgetContainer.appendChild(this._inlineChatWidget.domNode); this._focusTracker = this._register(trackFocus(this._widgetContainer)); From 3c306e9b87596190decd7749f68b00b0acc52525 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:52:43 -0800 Subject: [PATCH 1246/1897] Fix feedback icons --- .../chat/browser/terminalChatActions.ts | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 37cf45c77fc..a62ed944245 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -162,6 +162,7 @@ registerActiveXtermAction({ ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatRequestActive, ), + // TODO: toggled: CTX_INLINE_CHAT_LAST_FEEDBACK.isEqualTo('helpful'), icon: Codicon.thumbsup, menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, @@ -177,12 +178,13 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.FeedbackUnhelpful, - title: localize2('feedbackUnhelpful', 'Helpful'), + title: localize2('feedbackUnhelpful', 'Unhelpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatRequestActive, ), - icon: Codicon.thumbsup, + // TODO: toggled: CTX_INLINE_CHAT_LAST_FEEDBACK.isEqualTo('unhelpful'), + icon: Codicon.thumbsdown, menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', @@ -202,14 +204,19 @@ registerActiveXtermAction({ ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatRequestActive, ), - icon: Codicon.thumbsup, - menu: { + // TODO: precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), + icon: Codicon.report, + menu: [/*{ + // TODO: Enable this id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, - group: 'inline', - order: 3, - // TODO: Fill in ctx - // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), - }, + when: ContextKeyExpr.and(CTX_TERMINAL_CHAT_SUPPORT_ISSUE_REPORTING, CTX_TERMINAL_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), + group: '2_feedback', + order: 3 + }, */{ + id: MENU_TERMINAL_CHAT_WIDGET, + group: 'config', + order: 3 + }], run: (_xterm, _accessor, activeInstance) => { // TODO: Impl } From 3119ae83f7fc220079e3a8ef9b42d254fc0ead34 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:57:38 -0800 Subject: [PATCH 1247/1897] Move accept to status menu --- .../terminalContrib/chat/browser/terminalChatActions.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index a62ed944245..348d5bee159 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -13,7 +13,7 @@ import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONS import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; registerActiveXtermAction({ @@ -72,7 +72,8 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.AcceptCommand, - title: localize2('workbench.action.terminal.acceptCommand', 'Accept Command'), + title: localize2('acceptCommand', 'Accept Command'), + shortTitle: localize2('accept', 'Accept'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), @@ -87,8 +88,8 @@ registerActiveXtermAction({ primary: KeyMod.CtrlCmd | KeyCode.Enter, }, menu: { - id: MENU_TERMINAL_CHAT_WIDGET, - group: 'main', + id: MENU_TERMINAL_CHAT_WIDGET_STATUS, + group: '0_main', order: 0, when: CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), }, From 802e74686aab506468f23b1097034c72a22c057c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:58:43 -0800 Subject: [PATCH 1248/1897] Simplify localize id --- .../terminalContrib/chat/browser/terminalChatActions.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 348d5bee159..d0205008d74 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -18,7 +18,7 @@ import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/cha registerActiveXtermAction({ id: TerminalChatCommandId.Focus, - title: localize2('workbench.action.terminal.focusChat', 'Focus Chat'), + title: localize2('focusChat', 'Focus Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), @@ -40,7 +40,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.Hide, - title: localize2('workbench.action.terminal.closeChat', 'Close Chat'), + title: localize2('closeChat', 'Close Chat'), keybinding: { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], @@ -104,7 +104,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.MakeRequest, - title: localize2('workbench.action.terminal.submitChat', 'Make Chat Request'), + title: localize2('makeChatRequest', 'Make Chat Request'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), @@ -135,7 +135,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.Cancel, - title: localize2('workbench.action.terminal.cancelChat', 'Cancel Chat'), + title: localize2('cancelChat', 'Cancel Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatRequestActive, From 9209da7104e2c60b4f16315d9b07e007cc9ec837 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 13 Feb 2024 15:41:15 -0800 Subject: [PATCH 1249/1897] Ensure escape discard/accept changes properly. --- .../controller/chat/cellChatActions.ts | 23 ++++--- .../controller/chat/notebookChatContext.ts | 1 + .../controller/chat/notebookChatController.ts | 62 ++++++++++++++++--- .../browser/controller/editActions.ts | 4 +- 4 files changed, 74 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 3829e6e67c2..7b8ce9ff6df 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -14,12 +14,12 @@ import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkey import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; import { NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController'; import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, getEditorFromArgsOrActivePane } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; registerAction2(class extends NotebookAction { @@ -222,11 +222,18 @@ registerAction2(class extends NotebookAction { shortTitle: localize('apply2', 'Accept'), icon: Codicon.check, tooltip: localize('apply3', 'Accept Changes'), - keybinding: { - when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED), - weight: KeybindingWeight.EditorContrib + 10, - primary: KeyMod.CtrlCmd | KeyCode.Enter, - }, + keybinding: [ + { + when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED), + weight: KeybindingWeight.EditorContrib + 10, + primary: KeyMod.CtrlCmd | KeyCode.Enter, + }, + { + when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_USER_DID_EDIT), + weight: KeybindingWeight.EditorCore + 10, + primary: KeyCode.Escape + } + ], menu: [ { id: MENU_CELL_CHAT_WIDGET_STATUS, @@ -251,7 +258,7 @@ registerAction2(class extends NotebookAction { title: localize('discard', 'Discard'), icon: Codicon.discard, keybinding: { - when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED, NOTEBOOK_CELL_LIST_FOCUSED), + when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_USER_DID_EDIT.negate()), weight: KeybindingWeight.EditorContrib, primary: KeyCode.Escape }, diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts index af75c65626d..af6c5e38caa 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts @@ -9,6 +9,7 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused")); export const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request")); +export const CTX_NOTEBOOK_CHAT_USER_DID_EDIT = new RawContextKey('notebookChatUserDidEdit', false, localize('notebookChatUserDidEdit', "Whether the user did changes ontop of the notebook cell chat")); export const MENU_CELL_CHAT_INPUT = MenuId.for('cellChatInput'); export const MENU_CELL_CHAT_WIDGET = MenuId.for('cellChatWidget'); export const MENU_CELL_CHAT_WIDGET_STATUS = MenuId.for('cellChatWidget.status'); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 6f0a0ad584a..b977e9fbc92 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -40,11 +40,12 @@ import { IInlineChatMessageAppender, InlineChatWidget } from 'vs/workbench/contr import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; import { INotebookEditor, INotebookEditorContribution, INotebookViewZone, ScrollToRevealBehavior } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; @@ -100,10 +101,21 @@ class NotebookChatWidget extends Disposable implements INotebookViewZone { this.inlineChatWidget.focus(); } - async getEditingCellEditor() { + getEditingCell() { + return this._editingCell; + } + + async getOrCreateEditingCell(): Promise<{ cell: CellViewModel; editor: IActiveCodeEditor } | undefined> { if (this._editingCell) { await this._notebookEditor.focusNotebookCell(this._editingCell, 'editor'); - return this._notebookEditor.activeCodeEditor; + if (this._notebookEditor.activeCodeEditor?.hasModel()) { + return { + cell: this._editingCell, + editor: this._notebookEditor.activeCodeEditor + }; + } else { + return undefined; + } } if (!this._notebookEditor.hasModel()) { @@ -117,7 +129,14 @@ class NotebookChatWidget extends Disposable implements INotebookViewZone { } await this._notebookEditor.focusNotebookCell(this._editingCell, 'editor', { revealBehavior: ScrollToRevealBehavior.firstLine }); - return this._notebookEditor.activeCodeEditor; + if (this._notebookEditor.activeCodeEditor?.hasModel()) { + return { + cell: this._editingCell, + editor: this._notebookEditor.activeCodeEditor + }; + } + + return undefined; } async discardChange() { @@ -160,6 +179,8 @@ export class NotebookChatController extends Disposable implements INotebookEdito private _activeSession?: Session; private readonly _ctxHasActiveRequest: IContextKey; private readonly _ctxCellWidgetFocused: IContextKey; + private readonly _ctxUserDidEdit: IContextKey; + private readonly _userEditingDisposables = this._register(new DisposableStore()); private readonly _ctxLastResponseType: IContextKey; private _widget: NotebookChatWidget | undefined; private _widgetDisposableStore = this._register(new DisposableStore()); @@ -174,12 +195,14 @@ export class NotebookChatController extends Disposable implements INotebookEdito @IInlineChatSavingService private readonly _inlineChatSavingService: IInlineChatSavingService, @IModelService private readonly _modelService: IModelService, @ILanguageService private readonly _languageService: ILanguageService, + @INotebookExecutionStateService private _executionStateService: INotebookExecutionStateService, ) { super(); this._ctxHasActiveRequest = CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST.bindTo(this._contextKeyService); this._ctxCellWidgetFocused = CTX_NOTEBOOK_CELL_CHAT_FOCUSED.bindTo(this._contextKeyService); this._ctxLastResponseType = CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.bindTo(this._contextKeyService); + this._ctxUserDidEdit = CTX_NOTEBOOK_CHAT_USER_DID_EDIT.bindTo(this._contextKeyService); } run(index: number, input: string | undefined, autoSend: boolean | undefined): void { @@ -270,8 +293,9 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._languageService ); + this._ctxCellWidgetFocused.set(true); + disposableTimeout(() => { - this._ctxCellWidgetFocused.set(true); this._focusWidget(); }, 0, this._store); @@ -476,6 +500,22 @@ export class NotebookChatController extends Disposable implements INotebookEdito }); } } + + this._userEditingDisposables.clear(); + // monitor user edits + const editingCell = this._widget.getEditingCell(); + if (editingCell) { + this._userEditingDisposables.add(editingCell.model.onDidChangeContent(() => this._updateUserEditingState())); + this._userEditingDisposables.add(editingCell.model.onDidChangeLanguage(() => this._updateUserEditingState())); + this._userEditingDisposables.add(editingCell.model.onDidChangeMetadata(() => this._updateUserEditingState())); + this._userEditingDisposables.add(editingCell.model.onDidChangeInternalMetadata(() => this._updateUserEditingState())); + this._userEditingDisposables.add(editingCell.model.onDidChangeOutputs(() => this._updateUserEditingState())); + this._userEditingDisposables.add(this._executionStateService.onDidChangeExecution(e => { + if (e.type === NotebookExecutionType.cell && e.affectsCell(editingCell.uri)) { + this._updateUserEditingState(); + } + })); + } } } catch (e) { response = new ErrorResponse(e); @@ -519,12 +559,14 @@ export class NotebookChatController extends Disposable implements INotebookEdito assertType(this._strategy); assertType(this._widget); - const editor = await this._widget.getEditingCellEditor(); + const editingCell = await this._widget.getOrCreateEditingCell(); - if (!editor || !editor.hasModel()) { + if (!editingCell) { return; } + const editor = editingCell.editor; + const moreMinimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(editor.getModel().uri, edits); // this._log('edits from PROVIDER and after making them MORE MINIMAL', this._activeSession.provider.debugName, edits, moreMinimalEdits); @@ -551,6 +593,10 @@ export class NotebookChatController extends Disposable implements INotebookEdito } } + private _updateUserEditingState() { + this._ctxUserDidEdit.set(true); + } + async acceptSession() { assertType(this._activeSession); assertType(this._strategy); @@ -615,6 +661,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito discard() { this._strategy?.cancel(); this._widget?.discardChange(); + this.dismiss(); } async feedbackLast(kind: InlineChatResponseFeedbackKind) { @@ -627,6 +674,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito dismiss() { this._ctxCellWidgetFocused.set(false); + this._ctxUserDidEdit.set(false); this._sessionCtor?.cancel(); this._sessionCtor = undefined; this._widget?.dispose(); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index 59f95276c9f..f056d2e6c7f 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -32,6 +32,7 @@ import { IDialogService, IConfirmationResult } from 'vs/platform/dialogs/common/ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import { CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; const CLEAR_ALL_CELLS_OUTPUTS_COMMAND_ID = 'notebook.clearAllCellsOutputs'; @@ -85,7 +86,8 @@ registerAction2(class EditCellAction extends NotebookCellAction { const quitEditCondition = ContextKeyExpr.and( NOTEBOOK_EDITOR_FOCUSED, - InputFocusedContext + InputFocusedContext, + CTX_INLINE_CHAT_FOCUSED.toNegated() ); registerAction2(class QuitEditCellAction extends NotebookCellAction { constructor() { From 8bec0459452bd0cc6122b8f49f6d4943b8328c4e Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 12 Feb 2024 14:08:45 +0100 Subject: [PATCH 1250/1897] rename suggestions: migrate to using list widget --- src/vs/editor/common/languages.ts | 11 +- .../common/standalone/standaloneEnums.ts | 4 + .../editor/contrib/rename/browser/rename.ts | 93 ++++- .../rename/browser/renameInputField.css | 13 - .../rename/browser/renameInputField.ts | 384 ++++++++++++++---- .../standalone/browser/standaloneLanguages.ts | 10 + src/vs/monaco.d.ts | 16 +- .../api/browser/mainThreadLanguageFeatures.ts | 2 +- .../workbench/api/common/extHost.api.impl.ts | 2 + .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostLanguageFeatures.ts | 75 ++-- src/vs/workbench/api/common/extHostTypes.ts | 17 + ...scode.proposed.newSymbolNamesProvider.d.ts | 13 +- 13 files changed, 511 insertions(+), 131 deletions(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 1f0a242b31f..252dabcbda6 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1652,8 +1652,17 @@ export interface RenameProvider { resolveRenameLocation?(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult; } +export enum NewSymbolNameTag { + AIGenerated = 1 +} + +export interface NewSymbolName { + readonly newSymbolName: string; + readonly tags?: readonly NewSymbolNameTag[]; +} + export interface NewSymbolNamesProvider { - provideNewSymbolNames(model: model.ITextModel, range: IRange, token: CancellationToken): ProviderResult; + provideNewSymbolNames(model: model.ITextModel, range: IRange, token: CancellationToken): ProviderResult; } export interface Command { diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index bbdeb0560ff..ed3b49ae756 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -702,6 +702,10 @@ export enum MouseTargetType { OUTSIDE_EDITOR = 13 } +export enum NewSymbolNameTag { + AIGenerated = 1 +} + /** * A positioning preference for rendering overlay widgets. */ diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index 16229d323e9..9b0eef58955 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -11,20 +11,23 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, EditorCommand, EditorContributionInstantiation, registerEditorAction, registerEditorCommand, registerEditorContribution, registerModelAndPositionCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorAction, EditorCommand, EditorContributionInstantiation, ServicesAccessor, registerEditorAction, registerEditorCommand, registerEditorContribution, registerModelAndPositionCommand } from 'vs/editor/browser/editorExtensions'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; +import { NewSymbolName, Rejection, RenameLocation, RenameProvider, WorkspaceEdit } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; -import { Rejection, RenameLocation, RenameProvider, WorkspaceEdit } from 'vs/editor/common/languages'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; +import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState'; import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; import * as nls from 'vs/nls'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -33,9 +36,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; -import { CONTEXT_RENAME_INPUT_VISIBLE, RenameInputField } from './renameInputField'; -import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { CONTEXT_RENAME_INPUT_FOCUSED, CONTEXT_RENAME_INPUT_VISIBLE, RenameInputField } from './renameInputField'; class RenameSkeleton { @@ -213,7 +214,7 @@ class RenameController implements IEditorContribution { this._languageFeaturesService.newSymbolNamesProvider .all(model) .map(provider => provider.provideNewSymbolNames(model, loc.range, cts2.token)) // TODO@ulugbekna: make sure this works regardless if the result is then-able - ).then((candidates) => candidates.filter((c): c is string[] => !!c).flat()); + ).then((candidates) => candidates.filter((c): c is NewSymbolName[] => !!c).flat()); const selection = this.editor.getSelection(); let selectionStart = 0; @@ -288,6 +289,14 @@ class RenameController implements IEditorContribution { cancelRenameInput(): void { this._renameInputField.cancelInput(true); } + + focusNextRenameSuggestion(): void { + this._renameInputField.focusNextRenameSuggestion(); + } + + focusPreviousRenameSuggestion(): void { + this._renameInputField.focusPreviousRenameSuggestion(); + } } // ---- action implementation @@ -380,6 +389,76 @@ registerEditorCommand(new RenameCommand({ } })); +registerAction2(class FocusNextRenameSuggestion extends Action2 { + constructor() { + super({ + id: 'focusNextRenameSuggestion', + title: { + ...nls.localize2('focusNextRenameSuggestion', "Focus Next Rename Suggestion"), + }, + precondition: CONTEXT_RENAME_INPUT_VISIBLE, + keybinding: [ + { + when: CONTEXT_RENAME_INPUT_FOCUSED, + primary: KeyCode.Tab, + weight: KeybindingWeight.EditorContrib + 99, + }, + { + when: CONTEXT_RENAME_INPUT_FOCUSED.toNegated(), + primary: KeyCode.Tab, + secondary: [KeyCode.DownArrow], + weight: KeybindingWeight.EditorContrib + 99, + } + ] + }); + } + + override run(accessor: ServicesAccessor): void { + const currentEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); + if (!currentEditor) { return; } + + const controller = RenameController.get(currentEditor); + if (!controller) { return; } + + controller.focusNextRenameSuggestion(); + } +}); + +registerAction2(class FocusPreviousRenameSuggestion extends Action2 { + constructor() { + super({ + id: 'focusPreviousRenameSuggestion', + title: { + ...nls.localize2('focusPreviousRenameSuggestion', "Focus Previous Rename Suggestion"), + }, + precondition: CONTEXT_RENAME_INPUT_VISIBLE, + keybinding: [ + { + when: CONTEXT_RENAME_INPUT_FOCUSED, + primary: KeyCode.Tab | KeyCode.Shift, + weight: KeybindingWeight.EditorContrib + 99, + }, + { + when: CONTEXT_RENAME_INPUT_FOCUSED.toNegated(), + primary: KeyMod.Shift | KeyCode.Tab, + secondary: [KeyCode.UpArrow], + weight: KeybindingWeight.EditorContrib + 99, + } + ] + }); + } + + override run(accessor: ServicesAccessor): void { + const currentEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); + if (!currentEditor) { return; } + + const controller = RenameController.get(currentEditor); + if (!controller) { return; } + + controller.focusPreviousRenameSuggestion(); + } +}); + // ---- api bridge command registerModelAndPositionCommand('_executeDocumentRenameProvider', function (accessor, model, position, ...args) { diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.css b/src/vs/editor/contrib/rename/browser/renameInputField.css index d02e7d5afca..9fa6ac1d3f9 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.css +++ b/src/vs/editor/contrib/rename/browser/renameInputField.css @@ -24,19 +24,6 @@ opacity: .8; } -.rename-box .new-name-candidates-container { - display: flex; - flex-direction: row; - flex-wrap: wrap; - margin-top: 5px; -} - -.rename-box .new-name-candidates-container > .monaco-text-button { - width: auto; - margin: 2px; - padding: 2px; -} - .monaco-editor .rename-box.preview .rename-label { display: inherit; } diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 0f8a2dc7b26..742c5a72cba 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -3,26 +3,46 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Button } from 'vs/base/browser/ui/button/button'; +import { addDisposableListener, getClientArea, getDomNodePagePosition, getTotalHeight } from 'vs/base/browser/dom'; +import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { List } from 'vs/base/browser/ui/list/listWidget'; +import * as arrays from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Emitter } from 'vs/base/common/event'; +import { Codicon } from 'vs/base/common/codicons'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; import 'vs/css!./renameInputField'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { IDimension } from 'vs/editor/common/core/dimension'; import { Position } from 'vs/editor/common/core/position'; import { IRange } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; +import { NewSymbolName, NewSymbolNameTag } from 'vs/editor/common/languages'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; -import { editorWidgetBackground, inputBackground, inputBorder, inputForeground, widgetBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { defaultListStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { + editorWidgetBackground, + inputBackground, + inputBorder, + inputForeground, + widgetBorder, + widgetShadow +} from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; +/** for debugging */ +const _sticky = false + // || Boolean("true") // done "weirdly" so that a lint warning prevents you from pushing this + ; + + export const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey('renameInputVisible', false, localize('renameInputVisible', "Whether the rename input widget is visible")); +export const CONTEXT_RENAME_INPUT_FOCUSED = new RawContextKey('renameInputFocused', false, localize('renameInputFocused', "Whether the rename input widget is focused")); export interface RenameInputFieldResult { newName: string; @@ -34,10 +54,13 @@ export class RenameInputField implements IContentWidget { private _position?: Position; private _domNode?: HTMLElement; private _input?: HTMLInputElement; - private _newNameCandidates?: NewSymbolNameCandidates; + private _candidatesView?: CandidatesView; private _label?: HTMLDivElement; private _visible?: boolean; + private _nPxAvailableAbove?: number; + private _nPxAvailableBelow?: number; private readonly _visibleContextKey: IContextKey; + private readonly _focusedContextKey: IContextKey; private readonly _disposables = new DisposableStore(); readonly allowEditorOverflow: boolean = true; @@ -50,6 +73,7 @@ export class RenameInputField implements IContentWidget { @IContextKeyService contextKeyService: IContextKeyService, ) { this._visibleContextKey = CONTEXT_RENAME_INPUT_VISIBLE.bindTo(contextKeyService); + this._focusedContextKey = CONTEXT_RENAME_INPUT_FOCUSED.bindTo(contextKeyService); this._editor.addContentWidget(this); @@ -80,13 +104,12 @@ export class RenameInputField implements IContentWidget { this._input.className = 'rename-input'; this._input.type = 'text'; this._input.setAttribute('aria-label', localize('renameAriaLabel', "Rename input. Type new name and press Enter to commit.")); + // TODO@ulugbekna: is using addDisposableListener's right way to do it? + this._disposables.add(addDisposableListener(this._input, 'focus', () => { this._focusedContextKey.set(true); })); + this._disposables.add(addDisposableListener(this._input, 'blur', () => { this._focusedContextKey.reset(); })); this._domNode.appendChild(this._input); - // TODO@ulugbekna: support accept/escape corresponding to the keybindings - this._newNameCandidates = new NewSymbolNameCandidates(); - this._newNameCandidates.onAccept(() => this.acceptInput(false)); // FIXME@ulugbekna: need to handle preview - this._newNameCandidates.onEscape(() => this._input!.focus()); - this._domNode.appendChild(this._newNameCandidates!.domNode); + this._candidatesView = new CandidatesView(this._domNode, { fontInfo: this._editor.getOption(EditorOption.fontInfo) }); this._label = document.createElement('div'); this._label.className = 'rename-label'; @@ -119,7 +142,7 @@ export class RenameInputField implements IContentWidget { } private _updateFont(): void { - if (!this._input || !this._label || !this._newNameCandidates) { + if (!this._input || !this._label || !this._candidatesView) { return; } @@ -128,38 +151,85 @@ export class RenameInputField implements IContentWidget { this._input.style.fontWeight = fontInfo.fontWeight; this._input.style.fontSize = `${fontInfo.fontSize}px`; - this._newNameCandidates.domNode.style.fontFamily = fontInfo.fontFamily; - this._newNameCandidates.domNode.style.fontWeight = fontInfo.fontWeight; - this._newNameCandidates.domNode.style.fontSize = `${fontInfo.fontSize}px`; + this._candidatesView.updateFont(fontInfo); - this._label.style.fontSize = `${fontInfo.fontSize * 0.8}px`; + this._label.style.fontSize = `${this._computeLabelFontSize(fontInfo.fontSize)}px`; + } + + private _computeLabelFontSize(editorFontSize: number) { + return editorFontSize * 0.8; } getPosition(): IContentWidgetPosition | null { if (!this._visible) { return null; } + + if (!this._editor.hasModel() || // @ulugbekna: shouldn't happen + !this._editor.getDomNode() // @ulugbekna: can happen during tests based on suggestWidget's similar predicate check + ) { + return null; + } + + const bodyBox = getClientArea(this.getDomNode().ownerDocument.body); + const editorBox = getDomNodePagePosition(this._editor.getDomNode()); + const cursorBox = this._editor.getScrolledVisiblePosition(this._position!); + + this._nPxAvailableAbove = cursorBox.top + editorBox.top; + this._nPxAvailableBelow = bodyBox.height - this._nPxAvailableAbove; + + const lineHeight = this._editor.getOption(EditorOption.lineHeight); + const { totalHeight: candidateViewHeight } = CandidateView.getLayoutInfo({ lineHeight }); + + const positionPreference = this._nPxAvailableBelow > candidateViewHeight * 6 /* approximate # of candidates to fit in (inclusive of rename input box & rename label) */ + ? [ContentWidgetPositionPreference.BELOW, ContentWidgetPositionPreference.ABOVE] + : [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW]; + return { position: this._position!, - preference: [ContentWidgetPositionPreference.BELOW, ContentWidgetPositionPreference.ABOVE] + preference: positionPreference, }; } beforeRender(): IDimension | null { const [accept, preview] = this._acceptKeybindings; this._label!.innerText = localize({ key: 'label', comment: ['placeholders are keybindings, e.g "F2 to Rename, Shift+F2 to Preview"'] }, "{0} to Rename, {1} to Preview", this._keybindingService.lookupKeybinding(accept)?.getLabel(), this._keybindingService.lookupKeybinding(preview)?.getLabel()); - // TODO@ulugbekna: elements larger than maxWidth shouldn't overflow - const maxWidth = Math.ceil(this._editor.getLayoutInfo().contentWidth / 4); - this._domNode!.style.maxWidth = `${maxWidth}px`; + this._domNode!.style.minWidth = `250px`; // to prevent from widening when candidates come in + this._domNode!.style.maxWidth = `400px`; // TODO@ulugbekna: what if we have a very long name? + return null; } afterRender(position: ContentWidgetPositionPreference | null): void { - if (!position) { + if (position === null) { // cancel rename when input widget isn't rendered anymore this.cancelInput(true); + return; } + + if (!this._editor.hasModel() || // shouldn't happen + !this._editor.getDomNode() // can happen during tests based on suggestWidget's similar predicate check + ) { + return; + } + + assertType(this._candidatesView); + assertType(this._nPxAvailableAbove !== undefined); + assertType(this._nPxAvailableBelow !== undefined); + + const inputBoxHeight = getTotalHeight(this._input!); + + const labelHeight = getTotalHeight(this._label!); + + let totalHeightAvailable: number; + if (position === ContentWidgetPositionPreference.BELOW) { + totalHeightAvailable = this._nPxAvailableBelow; + } else { + totalHeightAvailable = this._nPxAvailableAbove; + } + + this._candidatesView!.layout({ height: totalHeightAvailable - labelHeight - inputBoxHeight }); } @@ -174,7 +244,17 @@ export class RenameInputField implements IContentWidget { this._currentCancelInput?.(focusEditor); } - getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, newNameCandidates: Promise, token: CancellationToken): Promise { + focusNextRenameSuggestion() { + this._candidatesView?.focusNext(); + } + + focusPreviousRenameSuggestion() { + if (!this._candidatesView?.focusPrevious()) { + this._input!.focus(); + } + } + + getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, candidates: Promise, token: CancellationToken): Promise { this._domNode!.classList.toggle('preview', supportPreview); @@ -182,51 +262,46 @@ export class RenameInputField implements IContentWidget { this._input!.value = value; this._input!.setAttribute('selectionStart', selectionStart.toString()); this._input!.setAttribute('selectionEnd', selectionEnd.toString()); - this._input!.size = Math.max((where.endColumn - where.startColumn) * 1.1, 20); + this._input!.size = Math.max((where.endColumn - where.startColumn) * 1.1, 20); // determines width const disposeOnDone = new DisposableStore(); - newNameCandidates.then(candidates => { - if (!token.isCancellationRequested) { // TODO@ulugbekna: make sure this's the correct token to check - this._newNameCandidates!.setCandidates(candidates); - } - }); + candidates.then(candidates => this._showRenameCandidates(candidates, value, token)); return new Promise(resolve => { this._currentCancelInput = (focusEditor) => { this._currentAcceptInput = undefined; this._currentCancelInput = undefined; - this._newNameCandidates?.clearCandidates(); + this._candidatesView?.clearCandidates(); resolve(focusEditor); return true; }; this._currentAcceptInput = (wantsPreview) => { - if (this._input!.value.trim().length === 0) { - // empty or whitespace only or not changed - this.cancelInput(true); - return; - } + assertType(this._input !== undefined); + assertType(this._candidatesView !== undefined); - const selectedCandidate = this._newNameCandidates?.selectedCandidate; - if ((selectedCandidate === undefined && this._input!.value === value) || selectedCandidate === value) { + const candidateName = this._candidatesView.focusedCandidate; + if ((candidateName === undefined && this._input.value === value) || this._input.value.trim().length === 0) { this.cancelInput(true); return; } this._currentAcceptInput = undefined; this._currentCancelInput = undefined; - this._newNameCandidates?.clearCandidates(); + this._candidatesView.clearCandidates(); resolve({ - newName: selectedCandidate ?? this._input!.value, + newName: candidateName ?? this._input.value, wantsPreview: supportPreview && wantsPreview }); }; disposeOnDone.add(token.onCancellationRequested(() => this.cancelInput(true))); - disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(!this._domNode?.ownerDocument.hasFocus()))); + if (!_sticky) { + disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(!this._domNode?.ownerDocument.hasFocus()))); + } this._show(); @@ -250,6 +325,26 @@ export class RenameInputField implements IContentWidget { }, 100); } + private _showRenameCandidates(candidates: NewSymbolName[], currentName: string, token: CancellationToken): void { + if (token.isCancellationRequested) { + return; + } + + // deduplicate and filter out the current value + candidates = arrays.distinct(candidates, candidate => candidate.newSymbolName); + candidates = candidates.filter(({ newSymbolName }) => newSymbolName.trim().length > 0 && newSymbolName !== this._input?.value && newSymbolName !== currentName); + + if (candidates.length < 1) { + return; + } + + // show the candidates + this._candidatesView!.setCandidates(candidates); + + // ask editor to re-layout given that the widget is now of a different size after rendering rename candidates + this._editor.layoutContentWidget(this); + } + private _hide(): void { this._visible = false; this._visibleContextKey.reset(); @@ -257,49 +352,198 @@ export class RenameInputField implements IContentWidget { } } -export class NewSymbolNameCandidates { +export class CandidatesView { - public readonly domNode: HTMLElement; + private readonly _listWidget: List; + private readonly _listContainer: HTMLDivElement; - private _onAcceptEmitter = new Emitter(); - public readonly onAccept = this._onAcceptEmitter.event; - private _onEscapeEmitter = new Emitter(); - public readonly onEscape = this._onEscapeEmitter.event; + private _lineHeight: number; + private _availableHeight: number; - private _candidates: Button[] = []; + constructor(parent: HTMLElement, opts: { fontInfo: FontInfo }) { - private _candidateDisposables: DisposableStore | undefined; + this._availableHeight = 0; - // TODO@ulugbekna: pressing escape when focus is on a candidate should return the focus to the input field - constructor() { - this.domNode = document.createElement('div'); - this.domNode.className = 'rename-box new-name-candidates-container'; - this.domNode.tabIndex = -1; // Make the div unfocusable - } + this._lineHeight = opts.fontInfo.lineHeight; - get selectedCandidate(): string | undefined { - const selected = this._candidates.find(c => c.hasFocus()); - return selected === undefined ? undefined : ( - assertType(typeof selected.label === 'string', 'string'), - selected.label + this._listContainer = document.createElement('div'); + this._listContainer.style.fontFamily = opts.fontInfo.fontFamily; + this._listContainer.style.fontWeight = opts.fontInfo.fontWeight; + this._listContainer.style.fontSize = `${opts.fontInfo.fontSize}px`; + parent.appendChild(this._listContainer); + + const that = this; + + const virtualDelegate = new class implements IListVirtualDelegate { + getTemplateId(element: NewSymbolName): string { + return 'candidate'; + } + + getHeight(element: NewSymbolName): number { + return that.candidateViewHeight; + } + }; + + const renderer = new class implements IListRenderer { + readonly templateId = 'candidate'; + + renderTemplate(container: HTMLElement): CandidateView { + return new CandidateView(container, { lineHeight: that._lineHeight }); + } + + renderElement(candidate: NewSymbolName, index: number, templateData: CandidateView): void { + templateData.model = candidate; + } + + disposeTemplate(templateData: CandidateView): void { + templateData.dispose(); + } + }; + + this._listWidget = new List( + 'NewSymbolNameCandidates', + this._listContainer, + virtualDelegate, + [renderer], + { + keyboardSupport: false, // @ulugbekna: because we handle keyboard events through proper commands & keybinding service, see `rename.ts` + mouseSupport: true, + multipleSelectionSupport: false, + } ); + + this._listWidget.style(defaultListStyles); } - setCandidates(candidates: string[]): void { - this._candidateDisposables = new DisposableStore(); - for (let i = 0; i < candidates.length; i++) { - const candidate = candidates[i]; - const candidateElt = new Button(this.domNode, defaultButtonStyles); - this._candidateDisposables.add(candidateElt.onDidClick(() => this._onAcceptEmitter.fire(candidate))); - this._candidateDisposables.add(candidateElt.onDidEscape(() => this._onEscapeEmitter.fire())); - candidateElt.label = candidate; - this._candidates.push(candidateElt); + public get candidateViewHeight(): number { + const { totalHeight } = CandidateView.getLayoutInfo({ lineHeight: this._lineHeight }); + return totalHeight; + } + + // height - max height allowed by parent element + public layout({ height }: { height: number }): void { + this._availableHeight = height; + if (this._listWidget.length > 0) { // candidates have been set + this._listWidget.layout(this._pickListHeight(this._listWidget.length)); } } - clearCandidates(): void { - this._candidateDisposables?.dispose(); - this.domNode.innerText = ''; - this._candidates = []; + private _pickListHeight(nCandidates: number) { + const heightToFitAllCandidates = this.candidateViewHeight * nCandidates; + const height = Math.min(heightToFitAllCandidates, this._availableHeight, this.candidateViewHeight * 7 /* max # of candidates we want to show at once */); + return height; + } + + public setCandidates(candidates: NewSymbolName[]): void { + const height = this._pickListHeight(candidates.length); + + this._listWidget.splice(0, 0, candidates); + + this._listWidget.layout(height); + + this._listContainer.style.height = `${height}px`; + } + + public clearCandidates(): void { + this._listContainer.style.height = '0px'; + this._listWidget.splice(0, this._listWidget.length, []); + } + + public get focusedCandidate(): string | undefined { + return this._listWidget.isDOMFocused() ? this._listWidget.getFocusedElements()[0].newSymbolName : undefined; + } + + public updateFont(fontInfo: FontInfo): void { + this._listContainer.style.fontFamily = fontInfo.fontFamily; + this._listContainer.style.fontWeight = fontInfo.fontWeight; + this._listContainer.style.fontSize = `${fontInfo.fontSize}px`; + + this._lineHeight = fontInfo.lineHeight; + + this._listWidget.rerender(); + } + + public focusNext() { + if (this._listWidget.isDOMFocused()) { + this._listWidget.focusNext(); + } else { + this._listWidget.domFocus(); + this._listWidget.focusFirst(); + } + this._listWidget.reveal(this._listWidget.getFocus()[0]); + } + + /** + * @returns true if focus is moved to previous element + */ + public focusPrevious() { + this._listWidget.domFocus(); + const focusedIx = this._listWidget.getFocus()[0]; + if (focusedIx !== 0) { + this._listWidget.focusPrevious(); + this._listWidget.reveal(this._listWidget.getFocus()[0]); + } + return focusedIx > 0; + } +} + +export class CandidateView { // TODO@ulugbekna: remove export + + // TODO@ulugbekna: accessibility + + private static _PADDING: number = 2; + + public readonly domNode: HTMLElement; + private readonly _icon: HTMLElement; + private readonly _label: HTMLElement; + + constructor(parent: HTMLElement, { lineHeight }: { lineHeight: number }) { + + this.domNode = document.createElement('div'); + this.domNode.style.display = `flex`; + this.domNode.style.alignItems = `center`; + this.domNode.style.height = `${lineHeight}px`; + this.domNode.style.padding = `${CandidateView._PADDING}px`; + + this._icon = document.createElement('div'); + this._icon.style.display = `flex`; + this._icon.style.alignItems = `center`; + this._icon.style.width = this._icon.style.height = `${lineHeight * 0.8}px`; + this.domNode.appendChild(this._icon); + + this._label = document.createElement('div'); + this._icon.style.display = `flex`; + this._icon.style.alignItems = `center`; + this._label.style.marginLeft = '5px'; + this.domNode.appendChild(this._label); + + parent.appendChild(this.domNode); + } + + public set model(value: NewSymbolName) { + + // @ulugbekna: a hack to always include sparkle for now + const alwaysIncludeSparkle = true; + + // update icon + if (alwaysIncludeSparkle || value.tags?.includes(NewSymbolNameTag.AIGenerated)) { + if (this._icon.children.length === 0) { + this._icon.appendChild(renderIcon(Codicon.sparkle)); + } + } else { + if (this._icon.children.length === 1) { + this._icon.removeChild(this._icon.children[0]); + } + } + + this._label.innerText = value.newSymbolName; + } + + public static getLayoutInfo({ lineHeight }: { lineHeight: number }): { totalHeight: number } { + const totalHeight = lineHeight + CandidateView._PADDING * 2 /* top & bottom padding */; + return { totalHeight }; + } + + public dispose() { } } diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index ec191dfec13..5323333e640 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -459,6 +459,14 @@ export function registerRenameProvider(languageSelector: LanguageSelector, provi return languageFeaturesService.renameProvider.register(languageSelector, provider); } +/** + * Register a new symbol-name provider (e.g., when a symbol is being renamed, show new possible symbol-names) + */ +export function registerNewSymbolNameProvider(languageSelector: LanguageSelector, provider: languages.NewSymbolNamesProvider): IDisposable { + const languageFeaturesService = StandaloneServices.get(ILanguageFeaturesService); + return languageFeaturesService.newSymbolNamesProvider.register(languageSelector, provider); +} + /** * Register a signature help provider (used by e.g. parameter hints). */ @@ -755,6 +763,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { setMonarchTokensProvider: setMonarchTokensProvider, registerReferenceProvider: registerReferenceProvider, registerRenameProvider: registerRenameProvider, + registerNewSymbolNameProvider: registerNewSymbolNameProvider, registerCompletionItemProvider: registerCompletionItemProvider, registerSignatureHelpProvider: registerSignatureHelpProvider, registerHoverProvider: registerHoverProvider, @@ -792,6 +801,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { InlayHintKind: standaloneEnums.InlayHintKind, InlineCompletionTriggerKind: standaloneEnums.InlineCompletionTriggerKind, CodeActionTriggerType: standaloneEnums.CodeActionTriggerType, + NewSymbolNameTag: standaloneEnums.NewSymbolNameTag, // classes FoldingRangeKind: languages.FoldingRangeKind, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index dc6e5fb74e9..b1bad3efdfd 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6331,6 +6331,11 @@ declare namespace monaco.languages { */ export function registerRenameProvider(languageSelector: LanguageSelector, provider: RenameProvider): IDisposable; + /** + * Register a new symbol-name provider (e.g., when a symbol is being renamed, show new possible symbol-names) + */ + export function registerNewSymbolNameProvider(languageSelector: LanguageSelector, provider: NewSymbolNamesProvider): IDisposable; + /** * Register a signature help provider (used by e.g. parameter hints). */ @@ -7766,8 +7771,17 @@ declare namespace monaco.languages { resolveRenameLocation?(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult; } + export enum NewSymbolNameTag { + AIGenerated = 1 + } + + export interface NewSymbolName { + readonly newSymbolName: string; + readonly tags?: readonly NewSymbolNameTag[]; + } + export interface NewSymbolNamesProvider { - provideNewSymbolNames(model: editor.ITextModel, range: IRange, token: CancellationToken): ProviderResult; + provideNewSymbolNames(model: editor.ITextModel, range: IRange, token: CancellationToken): ProviderResult; } export interface Command { diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index b9d86e64edf..28193f86065 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -491,7 +491,7 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread $registerNewSymbolNamesProvider(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, this._languageFeaturesService.newSymbolNamesProvider.register(selector, { - provideNewSymbolNames: (model: ITextModel, range: IRange, token: CancellationToken): Promise => { + provideNewSymbolNames: (model: ITextModel, range: IRange, token: CancellationToken): Promise => { return this._proxy.$provideNewSymbolNames(handle, model.uri, range, token); } } satisfies languages.NewSymbolNamesProvider)); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index ac17b86e67c..a588532185d 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1672,6 +1672,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I LanguageModelSystemMessage: extHostTypes.LanguageModelSystemMessage, LanguageModelUserMessage: extHostTypes.LanguageModelUserMessage, LanguageModelAssistantMessage: extHostTypes.LanguageModelAssistantMessage, + NewSymbolName: extHostTypes.NewSymbolName, + NewSymbolNameTag: extHostTypes.NewSymbolNameTag, }; }; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c3382650e3e..b0fbf742fbc 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2108,7 +2108,7 @@ export interface ExtHostLanguageFeaturesShape { $releaseWorkspaceSymbols(handle: number, id: number): void; $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise; $resolveRenameLocation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; - $provideNewSymbolNames(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise; + $provideNewSymbolNames(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise; $provideDocumentSemanticTokens(handle: number, resource: UriComponents, previousResultId: number, token: CancellationToken): Promise; $releaseDocumentSemanticTokens(handle: number, semanticColoringResultId: number): void; $provideDocumentRangeSemanticTokens(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 90bf9b20469..4554c2a0b59 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -3,40 +3,40 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI, UriComponents } from 'vs/base/common/uri'; -import { equals, mixin } from 'vs/base/common/objects'; -import type * as vscode from 'vscode'; -import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit, Location, InlineCompletionTriggerKind, InternalDataTransferItem, SyntaxTokenType } from 'vs/workbench/api/common/extHostTypes'; -import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; -import * as languages from 'vs/editor/common/languages'; -import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; -import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; -import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; -import * as extHostProtocol from './extHost.protocol'; -import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings'; -import { IPosition } from 'vs/editor/common/core/position'; -import { IRange, Range as EditorRange } from 'vs/editor/common/core/range'; -import { isFalsyOrEmpty, isNonEmptyArray, coalesce } from 'vs/base/common/arrays'; -import { assertType, isObject } from 'vs/base/common/types'; -import { ISelection, Selection } from 'vs/editor/common/core/selection'; -import { ILogService } from 'vs/platform/log/common/log'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { IURITransformer } from 'vs/base/common/uriIpc'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { encodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto'; -import { IdGenerator } from 'vs/base/common/idGenerator'; -import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; -import { Cache } from './cache'; -import { StopWatch } from 'vs/base/common/stopwatch'; -import { isCancellationError, NotImplementedError } from 'vs/base/common/errors'; +import { coalesce, isFalsyOrEmpty, isNonEmptyArray } from 'vs/base/common/arrays'; import { raceCancellationError } from 'vs/base/common/async'; -import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; -import { ExtHostTelemetry, IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; -import { localize } from 'vs/nls'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { NotImplementedError, isCancellationError } from 'vs/base/common/errors'; +import { IdGenerator } from 'vs/base/common/idGenerator'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { equals, mixin } from 'vs/base/common/objects'; +import { StopWatch } from 'vs/base/common/stopwatch'; +import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings'; +import { assertType, isObject } from 'vs/base/common/types'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { IURITransformer } from 'vs/base/common/uriIpc'; +import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; +import { IPosition } from 'vs/editor/common/core/position'; +import { Range as EditorRange, IRange } from 'vs/editor/common/core/range'; +import { ISelection, Selection } from 'vs/editor/common/core/selection'; +import * as languages from 'vs/editor/common/languages'; import { IAutoClosingPairConditional } from 'vs/editor/common/languages/languageConfiguration'; +import { encodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto'; +import { localize } from 'vs/nls'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; +import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; +import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; +import { ExtHostTelemetry, IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; +import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; +import { CodeActionKind, CompletionList, Disposable, DocumentSymbol, InlineCompletionTriggerKind, InternalDataTransferItem, Location, Range, SemanticTokens, SemanticTokensEdit, SemanticTokensEdits, SnippetString, SymbolInformation, SyntaxTokenType } from 'vs/workbench/api/common/extHostTypes'; +import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import type * as vscode from 'vscode'; +import { Cache } from './cache'; +import * as extHostProtocol from './extHost.protocol'; // --- adapter @@ -824,7 +824,7 @@ class NewSymbolNamesAdapter { private readonly _logService: ILogService ) { } - async provideNewSymbolNames(resource: URI, range: IRange, token: CancellationToken): Promise { + async provideNewSymbolNames(resource: URI, range: IRange, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); const pos = typeConvert.Range.to(range); @@ -834,8 +834,11 @@ class NewSymbolNamesAdapter { if (!value) { return undefined; } - return value; - + return value.map(v => + typeof v === 'string' /* @ulugbekna: for backward compatibility because `value` used to be just `string[]` */ + ? { newSymbolName: v } + : { newSymbolName: v.newSymbolName, tags: v.tags } + ); } catch (err: unknown) { this._logService.error(NewSymbolNamesAdapter._asMessage(err) ?? JSON.stringify(err, null, '\t') /* @ulugbekna: assuming `err` doesn't have circular references that could result in an exception when converting to JSON */); return undefined; @@ -2331,7 +2334,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._createDisposable(handle); } - $provideNewSymbolNames(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise { + $provideNewSymbolNames(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise { return this._withAdapter(handle, NewSymbolNamesAdapter, adapter => adapter.provideNewSymbolNames(URI.revive(resource), range, token), undefined, token); } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 9122ad1b881..d4d61dfa28b 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3100,6 +3100,23 @@ export class InlineValueContext implements vscode.InlineValueContext { } } +export enum NewSymbolNameTag { + AIGenerated = 1 +} + +export class NewSymbolName implements vscode.NewSymbolName { + readonly newSymbolName: string; + readonly tags?: readonly vscode.NewSymbolNameTag[] | undefined; + + constructor( + newSymbolName: string, + tags?: readonly NewSymbolNameTag[] + ) { + this.newSymbolName = newSymbolName; + this.tags = tags; + } +} + //#region file api export enum FileChangeType { diff --git a/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts b/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts index 5f2c994d283..5b1c719e23f 100644 --- a/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts @@ -7,6 +7,17 @@ declare module 'vscode' { + export enum NewSymbolNameTag { + AIGenerated = 1 + } + + export class NewSymbolName { + readonly newSymbolName: string; + readonly tags?: readonly NewSymbolNameTag[]; + + constructor(newSymbolName: string, tags?: readonly NewSymbolNameTag[]); + } + export interface NewSymbolNamesProvider { /** * Provide possible new names for the symbol at the given range. @@ -16,7 +27,7 @@ declare module 'vscode' { * @param token A cancellation token. * @return A list of new symbol names. */ - provideNewSymbolNames(document: TextDocument, range: Range, token: CancellationToken): ProviderResult; + provideNewSymbolNames(document: TextDocument, range: Range, token: CancellationToken): ProviderResult; } export namespace languages { From 702a1ffd587b5fa1ed2fed03a77f0989eac4eb94 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Tue, 13 Feb 2024 16:15:48 -0800 Subject: [PATCH 1251/1897] Access tweaks for requestLanguageModelAccess (#205156) 1. remove the requirement that it has to be done during agent invocation 2. don't ask for auth when the model provider and the model requester are the same extension 3. since we don't have "language model activation events" start with a simple 3*2s timeout poll to wait for the language model registration to happen. (scenario: an extension activates before the extension that registers the model activates) --- .../api/browser/mainThreadChatProvider.ts | 11 ++++- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 1 - .../api/common/extHostChatAgents2.ts | 5 --- .../api/common/extHostChatProvider.ts | 42 ++++++------------- 5 files changed, 24 insertions(+), 37 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index 801b0139627..d8a197db254 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { timeout } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -81,7 +82,15 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { } async $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise { - return this._chatProviderService.lookupChatResponseProvider(providerId); + const metadata = this._chatProviderService.lookupChatResponseProvider(providerId); + // TODO: This should use a real activation event. Perhaps following what authentication does. + for (let i = 0; i < 3; i++) { + if (metadata) { + return metadata; + } + await timeout(2000); + } + return undefined; } async $fetchResponse(extension: ExtensionIdentifier, providerId: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index a588532185d..2757392e5f5 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -208,7 +208,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I 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, extHostAuthentication)); - const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService, extHostCommands)); + const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostLogService, extHostCommands)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol)); const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b0fbf742fbc..b657fa82840 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1183,7 +1183,6 @@ export interface MainThreadChatProviderShape extends IDisposable { export interface ExtHostChatProviderShape { $updateLanguageModels(data: { added?: string[]; removed?: string[] }): void; - $updateAccesslist(data: { extension: ExtensionIdentifier; enabled: boolean }[]): void; $updateModelAccesslist(data: { from: ExtensionIdentifier; to: ExtensionIdentifier; enabled: boolean }[]): void; $provideLanguageModelResponse(handle: number, requestId: number, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index bd233330fb2..efaff6f31c9 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -16,7 +16,6 @@ import { localize } from 'vs/nls'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; @@ -164,7 +163,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { constructor( mainContext: IMainContext, - private readonly _extHostChatProvider: ExtHostChatProvider, private readonly _logService: ILogService, private readonly commands: ExtHostCommands, ) { @@ -186,8 +184,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); } - this._extHostChatProvider.$updateAccesslist([{ extension: agent.extension.identifier, enabled: true }]); - // Init session disposables let sessionDisposables = this._sessionDisposables.get(request.sessionId); if (!sessionDisposables) { @@ -224,7 +220,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } finally { stream.close(); - this._extHostChatProvider.$updateAccesslist([{ extension: agent.extension.identifier, enabled: false }]); } } diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index 923a2db9eda..51b9c7fee3b 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -10,7 +10,7 @@ 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, IChatResponseFragment } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { AsyncIterableSource } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; @@ -97,7 +97,6 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { private readonly _languageModels = new Map(); private readonly _languageModelIds = new Set(); // these are ALL models, not just the one in this EH - private readonly _accesslist = new ExtensionIdentifierMap(); private readonly _modelAccessList = new ExtensionIdentifierMap(); private readonly _pendingRequest = new Map(); @@ -197,18 +196,6 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { return Array.from(this._languageModelIds); } - $updateAccesslist(data: { extension: ExtensionIdentifier; enabled: boolean }[]): void { - const updated = new ExtensionIdentifierSet(); - for (const { extension, enabled } of data) { - const oldValue = this._accesslist.get(extension); - if (oldValue !== enabled) { - this._accesslist.set(extension, enabled); - updated.add(extension); - } - } - this._onDidChangeAccess.fire(updated); - } - $updateModelAccesslist(data: { from: ExtensionIdentifier; to: ExtensionIdentifier; enabled: boolean }[]): void { const updated = new Array<{ from: ExtensionIdentifier; to: ExtensionIdentifier }>(); for (const { from, to, enabled } of data) { @@ -230,23 +217,15 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { async requestLanguageModelAccess(extension: IExtensionDescription, languageModelId: string, options?: vscode.LanguageModelAccessOptions): Promise { const from = extension.identifier; - // check if the extension is in the access list and allowed to make chat requests - if (this._accesslist.get(from) === false) { - throw new Error('Extension is NOT allowed to make chat requests'); - } - const justification = options?.justification; const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, justification); if (!metadata) { - if (!this._accesslist.get(from)) { - throw new Error('Extension is NOT allowed to make chat requests'); - } throw new Error(`Language model '${languageModelId}' NOT found`); } - if (metadata.auth) { - await this._checkAuthAccess(extension, { identifier: metadata.extension, displayName: metadata.auth?.providerLabel }, justification); + if (this._isUsingAuth(from, metadata)) { + await this._getAuthAccess(extension, { identifier: metadata.extension, displayName: metadata.auth.providerLabel }, justification); } const that = this; @@ -256,9 +235,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { return metadata.model; }, get isRevoked() { - return !that._accesslist.get(from) - || (metadata.auth && !that._modelAccessList.get(from)?.has(metadata.extension)) - || !that._languageModelIds.has(languageModelId); + return (that._isUsingAuth(from, metadata) && !that._modelAccessList.get(from)?.has(metadata.extension)) || !that._languageModelIds.has(languageModelId); }, get onDidChangeAccess() { const onDidChangeAccess = Event.filter(that._onDidChangeAccess.event, set => set.has(from)); @@ -267,7 +244,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { return Event.signal(Event.any(onDidChangeAccess, onDidRemoveLM, onDidChangeModelAccess)); }, makeChatRequest(messages, options, token) { - if (!that._accesslist.get(from) || (metadata.auth && !that._modelAccessList.get(from)?.has(metadata.extension))) { + if (that._isUsingAuth(from, metadata) && !that._modelAccessList.get(from)?.has(metadata.extension)) { throw new Error('Access to chat has been revoked'); } if (!that._languageModelIds.has(languageModelId)) { @@ -297,7 +274,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { } // BIG HACK: Using AuthenticationProviders to check access to Language Models - private async _checkAuthAccess(from: IExtensionDescription, to: { identifier: ExtensionIdentifier; displayName: string }, detail?: string): Promise { + private async _getAuthAccess(from: IExtensionDescription, to: { identifier: ExtensionIdentifier; displayName: string }, detail?: string): Promise { // This needs to be done in both MainThread & ExtHost ChatProvider const providerId = INTERNAL_AUTH_PROVIDER_PREFIX + to.identifier.value; const session = await this._extHostAuthentication.getSession(from, providerId, [], { silent: true }); @@ -315,4 +292,11 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this.$updateModelAccesslist([{ from: from.identifier, to: to.identifier, enabled: true }]); } + + private _isUsingAuth(from: ExtensionIdentifier, toMetadata: IChatResponseProviderMetadata): toMetadata is IChatResponseProviderMetadata & { auth: NonNullable } { + // If the 'to' extension uses an auth check + return !!toMetadata.auth + // And we're asking from a different extension + && !ExtensionIdentifier.equals(toMetadata.extension, from); + } } From 7f359bb293ccec5aae8e42b74805c7c4d3060ee6 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 13 Feb 2024 16:18:29 -0800 Subject: [PATCH 1252/1897] debug: allow editing visualizers in debug tree (#205163) * debug: allow editing visualizers in debug tree * fix visibility lint --- .../contrib/debug/browser/baseDebugView.ts | 36 ++++--- .../contrib/debug/browser/debugHover.ts | 4 +- .../contrib/debug/browser/variablesView.ts | 50 ++++++---- .../debug/browser/watchExpressionsView.ts | 8 +- .../workbench/contrib/debug/common/debug.ts | 7 +- .../contrib/debug/common/debugModel.ts | 33 +++++-- .../contrib/debug/common/debugViewModel.ts | 19 +++- .../contrib/debug/common/debugVisualizers.ts | 96 ++++++------------- .../vscode.proposed.debugVisualization.d.ts | 2 +- 9 files changed, 135 insertions(+), 120 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index d55c8cefd4d..ac626b82f40 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -10,17 +10,18 @@ import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabe import { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { IAsyncDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Codicon } from 'vs/base/common/codicons'; -import { ThemeIcon } from 'vs/base/common/themables'; -import { createMatches, FuzzyScore } from 'vs/base/common/filters'; +import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { createSingleCallFunction } from 'vs/base/common/functional'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { IDebugService, IExpression, IExpressionValue } from 'vs/workbench/contrib/debug/common/debug'; import { Expression, ExpressionContainer, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; +import { IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; import { ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; @@ -146,24 +147,31 @@ export interface IExpressionTemplateData { } export abstract class AbstractExpressionDataSource implements IAsyncDataSource { - constructor(@IDebugService protected debugService: IDebugService) { } + constructor( + @IDebugService protected debugService: IDebugService, + @IDebugVisualizerService protected debugVisualizer: IDebugVisualizerService, + ) { } public abstract hasChildren(element: Input | Element): boolean; - public getChildren(element: Input | Element): Promise { + public async getChildren(element: Input | Element): Promise { const vm = this.debugService.getViewModel(); - return this.doGetChildren(element).then(r => { - let dup: Element[] | undefined; - for (let i = 0; i < r.length; i++) { - const visualized = vm.getVisualizedExpression(r[i] as IExpression); - if (visualized) { - dup ||= r.slice(); - dup[i] = visualized as Element; + const children = await this.doGetChildren(element); + return Promise.all(children.map(async r => { + const vizOrTree = vm.getVisualizedExpression(r as IExpression); + if (typeof vizOrTree === 'string') { + const viz = await this.debugVisualizer.getVisualizedNodeFor(vizOrTree, r); + if (viz) { + vm.setVisualizedExpression(r, viz); + return viz as IExpression as Element; } + } else if (vizOrTree) { + return vizOrTree as Element; } - return dup || r; - }); + + return r; + })); } protected abstract doGetChildren(element: Input | Element): Promise; diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index c0e1b530b3e..65ba580cbd4 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -127,7 +127,7 @@ export class DebugHoverWidget implements IContentWidget { this.treeContainer.setAttribute('role', 'tree'); const tip = dom.append(this.complexValueContainer, $('.tip')); tip.textContent = nls.localize({ key: 'quickTip', comment: ['"switch to editor language hover" means to show the programming language hover widget instead of the debug hover'] }, 'Hold {0} key to switch to editor language hover', isMacintosh ? 'Option' : 'Alt'); - const dataSource = new DebugHoverDataSource(this.debugService); + const dataSource = this.instantiationService.createInstance(DebugHoverDataSource); const linkeDetector = this.instantiationService.createInstance(LinkDetector); this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'DebugHover', this.treeContainer, new DebugHoverDelegate(), [ this.instantiationService.createInstance(VariablesRenderer, linkeDetector), @@ -414,7 +414,7 @@ class DebugHoverDataSource extends AbstractExpressionDataSource { + protected override doGetChildren(element: IExpression): Promise { return element.getChildren(); } } diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index ef5e0cfb7d0..03c81f3ff8a 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -126,7 +126,7 @@ export class VariablesView extends ViewPane { new ScopesRenderer(), new ScopeErrorRenderer(), ], - new VariablesDataSource(this.debugService), { + this.instantiationService.createInstance(VariablesDataSource), { accessibilityProvider: new VariablesAccessibilityProvider(), identityProvider: { getId: (element: IExpression | IScope) => element.getId() }, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression | IScope) => e.name }, @@ -171,7 +171,7 @@ export class VariablesView extends ViewPane { let horizontalScrolling: boolean | undefined; this._register(this.debugService.getViewModel().onDidSelectExpression(e => { const variable = e?.expression; - if (variable instanceof Variable && !e?.settingWatch) { + if (variable && this.tree.hasNode(variable)) { horizontalScrolling = this.tree.options.horizontalScrolling; if (horizontalScrolling) { this.tree.updateOptions({ horizontalScrolling: false }); @@ -210,12 +210,24 @@ export class VariablesView extends ViewPane { } private onMouseDblClick(e: ITreeMouseEvent): void { - const session = this.debugService.getViewModel().focusedSession; - if (session && e.element instanceof Variable && session.capabilities.supportsSetVariable && !e.element.presentationHint?.attributes?.includes('readOnly') && !e.element.presentationHint?.lazy) { + if (this.canSetExpressionValue(e.element)) { this.debugService.getViewModel().setSelectedExpression(e.element, false); } } + private canSetExpressionValue(e: IExpression | IScope | null): e is IExpression { + const session = this.debugService.getViewModel().focusedSession; + if (!session) { + return false; + } + + if (e instanceof VisualizedExpression) { + return !!e.treeItem.canEdit; + } + + return e instanceof Variable && !e.presentationHint?.attributes?.includes('readOnly') && !e.presentationHint?.lazy; + } + private async onContextMenu(e: ITreeContextMenuEvent): Promise { const variable = e.element; if (!(variable instanceof Variable) || !variable.value) { @@ -415,7 +427,7 @@ export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { */ public static rendererOnVisualizationRange(model: IViewModel, tree: AsyncDataTree): IDisposable { return model.onDidChangeVisualization(({ original }) => { - if (!tree.hasElement(original)) { + if (!tree.hasNode(original)) { return; } @@ -461,24 +473,21 @@ export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { } protected override getInputBoxOptions(expression: IExpression): IInputBoxOptions | undefined { - const variable = expression; + const viz = expression; return { initialValue: expression.value, ariaLabel: localize('variableValueAriaLabel', "Type new variable value"), validationOptions: { - validation: () => variable.errorMessage ? ({ content: variable.errorMessage }) : null + validation: () => viz.errorMessage ? ({ content: viz.errorMessage }) : null }, onFinish: (value: string, success: boolean) => { - variable.errorMessage = undefined; - const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; - if (success && variable.value !== value && focusedStackFrame) { - variable.setVariable(value, focusedStackFrame) - // Need to force watch expressions and variables to update since a variable change can have an effect on both - .then(() => { - // Do not refresh scopes due to a node limitation #15520 - forgetScopes = false; - this.debugService.getViewModel().updateViews(); - }); + viz.errorMessage = undefined; + if (success) { + viz.edit(value).then(() => { + // Do not refresh scopes due to a node limitation #15520 + forgetScopes = false; + this.debugService.getViewModel().updateViews(); + }); } } }; @@ -494,7 +503,10 @@ export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { createAndFillInContextMenuActions(menu, { arg: context, shouldForwardArgs: false }, { primary, secondary: [] }, 'inline'); if (viz.original) { - primary.push(new Action('debugViz', localize('removeVisualizer', 'Remove Visualizer'), ThemeIcon.asClassName(Codicon.close), undefined, () => this.debugService.getViewModel().setVisualizedExpression(viz.original!, undefined))); + const action = new Action('debugViz', localize('removeVisualizer', 'Remove Visualizer'), ThemeIcon.asClassName(Codicon.eye), true, () => this.debugService.getViewModel().setVisualizedExpression(viz.original!, undefined)); + action.checked = true; + primary.push(action); + actionBar.domNode.style.display = 'initial'; } actionBar.clear(); actionBar.context = context; @@ -601,7 +613,7 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { if (resolved.type === DebugVisualizationType.Command) { viz.execute(); } else { - const replacement = await this.visualization.setVisualizedNodeFor(resolved.id, expression); + const replacement = await this.visualization.getVisualizedNodeFor(resolved.id, expression); if (replacement) { this.debugService.getViewModel().setVisualizedExpression(expression, replacement); } diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 9bdf8801c54..8ab1bd56d16 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -93,7 +93,7 @@ export class WatchExpressionsView extends ViewPane { this.instantiationService.createInstance(VariablesRenderer, linkDetector), this.instantiationService.createInstance(VisualizedVariableRenderer, linkDetector), ], - new WatchExpressionsDataSource(this.debugService), { + this.instantiationService.createInstance(WatchExpressionsDataSource), { accessibilityProvider: new WatchExpressionsAccessibilityProvider(), identityProvider: { getId: (element: IExpression) => element.getId() }, keyboardNavigationLabelProvider: { @@ -157,7 +157,7 @@ export class WatchExpressionsView extends ViewPane { let horizontalScrolling: boolean | undefined; this._register(this.debugService.getViewModel().onDidSelectExpression(e => { const expression = e?.expression; - if (expression instanceof Expression || (expression instanceof Variable && e?.settingWatch)) { + if (expression && this.tree.hasElement(expression)) { horizontalScrolling = this.tree.options.horizontalScrolling; if (horizontalScrolling) { this.tree.updateOptions({ horizontalScrolling: false }); @@ -204,7 +204,7 @@ export class WatchExpressionsView extends ViewPane { const element = e.element; // double click on primitive value: open input box to be able to select and copy value. const selectedExpression = this.debugService.getViewModel().getSelectedExpression(); - if (element instanceof Expression && element !== selectedExpression?.expression) { + if ((element instanceof Expression && element !== selectedExpression?.expression) || (element instanceof VisualizedExpression && element.treeItem.canEdit)) { this.debugService.getViewModel().setSelectedExpression(element, false); } else if (!element) { // Double click in watch panel triggers to add a new watch expression @@ -259,7 +259,7 @@ class WatchExpressionsDataSource extends AbstractExpressionDataSource> { + protected override doGetChildren(element: IDebugService | IExpression): Promise> { if (isDebugService(element)) { const debugService = element as IDebugService; const watchExpressions = debugService.getModel().getWatchExpressions(); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 9dcab53e240..039bcc1fb10 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -634,8 +634,9 @@ export interface IViewModel extends ITreeElement { */ readonly focusedStackFrame: IStackFrame | undefined; - setVisualizedExpression(original: IExpression, visualized: IExpression | undefined): void; - getVisualizedExpression(expression: IExpression): IExpression | undefined; + setVisualizedExpression(original: IExpression, visualized: IExpression & { treeId: string } | undefined): void; + /** Returns the visualized expression if loaded, or a tree it should be visualized with, or undefined */ + getVisualizedExpression(expression: IExpression): IExpression | string | undefined; getSelectedExpression(): { expression: IExpression; settingWatch: boolean } | undefined; setSelectedExpression(expression: IExpression | undefined, settingWatch: boolean): void; updateViews(): void; @@ -1265,7 +1266,7 @@ export interface IReplOptions { export interface IDebugVisualizationContext { variable: DebugProtocol.Variable; - containerId?: string; + containerId?: number; frameId?: number; threadId: number; sessionId: string; diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 373ebfa9189..98618a40cf4 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -246,9 +246,7 @@ function handleSetResponse(expression: ExpressionContainer, response: DebugProto } export class VisualizedExpression implements IExpression { - public readonly name: string; - public readonly hasChildren: boolean; - public readonly value: string; + public errorMessage?: string; private readonly id = generateUuid(); evaluateLazy(): Promise { @@ -262,15 +260,34 @@ export class VisualizedExpression implements IExpression { return this.id; } + get name() { + return this.treeItem.label; + } + + get value() { + return this.treeItem.description || ''; + } + + get hasChildren() { + return this.treeItem.collapsibleState !== DebugTreeItemCollapsibleState.None; + } + constructor( private readonly visualizer: IDebugVisualizerService, - private readonly treeId: string, + public readonly treeId: string, public readonly treeItem: IDebugVisualizationTreeItem, public readonly original?: Variable, - ) { - this.name = treeItem.label; - this.hasChildren = treeItem.collapsibleState !== DebugTreeItemCollapsibleState.None; - this.value = treeItem.description || ''; + ) { } + + /** Edits the value, sets the {@link errorMessage} and returns false if unsuccessful */ + public async edit(newValue: string) { + try { + await this.visualizer.editTreeItem(this.treeId, this.treeItem, newValue); + return true; + } catch (e) { + this.errorMessage = e.message; + return false; + } } } diff --git a/src/vs/workbench/contrib/debug/common/debugViewModel.ts b/src/vs/workbench/contrib/debug/common/debugViewModel.ts index 9c4ee1217ab..4b0959a97a8 100644 --- a/src/vs/workbench/contrib/debug/common/debugViewModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugViewModel.ts @@ -24,6 +24,7 @@ export class ViewModel implements IViewModel { private readonly _onWillUpdateViews = new Emitter(); private readonly _onDidChangeVisualization = new Emitter<{ original: IExpression; replacement: IExpression }>(); private readonly visualized = new WeakMap(); + private readonly preferredVisualizers = new Map(); private expressionSelectedContextKey!: IContextKey; private loadedScriptsSupportedContextKey!: IContextKey; private stepBackSupportedContextKey!: IContextKey; @@ -165,23 +166,33 @@ export class ViewModel implements IViewModel { this.multiSessionDebug.set(isMultiSessionView); } - setVisualizedExpression(original: IExpression, visualized: IExpression | undefined): void { + setVisualizedExpression(original: IExpression, visualized: IExpression & { treeId: string } | undefined): void { const current = this.visualized.get(original) || original; - + const key = this.getPreferredVisualizedKey(original); if (visualized) { this.visualized.set(original, visualized); + this.preferredVisualizers.set(key, visualized.treeId); } else { this.visualized.delete(original); + this.preferredVisualizers.delete(key); } this._onDidChangeVisualization.fire({ original: current, replacement: visualized || original }); } - getVisualizedExpression(expression: IExpression): IExpression | undefined { - return this.visualized.get(expression); + getVisualizedExpression(expression: IExpression): IExpression | string | undefined { + return this.visualized.get(expression) || this.preferredVisualizers.get(this.getPreferredVisualizedKey(expression)); } async evaluateLazyExpression(expression: IExpressionContainer): Promise { await expression.evaluateLazy(); this._onDidEvaluateLazyExpression.fire(expression); } + + private getPreferredVisualizedKey(expr: IExpression) { + return JSON.stringify([ + expr.name, + expr.type, + !!expr.memoryReference, + ].join('\0')); + } } diff --git a/src/vs/workbench/contrib/debug/common/debugVisualizers.ts b/src/vs/workbench/contrib/debug/common/debugVisualizers.ts index 2b171d5f673..45d19aee6f9 100644 --- a/src/vs/workbench/contrib/debug/common/debugVisualizers.ts +++ b/src/vs/workbench/contrib/debug/common/debugVisualizers.ts @@ -79,18 +79,17 @@ export interface IDebugVisualizerService { /** * Sets that a certa tree should be used for the visualized node */ - setVisualizedNodeFor(treeId: string, expr: IExpression): Promise; - - /** - * Gets a visualized node for the given expression if the user has preferred - * to visualize it that way. - */ - getVisualizedNodeFor(expr: IExpression): Promise; + getVisualizedNodeFor(treeId: string, expr: IExpression): Promise; /** * Gets children for a visualized tree node. */ getVisualizedChildren(treeId: string, treeElementId: number): Promise; + + /** + * Gets children for a visualized tree node. + */ + editTreeItem(treeId: string, item: IDebugVisualizationTreeItem, newValue: string): Promise; } const emptyRef: IReference = { object: [], dispose: () => { } }; @@ -101,7 +100,6 @@ export class DebugVisualizerService implements IDebugVisualizerService { private readonly handles = new Map(); private readonly trees = new Map(); private readonly didActivate = new Map>(); - private readonly preferredTrees = new Map(); private registrations: { expr: ContextKeyExpression; id: string; extensionId: ExtensionIdentifier }[] = []; constructor( @@ -126,29 +124,7 @@ export class DebugVisualizerService implements IDebugVisualizerService { return emptyRef; } - const context: IDebugVisualizationContext = { - sessionId: variable.getSession()?.getId() || '', - containerId: variable.parent.getId(), - threadId, - variable: { - name: variable.name, - value: variable.value, - type: variable.type, - evaluateName: variable.evaluateName, - variablesReference: variable.reference || 0, - indexedVariables: variable.indexedVariables, - memoryReference: variable.memoryReference, - namedVariables: variable.namedVariables, - presentationHint: variable.presentationHint, - } - }; - - for (let p: IExpressionContainer = variable; p instanceof Variable; p = p.parent) { - if (p.parent instanceof Scope) { - context.frameId = p.parent.stackFrame.frameId; - } - } - + const context = this.getVariableContext(threadId, variable); const overlay = getContextForVariable(this.contextKeyService, variable, [ [CONTEXT_VARIABLE_NAME.key, variable.name], [CONTEXT_VARIABLE_VALUE.key, variable.value], @@ -205,22 +181,7 @@ export class DebugVisualizerService implements IDebugVisualizerService { } /** @inheritdoc */ - public async setVisualizedNodeFor(treeId: string, expr: IExpression): Promise { - return this.getOrSetNodeFor(expr, treeId); - } - - /** @inheritdoc */ - public async getVisualizedNodeFor(expr: IExpression): Promise { - return this.getOrSetNodeFor(expr); - } - - /** @inheritdoc */ - public async getVisualizedChildren(treeId: string, treeElementId: number): Promise { - const children = await this.trees.get(treeId)?.getChildren(treeElementId) || []; - return children.map(c => new VisualizedExpression(this, treeId, c, undefined)); - } - - private async getOrSetNodeFor(expr: IExpression, useTreeKey?: string): Promise { + public async getVisualizedNodeFor(treeId: string, expr: IExpression): Promise { if (!(expr instanceof Variable)) { return; } @@ -230,37 +191,42 @@ export class DebugVisualizerService implements IDebugVisualizerService { return; } - const exprPreferKey = useTreeKey || this.getPreferredTreeKey(expr); - const tree = exprPreferKey && this.trees.get(exprPreferKey); + const tree = this.trees.get(treeId); if (!tree) { return; } - const treeItem = await tree.getTreeItem(this.getVariableContext(threadId, expr)); - if (!treeItem) { + try { + const treeItem = await tree.getTreeItem(this.getVariableContext(threadId, expr)); + if (!treeItem) { + return; + } + + return new VisualizedExpression(this, treeId, treeItem, expr); + } catch (e) { + this.logService.warn('Failed to get visualized node', e); return; } - - if (useTreeKey) { - this.preferredTrees.set(exprPreferKey, exprPreferKey); - } - - return new VisualizedExpression(this, exprPreferKey, treeItem, expr); } - private getPreferredTreeKey(expr: Variable) { - return JSON.stringify([ - expr.name, - expr.value, - expr.type, - !!expr.memoryReference, - ].join('\0')); + /** @inheritdoc */ + public async getVisualizedChildren(treeId: string, treeElementId: number): Promise { + const children = await this.trees.get(treeId)?.getChildren(treeElementId) || []; + return children.map(c => new VisualizedExpression(this, treeId, c, undefined)); + } + + /** @inheritdoc */ + public async editTreeItem(treeId: string, treeItem: IDebugVisualizationTreeItem, newValue: string): Promise { + const newItem = await this.trees.get(treeId)?.editItem?.(treeItem.id, newValue); + if (newItem) { + Object.assign(treeItem, newItem); // replace in-place so rerenders work + } } private getVariableContext(threadId: number, variable: Variable) { const context: IDebugVisualizationContext = { sessionId: variable.getSession()?.getId() || '', - containerId: variable.parent.getId(), + containerId: (variable.parent instanceof Variable ? variable.reference : undefined), threadId, variable: { name: variable.name, diff --git a/src/vscode-dts/vscode.proposed.debugVisualization.d.ts b/src/vscode-dts/vscode.proposed.debugVisualization.d.ts index 0fc115f7619..cb12eaa00e3 100644 --- a/src/vscode-dts/vscode.proposed.debugVisualization.d.ts +++ b/src/vscode-dts/vscode.proposed.debugVisualization.d.ts @@ -150,7 +150,7 @@ declare module 'vscode' { * that came from user evaluations in the Debug Console. * @see https://microsoft.github.io/debug-adapter-protocol/specification#Types_Variable */ - containerId?: string; + containerId?: number; /** * The ID of the Debug Adapter Protocol StackFrame in which the variable was found, * for variables that came from scopes in a stack frame. From 3e097d925edc6a3096c85ef8093d2410797594e2 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 13 Feb 2024 16:19:23 -0800 Subject: [PATCH 1253/1897] Allow empty selections -> index. --- .../browser/controller/chat/notebookChatController.ts | 1 + .../notebook/browser/viewModel/notebookViewModelImpl.ts | 9 +-------- .../workbench/contrib/notebook/common/notebookRange.ts | 9 ++++++++- .../contrib/notebook/test/browser/notebookCommon.test.ts | 6 ++++++ .../notebook/test/browser/notebookSelection.test.ts | 2 +- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index b977e9fbc92..0c4c17d60bb 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -354,6 +354,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito } this._notebookEditor.focusContainer(true); + this._notebookEditor.setFocus({ start: this._widget.afterModelPosition, end: this._widget.afterModelPosition }); this._notebookEditor.setSelections([{ start: this._widget.afterModelPosition, end: this._widget.afterModelPosition diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts index 385a37ad948..6444aa95c45 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts @@ -361,9 +361,6 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this._focused = focused; } - /** - * Empty selection will be turned to `null` - */ validateRange(cellRange: ICellRange | null | undefined): ICellRange | null { if (!cellRange) { return null; @@ -372,11 +369,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD const start = clamp(cellRange.start, 0, this.length); const end = clamp(cellRange.end, 0, this.length); - if (start === end) { - return null; - } - - if (start < end) { + if (start <= end) { return { start, end }; } else { return { start: end, end: start }; diff --git a/src/vs/workbench/contrib/notebook/common/notebookRange.ts b/src/vs/workbench/contrib/notebook/common/notebookRange.ts index 6760e454fa3..75a7a105757 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookRange.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookRange.ts @@ -65,7 +65,7 @@ export function reduceCellRanges(ranges: ICellRange[]): ICellRange[] { return []; } - return sorted.reduce((prev: ICellRange[], curr) => { + const reduced = sorted.reduce((prev: ICellRange[], curr) => { const last = prev[prev.length - 1]; if (last.end >= curr.start) { last.end = Math.max(last.end, curr.end); @@ -74,6 +74,13 @@ export function reduceCellRanges(ranges: ICellRange[]): ICellRange[] { } return prev; }, [first] as ICellRange[]); + + if (reduced.length > 1) { + // remove the (0, 0) range + return reduced.filter(range => !(range.start === range.end && range.start === 0)); + } + + return reduced; } export function cellRangesEqual(a: ICellRange[], b: ICellRange[]) { diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts index 476c7aa5327..d18126e162b 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts @@ -399,6 +399,12 @@ suite('CellRange', function () { { start: 0, end: 4 } ]); }); + + test('Reduce ranges 2, empty ranges', function () { + assert.deepStrictEqual(reduceCellRanges([{ start: 0, end: 0 }, { start: 0, end: 0 }]), [{ start: 0, end: 0 }]); + assert.deepStrictEqual(reduceCellRanges([{ start: 0, end: 0 }, { start: 1, end: 2 }]), [{ start: 1, end: 2 }]); + assert.deepStrictEqual(reduceCellRanges([{ start: 2, end: 2 }]), [{ start: 2, end: 2 }]); + }); }); suite('NotebookWorkingCopyTypeIdentifier', function () { diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts index a45fd46828f..5bbbf50a502 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts @@ -278,7 +278,7 @@ suite('NotebookCellList focus/selection', () => { (editor, viewModel) => { assert.deepStrictEqual(viewModel.validateRange(null), null); assert.deepStrictEqual(viewModel.validateRange(undefined), null); - assert.deepStrictEqual(viewModel.validateRange({ start: 0, end: 0 }), null); + assert.deepStrictEqual(viewModel.validateRange({ start: 0, end: 0 }), { start: 0, end: 0 }); assert.deepStrictEqual(viewModel.validateRange({ start: 0, end: 2 }), { start: 0, end: 2 }); assert.deepStrictEqual(viewModel.validateRange({ start: 0, end: 3 }), { start: 0, end: 2 }); assert.deepStrictEqual(viewModel.validateRange({ start: -1, end: 3 }), { start: 0, end: 2 }); From 76aa3171d106b790445fbf9117594b8ce5678166 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 13 Feb 2024 17:52:05 -0800 Subject: [PATCH 1254/1897] easier validation for input document position --- .../browser/controller/chat/notebookChatController.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 0c4c17d60bb..58bd629421a 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -13,7 +13,6 @@ import { Schemas } from 'vs/base/common/network'; import { MovingAverage } from 'vs/base/common/numbers'; import { StopWatch } from 'vs/base/common/stopwatch'; import { assertType } from 'vs/base/common/types'; -import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -229,6 +228,10 @@ export class NotebookChatController extends Disposable implements INotebookEdito } private _createWidget(index: number, input: string | undefined, autoSend: boolean | undefined) { + if (!this._notebookEditor.hasModel()) { + return; + } + // Clear the widget if it's already there this._widgetDisposableStore.clear(); @@ -253,8 +256,9 @@ export class NotebookChatController extends Disposable implements INotebookEdito { isSimpleWidget: true } )); - const inputBoxPath = `/notebook-chat-input-${NotebookChatController.counter++}`; - const inputUri = URI.from({ scheme: Schemas.untitled, path: inputBoxPath }); + const inputBoxFragment = `notebook-chat-input-${NotebookChatController.counter++}`; + const notebookUri = this._notebookEditor.textModel.uri; + const inputUri = notebookUri.with({ scheme: Schemas.untitled, fragment: inputBoxFragment }); const result: ITextModel = this._modelService.createModel('', null, inputUri, false); fakeParentEditor.setModel(result); From 22ecb93990976d1a743c2a9ea999d0a2226bac08 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 14 Feb 2024 11:51:59 +0100 Subject: [PATCH 1255/1897] remove unused event, dispose new event (#205190) --- src/vs/workbench/api/common/extHostChatProvider.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index 51b9c7fee3b..ee4d7fe863f 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -90,7 +90,6 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { private static _idPool = 1; private readonly _proxy: MainThreadChatProviderShape; - private readonly _onDidChangeAccess = new Emitter(); private readonly _onDidChangeModelAccess = new Emitter<{ from: ExtensionIdentifier; to: ExtensionIdentifier }>(); private readonly _onDidChangeProviders = new Emitter(); readonly onDidChangeProviders = this._onDidChangeProviders.event; @@ -110,7 +109,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { } dispose(): void { - this._onDidChangeAccess.dispose(); + this._onDidChangeModelAccess.dispose(); this._onDidChangeProviders.dispose(); } @@ -238,10 +237,9 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { return (that._isUsingAuth(from, metadata) && !that._modelAccessList.get(from)?.has(metadata.extension)) || !that._languageModelIds.has(languageModelId); }, get onDidChangeAccess() { - const onDidChangeAccess = Event.filter(that._onDidChangeAccess.event, set => set.has(from)); const onDidRemoveLM = Event.filter(that._onDidChangeProviders.event, e => e.removed.includes(languageModelId)); const onDidChangeModelAccess = Event.filter(that._onDidChangeModelAccess.event, e => ExtensionIdentifier.equals(e.from, from) && ExtensionIdentifier.equals(e.to, metadata.extension)); - return Event.signal(Event.any(onDidChangeAccess, onDidRemoveLM, onDidChangeModelAccess)); + return Event.signal(Event.any(onDidRemoveLM, onDidChangeModelAccess)); }, makeChatRequest(messages, options, token) { if (that._isUsingAuth(from, metadata) && !that._modelAccessList.get(from)?.has(metadata.extension)) { From 16730821dd04e574c39afd2a5ca2b553c4c11672 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 14 Feb 2024 11:38:19 +0100 Subject: [PATCH 1256/1897] observable promise utilities --- src/vs/base/common/observable.ts | 8 +- .../common/observableInternal/promise.ts} | 46 ++++++++- .../base/common/observableInternal/utils.ts | 26 ------ src/vs/base/test/common/observable.test.ts | 93 ++++++++++++++++++- .../browser/multiDiffEditorInput.ts | 3 +- 5 files changed, 144 insertions(+), 32 deletions(-) rename src/vs/{workbench/contrib/multiDiffEditor/browser/utils.ts => base/common/observableInternal/promise.ts} (62%) diff --git a/src/vs/base/common/observable.ts b/src/vs/base/common/observable.ts index 033cecf010a..978c212d765 100644 --- a/src/vs/base/common/observable.ts +++ b/src/vs/base/common/observable.ts @@ -45,9 +45,15 @@ export { observableFromPromise, observableSignal, observableSignalFromEvent, - waitForState, wasEventTriggeredRecently, } from 'vs/base/common/observableInternal/utils'; +export { + ObservableLazy, + ObservableLazyStatefulPromise, + ObservablePromise, + PromiseResult, + waitForState, +} from 'vs/base/common/observableInternal/promise'; import { ConsoleObservableLogger, setLogger } from 'vs/base/common/observableInternal/logging'; diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/utils.ts b/src/vs/base/common/observableInternal/promise.ts similarity index 62% rename from src/vs/workbench/contrib/multiDiffEditor/browser/utils.ts rename to src/vs/base/common/observableInternal/promise.ts index 26383b333ea..23292cdea50 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/utils.ts +++ b/src/vs/base/common/observableInternal/promise.ts @@ -2,8 +2,9 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - -import { IObservable, derived, observableValue } from 'vs/base/common/observable'; +import { autorun } from 'vs/base/common/observableInternal/autorun'; +import { IObservable, observableValue } from './base'; +import { derived } from 'vs/base/common/observableInternal/derived'; export class ObservableLazy { private readonly _value = observableValue(this, undefined); @@ -97,3 +98,44 @@ export class ObservableLazyStatefulPromise { return this._lazyValue.getValue().promise; } } + +/** + * Resolves the promise when the observables state matches the predicate. + */ +export function waitForState(observable: IObservable, predicate: (state: T) => state is TState, isError?: (state: T) => boolean | unknown | undefined): Promise; +export function waitForState(observable: IObservable, predicate: (state: T) => boolean, isError?: (state: T) => boolean | unknown | undefined): Promise; +export function waitForState(observable: IObservable, predicate: (state: T) => boolean, isError?: (state: T) => boolean | unknown | undefined): Promise { + return new Promise((resolve, reject) => { + let isImmediateRun = true; + let shouldDispose = false; + const stateObs = observable.map(state => { + /** @description waitForState.state */ + return { + isFinished: predicate(state), + error: isError ? isError(state) : false, + state + }; + }); + const d = autorun(reader => { + /** @description waitForState */ + const { isFinished, error, state } = stateObs.read(reader); + if (isFinished || error) { + if (isImmediateRun) { + // The variable `d` is not initialized yet + shouldDispose = true; + } else { + d.dispose(); + } + if (error) { + reject(error === true ? state : error); + } else { + resolve(state); + } + } + }); + isImmediateRun = false; + if (shouldDispose) { + d.dispose(); + } + }); +} diff --git a/src/vs/base/common/observableInternal/utils.ts b/src/vs/base/common/observableInternal/utils.ts index a16feb1f65c..5831de89add 100644 --- a/src/vs/base/common/observableInternal/utils.ts +++ b/src/vs/base/common/observableInternal/utils.ts @@ -51,32 +51,6 @@ export function observableFromPromise(promise: Promise): IObservable<{ val return observable; } -export function waitForState(observable: IObservable, predicate: (state: T) => state is TState): Promise; -export function waitForState(observable: IObservable, predicate: (state: T) => boolean): Promise; -export function waitForState(observable: IObservable, predicate: (state: T) => boolean): Promise { - return new Promise(resolve => { - let didRun = false; - let shouldDispose = false; - const stateObs = observable.map(state => ({ isFinished: predicate(state), state })); - const d = autorun(reader => { - /** @description waitForState */ - const { isFinished, state } = stateObs.read(reader); - if (isFinished) { - if (!didRun) { - shouldDispose = true; - } else { - d.dispose(); - } - resolve(state); - } - }); - didRun = true; - if (shouldDispose) { - d.dispose(); - } - }); -} - export function observableFromEvent( event: Event, getValue: (args: TArgs | undefined) => T diff --git a/src/vs/base/test/common/observable.test.ts b/src/vs/base/test/common/observable.test.ts index 63b4c9c48df..426d7d4378c 100644 --- a/src/vs/base/test/common/observable.test.ts +++ b/src/vs/base/test/common/observable.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; -import { ISettableObservable, autorun, derived, ITransaction, observableFromEvent, observableValue, transaction, keepObserved } from 'vs/base/common/observable'; +import { ISettableObservable, autorun, derived, ITransaction, observableFromEvent, observableValue, transaction, keepObserved, waitForState } from 'vs/base/common/observable'; import { BaseObservable, IObservable, IObserver } from 'vs/base/common/observableInternal/base'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; @@ -1103,6 +1103,97 @@ suite('observables', () => { 'myObservable.lastObserverRemoved', ]); }); + + suite('waitForState', () => { + test('resolve', async () => { + const log = new Log(); + const myObservable = new LoggingObservableValue('myObservable', { state: 'initializing' as 'initializing' | 'ready' | 'error' }, log); + + const p = waitForState(myObservable, p => p.state === 'ready', p => p.state === 'error').then(r => { + log.log(`resolved ${JSON.stringify(r)}`); + }, (err) => { + log.log(`rejected ${JSON.stringify(err)}`); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'myObservable.firstObserverAdded', + 'myObservable.get', + ]); + + myObservable.set({ state: 'ready' }, undefined); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'myObservable.set (value [object Object])', + 'myObservable.get', + 'myObservable.lastObserverRemoved', + ]); + + await p; + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'resolved {\"state\":\"ready\"}', + ]); + }); + + test('resolveImmediate', async () => { + const log = new Log(); + const myObservable = new LoggingObservableValue('myObservable', { state: 'ready' as 'initializing' | 'ready' | 'error' }, log); + + const p = waitForState(myObservable, p => p.state === 'ready', p => p.state === 'error').then(r => { + log.log(`resolved ${JSON.stringify(r)}`); + }, (err) => { + log.log(`rejected ${JSON.stringify(err)}`); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'myObservable.firstObserverAdded', + 'myObservable.get', + 'myObservable.lastObserverRemoved', + ]); + + myObservable.set({ state: 'error' }, undefined); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'myObservable.set (value [object Object])', + ]); + + await p; + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'resolved {\"state\":\"ready\"}', + ]); + }); + + test('reject', async () => { + const log = new Log(); + const myObservable = new LoggingObservableValue('myObservable', { state: 'initializing' as 'initializing' | 'ready' | 'error' }, log); + + const p = waitForState(myObservable, p => p.state === 'ready', p => p.state === 'error').then(r => { + log.log(`resolved ${JSON.stringify(r)}`); + }, (err) => { + log.log(`rejected ${JSON.stringify(err)}`); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'myObservable.firstObserverAdded', + 'myObservable.get', + ]); + + myObservable.set({ state: 'error' }, undefined); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'myObservable.set (value [object Object])', + 'myObservable.get', + 'myObservable.lastObserverRemoved', + ]); + + await p; + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'rejected {\"state\":\"error\"}' + ]); + }); + }); }); export class LoggingObserver implements IObserver { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 907fd6e15fe..b813d41bff3 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -11,7 +11,7 @@ import { Disposable, DisposableStore, IDisposable, IReference, toDisposable } fr import { parse } from 'vs/base/common/marshalling'; import { Schemas } from 'vs/base/common/network'; import { deepClone } from 'vs/base/common/objects'; -import { autorun, derived, observableFromEvent } from 'vs/base/common/observable'; +import { ObservableLazyStatefulPromise, autorun, derived, observableFromEvent } from 'vs/base/common/observable'; import { constObservable, mapObservableArrayCached } from 'vs/base/common/observableInternal/utils'; import { ThemeIcon } from 'vs/base/common/themables'; import { isDefined, isObject } from 'vs/base/common/types'; @@ -29,7 +29,6 @@ import { DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, EditorInputWithOpt import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput'; import { MultiDiffEditorIcon } from 'vs/workbench/contrib/multiDiffEditor/browser/icons.contribution'; import { ConstResolvedMultiDiffSource, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService'; -import { ObservableLazyStatefulPromise } from 'vs/workbench/contrib/multiDiffEditor/browser/utils'; import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { ILanguageSupport, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; From cb5ae542533b19e7a79364902d21c123c145e7ca Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 14 Feb 2024 12:13:27 +0100 Subject: [PATCH 1257/1897] adding review changes --- .../browser/multiDiffEditor.ts | 12 ++++++++++ .../browser/multiDiffEditorInput.ts | 24 +++++++++++++++++-- .../browser/multiDiffSourceResolverService.ts | 8 +++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index be2fab93175..d32ed95ec3d 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -85,6 +85,18 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 907fd6e15fe..f7dae8fc3e0 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -49,6 +49,10 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor resource.modified.resource, ); }), + input.revealResource ? new MultiDiffEditorItem( + input.revealResource.original.resource, + input.revealResource.modified.resource + ) : undefined, ); } @@ -60,7 +64,11 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor data.resources?.map(resource => new MultiDiffEditorItem( resource.originalUri ? URI.parse(resource.originalUri) : undefined, resource.modifiedUri ? URI.parse(resource.modifiedUri) : undefined, - )) + )), + data.revealResource ? new MultiDiffEditorItem( + data.revealResource.originalUri ? URI.parse(data.revealResource.originalUri) : undefined, + data.revealResource.modifiedUri ? URI.parse(data.revealResource.modifiedUri) : undefined, + ) : undefined ); } @@ -81,6 +89,7 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor public readonly multiDiffSource: URI, public readonly label: string | undefined, public readonly initialResources: readonly MultiDiffEditorItem[] | undefined, + public readonly initialResourceToReveal: MultiDiffEditorItem | undefined, @ITextModelService private readonly _textModelService: ITextModelService, @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -106,6 +115,10 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor originalUri: resource.original?.toString(), modifiedUri: resource.modified?.toString(), })), + revealResource: this.initialResourceToReveal ? { + originalUri: this.initialResourceToReveal.original?.toString(), + modifiedUri: this.initialResourceToReveal.modified?.toString(), + } : undefined }; } @@ -224,7 +237,10 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor } if (otherInput instanceof MultiDiffEditorInput) { - return this.multiDiffSource.toString() === otherInput.multiDiffSource.toString(); + const initialResourcesEqual = (this.initialResourceToReveal && otherInput.initialResourceToReveal + && this.initialResourceToReveal.equals(otherInput.initialResourceToReveal)) + || (!this.initialResourceToReveal && !otherInput.initialResourceToReveal); + return (this.multiDiffSource.toString() === otherInput.multiDiffSource.toString()) && initialResourcesEqual; } return false; @@ -355,6 +371,10 @@ interface ISerializedMultiDiffEditorInput { originalUri: string | undefined; modifiedUri: string | undefined; }[] | undefined; + revealResource: { + originalUri: string | undefined; + modifiedUri: string | undefined; + } | undefined; } export class MultiDiffEditorSerializer implements IEditorSerializer { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts index e78363dc229..bab0d9e9613 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts @@ -51,6 +51,14 @@ export class MultiDiffEditorItem { throw new BugIndicatingError('Invalid arguments'); } } + + equals(other: MultiDiffEditorItem): boolean { + if (this.original?.toString() === other.original?.toString() + && this.modified?.toString() === other.modified?.toString()) { + return true; + } + return false; + } } export class MultiDiffSourceResolverService implements IMultiDiffSourceResolverService { From 40c4d29a9d50e2eeeb5b55e3828ee57c9cfa1123 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 14 Feb 2024 12:18:35 +0100 Subject: [PATCH 1258/1897] =?UTF-8?q?SCM=20-=20=F0=9F=92=84=20adopt=20Even?= =?UTF-8?q?t.runAndSubscribe=20(#205194)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workbench/contrib/scm/browser/scmViewPane.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 500967073c5..a42ecfd8374 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -3119,7 +3119,7 @@ export class SCMViewPane extends ViewPane { repositoryDisposables.add(repository.input.onDidChangeVisibility(() => this.updateChildren(repository))); repositoryDisposables.add(repository.provider.onDidChangeResourceGroups(() => this.updateChildren(repository))); - const onDidChangeHistoryProvider = () => { + repositoryDisposables.add(Event.runAndSubscribe(repository.provider.onDidChangeHistoryProvider, () => { if (!repository.provider.historyProvider) { this.logService.debug('SCMViewPane:onDidChangeVisibleRepositories - no history provider present'); return; @@ -3131,10 +3131,7 @@ export class SCMViewPane extends ViewPane { })); this.logService.debug('SCMViewPane:onDidChangeVisibleRepositories - onDidChangeCurrentHistoryItemGroup listener added'); - }; - - repositoryDisposables.add(repository.provider.onDidChangeHistoryProvider(onDidChangeHistoryProvider)); - onDidChangeHistoryProvider(); + })); const resourceGroupDisposables = repositoryDisposables.add(new DisposableMap()); @@ -3783,7 +3780,7 @@ class SCMTreeDataSource implements IAsyncDataSource { + repositoryDisposables.add(Event.runAndSubscribe(repository.provider.onDidChangeHistoryProvider, () => { if (!repository.provider.historyProvider) { this.logService.debug('SCMTreeDataSource:onDidChangeVisibleRepositories - no history provider present'); return; @@ -3795,10 +3792,7 @@ class SCMTreeDataSource implements IAsyncDataSource Date: Wed, 14 Feb 2024 12:19:03 +0100 Subject: [PATCH 1259/1897] Chat API updates (#205184) * api - remove unused types, add jsdoc, make request handler setable (for consistency), more readonly usage https://github.com/microsoft/vscode/issues/199908 * remove ChatMessage and Role * fix a ton of compile errors... * jsdoc --- .../workbench/api/common/extHost.api.impl.ts | 4 +- .../api/common/extHostChatAgents2.ts | 18 ++++- .../api/common/extHostChatProvider.ts | 8 +- .../api/common/extHostTypeConverters.ts | 28 ------- src/vs/workbench/api/common/extHostTypes.ts | 18 ----- .../vscode.proposed.chatAgents2.d.ts | 79 ++++++------------- .../vscode.proposed.chatAgents2Additions.d.ts | 4 +- .../vscode.proposed.chatProvider.d.ts | 24 +----- .../vscode.proposed.chatRequestAccess.d.ts | 68 ++++++++++++++-- 9 files changed, 105 insertions(+), 146 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 2757392e5f5..2f1aac77d56 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1421,7 +1421,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'mappedEditsProvider'); return extHostLanguageFeatures.registerMappedEditsProvider(extension, selector, provider); }, - createChatAgent(name: string, handler: vscode.ChatAgentExtendedHandler) { + createChatAgent(name: string, handler: vscode.ChatAgentExtendedRequestHandler) { checkProposedApiEnabled(extension, 'chatAgents2'); return extHostChatAgents2.createChatAgent(extension, name, handler); }, @@ -1460,8 +1460,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I Breakpoint: extHostTypes.Breakpoint, TerminalOutputAnchor: extHostTypes.TerminalOutputAnchor, ChatAgentResultFeedbackKind: extHostTypes.ChatAgentResultFeedbackKind, - ChatMessage: extHostTypes.ChatMessage, - ChatMessageRole: extHostTypes.ChatMessageRole, ChatVariableLevel: extHostTypes.ChatVariableLevel, ChatAgentCompletionItem: extHostTypes.ChatAgentCompletionItem, CallHierarchyIncomingCall: extHostTypes.CallHierarchyIncomingCall, diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index efaff6f31c9..61f74812130 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -11,6 +11,7 @@ import { Emitter } from 'vs/base/common/event'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; +import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -169,7 +170,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); } - createChatAgent(extension: IExtensionDescription, name: string, handler: vscode.ChatAgentExtendedHandler): vscode.ChatAgent2 { + createChatAgent(extension: IExtensionDescription, name: string, handler: vscode.ChatAgentExtendedRequestHandler): vscode.ChatAgent2 { const handle = ExtHostChatAgents2._idPool++; const agent = new ExtHostChatAgent(extension, name, this._proxy, handle, handler); this._agents.set(handle, agent); @@ -358,7 +359,7 @@ class ExtHostChatAgent { public readonly id: string, private readonly _proxy: MainThreadChatAgentsShape2, private readonly _handle: number, - private readonly _callback: vscode.ChatAgentExtendedHandler, + private _requestHandler: vscode.ChatAgentExtendedRequestHandler, ) { } acceptFeedback(feedback: vscode.ChatAgentResult2Feedback) { @@ -507,6 +508,13 @@ class ExtHostChatAgent { that._iconPath = v; updateMetadataSoon(); }, + get requestHandler() { + return that._requestHandler; + }, + set requestHandler(v) { + assertType(typeof v === 'function', 'Invalid request handler'); + that._requestHandler = v; + }, get commandProvider() { return that._commandProvider; }, @@ -585,6 +593,7 @@ class ExtHostChatAgent { return that._onDidReceiveFeedback.event; }, set agentVariableProvider(v) { + checkProposedApiEnabled(that.extension, 'chatAgents2Additions'); that._agentVariableProvider = v; if (v) { if (!v.triggerCharacters.length) { @@ -597,13 +606,16 @@ class ExtHostChatAgent { } }, get agentVariableProvider() { + checkProposedApiEnabled(that.extension, 'chatAgents2Additions'); return that._agentVariableProvider; }, set welcomeMessageProvider(v) { + checkProposedApiEnabled(that.extension, 'defaultChatAgent'); that._welcomeMessageProvider = v; updateMetadataSoon(); }, get welcomeMessageProvider() { + checkProposedApiEnabled(that.extension, 'defaultChatAgent'); return that._welcomeMessageProvider; }, onDidPerformAction: !isProposedApiEnabled(this.extension, 'chatAgents2Additions') @@ -621,6 +633,6 @@ class ExtHostChatAgent { } invoke(request: vscode.ChatAgentRequest, context: vscode.ChatAgentContext, response: vscode.ChatAgentExtendedResponseStream, token: CancellationToken): vscode.ProviderResult { - return this._callback(request, context, response, token); + return this._requestHandler(request, context, response, token); } } diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index ee4d7fe863f..5e725927e9b 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -149,13 +149,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._proxy.$handleProgressChunk(requestId, { index: fragment.index, part: fragment.part }); }); - if (data.provider.provideLanguageModelResponse2) { - return data.provider.provideLanguageModelResponse2(messages.map(typeConvert.LanguageModelMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); - } else { - // TODO@jrieken remove - return data.provider.provideLanguageModelResponse(messages.map(typeConvert.ChatMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); - } - + return data.provider.provideLanguageModelResponse2(messages.map(typeConvert.LanguageModelMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); } //#region --- making request diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 4c403c04398..ae82d84eae3 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2239,12 +2239,6 @@ export namespace ChatInlineFollowup { } } -export namespace ChatMessage { - export function to(message: chatProvider.IChatMessage): vscode.ChatMessage { - return new types.ChatMessage(ChatMessageRole.to(message.role), message.content); - } -} - export namespace LanguageModelMessage { export function to(message: chatProvider.IChatMessage): vscode.LanguageModelMessage { @@ -2268,28 +2262,6 @@ export namespace LanguageModelMessage { } } - -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; - } - } - - 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.User: - default: - return chatProvider.ChatMessageRole.User; - } - } -} - export namespace ChatVariable { export function objectTo(variableObject: Record): Record { const result: Record = {}; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index d4d61dfa28b..41b31a4901d 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4196,24 +4196,6 @@ export enum InteractiveEditorResponseFeedbackKind { Bug = 4 } -export enum ChatMessageRole { - System = 0, - User = 1, - Assistant = 2, -} - -export class ChatMessage implements vscode.ChatMessage { - - role: ChatMessageRole; - content: string; - name?: string; - - constructor(role: ChatMessageRole, content: string) { - this.role = role; - this.content = content; - } -} - export enum ChatAgentResultFeedbackKind { Unhelpful = 0, Helpful = 1, diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 8aa7e9c184f..ed55c5ffbf2 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -5,26 +5,6 @@ declare module 'vscode' { - /** - * One request/response pair in chat history. - */ - export interface ChatAgentHistoryEntry { - /** - * The request that was sent to the chat agent. - */ - request: ChatAgentRequest; - - /** - * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. - */ - response: ReadonlyArray; - - /** - * The result that was received from the chat agent. - */ - result: ChatAgentResult2; - } - // TODO@API name: Turn? export class ChatAgentRequestTurn { @@ -38,15 +18,10 @@ declare module 'vscode' { */ readonly prompt: string; - // TODO@API NAME agent - // TODO@API TYPE {agent:string, extension:string} - /** @deprecated */ - readonly agentId: string; - /** * The ID of the chat agent to which this request was directed. */ - readonly agent: { extensionId: string; agentId: string }; + readonly agent: { readonly extensionId: string; readonly agentId: string }; /** * The name of the {@link ChatAgentCommand command} that was selected for this request. @@ -74,10 +49,7 @@ declare module 'vscode' { */ readonly result: ChatAgentResult2; - /** @deprecated */ - readonly agentId: string; - - readonly agent: { extensionId: string; agentId: string }; + readonly agent: { readonly extensionId: string; readonly agentId: string }; private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: { extensionId: string; agentId: string }); } @@ -189,7 +161,7 @@ declare module 'vscode' { /** * Returns a list of commands that its agent is capable of handling. A command - * can be selected by the user and will then be passed to the {@link ChatAgentHandler handler} + * can be selected by the user and will then be passed to the {@link ChatAgentRequestHandler handler} * via the {@link ChatAgentRequest.command command} property. * * @@ -197,6 +169,7 @@ declare module 'vscode' { * @returns A list of commands. The lack of a result can be signaled by returning `undefined`, `null`, or * an empty array. */ + // TODO@API Q: should we provide the current history or last results for extra context? provideCommands(token: CancellationToken): ProviderResult; } @@ -228,6 +201,7 @@ declare module 'vscode' { /** * A title to show the user, when it is different than the message. */ + // TODO@API title vs tooltip? title?: string; } @@ -243,6 +217,12 @@ declare module 'vscode' { provideFollowups(result: ChatAgentResult2, token: CancellationToken): ProviderResult; } + /** + * A chat request handler is a callback that will be invoked when a request is made to a chat agent. + */ + export type ChatAgentRequestHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentResponseStream, token: CancellationToken) => ProviderResult; + + export interface ChatAgent2 { /** @@ -274,6 +254,11 @@ declare module 'vscode' { dark: Uri; } | ThemeIcon; + /** + * The handler for requests to this agent. + */ + requestHandler: ChatAgentRequestHandler; + /** * This provider will be called to retrieve the agent's commands. */ @@ -284,16 +269,6 @@ declare module 'vscode' { */ followupProvider?: ChatAgentFollowupProvider; - - // TODO@API - // notify(request: ChatResponsePart, reference: string): boolean; - // BETTER - // requestResponseStream(result: ChatAgentResult, callback: (stream: ChatAgentResponseStream) => void, why?: string): void; - - // TODO@API - // clear NEVER happens - // onDidClearResult(value: TResult): void; - /** * When the user clicks this agent in `/help`, this text will be submitted to this command */ @@ -315,9 +290,9 @@ declare module 'vscode' { } export interface ChatAgentResolvedVariable { - name: string; - range: [start: number, end: number]; - values: ChatVariableValue[]; + readonly name: string; + readonly range: [start: number, end: number]; + readonly values: ChatVariableValue[]; } export interface ChatAgentRequest { @@ -330,17 +305,17 @@ declare module 'vscode' { * *Note* that the {@link ChatAgent2.name name} of the agent and the {@link ChatAgentCommand.name command} * are not part of the prompt. */ - prompt: string; + readonly prompt: string; /** * The name of the {@link ChatAgentCommand command} that was selected for this request. */ - command?: string; + readonly command: string | undefined; /** * The list of variables that are referenced in the prompt. */ - variables: ChatAgentResolvedVariable[]; + readonly variables: readonly ChatAgentResolvedVariable[]; } export interface ChatAgentResponseStream { @@ -429,11 +404,6 @@ declare module 'vscode' { push(part: ChatResponsePart): ChatAgentResponseStream; } - // TODO@API - // support ChatResponseCommandPart - // support ChatResponseTextEditPart - // support ChatResponseCodeReferencePart - // TODO@API should the name suffix differentiate between rendered items (XYZPart) // and metadata like XYZItem export class ChatResponseTextPart { @@ -482,9 +452,6 @@ declare module 'vscode' { | ChatResponseProgressPart | ChatResponseReferencePart | ChatResponseCommandButtonPart; - // TODO@API Remove a different type of `request` so that they can - // evolve at their own pace - export type ChatAgentHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentResponseStream, token: CancellationToken) => ProviderResult; export namespace chat { @@ -495,7 +462,7 @@ declare module 'vscode' { * @param handler The reply-handler of the agent. * @returns A new chat agent */ - export function createChatAgent(name: string, handler: ChatAgentHandler): ChatAgent2; + export function createChatAgent(name: string, handler: ChatAgentRequestHandler): ChatAgent2; /** * Register a variable which can be used in a chat request to any agent. diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index b934eeea995..32eb4999065 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -158,13 +158,13 @@ declare module 'vscode' { constructor(label: string | CompletionItemLabel, values: ChatVariableValue[]); } - export type ChatAgentExtendedHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentExtendedResponseStream, token: CancellationToken) => ProviderResult; + export type ChatAgentExtendedRequestHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentExtendedResponseStream, token: CancellationToken) => ProviderResult; export namespace chat { /** * Create a chat agent with the extended progress type */ - export function createChatAgent(name: string, handler: ChatAgentExtendedHandler): ChatAgent2; + export function createChatAgent(name: string, handler: ChatAgentExtendedRequestHandler): ChatAgent2; } /* diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index 7ac3ddba1b9..7a9e140b5a5 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -5,27 +5,6 @@ declare module 'vscode' { - // TODO@API NAME: ChatMessageKind? - export enum ChatMessageRole { - System = 0, - User = 1, - // TODO@API name: align with ChatAgent (or whatever we'll rename that to) - Assistant = 2, - } - - /** - * A chat message that is used to make chat request against a language model. - */ - export class ChatMessage { - - readonly role: ChatMessageRole; - - readonly content: string; - - constructor(role: ChatMessageRole, content: string); - } - - export interface ChatResponseFragment { index: number; part: string; @@ -37,8 +16,7 @@ declare module 'vscode' { * Represents a large language model that accepts ChatML messages and produces a streaming response */ export interface ChatResponseProvider { - provideLanguageModelResponse(messages: ChatMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; - provideLanguageModelResponse2?(messages: LanguageModelMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; + provideLanguageModelResponse2(messages: LanguageModelMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; } export interface ChatResponseProviderMetadata { diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts index fdd1cb7f5ef..633c28dc61e 100644 --- a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts +++ b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts @@ -5,35 +5,91 @@ declare module 'vscode' { + /** + * Represents a language model response. + * + * @see {@link LanguageModelAccess.makeChatRequest} + */ export interface LanguageModelResponse { /** * The overall result of the request which represents failure or success - * but _not_ the actual response or responses + * but. The concrete value is not specified and depends on the selected language model. + * + * *Note* that the actual response represented by the {@link LanguageModelResponse.stream `stream`}-property */ - // TODO@API define this type! result: Thenable; - // TODO@API doc what to expect here + /** + * An async iterable that is a stream of text chunks forming the overall response. + */ stream: AsyncIterable; } - //TODO@API see https://learn.microsoft.com/en-us/dotnet/api/azure.ai.openai.chatrequestmessage?view=azure-dotnet-preview - // this allows to grow message by type, e.g add more content types to User message to support multimodal language models - + /** + * A language model message that represents a system message. + * + * System messages provide instructions to the language model that define the context in + * which user messages are interpreted. + * + * *Note* that a language model may choose to add additional system messages to the ones + * provided by extensions. + */ export class LanguageModelSystemMessage { + + /** + * The content of this message. + */ content: string; + + /** + * Create a new system message. + * + * @param content The content of the message. + */ constructor(content: string); } + /** + * A language model message that represents a user message. + */ export class LanguageModelUserMessage { + + /** + * The content of this message. + */ content: string; + + /** + * The optional name of a user for this message. + */ name: string | undefined; + + /** + * Create a new user message. + * + * @param content The content of the message. + * @param name The optional name of a user for the message. + */ constructor(content: string, name?: string); } + /** + * A language model message that represents an assistant message, usually in response to a user message + * or as a sample response/reply-pair. + */ export class LanguageModelAssistantMessage { + + /** + * The content of this message. + */ content: string; + + /** + * Create a new assistant message. + * + * @param content The content of the message. + */ constructor(content: string); } From 500401c8ac5afd54326a5addcdf092db3eedc311 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 14 Feb 2024 14:53:50 +0100 Subject: [PATCH 1260/1897] polishing the code --- .../multiDiffEditorWidget.ts | 4 +- .../multiDiffEditorWidgetImpl.ts | 2 +- src/vs/workbench/common/editor.ts | 2 +- .../bulkEdit/browser/preview/bulkEditPane.ts | 4 +- .../browser/multiDiffEditor.ts | 6 +-- .../browser/multiDiffEditorInput.ts | 43 ++++++++----------- .../browser/multiDiffSourceResolverService.ts | 8 ---- .../services/editor/common/editorService.ts | 1 + 8 files changed, 30 insertions(+), 40 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index e2392cc8b4a..8923fc2024e 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { derived, derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; -import { IMultiDiffEditorViewState, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorViewState, IMultiDiffResource, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import './colors'; @@ -44,7 +44,7 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public reveal(resource: { original: URI } | { modified: URI }, lineNumber: number): void { + public reveal(resource: IMultiDiffResource, lineNumber: number): void { this._widgetImpl.get().reveal(resource, lineNumber); } diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index b8547c7d347..b9a0287e63c 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -313,7 +313,7 @@ function isMultiDiffOriginalResourceUri(obj: any): obj is IMultiDiffOriginalReso return 'original' in obj && obj.original instanceof URI; } -type IMultiDiffResource = IMultiDiffOriginalResource | IMultiDiffModifiedResource; +export type IMultiDiffResource = IMultiDiffOriginalResource | IMultiDiffModifiedResource; class VirtualizedViewItem extends Disposable { private readonly _templateRef = this._register(disposableObservableValue | undefined>(this, undefined)); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 322582ad33c..283a89b9c85 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -507,7 +507,7 @@ export interface IResourceMultiDiffEditorInput extends IBaseUntypedEditorInput { readonly resources?: IResourceDiffEditorInput[]; /** - * Reveal the following resource on open + * Reveal the following resource on editor open */ readonly revealResource?: IResourceDiffEditorInput; } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index ff0cc489d28..21b1e44e809 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -11,6 +11,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IThemeService } from 'vs/platform/theme/common/themeService'; import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { BulkEditPreviewProvider, BulkFileOperation, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -36,7 +37,6 @@ import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; -import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IResourceDiffEditorInput } from 'vs/workbench/common/editor'; const enum State { @@ -344,6 +344,8 @@ export class BulkEditPane extends ViewPane { } else { resources = await this._getResources(fileOperations); } + this._fileOperations = fileOperations; + this._resources = resources; const revealResource = resources.find(r => r.original.resource!.toString() === fileElement.edit.uri.toString()); const multiDiffSource = URI.from({ scheme: 'refactor-preview' }); const label = 'Refactor Preview'; diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index d32ed95ec3d..4ef87792095 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -24,7 +24,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { URI } from 'vs/base/common/uri'; import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; -import { IMultiDiffEditorViewState } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorViewState, IMultiDiffResource } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; @@ -72,7 +72,7 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { + return new MultiDiffEditorItem( + resource.original.resource, + resource.modified.resource, + ); + }; const multiDiffSource = input.multiDiffSource ?? URI.parse(`multi-diff-editor:${new Date().getMilliseconds().toString() + Math.random().toString()}`); return instantiationService.createInstance( MultiDiffEditorInput, multiDiffSource, input.label, - input.resources?.map(resource => { - return new MultiDiffEditorItem( - resource.original.resource, - resource.modified.resource, - ); - }), - input.revealResource ? new MultiDiffEditorItem( - input.revealResource.original.resource, - input.revealResource.modified.resource - ) : undefined, + input.resources?.map(toMultiDiffEditorItem), + input.revealResource ? toMultiDiffEditorItem(input.revealResource) : undefined, ); } public static fromSerialized(data: ISerializedMultiDiffEditorInput, instantiationService: IInstantiationService): MultiDiffEditorInput { + const toMultiDiffEditorItem = (resource: { originalUri: string | undefined; modifiedUri: string | undefined }): MultiDiffEditorItem => { + return new MultiDiffEditorItem( + resource.originalUri ? URI.parse(resource.originalUri) : undefined, + resource.modifiedUri ? URI.parse(resource.modifiedUri) : undefined, + ); + }; return instantiationService.createInstance( MultiDiffEditorInput, URI.parse(data.multiDiffSourceUri), data.label, - data.resources?.map(resource => new MultiDiffEditorItem( - resource.originalUri ? URI.parse(resource.originalUri) : undefined, - resource.modifiedUri ? URI.parse(resource.modifiedUri) : undefined, - )), - data.revealResource ? new MultiDiffEditorItem( - data.revealResource.originalUri ? URI.parse(data.revealResource.originalUri) : undefined, - data.revealResource.modifiedUri ? URI.parse(data.revealResource.modifiedUri) : undefined, - ) : undefined + data.resources?.map(toMultiDiffEditorItem), + data.revealResource ? toMultiDiffEditorItem(data.revealResource) : undefined ); } @@ -236,11 +234,8 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor return true; } - if (otherInput instanceof MultiDiffEditorInput) { - const initialResourcesEqual = (this.initialResourceToReveal && otherInput.initialResourceToReveal - && this.initialResourceToReveal.equals(otherInput.initialResourceToReveal)) - || (!this.initialResourceToReveal && !otherInput.initialResourceToReveal); - return (this.multiDiffSource.toString() === otherInput.multiDiffSource.toString()) && initialResourcesEqual; + if (otherInput instanceof MultiDiffEditorInput && !this.initialResourceToReveal && !otherInput.initialResourceToReveal) { + return this.multiDiffSource.toString() === otherInput.multiDiffSource.toString(); } return false; diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts index bab0d9e9613..e78363dc229 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts @@ -51,14 +51,6 @@ export class MultiDiffEditorItem { throw new BugIndicatingError('Invalid arguments'); } } - - equals(other: MultiDiffEditorItem): boolean { - if (this.original?.toString() === other.original?.toString() - && this.modified?.toString() === other.modified?.toString()) { - return true; - } - return false; - } } export class MultiDiffSourceResolverService implements IMultiDiffSourceResolverService { diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 19b51a652c7..1be2060f98c 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -236,6 +236,7 @@ export interface IEditorService { * Open an editor in an editor group. * * @param editor the editor to open + * @param options the options to use for the editor * @param group the target group. If unspecified, the editor will open in the currently * active group. Use `SIDE_GROUP` to open the editor in a new editor group to the side * of the currently active group. From 61458114b4da140c638d7a4ceb14657149d8c3d8 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 14 Feb 2024 14:22:45 +0100 Subject: [PATCH 1261/1897] a11y: Use underlined links in HC themes --- src/vs/platform/theme/common/colorRegistry.ts | 6 ++++-- src/vs/workbench/browser/media/style.css | 5 +++++ src/vs/workbench/contrib/chat/browser/media/chat.css | 7 ------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 3702702f542..c0948533749 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -230,8 +230,10 @@ export const selectionBackground = registerColor('selection.background', { light // ------ text colors export const textSeparatorForeground = registerColor('textSeparator.foreground', { light: '#0000002e', dark: '#ffffff2e', hcDark: Color.black, hcLight: '#292929' }, nls.localize('textSeparatorForeground', "Color for text separators.")); -export const textLinkForeground = registerColor('textLink.foreground', { light: '#006AB1', dark: '#3794FF', hcDark: '#3794FF', hcLight: '#0F4A85' }, nls.localize('textLinkForeground', "Foreground color for links in text.")); -export const textLinkActiveForeground = registerColor('textLink.activeForeground', { light: '#006AB1', dark: '#3794FF', hcDark: '#3794FF', hcLight: '#0F4A85' }, nls.localize('textLinkActiveForeground', "Foreground color for links in text when clicked on and on mouse hover.")); + +export const textLinkForeground = registerColor('textLink.foreground', { light: '#006AB1', dark: '#3794FF', hcDark: '#21A6FF', hcLight: '#0F4A85' }, nls.localize('textLinkForeground', "Foreground color for links in text.")); +export const textLinkActiveForeground = registerColor('textLink.activeForeground', { light: '#006AB1', dark: '#3794FF', hcDark: '#21A6FF', hcLight: '#0F4A85' }, nls.localize('textLinkActiveForeground', "Foreground color for links in text when clicked on and on mouse hover.")); + export const textPreformatForeground = registerColor('textPreformat.foreground', { light: '#A31515', dark: '#D7BA7D', hcDark: '#000000', hcLight: '#FFFFFF' }, nls.localize('textPreformatForeground', "Foreground color for preformatted text segments.")); export const textPreformatBackground = registerColor('textPreformat.background', { light: '#0000001A', dark: '#FFFFFF1A', hcDark: '#FFFFFF', hcLight: '#09345f' }, nls.localize('textPreformatBackground', "Background color for preformatted text segments.")); export const textBlockQuoteBackground = registerColor('textBlockQuote.background', { light: '#f2f2f2', dark: '#222222', hcDark: null, hcLight: '#F2F2F2' }, nls.localize('textBlockQuoteBackground', "Background color for block quotes in text.")); diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index 262745f0ed2..db4862c7427 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -87,6 +87,11 @@ body.web { text-decoration: none; } +.monaco-workbench.hc-black p > a, +.monaco-workbench.hc-light p > a { + text-decoration: underline !important; +} + .monaco-workbench a:active { color: inherit; background-color: inherit; diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 140e418f1e0..20f6f5d621f 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -207,13 +207,6 @@ color: var(--vscode-textPreformat-foreground); } -.hc-black .interactive-item-container .value .rendered-markdown a:hover, -.hc-black .interactive-item-container .value .rendered-markdown a:active, -.hc-light .interactive-item-container .value .rendered-markdown a:hover, -.hc-light .interactive-item-container .value .rendered-markdown a:active { - color: var(--vscode-textPreformat-foreground); -} - .interactive-list { overflow: hidden; } From ca86859b09829468917c38ebb47b294406763a42 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 14 Feb 2024 07:19:00 -0800 Subject: [PATCH 1262/1897] align contents to top (#205211) --- .../contrib/extensions/browser/extensionFeaturesTab.ts | 2 +- .../contrib/extensions/browser/media/extensionEditor.css | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts index d09f329de49..35491eaa620 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts @@ -473,7 +473,7 @@ class ExtensionFeatureView extends Disposable { return $('td', undefined, ...data.map(item => { const result: Node[] = []; if (isMarkdownString(rowData)) { - const element = $('td', undefined); + const element = $('', undefined); this.renderMarkdown(rowData, element); result.push(element); } else if (item instanceof ResolvedKeybinding) { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 4243f5ebd44..ce78e8c7f02 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -616,6 +616,10 @@ padding: 2px 16px 2px 4px; } +.extension-editor > .body > .content table td { + vertical-align: top; +} + .extension-editor > .body > .content table th:last-child, .extension-editor > .body > .content table td:last-child { padding: 2px 4px; From 4d349caa60bb386e0bd7b1ad26c41c009baa62cd Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 14 Feb 2024 16:53:15 +0100 Subject: [PATCH 1263/1897] changes from review --- .../multiDiffEditorWidgetImpl.ts | 52 +++++++++++++------ .../browser/bracketMatching.ts | 2 +- src/vs/workbench/common/editor.ts | 5 -- .../bulkEdit/browser/preview/bulkEditPane.ts | 27 ++++++---- .../browser/multiDiffEditor.ts | 23 ++++---- .../browser/multiDiffEditorInput.ts | 39 +++++--------- 6 files changed, 77 insertions(+), 71 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index b9a0287e63c..31cf913dfeb 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -191,17 +191,47 @@ export class MultiDiffEditorWidgetImpl extends Disposable { public reveal(resource: IMultiDiffResource, lineNumber: number): void { const viewItems = this._viewItems.get(); let searchCallback: (item: VirtualizedViewItem) => boolean; - if (isMultiDiffOriginalResourceUri(resource)) { + if ('original' in resource) { searchCallback = (item) => item.viewModel.originalUri?.toString() === resource.original.toString(); } else { searchCallback = (item) => item.viewModel.modifiedUri?.toString() === resource.modified.toString(); } const index = viewItems.findIndex(searchCallback); - const scrollTopWithinItem = (lineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); + /* + // How to find the size of the current editor window? + const scrollTopViewport = this._scrollableElement.getScrollPosition().scrollTop; + const scrollBottomViewport = scrollTopViewport + this._sizeObserver.height.get(); + + const scrollTopWithinViewport = (scrollTop: number) => { + return scrollTop >= scrollTopViewport && scrollTop <= scrollBottomViewport; + } + + console.log('scrollTopViewport', scrollTopViewport); + console.log('scrollBottomViewport', scrollBottomViewport); + + let scrollTopOfResource = 0; + for (let i = 0; i < index; i++) { + scrollTopOfResource += viewItems[i].contentHeight.get() + this._spaceBetweenPx; + } + const lineHeight = this._configurationService.getValue('editor.lineHeight'); + const scrollTopOfRange = scrollTopOfResource + (range.startLineNumber - 1) * lineHeight; + const scrollBottomOfRange = scrollTopOfResource + (range.endLineNumber) * lineHeight; + + console.log('scrollTopOfRange', scrollTopOfRange); + console.log('scrollBottomOfRange', scrollBottomOfRange); + + if (scrollTopWithinViewport(scrollTopOfRange) && scrollTopWithinViewport(scrollBottomOfRange)) { + // Early return because the range is already visible + return; + } + + // The range is not visible, hence jump to the top of the top of the range + this._scrollableElement.setScrollPosition({ scrollTop: scrollTopOfRange }); + */ + // todo@aiday-mar: need to find the actual scroll top given the line number specific to the original or modified uri // following does not neccessarily correspond to the appropriate scroll top within the editor - const maxScroll = viewItems[index].template.get()?.maxScroll.get().maxScroll; - let scrollTop = (maxScroll && scrollTopWithinItem < maxScroll) ? scrollTopWithinItem : 0; + let scrollTop = (lineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); for (let i = 0; i < index; i++) { scrollTop += viewItems[i].contentHeight.get() + this._spaceBetweenPx; } @@ -301,19 +331,7 @@ interface IMultiDiffDocState { selections?: ISelection[]; } -interface IMultiDiffOriginalResource { - original: URI; -} - -interface IMultiDiffModifiedResource { - modified: URI; -} - -function isMultiDiffOriginalResourceUri(obj: any): obj is IMultiDiffOriginalResource { - return 'original' in obj && obj.original instanceof URI; -} - -export type IMultiDiffResource = IMultiDiffOriginalResource | IMultiDiffModifiedResource; +export type IMultiDiffResource = { original: URI } | { modified: URI }; class VirtualizedViewItem extends Disposable { private readonly _templateRef = this._register(disposableObservableValue | undefined>(this, undefined)); diff --git a/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts index ffd9e3240dd..552cb7c06a3 100644 --- a/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts @@ -216,7 +216,7 @@ export class BracketMatchingController extends Disposable implements IEditorCont } return new Selection(position.lineNumber, position.column, position.lineNumber, position.column); }); - + this._editor.revealLineInCenterIfOutsideViewport(); this._editor.setSelections(newSelections); this._editor.revealRange(newSelections[0]); } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 283a89b9c85..bf6785d9675 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -505,11 +505,6 @@ export interface IResourceMultiDiffEditorInput extends IBaseUntypedEditorInput { * If not set, the resources are dynamically derived from the {@link multiDiffSource}. */ readonly resources?: IResourceDiffEditorInput[]; - - /** - * Reveal the following resource on editor open - */ - readonly revealResource?: IResourceDiffEditorInput; } export type IResourceMergeEditorInputSide = (IResourceEditorInput | ITextResourceEditorInput) & { detail?: string }; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 21b1e44e809..f22ba75b048 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -324,6 +324,8 @@ export class BulkEditPane extends ViewPane { return; } const options: Mutable = { ...e.editorOptions }; + console.log('options : ', options); + // todo@aiday-mar, we may not need the following options let fileElement: FileElement; if (e.element instanceof TextEditElement) { fileElement = e.element.parent; @@ -338,20 +340,18 @@ export class BulkEditPane extends ViewPane { return; } - let resources: IResourceDiffEditorInput[]; - if (this._fileOperations === fileOperations && this._resources) { - resources = this._resources; - } else { - resources = await this._getResources(fileOperations); - } - this._fileOperations = fileOperations; - this._resources = resources; - const revealResource = resources.find(r => r.original.resource!.toString() === fileElement.edit.uri.toString()); + const resources = await this._resolveResources(fileOperations); + options.viewState = { + revealData: { + resource: { original: fileElement.edit.uri }, + lineNumber: 1 + } + }; + console.log('options : ', options); const multiDiffSource = URI.from({ scheme: 'refactor-preview' }); const label = 'Refactor Preview'; this._editorService.openEditor({ multiDiffSource, - revealResource, resources, label, description: label, @@ -359,7 +359,10 @@ export class BulkEditPane extends ViewPane { }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } - private async _getResources(fileOperations: BulkFileOperation[]): Promise { + private async _resolveResources(fileOperations: BulkFileOperation[]): Promise { + if (this._fileOperations === fileOperations && this._resources) { + return this._resources; + } const resources: IResourceDiffEditorInput[] = []; for (const operation of fileOperations) { const operationUri = operation.uri; @@ -386,6 +389,8 @@ export class BulkEditPane extends ViewPane { }); } } + this._fileOperations = fileOperations; + this._resources = resources; return resources; } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 4ef87792095..e047d5595b9 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -85,16 +85,19 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { - return new MultiDiffEditorItem( - resource.original.resource, - resource.modified.resource, - ); - }; const multiDiffSource = input.multiDiffSource ?? URI.parse(`multi-diff-editor:${new Date().getMilliseconds().toString() + Math.random().toString()}`); return instantiationService.createInstance( MultiDiffEditorInput, multiDiffSource, input.label, - input.resources?.map(toMultiDiffEditorItem), - input.revealResource ? toMultiDiffEditorItem(input.revealResource) : undefined, + input.resources?.map(resource => { + return new MultiDiffEditorItem( + resource.original.resource, + resource.modified.resource, + ); + }), ); } public static fromSerialized(data: ISerializedMultiDiffEditorInput, instantiationService: IInstantiationService): MultiDiffEditorInput { - const toMultiDiffEditorItem = (resource: { originalUri: string | undefined; modifiedUri: string | undefined }): MultiDiffEditorItem => { - return new MultiDiffEditorItem( - resource.originalUri ? URI.parse(resource.originalUri) : undefined, - resource.modifiedUri ? URI.parse(resource.modifiedUri) : undefined, - ); - }; return instantiationService.createInstance( MultiDiffEditorInput, URI.parse(data.multiDiffSourceUri), data.label, - data.resources?.map(toMultiDiffEditorItem), - data.revealResource ? toMultiDiffEditorItem(data.revealResource) : undefined + data.resources?.map(resource => new MultiDiffEditorItem( + resource.originalUri ? URI.parse(resource.originalUri) : undefined, + resource.modifiedUri ? URI.parse(resource.modifiedUri) : undefined, + )) ); } @@ -87,7 +81,6 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor public readonly multiDiffSource: URI, public readonly label: string | undefined, public readonly initialResources: readonly MultiDiffEditorItem[] | undefined, - public readonly initialResourceToReveal: MultiDiffEditorItem | undefined, @ITextModelService private readonly _textModelService: ITextModelService, @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -113,10 +106,6 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor originalUri: resource.original?.toString(), modifiedUri: resource.modified?.toString(), })), - revealResource: this.initialResourceToReveal ? { - originalUri: this.initialResourceToReveal.original?.toString(), - modifiedUri: this.initialResourceToReveal.modified?.toString(), - } : undefined }; } @@ -234,7 +223,7 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor return true; } - if (otherInput instanceof MultiDiffEditorInput && !this.initialResourceToReveal && !otherInput.initialResourceToReveal) { + if (otherInput instanceof MultiDiffEditorInput) { return this.multiDiffSource.toString() === otherInput.multiDiffSource.toString(); } @@ -366,10 +355,6 @@ interface ISerializedMultiDiffEditorInput { originalUri: string | undefined; modifiedUri: string | undefined; }[] | undefined; - revealResource: { - originalUri: string | undefined; - modifiedUri: string | undefined; - } | undefined; } export class MultiDiffEditorSerializer implements IEditorSerializer { From b3b6068187ca49a9e2f001acf6e67483e8ccf764 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 14 Feb 2024 17:07:31 +0100 Subject: [PATCH 1264/1897] jsdoc and todo-update (#205201) https://github.com/microsoft/vscode/issues/199908 --- .../vscode.proposed.chatAgents2.d.ts | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index ed55c5ffbf2..1d0caf6a5a4 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -21,6 +21,7 @@ declare module 'vscode' { /** * The ID of the chat agent to which this request was directed. */ + // TODO@API NAME: agentId shouldbe agentName or just agent (because it is ChatAgent#name) readonly agent: { readonly extensionId: string; readonly agentId: string }; /** @@ -49,6 +50,7 @@ declare module 'vscode' { */ readonly result: ChatAgentResult2; + // TODO@API NAME: agentId shouldbe agentName or just agent (because it is ChatAgent#name) readonly agent: { readonly extensionId: string; readonly agentId: string }; private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: { extensionId: string; agentId: string }); @@ -289,9 +291,28 @@ declare module 'vscode' { dispose(): void; } + /** + * A resolved variable value is a name-value pair as well as the range in the prompt where a variable was used. + */ export interface ChatAgentResolvedVariable { + + /** + * The name of the variable. + * + * *Note* that the name doesn't include the leading `#`-character, + * e.g `selection` for `#selection`. + */ readonly name: string; + + /** + * The start and end index of the variable in the {@link ChatAgentRequest.prompt prompt}. + * + * *Note* that the indices take the leading `#`-character into account which means they can + * used to modify the prompt as-is. + */ readonly range: [start: number, end: number]; + + // TODO@API decouple of resolve API, use `value: string | Uri | (maybe) unknown?` readonly values: ChatVariableValue[]; } @@ -313,8 +334,15 @@ declare module 'vscode' { readonly command: string | undefined; /** - * The list of variables that are referenced in the prompt. + * The list of variables and their values that are referenced in the prompt. + * + * *Note* that the prompt contains varibale references as authored and that it is up to the agent + * to further modify the prompt, for instance by inlining variable values or creating links to + * headings which contain the resolved values. vvariables are sorted in reverse by their range + * in the prompt. That means the last variable in the prompt is the first in this list. This simplifies + * string-manipulation of the prompt. */ + // TODO@API Q? are there implicit variables that are not part of the prompt? readonly variables: readonly ChatAgentResolvedVariable[]; } @@ -435,6 +463,7 @@ declare module 'vscode' { export class ChatResponseProgressPart { value: string; + // TODO@API inline constructor(value: string); } @@ -448,18 +477,21 @@ declare module 'vscode' { constructor(value: Command); } + /** + * Represents the different chat response types. + */ export type ChatResponsePart = ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseProgressPart | ChatResponseReferencePart | ChatResponseCommandButtonPart; - export namespace chat { /** * Create a new {@link ChatAgent2 chat agent} instance. * - * @param name Short name by which this agent is referred to in the UI - * @param handler The reply-handler of the agent. + * @param name Short name by which the agent is referred to in the UI. The name must be unique for the extension + * contributing the agent but can collide with names from other extensions. + * @param handler A request handler for the agent. * @returns A new chat agent */ export function createChatAgent(name: string, handler: ChatAgentRequestHandler): ChatAgent2; From abaae7f726d29299d4125a739c0c744e015e823c Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 14 Feb 2024 17:23:13 +0100 Subject: [PATCH 1265/1897] adding code --- .../multiDiffEditorWidgetImpl.ts | 9 +++++++- .../browser/bracketMatching.ts | 2 +- .../bulkEdit/browser/preview/bulkEditPane.ts | 21 +++++++------------ .../browser/multiDiffEditor.ts | 16 +++++++------- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 31cf913dfeb..6bb3d5f9185 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -224,7 +224,6 @@ export class MultiDiffEditorWidgetImpl extends Disposable { // Early return because the range is already visible return; } - // The range is not visible, hence jump to the top of the top of the range this._scrollableElement.setScrollPosition({ scrollTop: scrollTopOfRange }); */ @@ -333,6 +332,14 @@ interface IMultiDiffDocState { export type IMultiDiffResource = { original: URI } | { modified: URI }; +export function isIMultiDiffResource(obj: any): obj is IMultiDiffResource { + if (('original' in obj && obj.original instanceof URI) + || ('modified' in obj && obj.modified instanceof URI)) { + return true; + } + return false; +} + class VirtualizedViewItem extends Disposable { private readonly _templateRef = this._register(disposableObservableValue | undefined>(this, undefined)); diff --git a/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts index 552cb7c06a3..ffd9e3240dd 100644 --- a/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts @@ -216,7 +216,7 @@ export class BracketMatchingController extends Disposable implements IEditorCont } return new Selection(position.lineNumber, position.column, position.lineNumber, position.column); }); - this._editor.revealLineInCenterIfOutsideViewport(); + this._editor.setSelections(newSelections); this._editor.revealRange(newSelections[0]); } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index f22ba75b048..db1d7174f53 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -323,39 +323,34 @@ export class BulkEditPane extends ViewPane { if (!fileOperations) { return; } - const options: Mutable = { ...e.editorOptions }; - console.log('options : ', options); - // todo@aiday-mar, we may not need the following options let fileElement: FileElement; if (e.element instanceof TextEditElement) { fileElement = e.element.parent; - options.selection = e.element.edit.textEdit.textEdit.range; - } else if (e.element instanceof FileElement) { fileElement = e.element; - options.selection = e.element.edit.textEdits[0]?.textEdit.textEdit.range; - } else { // invalid event return; } const resources = await this._resolveResources(fileOperations); - options.viewState = { - revealData: { - resource: { original: fileElement.edit.uri }, - lineNumber: 1 + const options: Mutable = { + ...e.editorOptions, + viewState: { + revealData: { + resource: { original: fileElement.edit.uri }, + lineNumber: 1 + } } }; - console.log('options : ', options); const multiDiffSource = URI.from({ scheme: 'refactor-preview' }); const label = 'Refactor Preview'; this._editorService.openEditor({ multiDiffSource, resources, label, - description: label, options, + description: label }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index e047d5595b9..1270bc7e977 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -24,7 +24,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { URI } from 'vs/base/common/uri'; import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; -import { IMultiDiffEditorViewState, IMultiDiffResource } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorViewState, IMultiDiffResource, isIMultiDiffResource } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; @@ -93,12 +93,14 @@ export class MultiDiffEditor extends AbstractEditorWithViewState Date: Wed, 14 Feb 2024 17:37:50 +0100 Subject: [PATCH 1266/1897] polishing the code --- .../multiDiffEditorWidget.ts | 5 ++- .../multiDiffEditorWidgetImpl.ts | 39 ++----------------- .../browser/multiDiffEditor.ts | 5 ++- 3 files changed, 10 insertions(+), 39 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index 8923fc2024e..a9707084634 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -18,6 +18,7 @@ import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Range } from 'vs/editor/common/core/range'; export class MultiDiffEditorWidget extends Disposable { private readonly _dimension = observableValue(this, undefined); @@ -44,8 +45,8 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public reveal(resource: IMultiDiffResource, lineNumber: number): void { - this._widgetImpl.get().reveal(resource, lineNumber); + public reveal(resource: IMultiDiffResource, range: Range): void { + this._widgetImpl.get().reveal(resource, range); } public createViewModel(model: IMultiDiffEditorModel): MultiDiffEditorViewModel { diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 6bb3d5f9185..381e48b089f 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -26,6 +26,7 @@ import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Range } from 'vs/editor/common/core/range'; export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _elements = h('div.monaco-component.multiDiffEditor', [ @@ -188,7 +189,8 @@ export class MultiDiffEditorWidgetImpl extends Disposable { this._scrollableElement.setScrollPosition({ scrollLeft: scrollState.left, scrollTop: scrollState.top }); } - public reveal(resource: IMultiDiffResource, lineNumber: number): void { + // todo@aiday-mar need to reveal the range instead of just the start line number + public reveal(resource: IMultiDiffResource, range: Range): void { const viewItems = this._viewItems.get(); let searchCallback: (item: VirtualizedViewItem) => boolean; if ('original' in resource) { @@ -197,40 +199,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { searchCallback = (item) => item.viewModel.modifiedUri?.toString() === resource.modified.toString(); } const index = viewItems.findIndex(searchCallback); - /* - // How to find the size of the current editor window? - const scrollTopViewport = this._scrollableElement.getScrollPosition().scrollTop; - const scrollBottomViewport = scrollTopViewport + this._sizeObserver.height.get(); - - const scrollTopWithinViewport = (scrollTop: number) => { - return scrollTop >= scrollTopViewport && scrollTop <= scrollBottomViewport; - } - - console.log('scrollTopViewport', scrollTopViewport); - console.log('scrollBottomViewport', scrollBottomViewport); - - let scrollTopOfResource = 0; - for (let i = 0; i < index; i++) { - scrollTopOfResource += viewItems[i].contentHeight.get() + this._spaceBetweenPx; - } - const lineHeight = this._configurationService.getValue('editor.lineHeight'); - const scrollTopOfRange = scrollTopOfResource + (range.startLineNumber - 1) * lineHeight; - const scrollBottomOfRange = scrollTopOfResource + (range.endLineNumber) * lineHeight; - - console.log('scrollTopOfRange', scrollTopOfRange); - console.log('scrollBottomOfRange', scrollBottomOfRange); - - if (scrollTopWithinViewport(scrollTopOfRange) && scrollTopWithinViewport(scrollBottomOfRange)) { - // Early return because the range is already visible - return; - } - // The range is not visible, hence jump to the top of the top of the range - this._scrollableElement.setScrollPosition({ scrollTop: scrollTopOfRange }); - */ - - // todo@aiday-mar: need to find the actual scroll top given the line number specific to the original or modified uri - // following does not neccessarily correspond to the appropriate scroll top within the editor - let scrollTop = (lineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); + let scrollTop = (range.startLineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); for (let i = 0; i < index; i++) { scrollTop += viewItems[i].contentHeight.get() + this._spaceBetweenPx; } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 1270bc7e977..151f887f0e9 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -27,6 +27,7 @@ import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEdit import { IMultiDiffEditorViewState, IMultiDiffResource, isIMultiDiffResource } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; +import { Range } from 'vs/editor/common/core/range'; export class MultiDiffEditor extends AbstractEditorWithViewState { static readonly ID = 'multiDiffEditor'; @@ -72,8 +73,8 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { From d88ae06cede970c1e3ea216fd73346292a359f63 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 14 Feb 2024 17:50:33 +0100 Subject: [PATCH 1267/1897] IRange used --- .../widget/multiDiffEditorWidget/multiDiffEditorWidget.ts | 4 ++-- .../multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts | 4 ++-- .../contrib/bulkEdit/browser/preview/bulkEditPane.ts | 3 ++- .../contrib/multiDiffEditor/browser/multiDiffEditor.ts | 8 ++++---- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index a9707084634..4fed15c8da2 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -18,7 +18,7 @@ import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { Range } from 'vs/editor/common/core/range'; +import { IRange } from 'vs/editor/common/core/range'; export class MultiDiffEditorWidget extends Disposable { private readonly _dimension = observableValue(this, undefined); @@ -45,7 +45,7 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public reveal(resource: IMultiDiffResource, range: Range): void { + public reveal(resource: IMultiDiffResource, range: IRange): void { this._widgetImpl.get().reveal(resource, range); } diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 381e48b089f..3aedbd4cc57 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -26,7 +26,7 @@ import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Range } from 'vs/editor/common/core/range'; +import { IRange } from 'vs/editor/common/core/range'; export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _elements = h('div.monaco-component.multiDiffEditor', [ @@ -190,7 +190,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } // todo@aiday-mar need to reveal the range instead of just the start line number - public reveal(resource: IMultiDiffResource, range: Range): void { + public reveal(resource: IMultiDiffResource, range: IRange): void { const viewItems = this._viewItems.get(); let searchCallback: (item: VirtualizedViewItem) => boolean; if ('original' in resource) { diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index db1d7174f53..913f8bdf136 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -38,6 +38,7 @@ import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; import { IResourceDiffEditorInput } from 'vs/workbench/common/editor'; +import { Range } from 'vs/editor/common/core/range'; const enum State { Data = 'data', @@ -339,7 +340,7 @@ export class BulkEditPane extends ViewPane { viewState: { revealData: { resource: { original: fileElement.edit.uri }, - lineNumber: 1 + range: new Range(1, 1, 1, 1) } } }; diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 151f887f0e9..a01863e45ed 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -27,7 +27,7 @@ import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEdit import { IMultiDiffEditorViewState, IMultiDiffResource, isIMultiDiffResource } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; -import { Range } from 'vs/editor/common/core/range'; +import { IRange, Range } from 'vs/editor/common/core/range'; export class MultiDiffEditor extends AbstractEditorWithViewState { static readonly ID = 'multiDiffEditor'; @@ -73,7 +73,7 @@ export class MultiDiffEditor extends AbstractEditorWithViewState Date: Wed, 14 Feb 2024 10:16:07 -0800 Subject: [PATCH 1268/1897] Add api command for fetching notebook variables (#205224) --- .../api/common/extHostNotebookKernels.ts | 19 ++++++- .../notebookVariableCommands.ts | 40 ++++++++++++++ .../controller/chat/notebookChatController.ts | 53 +++++++++++++++++++ 3 files changed, 111 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index e73b409421a..e5b11fac0ce 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -12,7 +12,7 @@ import { ResourceMap } from 'vs/base/common/map'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; -import { ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, IMainContext, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape, NotebookOutputDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, IMainContext, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape, NotebookOutputDto, VariablesResult } from 'vs/workbench/api/common/extHost.protocol'; import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; @@ -90,7 +90,24 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { }) ], ApiCommandResult.Void); + + const requestKernelVariablesApiCommand = new ApiCommand( + 'vscode.executeNotebookVariableProvider', + '_executeNotebookVariableProvider', + 'Execute notebook variable provider', + [ApiCommandArgument.Uri], + new ApiCommandResult('A promise that resolves to an array of variables', (value, apiArgs) => { + return value.map(variable => { + return { + name: variable.name, + value: variable.value, + editable: false + }; + }); + }) + ); this._commands.registerApiCommand(selectKernelApiCommand); + this._commands.registerApiCommand(requestKernelVariablesApiCommand); } createNotebookController(extension: IExtensionDescription, id: string, viewType: string, label: string, handler?: (cells: vscode.NotebookCell[], notebook: vscode.NotebookDocument, controller: vscode.NotebookController) => void | Thenable, preloads?: vscode.NotebookRendererScript[]): vscode.NotebookController { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts index fcdd865e65e..03db0e250cd 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts @@ -3,11 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationToken } from 'vs/base/common/cancellation'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { contextMenuArg } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView'; +import { INotebookKernelService, VariablesResult } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; export const COPY_NOTEBOOK_VARIABLE_VALUE_ID = 'workbench.debug.viewlet.action.copyWorkspaceVariableValue'; export const COPY_NOTEBOOK_VARIABLE_VALUE_LABEL = localize('copyWorkspaceVariableValue', "Copy Value"); @@ -28,3 +32,39 @@ registerAction2(class extends Action2 { } } }); + + +registerAction2(class extends Action2 { + constructor() { + super({ + id: '_executeNotebookVariableProvider', + title: localize('executeNotebookVariableProvider', "Execute Notebook Variable Provider"), + f1: false, + }); + } + + async run(accessor: ServicesAccessor, resource: UriComponents | undefined): Promise { + if (!resource) { + return []; + } + + const uri = URI.revive(resource); + const notebookKernelService = accessor.get(INotebookKernelService); + const notebookService = accessor.get(INotebookService); + const notebookTextModel = notebookService.getNotebookTextModel(uri); + + if (!notebookTextModel) { + return []; + } + + const selectedKernel = notebookKernelService.getMatchingKernel(notebookTextModel).selected; + if (selectedKernel && selectedKernel.hasVariableProvider) { + const variables = selectedKernel.provideVariables(notebookTextModel.uri, undefined, 'named', 0, CancellationToken.None); + return await variables + .map(variable => { return variable; }) + .toPromise(); + } + + return []; + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 58bd629421a..61ac0a1a06c 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -176,6 +176,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito private _strategy: EditStrategy | undefined; private _sessionCtor: CancelablePromise | undefined; private _activeSession?: Session; + private _warmupRequestCts?: CancellationTokenSource; private readonly _ctxHasActiveRequest: IContextKey; private readonly _ctxCellWidgetFocused: IContextKey; private readonly _ctxUserDidEdit: IContextKey; @@ -275,6 +276,10 @@ export class NotebookChatController extends Disposable implements INotebookEdito inlineChatWidget.placeholder = localize('default.placeholder', "Ask a question"); inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); widgetContainer.appendChild(inlineChatWidget.domNode); + this._widgetDisposableStore.add(inlineChatWidget.onDidChangeInput(() => { + this._warmupRequestCts?.dispose(true); + this._warmupRequestCts = undefined; + })); this._notebookEditor.changeViewZones(accessor => { const notebookViewZone = { @@ -307,6 +312,8 @@ export class NotebookChatController extends Disposable implements INotebookEdito if (fakeParentEditor.hasModel()) { await this._startSession(fakeParentEditor, token); + this._warmupRequestCts = new CancellationTokenSource(); + this._startInitialFolowups(fakeParentEditor, this._warmupRequestCts.token); if (this._widget) { this._widget.inlineChatWidget.placeholder = this._activeSession?.session.placeholder ?? localize('default.placeholder', "Ask a question"); @@ -368,6 +375,8 @@ export class NotebookChatController extends Disposable implements INotebookEdito async acceptInput() { assertType(this._activeSession); assertType(this._widget); + this._warmupRequestCts?.dispose(true); + this._warmupRequestCts = undefined; this._activeSession.addInput(new SessionPrompt(this._widget.inlineChatWidget.value)); assertType(this._activeSession.lastInput); @@ -559,6 +568,50 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._strategy = new EditStrategy(session); } + private async _startInitialFolowups(editor: IActiveCodeEditor, token: CancellationToken) { + if (!this._activeSession || !this._activeSession.provider.provideFollowups) { + return; + } + + const request: IInlineChatRequest = { + requestId: generateUuid(), + prompt: '', + attempt: 0, + selection: { selectionStartLineNumber: 1, selectionStartColumn: 1, positionLineNumber: 1, positionColumn: 1 }, + wholeRange: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, + live: true, + previewDocument: editor.getModel().uri, + withIntentDetection: true + }; + + const progress = new AsyncProgress(async data => { }); + const task = this._activeSession.provider.provideResponse(this._activeSession.session, request, progress, token); + const reply = await raceCancellationError(Promise.resolve(task), token); + if (token.isCancellationRequested) { + return; + } + + if (!reply) { + return; + } + + const markdownContents = new MarkdownString('', { supportThemeIcons: true, supportHtml: true, isTrusted: false }); + const response = this._instantiationService.createInstance(ReplyResponse, reply, markdownContents, this._activeSession.textModelN.uri, this._activeSession.textModelN.getAlternativeVersionId(), [], request.requestId); + const followups = await this._activeSession.provider.provideFollowups(this._activeSession.session, response.raw, token); + if (followups && this._widget) { + const widget = this._widget; + widget.inlineChatWidget.updateFollowUps(followups, async followup => { + if (followup.kind === 'reply') { + widget.inlineChatWidget.value = followup.message; + this.acceptInput(); + } else { + await this.acceptSession(); + this._commandService.executeCommand(followup.commandId, ...(followup.args ?? [])); + } + }); + } + } + private async _makeChanges(edits: TextEdit[], opts: ProgressingEditsOptions | undefined) { assertType(this._activeSession); assertType(this._strategy); From ee47ee25bb67793e707129ed258b219419152216 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 12:18:38 -0600 Subject: [PATCH 1269/1897] get accept action to show up, add view in chat action --- .../inlineChat/browser/inlineChatWidget.ts | 2 +- .../chat/browser/terminalChat.ts | 1 + .../chat/browser/terminalChatActions.ts | 29 ++++++++++++- .../chat/browser/terminalChatController.ts | 41 ++++++++++++++----- .../chat/browser/terminalChatWidget.ts | 1 + 5 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index ca909ba98e0..537c7b8951f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -388,7 +388,7 @@ export class InlineChatWidget { buttonConfigProvider: action => { if (action.id === ACTION_REGENERATE_RESPONSE) { return { showIcon: true, showLabel: false, isSecondary: true }; - } else if (action.id === ACTION_VIEW_IN_CHAT || action.id === ACTION_ACCEPT_CHANGES) { + } else if ([ACTION_VIEW_IN_CHAT, ACTION_ACCEPT_CHANGES, 'workbench.action.terminal.chat.acceptCommand', 'workbench.action.terminal.chat.viewInChat'].includes(action.id)) { return { isSecondary: false }; } else { return { isSecondary: true }; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index 882baf8c26b..ddea1c2a53b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -14,6 +14,7 @@ export const enum TerminalChatCommandId { FeedbackUnhelpful = 'workbench.action.terminal.chat.feedbackUnhelpful', FeedbackReportIssue = 'workbench.action.terminal.chat.feedbackReportIssue', AcceptCommand = 'workbench.action.terminal.chat.acceptCommand', + ViewInChat = 'workbench.action.terminal.chat.viewInChat', } export const MENU_TERMINAL_CHAT_INPUT = MenuId.for('terminalChatInput'); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index d0205008d74..d6ba18a272e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -91,7 +91,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 0, - when: CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), + when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), TerminalContextKeys.chatRequestActive.negate()), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -102,6 +102,32 @@ registerActiveXtermAction({ } }); +registerActiveXtermAction({ + id: TerminalChatCommandId.ViewInChat, + title: localize2('viewInChat', 'View in Chat'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + TerminalContextKeys.chatRequestActive.negate(), + TerminalContextKeys.chatAgentRegistered, + CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages) + ), + icon: Codicon.commentDiscussion, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_STATUS, + group: '0_main', + order: 1, + when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages), TerminalContextKeys.chatRequestActive.negate()), + }, + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.viewInChat(); + } +}); + registerActiveXtermAction({ id: TerminalChatCommandId.MakeRequest, title: localize2('makeChatRequest', 'Make Chat Request'), @@ -222,3 +248,4 @@ registerActiveXtermAction({ // TODO: Impl } }); + diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 36db8ad2ab9..da56f4d4041 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -14,13 +14,13 @@ import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/te import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { generateUuid } from 'vs/base/common/uuid'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { marked } from 'vs/base/common/marked/marked'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; +import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { Emitter, Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; @@ -62,6 +62,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store); readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store); + private _lastInput: string | undefined; + private _lastResponseContent: string | undefined; + constructor( private readonly _instance: ITerminalInstance, processManager: ITerminalProcessManager, @@ -72,6 +75,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, + @IChatService private readonly _chatService: IChatService, + @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService ) { super(); if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { @@ -147,18 +152,22 @@ export class TerminalChatController extends Disposable implements ITerminalContr } async acceptInput(): Promise { - let message = ''; + this._lastInput = this._chatWidget?.rawValue?.input(); + if (!this._lastInput) { + return; + } this._chatAccessibilityService.acceptRequest(); this._ctxHasActiveRequest.set(true); const cancellationToken = this._cancellationTokenSource.token; const agentId = 'terminal'; + let responseContent = ''; const progressCallback = (progress: IChatProgress) => { if (cancellationToken.isCancellationRequested) { return; } if (progress.kind === 'content' || progress.kind === 'markdownContent') { - message += progress.content; + responseContent += progress.content; } this._chatWidget?.rawValue?.updateProgress(progress); }; @@ -167,11 +176,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr sessionId: generateUuid(), requestId, agentId, - message: this._chatWidget?.rawValue?.input() || '', + message: this._lastInput, variables: { variables: [] }, }; - this._chatWidget?.rawValue?.setValue(); - try { const task = this._chatAgentService.invokeAgent(agentId, requestProps, progressCallback, [], cancellationToken); this._chatWidget?.rawValue?.inlineChatWidget.updateChatMessage(undefined); @@ -188,7 +195,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(''); this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); } - const firstCodeBlockContent = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw; + this._lastResponseContent = responseContent; + const firstCodeBlockContent = marked.lexer(responseContent).filter(token => token.type === 'code')?.[0]?.raw; const regex = /```(?\w+)\n(?[\s\S]*?)```/g; const match = regex.exec(firstCodeBlockContent); const codeBlock = match?.groups?.content; @@ -200,12 +208,12 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (codeBlock) { // TODO: check the SR experience this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._accessibilityRequestId, shellType); + this._ctxLastResponseType.set(InlineChatResponseTypes.Empty); } else { - this._chatWidget?.rawValue?.renderMessage(message, this._accessibilityRequestId, requestId); + this._chatWidget?.rawValue?.renderMessage(responseContent, this._accessibilityRequestId, requestId); this._ctxLastResponseType.set(InlineChatResponseTypes.OnlyMessages); } - this._ctxHasActiveRequest.set(false); - this._chatWidget?.rawValue?.updateProgress(); + this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); this._messages.fire(Message.ACCEPT_INPUT); } @@ -239,6 +247,17 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.reveal(); } + async viewInChat(): Promise { + if (!this._lastInput || !this._lastResponseContent) { + return; + } + const widget = await this._chatWidgetService.revealViewForProvider('copilot'); + if (widget && widget.viewModel) { + this._chatService.addCompleteRequest(widget.viewModel.sessionId, this._lastInput, undefined, { message: this._lastResponseContent }); + widget.focusLastMessage(); + } + } + override dispose() { super.dispose(); this._chatWidget?.rawValue?.dispose(); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 402f8638558..b8c73ef1ffb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -163,6 +163,7 @@ export class TerminalChatWidget extends Disposable { } renderMessage(message: string, accessibilityRequestId: number, requestId: string): void { + this._responseElement.classList.add('hide'); this._inlineChatWidget.updateChatMessage({ message: new MarkdownString(message), requestId, providerId: 'terminal' }); this._chatAccessibilityService.acceptResponse(message, accessibilityRequestId); } From bfc4df0453af87e35090ddb39e1a992c3fcf1eaa Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 12:24:03 -0600 Subject: [PATCH 1270/1897] add show/hide commands for editor --- .../chat/browser/terminalChatWidget.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index b8c73ef1ffb..e522765f213 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -91,7 +91,7 @@ export class TerminalChatWidget extends Disposable { } renderTerminalCommand(codeBlock: string, requestId: number, shellType?: string): void { this._chatAccessibilityService.acceptResponse(codeBlock, requestId); - this._responseElement.classList.remove('hide'); + this.showTerminalCommandEditor(); if (!this._responseWidget) { this._responseWidget = this._register(this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, { padding: { top: 2, bottom: 2 }, @@ -163,7 +163,7 @@ export class TerminalChatWidget extends Disposable { } renderMessage(message: string, accessibilityRequestId: number, requestId: string): void { - this._responseElement.classList.add('hide'); + this.hideTerminalCommandEditor(); this._inlineChatWidget.updateChatMessage({ message: new MarkdownString(message), requestId, providerId: 'terminal' }); this._chatAccessibilityService.acceptResponse(message, accessibilityRequestId); } @@ -183,7 +183,7 @@ export class TerminalChatWidget extends Disposable { this._inlineChatWidget.focus(); } hide(): void { - this._responseElement?.classList.add('hide'); + this.hideTerminalCommandEditor(); this._widgetContainer.classList.add('hide'); this._inlineChatWidget.value = ''; this._responseWidget?.setValue(''); @@ -207,7 +207,7 @@ export class TerminalChatWidget extends Disposable { setValue(value?: string) { this._inlineChatWidget.value = value ?? ''; if (!value) { - this._responseElement?.classList.add('hide'); + this.hideTerminalCommandEditor(); } } acceptCommand(): void { @@ -224,4 +224,10 @@ export class TerminalChatWidget extends Disposable { public get focusTracker(): IFocusTracker { return this._focusTracker; } + hideTerminalCommandEditor(): void { + this._responseElement.classList.add('hide'); + } + showTerminalCommandEditor(): void { + this._responseElement.classList.remove('hide'); + } } From 2b7e5e52d276a902db01ee05e7c9e157e6a0a265 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 12:36:27 -0600 Subject: [PATCH 1271/1897] rename and reorder things in terminalChatWidget --- .../chat/browser/terminalChatWidget.ts | 108 +++++++++--------- 1 file changed, 56 insertions(+), 52 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index e522765f213..49eef7120fa 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -24,21 +24,24 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; export class TerminalChatWidget extends Disposable { - private readonly _scopedInstantiationService: IInstantiationService; - private readonly _widgetContainer: HTMLElement; - private readonly _ctxChatWidgetFocused: IContextKey; - private readonly _ctxChatWidgetVisible: IContextKey; - private readonly _ctxResponseEditorFocused!: IContextKey; + + private readonly _container: HTMLElement; private readonly _inlineChatWidget: InlineChatWidget; - private readonly _responseElement: HTMLElement; - private readonly _focusTracker: IFocusTracker; - private _responseWidget: CodeEditorWidget | undefined; - public get inlineChatWidget(): InlineChatWidget { return this._inlineChatWidget; } + private readonly _terminalCommandWidgetContainer: HTMLElement; + private _terminalCommandWidget: CodeEditorWidget | undefined; + + private readonly _focusTracker: IFocusTracker; + + private readonly _scopedInstantiationService: IInstantiationService; + private readonly _focusedContextKey: IContextKey; + private readonly _visibleContextKey: IContextKey; + private readonly _responseEditorFocusedContextKey!: IContextKey; + constructor( - private readonly _container: HTMLElement, + terminalElement: HTMLElement, private readonly _instance: ITerminalInstance, @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @@ -47,19 +50,19 @@ export class TerminalChatWidget extends Disposable { @IModelService private readonly _modelService: IModelService ) { super(); - const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); + const scopedContextKeyService = this._register(this._contextKeyService.createScoped(terminalElement)); this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); - this._ctxChatWidgetFocused = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); - this._ctxChatWidgetVisible = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); - this._ctxResponseEditorFocused = TerminalContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); + this._focusedContextKey = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); + this._visibleContextKey = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); + this._responseEditorFocusedContextKey = TerminalContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); - this._widgetContainer = document.createElement('div'); - this._widgetContainer.classList.add('terminal-inline-chat'); - this._container.appendChild(this._widgetContainer); + this._container = document.createElement('div'); + this._container.classList.add('terminal-inline-chat'); + terminalElement.appendChild(this._container); - this._responseElement = document.createElement('div'); - this._responseElement.classList.add('terminal-inline-chat-response'); - this._widgetContainer.prepend(this._responseElement); + this._terminalCommandWidgetContainer = document.createElement('div'); + this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); + this._container.prepend(this._terminalCommandWidgetContainer); // The inline chat widget requires a parent editor that it bases the diff view on, since the // terminal doesn't use that feature we can just pass in an unattached editor instance. @@ -85,15 +88,16 @@ export class TerminalChatWidget extends Disposable { ); this._inlineChatWidget.placeholder = localize('default.placeholder', "Ask how to do something in the terminal"); this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated commands may be incorrect")); - this._widgetContainer.appendChild(this._inlineChatWidget.domNode); + this._container.appendChild(this._inlineChatWidget.domNode); - this._focusTracker = this._register(trackFocus(this._widgetContainer)); + this._focusTracker = this._register(trackFocus(this._container)); } - renderTerminalCommand(codeBlock: string, requestId: number, shellType?: string): void { - this._chatAccessibilityService.acceptResponse(codeBlock, requestId); - this.showTerminalCommandEditor(); - if (!this._responseWidget) { - this._responseWidget = this._register(this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, { + + renderTerminalCommand(command: string, requestId: number, shellType?: string): void { + this._chatAccessibilityService.acceptResponse(command, requestId); + this.showTerminalCommandWidget(); + if (!this._terminalCommandWidget) { + this._terminalCommandWidget = this._register(this._scopedInstantiationService.createInstance(CodeEditorWidget, this._terminalCommandWidgetContainer, { padding: { top: 2, bottom: 2 }, overviewRulerLanes: 0, glyphMargin: false, @@ -128,21 +132,21 @@ export class TerminalChatWidget extends Disposable { showStatusBar: false, }, }, { isSimpleWidget: true })); - this._register(this._responseWidget.onDidFocusEditorText(() => this._ctxResponseEditorFocused.set(true))); - this._register(this._responseWidget.onDidBlurEditorText(() => this._ctxResponseEditorFocused.set(false))); - this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: codeBlock })).then((model) => { - if (!model || !this._responseWidget) { + this._register(this._terminalCommandWidget.onDidFocusEditorText(() => this._responseEditorFocusedContextKey.set(true))); + this._register(this._terminalCommandWidget.onDidBlurEditorText(() => this._responseEditorFocusedContextKey.set(false))); + this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: command })).then((model) => { + if (!model || !this._terminalCommandWidget) { return; } - this._responseWidget.layout(new Dimension(400, 0)); - this._responseWidget.setModel(model); - const height = this._responseWidget.getContentHeight(); - this._responseWidget.layout(new Dimension(400, height)); + this._terminalCommandWidget.layout(new Dimension(400, 0)); + this._terminalCommandWidget.setModel(model); + const height = this._terminalCommandWidget.getContentHeight(); + this._terminalCommandWidget.layout(new Dimension(400, height)); }); } else { - this._responseWidget.setValue(codeBlock); + this._terminalCommandWidget.setValue(command); } - this._responseWidget.getModel()?.setLanguage(this._getLanguageFromShell(shellType)); + this._terminalCommandWidget.getModel()?.setLanguage(this._getLanguageFromShell(shellType)); } private _getLanguageFromShell(shell?: string): string { @@ -163,7 +167,7 @@ export class TerminalChatWidget extends Disposable { } renderMessage(message: string, accessibilityRequestId: number, requestId: string): void { - this.hideTerminalCommandEditor(); + this.hideTerminalCommandWidget(); this._inlineChatWidget.updateChatMessage({ message: new MarkdownString(message), requestId, providerId: 'terminal' }); this._chatAccessibilityService.acceptResponse(message, accessibilityRequestId); } @@ -177,18 +181,18 @@ export class TerminalChatWidget extends Disposable { } reveal(): void { this._inlineChatWidget.layout(new Dimension(400, 150)); - this._widgetContainer.classList.remove('hide'); - this._ctxChatWidgetFocused.set(true); - this._ctxChatWidgetVisible.set(true); + this._container.classList.remove('hide'); + this._focusedContextKey.set(true); + this._visibleContextKey.set(true); this._inlineChatWidget.focus(); } hide(): void { - this.hideTerminalCommandEditor(); - this._widgetContainer.classList.add('hide'); + this.hideTerminalCommandWidget(); + this._container.classList.add('hide'); this._inlineChatWidget.value = ''; - this._responseWidget?.setValue(''); - this._ctxChatWidgetFocused.set(false); - this._ctxChatWidgetVisible.set(false); + this._terminalCommandWidget?.setValue(''); + this._focusedContextKey.set(false); + this._visibleContextKey.set(false); this._instance.focus(); } cancel(): void { @@ -207,11 +211,11 @@ export class TerminalChatWidget extends Disposable { setValue(value?: string) { this._inlineChatWidget.value = value ?? ''; if (!value) { - this.hideTerminalCommandEditor(); + this.hideTerminalCommandWidget(); } } acceptCommand(): void { - const value = this._responseWidget?.getValue(); + const value = this._terminalCommandWidget?.getValue(); if (!value) { return; } @@ -224,10 +228,10 @@ export class TerminalChatWidget extends Disposable { public get focusTracker(): IFocusTracker { return this._focusTracker; } - hideTerminalCommandEditor(): void { - this._responseElement.classList.add('hide'); + hideTerminalCommandWidget(): void { + this._terminalCommandWidgetContainer.classList.add('hide'); } - showTerminalCommandEditor(): void { - this._responseElement.classList.remove('hide'); + showTerminalCommandWidget(): void { + this._terminalCommandWidgetContainer.classList.remove('hide'); } } From 0353033a81166dcbcaf782bce151bb97174549b4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 14 Feb 2024 19:43:44 +0100 Subject: [PATCH 1272/1897] api - refine `agent` property of turn instances (#205231) https://github.com/microsoft/vscode/issues/199908 --- src/vs/workbench/api/common/extHostChatAgents2.ts | 4 ++-- src/vs/workbench/api/common/extHostTypes.ts | 15 ++++----------- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 4 ++-- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 61f74812130..360fdb9898a 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -235,11 +235,11 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { { ...ehResult, metadata: undefined }; // REQUEST turn - res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', agentId: h.request.agentId })); + res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', agent: h.request.agentId, agentId: h.request.agentId })); // RESPONSE turn const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))); - res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, { extensionId: '', agentId: h.request.agentId })); + res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, { extensionId: '', agent: h.request.agentId, agentId: h.request.agentId })); } return res; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 41b31a4901d..6d563a0e6a9 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4257,28 +4257,21 @@ export class ChatResponseReferencePart { export class ChatAgentRequestTurn implements vscode.ChatAgentRequestTurn { - agentId: string; constructor( readonly prompt: string, readonly command: string | undefined, readonly variables: vscode.ChatAgentResolvedVariable[], - readonly agent: { extensionId: string; agentId: string }, - ) { - this.agentId = agent.agentId; - } + readonly agent: { extensionId: string; agent: string; agentId: string }, + ) { } } export class ChatAgentResponseTurn implements vscode.ChatAgentResponseTurn { - agentId: string; - constructor( readonly response: ReadonlyArray, readonly result: vscode.ChatAgentResult2, - readonly agent: { extensionId: string; agentId: string } - ) { - this.agentId = agent.agentId; - } + readonly agent: { extensionId: string; agent: string; agentId: string } + ) { } } export class LanguageModelSystemMessage { diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 1d0caf6a5a4..82cdc7615f5 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -22,7 +22,7 @@ declare module 'vscode' { * The ID of the chat agent to which this request was directed. */ // TODO@API NAME: agentId shouldbe agentName or just agent (because it is ChatAgent#name) - readonly agent: { readonly extensionId: string; readonly agentId: string }; + readonly agent: { readonly extensionId: string; readonly agent: string; readonly agentId: string }; /** * The name of the {@link ChatAgentCommand command} that was selected for this request. @@ -51,7 +51,7 @@ declare module 'vscode' { readonly result: ChatAgentResult2; // TODO@API NAME: agentId shouldbe agentName or just agent (because it is ChatAgent#name) - readonly agent: { readonly extensionId: string; readonly agentId: string }; + readonly agent: { readonly extensionId: string; readonly agent: string; readonly agentId: string }; private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: { extensionId: string; agentId: string }); } From e6a59ec582fe27893f4fffebf884b9835d6e7e70 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 14 Feb 2024 10:46:04 -0800 Subject: [PATCH 1273/1897] Couple LanguageModel auth fixes (#205233) 1. use the accountLabel 2. always have our detail, and then add on a justification if provided --- .../workbench/api/browser/mainThreadChatProvider.ts | 13 +++++-------- src/vs/workbench/api/common/extHostChatProvider.ts | 11 +++++------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index d8a197db254..a3637ad0000 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -120,15 +120,16 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { return Disposable.None; } + const accountLabel = auth.accountLabel ?? localize('languageModelsAccountId', 'Language Models'); const disposables = new DisposableStore(); - this._authenticationService.registerAuthenticationProvider(authProviderId, new LanguageModelAccessAuthProvider(authProviderId, auth.providerLabel, auth.accountLabel)); + this._authenticationService.registerAuthenticationProvider(authProviderId, new LanguageModelAccessAuthProvider(authProviderId, auth.providerLabel, accountLabel)); disposables.add(toDisposable(() => { this._authenticationService.unregisterAuthenticationProvider(authProviderId); })); disposables.add(this._authenticationService.onDidChangeSessions(async (e) => { if (e.providerId === authProviderId) { if (e.event.removed?.length) { - const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, authProviderId); + const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, accountLabel); const extensionsToUpdateAccess = []; for (const allowed of allowedExtensions) { const from = await this._extensionService.getExtension(allowed.id); @@ -146,7 +147,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { } })); disposables.add(this._authenticationService.onDidChangeExtensionSessionAccess(async (e) => { - const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, authProviderId); + const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, accountLabel); const accessList = []; for (const allowedExtension of allowedExtensions) { const from = await this._extensionService.getExtension(allowedExtension.id); @@ -174,11 +175,7 @@ class LanguageModelAccessAuthProvider implements IAuthenticationProvider { private _session: AuthenticationSession | undefined; - constructor( - readonly id: string, - readonly label: string, - private readonly _accountLabel: string = localize('languageModelsAccountId', 'Language Models') - ) { } + constructor(readonly id: string, readonly label: string, private readonly _accountLabel: string) { } async getSessions(scopes?: string[] | undefined): Promise { // If there are no scopes and no session that means no extension has requested a session yet diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index 5e725927e9b..3f35da68ace 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -266,17 +266,16 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { } // BIG HACK: Using AuthenticationProviders to check access to Language Models - private async _getAuthAccess(from: IExtensionDescription, to: { identifier: ExtensionIdentifier; displayName: string }, detail?: string): Promise { + private async _getAuthAccess(from: IExtensionDescription, to: { identifier: ExtensionIdentifier; displayName: string }, justification?: string): Promise { // This needs to be done in both MainThread & ExtHost ChatProvider const providerId = INTERNAL_AUTH_PROVIDER_PREFIX + to.identifier.value; const session = await this._extHostAuthentication.getSession(from, providerId, [], { silent: true }); if (!session) { try { - await this._extHostAuthentication.getSession(from, providerId, [], { - forceNewSession: { - detail: detail ?? localize('chatAccess', "To allow access to the language models provided by {0}", to.displayName), - } - }); + const detail = justification + ? localize('chatAccessWithJustification', "To allow access to the language models provided by {0}. Justification:\n\n{1}", to.displayName, justification) + : localize('chatAccess', "To allow access to the language models provided by {0}", to.displayName); + await this._extHostAuthentication.getSession(from, providerId, [], { forceNewSession: { detail } }); } catch (err) { throw new Error('Access to language models has not been granted'); } From 1565341d4496a71da7860467d3ea32a72103dbab Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 12:50:49 -0600 Subject: [PATCH 1274/1897] reset everything on hide of widget --- .../terminalContrib/chat/browser/terminalChatController.ts | 1 - .../terminalContrib/chat/browser/terminalChatWidget.ts | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index da56f4d4041..a5d73cd459a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -169,7 +169,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (progress.kind === 'content' || progress.kind === 'markdownContent') { responseContent += progress.content; } - this._chatWidget?.rawValue?.updateProgress(progress); }; const requestId = generateUuid(); const requestProps: IChatAgentRequest = { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 49eef7120fa..a485dd40c4f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -191,6 +191,10 @@ export class TerminalChatWidget extends Disposable { this._container.classList.add('hide'); this._inlineChatWidget.value = ''; this._terminalCommandWidget?.setValue(''); + this._inlineChatWidget.updateChatMessage(undefined); + this._inlineChatWidget.updateFollowUps(undefined); + this._inlineChatWidget.updateProgress(false); + this._inlineChatWidget.updateToolbar(false); this._focusedContextKey.set(false); this._visibleContextKey.set(false); this._instance.focus(); From bbd14fc377163315e92a7aadc29581219828b175 Mon Sep 17 00:00:00 2001 From: John Murray Date: Wed, 14 Feb 2024 19:03:53 +0000 Subject: [PATCH 1275/1897] Make Collapse/Expand All button of Search tree initialize correctly (fix #204316) (#205235) --- src/vs/workbench/contrib/search/browser/searchView.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 654ac8d1ed2..649a025c5c3 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -901,6 +901,7 @@ export class SearchView extends ViewPane { const updateHasSomeCollapsible = () => this.toggleCollapseStateDelayer.trigger(() => this.hasSomeCollapsibleResultKey.set(this.hasSomeCollapsible())); updateHasSomeCollapsible(); this._register(this.tree.onDidChangeCollapseState(() => updateHasSomeCollapsible())); + this._register(this.tree.onDidChangeModel(() => updateHasSomeCollapsible())); this._register(Event.debounce(this.tree.onDidOpen, (last, event) => event, DEBOUNCE_DELAY, true)(options => { if (options.element instanceof Match) { From 55aca1a766b3223d5bd2fa164cbb17ff54615b9f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 13:06:47 -0600 Subject: [PATCH 1276/1897] more clean up --- .../terminal/common/terminalContextKey.ts | 1 + .../chat/browser/terminalChatController.ts | 46 ++++++++++--------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 8ea1469a4b1..a7805bfac46 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -45,6 +45,7 @@ export const enum TerminalContextKeyStrings { ChatInputHasText = 'terminalChatInputHasText', ChatAgentRegistered = 'terminalChatAgentRegistered', ChatResponseEditorFocused = 'terminalChatResponseEditorFocused', + ChatLastResponseType = 'terminalChatLastResponseType' } export namespace TerminalContextKeys { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index a5d73cd459a..5c105fee353 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -21,9 +21,9 @@ import { marked } from 'vs/base/common/marked/marked'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { Emitter, Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; +import { CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; const enum Message { NONE = 0, @@ -48,22 +48,25 @@ export class TerminalChatController extends Disposable implements ITerminalContr */ static activeChatWidget?: TerminalChatController; private _chatWidget: Lazy | undefined; - private _accessibilityRequestId: number = 0; get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } - private readonly _ctxHasActiveRequest!: IContextKey; - private readonly _ctxHasTerminalAgent!: IContextKey; - private readonly _ctxLastResponseType!: IContextKey; + private readonly _requestActiveContextKey!: IContextKey; + private readonly _terminalAgentRegisteredContextKey!: IContextKey; + private readonly _lastResponseTypeContextKey!: IContextKey; private _cancellationTokenSource!: CancellationTokenSource; + private _accessibilityRequestId: number = 0; + private _messages = this._store.add(new Emitter()); + private _lastInput: string | undefined; + private _lastResponseContent: string | undefined; + readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store); readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store); - private _lastInput: string | undefined; - private _lastResponseContent: string | undefined; + private _terminalAgentId = 'terminal'; constructor( private readonly _instance: ITerminalInstance, @@ -82,18 +85,18 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { return; } - this._ctxHasActiveRequest = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); - this._ctxHasTerminalAgent = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); - this._ctxLastResponseType = CTX_INLINE_CHAT_RESPONSE_TYPES.bindTo(this._contextKeyService); + this._requestActiveContextKey = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); + this._terminalAgentRegisteredContextKey = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); + this._lastResponseTypeContextKey = CTX_INLINE_CHAT_RESPONSE_TYPES.bindTo(this._contextKeyService); - if (!this._chatAgentService.hasAgent('terminal')) { + if (!this._chatAgentService.hasAgent(this._terminalAgentId)) { this._register(this._chatAgentService.onDidChangeAgents(() => { - if (this._chatAgentService.getAgent('terminal')) { - this._ctxHasTerminalAgent.set(true); + if (this._chatAgentService.getAgent(this._terminalAgentId)) { + this._terminalAgentRegisteredContextKey.set(true); } })); } else { - this._ctxHasTerminalAgent.set(true); + this._terminalAgentRegisteredContextKey.set(true); } this._cancellationTokenSource = new CancellationTokenSource(); } @@ -157,9 +160,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr return; } this._chatAccessibilityService.acceptRequest(); - this._ctxHasActiveRequest.set(true); + this._requestActiveContextKey.set(true); const cancellationToken = this._cancellationTokenSource.token; - const agentId = 'terminal'; let responseContent = ''; const progressCallback = (progress: IChatProgress) => { if (cancellationToken.isCancellationRequested) { @@ -174,12 +176,13 @@ export class TerminalChatController extends Disposable implements ITerminalContr const requestProps: IChatAgentRequest = { sessionId: generateUuid(), requestId, - agentId, + agentId: this._terminalAgentId, message: this._lastInput, + // TODO: ? variables: { variables: [] }, }; try { - const task = this._chatAgentService.invokeAgent(agentId, requestProps, progressCallback, [], cancellationToken); + const task = this._chatAgentService.invokeAgent(this._terminalAgentId, requestProps, progressCallback, [], cancellationToken); this._chatWidget?.rawValue?.inlineChatWidget.updateChatMessage(undefined); this._chatWidget?.rawValue?.inlineChatWidget.updateFollowUps(undefined); this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(true); @@ -189,7 +192,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } catch (e) { } finally { - this._ctxHasActiveRequest.set(false); + this._requestActiveContextKey.set(false); this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(false); this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(''); this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); @@ -205,12 +208,11 @@ export class TerminalChatController extends Disposable implements ITerminalContr return; } if (codeBlock) { - // TODO: check the SR experience this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._accessibilityRequestId, shellType); - this._ctxLastResponseType.set(InlineChatResponseTypes.Empty); + this._lastResponseTypeContextKey.set(InlineChatResponseTypes.Empty); } else { this._chatWidget?.rawValue?.renderMessage(responseContent, this._accessibilityRequestId, requestId); - this._ctxLastResponseType.set(InlineChatResponseTypes.OnlyMessages); + this._lastResponseTypeContextKey.set(InlineChatResponseTypes.OnlyMessages); } this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); this._messages.fire(Message.ACCEPT_INPUT); From 57857d6546a713f47a0639a55db18ea4224edf54 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 13:22:39 -0600 Subject: [PATCH 1277/1897] rm unused --- src/vs/workbench/contrib/terminal/common/terminalContextKey.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index a7805bfac46..8ea1469a4b1 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -45,7 +45,6 @@ export const enum TerminalContextKeyStrings { ChatInputHasText = 'terminalChatInputHasText', ChatAgentRegistered = 'terminalChatAgentRegistered', ChatResponseEditorFocused = 'terminalChatResponseEditorFocused', - ChatLastResponseType = 'terminalChatLastResponseType' } export namespace TerminalContextKeys { From a9b4e6fe4446bd2e90c5507e0d32f08e196b292c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 13:43:46 -0600 Subject: [PATCH 1278/1897] add accessible view --- .../browser/accessibilityConfiguration.ts | 8 +++- .../terminal/common/terminalContextKey.ts | 3 ++ .../browser/terminal.chat.contribution.ts | 6 +++ .../browser/terminalChatAccessibleView.ts | 41 +++++++++++++++++++ .../chat/browser/terminalChatController.ts | 1 + 5 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index afd920ac1f9..a5d30356fed 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -52,7 +52,8 @@ export const enum AccessibilityVerbositySettingId { Hover = 'accessibility.verbosity.hover', Notification = 'accessibility.verbosity.notification', EmptyEditorHint = 'accessibility.verbosity.emptyEditorHint', - Comments = 'accessibility.verbosity.comments' + Comments = 'accessibility.verbosity.comments', + TerminalInlineChat = 'accessibility.verbosity.terminalInlineChat' } export const enum AccessibleViewProviderId { @@ -62,6 +63,7 @@ export const enum AccessibleViewProviderId { Chat = 'panelChat', InlineChat = 'inlineChat', InlineCompletions = 'inlineCompletions', + TerminalInlineChat = 'terminalInlineChat', KeybindingsEditor = 'keybindingsEditor', Notebook = 'notebook', Editor = 'editor', @@ -168,6 +170,10 @@ const configuration: IConfigurationNode = { description: localize('verbosity.comments', 'Provide information about actions that can be taken in the comment widget or in a file which contains comments.'), ...baseVerbosityProperty }, + [AccessibilityVerbositySettingId.TerminalInlineChat]: { + description: localize('verbosity.terminalInlineChat', 'Provide information about actions that can be taken in the terminal inline chat widget.'), + ...baseVerbosityProperty + }, [AccessibilityAlertSettingId.Save]: { 'markdownDescription': localize('announcement.save', "Indicates when a file is saved. Also see {0}.", '`#audioCues.save#`'), 'enum': ['userGesture', 'always', 'never'], diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 8ea1469a4b1..5ac4cdca5ac 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -183,4 +183,7 @@ export namespace TerminalContextKeys { /** Whether the chat response editor is focused */ export const chatResponseEditorFocused = new RawContextKey(TerminalContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseEditorFocusedContextKey', "Whether the chat response editor is focused.")); + + /** Whether the chat response editor is focused */ + export const chatResponseMessageFocused = new RawContextKey(TerminalContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseMessageFocusedContextKey', "Whether the chat response message is focused.")); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index a3a7b55757a..eebaa8b3b8a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -5,7 +5,13 @@ import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; registerTerminalContribution(TerminalChatController.ID, TerminalChatController, false); import 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { TerminalInlineChatAccessibleViewContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView'; +const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(TerminalInlineChatAccessibleViewContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts new file mode 100644 index 00000000000..949a2d8cc51 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; +import { CTX_INLINE_CHAT_RESPONSE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; + +export class TerminalInlineChatAccessibleViewContribution extends Disposable { + static ID: 'terminalInlineChatAccessibleViewContribution'; + constructor() { + super(); + this._register(AccessibleViewAction.addImplementation(105, 'terminalInlineChat', accessor => { + const accessibleViewService = accessor.get(IAccessibleViewService); + const terminalService = accessor.get(ITerminalService); + const controller: TerminalChatController | undefined = terminalService.activeInstance?.getContribution(TerminalChatController.ID) ?? undefined; + if (!controller?.lastResponseContent) { + return false; + } + const responseContent = controller.lastResponseContent; + accessibleViewService.show({ + id: AccessibleViewProviderId.TerminalInlineChat, + verbositySettingKey: AccessibilityVerbositySettingId.TerminalInlineChat, + provideContent(): string { return responseContent; }, + onClose() { + controller.focus(); + }, + + options: { type: AccessibleViewType.View } + }); + return true; + }, ContextKeyExpr.and(TerminalContextKeys.chatFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); + } +} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 5c105fee353..515d7b895e8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -62,6 +62,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr private _lastInput: string | undefined; private _lastResponseContent: string | undefined; + get lastResponseContent(): string | undefined { return this._lastResponseContent; } readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store); readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store); From e9352a77392aa47f53783c0939579779b68df3e1 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 14 Feb 2024 20:58:17 +0000 Subject: [PATCH 1279/1897] Enable progress messages and references from variable resolvers (#205174) * Enable progress messages and references from variable resolvers For #204539 * Fixes --- .../api/browser/mainThreadChatVariables.ts | 19 +++- .../workbench/api/common/extHost.protocol.ts | 8 +- .../api/common/extHostChatVariables.ts | 93 ++++++++++++++++--- .../contrib/chat/browser/chatFollowups.ts | 5 + .../contrib/chat/browser/chatVariables.ts | 6 +- .../browser/contrib/chatHistoryVariables.ts | 2 +- .../contrib/chat/common/chatServiceImpl.ts | 2 +- .../contrib/chat/common/chatVariables.ts | 9 +- .../chat/test/browser/chatVariables.test.ts | 2 +- .../chat/test/common/mockChatVariables.ts | 4 +- .../vscode.proposed.chatAgents2Additions.d.ts | 41 ++++++++ 11 files changed, 163 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatVariables.ts b/src/vs/workbench/api/browser/mainThreadChatVariables.ts index 1f5e98ff8ac..fbdc3060424 100644 --- a/src/vs/workbench/api/browser/mainThreadChatVariables.ts +++ b/src/vs/workbench/api/browser/mainThreadChatVariables.ts @@ -5,8 +5,8 @@ import { DisposableMap } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; -import { ExtHostChatVariablesShape, ExtHostContext, MainContext, MainThreadChatVariablesShape } from 'vs/workbench/api/common/extHost.protocol'; -import { IChatRequestVariableValue, IChatVariableData, IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { ExtHostChatVariablesShape, ExtHostContext, IChatVariableResolverProgressDto, MainContext, MainThreadChatVariablesShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IChatRequestVariableValue, IChatVariableData, IChatVariableResolverProgress, IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; @extHostNamedCustomer(MainContext.MainThreadChatVariables) @@ -14,6 +14,7 @@ export class MainThreadChatVariables implements MainThreadChatVariablesShape { private readonly _proxy: ExtHostChatVariablesShape; private readonly _variables = new DisposableMap(); + private readonly _pendingProgress = new Map void>(); constructor( extHostContext: IExtHostContext, @@ -27,12 +28,22 @@ export class MainThreadChatVariables implements MainThreadChatVariablesShape { } $registerVariable(handle: number, data: IChatVariableData): void { - const registration = this._chatVariablesService.registerVariable(data, async (messageText, _arg, _model, token) => { - return revive(await this._proxy.$resolveVariable(handle, messageText, token)); + const registration = this._chatVariablesService.registerVariable(data, async (messageText, _arg, model, progress, token) => { + const varRequestId = `${model.sessionId}-${handle}`; + this._pendingProgress.set(varRequestId, progress); + const result = revive(await this._proxy.$resolveVariable(handle, varRequestId, messageText, token)); + + this._pendingProgress.delete(varRequestId); + return result; }); this._variables.set(handle, registration); } + async $handleProgressChunk(requestId: string, progress: IChatVariableResolverProgressDto): Promise { + const revivedProgress = revive(progress); + this._pendingProgress.get(requestId)?.(revivedProgress as IChatVariableResolverProgress); + } + $unregisterVariable(handle: number): void { this._variables.deleteAndDispose(handle); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b657fa82840..05ae4f752d2 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -54,7 +54,7 @@ import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentRes import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; import { IChatDynamicRequest, IChatProgress, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; -import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { IChatRequestVariableValue, IChatVariableData, IChatVariableResolverProgress } from 'vs/workbench/contrib/chat/common/chatVariables'; import { DebugConfigurationProviderTriggerKind, MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatFollowup, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -1231,15 +1231,19 @@ export interface ExtHostChatAgentsShape2 { $releaseSession(sessionId: string): void; } +export type IChatVariableResolverProgressDto = + | Dto; + export interface MainThreadChatVariablesShape extends IDisposable { $registerVariable(handle: number, data: IChatVariableData): void; + $handleProgressChunk(requestId: string, progress: IChatVariableResolverProgressDto): Promise; $unregisterVariable(handle: number): void; } export type IChatRequestVariableValueDto = Dto; export interface ExtHostChatVariablesShape { - $resolveVariable(handle: number, messageText: string, token: CancellationToken): Promise; + $resolveVariable(handle: number, requestId: string, messageText: string, token: CancellationToken): Promise; } export interface MainThreadInlineChatShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostChatVariables.ts b/src/vs/workbench/api/common/extHostChatVariables.ts index a151a67432f..ca3b265007b 100644 --- a/src/vs/workbench/api/common/extHostChatVariables.ts +++ b/src/vs/workbench/api/common/extHostChatVariables.ts @@ -3,35 +3,46 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type * as vscode from 'vscode'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { ExtHostChatVariablesShape, IMainContext, MainContext, MainThreadChatVariablesShape } from 'vs/workbench/api/common/extHost.protocol'; -import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; -import { ChatVariable } from 'vs/workbench/api/common/extHostTypeConverters'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtHostChatVariablesShape, IChatVariableResolverProgressDto, IMainContext, MainContext, MainThreadChatVariablesShape } from 'vs/workbench/api/common/extHost.protocol'; +import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; +import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; +import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import type * as vscode from 'vscode'; export class ExtHostChatVariables implements ExtHostChatVariablesShape { private static _idPool = 0; - private readonly _resolver = new Map(); + private readonly _resolver = new Map(); private readonly _proxy: MainThreadChatVariablesShape; constructor(mainContext: IMainContext) { this._proxy = mainContext.getProxy(MainContext.MainThreadChatVariables); } - async $resolveVariable(handle: number, messageText: string, token: CancellationToken): Promise { + async $resolveVariable(handle: number, requestId: string, messageText: string, token: CancellationToken): Promise { const item = this._resolver.get(handle); if (!item) { return undefined; } try { - const value = await item.resolver.resolve(item.data.name, { prompt: messageText }, token); - if (value) { - return value.map(ChatVariable.from); + if (item.resolver.resolve2) { + checkProposedApiEnabled(item.extension, 'chatAgents2Additions'); + const stream = new ChatVariableResolverResponseStream(requestId, this._proxy); + const value = await item.resolver.resolve2(item.data.name, { prompt: messageText }, stream.apiObject, token); + if (value) { + return value.map(typeConvert.ChatVariable.from); + } + } else { + const value = await item.resolver.resolve(item.data.name, { prompt: messageText }, token); + if (value) { + return value.map(typeConvert.ChatVariable.from); + } } } catch (err) { onUnexpectedExternalError(err); @@ -41,7 +52,7 @@ export class ExtHostChatVariables implements ExtHostChatVariablesShape { registerVariableResolver(extension: IExtensionDescription, name: string, description: string, resolver: vscode.ChatVariableResolver): IDisposable { const handle = ExtHostChatVariables._idPool++; - this._resolver.set(handle, { extension: extension.identifier, data: { name, description }, resolver: resolver }); + this._resolver.set(handle, { extension, data: { name, description }, resolver: resolver }); this._proxy.$registerVariable(handle, { name, description }); return toDisposable(() => { @@ -50,3 +61,61 @@ export class ExtHostChatVariables implements ExtHostChatVariablesShape { }); } } + +class ChatVariableResolverResponseStream { + + private _isClosed: boolean = false; + private _apiObject: vscode.ChatVariableResolverResponseStream | undefined; + + constructor( + private readonly _requestId: string, + private readonly _proxy: MainThreadChatVariablesShape, + ) { } + + close() { + this._isClosed = true; + } + + get apiObject() { + if (!this._apiObject) { + const that = this; + + function throwIfDone(source: Function | undefined) { + if (that._isClosed) { + const err = new Error('Response stream has been closed'); + Error.captureStackTrace(err, source); + throw err; + } + } + + const _report = (progress: IChatVariableResolverProgressDto) => { + this._proxy.$handleProgressChunk(this._requestId, progress); + }; + + this._apiObject = { + progress(value) { + throwIfDone(this.progress); + const part = new extHostTypes.ChatResponseProgressPart(value); + const dto = typeConvert.ChatResponseProgressPart.to(part); + _report(dto); + return this; + }, + reference(value) { + throwIfDone(this.reference); + const part = new extHostTypes.ChatResponseReferencePart(value); + const dto = typeConvert.ChatResponseReferencePart.to(part); + _report(dto); + return this; + }, + push(part) { + throwIfDone(this.push); + const dto = typeConvert.ChatResponsePart.to(part); + _report(dto as IChatVariableResolverProgressDto); + return this; + } + }; + } + + return this._apiObject; + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts index 87ab8978289..ab3733a1192 100644 --- a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts +++ b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts @@ -37,6 +37,11 @@ export class ChatFollowups extend return; } + if (!this.chatAgentService.getDefaultAgent()) { + // No default agent yet, which affects how followups are rendered, so can't render this yet + return; + } + const tooltip = 'tooltip' in followup ? followup.tooltip : undefined; const button = this._register(new Button(container, { ...this.options, supportIcons: true, title: tooltip })); if (followup.kind === 'reply') { diff --git a/src/vs/workbench/contrib/chat/browser/chatVariables.ts b/src/vs/workbench/contrib/chat/browser/chatVariables.ts index 95baa1d92da..147b267245c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/chatVariables.ts @@ -13,7 +13,7 @@ import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatDynamicVariableModel } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables'; import { IChatModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest, ChatRequestVariablePart, ChatRequestDynamicVariablePart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatVariablesService, IChatRequestVariableValue, IChatVariableData, IChatVariableResolver, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { IChatVariablesService, IChatRequestVariableValue, IChatVariableData, IChatVariableResolver, IDynamicVariable, IChatVariableResolverProgress } from 'vs/workbench/contrib/chat/common/chatVariables'; interface IChatData { data: IChatVariableData; @@ -30,7 +30,7 @@ export class ChatVariablesService implements IChatVariablesService { ) { } - async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise { + async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, progress: (part: IChatVariableResolverProgress) => void, token: CancellationToken): Promise { let resolvedVariables: { name: string; range: IOffsetRange; values: IChatRequestVariableValue[] }[] = []; const jobs: Promise[] = []; @@ -39,7 +39,7 @@ export class ChatVariablesService implements IChatVariablesService { if (part instanceof ChatRequestVariablePart) { const data = this._resolver.get(part.variableName.toLowerCase()); if (data) { - jobs.push(data.resolver(prompt.text, part.variableArg, model, token).then(values => { + jobs.push(data.resolver(prompt.text, part.variableArg, model, progress, token).then(values => { if (values?.length) { resolvedVariables[i] = { name: part.variableName, range: part.range, values }; } diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatHistoryVariables.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatHistoryVariables.ts index 5aa6815b698..5df9a406f5b 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatHistoryVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatHistoryVariables.ts @@ -15,7 +15,7 @@ class ChatHistoryVariables extends Disposable { ) { super(); - this._register(chatVariablesService.registerVariable({ name: 'response', description: '', canTakeArgument: true, hidden: true }, async (message, arg, model, token) => { + this._register(chatVariablesService.registerVariable({ name: 'response', description: '', canTakeArgument: true, hidden: true }, async (message, arg, model, progress, token) => { if (!arg) { return undefined; } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 7197f521f06..fe8185212e4 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -551,7 +551,7 @@ export class ChatService extends Disposable implements IChatService { const initVariableData: IChatRequestVariableData = { variables: [] }; request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); - const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); + const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, progressCallback, token); request.variableData = variableData; const promptTextResult = getPromptText(request.message); diff --git a/src/vs/workbench/contrib/chat/common/chatVariables.ts b/src/vs/workbench/contrib/chat/common/chatVariables.ts index f95b9da120e..859daf171e5 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariables.ts @@ -10,6 +10,7 @@ import { IRange } from 'vs/editor/common/core/range'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { IChatContentReference, IChatProgressMessage } from 'vs/workbench/contrib/chat/common/chatService'; export interface IChatVariableData { name: string; @@ -25,9 +26,13 @@ export interface IChatRequestVariableValue { description?: string; } +export type IChatVariableResolverProgress = + | IChatContentReference + | IChatProgressMessage; + export interface IChatVariableResolver { // TODO should we spec "zoom level" - (messageText: string, arg: string | undefined, model: IChatModel, token: CancellationToken): Promise; + (messageText: string, arg: string | undefined, model: IChatModel, progress: (part: IChatVariableResolverProgress) => void, token: CancellationToken): Promise; } export const IChatVariablesService = createDecorator('IChatVariablesService'); @@ -42,7 +47,7 @@ export interface IChatVariablesService { /** * Resolves all variables that occur in `prompt` */ - resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise; + resolveVariables(prompt: IParsedChatRequest, model: IChatModel, progress: (part: IChatVariableResolverProgress) => void, token: CancellationToken): Promise; } export interface IDynamicVariable { diff --git a/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts index 7b9bbde63c7..f835af908b4 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts @@ -42,7 +42,7 @@ suite('ChatVariables', function () { const resolveVariables = async (text: string) => { const result = parser.parseChatRequest('1', text); - return await service.resolveVariables(result, null!, CancellationToken.None); + return await service.resolveVariables(result, null!, () => { }, CancellationToken.None); }; { diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts b/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts index 75220254336..4e34de6d72a 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts @@ -7,7 +7,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IChatModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatVariableData, IChatVariableResolver, IChatVariablesService, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { IChatVariableData, IChatVariableResolver, IChatVariableResolverProgress, IChatVariablesService, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables'; export class MockChatVariablesService implements IChatVariablesService { _serviceBrand: undefined; @@ -27,7 +27,7 @@ export class MockChatVariablesService implements IChatVariablesService { return []; } - async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise { + async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, progress: (part: IChatVariableResolverProgress) => void, token: CancellationToken): Promise { return { variables: [] }; diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index 32eb4999065..40955904331 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -244,4 +244,45 @@ declare module 'vscode' { placeholder?: string; }; } + + export interface ChatVariableResolverResponseStream { + /** + * Push a progress part to this stream. Short-hand for + * `push(new ChatResponseProgressPart(value))`. + * + * @param value + * @returns This stream. + */ + progress(value: string): ChatVariableResolverResponseStream; + + /** + * Push a reference to this stream. Short-hand for + * `push(new ChatResponseReferencePart(value))`. + * + * *Note* that the reference is not rendered inline with the response. + * + * @param value A uri or location + * @returns This stream. + */ + reference(value: Uri | Location): ChatVariableResolverResponseStream; + + /** + * Pushes a part to this stream. + * + * @param part A response part, rendered or metadata + */ + push(part: ChatVariableResolverResponsePart): ChatVariableResolverResponseStream; + } + + export type ChatVariableResolverResponsePart = ChatResponseProgressPart | ChatResponseReferencePart; + + export interface ChatVariableResolver { + /** + * A callback to resolve the value of a chat variable. + * @param name The name of the variable. + * @param context Contextual information about this chat request. + * @param token A cancellation token. + */ + resolve2?(name: string, context: ChatVariableContext, stream: ChatVariableResolverResponseStream, token: CancellationToken): ProviderResult; + } } From d881ddbae52bec5cdc5e87351d6c3f8f7fa5c67f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 15:30:36 -0600 Subject: [PATCH 1280/1897] Add accessibility help dialog --- .../browser/accessibilityConfiguration.ts | 5 -- .../inlineChat/browser/inlineChatActions.ts | 3 +- .../terminal/common/terminalContextKey.ts | 3 - .../browser/terminal.chat.contribution.ts | 2 + .../chat/browser/terminalChat.ts | 2 +- .../browser/terminalChatAccessibilityHelp.ts | 64 +++++++++++++++++++ .../browser/terminalChatAccessibleView.ts | 3 +- .../chat/browser/terminalChatActions.ts | 14 ++-- 8 files changed, 77 insertions(+), 19 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index a5d30356fed..bba5a1f648e 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -53,7 +53,6 @@ export const enum AccessibilityVerbositySettingId { Notification = 'accessibility.verbosity.notification', EmptyEditorHint = 'accessibility.verbosity.emptyEditorHint', Comments = 'accessibility.verbosity.comments', - TerminalInlineChat = 'accessibility.verbosity.terminalInlineChat' } export const enum AccessibleViewProviderId { @@ -170,10 +169,6 @@ const configuration: IConfigurationNode = { description: localize('verbosity.comments', 'Provide information about actions that can be taken in the comment widget or in a file which contains comments.'), ...baseVerbosityProperty }, - [AccessibilityVerbositySettingId.TerminalInlineChat]: { - description: localize('verbosity.terminalInlineChat', 'Provide information about actions that can be taken in the terminal inline chat widget.'), - ...baseVerbosityProperty - }, [AccessibilityAlertSettingId.Save]: { 'markdownDescription': localize('announcement.save', "Indicates when a file is saved. Also see {0}.", '`#audioCues.save#`'), 'enum': ['userGesture', 'always', 'never'], diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 85033c64166..02cf92cfa93 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -30,6 +30,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); @@ -745,6 +746,6 @@ export class InlineAccessibilityHelpContribution extends Disposable { return; } runAccessibilityHelpAction(accessor, codeEditor, 'inlineChat'); - }, ContextKeyExpr.or(CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_FOCUSED))); + }, ContextKeyExpr.and(ContextKeyExpr.or(CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_FOCUSED), TerminalContextKeys.chatFocused.negate()))); } } diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 5ac4cdca5ac..8ea1469a4b1 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -183,7 +183,4 @@ export namespace TerminalContextKeys { /** Whether the chat response editor is focused */ export const chatResponseEditorFocused = new RawContextKey(TerminalContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseEditorFocusedContextKey', "Whether the chat response editor is focused.")); - - /** Whether the chat response editor is focused */ - export const chatResponseMessageFocused = new RawContextKey(TerminalContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseMessageFocusedContextKey', "Whether the chat response message is focused.")); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index eebaa8b3b8a..70804842c3e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -13,5 +13,7 @@ import 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { TerminalInlineChatAccessibleViewContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView'; +import { TerminalInlineChatAccessibilityHelpContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp'; const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(TerminalInlineChatAccessibleViewContribution, LifecyclePhase.Eventually); +workbenchContributionsRegistry.registerWorkbenchContribution(TerminalInlineChatAccessibilityHelpContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index ddea1c2a53b..f75c9c3d9a7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -6,7 +6,7 @@ import { MenuId } from 'vs/platform/actions/common/actions'; export const enum TerminalChatCommandId { - Focus = 'workbench.action.terminal.chat.focus', + Start = 'workbench.action.terminal.chat.start', Hide = 'workbench.action.terminal.chat.close', MakeRequest = 'workbench.action.terminal.chat.makeRequest', Cancel = 'workbench.action.terminal.chat.cancel', diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts new file mode 100644 index 00000000000..5bbe76db85f --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { localize } from 'vs/nls'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; +import { CTX_INLINE_CHAT_RESPONSE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; + +export class TerminalInlineChatAccessibilityHelpContribution extends Disposable { + constructor() { + super(); + this._register(AccessibilityHelpAction.addImplementation(106, 'terminalInlineChat', accessor => { + const terminalService = accessor.get(ITerminalService); + const accessibleViewService = accessor.get(IAccessibleViewService); + const controller: TerminalChatController | undefined = terminalService.activeInstance?.getContribution(TerminalChatController.ID) ?? undefined; + if (controller === undefined) { + return false; + } + const helpContent = getAccessibilityHelpText(accessor); + accessibleViewService.show({ + id: AccessibleViewProviderId.TerminalInlineChat, + verbositySettingKey: AccessibilityVerbositySettingId.InlineChat, + provideContent(): string { return helpContent; }, + onClose() { + controller.focus(); + }, + options: { type: AccessibleViewType.Help } + }); + return true; + }, ContextKeyExpr.or(TerminalContextKeys.chatFocused, TerminalContextKeys.chatResponseEditorFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); + } +} + + +export function getAccessibilityHelpText(accessor: ServicesAccessor): string { + const keybindingService = accessor.get(IKeybindingService); + const content = []; + const openAccessibleViewKeybinding = keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel(); + const acceptCommandKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.AcceptCommand)?.getAriaLabel(); + const makeRequestKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.MakeRequest)?.getAriaLabel(); + //TODO: using this instead of the terminal command bc by definition the inline terminal chat is focused when this dialog is invoked. + const startChatKeybinding = keybindingService.lookupKeybinding('inlineChat.start')?.getAriaLabel(); + content.push(localize('inlineChat.overview', "Inline chat occurs within a terminal. It is useful for suggesting terminal commands. Keep in mind that AI generated code may be incorrect.")); + content.push(localize('inlineChat.access', "It can be activated using the command: Terminal: Start Chat ({0}), which will focus the input box.", startChatKeybinding)); + content.push(makeRequestKeybinding ? localize('inlineChat.input', "The input box is where the user can type a request and can make the request ({0}). The widget will be closed and all content will be discarded when the Escape key is pressed and the terminal will regain focus.", makeRequestKeybinding) : localize('inlineChat.inputNoKb', "The input box is where the user can type a request and can make the request by tabbing to the Make Request button, which is not currently triggerable via keybindings. The widget will be closed and all content will be discarded when the Escape key is pressed and the terminal will regain focus.")); + content.push(localize('inlineChat.results', "A result may contain a terminal command or just a message. In either case, the result will be announced.")); + content.push(openAccessibleViewKeybinding ? localize('inlineChat.inspectResponseMessage', 'If just a message comes back, it can be inspected in the accessible view ({0}).', openAccessibleViewKeybinding) : localize('inlineChat.inspectResponseNoKb', 'With the input box focused, inspect the response in the accessible view via the Open Accessible View command, which is currently not triggerable by a keybinding.')); + content.push(localize('inlineChat.inspectTerminalCommand', 'If a terminal command comes back, it can be inspected in an editor reached via Shift+Tab.')); + content.push(acceptCommandKeybinding ? localize('inlineChat.acceptCommand', 'With focus in the command editor, the Terminal: Accept Chat Command ({0}) action.', acceptCommandKeybinding) : localize('inlineChat.acceptCommandNoKb', 'Accept a command by tabbing to the button as the action is currently not triggerable by a keybinding.')); + content.push(localize('inlineChat.toolbar', "Use tab to reach conditional parts like commands, status, message responses and more.")); + content.push(localize('chat.signals', "Accessibility Signals can be changed via settings with a prefix of signals.chat. By default, if a request takes more than 4 seconds, you will hear a sound indicating that progress is still occurring.")); + return content.join('\n\n'); +} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts index 949a2d8cc51..8c146698482 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts @@ -27,12 +27,11 @@ export class TerminalInlineChatAccessibleViewContribution extends Disposable { const responseContent = controller.lastResponseContent; accessibleViewService.show({ id: AccessibleViewProviderId.TerminalInlineChat, - verbositySettingKey: AccessibilityVerbositySettingId.TerminalInlineChat, + verbositySettingKey: AccessibilityVerbositySettingId.InlineChat, provideContent(): string { return responseContent; }, onClose() { controller.focus(); }, - options: { type: AccessibleViewType.View } }); return true; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index d6ba18a272e..641737229dc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -17,8 +17,8 @@ import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; registerActiveXtermAction({ - id: TerminalChatCommandId.Focus, - title: localize2('focusChat', 'Focus Chat'), + id: TerminalChatCommandId.Start, + title: localize2('startChat', 'Terminal: Start Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), @@ -40,7 +40,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.Hide, - title: localize2('closeChat', 'Close Chat'), + title: localize2('closeChat', 'Terminal: Close Chat'), keybinding: { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], @@ -72,7 +72,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.AcceptCommand, - title: localize2('acceptCommand', 'Accept Command'), + title: localize2('acceptCommand', 'Terminal: Accept Chat Command'), shortTitle: localize2('accept', 'Accept'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -104,7 +104,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.ViewInChat, - title: localize2('viewInChat', 'View in Chat'), + title: localize2('viewInChat', 'Terminal: View in Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), @@ -130,7 +130,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.MakeRequest, - title: localize2('makeChatRequest', 'Make Chat Request'), + title: localize2('makeChatRequest', 'Terminal: Make Chat Request'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), @@ -161,7 +161,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.Cancel, - title: localize2('cancelChat', 'Cancel Chat'), + title: localize2('cancelChat', 'Terminal: Cancel Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatRequestActive, From 55222fabf2a78be3699958314db20b1ed8e9991b Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 14 Feb 2024 13:47:56 -0800 Subject: [PATCH 1281/1897] bump distro for https://github.com/microsoft/vscode-distro/commit/660e1818ca4ddccb41c09b6a44360c278ae1610e --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 488e3cd2c58..dcd8d8916b3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "cee7e51a2bc638aa4fe4bc62668aec6d46ac94dc", + "distro": "660e1818ca4ddccb41c09b6a44360c278ae1610e", "author": { "name": "Microsoft Corporation" }, From d16c5304a07acc6158e3e1e6c52e61d373f1d80d Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Wed, 14 Feb 2024 16:08:50 -0800 Subject: [PATCH 1282/1897] enable variable view for IW (#205249) --- .../notebookVariables/notebookVariables.ts | 45 ++++++++++--------- .../notebookVariablesView.ts | 2 +- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts index 9e7435fce88..933c67cea81 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts @@ -3,23 +3,25 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { URI } from 'vs/base/common/uri'; +import * as nls from 'vs/nls'; +import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Extensions, IViewContainersRegistry, IViewsRegistry } from 'vs/workbench/common/views'; import { VIEWLET_ID as debugContainerId } from 'vs/workbench/contrib/debug/common/debug'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { NotebookVariablesView } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView'; -import { variablesViewIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { NOTEBOOK_VARIABLE_VIEW_ENABLED } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableContextKeys'; +import { NotebookVariablesView } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView'; +import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { variablesViewIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class NotebookVariables extends Disposable implements IWorkbenchContribution { private listeners: IDisposable[] = []; @@ -33,14 +35,15 @@ export class NotebookVariables extends Disposable implements IWorkbenchContribut @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, @INotebookExecutionStateService private readonly notebookExecutionStateService: INotebookExecutionStateService, - @INotebookKernelService private readonly notebookKernelService: INotebookKernelService + @INotebookKernelService private readonly notebookKernelService: INotebookKernelService, + @INotebookService private readonly notebookDocumentService: INotebookService ) { super(); this.viewEnabled = NOTEBOOK_VARIABLE_VIEW_ENABLED.bindTo(contextKeyService); this.listeners.push(this.editorService.onDidActiveEditorChange(() => this.handleInitEvent())); - this.listeners.push(this.notebookExecutionStateService.onDidChangeExecution((e) => this.handleInitEvent())); + this.listeners.push(this.notebookExecutionStateService.onDidChangeExecution((e) => this.handleInitEvent(e.notebook))); this.configListener = configurationService.onDidChangeConfiguration((e) => this.handleConfigChange(e)); } @@ -57,11 +60,11 @@ export class NotebookVariables extends Disposable implements IWorkbenchContribut } } - private handleInitEvent() { + private handleInitEvent(notebook?: URI) { if (this.configurationService.getValue(NotebookSetting.notebookVariablesView) - && this.editorService.activeEditorPane?.getId() === 'workbench.editor.notebook') { + && (!!notebook || this.editorService.activeEditorPane?.getId() === 'workbench.editor.notebook')) { - if (this.hasVariableProvider() && !this.initialized && this.initializeView()) { + if (this.hasVariableProvider(notebook) && !this.initialized && this.initializeView()) { this.viewEnabled.set(true); this.initialized = true; this.listeners.forEach(listener => listener.dispose()); @@ -69,9 +72,11 @@ export class NotebookVariables extends Disposable implements IWorkbenchContribut } } - private hasVariableProvider() { - const notebookDocument = getNotebookEditorFromEditorPane(this.editorService.activeEditorPane)?.getViewModel()?.notebookDocument; - return notebookDocument && this.notebookKernelService.getMatchingKernel(notebookDocument).selected?.hasVariableProvider; + private hasVariableProvider(notebookUri?: URI) { + const notebook = notebookUri ? + this.notebookDocumentService.getNotebookTextModel(notebookUri) : + getNotebookEditorFromEditorPane(this.editorService.activeEditorPane)?.getViewModel()?.notebookDocument; + return notebook && this.notebookKernelService.getMatchingKernel(notebook).selected?.hasVariableProvider; } private initializeView() { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index 515a43f8cf5..df59bbeb044 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -140,7 +140,7 @@ export class NotebookVariablesView extends ViewPane { private setActiveNotebook() { const current = this.activeNotebook; const activeEditorPane = this.editorService.activeEditorPane; - if (activeEditorPane?.getId() === 'workbench.editor.notebook') { + if (activeEditorPane?.getId() === 'workbench.editor.notebook' || activeEditorPane?.getId() === 'workbench.editor.interactive') { const notebookDocument = getNotebookEditorFromEditorPane(activeEditorPane)?.getViewModel()?.notebookDocument; this.activeNotebook = notebookDocument; } From e587755905208e47725c5196539c4ca898255fe6 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 14 Feb 2024 16:30:07 -0800 Subject: [PATCH 1283/1897] Expose notebook variable type (#205250) --- src/vs/workbench/api/common/extHost.protocol.ts | 1 + src/vs/workbench/api/common/extHostNotebookKernels.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 05ae4f752d2..b8b4266b5e1 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1119,6 +1119,7 @@ export interface VariablesResult { id: number; name: string; value: string; + type?: string; hasNamedChildren: boolean; indexedChildrenCount: number; extensionId: string; diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index e5b11fac0ce..f2a201e9e06 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -101,6 +101,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { return { name: variable.name, value: variable.value, + type: variable.type, editable: false }; }); From 3ce7ccff4116da34370cecfb329420f266499ec4 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 14 Feb 2024 21:36:53 -0800 Subject: [PATCH 1284/1897] debug: initial support of breakpoint modes (#205251) This supports breakpoint modes on exception, instruction, and source breakpoints. It doesn't yet do data breakpoints since I was having trouble figuring out a good user flow for that. You can test this on the `connor4312/breakpoint-modes` branch of mock-debug, which I'll merge in after the next DAP release. ![](https://memes.peet.io/img/24-02-68cd0222-8ef5-4d39-aa51-81ba5b8c2405.png) --- .../api/browser/mainThreadDebugService.ts | 7 +- .../workbench/api/common/extHost.protocol.ts | 4 + .../api/common/extHostDebugService.ts | 12 +- src/vs/workbench/api/common/extHostTypes.ts | 18 +- .../contrib/debug/browser/breakpointWidget.ts | 53 ++- .../contrib/debug/browser/breakpointsView.ts | 114 ++++- .../debug/browser/debugEditorActions.ts | 2 +- .../contrib/debug/browser/debugService.ts | 18 +- .../contrib/debug/browser/debugSession.ts | 9 +- .../contrib/debug/browser/disassemblyView.ts | 2 +- .../debug/browser/media/breakpointWidget.css | 93 ++-- .../contrib/debug/browser/variablesView.ts | 6 +- .../workbench/contrib/debug/common/debug.ts | 32 +- .../contrib/debug/common/debugModel.ts | 411 ++++++++++++------ .../contrib/debug/common/debugProtocol.d.ts | 194 ++++++--- .../contrib/debug/common/debugStorage.ts | 17 +- .../debug/test/browser/breakpoints.test.ts | 10 +- .../debug/test/common/debugModel.test.ts | 13 +- .../contrib/debug/test/common/mockDebug.ts | 3 +- 19 files changed, 734 insertions(+), 284 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 1b383b49d75..3178df6d093 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -217,14 +217,15 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb column: l.character > 0 ? l.character + 1 : undefined, // a column value of 0 results in an omitted column attribute; see #46784 condition: l.condition, hitCondition: l.hitCondition, - logMessage: l.logMessage + logMessage: l.logMessage, + mode: l.mode, } ); this.debugService.addBreakpoints(uri.revive(dto.uri), rawbps); } else if (dto.type === 'function') { - this.debugService.addFunctionBreakpoint(dto.functionName, dto.id); + this.debugService.addFunctionBreakpoint(dto.functionName, dto.id, dto.mode); } else if (dto.type === 'data') { - this.debugService.addDataBreakpoint(dto.label, dto.dataId, dto.canPersist, dto.accessTypes, dto.accessType); + this.debugService.addDataBreakpoint(dto.label, dto.dataId, dto.canPersist, dto.accessTypes, dto.accessType, dto.mode); } } return Promise.resolve(); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b8b4266b5e1..34e3f6664b3 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2277,11 +2277,13 @@ export interface IBreakpointDto { condition?: string; hitCondition?: string; logMessage?: string; + mode?: string; } export interface IFunctionBreakpointDto extends IBreakpointDto { type: 'function'; functionName: string; + mode?: string; } export interface IDataBreakpointDto extends IBreakpointDto { @@ -2291,6 +2293,7 @@ export interface IDataBreakpointDto extends IBreakpointDto { label: string; accessTypes?: DebugProtocol.DataBreakpointAccessType[]; accessType: DebugProtocol.DataBreakpointAccessType; + mode?: string; } export interface ISourceBreakpointDto extends IBreakpointDto { @@ -2317,6 +2320,7 @@ export interface ISourceMultiBreakpointDto { logMessage?: string; line: number; character: number; + mode?: string; }[]; } diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index 133ff216767..38d5f2a3205 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -427,7 +427,8 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E hitCondition: bp.hitCondition, logMessage: bp.logMessage, line: bp.location.range.start.line, - character: bp.location.range.start.character + character: bp.location.range.start.character, + mode: bp.mode, }); } else if (bp instanceof FunctionBreakpoint) { dtos.push({ @@ -437,7 +438,8 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E hitCondition: bp.hitCondition, logMessage: bp.logMessage, condition: bp.condition, - functionName: bp.functionName + functionName: bp.functionName, + mode: bp.mode, }); } } @@ -713,12 +715,12 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E if (id && !this._breakpoints.has(id)) { let bp: Breakpoint; if (bpd.type === 'function') { - bp = new FunctionBreakpoint(bpd.functionName, bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); + bp = new FunctionBreakpoint(bpd.functionName, bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage, bpd.mode); } else if (bpd.type === 'data') { - bp = new DataBreakpoint(bpd.label, bpd.dataId, bpd.canPersist, bpd.enabled, bpd.hitCondition, bpd.condition, bpd.logMessage); + bp = new DataBreakpoint(bpd.label, bpd.dataId, bpd.canPersist, bpd.enabled, bpd.hitCondition, bpd.condition, bpd.logMessage, bpd.mode); } else { const uri = URI.revive(bpd.uri); - bp = new SourceBreakpoint(new Location(uri, new Position(bpd.line, bpd.character)), bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); + bp = new SourceBreakpoint(new Location(uri, new Position(bpd.line, bpd.character)), bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage, bpd.mode); } setBreakpointId(bp, id); this._breakpoints.set(id, bp); diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 6d563a0e6a9..d6c3996105e 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2918,8 +2918,9 @@ export class Breakpoint { readonly condition?: string; readonly hitCondition?: string; readonly logMessage?: string; + readonly mode?: string; - protected constructor(enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) { + protected constructor(enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string, mode?: string) { this.enabled = typeof enabled === 'boolean' ? enabled : true; if (typeof condition === 'string') { this.condition = condition; @@ -2930,6 +2931,9 @@ export class Breakpoint { if (typeof logMessage === 'string') { this.logMessage = logMessage; } + if (typeof mode === 'string') { + this.mode = mode; + } } get id(): string { @@ -2944,8 +2948,8 @@ export class Breakpoint { export class SourceBreakpoint extends Breakpoint { readonly location: Location; - constructor(location: Location, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) { - super(enabled, condition, hitCondition, logMessage); + constructor(location: Location, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string, mode?: string) { + super(enabled, condition, hitCondition, logMessage, mode); if (location === null) { throw illegalArgument('location'); } @@ -2957,8 +2961,8 @@ export class SourceBreakpoint extends Breakpoint { export class FunctionBreakpoint extends Breakpoint { readonly functionName: string; - constructor(functionName: string, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) { - super(enabled, condition, hitCondition, logMessage); + constructor(functionName: string, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string, mode?: string) { + super(enabled, condition, hitCondition, logMessage, mode); this.functionName = functionName; } } @@ -2969,8 +2973,8 @@ export class DataBreakpoint extends Breakpoint { readonly dataId: string; readonly canPersist: boolean; - constructor(label: string, dataId: string, canPersist: boolean, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) { - super(enabled, condition, hitCondition, logMessage); + constructor(label: string, dataId: string, canPersist: boolean, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string, mode?: string) { + super(enabled, condition, hitCondition, logMessage, mode); if (!dataId) { throw illegalArgument('dataId'); } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 877c0921a4c..26ae95ccf1d 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -85,10 +85,12 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi private selectBreakpointContainer!: HTMLElement; private input!: IActiveCodeEditor; private selectBreakpointBox!: SelectBox; + private selectModeBox?: SelectBox; private toDispose: lifecycle.IDisposable[]; private conditionInput = ''; private hitCountInput = ''; private logMessageInput = ''; + private modeInput?: DebugProtocol.BreakpointMode; private breakpoint: IBreakpoint | undefined; private context: Context; private heightInPx: number | undefined; @@ -216,6 +218,8 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.updateContextInput(); }); + this.createModesInput(container); + this.inputContainer = $('.inputContainer'); this.createBreakpointInput(dom.append(container, this.inputContainer)); @@ -232,6 +236,33 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi setTimeout(() => this.focusInput(), 150); } + private createModesInput(container: HTMLElement) { + const modes = this.debugService.getModel().getBreakpointModes('source'); + if (modes.length <= 1) { + return; + } + + const sb = this.selectModeBox = new SelectBox( + [ + { text: nls.localize('bpMode', 'Mode'), isDisabled: true }, + ...modes.map(mode => ({ text: mode.label, description: mode.description })), + ], + modes.findIndex(m => m.mode === this.breakpoint?.mode) + 1, + this.contextViewService, + defaultSelectBoxStyles, + ); + this.toDispose.push(sb); + this.toDispose.push(sb.onDidSelect(e => { + this.modeInput = modes[e.index - 1]; + })); + + const modeWrapper = $('.select-mode-container'); + const selectionWrapper = $('.select-box-container'); + dom.append(modeWrapper, selectionWrapper); + sb.render(selectionWrapper); + dom.append(container, modeWrapper); + } + private createTriggerBreakpointInput(container: HTMLElement) { const breakpoints = this.debugService.getModel().getBreakpoints().filter(bp => bp !== this.breakpoint); @@ -404,10 +435,12 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi if (success) { // if there is already a breakpoint on this location - remove it. - let condition = this.breakpoint && this.breakpoint.condition; - let hitCondition = this.breakpoint && this.breakpoint.hitCondition; - let logMessage = this.breakpoint && this.breakpoint.logMessage; - let triggeredBy = this.breakpoint && this.breakpoint.triggeredBy; + let condition = this.breakpoint?.condition; + let hitCondition = this.breakpoint?.hitCondition; + let logMessage = this.breakpoint?.logMessage; + let triggeredBy = this.breakpoint?.triggeredBy; + let mode = this.breakpoint?.mode; + let modeLabel = this.breakpoint?.modeLabel; this.rememberInput(); @@ -420,6 +453,10 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi if (this.logMessageInput || this.context === Context.LOG_MESSAGE) { logMessage = this.logMessageInput; } + if (this.selectModeBox) { + mode = this.modeInput?.mode; + modeLabel = this.modeInput?.label; + } if (this.context === Context.TRIGGER_POINT) { // currently, trigger points don't support additional conditions: condition = undefined; @@ -434,7 +471,9 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi condition, hitCondition, logMessage, - triggeredBy + triggeredBy, + mode, + modeLabel, }); this.debugService.updateBreakpoints(this.breakpoint.originalUri, data, false).then(undefined, onUnexpectedError); } else { @@ -447,7 +486,9 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi condition, hitCondition, logMessage, - triggeredBy + triggeredBy, + mode, + modeLabel, }]); } } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 5222b57d2e7..e60c8079a14 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -39,6 +39,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ILabelService } from 'vs/platform/label/common/label'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -48,7 +49,7 @@ import { IEditorPane } from 'vs/workbench/common/editor'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; import { DisassemblyView } from 'vs/workbench/contrib/debug/browser/disassemblyView'; -import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_BREAKPOINT_ITEM_TYPE, CONTEXT_BREAKPOINT_SUPPORTS_CONDITION, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_IN_DEBUG_MODE, DEBUG_SCHEME, DebuggerString, IBaseBreakpoint, IBreakpoint, IBreakpointEditorContribution, IDataBreakpoint, IDebugModel, IDebugService, IEnablement, IExceptionBreakpoint, IFunctionBreakpoint, IInstructionBreakpoint, State } from 'vs/workbench/contrib/debug/common/debug'; +import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_BREAKPOINT_HAS_MODES, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_BREAKPOINT_ITEM_TYPE, CONTEXT_BREAKPOINT_SUPPORTS_CONDITION, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_IN_DEBUG_MODE, DEBUG_SCHEME, DebuggerString, IBaseBreakpoint, IBreakpoint, IBreakpointEditorContribution, IBreakpointUpdateData, IDataBreakpoint, IDebugModel, IDebugService, IEnablement, IExceptionBreakpoint, IFunctionBreakpoint, IInstructionBreakpoint, State } from 'vs/workbench/contrib/debug/common/debug'; import { Breakpoint, DataBreakpoint, ExceptionBreakpoint, FunctionBreakpoint, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; @@ -84,6 +85,7 @@ export class BreakpointsView extends ViewPane { private ignoreLayout = false; private menu: IMenu; private breakpointItemType: IContextKey; + private breakpointHasMultipleModes: IContextKey; private breakpointSupportsCondition: IContextKey; private _inputBoxData: InputBoxData | undefined; breakpointInputFocused: IContextKey; @@ -116,6 +118,7 @@ export class BreakpointsView extends ViewPane { this.menu = menuService.createMenu(MenuId.DebugBreakpointsContext, contextKeyService); this._register(this.menu); this.breakpointItemType = CONTEXT_BREAKPOINT_ITEM_TYPE.bindTo(contextKeyService); + this.breakpointHasMultipleModes = CONTEXT_BREAKPOINT_HAS_MODES.bindTo(contextKeyService); this.breakpointSupportsCondition = CONTEXT_BREAKPOINT_SUPPORTS_CONDITION.bindTo(contextKeyService); this.breakpointInputFocused = CONTEXT_BREAKPOINT_INPUT_FOCUSED.bindTo(contextKeyService); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); @@ -132,12 +135,12 @@ export class BreakpointsView extends ViewPane { const delegate = new BreakpointsDelegate(this); this.list = this.instantiationService.createInstance(WorkbenchList, 'Breakpoints', container, delegate, [ - this.instantiationService.createInstance(BreakpointsRenderer, this.menu, this.breakpointSupportsCondition, this.breakpointItemType), - new ExceptionBreakpointsRenderer(this.menu, this.breakpointSupportsCondition, this.breakpointItemType, this.debugService), + this.instantiationService.createInstance(BreakpointsRenderer, this.menu, this.breakpointHasMultipleModes, this.breakpointSupportsCondition, this.breakpointItemType), + new ExceptionBreakpointsRenderer(this.menu, this.breakpointHasMultipleModes, this.breakpointSupportsCondition, this.breakpointItemType, this.debugService), new ExceptionBreakpointInputRenderer(this, this.debugService, this.contextViewService), this.instantiationService.createInstance(FunctionBreakpointsRenderer, this.menu, this.breakpointSupportsCondition, this.breakpointItemType), new FunctionBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.labelService), - this.instantiationService.createInstance(DataBreakpointsRenderer, this.menu, this.breakpointSupportsCondition, this.breakpointItemType), + this.instantiationService.createInstance(DataBreakpointsRenderer, this.menu, this.breakpointHasMultipleModes, this.breakpointSupportsCondition, this.breakpointItemType), new DataBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.labelService), this.instantiationService.createInstance(InstructionBreakpointsRenderer), ], { @@ -426,6 +429,7 @@ interface IBaseBreakpointTemplateData { context: BreakpointItem; actionBar: ActionBar; toDispose: IDisposable[]; + badge: HTMLElement; } interface IBaseBreakpointWithIconTemplateData extends IBaseBreakpointTemplateData { @@ -433,7 +437,6 @@ interface IBaseBreakpointWithIconTemplateData extends IBaseBreakpointTemplateDat } interface IBreakpointTemplateData extends IBaseBreakpointWithIconTemplateData { - lineNumber: HTMLElement; filePath: HTMLElement; } @@ -486,6 +489,7 @@ class BreakpointsRenderer implements IListRenderer, private breakpointSupportsCondition: IContextKey, private breakpointItemType: IContextKey, @IDebugService private readonly debugService: IDebugService, @@ -521,8 +525,8 @@ class BreakpointsRenderer implements IListRenderer 1); createAndFillInActionBarActions(this.menu, { arg: breakpoint, shouldForwardArgs: true }, { primary, secondary: [] }, 'inline'); data.actionBar.clear(); data.actionBar.push(primary, { icon: true, label: false }); @@ -567,6 +576,7 @@ class ExceptionBreakpointsRenderer implements IListRenderer, private breakpointSupportsCondition: IContextKey, private breakpointItemType: IContextKey, private debugService: IDebugService @@ -598,6 +608,9 @@ class ExceptionBreakpointsRenderer implements IListRenderer 1); createAndFillInActionBarActions(this.menu, { arg: exceptionBreakpoint, shouldForwardArgs: true }, { primary, secondary: [] }, 'inline'); data.actionBar.clear(); data.actionBar.push(primary, { icon: true, label: false }); @@ -661,6 +682,8 @@ class FunctionBreakpointsRenderer implements IListRenderer, private breakpointSupportsCondition: IContextKey, private breakpointItemType: IContextKey, @IDebugService private readonly debugService: IDebugService, @@ -738,6 +769,8 @@ class DataBreakpointsRenderer implements IListRenderer 1); this.breakpointItemType.set('dataBreakpoint'); createAndFillInActionBarActions(this.menu, { arg: dataBreakpoint, shouldForwardArgs: true }, { primary, secondary: [] }, 'inline'); data.actionBar.clear(); @@ -817,6 +858,8 @@ class InstructionBreakpointsRenderer implements IListRenderer { view.renderInputBox({ breakpoint, type: 'hitCount' }); } }); + +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'debug.editBreakpointMode', + viewId: BREAKPOINTS_VIEW_ID, + title: localize('editMode', "Edit Mode..."), + menu: [{ + id: MenuId.DebugBreakpointsContext, + group: 'navigation', + order: 20, + when: ContextKeyExpr.and( + CONTEXT_BREAKPOINT_HAS_MODES, + ContextKeyExpr.or(CONTEXT_BREAKPOINT_ITEM_TYPE.isEqualTo('breakpoint'), CONTEXT_BREAKPOINT_ITEM_TYPE.isEqualTo('exceptionBreakpoint'), CONTEXT_BREAKPOINT_ITEM_TYPE.isEqualTo('instructionBreakpoint')) + ) + }] + }); + } + + async runInView(accessor: ServicesAccessor, view: BreakpointsView, breakpoint: IBreakpoint) { + const kind = breakpoint instanceof Breakpoint ? 'source' : breakpoint instanceof InstructionBreakpoint ? 'instruction' : 'exception'; + const debugService = accessor.get(IDebugService); + const modes = debugService.getModel().getBreakpointModes(kind); + const picked = await accessor.get(IQuickInputService).pick( + modes.map(mode => ({ label: mode.label, description: mode.description, mode: mode.mode })), + { placeHolder: localize('selectBreakpointMode', "Select Breakpoint Mode") } + ); + + if (!picked) { + return; + } + + if (kind === 'source') { + const data = new Map(); + data.set(breakpoint.getId(), { mode: picked.mode, modeLabel: picked.label }); + debugService.updateBreakpoints(breakpoint.originalUri, data, false); + } else if (breakpoint instanceof InstructionBreakpoint) { + debugService.removeInstructionBreakpoints(breakpoint.instructionReference, breakpoint.offset); + debugService.addInstructionBreakpoint({ ...breakpoint.toJSON(), mode: picked.mode, modeLabel: picked.label }); + } else if (breakpoint instanceof ExceptionBreakpoint) { + breakpoint.mode = picked.mode; + breakpoint.modeLabel = picked.label; + debugService.setExceptionBreakpointCondition(breakpoint, breakpoint.condition); // no-op to trigger a re-send + } + } +}); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 50a0b86f6b0..2dd7760e492 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -67,7 +67,7 @@ class ToggleBreakpointAction extends Action2 { if (toRemove) { debugService.removeInstructionBreakpoints(toRemove.instructionReference, toRemove.offset); } else { - debugService.addInstructionBreakpoint(location.reference, location.offset, location.address); + debugService.addInstructionBreakpoint({ instructionReference: location.reference, offset: location.offset, address: location.address, canPersist: false }); } } return; diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 73750df8fb6..5478398dfb6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -44,7 +44,7 @@ import { DebugTaskRunner, TaskRunResult } from 'vs/workbench/contrib/debug/brows import { CALLSTACK_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_HAS_DEBUGGED, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_UX, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_IN_DEBUG_MODE, debuggerDisabledMessage, DEBUG_MEMORY_SCHEME, getStateLabel, IAdapterManager, IBreakpoint, IBreakpointData, ICompound, IConfig, IConfigurationManager, IDebugConfiguration, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IEnablement, IExceptionBreakpoint, IGlobalConfig, ILaunch, IStackFrame, IThread, IViewModel, REPL_VIEW_ID, State, VIEWLET_ID, DEBUG_SCHEME, IBreakpointUpdateData } from 'vs/workbench/contrib/debug/common/debug'; import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; -import { Breakpoint, DataBreakpoint, DebugModel, FunctionBreakpoint, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Breakpoint, DataBreakpoint, DebugModel, FunctionBreakpoint, IInstructionBreakpointOptions, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage'; import { DebugTelemetry } from 'vs/workbench/contrib/debug/common/debugTelemetry'; @@ -1065,8 +1065,8 @@ export class DebugService implements IDebugService { return this.sendAllBreakpoints(); } - addFunctionBreakpoint(name?: string, id?: string): void { - this.model.addFunctionBreakpoint(name || '', id); + addFunctionBreakpoint(name?: string, id?: string, mode?: string): void { + this.model.addFunctionBreakpoint(name || '', id, mode); } async updateFunctionBreakpoint(id: string, update: { name?: string; hitCondition?: string; condition?: string }): Promise { @@ -1081,8 +1081,8 @@ export class DebugService implements IDebugService { await this.sendFunctionBreakpoints(); } - async addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, accessType: DebugProtocol.DataBreakpointAccessType): Promise { - this.model.addDataBreakpoint(label, dataId, canPersist, accessTypes, accessType); + async addDataBreakpoint(description: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, accessType: DebugProtocol.DataBreakpointAccessType, mode: string | undefined): Promise { + this.model.addDataBreakpoint({ description, dataId, canPersist, accessTypes, accessType, mode }); this.debugStorage.storeBreakpoints(this.model); await this.sendDataBreakpoints(); this.debugStorage.storeBreakpoints(this.model); @@ -1100,8 +1100,8 @@ export class DebugService implements IDebugService { await this.sendDataBreakpoints(); } - async addInstructionBreakpoint(instructionReference: string, offset: number, address: bigint, condition?: string, hitCondition?: string): Promise { - this.model.addInstructionBreakpoint(instructionReference, offset, address, condition, hitCondition); + async addInstructionBreakpoint(opts: IInstructionBreakpointOptions): Promise { + this.model.addInstructionBreakpoint(opts); this.debugStorage.storeBreakpoints(this.model); await this.sendInstructionBreakpoints(); this.debugStorage.storeBreakpoints(this.model); @@ -1118,8 +1118,8 @@ export class DebugService implements IDebugService { this.debugStorage.storeBreakpoints(this.model); } - setExceptionBreakpointsForSession(session: IDebugSession, data: DebugProtocol.ExceptionBreakpointsFilter[]): void { - this.model.setExceptionBreakpointsForSession(session.getId(), data); + setExceptionBreakpointsForSession(session: IDebugSession, filters: DebugProtocol.ExceptionBreakpointsFilter[]): void { + this.model.setExceptionBreakpointsForSession(session.getId(), filters); this.debugStorage.storeBreakpoints(this.model); } diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index b7502e41c74..861135c6f6a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -344,6 +344,7 @@ export class DebugSession implements IDebugSession, IDisposable { this.initialized = true; this._onDidChangeState.fire(); this.debugService.setExceptionBreakpointsForSession(this, (this.raw && this.raw.capabilities.exceptionBreakpointFilters) || []); + this.debugService.getModel().registerBreakpointModes(this.configuration.type, this.raw.capabilities.breakpointModes || []); } catch (err) { this.initialized = true; this._onDidChangeState.fire(); @@ -457,7 +458,7 @@ export class DebugSession implements IDebugSession, IDisposable { const response = await this.raw.setBreakpoints({ source: rawSource, lines: breakpointsToSend.map(bp => bp.sessionAgnosticData.lineNumber), - breakpoints: breakpointsToSend.map(bp => ({ line: bp.sessionAgnosticData.lineNumber, column: bp.sessionAgnosticData.column, condition: bp.condition, hitCondition: bp.hitCondition, logMessage: bp.logMessage })), + breakpoints: breakpointsToSend.map(bp => bp.toDAP()), sourceModified }); if (response && response.body) { @@ -476,7 +477,7 @@ export class DebugSession implements IDebugSession, IDisposable { } if (this.raw.readyForBreakpoints) { - const response = await this.raw.setFunctionBreakpoints({ breakpoints: fbpts }); + const response = await this.raw.setFunctionBreakpoints({ breakpoints: fbpts.map(bp => bp.toDAP()) }); if (response && response.body) { const data = new Map(); for (let i = 0; i < fbpts.length; i++) { @@ -534,7 +535,7 @@ export class DebugSession implements IDebugSession, IDisposable { } if (this.raw.readyForBreakpoints) { - const response = await this.raw.setDataBreakpoints({ breakpoints: dataBreakpoints }); + const response = await this.raw.setDataBreakpoints({ breakpoints: dataBreakpoints.map(bp => bp.toDAP()) }); if (response && response.body) { const data = new Map(); for (let i = 0; i < dataBreakpoints.length; i++) { @@ -551,7 +552,7 @@ export class DebugSession implements IDebugSession, IDisposable { } if (this.raw.readyForBreakpoints) { - const response = await this.raw.setInstructionBreakpoints({ breakpoints: instructionBreakpoints.map(ib => ib.toJSON()) }); + const response = await this.raw.setInstructionBreakpoints({ breakpoints: instructionBreakpoints.map(ib => ib.toDAP()) }); if (response && response.body) { const data = new Map(); for (let i = 0; i < instructionBreakpoints.length; i++) { diff --git a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts index f196bb14aa4..e092df64537 100644 --- a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts +++ b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts @@ -690,7 +690,7 @@ class BreakpointRenderer implements ITableRenderer { const debugService = accessor.get(IDebugService); if (dataBreakpointInfoResponse) { - await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'write'); + await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'write', undefined); } } }); @@ -813,7 +813,7 @@ CommandsRegistry.registerCommand({ handler: async (accessor: ServicesAccessor) => { const debugService = accessor.get(IDebugService); if (dataBreakpointInfoResponse) { - await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'readWrite'); + await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'readWrite', undefined); } } }); @@ -824,7 +824,7 @@ CommandsRegistry.registerCommand({ handler: async (accessor: ServicesAccessor) => { const debugService = accessor.get(IDebugService); if (dataBreakpointInfoResponse) { - await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'read'); + await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'read', undefined); } } }); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 039bcc1fb10..86d0e94b826 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -24,6 +24,7 @@ import { ITelemetryEndpoint } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IEditorPane } from 'vs/workbench/common/editor'; import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; +import { IInstructionBreakpointOptions } from 'vs/workbench/contrib/debug/common/debugModel'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { ITaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -61,6 +62,7 @@ export const CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD = new RawContextKey('watchItemType', undefined, { type: 'string', description: nls.localize('watchItemType', "Represents the item type of the focused element in the WATCH view. For example: 'expression', 'variable'") }); export const CONTEXT_CAN_VIEW_MEMORY = new RawContextKey('canViewMemory', undefined, { type: 'boolean', description: nls.localize('canViewMemory', "Indicates whether the item in the view has an associated memory refrence.") }); export const CONTEXT_BREAKPOINT_ITEM_TYPE = new RawContextKey('breakpointItemType', undefined, { type: 'string', description: nls.localize('breakpointItemType', "Represents the item type of the focused element in the BREAKPOINTS view. For example: 'breakpoint', 'exceptionBreakppint', 'functionBreakpoint', 'dataBreakpoint'") }); +export const CONTEXT_BREAKPOINT_HAS_MODES = new RawContextKey('breakpointHasModes', false, { type: 'boolean', description: nls.localize('breakpointHasModes', "Whether the breakpoint has multiple modes it can switch to.") }); export const CONTEXT_BREAKPOINT_SUPPORTS_CONDITION = new RawContextKey('breakpointSupportsCondition', false, { type: 'boolean', description: nls.localize('breakpointSupportsCondition', "True when the focused breakpoint supports conditions.") }); export const CONTEXT_LOADED_SCRIPTS_SUPPORTED = new RawContextKey('loadedScriptsSupported', false, { type: 'boolean', description: nls.localize('loadedScriptsSupported', "True when the focused sessions supports the LOADED SCRIPTS view") }); export const CONTEXT_LOADED_SCRIPTS_ITEM_TYPE = new RawContextKey('loadedScriptsItemType', undefined, { type: 'string', description: nls.localize('loadedScriptsItemType', "Represents the item type of the focused element in the LOADED SCRIPTS view.") }); @@ -540,6 +542,8 @@ export interface IBreakpointData { readonly logMessage?: string; readonly hitCondition?: string; readonly triggeredBy?: string; + readonly mode?: string; + readonly modeLabel?: string; } export interface IBreakpointUpdateData { @@ -549,6 +553,8 @@ export interface IBreakpointUpdateData { readonly lineNumber?: number; readonly column?: number; readonly triggeredBy?: string; + readonly mode?: string; + readonly modeLabel?: string; } export interface IBaseBreakpoint extends IEnablement { @@ -558,6 +564,10 @@ export interface IBaseBreakpoint extends IEnablement { readonly verified: boolean; readonly supported: boolean; readonly message?: string; + /** The preferred mode of the breakpoint from {@link DebugProtocol.BreakpointMode} */ + readonly mode?: string; + /** The preferred mode label of the breakpoint from {@link DebugProtocol.BreakpointMode} */ + readonly modeLabel?: string; readonly sessionsThatVerified: string[]; getIdFromAdapter(sessionId: string): number | undefined; } @@ -582,10 +592,13 @@ export interface IBreakpoint extends IBaseBreakpoint { setSessionDidTrigger(sessionId: string): void; /** Gets whether the `triggeredBy` condition has been met in the given sesison ID. */ getSessionDidTrigger(sessionId: string): boolean; + + toDAP(): DebugProtocol.SourceBreakpoint; } export interface IFunctionBreakpoint extends IBaseBreakpoint { readonly name: string; + toDAP(): DebugProtocol.FunctionBreakpoint; } export interface IExceptionBreakpoint extends IBaseBreakpoint { @@ -599,6 +612,7 @@ export interface IDataBreakpoint extends IBaseBreakpoint { readonly dataId: string; readonly canPersist: boolean; readonly accessType: DebugProtocol.DataBreakpointAccessType; + toDAP(): DebugProtocol.DataBreakpoint; } export interface IInstructionBreakpoint extends IBaseBreakpoint { @@ -606,7 +620,7 @@ export interface IInstructionBreakpoint extends IBaseBreakpoint { readonly offset?: number; /** Original instruction memory address; display purposes only */ readonly address: bigint; - toJSON(): DebugProtocol.InstructionBreakpoint; + toDAP(): DebugProtocol.InstructionBreakpoint; } export interface IExceptionInfo { @@ -683,7 +697,8 @@ export interface IDebugModel extends ITreeElement { getInstructionBreakpoints(): ReadonlyArray; getWatchExpressions(): ReadonlyArray; - + registerBreakpointModes(debugType: string, modes: DebugProtocol.BreakpointMode[]): void; + getBreakpointModes(forBreakpointType: 'source' | 'exception' | 'data' | 'instruction'): DebugProtocol.BreakpointMode[]; onDidChangeBreakpoints: Event; onDidChangeCallStack: Event; onDidChangeWatchExpressions: Event; @@ -1112,7 +1127,7 @@ export interface IDebugService { /** * Adds a new function breakpoint for the given name. */ - addFunctionBreakpoint(name?: string, id?: string): void; + addFunctionBreakpoint(name?: string, id?: string, mode?: string): void; /** * Updates an already existing function breakpoint. @@ -1129,7 +1144,7 @@ export interface IDebugService { /** * Adds a new data breakpoint. */ - addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, accessType: DebugProtocol.DataBreakpointAccessType): Promise; + addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, accessType: DebugProtocol.DataBreakpointAccessType, mode: string | undefined): Promise; /** * Updates an already existing data breakpoint. @@ -1146,7 +1161,7 @@ export interface IDebugService { /** * Adds a new instruction breakpoint. */ - addInstructionBreakpoint(instructionReference: string, offset: number, address: bigint, condition?: string, hitCondition?: string): Promise; + addInstructionBreakpoint(opts: IInstructionBreakpointOptions): Promise; /** * Removes all instruction breakpoints. If address is passed only removes the instruction breakpoint with the passed address. @@ -1157,7 +1172,12 @@ export interface IDebugService { setExceptionBreakpointCondition(breakpoint: IExceptionBreakpoint, condition: string | undefined): Promise; - setExceptionBreakpointsForSession(session: IDebugSession, data: DebugProtocol.ExceptionBreakpointsFilter[]): void; + /** + * Creates breakpoints based on the sesison filter options. This will create + * disabled breakpoints (or enabled, if the filter indicates it's a default) + * for each filter provided in the session. + */ + setExceptionBreakpointsForSession(session: IDebugSession, filters: DebugProtocol.ExceptionBreakpointsFilter[]): void; /** * Sends all breakpoints to the passed session. diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 98618a40cf4..8098d1ce57b 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -817,22 +817,35 @@ function toBreakpointSessionData(data: DebugProtocol.Breakpoint, capabilities: D }, data); } +export interface IBaseBreakpointOptions { + enabled?: boolean; + hitCondition?: string; + condition?: string; + logMessage?: string; + mode?: string; + modeLabel?: string; +} + export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoint { private sessionData = new Map(); protected data: IBreakpointSessionData | undefined; + public hitCondition: string | undefined; + public condition: string | undefined; + public logMessage: string | undefined; + public mode: string | undefined; + public modeLabel: string | undefined; constructor( - enabled: boolean, - public hitCondition: string | undefined, - public condition: string | undefined, - public logMessage: string | undefined, - id: string + id: string, + opts: IBaseBreakpointOptions ) { - super(enabled, id); - if (enabled === undefined) { - this.enabled = true; - } + super(opts.enabled ?? true, id); + this.condition = opts.condition; + this.hitCondition = opts.hitCondition; + this.logMessage = opts.logMessage; + this.mode = opts.mode; + this.modeLabel = opts.modeLabel; } setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void { @@ -904,37 +917,59 @@ export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoi return undefined; } - toJSON(): any { - const result = Object.create(null); - result.id = this.getId(); - result.enabled = this.enabled; - result.condition = this.condition; - result.hitCondition = this.hitCondition; - result.logMessage = this.logMessage; - - return result; + toJSON(): IBaseBreakpointOptions & { id: string } { + return { + id: this.getId(), + enabled: this.enabled, + condition: this.condition, + hitCondition: this.hitCondition, + logMessage: this.logMessage, + mode: this.mode, + modeLabel: this.modeLabel, + }; } } +export interface IBreakpointOptions extends IBaseBreakpointOptions { + uri: uri; + lineNumber: number; + column: number | undefined; + adapterData: any; + triggeredBy: string | undefined; +} + export class Breakpoint extends BaseBreakpoint implements IBreakpoint { private sessionsDidTrigger?: Set; + private readonly _uri: uri; + private _adapterData: any; + private _lineNumber: number; + private _column: number | undefined; + public triggeredBy: string | undefined; constructor( - private readonly _uri: uri, - private _lineNumber: number, - private _column: number | undefined, - enabled: boolean, - condition: string | undefined, - hitCondition: string | undefined, - logMessage: string | undefined, - private _adapterData: any, + opts: IBreakpointOptions, private readonly textFileService: ITextFileService, private readonly uriIdentityService: IUriIdentityService, private readonly logService: ILogService, id = generateUuid(), - public triggeredBy: string | undefined = undefined ) { - super(enabled, hitCondition, condition, logMessage, id); + super(id, opts); + this._uri = opts.uri; + this._lineNumber = opts.lineNumber; + this._column = opts.column; + this._adapterData = opts.adapterData; + this.triggeredBy = opts.triggeredBy; + } + + toDAP(): DebugProtocol.SourceBreakpoint { + return { + line: this.sessionAgnosticData.lineNumber, + column: this.sessionAgnosticData.column, + condition: this.condition, + hitCondition: this.hitCondition, + logMessage: this.logMessage, + mode: this.mode + }; } get originalUri() { @@ -1019,14 +1054,15 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { } } - override toJSON(): any { - const result = super.toJSON(); - result.uri = this._uri; - result.lineNumber = this._lineNumber; - result.column = this._column; - result.adapterData = this.adapterData; - result.triggeredBy = this.triggeredBy; - return result; + override toJSON(): IBreakpointOptions & { id: string } { + return { + ...super.toJSON(), + uri: this._uri, + lineNumber: this._lineNumber, + column: this._column, + adapterData: this.adapterData, + triggeredBy: this.triggeredBy, + }; } override toString(): string { @@ -1058,6 +1094,10 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { if (data.hasOwnProperty('logMessage')) { this.logMessage = data.logMessage; } + if (data.hasOwnProperty('mode')) { + this.mode = data.mode; + this.modeLabel = data.modeLabel; + } if (data.hasOwnProperty('triggeredBy')) { this.triggeredBy = data.triggeredBy; this.sessionsDidTrigger = undefined; @@ -1065,24 +1105,34 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { } } +export interface IFunctionBreakpointOptions extends IBaseBreakpointOptions { + name: string; +} + export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreakpoint { + public name: string; constructor( - public name: string, - enabled: boolean, - hitCondition: string | undefined, - condition: string | undefined, - logMessage: string | undefined, + opts: IFunctionBreakpointOptions, id = generateUuid() ) { - super(enabled, hitCondition, condition, logMessage, id); + super(id, opts); + this.name = opts.name; } - override toJSON(): any { - const result = super.toJSON(); - result.name = this.name; + toDAP(): DebugProtocol.FunctionBreakpoint { + return { + name: this.name, + condition: this.condition, + hitCondition: this.hitCondition, + }; + } - return result; + override toJSON(): IFunctionBreakpointOptions & { id: string } { + return { + ...super.toJSON(), + name: this.name, + }; } get supported(): boolean { @@ -1098,30 +1148,51 @@ export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreak } } +export interface IDataBreakpointOptions extends IBaseBreakpointOptions { + description: string; + dataId: string; + canPersist: boolean; + accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined; + accessType: DebugProtocol.DataBreakpointAccessType; +} + export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { + public readonly description: string; + public readonly dataId: string; + public readonly canPersist: boolean; + public readonly accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined; + public readonly accessType: DebugProtocol.DataBreakpointAccessType; constructor( - public readonly description: string, - public readonly dataId: string, - public readonly canPersist: boolean, - enabled: boolean, - hitCondition: string | undefined, - condition: string | undefined, - logMessage: string | undefined, - public readonly accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, - public readonly accessType: DebugProtocol.DataBreakpointAccessType, + opts: IDataBreakpointOptions, id = generateUuid() ) { - super(enabled, hitCondition, condition, logMessage, id); + super(id, opts); + this.description = opts.description; + this.dataId = opts.dataId; + this.canPersist = opts.canPersist; + this.accessTypes = opts.accessTypes; + this.accessType = opts.accessType; } - override toJSON(): any { - const result = super.toJSON(); - result.description = this.description; - result.dataId = this.dataId; - result.accessTypes = this.accessTypes; - result.accessType = this.accessType; - return result; + toDAP(): DebugProtocol.DataBreakpoint { + return { + dataId: this.dataId, + accessType: this.accessType, + condition: this.condition, + hitCondition: this.hitCondition, + }; + } + + override toJSON(): IDataBreakpointOptions & { id: string } { + return { + ...super.toJSON(), + description: this.description, + dataId: this.dataId, + accessTypes: this.accessTypes, + accessType: this.accessType, + canPersist: this.canPersist, + }; } get supported(): boolean { @@ -1137,35 +1208,51 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { } } +export interface IExceptionBreakpointOptions extends IBaseBreakpointOptions { + filter: string; + label: string; + supportsCondition: boolean; + description: string | undefined; + conditionDescription: string | undefined; + fallback?: boolean; +} + export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBreakpoint { private supportedSessions: Set = new Set(); + public readonly filter: string; + public readonly label: string; + public readonly supportsCondition: boolean; + public readonly description: string | undefined; + public readonly conditionDescription: string | undefined; + private fallback: boolean = false; + constructor( - public readonly filter: string, - public readonly label: string, - enabled: boolean, - public readonly supportsCondition: boolean, - condition: string | undefined, - public readonly description: string | undefined, - public readonly conditionDescription: string | undefined, - private fallback: boolean = false + opts: IExceptionBreakpointOptions, + id = generateUuid(), ) { - super(enabled, undefined, condition, undefined, generateUuid()); + super(id, opts); + this.filter = opts.filter; + this.label = opts.label; + this.supportsCondition = opts.supportsCondition; + this.description = opts.description; + this.conditionDescription = opts.conditionDescription; + this.fallback = opts.fallback || false; } - override toJSON(): any { - const result = Object.create(null); - result.filter = this.filter; - result.label = this.label; - result.enabled = this.enabled; - result.supportsCondition = this.supportsCondition; - result.conditionDescription = this.conditionDescription; - result.condition = this.condition; - result.fallback = this.fallback; - result.description = this.description; - - return result; + override toJSON(): IExceptionBreakpointOptions & { id: string } { + return { + ...super.toJSON(), + filter: this.filter, + label: this.label, + enabled: this.enabled, + supportsCondition: this.supportsCondition, + conditionDescription: this.conditionDescription, + condition: this.condition, + fallback: this.fallback, + description: this.description, + }; } setSupportedSession(sessionId: string, supported: boolean): void { @@ -1198,7 +1285,11 @@ export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBre } matches(filter: DebugProtocol.ExceptionBreakpointsFilter) { - return this.filter === filter.filter && this.label === filter.label && this.supportsCondition === !!filter.supportsCondition && this.conditionDescription === filter.conditionDescription && this.description === filter.description; + return this.filter === filter.filter + && this.label === filter.label + && this.supportsCondition === !!filter.supportsCondition + && this.conditionDescription === filter.conditionDescription + && this.description === filter.description; } override toString(): string { @@ -1206,27 +1297,48 @@ export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBre } } +export interface IInstructionBreakpointOptions extends IBaseBreakpointOptions { + instructionReference: string; + offset: number; + canPersist: boolean; + address: bigint; +} + export class InstructionBreakpoint extends BaseBreakpoint implements IInstructionBreakpoint { + public readonly instructionReference: string; + public readonly offset: number; + public readonly canPersist: boolean; + public readonly address: bigint; constructor( - public readonly instructionReference: string, - public readonly offset: number, - public readonly canPersist: boolean, - enabled: boolean, - hitCondition: string | undefined, - condition: string | undefined, - logMessage: string | undefined, - public readonly address: bigint, + opts: IInstructionBreakpointOptions, id = generateUuid() ) { - super(enabled, hitCondition, condition, logMessage, id); + super(id, opts); + this.instructionReference = opts.instructionReference; + this.offset = opts.offset; + this.canPersist = opts.canPersist; + this.address = opts.address; } - override toJSON(): DebugProtocol.InstructionBreakpoint { - const result = super.toJSON(); - result.instructionReference = this.instructionReference; - result.offset = this.offset; - return result; + toDAP(): DebugProtocol.InstructionBreakpoint { + return { + instructionReference: this.instructionReference, + condition: this.condition, + hitCondition: this.hitCondition, + mode: this.mode, + offset: this.offset, + }; + } + + override toJSON(): IInstructionBreakpointOptions & { id: string } { + return { + ...super.toJSON(), + instructionReference: this.instructionReference, + offset: this.offset, + canPersist: this.canPersist, + address: this.address, + }; } get supported(): boolean { @@ -1250,6 +1362,10 @@ export class ThreadAndSessionIds implements ITreeElement { } } +interface IBreakpointModeInternal extends DebugProtocol.BreakpointMode { + firstFromDebugType: string; +} + export class DebugModel extends Disposable implements IDebugModel { private sessions: IDebugSession[]; @@ -1258,6 +1374,7 @@ export class DebugModel extends Disposable implements IDebugModel { private readonly _onDidChangeBreakpoints = this._register(new Emitter()); private readonly _onDidChangeCallStack = this._register(new Emitter()); private readonly _onDidChangeWatchExpressions = this._register(new Emitter()); + private readonly _breakpointModes = new Map(); private breakpoints!: Breakpoint[]; private functionBreakpoints!: FunctionBreakpoint[]; private exceptionBreakpoints!: ExceptionBreakpoint[]; @@ -1492,24 +1609,33 @@ export class DebugModel extends Disposable implements IDebugModel { return this.instructionBreakpoints; } - setExceptionBreakpointsForSession(sessionId: string, data: DebugProtocol.ExceptionBreakpointsFilter[]): void { - if (data) { - let didChangeBreakpoints = false; - data.forEach(d => { - let ebp = this.exceptionBreakpoints.filter((exbp) => exbp.matches(d)).pop(); + setExceptionBreakpointsForSession(sessionId: string, filters: DebugProtocol.ExceptionBreakpointsFilter[]): void { + if (!filters) { + return; + } - if (!ebp) { - didChangeBreakpoints = true; - ebp = new ExceptionBreakpoint(d.filter, d.label, !!d.default, !!d.supportsCondition, undefined /* condition */, d.description, d.conditionDescription); - this.exceptionBreakpoints.push(ebp); - } + let didChangeBreakpoints = false; + filters.forEach((d) => { + let ebp = this.exceptionBreakpoints.filter((exbp) => exbp.matches(d)).pop(); - ebp.setSupportedSession(sessionId, true); - }); - - if (didChangeBreakpoints) { - this._onDidChangeBreakpoints.fire(undefined); + if (!ebp) { + didChangeBreakpoints = true; + ebp = new ExceptionBreakpoint({ + filter: d.filter, + label: d.label, + enabled: !!d.default, + supportsCondition: !!d.supportsCondition, + description: d.description, + conditionDescription: d.conditionDescription, + }); + this.exceptionBreakpoints.push(ebp); } + + ebp.setSupportedSession(sessionId, true); + }); + + if (didChangeBreakpoints) { + this._onDidChangeBreakpoints.fire(undefined); } } @@ -1539,7 +1665,19 @@ export class DebugModel extends Disposable implements IDebugModel { addBreakpoints(uri: uri, rawData: IBreakpointData[], fireEvent = true): IBreakpoint[] { const newBreakpoints = rawData.map(rawBp => { - return new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled === false ? false : true, rawBp.condition, rawBp.hitCondition, rawBp.logMessage, undefined, this.textFileService, this.uriIdentityService, this.logService, rawBp.id, rawBp.triggeredBy); + return new Breakpoint({ + uri, + lineNumber: rawBp.lineNumber, + column: rawBp.column, + enabled: rawBp.enabled ?? true, + condition: rawBp.condition, + hitCondition: rawBp.hitCondition, + logMessage: rawBp.logMessage, + triggeredBy: rawBp.triggeredBy, + adapterData: undefined, + mode: rawBp.mode, + modeLabel: rawBp.modeLabel, + }, this.textFileService, this.uriIdentityService, this.logService, rawBp.id); }); this.breakpoints = this.breakpoints.concat(newBreakpoints); this.breakpointsActivated = true; @@ -1635,6 +1773,37 @@ export class DebugModel extends Disposable implements IDebugModel { return undefined; } + getBreakpointModes(forBreakpointType: 'source' | 'exception' | 'data' | 'instruction'): DebugProtocol.BreakpointMode[] { + return [...this._breakpointModes.values()].filter(mode => mode.appliesTo.includes(forBreakpointType)); + } + + registerBreakpointModes(debugType: string, modes: DebugProtocol.BreakpointMode[]) { + for (const mode of modes) { + const key = `${mode.mode}/${mode.label}`; + const rec = this._breakpointModes.get(key); + if (rec) { + for (const target of mode.appliesTo) { + if (!rec.appliesTo.includes(target)) { + rec.appliesTo.push(target); + } + } + } else { + const duplicate = [...this._breakpointModes.values()].find(r => r !== rec && r.label === mode.label); + if (duplicate) { + duplicate.label = `${duplicate.label} (${duplicate.firstFromDebugType})`; + } + + this._breakpointModes.set(key, { + mode: mode.mode, + label: duplicate ? `${mode.label} (${debugType})` : mode.label, + firstFromDebugType: debugType, + description: mode.description, + appliesTo: mode.appliesTo.slice(), // avoid later mutations + }); + } + } + } + private sortAndDeDup(): void { this.breakpoints = this.breakpoints.sort((first, second) => { if (first.uri.toString() !== second.uri.toString()) { @@ -1703,8 +1872,8 @@ export class DebugModel extends Disposable implements IDebugModel { this._onDidChangeBreakpoints.fire({ changed: changed, sessionOnly: false }); } - addFunctionBreakpoint(functionName: string, id?: string): IFunctionBreakpoint { - const newFunctionBreakpoint = new FunctionBreakpoint(functionName, true, undefined, undefined, undefined, id); + addFunctionBreakpoint(functionName: string, id?: string, mode?: string): IFunctionBreakpoint { + const newFunctionBreakpoint = new FunctionBreakpoint({ name: functionName, mode }, id); this.functionBreakpoints.push(newFunctionBreakpoint); this._onDidChangeBreakpoints.fire({ added: [newFunctionBreakpoint], sessionOnly: false }); @@ -1739,8 +1908,8 @@ export class DebugModel extends Disposable implements IDebugModel { this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false }); } - addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, accessType: DebugProtocol.DataBreakpointAccessType, id?: string): void { - const newDataBreakpoint = new DataBreakpoint(label, dataId, canPersist, true, undefined, undefined, undefined, accessTypes, accessType, id); + addDataBreakpoint(opts: IDataBreakpointOptions, id?: string): void { + const newDataBreakpoint = new DataBreakpoint(opts, id); this.dataBreakpoints.push(newDataBreakpoint); this._onDidChangeBreakpoints.fire({ added: [newDataBreakpoint], sessionOnly: false }); } @@ -1770,8 +1939,8 @@ export class DebugModel extends Disposable implements IDebugModel { this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false }); } - addInstructionBreakpoint(instructionReference: string, offset: number, address: bigint, condition?: string, hitCondition?: string): void { - const newInstructionBreakpoint = new InstructionBreakpoint(instructionReference, offset, false, true, hitCondition, condition, undefined, address); + addInstructionBreakpoint(opts: IInstructionBreakpointOptions): void { + const newInstructionBreakpoint = new InstructionBreakpoint(opts); this.instructionBreakpoints.push(newInstructionBreakpoint); this._onDidChangeBreakpoints.fire({ added: [newInstructionBreakpoint], sessionOnly: true }); } diff --git a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts index 07a0024f988..b00a4fd466a 100644 --- a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts @@ -52,10 +52,11 @@ declare module DebugProtocol { This raw error might be interpreted by the client and is not shown in the UI. Some predefined values exist. Values: - 'cancelled': request was cancelled. + 'cancelled': the request was cancelled. + 'notStopped': the request may be retried once the adapter is in a 'stopped' state. etc. */ - message?: 'cancelled' | string; + message?: 'cancelled' | 'notStopped' | string; /** Contains request result if success is true and error details if success is false. */ body?: any; } @@ -71,7 +72,8 @@ declare module DebugProtocol { /** Cancel request; value of command field is 'cancel'. The `cancel` request is used by the client in two situations: - to indicate that it is no longer interested in the result produced by a specific request issued earlier - - to cancel a progress sequence. Clients should only call this request if the corresponding capability `supportsCancelRequest` is true. + - to cancel a progress sequence. + Clients should only call this request if the corresponding capability `supportsCancelRequest` is true. This request has a hint characteristic: a debug adapter can only be expected to make a 'best effort' in honoring this request but there are no guarantees. The `cancel` request may return an error if it could not cancel an operation but a client should refrain from presenting this error to end users. The request that got cancelled still needs to send a response back. This can either be a normal result (`success` attribute true) or an error response (`success` attribute false and the `message` set to `cancelled`). @@ -230,7 +232,7 @@ declare module DebugProtocol { A non-empty `output` attribute is shown as the unindented end of the group. */ group?: 'start' | 'startCollapsed' | 'end'; - /** If an attribute `variablesReference` exists and its value is > 0, the output contains objects which can be retrieved by passing `variablesReference` to the `variables` request. The value should be less than or equal to 2147483647 (2^31-1). */ + /** If an attribute `variablesReference` exists and its value is > 0, the output contains objects which can be retrieved by passing `variablesReference` to the `variables` request as long as execution remains suspended. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference?: number; /** The source location where the output was produced. */ source?: Source; @@ -430,7 +432,7 @@ declare module DebugProtocol { /** Arguments for `runInTerminal` request. */ interface RunInTerminalRequestArguments { - /** What kind of terminal to launch. */ + /** What kind of terminal to launch. Defaults to `integrated` if not specified. */ kind?: 'integrated' | 'external'; /** Title of the terminal. */ title?: string; @@ -676,7 +678,7 @@ declare module DebugProtocol { /** Arguments for `breakpointLocations` request. */ interface BreakpointLocationsArguments { - /** The source location of the breakpoints; either `source.path` or `source.reference` must be specified. */ + /** The source location of the breakpoints; either `source.path` or `source.sourceReference` must be specified. */ source: Source; /** Start line of range to search possible breakpoint locations in. If only the line is specified, the request returns all possible locations in that line. */ line: number; @@ -763,8 +765,7 @@ declare module DebugProtocol { } /** SetExceptionBreakpoints request; value of command field is 'setExceptionBreakpoints'. - The request configures the debugger's response to thrown exceptions. - If an exception is configured to break, a `stopped` event is fired (with reason `exception`). + The request configures the debugger's response to thrown exceptions. Each of the `filters`, `filterOptions`, and `exceptionOptions` in the request are independent configurations to a debug adapter indicating a kind of exception to catch. An exception thrown in a program should result in a `stopped` event from the debug adapter (with reason `exception`) if any of the configured filters match. Clients should only call this request if the corresponding capability `exceptionBreakpointFilters` returns one or more filters. */ interface SetExceptionBreakpointsRequest extends Request { @@ -786,7 +787,7 @@ declare module DebugProtocol { /** Response to `setExceptionBreakpoints` request. The response contains an array of `Breakpoint` objects with information about each exception breakpoint or filter. The `Breakpoint` objects are in the same order as the elements of the `filters`, `filterOptions`, `exceptionOptions` arrays given as arguments. If both `filters` and `filterOptions` are given, the returned array must start with `filters` information first, followed by `filterOptions` information. - The `verified` property of a `Breakpoint` object signals whether the exception breakpoint or filter could be successfully created and whether the condition or hit count expressions are valid. In case of an error the `message` property explains the problem. The `id` property can be used to introduce a unique ID for the exception breakpoint or filter so that it can be updated subsequently by sending breakpoint events. + The `verified` property of a `Breakpoint` object signals whether the exception breakpoint or filter could be successfully created and whether the condition is valid. In case of an error the `message` property explains the problem. The `id` property can be used to introduce a unique ID for the exception breakpoint or filter so that it can be updated subsequently by sending breakpoint events. For backward compatibility both the `breakpoints` array and the enclosing `body` are optional. If these elements are missing a client is not able to show problems for individual exception breakpoints or filters. */ interface SetExceptionBreakpointsResponse extends Response { @@ -809,18 +810,22 @@ declare module DebugProtocol { /** Arguments for `dataBreakpointInfo` request. */ interface DataBreakpointInfoArguments { - /** Reference to the variable container if the data breakpoint is requested for a child of the container. */ + /** Reference to the variable container if the data breakpoint is requested for a child of the container. The `variablesReference` must have been obtained in the current suspended state. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference?: number; /** The name of the variable's child to obtain data breakpoint information for. If `variablesReference` isn't specified, this can be an expression. */ name: string; + /** When `name` is an expression, evaluate it in the scope of this stack frame. If not specified, the expression is evaluated in the global scope. When `variablesReference` is specified, this property has no effect. */ + frameId?: number; + /** The mode of the desired breakpoint. If defined, this must be one of the `breakpointModes` the debug adapter advertised in its `Capabilities`. */ + mode?: string; } /** Response to `dataBreakpointInfo` request. */ interface DataBreakpointInfoResponse extends Response { body: { - /** An identifier for the data on which a data breakpoint can be registered with the `setDataBreakpoints` request or null if no data breakpoint is available. */ + /** An identifier for the data on which a data breakpoint can be registered with the `setDataBreakpoints` request or null if no data breakpoint is available. If a `variablesReference` or `frameId` is passed, the `dataId` is valid in the current suspended state, otherwise it's valid indefinitely. See 'Lifetime of Object References' in the Overview section for details. Breakpoints set using the `dataId` in the `setDataBreakpoints` request may outlive the lifetime of the associated `dataId`. */ dataId: string | null; /** UI string that describes on what data the breakpoint is set on or why a data breakpoint is not available. */ description: string; @@ -1032,7 +1037,7 @@ declare module DebugProtocol { } /** RestartFrame request; value of command field is 'restartFrame'. - The request restarts execution of the specified stackframe. + The request restarts execution of the specified stack frame. The debug adapter first sends the response and then a `stopped` event (with reason `restart`) after the restart has completed. Clients should only call this request if the corresponding capability `supportsRestartFrame` is true. */ @@ -1043,7 +1048,7 @@ declare module DebugProtocol { /** Arguments for `restartFrame` request. */ interface RestartFrameArguments { - /** Restart this stackframe. */ + /** Restart the stack frame identified by `frameId`. The `frameId` must have been obtained in the current suspended state. See 'Lifetime of Object References' in the Overview section for details. */ frameId: number; } @@ -1120,7 +1125,7 @@ declare module DebugProtocol { /** Response to `stackTrace` request. */ interface StackTraceResponse extends Response { body: { - /** The frames of the stackframe. If the array has length zero, there are no stackframes available. + /** The frames of the stack frame. If the array has length zero, there are no stack frames available. This means that there is no location information available. */ stackFrames: StackFrame[]; @@ -1130,7 +1135,7 @@ declare module DebugProtocol { } /** Scopes request; value of command field is 'scopes'. - The request returns the variable scopes for a given stackframe ID. + The request returns the variable scopes for a given stack frame ID. */ interface ScopesRequest extends Request { // command: 'scopes'; @@ -1139,14 +1144,14 @@ declare module DebugProtocol { /** Arguments for `scopes` request. */ interface ScopesArguments { - /** Retrieve the scopes for this stackframe. */ + /** Retrieve the scopes for the stack frame identified by `frameId`. The `frameId` must have been obtained in the current suspended state. See 'Lifetime of Object References' in the Overview section for details. */ frameId: number; } /** Response to `scopes` request. */ interface ScopesResponse extends Response { body: { - /** The scopes of the stackframe. If the array has length zero, there are no scopes available. */ + /** The scopes of the stack frame. If the array has length zero, there are no scopes available. */ scopes: Scope[]; }; } @@ -1162,13 +1167,17 @@ declare module DebugProtocol { /** Arguments for `variables` request. */ interface VariablesArguments { - /** The Variable reference. */ + /** The variable for which to retrieve its children. The `variablesReference` must have been obtained in the current suspended state. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference: number; /** Filter to limit the child variables to either named or indexed. If omitted, both types are fetched. */ filter?: 'indexed' | 'named'; - /** The index of the first variable to return; if omitted children start at 0. */ + /** The index of the first variable to return; if omitted children start at 0. + The attribute is only honored by a debug adapter if the corresponding capability `supportsVariablePaging` is true. + */ start?: number; - /** The number of variables to return. If count is missing or 0, all variables are returned. */ + /** The number of variables to return. If count is missing or 0, all variables are returned. + The attribute is only honored by a debug adapter if the corresponding capability `supportsVariablePaging` is true. + */ count?: number; /** Specifies details on how to format the Variable values. The attribute is only honored by a debug adapter if the corresponding capability `supportsValueFormattingOptions` is true. @@ -1195,7 +1204,7 @@ declare module DebugProtocol { /** Arguments for `setVariable` request. */ interface SetVariableArguments { - /** The reference of the variable container. */ + /** The reference of the variable container. The `variablesReference` must have been obtained in the current suspended state. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference: number; /** The name of the variable in the container. */ name: string; @@ -1212,9 +1221,7 @@ declare module DebugProtocol { value: string; /** The type of the new value. Typically shown in the UI when hovering over the value. */ type?: string; - /** If `variablesReference` is > 0, the new value is structured and its children can be retrieved by passing `variablesReference` to the `variables` request. - The value should be less than or equal to 2147483647 (2^31-1). - */ + /** If `variablesReference` is > 0, the new value is structured and its children can be retrieved by passing `variablesReference` to the `variables` request as long as execution remains suspended. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference?: number; /** The number of named child variables. The client can use this information to present the variables in a paged UI and fetch them in chunks. @@ -1226,6 +1233,11 @@ declare module DebugProtocol { The value should be less than or equal to 2147483647 (2^31-1). */ indexedVariables?: number; + /** A memory reference to a location appropriate for this result. + For pointer type eval results, this is generally a reference to the memory address contained in the pointer. + This attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. + */ + memoryReference?: string; }; } @@ -1356,16 +1368,16 @@ declare module DebugProtocol { frameId?: number; /** The context in which the evaluate request is used. Values: - 'variables': evaluate is called from a variables view context. 'watch': evaluate is called from a watch view context. 'repl': evaluate is called from a REPL context. 'hover': evaluate is called to generate the debug hover contents. This value should only be used if the corresponding capability `supportsEvaluateForHovers` is true. 'clipboard': evaluate is called to generate clipboard contents. This value should only be used if the corresponding capability `supportsClipboardContext` is true. + 'variables': evaluate is called from a variables view context. etc. */ - context?: 'variables' | 'watch' | 'repl' | 'hover' | 'clipboard' | string; + context?: 'watch' | 'repl' | 'hover' | 'clipboard' | 'variables' | string; /** Specifies details on how to format the result. The attribute is only honored by a debug adapter if the corresponding capability `supportsValueFormattingOptions` is true. */ @@ -1383,9 +1395,7 @@ declare module DebugProtocol { type?: string; /** Properties of an evaluate result that can be used to determine how to render the result in the UI. */ presentationHint?: VariablePresentationHint; - /** If `variablesReference` is > 0, the evaluate result is structured and its children can be retrieved by passing `variablesReference` to the `variables` request. - The value should be less than or equal to 2147483647 (2^31-1). - */ + /** If `variablesReference` is > 0, the evaluate result is structured and its children can be retrieved by passing `variablesReference` to the `variables` request as long as execution remains suspended. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference: number; /** The number of named child variables. The client can use this information to present the variables in a paged UI and fetch them in chunks. @@ -1399,7 +1409,7 @@ declare module DebugProtocol { indexedVariables?: number; /** A memory reference to a location appropriate for this result. For pointer type eval results, this is generally a reference to the memory address contained in the pointer. - This attribute should be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. + This attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. */ memoryReference?: string; }; @@ -1439,9 +1449,7 @@ declare module DebugProtocol { type?: string; /** Properties of a value that can be used to determine how to render the result in the UI. */ presentationHint?: VariablePresentationHint; - /** If `variablesReference` is > 0, the value is structured and its children can be retrieved by passing `variablesReference` to the `variables` request. - The value should be less than or equal to 2147483647 (2^31-1). - */ + /** If `variablesReference` is > 0, the evaluate result is structured and its children can be retrieved by passing `variablesReference` to the `variables` request as long as execution remains suspended. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference?: number; /** The number of named child variables. The client can use this information to present the variables in a paged UI and fetch them in chunks. @@ -1453,6 +1461,11 @@ declare module DebugProtocol { The value should be less than or equal to 2147483647 (2^31-1). */ indexedVariables?: number; + /** A memory reference to a location appropriate for this result. + For pointer type eval results, this is generally a reference to the memory address contained in the pointer. + This attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. + */ + memoryReference?: string; }; } @@ -1596,7 +1609,7 @@ declare module DebugProtocol { This can be used to determine the number of bytes that should be skipped before a subsequent `readMemory` request succeeds. */ unreadableBytes?: number; - /** The bytes read from memory, encoded using base64. */ + /** The bytes read from memory, encoded using base64. If the decoded length of `data` is less than the requested `count` in the original `readMemory` request, and `unreadableBytes` is zero or omitted, then the client should assume it's reached the end of readable memory. */ data?: string; }; } @@ -1667,6 +1680,42 @@ declare module DebugProtocol { }; } + /** DataAddressBreakpointInfo request; value of command field is 'DataAddressBreakpointInfo'. + Obtains information on a possible data breakpoint that could be set on a memory address or memory address range. + + Clients should only call this request if the corresponding capability `supportsDataAddressInfo` is true. + */ + interface DataAddressBreakpointInfoRequest extends Request { + // command: 'DataAddressBreakpointInfo'; + arguments: DataAddressBreakpointInfoArguments; + } + + /** Arguments for `dataAddressBreakpointInfo` request. */ + interface DataAddressBreakpointInfoArguments { + /** The address of the data for which to obtain breakpoint information. + Treated as a hex value if prefixed with `0x`, or as a decimal value otherwise. + */ + address?: string; + /** If passed, requests breakpoint information for an exclusive byte range rather than a single address. The range extends the given number of `bytes` from the start `address`. + Treated as a hex value if prefixed with `0x`, or as a decimal value otherwise. + */ + bytes?: string; + } + + /** Response to `dataAddressBreakpointInfo` request. */ + interface DataAddressBreakpointInfoResponse extends Response { + body: { + /** An identifier for the data on which a data breakpoint can be registered with the `setDataBreakpoints` request or null if no data breakpoint is available. If a `variablesReference` or `frameId` is passed, the `dataId` is valid in the current suspended state, otherwise it's valid indefinitely. See 'Lifetime of Object References' in the Overview section for details. Breakpoints set using the `dataId` in the `setDataBreakpoints` request may outlive the lifetime of the associated `dataId`. */ + dataId: string | null; + /** UI string that describes on what data the breakpoint is set on or why a data breakpoint is not available. */ + description: string; + /** Attribute lists the available access types for a potential data breakpoint. A UI client could surface this information. */ + accessTypes?: DataBreakpointAccessType[]; + /** Attribute indicates that a potential data breakpoint could be persisted across sessions. */ + canPersist?: boolean; + }; + } + /** Information about the capabilities of a debug adapter. */ interface Capabilities { /** The debug adapter supports the `configurationDone` request. */ @@ -1739,6 +1788,8 @@ declare module DebugProtocol { supportsBreakpointLocationsRequest?: boolean; /** The debug adapter supports the `clipboard` context value in the `evaluate` request. */ supportsClipboardContext?: boolean; + /** The debug adapter supports the `dataAddressBreakpointInfo` request. */ + supportsDataAddressInfo?: boolean; /** The debug adapter supports stepping granularities (argument `granularity`) for the stepping requests. */ supportsSteppingGranularity?: boolean; /** The debug adapter supports adding breakpoints based on instruction references. */ @@ -1747,6 +1798,11 @@ declare module DebugProtocol { supportsExceptionFilterOptions?: boolean; /** The debug adapter supports the `singleThread` property on the execution requests (`continue`, `next`, `stepIn`, `stepOut`, `reverseContinue`, `stepBack`). */ supportsSingleThreadExecutionRequests?: boolean; + /** Modes of breakpoints supported by the debug adapter, such as 'hardware' or 'software'. If present, the client may allow the user to select a mode and include it in its `setBreakpoints` request. + + Clients may present the first applicable mode in this array as the 'default' mode in gestures that set breakpoints. + */ + breakpointModes?: BreakpointMode[]; } /** An `ExceptionBreakpointsFilter` is shown in the UI as an filter option for configuring how exceptions are dealt with. */ @@ -1767,7 +1823,7 @@ declare module DebugProtocol { /** A structured message object. Used to return errors from requests. */ interface Message { - /** Unique identifier for the message. */ + /** Unique (within a debug adapter implementation) identifier for the message. The purpose of these error IDs is to help extension authors that have the requirement that every user visible error message needs a corresponding error number, so that users or customer support can find information about the specific error more easily. */ id: number; /** A format string for the message. Embedded variables have the form `{name}`. If variable name starts with an underscore character, the variable does not contain user data (PII) and can be safely used for telemetry purposes. @@ -1833,13 +1889,6 @@ declare module DebugProtocol { width?: number; } - /** The ModulesViewDescriptor is the container for all declarative configuration options of a module view. - For now it only specifies the columns to be shown in the modules view. - */ - interface ModulesViewDescriptor { - columns: ColumnDescriptor[]; - } - /** A Thread */ interface Thread { /** Unique identifier for the thread. */ @@ -1884,7 +1933,7 @@ declare module DebugProtocol { /** A Stackframe contains the source location. */ interface StackFrame { /** An identifier for the stack frame. It must be unique across all threads. - This id can be used to retrieve the scopes of the frame with the `scopes` request or to restart the execution of a stackframe. + This id can be used to retrieve the scopes of the frame with the `scopes` request or to restart the execution of a stack frame. */ id: number; /** The name of the stack frame, typically a method name. */ @@ -1899,7 +1948,7 @@ declare module DebugProtocol { endLine?: number; /** End position of the range covered by the stack frame. It is measured in UTF-16 code units and the client capability `columnsStartAt1` determines whether it is 0- or 1-based. */ endColumn?: number; - /** Indicates whether this frame can be restarted with the `restart` request. Clients should only use this if the debug adapter supports the `restart` request and the corresponding capability `supportsRestartRequest` is true. */ + /** Indicates whether this frame can be restarted with the `restart` request. Clients should only use this if the debug adapter supports the `restart` request and the corresponding capability `supportsRestartRequest` is true. If a debug adapter has this capability, then `canRestart` defaults to `true` if the property is absent. */ canRestart?: boolean; /** A memory reference for the current instruction pointer in this frame. */ instructionPointerReference?: string; @@ -1923,7 +1972,7 @@ declare module DebugProtocol { etc. */ presentationHint?: 'arguments' | 'locals' | 'registers' | string; - /** The variables of this scope can be retrieved by passing the value of `variablesReference` to the `variables` request. */ + /** The variables of this scope can be retrieved by passing the value of `variablesReference` to the `variables` request as long as execution remains suspended. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference: number; /** The number of named variables in this scope. The client can use this information to present the variables in a paged UI and fetch them in chunks. @@ -1971,7 +2020,7 @@ declare module DebugProtocol { presentationHint?: VariablePresentationHint; /** The evaluatable name of this variable which can be passed to the `evaluate` request to fetch the variable's value. */ evaluateName?: string; - /** If `variablesReference` is > 0, the variable is structured and its children can be retrieved by passing `variablesReference` to the `variables` request. */ + /** If `variablesReference` is > 0, the variable is structured and its children can be retrieved by passing `variablesReference` to the `variables` request as long as execution remains suspended. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference: number; /** The number of named child variables. The client can use this information to present the children in a paged UI and fetch them in chunks. @@ -1981,8 +2030,10 @@ declare module DebugProtocol { The client can use this information to present the children in a paged UI and fetch them in chunks. */ indexedVariables?: number; - /** The memory reference for the variable if the variable represents executable code, such as a function pointer. - This attribute is only required if the corresponding capability `supportsMemoryReferences` is true. + /** A memory reference associated with this variable. + For pointer type variables, this is generally a reference to the memory address contained in the pointer. + For executable data, this reference may later be used in a `disassemble` request. + This attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. */ memoryReference?: string; } @@ -2011,8 +2062,8 @@ declare module DebugProtocol { 'constant': Indicates that the object is a constant. 'readOnly': Indicates that the object is read only. 'rawString': Indicates that the object is a raw string. - 'hasObjectId': Indicates that the object can have an Object ID created for it. - 'canHaveObjectId': Indicates that the object has an Object ID associated with it. + 'hasObjectId': Indicates that the object can have an Object ID created for it. This is a vestigial attribute that is used by some clients; 'Object ID's are not specified in the protocol. + 'canHaveObjectId': Indicates that the object has an Object ID associated with it. This is a vestigial attribute that is used by some clients; 'Object ID's are not specified in the protocol. 'hasSideEffects': Indicates that the evaluation had side effects. 'hasDataBreakpoint': Indicates that the object has its value tracked by a data breakpoint. etc. @@ -2054,13 +2105,17 @@ declare module DebugProtocol { /** The expression that controls how many hits of the breakpoint are ignored. The debug adapter is expected to interpret the expression as needed. The attribute is only honored by a debug adapter if the corresponding capability `supportsHitConditionalBreakpoints` is true. + If both this property and `condition` are specified, `hitCondition` should be evaluated only if the `condition` is met, and the debug adapter should stop only if both conditions are met. */ hitCondition?: string; /** If this attribute exists and is non-empty, the debug adapter must not 'break' (stop) but log the message instead. Expressions within `{}` are interpolated. The attribute is only honored by a debug adapter if the corresponding capability `supportsLogPoints` is true. + If either `hitCondition` or `condition` is specified, then the message should only be logged if those conditions are met. */ logMessage?: string; + /** The mode of this breakpoint. If defined, this must be one of the `breakpointModes` the debug adapter advertised in its `Capabilities`. */ + mode?: string; } /** Properties of a breakpoint passed to the `setFunctionBreakpoints` request. */ @@ -2101,7 +2156,7 @@ declare module DebugProtocol { This should be a memory or instruction pointer reference from an `EvaluateResponse`, `Variable`, `StackFrame`, `GotoTarget`, or `Breakpoint`. */ instructionReference: string; - /** The offset from the instruction reference. + /** The offset from the instruction reference in bytes. This can be negative. */ offset?: number; @@ -2114,6 +2169,8 @@ declare module DebugProtocol { The attribute is only honored by a debug adapter if the corresponding capability `supportsHitConditionalBreakpoints` is true. */ hitCondition?: string; + /** The mode of this breakpoint. If defined, this must be one of the `breakpointModes` the debug adapter advertised in its `Capabilities`. */ + mode?: string; } /** Information about a breakpoint created in `setBreakpoints`, `setFunctionBreakpoints`, `setInstructionBreakpoints`, or `setDataBreakpoints` requests. */ @@ -2144,6 +2201,12 @@ declare module DebugProtocol { This can be negative. */ offset?: number; + /** A machine-readable explanation of why a breakpoint may not be verified. If a breakpoint is verified or a specific reason is not known, the adapter should omit this property. Possible values include: + + - `pending`: Indicates a breakpoint might be verified in the future, but the adapter cannot verify it in the current state. + - `failed`: Indicates a breakpoint was not able to be verified, and the adapter does not believe it can be verified without intervention. + */ + reason?: 'pending' | 'failed'; } /** The granularity of one 'step' in the stepping requests `next`, `stepIn`, `stepOut`, and `stepBack`. @@ -2259,6 +2322,8 @@ declare module DebugProtocol { The exception breaks into the debugger if the result of the condition is true. */ condition?: string; + /** The mode of this exception breakpoint. If defined, this must be one of the `breakpointModes` the debug adapter advertised in its `Capabilities`. */ + mode?: string; } /** An `ExceptionOptions` assigns configuration options to a set of exceptions. */ @@ -2328,6 +2393,11 @@ declare module DebugProtocol { endLine?: number; /** The end column of the range that corresponds to this instruction, if any. */ endColumn?: number; + /** A hint for how to present the instruction in the UI. + + A value of `invalid` may be used to indicate this instruction is 'filler' and cannot be reached by the program. For example, unreadable memory addresses may be presented is 'invalid.' + */ + presentationHint?: 'normal' | 'invalid'; } /** Logical areas that can be invalidated by the `invalidated` event. @@ -2339,5 +2409,27 @@ declare module DebugProtocol { etc. */ type InvalidatedAreas = 'all' | 'stacks' | 'threads' | 'variables' | string; + + /** A `BreakpointMode` is provided as a option when setting breakpoints on sources or instructions. */ + interface BreakpointMode { + /** The internal ID of the mode. This value is passed to the `setBreakpoints` request. */ + mode: string; + /** The name of the breakpoint mode. This is shown in the UI. */ + label: string; + /** A help text providing additional information about the breakpoint mode. This string is typically shown as a hover and can be translated. */ + description?: string; + /** Describes one or more type of breakpoint this mode applies to. */ + appliesTo: BreakpointModeApplicability[]; + } + + /** Describes one or more type of breakpoint a `BreakpointMode` applies to. This is a non-exhaustive enumeration and may expand as future breakpoint types are added. + Values: + 'source': In `SourceBreakpoint`s + 'exception': In exception breakpoints applied in the `ExceptionFilterOptions` + 'data': In data breakpoints requested in the the `DataBreakpointInfo` request + 'instruction': In `InstructionBreakpoint`s + etc. + */ + type BreakpointModeApplicability = 'source' | 'exception' | 'data' | 'instruction' | string; } diff --git a/src/vs/workbench/contrib/debug/common/debugStorage.ts b/src/vs/workbench/contrib/debug/common/debugStorage.ts index dd267f90358..b7e57fff654 100644 --- a/src/vs/workbench/contrib/debug/common/debugStorage.ts +++ b/src/vs/workbench/contrib/debug/common/debugStorage.ts @@ -65,8 +65,9 @@ export class DebugStorage extends Disposable { private loadBreakpoints(): Breakpoint[] { let result: Breakpoint[] | undefined; try { - result = JSON.parse(this.storageService.get(DEBUG_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((breakpoint: any) => { - return new Breakpoint(URI.parse(breakpoint.uri.external || breakpoint.source.uri.external), breakpoint.lineNumber, breakpoint.column, breakpoint.enabled, breakpoint.condition, breakpoint.hitCondition, breakpoint.logMessage, breakpoint.adapterData, this.textFileService, this.uriIdentityService, this.logService, breakpoint.id, breakpoint.triggeredBy); + result = JSON.parse(this.storageService.get(DEBUG_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((breakpoint: ReturnType) => { + breakpoint.uri = URI.revive(breakpoint.uri); + return new Breakpoint(breakpoint, this.textFileService, this.uriIdentityService, this.logService, breakpoint.id); }); } catch (e) { } @@ -76,8 +77,8 @@ export class DebugStorage extends Disposable { private loadFunctionBreakpoints(): FunctionBreakpoint[] { let result: FunctionBreakpoint[] | undefined; try { - result = JSON.parse(this.storageService.get(DEBUG_FUNCTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((fb: any) => { - return new FunctionBreakpoint(fb.name, fb.enabled, fb.hitCondition, fb.condition, fb.logMessage, fb.id); + result = JSON.parse(this.storageService.get(DEBUG_FUNCTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((fb: ReturnType) => { + return new FunctionBreakpoint(fb, fb.id); }); } catch (e) { } @@ -87,8 +88,8 @@ export class DebugStorage extends Disposable { private loadExceptionBreakpoints(): ExceptionBreakpoint[] { let result: ExceptionBreakpoint[] | undefined; try { - result = JSON.parse(this.storageService.get(DEBUG_EXCEPTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((exBreakpoint: any) => { - return new ExceptionBreakpoint(exBreakpoint.filter, exBreakpoint.label, exBreakpoint.enabled, exBreakpoint.supportsCondition, exBreakpoint.condition, exBreakpoint.description, exBreakpoint.conditionDescription, !!exBreakpoint.fallback); + result = JSON.parse(this.storageService.get(DEBUG_EXCEPTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((exBreakpoint: ReturnType) => { + return new ExceptionBreakpoint(exBreakpoint, exBreakpoint.id); }); } catch (e) { } @@ -98,8 +99,8 @@ export class DebugStorage extends Disposable { private loadDataBreakpoints(): DataBreakpoint[] { let result: DataBreakpoint[] | undefined; try { - result = JSON.parse(this.storageService.get(DEBUG_DATA_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((dbp: any) => { - return new DataBreakpoint(dbp.description, dbp.dataId, true, dbp.enabled, dbp.hitCondition, dbp.condition, dbp.logMessage, dbp.accessTypes, dbp.accessType, dbp.id); + result = JSON.parse(this.storageService.get(DEBUG_DATA_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((dbp: ReturnType) => { + return new DataBreakpoint(dbp, dbp.id); }); } catch (e) { } diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index 1b7bc10db56..b85e544f9bb 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -291,7 +291,7 @@ suite('Debug - Breakpoints', () => { let eventCount = 0; disposables.add(model.onDidChangeBreakpoints(() => eventCount++)); //address: string, offset: number, condition?: string, hitCondition?: string - model.addInstructionBreakpoint('0xCCCCFFFF', 0, 0n); + model.addInstructionBreakpoint({ instructionReference: '0xCCCCFFFF', offset: 0, address: 0n, canPersist: false }); assert.strictEqual(eventCount, 1); let instructionBreakpoints = model.getInstructionBreakpoints(); @@ -299,7 +299,7 @@ suite('Debug - Breakpoints', () => { assert.strictEqual(instructionBreakpoints[0].instructionReference, '0xCCCCFFFF'); assert.strictEqual(instructionBreakpoints[0].offset, 0); - model.addInstructionBreakpoint('0xCCCCEEEE', 1, 0n); + model.addInstructionBreakpoint({ instructionReference: '0xCCCCEEEE', offset: 1, address: 0n, canPersist: false }); assert.strictEqual(eventCount, 2); instructionBreakpoints = model.getInstructionBreakpoints(); assert.strictEqual(instructionBreakpoints.length, 2); @@ -313,8 +313,8 @@ suite('Debug - Breakpoints', () => { let eventCount = 0; disposables.add(model.onDidChangeBreakpoints(() => eventCount++)); - model.addDataBreakpoint('label', 'id', true, ['read'], 'read', '1'); - model.addDataBreakpoint('second', 'secondId', false, ['readWrite'], 'readWrite', '2'); + model.addDataBreakpoint({ description: 'label', dataId: 'id', canPersist: true, accessTypes: ['read'], accessType: 'read' }, '1'); + model.addDataBreakpoint({ description: 'second', dataId: 'secondId', canPersist: false, accessTypes: ['readWrite'], accessType: 'readWrite' }, '2'); model.updateDataBreakpoint('1', { condition: 'aCondition' }); model.updateDataBreakpoint('2', { hitCondition: '10' }); const dataBreakpoints = model.getDataBreakpoints(); @@ -374,7 +374,7 @@ suite('Debug - Breakpoints', () => { assert.strictEqual(result.message, 'Disabled Logpoint'); assert.strictEqual(result.icon.id, 'debug-breakpoint-log-disabled'); - model.addDataBreakpoint('label', 'id', true, ['read'], 'read'); + model.addDataBreakpoint({ description: 'label', canPersist: true, accessTypes: ['read'], accessType: 'read', dataId: 'id' }); const dataBreakpoints = model.getDataBreakpoints(); result = getBreakpointMessageAndIcon(State.Stopped, true, dataBreakpoints[0], ls, model); assert.strictEqual(result.message, 'Data Breakpoint'); diff --git a/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts b/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts index 96e097d1205..26c5549841b 100644 --- a/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts @@ -18,7 +18,7 @@ suite('DebugModel', () => { suite('FunctionBreakpoint', () => { test('Id is saved', () => { - const fbp = new FunctionBreakpoint('function', true, 'hit condition', 'condition', 'log message'); + const fbp = new FunctionBreakpoint({ name: 'function', enabled: true, hitCondition: 'hit condition', condition: 'condition', logMessage: 'log message' }); const strigified = JSON.stringify(fbp); const parsed = JSON.parse(strigified); assert.equal(parsed.id, fbp.getId()); @@ -27,10 +27,17 @@ suite('DebugModel', () => { suite('ExceptionBreakpoint', () => { test('Restored matches new', () => { - const ebp = new ExceptionBreakpoint('id', 'label', true, true, 'condition', 'description', 'condition description', false); + const ebp = new ExceptionBreakpoint({ + conditionDescription: 'condition description', + description: 'description', + filter: 'condition', + label: 'label', + supportsCondition: true, + enabled: true, + }, 'id'); const strigified = JSON.stringify(ebp); const parsed = JSON.parse(strigified); - const newEbp = new ExceptionBreakpoint(parsed.filter, parsed.label, parsed.enabled, parsed.supportsCondition, parsed.condition, parsed.description, parsed.conditionDescription, !!parsed.fallback); + const newEbp = new ExceptionBreakpoint(parsed); assert.ok(ebp.matches(newEbp)); }); }); diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index 5dca0f132e5..617f46d449f 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -15,6 +15,7 @@ import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; import { AdapterEndEvent, IAdapterManager, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IConfig, IConfigurationManager, IDataBreakpoint, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IDebugger, IExceptionBreakpoint, IExceptionInfo, IFunctionBreakpoint, IInstructionBreakpoint, ILaunch, IMemoryRegion, INewReplElementData, IRawModelUpdate, IRawStoppedDetails, IReplElement, IStackFrame, IThread, IViewModel, LoadedSourceEvent, State } from 'vs/workbench/contrib/debug/common/debug'; import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; +import { IInstructionBreakpointOptions } from 'vs/workbench/contrib/debug/common/debugModel'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage'; @@ -85,7 +86,7 @@ export class MockDebugService implements IDebugService { throw new Error('not implemented'); } - addInstructionBreakpoint(instructionReference: string, offset: number, address: bigint, condition?: string, hitCondition?: string): Promise { + addInstructionBreakpoint(opts: IInstructionBreakpointOptions): Promise { throw new Error('Method not implemented.'); } From 6722b81416d8f1ee24209ca4a19792b6e6a55beb Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 14 Feb 2024 21:52:18 -0800 Subject: [PATCH 1285/1897] Support cmd+arrow/enter when focus is outside of chat widget. (#205260) --- .../browser/contrib/navigation/arrow.ts | 38 +++++--- .../controller/chat/cellChatActions.ts | 90 ++++++++++++++++++- .../controller/chat/notebookChatContext.ts | 2 + .../controller/chat/notebookChatController.ts | 49 +++++++++- .../browser/controller/insertCellActions.ts | 3 +- .../notebook/browser/notebookBrowser.ts | 1 + .../notebook/browser/notebookEditorWidget.ts | 3 + 7 files changed, 171 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts index 2436576bbce..52eea945ec6 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts @@ -18,6 +18,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import { CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, findTargetCellEditor } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -196,12 +197,18 @@ registerAction2(class extends NotebookAction { super({ id: NOTEBOOK_FOCUS_TOP, title: localize('focusFirstCell', 'Focus First Cell'), - keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), - primary: KeyMod.CtrlCmd | KeyCode.Home, - mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }, - weight: KeybindingWeight.WorkbenchContrib - }, + keybinding: [ + { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + primary: KeyMod.CtrlCmd | KeyCode.Home, + weight: KeybindingWeight.WorkbenchContrib + }, + { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('')), + mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }, + weight: KeybindingWeight.WorkbenchContrib + } + ], }); } @@ -221,12 +228,19 @@ registerAction2(class extends NotebookAction { super({ id: NOTEBOOK_FOCUS_BOTTOM, title: localize('focusLastCell', 'Focus Last Cell'), - keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), - primary: KeyMod.CtrlCmd | KeyCode.End, - mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }, - weight: KeybindingWeight.WorkbenchContrib - }, + keybinding: [ + { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + primary: KeyMod.CtrlCmd | KeyCode.End, + mac: undefined, + weight: KeybindingWeight.WorkbenchContrib + }, + { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('')), + mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }, + weight: KeybindingWeight.WorkbenchContrib + } + ], }); } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 7b8ce9ff6df..8c97251257e 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -14,7 +14,7 @@ import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkey import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; import { NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController'; import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, getEditorFromArgsOrActivePane } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -232,6 +232,15 @@ registerAction2(class extends NotebookAction { when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_USER_DID_EDIT), weight: KeybindingWeight.EditorCore + 10, primary: KeyCode.Escape + }, + { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.not(InputFocusedContextKey), + CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('below') + ), + primary: KeyMod.CtrlCmd | KeyCode.Enter, + weight: KeybindingWeight.WorkbenchContrib } ], menu: [ @@ -483,3 +492,82 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, { ContextKeyExpr.equals(`config.${NotebookSetting.cellChat}`, true) ) }); + +registerAction2(class extends NotebookAction { + constructor() { + super({ + id: 'notebook.cell.chat.focus', + title: localize('focusNotebookChat', 'Focus Chat'), + keybinding: [ + { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.not(InputFocusedContextKey), + CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('above') + ), + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + weight: KeybindingWeight.WorkbenchContrib + }, + { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.not(InputFocusedContextKey), + CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('below') + ), + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + weight: KeybindingWeight.WorkbenchContrib + } + ], + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { + NotebookChatController.get(context.notebookEditor)?.focus(); + } +}); + +registerAction2(class extends NotebookAction { + constructor() { + super({ + id: 'notebook.cell.chat.focusNextCell', + title: localize('focusNextCell', 'Focus Next Cell'), + keybinding: [ + { + when: ContextKeyExpr.and( + CTX_NOTEBOOK_CELL_CHAT_FOCUSED, + CTX_INLINE_CHAT_FOCUSED, + ), + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + weight: KeybindingWeight.WorkbenchContrib + } + ], + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { + NotebookChatController.get(context.notebookEditor)?.focusNext(); + } +}); + +registerAction2(class extends NotebookAction { + constructor() { + super({ + id: 'notebook.cell.chat.focusPreviousCell', + title: localize('focusPreviousCell', 'Focus Previous Cell'), + keybinding: [ + { + when: ContextKeyExpr.and( + CTX_NOTEBOOK_CELL_CHAT_FOCUSED, + CTX_INLINE_CHAT_FOCUSED, + ), + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + weight: KeybindingWeight.WorkbenchContrib + } + ], + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { + NotebookChatController.get(context.notebookEditor)?.focusAbove(); + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts index af6c5e38caa..4c8a6aa024d 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts @@ -10,6 +10,8 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused")); export const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request")); export const CTX_NOTEBOOK_CHAT_USER_DID_EDIT = new RawContextKey('notebookChatUserDidEdit', false, localize('notebookChatUserDidEdit', "Whether the user did changes ontop of the notebook cell chat")); +export const CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION = new RawContextKey<'above' | 'below' | ''>('notebookChatOuterFocusPosition', '', localize('notebookChatOuterFocusPosition', "Whether the focus of the notebook editor is above or below the cell chat")); + export const MENU_CELL_CHAT_INPUT = MenuId.for('cellChatInput'); export const MENU_CELL_CHAT_WIDGET = MenuId.for('cellChatWidget'); export const MENU_CELL_CHAT_WIDGET_STATUS = MenuId.for('cellChatWidget.status'); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 61ac0a1a06c..7686f52b8b3 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -39,7 +39,7 @@ import { IInlineChatMessageAppender, InlineChatWidget } from 'vs/workbench/contr import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; import { INotebookEditor, INotebookEditorContribution, INotebookViewZone, ScrollToRevealBehavior } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; @@ -180,6 +180,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito private readonly _ctxHasActiveRequest: IContextKey; private readonly _ctxCellWidgetFocused: IContextKey; private readonly _ctxUserDidEdit: IContextKey; + private readonly _ctxOuterFocusPosition: IContextKey<'above' | 'below' | ''>; private readonly _userEditingDisposables = this._register(new DisposableStore()); private readonly _ctxLastResponseType: IContextKey; private _widget: NotebookChatWidget | undefined; @@ -203,6 +204,29 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._ctxCellWidgetFocused = CTX_NOTEBOOK_CELL_CHAT_FOCUSED.bindTo(this._contextKeyService); this._ctxLastResponseType = CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.bindTo(this._contextKeyService); this._ctxUserDidEdit = CTX_NOTEBOOK_CHAT_USER_DID_EDIT.bindTo(this._contextKeyService); + this._ctxOuterFocusPosition = CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.bindTo(this._contextKeyService); + + this._registerFocusTracker(); + } + + private _registerFocusTracker() { + this._register(this._notebookEditor.onDidChangeFocus(() => { + if (!this._widget) { + this._ctxOuterFocusPosition.set(''); + return; + } + + const widgetIndex = this._widget.afterModelPosition; + const focus = this._notebookEditor.getFocus().start; + + if (focus + 1 === widgetIndex) { + this._ctxOuterFocusPosition.set('above'); + } else if (focus === widgetIndex) { + this._ctxOuterFocusPosition.set('below'); + } else { + this._ctxOuterFocusPosition.set(''); + } + })); } run(index: number, input: string | undefined, autoSend: boolean | undefined): void { @@ -672,6 +696,25 @@ export class NotebookChatController extends Disposable implements INotebookEdito this.dismiss(); } + async focusAbove() { + if (!this._widget) { + return; + } + + const index = this._widget.afterModelPosition; + const prev = index - 1; + if (prev < 0) { + return; + } + + const cell = this._notebookEditor.cellAt(prev); + if (!cell) { + return; + } + + await this._notebookEditor.focusNotebookCell(cell, 'editor'); + } + async focusNext() { if (!this._widget) { return; @@ -686,6 +729,10 @@ export class NotebookChatController extends Disposable implements INotebookEdito await this._notebookEditor.focusNotebookCell(cell, 'editor'); } + focus() { + this._focusWidget(); + } + focusNearestWidget(index: number, direction: 'above' | 'below') { switch (direction) { case 'above': diff --git a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts index 92ecb509153..d998aed0f14 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts @@ -17,6 +17,7 @@ import { INotebookActionContext, NotebookAction } from 'vs/workbench/contrib/not import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_EDITABLE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; const INSERT_CODE_CELL_ABOVE_COMMAND_ID = 'notebook.cell.insertCodeCellAbove'; const INSERT_CODE_CELL_BELOW_COMMAND_ID = 'notebook.cell.insertCodeCellBelow'; @@ -110,7 +111,7 @@ registerAction2(class InsertCodeCellBelowAction extends InsertCellCommand { title: localize('notebookActions.insertCodeCellBelow', "Insert Code Cell Below"), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.Enter, - when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, InputFocusedContext.toNegated()), + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, InputFocusedContext.toNegated(), CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('')), weight: KeybindingWeight.WorkbenchContrib }, menu: { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index ae7113667a3..4f6205e00cc 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -467,6 +467,7 @@ export interface INotebookEditor { readonly onDidChangeViewCells: Event; readonly onDidChangeVisibleRanges: Event; readonly onDidChangeSelection: Event; + readonly onDidChangeFocus: Event; /** * An event emitted when the model of this editor has changed. */ diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 0eb7c280005..cdffe911b5d 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -154,6 +154,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD readonly onDidScroll: Event = this._onDidScroll.event; private readonly _onDidChangeActiveCell = this._register(new Emitter()); readonly onDidChangeActiveCell: Event = this._onDidChangeActiveCell.event; + private readonly _onDidChangeFocus = this._register(new Emitter()); + readonly onDidChangeFocus: Event = this._onDidChangeFocus.event; private readonly _onDidChangeSelection = this._register(new Emitter()); readonly onDidChangeSelection: Event = this._onDidChangeSelection.event; private readonly _onDidChangeVisibleRanges = this._register(new Emitter()); @@ -1010,6 +1012,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._register(this._list.onDidChangeFocus(_e => { this._onDidChangeActiveEditor.fire(this); this._onDidChangeActiveCell.fire(); + this._onDidChangeFocus.fire(); this._cursorNavMode.set(false); })); From 633992ca1cc89097c60d4a4e0eba8acb050edabb Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 15 Feb 2024 09:47:17 +0100 Subject: [PATCH 1286/1897] remove `XYZTurn#agent#agentId` because it is now just `agent` (#205266) https://github.com/microsoft/vscode/issues/199908 --- src/vs/workbench/api/common/extHostChatAgents2.ts | 4 ++-- src/vs/workbench/api/common/extHostTypes.ts | 4 ++-- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 15 ++++++++------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 360fdb9898a..78adfe193fd 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -235,11 +235,11 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { { ...ehResult, metadata: undefined }; // REQUEST turn - res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', agent: h.request.agentId, agentId: h.request.agentId })); + res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', agent: h.request.agentId })); // RESPONSE turn const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))); - res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, { extensionId: '', agent: h.request.agentId, agentId: h.request.agentId })); + res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, { extensionId: '', agent: h.request.agentId })); } return res; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index d6c3996105e..39e91887625 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4265,7 +4265,7 @@ export class ChatAgentRequestTurn implements vscode.ChatAgentRequestTurn { readonly prompt: string, readonly command: string | undefined, readonly variables: vscode.ChatAgentResolvedVariable[], - readonly agent: { extensionId: string; agent: string; agentId: string }, + readonly agent: { extensionId: string; agent: string }, ) { } } @@ -4274,7 +4274,7 @@ export class ChatAgentResponseTurn implements vscode.ChatAgentResponseTurn { constructor( readonly response: ReadonlyArray, readonly result: vscode.ChatAgentResult2, - readonly agent: { extensionId: string; agent: string; agentId: string } + readonly agent: { extensionId: string; agent: string } ) { } } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 82cdc7615f5..1762bdafb64 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -19,10 +19,9 @@ declare module 'vscode' { readonly prompt: string; /** - * The ID of the chat agent to which this request was directed. + * The name of the chat agent and contributing extension to which this request was directed. */ - // TODO@API NAME: agentId shouldbe agentName or just agent (because it is ChatAgent#name) - readonly agent: { readonly extensionId: string; readonly agent: string; readonly agentId: string }; + readonly agent: { readonly extensionId: string; readonly agent: string }; /** * The name of the {@link ChatAgentCommand command} that was selected for this request. @@ -34,7 +33,7 @@ declare module 'vscode' { */ readonly variables: ChatAgentResolvedVariable[]; - private constructor(prompt: string, command: string | undefined, variables: ChatAgentResolvedVariable[], agent: { extensionId: string; agentId: string }); + private constructor(prompt: string, command: string | undefined, variables: ChatAgentResolvedVariable[], agent: { extensionId: string; agent: string }); } // TODO@API name: Turn? @@ -50,10 +49,12 @@ declare module 'vscode' { */ readonly result: ChatAgentResult2; - // TODO@API NAME: agentId shouldbe agentName or just agent (because it is ChatAgent#name) - readonly agent: { readonly extensionId: string; readonly agent: string; readonly agentId: string }; + /** + * The name of the chat agent and contributing extension to which this request was directed. + */ + readonly agent: { readonly extensionId: string; readonly agent: string }; - private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: { extensionId: string; agentId: string }); + private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: { extensionId: string; agent: string }); } export interface ChatAgentContext { From 4c06e3f8670fb6005cf861acbe8a522243ca7b64 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 15 Feb 2024 12:02:08 +0100 Subject: [PATCH 1287/1897] rename proposal from `chatRequestAccess` to `languageModels`, move into new `lm` namespace (#205272) * rename proposal from `chatRequestAccess` to `languageModels`, move into new `lm` namespace https://github.com/microsoft/vscode/issues/199908 * fix itests --- extensions/vscode-api-tests/package.json | 2 +- .../workbench/api/common/extHost.api.impl.ts | 31 +++++++++++-------- .../common/extensionsApiProposals.ts | 2 +- ...ts => vscode.proposed.languageModels.d.ts} | 6 ++-- 4 files changed, 24 insertions(+), 17 deletions(-) rename src/vscode-dts/{vscode.proposed.chatRequestAccess.d.ts => vscode.proposed.languageModels.d.ts} (98%) diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index a35e4bc08cc..85f70af3f4f 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -8,7 +8,7 @@ "activeComment", "authSession", "chatAgents2", - "chatRequestAccess", + "languageModels", "defaultChatAgent", "contribViewsRemote", "contribStatusBarItems", diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 2f1aac77d56..4e95fc270b0 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1395,24 +1395,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } }; - // namespace: llm + // namespace: chat const chat: typeof vscode.chat = { registerChatResponseProvider(id: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata) { checkProposedApiEnabled(extension, 'chatProvider'); return extHostChatProvider.registerLanguageModel(extension, id, provider, metadata); }, - requestLanguageModelAccess(id, options) { - checkProposedApiEnabled(extension, 'chatRequestAccess'); - return extHostChatProvider.requestLanguageModelAccess(extension, id, options); - }, - get languageModels() { - checkProposedApiEnabled(extension, 'chatRequestAccess'); - return extHostChatProvider.getLanguageModelIds(); - }, - onDidChangeLanguageModels: (listener, thisArgs?, disposables?) => { - checkProposedApiEnabled(extension, 'chatRequestAccess'); - return extHostChatProvider.onDidChangeProviders(listener, thisArgs, disposables); - }, registerVariable(name: string, description: string, resolver: vscode.ChatVariableResolver) { checkProposedApiEnabled(extension, 'chatAgents2'); return extHostChatVariables.registerVariableResolver(extension, name, description, resolver); @@ -1427,6 +1415,22 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, }; + // namespace: lm + const lm: typeof vscode.lm = { + requestLanguageModelAccess(id, options) { + checkProposedApiEnabled(extension, 'languageModels'); + return extHostChatProvider.requestLanguageModelAccess(extension, id, options); + }, + get languageModels() { + checkProposedApiEnabled(extension, 'languageModels'); + return extHostChatProvider.getLanguageModelIds(); + }, + onDidChangeLanguageModels: (listener, thisArgs?, disposables?) => { + checkProposedApiEnabled(extension, 'languageModels'); + return extHostChatProvider.onDidChangeProviders(listener, thisArgs, disposables); + } + }; + // namespace: speech const speech: typeof vscode.speech = { registerSpeechProvider(id: string, provider: vscode.SpeechProvider) { @@ -1449,6 +1453,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I interactive, l10n, languages, + lm, notebooks, scm, speech, diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 879db067311..526ed79e092 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -14,7 +14,6 @@ export const allApiProposals = Object.freeze({ chatAgents2: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents2.d.ts', chatAgents2Additions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents2Additions.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', chatTab: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatTab.d.ts', codeActionAI: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionAI.d.ts', codeActionRanges: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts', @@ -66,6 +65,7 @@ export const allApiProposals = Object.freeze({ interactive: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactive.d.ts', interactiveWindow: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactiveWindow.d.ts', ipc: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.ipc.d.ts', + languageModels: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageModels.d.ts', languageStatusText: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageStatusText.d.ts', mappedEditsProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts', multiDocumentHighlightProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.multiDocumentHighlightProvider.d.ts', diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.languageModels.d.ts similarity index 98% rename from src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts rename to src/vscode-dts/vscode.proposed.languageModels.d.ts index 633c28dc61e..0e35351833b 100644 --- a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModels.d.ts @@ -152,8 +152,10 @@ declare module 'vscode' { readonly removed: readonly string[]; } - //@API DEFINE the namespace for this: lm (languageModels), copilot, ai, env,? - export namespace chat { + /** + * Namespace for language model related functionality. + */ + export namespace lm { /** * Request access to a language model. From 4f50cc2da005de52ac879866fd00d84d7d638391 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 14 Feb 2024 15:51:39 +0100 Subject: [PATCH 1288/1897] extensions: Match "Extensions" label with website --- src/vs/workbench/contrib/extensions/browser/extensionEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 53f55fa804c..658f429cff1 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -912,7 +912,7 @@ export class ExtensionEditor extends EditorPane { } if (resources.length || extension.publisherSponsorLink) { const extensionResourcesContainer = append(container, $('.resources-container.additional-details-element')); - append(extensionResourcesContainer, $('.additional-details-title', undefined, localize('resources', "Extension Resources"))); + append(extensionResourcesContainer, $('.additional-details-title', undefined, localize('resources', "Resources"))); const resourcesElement = append(extensionResourcesContainer, $('.resources')); for (const [label, uri] of resources) { this.transientDisposables.add(onClick(append(resourcesElement, $('a.resource', { title: uri.toString(), tabindex: '0' }, label)), () => this.openerService.open(uri))); From 474ca46d7bc18ddf5f663fbc7fcf31002ae36554 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 15 Feb 2024 11:53:54 +0100 Subject: [PATCH 1289/1897] Update icons --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 79504 -> 79468 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 4894dfa316d4a98f4804f67b7840b00e0e20051a..33316df13f2b83c5651215ccccfa5d5e311152fa 100644 GIT binary patch delta 726 zcmbR6mgUVGmI)3#!I!@kFfgd=Ffe?nndoD}av@>TuZbQu$MUj`Z+5HHYuK0BFR;JkAmR|>(BW{yk;~D-vB+_P;}yq0PEAgGoXwmY zTvA--xSVq}agA_Y=EmWc;5NtYk-M4uIu9m~5RWX6Sso8OB|Hs0BRnfSZ+P)|P4oKV z9p>HPeZz;vC(CD+&jDWvUlU&^-#>myer^5?{x$xG0*nH(0u}}Q3e>0%j0k)Yr>`g*%Ga zih7DQiX)0w72he5D9J0?Q1Yi#rnIm0Qkh6uURht+nX(_{LFEM%CKXF6-PkJkRWViB zZPrqMUR%%100Lha1sJ#)_!*QL7|caQ*p$?48BNTL#Mn)ZMb+8#7)^~u}2m_3=HZz3=CfuO!P5fxo$M`*u)lP!7~gDj6VwUi%ZOP6WbXW zWsgkUVZgF3B28lBo82n)CiWfno9y2?s5zuL%yM|;DC6km*y6az@tG5!(-fz3&K}N_ zT*_S5xjb-nbIo$y;U?u);mOH6EWlwLG0Xvpl;zUwO%St?*{^ z&hVb){mMtor^aWE&m~_iUpL0a8&{?5Z!sNng!q$bI3Fio(68}2Z^%DTpiq!du%O^op+Vt-!c#>O zMe~ZSinEIM6u&FcDrqP=RLWPXSGu6|Nts$%L)n6|d*xi^DdkNSZWY@qBiJf0REbmt zY}QhLUR%$^00Lhar5Jb^v>6zLmDpq%MZ}EEY#B||#LY~NMcCN+7}?p_MU_p})b$vF z2q-4UWMs!UO}SIG9<4xy7XAq{SE|6~(=QYCyySK7LMN z4xlV6qna8cD^NLyFeksX7 Date: Thu, 15 Feb 2024 11:55:29 +0100 Subject: [PATCH 1290/1897] theme: Reduce contrast between copilot chat rows --- src/vs/workbench/contrib/chat/common/chatColors.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatColors.ts b/src/vs/workbench/contrib/chat/common/chatColors.ts index a11134cac9d..385c0cf20c4 100644 --- a/src/vs/workbench/contrib/chat/common/chatColors.ts +++ b/src/vs/workbench/contrib/chat/common/chatColors.ts @@ -5,7 +5,7 @@ import { Color, RGBA } from 'vs/base/common/color'; import { localize } from 'vs/nls'; -import { badgeBackground, badgeForeground, contrastBorder, editorBackground, editorWidgetBackground, foreground, registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { badgeBackground, badgeForeground, contrastBorder, editorBackground, editorWidgetBackground, foreground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; export const chatRequestBorder = registerColor( 'chat.requestBorder', @@ -15,7 +15,7 @@ export const chatRequestBorder = registerColor( export const chatRequestBackground = registerColor( 'chat.requestBackground', - { dark: editorBackground, light: editorBackground, hcDark: editorWidgetBackground, hcLight: null }, + { dark: transparent(editorBackground, 0.62), light: transparent(editorBackground, 0.62), hcDark: editorWidgetBackground, hcLight: null }, localize('chat.requestBackground', 'The background color of a chat request.') ); From be73f5ea6db824b05c7a6810da2f9749923b06e4 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 15 Feb 2024 12:15:03 +0100 Subject: [PATCH 1291/1897] Fix context menu position when zoomed (#205276) * Add codefeature toggle * Fix button size * Fix context menu position when zoomed Also set toggle disabled color --- src/vs/base/common/network.ts | 5 + .../browser/markdownSettingRenderer.ts | 72 +++++++-- .../browser/markdownSettingRenderer.test.ts | 2 +- .../update/browser/releaseNotesEditor.ts | 153 +++++++++++++++++- 4 files changed, 214 insertions(+), 18 deletions(-) diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 5dde8ca080b..974d0c21743 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -116,6 +116,11 @@ export namespace Schemas { * Scheme used for special rendering of settings in the release notes */ export const codeSetting = 'code-setting'; + + /** + * Scheme used for special rendering of features in the release notes + */ + export const codeFeature = 'code-feature'; } export function matchesScheme(target: URI | string, scheme: string): boolean { diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index 89101b7134b..0deafe532a4 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -14,12 +14,13 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IAction } from 'vs/base/common/actions'; -const codeSettingRegex = /^/; +const codeSettingRegex = /^/; export class SimpleSettingRenderer { private _defaultSettings: DefaultSettings; private _updatedSettings = new Map(); // setting ID to user's original setting value private _encounteredSettings = new Map(); // setting ID to setting + private _featuredSettings = new Map(); // setting ID to feature value constructor( @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -29,12 +30,20 @@ export class SimpleSettingRenderer { this._defaultSettings = new DefaultSettings([], ConfigurationTarget.USER); } + get featuredSettingStates(): Map { + const result = new Map(); + for (const [settingId, value] of this._featuredSettings) { + result.set(settingId, this._configurationService.getValue(settingId) === value); + } + return result; + } + getHtmlRenderer(): (html: string) => string { return (html): string => { const match = codeSettingRegex.exec(html); - if (match && match.length === 3) { - const settingId = match[1]; - const rendered = this.render(settingId, match[2]); + if (match && match.length === 4) { + const settingId = match[2]; + const rendered = this.render(settingId, match[3], match[1] === 'codefeature'); if (rendered) { html = html.replace(codeSettingRegex, rendered); } @@ -47,6 +56,10 @@ export class SimpleSettingRenderer { return `${Schemas.codeSetting}://${settingId}${value ? `/${value}` : ''}`; } + featureToUriString(settingId: string, value?: any): string { + return `${Schemas.codeFeature}://${settingId}${value ? `/${value}` : ''}`; + } + private settingsGroups: ISettingsGroup[] | undefined = undefined; private getSetting(settingId: string): ISetting | undefined { if (!this.settingsGroups) { @@ -69,7 +82,7 @@ export class SimpleSettingRenderer { } parseValue(settingId: string, value: string): any { - if (value === 'undefined') { + if (value === 'undefined' || value === '') { return undefined; } const setting = this.getSetting(settingId); @@ -88,13 +101,16 @@ export class SimpleSettingRenderer { } } - private render(settingId: string, newValue: string): string | undefined { + private render(settingId: string, newValue: string, asFeature: boolean): string | undefined { const setting = this.getSetting(settingId); if (!setting) { return ''; } - - return this.renderSetting(setting, newValue); + if (asFeature) { + return this.renderFeature(setting, newValue); + } else { + return this.renderSetting(setting, newValue); + } } private viewInSettingsMessage(settingId: string, alreadyDisplayed: boolean) { @@ -149,7 +165,16 @@ export class SimpleSettingRenderer { private renderSetting(setting: ISetting, newValue: string | undefined): string | undefined { const href = this.settingToUriString(setting.key, newValue); const title = nls.localize('changeSettingTitle', "Try feature"); - return ``; + return ``; + } + + private renderFeature(setting: ISetting, newValue: string): string | undefined { + const href = this.featureToUriString(setting.key, newValue); + const parsedValue = this.parseValue(setting.key, newValue); + const isChecked = this._configurationService.getValue(setting.key) === parsedValue; + this._featuredSettings.set(setting.key, parsedValue); + const title = nls.localize('changeFeatureTitle', "Toggle feature with setting {0}", setting.key); + return `
`; } private getSettingMessage(setting: ISetting, newValue: boolean | string | number): string | undefined { @@ -185,7 +210,7 @@ export class SimpleSettingRenderer { const newSettingValue = this.parseValue(uri.authority, uri.path.substring(1)); const currentSettingValue = this._configurationService.inspect(settingId).userValue; - if (newSettingValue && newSettingValue === currentSettingValue && this._updatedSettings.has(settingId)) { + if ((newSettingValue !== undefined) && newSettingValue === currentSettingValue && this._updatedSettings.has(settingId)) { const restoreMessage = this.restorePreviousSettingMessage(settingId); actions.push({ class: undefined, @@ -197,7 +222,7 @@ export class SimpleSettingRenderer { return this.restoreSetting(settingId); } }); - } else if (newSettingValue) { + } else if (newSettingValue !== undefined) { const setting = this.getSetting(settingId); const trySettingMessage = setting ? this.getSettingMessage(setting, newSettingValue) : undefined; @@ -230,7 +255,7 @@ export class SimpleSettingRenderer { return actions; } - async updateSetting(uri: URI, x: number, y: number) { + private showContextMenu(uri: URI, x: number, y: number) { const actions = this.getActions(uri); if (!actions) { return; @@ -244,4 +269,27 @@ export class SimpleSettingRenderer { }, }); } + + private async setFeatureState(uri: URI) { + const settingId = uri.authority; + const newSettingValue = this.parseValue(uri.authority, uri.path.substring(1)); + let valueToSetSetting: any; + if (this._updatedSettings.has(settingId)) { + valueToSetSetting = this._updatedSettings.get(settingId); + this._updatedSettings.delete(settingId); + } else if (newSettingValue !== this._configurationService.getValue(settingId)) { + valueToSetSetting = newSettingValue; + } else { + valueToSetSetting = undefined; + } + await this._configurationService.updateValue(settingId, valueToSetSetting, ConfigurationTarget.USER); + } + + async updateSetting(uri: URI, x: number, y: number) { + if (uri.scheme === Schemas.codeSetting) { + return this.showContextMenu(uri, x, y); + } else if (uri.scheme === Schemas.codeFeature) { + return this.setFeatureState(uri); + } + } } diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index 15db5eac979..0457fbe8469 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -73,7 +73,7 @@ suite('Markdown Setting Renderer Test', () => { const htmlNoValue = ''; const renderedHtmlNoValue = htmlRenderer(htmlNoValue); assert.strictEqual(renderedHtmlNoValue, - ``); + ``); }); test('actions with no value', () => { diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index f03e00087d3..1ee92fcfdea 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -117,7 +117,9 @@ export class ReleaseNotesManager { } else if (e.message.type === 'scroll') { this.scrollPosition = e.message.value.scrollPosition; } else if (e.message.type === 'clickSetting') { - this._simpleSettingRenderer.updateSetting(URI.parse(e.message.value.uri), e.message.value.x, e.message.value.y); + const x = this._currentReleaseNotes?.webview.container.offsetLeft + e.message.value.x; + const y = this._currentReleaseNotes?.webview.container.offsetTop + e.message.value.y; + this._simpleSettingRenderer.updateSetting(URI.parse(e.message.value.uri), x, y); } })); @@ -221,7 +223,7 @@ export class ReleaseNotesManager { } private async onDidClickLink(uri: URI) { - if (uri.scheme === Schemas.codeSetting) { + if (uri.scheme === Schemas.codeSetting || uri.scheme === Schemas.codeFeature) { // handled in receive message } else { this.addGAParameters(uri, 'ReleaseNotes') @@ -260,7 +262,7 @@ export class ReleaseNotesManager { color: var(--vscode-button-foreground); background-color: var(--vscode-button-background); width: fit-content; - padding: 1px 1px 1px 1px; + padding: 1px 1px 0px 1px; font-size: 12px; overflow: hidden; text-overflow: ellipsis; @@ -298,6 +300,123 @@ export class ReleaseNotesManager { user-select: none; -webkit-user-select: none; } + + .codefeature-container { + display: flex; + } + + .codefeature { + position: relative; + display: inline-block; + width: 46px; + height: 24px; + } + + .codefeature-container input { + display: none; + } + + .toggle { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--vscode-button-background); + transition: .4s; + border-radius: 24px; + } + + .toggle:before { + position: absolute; + content: ""; + height: 16px; + width: 16px; + left: 4px; + bottom: 4px; + background-color: var(--vscode-editor-foreground); + transition: .4s; + border-radius: 50%; + } + + input:checked+.codefeature > .toggle:before { + transform: translateX(22px); + } + + .codefeature-container:has(input) .title { + line-height: 30px; + padding-left: 4px; + font-weight: bold; + } + + .codefeature-container:has(input:checked) .title:after { + content: "${nls.localize('disableFeature', "Disable this feature")}"; + } + .codefeature-container:has(input:not(:checked)) .title:after { + content: "${nls.localize('enableFeature', "Enable this feature")}"; + } + + .codefeature-container { + display: flex; + } + + .codefeature { + position: relative; + display: inline-block; + width: 58px; + height: 30px; + } + + .codefeature-container input { + display: none; + } + + .toggle { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--vscode-disabledForeground); + transition: .4s; + border-radius: 30px; + } + + .toggle:before { + position: absolute; + content: ""; + height: 22px; + width: 22px; + left: 4px; + bottom: 4px; + background-color: var(--vscode-editor-foreground); + transition: .4s; + border-radius: 50%; + } + + input:checked+.codefeature > .toggle:before { + transform: translateX(26px); + } + + input:checked+.codefeature > .toggle { + background-color: var(--vscode-button-background); + } + + .codefeature-container:has(input) .title { + line-height: 30px; + padding-left: 4px; + font-weight: bold; + } + + .codefeature-container:has(input:checked) .title:after { + content: "${nls.localize('disableFeature', "Disable this feature")}"; + } + .codefeature-container:has(input:not(:checked)) .title:after { + content: "${nls.localize('enableFeature', "Enable this feature")}"; + } + header { display: flex; align-items: center; padding-top: 1em; } @@ -332,6 +451,13 @@ export class ReleaseNotesManager { input.checked = event.data.value; } else if (event.data.type === 'setScroll') { window.scrollTo(event.data.value.scrollPosition.x, event.data.value.scrollPosition.y); + } else if (event.data.type === 'setFeaturedSettings') { + for (const [settingId, value] of event.data.value) { + const setting = document.getElementById(settingId); + if (setting instanceof HTMLInputElement) { + setting.checked = value; + } + } } }); @@ -349,8 +475,14 @@ export class ReleaseNotesManager { window.addEventListener('click', event => { const href = event.target.href ?? event.target.parentElement.href ?? event.target.parentElement.parentElement?.href; - if (href && href.startsWith('${Schemas.codeSetting}')) { - vscode.postMessage({ type: 'clickSetting', value: { uri: href, x: event.screenX, y: event.screenY }}); + if (href && (href.startsWith('${Schemas.codeSetting}') || href.startsWith('${Schemas.codeFeature}'))) { + vscode.postMessage({ type: 'clickSetting', value: { uri: href, x: event.clientX, y: event.clientY }}); + if (href.startsWith('${Schemas.codeFeature}')) { + const featureInput = event.target.parentElement.previousSibling; + if (featureInput instanceof HTMLInputElement) { + featureInput.checked = !featureInput.checked; + } + } } }); @@ -371,6 +503,7 @@ export class ReleaseNotesManager { private onDidChangeActiveWebviewEditor(input: WebviewInput | undefined): void { if (input && input === this._currentReleaseNotes) { this.updateCheckboxWebview(); + this.updateFeaturedSettingsWebview(); } } @@ -382,4 +515,14 @@ export class ReleaseNotesManager { }); } } + + private updateFeaturedSettingsWebview() { + if (this._currentReleaseNotes) { + this._currentReleaseNotes.webview.postMessage({ + type: 'setFeaturedSettings', + value: this._simpleSettingRenderer.featuredSettingStates + }); + } + } } + From 756a21701d34506a0d2f8daa8a477177f879c43a Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Thu, 15 Feb 2024 11:50:10 +0100 Subject: [PATCH 1292/1897] rename suggestions: arrow down always goes to first candidate --- src/vs/editor/contrib/rename/browser/rename.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index 9b0eef58955..6b9c5285a27 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -399,12 +399,6 @@ registerAction2(class FocusNextRenameSuggestion extends Action2 { precondition: CONTEXT_RENAME_INPUT_VISIBLE, keybinding: [ { - when: CONTEXT_RENAME_INPUT_FOCUSED, - primary: KeyCode.Tab, - weight: KeybindingWeight.EditorContrib + 99, - }, - { - when: CONTEXT_RENAME_INPUT_FOCUSED.toNegated(), primary: KeyCode.Tab, secondary: [KeyCode.DownArrow], weight: KeybindingWeight.EditorContrib + 99, From 95a3805aa71801a4318907f156f607079d9c79c7 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 15 Feb 2024 13:02:25 +0100 Subject: [PATCH 1293/1897] Polish change setting button in release notes (#205281) --- .../contrib/markdown/browser/markdownSettingRenderer.ts | 2 +- .../markdown/test/browser/markdownSettingRenderer.test.ts | 2 +- .../workbench/contrib/update/browser/releaseNotesEditor.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index 0deafe532a4..62bd755a1bf 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -165,7 +165,7 @@ export class SimpleSettingRenderer { private renderSetting(setting: ISetting, newValue: string | undefined): string | undefined { const href = this.settingToUriString(setting.key, newValue); const title = nls.localize('changeSettingTitle', "Try feature"); - return ``; + return ``; } private renderFeature(setting: ISetting, newValue: string): string | undefined { diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index 0457fbe8469..ed9ac269577 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -73,7 +73,7 @@ suite('Markdown Setting Renderer Test', () => { const htmlNoValue = ''; const renderedHtmlNoValue = htmlRenderer(htmlNoValue); assert.strictEqual(renderedHtmlNoValue, - ``); + ``); }); test('actions with no value', () => { diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 1ee92fcfdea..de8129bbdf0 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -262,7 +262,7 @@ export class ReleaseNotesManager { color: var(--vscode-button-foreground); background-color: var(--vscode-button-background); width: fit-content; - padding: 1px 1px 0px 1px; + padding: 0px 1px 1px 0px; font-size: 12px; overflow: hidden; text-overflow: ellipsis; @@ -272,11 +272,11 @@ export class ReleaseNotesManager { text-align: center; cursor: pointer; border: 1px solid var(--vscode-button-border, transparent); - line-height: 12px; + line-height: 9px; outline: 1px solid transparent; display: inline-block; margin-top: 3px; - margin-bottom: -6px !important; + margin-bottom: -4px !important; } .codesetting:hover { background-color: var(--vscode-button-hoverBackground); From fc18e59421cb31b40f646eb64c7c32b395a606c4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 15 Feb 2024 13:05:23 +0100 Subject: [PATCH 1294/1897] Allow to dictate by voice into the text editor (fix #205263) (#205264) --- .../lib/stylelint/vscode-known-variables.json | 2 + .../browser/codeEditor.contribution.ts | 1 + .../browser/dictation/editorDictation.css | 43 +++ .../browser/dictation/editorDictation.ts | 274 ++++++++++++++++++ .../electron-sandbox/inlineChatQuickVoice.ts | 1 + 5 files changed, 321 insertions(+) create mode 100644 src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.css create mode 100644 src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 5e0c031b144..e9ec91f29ea 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -796,6 +796,8 @@ "--vscode-inline-chat-expanded", "--vscode-inline-chat-quick-voice-height", "--vscode-inline-chat-quick-voice-width", + "--vscode-editor-dictation-widget-height", + "--vscode-editor-dictation-widget-width", "--vscode-interactive-session-foreground", "--vscode-interactive-result-editor-background-color", "--vscode-repl-font-family", diff --git a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts index b858435378c..ed593625401 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts @@ -23,3 +23,4 @@ import './toggleWordWrap'; import './emptyTextEditorHint/emptyTextEditorHint'; import './workbenchReferenceSearch'; import './editorLineNumberMenu'; +import './dictation/editorDictation'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.css b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.css new file mode 100644 index 00000000000..321f2b0a271 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.css @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .editor-dictation-widget { + background-color: var(--vscode-editor-background); + padding: 2px; + border-radius: 8px; + display: flex; + align-items: center; + box-shadow: 0 4px 8px var(--vscode-widget-shadow); + z-index: 1000; + min-height: var(--vscode-editor-dictation-widget-height); + line-height: var(--vscode-editor-dictation-widget-height); + max-width: var(--vscode-editor-dictation-widget-width); +} + +.monaco-editor .editor-dictation-widget .codicon.codicon-mic-filled { + display: flex; + align-items: center; + width: 16px; + height: 16px; +} + +.monaco-editor .editor-dictation-widget.recording .codicon.codicon-mic-filled { + color: var(--vscode-activityBarBadge-background); + animation: editor-dictation-animation 1s infinite; +} + +@keyframes editor-dictation-animation { + 0% { + color: var(--vscode-editorCursor-background); + } + + 50% { + color: var(--vscode-activityBarBadge-background); + } + + 100% { + color: var(--vscode-editorCursor-background); + } +} diff --git a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts new file mode 100644 index 00000000000..0150448c6bd --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts @@ -0,0 +1,274 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./editorDictation'; +import { localize2 } from 'vs/nls'; +import { IDimension, h, reset } from 'vs/base/browser/dom'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { Codicon } from 'vs/base/common/codicons'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { EditorAction2, EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { Selection } from 'vs/editor/common/core/selection'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { assertIsDefined } from 'vs/base/common/types'; + +const EDITOR_DICTATION_IN_PROGRESS = new RawContextKey('editorDictation.inProgress', false); +const VOICE_CATEGORY = localize2('voiceCategory', "Voice"); + +export class EditorDictationStartAction extends EditorAction2 { + + constructor() { + super({ + id: 'workbench.action.editorDictation.start', + title: localize2('startDictation', "Start Dictation in Editor"), + category: VOICE_CATEGORY, + precondition: ContextKeyExpr.and(HasSpeechProvider, EDITOR_DICTATION_IN_PROGRESS.toNegated(), EditorContextKeys.readOnly.toNegated()), + f1: true + }); + } + + override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void { + const keybindingService = accessor.get(IKeybindingService); + + const holdMode = keybindingService.enableKeybindingHoldMode(this.desc.id); + if (holdMode) { + let shouldCallStop = false; + + const handle = setTimeout(() => { + shouldCallStop = true; + }, 500); + + holdMode.finally(() => { + clearTimeout(handle); + + if (shouldCallStop) { + EditorDictation.get(editor)?.stop(); + } + }); + } + + EditorDictation.get(editor)?.start(); + } +} + +export class EditorDictationStopAction extends EditorAction2 { + + constructor() { + super({ + id: 'workbench.action.editorDictation.stop', + title: localize2('stopDictation', "Stop Dictation in Editor"), + category: VOICE_CATEGORY, + precondition: EDITOR_DICTATION_IN_PROGRESS, + f1: true, + keybinding: { + primary: KeyCode.Escape, + weight: KeybindingWeight.WorkbenchContrib + 100 + } + }); + } + + override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): void { + EditorDictation.get(editor)?.stop(); + } +} + +export class DictationWidget extends Disposable implements IContentWidget { + + readonly suppressMouseDown = true; + readonly allowEditorOverflow = true; + + private readonly domNode = document.createElement('div'); + private readonly elements = h('.editor-dictation-widget@main', [h('span@mic')]); + + constructor(private readonly editor: ICodeEditor) { + super(); + + this.domNode.appendChild(this.elements.root); + this.domNode.style.zIndex = '1000'; + + reset(this.elements.mic, renderIcon(Codicon.micFilled)); + } + + getId(): string { + return 'editorDictation'; + } + + getDomNode(): HTMLElement { + return this.domNode; + } + + getPosition(): IContentWidgetPosition | null { + if (!this.editor.hasModel()) { + return null; + } + + const selection = this.editor.getSelection(); + + return { + position: selection.getPosition(), + preference: [ + selection.getPosition().equals(selection.getStartPosition()) ? ContentWidgetPositionPreference.ABOVE : ContentWidgetPositionPreference.BELOW, + ContentWidgetPositionPreference.EXACT + ] + }; + } + + beforeRender(): IDimension | null { + const lineHeight = this.editor.getOption(EditorOption.lineHeight); + const width = this.editor.getLayoutInfo().contentWidth * 0.7; + + this.elements.main.style.setProperty('--vscode-editor-dictation-widget-height', `${lineHeight}px`); + this.elements.main.style.setProperty('--vscode-editor-dictation-widget-width', `${width}px`); + + return null; + } + + show() { + this.editor.addContentWidget(this); + } + + layout(): void { + this.editor.layoutContentWidget(this); + } + + active(): void { + this.elements.main.classList.add('recording'); + } + + hide() { + this.elements.main.classList.remove('recording'); + this.editor.removeContentWidget(this); + } +} + +export class EditorDictation extends Disposable implements IEditorContribution { + + static readonly ID = 'editorDictation'; + + static get(editor: ICodeEditor): EditorDictation | null { + return editor.getContribution(EditorDictation.ID); + } + + private readonly widget = this._register(new DictationWidget(this.editor)); + private readonly editorDictationInProgress = EDITOR_DICTATION_IN_PROGRESS.bindTo(this.contextKeyService); + + private sessionDisposables = this._register(new MutableDisposable()); + + constructor( + private readonly editor: ICodeEditor, + @ISpeechService private readonly speechService: ISpeechService, + @IContextKeyService private readonly contextKeyService: IContextKeyService + ) { + super(); + } + + start() { + const disposables = new DisposableStore(); + this.sessionDisposables.value = disposables; + + this.widget.show(); + disposables.add(toDisposable(() => this.widget.hide())); + + this.editorDictationInProgress.set(true); + disposables.add(toDisposable(() => this.editorDictationInProgress.reset())); + + const collection = this.editor.createDecorationsCollection(); + disposables.add(toDisposable(() => collection.clear())); + + let previewStart: Position | undefined = undefined; + + let lastReplaceTextLength = 0; + const replaceText = (text: string, isPreview: boolean) => { + if (!previewStart) { + previewStart = assertIsDefined(this.editor.getPosition()); + } + + this.editor.executeEdits(EditorDictation.ID, [ + EditOperation.replace(Range.fromPositions(previewStart, previewStart.with(undefined, previewStart.column + lastReplaceTextLength)), text) + ], [ + Selection.fromPositions(new Position(previewStart.lineNumber, previewStart.column + text.length)) + ]); + + if (isPreview) { + collection.set([ + { + range: Range.fromPositions(previewStart, previewStart.with(undefined, previewStart.column + text.length)), + options: { + description: 'editor-dictation-preview', + inlineClassName: 'ghost-text-decoration-preview' + } + } + ]); + } else { + collection.clear(); + } + + lastReplaceTextLength = text.length; + if (!isPreview) { + previewStart = undefined; + lastReplaceTextLength = 0; + } + + this.widget.layout(); + }; + + const cts = new CancellationTokenSource(); + disposables.add(toDisposable(() => cts.dispose(true))); + + const session = disposables.add(this.speechService.createSpeechToTextSession(cts.token)); + disposables.add(session.onDidChange(e => { + if (cts.token.isCancellationRequested) { + return; + } + + switch (e.status) { + case SpeechToTextStatus.Started: + this.widget.active(); + break; + case SpeechToTextStatus.Stopped: + disposables.dispose(); + break; + case SpeechToTextStatus.Recognizing: { + if (!e.text) { + return; + } + + replaceText(e.text, true); + break; + } + case SpeechToTextStatus.Recognized: { + if (!e.text) { + return; + } + + replaceText(`${e.text} `, false); + break; + } + } + })); + } + + stop(): void { + this.sessionDisposables.clear(); + } +} + +registerEditorContribution(EditorDictation.ID, EditorDictation, EditorContributionInstantiation.Lazy); +registerAction2(EditorDictationStartAction); +registerAction2(EditorDictationStopAction); diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index 60fea0a352c..ad596b50fe8 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -267,6 +267,7 @@ export class InlineChatQuickVoice implements IEditorContribution { const done = (abort: boolean) => { cts.dispose(true); + session.dispose(); listener.dispose(); this._widget.hide(); this._ctxQuickChatInProgress.reset(); From b71d2e5b0ba6d84ae5cec7591e315b2d3049f10c Mon Sep 17 00:00:00 2001 From: Andy Hippo Date: Thu, 15 Feb 2024 13:21:56 +0100 Subject: [PATCH 1295/1897] Fix memory leak in comments browser (#205162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix memory leak in comments browser * 💄 --------- Co-authored-by: Alex Ross --- .../contrib/comments/browser/commentNode.ts | 15 +++++++++------ .../contrib/comments/browser/commentReply.ts | 6 +++++- .../comments/browser/commentThreadWidget.ts | 1 + 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 3f72b63dad9..7085518f94a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -8,7 +8,7 @@ import * as dom from 'vs/base/browser/dom'; import * as languages from 'vs/editor/common/languages'; import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action, IActionRunner, IAction, Separator, ActionRunner } from 'vs/base/common/actions'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; @@ -576,12 +576,10 @@ export class CommentNode extends Disposable { this._commentEditorModel?.dispose(); - this._commentEditorDisposables.forEach(dispose => dispose.dispose()); + dispose(this._commentEditorDisposables); this._commentEditorDisposables = []; - if (this._commentEditor) { - this._commentEditor.dispose(); - this._commentEditor = null; - } + this._commentEditor?.dispose(); + this._commentEditor = null; this._commentEditContainer!.remove(); } @@ -766,6 +764,11 @@ export class CommentNode extends Disposable { }, 3000); } } + + override dispose(): void { + super.dispose(); + dispose(this._commentEditorDisposables); + } } function fillInActions(groups: [string, Array][], target: IAction[] | { primary: IAction[]; secondary: IAction[] }, useAlternativeActions: boolean, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation'): void { diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts index 4bf059c4267..21aeb3f22d5 100644 --- a/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -6,7 +6,7 @@ import * as dom from 'vs/base/browser/dom'; import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor'; import { IAction } from 'vs/base/common/actions'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { MarshalledId } from 'vs/base/common/marshallingIds'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; @@ -369,4 +369,8 @@ export class CommentReply extends Disposable { }); } + override dispose(): void { + super.dispose(); + dispose(this._commentThreadDisposables); + } } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 548157717f3..bcd9366e524 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -261,6 +261,7 @@ export class CommentThreadWidget extends override dispose() { super.dispose(); + dispose(this._commentThreadDisposables); this.updateCurrentThread(false, false); } From ed22015fc4c567bf2f1a0b13e2bcc75369d9f572 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 15 Feb 2024 13:28:05 +0100 Subject: [PATCH 1296/1897] Update codicons --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 79468 -> 79572 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 33316df13f2b83c5651215ccccfa5d5e311152fa..b34cfdc245c6a94f79b0f22da57ca8fe64b9721d 100644 GIT binary patch delta 5059 zcmXZf3w%!Z{Ri;RCxXVkaS574h(@a}5N4iIZPirCqiWi< zrma~=FVa$%rmAS!=C9WOE@oZV`I|lG>CO6AHEZ|z?Yq2Q@AEmo=RD^*=lp)>eDgcO zVCYAmg>FmqWgJHi5Y`3gy{oEbb>+~E=t;nUOyKL&)fJ`XMNjW30Pu;VR_lY1IU!$b z{e<+du5D=SQStq4fI*;b|C+jmr6+EEdku))5474*TiRG365x7m@2T&{EGez6@PBi3 zG8m~QZ`Ic=@7Qqp3oih$?a=`p(FvW=#owXbao-8F;1u4& zS$u$VIFDcALtMgd@DctOzs2wHdwhm3@fE(tH@JmwaU0*^dxo$TLm9^Q?7)uf%&zRl z2=?SXjASea@IKzpfgHrajAsH9IfSW9V>&Z9jKi795gf(Q9LsDU$8!R6IEj;)%PB16 z49?^%7O|M~xqziy$VyhThP7P6dM@Qku3{rsb1i?!b$o~$_y`~4<7}dz&+;dHoRyqj5rKnYiz-GoaRA|Zv@H_WN%L66TBBkS;BdY<}$`{C}#68#(IAD6QXhw%p93Ixgxnw1rXNyty zC~FK`l(mL)luHcVLES#Wa{tXvvG+cp)^elg5`hMzClrAd26y};J4bMDSd{jb%BQr?^doida4oFVDx+=@UYR7j=)BP<4Si1!e^9^8r1;=9y8di z+ywaCf?L#Z9|+xP|H#n&xZ5VKKn9*PbeH3LSm+MA-O%mVWatj+H!3y=sHi!3TltjX z8kL>EPBny^n)h}2bEparybz-j1h17*F@hIrRF>eiHY!l?+8C88cx{b}7QDNR$``ya zqe2F+y-`Vn*TJZ`!Rx3FaSPQsc%6(Y9=y&*wGW;~#!(f7r;%|~58;IyRYrJSjcO#k zZbsD-UU#Fq39pAy1%(%3R7-vE^l!*fS%s%t!cl>R*W0Mn!n?<)=)%+gDF@#xqYPaS zMjN^w>|^M9FvigJNMA$OBmEo=6?lp8*7-#6NaDbt^lKYJNf9li9Hsm(j@EjX;aH_>3$vAD-1v`C zW4sz}qi}-KZ4~Ax#~Dsiy6*`mE8X{mxyp%#Qw#L0Klf_I^?>k2Wu4(m zO4l30eM;9G!k;OZ8SYmuH$0$hFnmS1!thn4>pkIXO4obBW@V$#E!22J>AFw&rgDwp z5#Hw&Q7S*=2apzacWg7gWo2y|B0PIm2nn=MA4w z?lGFc!E-|*II46bA}mq9U^q|dMn@Q}bfY75BjH9z7^l?eM7xFNf$-e$2xcpPZg|+A z7Zp+3MlCmBqIn{`gGMt(cy90n|5Cca6ZTNL!4u6V;k|Bfzw(e_f2A8#VU+)DRD$nG zwcMx)-5&1ZguhT8Gj!*5+-Ob;?}ULn^4o@a$`-@vN_Uo`xhuSr2JSw0qc3zratf$) z3!YWO4ZrY=(rpwzrF3oKPNn;v@M)#HMTBl#-5nzQvGSbZZsmEy9sbMFvFzjjE;?d_ zJK>8)vu1c78qJ^KT{4}mAAf?zL$L}hPapj)u7 z8t%k}os^M=ZIw}mS^k!O5xq1*!5G7dO1GzQvH$aau^pGIb+2KWKRPyHh90BfK%-Y7 z1RpTSRt_@ER}MCMQ9>}@=#>e<1OuJ=SL#9s2by=quB319miV~%{qfi0Zzp6WlqYOU zxR`J=u`qFC;bI8r4xTNf)rlhk;f#jm(SCRumdk?KniAl*%c`G$0wITIz z>ebXcY2DJ2(u&j8rR_;;NxPDEJ3Tx-F@0isW%|1Go#_`dI%Je&+!~fXZ0@j@VZR-A zWB8`wdxoDJej{^crmrFMZ03y-F(WERY#DK2#OV=Vj_fgV{K$PHPmby_DtFZGQKz$z zm7dj@bs+1`==jldNADc{#^_sPV#e$n^U2uau`gzC%08KWV_ea=wd2l@A2fdB_>&Xb zOemgkX=2>O#)(@ew&aB7Wabp+tj&2g=gy?WNyjGloV;f8fysAr@5|kq+md_LHzjjQ z(Ui)(n7l{xUY^>2>Yl0Brp=tTb=ukK(bIFMug&j}UzvZZAhIC4pt@j7!PUah!utx> z7XE%l_>BA+jWZ6+xHdC!=GK`fXMQ}Z_pE(IU5YjrU7DRVd;jcDihCC~6knecKWF#c zesizSYcsEZNq9+iNpZpuzF+lmBm+Tnrd2VKCR8G zU0J)g_E_!pCBv3fFF96MRM%9ux9(8gsk%$`N%hO>57pmUI%esirB{{>T=v@X+~sSR zUuZx>WoFo>IQAAo4LITl%9*UujJ+}0{2UkPvSm*NmHph_373=w`0D>X@}e zt!rg%Yi8M|nVFfH?YdUx>3Ghmvz~VMtTF%GE@p1a+CFc;<@5Q@Gc*6;otgKY`F-9w z|3zpp7`iPX5R*G@Inb>K(0_Af_1cQOkFM4O!$$#E)>oC6lodX@I}gA`Nv_h4kVPTi zX#J=Rt*U8k?lbO(?*RsZF45I>r6nH}@6dMaV?d`JH6_jUA)oUe{oYTNd`rsfA?c!y3{d8de zAAdt=Z*=-`ZIrS*a6^BDcDm$cbc`i`aSu-68606N*I_e%##Lz#+Vq!#RSt@pg{l9gJf< z6F8R1Okpa=F^%InffJd*Oil@KDziDAGdPp8IGcIQ=K>b+PA=q9E@Ls5vz(Q@o7Jpg z9qYN8OU{$JxdwxtqV@Q+%4wa4(lO&;f4Ji${8yo;~!HLl=WEJHEsFb?%7oWmUU z#@&27z;}3(JNXEA@lig;U(mx%$mJlk@hI-XS^PKt3m1@sd6O~%wGmM=%3G?v-#UEbB(E!1c?~&f~gA z*r?oQc&~E1p=(1W$)P)&XH-JazSFpZ+Wv^)TIDXFvs>`NgZp~@F;wLQeuz~qvohQ!N1X{P{G$j<)~!A4+8>jq3Q)c z+^CMhS4TUlXzGL58k3^)Nehy%}xjdMC!PyK;!5M~kj| zVhvsQ3^jB;GR)B3?X8CHZigGXyBcBW?&>x}ckv?)-NoN-=uSGy(6tx1Lkpp6FwW35 z7;oqrOfYnf==F8z8c8&CZHzH=Z6q1GHpUvdHj)k9z7!Ln+nH+Uc8)W2`_c^EzVU`` z-vmRqPcN%Ow{N1M%SnbA%70^~)+ZZIQD(aF*G^8=hT9>`R%RJaSGwPXGnDQ(;Y{T; z!&ypqoN%_%9Vg6Fy5ofTN_U)afzll(EYSG-a}4iP&NW=9%rRW5oNu^HnQK_AbSDum zSGto3%a!gV!b)X<;oVAi4q>&jPy$-8Ml0@w!a8M2` zyFlSjly;&jKUKPm73$^#eyKLZ&E|X+aEJFP-CYPDP*xajQo1`5ZdXZg5Wjf9>XO{HzLBt zN;e|Hfl4 zG&6+nhEUL|!1%ycZ5=jr|L*Qr=q~Aq zp}XwYjV7P)j~cjBzhO8>=`Kx}qjV2}Xp##5xWV1Zw+!D_>LI9b3%;Wbw?lYR`ET5* zb@!X_5#=evT}t<$2p?6RHgv=Op5ZT)zcci_i-Tep4ARzFgEr;+M$=^Y9~e!T;eTi} zb%uY=Xc7(oBctgw{PPAEyu!ib`M$SP%Uth+!SRvq#{XpCZv4-NF@V_vcp2NRvG*O5DH=`*#{4WhID!($C#>3YuRN)qy z(Zm17Xl@VxiqR||{gkKu}%f;19~14ZA4S zwGN+9>Iro834)+r5(nAJzJ?WE{g8-&p7~&;L4vZsVYG69VVE+?&|RKGe0cIViR#H6N4yC;2;(K{nQV^hY3$=xTX zPHvdIZSwia9hoheN2erD*_c(9)s}U3YSPrgse7}#W*29-Wq&p;dD{NzA=C4xubAFC z{o;&%GZJPL&S;)-VP=n+yJlXQRWNJQtP8V4XRnyuI{WyXet|hjb28_2%w0bBfq9s> ze%^_k#GDm5dvd;?KVp92{7bo+xliU^%Ztv-%B#vdo_8@nG`}$amHf*K;uhpD*tFn8 zL63qJ1#JaK3a;MSa%ZrxqHzDh@P+p+Ji72|QBKjxMco%QExNw=eJOWfPaR6yIE&R2--+-d}vK_b$G?$>e}ko>SHw{YjSHg)a<1urru4pO4&~ox|o<7un5Vv(Y Rv@eF$-3|@EoiE%-{0m|KT(bZG From f396fe0e2b3e60889883e05c1d09fdf11f42c3d6 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:26:25 +0100 Subject: [PATCH 1297/1897] Git - refresh decorators when incoming/outgoing changes (#205282) --- extensions/git/src/decorationProvider.ts | 4 ++-- extensions/git/src/historyProvider.ts | 19 +++---------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index 943af377eb7..9e3e356628d 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -163,10 +163,10 @@ class GitIncomingChangesFileDecorationProvider implements FileDecorationProvider constructor(private readonly repository: Repository) { this.disposables.push(window.registerFileDecorationProvider(this)); - repository.historyProvider.onDidChangeCurrentHistoryItemGroupBase(this.onDidChangeCurrentHistoryItemGroupBase, this, this.disposables); + repository.historyProvider.onDidChangeCurrentHistoryItemGroup(this.onDidChangeCurrentHistoryItemGroup, this, this.disposables); } - private async onDidChangeCurrentHistoryItemGroupBase(): Promise { + private async onDidChangeCurrentHistoryItemGroup(): Promise { const newDecorations = new Map(); await this.collectIncomingChangesFileDecorations(newDecorations); const uris = new Set([...this.decorations.keys()].concat([...newDecorations.keys()])); diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 64230ecf46b..d7f547b6b03 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -17,9 +17,6 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec private readonly _onDidChangeCurrentHistoryItemGroup = new EventEmitter(); readonly onDidChangeCurrentHistoryItemGroup: Event = this._onDidChangeCurrentHistoryItemGroup.event; - private readonly _onDidChangeCurrentHistoryItemGroupBase = new EventEmitter(); - readonly onDidChangeCurrentHistoryItemGroupBase: Event = this._onDidChangeCurrentHistoryItemGroupBase.event; - private readonly _onDidChangeDecorations = new EventEmitter(); readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; @@ -29,8 +26,6 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec set currentHistoryItemGroup(value: SourceControlHistoryItemGroup | undefined) { this._currentHistoryItemGroup = value; this._onDidChangeCurrentHistoryItemGroup.fire(); - - this.logger.trace('GitHistoryProvider:onDidRunGitStatus - currentHistoryItemGroup:', JSON.stringify(value)); } private historyItemDecorations = new Map(); @@ -59,12 +54,13 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return; } + this._HEAD = this.repository.HEAD; + // Check if HEAD does not support incoming/outgoing (detached commit, tag) if (!this.repository.HEAD?.name || !this.repository.HEAD?.commit || this.repository.HEAD.type === RefType.Tag) { this.logger.trace('GitHistoryProvider:onDidRunGitStatus - HEAD does not support incoming/outgoing'); this.currentHistoryItemGroup = undefined; - this._HEAD = this.repository.HEAD; return; } @@ -78,16 +74,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec } : undefined }; - // Check if Upstream has changed - if (force || - this._HEAD?.upstream?.name !== this.repository.HEAD?.upstream?.name || - this._HEAD?.upstream?.remote !== this.repository.HEAD?.upstream?.remote || - this._HEAD?.upstream?.commit !== this.repository.HEAD?.upstream?.commit) { - this.logger.trace(`GitHistoryProvider:onDidRunGitStatus - Upstream has changed (${force})`); - this._onDidChangeCurrentHistoryItemGroupBase.fire(); - } - - this._HEAD = this.repository.HEAD; + this.logger.trace(`GitHistoryProvider:onDidRunGitStatus - currentHistoryItemGroup (${force}): ${JSON.stringify(this.currentHistoryItemGroup)}`); } async provideHistoryItems(historyItemGroupId: string, options: SourceControlHistoryOptions): Promise { From 4f9a706c7913884c65756edbf441cd7f801442e8 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:33:02 +0100 Subject: [PATCH 1298/1897] Git - add command to close all unmodified editors (#205278) * Git - add command to close all unmodified tabs * Fix compilation error --- extensions/git/package.json | 6 ++++++ extensions/git/package.nls.json | 1 + extensions/git/src/commands.ts | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index f35aa7989ad..4e26cff75f4 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -789,6 +789,12 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.closeAllUnmodifiedEditors", + "title": "%command.closeAllUnmodifiedEditors%", + "category": "Git", + "enablement": "!operationInProgress" + }, { "command": "git.api.getRepositories", "title": "%command.api.getRepositories%", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 28ae736038f..f6bbb18454d 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -33,6 +33,7 @@ "command.cleanAllTracked": "Discard All Tracked Changes", "command.cleanAllUntracked": "Discard All Untracked Changes", "command.closeAllDiffEditors": "Close All Diff Editors", + "command.closeAllUnmodifiedEditors": "Close All Unmodified Editors", "command.commit": "Commit", "command.commitAmend": "Commit (Amend)", "command.commitSigned": "Commit (Signed Off)", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 3f46f698ce2..7f7f769fec5 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ import * as os from 'os'; import * as path from 'path'; -import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage } from 'vscode'; +import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import { ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git'; @@ -3994,6 +3994,37 @@ export class CommandCenter { repository.closeDiffEditors(undefined, undefined, true); } + @command('git.closeAllUnmodifiedEditors') + closeUnmodifiedEditors(): void { + const editorTabsToClose: Tab[] = []; + + // Collect all modified files + const modifiedFiles: string[] = []; + for (const repository of this.model.repositories) { + modifiedFiles.push(...repository.indexGroup.resourceStates.map(r => r.resourceUri.fsPath)); + modifiedFiles.push(...repository.workingTreeGroup.resourceStates.map(r => r.resourceUri.fsPath)); + modifiedFiles.push(...repository.untrackedGroup.resourceStates.map(r => r.resourceUri.fsPath)); + modifiedFiles.push(...repository.mergeGroup.resourceStates.map(r => r.resourceUri.fsPath)); + } + + // Collect all editor tabs that are not dirty and not modified + for (const tab of window.tabGroups.all.map(g => g.tabs).flat()) { + if (tab.isDirty) { + continue; + } + + if (tab.input instanceof TabInputText || tab.input instanceof TabInputNotebook) { + const { uri } = tab.input; + if (!modifiedFiles.find(p => pathEquals(p, uri.fsPath))) { + editorTabsToClose.push(tab); + } + } + } + + // Close editors + window.tabGroups.close(editorTabsToClose, true); + } + @command('git.openRepositoriesInParentFolders') async openRepositoriesInParentFolders(): Promise { const parentRepositories: string[] = []; From 80b556e8fbc741214c54e53329442d2de782bd8f Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:54:17 +0100 Subject: [PATCH 1299/1897] SCM - do not show action for selected rows (#205292) --- src/vs/workbench/contrib/scm/browser/media/scm.css | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index c1c3290d585..fee45b4e002 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -267,20 +267,15 @@ } .scm-view .monaco-list .monaco-list-row:hover .resource-group > .actions, -.scm-view .monaco-list .monaco-list-row.selected .resource-group > .actions, .scm-view .monaco-list .monaco-list-row.focused .resource-group > .actions, .scm-view .monaco-list .monaco-list-row:hover .resource > .name > .monaco-icon-label > .actions, -.scm-view .monaco-list .monaco-list-row.selected .resource > .name > .monaco-icon-label > .actions, .scm-view .monaco-list .monaco-list-row.focused .resource > .name > .monaco-icon-label > .actions, .scm-view .monaco-list:not(.selection-multiple) .monaco-list-row .resource:hover > .actions, .scm-view .monaco-list .monaco-list-row:hover .separator-container > .actions, -.scm-view .monaco-list .monaco-list-row.selected .separator-container > .actions, .scm-view .monaco-list .monaco-list-row.focused .separator-container > .actions, .scm-view .monaco-list .monaco-list-row:hover .history-item-group > .actions, -.scm-view .monaco-list .monaco-list-row.selected .history-item-group > .actions, .scm-view .monaco-list .monaco-list-row.focused .history-item-group > .actions, .scm-view .monaco-list .monaco-list-row:hover .history-item > .actions, -.scm-view .monaco-list .monaco-list-row.selected .history-item > .actions, .scm-view .monaco-list .monaco-list-row.focused .history-item > .actions { display: block; } From 7215958b3c57945b49d3b70afdba7fb47319ca85 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 15 Feb 2024 15:57:24 +0100 Subject: [PATCH 1300/1897] add commands to navigate between hunks (#205293) https://github.com/microsoft/vscode/issues/203349 --- .../contrib/zoneWidget/browser/zoneWidget.ts | 5 +++ .../browser/inlineChat.contribution.ts | 2 + .../inlineChat/browser/inlineChatActions.ts | 40 +++++++++++++++++++ .../browser/inlineChatController.ts | 5 +++ .../browser/inlineChatStrategies.ts | 33 ++++++++++++++- 5 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts index d51aaf5eb5c..2ef43ea18a4 100644 --- a/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts +++ b/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts @@ -331,6 +331,11 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { this.editor.changeViewZones(accessor => { accessor.layoutZone(this._viewZone!.id); }); + this._positionMarkerId.set([{ + range: Range.isIRange(rangeOrPos) ? rangeOrPos : Range.fromPositions(rangeOrPos), + options: ModelDecorationOptions.EMPTY + }]); + } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index cc8dc4ca93d..ea70b146a8c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -42,6 +42,8 @@ registerAction2(InlineChatActions.DiscardAction); registerAction2(InlineChatActions.DiscardToClipboardAction); registerAction2(InlineChatActions.DiscardUndoToNewFileAction); registerAction2(InlineChatActions.CancelSessionAction); +registerAction2(InlineChatActions.MoveToNextHunk); +registerAction2(InlineChatActions.MoveToPreviousHunk); registerAction2(InlineChatActions.ArrowOutUpAction); registerAction2(InlineChatActions.ArrowOutDownAction); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 85033c64166..8deb70a073e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -640,6 +640,46 @@ export class ConfigureInlineChatAction extends AbstractInlineChatAction { } } +export class MoveToNextHunk extends AbstractInlineChatAction { + + constructor() { + super({ + id: 'inlineChat.moveToNextHunk', + title: localize2('moveToNextHunk', 'Move to Next Change'), + precondition: CTX_INLINE_CHAT_VISIBLE, + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyCode.F7 + } + }); + } + + override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, editor: ICodeEditor, ...args: any[]): void { + ctrl.moveHunk(true); + } +} + +export class MoveToPreviousHunk extends AbstractInlineChatAction { + + constructor() { + super({ + id: 'inlineChat.moveToPreviousHunk', + title: localize2('moveToPreviousHunk', 'Move to Previous Change'), + f1: true, + precondition: CTX_INLINE_CHAT_VISIBLE, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.Shift | KeyCode.F7 + } + }); + } + + override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, editor: ICodeEditor, ...args: any[]): void { + ctrl.moveHunk(false); + } +} + export class CopyRecordings extends AbstractInlineChatAction { constructor() { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 781e4477dc9..ce432473cfb 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -1057,6 +1057,11 @@ export class InlineChatController implements IEditorContribution { return this._zone.value.widget.hasFocus(); } + moveHunk(next: boolean) { + this.focus(); + this._strategy?.move?.(next); + } + populateHistory(up: boolean) { const len = InlineChatController._promptHistory.length; if (len === 0) { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 56777b09ba2..78cfd2a3bc5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -123,6 +123,8 @@ export abstract class EditModeStrategy { abstract renderChanges(response: ReplyResponse): Promise; + move?(next: boolean): void; + abstract hasFocus(): boolean; getWholeRangeDecoration(): IModelDeltaDecoration[] { @@ -401,6 +403,7 @@ type HunkDisplayData = { discardHunk: () => void; toggleDiff?: () => any; remove(): void; + move: (next: boolean) => void; }; @@ -429,7 +432,6 @@ export class LiveStrategy extends EditModeStrategy { private readonly _progressiveEditingDecorations: IEditorDecorationsCollection; - override acceptHunk: () => Promise = () => super.acceptHunk(); override discardHunk: () => Promise = () => super.discardHunk(); @@ -617,6 +619,33 @@ export class LiveStrategy extends EditModeStrategy { }); }; + const move = (next: boolean) => { + assertType(widgetData); + + const candidates: Position[] = []; + for (const item of this._session.hunkData.getInfo()) { + if (item.getState() === HunkState.Pending) { + candidates.push(item.getRangesN()[0].getStartPosition().delta(-1)); + } + } + if (candidates.length < 2) { + return; + } + for (let i = 0; i < candidates.length; i++) { + if (candidates[i].equals(widgetData.position)) { + let newPos: Position; + if (next) { + newPos = candidates[(i + 1) % candidates.length]; + } else { + newPos = candidates[(i + candidates.length - 1) % candidates.length]; + } + this._zone.updatePositionAndHeight(newPos); + renderHunks(); + break; + } + } + }; + const zoneLineNumber = this._zone.position!.lineNumber; const myDistance = zoneLineNumber <= hunkRanges[0].startLineNumber ? hunkRanges[0].startLineNumber - zoneLineNumber @@ -632,6 +661,7 @@ export class LiveStrategy extends EditModeStrategy { discardHunk, toggleDiff: !hunkData.isInsertion() ? toggleDiff : undefined, remove, + move }; this._hunkDisplayData.set(hunkData, data); @@ -674,6 +704,7 @@ export class LiveStrategy extends EditModeStrategy { this.toggleDiff = widgetData.toggleDiff; this.acceptHunk = async () => widgetData!.acceptHunk(); this.discardHunk = async () => widgetData!.discardHunk(); + this.move = next => widgetData!.move(next); } else if (this._hunkDisplayData.size > 0) { // everything accepted or rejected From b01c303b90fe931c2f4e816172fa8fdaf13ee963 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 10:50:32 -0600 Subject: [PATCH 1301/1897] add session support, get cancellation to work --- .../inlineChat/browser/inlineChatSession.ts | 9 +- .../chat/browser/terminalChatActions.ts | 4 +- .../chat/browser/terminalChatController.ts | 107 ++++++++++++++---- .../chat/browser/terminalChatWidget.ts | 17 +-- 4 files changed, 96 insertions(+), 41 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 693fde05bab..678c8177976 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -316,7 +316,7 @@ export class SessionExchange { constructor( readonly prompt: SessionPrompt, - readonly response: ReplyResponse | EmptyResponse | ErrorResponse + readonly response: ReplyResponse | EmptyResponse | ErrorResponse | TerminalResponse ) { } } @@ -324,6 +324,13 @@ export class EmptyResponse { } +export class TerminalResponse { + readonly message: string; + constructor(message: string) { + this.message = message; + } +} + export class ErrorResponse { readonly message: string; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 641737229dc..7aef66f5631 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -178,7 +178,7 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.chatWidget?.cancel(); + contr?.cancel(); } }); @@ -234,7 +234,7 @@ registerActiveXtermAction({ // TODO: precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), icon: Codicon.report, menu: [/*{ - // TODO: Enable this + // TODO: Enable id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, when: ContextKeyExpr.and(CTX_TERMINAL_CHAT_SUPPORT_ISSUE_REPORTING, CTX_TERMINAL_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), group: '2_feedback', diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 515d7b895e8..944fba8dbba 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -16,14 +16,24 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { generateUuid } from 'vs/base/common/uuid'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { marked } from 'vs/base/common/marked/marked'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { Emitter, Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; -import { CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_RESPONSE_TYPES, EditMode, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { EmptyResponse, Session, SessionExchange, SessionPrompt, TerminalResponse } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { assertType } from 'vs/base/common/types'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ITextModel } from 'vs/editor/common/model'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; const enum Message { NONE = 0, @@ -54,15 +64,17 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _terminalAgentRegisteredContextKey!: IContextKey; private readonly _lastResponseTypeContextKey!: IContextKey; - private _cancellationTokenSource!: CancellationTokenSource; + private _scopedInstantiationService: IInstantiationService | undefined; private _accessibilityRequestId: number = 0; private _messages = this._store.add(new Emitter()); - private _lastInput: string | undefined; - private _lastResponseContent: string | undefined; - get lastResponseContent(): string | undefined { return this._lastResponseContent; } + private _activeSession?: Session; + + private _fakeEditor: CodeEditorWidget | undefined; + + get lastResponseContent(): string | undefined { return (this._activeSession?.lastExchange?.response as TerminalResponse).message; } readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store); readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store); @@ -80,7 +92,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, @IChatService private readonly _chatService: IChatService, - @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService + @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, + @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, + @IModelService private readonly _modelService: IModelService ) { super(); if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { @@ -99,7 +113,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr } else { this._terminalAgentRegisteredContextKey.set(true); } - this._cancellationTokenSource = new CancellationTokenSource(); } xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { @@ -107,7 +120,25 @@ export class TerminalChatController extends Disposable implements ITerminalContr return; } this._chatWidget = new Lazy(() => { - const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); + const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._instance.domElement!)); + this._scopedInstantiationService = this._instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); + // The inline chat widget requires a parent editor that it bases the diff view on, since the + // terminal doesn't use that feature we can just pass in an unattached editor instance. + const fakeParentEditorElement = document.createElement('div'); + this._fakeEditor = this._scopedInstantiationService.createInstance( + CodeEditorWidget, + fakeParentEditorElement, + { + extraEditorClassName: 'ignore-panel-bg' + }, + { isSimpleWidget: true } + ); + + const path = `terminal-chat-input-${this._instance.instanceId}`; + const inputUri = URI.from({ path: path, scheme: Schemas.untitled, fragment: '' }); + const result: ITextModel = this._modelService.createModel('', null, inputUri, false); + this._fakeEditor.setModel(result); + const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._fakeEditor!, this._instance); chatWidget.focusTracker.onDidFocus(() => { TerminalChatController.activeChatWidget = this; if (!isDetachedTerminalInstance(this._instance)) { @@ -127,7 +158,28 @@ export class TerminalChatController extends Disposable implements ITerminalContr } cancel(): void { - this._cancellationTokenSource.cancel(); + if (this._activeSession) { + this._inlineChatSessionService.releaseSession(this._activeSession); + this._activeSession = undefined; + } + } + + private async _startSession(editor: IActiveCodeEditor, token: CancellationToken) { + if (this._activeSession) { + this._inlineChatSessionService.releaseSession(this._activeSession); + } + + const session = await this._inlineChatSessionService.createSession( + editor, + { editMode: EditMode.Live }, + token + ); + + if (!session) { + return; + } + + this._activeSession = session; } private _forcedPlaceholder: string | undefined = undefined; @@ -156,13 +208,15 @@ export class TerminalChatController extends Disposable implements ITerminalContr } async acceptInput(): Promise { - this._lastInput = this._chatWidget?.rawValue?.input(); - if (!this._lastInput) { - return; - } this._chatAccessibilityService.acceptRequest(); this._requestActiveContextKey.set(true); - const cancellationToken = this._cancellationTokenSource.token; + const cancellationToken = new CancellationTokenSource().token; + if (this._fakeEditor?.hasModel()) { + await this._startSession(this._fakeEditor, cancellationToken); + } + assertType(this._activeSession); + const inputValue = this.chatWidget!.inlineChatWidget.value; + this._activeSession!.addInput(new SessionPrompt(inputValue)); let responseContent = ''; const progressCallback = (progress: IChatProgress) => { if (cancellationToken.isCancellationRequested) { @@ -178,10 +232,11 @@ export class TerminalChatController extends Disposable implements ITerminalContr sessionId: generateUuid(), requestId, agentId: this._terminalAgentId, - message: this._lastInput, + message: inputValue, // TODO: ? variables: { variables: [] }, }; + let response: EmptyResponse | TerminalResponse = EmptyResponse; try { const task = this._chatAgentService.invokeAgent(this._terminalAgentId, requestProps, progressCallback, [], cancellationToken); this._chatWidget?.rawValue?.inlineChatWidget.updateChatMessage(undefined); @@ -191,14 +246,16 @@ export class TerminalChatController extends Disposable implements ITerminalContr // TODO: this._zone.value.widget.updateInfo(!this._session.lastExchange ? localize('thinking', "Thinking\u2026") : ''); await task; } catch (e) { - + response = e; } finally { this._requestActiveContextKey.set(false); this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(false); this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(''); this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); + if (response === EmptyResponse) { + response = new TerminalResponse(responseContent); + } } - this._lastResponseContent = responseContent; const firstCodeBlockContent = marked.lexer(responseContent).filter(token => token.type === 'code')?.[0]?.raw; const regex = /```(?\w+)\n(?[\s\S]*?)```/g; const match = regex.exec(firstCodeBlockContent); @@ -215,6 +272,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.renderMessage(responseContent, this._accessibilityRequestId, requestId); this._lastResponseTypeContextKey.set(InlineChatResponseTypes.OnlyMessages); } + if (this._activeSession?.lastInput) { + this._activeSession.addExchange(new SessionExchange(this._activeSession.lastInput, response)); + } this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); this._messages.fire(Message.ACCEPT_INPUT); } @@ -250,12 +310,15 @@ export class TerminalChatController extends Disposable implements ITerminalContr } async viewInChat(): Promise { - if (!this._lastInput || !this._lastResponseContent) { - return; - } const widget = await this._chatWidgetService.revealViewForProvider('copilot'); if (widget && widget.viewModel) { - this._chatService.addCompleteRequest(widget.viewModel.sessionId, this._lastInput, undefined, { message: this._lastResponseContent }); + const request = this._activeSession?.lastExchange; + const input = request?.prompt.value; + const response = request?.response as TerminalResponse; + if (!input || !response) { + return; + } + this._chatService.addCompleteRequest(widget.viewModel.sessionId, input, undefined, response); widget.focusLastMessage(); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index a485dd40c4f..bbe822e01a6 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -42,6 +42,7 @@ export class TerminalChatWidget extends Disposable { constructor( terminalElement: HTMLElement, + fakeParentEditor: CodeEditorWidget, private readonly _instance: ITerminalInstance, @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @@ -64,18 +65,6 @@ export class TerminalChatWidget extends Disposable { this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); this._container.prepend(this._terminalCommandWidgetContainer); - // The inline chat widget requires a parent editor that it bases the diff view on, since the - // terminal doesn't use that feature we can just pass in an unattached editor instance. - const fakeParentEditorElement = document.createElement('div'); - const fakeParentEditor = this._scopedInstantiationService.createInstance( - CodeEditorWidget, - fakeParentEditorElement, - { - extraEditorClassName: 'ignore-panel-bg' - }, - { isSimpleWidget: true } - ); - this._inlineChatWidget = this._scopedInstantiationService.createInstance( InlineChatWidget, fakeParentEditor, @@ -199,10 +188,6 @@ export class TerminalChatWidget extends Disposable { this._visibleContextKey.set(false); this._instance.focus(); } - cancel(): void { - // TODO: Impl - this._inlineChatWidget.value = ''; - } focus(): void { this._inlineChatWidget.focus(); } From 216b941cd96e4022e50b7c92d446d090c64379ae Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 10:28:12 -0800 Subject: [PATCH 1302/1897] Move to registerWorkbenchContribution2 --- .../chat/browser/terminal.chat.contribution.ts | 16 +++++++--------- .../browser/terminalChatAccessibilityHelp.ts | 1 + 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index 70804842c3e..0c2dba50035 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -3,17 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; +import { TerminalInlineChatAccessibilityHelpContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp'; +import { TerminalInlineChatAccessibleViewContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; + +import 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions'; registerTerminalContribution(TerminalChatController.ID, TerminalChatController, false); -import 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { TerminalInlineChatAccessibleViewContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView'; -import { TerminalInlineChatAccessibilityHelpContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp'; -const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(TerminalInlineChatAccessibleViewContribution, LifecyclePhase.Eventually); -workbenchContributionsRegistry.registerWorkbenchContribution(TerminalInlineChatAccessibilityHelpContribution, LifecyclePhase.Eventually); +registerWorkbenchContribution2(TerminalInlineChatAccessibleViewContribution.ID, TerminalInlineChatAccessibleViewContribution, WorkbenchPhase.Eventually); +registerWorkbenchContribution2(TerminalInlineChatAccessibilityHelpContribution.ID, TerminalInlineChatAccessibilityHelpContribution, WorkbenchPhase.Eventually); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts index 5bbe76db85f..2fe29ea5d8a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -18,6 +18,7 @@ import { TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; export class TerminalInlineChatAccessibilityHelpContribution extends Disposable { + static ID: 'terminalInlineChatAccessibilityHelpContribution'; constructor() { super(); this._register(AccessibilityHelpAction.addImplementation(106, 'terminalInlineChat', accessor => { From 58f5274002e29d30dba54da2ac53a4dc8c5a0b3b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 10:36:59 -0800 Subject: [PATCH 1303/1897] Remove scoped services, they were causing an error and breaking language highlighting --- .../chat/browser/terminalChatController.ts | 58 +++++++++---------- .../chat/browser/terminalChatWidget.ts | 12 ++-- 2 files changed, 32 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 944fba8dbba..86c17c99d22 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -3,37 +3,36 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; -import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; -import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { Lazy } from 'vs/base/common/lazy'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; -import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Lazy } from 'vs/base/common/lazy'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { marked } from 'vs/base/common/marked/marked'; +import { Schemas } from 'vs/base/common/network'; +import { assertType } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { ITextModel } from 'vs/editor/common/model'; +import { IModelService } from 'vs/editor/common/services/model'; +import { localize } from 'vs/nls'; +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 { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { generateUuid } from 'vs/base/common/uuid'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { marked } from 'vs/base/common/marked/marked'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { Emitter, Event } from 'vs/base/common/event'; -import { localize } from 'vs/nls'; -import { CTX_INLINE_CHAT_RESPONSE_TYPES, EditMode, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { EmptyResponse, Session, SessionExchange, SessionPrompt, TerminalResponse } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { assertType } from 'vs/base/common/types'; -import { IModelService } from 'vs/editor/common/services/model'; -import { ITextModel } from 'vs/editor/common/model'; -import { Schemas } from 'vs/base/common/network'; -import { URI } from 'vs/base/common/uri'; +import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; +import { CTX_INLINE_CHAT_RESPONSE_TYPES, EditMode, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; +import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; const enum Message { NONE = 0, @@ -64,8 +63,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _terminalAgentRegisteredContextKey!: IContextKey; private readonly _lastResponseTypeContextKey!: IContextKey; - private _scopedInstantiationService: IInstantiationService | undefined; - private _accessibilityRequestId: number = 0; private _messages = this._store.add(new Emitter()); @@ -97,6 +94,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IModelService private readonly _modelService: IModelService ) { super(); + if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { return; } @@ -120,12 +118,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr return; } this._chatWidget = new Lazy(() => { - const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._instance.domElement!)); - this._scopedInstantiationService = this._instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); // The inline chat widget requires a parent editor that it bases the diff view on, since the // terminal doesn't use that feature we can just pass in an unattached editor instance. const fakeParentEditorElement = document.createElement('div'); - this._fakeEditor = this._scopedInstantiationService.createInstance( + this._fakeEditor = this._instantiationService.createInstance( CodeEditorWidget, fakeParentEditorElement, { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index bbe822e01a6..67d671aae28 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -15,7 +15,6 @@ import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; @@ -35,7 +34,7 @@ export class TerminalChatWidget extends Disposable { private readonly _focusTracker: IFocusTracker; - private readonly _scopedInstantiationService: IInstantiationService; + // private readonly _scopedInstantiationService: IInstantiationService; private readonly _focusedContextKey: IContextKey; private readonly _visibleContextKey: IContextKey; private readonly _responseEditorFocusedContextKey!: IContextKey; @@ -44,15 +43,14 @@ export class TerminalChatWidget extends Disposable { terminalElement: HTMLElement, fakeParentEditor: CodeEditorWidget, private readonly _instance: ITerminalInstance, - @IInstantiationService instantiationService: IInstantiationService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, @ILanguageService private readonly _languageService: ILanguageService, @IModelService private readonly _modelService: IModelService ) { super(); - const scopedContextKeyService = this._register(this._contextKeyService.createScoped(terminalElement)); - this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); + this._focusedContextKey = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); this._visibleContextKey = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); this._responseEditorFocusedContextKey = TerminalContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); @@ -65,7 +63,7 @@ export class TerminalChatWidget extends Disposable { this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); this._container.prepend(this._terminalCommandWidgetContainer); - this._inlineChatWidget = this._scopedInstantiationService.createInstance( + this._inlineChatWidget = this._instantiationService.createInstance( InlineChatWidget, fakeParentEditor, { @@ -86,7 +84,7 @@ export class TerminalChatWidget extends Disposable { this._chatAccessibilityService.acceptResponse(command, requestId); this.showTerminalCommandWidget(); if (!this._terminalCommandWidget) { - this._terminalCommandWidget = this._register(this._scopedInstantiationService.createInstance(CodeEditorWidget, this._terminalCommandWidgetContainer, { + this._terminalCommandWidget = this._register(this._instantiationService.createInstance(CodeEditorWidget, this._terminalCommandWidgetContainer, { padding: { top: 2, bottom: 2 }, overviewRulerLanes: 0, glyphMargin: false, From 804a4816393bb289678a0457e8653ce72032d1b7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:00:18 -0800 Subject: [PATCH 1304/1897] Improve command suggestion presentation --- .../chat/browser/media/terminalChatWidget.css | 23 ++++++++++++++++--- .../chat/browser/terminalChatWidget.ts | 23 ++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css index 633b6778dfe..a916f2dd0d7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css @@ -10,6 +10,23 @@ z-index: 100; height: auto !important; } -/* .terminal-inline-chat .inline-chat .body { - display: flex; -} */ + +.terminal-inline-chat .terminal-inline-chat-response { + border: 1px solid var(--vscode-input-border, transparent); + background-color: var(--vscode-interactive-result-editor-background-color); +} + +.terminal-inline-chat .terminal-inline-chat-response:has(.monaco-editor.focused) { + border-color: var(--vscode-focusBorder, transparent); +} + +.terminal-inline-chat .terminal-inline-chat-response, +.terminal-inline-chat .terminal-inline-chat-response .monaco-editor, +.terminal-inline-chat .terminal-inline-chat-response .monaco-editor .overflow-guard { + border-radius: 4px; +} + +.terminal-inline-chat .terminal-inline-chat-response { + margin: 0 0 8px 0; + padding-left: 8px; +} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 67d671aae28..9f4f960ee5e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -13,8 +13,10 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; +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 { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; @@ -34,7 +36,6 @@ export class TerminalChatWidget extends Disposable { private readonly _focusTracker: IFocusTracker; - // private readonly _scopedInstantiationService: IInstantiationService; private readonly _focusedContextKey: IContextKey; private readonly _visibleContextKey: IContextKey; private readonly _responseEditorFocusedContextKey!: IContextKey; @@ -44,6 +45,7 @@ export class TerminalChatWidget extends Disposable { fakeParentEditor: CodeEditorWidget, private readonly _instance: ITerminalInstance, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IConfigurationService private readonly _configurationService: IConfigurationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, @ILanguageService private readonly _languageService: ILanguageService, @@ -80,12 +82,25 @@ export class TerminalChatWidget extends Disposable { this._focusTracker = this._register(trackFocus(this._container)); } + + private _getAriaLabel(): string { + const verbose = this._configurationService.getValue(AccessibilityVerbositySettingId.Chat); + if (verbose) { + // TODO: Add verbose description + } + return localize('terminalChatInput', "Terminal Chat Input"); + } + renderTerminalCommand(command: string, requestId: number, shellType?: string): void { this._chatAccessibilityService.acceptResponse(command, requestId); this.showTerminalCommandWidget(); if (!this._terminalCommandWidget) { this._terminalCommandWidget = this._register(this._instantiationService.createInstance(CodeEditorWidget, this._terminalCommandWidgetContainer, { - padding: { top: 2, bottom: 2 }, + readOnly: false, + ariaLabel: this._getAriaLabel(), + fontSize: 13, + lineHeight: 20, + padding: { top: 8, bottom: 8 }, overviewRulerLanes: 0, glyphMargin: false, lineNumbers: 'off', @@ -133,7 +148,9 @@ export class TerminalChatWidget extends Disposable { } else { this._terminalCommandWidget.setValue(command); } - this._terminalCommandWidget.getModel()?.setLanguage(this._getLanguageFromShell(shellType)); + const languageId = this._getLanguageFromShell(shellType); + console.log('languageId', languageId); + this._terminalCommandWidget.getModel()?.setLanguage(languageId); } private _getLanguageFromShell(shell?: string): string { From bf87f8dc1e9afcef04eb8465f221628ee13645d3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 12:20:22 -0600 Subject: [PATCH 1305/1897] Revert "add session support, get cancellation to work" This reverts commit b01c303b90fe931c2f4e816172fa8fdaf13ee963. --- .../inlineChat/browser/inlineChatSession.ts | 9 +- .../chat/browser/terminalChatActions.ts | 4 +- .../chat/browser/terminalChatController.ts | 102 +++++++----------- .../chat/browser/terminalChatWidget.ts | 17 ++- 4 files changed, 60 insertions(+), 72 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 678c8177976..693fde05bab 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -316,7 +316,7 @@ export class SessionExchange { constructor( readonly prompt: SessionPrompt, - readonly response: ReplyResponse | EmptyResponse | ErrorResponse | TerminalResponse + readonly response: ReplyResponse | EmptyResponse | ErrorResponse ) { } } @@ -324,13 +324,6 @@ export class EmptyResponse { } -export class TerminalResponse { - readonly message: string; - constructor(message: string) { - this.message = message; - } -} - export class ErrorResponse { readonly message: string; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 7aef66f5631..641737229dc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -178,7 +178,7 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.cancel(); + contr?.chatWidget?.cancel(); } }); @@ -234,7 +234,7 @@ registerActiveXtermAction({ // TODO: precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), icon: Codicon.report, menu: [/*{ - // TODO: Enable + // TODO: Enable this id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, when: ContextKeyExpr.and(CTX_TERMINAL_CHAT_SUPPORT_ISSUE_REPORTING, CTX_TERMINAL_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), group: '2_feedback', diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 86c17c99d22..54430cd8fee 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -25,14 +25,25 @@ import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { EmptyResponse, Session, SessionExchange, SessionPrompt, TerminalResponse } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; -import { CTX_INLINE_CHAT_RESPONSE_TYPES, EditMode, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; -import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; +import { generateUuid } from 'vs/base/common/uuid'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { marked } from 'vs/base/common/marked/marked'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { Emitter, Event } from 'vs/base/common/event'; +import { localize } from 'vs/nls'; +import { CTX_INLINE_CHAT_RESPONSE_TYPES, EditMode, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { EmptyResponse, Session, SessionExchange, SessionPrompt, TerminalResponse } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { assertType } from 'vs/base/common/types'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ITextModel } from 'vs/editor/common/model'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; const enum Message { NONE = 0, @@ -63,15 +74,15 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _terminalAgentRegisteredContextKey!: IContextKey; private readonly _lastResponseTypeContextKey!: IContextKey; + private _scopedInstantiationService: IInstantiationService | undefined; + private _accessibilityRequestId: number = 0; private _messages = this._store.add(new Emitter()); - private _activeSession?: Session; - - private _fakeEditor: CodeEditorWidget | undefined; - - get lastResponseContent(): string | undefined { return (this._activeSession?.lastExchange?.response as TerminalResponse).message; } + private _lastInput: string | undefined; + private _lastResponseContent: string | undefined; + get lastResponseContent(): string | undefined { return this._lastResponseContent; } readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store); readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store); @@ -89,9 +100,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, @IChatService private readonly _chatService: IChatService, - @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, - @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, - @IModelService private readonly _modelService: IModelService + @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService ) { super(); @@ -111,6 +120,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } else { this._terminalAgentRegisteredContextKey.set(true); } + this._cancellationTokenSource = new CancellationTokenSource(); } xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { @@ -118,10 +128,12 @@ export class TerminalChatController extends Disposable implements ITerminalContr return; } this._chatWidget = new Lazy(() => { + const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._instance.domElement!)); + this._scopedInstantiationService = this._instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); // The inline chat widget requires a parent editor that it bases the diff view on, since the // terminal doesn't use that feature we can just pass in an unattached editor instance. const fakeParentEditorElement = document.createElement('div'); - this._fakeEditor = this._instantiationService.createInstance( + this._fakeEditor = this._scopedInstantiationService.createInstance( CodeEditorWidget, fakeParentEditorElement, { @@ -154,28 +166,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } cancel(): void { - if (this._activeSession) { - this._inlineChatSessionService.releaseSession(this._activeSession); - this._activeSession = undefined; - } - } - - private async _startSession(editor: IActiveCodeEditor, token: CancellationToken) { - if (this._activeSession) { - this._inlineChatSessionService.releaseSession(this._activeSession); - } - - const session = await this._inlineChatSessionService.createSession( - editor, - { editMode: EditMode.Live }, - token - ); - - if (!session) { - return; - } - - this._activeSession = session; + this._cancellationTokenSource.cancel(); } private _forcedPlaceholder: string | undefined = undefined; @@ -204,15 +195,13 @@ export class TerminalChatController extends Disposable implements ITerminalContr } async acceptInput(): Promise { + this._lastInput = this._chatWidget?.rawValue?.input(); + if (!this._lastInput) { + return; + } this._chatAccessibilityService.acceptRequest(); this._requestActiveContextKey.set(true); - const cancellationToken = new CancellationTokenSource().token; - if (this._fakeEditor?.hasModel()) { - await this._startSession(this._fakeEditor, cancellationToken); - } - assertType(this._activeSession); - const inputValue = this.chatWidget!.inlineChatWidget.value; - this._activeSession!.addInput(new SessionPrompt(inputValue)); + const cancellationToken = this._cancellationTokenSource.token; let responseContent = ''; const progressCallback = (progress: IChatProgress) => { if (cancellationToken.isCancellationRequested) { @@ -228,11 +217,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr sessionId: generateUuid(), requestId, agentId: this._terminalAgentId, - message: inputValue, + message: this._lastInput, // TODO: ? variables: { variables: [] }, }; - let response: EmptyResponse | TerminalResponse = EmptyResponse; try { const task = this._chatAgentService.invokeAgent(this._terminalAgentId, requestProps, progressCallback, [], cancellationToken); this._chatWidget?.rawValue?.inlineChatWidget.updateChatMessage(undefined); @@ -242,16 +230,14 @@ export class TerminalChatController extends Disposable implements ITerminalContr // TODO: this._zone.value.widget.updateInfo(!this._session.lastExchange ? localize('thinking', "Thinking\u2026") : ''); await task; } catch (e) { - response = e; + } finally { this._requestActiveContextKey.set(false); this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(false); this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(''); this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); - if (response === EmptyResponse) { - response = new TerminalResponse(responseContent); - } } + this._lastResponseContent = responseContent; const firstCodeBlockContent = marked.lexer(responseContent).filter(token => token.type === 'code')?.[0]?.raw; const regex = /```(?\w+)\n(?[\s\S]*?)```/g; const match = regex.exec(firstCodeBlockContent); @@ -268,9 +254,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.renderMessage(responseContent, this._accessibilityRequestId, requestId); this._lastResponseTypeContextKey.set(InlineChatResponseTypes.OnlyMessages); } - if (this._activeSession?.lastInput) { - this._activeSession.addExchange(new SessionExchange(this._activeSession.lastInput, response)); - } this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); this._messages.fire(Message.ACCEPT_INPUT); } @@ -306,15 +289,12 @@ export class TerminalChatController extends Disposable implements ITerminalContr } async viewInChat(): Promise { + if (!this._lastInput || !this._lastResponseContent) { + return; + } const widget = await this._chatWidgetService.revealViewForProvider('copilot'); if (widget && widget.viewModel) { - const request = this._activeSession?.lastExchange; - const input = request?.prompt.value; - const response = request?.response as TerminalResponse; - if (!input || !response) { - return; - } - this._chatService.addCompleteRequest(widget.viewModel.sessionId, input, undefined, response); + this._chatService.addCompleteRequest(widget.viewModel.sessionId, this._lastInput, undefined, { message: this._lastResponseContent }); widget.focusLastMessage(); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 9f4f960ee5e..39892d8a381 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -42,7 +42,6 @@ export class TerminalChatWidget extends Disposable { constructor( terminalElement: HTMLElement, - fakeParentEditor: CodeEditorWidget, private readonly _instance: ITerminalInstance, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -65,6 +64,18 @@ export class TerminalChatWidget extends Disposable { this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); this._container.prepend(this._terminalCommandWidgetContainer); + // The inline chat widget requires a parent editor that it bases the diff view on, since the + // terminal doesn't use that feature we can just pass in an unattached editor instance. + const fakeParentEditorElement = document.createElement('div'); + const fakeParentEditor = this._instantiationService.createInstance( + CodeEditorWidget, + fakeParentEditorElement, + { + extraEditorClassName: 'ignore-panel-bg' + }, + { isSimpleWidget: true } + ); + this._inlineChatWidget = this._instantiationService.createInstance( InlineChatWidget, fakeParentEditor, @@ -203,6 +214,10 @@ export class TerminalChatWidget extends Disposable { this._visibleContextKey.set(false); this._instance.focus(); } + cancel(): void { + // TODO: Impl + this._inlineChatWidget.value = ''; + } focus(): void { this._inlineChatWidget.focus(); } From 55b7c86d9a818977b50d602a01d2bdfe7e72f74b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 13:00:27 -0600 Subject: [PATCH 1306/1897] use chat model --- .../chat/browser/terminalChatActions.ts | 4 +- .../chat/browser/terminalChatController.ts | 73 +++++++++++++++++-- 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 641737229dc..a1561e9b63c 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -63,7 +63,7 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.chatWidget?.hide(); + contr?.clear(); } }); @@ -178,7 +178,7 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.chatWidget?.cancel(); + contr?.cancel(); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 54430cd8fee..6daeb2bd3cb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -44,6 +44,7 @@ import { IModelService } from 'vs/editor/common/services/model'; import { ITextModel } from 'vs/editor/common/model'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; +import { ChatRequestModel } from 'vs/workbench/contrib/chat/common/chatModel'; const enum Message { NONE = 0, @@ -80,15 +81,22 @@ export class TerminalChatController extends Disposable implements ITerminalContr private _messages = this._store.add(new Emitter()); + private _currentRequest: ChatRequestModel | undefined; + private _lastInput: string | undefined; private _lastResponseContent: string | undefined; - get lastResponseContent(): string | undefined { return this._lastResponseContent; } + get lastResponseContent(): string | undefined { + // TODO: use model + return this._lastResponseContent; + } readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store); readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store); private _terminalAgentId = 'terminal'; + private _model: ChatModel | undefined; + constructor( private readonly _instance: ITerminalInstance, processManager: ITerminalProcessManager, @@ -99,8 +107,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, + @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, @IChatService private readonly _chatService: IChatService, - @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService ) { super(); @@ -166,7 +174,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr } cancel(): void { - this._cancellationTokenSource.cancel(); + if (this._currentRequest) { + this._model?.cancelRequest(this._currentRequest); + } } private _forcedPlaceholder: string | undefined = undefined; @@ -194,7 +204,24 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._updatePlaceholder(); } + clear(): void { + this._model?.dispose(); + this._model = undefined; + this.updateModel(); + this._chatWidget?.rawValue?.hide(); + this._chatWidget?.rawValue?.setValue(undefined); + } + + private updateModel(): void { + const providerInfo = this._chatService.getProviderInfos()?.[0]; + if (!providerInfo) { + return; + } + this._model ??= this._chatService.startSession(providerInfo.id, CancellationToken.None); + } + async acceptInput(): Promise { + this.updateModel(); this._lastInput = this._chatWidget?.rawValue?.input(); if (!this._lastInput) { return; @@ -211,6 +238,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (progress.kind === 'content' || progress.kind === 'markdownContent') { responseContent += progress.content; } + if (this._currentRequest) { + this._model?.acceptResponseProgress(this._currentRequest, progress); + } }; const requestId = generateUuid(); const requestProps: IChatAgentRequest = { @@ -221,6 +251,16 @@ export class TerminalChatController extends Disposable implements ITerminalContr // TODO: ? variables: { variables: [] }, }; + // TODO: fix requester usrname, responder username + this._model?.initialize({ id: this._accessibilityRequestId, requesterUsername: 'userGesture', responderUsername: 'terminal' }, undefined); + const request: IParsedChatRequest = { + text: this._lastInput, + parts: [] + }; + const requestVarData: IChatRequestVariableData = { + variables: [] + }; + this._currentRequest = this._model?.addRequest(request, requestVarData); try { const task = this._chatAgentService.invokeAgent(this._terminalAgentId, requestProps, progressCallback, [], cancellationToken); this._chatWidget?.rawValue?.inlineChatWidget.updateChatMessage(undefined); @@ -236,6 +276,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(false); this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(''); this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); + if (this._currentRequest) { + this._model?.completeResponse(this._currentRequest); + } } this._lastResponseContent = responseContent; const firstCodeBlockContent = marked.lexer(responseContent).filter(token => token.type === 'code')?.[0]?.raw; @@ -289,18 +332,34 @@ export class TerminalChatController extends Disposable implements ITerminalContr } async viewInChat(): Promise { - if (!this._lastInput || !this._lastResponseContent) { + const providerInfo = this._chatService.getProviderInfos()?.[0]; + if (!providerInfo) { return; } - const widget = await this._chatWidgetService.revealViewForProvider('copilot'); - if (widget && widget.viewModel) { - this._chatService.addCompleteRequest(widget.viewModel.sessionId, this._lastInput, undefined, { message: this._lastResponseContent }); + const widget = await this._chatWidgetService.revealViewForProvider(providerInfo.id); + if (widget && widget.viewModel && this._model) { + for (const request of this._model.getRequests()) { + if (request.response?.response.value || request.response?.result) { + this._chatService.addCompleteRequest(widget.viewModel.sessionId, + request.message as IParsedChatRequest, + request.variableData, + { + message: request.response.response.value, + result: request.response.result, + followups: request.response.followups + }); + } + } widget.focusLastMessage(); } } override dispose() { + if (this._currentRequest) { + this._model?.cancelRequest(this._currentRequest); + } super.dispose(); + this.clear(); this._chatWidget?.rawValue?.dispose(); } } From 79ef477d826dc46e9617a8b2db3477841c89e4e4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 13:11:42 -0600 Subject: [PATCH 1307/1897] rm unused --- .../chat/browser/terminalChatController.ts | 64 ++++--------------- 1 file changed, 13 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 6daeb2bd3cb..bfafbb102fc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -9,42 +9,25 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; import { marked } from 'vs/base/common/marked/marked'; -import { Schemas } from 'vs/base/common/network'; -import { assertType } from 'vs/base/common/types'; -import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { ITextModel } from 'vs/editor/common/model'; -import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; 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 { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { generateUuid } from 'vs/base/common/uuid'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { marked } from 'vs/base/common/marked/marked'; +import { IChatAgentService, IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { IChatService, IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; +import { InlineChatResponseTypes, CTX_INLINE_CHAT_RESPONSE_TYPES } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; +import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { Emitter, Event } from 'vs/base/common/event'; -import { localize } from 'vs/nls'; -import { CTX_INLINE_CHAT_RESPONSE_TYPES, EditMode, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { EmptyResponse, Session, SessionExchange, SessionPrompt, TerminalResponse } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { assertType } from 'vs/base/common/types'; -import { IModelService } from 'vs/editor/common/services/model'; -import { ITextModel } from 'vs/editor/common/model'; -import { Schemas } from 'vs/base/common/network'; -import { URI } from 'vs/base/common/uri'; -import { ChatRequestModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; + + +import { ChatModel, ChatRequestModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; const enum Message { NONE = 0, @@ -74,9 +57,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _requestActiveContextKey!: IContextKey; private readonly _terminalAgentRegisteredContextKey!: IContextKey; private readonly _lastResponseTypeContextKey!: IContextKey; - - private _scopedInstantiationService: IInstantiationService | undefined; - private _accessibilityRequestId: number = 0; private _messages = this._store.add(new Emitter()); @@ -128,7 +108,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr } else { this._terminalAgentRegisteredContextKey.set(true); } - this._cancellationTokenSource = new CancellationTokenSource(); } xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { @@ -136,25 +115,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr return; } this._chatWidget = new Lazy(() => { - const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._instance.domElement!)); - this._scopedInstantiationService = this._instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); - // The inline chat widget requires a parent editor that it bases the diff view on, since the - // terminal doesn't use that feature we can just pass in an unattached editor instance. - const fakeParentEditorElement = document.createElement('div'); - this._fakeEditor = this._scopedInstantiationService.createInstance( - CodeEditorWidget, - fakeParentEditorElement, - { - extraEditorClassName: 'ignore-panel-bg' - }, - { isSimpleWidget: true } - ); - const path = `terminal-chat-input-${this._instance.instanceId}`; - const inputUri = URI.from({ path: path, scheme: Schemas.untitled, fragment: '' }); - const result: ITextModel = this._modelService.createModel('', null, inputUri, false); - this._fakeEditor.setModel(result); - const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._fakeEditor!, this._instance); + const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); chatWidget.focusTracker.onDidFocus(() => { TerminalChatController.activeChatWidget = this; if (!isDetachedTerminalInstance(this._instance)) { @@ -228,7 +190,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } this._chatAccessibilityService.acceptRequest(); this._requestActiveContextKey.set(true); - const cancellationToken = this._cancellationTokenSource.token; + const cancellationToken = new CancellationTokenSource().token; let responseContent = ''; const progressCallback = (progress: IChatProgress) => { if (cancellationToken.isCancellationRequested) { From f7915fc65f7639a109599dc133f7dd2fa17282a3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:06:19 -0800 Subject: [PATCH 1308/1897] Accept -> Run, add insert button, improve keybindings --- .../chat/browser/terminalChat.ts | 3 +- .../browser/terminalChatAccessibilityHelp.ts | 2 +- .../chat/browser/terminalChatActions.ts | 44 ++++++++++++++++--- .../chat/browser/terminalChatController.ts | 4 +- .../chat/browser/terminalChatWidget.ts | 4 +- 5 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index f75c9c3d9a7..d409361d517 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -13,7 +13,8 @@ export const enum TerminalChatCommandId { FeedbackHelpful = 'workbench.action.terminal.chat.feedbackHelpful', FeedbackUnhelpful = 'workbench.action.terminal.chat.feedbackUnhelpful', FeedbackReportIssue = 'workbench.action.terminal.chat.feedbackReportIssue', - AcceptCommand = 'workbench.action.terminal.chat.acceptCommand', + RunCommand = 'workbench.action.terminal.chat.runCommand', + InsertCommand = 'workbench.action.terminal.chat.insertCommand', ViewInChat = 'workbench.action.terminal.chat.viewInChat', } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts index 2fe29ea5d8a..d851609de14 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -48,7 +48,7 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor): string { const keybindingService = accessor.get(IKeybindingService); const content = []; const openAccessibleViewKeybinding = keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel(); - const acceptCommandKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.AcceptCommand)?.getAriaLabel(); + const acceptCommandKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.RunCommand)?.getAriaLabel(); const makeRequestKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.MakeRequest)?.getAriaLabel(); //TODO: using this instead of the terminal command bc by definition the inline terminal chat is focused when this dialog is invoked. const startChatKeybinding = keybindingService.lookupKeybinding('inlineChat.start')?.getAriaLabel(); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index a1561e9b63c..901f536f0bf 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -71,9 +71,9 @@ registerActiveXtermAction({ registerActiveXtermAction({ - id: TerminalChatCommandId.AcceptCommand, - title: localize2('acceptCommand', 'Terminal: Accept Chat Command'), - shortTitle: localize2('accept', 'Accept'), + id: TerminalChatCommandId.RunCommand, + title: localize2('runCommand', 'Terminal: Run Chat Command'), + shortTitle: localize2('run', 'Run'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), @@ -83,8 +83,8 @@ registerActiveXtermAction({ ), icon: Codicon.check, keybinding: { - when: ContextKeyExpr.and(TerminalContextKeys.chatResponseEditorFocused, TerminalContextKeys.chatRequestActive.negate()), - weight: KeybindingWeight.EditorCore + 7, + when: TerminalContextKeys.chatRequestActive.negate(), + weight: KeybindingWeight.WorkbenchContrib + 7, primary: KeyMod.CtrlCmd | KeyCode.Enter, }, menu: { @@ -98,7 +98,39 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.acceptCommand(); + contr?.acceptCommand(true); + } +}); + +registerActiveXtermAction({ + id: TerminalChatCommandId.InsertCommand, + title: localize2('insertCommand', 'Terminal: Insert Chat Command'), + shortTitle: localize2('insert', 'Insert'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + TerminalContextKeys.chatRequestActive.negate(), + TerminalContextKeys.chatAgentRegistered, + CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty) + ), + icon: Codicon.check, + keybinding: { + when: TerminalContextKeys.chatRequestActive.negate(), + weight: KeybindingWeight.WorkbenchContrib + 7, + primary: KeyMod.Alt | KeyCode.Enter, + }, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_STATUS, + group: '0_main', + order: 1, + when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), TerminalContextKeys.chatRequestActive.negate()), + }, + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.acceptCommand(false); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index bfafbb102fc..903c81b74fb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -285,8 +285,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr return !!this._chatWidget?.rawValue?.hasFocus(); } - acceptCommand(): void { - this._chatWidget?.rawValue?.acceptCommand(); + acceptCommand(shouldExecute: boolean): void { + this._chatWidget?.rawValue?.acceptCommand(shouldExecute); } reveal(): void { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 39892d8a381..53a67093ffb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -233,12 +233,12 @@ export class TerminalChatWidget extends Disposable { this.hideTerminalCommandWidget(); } } - acceptCommand(): void { + acceptCommand(shouldExecute: boolean): void { const value = this._terminalCommandWidget?.getValue(); if (!value) { return; } - this._instance.sendText(value, false, true); + this._instance.runCommand(value, shouldExecute); this.hide(); } updateProgress(progress?: IChatProgress): void { From a9c300cd68ec0b2f1290aa32141367fbb0abf6da Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:11:28 -0800 Subject: [PATCH 1309/1897] Trim suggestion to prevent running on alt+enter --- .../terminalContrib/chat/browser/terminalChatController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 903c81b74fb..d0d9590813e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -246,7 +246,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr const firstCodeBlockContent = marked.lexer(responseContent).filter(token => token.type === 'code')?.[0]?.raw; const regex = /```(?\w+)\n(?[\s\S]*?)```/g; const match = regex.exec(firstCodeBlockContent); - const codeBlock = match?.groups?.content; + const codeBlock = match?.groups?.content.trim(); const shellType = match?.groups?.language; this._accessibilityRequestId++; if (cancellationToken.isCancellationRequested) { From 4d81b4e49afb5581431a2534463ed522894a0b36 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 13:15:53 -0600 Subject: [PATCH 1310/1897] cancel request on clear --- .../terminalContrib/chat/browser/terminalChatController.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index d0d9590813e..6f292f51fde 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -167,9 +167,11 @@ export class TerminalChatController extends Disposable implements ITerminalContr } clear(): void { + if (this._currentRequest) { + this._model?.cancelRequest(this._currentRequest); + } this._model?.dispose(); this._model = undefined; - this.updateModel(); this._chatWidget?.rawValue?.hide(); this._chatWidget?.rawValue?.setValue(undefined); } From 50a0433578968dea132f628203144e55c991246f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 13:17:31 -0600 Subject: [PATCH 1311/1897] rename --- .../chat/browser/terminalChatController.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 6f292f51fde..2585ae766da 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -57,7 +57,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _requestActiveContextKey!: IContextKey; private readonly _terminalAgentRegisteredContextKey!: IContextKey; private readonly _lastResponseTypeContextKey!: IContextKey; - private _accessibilityRequestId: number = 0; + private _requestId: number = 0; private _messages = this._store.add(new Emitter()); @@ -216,7 +216,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr variables: { variables: [] }, }; // TODO: fix requester usrname, responder username - this._model?.initialize({ id: this._accessibilityRequestId, requesterUsername: 'userGesture', responderUsername: 'terminal' }, undefined); + this._model?.initialize({ id: this._requestId, requesterUsername: 'userGesture', responderUsername: 'terminal' }, undefined); const request: IParsedChatRequest = { text: this._lastInput, parts: [] @@ -250,15 +250,15 @@ export class TerminalChatController extends Disposable implements ITerminalContr const match = regex.exec(firstCodeBlockContent); const codeBlock = match?.groups?.content.trim(); const shellType = match?.groups?.language; - this._accessibilityRequestId++; + this._requestId++; if (cancellationToken.isCancellationRequested) { return; } if (codeBlock) { - this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._accessibilityRequestId, shellType); + this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._requestId, shellType); this._lastResponseTypeContextKey.set(InlineChatResponseTypes.Empty); } else { - this._chatWidget?.rawValue?.renderMessage(responseContent, this._accessibilityRequestId, requestId); + this._chatWidget?.rawValue?.renderMessage(responseContent, this._requestId, requestId); this._lastResponseTypeContextKey.set(InlineChatResponseTypes.OnlyMessages); } this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); From 1a0199379a05a6a2f9f5c9e80ec410b2808334a0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:22:56 -0800 Subject: [PATCH 1312/1897] More tweaks to response style --- .../terminalContrib/chat/browser/media/terminalChatWidget.css | 3 ++- .../terminalContrib/chat/browser/terminalChatActions.ts | 2 ++ .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css index a916f2dd0d7..1feb4831c23 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css @@ -13,7 +13,8 @@ .terminal-inline-chat .terminal-inline-chat-response { border: 1px solid var(--vscode-input-border, transparent); - background-color: var(--vscode-interactive-result-editor-background-color); + /* TODO: Make themeable */ + background-color: #181818; } .terminal-inline-chat .terminal-inline-chat-response:has(.monaco-editor.focused) { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 901f536f0bf..50c1587b124 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -35,6 +35,8 @@ registerActiveXtermAction({ } const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); contr?.chatWidget?.reveal(); + // TODO: Remove this before merging to main + contr?.chatWidget?.setValue('list files'); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 53a67093ffb..637e2f77104 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -122,7 +122,7 @@ export class TerminalChatWidget extends Disposable { scrollbar: { useShadows: false, vertical: 'hidden', - horizontal: 'auto', + horizontal: 'hidden', alwaysConsumeMouseWheel: false }, lineDecorationsWidth: 0, From 64e1a39e1bcf91e38860ead296912c06976f8f60 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:23:34 -0800 Subject: [PATCH 1313/1897] Remove category prefix from commands --- .../chat/browser/terminalChatActions.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 50c1587b124..19d9fdf140c 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -18,7 +18,7 @@ import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/cha registerActiveXtermAction({ id: TerminalChatCommandId.Start, - title: localize2('startChat', 'Terminal: Start Chat'), + title: localize2('startChat', 'Start Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), @@ -42,7 +42,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.Hide, - title: localize2('closeChat', 'Terminal: Close Chat'), + title: localize2('closeChat', 'Close Chat'), keybinding: { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], @@ -74,7 +74,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.RunCommand, - title: localize2('runCommand', 'Terminal: Run Chat Command'), + title: localize2('runCommand', 'Run Chat Command'), shortTitle: localize2('run', 'Run'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -106,7 +106,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.InsertCommand, - title: localize2('insertCommand', 'Terminal: Insert Chat Command'), + title: localize2('insertCommand', 'Insert Chat Command'), shortTitle: localize2('insert', 'Insert'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -138,7 +138,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.ViewInChat, - title: localize2('viewInChat', 'Terminal: View in Chat'), + title: localize2('viewInChat', 'View in Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), @@ -164,7 +164,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.MakeRequest, - title: localize2('makeChatRequest', 'Terminal: Make Chat Request'), + title: localize2('makeChatRequest', 'Make Chat Request'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), @@ -195,7 +195,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.Cancel, - title: localize2('cancelChat', 'Terminal: Cancel Chat'), + title: localize2('cancelChat', 'Cancel Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatRequestActive, From e4e853fecf4e83033bade6bad9ea74c6f58dd1ef Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 15 Feb 2024 20:25:45 +0100 Subject: [PATCH 1314/1897] Workbench hover to align and correctly apply delay setting (#204871) * Workbench hover * :lipstick: * placement element for tabs and breadcrumbs * enableInstantHoverAfterRecentlyShown * :lipstick: * adobt custom hover in all toolbars and labels * dropdown hover * widgets * dispose hover delegate uf own instance * testing hover delegate (might be a better way of doing this?) * nullHoverDelegateFactory * registering the disposables and handing down options * Hygiene * fix quickinput --- .../browser/ui/actionbar/actionViewItems.ts | 17 +++--- src/vs/base/browser/ui/actionbar/actionbar.ts | 6 +- src/vs/base/browser/ui/button/button.ts | 15 ++++- src/vs/base/browser/ui/dropdown/dropdown.ts | 10 +++- .../ui/dropdown/dropdownActionViewItem.ts | 8 ++- src/vs/base/browser/ui/hover/hoverDelegate.ts | 35 +++++++++++ .../browser/ui/iconLabel/iconHoverDelegate.ts | 2 + src/vs/base/browser/ui/iconLabel/iconLabel.ts | 17 +++--- src/vs/base/browser/ui/inputbox/inputBox.ts | 9 ++- .../browser/ui/selectBox/selectBoxCustom.ts | 13 +++-- src/vs/base/browser/ui/table/tableWidget.ts | 13 +++-- src/vs/base/browser/ui/toggle/toggle.ts | 7 ++- src/vs/base/browser/ui/toolbar/toolbar.ts | 11 +++- .../diffEditorItemTemplate.ts | 2 +- .../quickInput/standaloneQuickInputService.ts | 3 - .../browser/standaloneCodeEditor.ts | 4 ++ .../dropdownWithPrimaryActionViewItem.ts | 12 ++-- .../browser/menuEntryActionViewItem.ts | 2 +- src/vs/platform/hover/browser/hover.ts | 58 ++++++++++++++++++- .../platform/quickinput/browser/quickInput.ts | 36 ++++-------- .../quickinput/browser/quickInputService.ts | 4 +- src/vs/workbench/browser/composite.ts | 3 +- src/vs/workbench/browser/panecomposite.ts | 5 +- .../workbench/browser/parts/compositePart.ts | 16 +++-- .../parts/editor/breadcrumbsControl.ts | 16 +---- .../browser/parts/editor/editorTabsControl.ts | 28 ++++----- .../parts/editor/multiEditorTabsControl.ts | 6 +- .../browser/parts/globalCompositeBar.ts | 5 ++ .../browser/parts/paneCompositePart.ts | 7 ++- .../browser/parts/statusbar/statusbarPart.ts | 56 +++++------------- .../browser/parts/titlebar/titlebarPart.ts | 50 +++++----------- .../workbench/browser/parts/views/treeView.ts | 9 +-- .../workbench/browser/parts/views/viewPane.ts | 2 +- .../browser/parts/views/viewPaneContainer.ts | 7 ++- src/vs/workbench/browser/workbench.ts | 6 ++ .../browser/outline/documentSymbolsOutline.ts | 2 +- .../browser/outline/documentSymbolsTree.ts | 18 ++---- .../contrib/debug/browser/callStackView.ts | 4 +- .../debug/browser/debugActionViewItems.ts | 5 +- .../contrib/debug/browser/debugToolBar.ts | 13 +++-- .../contrib/debug/browser/debugViewlet.ts | 9 +-- .../extensions/browser/extensionsViewlet.ts | 2 +- .../interactive/browser/interactiveEditor.ts | 4 +- .../browser/view/cellParts/cellToolbars.ts | 8 +-- .../contrib/remote/browser/tunnelView.ts | 23 ++++---- .../contrib/scm/browser/scmViewPane.ts | 6 +- src/vs/workbench/contrib/scm/browser/util.ts | 12 ++-- .../terminal/browser/terminalEditor.ts | 7 ++- .../contrib/terminal/browser/terminalView.ts | 10 ++-- .../testing/browser/testingExplorerView.ts | 2 +- .../contrib/timeline/browser/timelinePane.ts | 11 +--- .../parts/titlebar/titlebarPart.ts | 10 +--- .../quickinput/browser/quickInputService.ts | 4 +- .../test/browser/workbenchTestServices.ts | 24 +------- 54 files changed, 356 insertions(+), 318 deletions(-) create mode 100644 src/vs/base/browser/ui/hover/hoverDelegate.ts diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index 906c437b325..804b0c386ac 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -9,6 +9,7 @@ import { addDisposableListener, EventHelper, EventLike, EventType } from 'vs/bas import { EventType as TouchEventType, Gesture } from 'vs/base/browser/touch'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { ISelectBoxOptions, ISelectBoxStyles, ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; @@ -224,16 +225,14 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { } const title = this.getTooltip() ?? ''; this.updateAriaLabel(); - if (!this.options.hoverDelegate) { - this.element.title = title; + + this.element.title = ''; + if (!this.customHover) { + const hoverDelegate = this.options.hoverDelegate ?? getDefaultHoverDelegate('element'); + this.customHover = setupCustomHover(hoverDelegate, this.element, title); + this._store.add(this.customHover); } else { - this.element.title = ''; - if (!this.customHover) { - this.customHover = setupCustomHover(this.options.hoverDelegate, this.element, title); - this._store.add(this.customHover); - } else { - this.customHover.update(title); - } + this.customHover.update(title); } } diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index fafc3417f8b..9ba16500c8f 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -6,6 +6,7 @@ import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { ActionRunner, IAction, IActionRunner, IRunEvent, Separator } from 'vs/base/common/actions'; import { Emitter } from 'vs/base/common/event'; @@ -67,6 +68,7 @@ export interface IActionOptions extends IActionViewItemOptions { export class ActionBar extends Disposable implements IActionRunner { private readonly options: IActionBarOptions; + private readonly _hoverDelegate: IHoverDelegate; private _actionRunner: IActionRunner; private readonly _actionRunnerDisposables = this._register(new DisposableStore()); @@ -117,6 +119,8 @@ export class ActionBar extends Disposable implements IActionRunner { keys: this.options.triggerKeys?.keys ?? [KeyCode.Enter, KeyCode.Space] }; + this._hoverDelegate = options.hoverDelegate ?? this._register(getDefaultHoverDelegate('element', true)); + if (this.options.actionRunner) { this._actionRunner = this.options.actionRunner; } else { @@ -358,7 +362,7 @@ export class ActionBar extends Disposable implements IActionRunner { let item: IActionViewItem | undefined; - const viewItemOptions = { hoverDelegate: this.options.hoverDelegate, ...options }; + const viewItemOptions = { hoverDelegate: this._hoverDelegate, ...options }; if (this.options.actionViewItemProvider) { item = this.options.actionViewItemProvider(action, viewItemOptions); } diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index 3b1ad7915bb..4675ee8c5ee 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -9,6 +9,8 @@ import { sanitize } from 'vs/base/browser/dompurify/dompurify'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { renderMarkdown, renderStringAsPlaintext } from 'vs/base/browser/markdownRenderer'; import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { Action, IAction, IActionRunner } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; @@ -74,6 +76,7 @@ export class Button extends Disposable implements IButton { protected _label: string | IMarkdownString = ''; protected _labelElement: HTMLElement | undefined; protected _labelShortElement: HTMLElement | undefined; + private _hover: ICustomHover | undefined; private _onDidClick = this._register(new Emitter()); get onDidClick(): BaseEvent { return this._onDidClick.event; } @@ -240,10 +243,16 @@ export class Button extends Disposable implements IButton { } } + let title: string = ''; if (typeof this.options.title === 'string') { - this._element.title = this.options.title; + title = this.options.title; } else if (this.options.title) { - this._element.title = renderStringAsPlaintext(value); + title = renderStringAsPlaintext(value); + } + if (!this._hover) { + this._hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this._element, title)); + } else { + this._hover.update(title); } if (typeof this.options.ariaLabel === 'string') { @@ -348,7 +357,7 @@ export class ButtonWithDropdown extends Disposable implements IButton { this.separator.style.backgroundColor = options.buttonSeparator ?? ''; this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportIcons: true })); - this.dropdownButton.element.title = localize("button dropdown more actions", 'More Actions...'); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.dropdownButton.element, localize("button dropdown more actions", 'More Actions...'))); this.dropdownButton.element.setAttribute('aria-haspopup', 'true'); this.dropdownButton.element.setAttribute('aria-expanded', 'false'); this.dropdownButton.element.classList.add('monaco-dropdown-button'); diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index 8fd8867058a..88dfaf2c5b1 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -8,6 +8,8 @@ import { $, addDisposableListener, append, EventHelper, EventType, isMouseEvent import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventType as GestureEventType, Gesture } from 'vs/base/browser/touch'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { IMenuOptions } from 'vs/base/browser/ui/menu/menu'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { Emitter } from 'vs/base/common/event'; @@ -34,6 +36,8 @@ class BaseDropdown extends ActionRunner { private _onDidChangeVisibility = this._register(new Emitter()); readonly onDidChangeVisibility = this._onDidChangeVisibility.event; + private hover: ICustomHover | undefined; + constructor(container: HTMLElement, options: IBaseDropdownOptions) { super(); @@ -101,7 +105,11 @@ class BaseDropdown extends ActionRunner { set tooltip(tooltip: string) { if (this._label) { - this._label.title = tooltip; + if (!this.hover) { + this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this._label, tooltip)); + } else { + this.hover.update(tooltip); + } } } diff --git a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index a5fee4835b3..419658a21bb 100644 --- a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -19,6 +19,8 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { IDisposable } from 'vs/base/common/lifecycle'; import 'vs/css!./dropdown'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export interface IKeybindingProvider { (action: IAction): ResolvedKeybinding | undefined; @@ -90,7 +92,9 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { this.element.setAttribute('role', 'button'); this.element.setAttribute('aria-haspopup', 'true'); this.element.setAttribute('aria-expanded', 'false'); - this.element.title = this._action.label || ''; + if (this._action.label) { + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.element, this._action.label)); + } this.element.ariaLabel = this._action.label || ''; return null; @@ -203,7 +207,7 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem { separator.classList.toggle('prominent', menuActionClassNames.includes('prominent')); append(this.element, separator); - this.dropdownMenuActionViewItem = this._register(new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', nls.localize('moreActions', "More Actions..."))), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...ThemeIcon.asClassNameArray(Codicon.dropDownButton), ...menuActionClassNames] })); + this.dropdownMenuActionViewItem = this._register(new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', nls.localize('moreActions', "More Actions..."))), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...ThemeIcon.asClassNameArray(Codicon.dropDownButton), ...menuActionClassNames], hoverDelegate: this.options.hoverDelegate })); this.dropdownMenuActionViewItem.render(this.element); this._register(addDisposableListener(this.element, EventType.KEY_DOWN, e => { diff --git a/src/vs/base/browser/ui/hover/hoverDelegate.ts b/src/vs/base/browser/ui/hover/hoverDelegate.ts new file mode 100644 index 00000000000..6682d739c26 --- /dev/null +++ b/src/vs/base/browser/ui/hover/hoverDelegate.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IHoverDelegate, IScopedHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { Lazy } from 'vs/base/common/lazy'; + +const nullHoverDelegateFactory = () => ({ + get delay(): number { return -1; }, + dispose: () => { }, + showHover: () => { return undefined; }, +}); + +let hoverDelegateFactory: (placement: 'mouse' | 'element', enableInstantHover: boolean) => IScopedHoverDelegate = nullHoverDelegateFactory; +const defaultHoverDelegateMouse = new Lazy(() => hoverDelegateFactory('mouse', false)); +const defaultHoverDelegateElement = new Lazy(() => hoverDelegateFactory('element', false)); + +export function setHoverDelegateFactory(hoverDelegateProvider: ((placement: 'mouse' | 'element', enableInstantHover: boolean) => IScopedHoverDelegate)): void { + hoverDelegateFactory = hoverDelegateProvider; +} + +export function getDefaultHoverDelegate(placement: 'mouse' | 'element'): IHoverDelegate; +export function getDefaultHoverDelegate(placement: 'mouse' | 'element', enableInstantHover: true): IScopedHoverDelegate; +export function getDefaultHoverDelegate(placement: 'mouse' | 'element', enableInstantHover?: boolean): IHoverDelegate | IScopedHoverDelegate { + if (enableInstantHover) { + // If instant hover is enabled, the consumer is responsible for disposing the hover delegate + return hoverDelegateFactory(placement, true); + } + + if (placement === 'element') { + return defaultHoverDelegateElement.value; + } + return defaultHoverDelegateMouse.value; +} diff --git a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts index 74fcd97d4a9..d6d9096b5f1 100644 --- a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts +++ b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts @@ -64,6 +64,8 @@ export interface IHoverDelegate { placement?: 'mouse' | 'element'; } +export interface IScopedHoverDelegate extends IHoverDelegate, IDisposable { } + export interface IHoverWidget extends IDisposable { readonly isDisposed: boolean; } diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index f95a11c9c44..6a7edc12dcf 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -7,11 +7,12 @@ import 'vs/css!./iconlabel'; import * as dom from 'vs/base/browser/dom'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { ITooltipMarkdownString, setupCustomHover, setupNativeHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { ITooltipMarkdownString, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { IMatch } from 'vs/base/common/filters'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { equals } from 'vs/base/common/objects'; import { Range } from 'vs/base/common/range'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export interface IIconLabelCreationOptions { readonly supportHighlights?: boolean; @@ -94,7 +95,7 @@ export class IconLabel extends Disposable { private readonly labelContainer: HTMLElement; - private readonly hoverDelegate: IHoverDelegate | undefined; + private readonly hoverDelegate: IHoverDelegate; private readonly customHovers: Map = new Map(); constructor(container: HTMLElement, options?: IIconLabelCreationOptions) { @@ -113,7 +114,7 @@ export class IconLabel extends Disposable { this.nameNode = new Label(this.nameContainer); } - this.hoverDelegate = options?.hoverDelegate; + this.hoverDelegate = options?.hoverDelegate ?? getDefaultHoverDelegate('mouse'); } get element(): HTMLElement { @@ -186,13 +187,9 @@ export class IconLabel extends Disposable { return; } - if (!this.hoverDelegate) { - setupNativeHover(htmlElement, tooltip); - } else { - const hoverDisposable = setupCustomHover(this.hoverDelegate, htmlElement, tooltip); - if (hoverDisposable) { - this.customHovers.set(htmlElement, hoverDisposable); - } + const hoverDisposable = setupCustomHover(this.hoverDelegate, htmlElement, tooltip); + if (hoverDisposable) { + this.customHovers.set(htmlElement, hoverDisposable); } } diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index e4c89dd3aff..a23848dd914 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -11,6 +11,8 @@ import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { AnchorAlignment, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Widget } from 'vs/base/browser/ui/widget'; import { IAction } from 'vs/base/common/actions'; @@ -111,6 +113,7 @@ export class InputBox extends Widget { private cachedContentHeight: number | undefined; private maxHeight: number = Number.POSITIVE_INFINITY; private scrollableElement: ScrollableElement | undefined; + private hover: ICustomHover | undefined; private _onDidChange = this._register(new Emitter()); public readonly onDidChange: Event = this._onDidChange.event; @@ -230,7 +233,11 @@ export class InputBox extends Widget { public setTooltip(tooltip: string): void { this.tooltip = tooltip; - this.input.title = tooltip; + if (!this.hover) { + this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('element'), this.input, tooltip)); + } else { + this.hover.update(tooltip); + } } public setAriaLabel(label: string): void { diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 83073ee2666..ca6aaa33505 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -9,6 +9,8 @@ import { IContentActionHandler } from 'vs/base/browser/formattedTextRenderer'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { AnchorPosition, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { IListEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { ISelectBoxDelegate, ISelectBoxOptions, ISelectBoxStyles, ISelectData, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; @@ -101,6 +103,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi private selectionDetailsPane!: HTMLElement; private _skipLayout: boolean = false; private _cachedMaxDetailsHeight?: number; + private _hover: ICustomHover; private _sticky: boolean = false; // for dev purposes only @@ -131,6 +134,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.selectElement.setAttribute('aria-description', this.selectBoxOptions.ariaDescription); } + this._hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.selectElement, '')); + this._onDidSelect = new Emitter(); this._register(this._onDidSelect); @@ -199,7 +204,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi selected: e.target.value }); if (!!this.options[this.selected] && !!this.options[this.selected].text) { - this.selectElement.title = this.options[this.selected].text; + this._hover.update(this.options[this.selected].text); } })); @@ -309,7 +314,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.selectElement.selectedIndex = this.selected; if (!!this.options[this.selected] && !!this.options[this.selected].text) { - this.selectElement.title = this.options[this.selected].text; + this._hover.update(this.options[this.selected].text); } } @@ -837,7 +842,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi }); if (!!this.options[this.selected] && !!this.options[this.selected].text) { - this.selectElement.title = this.options[this.selected].text; + this._hover.update(this.options[this.selected].text); } } @@ -936,7 +941,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi selected: this.options[this.selected].text }); if (!!this.options[this.selected] && !!this.options[this.selected].text) { - this.selectElement.title = this.options[this.selected].text; + this._hover.update(this.options[this.selected].text); } } diff --git a/src/vs/base/browser/ui/table/tableWidget.ts b/src/vs/base/browser/ui/table/tableWidget.ts index 6e20fd6e34a..536fb25608e 100644 --- a/src/vs/base/browser/ui/table/tableWidget.ts +++ b/src/vs/base/browser/ui/table/tableWidget.ts @@ -4,12 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { $, append, clearNode, createStyleSheet, getContentHeight, getContentWidth } from 'vs/base/browser/dom'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListOptions, IListOptionsUpdate, IListStyles, List, unthemedListStyles } from 'vs/base/browser/ui/list/listWidget'; import { ISplitViewDescriptor, IView, Orientation, SplitView } from 'vs/base/browser/ui/splitview/splitview'; import { ITableColumn, ITableContextMenuEvent, ITableEvent, ITableGestureEvent, ITableMouseEvent, ITableRenderer, ITableTouchEvent, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; import { ISpliceable } from 'vs/base/common/sequence'; import 'vs/css!./table'; @@ -115,7 +117,7 @@ function asListVirtualDelegate(delegate: ITableVirtualDelegate): ILi }; } -class ColumnHeader implements IView { +class ColumnHeader extends Disposable implements IView { readonly element: HTMLElement; @@ -127,7 +129,10 @@ class ColumnHeader implements IView { readonly onDidLayout = this._onDidLayout.event; constructor(readonly column: ITableColumn, private index: number) { - this.element = $('.monaco-table-th', { 'data-col-index': index, title: column.tooltip }, column.label); + super(); + + this.element = $('.monaco-table-th', { 'data-col-index': index }, column.label); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.element, column.tooltip)); } layout(size: number): void { @@ -191,7 +196,7 @@ export class Table implements ISpliceable, IDisposable { ) { this.domNode = append(container, $(`.monaco-table.${this.domId}`)); - const headers = columns.map((c, i) => new ColumnHeader(c, i)); + const headers = columns.map((c, i) => this.disposables.add(new ColumnHeader(c, i))); const descriptor: ISplitViewDescriptor = { size: headers.reduce((a, b) => a + b.column.weight, 0), views: headers.map(view => ({ size: view.column.weight, view })) diff --git a/src/vs/base/browser/ui/toggle/toggle.ts b/src/vs/base/browser/ui/toggle/toggle.ts index 4146f24d141..54b9130d9e4 100644 --- a/src/vs/base/browser/ui/toggle/toggle.ts +++ b/src/vs/base/browser/ui/toggle/toggle.ts @@ -13,6 +13,8 @@ import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import 'vs/css!./toggle'; import { isActiveElement, $, addDisposableListener, EventType } from 'vs/base/browser/dom'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export interface IToggleOpts extends IToggleStyles { readonly actionClassName?: string; @@ -107,6 +109,7 @@ export class Toggle extends Widget { readonly domNode: HTMLElement; private _checked: boolean; + private _hover: ICustomHover; constructor(opts: IToggleOpts) { super(); @@ -127,7 +130,7 @@ export class Toggle extends Widget { } this.domNode = document.createElement('div'); - this.domNode.title = this._opts.title; + this._hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.domNode, this._opts.title)); this.domNode.classList.add(...classes); if (!this._opts.notFocusable) { this.domNode.tabIndex = 0; @@ -213,7 +216,7 @@ export class Toggle extends Widget { } setTitle(newTitle: string): void { - this.domNode.title = newTitle; + this._hover.update(newTitle); this.domNode.setAttribute('aria-label', newTitle); } } diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 2f46a692357..f92369cddfb 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -15,6 +15,8 @@ import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./toolbar'; import * as nls from 'vs/nls'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; @@ -30,6 +32,7 @@ export interface IToolBarOptions { moreIcon?: ThemeIcon; allowContextMenu?: boolean; skipTelemetry?: boolean; + hoverDelegate?: IHoverDelegate; /** * If true, toggled primary items are highlighted with a background color. @@ -57,6 +60,7 @@ export class ToolBar extends Disposable { constructor(container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) { super(); + options.hoverDelegate = options.hoverDelegate ?? this._register(getDefaultHoverDelegate('element', true)); this.options = options; this.lookupKeybindings = typeof this.options.getKeyBinding === 'function'; @@ -72,6 +76,7 @@ export class ToolBar extends Disposable { actionRunner: options.actionRunner, allowContextMenu: options.allowContextMenu, highlightToggledItems: options.highlightToggledItems, + hoverDelegate: options.hoverDelegate, actionViewItemProvider: (action, viewItemOptions) => { if (action.id === ToggleMenuAction.ID) { this.toggleMenuActionViewItem = new DropdownMenuActionViewItem( @@ -86,7 +91,8 @@ export class ToolBar extends Disposable { anchorAlignmentProvider: this.options.anchorAlignmentProvider, menuAsChild: !!this.options.renderDropdownAsChildElement, skipTelemetry: this.options.skipTelemetry, - isMenu: true + isMenu: true, + hoverDelegate: this.options.hoverDelegate } ); this.toggleMenuActionViewItem.setActionContext(this.actionBar.context); @@ -115,7 +121,8 @@ export class ToolBar extends Disposable { classNames: action.class, anchorAlignmentProvider: this.options.anchorAlignmentProvider, menuAsChild: !!this.options.renderDropdownAsChildElement, - skipTelemetry: this.options.skipTelemetry + skipTelemetry: this.options.skipTelemetry, + hoverDelegate: this.options.hoverDelegate } ); result.setActionContext(this.actionBar.context); diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts index da71ca719f7..8c44f6e4181 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts @@ -148,7 +148,7 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< shouldForwardArgs: true, }, toolbarOptions: { primaryGroup: g => g.startsWith('navigation') }, - actionViewItemProvider: action => createActionViewItem(_instantiationService, action), + actionViewItemProvider: (action, options) => createActionViewItem(_instantiationService, action, options), })); } diff --git a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts index 75a3520379b..0c94d2824e9 100644 --- a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts +++ b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts @@ -20,7 +20,6 @@ import { QuickInputService } from 'vs/platform/quickinput/browser/quickInputServ import { createSingleCallFunction } from 'vs/base/common/functional'; import { IQuickAccessController } from 'vs/platform/quickinput/common/quickAccess'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; class EditorScopedQuickInputService extends QuickInputService { @@ -33,7 +32,6 @@ class EditorScopedQuickInputService extends QuickInputService { @IThemeService themeService: IThemeService, @ICodeEditorService codeEditorService: ICodeEditorService, @IConfigurationService configurationService: IConfigurationService, - @IHoverService hoverService: IHoverService, ) { super( instantiationService, @@ -41,7 +39,6 @@ class EditorScopedQuickInputService extends QuickInputService { themeService, new EditorScopedLayoutService(editor.getContainerDomNode(), codeEditorService), configurationService, - hoverService ); // Use the passed in code editor as host for the quick input widget diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 739e76f97c2..479bb75745c 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -39,6 +39,8 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { mainWindow } from 'vs/base/browser/window'; +import { setHoverDelegateFactory } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover'; /** * Description of an action contribution @@ -289,6 +291,8 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon } createAriaDomNode(options.ariaContainerElement); + + setHoverDelegateFactory((placement, enableInstantHover) => instantiationService.createInstance(WorkbenchHoverDelegate, placement, enableInstantHover, {})); } public addCommand(keybinding: number, handler: ICommandHandler, context?: string): string | null { diff --git a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts index 99e25133cd8..cfcc0e2c73b 100644 --- a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts +++ b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts @@ -19,10 +19,12 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; export interface IDropdownWithPrimaryActionViewItemOptions { actionRunner?: IActionRunner; getKeyBinding?: (action: IAction) => ResolvedKeybinding | undefined; + hoverDelegate?: IHoverDelegate; } export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem { @@ -48,8 +50,8 @@ export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem { @IThemeService _themeService: IThemeService, @IAccessibilityService _accessibilityService: IAccessibilityService ) { - super(null, primaryAction); - this._primaryAction = new MenuEntryActionViewItem(primaryAction, undefined, _keybindingService, _notificationService, _contextKeyService, _themeService, _contextMenuProvider, _accessibilityService); + super(null, primaryAction, { hoverDelegate: _options?.hoverDelegate }); + this._primaryAction = new MenuEntryActionViewItem(primaryAction, { hoverDelegate: _options?.hoverDelegate }, _keybindingService, _notificationService, _contextKeyService, _themeService, _contextMenuProvider, _accessibilityService); if (_options?.actionRunner) { this._primaryAction.actionRunner = _options.actionRunner; } @@ -58,7 +60,8 @@ export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem { menuAsChild: true, classNames: className ? ['codicon', 'codicon-chevron-down', className] : ['codicon', 'codicon-chevron-down'], actionRunner: this._options?.actionRunner, - keybindingProvider: this._options?.getKeyBinding + keybindingProvider: this._options?.getKeyBinding, + hoverDelegate: _options?.hoverDelegate }); } @@ -130,7 +133,8 @@ export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem { this._dropdown.dispose(); this._dropdown = new DropdownMenuActionViewItem(dropdownAction, dropdownMenuActions, this._contextMenuProvider, { menuAsChild: true, - classNames: ['codicon', dropdownIcon || 'codicon-chevron-down'] + classNames: ['codicon', dropdownIcon || 'codicon-chevron-down'], + hoverDelegate: this._options?.hoverDelegate }); if (this._dropdownContainer) { this._dropdown.render(this._dropdownContainer); diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 8374e100d3c..c1edd287bea 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -515,7 +515,7 @@ class SubmenuEntrySelectActionViewItem extends SelectActionViewItem { /** * Creates action view items for menu actions or submenu actions. */ -export function createActionViewItem(instaService: IInstantiationService, action: IAction, options?: IDropdownMenuActionViewItemOptions | IMenuEntryActionViewItemOptions): undefined | MenuEntryActionViewItem | SubmenuEntryActionViewItem | BaseActionViewItem { +export function createActionViewItem(instaService: IInstantiationService, action: IAction, options: IDropdownMenuActionViewItemOptions | IMenuEntryActionViewItemOptions | undefined): undefined | MenuEntryActionViewItem | SubmenuEntryActionViewItem | BaseActionViewItem { if (action instanceof MenuItemAction) { return instaService.createInstance(MenuEntryActionViewItem, action, options); } else if (action instanceof SubmenuItemAction) { diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 5f3bb1e3317..5bbd3ff00b9 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -4,10 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; -import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate, IHoverDelegateOptions, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export const IHoverService = createDecorator('hoverService'); @@ -229,3 +230,56 @@ export interface IHoverTarget extends IDisposable { */ y?: number; } + +export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate { + + private lastHoverHideTime = Number.MAX_VALUE; + private timeLimit = 200; + + + private _delay: number; + get delay(): number { + if (this.instantHover && Date.now() - this.lastHoverHideTime < this.timeLimit) { + return 0; // show instantly when a hover was recently shown + } + return this._delay; + } + + constructor( + public readonly placement: 'mouse' | 'element', + private readonly instantHover: boolean, + private overrideOptions: Partial | ((options: IHoverDelegateOptions, focus?: boolean) => Partial) = {}, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IHoverService private readonly hoverService: IHoverService, + ) { + super(); + + this._delay = this.configurationService.getValue('workbench.hover.delay'); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('workbench.hover.delay')) { + this._delay = this.configurationService.getValue('workbench.hover.delay'); + } + })); + } + + showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined { + const overrideOptions = typeof this.overrideOptions === 'function' ? this.overrideOptions(options, focus) : this.overrideOptions; + return this.hoverService.showHover({ + ...options, + persistence: { + hideOnHover: true + }, + ...overrideOptions + }, focus); + } + + setOptions(options: Partial | ((options: IHoverDelegateOptions, focus?: boolean) => Partial)): void { + this.overrideOptions = options; + } + + onDidHideHover(): void { + if (this.instantHover) { + this.lastHoverHideTime = Date.now(); + } + } +} diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 79e8b3d6aa1..f23f8447a02 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -8,7 +8,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button, IButtonStyles } from 'vs/base/browser/ui/button/button'; import { CountBadge, ICountBadgeStyles } from 'vs/base/browser/ui/countBadge/countBadge'; -import { IHoverDelegate, IHoverDelegateOptions, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; import { IKeybindingLabelStyles } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; @@ -31,7 +31,7 @@ import { QuickInputBox } from './quickInputBox'; import { QuickInputList, QuickInputListFocus } from './quickInputList'; import { quickInputButtonToAction, renderQuickInputDescription } from './quickInputUtils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { IHoverOptions, IHoverService, WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover'; export interface IQuickInputOptions { idPrefix: string; @@ -1258,24 +1258,16 @@ export class QuickWidget extends QuickInput implements IQuickWidget { } } -export class QuickInputHoverDelegate implements IHoverDelegate { - private lastHoverHideTime = 0; - readonly placement = 'element'; - - get delay() { - if (Date.now() - this.lastHoverHideTime < 200) { - return 0; // show instantly when a hover was recently shown - } - - return this.configurationService.getValue('workbench.hover.delay'); - } +export class QuickInputHoverDelegate extends WorkbenchHoverDelegate { constructor( - private readonly configurationService: IConfigurationService, - private readonly hoverService: IHoverService - ) { } + @IConfigurationService configurationService: IConfigurationService, + @IHoverService hoverService: IHoverService + ) { + super('mouse', true, (options) => this.getOverrideOptions(options), configurationService, hoverService); + } - showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined { + private getOverrideOptions(options: IHoverDelegateOptions): Partial { // Only show the hover hint if the content is of a decent size const showHoverHint = ( options.content instanceof HTMLElement @@ -1284,8 +1276,8 @@ export class QuickInputHoverDelegate implements IHoverDelegate { ? options.content : options.content.value ).includes('\n'); - return this.hoverService.showHover({ - ...options, + + return { persistence: { hideOnKeyDown: false, }, @@ -1293,10 +1285,6 @@ export class QuickInputHoverDelegate implements IHoverDelegate { showHoverHint, skipFadeInAnimation: true, }, - }, focus); - } - - onDidHideHover(): void { - this.lastHoverHideTime = Date.now(); + }; } } diff --git a/src/vs/platform/quickinput/browser/quickInputService.ts b/src/vs/platform/quickinput/browser/quickInputService.ts index c36470d2a95..c2d178c4c46 100644 --- a/src/vs/platform/quickinput/browser/quickInputService.ts +++ b/src/vs/platform/quickinput/browser/quickInputService.ts @@ -21,7 +21,6 @@ import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { IQuickInputOptions, IQuickInputStyles, QuickInputHoverDelegate } from './quickInput'; import { QuickInputController, IQuickInputControllerHost } from 'vs/platform/quickinput/browser/quickInputController'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; import { getWindow } from 'vs/base/browser/dom'; export class QuickInputService extends Themable implements IQuickInputService { @@ -64,7 +63,6 @@ export class QuickInputService extends Themable implements IQuickInputService { @IThemeService themeService: IThemeService, @ILayoutService protected readonly layoutService: ILayoutService, @IConfigurationService protected readonly configurationService: IConfigurationService, - @IHoverService private readonly hoverService: IHoverService ) { super(themeService); } @@ -92,7 +90,7 @@ export class QuickInputService extends Themable implements IQuickInputService { options: IWorkbenchListOptions ) => this.instantiationService.createInstance(WorkbenchList, user, container, delegate, renderers, options) as List, styles: this.computeStyles(), - hoverDelegate: new QuickInputHoverDelegate(this.configurationService, this.hoverService) + hoverDelegate: this.instantiationService.createInstance(QuickInputHoverDelegate) }; const controller = this._register(new QuickInputController({ diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index ab28dbb003b..59eba8e11ff 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -17,6 +17,7 @@ import { assertIsDefined } from 'vs/base/common/types'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { MenuId } from 'vs/platform/actions/common/actions'; import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; /** * Composites are layed out in the sidebar and panel part of the workbench. At a time only one composite @@ -204,7 +205,7 @@ export abstract class Composite extends Component implements IComposite { * of an action. Returns undefined to indicate that the action is not rendered through * an action item. */ - getActionViewItem(action: IAction): IActionViewItem | undefined { + getActionViewItem(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { return undefined; } diff --git a/src/vs/workbench/browser/panecomposite.ts b/src/vs/workbench/browser/panecomposite.ts index b829c9f3d77..89e701e2d0b 100644 --- a/src/vs/workbench/browser/panecomposite.ts +++ b/src/vs/workbench/browser/panecomposite.ts @@ -22,6 +22,7 @@ import { IView } from 'vs/workbench/common/views'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { VIEWPANE_FILTER_ACTION } from 'vs/workbench/browser/parts/views/viewPane'; import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; export abstract class PaneComposite extends Composite implements IPaneComposite { @@ -140,8 +141,8 @@ export abstract class PaneComposite extends Composite implements IPaneComposite return menuActions.length ? menuActions : viewPaneActions; } - override getActionViewItem(action: IAction): IActionViewItem | undefined { - return this.viewPaneContainer?.getActionViewItem(action); + override getActionViewItem(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { + return this.viewPaneContainer?.getActionViewItem(action, options); } override getTitle(): string { diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index de055f7b032..b4d406a6364 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -32,6 +32,9 @@ import { AbstractProgressScope, ScopedProgressIndicator } from 'vs/workbench/ser import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export interface ICompositeTitleLabel { @@ -59,6 +62,7 @@ export abstract class CompositePart extends Part { protected toolBar: WorkbenchToolBar | undefined; protected titleLabelElement: HTMLElement | undefined; + protected readonly hoverDelegate: IHoverDelegate; private readonly mapCompositeToCompositeContainer = new Map(); private readonly mapActionsBindingToComposite = new Map void>(); @@ -92,6 +96,7 @@ export abstract class CompositePart extends Part { super(id, options, themeService, storageService, layoutService); this.lastActiveCompositeId = storageService.get(activeCompositeSettingsKey, StorageScope.WORKSPACE, this.defaultCompositeId); + this.hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); } protected openComposite(id: string, focus?: boolean): Composite | undefined { @@ -396,12 +401,13 @@ export abstract class CompositePart extends Part { // Toolbar this.toolBar = this._register(this.instantiationService.createInstance(WorkbenchToolBar, titleActionsContainer, { - actionViewItemProvider: action => this.actionViewItemProvider(action), + actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options), orientation: ActionsOrientation.HORIZONTAL, getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment(), toggleMenuTitle: localize('viewsAndMoreActions', "Views and More Actions..."), - telemetrySource: this.nameForTelemetry + telemetrySource: this.nameForTelemetry, + hoverDelegate: this.hoverDelegate })); this.collectCompositeActions()(); @@ -438,14 +444,14 @@ export abstract class CompositePart extends Part { titleLabel.updateStyles(); } - protected actionViewItemProvider(action: IAction): IActionViewItem | undefined { + protected actionViewItemProvider(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { // Check Active Composite if (this.activeComposite) { - return this.activeComposite.getActionViewItem(action); + return this.activeComposite.getActionViewItem(action, options); } - return createActionViewItem(this.instantiationService, action); + return createActionViewItem(this.instantiationService, action, options); } protected actionsContextProvider(): unknown { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index b8b74e5aec1..2c88a644bd5 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -41,7 +41,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { defaultBreadcrumbsWidgetStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Emitter } from 'vs/base/common/event'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { IHoverOptions, IHoverService } from 'vs/platform/hover/browser/hover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; class OutlineItem extends BreadcrumbsItem { @@ -207,7 +207,7 @@ export class BreadcrumbsControl { @ILabelService private readonly _labelService: ILabelService, @IConfigurationService configurationService: IConfigurationService, @IBreadcrumbsService breadcrumbsService: IBreadcrumbsService, - @IHoverService private readonly hoverService: IHoverService + @IInstantiationService instantiationService: IInstantiationService, ) { this.domNode = document.createElement('div'); this.domNode.classList.add('breadcrumbs-control'); @@ -230,17 +230,7 @@ export class BreadcrumbsControl { this._ckBreadcrumbsVisible = BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo(this._contextKeyService); this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService); - this._hoverDelegate = { - delay: 500, - showHover: (options: IHoverOptions) => { - return this.hoverService.showHover({ - ...options, - persistence: { - hideOnHover: true - } - }); - } - }; + this._hoverDelegate = getDefaultHoverDelegate('element'); this._disposables.add(breadcrumbsService.register(this._editorGroup.id, this._widget)); this.hide(); diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index 651a9b3ff16..782ad9b007d 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -45,7 +45,8 @@ import { isMacintosh } from 'vs/base/common/platform'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class EditorCommandsContextActionRunner extends ActionRunner { @@ -123,6 +124,8 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC private renderDropdownAsChildElement: boolean; + private readonly tabsHoverDelegate: IHoverDelegate; + constructor( protected readonly parent: HTMLElement, protected readonly editorPartsView: IEditorPartsView, @@ -138,7 +141,6 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC @IThemeService themeService: IThemeService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService, @IHostService private readonly hostService: IHostService, - @IHoverService private readonly hoverService: IHoverService ) { super(themeService); @@ -162,6 +164,8 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC this.renderDropdownAsChildElement = false; + this.tabsHoverDelegate = getDefaultHoverDelegate('element'); + this.create(parent); } @@ -205,7 +209,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC // Toolbar Widget this.editorActionsToolbar = this.editorActionsToolbarDisposables.add(this.instantiationService.createInstance(WorkbenchToolBar, container, { - actionViewItemProvider: action => this.actionViewItemProvider(action), + actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options), orientation: ActionsOrientation.HORIZONTAL, ariaLabel: localize('ariaLabelEditorActions', "Editor actions"), getKeyBinding: action => this.getKeybinding(action), @@ -231,12 +235,12 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC })); } - private actionViewItemProvider(action: IAction): IActionViewItem | undefined { + private actionViewItemProvider(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { const activeEditorPane = this.groupView.activeEditorPane; // Check Active Editor if (activeEditorPane instanceof EditorPane) { - const result = activeEditorPane.getActionViewItem(action); + const result = activeEditorPane.getActionViewItem(action, options); if (result) { return result; @@ -244,7 +248,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC } // Check extensions - return createActionViewItem(this.instantiationService, action, { menuAsChild: this.renderDropdownAsChildElement }); + return createActionViewItem(this.instantiationService, action, { ...options, menuAsChild: this.renderDropdownAsChildElement }); } protected updateEditorActionsToolbar(): void { @@ -452,17 +456,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC } protected getHoverDelegate(): IHoverDelegate { - return { - delay: 500, - showHover: options => { - return this.hoverService.showHover({ - ...options, - persistence: { - hideOnHover: true - } - }); - } - }; + return this.tabsHoverDelegate; } protected updateTabHeight(): void { diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 2f613acd93f..0043df4e615 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -56,8 +56,6 @@ import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor import { StickyEditorGroupModel, UnstickyEditorGroupModel } from 'vs/workbench/common/editor/filteredEditorGroupModel'; import { IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; -import { IDecorationsService } from 'vs/workbench/services/decorations/common/decorations'; interface IEditorInputLabel { readonly editor: EditorInput; @@ -152,10 +150,8 @@ export class MultiEditorTabsControl extends EditorTabsControl { @ITreeViewsDnDService private readonly treeViewsDragAndDropService: ITreeViewsDnDService, @IEditorResolverService editorResolverService: IEditorResolverService, @IHostService hostService: IHostService, - @IDecorationsService decorationsService: IDecorationsService, - @IHoverService hoverService: IHoverService, ) { - super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService, hostService, hoverService); + super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService, hostService); // Resolve the correct path library for the OS we are on // If we are connected to remote, this accounts for the diff --git a/src/vs/workbench/browser/parts/globalCompositeBar.ts b/src/vs/workbench/browser/parts/globalCompositeBar.ts index 263ef8d9616..550866d685b 100644 --- a/src/vs/workbench/browser/parts/globalCompositeBar.ts +++ b/src/vs/workbench/browser/parts/globalCompositeBar.ts @@ -42,6 +42,7 @@ import { DEFAULT_ICON } from 'vs/workbench/services/userDataProfile/common/userD import { isString } from 'vs/base/common/types'; import { KeyCode } from 'vs/base/common/keyCodes'; import { ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND } from 'vs/workbench/common/theme'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class GlobalCompositeBar extends Disposable { @@ -612,6 +613,7 @@ export class SimpleAccountActivityActionViewItem extends AccountsActivityActionV constructor( hoverOptions: IActivityHoverOptions, + options: IBaseActionViewItemOptions, @IThemeService themeService: IThemeService, @ILifecycleService lifecycleService: ILifecycleService, @IHoverService hoverService: IHoverService, @@ -629,6 +631,7 @@ export class SimpleAccountActivityActionViewItem extends AccountsActivityActionV @IInstantiationService instantiationService: IInstantiationService ) { super(() => [], { + ...options, colors: theme => ({ badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND), @@ -643,6 +646,7 @@ export class SimpleGlobalActivityActionViewItem extends GlobalActivityActionView constructor( hoverOptions: IActivityHoverOptions, + options: IBaseActionViewItemOptions, @IUserDataProfileService userDataProfileService: IUserDataProfileService, @IThemeService themeService: IThemeService, @IHoverService hoverService: IHoverService, @@ -656,6 +660,7 @@ export class SimpleGlobalActivityActionViewItem extends GlobalActivityActionView @IActivityService activityService: IActivityService, ) { super(() => [], { + ...options, colors: theme => ({ badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND), diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index ebee8aa4b42..82d20fd9666 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -302,11 +302,12 @@ export abstract class AbstractPaneCompositePart extends CompositePart this.actionViewItemProvider(action), + actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options), orientation: ActionsOrientation.HORIZONTAL, getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment(), - toggleMenuTitle: localize('moreActions', "More Actions...") + toggleMenuTitle: localize('moreActions', "More Actions..."), + hoverDelegate: this.hoverDelegate })); this.updateGlobalToolbarActions(); @@ -503,7 +504,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart event, getActions: () => activePaneCompositeActions, - getActionViewItem: action => this.actionViewItemProvider(action), + getActionViewItem: (action, options) => this.actionViewItemProvider(action, options), actionRunner: activePaneComposite.getActionRunner(), skipTelemetry: true }); diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 940b074561a..e8a133fd4b4 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -27,9 +27,7 @@ import { assertIsDefined } from 'vs/base/common/types'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { isHighContrast } from 'vs/platform/theme/common/theme'; import { hash } from 'vs/base/common/hash'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IHoverDelegate, IHoverDelegateOptions, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover'; import { HideStatusbarEntryAction, ToggleStatusbarEntryVisibilityAction } from 'vs/workbench/browser/parts/statusbar/statusbarActions'; import { IStatusbarViewModelEntry, StatusbarViewModel } from 'vs/workbench/browser/parts/statusbar/statusbarModel'; import { StatusbarEntryItem } from 'vs/workbench/browser/parts/statusbar/statusbarItem'; @@ -138,39 +136,8 @@ class StatusbarPart extends Part implements IStatusbarEntryContainer { private leftItemsContainer: HTMLElement | undefined; private rightItemsContainer: HTMLElement | undefined; - private readonly hoverDelegate = new class implements IHoverDelegate { - private lastHoverHideTime = 0; - - readonly placement = 'element'; - - get delay() { - if (Date.now() - this.lastHoverHideTime < 200) { - return 0; // show instantly when a hover was recently shown - } - - return this.configurationService.getValue('workbench.hover.delay'); - } - - constructor( - private readonly configurationService: IConfigurationService, - private readonly hoverService: IHoverService - ) { } - - showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined { - return this.hoverService.showHover({ - ...options, - persistence: { - hideOnKeyDown: true, - sticky: focus - } - }, focus); - } - - onDidHideHover(): void { - this.lastHoverHideTime = Date.now(); - } - }(this.configurationService, this.hoverService); + private readonly hoverDelegate: WorkbenchHoverDelegate; private readonly compactEntriesDisposable = this._register(new MutableDisposable()); private readonly styleOverrides = new Set(); @@ -184,11 +151,18 @@ class StatusbarPart extends Part implements IStatusbarEntryContainer { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextMenuService private contextMenuService: IContextMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IHoverService private readonly hoverService: IHoverService, - @IConfigurationService private readonly configurationService: IConfigurationService ) { super(id, { hasTitle: false }, themeService, storageService, layoutService); + this.hoverDelegate = this._register(instantiationService.createInstance(WorkbenchHoverDelegate, 'element', true, (_, focus?: boolean) => ( + { + persistence: { + hideOnKeyDown: true, + sticky: focus + } + } + ))); + this.registerListeners(); } @@ -667,10 +641,8 @@ export class MainStatusbarPart extends StatusbarPart { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextMenuService contextMenuService: IContextMenuService, @IContextKeyService contextKeyService: IContextKeyService, - @IHoverService hoverService: IHoverService, - @IConfigurationService configurationService: IConfigurationService ) { - super(Parts.STATUSBAR_PART, instantiationService, themeService, contextService, storageService, layoutService, contextMenuService, contextKeyService, hoverService, configurationService); + super(Parts.STATUSBAR_PART, instantiationService, themeService, contextService, storageService, layoutService, contextMenuService, contextKeyService); } } @@ -694,11 +666,9 @@ export class AuxiliaryStatusbarPart extends StatusbarPart implements IAuxiliaryS @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextMenuService contextMenuService: IContextMenuService, @IContextKeyService contextKeyService: IContextKeyService, - @IHoverService hoverService: IHoverService, - @IConfigurationService configurationService: IConfigurationService ) { const id = AuxiliaryStatusbarPart.COUNTER++; - super(`workbench.parts.auxiliaryStatus.${id}`, instantiationService, themeService, contextService, storageService, layoutService, contextMenuService, contextKeyService, hoverService, configurationService); + super(`workbench.parts.auxiliaryStatus.${id}`, instantiationService, themeService, contextService, storageService, layoutService, contextMenuService, contextKeyService); } } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index cbe98d2e852..05ad43933f8 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -33,8 +33,6 @@ import { Codicon } from 'vs/base/common/codicons'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; import { WindowTitle } from 'vs/workbench/browser/parts/titlebar/windowTitle'; import { CommandCenterControl } from 'vs/workbench/browser/parts/titlebar/commandCenterControl'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { ACCOUNTS_ACTIVITY_ID, GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; @@ -54,6 +52,9 @@ import { IEditorCommandsContext, IEditorPartOptionsChangeEvent, IToolbarActions import { mainWindow } from 'vs/base/browser/window'; import { ACCOUNTS_ACTIVITY_TILE_ACTION, GLOBAL_ACTIVITY_TITLE_ACTION } from 'vs/workbench/browser/parts/titlebar/titlebarActions'; import { IView } from 'vs/base/browser/ui/grid/grid'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; export interface ITitleVariable { readonly name: string; @@ -191,28 +192,6 @@ export class BrowserTitleService extends MultiWindowParts i //#endregion } -class TitlebarPartHoverDelegate implements IHoverDelegate { - - readonly showHover = this.hoverService.showHover.bind(this.hoverService); - readonly placement = 'element'; - - private lastHoverHideTime: number = 0; - get delay(): number { - return Date.now() - this.lastHoverHideTime < 200 - ? 0 // show instantly when a hover was recently shown - : this.configurationService.getValue('workbench.hover.delay'); - } - - constructor( - @IHoverService private readonly hoverService: IHoverService, - @IConfigurationService private readonly configurationService: IConfigurationService - ) { } - - onDidHideHover() { - this.lastHoverHideTime = Date.now(); - } -} - export class BrowserTitlebarPart extends Part implements ITitlebarPart { //#region IView @@ -264,7 +243,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { private readonly editorToolbarMenuDisposables = this._register(new DisposableStore()); private readonly layoutToolbarMenuDisposables = this._register(new DisposableStore()); - private readonly hoverDelegate = new TitlebarPartHoverDelegate(this.hoverService, this.configurationService); + private readonly hoverDelegate: IHoverDelegate; private readonly titleDisposables = this._register(new DisposableStore()); private titleBarStyle: TitlebarStyle = getTitleBarStyle(this.configurationService); @@ -290,7 +269,6 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IHostService private readonly hostService: IHostService, - @IHoverService private readonly hoverService: IHoverService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IMenuService private readonly menuService: IMenuService, @@ -304,6 +282,8 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.windowTitle = this._register(instantiationService.createInstance(WindowTitle, targetWindow, editorGroupsContainer)); + this.hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + this.registerListeners(getWindowId(targetWindow)); } @@ -537,22 +517,22 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } } - private actionViewItemProvider(action: IAction): IActionViewItem | undefined { + private actionViewItemProvider(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { // --- Activity Actions if (!this.isAuxiliary) { if (action.id === GLOBAL_ACTIVITY_ID) { - return this.instantiationService.createInstance(SimpleGlobalActivityActionViewItem, { position: () => HoverPosition.BELOW }); + return this.instantiationService.createInstance(SimpleGlobalActivityActionViewItem, { position: () => HoverPosition.BELOW }, options); } if (action.id === ACCOUNTS_ACTIVITY_ID) { - return this.instantiationService.createInstance(SimpleAccountActivityActionViewItem, { position: () => HoverPosition.BELOW }); + return this.instantiationService.createInstance(SimpleAccountActivityActionViewItem, { position: () => HoverPosition.BELOW }, options); } } // --- Editor Actions const activeEditorPane = this.editorGroupsContainer.activeGroup?.activeEditorPane; if (activeEditorPane && activeEditorPane instanceof EditorPane) { - const result = activeEditorPane.getActionViewItem(action); + const result = activeEditorPane.getActionViewItem(action, { hoverDelegate: this.hoverDelegate }); if (result) { return result; @@ -560,7 +540,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } // Check extensions - return createActionViewItem(this.instantiationService, action, { hoverDelegate: this.hoverDelegate, menuAsChild: false }); + return createActionViewItem(this.instantiationService, action, { ...options, hoverDelegate: this.hoverDelegate, menuAsChild: false }); } private getKeybinding(action: IAction): ResolvedKeybinding | undefined { @@ -585,7 +565,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { anchorAlignmentProvider: () => AnchorAlignment.RIGHT, telemetrySource: 'titlePart', highlightToggledItems: this.editorActionsEnabled, // Only show toggled state for editor actions (Layout actions are not shown as toggled) - actionViewItemProvider: action => this.actionViewItemProvider(action) + actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options) })); if (this.editorActionsEnabled) { @@ -820,13 +800,12 @@ export class MainBrowserTitlebarPart extends BrowserTitlebarPart { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextKeyService contextKeyService: IContextKeyService, @IHostService hostService: IHostService, - @IHoverService hoverService: IHoverService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IMenuService menuService: IMenuService, @IKeybindingService keybindingService: IKeybindingService, ) { - super(Parts.TITLEBAR_PART, mainWindow, 'main', contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, hoverService, editorGroupService, editorService, menuService, keybindingService); + super(Parts.TITLEBAR_PART, mainWindow, 'main', contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, editorGroupService, editorService, menuService, keybindingService); } } @@ -854,14 +833,13 @@ export class AuxiliaryBrowserTitlebarPart extends BrowserTitlebarPart implements @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextKeyService contextKeyService: IContextKeyService, @IHostService hostService: IHostService, - @IHoverService hoverService: IHoverService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IMenuService menuService: IMenuService, @IKeybindingService keybindingService: IKeybindingService, ) { const id = AuxiliaryBrowserTitlebarPart.COUNTER++; - super(`workbench.parts.auxiliaryTitle.${id}`, getWindow(container), editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, hoverService, editorGroupService, editorService, menuService, keybindingService); + super(`workbench.parts.auxiliaryTitle.${id}`, getWindow(container), editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, editorGroupService, editorService, menuService, keybindingService); } override get preventZoom(): boolean { diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 19bfe50a6e2..d142226edb7 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -8,7 +8,7 @@ import * as DOM from 'vs/base/browser/dom'; import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { ITooltipMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ElementsDragAndDropData, ListViewTargetSector } from 'vs/base/browser/ui/list/listView'; @@ -73,6 +73,7 @@ import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUti import { ITreeViewsDnDService } from 'vs/editor/common/services/treeViewsDndService'; import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd'; import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export class TreeViewPane extends ViewPane { @@ -1106,15 +1107,11 @@ class TreeRenderer extends Disposable implements ITreeRenderer this.hoverService.showHover(options), - delay: this.configurationService.getValue('workbench.hover.delay') - }; + this._hoverDelegate = getDefaultHoverDelegate('mouse'); this._register(this.themeService.onDidFileIconThemeChange(() => this.rerender())); this._register(this.themeService.onDidColorThemeChange(() => this.rerender())); this._register(checkboxStateHandler.onDidChangeCheckboxState(items => { diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index aa66323770b..978baf437cd 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -432,7 +432,7 @@ export abstract class ViewPane extends Pane implements IView { actions.classList.toggle('show-expanded', this.showActions === ViewPaneShowActions.WhenExpanded); this.toolbar = this.instantiationService.createInstance(WorkbenchToolBar, actions, { orientation: ActionsOrientation.HORIZONTAL, - actionViewItemProvider: action => this.getActionViewItem(action), + actionViewItemProvider: (action, options) => this.getActionViewItem(action, options), ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title), getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), renderDropdownAsChildElement: true, diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 1c7201e246b..f575fa95242 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -40,6 +40,7 @@ import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { FocusedViewContext } from 'vs/workbench/common/contextkeys'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; export const ViewsSubMenu = new MenuId('Views'); MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { @@ -590,11 +591,11 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { return undefined; } - getActionViewItem(action: IAction): IActionViewItem | undefined { + getActionViewItem(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { if (this.isViewMergedWithContainer()) { - return this.paneItems[0].pane.getActionViewItem(action); + return this.paneItems[0].pane.getActionViewItem(action, options); } - return createActionViewItem(this.instantiationService, action); + return createActionViewItem(this.instantiationService, action, options); } focus(): void { diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 9ac40779c6e..8a311d4bb0e 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -43,6 +43,8 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { mainWindow } from 'vs/base/browser/window'; import { PixelRatio } from 'vs/base/browser/pixelRatio'; +import { WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover'; +import { setHoverDelegateFactory } from 'vs/base/browser/ui/hover/hoverDelegate'; export interface IWorkbenchOptions { @@ -152,6 +154,10 @@ export class Workbench extends Layout { const dialogService = accessor.get(IDialogService); const notificationService = accessor.get(INotificationService) as NotificationService; + // Default Hover Delegate must be registered before creating any workbench/layout components + // as these possibly will use the default hover delegate + setHoverDelegateFactory((placement, enableInstantHover) => instantiationService.createInstance(WorkbenchHoverDelegate, placement, enableInstantHover, {})); + // Layout this.initLayout(accessor); diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts index 10c5c92bd46..557d9a025ec 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts @@ -144,7 +144,7 @@ class DocumentSymbolsOutline implements IOutline { this._breadcrumbsDataSource = new DocumentSymbolBreadcrumbsSource(_editor, textResourceConfigurationService); const delegate = new DocumentSymbolVirtualDelegate(); - const renderers = [new DocumentSymbolGroupRenderer(), instantiationService.createInstance(DocumentSymbolRenderer, true)]; + const renderers = [new DocumentSymbolGroupRenderer(), instantiationService.createInstance(DocumentSymbolRenderer, true, target)]; const treeDataSource: IDataSource = { getChildren: (parent) => { if (parent instanceof OutlineElement || parent instanceof OutlineGroup) { diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index 8434fa5acf3..450c690d6a8 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -21,11 +21,11 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { listErrorForeground, listWarningForeground } from 'vs/platform/theme/common/colorRegistry'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { IOutlineComparator, OutlineConfigKeys } from 'vs/workbench/services/outline/browser/outline'; +import { IOutlineComparator, OutlineConfigKeys, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; import { ThemeIcon } from 'vs/base/common/themables'; import { mainWindow } from 'vs/base/browser/window'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { IHoverOptions, IHoverService } from 'vs/platform/hover/browser/hover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export type DocumentSymbolItem = OutlineGroup | OutlineElement; @@ -121,21 +121,11 @@ export class DocumentSymbolRenderer implements ITreeRenderer { - return hoverService.showHover({ - ...options, - persistence: { - hideOnHover: true - } - }); - } - }; + this._hoverDelegate = getDefaultHoverDelegate(target === OutlineTarget.Breadcrumbs ? 'element' : 'mouse'); } renderTemplate(container: HTMLElement): DocumentSymbolTemplate { diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index c930fbdf0e2..4145d4ae91a 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -541,10 +541,10 @@ class SessionsRenderer implements ICompressibleTreeRenderer { + actionViewItemProvider: (action, options) => { if ((action.id === STOP_ID || action.id === DISCONNECT_ID) && action instanceof MenuItemAction) { stopActionViewItemDisposables.clear(); - const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, stopActionViewItemDisposables, accessor)); + const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, stopActionViewItemDisposables, accessor, options)); if (item) { return item; } diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index db14887850c..433a562e4bf 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -18,7 +18,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ADD_CONFIGURATION_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; -import { BaseActionViewItem, SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { BaseActionViewItem, IBaseActionViewItemOptions, SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { debugStart } from 'vs/workbench/contrib/debug/browser/debugIcons'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; @@ -40,6 +40,7 @@ export class StartDebugActionViewItem extends BaseActionViewItem { constructor( private context: unknown, action: IAction, + options: IBaseActionViewItemOptions, @IDebugService private readonly debugService: IDebugService, @IConfigurationService private readonly configurationService: IConfigurationService, @ICommandService private readonly commandService: ICommandService, @@ -47,7 +48,7 @@ export class StartDebugActionViewItem extends BaseActionViewItem { @IContextViewService contextViewService: IContextViewService, @IKeybindingService private readonly keybindingService: IKeybindingService ) { - super(context, action); + super(context, action, options); this.toDispose = []; this.selectBox = new SelectBox([], -1, contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('debugLaunchConfigurations', 'Debug Launch Configurations') }); this.selectBox.setFocusable(false); diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index c46cd2d9a6f..0b9fb05501d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -16,7 +16,7 @@ import 'vs/css!./media/debugToolBar'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { ICommandAction, ICommandActionTitle } from 'vs/platform/action/common/action'; -import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem'; +import { DropdownWithPrimaryActionViewItem, IDropdownWithPrimaryActionViewItemOptions } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem'; import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu, IMenuService, MenuId, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -40,6 +40,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { CodeWindow, mainWindow } from 'vs/base/browser/window'; import { clamp } from 'vs/base/common/numbers'; import { PixelRatio } from 'vs/base/browser/pixelRatio'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; const DEBUG_TOOLBAR_POSITION_KEY = 'debug.actionswidgetposition'; const DEBUG_TOOLBAR_Y_KEY = 'debug.actionswidgety'; @@ -89,18 +90,18 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this.activeActions = []; this.actionBar = this._register(new ActionBar(actionBarContainer, { orientation: ActionsOrientation.HORIZONTAL, - actionViewItemProvider: (action: IAction) => { + actionViewItemProvider: (action: IAction, options: IBaseActionViewItemOptions) => { if (action.id === FOCUS_SESSION_ID) { return this.instantiationService.createInstance(FocusSessionActionViewItem, action, undefined); } else if (action.id === STOP_ID || action.id === DISCONNECT_ID) { this.stopActionViewItemDisposables.clear(); - const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, this.stopActionViewItemDisposables, accessor)); + const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, this.stopActionViewItemDisposables, accessor, { hoverDelegate: options.hoverDelegate })); if (item) { return item; } } - return createActionViewItem(this.instantiationService, action); + return createActionViewItem(this.instantiationService, action, options); } })); @@ -337,7 +338,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } } -export function createDisconnectMenuItemAction(action: MenuItemAction, disposables: DisposableStore, accessor: ServicesAccessor): IActionViewItem | undefined { +export function createDisconnectMenuItemAction(action: MenuItemAction, disposables: DisposableStore, accessor: ServicesAccessor, options: IDropdownWithPrimaryActionViewItemOptions): IActionViewItem | undefined { const menuService = accessor.get(IMenuService); const contextKeyService = accessor.get(IContextKeyService); const instantiationService = accessor.get(IInstantiationService); @@ -358,7 +359,7 @@ export function createDisconnectMenuItemAction(action: MenuItemAction, disposabl secondary, 'debug-stop-actions', contextMenuService, - {}); + options); return item; } diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 24c202b7c34..6a51b8f273a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -33,6 +33,7 @@ import { WelcomeView } from 'vs/workbench/contrib/debug/browser/welcomeView'; import { BREAKPOINTS_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY, getStateLabel, IDebugService, ILaunch, REPL_VIEW_ID, State, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class DebugViewPaneContainer extends ViewPaneContainer { @@ -93,9 +94,9 @@ export class DebugViewPaneContainer extends ViewPaneContainer { } } - override getActionViewItem(action: IAction): IActionViewItem | undefined { + override getActionViewItem(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { if (action.id === DEBUG_START_COMMAND_ID) { - this.startDebugActionViewItem = this.instantiationService.createInstance(StartDebugActionViewItem, null, action); + this.startDebugActionViewItem = this.instantiationService.createInstance(StartDebugActionViewItem, null, action, options); return this.startDebugActionViewItem; } if (action.id === FOCUS_SESSION_ID) { @@ -104,13 +105,13 @@ export class DebugViewPaneContainer extends ViewPaneContainer { if (action.id === STOP_ID || action.id === DISCONNECT_ID) { this.stopActionViewItemDisposables.clear(); - const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, this.stopActionViewItemDisposables, accessor)); + const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, this.stopActionViewItemDisposables, accessor, { hoverDelegate: options.hoverDelegate })); if (item) { return item; } } - return createActionViewItem(this.instantiationService, action); + return createActionViewItem(this.instantiationService, action, options); } focusView(id: string): void { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 53819a2eb53..38c0e666af1 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -590,7 +590,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE toolbarOptions: { primaryGroup: () => true, }, - actionViewItemProvider: action => createActionViewItem(this.instantiationService, action) + actionViewItemProvider: (action, options) => createActionViewItem(this.instantiationService, action, options) })); // Register DragAndDrop support diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index ffd76aca945..f9c766ca849 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -201,8 +201,8 @@ export class InteractiveEditor extends EditorPane { const menu = this._register(this._menuService.createMenu(MenuId.InteractiveInputExecute, this._contextKeyService)); this._runbuttonToolbar = this._register(new ToolBar(runButtonContainer, this._contextMenuService, { getKeyBinding: action => this._keybindingService.lookupKeybinding(action.id), - actionViewItemProvider: action => { - return createActionViewItem(this._instantiationService, action); + actionViewItemProvider: (action, options) => { + return createActionViewItem(this._instantiationService, action, options); }, renderDropdownAsChildElement: true })); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts index 3a38688600a..a7b79d85684 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts @@ -167,8 +167,8 @@ export class CellTitleToolbarPart extends CellOverlayPart { } const toolbar = this._register(this.instantiationService.createInstance(WorkbenchToolBar, this.toolbarContainer, { - actionViewItemProvider: action => { - return createActionViewItem(this.instantiationService, action); + actionViewItemProvider: (action, options) => { + return createActionViewItem(this.instantiationService, action, options); }, renderDropdownAsChildElement: true })); @@ -275,8 +275,8 @@ function createDeleteToolbar(accessor: ServicesAccessor, container: HTMLElement, const instantiationService = accessor.get(IInstantiationService); const toolbar = new ToolBar(container, contextMenuService, { getKeyBinding: action => keybindingService.lookupKeybinding(action.id), - actionViewItemProvider: action => { - return createActionViewItem(instantiationService, action); + actionViewItemProvider: (action, options) => { + return createActionViewItem(instantiationService, action, options); }, renderDropdownAsChildElement: true }); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 0753f9429c5..3559f22390e 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -51,12 +51,12 @@ import { WorkbenchTable } from 'vs/platform/list/browser/listService'; import { Button } from 'vs/base/browser/ui/button/button'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { STATUS_BAR_REMOTE_ITEM_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, forwardedPortsViewEnabled, makeAddress, mapHasAddressLocalhostOrAllInterfaces, parseAddress } from 'vs/workbench/services/remote/common/tunnelModel'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export const openPreviewEnabledContext = new RawContextKey('openPreviewEnabled', false); @@ -342,6 +342,7 @@ class ActionBarRenderer extends Disposable implements ITableRenderer void; private _actionRunner: ActionRunner | undefined; + private readonly _hoverDelegate: IHoverDelegate; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -351,8 +352,11 @@ class ActionBarRenderer extends Disposable implements ITableRenderer this.hoverService.showHover(options), - delay: this.configurationService.getValue('workbench.hover.delay') - } + hoverDelegate: this._hoverDelegate }); const actionsContainer = dom.append(cell, dom.$('.actions')); const actionBar = new ActionBar(actionsContainer, { - actionViewItemProvider: createActionViewItem.bind(undefined, this.instantiationService) + actionViewItemProvider: createActionViewItem.bind(undefined, this.instantiationService), + hoverDelegate: this._hoverDelegate }); return { label, icon, actionBar, container: cell, elementDisposable: Disposable.None }; } @@ -781,7 +783,6 @@ export class TunnelPanel extends ViewPane { @ITelemetryService telemetryService: ITelemetryService, @ITunnelService private readonly tunnelService: ITunnelService, @IContextViewService private readonly contextViewService: IContextViewService, - @IHoverService private readonly hoverService: IHoverService ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.tunnelTypeContext = TunnelTypeContextKey.bindTo(contextKeyService); @@ -865,7 +866,7 @@ export class TunnelPanel extends ViewPane { const actionBarRenderer = new ActionBarRenderer(this.instantiationService, this.contextKeyService, this.menuService, this.contextViewService, this.remoteExplorerService, this.commandService, - this.configurationService, this.hoverService); + this.configurationService); const columns = [new IconColumn(), new PortColumn(), new LocalAddressColumn(), new RunningProcessColumn()]; if (this.tunnelService.canChangePrivacy) { columns.push(new PrivacyColumn()); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index a42ecfd8374..ca7a033fdcd 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -2492,12 +2492,12 @@ class SCMInputWidget { // Toolbar this.toolbar = instantiationService2.createInstance(SCMInputWidgetToolbar, this.toolbarContainer, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action instanceof MenuItemAction && this.toolbar.dropdownActions.length > 1) { - return instantiationService.createInstance(DropdownWithPrimaryActionViewItem, action, this.toolbar.dropdownAction, this.toolbar.dropdownActions, '', this.contextMenuService, { actionRunner: this.toolbar.actionRunner }); + return instantiationService.createInstance(DropdownWithPrimaryActionViewItem, action, this.toolbar.dropdownAction, this.toolbar.dropdownActions, '', this.contextMenuService, { actionRunner: this.toolbar.actionRunner, hoverDelegate: options.hoverDelegate }); } - return createActionViewItem(instantiationService, action); + return createActionViewItem(instantiationService, action, options); }, menuOptions: { shouldForwardArgs: true diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index be5afbb292f..d44c36c332b 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -12,7 +12,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { Action, IAction } from 'vs/base/common/actions'; import { createActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { equals } from 'vs/base/common/arrays'; -import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { ActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { Command } from 'vs/editor/common/languages'; @@ -149,8 +149,8 @@ export class StatusBarAction extends Action { class StatusBarActionViewItem extends ActionViewItem { - constructor(action: StatusBarAction) { - super(null, action, {}); + constructor(action: StatusBarAction, options: IBaseActionViewItemOptions) { + super(null, action, options); } protected override updateLabel(): void { @@ -161,11 +161,11 @@ class StatusBarActionViewItem extends ActionViewItem { } export function getActionViewItemProvider(instaService: IInstantiationService): IActionViewItemProvider { - return action => { + return (action, options) => { if (action instanceof StatusBarAction) { - return new StatusBarActionViewItem(action); + return new StatusBarActionViewItem(action, options); } - return createActionViewItem(instaService, action); + return createActionViewItem(instaService, action, options); }; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts index 63ae90748b4..6835b5e9c35 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts @@ -29,6 +29,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { openContextMenu } from 'vs/workbench/contrib/terminal/browser/terminalContextMenu'; import { ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class TerminalEditor extends EditorPane { @@ -203,18 +204,18 @@ export class TerminalEditor extends EditorPane { this._editorInput?.terminalInstance?.setVisible(visible && this._workbenchLayoutService.isVisible(Parts.EDITOR_PART, dom.getWindow(this._editorInstanceElement))); } - override getActionViewItem(action: IAction): IActionViewItem | undefined { + override getActionViewItem(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { switch (action.id) { case TerminalCommandId.CreateTerminalEditor: { if (action instanceof MenuItemAction) { const location = { viewColumn: ACTIVE_GROUP }; const actions = getTerminalActionBarArgs(location, this._terminalProfileService.availableProfiles, this._getDefaultProfileName(), this._terminalProfileService.contributedProfiles, this._terminalService, this._dropdownMenu); - const button = this._instantiationService.createInstance(DropdownWithPrimaryActionViewItem, action, actions.dropdownAction, actions.dropdownMenuActions, actions.className, this._contextMenuService, {}); + const button = this._instantiationService.createInstance(DropdownWithPrimaryActionViewItem, action, actions.dropdownAction, actions.dropdownMenuActions, actions.className, this._contextMenuService, { hoverDelegate: options.hoverDelegate }); return button; } } } - return super.getActionViewItem(action); + return super.getActionViewItem(action, options); } private _getDefaultProfileName(): string { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index c5d7591819e..31ed968488e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -23,7 +23,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IMenu, IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ITerminalProfileResolverService, ITerminalProfileService, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalSettingId, ITerminalProfile, TerminalLocation } from 'vs/platform/terminal/common/terminal'; -import { ActionViewItem, SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { ActionViewItem, IBaseActionViewItemOptions, SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { asCssVariable, selectBorder } from 'vs/platform/theme/common/colorRegistry'; import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -236,7 +236,7 @@ export class TerminalViewPane extends ViewPane { this._terminalTabbedView?.layout(width, height); } - override getActionViewItem(action: Action): IActionViewItem | undefined { + override getActionViewItem(action: Action, options: IBaseActionViewItemOptions): IActionViewItem | undefined { switch (action.id) { case TerminalCommandId.Split: { // Split needs to be special cased to force splitting within the panel, not the editor @@ -257,7 +257,7 @@ export class TerminalViewPane extends ViewPane { return; } }; - return new ActionViewItem(action, panelOnlySplitAction, { icon: true, label: false, keybinding: this._getKeybindingLabel(action) }); + return new ActionViewItem(action, panelOnlySplitAction, { ...options, icon: true, label: false, keybinding: this._getKeybindingLabel(action) }); } case TerminalCommandId.SwitchTerminal: { return this._instantiationService.createInstance(SwitchTerminalActionViewItem, action); @@ -273,13 +273,13 @@ export class TerminalViewPane extends ViewPane { if (action instanceof MenuItemAction) { const actions = getTerminalActionBarArgs(TerminalLocation.Panel, this._terminalProfileService.availableProfiles, this._getDefaultProfileName(), this._terminalProfileService.contributedProfiles, this._terminalService, this._dropdownMenu); this._newDropdown?.dispose(); - this._newDropdown = new DropdownWithPrimaryActionViewItem(action, actions.dropdownAction, actions.dropdownMenuActions, actions.className, this._contextMenuService, {}, this._keybindingService, this._notificationService, this._contextKeyService, this._themeService, this._accessibilityService); + this._newDropdown = new DropdownWithPrimaryActionViewItem(action, actions.dropdownAction, actions.dropdownMenuActions, actions.className, this._contextMenuService, { hoverDelegate: options.hoverDelegate }, this._keybindingService, this._notificationService, this._contextKeyService, this._themeService, this._accessibilityService); this._updateTabActionBar(this._terminalProfileService.availableProfiles); return this._newDropdown; } } } - return super.getActionViewItem(action); + return super.getActionViewItem(action, options); } private _getDefaultProfileName(): string { diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index 4110388332e..ba7f2800830 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -473,7 +473,7 @@ class ResultSummaryView extends Disposable { })); const ab = this._register(new ActionBar(this.elements.rerun, { - actionViewItemProvider: action => createActionViewItem(instantiationService, action), + actionViewItemProvider: (action, options) => createActionViewItem(instantiationService, action, options), })); ab.push(instantiationService.createInstance(MenuItemAction, { ...new ReRunLastRun().desc, icon: icons.testingRerunIcon }, diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index d919f15af47..70afef1e015 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -49,13 +49,13 @@ import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from 'vs/ import { MarshalledId } from 'vs/base/common/marshallingIds'; import { isString } from 'vs/base/common/types'; import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; -import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { AriaRole } from 'vs/base/browser/ui/aria/aria'; import { ILocalizedString } from 'vs/platform/action/common/action'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; const ItemHeight = 22; @@ -1147,14 +1147,9 @@ class TimelineTreeRenderer implements ITreeRenderer this.hoverService.showHover(options), - delay: this.configurationService.getValue('workbench.hover.delay') - }; + this._hoverDelegate = getDefaultHoverDelegate('element'); } private uri: URI | undefined; diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index fbf85313f6c..5d989fc5e1b 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -23,7 +23,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { NativeMenubarControl } from 'vs/workbench/electron-sandbox/parts/titlebar/menubarControl'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IEditorGroupsContainer, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -72,13 +71,12 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { @IContextKeyService contextKeyService: IContextKeyService, @IHostService hostService: IHostService, @INativeHostService private readonly nativeHostService: INativeHostService, - @IHoverService hoverService: IHoverService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IMenuService menuService: IMenuService, @IKeybindingService keybindingService: IKeybindingService ) { - super(id, targetWindow, editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, hoverService, editorGroupService, editorService, menuService, keybindingService); + super(id, targetWindow, editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, editorGroupService, editorService, menuService, keybindingService); this.bigSurOrNewer = isBigSurOrNewer(environmentService.os.release); } @@ -286,13 +284,12 @@ export class MainNativeTitlebarPart extends NativeTitlebarPart { @IContextKeyService contextKeyService: IContextKeyService, @IHostService hostService: IHostService, @INativeHostService nativeHostService: INativeHostService, - @IHoverService hoverService: IHoverService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IMenuService menuService: IMenuService, @IKeybindingService keybindingService: IKeybindingService ) { - super(Parts.TITLEBAR_PART, mainWindow, 'main', contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, nativeHostService, hoverService, editorGroupService, editorService, menuService, keybindingService); + super(Parts.TITLEBAR_PART, mainWindow, 'main', contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, nativeHostService, editorGroupService, editorService, menuService, keybindingService); } } @@ -316,14 +313,13 @@ export class AuxiliaryNativeTitlebarPart extends NativeTitlebarPart implements I @IContextKeyService contextKeyService: IContextKeyService, @IHostService hostService: IHostService, @INativeHostService nativeHostService: INativeHostService, - @IHoverService hoverService: IHoverService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IMenuService menuService: IMenuService, @IKeybindingService keybindingService: IKeybindingService ) { const id = AuxiliaryNativeTitlebarPart.COUNTER++; - super(`workbench.parts.auxiliaryTitle.${id}`, getWindow(container), editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, nativeHostService, hoverService, editorGroupService, editorService, menuService, keybindingService); + super(`workbench.parts.auxiliaryTitle.${id}`, getWindow(container), editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, nativeHostService, editorGroupService, editorService, menuService, keybindingService); } override get preventZoom(): boolean { diff --git a/src/vs/workbench/services/quickinput/browser/quickInputService.ts b/src/vs/workbench/services/quickinput/browser/quickInputService.ts index 463bea822b2..8e09c681660 100644 --- a/src/vs/workbench/services/quickinput/browser/quickInputService.ts +++ b/src/vs/workbench/services/quickinput/browser/quickInputService.ts @@ -14,7 +14,6 @@ import { QuickInputService as BaseQuickInputService } from 'vs/platform/quickinp import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { InQuickPickContextKey } from 'vs/workbench/browser/quickaccess'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; export class QuickInputService extends BaseQuickInputService { @@ -27,9 +26,8 @@ export class QuickInputService extends BaseQuickInputService { @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, @ILayoutService layoutService: ILayoutService, - @IHoverService hoverService: IHoverService ) { - super(instantiationService, contextKeyService, themeService, layoutService, configurationService, hoverService); + super(instantiationService, contextKeyService, themeService, layoutService, configurationService); this.registerListeners(); } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 10461c8e293..23355faf76d 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -163,13 +163,11 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { EnablementState, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { InstallVSIXOptions, ILocalExtension, IGalleryExtension, InstallOptions, IExtensionIdentifier, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant, Metadata, InstallExtensionResult, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Codicon } from 'vs/base/common/codicons'; -import { IHoverOptions, IHoverService } from 'vs/platform/hover/browser/hover'; import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; import { IRemoteSocketFactoryService, RemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; import { EditorParts } from 'vs/workbench/browser/parts/editor/editorParts'; import { mainWindow } from 'vs/base/browser/window'; import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IEditorPaneService } from 'vs/workbench/services/editor/common/editorPaneService'; import { EditorPaneService } from 'vs/workbench/services/editor/browser/editorPaneService'; @@ -334,8 +332,7 @@ export function workbenchInstantiationService( instantiationService.stub(ICodeEditorService, disposables.add(new CodeEditorService(editorService, themeService, configService))); instantiationService.stub(IPaneCompositePartService, disposables.add(new TestPaneCompositeService())); instantiationService.stub(IListService, new TestListService()); - const hoverService = instantiationService.stub(IHoverService, instantiationService.createInstance(TestHoverService)); - instantiationService.stub(IQuickInputService, disposables.add(new QuickInputService(configService, instantiationService, keybindingService, contextKeyService, themeService, layoutService, hoverService))); + instantiationService.stub(IQuickInputService, disposables.add(new QuickInputService(configService, instantiationService, keybindingService, contextKeyService, themeService, layoutService))); instantiationService.stub(IWorkspacesService, new TestWorkspacesService()); instantiationService.stub(IWorkspaceTrustManagementService, disposables.add(new TestWorkspaceTrustManagementService())); instantiationService.stub(IWorkspaceTrustRequestService, disposables.add(new TestWorkspaceTrustRequestService(false))); @@ -758,25 +755,6 @@ export class TestSideBarPart implements IPaneCompositePart { layout(width: number, height: number, top: number, left: number): void { } } -class TestHoverService implements IHoverService { - private currentHover: IHoverWidget | undefined; - _serviceBrand: undefined; - showHover(options: IHoverOptions, focus?: boolean | undefined): IHoverWidget | undefined { - this.currentHover = new class implements IHoverWidget { - private _isDisposed = false; - get isDisposed(): boolean { return this._isDisposed; } - dispose(): void { - this._isDisposed = true; - } - }; - return this.currentHover; - } - showAndFocusLastHover(): void { } - hideHover(): void { - this.currentHover?.dispose(); - } -} - export class TestPanelPart implements IPaneCompositePart { declare readonly _serviceBrand: undefined; From 85aaa473fbf47de6762d990d6982c42e53886340 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:25:54 -0800 Subject: [PATCH 1315/1897] Hide command border before it's shown --- .../chat/browser/terminalChatWidget.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 637e2f77104..bc7442b5f48 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -31,7 +31,7 @@ export class TerminalChatWidget extends Disposable { private readonly _inlineChatWidget: InlineChatWidget; public get inlineChatWidget(): InlineChatWidget { return this._inlineChatWidget; } - private readonly _terminalCommandWidgetContainer: HTMLElement; + private _terminalCommandWidgetContainer: HTMLElement | undefined; private _terminalCommandWidget: CodeEditorWidget | undefined; private readonly _focusTracker: IFocusTracker; @@ -60,10 +60,6 @@ export class TerminalChatWidget extends Disposable { this._container.classList.add('terminal-inline-chat'); terminalElement.appendChild(this._container); - this._terminalCommandWidgetContainer = document.createElement('div'); - this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); - this._container.prepend(this._terminalCommandWidgetContainer); - // The inline chat widget requires a parent editor that it bases the diff view on, since the // terminal doesn't use that feature we can just pass in an unattached editor instance. const fakeParentEditorElement = document.createElement('div'); @@ -106,6 +102,9 @@ export class TerminalChatWidget extends Disposable { this._chatAccessibilityService.acceptResponse(command, requestId); this.showTerminalCommandWidget(); if (!this._terminalCommandWidget) { + this._terminalCommandWidgetContainer = document.createElement('div'); + this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); + this._container.prepend(this._terminalCommandWidgetContainer); this._terminalCommandWidget = this._register(this._instantiationService.createInstance(CodeEditorWidget, this._terminalCommandWidgetContainer, { readOnly: false, ariaLabel: this._getAriaLabel(), @@ -248,9 +247,9 @@ export class TerminalChatWidget extends Disposable { return this._focusTracker; } hideTerminalCommandWidget(): void { - this._terminalCommandWidgetContainer.classList.add('hide'); + this._terminalCommandWidgetContainer?.classList.add('hide'); } showTerminalCommandWidget(): void { - this._terminalCommandWidgetContainer.classList.remove('hide'); + this._terminalCommandWidgetContainer?.classList.remove('hide'); } } From 45e54c6f405e759e91e736a35ae8a58b1209cceb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:28:54 -0800 Subject: [PATCH 1316/1897] Wider widget, resize suggestion based on content height --- .../chat/browser/terminalChatWidget.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index bc7442b5f48..d65b9a0ffc3 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -105,7 +105,7 @@ export class TerminalChatWidget extends Disposable { this._terminalCommandWidgetContainer = document.createElement('div'); this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); this._container.prepend(this._terminalCommandWidgetContainer); - this._terminalCommandWidget = this._register(this._instantiationService.createInstance(CodeEditorWidget, this._terminalCommandWidgetContainer, { + const widget = this._terminalCommandWidget = this._register(this._instantiationService.createInstance(CodeEditorWidget, this._terminalCommandWidgetContainer, { readOnly: false, ariaLabel: this._getAriaLabel(), fontSize: 13, @@ -146,14 +146,18 @@ export class TerminalChatWidget extends Disposable { }, { isSimpleWidget: true })); this._register(this._terminalCommandWidget.onDidFocusEditorText(() => this._responseEditorFocusedContextKey.set(true))); this._register(this._terminalCommandWidget.onDidBlurEditorText(() => this._responseEditorFocusedContextKey.set(false))); + this._register(this._terminalCommandWidget.onDidChangeModelContent(e => { + const height = widget.getContentHeight(); + widget.layout(new Dimension(640, height)); + })); this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: command })).then((model) => { if (!model || !this._terminalCommandWidget) { return; } - this._terminalCommandWidget.layout(new Dimension(400, 0)); + this._terminalCommandWidget.layout(new Dimension(640, 0)); this._terminalCommandWidget.setModel(model); const height = this._terminalCommandWidget.getContentHeight(); - this._terminalCommandWidget.layout(new Dimension(400, height)); + this._terminalCommandWidget.layout(new Dimension(640, height)); }); } else { this._terminalCommandWidget.setValue(command); @@ -194,7 +198,7 @@ export class TerminalChatWidget extends Disposable { return this._modelService.createModel(resource.fragment, null, resource, false); } reveal(): void { - this._inlineChatWidget.layout(new Dimension(400, 150)); + this._inlineChatWidget.layout(new Dimension(640, 150)); this._container.classList.remove('hide'); this._focusedContextKey.set(true); this._visibleContextKey.set(true); From 0dadd8f5402a6e2b21c696d16750c72b34f03ec3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:29:53 -0800 Subject: [PATCH 1317/1897] Trim command before accepting --- .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index d65b9a0ffc3..c049e71d013 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -237,7 +237,8 @@ export class TerminalChatWidget extends Disposable { } } acceptCommand(shouldExecute: boolean): void { - const value = this._terminalCommandWidget?.getValue(); + // Trim command to remove any whitespace, otherwise this may execute the command + const value = this._terminalCommandWidget?.getValue().trim(); if (!value) { return; } From 69592b6d498fa3dab8c4c7a1710843f7c12aae37 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:33:37 -0800 Subject: [PATCH 1318/1897] Standardize on workbench contrib weight --- .../chat/browser/terminalChatActions.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 19d9fdf140c..3ad13dbb898 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -22,7 +22,7 @@ registerActiveXtermAction({ keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), - weight: KeybindingWeight.WorkbenchContrib + weight: KeybindingWeight.WorkbenchContrib, }, f1: true, precondition: ContextKeyExpr.and( @@ -47,7 +47,7 @@ registerActiveXtermAction({ primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], when: ContextKeyExpr.and(TerminalContextKeys.chatFocused, TerminalContextKeys.chatVisible), - weight: KeybindingWeight.WorkbenchContrib + weight: KeybindingWeight.WorkbenchContrib, }, icon: Codicon.close, menu: { @@ -86,7 +86,7 @@ registerActiveXtermAction({ icon: Codicon.check, keybinding: { when: TerminalContextKeys.chatRequestActive.negate(), - weight: KeybindingWeight.WorkbenchContrib + 7, + weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.Enter, }, menu: { @@ -118,7 +118,7 @@ registerActiveXtermAction({ icon: Codicon.check, keybinding: { when: TerminalContextKeys.chatRequestActive.negate(), - weight: KeybindingWeight.WorkbenchContrib + 7, + weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Alt | KeyCode.Enter, }, menu: { @@ -175,7 +175,7 @@ registerActiveXtermAction({ icon: Codicon.send, keybinding: { when: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, TerminalContextKeys.chatRequestActive.negate()), - weight: KeybindingWeight.EditorCore + 7, + weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.Enter }, menu: { From 06afd593735a230025ed5b9b5010574ef5748d22 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:36:50 -0800 Subject: [PATCH 1319/1897] Await model (fixes lang mode) --- .../chat/browser/terminalChatWidget.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index c049e71d013..91bec4acda0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -98,9 +98,10 @@ export class TerminalChatWidget extends Disposable { return localize('terminalChatInput', "Terminal Chat Input"); } - renderTerminalCommand(command: string, requestId: number, shellType?: string): void { + async renderTerminalCommand(command: string, requestId: number, shellType?: string): Promise { this._chatAccessibilityService.acceptResponse(command, requestId); this.showTerminalCommandWidget(); + let model: ITextModel | null | void = null; if (!this._terminalCommandWidget) { this._terminalCommandWidgetContainer = document.createElement('div'); this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); @@ -150,7 +151,7 @@ export class TerminalChatWidget extends Disposable { const height = widget.getContentHeight(); widget.layout(new Dimension(640, height)); })); - this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: command })).then((model) => { + model = await this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: command })).then((model) => { if (!model || !this._terminalCommandWidget) { return; } @@ -162,9 +163,13 @@ export class TerminalChatWidget extends Disposable { } else { this._terminalCommandWidget.setValue(command); } - const languageId = this._getLanguageFromShell(shellType); - console.log('languageId', languageId); - this._terminalCommandWidget.getModel()?.setLanguage(languageId); + if (!model) { + model = this._terminalCommandWidget.getModel(); + } + if (model) { + const languageId = this._getLanguageFromShell(shellType); + model.setLanguage(languageId); + } } private _getLanguageFromShell(shell?: string): string { From d30f7018d2ba0b4fe35816989363e6f5b84f7361 Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:39:05 -0800 Subject: [PATCH 1320/1897] Add Python Shell Type and .python_history (#204680) * Add Python Shell type and history * remove one comment * remove unncessary * Why can't I manually override setShellType * Detect python shell type on Windows * fire shellType in _sendProcessTitle * Detect python shell type on Windows * delete comment * remove more comments * remove comment * remove dup * clean up * more comprehensive regex for windows python * follow previous style for map key name * remove unused variable * remove comment * last cleanup * More cleanup * more clean up * re-arrange * remove unused --------- Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/vs/platform/terminal/common/terminal.ts | 4 ++- .../platform/terminal/node/terminalProcess.ts | 8 +++++- .../terminal/node/windowsShellHelper.ts | 3 +++ .../terminal/browser/terminalInstance.ts | 1 + .../contrib/terminal/common/history.ts | 27 +++++++++++++++++++ 5 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index ffe0e56a189..a0b94d2a7f7 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -140,12 +140,14 @@ export const enum PosixShellType { Csh = 'csh', Ksh = 'ksh', Zsh = 'zsh', + Python = 'python' } export const enum WindowsShellType { CommandPrompt = 'cmd', PowerShell = 'pwsh', Wsl = 'wsl', - GitBash = 'gitbash' + GitBash = 'gitbash', + Python = 'python' } export type TerminalShellType = PosixShellType | WindowsShellType; diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index 023b6dc66e0..79be9013c81 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -73,6 +73,7 @@ const posixShellTypeMap = new Map([ ['ksh', PosixShellType.Ksh], ['sh', PosixShellType.Sh], ['pwsh', PosixShellType.PowerShell], + ['python', PosixShellType.Python], ['zsh', PosixShellType.Zsh] ]); @@ -404,7 +405,12 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess this._onDidChangeProperty.fire({ type: ProcessPropertyType.Title, value: this._currentTitle }); // If fig is installed it may change the title of the process const sanitizedTitle = this.currentTitle.replace(/ \(figterm\)$/g, ''); - this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellType, value: posixShellTypeMap.get(sanitizedTitle) }); + + if (sanitizedTitle.toLowerCase().startsWith('python')) { + this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellType, value: PosixShellType.Python }); + } else { + this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellType, value: posixShellTypeMap.get(sanitizedTitle) }); + } } shutdown(immediate: boolean): void { diff --git a/src/vs/platform/terminal/node/windowsShellHelper.ts b/src/vs/platform/terminal/node/windowsShellHelper.ts index 1def6545d69..e9fcb6f8130 100644 --- a/src/vs/platform/terminal/node/windowsShellHelper.ts +++ b/src/vs/platform/terminal/node/windowsShellHelper.ts @@ -152,6 +152,9 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe case 'sles-12.exe': return WindowsShellType.Wsl; default: + if (executable.match(/python(\d(\.\d{0,2})?)?\.exe/)) { + return WindowsShellType.Python; + } return undefined; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index e46e771f2a8..8b11cc8d9e8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -118,6 +118,7 @@ const shellIntegrationSupportedShellTypes = [ PosixShellType.Bash, PosixShellType.Zsh, PosixShellType.PowerShell, + PosixShellType.Python, WindowsShellType.PowerShell ]; diff --git a/src/vs/workbench/contrib/terminal/common/history.ts b/src/vs/workbench/contrib/terminal/common/history.ts index cf0d940e716..ca2588453d2 100644 --- a/src/vs/workbench/contrib/terminal/common/history.ts +++ b/src/vs/workbench/contrib/terminal/common/history.ts @@ -92,6 +92,9 @@ export async function getShellFileHistory(accessor: ServicesAccessor, shellType: case PosixShellType.Fish: result = await fetchFishHistory(accessor); break; + case PosixShellType.Python: + result = await fetchPythonHistory(accessor); + break; default: return []; } if (result === undefined) { @@ -295,6 +298,30 @@ export async function fetchZshHistory(accessor: ServicesAccessor) { return result.values(); } + +export async function fetchPythonHistory(accessor: ServicesAccessor): Promise | undefined> { + const fileService = accessor.get(IFileService); + const remoteAgentService = accessor.get(IRemoteAgentService); + + const content = await fetchFileContents(env['HOME'], '.python_history', false, fileService, remoteAgentService); + + if (content === undefined) { + return undefined; + } + + // Python history file is a simple text file with one command per line + const fileLines = content.split('\n'); + const result: Set = new Set(); + + fileLines.forEach(line => { + if (line.trim().length > 0) { + result.add(line.trim()); + } + }); + + return result.values(); +} + export async function fetchPwshHistory(accessor: ServicesAccessor) { const fileService: Pick = accessor.get(IFileService); const remoteAgentService: Pick = accessor.get(IRemoteAgentService); From b269a4b6a536614f5f6357fa686572e2bc8aa1b3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:44:46 -0800 Subject: [PATCH 1321/1897] Improve handling of wrapping in response editor --- .../chat/browser/terminalChatWidget.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 91bec4acda0..32e1e685ba8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Dimension, IFocusTracker, trackFocus } from 'vs/base/browser/dom'; +import { Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -144,21 +145,22 @@ export class TerminalChatWidget extends Disposable { showWords: true, showStatusBar: false, }, + wordWrap: 'on' }, { isSimpleWidget: true })); this._register(this._terminalCommandWidget.onDidFocusEditorText(() => this._responseEditorFocusedContextKey.set(true))); this._register(this._terminalCommandWidget.onDidBlurEditorText(() => this._responseEditorFocusedContextKey.set(false))); - this._register(this._terminalCommandWidget.onDidChangeModelContent(e => { + this._register(Event.any(this._terminalCommandWidget.onDidChangeModelContent, this._terminalCommandWidget.onDidChangeModelDecorations)(() => { const height = widget.getContentHeight(); widget.layout(new Dimension(640, height)); })); model = await this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: command })).then((model) => { - if (!model || !this._terminalCommandWidget) { + if (!model) { return; } - this._terminalCommandWidget.layout(new Dimension(640, 0)); - this._terminalCommandWidget.setModel(model); - const height = this._terminalCommandWidget.getContentHeight(); - this._terminalCommandWidget.layout(new Dimension(640, height)); + widget.layout(new Dimension(640, 0)); + widget.setModel(model); + const height = widget.getContentHeight(); + widget.layout(new Dimension(640, height)); }); } else { this._terminalCommandWidget.setValue(command); From aa5626dc23146a651ae078db2860e436d9e9104a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 13:57:34 -0600 Subject: [PATCH 1322/1897] fix issue with cancel --- .../terminalContrib/chat/browser/terminalChatController.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 2585ae766da..8f9c3defee0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -139,6 +139,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (this._currentRequest) { this._model?.cancelRequest(this._currentRequest); } + this._requestActiveContextKey.set(false); + this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(false); + this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(''); + this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); } private _forcedPlaceholder: string | undefined = undefined; From a1ac397ba2b9c4404836b3606b5b52bc580a880f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:06:24 -0800 Subject: [PATCH 1323/1897] Pull response editor into separate class --- .../chat/browser/terminalChatWidget.ts | 256 ++++++++++-------- 1 file changed, 145 insertions(+), 111 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 32e1e685ba8..014f746c786 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -6,7 +6,7 @@ import { Dimension, IFocusTracker, trackFocus } from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/terminalChatWidget'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -33,29 +33,24 @@ export class TerminalChatWidget extends Disposable { public get inlineChatWidget(): InlineChatWidget { return this._inlineChatWidget; } private _terminalCommandWidgetContainer: HTMLElement | undefined; - private _terminalCommandWidget: CodeEditorWidget | undefined; + private _responseEditor: TerminalChatResponseEditor | undefined; private readonly _focusTracker: IFocusTracker; private readonly _focusedContextKey: IContextKey; private readonly _visibleContextKey: IContextKey; - private readonly _responseEditorFocusedContextKey!: IContextKey; constructor( terminalElement: HTMLElement, private readonly _instance: ITerminalInstance, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IConfigurationService private readonly _configurationService: IConfigurationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, - @ILanguageService private readonly _languageService: ILanguageService, - @IModelService private readonly _modelService: IModelService ) { super(); this._focusedContextKey = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); this._visibleContextKey = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); - this._responseEditorFocusedContextKey = TerminalContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); this._container = document.createElement('div'); this._container.classList.add('terminal-inline-chat'); @@ -90,105 +85,13 @@ export class TerminalChatWidget extends Disposable { this._focusTracker = this._register(trackFocus(this._container)); } - - private _getAriaLabel(): string { - const verbose = this._configurationService.getValue(AccessibilityVerbositySettingId.Chat); - if (verbose) { - // TODO: Add verbose description - } - return localize('terminalChatInput', "Terminal Chat Input"); - } - async renderTerminalCommand(command: string, requestId: number, shellType?: string): Promise { this._chatAccessibilityService.acceptResponse(command, requestId); this.showTerminalCommandWidget(); - let model: ITextModel | null | void = null; - if (!this._terminalCommandWidget) { - this._terminalCommandWidgetContainer = document.createElement('div'); - this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); - this._container.prepend(this._terminalCommandWidgetContainer); - const widget = this._terminalCommandWidget = this._register(this._instantiationService.createInstance(CodeEditorWidget, this._terminalCommandWidgetContainer, { - readOnly: false, - ariaLabel: this._getAriaLabel(), - fontSize: 13, - lineHeight: 20, - padding: { top: 8, bottom: 8 }, - overviewRulerLanes: 0, - glyphMargin: false, - lineNumbers: 'off', - folding: false, - hideCursorInOverviewRuler: true, - selectOnLineNumbers: false, - selectionHighlight: false, - scrollbar: { - useShadows: false, - vertical: 'hidden', - horizontal: 'hidden', - alwaysConsumeMouseWheel: false - }, - lineDecorationsWidth: 0, - overviewRulerBorder: false, - scrollBeyondLastLine: false, - renderLineHighlight: 'none', - fixedOverflowWidgets: true, - dragAndDrop: false, - revealHorizontalRightPadding: 5, - minimap: { enabled: false }, - guides: { indentation: false }, - rulers: [], - renderWhitespace: 'none', - dropIntoEditor: { enabled: true }, - quickSuggestions: false, - suggest: { - showIcons: false, - showSnippets: false, - showWords: true, - showStatusBar: false, - }, - wordWrap: 'on' - }, { isSimpleWidget: true })); - this._register(this._terminalCommandWidget.onDidFocusEditorText(() => this._responseEditorFocusedContextKey.set(true))); - this._register(this._terminalCommandWidget.onDidBlurEditorText(() => this._responseEditorFocusedContextKey.set(false))); - this._register(Event.any(this._terminalCommandWidget.onDidChangeModelContent, this._terminalCommandWidget.onDidChangeModelDecorations)(() => { - const height = widget.getContentHeight(); - widget.layout(new Dimension(640, height)); - })); - model = await this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: command })).then((model) => { - if (!model) { - return; - } - widget.layout(new Dimension(640, 0)); - widget.setModel(model); - const height = widget.getContentHeight(); - widget.layout(new Dimension(640, height)); - }); - } else { - this._terminalCommandWidget.setValue(command); - } - if (!model) { - model = this._terminalCommandWidget.getModel(); - } - if (model) { - const languageId = this._getLanguageFromShell(shellType); - model.setLanguage(languageId); - } - } - - private _getLanguageFromShell(shell?: string): string { - switch (shell) { - case 'fish': - return this._languageService.isRegisteredLanguageId('fish') ? 'fish' : 'shellscript'; - case 'zsh': - return this._languageService.isRegisteredLanguageId('zsh') ? 'zsh' : 'shellscript'; - case 'bash': - return this._languageService.isRegisteredLanguageId('bash') ? 'bash' : 'shellscript'; - case 'sh': - return 'shellscript'; - case 'pwsh': - return 'powershell'; - default: - return 'plaintext'; + if (!this._responseEditor) { + this._responseEditor = this._instantiationService.createInstance(TerminalChatResponseEditor, command, shellType, this._container, this._instance); } + this._responseEditor.setValue(command); } renderMessage(message: string, accessibilityRequestId: number, requestId: string): void { @@ -197,13 +100,6 @@ export class TerminalChatWidget extends Disposable { this._chatAccessibilityService.acceptResponse(message, accessibilityRequestId); } - private async _getTextModel(resource: URI): Promise { - const existing = this._modelService.getModel(resource); - if (existing && !existing.isDisposed()) { - return existing; - } - return this._modelService.createModel(resource.fragment, null, resource, false); - } reveal(): void { this._inlineChatWidget.layout(new Dimension(640, 150)); this._container.classList.remove('hide'); @@ -215,7 +111,8 @@ export class TerminalChatWidget extends Disposable { this.hideTerminalCommandWidget(); this._container.classList.add('hide'); this._inlineChatWidget.value = ''; - this._terminalCommandWidget?.setValue(''); + this._responseEditor?.dispose(); + this._responseEditor = undefined; this._inlineChatWidget.updateChatMessage(undefined); this._inlineChatWidget.updateFollowUps(undefined); this._inlineChatWidget.updateProgress(false); @@ -245,7 +142,7 @@ export class TerminalChatWidget extends Disposable { } acceptCommand(shouldExecute: boolean): void { // Trim command to remove any whitespace, otherwise this may execute the command - const value = this._terminalCommandWidget?.getValue().trim(); + const value = this._responseEditor?.getValue().trim(); if (!value) { return; } @@ -265,3 +162,140 @@ export class TerminalChatWidget extends Disposable { this._terminalCommandWidgetContainer?.classList.remove('hide'); } } + +class TerminalChatResponseEditor extends Disposable { + private readonly _editorContainer: HTMLElement; + private readonly _editor: CodeEditorWidget; + + private readonly _responseEditorFocusedContextKey: IContextKey; + + readonly model: Promise; + + constructor( + initialCommandResponse: string, + shellType: string | undefined, + private readonly _container: HTMLElement, + private readonly _instance: ITerminalInstance, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ILanguageService private readonly _languageService: ILanguageService, + @IModelService private readonly _modelService: IModelService, + ) { + super(); + + this._responseEditorFocusedContextKey = TerminalContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); + + this._editorContainer = document.createElement('div'); + this._editorContainer.classList.add('terminal-inline-chat-response'); + this._container.prepend(this._editorContainer); + this._register(toDisposable(() => this._editorContainer.remove())); + const editor = this._register(this._instantiationService.createInstance(CodeEditorWidget, this._editorContainer, { + readOnly: false, + ariaLabel: this._getAriaLabel(), + fontSize: 13, + lineHeight: 20, + padding: { top: 8, bottom: 8 }, + overviewRulerLanes: 0, + glyphMargin: false, + lineNumbers: 'off', + folding: false, + hideCursorInOverviewRuler: true, + selectOnLineNumbers: false, + selectionHighlight: false, + scrollbar: { + useShadows: false, + vertical: 'hidden', + horizontal: 'hidden', + alwaysConsumeMouseWheel: false + }, + lineDecorationsWidth: 0, + overviewRulerBorder: false, + scrollBeyondLastLine: false, + renderLineHighlight: 'none', + fixedOverflowWidgets: true, + dragAndDrop: false, + revealHorizontalRightPadding: 5, + minimap: { enabled: false }, + guides: { indentation: false }, + rulers: [], + renderWhitespace: 'none', + dropIntoEditor: { enabled: true }, + quickSuggestions: false, + suggest: { + showIcons: false, + showSnippets: false, + showWords: true, + showStatusBar: false, + }, + wordWrap: 'on' + }, { isSimpleWidget: true })); + this._editor = editor; + this._register(editor.onDidFocusEditorText(() => this._responseEditorFocusedContextKey.set(true))); + this._register(editor.onDidBlurEditorText(() => this._responseEditorFocusedContextKey.set(false))); + this._register(Event.any(editor.onDidChangeModelContent, editor.onDidChangeModelDecorations)(() => { + const height = editor.getContentHeight(); + editor.layout(new Dimension(640, height)); + })); + + this.model = this._getTextModel(URI.from({ + path: `terminal-inline-chat-${this._instance.instanceId}`, + scheme: 'terminal-inline-chat', + fragment: initialCommandResponse + })); + this.model.then(model => { + if (model) { + // Initial layout + editor.layout(new Dimension(640, 0)); + editor.setModel(model); + const height = editor.getContentHeight(); + editor.layout(new Dimension(640, height)); + + // Initialize language + const languageId = this._getLanguageFromShell(shellType); + model.setLanguage(languageId); + } + }); + } + + private _getAriaLabel(): string { + const verbose = this._configurationService.getValue(AccessibilityVerbositySettingId.Chat); + if (verbose) { + // TODO: Add verbose description + } + return localize('terminalChatInput', "Terminal Chat Input"); + } + + private async _getTextModel(resource: URI): Promise { + const existing = this._modelService.getModel(resource); + if (existing && !existing.isDisposed()) { + return existing; + } + return this._modelService.createModel(resource.fragment, null, resource, false); + } + + private _getLanguageFromShell(shell?: string): string { + switch (shell) { + case 'fish': + return this._languageService.isRegisteredLanguageId('fish') ? 'fish' : 'shellscript'; + case 'zsh': + return this._languageService.isRegisteredLanguageId('zsh') ? 'zsh' : 'shellscript'; + case 'bash': + return this._languageService.isRegisteredLanguageId('bash') ? 'bash' : 'shellscript'; + case 'sh': + return 'shellscript'; + case 'pwsh': + return 'powershell'; + default: + return 'plaintext'; + } + } + + setValue(value: string) { + this._editor.setValue(value); + } + + getValue(): string { + return this._editor.getValue(); + } +} From 98e01f9016d920127d70b468731b75e5ae65dfb4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 14:07:54 -0600 Subject: [PATCH 1324/1897] rm unused --- .../terminalContrib/chat/browser/terminalChatWidget.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 014f746c786..8448d7e5c61 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -121,10 +121,6 @@ export class TerminalChatWidget extends Disposable { this._visibleContextKey.set(false); this._instance.focus(); } - cancel(): void { - // TODO: Impl - this._inlineChatWidget.value = ''; - } focus(): void { this._inlineChatWidget.focus(); } From 204e5ba8295196b5305ae15747ddfbb7159405e1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:08:26 -0800 Subject: [PATCH 1325/1897] Register chat widget against contr --- .../chat/browser/terminalChatController.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 8f9c3defee0..e609ce9067f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -116,17 +116,17 @@ export class TerminalChatController extends Disposable implements ITerminalContr } this._chatWidget = new Lazy(() => { - const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); - chatWidget.focusTracker.onDidFocus(() => { + const chatWidget = this._register(this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance)); + this._register(chatWidget.focusTracker.onDidFocus(() => { TerminalChatController.activeChatWidget = this; if (!isDetachedTerminalInstance(this._instance)) { this._terminalService.setActiveInstance(this._instance); } - }); - chatWidget.focusTracker.onDidBlur(() => { + })); + this._register(chatWidget.focusTracker.onDidBlur(() => { TerminalChatController.activeChatWidget = undefined; this._instance.resetScrollbarVisibility(); - }); + })); if (!this._instance.domElement) { throw new Error('FindWidget expected terminal DOM to be initialized'); } From 2453accbe856fa1a9266459fda3d84572b1fdd89 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:09:49 -0800 Subject: [PATCH 1326/1897] Pull placeholder from model --- .../terminalContrib/chat/browser/terminalChatController.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index e609ce9067f..4c0ef9145d8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -155,9 +155,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } private _getPlaceholderText(): string { - return this._forcedPlaceholder ?? ''; - // TODO: Pass through session placeholder - // return this._forcedPlaceholder ?? this._session?.session.placeholder ?? ''; + return this._forcedPlaceholder ?? this._model?.inputPlaceholder ?? ''; } setPlaceholder(text: string): void { From 342570da6a7973b7030b00ae45ecb524944bb64a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:14:59 -0800 Subject: [PATCH 1327/1897] Bring back initial placeholder and info on hide --- .../terminalContrib/chat/browser/terminalChatWidget.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 8448d7e5c61..a4bf3b55990 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -78,13 +78,17 @@ export class TerminalChatWidget extends Disposable { feedbackMenuId: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK } ); - this._inlineChatWidget.placeholder = localize('default.placeholder', "Ask how to do something in the terminal"); - this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated commands may be incorrect")); + this._reset(); this._container.appendChild(this._inlineChatWidget.domNode); this._focusTracker = this._register(trackFocus(this._container)); } + private _reset() { + this._inlineChatWidget.placeholder = localize('default.placeholder', "Ask how to do something in the terminal"); + this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated commands may be incorrect")); + } + async renderTerminalCommand(command: string, requestId: number, shellType?: string): Promise { this._chatAccessibilityService.acceptResponse(command, requestId); this.showTerminalCommandWidget(); @@ -110,7 +114,7 @@ export class TerminalChatWidget extends Disposable { hide(): void { this.hideTerminalCommandWidget(); this._container.classList.add('hide'); - this._inlineChatWidget.value = ''; + this._reset(); this._responseEditor?.dispose(); this._responseEditor = undefined; this._inlineChatWidget.updateChatMessage(undefined); From 4de80e1c389db99170b22f785587c7b93bcb5f3f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:19:11 -0800 Subject: [PATCH 1328/1897] Handle hide/show in editor class --- .../chat/browser/terminalChatWidget.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index a4bf3b55990..499d7e375da 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Dimension, IFocusTracker, trackFocus } from 'vs/base/browser/dom'; +import { Dimension, IFocusTracker, hide, show, trackFocus } from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -32,7 +32,6 @@ export class TerminalChatWidget extends Disposable { private readonly _inlineChatWidget: InlineChatWidget; public get inlineChatWidget(): InlineChatWidget { return this._inlineChatWidget; } - private _terminalCommandWidgetContainer: HTMLElement | undefined; private _responseEditor: TerminalChatResponseEditor | undefined; private readonly _focusTracker: IFocusTracker; @@ -91,7 +90,7 @@ export class TerminalChatWidget extends Disposable { async renderTerminalCommand(command: string, requestId: number, shellType?: string): Promise { this._chatAccessibilityService.acceptResponse(command, requestId); - this.showTerminalCommandWidget(); + this._responseEditor?.show(); if (!this._responseEditor) { this._responseEditor = this._instantiationService.createInstance(TerminalChatResponseEditor, command, shellType, this._container, this._instance); } @@ -99,7 +98,7 @@ export class TerminalChatWidget extends Disposable { } renderMessage(message: string, accessibilityRequestId: number, requestId: string): void { - this.hideTerminalCommandWidget(); + this._responseEditor?.hide(); this._inlineChatWidget.updateChatMessage({ message: new MarkdownString(message), requestId, providerId: 'terminal' }); this._chatAccessibilityService.acceptResponse(message, accessibilityRequestId); } @@ -112,7 +111,6 @@ export class TerminalChatWidget extends Disposable { this._inlineChatWidget.focus(); } hide(): void { - this.hideTerminalCommandWidget(); this._container.classList.add('hide'); this._reset(); this._responseEditor?.dispose(); @@ -137,7 +135,7 @@ export class TerminalChatWidget extends Disposable { setValue(value?: string) { this._inlineChatWidget.value = value ?? ''; if (!value) { - this.hideTerminalCommandWidget(); + this._responseEditor?.hide(); } } acceptCommand(shouldExecute: boolean): void { @@ -155,12 +153,6 @@ export class TerminalChatWidget extends Disposable { public get focusTracker(): IFocusTracker { return this._focusTracker; } - hideTerminalCommandWidget(): void { - this._terminalCommandWidgetContainer?.classList.add('hide'); - } - showTerminalCommandWidget(): void { - this._terminalCommandWidgetContainer?.classList.remove('hide'); - } } class TerminalChatResponseEditor extends Disposable { @@ -298,4 +290,12 @@ class TerminalChatResponseEditor extends Disposable { getValue(): string { return this._editor.getValue(); } + + hide() { + hide(this._editorContainer); + } + + show() { + show(this._editorContainer); + } } From 556c0e67c9df1257fefbf9fada21a519ee6c4897 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 14:25:55 -0600 Subject: [PATCH 1329/1897] await initialization --- .../terminalContrib/chat/browser/terminalChatController.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 4c0ef9145d8..c96dd7326eb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -217,8 +217,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr // TODO: ? variables: { variables: [] }, }; - // TODO: fix requester usrname, responder username - this._model?.initialize({ id: this._requestId, requesterUsername: 'userGesture', responderUsername: 'terminal' }, undefined); + await this._model?.waitForInitialization(); const request: IParsedChatRequest = { text: this._lastInput, parts: [] From 7162515cd775bae7eec4af2b04f3ba6d6574bb08 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 14:40:35 -0600 Subject: [PATCH 1330/1897] open in chat view --- .../terminalContrib/chat/browser/terminalChatActions.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 3ad13dbb898..1a813210025 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -144,15 +144,20 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.chatRequestActive.negate(), TerminalContextKeys.chatAgentRegistered, - CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages) ), icon: Codicon.commentDiscussion, - menu: { + menu: [{ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 1, when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages), TerminalContextKeys.chatRequestActive.negate()), }, + { + id: MENU_TERMINAL_CHAT_WIDGET, + group: 'main', + order: 1, + when: ContextKeyExpr.and(CTX_INLINE_CHAT_EMPTY.negate(), CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), TerminalContextKeys.chatRequestActive.negate()), + }], run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { return; From 2408cc098b7feb83e9b48054e3e59b90dfb444e2 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 14:48:23 -0600 Subject: [PATCH 1331/1897] if no model, focus input like quick chat does --- .../terminalContrib/chat/browser/terminalChatController.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index c96dd7326eb..9c274d92b9a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -316,6 +316,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr } } widget.focusLastMessage(); + } else if (!this._model) { + widget?.focusInput(); } } From a3602c3db9bb1c8fc41a050c727fe8a5090dfbc6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:53:30 -0800 Subject: [PATCH 1332/1897] Remove margin between chat and response --- .../chat/browser/media/terminalChatWidget.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css index 1feb4831c23..25d492a063c 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css @@ -11,6 +11,10 @@ height: auto !important; } +.terminal-inline-chat .inline-chat { + margin-top: 0 !important; +} + .terminal-inline-chat .terminal-inline-chat-response { border: 1px solid var(--vscode-input-border, transparent); /* TODO: Make themeable */ @@ -28,6 +32,5 @@ } .terminal-inline-chat .terminal-inline-chat-response { - margin: 0 0 8px 0; padding-left: 8px; } From 233fd797c0878a551994e55f1e87c23f5a200969 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 15 Feb 2024 14:05:54 -0700 Subject: [PATCH 1333/1897] Skip flaky extension smoke tests (#205309) --- test/smoke/src/areas/extensions/extensions.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/smoke/src/areas/extensions/extensions.test.ts b/test/smoke/src/areas/extensions/extensions.test.ts index c78cbe87089..bc65a8631c4 100644 --- a/test/smoke/src/areas/extensions/extensions.test.ts +++ b/test/smoke/src/areas/extensions/extensions.test.ts @@ -7,7 +7,7 @@ import { Application, Logger } from '../../../../automation'; import { installAllHandlers } from '../../utils'; export function setup(logger: Logger) { - describe('Extensions', () => { + describe.skip('Extensions', () => { // Shared before/after handling installAllHandlers(logger); From 3c0c5106b81dc4f2c169415e816b361d6d13010a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 15:08:56 -0600 Subject: [PATCH 1334/1897] add chatResponseType context key for terminal --- .../terminal/common/terminalContextKey.ts | 9 +++++++++ .../chat/browser/terminalChatActions.ts | 16 ++++++++-------- .../chat/browser/terminalChatController.ts | 11 +++++------ 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 8ea1469a4b1..dcca7e64e68 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -45,6 +45,12 @@ export const enum TerminalContextKeyStrings { ChatInputHasText = 'terminalChatInputHasText', ChatAgentRegistered = 'terminalChatAgentRegistered', ChatResponseEditorFocused = 'terminalChatResponseEditorFocused', + ChatResponseType = 'terminalChatResponseType', +} + +export const enum TerminalChatResponseTypes { + Message = 'message', + TerminalCommand = 'terminalCommand' } export namespace TerminalContextKeys { @@ -183,4 +189,7 @@ export namespace TerminalContextKeys { /** Whether the chat response editor is focused */ export const chatResponseEditorFocused = new RawContextKey(TerminalContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseEditorFocusedContextKey', "Whether the chat response editor is focused.")); + + /** The type of chat response, if any */ + export const chatResponseType = new RawContextKey(TerminalContextKeyStrings.ChatResponseType, undefined, localize('chatResponseTypeContextKey', "The type of chat response, if any")); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 1a813210025..aa8f4e045d1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -9,10 +9,10 @@ import { localize2 } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { TerminalChatResponseTypes, TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; @@ -81,7 +81,7 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.chatRequestActive.negate(), TerminalContextKeys.chatAgentRegistered, - CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty) + TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) ), icon: Codicon.check, keybinding: { @@ -93,7 +93,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 0, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalContextKeys.chatRequestActive.negate()), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -113,7 +113,7 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.chatRequestActive.negate(), TerminalContextKeys.chatAgentRegistered, - CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty) + TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) ), icon: Codicon.check, keybinding: { @@ -125,7 +125,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 1, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalContextKeys.chatRequestActive.negate()), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -150,13 +150,13 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 1, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages), TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.Message), TerminalContextKeys.chatRequestActive.negate()), }, { id: MENU_TERMINAL_CHAT_WIDGET, group: 'main', order: 1, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_EMPTY.negate(), CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(CTX_INLINE_CHAT_EMPTY.negate(), TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalContextKeys.chatRequestActive.negate()), }], run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 9c274d92b9a..fd5f072846b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -19,11 +19,10 @@ import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/cont import { IChatAgentService, IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatService, IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; -import { InlineChatResponseTypes, CTX_INLINE_CHAT_RESPONSE_TYPES } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { TerminalChatResponseTypes, TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; @@ -56,7 +55,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _requestActiveContextKey!: IContextKey; private readonly _terminalAgentRegisteredContextKey!: IContextKey; - private readonly _lastResponseTypeContextKey!: IContextKey; + private readonly _responseTypeContextKey!: IContextKey; private _requestId: number = 0; private _messages = this._store.add(new Emitter()); @@ -97,7 +96,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } this._requestActiveContextKey = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); this._terminalAgentRegisteredContextKey = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); - this._lastResponseTypeContextKey = CTX_INLINE_CHAT_RESPONSE_TYPES.bindTo(this._contextKeyService); + this._responseTypeContextKey = TerminalContextKeys.chatResponseType.bindTo(this._contextKeyService); if (!this._chatAgentService.hasAgent(this._terminalAgentId)) { this._register(this._chatAgentService.onDidChangeAgents(() => { @@ -257,10 +256,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr } if (codeBlock) { this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._requestId, shellType); - this._lastResponseTypeContextKey.set(InlineChatResponseTypes.Empty); + this._responseTypeContextKey.set(TerminalChatResponseTypes.TerminalCommand); } else { this._chatWidget?.rawValue?.renderMessage(responseContent, this._requestId, requestId); - this._lastResponseTypeContextKey.set(InlineChatResponseTypes.OnlyMessages); + this._responseTypeContextKey.set(TerminalChatResponseTypes.Message); } this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); this._messages.fire(Message.ACCEPT_INPUT); From de3d5a4c4b128f59afa06a25253c1d243dd720e4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 15:30:14 -0600 Subject: [PATCH 1335/1897] rm terminal stuff from inline chat actions --- .../contrib/inlineChat/electron-sandbox/inlineChatActions.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts index 57c29d41e9d..99862b01d7b 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts @@ -20,17 +20,16 @@ import { HasSpeechProvider, ISpeechService } from 'vs/workbench/contrib/speech/c import { localize2 } from 'vs/nls'; import { Action2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; export class HoldToSpeak extends AbstractInlineChatAction { constructor() { super({ id: 'inlineChat.holdForSpeech', - precondition: ContextKeyExpr.and(HasSpeechProvider, ContextKeyExpr.or(CTX_INLINE_CHAT_VISIBLE, TerminalContextKeys.chatVisible)), + precondition: ContextKeyExpr.and(HasSpeechProvider, CTX_INLINE_CHAT_VISIBLE), title: localize2('holdForSpeech', "Hold for Speech"), keybinding: { - when: ContextKeyExpr.or(EditorContextKeys.textInputFocus, TerminalContextKeys.chatFocused), + when: EditorContextKeys.textInputFocus, weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.KeyI, }, From a1a29cfd29bc6d59aac90b855d6a724a5d8294da Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 15 Feb 2024 14:02:00 -0800 Subject: [PATCH 1336/1897] Fix list view npe when inserting whitespaces at top (#205311) --- src/vs/base/browser/ui/list/listView.ts | 6 +++--- .../notebook/browser/view/notebookCellList.ts | 2 +- .../notebook/browser/view/notebookCellListView.ts | 7 ++++++- .../notebook/browser/viewParts/notebookViewZones.ts | 3 ++- .../notebook/test/browser/notebookViewZones.test.ts | 13 +++++++++++++ 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 546f0bdb1f4..fdd9df0e2ae 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -295,8 +295,8 @@ export class ListView implements IListView { protected rangeMap: IRangeMap; private cache: RowCache; private renderers = new Map>(); - private lastRenderTop: number; - private lastRenderHeight: number; + protected lastRenderTop: number; + protected lastRenderHeight: number; private renderWidth = 0; private rowsContainer: HTMLElement; private scrollable: Scrollable; @@ -1400,7 +1400,7 @@ export class ListView implements IListView { return undefined; } - private getRenderRange(renderTop: number, renderHeight: number): IRange { + protected getRenderRange(renderTop: number, renderHeight: number): IRange { return { start: this.rangeMap.indexAt(renderTop), end: this.rangeMap.indexAfter(renderTop + renderHeight - 1) diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 94cbebb16b5..bd47eb879e9 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -594,7 +594,7 @@ export class NotebookCellList extends WorkbenchList implements ID if (modelIndex >= this.hiddenRangesPrefixSum.getTotalSum()) { // it's already after the last hidden range - return this.hiddenRangesPrefixSum.getTotalSum(); + return Math.min(this.length, this.hiddenRangesPrefixSum.getTotalSum()); } return this.hiddenRangesPrefixSum.getIndexOf(modelIndex).index; diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts index bafaac004e0..e7f7d018a80 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts @@ -276,9 +276,14 @@ export class NotebookCellListView extends ListView { insertWhitespace(afterPosition: number, size: number): string { const scrollTop = this.scrollTop; const id = `${++this._lastWhitespaceId}`; + const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); + const elementPosition = this.elementTop(afterPosition); + const aboveScrollTop = scrollTop > elementPosition; this.notebookRangeMap.insertWhitespace(id, afterPosition, size); - this._rerender(scrollTop, this.renderHeight, false); + const newScrolltop = aboveScrollTop ? scrollTop + size : scrollTop; + this.render(previousRenderRange, newScrolltop, this.lastRenderHeight, undefined, undefined, false); + this._rerender(newScrolltop, this.renderHeight, false); this.eventuallyUpdateScrollDimensions(); return id; diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts index bc7384c7654..7332529b231 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts @@ -120,7 +120,8 @@ export class NotebookViewZones extends Disposable { } private _addZone(zone: INotebookViewZone): string { - const whitespaceId = this.listView.insertWhitespace(zone.afterModelPosition, zone.heightInPx); + const viewPosition = this.coordinator.convertModelIndexToViewIndex(zone.afterModelPosition); + const whitespaceId = this.listView.insertWhitespace(viewPosition, zone.heightInPx); const isInHiddenArea = this._isInHiddenRanges(zone); const myZone: IZoneWidget = { whitespaceId: whitespaceId, diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts index a858c9d7866..2f87376fe5c 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts @@ -346,6 +346,19 @@ suite('NotebookRangeMap with whitesspaces', () => { instantiationService.stub(IConfigurationService, config); }); + test('simple', () => { + const rangeMap = new NotebookCellsLayout(0); + rangeMap.splice(0, 0, [{ size: 479 }, { size: 163 }, { size: 182 }, { size: 106 }, { size: 106 }, { size: 106 }, { size: 87 }]); + + const start = rangeMap.indexAt(650); + const end = rangeMap.indexAfter(650 + 890 - 1); + assert.strictEqual(start, 2); + assert.strictEqual(end, 7); + + rangeMap.insertWhitespace('1', 0, 18); + assert.strictEqual(rangeMap.indexAt(650), 1); + }); + test('Whitespace CRUD', async function () { const twenty = { size: 20 }; From a9f226d8eba2370051e88cfedf8e47f716e261f8 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 16:02:41 -0600 Subject: [PATCH 1337/1897] fix requestId, sessionId, wip feedback --- .../chat/browser/terminalChatActions.ts | 28 ++++++++---- .../chat/browser/terminalChatController.ts | 43 +++++++++++++------ 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index aa8f4e045d1..187d16f6d51 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -226,7 +226,7 @@ registerActiveXtermAction({ title: localize2('feedbackHelpful', 'Helpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive, + TerminalContextKeys.chatResponseType.notEqualsTo(undefined) ), // TODO: toggled: CTX_INLINE_CHAT_LAST_FEEDBACK.isEqualTo('helpful'), icon: Codicon.thumbsup, @@ -234,11 +234,14 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', order: 1, - // TODO: Fill in ctx - // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), + when: TerminalContextKeys.chatResponseType.notEqualsTo(undefined), }, run: (_xterm, _accessor, activeInstance) => { - // TODO: Impl + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.acceptFeedback(true); } }); @@ -247,7 +250,7 @@ registerActiveXtermAction({ title: localize2('feedbackUnhelpful', 'Unhelpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive, + TerminalContextKeys.chatResponseType.notEqualsTo(undefined), ), // TODO: toggled: CTX_INLINE_CHAT_LAST_FEEDBACK.isEqualTo('unhelpful'), icon: Codicon.thumbsdown, @@ -255,11 +258,14 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', order: 2, - // TODO: Fill in ctx - // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), + when: TerminalContextKeys.chatResponseType.notEqualsTo(undefined), }, run: (_xterm, _accessor, activeInstance) => { - // TODO: Impl + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.acceptFeedback(false); } }); @@ -284,7 +290,11 @@ registerActiveXtermAction({ order: 3 }], run: (_xterm, _accessor, activeInstance) => { - // TODO: Impl + // if (isDetachedTerminalInstance(activeInstance)) { + // return; + // } + // const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + // contr?.acceptFeedback(true); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index fd5f072846b..030e2bb8fdf 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -9,7 +9,6 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; import { marked } from 'vs/base/common/marked/marked'; -import { generateUuid } from 'vs/base/common/uuid'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -18,7 +17,7 @@ import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatAgentService, IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatService, IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatService, IChatProgress, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -134,6 +133,25 @@ export class TerminalChatController extends Disposable implements ITerminalContr }); } + acceptFeedback(helpful: boolean): void { + const providerId = this._chatService.getProviderInfos()?.[0]?.id; + if (!providerId || !this._currentRequest || !this._model) { + return; + } + this._chatService.notifyUserAction({ + providerId, + sessionId: this._model?.sessionId, + requestId: this._currentRequest.id, + agentId: this._terminalAgentId, + //TODO: fill in error details if any etc. + result: {}, + action: { + kind: 'vote', + direction: helpful ? InteractiveSessionVoteDirection.Up : InteractiveSessionVoteDirection.Down + }, + }); + } + cancel(): void { if (this._currentRequest) { this._model?.cancelRequest(this._currentRequest); @@ -175,6 +193,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._model = undefined; this._chatWidget?.rawValue?.hide(); this._chatWidget?.rawValue?.setValue(undefined); + this._responseTypeContextKey.reset(); } private updateModel(): void { @@ -207,15 +226,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._model?.acceptResponseProgress(this._currentRequest, progress); } }; - const requestId = generateUuid(); - const requestProps: IChatAgentRequest = { - sessionId: generateUuid(), - requestId, - agentId: this._terminalAgentId, - message: this._lastInput, - // TODO: ? - variables: { variables: [] }, - }; + await this._model?.waitForInitialization(); const request: IParsedChatRequest = { text: this._lastInput, @@ -225,6 +236,14 @@ export class TerminalChatController extends Disposable implements ITerminalContr variables: [] }; this._currentRequest = this._model?.addRequest(request, requestVarData); + const requestProps: IChatAgentRequest = { + sessionId: this._model!.sessionId, + requestId: this._currentRequest!.id, + agentId: this._terminalAgentId, + message: this._lastInput, + // TODO: ? + variables: { variables: [] }, + }; try { const task = this._chatAgentService.invokeAgent(this._terminalAgentId, requestProps, progressCallback, [], cancellationToken); this._chatWidget?.rawValue?.inlineChatWidget.updateChatMessage(undefined); @@ -258,7 +277,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._requestId, shellType); this._responseTypeContextKey.set(TerminalChatResponseTypes.TerminalCommand); } else { - this._chatWidget?.rawValue?.renderMessage(responseContent, this._requestId, requestId); + this._chatWidget?.rawValue?.renderMessage(responseContent, this._requestId, this._currentRequest!.id); this._responseTypeContextKey.set(TerminalChatResponseTypes.Message); } this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); From 4b295eda6f9de1762e44a818085d1736976bef8f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 16:08:57 -0600 Subject: [PATCH 1338/1897] fix feedback action --- .../chat/browser/terminalChatController.ts | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 030e2bb8fdf..baa3943274c 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -138,18 +138,22 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (!providerId || !this._currentRequest || !this._model) { return; } - this._chatService.notifyUserAction({ - providerId, - sessionId: this._model?.sessionId, - requestId: this._currentRequest.id, - agentId: this._terminalAgentId, - //TODO: fill in error details if any etc. - result: {}, - action: { - kind: 'vote', - direction: helpful ? InteractiveSessionVoteDirection.Up : InteractiveSessionVoteDirection.Down - }, - }); + // TODO:extract into helper method + for (const request of this._model.getRequests()) { + if (request.response?.response.value || request.response?.result) { + this._chatService.notifyUserAction({ + providerId, + sessionId: request.session.sessionId, + requestId: request.id, + agentId: request.response?.agent?.id, + result: request.response?.result, + action: { + kind: 'vote', + direction: helpful ? InteractiveSessionVoteDirection.Up : InteractiveSessionVoteDirection.Down + }, + }); + } + } } cancel(): void { From ed4a3ce3ec379421c165b7fb1af78c5c241dd95d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 16:12:04 -0600 Subject: [PATCH 1339/1897] fix feedback action, add styling --- .../terminalContrib/chat/browser/terminalChatController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index baa3943274c..f8e97671171 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -154,6 +154,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr }); } } + this._chatWidget?.rawValue?.inlineChatWidget.updateStatus('Thank you for your feedback!', { resetAfter: 1250 }); } cancel(): void { From 93307188f0428375840c99bb140c4dec1f4e4afd Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 15 Feb 2024 14:47:22 -0800 Subject: [PATCH 1340/1897] Fix #205291. (#205313) --- .../notebook/browser/notebookOptions.ts | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts index 36ed3544b98..c05004146c2 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts @@ -284,28 +284,33 @@ export class NotebookOptions extends Disposable { return; } - const options = this.codeEditorService.resolveDecorationOptions(e, true); - if (options.afterContentClassName || options.beforeContentClassName) { - const cssRules = this.codeEditorService.resolveDecorationCSSRules(e); - if (cssRules !== null) { - for (let i = 0; i < cssRules.length; i++) { - // The following ways to index into the list are equivalent - if ( - ((cssRules[i] as CSSStyleRule).selectorText.endsWith('::after') || (cssRules[i] as CSSStyleRule).selectorText.endsWith('::after')) - && (cssRules[i] as CSSStyleRule).cssText.indexOf('top:') > -1 - ) { - // there is a `::before` or `::after` text decoration whose position is above or below current line - // we at least make sure that the editor top padding is at least one line - const editorOptions = this.configurationService.getValue('editor'); - updateEditorTopPadding(BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(this.targetWindow).value).lineHeight + 2); - decorationTriggeredAdjustment = true; - break; + try { + const options = this.codeEditorService.resolveDecorationOptions(e, true); + if (options.afterContentClassName || options.beforeContentClassName) { + const cssRules = this.codeEditorService.resolveDecorationCSSRules(e); + if (cssRules !== null) { + for (let i = 0; i < cssRules.length; i++) { + // The following ways to index into the list are equivalent + if ( + ((cssRules[i] as CSSStyleRule).selectorText.endsWith('::after') || (cssRules[i] as CSSStyleRule).selectorText.endsWith('::after')) + && (cssRules[i] as CSSStyleRule).cssText.indexOf('top:') > -1 + ) { + // there is a `::before` or `::after` text decoration whose position is above or below current line + // we at least make sure that the editor top padding is at least one line + const editorOptions = this.configurationService.getValue('editor'); + updateEditorTopPadding(BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(this.targetWindow).value).lineHeight + 2); + decorationTriggeredAdjustment = true; + break; + } } } } + + decorationCheckSet.add(e); + } catch (_ex) { + // do not throw and break notebook } - decorationCheckSet.add(e); }; this._register(this.codeEditorService.onDecorationTypeRegistered(onDidAddDecorationType)); this.codeEditorService.listDecorationTypes().forEach(onDidAddDecorationType); From 03aeb958495cd47a902aa5102bb23703ba9b35df Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 16 Feb 2024 01:06:58 +0000 Subject: [PATCH 1341/1897] Add isSticky to chat agent (#205322) --- src/vs/workbench/api/common/extHostChatAgents2.ts | 11 ++++++++++- .../chat/browser/contrib/chatInputEditorContrib.ts | 8 +++----- src/vs/workbench/contrib/chat/common/chatAgents.ts | 3 ++- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 5 +++++ 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 78adfe193fd..1f40b04c924 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -353,6 +353,7 @@ class ExtHostChatAgent { private _supportIssueReporting: boolean | undefined; private _agentVariableProvider?: { provider: vscode.ChatAgentCompletionItemProvider; triggerCharacters: string[] }; private _welcomeMessageProvider?: vscode.ChatAgentWelcomeMessageProvider | undefined; + private _isSticky: boolean | undefined; constructor( public readonly extension: IExtensionDescription, @@ -476,7 +477,8 @@ class ExtHostChatAgent { helpTextPrefix: (!this._helpTextPrefix || typeof this._helpTextPrefix === 'string') ? this._helpTextPrefix : typeConvert.MarkdownString.from(this._helpTextPrefix), helpTextPostfix: (!this._helpTextPostfix || typeof this._helpTextPostfix === 'string') ? this._helpTextPostfix : typeConvert.MarkdownString.from(this._helpTextPostfix), sampleRequest: this._sampleRequest, - supportIssueReporting: this._supportIssueReporting + supportIssueReporting: this._supportIssueReporting, + isSticky: this._isSticky, }); updateScheduled = false; }); @@ -622,6 +624,13 @@ class ExtHostChatAgent { ? undefined! : this._onDidPerformAction.event , + get isSticky() { + return that._isSticky; + }, + set isSticky(v) { + that._isSticky = v; + updateMetadataSoon(); + }, dispose() { disposed = true; that._commandProvider = undefined; diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 0b1a1793ea2..12c466bfd6f 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -237,8 +237,7 @@ class InputEditorSlashCommandMode extends Disposable { public readonly id = 'InputEditorSlashCommandMode'; constructor( - private readonly widget: IChatWidget, - @IChatAgentService private readonly _chatAgentService: IChatAgentService + private readonly widget: IChatWidget ) { super(); this._register(this.widget.onDidSubmitAgent(e => { @@ -248,10 +247,9 @@ class InputEditorSlashCommandMode extends Disposable { private async repopulateAgentCommand(agent: IChatAgentData, slashCommand: IChatAgentCommand | undefined) { let value: string | undefined; - if (slashCommand && slashCommand.shouldRepopulate) { + if (slashCommand && slashCommand.isSticky) { value = `${chatAgentLeader}${agent.id} ${chatSubcommandLeader}${slashCommand.name} `; - } else if (agent.id !== this._chatAgentService.getDefaultAgent()?.id) { - // Agents always repopulate, and slash commands fall back to the agent if they don't repopulate + } else if (agent.metadata.isSticky) { value = `${chatAgentLeader}${agent.id} `; } diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index dbcabdd422d..a6ef4c6c359 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -54,7 +54,7 @@ export interface IChatAgentCommand { * chat into a persistent mode, where the * slash command is prepended to the chat input. */ - shouldRepopulate?: boolean; + isSticky?: boolean; /** * Placeholder text to render in the chat input @@ -79,6 +79,7 @@ export interface IChatAgentMetadata { sampleRequest?: string; supportIssueReporting?: boolean; followupPlaceholder?: string; + isSticky?: boolean; } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 1762bdafb64..63cad62d20a 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -277,6 +277,11 @@ declare module 'vscode' { */ sampleRequest?: string; + /** + * Whether invoking the agent puts the chat into a persistent mode, where the agent is automatically added to the chat input for the next message. + */ + isSticky?: boolean; + /** * An event that fires whenever feedback for a result is received, e.g. when a user up- or down-votes * a result. From d84c946d27b649d614319c796911871320befd42 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Thu, 15 Feb 2024 17:38:10 -0800 Subject: [PATCH 1342/1897] Support `command:toSide` in markdown also (#205316) --- .../browser/gettingStarted.ts | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 7c23216426b..99d6a260886 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -645,7 +645,12 @@ export class GettingStartedPage extends EditorPane { this.stepDisposables.add(this.webview.onDidClickLink(link => { if (matchesScheme(link, Schemas.https) || matchesScheme(link, Schemas.http) || (matchesScheme(link, Schemas.command))) { - this.openerService.open(link, { allowCommands: true }); + const toSide = link.startsWith('command:toSide:'); + if (toSide) { + link = link.replace('command:toSide:', 'command:'); + this.focusSideEditorGroup(); + } + this.openerService.open(link, { allowCommands: true, openToSide: toSide }); } })); @@ -1164,6 +1169,25 @@ export class GettingStartedPage extends EditorPane { return widget; } + private focusSideEditorGroup() { + const fullSize = this.group ? this.groupsService.getPart(this.group).contentDimension : undefined; + if (!fullSize || fullSize.width <= 700) { return; } + if (this.groupsService.count === 1) { + const sideGroup = this.groupsService.addGroup(this.groupsService.groups[0], GroupDirection.RIGHT); + this.groupsService.activateGroup(sideGroup); + + const gettingStartedSize = Math.floor(fullSize.width / 2); + + const gettingStartedGroup = this.groupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(group => (group.activeEditor instanceof GettingStartedInput)); + this.groupsService.setSize(assertIsDefined(gettingStartedGroup), { width: gettingStartedSize, height: fullSize.height }); + } + + const nonGettingStartedGroup = this.groupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(group => !(group.activeEditor instanceof GettingStartedInput)); + if (nonGettingStartedGroup) { + this.groupsService.activateGroup(nonGettingStartedGroup); + nonGettingStartedGroup.focus(); + } + } private runStepCommand(href: string) { const isCommand = href.startsWith('command:'); @@ -1172,24 +1196,8 @@ export class GettingStartedPage extends EditorPane { this.telemetryService.publicLog2('gettingStarted.ActionExecuted', { command: 'runStepAction', argument: href, walkthroughId: this.currentWalkthrough?.id }); - const fullSize = this.group ? this.groupsService.getPart(this.group).contentDimension : undefined; - - if (toSide && fullSize && fullSize.width > 700) { - if (this.groupsService.count === 1) { - const sideGroup = this.groupsService.addGroup(this.groupsService.groups[0], GroupDirection.RIGHT); - this.groupsService.activateGroup(sideGroup); - - const gettingStartedSize = Math.floor(fullSize.width / 2); - - const gettingStartedGroup = this.groupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(group => (group.activeEditor instanceof GettingStartedInput)); - this.groupsService.setSize(assertIsDefined(gettingStartedGroup), { width: gettingStartedSize, height: fullSize.height }); - } - - const nonGettingStartedGroup = this.groupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(group => !(group.activeEditor instanceof GettingStartedInput)); - if (nonGettingStartedGroup) { - this.groupsService.activateGroup(nonGettingStartedGroup); - nonGettingStartedGroup.focus(); - } + if (toSide) { + this.focusSideEditorGroup(); } if (isCommand) { const commandURI = URI.parse(command); From 877caad9437ec2f26970b48e60e394a7a45b47e3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 16 Feb 2024 09:33:07 +0100 Subject: [PATCH 1343/1897] debt - make history suspend more explicit (#205181) * debt - make history suspend more explicit * added tests * :lipstick: --------- Co-authored-by: andreamah --- .../browser/debugConfigurationManager.test.ts | 4 +- .../quickTextSearch/textSearchQuickAccess.ts | 15 +- .../history/browser/historyService.ts | 22 +-- .../services/history/common/history.ts | 7 +- .../test/browser/historyService.test.ts | 138 ++++++++++++++++++ .../test/browser/workbenchTestServices.ts | 24 +-- .../test/common/workbenchTestServices.ts | 2 +- 7 files changed, 170 insertions(+), 42 deletions(-) diff --git a/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts index a63840db4e6..71b53f1ab25 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts @@ -20,8 +20,8 @@ import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentitySe import { ConfigurationManager } from 'vs/workbench/contrib/debug/browser/debugConfigurationManager'; import { DebugConfigurationProviderTriggerKind, IAdapterManager, IConfig, IDebugAdapterExecutable, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { TestHistoryService, TestQuickInputService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { TestContextService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; +import { TestQuickInputService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestHistoryService, TestContextService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; suite('debugConfigurationManager', () => { const configurationProviderType = 'custom-type'; diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index 15a07cb0675..3691e98ec1d 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -125,12 +125,15 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { // disable and re-enable history service so that we can ignore this history entry - this._historyService.shouldIgnoreActiveEditorChange = true; - await this._editorService.openEditor({ - resource: itemMatch.parent().resource, - options: { preserveFocus: true, revealIfOpened: true, ignoreError: true, selection: itemMatch.range() } - }); - this._historyService.shouldIgnoreActiveEditorChange = false; + const disposable = this._historyService.suspendTracking(); + try { + await this._editorService.openEditor({ + resource: itemMatch.parent().resource, + options: { preserveFocus: true, revealIfOpened: true, ignoreError: true, selection: itemMatch.range() } + }); + } finally { + disposable.dispose(); + } }); } })); diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts index 3c580614b86..108685831ad 100644 --- a/src/vs/workbench/services/history/browser/historyService.ts +++ b/src/vs/workbench/services/history/browser/historyService.ts @@ -12,7 +12,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { GoFilter, GoScope, IHistoryService } from 'vs/workbench/services/history/common/history'; import { FileChangesEvent, IFileService, FileChangeType, FILES_EXCLUDE_CONFIG, FileOperationEvent, FileOperation } from 'vs/platform/files/common/files'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { dispose, Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { dispose, Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Emitter, Event } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -47,9 +47,7 @@ export class HistoryService extends Disposable implements IHistoryService { private readonly editorHelper = this.instantiationService.createInstance(EditorHelper); - // Can be set to temporarily ignore messages from the editor service that indicate a new active editor. - // Used for ignoring some editors for history. - public shouldIgnoreActiveEditorChange: boolean = false; + constructor( @IEditorService private readonly editorService: EditorServiceImpl, @@ -75,6 +73,13 @@ export class HistoryService extends Disposable implements IHistoryService { } } + private trackingSuspended = false; + suspendTracking(): IDisposable { + this.trackingSuspended = true; + + return toDisposable(() => this.trackingSuspended = false); + } + private registerListeners(): void { // Mouse back/forward support @@ -82,14 +87,14 @@ export class HistoryService extends Disposable implements IHistoryService { // Editor changes this._register(this.editorService.onDidActiveEditorChange((e) => { - if (!this.shouldIgnoreActiveEditorChange) { + if (!this.trackingSuspended) { this.onDidActiveEditorChange(); } })); this._register(this.editorService.onDidOpenEditorFail(event => this.remove(event.editor))); this._register(this.editorService.onDidCloseEditor(event => this.onDidCloseEditor(event))); this._register(this.editorService.onDidMostRecentlyActiveEditorsChange(() => { - if (!this.shouldIgnoreActiveEditorChange) { + if (!this.trackingSuspended) { this.handleEditorEventInRecentEditorsStack(); } })); @@ -190,11 +195,10 @@ export class HistoryService extends Disposable implements IHistoryService { // is having a selection concept. if (isEditorPaneWithSelection(activeEditorPane)) { this.activeEditorListeners.add(activeEditorPane.onDidChangeSelection(e => { - if (!this.shouldIgnoreActiveEditorChange) { + if (!this.trackingSuspended) { this.handleActiveEditorSelectionChangeEvent(activeEditorGroup, activeEditorPane, e); } - } - )); + })); } // Context keys diff --git a/src/vs/workbench/services/history/common/history.ts b/src/vs/workbench/services/history/common/history.ts index 86d21f49dad..3d135ec2a70 100644 --- a/src/vs/workbench/services/history/common/history.ts +++ b/src/vs/workbench/services/history/common/history.ts @@ -8,6 +8,7 @@ import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { GroupIdentifier } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { URI } from 'vs/base/common/uri'; +import { IDisposable } from 'vs/base/common/lifecycle'; export const IHistoryService = createDecorator('historyService'); @@ -60,7 +61,6 @@ export const enum GoScope { export interface IHistoryService { readonly _serviceBrand: undefined; - shouldIgnoreActiveEditorChange: boolean; /** * Navigate forwards in editor navigation history. @@ -138,4 +138,9 @@ export interface IHistoryService { * Clear list of recently opened editors. */ clearRecentlyOpened(): void; + + /** + * Temporarily suspend tracking of editor events for the history. + */ + suspendTracking(): IDisposable; } diff --git a/src/vs/workbench/services/history/test/browser/historyService.test.ts b/src/vs/workbench/services/history/test/browser/historyService.test.ts index 124a79c0203..43838aad189 100644 --- a/src/vs/workbench/services/history/test/browser/historyService.test.ts +++ b/src/vs/workbench/services/history/test/browser/historyService.test.ts @@ -807,5 +807,143 @@ suite('HistoryService', function () { return workbenchTeardown(instantiationService); }); + test('suspend should suspend editor changes- skip two editors and continue (single group)', async () => { + const [part, historyService, editorService, , instantiationService] = await createServices(); + + const input1 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID)); + const input2 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID)); + const input3 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID)); + const input4 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID)); + const input5 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar5'), TEST_EDITOR_INPUT_ID)); + + let editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); + await part.activeGroup.openEditor(input1, { pinned: true }); + assert.strictEqual(part.activeGroup.activeEditor, input1); + await editorChangePromise; + + const disposable = historyService.suspendTracking(); + + // wait on two editor changes before disposing + editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange) + .then(() => Event.toPromise(editorService.onDidActiveEditorChange)); + + await part.activeGroup.openEditor(input2, { pinned: true }); + assert.strictEqual(part.activeGroup.activeEditor, input2); + await part.activeGroup.openEditor(input3, { pinned: true }); + assert.strictEqual(part.activeGroup.activeEditor, input3); + + await editorChangePromise; + disposable.dispose(); + + await part.activeGroup.openEditor(input4, { pinned: true }); + assert.strictEqual(part.activeGroup.activeEditor, input4); + await part.activeGroup.openEditor(input5, { pinned: true }); + assert.strictEqual(part.activeGroup.activeEditor, input5); + + // stack should be [input1, input4, input5] + await historyService.goBack(); + assert.strictEqual(part.activeGroup.activeEditor, input4); + await historyService.goBack(); + assert.strictEqual(part.activeGroup.activeEditor, input1); + await historyService.goBack(); + assert.strictEqual(part.activeGroup.activeEditor, input1); + + await historyService.goForward(); + assert.strictEqual(part.activeGroup.activeEditor, input4); + await historyService.goForward(); + assert.strictEqual(part.activeGroup.activeEditor, input5); + + return workbenchTeardown(instantiationService); + }); + + test('suspend should suspend editor changes- skip two editors and continue (multi group)', async () => { + const [part, historyService, editorService, , instantiationService] = await createServices(); + const rootGroup = part.activeGroup; + + const input1 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID)); + const input2 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID)); + const input3 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID)); + const input4 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID)); + const input5 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar5'), TEST_EDITOR_INPUT_ID)); + + const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); + + let editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); + await rootGroup.openEditor(input1, { pinned: true }); + await editorChangePromise; + + const disposable = historyService.suspendTracking(); + editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange) + .then(() => Event.toPromise(editorService.onDidActiveEditorChange)); + await sideGroup.openEditor(input2, { pinned: true }); + await rootGroup.openEditor(input3, { pinned: true }); + await editorChangePromise; + disposable.dispose(); + + await sideGroup.openEditor(input4, { pinned: true }); + await rootGroup.openEditor(input5, { pinned: true }); + + // stack should be [input1, input4, input5] + await historyService.goBack(); + assert.strictEqual(part.activeGroup.activeEditor, input4); + assert.strictEqual(part.activeGroup, sideGroup); + assert.strictEqual(rootGroup.activeEditor, input5); + + await historyService.goBack(); + assert.strictEqual(part.activeGroup.activeEditor, input1); + assert.strictEqual(part.activeGroup, rootGroup); + assert.strictEqual(sideGroup.activeEditor, input4); + + await historyService.goBack(); + assert.strictEqual(part.activeGroup.activeEditor, input1); + + await historyService.goForward(); + assert.strictEqual(part.activeGroup.activeEditor, input4); + await historyService.goForward(); + assert.strictEqual(part.activeGroup.activeEditor, input5); + + return workbenchTeardown(instantiationService); + }); + + test('suspend should suspend editor changes - interleaved skips', async () => { + const [part, historyService, editorService, , instantiationService] = await createServices(); + + const input1 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID)); + const input2 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID)); + const input3 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID)); + const input4 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID)); + const input5 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar5'), TEST_EDITOR_INPUT_ID)); + + let editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); + await part.activeGroup.openEditor(input1, { pinned: true }); + await editorChangePromise; + + let disposable = historyService.suspendTracking(); + editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); + await part.activeGroup.openEditor(input2, { pinned: true }); + await editorChangePromise; + disposable.dispose(); + + await part.activeGroup.openEditor(input3, { pinned: true }); + + disposable = historyService.suspendTracking(); + editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); + await part.activeGroup.openEditor(input4, { pinned: true }); + await editorChangePromise; + disposable.dispose(); + + await part.activeGroup.openEditor(input5, { pinned: true }); + + // stack should be [input1, input3, input5] + await historyService.goBack(); + assert.strictEqual(part.activeGroup.activeEditor, input3); + await historyService.goBack(); + assert.strictEqual(part.activeGroup.activeEditor, input1); + await historyService.goBack(); + assert.strictEqual(part.activeGroup.activeEditor, input1); + + return workbenchTeardown(instantiationService); + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 23355faf76d..6db63c1cc2e 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -98,7 +98,7 @@ import { IInputBox, IInputOptions, IPickOptions, IQuickInputButton, IQuickInputS import { QuickInputService } from 'vs/workbench/services/quickinput/browser/quickInputService'; import { IListService } from 'vs/platform/list/browser/listService'; import { win32, posix } from 'vs/base/common/path'; -import { TestContextService, TestStorageService, TestTextResourcePropertiesService, TestExtensionService, TestProductService, createFileStat, TestLoggerService, TestWorkspaceTrustManagementService, TestWorkspaceTrustRequestService, TestMarkerService } from 'vs/workbench/test/common/workbenchTestServices'; +import { TestContextService, TestStorageService, TestTextResourcePropertiesService, TestExtensionService, TestProductService, createFileStat, TestLoggerService, TestWorkspaceTrustManagementService, TestWorkspaceTrustRequestService, TestMarkerService, TestHistoryService } from 'vs/workbench/test/common/workbenchTestServices'; import { IView, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; @@ -542,28 +542,6 @@ export class TestMenuService implements IMenuService { } } -export class TestHistoryService implements IHistoryService { - - declare readonly _serviceBrand: undefined; - declare shouldIgnoreActiveEditorChange: boolean; - - constructor(private root?: URI) { } - - async reopenLastClosedEditor(): Promise { } - async goForward(): Promise { } - async goBack(): Promise { } - async goPrevious(): Promise { } - async goLast(): Promise { } - removeFromHistory(_input: EditorInput | IResourceEditorInput): void { } - clear(): void { } - clearRecentlyOpened(): void { } - getHistory(): readonly (EditorInput | IResourceEditorInput)[] { return []; } - async openNextRecentlyUsedEditor(group?: GroupIdentifier): Promise { } - async openPreviouslyUsedEditor(group?: GroupIdentifier): Promise { } - getLastActiveWorkspaceRoot(_schemeFilter: string): URI | undefined { return this.root; } - getLastActiveFile(_schemeFilter: string): URI | undefined { return undefined; } -} - export class TestFileDialogService implements IFileDialogService { declare readonly _serviceBrand: undefined; diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index d1483ff15be..1d09528dcf6 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -149,7 +149,6 @@ export class TestStorageService extends InMemoryStorageService { export class TestHistoryService implements IHistoryService { declare readonly _serviceBrand: undefined; - declare shouldIgnoreActiveEditorChange: boolean; constructor(private root?: URI) { } @@ -166,6 +165,7 @@ export class TestHistoryService implements IHistoryService { async openPreviouslyUsedEditor(group?: GroupIdentifier): Promise { } getLastActiveWorkspaceRoot(_schemeFilter: string): URI | undefined { return this.root; } getLastActiveFile(_schemeFilter: string): URI | undefined { return undefined; } + suspendTracking() { return Disposable.None; } } export class TestWorkingCopy extends Disposable implements IWorkingCopy { From 10a596e4d046742624a6550e1dc233e462076eda Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 16 Feb 2024 10:02:00 +0100 Subject: [PATCH 1344/1897] voice - suspend keyword recognition when focus lost (#205336) * speech - bind stop keyword detection to window focus * . * . * . --- src/vs/code/electron-main/app.ts | 6 +- .../electron-main/auxiliaryWindows.ts | 5 - .../electron-main/nativeHostMainService.ts | 6 +- .../speech.contribution.ts | 3 +- .../contrib/speech/browser/speechService.ts | 210 ++++++++++++++++++ .../contrib/speech/common/speechService.ts | 159 +------------ src/vs/workbench/workbench.common.main.ts | 2 +- 7 files changed, 224 insertions(+), 167 deletions(-) rename src/vs/workbench/contrib/speech/{common => browser}/speech.contribution.ts (76%) create mode 100644 src/vs/workbench/contrib/speech/browser/speechService.ts diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 1dfa17a817c..7a039050402 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -15,7 +15,7 @@ import { Event } from 'vs/base/common/event'; import { stripComments } from 'vs/base/common/json'; import { getPathLabel } from 'vs/base/common/labels'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; +import { Schemas, VSCODE_AUTHORITY } from 'vs/base/common/network'; import { isAbsolute, join, posix } from 'vs/base/common/path'; import { IProcessEnvironment, isLinux, isLinuxSnap, isMacintosh, isWindows, OS } from 'vs/base/common/platform'; import { assertType } from 'vs/base/common/types'; @@ -118,7 +118,7 @@ import { ElectronPtyHostStarter } from 'vs/platform/terminal/electron-main/elect import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService'; import { NODE_REMOTE_RESOURCE_CHANNEL_NAME, NODE_REMOTE_RESOURCE_IPC_METHOD_NAME, NodeRemoteResourceResponse, NodeRemoteResourceRouter } from 'vs/platform/remote/common/electronRemoteResources'; import { Lazy } from 'vs/base/common/lazy'; -import { IAuxiliaryWindowsMainService, isAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; +import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; import { AuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService'; /** @@ -392,7 +392,7 @@ export class CodeApplication extends Disposable { app.on('web-contents-created', (event, contents) => { // Auxiliary Window: delegate to `AuxiliaryWindow` class - if (isAuxiliaryWindow(contents)) { + if (contents?.opener?.url.startsWith(`${Schemas.vscodeFileResource}://${VSCODE_AUTHORITY}/`)) { this.logService.trace('[aux window] app.on("web-contents-created"): Registering auxiliary window'); this.auxiliaryWindowsMainService?.registerWindow(contents); diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts index 699659d0c77..4ce7e22bbff 100644 --- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts +++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts @@ -5,7 +5,6 @@ import { BrowserWindowConstructorOptions, HandlerDetails, WebContents } from 'electron'; import { Event } from 'vs/base/common/event'; -import { Schemas, VSCODE_AUTHORITY } from 'vs/base/common/network'; import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -30,7 +29,3 @@ export interface IAuxiliaryWindowsMainService { getWindows(): readonly IAuxiliaryWindow[]; } - -export function isAuxiliaryWindow(webContents: WebContents): boolean { - return webContents?.opener?.url.startsWith(`${Schemas.vscodeFileResource}://${VSCODE_AUTHORITY}/`); -} diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index fa4087a411f..a1d28301611 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -40,7 +40,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { hasWSLFeatureInstalled } from 'vs/platform/remote/node/wsl'; import { WindowProfiler } from 'vs/platform/profiling/electron-main/windowProfiling'; import { IV8Profile } from 'vs/platform/profiling/common/profiling'; -import { IAuxiliaryWindowsMainService, isAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; +import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; import { CancellationError } from 'vs/base/common/errors'; @@ -105,11 +105,11 @@ export class NativeHostMainService extends Disposable implements INativeHostMain readonly onDidBlurMainOrAuxiliaryWindow = Event.any( this.onDidBlurMainWindow, - Event.map(Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-blur', (event, window: BrowserWindow) => window), window => isAuxiliaryWindow(window.webContents)), window => window.id) + Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-blur', (event, window: BrowserWindow) => window.id), windowId => !!this.auxiliaryWindowsMainService.getWindowById(windowId)) ); readonly onDidFocusMainOrAuxiliaryWindow = Event.any( this.onDidFocusMainWindow, - Event.map(Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-focus', (event, window: BrowserWindow) => window), window => isAuxiliaryWindow(window.webContents)), window => window.id) + Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-focus', (event, window: BrowserWindow) => window.id), windowId => !!this.auxiliaryWindowsMainService.getWindowById(windowId)) ); readonly onDidResumeOS = Event.fromNodeEventEmitter(powerMonitor, 'resume'); diff --git a/src/vs/workbench/contrib/speech/common/speech.contribution.ts b/src/vs/workbench/contrib/speech/browser/speech.contribution.ts similarity index 76% rename from src/vs/workbench/contrib/speech/common/speech.contribution.ts rename to src/vs/workbench/contrib/speech/browser/speech.contribution.ts index 6a093cd32e7..03a6035fb80 100644 --- a/src/vs/workbench/contrib/speech/common/speech.contribution.ts +++ b/src/vs/workbench/contrib/speech/browser/speech.contribution.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ISpeechService, SpeechService } from 'vs/workbench/contrib/speech/common/speechService'; +import { ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; +import { SpeechService } from 'vs/workbench/contrib/speech/browser/speechService'; registerSingleton(ISpeechService, SpeechService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/speech/browser/speechService.ts b/src/vs/workbench/contrib/speech/browser/speechService.ts new file mode 100644 index 00000000000..99a2fe06e81 --- /dev/null +++ b/src/vs/workbench/contrib/speech/browser/speechService.ts @@ -0,0 +1,210 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { firstOrDefault } from 'vs/base/common/arrays'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { DeferredPromise } from 'vs/base/common/async'; +import { ISpeechService, ISpeechProvider, HasSpeechProvider, ISpeechToTextSession, SpeechToTextInProgress, IKeywordRecognitionSession, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; + +export class SpeechService extends Disposable implements ISpeechService { + + readonly _serviceBrand: undefined; + + private readonly _onDidRegisterSpeechProvider = this._register(new Emitter()); + readonly onDidRegisterSpeechProvider = this._onDidRegisterSpeechProvider.event; + + private readonly _onDidUnregisterSpeechProvider = this._register(new Emitter()); + readonly onDidUnregisterSpeechProvider = this._onDidUnregisterSpeechProvider.event; + + get hasSpeechProvider() { return this.providers.size > 0; } + + private readonly providers = new Map(); + + private readonly hasSpeechProviderContext = HasSpeechProvider.bindTo(this.contextKeyService); + + constructor( + @ILogService private readonly logService: ILogService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IHostService private readonly hostService: IHostService + ) { + super(); + } + + registerSpeechProvider(identifier: string, provider: ISpeechProvider): IDisposable { + if (this.providers.has(identifier)) { + throw new Error(`Speech provider with identifier ${identifier} is already registered.`); + } + + this.providers.set(identifier, provider); + this.hasSpeechProviderContext.set(true); + + this._onDidRegisterSpeechProvider.fire(provider); + + return toDisposable(() => { + this.providers.delete(identifier); + this._onDidUnregisterSpeechProvider.fire(provider); + + if (this.providers.size === 0) { + this.hasSpeechProviderContext.set(false); + } + }); + } + + private readonly _onDidStartSpeechToTextSession = this._register(new Emitter()); + readonly onDidStartSpeechToTextSession = this._onDidStartSpeechToTextSession.event; + + private readonly _onDidEndSpeechToTextSession = this._register(new Emitter()); + readonly onDidEndSpeechToTextSession = this._onDidEndSpeechToTextSession.event; + + private _activeSpeechToTextSession: ISpeechToTextSession | undefined = undefined; + get hasActiveSpeechToTextSession() { return !!this._activeSpeechToTextSession; } + + private readonly speechToTextInProgress = SpeechToTextInProgress.bindTo(this.contextKeyService); + + createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { + const provider = firstOrDefault(Array.from(this.providers.values())); + if (!provider) { + throw new Error(`No Speech provider is registered.`); + } else if (this.providers.size > 1) { + this.logService.warn(`Multiple speech providers registered. Picking first one: ${provider.metadata.displayName}`); + } + + const session = this._activeSpeechToTextSession = provider.createSpeechToTextSession(token); + + const disposables = new DisposableStore(); + + const onSessionStoppedOrCanceled = () => { + if (session === this._activeSpeechToTextSession) { + this._activeSpeechToTextSession = undefined; + this.speechToTextInProgress.reset(); + this._onDidEndSpeechToTextSession.fire(); + } + + disposables.dispose(); + }; + + disposables.add(token.onCancellationRequested(() => onSessionStoppedOrCanceled())); + if (token.isCancellationRequested) { + onSessionStoppedOrCanceled(); + } + + disposables.add(session.onDidChange(e => { + switch (e.status) { + case SpeechToTextStatus.Started: + if (session === this._activeSpeechToTextSession) { + this.speechToTextInProgress.set(true); + this._onDidStartSpeechToTextSession.fire(); + } + break; + case SpeechToTextStatus.Stopped: + onSessionStoppedOrCanceled(); + break; + } + })); + + return session; + } + + private readonly _onDidStartKeywordRecognition = this._register(new Emitter()); + readonly onDidStartKeywordRecognition = this._onDidStartKeywordRecognition.event; + + private readonly _onDidEndKeywordRecognition = this._register(new Emitter()); + readonly onDidEndKeywordRecognition = this._onDidEndKeywordRecognition.event; + + private _activeKeywordRecognitionSession: IKeywordRecognitionSession | undefined = undefined; + get hasActiveKeywordRecognition() { return !!this._activeKeywordRecognitionSession; } + + async recognizeKeyword(token: CancellationToken): Promise { + const result = new DeferredPromise(); + + const disposables = new DisposableStore(); + disposables.add(token.onCancellationRequested(() => { + disposables.dispose(); + result.complete(KeywordRecognitionStatus.Canceled); + })); + + const recognizeKeywordDisposables = disposables.add(new DisposableStore()); + let activeRecognizeKeywordSession: Promise | undefined = undefined; + const recognizeKeyword = () => { + recognizeKeywordDisposables.clear(); + + const cts = new CancellationTokenSource(token); + recognizeKeywordDisposables.add(toDisposable(() => cts.dispose(true))); + const currentRecognizeKeywordSession = activeRecognizeKeywordSession = this.doRecognizeKeyword(cts.token).then(status => { + if (currentRecognizeKeywordSession === activeRecognizeKeywordSession) { + result.complete(status); + } + }, error => { + if (currentRecognizeKeywordSession === activeRecognizeKeywordSession) { + result.error(error); + } + }); + }; + + disposables.add(this.hostService.onDidChangeFocus(focused => { + if (!focused && activeRecognizeKeywordSession) { + recognizeKeywordDisposables.clear(); + activeRecognizeKeywordSession = undefined; + } else if (!activeRecognizeKeywordSession) { + recognizeKeyword(); + } + })); + + if (this.hostService.hasFocus) { + recognizeKeyword(); + } + + try { + return await result.p; + } finally { + disposables.dispose(); + } + } + + private async doRecognizeKeyword(token: CancellationToken): Promise { + const provider = firstOrDefault(Array.from(this.providers.values())); + if (!provider) { + throw new Error(`No Speech provider is registered.`); + } else if (this.providers.size > 1) { + this.logService.warn(`Multiple speech providers registered. Picking first one: ${provider.metadata.displayName}`); + } + + const session = this._activeKeywordRecognitionSession = provider.createKeywordRecognitionSession(token); + this._onDidStartKeywordRecognition.fire(); + + const disposables = new DisposableStore(); + + const onSessionStoppedOrCanceled = () => { + if (session === this._activeKeywordRecognitionSession) { + this._activeKeywordRecognitionSession = undefined; + this._onDidEndKeywordRecognition.fire(); + } + + disposables.dispose(); + }; + + disposables.add(token.onCancellationRequested(() => onSessionStoppedOrCanceled())); + if (token.isCancellationRequested) { + onSessionStoppedOrCanceled(); + } + + disposables.add(session.onDidChange(e => { + if (e.status === KeywordRecognitionStatus.Stopped) { + onSessionStoppedOrCanceled(); + } + })); + + try { + return (await Event.toPromise(session.onDidChange)).status; + } finally { + onSessionStoppedOrCanceled(); + } + } +} diff --git a/src/vs/workbench/contrib/speech/common/speechService.ts b/src/vs/workbench/contrib/speech/common/speechService.ts index 48f21fb8ac1..d70eabdfb2d 100644 --- a/src/vs/workbench/contrib/speech/common/speechService.ts +++ b/src/vs/workbench/contrib/speech/common/speechService.ts @@ -4,14 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { firstOrDefault } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { Event } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ILogService } from 'vs/platform/log/common/log'; export const ISpeechService = createDecorator('speechService'); @@ -41,7 +39,8 @@ export interface ISpeechToTextSession extends IDisposable { export enum KeywordRecognitionStatus { Recognized = 1, - Stopped = 2 + Stopped = 2, + Canceled = 3 } export interface IKeywordRecognitionEvent { @@ -94,151 +93,3 @@ export interface ISpeechService { */ recognizeKeyword(token: CancellationToken): Promise; } - -export class SpeechService extends Disposable implements ISpeechService { - - readonly _serviceBrand: undefined; - - private readonly _onDidRegisterSpeechProvider = this._register(new Emitter()); - readonly onDidRegisterSpeechProvider = this._onDidRegisterSpeechProvider.event; - - private readonly _onDidUnregisterSpeechProvider = this._register(new Emitter()); - readonly onDidUnregisterSpeechProvider = this._onDidUnregisterSpeechProvider.event; - - get hasSpeechProvider() { return this.providers.size > 0; } - - private readonly providers = new Map(); - - private readonly hasSpeechProviderContext = HasSpeechProvider.bindTo(this.contextKeyService); - - constructor( - @ILogService private readonly logService: ILogService, - @IContextKeyService private readonly contextKeyService: IContextKeyService - ) { - super(); - } - - registerSpeechProvider(identifier: string, provider: ISpeechProvider): IDisposable { - if (this.providers.has(identifier)) { - throw new Error(`Speech provider with identifier ${identifier} is already registered.`); - } - - this.providers.set(identifier, provider); - this.hasSpeechProviderContext.set(true); - - this._onDidRegisterSpeechProvider.fire(provider); - - return toDisposable(() => { - this.providers.delete(identifier); - this._onDidUnregisterSpeechProvider.fire(provider); - - if (this.providers.size === 0) { - this.hasSpeechProviderContext.set(false); - } - }); - } - - private readonly _onDidStartSpeechToTextSession = this._register(new Emitter()); - readonly onDidStartSpeechToTextSession = this._onDidStartSpeechToTextSession.event; - - private readonly _onDidEndSpeechToTextSession = this._register(new Emitter()); - readonly onDidEndSpeechToTextSession = this._onDidEndSpeechToTextSession.event; - - private _activeSpeechToTextSession: ISpeechToTextSession | undefined = undefined; - get hasActiveSpeechToTextSession() { return !!this._activeSpeechToTextSession; } - - private readonly speechToTextInProgress = SpeechToTextInProgress.bindTo(this.contextKeyService); - - createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { - const provider = firstOrDefault(Array.from(this.providers.values())); - if (!provider) { - throw new Error(`No Speech provider is registered.`); - } else if (this.providers.size > 1) { - this.logService.warn(`Multiple speech providers registered. Picking first one: ${provider.metadata.displayName}`); - } - - const session = this._activeSpeechToTextSession = provider.createSpeechToTextSession(token); - - const disposables = new DisposableStore(); - - const onSessionStoppedOrCanceled = () => { - if (session === this._activeSpeechToTextSession) { - this._activeSpeechToTextSession = undefined; - this.speechToTextInProgress.reset(); - this._onDidEndSpeechToTextSession.fire(); - } - - disposables.dispose(); - }; - - disposables.add(token.onCancellationRequested(() => onSessionStoppedOrCanceled())); - if (token.isCancellationRequested) { - onSessionStoppedOrCanceled(); - } - - disposables.add(session.onDidChange(e => { - switch (e.status) { - case SpeechToTextStatus.Started: - if (session === this._activeSpeechToTextSession) { - this.speechToTextInProgress.set(true); - this._onDidStartSpeechToTextSession.fire(); - } - break; - case SpeechToTextStatus.Stopped: - onSessionStoppedOrCanceled(); - break; - } - })); - - return session; - } - - private readonly _onDidStartKeywordRecognition = this._register(new Emitter()); - readonly onDidStartKeywordRecognition = this._onDidStartKeywordRecognition.event; - - private readonly _onDidEndKeywordRecognition = this._register(new Emitter()); - readonly onDidEndKeywordRecognition = this._onDidEndKeywordRecognition.event; - - private _activeKeywordRecognitionSession: IKeywordRecognitionSession | undefined = undefined; - get hasActiveKeywordRecognition() { return !!this._activeKeywordRecognitionSession; } - - async recognizeKeyword(token: CancellationToken): Promise { - const provider = firstOrDefault(Array.from(this.providers.values())); - if (!provider) { - throw new Error(`No Speech provider is registered.`); - } else if (this.providers.size > 1) { - this.logService.warn(`Multiple speech providers registered. Picking first one: ${provider.metadata.displayName}`); - } - - const session = this._activeKeywordRecognitionSession = provider.createKeywordRecognitionSession(token); - this._onDidStartKeywordRecognition.fire(); - - const disposables = new DisposableStore(); - - const onSessionStoppedOrCanceled = () => { - if (session === this._activeKeywordRecognitionSession) { - this._activeKeywordRecognitionSession = undefined; - this._onDidEndKeywordRecognition.fire(); - } - - disposables.dispose(); - }; - - disposables.add(token.onCancellationRequested(() => onSessionStoppedOrCanceled())); - if (token.isCancellationRequested) { - onSessionStoppedOrCanceled(); - } - - disposables.add(session.onDidChange(e => { - if (e.status === KeywordRecognitionStatus.Stopped) { - onSessionStoppedOrCanceled(); - } - })); - - try { - return (await Event.toPromise(session.onDidChange)).status; - } finally { - onSessionStoppedOrCanceled(); - } - } -} diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 719bd0bc24c..a7d0a54f6dd 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -181,7 +181,7 @@ import 'vs/workbench/contrib/contextmenu/browser/contextmenu.contribution'; import 'vs/workbench/contrib/notebook/browser/notebook.contribution'; // Speech -import 'vs/workbench/contrib/speech/common/speech.contribution'; +import 'vs/workbench/contrib/speech/browser/speech.contribution'; // Chat import 'vs/workbench/contrib/chat/browser/chat.contribution'; From 133b4e591c6e14847b775ddf8c8478d8d619fc23 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 16 Feb 2024 10:07:01 +0100 Subject: [PATCH 1345/1897] Revert custom hover for input boxes (#205337) --- src/vs/base/browser/ui/inputbox/inputBox.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index a23848dd914..e4c89dd3aff 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -11,8 +11,6 @@ import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { AnchorAlignment, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Widget } from 'vs/base/browser/ui/widget'; import { IAction } from 'vs/base/common/actions'; @@ -113,7 +111,6 @@ export class InputBox extends Widget { private cachedContentHeight: number | undefined; private maxHeight: number = Number.POSITIVE_INFINITY; private scrollableElement: ScrollableElement | undefined; - private hover: ICustomHover | undefined; private _onDidChange = this._register(new Emitter()); public readonly onDidChange: Event = this._onDidChange.event; @@ -233,11 +230,7 @@ export class InputBox extends Widget { public setTooltip(tooltip: string): void { this.tooltip = tooltip; - if (!this.hover) { - this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('element'), this.input, tooltip)); - } else { - this.hover.update(tooltip); - } + this.input.title = tooltip; } public setAriaLabel(label: string): void { From 00b91ce10ab487b4fa6e8fe3b157d1b97342956b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 16 Feb 2024 10:21:57 +0100 Subject: [PATCH 1346/1897] fix https://github.com/microsoft/vscode/issues/203299 (#205342) --- .../inlineChat/electron-sandbox/inlineChatQuickVoice.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index ad596b50fe8..16c1d70d90b 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -151,13 +151,9 @@ class QuickVoiceWidget implements IContentWidget { return null; } const selection = this._editor.getSelection(); - // const position = this._editor.getPosition(); return { - position: selection.getPosition(), - preference: [ - selection.getPosition().equals(selection.getStartPosition()) ? ContentWidgetPositionPreference.ABOVE : ContentWidgetPositionPreference.BELOW, - ContentWidgetPositionPreference.EXACT - ] + position: selection.getStartPosition(), + preference: [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.EXACT] }; } From 0f8cab37f1c453af2ce8a717d4a7c661ac8fe333 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 16 Feb 2024 10:58:44 +0100 Subject: [PATCH 1347/1897] Git - add "Hard wrap all lines" code action is there are multiple long lines (#205349) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Git - add "Hard wrap all lines" code action is there are multiple long lines * 💄 Moved code around --- extensions/git/src/diagnostics.ts | 52 ++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/extensions/git/src/diagnostics.ts b/extensions/git/src/diagnostics.ts index f9b45a9b23e..aa09fbe61e8 100644 --- a/extensions/git/src/diagnostics.ts +++ b/extensions/git/src/diagnostics.ts @@ -82,6 +82,7 @@ export class GitCommitInputBoxCodeActionsProvider implements CodeActionProvider provideCodeActions(document: TextDocument, range: Range | Selection): CodeAction[] { const codeActions: CodeAction[] = []; const diagnostics = this.diagnosticsManager.getDiagnostics(document.uri); + const wrapAllLinesCodeAction = this.getWrapAllLinesCodeAction(document, diagnostics); for (const diagnostic of diagnostics) { if (!diagnostic.range.contains(range)) { @@ -96,17 +97,23 @@ export class GitCommitInputBoxCodeActionsProvider implements CodeActionProvider const codeAction = new CodeAction(l10n.t('Remove empty characters'), CodeActionKind.QuickFix); codeAction.diagnostics = [diagnostic]; codeAction.edit = workspaceEdit; - codeActions.push(codeAction); + break; } case DiagnosticCodes.line_length: { const workspaceEdit = this.getWrapLineWorkspaceEdit(document, diagnostic.range); + const codeAction = new CodeAction(l10n.t('Hard wrap line'), CodeActionKind.QuickFix); codeAction.diagnostics = [diagnostic]; codeAction.edit = workspaceEdit; - codeActions.push(codeAction); + + if (wrapAllLinesCodeAction) { + wrapAllLinesCodeAction.diagnostics = [diagnostic]; + codeActions.push(wrapAllLinesCodeAction); + } + break; } } @@ -116,13 +123,45 @@ export class GitCommitInputBoxCodeActionsProvider implements CodeActionProvider } private getWrapLineWorkspaceEdit(document: TextDocument, range: Range): WorkspaceEdit { + const lineSegments = this.wrapTextDocumentLine(document, range.start.line); + + const workspaceEdit = new WorkspaceEdit(); + workspaceEdit.replace(document.uri, range, lineSegments.join('\n')); + + return workspaceEdit; + } + + private getWrapAllLinesCodeAction(document: TextDocument, diagnostics: readonly Diagnostic[]): CodeAction | undefined { + const lineLengthDiagnostics = diagnostics.filter(d => d.code === DiagnosticCodes.line_length); + if (lineLengthDiagnostics.length < 2) { + return undefined; + } + + const wrapAllLinesCodeAction = new CodeAction(l10n.t('Hard wrap all lines'), CodeActionKind.QuickFix); + wrapAllLinesCodeAction.edit = this.getWrapAllLinesWorkspaceEdit(document, lineLengthDiagnostics); + + return wrapAllLinesCodeAction; + } + + private getWrapAllLinesWorkspaceEdit(document: TextDocument, diagnostics: Diagnostic[]): WorkspaceEdit { + const workspaceEdit = new WorkspaceEdit(); + + for (const diagnostic of diagnostics) { + const lineSegments = this.wrapTextDocumentLine(document, diagnostic.range.start.line); + workspaceEdit.replace(document.uri, diagnostic.range, lineSegments.join('\n')); + } + + return workspaceEdit; + } + + private wrapTextDocumentLine(document: TextDocument, line: number): string[] { const config = workspace.getConfiguration('git'); const inputValidationLength = config.get('inputValidationLength', 50); const inputValidationSubjectLength = config.get('inputValidationSubjectLength', undefined); - const lineLengthThreshold = range.start.line === 0 ? inputValidationSubjectLength ?? inputValidationLength : inputValidationLength; + const lineLengthThreshold = line === 0 ? inputValidationSubjectLength ?? inputValidationLength : inputValidationLength; const lineSegments: string[] = []; - const lineText = document.lineAt(range.start.line).text; + const lineText = document.lineAt(line).text; let position = 0; while (lineText.length - position > lineLengthThreshold) { @@ -147,10 +186,7 @@ export class GitCommitInputBoxCodeActionsProvider implements CodeActionProvider lineSegments.push(lineText.substring(position)); } - const workspaceEdit = new WorkspaceEdit(); - workspaceEdit.replace(document.uri, range, lineSegments.join('\n')); - - return workspaceEdit; + return lineSegments; } dispose() { From b28c2debc506c4c9df9433846797d976948a1dfb Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 16 Feb 2024 11:02:03 +0100 Subject: [PATCH 1348/1897] fix https://github.com/microsoft/vscode/issues/202232 (#205351) --- src/vs/editor/common/languageSelector.ts | 11 +++++++++ .../workbench/api/common/extHost.api.impl.ts | 10 +++++--- .../api/common/extHostDocumentData.ts | 1 - .../api/common/extHostDocumentsAndEditors.ts | 13 ++--------- .../api/common/extHostInteractive.ts | 1 - .../workbench/api/common/extHostNotebook.ts | 2 +- .../api/common/extHostNotebookDocument.ts | 23 +++++++++++++------ 7 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/vs/editor/common/languageSelector.ts b/src/vs/editor/common/languageSelector.ts index e657a753376..db32360aa2e 100644 --- a/src/vs/editor/common/languageSelector.ts +++ b/src/vs/editor/common/languageSelector.ts @@ -131,3 +131,14 @@ export function score(selector: LanguageSelector | undefined, candidateUri: URI, return 0; } } + + +export function targetsNotebooks(selector: LanguageSelector): boolean { + if (typeof selector === 'string') { + return false; + } else if (Array.isArray(selector)) { + return selector.some(targetsNotebooks); + } else { + return !!(selector).notebookType; + } +} diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 4e95fc270b0..b93fdbeb5d7 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -11,7 +11,7 @@ import { Schemas, matchesScheme } from 'vs/base/common/network'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions'; -import { score } from 'vs/editor/common/languageSelector'; +import { score, targetsNotebooks } from 'vs/editor/common/languageSelector'; import * as languageConfiguration from 'vs/editor/common/languages/languageConfiguration'; import { OverviewRulerLane } from 'vs/editor/common/model'; import { ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -533,8 +533,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostLanguages.changeLanguage(document.uri, languageId); }, match(selector: vscode.DocumentSelector, document: vscode.TextDocument): number { - const notebook = extHostDocuments.getDocumentData(document.uri)?.notebook; - return score(typeConverters.LanguageSelector.from(selector), document.uri, document.languageId, true, notebook?.uri, notebook?.notebookType); + const interalSelector = typeConverters.LanguageSelector.from(selector); + let notebook: vscode.NotebookDocument | undefined; + if (targetsNotebooks(interalSelector)) { + notebook = extHostNotebook.notebookDocuments.find(value => Boolean(value.getCell(document.uri)))?.apiNotebook; + } + return score(interalSelector, document.uri, document.languageId, true, notebook?.uri, notebook?.notebookType); }, registerCodeActionsProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable { return extHostLanguageFeatures.registerCodeActionProvider(extension, checkSelector(selector), provider, metadata); diff --git a/src/vs/workbench/api/common/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index 139f826be43..ee321b2575a 100644 --- a/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/src/vs/workbench/api/common/extHostDocumentData.ts @@ -37,7 +37,6 @@ export class ExtHostDocumentData extends MirrorTextModel { uri: URI, lines: string[], eol: string, versionId: number, private _languageId: string, private _isDirty: boolean, - public readonly notebook?: vscode.NotebookDocument | undefined ) { super(uri, lines, eol, versionId); } diff --git a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts index bb64f26fa8e..224d9d55371 100644 --- a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts +++ b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts @@ -9,7 +9,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { dispose } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, IModelAddedData, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor'; @@ -31,14 +31,6 @@ class Reference { } } -export interface IExtHostModelAddedData extends IModelAddedData { - notebook?: vscode.NotebookDocument; -} - -export interface IExtHostDocumentsAndEditorsDelta extends IDocumentsAndEditorsDelta { - addedDocuments?: IExtHostModelAddedData[]; -} - export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape { readonly _serviceBrand: undefined; @@ -67,7 +59,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha this.acceptDocumentsAndEditorsDelta(delta); } - acceptDocumentsAndEditorsDelta(delta: IExtHostDocumentsAndEditorsDelta): void { + acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void { const removedDocuments: ExtHostDocumentData[] = []; const addedDocuments: ExtHostDocumentData[] = []; @@ -105,7 +97,6 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha data.versionId, data.languageId, data.isDirty, - data.notebook )); this._documents.set(resource, ref); addedDocuments.push(ref.value); diff --git a/src/vs/workbench/api/common/extHostInteractive.ts b/src/vs/workbench/api/common/extHostInteractive.ts index b53d846c2e1..63296ab1030 100644 --- a/src/vs/workbench/api/common/extHostInteractive.ts +++ b/src/vs/workbench/api/common/extHostInteractive.ts @@ -52,7 +52,6 @@ export class ExtHostInteractive implements ExtHostInteractiveShape { uri: uri, isDirty: false, versionId: 1, - notebook: this._extHostNotebooks.getNotebookDocument(URI.revive(notebookUri))?.apiNotebook }] }); } diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 6af6206b7fc..75dc5a402ff 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -625,7 +625,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { ); // add cell document as vscode.TextDocument - addedCellDocuments.push(...modelData.cells.map(cell => ExtHostCell.asModelAddData(document.apiNotebook, cell))); + addedCellDocuments.push(...modelData.cells.map(cell => ExtHostCell.asModelAddData(cell))); this._documents.get(uri)?.dispose(); this._documents.set(uri, document); diff --git a/src/vs/workbench/api/common/extHostNotebookDocument.ts b/src/vs/workbench/api/common/extHostNotebookDocument.ts index e8970f70dee..2cc7a200edc 100644 --- a/src/vs/workbench/api/common/extHostNotebookDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookDocument.ts @@ -7,7 +7,7 @@ import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; -import { ExtHostDocumentsAndEditors, IExtHostModelAddedData } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import * as extHostTypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { NotebookRange } from 'vs/workbench/api/common/extHostTypes'; import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -33,15 +33,14 @@ class RawContentChangeEvent { export class ExtHostCell { - static asModelAddData(notebook: vscode.NotebookDocument, cell: extHostProtocol.NotebookCellDto): IExtHostModelAddedData { + static asModelAddData(cell: extHostProtocol.NotebookCellDto): extHostProtocol.IModelAddedData { return { EOL: cell.eol, lines: cell.source, languageId: cell.language, uri: cell.uri, isDirty: false, - versionId: 1, - notebook + versionId: 1 }; } @@ -356,7 +355,7 @@ export class ExtHostNotebookDocument { } const contentChangeEvents: RawContentChangeEvent[] = []; - const addedCellDocuments: IExtHostModelAddedData[] = []; + const addedCellDocuments: extHostProtocol.IModelAddedData[] = []; const removedCellDocuments: URI[] = []; splices.reverse().forEach(splice => { @@ -365,7 +364,7 @@ export class ExtHostNotebookDocument { const extCell = new ExtHostCell(this, this._textDocumentsAndEditors, cell); if (!initialization) { - addedCellDocuments.push(ExtHostCell.asModelAddData(this.apiNotebook, cell)); + addedCellDocuments.push(ExtHostCell.asModelAddData(cell)); } return extCell; }); @@ -443,7 +442,17 @@ export class ExtHostNotebookDocument { return this._cells[index]; } - getCell(cellHandle: number): ExtHostCell | undefined { + getCell(cellHandle: number | URI): ExtHostCell | undefined { + if (URI.isUri(cellHandle)) { + const data = notebookCommon.CellUri.parse(cellHandle); + if (!data) { + return undefined; + } + if (data.notebook.toString() !== this.uri.toString()) { + return undefined; + } + cellHandle = data.handle; + } return this._cells.find(cell => cell.handle === cellHandle); } From 746c6d8b84260c475e173cf6e857e31f14ce8f09 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 16 Feb 2024 11:03:19 +0100 Subject: [PATCH 1349/1897] adding review changes --- .../multiDiffEditorWidget.ts | 7 ++--- .../multiDiffEditorWidgetImpl.ts | 29 ++++++++++++------- .../bulkEdit/browser/preview/bulkEditPane.ts | 4 +-- .../browser/multiDiffEditor.ts | 22 ++++---------- 4 files changed, 29 insertions(+), 33 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index a1405574d34..dcc315e5b7d 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { derived, derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; -import { IMultiDiffEditorViewState, IMultiDiffResource, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorOptionRevealData, IMultiDiffEditorViewState, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import './colors'; @@ -18,7 +18,6 @@ import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IRange } from 'vs/editor/common/core/range'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; export class MultiDiffEditorWidget extends Disposable { @@ -46,8 +45,8 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public reveal(resource: IMultiDiffResource, range: IRange): void { - this._widgetImpl.get().reveal(resource, range); + public reveal(revealData: IMultiDiffEditorOptionRevealData): void { + this._widgetImpl.get().reveal(revealData); } public createViewModel(model: IMultiDiffEditorModel): MultiDiffEditorViewModel { diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 3aedbd4cc57..26ad0f1b164 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -26,7 +26,8 @@ import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IRange } from 'vs/editor/common/core/range'; +import { Range } from 'vs/editor/common/core/range'; +import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _elements = h('div.monaco-component.multiDiffEditor', [ @@ -190,7 +191,8 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } // todo@aiday-mar need to reveal the range instead of just the start line number - public reveal(resource: IMultiDiffResource, range: IRange): void { + public reveal(revealData: IMultiDiffEditorOptionRevealData): void { + const resource = revealData.resource; const viewItems = this._viewItems.get(); let searchCallback: (item: VirtualizedViewItem) => boolean; if ('original' in resource) { @@ -199,7 +201,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { searchCallback = (item) => item.viewModel.modifiedUri?.toString() === resource.modified.toString(); } const index = viewItems.findIndex(searchCallback); - let scrollTop = (range.startLineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); + let scrollTop = (revealData.range.startLineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); for (let i = 0; i < index; i++) { scrollTop += viewItems[i].contentHeight.get() + this._spaceBetweenPx; } @@ -299,16 +301,21 @@ interface IMultiDiffDocState { selections?: ISelection[]; } -export type IMultiDiffResource = { original: URI } | { modified: URI }; - -export function isIMultiDiffResource(obj: any): obj is IMultiDiffResource { - if (('original' in obj && obj.original instanceof URI) - || ('modified' in obj && obj.modified instanceof URI)) { - return true; - } - return false; +export interface IMultiDiffEditorOptions extends ITextEditorOptions { + viewState?: IMultiDiffEditorOptionsViewState; } +export interface IMultiDiffEditorOptionsViewState { + revealData?: IMultiDiffEditorOptionRevealData; +} + +export interface IMultiDiffEditorOptionRevealData { + resource: IMultiDiffResource; + range: Range; +} + +export type IMultiDiffResource = { original: URI } | { modified: URI }; + class VirtualizedViewItem extends Disposable { private readonly _templateRef = this._register(disposableObservableValue | undefined>(this, undefined)); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 913f8bdf136..8739a79bbbb 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -27,7 +27,6 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { MenuId } from 'vs/platform/actions/common/actions'; import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import type { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IViewDescriptorService } from 'vs/workbench/common/views'; @@ -39,6 +38,7 @@ import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; import { IResourceDiffEditorInput } from 'vs/workbench/common/editor'; import { Range } from 'vs/editor/common/core/range'; +import { IMultiDiffEditorOptions } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; const enum State { Data = 'data', @@ -335,7 +335,7 @@ export class BulkEditPane extends ViewPane { } const resources = await this._resolveResources(fileOperations); - const options: Mutable = { + const options: Mutable = { ...e.editorOptions, viewState: { revealData: { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 7a7aa309a9e..ed582ca29b6 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -8,7 +8,6 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { MultiDiffEditorWidget } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget'; import { IResourceLabel, IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; -import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -24,10 +23,9 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { URI } from 'vs/base/common/uri'; import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; -import { IMultiDiffEditorViewState, IMultiDiffResource, isIMultiDiffResource } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorOptions, IMultiDiffEditorViewState } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; -import { IRange, Range } from 'vs/editor/common/core/range'; export class MultiDiffEditor extends AbstractEditorWithViewState { static readonly ID = 'multiDiffEditor'; @@ -73,11 +71,7 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { + override async setInput(input: MultiDiffEditorInput, options: IMultiDiffEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { await super.setInput(input, options, context, token); this._viewModel = await input.getViewModel(); this._multiDiffEditorWidget!.setViewModel(this._viewModel); @@ -89,20 +83,16 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { From ac465646a9e89eea1bf23941299694716555a2b1 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 16 Feb 2024 11:12:15 +0100 Subject: [PATCH 1350/1897] removing the interface for revealData --- .../multiDiffEditorWidget/multiDiffEditorWidget.ts | 5 +++-- .../multiDiffEditorWidgetImpl.ts | 12 +++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index dcc315e5b7d..60adf436d15 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { derived, derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; -import { IMultiDiffEditorOptionRevealData, IMultiDiffEditorViewState, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorViewState, IMultiDiffResource, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import './colors'; @@ -19,6 +19,7 @@ import { URI } from 'vs/base/common/uri'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; +import { Range } from 'vs/editor/common/core/range'; export class MultiDiffEditorWidget extends Disposable { private readonly _dimension = observableValue(this, undefined); @@ -45,7 +46,7 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public reveal(revealData: IMultiDiffEditorOptionRevealData): void { + public reveal(revealData: { resource: IMultiDiffResource; range: Range }): void { this._widgetImpl.get().reveal(revealData); } diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 26ad0f1b164..e423974d4fb 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -191,7 +191,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } // todo@aiday-mar need to reveal the range instead of just the start line number - public reveal(revealData: IMultiDiffEditorOptionRevealData): void { + public reveal(revealData: { resource: IMultiDiffResource; range: Range }): void { const resource = revealData.resource; const viewItems = this._viewItems.get(); let searchCallback: (item: VirtualizedViewItem) => boolean; @@ -306,12 +306,10 @@ export interface IMultiDiffEditorOptions extends ITextEditorOptions { } export interface IMultiDiffEditorOptionsViewState { - revealData?: IMultiDiffEditorOptionRevealData; -} - -export interface IMultiDiffEditorOptionRevealData { - resource: IMultiDiffResource; - range: Range; + revealData?: { + resource: IMultiDiffResource; + range: Range; + }; } export type IMultiDiffResource = { original: URI } | { modified: URI }; From cff1e8e9a755d206fb33237bb6b6337d171471f2 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 16 Feb 2024 11:00:18 +0100 Subject: [PATCH 1351/1897] Removes accessibleDiffViewer dependency to DiffEditorEditors --- .../components/accessibleDiffViewer.ts | 113 ++++++++++++++---- .../widget/diffEditor/diffEditorWidget.ts | 4 +- 2 files changed, 92 insertions(+), 25 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts index aa3d6da5402..3d4e7ef3067 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts @@ -15,7 +15,6 @@ import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecy import { IObservable, ITransaction, autorun, autorunWithStore, derived, derivedWithStore, observableValue, subtransaction, transaction } from 'vs/base/common/observable'; import { ThemeIcon } from 'vs/base/common/themables'; import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; -import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/components/diffEditorEditors'; import { applyStyle } from 'vs/editor/browser/widget/diffEditor/utils'; import { EditorFontLigatures, EditorOption, IComputedEditorOptions } from 'vs/editor/common/config/editorOptions'; import { LineRange } from 'vs/editor/common/core/lineRange'; @@ -34,11 +33,33 @@ import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/ac import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import 'vs/css!./accessibleDiffViewer'; +import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/components/diffEditorEditors'; const accessibleDiffViewerInsertIcon = registerIcon('diff-review-insert', Codicon.add, localize('accessibleDiffViewerInsertIcon', 'Icon for \'Insert\' in accessible diff viewer.')); const accessibleDiffViewerRemoveIcon = registerIcon('diff-review-remove', Codicon.remove, localize('accessibleDiffViewerRemoveIcon', 'Icon for \'Remove\' in accessible diff viewer.')); const accessibleDiffViewerCloseIcon = registerIcon('diff-review-close', Codicon.close, localize('accessibleDiffViewerCloseIcon', 'Icon for \'Close\' in accessible diff viewer.')); +export interface IAccessibleDiffViewerModel { + getOriginalModel(): ITextModel; + getOriginalOptions(): IComputedEditorOptions; + + /** + * Should do: `setSelection`, `revealLine` and `focus` + */ + originalReveal(range: Range): void; + + getModifiedModel(): ITextModel; + getModifiedOptions(): IComputedEditorOptions; + /** + * Should do: `setSelection`, `revealLine` and `focus` + */ + modifiedReveal(range?: Range): void; + modifiedSetSelection(range: Range): void; + modifiedFocus(): void; + + getModifiedPosition(): Position | undefined; +} + export class AccessibleDiffViewer extends Disposable { public static _ttPolicy = createTrustedTypesPolicy('diffReview', { createHTML: value => value }); @@ -50,7 +71,7 @@ export class AccessibleDiffViewer extends Disposable { private readonly _width: IObservable, private readonly _height: IObservable, private readonly _diffs: IObservable, - private readonly _editors: DiffEditorEditors, + private readonly _models: IAccessibleDiffViewerModel, @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { super(); @@ -62,8 +83,8 @@ export class AccessibleDiffViewer extends Disposable { if (!visible) { return null; } - const model = store.add(this._instantiationService.createInstance(ViewModel, this._diffs, this._editors, this._setVisible, this._canClose)); - const view = store.add(this._instantiationService.createInstance(View, this._parentNode, model, this._width, this._height, this._editors)); + const model = store.add(this._instantiationService.createInstance(ViewModel, this._diffs, this._models, this._setVisible, this._canClose)); + const view = store.add(this._instantiationService.createInstance(View, this._parentNode, model, this._width, this._height, this._models)); return { model, view, }; }).recomputeInitiallyAndOnChange(this._store); @@ -106,7 +127,7 @@ class ViewModel extends Disposable { constructor( private readonly _diffs: IObservable, - private readonly _editors: DiffEditorEditors, + private readonly _models: IAccessibleDiffViewerModel, private readonly _setVisible: (visible: boolean, tx: ITransaction | undefined) => void, public readonly canClose: IObservable, @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @@ -123,12 +144,12 @@ class ViewModel extends Disposable { const groups = computeViewElementGroups( diffs, - this._editors.original.getModel()!.getLineCount(), - this._editors.modified.getModel()!.getLineCount() + this._models.getOriginalModel().getLineCount(), + this._models.getModifiedModel().getLineCount() ); transaction(tx => { - const p = this._editors.modified.getPosition(); + const p = this._models.getModifiedPosition(); if (p) { const nextGroup = groups.findIndex(g => p?.lineNumber < g.range.modified.endLineNumberExclusive); if (nextGroup !== -1) { @@ -155,7 +176,7 @@ class ViewModel extends Disposable { const currentViewItem = this.currentElement.read(reader); if (currentViewItem && currentViewItem.type !== LineType.Header) { const lineNumber = currentViewItem.modifiedLineNumber ?? currentViewItem.diff.modified.startLineNumber; - this._editors.modified.setSelection(Range.fromPositions(new Position(lineNumber, 1))); + this._models.modifiedSetSelection(Range.fromPositions(new Position(lineNumber, 1))); } })); } @@ -194,27 +215,27 @@ class ViewModel extends Disposable { } revealCurrentElementInEditor(): void { + if (!this.canClose.get()) { return; } this._setVisible(false, undefined); const curElem = this.currentElement.get(); if (curElem) { if (curElem.type === LineType.Deleted) { - this._editors.original.setSelection(Range.fromPositions(new Position(curElem.originalLineNumber, 1))); - this._editors.original.revealLine(curElem.originalLineNumber); - this._editors.original.focus(); + this._models.originalReveal(Range.fromPositions(new Position(curElem.originalLineNumber, 1))); } else { - if (curElem.type !== LineType.Header) { - this._editors.modified.setSelection(Range.fromPositions(new Position(curElem.modifiedLineNumber, 1))); - this._editors.modified.revealLine(curElem.modifiedLineNumber); - } - this._editors.modified.focus(); + this._models.modifiedReveal( + curElem.type !== LineType.Header + ? Range.fromPositions(new Position(curElem.modifiedLineNumber, 1)) + : undefined + ); } } } close(): void { + if (!this.canClose.get()) { return; } this._setVisible(false, undefined); - this._editors.modified.focus(); + this._models.modifiedFocus(); } } @@ -327,7 +348,7 @@ class View extends Disposable { private readonly _model: ViewModel, private readonly _width: IObservable, private readonly _height: IObservable, - private readonly _editors: DiffEditorEditors, + private readonly _models: IAccessibleDiffViewerModel, @ILanguageService private readonly _languageService: ILanguageService, ) { super(); @@ -412,8 +433,8 @@ class View extends Disposable { } private _render(store: DisposableStore): void { - const originalOptions = this._editors.original.getOptions(); - const modifiedOptions = this._editors.modified.getOptions(); + const originalOptions = this._models.getOriginalOptions(); + const modifiedOptions = this._models.getModifiedOptions(); const container = document.createElement('div'); container.className = 'diff-review-table'; @@ -423,8 +444,8 @@ class View extends Disposable { reset(this._content, container); - const originalModel = this._editors.original.getModel(); - const modifiedModel = this._editors.modified.getModel(); + const originalModel = this._models.getOriginalModel(); + const modifiedModel = this._models.getModifiedModel(); if (!originalModel || !modifiedModel) { return; } @@ -659,3 +680,49 @@ class View extends Disposable { return r.html; } } + +export class AccessibleDiffViewerModelFromEditors implements IAccessibleDiffViewerModel { + constructor(private readonly editors: DiffEditorEditors) { } + + getOriginalModel(): ITextModel { + return this.editors.original.getModel()!; + } + + getOriginalOptions(): IComputedEditorOptions { + return this.editors.original.getOptions(); + } + + originalReveal(range: Range): void { + this.editors.original.revealRange(range); + this.editors.original.setSelection(range); + this.editors.original.focus(); + } + + getModifiedModel(): ITextModel { + return this.editors.modified.getModel()!; + } + + getModifiedOptions(): IComputedEditorOptions { + return this.editors.modified.getOptions(); + } + + modifiedReveal(range?: Range | undefined): void { + if (range) { + this.editors.modified.revealRange(range); + this.editors.modified.setSelection(range); + } + this.editors.modified.focus(); + } + + modifiedSetSelection(range: Range): void { + this.editors.modified.setSelection(range); + } + + modifiedFocus(): void { + this.editors.modified.focus(); + } + + getModifiedPosition(): Position | undefined { + return this.editors.modified.getPosition() ?? undefined; + } +} diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index acf848c25c4..6b57de66397 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -17,7 +17,7 @@ import { EditorExtensionsRegistry, IDiffEditorContributionDescription } from 'vs import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; -import { AccessibleDiffViewer } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer'; +import { AccessibleDiffViewer, AccessibleDiffViewerModelFromEditors } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer'; import { DiffEditorDecorations } from 'vs/editor/browser/widget/diffEditor/components/diffEditorDecorations'; import { DiffEditorSash } from 'vs/editor/browser/widget/diffEditor/components/diffEditorSash'; import { HideUnchangedRegionsFeature } from 'vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature'; @@ -233,7 +233,7 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { this._rootSizeObserver.width, this._rootSizeObserver.height, this._diffModel.map((m, r) => m?.diff.read(r)?.mappings.map(m => m.lineRangeMapping)), - this._editors, + new AccessibleDiffViewerModelFromEditors(this._editors), ) ).recomputeInitiallyAndOnChange(this._store); From 10bc7cabe1d267cfa9c34dd0c5abc29ff9ba976c Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 16 Feb 2024 11:25:10 +0100 Subject: [PATCH 1352/1897] Diff editor code improvements (#205147) --- .../widget/diffEditor/components/diffEditorEditors.ts | 8 ++++++-- .../diffEditor/features/hideUnchangedRegionsFeature.ts | 1 + .../widget/diffEditor/features/movedBlocksLinesFeature.ts | 6 ++---- src/vs/editor/browser/widget/diffEditor/utils.ts | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts index 1f26c2cad8b..79d6509b225 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts @@ -5,7 +5,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IObservable, IReader, autorunHandleChanges, observableFromEvent } from 'vs/base/common/observable'; +import { IObservable, IReader, autorunHandleChanges, derivedOpts, observableFromEvent } from 'vs/base/common/observable'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -36,6 +36,8 @@ export class DiffEditorEditors extends Disposable { public readonly modifiedSelections: IObservable; public readonly modifiedCursor: IObservable; + public readonly originalCursor: IObservable; + constructor( private readonly originalEditorElement: HTMLElement, private readonly modifiedEditorElement: HTMLElement, @@ -56,7 +58,9 @@ export class DiffEditorEditors extends Disposable { this.modifiedScrollHeight = observableFromEvent(this.modified.onDidScrollChange, () => /** @description modified.getScrollHeight */ this.modified.getScrollHeight()); this.modifiedSelections = observableFromEvent(this.modified.onDidChangeCursorSelection, () => this.modified.getSelections() ?? []); - this.modifiedCursor = observableFromEvent(this.modified.onDidChangeCursorPosition, () => this.modified.getPosition() ?? new Position(1, 1)); + this.modifiedCursor = derivedOpts({ owner: this, equalityComparer: Position.equals }, reader => this.modifiedSelections.read(reader)[0]?.getPosition() ?? new Position(1, 1)); + + this.originalCursor = observableFromEvent(this.original.onDidChangeCursorPosition, () => this.original.getPosition() ?? new Position(1, 1)); this._register(autorunHandleChanges({ createEmptyChangeSummary: () => ({} as IDiffEditorConstructionOptions), diff --git a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts index fb45a24f3c0..f2eb90c6d2f 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts @@ -268,6 +268,7 @@ class CollapsedCodeOverlayWidget extends ViewZoneOverlayWidget { } this._register(autorun(reader => { + /** @description Update CollapsedCodeOverlayWidget canMove* css classes */ const isFullyRevealed = this._unchangedRegion.visibleLineCountTop.read(reader) + this._unchangedRegion.visibleLineCountBottom.read(reader) === this._unchangedRegion.lineCount; this._nodes.bottom.classList.toggle('canMoveTop', !isFullyRevealed); diff --git a/src/vs/editor/browser/widget/diffEditor/features/movedBlocksLinesFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/movedBlocksLinesFeature.ts index 5fa2d56fb0a..af2b6a88995 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/movedBlocksLinesFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/movedBlocksLinesFeature.ts @@ -83,8 +83,6 @@ export class MovedBlocksLinesFeature extends Disposable { } })); - const originalCursorPosition = observableFromEvent(this._editors.original.onDidChangeCursorPosition, () => this._editors.original.getPosition()); - const modifiedCursorPosition = observableFromEvent(this._editors.modified.onDidChangeCursorPosition, () => this._editors.modified.getPosition()); const originalHasFocus = observableSignalFromEvent( 'original.onDidFocusEditorWidget', e => this._editors.original.onDidFocusEditorWidget(() => setTimeout(() => e(undefined), 0)) @@ -115,14 +113,14 @@ export class MovedBlocksLinesFeature extends Disposable { let movedText: MovedText | undefined = undefined; if (diff && lastChangedEditor === 'original') { - const originalPos = originalCursorPosition.read(reader); + const originalPos = this._editors.originalCursor.read(reader); if (originalPos) { movedText = diff.movedTexts.find(m => m.lineRangeMapping.original.contains(originalPos.lineNumber)); } } if (diff && lastChangedEditor === 'modified') { - const modifiedPos = modifiedCursorPosition.read(reader); + const modifiedPos = this._editors.modifiedCursor.read(reader); if (modifiedPos) { movedText = diff.movedTexts.find(m => m.lineRangeMapping.modified.contains(modifiedPos.lineNumber)); } diff --git a/src/vs/editor/browser/widget/diffEditor/utils.ts b/src/vs/editor/browser/widget/diffEditor/utils.ts index 0109a630f20..b71da8bb55d 100644 --- a/src/vs/editor/browser/widget/diffEditor/utils.ts +++ b/src/vs/editor/browser/widget/diffEditor/utils.ts @@ -443,7 +443,7 @@ function addLength(position: Position, length: LengthObj): Position { export function bindContextKey(key: RawContextKey, service: IContextKeyService, computeValue: (reader: IReader) => T): IDisposable { const boundKey = key.bindTo(service); - return autorunOpts({ debugName: () => `Update ${key.key}` }, reader => { + return autorunOpts({ debugName: () => `Set Context Key "${key.key}"` }, reader => { boundKey.set(computeValue(reader)); }); } From 05e5d4bb073221eb49c4f161d55ea4f48ff88958 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 16 Feb 2024 11:32:28 +0100 Subject: [PATCH 1353/1897] fix https://github.com/microsoft/vscode/issues/203373 (#205354) --- .../workbench/browser/parts/titlebar/media/titlebarpart.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 9b713be146e..15afa9df6bf 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -165,6 +165,10 @@ .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center .action-item.command-center-quick-pick { display: flex; + justify-content: start; + overflow: hidden; + margin: auto; + max-width: 600px; } .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center .action-item.command-center-quick-pick .search-icon { From 0fd2b0ce5d941c8c24713076ec07c6f794a1d860 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 16 Feb 2024 11:34:48 +0100 Subject: [PATCH 1354/1897] inlining the resource and the range --- .../widget/multiDiffEditorWidget/multiDiffEditorWidget.ts | 4 ++-- .../multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts | 5 ++--- .../contrib/multiDiffEditor/browser/multiDiffEditor.ts | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index 60adf436d15..5a869f83bd2 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -46,8 +46,8 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public reveal(revealData: { resource: IMultiDiffResource; range: Range }): void { - this._widgetImpl.get().reveal(revealData); + public reveal(resource: IMultiDiffResource, range: Range): void { + this._widgetImpl.get().reveal(resource, range); } public createViewModel(model: IMultiDiffEditorModel): MultiDiffEditorViewModel { diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index e423974d4fb..ca4e240c1da 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -191,8 +191,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } // todo@aiday-mar need to reveal the range instead of just the start line number - public reveal(revealData: { resource: IMultiDiffResource; range: Range }): void { - const resource = revealData.resource; + public reveal(resource: IMultiDiffResource, range: Range): void { const viewItems = this._viewItems.get(); let searchCallback: (item: VirtualizedViewItem) => boolean; if ('original' in resource) { @@ -201,7 +200,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { searchCallback = (item) => item.viewModel.modifiedUri?.toString() === resource.modified.toString(); } const index = viewItems.findIndex(searchCallback); - let scrollTop = (revealData.range.startLineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); + let scrollTop = (range.startLineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); for (let i = 0; i < index; i++) { scrollTop += viewItems[i].contentHeight.get() + this._spaceBetweenPx; } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index ed582ca29b6..bb8bdc7c16e 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -92,7 +92,7 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { From e713ba0fb922a8e466897652d8a587b8669eeb8e Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:48:08 +0100 Subject: [PATCH 1355/1897] Git - Add context menu to history items (#205359) --- extensions/git/package.json | 28 ++++++++++++++++--- .../contrib/scm/browser/scmViewPane.ts | 7 +++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 4e26cff75f4..c2745213c68 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1882,6 +1882,11 @@ "command": "git.viewAllChanges", "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", "group": "inline@1" + }, + { + "command": "git.viewAllChanges", + "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", + "group": "1_view@1" } ], "scm/incomingChanges/historyItem/context": [ @@ -1889,6 +1894,11 @@ "command": "git.viewCommit", "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", "group": "inline@1" + }, + { + "command": "git.viewCommit", + "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", + "group": "1_view@1" } ], "scm/outgoingChanges": [ @@ -1906,13 +1916,13 @@ "scm/outgoingChanges/context": [ { "command": "git.pushRef", - "group": "1_modification@1", - "when": "scmProvider == git && scmHistoryItemGroupHasUpstream" + "when": "scmProvider == git && scmHistoryItemGroupHasUpstream", + "group": "1_modification@1" }, { "command": "git.publish", - "group": "1_modification@1", - "when": "scmProvider == git && !scmHistoryItemGroupHasUpstream" + "when": "scmProvider == git && !scmHistoryItemGroupHasUpstream", + "group": "1_modification@1" } ], "scm/outgoingChanges/allChanges/context": [ @@ -1920,6 +1930,11 @@ "command": "git.viewAllChanges", "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", "group": "inline@1" + }, + { + "command": "git.viewAllChanges", + "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", + "group": "1_view@1" } ], "scm/outgoingChanges/historyItem/context": [ @@ -1927,6 +1942,11 @@ "command": "git.viewCommit", "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", "group": "inline@1" + }, + { + "command": "git.viewCommit", + "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", + "group": "1_view@1" } ], "editor/title": [ diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index ca7a033fdcd..e075d69cca4 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -3222,6 +3222,13 @@ export class SCMViewPane extends ViewPane { actionRunner = new HistoryItemGroupActionRunner(); createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, actions); } + } else if (isSCMHistoryItemTreeElement(element)) { + const menus = this.scmViewService.menus.getRepositoryMenus(element.historyItemGroup.repository.provider); + const menu = menus.historyProviderMenu?.getHistoryItemMenu(element); + if (menu) { + actionRunner = new HistoryItemActionRunner(); + actions = collectContextMenuActions(menu); + } } actionRunner.onWillRun(() => this.tree.domFocus()); From bc7e1fc60904d89b111bb107f6fa0b9f7b331d3a Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 16 Feb 2024 10:20:57 +0100 Subject: [PATCH 1356/1897] ObservablePromise cleanup --- src/vs/base/common/observable.ts | 9 ++- .../base/common/observableInternal/derived.ts | 2 +- .../base/common/observableInternal/promise.ts | 71 +++++++++++++++---- .../browser/multiDiffEditorInput.ts | 8 +-- 4 files changed, 70 insertions(+), 20 deletions(-) diff --git a/src/vs/base/common/observable.ts b/src/vs/base/common/observable.ts index 978c212d765..155ad2aa4f6 100644 --- a/src/vs/base/common/observable.ts +++ b/src/vs/base/common/observable.ts @@ -49,15 +49,20 @@ export { } from 'vs/base/common/observableInternal/utils'; export { ObservableLazy, - ObservableLazyStatefulPromise, + ObservableLazyPromise, ObservablePromise, PromiseResult, waitForState, + derivedWithCancellationToken, } from 'vs/base/common/observableInternal/promise'; import { ConsoleObservableLogger, setLogger } from 'vs/base/common/observableInternal/logging'; -const enableLogging = false; +// Remove "//" in the next line to enable logging +const enableLogging = false + // || Boolean("true") // done "weirdly" so that a lint warning prevents you from pushing this + ; + if (enableLogging) { setLogger(new ConsoleObservableLogger()); } diff --git a/src/vs/base/common/observableInternal/derived.ts b/src/vs/base/common/observableInternal/derived.ts index cab2c7c4bc1..70dc7168418 100644 --- a/src/vs/base/common/observableInternal/derived.ts +++ b/src/vs/base/common/observableInternal/derived.ts @@ -10,7 +10,7 @@ import { DebugNameData, IDebugNameData, Owner } from 'vs/base/common/observableI import { getLogger } from 'vs/base/common/observableInternal/logging'; export type EqualityComparer = (a: T, b: T) => boolean; -const defaultEqualityComparer: EqualityComparer = (a, b) => a === b; +export const defaultEqualityComparer: EqualityComparer = (a, b) => a === b; /** * Creates an observable that is derived from other observables. diff --git a/src/vs/base/common/observableInternal/promise.ts b/src/vs/base/common/observableInternal/promise.ts index 23292cdea50..363ffea7d6e 100644 --- a/src/vs/base/common/observableInternal/promise.ts +++ b/src/vs/base/common/observableInternal/promise.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { autorun } from 'vs/base/common/observableInternal/autorun'; -import { IObservable, observableValue } from './base'; -import { derived } from 'vs/base/common/observableInternal/derived'; +import { IObservable, IReader, observableValue, transaction } from './base'; +import { Derived, defaultEqualityComparer, derived } from 'vs/base/common/observableInternal/derived'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { DebugNameData, Owner } from 'vs/base/common/observableInternal/debugName'; export class ObservableLazy { private readonly _value = observableValue(this, undefined); @@ -38,15 +40,29 @@ export class ObservableLazy { export class ObservablePromise { private readonly _value = observableValue | undefined>(this, undefined); + /** + * The promise that this object wraps. + */ public readonly promise: Promise; - public readonly value: IObservable | undefined> = this._value; + + /** + * The current state of the promise. + * Is `undefined` if the promise didn't resolve yet. + */ + public readonly promiseResult: IObservable | undefined> = this._value; constructor(promise: Promise) { this.promise = promise.then(value => { - this._value.set(new PromiseResult(value, undefined), undefined); + transaction(tx => { + /** @description onPromiseResolved */ + this._value.set(new PromiseResult(value, undefined), tx); + }); return value; }, error => { - this._value.set(new PromiseResult(undefined, error), undefined); + transaction(tx => { + /** @description onPromiseRejected */ + this._value.set(new PromiseResult(undefined, error), tx); + }); throw error; }); } @@ -58,7 +74,7 @@ export class PromiseResult { * The value of the resolved promise. * Undefined if the promise rejected. */ - public readonly value: T | undefined, + public readonly data: T | undefined, /** * The error in case of a rejected promise. @@ -71,30 +87,30 @@ export class PromiseResult { /** * Returns the value if the promise resolved, otherwise throws the error. */ - public getValue(): T { + public getDataOrThrow(): T { if (this.error) { throw this.error; } - return this.value!; + return this.data!; } } /** * A lazy promise whose state is observable. */ -export class ObservableLazyStatefulPromise { - private readonly _lazyValue = new ObservableLazy(() => new ObservablePromise(this._computeValue())); +export class ObservableLazyPromise { + private readonly _lazyValue = new ObservableLazy(() => new ObservablePromise(this._computePromise())); /** * Does not enforce evaluation of the promise compute function. * Is undefined if the promise has not been computed yet. */ - public readonly cachedValue = derived(this, reader => this._lazyValue.cachedValue.read(reader)?.value.read(reader)); + public readonly cachedPromiseResult = derived(this, reader => this._lazyValue.cachedValue.read(reader)?.promiseResult.read(reader)); - constructor(private readonly _computeValue: () => Promise) { + constructor(private readonly _computePromise: () => Promise) { } - public getValue(): Promise { + public getPromise(): Promise { return this._lazyValue.getValue().promise; } } @@ -139,3 +155,32 @@ export function waitForState(observable: IObservable, predicate: (state: T } }); } + +export function derivedWithCancellationToken(computeFn: (reader: IReader, cancellationToken: CancellationToken) => T): IObservable; +export function derivedWithCancellationToken(owner: object, computeFn: (reader: IReader, cancellationToken: CancellationToken) => T): IObservable; +export function derivedWithCancellationToken(computeFnOrOwner: ((reader: IReader, cancellationToken: CancellationToken) => T) | object, computeFnOrUndefined?: ((reader: IReader, cancellationToken: CancellationToken) => T)): IObservable { + let computeFn: (reader: IReader, store: CancellationToken) => T; + let owner: Owner; + if (computeFnOrUndefined === undefined) { + computeFn = computeFnOrOwner as any; + owner = undefined; + } else { + owner = computeFnOrOwner; + computeFn = computeFnOrUndefined as any; + } + + let cancellationTokenSource: CancellationTokenSource | undefined = undefined; + return new Derived( + new DebugNameData(owner, undefined, computeFn), + r => { + if (cancellationTokenSource) { + cancellationTokenSource.dispose(true); + } + cancellationTokenSource = new CancellationTokenSource(); + return computeFn(r, cancellationTokenSource.token); + }, undefined, + undefined, + () => cancellationTokenSource?.dispose(), + defaultEqualityComparer, + ); +} diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index b813d41bff3..cffa05225e6 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -11,7 +11,7 @@ import { Disposable, DisposableStore, IDisposable, IReference, toDisposable } fr import { parse } from 'vs/base/common/marshalling'; import { Schemas } from 'vs/base/common/network'; import { deepClone } from 'vs/base/common/objects'; -import { ObservableLazyStatefulPromise, autorun, derived, observableFromEvent } from 'vs/base/common/observable'; +import { ObservableLazyPromise, autorun, derived, observableFromEvent } from 'vs/base/common/observable'; import { constObservable, mapObservableArrayCached } from 'vs/base/common/observableInternal/utils'; import { ThemeIcon } from 'vs/base/common/themables'; import { isDefined, isObject } from 'vs/base/common/types'; @@ -131,7 +131,7 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor }); private async _createModel(): Promise { - const source = await this._resolvedSource.getValue(); + const source = await this._resolvedSource.getPromise(); const textResourceConfigurationService = this._textResourceConfigurationService; // Enables delayed disposing @@ -207,7 +207,7 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor }; } - private readonly _resolvedSource = new ObservableLazyStatefulPromise(async () => { + private readonly _resolvedSource = new ObservableLazyPromise(async () => { const source: IResolvedMultiDiffSource | undefined = this.initialResources ? new ConstResolvedMultiDiffSource(this.initialResources) : await this._multiDiffSourceResolverService.resolve(this.multiDiffSource); @@ -229,7 +229,7 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor return false; } - private readonly _resources = derived(this, reader => this._resolvedSource.cachedValue.read(reader)?.value?.resources.read(reader)); + private readonly _resources = derived(this, reader => this._resolvedSource.cachedPromiseResult.read(reader)?.data?.resources.read(reader)); private readonly _isDirtyObservables = mapObservableArrayCached(this, this._resources.map(r => r || []), res => { const isModifiedDirty = res.modified ? isUriDirty(this._textFileService, res.modified) : constObservable(false); const isOriginalDirty = res.original ? isUriDirty(this._textFileService, res.original) : constObservable(false); From a0762a9a5c3a5daa5bbcee6e41823ed245a8d218 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 16 Feb 2024 14:06:19 +0100 Subject: [PATCH 1357/1897] voice - log success rates for speech and keyword (#205360) --- .../contrib/chat/common/voiceChat.ts | 2 +- .../contrib/speech/browser/speechService.ts | 48 +++++++++++++++++-- .../contrib/speech/common/speechService.ts | 2 +- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index b1d1d468ea1..ba4156495db 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -136,7 +136,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { let detectedSlashCommand = false; const emitter = disposables.add(new Emitter()); - const session = disposables.add(this.speechService.createSpeechToTextSession(token)); + const session = disposables.add(this.speechService.createSpeechToTextSession(token, 'chat')); disposables.add(session.onDidChange(e => { switch (e.status) { case SpeechToTextStatus.Recognizing: diff --git a/src/vs/workbench/contrib/speech/browser/speechService.ts b/src/vs/workbench/contrib/speech/browser/speechService.ts index 99a2fe06e81..e190ac66d36 100644 --- a/src/vs/workbench/contrib/speech/browser/speechService.ts +++ b/src/vs/workbench/contrib/speech/browser/speechService.ts @@ -12,6 +12,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { DeferredPromise } from 'vs/base/common/async'; import { ISpeechService, ISpeechProvider, HasSpeechProvider, ISpeechToTextSession, SpeechToTextInProgress, IKeywordRecognitionSession, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export class SpeechService extends Disposable implements ISpeechService { @@ -32,7 +33,8 @@ export class SpeechService extends Disposable implements ISpeechService { constructor( @ILogService private readonly logService: ILogService, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IHostService private readonly hostService: IHostService + @IHostService private readonly hostService: IHostService, + @ITelemetryService private readonly telemetryService: ITelemetryService ) { super(); } @@ -68,7 +70,7 @@ export class SpeechService extends Disposable implements ISpeechService { private readonly speechToTextInProgress = SpeechToTextInProgress.bindTo(this.contextKeyService); - createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { + createSpeechToTextSession(token: CancellationToken, context: string = 'speech'): ISpeechToTextSession { const provider = firstOrDefault(Array.from(this.providers.values())); if (!provider) { throw new Error(`No Speech provider is registered.`); @@ -78,6 +80,9 @@ export class SpeechService extends Disposable implements ISpeechService { const session = this._activeSpeechToTextSession = provider.createSpeechToTextSession(token); + const sessionStart = Date.now(); + let sessionRecognized = false; + const disposables = new DisposableStore(); const onSessionStoppedOrCanceled = () => { @@ -85,6 +90,24 @@ export class SpeechService extends Disposable implements ISpeechService { this._activeSpeechToTextSession = undefined; this.speechToTextInProgress.reset(); this._onDidEndSpeechToTextSession.fire(); + + type SpeechToTextSessionClassification = { + owner: 'bpasero'; + comment: 'An event that fires when a speech to text session is created'; + context: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Context of the session.' }; + duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Duration of the session.' }; + recognized: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'If speech was recognized.' }; + }; + type SpeechToTextSessionEvent = { + context: string; + duration: number; + recognized: boolean; + }; + this.telemetryService.publicLog2('speechToTextSession', { + context, + duration: Date.now() - sessionStart, + recognized: sessionRecognized + }); } disposables.dispose(); @@ -103,6 +126,10 @@ export class SpeechService extends Disposable implements ISpeechService { this._onDidStartSpeechToTextSession.fire(); } break; + case SpeechToTextStatus.Recognizing: + case SpeechToTextStatus.Recognized: + sessionRecognized = true; + break; case SpeechToTextStatus.Stopped: onSessionStoppedOrCanceled(); break; @@ -161,11 +188,26 @@ export class SpeechService extends Disposable implements ISpeechService { recognizeKeyword(); } + let status: KeywordRecognitionStatus; try { - return await result.p; + status = await result.p; } finally { disposables.dispose(); } + + type KeywordRecognitionClassification = { + owner: 'bpasero'; + comment: 'An event that fires when a speech keyword detection is started'; + recognized: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'If the keyword was recognized.' }; + }; + type KeywordRecognitionEvent = { + recognized: boolean; + }; + this.telemetryService.publicLog2('keywordRecognition', { + recognized: status === KeywordRecognitionStatus.Recognized + }); + + return status; } private async doRecognizeKeyword(token: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/speech/common/speechService.ts b/src/vs/workbench/contrib/speech/common/speechService.ts index d70eabdfb2d..1e6e442eaef 100644 --- a/src/vs/workbench/contrib/speech/common/speechService.ts +++ b/src/vs/workbench/contrib/speech/common/speechService.ts @@ -79,7 +79,7 @@ export interface ISpeechService { * Starts to transcribe speech from the default microphone. The returned * session object provides an event to subscribe for transcribed text. */ - createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession; + createSpeechToTextSession(token: CancellationToken, context?: string): ISpeechToTextSession; readonly onDidStartKeywordRecognition: Event; readonly onDidEndKeywordRecognition: Event; From ae7a786d1b5f11001fcae8a465577ce0fb0ccb30 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 16 Feb 2024 14:32:31 +0100 Subject: [PATCH 1358/1897] Git - do not invoke post commit commands when calling commit through the git extension api (#205364) --- extensions/git/src/api/api1.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 1a0a483409c..38aa9ec4236 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -257,7 +257,7 @@ export class ApiRepository implements Repository { } commit(message: string, opts?: CommitOptions): Promise { - return this.repository.commit(message, opts); + return this.repository.commit(message, { ...opts, postCommitCommand: null }); } merge(ref: string): Promise { From f439a7d9c53d6316ed67f0d663d78f29f824a04b Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 16 Feb 2024 14:52:45 +0100 Subject: [PATCH 1359/1897] wip - use accessible diff viewer for hunk info --- .../contrib/inlineChat/browser/inlineChat.css | 1 + .../browser/inlineChatController.ts | 10 +- .../browser/inlineChatStrategies.ts | 11 +- .../inlineChat/browser/inlineChatWidget.ts | 125 +++++++++++++++++- 4 files changed, 134 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css index ee46f6df25e..7d299bbc1e5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css @@ -307,6 +307,7 @@ padding-top: 6px; } +.monaco-editor .inline-chat .diff-review.hidden, .monaco-editor .inline-chat .previewDiff.hidden, .monaco-editor .inline-chat .previewCreate.hidden, .monaco-editor .inline-chat .previewCreateTitle.hidden { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index ce432473cfb..39b4563509b 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -27,7 +27,6 @@ import { ProviderResult, TextEdit } from 'vs/editor/common/languages'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; import { localize } from 'vs/nls'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -145,7 +144,6 @@ export class InlineChatController implements IEditorContribution { @IConfigurationService private readonly _configurationService: IConfigurationService, @IDialogService private readonly _dialogService: IDialogService, @IContextKeyService contextKeyService: IContextKeyService, - @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IBulkEditService private readonly _bulkEditService: IBulkEditService, @@ -235,13 +233,7 @@ export class InlineChatController implements IEditorContribution { } private _getMode(): EditMode { - const editMode = this._configurationService.inspect(InlineChatConfigKeys.Mode); - let editModeValue = editMode.value; - if (this._accessibilityService.isScreenReaderOptimized() && editModeValue === editMode.defaultValue) { - // By default, use preview mode for screen reader users - editModeValue = EditMode.Preview; - } - return editModeValue!; + return this._configurationService.getValue(InlineChatConfigKeys.Mode); } getWidgetPosition(): Position | undefined { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 78cfd2a3bc5..9659c4cdb28 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -36,6 +36,7 @@ import { HunkState } from './inlineChatSession'; import { assertType } from 'vs/base/common/types'; import { IModelService } from 'vs/editor/common/services/model'; import { performAsyncTextEdit, asProgressiveEdit } from './utils'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; export interface IEditObserver { start(): void; @@ -404,6 +405,8 @@ type HunkDisplayData = { toggleDiff?: () => any; remove(): void; move: (next: boolean) => void; + + hunk: HunkInformation; }; @@ -441,6 +444,7 @@ export class LiveStrategy extends EditModeStrategy { zone: InlineChatZoneWidget, @IContextKeyService contextKeyService: IContextKeyService, @IEditorWorkerService protected readonly _editorWorkerService: IEditorWorkerService, + @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IInstantiationService protected readonly _instaService: IInstantiationService, ) { super(session, editor, zone); @@ -652,6 +656,7 @@ export class LiveStrategy extends EditModeStrategy { : zoneLineNumber - hunkRanges[0].endLineNumber; data = { + hunk: hunkData, decorationIds, viewZoneId: '', viewZone: viewZoneData, @@ -661,7 +666,7 @@ export class LiveStrategy extends EditModeStrategy { discardHunk, toggleDiff: !hunkData.isInsertion() ? toggleDiff : undefined, remove, - move + move, }; this._hunkDisplayData.set(hunkData, data); @@ -700,6 +705,10 @@ export class LiveStrategy extends EditModeStrategy { const remainingHunks = this._session.hunkData.pending; this._updateSummaryMessage(remainingHunks); + if (this._accessibilityService.isScreenReaderOptimized()) { + this._zone.widget.showAccessibleHunk(this._session, widgetData.hunk); + } + this._ctxCurrentChangeHasDiff.set(Boolean(widgetData.toggleDiff)); this.toggleDiff = widgetData.toggleDiff; this.acceptHunk = async () => widgetData!.acceptHunk(); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index ca909ba98e0..ba003be69a7 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -12,6 +12,7 @@ import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { Lazy } from 'vs/base/common/lazy'; import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ISettableObservable, constObservable, derived, observableValue } from 'vs/base/common/observable'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./inlineChat'; @@ -19,11 +20,13 @@ import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfi import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer'; import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; -import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; +import { EditorLayoutInfo, EditorOption, IComputedEditorOptions } from 'vs/editor/common/config/editorOptions'; import { LineRange } from 'vs/editor/common/core/lineRange'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; +import { DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; import { LanguageSelector } from 'vs/editor/common/languageSelector'; import { CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages'; @@ -58,7 +61,7 @@ import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/cha import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; -import { ExpansionState, HunkData } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { ExpansionState, HunkData, HunkInformation, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_VISIBLE, IInlineChatFollowup, IInlineChatSlashCommand, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_FEEDBACK, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; @@ -182,6 +185,7 @@ export class InlineChatWidget { h('div.label.status.hidden@statusLabel'), h('div.actions.hidden@feedbackToolbar'), ]), + h('div.accessibleViewer@accessibleViewer') ] ); @@ -204,6 +208,8 @@ export class InlineChatWidget { private readonly _previewDiffEditor: Lazy; private readonly _previewDiffModel = this._store.add(new MutableDisposable()); + private readonly _accessibleViewer = this._store.add(new MutableDisposable()); + private readonly _previewCreateTitle: ResourceLabel; private readonly _previewCreateEditor: Lazy; private readonly _previewCreateDispoable = this._store.add(new MutableDisposable()); @@ -467,6 +473,9 @@ export class InlineChatWidget { layout(_dim: Dimension) { this._isLayouting = true; try { + if (this._accessibleViewer.value) { + this._accessibleViewer.value.width = _dim.width - 12; + } const widgetToolbarWidth = getTotalWidth(this._elements.widgetToolbar); const editorToolbarWidth = getTotalWidth(this._elements.editorToolbar) + 8 /* L/R-padding */; const innerEditorWidth = _dim.width - editorToolbarWidth - widgetToolbarWidth; @@ -489,6 +498,7 @@ export class InlineChatWidget { this._elements.previewCreate.style.height = `${previewCreateDim.height}px`; } + const lineHeight = this.parentEditor.getOption(EditorOption.lineHeight); const editorHeight = this.parentEditor.getLayoutInfo().height; const editorHeightInLines = Math.floor(editorHeight / lineHeight); @@ -510,7 +520,8 @@ export class InlineChatWidget { const previewDiffHeight = this._previewDiffEditor.hasValue && this._previewDiffEditor.value.getModel() ? 12 + Math.min(300, Math.max(0, this._previewDiffEditor.value.getContentHeight())) : 0; const previewCreateTitleHeight = getTotalHeight(this._elements.previewCreateTitle); const previewCreateHeight = this._previewCreateEditor.hasValue && this._previewCreateEditor.value.getModel() ? 18 + Math.min(300, Math.max(0, this._previewCreateEditor.value.getContentHeight())) : 0; - return base + editorHeight + detectedIntentHeight + followUpsHeight + chatResponseHeight + previewDiffHeight + previewCreateTitleHeight + previewCreateHeight + 18 /* padding */ + 8 /*shadow*/; + const accessibleViewHeight = this._accessibleViewer.value?.height ?? 0; + return base + editorHeight + detectedIntentHeight + followUpsHeight + chatResponseHeight + previewDiffHeight + previewCreateTitleHeight + previewCreateHeight + accessibleViewHeight + 18 /* padding */ + 8 /*shadow*/; } updateProgress(show: boolean) { @@ -735,6 +746,10 @@ export class InlineChatWidget { this.updateInfo(''); this.hideCreatePreview(); this.hideEditsPreview(); + + this._accessibleViewer.clear(); + this._elements.accessibleViewer.classList.toggle('hidden', true); + this._onDidChangeHeight.fire(); } @@ -908,6 +923,25 @@ export class InlineChatWidget { this._slashCommands.add(this._inputEditor.onDidChangeModelContent(updateSlashDecorations)); updateSlashDecorations(); } + + + // --- accessible viewer + + showAccessibleHunk(session: Session, hunkData: HunkInformation): void { + + this._elements.accessibleViewer.classList.remove('hidden'); + this._accessibleViewer.clear(); + + this._accessibleViewer.value = this._instantiationService.createInstance(HunkAccessibleDiffViewer, + this._elements.accessibleViewer, + session, + hunkData, + new AccessibleHunk(this.parentEditor, session, hunkData) + ); + + this._onDidChangeHeight.fire(); + + } } export class InlineChatZoneWidget extends ZoneWidget { @@ -1063,3 +1097,88 @@ export class InlineChatZoneWidget extends ZoneWidget { aria.status(localize('inlineChatClosed', 'Closed inline chat widget')); } } + +class HunkAccessibleDiffViewer extends AccessibleDiffViewer { + + readonly height: number; + + set width(value: number) { + this._width2.set(value, undefined); + } + + private readonly _width2: ISettableObservable; + + constructor( + parentNode: HTMLElement, + session: Session, + hunk: HunkInformation, + models: IAccessibleDiffViewerModel, + @IInstantiationService instantiationService: IInstantiationService, + ) { + const width = observableValue('width', 0); + const diff = observableValue('diff', HunkAccessibleDiffViewer._asMapping(hunk)); + const diffs = derived(r => [diff.read(r)]); + const lines = Math.min(6, 3 * diff.get().changedLineCount); + const height = models.getModifiedOptions().get(EditorOption.lineHeight) * lines; + + super(parentNode, constObservable(true), () => { }, constObservable(false), width, constObservable(height), diffs, models, instantiationService); + + this.height = height; + this._width2 = width; + + this._store.add(session.textModelN.onDidChangeContent(() => { + diff.set(HunkAccessibleDiffViewer._asMapping(hunk), undefined); + })); + } + + private static _asMapping(hunk: HunkInformation): DetailedLineRangeMapping { + const ranges0 = hunk.getRanges0(); + const rangesN = hunk.getRangesN(); + const originalLineRange = LineRange.fromRangeInclusive(ranges0[0]); + const modifiedLineRange = LineRange.fromRangeInclusive(rangesN[0]); + const innerChanges: RangeMapping[] = []; + for (let i = 1; i < ranges0.length; i++) { + innerChanges.push(new RangeMapping(ranges0[i], rangesN[i])); + } + return new DetailedLineRangeMapping(originalLineRange, modifiedLineRange, innerChanges); + } + +} + +class AccessibleHunk implements IAccessibleDiffViewerModel { + + constructor( + private readonly _editor: ICodeEditor, + private readonly _session: Session, + private readonly _hunk: HunkInformation + ) { } + + getOriginalModel(): ITextModel { + return this._session.textModel0; + } + getModifiedModel(): ITextModel { + return this._session.textModelN; + } + getOriginalOptions(): IComputedEditorOptions { + return this._editor.getOptions(); + } + getModifiedOptions(): IComputedEditorOptions { + return this._editor.getOptions(); + } + originalReveal(range: Range): void { + // throw new Error('Method not implemented.'); + } + modifiedReveal(range?: Range | undefined): void { + this._editor.revealRangeInCenterIfOutsideViewport(range || this._hunk.getRangesN()[0], ScrollType.Smooth); + } + modifiedSetSelection(range: Range): void { + // this._editor.revealRangeInCenterIfOutsideViewport(range, ScrollType.Smooth); + // this._editor.setSelection(range); + } + modifiedFocus(): void { + this._editor.focus(); + } + getModifiedPosition(): Position | undefined { + return this._hunk.getRangesN()[0].getStartPosition(); + } +} From 3fdb0fa6e0e8c8b8443123e37caa8666d538f024 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 16 Feb 2024 14:54:39 +0100 Subject: [PATCH 1360/1897] Refactor hover delegate assignments (#205366) :lipstick: --- src/vs/base/browser/ui/actionbar/actionViewItems.ts | 1 - src/vs/platform/quickinput/browser/quickInputService.ts | 2 +- src/vs/workbench/contrib/remote/browser/tunnelView.ts | 2 +- src/vs/workbench/contrib/timeline/browser/timelinePane.ts | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index 804b0c386ac..a714411e3a2 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -226,7 +226,6 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { const title = this.getTooltip() ?? ''; this.updateAriaLabel(); - this.element.title = ''; if (!this.customHover) { const hoverDelegate = this.options.hoverDelegate ?? getDefaultHoverDelegate('element'); this.customHover = setupCustomHover(hoverDelegate, this.element, title); diff --git a/src/vs/platform/quickinput/browser/quickInputService.ts b/src/vs/platform/quickinput/browser/quickInputService.ts index c2d178c4c46..2974eed8077 100644 --- a/src/vs/platform/quickinput/browser/quickInputService.ts +++ b/src/vs/platform/quickinput/browser/quickInputService.ts @@ -90,7 +90,7 @@ export class QuickInputService extends Themable implements IQuickInputService { options: IWorkbenchListOptions ) => this.instantiationService.createInstance(WorkbenchList, user, container, delegate, renderers, options) as List, styles: this.computeStyles(), - hoverDelegate: this.instantiationService.createInstance(QuickInputHoverDelegate) + hoverDelegate: this._register(this.instantiationService.createInstance(QuickInputHoverDelegate)) }; const controller = this._register(new QuickInputController({ diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 3559f22390e..102de6b52f8 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -355,7 +355,7 @@ class ActionBarRenderer extends Disposable implements ITableRenderer Date: Fri, 16 Feb 2024 15:06:36 +0100 Subject: [PATCH 1361/1897] Git - toggle diagnostics when git.experimental.inputValidation setting changes (#205355) --- extensions/git/src/diagnostics.ts | 23 ++++++++++++++--------- extensions/git/src/main.ts | 2 +- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/extensions/git/src/diagnostics.ts b/extensions/git/src/diagnostics.ts index aa09fbe61e8..3d5cba205c5 100644 --- a/extensions/git/src/diagnostics.ts +++ b/extensions/git/src/diagnostics.ts @@ -5,6 +5,7 @@ import { CodeAction, CodeActionKind, CodeActionProvider, Diagnostic, DiagnosticCollection, DiagnosticSeverity, Disposable, Range, Selection, TextDocument, Uri, WorkspaceEdit, l10n, languages, workspace } from 'vscode'; import { mapEvent, filterEvent, dispose } from './util'; +import { Model } from './model'; export enum DiagnosticCodes { empty_message = 'empty_message', @@ -17,37 +18,41 @@ export class GitCommitInputBoxDiagnosticsManager { private readonly severity = DiagnosticSeverity.Warning; private readonly disposables: Disposable[] = []; - constructor() { + constructor(private readonly model: Model) { this.diagnostics = languages.createDiagnosticCollection(); - mapEvent(filterEvent(workspace.onDidChangeTextDocument, e => e.document.uri.scheme === 'vscode-scm'), e => e.document)(this.validateTextDocument, this, this.disposables); + + mapEvent(filterEvent(workspace.onDidChangeTextDocument, e => e.document.uri.scheme === 'vscode-scm'), e => e.document)(this.onDidChangeTextDocument, this, this.disposables); + filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.experimental.inputValidation'))(this.onDidChangeConfiguration, this, this.disposables); } public getDiagnostics(uri: Uri): ReadonlyArray { return this.diagnostics.get(uri) ?? []; } - private validateTextDocument(document: TextDocument): void { - this.diagnostics.delete(document.uri); + private onDidChangeConfiguration(): void { + for (const repository of this.model.repositories) { + this.onDidChangeTextDocument(repository.inputBox.document); + } + } + private onDidChangeTextDocument(document: TextDocument): void { const config = workspace.getConfiguration('git'); const inputValidation = config.get('experimental.inputValidation', false) === true; if (!inputValidation) { + this.diagnostics.set(document.uri, undefined); return; } - const diagnostics: Diagnostic[] = []; - if (/^\s+$/.test(document.getText())) { const documentRange = new Range(document.lineAt(0).range.start, document.lineAt(document.lineCount - 1).range.end); const diagnostic = new Diagnostic(documentRange, l10n.t('Current commit message only contains whitespace characters'), this.severity); diagnostic.code = DiagnosticCodes.empty_message; - diagnostics.push(diagnostic); - this.diagnostics.set(document.uri, diagnostics); - + this.diagnostics.set(document.uri, [diagnostic]); return; } + const diagnostics: Diagnostic[] = []; const inputValidationLength = config.get('inputValidationLength', 50); const inputValidationSubjectLength = config.get('inputValidationSubjectLength', undefined); diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index c40aedaec69..c2d9b974be7 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -119,7 +119,7 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, const postCommitCommandsProvider = new GitPostCommitCommandsProvider(); model.registerPostCommitCommandsProvider(postCommitCommandsProvider); - const diagnosticsManager = new GitCommitInputBoxDiagnosticsManager(); + const diagnosticsManager = new GitCommitInputBoxDiagnosticsManager(model); disposables.push(diagnosticsManager); const codeActionsProvider = new GitCommitInputBoxCodeActionsProvider(diagnosticsManager); From 03510273fc85ff1d3b9e4f6321d1658f517c3d8c Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 16 Feb 2024 15:14:45 +0100 Subject: [PATCH 1362/1897] Less hovers with pointers (#205368) less hovers with pointers --- src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts | 2 +- src/vs/workbench/browser/parts/editor/editorTabsControl.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 2c88a644bd5..a3b752ff1f2 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -230,7 +230,7 @@ export class BreadcrumbsControl { this._ckBreadcrumbsVisible = BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo(this._contextKeyService); this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService); - this._hoverDelegate = getDefaultHoverDelegate('element'); + this._hoverDelegate = getDefaultHoverDelegate('mouse'); this._disposables.add(breadcrumbsService.register(this._editorGroup.id, this._widget)); this.hide(); diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index 782ad9b007d..218c8808205 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -164,7 +164,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC this.renderDropdownAsChildElement = false; - this.tabsHoverDelegate = getDefaultHoverDelegate('element'); + this.tabsHoverDelegate = getDefaultHoverDelegate('mouse'); this.create(parent); } From 559a3413bc85864e22f29df1fb7a207d5aba7590 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 08:57:13 -0600 Subject: [PATCH 1363/1897] add support for issue reporting when terminal agent supports that --- .../terminal/common/terminalContextKey.ts | 4 +++ .../chat/browser/terminalChatActions.ts | 36 +++++++++---------- .../chat/browser/terminalChatController.ts | 24 +++++++++---- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index dcca7e64e68..d823a5b7fd9 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -46,6 +46,7 @@ export const enum TerminalContextKeyStrings { ChatAgentRegistered = 'terminalChatAgentRegistered', ChatResponseEditorFocused = 'terminalChatResponseEditorFocused', ChatResponseType = 'terminalChatResponseType', + ChatResponseSupportsIssueReporting = 'terminalChatResponseSupportsIssueReporting' } export const enum TerminalChatResponseTypes { @@ -192,4 +193,7 @@ export namespace TerminalContextKeys { /** The type of chat response, if any */ export const chatResponseType = new RawContextKey(TerminalContextKeyStrings.ChatResponseType, undefined, localize('chatResponseTypeContextKey', "The type of chat response, if any")); + + /** Whether the response supports issue reporting */ + export const chatResponseSupportsIssueReporting = new RawContextKey(TerminalContextKeyStrings.ChatResponseSupportsIssueReporting, false, localize('chatResponseSupportsIssueReportingContextKey', "Whether the response supports issue reporting")); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 187d16f6d51..d1586f8c4ba 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -228,7 +228,6 @@ registerActiveXtermAction({ ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatResponseType.notEqualsTo(undefined) ), - // TODO: toggled: CTX_INLINE_CHAT_LAST_FEEDBACK.isEqualTo('helpful'), icon: Codicon.thumbsup, menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, @@ -252,7 +251,6 @@ registerActiveXtermAction({ ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatResponseType.notEqualsTo(undefined), ), - // TODO: toggled: CTX_INLINE_CHAT_LAST_FEEDBACK.isEqualTo('unhelpful'), icon: Codicon.thumbsdown, menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, @@ -274,27 +272,29 @@ registerActiveXtermAction({ title: localize2('reportIssue', 'Report Issue'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive, + TerminalContextKeys.chatRequestActive.negate(), + TerminalContextKeys.chatResponseType.notEqualsTo(undefined), + TerminalContextKeys.chatResponseSupportsIssueReporting ), - // TODO: precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), icon: Codicon.report, - menu: [/*{ - // TODO: Enable this + menu: [{ id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, - when: ContextKeyExpr.and(CTX_TERMINAL_CHAT_SUPPORT_ISSUE_REPORTING, CTX_TERMINAL_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), - group: '2_feedback', + when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.notEqualsTo(undefined), TerminalContextKeys.chatResponseSupportsIssueReporting), + group: 'inline', order: 3 - }, */{ - id: MENU_TERMINAL_CHAT_WIDGET, - group: 'config', - order: 3 - }], + }], + // { + // id: MENU_TERMINAL_CHAT_WIDGET, + // when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.notEqualsTo(undefined), TerminalContextKeys.chatResponseSupportsIssueReporting), + // group: 'config', + // order: 3 + // }], run: (_xterm, _accessor, activeInstance) => { - // if (isDetachedTerminalInstance(activeInstance)) { - // return; - // } - // const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - // contr?.acceptFeedback(true); + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.acceptFeedback(); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index f8e97671171..996effa0743 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -17,7 +17,7 @@ import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatAgentService, IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatService, IChatProgress, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatService, IChatProgress, InteractiveSessionVoteDirection, ChatUserAction } from 'vs/workbench/contrib/chat/common/chatService'; import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -55,6 +55,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _requestActiveContextKey!: IContextKey; private readonly _terminalAgentRegisteredContextKey!: IContextKey; private readonly _responseTypeContextKey!: IContextKey; + private readonly __responseSupportsIssueReportingContextKey!: IContextKey; + private _requestId: number = 0; private _messages = this._store.add(new Emitter()); @@ -96,6 +98,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._requestActiveContextKey = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); this._terminalAgentRegisteredContextKey = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); this._responseTypeContextKey = TerminalContextKeys.chatResponseType.bindTo(this._contextKeyService); + this.__responseSupportsIssueReportingContextKey = TerminalContextKeys.chatResponseSupportsIssueReporting.bindTo(this._contextKeyService); if (!this._chatAgentService.hasAgent(this._terminalAgentId)) { this._register(this._chatAgentService.onDidChangeAgents(() => { @@ -133,11 +136,17 @@ export class TerminalChatController extends Disposable implements ITerminalContr }); } - acceptFeedback(helpful: boolean): void { + acceptFeedback(helpful?: boolean): void { const providerId = this._chatService.getProviderInfos()?.[0]?.id; if (!providerId || !this._currentRequest || !this._model) { return; } + let action: ChatUserAction; + if (helpful === undefined) { + action = { kind: 'bug' }; + } else { + action = { kind: 'vote', direction: helpful ? InteractiveSessionVoteDirection.Up : InteractiveSessionVoteDirection.Down }; + } // TODO:extract into helper method for (const request of this._model.getRequests()) { if (request.response?.response.value || request.response?.result) { @@ -147,10 +156,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr requestId: request.id, agentId: request.response?.agent?.id, result: request.response?.result, - action: { - kind: 'vote', - direction: helpful ? InteractiveSessionVoteDirection.Up : InteractiveSessionVoteDirection.Down - }, + action }); } } @@ -267,8 +273,12 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (this._currentRequest) { this._model?.completeResponse(this._currentRequest); } + const supportIssueReporting = this._currentRequest?.response?.agent?.metadata?.supportIssueReporting; + if (supportIssueReporting !== undefined) { + this.__responseSupportsIssueReportingContextKey.set(supportIssueReporting); + } + this._lastResponseContent = responseContent; } - this._lastResponseContent = responseContent; const firstCodeBlockContent = marked.lexer(responseContent).filter(token => token.type === 'code')?.[0]?.raw; const regex = /```(?\w+)\n(?[\s\S]*?)```/g; const match = regex.exec(firstCodeBlockContent); From 705d61ad975a564f0b7b30d6c3e12fefda85dfb3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 09:02:11 -0600 Subject: [PATCH 1364/1897] rm todo --- .../terminalContrib/chat/browser/terminalChatController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 996effa0743..70ee41baa68 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -66,7 +66,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr private _lastInput: string | undefined; private _lastResponseContent: string | undefined; get lastResponseContent(): string | undefined { - // TODO: use model return this._lastResponseContent; } From 45ccdfa1af4e923edd954c9dfa0b131b91511ff5 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 16 Feb 2024 16:14:41 +0100 Subject: [PATCH 1365/1897] Revert "Tunnel: Extend port mapping lookup also for querystring (#203908)" (#205370) This reverts commit 08eda834f933e05d5c3bac64b147da8e2cffcb5a. --- src/vs/platform/tunnel/common/tunnel.ts | 25 +++-------- .../tunnel/test/common/tunnel.test.ts | 44 ------------------- .../webview/common/webviewPortMapping.ts | 2 +- 3 files changed, 7 insertions(+), 64 deletions(-) delete mode 100644 src/vs/platform/tunnel/test/common/tunnel.test.ts diff --git a/src/vs/platform/tunnel/common/tunnel.ts b/src/vs/platform/tunnel/common/tunnel.ts index bff2feb7576..62c9059d2f8 100644 --- a/src/vs/platform/tunnel/common/tunnel.ts +++ b/src/vs/platform/tunnel/common/tunnel.ts @@ -141,31 +141,18 @@ export interface ITunnelService { isPortPrivileged(port: number): boolean; } -export function extractLocalHostUriMetaDataForPortMapping(uri: URI, { checkQuery = true } = {}): { address: string; port: number } | undefined { +export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string; port: number } | undefined { if (uri.scheme !== 'http' && uri.scheme !== 'https') { return undefined; } const localhostMatch = /^(localhost|127\.0\.0\.1|0\.0\.0\.0):(\d+)$/.exec(uri.authority); - if (localhostMatch) { - return { - address: localhostMatch[1], - port: +localhostMatch[2], - }; - } - if (!uri.query || !checkQuery) { + if (!localhostMatch) { return undefined; } - const keyvalues = uri.query.split('&'); - for (const keyvalue of keyvalues) { - const value = keyvalue.split('=')[1]; - if (/^https?:/.exec(value)) { - const result = extractLocalHostUriMetaDataForPortMapping(URI.parse(value)); - if (result) { - return result; - } - } - } - return undefined; + return { + address: localhostMatch[1], + port: +localhostMatch[2], + }; } export const LOCALHOST_ADDRESSES = ['localhost', '127.0.0.1', '0:0:0:0:0:0:0:1', '::1']; diff --git a/src/vs/platform/tunnel/test/common/tunnel.test.ts b/src/vs/platform/tunnel/test/common/tunnel.test.ts deleted file mode 100644 index 4c763765aa0..00000000000 --- a/src/vs/platform/tunnel/test/common/tunnel.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; -import { URI } from 'vs/base/common/uri'; -import { extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/tunnel/common/tunnel'; -import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; - - -suite('Tunnel', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - function portMappingDoTest(res: { address: string; port: number } | undefined, expectedAddress?: string, expectedPort?: number) { - assert.strictEqual(!expectedAddress, !res); - assert.strictEqual(res?.address, expectedAddress); - assert.strictEqual(res?.port, expectedPort); - } - - function portMappingTest(uri: string, expectedAddress?: string, expectedPort?: number) { - for (const checkQuery of [true, false]) { - portMappingDoTest(extractLocalHostUriMetaDataForPortMapping(URI.parse(uri), { checkQuery }), expectedAddress, expectedPort); - } - } - - function portMappingTestQuery(uri: string, expectedAddress?: string, expectedPort?: number) { - portMappingDoTest(extractLocalHostUriMetaDataForPortMapping(URI.parse(uri)), expectedAddress, expectedPort); - portMappingDoTest(extractLocalHostUriMetaDataForPortMapping(URI.parse(uri), { checkQuery: false }), undefined, undefined); - } - - test('portMapping', () => { - portMappingTest('file:///foo.bar/baz'); - portMappingTest('http://foo.bar:1234'); - portMappingTest('http://localhost:8080', 'localhost', 8080); - portMappingTest('https://localhost:443', 'localhost', 443); - portMappingTest('http://127.0.0.1:3456', '127.0.0.1', 3456); - portMappingTest('http://0.0.0.0:7654', '0.0.0.0', 7654); - portMappingTest('http://localhost:8080/path?foo=bar', 'localhost', 8080); - portMappingTest('http://localhost:8080/path?foo=http%3A%2F%2Flocalhost%3A8081', 'localhost', 8080); - portMappingTestQuery('http://foo.bar/path?url=http%3A%2F%2Flocalhost%3A8081', 'localhost', 8081); - portMappingTestQuery('http://foo.bar/path?url=http%3A%2F%2Flocalhost%3A8081&url2=http%3A%2F%2Flocalhost%3A8082', 'localhost', 8081); - portMappingTestQuery('http://foo.bar/path?url=http%3A%2F%2Fmicrosoft.com%2Fbad&url2=http%3A%2F%2Flocalhost%3A8081', 'localhost', 8081); - }); -}); diff --git a/src/vs/platform/webview/common/webviewPortMapping.ts b/src/vs/platform/webview/common/webviewPortMapping.ts index 1ebc8534a28..b47339d28fa 100644 --- a/src/vs/platform/webview/common/webviewPortMapping.ts +++ b/src/vs/platform/webview/common/webviewPortMapping.ts @@ -29,7 +29,7 @@ export class WebviewPortMappingManager implements IDisposable { public async getRedirect(resolveAuthority: IAddress | null | undefined, url: string): Promise { const uri = URI.parse(url); - const requestLocalHostInfo = extractLocalHostUriMetaDataForPortMapping(uri, { checkQuery: false }); + const requestLocalHostInfo = extractLocalHostUriMetaDataForPortMapping(uri); if (!requestLocalHostInfo) { return undefined; } From ad35b733520004674f14684fcbe1231afd65a5e0 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 16 Feb 2024 16:16:05 +0100 Subject: [PATCH 1366/1897] Inline Edits (#204158) * implementation of inline edits --------- Co-authored-by: Krzysztof Cieslak Co-authored-by: Andrew Rice --- src/vs/editor/common/config/editorOptions.ts | 87 +++++ src/vs/editor/common/languages.ts | 21 ++ .../common/services/languageFeatures.ts | 4 +- .../services/languageFeaturesService.ts | 3 +- .../common/standalone/standaloneEnums.ts | 176 ++++----- .../browser/ghostTextWidget.ts | 6 +- .../contrib/inlineEdit/browser/commandIds.ts | 10 + .../contrib/inlineEdit/browser/commands.ts | 151 ++++++++ .../inlineEdit/browser/ghostTextWidget.ts | 231 ++++++++++++ .../inlineEdit/browser/hoverParticipant.ts | 105 ++++++ .../browser/inlineEdit.contribution.ts | 20 + .../contrib/inlineEdit/browser/inlineEdit.css | 38 ++ .../browser/inlineEditController.ts | 344 ++++++++++++++++++ .../browser/inlineEditHintsWidget.css | 34 ++ .../browser/inlineEditHintsWidget.ts | 246 +++++++++++++ src/vs/editor/editor.all.ts | 1 + .../standalone/browser/standaloneLanguages.ts | 7 + src/vs/monaco.d.ts | 213 ++++++----- src/vs/platform/actions/common/actions.ts | 2 + .../api/browser/mainThreadLanguageFeatures.ts | 15 +- .../workbench/api/common/extHost.api.impl.ts | 6 + .../workbench/api/common/extHost.protocol.ts | 7 + .../api/common/extHostLanguageFeatures.ts | 96 ++++- src/vs/workbench/api/common/extHostTypes.ts | 16 + .../actions/common/menusExtensionPoint.ts | 7 + .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.inlineEdit.d.ts | 86 +++++ 27 files changed, 1755 insertions(+), 178 deletions(-) create mode 100644 src/vs/editor/contrib/inlineEdit/browser/commandIds.ts create mode 100644 src/vs/editor/contrib/inlineEdit/browser/commands.ts create mode 100644 src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts create mode 100644 src/vs/editor/contrib/inlineEdit/browser/hoverParticipant.ts create mode 100644 src/vs/editor/contrib/inlineEdit/browser/inlineEdit.contribution.ts create mode 100644 src/vs/editor/contrib/inlineEdit/browser/inlineEdit.css create mode 100644 src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts create mode 100644 src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.css create mode 100644 src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.ts create mode 100644 src/vscode-dts/vscode.proposed.inlineEdit.d.ts diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index c0225ebc0b8..d06e32cef09 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -428,6 +428,7 @@ export interface IEditorOptions { */ suggest?: ISuggestOptions; inlineSuggest?: IInlineSuggestOptions; + experimentalInlineEdit?: IInlineEditOptions; /** * Smart select options. */ @@ -4072,6 +4073,90 @@ class InlineEditorSuggest extends BaseEditorOption>; + +class InlineEditorEdit extends BaseEditorOption { + constructor() { + const defaults: InternalInlineEditOptions = { + enabled: false, + showToolbar: 'onHover', + fontFamily: 'default', + keepOnBlur: false, + backgroundColoring: false, + }; + + super( + EditorOption.inlineEdit, 'experimentalInlineEdit', defaults, + { + 'editor.experimentalInlineEdit.enabled': { + type: 'boolean', + default: defaults.enabled, + description: nls.localize('inlineEdit.enabled', "Controls whether to show inline edits in the editor.") + }, + 'editor.experimentalInlineEdit.showToolbar': { + type: 'string', + default: defaults.showToolbar, + enum: ['always', 'onHover', 'never'], + enumDescriptions: [ + nls.localize('inlineEdit.showToolbar.always', "Show the inline edit toolbar whenever an inline suggestion is shown."), + nls.localize('inlineEdit.showToolbar.onHover', "Show the inline edit toolbar when hovering over an inline suggestion."), + nls.localize('inlineEdit.showToolbar.never', "Never show the inline edit toolbar."), + ], + description: nls.localize('inlineEdit.showToolbar', "Controls when to show the inline edit toolbar."), + }, + 'editor.experimentalInlineEdit.fontFamily': { + type: 'string', + default: defaults.fontFamily, + description: nls.localize('inlineEdit.fontFamily', "Controls the font family of the inline edit.") + }, + 'editor.experimentalInlineEdit.backgroundColoring': { + type: 'boolean', + default: defaults.backgroundColoring, + description: nls.localize('inlineEdit.backgroundColoring', "Controls whether to color the background of inline edits.") + }, + } + ); + } + + public validate(_input: any): InternalInlineEditOptions { + if (!_input || typeof _input !== 'object') { + return this.defaultValue; + } + const input = _input as IInlineEditOptions; + return { + enabled: boolean(input.enabled, this.defaultValue.enabled), + showToolbar: stringSet(input.showToolbar, this.defaultValue.showToolbar, ['always', 'onHover', 'never']), + fontFamily: EditorStringOption.string(input.fontFamily, this.defaultValue.fontFamily), + keepOnBlur: boolean(input.keepOnBlur, this.defaultValue.keepOnBlur), + backgroundColoring: boolean(input.backgroundColoring, this.defaultValue.backgroundColoring) + }; + } +} + //#region bracketPairColorization export interface IBracketPairColorizationOptions { @@ -5132,6 +5217,7 @@ export const enum EditorOption { hover, inDiffEditor, inlineSuggest, + inlineEdit, letterSpacing, lightbulb, lineDecorationsWidth, @@ -5835,6 +5921,7 @@ export const EditorOptions = { )), suggest: register(new EditorSuggest()), inlineSuggest: register(new InlineEditorSuggest()), + inlineEdit: register(new InlineEditorEdit()), inlineCompletionsAccessibilityVerbose: register(new EditorBooleanOption(EditorOption.inlineCompletionsAccessibilityVerbose, 'inlineCompletionsAccessibilityVerbose', false, { description: nls.localize('inlineCompletionsAccessibilityVerbose', "Controls whether the accessibility hint should be provided to screen reader users when an inline completion is shown.") })), suggestFontSize: register(new EditorIntOption( diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 252dabcbda6..16550bdef8d 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -2143,3 +2143,24 @@ export interface MappedEditsProvider { token: CancellationToken ): Promise; } + +export interface IInlineEdit { + text: string; + range: IRange; + accepted?: Command; + rejected?: Command; +} + +export interface IInlineEditContext { + triggerKind: InlineEditTriggerKind; +} + +export enum InlineEditTriggerKind { + Invoke = 0, + Automatic = 1, +} + +export interface InlineEditProvider { + provideInlineEdit(model: model.ITextModel, context: IInlineEditContext, token: CancellationToken): ProviderResult; + freeInlineEdit(edit: T): void; +} diff --git a/src/vs/editor/common/services/languageFeatures.ts b/src/vs/editor/common/services/languageFeatures.ts index 92f7e271f1a..72889bd0b7e 100644 --- a/src/vs/editor/common/services/languageFeatures.ts +++ b/src/vs/editor/common/services/languageFeatures.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { LanguageFeatureRegistry, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry'; -import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentPasteEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, MappedEditsProvider, MultiDocumentHighlightProvider, NewSymbolNamesProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; +import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentPasteEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, MappedEditsProvider, MultiDocumentHighlightProvider, NewSymbolNamesProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, InlineEditProvider } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export const ILanguageFeaturesService = createDecorator('ILanguageFeaturesService'); @@ -65,6 +65,8 @@ export interface ILanguageFeaturesService { readonly inlineCompletionsProvider: LanguageFeatureRegistry; + readonly inlineEditProvider: LanguageFeatureRegistry; + readonly completionProvider: LanguageFeatureRegistry; readonly linkedEditingRangeProvider: LanguageFeatureRegistry; diff --git a/src/vs/editor/common/services/languageFeaturesService.ts b/src/vs/editor/common/services/languageFeaturesService.ts index 4e414b34b36..920a78d7402 100644 --- a/src/vs/editor/common/services/languageFeaturesService.ts +++ b/src/vs/editor/common/services/languageFeaturesService.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { LanguageFeatureRegistry, NotebookInfo, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry'; -import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DocumentPasteEditProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, MultiDocumentHighlightProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, MappedEditsProvider, NewSymbolNamesProvider } from 'vs/editor/common/languages'; +import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DocumentPasteEditProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, MultiDocumentHighlightProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, MappedEditsProvider, NewSymbolNamesProvider, InlineEditProvider } from 'vs/editor/common/languages'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -36,6 +36,7 @@ export class LanguageFeaturesService implements ILanguageFeaturesService { readonly foldingRangeProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly linkProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly inlineCompletionsProvider = new LanguageFeatureRegistry(this._score.bind(this)); + readonly inlineEditProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly completionProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly linkedEditingRangeProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly inlineValuesProvider = new LanguageFeatureRegistry(this._score.bind(this)); diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index ed3b49ae756..26e9ad27095 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -236,91 +236,92 @@ export enum EditorOption { hover = 60, inDiffEditor = 61, inlineSuggest = 62, - letterSpacing = 63, - lightbulb = 64, - lineDecorationsWidth = 65, - lineHeight = 66, - lineNumbers = 67, - lineNumbersMinChars = 68, - linkedEditing = 69, - links = 70, - matchBrackets = 71, - minimap = 72, - mouseStyle = 73, - mouseWheelScrollSensitivity = 74, - mouseWheelZoom = 75, - multiCursorMergeOverlapping = 76, - multiCursorModifier = 77, - multiCursorPaste = 78, - multiCursorLimit = 79, - occurrencesHighlight = 80, - overviewRulerBorder = 81, - overviewRulerLanes = 82, - padding = 83, - pasteAs = 84, - parameterHints = 85, - peekWidgetDefaultFocus = 86, - definitionLinkOpensInPeek = 87, - quickSuggestions = 88, - quickSuggestionsDelay = 89, - readOnly = 90, - readOnlyMessage = 91, - renameOnType = 92, - renderControlCharacters = 93, - renderFinalNewline = 94, - renderLineHighlight = 95, - renderLineHighlightOnlyWhenFocus = 96, - renderValidationDecorations = 97, - renderWhitespace = 98, - revealHorizontalRightPadding = 99, - roundedSelection = 100, - rulers = 101, - scrollbar = 102, - scrollBeyondLastColumn = 103, - scrollBeyondLastLine = 104, - scrollPredominantAxis = 105, - selectionClipboard = 106, - selectionHighlight = 107, - selectOnLineNumbers = 108, - showFoldingControls = 109, - showUnused = 110, - snippetSuggestions = 111, - smartSelect = 112, - smoothScrolling = 113, - stickyScroll = 114, - stickyTabStops = 115, - stopRenderingLineAfter = 116, - suggest = 117, - suggestFontSize = 118, - suggestLineHeight = 119, - suggestOnTriggerCharacters = 120, - suggestSelection = 121, - tabCompletion = 122, - tabIndex = 123, - unicodeHighlighting = 124, - unusualLineTerminators = 125, - useShadowDOM = 126, - useTabStops = 127, - wordBreak = 128, - wordSeparators = 129, - wordWrap = 130, - wordWrapBreakAfterCharacters = 131, - wordWrapBreakBeforeCharacters = 132, - wordWrapColumn = 133, - wordWrapOverride1 = 134, - wordWrapOverride2 = 135, - wrappingIndent = 136, - wrappingStrategy = 137, - showDeprecated = 138, - inlayHints = 139, - editorClassName = 140, - pixelRatio = 141, - tabFocusMode = 142, - layoutInfo = 143, - wrappingInfo = 144, - defaultColorDecorators = 145, - colorDecoratorsActivatedOn = 146, - inlineCompletionsAccessibilityVerbose = 147 + inlineEdit = 63, + letterSpacing = 64, + lightbulb = 65, + lineDecorationsWidth = 66, + lineHeight = 67, + lineNumbers = 68, + lineNumbersMinChars = 69, + linkedEditing = 70, + links = 71, + matchBrackets = 72, + minimap = 73, + mouseStyle = 74, + mouseWheelScrollSensitivity = 75, + mouseWheelZoom = 76, + multiCursorMergeOverlapping = 77, + multiCursorModifier = 78, + multiCursorPaste = 79, + multiCursorLimit = 80, + occurrencesHighlight = 81, + overviewRulerBorder = 82, + overviewRulerLanes = 83, + padding = 84, + pasteAs = 85, + parameterHints = 86, + peekWidgetDefaultFocus = 87, + definitionLinkOpensInPeek = 88, + quickSuggestions = 89, + quickSuggestionsDelay = 90, + readOnly = 91, + readOnlyMessage = 92, + renameOnType = 93, + renderControlCharacters = 94, + renderFinalNewline = 95, + renderLineHighlight = 96, + renderLineHighlightOnlyWhenFocus = 97, + renderValidationDecorations = 98, + renderWhitespace = 99, + revealHorizontalRightPadding = 100, + roundedSelection = 101, + rulers = 102, + scrollbar = 103, + scrollBeyondLastColumn = 104, + scrollBeyondLastLine = 105, + scrollPredominantAxis = 106, + selectionClipboard = 107, + selectionHighlight = 108, + selectOnLineNumbers = 109, + showFoldingControls = 110, + showUnused = 111, + snippetSuggestions = 112, + smartSelect = 113, + smoothScrolling = 114, + stickyScroll = 115, + stickyTabStops = 116, + stopRenderingLineAfter = 117, + suggest = 118, + suggestFontSize = 119, + suggestLineHeight = 120, + suggestOnTriggerCharacters = 121, + suggestSelection = 122, + tabCompletion = 123, + tabIndex = 124, + unicodeHighlighting = 125, + unusualLineTerminators = 126, + useShadowDOM = 127, + useTabStops = 128, + wordBreak = 129, + wordSeparators = 130, + wordWrap = 131, + wordWrapBreakAfterCharacters = 132, + wordWrapBreakBeforeCharacters = 133, + wordWrapColumn = 134, + wordWrapOverride1 = 135, + wordWrapOverride2 = 136, + wrappingIndent = 137, + wrappingStrategy = 138, + showDeprecated = 139, + inlayHints = 140, + editorClassName = 141, + pixelRatio = 142, + tabFocusMode = 143, + layoutInfo = 144, + wrappingInfo = 145, + defaultColorDecorators = 146, + colorDecoratorsActivatedOn = 147, + inlineCompletionsAccessibilityVerbose = 148 } /** @@ -415,6 +416,11 @@ export enum InlineCompletionTriggerKind { */ Explicit = 1 } + +export enum InlineEditTriggerKind { + Invoke = 0, + Automatic = 1 +} /** * Virtual Key Codes, the value does not hold any inherent meaning. * Inspired somewhat from https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx diff --git a/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts b/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts index 0cfd4f3cb44..0c93d8465ab 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts @@ -184,7 +184,7 @@ export class GhostTextWidget extends Disposable { } } -class AdditionalLinesWidget extends Disposable { +export class AdditionalLinesWidget extends Disposable { private _viewZoneId: string | undefined = undefined; public get viewZoneId(): string | undefined { return this._viewZoneId; } @@ -263,7 +263,7 @@ class AdditionalLinesWidget extends Disposable { } } -interface LineData { +export interface LineData { content: string; // Must not contain a linebreak! decorations: LineDecoration[]; } @@ -325,4 +325,4 @@ function renderLines(domNode: HTMLElement, tabSize: number, lines: LineData[], o domNode.innerHTML = trustedhtml as string; } -const ttPolicy = createTrustedTypesPolicy('editorGhostText', { createHTML: value => value }); +export const ttPolicy = createTrustedTypesPolicy('editorGhostText', { createHTML: value => value }); diff --git a/src/vs/editor/contrib/inlineEdit/browser/commandIds.ts b/src/vs/editor/contrib/inlineEdit/browser/commandIds.ts new file mode 100644 index 00000000000..ccd1b40df5a --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/commandIds.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export const inlineEditAcceptId = 'editor.action.inlineEdit.accept'; +export const inlineEditTriggerId = 'editor.action.inlineEdit.trigger'; +export const inlineEditRejectId = 'editor.action.inlineEdit.reject'; +export const inlineEditJumpToId = 'editor.action.inlineEdit.jumpTo'; +export const inlineEditJumpBackId = 'editor.action.inlineEdit.jumpBack'; diff --git a/src/vs/editor/contrib/inlineEdit/browser/commands.ts b/src/vs/editor/contrib/inlineEdit/browser/commands.ts new file mode 100644 index 00000000000..93f1f9d4a2c --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/commands.ts @@ -0,0 +1,151 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { inlineEditAcceptId, inlineEditJumpBackId, inlineEditJumpToId, inlineEditRejectId } from 'vs/editor/contrib/inlineEdit/browser/commandIds'; +import { InlineEditController } from 'vs/editor/contrib/inlineEdit/browser/inlineEditController'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; + +export class AcceptInlineEdit extends EditorAction { + constructor() { + super({ + id: inlineEditAcceptId, + label: 'Accept Inline Edit', + alias: 'Accept Inline Edit', + precondition: EditorContextKeys.writable, + kbOpts: [ + { + weight: KeybindingWeight.EditorContrib + 1, + primary: KeyCode.Tab, + kbExpr: ContextKeyExpr.and(EditorContextKeys.writable, InlineEditController.inlineEditVisibleContext, InlineEditController.cursorAtInlineEditContext) + }], + menuOpts: [{ + menuId: MenuId.InlineEditToolbar, + title: 'Accept', + group: 'primary', + order: 1, + }], + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + const controller = InlineEditController.get(editor); + controller?.accept(); + } +} + +export class TriggerInlineEdit extends EditorAction { + constructor() { + super({ + id: 'editor.action.inlineEdit.trigger', + label: 'Trigger Inline Edit', + alias: 'Trigger Inline Edit', + precondition: EditorContextKeys.writable, + kbOpts: { + weight: KeybindingWeight.EditorContrib + 1, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Equal, + kbExpr: ContextKeyExpr.and(EditorContextKeys.writable, ContextKeyExpr.not(InlineEditController.inlineEditVisibleKey)) + }, + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + const controller = InlineEditController.get(editor); + controller?.trigger(); + } +} + +export class JumpToInlineEdit extends EditorAction { + constructor() { + const activeExpr = ContextKeyExpr.and(EditorContextKeys.writable, InlineEditController.inlineEditVisibleContext, ContextKeyExpr.not(InlineEditController.cursorAtInlineEditKey)); + + super({ + id: inlineEditJumpToId, + label: 'Jump to Inline Edit', + alias: 'Jump to Inline Edit', + precondition: activeExpr, + kbOpts: { + weight: KeybindingWeight.EditorContrib + 1, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Equal, + kbExpr: activeExpr + }, + menuOpts: [{ + menuId: MenuId.InlineEditToolbar, + title: 'Jump To Edit', + group: 'primary', + order: 3, + when: activeExpr + }], + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + const controller = InlineEditController.get(editor); + controller?.jumpToCurrent(); + } +} + +export class JumpBackInlineEdit extends EditorAction { + constructor() { + const activeExpr = ContextKeyExpr.and(EditorContextKeys.writable, InlineEditController.cursorAtInlineEditContext); + + super({ + id: inlineEditJumpBackId, + label: 'Jump Back from Inline Edit', + alias: 'Jump Back from Inline Edit', + precondition: activeExpr, + kbOpts: { + weight: KeybindingWeight.EditorContrib + 10, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Equal, + kbExpr: activeExpr + }, + menuOpts: [{ + menuId: MenuId.InlineEditToolbar, + title: 'Jump Back', + group: 'primary', + order: 3, + when: activeExpr + }], + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + const controller = InlineEditController.get(editor); + controller?.jumpBack(); + } +} + +export class RejectInlineEdit extends EditorAction { + constructor() { + super({ + id: inlineEditRejectId, + label: 'Reject Inline Edit', + alias: 'Reject Inline Edit', + precondition: EditorContextKeys.writable, + kbOpts: { + weight: KeybindingWeight.EditorContrib, + primary: KeyCode.Escape, + kbExpr: ContextKeyExpr.and(EditorContextKeys.writable, InlineEditController.inlineEditVisibleContext) + }, + menuOpts: [{ + menuId: MenuId.InlineEditToolbar, + title: 'Reject', + group: 'secondary', + order: 2, + }], + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + const controller = InlineEditController.get(editor); + controller?.clear(); + } +} + diff --git a/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts b/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts new file mode 100644 index 00000000000..da0fd80b543 --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts @@ -0,0 +1,231 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IObservable, derived, observableFromEvent, observableValue } from 'vs/base/common/observable'; +import 'vs/css!./inlineEdit'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Position } from 'vs/editor/common/core/position'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { IModelDeltaDecoration, ITextModel, InjectedTextCursorStops } from 'vs/editor/common/model'; +import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; +import { InlineDecorationType } from 'vs/editor/common/viewModel'; +import { AdditionalLinesWidget, LineData } from 'vs/editor/contrib/inlineCompletions/browser/ghostTextWidget'; +import { GhostText } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; +import { ColumnRange, applyObservableDecorations } from 'vs/editor/contrib/inlineCompletions/browser/utils'; + +export const INLINE_EDIT_DESCRIPTION = 'inline-edit'; +export interface IGhostTextWidgetModel { + readonly targetTextModel: IObservable; + readonly ghostText: IObservable; + readonly minReservedLineCount: IObservable; + readonly range: IObservable; + readonly backgroundColoring: IObservable; +} + +export class GhostTextWidget extends Disposable { + private readonly isDisposed = observableValue(this, false); + private readonly currentTextModel = observableFromEvent(this.editor.onDidChangeModel, () => /** @description editor.model */ this.editor.getModel()); + + constructor( + private readonly editor: ICodeEditor, + readonly model: IGhostTextWidgetModel, + @ILanguageService private readonly languageService: ILanguageService, + ) { + super(); + + this._register(toDisposable(() => { this.isDisposed.set(true, undefined); })); + this._register(applyObservableDecorations(this.editor, this.decorations)); + } + + private readonly uiState = derived(this, reader => { + if (this.isDisposed.read(reader)) { + return undefined; + } + const textModel = this.currentTextModel.read(reader); + if (textModel !== this.model.targetTextModel.read(reader)) { + return undefined; + } + const ghostText = this.model.ghostText.read(reader); + if (!ghostText) { + return undefined; + } + + + let range = this.model.range?.read(reader); + //if range is empty, we want to remove it + if (range && range.startLineNumber === range.endLineNumber && range.startColumn === range.endColumn) { + range = undefined; + } + //check if both range and text are single line - in this case we want to do inline replacement + //rather than replacing whole lines + const isSingleLine = (range ? range.startLineNumber === range.endLineNumber : true) && ghostText.parts.length === 1 && ghostText.parts[0].lines.length === 1; + + //check if we're just removing code + const isPureRemove = ghostText.parts.length === 1 && ghostText.parts[0].lines.every(l => l.length === 0); + + const inlineTexts: { column: number; text: string; preview: boolean }[] = []; + const additionalLines: LineData[] = []; + + function addToAdditionalLines(lines: readonly string[], className: string | undefined) { + if (additionalLines.length > 0) { + const lastLine = additionalLines[additionalLines.length - 1]; + if (className) { + lastLine.decorations.push(new LineDecoration(lastLine.content.length + 1, lastLine.content.length + 1 + lines[0].length, className, InlineDecorationType.Regular)); + } + lastLine.content += lines[0]; + + lines = lines.slice(1); + } + for (const line of lines) { + additionalLines.push({ + content: line, + decorations: className ? [new LineDecoration(1, line.length + 1, className, InlineDecorationType.Regular)] : [] + }); + } + } + + const textBufferLine = textModel.getLineContent(ghostText.lineNumber); + + let hiddenTextStartColumn: number | undefined = undefined; + let lastIdx = 0; + if (!isPureRemove) { + for (const part of ghostText.parts) { + let lines = part.lines; + //If remove range is set, we want to push all new liens to virtual area + if (range && !isSingleLine) { + addToAdditionalLines(lines, INLINE_EDIT_DESCRIPTION); + lines = []; + } + if (hiddenTextStartColumn === undefined) { + inlineTexts.push({ + column: part.column, + text: lines[0], + preview: part.preview, + }); + lines = lines.slice(1); + } else { + addToAdditionalLines([textBufferLine.substring(lastIdx, part.column - 1)], undefined); + } + + if (lines.length > 0) { + addToAdditionalLines(lines, INLINE_EDIT_DESCRIPTION); + if (hiddenTextStartColumn === undefined && part.column <= textBufferLine.length) { + hiddenTextStartColumn = part.column; + } + } + + lastIdx = part.column - 1; + } + if (hiddenTextStartColumn !== undefined) { + addToAdditionalLines([textBufferLine.substring(lastIdx)], undefined); + } + } + + const hiddenRange = hiddenTextStartColumn !== undefined ? new ColumnRange(hiddenTextStartColumn, textBufferLine.length + 1) : undefined; + + const lineNumber = + (isSingleLine || !range) ? ghostText.lineNumber : range.endLineNumber - 1; + + return { + inlineTexts, + additionalLines, + hiddenRange, + lineNumber, + additionalReservedLineCount: this.model.minReservedLineCount.read(reader), + targetTextModel: textModel, + range, + isSingleLine, + isPureRemove, + backgroundColoring: this.model.backgroundColoring.read(reader) + }; + }); + + private readonly decorations = derived(this, reader => { + const uiState = this.uiState.read(reader); + if (!uiState) { + return []; + } + + const decorations: IModelDeltaDecoration[] = []; + + if (uiState.hiddenRange) { + decorations.push({ + range: uiState.hiddenRange.toRange(uiState.lineNumber), + options: { inlineClassName: 'inline-edit-hidden', description: 'inline-edit-hidden', } + }); + } + + if (uiState.range) { + const ranges = []; + if (uiState.isSingleLine) { + ranges.push(uiState.range); + } + else if (uiState.isPureRemove) { + const lines = uiState.range.endLineNumber - uiState.range.startLineNumber; + for (let i = 0; i < lines; i++) { + const line = uiState.range.startLineNumber + i; + const firstNonWhitespace = uiState.targetTextModel.getLineFirstNonWhitespaceColumn(line); + const lastNonWhitespace = uiState.targetTextModel.getLineLastNonWhitespaceColumn(line); + const range = new Range(line, firstNonWhitespace, line, lastNonWhitespace); + ranges.push(range); + } + } + else { + const lines = uiState.range.endLineNumber - uiState.range.startLineNumber; + for (let i = 0; i <= lines; i++) { + const line = uiState.range.startLineNumber + i; + const firstNonWhitespace = uiState.targetTextModel.getLineFirstNonWhitespaceColumn(line); + const lastNonWhitespace = uiState.targetTextModel.getLineLastNonWhitespaceColumn(line); + const range = new Range(line, firstNonWhitespace, line, lastNonWhitespace); + ranges.push(range); + } + } + const className = uiState.backgroundColoring ? 'inline-edit-remove backgroundColoring' : 'inline-edit-remove'; + for (const range of ranges) { + decorations.push({ + range, + options: { inlineClassName: className, description: 'inline-edit-remove', } + }); + } + } + + for (const p of uiState.inlineTexts) { + + decorations.push({ + range: Range.fromPositions(new Position(uiState.lineNumber, p.column)), + options: { + description: INLINE_EDIT_DESCRIPTION, + after: { content: p.text, inlineClassName: p.preview ? 'inline-edit-decoration-preview' : 'inline-edit-decoration', cursorStops: InjectedTextCursorStops.Left }, + showIfCollapsed: true, + } + }); + } + + return decorations; + }); + + private readonly additionalLinesWidget = this._register( + new AdditionalLinesWidget( + this.editor, + this.languageService.languageIdCodec, + derived(reader => { + /** @description lines */ + const uiState = this.uiState.read(reader); + return uiState && !uiState.isPureRemove ? { + lineNumber: uiState.lineNumber, + additionalLines: uiState.additionalLines, + minReservedLineCount: uiState.additionalReservedLineCount, + targetTextModel: uiState.targetTextModel, + } : undefined; + }) + ) + ); + + public ownsViewZone(viewZoneId: string): boolean { + return this.additionalLinesWidget.viewZoneId === viewZoneId; + } +} diff --git a/src/vs/editor/contrib/inlineEdit/browser/hoverParticipant.ts b/src/vs/editor/contrib/inlineEdit/browser/hoverParticipant.ts new file mode 100644 index 00000000000..6c1c7337f7f --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/hoverParticipant.ts @@ -0,0 +1,105 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { constObservable } from 'vs/base/common/observable'; +import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Range } from 'vs/editor/common/core/range'; +import { IModelDecoration } from 'vs/editor/common/model'; +import { HoverAnchor, HoverAnchorType, HoverForeignElementAnchor, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { InlineEditController } from 'vs/editor/contrib/inlineEdit/browser/inlineEditController'; +import { InlineEditHintsContentWidget } from 'vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget'; + +export class InlineEditHover implements IHoverPart { + constructor( + public readonly owner: IEditorHoverParticipant, + public readonly range: Range, + public readonly controller: InlineEditController + ) { } + + public isValidForHoverAnchor(anchor: HoverAnchor): boolean { + return ( + anchor.type === HoverAnchorType.Range + && this.range.startColumn <= anchor.range.startColumn + && this.range.endColumn >= anchor.range.endColumn + ); + } +} + +export class InlineEditHoverParticipant implements IEditorHoverParticipant { + + public readonly hoverOrdinal: number = 5; + + constructor( + private readonly _editor: ICodeEditor, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, + ) { + } + + suggestHoverAnchor(mouseEvent: IEditorMouseEvent): HoverAnchor | null { + const controller = InlineEditController.get(this._editor); + if (!controller) { + return null; + } + + const target = mouseEvent.target; + if (target.type === MouseTargetType.CONTENT_VIEW_ZONE) { + // handle the case where the mouse is over the view zone + const viewZoneData = target.detail; + if (controller.shouldShowHoverAtViewZone(viewZoneData.viewZoneId)) { + // const range = Range.fromPositions(this._editor.getModel()!.validatePosition(viewZoneData.positionBefore || viewZoneData.position)); + const range = target.range; + return new HoverForeignElementAnchor(1000, this, range, mouseEvent.event.posx, mouseEvent.event.posy, false); + } + } + if (target.type === MouseTargetType.CONTENT_EMPTY) { + // handle the case where the mouse is over the empty portion of a line following ghost text + if (controller.shouldShowHoverAt(target.range)) { + return new HoverForeignElementAnchor(1000, this, target.range, mouseEvent.event.posx, mouseEvent.event.posy, false); + } + } + if (target.type === MouseTargetType.CONTENT_TEXT) { + // handle the case where the mouse is directly over ghost text + const mightBeForeignElement = target.detail.mightBeForeignElement; + if (mightBeForeignElement && controller.shouldShowHoverAt(target.range)) { + return new HoverForeignElementAnchor(1000, this, target.range, mouseEvent.event.posx, mouseEvent.event.posy, false); + } + } + return null; + } + + computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): InlineEditHover[] { + if (this._editor.getOption(EditorOption.inlineEdit).showToolbar !== 'onHover') { + return []; + } + + const controller = InlineEditController.get(this._editor); + if (controller && controller.shouldShowHoverAt(anchor.range)) { + return [new InlineEditHover(this, anchor.range, controller)]; + } + return []; + } + + renderHoverParts(context: IEditorHoverRenderContext, hoverParts: InlineEditHover[]): IDisposable { + const disposableStore = new DisposableStore(); + + this._telemetryService.publicLog2<{}, { + owner: 'hediet'; + comment: 'This event tracks whenever an inline edit hover is shown.'; + }>('inlineEditHover.shown'); + + const w = this._instantiationService.createInstance(InlineEditHintsContentWidget, this._editor, false, + constObservable(null), + ); + context.fragment.appendChild(w.getDomNode()); + disposableStore.add(w); + + return disposableStore; + } +} diff --git a/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.contribution.ts b/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.contribution.ts new file mode 100644 index 00000000000..7196773a7cf --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.contribution.ts @@ -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. + *--------------------------------------------------------------------------------------------*/ + +import { EditorContributionInstantiation, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { HoverParticipantRegistry } from 'vs/editor/contrib/hover/browser/hoverTypes'; +import { AcceptInlineEdit, JumpBackInlineEdit, JumpToInlineEdit, RejectInlineEdit, TriggerInlineEdit } from 'vs/editor/contrib/inlineEdit/browser/commands'; +import { InlineEditHoverParticipant } from 'vs/editor/contrib/inlineEdit/browser/hoverParticipant'; +import { InlineEditController } from 'vs/editor/contrib/inlineEdit/browser/inlineEditController'; + +registerEditorAction(AcceptInlineEdit); +registerEditorAction(RejectInlineEdit); +registerEditorAction(JumpToInlineEdit); +registerEditorAction(JumpBackInlineEdit); +registerEditorAction(TriggerInlineEdit); +registerEditorContribution(InlineEditController.ID, InlineEditController, EditorContributionInstantiation.Eventually); + + +HoverParticipantRegistry.register(InlineEditHoverParticipant); diff --git a/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.css b/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.css new file mode 100644 index 00000000000..d6d156544e0 --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.css @@ -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. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .inline-edit-remove { + background-color: var(--vscode-editorGhostText-background); + font-style: italic; + text-decoration: line-through; +} + +.monaco-editor .inline-edit-remove.backgroundColoring { + background-color: var(--vscode-diffEditor-removedLineBackground); +} + +.monaco-editor .inline-edit-hidden { + opacity: 0; + font-size: 0; +} + +.monaco-editor .inline-edit-decoration, .monaco-editor .suggest-preview-text .inline-edit { + font-style: italic; +} + +.monaco-editor .inline-completion-text-to-replace { + text-decoration: underline; + text-underline-position: under; +} + +.monaco-editor .inline-edit-decoration, +.monaco-editor .inline-edit-decoration-preview, +.monaco-editor .suggest-preview-text .inline-edit { + color: var(--vscode-editorGhostText-foreground) !important; + background-color: var(--vscode-editorGhostText-background); + border: 1px solid var(--vscode-editorGhostText-border); +} + + diff --git a/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts b/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts new file mode 100644 index 00000000000..2b5db89b913 --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts @@ -0,0 +1,344 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { ISettableObservable, autorun, constObservable, disposableObservableValue, observableFromEvent, observableSignalFromEvent } from 'vs/base/common/observable'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { GhostTextWidget } from 'vs/editor/contrib/inlineEdit/browser/ghostTextWidget'; +import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInlineEdit, InlineEditTriggerKind } from 'vs/editor/common/languages'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { GhostText, GhostTextPart } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { InlineEditHintsWidget } from 'vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { createStyleSheet2 } from 'vs/base/browser/dom'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; + +export class InlineEditWidget implements IDisposable { + constructor(public readonly widget: GhostTextWidget, public readonly edit: IInlineEdit) { } + + dispose(): void { + this.widget.dispose(); + } +} + +export class InlineEditController extends Disposable { + static ID = 'editor.contrib.inlineEditController'; + + public static readonly inlineEditVisibleKey = 'inlineEditVisible'; + public static readonly inlineEditVisibleContext = new RawContextKey(InlineEditController.inlineEditVisibleKey, false); + private _isVisibleContext = InlineEditController.inlineEditVisibleContext.bindTo(this.contextKeyService); + + public static readonly cursorAtInlineEditKey = 'cursorAtInlineEdit'; + public static readonly cursorAtInlineEditContext = new RawContextKey(InlineEditController.cursorAtInlineEditKey, false); + private _isCursorAtInlineEditContext = InlineEditController.cursorAtInlineEditContext.bindTo(this.contextKeyService); + + public static get(editor: ICodeEditor): InlineEditController | null { + return editor.getContribution(InlineEditController.ID); + } + + private _currentEdit: ISettableObservable = this._register(disposableObservableValue(this, undefined)); + private _currentRequestCts: CancellationTokenSource | undefined; + + private _jumpBackPosition: Position | undefined; + private _isAccepting: boolean = false; + + private readonly _enabled = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineEdit).enabled); + private readonly _fontFamily = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineEdit).fontFamily); + private readonly _backgroundColoring = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineEdit).backgroundColoring); + + + constructor( + public readonly editor: ICodeEditor, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, + @ICommandService private readonly _commandService: ICommandService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + ) { + super(); + + //Automatically request inline edit when the content was changed + //Cancel the previous request if there is one + //Remove the previous ghost text + const modelChangedSignal = observableSignalFromEvent('InlineEditController.modelContentChangedSignal', editor.onDidChangeModelContent); + this._register(autorun(reader => { + /** @description InlineEditController.modelContentChanged model */ + if (!this._enabled.read(reader)) { + return; + } + modelChangedSignal.read(reader); + this.getInlineEdit(editor, true); + })); + + //Check if the cursor is at the ghost text + const cursorPosition = observableFromEvent(editor.onDidChangeCursorPosition, () => editor.getPosition()); + this._register(autorun(reader => { + /** @description InlineEditController.cursorPositionChanged model */ + if (!this._enabled.read(reader)) { + return; + } + + const pos = cursorPosition.read(reader); + if (pos) { + this.checkCursorPosition(pos); + } + })); + + //Perform stuff when the current edit has changed + this._register(autorun((reader) => { + /** @description InlineEditController.update model */ + const currentEdit = this._currentEdit.read(reader); + this._isCursorAtInlineEditContext.set(false); + if (!currentEdit) { + this._isVisibleContext.set(false); + return; + } + this._isVisibleContext.set(true); + const pos = editor.getPosition(); + if (pos) { + this.checkCursorPosition(pos); + } + })); + + //Clear suggestions on lost focus + this._register(editor.onDidBlurEditorWidget(() => { + // This is a hidden setting very useful for debugging + if (this._configurationService.getValue('editor.experimentalInlineEdit.keepOnBlur') || editor.getOption(EditorOption.inlineEdit).keepOnBlur) { + return; + } + this._currentRequestCts?.dispose(); + this._currentRequestCts = undefined; + this.clear(); + })); + + //Invoke provider on focus + this._register(editor.onDidFocusEditorText(async () => { + if (!this._enabled.get()) { + return; + } + await this.getInlineEdit(editor, true); + })); + + + //handle changes of font setting + const styleElement = this._register(createStyleSheet2()); + this._register(autorun(reader => { + const fontFamily = this._fontFamily.read(reader); + styleElement.setStyle(fontFamily === '' || fontFamily === 'default' ? `` : ` +.monaco-editor .inline-edit-decoration, +.monaco-editor .inline-edit-decoration-preview, +.monaco-editor .inline-edit { + font-family: ${fontFamily}; +}`); + })); + + this._register(new InlineEditHintsWidget(this.editor, this._currentEdit, this.instantiationService)); + } + + private checkCursorPosition(position: Position) { + if (!this._currentEdit) { + this._isCursorAtInlineEditContext.set(false); + return; + } + const gt = this._currentEdit.get()?.edit; + if (!gt) { + this._isCursorAtInlineEditContext.set(false); + return; + } + this._isCursorAtInlineEditContext.set(Range.containsPosition(gt.range, position)); + } + + private validateInlineEdit(editor: ICodeEditor, edit: IInlineEdit): boolean { + //Multiline inline replacing edit must replace whole lines + if (edit.text.includes('\n') && edit.range.startLineNumber !== edit.range.endLineNumber && edit.range.startColumn !== edit.range.endColumn) { + const firstColumn = edit.range.startColumn; + if (firstColumn !== 1) { + return false; + } + const lastLine = edit.range.endLineNumber; + const lastColumn = edit.range.endColumn; + const lineLength = editor.getModel()?.getLineLength(lastLine) ?? 0; + if (lastColumn !== lineLength + 1) { + return false; + } + } + return true; + } + + private async fetchInlineEdit(editor: ICodeEditor, auto: boolean): Promise { + if (this._currentRequestCts) { + this._currentRequestCts.dispose(true); + } + const model = editor.getModel(); + if (!model) { + return; + } + const modelVersion = model.getVersionId(); + const providers = this.languageFeaturesService.inlineEditProvider.all(model); + if (providers.length === 0) { + return; + } + const provider = providers[0]; + this._currentRequestCts = new CancellationTokenSource(); + const token = this._currentRequestCts.token; + const triggerKind = auto ? InlineEditTriggerKind.Automatic : InlineEditTriggerKind.Invoke; + const shouldDebounce = auto; + if (shouldDebounce) { + await wait(50, token); + } + if (token.isCancellationRequested || model.isDisposed() || model.getVersionId() !== modelVersion) { + return; + } + const edit = await provider.provideInlineEdit(model, { triggerKind }, token); + if (!edit) { + return; + } + if (token.isCancellationRequested || model.isDisposed() || model.getVersionId() !== modelVersion) { + return; + } + if (!this.validateInlineEdit(editor, edit)) { + return; + } + return edit; + } + + private async getInlineEdit(editor: ICodeEditor, auto: boolean) { + this._isCursorAtInlineEditContext.set(false); + this.clear(); + this._isAccepting = false; + const edit = await this.fetchInlineEdit(editor, auto); + if (!edit) { + return; + } + const line = edit.range.endLineNumber; + const column = edit.range.endColumn; + const ghostText = new GhostText(line, [new GhostTextPart(column, edit.text, false)]); + const instance = this.instantiationService.createInstance(GhostTextWidget, this.editor, { + ghostText: constObservable(ghostText), + minReservedLineCount: constObservable(0), + targetTextModel: constObservable(this.editor.getModel() ?? undefined), + range: constObservable(edit.range), + backgroundColoring: this._backgroundColoring + }); + this._currentEdit.set(new InlineEditWidget(instance, edit), undefined); + } + + public async trigger() { + await this.getInlineEdit(this.editor, false); + } + + public async jumpBack() { + if (!this._jumpBackPosition) { + return; + } + this.editor.setPosition(this._jumpBackPosition); + //if position is outside viewports, scroll to it + this.editor.revealPositionInCenterIfOutsideViewport(this._jumpBackPosition); + } + + public accept(): void { + this._isAccepting = true; + const data = this._currentEdit.get()?.edit; + if (!data) { + return; + } + + //It should only happen in case of last line suggestion + let text = data.text; + if (data.text.startsWith('\n')) { + text = data.text.substring(1); + } + this.editor.pushUndoStop(); + this.editor.executeEdits('acceptCurrent', [EditOperation.replace(Range.lift(data.range), text)]); + if (data.accepted) { + this._commandService.executeCommand(data.accepted.id, ...data.accepted.arguments || []); + } + this.freeEdit(data); + this._currentEdit.set(undefined, undefined); + } + + public jumpToCurrent(): void { + this._jumpBackPosition = this.editor.getSelection()?.getStartPosition(); + + const data = this._currentEdit.get()?.edit; + if (!data) { + return; + } + const position = Position.lift({ lineNumber: data.range.startLineNumber, column: data.range.startColumn }); + this.editor.setPosition(position); + //if position is outside viewports, scroll to it + this.editor.revealPositionInCenterIfOutsideViewport(position); + } + + public clear() { + const edit = this._currentEdit.get()?.edit; + if (edit && edit?.rejected && !this._isAccepting) { + this._commandService.executeCommand(edit.rejected.id, ...edit.rejected.arguments || []); + } + if (edit) { + this.freeEdit(edit); + } + this._currentEdit.set(undefined, undefined); + } + + private freeEdit(edit: IInlineEdit) { + const model = this.editor.getModel(); + if (!model) { + return; + } + const providers = this.languageFeaturesService.inlineEditProvider.all(model); + if (providers.length === 0) { + return; + } + providers[0].freeInlineEdit(edit); + } + + public shouldShowHoverAt(range: Range) { + const currentEdit = this._currentEdit.get(); + if (!currentEdit) { + return false; + } + const edit = currentEdit.edit; + const model = currentEdit.widget.model; + const overReplaceRange = Range.containsPosition(edit.range, range.getStartPosition()) || Range.containsPosition(edit.range, range.getEndPosition()); + if (overReplaceRange) { + return true; + } + const ghostText = model.ghostText.get(); + if (ghostText) { + return ghostText.parts.some(p => range.containsPosition(new Position(ghostText.lineNumber, p.column))); + } + return false; + } + + public shouldShowHoverAtViewZone(viewZoneId: string): boolean { + return this._currentEdit.get()?.widget.ownsViewZone(viewZoneId) ?? false; + } + +} + +function wait(ms: number, cancellationToken?: CancellationToken): Promise { + return new Promise(resolve => { + let d: IDisposable | undefined = undefined; + const handle = setTimeout(() => { + if (d) { d.dispose(); } + resolve(); + }, ms); + if (cancellationToken) { + d = cancellationToken.onCancellationRequested(() => { + clearTimeout(handle); + if (d) { d.dispose(); } + resolve(); + }); + } + }); +} diff --git a/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.css b/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.css new file mode 100644 index 00000000000..8f369a27c3d --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.css @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .inlineEditHints.withBorder { + z-index: 39; + color: var(--vscode-editorHoverWidget-foreground); + background-color: var(--vscode-editorHoverWidget-background); + border: 1px solid var(--vscode-editorHoverWidget-border); +} + +.monaco-editor .inlineEditHints a { + color: var(--vscode-foreground); +} + +.monaco-editor .inlineEditHints a:hover { + color: var(--vscode-foreground); +} + +.monaco-editor .inlineEditHints .keybinding { + display: flex; + margin-left: 4px; + opacity: 0.6; +} + +.monaco-editor .inlineEditHints .keybinding .monaco-keybinding-key { + font-size: 8px; + padding: 2px 3px; +} + +.monaco-editor .inlineEditStatusBarItemLabel { + margin-right: 2px; +} diff --git a/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.ts b/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.ts new file mode 100644 index 00000000000..73824bd5e6c --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.ts @@ -0,0 +1,246 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { h } from 'vs/base/browser/dom'; +import { KeybindingLabel, unthemedKeybindingLabelOptions } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; +import { IAction, Separator } from 'vs/base/common/actions'; +import { equals } from 'vs/base/common/arrays'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IObservable, autorun, autorunWithStore, derived, observableFromEvent } from 'vs/base/common/observable'; +import { OS } from 'vs/base/common/platform'; +import 'vs/css!./inlineEditHintsWidget'; +import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Position } from 'vs/editor/common/core/position'; +import { PositionAffinity } from 'vs/editor/common/model'; +import { InlineEditWidget } from 'vs/editor/contrib/inlineEdit/browser/inlineEditController'; +import { MenuEntryActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenuWorkbenchToolBarOptions, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; + +export class InlineEditHintsWidget extends Disposable { + private readonly alwaysShowToolbar = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineEdit).showToolbar === 'always'); + + private sessionPosition: Position | undefined = undefined; + + private readonly position = derived(this, reader => { + const ghostText = this.model.read(reader)?.widget.model.ghostText.read(reader); + + if (!this.alwaysShowToolbar.read(reader) || !ghostText || ghostText.parts.length === 0) { + this.sessionPosition = undefined; + return null; + } + + const firstColumn = ghostText.parts[0].column; + if (this.sessionPosition && this.sessionPosition.lineNumber !== ghostText.lineNumber) { + this.sessionPosition = undefined; + } + + const position = new Position(ghostText.lineNumber, Math.min(firstColumn, this.sessionPosition?.column ?? Number.MAX_SAFE_INTEGER)); + this.sessionPosition = position; + return position; + }); + + constructor( + private readonly editor: ICodeEditor, + private readonly model: IObservable, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super(); + + this._register(autorunWithStore((reader, store) => { + /** @description setup content widget */ + const model = this.model.read(reader); + if (!model || !this.alwaysShowToolbar.read(reader)) { + return; + } + + const contentWidget = store.add(this.instantiationService.createInstance( + InlineEditHintsContentWidget, + this.editor, + true, + this.position, + )); + editor.addContentWidget(contentWidget); + store.add(toDisposable(() => editor.removeContentWidget(contentWidget))); + })); + } +} + +export class InlineEditHintsContentWidget extends Disposable implements IContentWidget { + private static _dropDownVisible = false; + public static get dropDownVisible() { return this._dropDownVisible; } + + private static id = 0; + + private readonly id = `InlineEditHintsContentWidget${InlineEditHintsContentWidget.id++}`; + public readonly allowEditorOverflow = true; + public readonly suppressMouseDown = false; + + private readonly nodes = h('div.inlineEditHints', { className: this.withBorder ? '.withBorder' : '' }, [ + h('div@toolBar'), + ]); + + private readonly toolBar: CustomizedMenuWorkbenchToolBar; + + private readonly inlineCompletionsActionsMenus = this._register(this._menuService.createMenu( + MenuId.InlineEditActions, + this._contextKeyService + )); + + constructor( + private readonly editor: ICodeEditor, + private readonly withBorder: boolean, + private readonly _position: IObservable, + + @IInstantiationService instantiationService: IInstantiationService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IMenuService private readonly _menuService: IMenuService, + ) { + super(); + + this.toolBar = this._register(instantiationService.createInstance(CustomizedMenuWorkbenchToolBar, this.nodes.toolBar, this.editor, MenuId.InlineEditToolbar, { + menuOptions: { renderShortTitle: true }, + toolbarOptions: { primaryGroup: g => g.startsWith('primary') }, + actionViewItemProvider: (action, options) => { + if (action instanceof MenuItemAction) { + return instantiationService.createInstance(StatusBarViewItem, action, undefined); + } + return undefined; + }, + telemetrySource: 'InlineEditToolbar', + })); + + this._register(this.toolBar.onDidChangeDropdownVisibility(e => { + InlineEditHintsContentWidget._dropDownVisible = e; + })); + + this._register(autorun(reader => { + /** @description update position */ + this._position.read(reader); + this.editor.layoutContentWidget(this); + })); + + this._register(autorun(reader => { + /** @description actions menu */ + + const extraActions = []; + + for (const [_, group] of this.inlineCompletionsActionsMenus.getActions()) { + for (const action of group) { + if (action instanceof MenuItemAction) { + extraActions.push(action); + } + } + } + + if (extraActions.length > 0) { + extraActions.unshift(new Separator()); + } + + this.toolBar.setAdditionalSecondaryActions(extraActions); + })); + + + } + + getId(): string { return this.id; } + + getDomNode(): HTMLElement { + return this.nodes.root; + } + + getPosition(): IContentWidgetPosition | null { + return { + position: this._position.get(), + preference: [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW], + positionAffinity: PositionAffinity.LeftOfInjectedText, + }; + } +} + +class StatusBarViewItem extends MenuEntryActionViewItem { + protected override updateLabel() { + const kb = this._keybindingService.lookupKeybinding(this._action.id, this._contextKeyService); + if (!kb) { + return super.updateLabel(); + } + if (this.label) { + const div = h('div.keybinding').root; + + const k = new KeybindingLabel(div, OS, { disableTitle: true, ...unthemedKeybindingLabelOptions }); + k.set(kb); + this.label.textContent = this._action.label; + this.label.appendChild(div); + this.label.classList.add('inlineEditStatusBarItemLabel'); + } + } + + protected override updateTooltip(): void { + // NOOP, disable tooltip + } +} + +export class CustomizedMenuWorkbenchToolBar extends WorkbenchToolBar { + private readonly menu = this._store.add(this.menuService.createMenu(this.menuId, this.contextKeyService, { emitEventsForSubmenuChanges: true })); + private additionalActions: IAction[] = []; + private prependedPrimaryActions: IAction[] = []; + + constructor( + container: HTMLElement, + private readonly editor: ICodeEditor, + private readonly menuId: MenuId, + private readonly options2: IMenuWorkbenchToolBarOptions | undefined, + @IMenuService private readonly menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextMenuService contextMenuService: IContextMenuService, + @IKeybindingService keybindingService: IKeybindingService, + @ITelemetryService telemetryService: ITelemetryService, + ) { + super(container, { resetMenu: menuId, ...options2 }, menuService, contextKeyService, contextMenuService, keybindingService, telemetryService); + + this._store.add(this.menu.onDidChange(() => this.updateToolbar())); + this._store.add(this.editor.onDidChangeCursorPosition(() => this.updateToolbar())); + this.updateToolbar(); + } + + private updateToolbar(): void { + const primary: IAction[] = []; + const secondary: IAction[] = []; + createAndFillInActionBarActions( + this.menu, + this.options2?.menuOptions, + { primary, secondary }, + this.options2?.toolbarOptions?.primaryGroup, this.options2?.toolbarOptions?.shouldInlineSubmenu, this.options2?.toolbarOptions?.useSeparatorsInPrimaryActions + ); + + secondary.push(...this.additionalActions); + primary.unshift(...this.prependedPrimaryActions); + this.setActions(primary, secondary); + } + + setPrependedPrimaryActions(actions: IAction[]): void { + if (equals(this.prependedPrimaryActions, actions, (a, b) => a === b)) { + return; + } + + this.prependedPrimaryActions = actions; + this.updateToolbar(); + } + + setAdditionalSecondaryActions(actions: IAction[]): void { + if (equals(this.additionalActions, actions, (a, b) => a === b)) { + return; + } + + this.additionalActions = actions; + this.updateToolbar(); + } +} diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index cedb454f595..227595d6224 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -41,6 +41,7 @@ import 'vs/editor/contrib/linkedEditing/browser/linkedEditing'; import 'vs/editor/contrib/links/browser/links'; import 'vs/editor/contrib/longLinesHelper/browser/longLinesHelper'; import 'vs/editor/contrib/multicursor/browser/multicursor'; +import 'vs/editor/contrib/inlineEdit/browser/inlineEdit.contribution'; import 'vs/editor/contrib/parameterHints/browser/parameterHints'; import 'vs/editor/contrib/rename/browser/rename'; import 'vs/editor/contrib/semanticTokens/browser/documentSemanticTokens'; diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index 5323333e640..8466fbf9f99 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -679,6 +679,11 @@ export function registerInlineCompletionsProvider(languageSelector: LanguageSele return languageFeaturesService.inlineCompletionsProvider.register(languageSelector, provider); } +export function registerInlineEditProvider(languageSelector: LanguageSelector, provider: languages.InlineEditProvider): IDisposable { + const languageFeaturesService = StandaloneServices.get(ILanguageFeaturesService); + return languageFeaturesService.inlineEditProvider.register(languageSelector, provider); +} + /** * Register an inlay hints provider. */ @@ -786,6 +791,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { registerDocumentSemanticTokensProvider: registerDocumentSemanticTokensProvider, registerDocumentRangeSemanticTokensProvider: registerDocumentRangeSemanticTokensProvider, registerInlineCompletionsProvider: registerInlineCompletionsProvider, + registerInlineEditProvider: registerInlineEditProvider, registerInlayHintsProvider: registerInlayHintsProvider, // enums @@ -800,6 +806,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { SignatureHelpTriggerKind: standaloneEnums.SignatureHelpTriggerKind, InlayHintKind: standaloneEnums.InlayHintKind, InlineCompletionTriggerKind: standaloneEnums.InlineCompletionTriggerKind, + InlineEditTriggerKind: standaloneEnums.InlineEditTriggerKind, CodeActionTriggerType: standaloneEnums.CodeActionTriggerType, NewSymbolNameTag: standaloneEnums.NewSymbolNameTag, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index b1bad3efdfd..c094df337af 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3455,6 +3455,7 @@ declare namespace monaco.editor { */ suggest?: ISuggestOptions; inlineSuggest?: IInlineSuggestOptions; + experimentalInlineEdit?: IInlineEditOptions; /** * Smart select options. */ @@ -4508,6 +4509,23 @@ declare namespace monaco.editor { fontFamily?: string | 'default'; } + export interface IInlineEditOptions { + /** + * Enable or disable the rendering of automatic inline edit. + */ + enabled?: boolean; + showToolbar?: 'always' | 'onHover' | 'never'; + /** + * Font family for inline suggestions. + */ + fontFamily?: string | 'default'; + /** + * Does not clear active inline suggestions when the editor loses focus. + */ + keepOnBlur?: boolean; + backgroundColoring?: boolean; + } + export interface IBracketPairColorizationOptions { /** * Enable or disable bracket pair colorization. @@ -4843,91 +4861,92 @@ declare namespace monaco.editor { hover = 60, inDiffEditor = 61, inlineSuggest = 62, - letterSpacing = 63, - lightbulb = 64, - lineDecorationsWidth = 65, - lineHeight = 66, - lineNumbers = 67, - lineNumbersMinChars = 68, - linkedEditing = 69, - links = 70, - matchBrackets = 71, - minimap = 72, - mouseStyle = 73, - mouseWheelScrollSensitivity = 74, - mouseWheelZoom = 75, - multiCursorMergeOverlapping = 76, - multiCursorModifier = 77, - multiCursorPaste = 78, - multiCursorLimit = 79, - occurrencesHighlight = 80, - overviewRulerBorder = 81, - overviewRulerLanes = 82, - padding = 83, - pasteAs = 84, - parameterHints = 85, - peekWidgetDefaultFocus = 86, - definitionLinkOpensInPeek = 87, - quickSuggestions = 88, - quickSuggestionsDelay = 89, - readOnly = 90, - readOnlyMessage = 91, - renameOnType = 92, - renderControlCharacters = 93, - renderFinalNewline = 94, - renderLineHighlight = 95, - renderLineHighlightOnlyWhenFocus = 96, - renderValidationDecorations = 97, - renderWhitespace = 98, - revealHorizontalRightPadding = 99, - roundedSelection = 100, - rulers = 101, - scrollbar = 102, - scrollBeyondLastColumn = 103, - scrollBeyondLastLine = 104, - scrollPredominantAxis = 105, - selectionClipboard = 106, - selectionHighlight = 107, - selectOnLineNumbers = 108, - showFoldingControls = 109, - showUnused = 110, - snippetSuggestions = 111, - smartSelect = 112, - smoothScrolling = 113, - stickyScroll = 114, - stickyTabStops = 115, - stopRenderingLineAfter = 116, - suggest = 117, - suggestFontSize = 118, - suggestLineHeight = 119, - suggestOnTriggerCharacters = 120, - suggestSelection = 121, - tabCompletion = 122, - tabIndex = 123, - unicodeHighlighting = 124, - unusualLineTerminators = 125, - useShadowDOM = 126, - useTabStops = 127, - wordBreak = 128, - wordSeparators = 129, - wordWrap = 130, - wordWrapBreakAfterCharacters = 131, - wordWrapBreakBeforeCharacters = 132, - wordWrapColumn = 133, - wordWrapOverride1 = 134, - wordWrapOverride2 = 135, - wrappingIndent = 136, - wrappingStrategy = 137, - showDeprecated = 138, - inlayHints = 139, - editorClassName = 140, - pixelRatio = 141, - tabFocusMode = 142, - layoutInfo = 143, - wrappingInfo = 144, - defaultColorDecorators = 145, - colorDecoratorsActivatedOn = 146, - inlineCompletionsAccessibilityVerbose = 147 + inlineEdit = 63, + letterSpacing = 64, + lightbulb = 65, + lineDecorationsWidth = 66, + lineHeight = 67, + lineNumbers = 68, + lineNumbersMinChars = 69, + linkedEditing = 70, + links = 71, + matchBrackets = 72, + minimap = 73, + mouseStyle = 74, + mouseWheelScrollSensitivity = 75, + mouseWheelZoom = 76, + multiCursorMergeOverlapping = 77, + multiCursorModifier = 78, + multiCursorPaste = 79, + multiCursorLimit = 80, + occurrencesHighlight = 81, + overviewRulerBorder = 82, + overviewRulerLanes = 83, + padding = 84, + pasteAs = 85, + parameterHints = 86, + peekWidgetDefaultFocus = 87, + definitionLinkOpensInPeek = 88, + quickSuggestions = 89, + quickSuggestionsDelay = 90, + readOnly = 91, + readOnlyMessage = 92, + renameOnType = 93, + renderControlCharacters = 94, + renderFinalNewline = 95, + renderLineHighlight = 96, + renderLineHighlightOnlyWhenFocus = 97, + renderValidationDecorations = 98, + renderWhitespace = 99, + revealHorizontalRightPadding = 100, + roundedSelection = 101, + rulers = 102, + scrollbar = 103, + scrollBeyondLastColumn = 104, + scrollBeyondLastLine = 105, + scrollPredominantAxis = 106, + selectionClipboard = 107, + selectionHighlight = 108, + selectOnLineNumbers = 109, + showFoldingControls = 110, + showUnused = 111, + snippetSuggestions = 112, + smartSelect = 113, + smoothScrolling = 114, + stickyScroll = 115, + stickyTabStops = 116, + stopRenderingLineAfter = 117, + suggest = 118, + suggestFontSize = 119, + suggestLineHeight = 120, + suggestOnTriggerCharacters = 121, + suggestSelection = 122, + tabCompletion = 123, + tabIndex = 124, + unicodeHighlighting = 125, + unusualLineTerminators = 126, + useShadowDOM = 127, + useTabStops = 128, + wordBreak = 129, + wordSeparators = 130, + wordWrap = 131, + wordWrapBreakAfterCharacters = 132, + wordWrapBreakBeforeCharacters = 133, + wordWrapColumn = 134, + wordWrapOverride1 = 135, + wordWrapOverride2 = 136, + wrappingIndent = 137, + wrappingStrategy = 138, + showDeprecated = 139, + inlayHints = 140, + editorClassName = 141, + pixelRatio = 142, + tabFocusMode = 143, + layoutInfo = 144, + wrappingInfo = 145, + defaultColorDecorators = 146, + colorDecoratorsActivatedOn = 147, + inlineCompletionsAccessibilityVerbose = 148 } export const EditorOptions: { @@ -5052,6 +5071,7 @@ declare namespace monaco.editor { stopRenderingLineAfter: IEditorOption; suggest: IEditorOption>>; inlineSuggest: IEditorOption>>; + inlineEdit: IEditorOption>>; inlineCompletionsAccessibilityVerbose: IEditorOption; suggestFontSize: IEditorOption; suggestLineHeight: IEditorOption; @@ -6454,6 +6474,8 @@ declare namespace monaco.languages { */ export function registerInlineCompletionsProvider(languageSelector: LanguageSelector, provider: InlineCompletionsProvider): IDisposable; + export function registerInlineEditProvider(languageSelector: LanguageSelector, provider: InlineEditProvider): IDisposable; + /** * Register an inlay hints provider. */ @@ -7913,6 +7935,27 @@ declare namespace monaco.languages { provideMappedEdits(document: editor.ITextModel, codeBlocks: string[], context: MappedEditsContext, token: CancellationToken): Promise; } + export interface IInlineEdit { + text: string; + range: IRange; + accepted?: Command; + rejected?: Command; + } + + export interface IInlineEditContext { + triggerKind: InlineEditTriggerKind; + } + + export enum InlineEditTriggerKind { + Invoke = 0, + Automatic = 1 + } + + export interface InlineEditProvider { + provideInlineEdit(model: editor.ITextModel, context: IInlineEditContext, token: CancellationToken): ProviderResult; + freeInlineEdit(edit: T): void; + } + export interface ILanguageExtensionPoint { id: string; extensions?: string[]; diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 0b3015ddf75..15c6a1dfe59 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -201,12 +201,14 @@ export class MenuId { static readonly TerminalStickyScrollContext = new MenuId('TerminalStickyScrollContext'); static readonly WebviewContext = new MenuId('WebviewContext'); static readonly InlineCompletionsActions = new MenuId('InlineCompletionsActions'); + static readonly InlineEditActions = new MenuId('InlineEditActions'); static readonly NewFile = new MenuId('NewFile'); static readonly MergeInput1Toolbar = new MenuId('MergeToolbar1Toolbar'); static readonly MergeInput2Toolbar = new MenuId('MergeToolbar2Toolbar'); static readonly MergeBaseToolbar = new MenuId('MergeBaseToolbar'); static readonly MergeInputResultToolbar = new MenuId('MergeToolbarResultToolbar'); static readonly InlineSuggestionToolbar = new MenuId('InlineSuggestionToolbar'); + static readonly InlineEditToolbar = new MenuId('InlineEditToolbar'); static readonly ChatContext = new MenuId('ChatContext'); static readonly ChatCodeBlock = new MenuId('ChatCodeblock'); static readonly ChatMessageTitle = new MenuId('ChatMessageTitle'); diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 28193f86065..80d13f11362 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -32,7 +32,7 @@ import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy' import * as search from 'vs/workbench/contrib/search/common/search'; import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IDocumentDropEditProviderMetadata, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IPasteEditProviderMetadataDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape } from '../common/extHost.protocol'; +import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IdentifiableInlineEdit, IDocumentDropEditProviderMetadata, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IPasteEditProviderMetadataDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape } from '../common/extHost.protocol'; import { ResourceMap } from 'vs/base/common/map'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; @@ -618,6 +618,19 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread this._registrations.set(handle, this._languageFeaturesService.inlineCompletionsProvider.register(selector, provider)); } + $registerInlineEditProvider(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier): void { + const provider: languages.InlineEditProvider = { + provideInlineEdit: async (model: ITextModel, context: languages.IInlineEditContext, token: CancellationToken): Promise => { + return this._proxy.$provideInlineEdit(handle, model.uri, context, token); + }, + freeInlineEdit: (edit: IdentifiableInlineEdit): void => { + this._proxy.$freeInlineEdit(handle, edit.pid); + } + + }; + this._registrations.set(handle, this._languageFeaturesService.inlineEditProvider.register(selector, provider)); + } + // --- parameter hints $registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index b93fdbeb5d7..b0b9c8c0b47 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -632,6 +632,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } return extHostLanguageFeatures.registerInlineCompletionsProvider(extension, checkSelector(selector), provider, metadata); }, + registerInlineEditProvider(selector: vscode.DocumentSelector, provider: vscode.InlineEditProvider): vscode.Disposable { + checkProposedApiEnabled(extension, 'inlineEdit'); + return extHostLanguageFeatures.registerInlineEditProvider(extension, checkSelector(selector), provider); + }, registerDocumentLinkProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable { return extHostLanguageFeatures.registerDocumentLinkProvider(extension, checkSelector(selector), provider); }, @@ -1681,6 +1685,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I LanguageModelAssistantMessage: extHostTypes.LanguageModelAssistantMessage, NewSymbolName: extHostTypes.NewSymbolName, NewSymbolNameTag: extHostTypes.NewSymbolNameTag, + InlineEdit: extHostTypes.InlineEdit, + InlineEditTriggerKind: extHostTypes.InlineEditTriggerKind, }; }; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 34e3f6664b3..3481cd5a510 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -392,6 +392,10 @@ export interface IdentifiableInlineCompletion extends languages.InlineCompletion idx: number; } +export interface IdentifiableInlineEdit extends languages.IInlineEdit { + pid: number; +} + export interface MainThreadLanguageFeaturesShape extends IDisposable { $unregister(handle: number): void; $registerDocumentSymbolProvider(handle: number, selector: IDocumentFilterDto[], label: string): void; @@ -422,6 +426,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerDocumentRangeSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: languages.SemanticTokensLegend): void; $registerCompletionsProvider(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, extensionId: ExtensionIdentifier): void; $registerInlineCompletionsSupport(handle: number, selector: IDocumentFilterDto[], supportsHandleDidShowCompletionItem: boolean, extensionId: string, yieldsToExtensionIds: string[]): void; + $registerInlineEditProvider(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier): void; $registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void; $registerInlayHintsProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean, eventHandle: number | undefined, displayName: string | undefined): void; $emitInlayHintsEvent(eventHandle: number): void; @@ -2146,6 +2151,8 @@ export interface ExtHostLanguageFeaturesShape { $releaseTypeHierarchy(handle: number, sessionId: string): void; $provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise; $provideMappedEdits(handle: number, document: UriComponents, codeBlocks: string[], context: IMappedEditsContextDto, token: CancellationToken): Promise; + $provideInlineEdit(handle: number, document: UriComponents, context: languages.IInlineEditContext, token: CancellationToken): Promise; + $freeInlineEdit(handle: number, pid: number): void; } export interface ExtHostQuickOpenShape { diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 4554c2a0b59..d6bc4f793aa 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -32,7 +32,7 @@ import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostTelemetry, IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { CodeActionKind, CompletionList, Disposable, DocumentSymbol, InlineCompletionTriggerKind, InternalDataTransferItem, Location, Range, SemanticTokens, SemanticTokensEdit, SemanticTokensEdits, SnippetString, SymbolInformation, SyntaxTokenType } from 'vs/workbench/api/common/extHostTypes'; +import { CodeActionKind, CompletionList, Disposable, DocumentSymbol, InlineCompletionTriggerKind, InternalDataTransferItem, Location, Range, SemanticTokens, SemanticTokensEdit, SemanticTokensEdits, SnippetString, SymbolInformation, SyntaxTokenType, InlineEditTriggerKind } from 'vs/workbench/api/common/extHostTypes'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import type * as vscode from 'vscode'; import { Cache } from './cache'; @@ -1355,6 +1355,81 @@ class InlineCompletionAdapter extends InlineCompletionAdapterBase { } } +class InlineEditAdapter { + private readonly _references = new ReferenceMap<{ + dispose(): void; + item: vscode.InlineEdit; + }>(); + + private languageTriggerKindToVSCodeTriggerKind: Record = { + [languages.InlineEditTriggerKind.Automatic]: InlineEditTriggerKind.Automatic, + [languages.InlineEditTriggerKind.Invoke]: InlineEditTriggerKind.Invoke, + }; + + async provideInlineEdits(uri: URI, context: languages.IInlineEditContext, token: CancellationToken): Promise { + const doc = this._documents.getDocument(uri); + const result = await this._provider.provideInlineEdit(doc, { + triggerKind: this.languageTriggerKindToVSCodeTriggerKind[context.triggerKind] + }, token); + + if (!result) { + // undefined and null are valid results + return undefined; + } + + if (token.isCancellationRequested) { + // cancelled -> return without further ado, esp no caching + // of results as they will leak + return undefined; + } + let disposableStore: DisposableStore | undefined = undefined; + const pid = this._references.createReferenceId({ + dispose() { + disposableStore?.dispose(); + }, + item: result + }); + + let acceptCommand: languages.Command | undefined = undefined; + if (result.accepted) { + if (!disposableStore) { + disposableStore = new DisposableStore(); + } + acceptCommand = this._commands.toInternal(result.accepted, disposableStore); + } + let rejectCommand: languages.Command | undefined = undefined; + if (result.rejected) { + if (!disposableStore) { + disposableStore = new DisposableStore(); + } + rejectCommand = this._commands.toInternal(result.rejected, disposableStore); + } + + const langResult: extHostProtocol.IdentifiableInlineEdit = { + pid, + text: result.text, + range: typeConvert.Range.from(result.range), + accepted: acceptCommand, + rejected: rejectCommand, + }; + + return langResult; + } + + disposeEdit(pid: number) { + const data = this._references.disposeReferenceId(pid); + data?.dispose(); + } + + constructor( + _extension: IExtensionDescription, + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.InlineEditProvider, + private readonly _commands: CommandsConverter, + ) { + } +} + class ReferenceMap { private readonly _references = new Map(); private _idPool = 1; @@ -1946,7 +2021,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter | InlineValuesAdapter | LinkedEditingRangeAdapter | InlayHintsAdapter | InlineCompletionAdapter - | DocumentOnDropEditAdapter | MappedEditsAdapter | NewSymbolNamesAdapter; + | DocumentOnDropEditAdapter | MappedEditsAdapter | NewSymbolNamesAdapter | InlineEditAdapter; class AdapterData { constructor( @@ -2424,6 +2499,23 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF this._withAdapter(handle, InlineCompletionAdapterBase, async adapter => { adapter.disposeCompletions(pid); }, undefined, undefined); } + // --- inline edit + + registerInlineEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.InlineEditProvider): vscode.Disposable { + const adapter = new InlineEditAdapter(extension, this._documents, provider, this._commands.converter); + const handle = this._addNewAdapter(adapter, extension); + this._proxy.$registerInlineEditProvider(handle, this._transformDocumentSelector(selector, extension), extension.identifier); + return this._createDisposable(handle); + } + + $provideInlineEdit(handle: number, resource: UriComponents, context: languages.IInlineEditContext, token: CancellationToken): Promise { + return this._withAdapter(handle, InlineEditAdapter, adapter => adapter.provideInlineEdits(URI.revive(resource), context, token), undefined, token); + } + + $freeInlineEdit(handle: number, pid: number): void { + this._withAdapter(handle, InlineEditAdapter, async adapter => { adapter.disposeEdit(pid); }, undefined, undefined); + } + // --- parameter hints registerSignatureHelpProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, metadataOrTriggerChars: string[] | vscode.SignatureHelpProviderMetadata): vscode.Disposable { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 39e91887625..906dc75413e 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4332,3 +4332,19 @@ export enum KeywordRecognitionStatus { } //#endregion + +//#region InlineEdit + +export class InlineEdit implements vscode.InlineEdit { + constructor( + public readonly text: string, + public readonly range: Range, + ) { } +} + +export enum InlineEditTriggerKind { + Invoke = 0, + Automatic = 1, +} + +//#endregion diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index a81d30aee5e..3a88ed4a23f 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -395,6 +395,13 @@ const apiMenus: IAPIMenu[] = [ supportsSubmenus: false, proposed: 'inlineCompletionsAdditions' }, + { + key: 'editor/inlineEdit/actions', + id: MenuId.InlineEditActions, + description: localize('inlineEdit.actions', "The actions shown when hovering on an inline edit"), + supportsSubmenus: false, + proposed: 'inlineEdit' + }, { key: 'editor/content', id: MenuId.EditorContent, diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 526ed79e092..12ddfb110ec 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -62,6 +62,7 @@ export const allApiProposals = Object.freeze({ handleIssueUri: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.handleIssueUri.d.ts', idToken: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.idToken.d.ts', inlineCompletionsAdditions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts', + inlineEdit: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineEdit.d.ts', interactive: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactive.d.ts', interactiveWindow: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactiveWindow.d.ts', ipc: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.ipc.d.ts', diff --git a/src/vscode-dts/vscode.proposed.inlineEdit.d.ts b/src/vscode-dts/vscode.proposed.inlineEdit.d.ts new file mode 100644 index 00000000000..90a1957e849 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.inlineEdit.d.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * 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 class InlineEdit { + + + /** + * The new text for this edit. + */ + readonly text: string; + + /** + * An range that will be replaced by the text of the inline edit. + * If change is only additive, this can be empty (same start and end position). + */ + readonly range: Range; + + /** + * An optional command that will be executed after applying the inline edit. + */ + accepted?: Command; + + /** + * An optional command that will be executed after rejecting the inline edit. + */ + rejected?: Command; + + /** + * Creates a new inline edit. + * + * @param text The new text for this edit. + * @param replaceRange An range that will be replaced by the text of the inline edit. + */ + constructor(text: string, range: Range); + } + + export interface InlineEditContext { + /** + * Describes how the inline edit was triggered. + */ + triggerKind: InlineEditTriggerKind; + } + + export enum InlineEditTriggerKind { + /** + * Completion was triggered explicitly by a user gesture. + * Return multiple completion items to enable cycling through them. + */ + Invoke = 0, + + /** + * Completion was triggered automatically while editing. + * It is sufficient to return a single completion item in this case. + */ + Automatic = 1, + } + + export interface InlineEditProvider { + /** + * Provide inline edit for the given document. + * + * @param document The document for which the inline edit are computed. + * @param context Additional context information about the request. + * @param token A cancellation token. + * @return An inline edit or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideInlineEdit(document: TextDocument, context: InlineEditContext, token: CancellationToken): ProviderResult; + } + + export namespace languages { + + /** + * Register a provider that can handle inline edits. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A provider that can handle inline edits. + * @return A {@link Disposable} that unregisters this provider when being disposed. + */ + export function registerInlineEditProvider(selector: DocumentSelector, provider: InlineEditProvider): Disposable; + + } +} From 881321da5cfac6fe0ce6894fe40b2ba6623cba0c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 09:22:53 -0600 Subject: [PATCH 1367/1897] fix aria label for terminal response editor --- .../chat/browser/terminalChatAccessibilityHelp.ts | 6 ++++-- .../terminalContrib/chat/browser/terminalChatWidget.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts index d851609de14..890fb103292 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -48,7 +48,8 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor): string { const keybindingService = accessor.get(IKeybindingService); const content = []; const openAccessibleViewKeybinding = keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel(); - const acceptCommandKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.RunCommand)?.getAriaLabel(); + const runCommandKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.RunCommand)?.getAriaLabel(); + const insertCommandKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.InsertCommand)?.getAriaLabel(); const makeRequestKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.MakeRequest)?.getAriaLabel(); //TODO: using this instead of the terminal command bc by definition the inline terminal chat is focused when this dialog is invoked. const startChatKeybinding = keybindingService.lookupKeybinding('inlineChat.start')?.getAriaLabel(); @@ -58,7 +59,8 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor): string { content.push(localize('inlineChat.results', "A result may contain a terminal command or just a message. In either case, the result will be announced.")); content.push(openAccessibleViewKeybinding ? localize('inlineChat.inspectResponseMessage', 'If just a message comes back, it can be inspected in the accessible view ({0}).', openAccessibleViewKeybinding) : localize('inlineChat.inspectResponseNoKb', 'With the input box focused, inspect the response in the accessible view via the Open Accessible View command, which is currently not triggerable by a keybinding.')); content.push(localize('inlineChat.inspectTerminalCommand', 'If a terminal command comes back, it can be inspected in an editor reached via Shift+Tab.')); - content.push(acceptCommandKeybinding ? localize('inlineChat.acceptCommand', 'With focus in the command editor, the Terminal: Accept Chat Command ({0}) action.', acceptCommandKeybinding) : localize('inlineChat.acceptCommandNoKb', 'Accept a command by tabbing to the button as the action is currently not triggerable by a keybinding.')); + content.push(runCommandKeybinding ? localize('inlineChat.runCommand', 'With focus in the input box or command editor, the Terminal: Run Chat Command ({0}) action.', runCommandKeybinding) : localize('inlineChat.runCommandNoKb', 'Run a command by tabbing to the button as the action is currently not triggerable by a keybinding.')); + content.push(insertCommandKeybinding ? localize('inlineChat.insertCommand', 'With focus in the input box command editor, the Terminal: Insert Chat Command ({0}) action.', insertCommandKeybinding) : localize('inlineChat.insertCommandNoKb', 'Insert a command by tabbing to the button as the action is currently not triggerable by a keybinding.')); content.push(localize('inlineChat.toolbar', "Use tab to reach conditional parts like commands, status, message responses and more.")); content.push(localize('chat.signals', "Accessibility Signals can be changed via settings with a prefix of signals.chat. By default, if a request takes more than 4 seconds, you will hear a sound indicating that progress is still occurring.")); return content.join('\n\n'); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 499d7e375da..2cc70c7eb07 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -255,7 +255,7 @@ class TerminalChatResponseEditor extends Disposable { if (verbose) { // TODO: Add verbose description } - return localize('terminalChatInput', "Terminal Chat Input"); + return localize('terminalResponseEditor', "Terminal Response Editor"); } private async _getTextModel(resource: URI): Promise { From 00124e9e5830e3efc897db71c781899f8a676295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Cie=C5=9Blak?= Date: Fri, 16 Feb 2024 16:40:34 +0100 Subject: [PATCH 1368/1897] Improve inline edit commands' preconditions (#205373) Improve inline edit commands preconditions --- src/vs/editor/contrib/inlineEdit/browser/commands.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/inlineEdit/browser/commands.ts b/src/vs/editor/contrib/inlineEdit/browser/commands.ts index 93f1f9d4a2c..11d6dfa2f22 100644 --- a/src/vs/editor/contrib/inlineEdit/browser/commands.ts +++ b/src/vs/editor/contrib/inlineEdit/browser/commands.ts @@ -19,7 +19,7 @@ export class AcceptInlineEdit extends EditorAction { id: inlineEditAcceptId, label: 'Accept Inline Edit', alias: 'Accept Inline Edit', - precondition: EditorContextKeys.writable, + precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineEditController.inlineEditVisibleContext), kbOpts: [ { weight: KeybindingWeight.EditorContrib + 1, @@ -43,15 +43,16 @@ export class AcceptInlineEdit extends EditorAction { export class TriggerInlineEdit extends EditorAction { constructor() { + const activeExpr = ContextKeyExpr.and(EditorContextKeys.writable, ContextKeyExpr.not(InlineEditController.inlineEditVisibleKey)); super({ id: 'editor.action.inlineEdit.trigger', label: 'Trigger Inline Edit', alias: 'Trigger Inline Edit', - precondition: EditorContextKeys.writable, + precondition: activeExpr, kbOpts: { weight: KeybindingWeight.EditorContrib + 1, primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Equal, - kbExpr: ContextKeyExpr.and(EditorContextKeys.writable, ContextKeyExpr.not(InlineEditController.inlineEditVisibleKey)) + kbExpr: activeExpr }, }); } @@ -124,15 +125,16 @@ export class JumpBackInlineEdit extends EditorAction { export class RejectInlineEdit extends EditorAction { constructor() { + const activeExpr = ContextKeyExpr.and(EditorContextKeys.writable, InlineEditController.inlineEditVisibleContext); super({ id: inlineEditRejectId, label: 'Reject Inline Edit', alias: 'Reject Inline Edit', - precondition: EditorContextKeys.writable, + precondition: activeExpr, kbOpts: { weight: KeybindingWeight.EditorContrib, primary: KeyCode.Escape, - kbExpr: ContextKeyExpr.and(EditorContextKeys.writable, InlineEditController.inlineEditVisibleContext) + kbExpr: activeExpr }, menuOpts: [{ menuId: MenuId.InlineEditToolbar, From 4ba66bf35e20a55e7261702371db549f2ad396bd Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 16 Feb 2024 16:09:04 +0000 Subject: [PATCH 1369/1897] Chat API: add 'command' to response history (#205377) --- .../src/singlefolder-tests/chat.test.ts | 39 ++++++++++++++----- .../api/common/extHostChatAgents2.ts | 2 +- src/vs/workbench/api/common/extHostTypes.ts | 3 +- .../vscode.proposed.chatAgents2.d.ts | 2 + 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index 6c0406aadbb..bdb033322eb 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -5,8 +5,8 @@ import * as assert from 'assert'; import 'mocha'; -import { CancellationToken, chat, ChatAgentRequest, ChatAgentResult2, ChatVariableLevel, Disposable, interactive, InteractiveSession, ProviderResult } from 'vscode'; -import { assertNoRpc, closeAllEditors, DeferredPromise, disposeAll } from '../utils'; +import { CancellationToken, ChatAgentContext, ChatAgentRequest, ChatAgentResult2, ChatVariableLevel, Disposable, Event, EventEmitter, InteractiveSession, ProviderResult, chat, interactive } from 'vscode'; +import { DeferredPromise, assertNoRpc, closeAllEditors, disposeAll } from '../utils'; suite('chat', () => { @@ -22,6 +22,15 @@ suite('chat', () => { }); function getDeferredForRequest(): DeferredPromise { + const deferred = new DeferredPromise(); + disposables.push(setupAgent()(request => deferred.complete(request.request))); + + return deferred; + } + + function setupAgent(): Event<{ request: ChatAgentRequest; context: ChatAgentContext }> { + const emitter = new EventEmitter<{ request: ChatAgentRequest; context: ChatAgentContext }>(); + disposables.push(); disposables.push(interactive.registerInteractiveSessionProvider('provider', { prepareSession: (_token: CancellationToken): ProviderResult => { return { @@ -31,9 +40,8 @@ suite('chat', () => { }, })); - const deferred = new DeferredPromise(); - const agent = chat.createChatAgent('agent', (request, _context, _progress, _token) => { - deferred.complete(request); + const agent = chat.createChatAgent('agent', (request, context, _progress, _token) => { + emitter.fire({ request, context }); return null; }); agent.isDefault = true; @@ -43,15 +51,26 @@ suite('chat', () => { } }; disposables.push(agent); - return deferred; + return emitter.event; } test('agent and slash command', async () => { - const deferred = getDeferredForRequest(); + const onRequest = setupAgent(); interactive.sendInteractiveRequestToProvider('provider', { message: '@agent /hello friend' }); - const request = await deferred.p; - assert.deepStrictEqual(request.command, 'hello'); - assert.strictEqual(request.prompt, 'friend'); + + let i = 0; + onRequest(request => { + if (i === 0) { + assert.deepStrictEqual(request.request.command, 'hello'); + assert.strictEqual(request.request.prompt, 'friend'); + i++; + interactive.sendInteractiveRequestToProvider('provider', { message: '@agent /hello friend' }); + } else { + assert.strictEqual(request.context.history.length, 1); + assert.strictEqual(request.context.history[0].agent.agent, 'agent'); + assert.strictEqual(request.context.history[0].command, 'hello'); + } + }); }); test('agent and variable', async () => { diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 1f40b04c924..399ff60d030 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -239,7 +239,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { // RESPONSE turn const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))); - res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, { extensionId: '', agent: h.request.agentId })); + res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, { extensionId: '', agent: h.request.agentId }, h.request.command)); } return res; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 906dc75413e..611314d600f 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4274,7 +4274,8 @@ export class ChatAgentResponseTurn implements vscode.ChatAgentResponseTurn { constructor( readonly response: ReadonlyArray, readonly result: vscode.ChatAgentResult2, - readonly agent: { extensionId: string; agent: string } + readonly agent: { extensionId: string; agent: string }, + readonly command?: string ) { } } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 63cad62d20a..93938b8ba68 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -54,6 +54,8 @@ declare module 'vscode' { */ readonly agent: { readonly extensionId: string; readonly agent: string }; + readonly command?: string; + private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: { extensionId: string; agent: string }); } From f12778f1837d80f6210e9157d0f853a94d08c0b0 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 16 Feb 2024 17:14:18 +0100 Subject: [PATCH 1370/1897] Fix statusbar actions (#205380) fix statusbar actions --- src/vs/workbench/contrib/scm/browser/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index d44c36c332b..6f20e40b24c 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -150,7 +150,7 @@ export class StatusBarAction extends Action { class StatusBarActionViewItem extends ActionViewItem { constructor(action: StatusBarAction, options: IBaseActionViewItemOptions) { - super(null, action, options); + super(null, action, { ...options, icon: false, label: true }); } protected override updateLabel(): void { From ee1cb48bb1005d7299e97f8445783a7c976f9c08 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 10:17:11 -0600 Subject: [PATCH 1371/1897] add discard action --- .../chat/browser/terminalChat.ts | 3 +- .../chat/browser/terminalChatActions.ts | 28 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index d409361d517..a2349bc25c1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -7,7 +7,8 @@ import { MenuId } from 'vs/platform/actions/common/actions'; export const enum TerminalChatCommandId { Start = 'workbench.action.terminal.chat.start', - Hide = 'workbench.action.terminal.chat.close', + Close = 'workbench.action.terminal.chat.close', + Discard = 'workbench.action.terminal.chat.discard', MakeRequest = 'workbench.action.terminal.chat.makeRequest', Cancel = 'workbench.action.terminal.chat.cancel', FeedbackHelpful = 'workbench.action.terminal.chat.feedbackHelpful', diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index d1586f8c4ba..abf356fbdff 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -41,7 +41,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalChatCommandId.Hide, + id: TerminalChatCommandId.Close, title: localize2('closeChat', 'Close Chat'), keybinding: { primary: KeyCode.Escape, @@ -70,6 +70,32 @@ registerActiveXtermAction({ }); +registerActiveXtermAction({ + id: TerminalChatCommandId.Discard, + title: localize2('discard', 'Discard'), + icon: Codicon.discard, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_STATUS, + group: '0_main', + order: 2, + when: ContextKeyExpr.and(TerminalContextKeys.chatFocused, + TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand)) + }, + f1: true, + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + TerminalContextKeys.chatFocused, + TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) + ), + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.clear(); + } +}); registerActiveXtermAction({ From 772e2113447bbb131b3a8c55642aee9905e8abf4 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 16 Feb 2024 16:54:57 +0100 Subject: [PATCH 1372/1897] Small code cleanup --- .../features/revertButtonsFeature.ts | 122 ++++++++++-------- 1 file changed, 66 insertions(+), 56 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts index 084706bf613..10673e3cdcb 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts @@ -19,6 +19,8 @@ import { RangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { GlyphMarginLane } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; +const emptyArr: never[] = []; + export class RevertButtonsFeature extends Disposable { constructor( private readonly _editors: DiffEditorEditors, @@ -27,71 +29,79 @@ export class RevertButtonsFeature extends Disposable { private readonly _widget: DiffEditorWidget ) { super(); + } - const emptyArr: never[] = []; - const selectedDiffs = derived(this, (reader) => { - /** @description selectedDiffs */ - const model = this._diffModel.read(reader); - const diff = model?.diff.read(reader); - if (!diff) { return emptyArr; } + private readonly _selectedDiffs = derived(this, (reader) => { + /** @description selectedDiffs */ + const model = this._diffModel.read(reader); + const diff = model?.diff.read(reader); + // Return `emptyArr` because it is a constant. [] is always a new array and would trigger a change. + if (!diff) { return emptyArr; } + const selections = this._editors.modifiedSelections.read(reader); + if (selections.every(s => s.isEmpty())) { return emptyArr; } + + const selectedLineNumbers = new LineRangeSet(selections.map(s => LineRange.fromRangeInclusive(s))); + + const selectedMappings = diff.mappings.filter(m => + m.lineRangeMapping.innerChanges && selectedLineNumbers.intersects(m.lineRangeMapping.modified) + ); + const result = selectedMappings.map(mapping => ({ + mapping, + rangeMappings: mapping.lineRangeMapping.innerChanges!.filter( + c => selections.some(s => Range.areIntersecting(c.modifiedRange, s)) + ) + })); + if (result.length === 0 || result.every(r => r.rangeMappings.length === 0)) { return emptyArr; } + return result; + }); + + private readonly _revertButtons = this._register(autorunWithStore((reader, store) => { + const model = this._diffModel.read(reader); + const diff = model?.diff.read(reader); + if (!model || !diff) { return; } + if (!this._options.shouldRenderRevertArrows.read(reader)) { return; } + if (model.movedTextToCompare.read(reader)) { return; } + + const glyphWidgetsModified: IGlyphMarginWidget[] = []; + + const selectedDiffs = this._selectedDiffs.read(reader); + const selectedDiffsSet = new Set(selectedDiffs.map(d => d.mapping)); + + if (selectedDiffs.length > 0) { + // The button to revert the selection const selections = this._editors.modifiedSelections.read(reader); - if (selections.every(s => s.isEmpty())) { - return emptyArr; - } + const btn = store.add(new RevertButton( + selections[selections.length - 1].positionLineNumber, + this._widget, + selectedDiffs.flatMap(d => d.rangeMappings), + true + )); + this._editors.modified.addGlyphMarginWidget(btn); + glyphWidgetsModified.push(btn); + } - const lineRanges = new LineRangeSet(selections.map(s => LineRange.fromRangeInclusive(s))); - - const mappings = diff.mappings.filter(m => m.lineRangeMapping.innerChanges && lineRanges.intersects(m.lineRangeMapping.modified)); - - const result = mappings.map(mapping => ({ - mapping, - rangeMappings: mapping.lineRangeMapping.innerChanges!.filter(c => selections.some(s => Range.areIntersecting(c.modifiedRange, s))) - })); - if (result.length === 0 || result.every(r => r.rangeMappings.length === 0)) { return emptyArr; } - return result; - }); - - this._register(autorunWithStore((reader, store) => { - const model = this._diffModel.read(reader); - const diff = model?.diff.read(reader); - if (!model || !diff) { return; } - const movedTextToCompare = this._diffModel.read(reader)!.movedTextToCompare.read(reader); - if (movedTextToCompare) { return; } - if (!this._options.shouldRenderRevertArrows.read(reader)) { return; } - - const glyphWidgetsModified: IGlyphMarginWidget[] = []; - - const selectedDiffs_ = selectedDiffs.read(reader); - const diffsSet = new Set(selectedDiffs_.map(d => d.mapping)); - - if (selectedDiffs_.length > 0) { - const selections = this._editors.modifiedSelections.read(reader); - - const btn = store.add(new RevertButton(selections[selections.length - 1].positionLineNumber, this._widget, selectedDiffs_.flatMap(d => d.rangeMappings), true)); + for (const m of diff.mappings) { + if (selectedDiffsSet.has(m)) { continue; } + if (!m.lineRangeMapping.modified.isEmpty && m.lineRangeMapping.innerChanges) { + const btn = store.add(new RevertButton( + m.lineRangeMapping.modified.startLineNumber, + this._widget, + m.lineRangeMapping.innerChanges, + false + )); this._editors.modified.addGlyphMarginWidget(btn); glyphWidgetsModified.push(btn); } + } - for (const m of diff.mappings) { - if (diffsSet.has(m)) { - continue; - } - if (!m.lineRangeMapping.modified.isEmpty && m.lineRangeMapping.innerChanges) { - const btn = store.add(new RevertButton(m.lineRangeMapping.modified.startLineNumber, this._widget, m.lineRangeMapping.innerChanges, false)); - this._editors.modified.addGlyphMarginWidget(btn); - glyphWidgetsModified.push(btn); - } + store.add(toDisposable(() => { + for (const w of glyphWidgetsModified) { + this._editors.modified.removeGlyphMarginWidget(w); } - - store.add(toDisposable(() => { - for (const w of glyphWidgetsModified) { - this._editors.modified.removeGlyphMarginWidget(w); - } - })); })); - } + })); } export class RevertButton extends Disposable implements IGlyphMarginWidget { @@ -102,7 +112,7 @@ export class RevertButton extends Disposable implements IGlyphMarginWidget { getId(): string { return this._id; } private readonly _domNode = h('div.revertButton', { - title: this._selection + title: this._revertSelection ? localize('revertSelectedChanges', 'Revert Selected Changes') : localize('revertChange', 'Revert Change') }, @@ -113,7 +123,7 @@ export class RevertButton extends Disposable implements IGlyphMarginWidget { private readonly _lineNumber: number, private readonly _widget: DiffEditorWidget, private readonly _diffs: RangeMapping[], - private readonly _selection: boolean, + private readonly _revertSelection: boolean, ) { super(); From 4ab700330c138d400bb21d3670c7f66f745fe33a Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 16 Feb 2024 17:01:40 +0100 Subject: [PATCH 1373/1897] Fixes CI --- .../features/revertButtonsFeature.ts | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts index 10673e3cdcb..06a7ade202d 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts @@ -29,6 +29,53 @@ export class RevertButtonsFeature extends Disposable { private readonly _widget: DiffEditorWidget ) { super(); + + this._register(autorunWithStore((reader, store) => { + const model = this._diffModel.read(reader); + const diff = model?.diff.read(reader); + if (!model || !diff) { return; } + if (!this._options.shouldRenderRevertArrows.read(reader)) { return; } + if (model.movedTextToCompare.read(reader)) { return; } + + const glyphWidgetsModified: IGlyphMarginWidget[] = []; + + const selectedDiffs = this._selectedDiffs.read(reader); + const selectedDiffsSet = new Set(selectedDiffs.map(d => d.mapping)); + + if (selectedDiffs.length > 0) { + // The button to revert the selection + const selections = this._editors.modifiedSelections.read(reader); + + const btn = store.add(new RevertButton( + selections[selections.length - 1].positionLineNumber, + this._widget, + selectedDiffs.flatMap(d => d.rangeMappings), + true + )); + this._editors.modified.addGlyphMarginWidget(btn); + glyphWidgetsModified.push(btn); + } + + for (const m of diff.mappings) { + if (selectedDiffsSet.has(m)) { continue; } + if (!m.lineRangeMapping.modified.isEmpty && m.lineRangeMapping.innerChanges) { + const btn = store.add(new RevertButton( + m.lineRangeMapping.modified.startLineNumber, + this._widget, + m.lineRangeMapping.innerChanges, + false + )); + this._editors.modified.addGlyphMarginWidget(btn); + glyphWidgetsModified.push(btn); + } + } + + store.add(toDisposable(() => { + for (const w of glyphWidgetsModified) { + this._editors.modified.removeGlyphMarginWidget(w); + } + })); + })); } private readonly _selectedDiffs = derived(this, (reader) => { @@ -55,53 +102,6 @@ export class RevertButtonsFeature extends Disposable { if (result.length === 0 || result.every(r => r.rangeMappings.length === 0)) { return emptyArr; } return result; }); - - private readonly _revertButtons = this._register(autorunWithStore((reader, store) => { - const model = this._diffModel.read(reader); - const diff = model?.diff.read(reader); - if (!model || !diff) { return; } - if (!this._options.shouldRenderRevertArrows.read(reader)) { return; } - if (model.movedTextToCompare.read(reader)) { return; } - - const glyphWidgetsModified: IGlyphMarginWidget[] = []; - - const selectedDiffs = this._selectedDiffs.read(reader); - const selectedDiffsSet = new Set(selectedDiffs.map(d => d.mapping)); - - if (selectedDiffs.length > 0) { - // The button to revert the selection - const selections = this._editors.modifiedSelections.read(reader); - - const btn = store.add(new RevertButton( - selections[selections.length - 1].positionLineNumber, - this._widget, - selectedDiffs.flatMap(d => d.rangeMappings), - true - )); - this._editors.modified.addGlyphMarginWidget(btn); - glyphWidgetsModified.push(btn); - } - - for (const m of diff.mappings) { - if (selectedDiffsSet.has(m)) { continue; } - if (!m.lineRangeMapping.modified.isEmpty && m.lineRangeMapping.innerChanges) { - const btn = store.add(new RevertButton( - m.lineRangeMapping.modified.startLineNumber, - this._widget, - m.lineRangeMapping.innerChanges, - false - )); - this._editors.modified.addGlyphMarginWidget(btn); - glyphWidgetsModified.push(btn); - } - } - - store.add(toDisposable(() => { - for (const w of glyphWidgetsModified) { - this._editors.modified.removeGlyphMarginWidget(w); - } - })); - })); } export class RevertButton extends Disposable implements IGlyphMarginWidget { From 7f07489455fda8bc1841b98b9b7e58b20b7ee3ef Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 16 Feb 2024 17:10:21 +0100 Subject: [PATCH 1374/1897] Small improvement --- .../browser/widget/diffEditor/features/revertButtonsFeature.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts index 06a7ade202d..c82167c3d13 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts @@ -31,10 +31,10 @@ export class RevertButtonsFeature extends Disposable { super(); this._register(autorunWithStore((reader, store) => { + if (!this._options.shouldRenderRevertArrows.read(reader)) { return; } const model = this._diffModel.read(reader); const diff = model?.diff.read(reader); if (!model || !diff) { return; } - if (!this._options.shouldRenderRevertArrows.read(reader)) { return; } if (model.movedTextToCompare.read(reader)) { return; } const glyphWidgetsModified: IGlyphMarginWidget[] = []; From 29924c918fc21025c13354b62ff260678fda83db Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 10:32:42 -0600 Subject: [PATCH 1375/1897] add feedback context key, get styling to apply --- .../contrib/terminal/common/terminalContextKey.ts | 6 +++++- .../chat/browser/terminalChatActions.ts | 2 ++ .../chat/browser/terminalChatController.ts | 11 ++++++++--- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index d823a5b7fd9..0c577a9df8a 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -46,7 +46,8 @@ export const enum TerminalContextKeyStrings { ChatAgentRegistered = 'terminalChatAgentRegistered', ChatResponseEditorFocused = 'terminalChatResponseEditorFocused', ChatResponseType = 'terminalChatResponseType', - ChatResponseSupportsIssueReporting = 'terminalChatResponseSupportsIssueReporting' + ChatResponseSupportsIssueReporting = 'terminalChatResponseSupportsIssueReporting', + ChatSessionResponseVote = 'terminalChatSessionResponseVote', } export const enum TerminalChatResponseTypes { @@ -196,4 +197,7 @@ export namespace TerminalContextKeys { /** Whether the response supports issue reporting */ export const chatResponseSupportsIssueReporting = new RawContextKey(TerminalContextKeyStrings.ChatResponseSupportsIssueReporting, false, localize('chatResponseSupportsIssueReportingContextKey', "Whether the response supports issue reporting")); + + /** The chat vote, if any for the response, if any */ + export const chatSessionResponseVote = new RawContextKey(TerminalContextKeyStrings.ChatSessionResponseVote, undefined, { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index abf356fbdff..cd984a4d61e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -255,6 +255,7 @@ registerActiveXtermAction({ TerminalContextKeys.chatResponseType.notEqualsTo(undefined) ), icon: Codicon.thumbsup, + toggled: TerminalContextKeys.chatSessionResponseVote.isEqualTo('up'), menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', @@ -277,6 +278,7 @@ registerActiveXtermAction({ ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatResponseType.notEqualsTo(undefined), ), + toggled: TerminalContextKeys.chatSessionResponseVote.isEqualTo('down'), icon: Codicon.thumbsdown, menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 70ee41baa68..ca3bdbab893 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -55,7 +55,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _requestActiveContextKey!: IContextKey; private readonly _terminalAgentRegisteredContextKey!: IContextKey; private readonly _responseTypeContextKey!: IContextKey; - private readonly __responseSupportsIssueReportingContextKey!: IContextKey; + private readonly _responseSupportsIssueReportingContextKey!: IContextKey; + private readonly _sessionResponseVoteContextKey!: IContextKey; private _requestId: number = 0; @@ -97,7 +98,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._requestActiveContextKey = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); this._terminalAgentRegisteredContextKey = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); this._responseTypeContextKey = TerminalContextKeys.chatResponseType.bindTo(this._contextKeyService); - this.__responseSupportsIssueReportingContextKey = TerminalContextKeys.chatResponseSupportsIssueReporting.bindTo(this._contextKeyService); + this._responseSupportsIssueReportingContextKey = TerminalContextKeys.chatResponseSupportsIssueReporting.bindTo(this._contextKeyService); + this._sessionResponseVoteContextKey = TerminalContextKeys.chatSessionResponseVote.bindTo(this._contextKeyService); if (!this._chatAgentService.hasAgent(this._terminalAgentId)) { this._register(this._chatAgentService.onDidChangeAgents(() => { @@ -144,6 +146,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (helpful === undefined) { action = { kind: 'bug' }; } else { + this._sessionResponseVoteContextKey.set(helpful ? 'up' : 'down'); action = { kind: 'vote', direction: helpful ? InteractiveSessionVoteDirection.Up : InteractiveSessionVoteDirection.Down }; } // TODO:extract into helper method @@ -204,6 +207,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.hide(); this._chatWidget?.rawValue?.setValue(undefined); this._responseTypeContextKey.reset(); + this._sessionResponseVoteContextKey.reset(); + this._requestActiveContextKey.reset(); } private updateModel(): void { @@ -274,7 +279,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } const supportIssueReporting = this._currentRequest?.response?.agent?.metadata?.supportIssueReporting; if (supportIssueReporting !== undefined) { - this.__responseSupportsIssueReportingContextKey.set(supportIssueReporting); + this._responseSupportsIssueReportingContextKey.set(supportIssueReporting); } this._lastResponseContent = responseContent; } From 12281b11be58d840611d60415c105c7507e63ae2 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 16 Feb 2024 10:20:16 -0800 Subject: [PATCH 1376/1897] testing: adopt hover delegate (#205386) --- .../browser/ui/iconLabel/iconLabelHover.ts | 5 +- .../testing/browser/testCoverageBars.ts | 61 ++++++++----------- 2 files changed, 27 insertions(+), 39 deletions(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts index 23878f78ea8..508a643abb4 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts @@ -31,6 +31,7 @@ export function setupNativeHover(htmlElement: HTMLElement, tooltip: string | ITo } type IHoverContent = string | ITooltipMarkdownString | HTMLElement | undefined; +type IHoverContentOrFactory = IHoverContent | (() => IHoverContent); type IResolvedHoverContent = IMarkdownString | string | HTMLElement | undefined; /** @@ -162,7 +163,7 @@ class UpdatableHoverWidget implements IDisposable { } } -export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, content: IHoverContent, options?: IUpdatableHoverOptions): ICustomHover { +export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, content: IHoverContentOrFactory, options?: IUpdatableHoverOptions): ICustomHover { let hoverPreparation: IDisposable | undefined; let hoverWidget: UpdatableHoverWidget | undefined; @@ -186,7 +187,7 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM return new TimeoutTimer(async () => { if (!hoverWidget || hoverWidget.isDisposed) { hoverWidget = new UpdatableHoverWidget(hoverDelegate, target || htmlElement, delay > 0); - await hoverWidget.update(content, focus, options); + await hoverWidget.update(typeof content === 'function' ? content() : content, focus, options); } }, delay); }; diff --git a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts index 08cb528de32..7261e5e1021 100644 --- a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts +++ b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts @@ -4,8 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { h } from 'vs/base/browser/dom'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, ITooltipMarkdownString, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { assertNever } from 'vs/base/common/assert'; -import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { MarkdownString } from 'vs/base/common/htmlContent'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { clamp } from 'vs/base/common/numbers'; @@ -20,7 +22,6 @@ import { ITestingCoverageBarThresholds, TestingConfigKeys, TestingDisplayedCover import { AbstractFileCoverage, getTotalCoveragePercent } from 'vs/workbench/contrib/testing/common/testCoverage'; import { ITestCoverageService } from 'vs/workbench/contrib/testing/common/testCoverageService'; import { ICoveredCount } from 'vs/workbench/contrib/testing/common/testTypes'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; export interface TestCoverageBarsOptions { /** @@ -62,6 +63,7 @@ export class ManagedTestCoverageBars extends Disposable { }); private readonly visibleStore = this._register(new DisposableStore()); + private readonly customHovers: ICustomHover[] = []; /** Gets whether coverage is currently visible for the resource. */ public get visible() { @@ -70,36 +72,13 @@ export class ManagedTestCoverageBars extends Disposable { constructor( protected readonly options: TestCoverageBarsOptions, - @IHoverService private readonly hoverService: IHoverService, @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(); } - private attachHover(target: HTMLElement, factory: (coverage: CoverageBarSource) => string | IMarkdownString | undefined) { - target.onmouseenter = () => { - if (!this._coverage) { - return; - } - - const content = factory(this._coverage); - if (!content) { - return; - } - - const hover = this.hoverService.showHover({ - content, - target, - appearance: { - showPointer: true, - compact: true, - skipFadeInAnimation: true, - } - }); - if (hover) { - this.visibleStore.add(hover); - } - }; + private attachHover(target: HTMLElement, factory: (coverage: CoverageBarSource) => string | ITooltipMarkdownString | undefined) { + this._register(setupCustomHover(getDefaultHoverDelegate('element'), target, () => this._coverage && factory(this._coverage))); } public setCoverageInfo(coverage: CoverageBarSource | undefined) { @@ -107,6 +86,7 @@ export class ManagedTestCoverageBars extends Disposable { if (!coverage) { if (this._coverage) { this._coverage = undefined; + this.customHovers.forEach(c => c.hide()); ds.clear(); } return; @@ -218,15 +198,23 @@ const displayPercent = (value: number, precision = 2) => { return `${display}%`; }; -const stmtCoverageText = (coverage: CoverageBarSource) => localize('statementCoverage', '{0}/{1} statements covered ({2})', coverage.statement.covered, coverage.statement.total, displayPercent(percent(coverage.statement))); -const fnCoverageText = (coverage: CoverageBarSource) => coverage.declaration && localize('functionCoverage', '{0}/{1} functions covered ({2})', coverage.declaration.covered, coverage.declaration.total, displayPercent(percent(coverage.declaration))); -const branchCoverageText = (coverage: CoverageBarSource) => coverage.branch && localize('branchCoverage', '{0}/{1} branches covered ({2})', coverage.branch.covered, coverage.branch.total, displayPercent(percent(coverage.branch))); +const nf = new Intl.NumberFormat(); +const stmtCoverageText = (coverage: CoverageBarSource) => localize('statementCoverage', '{0}/{1} statements covered ({2})', nf.format(coverage.statement.covered), nf.format(coverage.statement.total), displayPercent(percent(coverage.statement))); +const fnCoverageText = (coverage: CoverageBarSource) => coverage.declaration && localize('functionCoverage', '{0}/{1} functions covered ({2})', nf.format(coverage.declaration.covered), nf.format(coverage.declaration.total), displayPercent(percent(coverage.declaration))); +const branchCoverageText = (coverage: CoverageBarSource) => coverage.branch && localize('branchCoverage', '{0}/{1} branches covered ({2})', nf.format(coverage.branch.covered), nf.format(coverage.branch.total), displayPercent(percent(coverage.branch))); -const getOverallHoverText = (coverage: CoverageBarSource) => new MarkdownString([ - stmtCoverageText(coverage), - fnCoverageText(coverage), - branchCoverageText(coverage), -].filter(isDefined).join('\n\n')); +const getOverallHoverText = (coverage: CoverageBarSource): ITooltipMarkdownString => { + const str = [ + stmtCoverageText(coverage), + fnCoverageText(coverage), + branchCoverageText(coverage), + ].filter(isDefined).join('\n\n'); + + return { + markdown: new MarkdownString().appendText(str), + markdownNotSupportedFallback: str + }; +}; /** * Renders test coverage bars for a resource in the given container. It will @@ -237,11 +225,10 @@ export class ExplorerTestCoverageBars extends ManagedTestCoverageBars implements constructor( options: TestCoverageBarsOptions, - @IHoverService hoverService: IHoverService, @IConfigurationService configurationService: IConfigurationService, @ITestCoverageService testCoverageService: ITestCoverageService, ) { - super(options, hoverService, configurationService); + super(options, configurationService); const isEnabled = observeTestingConfiguration(configurationService, TestingConfigKeys.ShowCoverageInExplorer); From d5a79d5abe3239e61e332aeea88e38938d574419 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 16 Feb 2024 10:21:51 -0800 Subject: [PATCH 1377/1897] rename value property name (#205387) --- .../browser/telemetry.contribution.ts | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index 68275f3ea20..a2d81dfbe73 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -304,7 +304,7 @@ class ConfigurationTelemetryContribution extends Disposable implements IWorkbenc private reportTelemetry(key: string, target: ConfigurationTarget.USER_LOCAL | ConfigurationTarget.WORKSPACE): void { type UpdatedSettingEvent = { - value: string | undefined; + settingValue: string | undefined; source: string; }; const source = ConfigurationTargetToString(target); @@ -315,90 +315,90 @@ class ConfigurationTelemetryContribution extends Disposable implements IWorkbenc this.telemetryService.publicLog2('workbench.activityBar.location', { value: this.getValueToReport(key, target), source }); + }>('workbench.activityBar.location', { settingValue: this.getValueToReport(key, target), source }); return; case AutoUpdateConfigurationKey: this.telemetryService.publicLog2('extensions.autoUpdate', { value: this.getValueToReport(key, target), source }); + }>('extensions.autoUpdate', { settingValue: this.getValueToReport(key, target), source }); return; case 'files.autoSave': this.telemetryService.publicLog2('files.autoSave', { value: this.getValueToReport(key, target), source }); + }>('files.autoSave', { settingValue: this.getValueToReport(key, target), source }); return; case 'editor.stickyScroll.enabled': this.telemetryService.publicLog2('editor.stickyScroll.enabled', { value: this.getValueToReport(key, target), source }); + }>('editor.stickyScroll.enabled', { settingValue: this.getValueToReport(key, target), source }); return; case KEYWORD_ACTIVIATION_SETTING_ID: this.telemetryService.publicLog2('accessibility.voice.keywordActivation', { value: this.getValueToReport(key, target), source }); + }>('accessibility.voice.keywordActivation', { settingValue: this.getValueToReport(key, target), source }); return; case 'window.zoomLevel': this.telemetryService.publicLog2('window.zoomLevel', { value: this.getValueToReport(key, target), source }); + }>('window.zoomLevel', { settingValue: this.getValueToReport(key, target), source }); return; case 'window.zoomPerWindow': this.telemetryService.publicLog2('window.zoomPerWindow', { value: this.getValueToReport(key, target), source }); + }>('window.zoomPerWindow', { settingValue: this.getValueToReport(key, target), source }); return; case 'window.titleBarStyle': this.telemetryService.publicLog2('window.titleBarStyle', { value: this.getValueToReport(key, target), source }); + }>('window.titleBarStyle', { settingValue: this.getValueToReport(key, target), source }); return; case 'window.customTitleBarVisibility': this.telemetryService.publicLog2('window.customTitleBarVisibility', { value: this.getValueToReport(key, target), source }); + }>('window.customTitleBarVisibility', { settingValue: this.getValueToReport(key, target), source }); return; case 'window.nativeTabs': this.telemetryService.publicLog2('window.nativeTabs', { value: this.getValueToReport(key, target), source }); + }>('window.nativeTabs', { settingValue: this.getValueToReport(key, target), source }); return; } } From 0cbb15fb632a5e8933f7b600d2849c3f60e5653e Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Fri, 16 Feb 2024 10:29:13 -0800 Subject: [PATCH 1378/1897] Add Quick pick for notebook indentation status bar entry (#205052) * initial quick pick setup + indent multiEditorActions * finish convert whitespace commands for notebook scope * fix leaked disposable * fix setTimeout for indentation quickpick --- .../editorStatusBar/editorStatusBar.ts | 32 ++- .../browser/controller/editActions.ts | 92 +++++-- .../controller/notebookIndentationActions.ts | 257 ++++++++++++++++++ .../notebook/common/notebookEditorInput.ts | 6 + 4 files changed, 357 insertions(+), 30 deletions(-) create mode 100644 src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index 68b5eba7d19..9900e41b0c3 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -3,21 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, registerWorkbenchContribution2, WorkbenchPhase } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { CENTER_ACTIVE_CELL } from 'vs/workbench/contrib/notebook/browser/contrib/navigation/arrow'; import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -// import { SELECT_NOTEBOOK_INDENTATION_ID } from 'vs/workbench/contrib/notebook/browser/controller/editActions'; -import { getNotebookEditorFromEditorPane, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { SELECT_NOTEBOOK_INDENTATION_ID } from 'vs/workbench/contrib/notebook/browser/controller/editActions'; +import { INotebookEditor, getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { NotebookCellsChangeType, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -283,6 +283,10 @@ export class NotebookIndentationStatus extends Disposable implements IWorkbenchC const activeEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); if (activeEditor) { this._show(activeEditor); + this._itemDisposables.add(activeEditor.onDidChangeSelection(() => { + this._accessor.clear(); + this._show(activeEditor); + })); } else { this._accessor.clear(); } @@ -294,11 +298,14 @@ export class NotebookIndentationStatus extends Disposable implements IWorkbenchC return; } - const cellEditorOverrides = this._configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations) as any; - - const indentSize = cellEditorOverrides['editor.indentSize'] ?? this._configurationService.getValue('editor.indentSize'); - const tabSize = cellEditorOverrides['editor.tabSize'] ?? this._configurationService.getValue('editor.tabSize'); - const insertSpaces = cellEditorOverrides['editor.insertSpaces'] ?? this._configurationService.getValue('editor.insertSpaces'); + const cellOptions = editor.getActiveCell()?.textModel?.getOptions(); + if (!cellOptions) { + this._accessor.clear(); + return; + } + const indentSize = cellOptions?.indentSize; + const tabSize = cellOptions?.tabSize; + const insertSpaces = cellOptions?.insertSpaces; const width = typeof indentSize === 'number' ? indentSize : tabSize; @@ -313,9 +320,8 @@ export class NotebookIndentationStatus extends Disposable implements IWorkbenchC name: nls.localize('notebook.indentation', "Notebook Indentation"), text: newText, ariaLabel: newText, - // tooltip: nls.localize('selectNotebookIndentation', "Select Notebook Indentation"), - tooltip: nls.localize('selectNotebookIndentation', "Notebook Indentation"), - // command: SELECT_NOTEBOOK_INDENTATION_ID // TODO@Yoyokrazy -- finish hooking this up + tooltip: nls.localize('selectNotebookIndentation', "Select Indentation"), + command: SELECT_NOTEBOOK_INDENTATION_ID }; if (!this._accessor.value) { diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index f056d2e6c7f..86ffcc2b334 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -6,34 +6,35 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Mimes } from 'vs/base/common/mime'; import { URI } from 'vs/base/common/uri'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ILanguageService } from 'vs/editor/common/languages/language'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/model'; -import { ILanguageService } from 'vs/editor/common/languages/language'; import { localize, localize2 } from 'vs/nls'; import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IConfirmationResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; -import { changeCellToKind, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; -import { CellToolbarOrder, CELL_TITLE_CELL_GROUP_ID, CELL_TITLE_OUTPUT_GROUP_ID, executeNotebookCondition, INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, findTargetCellEditor } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; -import { CellEditState, CHANGE_CELL_LANGUAGE, DETECT_CELL_LANGUAGE, QUIT_EDIT_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { CellEditType, CellKind, ICellEditOperation, NotebookCellExecutionState, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; -import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService'; -import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; -import { IDialogService, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; - +import { changeCellToKind, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; +import { CELL_TITLE_CELL_GROUP_ID, CELL_TITLE_OUTPUT_GROUP_ID, CellToolbarOrder, INotebookActionContext, INotebookCellActionContext, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, NotebookAction, NotebookCellAction, executeNotebookCondition, findTargetCellEditor } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; +import { NotebookChangeTabDisplaySize, NotebookIndentUsingSpaces, NotebookIndentUsingTabs, NotebookIndentationToSpacesAction, NotebookIndentationToTabsAction } from 'vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions'; +import { CHANGE_CELL_LANGUAGE, CellEditState, DETECT_CELL_LANGUAGE, QUIT_EDIT_CELL_COMMAND_ID, getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditType, CellKind, ICellEditOperation, NotebookCellExecutionState, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService'; +import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; const CLEAR_ALL_CELLS_OUTPUTS_COMMAND_ID = 'notebook.clearAllCellsOutputs'; const EDIT_CELL_COMMAND_ID = 'notebook.cell.edit'; @@ -562,3 +563,60 @@ async function setCellToLanguage(languageId: string, context: IChangeCellContext ); } } + +registerAction2(class SelectNotebookIndentation extends NotebookAction { + constructor() { + super({ + id: SELECT_NOTEBOOK_INDENTATION_ID, + title: localize2('selectNotebookIndentation', 'Select Indentation'), + f1: true, + precondition: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { + await this.showNotebookIndentationPicker(accessor, context); + } + + private async showNotebookIndentationPicker(accessor: ServicesAccessor, context: INotebookActionContext) { + const quickInputService = accessor.get(IQuickInputService); + const editorService = accessor.get(IEditorService); + const instantiationService = accessor.get(IInstantiationService); + + const activeNotebook = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + if (!activeNotebook || activeNotebook.isDisposed) { + return quickInputService.pick([{ label: localize('noNotebookEditor', "No notebook editor active at this time") }]); + } + + if (activeNotebook.isReadOnly) { + return quickInputService.pick([{ label: localize('noWritableCodeEditor', "The active notebook editor is read-only.") }]); + } + + const picks: QuickPickInput[] = [ + new NotebookIndentUsingTabs(), // indent using tabs + new NotebookIndentUsingSpaces(), // indent using spaces + new NotebookChangeTabDisplaySize(), // change tab size + new NotebookIndentationToTabsAction(), // convert indentation to tabs + new NotebookIndentationToSpacesAction() // convert indentation to spaces + ].map(item => { + return { + id: item.desc.id, + label: item.desc.title.toString(), + run: () => { + instantiationService.invokeFunction(item.run); + } + }; + }); + + picks.splice(3, 0, { type: 'separator', label: localize('indentConvert', "convert file") }); + picks.unshift({ type: 'separator', label: localize('indentView', "change view") }); + + const action = await quickInputService.pick(picks, { placeHolder: localize('pickAction', "Select Action"), matchOnDetail: true }); + if (!action) { + return; + } + action.run(); + context.notebookEditor.focus(); + return; + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts new file mode 100644 index 00000000000..58d4bf63b6d --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts @@ -0,0 +1,257 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { DisposableStore } from 'vs/base/common/lifecycle'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; +import { Range } from 'vs/editor/common/core/range'; +import { ITextModel } from 'vs/editor/common/model'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; +import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { isNotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +export class NotebookIndentUsingTabs extends Action2 { + public static readonly ID = 'notebook.action.indentUsingTabs'; + + constructor() { + super({ + id: NotebookIndentUsingSpaces.ID, + title: nls.localize('indentUsingTabs', "Indent Using Tabs"), + precondition: undefined, + }); + } + + override run(accessor: ServicesAccessor, ...args: any[]): void { + changeNotebookIndentation(accessor, false, false); + } +} + +export class NotebookIndentUsingSpaces extends Action2 { + public static readonly ID = 'notebook.action.indentUsingSpaces'; + + constructor() { + super({ + id: NotebookIndentUsingSpaces.ID, + title: nls.localize('indentUsingSpaces', "Indent Using Spaces"), + precondition: undefined, + }); + } + + override run(accessor: ServicesAccessor, ...args: any[]): void { + changeNotebookIndentation(accessor, true, false); + } +} + +export class NotebookChangeTabDisplaySize extends Action2 { + public static readonly ID = 'notebook.action.changeTabDisplaySize'; + + constructor() { + super({ + id: NotebookIndentUsingSpaces.ID, + title: nls.localize('changeTabDisplaySize', "Change Tab Display Size"), + precondition: undefined, + }); + } + + override run(accessor: ServicesAccessor, ...args: any[]): void { + changeNotebookIndentation(accessor, true, true); + } +} + +export class NotebookIndentationToSpacesAction extends Action2 { + public static readonly ID = 'notebook.action.convertIndentationToSpaces'; + + constructor() { + super({ + id: NotebookIndentUsingSpaces.ID, + title: nls.localize('convertIndentationToSpaces', "Convert Indentation to Spaces"), + precondition: undefined, + }); + } + + override run(accessor: ServicesAccessor, ...args: any[]): void { + convertNotebookIndentation(accessor, true); + + } +} + +export class NotebookIndentationToTabsAction extends Action2 { + public static readonly ID = 'notebook.action.convertIndentationToTabs'; + + constructor() { + super({ + id: NotebookIndentUsingSpaces.ID, + title: nls.localize('convertIndentationToTabs', "Convert Indentation to Tabs"), + precondition: undefined, + }); + } + + override run(accessor: ServicesAccessor, ...args: any[]): void { + convertNotebookIndentation(accessor, false); + } +} + +function changeNotebookIndentation(accessor: ServicesAccessor, insertSpaces: boolean, displaySizeOnly: boolean) { + const editorService = accessor.get(IEditorService); + const configurationService = accessor.get(IConfigurationService); + const notebookEditorService = accessor.get(INotebookEditorService); + const quickInputService = accessor.get(IQuickInputService); + + // keep this check here to pop on non-notebook actions + const activeInput = editorService.activeEditorPane?.input; + const isNotebook = isNotebookEditorInput(activeInput); + if (!isNotebook) { + return; + } + + // get notebook editor to access all codeEditors + const notebookEditor = notebookEditorService.retrieveExistingWidgetFromURI(activeInput.resource)?.value; + if (!notebookEditor) { + return; + } + + const picks = [1, 2, 3, 4, 5, 6, 7, 8].map(n => ({ + id: n.toString(), + label: n.toString(), + })); + + // store the initial values of the configuration + const initialConfig = configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations) as any; + const initialInsertSpaces = initialConfig['editor.insertSpaces']; + // remove the initial values from the configuration + delete initialConfig['editor.indentSize']; + delete initialConfig['editor.tabSize']; + delete initialConfig['editor.insertSpaces']; + + setTimeout(() => { + quickInputService.pick(picks, { placeHolder: nls.localize({ key: 'selectTabWidth', comment: ['Tab corresponds to the tab key'] }, "Select Tab Size for Current File") }).then(pick => { + if (pick) { + const pickedVal = parseInt(pick.label, 10); + if (displaySizeOnly) { + configurationService.updateValue(NotebookSetting.cellEditorOptionsCustomizations, { + ...initialConfig, + 'editor.tabSize': pickedVal, + 'editor.indentSize': pickedVal, + 'editor.insertSpaces': initialInsertSpaces + }); + } else { + configurationService.updateValue(NotebookSetting.cellEditorOptionsCustomizations, { + ...initialConfig, + 'editor.tabSize': pickedVal, + 'editor.indentSize': pickedVal, + 'editor.insertSpaces': insertSpaces + }); + } + + } + }); + }, 50/* quick input is sensitive to being opened so soon after another */); +} + +function convertNotebookIndentation(accessor: ServicesAccessor, tabsToSpaces: boolean): void { + const editorService = accessor.get(IEditorService); + const configurationService = accessor.get(IConfigurationService); + const logService = accessor.get(ILogService); + const textModelService = accessor.get(ITextModelService); + const notebookEditorService = accessor.get(INotebookEditorService); + const bulkEditService = accessor.get(IBulkEditService); + + // keep this check here to pop on non-notebook + const activeInput = editorService.activeEditorPane?.input; + const isNotebook = isNotebookEditorInput(activeInput); + if (!isNotebook) { + return; + } + + // get notebook editor to access all codeEditors + const notebookTextModel = notebookEditorService.retrieveExistingWidgetFromURI(activeInput.resource)?.value?.textModel; + if (!notebookTextModel) { + return; + } + + const disposable = new DisposableStore(); + try { + Promise.all(notebookTextModel.cells.map(async cell => { + const ref = await textModelService.createModelReference(cell.uri); + disposable.add(ref); + const textEditorModel = ref.object.textEditorModel; + + const modelOpts = cell.textModel?.getOptions(); + if (!modelOpts) { + return; + } + + const edits = getIndentationEditOperations(textEditorModel, modelOpts.tabSize, tabsToSpaces); + + bulkEditService.apply(edits, { label: nls.localize('convertIndentation', "Convert Indentation"), code: 'undoredo.convertIndentation', }); + + })).then((notebookEdits) => { + + // store the initial values of the configuration + const initialConfig = configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations) as any; + const initialIndentSize = initialConfig['editor.indentSize']; + const initialTabSize = initialConfig['editor.tabSize']; + // remove the initial values from the configuration + delete initialConfig['editor.indentSize']; + delete initialConfig['editor.tabSize']; + delete initialConfig['editor.insertSpaces']; + + configurationService.updateValue(NotebookSetting.cellEditorOptionsCustomizations, { + ...initialConfig, + 'editor.tabSize': initialTabSize, + 'editor.indentSize': initialIndentSize, + 'editor.insertSpaces': tabsToSpaces + }); + disposable.dispose(); + }); + } catch { + logService.error('Failed to convert indentation to spaces for notebook cells.'); + } +} + +function getIndentationEditOperations(model: ITextModel, tabSize: number, tabsToSpaces: boolean): ResourceTextEdit[] { + if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) { + // Model is empty + return []; + } + + let spaces = ''; + for (let i = 0; i < tabSize; i++) { + spaces += ' '; + } + + const spacesRegExp = new RegExp(spaces, 'gi'); + + const edits: ResourceTextEdit[] = []; + for (let lineNumber = 1, lineCount = model.getLineCount(); lineNumber <= lineCount; lineNumber++) { + let lastIndentationColumn = model.getLineFirstNonWhitespaceColumn(lineNumber); + if (lastIndentationColumn === 0) { + lastIndentationColumn = model.getLineMaxColumn(lineNumber); + } + + if (lastIndentationColumn === 1) { + continue; + } + + const originalIndentationRange = new Range(lineNumber, 1, lineNumber, lastIndentationColumn); + const originalIndentation = model.getValueInRange(originalIndentationRange); + const newIndentation = ( + tabsToSpaces + ? originalIndentation.replace(/\t/ig, spaces) + : originalIndentation.replace(spacesRegExp, '\t') + ); + edits.push(new ResourceTextEdit(model.uri, { range: originalIndentationRange, text: newIndentation })); + } + return edits; +} + +registerAction2(NotebookIndentUsingSpaces); diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts index db98d0bbb96..7c1a89da760 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts @@ -374,3 +374,9 @@ export function isCompositeNotebookEditorInput(thing: unknown): thing is ICompos && Array.isArray((thing).editorInputs) && ((thing).editorInputs.every(input => input instanceof NotebookEditorInput)); } + +export function isNotebookEditorInput(thing: unknown): thing is NotebookEditorInput { + return !!thing + && typeof thing === 'object' + && thing instanceof NotebookEditorInput; +} From 687a754a431df5e59ab528d8c7d35042e1c6ad92 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 16 Feb 2024 19:54:46 +0100 Subject: [PATCH 1379/1897] tweak height logic --- src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index ba003be69a7..7fd28a442ac 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -1118,7 +1118,7 @@ class HunkAccessibleDiffViewer extends AccessibleDiffViewer { const width = observableValue('width', 0); const diff = observableValue('diff', HunkAccessibleDiffViewer._asMapping(hunk)); const diffs = derived(r => [diff.read(r)]); - const lines = Math.min(6, 3 * diff.get().changedLineCount); + const lines = Math.min(10, 8 + diff.get().changedLineCount); const height = models.getModifiedOptions().get(EditorOption.lineHeight) * lines; super(parentNode, constObservable(true), () => { }, constObservable(false), width, constObservable(height), diffs, models, instantiationService); From b0593428a503eb46f04513682969ab63778fbfba Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 13:01:10 -0600 Subject: [PATCH 1380/1897] fix #205391 --- src/vs/workbench/contrib/preferences/browser/settingsLayout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index 2edaff0b3b7..6bee52d8257 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -149,7 +149,7 @@ export const tocData: ITOCEntry = { { id: 'features/accessibilitySignals', label: localize('accessibility.signals', 'Accessibility Signals'), - settings: ['accessibility.signals.*'] + settings: ['accessibility.signals.*', 'audioCues.*'] }, { id: 'features/accessibility', From ba24d929651af34cd13b52b21cf88d5521068194 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 13:07:26 -0600 Subject: [PATCH 1381/1897] move css to right file --- .../terminal/browser/media/terminal.css | 18 ------------------ .../chat/browser/media/terminalChatWidget.css | 12 ++++++++++++ 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 1aaf1516c5e..488815cf258 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -565,21 +565,3 @@ .monaco-workbench .xterm.terminal.hide { visibility: hidden; } - -.monaco-workbench .terminal-chat-widget { - z-index: 33 !important; - position: absolute; - top: 10px; -} - -.monaco-workbench .terminal-inline-chat.hide { - visibility: hidden; -} - -.monaco-workbench .terminal-inline-chat-response.hide { - visibility: hidden; -} - -.monaco-workbench .terminal-inline-chat .chatMessageContent { - width: 400px !important; -} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css index 25d492a063c..602560618cc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css @@ -15,6 +15,18 @@ margin-top: 0 !important; } +.terminal-inline-chat.hide { + visibility: hidden; +} + +.terminal-inline-chat-response.hide { + visibility: hidden; +} + +.terminal-inline-chat .chatMessageContent { + width: 400px !important; +} + .terminal-inline-chat .terminal-inline-chat-response { border: 1px solid var(--vscode-input-border, transparent); /* TODO: Make themeable */ From f1deb6932a0c0a8a151f78cd8a3fe97c3881d755 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 16 Feb 2024 11:12:12 -0800 Subject: [PATCH 1382/1897] Update unit tests. --- .../test/browser/indentation.test.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts index 18ee1b9309c..5748693d84b 100644 --- a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts +++ b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts @@ -325,3 +325,47 @@ suite('Editor Contrib - Keep Indent On Paste', () => { }); }); }); + + +suite('Editor Contrib - Auto Dedent On Type', () => { + const rubyIndentationRules = { + // decreaseIndentPattern: /\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif|when|in)\b/, + decreaseIndentPattern: /\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif)\b)|((in|when)\s/, + increaseIndentPattern: /^\s*((begin|class|(private|protected)\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|in|while|case)|([^#]*\sdo\b)|([^#]*=\s*(case|if|unless)))\b([^#\{;]|(\"|'|\/).*\4)*(#.*)?$/, + }; + + let disposables: DisposableStore; + + setup(() => { + disposables = new DisposableStore(); + }); + + teardown(() => { + disposables.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('issue #198350: in or when incorrectly match non keywords for Ruby', () => { + const languageId = "ruby"; + const model = createTextModel("", languageId, {}); + disposables.add(model); + + withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { + const languageService = instantiationService.get(ILanguageService); + const languageConfigurationService = instantiationService.get(ILanguageConfigurationService); + disposables.add(languageService.registerLanguage({ id: languageId })); + disposables.add(languageConfigurationService.register(languageId, { + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'] + ], + indentationRules: rubyIndentationRules, + })); + + viewModel.type("def foo\n in"); + assert.strictEqual(model.getValue(), "def foo\n in"); + }); + }); +}); From 89bc8c3765b9f1c27f4c2236cb2ca0f2b01441f0 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 16 Feb 2024 20:09:23 +0000 Subject: [PATCH 1383/1897] Render followup agent+command in tooltip only (#205398) * Rename /newChat to /clear * Remove text() from the Stream * Render followup agent+command in tooltip only * Fix sticky slash command --- .../api/common/extHostChatAgents2.ts | 13 +++------ .../contrib/chat/browser/chat.contribution.ts | 8 +++--- .../contrib/chat/browser/chatFollowups.ts | 27 +++++++++++-------- .../vscode.proposed.chatAgents2.d.ts | 12 --------- 4 files changed, 24 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 399ff60d030..110b89a042b 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -8,7 +8,7 @@ import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; -import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; import { assertType } from 'vs/base/common/types'; @@ -77,11 +77,6 @@ class ChatAgentResponseStream { }; this._apiObject = { - text(value) { - throwIfDone(this.text); - this.markdown(new MarkdownString().appendText(value)); - return this; - }, markdown(value) { throwIfDone(this.markdown); const part = new extHostTypes.ChatResponseMarkdownPart(value); @@ -389,7 +384,7 @@ class ExtHostChatAgent { } return result .map(c => { - if ('repopulate2' in c) { + if ('isSticky2' in c) { checkProposedApiEnabled(this.extension, 'chatAgents2Additions'); } @@ -397,9 +392,9 @@ class ExtHostChatAgent { name: c.name, description: c.description, followupPlaceholder: c.isSticky2?.placeholder, - shouldRepopulate: c.isSticky2?.isSticky ?? c.isSticky, + isSticky: c.isSticky2?.isSticky ?? c.isSticky, sampleRequest: c.sampleRequest - }; + } satisfies IChatAgentCommand; }); } diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 52871880c7c..c07a0b1afb6 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -195,7 +195,7 @@ class ChatAccessibleViewContribution extends Disposable { accessibleViewService.show({ id: AccessibleViewProviderId.Chat, verbositySettingKey: AccessibilityVerbositySettingId.Chat, - provideContent(): string { return responseContent; }, + provideContent(): string { return responseContent!; }, onClose() { verifiedWidget.reveal(focusedItem); if (chatInputFocused) { @@ -231,9 +231,9 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { ) { super(); this._store.add(slashCommandService.registerSlashCommand({ - command: 'newChat', - detail: nls.localize('newChat', "Start a new chat"), - sortText: 'z2_newChat', + command: 'clear', + detail: nls.localize('clear', "Start a new chat"), + sortText: 'z2_clear', executeImmediately: true }, async () => { commandService.executeCommand(ACTION_ID_NEW_CHAT); diff --git a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts index ab3733a1192..7f0c4279c5d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts +++ b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts @@ -42,7 +42,20 @@ export class ChatFollowups extend return; } - const tooltip = 'tooltip' in followup ? followup.tooltip : undefined; + let tooltipPrefix = ''; + if ('agentId' in followup && followup.agentId && followup.agentId !== this.chatAgentService.getDefaultAgent()?.id) { + tooltipPrefix += `${chatAgentLeader}${followup.agentId} `; + if ('subCommand' in followup && followup.subCommand) { + tooltipPrefix += `${chatSubcommandLeader}${followup.subCommand} `; + } + } + + const baseTitle = followup.kind === 'reply' ? + (followup.title || followup.message) + : followup.title; + + const tooltip = tooltipPrefix + + ('tooltip' in followup && followup.tooltip || baseTitle); const button = this._register(new Button(container, { ...this.options, supportIcons: true, title: tooltip })); if (followup.kind === 'reply') { button.element.classList.add('interactive-followup-reply'); @@ -50,19 +63,11 @@ export class ChatFollowups extend button.element.classList.add('interactive-followup-command'); } button.element.ariaLabel = localize('followUpAriaLabel', "Follow up question: {0}", followup.title); - let prefix = ''; - if ('agentId' in followup && followup.agentId && followup.agentId !== this.chatAgentService.getDefaultAgent()?.id) { - prefix += `${chatAgentLeader}${followup.agentId} `; - if ('subCommand' in followup && followup.subCommand) { - prefix += `${chatSubcommandLeader}${followup.subCommand} `; - } - } - let label = ''; if (followup.kind === 'reply') { - label = '$(sparkle) ' + (followup.title || (prefix + followup.message)); + label = '$(sparkle) ' + baseTitle; } else { - label = followup.title; + label = baseTitle; } button.label = new MarkdownString(label, { supportThemeIcons: true }); diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 93938b8ba68..783c1bcf78e 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -356,17 +356,6 @@ declare module 'vscode' { export interface ChatAgentResponseStream { - /** - * Push a text part to this stream. Short-hand for - * `push(new ChatResponseTextPart(value))`. - * - * @see {@link ChatAgentResponseStream.push} - * @param value A plain text value. - * @returns This stream. - */ - // TODO@API remove? - text(value: string): ChatAgentResponseStream; - /** * Push a markdown part to this stream. Short-hand for * `push(new ChatResponseMarkdownPart(value))`. @@ -375,7 +364,6 @@ declare module 'vscode' { * @param value A markdown string or a string that should be interpreted as markdown. * @returns This stream. */ - // TODO@API NAME: content markdown(value: string | MarkdownString): ChatAgentResponseStream; /** From 025e11a803f6176ff2f0cb7de32576bc6d06124f Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 16 Feb 2024 20:10:39 +0000 Subject: [PATCH 1384/1897] Fix indentation rules in test and validate the rules update. --- .../test/browser/indentation.test.ts | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts index 5748693d84b..91666a05d19 100644 --- a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts +++ b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts @@ -326,14 +326,7 @@ suite('Editor Contrib - Keep Indent On Paste', () => { }); }); - suite('Editor Contrib - Auto Dedent On Type', () => { - const rubyIndentationRules = { - // decreaseIndentPattern: /\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif|when|in)\b/, - decreaseIndentPattern: /\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif)\b)|((in|when)\s/, - increaseIndentPattern: /^\s*((begin|class|(private|protected)\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|in|while|case)|([^#]*\sdo\b)|([^#]*=\s*(case|if|unless)))\b([^#\{;]|(\"|'|\/).*\4)*(#.*)?$/, - }; - let disposables: DisposableStore; setup(() => { @@ -355,17 +348,42 @@ suite('Editor Contrib - Auto Dedent On Type', () => { const languageService = instantiationService.get(ILanguageService); const languageConfigurationService = instantiationService.get(ILanguageConfigurationService); disposables.add(languageService.registerLanguage({ id: languageId })); - disposables.add(languageConfigurationService.register(languageId, { + const languageModel = languageConfigurationService.register(languageId, { brackets: [ ['{', '}'], ['[', ']'], ['(', ')'] ], - indentationRules: rubyIndentationRules, - })); + indentationRules: { + decreaseIndentPattern: /\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif|when|in)\b)/, + increaseIndentPattern: /^\s*((begin|class|(private|protected)\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|in|while|case)|([^#]*\sdo\b)|([^#]*=\s*(case|if|unless)))\b([^#\{;]|(\"|'|\/).*\4)*(#.*)?$/, + }, + }); - viewModel.type("def foo\n in"); - assert.strictEqual(model.getValue(), "def foo\n in"); + viewModel.type("def foo\n i", 'keyboard'); + viewModel.type("n", 'keyboard'); + // The 'in' triggers decreaseIndentPattern immediately, which is incorrect + assert.strictEqual(model.getValue(), "def foo\nin"); + languageModel.dispose(); + + const improvedLanguageModel = languageConfigurationService.register(languageId, { + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'] + ], + indentationRules: { + decreaseIndentPattern: /\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif)\b)|((in|when)\s)/, + increaseIndentPattern: /^\s*((begin|class|(private|protected)\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|in|while|case)|([^#]*\sdo\b)|([^#]*=\s*(case|if|unless)))\b([^#\{;]|(\"|'|\/).*\4)*(#.*)?$/, + }, + }); + viewModel.model.setValue(""); + viewModel.type("def foo\n i"); + viewModel.type("n", 'keyboard'); + assert.strictEqual(model.getValue(), "def foo\n in"); + viewModel.type(" ", 'keyboard'); + assert.strictEqual(model.getValue(), "def foo\nin "); + improvedLanguageModel.dispose(); }); }); }); From c57d3061b82d738f3062365127a60660af3929da Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:23:50 -0800 Subject: [PATCH 1385/1897] Move a11y help provider into terminal chat folder --- .../browser/accessibilityConfiguration.ts | 2 + .../inlineChat/browser/inlineChatActions.ts | 3 +- .../browser/terminal.chat.contribution.ts | 2 + ...rminalChatAccessibilityHelpContribution.ts | 48 +++++++++++++++++++ 4 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index bba5a1f648e..ca7ebdca1a0 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -45,6 +45,7 @@ export const enum AccessibilityVerbositySettingId { DiffEditor = 'accessibility.verbosity.diffEditor', Chat = 'accessibility.verbosity.panelChat', InlineChat = 'accessibility.verbosity.inlineChat', + TerminalChat = 'accessibility.verbosity.terminalChat', InlineCompletions = 'accessibility.verbosity.inlineCompletions', KeybindingsEditor = 'accessibility.verbosity.keybindingsEditor', Notebook = 'accessibility.verbosity.notebook', @@ -57,6 +58,7 @@ export const enum AccessibilityVerbositySettingId { export const enum AccessibleViewProviderId { Terminal = 'terminal', + TerminalChat = 'terminal-chat', TerminalHelp = 'terminal-help', DiffEditor = 'diffEditor', Chat = 'panelChat', diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index d9f725ed61d..8deb70a073e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -30,7 +30,6 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); @@ -786,6 +785,6 @@ export class InlineAccessibilityHelpContribution extends Disposable { return; } runAccessibilityHelpAction(accessor, codeEditor, 'inlineChat'); - }, ContextKeyExpr.and(ContextKeyExpr.or(CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_FOCUSED), TerminalContextKeys.chatFocused.negate()))); + }, ContextKeyExpr.or(CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_FOCUSED))); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index 0c2dba50035..0049340fadd 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -8,6 +8,7 @@ import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/brow import { TerminalInlineChatAccessibilityHelpContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp'; import { TerminalInlineChatAccessibleViewContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; +import { TerminalChatAccessibilityHelpContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution'; import 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions'; @@ -15,3 +16,4 @@ registerTerminalContribution(TerminalChatController.ID, TerminalChatController, registerWorkbenchContribution2(TerminalInlineChatAccessibleViewContribution.ID, TerminalInlineChatAccessibleViewContribution, WorkbenchPhase.Eventually); registerWorkbenchContribution2(TerminalInlineChatAccessibilityHelpContribution.ID, TerminalInlineChatAccessibilityHelpContribution, WorkbenchPhase.Eventually); +registerWorkbenchContribution2(TerminalChatAccessibilityHelpContribution.ID, TerminalChatAccessibilityHelpContribution, WorkbenchPhase.Eventually); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts new file mode 100644 index 00000000000..6e5f79e1cb1 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { localize } from 'vs/nls'; +import type { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; + +export class TerminalChatAccessibilityHelpContribution extends Disposable { + static ID = 'terminalChatAccessiblityHelp'; + constructor() { + super(); + this._register(AccessibilityHelpAction.addImplementation(110, 'terminalChat', runAccessibilityHelpAction, TerminalContextKeys.chatFocused)); + } +} + +export async function runAccessibilityHelpAction(accessor: ServicesAccessor): Promise { + const accessibleViewService = accessor.get(IAccessibleViewService); + const terminalService = accessor.get(ITerminalService); + + const instance = terminalService.activeInstance; + if (!instance) { + return; + } + + const helpText = getAccessibilityHelpText(accessor); + accessibleViewService.show({ + id: AccessibleViewProviderId.TerminalChat, + verbositySettingKey: AccessibilityVerbositySettingId.TerminalChat, + provideContent: () => helpText, + onClose: () => TerminalChatController.get(instance)?.focus(), + options: { type: AccessibleViewType.Help } + }); +} + +export function getAccessibilityHelpText(accessor: ServicesAccessor): string { + const content = []; + // TODO: Fill in more help text + content.push(localize('chat.overview', 'The terminal chat view is comprised of an input box, an editor where suggested commands are provided (Shift+Tab) and buttons to action the suggestion.')); + return content.join('\n\n'); +} From ab75d8d74b5dc30c84c65581e3674ee19f3744bd Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:27:24 -0800 Subject: [PATCH 1386/1897] Remove terminal action ids from inline button config provider --- src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts | 2 +- .../contrib/terminalContrib/chat/browser/terminalChatActions.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 537c7b8951f..3aeb25cd2e2 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -388,7 +388,7 @@ export class InlineChatWidget { buttonConfigProvider: action => { if (action.id === ACTION_REGENERATE_RESPONSE) { return { showIcon: true, showLabel: false, isSecondary: true }; - } else if ([ACTION_VIEW_IN_CHAT, ACTION_ACCEPT_CHANGES, 'workbench.action.terminal.chat.acceptCommand', 'workbench.action.terminal.chat.viewInChat'].includes(action.id)) { + } else if ([ACTION_VIEW_IN_CHAT, ACTION_ACCEPT_CHANGES].includes(action.id)) { return { isSecondary: false }; } else { return { isSecondary: true }; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index cd984a4d61e..f374aee77e8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -116,6 +116,7 @@ registerActiveXtermAction({ primary: KeyMod.CtrlCmd | KeyCode.Enter, }, menu: { + // TODO: Allow action to be made primary, the action list is hardcoded within InlineChatWidget id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 0, From ae04cff1450064534d4490d7b21fa138fb06e03e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:31:56 -0800 Subject: [PATCH 1387/1897] Move context keys and response types into terminalContrib --- .../terminal/common/terminalContextKey.ts | 42 ---------- .../chat/browser/terminalChat.ts | 49 ++++++++++++ .../browser/terminalChatAccessibilityHelp.ts | 5 +- ...rminalChatAccessibilityHelpContribution.ts | 4 +- .../browser/terminalChatAccessibleView.ts | 4 +- .../chat/browser/terminalChatActions.ts | 80 +++++++++---------- .../chat/browser/terminalChatController.ts | 12 +-- .../chat/browser/terminalChatWidget.ts | 9 +-- 8 files changed, 105 insertions(+), 100 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 0c577a9df8a..72335480aee 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -39,20 +39,6 @@ export const enum TerminalContextKeyStrings { ShellType = 'terminalShellType', InTerminalRunCommandPicker = 'inTerminalRunCommandPicker', TerminalShellIntegrationEnabled = 'terminalShellIntegrationEnabled', - ChatFocus = 'terminalChatFocus', - ChatVisible = 'terminalChatVisible', - ChatActiveRequest = 'terminalChatActiveRequest', - ChatInputHasText = 'terminalChatInputHasText', - ChatAgentRegistered = 'terminalChatAgentRegistered', - ChatResponseEditorFocused = 'terminalChatResponseEditorFocused', - ChatResponseType = 'terminalChatResponseType', - ChatResponseSupportsIssueReporting = 'terminalChatResponseSupportsIssueReporting', - ChatSessionResponseVote = 'terminalChatSessionResponseVote', -} - -export const enum TerminalChatResponseTypes { - Message = 'message', - TerminalCommand = 'terminalCommand' } export namespace TerminalContextKeys { @@ -172,32 +158,4 @@ export namespace TerminalContextKeys { ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActions}`, 'always') ) ); - - - /** Whether the chat widget is focused */ - export const chatFocused = new RawContextKey(TerminalContextKeyStrings.ChatFocus, false, localize('chatFocusedContextKey', "Whether the chat view is focused.")); - - /** Whether the chat widget is visible */ - export const chatVisible = new RawContextKey(TerminalContextKeyStrings.ChatVisible, false, localize('chatVisibleContextKey', "Whether the chat view is visible.")); - - /** Whether there is an active chat request */ - export const chatRequestActive = new RawContextKey(TerminalContextKeyStrings.ChatActiveRequest, false, localize('chatRequestActiveContextKey', "Whether there is an active chat request.")); - - /** Whether the chat input has text */ - export const chatInputHasText = new RawContextKey(TerminalContextKeyStrings.ChatInputHasText, false, localize('chatInputHasTextContextKey', "Whether the chat input has text.")); - - /** Whether the terminal chat agent has been registered */ - export const chatAgentRegistered = new RawContextKey(TerminalContextKeyStrings.ChatAgentRegistered, false, localize('chatAgentRegisteredContextKey', "Whether the terminal chat agent has been registered.")); - - /** Whether the chat response editor is focused */ - export const chatResponseEditorFocused = new RawContextKey(TerminalContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseEditorFocusedContextKey', "Whether the chat response editor is focused.")); - - /** The type of chat response, if any */ - export const chatResponseType = new RawContextKey(TerminalContextKeyStrings.ChatResponseType, undefined, localize('chatResponseTypeContextKey', "The type of chat response, if any")); - - /** Whether the response supports issue reporting */ - export const chatResponseSupportsIssueReporting = new RawContextKey(TerminalContextKeyStrings.ChatResponseSupportsIssueReporting, false, localize('chatResponseSupportsIssueReportingContextKey', "Whether the response supports issue reporting")); - - /** The chat vote, if any for the response, if any */ - export const chatSessionResponseVote = new RawContextKey(TerminalContextKeyStrings.ChatSessionResponseVote, undefined, { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index a2349bc25c1..5aefe757469 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const enum TerminalChatCommandId { Start = 'workbench.action.terminal.chat.start', @@ -24,3 +26,50 @@ export const MENU_TERMINAL_CHAT_WIDGET = MenuId.for('terminalChatWidget'); export const MENU_TERMINAL_CHAT_WIDGET_STATUS = MenuId.for('terminalChatWidget.status'); export const MENU_TERMINAL_CHAT_WIDGET_FEEDBACK = MenuId.for('terminalChatWidget.feedback'); export const MENU_TERMINAL_CHAT_WIDGET_TOOLBAR = MenuId.for('terminalChatWidget.toolbar'); + +export const enum TerminalChatContextKeyStrings { + ChatFocus = 'terminalChatFocus', + ChatVisible = 'terminalChatVisible', + ChatActiveRequest = 'terminalChatActiveRequest', + ChatInputHasText = 'terminalChatInputHasText', + ChatAgentRegistered = 'terminalChatAgentRegistered', + ChatResponseEditorFocused = 'terminalChatResponseEditorFocused', + ChatResponseType = 'terminalChatResponseType', + ChatResponseSupportsIssueReporting = 'terminalChatResponseSupportsIssueReporting', + ChatSessionResponseVote = 'terminalChatSessionResponseVote', +} + +export const enum TerminalChatResponseTypes { + Message = 'message', + TerminalCommand = 'terminalCommand' +} + +export namespace TerminalChatContextKeys { + + /** Whether the chat widget is focused */ + export const chatFocused = new RawContextKey(TerminalChatContextKeyStrings.ChatFocus, false, localize('chatFocusedContextKey', "Whether the chat view is focused.")); + + /** Whether the chat widget is visible */ + export const chatVisible = new RawContextKey(TerminalChatContextKeyStrings.ChatVisible, false, localize('chatVisibleContextKey', "Whether the chat view is visible.")); + + /** Whether there is an active chat request */ + export const chatRequestActive = new RawContextKey(TerminalChatContextKeyStrings.ChatActiveRequest, false, localize('chatRequestActiveContextKey', "Whether there is an active chat request.")); + + /** Whether the chat input has text */ + export const chatInputHasText = new RawContextKey(TerminalChatContextKeyStrings.ChatInputHasText, false, localize('chatInputHasTextContextKey', "Whether the chat input has text.")); + + /** Whether the terminal chat agent has been registered */ + export const chatAgentRegistered = new RawContextKey(TerminalChatContextKeyStrings.ChatAgentRegistered, false, localize('chatAgentRegisteredContextKey', "Whether the terminal chat agent has been registered.")); + + /** Whether the chat response editor is focused */ + export const chatResponseEditorFocused = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseEditorFocusedContextKey', "Whether the chat response editor is focused.")); + + /** The type of chat response, if any */ + export const chatResponseType = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseType, undefined, localize('chatResponseTypeContextKey', "The type of chat response, if any")); + + /** Whether the response supports issue reporting */ + export const chatResponseSupportsIssueReporting = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseSupportsIssueReporting, false, localize('chatResponseSupportsIssueReportingContextKey', "Whether the response supports issue reporting")); + + /** The chat vote, if any for the response, if any */ + export const chatSessionResponseVote = new RawContextKey(TerminalChatContextKeyStrings.ChatSessionResponseVote, undefined, { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); +} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts index 890fb103292..c9631f7fa1d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -13,8 +13,7 @@ import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { CTX_INLINE_CHAT_RESPONSE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { TerminalChatCommandId, TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; export class TerminalInlineChatAccessibilityHelpContribution extends Disposable { @@ -39,7 +38,7 @@ export class TerminalInlineChatAccessibilityHelpContribution extends Disposable options: { type: AccessibleViewType.Help } }); return true; - }, ContextKeyExpr.or(TerminalContextKeys.chatFocused, TerminalContextKeys.chatResponseEditorFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); + }, ContextKeyExpr.or(TerminalChatContextKeys.chatFocused, TerminalChatContextKeys.chatResponseEditorFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts index 6e5f79e1cb1..afc24a15163 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts @@ -10,14 +10,14 @@ import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/wo import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; export class TerminalChatAccessibilityHelpContribution extends Disposable { static ID = 'terminalChatAccessiblityHelp'; constructor() { super(); - this._register(AccessibilityHelpAction.addImplementation(110, 'terminalChat', runAccessibilityHelpAction, TerminalContextKeys.chatFocused)); + this._register(AccessibilityHelpAction.addImplementation(110, 'terminalChat', runAccessibilityHelpAction, TerminalChatContextKeys.chatFocused)); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts index 8c146698482..93854689479 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts @@ -10,7 +10,7 @@ import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { CTX_INLINE_CHAT_RESPONSE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; export class TerminalInlineChatAccessibleViewContribution extends Disposable { @@ -35,6 +35,6 @@ export class TerminalInlineChatAccessibleViewContribution extends Disposable { options: { type: AccessibleViewType.View } }); return true; - }, ContextKeyExpr.and(TerminalContextKeys.chatFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); + }, ContextKeyExpr.and(TerminalChatContextKeys.chatFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index f374aee77e8..7f8dc50524d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -12,8 +12,8 @@ import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; -import { TerminalChatResponseTypes, TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId, TerminalChatContextKeys, TerminalChatResponseTypes } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; registerActiveXtermAction({ @@ -21,7 +21,7 @@ registerActiveXtermAction({ title: localize2('startChat', 'Start Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, - when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), + when: ContextKeyExpr.and(TerminalChatContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), weight: KeybindingWeight.WorkbenchContrib, }, f1: true, @@ -46,7 +46,7 @@ registerActiveXtermAction({ keybinding: { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], - when: ContextKeyExpr.and(TerminalContextKeys.chatFocused, TerminalContextKeys.chatVisible), + when: ContextKeyExpr.and(TerminalChatContextKeys.chatFocused, TerminalChatContextKeys.chatVisible), weight: KeybindingWeight.WorkbenchContrib, }, icon: Codicon.close, @@ -78,15 +78,15 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 2, - when: ContextKeyExpr.and(TerminalContextKeys.chatFocused, - TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand)) + when: ContextKeyExpr.and(TerminalChatContextKeys.chatFocused, + TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand)) }, f1: true, precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalContextKeys.chatFocused, - TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) + TerminalChatContextKeys.chatFocused, + TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) ), run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -105,13 +105,13 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalContextKeys.chatRequestActive.negate(), - TerminalContextKeys.chatAgentRegistered, - TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) + TerminalChatContextKeys.chatRequestActive.negate(), + TerminalChatContextKeys.chatAgentRegistered, + TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) ), icon: Codicon.check, keybinding: { - when: TerminalContextKeys.chatRequestActive.negate(), + when: TerminalChatContextKeys.chatRequestActive.negate(), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.Enter, }, @@ -120,7 +120,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 0, - when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.chatRequestActive.negate()), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -138,13 +138,13 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalContextKeys.chatRequestActive.negate(), - TerminalContextKeys.chatAgentRegistered, - TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) + TerminalChatContextKeys.chatRequestActive.negate(), + TerminalChatContextKeys.chatAgentRegistered, + TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) ), icon: Codicon.check, keybinding: { - when: TerminalContextKeys.chatRequestActive.negate(), + when: TerminalChatContextKeys.chatRequestActive.negate(), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Alt | KeyCode.Enter, }, @@ -152,7 +152,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 1, - when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.chatRequestActive.negate()), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -169,21 +169,21 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalContextKeys.chatRequestActive.negate(), - TerminalContextKeys.chatAgentRegistered, + TerminalChatContextKeys.chatRequestActive.negate(), + TerminalChatContextKeys.chatAgentRegistered, ), icon: Codicon.commentDiscussion, menu: [{ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 1, - when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.Message), TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.Message), TerminalChatContextKeys.chatRequestActive.negate()), }, { id: MENU_TERMINAL_CHAT_WIDGET, group: 'main', order: 1, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_EMPTY.negate(), TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(CTX_INLINE_CHAT_EMPTY.negate(), TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.chatRequestActive.negate()), }], run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -200,13 +200,13 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalContextKeys.chatRequestActive.negate(), - TerminalContextKeys.chatAgentRegistered, + TerminalChatContextKeys.chatRequestActive.negate(), + TerminalChatContextKeys.chatAgentRegistered, CTX_INLINE_CHAT_EMPTY.negate() ), icon: Codicon.send, keybinding: { - when: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, TerminalChatContextKeys.chatRequestActive.negate()), weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.Enter }, @@ -214,7 +214,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_INPUT, group: 'main', order: 1, - when: TerminalContextKeys.chatRequestActive.negate(), + when: TerminalChatContextKeys.chatRequestActive.negate(), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -230,14 +230,14 @@ registerActiveXtermAction({ title: localize2('cancelChat', 'Cancel Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive, - TerminalContextKeys.chatAgentRegistered + TerminalChatContextKeys.chatRequestActive, + TerminalChatContextKeys.chatAgentRegistered ), icon: Codicon.debugStop, menu: { id: MENU_TERMINAL_CHAT_INPUT, group: 'main', - when: TerminalContextKeys.chatRequestActive, + when: TerminalChatContextKeys.chatRequestActive, }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -253,15 +253,15 @@ registerActiveXtermAction({ title: localize2('feedbackHelpful', 'Helpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatResponseType.notEqualsTo(undefined) + TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined) ), icon: Codicon.thumbsup, - toggled: TerminalContextKeys.chatSessionResponseVote.isEqualTo('up'), + toggled: TerminalChatContextKeys.chatSessionResponseVote.isEqualTo('up'), menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', order: 1, - when: TerminalContextKeys.chatResponseType.notEqualsTo(undefined), + when: TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -277,15 +277,15 @@ registerActiveXtermAction({ title: localize2('feedbackUnhelpful', 'Unhelpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatResponseType.notEqualsTo(undefined), + TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), ), - toggled: TerminalContextKeys.chatSessionResponseVote.isEqualTo('down'), + toggled: TerminalChatContextKeys.chatSessionResponseVote.isEqualTo('down'), icon: Codicon.thumbsdown, menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', order: 2, - when: TerminalContextKeys.chatResponseType.notEqualsTo(undefined), + when: TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -301,20 +301,20 @@ registerActiveXtermAction({ title: localize2('reportIssue', 'Report Issue'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive.negate(), - TerminalContextKeys.chatResponseType.notEqualsTo(undefined), - TerminalContextKeys.chatResponseSupportsIssueReporting + TerminalChatContextKeys.chatRequestActive.negate(), + TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), + TerminalChatContextKeys.chatResponseSupportsIssueReporting ), icon: Codicon.report, menu: [{ id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, - when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.notEqualsTo(undefined), TerminalContextKeys.chatResponseSupportsIssueReporting), + when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), TerminalChatContextKeys.chatResponseSupportsIssueReporting), group: 'inline', order: 3 }], // { // id: MENU_TERMINAL_CHAT_WIDGET, - // when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.notEqualsTo(undefined), TerminalContextKeys.chatResponseSupportsIssueReporting), + // when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), TerminalChatContextKeys.chatResponseSupportsIssueReporting), // group: 'config', // order: 3 // }], diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index ca3bdbab893..c3409674e12 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -21,11 +21,11 @@ import { IChatService, IChatProgress, InteractiveSessionVoteDirection, ChatUserA import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; -import { TerminalChatResponseTypes, TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; import { ChatModel, ChatRequestModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { TerminalChatContextKeys, TerminalChatResponseTypes } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; const enum Message { NONE = 0, @@ -95,11 +95,11 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { return; } - this._requestActiveContextKey = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); - this._terminalAgentRegisteredContextKey = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); - this._responseTypeContextKey = TerminalContextKeys.chatResponseType.bindTo(this._contextKeyService); - this._responseSupportsIssueReportingContextKey = TerminalContextKeys.chatResponseSupportsIssueReporting.bindTo(this._contextKeyService); - this._sessionResponseVoteContextKey = TerminalContextKeys.chatSessionResponseVote.bindTo(this._contextKeyService); + this._requestActiveContextKey = TerminalChatContextKeys.chatRequestActive.bindTo(this._contextKeyService); + this._terminalAgentRegisteredContextKey = TerminalChatContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); + this._responseTypeContextKey = TerminalChatContextKeys.chatResponseType.bindTo(this._contextKeyService); + this._responseSupportsIssueReportingContextKey = TerminalChatContextKeys.chatResponseSupportsIssueReporting.bindTo(this._contextKeyService); + this._sessionResponseVoteContextKey = TerminalChatContextKeys.chatSessionResponseVote.bindTo(this._contextKeyService); if (!this._chatAgentService.hasAgent(this._terminalAgentId)) { this._register(this._chatAgentService.onDidChangeAgents(() => { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 2cc70c7eb07..82f68ccf9f4 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -22,8 +22,7 @@ import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/cha import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; export class TerminalChatWidget extends Disposable { @@ -48,8 +47,8 @@ export class TerminalChatWidget extends Disposable { ) { super(); - this._focusedContextKey = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); - this._visibleContextKey = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); + this._focusedContextKey = TerminalChatContextKeys.chatFocused.bindTo(this._contextKeyService); + this._visibleContextKey = TerminalChatContextKeys.chatVisible.bindTo(this._contextKeyService); this._container = document.createElement('div'); this._container.classList.add('terminal-inline-chat'); @@ -176,7 +175,7 @@ class TerminalChatResponseEditor extends Disposable { ) { super(); - this._responseEditorFocusedContextKey = TerminalContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); + this._responseEditorFocusedContextKey = TerminalChatContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); this._editorContainer = document.createElement('div'); this._editorContainer.classList.add('terminal-inline-chat-response'); From 9b38a276ce04615099802f185e911b2f086274e3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:33:19 -0800 Subject: [PATCH 1388/1897] Remove 'chat' from context keys as it's in the namespace --- .../chat/browser/terminalChat.ts | 18 ++--- .../browser/terminalChatAccessibilityHelp.ts | 2 +- ...rminalChatAccessibilityHelpContribution.ts | 2 +- .../browser/terminalChatAccessibleView.ts | 2 +- .../chat/browser/terminalChatActions.ts | 74 +++++++++---------- .../chat/browser/terminalChatController.ts | 10 +-- .../chat/browser/terminalChatWidget.ts | 6 +- 7 files changed, 57 insertions(+), 57 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index 5aefe757469..89893d6d31e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -47,29 +47,29 @@ export const enum TerminalChatResponseTypes { export namespace TerminalChatContextKeys { /** Whether the chat widget is focused */ - export const chatFocused = new RawContextKey(TerminalChatContextKeyStrings.ChatFocus, false, localize('chatFocusedContextKey', "Whether the chat view is focused.")); + export const focused = new RawContextKey(TerminalChatContextKeyStrings.ChatFocus, false, localize('chatFocusedContextKey', "Whether the chat view is focused.")); /** Whether the chat widget is visible */ - export const chatVisible = new RawContextKey(TerminalChatContextKeyStrings.ChatVisible, false, localize('chatVisibleContextKey', "Whether the chat view is visible.")); + export const visible = new RawContextKey(TerminalChatContextKeyStrings.ChatVisible, false, localize('chatVisibleContextKey', "Whether the chat view is visible.")); /** Whether there is an active chat request */ - export const chatRequestActive = new RawContextKey(TerminalChatContextKeyStrings.ChatActiveRequest, false, localize('chatRequestActiveContextKey', "Whether there is an active chat request.")); + export const requestActive = new RawContextKey(TerminalChatContextKeyStrings.ChatActiveRequest, false, localize('chatRequestActiveContextKey', "Whether there is an active chat request.")); /** Whether the chat input has text */ - export const chatInputHasText = new RawContextKey(TerminalChatContextKeyStrings.ChatInputHasText, false, localize('chatInputHasTextContextKey', "Whether the chat input has text.")); + export const inputHasText = new RawContextKey(TerminalChatContextKeyStrings.ChatInputHasText, false, localize('chatInputHasTextContextKey', "Whether the chat input has text.")); /** Whether the terminal chat agent has been registered */ - export const chatAgentRegistered = new RawContextKey(TerminalChatContextKeyStrings.ChatAgentRegistered, false, localize('chatAgentRegisteredContextKey', "Whether the terminal chat agent has been registered.")); + export const agentRegistered = new RawContextKey(TerminalChatContextKeyStrings.ChatAgentRegistered, false, localize('chatAgentRegisteredContextKey', "Whether the terminal chat agent has been registered.")); /** Whether the chat response editor is focused */ - export const chatResponseEditorFocused = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseEditorFocusedContextKey', "Whether the chat response editor is focused.")); + export const responseEditorFocused = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseEditorFocusedContextKey', "Whether the chat response editor is focused.")); /** The type of chat response, if any */ - export const chatResponseType = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseType, undefined, localize('chatResponseTypeContextKey', "The type of chat response, if any")); + export const responseType = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseType, undefined, localize('chatResponseTypeContextKey', "The type of chat response, if any")); /** Whether the response supports issue reporting */ - export const chatResponseSupportsIssueReporting = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseSupportsIssueReporting, false, localize('chatResponseSupportsIssueReportingContextKey', "Whether the response supports issue reporting")); + export const responseSupportsIssueReporting = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseSupportsIssueReporting, false, localize('chatResponseSupportsIssueReportingContextKey', "Whether the response supports issue reporting")); /** The chat vote, if any for the response, if any */ - export const chatSessionResponseVote = new RawContextKey(TerminalChatContextKeyStrings.ChatSessionResponseVote, undefined, { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); + export const sessionResponseVote = new RawContextKey(TerminalChatContextKeyStrings.ChatSessionResponseVote, undefined, { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts index c9631f7fa1d..d330683af2e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -38,7 +38,7 @@ export class TerminalInlineChatAccessibilityHelpContribution extends Disposable options: { type: AccessibleViewType.Help } }); return true; - }, ContextKeyExpr.or(TerminalChatContextKeys.chatFocused, TerminalChatContextKeys.chatResponseEditorFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); + }, ContextKeyExpr.or(TerminalChatContextKeys.focused, TerminalChatContextKeys.responseEditorFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts index afc24a15163..61d11998690 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts @@ -17,7 +17,7 @@ export class TerminalChatAccessibilityHelpContribution extends Disposable { static ID = 'terminalChatAccessiblityHelp'; constructor() { super(); - this._register(AccessibilityHelpAction.addImplementation(110, 'terminalChat', runAccessibilityHelpAction, TerminalChatContextKeys.chatFocused)); + this._register(AccessibilityHelpAction.addImplementation(110, 'terminalChat', runAccessibilityHelpAction, TerminalChatContextKeys.focused)); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts index 93854689479..798c0bf9774 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts @@ -35,6 +35,6 @@ export class TerminalInlineChatAccessibleViewContribution extends Disposable { options: { type: AccessibleViewType.View } }); return true; - }, ContextKeyExpr.and(TerminalChatContextKeys.chatFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); + }, ContextKeyExpr.and(TerminalChatContextKeys.focused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 7f8dc50524d..fc0777f9cde 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -21,7 +21,7 @@ registerActiveXtermAction({ title: localize2('startChat', 'Start Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, - when: ContextKeyExpr.and(TerminalChatContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), + when: ContextKeyExpr.and(TerminalChatContextKeys.focused.negate(), TerminalContextKeys.focusInAny), weight: KeybindingWeight.WorkbenchContrib, }, f1: true, @@ -46,7 +46,7 @@ registerActiveXtermAction({ keybinding: { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], - when: ContextKeyExpr.and(TerminalChatContextKeys.chatFocused, TerminalChatContextKeys.chatVisible), + when: ContextKeyExpr.and(TerminalChatContextKeys.focused, TerminalChatContextKeys.visible), weight: KeybindingWeight.WorkbenchContrib, }, icon: Codicon.close, @@ -78,15 +78,15 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 2, - when: ContextKeyExpr.and(TerminalChatContextKeys.chatFocused, - TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand)) + when: ContextKeyExpr.and(TerminalChatContextKeys.focused, + TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand)) }, f1: true, precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalChatContextKeys.chatFocused, - TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) + TerminalChatContextKeys.focused, + TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) ), run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -105,13 +105,13 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalChatContextKeys.chatRequestActive.negate(), - TerminalChatContextKeys.chatAgentRegistered, - TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) + TerminalChatContextKeys.requestActive.negate(), + TerminalChatContextKeys.agentRegistered, + TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) ), icon: Codicon.check, keybinding: { - when: TerminalChatContextKeys.chatRequestActive.negate(), + when: TerminalChatContextKeys.requestActive.negate(), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.Enter, }, @@ -120,7 +120,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 0, - when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.requestActive.negate()), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -138,13 +138,13 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalChatContextKeys.chatRequestActive.negate(), - TerminalChatContextKeys.chatAgentRegistered, - TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) + TerminalChatContextKeys.requestActive.negate(), + TerminalChatContextKeys.agentRegistered, + TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) ), icon: Codicon.check, keybinding: { - when: TerminalChatContextKeys.chatRequestActive.negate(), + when: TerminalChatContextKeys.requestActive.negate(), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Alt | KeyCode.Enter, }, @@ -152,7 +152,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 1, - when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.requestActive.negate()), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -169,21 +169,21 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalChatContextKeys.chatRequestActive.negate(), - TerminalChatContextKeys.chatAgentRegistered, + TerminalChatContextKeys.requestActive.negate(), + TerminalChatContextKeys.agentRegistered, ), icon: Codicon.commentDiscussion, menu: [{ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 1, - when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.Message), TerminalChatContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.Message), TerminalChatContextKeys.requestActive.negate()), }, { id: MENU_TERMINAL_CHAT_WIDGET, group: 'main', order: 1, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_EMPTY.negate(), TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(CTX_INLINE_CHAT_EMPTY.negate(), TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.requestActive.negate()), }], run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -200,13 +200,13 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalChatContextKeys.chatRequestActive.negate(), - TerminalChatContextKeys.chatAgentRegistered, + TerminalChatContextKeys.requestActive.negate(), + TerminalChatContextKeys.agentRegistered, CTX_INLINE_CHAT_EMPTY.negate() ), icon: Codicon.send, keybinding: { - when: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, TerminalChatContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, TerminalChatContextKeys.requestActive.negate()), weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.Enter }, @@ -214,7 +214,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_INPUT, group: 'main', order: 1, - when: TerminalChatContextKeys.chatRequestActive.negate(), + when: TerminalChatContextKeys.requestActive.negate(), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -230,14 +230,14 @@ registerActiveXtermAction({ title: localize2('cancelChat', 'Cancel Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalChatContextKeys.chatRequestActive, - TerminalChatContextKeys.chatAgentRegistered + TerminalChatContextKeys.requestActive, + TerminalChatContextKeys.agentRegistered ), icon: Codicon.debugStop, menu: { id: MENU_TERMINAL_CHAT_INPUT, group: 'main', - when: TerminalChatContextKeys.chatRequestActive, + when: TerminalChatContextKeys.requestActive, }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -253,15 +253,15 @@ registerActiveXtermAction({ title: localize2('feedbackHelpful', 'Helpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined) + TerminalChatContextKeys.responseType.notEqualsTo(undefined) ), icon: Codicon.thumbsup, - toggled: TerminalChatContextKeys.chatSessionResponseVote.isEqualTo('up'), + toggled: TerminalChatContextKeys.sessionResponseVote.isEqualTo('up'), menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', order: 1, - when: TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), + when: TerminalChatContextKeys.responseType.notEqualsTo(undefined), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -277,15 +277,15 @@ registerActiveXtermAction({ title: localize2('feedbackUnhelpful', 'Unhelpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), + TerminalChatContextKeys.responseType.notEqualsTo(undefined), ), - toggled: TerminalChatContextKeys.chatSessionResponseVote.isEqualTo('down'), + toggled: TerminalChatContextKeys.sessionResponseVote.isEqualTo('down'), icon: Codicon.thumbsdown, menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', order: 2, - when: TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), + when: TerminalChatContextKeys.responseType.notEqualsTo(undefined), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -301,14 +301,14 @@ registerActiveXtermAction({ title: localize2('reportIssue', 'Report Issue'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalChatContextKeys.chatRequestActive.negate(), - TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), - TerminalChatContextKeys.chatResponseSupportsIssueReporting + TerminalChatContextKeys.requestActive.negate(), + TerminalChatContextKeys.responseType.notEqualsTo(undefined), + TerminalChatContextKeys.responseSupportsIssueReporting ), icon: Codicon.report, menu: [{ id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, - when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), TerminalChatContextKeys.chatResponseSupportsIssueReporting), + when: ContextKeyExpr.and(TerminalChatContextKeys.responseType.notEqualsTo(undefined), TerminalChatContextKeys.responseSupportsIssueReporting), group: 'inline', order: 3 }], diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index c3409674e12..0226d6536f0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -95,11 +95,11 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { return; } - this._requestActiveContextKey = TerminalChatContextKeys.chatRequestActive.bindTo(this._contextKeyService); - this._terminalAgentRegisteredContextKey = TerminalChatContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); - this._responseTypeContextKey = TerminalChatContextKeys.chatResponseType.bindTo(this._contextKeyService); - this._responseSupportsIssueReportingContextKey = TerminalChatContextKeys.chatResponseSupportsIssueReporting.bindTo(this._contextKeyService); - this._sessionResponseVoteContextKey = TerminalChatContextKeys.chatSessionResponseVote.bindTo(this._contextKeyService); + this._requestActiveContextKey = TerminalChatContextKeys.requestActive.bindTo(this._contextKeyService); + this._terminalAgentRegisteredContextKey = TerminalChatContextKeys.agentRegistered.bindTo(this._contextKeyService); + this._responseTypeContextKey = TerminalChatContextKeys.responseType.bindTo(this._contextKeyService); + this._responseSupportsIssueReportingContextKey = TerminalChatContextKeys.responseSupportsIssueReporting.bindTo(this._contextKeyService); + this._sessionResponseVoteContextKey = TerminalChatContextKeys.sessionResponseVote.bindTo(this._contextKeyService); if (!this._chatAgentService.hasAgent(this._terminalAgentId)) { this._register(this._chatAgentService.onDidChangeAgents(() => { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 82f68ccf9f4..86b3fd8de33 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -47,8 +47,8 @@ export class TerminalChatWidget extends Disposable { ) { super(); - this._focusedContextKey = TerminalChatContextKeys.chatFocused.bindTo(this._contextKeyService); - this._visibleContextKey = TerminalChatContextKeys.chatVisible.bindTo(this._contextKeyService); + this._focusedContextKey = TerminalChatContextKeys.focused.bindTo(this._contextKeyService); + this._visibleContextKey = TerminalChatContextKeys.visible.bindTo(this._contextKeyService); this._container = document.createElement('div'); this._container.classList.add('terminal-inline-chat'); @@ -175,7 +175,7 @@ class TerminalChatResponseEditor extends Disposable { ) { super(); - this._responseEditorFocusedContextKey = TerminalChatContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); + this._responseEditorFocusedContextKey = TerminalChatContextKeys.responseEditorFocused.bindTo(this._contextKeyService); this._editorContainer = document.createElement('div'); this._editorContainer.classList.add('terminal-inline-chat-response'); From 31df2b2df478f28cc65dc45ce7c7d11500052ba0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:34:40 -0800 Subject: [PATCH 1389/1897] Revert unneeded change to InlineChatWidget --- src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 3aeb25cd2e2..ca909ba98e0 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -388,7 +388,7 @@ export class InlineChatWidget { buttonConfigProvider: action => { if (action.id === ACTION_REGENERATE_RESPONSE) { return { showIcon: true, showLabel: false, isSecondary: true }; - } else if ([ACTION_VIEW_IN_CHAT, ACTION_ACCEPT_CHANGES].includes(action.id)) { + } else if (action.id === ACTION_VIEW_IN_CHAT || action.id === ACTION_ACCEPT_CHANGES) { return { isSecondary: false }; } else { return { isSecondary: true }; From b9f9060711db2ab23c91e16687065523ff5b6161 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:37:54 -0800 Subject: [PATCH 1390/1897] Clarify lint suppression reason --- .../contrib/chat/electron-sandbox/actions/voiceChatActions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index aeb85706fc6..fe9ddb2f8f0 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -55,7 +55,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ThemeIcon } from 'vs/base/common/themables'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; -// TODO: The chat needs to move into contrib/terminal/ as we don't want anything importing from terminalContrib/ + +// This is a one-off/safe import, changing the eslint rules would require duplicating/complicating the rules // eslint-disable-next-line local/code-import-patterns import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; From 6077d59e422b88df1ec5ce7b539bcf50dd7077f6 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 16 Feb 2024 12:46:29 -0800 Subject: [PATCH 1391/1897] add expression field to help access non-root variables (#205400) --- .../contrib/notebookVariables/notebookVariablesDataSource.ts | 1 + .../browser/contrib/notebookVariables/notebookVariablesView.ts | 3 ++- src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts index c233e02a8a0..b9cdadad7ff 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts @@ -21,6 +21,7 @@ export interface INotebookVariableElement { readonly name: string; readonly value: string; readonly type?: string; + readonly expression?: string; readonly language?: string; readonly indexedChildrenCount: number; readonly indexStart?: number; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index df59bbeb044..1c3c61d25ea 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -34,7 +34,7 @@ import { ICellExecutionStateChangedEvent, IExecutionStateChangedEvent, INotebook import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -export type contextMenuArg = { source?: string; type?: string; value?: string; language?: string; extensionId?: string }; +export type contextMenuArg = { source?: string; type?: string; value?: string; expression?: string; language?: string; extensionId?: string }; export class NotebookVariablesView extends ViewPane { @@ -111,6 +111,7 @@ export class NotebookVariablesView extends ViewPane { source: element.notebook.uri.toString(), value: element.value, type: element.type, + expression: element.expression, language: element.language, extensionId: element.extensionId }; diff --git a/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts b/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts index 515818d3a1b..883319ddcd9 100644 --- a/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts @@ -39,6 +39,9 @@ declare module 'vscode' { */ value: string; + /** The code that represents how the variable would be accessed in the runtime environment */ + expression?: string; + /** The type of the variable's value */ type?: string; From ec00f84dce8115176af91c856894bc749e2367db Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 16 Feb 2024 13:01:13 -0800 Subject: [PATCH 1392/1897] Remove test string --- .../contrib/terminalContrib/chat/browser/terminalChatActions.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index fc0777f9cde..72eac948274 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -35,8 +35,6 @@ registerActiveXtermAction({ } const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); contr?.chatWidget?.reveal(); - // TODO: Remove this before merging to main - contr?.chatWidget?.setValue('list files'); } }); From 29d9a36ba6ea6f23dba59caec8ab344a4db10606 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 15:08:12 -0600 Subject: [PATCH 1393/1897] set vertical position --- .../chat/browser/terminalChatWidget.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 86b3fd8de33..0653f403de0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -108,6 +108,18 @@ export class TerminalChatWidget extends Disposable { this._focusedContextKey.set(true); this._visibleContextKey.set(true); this._inlineChatWidget.focus(); + const font = this._instance.xterm?.getFont(); + if (!font?.charHeight) { + return; + } + const cursorY = this._instance.xterm?.raw.buffer.active.cursorY ?? 0; + const height = font.charHeight * font.lineHeight; + const top = (cursorY + .5) * height; + this._container.style.top = `${top}px`; + const terminalHeight = this._instance.domElement.clientHeight; + if (terminalHeight && top > terminalHeight - this._inlineChatWidget.getHeight()) { + this._container.style.top = ''; + } } hide(): void { this._container.classList.add('hide'); From f70e23725040fca3ae5517c2d26a4b0765a9e444 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 16 Feb 2024 13:09:23 -0800 Subject: [PATCH 1394/1897] Resolve simple todos, remove duplication --- .../chat/browser/media/terminalChatWidget.css | 3 +- .../browser/terminal.chat.contribution.ts | 4 +- .../browser/terminalChatAccessibilityHelp.ts | 44 ++++++++--------- ...rminalChatAccessibilityHelpContribution.ts | 48 ------------------- .../chat/browser/terminalChatController.ts | 2 - 5 files changed, 23 insertions(+), 78 deletions(-) delete mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css index 602560618cc..64253f67db5 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css @@ -29,8 +29,7 @@ .terminal-inline-chat .terminal-inline-chat-response { border: 1px solid var(--vscode-input-border, transparent); - /* TODO: Make themeable */ - background-color: #181818; + background-color: var(--vscode-panel-background); } .terminal-inline-chat .terminal-inline-chat-response:has(.monaco-editor.focused) { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index 0049340fadd..44eabbf13f6 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -5,15 +5,13 @@ import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; -import { TerminalInlineChatAccessibilityHelpContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp'; import { TerminalInlineChatAccessibleViewContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; -import { TerminalChatAccessibilityHelpContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution'; import 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions'; +import { TerminalChatAccessibilityHelpContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp'; registerTerminalContribution(TerminalChatController.ID, TerminalChatController, false); registerWorkbenchContribution2(TerminalInlineChatAccessibleViewContribution.ID, TerminalInlineChatAccessibleViewContribution, WorkbenchPhase.Eventually); -registerWorkbenchContribution2(TerminalInlineChatAccessibilityHelpContribution.ID, TerminalInlineChatAccessibilityHelpContribution, WorkbenchPhase.Eventually); registerWorkbenchContribution2(TerminalChatAccessibilityHelpContribution.ID, TerminalChatAccessibilityHelpContribution, WorkbenchPhase.Eventually); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts index d330683af2e..c29c7394c3f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -5,43 +5,41 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; -import { CTX_INLINE_CHAT_RESPONSE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalChatCommandId, TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; -export class TerminalInlineChatAccessibilityHelpContribution extends Disposable { - static ID: 'terminalInlineChatAccessibilityHelpContribution'; +export class TerminalChatAccessibilityHelpContribution extends Disposable { + static ID = 'terminalChatAccessiblityHelp'; constructor() { super(); - this._register(AccessibilityHelpAction.addImplementation(106, 'terminalInlineChat', accessor => { - const terminalService = accessor.get(ITerminalService); - const accessibleViewService = accessor.get(IAccessibleViewService); - const controller: TerminalChatController | undefined = terminalService.activeInstance?.getContribution(TerminalChatController.ID) ?? undefined; - if (controller === undefined) { - return false; - } - const helpContent = getAccessibilityHelpText(accessor); - accessibleViewService.show({ - id: AccessibleViewProviderId.TerminalInlineChat, - verbositySettingKey: AccessibilityVerbositySettingId.InlineChat, - provideContent(): string { return helpContent; }, - onClose() { - controller.focus(); - }, - options: { type: AccessibleViewType.Help } - }); - return true; - }, ContextKeyExpr.or(TerminalChatContextKeys.focused, TerminalChatContextKeys.responseEditorFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); + this._register(AccessibilityHelpAction.addImplementation(110, 'terminalChat', runAccessibilityHelpAction, TerminalChatContextKeys.focused)); } } +export async function runAccessibilityHelpAction(accessor: ServicesAccessor): Promise { + const accessibleViewService = accessor.get(IAccessibleViewService); + const terminalService = accessor.get(ITerminalService); + + const instance = terminalService.activeInstance; + if (!instance) { + return; + } + + const helpText = getAccessibilityHelpText(accessor); + accessibleViewService.show({ + id: AccessibleViewProviderId.TerminalChat, + verbositySettingKey: AccessibilityVerbositySettingId.TerminalChat, + provideContent: () => helpText, + onClose: () => TerminalChatController.get(instance)?.focus(), + options: { type: AccessibleViewType.Help } + }); +} export function getAccessibilityHelpText(accessor: ServicesAccessor): string { const keybindingService = accessor.get(IKeybindingService); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts deleted file mode 100644 index 61d11998690..00000000000 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable } from 'vs/base/common/lifecycle'; -import { localize } from 'vs/nls'; -import type { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; -import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; -import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; -import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; - -export class TerminalChatAccessibilityHelpContribution extends Disposable { - static ID = 'terminalChatAccessiblityHelp'; - constructor() { - super(); - this._register(AccessibilityHelpAction.addImplementation(110, 'terminalChat', runAccessibilityHelpAction, TerminalChatContextKeys.focused)); - } -} - -export async function runAccessibilityHelpAction(accessor: ServicesAccessor): Promise { - const accessibleViewService = accessor.get(IAccessibleViewService); - const terminalService = accessor.get(ITerminalService); - - const instance = terminalService.activeInstance; - if (!instance) { - return; - } - - const helpText = getAccessibilityHelpText(accessor); - accessibleViewService.show({ - id: AccessibleViewProviderId.TerminalChat, - verbositySettingKey: AccessibilityVerbositySettingId.TerminalChat, - provideContent: () => helpText, - onClose: () => TerminalChatController.get(instance)?.focus(), - options: { type: AccessibleViewType.Help } - }); -} - -export function getAccessibilityHelpText(accessor: ServicesAccessor): string { - const content = []; - // TODO: Fill in more help text - content.push(localize('chat.overview', 'The terminal chat view is comprised of an input box, an editor where suggested commands are provided (Shift+Tab) and buttons to action the suggestion.')); - return content.join('\n\n'); -} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 0226d6536f0..47025af317b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -256,7 +256,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr requestId: this._currentRequest!.id, agentId: this._terminalAgentId, message: this._lastInput, - // TODO: ? variables: { variables: [] }, }; try { @@ -265,7 +264,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.inlineChatWidget.updateFollowUps(undefined); this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(true); this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(localize('thinking', "Thinking\u2026")); - // TODO: this._zone.value.widget.updateInfo(!this._session.lastExchange ? localize('thinking', "Thinking\u2026") : ''); await task; } catch (e) { From fa685221f2d1598d0da2d229ce3885a16b61a492 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 15:19:53 -0600 Subject: [PATCH 1395/1897] rm todo --- .../chat/browser/terminalChatAccessibilityHelp.ts | 3 +-- .../terminalContrib/chat/browser/terminalChatActions.ts | 2 +- .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts index c29c7394c3f..dad7747e7f1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -48,8 +48,7 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor): string { const runCommandKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.RunCommand)?.getAriaLabel(); const insertCommandKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.InsertCommand)?.getAriaLabel(); const makeRequestKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.MakeRequest)?.getAriaLabel(); - //TODO: using this instead of the terminal command bc by definition the inline terminal chat is focused when this dialog is invoked. - const startChatKeybinding = keybindingService.lookupKeybinding('inlineChat.start')?.getAriaLabel(); + const startChatKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.Start)?.getAriaLabel(); content.push(localize('inlineChat.overview', "Inline chat occurs within a terminal. It is useful for suggesting terminal commands. Keep in mind that AI generated code may be incorrect.")); content.push(localize('inlineChat.access', "It can be activated using the command: Terminal: Start Chat ({0}), which will focus the input box.", startChatKeybinding)); content.push(makeRequestKeybinding ? localize('inlineChat.input', "The input box is where the user can type a request and can make the request ({0}). The widget will be closed and all content will be discarded when the Escape key is pressed and the terminal will regain focus.", makeRequestKeybinding) : localize('inlineChat.inputNoKb', "The input box is where the user can type a request and can make the request by tabbing to the Make Request button, which is not currently triggerable via keybindings. The widget will be closed and all content will be discarded when the Escape key is pressed and the terminal will regain focus.")); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 72eac948274..04ff3b3a472 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -21,7 +21,7 @@ registerActiveXtermAction({ title: localize2('startChat', 'Start Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, - when: ContextKeyExpr.and(TerminalChatContextKeys.focused.negate(), TerminalContextKeys.focusInAny), + when: ContextKeyExpr.and(TerminalContextKeys.focusInAny), weight: KeybindingWeight.WorkbenchContrib, }, f1: true, diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 0653f403de0..4e9bed0f354 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -114,7 +114,7 @@ export class TerminalChatWidget extends Disposable { } const cursorY = this._instance.xterm?.raw.buffer.active.cursorY ?? 0; const height = font.charHeight * font.lineHeight; - const top = (cursorY + .5) * height; + const top = cursorY * height; this._container.style.top = `${top}px`; const terminalHeight = this._instance.domElement.clientHeight; if (terminalHeight && top > terminalHeight - this._inlineChatWidget.getHeight()) { From 33c385ba7ac77f9532223ea26abd200c71ef7125 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Fri, 16 Feb 2024 16:21:46 -0600 Subject: [PATCH 1396/1897] add clarity to FindFiles2 (#205407) --- src/vscode-dts/vscode.proposed.findFiles2.d.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.findFiles2.d.ts b/src/vscode-dts/vscode.proposed.findFiles2.d.ts index 37743a897da..f861c7930ce 100644 --- a/src/vscode-dts/vscode.proposed.findFiles2.d.ts +++ b/src/vscode-dts/vscode.proposed.findFiles2.d.ts @@ -10,8 +10,7 @@ declare module 'vscode' { /** * A {@link GlobPattern glob pattern} that defines files and folders to exclude. The glob pattern - * will be matched against the file paths of resulting matches relative to their workspace. When `undefined`, default excludes will - * apply. + * will be matched against the file paths of resulting matches relative to their workspace. */ exclude?: GlobPattern; @@ -32,21 +31,24 @@ declare module 'vscode' { /** * Whether external files that exclude files, like .gitignore, should be respected. - * See the vscode setting `"search.useIgnoreFiles"`. + * Defaults to the value for `search.useIgnoreFiles` in settings. + * For more info, see the setting listed above. */ useIgnoreFiles?: boolean; /** * Whether global files that exclude files, like .gitignore, should be respected. * Must set `useIgnoreFiles` to `true` to use this. - * See the vscode setting `"search.useGlobalIgnoreFiles"`. + * Defaults to the value for `search.useGlobalIgnoreFiles` in settings. + * For more info, see the setting listed above. */ useGlobalIgnoreFiles?: boolean; /** * Whether files in parent directories that exclude files, like .gitignore, should be respected. * Must set `useIgnoreFiles` to `true` to use this. - * See the vscode setting `"search.useParentIgnoreFiles"`. + * Defaults to the value for `search.useParentIgnoreFiles` in settings. + * For more info, see the setting listed above. */ useParentIgnoreFiles?: boolean; @@ -77,7 +79,6 @@ declare module 'vscode' { * will be matched against the file paths of resulting matches relative to their workspace. Use a {@link RelativePattern relative pattern} * to restrict the search results to a {@link WorkspaceFolder workspace folder}. * @param options A set of {@link FindFiles2Options FindFiles2Options} that defines where and how to search (e.g. exclude settings). - * If enabled, any ignore files will take prescendence over any files found in the `filePattern` parameter. * @param token A token that can be used to signal cancellation to the underlying search engine. * @returns A thenable that resolves to an array of resource identifiers. Will return no results if no * {@link workspace.workspaceFolders workspace folders} are opened. From 27d1fc77f4fee8fdf442f9ae3f1175c1105760aa Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 16:52:57 -0600 Subject: [PATCH 1397/1897] Fix position --- .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 4e9bed0f354..180d51c1549 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -114,7 +114,7 @@ export class TerminalChatWidget extends Disposable { } const cursorY = this._instance.xterm?.raw.buffer.active.cursorY ?? 0; const height = font.charHeight * font.lineHeight; - const top = cursorY * height; + const top = cursorY * height + 10; this._container.style.top = `${top}px`; const terminalHeight = this._instance.domElement.clientHeight; if (terminalHeight && top > terminalHeight - this._inlineChatWidget.getHeight()) { From 0cbc11c26ca83176463af31ad0001ff9baabb677 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Fri, 16 Feb 2024 16:19:41 -0800 Subject: [PATCH 1398/1897] use menu contributions in issue reporter (#205176) * merge changes to branch * groundwork for issue command * removed all the extra random imports * working flow and rerender/repopulate data * cleaner working version;' * cleanup * ensures that IPC is replied to in both cases * added data overwrite * cleanedup * more cleanup * cleanup part 2 * comment and make sure to clear uri * removed unused var * ensures we don't send ipc early before open reporter call * removed console logs * addressing some of the comments * move is running location * nicely working version * removed incorrect package.json location * re-enables dropdown, switch to extensionidentifier * removed tokey * fixes what happens when extensions change mid-await * use proposed contribIssueReporter * finally working in all scenarios * make sure we clear data well enough --- .../issue/issueReporterService.ts | 91 ++++++++++++++++--- src/vs/platform/actions/common/actions.ts | 1 + src/vs/platform/issue/common/issue.ts | 5 +- .../issue/electron-main/issueMainService.ts | 17 +++- .../actions/common/menusExtensionPoint.ts | 6 ++ .../common/extensionsApiProposals.ts | 1 + .../issue/electron-sandbox/issueService.ts | 38 +++++++- .../vscode.proposed.contribIssueReporter.d.ts | 7 ++ 8 files changed, 148 insertions(+), 18 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index 82de2be993f..c8f4276e7f7 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -49,6 +49,9 @@ export class IssueReporter extends Disposable { private receivedPerformanceInfo = false; private shouldQueueSearch = false; private hasBeenSubmitted = false; + private openReporter = false; + private loadingExtensionData = false; + private changeExtension: string = ''; private delayedSubmit = new Delayer(300); private readonly previewButton!: Button; @@ -299,6 +302,16 @@ export class IssueReporter extends Disposable { } } + private async sendReporterMenu(extension: IssueReporterExtensionData): Promise { + try { + const data = await this.issueMainService.$sendReporterMenu(extension.id, extension.name); + return data; + } catch (e) { + console.error(e); + return undefined; + } + } + private setEventHandlers(): void { this.addEventListener('issue-type', 'change', (event: Event) => { const issueType = parseInt((event.target).value); @@ -497,6 +510,10 @@ export class IssueReporter extends Disposable { return false; } + if (this.loadingExtensionData) { + return false; + } + if (issueType === IssueType.Bug && this.receivedSystemInfo) { return true; } @@ -820,6 +837,17 @@ export class IssueReporter extends Disposable { show(extensionDataBlock); } + // only if we know comes from the open reporter command + if (fileOnExtension && this.openReporter) { + (extensionDataTextArea as HTMLTextAreaElement).readOnly = true; + setTimeout(() => { + // delay to make sure from command or not + if (this.openReporter) { + show(extensionDataBlock); + } + }, 100); + } + if (issueType === IssueType.Bug) { if (!fileOnMarketplace) { show(blockContainer); @@ -1168,20 +1196,45 @@ export class IssueReporter extends Disposable { this.addEventListener('extension-selector', 'change', async (e: Event) => { this.clearExtensionData(); const selectedExtensionId = (e.target).value; + this.changeExtension = selectedExtensionId; const extensions = this.issueReporterModel.getData().allExtensions; const matches = extensions.filter(extension => extension.id === selectedExtensionId); if (matches.length) { this.issueReporterModel.update({ selectedExtension: matches[0] }); const selectedExtension = this.issueReporterModel.getData().selectedExtension; - if (selectedExtension) { - selectedExtension.data = undefined; - selectedExtension.uri = undefined; + if (selectedExtension && !this.loadingExtensionData) { + const iconElement = document.createElement('span'); + iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin'); + this.setLoading(iconElement); + const openReporterData = await this.sendReporterMenu(selectedExtension); + if (openReporterData && (this.changeExtension === selectedExtensionId)) { + this.removeLoading(iconElement, true); + this.configuration.data = openReporterData; + } else if (openReporterData && this.changeExtension !== selectedExtensionId) { + this.removeLoading(iconElement, this.openReporter); + } else { + this.removeLoading(iconElement); + // if not using command, should have no configuration data in fields we care about and check later. + this.configuration.data.issueBody = undefined; + this.configuration.data.data = undefined; + this.configuration.data.uri = undefined; + + // case when previous extension was opened from normal openIssueReporter command + selectedExtension.data = undefined; + selectedExtension.uri = undefined; + } + } + if (this.changeExtension === selectedExtensionId) { + // repopulates the fields with the new data given the selected extension. + this.updateExtensionStatus(matches[0]); + this.openReporter = false; } - this.updateExtensionStatus(matches[0]); } else { this.issueReporterModel.update({ selectedExtension: undefined }); this.clearSearchResults(); + this.clearExtensionData(); this.validateSelectedExtension(); + this.updateExtensionStatus(matches[0]); } }); } @@ -1193,12 +1246,15 @@ export class IssueReporter extends Disposable { private clearExtensionData(): void { this.issueReporterModel.update({ extensionData: undefined }); + this.configuration.data.issueBody = undefined; this.configuration.data.data = undefined; this.configuration.data.uri = undefined; } private async updateExtensionStatus(extension: IssueReporterExtensionData) { this.issueReporterModel.update({ selectedExtension: extension }); + + // uses this.configuuration.data to ensure that data is coming from `openReporter` command. const template = this.configuration.data.issueBody; if (template) { const descriptionTextArea = this.getElementById('description')!; @@ -1212,13 +1268,16 @@ export class IssueReporter extends Disposable { const data = this.configuration.data.data; if (data) { + this.issueReporterModel.update({ extensionData: data }); + extension.data = data; const extensionDataBlock = mainWindow.document.querySelector('.block-extension-data')!; show(extensionDataBlock); - this.issueReporterModel.update({ extensionData: data }); + this.renderBlocks(); } const uri = this.configuration.data.uri; if (uri) { + extension.uri = uri; this.updateIssueReporterUri(extension); } @@ -1227,7 +1286,6 @@ export class IssueReporter extends Disposable { const toActivate = await this.getReporterStatus(extension); extension.hasIssueDataProviders = toActivate[0]; extension.hasIssueUriRequestHandler = toActivate[1]; - this.renderBlocks(); } if (extension.hasIssueUriRequestHandler && extension.hasIssueDataProviders) { @@ -1273,13 +1331,12 @@ export class IssueReporter extends Disposable { this.setLoading(iconElement); await this.getIssueDataFromExtension(extension); this.removeLoading(iconElement); - } else { - this.validateSelectedExtension(); - this.issueReporterModel.update({ extensionData: extension.data ?? undefined }); - const title = (this.getElementById('issue-title')).value; - this.searchExtensionIssues(title); } + this.validateSelectedExtension(); + const title = (this.getElementById('issue-title')).value; + this.searchExtensionIssues(title); + this.updatePreviewButtonState(); this.renderBlocks(); } @@ -1296,6 +1353,10 @@ export class IssueReporter extends Disposable { return; } + if (this.loadingExtensionData) { + return; + } + const hasValidGitHubUrl = this.getExtensionGitHubUrl(); if (hasValidGitHubUrl || (extension.hasIssueUriRequestHandler && !extension.hasIssueDataProviders)) { this.previewButton.enabled = true; @@ -1308,6 +1369,8 @@ export class IssueReporter extends Disposable { private setLoading(element: HTMLElement) { // Show loading this.receivedExtensionData = false; + this.openReporter = true; + this.loadingExtensionData = true; this.updatePreviewButtonState(); const extensionDataCaption = this.getElementById('extension-id')!; @@ -1323,7 +1386,9 @@ export class IssueReporter extends Disposable { this.renderBlocks(); } - private removeLoading(element: HTMLElement) { + private removeLoading(element: HTMLElement, fromReporter: boolean = false) { + this.openReporter = fromReporter; + this.loadingExtensionData = false; this.updatePreviewButtonState(); const extensionDataCaption = this.getElementById('extension-id')!; @@ -1335,6 +1400,8 @@ export class IssueReporter extends Disposable { const hideLoading = this.getElementById('ext-loading')!; hide(hideLoading); hideLoading.removeChild(element); + + this.renderBlocks(); } private setExtensionValidationMessage(): void { diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 15c6a1dfe59..98cd2259f98 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -167,6 +167,7 @@ export class MenuId { static readonly InteractiveCellDelete = new MenuId('InteractiveCellDelete'); static readonly InteractiveCellExecute = new MenuId('InteractiveCellExecute'); static readonly InteractiveInputExecute = new MenuId('InteractiveInputExecute'); + static readonly IssueReporter = new MenuId('IssueReporter'); static readonly NotebookToolbar = new MenuId('NotebookToolbar'); static readonly NotebookStickyScrollContext = new MenuId('NotebookStickyScrollContext'); static readonly NotebookCellTitle = new MenuId('NotebookCellTitle'); diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index 76da6c0fee4..d1c4e29bb63 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -70,8 +70,8 @@ export interface IssueReporterData extends WindowData { restrictedMode: boolean; isUnsupported: boolean; githubAccessToken: string; - readonly issueTitle?: string; - readonly issueBody?: string; + issueTitle?: string; + issueBody?: string; data?: string; uri?: UriComponents; } @@ -138,5 +138,6 @@ export interface IIssueMainService { $getIssueReporterData(extensionId: string): Promise; $getIssueReporterTemplate(extensionId: string): Promise; $getReporterStatus(extensionId: string, extensionName: string): Promise; + $sendReporterMenu(extensionId: string, extensionName: string): Promise; $closeReporter(): Promise; } diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 3998b23ead9..8e00947b2ea 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -179,7 +179,6 @@ export class IssueMainService implements IIssueMainService { this.issueReporterWindow.on('close', () => { this.issueReporterWindow = null; - issueReporterDisposables.dispose(); }); @@ -187,14 +186,13 @@ export class IssueMainService implements IIssueMainService { if (this.issueReporterWindow) { this.issueReporterWindow.close(); this.issueReporterWindow = null; - issueReporterDisposables.dispose(); } }); } } - if (this.issueReporterWindow) { + else if (this.issueReporterWindow) { this.focusWindow(this.issueReporterWindow); } } @@ -455,6 +453,19 @@ export class IssueMainService implements IIssueMainService { return (result ?? defaultResult) as boolean[]; } + + async $sendReporterMenu(extensionId: string, extensionName: string): Promise { + const window = this.issueReporterWindowCheck(); + const replyChannel = `vscode:triggerReporterMenu`; + const cts = new CancellationTokenSource(); + window.sendWhenReady(replyChannel, cts.token, { replyChannel, extensionId, extensionName }); + const result = await raceTimeout(new Promise(resolve => validatedIpcMain.once('vscode:triggerReporterMenuResponse', (_: unknown, data: IssueReporterData | undefined) => resolve(data))), 5000, () => { + this.logService.error('Error: Extension timed out waiting for menu response'); + cts.cancel(); + }); + return result as IssueReporterData | undefined; + } + async $closeReporter(): Promise { this.issueReporterWindow?.close(); } diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 3a88ed4a23f..31c61213f4d 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -321,6 +321,12 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.InteractiveCellTitle, description: localize('interactive.cell.title', "The contributed interactive cell title menu"), }, + { + key: 'issue/reporter', + id: MenuId.IssueReporter, + description: localize('issue.reporter', "The contributed issue reporter menu"), + proposed: 'contribIssueReporter' + }, { key: 'testing/item/context', id: MenuId.TestItem, diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 12ddfb110ec..53e77557226 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -25,6 +25,7 @@ export const allApiProposals = Object.freeze({ contribCommentThreadAdditionalMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentThreadAdditionalMenu.d.ts', contribEditSessions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditSessions.d.ts', contribEditorContentMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditorContentMenu.d.ts', + contribIssueReporter: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts', contribLabelFormatterWorkspaceTooltip: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts', contribMenuBarHome: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMenuBarHome.d.ts', contribMergeEditorMenus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMergeEditorMenus.d.ts', diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts index 8578bc63d35..f91f39db09e 100644 --- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts @@ -10,7 +10,7 @@ import { platform } from 'vs/base/common/process'; import { URI } from 'vs/base/common/uri'; import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionIdentifier, ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionType, ExtensionIdentifierSet } from 'vs/platform/extensions/common/extensions'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IIssueMainService, IssueReporterData, IssueReporterExtensionData, IssueReporterStyles, ProcessExplorerData } from 'vs/platform/issue/common/issue'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -28,6 +28,8 @@ import { IIntegrityService } from 'vs/workbench/services/integrity/common/integr import { ILogService } from 'vs/platform/log/common/log'; import { IIssueDataProvider, IIssueUriRequestHandler, IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; import { mainWindow } from 'vs/base/browser/window'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export class NativeIssueService implements IWorkbenchIssueService { declare readonly _serviceBrand: undefined; @@ -35,6 +37,7 @@ export class NativeIssueService implements IWorkbenchIssueService { private readonly _handlers = new Map(); private readonly _providers = new Map(); private readonly _activationEventReader = new ImplicitActivationAwareReader(); + private extensionIdentifierSet: ExtensionIdentifierSet = new ExtensionIdentifierSet(); constructor( @IIssueMainService private readonly issueMainService: IIssueMainService, @@ -49,6 +52,8 @@ export class NativeIssueService implements IWorkbenchIssueService { @IIntegrityService private readonly integrityService: IIntegrityService, @IExtensionService private readonly extensionService: IExtensionService, @ILogService private readonly logService: ILogService, + @IMenuService private readonly menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { ipcRenderer.on('vscode:triggerIssueUriRequestHandler', async (event: unknown, request: { replyChannel: string; extensionId: string }) => { const result = await this.getIssueReporterUri(request.extensionId, CancellationToken.None); @@ -82,6 +87,32 @@ export class NativeIssueService implements IWorkbenchIssueService { const result = [this._providers.has(extensionId.toLowerCase()), this._handlers.has(extensionId.toLowerCase())]; ipcRenderer.send('vscode:triggerReporterStatusResponse', result); }); + ipcRenderer.on('vscode:triggerReporterMenu', async (event, arg) => { + const extensionId = arg.extensionId; + + // creates menu from contributed + const menu = this.menuService.createMenu(MenuId.IssueReporter, this.contextKeyService); + + // render menu and dispose + const actions = menu.getActions({ renderShortTitle: true }).flatMap(entry => entry[1]); + actions.forEach(async action => { + try { + if (action.item && 'source' in action.item && action.item.source?.id === extensionId) { + this.extensionIdentifierSet.add(extensionId); + await action.run(); + } + } catch (error) { + console.error(error); + } + }); + + if (this.extensionIdentifierSet.size === 0) { + // send undefined to indicate no action was taken + ipcRenderer.send('vscode:triggerReporterMenuResponse', undefined); + } + + menu.dispose(); + }); } async openReporter(dataOverrides: Partial = {}): Promise { @@ -154,6 +185,11 @@ export class NativeIssueService implements IWorkbenchIssueService { isUnsupported, githubAccessToken }, dataOverrides); + + if (issueReporterData.extensionId && this.extensionIdentifierSet.has(issueReporterData.extensionId)) { + ipcRenderer.send('vscode:triggerReporterMenuResponse', issueReporterData); + this.extensionIdentifierSet.delete(new ExtensionIdentifier(issueReporterData.extensionId)); + } return this.issueMainService.openReporter(issueReporterData); } diff --git a/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts b/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts new file mode 100644 index 00000000000..522d3334467 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `issue reporter`-submenu contribution point +// https://github.com/microsoft/vscode/issues/196863 From 588fb3856b796674ea2f1bfe09503e7a9d6268cc Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 17 Feb 2024 16:13:33 +0100 Subject: [PATCH 1399/1897] [Accessibility] Status bar items with no command can still have focus, but do not show it (fix #205388) (#205435) --- src/vs/workbench/browser/parts/statusbar/statusbarPart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index e8a133fd4b4..ae4fd401456 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -591,7 +591,7 @@ class StatusbarPart extends Part implements IStatusbarEntryContainer { } /* Status bar item focus outline */ - .monaco-workbench .part.statusbar > .items-container > .statusbar-item a:focus-visible:not(.disabled) { + .monaco-workbench .part.statusbar > .items-container > .statusbar-item a:focus-visible { outline: 1px solid ${this.getColor(activeContrastBorder) ?? itemBorderColor}; outline-offset: ${borderColor ? '-2px' : '-1px'}; } From a8f73340be02966c3816a2f23cb7e446a3a7cb9b Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Sat, 17 Feb 2024 17:58:12 +0100 Subject: [PATCH 1400/1897] Fix for smoketest extension installation timeout (#205444) hover smoke test fix #204771 --- test/automation/src/extensions.ts | 2 +- test/automation/src/scm.ts | 8 ++++---- test/automation/src/search.ts | 2 +- test/automation/src/statusbar.ts | 2 +- test/smoke/src/areas/extensions/extensions.test.ts | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/automation/src/extensions.ts b/test/automation/src/extensions.ts index 41dc79d442d..b092a2e8b4d 100644 --- a/test/automation/src/extensions.ts +++ b/test/automation/src/extensions.ts @@ -54,7 +54,7 @@ export class Extensions extends Viewlet { await this.code.waitAndClick(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[data-extension-id="${id}"] .extension-list-item .monaco-action-bar .action-item:not(.disabled) .extension-action.install`); await this.code.waitForElement(`.extension-editor .monaco-action-bar .action-item:not(.disabled) .extension-action.uninstall`); if (waitUntilEnabled) { - await this.code.waitForElement(`.extension-editor .monaco-action-bar .action-item:not(.disabled)[title="Disable this extension"]`); + await this.code.waitForElement(`.extension-editor .monaco-action-bar .action-item:not(.disabled) a[aria-label="Disable this extension"]`); } } } diff --git a/test/automation/src/scm.ts b/test/automation/src/scm.ts index 7186fee17ca..9f950f2b16a 100644 --- a/test/automation/src/scm.ts +++ b/test/automation/src/scm.ts @@ -10,10 +10,10 @@ import { findElement, findElements, Code } from './code'; const VIEWLET = 'div[id="workbench.view.scm"]'; const SCM_INPUT = `${VIEWLET} .scm-editor textarea`; const SCM_RESOURCE = `${VIEWLET} .monaco-list-row .resource`; -const REFRESH_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[title="Refresh"]`; -const COMMIT_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[title="Commit"]`; -const SCM_RESOURCE_CLICK = (name: string) => `${SCM_RESOURCE} .monaco-icon-label[title*="${name}"] .label-name`; -const SCM_RESOURCE_ACTION_CLICK = (name: string, actionName: string) => `${SCM_RESOURCE} .monaco-icon-label[title*="${name}"] .actions .action-label[title="${actionName}"]`; +const REFRESH_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[aria-label="Refresh"]`; +const COMMIT_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[aria-label="Commit"]`; +const SCM_RESOURCE_CLICK = (name: string) => `${SCM_RESOURCE} .monaco-icon-label[aria-label*="${name}"] .label-name`; +const SCM_RESOURCE_ACTION_CLICK = (name: string, actionName: string) => `${SCM_RESOURCE} .monaco-icon-label[aria-label*="${name}"] .actions .action-label[aria-label="${actionName}"]`; interface Change { name: string; diff --git a/test/automation/src/search.ts b/test/automation/src/search.ts index 844003955e7..567ec20fa91 100644 --- a/test/automation/src/search.ts +++ b/test/automation/src/search.ts @@ -102,7 +102,7 @@ export class Search extends Viewlet { } async setReplaceText(text: string): Promise { - await this.code.waitForSetValue(`${VIEWLET} .search-widget .replace-container .monaco-inputbox textarea[title="Replace"]`, text); + await this.code.waitForSetValue(`${VIEWLET} .search-widget .replace-container .monaco-inputbox textarea[aria-label="Replace"]`, text); } async replaceFileMatch(filename: string, expectedText: string): Promise { diff --git a/test/automation/src/statusbar.ts b/test/automation/src/statusbar.ts index 86254146975..423a7585c04 100644 --- a/test/automation/src/statusbar.ts +++ b/test/automation/src/statusbar.ts @@ -35,7 +35,7 @@ export class StatusBar { } async waitForStatusbarText(title: string, text: string): Promise { - await this.code.waitForTextContent(`${this.mainSelector} .statusbar-item[title="${title}"]`, text); + await this.code.waitForTextContent(`${this.mainSelector} .statusbar-item[aria-label="${title}"]`, text); } private getSelector(element: StatusBarElement): string { diff --git a/test/smoke/src/areas/extensions/extensions.test.ts b/test/smoke/src/areas/extensions/extensions.test.ts index bc65a8631c4..c78cbe87089 100644 --- a/test/smoke/src/areas/extensions/extensions.test.ts +++ b/test/smoke/src/areas/extensions/extensions.test.ts @@ -7,7 +7,7 @@ import { Application, Logger } from '../../../../automation'; import { installAllHandlers } from '../../utils'; export function setup(logger: Logger) { - describe.skip('Extensions', () => { + describe('Extensions', () => { // Shared before/after handling installAllHandlers(logger); From cdfa3491ce6c6cdd581b376df9bb08f66f74bc62 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Sat, 17 Feb 2024 21:47:34 +0100 Subject: [PATCH 1401/1897] rename suggestions: don't focus unless there're already elements --- .../contrib/rename/browser/renameInputField.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 742c5a72cba..5db0fd648bf 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -254,6 +254,9 @@ export class RenameInputField implements IContentWidget { } } + /** + * @returns a `boolean` standing for `shouldFocusEditor`, if user didn't pick a new name, or a {@link RenameInputFieldResult} + */ getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, candidates: Promise, token: CancellationToken): Promise { this._domNode!.classList.toggle('preview', supportPreview); @@ -463,7 +466,10 @@ export class CandidatesView { this._listWidget.rerender(); } - public focusNext() { + public focusNext(): void { + if (this._listWidget.length === 0) { + return; + } if (this._listWidget.isDOMFocused()) { this._listWidget.focusNext(); } else { @@ -476,7 +482,10 @@ export class CandidatesView { /** * @returns true if focus is moved to previous element */ - public focusPrevious() { + public focusPrevious(): boolean { + if (this._listWidget.length === 0) { + return false; + } this._listWidget.domFocus(); const focusedIx = this._listWidget.getFocus()[0]; if (focusedIx !== 0) { From 89d33f932bd862024b7d168870f33b5bf3804583 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Sat, 17 Feb 2024 22:21:57 +0100 Subject: [PATCH 1402/1897] rename suggestions: use `Promise.allSettled` so that if one provider throws, we still get names from other providers --- .../editor/contrib/rename/browser/rename.ts | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index 6b9c5285a27..8d8035b5cf7 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { alert } from 'vs/base/browser/ui/aria/aria'; +import * as arrays from 'vs/base/common/arrays'; import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { assertType } from 'vs/base/common/types'; +import { assertType, isDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorCommand, EditorContributionInstantiation, ServicesAccessor, registerEditorAction, registerEditorCommand, registerEditorContribution, registerModelAndPositionCommand } from 'vs/editor/browser/editorExtensions'; @@ -210,11 +211,20 @@ class RenameController implements IEditorContribution { const cts2 = new EditorStateCancellationTokenSource(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value, loc.range, this._cts.token); const model = this.editor.getModel(); // @ulugbekna: assumes editor still has a model, otherwise, cts1 should've been cancelled - const newNameCandidates = Promise.all( - this._languageFeaturesService.newSymbolNamesProvider - .all(model) - .map(provider => provider.provideNewSymbolNames(model, loc.range, cts2.token)) // TODO@ulugbekna: make sure this works regardless if the result is then-able - ).then((candidates) => candidates.filter((c): c is NewSymbolName[] => !!c).flat()); + const newSymbolNamesProviders = this._languageFeaturesService.newSymbolNamesProvider.all(model); + const newSymbolNames = newSymbolNamesProviders.map(p => p.provideNewSymbolNames(model, loc.range, cts2.token)); + const newNameCandidates = Promise.allSettled(newSymbolNames).then(namesListResults => { + if (cts2.token.isCancellationRequested) { + return []; + } + const newNames: NewSymbolName[] = []; + for (const namesListResult of namesListResults) { + if (namesListResult.status === 'fulfilled' && isDefined(namesListResult.value)) { + arrays.pushMany(newNames, namesListResult.value); + } + } + return newNames; + }); const selection = this.editor.getSelection(); let selectionStart = 0; From 90e788d1fd9e5b627dbe8959444da08b5b064458 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Sat, 17 Feb 2024 22:54:42 +0100 Subject: [PATCH 1403/1897] rename suggestions: fix old candidates being shown when re-triggering/re-opening rename widget in the same document position quickly --- .../editor/contrib/rename/browser/rename.ts | 24 ++++-------- .../rename/browser/renameInputField.ts | 39 ++++++++++++------- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index 8d8035b5cf7..ae1f9b06e11 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -4,13 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { alert } from 'vs/base/browser/ui/aria/aria'; -import * as arrays from 'vs/base/common/arrays'; import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { assertType, isDefined } from 'vs/base/common/types'; +import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorCommand, EditorContributionInstantiation, ServicesAccessor, registerEditorAction, registerEditorCommand, registerEditorContribution, registerModelAndPositionCommand } from 'vs/editor/browser/editorExtensions'; @@ -21,7 +20,7 @@ import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; -import { NewSymbolName, Rejection, RenameLocation, RenameProvider, WorkspaceEdit } from 'vs/editor/common/languages'; +import { Rejection, RenameLocation, RenameProvider, WorkspaceEdit } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; @@ -211,20 +210,11 @@ class RenameController implements IEditorContribution { const cts2 = new EditorStateCancellationTokenSource(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value, loc.range, this._cts.token); const model = this.editor.getModel(); // @ulugbekna: assumes editor still has a model, otherwise, cts1 should've been cancelled + + const renameCandidatesCts = new CancellationTokenSource(cts2.token); const newSymbolNamesProviders = this._languageFeaturesService.newSymbolNamesProvider.all(model); - const newSymbolNames = newSymbolNamesProviders.map(p => p.provideNewSymbolNames(model, loc.range, cts2.token)); - const newNameCandidates = Promise.allSettled(newSymbolNames).then(namesListResults => { - if (cts2.token.isCancellationRequested) { - return []; - } - const newNames: NewSymbolName[] = []; - for (const namesListResult of namesListResults) { - if (namesListResult.status === 'fulfilled' && isDefined(namesListResult.value)) { - arrays.pushMany(newNames, namesListResult.value); - } - } - return newNames; - }); + // TODO@ulugbekna: providers should get timeout token (use createTimeoutCancellation(x)) + const newSymbolNameProvidersResults = newSymbolNamesProviders.map(p => p.provideNewSymbolNames(model, loc.range, renameCandidatesCts.token)); const selection = this.editor.getSelection(); let selectionStart = 0; @@ -236,7 +226,7 @@ class RenameController implements IEditorContribution { } const supportPreview = this._bulkEditService.hasPreviewHandler() && this._configService.getValue(this.editor.getModel().uri, 'editor.rename.enablePreview'); - const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, newNameCandidates, cts2.token); + const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, newSymbolNameProvidersResults, renameCandidatesCts); // no result, only hint to focus the editor or not if (typeof inputFieldResult === 'boolean') { diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 5db0fd648bf..038fb1fd1df 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -8,10 +8,11 @@ import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import * as arrays from 'vs/base/common/arrays'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { raceCancellation } from 'vs/base/common/async'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { assertType } from 'vs/base/common/types'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { assertType, isDefined } from 'vs/base/common/types'; import 'vs/css!./renameInputField'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -20,7 +21,7 @@ import { IDimension } from 'vs/editor/common/core/dimension'; import { Position } from 'vs/editor/common/core/position'; import { IRange } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { NewSymbolName, NewSymbolNameTag } from 'vs/editor/common/languages'; +import { NewSymbolName, NewSymbolNameTag, ProviderResult } from 'vs/editor/common/languages'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -257,7 +258,7 @@ export class RenameInputField implements IContentWidget { /** * @returns a `boolean` standing for `shouldFocusEditor`, if user didn't pick a new name, or a {@link RenameInputFieldResult} */ - getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, candidates: Promise, token: CancellationToken): Promise { + getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, candidates: ProviderResult[], cts: CancellationTokenSource): Promise { this._domNode!.classList.toggle('preview', supportPreview); @@ -269,7 +270,9 @@ export class RenameInputField implements IContentWidget { const disposeOnDone = new DisposableStore(); - candidates.then(candidates => this._showRenameCandidates(candidates, value, token)); + disposeOnDone.add(toDisposable(() => cts.dispose(true))); // @ulugbekna: this may result in `this.cancelInput` being called twice, but it should be safe since we set it to undefined after 1st call + + this._updateRenameCandidates(candidates, value, cts.token); return new Promise(resolve => { @@ -301,7 +304,7 @@ export class RenameInputField implements IContentWidget { }); }; - disposeOnDone.add(token.onCancellationRequested(() => this.cancelInput(true))); + disposeOnDone.add(cts.token.onCancellationRequested(() => this.cancelInput(true))); if (!_sticky) { disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(!this._domNode?.ownerDocument.hasFocus()))); } @@ -328,21 +331,29 @@ export class RenameInputField implements IContentWidget { }, 100); } - private _showRenameCandidates(candidates: NewSymbolName[], currentName: string, token: CancellationToken): void { - if (token.isCancellationRequested) { + private async _updateRenameCandidates(candidates: ProviderResult[], currentName: string, token: CancellationToken) { + const namesListResults = await raceCancellation(Promise.allSettled(candidates), token); + + if (namesListResults === undefined) { return; } - // deduplicate and filter out the current value - candidates = arrays.distinct(candidates, candidate => candidate.newSymbolName); - candidates = candidates.filter(({ newSymbolName }) => newSymbolName.trim().length > 0 && newSymbolName !== this._input?.value && newSymbolName !== currentName); + const newNames = namesListResults.flatMap(namesListResult => + namesListResult.status === 'fulfilled' && isDefined(namesListResult.value) + ? namesListResult.value + : [] + ); - if (candidates.length < 1) { + // deduplicate and filter out the current value + const distinctNames = arrays.distinct(newNames, v => v.newSymbolName); + const validDistinctNames = distinctNames.filter(({ newSymbolName }) => newSymbolName.trim().length > 0 && newSymbolName !== this._input?.value && newSymbolName !== currentName); + + if (validDistinctNames.length < 1) { return; } // show the candidates - this._candidatesView!.setCandidates(candidates); + this._candidatesView!.setCandidates(validDistinctNames); // ask editor to re-layout given that the widget is now of a different size after rendering rename candidates this._editor.layoutContentWidget(this); From 6e539e3b7bed24d888ee56bb56c23cafb35b855a Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Sun, 18 Feb 2024 15:11:07 +0100 Subject: [PATCH 1404/1897] rename suggestions: fix: allow clicking on a rename candidate with a mouse fixes https://github.com/microsoft/vscode/issues/205376 --- .../rename/browser/renameInputField.ts | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 038fb1fd1df..106d866178a 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -110,7 +110,10 @@ export class RenameInputField implements IContentWidget { this._disposables.add(addDisposableListener(this._input, 'blur', () => { this._focusedContextKey.reset(); })); this._domNode.appendChild(this._input); - this._candidatesView = new CandidatesView(this._domNode, { fontInfo: this._editor.getOption(EditorOption.fontInfo) }); + this._candidatesView = new CandidatesView(this._domNode, { + fontInfo: this._editor.getOption(EditorOption.fontInfo), + onSelectionChange: () => this.acceptInput(false) // we don't allow preview with mouse click for now + }); this._label = document.createElement('div'); this._label.className = 'rename-label'; @@ -374,7 +377,7 @@ export class CandidatesView { private _lineHeight: number; private _availableHeight: number; - constructor(parent: HTMLElement, opts: { fontInfo: FontInfo }) { + constructor(parent: HTMLElement, opts: { fontInfo: FontInfo; onSelectionChange: () => void }) { this._availableHeight = 0; @@ -426,6 +429,12 @@ export class CandidatesView { } ); + this._listWidget.onDidChangeSelection(e => { + if (e.elements.length > 0) { + opts.onSelectionChange(); + } + }); + this._listWidget.style(defaultListStyles); } @@ -464,7 +473,18 @@ export class CandidatesView { } public get focusedCandidate(): string | undefined { - return this._listWidget.isDOMFocused() ? this._listWidget.getFocusedElements()[0].newSymbolName : undefined; + if (this._listWidget.length === 0) { + return; + } + const selectedElement = this._listWidget.getSelectedElements()[0]; + if (selectedElement !== undefined) { + return selectedElement.newSymbolName; + } + const focusedElement = this._listWidget.getFocusedElements()[0]; + if (focusedElement !== undefined) { + return focusedElement.newSymbolName; + } + return; } public updateFont(fontInfo: FontInfo): void { From b48ad70a420c8a49f64c605c44eb74bf60eaf676 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 18 Feb 2024 17:41:26 +0000 Subject: [PATCH 1405/1897] Allow https URIs as chat references (#205482) Fix #203822 --- .../contrib/chat/browser/chatListRenderer.ts | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 8ab85eb1297..609da660c84 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -24,7 +24,7 @@ import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; import { marked } from 'vs/base/common/marked/marked'; -import { FileAccess, Schemas } from 'vs/base/common/network'; +import { FileAccess, Schemas, matchesSomeScheme } from 'vs/base/common/network'; import { clamp } from 'vs/base/common/numbers'; import { basename } from 'vs/base/common/path'; import { equalsIgnoreCase } from 'vs/base/common/strings'; @@ -68,7 +68,6 @@ import { IChatProgressMessageRenderData, IChatRenderData, IChatResponseMarkdownR import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView'; import { IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; const $ = dom.$; @@ -143,7 +142,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { if (e.element) { - this.editorService.openEditor({ - resource: 'uri' in e.element.reference ? e.element.reference.uri : e.element.reference, - options: { - ...e.editorOptions, - ...{ - selection: 'range' in e.element.reference ? e.element.reference.range : undefined + this.openerService.open( + 'uri' in e.element.reference ? e.element.reference.uri : e.element.reference, + { + fromUserGesture: true, + editorOptions: { + ...e.editorOptions, + ...{ + selection: 'range' in e.element.reference ? e.element.reference.range : undefined + } } - } - }); + }); } })); listDisposables.add(list.onContextMenu((e) => { @@ -1204,7 +1204,7 @@ class ContentReferencesListPool extends Disposable { 'ChatListRenderer', container, new ContentReferencesListDelegate(), - [new ContentReferencesListRenderer(resourceLabels)], + [this.instantiationService.createInstance(ContentReferencesListRenderer, resourceLabels)], { alwaysConsumeMouseWheel: false, accessibilityProvider: { @@ -1256,7 +1256,9 @@ class ContentReferencesListRenderer implements IListRenderer Date: Sun, 18 Feb 2024 21:40:06 +0100 Subject: [PATCH 1406/1897] rename controller & action: add tracing --- .../editor/contrib/rename/browser/rename.ts | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index ae1f9b06e11..cdcadcf58f7 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -160,12 +160,15 @@ class RenameController implements IEditorContribution { async run(): Promise { + const trace = this._logService.trace.bind(this._logService, '[rename]'); + // set up cancellation token to prevent reentrant rename, this // is the parent to the resolve- and rename-tokens this._cts.dispose(true); this._cts = new CancellationTokenSource(); if (!this.editor.hasModel()) { + trace('editor has no model'); return undefined; } @@ -173,6 +176,7 @@ class RenameController implements IEditorContribution { const skeleton = new RenameSkeleton(this.editor.getModel(), position, this._languageFeaturesService.renameProvider); if (!skeleton.hasProvider()) { + trace('skeleton has no provider'); return undefined; } @@ -181,11 +185,13 @@ class RenameController implements IEditorContribution { let loc: RenameLocation & Rejection | undefined; try { + trace('resolving rename location'); const resolveLocationOperation = skeleton.resolveRenameLocation(cts1.token); this._progressService.showWhile(resolveLocationOperation, 250); loc = await resolveLocationOperation; - + trace('resolved rename location'); } catch (e) { + trace('resolve rename location failed', JSON.stringify(e, null, '\t')); MessageController.get(this.editor)?.showMessage(e || nls.localize('resolveRenameLocationFailed', "An unknown error occurred while resolving rename location"), position); return undefined; @@ -194,15 +200,18 @@ class RenameController implements IEditorContribution { } if (!loc) { + trace('returning early - no loc'); return undefined; } if (loc.rejectReason) { + trace(`returning early - rejected with reason: ${loc.rejectReason}`, loc.rejectReason); MessageController.get(this.editor)?.showMessage(loc.rejectReason, position); return undefined; } if (cts1.token.isCancellationRequested) { + trace('returning early - cts1 cancelled'); return undefined; } @@ -215,6 +224,7 @@ class RenameController implements IEditorContribution { const newSymbolNamesProviders = this._languageFeaturesService.newSymbolNamesProvider.all(model); // TODO@ulugbekna: providers should get timeout token (use createTimeoutCancellation(x)) const newSymbolNameProvidersResults = newSymbolNamesProviders.map(p => p.provideNewSymbolNames(model, loc.range, renameCandidatesCts.token)); + trace(`requested new symbol names from ${newSymbolNamesProviders.length} providers`); const selection = this.editor.getSelection(); let selectionStart = 0; @@ -225,11 +235,14 @@ class RenameController implements IEditorContribution { selectionEnd = Math.min(loc.range.endColumn, selection.endColumn) - loc.range.startColumn; } + trace('creating rename input field and awaiting its result'); const supportPreview = this._bulkEditService.hasPreviewHandler() && this._configService.getValue(this.editor.getModel().uri, 'editor.rename.enablePreview'); const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, newSymbolNameProvidersResults, renameCandidatesCts); + trace('received response from rename input field'); // no result, only hint to focus the editor or not if (typeof inputFieldResult === 'boolean') { + trace(`returning early - rename input field response - ${inputFieldResult}`); if (inputFieldResult) { this.editor.focus(); } @@ -239,13 +252,20 @@ class RenameController implements IEditorContribution { this.editor.focus(); + trace('requesting rename edits'); const renameOperation = raceCancellation(skeleton.provideRenameEdits(inputFieldResult.newName, cts2.token), cts2.token).then(async renameResult => { - if (!renameResult || !this.editor.hasModel()) { + if (!renameResult) { + trace('returning early - no rename edits result'); + return; + } + if (!this.editor.hasModel()) { + trace('returning early - no model after rename edits are provided'); return; } if (renameResult.rejectReason) { + trace(`returning early - rejected with reason: ${renameResult.rejectReason}`); this._notificationService.info(renameResult.rejectReason); return; } @@ -253,6 +273,8 @@ class RenameController implements IEditorContribution { // collapse selection to active end this.editor.setSelection(Range.fromPositions(this.editor.getSelection().getPosition())); + trace('applying edits'); + this._bulkEditService.apply(renameResult, { editor: this.editor, showPreview: inputFieldResult.wantsPreview, @@ -261,15 +283,19 @@ class RenameController implements IEditorContribution { quotableLabel: nls.localize('quotableLabel', "Renaming {0} to {1}", loc?.text, inputFieldResult.newName), respectAutoSaveConfig: true }).then(result => { + trace('edits applied'); if (result.ariaSummary) { alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", loc.text, inputFieldResult.newName, result.ariaSummary)); } }).catch(err => { + trace(`error when applying edits ${JSON.stringify(err, null, '\t')}`); this._notificationService.error(nls.localize('rename.failedApply', "Rename failed to apply edits")); this._logService.error(err); }); }, err => { + trace('error when providing rename edits', JSON.stringify(err, null, '\t')); + this._notificationService.error(nls.localize('rename.failed', "Rename failed to compute edits")); this._logService.error(err); @@ -277,6 +303,8 @@ class RenameController implements IEditorContribution { cts2.dispose(); }); + trace('returning rename operation'); + this._progressService.showWhile(renameOperation, 250); return renameOperation; @@ -342,10 +370,15 @@ export class RenameAction extends EditorAction { } run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + const logService = accessor.get(ILogService); + const controller = RenameController.get(editor); + if (controller) { + logService.trace('[RenameAction] got controller, running...'); return controller.run(); } + logService.trace('[RenameAction] returning early - controller missing'); return Promise.resolve(); } } From 227096140942ce2b08a2358d55b6f28fa1dce573 Mon Sep 17 00:00:00 2001 From: NriotHrreion Date: Mon, 19 Feb 2024 09:27:00 +0800 Subject: [PATCH 1407/1897] fix #205353 --- src/vs/editor/browser/widget/hoverWidget/hoverWidget.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/editor/browser/widget/hoverWidget/hoverWidget.ts b/src/vs/editor/browser/widget/hoverWidget/hoverWidget.ts index d6f6249675c..8b4814a92c9 100644 --- a/src/vs/editor/browser/widget/hoverWidget/hoverWidget.ts +++ b/src/vs/editor/browser/widget/hoverWidget/hoverWidget.ts @@ -331,10 +331,7 @@ export class HoverWidget extends Widget implements IHoverWidget { }; const targetBounds = this._target.targetElements.map(e => getZoomAccountedBoundingClientRect(e)); - const top = Math.min(...targetBounds.map(e => e.top)); - const right = Math.max(...targetBounds.map(e => e.right)); - const bottom = Math.max(...targetBounds.map(e => e.bottom)); - const left = Math.min(...targetBounds.map(e => e.left)); + const { top, right, bottom, left } = targetBounds[0]; const width = right - left; const height = bottom - top; From 3c1ceb6a1f464f2d2299683e88510346ad33a2b6 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Sun, 18 Feb 2024 23:50:01 -0800 Subject: [PATCH 1408/1897] issue reporter selection handling, adds `extensionId` to ipc (#205507) * fixed IPC handling * IT ALL WORKS NOW * fix quotes --- .../issue/issueReporterService.ts | 55 +++++++++++-------- .../issue/electron-main/issueMainService.ts | 4 +- .../issue/electron-sandbox/issueService.ts | 7 +-- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index c8f4276e7f7..6bfb60db1f7 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -51,7 +51,7 @@ export class IssueReporter extends Disposable { private hasBeenSubmitted = false; private openReporter = false; private loadingExtensionData = false; - private changeExtension: string = ''; + private selectedExtension = ''; private delayedSubmit = new Delayer(300); private readonly previewButton!: Button; @@ -1196,45 +1196,48 @@ export class IssueReporter extends Disposable { this.addEventListener('extension-selector', 'change', async (e: Event) => { this.clearExtensionData(); const selectedExtensionId = (e.target).value; - this.changeExtension = selectedExtensionId; + this.selectedExtension = selectedExtensionId; const extensions = this.issueReporterModel.getData().allExtensions; const matches = extensions.filter(extension => extension.id === selectedExtensionId); if (matches.length) { this.issueReporterModel.update({ selectedExtension: matches[0] }); const selectedExtension = this.issueReporterModel.getData().selectedExtension; - if (selectedExtension && !this.loadingExtensionData) { + if (selectedExtension) { const iconElement = document.createElement('span'); iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin'); this.setLoading(iconElement); const openReporterData = await this.sendReporterMenu(selectedExtension); - if (openReporterData && (this.changeExtension === selectedExtensionId)) { - this.removeLoading(iconElement, true); - this.configuration.data = openReporterData; - } else if (openReporterData && this.changeExtension !== selectedExtensionId) { - this.removeLoading(iconElement, this.openReporter); - } else { + if (openReporterData) { + if (this.selectedExtension === selectedExtensionId) { + this.removeLoading(iconElement, true); + this.configuration.data = openReporterData; + } else if (this.selectedExtension !== selectedExtensionId) { + } + } + else { + if (!this.loadingExtensionData) { + iconElement.classList.remove(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin'); + } this.removeLoading(iconElement); // if not using command, should have no configuration data in fields we care about and check later. - this.configuration.data.issueBody = undefined; - this.configuration.data.data = undefined; - this.configuration.data.uri = undefined; + this.clearExtensionData(); // case when previous extension was opened from normal openIssueReporter command selectedExtension.data = undefined; selectedExtension.uri = undefined; } - } - if (this.changeExtension === selectedExtensionId) { - // repopulates the fields with the new data given the selected extension. + if (this.selectedExtension === selectedExtensionId) { + // repopulates the fields with the new data given the selected extension. + this.updateExtensionStatus(matches[0]); + this.openReporter = false; + } + } else { + this.issueReporterModel.update({ selectedExtension: undefined }); + this.clearSearchResults(); + this.clearExtensionData(); + this.validateSelectedExtension(); this.updateExtensionStatus(matches[0]); - this.openReporter = false; } - } else { - this.issueReporterModel.update({ selectedExtension: undefined }); - this.clearSearchResults(); - this.clearExtensionData(); - this.validateSelectedExtension(); - this.updateExtensionStatus(matches[0]); } }); } @@ -1381,6 +1384,9 @@ export class IssueReporter extends Disposable { const showLoading = this.getElementById('ext-loading')!; show(showLoading); + while (showLoading.firstChild) { + showLoading.removeChild(showLoading.firstChild); + } showLoading.append(element); this.renderBlocks(); @@ -1399,8 +1405,9 @@ export class IssueReporter extends Disposable { const hideLoading = this.getElementById('ext-loading')!; hide(hideLoading); - hideLoading.removeChild(element); - + if (hideLoading.firstChild) { + hideLoading.removeChild(element); + } this.renderBlocks(); } diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 8e00947b2ea..32f2be6af45 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -459,8 +459,8 @@ export class IssueMainService implements IIssueMainService { const replyChannel = `vscode:triggerReporterMenu`; const cts = new CancellationTokenSource(); window.sendWhenReady(replyChannel, cts.token, { replyChannel, extensionId, extensionName }); - const result = await raceTimeout(new Promise(resolve => validatedIpcMain.once('vscode:triggerReporterMenuResponse', (_: unknown, data: IssueReporterData | undefined) => resolve(data))), 5000, () => { - this.logService.error('Error: Extension timed out waiting for menu response'); + const result = await raceTimeout(new Promise(resolve => validatedIpcMain.once('vscode:triggerReporterMenuResponse:${extensionId}', (_: unknown, data: IssueReporterData | undefined) => resolve(data))), 5000, () => { + this.logService.error('Error: Extension ${extensionId} timed out waiting for menu response'); cts.cancel(); }); return result as IssueReporterData | undefined; diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts index f91f39db09e..039ce81f5a9 100644 --- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts @@ -106,11 +106,10 @@ export class NativeIssueService implements IWorkbenchIssueService { } }); - if (this.extensionIdentifierSet.size === 0) { + if (!this.extensionIdentifierSet.has(extensionId)) { // send undefined to indicate no action was taken - ipcRenderer.send('vscode:triggerReporterMenuResponse', undefined); + ipcRenderer.send('vscode:triggerReporterMenuResponse:${extensionId}', undefined); } - menu.dispose(); }); } @@ -187,7 +186,7 @@ export class NativeIssueService implements IWorkbenchIssueService { }, dataOverrides); if (issueReporterData.extensionId && this.extensionIdentifierSet.has(issueReporterData.extensionId)) { - ipcRenderer.send('vscode:triggerReporterMenuResponse', issueReporterData); + ipcRenderer.send('vscode:triggerReporterMenuResponse:${issueReporterData.extensionId}', issueReporterData); this.extensionIdentifierSet.delete(new ExtensionIdentifier(issueReporterData.extensionId)); } return this.issueMainService.openReporter(issueReporterData); From 378b4c969b89d3ee42ca0cc07eb91d2cd1bfbe7c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 19 Feb 2024 00:17:54 -0800 Subject: [PATCH 1409/1897] use vsce-sign module from npm (#205511) * update distro * use vsce-sign module * update distro * update distro --- build/.moduleignore | 9 +++++---- build/gulpfile.vscode.js | 2 +- package.json | 2 +- .../node/extensionSignatureVerificationService.ts | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/build/.moduleignore b/build/.moduleignore index 87dafa92b0f..e40224556c6 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -63,10 +63,11 @@ native-watchdog/build/** native-watchdog/src/** !native-watchdog/build/Release/*.node -node-vsce-sign/** -!node-vsce-sign/src/main.js -!node-vsce-sign/package.json -!node-vsce-sign/bin/** +@vscode/vsce-sign/** +!@vscode/vsce-sign/src/main.d.ts +!@vscode/vsce-sign/src/main.js +!@vscode/vsce-sign/package.json +!@vscode/vsce-sign/bin/** windows-foreground-love/binding.gyp windows-foreground-love/build/** diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index bfd5c896e2f..e1507e0424f 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -281,7 +281,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op '**/node-pty/lib/worker/conoutSocketWorker.js', '**/node-pty/lib/shared/conout.js', '**/*.wasm', - '**/node-vsce-sign/bin/*', + '**/@vscode/vsce-sign/bin/*', ], 'node_modules.asar')); let all = es.merge( diff --git a/package.json b/package.json index dcd8d8916b3..78f245d387a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "660e1818ca4ddccb41c09b6a44360c278ae1610e", + "distro": "ca316c089fc5ea2836f210e13cf89d4bd52a3309", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts b/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts index 4f33aa1f48e..66d1ffc28e2 100644 --- a/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts +++ b/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts @@ -54,7 +54,7 @@ export class ExtensionSignatureVerificationService implements IExtensionSignatur if (!this.moduleLoadingPromise) { this.moduleLoadingPromise = new Promise( (resolve, reject) => require( - ['node-vsce-sign'], + ['@vscode/vsce-sign'], async (obj) => { const instance = obj; From a7844a63496b68384b686d733703d33a601442bf Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 19 Feb 2024 11:24:18 +0100 Subject: [PATCH 1410/1897] Other files don't open when launched with an unsaved workspace (fix #205453) (#205517) * Other files don't open when launched with an unsaved workspace (fix #205453) * polish --- .../contrib/workspace/browser/workspace.contribution.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index 9bad5ae61a3..37fb5d347dd 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -103,11 +103,12 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben return !isSingleFolderWorkspaceIdentifier(toWorkspaceIdentifier(this.workspaceContextService.getWorkspace())); } - private async registerListeners(): Promise { - await this.workspaceTrustManagementService.workspaceResolved; + private registerListeners(): void { // Open files trust request this._register(this.workspaceTrustRequestService.onDidInitiateOpenFilesTrustRequest(async () => { + await this.workspaceTrustManagementService.workspaceResolved; + // Details const markdownDetails = [ this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY ? @@ -148,6 +149,8 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben // Workspace trust request this._register(this.workspaceTrustRequestService.onDidInitiateWorkspaceTrustRequest(async requestOptions => { + await this.workspaceTrustManagementService.workspaceResolved; + // Title const message = this.useWorkspaceLanguage ? localize('workspaceTrust', "Do you trust the authors of the files in this workspace?") : From 2819e44d2cd729a447d6b5b8b7f002e2228e216c Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 19 Feb 2024 11:40:19 +0100 Subject: [PATCH 1411/1897] update scrollbars when dimensions change --- .../components/accessibleDiffViewer.css | 40 ++++++++++--------- .../components/accessibleDiffViewer.ts | 8 +++- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.css b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.css index cd20cfb12f3..640909467f4 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.css +++ b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.css @@ -3,53 +3,57 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-diff-editor .diff-review-line-number { - text-align: right; - display: inline-block; - color: var(--vscode-editorLineNumber-foreground); -} - -.monaco-diff-editor .diff-review { - position: absolute; +.monaco-component.diff-review { user-select: none; -webkit-user-select: none; z-index: 99; } -.monaco-diff-editor .diff-review-summary { +.monaco-diff-editor .diff-review { + position: absolute; + +} + +.monaco-component.diff-review .diff-review-line-number { + text-align: right; + display: inline-block; + color: var(--vscode-editorLineNumber-foreground); +} + +.monaco-component.diff-review .diff-review-summary { padding-left: 10px; } -.monaco-diff-editor .diff-review-shadow { +.monaco-component.diff-review .diff-review-shadow { position: absolute; box-shadow: var(--vscode-scrollbar-shadow) 0 -6px 6px -6px inset; } -.monaco-diff-editor .diff-review-row { +.monaco-component.diff-review .diff-review-row { white-space: pre; } -.monaco-diff-editor .diff-review-table { +.monaco-component.diff-review .diff-review-table { display: table; min-width: 100%; } -.monaco-diff-editor .diff-review-row { +.monaco-component.diff-review .diff-review-row { display: table-row; width: 100%; } -.monaco-diff-editor .diff-review-spacer { +.monaco-component.diff-review .diff-review-spacer { display: inline-block; width: 10px; vertical-align: middle; } -.monaco-diff-editor .diff-review-spacer > .codicon { +.monaco-component.diff-review .diff-review-spacer > .codicon { font-size: 9px !important; } -.monaco-diff-editor .diff-review-actions { +.monaco-component.diff-review .diff-review-actions { display: inline-block; position: absolute; right: 10px; @@ -57,12 +61,12 @@ z-index: 100; } -.monaco-diff-editor .diff-review-actions .action-label { +.monaco-component.diff-review .diff-review-actions .action-label { width: 16px; height: 16px; margin: 2px 0; } -.monaco-diff-editor .revertButton { +.monaco-component.diff-review .revertButton { cursor: pointer; } diff --git a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts index 3d4e7ef3067..3c27606e5d6 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts @@ -354,7 +354,7 @@ class View extends Disposable { super(); this.domNode = this._element; - this.domNode.className = 'diff-review monaco-editor-background'; + this.domNode.className = 'monaco-component diff-review monaco-editor-background'; const actionBarContainer = document.createElement('div'); actionBarContainer.className = 'diff-review-actions'; @@ -381,6 +381,12 @@ class View extends Disposable { this._scrollbar = this._register(new DomScrollableElement(this._content, {})); reset(this.domNode, this._scrollbar.getDomNode(), actionBarContainer); + this._register(autorun(r => { + this._height.read(r); + this._width.read(r); + this._scrollbar.scanDomNode(); + })); + this._register(toDisposable(() => { reset(this.domNode); })); this._register(applyStyle(this.domNode, { width: this._width, height: this._height })); From bc525c0f3386f0d3e09fbad83506d841a282d442 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 19 Feb 2024 11:53:12 +0100 Subject: [PATCH 1412/1897] Update distro hash in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 78f245d387a..e413eef05b2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "ca316c089fc5ea2836f210e13cf89d4bd52a3309", + "distro": "664b4b796ea2343e71889a507e125feb14390bdf", "author": { "name": "Microsoft Corporation" }, From 3ee2bef56a5fd06b6c4858253493781f50d5797a Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 19 Feb 2024 11:54:09 +0100 Subject: [PATCH 1413/1897] Fix custom hover closing context view for breadcrumbs (#205518) Fix custom hover closes context view for breadcrumbs --- .../base/browser/ui/iconLabel/iconHoverDelegate.ts | 1 + src/vs/base/browser/ui/iconLabel/iconLabel.ts | 12 ++++++++---- src/vs/platform/hover/browser/hover.ts | 9 +++++++++ .../browser/parts/editor/breadcrumbsControl.ts | 4 ++-- .../browser/parts/editor/breadcrumbsPicker.ts | 7 +++++-- .../browser/outline/documentSymbolsTree.ts | 3 ++- 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts index d6d9096b5f1..f0209856dc3 100644 --- a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts +++ b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts @@ -62,6 +62,7 @@ export interface IHoverDelegate { onDidHideHover?: () => void; delay: number; placement?: 'mouse' | 'element'; + showNativeHover?: boolean; // TODO@benibenj remove this, only temp fix for contextviews } export interface IScopedHoverDelegate extends IHoverDelegate, IDisposable { } diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 6a7edc12dcf..c9d62a9bc4e 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -7,7 +7,7 @@ import 'vs/css!./iconlabel'; import * as dom from 'vs/base/browser/dom'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { ITooltipMarkdownString, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { ITooltipMarkdownString, setupCustomHover, setupNativeHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { IMatch } from 'vs/base/common/filters'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { equals } from 'vs/base/common/objects'; @@ -187,9 +187,13 @@ export class IconLabel extends Disposable { return; } - const hoverDisposable = setupCustomHover(this.hoverDelegate, htmlElement, tooltip); - if (hoverDisposable) { - this.customHovers.set(htmlElement, hoverDisposable); + if (this.hoverDelegate.showNativeHover) { + setupNativeHover(htmlElement, tooltip); + } else { + const hoverDisposable = setupCustomHover(this.hoverDelegate, htmlElement, tooltip); + if (hoverDisposable) { + this.customHovers.set(htmlElement, hoverDisposable); + } } } diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 5bbd3ff00b9..750adf61b46 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -283,3 +283,12 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate } } } + +// TODO@benibenj remove this, only temp fix for contextviews +export const nativeHoverDelegate: IHoverDelegate = { + showHover: function (): IHoverWidget | undefined { + throw new Error('Native hover function not implemented.'); + }, + delay: 0, + showNativeHover: true +}; diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index a3b752ff1f2..4d9e3906b4e 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -41,7 +41,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { defaultBreadcrumbsWidgetStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Emitter } from 'vs/base/common/event'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { nativeHoverDelegate } from 'vs/platform/hover/browser/hover'; class OutlineItem extends BreadcrumbsItem { @@ -230,7 +230,7 @@ export class BreadcrumbsControl { this._ckBreadcrumbsVisible = BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo(this._contextKeyService); this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService); - this._hoverDelegate = getDefaultHoverDelegate('mouse'); + this._hoverDelegate = nativeHoverDelegate; this._disposables.add(breadcrumbsService.register(this._editorGroup.id, this._widget)); this.hide(); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 9ca0b4310a5..26f77202d53 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -31,6 +31,8 @@ import { IOutline, IOutlineComparator } from 'vs/workbench/services/outline/brow import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { nativeHoverDelegate } from 'vs/platform/hover/browser/hover'; interface ILayoutInfo { maxHeight: number; @@ -213,12 +215,13 @@ class FileRenderer implements ITreeRenderer, index: number, templateData: IResourceLabel): void { @@ -374,7 +377,7 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { 'BreadcrumbsFilePicker', container, new FileVirtualDelegate(), - [this._instantiationService.createInstance(FileRenderer, labels)], + [this._instantiationService.createInstance(FileRenderer, labels, nativeHoverDelegate)], this._instantiationService.createInstance(FileDataSource), { multipleSelectionSupport: false, diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index 450c690d6a8..4e8761ffb1d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -26,6 +26,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { mainWindow } from 'vs/base/browser/window'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { nativeHoverDelegate } from 'vs/platform/hover/browser/hover'; export type DocumentSymbolItem = OutlineGroup | OutlineElement; @@ -125,7 +126,7 @@ export class DocumentSymbolRenderer implements ITreeRenderer Date: Mon, 19 Feb 2024 12:19:57 +0100 Subject: [PATCH 1414/1897] Close custom hover on escape (#205527) close custom hover on escape --- src/vs/platform/hover/browser/hover.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 750adf61b46..93974adc1a9 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -4,11 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { IHoverDelegate, IHoverDelegateOptions, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { addStandardDisposableListener } from 'vs/base/browser/dom'; +import { KeyCode } from 'vs/base/common/keyCodes'; export const IHoverService = createDecorator('hoverService'); @@ -245,6 +247,8 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate return this._delay; } + private hoverDisposables = this._register(new DisposableStore()); + constructor( public readonly placement: 'mouse' | 'element', private readonly instantHover: boolean, @@ -264,6 +268,18 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined { const overrideOptions = typeof this.overrideOptions === 'function' ? this.overrideOptions(options, focus) : this.overrideOptions; + + // close hover on escape + this.hoverDisposables.clear(); + const targets = options.target instanceof HTMLElement ? [options.target] : options.target.targetElements; + for (const target of targets) { + this.hoverDisposables.add(addStandardDisposableListener(target, 'keydown', (e) => { + if (e.equals(KeyCode.Escape)) { + this.hoverService.hideHover(); + } + })); + } + return this.hoverService.showHover({ ...options, persistence: { @@ -278,6 +294,7 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate } onDidHideHover(): void { + this.hoverDisposables.clear(); if (this.instantHover) { this.lastHoverHideTime = Date.now(); } From 735d80e0e243d0e8e4b5bb9b63bfb3f8673a9d74 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Mon, 19 Feb 2024 04:09:35 -0800 Subject: [PATCH 1415/1897] revert early disposal of code actions (#205508) revert early disposal of actinos --- .../codeAction/browser/codeActionController.ts | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index a29f4334e1f..5511dde958b 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -112,12 +112,7 @@ export class CodeActionController extends Disposable implements IEditorContribut command.arguments[0] = { ...command.arguments[0], autoSend: false }; } } - try { - this._lightBulbWidget.value?.hide(); - await this._applyCodeAction(actionItem, false, false, ApplyCodeActionReason.FromAILightbulb); - } finally { - actions.dispose(); - } + await this._applyCodeAction(actionItem, false, false, ApplyCodeActionReason.FromAILightbulb); return; } await this.showCodeActionList(actions, at, { includeDisabledActions: false, fromLightbulb: true }); @@ -284,11 +279,7 @@ export class CodeActionController extends Disposable implements IEditorContribut const delegate: IActionListDelegate = { onSelect: async (action: CodeActionItem, preview?: boolean) => { - try { - await this._applyCodeAction(action, /* retrigger */ true, !!preview, ApplyCodeActionReason.FromCodeActions); - } finally { - actions.dispose(); - } + this._applyCodeAction(action, /* retrigger */ true, !!preview, ApplyCodeActionReason.FromCodeActions); this._actionWidgetService.hide(); currentDecorations.clear(); }, From 8ec6cec39b6892254ae2cfd774fd7ed00a428ad2 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 19 Feb 2024 13:52:10 +0100 Subject: [PATCH 1416/1897] report client OS / DE --- src/vs/base/common/desktopEnvironmentInfo.ts | 101 ++++++++++++++++++ .../node/sharedProcess/sharedProcessMain.ts | 34 ++++++ 2 files changed, 135 insertions(+) create mode 100644 src/vs/base/common/desktopEnvironmentInfo.ts diff --git a/src/vs/base/common/desktopEnvironmentInfo.ts b/src/vs/base/common/desktopEnvironmentInfo.ts new file mode 100644 index 00000000000..b6e4c107db1 --- /dev/null +++ b/src/vs/base/common/desktopEnvironmentInfo.ts @@ -0,0 +1,101 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { env } from 'vs/base/common/process'; + +// Define the enumeration for Desktop Environments +enum DesktopEnvironment { + UNKNOWN = 'UNKNOWN', + CINNAMON = 'CINNAMON', + DEEPIN = 'DEEPIN', + GNOME = 'GNOME', + KDE3 = 'KDE3', + KDE4 = 'KDE4', + KDE5 = 'KDE5', + KDE6 = 'KDE6', + PANTHEON = 'PANTHEON', + UNITY = 'UNITY', + XFCE = 'XFCE', + UKUI = 'UKUI', + LXQT = 'LXQT', +} + +const kXdgCurrentDesktopEnvVar = 'XDG_CURRENT_DESKTOP'; +const kKDESessionEnvVar = 'KDE_SESSION_VERSION'; + +export function getDesktopEnvironment(): DesktopEnvironment { + const xdgCurrentDesktop = env[kXdgCurrentDesktopEnvVar]; + if (xdgCurrentDesktop) { + const values = xdgCurrentDesktop.split(':').map(value => value.trim()).filter(value => value.length > 0); + for (const value of values) { + switch (value) { + case 'Unity': { + const desktopSessionUnity = env['DESKTOP_SESSION']; + if (desktopSessionUnity && desktopSessionUnity.includes('gnome-fallback')) { + return DesktopEnvironment.GNOME; + } + + return DesktopEnvironment.UNITY; + } + case 'Deepin': + return DesktopEnvironment.DEEPIN; + case 'GNOME': + return DesktopEnvironment.GNOME; + case 'X-Cinnamon': + return DesktopEnvironment.CINNAMON; + case 'KDE': { + const kdeSession = env[kKDESessionEnvVar]; + if (kdeSession === '5') { return DesktopEnvironment.KDE5; } + if (kdeSession === '6') { return DesktopEnvironment.KDE6; } + return DesktopEnvironment.KDE4; + } + case 'Pantheon': + return DesktopEnvironment.PANTHEON; + case 'XFCE': + return DesktopEnvironment.XFCE; + case 'UKUI': + return DesktopEnvironment.UKUI; + case 'LXQt': + return DesktopEnvironment.LXQT; + } + } + } + + const desktopSession = env['DESKTOP_SESSION']; + if (desktopSession) { + switch (desktopSession) { + case 'deepin': + return DesktopEnvironment.DEEPIN; + case 'gnome': + case 'mate': + return DesktopEnvironment.GNOME; + case 'kde4': + case 'kde-plasma': + return DesktopEnvironment.KDE4; + case 'kde': + if (kKDESessionEnvVar in env) { + return DesktopEnvironment.KDE4; + } + return DesktopEnvironment.KDE3; + case 'xfce': + case 'xubuntu': + return DesktopEnvironment.XFCE; + case 'ukui': + return DesktopEnvironment.UKUI; + } + } + + if ('GNOME_DESKTOP_SESSION_ID' in env) { + return DesktopEnvironment.GNOME; + } + if ('KDE_FULL_SESSION' in env) { + if (kKDESessionEnvVar in env) { + return DesktopEnvironment.KDE4; + } + return DesktopEnvironment.KDE3; + } + + return DesktopEnvironment.UNKNOWN; +} diff --git a/src/vs/code/node/sharedProcess/sharedProcessMain.ts b/src/vs/code/node/sharedProcess/sharedProcessMain.ts index 82b79418af0..3c0de38db43 100644 --- a/src/vs/code/node/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/node/sharedProcess/sharedProcessMain.ts @@ -116,6 +116,8 @@ import { RemoteConnectionType } from 'vs/platform/remote/common/remoteAuthorityR import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory'; import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { SharedProcessRawConnection, SharedProcessLifecycle } from 'vs/platform/sharedProcess/common/sharedProcess'; +import { getOSReleaseInfo } from 'vs/base/node/osReleaseInfo'; +import { getDesktopEnvironment } from 'vs/base/common/desktopEnvironmentInfo'; class SharedProcessMain extends Disposable implements IClientConnectionFilter { @@ -172,6 +174,9 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { // Report Profiles Info this.reportProfilesInfo(telemetryService, userDataProfilesService); this._register(userDataProfilesService.onDidChangeProfiles(() => this.reportProfilesInfo(telemetryService, userDataProfilesService))); + + // Report Client OS/DE Info + this.reportClientOSInfo(telemetryService, logService); }); // Instantiate Contributions @@ -458,6 +463,35 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { }); } + private async reportClientOSInfo(telemetryService: ITelemetryService, logService: ILogService): Promise { + if (isLinux) { + const releaseInfo = await getOSReleaseInfo(logService.error.bind(logService)); + const desktopEnvironment = getDesktopEnvironment(); + if (releaseInfo) { + type ClientPlatformInfoClassification = { + platformId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'A string identifying the operating system without any version information.' }; + platformVersionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'A string identifying the operating system version excluding any name information or release code.' }; + platformIdLike: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'A string identifying the operating system the current OS derivate is closely related to.' }; + desktopEnvironment: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'A string identifying the desktop environment the user is using.' }; + owner: 'benibenj'; + comment: 'Provides insight into the distro and desktop environment information on Linux.'; + }; + type ClientPlatformInfoEvent = { + platformId: string; + platformVersionId: string | undefined; + platformIdLike: string | undefined; + desktopEnvironment: string | undefined; + }; + telemetryService.publicLog2('clientPlatformInfo', { + platformId: releaseInfo.id, + platformVersionId: releaseInfo.version_id, + platformIdLike: releaseInfo.id_like, + desktopEnvironment: desktopEnvironment + }); + } + } + } + handledClientConnection(e: MessageEvent): boolean { // This filter on message port messages will look for From 7329258fc98c1c4596efd08294cd582b17d68534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Cie=C5=9Blak?= Date: Mon, 19 Feb 2024 13:56:47 +0100 Subject: [PATCH 1417/1897] Don't run `onDidBlurEditorWidget` and `onDidFocusEditorText` if inline edit is disabled (#205378) --- .../inlineEdit/browser/inlineEditController.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts b/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts index 2b5db89b913..81d6fdb9d54 100644 --- a/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts +++ b/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts @@ -110,7 +110,13 @@ export class InlineEditController extends Disposable { })); //Clear suggestions on lost focus - this._register(editor.onDidBlurEditorWidget(() => { + const editorBlurSingal = observableSignalFromEvent('InlineEditController.editorBlurSignal', editor.onDidBlurEditorWidget); + this._register(autorun(reader => { + /** @description InlineEditController.editorBlur */ + if (!this._enabled.read(reader)) { + return; + } + editorBlurSingal.read(reader); // This is a hidden setting very useful for debugging if (this._configurationService.getValue('editor.experimentalInlineEdit.keepOnBlur') || editor.getOption(EditorOption.inlineEdit).keepOnBlur) { return; @@ -121,11 +127,14 @@ export class InlineEditController extends Disposable { })); //Invoke provider on focus - this._register(editor.onDidFocusEditorText(async () => { - if (!this._enabled.get()) { + const editorFocusSignal = observableSignalFromEvent('InlineEditController.editorFocusSignal', editor.onDidFocusEditorText); + this._register(autorun(reader => { + /** @description InlineEditController.editorFocus */ + if (!this._enabled.read(reader)) { return; } - await this.getInlineEdit(editor, true); + editorFocusSignal.read(reader); + this.getInlineEdit(editor, true); })); From 69f86adcad727b6ff21913a81ea816f5a59dc2e4 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 19 Feb 2024 13:59:55 +0100 Subject: [PATCH 1418/1897] rename widget: lots of traces to identify why rename widget doesn't appear sometimes --- .../editor/contrib/rename/browser/rename.ts | 2 +- .../rename/browser/renameInputField.ts | 34 ++++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index cdcadcf58f7..fcf0db314c2 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -315,7 +315,7 @@ class RenameController implements IEditorContribution { } cancelRenameInput(): void { - this._renameInputField.cancelInput(true); + this._renameInputField.cancelInput(true, 'cancelRenameInput command'); } focusNextRenameSuggestion(): void { diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 106d866178a..520ed6d0e0e 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -25,6 +25,7 @@ import { NewSymbolName, NewSymbolNameTag, ProviderResult } from 'vs/editor/commo import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ILogService } from 'vs/platform/log/common/log'; import { defaultListStyles } from 'vs/platform/theme/browser/defaultStyles'; import { editorWidgetBackground, @@ -72,6 +73,7 @@ export class RenameInputField implements IContentWidget { @IThemeService private readonly _themeService: IThemeService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @IContextKeyService contextKeyService: IContextKeyService, + @ILogService private readonly _logService: ILogService, ) { this._visibleContextKey = CONTEXT_RENAME_INPUT_VISIBLE.bindTo(contextKeyService); this._focusedContextKey = CONTEXT_RENAME_INPUT_FOCUSED.bindTo(contextKeyService); @@ -206,9 +208,10 @@ export class RenameInputField implements IContentWidget { } afterRender(position: ContentWidgetPositionPreference | null): void { + this._trace('invoking afterRender, position: ', position ? 'not null' : 'null'); if (position === null) { // cancel rename when input widget isn't rendered anymore - this.cancelInput(true); + this.cancelInput(true, 'afterRender (because position is null)'); return; } @@ -241,10 +244,12 @@ export class RenameInputField implements IContentWidget { private _currentCancelInput?: (focusEditor: boolean) => void; acceptInput(wantsPreview: boolean): void { + this._trace(`invoking acceptInput`); this._currentAcceptInput?.(wantsPreview); } - cancelInput(focusEditor: boolean): void { + cancelInput(focusEditor: boolean, caller: string): void { + this._trace(`invoking cancelInput, caller: ${caller}, _currentCancelInput: ${this._currentAcceptInput ? 'not undefined' : 'undefined'}`); this._currentCancelInput?.(focusEditor); } @@ -280,6 +285,7 @@ export class RenameInputField implements IContentWidget { return new Promise(resolve => { this._currentCancelInput = (focusEditor) => { + this._trace('invoking _currentCancelInput'); this._currentAcceptInput = undefined; this._currentCancelInput = undefined; this._candidatesView?.clearCandidates(); @@ -288,12 +294,13 @@ export class RenameInputField implements IContentWidget { }; this._currentAcceptInput = (wantsPreview) => { + this._trace('invoking _currentAcceptInput'); assertType(this._input !== undefined); assertType(this._candidatesView !== undefined); const candidateName = this._candidatesView.focusedCandidate; if ((candidateName === undefined && this._input.value === value) || this._input.value.trim().length === 0) { - this.cancelInput(true); + this.cancelInput(true, '_currentAcceptInput (because candidateName is undefined or input.value is empty)'); return; } @@ -307,9 +314,9 @@ export class RenameInputField implements IContentWidget { }); }; - disposeOnDone.add(cts.token.onCancellationRequested(() => this.cancelInput(true))); + disposeOnDone.add(cts.token.onCancellationRequested(() => this.cancelInput(true, 'cts.token.onCancellationRequested'))); if (!_sticky) { - disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(!this._domNode?.ownerDocument.hasFocus()))); + disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(!this._domNode?.ownerDocument.hasFocus(), 'editor.onDidBlurEditorWidget'))); } this._show(); @@ -321,6 +328,7 @@ export class RenameInputField implements IContentWidget { } private _show(): void { + this._trace('invoking _show'); this._editor.revealLineInCenterIfOutsideViewport(this._position!.lineNumber, ScrollType.Smooth); this._visible = true; this._visibleContextKey.set(true); @@ -335,9 +343,13 @@ export class RenameInputField implements IContentWidget { } private async _updateRenameCandidates(candidates: ProviderResult[], currentName: string, token: CancellationToken) { + const trace = (...args: any[]) => this._trace('_updateRenameCandidates', ...args); + + trace('start'); const namesListResults = await raceCancellation(Promise.allSettled(candidates), token); if (namesListResults === undefined) { + trace('returning early - received updateRenameCandidates results - undefined'); return; } @@ -346,27 +358,39 @@ export class RenameInputField implements IContentWidget { ? namesListResult.value : [] ); + trace(`received updateRenameCandidates results - total (unfiltered) ${newNames.length} candidates.`); // deduplicate and filter out the current value const distinctNames = arrays.distinct(newNames, v => v.newSymbolName); + trace(`distinct candidates - ${distinctNames.length} candidates.`); + const validDistinctNames = distinctNames.filter(({ newSymbolName }) => newSymbolName.trim().length > 0 && newSymbolName !== this._input?.value && newSymbolName !== currentName); + trace(`valid distinct candidates - ${newNames.length} candidates.`); if (validDistinctNames.length < 1) { + trace('returning early - no valid distinct candidates'); return; } // show the candidates + trace('setting candidates'); this._candidatesView!.setCandidates(validDistinctNames); // ask editor to re-layout given that the widget is now of a different size after rendering rename candidates + trace('asking editor to re-layout'); this._editor.layoutContentWidget(this); } private _hide(): void { + this._trace('invoked _hide'); this._visible = false; this._visibleContextKey.reset(); this._editor.layoutContentWidget(this); } + + private _trace(...args: any[]) { + this._logService.trace('RenameInputField', ...args); + } } export class CandidatesView { From 26fdfc6c98f742a2ea967cbc259aab4ecbae5bbc Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 19 Feb 2024 14:19:29 +0100 Subject: [PATCH 1419/1897] move accessible diff view above status actions (#205538) --- src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 7fd28a442ac..f65363a736b 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -179,13 +179,13 @@ export class InlineChatWidget { h('div.messageActions@messageActions') ]), h('div.followUps.hidden@followUps'), + h('div.accessibleViewer@accessibleViewer'), h('div.status@status', [ h('div.label.info.hidden@infoLabel'), h('div.actions.hidden@statusToolbar'), h('div.label.status.hidden@statusLabel'), h('div.actions.hidden@feedbackToolbar'), ]), - h('div.accessibleViewer@accessibleViewer') ] ); From d83cc26e1b1046e6464fe1a363287bc4baf4e2c8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 19 Feb 2024 14:37:31 +0100 Subject: [PATCH 1420/1897] voice - avoid cancellation token and disposable at the same time (#205532) * voice - avoid cancellation token and disposable at the same time * fix tests * . * . --- .../workbench/api/browser/mainThreadSpeech.ts | 45 +++++++++++-------- .../contrib/chat/common/voiceChat.ts | 10 ++--- .../actions/voiceChatActions.ts | 2 +- .../chat/test/common/voiceChat.test.ts | 14 +++--- .../browser/dictation/editorDictation.ts | 2 +- .../electron-sandbox/inlineChatQuickVoice.ts | 1 - .../contrib/speech/common/speechService.ts | 4 +- .../contrib/terminal/browser/terminalVoice.ts | 7 +-- 8 files changed, 46 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadSpeech.ts b/src/vs/workbench/api/browser/mainThreadSpeech.ts index d670ffd66a1..389fa1b8709 100644 --- a/src/vs/workbench/api/browser/mainThreadSpeech.ts +++ b/src/vs/workbench/api/browser/mainThreadSpeech.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostContext, ExtHostSpeechShape, MainContext, MainThreadSpeechShape } from 'vs/workbench/api/common/extHost.protocol'; @@ -43,43 +42,53 @@ export class MainThreadSpeech implements MainThreadSpeechShape { const registration = this.speechService.registerSpeechProvider(identifier, { metadata, createSpeechToTextSession: token => { + if (token.isCancellationRequested) { + return { + onDidChange: Event.None + }; + } + const disposables = new DisposableStore(); - const cts = new CancellationTokenSource(token); const session = Math.random(); this.proxy.$createSpeechToTextSession(handle, session); - disposables.add(token.onCancellationRequested(() => this.proxy.$cancelSpeechToTextSession(session))); const onDidChange = disposables.add(new Emitter()); this.speechToTextSessions.set(session, { onDidChange }); + disposables.add(token.onCancellationRequested(() => { + this.proxy.$cancelSpeechToTextSession(session); + this.speechToTextSessions.delete(session); + disposables.dispose(); + })); + return { - onDidChange: onDidChange.event, - dispose: () => { - cts.dispose(true); - this.speechToTextSessions.delete(session); - disposables.dispose(); - } + onDidChange: onDidChange.event }; }, createKeywordRecognitionSession: token => { + if (token.isCancellationRequested) { + return { + onDidChange: Event.None + }; + } + const disposables = new DisposableStore(); - const cts = new CancellationTokenSource(token); const session = Math.random(); this.proxy.$createKeywordRecognitionSession(handle, session); - disposables.add(token.onCancellationRequested(() => this.proxy.$cancelKeywordRecognitionSession(session))); const onDidChange = disposables.add(new Emitter()); this.keywordRecognitionSessions.set(session, { onDidChange }); + disposables.add(token.onCancellationRequested(() => { + this.proxy.$cancelKeywordRecognitionSession(session); + this.keywordRecognitionSessions.delete(session); + disposables.dispose(); + })); + return { - onDidChange: onDidChange.event, - dispose: () => { - cts.dispose(true); - this.keywordRecognitionSessions.delete(session); - disposables.dispose(); - } + onDidChange: onDidChange.event }; } }); diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index ba4156495db..c0cf6daa574 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -5,7 +5,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { rtrim } from 'vs/base/common/strings'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; @@ -41,7 +41,7 @@ export interface IVoiceChatTextEvent extends ISpeechToTextEvent { readonly waitingForInput?: boolean; } -export interface IVoiceChatSession extends IDisposable { +export interface IVoiceChatSession { readonly onDidChange: Event; } @@ -131,12 +131,13 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { createVoiceChatSession(token: CancellationToken, options: IVoiceChatSessionOptions): IVoiceChatSession { const disposables = new DisposableStore(); + disposables.add(token.onCancellationRequested(() => disposables.dispose())); let detectedAgent = false; let detectedSlashCommand = false; const emitter = disposables.add(new Emitter()); - const session = disposables.add(this.speechService.createSpeechToTextSession(token, 'chat')); + const session = this.speechService.createSpeechToTextSession(token, 'chat'); disposables.add(session.onDidChange(e => { switch (e.status) { case SpeechToTextStatus.Recognizing: @@ -212,8 +213,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { })); return { - onDidChange: emitter.event, - dispose: () => disposables.dispose() + onDidChange: emitter.event }; } diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 75867e30b4b..e56a11a9ca6 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -292,7 +292,7 @@ class VoiceChatSessions { this.voiceChatGettingReadyKey.set(true); - const voiceChatSession = session.disposables.add(this.voiceChatService.createVoiceChatSession(cts.token, { usesAgents: controller.context !== 'inline' })); + const voiceChatSession = this.voiceChatService.createVoiceChatSession(cts.token, { usesAgents: controller.context !== 'inline' }); let inputValue = controller.getInput(); diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index fd4184ab8eb..eaaea9096dd 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ProviderResult } from 'vs/editor/common/languages'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -74,8 +74,7 @@ suite('VoiceChat', () => { createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { return { - onDidChange: emitter.event, - dispose: () => { } + onDidChange: emitter.event }; } @@ -89,12 +88,11 @@ suite('VoiceChat', () => { let service: VoiceChatService; let event: IVoiceChatTextEvent | undefined; - let session: ISpeechToTextSession | undefined; function createSession(options: IVoiceChatSessionOptions) { - session?.dispose(); - - session = disposables.add(service.createVoiceChatSession(CancellationToken.None, options)); + const cts = new CancellationTokenSource(); + disposables.add(toDisposable(() => cts.dispose(true))); + const session = service.createVoiceChatSession(cts.token, options); disposables.add(session.onDidChange(e => { event = e; })); diff --git a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts index 0150448c6bd..b4ed9df731d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts @@ -231,7 +231,7 @@ export class EditorDictation extends Disposable implements IEditorContribution { const cts = new CancellationTokenSource(); disposables.add(toDisposable(() => cts.dispose(true))); - const session = disposables.add(this.speechService.createSpeechToTextSession(cts.token)); + const session = this.speechService.createSpeechToTextSession(cts.token); disposables.add(session.onDidChange(e => { if (cts.token.isCancellationRequested) { return; diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index 16c1d70d90b..b75bb758c6c 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -263,7 +263,6 @@ export class InlineChatQuickVoice implements IEditorContribution { const done = (abort: boolean) => { cts.dispose(true); - session.dispose(); listener.dispose(); this._widget.hide(); this._ctxQuickChatInProgress.reset(); diff --git a/src/vs/workbench/contrib/speech/common/speechService.ts b/src/vs/workbench/contrib/speech/common/speechService.ts index 1e6e442eaef..4e4ff9345e6 100644 --- a/src/vs/workbench/contrib/speech/common/speechService.ts +++ b/src/vs/workbench/contrib/speech/common/speechService.ts @@ -33,7 +33,7 @@ export interface ISpeechToTextEvent { readonly text?: string; } -export interface ISpeechToTextSession extends IDisposable { +export interface ISpeechToTextSession { readonly onDidChange: Event; } @@ -48,7 +48,7 @@ export interface IKeywordRecognitionEvent { readonly text?: string; } -export interface IKeywordRecognitionSession extends IDisposable { +export interface IKeywordRecognitionSession { readonly onDidChange: Event; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts b/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts index 57464855e57..9ff52e29fc5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts @@ -5,7 +5,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { AccessibilityVoiceSettingId, SpeechTimeoutDefault } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; @@ -87,8 +87,9 @@ export class TerminalVoiceSession extends Disposable { this._sendText(); this.stop(); }, voiceTimeout)); - this._cancellationTokenSource = this._register(new CancellationTokenSource()); - const session = this._disposables.add(this._speechService.createSpeechToTextSession(this._cancellationTokenSource!.token)); + this._cancellationTokenSource = new CancellationTokenSource(); + this._register(toDisposable(() => this._cancellationTokenSource?.dispose(true))); + const session = this._speechService.createSpeechToTextSession(this._cancellationTokenSource?.token); this._disposables.add(session.onDidChange((e) => { if (this._cancellationTokenSource?.token.isCancellationRequested) { From b2b60ac5b3fd3d359994b8f2f79ab54c0c6523b6 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Mon, 19 Feb 2024 14:39:49 +0100 Subject: [PATCH 1421/1897] fix: memory leak in code editor widget (#205488) --- src/vs/editor/browser/widget/codeEditorContributions.ts | 8 ++++---- src/vs/editor/browser/widget/codeEditorWidget.ts | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditorContributions.ts b/src/vs/editor/browser/widget/codeEditorContributions.ts index f556e1440f1..840bcc4f525 100644 --- a/src/vs/editor/browser/widget/codeEditorContributions.ts +++ b/src/vs/editor/browser/widget/codeEditorContributions.ts @@ -5,7 +5,7 @@ import { getWindow, runWhenWindowIdle } from 'vs/base/browser/dom'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap, IDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorContributionInstantiation, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; @@ -111,10 +111,10 @@ export class CodeEditorContributions extends Disposable { this._instantiateSome(EditorContributionInstantiation.BeforeFirstInteraction); } - public onAfterModelAttached(): void { - this._register(runWhenWindowIdle(getWindow(this._editor?.getDomNode()), () => { + public onAfterModelAttached(): IDisposable { + return runWhenWindowIdle(getWindow(this._editor?.getDomNode()), () => { this._instantiateSome(EditorContributionInstantiation.AfterFirstRender); - }, 50)); + }, 50); } private _instantiateSome(instantiation: EditorContributionInstantiation): void { diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 63be27baefb..5466f913d24 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -242,6 +242,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE private readonly _overflowWidgetsDomNode: HTMLElement | undefined; private readonly _id: number; private readonly _configuration: IEditorConfiguration; + private _contributionsDisposable: IDisposable | undefined; protected readonly _actions = new Map(); @@ -523,7 +524,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._onDidChangeModel.fire(e); this._postDetachModelCleanup(detachedModel); - this._contributions.onAfterModelAttached(); + this._contributionsDisposable = this._contributions.onAfterModelAttached(); } private _removeDecorationTypes(): void { @@ -1871,6 +1872,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } private _detachModel(): ITextModel | null { + this._contributionsDisposable?.dispose(); + this._contributionsDisposable = undefined; if (!this._modelData) { return null; } @@ -1887,7 +1890,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE if (this._bannerDomNode && this._domElement.contains(this._bannerDomNode)) { this._domElement.removeChild(this._bannerDomNode); } - return model; } From a6921e0bc8720b2f5552a1d435b54bce471ef1a3 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 19 Feb 2024 14:47:46 +0100 Subject: [PATCH 1422/1897] rename widget: fix not showing on 2nd try --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 520ed6d0e0e..4f9f1b15be8 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -179,9 +179,12 @@ export class RenameInputField implements IContentWidget { const bodyBox = getClientArea(this.getDomNode().ownerDocument.body); const editorBox = getDomNodePagePosition(this._editor.getDomNode()); - const cursorBox = this._editor.getScrolledVisiblePosition(this._position!); - this._nPxAvailableAbove = cursorBox.top + editorBox.top; + // FIXME@ulugbekna: can getVisibleRanges() be empty? if so what to do about it + const firstLineInViewport = this._editor.getVisibleRanges()[0].startLineNumber; + const cursorBoxTop = this._editor.getTopForLineNumber(this._position!.lineNumber) - this._editor.getTopForLineNumber(firstLineInViewport); + + this._nPxAvailableAbove = cursorBoxTop + editorBox.top; this._nPxAvailableBelow = bodyBox.height - this._nPxAvailableAbove; const lineHeight = this._editor.getOption(EditorOption.lineHeight); From 05bf957b312e16532c7669004df043e7c580af1d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 19 Feb 2024 13:53:58 +0000 Subject: [PATCH 1423/1897] Rename the chat agent API to "participant" (#205477) * Start renaming chat API from "agent" to "participant" * Rename the rest of the API * Rename in integration test * Update integration test api proposals * Bump distro --- extensions/vscode-api-tests/package.json | 4 +- .../src/singlefolder-tests/chat.test.ts | 48 ++--- package.json | 2 +- .../workbench/api/common/extHost.api.impl.ts | 16 +- .../api/common/extHostChatAgents2.ts | 86 ++++----- .../api/common/extHostChatVariables.ts | 2 +- .../api/common/extHostTypeConverters.ts | 40 ++--- src/vs/workbench/api/common/extHostTypes.ts | 18 +- .../browser/actions/chatCodeblockActions.ts | 6 +- .../contrib/chat/common/chatService.ts | 4 +- .../contrib/chat/common/chatServiceImpl.ts | 4 +- .../common/extensionsApiProposals.ts | 6 +- ...s => vscode.proposed.chatParticipant.d.ts} | 168 +++++++++--------- ...de.proposed.chatParticipantAdditions.d.ts} | 122 ++++++------- ...code.proposed.defaultChatParticipant.d.ts} | 20 +-- .../vscode.proposed.interactive.d.ts | 2 +- 16 files changed, 273 insertions(+), 275 deletions(-) rename src/vscode-dts/{vscode.proposed.chatAgents2.d.ts => vscode.proposed.chatParticipant.d.ts} (68%) rename src/vscode-dts/{vscode.proposed.chatAgents2Additions.d.ts => vscode.proposed.chatParticipantAdditions.d.ts} (63%) rename src/vscode-dts/{vscode.proposed.defaultChatAgent.d.ts => vscode.proposed.defaultChatParticipant.d.ts} (53%) diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 85f70af3f4f..adf3c5fae9f 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -7,9 +7,9 @@ "enabledApiProposals": [ "activeComment", "authSession", - "chatAgents2", + "chatParticipant", "languageModels", - "defaultChatAgent", + "defaultChatParticipant", "contribViewsRemote", "contribStatusBarItems", "createFileSystemWatcher", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index bdb033322eb..28621d1bb6b 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import 'mocha'; -import { CancellationToken, ChatAgentContext, ChatAgentRequest, ChatAgentResult2, ChatVariableLevel, Disposable, Event, EventEmitter, InteractiveSession, ProviderResult, chat, interactive } from 'vscode'; +import { CancellationToken, ChatContext, ChatRequest, ChatResult, ChatVariableLevel, Disposable, Event, EventEmitter, InteractiveSession, ProviderResult, chat, interactive } from 'vscode'; import { DeferredPromise, assertNoRpc, closeAllEditors, disposeAll } from '../utils'; suite('chat', () => { @@ -21,15 +21,15 @@ suite('chat', () => { disposeAll(disposables); }); - function getDeferredForRequest(): DeferredPromise { - const deferred = new DeferredPromise(); - disposables.push(setupAgent()(request => deferred.complete(request.request))); + function getDeferredForRequest(): DeferredPromise { + const deferred = new DeferredPromise(); + disposables.push(setupParticipant()(request => deferred.complete(request.request))); return deferred; } - function setupAgent(): Event<{ request: ChatAgentRequest; context: ChatAgentContext }> { - const emitter = new EventEmitter<{ request: ChatAgentRequest; context: ChatAgentContext }>(); + function setupParticipant(): Event<{ request: ChatRequest; context: ChatContext }> { + const emitter = new EventEmitter<{ request: ChatRequest; context: ChatContext }>(); disposables.push(); disposables.push(interactive.registerInteractiveSessionProvider('provider', { prepareSession: (_token: CancellationToken): ProviderResult => { @@ -40,23 +40,23 @@ suite('chat', () => { }, })); - const agent = chat.createChatAgent('agent', (request, context, _progress, _token) => { + const participant = chat.createChatParticipant('participant', (request, context, _progress, _token) => { emitter.fire({ request, context }); return null; }); - agent.isDefault = true; - agent.commandProvider = { + participant.isDefault = true; + participant.commandProvider = { provideCommands: (_token) => { return [{ name: 'hello', description: 'Hello' }]; } }; - disposables.push(agent); + disposables.push(participant); return emitter.event; } - test('agent and slash command', async () => { - const onRequest = setupAgent(); - interactive.sendInteractiveRequestToProvider('provider', { message: '@agent /hello friend' }); + test('participant and slash command', async () => { + const onRequest = setupParticipant(); + interactive.sendInteractiveRequestToProvider('provider', { message: '@participant /hello friend' }); let i = 0; onRequest(request => { @@ -64,16 +64,16 @@ suite('chat', () => { assert.deepStrictEqual(request.request.command, 'hello'); assert.strictEqual(request.request.prompt, 'friend'); i++; - interactive.sendInteractiveRequestToProvider('provider', { message: '@agent /hello friend' }); + interactive.sendInteractiveRequestToProvider('provider', { message: '@participant /hello friend' }); } else { assert.strictEqual(request.context.history.length, 1); - assert.strictEqual(request.context.history[0].agent.agent, 'agent'); + assert.strictEqual(request.context.history[0].participant.participant, 'participant'); assert.strictEqual(request.context.history[0].command, 'hello'); } }); }); - test('agent and variable', async () => { + test('participant and variable', async () => { disposables.push(chat.registerVariable('myVar', 'My variable', { resolve(_name, _context, _token) { return [{ level: ChatVariableLevel.Full, value: 'myValue' }]; @@ -81,7 +81,7 @@ suite('chat', () => { })); const deferred = getDeferredForRequest(); - interactive.sendInteractiveRequestToProvider('provider', { message: '@agent hi #myVar' }); + interactive.sendInteractiveRequestToProvider('provider', { message: '@participant hi #myVar' }); const request = await deferred.p; assert.strictEqual(request.prompt, 'hi #myVar'); assert.strictEqual(request.variables[0].values[0].value, 'myValue'); @@ -97,25 +97,25 @@ suite('chat', () => { }, })); - const deferred = new DeferredPromise(); - const agent = chat.createChatAgent('agent', (_request, _context, _progress, _token) => { + const deferred = new DeferredPromise(); + const participant = chat.createChatParticipant('participant', (_request, _context, _progress, _token) => { return { metadata: { key: 'value' } }; }); - agent.isDefault = true; - agent.commandProvider = { + participant.isDefault = true; + participant.commandProvider = { provideCommands: (_token) => { return [{ name: 'hello', description: 'Hello' }]; } }; - agent.followupProvider = { + participant.followupProvider = { provideFollowups(result, _token) { deferred.complete(result); return []; }, }; - disposables.push(agent); + disposables.push(participant); - interactive.sendInteractiveRequestToProvider('provider', { message: '@agent /hello friend' }); + interactive.sendInteractiveRequestToProvider('provider', { message: '@participant /hello friend' }); const result = await deferred.p; assert.deepStrictEqual(result.metadata, { key: 'value' }); }); diff --git a/package.json b/package.json index e413eef05b2..df171934c34 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "664b4b796ea2343e71889a507e125feb14390bdf", + "distro": "af73a537ea203329debad3df7ca7b42b4799473f", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index b0b9c8c0b47..46292e1bb11 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1410,15 +1410,15 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostChatProvider.registerLanguageModel(extension, id, provider, metadata); }, registerVariable(name: string, description: string, resolver: vscode.ChatVariableResolver) { - checkProposedApiEnabled(extension, 'chatAgents2'); + checkProposedApiEnabled(extension, 'chatParticipant'); return extHostChatVariables.registerVariableResolver(extension, name, description, resolver); }, registerMappedEditsProvider(selector: vscode.DocumentSelector, provider: vscode.MappedEditsProvider) { checkProposedApiEnabled(extension, 'mappedEditsProvider'); return extHostLanguageFeatures.registerMappedEditsProvider(extension, selector, provider); }, - createChatAgent(name: string, handler: vscode.ChatAgentExtendedRequestHandler) { - checkProposedApiEnabled(extension, 'chatAgents2'); + createChatParticipant(name: string, handler: vscode.ChatExtendedRequestHandler) { + checkProposedApiEnabled(extension, 'chatParticipant'); return extHostChatAgents2.createChatAgent(extension, name, handler); }, }; @@ -1472,9 +1472,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // types Breakpoint: extHostTypes.Breakpoint, TerminalOutputAnchor: extHostTypes.TerminalOutputAnchor, - ChatAgentResultFeedbackKind: extHostTypes.ChatAgentResultFeedbackKind, + ChatResultFeedbackKind: extHostTypes.ChatResultFeedbackKind, ChatVariableLevel: extHostTypes.ChatVariableLevel, - ChatAgentCompletionItem: extHostTypes.ChatAgentCompletionItem, + ChatCompletionItem: extHostTypes.ChatCompletionItem, CallHierarchyIncomingCall: extHostTypes.CallHierarchyIncomingCall, CallHierarchyItem: extHostTypes.CallHierarchyItem, CallHierarchyOutgoingCall: extHostTypes.CallHierarchyOutgoingCall, @@ -1664,7 +1664,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I LogLevel: LogLevel, EditSessionIdentityMatch: EditSessionIdentityMatch, InteractiveSessionVoteDirection: extHostTypes.InteractiveSessionVoteDirection, - ChatAgentCopyKind: extHostTypes.ChatAgentCopyKind, + ChatCopyKind: extHostTypes.ChatCopyKind, InteractiveEditorResponseFeedbackKind: extHostTypes.InteractiveEditorResponseFeedbackKind, StackFrameFocus: extHostTypes.StackFrameFocus, ThreadFocus: extHostTypes.ThreadFocus, @@ -1678,8 +1678,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ChatResponseProgressPart: extHostTypes.ChatResponseProgressPart, ChatResponseReferencePart: extHostTypes.ChatResponseReferencePart, ChatResponseCommandButtonPart: extHostTypes.ChatResponseCommandButtonPart, - ChatAgentRequestTurn: extHostTypes.ChatAgentRequestTurn, - ChatAgentResponseTurn: extHostTypes.ChatAgentResponseTurn, + ChatRequestTurn: extHostTypes.ChatRequestTurn, + ChatResponseTurn: extHostTypes.ChatResponseTurn, LanguageModelSystemMessage: extHostTypes.LanguageModelSystemMessage, LanguageModelUserMessage: extHostTypes.LanguageModelUserMessage, LanguageModelAssistantMessage: extHostTypes.LanguageModelAssistantMessage, diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 110b89a042b..59a5684877c 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -31,7 +31,7 @@ class ChatAgentResponseStream { private _stopWatch = StopWatch.create(false); private _isClosed: boolean = false; private _firstProgress: number | undefined; - private _apiObject: vscode.ChatAgentExtendedResponseStream | undefined; + private _apiObject: vscode.ChatExtendedResponseStream | undefined; constructor( private readonly _extension: IExtensionDescription, @@ -165,7 +165,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); } - createChatAgent(extension: IExtensionDescription, name: string, handler: vscode.ChatAgentExtendedRequestHandler): vscode.ChatAgent2 { + createChatAgent(extension: IExtensionDescription, name: string, handler: vscode.ChatExtendedRequestHandler): vscode.ChatParticipant { const handle = ExtHostChatAgents2._idPool++; const agent = new ExtHostChatAgent(extension, name, this._proxy, handle, handler); this._agents.set(handle, agent); @@ -219,22 +219,22 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } } - private async prepareHistoryTurns(request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise<(vscode.ChatAgentRequestTurn | vscode.ChatAgentResponseTurn)[]> { + private async prepareHistoryTurns(request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise<(vscode.ChatRequestTurn | vscode.ChatResponseTurn)[]> { - const res: (vscode.ChatAgentRequestTurn | vscode.ChatAgentResponseTurn)[] = []; + const res: (vscode.ChatRequestTurn | vscode.ChatResponseTurn)[] = []; for (const h of context.history) { const ehResult = typeConvert.ChatAgentResult.to(h.result); - const result: vscode.ChatAgentResult2 = request.agentId === h.request.agentId ? + const result: vscode.ChatResult = request.agentId === h.request.agentId ? ehResult : { ...ehResult, metadata: undefined }; // REQUEST turn - res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', agent: h.request.agentId })); + res.push(new extHostTypes.ChatRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', participant: h.request.agentId })); // RESPONSE turn const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))); - res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, { extensionId: '', agent: h.request.agentId }, h.request.command)); + res.push(new extHostTypes.ChatResponseTurn(parts, result, { extensionId: '', participant: h.request.agentId }, h.request.command)); } return res; @@ -271,13 +271,13 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } const ehResult = typeConvert.ChatAgentResult.to(result); - let kind: extHostTypes.ChatAgentResultFeedbackKind; + let kind: extHostTypes.ChatResultFeedbackKind; switch (vote) { case InteractiveSessionVoteDirection.Down: - kind = extHostTypes.ChatAgentResultFeedbackKind.Unhelpful; + kind = extHostTypes.ChatResultFeedbackKind.Unhelpful; break; case InteractiveSessionVoteDirection.Up: - kind = extHostTypes.ChatAgentResultFeedbackKind.Helpful; + kind = extHostTypes.ChatResultFeedbackKind.Helpful; break; } agent.acceptFeedback(reportIssue ? @@ -333,8 +333,8 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { class ExtHostChatAgent { - private _commandProvider: vscode.ChatAgentCommandProvider | undefined; - private _followupProvider: vscode.ChatAgentFollowupProvider | undefined; + private _commandProvider: vscode.ChatCommandProvider | undefined; + private _followupProvider: vscode.ChatFollowupProvider | undefined; private _description: string | undefined; private _fullName: string | undefined; private _iconPath: vscode.Uri | { light: vscode.Uri; dark: vscode.Uri } | vscode.ThemeIcon | undefined; @@ -343,11 +343,11 @@ class ExtHostChatAgent { private _helpTextPostfix: string | vscode.MarkdownString | undefined; private _sampleRequest?: string; private _isSecondary: boolean | undefined; - private _onDidReceiveFeedback = new Emitter(); - private _onDidPerformAction = new Emitter(); + private _onDidReceiveFeedback = new Emitter(); + private _onDidPerformAction = new Emitter(); private _supportIssueReporting: boolean | undefined; - private _agentVariableProvider?: { provider: vscode.ChatAgentCompletionItemProvider; triggerCharacters: string[] }; - private _welcomeMessageProvider?: vscode.ChatAgentWelcomeMessageProvider | undefined; + private _agentVariableProvider?: { provider: vscode.ChatParticipantCompletionItemProvider; triggerCharacters: string[] }; + private _welcomeMessageProvider?: vscode.ChatWelcomeMessageProvider | undefined; private _isSticky: boolean | undefined; constructor( @@ -355,18 +355,18 @@ class ExtHostChatAgent { public readonly id: string, private readonly _proxy: MainThreadChatAgentsShape2, private readonly _handle: number, - private _requestHandler: vscode.ChatAgentExtendedRequestHandler, + private _requestHandler: vscode.ChatExtendedRequestHandler, ) { } - acceptFeedback(feedback: vscode.ChatAgentResult2Feedback) { + acceptFeedback(feedback: vscode.ChatResultFeedback) { this._onDidReceiveFeedback.fire(feedback); } - acceptAction(event: vscode.ChatAgentUserActionEvent) { + acceptAction(event: vscode.ChatUserActionEvent) { this._onDidPerformAction.fire(event); } - async invokeCompletionProvider(query: string, token: CancellationToken): Promise { + async invokeCompletionProvider(query: string, token: CancellationToken): Promise { if (!this._agentVariableProvider) { return []; } @@ -385,7 +385,7 @@ class ExtHostChatAgent { return result .map(c => { if ('isSticky2' in c) { - checkProposedApiEnabled(this.extension, 'chatAgents2Additions'); + checkProposedApiEnabled(this.extension, 'chatParticipantAdditions'); } return { @@ -398,7 +398,7 @@ class ExtHostChatAgent { }); } - async provideFollowups(result: vscode.ChatAgentResult2, token: CancellationToken): Promise { + async provideFollowups(result: vscode.ChatResult, token: CancellationToken): Promise { if (!this._followupProvider) { return []; } @@ -430,7 +430,7 @@ class ExtHostChatAgent { }); } - async provideSampleQuestions(token: CancellationToken): Promise { + async provideSampleQuestions(token: CancellationToken): Promise { if (!this._welcomeMessageProvider || !this._welcomeMessageProvider.provideSampleQuestions) { return []; } @@ -442,7 +442,7 @@ class ExtHostChatAgent { return content; } - get apiAgent(): vscode.ChatAgent2 { + get apiAgent(): vscode.ChatParticipant { let disposed = false; let updateScheduled = false; const updateMetadataSoon = () => { @@ -527,20 +527,20 @@ class ExtHostChatAgent { updateMetadataSoon(); }, get isDefault() { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); return that._isDefault; }, set isDefault(v) { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); that._isDefault = v; updateMetadataSoon(); }, get helpTextPrefix() { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); return that._helpTextPrefix; }, set helpTextPrefix(v) { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); if (!that._isDefault) { throw new Error('helpTextPrefix is only available on the default chat agent'); } @@ -549,11 +549,11 @@ class ExtHostChatAgent { updateMetadataSoon(); }, get helpTextPostfix() { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); return that._helpTextPostfix; }, set helpTextPostfix(v) { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); if (!that._isDefault) { throw new Error('helpTextPostfix is only available on the default chat agent'); } @@ -562,11 +562,11 @@ class ExtHostChatAgent { updateMetadataSoon(); }, get isSecondary() { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); return that._isSecondary; }, set isSecondary(v) { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); that._isSecondary = v; updateMetadataSoon(); }, @@ -578,19 +578,19 @@ class ExtHostChatAgent { updateMetadataSoon(); }, get supportIssueReporting() { - checkProposedApiEnabled(that.extension, 'chatAgents2Additions'); + checkProposedApiEnabled(that.extension, 'chatParticipantAdditions'); return that._supportIssueReporting; }, set supportIssueReporting(v) { - checkProposedApiEnabled(that.extension, 'chatAgents2Additions'); + checkProposedApiEnabled(that.extension, 'chatParticipantAdditions'); that._supportIssueReporting = v; updateMetadataSoon(); }, get onDidReceiveFeedback() { return that._onDidReceiveFeedback.event; }, - set agentVariableProvider(v) { - checkProposedApiEnabled(that.extension, 'chatAgents2Additions'); + set participantVariableProvider(v) { + checkProposedApiEnabled(that.extension, 'chatParticipantAdditions'); that._agentVariableProvider = v; if (v) { if (!v.triggerCharacters.length) { @@ -602,20 +602,20 @@ class ExtHostChatAgent { that._proxy.$unregisterAgentCompletionsProvider(that._handle); } }, - get agentVariableProvider() { - checkProposedApiEnabled(that.extension, 'chatAgents2Additions'); + get participantVariableProvider() { + checkProposedApiEnabled(that.extension, 'chatParticipantAdditions'); return that._agentVariableProvider; }, set welcomeMessageProvider(v) { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); that._welcomeMessageProvider = v; updateMetadataSoon(); }, get welcomeMessageProvider() { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); return that._welcomeMessageProvider; }, - onDidPerformAction: !isProposedApiEnabled(this.extension, 'chatAgents2Additions') + onDidPerformAction: !isProposedApiEnabled(this.extension, 'chatParticipantAdditions') ? undefined! : this._onDidPerformAction.event , @@ -633,10 +633,10 @@ class ExtHostChatAgent { that._onDidReceiveFeedback.dispose(); that._proxy.$unregisterAgent(that._handle); }, - } satisfies vscode.ChatAgent2; + } satisfies vscode.ChatParticipant; } - invoke(request: vscode.ChatAgentRequest, context: vscode.ChatAgentContext, response: vscode.ChatAgentExtendedResponseStream, token: CancellationToken): vscode.ProviderResult { + invoke(request: vscode.ChatRequest, context: vscode.ChatContext, response: vscode.ChatExtendedResponseStream, token: CancellationToken): vscode.ProviderResult { return this._requestHandler(request, context, response, token); } } diff --git a/src/vs/workbench/api/common/extHostChatVariables.ts b/src/vs/workbench/api/common/extHostChatVariables.ts index ca3b265007b..e56e51676d4 100644 --- a/src/vs/workbench/api/common/extHostChatVariables.ts +++ b/src/vs/workbench/api/common/extHostChatVariables.ts @@ -32,7 +32,7 @@ export class ExtHostChatVariables implements ExtHostChatVariablesShape { } try { if (item.resolver.resolve2) { - checkProposedApiEnabled(item.extension, 'chatAgents2Additions'); + checkProposedApiEnabled(item.extension, 'chatParticipantAdditions'); const stream = new ChatVariableResolverResponseStream(requestId, this._proxy); const value = await item.resolver.resolve2(item.data.name, { prompt: messageText }, stream.apiObject, token); if (value) { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index ae82d84eae3..175a9213112 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2195,10 +2195,10 @@ export namespace DataTransfer { } export namespace ChatFollowup { - export function from(followup: vscode.ChatAgentFollowup, request: IChatAgentRequest | undefined): IChatFollowup { + export function from(followup: vscode.ChatFollowup, request: IChatAgentRequest | undefined): IChatFollowup { return { kind: 'reply', - agentId: followup.agentId ?? request?.agentId ?? '', + agentId: followup.participant ?? request?.agentId ?? '', subCommand: followup.command ?? request?.command, message: followup.prompt, title: followup.title, @@ -2206,11 +2206,11 @@ export namespace ChatFollowup { }; } - export function to(followup: IChatFollowup): vscode.ChatAgentFollowup { + export function to(followup: IChatFollowup): vscode.ChatFollowup { return { prompt: followup.message, title: followup.title, - agentId: followup.agentId, + participant: followup.agentId, command: followup.subCommand, tooltip: followup.tooltip, }; @@ -2490,13 +2490,13 @@ export namespace ChatResponsePart { } export namespace ChatResponseProgress { - export function from(extension: IExtensionDescription, progress: vscode.ChatAgentExtendedProgress): extHostProtocol.IChatProgressDto | undefined { + export function from(extension: IExtensionDescription, progress: vscode.ChatExtendedProgress): extHostProtocol.IChatProgressDto | undefined { if ('markdownContent' in progress) { - checkProposedApiEnabled(extension, 'chatAgents2Additions'); + checkProposedApiEnabled(extension, 'chatParticipantAdditions'); return { content: MarkdownString.from(progress.markdownContent), kind: 'markdownContent' }; } else if ('content' in progress) { if ('vulnerabilities' in progress && progress.vulnerabilities) { - checkProposedApiEnabled(extension, 'chatAgents2Additions'); + checkProposedApiEnabled(extension, 'chatParticipantAdditions'); return { content: progress.content, vulnerabilities: progress.vulnerabilities, kind: 'vulnerability' }; } @@ -2504,7 +2504,7 @@ export namespace ChatResponseProgress { return { content: progress.content, kind: 'content' }; } - checkProposedApiEnabled(extension, 'chatAgents2Additions'); + checkProposedApiEnabled(extension, 'chatParticipantAdditions'); return { content: MarkdownString.from(progress.content), kind: 'markdownContent' }; } else if ('documents' in progress) { return { @@ -2534,9 +2534,9 @@ export namespace ChatResponseProgress { name: progress.title, kind: 'inlineReference' }; - } else if ('agentName' in progress) { - checkProposedApiEnabled(extension, 'chatAgents2Additions'); - return { agentName: progress.agentName, command: progress.command, kind: 'agentDetection' }; + } else if ('participant' in progress) { + checkProposedApiEnabled(extension, 'chatParticipantAdditions'); + return { agentName: progress.participant, command: progress.command, kind: 'agentDetection' }; } else if ('message' in progress) { return { content: MarkdownString.from(progress.message), kind: 'progressMessage' }; } else { @@ -2544,7 +2544,7 @@ export namespace ChatResponseProgress { } } - export function to(progress: extHostProtocol.IChatProgressDto): vscode.ChatAgentProgress | undefined { + export function to(progress: extHostProtocol.IChatProgressDto): vscode.ChatProgress | undefined { switch (progress.kind) { case 'markdownContent': case 'inlineReference': @@ -2574,7 +2574,7 @@ export namespace ChatResponseProgress { } } - export function toProgressContent(progress: extHostProtocol.IChatContentProgressDto, commandsConverter: Command.ICommandsConverter): vscode.ChatAgentContentProgress | undefined { + export function toProgressContent(progress: extHostProtocol.IChatContentProgressDto, commandsConverter: Command.ICommandsConverter): vscode.ChatContentProgress | undefined { switch (progress.kind) { case 'markdownContent': // For simplicity, don't sent back the 'extended' types, so downgrade markdown to just some text @@ -2600,7 +2600,7 @@ export namespace ChatResponseProgress { } export namespace ChatAgentRequest { - export function to(request: IChatAgentRequest): vscode.ChatAgentRequest { + export function to(request: IChatAgentRequest): vscode.ChatRequest { return { prompt: request.message, command: request.command, @@ -2610,7 +2610,7 @@ export namespace ChatAgentRequest { } export namespace ChatAgentResolvedVariable { - export function to(request: { name: string; range: IOffsetRange; values: IChatRequestVariableValue[] }): vscode.ChatAgentResolvedVariable { + export function to(request: { name: string; range: IOffsetRange; values: IChatRequestVariableValue[] }): vscode.ChatResolvedVariable { return { name: request.name, range: [request.range.start, request.range.endExclusive], @@ -2620,7 +2620,7 @@ export namespace ChatAgentResolvedVariable { } export namespace ChatAgentCompletionItem { - export function from(item: vscode.ChatAgentCompletionItem): extHostProtocol.IChatAgentCompletionItem { + export function from(item: vscode.ChatCompletionItem): extHostProtocol.IChatAgentCompletionItem { return { label: item.label, values: item.values.map(ChatVariable.from), @@ -2632,7 +2632,7 @@ export namespace ChatAgentCompletionItem { } export namespace ChatAgentResult { - export function to(result: IChatAgentResult): vscode.ChatAgentResult2 { + export function to(result: IChatAgentResult): vscode.ChatResult { return { errorDetails: result.errorDetails, metadata: result.metadata, @@ -2641,7 +2641,7 @@ export namespace ChatAgentResult { } export namespace ChatAgentUserActionEvent { - export function to(result: IChatAgentResult, event: IChatUserActionEvent, commandsConverter: CommandsConverter): vscode.ChatAgentUserActionEvent | undefined { + export function to(result: IChatAgentResult, event: IChatUserActionEvent, commandsConverter: CommandsConverter): vscode.ChatUserActionEvent | undefined { if (event.action.kind === 'vote') { // Is the "feedback" type return; @@ -2649,10 +2649,10 @@ export namespace ChatAgentUserActionEvent { const ehResult = ChatAgentResult.to(result); if (event.action.kind === 'command') { - const commandAction: vscode.ChatAgentCommandAction = { kind: 'command', commandButton: ChatResponseProgress.toProgressContent(event.action.commandButton, commandsConverter) as vscode.ChatAgentCommandButton }; + const commandAction: vscode.ChatCommandAction = { kind: 'command', commandButton: ChatResponseProgress.toProgressContent(event.action.commandButton, commandsConverter) as vscode.ChatCommandButton }; return { action: commandAction, result: ehResult }; } else if (event.action.kind === 'followUp') { - const followupAction: vscode.ChatAgentFollowupAction = { kind: 'followUp', followup: ChatFollowup.to(event.action.followup) }; + const followupAction: vscode.ChatFollowupAction = { kind: 'followUp', followup: ChatFollowup.to(event.action.followup) }; return { action: followupAction, result: ehResult }; } else { return { action: event.action, result: ehResult }; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 611314d600f..db038450765 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4164,7 +4164,7 @@ export enum InteractiveSessionVoteDirection { Up = 1 } -export enum ChatAgentCopyKind { +export enum ChatCopyKind { Action = 1, Toolbar = 2 } @@ -4175,7 +4175,7 @@ export enum ChatVariableLevel { Full = 3 } -export class ChatAgentCompletionItem implements vscode.ChatAgentCompletionItem { +export class ChatCompletionItem implements vscode.ChatCompletionItem { label: string | CompletionItemLabel; insertText?: string; values: vscode.ChatVariableValue[]; @@ -4200,7 +4200,7 @@ export enum InteractiveEditorResponseFeedbackKind { Bug = 4 } -export enum ChatAgentResultFeedbackKind { +export enum ChatResultFeedbackKind { Unhelpful = 0, Helpful = 1, } @@ -4260,21 +4260,21 @@ export class ChatResponseReferencePart { } -export class ChatAgentRequestTurn implements vscode.ChatAgentRequestTurn { +export class ChatRequestTurn implements vscode.ChatRequestTurn { constructor( readonly prompt: string, readonly command: string | undefined, - readonly variables: vscode.ChatAgentResolvedVariable[], - readonly agent: { extensionId: string; agent: string }, + readonly variables: vscode.ChatResolvedVariable[], + readonly participant: { extensionId: string; participant: string }, ) { } } -export class ChatAgentResponseTurn implements vscode.ChatAgentResponseTurn { +export class ChatResponseTurn implements vscode.ChatResponseTurn { constructor( readonly response: ReadonlyArray, - readonly result: vscode.ChatAgentResult2, - readonly agent: { extensionId: string; agent: string }, + readonly result: vscode.ChatResult, + readonly participant: { extensionId: string; participant: string }, readonly command?: string ) { } } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index cee07851804..27f5c9f07f8 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -28,7 +28,7 @@ import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatAct import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { ChatAgentCopyKind, IChatService, IDocumentContext } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatCopyKind, IChatService, IDocumentContext } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CTX_INLINE_CHAT_VISIBLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; @@ -112,7 +112,7 @@ export function registerChatCodeBlockActions() { action: { kind: 'copy', codeBlockIndex: context.codeBlockIndex, - copyKind: ChatAgentCopyKind.Toolbar, + copyKind: ChatCopyKind.Toolbar, copiedCharacters: context.code.length, totalCharacters: context.code.length, copiedText: context.code, @@ -156,7 +156,7 @@ export function registerChatCodeBlockActions() { action: { kind: 'copy', codeBlockIndex: context.codeBlockIndex, - copyKind: ChatAgentCopyKind.Action, + copyKind: ChatCopyKind.Action, copiedText, copiedCharacters: copiedText.length, totalCharacters, diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 769476c60a7..5da8d9b747b 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -177,7 +177,7 @@ export interface IChatVoteAction { reportIssue?: boolean; } -export enum ChatAgentCopyKind { +export enum ChatCopyKind { // Keyboard shortcut or context menu Action = 1, Toolbar = 2 @@ -186,7 +186,7 @@ export enum ChatAgentCopyKind { export interface IChatCopyAction { kind: 'copy'; codeBlockIndex: number; - copyKind: ChatAgentCopyKind; + copyKind: ChatCopyKind; copiedCharacters: number; totalCharacters: number; copiedText: string; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index fe8185212e4..630e7e853e8 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -26,7 +26,7 @@ import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageMode import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -226,7 +226,7 @@ export class ChatService extends Disposable implements IChatService { } else if (action.action.kind === 'copy') { this.telemetryService.publicLog2('interactiveSessionCopy', { providerId: action.providerId, - copyKind: action.action.copyKind === ChatAgentCopyKind.Action ? 'action' : 'toolbar' + copyKind: action.action.copyKind === ChatCopyKind.Action ? 'action' : 'toolbar' }); } else if (action.action.kind === 'insert') { this.telemetryService.publicLog2('interactiveSessionInsert', { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 53e77557226..0ba3e2b1ea2 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -11,8 +11,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', - chatAgents2: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents2.d.ts', - chatAgents2Additions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts', + chatParticipant: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipant.d.ts', + chatParticipantAdditions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts', chatProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts', chatTab: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatTab.d.ts', codeActionAI: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionAI.d.ts', @@ -44,7 +44,7 @@ export const allApiProposals = Object.freeze({ customEditorMove: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.customEditorMove.d.ts', debugFocus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.debugFocus.d.ts', debugVisualization: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.debugVisualization.d.ts', - defaultChatAgent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts', + defaultChatParticipant: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts', diffCommand: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffCommand.d.ts', diffContentOptions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffContentOptions.d.ts', documentFiltersExclusive: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.documentFiltersExclusive.d.ts', diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts similarity index 68% rename from src/vscode-dts/vscode.proposed.chatAgents2.d.ts rename to src/vscode-dts/vscode.proposed.chatParticipant.d.ts index 783c1bcf78e..f4b2242ed46 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -6,70 +6,70 @@ declare module 'vscode' { // TODO@API name: Turn? - export class ChatAgentRequestTurn { + export class ChatRequestTurn { /** * The prompt as entered by the user. * - * Information about variables used in this request are is stored in {@link ChatAgentRequest.variables}. + * Information about variables used in this request are is stored in {@link ChatRequest.variables}. * - * *Note* that the {@link ChatAgent2.name name} of the agent and the {@link ChatAgentCommand.name command} + * *Note* that the {@link ChatParticipant.name name} of the participant and the {@link ChatCommand.name command} * are not part of the prompt. */ readonly prompt: string; /** - * The name of the chat agent and contributing extension to which this request was directed. + * The name of the chat participant and contributing extension to which this request was directed. */ - readonly agent: { readonly extensionId: string; readonly agent: string }; + readonly participant: { readonly extensionId: string; readonly participant: string }; /** - * The name of the {@link ChatAgentCommand command} that was selected for this request. + * The name of the {@link ChatCommand command} that was selected for this request. */ readonly command: string | undefined; /** * The variables that were referenced in this message. */ - readonly variables: ChatAgentResolvedVariable[]; + readonly variables: ChatResolvedVariable[]; - private constructor(prompt: string, command: string | undefined, variables: ChatAgentResolvedVariable[], agent: { extensionId: string; agent: string }); + private constructor(prompt: string, command: string | undefined, variables: ChatResolvedVariable[], participant: { extensionId: string; participant: string }); } // TODO@API name: Turn? - export class ChatAgentResponseTurn { + export class ChatResponseTurn { /** - * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. + * The content that was received from the chat participant. Only the progress parts that represent actual content (not metadata) are represented. */ readonly response: ReadonlyArray; /** - * The result that was received from the chat agent. + * The result that was received from the chat participant. */ - readonly result: ChatAgentResult2; + readonly result: ChatResult; /** - * The name of the chat agent and contributing extension to which this request was directed. + * The name of the chat participant and contributing extension to which this request was directed. */ - readonly agent: { readonly extensionId: string; readonly agent: string }; + readonly participant: { readonly extensionId: string; readonly participant: string }; readonly command?: string; - private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: { extensionId: string; agent: string }); + private constructor(response: ReadonlyArray, result: ChatResult, participant: { extensionId: string; participant: string }); } - export interface ChatAgentContext { + export interface ChatContext { /** * All of the chat messages so far in the current chat session. */ - readonly history: ReadonlyArray; + readonly history: ReadonlyArray; } /** * Represents an error result from a chat request. */ - export interface ChatAgentErrorDetails { + export interface ChatErrorDetails { /** * An error message that is shown to the user. */ @@ -93,11 +93,11 @@ declare module 'vscode' { /** * The result of a chat request. */ - export interface ChatAgentResult2 { + export interface ChatResult { /** * If the request resulted in an error, this property defines the error details. */ - errorDetails?: ChatAgentErrorDetails; + errorDetails?: ChatErrorDetails; /** * Arbitrary metadata for this result. Can be anything but must be JSON-stringifyable. @@ -108,7 +108,7 @@ declare module 'vscode' { /** * Represents the type of user feedback received. */ - export enum ChatAgentResultFeedbackKind { + export enum ChatResultFeedbackKind { /** * The user marked the result as helpful. */ @@ -123,25 +123,24 @@ declare module 'vscode' { /** * Represents user feedback for a result. */ - export interface ChatAgentResult2Feedback { + export interface ChatResultFeedback { /** - * This instance of ChatAgentResult2 is the same instance that was returned from the chat agent, - * and it can be extended with arbitrary properties if needed. + * This instance of ChatResult has the same properties as the result returned from the participant callback, including `metadata`, but is not the same instance. */ - readonly result: ChatAgentResult2; + readonly result: ChatResult; /** * The kind of feedback that was received. */ - readonly kind: ChatAgentResultFeedbackKind; + readonly kind: ChatResultFeedbackKind; } - export interface ChatAgentCommand { + export interface ChatCommand { /** * A short name by which this command is referred to in the UI, e.g. `fix` or * `explain` for commands that fix an issue or explain code. * - * **Note**: The name should be unique among the commands provided by this agent. + * **Note**: The name should be unique among the commands provided by this participant. */ readonly name: string; @@ -157,17 +156,16 @@ declare module 'vscode' { /** * Whether executing the command puts the chat into a persistent mode, where the command is automatically added to the chat input for the next message. - * If this is not set, the chat input will fall back to the agent after submitting this command. */ readonly isSticky?: boolean; } - export interface ChatAgentCommandProvider { + export interface ChatCommandProvider { /** - * Returns a list of commands that its agent is capable of handling. A command - * can be selected by the user and will then be passed to the {@link ChatAgentRequestHandler handler} - * via the {@link ChatAgentRequest.command command} property. + * Returns a list of commands that its participant is capable of handling. A command + * can be selected by the user and will then be passed to the {@link ChatRequestHandler handler} + * via the {@link ChatRequest.command command} property. * * * @param token A cancellation token. @@ -175,26 +173,26 @@ declare module 'vscode' { * an empty array. */ // TODO@API Q: should we provide the current history or last results for extra context? - provideCommands(token: CancellationToken): ProviderResult; + provideCommands(token: CancellationToken): ProviderResult; } /** * A followup question suggested by the model. */ - export interface ChatAgentFollowup { + export interface ChatFollowup { /** * The message to send to the chat. */ prompt: string; /** - * By default, the followup goes to the same agent/command. But this property can be set to invoke a different agent. - * TODO@API do extensions need to specify the extensionID of the agent here as well? + * By default, the followup goes to the same participant/command. But this property can be set to invoke a different participant. + * TODO@API do extensions need to specify the extensionID of the participant here as well? */ - agentId?: string; + participant?: string; /** - * By default, the followup goes to the same agent/command. But this property can be set to invoke a different command. + * By default, the followup goes to the same participant/command. But this property can be set to invoke a different command. */ command?: string; @@ -213,40 +211,40 @@ declare module 'vscode' { /** * Will be invoked once after each request to get suggested followup questions to show the user. The user can click the followup to send it to the chat. */ - export interface ChatAgentFollowupProvider { + export interface ChatFollowupProvider { /** * - * @param result The same instance of the result object that was returned by the chat agent, and it can be extended with arbitrary properties if needed. + * @param result The same instance of the result object that was returned by the chat participant, and it can be extended with arbitrary properties if needed. * @param token A cancellation token. */ - provideFollowups(result: ChatAgentResult2, token: CancellationToken): ProviderResult; + provideFollowups(result: ChatResult, token: CancellationToken): ProviderResult; } /** - * A chat request handler is a callback that will be invoked when a request is made to a chat agent. + * A chat request handler is a callback that will be invoked when a request is made to a chat participant. */ - export type ChatAgentRequestHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentResponseStream, token: CancellationToken) => ProviderResult; + export type ChatRequestHandler = (request: ChatRequest, context: ChatContext, response: ChatResponseStream, token: CancellationToken) => ProviderResult; - export interface ChatAgent2 { + export interface ChatParticipant { /** - * The short name by which this agent is referred to in the UI, e.g `workspace`. + * The short name by which this participant is referred to in the UI, e.g `workspace`. */ readonly name: string; /** - * The full name of this agent. + * The full name of this participant. */ fullName: string; /** - * A human-readable description explaining what this agent does. + * A human-readable description explaining what this participant does. */ description: string; /** - * Icon for the agent shown in UI. + * Icon for the participant shown in UI. */ iconPath?: Uri | { /** @@ -260,27 +258,27 @@ declare module 'vscode' { } | ThemeIcon; /** - * The handler for requests to this agent. + * The handler for requests to this participant. */ - requestHandler: ChatAgentRequestHandler; + requestHandler: ChatRequestHandler; /** - * This provider will be called to retrieve the agent's commands. + * This provider will be called to retrieve the participant's commands. */ - commandProvider?: ChatAgentCommandProvider; + commandProvider?: ChatCommandProvider; /** * This provider will be called once after each request to retrieve suggested followup questions. */ - followupProvider?: ChatAgentFollowupProvider; + followupProvider?: ChatFollowupProvider; /** - * When the user clicks this agent in `/help`, this text will be submitted to this command + * When the user clicks this participant in `/help`, this text will be submitted to this command */ sampleRequest?: string; /** - * Whether invoking the agent puts the chat into a persistent mode, where the agent is automatically added to the chat input for the next message. + * Whether invoking the participant puts the chat into a persistent mode, where the participant is automatically added to the chat input for the next message. */ isSticky?: boolean; @@ -288,13 +286,13 @@ declare module 'vscode' { * An event that fires whenever feedback for a result is received, e.g. when a user up- or down-votes * a result. * - * The passed {@link ChatAgentResult2Feedback.result result} is guaranteed to be the same instance that was - * previously returned from this chat agent. + * The passed {@link ChatResultFeedback.result result} is guaranteed to be the same instance that was + * previously returned from this chat participant. */ - onDidReceiveFeedback: Event; + onDidReceiveFeedback: Event; /** - * Dispose this agent and free resources + * Dispose this participant and free resources */ dispose(): void; } @@ -302,7 +300,7 @@ declare module 'vscode' { /** * A resolved variable value is a name-value pair as well as the range in the prompt where a variable was used. */ - export interface ChatAgentResolvedVariable { + export interface ChatResolvedVariable { /** * The name of the variable. @@ -313,7 +311,7 @@ declare module 'vscode' { readonly name: string; /** - * The start and end index of the variable in the {@link ChatAgentRequest.prompt prompt}. + * The start and end index of the variable in the {@link ChatRequest.prompt prompt}. * * *Note* that the indices take the leading `#`-character into account which means they can * used to modify the prompt as-is. @@ -324,47 +322,47 @@ declare module 'vscode' { readonly values: ChatVariableValue[]; } - export interface ChatAgentRequest { + export interface ChatRequest { /** * The prompt as entered by the user. * - * Information about variables used in this request are is stored in {@link ChatAgentRequest.variables}. + * Information about variables used in this request are is stored in {@link ChatRequest.variables}. * - * *Note* that the {@link ChatAgent2.name name} of the agent and the {@link ChatAgentCommand.name command} + * *Note* that the {@link ChatParticipant.name name} of the participant and the {@link ChatCommand.name command} * are not part of the prompt. */ readonly prompt: string; /** - * The name of the {@link ChatAgentCommand command} that was selected for this request. + * The name of the {@link ChatCommand command} that was selected for this request. */ readonly command: string | undefined; /** * The list of variables and their values that are referenced in the prompt. * - * *Note* that the prompt contains varibale references as authored and that it is up to the agent + * *Note* that the prompt contains varibale references as authored and that it is up to the participant * to further modify the prompt, for instance by inlining variable values or creating links to * headings which contain the resolved values. vvariables are sorted in reverse by their range * in the prompt. That means the last variable in the prompt is the first in this list. This simplifies * string-manipulation of the prompt. */ // TODO@API Q? are there implicit variables that are not part of the prompt? - readonly variables: readonly ChatAgentResolvedVariable[]; + readonly variables: readonly ChatResolvedVariable[]; } - export interface ChatAgentResponseStream { + export interface ChatResponseStream { /** * Push a markdown part to this stream. Short-hand for * `push(new ChatResponseMarkdownPart(value))`. * - * @see {@link ChatAgentResponseStream.push} + * @see {@link ChatResponseStream.push} * @param value A markdown string or a string that should be interpreted as markdown. * @returns This stream. */ - markdown(value: string | MarkdownString): ChatAgentResponseStream; + markdown(value: string | MarkdownString): ChatResponseStream; /** * Push an anchor part to this stream. Short-hand for @@ -374,7 +372,7 @@ declare module 'vscode' { * @param title An optional title that is rendered with value * @returns This stream. */ - anchor(value: Uri | Location, title?: string): ChatAgentResponseStream; + anchor(value: Uri | Location, title?: string): ChatResponseStream; /** * Push a command button part to this stream. Short-hand for @@ -383,7 +381,7 @@ declare module 'vscode' { * @param command A Command that will be executed when the button is clicked. * @returns This stream. */ - button(command: Command): ChatAgentResponseStream; + button(command: Command): ChatResponseStream; /** * Push a filetree part to this stream. Short-hand for @@ -393,7 +391,7 @@ declare module 'vscode' { * @param baseUri The base uri to which this file tree is relative to. * @returns This stream. */ - filetree(value: ChatResponseFileTree[], baseUri: Uri): ChatAgentResponseStream; + filetree(value: ChatResponseFileTree[], baseUri: Uri): ChatResponseStream; /** * Push a progress part to this stream. Short-hand for @@ -405,7 +403,7 @@ declare module 'vscode' { // TODO@API is this always inline or not // TODO@API is this markdown or string? // TODO@API this influences the rendering, it inserts new lines which is likely a bug - progress(value: string): ChatAgentResponseStream; + progress(value: string): ChatResponseStream; /** * Push a reference to this stream. Short-hand for @@ -418,14 +416,14 @@ declare module 'vscode' { */ // TODO@API support non-file uris, like http://example.com // TODO@API support mapped edits - reference(value: Uri | Location): ChatAgentResponseStream; + reference(value: Uri | Location): ChatResponseStream; /** * Pushes a part to this stream. * * @param part A response part, rendered or metadata */ - push(part: ChatResponsePart): ChatAgentResponseStream; + push(part: ChatResponsePart): ChatResponseStream; } // TODO@API should the name suffix differentiate between rendered items (XYZPart) @@ -483,17 +481,17 @@ declare module 'vscode' { export namespace chat { /** - * Create a new {@link ChatAgent2 chat agent} instance. + * Create a new {@link ChatParticipant chat participant} instance. * - * @param name Short name by which the agent is referred to in the UI. The name must be unique for the extension - * contributing the agent but can collide with names from other extensions. - * @param handler A request handler for the agent. - * @returns A new chat agent + * @param name Short name by which the participant is referred to in the UI. The name must be unique for the extension + * contributing the participant but can collide with names from other extensions. + * @param handler A request handler for the participant. + * @returns A new chat participant */ - export function createChatAgent(name: string, handler: ChatAgentRequestHandler): ChatAgent2; + export function createChatParticipant(name: string, handler: ChatRequestHandler): ChatParticipant; /** - * Register a variable which can be used in a chat request to any agent. + * Register a variable which can be used in a chat request to any participant. * @param name The name of the variable, to be used in the chat input as `#name`. * @param description A description of the variable for the chat input suggest widget. * @param resolver Will be called to provide the chat variable's value when it is used. @@ -519,7 +517,7 @@ declare module 'vscode' { level: ChatVariableLevel; /** - * The variable's value, which can be included in an LLM prompt as-is, or the chat agent may decide to read the value and do something else with it. + * The variable's value, which can be included in an LLM prompt as-is, or the chat participant may decide to read the value and do something else with it. */ value: string | Uri; diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts similarity index 63% rename from src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts rename to src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts index 40955904331..f9ed2a208bd 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts @@ -5,70 +5,70 @@ declare module 'vscode' { - export interface ChatAgent2 { - onDidPerformAction: Event; + export interface ChatParticipant { + onDidPerformAction: Event; supportIssueReporting?: boolean; } - export interface ChatAgentErrorDetails { + export interface ChatErrorDetails { /** - * If set to true, the message content is completely hidden. Only ChatAgentErrorDetails#message will be shown. + * If set to true, the message content is completely hidden. Only ChatErrorDetails#message will be shown. */ responseIsRedacted?: boolean; } /** @deprecated */ - export interface ChatAgentMarkdownContent { + export interface ChatMarkdownContent { markdownContent: MarkdownString; } // TODO@API fit this into the stream - export interface ChatAgentDetectedAgent { - agentName: string; - command?: ChatAgentCommand; + export interface ChatDetectedParticipant { + participant: string; + command?: ChatCommand; } // TODO@API fit this into the stream - export interface ChatAgentVulnerability { + export interface ChatVulnerability { title: string; description: string; // id: string; // Later we will need to be able to link these across multiple content chunks. } // TODO@API fit this into the stream - export interface ChatAgentContent { - vulnerabilities?: ChatAgentVulnerability[]; + export interface ChatContent { + vulnerabilities?: ChatVulnerability[]; } /** - * @deprecated use ChatAgentResponseStream instead + * @deprecated use ChatResponseStream instead */ - export type ChatAgentContentProgress = - | ChatAgentContent - | ChatAgentInlineContentReference - | ChatAgentCommandButton; + export type ChatContentProgress = + | ChatContent + | ChatInlineContentReference + | ChatCommandButton; /** - * @deprecated use ChatAgentResponseStream instead + * @deprecated use ChatResponseStream instead */ - export type ChatAgentMetadataProgress = - | ChatAgentUsedContext - | ChatAgentContentReference - | ChatAgentProgressMessage; + export type ChatMetadataProgress = + | ChatUsedContext + | ChatContentReference + | ChatProgressMessage; /** - * @deprecated use ChatAgentResponseStream instead + * @deprecated use ChatResponseStream instead */ - export type ChatAgentProgress = ChatAgentContentProgress | ChatAgentMetadataProgress; + export type ChatProgress = ChatContentProgress | ChatMetadataProgress; /** @deprecated */ - export interface ChatAgentProgressMessage { + export interface ChatProgressMessage { message: string; } /** @deprecated */ - export interface ChatAgentContentReference { + export interface ChatContentReference { /** * The resource that was referenced. */ @@ -78,7 +78,7 @@ declare module 'vscode' { /** * A reference to a piece of content that will be rendered inline with the markdown content. */ - export interface ChatAgentInlineContentReference { + export interface ChatInlineContentReference { /** * The resource being referenced. */ @@ -93,62 +93,62 @@ declare module 'vscode' { /** * Displays a {@link Command command} as a button in the chat response. */ - export interface ChatAgentCommandButton { + export interface ChatCommandButton { command: Command; } /** * A piece of the chat response's content. Will be merged with other progress pieces as needed, and rendered as markdown. */ - export interface ChatAgentContent { + export interface ChatContent { /** * The content as a string of markdown source. */ content: string; } - export interface ChatAgentDocumentContext { + export interface ChatDocumentContext { uri: Uri; version: number; ranges: Range[]; } // TODO@API fit this into the stream - export interface ChatAgentUsedContext { - documents: ChatAgentDocumentContext[]; + export interface ChatUsedContext { + documents: ChatDocumentContext[]; } - export interface ChatAgentResponseStream { + export interface ChatResponseStream { /** * @deprecated use above methods instread */ - report(value: ChatAgentProgress): void; + report(value: ChatProgress): void; } /** @deprecated */ - export type ChatAgentExtendedProgress = ChatAgentProgress - | ChatAgentMarkdownContent - | ChatAgentDetectedAgent; + export type ChatExtendedProgress = ChatProgress + | ChatMarkdownContent + | ChatDetectedParticipant; - export type ChatAgentExtendedResponseStream = ChatAgentResponseStream & { + export type ChatExtendedResponseStream = ChatResponseStream & { /** * @deprecated */ - report(value: ChatAgentExtendedProgress): void; + report(value: ChatExtendedProgress): void; }; - export interface ChatAgent2 { + export interface ChatParticipant { /** - * Provide a set of variables that can only be used with this agent. + * Provide a set of variables that can only be used with this participant. */ - agentVariableProvider?: { provider: ChatAgentCompletionItemProvider; triggerCharacters: string[] }; + participantVariableProvider?: { provider: ChatParticipantCompletionItemProvider; triggerCharacters: string[] }; } - export interface ChatAgentCompletionItemProvider { - provideCompletionItems(query: string, token: CancellationToken): ProviderResult; + export interface ChatParticipantCompletionItemProvider { + provideCompletionItems(query: string, token: CancellationToken): ProviderResult; } - export class ChatAgentCompletionItem { + export class ChatCompletionItem { label: string | CompletionItemLabel; values: ChatVariableValue[]; insertText?: string; @@ -158,36 +158,36 @@ declare module 'vscode' { constructor(label: string | CompletionItemLabel, values: ChatVariableValue[]); } - export type ChatAgentExtendedRequestHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentExtendedResponseStream, token: CancellationToken) => ProviderResult; + export type ChatExtendedRequestHandler = (request: ChatRequest, context: ChatContext, response: ChatExtendedResponseStream, token: CancellationToken) => ProviderResult; export namespace chat { /** - * Create a chat agent with the extended progress type + * Create a chat participant with the extended progress type */ - export function createChatAgent(name: string, handler: ChatAgentExtendedRequestHandler): ChatAgent2; + export function createChatParticipant(name: string, handler: ChatExtendedRequestHandler): ChatParticipant; } /* * User action events */ - export enum ChatAgentCopyKind { + export enum ChatCopyKind { // Keyboard shortcut or context menu Action = 1, Toolbar = 2 } - export interface ChatAgentCopyAction { + export interface ChatCopyAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'copy'; codeBlockIndex: number; - copyKind: ChatAgentCopyKind; + copyKind: ChatCopyKind; copiedCharacters: number; totalCharacters: number; copiedText: string; } - export interface ChatAgentInsertAction { + export interface ChatInsertAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'insert'; codeBlockIndex: number; @@ -195,33 +195,33 @@ declare module 'vscode' { newFile?: boolean; } - export interface ChatAgentTerminalAction { + export interface ChatTerminalAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'runInTerminal'; codeBlockIndex: number; languageId?: string; } - export interface ChatAgentCommandAction { + export interface ChatCommandAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'command'; - commandButton: ChatAgentCommandButton; + commandButton: ChatCommandButton; } - export interface ChatAgentFollowupAction { + export interface ChatFollowupAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'followUp'; - followup: ChatAgentFollowup; + followup: ChatFollowup; } - export interface ChatAgentBugReportAction { + export interface ChatBugReportAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'bug'; } - export interface ChatAgentUserActionEvent { - readonly result: ChatAgentResult2; - readonly action: ChatAgentCopyAction | ChatAgentInsertAction | ChatAgentTerminalAction | ChatAgentCommandAction | ChatAgentFollowupAction | ChatAgentBugReportAction; + export interface ChatUserActionEvent { + readonly result: ChatResult; + readonly action: ChatCopyAction | ChatInsertAction | ChatTerminalAction | ChatCommandAction | ChatFollowupAction | ChatBugReportAction; } export interface ChatVariableValue { @@ -231,7 +231,7 @@ declare module 'vscode' { kind?: string; } - export interface ChatAgentCommand { + export interface ChatCommand { readonly isSticky2?: { /** * Indicates that the command should be automatically repopulated. diff --git a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts b/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts similarity index 53% rename from src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts rename to src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts index 3a4c675a96e..1c71b40e122 100644 --- a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts +++ b/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts @@ -5,35 +5,35 @@ declare module 'vscode' { - export type ChatAgentWelcomeMessageContent = string | MarkdownString; + export type ChatWelcomeMessageContent = string | MarkdownString; - export interface ChatAgentWelcomeMessageProvider { - provideWelcomeMessage(token: CancellationToken): ProviderResult; - provideSampleQuestions?(token: CancellationToken): ProviderResult; + export interface ChatWelcomeMessageProvider { + provideWelcomeMessage(token: CancellationToken): ProviderResult; + provideSampleQuestions?(token: CancellationToken): ProviderResult; } - export interface ChatAgent2 { + export interface ChatParticipant { /** - * When true, this agent is invoked by default when no other agent is being invoked + * When true, this participant is invoked by default when no other participant is being invoked */ isDefault?: boolean; /** - * When true, this agent is invoked when the user submits their query using ctrl/cmd+enter + * When true, this participant is invoked when the user submits their query using ctrl/cmd+enter * TODO@API name */ isSecondary?: boolean; /** - * A string that will be added before the listing of chat agents in `/help`. + * A string that will be added before the listing of chat participants in `/help`. */ helpTextPrefix?: string | MarkdownString; /** - * A string that will be appended after the listing of chat agents in `/help`. + * A string that will be appended after the listing of chat participants in `/help`. */ helpTextPostfix?: string | MarkdownString; - welcomeMessageProvider?: ChatAgentWelcomeMessageProvider; + welcomeMessageProvider?: ChatWelcomeMessageProvider; } } diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index bd580590d68..5644d00ce83 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -125,7 +125,7 @@ declare module 'vscode' { inputPlaceholder?: string; } - export type InteractiveWelcomeMessageContent = string | MarkdownString | ChatAgentFollowup[]; + export type InteractiveWelcomeMessageContent = string | MarkdownString | ChatFollowup[]; export interface InteractiveSessionProvider { prepareSession(token: CancellationToken): ProviderResult; From 875855a4445749034dfccd596f3c5234481c1e40 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 19 Feb 2024 15:26:08 +0100 Subject: [PATCH 1424/1897] fix: memory leak in code editor widget (#205488) (#205542) Documentation: CSS setting is incorrect. --- extensions/css-language-features/package.nls.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/css-language-features/package.nls.json b/extensions/css-language-features/package.nls.json index 5e9129c84e1..d6e25a57a43 100644 --- a/extensions/css-language-features/package.nls.json +++ b/extensions/css-language-features/package.nls.json @@ -12,7 +12,7 @@ "css.lint.emptyRules.desc": "Do not use empty rulesets.", "css.lint.float.desc": "Avoid using `float`. Floats lead to fragile CSS that is easy to break if one aspect of the layout changes.", "css.lint.fontFaceProperties.desc": "`@font-face` rule must define `src` and `font-family` properties.", - "css.lint.hexColorLength.desc": "Hex colors must consist of three or six hex numbers.", + "css.lint.hexColorLength.desc": "Hex colors must consist of 3, 4, 6 or 8 hex numbers.", "css.lint.idSelector.desc": "Selectors should not contain IDs because these rules are too tightly coupled with the HTML.", "css.lint.ieHack.desc": "IE hacks are only necessary when supporting IE7 and older.", "css.lint.important.desc": "Avoid using `!important`. It is an indication that the specificity of the entire CSS has gotten out of control and needs to be refactored.", @@ -47,7 +47,7 @@ "less.lint.emptyRules.desc": "Do not use empty rulesets.", "less.lint.float.desc": "Avoid using `float`. Floats lead to fragile CSS that is easy to break if one aspect of the layout changes.", "less.lint.fontFaceProperties.desc": "`@font-face` rule must define `src` and `font-family` properties.", - "less.lint.hexColorLength.desc": "Hex colors must consist of three or six hex numbers.", + "less.lint.hexColorLength.desc": "Hex colors must consist of 3, 4, 6 or 8 hex numbers.", "less.lint.idSelector.desc": "Selectors should not contain IDs because these rules are too tightly coupled with the HTML.", "less.lint.ieHack.desc": "IE hacks are only necessary when supporting IE7 and older.", "less.lint.important.desc": "Avoid using `!important`. It is an indication that the specificity of the entire CSS has gotten out of control and needs to be refactored.", @@ -81,7 +81,7 @@ "scss.lint.emptyRules.desc": "Do not use empty rulesets.", "scss.lint.float.desc": "Avoid using `float`. Floats lead to fragile CSS that is easy to break if one aspect of the layout changes.", "scss.lint.fontFaceProperties.desc": "`@font-face` rule must define `src` and `font-family` properties.", - "scss.lint.hexColorLength.desc": "Hex colors must consist of three or six hex numbers.", + "scss.lint.hexColorLength.desc": "Hex colors must consist of 3, 4, 6 or 8 hex numbers.", "scss.lint.idSelector.desc": "Selectors should not contain IDs because these rules are too tightly coupled with the HTML.", "scss.lint.ieHack.desc": "IE hacks are only necessary when supporting IE7 and older.", "scss.lint.important.desc": "Avoid using `!important`. It is an indication that the specificity of the entire CSS has gotten out of control and needs to be refactored.", From c28d1668eeccc0c4f895fea3554329b8bc341984 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 19 Feb 2024 15:35:05 +0100 Subject: [PATCH 1425/1897] rename widget: clean up computing cursor-box top --- .../contrib/rename/browser/renameInputField.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 4f9f1b15be8..d620226129b 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -180,9 +180,7 @@ export class RenameInputField implements IContentWidget { const bodyBox = getClientArea(this.getDomNode().ownerDocument.body); const editorBox = getDomNodePagePosition(this._editor.getDomNode()); - // FIXME@ulugbekna: can getVisibleRanges() be empty? if so what to do about it - const firstLineInViewport = this._editor.getVisibleRanges()[0].startLineNumber; - const cursorBoxTop = this._editor.getTopForLineNumber(this._position!.lineNumber) - this._editor.getTopForLineNumber(firstLineInViewport); + const cursorBoxTop = this._getTopForPosition(); this._nPxAvailableAbove = cursorBoxTop + editorBox.top; this._nPxAvailableBelow = bodyBox.height - this._nPxAvailableAbove; @@ -391,6 +389,18 @@ export class RenameInputField implements IContentWidget { this._editor.layoutContentWidget(this); } + private _getTopForPosition(): number { + const visibleRanges = this._editor.getVisibleRanges(); + let firstLineInViewport: number; + if (visibleRanges.length > 0) { + firstLineInViewport = visibleRanges[0].startLineNumber; + } else { + this._logService.warn('RenameInputField#_getTopForPosition: this should not happen - visibleRanges is empty'); + firstLineInViewport = Math.max(1, this._position!.lineNumber - 5); // @ulugbekna: fallback to current line minus 5 + } + return this._editor.getTopForLineNumber(this._position!.lineNumber) - this._editor.getTopForLineNumber(firstLineInViewport); + } + private _trace(...args: any[]) { this._logService.trace('RenameInputField', ...args); } From 47becdc1522087833766792fb84854daab7b9f04 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 19 Feb 2024 15:44:53 +0100 Subject: [PATCH 1426/1897] rename suggestions: add `dispose()` to `CandidatesView` --- .../contrib/rename/browser/renameInputField.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index d620226129b..292c37d6bf1 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -107,15 +107,15 @@ export class RenameInputField implements IContentWidget { this._input.className = 'rename-input'; this._input.type = 'text'; this._input.setAttribute('aria-label', localize('renameAriaLabel', "Rename input. Type new name and press Enter to commit.")); - // TODO@ulugbekna: is using addDisposableListener's right way to do it? this._disposables.add(addDisposableListener(this._input, 'focus', () => { this._focusedContextKey.set(true); })); this._disposables.add(addDisposableListener(this._input, 'blur', () => { this._focusedContextKey.reset(); })); this._domNode.appendChild(this._input); - this._candidatesView = new CandidatesView(this._domNode, { - fontInfo: this._editor.getOption(EditorOption.fontInfo), - onSelectionChange: () => this.acceptInput(false) // we don't allow preview with mouse click for now - }); + this._candidatesView = this._disposables.add( + new CandidatesView(this._domNode, { + fontInfo: this._editor.getOption(EditorOption.fontInfo), + onSelectionChange: () => this.acceptInput(false) // we don't allow preview with mouse click for now + })); this._label = document.createElement('div'); this._label.className = 'rename-label'; @@ -562,6 +562,10 @@ export class CandidatesView { } return focusedIx > 0; } + + dispose() { + this._listWidget.dispose(); + } } export class CandidateView { // TODO@ulugbekna: remove export From 372baec4acad52c05e6d051ac940d00fd972f146 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 19 Feb 2024 15:49:41 +0100 Subject: [PATCH 1427/1897] rename suggestions: clean up --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 292c37d6bf1..60049237806 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -406,7 +406,7 @@ export class RenameInputField implements IContentWidget { } } -export class CandidatesView { +class CandidatesView { private readonly _listWidget: List; private readonly _listContainer: HTMLDivElement; @@ -568,7 +568,7 @@ export class CandidatesView { } } -export class CandidateView { // TODO@ulugbekna: remove export +class CandidateView { // TODO@ulugbekna: accessibility From 11b3d6b71a638e8ac6b8dfa6cf756bd0005e3a48 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 19 Feb 2024 15:39:11 +0100 Subject: [PATCH 1428/1897] Fixes #205446 --- .../contrib/multiDiffEditor/browser/multiDiffEditorInput.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index cffa05225e6..1cb31624f06 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -92,7 +92,10 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor /** @description Updates name */ const resources = this._resources.read(reader) ?? []; const label = this.label ?? localize('name', "Multi Diff Editor"); - this._name = label + localize('files', " ({0} files)", resources?.length ?? 0); + this._name = label + localize({ + key: 'files', + comment: ['the number of files being shown'] + }, " ({0} files)", resources?.length ?? 0); this._onDidChangeLabel.fire(); })); } From 43d55cbf884234f8421fa07f59f2f0fe2d534d98 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 19 Feb 2024 16:03:48 +0100 Subject: [PATCH 1429/1897] Localize Inno Updater (#205279) * without translations * Update messages.en.isl * translations, hopefully correct encoding * inno updater v0.11.0 --- build/win32/Cargo.lock | 2 +- build/win32/Cargo.toml | 2 +- build/win32/code.iss | 2 +- build/win32/i18n/messages.de.isl | 3 ++- build/win32/i18n/messages.en.isl | 1 + build/win32/i18n/messages.es.isl | 1 + build/win32/i18n/messages.fr.isl | 3 ++- build/win32/i18n/messages.hu.isl | 3 ++- build/win32/i18n/messages.it.isl | 3 ++- build/win32/i18n/messages.ja.isl | 3 ++- build/win32/i18n/messages.ko.isl | 3 ++- build/win32/i18n/messages.pt-br.isl | 3 ++- build/win32/i18n/messages.ru.isl | 3 ++- build/win32/i18n/messages.tr.isl | 3 ++- build/win32/i18n/messages.zh-cn.isl | 3 ++- build/win32/i18n/messages.zh-tw.isl | 3 ++- build/win32/inno_updater.exe | Bin 464896 -> 451072 bytes 17 files changed, 27 insertions(+), 14 deletions(-) diff --git a/build/win32/Cargo.lock b/build/win32/Cargo.lock index fb521755690..18edefc752d 100644 --- a/build/win32/Cargo.lock +++ b/build/win32/Cargo.lock @@ -109,7 +109,7 @@ dependencies = [ [[package]] name = "inno_updater" -version = "0.10.1" +version = "0.11.0" dependencies = [ "byteorder", "crc", diff --git a/build/win32/Cargo.toml b/build/win32/Cargo.toml index cf3cc9de80b..3925505c225 100644 --- a/build/win32/Cargo.toml +++ b/build/win32/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "inno_updater" -version = "0.10.1" +version = "0.11.0" authors = ["Microsoft "] build = "build.rs" diff --git a/build/win32/code.iss b/build/win32/code.iss index f8d231f5858..fca3d1e9d9b 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -1519,7 +1519,7 @@ begin StopTunnelServiceIfNeeded(); - Exec(ExpandConstant('{app}\tools\inno_updater.exe'), ExpandConstant('"{app}\{#ExeBasename}.exe" ' + BoolToStr(LockFileExists())), '', SW_SHOW, ewWaitUntilTerminated, UpdateResultCode); + Exec(ExpandConstant('{app}\tools\inno_updater.exe'), ExpandConstant('"{app}\{#ExeBasename}.exe" ' + BoolToStr(LockFileExists()) + ' "{cm:UpdatingVisualStudioCode}"'), '', SW_SHOW, ewWaitUntilTerminated, UpdateResultCode); end; if ShouldRestartTunnelService then diff --git a/build/win32/i18n/messages.de.isl b/build/win32/i18n/messages.de.isl index 6a9f29aa9c2..8d065e6c10a 100644 --- a/build/win32/i18n/messages.de.isl +++ b/build/win32/i18n/messages.de.isl @@ -6,4 +6,5 @@ AddToPath=Zu PATH hinzuf RunAfter=%1 nach der Installation ausführen Other=Andere: SourceFile=%1-Quelldatei -OpenWithCodeContextMenu=Mit %1 öffnen \ No newline at end of file +OpenWithCodeContextMenu=Mit %1 öffnen +UpdatingVisualStudioCode=Visual Studio Code wird aktualisiert... \ No newline at end of file diff --git a/build/win32/i18n/messages.en.isl b/build/win32/i18n/messages.en.isl index 986eba00d3e..a5cc5820154 100644 --- a/build/win32/i18n/messages.en.isl +++ b/build/win32/i18n/messages.en.isl @@ -14,3 +14,4 @@ RunAfter=Run %1 after installation Other=Other: SourceFile=%1 Source File OpenWithCodeContextMenu=Open w&ith %1 +UpdatingVisualStudioCode=Updating Visual Studio Code... diff --git a/build/win32/i18n/messages.es.isl b/build/win32/i18n/messages.es.isl index 0ba4d0c44f2..66b7534a207 100644 --- a/build/win32/i18n/messages.es.isl +++ b/build/win32/i18n/messages.es.isl @@ -7,3 +7,4 @@ RunAfter=Ejecutar %1 despu Other=Otros: SourceFile=Archivo de origen %1 OpenWithCodeContextMenu=Abrir &con %1 +UpdatingVisualStudioCode=Actualizando Visual Studio Code... \ No newline at end of file diff --git a/build/win32/i18n/messages.fr.isl b/build/win32/i18n/messages.fr.isl index df140418625..348d6be0049 100644 --- a/build/win32/i18n/messages.fr.isl +++ b/build/win32/i18n/messages.fr.isl @@ -6,4 +6,5 @@ AddToPath=Ajouter RunAfter=Exécuter %1 après l'installation Other=Autre : SourceFile=Fichier source %1 -OpenWithCodeContextMenu=Ouvrir avec %1 \ No newline at end of file +OpenWithCodeContextMenu=Ouvrir avec %1 +UpdatingVisualStudioCode=Mise à jour de Visual Studio Code... \ No newline at end of file diff --git a/build/win32/i18n/messages.hu.isl b/build/win32/i18n/messages.hu.isl index b64553da8e6..ef3862ad35b 100644 --- a/build/win32/i18n/messages.hu.isl +++ b/build/win32/i18n/messages.hu.isl @@ -6,4 +6,5 @@ AddToPath=Hozz RunAfter=%1 indítása a telepítés után Other=Egyéb: SourceFile=%1 forrásfájl -OpenWithCodeContextMenu=Megnyitás a következõvel: %1 \ No newline at end of file +OpenWithCodeContextMenu=Megnyitás a következõvel: %1 +UpdatingVisualStudioCode=A Visual Studio Code frissítése... \ No newline at end of file diff --git a/build/win32/i18n/messages.it.isl b/build/win32/i18n/messages.it.isl index 08248c4ce1b..bc23825844a 100644 --- a/build/win32/i18n/messages.it.isl +++ b/build/win32/i18n/messages.it.isl @@ -6,4 +6,5 @@ AddToPath=Aggiungi a PATH (disponibile dopo il riavvio) RunAfter=Esegui %1 dopo l'installazione Other=Altro: SourceFile=File di origine %1 -OpenWithCodeContextMenu=Apri con %1 \ No newline at end of file +OpenWithCodeContextMenu=Apri con %1 +UpdatingVisualStudioCode=Aggiornamento di Visual Studio Code... \ No newline at end of file diff --git a/build/win32/i18n/messages.ja.isl b/build/win32/i18n/messages.ja.isl index 9675060e94a..ef10366b469 100644 --- a/build/win32/i18n/messages.ja.isl +++ b/build/win32/i18n/messages.ja.isl @@ -6,4 +6,5 @@ AddToPath=PATH RunAfter=ƒCƒ“ƒXƒg[ƒ‹Œã‚É %1 ‚ðŽÀs‚·‚é Other=‚»‚Ì‘¼: SourceFile=%1 ƒ\[ƒX ƒtƒ@ƒCƒ‹ -OpenWithCodeContextMenu=%1 ‚ÅŠJ‚­ \ No newline at end of file +OpenWithCodeContextMenu=%1 ‚ÅŠJ‚­ +UpdatingVisualStudioCode=Visual Studio Code ‚ðXV‚µ‚Ä‚¢‚Ü‚·... \ No newline at end of file diff --git a/build/win32/i18n/messages.ko.isl b/build/win32/i18n/messages.ko.isl index 5a510558bbd..f938c75e289 100644 --- a/build/win32/i18n/messages.ko.isl +++ b/build/win32/i18n/messages.ko.isl @@ -6,4 +6,5 @@ AddToPath=PATH RunAfter=¼³Ä¡ ÈÄ %1 ½ÇÇà Other=±âŸ: SourceFile=%1 ¿øº» ÆÄÀÏ -OpenWithCodeContextMenu=%1(À¸)·Î ¿­±â \ No newline at end of file +OpenWithCodeContextMenu=%1(À¸)·Î ¿­±â +UpdatingVisualStudioCode=Visual Studio Code ¾÷µ¥ÀÌÆ® Áß... \ No newline at end of file diff --git a/build/win32/i18n/messages.pt-br.isl b/build/win32/i18n/messages.pt-br.isl index e327e8fd1a0..e85aede3862 100644 --- a/build/win32/i18n/messages.pt-br.isl +++ b/build/win32/i18n/messages.pt-br.isl @@ -6,4 +6,5 @@ AddToPath=Adicione em PATH (dispon RunAfter=Executar %1 após a instalação Other=Outros: SourceFile=Arquivo Fonte %1 -OpenWithCodeContextMenu=Abrir com %1 \ No newline at end of file +OpenWithCodeContextMenu=Abrir com %1 +UpdatingVisualStudioCode=Atualizando o Visual Studio Code... \ No newline at end of file diff --git a/build/win32/i18n/messages.ru.isl b/build/win32/i18n/messages.ru.isl index bca3b864a5f..2b1d906e55d 100644 --- a/build/win32/i18n/messages.ru.isl +++ b/build/win32/i18n/messages.ru.isl @@ -6,4 +6,5 @@ AddToPath= RunAfter=Çàïóñòèòü %1 ïîñëå óñòàíîâêè Other=Äðóãîå: SourceFile=Èñõîäíûé ôàéë %1 -OpenWithCodeContextMenu=Îòêðûòü ñ ïîìîùüþ %1 \ No newline at end of file +OpenWithCodeContextMenu=Îòêðûòü ñ ïîìîùüþ %1 +UpdatingVisualStudioCode=Îáíîâëåíèå Visual Studio Code... \ No newline at end of file diff --git a/build/win32/i18n/messages.tr.isl b/build/win32/i18n/messages.tr.isl index b13e5e27bd2..5eff39c24a7 100644 --- a/build/win32/i18n/messages.tr.isl +++ b/build/win32/i18n/messages.tr.isl @@ -6,4 +6,5 @@ AddToPath=PATH'e ekle (yeniden ba RunAfter=Kurulumdan sonra %1 uygulamasýný çalýþtýr. Other=Diðer: SourceFile=%1 Kaynak Dosyasý -OpenWithCodeContextMenu=%1 Ýle Aç \ No newline at end of file +OpenWithCodeContextMenu=%1 Ýle Aç +UpdatingVisualStudioCode=Visual Studio Code güncelleniyor... \ No newline at end of file diff --git a/build/win32/i18n/messages.zh-cn.isl b/build/win32/i18n/messages.zh-cn.isl index 8fa136f6d5a..629bf9ea401 100644 --- a/build/win32/i18n/messages.zh-cn.isl +++ b/build/win32/i18n/messages.zh-cn.isl @@ -6,4 +6,5 @@ AddToPath= RunAfter=°²×°ºóÔËÐÐ %1 Other=ÆäËû: SourceFile=%1 Ô´Îļþ -OpenWithCodeContextMenu=ͨ¹ý %1 ´ò¿ª \ No newline at end of file +OpenWithCodeContextMenu=ͨ¹ý %1 ´ò¿ª +UpdatingVisualStudioCode=ÕýÔÚ¸üРVisual Studio Code... \ No newline at end of file diff --git a/build/win32/i18n/messages.zh-tw.isl b/build/win32/i18n/messages.zh-tw.isl index 40c5fa92d79..8ed1f5a5061 100644 --- a/build/win32/i18n/messages.zh-tw.isl +++ b/build/win32/i18n/messages.zh-tw.isl @@ -6,4 +6,5 @@ AddToPath= RunAfter=¦w¸Ë«á°õ¦æ %1 Other=¨ä¥L: SourceFile=%1 ¨Ó·½ÀÉ®× -OpenWithCodeContextMenu=¥H %1 ¶}±Ò \ No newline at end of file +OpenWithCodeContextMenu=¥H %1 ¶}±Ò +UpdatingVisualStudioCode=¥¿¦b§ó·s Visual Studio Code... \ No newline at end of file diff --git a/build/win32/inno_updater.exe b/build/win32/inno_updater.exe index fa2fd26a466dedee7755c873a88cdb7168c38737..b87cbd47f24f5d235de44db8d5ef3f2aa180bfdc 100644 GIT binary patch literal 451072 zcmeFadtg-6wLg9)8DOHLXTTsqqYOG#Qqd+7HI$$OBx6vB!Neq1j8QQLM2KMqqrx!p zOvdaUC#5ar`fZhJ>uv45w#ADkfEs5AG7(Uo6$BN;*BJ+EK!xy-`F+;jXI@F5_V#zb z_n&WI&SO8YQIx}tve0&7F#{QDd4e$aaF-Sg(ncUtec&)VpkXPrIIT69af^}+eG?z`%obI!8q zMZdg!=7l{+uP%xGj{WSplH+(kvT)qg!~A~TRE^)SoqCer^QL}-d(Y8vB@f}A|M3-|%d+Vv}zAY&$mZWKhL6Wh2$9=K5{n90dvkm87CS5#8 zlDZ8%_?qv484DhB5k^>{=QK$gj2r#M?viDQKqA7@BzrSYNNFhpzsGMBD9!w6kkrGU z21^a?{2}!c7%c5BlBC|QK~hC(41e>iA0%brsQ~fzmtB-LKkIO}fb-IBDq6A{$*0PdF%4ibNMAml2MO!~%z@E*id-3R)m3tZK> zpz&TriM9f3L};J%OJ~2T@xHn9k&xOh0SA%;_os1B`m;;YZuzetuJh7uo*?a6; zX0!#rGOn#c$m(2NJRiyO^B-h;j-4e>-YRWW%{JwPg+-JPC3ea`DcV|LPFoO)lqQ!` zCy$~&K#_^^uEXp1FHZ@cYCh&cmWRi65^Nm&oDKdWvr$J7T9H=2@9|*oG!zjI$ng=;-IcFV300juPbrXNP$|eR{Z8 zeW@BkAUb?=9)_u}0?$2Blgq5O(PK?`&s>946VFzujF;Fu&Y{tT#yL{-8q>GM$hjUl z6}MTUU`yZNEFPSl8XPaMQgzx)irXkTEzxUJ(?l0$Mu!JbbUZO&CVEy5nvg5ogBo2I zvX9td?GJ4RNp3gV)I~O<(rbM9Ql&TDd9iXmuevXw>rEU{>L_)-u{?SkMv_rk3;?o} z#ioqLboGvt<*9&FrM{wA*PkM*1p!H^v>rIicPPeh!nadv)FaM5M`2!W+aUx#0;FnJV ziEInop%FIk^iQ*y0fe^yL^@zF5)#d5N2Xd~%VsleMz+XilG_ipPP3)2A zF1HUU{E@sYSU6K&7BF?A`^QF1AzTS8^)=hGbOUW1mMulLb8wwz8?HWW1C2=nX5_fx zusn2{tpL{|TW-0QJwO`->}eeK^s%qZ1nkjIzc=KfJ_7zp$cywRIaD0}p~>)fnKnhz z^7azRB|w(jD{SdMooh=IuupiI!w#s+In>iQ)J3jqJjc`JUj#hg;Tp^hx_(d@6)B@m zn?>{e-6)}qbEz;HG}IE1yZcH|9XJ38 zy|x$e%r^0XR^xw-V>E_X6oSX4xX~XTNpYb+Jd!FiL_GeEg7AmXXZZtvBwd*P5c&u4 zByAA*Gysv+&}AoG$wxhSU$xP7zB<=7Q*Ix$=-XoHIg)BE0oV1B9H=`VVr*7ZtmlML zZW#kyu&>#MK1t7u4u1`jib&6#AxXcd66z(ZB+Ze^qQh6~QHM;DR430LRC{$X^=kFk z@{%IkOg7gxw?2S!=SWrB8xhV2{pfxkRs~>dXMq3RU;yv209dopRR)e@1SOa#7maif zl5vh*yJ0FNqVZ*(h!~ID@)nv7*exL1Le74{ikqpA5jPA;*tcPW5pO6qz0r;o7WRR9 zy;@_eik^p(l*hPSe2mM*B`5m`LTDk$8UTyD{=>V9r8h>Q`0#(iNO5``Ri35eXpSib zLDwUkWwClAa~MMo)0k_Er4VscPxvjA2f=kKz?*5aOr$VZzUM?bi@2^wNTxbYeau)H z9YH`Z;Xp6pKrcMm2g*LO9Y9YdphptWN5Fk!=&bi3MEml_I7ZP;WgM%n(Lnz+2sM(+ znGo(-%`n6O9w$V`o8jM}5I|a;4C#*rq(uZN%eQ@RVAXtEe|Ya9crPM&b5Rw0(&oi8 z$f+n5^sAFVUt}v0pkD+IVFAb_hqIcG>tKi2+u@C%hW}Bpm&9p<<`rPF9@GvnOi4^qdF9bA49aS7TOR~p+vdtT_A4&#iwCF zSk})jmLgVlhEWNclt3?vF#Z%kaF8UWrd^e>;*}DO7vJfD$qCagd z+nw@^)Ul|b#|gPbP8oCGn5fknre0q#z-F% znd@K1Wz(991>o$jfkd^uY|a}dAdzbdT0QO3*CRC`ViJzd5(>z6U4s$)wLE_?ex>F* z*?CR_SU>KAJp8^lSSRlaOe8~YWbEr%Gz5krH`qizGKMciv*uh{-;GkinoX73Z$9PH zp(Vgm=-n^3ocJypZC+%n7ztLs$o%?Af+8E`MB4pAe%6!e5S&)46$Jpe0@y}u)fk4P z!$3%q9^s@;L=GgTKRhneA7yV5cn#_(sH>|REI%<7mO%7Uf#29dDgD`=mc4T8IE0>_ zk}g0jrzEhnB%zp2ML|Qwm?CA!f?*7U5@!JokwUq>3vIQa5A4mK!r~qEn}0DmCh~D2}npIi@(qP`KT7PWV;Cp=vwwC%3Q?qpV1)6O}@&k45X|A|FuH%0CGz7{p2)SUnj=@Q@)&2-`BL#=A zN5~q2aIIv;h#B>Xkzh6>fbJH;8}7Wjfl894`CrR!qyV{>shKu5$zCVFJE?%>u^C2$ zO+%?A>>(4IK`Dx$;CfL6d&t6OWFo+ZXe)ati_OSZU$%J=g@6k7kd@7_A-Vu>i`YYB z*o?7wYr@+Y_E0XHksmHO5sen6%gXPO<<%XJLWYc^*VpOwE4;pODW1Zl`%y62j9wk~ z7=kaMV6vRO+Ciyk%!SkNN{CNyoCwj9t1gz*JHMAYbz3UCY&r63KGi{zUiQ9-E4tPm z{uLg8t6X)A+CZ$a@V4mr3X~q0P-O2TsGCC}8c}xIgd~D_qj>Wom!CFaF(ssC@&+qz zu2emEvJb3)tS}Y&K5Rga>Br8R{=&q06Lh{NGLz(^UozPwV`!3TCGjFuE_qsxI19M8 zbrH8Sg^ZEGN+`3`d8xAGq~!c2dR@Hc9g)x05KXOxo-JuFeFSC+#a0NTzIG`t28TxT zZSmZ~j75TzORu7S5jJ4>;$z@JFvX-shKC?Hf%x!2(0u~E;ScCFj>Pagu~7lbl^H;4 zBOaF|4mjJRo!F0nx-X4V-O_fIkLKvgLDHLT7|&zCSxxK5V-$g((m=vJ3pa_G zZR{hpEDP_(mBa0%Yg^QkOpC5!AQ zJ*!uqk1==74x_|AV}E%HFYFIb;~Mh4fGY+~G~|08PtvzV65HYTJxeJ{^Jl5U5hk(a z^gfIDujDT``=?|fA0YXJ;P<_VCx|UPttROBQx*n#y&d!D<*m>}L%t_*<*o58#gl)E zE#zAk3#znW>fn2d!hIq&tx$^8D^a4K*A?=;tk=VQ*(hBFAM#o8Kr2TfA4z%3bTjGGWrY zQEsjHQL2)}O0BRpBp75?A00_8wXzUW4CR6p9OAwAjnW&0hw>R!nzEN`gzOmG!&WpP zlyy4kx}pi!4ZcN`PV4wIUGfH;clq0>^<>I?_sIthE}a!X|z z5YR~&REMZ{VLqN&7Pkn<-WF(6erVPq?Ed3*DDdkb)NUD-U| z+332@We;cIHRPiN^cV6`B6unoa-F}r?(Vq@#u?=HsYd0{eHlU7`wk*S zSNeGYqD?!|CP8JQ7@{Wn^K<%RZJg@bh-R6O$RNaNh-gd@BIVNlvLvxwo{%sNGc_gTe7I&}73mG0Dbq~dkQ6$9h-RfL}wuMKFP8sNKxhgo=9>KF%um8Sp_=I*f5TG@_$yc$IPO1kK{4uT~!CG1!opV|Jg>_Kehb4!Nsy}%j zt(V_UGj)^7nVh}luy6c%G*%*V^%Y=sF<(@7#QS^m9@l0B=4oslFo0k6r! z5X_zyB*pkGi!PiMt*i%|X2-O%>M|>{3Mg>A^?>eU6`d+?F65yv;a78v1Ngly)IMa;ym1)S%{8}bk-%7wULtT z_6GbM^RCNPhgAt#0Epr+0}RUqUjP*gFyxj6=*RGH0K)iA3RX*X0G;E5Ul@<>!Mr7C znd}^^Ji_NKdGWgTu~IYJp;w5ZgQ`ST; z=e?D;y`&9euac&Pd}5~~bk`XX8p8~Y zny2EUW*|%sgfpSC8i7%vX8uz+R2}&kZ9wZzUat*Ld1&35Sr3uHa2^bWOMDFgORYAl z7IlhAz1hr`KaUh_MK#ivu(szZ%UjC+UVo9rq*i7!-?IqO+kIhQDO{S1BqGwk!i0pZ zbmk;MLp=>Kelc*%V>BY#`z%rmy!i!Z#mH=<0tJeQWv~?k5tD?L-RkEYzbe{Jutt|t z8;EVZr|r1+3~Zw`n>ngowWsIcjpGL9;6FjQT3QW+23WPRPQHiZl$lh=On8$)TsaD4 z=m@&G+62|w+ z-or?(KGGxBg=FuqDf6TrR$~E6g7~zX(2HPSb+S7!!AA_v^~17gJMp-3>gNC+w7+hF z4M3jHU;J&$5X4!QGYi6*^;s1PyTExE*P7HeA_vyliVSSUlemHy>BZ+iXRze~L`S!yrO|e3GW?u|f@wsDzpO8zzKlQ?s;BA39Oy_(-{G27y%D*Ald9oSIC ze_NL9JqkUFWq~f%l4bzA7Pklr^g|pza6uZx8Szh*vsiUG)hNWF?46F@U}74{nKD~$ zMf3g3iDCOo3&i;R23&YRwYaiC%tP2QU3YdACb|Q)NZOSPKt`QZngj?*FQGo{BU?%v z*m3RS8RSxxydExlXA#uytZ*alKsEVh`FUYz*Dps>yQpMU%$_;qs6;f)HJ36@MDs)+ zK*ssXs2J_1yLGP`(G6Vt(AC^d>Q=UbmhxHKQ@G0Q-K{>7PpZ-X%^bTVTvoWw%j2Q; zaw{z>Entp9R%!QF7WhjW1fweCXd_O9|3DxqsPxAw8+4rqXrqn^Ih+D*B=RVccH|+T z&0WCV$@&;N(Hx`@J*52wO-e=@+MGHS*@H>&Kt!_+;asmqS&^%Rv6N?)y?;DSmY<0% zjs{L=7@$)4L&82sM2g-&vR_zS_2(ES?SHF{w119i(*8&E(f*ll8W0gVE5z>r5-t7{ zLd2{?bN;K;)ZB zE=&d{2B{dAya!uX5MROMD<3!b6Zx*i-FbmZ?Xfr-a4BtKjvJ|Qc3d0uCugBBQO<4Ayk_MAWkwd0i|QEP=H!FPxW)96E&^}WT1QUlKr3E`QKB;E3%cHlo8{6!N5X7fMNn?&UaiLeBMr?)m>Y=wh%D16DeT zjdDu`(QGO!hlPRbC*k#oO3nq|qH?~P7lou|Y7uy|^q>*%LOQ{tG z05nE&=0w{`yNNXzwh&!=;Of50dlI%LpOSUV^MEXNxN%G8GNW z+2WjxEdaz=G84L;JjxDUW4Xn7S-%;ZJB#gOo5WlUF8?P`Mr5bz$VTXUYE88vkh3r6 zJ%;yEi|l3~gz|xOnagzLH-!1lwv*VM{dRfT~AUpE%X(Q+P-2+8xvHvB-Z7JkWhL>pprQ z_{HGa>ojD-eu5~Beim2ig^+2g?EM)~t=@Q29R)gsDI{UScfL=>F9;%gWZmhz zBt}+*?FzmWLzV^NHwnvvm?X^Nu?af@$*JBOh&;bPA=2tff)Lf3S>>v`KDrCE=y%*J z#zX6AfuIV<(0Y)tASc`4P5<0@NCl%@0Kg4c+Qs`YDHS#pUjSGT7A@wePMT}$Gxo%v$nU~h*>$7F0O7&$HodtZu?C(NU|%Hy(TXM%*Kkp&=Fycio1Qt-WdoF z3FO;Jyi3?{fGcj8s>Y}s^I~4fwWtJDy!cHqm#rikHt;ut7M^0{_7~?UG{6+1yi&_s z&PB|f%WA-}tUXbK-1aR-u$}|Vh8FJdr~O&aJBH^~E3Ik`1Vb*fL>^F~B$O7mL@}>L z8T|(Zx0b4Ivswcdu2E@n%iCYTz_SaNIH=BXF)%0VRlN<{Rt(IL!%7(e?Rh=X4A;5Z zcjFCGWQZQD3caJmg!r?1qLa*WTRt)~NABs7eKznUkPSzGBxHY046L*o6S?J|phi|= zq;)r9z606+7r$TkCWVGDgb^&-wYssa; zGF@-QMvpS><6CLw3Kt}9xP|kY-0~t|(QTx#p$KD&pet@t2XrLxd!lE%&K3q$?L&BQ ziX7Ooqq;L)XZv|l?bGW(s1Puf7VQFfQ9yp@C)>p@tzU|Pq}?@Hm-L*Z$q}jmbQL4& z>PR}PtoHjF5GF>S6d!#;=ZP`gkgpJFjx>|3vf~N=@kH8JJ!b(zv!knE#eSY%Fl1@+ zWQ3ANm%Iu~#>oDxLooBcR)^TaY?a_v3A3c?qbgN&U71PyWsacw4b)#jwgdYyyjDb$ z-&$}eA1ra-Z_sb*w5|C-zja9>2Q16VE&EYmxE2Xo9hq{wPX&XNJ615U&{jMgYt(Axe<30flz z3E?kjjmv#*g9X{9uJ|)wEiaTrj3CN{Zqc9U+BlTU2|n%O%Z+G0RYlk)R?BuUUx4oG zSWQK__Q5YWE=VD!R5Xhtd8sZZp#9v8ig{I=1#dL_GvOX>rzUE@JYQgOxtXV=+|hPQ zr@e*ZGE&qmURjwMTE69Ya6GMVPv{82oKsnbefPs{g0ShUeR5AGV(jlPr-{KNm za^qB&b-kcE&dCJT^)I_?rYL(iq!>#oJ6wk1)aBg(=;%5LxW}5zQQu~~;?IiBPqa(!?UfzJUDGCSn+2MdhE= zxroA+0yd(sR@~LeCT^j{oTN%9<5gwRTWP1Ex}2t5>{zs&(ra5_UnNT8re)on$tUMZ zG!3&E=$|HtfHJUcKliOE^K-RtVQP<^i=?~) zN%o#VQEKOlgeopF)D>i+Bb@`4?S+0_U&1S+$v7$_$pD|{cxB19I^8D1?I6P6M=Ki! z>rCg1Xc;CN>^Rn)sgcM30E-hjSQcOayofqfN1<9{SK9~`>T*H^st0jI+XIw-Z3w#i zdK3I@9-2^Nc8!cKH1m~5tM)+$Z#A)k69AChPqLvxt#QtuKUYnY+ix}Ibt{J#WUO;# zhiOrC!Eo{zxB^&eF1Wj)p~+hNRyzGP1?;`(!Y1u=uwbAB zD()hzOrT!2r`1uUZ7nenN$ch#e;Xeom9+J+)DGHm6li}zif|XO6x58OVAF27IzDS zvZxy?v!M{=N82+Iq_y=D!Gi7z)XG8<-sD#1ZzHat{f_n&?4@~WpS{Y8yMY^ioiS5` znB{>e2{f{y6Un4Ts(cJXDU(~SrbT?N4QfC85t@Gq-eY;?zN>PHY~NzpQ_ogS**?X4 zw0%DYRBWgA`*^0E+E51RQOiTJm~jL^5qb6j%AF}#lVD%rT6jGtD-qWEIiy5Pg%St? z?$5%dD#5~sX9?EDM{bCWI9yJ|rNkVdGyDiH$;+wh|C`IH`B+X3zXpqhAXxhQ`+Zv)Ui$EM;btCPYcHYPMbab z$w15tw(ka2-SGhsiv>4^=#{a`+*={1quXd?`$%%Dd_=?9 z4f3Tlo18q}f+ArGwwA)(#LgvzfRwH)zXyE8TX*vo{bNWI77Swhl_WMj?wMF_%a*-g zqnsF)sP4_*vu+dgW1(}N8A}7yt@=o-Ax(obYKK^>(ES@L8?-wx2W;l{E)?yNy^rdt ziI7Pzu@Wogi0)&3J9rw{p?0GrWWg0`HvtR!JjM-sT-P!)?C_AR!fUSnwD__whTW%SLN|lH~ ze*D+q@-0!4Hvgg$l|vFn2-lk~gWMX4 zcd^ySOo-UBKS3wuZ>47OfPsXd#Q1#Z3!{0C%E+^a6M*<8z8+Z?}YL zi)I)kfrMK|Y}J6w1)Q((`FdiBg{)2R0y3$OnD`cfiV-GEL^vb6RUGX?tk{GyiMbLN z5G&_mU4zfWAQ$*d40r9%M`@nU^A{RhX$|FmWKoyf>FOgDSy)$u$CXg#SS99ZB=%`q z(oWVA&DTlVyzcHl5g{JA9wtu)8d7$JBJq7wQapfz}Ma>Gi`rir4^L+HKY}m zGS%0OU?NW<3Nc_-_Zcv;VIhoIg0P8LN!T(+0hj_f&a<-yGTtHuDmtC&mq(95{i|MVKR{j{1!o@hYvD<5H`S(YRiTC$8<_4x50sJ&os5eE|eu zH31v|4}PPEaTe}3HplEpPa+&HcerX7!8YsX>_Z^s;2Dupz>%V(Lm1i#u>TG^;vU^9 zH*Tq1fap%#rP(SsUol5M^l-Ul88ryc4r};bREHDEjKElZ5jhu_{{u3za;`X&=7vAT z%%dsx#O@ryHqK~ITu>sI2Mj+gaIb_$7IK(5J0j*0eiRj}%Xz*0Je-{`pk9tR(Qo>B zT#FBL@eEHN>NzsExM1TV9Eq(`T=c>TF@Ofl!9Q&gKR?L=l@N&}*}Ik6+R3>nw_){X zof~hA14`3$01EgR^aB#wVo4`*dwk)blNJt=m<%@}##Rwx2h?Z_6J@qinIaWd<4R`+ z_ggYT!Ou+@``C}!O|a?btCeQ$(~r?QxRUEQlD!jQNQI7_s=S3SN2q+Z-;HHWCOC+>S3eIpEH^Bimw2Z!m)1L5k#3UabA8jXgtPP39 ztfzwco|$NSH^qDj9fz};`JBnCJ}v-+g2>e)94RD3gO5ZAku3hDX>azDrvb>8SS$#Z zW_7aHK>8^6c<4{I=M~4v_6H~_X?)?xkF4;okyR&JFyuJuarh;pg zX}`uuM$MzB1s$Ci;ij$_iEGlJapddm(WV#0TX7dH9Z(F^BhpFRQ6To1NCepSAwkK?PoiZN>>u4 z|0-VklcatUzt@+3wGaNx6el6I)^NYb8CBa?adnN5PF z6=H^`%>{)k?p$s_u1GCSpOp?gk5)QKnd5NKVn*MbB#(F`afm*VZYzd1L@4h8u8|bR z*X=3WnMqbmik=dh1K2eDsKv-Ty=^XGbxQTw(cRS-t)QVZ(10Pf&`mX^tauSElS9@& zyNs0pL-_0nBX7d33I}|i<{+~(noId;7zS^(=5`>qFd`nKZoV@Q(1KFpCN4357(`@>hUMkFra*;rLSjD-~fB z$PWcbW`;|_M??!-xPm1e1!tD=N9N+EXth8(0oAOSzgUH3+6j*@s?aDC*Wghy%J zQnkh;``$!iJ$jVix0On(F|iXNhlMI+p-55uJZX$TxRUN3-@9P6fR-xqmu9g&Ra$_1 zWEfQ(E&weITd&=jtj5Y}%(T&{2Hqsm;V-~p=(&m>46)9Y$XHb#-O3LWI5#@{c|?)P zQo)3O{64wm9>N0;ReB%AL5FCpzwpAAy?}T%Mad<-0%rN~61;{fJ^YIjP>tUuKk*50 zgB>j##@Z^a^moF+v1XR)s8`*!(EI_O{9+fRKY3}5#+Z$9FXn~^tcC5;oobJh@2(rG zP^ISBHmY#|338gb%R(G!gbMh(g}4cgUNScM+XOZ%Smn&;%Fyl^(XA>EQ^nrdZfr{^ zHL-nzBv0^%uxG56JlFrNdHd+ndrswaulew$XAC3XYj&G>=stRdG@9-<%hNZa`{^B% z0nhTMBpki)66kbIbjkI9n^0;RiB|dF1twYLEPrJ!I>^6*AQ6CS6Nm%Sc;|HF+m9#u zV~$!@nI(*W{fRl#bQTt40f(zXph_goOX8O)n8gh%hH?isQvOAj#{OYvcU(n z?@A(0q;S_J@D$?P%qXcSSv>8>Z3qwhf&%at^8H;r;7N-=eWE{Y@O_CVLtV)C72OT& zSUDJ@cJw`h#pH3<8eM}R10v_Dj#}KwqwHfKMQ0=foN#p%Q#bB@S1(;af-K1c?|nqE z5D7m_o9FGwdy6*htuC9#w?lTbUTogmieI-9$gPv-;&}2UobD2XO`KP5nxq8s#mVZf z!>0#iaU2j2YD@v-9s;CG`8dF)6o#hQ0S`C8Q1{YQpDeMMcuc{WuS0LfiM1Bqy%Ylb ziiIPQUS`rukDhiGz4YoYbLoY;nvyxiB$Z8B6jD4$K!1pL)Z$WFj}(fRhYVGp)MLmj ztSr-Cn&<^4XUgOA@cU5ZdHuare}7SbU#h>8h?2()T1wK*_Y_{3@)`x@C;{9yDAZC) z@GKI@9gd4YgLHMLJFac{x&Gz&Om+R1ej8Y7eak5;WS@=8AC5) z^_MK_6e~hh8xI+(WaA!E?6~4To{IFR4N3_GXm7(e3*NwvOzl#K?hYI<5uKl&G7m!y z9mpMc55mvn4h-JH*WkE2@ExcGRggO{b6en>QX6x#-8(`UwH`ovoSb;pow-OJq z(JEbb?$c|6PjAeDy4n)5XA&gxP+)Wy~I6tbGUyUdG6A{;GVl9 z(HAOb@N!ik#68ofoDlbh6;HhX@hIo;LuGKYf+BDu+Qf|r1~(#g-1dtbRRHH$dP&)e znd~kOGlkyna9I9dOhQAG*=Yfw=x5HHoSEkTabxdOl@xKiUaXn;NO*i~) z;5-lK03~mEO~&)TVb&?=E7ebT3BqyaKe|tE)R_kJ-SI+j9$k{Kn~Lz*(qUH0sAf$> zpR4b|z^%!Ga$vd_PuL{mh6%&AAHmuqFJqY&!;DNpG+T3EM_g?;wnF%8ii83nW?;ml zIh35bsLj|Qi|Gt4<_RTq1u^fCql9K(2?c~XpuJ^>9Q+F^A;(NMMa31S3$;TQb_)p(Mf?$ABD{b1oA!Lv#lU&`A zU2mW*9({@-)NrmM%PsGH17-VLTtp|KZ=XWn3bf@G#uUp`v>ZyB4`bialn#ntutf|bQmeGd$BW!lxtIEM=po1We6nSFnh38OeC zSf1B#?18st3H6;9Qr`` zG_2(gCUUZ**C2@73oY7r-N@)5>m;v0Knw#Zl`a}pUj`mf|1>VKIv*j{%S%^d6bbTdNC-jGP|;oK+P zHC5$K68d3wI88TxyuFoMg)#sjHqt?CMi8=kDNNBDhKJjIO;{N?xPMojLX|_be=PACF0FOH>7oq3j66Pe!fW^ z%YNiW<}MLMITt8_v^wQPhVyc{V~29w>KvsUzr+P*We`6 zNHDYr454zE6q;ZWaoEbV#{c9)SzY0woNlb630z?LP<7bCA%wzCH=(Au*9Go)kB`ht27oocTP*j>Dvy|g$^5Zm*qb=HtK)f)M$&Zub#x?>2 z{%JQ`l>I%}ptH`<6(8SNPn*<(3r_vF;@B_OS8Azkd{A!bA|#bsfSmA01T}V(WnKNTo1=Wl&>6+>y}conbMS-Tt~ulQ61oC-RwPOzsxoX zdr6S*xJR-nGbMkufpwR-BFKDXok8@U>>Y{X@&a|r4U+t;AiC3}36*1A!!gta8H2e= z+N>@h2P@M32RhpZVxAoQh>vhu9mc^!aoWbfs>HclylNB^!PaW<8&D(h8;shX@P)8z z$L+v&r0K^yZJaBJIjpddHeL|9F4i8<7@yQ7?7hUW$U+NyZ|wl~UQ@!}OVWU}8seO= z_in}FAQ3n=pE$q;ZK~VGYD&m-44ztp35BrtmNU1RWY+MfAs_l$d#h17S$k)Zlu1~7 z3ok@1!rE);Ywf)WDZ`W=qNK%+{DOheSAo=;T-6Pvk5$}Fn0w%Rf+|A8zow|lTG2h! z05T2Vm`#CZ5K5JH?UTGK$-1&g^?8v(b(85z%+!3VDZqhKMoFQ@zT9MKtbc3e0qVj4 zSha^6w$q`UqEayKC15Ho*cFVa2F>uurcR|n(XwH8mH-K+1k4)>WP^+6W#D#J(cBJjx-`3*b^R5Z%Ln%B3! zA4i$JJ1w24+nAo{4lFG$T$GAFz@oSzro0}M8eLe_FE%nqM_J@D+9uQQY=M3qk|c5o zBif!fVvutFNMjAUAqSF~(^*A2!CI7#BiF{_bTuKr{mVBaD_g;@`doA;LJa#DKmSqt z0h-_3NzKNe$91!=&GFe`&I#FjA7X+LeiYz{gm{F!!nuGq%U(FKNc>p9p1#KdMm|)z zWt={zAYH+y3D}#2PtwR!b~m5+(|`QOS@ILZMq+RnpP_#67us8*a;t=p(_0G0A(mST zFbb3A_}K_nx$ZHAPbO)#qXg&k#Erk;3ielNeuNo0G&5=}bv7Y|I-3vz)bSE<=R^>> zGP$)yM5`VoyOA`8qLFk|Fn9Pjj)V%QbkX4Ri<5|Ub^@Uow7{lwCe$c#g;6ToO8l?rQS>D z==_oT2Me=wWMlG4{`?G=$N`0)PsJCysqa(ERvmU|gKB9BJpNdv4X$UAb9GSQ{S1Jh zPwNzeCvKoYm>{^s+7P8s#X)1;IF1N-J26PC4Vea58#>nPw(zx~y?kw`OP-GPJX#wv zC9e&YYrk?~iX+yBEF-as<=+9<7Lt*bi~Mf8=mP1q}}ra;w)Hawkf@7;y7G<^%C2BaiF?7B~$4gBzu2?luGX~97xC?aB|5b zJl3-2$}1c0UVC@rg8Rmq#+9XKY=qkt>Q|>w%zce(8yn|0#$sBEDUTzUcKC4O5r7Mm z4FOKzgc5)YcsVIijIdv_HsXYS?gh!))r1ytlg+PfDBZnCUGAa8ajVU^&vdIz6PT4D z6FPqQ%k%V>r8&{&4D7~4Fut656jROU0*kiTO>~N5U7hELe~u`$M(&U%Ubzs1wZ($~ zo!K~0cau1F|(Q1P2U~b%4avubN2K5UWDxmLM9Q0 zYS1KsA??}(TO({o;kKn-L#GC|Y zY}pUe<51K0FBZePup2d?coghgkH*N}UqFkHTUnp7Qwq)&SZSvWDW%iehq|1C1!n#a zYIv1!1PoS{Ym*jns?n~-Mq@0^_yUO0TE^ti$;l)TAcUB)@7!gN~h&{ZyOPE;*^#ttfrc|$HbOq49FZ`njv)&Yn8C4 z>3JvfytFjq9)nY<23VST37C)7xa~VpV|G%Fy3H@KH1p&)00+~t ze8xm15J<8n_l)-k2v>31WPxy&iZ95hAgdC1=@rrmE8xgDUZt9jR}s`%Z{f=@zdMKe z4MUF~(g)XFxn&ZS$2mP6-NMxcF?tz4un!a`4p5P4Cjbq7?U`3%IbfYq-A-~=Bu=0` ze;5%pGwHhu+U{m@PQx4o=cAy8H9)hJw;j&FTkhmKTg*eqB0Pk69_MedOXVgwtAPIb z8g_Y=AgwZO>m!2Tg5V>x7TBPT_@Pd1SHo-NOw{%%l-Yr(?QMkrBWe@p0&PV~bd*kQ zcgFhuZ*XfSYO@js6Vx^_iPV-OX9}s|TaA+Tw4QMnAjkO!oYXj-J@*fP&8Lso#3GFh%zPm}i1x_NaiIHMDU`>DfQRF&3LyQdN78mA^;YV?y zITg(};+bRT0PRIjoSr_PLF_>E*+Ts%4gm_F(0=qeGeMtaSP*FFOQO-UbQ0xy2q~2< zBY;IgqKwlm6RHQ7F>(6j2mEmQ{O-SE$;o;eBhDc`1Xm%pE3i4IzxQ>_W(W{hI3-rN?iyGLvmo+stbNhIB?7gz@USy=~IvA*u(#hS6%<}^Q9fX?lROQREu6D6&f39dq*L|TG z#YFRR58|P(#`F>|Ud7@SNi9;SvsQ~w9NeI6t&XYA?+S-Tijtn(8@(ARc` zJi)y`Ro=N15$KVa}5ZMu6&8&|eE3qTh*!~<7ZdeM>uIuinr^&dh)~2bJ%XHF=>19kog|*NUtMzelE|1)|!{VyS*BUsaOhJj5TYhI_*v@6-9m~o@d}$uNvyd z%cp1eufip5Aa?(IgkvQil|z{Ft38WqVIdt@*?R=0qY|8kGu(>?!q-E0+`x#p=tGwV z{`0wni6)#UKc9CwKKb5eg4Uq@eTWe(a=wpVLN|(zD;eT zenU@R)Pyd0~vG+d}aaF zU~3;rixj~B)L}Wg&u{Kki+WGm6`V`JCY>Y?^Z?@fPOqI4C_g!Vs1|=oruba2?1lPF z0tRQTpjKAuB!d@Duc@|>ea%sbHym%-eE9Ch`{oIi2j)pkKQ^k%i6EN~x$axgkat|| zBq~Jsc6E6cp7QqKc^y06eDuEiADIiszIA9ck3smp=EL_rIDY~B{NBSGc>*55^GEot ztCPPP)Wat;^S8GJI4W?8>56XgyOeg8{7rw?FMF%od@)s zgU~}3gy16!^EcMXmFwjVn@a2Cs&(>)jg#x-F#YDHwjHgKFuEGc|7TpCv_T|=uJC>)i|L!lcs8RQM!)SR92 znEmpG1Au<#W<2U8@D{kYav&CLkHLOGymQbG6X>XLK71ev^c@6xP~NbOKnKVibOLgl z03E3Tt7!UA1RolX5I_@06TwP&An*tWgbMkZ_4X}yHd@Vpf(a1ga%P9*nXW{8#pXC1km-kK(QFT37o*3g+fuV z6IJip4tr%CKIVMI*Yaz}G4JtDjvDrj-g>FqisSk>#s&*)|AT=QcI#-faC>YEC_}vIt!u|7a?T) zoV#eRKME@bpU8=B2u~vWc})Y`vi5DPrnB8SI9-?Zjz8fnV7run5r;TOh9yj*byz>n zyv3QVG;#YP90SEtLgIAk$cM3SH-QNflZ++NkMb3Di#a8i&eV9QVg9?Y`dLU=IJz}V zMo?X!1x{c_#47ufBuO1bT!D3zQJaGwrRQw)mqbSg{gZo@btgeM`sV%R?<3mJui{Hg zIH8zA_*H1no+vC)%V}XGyY5@q-LWF+Feza}U z@&FK)KnUn%KGwKA=wyO@B3XW%5j3HQ@LF1niT>q&slw-i)DS85T{od)e92(@J9#u^ ztIN~}jOdiuT3ufcsK*{8mCzWjf{Y_`Ew%}*=%I4?5e-)lW zCE}A^nm+gf{WPHKmMHJLx#AQHYO$ZrnW5m*PAVKtem<8MyZwk-5~NBK3%gEuL$KFH z5Z*JGXo&r2R`jO!&i(jCQrsJcvP+A`aTQUr-$#KOt03EPYLE)uzp}c+(Oa?pC3Qt zq9q9_G=Us4lH3S6NSn(mu^|#)XU@i#&aL=-JAA9M<_xXRg`*_)!U->G_7QX1jIBlZ z9<$K~GM6?K*)k-5h0R=F2(GPl-$52SqiVFt?bB=~h`yUv8N6?~94He@*9Mmj-=EKr zoEIH&WE)*0s0Ks*<|RorcmuAHBKYugNx`fOhj4B3?v;NPfZt-x9dKMc4ZM^78F;h) zx8b$a-}=wMTmO^)W&B#|@A+roU48riGQ8*1(^ta35B*ldbzL+4UxZiR|9dc(-Sj1_l>in}GK@G$!{@d)}_ zH*KF)dYih`WY4+uYP8U#^u7d{*N8mAHGRoujg1daO) zM-xeqE7M04OZMk+6eZ)vbqR7(^WX$Z-}T_7a5Fm_4D5&}hY>3?*ey;WvGJXsGQRB?-)z$W zBV3)JE-;Nh>bi^F`9Ax4itHu-KDOuR_TU$z0P1kH_hGW?O7HK$S8@O!P(# zXZ!urZ052mZ6X>(T?HDJFbpitb44$?hC&?!`7ji6y?6vJ25ElS)G#D6HRl~-6vgPv zmO@`;L|@alg233jeT4`{5{?`8UP4_xI4_FNVXY;Q*bJ*+`ki+dY_Id~he2`P!@ao7?}0^}EaX*N@c?V_yb-V%e_SV@T94=ZH6>(M zx{&am!}xuQsLv3!+4T~x-Q!_jYzV?Wd5Wx_-7t2ZB17dC=Y=`j{1sb`$q-2CKUUV6 zrn-6+w;A7^=3cSO0!q{(|00OtUuF%urWxo^*y(cX>(r`$VrY*RgnS$%Ok!9Y`V-hq z#Mt1Jn-^t^ce3K$q2l)mVS1E*`p&f`@V!dy=jU?X1>My~!*u#q*kp@$vPp2E9BKgB z$BEv?i5{|UO+7NM9f!i|U&d2?FSL&`?e-cj-Q=@lfINlSV$45Zmj}w?okZ7tsg{e7 zB<{Rx%oTx%~qLgxGbod&HE9>b|W37-sG{} zVD?~oV?gRO?syv+7u1H?hM0SJI^>9@=$|PKnbKdSQJz56#Q>6h1Y}`jaAT#8EJz46 z71-($$oemiMI#|=YoZHd<9m$iGNlahT0X>gvRw%&(sG2wG)3nPax1Oe{sa22jB5sv zdqZ_bu4MrdPVs*8!_2jt6@#nqM}vr^L@rAnj&}I&LDLiRV>-moZtZ5;wXt~fgvDE( zv$0dbe@hx$*L*;N=@9F4(7If2BTMt~=w7%b%$zWNtFub($ngJMvI5wS?MMULV&jw_ zz}^PmXk+t7^q|)e+Yp^7J2HvGD;K-O_TzIkBm)n_0pwayZu} z5-4YHCL{g#w;83^(PL6&`EAKIyT?P9yST^evVUFX*?U%B%4y?RDxFMiQrKV zjzO|Q)wCL}9QbrId34B+lkWZo1$5p%qGMZ(pI~>*rlX$)zc^?1I_~(1ey7o}mhh7kssaxAW^WA~7yP1t<452@wBG`Dm8&6AswUNtgfBJ< zBR3B9!*cLYY4sI|Lxl$na#6^zhZ${PZe#KSM}kJ#OIFUjy)9qL-q)!7?@Y9U3=A?{ zp=}a!X$g0@1*@gFUu5NXU1omX>&J0&-^K6?>G!}%TrO3*Ov;;(fU~~94+66G%R2dP z{Psp5@7~1wCwRwiFA2@fE_}A)RQCi_HlS?WxdF`bN@`mi1ufr^3VFTc+@{Vm zk`*ZDZwp5q37TBxa(j&x<(blr&(wF=rRaVZ$@?-)v#> ze?fo%Ln*c5^N6upT~2BP{iq!;82}8V{#{Ial@y1gyFpH|{#T7QELrwOoj*>hW(uJC zEb_xdA?5TH25;acgX;o9uH0T>8$zy^mexvZWC)9lk3hd+-DLE->kfV%8O{%t?gsJ( z>-QKJ5cF^MKa>vD?z5AuC#O4S9rhz;+Sp-bfBN`$I?*=Cc|LnP??}$y>Xzp1^^6C- zbWJjRSnN8C$zQf=wjt|BCk@~cc8r5q{Js*`VZ;&P=Yrr3N3paUg|k^WEpmMV|122P zO#Yd#NZ{O3RQds34Bc!S3*{WbiPMG<>j{2^O^4~}g*I3)TKHKeT*PyR{gy(ap-FSwsJAXr+-1zQb)Qb#_Td~|6p-_zD#=q&Idk^U`r@lrE zbmPYDh>4)E`RkEy9|Cs93fO@H_J;NJ5%`5fpkwbv{d=iik8V8@Evl zfQ!BBs1VY{5#10ZT^ynAprL#G30GJ6Viec>86-5;n$q&#@>jf)B#Leb3RBl|k| z_rXWuhd3nT!{*Nn^mhm-cO78e!LW(bgdh(Ch#IR_hVGzdfiK{Ca}p;P6b`cP%Tm1I*MiD=r0Cu1;WQXGvWOq}upxWwgQCVNLn12uz~9H@iVp_$p1 z5tO~>fP%=FW+zevFHjen{l*_+_`&pmlx9a{bb`L|@piT^XB+n525G0?Q0f0;?rp%M zs;9&~g9b<-TF@Y$R%*E|0w&h z>aFdyt?ku&?XCW`SIS33$Rv^gDh4PJq4Md+3?mXiNy10w|6BXaBonmv_I;lFKJRNw z=A3=@$J%SJz4lsbuf4WjCo}3^zyA*G-+JAjdC^znZq#1a_~;IO^$+c#BSS+U-Bo&E z!7cjgIT?|U28KSkE9-n|$NaDIH8XT%P~?Mwkt3IqC+m3DfmEe00}Q8Tg?Mybb`>v0w*GM)7~ud}oqGD|&4F+P61l#dR@{leS2@NEH5*lBF4 zOa<6PYuWbW+oQrTI?M&Gv1YZ~oR*{L@jkz6%H-aq;KVETcoij`QkrBTmLwthsAnV9 z3HxTyd-*rdP$mpqPt`LP?j7Z}?^4elr5^p~jiEQ(u{|R7im8f`L0z&isn>r>a_lAk ztUSbc3a|Ss|KvHvuOVN{{^i0*5xrNL1JcZ2mb_P~3A~9SrMoG!SE_hYzjK>aIb_kI zVpc(`=vD8M@?as&lwo}E@L4Q#Ir;pylzR#?c|n|3i7xqthvqSE`Q|h(V6)6mU{hqF zxj>Bv{US%WA`xoNi+nJk`1G~lYfu^Z5>tmY|BU5(eQ_iQ5uWtu_OtcvV(FNP$fid`t**q!jVfylP> z`urN?oFT^V!d|{9>woRXb@aom^E@p-D?HE0kIT$3f5;70d2(N6{qIOR-ITA_ALe0% zNdCr|*F-tM?^|Csj`2y_(~LZi(ISzG_&BvH`pppPrXZAiN>c_Jm zj?eVQUKbrfU$e#cnUXVdd-OG{l&ZjN(bufVvUGqF!sCrA%?N+%oUv#AZ;}uiO^E;c z6MFz6*3F-WTK*VkH;7c&$A8&gI1lPMrP(l*55Ysa{@kX#VE15s)Nb2W)5*a*mk|ve zuo(xIPjY62CnRd#ir@RibqPO8tM9gdw7yWJ#|qe}Kk=`W3HzN|vJV>tX73)Qr=X=| zR3BJYVT;dFU0Xm%lD5dvjM+OB!j5J?d~&9@LtoRVuc@&&@84NA? z(1AqwCTL`Bz?5k|ORiA2R}cM?frr{3G)wDkBPe9V|G;)ci%Mpx%G_NcKd zw9957NNAy!bAh<9hk7!iY-lF6c3-bQF`TN5LUW{{YCqlN2wWOlB9(3FP4PdH*0lKE z()5UMq19%>*e~O=)^_N#0iD;`yKtY|V>1SZTC~unbAc!(z8g|ay-l@7sW~!!W$~8y z6-gj^TM7I+o<+{oyDLx(;RnW?ZYm2rShr)*JZ^QRX@9oyZi9Ptg>BB2(HE!>CTg_9 z6)ZHqFsg#69K@*?*kq3KnOgh}4X;>Vv}jHGmooqJ4UN{9K~Z)SQ)TLQJi4;sZ`OxP zqGf#_EFkoHS=8f?uPfk-6{X7a0AqIruGnd>)x4$_0BgR#B{kJ~4$99TpXD#E-!P|q zJ-f~$I;{K(Z)GX=x$IO653NNyU zH5q7IIPZ_f+s0z#ZOv2MSd7FxREUm^h4=KjIQzi#P z>MkhCCG3;6CJ%nxyWWPn3-r(rqym<3DDhr+-KHG-(J1$IcfA})UhUQB;EU%JgOf6;#O`GbLpGa&rS5x z36AKhL(QMic>q&X1VD6N`gk2gow@kMR2N@VU8FKk1zwnex9OWp^^vORW7L1CiBcI2 zbrsZ1GZxp$+Fp>!vMar#8P&VkBzh*Wu-p_@KMTNBQ=*+CEvwQ7c{e(0-w?7<1a8FA z$m@^gc(6s%5}uZX^c)|NkR(8h{wmoBHg=nrnh$A4TUI+~ZJZ>?x`jtD=N6stn^I3! zmtMD(5BT|--*+2bSy8|i4whR4+;9lF*!uQSq!0I zfR@EO(lyk6MyN(gg_;tt-$h44Z3+GLu2APt{q-Gs#nFaQ<6YxJpA8Ld$`|4EIOw5C zZ?yC1fMC^-^0nyAw$P3t&~>QYMtcsU&a;^`#L+JSdBFzBz~~i<0Rk)*Rm*CMy)?SM zOrg7bO@A7ZuHeoQxG7zfe`3XAwWWrI#d2k7KlOvk6gpzs*?XSw(3| zN+fSe=I}#FiPzm*vfL_3avP9T%RClvzG5Y-Eu_{ukBDjf-^Sxx{K(^?yYw}kVftj8 zjJ}9dWO#)I;K}H3tvA)^$>>VnfXRVt?xO2htjH%-#7A->!+O*=?upLn%k}8q&UUF+ zU(-R=zwgF5y0_|ISZ~Ry>v=QRD+GtJ4$%j}og&OBRndp;vM{>B!oqsbG;A%tcZ|*Y zSPtA2=uh0kSx)?gOFjO(i+7}lv|4Gde@2?nSMSgp4%@QVKO^;qAGGT~Z}PPV4%O}O z=Z3mij&^VysSe-<6M!(&Nen+eRV)Geo73C1f zp3xMEJk_L+-Gf3)?FVkhRP&fu&?a)QBPK?faGj>WcvAI}s8CNeo!%=M7w=)}Qe~wP z53|JbU-7X%?xNN^Ikn1qmt11ideu zKP*;81~MD(eeR%50GuGg6H}aJcF>whnf{U zdfitXOrf6Zt?k1n=J95{xJpRhEcA>qI=EEFniT%K!+FB;qzM_U;?sS7PSxLG><)1x zv#jNN`_c)e0#$5-#whA`vPQ$_%gaOovhw8BLuMy#nDhghF>hNk_(uo^yPR zHc$b0ka-b)#$(e``6os{Dz);z+d$chcmHg$fz34Y2{|fb- zq;M#AoX4Y&eMeTIC;l>s?{%tfzz~qkClxe^RVYBRzzDmGC-o;$fw#Z`gyKx9@x7C( zXf73nHHFAN{6S1}%~hH}4t@GH&jz7d2u>fn+oI6-f@katzl~^hL-CGR?B=Q`rAyca z^oE1B&fVGmCkw*&GhG^e+XIJD&gza6nC`zEK(LvLJ{P|Q(w6e!Cq43$ByCz)@LL>p zx|s$!P!s{k#zxds`WcmlOBAJB@+0fzTyuSiprLr*9g9oQxfQ(4`q%?3>UI14?*-2a zyp6zn`ppt28}W4jFLuMbXd<{(OHP3J4Sl$g`4nDN zBIO|%`N6s1spixBXko!`i8wdT32=v|qFR~W>953k_&H27V=N=Pz= z-#&O7eJ3pZ{uH7KHf5v`{CRk#Jn+g-KX@ao=Pus~ee4NlxgL6udcvBTx_w<!!k^Z?2$tmeND_-3EdW& zbxxVR^rxIPx3@p-p@`~FJio8N%5a@iI1uzHKSH03w_*??301N1G}3ZaG(NoFlC#QM z0=CJL)1sD~J)oZ@2geNm*NyjN)v@dw|Kob|6~&{!P4Tq##)y~2FBf_&Zj4{j2kxwu zMjX*jL9ODym^4)EJ5_Y(O+t=Ppv=Jz$S;ZZxPz7uSQ52-u&3t)bf*F{}(Pr z$?W5Y(r4R&3q$f|f>ST>;2wwKVylIuacv6ImH7Qa^d*gpRy ztObNo1HH!dRRv?jUXSRSXIRU=fxZ8je@r;vcb+2DQ|@O694gCibXu zx{&tMnhQJ?dysV2H(0sQvk85G!0;A89p0>mf6T8)L}~?!-UTeMewjFsywGqTyZJ3| z(7Gb$Gc)swS+wO-Ox*|8TO^;F*m1*Tknm>uT;jsTRW|FYe1ya~pTe za(d-VU-i(_4u6HwHu9d};(-aD|3r^=xi7l#5_`vlIYm{`!?&nO5jvl(haLew*twWy z9Yzs1=*%J*SnV4k=_{3?{#eVvVt-C}jD4~c{f|^pU4O!q{)&B#;w|Mj*^5G(GK}LR z<7>g2(Sh)h5)8O~gDv;i^D%g2uMUu>p+e4eZJ%_3yhwXIL9L_!!o_!pK6~cs5Ia0@6z#4;;j( zad2qpY`&p+=3fyyZ^u^FCY%N%(v;}ouUSm-aj6^)v(&@(h8S%)6{u#2GAK@Z{9NZg zR56X64SWc9?L1~gnA#V?_ZH4H3Lg(w+L;y_yxk$%Z}C0i%RZ|w`#0WvpBde`5q;U8c-A-0&oQD<+g$?fsmR%(p~ggL zJ6b9FXPf?dj2@gEDk^DHe*KnpqMz#JN6|=Q{Wa1=QX@@?GztY`idgI^?5Bu+?PfJx z;R_VPJS(|=-`~*&hv{o}3y+FEEABI3`a*s0n$G>|_CiA>@iV5_JmqHLy4>fSJAl05>z_>RZMt&3}aLYN5e{A z4`OYT=qFc+PSl&X=C3q7N*ZqLUWto;uERZ3v^8`3=jgXG}< zD5wMFBYSOa2nW)bKE@Ia4v>)v+aPj~k`KlsAE@b1uikC`WrTIu-`h6E$Ry ze^KdMWsTdbZ*2CS(KjZ%UCCbQ8;>H5jy^87=~qRxT%Trgm>P}WWU)G7FWr{DXvtvF zAIk#2-lAyB9+M*FHC7qsh6ov!(zsqJYSGDkg{<80$DHts%Jefiy2L7I#9$t-22gH8 zD3URaUM1zM@kK(Ao~v<1G4zluIv|hvJc@Y8V;+w(#5_V9N^Z_#e)5`ArVN(uq=uN9 zU*Mol20662w7?c#s`)=*E_>k1TAz{@mSMNd5i1$B8RmvPiKkKsevpv-Q=ahBLaC+he;q|3zA|z$> zJ*vw|#UW$hJBCe+C)RK{HAC~nhXJw_L#>b>Uq6uT3w~onz-nF}M#bqViP3cSQVwTG z_-6vIjO^cmcjkro6;slGgWrX;#afxve}j<>!Lb0GZi2c1;Aa$a9ev1s13LU1a%Fxe z@tfo?pQi`=!l^>k;P^(t@k;ZV_38TL7vPtO_i6a;X%zhC0 zc!X4xQZR$%4miSj^)esQ_FvXQy<$I1-!Bt;U9w?@^DA`RA)Qcql>{a>*1ssoPXb>N z|57q@@v*qBdTaGc%K2iL)zlg@HWrd?p}mxkd|;_Die8SPzdTu}=w-_tlM3M<7=t#m zXIHlc{RPPN)vS|t|5xMH;krVlzgV&c#Q0=mo#yc`m0aQR317D!`VhEIYnRylclgfh zPrOKDmQI5&NgbcS#t#2=7wR)LDShTruZ39zy7+gD{mO=FQO&DG$i6MMV*k}NRHl@l z4+>P~xRY^m3JCF{SBr5)RcofCq3X=??OvRn#E$QL&Be>}ZmNh6N)2kWz_=HnjACOp z!}tLDl1Q3LXuVKX8GR$N#rEoH6-fHG$@7`&0(rhsp3?=~ACi!!dvu9v&?0Glx`#sw z{+Fcn=^i<0;+K(lzPxn|PX$vInVG)?G1bj>3`Zj6(6s{A{sSyLtN-1)L zw+r>Auc{)|)$+YLm3z3n{e?HRW0dq~f4{5t}nG@YcOV)0#g5}-lvA2jpEPJ=U>@4rNP{`|*m{av-JtQy%#(Bme zG;2esn+;Z?M(Fije8C~ORWCoIm$#YR-E}rR@1QKFdPRrP7CGY5pX*Y&aWJen5MI)u zS2XDrJ@^Ui*PnaW5&6*ZM{45RDZOHIcx)FoX`Wj3=T3^lVP}Mo6xps<>a+ z(%+D1)1TX)p`d6CkL=MaPU;nB^@>RG7G{_WGsLeV`tw}zx$`4PjU7w8c7YtFBp=wE zHGCFeG^<_0k!}{}QcYZ_xwG%*JxsL%xZHaf16 z&sJ^iK5;FUx2oLz`m$@NZ|$=H(eR5c<9~D`AU_?}g2a6v@XAEHt@9|DJu^5G9cstP zV}c$!;$W27HN~TIv8RNd7U|EQ+W8^CfH%PBz7zAu&pc$^&$7-B zX}OS9%zRpMO;W2vLFY%Liu;m>Tuu*}T6jQ=W_|4Zh-ONP8|^2luMB&4Cfmm7zI-mH zRHf0;d4%75ql1)ERnu@)xTy08J(YdO0`;CJynxzR0%m0{f0pKp8@XU~5}$=}jDy3e z%7;^Vtx#Q1T9MKnorm}xP78a?5>Mw$#ut|#)nJMG4ULwB(ydq5!8 zUup-tImP>S9=8U8!OPdnyOLxf!y$bo&u}VtF(M_QmPBZ0qBf!YhWy}fI@=vq zK%cC+Jd~JOb0_j?VzB;;#;~8La3!AjxY|V7C>US=YHell~5rb z`15b|0li+gg29)zc3R=12p$zVni=XIY;3Q2YMtv}V9>dsqk7uc;i-{p;m6Di6BjLU zB-o)pzjx;m=FmE_*z}5>dd0iI;NYVZq}~XHFy0Q5a)fNIlmi%ac@o{)N*K-%b3MUZ zAs-7cTX@fqJBI{0ua%d35&*?v!41StDUU;jZ;x>@cPlXD>!Fd15RfxfoVXurxv(pJ z88@u3u*oD&t=IOg+-^fn*>B<+U=5mBirl{KFu0ugU-g~=J~?eBZRL|AC-)?fJLD_% z=F>#(Ue)|Eo3tp*3S>eGc|B^Xdrw^ng6{w)-Sp5Y8%4Vfne*aqBGycezft1PIO8Uz|V~lc_l-U81*v{7?^j9K?NrY#Lz4 z=(!B+J6OhOli*+D;4SbfnL{*F2wM#2jWRZ`ldQ~o95;lpKyl}xuydn9@WXqIlR|Tf z;4GM(*9e!wgF%03|IWBDc^Q_;4!wN8^eiEK`Q2coXu0!dftFt8OS-Sg5SCt$`;Op8 zS}q-{hwi3xe05NNIbU;b^n0dXDU66`Q-f>jFL&}ReAHngXOpbI+{B!5u;P+#Z1j6 znig#Ss)3hKQy+raACYQ6{28EeW^ZPTQT#wOkME2SL2ldpuad+QO}fbksc5+}+D>l| zeRB9t2n^t(B)3Ul_Csl0=$$%9&o@gwH&ShGk57&x!I*P5Cjk`@0no{>3wm;xk2#i4 zAHiE??nH#FtEn<66y#+JxN3y-^rngFNY@_DBw!5^osd;!sOh}X1$PMbWZ*YLL*w*i?~#k9y7gr{d17iA!el%Wj(VJD zn(Q0I8s!6PpX%&8KwL` z45??nobeY9=JB?}8%PIkj}TeSL~5#}&GH70u!*bUVPA9D`C}PZ9K-#dar3=~=2_Ry zQ1S5)ISagJ2zwWH8s4q4D6mH6``WK`duqPa%62dozQHs$u2mj;BdwVg9PKBW@P!&b zba0F#emHFdqguH0cZR~9w>M0nV0HUFB-~FzxZLBX^L|y`I8Rl0l4nL$ba+0Zbl>hm zRC2#h?lcL%OVZjTtxv*tNHgmtElt9gv=<~TO~RM7CnYV1r-rWxM3s^jl(eDPM=dk? z44$HMNlW<_%CF@wT0%c7jon!-Fgg}*k$?}v*qnNO2KuJH20 z1#yfooNGTnF@cj}ENeNlb% zXPxhMz6W)XQ!fi5qIR!N}l-s zPn|fWN?%j%nI|bFb8PDRS1D#d{W}i}k%<0Rd`y4-Jzj8xG|pJbh=c9=^IarbH8*9l z7b8^k#F%&i2!B(zKouXW3>~}+w7V>OuMkrgXL(+VDs3}Kj;P4nvzqk! zXQk*Q&m`Y&J+#`Y#*vbAx{X$9)sg~`64I&G(JKmt$Gv0=7v6(>2vFCIW0__>QW$yZ zfD(H2@&iU|u(@b?-pykeEWQ2*BFTWJ$x{V2{43<2Tl&(6BHr@(wPmV3ojj-W@Sl*7#;Ml}7rp&Uq`f7a)k6!_9g=n( zX)?Yk{^?}#DQB%NgFc4SeyJ~Rbmq3|A=xT7jw?R0*L%Qd&OL>eelmA&O@O7Wh!Q2n zCq|Ru^mu!WcXRjpI&-(z6dU`)?z@bb{#=h;PNIP(hmv{9!9|(=+%~%*Qcs!vv=2Fq zW$`vi7YKZuX{GKD$kA;r-z*Zyz!IE_jk0K(}<*S+IYtf&O z!6rA=X@u7?7 zIDQ8PyQ?ir9j*qKW2wUgr)+gTD`Pz4)ndsBOk;h!YA%|Elu)>Ycn~*L_;xKUOj=VH zIbt`+ir>VrE|Os_$YHKh%!pM_llL6nd9IdcC(qW%S${cBBX?)E)2X1ZNPqkY57Jo| zh{>Uw?pgJTS*={C^{wZKj8rBO#cibJ zWN=qU-a2@*&AZ#E%3t_T@$a#BMQrzvfq|QN;7XFu32~h5+(({iuKvX+wBUB%{sp(% zd*V2;Fb>J=(_H=nD{E_6p4Do;w7O2Ss)R<3c?HH+Y?96D^4R#Pbx#ZN4&!_Ei-LmL zS3uC~d1@FcX@8Tn$)qWSSo`O7d@x7lsWT)maGYoj?=Ntg=mUPOpaW-RvCx-|plx-0 zf1MR{0A6bB?>zJm#T)3c&Jv|X8z|)5(Dc#_1jN>btG_VZK=({`0sDs=$gsi#0DJPz5 z_QlIosRmVQe>#Or`S*fH^vEaLT5;_#x`St_p~==R=OIrmFIRQcyxV((XyKf-wd7Mf zklwEFVvZ6yJylavM~8gIUUe$U4FSi7^Cvwvg7i!s+ES&%i#5&LJdtQsR&0cm8~&Tq zK;LyI#}hJkn8b&073vn8^?)<_yQ!!4ywtMrtL$@%8;R<6xTG-I^QzQosp##%UHisLvRKX@v9v=> z|7S@5*_qB?^D!pE360Ls@j?2iF7fR89@H0}lNtKpoOc)Ejuv;W9BFL!b)c>Bh0x#` zgbN>G#JrKykxc)fviSQeLyZn!SB-9t%g`=zB3}mi&wtd{QA1Gwq42L@X@+gl>t(){ zz-KYhW%bp6n)&An+n??5=+Jr1e^cmurhiK4e1={>P+G6FLWvTpA(xYaW&5#7tTo8xoX zND6JvsJcH9NQOIlmFN?JznB$rNVamSBv)Ax7Xr_c$YlH1KS2Cx$%dRSu1ZYuObfl? zFq&gBV4OB5s7%hB%<7JN({fAQdi`WF08vZ1C)18|O=cJgTjJ5U3I2r817$kR@$7v~ z&PV1c`il{O1eY_OaaNE zI+tHwH35iZX>_F!{P!A=JV=rC;&UnZ`wxD7 zXBW7P_hOYi_MRs*+@APleJ!r^oj2n86W5Z38VZTv|8A(;5!P~QZspeCx{F9pKV2|u zWw5kpuwEzIkz;VcJ`s!Y)K}fCmEo$)(BX65CjWJQOt`81PpSMT11G|dbj9=f<$nYP zBF=pc$^A#)$%Um{1fs5HCuG8LZaL_AbC!NUVkr>jVF?i-<~0&VVM%M&@xZ0TE{nh2 zR~_q(Z>#@)83=n@@o9Rq-TQ9(=Q6lP(O~}|f&H`iSM0k@wsz? zOLGQul9xAzDQeld(V8B15tXY&u6qg8)w(T5r?V-Evn zoS|H)MRRg`yO8K1ZNA-MXSQDNmfCm3E>n%fGhSiBx)py;7nXQNUXr0Tr#Z~w<~%KU zhs~dpY%3X@0YV-4ie2OnGI9+l)RUPC>L988D+CO!Avr4?(VHT&rllQkSMh%fsHlXD z!}ZmK5uG{z27Pr~xa8;Iypxf*^WmK{3)+-uE^GoDo4Y$LMWi?I(6PQ%t!BTwN~|=w_UkLpc$^>5T~F{MPIVu;dW0QIcUMX>gcaF z`{Y<;zQ`v-X73j9$lFG8>NxO_htJGRE$-f~e#e3*c{~l*q`)yc;-%_7v{|99?<}}6 zd8YWJI#c{f_QShpdfOE2{c4~aMh69~j%x0E;zRm>cPBzkJ%7Ka|GwJlX>UBs_q#$L zSD6(~b5V{t&uxCoImcODhX64pu@m~Sg8dAYNm?N>5A)~piC>RMy!?vXgt^iz6MA%n zD;=Rmv0^%lUN5u38eKfQgPi*3YYu!)s>oCCgd^qf7!{Jj4VdD{f8|C+62e1%NdgU8 zcR$`wMH*c2U;dba3QoERO3@_T8mzgDYiVG;Im}+U$EeaFEs;d~Tvfz#RdnNqWZeYj znG~;rqRTXx`|zm~6no?P;vZnslxs`pDc+g9up)5X1?~E^KilI&5(~A&Z5D!5s2`jj zjm@d+8&dT1#qyStPt6=-|4L)8h?bUg8a%8Ak!eS5vbBZITGFMGw)XW&mtPH+IMW5L z&q-Tvt?DOjrO8^O28*oKyB<4iX0EaA6}#UxdifX!X5*`S7i2<2C0Apg+*7yiZ9;jc zuf|U`mZh!5$#@&S_4>SC9DFjQ8)K5gl3*6n!Zo_*O(>QHfx+GclB(;l88tc&dp zcV4S6cuoFbs@r8_!%kVLjNSHkvOl4&&Ld)hobH)!@5mOh)oo`PT_;ctVe7%#+IO>k znpVH8s56XRw9rD>nsx=7{S)kfRKAI=U4hjuu+GSSGX-tC(G0BFO%!#p6CoQ<)3eWS z6xe0Ep8CZDf=v=U6X+jy=#Lbj(~VuK2lfLN@Up1RVAouA=&bYriIjGCNldX!qt)J- zy$cTlH#~vFzHy3YC>rH?0?A7{<$FGhYyBd`vXMxeq#`dAvZ=U_z@PK{mZ0=CZ zSzs4DdA^`$yT95uM=xfsPSXC<}J$EZE2=5@fck~ z7j8ac#)k;nFJ`o~K0+X>=*cb!VLKb=0MXKUc=i_cgl!KnwprlVn+1gIGse3~5FWBX zsAaQC$Vot`eNU)s{#BKL@MQ(UxeUw7#G)drzGql*qabG{ovKcS8XT4$W$=ZUWf_>J z#2t&ib5N0r%*Sk@#1Q>)ZgnAW8SDBFaH-gBym{5h$FITk@e-%`f`p8Wj zb7u18W_jI|EVzjmWY=}Jp@Xr{Sn*D3w>%@kVqTU22Ss)vHzb99lwZbxqr%G=H$+ue2!N2=R;LaiM!+1V!` zEcigtY-jYEL39#B4Bael7Mk55z{|L;3+=yhw0*z$^C+_x(@`e-_Q+8^a_rLD!-=~X z@fZ;`MJQ9ak3_Q{ZWO%3m|y_j=|9bg#0MDdorf6?Bvfy+cQ7>0z0EJTU~TVjeQQ>N zV|2*Fn~^@Paq+&XuX&Tryxwp<<#(nKb#7MZ?93V$e+Sq2#1cRK&cuE3QTm$ZNHk-1 zqg7Wnby;;Z_ti1(PGIYig7aI+o@$FxP#*v~Bmu7pZn&=SKEwxg&Z?59=w7C4-x4hP z1;mSXI?`!A;xH#@W>$S8{(Qe`jwFT_cNcdR_Y|M;cITeS?a3u-m&ZMk$QQG^Wd_UX zp$pWDw_p*n$KVJOC*0Mr3p>IbE!z5LCHFNqw8{H`=$hm^U|92iI0K}@5}OsSIbRkn z9JSwTr9hSrlmmmSh9VibSmJKfMA#fv&Y2Z4w)SbD=*OZ4 zve8y;=U7VgPP!6$Lz8{^pua?pk=}Mgx#!kWyZ?HsCN8wT>;%o=W$eWiuhm=?EWNYw zv8!T#Bhj*-NjB5zi9JY)UjGJv30y&-{wrk=?m%R(xhPnA$;PD@#a>W3{=i>*Co_SCxCIIfank-sW^Bx=x@auyn4Fl^4q6-uBiE;(caUDA(zGCay`^TF}j{d zpaElNd_y0$#2-v>a)l~Mg|CZ`5642sHiyn$q_5^UO5AtAUnsTt-cyw&;{{S#)f53l zumCWl5zKbQ{$icHDp31yBZAa5KX;>S4UCWo*YhtPAxDKaY9tWvN+H%y4UD6`^+pET zz8QS7OZF7iFe0~;L-TemxScWCxO8BAMlt3Bdry^w_ntJR54CrZea@E^G@&=jVz*d8 zXvK;6jzA&9pv7_FQjSj5pH5?b{;&tujugP|%39_tlfgMPI+cn%H?%>Y$UJpstmr#q7B{Ow-2<188Qe1FqQM~* z-7=I|sxPzCHMY&^cg>xgVLCjS2=ZSgt$M;#o<#euzIh7#t01nhczx>_<1;{#Tz-&vdWs))?B#pDk^@t2R!B|=>T z^kovOH2g%>-_9jQRJ}Kt`lgcr&JG?Naq!=%ziwRey=TPLoNdwTN_lS?7&Hhrsj#90 z(6|4Ge+7KMIW13V+VlTZ_87= zaijcQdQ)&6gTkBqU78P`OYKhYRZVd6~9jdB6iD5)?FiVRQugh&LeV-RT=G(pSQQs;3aU!zI*>!&yxQxTXFy~T{FiK%D z{K}dvdYrOUD|-ym1?ffVd6K6DcP0;|$B{N%*>c1c4&jaWGI{5h?N!x^(`bsm(wzw) zG&#v?&UwGpT+&LUn$yRg>enK|k+dSMCR>;!EhbyZk)mjsB`FA~Tv{4KOC0zd?Auv0 zFj$(~xH!W&1qybv9#5~i!#GfUI&?f!AJr4CvHLm~Tq3cbtIL2Udeym1n|+)8P46~u zkJ|qs9!S=`!8;nm4<;6$viH=V4q*I`b5CBZU*R9 zmrhnib#z;Hor?9m3cI(}7Ewa~*HTeKZ#ROExKqsI35pvYlo)}G`M zM=NR}_;k#_QXOBUu&g9pIwEYOK`kYFZT6F(a1yAS4r(WXO6OZiLJ;!e-68}wM;Hnb zU-}4w;Zb8*RCOu$za|G_#*mQ&NmPSWnjAbu4ty`Cwn{aS7MbKo3&}zI89GC9zKBL$ zU{<=#vK(`fYtHqw>6(z(!%E3Qsl+W;Y!fBP<}nVLu4Avccbe(%@*u6+r8~CG$0d2;+vX~D>47jM~!9FoyaLq&dYK;x>_?xog zv$&IKL;vL-KcrJDeee0XS%UR`zi@d!XMwf6zGh^de=k;wk!8;HskSXCxZe_cgrV0!%3?VqqIk@uJ7oE?fBxyaY-AA-x} z_wD-F9(@$M4&L1h--~CCA@=wgTtg&Url~!pGDj~XDd1E-1{?Ok!Z>SI^3)2c=ab7g z)iwE^jfuoi+cl*syV2CS!+z55^LCGDCvSrk&_}o3%gML2@FeBzAvO80*H=I7{00%q z5BT0$I7qf?j~X2w*>U)0GeWqkDX=oU?Z(y{fgRR|D`9E2vCVXP zd!;<;OjX)oSjJJ9B!#}lfi4S*}w|%>7ri7h?r1e+9 z1A3$w0*mk|a13`AE!+~ge(XAWgOyVBGK2GH-Fo>6*11l5EdE+w7gvVI3p}bx8i~e# zCs>16ML!aOUY&7Yf8zVRnrH&y@)5QvL{eCG zT&}8hakKzM_J%Q*%Y!J9+RB1RS{Q6CUJ_KkPI|qJdiYxz z@T?5!G8RCQVY+eaveuMy(#H9BTU@!mWKtvq^N z6=XU>`Fh=tub2LlalFT8Bptmjj!)6_(u{>w;-U5RFX&3(*6_D7>0f3#P=R3t^^6Ur zqUD>Cu3iH@J*A%nvZzL1aTG0^v6QZO_j}J8cH zXy18CFM1yk)l8s0XYAi4!vtq>W9&(u!tQ^StRS?I9v~7g_@0!0 zO#CR6(KEKdYQ$+JOJ+-onO^ea(~$vd+XN&Co><*F&~ia2B&SUnC;mykg;dEX5n#Ub@-RaGhlOU93oU3 zENCBt8Inr%T>&?j;@?3VIlN|L+^LlYY$dj~R61$X+gt7(qgj-YGnWu;?E{4g(GQ%bE=&*DdC!NJtn-QaZgIcZ{r%%WcIu#0gp@kvZ6 zaH+A3FS5>*tDa+q6aaZ2H^cHg6c1~`Lr$_g=(4Iy5^4;;!+-miv_1sxHpjRvQ1TZ4 z*20Vwqqv1=0YytzV4=EPd8J8%3SU*x`>hI76RAOXd-Zcd1&faK3AC0yqYyGHQ@$#u z-F!r|F7P=aoT}ymeu~yAP8iBv0~Yw3T{tpW_xHpuyuat{OmFwb)crljH`2T?ntLoFSk^jdFEpU3UhD+KAK%9Ll#0>MX8w6xBrnAY)<4Yh#3Q3MWswQKnvm|>lC z$hLfEIWss9p;zo7xb`ixDPfPqFMCys52=mJ;f+Gd&Z5cyn#4?*4pq*ipwRCO zMgEHk|AQbRfrsWTpYE9-Z0_NmtLB=6XEW6m$sFXZLj)GIlZmBx?Yv)9*fxsEIT9Pn z60RYccc}%FOx(pPQRiODBK}O>&iWB&T`iPVDMa^ z4#+X9WD?3pMi|+qMD1oUzdW&(WR$YWl2YaqIaEg(#Iw}JNgUEnY!KLDGn4sQ%e1sI zC(_DDy^0jo?lT#kYd-~Z>tMw;j{k9TPp{mpzuaA$XuK183;CqV@s7xG?KMUK&G9l0 z0JpQp!VCP<1FEQU@x-aJ5g-)q=KmIJTcO(h6>D0K;>h?rvCs-6 zvwR1Ze3vC$tzkw78!MhEMoS{#m>RvWRcdi62NLP{d{8VM2cEu;#~%R(7t6x5iBrRM zo;to&ML&2!l}vAB>A%v4Bt{7D_hp4*sr8G7fB+lu%f7Hl84JNJx}4Inq(_ih9sszM zKS_$=y_ylLMqXI=shm8#7O1N8*v5$N+uu8vWX7k@C-SKI#ClGR1W#f%$y`d#qAsI@ z`{|b2E|z(~FqJnOr)rPLl##2yle1~8noZAvq2z2TNzSIC7wlmt{&fkN7(1|B2W=ug zGnY8)90}iJuRWd~XB1Tu z_#oJA_ur!e>tzJKC1aMusZ*A-Bmv}0tMRh556LN!pFSlB0i~vEd|_I~ zHz2r&p`aWFuyRVY@V|s`Gzd2b_X`L&UlDE&eHXU{9F272kd<}2wdzB}fRe3s|JZ`qf`+J@g>>v?(jW19EHe-C)>( z0Hm*;l5Z~3E7>NYBxlZ%D&Y0?$AhQC}^RDUe+&#^5u zY;CGlMV|+Qp*IR*cks*YlN#|D_}PY$u~2{k)Ikx%{O^feQ^*a<6?n-D+J*F1ct z_Pl7kHTi1TXNArWp1*^?GaFRAuVsTddY~mtD>SCF`$fvOT1zS8TY#665>(D+atFh+a8l!o*&Ao z0C-Ek7_amTXWwnhoIa9@*jhl)`@U6KYLnKZ7H(7Xyv|Iq6b;7(@(L8>Ao4h#d!lco z>a9@vMy6Tlxr2z3KW~hElkzKNX%dH*2a>;vKZv#IV;7ngL9~o_kXYGsxV$QHD-eqF zSW4!cCPOwx;$n#rsZ!n^a+oEauvQk%^ZcQR7I|XuU_5^m?(+FhFPHb zuCK|YiK#@32yo5$=nUjbhfF?=zQaE*5}{UEk}xUN*Q>ON8MSAW?IQF9@9U`1HcL(? zs~g67nyI)!Y!T_^!^G(vIsbYtrB(XzoU-a^I8;qd?4~o~M5C(S)EGKnw(v;&B8hhV z5V#@x=41e8WW}#ixe4~C#;4+B7$4Oqx6r$EOWJxYx9!du~ zW}klhEw+6>84@nMH9Yqvn>VTsOJ&|;bOa7k^907*Q59=vVy5n+wBocY>Z^VlaHD@| zPjYeSO6~>F*dxGVl^1#x-#?ve*b|R9qiv@ZFKOmA>PByp4ZJjdzZ{Ih6O`&B3zAduX(HF*chj-dkD4CPUeV&Ed}a@A%|1TYo|cOB z=l0l(w`A?!V6&BBf4fvK@8Oa0e=>q%?|pM*ZSz}H%fUG@FV2AR&xAzgN)~>rg#zG5 z^XP&fhhEPj)WRQXsYRiqlr>?S+;t=5L-{Jv;HhKM23Z%@3db?_M2@GVtQNIBMbOIn z9Z4yxg9U>8nt!1L4U+(@NLg~oQHG;_hwyteIm>{_vnRR47o?;tlo*;VQX)W1sqvIc zmXiF*<)k2eImu5+SaJ$m4SQ0;q9rMlTv=2ZD9B=Ju`)8FzxN4PGOGH=ZnB4Xv+h#8F_qgE>6 z`@JF-kF$JTOJ)PEi)!4gb@@{et*WA*!m^lgseV9t&e)S6P_bg^PI9SL%ACkM@jz0{ znlW_>psXNoRdmVkR4w`%tD&X8NJe0$<`L+^}8TA~)=~2^7Y*HTZmd{qpZWZMa=S8Mli(PaS zrrRMk-9A*)?RRRrZ6QFNEa%}orW{5;(OlXXyV}EQ%60j#5>(ai2zYBFYT7lY#3z|{ zRq|2P7&Y~FB&S}pntJFsy;JW1TeMmGt*MuHW#80mqIK`~QMHlYsV5;J7k`0zGYR#^ zpHgqiJ@d65l(5oFWp36-ML=*KE6@bvO*NnTi)35$%H4Wp4OiJGs@1lcgP3NXCwy-LsmnNuMIoc+tx!u(ZI3jxu*iyf;V;qD#Px1AEo()e zS)o+HC!9siRIb30f)<3Yv@t)Gd?Q}(W%>qUCmBSxZAZl5ezeNXR_K*(k^J;!zoncq z5pA$SuSlQh&Sv9n2}Z@~6v^h@H2XbuY9l`6;bu;NJfn`$csGT|o~!K^TM(`hYmVt_ zUQ|a@B>D=LpSh(RBwP5GFATtPVkVKe`Uhaiv;wdYmQQXr9%4mb(JJG)gdIKeorSN* z-%mT3qe<@K9r)~7z=!?X0R2}@>Bq5he<);>GyoXE zl)P&kh;$Dv-dy~ScXRGLmgTe?X;)t+vt5}UQ3`tPg)9qN`UU9I8+#3?T!0>n9Ltz} zOcBhn?1$f*>3#RLlmYb=>k$T2q{LO$u`brKzT;5-(g9*Y9TZ+DSD^nImVpsw9 z1;9UOP%Rk*XHf>#%3gzNiR0dW233`ZE4TJp2rDnilG)Tb6((T#Y7qg~&uKh;v4 z2tIZ>LdQEC4STAIqZqXx3B1?JpQ4ilmdxrhS~;{<`;h>At%6!xYqV~U)_x@LUMqj< zc7(M>*o}5ZzgEGq94&>Ubo)cfzd@AFtDY|Ay7#O5X4I^eQxcaolBmv7CN5hckD0ci zwmBIbjIjS#2nh z2)rEsLmE6NzmDJAI#hw7l~sw$mXjYLWPU+t5l7Zr{7A}JoroMH@cK#PKj29^2HqqN5B!j&k@~**hYY@#4Xan#AR;)o+@~GrRuAI zix;(VBKqAA>BE2VFkK!F_YDx!>-R1-JbImQh!m1#h%Po-@VOnM>GcQr(wj}R{wjRZYtcnFxP^M6CN+uvlnUbapfT>ObHYmt#lj@Lqm!h2!gfc-Rzg1jk>U^(8oE(p@x!gGBud|@&hjb!SXBJmfwDU`_UgNJftYf z*t$+(XXYl0u7!WACBe;QcD`*3MzA4#Kl<0*o!jjvxNkD-yxW}nYll&db=IZ*8pV#Y zslN_S`n7Wc&J7vPHjfIvQmvIu+IVbub$;xTek}q6xQ{Eh>`6Y9d^1fiU`94W% z2#&y)Fg6;UaTqr8M5gg|&6tQokkj{xf0U{5CDh_H<~htk7}ae-|E=a!2j>ntriS)WPj Lz@_z{9RH2mp5|MNdvx!hjRWtX4U zkq+y}DL-Bb1l{BJnYYMj67wq6 z@}5wO7CD|7clE6h;Nm{oU!vN-wYUA9s(oSEepjNXH(7Cai$fpRF!aiAcFM%b(YGUt4vgE3A809Bh-S~k z@{T>)Crw%%dWT!PTeW`uX?5QF1^A>tGTq<$2g$!K_XU@5n0sK|zQA?yobbJlmNCQaQ9pwhzBfNACO5t~(@KtFF`Y|g zvL-&*9K9XZU^E9$*{$mQe>R8_+F)$jACD*B(y$LIhG(ez(h zSeW83rzU6bd?__Yiq{^Ci4|Q~v}CAfgV@)?g=P3S#V?(jXv5wrw*O6^>UB@>#k{Dv zu|xm!Bkn-V%;enDEB437=9DG^`+o1C+o9GR#&3&xaV0!B)SU=az(GJo;`h@?5c#2+ zIvQCCof8smv|Rra_zKfztz@EJRnV_bK&y9HQVL*czn`8g%^pWv?0aNYD*Z^e`KYUP zq?;;jVP{!=W4Q3AmUksHD1Jq+ye#myI(>1yPW&d!`#zPWS$0yw-}=;?sByIqco0K^ z+Okvqo8S(3WeS<|q=#dqhq+b{Tj-&G*px(D-+YmbvJm}J`W3wsivm9Hy(kz9x6G05 zeGUhnBo5XC3LVP%2azy^V#y}i=Aq*F6Gn8MFaVg!A>VobEqrwtJ0tN-!1s3I(N6@8 z%&>NUxa9G;W7bOM_t*U{bD+R{iiKwdXVT(FQ|c>t$V18#FAz`1D#iSj0g?9-!8G5U zJiCH-aDtBK_*V=V=)wOcd#bv8RfNsvhJwGq^RPToNLihtK2(@q3S}zWM?i+b#f#N4 zmYrCj^=CJU1v&i_LQ9-Bc6}BgSfsCJ=9oO`!p=MFF9d%x|WA z(Jgu|F_ZM%+>f3&Ic4ekEJ@E_@1y63KQ$+7au)!$V(=@Q+U|6EcBReOBt6TF^$%mB z_oHWI4~w2%(eHPDo}Np-kesgmX|k7|J1ly}AKJGc zdWOJuu{@gda=fR+Njgc+d5_1lEdDeedd{@y`Q7Mxtbp;OtU#Js>Ip7!*#fRU)~eU9 zX3?B#u~)X@uy)CqnmJ~*CNkwnw=hH}50I`DknkyYF5nxa&hU@YfT}`8;_?hMGS0eNzj6&l&jG z_m2K}f;1&1nT1b_z4U3#-W_&79WH;`X-Q4W1n!zpb$_&p{npSM&ilAGU`c^q_p-DM zN*M(=`^b6Fg9m!Z??{Zd1+rjMdC^~fMobek#6lQ9ep~AtxKY+IC6cSIZBwv}8uPv4D>&bj$$j9ftoPBwpOXVG!%qt#+7vz6P>qbCL9|K;vo;G?Rp1@4(-gaJp+ph1I1iFMSZMw=+N5{Vihfrx9#cyp;L=Ywt5p z612VT{l4GlPcrB1v!83Pz4qE`uLn0I3JyPJ$`kz>U_hMtO6EL>Dk`H1-XhQnc9-iV zC)aq%HKw~Y)?iZqPS)Rb z%ueq5yMweZxc-JdmbLy)q2%YRzmq=bYW>N%I(z-OI$c!O7!yF)=cDK?l;z?>c$pZ% z+8oG(T}0|Y3GbOfd?G!eGD0_TPL}2ok#H5WXBRarPxFemKMAk6V@DTNR8x8PRjf=` zF<7ej0ad8J(Xge84nqzz7JB1ZJ}F`k=8D0^GGv}#b9{zV%{DH zzHFRTkgjUB+aiZC5hWKnjdB;P7!&dAP%XqxNSxv__*f%s2fvDov0tDjG#80ngo3Ol zMj*GKp!lS*r_apGzu+7#<_QQY3>uM$zr*Czxy9UE`Fj7@>?cqMSXZ&5Vt22Np<^nm0<(W-Emvey9e30=JyPu0JVxZC zRw#rj*dRhKQv7h_p<8Z#Et) zIM~ng+ArKD;Pr$rMQV{Nn{XI1(Wa5xra|VWD7Sz>KqN9U#YyReIuh_+2s0+>Uvj>m z<@^>qzsl^d15bf&Vdc(tyUksb>+Pu@$)tMO9X%oO^57w;Qb!L@xV;RPP(&1R_uNCj z${_Up^ul*vg0frFyymyw?yTJd@+UfL-);Q32JdLmTKih7pETO^syWfV_LuT#L{4DN zjOH1TK67+?&KWsFd|6Bebxt0iIwuP%^dfouB(wIVdi&x3QRJulDq>KNE~0j;y;t=~ z6){y2Gu4Q_^8o9$NwQf#4anaGFX^hTu^uhq9W#h&+Y#l57p#{9Lg=~d140hVZ;fuZ zYPTcG=a$^O+1qP7{@^>^00|>OXVwh7%F%wM*@2PtEAgcm&N(mGTzQHRm867tKO zUkXUI2$cg+h6wd+($z*Gf>Z%J!Yp$>A}v1=KBRK9c6GE#rp8h;Y(1)Gc-m&;4gYv%R6()c{?-aL2KpxTNM{6Y+q2=;4LHLKUP}Z|uxeXi6*)i)G&jfo2$z)u=g!*K zWPRw2!I!NC?ZDjZX{@TNmOBJRM#oUxvg zNk!tGP{7ItfK(2{J*^G?-N|pPn)YTlaA}_QmddQ}i=6z4&`a5MVz!v*_nhEO*Ff*6 zeG|Gwo0+Ev=&w^>!=OvsvO{hRZ}xs2{Qx!jnG} zdO1EjJqXLwRQ9zYF87|qPE5HyjSIYIgoZ$U^Jo7t-j`c6_Zgw{)dxxXu@%BD(=#b? zlI#-Ph!9c(RKg^um$7JqAl2MYV`70UgGmx=SFA)*(410Q+~(fv`!$?2hkfYoa_JwJ zPch+_n9FT+_^136Q#SdRhF39%sXTSNWIFdDbwGbt_&52M_Vg4j|*D z45D7S*Fo2$z}!3DK6kI8c`;e!WVPIte=$9Q+2gn|wuX4jXW?1ouwEeMa$^f**OJ0) z%^p%kcH*zMV_+)dm3vvCh0Jrd8^kL_5rj{M#`oaKUHop zdy2o)W0+$v-d$RE753VKPsTntIg<;xVoeMWG`xK;5dHa|GIn;2cCv*MIB0pxl9P!e zbla>|dm&Og7k~GWPS>%+zPU%dg2?xtDqVdhG?6XzZ8H1 zWeQ)X&~=%G5;|WFjQkKstglP=;9Da6EU+{G4pg_r&u72Plm_KsvD8N`5(mpGhWLM` zB!cG2_LCg-Gmus$-XkKzUrW_{O4l2U#A4q4EcacXgr1Lo*YY(LOjE9x!g2lCht6%?AT~}H7 zFW57#ek_g&pZlGfI`?67dq=*?C=76Auh zn|ZZA1qX!nc$iWA!ugj;AT+$az($515S*Rk!>H>zZ|ULu!KHTPp4m5oxo&&XU6^%0Lp(n9h0g}^c-CxnC5Z|(+=yzSk2H7o@ z-uQm+YQ~5z#d7{ip6HlCNps>e_oEn?8QfLf=VMwtb|-(XL_6@tZ>;Cf&HMRt-JTi7 zHLvh67De4_-sGY03}fb71lMTi`5Sh)mDt5(yygS`{f$rf5jp%^=wbe@A56PP+8ncH zHw62G&Gn`0qMnZAc{HEc1KyFL{no8MYrGc%V}4e7Ff_cfb@otjwm#gkKO5Gqf6Gr3 zQ;{d3aNVEz<*3ZXdia_B9Ao0vTu)3wkyyu?|ESzOyPi{$sF?<=VG_e0s#79RGBJYS z9)J&J5T4?Cd)B)t!sCVT7)`H}OA((E3+sXx83Mi)p+W8~<{_bE74M;$C;FMpRedR8 zQ;B67|7uqMkjfdWjAk!6xS4YAbAL<>l5p#OE~lyjD6tnoXbR1?xffyL^Hu92xzBr@ z?s_WUpz0!H=)Uk_cW{kdl6yeF34vn!;om6S5dGLWnXwOp#KtSVZlpoX!V2k^#Q^1h4{51H}P1s#tSg4Zia?1WVIYE?CO&3g97a)p)2 zHKXxQ)=CxgGYEeWB2jou(2d9s3tU!63Csn)z*z-cOHz4y?{jySb{I_`F*g;jsza4~ zI}6MH>#y~4|FE-GFRWUl89s4l$P=hye(slMXCn67#feoeN^iUu zx&o0w)@MY%Bp85g+T-^_2s(QT`-4)Z-(_AwQStZ}yGC+APpA1R5~UyJhg_BYjYV@L zkrlt6qk_4~9_n=MjU&27tG9biR_peUGkS&C!512y&xKWdvRdrYh2|?*^P>Xjs$Jn0 zOlaPh6U^n}hAWYh0h2g$Mp>Vm9ut~>qgL>%@Rvcv-(h^IX|KhZPEp1V9X?%;`1 zN|Z^9syv|^o{+(>$cxXE1og$dTIi`nfm(0&j9;T>(%BiP)ML3zx3h~b3e&!TLFNa- zH~3f14C#(vt%3%d^E@3rJ;Bo^#x8|I$>3UJe#RGdyO{7z{TE5-ljm?GEHD8%zPsvEW(i@wRriRJ;a9KqIp@-#Q zZ{r6V93aP-s*tQWq^L_V@($_0L zFou4>N)F+u&`qvE&3W-Y+-(sh(%$t;1zCVmPFSJ1G3>AA)e_v%>`=R%vti(xO=c&# zN;6oJqOA29epl9}sEL=*cdz#T6t&^*^fbxkdO4)Vd*``ch!z}~JP;Mc48YdMF ztWHqB$X0gpR^_~j9eOqIqohyu_F&cVsY$I9P5=DnUl>8ad=@FtRYvo*fH$d%brPYR zlroliFzXOI?s{*%d=h;u!i;yyM@6ARp3-e|&g>L9KqGsdFfpCOJVLEvZ}LYO2V0V9 z_N!)5z?$Y7H{QKtQYPpn4dXQUX-zn8z=>M(tuuSW<{f_IqDjGsKw{xa>;wqo6PkGDU% zOH&|lRGf*!?t@NXqR%|pyxwDe&1;VIn?rr^<1OU0Q#|Oum{W)X3*Z`a&dYG43Zs=W z1k$Lpe|DAiU5_=ux+#IF3Zd! z%y(9n;>4UHTV$81THcW?;_3=c#2O~?s(nhaDV*>?^x*dQ1m;;qhV!uV>CZj;5UYTOqCk;?8<)oa$N_}MCVSIE!`ScUiz_e&}_!t zdxk&MDK|W%n_Fim{~_Y-}Ae}FbsZjUYX8WEIH36C()!tdnd`S`Ou1# z{gac^)gG%Sj!So#uVZ&#x+z-b6}Bi)Z*SmYmSw{}5-`64r&-xHM>dZRmmKv4nlahE z;2%rO-KA~nFcJJOqxp9vCimiudxa$=JA0WoB})zlt&=QjMllGOdW@mZM$3AmL*O<4 zYJOPytSB1{OZCEwhGkDU+BpT7L3h;Bjatgp26r@ z1*ka3u`8H2yQ(U6v{y$g*PT*_0RYZ*MHZ4xc*1}6ZMNu@Fr%mtfD1a z)hqKjUyYr3)lgT^Ta7Cn^vTwX00BYc&(&!FR8=gG(00UN1;Sf;!0}*z^E2cCeD;_< zM38tR#thocIB`LUZ$`)xt0-VRHZ#zVLteCCaIa_LMd7x*O8nwJo85lp7W3KQ3S$)p znHnZ>xJp z)t=c;RJ2g@y)8JRSs@2YamV3mM03m&X#JZv2E&_(j`64- z`f@d#jn|6fYK)a;tnVVmil0Y_a-n-277G&s-XO8y1{kY8#PR+BXrpID?8mf943tkQ zHVA#>g?ljNThLfF*&TjoKv+lIIp0_{Lc;BYcV60Sv-sgZhwu5}cX~F!k==Q?TX!%lO zi8%9(PCYjd{<>K-b(s3@nd=wGa0GL0M5I$R>w$w-PGM_jm|iH7I+6k=bvlb*XnyRd zU@7>uRD*^-61={KOwfqu?^!&3S|3!PQtrn4J&slYR*;jc_WwJO0OX zUM*jw4z$B$a$nRw8>)e4#RmLud>s1*kH)H-)MOKN;ouu{@ojm93c5gWg;?XkJ+o_) zlf5frvN!gUK)iZRjm2M~7JL8}1e|GqUw6i6a`7S7pFa*3Y|1EfkSWVVhFm=O*xaPL z+w=q(V=wUsSc%IYY}HXU@FCYg{Ok_5>*Kc_{3<6CHa;QjQ|?V)3rs?*b3^cq`PW5W z44#$Tm(n`n!xaily51W*}T-mkUj?Htjs zAKjClzEuB?2ET95n9(B>evLWsGe5tdA;Q`p1%4-X)z7`(r=MCLQPYJ+zR`3Y_{dBt zsaH}@6O|ui<$iNuKm-s%*tD#T|Wk z(H0N2XDfqyS$#(;gBLi;vv^4oZ1Jv^T%WHIkHBnm>;|%0@Qp)?tXm2@hbXNUtw8L% zOQ^RIk+Xs*#TO|167IG;eS>Gp2k|fMXv9BFp6E?!4dMc}>LL?o3yuac2dje68P-*z z5kE{c;!n~V@okJTc*^)ReD);zgnq&zD6r3e7BaLfd-xK*_Q>HEIm5qP(|Ugw!>>u{ z4p*mihsRTl=FlIc(zqpOKczX$HJ#|fi-8TYm89-)rYd~Z;`nin|9F5Q`olf7{_p|& zwv9@Ecwium?r^YAMa$yh#qcgZS7<=IZ}@inMl4S!Wm;{BVWhU&s8L+4y$G!uH1ohW z2iGFvV*sitXReYgy*#;ZDQWOuU+Zcpzor%;+dD$HhEM?-SHDp<9KWCDflC|> zIORu;_d!ga@K);n*ip?dh|BG$_)iex)Ln^WjR8kHza*)hFEThKByNb-?B8pAEXaSP4FHPLAcs#Y^D@wk4+-22OH#N(bph5d; zXG!Yop^jQ2R^0VoMuR4yNTj26330a`hJ}K#c&x%`Sb@evZ0RE)gBK#9NNVpeR)v%B zd6i`Akrt^TEaQ&)9_4SBD&%t8#PyZdUtQqnk8d&-A4i?hDy;0RvemLfulBh2MhjL) zYgT9Kj~B_2ZqZV8upDEm7P$>?`VAF^pGZNIpu%ijBFQwGe$S`a5BLMQY!VeR)UMUo zopCP`3Mb!Isc*So#d*7Yz0dh7+REAG*E>1pX5>ifG-v1hnUix=7dew9lE2v{uXRcu z{{>1WYv}GbyXM|b&BC^JSK|LFJ-cV3|0R$YZ%Gh#pQCV{HFu+&JO`QYuD+K!-(TtG zyU+Q)ChI$mu{M(UVWY<_{rCZS@TG$;uF8H>1n6%OTWm3B2#L5OegO+omJxn? zLA$z^(GRQ+lCjr7vr%weLTp(#bb&$!FJ&MofGpQZuxI{nE#< zrS=*4bBoK7mr?H>mI&|MAFc5spE^E_Fc-V!3-=_jTdu%7H+<+AE$|%HMg*-_qNBV$ zo3;c`3h$SzjJcoY$aa~}412D6tG~G^d@G#!t$vRExZ=EbUGcg`yc&Mhx}jilGhPN! z&Qab5rCl_1f~Pu{Bu5yENv`}(t@mPGj>qWWgm5b=q%THq_1bU1aauj$kQ`g}1NO9E z;J}@hgXhsU$Ir8^uNQm~S@qE@!0DsHS9Qk0@KOxLFGHg*N8F|XFe!WsH~yOtTg=V5 zVL!TEc=7;6c|@nGJpL3iHLSi^d8QW*$STJTW?GGpJ1A86x}LbyO^NOS_@SIC6bl*K z1gP!rvSh_hvxY?yQLekMDnu?*glEWj&t>p;V(*yZ@9u9A{!TG>M}ok*DnQ9ES+^Hw z@`qXFr#j_}l*wUnHVCY%%60j&Zp#-rGXkAsM7U$%5!lworqFyu%EMP&a*G( zy)n3hb%C?t^WM!KgRUYm@Vr#=i{abO`^Y8!H_zLjO4PnCYR>Zr<9?hq>O7A%;XJSX z6oio`eAdkK{E&7}eOk*-SDc}17ajz$lySt6RV4Pkc>1==j$mJgZr#^N4}nF~M-VC` zk>Mv0g~0$8Bpwl1gP1OJNFs`-0jIXP8@QY5#+ zhe)HsQtOEad5ZTeZqpP`lLi|eZm>) z`quMw)cVSt`r65;>XQ=L^(E$fnw=1KE(T=9JLma53A2TQM$;Zx<2}Zj>#AA(V6w4j zB|KboZ6hDMIC*T;{I!p-a?6M=^5O`OvYkFJJoe&cys;xigk#|poq6#8(Nfalr({`s zGoOw<{1-j{w`3oHJz(0A2Vi1{ZkBzqQToK0y^8}d{F?af;j1@w0M0u9FBB}a9v5`# zRL?v(mhl6zj29C*a4~*r8kdwjQgq$1k5AeYXV}LdMH-;&;|r`QDQSRXPpqT?ry~tW zH^TaYztZ-^1@>uAfUP<9@x@(&q|5;cHwiN6;x7mikqJ0W=Gqf$nLyf}c!hu}%R(Np z07BJldB9BM-PjW&4_GU>f=_9sJLq4$_yOLuWZ>NRDdDvOemQ%}Wk=@6tV1VZN0Nt5 zwyedn7G&2_CWE(EE4|?GTZhAcKU{ezR2aV)ea;(N26Cb6v1`s`{j{P61?jnR{EuYOtC@(y*)N}H zmf;BjltS6d)k+dT=#{|qsPv%7sqZNCuM2wMh*Z9Uii;MhWH08(1;SNlbLHqbk>Z1` zE{9())6$h{L@f1oa~QVzEv^QH4oH;m4n-B*v2XCqt|7j_MZjC;$XS$|0^X|x27EtK z&XNqJ(OrpTjU|qpr7S6D8A!E*Knjd<6L&UebsGBBG7GR zbCuvd+o9Gq<{;5ys@-YMhyCqFVn3>jf`hElJ54WkJzQhY;v~>q=k4f`kLFU!>?(OV znBg|-7&@GO#h6FnVgG}E8^_xFPZe_xA@nKxm^D%}4cWJimlF#Q+&X9U?pOW8g=@DH( z@&dEafni6r_v|BpSfEY`X{gyPw`Ky-oVGIw$KUY5I9BZsj(t%IepTa+grDUVZ&kfT zh~}{eW80hrE-)7NI6{y)!TZTjEyA*q;Wzh3NA>oaZ*l@HPW~G^E>Vw1^a3<_dW3p+ z^yuL@cvI#LVsz=eI}_*pB<(MS?a7*w6-x_q^_bA<1~X4Fjnd}IdMCQ895Pq<;b|-(KS&L=! zl#Q@cy=D=Q4B&P$s;*|}&!iI)u+XaAxwe3jG>e@9;hD6*4i5lB*Md7AxY9{Sy>o*pq5^rial~ z$p@6GS3N*>YoVZ~b@xO3?OZ4+@}JDEIMFaen=#_0_UM8}b0T)Kn5@1=tb*E=(Ry#R zc4fW&>U}aNh>)f+wT+F3aFi~hGthv)jnSygtmU5!`Lg%?R88N)rzDNa8*e*Sf44tb z{6zS2>E=j>>9*0VHG^gX7%s@K_WdXA3^4 zg=Bdh!XV)}o4%G#pM|rXg7ADFgu_Qc7xrW7qf87#;FPJ#%VQCCWM{{}ZK;X0FP2^= zzlGk8svg)E;wg*s*3jta#x?zb!u7@WgohMbd=VLnH4U_(-!5ULr@hv=U}uiwmkase zKrFc?n%)(T>UK`ocZp7NXo5AO+PX`az)W_rhKXC(Ya?)(TpjwjX^DIfofy4K=#D*` zI0wI+&xp$PgqEZdSfPzNqXR$w^wkdb|YD#c?$JyOfUJPet= zQ$E_q{fVrLALJ2S3LR(eUDzrwcE333X|r`nQ@g(visUTNv^vENM=s9bWT+|x3G0i_ zwaq3hl&m)ljlhoB68_ltquU`VPqNY=V;zk`%H6R)@@h9a-)+)*KDdS-xqxulD`+}Pcpj97b_a=hmlsBIl~RQkfPK%gPN zNpG7aYHR0IqV{%)*I`r8E>og1T6`UeThxGq+*)D7r5_ z)GsE;H;QY&Ld+jqr5osDP534d&Kb{|CrK0uqBb7FBuMho^_@Rdep*EM$bAJQ)&U^h z@IG#kX#BF3Ks&^d%U~|aBmNg+DpYoqEVCtI1+jt zb+L|`Kritv<7?PXKKtk2R;s=his#q?dJ#}gf5aWx5SmKm;)N&_;K1LD+W=xWc|s>i z3+{&J?#O6?&FCvS`Ga3%B%5vVLP_>CJeSd*BHTNg&l08XUSR6yh4ENtl%HZ&g9A9h2_wdCF$UXbv(97GY$D9|a8F`5Cb?}HCy3i`N==kAU!T*S*6+GL z(}6|sd`81z$A5(kMh%N?Q(-^tN5}f53k5{2G4pb|P)HZ;LpNr1LCXuhcnnwJtgb<* zr`@btS9+sZ+=GvoXF9k^OGvg|4x~Q|mRk$_6pD@zz*kBTV~+-gc_oZ73*q@hD1B1g z7GE27?36VCEI0F4E4{#C&+$sS>54S{uu5>#7BkNSq>hJJj3(0)xK+Hk*`Xb&3mvIx z;k)8##N31r&Addv=|X9QQ+b==o1L=Q8VJRKPzGNxGu6SJG?8DZsD$;1MW<3kc)m7D zRJ_U;4-bmA$OCJ_Pnx19tP7Qdwr>0ir7x+Bf<0*QU9$r!4m4RkD9QR--x322v#ij( ztq=v#Lc$;wN=Q<1MCBU>UGhn|ltNmcqFN8m7V;nt5F3QT*}vSX*V;^C1*0%e-r|Ly z(VNELw>fi{p={_0E@CCde~{9>J-wmjTKt)xNS#7%Q|ZMG50GxRvUf5Wj{5Cl>T)uI zTMU_6g-}R9SvT5>@djvKfy3xQZE?;sU(5{EWH$yY=#J3R3|NqIuJ!+&`f z{&8Q9zk{wHrwxe`#`!VxVr*f%OrJBO8b6D*{><}e&}iAU5G;C|drI0yLD8H^Nv&Te z0iY<&nVW^B{J}EDIrs;P^XePDQ-1BjmzgQ`K{RI;3pbY1oFPtqhUSctf1^2*l3r@b zWlD2q@dK3QEsMeKq!08}7;LMDHKw|v6@`~My{%dv0*!rzHFX1M+>xcAv zLKmc$gI50V2~Dzh6+74dyaxB`>wa-l5L*wvh!ukJ(-=QDT^jkQIOifl08sba{=AH< z>=V9`zRH&Rr`mUq;Tn5e<#zK$^M(GqXFo$UiVqx@Tylx+nf*xiB{r8x==PqkFRy2R znFZ8z2|dZUxE_RUqP>wdCplGz!$st!)nrMoL6o8*0%GbGAA#1R zGM!eFnlkRylZroWN#Bvn;ApQ^zUprEM8EC}?Wsi9=XOdeT^|&DtfJNKz36uoSrb+# zwN*s{W5Q~5!B#W-0q5_%=ZCq;f?PgpsG@y6&jT6@hd$i=>*rq75Oy7tNNW6u4M; z*LLT_>G0wmssVSbAJ19*VV#=)>y>hahnnp^*JW)+Yzcb3m!Pv&X9F0QgtPUD($-v= zZu}BNXD6anf00iW8_>hYC&lnIf^*+gwcZu-4heVe4}FBz#ml8H7>m|Y)QJ3-%zD}S zcxjH==8*&l1&4?TohDD@o0#+BoM$=jFda$fJYs|lyN54QIuf0nhz^tox@5LVyt>4I zp^m=fIa8l^&Grq!4~4ls^Z_2WwwMP?HyVp%1kvF0uF!c+EpQnh2Pefgk;M9Ldf>l@ zxbkTf4?i^0INy6lurem(S@sv}lG09N(JJ1|+n#s1Yn~Tpa34#zaA*)2O4IQ|unsps zla)L&`9*X-2}8F?ZjYo|Xak27_NKI;%82X*AH>72u}Fdeidz-&OiK1D?2*FlCHhFj zD?5a8?BH81hT^7U8$lD$YGSNYcrTBeh3BZ*JS zD1qN%MkVisN;U_ND)074D$-TX;_3U$gD6@GxaFV57jXknPEW*!zl7yi7WK8Y?#vgS zhB^8r`K)uoZMX{A&n?fpmSL_xXN0%%nYky^9iMs7?jVP9ZXjMEek9qA%ID{N7asuD zJaG^#PCh*DJrlWV!Oh3DiV(Qp$NkpeqCoJHO#Gu0UTZM!bvoyX#~BJuCjKO5;JOe= zW$wf7&*%(Z$0!}zBwaRd^u&8&dP>VKaU@iV_W%T9ereD?P%f0&+kyLmxOnXlPqoLH zlRV}ace4`mYC*N;r!+i@&yoY z=FQhjpT#*8*{#VQ=>`l9Ms27zTYiBS-sKx~hZMl1SK`4y6de2lY^j3^-K|pPyoX~^ z-qQeDhX0@WzXHF}c*ge+cYlp{c+W7Jgp3P%ihHW~2_W!sXN2yM&ypxYCn9`YjRwpS zR;ov+F6CHZJLAKr0|Q(#z`_2e4Psz-z4=P%v$N-SYWEJouQT25*70NOA4h@uYZ#Bx*y#K|f5{~uw7LETvoN%)yqHUcp&xz!ib66hnvNl)x2Ke{rN#rU~2nU z4R@t%vU-Q!io1^hh*-?PO4jUzObgO?c)*uk_4fRe6zhp3>Uc3_4o&UoktfLdAPf3R zAuzy4Ienyi$I!h{&A)6kAMceUEef->!DFhkJZSv()1olWa%EY<61DnRH+oBBl{@AR z;0)xBjmMaF!*sn&|B4?GF__a{0YG7|`2iLuXDHU+3#xfNArBrD|7xu22?!Lkj>*5k zx{tuR*9tdr6t4EfpF~1njh67G!pYauv^OCfGIe`Yq=(}0d~dHg$AAumw*V?fsQaW3 zPf8sp{;TA83aF-xF^L#}$#-4c$e%gKVeP8~eTw>&Sw66wDW3_Yh#)mD}f@j>5LN%Wbn}?5|y&4Q@@8%$7m&`vK!4dPX1nFJOf9BM$7F@`j|D#U53W}-b|DT+Cr9Z0O6V#7Q%=M15 zUpU2Kia5O@G7sVoss~ss2LV4mv}6et$r3t=sVU*UyTK!Zb&=j^?2zM-UQeu|E*4ag zOfO}%PRr9$Y0;cA3?eV$unS?j67RHWTAR&Cj{8w zC?x)lMM%cu1ok18YdJ!PnKoK6utcy`;X}QRNN=_$SP$_|R4x0y9QOw@&4XYJd_oZ^ zCt*cL35~EvsIsIGTGNPZqzC3JVlm}ve!g+tL1WdrYCPK+!#W$E;pHzw8$aXaWsC}& zeoh_`sF0>W98Yiv97WT0Y}%4#k;wT6JsVme9q4EIep>g(Aub_ifVx59e+1^fNE1EV4UZ6i z+98v$A8s}l#7zI@_#(r$z|{S#@kP3@r)s2=vhcc)3jLCOPT5n*T9DK0?@3^TR#Kr$ zxE`?XRZ^jQoB1yGRPt-*Jwpf82W3x{B^AoCr;^fQPn8r0=)-?XD%A7<-*crx%jrW} zDukwoTzYsb)?>AS14(5_)s=LpKz#alYee-j4LL?sjXauN%2T$&9dOE`eL3`mlR()< zvetI9!ZO&TEF~3(L!Br84faz*C#$81{gl8hZ9kQQUibmhSyo@je(E?@vrvhy?Wg|1 zij}Y3+fP03ztDRe)}oIsae>B|0_GG?%H8RW!No+kO~Rph4{%P z(UpDFSSQax=DVx!70&lpy7}JM`7YYX>GCv|(U14IJ(Yfl5=b}pQ}^NHMt5EWsq8-) z_EQpgBiDZF0jg5ynJwA-SL~-QR&LJ%YS*wE=cPn^DG}X76!g0h@hST$t)cnOmB{9X zP&_H(ORnn3m*p<`s3JaPKc%brxm0s-t`_lKP0LA1-gutvUCxI>R$PZ_pbPt{ax_Nm zzfV#kK4m|34(dRe_ESHJi!{TrpZeLzw9vUgW=Yvk6%P7e*iU_yVL!FEYx}8U>Ekf^ z=RuSd~L7-jM`NW-5VdnOUzT;*cXwGAD>PrQi6Z;C%Q^Dp2aS8K~INK?N?gLcVCcxl0<3#2;LJbb&v~cyq@IyW4fVxjN@^cT}JzWilf{ zBso(33-RU%pQz%^btwCgbo|6`3;rmvPgNK3<`9dfxMFzV)HSFtfS$-yj(5znHA3zu zkJs@b>{s*Iyd@?0k&1iO%B;l1>wv!f{7QOq6#tsO|Mc1Zzh!JiC)DVzsD=irrGe}1 zWffT$pzRqKpvMd*Ceb4tQyVfI9Ec=KVgdM!$RIi(-HCifJ>&(yD!W@>1T`7gp`IJq zhn~PmWU`zpA-5W0y501a3}UVq<;vV^?t7@mkrXBUviyLg?qYYPl(9IM3je;9yt(na z{zBRpjNi5Ba>c(VpQ;%T!v0Qp6xRoGA)sL>vzQbredC1Ui49F@9jmpw^tdX>Dkfx%dsjKE+GK(3dCPoAeiW4Z2^Af71)wUjJHG)D z|J5&2QH+Cqh>4oa5{{4uj) zpbim~(|(M~vtppKC-qA&6}2A_-&gz@rS_+Hp)+cK@>;S#739#YNap^Op{C>PPjXX; z=lIP1Y4B%g-RJI4ugP3U6xI0A_NQ5BFQtx3bCf4Cam~l+R_{-HzWn+7lP_m~Qb$`O z@^w0(cAIhP!P%cs_{rR#BC`nDun;`uGFv|;Y*5Ej}Stl#52}B zKae;=u9P_=YJd8W;#u?>ogG3X95m*Rp>(@Tm9%)<;7m35?K} zG61ns0I_R0^q0-8P=!fnhwCi@ZM}mjuk;nKvUi=4IsMg+7)KRzAF9O*Cwtg>*~8vZ zd)Ripf4yN(n4$NIo09w24B5XfA=TNx6xu}|aP}{*R?{wIxHHfm#Fo=dXa9PzjtwZm zU3^yYkK2$qF7>V&7 zf=HdzM+luaf%&G6eOAh1Rbix*a=I;Hq{J_?`vvnEd`>?eZ%DS=6YYws{7{q9BtaEQLIDv?%4nKJqZYULAvp%(Cnst2W<&@KpjDs3XGk8t#zd8k zhtCNbC7z^{_nD7x{tolj3%oaXH`I$3)Kf$AUCX?HU9P_*XqNm-0iTCSfQTD)u}0}> z^|8NmD{T(biy;WW)b^KiDS{eHTPZ> zt6@LAim6S`r|3oXu2Rmj_DN&Ig&~l;#^GrH`o~BJ zRM-^$BH`_0-}0dwPvrJBj0B6P@Eu%8eZ{&Oh2UU`brW$>QgOPv50!uor=Viz2t`Ll zi$HE;00H%W?=iWv)fdFiJ%{sA%iRInM1(jGogo|yofzVWTj8}Y^CVpb6#XW^5}Uim zHWW`Eif(s9jGY=~Qvdl(H(JaiOtlNP*QC4phMp>L=mzOcnI* zpN8RWQ;!Zq!MV);a_~Dk|497)5uJ~Y@!2<@143nE@$HXlEKb*`=SOsv24&69%6Ikr zEM>FF#OEq1w}zL2Bog3octA~)T%Ld4jNL;-RKM&X{Q06h-Wdrf z8ZM8pjTFaF#H)Vf4=+xdx(ei$VHPFi?$as!fIRY_tS}m`qhSssgDivEiseJRzrJ-L z+OgHs_>a7Xqmq%}Q zS8fYkIb!e=jpQ=lnBDvI_4!NWPLfE;>p2gcgm9$Th;UIr=z<=Pb8gZR4RE%v>@Bu^ zxNPXw_ffw7IDDvQY#uZ_lcAu-%8j9s#-H|BBdV<*N{^&IbKAacr*~9t3eHN_sq}29 z(_EK~&aCP?Gq*k|O&McJ9$!?U%;u`I<2%RRg8j(;-n7nW@-hhO@c}lY=?&?Uo~Vlj z?!j7XWniZy-XD0IAHiI^$ACOnu2HVOf=MI7ES*lU5LwZY#g@T8=D^A~&-TWZCqxK!Y6Z2s_ z-pamDUMU&7nrEOl-J+3v;APeq=ij9et}bBdrmP{wi0dNbSr=A)nRQu!vl;}4X0`(c zh)1>1kw{>(^CrYU5?JrN$#xkDv^a0 z1?#x^9}`@>WnU}Zy~AjFhYZH5JC{lR4Mx*@`c)O*5Ijz*_%cbce*95OYn9xl11u3f zCbE5?dU4?;tN<;z1o61tt*jqvFxuZ2)$$t5k?5tCWT2y%F-szluyv22XUW-W-~*^ zg_89%8FA$yWAS1Vl0CU&-^=bdTK|rU(A{33A!pa9=Wb@gb9Z#q-M(nS9cn@ZF~*_9 z_?xe)qtfN3stk;j5A-30-STcX13rtDXf1q5M)CNA{OwfFJUG%A;e=RMTnJBZ!5Eyxkh^czdp zn(3NzVX!n1k&#eg>fT@+(jB$(#KETW)6diuXS&CRk-B4(&+={D-A8;MfJ@8;!H$)QU1YX z$TCvSqV#ZhbCFsizx+qW0y14@sQ+yRoLfL*HV7H_ZxpI>)GtrCDG zi-qj6wxpr>kJOCnIc7#>j%iqC%Di7;JD4$_H-#C_{Emf_?5ngg^#m(im?A}nl<;Lp zv3!<;Q(uinM$0{!fu-=A5Pz)lZ}G&>Or5_SwVT`6J1(^Dz|HKjiVtjc zw-I5gbxX|L7=Cj=_~3w$;sUm?DvMW3_mlTC+5Es0%c8UL9ug<;(`6S6eHq4$K;5uF z=*OAsw(ORzL+o=Df2tZ^klnv+$2LWb$LmO&1<=Fl$1wc*U|_(j9K3-5mZ-R#bX(6MFR!j!mZY zp~)nFr*5<~(x&-4L1*|oS;~yH*gmV@=ks@OBR10f-77rVTd}>D?N4-{viUoqh2Zy2 z`B|>9T`!=2ip9HU8-vee@r=k$+GXSCf`$jyhEPBIR^;!o4ifZztnhj#s~Ohs+4Gyp z@5O#3!x(j;V(_H1W{1;V2&quZG><3X>W;^Y|Ha|)hU!j9N&6jihGU{V7Cv~I`9kO% z+3duzuo2lIi^>~*=WxY~gekvwU3AomdF))pCcz(j%N7O(`OWzyCVZc>1nNp8p!tK; zYgL!A2Bt@E_QtHImcNaWoEA99EOi%^sOW-N2o!wA%-j}31H9YRF{65B!gZ2XD8 zY>gp*KlkMd8}<%Qml@?hnq)?;V*1rICdqR__=<%PDytE>^_X$)t*tP5|<aQ?#jC^8mHJS5bmr)SHg==uNY?s^gWUdi1R(k?6t|2K*aO`}7ABX@{?n5OGG zZj~jZ+ApNqf0+md#)=T9KmWcV9Ah0EkeZBY{jfBEa+;(p4`2l+5BCT5P(Ke32KMWR zYtF&@P} zkN@Ryy8QB=^`w#zNIbsi)W9s(k}MngmAOgjuF{}%^@vT1Hn)>*tXdjCoFe@#rVFHQ z)^7p*_L6=RCkoIR>1^{-@^wNt z?;{`X1Xu*tay3SoV1Wh5Rnw`-$1?J$S4}@;=@?C~@Yg&PC6K&O#wJuLzT<^Uz2kU3 zI{qSso){4~%w7gf*`1^*)i~V9q9?H!9By=FK+dt$JJfh=(mU4NH)oiWJ?4cNr&S}J z9KxuJlxvy`d(m1s-fYZu6&o7YeM-Qv;f)9LjG+<}Yc77LLRY}NRpm;znq=KMmfl=^AVX`FTX|SHo z7bfbf!tJ;+6_jPkg*2mE07PfORL*)p+k6KF=&Ytje&jzs&3DYbZw+SY_&6iSAS$fHJJ^)#@-F^b^-yJsBVDAAX0#GfGAy4 zKuiv`zCNRE1-Iv@8`P7!aRm+Hzs*B7;`3S13vUR4St^U1$a1p4nX_%~f(qh)F5GrF zTGP4Ne8p_DE-$e1;S+_zT}-h?P7jN z{+?jm7sT%I0uex!gWfk&%Ot1zEWGyc;S1pPQ3!%+@kpfJ2ReK~q-N6={5lG5%g!2Z zv%~8W#-ejMv1n=^I{`M;(sw@g6d~5%`bew(aXjg)E};jCsgNIaK53f%A!I%A5xeVh zwYzcwGjf+T?(KL{Zo-)8gg`$&_qN96vj#3_d#IBgMmov%TTG8GXX_A60`WgIC^^*7 z1@Yl<0}}$5#e1mVeyWOO(PVb1B3a~fmV7tMoel`I(R31Qa*neWu}Cde-Z{lt0t;ET zRl%V>M<>dN&xCm*r`lyWBuGKeniga)lk6XEli1VvjMep_s?ry>G4$Ps^Hz(KgV<7b z*~SCt4=kjRrFZXt)%!xcnw2mJ_znh=L(P$S=Fmj*)PST4(-RyXyHu`+bWbeJcA6Vo zcY75hnW~I|%Bdb}5TVW}fk`^#gQ{i@=vPmqYdHWHG zlzA>yX7drB>7B5%a`STn9hPTnxngwfZ~YMTh1yHflL4EbFXVYuBfRByt~K)`>XMVY z&6~Rrg9*c;#9-NXn(THAUteuPFuK3ftc#8j-!8%)`H*O)l&nD#795lddjDIPC2n6X zrczX5eIik<9D3>Y!O%LqdlSL3i{2cMt4dtEAJm|}mx&r4WB7&HA zEEVM5rl^~go2~?E+;)s9h^FpPE};$XP)*X{kS0($nD$(~NHFxW=9C=T|5Q`D-2LlcE{8P1NnK`GB8jIyjauJb6Me}%{JPcE>oDC-ze?v4=kv<2ttnOI%TeSwa0$TAEKj|L)IdLnUdUPHMA#b zbISpjtgOkbtSbtvJIc(xMkz$VC}p2EN|C+WbHAQQ3_97?fM?yW~cz zpvxPrCfPpu*!_)G_H9;rN_PO&SP6$}`^UvxWpyxDg4VLkL3F_puuf*rnM7tzPBpx# zdDN>~SPDh<05^vxE+vm4EMQXFYtiqrAY2SwESC;N=sa1 z$NZHD;t_(swVFX?I0IAqJ6ZZ5o%U@`aHkuqstaKY z&y6k28UgZtuC)mbX}A9=Ls$PKE}fvp_7&V6v;9qn!zf01=MV~))+C=b*I&85|JK>< zW*hC+b5C}-;@OIAy3FNqk+zq zp2z=u`{uFHIbIa5rc=$zfB<;kuF`E=-gj?neZR*I9pBiuJ$hTdH*O^5b*AUD2lv*Z z68YH!@}u9vxN4rOCTB@Hp_VB<&AHD|-sa|srs|$bD507y4h=8 z>9Z~qW0!iT1My4s{SXYJRA9}NCtzJ{zFxUy z-f_?-DM=Y0?iue_qGod|F2ZF#rsvNpJfU7(C#|zj2zR){ zWN;aaB*eopuCIQpo$mN~Znb+o9=_bethDa#>E7n#5{ie&?MeArskA3@p+5rX7jqx0 z*VF$3{?KS^q}Lkf0~o~@ELVd=UfJ{^i&BAs43#7OM$?-lT(?y9Nw{hzM*0sEACVt4 zDoQuXWQJSv%$vRDl|FN+1T_T+^GPqK~dl%M5lUto`vorJ4+-NDX!$x-?_i89eSsl%6PZxo$br)0E{#XmpFTYD)@;;)A02kE=qq~c5@%=m4OmuvUB|Jjf zocI<-TMb=n|s| zD|(l8vDieiYO)MUT#1*hK6)ogX1(;Dzsaf&k%AE2lUh16U z>_fUWr(BoMikF&I{@szfeHAZtPHw!^IaRuRR=m`#@_%s36ZwGt=fq1LADbkQ_5`-X ztU=;GcK;Teh$2lw7OB|CVF4{)fY5wNx9ye8_`>zfsX($w>ba%b>Tg}=Nk#BGF+O-% zGJanK&F}wx{60(tKpJbsGMc4>C3M|dy_A>Cc(K{zzf9?uN&LcQ@<|B4RZ<{lB9wY1 zHJ+tvyiaO${Db7+!_;(r%aip<_U!r+b8|v^vJS=nnewKIu_s8W!U=ioQA^nWLdocs z7-}vk_(GDE5CQUvzDUln<0zB8+8aM}89SgaI;w}q{_qj@3jrqSm}NOrA2|k=*a@x} z5OtB@0_(IKG%#O}Z%pY6F~2Z9Tx|9brHNli^%bv1XZ9E<>|+fys-G-l8NQ=GVKm*s zh-JaPtvQX9E~(0&tzXi4I{G%k zn@j^9@xr#=>O~|sNE7igurpax&fyDPI(K3gF8%B;6Cr?aqKmm*j&$-8pkGEq%{?_Ht!1$`}r{ziACcRlJNAECq1@B z@$-Akz5D*&y8F26)=8Gq*6U)o7QhC*cft+NTx&e`hjUx*t|c`(?9+vZ6FWaX#@uE+ zw!wI8X)j5iVa_c2bcQ*q=&;!~@6qtVZ_X_>9^2R&%e!uaswCF;hP~I8eqcQIqVd@B z<8_G%p2Ojm!>E}Z>}`bqN>>`!l{ZiDBS#8g4zj@IWJZj3qDdH#*Sg0B5ySrnJaP_t#H~ zPsy#G{ci{Luep$H=KjWAhB!bR`WM$mUH^n+{Vj(tZY0aR*-pJ{Q}w!cmL8r|e|=uV z8;js%E=9xb;5UsWpRmeeCxf^^${F`4HEyT;(=rFDU(tb{($2}Ozy`Thwg0$EB-{Rl z?b4jOKrma^$a6!{6?{I;mthL1P)6{@LOdxB5WR`=#8`Z-I-wl(_fdSW!DOwk2>(5a zS%ryN1@^CdvqV)sRUQ&y`6a^IB~L2BdWKEV5%v4C>(~593E8UXhF4?T0gY`xq2Fqx zYX#dJ{j&Auv-{pj<6GNx@h?mzy)~h08i@pd@Cxe+og|kfT|^nM)EgVfRkxh*tV7@6OJmmJ6Bh*ivw#H;X%RSDqonpFYw?|kqS|9z;KniP^K@KgZS zAQ_SDLRiHR$RG#L$$VNa<}%c?Q)+g#Y1w6aZ#|0|e+sNY;Y(e(+2TCd*%LY_vY{e+ zU5JxqPhwHd&9UI32cN7eo`!E?hEe%%_^9J)+$q_<^J#3c&Rz{3gxkv$ zvQWAjNoGbo4Zh|PC{rxU<2y7aKiU(a^D8V8DHhT8JAlgE zlzDsX+!+4~09UjatHyZ5Tu+=A@LWu6Rb{L95XE9YN*s89l#@#O zcB*0EEH0t0L$u5J&`i0+vUAWo^+OvU*O(lqIjIU!C(2A zv3M=rvo04SJ+7wzAy$0ByS-db(?2xevrPEtso3xR{jkUfeWT4A{LDOFa)N#2(Csdz(?Mi?t&%uv$a>0(unE##Gx6K{8*ivUek=(RD_0}D#u?Ehhj zdmk4ND(lFR5_!u5z6Z~Bf{>j_J>SgmxYsCCaTx$~GD_Fy3|%821K>sZ<2{q@ks59o zHXwsLPu(#U2^ryDi1}iUB-E!kM0BEbtvOLTu45x*xWHK^O}$Q7>Eujh&XDpxb36LdQY@k;=4U>EZ!(tJ(um#n@0?=m%&p0V}g>{SOHf^nlC$1;WkHWkk8z2II2HD=o<&X5!)cudhCsP<%$ zh%qTZ`ZXXQLoX)Fjo!Ho!on#T?%dK~gv1hKF5qbhq``SAxT(}?pfJBx;QS>NL^%L@ z^N`?@hG&(9M~|ERokI|;y>_P$_@r9a-3H&RdPPTuiGDh(P_vNp0PoAN7oj4qVc;J@KcIC}_ud>H*nkCHTRsmN3Lyx$XUkxvVInav>=XQ#6Bk#wr22b{( zP0GQ{HP#@rO`ljfB~HMhkxW{rc4)+^E(SetaH5u$1WBo)@@mos5pAx-qT~6TR8Ryp z2d7z-LH3PIGhOq=@)>}=f?G^^Gdl(EoJz$x29@wuPrs{ptF~YMp4dPdM$>cL;nGN< zeI^yf*7DXZW*u809y+`ZcKfCG@w2q_Jq^$q%dBH%NsVxK$h|L6pgnIm(PNFw8@qdf z^~g*A4|{JPA60cN{Ldr@n84^6B-Ef$qK-;xS`$r`XwU!&pn^nst)*gnksFaqX)*&? z0SC?mI6Y1;wYK&8R@?gY+SYr;>P3tS3Bd%>VgQvOzJRZ17^}P}?`D4Awa=N!OhBLB zKKJ)~{`v7CbI#fCd+oK?UVH7e*5*wgvE6!37>TlG$I(mdyI%c?tz;O3%NMJ>tKvZT zR=(%^l z5pBF+{Yeq6*QqX|eGZP;qeao>??JRKuv{V93n@f<{uB`H3Hci7b8xN7rDC@Lc}2x) z!I{8G?sYBjDZ;8Kn!b9ZQm#FdFm=MJM<;S(ZU4(poy}02fR)+Q@)5hCGOpX4$9zJo zAwSdit?&bCf7HhQXdu~B`=e`}{n2%@jrE1!%Ge_{I4;|z^Rh`Qrg~?S)W#+W0Cm|Qerj1=9$3^I0OhRp2)DFN-wEwQRwG!D&1I*xyL zq7{5r%HG&b={+zSx4&+>Gr`w}ccD(Z&CZPZBL0701+B-bHkBWajrU??tT#U1OSMGUPp=}UT~FLBZjPNy%= zPG8<5eYunVGp2^qp33a>l|9l|I_cM?(^qGwukMk)+DZSf>GWesKe!wIW7Ciy>!iFj zopNG!%8BWe6P=V-rc+MKPB|@|a+;G;E?{wbHzPacjC9HwPRftb9CT97&Q3WyopQF5 z@`ZHDP2Q>Qp$!k7kVQh$S>(gX@=!X0XORylORsd6 z4^GG;A?Ymg;bhtBD7iBFpQtQ)?D#<5E6V3m5;>`d=Jw%H+c z!q`H~ro>?lq8}boxRQCAju&Ka zM%|6*R#7`6GJ7Z5&umTkI@%#TLV71UnQWNxf3H9sj@=@8#bzhvZ7kD-#%E=k(4eAd zwkzwIfPKXtC40E8F=^gh5E9ln#8Cr|P9+g;HR!xZ42JM`OSXl|!!XATLBO$*5tEZ| z!^cM!=Xp@gkBU=jq=B3{IY&);^sl6E8}9SQt0fBdLYc#SOVu_L&=h#R*%iD?y6y_% zGGXl;?m864oa`Z7enyR5shBd9 zVbgG_Y+cxb7frFxzaPXvcqtBOD9602dzVyXkI993mGe8Qh<3}B{W;`Pk(fi zuVpuO=*@W>DOWt%;s8}n3i;4ix26m!SBY2ILEgj_i1K}+k|r8I*=rGjk?V<;d=^+-A)z~FgO5KOi&{WsNParkBOpOi783?n|4lSiVv9c_B>K#ulZ&v6gTyXEbxNFws~bN9F? zYh7RE$lH;$Z>m`T-;%VSo06|<4}s(Vhw}B(#ToMTOW!_0zP@VH|FnGl77=>K0T+`* z1o5tXT|fkQ9r^m6xPsH)EnmMW$wui!`TAU`YH`NMWXjjePa<8{8_#1jyU*lAuxFwV zIx4_y)`YUj5e9TcYuS~v4vHz_X6rV8qJSMBi(!-yS|aypu)VZ+x5hc=4tMy&HX4i8 zTxT>SZrON+1fgZx+=^gr`}{P+a68 z5Oqxs2HqCn%E8vDINtRKMgIR%cyOY>E7(p4o%|f2+!0?N?wxre*vVW6LbA$=3@X1{ zfL)j0Ex4W$XY#HiBZ6!99DvCeplc;ewtRQ;Wt~Se`NWO4xYQ~X5TqtwsXO_^F3)3M z_hI+slk)`gLs92h^2sAaX6j9vn}PL!%hNymj06tL9I`co$p^$nF!(a9u@QWEID^pVt; zG;8T&sV~~sf2qtem69g*U%G~`_NC4I%D=Xy8|0x6>GcQ>(Z#QdZ^cp(U+T|P2Yb&n zm%hk*`_he)h=1)%g?rZc(~FmO@Mv4Qi(fI`tPbX>Cij<0b`!|+r*ZWp4!>Fbmx`II zN?9adBHL(RDi#g==S@rqyoo7ekew;sP4C7LS2n9{8=*(z46Drl(oQ|DwI>F(Eixk2;r_z-R@Iey|I>rAf}I4 zfWx2TO!hiGx=sT7$T`es*ij5QiY)gduH;e8&1?|zG0uZTePAV(TK!Vz zJ#i~HeUUbjdyA)wjR>r^?;(Ol}~52lHioaU$& z#&KonP%W-C=xH)>bC4sx!Pep;_xy(b;ty8!^#FaX#IF_SH=N+33^=c-2dj2-DVfi; z7H8Q@WaU2&dP;s}FEOvDy~NFxPW~)=iLCt3yZM#9#JnE%5|iU{)mrRlGBfQTvO&ri z_GlYX=00OXwGiF7oG-afl-J%z@^vmAsw#{_%;#~`_bptB+aT(XF!aA(rZi&xG2~Ltf{hKjl_j;xWE~5F=!4IVa%Lf z?<8I7d@o>cJ_NKf&i7xEr3FSl9sgetZC^@kl zHp1~6+*y2>L%w67ORR?lLh6R6;&)8~-7~&QBhee+$bC*}{3w(b?=jX2QN{NKtK!0- zf7!A}_+tBm9_hCdXS#lbxx>9@WR}}tZoW*p4VG~f7|Xt)i>IiyR&jR$<}O&XRJlGxKdXJNSM@{{I~Lt75ETovAHJqE({ zbOUDEiS1u+ucF-YQuPIv!mh-rY5eIipFa7*f!Dp%qKsbz)Y-G@1Y;E!hgE`GoW}8GL{lp&bWK* z*0{+FS3#d1-R$6+o)OARRl;Pi&*Tg zMPl#3k*vD{neE|ymRf@v4|B`CQWUaZ!DEmUXq|1g&|6xLj?k8KUQL`P=PBpB?8N3i zEcC|ul`KDF(9ZngoX(4sI#H~_Yvr%!4u^qN=BHOSMu>0N1hkIbQ~ZH%QzQ))Ci*1H(LAx> zD^iseC4AiX+QZ)K@%^fNDd6Nw(mXNY8>|W_N%T1(|LpGkCts50i3y*Rf9Of_mvra< ziX{n!Mh||O*+y7rTaz_8{N@b*<H1|m$A^*9Nm7FaL-rV@z8?aFVxXetwyqQ z1P?z14?i~TZc?TA9Y=rQRoSIfysS4Wd%<$C!t+D9pbGryflQr`h# z@Kbh~wV;aQC!F;{SMgQTxS8;i5mtsiW#V_fK-MwwbNt8m7#}~(DdYb?X5aVx-gJ5Y zJ(B6D3R!2#9peaPQ}dtWyNW}4^cyUF7*Z*k|4C1pzfI^pT@a`yuE`vLDvzc3ZDCx0 zC$sJ^vRd?jy18HSGC)FH5;FFP@I;{V{$Qa+#<9VnXRYct<*4y(QRZ``x^mCx8EOB< zLZ3+DTr*0-Au_SwXJW^*6|_?HDcwJ}YH50WA)QPWj@@pyK8xn`#CvEYAfZ2vrKH9^ zH}r2O^pDF-BXliQeo8!}^rM!1A`BM}EJ5;+b*+Y1fqR3G5M;70aQKV;^n>yhRR(W3 z7l>B+Ngq{dV%O&r|ERLexcn z>*ELPoIw{8;na9qb9{2i#bS-y2-!j6zzABSuQ;2N`Rsg|d)Gt6s=X^p`tH8f6cw4q zfn<*FAlBLEQj?7IC#mGK7NpZl_-lllZ#M2U<3iVQkt6`{@lJ1fUl^PnE{_|8ccais z86O0t*4@DfX)2EDOFtyF_ek+kc%#|e+rg=}zS=wNOJZ5O7wAi;5vV^NWaAOON2*#E!&(G zUvl7YzQfelSM29i0oUA~y~Vf5ufQ~U7I(R+?Y-M>q{ebk&-%Q9H0#*Vd5S+XzU-|p z9m#jac76F+o@OWbUWhXX;meF&UFZO*b_q6^@5F}p+VbgyS}?U_lD%-NlGP)oC^z>^ zwl7$yG~SWAl%r7uD~YOrkKUr808>**o}pJVMJR4;_!Kf}_S09f&N|UPbT&uOZ?k-b zIeUdh;;}q^w8gJ4rC>Q-31$<@_^kx1`Jwv4uo$-y62At}s2MbA%4EB9i)te?d)iEW z&WrqQM&3PB|KY|ZyTx85uZ8(AjrQdOC7A&Db`U&Y@+G4~4-RmA8NZfp3`63^X#N4r zFpi=;^}l2w!62Ematboc>G5om(!&`FDEp__Wz7mG2=>a!-6imVC!;x)3`R8g7`YhY z$K|olo>A$XCTy2N-cUuXpKO1+F0+W|GKv6;rwN?UMg!~U$U-hMqTNf5{rOLS!t(R4 zv@xbVMB&(hRDJc-`Wu~caV9mjWU9PDFjpzRMBxRAf6kZ>*)!05Q;*(6(n&W?wr}fD z;HF8n5QT|6#fW7p{S{|uV*wUTt?(Vt7lTwY^*6K%q6*;`h6}-k%?4eD0n=eV>p=%9 zM4O)$zf*tKgd1^sAv6q}D7AZ25vWsYY!G$pF%a1+^c8Odo6KIKSO|T%o?-45fvo~2 zs;}|O2!;e#yVw%1 z-nui%XeK%QW~v``9!kg}aXL|ytVslga2TqTH<`e~Dd^D~36~LxX;m?8iTPL3+>$8D z7+;2IwhR|?-79C~t15a>7-%-lgahrl)*{?Op02On6y3UDfQgCsQPkJsk5Ie=Pkr@# zud#cu@zK?hqwmgJObQ%~=d^s(+t__|-oD7uy?FJcM1A#?Tu5un?%u|GR~sMo$=lTO z(P`$EynW)AyFb&VS`xu;uT=vO4MwOz{+0cWE@?tJDmwH=nW-{jv%I#kUC>W#aaC*_ zU3m}1OZ#&2_R$VOXYcyh2+`aOHZe>7#3YPRWO3)A_tsDy^A0~jn~g3_AF+2y7o&8w zzPu5&q}c4sa-iBP?%@5aN9AU8qk1e-0`buVTmIlEwZEBZ`hdQiVv5!O(1dU0OtZ4C z#ym8WL(kd4O1)8z7G*xD-OK6$`s$+qy7$QVqlD|L8@$HHDcFwES6`P2*!rvW)e~}! zkNZfky8tJU%@Y?jL34ZFp(W5T;2ITjW3*~ZKzNU5-VKSfTR!fMUx~a!iJF#=5#{IR z39lM-FtyVmnf``x zQbuuSh3%NR#k9e&B0J1CLuZ-(U~F9S1}2pK*vrn^p#@_TdnLWw&BKUQM@Vf6y}NO; zpWIK$vQjM{+KZ8ULtlOsPw%^vX!MZDQ|t*&L3j?8<}&~fu&VTM2oYaSm_lRCe*QHF zjj2hHJbt;rv0P2F+?J30d7Dk9h>V@SYzU`^3Axu+GvAzK8yOklWn-EiFCnkszn02z zQ0jS7jp%L}Q3jIXZ27dW`Kq!SS+ZFTY+U3bn zOTHu|5|VKX5?*5vk*)haEVYdmsq)$QlKw z++M_oQPc~Y@8HCUJ~bW;*TjEE7hZUbkmoLt?m5BVj>}Qj3;PL%C#jcK_h3F0N9Uvt z{ZP7Yv}@V^Q^p#19ruJjPhOtg%EWsP)b!}jr9K?81J1d?X_`5N1#ygKj`5qdf%ryZ zRg+ZX=(XnD(Aq=At9{WCi>o$t$D|L;II_ni~>E8WhW%3DO@wMLDp?` zjs^}~91ClS)4C@R@&dWftiV{T;B{F~c+J#*bjq$v83`HrHPuebo29{e^cu!9ZM3Ue zT$)su0LTGSF#b&$?zu9!=hFaZxb((x(2V$>_;G>vENp|P39LoG7!Fbhwh(G_DC4R$ zB%bwAsX12xib@G1L9KDed)7O|QN+KpsV zojTD;5P(!Ykm1l9|3w=7M}F)d(mp1;_$U|OeC0v<#&ammO>-zs_Ys&l|9_jTgk;Rk zN)pOKR7*J%=MEXJ7#Ate{&1tH7}V90&dz%Y#Q`dAs&r~BCjpjye9)Akwf1+RJkLV( zkge5KNrpAhJTj~IyCw78YDWPlBo#T?7c59+KZESh-~DxCenQ@bl6Rl;UQ1p@m&`r( zM7+j43;J@Cv^^&MzTd8tcNxE?1^n3GN`K10+GoqBbf`%vldY#>&HZ*R@9dJ9eZn*N zGQD>U!A+szdih|6I`REw<9wsfb@0(EmBf*Mj%63k315!689)W-s2uVJs8^VH_bf#1`1M?M)_^fL_E#!VA_(_4u616eBYWfqe$` zV5v|GHRc!P!RG|3FXZzR3&97E52ypq0(BHB$DHGVNB=%RL6iQmw^sXX$)pmNsDuI; z?&47JsZ3lX{2ovkg8$ZwdzLr$VkuC&Se9_(MKK30U=Yv7`)~@Bp+amRweVV^SP)qN zS}+)p1%SE}kEwBFFYf@k@d0L(d8)ZsGjH}88^$X7+;v!U^R21^ zYk_}OUR{)(!Q|w2b{`mc_gdw}V0u=cx-YIzg=QO2r|g|Lp%-z9<6t9(=?~~o1>;bS zD8!oQGn##ZDP_$-s+1+JByoT0<$|`1#?CgD}7MyI4zu4(9PFRh- zIsMpe>ro$*2L9a|=Vvg+JYBDd##mu@`7Vs|4M>Z454yJSQ;CweA)X%-hj7H)#aF;H ztXjV0vJfe$Ao5g|d@Ir+^AacJ*J#!azH5#54#%ogKfh=yx3=$5`z%(Li9mH8g{qCj()aN<*-QD9(@Y6S!W0NKDXV1jyu zbZm?Q!iOUy&qM)$J^Y`$l5Cxd7#q1CC}{4O<*f?(WE!kP zBHb7B4QC}b_b1+PW|gAGD_e3r4!yjkY$qTXlQ&c?LnXYAJri&w0xNrvss{cvTZnVb9TDCuMfektLKbWVMth&DnDvwF2u_#MDB zMye2)NM~G>E_Y|U(-}HIha|qpMd={v1qjliArS|q5l7LXIWs_MVf#a@f^0|3BPo>D zV3`$4hv${;Wbk{kK4zzj#;sqE#>ao_pfNsjs3d<%hPMz8G}`eN^0DCXUB~0^^2DRv zVE)czrUe-4{FBygV!?V#22oe&*B>P|*IqcpFb=UCwgjo)-7?6q;g+MXggq5PPW-c2 z*Z7P!ABR%7BCka}#5X_z zCc#xsRH4A!Oi-7IpnO7)Sq`T1?ssXi91Y8NsY{JTvNTjE@kyv}EhDO>s4s&zR(bFk zWB-Xe++|q=c6ddLQ01&#_oiE~XHs%tHIoY@=6-^*WJOk*9IdY}oIPrDv{BBRQ%EmL)hrZ7d^cZ1W&YU*Z(-ia zoGTRK>NEGHrd{mZ^h#pT=`z_^_jEIA9(Jc*``1ssB@a7O5B`nGR5dv{0JJdf)LE7`jit_#`yBi$B|;H(9uEE?=yzv-IkkyQS4OVZ#Xq$L zvqx}d_%--9kxYL3%VA%Ke`BclWcViyQgEKwgZ4Z9St>_?U|+RVA<&S%xY$?flAB7^ z7iYUC@r5q!FEV$#y$QOMUx~c!(9MeSi;jZAz%=3SSM)za_;Vjj5q@P$oJsgkPZRzw z!8!>4L7o|e-_HmKzLM}y>q++#7$tO`6+FL(puVFKmOt#m2()>lW@X4{T zUAA+EHB5nPkv+rwj(UsDC6)Qh5Wn1OgWtYdEH?dS&E4j>h#8KWlU5|~7Eq0KY!AXS z(rb;2#unXcKD1CuXj~w_H5;v(h~+WIb;QPPY_EB3<>%F&_L_S?kY%9eJ~d)Bt6t&D zT=aB%%^Jm+*E}i>X|H)+Ed}E`W8*s7Yu-{^__zn#YaS7bg4b1it2O)Z9)G>@(c#$G zQ+>6u@2^6M>_*&H;P2>#Gk~4+&gbb{8lgX&$+Ma=WkP zJzw4%#tQ=XzIcemO>7Ooe#PPcm(bW)ql{{7(nbZ#d)16dfnPz}9QUAE1DIb^lVuVx zUlp5luYg^cBnRg9wclf=grBw+0fRN&l=}U`G^MU%`obh7Po&g;CzG`OK4)yg8?8mJ zbhG+;^daVj!{$e2ra-3`^6VZi*4LU1=A>Qbq{qQ@x~wEEQ|x6pLlPcfQit{Z*Q`a- zu_Y+UB4Ze0)sVU)HmSq>{%huxNcS16}D><-A(d*DRWzwd%2`*2uXammFS6bobr`QjQ#|CmFo=OgUlssnp zNpg^s+RJttl4-i=r__Ke>vD0D8_AQYe{rTFye zoDA+nGm7C@zs;) zDPm7Hi5?)_9Et!K8B_ZxpUeqI$dtILM`3#E{514Er=U|ZghHXwW;>Ql_3c%@TkGq8 zgp&7V`$?g`Jiea>^IP>0+w=`njFWkJ@mWP?Bkn786|S2|x;3XQXGLu<_u(n(@_ z7EsFI7{146ig@#M_ysvpF11OGKghAH&B!H07rc`Iz*BHBk^+8B0my0hOqgQ-UQ_co<&g10m{8fx>BgBX;m< zj{|xuQGxUP2Y(N_e;MHX{|=t-Q@J7c;W`d3{~PekpZb3Xp5kvZ1D>`23Ov7j^6!FY zfUVCE4|^LuD)%yilS5+YS#47a6_2u$ze&PMAzVsg6IB6ZCFTa;J+<9uCTyQcz|8}dJcl?$r&bxP*;};q=A^^GsAA_nX&y6MidS&}@h~LR+P+$T3oW@LTFWm=7ej zfm!C3CMn$s_#L1T?|u@wnn=nVV#b~CWyCpHmzux#OJ`SUGU-HzA6HEx*+nyLyG&?d zvaPy8bJr}53y_nqQ@QJCwDAd2o1KR`WZujYJ?4O@n-t1jsLZ3xl#Qh1qB7~e;`-&# zE%qZ{*-J|Io^81w`H#MO;whftrR!U4P;=|0+ApUz)NlYF{y0^GIKWJmjyN;jl5p{a zNp7WC!KVR4U;A?Dp8Q+Id6$F$4LMBpns|Nvv3G?xjaWMz5WXg0o4zUqrt}mguZX?G zzj)^PL(PARVi1nV9e54_->O8G1;X`(KLXjTzxZha2q(v|$ZIL2ivBhpaL~2eX2+ck@lK zK45)6vt+8e!F;jy#^knCxq^^V_6+hI+FsEWE`-IPA^NfxXrQ^fNw$Hr&aRsecoL_k zz6SP^pWgMN;PUo<0{<>@^R7Ao==;PV;S=2Yp+tEHiv zt)I$RnXh`^b>h&JkOnN5bh+$}nO6l9ZkTMFe^#qE{TDB)MN*>~FMg-E(`-@&D0u7Y zWFfvxpiB2BBLy$SgJ-(G(pQDSUn(Soxsgf7QASEXQeQo2cxkJ`#$%UiF9o!Q-D&ou|4t}eEyNFC9xu=hCLD*DUD zTA`|6Veer*{1|ZvUW%=?vQ5Zz_bmGBS@uwmyX8GXLRFsR=-I$#!kqG+=k06Do933c zhJ>m-$;+sND3mgjfN+dx89p5(|b?*#i6;owrkDD;Pc5%QA1ZtgE?xDmtp zZlT(;8t;3pZ|K#Bu><0S%X~fW#n5TUu@?S&jc3A?I?wJedQGlP{PU8ot2xt{7i==# zIddcq3OVmM?Y8xD&QaWkOa4hWs??7x!{qU?jndufPx&-dX)HFWN<00cyDoDEZJ zh4Uke^F5(Kvpl%Z@L$5*ltYLc>^b-6c;eTRLas-t>#MPr_P>w_^VQ@)jbz-82ZgLn z&I{NF@GK=;HqIQN;9BjhBvzcCvthu?n5=^0A2Co%*m1&Oj|-qbg%=?tzX%^1$$^In zrKrFS9O$YPSot$Z#nFJbIVb*sJLEvuy>Prq$NIh%Te!G*+D2lge6?10&Q$8%xAng&ytAdc^H`dgJB1U|?b0RAr5*M(J5^ z{JCFNC{B~OMBp7cBi{0<@zG$T3-4PyfRDK`wlELlix%my%U>Hu`ZS!0t*a006<&6iyZ4T{{6QPo2tHF(F%-f1`{WT|>WtBkI*h4Bc_2VB0!WcViP@|iF9*aT(R zQRmWTwGpa2u4YQc0lD0TW45Ql?HT{eSuhkEypL+Mhj|s#KXa-c%i)f?79vaDvJH*q9vgRaT`(zX`4}{+i=U@Cd#H zTTt)(2FH%w(gWH5D*Kh;-GGcvU{Q&Spxk_iUG`}`&g+C{q*U`zMvikN$2sJH5M;Yw zECPbcC0+o_763ogQr?%923g|~P8v91h(9a}l#AV*V@`0U7n|?H-E8`@>OZm!-laE& zpcAk(j1>*#^neREIj+|m=M!$tFEuMMThgPy1qk*T-1I7VT7e!j%~u}vVwLmC#N`JX zA0>%F-4H=vX}uq(9~cbZDgbo-jc5AP)3W^O-6))!h^>1ZQ>)%@C-}h1qmc_vS?sLov4)H|U zKuehdi@)Ck=63oSs2kmgUS*Pc!EUSmX6j``eSUgNhAZ@<#&n!Nfm zrd`w&by5gxx33Iftz6Qta8ybHbu(_SN%^W8@&{;GlZ-KL=$qk7eajT0Qv#DX+cQ&; zspGnvj=$WCwrBac5_!D!vBNcI5b%cjz_6$L20OjKFviK=JCV57qvVCJz~6UxG%z)Q$P5IvtNp2FCPd`LW-n)AL2YK!$Fa9Yt-`F9FKBI zAzgRr_!T6Er;*+XL2BVs8rt5QUU=BkwBaN}?(c4V5j>EN*jG|d{29LN;i#LGd!ZNI zWG|Y>76TZIorUx;!+Dx>or7a={qMiW;Ccj2RGx;#8=QEz4k%5lzY9QiFRtYkNSJNP zJ#2~7x>Eb0XJ{SX;XU^IwC*1+{tpwgihq<9mIju_f5nS^O|xSu{yy_jIkI|FCg!Dt z0t;CzUST%2Er|j_Z@igzdkSq~houx-l&`K*$YB*Fiq1nd?_+qcLf`J;hsCk-se0o8 z5KqG?l233dgE2JQg}d6 z$8Rl@eH^Mq#?ju-G1o*p1FMWTuPNJl(XXW8`V%|PIYP=xOWRn{9v&1M?!9tZgouWl zV^h3|AEf!gTxBXxn3#*)B{^}1+*`JjPE5sN!8XSxo621O+v|lPxB=RM|E}xsr#y;R zM<4H;qq(X9=3&V9WEiQS%rM!VQjEh)9fg@v1q8_Do-~Q$X!MsGi+kP zS?$_yD*wrVY3eZLo%?Fi)dFP2_}hIW-0^%^dFPh9rUvbo+Uw7}R120tz{?KiwYpX$ zC=TexU+A(urNN;F1E>dT(AG|%>=1GCMo158AT)rH7kmJ~RaqGDrv2c96ybQGt1{2- zp5RsX`lr?094bc2nQUD6lK4)Bmhwc+pk(xtwpCOf(>76hfC0}pr)|>A%1tajPvG-S z8T=SsIpOKFBGB|Gdcw2LvA@W1c0VGUVhdtl8H1!EU_pbfhcLga+0{&2AMn^$z2HoR zXT=q#78Ivz!10#&ANj@vAV1?OjN@~VSX{+6L1ugQIwgdn?p_*Fr}0y3uYjIACls;; zu%BfU=g^Xqd2#z5i8)d@>RfyBtp~E`UWi4{e(AxJ=bx&aKb$G~(GsBs(a#FFJ|24N z$W#$uMSQh*Y)DjR(AFVx4sa`VP|5;QE%=&Jq=s_WQ-oNS1X6wo3VdR z$#c<16{XHnliefNTG`dyab>Vwovl>s{Ore1vrFAD#!Va#Mk4{%pV}#+V>ECt6rKU~ zE3Km*n~JZ}s<__h>N`IVw;Io*qo2qrYfcGOGumf-BH}XHEs9o&Z!S9KJpIxYo&`KS zrd(8N)k+sNR~IK>jgjTSN00(^tsTX3z}OIM?NQ#!f3&=Ew>$@aT+lQ;V7`js=QIf5 zZdqD=vvMZbH-DrgoLsko+z_^pTc-Vp%J!>xZGwGOX8b1-|0A*!Kb!o-pPd=69951M zONJMbxiPvvbN@`S#?(wLXdn1cRaJISDhrK~YpT|E+*#wP9|raL$1kdCx{y}I6Z}Yt z^`d+F4_|t?_6FM1bb%Cj|Nl&Z^QFLVQC4E?|3LE|mC{Nm&D?8j@H>*G3CK5v(Ur^f ziWoM(HL74%|GIMU2H#G}TG7j3#DEGci4R>svU}R9gL&es(O>sCm~D@V3GQi@1}DR) zu_wCrDKCEPMhP$xt%QTHhIyXQSqDcIYT?tR25;RfcV*N7*F4Ax-v8T-V6!!mW4;{S zssECx6S!HVdOh&F_?e1SsMSk|+rP_#g&*YY2e`sAJ zkZ9^pryY?Z>G32`FsonP;{u|&`HuE@THa`UA{=QEfO^zvn&wVr#mX{YmD(gv#o;oI znb_v8T6@Da(w@SUJa7N0^RJ;j+s!cv+OvPymG`%MZi;@szjcjN+k{NTZO>PXpC3Dy zzkB>vSpV0?PjgIm{`>Uq-Z5F@SF&B(0=#N37~UUvjfy;bd%Ne&cYgM0>l%SX#`wAN z&`+wfl&d;Wt`co0^e-%=oy?GNt|o>x?6ti}3%iVua>M5ti#j}^3sCMkZ0tU6yyTS~ zvWjjGza9U8Y{*}_kQ@FNud$lfynD@>UER#3;-Bn`xUEmPHxG;{LNDIKrldoj?$jKy5=p6K@O-?dUObUpW3ny=0#HfJE`NR@gaQ8 zS!pd4!|`?2+TDejD-&H-xB~{&=kRFUq&>W-eRvTaR=xTet&fd+nhmTFgn2jwmTO-J z84LWL(17IJK(^%G>fJ6L`9ycH=4pP$J8|{h4QwZ}GU-^UHC1ytNw#LYD5R~*=Y7(nSuD_lmnq7Y^1M_e%CA{gfLRdRJmH zZdY1hA};jQ{)FvUGI7td%@2Z{H&Z}e(L)f@@xOx88uxTl;MbyP_w<@y$wO^h+G{qD zO4RF=)^*LU_L^oVV6Qyv*MFYl(feg^Rn?0qPyYq`#964X#KyfAn}n(!EHH^5dyJ4t zs|X}S@oQO?m^G_Jdv4tG(v%_pC2i;wo{K7Furr8cur)fgG9>Tg3J+-@C&|Y8ZUWR%tm$F|y?oJES7xun+MCexxT}o@}_34Vz3o z6c9XR2c7-W(EKv?H*{+h(`bJjfMqFI$sRBE-CX1S0&C?ST+<>y35^x(f8eaZo&L^vKP}yg(XOdlAU0ix|$O;Y8 z%07+#N`1d^&Kr4MhuR|i3+EZFKCvI0x0x1+LJ<2EKah&f@X(%6U+K%i zxOfxyzLPjbQm4m@EspZT9h-Kxmots2{)>QAeKAD`=by*Qm5;bW1U7)Z)~;k1L+(O& z0D+q+fEB@nzd((WA=`PHQ}$9^&c_vf%ZRW4;AEp9JLQH7cO+zU%1ETm6}Z2MXa&=4 z3Z{F*pT;pJ@5ZrR~5`MnGTwfyuy(#-jqIo+3tcW)?Q=ftIr+{08LT)1#mr_@cSpl)`6 z8v@~aWd8so*Gse*o`LJCdt~7V92IJI{7#v|et5QCN()B|eLLE`3ZGFhcn7QC;QYb> z<SF$1<29WH`dG`Q-{$IwNO-J{F2v{JQ5Q%VTQ{(w4_wkq7*fQvFTy zPpR`tE&!iu4Kg>|45Medhyvz`^mLTJYM8R`80FWE6+~Irb7Q8jVxw+6u0bQlqC2wM zpUa#N#-@5r9220~$Fzz4cr9_2M%U^3ayh!ot^BXU#t2XHW7;RG5~4?5u}L??p_V+~(MbFMkc7k^)f)%5{R{RTBn11Pfw*}#E-N{Sy!M%9tin_iVzuU*m|TTLv*{WM=K^^M=*Gk&k+kRdJ)UMa9QE^q>5C*bwU z9b}_?=0JumwHYO%xqu}|70eQ5k4Wgr0X5^Zcn&$W)lIzEm-<0?kt zQ(i!W05e%^E!m{n+xrmbjFX73x#?4c?VdV4oiSF0U#4{8L4`hwB+dM(HUp z5Ei-QB_yMPL+s0#C^S7Pr?~Nd=0~QA->H8M3AkcS~P1JLcq~Cx4W-*)QO@m5&EW#{G6Lg$6WjcINSnbi(Txyij%WdVk_-r?8&sM zI^Rf62v<2}{jh1d)BeA9r~42xmAJ-s61<`?L9Otuf>XZ*h)tH$g~{E;&LIN}o6LL`3Y4(}EEH0H$Swcq z?^XFy_y|d>@XtZhzwq58OJDqc-T-%1(+7W6F#6)M?*6+K)Pl01!3tfMGn=Hm{Q_L- z9wSKKI2}MydVCrgWO07>mI%9pVKlq0l4jR^4*y(h|Jlre)ADR3_*4Ea0%LpriGpEf z*!M_>EnQYvW_;$mq}@u|6i4C6|B5WH`BzTOknluh`Lt>@JOx0xeP&BVTSJgeL@G4VWiKgyi3=3{dh|3BQ3j;G zR7m|VNuDP47q8e5+3=gl@iQ6*K!T3XjjWSxiWH7_N z`X9;)<8-oNGBM0o%_l zNs3I$l7XHY!OD1goG($L8WTD_MYn!iu5ik1StY_XE{tfep-GUg8m>z;HWVp=o|7ld zhx|)sp3vq*=PGYvV`^;W>5Ol{eBHWH`5tFTD>@o3vnC-A&1ndKD>m1ACHnz-+;CZ> z@{;M{QWkYNL}Q4Jb^(U2E;5MAPP=`$l4`XNha8JaM_kUxh1*scSlMT;l)@SoGdU3{ z%V39QrU&}m^g#Dzpigu5XQ}aRxYill)QV1n%(yeaFLE@svO1W{`0ml~Y45u*m>Vv{ zbzD1c7QC<0!iJwFL@CA{xrThZ=Qj%{L?K+agB~)>ky4_v5KK^d4E} z_J#d{sq=O;Y-hT(RcRgpJL2JCWz969AW>v~VPTUCkP`#l7aH6*QdvAbtkqBiey94N zay;5M@3gj&WQSd2xpt+&xGEe-@_r<5UequC??ma1zvUNlcxuI7DaX4`|JlD1Vy=&s z7Z>jV%QfUs-i@)brzDH_&Rj{#Z=3Ci=PRa%5Aab6HeC>@?7i-;3lj6pOU(&d`$$Iw z>YVW>jI!ouMW&G`3QuMYy~Ny7d}N+4QhC+7yLu-s3zq})P2NMCP<6?QKZc2F_A^In zW`ob1;E#{sn|n3i!99;38d*EZzHXVEXVsPPb>kF!nDbRAaVS}So6k(_cgAjBtF-8uTRT|y<6TpjZ}G`6D{xc=342zqp6z0=fj#)SA}t+}BYFDrLW+*G?lE^HWeIqR&D=6s0Yn+zOO*L??0TX z{WI@I*IoMZ-%0A%%`Fw5>dRzBMgCCzbfsDSbk+40o97>(iu`z+gr_AGP0}^08fw0* zph3;$#p`7cKn2e17mE99ifiHm(scA+j{K@Js(U;%K%M87wth(p_d!Wc_a< zN6)A~P2M^pM}deqK+^V@>-E)pO#9Fbmd(f^it)DUOF!dT5!aWCf-YmDsRjW!qdi!> z2`WL5zC^8OFOp*Ak|Plwx-s9W&s=zS)s+mE9u=Eyv$Je#*-M8Iq5<4io|@V2eRoN6 zBXRZLLEzB!-0Q>7dab+n*lQj(T7rk(FBVsnZ?t^cYe{kndn@zM{Erg7MIY1bqE}^G zYnTxr1XZKuc0o*Qo_Xk?EST`tK22ec;1q+)g*J!1;-_&4lP4HoFB}mdr9O;IgUD@+=;L(T|353$6LQTR>=h^xF3{y3p`l7!AP zv9px9pU6i}lAMo9zau4CvSBQtYC&~{8{|pE!G{9OPbzW0QRU7Ti^BobJaFfG$qL0M znyu9R@5YO&tH;CdCLU;t(9ODL8f~v-@7II=m>-IXzz4`XO07XQDLprdw4;xV*Kp0c z!&lLcSV9I1dnJ8!{*_c0jE(jxJs7_^+_z$4u%E)9LG%0TtJkkxckKCG{r7G9Qjt(t zH~2>^Ir_O|zX2bRUS5!e2KxbQ%~-$AI66RIuF+LMI3!p5<>U7t6WL{5B%PprP%hSie#7zH!#uFAmE31G-4D%0ypXsE^JBOQV}mC7 zIh_}?EZX6*_x2Jt_(={-{!_>*nCo!D(Vz3#Bsn7HvNpvXe5zu2os|5eILAq2bFN2C$C z_QS;rtjw@Uf`sC+>p9Ls6>fn?(WzvM|9~HRLosh7r4$#we|6wH%_+fYvFuG0GB2Yo zP>`FR^v2I4jB#A)^!i(=3Z-<>5{TzYp6so=k3>&;RU`Dq_oeT&++oawMT>7H2*=!< z(xrOD83bWXat6)>+c?DSmEL%#K%zU9a0X3sBu=W|kxJE)_%ZWIPMHgFNZb-Ca|WW; zeiBBJMo8Dfw>V84*wg?8B=fDQ%*0g-F5bwc7^!*?jkw*t%4vxz;w%N3X-GNLrx@~T za+sH!9K+@f+lN~}R|JyDJ}9+C>AFz)g^|gwGVO3?62g_lD>GLvp*M5DtOfincb_6u zl<%+mdjgQl->2@Mlyg1#gpyib#%!!VjsU9|8sC-IYYn|4mA;x#mGPOk{tO2c z1DYNba+YJ?L>p-zfEWb&g8a}R?tJXcLG1dnW~{c#np@SG8?VC=1F5hy(RTG=tp40z zw>5ORc*jHhe7FJ2HGhtA5M{qhRJ=*z5pl3*Ql~~{ez1E1UWsL(uVF5Gif^)a;(l+%L-no4 zJ$HsY)Sr@vHjHA|)M9o%C-TplL+{ff)(y5xFY><4dq7Y}zh}K~5By5_4B0Wg#Exm3 zr{TVYzecu0+dR^VOB@iUccQ0Xpg`#EMEz-U$(GOUSF5O$q2J>Zlnf+3>4snTdN&6D z%z0|X`<@Vu-$}pOD?0k_s6!f0I3b+g<$Gn7i`9C~DL(l0F46_8yZobY(5-QW{WAx)X1t3TwPtjNT$FwHW0&iQ}&jrlPy&!>7TFa{feodmqa1w2g); z{F@|q35mN9t;aft4(fUDEENdzlt?6tfg)QgYc$prVa1oUHG zNrtkW@hb>bgU#aa2ki$!H4`sLNW2^v0h+=Jz|(M+013d{8Xh2#0-Sw6 zw@41BToIMKp|5m@7Ie2k35lJwi`_P`1mq($uSZruZTYgT8%AD&i{Q=*>?1@lL6IRB z!5YX;07^6j@>61NGWL7()*~N=3(0Eg;3ahr}Zy)5~52a?i^c zYPps-J$9b>fAZ-<#6G#k`O$V32Ra}}Kahzs1?U3Gi?T5})wFuuUeA5(4 zi@!s>$h!+DNLKo@36u$SG0)UWKhUk#t*cm~+#;{dE>ap8@0Xo?R7UbMvy;D0<#Dx{ zVUXR@f02>=6RfKk%v6K_Aj##(LGZ^-ek3FL3z^BocgEi(4spK%^}r^VN4n2Rq*j=z zz?H?UFqOnz$if*(zGLol_{|7wS^Q~AWXVdO23(hsvR`_+%3R>O>vtLAf{ZMkOr{08 z%tqFam)#!SBs~)Tl@Uu6xyyFEGOI5xKk^FEsnzms%6go#Skd~xiHPF^VH=zSJICdI zie>nvlZ}<@4Y$sZ1WR02VSXxBa765Kl*{g?yeUBZ`n?qI{te+bSo&O>4z+mdMvKBI z%_qyBad?6r{gMFV@FYDNQ7;qq#)o-vV19a&+ZAuBR&?blmd;E+RXU?LVh8U`7d@IM zpmCb1R>Bl;2cf^vQhwQ|VaOacVQ#ZJQQco#<=N(c|I7dx5by9jfm z!<_!4*pul*rx~>~DJnjNSSMlQx5yLM2y`?4LCSqyv4M#{s4zsaZ_0PB{SBucSsldQ zkS2!Qu2!cC2%Xy`2jnmu@v~Kr1mm4q`&;(3>WU8)v{P@qM26@x>YKT;7WmBMZ^1wK z_Zaz>xOv6iM#_23ef~XV&SYHokiHheF6lpTpYkr#T;k;^->LW1^JqreR9I%6sjxIN zz>C9Am8wlt=tkD|zruXabBNulv(l&#u7LhmoBQB*E39$(Wjl-a4Qg!|PFi=<7bUGb zwBb`+!FXGZCJrVx8XHO#r%Gy}ui(jKRdRX4MkK=>N1dLAK}O3D(6X5-^eO(%Z%bT^k?{g z?`e{Gra4jTZ8UcoNBSbrZ7E$bR_ik+5lB@^sVds?2iHl5Vj<0^$Y*i;6igZFL{@9} z{)T{uWB2&O;Ct41cJp_&W{1^ZR`=V$6{Hq^>kiEtjxz)(|(PJ?>azs^Zfqj1b%hVMFaM{$dZ zDb>;eAfb5(5{j@o2Vr$C-(0K07CDJF$Q$+punrD)5%5@d1-OliBGd++8ify{xPoD7 zWVaYQINcBp#b*T)j56{sMonXxEM{vw)z|l=J4c${bqLg$%pzY>QRaT2Wnb(>B<4H) zUoQxDq5e!q%buLuwyGZP>3{qF8O3`+(G+fikpA}=Knh*Y)>KT(?yncH_+9@{Y|q}be~o>D%n)fP zDz7==xb=M>+MP7TI2@RN7OJV|%CUUhCp0d^-kE48a^Cx82RW!!7QG1W}~b7 zp$*|ne9PEav<~hhX*B=FXzr_zXs1Br1RU){pC8?%wd~gNj#Rv(NB<}Rt9$FKJEP4D z@*~D2d7Z=5o@KRW@Au}C$-HA`C+nckiPD$;3o-Gr z{7_)a`~Ezxnl{>UGgDkADZ~*uWS28@;g3A#c%ON>ImK^|*1Da>XV8YXL~h@9Yf5hK z%70{kCFd*F@B+?{HqW{+`D2V_0*t9cn3d?_W8=$n!$qQjd9FHEkmwhrS3gAxH8Al7+!U8RvlvEBklX?xvlUj#TMGX6yO_e6L`FA|*pCuZSFr zg#Icv&wa0`4)$vpC?`l8yVh6sMSRk?;c~=A{=_ELgr~gHI3s>D(7Nwozjn8_Je_WE9| z)^`!3($I^O_!FPwtNE(+T}hI)pjzJbrEihRS=sxrvTx?=U<*4OY?eJ(>hA7hx&&e+ zIv6ZUJoz%iXDe^Rg)Y>JU-^MLLpoIe&i7SJ>gm83R-glaucRH!>^GM z3}+3nk_B-{MODIEz{iN!tsqZjikMlS-3iO#lswJKEUGjy+F9Jwo6)tWbuV8RbT8}) z+1j&dO{-x2N~PmIVp-x+8o~JT3_uk$)feswq7BC33&VI%li^G?9}Tqn1jel9dh?L| zvp#ARU}@SC7t?-UnOyxqa}|yuAyH3-bJjR;jY3kmw%puX842NtraE{k$fh@ZBNN$9 zZD68T`?bVIvrF2AU*D}rH53%|#+zgw?t^p8z^vc@0M__OYJqN?$pXFcdGwzyq78K) zp!!soj}r4_`_D77Fi_k(i3s=>1i(8*?2f597yrJsrsj3yKqnxGkexQ-go8rlsx)MmW4AM5n);!{c<2x z{Y7wOQh?pLM^!(->N5(V!V3vTMcObP|0}ZFkXERg68lp8N{Lk=vk%Y79EXt#A0*s) zi(uLoosB*<_!S`v%Y{oFL@G%M!^b(bzaaGrReF^d>nbTrQ8!fx3xURMbcP%rQW$#C zau&`00NtR9Pf?t0=pU)Pe0sQ_NDGJi;-Vm?77|3&D>+#=t|cR+=kgO&yMpfec}9Z| zjUOCTS9v7F(dWu1m(t|bf}_m2K3A_JfbG2$K4 zP?9Rf}1hQ`%i~efrX0k^G!3#t|T4@ycQr+dCEj-&J|hD*?%#=j{wub@)%Dhx@cLTG#JZ8fx0(Or7rcS<_&@~NruAL*i> z8Vq}z%`ye#g$K5-8p)6}ayXMre47?HgLp=2@4F&ZM5vF-o5+;N|IM_G4JC@Mi;xhv zHV6qrgP|suq?Fair1FQG6KCOx&zE?K=mDwXi__7+1xW1*3@fH;iAQA)IP|~P`Z0Iw z1l9=oWV5+60=8LR<7iH-GGJCHdi-N_n#HDa$Ei{cnu* zeNhjAzt9a)evVD(U=Klxmy4R97fGY_H%2Qdwi!oBQBI2HU7BzGg6b78Nbk*rZh7gqV8`q5qM zSqPeoCdJ9!yfR{ocjA2beFz*;pk-SrVhOR*B1hU=TrwB5R9OMI=cJIZ=j0U6a|Dr<5d?3?Y}YFVZ)9Z zN!>iEFaH*4WDi+ElLk2NTJ!rFH--D&T#?_0J&(VtV!hrNrUKaj35}2c4Lq{IM?Q*T z`A|<5%Tn519zrRPP1k}`V|REHQx#0iR}<$ekP8c{luQTxt__Wv^Ie7=n&O~20ve+HDd2gF`#rDu-ef40(c&5y< z$gy4eQsIUo$GCh|W|_YFNBEAKpQEq7OO;EBA94xn)i9AQANskgHSg2BLwV?_D8}jD zR<+=T`z5Y>%Bx;OBf#*hj19F4>pA~ATK72nKD+9BS-ZaD!r6TyVYTWh5_ne1;>dFh4-snQgJ){RU-{GsWqu;KM!tN~1dE;~Ox(;m z-b#ixJ?IotB5v_XP%VgM54tI1MH7FMesta(!Xo1uUN!b{W~VAa5GAsJBmJn2Tc2+R zMcZHt?xa=zoM~YlM?HO&`fJWi=nllP_loQLwvFo7SMj2)(!&t<+3=W?7$A@OgkK)# zCVI)^!uC8huFl+0B6I;yWH$H{8oW^L(v1PYpXJq?3dUEiAo8zILcbvI1mZLqvu zG8r;FByqkZ%keZ^LNaXvk?SS0Xd-VLD5P#0VY5o??*nCHLq1Do^g(7(dJ`v8&^Rab;tHxyc)gX;s~s}8O&3ZrnqUSbB8 ziloq2cf`u?i52c|N%(KyHe*PKQb9y6mmvZSo^N)>g)L&aU%Q0ox>9~tY+t!zTk~hy znkjskUqs&5vE@Rh;OUGU4Xil2qPn~*HZR$*GjUaqY~2zWC;#PNpWk?~Ysr@p(#-$o zHBz}FW851nFR7i3;i8B=6IUol9hH3>`uIy%X!(iLQmt<~pBzXrgXcRkN-e+m4OKw5 zfU*x-i3li{Y>1H5S!Ci(@?KXso{6_$VmCo|@L{ZR1!}A)T|)4!S%p@8xm;R2M7~Cp zGUNsLfM$5D5x5!bmE^UsVjubstTim|h1LkU8Lq^drDQ>9Zz8Ceo-DS>SWVhWkgddf z*xSl`5YyvM!iVU;Xl_heKMCr;+>lFN--Z=4g0eA|i{h$anM+LDOk6}+Rpc6KjSyc- z4>eU-D%`7xuZnzNo601JT zQ?XY!DrgL8%fYWP?9B?U&<2O^99e;NIQ~I}9sJCZ--%JItOwUf8M^Tiud%`k^Kiuh z-T0UfRmN;lyU~1CPX1 zT5u?L%;pDGc5;L_C$`bvL|5b|r+PyD+T|R_6Mv7S<{SFz8~x@NW=FYq#V8!sph!?^ z%`2Bv?3E-k4;8oT%kJULF<1B*Z&(&A{%JW}<-zDd-Mn8?G3(e6VWf&j-}kSMbiF%o z9&EWcM}&*$)xR6*+FO6Sn6c&>A1Uc1H`edII`2r{=EQfMK04#ZaE1D`S1I(RTLsPr zNiOsmjXwI4z)QyAKJ620X1^e(1EL3C@V zzgReo^f}fU@xRerx!DHMR9;)nsowZ!Ec(j++fm@JKl51u4oO($>Q9xVUdI6#2Uy+~ zDeF26khFFYy5X=^dcQYulgtrwZ~fRrEf{Fl6)O7^{ClvSI*_=Gg`==;0UjvqJkEmE z1;{Jp>)81JhrKg_kE*&J|4cH#L`PqMsKG{wb*xEEZBo-pYODi<5CLP@Q4wr^ltx5L zVFpnV2WEnKK1ON(X#KUewXJQ{Zdw%(w@iWw-~xyXLVt?X<&EQl;=-cL|9kFxZ3 zgGDuO&3ivwt39-6H||-FTk;Iz*GjOPVr0i7pJToCW)G))D;`M1zVX)HpNIx!+uPMy z?%tpZV{p}o>wyKHRVlQ6Bej-1Mu^n$v(rl?75;?F$0D|!Wn)HGShYSG zuyO`4r6oW_yNvTv(e!!DX<22CPSqFL5(hkEf;%{2i&bRss5{;Ix`dp}#7a@=C=z`! zz^Hm1DYO7k^7zblyga9puRE25?lX|Rnl*a|ZJl=lK69)SeTqCKmN$<%vZu`-ssYgC znxtWMdC;0Gq@#J}J$y@BYKUMT6N|;zx0Wv|vioE+T-N?t(zAc!Kl4qjVmOH%3@O() zkt;aT@*a?c=!;5kBHkMRf`8&#+Od4tl-t_J`X>q__7VTY2#L0T;%|6#VD}nabR%m^ zm6Na|!NNar3hbxjpExGtpBPg9iQnhU)y*+ciK~i~xXNeF+-Pg*Y-twBy?G@bs+tGg zd-h7S0@`=;N_=L&T6wEXu9nW>pOwFzakLI@=)3SqJ3d@{(sW`Iu`!Owbu-kPG0{!@ zjv1+WSm?pXhn2Tsov_mZSIul$zeRjy@lMJ?;4h~UILqHq(iHwS3jV6*P^2|?7e97b zMzlNK+q9o_c$1-0g}(Z3tY?+Kp<LV@kewm?b)HPYytIe7Gf!LT-#bGeYDx5g~U! zuMA@9Al_%Q6;!?V0MkYA0gn-Yk6_ovjQ=Y_8Hv4@50O2ZXY{2ENn%zrf_dK#*Pb># zR85M|+V`qn7dfy58xr0|c{fLs6mmfJnZ45I!bF5^OLAw?6BPwJve;U$(^#+Xr3voG zua}~?G&fRT;ML6szC#V~zoY{N`Ys{3FX>Og2O}uP*g159-YJ&Ok3*2=U7I{gN<})| zyipchwKh&O2E3TNyKIEpY}!7L;=|g;*niO|RVk?G)3Zzam-RRbXn-FU#O< z8oX_fS};pbxKIjUX~U0oN=Xgn)?4%CZ8kMVSHdS0bfpe|=ku$>h?Va_Ctv96HSgO6XR_)_rXUTiaXtE8j8D`AOv#kugbcNPwoG71{1qZ^f4*t;M>Os##2Woc9(I>ISwO;2 zI@I9hp?pqJM(HVsVg9q|Pbu=Z-!MIQx@+^a=+!*IxV(bfS7Gv)46q7*vM<$a5S-Im zM4V*#zq~DDwf$dS<{3V8j@BZ&%mDXFAn0PZ3!cC%40BfIVQvt$w%@NOgqu%~UsM-M zNN@s6++=06py$tgkZ2AUp*Mcp9NPiVaKW$^{cqB(AS50X5U3-F;;ZPp?RBn2N640% zFb}L%Vv>_}XpFz(3SDn))FB6zeO5NOy%`KGc)s;{ zk`s2~^>^)Y-RTYvO%A=&oy4UTtp?eU(H_PWeA(7QBoE~!&D`8$l~X3ZxmLMsb?U@z zEtj)iMmhTD*Mm)Re@COYX7jvyz|ToBY5X@Xjs-T=D=(Yc*D zakjtV`jqaQUzddy;5^KZ>$^+ z2ooDP{T;e~>1C0th?g)MDDB5TY%%ugmwNS)@EwHAae^dZ)YCmk0=fI2?M+<77geFS zWdze2vH80lbn16kUP5egA=CXBm8*5SxI=tHik)qRNO{FkgAH{Gab4mQqVi1~N;VtL zoF9um%)sfeKu}Y5Fo8K#t()cz0M93ed;x4J!4=Xi{65B=7dR>)R=jQ;GJ+ogK1IWo8U z#dfXs#mg1bDTnB&646mL+Jqsz%9VAsJO@}qrOA*yyf@h2#)#ShTV}K54u}#f?g|bA zCghqqxu~n5MWW9)Xwf_bEAEZu>u`B(kzT7X5<($_L_#6H%c?GPD8zJxMPnJqWAqQX z=~y@KO#qL`FsOV?a;}AfUve~;KYS}^+HRw>SqG)I#Em4-Ksx^!x%n?k=f{B!9#iaC zowiW8MMuz&@;&*?4Aa@4Bl5l`^MhSBG>)WT;RX zs!6Hy%U6`kDvyW3l|#$L6`UPSLHAl&t~~Qbr;T90JKPC5$2#Ngv}pIGRt5)L#5GEc z{DT1jtStYyDMD+y`^SCTsXg=?=*(0$6*l&tN_p{*vs(J9<7seMc1xkrDp&vH34%Q; zXXt5~J<@LUaEM$e|V;%^sk4`NIqmu zbIX5I9GEH>m@0Dx51}e4;!jF=EA>>fO^ePWSNS$%Re0yI-7Eo!C!or=y~bSD2xLE) zK}KsYJ`bvF)cW;p7^xB>-`xI&4Io-Hz^BBw$(dztb~lq@PCg?oKu*WQTl|{h?5-3G zb-9ctK-1c>M8 zyZJJMJyFF|B4q(NLbFt|(ZmGG zNAehcB_=}?zt!{uPy5qo!K+wXkz-c1X}{aEi}I<*nIRrw(0J418H>W}$`fRA$eDsk^2(1B;} zgGwH(65iIIiIo&e6iqF1n;6;2`HK1#Lrv|)(#1_%c4oTRu8r8Nnt*H-w1)<{ce~%0 z@^jZI(J=EX0^YLZAgEm>a{{N1>>wR10{Iv9SF>Vejy$$P)3)W{}W&z7c#Z-&#?%MreDaUVWboC3uLsK}3K+-0ezYzCxtF9&O3j zfYHRI)OFGXe|1&cFjbVxd?*uoH2GxgZhtG4puq|(7uA*7?-gJ^suIN{H7Pzod&yzk z?_VZA;=WA$o;{JazQJS4JLexIrutV6#729fx48_*dW`BLfjn2}b@wI=Q5j0|cnN0G zmRGPLHmZ#~*5*3$wn)1eNN5oe=_1!waTAC=L6Ds2_5}k1R_=MvCjVj2m$tG&#aDUZ zjPU;F=ijcaoRb%Sp)bY-1&?EYFqh~o4Jy9M%YC^X9>W79zH+6gN8eDe&2j^@bRdy7 zk5#u8xePQ;M&dhtgo7+l9-7XV>4M-h`NM)08&`TGseEmT&}(fKqUf=C-<*CfoAcX^ zGYi^-Cy{pgxsBXt^n*2QiY@}$YD+}kwzy{c^m7~0^!AZ;O7tfQOEWu*zGv{ zxsM#m@i-|#?<%QgDu6H}c_gsymFAMec$&e3qep71Cin@_f-Yva zT(Hm(f4Q*W?f6b_0s8PoZLq8;K1$r-j)%hgkDfQ3R%N%Tvs#|1JxvMPQ|&nIn#1k( z?xqcCPtBAxdU&@+QCb3DnI^Xp*{O{~F4L<&Bbpmxq*$7p>m(>ZLgJ#zLTfF$JlR+j z6UEUzc<*T|$Gao1436wPBfR%{ZRybz(N+%66LNtW5|Vn1ihd9Hk}p5Q;Im;fIzzHiCU)s2G_hN_0z`}ak?cZ-m2t-VJ28}9-lhe%xSu9mS+tEBO0|{> z%0)5q^YbfISAU#?H_zJ2V~JB2-G15i+R8>XQj2s|V=6DC3()Y(NY@GZsw7W7o3Ni@ zVmL2-W6TgS23!zN9unyk+j~ha*r+ZIlDrtnOG^9|cHu-75DIxO6*mEHzF~Xs2n|Xx zEo!1(lZ{{#qZyilr3${Hwuk7CSO(O(E3w|+7k%Qge`qVuQ5{9Aw(t4*0YL(kYQ)f| zn`ruX&BYL%C{}3IcJ!60i=$aX(J7M#`bl&>R@ z>@$QhYB`-^?l@v1xqPWuD2=j_!&$74R9~H;(74RcNiS=qF%socm6xs27 zr0pT%a?LG_4eN~T={MIK8}_@%p1$)6YxV{)?CZ~ZKAJdHfTDeIWZwt#Ooi7;Is3_+ zyOI)ST}=^FSPS5n=ucvj{5Nh&DRf!#h z!eDV@qw@>86to?-4`@3VkdwVAVemQK7Q;$jYpEw^or3m}1_=756qM)6J4}_Nx%Z;K z9=X9%Qx4@ra{vRsad?3Ghw##*Uh>SNc!^p3N>KcoCU`B~NH# zN@mdEX2WF+k$wC3=(;QcJoE>B6rfg`WS5YUHRE)GcOIt+ukt#PD_Wv$)F9^^KT}jl zoFU4CoTWm&2*#~Ne@p^+?G>Oc(W19V{bJ(*Y*)P=Aklx#iQ39Xb#7pL@v^TbCpr8m zT4{Y_!7jbyni+(H=-3qVeLb?jVE)mGzp-TWM_c*#bgFjsFG$4dS6fm?{nyO+x;y*< zyWbCa88w1>pV0TBV*k3kb%!9tE|5O_!SnOSv_diJ$^|o9;jM*A>=9xXMGjD&FL3^H zKYgPR2{1?{;!3Kxz8gsFriB;NqBh7@~up_cYeq~>%JjeTl~;f z3lmq7SJe*#^c2V0m1pQ%?cG9K-X7_~`H33y$fbAOktY|V?tvX2(dgck2#h<^V`13c|%I8*P z@#jy)U$Xdfc=J~sTKNaqS;d>HN*5=ub@+0t87(6IV+k>bw&%;9r_f=^uQAN!YSMyV zW~BtpFcy<2>Dg!|Q*nVbvOf=rPv_i|DW&7~>pFe9pXuYJGjy~yL2{caJ`&7P20lQ~ zX14%9AtqmsBh^`?t;~CrR_Z|hTrGS>UYoY;QR#2z0rdGDdHQq7mP~h+>-aNx zPC;7yESs1oc?@#$$Ub@_2MGO*^2dq7_grM($w)e7_2OFe3U*Cxb4JQY% zcwG2U@V0cRIk*$&AS-AFO=Ba^!+bcJ5Qr4nrE#sIi^0a9& z@nT8TkQk|c+_1WJNRi5z9+?-Uogx-d*U!P zJYy|&%C9m4GRuFiSg1NqBaQ4}kl2REK4|*=vSK)Iz}&@#xuw}d!dJRj9yx}HO_r!_ z5EYa1T~cC~=c)L)0x`1CGET0Nb{3imsCpeuUTtDNAR%+0wKPynt|1LbO9H_aDD#TD zO@%E0YL+7IH}q!{^Mqri0d8@)1z2|)s9Fy!Mem1oP887s*K+cg>+=ZF!$DkPDhKNY z+z;sS^1bXc#q%{8ns~ilsU@D2PKofKWrI8k)IUxzli;^xdx(x@!djj{jS#JnU0v8+ z1VQ1;+#$5fk^*-TULeQQjg%ghJWgAAmTR?yON;CsPCx-VRrN$=JpEykeZUfiTo2NVxOj;oii1wvaS^B9hd+_CArj#RG_ zj>r0{%F3}wy^d3_30{F(Yv!XE5~mUu(hngA^YS7)#6?7%!z7N09Jz$i<)rntI)^P% ziK9Kj?C$IE75i0v11Ac9}a0qyFi-bZ+Q*0O>^n8?odm&JD+dOF!#wa7&2+J~jul1up*d3bT;!{Z>- zJPUYFCVEIklUbnxZY#+IE36y^1_~RySq&lJl8zL7$m+=!#LC7NDZgej^0@71-TA9X zCpOXVM-FKE`|~#?4yFFpn}Ph~d{`kkF>9{eLRmhFw210wLCYW^U39ile`~n@T;eBS z)M^P#nLLHlLBS@wiU&zf-nUwUDkYCzEf*Ig2d`fjEvx%5Ef*bS z%c!z6-zuI9KRprt8a_4oT$W_qLzm3c%ko@zxN#;a%kmDb=1a35#+oIlY%=w)!eRG^g(jIhNfN3<#^wc5Zlbx7BxNc2B=3``_dzG2)lT@cBqW;em;8MH zrM$-VZO!k{`|%{ioP;Ulkc5R!4e!`BT;wFYs1k^0NP?rC#NJsjlb#( zZ`i?iz1@EX1$^N!0-X)v)(L)D?L12)TH^|y*(Dn|Fc4Ymnbr}1bwGG$zwm}#s-#qs zF7?rh^3rdp5{UNf83k>i$m>{g|` zag3*!c9~C5Kpn;^E$8U0ubl6!cn+@><=i*8L)|yHgZl=NXX#IOzjRPr*=OOqBV51s z@&~osz|;}SAj_p{nB~i=A~;(g zA_sSDr!bAmXK>W9&k3vVf<$5S|t zZs4RZ<8a5O?povHk6DuQs)xAPZkH4@^ZB41x}5vv*e`u+q`{L(OtE+nF5$PjGzxh~ozqx>^y5HQs+kBlC-9!?s?7eW_mfx;$XTu(W`I5TXK-zaN4{UjrZU5cK z{(gxW?3$U=3-X8KHs-5d=<9Ul2~J&HkGUuiY^EYrtEJsjRn}48)4P`s+wum-?04Cp zeNyK@sncnX^3sE=--feMc9YqyN%?mDS*J(46Hky!=GFb`P`#;CZ}2B>B?VJ~-}0k~ zvb7##Sc@_K3&`3mB5~2=qV^A{oa2Ha>O49H_#t7eFL#V`NupEv3RPw`nV{yIXQ| z(~P*Y@Tv;04~~%vUV#ZrJk1|zMY_%cxe@Gm6)> zfq~u>pL~-mP5bF_Gk*%9g_lajLI;%^k*gU!0pq&+NsFz5i$mJ77|`T^LcA#(ej;(- z$ePW;=|+`t376sdPMMAFX=}m5!IzB-Q9=PLmlTDzLH+3#m$^xxjYz!Md}_B_sVU)I zDw^Atii&*z9f+(I2bK!*9xnYVkk4lMWO|0#5T8LQ27H zEGWFgy>w)8Z7L|SM@pnechI8}^QTO6@^|Wf(@0mb^p^Wgoh-5B_RQs`BOyISKuP!8 zO%Wa1*PD)zZV}L6wd9fuZyR=T^Q<;jme;=ElvT>P;nZ#ki%5zjX7NYgb?+ZFem85gTq%FQp?^YgH8H1IOQC8K*Alg0Zc zGPVTZ6M3kO!mgkPGI&VjA(?!awjvHzD}4-|@GiALye;I*phIXZ7&03S=@PMe*l{&6 z??UJ=4nhu+M7N7pW2(ObhCwd3ugGFJO96=HLjE+FCFm&4ODPNI{rk?V1;gCQUv$6N zbw0U7oWSbTG2<2L(pBoE2eJVvu9yieV~y)r?`nZ7>*`fBW$Nx#1P+qfeCD=ITStH_m7g8fMU7Q7a_Oqfclh+CnS*9#M!}q;%-W$OE^^tG1$T zY0IB@!1V0cB-iSs-wHFUf)0*8&{tU*t`}|y7=^5)H-CAQl18|dzwd84Ze2|~u#dwR z1R~JVDycz@UMS{5A;&0ga_|~u&=#XfgrG)1j8Z(YhE!y4o)&ppYNLE{ETSgJZ&bhXUiX`FmhjdHX|~dBA7)e^G7}0?3pRAs6`q0d>M>DU%w*;L8H5&*tr!Dy_#gpe(y)Ik)IWz}) zXTUR*gW=ny7wD}Nj*A@kB@$$>V0$S=(xTXJo&9K6>MT4Rp@D_wV`s~z>sRcpS%{jB z+roB3r5(~09U8^9;v9g8BW`#((^G3FgK%et9H`>&Z-VlM^B>v3^l8` z9B;`3*)Nico2O)|`MOkd4b=$IJe!s~RX^N`;XIo^vOg4qah<9Gcfu!RA^To#7rTae zDHx9QVUs-HcLdJ~ecR-wNpSbY=1J(UVatg+8=qvJOhnJfT9309TFX_yvw16LARGxd z>YMdVHE#@hcJ4!*P1>sxRoepJ6)%r;{b9h5GYIwW!H31EI(T>Fz_Foofyhbha;#y_DK+bid;>d} zI;l1{ajM8VS2fBthsuDq>UDg^4w1hY z4`8GR`#6Bmtlf$V{ys1G%dzI7&guu5D>*vIIi`PIA2Di{gA9+@G#AQAhDXev3*{)o zqu4&X`KQh@WBQ26b6p?LuE>}^!h?P1;~esr5YR~>5@hPLvWs1%Sp zG}X!Gnw#y{BY8FcCL1_t?-Lp$bZR;iV*Z0@P{6)&(vrhSnGx_9*D0m|?B`&Tcz|Lu zJT*4MeLAJWoMRZNblitqwTwsp5G!M7g|bp*9HZq}bf$Px4wr-4_)>j%r6kMQ=}cy$ z|LC&B^;7~hYs&0crdf3Uual@>UiaX!w7V-ZERd&P=}JhH8FTV(deY!Ca6@E)ofFYp z*!&7*2+R0nC|65|?L(0aB(_QzBsmmq*WXmfqJrg|hlbndAzbG#@t(q9t3Ldv=b?vR z?sgs;Z|05eEtK;RgHq?AERQPgUjvD(BhmhxBT=W6 z{kS8MT&`1M%#kBeJx8L@k=F62Q;t8!PD*WZ>}BnP(ofYv=@Nh-x$Iz$LsNkQfZ~%L zhbD1~l8&)voWsG!i;LeHP-29ES#GhzIfx4vV8X?q$wH ze{5j3i~Px#BWnZ>ob%8cnS6B~0=kfV$)4t0st`lLGsl(eLs1j>C9Jo8O8P+bn@ZU( zsXBvR(O=7%2wiAzXQF^Q6L|-+P5u|ffdbmOrE{^Mr#ty`OYSFB+4LWt6N{@-w!#Yv z|6Qk~PHMMg;@19EZOrxR8xi^(uWvCEBLwr2V8JPPkc)YZ1-2lPmJaio^>Q& zaeigjh-w*_B^|0Ztm`)*e7WwWb&5ceESliOlFe4ud4YEK<$%2kU7L*|=l2O1}=wYEb4OhAD^srjsDi@~68?fQ(DUcbss?LQg zB_66xi-(4!VQaEQ52eX8J+x&*TagVt7yKqWn*LP$^?YMaWjV2o(t@FqrO5)Je;vNF zf`vxD^m#YId;c_?9>2k&@nXp*@g9tmU2CyYauSRgam^uGo9 zz-00&`M~+Md|;!t^c>P+B}zVUBal~jBesjNq8np%H)hKR3I)af)=#0>*~AcAgh^_F zv@FXF0zsQg1WAC0()1~+#Jqag0pg?hyUoq-JuWWCv?MO5M}The<_p_=j>%UfJEHHM z-6dgFT#27EKZpm07Re+Xlf7t>1k1EqgRVKzVi{`qoSQ3nXaQx>gI(K3l59Z%UMLfcRB16`q8$b-9$U%Qe zMnnSlSBhw)q}khwgr$`~O=b~BMdlBCs^VoT@4bp4nJPBpA!JtAeamOlQTN{MqX*`Z zcIsIobE&7tbv$I*h7?nad0gKdma;+84kJxF6w_@bYdM>h$tqPQIg)*H(xyn-Go-PB z*RszAKZYl%Wy=kA=Sddi@Fb_xA`T5m)k0_56b}p%IIjfN%*R)=h-t)v&wD)~J|Dsz zAt`H`G}$cqS(Z3x@Lo13#6ejl(ARe<9Zvuj?B+q_b~deHR+L zX&vB$1j6X=yl2%&9m-y&PxCX_I;Cwt(l#7?7yE)=U4QX5>r%f>h-Q#xmv` zqb&0c;>)BhM}psynW2qRxfQw+F24foFVhA})CPTlueGUA@7q=Hd65HWr@OD18i>I_ zhjoxE0u0RafQGF3bh;y)_s4f8uA6kh2_3ULAcn z9yPKYMLB)QS&l~!*b`x|$wBnzUw{vP?;ScVw?m&zf2w=$&mTX`nV^H{&*&c3>r`jG z#G^82!4&??)NKT+>%Buq=XB^Z>d%DU`*Y0)*%NdS{W&OjR@39m&WzrB^TGb?-W(LY z`}F+@_THb_-TL#%>CKoPz-Ne7h@cdcjore$$@Q}~)r0s7)!VK04_GK4&FNA$+1a)) zdxmWLFE)g-e@cPJT0O$GAhRKyZUeNRz6^l^SF;~fB0sDUN{sy3B0^0?iVm&F>C}1W zciiiR7_TW7iagxv)@RzETRx^gckNZvbkOUSGdt<^Vs3j{FL(@lz3$HGO7_Hj)#`{% zpY7dSR3jh1U7l+#R%!NP)jQOO#j2JCLP9C0e^dq7T;pk}HwdFtp*M&dW~F#v_JtiO zW(!#>{d4t|{oL+csjOOTPc|^o$NyCSp6t1QufN}|f1e3`dNF4)bN1V*-8%G{^yi)4 z`}62~xf7JZzafafhia`GS>|3@#3K}tVI*%81icG&YkzfOAjH%h>VAk5fH)(UiMD#k zWOLxVay~(SSD62?%%@HLO43zxya1aN^C`3@O=EB!M#w&=P;V~Mn~L@PuHha}ESu#L zGK1vAwWdS>_V2WBzH-BdPMj_^|3Xr0sZ;_DNZbUUG0KBTtdPLwk-hHVkuB>(M>4Ph zYA|?@%Y6gMN%W{df5ANZXCJ1L(~Uf14rW5k*{NFVPd;wWW_O!2syRpRO7^oWpEQ25 z`3Ba6Op;!$R1P8*rgMU-Lzp+FAc(~m2P~1=ailEa=qc($3=qtVk0#WWb&e4uhz@5d zULts!x$F85Q_!;-?tBXNPtEt_Drr;mAzj&8+sGoHOY*^SXD1u=%5v~fqp`%8RBE0_ zNHt@T-}`<_Wfj1kLlxYQ!dao6IQ#lvqW= z&PgOIZ+L%RXhitIKatO68RHs9#jGv}*x(E0F=relWhD4P!(z^TSO&B_AZst;04e86 z{0(d`VOn;idg8gg_TG!ItEWa)E| zOL$vBGt1M*d;*bKLi8cj<;+Im3N?djVmDEU%9r`~s0Z@Mbds{dFOo>|tgu&sITTb@ znN26NESJhGh^{{E;KAr8vfEkYR&rbHr(DKlPi8Wi{hw_|l8jIbsUtqsTJ&6qJFDj* zBw+MntGlo;G*9dm{^D4s5KA?2qiPH2m56$lcuQpzdy=UCX7!a3wK~~^tWNMRP-Vj; zss`Uf;+)t*&s5PF{2il0#RXMsvt`NGOl2NbS-Jqh5q52Ye~ijH0kU09D*T}OX0_o% zx$K=)VlF%GpRBrg@AkCn(@0kxXwW+)B#J9c?UvNdYxWhHDr?<+krV2IkxM;7*&O95 zzhx8`WvbFS@>?nI1ozu$1rd!-V|_Af(xAWH#O7Wp|IAt8y2W;I*f9X-b?*?Z#D}*mN)E+1(9t|H9%B4twQD;#_Dg_wW{* zEVk4Q<}ng+p1y&nKc)tK7m#CsN)m#Y-Ki3(^%>>b1$2oa;qbQ-9|xq&we z4f@xYsy7P-`qvBfjQTC8?_W>#Hc8@PnSTenq&?#|hc9)#r8Ub9bDp z5|p;?-ch;IShw8CeydgOM<;HXB9au(hG}V2!S%6DyEc}j*CAbzg;x%KP>Ud#RhI?R z7Njrphpir#QM8r-g5LPzUF2W4oZi@HdWYUKKdq%-HCRUlPT0W(&tVzin5@;ohnh9Rq9uG zuvpSM%On2pwxOe?TKxrOINh@k-GfC~E$i z2v=G1_rP_?-yeI;?XYR)udh3ht#er*nvy^B-9E`$Kk(m78CN`diEXm8Z+M?47zppp z51l65)pv|(_#+I2+DEQXT;&x#bCn!GvN=>?e4R$@^A+3-Ys7ukToe^<|A4?#Bf|t1 z7`Bm!8be|Ih*rS3pv+tkQ#>ob`2#wuZZsAbX8i>ygBbXFZ9QRUoxea7KE7rb^OO9R zQ!cOB{DkFRlgS9|Q1W?0Wbi->Z02|Ga!1#P&Q~b8#~qT}jtX4CW8SOhPWz**Yr!O_ zLAqxsNGh#l@w`qa!%Ys76JRsF0A^|itmqV$ zLjr*|!KzGv_7~gEkIKo>*&75dIeGYG-@p;F65dyAN{V!>dw1#*Fve5*$}q80FC~lk zCkp@TGT&JCp|ZXvYV$Vo*P0LD^J$+~0;)r+MR&2LoXpk|3VewA=97W~2M={)SD=yuOp!AA!~DP~)Pa$*mLo{aSYxuc_SAoGR zfx+V#-q;oJ;ZK-5An3y21bvo4*j`hNoFZ3n z%sN^6k_w(^oLBTo0N&`!9fwzLJ7%m6(4>M8iYu>~EaklR zF~!q2(^(i~$pge9P!LGsxhjp1{(>>WGkH98vI1|`u~}-gfvHeoD&*tl`SFudlPl1! zB@3W3+4{6h?;L-mPhEfEzrFsf&NqQ2AKQ7R=%DB4Bx8h^gA7wLqA>EB9IGIIB69#A zyx^5b$K_#iAhNc&$EFXuz1;OE{kPATJst$oGM*wN&^?W(y0>=!#qr=p{cnb^l2nNQ z7t7V<*?`;-SzFrUf;bzE{d>^kIkJ08LwZ|!)xts7e}n~Z;VTK7Uo^H!kg_R>;i8R^ z$wREP3BbCg{m#bDPXXV+o7b8-X{z|MTA#ZA#r3gBf`Eh!Y0STOeRA;4>ZIX>6UgHhp(Nf|pjL z-ITwVlf7+S#DwGSjO_EwtK!ZmVF>ZD4K^iOcp|Qv80yL0i-}+G zM}J$rFOv5de^hqtVfD9+>c5^A{HuqcM9&f4f0bTwU2OKZ+%*r+e=*X2pysL2VTSLo zG0$Ih-zxo$BtGJ+46K~E)yay_g)5u>_~ti^qE)9A`RDueqO0Nl^<9^~lFZXP4P@(Z zw7O@N8WBCfb>fI!GS*E2Eh?+lFU(@shqY60PTGAXX zU%7}R3dPVtJ2H+4Q;e9V6zR?AqLCMgZjY^A!DW_Oug^AHu8^fqx=|qXO0KekyIvR^ zEJ5Ow`>hB9^?gue^X!L}C^^w6DkEi#7+c{FGhg~YP&>8qAeNXY3S=Qkksx)F!s9LS zk^gwSSv{ynr2?>0Z_T8NJk2g9Qg8VWeFcX>S7H)1#xC{7qDowv+eB-owb}B>e`ILR zJO(q5b25(?$fMQC1$HVrjsTD7H|qFd4ZG9&RbDH?gC#J1h&KeL=r-Trsxs?ZlAA#; z0TmY6<5yaRs6R|Y)Y)qNNMpIAZ4uMz7$isi@|}Z^5Rqr7jEa==X8?j^`F=_`8_V@m z>7113r&2kk9X~C7Q)i&kosj-W{8w{(f6lXN?eN%#g$fBxpUjGqVHBi+|JS`D@!j~Z?Ln9GpLiLx8H89oM#vP)h8(0 zTa`|Fx2j!s)ph@ds+}f2?QgfPeg7V7`fp=FQ%~D#X6G>*?2pTO`goH4Q7(!8NXf(P zkK=m!_}1N4o8RQ4VIlkJf=I!0fGp8w7&rOH@B_%#@?(y@m*@LcoRykAWRXPuX*7po zYCmY(+%KOJRUfOKrqeD+^@cCkyc*J*SRu`?RTb8Nr2RUSeigIsXv4u z2z`JY2}q-&llHsni%8=~=)X(H%zgsm0wjkLL(u&48gx8Jmn8jEEo!o#DX*#Lc_3=* z7TMn(ls&^z=3)K)54-y05$5kQAM8ZFiyjm=MXT|gwpJ3qWLJtyW|mN^(9=Ya2i8X+ zpw)N%Sz&tT_)~LA&95-;5wt?JA_fev;g4%IK<5e$2jxc#-j5eW$?!6LlVVS1OS0%< zRJ5zYfr@iMzXs1OF+2T}=%!9sU?>a5I*-C3C(tFspW#xo`jK#34^x?g1J8AV53**cXg zgoyvc*DzmstmQFaJqFCn3oJ+WLV7D;=K!4+eUX%07&AP6MBXx@)8&sA-E2{A5m#A* zqaVupggx6MfM9Q+bZMEoyNBp;pf|BF`P z6@D>%A!UXCHBusbVAyF{FiGVvw!SX&zvJXDbv~8xphZVI8LV{bv5Q3gl7UJrxL;;B zWFwV@tbENz0H=Go*k$8wupE})kr4YWP{vWFyYu9a0A**tXNoyu2G5r5{)YnMb^e8n z(dvnX)mFPIc$`A({wjo=g?+*m^cUEt!~`CB!rCNxh*YEO6T*%b&ipEtB6gG)OLaCB z%_1C6nCWW0Nr4&QFK-$4y?N`^aLTPHVVcZqo`hXL-U$9*p_ws2=s5U zX8Lmi{gcfziz<^o8hpeYmyB?&t zYx4L><_@BBvw8MfRnR&_<%DOwfbgtyRCv~oUd!432|49XmGG>^9HmQ`HNvwV+G}`L zHUc{&tNticn10pIPlsfk!w2n9^RkOS%*4xd;sII7g5lE2L& zJs0RNH8(6LG&3rzCM0m^o9zs(Rt695b9_Qwl_`6%d{PFdgsSs<=TKgR2(m2vDjtmA zLa`C#w}@oX30ID98kZW2UV;U~az;8)o6`pJ{Y)DYTHTo7pUp@-NPV=G<9yWRwRNsJ zZ5g#sl@e&lD0<4z59UIZG5*1pRnw~2fy%{ObZ@XUobFT3qR9Sykc%?DX>zv( zvG3Qi$6T2ib7&#W&SB0P=$6D7Q#UvWsFa5>v%=|MvfeN6r zVZ5d9U5h*f8>?DRs1m8&5SMm+y}Prohnqvk_zReCpFYW>H@aIfwjX23-^Tc>NUu)5 zOpbD%Dw2Qa={TPJ$tN62W&J9BzKEtu2#3Wwbb2@tw6D?n*m4aFDBq{hrWy zoqhAf2Im%x7^ZOFqn~cDx8pPIxwH?ANND&c*1LBz!N}5KfC-GkF~|^klUz83E?Rf#oTR~8ay%`U$*vf7gyRwI`)L# z!3yG;@{QV(jexrrtrws#B&t-bmO=Vd5UV zuY7~HMDCc6RX-A{WuqJUe1=M}1-{FeB zC98PTYaemfJ>rq#(k<{jI-I7HgCIoi<3r~-_h(q2%J@{v?Uxb*Nv&xmazy5X8%-v% zVJKq^Eh^zC#UO(Y@?^~aAhA(?>-=?{E>C1_r6azsgOOAg2rfdws;_x$D^LCwxaSt; z=Cxpl`F1B~V*+}wd6GBKxHur6`>UiO76m9?ND`k)m~8nK{z86B?LELR@8)GxWL5bq zp3qtRmh8j%C&u3LFB@aK!CzrYU{r{q4WkykRbzKC2Iy)+tlxmK&V=w)N4PlaRX}+w zgJ+59t3-O&T18yd>xMr_F$&|;#=`o;B6nXxLCZ~GoB7~cH>c<#pz#AzjZ1yT^g;qlW88ky|H7l8OW!}OAM z`s{VM1*~IlTIZAPOAoGtUayneYFtDpk`8r_aP>`WV2!M?caAJ_vvqkVO-_vuWRL#> zvVdq6G6Owr);dBMnI4bqfr7?77g0tEl^PfMF+xf`#VX5Aptd}*XO&jQWVcd5qO@TC zQGs0!J3x94>oIu}RePOUt!e%}4N9dsJE)+tRQgHynU7W)nkW_4W%qKaHLNnJwBpAf z?Wr?-vbjE5p%#q_%8v27p{br^owErh2c(~>ov+Zu^t0TY@N_fc1eTHoP?tKGPsKS8?D*b?se{C?6aO{*nntr0e(k3#AA?^L|26#L zMiUFaenvBC{Q4>SNd&Krd$G zpX2O5L9go1KYGb(oAWumnv197WoO}6tr(NCf5BwdVQD(mKh@sAs^#c_5;fo2#5yRf zwbmRwl3hnZXA|2Cz*|VBf$)N4KU6weNbW>y6T@#Oyq|N%&*&`syBxWE_C8i9G=ldM ze&n~v-rCi14+`raE7e$g8Moa^HPOG271DD8R!7l5o2jYrh+_W_WoAHv+Albg1t?G{nw^8 zDO!U?qnhiZsaTq`HaMrb!%3y6&)(s3W`*>e*;jil_gk_$7CmhfGa7x58*z_$QwEO+ds1 zP;ZKjdX=DFbvD|GFVI2qj@f9p1htNZcEYEm(axKVcDTxccK^>pJCPB5BKDC9FU>)_ zQiXQv6mReSiILf8_m|ZQ?erHYIaONeiA4kHBs{JJk}QQ?Bb%q~h>YOG95yW*=O%pb z<8ZDtjdNugoEsxJw>~`=S-fN#=dAg-j6C$TvbUb%f4~gLERaJR~Hi&XK z&Yk8>Q?jw`v&6Rnt3EgQ_MLk^9lnVK5MWdXBlC%Kv+?akTqAlsys16z3w+aB{`2^z zgt=Li=(E7Lii!UzeEZE^pBsF8{LW8_Z>MGLZ{NdPFvC*@&-()3ruskM`DhZ49p4+4_Oc?)=vN zr_T+(y>a`e!?z1+vi7$nP1*RiV!{{r_IbxQS-#I2-~K-8KZS4OuxPCHmZ=ZL3 zljZxY@oib48@^fg<1g-syw+e`=`+saLa!2IVzEIqp7V>8DvsE*^s*W?pQz*l!6q^1 zilYh9T8Dj{O3sR-nK9?0Zm}ZQTCSn&z36#d`Zn{mUpiRWmlVt5MBjl<&bH^W42Aw% zG;-*d^cQgVu{GfysR8MHAwNoGWNX5G@;+7^jieS!6HZaF+nq`^GB6CIloP))VIMzs zl6lN86au1ZD^`jwkKfkFboOg(6a98 zNkKrT(1R0bD)D!jg^^;{Ci*!2g{HB3mt)&y%|(P9bh4J2=&6&a4U1W>?&>&Y@GLW? z?=VLDn2r0fZjHRRo^?BUc(sxg#i z^orK}IC}6wXx&PZUq>%zS%jUCeiHjGW$6W~e2Mv0NF+bQ%HfvxkW-5h0SG)5HWZjKv&`X5Kerb(vjhEIo#t|3{-xZ<>IWc>XZ zUm)ZE31pNt{0}4Jwr{49F>Nmvz+P+$_F_}nL}#erpm^HZc4I@>d4*9`OzXeVer(tZ z2O0n4_G6d)By(Qu)heq8`>|}tJ2P)cHbxfJcE?ED8KEb3)w0=~aFF(6;)2(srP~+w zV;^H%_fb|pZCUs4vS*o)u^*Elx94}HS<{{7)Nf>C-)Ct*_Qdkf4Za^R<(^;hQa=*|qq!9dcHFS^tZKP|$z> zD1W<_Zsji?h^elnPx9n?51;==e{87)4bgA7iN7-pB3Scstt45#|6TPLNK#k*4Ec#Q zUhwOtUEEZY+GH%cz-XLtpDX;Gr}RDghCZ%v{Hu$fIIi(~2lVgc(l^Cl&C_mKcdWZT@`Nk$LcYF9-?Bz;ZEbyjXZ%&-BK{=i36yulJpZlN z6MwDQRo`*EF|CZ-hX$#-@zdO-vd4+qc+19P-CdDqR5g3D%Xh^*_tN=U-ez1LzdJrP zzrx%WmaeJdf4u$n+v8?F#gDOz_tPo9`PG6dv{$=!n23$0m8_OvODEO) z-rJHmgA|>?^0%?1c|E_)uC@!mn&Ofn^K$BswN5`{OKS1nzhARFr7h78Wtv+r-ggL- z6S(Nv8TVdx!9)Jo6|c}%LhiR+bM>zR+vDco_}+o;IEk+^H~Wqt!QJlOaPM1lpPGA7 z`wV?jX>4J!Rff-ptIrp<|CxxC$R4oNaseKrmc#felc>M`G+lTe;gZTjNDzW1Dkp8#qQf&eHmf& z$IQjdnvL4hxByLKVE9a&+7j=|%d`?g+ec3&+k4xq+W2NHst9+~zPA~iFzU;~@!I#E z(5IE?jRDp=15{wsi3}>%x^VOZX^`>~y6#?Cu{8oq<#C`&Z!C+Aenl;^poRlese$e; zl_-EFuq5EH_Xu~~z4xN{jC=}NP^EybH>d)IFH9-G66pL0V3Fv4)|gfyaGQoMpz_VZ z?!5q$zR?D!oy=GUoZ1DPULEY-q;IpqseLA%>Mi$^D>m{p{aJ0*rp5bgi0TrEy3dBF z?K4~29UHgX#;!iL&*{INbNV6L|7#QYUlI4lwUu$|?jw0s)ycZp zs9FK_OGweK?oTim3&Q0+F^JdL$XdJaR+a$txk`0eWoh7bw7$+$Oea_J|2FN+cz9p! z!b5Ot&HnR#o~l-6~VFMKu)AR<^5}y3_)#tS@Dh%p4u;s-M6r zu<=Zx4SuG|Tl@uY!Sk_cg$2xX`V2^z8YoUyeOi%TUpUJht0%JY`~X!nnfbY;CP=Az zA30-3Ot4FJFS4m5r=jDrid@i3kzeE%d4qvEt<3JF$gJEV0^?4R1-%qGHMdA>R*`5g zMLfAhCT0~`-b<0qU&xN_YcEAE$}Mt#R*}1VDKaFth^~rE zF&0f_>0>u6<5gd({yw;zXS0SI@r=ewcB<$bDrrpqc#;?Sq_y_P z*S=RDv!j%6e+PACkmygraRVr|t3 zmsVCEh+W%8!PulqeNm-0u)f-?V}sqTEm|tcGqtDl4aLJ*3|m@bptcOQimL6=^}&Mo zo~>$2wCzo$-rLqy&+-aNDdksJeL&t4_wdbrRyPGEE@cVTuBT0#)#1MRvT# znvL_1vlvP#9BVABdSbG9KH8?pT8~j*WK1ijfP3rY)O=r)`5HVJx+C{~o#gt$@~s3@ zyR;;yxV~*xzIlvYUJ4g=)(y^kU}l$R@S6`sIvAm>k#Vi85w++`@0N#aSMxV{lxnS8 z0WzrtmVD+6uE?|(RVpy*YmG_Otf}fu&8B(mMJ#K#4he5+UbDm+(jqmaMxQaMOkd=S zc?ORg+oV5Z4sb>mSrm|d1uLBKX;)vUIBns%B9SmaAg=2xEtyp_?KW?wtF&3u5~!BEJ3U-NyF&F{l&iecPd zW1?3gVv+rT_9~`MdN_{Eex< zT}$ua`Hmx)iTdVP{RLh1vnBsb?R)D0(ra(wxhb{sCVo=${i$)HY>hm4Gp}$$-)8?v zt&}Vd3RvAq9LEQ(tdFaDgsac8(@${S8;%nTt(1)P>P((FnH(Zj^sjH$>lbv@FXYA5 zxil<4%{P;wd21VVzW#zH^WqjFm{@h)D|O}8u;xTcloOc;Y&h+@cu*Q{QyBg%~4Pz=9Xw6ou0zVHb+YG zj#qml@8y?ot=TpAA_n8$I%#Z^`2cR)zy^wUk9)WKU46P&Z}ubuPH|P+Ope-i9kVla z47ci-*koRoU5C^#$CDLAM<*mbmo`l@lS>Jg$P5eBn(j?pCdjBhJi!MmG5JS*?<}_& zw-Y9N6H8$(Rob63X$EPIysJXiPXgS?O;imCpX*q_L9&1+>Tl`$#+&cLQc}6TE72lb z3Vgn3&9w(Qh`gT=dGTt2=*9aBfkd(LbLj2Xk zE^L>)jN)(c!sv>PhXSi=Fn3A!Q%a?9q;zhx4>9wtL{I;T84`1nfk(N zt_rwxWn|xf%`ewhZHOoGE?lqbNc6wxtr_KSYpb5nR;?Id6_`L6{k8-81{qUi-y!n` zhTAJ!CwTH3l7sYZRB`D8$rk}5JNFos+t8R<)&JORkEgt|{Mnkfv?Ykg1$tR<7VFU~ zoCOcKcUy~k4ttDHOOO5ip%E^__t;i`+|L%d2&<&XPS%v^6@G1r z(A~_e+4#tO{ZICMJ$&JZYOHS#yr_%$vgW1y;hgybY^`~msn;@Jv;UR(Iy*gI6YunZ zu4S;X2`+XRWcSa)=h9(~V5=4_2O4XR3cadeZ7A%503>MqtToq*led17xt=>TJ^gn5D#Gu4v1?++=52X{6f3}TYEqe7&;r%7fpI^Xx z2HyX^;9r1u_y19N&pSAHAFDX4e+}M)u&}a`{8M;;QE^6U3&_Cx#&lvgc&~)4J3I6C zpx2_?$v&TmTJ@R#bw!mH@7EyMmwq?0|G%`W{{XFu>_0^4R%8>;;SSkO`!y!E@N#G0 ztcfehCjS9v|5x-P_y=gk8;VvOVfP$b@jq(Q(6>H_Ajy?Y`Z_M`P^8Zs2nXP7t>uqs zs~(X}!r=t!{D%uWP$=D8dJgnRTh)P-%o6d%&Hj|N%4Bm7)qNwK2kh}IsZ@ocU$QrT z^-b;l56G5Q{w{mJIk(a{n~*ZqUiY&To5q?CgtC^+<4&?8xDyVUvZCM#nVM*R?JCJA z%a(jlQ6!sx`WM#6bMWia=b+aogoMtf<^LDg=a}FpT%VX?!v3x6!)`ApIvaH6F)dK(LvLV0o!p1kywGi1!*a<&lPMTv4wJ!UZm3>IstP_VC#;@}-4oQSF6q0qWH@R9r zpmv$Rl5A12#1DX60h#RaTPN3poPk=noBy-;oz3C=Q~dss;sLYyn}f&i6{rZ({LRO0 zlpedW^hW96w-XOOCm4L8D25-GVDQg43gNTtNbtQ%JLmThw*7m0hcVL2y>3_q^~2&Z zzt_?udnkR1irgMpp(3}R*x5Hh=95RzL)-R_ASKEFZ}iKeF${iZ&69K%hhnv z+Z7$F!}MBRj9`_L{1Q<*RcU^AyezFZyS7IkHPNkS2P}@f#zl@sfkFH^BtkD>iSJo6 zn<{6yX1P;y2uy7r{guq^36DOiz{6)$8VfvK)P(Hjz2K-`q53!rs;MR&-8XocUa2qe ztZ8DP)24S=@G2zR?2oLp`*n7Y%6K9}xRp^A=;-k~^RxKK`#)rUX1sZ)+#7h)y5GdR zT=6T8eJnd(c#+Xmocfk|ZmC*fJ7Ty+uLUB@SYf#xM+S9zm_PFlYxVe*@HnxQS3Q2Z zi+;{4jwky1&v;10isAKbzHqD04`8~J-#^{ua=Yf87*F^f!VG|qJNTr>{bx|X7Y@I0 zMtFU=6)y;K=Os^#Ck8y^k!&yY3$Nd$a#Avr>!StpD41_d^C}Sb8pA!4$EHrSCQ6$tfbuk?hAh>Kh$7c;sGf<#+CT=cqXTgjI8xU7I95r zF?XyN%*{8h`T$i;b{z7Fxvw%mvK&yMJKXxQw5kY@gW1-v!1J0RtgQf{`NpwouCqYU znQy6!yT@@3@-ieD3FA*bUGmglu?!~>` z>dvhQ-3Ikf24wZOR$KWEWkNfZP>qk7ESu{bqxzov?-=fKKc0Ho80IlXd5wX_JTG?) zc*@__H|idL4RO}BKJY8t?zw9$!}BzmFa7{?i@gyvK?n4C-o$W`0SYX4QH|guK-t5ccC(3lEWo^j86>YDP8{C#E4y@*w}rE+l4v2P@@8 zN5^`}-2*?IsV!>L--x;LJ*X`4b|2P|5&O3^U3BTDO3rBa3hO{q$;Tw2fp#1VS}qGCw^9SSKsfh;$f z;f#95nb8?Jc;<4BEF`=HD9-ly5Xm@5R<8`op?uEX`;|xbaoc= z3C?d3>+MY3Mk-5+;zI#zfPxgXP1t^}+eDfx7ZSW(ahRnH=reVkKNU;5-eIkiMGd5L zpl#B2YFic^gZ`-IiCqiX*WT_JZ7u6a-LKBM9|NeG7{J%-i zX#PrVWp!j%8NL8z7Us*Jf_P;xzM$q+J*eYJ1*eFHF@jXO6+p)t{S_!iNx1_lA)cz5 zLQZ3guq{biX}UhWiNJSV3zE@5ov))RtCv0ri9t~zI0O^%N62feeiSi6UI(rSG2rYZ zOg_7fHm_2aWfZ6AWBfbms=p*YLAKe_2z$5QdOJH*^8pY#YdL>{b+C`TU&iJn3Qh~) z-ko`>i0dpbZwa19jj*&2b5a-f5J>dTasp)dV*|HUzJt0CJ zaHjugH(Ef*><<`DN~+GgtpF+muHRyycW!)svIZSQ4+ZDVs6gGQ*#-ZdG;A$iIhlFS z9YIs7&hn|duY+}BW{OJuH5e7XN^k|@1vMYmNq7~oj-7V*WX%)17p6Bd@A)IpFY~8o9585>k6n!Z{?1LJ2QC5s4CbS!>T@+=Z>RRNSJbjd<~%GV*GLtsWd-!=k}~ z{4z!VF{I$bvi?40So}LNZ~vFC`LD3%MIZF5e2Y9Un9y9=h#Nz;9^QXSTT2?OeYYzA zp9x%O@*l;;cLe>+k9kw45!Wi8YVY032sUUtbGnhqzXpHg+mMS^Ado1Q9!IJUunwdw zWktqHGx<6Ju5F}>CgZZ6X)&&;#u#BM7`q0eSR2dnEt{$LZ-D|xzw?86&fL}SBn6Ua zrx3((1Z1?Vs!@wZ5pwDM69{;yZ=V(LR>r*S!SDnI^0@to(~#{z5hTSpl?(v4V*oaj zMT)G5YsvUWqDegUW~Bs5j=s?bqV5|d@>pcR##*oQf76N7wA7BVM%GQp%$w%LG7XO> zNQ2+C^^PYW%ZY$>b_6?v!T&bp=ji!trGW(q?{yLo(y;eH>4)x+xBFp<1yN*!za4Mv z0#;R5%`+H#H2qDWU)F(^V40zF%Chxyg%iL$9~x!2=stwdAk9Y@qt5#qy$aAU4Fj4U zxRZz@_bQzp!c#v9PicV=T_u-5!R=S`LregO%Ae3{&-w#&W#9#{$bB_Sm6jz@%Q|~o zhH=DQnPl^G{|pdu0y%0e0Na-pn# zxg|m%E374@k6vl)|59wdBnuqbRaxj`R3&2VEJwTblgo1!EQVvTAr~&Vhe8W8# zv*yEo^^88fBfC>ryNK4r5J@bvXCn_k=HAB{^50kgPqQV%5}%zdoE zsRua~Y~S|8?nj=$uG9|nEniEZ3S^&EBs)*zD*B%xB>?Gi7}^#FAFmWkMEVD4N~5rL zJ=&*G=YXR7&zs4Y5+nS zR;*!WTwkL1KZ2y#08f?3%N|$8{t3PRCs6RAU9ni6k4S0*-xYt3>B5<-h@D*=Bj>5f zOZ0vt7AdICSUE$D1LiW}vP(XtDQX;)G`__s(m=DXnSZt%tA4QQ{i{J|Oi}-8VFr1$HdOolqtAEM#C@%586(v zEBG~Nxs=W3;H-36W`Rg~O5LJwxIE>nYo2I;Byr2#T_D>C9ZVxk+Aicu=N#{ z4R6iC>YbYt&wN^$>_ zrsqD)x-vbWS|b~|{%5pelp>e^jfA)dL2)eV=FK`Yir<3Zj$S%_Q@n)~ zklfMw^U86`@=2gX!C>ir5F%96aI6&Z->9glrQBbt8!1&3=8R7DTPkb)o1ntCv2mXl z(_|7_h1>;8+A9w)l1F_`h!3ar{z&j>hA6JL3Nh z&nbBB!}C!*ci?vu(ED;QFy2FfZTRU1!IA;LeEc5A?~qZWvEfO#C_Mo>ARWK)_z}m7 z_&eB~9-`aAl5n2KnHSRd>m^(~+fs~O;U^`0bAkwdzFtueI!ki*cOFr&_k4YznTiSj z73-6U36FV9lHWpCIDdaGt`^RtDb- z7CNk{7zzR4G+q~Up_GIkH1;P5d+x&{p z{1*U-rfB*M`2^B~I+hBX?dA2cwx3DnMoG`iEx6$nO`%k{IeR)Yw+4;qzGQCYI3*Q0 zC2?7t1Q?CSA1U`@Q7>{K>yDtUB{~8XbOaM^aq^H2Fj)xA7Gz}ga|i*~>NMLtuR!KC zA2nK1U&V+g&km0`>6;tW@oq=-M)f_^oLhJR;*Lb6F<3i68x~ch*yFMmq1R#g=xoR@ zlR*RRX6*$<@>f6Q@(nS^1PK+#wS;PkooxUoTQBdtos%jcGz}F!Xs_KS&^Ng%gW`dj z>u?q`28UGwHA#b}c9=D@i-P+hQhU3PC`L&ffsaz7;3xO0<<$|lZ$6I(De}40NX)z3 zM{GnAqwzUi3W#c9E0e&;kJA8s5f{HySn4T8ptf`)IGp>%I<2^0QUJV&)Jmi}k6>9$MbaY^^HP$h&|I1F74vAuCo5jDKw~pA3We`3J#039l!b zU$MMZW4+~^`J~o%BTZ>}v!(&Jv=0f|TB9eE&s{GL>4k@uj-Vc{?Fw7uzxpl(QJa=0 z4Xw|#sPQf0>(eL|8r)cz679YkW<$4z@lauNgLyBl0(!$*s%iSY8P_v+#457nLcPO- z8=A}a>TFXQv@yPkI(|d*(};;}(CTcZlDX+BmJ)5xiOb+`uq`lzoGR^wLV-4rezmJg zA6`qWQLInn6H(eb9?Ek4*Vjj{y$8*Y+q}0(t{g>Bt{QfzyJ~2BQ=-Oq0mj$f70b8O z@ZfHy(X^F7i|2kFD{fJT7b~`i6}AS4C`d-KwOk#Z>2vFHcre8J6h0Ku?f*Z9FGBsz zi|X%ETCatz{g4m@h-yz|4H)|%AZ~#C+jhTH*vfjRP^{16twuXlIBu2O*If!CE6zYc zx4C6o;|?Ind<>yw<$uBisO@C}$tS17GTMx>tW5n2Z30{PQa5{OsD0Q}@UOw#Obd@b zxC@)y#f{8d?KW5X%-iWJ?F212PVk+eQ7Yam|CqQ5XsPCdbYWlwJcZkJAKFE$Sy^8S zezupN)K~$^y{*3msdx$%Q2M}2PSp5q#Q60CEbE7`Cb2CRwsqJA1?O$(gIL>R8yPX# z%Jq7^2*G)b9km{p?vx(Z`DjDrvL3nyuVIp2Td7@_pPUo437d#CbgxdvYz&zlZ0ZOO zwl%P`^3%>s!N3k_PyrCP*cG%*VzxFPHbDH_GzW1|qJX0VY~Jp|R``o>dTcW>jn(wM zLwJj4vkqhp(}Tl2*a_KL$=0@Mu9^?ReX2)rK8)0XU#k-vL2SHUjjiwYLd<7zle@6l zXRhu5`GILtrnHed!^};{$V0`=tPt&}u1R}`Qnk?KGd^=YC6<5l0O41ZKq=jr?!xAp zl_=h_1%Ha=ej% z7q?&uU14Sjq0#8G;-;vXX^xzkOOX5s1AD=h)|V0EFMo@iG#=`EH(17*E^a%w4hszPI>OjqOGj-x1WT0bL7U3=uoJVl?9}Y^*WAJDKHwJo3(flo^e*sbZ||UAP}~Q&4CqXl_ySH4Pmkt~N0S_& z{BSI$6AwL2yxctHnA`xx1G)%S$927+YzFlNy0$Zw5)lTw7A#c4V4%f7uNmc1NLj)- zED8Zx9;=EtOX0{L^8rC zDAdTu3gASUpU$OeurStP)6j$e!KX2wC|r%;ma+{1{D9xWd5#e;Zz;=Uy8 zJnH>QS3R3vyX+?fJ7lqw-q$pV7mVy1S6)t!drffJG11#o(Pc*(Vf}~r>&`N$gHug~ zk$!+Cy|btUx^jJrNB@tY{F-}^f_2*41$!6D|46f2DSuX|d{57KJCdv@Q_I~ME?3)@ z-cwSvIL2~(Ed;+4*w+{ZVf{JKO#bUJrIBJY>bIKzJ(>gFE_ap={AUoa7FztW`>Ttz z*H+Y1b;?=FTSH+>%7gga1BJ3a>zBg=?cFGvWRQRd4i;f}*n>z852+Xj35$y9Ok5_a zKt@o33`mbOJE&FfMw{TmAxhWzlacUomcvK1D8%bFU#tjn_&6K|A6cWxvLuGR|AXys zQ%vC6M?y$!e-N_qpTI{j>`@;%R83idsZsih9(r4oferOIv;$(a+x@QfhiLua-SzlWbEGQmRQhr#QlT&mDW z6%ItCRG^pZo`qb-22JEhRu4aL^r1h({G*9~?+J#^Gqj~<_Ia-`V;uYLuT6dSS0d-J zzj}6Ie^tVL)~8A@N7Dx#>i+ua|9XGj4i%LC=!1H`TkPFi1pdDst6pakPt%?D&`~9x zA!G}4I3qj+AJOg8oc|tvrF|^%Vg$$SKpAxI+f?~2ZQ5XhccNsPDS_YdF1H~8aV|O6 zdQcNmv4-rkwvVcJhd?NCWPo z5;jv_l}j3?pHzy8)1IX`1fYN8w;&xFLc5`RP&H+WqbRI6Iz*#o8aWprh$E`XYcUo! zZ*D{ia9sv@lxG~}3o7{rQN^~~l=k`Js4eslEnNc84GeV`o|J!u{gwCMLG?s{v%-yu zINYs5&*dV_9T+M7pX4`V6>m$!$v(7HCLux?sPa35N^`EeLUTCWN7{pc^pwFGP|oPR z3nG#~bXCJ}*$KX6lLIS&X8Aub8oeY~Oqm|$C75vAbKw*(M5O?cMu?6RY0wvqTZNjQ&8KiZ$u-y%4PK!*}BxWVjD`aWUZt$1&kVhC~KYM`|fgzBue(OtMb zXsZYF8p=RJfF95`p~}2nD)!{)tb-gLTcX$3&zDJQrqzS+AP`C0Q*Ua^WKA?G7SkD z1|9I;Rau~n?>vYOJ*>P9vODxc1IJuGxjv0b1YKsGEtNy5af%M*FT=$(q6hsHss^&% z_7fg^w_R}l33*(G1ky$C@wy&_ng_x!FMP|a8GD4ys}w{D3BY1fWkQ&3|4K}rileCN z?vk0Zmahjgbu@dB82hBgKOM;+`7&aUra$h&E$k>%M~mbww{Y2rSiVJLh5Vx$ZF?@e zLs(De47UxQ8`)VXb}EU$X}v2RtKd*zQUgXLV1oXsy(>--X_-=y-9_@k-ts7o^&zxC zfw?XJfJXi<0&a7)WW!Qz1?LXS_fDm`{x|VSsDfpti#jZJ#gsjheRlbiNaRYW)S)mf z1U9Hh03X1m)<}B#xM7Uex{6>;zL(Zl!t^_8ja3#nV}!DRZI!GL?LU zfbGU{2cEiEnlH#li7!6AXa+qNgONL|4bVQGKH8EN2l2iU?q~mYGz(#zsf+M{`jEi{ zr>Ow+vODY?;NN?fzhBEVgz_mY|mmE9sP71-6BexLRB9@S?dcCBCgd4 zJlzxFpmh|M_8PG>9=d@T?_Bmlsc^=Fe&pe~i`7<~0FQNz+c1 zswoAbX`Q9Q(>G>hnc>QD4dzR1Fp55k^Wt|3Ke*w0`b4R4DS%%c!Z#X`7UhpppSP<| z{wJC9O4^Fh2!tj_tc+Ina92>6$1GF zG|D>0^C@K?k5lrWxrFa6_#U@_mv^icZGu<7OHCBFl*gSJq~tmCQK_)}OVmgG7^HlA z6jJlYcjJAS(!R&Ld6?JzXb^Rt$FH_j=ti54cYY&0jqyN@J*7gA4=vt>ugKkvQ9j;| zuNXjlJ&CV-1?QiEFA!?XMq8G8zd{fdFKvW~z6UK_y~p}ASQ~nQMGjW=lMfk?fTH20I*34c!Q2q@!gEe5e>Y-(9#Yc+~pNbYI z%Bz%5u-}cto{R#lcQ`ziT3Z^1@LX<+&SsDblEFUwnAnE{D6F+S0?}=7X;N^y0hA1s z&0g~02_#7Ejkd4X;4BVZH1snU4d>SNI!i@V!`u6_g(ta*3dxxWW~zo}0~nDKuPKbv#2-$maz_i_k8 zqz^Y6I+_vOY{g&7|Kn%>U&YVfark@r*>`}4L{Q!PkLUne1p_TFLkTRr3c^a8q4Qxh zKQov3RX>=YX(VgJXny9^aV-x+u6bpyW$$*#<(-`0$oZGhUiu;Y$xXkD=1=~6pZ9j4 zMeaXAGD-trqOR&U?6OX5&}w~HH$bIekMM_rU~@S>g*yd`EcN9A-TI)w#Z#mxk5qcp zMR2O>iBt8b(V=-N*|ZSM6rhRZNM6rKeqOyn^ytBDp#<`lf=F)5vg4s_<-OU!09uF2 zI0NXxJ`5lstV682T`@+gu>IVpp-OtCb84G@8s`5*%jDeo`Ubdy4z(>gvTb1XJO@fA z(-ZpI4T)u7tHS7^@sc0H(Jc(1A^1yt_M2pZ@hz?PG~Cm8*+TV55HhAJPTwJ?SbO z|9_4D)JMZKvcsYNW<<@`QtEFx_(5QA^sDj*ZQ1EX!4>)$@&#-%*s6r8;(B|x&bk_B zKB=(LJ|Wij6f^GyzXW_KSMf<29+hvW3{NXmWm{BL*%s6l&CzKkZQ0iTv}OD7(j?ST z*o4|ovIGXR_d*%1!0xGP4~zARx4=mL3F(i3&&Df+z8?aLJ}gy7>breaD1)}k>cq%2 zLH7$j7Z{X%BQ^ZUKA!v*haB}N7DT`n7_Jj4%EOzQ_w{jMq;P_9ZXPxOVcHD}Pb`SFfs(Uw}(v6bAIrTHR6FCb% z6@@MKXTg13m=kMzF)+wp{?RR1*@Z25bxK-*v2>q~&1+=k^(xZp%a*k5mCEa-d5r{F zf@3^dMxZ5#Hz9-``cwE*k7GgL24E9n6g3lKFA|R zMYALF{zZOZpK;~*0DJMZ0MKfT(qGjdK|pAJv!d|BMoca%M0ZI<$_|FO%;il}!V}C* z_^l5>>KzPD5Z2RCob_mX1lJP?_1VoL#2GX2mFt{<(g35Q8jKEN0gOYmc3u-JZ^;b8 z6^j}C(>AnH6DaMSOvRs~8QNsc+o)DHubve*hUTi=gG&cIvBD&;yAa+IVqk}@g{-+n zBYI+BKMT>CvinHo9%&v>82GP^u#aCk1su-uHe5+$P0BnluDr!|D)9T>Iya7+*Q5ZY zP<0LNL~$HgIcTzyuGYU4jS#AqX~p&t;^!0W-N%>Ti?#_>55#ysj~Ck~B%ZZ*ca;59 zvLS9}thYU0tka2~Cx|B|ct0PSxSO3#+^=AP<<3AR{~VKfAn`03AvhmJ*?s|kq~c~= zj^I6EKn69ruxPCN{-o5EwT#%dD1onoy~ z>`GX%Q@cy$XA>J8>L*m9dq#|{Ln_=WmbYjcChEZN)3;TEN+Gmpx8@f`FZRG)G}zZ+ zvRJn|hw_I=O4|bF!>3ybLpWf6Ob#t1sOV!s4Z-KIe>m{PKo-cmy%&QVr?Ri*H^Ca~ zojg^4P|>GvxuW)bn6`;r57G3&Ygi8)%_;iu(2?iFjiepV`<4iLa3K!ifg<~}a4Gi? z*z2?(V#5h3%`4?7|d8xF|(EB%o7E8(c&+q-yWaM)k4sH?TP&}`CcLiU1BNyFJb#a_^GL-u}Q z%Klz^L5yLpTQ>o=g4zQx4#PbhI@LlDSm8<*g4Zz?3u8BEM=O!NTG8X+Dgi4k$lCkU zy3GLN=*k+=QR>i2!Nfo&P9@ktMy~M;-A-CUcu&QHZXndbjh!BrlgUVDR1}``;53Xh zp275`#PaKESLI{P(8lvvwRL^9cx!QiPZKiF>G`s zJ+Z`UMrant5I;LO>}3^cyOq}HxAA-a$O^szK4~`QnGz7(55Myb!mm98@Jhy1dVcpD z-NhHDJw)a-Q&yTYJ?B3e-UtSb5%nd3ePmQv=GYp*eo28@;ETK}>xjicyb#+Q*DHkd z$;FLl^HPHU_>_!Cu`zY5a0=8RJuF ztaP+5e=Na+FF&i2@J4<{X#bp+q2LXi$HG_hfK23C6s;M`PDp|}=o3lv4J=-UqBR5= ziTecy=?=^∋$sJt|4X6^Qc9MsNjjX-^6(fy+9R@b|{^Uubu;F5&OfblUyWwdu9P z_pqi#d+3(Jcj?dhlW?lGJe5RzpF=kit~>Kh@TA2*onXF2FnvWTc*{CIq>=Rc@q)YN z-bKLAz8^Fyk#@VW z!YZ|3$U5Ie2=oRqr3L5j=_SmV4zuC>|ABa+Dp%{mv6M4+4@UNeh#6a}i|hbEiIH|# zJFHjsTDBt*ZY5PX&E^)Ns<4fS-WyODF`EVF&!{?Et4J5f*vs2B){*D(!5hDU>d;#D zu89~i@WY+pMgd1evz-0ys1RODDL&3}II>yH*5bCc@G*0lTN}(D<2pq!>civL63+(( z!^+1hb;~3gtH2DUG*-S>^ywGJTUx)rOtrw-n6>rc0#6_hRJ<&&@u`ZFpH#ls%Ri>^ zZ?!(|IQF&{?$gXITo_>N9rM6owkMF^k3fLc1;+Fn4JabgvmYZ#oVYm4J`}neR&sDdYx*nzHjKGr0kyLyUbyHAt6-w6DM;Ag^bIDSU_Qt-P0zrpxjkKaxB3G?f4MxKJ^((e~&Jc(TBNx#P; z!lAfOd?@|z)ii&+LfXiDKP3Eb|6+NecKwjLDDA(J58eR7W7W65r@!Z0-?TsEeOW}g z|7?slBQ43xsC-Z~S8Qvj{y$$uno!!%^MC$@JfXbwyqb3X{a>i#`{pLH{(mKWHEsT9 z<-UCteTtmxe^%cA=6eW6zORg}Xm2ELxKi4uucBXl^ODTIZ`?n7UxRu?{MO^QLakH6 z^H*w^^1l!frat~A;+?)ffFJE^3cXjo0JNx_0kyCwwqP5`KA3eg5Mh`3hJhxp+xa5A zB5^ezoZ#e72XRU@^Vm+!P#ARh&@-cTgk>p|n1aJ*DKxUNg0?Q>PR;e$UhBaAql1SY zdk!xO+>hBaakk4*5-%IC#V0`=kIe-7VeXfxx*|D!D`#3ug&uI-!DQBzut+z5*pR2r!^grr4vth5^vXm;P}P+_EjOlqIIq8-brGLz&30y@4M&WwJbM{myZ$Mz!hKeH@T$=1|!Ep?cLe;}T zcF1vTIqCa7Z3*2A7^(gKVr|h=e;!tRpNhJ zGn8*@P!s5X=wQfggEM`C#!CnWnwhQ#`mUQr?^ z14)ff!cjrJlz{83L|#ci@)Z&n301g*AUT-jUivP;Kg*p21syt8%%D?Hq`c+I_H0}q zw|!Mb7gBb+70`&W`jSI|7XwkefEz3zu2?A{Lc5zeQvODO1DN2;+Mgs-RR-0K=CNr^th*Z3v9E36GA3tlzyTORN*>u3Sr zhm%D|&jZQOhD3A2p!fQ zhevNP-WqLVQ37}3M?NKoY7Y|Q&7?BkcBSVh2`_oUciV$#;n!ZycY_}03LtFGdy}Rm z7pXoGz<-(@+|Qj8%s2YUpLj19HrJO=hQ~qow5k2DVFl+eDfP6eyvPXW6uyviiY~OA zP0QWJ*hkk*v1`Z;e?6Ssfub}AI)jIKjCKg6`htcyKgxTsj!mso7*!sZ(i!Q4wp zJd)TYyqKRX?ASxDLeGwX?@!U23iqMrMqvlWcL$Oq>ih`NlZv}7G=kap9#t)RauW}} zOQ9P2BbdG0p2aiWTfGL4;m3vthabpp9EVx-AIPW$){#d?_E7 zfxm$O{qwE+ixNPHZ^%6EH8qZ{BN;GwlUZwA?|bA}lVJZW;arQ$FYNS4+v9m@uf$Ut z;lC~%{NmhxHb0B49*a-B##iEbBRcoX2W8~>uj2<#vLm>FovltHKTaj1HDXDc2E)DV z)-Vq?g{t;fVxs)J@F%gwzaG+Zbj?rxV-oYn5^SuqBN6YJg9p$v7Z0FkuOLAGeCu9T z0tmer4sBFJ>MiswY+5RtZ*qN%&Y^220nLPi?^oh^aogiJAUP{3U`MsbguitI2&EU} zsY+IYyU7g)Po3L;@TAM1c#vMsz*nF&KTUGP+bIQ@rK`by=K4+Q9q_{6n!t2q`FT=) z7S6Y9K5jQ3Ev?o`CCHd3yuLbFE4+RfzoYooX@MoAlCf|?h?~pRf`r$bw27Sz*VtpJ zzFv!Djau!lfzwhFyFomx9?eR|N+m!IN$A(M$Iy)IBiry-bHg;f`qc5~@cne-cB9z7 zP;Aqt?-A$g0V^qR!dYTs^YhRPalTGN0^8ak$14Tm;UFt9i3j2NS8NI<9tFJR7oZ!7 zyU`=TnTaLIFQPZ9Ey_2bQA#_cl3CKUB58Fh2k!I7Z^%y)SLeJc&Yu$$k7kNTG}I!y zFiBYr!sJdiZJ~SG0=9Yq_k#jgjU3l3&n2%myo_}T)?qnX+mNuIl;HA=Absx$4zwk( zBl0BpN|QLIRhj&cf6GaezH*oQ!T13qJ22H{S ziu#Vr>{YX6qii2fe85ObEYLUqA#~);$O4(3EmRikNwHh7pAysEkpGm38domteM2EeIo<-D*2FUed zQCJ=dn~F$c?or`6Y9DfW`=*-GYL=`famjO4%5euVaJx1V0{9zgV5N!4i<2w`BFM^T zROUK94+xBd(cV3@Vyy`A;*NXI3Ux_ z=9ZQJ1Cn_~4Aj`s^=)%e9d{mKc@ZItjyKRBcNx7>ak>?qlJSH2Vx-lWrZbNi!Fo7u zbQjujmRh)p5;3z(N6gI>hGr*}3#2kVtF?~8eeST75=oEHE)_bYVi%o~3Y#6r7;K50 z&*5Me$SRTV=58c#SCU;*P2M4E$mUz|@MRbB%nl)sGKO;HAr}-v)Ki1}EE2;L9*jX= ztj*Hup$LYOQz;G9?4O0jAXGAVW1+6nS|ZoLKP4IjroLS=yQJcc+$!>{tFUn!nyQgf z-f0|6+PQ79^Y#W$iXI*F21dH?*J3}<+wGpJ*9WeNc#DBsa)KJ9hoR=$$)LYt3!B-8 zIN&6~Y%OPu@S-!cpWZp;z)(Qv_@GN8!`OLC2CwYv|X%Zv9PeD#t!MzSOO%;)`YV!Mh^-e z)LJ_=)s=aSaeT$x8HBY8H?rcru#I%Tvq)`omhyDx;K|(NRR&|-_)d(=c|XFXK4$?Q znizXmyw$W(QoC&AT}1`F>&*Nsj6$Ice*ib6RO0Dv0A>oz3)hpa3<^k2vWR9o$og3- z|CP&aK>)ep*=hlgX?oX7s}bXRbrt>nTRHw>oZT-`F1d0ru^nDfZGxP<-!Zgz z$1o>d+C=52Do@R9@;mBSM_?0Vhm;-bSA#nd@;Wj-+54bC zs_n2}=qMYUc`Un;tEC7p)oEMU9{*G55tIU16X_IQLPN$Cx3OaIa5_)`32dmZfa3dL zr)O(|1v0u5+>Z?cl01)X`Qlg$?Qn<>7Pq{uMLc2`NU#_!Zi9MSb`$do>%PRmGq1%# z67$VkxC&ja zd9S#hd=;STD*l7Z{6~4F5or1_dPQf!7<9;OvqQJ+>}+|P3?3SK3t>I*ui*TQXlb*< z$3gmKU}GP5F5nq95d-P+ne8r$GduWZX{H6CVNO~?ZvI!n3(QF?(ap>7IhP=;IPrA> z+H?_*)85w&v~v2(J*?UB6LNR#Ucf{PYhA4SYwvw~|bQ^K?j;R?*q zqO)}2=#9GJ0=6Dvd$(>yCZLfR3gnm>{RZ@psukXaKy6R%!y%H>!%@!|A%qcL_?B%QAO)T(G}3w+F_s z-Tc1IcL_gvq14?z0e5NcZXWodsG^G;Q1YXSR`Q`h7dooqvAASUYy2@p#(@*)qD+T6{?cA%|*2R!Wbd% z2sEm(lCXfVS-0@7bFhkF*t}GzvYM7sBJ&^Yft}2MxNiCph=5;Ng3KrPA&>+;%MU^~884+4#der-cKJfyD z7NTNYC^*+tiqgDj_f*s4lMmkbgsK9(qDMtkMiJFwK1(@fct6)C zB4jm9r_gx{%|aDEN>>uDqBz_mh+otmeTe%+s6O=Qw@MXCM{;?;DF0+?=Om>|=m3?S ztMpPZcE z=@x3@%~4%SRk}1XvP-6@E{&ot9j6YUo>it>sakX?fns8~TL^Z~6HAATyWA8Lw z8=j_X!qYT_Lb&6~Y8r;xI7}o_iCP|yP#poBHx@?Fx&Wr;j7RL&@6=PjCVegT#FRY( zwLdT4faYQtZMd&WY#$6i8rIdN!uyR-{J8_sf`cUC6U;otg!A;ED`(IF$B5R zYwatwW2{E|%9t_M0rr)#V+7|=q<5F~1f~Gu%WlK>du<|?#HuMM%Vh_3tVnASuwHM(=6E;dJKS-zo z$Lqovs~}#OYaMX^lw7g92PO_B?gzE$`>YQF{~697%{=Cwnyn@DxlyPxBoms0n*Utu zK=%V$?LoH3=4anHgWxqB1FsqLIKpz`4@gdztJ`9V{ zPn>!)ccE$4VM0}wUhE+>+5EyP!(V}~ zq9>brc(l6U{|gP?1^06o-Ai?1Fm{;%E7ZCGFl{vkvH@lBGyriJ02`(yj!#Q`@3ahG zLDLc&6P}g_wElr018C$u_MG^+)@yRJYi-{J28Sy>7Py=F8e%nUnU4KJ@BbM4DMr?9 zy-Az?AzH7uUWYk2XV1x)YZb5yXb-V{wzI4UXXbO!!>y&^#>E2|?xjZmODKn1cv)6K z#w~9;i_1EQ$gymo*8?5uEBv4=p$IQU5L_$Y|AE&Kf6qj7|NP<u;jyBon00#1Hu~cr=*@psFigh{c|q3fBqFpnKMg z1n8-!NF_%13ayZL-o5J17`OFKJ#B!nzeje!ZxU&L3sp02a?LU&u-d>V?3qc}6rFo8 z!%~*%9W6W4ke{LjH!yYV2kerto-}zJUs{(7Rp0S@YH2B8J5Hd~NC_b_={LL+gSH53 z3lS7A#0ic(9>B?W76P=rU56()Pa4|P9M|B*e&G(mIe^F7v;a|TKzIR|3eE%s{3KH? z-Dtl6_g9=dkn?Iw3ivtzzF4?V2!4T((LV`*&`Yj?-%eGXLJKjy7h;zU*=S#Nmu9YY zQhxT=wr}C80_!dBE;tVyV;v)Q&b9uS;!k4%A99|v{Rn4J+$Xpm^$DmLWe#gD#}svDRRY^SVXD(Nj5vNAHZvUuq2{)og)6p@XHjcbvc z&VI${1BF-PM`3br^D2ICs^QIe{uRGQ{Py8@Tn#tlc@#h5&odB0iR>`$klJIf>&6}G zlk68j6ORxkH@S}6eTX?@wg+)BvYdU2ukd-cMU|j}t+&usXY(P`8aG96n+1DcQVf@W z1bD>(4wLYrCy2uco&Rfiz*<6r(K>pTImf1m1>5 zD;a4)`9yo~6fG5argS41W164h_Y8iKVXl9~^;!c4=+J_MhHiE7I=I-Xp9fuAX*Z`X1ZgGLHpv!s|&* zZ~L+KQ2IgZr_t5rp)omw=UR_aJ>MzySP!ZF%89X=S-boM%FV8W<>6=C9snnjRCY>! z2cQL{Xc}M}$&TN)%9IMnb$%ijqJ80EAzZ9IC z@m;8zcvConn4skSMQ}ck(DTpgGd(*I(73wowHNH&+7-*`-fvyzIma>L<&kj5c8xt4 zZyme?pbk_WBdmQ3f$WdCNX|M1C|~=vJMRu{cGL4&cYt4iu2ySHviIC!6JUNo^K>KR z7u|CM0^-gcKpXWi@j)V9tu0|EU&Nlw1CC?1spyqnyby1l91GN*ny0BDYy!MK9d{_} z0=MHS-IwfsRtt_sUYC2;y>tz2-#8rHxK|lq$-;GJ(jJ1qYas8*T{i2)d>>***bjbV+rf`0BGjSVujDSA5g zW(ET`h9fBZc_!!_)Sv%3)&~lwPrn-YdaqxyDL4V zp%mfoKpR9Z4k2>UfOKcDz35THHRqdfV%BEwU-5|c1HSjLK>BX$$VxbbjDeea>=d`! z$K7GO88i^;j}>0$`7DMud}2*VOj&l@P2jm;B(+E#6Zi?(Mx0kg{Jv;0St^)){peguH+FstWgOtGW()*u=YlhX!#>S`AK5gnq+?6A7`A~KJxoTN z)3cdk1qVq}L5_^Sh`eCJ5tSni8ci8?*>12TBLmFcl&b?_1%R*2yO}-*_cPz745i?k zEI*=x@1hcW6PF|LW=bJ`sDq)TG(L+`2W_dAtbS6LQPIB5lqNXrG+MY=OE%$$8{(*C zT7N7048!apANyX46BwkU@FKak$vNT4DTG%1PqAm zMCXS4FC>@nSPnsKgwyDo1_32Tc zmFn{~^|?`f+W8YrFUZDq#a_1~kD3NLCoR)c3KyrVOeu2^hP(tl4y zZ2b^DNkRyG2;!)3y9BH!tp6(_##v2ig5!?}v2TQ}cXR&lER#vBi$OI>=s2#EsHoGV zQYpuvo_i7abT#`L$e#}SCXml5rPwcS=8Lj-$$>x43vQ$Ko`slA*qYs^X^zJM&S)AH zq4391j&a#+z63(;^%Fgy_;)}uN)T8sY~9DJ#|t%~;tS&Wd%qY^(M|#P27naU+)<^L z{db7pp3Kh%q2dE~g!i|d00tT!j7;e33S)1FHts~(*ivxU-#VyWsvLdLiTmq;0 z{d6)3m#6ag(?EK|drN?OZ`tEt)HS|l%f>Z zFr^!bupl^Qwc7JqD`#07aTrSq!JSir^v-`RFjxgnxmu(YL<{~(j|yxJ5H5AU3^{&4$!Mi1%T|Scot#yW`QhT%4V770FsjN zJ{O4KFVuutrUiIiSQ4})2WJ|^dVPp>Wm$^bp5$q5fHaIWvRjaL32gJ1P~W!WBc53_ zeR$^LLF<)TKh^$rF8M5=tK#W>R>oKu1)%DhW+l^HHNjXUpn=MvLga5hi9+j!8q}WA zhH=JGg(E4;Mch>;?>OGey60Fp%)eLgZ}|aau^FXugEFiPm$V0=u9fE_7D1j#2X}9_ zB>xsvxv2<)4`1^)S!(*3pqJvXl~W3AbD$Gvk{%oyyA&BU*5x1sW`%M$_mg`z<)#!W zj#lwIWyTOLoetNy=$H{|cKDA!C_eKXrka!Wws=sbOIfEoFGHK%fJQGU8}4TS_RMpc zASy$Vf@nh=P}kf*aW6mscjO>TE@o{h!g-j90*wAt#PO>j@SKIsfKPs&Y0hj+ykt%Z z7SY_JT4O#C+5*9GJ&IO$E$m^d04hat5>P6(zfA?`*A@0I8qM|nj0Ze6Yc^B9(0KIS zuds<(>4FoCbrJ>xLm@by0i;LoTZ9FZ-csdZBRF_4eI8# z!K7?n8@TgeN~dgI8`RBfgSvTbFtw@WD4W*?lSh45Hm?n)*VK1q^V(pt^LJWaStfP= z(&b~6llk6deY$sQIDQwl=9vadIVScA%zau$ku-Qb34?7*gsporJrNt%3hOvv9P3kX zDY10|ocJ)1?45C%RCE(Ae)$gd5bG?(3Lnh3vv2&bp(&EiG!B&u4q_AXdW5YbNN^ib zm-R)|rS3)FupHB}dpYWWL=N^5XxcY{hlH&@WUe^L_neahxV+iD0V`D-+7c*ZnJ>KG zaMlt^F!kwgriW{nC_B!I_>J$;YKPPL9$_6JQ?(V!{xb0U_Lo_vg&=7gX_vv7WtZLH zamDfr>=(KoP`4SH5@mnE_$Mv9SJ`7ys77Jk!>A2{aoK>wPi#av{A4p(OE};4fV#ol zuWT?WfLi2CRdWu-aR@m>r=iK(%Swq+N>TR~KHs#ze1u}OtDuB=fqjjAMR1;{J!J;= zl+umZRrJcPLfo)TQ?W*{L6P@WBi2L??Ix)J?<^ESm^MRwrt;l|mfNfSEH}UtywlKf z12+V@awr$kgt$SQud$R6!U3Q80#+GFE#RZkeKr@e50BViIK|@sJ<>;S66r_zCXv!- zlSoxJ2@{QfsOgxP9E5Y#Js=Nx=)FMQB&LQoi6S*Yn%VUa0)^sy0Sl1oGu_-8L4@hLHZ;6`Z-j(-EW|aiwDc?Z-WDjQ>v5R$DLc+{w&q*sA&?pLV^?0g^x%9W&+klusCcE_@ta2}$vaBBrT^vkcE z=Vu(L91iR~jyhEJzE;U=m}VDd*^RfO0XQbXi7=dO@(_ z_USo(b@C%{LKlFp8T2z+Sq2ep8tEea7;HOWr z)kK|2*O`x+DL9L%ljM&K#lw9Fj=qt_T;@)ZsIpne7{T8 z=Z2;|Nh-V$6&!$(tzoG@!g86ko>Tpr3Mx|l?~8e-S$ z@j~dHH02>z;@tvymsTV{y*}JE;s+{STaT1dtlBxk$q@~BrQZhR-kZn!`-bv0)S0LT zUEUCCB6TFPL7%J(H|WP`5U4jhQix+AIjutgt^pxehrIC%WCRV zxv8uj3?`gX@D~&^qUgs}t+;Rlk)(E&WRghTX#x6|hK3Y`rcRxYG_zpA?rW=d$8tF;B^ngy9tS4#w5?t%C#H7Q-W7!wan9k2D1~Q(hLk_-z z7oc9bpL)>~A|eP%Myo^xEht0p#L^s%>TLswcX=;5`Y8=LqtHz6ET0tlIE2h(aEzS2LTw`|T$M2-Ag0hcritvE1pJHF~J~N#%Z4egTM1A@^5ImbUY@R8zUQx*~~7 z0n=sHRaBy0r7DZh(s&w4rm2$*CgB>mKTfHrr=IY2Qe#I*xfNB0qF-oNDFH33huVmu zp<}F|p|T6+j*{R~qx|$r`D=#i^+Qr05*I;BonGlz_jSPKMzxjI~7Z^Zp=^0{)}sDMC@!3%UH!HgiJ zgy0}Vxb{`9u2TJ#hX}s@Ja29siAw)~A3@#q`1Q`t-o0R0an%Y5iV8FI!KKM?8X$vX z4jPKP8Bpj4x{+uqJOl2LmKuOwTXAk3%UNM=Nkm2(OS? z#tZo1P8tY-BXn{CDPnC*b|;s%3g%kS!uGgaLgN8=N2075n#fRQE%-;$fY%cUJA ze1#JAxJcHn;gb%6Zjprj7efd$6LuB^|GKw<=s?eMgN}#ype3+M#K9J)*aA%8)bvK} zhq&)+wmc05@it+;jBjaef;w3xz<%7ff8URR`ZrN5P<&HR5D6v{{RJJ zGdQcf{sZ-d^tWco)Qvf)kJyxH|ER#8o<)ChD}WwN=*wu-xCB!_Roj8L4E%~1sz7J) zOM!(ncr5@QgUS1aF>o(CQgfmh27P1DbIbJE2#ui7oh9H0rpaSJqJc=Fk^KhM`kV1B zEMM-funwV1*BnDI#bYr|d3Zvh%drVhFxN5!$8Qi4s;(8PYT-_Ka6YROFWkDk)L((J zpbs@)i#Zp;!G=HOaWJBf@t)+)NRE&*YALViN%Nn+q`WCOd-sQ_fVcs>AVlQx8TqNIAGCR zbns%3I{7Uc26rdIS|~&yux8o4#KlTsAOFMd4H&(j851ugzE6tj*SH$!F%&jFcnsls zT9K!fJe+JV5D!UMi@Pj>6pqm>yA7kQn~5>5r7=#Og-|^zvdl%?;?eSMw1SVejRl@` zi^iU_!STkP7){v%QfH+Lm|pt{xu#s|!JnQuJAXM7w~;?f8-*e-AU4Y_zC*Vr!*ffz zTP$k57#v^p@c>O(3fwxhwT*B4Q;f!Hq;Svpn}B!)=k=gvSPLLXkBc&}bU5`bl>cLZ zS55-v5UT81g=P(e=85F{#VEEx;UW|@fT9dhQ3DkZF_EbZ6s3zwH3%)(gXtH@^?_2H zcs9lBk3l_y8}bbCzd&0Iy16#@#2#8-!qz3Gc-rdN1$Ih!V~^46HwGT7jQPjhsth1H zohdNmA9E{XGIv#3>18r~!OK(ha<}?YMlb2=%gywXqQ2ZpFHkV10uXe4P4JO<3=YaY zSX9Zv>qm^X$B;F5SGc;dN?o%62DR*{YnAC5!TASJuKw3BsjMaQ z02M(i5yKiHWU}yyu#!Tb>rC|O`bcGk_a=VA$5kz@J5uwgVG!qK(0kw z`;u9!#?k~2t+L#p(IjAP6#_oL@0Wx$BO}t?5fT2rd_UydxPHnkKwN0g?CZa}xR3wp zSsmCK+p3r5_e_aO`*53Az7i%3x5_6~z5aCTP8g>$ttpOJ*7Cy--GMLTd4pp4@tV1fg zFv@o8U?^h@k-u8lj~DA*b-mpU$=^>$`)4f*>5JUOYqNz-srBO~8LUS4QX?zVOBpJ^ zN$*3I8hapBD$+?Sac{8!UaW{|W*q@u8(3Q`6AGRT8hIgdNUT>pXmU46$Aqm#u#U-v z*C=Bm>_=KbT7gyM&WQykyDfW-ZL_VHMhZ_f$<9i;98cu)3xaS z0Q9Q)tr`3IL@?N}>;8Z2y$^g;)tUc4lT5+@0~0W4)F^R{mMFDB(SigGkQss!3_^mo zAlM=s5iNxo#A4Q4@%nh^XP820@Mg+8ahI;vWJk z^L;<(-aCInXt%rFef_?_-)~;8nYriQbI(2ZoadbLoafKy>DAuBmM8TVSv5f$a$xdb zIr^<`CZt0B`z|`#HR8jqs@-!7F+yJSb?=txSDhO1q1Ik_v}A4Y!Vw>a9=PhO4};jk zFSsfmerM=!oXzj#gx|lu`Tc%<4`7hK=$nk}(5MfCSMl%NQt4E8ZVVRIy$~qi6t{?R zGp;e-u&fj;`>w9%IlxALsXn|gGI^{6PVV`P(Yw691E~v8sblmm?Lc4}wl1Kc!5H56 zQAy{b10~Y@y8%H8fyz;=4TgvSM*IZ+ER1wH z=+bAoV=v1)gm-?0QCMW+R}-7xktJV~txvWpv2yti(fIUzDeS_}>*M9`G{s3BSk7DH z$#%pzaF9)FwhkBy&98k{)SdN9bMGTBr{ z))APs94Zz2k<_u;_~>o3j#O~7)$6vt6}<0Z@XT)$(#mLj%#`tZgy~bnAacW51Ie!QY4OFK`4qL-$|g2)>R< z9w{E58_46XyhfRW!ec}$?cr=rFfUe&5at}^^OhDKnSQm#n(`HsN3 z7*2i&QG|D$YJTU!x)-+QItPQVuq&2N#rsonuZ)Az*Z={}sDz`C3=VyoXGBx~Y3}~h zaxoRmh1qXv?i|t=@pL74LNg2Vrm1aC&Q#)bmN6R>GC1%ptvURKD>x3@qR`TzK*?$^ zT0-iBW1r?3!LQjIf@YS@*vr&FW_-2Ci{xgQ{NyQd zQsvF@Aud%Z6CoGk!x;8>vxBY)jIiFpekHAQzMEKzGmO_TQ|2|6=xi^zfmUno9(u8i z5*B~RKwjLGU5MDggCvPCnot@LJwn=}1l$Kxny6otzktd}AlTxlKhI3%lcI`#OsEG< zDF#wWt1bZ%3+SM5O9^2Rthe_IY!BE#*3)DkWnNb!#I|27Op_jv1xunPUt`#EpwX=3Li=O8o`06Xm_5fthW3=agM8E<#h#zW{QidTf5J#kMpn; zOzhU~!zsSA)#*}BQknUyCs!bvco%Pa)+To5ohAK)L+}9wP2*L=_Y2tDW9nq!Y>~LN z^U}`PT}+I7%&eF(XHr=37792m_Ll^=>a>Deb*2aNICEsH9#Ohd7E{wj0`G$NR>N+c z`%H>_$&w$imf7*FIQVn%ta?0&MGh1Ep^hcAsmPD!Sznu8|KfPYhoM|Ep7DV6c*d9j z5+dHBjo4r_zVX+HZ!9)N65m+Ho(gD;iKzrRwuaqbE5`AKcgalbVQ`o+j_&~|AU(!$ z@ASD)DMOid5qc@woSmf2NJ6Fq3MeewE=GD-VqnkekSS>(?1%Yc%6A;w@eMaYcYolt z&>ciKFg6s0oU!+bc)X;4;4GoG4yY|V_C2WWAQ?UsWuG~oXdj`t!$NWGJ;TTBwmKLEVW)Wb7#`{g)OHXXd*#hW}(DO2 zln^>2i>fao@MmK2GW4rnBhJVU0I5_jd%QCJF&hS3%cg4OI7y@=x;0CcK`vJ{6FFeW-{C;evndd6W zgJDA8f_dVJ#kR5uXRKhp_h}HX#MJ}OtiiEwSjjB8_BSNRJk!ejjbNel_tT{ra*iC| z2%e>nM^TfLVWYhM6PuJqQD%?8XMYn_MEI#hoEqWfMSE-sL|e>DZ8EQpRf_T9v2#** zxRoNzGB2Ek#8gU*Y<^VK<2z%c!BeSfDQYizZFF>TzQqm`K%V$lh|XYa^r}q)&jQ$N zu#FXJpH{V9t7_M(T2Qm9uss+^pdBanxkoKdK?Jz_wc}c=Irn3WdQ>|FI6!bQ_yhG4 z|H{0ewB#R2Rb1!SA5zB4-tuNuOYe$ma&;+}xvF+{P7jIlOP|$AheRj1m zPupD^{0?2h2LIbyJLUq9ZA5h}>rB^1enuQJAEvdA*c+~Gf#G?Zg{$jheu7ww4xA?* zn|GTodZCM%!QDaylDazFeMPW^ zdSuTGJ|jiARUNGQgqyyMU81$i%>e|KoE5*)*kCh%MAch)n(o!lk35hAS_bXAd-NBJ zzcXSlv8G{%2z8{*+HXfY8WQoGug?N|Xf5c((&<{odVzI%l0!#i*4jwLqf9!N%r^Il zwU2_Cvc_9?J1SlVKqF9QW-WWe{i9#Q12%Y$cF-!# zIJ6qhopKhtD2T9M%fO%it+>eooQS$8OKHZQ4HH4$rzVdm6i zv<=FLeUmx$KY(&+7}p2fi{H%C#$_=%HcWMC-?z5841I8N7WN2$l30xa6^7zW8LVn9 zWJ{P+;CDO9YF_V($5{fpn)tC^qNk0+y^K-WMvS(btl2bmh;KH}1KJiGXg5H=sxRXx z5~?1vPjt~>(P{?IDCM;B|8OKkbd!Zh=*7uXmMclkX~7?<(4{{!y%| z$&N*WPbODAlOq$S2fk0T^67);7gQV1bX$v`ucO2jER&cer!Mk_hY2|nPn5_rZuV z`&6&fG+jra8N#d-@Y*%qKZn=*kiJft-2aQ~agR2#*W+vGgWvP0*Q{@HJr2Luz5Ijq zoEH4xRr3D}VGN@y*Ye9-fgk)BM{9*lrsPB(SwjMtPm@mOt|R5pR?0~`n9-DS{aWp$ zaq1W?<1c3yA3mDV-#8%(4YTZ8_t_Dn5g5s&?s8XhQroug$j1AF=xKTCs z9xj@VEq;^0$f^}U|L2gTR@U(w^-t!PzjFDbjQP%pzZ~NvFyrGxa@6t_z=G!T+sc8a zJ%7q%?|7tAZtXTA`7#4)K-uPt$Ch`+BdpBY>s;gMh#46Dky0vJvY3!ec~W@nZgb(` zdTg@Reo1^N1GH`!w{3M4NEKABqGsnV;B(hIgV)8zQR7}rrIzIO9KLR1S1)x(%H12C zV4iAIO2Gf!CWW#J0;!w#`5t!1bL0gin;+(r}`%rj{_YQu-0U+MW&RC zJi?8%ndt|o+wu~Zw|)kzN_g31s(h!cyu2r2cGzYK?&`8qK{_6#O9N`%OmNM?iDUfv z!SNdKvZt5)YxsPVgdJ=EHUH$OzfxLVTQ)titeo9v>>Ax)&|7PIdbDP>MOZ?>6gk8A z-p6)zi)jdjwI@isuKM?8GC2q`kbh)osrzDY{Uy{bw5&KYxGh$YHXcLEMsQg)vyxYG ztPv`vcddtUg)ve973W%1O#DJOH8%-am>-jnm34@}pzCVRBp)H80z~L&(JS5+Uz4X) zPZl0YCR7ZB>RzFA{kGwHb&2!E(6T&$VTWty1+E3Tm);xQVcH}5+D9J@7LDG=a~EAw zzn2x==D+|{$W1;=^cUPFy+a51hv98h=ar>ws!U7!*iP%!!{!$l-PjYTP85!jlTLjS zuRN^lda^O!`HOM$miaC=0rlLC;5&GqzHO4eY;wa5ne()7IU6#xAHZ^xDO$>4V*C&Z zP7RT+OEJpe*_Tf#E>H=9vP5S5$QjPqsn;~zU z>kXMdUxG0NNlzWG`!)JQ_A|MW%`<6ZT}^DbW2)WK#p*z9Y}C-C3vp0SCm`Tn(@&sw z1G_B&o-6Hldil9_`5c(#3$cQkO{~52;!b;@S^lb1%p`wg?CiF`GErL}YyFn^f_Qbf z(L(rg^a{~ZtEROiMcTW1$us#_ILyHB6w#73{SBnIT!kaRx*X(p#|E3T7{-WZiu>d z)AUSbFO=NVxqG
    ?=K2^o>P!k=;+yVu?p4ffWDIH7E%9bJ{wMi~N4pNIT8!>poT zsyNN1-za?Sjrr7ULLo=faw8K6_L!*FjULe~sM>H@YV6(c3MOL1wS6j&Tpk{IxNr*% zSR?Q(l~avsmU!Aa2Fj&bA(RljFt|z*(&LNz4+A%==E$dchN~u#CnFU`XgybzTB@$+ za#y8#bh&zTG4CnQQCUXWPc#0->{->MH1a6z9&z$ku! z@u@mbd2(pSY?l6PIwJO<@-o5ItZjy|{TF6ip<4?ABfOsweXSV8lb;%3JKjFc5R08~ z3mBS>v@-%q1)<~AjyDs5izD#2vdMP@?vOpR zBk*1Rh&g>bf5e=g%^xwRXYohO>2HU3c86PY!e8{cciX)1^^U+z^TLf+Qm7*^hoh0| zh+7)3k)vnjp}}jjBtanfi?RPe57z2J580X>8_Z5VHXz*EFNWccCzcs*9jF~IdQCgE z=a0+d{kUcE&}!r14T@!eo_1!*b13F;mOO@oeX#EGgc>*MGviA7)TX!PABglOjpUky zOr`M~6_c z%DdXvQE5!us46DA|1G{%T>3-=>J@7n2nw;KNm;h-CLC-@{rcMWfz#Z`rHr0Chq~Wcrne9NNiQ6uXDm0V>3hASn z1_b#cg9}0QR-xLqEWr>c>i}&N&86rMjfwvj$51FZi_X%HO*7X2T6L8MYG3Ut9Z=n> zwd&!l(FOA0tk3Q&q=uOQ1lsIOi_7*YKrTesFGmI5)n*?F& z}S&Gd%l3O+H^FS2@+^h&+_BJ0)aOE`d{+8XLhM;_M3~b@`Qi1gww+8IhU z`Vv~0p>vF#y=wH#SuH4mz<_Rcb?xC%)aF*SJYZI0ayBf;AXm1w zTJV)=2jk7(Bx*v*!4-0odcHznrehfa+~UKhX?v^ht;G7JT5g*0vtL=AwOO8|*UNMC zS)le?Fs&t18rzNdhj6t8L6Nosz3w{t#$P5fZ zM0B4c;`sitOp>A6&aB1PA~1xIK*=^T?Yb#KVu(UJw+QW2bf(gcOrBkTgh8)+L9f{8 zty}SBJihHV5NhT-4ab|jPQ^yZvFk2Q1L+48OWBJSrhhVJ*HmUt1x68U54!LbJ(~&x z-IYxPq~iJ$*Pv(TAQKkS5IXd&(SktaKROS4XNH>Z*Rh+EI~0-sZ$~o)v(Omv?2T*= zN=JGoIrofj$fu`rviS;D{Gc+oli$g1!PHXBSDAZ|zT_M&;LbxSV3Ix*Af0M-{JTsM zk4qnleS4h5)o4oacpohw$VHP2wdG=xYwM$UvZTrot?PDa{ z`RUmXtf2>z8st4N66Ua<0`ELiX38<2dNsEgNkygT}WIxyr7H zt4W5=NiFUKEJ~|tgMV%cNAt(jwjp37pF#qr9cFXfz?6@${l!StkJdXiMBF#!X*Zlh zqfQj8vGFp@CeqS5Hwm2F4bt)%fp0>B+lrrphq#WB&cbmivjr@M!)$hoyc%ZhH~#sM zwEV37ybn|cxSL+PMqSnjB7JB=iK|gw#yg=y_dLE{h!>B?4(G>6J*%G7ee11af!C9W zgz6DmcWk(IxNSCMthRx%bL~?z<&;##w&x{Mf_2TTT&2;EI#C&_r1QEJ`;|k}Atq@c z<=F7;lj)jR02ONq_>jALJGF8iV@3BhvF2f7#sIVy)<{`~TLj;!Co-6N#i$IaSC>Xf z;o9Ab@9d$`)_oh5-1{`4Hy3E^o)eqNFL&+fmgy&j-RYkZJ3IY^z3$5L^N{Q{^@^6? z2~U#gsSoffv@Dvdovzpw419pxt`ZKyj^#1aE#;-1pge^~HJUzT`dBf6DrmR5yTQd z!BKpWyjNslXu?;H$Mav!_fEb~^ZiFS*^PYVSmXa!eE0I*#rFtb2PUv`e30)Kd<)6< z9^WhoLXMB|b#Qzh--YmH0M{y7ToP{zNFYuh^trV2dR4K`pbMr6RlHXzgR)`>pWyJh?Z+G{1aJ-$n7 z6#IDK>(u$1-1<482QPH!vt4+!UCk{RGQNN%nwuhl@zr?)aA1nTMpgXIMrZ593!G*N z(&r`My9&&hcEb2R%M!?kZ@_||G+NjmG4inpI>;ZQ9}MtE>Q(1ljw7EebV{^S!E}rX zmMQBgcHJ^{Y%e02c6?T^R`EbL_nRsdQCzuO)7TIdA8d~!lu zrGzgk621t*ph7hjSe35JxSv&y8|sI0$Q=mrq|BJpyF#gNATR3D>gW?YmlqZxhXDO$ z2ZvJ`L3`y=KU?v|T~bBwiWj9xW{XWEm$PGUaUkgZo=jI{!y=~BPJLpItP9ngbkRI! zKhZ@E!k!2QE2j#%>vEFg)H(23^I|_`o#j@w-Sd|1iIn9a-#qPHhOqUscneiUo&Zt1 z^@$>SnkYrzBv`LPDVf&81n||VP0iJA&Fd=WKI{cQVR9~zwR${kk9H8m*BnWWv{MdA zT}o=+suD;k!1w1uQhXb%*-wGjk352EtOt)D^UW7^+wEmE#F~r}DjnZ$kdP z{DU@5g(tttboap{J`jol9bg&Kt25P&Ozr)=H-g*&WqC}x{ zr&RJ&UCxjHme6%V_BSv+=1eK&=;mQ^Z4^i2<>v-EJ!gtco;M3|R0|E^*xu_hizGdQ$?sk&XICYdX5wfF`-J9Z;))tgeUhYAMWEq2}2 z9?Yn;sh;RKS>YT}v5RtaofK9xySGAmD0aHq$!V@9!tX>jmq0SQBBY2vuhRGf5FW~0 z!o)jFy^fyB*6(m&>UX5xXi(xy-02KXFZ!b%;#8AAOE2HlPZikusXpqBwCwmbJqz#g zQi_#I*w<~MMbhMtoiY|)B#xbEjb1g!!SWYqHdyRraJ`6cRn#YR&(?i|{IRWMd_{e0 zA$|Uylygzvdh>6U5G+y9C8brYK}?NLQo|g{@;`wcMepEC{VQHkQ0b?pn5mjmiWomEf^KV{vDr5?#5?nJy;5R?H`&NSTdBc2=A!u$F`gE#En$OoF|5JB=dwY+)yPJ5V7+auA<*N%$n+m4Nzw@qNT3=PcYbR z@qW0)46p?|+R1t2lZO@WWbNPoGCZRmjc3ufBZm>VnOFIR^%XL-`?FlH%1!H1$B8m4 zpsQ^(=xagSg)WP}eL`BqTlaf47v@A3#UmvL|;vMeBlaMC{M&+tjSe>dRUucHx*c*~^_cx$L%Z}Z{^!7)|AnMvK@au-Nl5TGmiVbs6UsNH7%&DpzcwBfjsTDoyyK&o7`vo_MtRzz!uq;{*lIzS|&gC zcpjNHm~$A+Xk~gckQk3CAF@_&{gg~-OxVdBhGbOjx+3E;iLv(u!6Q1qENUxiUE~e_ zzK`}r(I?&&bIGmkkjXpfVS^v>Tt`zEeOz=fQg&U6`P(X-rO%oa8r2U)Snw0)L553L zEpG;_K~||kF!P4f;zsA zVG_a079OhN;H|z`vU>tAP-~pT9ulUDN2H7|8NH&(2tAM;?}w^RYYTobPK@#Hl&p(A zT5TIlDBb!^Tu$^p1YT_Ot`xInkeQrEr^>{jy=Z*MWCoS+y3RMXg&u1NbqP%nfnnMp z>qMktHzx5O<29teCd<5A3^YYZf_jUJ>9qt1_J}s6PAp*IBeh)(aFVUw4LjV;MqcwD z-CEUw2H&B1TJT^zvrDkpsHDJa01hH`Qt#odZ(pRZ@ILbZ5h>cty~!tDVjAQo`uUtw|n zmdQ>``>Wby>T5EmJ{lAzwB^kHeJ$c`)zaH))RfMWsAu6l-=-0-i%{yRlcS08vUQZ0 zFwSpCRDbIn;1;%BOciQ2;iqnVgIx;DRqRUZ^#F)DS!3M9mCg0$6;O{%?&Ba=y5-6t zMT%$&lKWl!2cXQjP-wb!PZ{zR_QU3Ndwxv#7s^Yr4T}fGJ1B*Nj|Y2uQwUdiN#v-f zBqp3nk@mqfx-CmZ69J159-cxqcXhGdaUiKUp+b0^*L)-!gxk=rCW(IvipJzZ6Wca6 z-$KwWY9>bNu1>CLy95iD{4azE<>4y;Zq-zdf;j{=dCK?v}n>$gSQzr`T|k; zI<-NmlU$w>Z!qm1Q7|Dn5a!Xi8$zw&WRP*D`97M$6)N-kp{ zYSm<}#jjiUdh1v}9Lf7Z?A$1=(u&q&Mi2JxtoV@aijOnHYbX|PKBBq=e@6@*?u|O+ zfo$qHrXahL-z~2uV;u$PQ`}+{8CeGV=aL~A#nGORw{9|xB>Ppd!#W!8>#ZNdfiXeK zNxn%LLcGMApv0*p4=_W?3>T9j_G43H?BQNx1$-9#Yxuq#aG8DMpS(`*1HELH$IjPIwEEr^Qi9XM3+%8Ff_vaDIF9*Ft5uYp zXfC2+?ipZG&lI;Yk(cWsR5w()x^oYbiSfZ_D|OO7pi?1+yy2I zHOri%m&yZkSUhS2Wqp{K$#0)?!C#LK@#zdybE)LtbsqrBTp(+JO%(l=(Bpv|@HZ4QLIE*JC zeQZCmc^T=%c{7$j;!r}?AgUUZMFFf^a^p(miQvy+ndW&#T zme0EC(F$2oI;;((iBnx5d#}ZKWS$u%p@(mIsCK_pGw}6zaJPxEtoz#j!^yLXWlT(> zQ`DF!<-qs`Ez)%jM=2=GQTv5|%K)Gx7R*hpevdQuYqcLv@IPh+@qg#w&+QL;)KO7E zay=7%N~#^qPpL+ZaV5{^;eD6}!OL+b-p8aNTV9Z;Pjarj72C4JOlQ-}u(a_uN5-2t zhIk^(-1^nhXIhZWxH+3w>~Hn$wm0^0^4r$3KvWvVSE$ti7awN#!J*pSuHeL0XNHJ| zF$0Qyk24AaW1h&f>D-P~7Ws5J?d7U-Wtxv+`;_C`&8FW8;70EPyq;?`en$526cof^ zYJeu=2JUI zxG9gf5KQ_IASDYEV=JM9|Idv|v(W9aUm8!Kfieb;R!v&}Z!CrpUt{llTi1QGj&E(G z3Y%?A5F6L3?%FPm7WK7B<+NGtT5vxNDO4=@7h_hrZIp)HsBYObXAHpXz81OD z*cW6O>Tj>7f9?6;jIGyWg}0v{s%_5r$KOlN^6^`X&L{&hL_9!@1-xcZ z+tJ0|M|V070pc`h#3AplretCYcD@18^Vakfgi!dJ>ipmy%SDq zCy(!KJ~EVN=VB?Nd6X3>qucbJP2!E>!c-_KtT*zciBqgV2b%aX!9n2`_Uk)=$J4Ge z55XwOGVad#GOjE^r+?3^muatBppSizW<r>|c1BNB*7`Czzxl8-ss% z4XBQ}Ty<&3SpM^jpUqU$0XC@2l*J=HtUq*`c|iS#!7OZn3mlP&&L`v*i{2lMA9pTpY@TtD{o>jLu=+&aW zNzUWin|glX;ytk}bufair3`O9Y(~6GHs{iBHh*4_#?P#h`TAAu70Vng#Q^8b(u5{n z)08H#>0|;kwME{#^_&-EdbgGd8lpAJ9#4igZ;1a82>y$$TJTiV9I=_uK)M=e@e6lmEAwZY5lX7{^>>erGgT) zI4YHBWvOb^V1L`Sdui}45e+DlH>*)CYkRBwq>&VYm;uYw&zjbZc`K5E^TZ#^|}S=O`W>m5QUCOfG7ese_$z@OvdspjR9fZ&MgAOQ4D)lK z^}=GGNE`)dLRWDl2-g8ZiSD=ZYO_7s3^zvGvNh>LsK`4$Ra1o7H<4<0WXiCpzu5H~ z8qBW(2}S*-(l)I&dgU#W(yE7PQ$_(5vaML<9IhBnzls23eCIA{KIqd81dx7DdBpFo zG@ijRGO-tzS0?_GA@r?%Y>eY?_qz4WS>pcW+$ndK6=t^bb#9f@Ble2-z2K783?l$e z&1Ci;mB#DLXW^Dyi$Iav6hLTJYf}EYN0m6!TlZ6nPl_Zn7J<|3LXsH#K_bUb(sCfQ zMB-oyEx~ZjdH_CcorM2Fh?yd^MD5&zDU1v#Lz2R9hEzaS`y!=G!WdxMsMmXa0W{prQvo*s(5&rTV^JPh>?cMX?b|GugWqL?UP9!^@U%Os1lJTf;ZfNPyY*J^ps{kv`5>!>?ia{uX!MxkKgH zsU7V4nOzW%(O%LH7JYo~SEJjPoX?7)we>Z;b?=iQ+#HXrkXynB;smngQv2oP-=-em zDwHR@BTv}FYq-M)9E)lZKfK<5CJSr~o4V1>a3hPZ1YB2a$a#IvjcD%MB`*lX4Gn%L zia04>Y{Peqp)I@*4OIOAsQn8PeN!%a%euW~N4;fj;J%%-B{5rbe#&O~Tq;?!YBRn4 z=$)`4vhG(RtH%^P$6@~Gs%Z&}adOJ}JT);9EkV-8K>Bp+DDK~OG0B>lQqr|dQweh4 z-4nn48Wwq|$_z&z=<`G+qy~O_@z23;H;RtWl)*tk{tGLP=hD>?x* zRomi=QO&C=N?}Y>0=@S;Qzxu1V{#mc1-i_PK=m%m)0P3}BsRhd$WgnkU>Z1B!)Of+ z1x;{p5-^z^V7)BHqfdD2F{+JgU)W0;p$^~Sy+9Vn84t0=b8D8EZ4NtgbJSlH=5m03t%DVOwTkr83>o(Be$pga)FBR6n< zd`Z5|HuC~n)WZ1drYRKb?yX0Nk70fuf*r;Cog?uOvAtjBF_Cs zdN^ALkA6WZ3E1$-vCC?1>LrfrhD_Qe`h?t)$XifT(@V~$B#i-K3!EoR}a>oeN3w7pTjRt5E@g@ zUIzM3jW%V0R=tqvBvL)cc!2ZzvI1ribQ3G$ONPgXbko1mZNcw^mi4FeupwhsWUNU? zyV&Oa<;0y%&5c7WY7UGz?icOmgQ9)}x-WI_jKPeWhOJ4~=J3vZ1B&LCtS*#+CtdiR z*@`Bu&NDmG>cYQf$0c_&^H!}JB_u86HlY`ClR4)J$zgT!BhsW(&3SWbYA2f!!0#5F zhjs#%z&H(DqD zHu;2}A&48SEM;fTa)*`WqBCb1Z)Qo|9Gd#sVUf*cNcnZOaEF%_i$nO5k?GU|n<5bB z%D*NpKhS}e;z$mZVoy!+0@v45J?Dx3}`oOpmHcA}>y>Phy0r%Sm~`g0%f; zr|gqN|Do-(w@J2DwVoXUyV{GiCp>!Yl0LX@7CHr0?0dLVEz65kyo*vDnfe;d_ZU!P zzO`EIM)_~hW^FQlhRXnQ0ko6vGOpjI`(yWp$5uj*XbU@@Ex4Sgisk*`;6*jtP9tC8 zzQ`KOdUow4@JSOqdVg@sv{|-^VWf6VWL8s6F<~QT+&r~%nna(=!&uCmQ2^@^3(8r$ z<>A2^D(q_z2sz#+@GX_nr(tb*n2JPwYh<>HngOKQ&hmm-gf48thijRy-P-hqKXBnS z5ebSL5`(?k_)UYb9iZeP&gf`B&)SX!6dT02BXm7Nx~5dy6f@SSNnavgNL165J|rOj zr2kC$gA}GNrt_O!{i$2ZDOQpY;`!XjFf(@)bGvJAEX;M ziBIVFCYkawz<-11>s5`Mjt93JZ5N&r{t9n>oQ{7_<5<~Cli7KUw@xJWViJn=Zrwl0 z@nVmjP5(cr-=9O11n(O00=6-Y4=|-~6Sl-Vp-|YHaG)6TfqR5?WN-}*ViGtM zbL+V>`TLY06*&Yi4xz1$RFLCIrM#)Y8KQ|5(vBGG19|*KuX=~pJ`U<+S~~L(UQ;TK zS^tFmsIOCs(r4!pk)hP1`5vBT1i>paU(vBm3RIXB!COUFR@JD@dK3&4OUywrmBy9} zREvQ8A@C!G7@>&*x{aTzMt;yRRcu5Xtx$Fpib~ZQYNO-FG3!xPKHv;;$9lPAjoh&| zj0Zmu$#NIv46112herc-gJ0XMjsWh*9{r{q1wZtthSqnyM2+N+q2Yi*-XEgsEoFpqaMUY{pj=RwGu=fLHREj5QF zaYVtB2}M%LzH~bN#RX*d1n!ydh%E>Xsm9)N;|#S{vpIRRsp}2u%gpww{C$p_`K3%( z&yb)riDxodw&X-+D=fro;zZzD?L<-Yk}E?c8BMiU5NSsO`?%QjDtGT12cj0rKMP)9 zzEjlkMz?B>+}NVvUPQkYKP<_xZ}M)5*TwH6$l&A*XZR08i9r;KcP<)%Q+7rMAqT_n zT;F$BD1LO&rBZtM4}FMJ)cgnVh9)~R$=i2NtZ&_+;NaMh=09ZjeI=IF{0C3p*MKTN z_gR_25JpI=ucu(2FE{})Ew&;8XM8*|2iGh;CX(D}u4?3J}#1@-e z=;nQg&r9S$t%s$GAw*fR6)Dq`6|YJ>xpT=-u8+^v04i|C z$AHD?4NqdM>9tDYp)EkF@BrTW*!%CpgU2bM$r+)%MPbnwTq)3NT?CoS*JtIEQf{UQ zENFfDYqBFyZQ38x?Ij})Jtyz40*89Hcm|I6-22oQ``-V&xxnjt!|VIw#WM>%7cVHx z>H9`-4uKiV5$?MpBQqi%Tvc-k8Ckk$YuCS1f~>z%o+?e5PS~ZL+V?J{ZRH^jukRDD z@8HF>HO1!i{Uk6%FDcYcb%}6H{AEfCyJT}DFCWQwM?B|gvvQ+8C7v+bz{iuQMV?jv z$<+Tt^+XKP71_~R-lx9W_Xkyfgi5EVyXRt>s_#d^i&Whw=fs`+sJpXWinK~!w5jU| z55_33Bgs;%l3v5ZU)#ZMeBue!bY960ZhT2bze%TvdLDossq}&_+C5IF_MubD*f3=EWM3tDu z4At5LFY)pPsv;DW&szkuwTgU=;8uhVqEA#StvClRGIqh`3wTr+0nc9WsXMekYBjmq z^V-qC?pQk)N-2U!ySeb@!#FH=Hk;XYX%)G_K32o1JSx7NQY~vvTjnIqU9%LqlQFNF zmz|D0?_f=Xq^qs8GfMc_i6@Divqnx|&*@ZR=U8fYTtqw%B9T1}E+~nQpB}HsaZ=e+ zVN7K@P@iT}1uay%Stn$nlqF(6OND+8h!ob8Z-(~L@a?MF@AVyL()r|OnfBesvC^h8 zgfa%SpLo7!3PT|JNHZ)@vhywe)3n!jn{Nd#+_Q`{`LxZCq~rhR@P57OJK*J>{6He) z`qs;`n?USqC&9b!Zp9uDek)Sd0*F_{C+ocmD(*uo{v@vBGR`qmH&!zXQIAkIF;|3wyj~l}T0IXu4LIs|utKW<+I39a6^EcDr_LzH^4Lr;lPXLbclw)b%4f zm|rQDO+rubl+@7lq3`-PPK;<;nN@&D7s*!LYwBGgsqOB6mBwolVnVgWFMlnjhO5m? zsV7%fM|_Xk4f7}uUCl+o+f15MN&BTfG&Ux!*2t~SPu5zjSW{~(&riMCCaOeJo>`^W zgQ8N{yE%%zOVw|h`&+-5C@=mw66`I5n8F0q*z5Qu~nKnM*=vcq2(d+Ce;N==Y0R2#D`vrS@jR2f+dp`6Ku1?)M9(-CV+tRkkp zg9P`KxVicDKrtw%JGdapKjOo1+$~ZaG(NzxU_cO=G75*pCcZC(I|+80I(?VPHuj#W zcq`s>YX?8KxAF6RoEY!9^XNS9cko=iXKFhK?{F|^o_7JRji*{TzTF5nmxgzc@jJ)( zzR#uKb7^<*H~h9qJw|hFVe=nJHgnL1>*FcPUiMPp9PMNgs9~+PK0R|-xXFD(u$$TF zR<}01kZ3K&qPxSbfP!bnz6O5eIAXR`;fHtLWYr?WX>4BJ<&1?}-5}S6quqf^xcQzh zVf9>iA9MfWUDAX*?ol@gS^!1Iwn%g7oRt#e-BOc_qT7f`n#9T&NClwclL{k&gQ`E@8K{Tlk4{q#9SC+Skm5I z*y+#S{MWx=;_%kJ%5P+3TkN7#3P9TJt$&{@Y+PQ|z`&3d6Aa8RY$6h2tJZO5A`%k$ zE7Q+ttSx$>on3+RQByWUJINQAw<D&q^kqzAKela>B9}_&%lSq)3M6;8cBeb=4N9E-rG5@&b}ZhjoXdS6=alkkXR84X zh_19y8jb&aOJ`ajY2)=9QkmND2gTGzA~ud9XWjyWu*)5?&hbA??$G;cgq@uV#({@Z zF2zjHJRExzuZGrEdt0x}<3z4?q5uIsb_sH-P>QNFa!-8$12=M~&Htq1c@ztO4EVL6 znAuPsNg$7!!x%fH_@5}}6K_2Ve%A5BQbo+8VJxyb$)`^!WW|2~ch{aP{!z)|Hc{Qo9Xs#rNR#rK>^();N*yYz)PWADeEoQ~dBN(Ire0#~;z2Q~IW!P+t~R}yv%R2x$Q9MVhO z+or3Ww+_<3k3Hah_V$Z1rp-VkEEbgrv6M4(9mmSwS>#*C;WK1Un59Hq1iwpXIg)~} zguH9{2Wz=q><2`Ap#9=ue$fQ-OF<}9M4?nkGen+U#gS#`r?hu-wXvLW9F1o!MSSjk zWC+?gLNC5zY&>7Y@xcos{@mUz>3_!17b_oc{axG>7i%2kg*XTfS2KjaxJJEw72d!V zxu|f(9?(Ckp}M4D%Ger14Z>o`pON+fiSXE?{4(HjOb3Zpf40+n1F7B&8Ah&9Z7v#D z%}>POVo>P_80@WkigDKJA1Ky_ktt}0=j!7xf=F-QqZ(y@A?A*f4HnhuU@eHc*CndKC=AdeC@)UmLD@7 zzx=wZo8uEMv-`D+$3<>*g?GBO4(-J*^b|w@$Wc~gfqZ!nV>oDV3i3ZZ%DLokAuDIs zlgOPT{xY$J;S=?j4MT$z^_NTcME%pvlaqzwTm8d0Qz>Uuqf9A`ROgGh;nGh;{_i0^ z8fGZ!Ie|}4B_sdJ`xra+*bUR*AJ`?Xg0?fW>O}^a!+3V58j%yu*jxi9n$4X-a!itQaTUs~DHgK5^&>^Lp=G7%E8utIOXip^Kxr>3 z>$`DahkDWP(&Y)S+K+j8DqxYJRN#!wkc~ot_V)JgQQ;|6m<4nt#fpz(*AJK)SpljE z=1EwN9D6`L#FKb0HORd6e~*7-*JJ#gHnl+u8<^ps1*wfi3y~?gk!oU?=W+@%ASRkU z@nI02)8I#x>BHt~Y=lY7pU-3gSWf3xrDS7hO+ z@)@lOyq&V6W<`1<$^x4xrPP12OC@MqvGL)@DW%%-yU1o0LqRPhX%kKSzAx#4Y^UNc zO!+hV81O1n^V4B6DNUdUrMJwy35Dh9`B@xp^XkiHCg-y?Jf=P#?3;Mh=R2K&WJ+eZ zKFn}$H_H>O+SvQYp5=#@^>GBwr!(!le#KqdvYE`X=4%KkaIE=fs+yjDKM{cXr%Z%@ z=6$?ds!R1La|{7I7UEOcQ#clzUrm?i&JljKcYLF3O+-jynU43sU9YxQrr^hItnR9bYr>Bc|aEv57@!1daw z0fDPdM+^uI)kbNzxU_}tZ3R$Ws@~cZ2`9by1@ONXjkr>g8~+ZYak~A4$d|&k9STRk z2h>%M#!kvl!S4Cb#e1^bJKg_(p|m%Kl(2!iu$1s^wlnhbQ6xjb@hmg{x#s@i$7XY* z3ay}}X>rZO^QT=@`&gW2xW#npPwqAC@%CrlKZSZseL=L;Ba2$J)Gf;#`#M?Yq7GU4 zb}IRU$eC>a;9i`A-?~=Kj?UAZgMT(lE+n0Ud9o-ZDN8m3xy!!@(Kd?=a@o-~GXsUJKUjtQH{=|#VZ8ow)?_Td|3cVO=a1}PpEOaWD)7b6H z0cUnN?!C0x@OV18q;-%_a=#cVyxtQ6$Jt5ljNi$c5WA|`7+l!Lb5!Ny4m-!`H)U{> z>AsrujZXCvesi?d0Qg3W6GMuZ@MG{`Rud>MVKzc8HLln+xs8ARNcDBfhK3E2i~blE z{`C?1qy32c75bwMJp8C*@6ERj-^q5WjBS6hz7)*6G#mHi5WDDfH=4iztyo*?+E&c? z5;PE5P*tFC<1RWcg(BqnDwkC!o8Hssmk{)>^Z_aRpC>5Nq3kV|fW1u6P1)WAfiLqu zbTiY7J~?lc9>^=vbMvrWN2M}3ucnbZLVG^?yxAFG!<4bOTxo(wGy$sU>-5%1iO$yr zE31RqCLGW3*8QAQ;Wz^M{T$2-kG{e*`tN$4WYo!HLJBz=fUpMP&BDJwJ7 ziykeO;uX+L9@T2Nt-`a|zDNJcX#&_CuGCtM6S%A~e+cYgxAw7F#rj#Y;8%56!6K<) z{cW6)iUqHp7rrm!ir^JI7Yr_@EXr~i8(L@6EOFOhe~R2-cHej2m`zSIo-GoIVqo~^ z{?eS_DC|EP`_uQaVZ>+k5}b7aFPvRoG|cCbIQuwQ`Tow2cEFkPKj*CbJW{RM$O8u0n9 z*rm8m2Kt3h&In|MPhJ^3LYWldz%i$0;b97FB|(AAYYsM}!>9Ut>z2~MnB7UciS?4zZr`CHRJ+aE6b5p_PY5!Ahj>S* zj(R^9@jX_l^tDxPHN(-os`kw70)qJo-4zc$7Jn`FXp)0NzVCXxoEan2w@Sco8-cOL z#4UJ!*AJyPh}il-zTg`K`^6rh!!~367QkuO`_OVqt3%atpAdnsajb+AvLNT@aVk{1 z33c%>+St|=n^lF%pwI0I9W@%?L?xw_iD$!lC3eK#a0^GYUDpo&rMw_`z>QKIxQU{X zhjuCJzXYq-18_05JuJF{yxB0PP?bqjU2kzV{LBI_fpyew4pAg%0#y8@a&7Jkl7j@N zWI#&j=E%6XvSQRQD*yr5w*jKn%VdOxu-hr_m6&>|Bn0D>ywEB$dv=qiLD zFmV06b+1!t@0OiFBi~|^1`DP;)*p^}0ScN}MniWALeAfk&m`N>7j*XwDJEOo6&`mQ+n-2v5?@war9qIOo8*#&~({9#;?Ncd#CFr^ZCMGb=ni3NupFC_G5y+ zY0b@@In7-;^MqH_#%IjaZpiKuogGim^0b9iF=X)uYMSWk%QcCx{;{S_u@fp#8cf%*e!~~fZ(slb*6G-Diu+uQ^%WiolU;wMj!_! zKA6n00&vz-dC&_>sh$;*8d~L9oh$8=SQyOq=~073{X1>uhq96twBOG*O-c3NK6Cwg zbcxj#sr5@xVUOx0pLcqDoEeX^$9&ey^LnelEK3+mVey{cb*D9WFXbhO$GYa2!RmPj z--*_@hUxkHe7|FTZJsX4AGVg^w~e|sbG~!xdvbY=8CjVAvchnJ0Fc-b+x7`F3(L@Tms%o#DEzEY7yq72W<8~t zb)7#?{LSSjThG9r*Nxq^M;}mzy%iaA^SFU)dE$`Hf41&&KNFF$@2;# zu4klZLgty#^3DOKsrl^;EhSDKX0ov)Ldv_msD~Zc?*Mv)KT(F-2q$s^gG_{!z(4>| z1{pJ7{34zNN%^)JChwbck4utIVBq}?fSwpoUsU4nrGNX=SpWuJdmvnX?LzGMC}Oi zH3j-AUHLEs9{PZC8b5p@9vlM+F|)?Cx7XdmDXXg;O{jmUPB9id$`kmg_c!X6Kz}U? zMuB4bPpq41-BC?$^I?#fit6RH)!_Aprotl(llfX} z4HSso68kncI0cf=IBz22{{&9K;tu=nMfTkzJ{JeeKGsbrAzOoY_2|Tu&DJWaW^T6d zy{24KR1$a~>X4@)tl~QLu$~gkGb^#$&wv60p~Dz+QW78`8}e68JXh^4^VXcK>p`*HsMvKa2MYqUm3N_d1p$RX6;%@LnVT zCA`;zTG1|VmMu4%Ma7ruj$O~w=BrV8Hhg(JOOo}UuamUy*%%nA{5>M~#I+-Pj_4(; zM!!Nh?~;w-ubkSe_ZpaC?yWn7WFmM@tgm*=JWREhniI*Ax-x@_#T1K;$@9N$TwBhp zauu_Rv5>)~?F@Yac$Qu7Iw0HPVhM^yK+tYnTidqdc1F)SY8q+rnomhq8m}@785AqZ z;lIbaiU&Dkk4O(IYfg{;O(iJ5wmb>^{2&lv?#NAU;~&|1^+aG0FUGUW6BI1KW#I3O zV;+5$gHdwuj!8c~?Uz_Cv^Kr3UMXN{%IL|pZQ-+O-E(4RG~Z$LJ8avcN(ukt{Kkzh z{z-o0gB`u_8_|mrlY00Au|pQyRzkB0}hc(iFobtE?w8M#W^ z<$Zd4aZR(&K@30cm^b?5lDuwh@uZ+NkX&gUcYY$V@9^tzvA}mUe~|$$-)r06 z{z2}qj~{9Ns*kMsS!&JKx|`!!&R6wZKe^}ox{D8UdeP7hMW3%pKiPJg`dFJBUmP|=hN+bjyQKUAIS)vygG2UcgqeW z8pF~5x?mEg9fxt<5xaoHat=qlP%=`H$+oL0-}`8@x2)ayvMB@TDSy*mv>RvA@^@9+ z2*1;YLr+?3G=DU-`Bau_6X#dXQ_>JIxS?igLMMUUoE^<4Gwystn!>q5Yb)w#?&b~e zdUbBmp60Hhk&*65om1X%t7?m`NZpmHEt;h*x`sO2A|A6X+Pqupitp|-#}oIV7w}S#HnLFrJ=Q@kgrHfD zZJ%>Z`?zKmY~qahE@OSRLU_O%;UpJoDvy;ps^<4Gev~-v++m%b#_1mU$q zvy3_B6}Al?${HY7H}Z(e`GX9fc?oavEL1&IBydGt(?(}-nC>e4yfohx1RyPMen!n3 zEAY7KC&$j3+4wEn?5T%6)#@fJ1=574w28Ebsjb%^E?i4j>ve^X$=}rc&v&3R5)6UQ zcNBQ*Pszy|7v||lv>4PmqJ6}^qV{w5jm=>%qrDz}9gn|T>AL4gIs`1iwAa+|<&PpQ zm0U;@Ynij|Y&+UP7oYGx-PQbI#+@JD&-StT!$Eg_Hn-^GXXBlpWsN>1X5P^93wlZ6 zxo>)(-t_gRA7Qrt^uvASU)wiN3zQ(oom`R~dGO0ckJ&Znq2ccDdLKH>&0T;*IPjs+ z^BR{+g^pYPB!uApjG^27k!u?O8;-_eenM}1y-&W8A3D*`JHFkqOZOCpzsS*I;Wr)H zDU^W?(>>XXqp>Ner6u%BDdl1R^B0sCZa!tke)B%GjvJqqS}L_;QEQ~kpy%}dMi>^;`aW-j-pdMjbKw4mK#HwWTTUlqV3uiB4-?;VaPoHylL3BtzFVt#%3$6 zJtCQ*67fIJA)2k#MOs30rlfEOFUMlIT zWco=-`;zH)Nnf2z4||F9CCT&xNe@Y;*GM`mnQoEvXM+;;Ju2zSTJU zq%TRPw@Z3RGHpmYE1AyUL;5oWMP>&!mP`8mWO}Wn4w-O}zhOl72s#Zj^i#=noupSM(;FpSpG0mNl@FwXylIh8ku1coYO4^r9@0aw|$+Y7w z(w8LD!zDc=nVuu*tYmt>q(5UzXubc^PSWou((y~&SHh-T^1%rCZ7Y`F-RjrPndr!q_A1MJ09XPs)m7q@A%STyoEfn!FPCF-Df_>RWFPi)9kGiEiuoSqz#ahHKjCE%}9O>5#q}0 z1PG)ohB)y?tlAx2AIS<`EV&9L*LIaFnk6@jurrqR!-G5xa}1+f>R^Paq<-|O_~`2b z=T{q*X1%bEx@N}wj2ZgN@geyX6MxmXUF9R!|KaUR;G(M9|L<%tYz~&h6qi7IEpf>$Q!=YDeUr@8 zO6C83&b=2dsOA6n_WR9z&OP@z>vOi}ob#OLJooOYo&jPgZDWk*3Ch4T5zmUN9vfMD zj~N!OAnQG4uh9(ekCqyNib3tog(@(OXA=BzrmFBfNeN9C@vo>k7hN&_Jnp(f1gReC zDNbOWIDPug3bV(f5~4@A7X82>fgmIx#iB2}Hi*|9rf1BaxrbVV4*RBh{*446Yvb7i zd#~pNGCS))P3=u1`S^~0)y|GsWwgV4VB~=-KUW;Zn?LM>=aMRR z<@lAOShW+fQ!371HfR-7D}Ju^3c>Et;XB8Fsz-y0pFSPsq3R*W91!ag#o zUFD>Xsye)Q&20!#T}(La=mgDB31j_HvJqO<2!E8(zidZH!eQr+k=3(j?X1{w?zgF) z6>QWvc+(mZw%EE9JrgGn=hO*xj=;$3iqCMW-UdCsA)1A^oQiW*oRn#B z?b?~;2@ufei~HBT@~RNZa}r~d=z}hA6@mK%&@nIq0)J(D8r=aD${nzRJy)_P1#}0j zX3v-KtkF<@3ZOk}Cswo(7;<~(X!8WD#h3LF&$oDHN4(1m26o$I9~yhQ5uV6 ztFcqB*iPd{3=jeFw3b5zRl{f%$cN5FmFp8({UNo)qf)(5RpSKGwaA}y-i0$x&-{-; zV_;ihPoFNRu)GY)ejDYiz`cpQjSd4piJrmf=>d zyK*T#pDw583od%Tw~(Hn6yuq*>}k4OoU^QQC9_^|Fl$K>v(^y12Es~%sn3RZQ>T$O zupyYUmk&0qxd9 zv8ksod-J>pYj0VfYP3AJGi~bDRoIq(OpkH8ct^P$EBtdOJ@ew9*)^iFCki zxLRpw5H8@b(t{vk3ik!tjm~y%h}i}R0Qw ztKIi%-D&>LWF*t4;hsj_uxc*_=jc5w(@V)Yx(+KMtNpN4FBRu#14mfia8z;5wiUy6 z1NX9h3O9Sf|zPU<$xcB=(nL~r)xw2l%A#RWPZh}@$CC)9A> zJ*9!D`uK)9UbB1GB-A*kiOFk)1kf}>0+)C4j)bz^yx@%SCpcqHQPuT6nsC(F9qD{j z4|gQidq=_!j4sw>iSB5^6Fo4pdJiK<<-nFkN&=&ZSlON(glK0^qbTSAH2i4fpd$za zHP1)QRSQ>TFmyM-i**fwddZ2`9Od-S`>(CLOta-)dbZz9&u+Wu8C^}!VO#LbS=Qde ztbh~D>b99#noZ2=vyoYYHo#i-xTYRfg@Idph}r73M`2eQv}?~0%t%zNhQ~V*sx{=f z_5?~>tEt125UFQQtqenyYo40Zc6f^`E@RJh_RM6@SoREM&sFSMz@A0ySfp->hVN7PbwS6`tQP= z(nZ=$N=UBy;N?BMI4A4goQ2VOt*qgm^`(w`UR9%#oZg$4W7V-|=c}Xv!ZF}gEeHx6 zpAZ}av5o?S&QC#jEOec1Hb2gw%~j08B4 z8rqo11X};%lYb8a6)d;gd5oCB_d?$kE}Tvdj)FW1mi8pq+3|c5lPGDSbP5LvYC(V) zp_m>i>Qd~K0X`&$;(9lc;6S`HwNoBcAf}M>^jT!4&IaMC##2{=bSP;f^3OrgKm|6b zA2dtk+zOwesRBNfBcg=B668$lkrpYMzB7DC6VxrstDbP{^hw_7RynxqZ&}{9w@W|$ z@4^3V+V7Q}=P^wNqqPL#CaO)b-TbM-ff;Q@$PN^1ok_DnruG4fPx7>Eo<#yA%1ei3 zr^EN;6^(!#SKd(6#8me55J^V=70#FPDmDc#g@e1Ue9wmy5U65RunTa-ir{5rZ3!+X zs~~uxTOGW490Jhir6B$bG}=+Kl?@lP#oOl)*dJCDi^<$`I&K4;+nG zYGG>Eu1vp~tesOoK|jM$7yLrm`?%ml)IcoY6iUP6rWG8Lr(3M_2!feFl|(^eH;LI{ zTvH`AWrY~2fQ9TJ%*+}&xQH@CmxC`MCS;frtOwi?*M3rhGw~c3d_SI(S^Ox55){ZG zbPDC+`5o(8wiRZNTlMd%zo&mZK^b8U2^87psBSWFfSVf;}t zCLgx(*F*}#gu*;yTi{FdDi#!CA?_nUXClrRM+=mKM+AQ9>n!nLab)UQ;xuaNi4usF z)=n=}B3(qF4$+lW5vg%U#gaxrxP?**o_)Af)m&9#eT6Xk6e{FO@dKGMIdm+sNPLT2 zNlKBL%3sQra-@)+2Pkz(K6hw~%e+Ouiu#hV%18A`dVcXG;95f+8%BmhM7zXmNa#>W z)U(>cjL|}0GOh_hsYo?K(6W{oy@0Wb)Pr|d*HK>3$%P*qm zp>4FFn(c(Hs_siTiKY6pG4AO>6sYBoj$$R_d8@J2ZGye%FCFnY-IWiaY}kki9Z}G0 zf{I4I)GxFmAp!Rf)i?mxahvlX`jG0Gy7307vnnq`0C%lt6ayW_?wX1`d_U74MB%m~ zy}5#LJWB_g`N;8L{S11wp@Ec?&_%t&m)3u4{Vx`_~Vzt!{M|u?KNL{{Ux@lzcGJZf6CT)w!FBr_Ssw1-{Q-y>raNSrJ@tQxl98QAH);C zxvarAm%|URD@KWx>U(g#NO1uAN86#DMGT2h(WHBet3{drY*mvjF;v_N!xO)@#88-T zJ9>}{)CwRUFmLscI{~@F9k1;J$7^gRhqS$&#-S?PbLVsr5K;7YP9|qKxS{zO z7o>nBE)ruik5A;#o$YjxJ+M`AXL~5#s0?T0>1AB70v%P2IZG?uzCAa9>H$@7t{Z5# zI+!33Ywlbd!CeV*Lt0)@HAo*{%$_-Gr~7zb>f@jyMl@B`Y}6dx!21OSAtSVx*1Zn` z1L;^b9Cq&>I6@2Ews)hAE_%LRF13*}sek=A?XFtNjeXJq|A3j|e+;00>_2r#9?BelVlz}-q ztEM3medXuo3oKQnhkJC6&g7G$I|q%}5BXG%33UDlc^6&TWh8|U4yR#$y60y$21q&c zCAgvz>8PaZK&()qZ0hV#9I6P{3Uy_}@D5$s?DHdKjcsh9ANCpZWU%`xv5Xt9eAEBP z{tJpD+b<|5>OR?bW&go*rGH>q-9z`N2JwaheDVtb>&aO z$6Z_YLxX#ie_3td+1;qdUv!JTU?ymDbce@vr>D9*eduBeKK`PvD1?J$PwCG(qEirdl8&YK{@$$d~IxUupZC>7^teY zqS^~cwfVvDukd#IT>s|gVfvf5dpcF7LF-iao}VpEi3p7QcjZiwonlm*YiM_u^8!>! zb`8Gr)s;HZC%wI1g^oqSR^?Qa#1qs4k&gI+7?$10?GKCsf87TuP$&ImIFzAVeg;wv z$Qbs8=mwndA<$u6%?&77xBjH7ITW0uvws8a%weQO(d*D7?FR-ULDB?JhZyj+ zUv3*-3#*|$+<;b5E#*i`+iRh;eJa$uTY$#}4Tp}5bgz{DOh=tSe+Q+ktEmIJ76h-s zR~T3s4*kw@Wa-!spd5><85;IFJ>@FAGsGJRab#(!7BaFTqiI)=BnrR~M{OKIAq2#~ z1mcGdBZj95aiPr3sP7=uDY0LLM5z|8P7rHCss%zxC?rC%8W0eZQl%>WKf;WP*^C`3 z2w4+)hi*_!-3lRUk7;P@q8g~yw^EU)?O2%96>L0XJjklB_T<#h-T(NXqkYZ`dZ~!SmN^()c zMF|%GDM2c7QNaZX4hN7DgoMToSHndO7XT?i8gkLVMFSTADM4Q3;sqBkxBy5A@+KE= zxOl_G`>Y;#L!Ud9q%ygoM%}%!=kA&meIQNQYS1e+%6(Wp{OoaT)V8tuI_f@Nd2i!( zkE-nSRgg)2{~cw2_&}^#1u0DM(6F)5=F#}UT+wx!C+!@g^0?X^$W?TQmJg6Zssz-i z3bZA(Vvc{s2sKIefdU@6dgX|_2C4Aw&q{wL+80e^5Iy}%5Wo=F;5p!VjQW1*Wkv@t z5*>v4`J))GP^xb^iHS21_Gve0MZpOWqR-AvYNmd)-*bPBWkJWv#SJ}=Y(Lj&X?irb zG<|R&+|=8@S3w-~%1IPF?J68XE2lN~L^_8jM_;8S%LFKOE1v<5Mh>nC19oDj~#~g5<#9Ao?LeZyH{SPCWTKB4b z=N9z$Eb8+OK>RES(jbv?Uf0$9dK&Iu>T3QV$|;N=^zJJ}3@*b5#K3-7k0idwXh2fT z;a3{wmx%yTq8L$Af7I1nnt;)DL05AHL?`4+7<|~`5Jf*gl=Bc#&MSz)vV+5~ZNova z>=%i0{)ik8Q;x`>=eo4Ec+MJhG)}ZR&ZD5kejm^M)Mx>vH6+Es>X_euPkG z2?+^OEnGx`geTPoLx-w@At&Gk{7^?CVj}RrgBjH&VK)ub$#Ou%40%)gAb*A);VHBY zwH#};7RXQ<)6mvMU*chdg^&M8lI(e z0huj682$@}Ysa3aFe=)CQnpJ#SHB41hEVrO_rXsXWw=G*y%%^9zj>a7wU_Z* z-F0sNVDM4Y`b{|1!I)$m*U5{Br>JNFUy7f{1cgkz}YZyxBD zP>&C0_IdK?3~~-RYH_dosRCl2WNv zRHjryz=(a}ylhuHp?W{l(x)B7Iw&V`F_1ZbTxKACqrdEa^7%>}{X)nBC;2g$89YxQ zBC@ZnfwF~jk9us6f{)QInCVjRO!;2ihD-aW^;L|ssfOSu+dp-E0xk4V$&J?gCyAe! z*n*$vx{M3AQ#QHB*Nf^R4IcU&+gc=s?}VMxHO~=HJX9fk0`=V8OdlN7%QZl3sI9L2 zWw09DHISD$M^ZjP&_TvI>(A!UjL@VGx~lAkN>~0ag={FRSJ4N&I-D&=5NZ7^IXjo0E1so_$CNTaBgYg$Qgvi2b^hN=GRb z;5X2{lQe2PboMRa(ISE6%?Y4G9YuV3H-nRGi_;sCZ&tK{jS<{yIffQ)?BnO&DIy`L zHyfP>6q!1Rd$nZ?)Li8PZEdo^!c0bsV|MI z?J=`s(A>m>n*-=M1v zpn%TrEAUC)8Cv&P|C$jUxiWmwcu7C_yMee;w>%B6q4LD~md zXK_phtJMI7E~qXH2&jD5Em7hHAIkMS*6mv3*?Tz3;TeI4ze|7`(4#m{*0;oy7ZYjCF zMJc^l#Q7)@`9KiGGwKYo>fr(;>CdPbe?n06ettT;Yel-sTKF5_uSM;RKjGQar6~xV zADkDyCx<3+1c+hkJnN(|qCdNNgX;Dye3}?YO`o$7__cgl33TUt;mEF>O@}XRRDpPs zHc@DsZEU|uPXk`zRtG!hV>Jy;4N!5hnK@!Ean~lgz19Sx(_;KUWeuoRG(pZ^YBc_^ z6{KSbscb=nb74&pk(y(XdwY0w5=limeX#fiOKUi^`qe@!7E}%)_(b>BvRypr{7&~b z&@@EnIBW=eoTxe^s}_@`1Cozim`y3bXuDEyJ%y5MjP&TA%V>Vo`V&g`S=lDN2hU@%R6PAD4(&)AH!) z$zpNWY`jEmrGk75j@m0Vn*b6+=|gg7y#SNDKN^?VD(pm#CVOh^yk+Y!Kd!<9^CMJ} zqT(UAAe;y7#i}?(t#19fpdz$*i}nr_ z+Pg(t)A=X0HMKdNu*%wcBWvriIJPO<)m0Qyk@(kNi2Um#a{l%2p7CpG8lW=jPi0O}FA}(yDCLo-;yunWA!9fcqP;ufLc`o=|s9DL8akefbeR z@p7s+sxkO+JWv7t=V!W?ws+kAqgMCQXWH%GAape;K=av6OnWaG+gsG2i3Hh7kc}H@ z{nfF>`m6dk@jN#GV;EXm#d*f_=2KMHOxgOA@BpzUf6sgyhyzwyy7DT3trh~!+K0vp zi-3+Y{;MN~=Cl?Em7}##Cq&7LO96LLJNXwLgfj)U>imf43~*p`-a*vkxv19j&~*&1 zO2esJ_lHb)g0{EThbhbt_2S%6?{T-k)%?-ypD+1Sg-Yw+$F~YYe*mF~p2Tkh$$1>A z;#yEEfnJcb0T~eNO3l#*Mu7a`q5^k?T|@>!Y6sZgGknx_VBB#hRgdsnd``n9!i z#)kKKu*+c<<9!*-3cS~l4MV?Gu*2X+zc;@V?DIcpxEnBcc{E%oOf1Ye7+fvj95Ces z|D@sGguM&q5KI#k2IeY^Kg!z`W**`u!kz$= z2jhZS0kZ*S7tD?CQD&IaFzJ*A>?aZDMcB(=9)y_dZ$P6lj0C&Hp9Irq+vsy`?89yNVp|Os^RvURH}+_Ez45f>Ix!era)WyT zE?oX|_3-Chr_tQ^+#{tV*izZ$Hofb`V3NBOOTtI8ecYJT@C&^$NeJRX{<44LqrzW` ze^+ne-vpQ4#r_gZn-{rpH41;p-}R{QmtgOET9&8*h)I5A6aHj)80n|R3oinKKMO%^ z$R)N*WIp85a3Z+{T-QF4!GZ9jWo-lCRBi-&r!-v`lDV`JyTe>KIls_su-U9O7M}cA zo*5&!k@*GY`^?#?Mw?v-Av#R6Y+3nvXZ#^SxH>o zvq_vDc4lT0S3NU{OPrC!1x|;Ju!Jjxt+tM639n6LZmXjWEIyYxf^&6FWbYJD9+xw< zOp9Nah_5InaVNyMF66md7FS*`nF&xD(W;)vtz(RVt6Gr4tsV-x0J{i=)j#T$goFs^ zjX{ar#(yDg*qLvjOdS$A*Xks$VO>jnQd{9y2mHuwA@ZQQSq=O)%J7qyEwHF%y%fEa z$dTJ-F>XEDtx6X6KD1E2C6_nl+sxSxtF0uS=Pn`a-N2XO51aZA7nsN;Tb%_a-ePs| zxn>7%wt%+q7Gt4#Vv!l-Dwoeef8g_s7E^&4sc>BE5Cca-Y{}Mw0wiuW@kH#Tobt_f z9%1FN?2t1-9Gd5o5K@+&oi8-n<|2h0tBp6A3(Q2#mLY7gEUaDdF`MAF8GdbHr?9X< z&e)VO6_Gmx{~-8h7g+6tyHTDS;W zk2IojO}xWOg~`qnlOrFN9@jO_!1X{}Hirqtv+?s#|8NJ*=Ja9V;b9=e>3~7Uxip8- z=3scSstD!nNFP--jOVxlgz1bh&2@=Vne*qF`B78G@bmL6ChL5$pN%l0Q`vHzg^0__ z{Vw7XpEOByWRy$F3>9+>{#18Tdr7w4mYrT`%(sNu?8#P>S!gng*=({?nX}BiG0Sc( za5~Ik{20vIsChou>WJsP;|<&(q{lByV!8uqobv+`TfnZD0mE%5!qWJy0!=6aOz$+t znlm(6&2~#a2X8l<3n^c7alRdyAkAXL?Fl>XE+G`I?UHsXh0ftB}g_OgBDo;AEU*QKWJXQ&1vWD)*Q!t zbZ;8NgOR(S87m#3!Aq=88xqdW!<{5lECv_S zaA2Y-V*LTR2x#!eTxt(rUojaQ)wv_j%okv_#QHFe%R-8tZ^3IJTj9j}`f?{wW|AbE zXU|4c4$8-}YLJkoZBQ6u*CiMjO+Evg+6(uRSij#0LPvd7f{9T=eMP7Ze!dZ)*QR%zcZ{5piCg^=eC!@nhl?>M|A?zc;y^fyuZxAWfxKMhRY9j15m zFVa(>jVZm&cbJ|J!n5+t_6B;Gv+(^{KBFFGfm=* zB4N64IBgbMFj{ys?LBVJI;YTqY`N!=He1IaZ60a&=k0lHnTjcUD32utAA;qT4I(r1 zFxg@0U@m4Z#}V%?gx>`_2=>J_0-cpI0wp%HA|ju3Y><(c5wjYmFsp@4iDpwgZ^la0 zVdRGoVK+=22ptcP!~hlN zD(}v5gTfWuj&pw8ifB~rcI(AQdLtC1I4^8iGBc@g&`;D@;!n|I&NkD3VlDr9_Lbw8 ze!J(aY9iH8%b#NS(_udUIHkHC+g>;LjHCDYU-uo1>PzVY)dogRvOS=G14*$ITu zU;Gpc!&c6G^x#(8KaU-Kba4B-YEnANwf=8fQ(_T6S295J-b z9e82g}Mg5AZY@Tn3oR zE7gY?u)IF*1>8&K-v+QeeL9mS_>`gZFx9@!-*5k8FXJVBR`XnKhZ{w&pZMbtgD0NY zvh~{A^BXV!ae~4Ahvz0;cw*|-w%5)uc<8l~WAMK#e(F8H*Qy^ETvHPdiYrVjPJCj_-Z!7Q=Fi}1U%Wr7htz#ad-)|J5%SB{Rg{`gGQ_t%0MTpRyzpMNiL9O15qGWgQ7+n)V= z-y^HLT#sV#cN+$DSm_&eA^Q4I1}}dndcYsKvG1f_PhoJvJv9eT9ok(!_xd;nfAsmm z&izup{&dOp$qasGPoIy!_~+cGR$otNaQV8dMZpCdzWV68iNTkTX9V1z^ZME^uNN@5 z@6S6=K6UlHj9plu$;f)N&y8{hr|GvxWj1cvbI*-s z44%^WqbHUvnefbv8!H(6$>RP+!SjDR<+!nm!H;kLZu37sIrZ}6H(p}!ibam4`!Zg= z`s$4h4Bq$r`p2ItO8IctjZF-m+g`WrYJ>K%Q#ZCS`2Fy3^OQatKmYB3jasu5mAey%Giv*cF(5Cam!ggAYDw&;Lho@M~ilPcXR6XI$CE_a42T-FSw<=Q_82 z(xm-v%i_lK4Bopm)#|f%%d&qoUSe>lxzp+UJAAPJ-NtJSrZxxVUG+s{Gf@p(cgW*u zq*;-ESsOk*SNw*GQELeD_^h~E9CuG?cQ(s$+`H|)_bmA0+3Hv}IykN<{l^7gL@a(h zjqMJw9(0|3{iWCgUlwvvEHy)~=Q{N5H}RiKxuFd1_s6b7;s471&%d}72Csi(_p(^? z$&H_|nSYtBsGG+$B9;2obn zKl8~)W&UuFcYEL9$2aJ;_9vnSqLefoLj2?js&?zib_`;V=E zUwMYXLsMRP>Da>O|EN=*XK-N3ORtO@H}B(f%1aFHyejei3(H@x@KRl4aA^6omjf^K zsN+@aw3+K4{>0huUJ86ZLB-DXxflFbt@-2mz#pcl*tJit|D}YiV|>VlA{D#b$t~IU z%7e4-O>tGIfaq_m`zd`zd+)G{4Y#P|7b6FwK_|{6?$r z)LSV2&H4Ata^}#)NKAtg%#6)xoIskljw3D(WT;B{el5m<#rE7L*mUQd^y;pDM8PGF z&~dJ3R&v}ozv9s7xdbU*B-Tgr^27j^m#3f={4HQw>q+r103IuY_loeJwy&@j<(7^! zaI^xX5p(PEqKanfXJL7h)lc8ADLjc=Qh0sX1mgtoO>VARs#>g@L;BOYw$NCNcZ8Sn zdk8qv9G?D?!0*(DM?^+N#|(*$GiGI*%sC^hSgy|>?{qL`fw6$NHYI!(bEPHoedC37 zx3u>^^SZF^mh2*dP4W!!zY2?EYZp~#Q{vfsNfNZDDhVB*~CxyKrjw`T`|Er&R zgpl_|G-?mzEy2;K9O4s8a2ViTfNv^^y&yl^oL*=y%+9l+IJ+kbZTwG!rTUfNGQ4+( zu^BD7W}Y!D#je4Nok;K@z9UPL`W1#pF1Y*JRH0vYhD|sKAB%BLOgH3&NCTyyoxv+^2XO%dlaoK!6lAUL?2?H5y zhyQ205Vjq{NO2xP9GW{aK!>TmZps9QH^lu;FS6x>tCCf~ za>RUrbowAXjT_pVNw6>82f}b(3WZ9cR%*Pxm0GQj(pTfBY@-fTv{l}vxw~yUMSEoj zWv76ynr_-2ieSYY^<3p!s<)L}l>3zXm52Pl^f|0NqCBps*LQ)j+M92E5?z1S zqweJ|ssq}zy{mtCRJ>v2n6VQkq9E^o(50)l*4M9HhnTqd^{*a1<};+~iS^#T3Bz;p zAAho~HDlXP7iMH#y4ILB`MKxA2KNh@^1|vhYhQY0{Tm-`+2!Tu-@aS?J%&jyzq0r9 z)!v;t_v$nJo*&L%X#BKB&G+upHzYDPe)QOJscBQD-ajLKc4oFYXRf_?(L;~D^x9h+ ztLxr;%VOR7)a+ghG%EEVRgOwAIL!5MH&wWou=nb+9@_8|614 zxpat6kgv95f+0?ot@Y6dX?m)U%YV)xO?7-idrawZBh{DqhpsTkY?iI%Y^@ zK%{q=*0;3pefJL34hZVpw`;o&J`<2ZNHem+jW5yJ;~C1`xTCV43|fhvuw zBCGpot*`6l*}V*YzFq-$$9wt4gsMBZJ{)RF^B?WwJJQf)v^FhZjJL1r@<`uqs!?Nx zsM=_Kz2dxmOJh2DC#bqkRp=rD%AU`0`nh&JHZD7$Tpt+p_-hZ3TJzz0W(( zNBV|n?tXaVO!K|!IPbQJ)Fqz1sx3b}z~^5-lt$_l-MreUwWZ74>baT#m5+DelbNG@ z97A1~eeK$!_9Gu`=iknMnolR!qot!%OGfD0m#6md@^XDSP&2%zqG*t+vszi2*rRQ{ zMp0Tf;NkCGSNe}r`>K^?ZBxb#bL|-FrBF}Nbcs@ywh2|6{HOZ5-i+-Q5UTd^R<`kS zJy&*A-BuN#ny=3A@>eT#{_0p1E=1dFLg{4xZonl*+Xg{>yj}n9<6G{Oj?9cirt1r0AgTsOY5X?A=AtRoNqmR}EAR@(WYwRpH7A z#Vg8Jm9ME^*Z!{jLvvkuL)GZ>MsZ0+<-hdP?yp!@)%EK(x_ig{aV>1{J?XPDPM23M zf8xnk-~Q<1PiyvkcJj=3jhvcw=ppe5!^VuARlXdan?Cxu=Cd#Mo%xPy>h1~D-)Ec5 zZD#uH43oL?iB}Qj(>Fw07U|uST!@oWl zg%6xO)uxBV+O=E8qQ!5%_0Y##f;x2XF>=&|iPZlWKlI+GhrT?~@bj-W`{NGhGkwDb zzw*`x+iLe6IrUuPvn%zF_c(Cqi^d5PXUz20>H_->{^^3nI%L>ABMenfq~$ty@2T5= z^wc`Lm-_@}CqRf9mD+Is2yTq+se7!n*P4^zC8R;9UM&D3{`-Q4Id-v8jb7az|vp675o4%_*SZ2TUA|Dx`5l#aFLd0;I!R{ ziNkfjfup%O6^3jHPRBdVRX3$$Cic8A0qjOxqW!TjG(Spk4&G@Fx+yO4V6*abrG=n+ zx`C~cHek}BdFSSJpv5Yz_HXtv<>%%*?AR2LP5Vj)h;M?Df%;o_E>=bq4Ha_-4rK zZH#e)JFbb-cQuawrTdz3iBZPXhB<2{O|)fZi;w{uGS)mud=g3sf@$Cs#c!MkUUaVY0&Gvm3}N$LJ65F3GS+l1AbC2-4vr0 zY6TKkXcdzbN^gH{mO|;{=RHQ*1?ediL)s{iqsC7W?4!t0E4+Y|vXfG+3RDN+)k~pM zpzc-Ol-==9R4Tl+3Zm3c3NO`Hs03i^O(`n1UcO3&zDKxP4_KoJ z@$pykD5OFa3r|E;#cP$yl`2Jm!kaQwDQglr#m=6bs#1~3b6)vMPOb3em8nY5X5ijQ zsZl(u?0i>%qOZ1-UzkddvMH7Q6v?Q0rP3ee8mx#!s!F8>IKCr8)%sn{`N^Q>Lm$`h}|(C}MQ|QBGe~IMVc13{wSb6x!hme`S;pCLBeE zimDQoqIf}}(za(ct55_f+IXupJGE4p4pggXD+;Gjo&#=Pu)8RyXvt*`)dzgdDm1Ew z^HC^&Ma!c{D5{X3TEY8h z9*IGAaF(#on2KRbv?%jYT)u&w1L8pINeQmTJFPb*n9k1SaKBdJM*^mGyA+d8N$`x$($gO2yq|CFbYSF@%$x}iPqYp-+U5hl>7o` z%l$(6eC&Kfd1FzL?2TmN$W;`ppjVJj972+4Rw&yPS#xqAeM?YIfiV}(`r=SN+m=l) zTqyFOJ$o!{T9=Oy;R&#bW=|JkGwe2i?V|f)*tDNzavU-_V!4bl9}_hwE8j83g5%J9 z6EBF6@ncfNvWh4(c{IDTsK{!A*k1B|$&nE}JDd`E7tPW{Oe#lA1)ESXk8;emW^bWh zn{v602UXvglt1yL8oiCTi1BTFrFy@OH|XyslNU)!RDI#%b=_d|1y+Rn|~P)_4=1#P)t^x zHWE_6$uW2icG_`s+(Dx-X1d~?_*u;|hS^pNY6;?uHY=DYj-2qA;w1)d9>Tl>+CjV& z3El)a4sdH)QdOGNL|b^s2yb#5I3vQ*w?=53%4G!guO+sCQvs7(W|kvQS0o>^W2|ZztbQTnh#B#X+?^HijE;8JWB$+H^HpmxfyG+oSaP}ZA;seU-{C0BR zfu>u_eK!w%5+-hzSCnLzDW7qOOT5D7uq2Iq6FOWm!VzD)xv!G^bxyv`4$)#k+>)oQ zL9@SDF3HnwF&05A#&K^x#D68n{2YkkfF~Z$e}nXirz^?Aw(d{O_))%6d{$|~REM}7 z&4O%8-~+<=dK7NMVMbCG;C!RiF$hEXA`m~!mo4Ny=K?LT4Y|okgLrc@VAFWXgH3p) zH3?l7l<*YTOleR`Xm5m}xq5>bw;5LoC?VVI3aLkn-VbcKFaa8qt%KI}E>54DYzZ@US1-C<(n z)m~zOV0=+wHhk=&9NQt3VG`250Fw+O!P_v;(L5=^+X2%&DW@lN52^LMiIHPA7UF(} zaJdAd%YoU07gnfKCXXBxi@7&npq12ja+>ZUfCd%dWWkDID6BlW^$6DmVTi|F{J4P| z16z-dMrRyV>zZZsSHGOZ6}^JHH|rsfFLSSd4eqaE&V!x$h=H>po(kolz0B_A!rrD{ zET;sY7R%YXT%!@b2=V%(;}YHy93jGgU)wxrwpoSsP0<{oZMus%OK>^zr~Q@$FKPv! z1x#g>{I!Tr`!5Nmv-^In;Cp2-$!^L036}elq(q`D{5rhL^Vrei zFtx3O-!8n5lEJ(2E)P$zJp3Lp{NL9G)NRehG(;}VH89DXaO6Sj2=S;G0*n@WA+C^Q zWLt|$c+9^kn7~193$O|T?+eGu7Cz0E&D~!h$S(fXxRKVEA*kD)$cNS|v~MK3B=5(~ znFt;a(jcb}? zD8I}!k zq4}T+pYK7sYk*K7Yzclrgb5c3U+#ruZZ+zQ=6;6%q-3scaxzymHW_FC!2AAWZsW{k zaIllPs;p!#Q}pL@5GFqvLV<8ELL8)#sTjfW4$!qxh?9(T=OYcc$;-}pqfDYHwir0M zk%yDNSEJmLk%y=Ezpvj4hnE|WW-97p2@H+Hzb}2G&1NiN#L3@k;O;}(Rp_rYPe|~S zfN7qP;90HwGXM|yTXC9ioNFdY0FGNN;xr5C6FrjP1iXjHV4Cmc{%c#oZvq}H3;&$x zFK^FV&3)ug{f|ro#!nhH*3$Tp)=ZMUqSe}=#d5RAYPaX(n3o3~%E2)^TNz5ryr$J% zizVpBA_Jqz(z;WQEA1m`^DM?>b=9=i0It+tn`HQagm7$o!Nm1>E)n|xKe);1!R_`? zlM(h-cV{Q#M8M3hT}jsfFxS(P8<)JAu7zRGo}W`fVmx$@3Pca^w^PTba-XD(p(SXUrE%`vUhS=uTcc7GIH z$rA`qG_ZB}nKRP_EdkhD2~Ajtdv3VoHEUM$^ud-}!a|E`A`90*=H?3vj}N+SS4&sh z`o>-Q_qY7VCwYZ}8)`QoH|Su{d(RIww)yF!Nk0GRw5!nj@k?`xqSs}gUHi1@#fYU} zwRvgtiIVPTzpP(+?7*`F$Gz|K$-=i6dk@0kS zL&rOxJM(qnr1uV8s$FuhZ0gE_yQ|mFH9h!S*7fsy%#Te>SF}4;;FX;{O!xb*0ouyl zyd`0FVE(u>Nts{T9?1RI{xhnfqYkO>y}l&iz})wI*Zsc!z376K?~FLS=i{#uf7`O* z+k&GJ&h%4;eF2A0Odq!Y^$q%CedD>4lU$RI><%*4ZF}$1rk=fby*u?EPk*#ex9*e8 zFK$+bXqRd--Z|!96MWY1?MD|{2e%!UcmM0%jN69|%6Y#k>BKy*2fk8l;Sa1F5cuT< z_ma0C=<(kBUhB?m`)A~#&7Obl@h*<~jnge(@AFK@m2JNHIAG30gN7AqpDi#wp-9jE z{?eqmbF+*EU61O`OD1qx7yt9LSHXgHiZ0tPX&;`pOZRKdv5n@`vme~`5|%n~(nY3>_!n=_Bw2dPuO>!ym#80Y)io`nHl4}CzB`dX5*Utpl$)<<~% zJ$>HGoA&M!EAMkC=fi)VF@DkJx0AD1?m5=K*UGw$o7Nwx?J(@!>7@ai!V=Oy`QVL$ zkE;2y&Hs7--EGhLpMGEU+<~_paihNc@vZp>UTEL*D*?<*}-m%m>v;J1=COnsO?4{e|V0 z@RF_hBbU8v{CDTFoGq&lSo_4B&b#mF`Tl!Vqx?SpFi6>Ja9_=otqC7}8shiKJ5SEo zG*CU~-Sk%)kL(EFSNC4Ui^oE{y?Jux#vP{T`(DgGziRCqr|(mR%2&O7R^1cR=G>B8 zZ`<9|IL*Ujw>{nN!Of@N9QgKv*e>t;KfnCIlL1@5{O*GbCl>ZR^_AbD0|y@&ar)5D zSKs|Q&r){0$LB#?OK0}|@bWkFH(KY9`k>?YGqg*K=I9zfOYq7`_fxcOe9`=P{+n4> z4>ry<8uk?g6`%H9{nLSfBa_S26L!3-iZk}jedS6}X5IKv`D4DCZy7#z&i*$~%s6~3 z$L~~PL&DJ^X>V?M_0tzWKKanTcSfCUd{5u?;wPUxw&SDE^iRE;_VU_I!J3%5kDl^6 z@_GMhCns;qJ=Qzy!JKzDk2UU6cFCKyVxV=vkga~3)-3nm+2xdGM&$+NpbgbyDC%jq4MXfy5goCn%$624 z74)FIo(#-WTDM8%d>gpZI&vf$0{fnphm3H)Ura;tZ*kBF|6!+o3L-AH4?4K69JRk)dj-2Orjn8Js9a+M!sgQg1a0p z0!(gE3x&9KcyEA_$CdKg=xCWQ2bkQ7#JHRjdrTO4Txs9H<4gl`2;OQR4G!d0Eod0+ zDgHhhX0>F4dtojp01XR|5KmxmcQ*sMQqcs2HeQ1-gtrb!XlH@*6T!Hjyu6S8YUw+4c?mo-mz%$r?zk_`{ ze%OL?b$BP3-pK?m9Kjjr28u|?Qb@ij)oP!JJqcZbo?yRbwM#XIe3B)ACg#3Pbm8{T`if@zMD{OPQf$i74%?%gmFOgPEmfg+rV8x`^KjAmy-l}OV) zbqpYB^FBY{0cSdM5dx1CF3Ov2nMgxQ|6aV)nn!|X;5|eJQ@sVi6ge#=G-)l6`4L|@ zTn6J-ITs{@=|QGomxr86M+|jBv(8 zIpN0cNaRFDVd2o>e0=-@I{kG*4k9nVus087GvE~y zHwR4O1m09);rW56e!DRT>$PHP9Uh2C`5pq}g7|j5_#8_JKWI3QW?%||NITF1T=L-; zky@Fi@SmZQDSTK#!Mwt-JL>h~Njt~={;+}Z!m<#b&RQgx=sxxP1#At{vY7I|FNj!V zrlDQNQC&cUrqGdOG8UmwnXK=a@*%N`wX&%xZIbVzc4&%ZD9$s2Ad<2#nkR^s`iX}6 zPZa229E<@b6=o(3nx=`@MtmO0P)%WFYJ1l12EsiT!x`=WcI*(4MY6DE27&z*x$nZ3Uep&Bpb_bEl+|@ z2Zc$DNytX{#E~PXCEYh+%!H8>lkXcd*>K-|6H^Q$Moqpi8S))SgUac>L>QZXqTL90 zR(28AiT2&1{rta#I+xDT_9G3Vg%VsN!hhfTB-;i;nuWu}L7)YYGbz9<866hQRu~XN znrDRoN;5xNlIJ141+*8G;9|fu_qIOIlkh$XKcex{`5&DRCf_3b|4q4?dD#+vdB8
    `; - } - private getSettingMessage(setting: ISetting, newValue: boolean | string | number): string | undefined { if (setting.type === 'boolean') { return this.booleanSettingMessage(setting, newValue as boolean); @@ -289,21 +272,6 @@ export class SimpleSettingRenderer { }); } - private async setFeatureState(uri: URI) { - const settingId = uri.authority; - const newSettingValue = this.parseValue(uri.authority, uri.path.substring(1)); - let valueToSetSetting: any; - if (this._updatedSettings.has(settingId)) { - valueToSetSetting = this._updatedSettings.get(settingId); - this._updatedSettings.delete(settingId); - } else if (newSettingValue !== this._configurationService.getValue(settingId)) { - valueToSetSetting = newSettingValue; - } else { - valueToSetSetting = undefined; - } - await this._configurationService.updateValue(settingId, valueToSetSetting, ConfigurationTarget.USER); - } - async updateSetting(uri: URI, x: number, y: number) { if (uri.scheme === Schemas.codeSetting) { type ReleaseNotesSettingUsedClassification = { @@ -318,8 +286,6 @@ export class SimpleSettingRenderer { settingId: uri.authority }); return this.showContextMenu(uri, x, y); - } else if (uri.scheme === Schemas.codeFeature) { - return this.setFeatureState(uri); } } } diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index b5e2de0ed52..9f6cc07ba5f 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -38,7 +38,6 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService export class ReleaseNotesManager { private readonly _simpleSettingRenderer: SimpleSettingRenderer; private readonly _releaseNotesCache = new Map>(); - private scrollPosition: { x: number; y: number } | undefined; private _currentReleaseNotes: WebviewInput | undefined = undefined; private _lastText: string | undefined; @@ -72,11 +71,9 @@ export class ReleaseNotesManager { if (!this._currentReleaseNotes || !this._lastText) { return; } - const captureScroll = this.scrollPosition; const html = await this.renderBody(this._lastText); if (this._currentReleaseNotes) { this._currentReleaseNotes.webview.setHtml(html); - this._currentReleaseNotes.webview.postMessage({ type: 'setScroll', value: { scrollPosition: captureScroll } }); } } @@ -116,8 +113,6 @@ export class ReleaseNotesManager { disposables.add(this._currentReleaseNotes.webview.onMessage(e => { if (e.message.type === 'showReleaseNotes') { this._configurationService.updateValue('update.showReleaseNotes', e.message.value); - } else if (e.message.type === 'scroll') { - this.scrollPosition = e.message.value.scrollPosition; } else if (e.message.type === 'clickSetting') { const x = this._currentReleaseNotes?.webview.container.offsetLeft + e.message.value.x; const y = this._currentReleaseNotes?.webview.container.offsetTop + e.message.value.y; @@ -234,7 +229,7 @@ export class ReleaseNotesManager { } private async onDidClickLink(uri: URI) { - if (uri.scheme === Schemas.codeSetting || uri.scheme === Schemas.codeFeature) { + if (uri.scheme === Schemas.codeSetting) { // handled in receive message } else { this.addGAParameters(uri, 'ReleaseNotes') @@ -353,64 +348,6 @@ export class ReleaseNotesManager { margin-right: 8px; } - /* codefeature */ - - .codefeature-container { - display: flex; - } - - .codefeature { - position: relative; - display: inline-block; - width: 46px; - height: 24px; - } - - .codefeature-container input { - display: none; - } - - .toggle { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: var(--vscode-button-background); - transition: .4s; - border-radius: 24px; - } - - .toggle:before { - position: absolute; - content: ""; - height: 16px; - width: 16px; - left: 4px; - bottom: 4px; - background-color: var(--vscode-editor-foreground); - transition: .4s; - border-radius: 50%; - } - - input:checked+.codefeature > .toggle:before { - transform: translateX(22px); - } - - .codefeature-container:has(input) .title { - line-height: 30px; - padding-left: 4px; - font-weight: bold; - } - - .codefeature-container:has(input:checked) .title:after { - content: "${nls.localize('disableFeature', "Disable this feature")}"; - } - .codefeature-container:has(input:not(:checked)) .title:after { - content: "${nls.localize('enableFeature', "Enable this feature")}"; - } - header { display: flex; align-items: center; padding-top: 1em; } @@ -443,40 +380,13 @@ export class ReleaseNotesManager { window.addEventListener('message', event => { if (event.data.type === 'showReleaseNotes') { input.checked = event.data.value; - } else if (event.data.type === 'setScroll') { - window.scrollTo(event.data.value.scrollPosition.x, event.data.value.scrollPosition.y); - } else if (event.data.type === 'setFeaturedSettings') { - for (const [settingId, value] of event.data.value) { - const setting = document.getElementById(settingId); - if (setting instanceof HTMLInputElement) { - setting.checked = value; - } - } } }); - window.onscroll = () => { - vscode.postMessage({ - type: 'scroll', - value: { - scrollPosition: { - x: window.scrollX, - y: window.scrollY - } - } - }); - }; - window.addEventListener('click', event => { const href = event.target.href ?? event.target.parentElement.href ?? event.target.parentElement.parentElement?.href; - if (href && (href.startsWith('${Schemas.codeSetting}') || href.startsWith('${Schemas.codeFeature}'))) { + if (href && (href.startsWith('${Schemas.codeSetting}'))) { vscode.postMessage({ type: 'clickSetting', value: { uri: href, x: event.clientX, y: event.clientY }}); - if (href.startsWith('${Schemas.codeFeature}')) { - const featureInput = event.target.parentElement.previousSibling; - if (featureInput instanceof HTMLInputElement) { - featureInput.checked = !featureInput.checked; - } - } } }); @@ -506,7 +416,6 @@ export class ReleaseNotesManager { private onDidChangeActiveWebviewEditor(input: WebviewInput | undefined): void { if (input && input === this._currentReleaseNotes) { this.updateCheckboxWebview(); - this.updateFeaturedSettingsWebview(); } } @@ -518,13 +427,4 @@ export class ReleaseNotesManager { }); } } - - private updateFeaturedSettingsWebview() { - if (this._currentReleaseNotes) { - this._currentReleaseNotes.webview.postMessage({ - type: 'setFeaturedSettings', - value: this._simpleSettingRenderer.featuredSettingStates - }); - } - } } From e378f55a1f9c52edaa4284bf60e990ad3daf2897 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 27 Feb 2024 12:15:34 +0100 Subject: [PATCH 1629/1897] Add error logging for PortAttributesProvider (#206332) --- src/vs/workbench/api/common/extHostTunnelService.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index 7200372acca..650b852e1e4 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -103,6 +103,9 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe } registerPortsAttributesProvider(portSelector: PortAttributesSelector, provider: vscode.PortAttributesProvider): vscode.Disposable { + if (portSelector.portRange === undefined && portSelector.commandPattern === undefined) { + this.logService.error('PortAttributesProvider must specify either a portRange or a commandPattern'); + } const providerHandle = this.nextPortAttributesProviderHandle(); this._portAttributesProviders.set(providerHandle, { selector: portSelector, provider }); From 8f44284342a7fe8f566c52bebf8b30746fe5d63e Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 27 Feb 2024 12:28:09 +0100 Subject: [PATCH 1630/1897] add 'AI' as extension category --- src/vs/platform/extensions/common/extensions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 62977ee07e3..331aba1b55f 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -255,6 +255,7 @@ export const EXTENSION_CATEGORIES = [ 'Testing', 'Themes', 'Visualization', + 'AI', 'Chat', 'Other', ]; From cf03d0e639074ad51db82075e5a9745163af7f50 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 27 Feb 2024 14:17:19 +0100 Subject: [PATCH 1631/1897] Error: Uncaught exception in sharedProcess (fix #206035) (#206339) --- src/vs/base/parts/ipc/common/ipc.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index f943347519e..06aefade7ad 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -1093,6 +1093,9 @@ export namespace ProxyChannel { // Buffer any event that should be supported by // iterating over all property keys and finding them + // However, this will not work for services that + // are lazy and use a Proxy within. For that we + // still need to check later (see below). const mapEventNameToEvent = new Map>(); for (const key in handler) { if (propertyIsEvent(key)) { @@ -1108,11 +1111,17 @@ export namespace ProxyChannel { return eventImpl as Event; } - if (propertyIsDynamicEvent(event)) { - const target = handler[event]; - if (typeof target === 'function') { + const target = handler[event]; + if (typeof target === 'function') { + if (propertyIsDynamicEvent(event)) { return target.call(handler, arg); } + + if (propertyIsEvent(event)) { + mapEventNameToEvent.set(event, Event.buffer(handler[event] as Event, true, undefined, disposables)); + + return mapEventNameToEvent.get(event) as Event; + } } throw new ErrorNoTelemetry(`Event not found: ${event}`); From 0c3664116e7e594bbe5f6d4e5bfc032226d1b62e Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 27 Feb 2024 10:36:47 -0300 Subject: [PATCH 1632/1897] Warm up slash command cache- port candidate to main (#206302) Warm up slash command cache (#206290) Fix #206050 --- .../workbench/contrib/chat/common/chatAgents.ts | 15 +++++++++------ .../contrib/chat/common/chatServiceImpl.ts | 14 +++++++++++++- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 4ee4c07f0cf..b1ef64dae9d 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -107,7 +107,10 @@ export const IChatAgentService = createDecorator('chatAgentSe export interface IChatAgentService { _serviceBrand: undefined; - readonly onDidChangeAgents: Event; + /** + * undefined when an agent was removed + */ + readonly onDidChangeAgents: Event; registerAgent(agent: IChatAgent): IDisposable; invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise; @@ -127,8 +130,8 @@ export class ChatAgentService extends Disposable implements IChatAgentService { private readonly _agents = new Map(); - private readonly _onDidChangeAgents = this._register(new Emitter()); - readonly onDidChangeAgents: Event = this._onDidChangeAgents.event; + private readonly _onDidChangeAgents = this._register(new Emitter()); + readonly onDidChangeAgents: Event = this._onDidChangeAgents.event; override dispose(): void { super.dispose(); @@ -140,11 +143,11 @@ export class ChatAgentService extends Disposable implements IChatAgentService { throw new Error(`Already registered an agent with id ${agent.id}`); } this._agents.set(agent.id, { agent }); - this._onDidChangeAgents.fire(); + this._onDidChangeAgents.fire(agent); return toDisposable(() => { if (this._agents.delete(agent.id)) { - this._onDidChangeAgents.fire(); + this._onDidChangeAgents.fire(undefined); } }); } @@ -155,7 +158,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService { throw new Error(`No agent with id ${id} registered`); } data.agent.metadata = { ...data.agent.metadata, ...updateMetadata }; - this._onDidChangeAgents.fire(); + this._onDidChangeAgents.fire(data.agent); } getDefaultAgent(): IChatAgent | undefined { diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 6d89b981afc..e28b1e14e28 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -20,7 +20,7 @@ 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 { IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgent, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, ISerializableChatData, ISerializableChatsData, getHistoryEntriesFromModel, updateRanges } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; @@ -193,6 +193,12 @@ export class ChatService extends Disposable implements IChatService { } this._register(storageService.onWillSaveState(() => this.saveState())); + + this._register(Event.debounce(this.chatAgentService.onDidChangeAgents, () => { }, 500)(() => { + for (const model of this._sessionModels.values()) { + this.warmSlashCommandCache(model); + } + })); } private saveState(): void { @@ -358,9 +364,15 @@ export class ChatService extends Disposable implements IChatService { this.initializeSession(model, CancellationToken.None); } + private warmSlashCommandCache(model: IChatModel, agent?: IChatAgent) { + const agents = agent ? [agent] : this.chatAgentService.getAgents(); + agents.forEach(agent => agent.provideSlashCommands(model, [], CancellationToken.None)); + } + private async initializeSession(model: ChatModel, token: CancellationToken): Promise { try { this.trace('initializeSession', `Initialize session ${model.sessionId}`); + this.warmSlashCommandCache(model); model.startInitialize(); await this.extensionService.activateByEvent(`onInteractiveSession:${model.providerId}`); From 03bd0bb8d117dbe4ea13da19b03b82e8b70194a8 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 27 Feb 2024 15:16:20 +0100 Subject: [PATCH 1633/1897] Commenting range resource change proposal (#206346) Part of #185551 --- src/vs/editor/common/languages.ts | 8 +++++++ .../api/browser/mainThreadComments.ts | 8 +++++++ .../workbench/api/common/extHost.protocol.ts | 1 + .../workbench/api/common/extHostComments.ts | 11 +++++++++- .../comments/browser/commentService.ts | 21 ++++++++++++++++++- .../comments/browser/commentsController.ts | 7 ++++++- .../common/extensionsApiProposals.ts | 1 + ...posed.commentingRangeResourcesChanged.d.ts | 11 ++++++++++ 8 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.commentingRangeResourcesChanged.d.ts diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 16550bdef8d..01eb0df109e 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1888,6 +1888,14 @@ export interface CommentThreadChangedEvent { readonly changed: CommentThread[]; } +/** + * @internal + */ +export interface CommentingRangeResources { + schemes: string[]; + uris: URI[]; +} + export interface CodeLens { range: IRange; id?: string; diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 538c754e1b3..f885f89333a 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -550,6 +550,14 @@ export class MainThreadComments extends Disposable implements MainThreadComments provider.updateFeatures(features); } + async $onDidChangeResourcesWithCommentingRanges(handle: number, schemes: string[], uris: UriComponents[]): Promise { + const provider = this._commentControllers.get(handle); + if (!provider) { + return; + } + this._commentService.setResourcesWithCommentingRanges(provider.id, { schemes, uris: uris.map(uri => URI.revive(uri)) }); + } + $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 29b99b8cc41..b512d5da601 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -146,6 +146,7 @@ export interface MainThreadCommentsShape extends IDisposable { $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, changes: CommentThreadChanges): void; $deleteCommentThread(handle: number, commentThreadHandle: number): void; $updateCommentingRanges(handle: number): void; + $onDidChangeResourcesWithCommentingRanges(handle: number, schemes: string[], uris: UriComponents[]): Promise; } export interface AuthenticationForceNewSessionOptions { diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 0f04b3f2455..18764746b77 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -563,8 +563,17 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo return this._commentingRangeProvider; } + private _commentingRangeProviderResourcesChanged: vscode.Disposable | undefined; set commentingRangeProvider(provider: vscode.CommentingRangeProvider | undefined) { this._commentingRangeProvider = provider; + this._commentingRangeProviderResourcesChanged?.dispose(); + this._commentingRangeProviderResourcesChanged = undefined; + if (this._commentingRangeProvider?.onDidChangeResourcesWithCommentingRanges) { + checkProposedApiEnabled(this._extension, 'commentingRangeResourcesChanged'); + this._commentingRangeProviderResourcesChanged = this._commentingRangeProvider.onDidChangeResourcesWithCommentingRanges(e => { + proxy.$onDidChangeResourcesWithCommentingRanges(this.handle, e.schemes, e.resources); + }); + } proxy.$updateCommentingRanges(this.handle); } @@ -695,7 +704,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo this._threads.forEach(value => { value.dispose(); }); - + this._commentingRangeProviderResourcesChanged?.dispose(); this._localDisposables.forEach(disposable => disposable.dispose()); } } diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index 1072a60aedf..2d9dcdde5e6 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread, CommentOptions, PendingCommentThread } from 'vs/editor/common/languages'; +import { CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread, CommentOptions, PendingCommentThread, CommentingRangeResources } from 'vs/editor/common/languages'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; @@ -112,6 +112,8 @@ export interface ICommentService { enableCommenting(enable: boolean): void; registerContinueOnCommentProvider(provider: IContinueOnCommentProvider): IDisposable; removeContinueOnComment(pendingComment: { range: IRange | undefined; uri: URI; owner: string; isReply?: boolean }): PendingCommentThread | undefined; + setResourcesWithCommentingRanges(owner: string, resources: CommentingRangeResources): void; + resourceHasCommentingRanges(resource: URI): boolean; } const CONTINUE_ON_COMMENTS = 'comments.continueOnComments'; @@ -169,6 +171,8 @@ export class CommentService extends Disposable implements ICommentService { private readonly _commentsModel: CommentsModel = this._register(new CommentsModel()); public readonly commentsModel: ICommentsModel = this._commentsModel; + private _commentingRangeResources = new Map(); // owner -> CommentingRangeResources + constructor( @IInstantiationService protected readonly instantiationService: IInstantiationService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @@ -494,4 +498,19 @@ export class CommentService extends Disposable implements ICommentService { } return changedOwners; } + + setResourcesWithCommentingRanges(owner: string, resources: CommentingRangeResources): void { + this._commentingRangeResources.set(owner, resources); + } + + resourceHasCommentingRanges(resource: URI): boolean { + for (const resources of this._commentingRangeResources.values()) { + if (resources.schemes.includes(resource.scheme)) { + return true; + } else if (resources.uris.some(uri => uri.toString() === resource.toString())) { + return true; + } + } + return false; + } } diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index 4faf49116c0..1f795367c89 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -780,6 +780,7 @@ export class CommentController implements IEditorContribution { public onModelChanged(): void { this.localToDispose.clear(); + this.tryUpdateReservedSpace(); this.removeCommentWidgetsAndStoreCache(); if (!this.editor) { @@ -1194,10 +1195,14 @@ export class CommentController implements IEditorContribution { return; } - const hasCommentsOrRanges = this._commentInfos.some(info => { + const hasCommentsOrRangesInInfo = this._commentInfos.some(info => { const hasRanges = Boolean(info.commentingRanges && (Array.isArray(info.commentingRanges) ? info.commentingRanges : info.commentingRanges.ranges).length); return hasRanges || (info.threads.length > 0); }); + const uri = this.editor.getModel()?.uri; + const resourceHasCommentingRanges = uri ? this.commentService.resourceHasCommentingRanges(uri) : false; + + const hasCommentsOrRanges = hasCommentsOrRangesInInfo || resourceHasCommentingRanges; if (hasCommentsOrRanges && !this._commentingRangeSpaceReserved && this.commentService.isCommentingEnabled) { this._commentingRangeSpaceReserved = true; diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 76ffed1feeb..48e060c3e8f 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -20,6 +20,7 @@ export const allApiProposals = Object.freeze({ codeActionRanges: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts', codiconDecoration: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts', commentReactor: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentReactor.d.ts', + commentingRangeResourcesChanged: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentingRangeResourcesChanged.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', contribCommentPeekContext: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentPeekContext.d.ts', diff --git a/src/vscode-dts/vscode.proposed.commentingRangeResourcesChanged.d.ts b/src/vscode-dts/vscode.proposed.commentingRangeResourcesChanged.d.ts new file mode 100644 index 00000000000..7a4f22aecf7 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.commentingRangeResourcesChanged.d.ts @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * 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 CommentingRangeProvider { + readonly onDidChangeResourcesWithCommentingRanges?: Event<{ schemes: string[]; resources: Uri[] }>; + } +} From 3b8fe3ec81c6f49d109830db8b775beba23196af Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:55:13 +0100 Subject: [PATCH 1634/1897] Add custom hover to highlight labels and links (#206351) Add custom hover to highlight labels and Links --- .../browser/ui/actionbar/actionViewItems.ts | 2 +- .../ui/highlightedlabel/highlightedLabel.ts | 23 +++++++++++--- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 14 +++++---- src/vs/base/browser/ui/icons/iconSelectBox.ts | 2 +- .../test/browser/highlightedLabel.test.ts | 4 +++ .../gotoSymbol/browser/peek/referencesTree.ts | 9 ++++-- src/vs/platform/opener/browser/link.ts | 19 +++++++++--- .../bulkEdit/browser/preview/bulkEditTree.ts | 2 +- .../browser/outline/documentSymbolsTree.ts | 6 +++- .../contrib/debug/browser/baseDebugView.ts | 2 +- .../contrib/debug/browser/callStackView.ts | 7 +++-- .../contrib/debug/browser/replViewer.ts | 4 +-- .../contrib/debug/browser/variablesView.ts | 2 +- .../debug/test/browser/baseDebugView.test.ts | 4 +-- .../contrib/markers/browser/markersTable.ts | 31 +++++++++++++------ .../markers/browser/markersTreeViewer.ts | 24 ++++++++------ .../preferences/browser/keybindingsEditor.ts | 18 +++++++---- 17 files changed, 117 insertions(+), 56 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index 698d711f4dc..df6ec99660c 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -227,7 +227,7 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { this.updateAriaLabel(); if (this.options.hoverDelegate?.showNativeHover) { - /* While custom hover is not supported with context view */ + /* While custom hover is not inside custom hover */ this.element.title = title; } else { if (!this.customHover && title !== '') { diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index c2b41545d79..5aaa8bcc551 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -4,7 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { Disposable } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; /** @@ -22,13 +26,15 @@ export interface IHighlightedLabelOptions { * Whether the label supports rendering icons. */ readonly supportIcons?: boolean; + + readonly hoverDelegate?: IHoverDelegate; } /** * A widget which can render a label with substring highlights, often * originating from a filter function like the fuzzy matcher. */ -export class HighlightedLabel { +export class HighlightedLabel extends Disposable { private readonly domNode: HTMLElement; private text: string = ''; @@ -36,13 +42,16 @@ export class HighlightedLabel { private highlights: readonly IHighlight[] = []; private supportIcons: boolean; private didEverRender: boolean = false; + private customHover: ICustomHover | undefined; /** * Create a new {@link HighlightedLabel}. * * @param container The parent container to append to. */ - constructor(container: HTMLElement, options?: IHighlightedLabelOptions) { + constructor(container: HTMLElement, private readonly options?: IHighlightedLabelOptions) { + super(); + this.supportIcons = options?.supportIcons ?? false; this.domNode = dom.append(container, dom.$('span.monaco-highlighted-label')); } @@ -125,10 +134,16 @@ export class HighlightedLabel { dom.reset(this.domNode, ...children); - if (this.title) { + if (this.options?.hoverDelegate?.showNativeHover) { + /* While custom hover is not inside custom hover */ this.domNode.title = this.title; } else { - this.domNode.removeAttribute('title'); + if (!this.customHover && this.title !== '') { + const hoverDelegate = this.options?.hoverDelegate ?? getDefaultHoverDelegate('mouse'); + this.customHover = this._store.add(setupCustomHover(hoverDelegate, this.domNode, this.title)); + } else if (this.customHover) { + this.customHover.update(this.title); + } } this.didEverRender = true; diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 5bf35b8ffc2..c0e0544a93b 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -109,7 +109,7 @@ export class IconLabel extends Disposable { this.nameContainer = dom.append(this.labelContainer, dom.$('span.monaco-icon-name-container')); if (options?.supportHighlights || options?.supportIcons) { - this.nameNode = new LabelWithHighlights(this.nameContainer, !!options.supportIcons); + this.nameNode = this._register(new LabelWithHighlights(this.nameContainer, !!options.supportIcons)); } else { this.nameNode = new Label(this.nameContainer); } @@ -218,7 +218,7 @@ export class IconLabel extends Disposable { if (!this.descriptionNode) { const descriptionContainer = this._register(new FastLabelNode(dom.append(this.labelContainer, dom.$('span.monaco-icon-description-container')))); if (this.creationOptions?.supportDescriptionHighlights) { - this.descriptionNode = new HighlightedLabel(dom.append(descriptionContainer.element, dom.$('span.label-description')), { supportIcons: !!this.creationOptions.supportIcons }); + this.descriptionNode = this._register(new HighlightedLabel(dom.append(descriptionContainer.element, dom.$('span.label-description')), { supportIcons: !!this.creationOptions.supportIcons })); } else { this.descriptionNode = this._register(new FastLabelNode(dom.append(descriptionContainer.element, dom.$('span.label-description')))); } @@ -291,13 +291,15 @@ function splitMatches(labels: string[], separator: string, matches: readonly IMa }); } -class LabelWithHighlights { +class LabelWithHighlights extends Disposable { private label: string | string[] | undefined = undefined; private singleLabel: HighlightedLabel | undefined = undefined; private options: IIconLabelValueOptions | undefined; - constructor(private container: HTMLElement, private supportIcons: boolean) { } + constructor(private container: HTMLElement, private supportIcons: boolean) { + super(); + } setLabel(label: string | string[], options?: IIconLabelValueOptions): void { if (this.label === label && equals(this.options, options)) { @@ -311,7 +313,7 @@ class LabelWithHighlights { if (!this.singleLabel) { this.container.innerText = ''; this.container.classList.remove('multiple'); - this.singleLabel = new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId })), { supportIcons: this.supportIcons }); + this.singleLabel = this._register(new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId })), { supportIcons: this.supportIcons })); } this.singleLabel.set(label, options?.matches, undefined, options?.labelEscapeNewLines); @@ -329,7 +331,7 @@ class LabelWithHighlights { const id = options?.domId && `${options?.domId}_${i}`; const name = dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i, 'role': 'treeitem' }); - const highlightedLabel = new HighlightedLabel(dom.append(this.container, name), { supportIcons: this.supportIcons }); + const highlightedLabel = this._register(new HighlightedLabel(dom.append(this.container, name), { supportIcons: this.supportIcons })); highlightedLabel.set(l, m, undefined, options?.labelEscapeNewLines); if (i < label.length - 1) { diff --git a/src/vs/base/browser/ui/icons/iconSelectBox.ts b/src/vs/base/browser/ui/icons/iconSelectBox.ts index 465c1dc1181..b59529ffd81 100644 --- a/src/vs/base/browser/ui/icons/iconSelectBox.ts +++ b/src/vs/base/browser/ui/icons/iconSelectBox.ts @@ -81,7 +81,7 @@ export class IconSelectBox extends Disposable { dom.append(iconSelectBoxContainer, this.scrollableElement.getDomNode()); if (this.options.showIconInfo) { - this.iconIdElement = new HighlightedLabel(dom.append(dom.append(iconSelectBoxContainer, dom.$('.icon-select-id-container')), dom.$('.icon-select-id-label'))); + this.iconIdElement = this._register(new HighlightedLabel(dom.append(dom.append(iconSelectBoxContainer, dom.$('.icon-select-id-container')), dom.$('.icon-select-id-label')))); } const iconsDisposables = disposables.add(new MutableDisposable()); diff --git a/src/vs/base/test/browser/highlightedLabel.test.ts b/src/vs/base/test/browser/highlightedLabel.test.ts index 4f5eb5ca015..fe2ceb43d61 100644 --- a/src/vs/base/test/browser/highlightedLabel.test.ts +++ b/src/vs/base/test/browser/highlightedLabel.test.ts @@ -61,5 +61,9 @@ suite('HighlightedLabel', () => { assert.deepStrictEqual(highlights, [{ start: 5, end: 8 }, { start: 10, end: 11 }]); }); + teardown(() => { + label.dispose(); + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesTree.ts b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesTree.ts index ec127689cb0..bb76dd67d6c 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesTree.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesTree.ts @@ -162,12 +162,14 @@ export class FileReferencesRenderer implements ITreeRenderer, index: number, templateData: OneReferenceTemplate): void { templateData.set(node.element, node.filterData); } - disposeTemplate(): void { + disposeTemplate(templateData: OneReferenceTemplate): void { + templateData.dispose(); } } diff --git a/src/vs/platform/opener/browser/link.ts b/src/vs/platform/opener/browser/link.ts index 2b455fa8dc6..a52cad5c5f0 100644 --- a/src/vs/platform/opener/browser/link.ts +++ b/src/vs/platform/opener/browser/link.ts @@ -12,6 +12,8 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import 'vs/css!./link'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export interface ILinkDescriptor { readonly label: string | HTMLElement; @@ -28,6 +30,8 @@ export interface ILinkOptions { export class Link extends Disposable { private el: HTMLAnchorElement; + private hover?: ICustomHover; + private _enabled: boolean = true; get enabled(): boolean { @@ -68,9 +72,7 @@ export class Link extends Disposable { this.el.tabIndex = link.tabIndex; } - if (typeof link.title !== 'undefined') { - this.el.title = link.title; - } + this.setTooltip(link.title); this._link = link; } @@ -86,9 +88,10 @@ export class Link extends Disposable { this.el = append(container, $('a.monaco-link', { tabIndex: _link.tabIndex ?? 0, href: _link.href, - title: _link.title }, _link.label)); + this.setTooltip(_link.title); + this.el.setAttribute('role', 'button'); const onClickEmitter = this._register(new DomEmitter(this.el, 'click')); @@ -117,4 +120,12 @@ export class Link extends Disposable { this.enabled = true; } + + private setTooltip(title: string | undefined): void { + if (!this.hover && title) { + this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.el, title)); + } else if (this.hover) { + this.hover.update(title); + } + } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts index c5fd28d6a8e..e45a95008c3 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts @@ -580,7 +580,7 @@ class TextEditElementTemplate { this._icon = document.createElement('div'); container.appendChild(this._icon); - this._label = new HighlightedLabel(container); + this._label = this._disposables.add(new HighlightedLabel(container)); } dispose(): void { diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index c4f2a2def5a..3d9af339abe 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -66,6 +66,10 @@ class DocumentSymbolGroupTemplate { readonly labelContainer: HTMLElement, readonly label: HighlightedLabel, ) { } + + dispose() { + this.label.dispose(); + } } class DocumentSymbolTemplate { @@ -107,7 +111,7 @@ export class DocumentSymbolGroupRenderer implements ITreeRenderer implements IT templateDisposable.add(setupCustomHover(getDefaultHoverDelegate('mouse'), lazyButton, localize('debug.lazyButton.tooltip', "Click to expand"))); const value = dom.append(expression, $('span.value')); - const label = new HighlightedLabel(name); + const label = templateDisposable.add(new HighlightedLabel(name)); const inputBoxContainer = dom.append(expression, $('.inputBoxContainer')); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 139928ec9fe..b9d319bad85 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -541,8 +541,8 @@ class SessionsRenderer implements ICompressibleTreeRenderer { renderVariable(variable, { expression, name, value, label, lazyButton }, false, [], linkDetector); assert.strictEqual(value.textContent, 'hey'); assert.strictEqual(label.element.textContent, 'foo:'); - assert.strictEqual(label.element.title, 'string'); variable.value = isWindows ? 'C:\\foo.js:5' : '/foo.js:5'; expression = $('.'); @@ -122,8 +121,9 @@ suite('Debug - Base Debug View', () => { renderVariable(variable, { expression, name, value, label, lazyButton }, false, [], linkDetector); assert.strictEqual(name.className, 'virtual'); assert.strictEqual(label.element.textContent, 'console:'); - assert.strictEqual(label.element.title, 'console'); assert.strictEqual(value.className, 'value number'); + + label.dispose(); }); test('statusbar in debug mode', () => { diff --git a/src/vs/workbench/contrib/markers/browser/markersTable.ts b/src/vs/workbench/contrib/markers/browser/markersTable.ts index bb63d92e9d2..44467e7e01d 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTable.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTable.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; import { ITableContextMenuEvent, ITableEvent, ITableRenderer, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IOpenEvent, IWorkbenchTableOptions, WorkbenchTable } from 'vs/platform/list/browser/listService'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; @@ -43,6 +43,7 @@ interface IMarkerCodeColumnTemplateData { readonly sourceLabel: HighlightedLabel; readonly codeLabel: HighlightedLabel; readonly codeLink: Link; + readonly templateDisposable: DisposableStore; } interface IMarkerFileColumnTemplateData { @@ -121,17 +122,18 @@ class MarkerCodeColumnRenderer implements ITableRenderer { @@ -185,7 +189,9 @@ class MarkerMessageColumnRenderer implements ITableRenderer { @@ -216,7 +222,10 @@ class MarkerFileColumnRenderer implements ITableRenderer { @@ -236,7 +245,9 @@ class MarkerOwnerColumnRenderer implements ITableRenderer { diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 180a378ac81..a3556f80678 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -51,6 +51,8 @@ import { MarkersContextKeys, MarkersViewMode } from 'vs/workbench/contrib/marker import { unsupportedSchemas } from 'vs/platform/markers/common/markerService'; import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; import Severity from 'vs/base/common/severity'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; interface IResourceMarkersTemplateData { readonly resourceLabel: IResourceLabel; @@ -285,6 +287,7 @@ class MarkerWidget extends Disposable { private readonly icon: HTMLElement; private readonly iconContainer: HTMLElement; private readonly messageAndDetailsContainer: HTMLElement; + private readonly messageAndDetailsContainerHover: ICustomHover; private readonly disposables = this._register(new DisposableStore()); constructor( @@ -304,6 +307,7 @@ class MarkerWidget extends Disposable { this.iconContainer = dom.append(parent, dom.$('')); this.icon = dom.append(this.iconContainer, dom.$('')); this.messageAndDetailsContainer = dom.append(parent, dom.$('.marker-message-details-container')); + this.messageAndDetailsContainerHover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.messageAndDetailsContainer, '')); } render(element: Marker, filterData: MarkerFilterData | undefined): void { @@ -366,13 +370,13 @@ class MarkerWidget extends Disposable { const viewState = this.markersViewModel.getViewModel(element); const multiline = !viewState || viewState.multiline; const lineMatches = filterData && filterData.lineMatches || []; - this.messageAndDetailsContainer.title = element.marker.message; + this.messageAndDetailsContainerHover.update(element.marker.message); const lineElements: HTMLElement[] = []; for (let index = 0; index < (multiline ? lines.length : 1); index++) { const lineElement = dom.append(this.messageAndDetailsContainer, dom.$('.marker-message-line')); const messageElement = dom.append(lineElement, dom.$('.marker-message')); - const highlightedLabel = new HighlightedLabel(messageElement); + const highlightedLabel = this.disposables.add(new HighlightedLabel(messageElement)); highlightedLabel.set(lines[index].length > 1000 ? `${lines[index].substring(0, 1000)}...` : lines[index], lineMatches[index]); if (lines[index] === '') { lineElement.style.height = `${VirtualDelegate.LINE_HEIGHT}px`; @@ -387,18 +391,18 @@ class MarkerWidget extends Disposable { parent.classList.add('details-container'); if (marker.source || marker.code) { - const source = new HighlightedLabel(dom.append(parent, dom.$('.marker-source'))); + const source = this.disposables.add(new HighlightedLabel(dom.append(parent, dom.$('.marker-source')))); const sourceMatches = filterData && filterData.sourceMatches || []; source.set(marker.source, sourceMatches); if (marker.code) { if (typeof marker.code === 'string') { - const code = new HighlightedLabel(dom.append(parent, dom.$('.marker-code'))); + const code = this.disposables.add(new HighlightedLabel(dom.append(parent, dom.$('.marker-code')))); const codeMatches = filterData && filterData.codeMatches || []; code.set(marker.code, codeMatches); } else { const container = dom.$('.marker-code'); - const code = new HighlightedLabel(container); + const code = this.disposables.add(new HighlightedLabel(container)); const link = marker.code.target.toString(true); this.disposables.add(new Link(parent, { href: link, label: container, title: link }, undefined, this._openerService)); const codeMatches = filterData && filterData.codeMatches || []; @@ -443,15 +447,15 @@ export class RelatedInformationRenderer implements ITreeRenderer Date: Tue, 27 Feb 2024 10:10:08 -0600 Subject: [PATCH 1635/1897] progress on AI TextSearchProvider (#205319) These are the initial steps to having an API to contribute AI text results. --- .../workbench/api/browser/mainThreadSearch.ts | 24 +- .../workbench/api/common/extHost.api.impl.ts | 6 + .../workbench/api/common/extHost.protocol.ts | 2 + src/vs/workbench/api/common/extHostSearch.ts | 51 +++- .../api/test/node/extHostSearch.test.ts | 4 + .../search/test/browser/searchModel.test.ts | 26 +- .../common/extensionsApiProposals.ts | 1 + .../services/search/common/search.ts | 27 +- .../services/search/common/searchExtTypes.ts | 40 +++ .../services/search/common/searchService.ts | 78 +++++- .../search/common/textSearchManager.ts | 31 ++- .../services/search/node/textSearchManager.ts | 2 +- .../vscode.proposed.aiTextSearchProvider.d.ts | 261 ++++++++++++++++++ 13 files changed, 516 insertions(+), 37 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts diff --git a/src/vs/workbench/api/browser/mainThreadSearch.ts b/src/vs/workbench/api/browser/mainThreadSearch.ts index 797a4ee92a4..236148f08ff 100644 --- a/src/vs/workbench/api/browser/mainThreadSearch.ts +++ b/src/vs/workbench/api/browser/mainThreadSearch.ts @@ -9,7 +9,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { IFileMatch, IFileQuery, IRawFileMatch2, ISearchComplete, ISearchCompleteStats, ISearchProgressItem, ISearchResultProvider, ISearchService, ITextQuery, QueryType, SearchProviderType } from 'vs/workbench/services/search/common/search'; +import { IFileMatch, IFileQuery, IRawFileMatch2, ISearchComplete, ISearchCompleteStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, ITextQuery, QueryType, SearchProviderType } from 'vs/workbench/services/search/common/search'; import { ExtHostContext, ExtHostSearchShape, MainContext, MainThreadSearchShape } from '../common/extHost.protocol'; import { revive } from 'vs/base/common/marshalling'; @@ -38,6 +38,10 @@ export class MainThreadSearch implements MainThreadSearchShape { this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, SearchProviderType.text, scheme, handle, this._proxy)); } + $registerAITextSearchProvider(handle: number, scheme: string): void { + this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, SearchProviderType.aiText, scheme, handle, this._proxy)); + } + $registerFileSearchProvider(handle: number, scheme: string): void { this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, SearchProviderType.file, scheme, handle, this._proxy)); } @@ -64,7 +68,6 @@ export class MainThreadSearch implements MainThreadSearchShape { provider.handleFindMatch(session, data); } - $handleTelemetry(eventName: string, data: any): void { this._telemetryService.publicLog(eventName, data); } @@ -126,7 +129,7 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable { return this.doSearch(query, onProgress, token); } - doSearch(query: ITextQuery | IFileQuery, onProgress?: (p: ISearchProgressItem) => void, token: CancellationToken = CancellationToken.None): Promise { + doSearch(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void, token: CancellationToken = CancellationToken.None): Promise { if (!query.folderQueries.length) { throw new Error('Empty folderQueries'); } @@ -134,9 +137,7 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable { const search = new SearchOperation(onProgress); this._searches.set(search.id, search); - const searchP = query.type === QueryType.File - ? this._proxy.$provideFileSearchResults(this._handle, search.id, query, token) - : this._proxy.$provideTextSearchResults(this._handle, search.id, query, token); + const searchP = this._provideSearchResults(query, search.id, token); return Promise.resolve(searchP).then((result: ISearchCompleteStats) => { this._searches.delete(search.id); @@ -169,4 +170,15 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable { } }); } + + private _provideSearchResults(query: ISearchQuery, session: number, token: CancellationToken): Promise { + switch (query.type) { + case QueryType.File: + return this._proxy.$provideFileSearchResults(this._handle, session, query, token); + case QueryType.Text: + return this._proxy.$provideTextSearchResults(this._handle, session, query, token); + default: + return this._proxy.$provideAITextSearchResults(this._handle, session, query, token); + } + } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 65b718f3f05..20b128c9af4 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1112,6 +1112,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'textSearchProvider'); return extHostSearch.registerTextSearchProvider(scheme, provider); }, + registerAITextSearchProvider: (scheme: string, provider: vscode.AITextSearchProvider) => { + // there are some dependencies on textSearchProvider, so we need to check for both + checkProposedApiEnabled(extension, 'aiTextSearchProvider'); + checkProposedApiEnabled(extension, 'textSearchProvider'); + return extHostSearch.registerAITextSearchProvider(scheme, provider); + }, registerRemoteAuthorityResolver: (authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver) => { checkProposedApiEnabled(extension, 'resolvers'); return extensionService.registerRemoteAuthorityResolver(authorityPrefix, resolver); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b512d5da601..869fc7dc50d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1404,6 +1404,7 @@ export interface MainThreadLabelServiceShape extends IDisposable { export interface MainThreadSearchShape extends IDisposable { $registerFileSearchProvider(handle: number, scheme: string): void; + $registerAITextSearchProvider(handle: number, scheme: string): void; $registerTextSearchProvider(handle: number, scheme: string): void; $unregisterProvider(handle: number): void; $handleFileMatch(handle: number, session: number, data: UriComponents[]): void; @@ -1817,6 +1818,7 @@ export interface ExtHostSecretStateShape { export interface ExtHostSearchShape { $enableExtensionHostSearch(): void; $provideFileSearchResults(handle: number, session: number, query: search.IRawQuery, token: CancellationToken): Promise; + $provideAITextSearchResults(handle: number, session: number, query: search.IRawAITextQuery, token: CancellationToken): Promise; $provideTextSearchResults(handle: number, session: number, query: search.IRawTextQuery, token: CancellationToken): Promise; $clearCache(cacheKey: string): Promise; } diff --git a/src/vs/workbench/api/common/extHostSearch.ts b/src/vs/workbench/api/common/extHostSearch.ts index d0289669abf..c2e0b93f7b9 100644 --- a/src/vs/workbench/api/common/extHostSearch.ts +++ b/src/vs/workbench/api/common/extHostSearch.ts @@ -11,13 +11,14 @@ import { FileSearchManager } from 'vs/workbench/services/search/common/fileSearc import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { ILogService } from 'vs/platform/log/common/log'; -import { IRawFileQuery, ISearchCompleteStats, IFileQuery, IRawTextQuery, IRawQuery, ITextQuery, IFolderQuery } from 'vs/workbench/services/search/common/search'; +import { IRawFileQuery, ISearchCompleteStats, IFileQuery, IRawTextQuery, IRawQuery, ITextQuery, IFolderQuery, IRawAITextQuery, IAITextQuery } from 'vs/workbench/services/search/common/search'; import { URI, UriComponents } from 'vs/base/common/uri'; import { TextSearchManager } from 'vs/workbench/services/search/common/textSearchManager'; import { CancellationToken } from 'vs/base/common/cancellation'; export interface IExtHostSearch extends ExtHostSearchShape { registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider): IDisposable; + registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProvider): IDisposable; registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider): IDisposable; doInternalFileSearchWithCustomCallback(query: IFileQuery, token: CancellationToken, handleFileMatch: (data: URI[]) => void): Promise; } @@ -31,6 +32,10 @@ export class ExtHostSearch implements ExtHostSearchShape { private readonly _textSearchProvider = new Map(); private readonly _textSearchUsedSchemes = new Set(); + + private readonly _aiTextSearchProvider = new Map(); + private readonly _aiTextSearchUsedSchemes = new Set(); + private readonly _fileSearchProvider = new Map(); private readonly _fileSearchUsedSchemes = new Set(); @@ -62,6 +67,22 @@ export class ExtHostSearch implements ExtHostSearchShape { }); } + registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProvider): IDisposable { + if (this._aiTextSearchUsedSchemes.has(scheme)) { + throw new Error(`an AI text search provider for the scheme '${scheme}'is already registered`); + } + + this._aiTextSearchUsedSchemes.add(scheme); + const handle = this._handlePool++; + this._aiTextSearchProvider.set(handle, provider); + this._proxy.$registerAITextSearchProvider(handle, this._transformScheme(scheme)); + return toDisposable(() => { + this._aiTextSearchUsedSchemes.delete(scheme); + this._aiTextSearchProvider.delete(handle); + this._proxy.$unregisterProvider(handle); + }); + } + registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider): IDisposable { if (this._fileSearchUsedSchemes.has(scheme)) { throw new Error(`a file search provider for the scheme '${scheme}' is already registered`); @@ -86,7 +107,7 @@ export class ExtHostSearch implements ExtHostSearchShape { this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource)); }, token); } else { - throw new Error('unknown provider: ' + handle); + throw new Error('3 unknown provider: ' + handle); } } @@ -103,7 +124,7 @@ export class ExtHostSearch implements ExtHostSearchShape { $provideTextSearchResults(handle: number, session: number, rawQuery: IRawTextQuery, token: vscode.CancellationToken): Promise { const provider = this._textSearchProvider.get(handle); if (!provider || !provider.provideTextSearchResults) { - throw new Error(`Unknown provider ${handle}`); + throw new Error(`2 Unknown provider ${handle}`); } const query = reviveQuery(rawQuery); @@ -111,17 +132,35 @@ export class ExtHostSearch implements ExtHostSearchShape { return engine.search(progress => this._proxy.$handleTextMatch(handle, session, progress), token); } + $provideAITextSearchResults(handle: number, session: number, rawQuery: IRawAITextQuery, token: vscode.CancellationToken): Promise { + const provider = this._aiTextSearchProvider.get(handle); + if (!provider || !provider.provideAITextSearchResults) { + throw new Error(`1 Unknown provider ${handle}`); + } + + const query = reviveQuery(rawQuery); + const engine = this.createAITextSearchManager(query, provider); + return engine.search(progress => this._proxy.$handleTextMatch(handle, session, progress), token); + } + $enableExtensionHostSearch(): void { } protected createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager { - return new TextSearchManager(query, provider, { - readdir: resource => Promise.resolve([]), // TODO@rob implement + return new TextSearchManager({ query, provider }, { + readdir: resource => Promise.resolve([]), toCanonicalName: encoding => encoding }, 'textSearchProvider'); } + + protected createAITextSearchManager(query: IAITextQuery, provider: vscode.AITextSearchProvider): TextSearchManager { + return new TextSearchManager({ query, provider }, { + readdir: resource => Promise.resolve([]), + toCanonicalName: encoding => encoding + }, 'aiTextSearchProvider'); + } } -export function reviveQuery(rawQuery: U): U extends IRawTextQuery ? ITextQuery : IFileQuery { +export function reviveQuery(rawQuery: U): U extends IRawTextQuery ? ITextQuery : U extends IRawAITextQuery ? IAITextQuery : IFileQuery { return { ...rawQuery, // TODO@rob ??? ...{ diff --git a/src/vs/workbench/api/test/node/extHostSearch.test.ts b/src/vs/workbench/api/test/node/extHostSearch.test.ts index 1d903c40815..1502ef3f565 100644 --- a/src/vs/workbench/api/test/node/extHostSearch.test.ts +++ b/src/vs/workbench/api/test/node/extHostSearch.test.ts @@ -43,6 +43,10 @@ class MockMainThreadSearch implements MainThreadSearchShape { this.lastHandle = handle; } + $registerAITextSearchProvider(handle: number, scheme: string): void { + this.lastHandle = handle; + } + $unregisterProvider(handle: number): void { } diff --git a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts index 24e2448a9b3..17f9e88b338 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts @@ -14,7 +14,7 @@ import { ModelService } from 'vs/editor/common/services/modelService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, ISearchComplete, ISearchProgressItem, ISearchQuery, ISearchService, ITextQuery, ITextSearchMatch, OneLineRange, QueryType, TextSearchMatch } from 'vs/workbench/services/search/common/search'; +import { IAITextQuery, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, ISearchComplete, ISearchProgressItem, ISearchQuery, ISearchService, ITextQuery, ITextSearchMatch, OneLineRange, QueryType, TextSearchMatch } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { CellMatch, MatchInNotebook, SearchModel } from 'vs/workbench/contrib/search/browser/searchModel'; @@ -122,6 +122,14 @@ suite('SearchModel', () => { }); }, + aiTextSearch(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void, notebookURIs?: ResourceSet): Promise { + return new Promise(resolve => { + queueMicrotask(() => { + results.forEach(onProgress!); + resolve(complete!); + }); + }); + }, textSearchSplitSyncAsync(query: ITextQuery, token?: CancellationToken | undefined, onProgress?: ((result: ISearchProgressItem) => void) | undefined): { syncResults: ISearchComplete; asyncResults: Promise } { return { syncResults: { @@ -153,6 +161,11 @@ suite('SearchModel', () => { }); }); }, + aiTextSearch(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void, notebookURIs?: ResourceSet): Promise { + return new Promise((resolve, reject) => { + reject(error); + }); + }, textSearchSplitSyncAsync(query: ITextQuery, token?: CancellationToken | undefined, onProgress?: ((result: ISearchProgressItem) => void) | undefined): { syncResults: ISearchComplete; asyncResults: Promise } { return { syncResults: { @@ -188,6 +201,17 @@ suite('SearchModel', () => { }); }); }, + aiTextSearch(query: IAITextQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void, notebookURIs?: ResourceSet): Promise { + const disposable = token?.onCancellationRequested(() => tokenSource.cancel()); + if (disposable) { + store.add(disposable); + } + + return Promise.resolve({ + results: [], + messages: [] + }); + }, textSearchSplitSyncAsync(query: ITextQuery, token?: CancellationToken | undefined, onProgress?: ((result: ISearchProgressItem) => void) | undefined): { syncResults: ISearchComplete; asyncResults: Promise } { const disposable = token?.onCancellationRequested(() => tokenSource.cancel()); if (disposable) { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 48e060c3e8f..7e70af867c3 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -8,6 +8,7 @@ export const allApiProposals = Object.freeze({ activeComment: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.activeComment.d.ts', aiRelatedInformation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiRelatedInformation.d.ts', + aiTextSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts', 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', diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index ca93adc467b..0ebb5a9f69d 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -44,6 +44,7 @@ export const ISearchService = createDecorator('searchService'); export interface ISearchService { readonly _serviceBrand: undefined; textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): Promise; + aiTextSearch(query: IAITextQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): Promise; textSearchSplitSyncAsync(query: ITextQuery, token?: CancellationToken | undefined, onProgress?: ((result: ISearchProgressItem) => void) | undefined, notebookFilesToIgnore?: ResourceSet, asyncNotebookFilesToIgnore?: Promise): { syncResults: ISearchComplete; asyncResults: Promise }; fileSearch(query: IFileQuery, token?: CancellationToken): Promise; clearCache(cacheKey: string): Promise; @@ -55,7 +56,8 @@ export interface ISearchService { */ export const enum SearchProviderType { file, - text + text, + aiText } export interface ISearchResultProvider { @@ -123,17 +125,32 @@ export interface ITextQueryProps extends ICommonQueryPr userDisabledExcludesAndIgnoreFiles?: boolean; } +export interface IAITextQueryProps extends ICommonQueryProps { + type: QueryType.aiText; + contentPattern: string; + + previewOptions?: ITextSearchPreviewOptions; + maxFileSize?: number; + afterContext?: number; + beforeContext?: number; + + userDisabledExcludesAndIgnoreFiles?: boolean; +} + export type IFileQuery = IFileQueryProps; export type IRawFileQuery = IFileQueryProps; export type ITextQuery = ITextQueryProps; export type IRawTextQuery = ITextQueryProps; +export type IAITextQuery = IAITextQueryProps; +export type IRawAITextQuery = IAITextQueryProps; -export type IRawQuery = IRawTextQuery | IRawFileQuery; -export type ISearchQuery = ITextQuery | IFileQuery; +export type IRawQuery = IRawTextQuery | IRawFileQuery | IRawAITextQuery; +export type ISearchQuery = ITextQuery | IFileQuery | IAITextQuery; export const enum QueryType { File = 1, - Text = 2 + Text = 2, + aiText = 3 } /* __GDPR__FRAGMENT__ @@ -249,7 +266,7 @@ export const enum SearchCompletionExitCode { } export interface ITextSearchStats { - type: 'textSearchProvider' | 'searchProcess'; + type: 'textSearchProvider' | 'searchProcess' | 'aiTextSearchProvider'; } export interface IFileSearchStats { diff --git a/src/vs/workbench/services/search/common/searchExtTypes.ts b/src/vs/workbench/services/search/common/searchExtTypes.ts index 6d037174bed..48da6b3f96d 100644 --- a/src/vs/workbench/services/search/common/searchExtTypes.ts +++ b/src/vs/workbench/services/search/common/searchExtTypes.ts @@ -221,6 +221,35 @@ export interface TextSearchOptions extends SearchOptions { */ afterContext?: number; } +/** + * Options that apply to AI text search. + */ +export interface AITextSearchOptions extends SearchOptions { + /** + * The maximum number of results to be returned. + */ + maxResults: number; + + /** + * Options to specify the size of the result text preview. + */ + previewOptions?: TextSearchPreviewOptions; + + /** + * Exclude files larger than `maxFileSize` in bytes. + */ + maxFileSize?: number; + + /** + * Number of lines of context to include before each match. + */ + beforeContext?: number; + + /** + * Number of lines of context to include after each match. + */ + afterContext?: number; +} /** * Represents the severity of a TextSearchComplete message. @@ -390,6 +419,17 @@ export interface TextSearchProvider { provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: IProgress, token: CancellationToken): ProviderResult; } +export interface AITextSearchProvider { + /** + * Provide results that match the given text pattern. + * @param query The parameter for this query. + * @param options A set of options to consider while searching. + * @param progress A progress callback that must be invoked for all results. + * @param token A cancellation token. + */ + provideAITextSearchResults(query: string, options: AITextSearchOptions, progress: IProgress, token: CancellationToken): ProviderResult; +} + /** * Options that can be set on a findTextInFiles search. */ diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index ac934456627..42ffb1111ed 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -21,7 +21,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, isFileMatch, isProgressMessage, ITextQuery, pathIncludedInQuery, QueryType, SEARCH_RESULT_LANGUAGE_ID, SearchError, SearchErrorCode, SearchProviderType } from 'vs/workbench/services/search/common/search'; +import { deserializeSearchError, FileMatch, IAITextQuery, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, isFileMatch, isProgressMessage, ITextQuery, pathIncludedInQuery, QueryType, SEARCH_RESULT_LANGUAGE_ID, SearchError, SearchErrorCode, SearchProviderType } from 'vs/workbench/services/search/common/search'; import { getTextSearchMatchWithModelContext, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; export class SearchService extends Disposable implements ISearchService { @@ -30,9 +30,11 @@ export class SearchService extends Disposable implements ISearchService { private readonly fileSearchProviders = new Map(); private readonly textSearchProviders = new Map(); + private readonly aiTextSearchProviders = new Map(); private deferredFileSearchesByScheme = new Map>(); private deferredTextSearchesByScheme = new Map>(); + private deferredAITextSearchesByScheme = new Map>(); private loggedSchemesMissingProviders = new Set(); @@ -57,6 +59,9 @@ export class SearchService extends Disposable implements ISearchService { } else if (type === SearchProviderType.text) { list = this.textSearchProviders; deferredMap = this.deferredTextSearchesByScheme; + } else if (type === SearchProviderType.aiText) { + list = this.aiTextSearchProviders; + deferredMap = this.deferredAITextSearchesByScheme; } else { throw new Error('Unknown SearchProviderType'); } @@ -84,6 +89,24 @@ export class SearchService extends Disposable implements ISearchService { }; } + async aiTextSearch(query: IAITextQuery, token?: CancellationToken, onProgress?: (item: ISearchProgressItem) => void): Promise { + const onProviderProgress = (progress: ISearchProgressItem) => { + // Match + if (onProgress) { // don't override open editor results + if (isFileMatch(progress)) { + onProgress(progress); + } else { + onProgress(progress); + } + } + + if (isProgressMessage(progress)) { + this.logService.debug('SearchService#search', progress.message); + } + }; + return this.doSearch(query, token, onProviderProgress); + } + textSearchSplitSyncAsync( query: ITextQuery, token?: CancellationToken | undefined, @@ -205,9 +228,7 @@ export class SearchService extends Disposable implements ISearchService { } private async waitForProvider(queryType: QueryType, scheme: string): Promise { - const deferredMap: Map> = queryType === QueryType.File ? - this.deferredFileSearchesByScheme : - this.deferredTextSearchesByScheme; + const deferredMap: Map> = this.getDeferredTextSearchesByScheme(queryType); if (deferredMap.has(scheme)) { return deferredMap.get(scheme)!.p; @@ -218,6 +239,32 @@ export class SearchService extends Disposable implements ISearchService { } } + private getSearchProvider(type: QueryType): Map { + switch (type) { + case QueryType.File: + return this.fileSearchProviders; + case QueryType.Text: + return this.textSearchProviders; + case QueryType.aiText: + return this.aiTextSearchProviders; + default: + throw new Error(`Unknown query type: ${type}`); + } + } + + private getDeferredTextSearchesByScheme(type: QueryType): Map> { + switch (type) { + case QueryType.File: + return this.deferredFileSearchesByScheme; + case QueryType.Text: + return this.deferredTextSearchesByScheme; + case QueryType.aiText: + return this.deferredAITextSearchesByScheme; + default: + throw new Error(`Unknown query type: ${type}`); + } + } + private async searchWithProviders(query: ISearchQuery, onProviderProgress: (progress: ISearchProgressItem) => void, token?: CancellationToken) { const e2eSW = StopWatch.create(false); @@ -225,16 +272,12 @@ export class SearchService extends Disposable implements ISearchService { const fqs = this.groupFolderQueriesByScheme(query); const someSchemeHasProvider = [...fqs.keys()].some(scheme => { - return query.type === QueryType.File ? - this.fileSearchProviders.has(scheme) : - this.textSearchProviders.has(scheme); + return this.getSearchProvider(query.type).has(scheme); }); await Promise.all([...fqs.keys()].map(async scheme => { const schemeFQs = fqs.get(scheme)!; - let provider = query.type === QueryType.File ? - this.fileSearchProviders.get(scheme) : - this.textSearchProviders.get(scheme); + let provider = this.getSearchProvider(query.type).get(scheme); if (!provider) { if (someSchemeHasProvider) { @@ -259,9 +302,18 @@ export class SearchService extends Disposable implements ISearchService { } }; - searchPs.push(query.type === QueryType.File ? - provider.fileSearch(oneSchemeQuery, token) : - provider.textSearch(oneSchemeQuery, onProviderProgress, token)); + const doProviderSearch = () => { + switch (query.type) { + case QueryType.File: + return provider.fileSearch(oneSchemeQuery, token); + case QueryType.Text: + return provider.textSearch(oneSchemeQuery, onProviderProgress, token); + default: + return provider.textSearch(oneSchemeQuery, onProviderProgress, token); + } + }; + + searchPs.push(doProviderSearch()); })); return Promise.all(searchPs).then(completes => { diff --git a/src/vs/workbench/services/search/common/textSearchManager.ts b/src/vs/workbench/services/search/common/textSearchManager.ts index cb4af3dd4e9..eb83a7ae27e 100644 --- a/src/vs/workbench/services/search/common/textSearchManager.ts +++ b/src/vs/workbench/services/search/common/textSearchManager.ts @@ -11,14 +11,20 @@ import { Schemas } from 'vs/base/common/network'; import * as path from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { hasSiblingPromiseFn, IExtendedExtensionSearchOptions, IFileMatch, IFolderQuery, IPatternInfo, ISearchCompleteStats, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchResult, ITextSearchStats, QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/common/search'; -import { Range, TextSearchComplete, TextSearchMatch, TextSearchOptions, TextSearchProvider, TextSearchQuery, TextSearchResult } from 'vs/workbench/services/search/common/searchExtTypes'; +import { hasSiblingPromiseFn, IAITextQuery, IExtendedExtensionSearchOptions, IFileMatch, IFolderQuery, IPatternInfo, ISearchCompleteStats, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchResult, ITextSearchStats, QueryGlobTester, QueryType, resolvePatternsForProvider } from 'vs/workbench/services/search/common/search'; +import { AITextSearchProvider, Range, TextSearchComplete, TextSearchMatch, TextSearchOptions, TextSearchProvider, TextSearchQuery, TextSearchResult } from 'vs/workbench/services/search/common/searchExtTypes'; export interface IFileUtils { readdir: (resource: URI) => Promise; toCanonicalName: (encoding: string) => string; } +interface IAITextQueryProviderPair { + query: IAITextQuery; provider: AITextSearchProvider; +} +interface ITextQueryProviderPair { + query: ITextQuery; provider: TextSearchProvider; +} export class TextSearchManager { private collector: TextSearchResultsCollector | null = null; @@ -26,7 +32,13 @@ export class TextSearchManager { private isLimitHit = false; private resultCount = 0; - constructor(private query: ITextQuery, private provider: TextSearchProvider, private fileUtils: IFileUtils, private processType: ITextSearchStats['type']) { } + constructor(private queryProviderPair: IAITextQueryProviderPair | ITextQueryProviderPair, + private fileUtils: IFileUtils, + private processType: ITextSearchStats['type']) { } + + private get query() { + return this.queryProviderPair.query; + } search(onProgress: (matches: IFileMatch[]) => void, token: CancellationToken): Promise { const folderQueries = this.query.folderQueries || []; @@ -146,7 +158,14 @@ export class TextSearchManager { }; const searchOptions = this.getSearchOptionsForFolder(folderQuery); - const result = await this.provider.provideTextSearchResults(patternInfoToQuery(this.query.contentPattern), searchOptions, progress, token); + + + let result; + if (this.queryProviderPair.query.type === QueryType.aiText) { + result = await (this.queryProviderPair as IAITextQueryProviderPair).provider.provideAITextSearchResults(this.queryProviderPair.query.contentPattern, searchOptions, progress, token); + } else { + result = await (this.queryProviderPair as ITextQueryProviderPair).provider.provideTextSearchResults(patternInfoToQuery(this.queryProviderPair.query.contentPattern), searchOptions, progress, token); + } if (testingPs.length) { await Promise.all(testingPs); } @@ -196,7 +215,9 @@ export class TextSearchManager { afterContext: this.query.afterContext, beforeContext: this.query.beforeContext }; - (options).usePCRE2 = this.query.usePCRE2; + if ('usePCRE2' in this.query) { + (options).usePCRE2 = this.query.usePCRE2; + } return options; } } diff --git a/src/vs/workbench/services/search/node/textSearchManager.ts b/src/vs/workbench/services/search/node/textSearchManager.ts index 42cb5e59e80..34cf4cce311 100644 --- a/src/vs/workbench/services/search/node/textSearchManager.ts +++ b/src/vs/workbench/services/search/node/textSearchManager.ts @@ -12,7 +12,7 @@ import { TextSearchManager } from 'vs/workbench/services/search/common/textSearc export class NativeTextSearchManager extends TextSearchManager { constructor(query: ITextQuery, provider: TextSearchProvider, _pfs: typeof pfs = pfs, processType: ITextSearchStats['type'] = 'searchProcess') { - super(query, provider, { + super({ query, provider }, { readdir: resource => _pfs.Promises.readdir(resource.fsPath), toCanonicalName: name => toCanonicalName(name) }, processType); diff --git a/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts b/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts new file mode 100644 index 00000000000..93f9761211b --- /dev/null +++ b/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts @@ -0,0 +1,261 @@ +/*--------------------------------------------------------------------------------------------- + * 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/205317 + + /** + * The parameters of a query for text search. + */ + export interface TextSearchQuery { + /** + * The text pattern to search for. + */ + pattern: string; + + /** + * Whether or not `pattern` should match multiple lines of text. + */ + isMultiline?: boolean; + + /** + * Whether or not `pattern` should be interpreted as a regular expression. + */ + isRegExp?: boolean; + + /** + * Whether or not the search should be case-sensitive. + */ + isCaseSensitive?: boolean; + + /** + * Whether or not to search for whole word matches only. + */ + isWordMatch?: boolean; + } + + /** + * Options common to file and text search + */ + export interface SearchOptions { + /** + * The root folder to search within. + */ + folder: Uri; + + /** + * Files that match an `includes` glob pattern should be included in the search. + */ + includes: GlobString[]; + + /** + * Files that match an `excludes` glob pattern should be excluded from the search. + */ + excludes: GlobString[]; + + /** + * Whether external files that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useIgnoreFiles"`. + */ + useIgnoreFiles: boolean; + + /** + * Whether symlinks should be followed while searching. + * See the vscode setting `"search.followSymlinks"`. + */ + followSymlinks: boolean; + + /** + * Whether global files that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useGlobalIgnoreFiles"`. + */ + useGlobalIgnoreFiles: boolean; + + /** + * Whether files in parent directories that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useParentIgnoreFiles"`. + */ + useParentIgnoreFiles: boolean; + } + + /** + * Options to specify the size of the result text preview. + * These options don't affect the size of the match itself, just the amount of preview text. + */ + export interface TextSearchPreviewOptions { + /** + * The maximum number of lines in the preview. + * Only search providers that support multiline search will ever return more than one line in the match. + */ + matchLines: number; + + /** + * The maximum number of characters included per line. + */ + charsPerLine: number; + } + + /** + * Options that apply to AI text search. + */ + export interface AITextSearchOptions extends SearchOptions { + /** + * The maximum number of results to be returned. + */ + maxResults: number; + + /** + * Options to specify the size of the result text preview. + */ + previewOptions?: TextSearchPreviewOptions; + + /** + * Exclude files larger than `maxFileSize` in bytes. + */ + maxFileSize?: number; + + /** + * Number of lines of context to include before each match. + */ + beforeContext?: number; + + /** + * Number of lines of context to include after each match. + */ + afterContext?: number; + } + + + + /** + * A message regarding a completed search. + */ + export interface TextSearchCompleteMessage { + /** + * Markdown text of the message. + */ + text: string; + /** + * Whether the source of the message is trusted, command links are disabled for untrusted message sources. + * Messaged are untrusted by default. + */ + trusted?: boolean; + /** + * The message type, this affects how the message will be rendered. + */ + type: TextSearchCompleteMessageType; + } + + /** + * Information collected when text search is complete. + */ + export interface TextSearchComplete { + /** + * Whether the search hit the limit on the maximum number of search results. + * `maxResults` on {@linkcode AITextSearchOptions} specifies the max number of results. + * - If exactly that number of matches exist, this should be false. + * - If `maxResults` matches are returned and more exist, this should be true. + * - If search hits an internal limit which is less than `maxResults`, this should be true. + */ + limitHit?: boolean; + + /** + * Additional information regarding the state of the completed search. + * + * Messages with "Information" style support links in markdown syntax: + * - Click to [run a command](command:workbench.action.OpenQuickPick) + * - Click to [open a website](https://aka.ms) + * + * Commands may optionally return { triggerSearch: true } to signal to the editor that the original search should run be again. + */ + message?: TextSearchCompleteMessage | TextSearchCompleteMessage[]; + } + + /** + * A preview of the text result. + */ + export interface TextSearchMatchPreview { + /** + * The matching lines of text, or a portion of the matching line that contains the match. + */ + text: string; + + /** + * The Range within `text` corresponding to the text of the match. + * The number of matches must match the TextSearchMatch's range property. + */ + matches: Range | Range[]; + } + + /** + * A match from a text search + */ + export interface TextSearchMatch { + /** + * The uri for the matching document. + */ + uri: Uri; + + /** + * The range of the match within the document, or multiple ranges for multiple matches. + */ + ranges: Range | Range[]; + + /** + * A preview of the text match. + */ + preview: TextSearchMatchPreview; + } + + /** + * A line of context surrounding a TextSearchMatch. + */ + export interface TextSearchContext { + /** + * The uri for the matching document. + */ + uri: Uri; + + /** + * One line of text. + * previewOptions.charsPerLine applies to this + */ + text: string; + + /** + * The line number of this line of context. + */ + lineNumber: number; + } + + + /** + * An AITextSearchProvider provides additional AI text search results in the workspace. + */ + export interface AITextSearchProvider { + /** + * Provide results that match the given text pattern. + * @param query The parameter for this query. + * @param options A set of options to consider while searching. + * @param progress A progress callback that must be invoked for all results. + * @param token A cancellation token. + */ + provideAITextSearchResults(query: string, options: AITextSearchOptions, progress: Progress, token: CancellationToken): ProviderResult; + } + + export namespace workspace { + /** + * Register an AI text search provider. + * + * Only one provider can be registered per scheme. + * + * @param scheme The provider will be invoked for workspace folders that have this file scheme. + * @param provider The provider. + * @return A {@link Disposable} that unregisters this provider when being disposed. + */ + export function registerAITextSearchProvider(scheme: string, provider: AITextSearchProvider): Disposable; + } +} From 1025d0dafbc2ab15f18f7e74fd0850ce38ba414e Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 27 Feb 2024 17:33:49 +0100 Subject: [PATCH 1636/1897] Render hovers over context views (#206353) render hovers over context views --- src/vs/base/browser/ui/contextview/contextview.ts | 5 ++++- src/vs/editor/browser/services/hoverService/hoverService.ts | 3 +++ src/vs/platform/contextview/browser/contextView.ts | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index af49847a810..a7debba4e6c 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -60,6 +60,9 @@ export interface IDelegate { canRelayout?: boolean; // default: true onDOMEvent?(e: Event, activeElement: HTMLElement): void; onHide?(data?: unknown): void; + + // context views with higher layers are rendered over contet views with lower layers + layer?: number; // Default: 0 } export interface IContextViewProvider { @@ -222,7 +225,7 @@ export class ContextView extends Disposable { this.view.className = 'context-view'; this.view.style.top = '0px'; this.view.style.left = '0px'; - this.view.style.zIndex = '2575'; + this.view.style.zIndex = `${2575 + (delegate.layer ?? 0)}`; this.view.style.position = this.useFixedPosition ? 'fixed' : 'absolute'; DOM.show(this.view); diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index f3d6b5c9f16..38357608b86 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -194,6 +194,9 @@ function getHoverOptionsIdentity(options: IHoverOptions | undefined): IHoverOpti class HoverContextViewDelegate implements IDelegate { + // Render over all other context views + public readonly layer = 1; + get anchorPosition() { return this._hover.anchor; } diff --git a/src/vs/platform/contextview/browser/contextView.ts b/src/vs/platform/contextview/browser/contextView.ts index 10158c8d75c..87c58811d8d 100644 --- a/src/vs/platform/contextview/browser/contextView.ts +++ b/src/vs/platform/contextview/browser/contextView.ts @@ -43,6 +43,9 @@ export interface IContextViewDelegate { focus?(): void; anchorAlignment?: AnchorAlignment; anchorAxisAlignment?: AnchorAxisAlignment; + + // context views with higher layers are rendered over contet views with lower layers + layer?: number; // Default: 0 } export const IContextMenuService = createDecorator('contextMenuService'); From ef300d9945b03454b84ac03d7e4562f3e9c39d75 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 27 Feb 2024 09:04:54 -0800 Subject: [PATCH 1637/1897] Fix extra background in markdown code blocks (#206304) Fixes #205129 --- .../contrib/markdown/browser/markdownDocumentRenderer.ts | 4 ++-- .../workbench/contrib/webview/browser/pre/index-no-csp.html | 4 ++++ src/vs/workbench/contrib/webview/browser/pre/index.html | 6 +++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts index 8ac086cada8..9b18b5cd028 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts @@ -205,7 +205,7 @@ export async function renderMarkdownDocument( } if (typeof lang !== 'string') { - callback(null, `${escape(code)}`); + callback(null, escape(code)); return ''; } @@ -217,7 +217,7 @@ export async function renderMarkdownDocument( const languageId = languageService.getLanguageIdByLanguageName(lang) ?? languageService.getLanguageIdByLanguageName(lang.split(/\s+|:|,|(?!^)\{|\?]/, 1)[0]); const html = await tokenizeToString(languageService, code, languageId); - callback(null, `${html}`); + callback(null, html); }); return ''; }; 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 8b22da14204..45a4086cc9e 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 @@ -128,6 +128,10 @@ border-radius: 4px; } + pre code { + padding: 0; + } + blockquote { background: var(--vscode-textBlockQuote-background); border-color: var(--vscode-textBlockQuote-border); diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index 277a619ddc7..0bf6401663d 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-zUToXLPvfhhGwJ9G2aKXxI1DL+LnafBpNyx1mQ/S5qU=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> Date: Tue, 27 Feb 2024 18:14:05 +0100 Subject: [PATCH 1638/1897] refine language model (#206358) * api - use `LanguageModelChat` prefix for messages and response, add todos * update todos, tweak how `chatRequest` errors, remove `LanguageModelChatResponse#result` * api - refine language model access removes `requestLanguageModelAccess`, removes `LanguageModelChatResponse#result`, adds `LanguageModelChatRequestOptions`, refines how errors happen when making a chat request * use `throw` over Promise.reject * don't error from `_getAuthAccess`, polish error messages * rename to `sendChatRequest` --- .../workbench/api/common/extHost.api.impl.ts | 29 +-- .../api/common/extHostLanguageModels.ts | 189 ++++++++---------- .../api/common/extHostTypeConverters.ts | 16 +- src/vs/workbench/api/common/extHostTypes.ts | 6 +- .../vscode.proposed.chatProvider.d.ts | 2 +- .../vscode.proposed.languageModels.d.ts | 144 +++++-------- 6 files changed, 158 insertions(+), 228 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 20b128c9af4..1b9c7eb2b08 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { combinedDisposable } from 'vs/base/common/lifecycle'; @@ -1431,10 +1431,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // namespace: lm const lm: typeof vscode.lm = { - requestLanguageModelAccess(id, options) { - checkProposedApiEnabled(extension, 'languageModels'); - return extHostChatProvider.requestLanguageModelAccess(extension, id, options); - }, get languageModels() { checkProposedApiEnabled(extension, 'languageModels'); return extHostChatProvider.getLanguageModelIds(); @@ -1443,19 +1439,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'languageModels'); return extHostChatProvider.onDidChangeProviders(listener, thisArgs, disposables); }, - chatRequest(languageModel: string, messages: vscode.LanguageModelMessage[], optionsOrToken: { [name: string]: any } | vscode.CancellationToken, token?: vscode.CancellationToken) { + sendChatRequest(languageModel: string, messages: vscode.LanguageModelChatMessage[], options: vscode.LanguageModelChatRequestOptions, token: vscode.CancellationToken) { checkProposedApiEnabled(extension, 'languageModels'); - let options: Record; - if (CancellationToken.isCancellationToken(optionsOrToken)) { - options = {}; - token = optionsOrToken; - } else if (CancellationToken.isCancellationToken(token)) { - options = optionsOrToken; - token = token; - } else { - throw new Error('Invalid arguments'); - } - return extHostChatProvider.makeChatRequest(extension, languageModel, messages, options, token); + return extHostChatProvider.sendChatRequest(extension, languageModel, messages, options, token); } }; @@ -1699,9 +1685,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ChatResponseCommandButtonPart: extHostTypes.ChatResponseCommandButtonPart, ChatRequestTurn: extHostTypes.ChatRequestTurn, ChatResponseTurn: extHostTypes.ChatResponseTurn, - LanguageModelSystemMessage: extHostTypes.LanguageModelSystemMessage, - LanguageModelUserMessage: extHostTypes.LanguageModelUserMessage, - LanguageModelAssistantMessage: extHostTypes.LanguageModelAssistantMessage, + LanguageModelChatSystemMessage: extHostTypes.LanguageModelChatSystemMessage, + LanguageModelChatUserMessage: extHostTypes.LanguageModelChatUserMessage, + LanguageModelChatAssistantMessage: extHostTypes.LanguageModelChatAssistantMessage, + LanguageModelSystemMessage: extHostTypes.LanguageModelChatSystemMessage, + LanguageModelUserMessage: extHostTypes.LanguageModelChatUserMessage, + LanguageModelAssistantMessage: extHostTypes.LanguageModelChatAssistantMessage, NewSymbolName: extHostTypes.NewSymbolName, NewSymbolNameTag: extHostTypes.NewSymbolNameTag, InlineEdit: extHostTypes.InlineEdit, diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index aa45f8f58aa..e51c875984d 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -3,7 +3,7 @@ * 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 { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostLanguageModelsShape, IMainContext, MainContext, MainThreadLanguageModelsShape } from 'vs/workbench/api/common/extHost.protocol'; @@ -12,12 +12,12 @@ import type * as vscode from 'vscode'; import { Progress } from 'vs/platform/progress/common/progress'; import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata } from 'vs/workbench/contrib/chat/common/languageModels'; import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { AsyncIterableSource } from 'vs/base/common/async'; -import { Emitter, Event } from 'vs/base/common/event'; +import { AsyncIterableSource, Barrier } from 'vs/base/common/async'; +import { Emitter } from 'vs/base/common/event'; import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; import { localize } from 'vs/nls'; import { INTERNAL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { CancellationError } from 'vs/base/common/errors'; type LanguageModelData = { readonly extension: ExtensionIdentifier; @@ -36,43 +36,23 @@ class LanguageModelResponseStream { } } -class LanguageModelRequest { +class LanguageModelResponse { - static fromError(err: Error): vscode.LanguageModelResponse { - return new LanguageModelRequest(Promise.reject(err), new CancellationTokenSource()).apiObject; - } - - readonly apiObject: vscode.LanguageModelResponse; + readonly apiObject: vscode.LanguageModelChatResponse; private readonly _responseStreams = new Map(); private readonly _defaultStream = new AsyncIterableSource(); private _isDone: boolean = false; + private _isStreaming: boolean = false; + + constructor() { - constructor( - promise: Promise, - readonly cts: CancellationTokenSource - ) { const that = this; this.apiObject = { - result: promise, + // result: promise, stream: that._defaultStream.asyncIterable, - // responses: AsyncIterable[] // FUTURE responses per N + // streams: AsyncIterable[] // FUTURE responses per N }; - - promise.then(() => { - for (const stream of this._streams()) { - stream.resolve(); - } - }).catch(err => { - if (!(err instanceof Error)) { - err = new Error(toErrorMessage(err), { cause: err }); - } - for (const stream of this._streams()) { - stream.reject(err); - } - }).finally(() => { - this._isDone = true; - }); } private * _streams() { @@ -89,6 +69,7 @@ class LanguageModelRequest { if (this._isDone) { return; } + this._isStreaming = true; let res = this._responseStreams.get(fragment.index); if (!res) { if (this._responseStreams.size === 0) { @@ -102,6 +83,24 @@ class LanguageModelRequest { res.stream.emitOne(fragment.part); } + get isStreaming(): boolean { + return this._isStreaming; + } + + reject(err: Error): void { + this._isDone = true; + for (const stream of this._streams()) { + stream.reject(err); + } + } + + resolve(): void { + this._isDone = true; + for (const stream of this._streams()) { + stream.resolve(); + } + } + } export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { @@ -116,7 +115,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { private readonly _languageModels = new Map(); private readonly _languageModelIds = new Set(); // these are ALL models, not just the one in this EH private readonly _modelAccessList = new ExtensionIdentifierMap(); - private readonly _pendingRequest = new Map(); + private readonly _pendingRequest = new Map(); constructor( @@ -191,7 +190,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { // cancel pending requests for this model for (const [key, value] of this._pendingRequest) { if (value.languageModelId === id) { - value.res.cts.cancel(); + value.res.reject(new CancellationError()); this._pendingRequest.delete(key); } } @@ -227,88 +226,56 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { } } - async makeChatRequest(extension: IExtensionDescription, languageModelId: string, messages: vscode.LanguageModelMessage[], options: Record, token: CancellationToken) { + async sendChatRequest(extension: IExtensionDescription, languageModelId: string, messages: vscode.LanguageModelChatMessage[], options: vscode.LanguageModelChatRequestOptions, token: CancellationToken) { const from = extension.identifier; - // const justification = options?.justification; // TODO@jrieken - const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, undefined); + const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, options.justification); if (!metadata || !this._languageModelIds.has(languageModelId)) { - return LanguageModelRequest.fromError(new Error(`Language model ${languageModelId} is unknown`)); + throw new Error(`Language model '${languageModelId}' is unknown.`); } if (this._isUsingAuth(from, metadata)) { - await this._getAuthAccess(extension, { identifier: metadata.extension, displayName: metadata.auth.providerLabel }, undefined); + const success = await this._getAuthAccess(extension, { identifier: metadata.extension, displayName: metadata.auth.providerLabel }, options.justification, options.silent); - if (!this._modelAccessList.get(from)?.has(metadata.extension)) { - return LanguageModelRequest.fromError(new Error('Access to chat has been revoked')); + if (!success || !this._modelAccessList.get(from)?.has(metadata.extension)) { + throw new Error(`Language model '${languageModelId}' cannot be used by '${from.value}'.`); } } - const cts = new CancellationTokenSource(token); const requestId = (Math.random() * 1e6) | 0; - const requestPromise = this._proxy.$fetchResponse(from, languageModelId, requestId, messages.map(typeConvert.LanguageModelMessage.from), options ?? {}, cts.token); - const res = new LanguageModelRequest(requestPromise, cts); + const requestPromise = this._proxy.$fetchResponse(from, languageModelId, requestId, messages.map(typeConvert.LanguageModelMessage.from), options.modelOptions ?? {}, token); + + const barrier = new Barrier(); + + const res = new LanguageModelResponse(); this._pendingRequest.set(requestId, { languageModelId, res }); - requestPromise.finally(() => { + let error: Error | undefined; + + requestPromise.catch(err => { + if (barrier.isOpen()) { + // we received an error while streaming. this means we need to reject the "stream" + // because we have already returned the request object + res.reject(err); + } else { + error = err; + } + }).finally(() => { this._pendingRequest.delete(requestId); - cts.dispose(); + res.resolve(); + barrier.open(); }); + await barrier.wait(); + + if (error) { + throw new Error(`Language model '${languageModelId}' errored, check cause for more details`, { cause: error }); + } + return res.apiObject; } - async requestLanguageModelAccess(extension: IExtensionDescription, languageModelId: string, options?: vscode.LanguageModelAccessOptions): Promise { - const from = extension.identifier; - const justification = options?.justification; - const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, justification); - - if (!metadata) { - throw new Error(`Language model '${languageModelId}' NOT found`); - } - - if (this._isUsingAuth(from, metadata)) { - await this._getAuthAccess(extension, { identifier: metadata.extension, displayName: metadata.auth.providerLabel }, justification); - } - - const that = this; - - return { - get model() { - return metadata.model; - }, - get isRevoked() { - return (that._isUsingAuth(from, metadata) && !that._modelAccessList.get(from)?.has(metadata.extension)) || !that._languageModelIds.has(languageModelId); - }, - get onDidChangeAccess() { - const onDidRemoveLM = Event.filter(that._onDidChangeProviders.event, e => e.removed.includes(languageModelId)); - const onDidChangeModelAccess = Event.filter(that._onDidChangeModelAccess.event, e => ExtensionIdentifier.equals(e.from, from) && ExtensionIdentifier.equals(e.to, metadata.extension)); - return Event.signal(Event.any(onDidRemoveLM, onDidChangeModelAccess)); - }, - makeChatRequest(messages, options, token) { - if (that._isUsingAuth(from, metadata) && !that._modelAccessList.get(from)?.has(metadata.extension)) { - throw new Error('Access to chat has been revoked'); - } - if (!that._languageModelIds.has(languageModelId)) { - throw new Error('Language Model has been removed'); - } - const cts = new CancellationTokenSource(token); - const requestId = (Math.random() * 1e6) | 0; - const requestPromise = that._proxy.$fetchResponse(from, languageModelId, requestId, messages.map(typeConvert.LanguageModelMessage.from), options ?? {}, cts.token); - const res = new LanguageModelRequest(requestPromise, cts); - that._pendingRequest.set(requestId, { languageModelId, res }); - - requestPromise.finally(() => { - that._pendingRequest.delete(requestId); - cts.dispose(); - }); - - return res.apiObject; - }, - }; - } - async $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise { const data = this._pendingRequest.get(requestId);//.report(chunk); if (data) { @@ -317,22 +284,32 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { } // BIG HACK: Using AuthenticationProviders to check access to Language Models - private async _getAuthAccess(from: IExtensionDescription, to: { identifier: ExtensionIdentifier; displayName: string }, justification?: string): Promise { + private async _getAuthAccess(from: IExtensionDescription, to: { identifier: ExtensionIdentifier; displayName: string }, justification: string | undefined, silent: boolean | undefined): Promise { // This needs to be done in both MainThread & ExtHost ChatProvider const providerId = INTERNAL_AUTH_PROVIDER_PREFIX + to.identifier.value; const session = await this._extHostAuthentication.getSession(from, providerId, [], { silent: true }); - if (!session) { - try { - const detail = justification - ? localize('chatAccessWithJustification', "To allow access to the language models provided by {0}. Justification:\n\n{1}", to.displayName, justification) - : localize('chatAccess', "To allow access to the language models provided by {0}", to.displayName); - await this._extHostAuthentication.getSession(from, providerId, [], { forceNewSession: { detail } }); - } catch (err) { - throw new Error('Access to language models has not been granted'); - } + + if (session) { + this.$updateModelAccesslist([{ from: from.identifier, to: to.identifier, enabled: true }]); + return true; } - this.$updateModelAccesslist([{ from: from.identifier, to: to.identifier, enabled: true }]); + if (silent) { + return false; + } + + try { + const detail = justification + ? localize('chatAccessWithJustification', "To allow access to the language models provided by {0}. Justification:\n\n{1}", to.displayName, justification) + : localize('chatAccess', "To allow access to the language models provided by {0}", to.displayName); + await this._extHostAuthentication.getSession(from, providerId, [], { forceNewSession: { detail } }); + this.$updateModelAccesslist([{ from: from.identifier, to: to.identifier, enabled: true }]); + return true; + + } catch (err) { + // ignore + return false; + } } private _isUsingAuth(from: ExtensionIdentifier, toMetadata: ILanguageModelChatMetadata): toMetadata is ILanguageModelChatMetadata & { auth: NonNullable } { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 816f0227361..68cbe330492 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2239,20 +2239,20 @@ export namespace ChatInlineFollowup { export namespace LanguageModelMessage { - export function to(message: chatProvider.IChatMessage): vscode.LanguageModelMessage { + export function to(message: chatProvider.IChatMessage): vscode.LanguageModelChatMessage { switch (message.role) { - case chatProvider.ChatMessageRole.System: return new types.LanguageModelSystemMessage(message.content); - case chatProvider.ChatMessageRole.User: return new types.LanguageModelUserMessage(message.content); - case chatProvider.ChatMessageRole.Assistant: return new types.LanguageModelAssistantMessage(message.content); + case chatProvider.ChatMessageRole.System: return new types.LanguageModelChatSystemMessage(message.content); + case chatProvider.ChatMessageRole.User: return new types.LanguageModelChatUserMessage(message.content); + case chatProvider.ChatMessageRole.Assistant: return new types.LanguageModelChatAssistantMessage(message.content); } } - export function from(message: vscode.LanguageModelMessage): chatProvider.IChatMessage { - if (message instanceof types.LanguageModelSystemMessage) { + export function from(message: vscode.LanguageModelChatMessage): chatProvider.IChatMessage { + if (message instanceof types.LanguageModelChatSystemMessage) { return { role: chatProvider.ChatMessageRole.System, content: message.content }; - } else if (message instanceof types.LanguageModelUserMessage) { + } else if (message instanceof types.LanguageModelChatUserMessage) { return { role: chatProvider.ChatMessageRole.User, content: message.content }; - } else if (message instanceof types.LanguageModelAssistantMessage) { + } else if (message instanceof types.LanguageModelChatAssistantMessage) { return { role: chatProvider.ChatMessageRole.Assistant, content: message.content }; } else { throw new Error('Invalid LanguageModelMessage'); diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 4fb94e3f1c8..4eec0964cd2 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4271,7 +4271,7 @@ export class ChatResponseTurn implements vscode.ChatResponseTurn { ) { } } -export class LanguageModelSystemMessage { +export class LanguageModelChatSystemMessage { content: string; constructor(content: string) { @@ -4279,7 +4279,7 @@ export class LanguageModelSystemMessage { } } -export class LanguageModelUserMessage { +export class LanguageModelChatUserMessage { content: string; name: string | undefined; @@ -4289,7 +4289,7 @@ export class LanguageModelUserMessage { } } -export class LanguageModelAssistantMessage { +export class LanguageModelChatAssistantMessage { content: string; constructor(content: string) { diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index 7a9e140b5a5..b6aa1ffdadf 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -16,7 +16,7 @@ declare module 'vscode' { * Represents a large language model that accepts ChatML messages and produces a streaming response */ export interface ChatResponseProvider { - provideLanguageModelResponse2(messages: LanguageModelMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; + provideLanguageModelResponse2(messages: LanguageModelChatMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; } export interface ChatResponseProviderMetadata { diff --git a/src/vscode-dts/vscode.proposed.languageModels.d.ts b/src/vscode-dts/vscode.proposed.languageModels.d.ts index 258a5bf18e9..59b053f149d 100644 --- a/src/vscode-dts/vscode.proposed.languageModels.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModels.d.ts @@ -8,20 +8,14 @@ declare module 'vscode' { /** * Represents a language model response. * - * @see {@link LanguageModelAccess.makeChatRequest} + * @see {@link LanguageModelAccess.chatRequest} */ - export interface LanguageModelResponse { - - /** - * The overall result of the request which represents failure or success - * but. The concrete value is not specified and depends on the selected language model. - * - * *Note* that the actual response represented by the {@link LanguageModelResponse.stream `stream`}-property - */ - result: Thenable; + export interface LanguageModelChatResponse { /** * An async iterable that is a stream of text chunks forming the overall response. + * + * *Note* that this stream will error when during receiving an error occurrs. */ stream: AsyncIterable; } @@ -35,7 +29,7 @@ declare module 'vscode' { * *Note* that a language model may choose to add additional system messages to the ones * provided by extensions. */ - export class LanguageModelSystemMessage { + export class LanguageModelChatSystemMessage { /** * The content of this message. @@ -53,7 +47,7 @@ declare module 'vscode' { /** * A language model message that represents a user message. */ - export class LanguageModelUserMessage { + export class LanguageModelChatUserMessage { /** * The content of this message. @@ -78,7 +72,7 @@ declare module 'vscode' { * A language model message that represents an assistant message, usually in response to a user message * or as a sample response/reply-pair. */ - export class LanguageModelAssistantMessage { + export class LanguageModelChatAssistantMessage { /** * The content of this message. @@ -93,50 +87,8 @@ declare module 'vscode' { constructor(content: string); } - export type LanguageModelMessage = LanguageModelSystemMessage | LanguageModelUserMessage | LanguageModelAssistantMessage; + export type LanguageModelChatMessage = LanguageModelChatSystemMessage | LanguageModelChatUserMessage | LanguageModelChatAssistantMessage; - /** - * Represents access to using a language model. Access can be revoked at any time and extension - * must check if the access is {@link LanguageModelAccess.isRevoked still valid} before using it. - */ - export interface LanguageModelAccess { - - /** - * Whether the access to the language model has been revoked. - */ - readonly isRevoked: boolean; - - /** - * An event that is fired when the access the language model has has been revoked or re-granted. - */ - // TODO@API NAME? - readonly onDidChangeAccess: Event; - - /** - * The name of the model. - * - * It is expected that the model name can be used to lookup properties like token limits or what - * `options` are available. - */ - readonly model: string; - - /** - * Make a request to the language model. - * - * *Note:* This will throw an error if access has been revoked. - * - * @param messages - * @param options - */ - makeChatRequest(messages: LanguageModelMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelResponse; - } - - export interface LanguageModelAccessOptions { - /** - * A human-readable message that explains why access to a language model is needed and what feature is enabled by it. - */ - justification?: string; - } /** * An event describing the change in the set of available language models. @@ -152,57 +104,61 @@ declare module 'vscode' { readonly removed: readonly string[]; } + /** + * Options for making a chat request using a language model. + * + * @see {@link lm.chatRequest} + */ + export interface LanguageModelChatRequestOptions { + + /** + * A human-readable message that explains why access to a language model is needed and what feature is enabled by it. + */ + justification?: string; + + /** + * Do not show the consent UI if the user has not yet granted access to the language model but fail the request instead. + */ + // TODO@API refine/define + silent?: boolean; + + /** + * A set of options that control the behavior of the language model. These options are specific to the language model + * and need to be lookup in the respective documentation. + */ + modelOptions?: { [name: string]: any }; + } + /** * Namespace for language model related functionality. */ export namespace lm { - /** - * Request access to a language model. - * - * - *Note 1:* This function will throw an error when the user didn't grant access or when the - * requested language model is not available. - * - * - *Note 2:* It is OK to hold on to the returned access object and use it later, but extensions - * should check {@link LanguageModelAccess.isRevoked} before using it. - * - * @param id The id of the language model, see {@link languageModels} for valid values. - * @returns A thenable that resolves to a language model access object, rejects if access wasn't granted - */ - export function requestLanguageModelAccess(id: string, options?: LanguageModelAccessOptions): Thenable; - - - /** * Make a chat request using a language model. * - * *Note* that language model use may be subject to access restrictions and user consent. This function always returns a response-object - * but its {@link LanguageModelResponse.result `result`}-property may indicate failure, e.g. due to + * *Note* that language model use may be subject to access restrictions and user consent. This function will return a rejected promise + * if access to the language model is not possible. Reasons for this can be: * * - user consent not given * - quote limits exceeded * - model does not exist * * @param languageModel A language model identifier. See {@link languageModels} for aviailable values. - * @param messages - * @param options - * @param token + * @param messages An array of message instances. + * @param options Objects that control the request. + * @param token A cancellation token which controls the request. See {@link CancellationTokenSource} for how to create one. + * @returns A thenable that resolves to a {@link LanguageModelChatResponse}. The promise will reject when making the request failed. */ // TODO@API refine doc // TODO@API define specific error types? - // TODO@API NAME: chatRequest - // TODO@API NAME: ChatRequestXYZMessage - // TODO@API NAME: ChatRequestResponse - export function chatRequest(languageModel: string, messages: LanguageModelMessage[], options: { [name: string]: any }, token: CancellationToken): Thenable; - - /** - * @see {@link chatRequest} - */ - export function chatRequest(languageModel: string, messages: LanguageModelMessage[], token: CancellationToken): Thenable; - - // TODO@API probe on having access - // TODO@API, BETTER?: ExtensionContext.permissions.languageModels: Record; - // export function canMakeChatRequest(languageModel: string): Thenable; + // TODO@API NAME: sendChatRequest, fetchChatResponse, makeChatRequest, chat, chatRequest sendChatRequest + // TODO@API ExtensionContext#permission#languageModels: { languageModel: string: LanguageModelAccessInformation} + // TODO@API ✅ NAME: LanguageModelChatXYZMessage + // TODO@API ✅ errors on everything that prevents us to make the actual request + // TODO@API ✅ double auth + // TODO@API ✅ NAME: LanguageModelChatResponse, ChatResponse, ChatRequestResponse + export function sendChatRequest(languageModel: string, messages: LanguageModelChatMessage[], options: LanguageModelChatRequestOptions, token: CancellationToken): Thenable; /** * The identifiers of all language models that are currently available. @@ -214,4 +170,12 @@ declare module 'vscode' { */ export const onDidChangeLanguageModels: Event; } + + // export function chatRequest2(languageModel: string, callback: (request: LanguageModelRequest) => R): Thenable; + + // interface LanguageModelRequest { + // readonly quota: any; + // readonly permissions: any; + // makeRequest(messages: LanguageModelChatMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelChatResponse; + // } } From 8fd15a863481726f1ea4bdd4bd4f26f40f3e592d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 27 Feb 2024 18:50:12 +0100 Subject: [PATCH 1639/1897] add `LanguageModelError`-type (#206361) https://github.com/microsoft/vscode/issues/206265 --- .../workbench/api/common/extHost.api.impl.ts | 1 + .../api/common/extHostLanguageModels.ts | 11 ++++-- src/vs/workbench/api/common/extHostTypes.ts | 20 +++++++++++ .../vscode.proposed.languageModels.d.ts | 34 +++++++++++++++++-- 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 1b9c7eb2b08..8a99d63cb4c 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1691,6 +1691,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I LanguageModelSystemMessage: extHostTypes.LanguageModelChatSystemMessage, LanguageModelUserMessage: extHostTypes.LanguageModelChatUserMessage, LanguageModelAssistantMessage: extHostTypes.LanguageModelChatAssistantMessage, + LanguageModelError: extHostTypes.LanguageModelError, NewSymbolName: extHostTypes.NewSymbolName, NewSymbolNameTag: extHostTypes.NewSymbolNameTag, InlineEdit: extHostTypes.InlineEdit, diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index e51c875984d..ea10161e75f 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -8,6 +8,7 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostLanguageModelsShape, IMainContext, MainContext, MainThreadLanguageModelsShape } from 'vs/workbench/api/common/extHost.protocol'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; +import { LanguageModelError } from 'vs/workbench/api/common/extHostTypes'; import type * as vscode from 'vscode'; import { Progress } from 'vs/platform/progress/common/progress'; import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata } from 'vs/workbench/contrib/chat/common/languageModels'; @@ -232,14 +233,14 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, options.justification); if (!metadata || !this._languageModelIds.has(languageModelId)) { - throw new Error(`Language model '${languageModelId}' is unknown.`); + throw LanguageModelError.NotFound(`Language model '${languageModelId}' is unknown.`); } if (this._isUsingAuth(from, metadata)) { const success = await this._getAuthAccess(extension, { identifier: metadata.extension, displayName: metadata.auth.providerLabel }, options.justification, options.silent); if (!success || !this._modelAccessList.get(from)?.has(metadata.extension)) { - throw new Error(`Language model '${languageModelId}' cannot be used by '${from.value}'.`); + throw LanguageModelError.NoPermissions(`Language model '${languageModelId}' cannot be used by '${from.value}'.`); } } @@ -270,7 +271,11 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { await barrier.wait(); if (error) { - throw new Error(`Language model '${languageModelId}' errored, check cause for more details`, { cause: error }); + throw new LanguageModelError( + `Language model '${languageModelId}' errored, check cause for more details`, + 'Unknown', + error + ); } return res.apiObject; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 4eec0964cd2..2e1ff171d1f 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4297,6 +4297,26 @@ export class LanguageModelChatAssistantMessage { } } +export class LanguageModelError extends Error { + + static NotFound(message?: string): LanguageModelError { + return new LanguageModelError(message, LanguageModelError.NotFound.name); + } + + static NoPermissions(message?: string): LanguageModelError { + return new LanguageModelError(message, LanguageModelError.NoPermissions.name); + } + + readonly code: string; + + constructor(message?: string, code?: string, cause?: Error) { + super(message, { cause }); + this.name = 'LanguageModelError'; + this.code = code ?? ''; + } + +} + //#endregion //#region ai diff --git a/src/vscode-dts/vscode.proposed.languageModels.d.ts b/src/vscode-dts/vscode.proposed.languageModels.d.ts index 59b053f149d..775e4d5e1dc 100644 --- a/src/vscode-dts/vscode.proposed.languageModels.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModels.d.ts @@ -104,6 +104,36 @@ declare module 'vscode' { readonly removed: readonly string[]; } + /** + * An error type for language model specific errors. + * + * Consumers of language models should check the code property to determine specific + * failure causes, like `if(someError.code === vscode.LanguageModelError.NotFound.name) {...}` + * for the case of referring to an unknown language model. + */ + export class LanguageModelError extends Error { + + /** + * The language model does not exist. + */ + static NotFound(message?: string): LanguageModelError; + + /** + * The requestor does not have permissions to use this + * language model + */ + static NoPermissions(message?: string): LanguageModelError; + + /** + * A code that identifies this error. + * + * Possible values are names of errors, like {@linkcode LanguageModelError.NotFound NotFound}, + * or `Unknown` for unspecified errors from the language model itself. In the latter case the + * `cause`-property will contain the actual error. + */ + readonly code: string; + } + /** * Options for making a chat request using a language model. * @@ -151,9 +181,9 @@ declare module 'vscode' { * @returns A thenable that resolves to a {@link LanguageModelChatResponse}. The promise will reject when making the request failed. */ // TODO@API refine doc - // TODO@API define specific error types? - // TODO@API NAME: sendChatRequest, fetchChatResponse, makeChatRequest, chat, chatRequest sendChatRequest // TODO@API ExtensionContext#permission#languageModels: { languageModel: string: LanguageModelAccessInformation} + // TODO@API ✅ define specific error types? + // TODO@API ✅ NAME: sendChatRequest, fetchChatResponse, makeChatRequest, chat, chatRequest sendChatRequest // TODO@API ✅ NAME: LanguageModelChatXYZMessage // TODO@API ✅ errors on everything that prevents us to make the actual request // TODO@API ✅ double auth From 1d3ff8e891800d26b639d9ecc3656aa75f8ea983 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 27 Feb 2024 18:53:46 +0100 Subject: [PATCH 1640/1897] Refactor and Improve Hover Functionality (#206357) * minor hover improvements? * :lipstick: --- src/vs/platform/hover/browser/hover.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 3a44ead957b..3dd6ec94d02 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -241,7 +241,7 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate private _delay: number; get delay(): number { - if (this.instantHover && Date.now() - this.lastHoverHideTime < this.timeLimit) { + if (this.isInstantlyHovering()) { return 0; // show instantly when a hover was recently shown } return this._delay; @@ -280,6 +280,8 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate })); } + const id = options.content instanceof HTMLElement ? undefined : options.content.toString(); + return this.hoverService.showHover({ ...options, ...overrideOptions, @@ -288,14 +290,20 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate hideOnKeyDown: true, ...overrideOptions.persistence }, + id, appearance: { ...options.appearance, compact: true, + skipFadeInAnimation: this.isInstantlyHovering(), ...overrideOptions.appearance } }, focus); } + private isInstantlyHovering(): boolean { + return this.instantHover && Date.now() - this.lastHoverHideTime < this.timeLimit; + } + setInstantHoverTimeLimit(timeLimit: number): void { if (!this.instantHover) { throw new Error('Instant hover is not enabled'); From 7d546baaeacf0a045fa68259f934109c9f51e973 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 27 Feb 2024 10:08:15 -0800 Subject: [PATCH 1641/1897] align start command name with inline chat, get it to show up in command pallette --- .../contrib/inlineChat/browser/inlineChatActions.ts | 2 +- .../terminalContrib/chat/browser/terminalChatActions.ts | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 75ca16a1f24..ae407d07b67 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -35,7 +35,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); -export const LOCALIZED_START_INLINE_CHAT_STRING = localize2('run', 'Start Inline Chat'); +export const LOCALIZED_START_INLINE_CHAT_STRING = localize2('run', 'Start in Editor'); export const START_INLINE_CHAT = registerIcon('start-inline-chat', Codicon.sparkle, localize('startInlineChat', 'Icon which spawns the inline chat from the editor toolbar.')); // some gymnastics to enable hold for speech without moving the StartSessionAction into the electron-layer diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 878246a2e26..d7e2d3850d3 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -9,7 +9,8 @@ import { localize2 } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { AbstractInlineChatAction } from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; +import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; @@ -18,17 +19,18 @@ import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/cha registerActiveXtermAction({ id: TerminalChatCommandId.Start, - title: localize2('startChat', 'Start Chat'), + title: localize2('startChat', 'Start in Terminal'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, when: ContextKeyExpr.and(TerminalContextKeys.focusInAny), weight: KeybindingWeight.WorkbenchContrib, }, f1: true, + category: AbstractInlineChatAction.category, precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalChatContextKeys.agentRegistered + CTX_INLINE_CHAT_HAS_PROVIDER ), run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { From 37b275146adf89a63d6f29c3f72129069f6fed12 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 27 Feb 2024 10:19:17 -0800 Subject: [PATCH 1642/1897] fix context keys for terminal chat start/close --- .../terminalContrib/chat/browser/terminalChatActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index d7e2d3850d3..709b4ac8f24 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -29,7 +29,7 @@ registerActiveXtermAction({ category: AbstractInlineChatAction.category, precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + ContextKeyExpr.and(TerminalContextKeys.processSupported, TerminalContextKeys.focusInAny), CTX_INLINE_CHAT_HAS_PROVIDER ), run: (_xterm, _accessor, activeInstance) => { @@ -59,7 +59,7 @@ registerActiveXtermAction({ f1: true, precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + ContextKeyExpr.and(TerminalChatContextKeys.focused, TerminalChatContextKeys.visible) ), run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { From e9f90c2abc99e77f78174e5b990335123a3abb2b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 27 Feb 2024 20:18:19 +0100 Subject: [PATCH 1643/1897] =?UTF-8?q?Python=20error=20terminal=20output=20?= =?UTF-8?q?=E2=8C=98=20+=20Click=20links=20erroneously=20treats=20filename?= =?UTF-8?q?=20as=20if=20it=20ends=20in=20a=20single=20quote=20'=20(fix=20#?= =?UTF-8?q?206001)=20(#206366)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/vs/workbench/contrib/output/common/outputLinkComputer.ts | 2 +- .../contrib/output/test/browser/outputLinkProvider.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/output/common/outputLinkComputer.ts b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts index efab6b06075..0de2f9f2e15 100644 --- a/src/vs/workbench/contrib/output/common/outputLinkComputer.ts +++ b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts @@ -88,7 +88,7 @@ export class OutputLinkComputer { } for (const workspaceFolderVariant of workspaceFolderVariants) { - const validPathCharacterPattern = '[^\\s\\(\\):<>"]'; + const validPathCharacterPattern = '[^\\s\\(\\):<>\'"]'; const validPathCharacterOrSpacePattern = `(?:${validPathCharacterPattern}| ${validPathCharacterPattern})`; const pathPattern = `${validPathCharacterOrSpacePattern}+\\.${validPathCharacterPattern}+`; const strictPathPattern = `${validPathCharacterPattern}+`; diff --git a/src/vs/workbench/contrib/output/test/browser/outputLinkProvider.test.ts b/src/vs/workbench/contrib/output/test/browser/outputLinkProvider.test.ts index 1c92342f5bb..2f7988f5236 100644 --- a/src/vs/workbench/contrib/output/test/browser/outputLinkProvider.test.ts +++ b/src/vs/workbench/contrib/output/test/browser/outputLinkProvider.test.ts @@ -277,9 +277,9 @@ suite('OutputLinkProvider', () => { line = toOSPath(' at \'C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts\' in'); result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.strictEqual(result.length, 1); - assert.strictEqual(result[0].url, contextService.toResource('/Game.ts\'').toString()); + assert.strictEqual(result[0].url, contextService.toResource('/Game.ts').toString()); assert.strictEqual(result[0].range.startColumn, 6); - assert.strictEqual(result[0].range.endColumn, 86); + assert.strictEqual(result[0].range.endColumn, 85); }); test('OutputLinkProvider - #106847', function () { From 887c9d0f3e2c8ad6476e8027324f2e38154f1db9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 27 Feb 2024 20:35:22 +0100 Subject: [PATCH 1644/1897] Explorer: view title is different for 1 view vs. N views (fix #206368) (#206370) --- src/vs/workbench/contrib/files/browser/views/explorerView.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index d7e695e1958..481d8354c7e 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -273,10 +273,8 @@ export class ExplorerView extends ViewPane implements IExplorerView { const titleElement = container.querySelector('.title') as HTMLElement; const setHeader = () => { - const workspace = this.contextService.getWorkspace(); - const title = workspace.folders.map(folder => folder.name).join(); titleElement.textContent = this.name; - this.updateTitle(title); + this.updateTitle(this.name); this.ariaHeaderLabel = nls.localize('explorerSection', "Explorer Section: {0}", this.name); titleElement.setAttribute('aria-label', this.ariaHeaderLabel); }; From 46bdbb4721358379f17b9d4bbfbae5874b13e345 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 27 Feb 2024 20:45:55 +0100 Subject: [PATCH 1645/1897] dictation - layout widget when cursor position changes (#206371) --- .../contrib/codeEditor/browser/dictation/editorDictation.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts index 842953efd0d..c1263ccce50 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts @@ -206,6 +206,8 @@ export class EditorDictation extends Disposable implements IEditorContribution { const collection = this.editor.createDecorationsCollection(); disposables.add(toDisposable(() => collection.clear())); + disposables.add(this.editor.onDidChangeCursorPosition(() => this.widget.layout())); + let previewStart: Position | undefined = undefined; let lastReplaceTextLength = 0; @@ -242,7 +244,6 @@ export class EditorDictation extends Disposable implements IEditorContribution { } this.editor.revealPositionInCenterIfOutsideViewport(endPosition); - this.widget.layout(); }; const cts = new CancellationTokenSource(); From 8b8b573dccb0cf84bb381c1785a30add25fd3c1e Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Tue, 27 Feb 2024 12:06:07 -0800 Subject: [PATCH 1646/1897] Check for invalid Range parameters when trimming nb newlines (#206375) Fix #201191 --- .../browser/contrib/saveParticipants/saveParticipants.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts b/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts index 0ffd61f705a..993faeb2acb 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts @@ -224,8 +224,11 @@ class TrimFinalNewLinesParticipant implements IStoredFileWorkingCopySaveParticip const textBuffer = cell.textBuffer; const lastNonEmptyLine = this.findLastNonEmptyLine(textBuffer); const deleteFromLineNumber = Math.max(lastNonEmptyLine + 1, cannotTouchLineNumber + 1); - const deletionRange = new Range(deleteFromLineNumber, 1, textBuffer.getLineCount(), textBuffer.getLineLastNonWhitespaceColumn(textBuffer.getLineCount())); + if (deleteFromLineNumber > textBuffer.getLineCount()) { + return; + } + const deletionRange = new Range(deleteFromLineNumber, 1, textBuffer.getLineCount(), textBuffer.getLineLastNonWhitespaceColumn(textBuffer.getLineCount())); if (deletionRange.isEmpty()) { return; } @@ -244,7 +247,7 @@ class TrimFinalNewLinesParticipant implements IStoredFileWorkingCopySaveParticip } } -class FinalNewLineParticipant implements IStoredFileWorkingCopySaveParticipant { +class InsertFinalNewLineParticipant implements IStoredFileWorkingCopySaveParticipant { constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @@ -520,7 +523,7 @@ export class SaveParticipantsContribution extends Disposable implements IWorkben this._register(this.workingCopyFileService.addSaveParticipant(this.instantiationService.createInstance(TrimWhitespaceParticipant))); this._register(this.workingCopyFileService.addSaveParticipant(this.instantiationService.createInstance(CodeActionOnSaveParticipant))); this._register(this.workingCopyFileService.addSaveParticipant(this.instantiationService.createInstance(FormatOnSaveParticipant))); - this._register(this.workingCopyFileService.addSaveParticipant(this.instantiationService.createInstance(FinalNewLineParticipant))); + this._register(this.workingCopyFileService.addSaveParticipant(this.instantiationService.createInstance(InsertFinalNewLineParticipant))); this._register(this.workingCopyFileService.addSaveParticipant(this.instantiationService.createInstance(TrimFinalNewLinesParticipant))); } } From 490b0caf3031e535002b61ff4ddffb1a34541af7 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Tue, 27 Feb 2024 12:33:37 -0800 Subject: [PATCH 1647/1897] update variables via `test-documentation` script (#206379) update variables via script --- build/lib/stylelint/vscode-known-variables.json | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index e9ec91f29ea..ffbd6ae9edd 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -43,11 +43,10 @@ "--vscode-charts-yellow", "--vscode-chat-avatarBackground", "--vscode-chat-avatarForeground", - "--vscode-chat-requestBorder", "--vscode-chat-requestBackground", + "--vscode-chat-requestBorder", "--vscode-chat-slashCommandBackground", "--vscode-chat-slashCommandForeground", - "--vscode-chat-list-background", "--vscode-checkbox-background", "--vscode-checkbox-border", "--vscode-checkbox-foreground", @@ -694,7 +693,6 @@ "--vscode-terminalOverviewRuler-findMatchForeground", "--vscode-terminalStickyScroll-background", "--vscode-terminalStickyScrollHover-background", - "--vscode-testing-coverage-lineHeight", "--vscode-testing-coverCountBadgeBackground", "--vscode-testing-coverCountBadgeForeground", "--vscode-testing-coveredBackground", @@ -750,8 +748,7 @@ "--vscode-widget-border", "--vscode-widget-shadow", "--vscode-window-activeBorder", - "--vscode-window-inactiveBorder", - "--vscode-multiDiffEditor-headerBackground" + "--vscode-window-inactiveBorder" ], "others": [ "--background-dark", @@ -830,4 +827,4 @@ "--zoom-factor", "--test-bar-width" ] -} +} \ No newline at end of file From c8c243a3d8183168504f82434f9fec64095cdd34 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Tue, 27 Feb 2024 12:56:12 -0800 Subject: [PATCH 1648/1897] chore: add Windows as package platform (#206369) --- extensions/markdown-language-features/server/build/pipeline.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/markdown-language-features/server/build/pipeline.yml b/extensions/markdown-language-features/server/build/pipeline.yml index c229f78cfbf..0c9e3bdbd11 100644 --- a/extensions/markdown-language-features/server/build/pipeline.yml +++ b/extensions/markdown-language-features/server/build/pipeline.yml @@ -32,3 +32,4 @@ extends: displayName: Compile publishPackage: ${{ parameters.publishPackage }} + packagePlatform: 'Windows' From 6b6da7928db8f9b00ba8c1551e955c068ebaef76 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 27 Feb 2024 13:42:24 -0800 Subject: [PATCH 1649/1897] debug: pass environment via terminal API instead of command (#206376) Fixes #198550 --- .../workbench/api/node/extHostDebugService.ts | 18 ++++++-- .../workbench/contrib/debug/node/terminals.ts | 43 +------------------ 2 files changed, 15 insertions(+), 46 deletions(-) diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 90b977aa644..f38194be406 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -27,6 +27,7 @@ import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/c import type * as vscode from 'vscode'; import { ExtHostConfigProvider, IExtHostConfiguration } from '../common/extHostConfiguration'; import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { createHash } from 'crypto'; export class ExtHostDebugService extends ExtHostDebugServiceBase { @@ -89,8 +90,8 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { const terminalName = args.title || nls.localize('debug.terminal.title', "Debug Process"); - const shellConfig = JSON.stringify({ shell, shellArgs }); - let terminal = await this._integratedTerminalInstances.checkout(shellConfig, terminalName); + const termKey = createKeyForShell(shell, shellArgs, args); + let terminal = await this._integratedTerminalInstances.checkout(termKey, terminalName); let cwdForPrepareCommand: string | undefined; let giveShellTimeToInitialize = false; @@ -102,6 +103,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { cwd: args.cwd, name: terminalName, iconPath: new ThemeIcon('debug'), + env: args.env, }; giveShellTimeToInitialize = true; terminal = this._terminalService.createTerminalFromOptions(options, { @@ -111,7 +113,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { forceShellIntegration: true, useShellEnvironment: true }); - this._integratedTerminalInstances.insert(terminal, shellConfig); + this._integratedTerminalInstances.insert(terminal, termKey); } else { cwdForPrepareCommand = args.cwd; @@ -139,7 +141,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { } } - const command = prepareCommand(shell, args.args, !!args.argsCanBeInterpretedByShell, cwdForPrepareCommand, args.env); + const command = prepareCommand(shell, args.args, !!args.argsCanBeInterpretedByShell, cwdForPrepareCommand); terminal.sendText(command); // Mark terminal as unused when its session ends, see #112055 @@ -159,6 +161,14 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { } } +/** Creates a key that determines how terminals get reused */ +function createKeyForShell(shell: string, shellArgs: string | string[], args: DebugProtocol.RunInTerminalRequestArguments) { + const hash = createHash('sha256'); + hash.update(JSON.stringify({ shell, shellArgs })); + hash.update(JSON.stringify(Object.entries(args.env || {}).sort(([k1], [k2]) => k1.localeCompare(k2)))); + return hash.digest('base64'); +} + let externalTerminalService: IExternalTerminalService | undefined = undefined; function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, configProvider: ExtHostConfigProvider): Promise { diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index c3f3cd92928..84c3d7947d4 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -56,7 +56,7 @@ export async function hasChildProcesses(processId: number | undefined): Promise< const enum ShellType { cmd, powershell, bash } -export function prepareCommand(shell: string, args: string[], argsCanBeInterpretedByShell: boolean, cwd?: string, env?: { [key: string]: string | null }): string { +export function prepareCommand(shell: string, args: string[], argsCanBeInterpretedByShell: boolean, cwd?: string): string { shell = shell.trim().toLowerCase(); @@ -97,16 +97,6 @@ export function prepareCommand(shell: string, args: string[], argsCanBeInterpret } command += `cd ${quote(cwd)}; `; } - if (env) { - for (const key in env) { - const value = env[key]; - if (value === null) { - command += `Remove-Item env:${key}; `; - } else { - command += `\${env:${key}}='${value}'; `; - } - } - } if (args.length > 0) { const arg = args.shift()!; const cmd = argsCanBeInterpretedByShell ? arg : quote(arg); @@ -137,25 +127,10 @@ export function prepareCommand(shell: string, args: string[], argsCanBeInterpret } command += `cd ${quote(cwd)} && `; } - if (env) { - command += 'cmd /C "'; - for (const key in env) { - let value = env[key]; - if (value === null) { - command += `set "${key}=" && `; - } else { - value = value.replace(/[&^|<>]/g, s => `^${s}`); - command += `set "${key}=${value}" && `; - } - } - } for (const a of args) { command += (a === '<' || a === '>' || argsCanBeInterpretedByShell) ? a : quote(a); command += ' '; } - if (env) { - command += '"'; - } break; case ShellType.bash: { @@ -165,25 +140,9 @@ export function prepareCommand(shell: string, args: string[], argsCanBeInterpret return s.length === 0 ? `""` : s; }; - const hardQuote = (s: string) => { - return /[^\w@%\/+=,.:^-]/.test(s) ? `'${s.replace(/'/g, '\'\\\'\'')}'` : s; - }; - if (cwd) { command += `cd ${quote(cwd)} ; `; } - if (env) { - command += '/usr/bin/env'; - for (const key in env) { - const value = env[key]; - if (value === null) { - command += ` -u ${hardQuote(key)}`; - } else { - command += ` ${hardQuote(`${key}=${value}`)}`; - } - } - command += ' '; - } for (const a of args) { command += (a === '<' || a === '>' || argsCanBeInterpretedByShell) ? a : quote(a); command += ' '; From 2a57d4139f0bb52c560ef75abdab4e851c31e232 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 27 Feb 2024 23:37:27 +0100 Subject: [PATCH 1650/1897] fix #204615 (#206381) * fix #204615 * Fix extension installation condition --- .../abstractExtensionManagementService.ts | 10 +- .../common/extensionManagement.ts | 6 +- .../common/extensionManagementIpc.ts | 4 +- .../node/extensionManagementService.ts | 4 +- .../actions/voiceChatActions.ts | 40 +--- .../contrib/debug/browser/variablesView.ts | 74 ++------ .../browser/extensions.contribution.ts | 55 ++++-- .../extensions/browser/extensionsActions.ts | 2 +- .../browser/extensionsWorkbenchService.ts | 171 ++++++++++++++---- .../contrib/extensions/common/extensions.ts | 16 +- .../common/extensionManagement.ts | 4 +- .../extensionManagementChannelClient.ts | 4 +- .../common/extensionManagementService.ts | 8 +- .../extensionManagementService.ts | 4 +- .../nativeExtensionManagementService.ts | 4 +- .../remoteExtensionManagementService.ts | 4 +- .../extensions/browser/extensionUrlHandler.ts | 115 ++---------- .../test/browser/workbenchTestServices.ts | 6 +- 18 files changed, 259 insertions(+), 272 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index ced410cb52e..d0cedb8d962 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -16,7 +16,7 @@ import * as nls from 'vs/nls'; import { ExtensionManagementError, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementParticipant, IGalleryExtension, ILocalExtension, InstallOperation, IExtensionsControlManifest, StatisticType, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode, - InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService, InstallExtensionInfo, EXTENSION_INSTALL_DEP_PACK_CONTEXT, ExtensionGalleryError + InstallOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService, InstallExtensionInfo, EXTENSION_INSTALL_DEP_PACK_CONTEXT, ExtensionGalleryError } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, ExtensionKey, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, IExtensionManifest, isApplicationScopedExtension, TargetPlatform } from 'vs/platform/extensions/common/extensions'; @@ -27,9 +27,9 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; export type ExtensionVerificationStatus = boolean | string; -export type InstallableExtension = { readonly manifest: IExtensionManifest; extension: IGalleryExtension | URI; options: InstallOptions & InstallVSIXOptions }; +export type InstallableExtension = { readonly manifest: IExtensionManifest; extension: IGalleryExtension | URI; options: InstallOptions }; -export type InstallExtensionTaskOptions = InstallOptions & InstallVSIXOptions & { readonly profileLocation: URI }; +export type InstallExtensionTaskOptions = InstallOptions & { readonly profileLocation: URI }; export interface IInstallExtensionTask { readonly identifier: IExtensionIdentifier; readonly source: IGalleryExtension | URI; @@ -228,7 +228,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl const isApplicationScoped = options.isApplicationScoped || options.isBuiltin || isApplicationScopedExtension(manifest); const installExtensionTaskOptions: InstallExtensionTaskOptions = { ...options, - installOnlyNewlyAddedFromExtensionPack: URI.isUri(extension) ? options.installOnlyNewlyAddedFromExtensionPack : true, /* always true for gallery extensions */ + installOnlyNewlyAddedFromExtensionPack: options.installOnlyNewlyAddedFromExtensionPack ?? !URI.isUri(extension) /* always true for gallery extensions */, isApplicationScoped, profileLocation: isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation() }; @@ -715,7 +715,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl abstract zip(extension: ILocalExtension): Promise; abstract unzip(zipLocation: URI): Promise; abstract getManifest(vsix: URI): Promise; - abstract install(vsix: URI, options?: InstallVSIXOptions): Promise; + abstract install(vsix: URI, options?: InstallOptions): Promise; abstract installFromLocation(location: URI, profileLocation: URI): Promise; abstract installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise; abstract getInstalled(type?: ExtensionType, profileLocation?: URI): Promise; diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 756acf86b45..09a2b1dddcd 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -453,16 +453,16 @@ export type InstallOptions = { donotVerifySignature?: boolean; operation?: InstallOperation; profileLocation?: URI; + installOnlyNewlyAddedFromExtensionPack?: boolean; /** * Context passed through to InstallExtensionResult */ context?: IStringDictionary; }; -export type InstallVSIXOptions = InstallOptions & { installOnlyNewlyAddedFromExtensionPack?: boolean }; export type UninstallOptions = { readonly donotIncludePack?: boolean; readonly donotCheckDependents?: boolean; readonly versionOnly?: boolean; readonly remove?: boolean; readonly profileLocation?: URI }; export interface IExtensionManagementParticipant { - postInstall(local: ILocalExtension, source: URI | IGalleryExtension, options: InstallOptions | InstallVSIXOptions, token: CancellationToken): Promise; + postInstall(local: ILocalExtension, source: URI | IGalleryExtension, options: InstallOptions, token: CancellationToken): Promise; postUninstall(local: ILocalExtension, options: UninstallOptions, token: CancellationToken): Promise; } @@ -481,7 +481,7 @@ export interface IExtensionManagementService { zip(extension: ILocalExtension): Promise; unzip(zipLocation: URI): Promise; getManifest(vsix: URI): Promise; - install(vsix: URI, options?: InstallVSIXOptions): Promise; + install(vsix: URI, options?: InstallOptions): Promise; canInstall(extension: IGalleryExtension): Promise; installFromGallery(extension: IGalleryExtension, options?: InstallOptions): Promise; installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise; diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index d4f15349aad..c4403134b93 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -9,7 +9,7 @@ import { cloneAndChange } from 'vs/base/common/objects'; import { URI, UriComponents } from 'vs/base/common/uri'; import { DefaultURITransformer, IURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IExtensionIdentifier, IExtensionTipsService, IGalleryExtension, ILocalExtension, IExtensionsControlManifest, isTargetPlatformCompatible, InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallOperation, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionIdentifier, IExtensionTipsService, IGalleryExtension, ILocalExtension, IExtensionsControlManifest, isTargetPlatformCompatible, InstallOptions, UninstallOptions, Metadata, IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallOperation, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionType, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI; @@ -237,7 +237,7 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt return Promise.resolve(this.channel.call('unzip', [zipLocation])); } - install(vsix: URI, options?: InstallVSIXOptions): Promise { + install(vsix: URI, options?: InstallOptions): Promise { return Promise.resolve(this.channel.call('install', [vsix, options])).then(local => transformIncomingExtension(local, null)); } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 217d7720d15..bb2828a935f 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -28,7 +28,7 @@ import { INativeEnvironmentService } from 'vs/platform/environment/common/enviro import { AbstractExtensionManagementService, AbstractExtensionTask, ExtensionVerificationStatus, IInstallExtensionTask, InstallExtensionTaskOptions, IUninstallExtensionTask, joinErrors, toExtensionManagementError, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService'; import { ExtensionManagementError, ExtensionManagementErrorCode, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOperation, - Metadata, InstallVSIXOptions + Metadata, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, computeTargetPlatform, ExtensionKey, getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionsProfileScannerService, IScannedProfileExtension } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; @@ -140,7 +140,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return this.extensionsScanner.scanUserExtensionAtLocation(location); } - async install(vsix: URI, options: InstallVSIXOptions = {}): Promise { + async install(vsix: URI, options: InstallOptions = {}): Promise { this.logService.trace('ExtensionManagementService#install', vsix.toString()); const { location, cleanup } = await this.downloadVsix(vsix); diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 36ff7b6c4b0..b0f09ffdf75 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/voiceChatActions'; import { Event } from 'vs/base/common/event'; import { firstOrDefault } from 'vs/base/common/arrays'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { localize, localize2 } from 'vs/nls'; @@ -46,13 +46,12 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IHostService } from 'vs/workbench/services/host/browser/host'; import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ProgressLocation } from 'vs/platform/progress/common/progress'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { IVoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ThemeIcon } from 'vs/base/common/themables'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ProgressLocation } from 'vs/platform/progress/common/progress'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); @@ -634,36 +633,13 @@ export class InstallVoiceChatAction extends Action2 { async run(accessor: ServicesAccessor): Promise { const contextKeyService = accessor.get(IContextKeyService); - const dialogService = accessor.get(IDialogService); - const extensionManagementService = accessor.get(IExtensionsWorkbenchService); - - const extension = firstOrDefault((await extensionManagementService.getExtensions([{ id: InstallVoiceChatAction.SPEECH_EXTENSION_ID }], CancellationToken.None))); - if (!extension) { - return; - } - - if (extension.state === ExtensionState.Installed) { - await dialogService.info( - localize('enableExtensionMessage', "Microphone support requires an extension. Please enable it."), - localize('enableExtensionDetail', "Extension '{0}' is currently disabled.", InstallVoiceChatAction.SPEECH_EXTENSION_ID), - ); - - return extensionManagementService.open(extension); - } - - const { confirmed } = await dialogService.confirm({ - message: localize('confirmInstallMessage', "Microphone support requires an extension. Would you like to install it now?"), - detail: localize('confirmInstallDetail', "This will install the '{0}' extension.", InstallVoiceChatAction.SPEECH_EXTENSION_ID), - primaryButton: localize({ key: 'installButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Install") - }); - - if (!confirmed) { - return; - } - + const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); try { InstallingSpeechProvider.bindTo(contextKeyService).set(true); - await extensionManagementService.install(extension, undefined, ProgressLocation.Notification); + await extensionsWorkbenchService.install(InstallVoiceChatAction.SPEECH_EXTENSION_ID, { + justification: localize('confirmInstallDetail', "Microphone support requires this extension."), + enable: true + }, ProgressLocation.Notification); } finally { InstallingSpeechProvider.bindTo(contextKeyService).set(false); } diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index d7b570523ce..cce34ecbbe6 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -12,7 +12,7 @@ import { AsyncDataTree, IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/ import { ITreeContextMenuEvent, ITreeMouseEvent, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Action, IAction } from 'vs/base/common/actions'; import { coalesce } from 'vs/base/common/arrays'; -import { RunOnceScheduler, timeout } from 'vs/base/common/async'; +import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; @@ -22,16 +22,16 @@ import { localize } from 'vs/nls'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { ProgressLocation } from 'vs/platform/progress/common/progress'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ViewAction, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; @@ -43,6 +43,7 @@ import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS import { getContextForVariable } from 'vs/workbench/contrib/debug/common/debugContext'; import { ErrorScope, Expression, Scope, StackFrame, Variable, VisualizedExpression, getUriForDebugMemory } from 'vs/workbench/contrib/debug/common/debugModel'; import { DebugVisualizer, IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -717,15 +718,14 @@ CommandsRegistry.registerCommand({ memoryReference = arg.memoryReference; } - const commandService = accessor.get(ICommandService); + const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); const editorService = accessor.get(IEditorService); - const notifications = accessor.get(INotificationService); - const progressService = accessor.get(IProgressService); + const notificationService = accessor.get(INotificationService); const extensionService = accessor.get(IExtensionService); const telemetryService = accessor.get(ITelemetryService); const ext = await extensionService.getExtension(HEX_EDITOR_EXTENSION_ID); - if (ext || await tryInstallHexEditor(notifications, progressService, extensionService, commandService)) { + if (ext || await tryInstallHexEditor(extensionsWorkbenchService, notificationService)) { /* __GDPR__ "debug/didViewMemory" : { "owner": "connor4312", @@ -747,53 +747,17 @@ CommandsRegistry.registerCommand({ } }); -function tryInstallHexEditor(notifications: INotificationService, progressService: IProgressService, extensionService: IExtensionService, commandService: ICommandService) { - return new Promise(resolve => { - let installing = false; - - const handle = notifications.prompt( - Severity.Info, - localize("viewMemory.prompt", "Inspecting binary data requires the Hex Editor extension. Would you like to install it now?"), [ - { - label: localize("cancel", "Cancel"), - run: () => resolve(false), - }, - { - label: localize("install", "Install"), - run: async () => { - installing = true; - try { - await progressService.withProgress( - { - location: ProgressLocation.Notification, - title: localize("viewMemory.install.progress", "Installing the Hex Editor..."), - }, - async () => { - await commandService.executeCommand('workbench.extensions.installExtension', HEX_EDITOR_EXTENSION_ID); - // it seems like the extension is not registered immediately on install -- - // wait for it to appear before returning. - while (!(await extensionService.getExtension(HEX_EDITOR_EXTENSION_ID))) { - await timeout(30); - } - }, - ); - resolve(true); - } catch (e) { - notifications.error(e as Error); - resolve(false); - } - } - }, - ], - { sticky: true }, - ); - - handle.onDidClose(e => { - if (!installing) { - resolve(false); - } - }); - }); +async function tryInstallHexEditor(extensionsWorkbenchService: IExtensionsWorkbenchService, notificationService: INotificationService): Promise { + try { + await extensionsWorkbenchService.install(HEX_EDITOR_EXTENSION_ID, { + justification: localize("viewMemory.prompt", "Inspecting binary data requires this extension."), + enable: true + }, ProgressLocation.Notification); + return true; + } catch (error) { + notificationService.error(error); + return false; + } } export const BREAK_WHEN_VALUE_CHANGES_ID = 'debug.breakWhenValueChanges'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index c2747ac3285..571b1041e79 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -8,7 +8,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; import { MenuRegistry, MenuId, registerAction2, Action2, ISubmenuItem, IMenuItem, IAction2Options } from 'vs/platform/actions/common/actions'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ExtensionsLocalizedLabel, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel, InstallOperation, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionsLocalizedLabel, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { EnablementState, IExtensionManagementServerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -79,6 +79,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IStringDictionary } from 'vs/base/common/collections'; import { CONTEXT_KEYBINDINGS_EDITOR } from 'vs/workbench/contrib/preferences/common/preferences'; import { DeprecatedExtensionsChecker } from 'vs/workbench/contrib/extensions/browser/deprecatedExtensionsChecker'; +import { ProgressLocation } from 'vs/platform/progress/common/progress'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService, InstantiationType.Eager /* Auto updates extensions */); @@ -318,6 +319,15 @@ CommandsRegistry.registerCommand({ 'description': localize('workbench.extensions.installExtension.option.donotSync', "When enabled, VS Code do not sync this extension when Settings Sync is on."), default: false }, + 'justification': { + 'type': ['string', 'object'], + 'description': localize('workbench.extensions.installExtension.option.justification', "Justification for installing the extension. This is a string or an object that can be used to pass any information to the installation handlers. i.e. `{reason: 'This extension wants to open a URI', action: 'Open URI'}` will show a message box with the reason and action upon install."), + }, + 'enable': { + 'type': 'boolean', + 'description': localize('workbench.extensions.installExtension.option.enable', "When enabled, the extension will be enabled if it is installed but disabled. If the extension is already enabled, this has no effect."), + default: false + }, 'context': { 'type': 'object', 'description': localize('workbench.extensions.installExtension.option.context', "Context for the installation. This is a JSON object that can be used to pass any information to the installation handlers. i.e. `{skipWalkthrough: true}` will skip opening the walkthrough upon install."), @@ -327,31 +337,44 @@ CommandsRegistry.registerCommand({ } ] }, - handler: async (accessor, arg: string | UriComponents, options?: { installOnlyNewlyAddedFromExtensionPackVSIX?: boolean; installPreReleaseVersion?: boolean; donotSync?: boolean; context?: IStringDictionary }) => { + handler: async ( + accessor, + arg: string | UriComponents, + options?: { + installOnlyNewlyAddedFromExtensionPackVSIX?: boolean; + installPreReleaseVersion?: boolean; + donotSync?: boolean; + justification?: string | { reason: string; action: string }; + enable?: boolean; + context?: IStringDictionary; + }) => { const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); const extensionManagementService = accessor.get(IWorkbenchExtensionManagementService); + const extensionGalleryService = accessor.get(IExtensionGalleryService); try { if (typeof arg === 'string') { const [id, version] = getIdAndVersion(arg); - const [extension] = await extensionsWorkbenchService.getExtensions([{ id, preRelease: options?.installPreReleaseVersion }], CancellationToken.None); - if (extension) { - const installOptions: InstallOptions = { + const extension = extensionsWorkbenchService.local.find(e => areSameExtensions(e.identifier, { id, uuid: version })); + if (extension?.enablementState === EnablementState.DisabledByExtensionKind) { + const [gallery] = await extensionGalleryService.getExtensions([{ id, preRelease: options?.installPreReleaseVersion }], CancellationToken.None); + if (gallery) { + throw new Error(localize('notFound', "Extension '{0}' not found.", arg)); + } + await extensionManagementService.installFromGallery(gallery, { isMachineScoped: options?.donotSync ? true : undefined, /* do not allow syncing extensions automatically while installing through the command */ installPreReleaseVersion: options?.installPreReleaseVersion, installGivenVersion: !!version, context: options?.context - }; - if (extension.gallery && extension.enablementState === EnablementState.DisabledByExtensionKind) { - await extensionManagementService.installFromGallery(extension.gallery, installOptions); - return; - } - if (version) { - await extensionsWorkbenchService.installVersion(extension, version, installOptions); - } else { - await extensionsWorkbenchService.install(extension, installOptions); - } + }); } else { - throw new Error(localize('notFound', "Extension '{0}' not found.", arg)); + await extensionsWorkbenchService.install(arg, { + version, + installPreReleaseVersion: options?.installPreReleaseVersion, + context: options?.context, + justification: options?.justification, + enable: options?.enable, + isMachineScoped: options?.donotSync ? true : undefined, /* do not allow syncing extensions automatically while installing through the command */ + }, ProgressLocation.Notification); } } else { const vsix = URI.revive(arg); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 6e0155f3249..1ff80d2256d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1386,7 +1386,7 @@ export class InstallAnotherVersionAction extends ExtensionAction { const [extension] = pick.id !== this.extension?.version ? await this.extensionsWorkbenchService.getExtensions([{ id: this.extension!.identifier.id, preRelease: pick.isPreReleaseVersion }], CancellationToken.None) : [this.extension]; await this.extensionsWorkbenchService.install(extension ?? this.extension!, { installPreReleaseVersion: pick.isPreReleaseVersion }); } else { - await this.extensionsWorkbenchService.installVersion(this.extension!, pick.id, { installPreReleaseVersion: pick.isPreReleaseVersion }); + await this.extensionsWorkbenchService.install(this.extension!, { installPreReleaseVersion: pick.isPreReleaseVersion, version: pick.id }); } } catch (error) { this.instantiationService.createInstance(PromptExtensionInstallFailureAction, this.extension!, pick.latest ? this.extension!.latestVersion : pick.id, InstallOperation.Install, error).run(); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index fa11076ab65..b2e33dd00ae 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import * as semver from 'vs/base/common/semver/semver'; import { Event, Emitter } from 'vs/base/common/event'; -import { index } from 'vs/base/common/arrays'; +import { firstOrDefault, index } from 'vs/base/common/arrays'; import { CancelablePromise, Promises, ThrottledDelayer, createCancelablePromise } from 'vs/base/common/async'; import { CancellationError, isCancellationError } from 'vs/base/common/errors'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -14,8 +14,9 @@ import { IPager, singlePagePager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, - InstallExtensionEvent, DidUninstallExtensionEvent, InstallOperation, InstallOptions, WEB_EXTENSION_TAG, InstallExtensionResult, - IExtensionsControlManifest, InstallVSIXOptions, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo, isTargetPlatformCompatible, InstallExtensionInfo, EXTENSION_IDENTIFIER_REGEX + InstallExtensionEvent, DidUninstallExtensionEvent, InstallOperation, WEB_EXTENSION_TAG, InstallExtensionResult, + IExtensionsControlManifest, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo, isTargetPlatformCompatible, InstallExtensionInfo, EXTENSION_IDENTIFIER_REGEX, + InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService, DefaultIconPath } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, groupByExtension, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -23,7 +24,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { URI } from 'vs/base/common/uri'; -import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, HasOutdatedExtensionsContext, AutoUpdateConfigurationValue } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, HasOutdatedExtensionsContext, AutoUpdateConfigurationValue, InstallExtensionOptions } from 'vs/workbench/contrib/extensions/common/extensions'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url'; import { ExtensionsInput, IExtensionEditorOptions } from 'vs/workbench/contrib/extensions/common/extensionsInput'; @@ -39,7 +40,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { IProductService } from 'vs/platform/product/common/productService'; import { FileAccess } from 'vs/base/common/network'; import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; -import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataAutoSyncService, IUserDataSyncEnablementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { isBoolean, isString, isUndefined } from 'vs/base/common/types'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; @@ -51,6 +52,7 @@ import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUti import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { mainWindow } from 'vs/base/browser/window'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; interface IExtensionStateProvider { (extension: Extension): T; @@ -581,8 +583,8 @@ class Extensions extends Disposable { private onInstallExtension(event: InstallExtensionEvent): void { const { source } = event; if (source && !URI.isUri(source)) { - const extension = this.installed.filter(e => areSameExtensions(e.identifier, source.identifier))[0] - || this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, undefined, source); + const extension = this.installed.find(e => areSameExtensions(e.identifier, source.identifier)) + ?? this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, undefined, source); this.installing.push(extension); this._onChange.fire({ extension }); } @@ -779,6 +781,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension @IFileService private readonly fileService: IFileService, @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @IStorageService private readonly storageService: IStorageService, + @IDialogService private readonly dialogService: IDialogService, + @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, ) { super(); const preferPreReleasesValue = configurationService.getValue('_extensions.preferPreReleases'); @@ -1645,21 +1649,112 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return false; } + async install(arg: string | URI | IExtension, installOptions: InstallExtensionOptions = {}, progressLocation?: ProgressLocation): Promise { + let installable: URI | { extension: IExtension; gallery: IGalleryExtension }; - - install(extension: URI | IExtension, installOptions?: InstallOptions | InstallVSIXOptions, progressLocation?: ProgressLocation): Promise { - return this.doInstall(extension, async () => { - if (extension instanceof URI) { - return this.installFromVSIX(extension, installOptions); + if (arg instanceof URI) { + installable = arg; + } else { + let installableInfo: IExtensionInfo | undefined; + let gallery: IGalleryExtension | undefined; + let extension: IExtension | undefined; + if (isString(arg)) { + installableInfo = { id: arg, version: installOptions.version, preRelease: installOptions?.installPreReleaseVersion ?? this.preferPreReleases }; + extension = this.local.find(e => areSameExtensions(e.identifier, { id: arg })); + } else { + extension = arg; + gallery = arg.gallery; + if (installOptions.version && installOptions.version !== gallery?.version) { + installableInfo = { id: extension.identifier.id, version: installOptions.version }; + } } - if (extension.isMalicious) { + if (installableInfo) { + const targetPlatform = extension?.server ? await extension.server.extensionManagementService.getTargetPlatform() : undefined; + gallery = firstOrDefault(await this.galleryService.getExtensions([installableInfo], { targetPlatform }, CancellationToken.None)); + } + if (!gallery) { + const id = isString(arg) ? arg : (arg).identifier.id; + if (installOptions.version) { + throw new Error(nls.localize('not found version', "Unable to install extension '{0}' because the requested version '{1}' is not found.", id, installOptions.version)); + } else { + throw new Error(nls.localize('not found', "Unable to install extension '{0}' because it is not found.", id)); + } + } + if (!extension) { + extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getReloadStatus(ext), undefined, undefined, gallery); + } + if (extension?.isMalicious) { throw new Error(nls.localize('malicious', "This extension is reported to be problematic.")); } - if (!extension.gallery) { - throw new Error('Missing gallery'); + installable = { extension, gallery }; + if (installOptions.version) { + installOptions.installGivenVersion = true; } - return this.installFromGallery(extension, extension.gallery, installOptions); - }, progressLocation); + } + + let extension: IExtension; + if (installable instanceof URI || !(installOptions.enable && installable.extension.local)) { + if (installOptions.justification) { + const syncCheck = isUndefined(installOptions.isMachineScoped) && this.userDataSyncEnablementService.isEnabled() && this.userDataSyncEnablementService.isResourceEnabled(SyncResource.Extensions); + const result = await this.dialogService.prompt({ + title: nls.localize('installExtensionTitle', "Install Extension"), + message: installable instanceof URI ? nls.localize('installVSIXMessage', "Would you like to install the extension?") : nls.localize('installExtensionMessage', "Would you like to install '{0}' extension from '{1}'?", installable.extension.displayName, installable.extension.publisherDisplayName), + detail: isString(installOptions.justification) ? installOptions.justification : installOptions.justification.reason, + cancelButton: true, + buttons: [{ + label: isString(installOptions.justification) ? nls.localize({ key: 'installButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Install Extension") : nls.localize({ key: 'installButtonLabelWithAction', comment: ['&& denotes a mnemonic'] }, "&&Install Extension and {0}", installOptions.justification.action), + run: () => true + }, { + label: nls.localize('open', "Open Extension"), + run: () => { + if (!(installable instanceof URI)) { + this.open(installable.extension); + } + return false; + } + }], + checkbox: syncCheck ? { + label: nls.localize('sync extension', "Sync this extension"), + checked: true, + } : undefined, + }); + if (!result.result) { + throw new CancellationError(); + } + if (syncCheck) { + installOptions.isMachineScoped = !result.checkboxChecked; + } + } + extension = await this.doInstall(installable instanceof URI ? installable : installable.extension, + () => installable instanceof URI ? this.installFromVSIX(installable, installOptions) : this.installFromGallery(installable.extension, installable.gallery, installOptions), + progressLocation); + } else { + extension = installable.extension; + } + + if (installOptions.version) { + await this.updateAutoUpdateEnablementFor(extension, false); + } + + if (installOptions.enable) { + if (extension.enablementState === EnablementState.DisabledWorkspace || extension.enablementState === EnablementState.DisabledGlobally) { + if (installOptions.justification) { + const result = await this.dialogService.confirm({ + title: nls.localize('enableExtensionTitle', "Enable Extension"), + message: nls.localize('enableExtensionMessage', "Would you like to enable '{0}' extension?", extension.displayName), + detail: isString(installOptions.justification) ? installOptions.justification : installOptions.justification.reason, + primaryButton: isString(installOptions.justification) ? nls.localize({ key: 'enableButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Enable Extension") : nls.localize({ key: 'enableButtonLabelWithAction', comment: ['&& denotes a mnemonic'] }, "&&Enable Extension and {0}", installOptions.justification.action), + }); + if (!result.confirmed) { + throw new CancellationError(); + } + } + await this.setEnablement(extension, extension.enablementState === EnablementState.DisabledWorkspace ? EnablementState.EnabledWorkspace : EnablementState.EnabledGlobally); + } + await this.waitUntilExtensionIsEnabled(extension); + } + + return extension; } async installInServer(extension: IExtension, server: IExtensionManagementServer): Promise { @@ -1741,25 +1836,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension }, () => this.extensionManagementService.uninstall(toUninstall).then(() => undefined)); } - async installVersion(extension: IExtension, version: string, installOptions: InstallOptions = {}): Promise { - extension = await this.doInstall(extension, async () => { - if (!extension.gallery) { - throw new Error('Missing gallery'); - } - - const targetPlatform = extension.server ? await extension.server.extensionManagementService.getTargetPlatform() : undefined; - const [gallery] = await this.galleryService.getExtensions([{ id: extension.gallery.identifier.id, version }], { targetPlatform }, CancellationToken.None); - if (!gallery) { - throw new Error(nls.localize('not found', "Unable to install extension '{0}' because the requested version '{1}' is not found.", extension.gallery.identifier.id, version)); - } - - installOptions.installGivenVersion = true; - return this.installFromGallery(extension, gallery, installOptions); - }); - await this.updateAutoUpdateEnablementFor(extension, false); - return extension; - } - reinstall(extension: IExtension): Promise { return this.doInstall(extension, () => { const ext = extension.local ? extension : this.local.filter(e => areSameExtensions(e.identifier, extension.identifier))[0]; @@ -1849,7 +1925,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension }); } - private async installFromVSIX(vsix: URI, installOptions?: InstallVSIXOptions): Promise { + private async installFromVSIX(vsix: URI, installOptions: InstallOptions): Promise { const manifest = await this.extensionManagementService.getManifest(vsix); const existingExtension = this.local.find(local => areSameExtensions(local.identifier, { id: getGalleryExtensionId(manifest.publisher, manifest.name) })); if (existingExtension) { @@ -1886,6 +1962,27 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return installedExtension; } + private async waitUntilExtensionIsEnabled(extension: IExtension): Promise { + if (this.extensionService.extensions.find(e => ExtensionIdentifier.equals(e.identifier, extension.identifier.id))) { + return; + } + if (!extension.local || !this.extensionService.canAddExtension(toExtensionDescription(extension.local))) { + return; + } + await new Promise((c, e) => { + const disposable = this.extensionService.onDidChangeExtensions(() => { + try { + if (this.extensionService.extensions.find(e => ExtensionIdentifier.equals(e.identifier, extension.identifier.id))) { + disposable.dispose(); + c(); + } + } catch (error) { + e(error); + } + }); + }); + } + private promptAndSetEnablement(extensions: IExtension[], enablementState: EnablementState): Promise { const enable = enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace; if (enable) { diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 3e5d85b7bf2..0158101cc31 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -6,7 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { IPager } from 'vs/base/common/paging'; -import { IQueryOptions, ILocalExtension, IGalleryExtension, IExtensionIdentifier, InstallOptions, InstallVSIXOptions, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IQueryOptions, ILocalExtension, IGalleryExtension, IExtensionIdentifier, InstallOptions, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement'; import { EnablementState, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; @@ -17,8 +17,8 @@ import { IView, IViewPaneContainer } from 'vs/workbench/common/views'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IExtensionsStatus } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionEditorOptions } from 'vs/workbench/contrib/extensions/common/extensionsInput'; -import { ProgressLocation } from 'vs/platform/progress/common/progress'; import { MenuId } from 'vs/platform/actions/common/actions'; +import { ProgressLocation } from 'vs/platform/progress/common/progress'; export const VIEWLET_ID = 'workbench.view.extensions'; @@ -91,6 +91,12 @@ export interface IExtension { export const IExtensionsWorkbenchService = createDecorator('extensionsWorkbenchService'); +export interface InstallExtensionOptions extends InstallOptions { + version?: string; + justification?: string | { reason: string; action: string }; + enable?: boolean; +} + export interface IExtensionsWorkbenchService { readonly _serviceBrand: undefined; readonly onChange: Event; @@ -106,11 +112,11 @@ export interface IExtensionsWorkbenchService { getExtensions(extensionInfos: IExtensionInfo[], token: CancellationToken): Promise; getExtensions(extensionInfos: IExtensionInfo[], options: IExtensionQueryOptions, token: CancellationToken): Promise; canInstall(extension: IExtension): Promise; - install(vsix: URI, installOptions?: InstallVSIXOptions): Promise; - install(extension: IExtension, installOptions?: InstallOptions, progressLocation?: ProgressLocation): Promise; + install(id: string, installOptions?: InstallExtensionOptions, progressLocation?: ProgressLocation): Promise; + install(vsix: URI, installOptions?: InstallExtensionOptions, progressLocation?: ProgressLocation): Promise; + install(extension: IExtension, installOptions?: InstallExtensionOptions, progressLocation?: ProgressLocation): Promise; installInServer(extension: IExtension, server: IExtensionManagementServer): Promise; uninstall(extension: IExtension): Promise; - installVersion(extension: IExtension, version: string, installOptions?: InstallOptions): Promise; reinstall(extension: IExtension): Promise; togglePreRelease(extension: IExtension): Promise; canSetLanguage(extension: IExtension): boolean; diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts index 362e485948a..d87d00552d8 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { createDecorator, refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtension, ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; -import { IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOptions, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, Metadata, InstallVSIXOptions, UninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOptions, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, Metadata, UninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; import { FileAccess } from 'vs/base/common/network'; @@ -56,7 +56,7 @@ export interface IWorkbenchExtensionManagementService extends IProfileAwareExten onDidUninstallExtension: Event; onDidChangeProfile: Event; - installVSIX(location: URI, manifest: IExtensionManifest, installOptions?: InstallVSIXOptions): Promise; + installVSIX(location: URI, manifest: IExtensionManifest, installOptions?: InstallOptions): Promise; installFromLocation(location: URI): Promise; updateFromGallery(gallery: IGalleryExtension, extension: ILocalExtension, installOptions?: InstallOptions): Promise; } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts index 733a388aeca..600b2380d39 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ILocalExtension, IGalleryExtension, InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ILocalExtension, IGalleryExtension, InstallOptions, UninstallOptions, Metadata, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier, ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionManagementChannelClient as BaseExtensionManagementChannelClient, ExtensionEventResult } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; @@ -62,7 +62,7 @@ export abstract class ProfileAwareExtensionManagementChannelClient extends BaseE } } - override async install(vsix: URI, installOptions?: InstallVSIXOptions): Promise { + override async install(vsix: URI, installOptions?: InstallOptions): Promise { installOptions = { ...installOptions, profileLocation: await this.getProfileLocation(installOptions?.profileLocation) }; return super.install(vsix, installOptions); } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 5dcc2ffe5a7..45978737960 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -5,7 +5,7 @@ import { Event, EventMultiplexer } from 'vs/base/common/event'; import { - ILocalExtension, IGalleryExtension, IExtensionIdentifier, IExtensionsControlManifest, IExtensionGalleryService, InstallOptions, UninstallOptions, InstallVSIXOptions, InstallExtensionResult, ExtensionManagementError, ExtensionManagementErrorCode, Metadata, InstallOperation, EXTENSION_INSTALL_SYNC_CONTEXT, InstallExtensionInfo + ILocalExtension, IGalleryExtension, IExtensionIdentifier, IExtensionsControlManifest, IExtensionGalleryService, InstallOptions, UninstallOptions, InstallExtensionResult, ExtensionManagementError, ExtensionManagementErrorCode, Metadata, InstallOperation, EXTENSION_INSTALL_SYNC_CONTEXT, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { DidChangeProfileForServerEvent, DidUninstallExtensionOnServerEvent, IExtensionManagementServer, IExtensionManagementServerService, InstallExtensionOnServerEvent, IWorkbenchExtensionManagementService, UninstallExtensionOnServerEvent } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionType, isLanguagePackExtension, IExtensionManifest, getWorkspaceSupportTypeMessage, TargetPlatform } from 'vs/platform/extensions/common/extensions'; @@ -183,12 +183,12 @@ export class ExtensionManagementService extends Disposable implements IWorkbench throw new Error('Cannot download extension'); } - async install(vsix: URI, options?: InstallVSIXOptions): Promise { + async install(vsix: URI, options?: InstallOptions): Promise { const manifest = await this.getManifest(vsix); return this.installVSIX(vsix, manifest, options); } - async installVSIX(vsix: URI, manifest: IExtensionManifest, options?: InstallVSIXOptions): Promise { + async installVSIX(vsix: URI, manifest: IExtensionManifest, options?: InstallOptions): Promise { const serversToInstall = this.getServersToInstall(manifest); if (serversToInstall?.length) { await this.checkForWorkspaceTrust(manifest); @@ -239,7 +239,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.installFromLocation(location, this.userDataProfileService.currentProfile.extensionsResource); } - protected installVSIXInServer(vsix: URI, server: IExtensionManagementServer, options: InstallVSIXOptions | undefined): Promise { + protected installVSIXInServer(vsix: URI, server: IExtensionManagementServer, options: InstallOptions | undefined): Promise { return server.extensionManagementService.install(vsix, options); } diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService.ts index 51471d93460..2e04440e43a 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { generateUuid } from 'vs/base/common/uuid'; -import { ILocalExtension, IExtensionGalleryService, InstallVSIXOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ILocalExtension, IExtensionGalleryService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; import { ExtensionManagementService as BaseExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -45,7 +45,7 @@ export class ExtensionManagementService extends BaseExtensionManagementService { super(extensionManagementServerService, extensionGalleryService, userDataProfileService, configurationService, productService, downloadService, userDataSyncEnablementService, dialogService, workspaceTrustRequestService, extensionManifestPropertiesService, fileService, logService, instantiationService); } - protected override async installVSIXInServer(vsix: URI, server: IExtensionManagementServer, options: InstallVSIXOptions | undefined): Promise { + protected override async installVSIXInServer(vsix: URI, server: IExtensionManagementServer, options: InstallOptions | undefined): Promise { if (vsix.scheme === Schemas.vscodeRemote && server === this.extensionManagementServerService.localExtensionManagementServer) { const downloadedLocation = joinPath(this.environmentService.tmpDir, generateUuid()); await this.downloadService.download(vsix, downloadedLocation); diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts index 4cbd6b0a943..6953afe5736 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts @@ -6,7 +6,7 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { DidChangeProfileEvent, IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; -import { ILocalExtension, InstallVSIXOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ILocalExtension, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { joinPath } from 'vs/base/common/resources'; @@ -37,7 +37,7 @@ export class NativeExtensionManagementService extends ProfileAwareExtensionManag return applicationScoped || this.uriIdentityService.extUri.isEqual(this.userDataProfileService.currentProfile.extensionsResource, profileLocation); } - override async install(vsix: URI, options?: InstallVSIXOptions): Promise { + override async install(vsix: URI, options?: InstallOptions): Promise { const { location, cleanup } = await this.downloadVsix(vsix); try { return await super.install(location, options); diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts index 68181b0b830..d5e8f029682 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation, InstallOptions, InstallVSIXOptions, ExtensionManagementError, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation, InstallOptions, ExtensionManagementError, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -44,7 +44,7 @@ export class NativeRemoteExtensionManagementService extends RemoteExtensionManag super(channel, userDataProfileService, userDataProfilesService, remoteUserDataProfilesService, uriIdentityService); } - override async install(vsix: URI, options?: InstallVSIXOptions): Promise { + override async install(vsix: URI, options?: InstallOptions): Promise { const local = await super.install(vsix, options); await this.installUIDependenciesAndPackedExtensions(local); return local; diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index d019d14c4fb..6e885b79db8 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -8,27 +8,22 @@ import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IWorkbenchExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IURLHandler, IURLService, IOpenURLOptions } from 'vs/platform/url/common/url'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { ActivationKind, IExtensionService, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IProductService } from 'vs/platform/product/common/productService'; import { disposableWindowInterval } from 'vs/base/browser/dom'; import { mainWindow } from 'vs/base/browser/window'; +import { ICommandService } from 'vs/platform/commands/common/commands'; const FIVE_MINUTES = 5 * 60 * 1000; const THIRTY_SECONDS = 30 * 1000; @@ -111,14 +106,10 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { @IURLService urlService: IURLService, @IExtensionService private readonly extensionService: IExtensionService, @IDialogService private readonly dialogService: IDialogService, - @INotificationService private readonly notificationService: INotificationService, - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @ICommandService private readonly commandService: ICommandService, @IHostService private readonly hostService: IHostService, - @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IStorageService private readonly storageService: IStorageService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IProgressService private readonly progressService: IProgressService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IProductService private readonly productService: IProductService ) { @@ -155,7 +146,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { // The extension is not yet activated, so let's check if it is installed and enabled const extension = await this.extensionService.getExtension(extensionId); if (!extension) { - await this.handleUnhandledURL(uri, { id: extensionId }, options); + await this.handleUnhandledURL(uri, extensionId, options); return true; } else { extensionDisplayName = extension.displayName ?? ''; @@ -244,111 +235,41 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { return await handler.handleURL(uri, options); } - private async handleUnhandledURL(uri: URI, extensionIdentifier: IExtensionIdentifier, options?: IOpenURLOptions): Promise { - const installedExtensions = await this.extensionManagementService.getInstalled(); - let extension = installedExtensions.find(e => areSameExtensions(e.identifier, extensionIdentifier)); + private async handleUnhandledURL(uri: URI, extensionId: string, options?: IOpenURLOptions): Promise { - // Extension is not installed - if (!extension) { - let galleryExtension: IGalleryExtension | undefined; + await this.commandService.executeCommand('workbench.extensions.installExtension', extensionId, { + justification: { + reason: `${localize('installDetail', "This extension wants to open a URI:")}\n${uri.toString()}`, + action: localize('openUri', "Open URI") + }, + enable: true + }); - try { - galleryExtension = (await this.galleryService.getExtensions([extensionIdentifier], CancellationToken.None))[0] ?? undefined; - } catch (err) { - return; - } + const extension = await this.extensionService.getExtension(extensionId); - if (!galleryExtension) { - return; - } - - this.telemetryService.publicLog2('uri_invoked/install_extension/start', { extensionId: extensionIdentifier.id }); - - // Install the Extension and reload the window to handle. - const result = await this.dialogService.confirm({ - message: localize('installAndHandle', "Would you like to install '{0}' extension from '{1}' to open this URI?", galleryExtension.displayName, galleryExtension.publisherDisplayName), - detail: `${localize('installDetail', "'{0}' extension wants to open a URI:", galleryExtension.displayName)}\n\n${uri.toString()}`, - primaryButton: localize({ key: 'install and open', comment: ['&& denotes a mnemonic'] }, "&&Install and Open") - }); - - if (!result.confirmed) { - this.telemetryService.publicLog2('uri_invoked/install_extension/cancel', { extensionId: extensionIdentifier.id }); - return; - } - - this.telemetryService.publicLog2('uri_invoked/install_extension/accept', { extensionId: extensionIdentifier.id }); - - try { - extension = await this.progressService.withProgress({ - location: ProgressLocation.Notification, - title: localize('Installing', "Installing Extension '{0}'...", galleryExtension.displayName || galleryExtension.name) - }, () => this.extensionManagementService.installFromGallery(galleryExtension)); - } catch (error) { - this.notificationService.error(error); - return; - } - } - - // Extension is installed but not enabled - if (!this.extensionEnablementService.isEnabled(extension)) { - this.telemetryService.publicLog2('uri_invoked/enable_extension/start', { extensionId: extensionIdentifier.id }); - const result = await this.dialogService.confirm({ - message: localize('enableAndHandle', "Extension '{0}' is disabled. Would you like to enable the extension and open the URL?", extension.manifest.displayName || extension.manifest.name), - detail: `${extension.manifest.displayName || extension.manifest.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`, - primaryButton: localize({ key: 'enableAndReload', comment: ['&& denotes a mnemonic'] }, "&&Enable and Open") - }); - - if (!result.confirmed) { - this.telemetryService.publicLog2('uri_invoked/enable_extension/cancel', { extensionId: extensionIdentifier.id }); - return; - } - - this.telemetryService.publicLog2('uri_invoked/enable_extension/accept', { extensionId: extensionIdentifier.id }); - await this.extensionEnablementService.setEnablement([extension], EnablementState.EnabledGlobally); - } - - if (this.extensionService.canAddExtension(toExtensionDescription(extension))) { - await this.waitUntilExtensionIsAdded(extensionIdentifier); + if (extension) { await this.handleURL(uri, { ...options, trusted: true }); } /* Extension cannot be added and require window reload */ else { - this.telemetryService.publicLog2('uri_invoked/activate_extension/start', { extensionId: extensionIdentifier.id }); + this.telemetryService.publicLog2('uri_invoked/activate_extension/start', { extensionId }); const result = await this.dialogService.confirm({ - message: localize('reloadAndHandle', "Extension '{0}' is not loaded. Would you like to reload the window to load the extension and open the URL?", extension.manifest.displayName || extension.manifest.name), - detail: `${extension.manifest.displayName || extension.manifest.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`, + message: localize('reloadAndHandle', "Extension '{0}' is not loaded. Would you like to reload the window to load the extension and open the URL?", extensionId), primaryButton: localize({ key: 'reloadAndOpen', comment: ['&& denotes a mnemonic'] }, "&&Reload Window and Open") }); if (!result.confirmed) { - this.telemetryService.publicLog2('uri_invoked/activate_extension/cancel', { extensionId: extensionIdentifier.id }); + this.telemetryService.publicLog2('uri_invoked/activate_extension/cancel', { extensionId }); return; } - this.telemetryService.publicLog2('uri_invoked/activate_extension/accept', { extensionId: extensionIdentifier.id }); + this.telemetryService.publicLog2('uri_invoked/activate_extension/accept', { extensionId }); this.storageService.store(URL_TO_HANDLE, JSON.stringify(uri.toJSON()), StorageScope.WORKSPACE, StorageTarget.MACHINE); await this.hostService.reload(); } } - private async waitUntilExtensionIsAdded(extensionId: IExtensionIdentifier): Promise { - if (!(await this.extensionService.getExtension(extensionId.id))) { - await new Promise((c, e) => { - const disposable = this.extensionService.onDidChangeExtensions(async () => { - try { - if (await this.extensionService.getExtension(extensionId.id)) { - disposable.dispose(); - c(); - } - } catch (error) { - e(error); - } - }); - }); - } - } - // forget about all uris buffered more than 5 minutes ago private garbageCollect(): void { const now = new Date().getTime(); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 4f297a34c83..4a05917faec 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -161,7 +161,7 @@ import { IUserDataProfile, IUserDataProfilesService, toUserDataProfile, UserData import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { EnablementState, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { InstallVSIXOptions, ILocalExtension, IGalleryExtension, InstallOptions, IExtensionIdentifier, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant, Metadata, InstallExtensionResult, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ILocalExtension, IGalleryExtension, InstallOptions, IExtensionIdentifier, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant, Metadata, InstallExtensionResult, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Codicon } from 'vs/base/common/codicons'; import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; import { IRemoteSocketFactoryService, RemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; @@ -2102,7 +2102,7 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens onProfileAwareUninstallExtension = Event.None; onProfileAwareDidUninstallExtension = Event.None; onDidChangeProfile = Event.None; - installVSIX(location: URI, manifest: Readonly, installOptions?: InstallVSIXOptions | undefined): Promise { + installVSIX(location: URI, manifest: Readonly, installOptions?: InstallOptions | undefined): Promise { throw new Error('Method not implemented.'); } installFromLocation(location: URI): Promise { @@ -2121,7 +2121,7 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens getManifest(vsix: URI): Promise> { throw new Error('Method not implemented.'); } - install(vsix: URI, options?: InstallVSIXOptions | undefined): Promise { + install(vsix: URI, options?: InstallOptions | undefined): Promise { throw new Error('Method not implemented.'); } async canInstall(extension: IGalleryExtension): Promise { return false; } From d528f1cd4a2957711d7b948395549adde32ff431 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 27 Feb 2024 14:54:24 -0800 Subject: [PATCH 1651/1897] render first code block ASAP, fix issue with accessibility service --- .../chat/browser/terminalChatController.ts | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 47025af317b..20d8959e32b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -58,8 +58,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _responseSupportsIssueReportingContextKey!: IContextKey; private readonly _sessionResponseVoteContextKey!: IContextKey; - private _requestId: number = 0; - private _messages = this._store.add(new Emitter()); private _currentRequest: ChatRequestModel | undefined; @@ -225,10 +223,12 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (!this._lastInput) { return; } - this._chatAccessibilityService.acceptRequest(); + const accessibilityRequestId = this._chatAccessibilityService.acceptRequest(); this._requestActiveContextKey.set(true); const cancellationToken = new CancellationTokenSource().token; let responseContent = ''; + let firstCodeBlock: string | undefined; + let shellType: string | undefined; const progressCallback = (progress: IChatProgress) => { if (cancellationToken.isCancellationRequested) { return; @@ -240,6 +240,21 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (this._currentRequest) { this._model?.acceptResponseProgress(this._currentRequest, progress); } + if (!firstCodeBlock) { + const firstCodeBlockContent = marked.lexer(responseContent).filter(token => token.type === 'code')?.[0]?.raw; + if (firstCodeBlockContent) { + const regex = /```(?[\w\n]+)\n(?[\s\S]*?)```/g; + const match = regex.exec(firstCodeBlockContent); + firstCodeBlock = match?.groups?.content.trim(); + shellType = match?.groups?.language; + if (firstCodeBlock) { + this._chatWidget?.rawValue?.renderTerminalCommand(firstCodeBlock, accessibilityRequestId, shellType); + this._responseTypeContextKey.set(TerminalChatResponseTypes.TerminalCommand); + this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); + this._messages.fire(Message.ACCEPT_INPUT); + } + } + } }; await this._model?.waitForInitialization(); @@ -280,25 +295,13 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._responseSupportsIssueReportingContextKey.set(supportIssueReporting); } this._lastResponseContent = responseContent; + if (!firstCodeBlock) { + this._chatWidget?.rawValue?.renderMessage(responseContent, accessibilityRequestId, this._currentRequest!.id); + this._responseTypeContextKey.set(TerminalChatResponseTypes.Message); + this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); + this._messages.fire(Message.ACCEPT_INPUT); + } } - const firstCodeBlockContent = marked.lexer(responseContent).filter(token => token.type === 'code')?.[0]?.raw; - const regex = /```(?\w+)\n(?[\s\S]*?)```/g; - const match = regex.exec(firstCodeBlockContent); - const codeBlock = match?.groups?.content.trim(); - const shellType = match?.groups?.language; - this._requestId++; - if (cancellationToken.isCancellationRequested) { - return; - } - if (codeBlock) { - this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._requestId, shellType); - this._responseTypeContextKey.set(TerminalChatResponseTypes.TerminalCommand); - } else { - this._chatWidget?.rawValue?.renderMessage(responseContent, this._requestId, this._currentRequest!.id); - this._responseTypeContextKey.set(TerminalChatResponseTypes.Message); - } - this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); - this._messages.fire(Message.ACCEPT_INPUT); } updateInput(text: string, selectAll = true): void { From 179eeb65ce28e5aae850ae09692995192615e073 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 27 Feb 2024 15:04:43 -0800 Subject: [PATCH 1652/1897] move all accessibility chat service code into controller --- .../chat/browser/terminalChatController.ts | 16 +++++++++------- .../chat/browser/terminalChatWidget.ts | 10 +++------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 20d8959e32b..9ddc1e54349 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -248,7 +248,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr firstCodeBlock = match?.groups?.content.trim(); shellType = match?.groups?.language; if (firstCodeBlock) { - this._chatWidget?.rawValue?.renderTerminalCommand(firstCodeBlock, accessibilityRequestId, shellType); + this._chatWidget?.rawValue?.renderTerminalCommand(firstCodeBlock, shellType); + this._chatAccessibilityService.acceptResponse(firstCodeBlock, accessibilityRequestId); this._responseTypeContextKey.set(TerminalChatResponseTypes.TerminalCommand); this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); this._messages.fire(Message.ACCEPT_INPUT); @@ -290,17 +291,18 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (this._currentRequest) { this._model?.completeResponse(this._currentRequest); } - const supportIssueReporting = this._currentRequest?.response?.agent?.metadata?.supportIssueReporting; - if (supportIssueReporting !== undefined) { - this._responseSupportsIssueReportingContextKey.set(supportIssueReporting); - } this._lastResponseContent = responseContent; - if (!firstCodeBlock) { - this._chatWidget?.rawValue?.renderMessage(responseContent, accessibilityRequestId, this._currentRequest!.id); + if (!firstCodeBlock && this._currentRequest) { + this._chatAccessibilityService.acceptResponse(responseContent, accessibilityRequestId); + this._chatWidget?.rawValue?.renderMessage(responseContent, this._currentRequest.id); this._responseTypeContextKey.set(TerminalChatResponseTypes.Message); this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); this._messages.fire(Message.ACCEPT_INPUT); } + const supportIssueReporting = this._currentRequest?.response?.agent?.metadata?.supportIssueReporting; + if (supportIssueReporting !== undefined) { + this._responseSupportsIssueReportingContextKey.set(supportIssueReporting); + } } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 26efec69639..8400830c1d7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -18,7 +18,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -42,8 +41,7 @@ export class TerminalChatWidget extends Disposable { terminalElement: HTMLElement, private readonly _instance: ITerminalInstance, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService ) { super(); @@ -87,8 +85,7 @@ export class TerminalChatWidget extends Disposable { this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated commands may be incorrect")); } - async renderTerminalCommand(command: string, requestId: number, shellType?: string): Promise { - this._chatAccessibilityService.acceptResponse(command, requestId); + async renderTerminalCommand(command: string, shellType?: string): Promise { this._responseEditor?.show(); if (!this._responseEditor) { this._responseEditor = this._instantiationService.createInstance(TerminalChatResponseEditor, command, shellType, this._container, this._instance); @@ -96,10 +93,9 @@ export class TerminalChatWidget extends Disposable { this._responseEditor.setValue(command); } - renderMessage(message: string, accessibilityRequestId: number, requestId: string): void { + renderMessage(message: string, requestId: string): void { this._responseEditor?.hide(); this._inlineChatWidget.updateChatMessage({ message: new MarkdownString(message), requestId, providerId: 'terminal' }); - this._chatAccessibilityService.acceptResponse(message, accessibilityRequestId); } reveal(): void { From 57ebec79c2ded13f497a279b09ef691e060722a5 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 27 Feb 2024 15:19:27 -0800 Subject: [PATCH 1653/1897] fix width issues --- .../terminalContrib/chat/browser/terminalChatWidget.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 8400830c1d7..3bf19460298 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -116,7 +116,17 @@ export class TerminalChatWidget extends Disposable { if (terminalHeight && top > terminalHeight - this._inlineChatWidget.getHeight()) { this._container.style.top = ''; } + this._updateWidth(); + this._register(this._instance.onDimensionsChanged(() => this._updateWidth())); } + + private _updateWidth() { + const terminalWidth = this._instance.domElement.clientWidth; + if (terminalWidth && terminalWidth < 640) { + this._inlineChatWidget.layout(new Dimension(terminalWidth - 40, this._inlineChatWidget.getHeight())); + } + } + hide(): void { this._container.classList.add('hide'); this._reset(); From 7c5742f4b958f3c5efb2284ebaf1152d4d1abf27 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 27 Feb 2024 15:53:28 -0800 Subject: [PATCH 1654/1897] testing: fix TreeError [Test Explorer List] Tree element not found: [object Object] (#206389) also an issue where we weren't clearing the render list which caused extra work to happen Fixes #202005 --- .../explorerProjections/treeProjection.ts | 19 +++++++-------- ...lByName.test.ts => nameProjection.test.ts} | 0 ...ocation.test.ts => treeProjection.test.ts} | 0 .../testing/test/browser/testObjectTree.ts | 24 ++++++++++++------- 4 files changed, 25 insertions(+), 18 deletions(-) rename src/vs/workbench/contrib/testing/test/browser/explorerProjections/{hierarchalByName.test.ts => nameProjection.test.ts} (100%) rename src/vs/workbench/contrib/testing/test/browser/explorerProjections/{hierarchalByLocation.test.ts => treeProjection.test.ts} (100%) diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/treeProjection.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/treeProjection.ts index d7f385457ae..33060d117ea 100644 --- a/src/vs/workbench/contrib/testing/browser/explorerProjections/treeProjection.ts +++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/treeProjection.ts @@ -253,21 +253,20 @@ export class TreeProjection extends Disposable implements ITestTreeProjection { * @inheritdoc */ public applyTo(tree: ObjectTree) { - for (const s of [this.changedParents, this.resortedParents]) { - for (const element of s) { - if (element && !tree.hasElement(element)) { - s.delete(element); - } + for (const parent of this.changedParents) { + if (!parent || tree.hasElement(parent)) { + tree.setChildren(parent, getChildrenForParent(this.lastState, this.rootsWithChildren, parent), { diffIdentityProvider: testIdentityProvider }); } } - for (const parent of this.changedParents) { - tree.setChildren(parent, getChildrenForParent(this.lastState, this.rootsWithChildren, parent), { diffIdentityProvider: testIdentityProvider }); + for (const parent of this.resortedParents) { + if (!parent || tree.hasElement(parent)) { + tree.resort(parent, false); + } } - for (const parent of this.resortedParents) { - tree.resort(parent, false); - } + this.changedParents.clear(); + this.resortedParents.clear(); } /** diff --git a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByName.test.ts b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/nameProjection.test.ts similarity index 100% rename from src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByName.test.ts rename to src/vs/workbench/contrib/testing/test/browser/explorerProjections/nameProjection.test.ts diff --git a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts similarity index 100% rename from src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts rename to src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts diff --git a/src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts b/src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts index 569661d627d..e0923164c97 100644 --- a/src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts +++ b/src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts @@ -5,14 +5,14 @@ import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; import { Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace'; import { ITestTreeProjection, TestExplorerTreeElement, TestItemTreeElement, TestTreeErrorMessage } from 'vs/workbench/contrib/testing/browser/explorerProjections/index'; import { MainThreadTestCollection } from 'vs/workbench/contrib/testing/common/mainThreadTestCollection'; import { TestsDiff, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes'; import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; import { testStubs } from 'vs/workbench/contrib/testing/test/common/testStubs'; -import { ITreeSorter } from 'vs/base/browser/ui/tree/tree'; +import { ITreeRenderer, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; type SerializedTree = { e: string; children?: SerializedTree[]; data?: string }; @@ -31,14 +31,22 @@ class TestObjectTree extends ObjectTree { }, [ { - disposeTemplate: () => undefined, - renderElement: (node, _index, container: HTMLElement) => { - Object.assign(container.dataset, node.element); - container.textContent = `${node.depth}:${serializer(node.element)}`; + disposeTemplate: ({ store }) => store.dispose(), + renderElement: ({ depth, element }, _index, { container, store }) => { + const render = () => { + container.textContent = `${depth}:${serializer(element)}`; + Object.assign(container.dataset, element); + }; + render(); + + if (element instanceof TestItemTreeElement) { + store.add(element.onChange(render)); + } }, - renderTemplate: c => c, + disposeElement: (_el, _index, { store }) => store.clear(), + renderTemplate: container => ({ container, store: new DisposableStore() }), templateId: 'default' - } + } as ITreeRenderer ], { sorter: sorter ?? { From 6b1906a0ecc930c2214ef0b4ca36d228eef2535b Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 27 Feb 2024 21:11:54 -0800 Subject: [PATCH 1655/1897] Enable AUX window for Interactive Window (#206398) --- .../contrib/interactive/browser/interactiveEditor.ts | 5 ++++- .../contrib/interactive/browser/interactiveEditorInput.ts | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index af62b50a84c..e0f91695461 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -362,6 +362,8 @@ export class InteractiveEditor extends EditorPane { this._widgetDisposableStore.clear(); + const codeWindow = this.group ? DOM.getWindowById(group.windowId, true).window : mainWindow; + this._notebookWidget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, group, notebookInput, { isEmbedded: true, isReadOnly: true, @@ -385,7 +387,8 @@ export class InteractiveEditor extends EditorPane { HoverController.ID, MarkerController.ID ]), - options: this._notebookOptions + options: this._notebookOptions, + codeWindow: codeWindow }, undefined, this._rootElement ? DOM.getWindow(this._rootElement) : mainWindow); this._codeEditorWidget = this._instantiationService.createInstance(CodeEditorWidget, this._inputEditorContainer, this._editorOptions, { diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts index e8317893d1e..17dc7e1b09b 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts @@ -132,7 +132,6 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot override get capabilities(): EditorInputCapabilities { return EditorInputCapabilities.Untitled | EditorInputCapabilities.Readonly - | EditorInputCapabilities.AuxWindowUnsupported | EditorInputCapabilities.Scratchpad; } From d4a6faf8e0382c6cc6aff628d2d5494910a2c31d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 28 Feb 2024 08:29:04 +0100 Subject: [PATCH 1656/1897] Strange close icon in Switch window (fix #206391) (#206404) --- .../chat/electron-sandbox/actions/media/voiceChatActions.css | 2 ++ .../contrib/search/browser/media/anythingQuickAccess.css | 1 + src/vs/workbench/electron-sandbox/actions/windowActions.ts | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css b/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css index 7a43cef7d12..900330afe18 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css @@ -9,6 +9,7 @@ .monaco-workbench .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, .monaco-workbench .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { content: "\ec1c"; + font-family: 'codicon'; } /* @@ -25,4 +26,5 @@ .monaco-workbench.reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, .monaco-workbench.reduce-motion .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { content: "\ead7"; + font-family: 'codicon'; } diff --git a/src/vs/workbench/contrib/search/browser/media/anythingQuickAccess.css b/src/vs/workbench/contrib/search/browser/media/anythingQuickAccess.css index 55fe37ea1e7..7804edf9678 100644 --- a/src/vs/workbench/contrib/search/browser/media/anythingQuickAccess.css +++ b/src/vs/workbench/contrib/search/browser/media/anythingQuickAccess.css @@ -5,4 +5,5 @@ .quick-input-list .quick-input-list-entry.has-actions:hover .quick-input-list-entry-action-bar .action-label.dirty-anything::before { content: "\ea76"; /* Close icon flips between black dot and "X" for dirty open editors */ + font-family: 'codicon'; } diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index 0be3e3918c7..5693f076eec 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -212,7 +212,7 @@ abstract class BaseSwitchWindow extends Action2 { }; private readonly closeDirtyWindowAction: IQuickInputButton = { - iconClass: 'dirty-window ' + Codicon.closeDirty, + iconClass: 'dirty-window ' + ThemeIcon.asClassName(Codicon.closeDirty), tooltip: localize('close', "Close Window"), alwaysVisible: true }; From 6504145957b330d6dbbfa83ec6b09c039d4a87ae Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 28 Feb 2024 09:27:56 +0100 Subject: [PATCH 1657/1897] Fix for native and custom hover over view titles (#206408) fix #206367 --- src/vs/workbench/browser/parts/views/viewPane.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index 06b591f477a..84e11baf381 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -342,7 +342,9 @@ export abstract class ViewPane extends Pane implements IView { private readonly showActions: ViewPaneShowActions; private headerContainer?: HTMLElement; private titleContainer?: HTMLElement; + private titleContainerHover?: ICustomHover; private titleDescriptionContainer?: HTMLElement; + private titleDescriptionContainerHover?: ICustomHover; private iconContainer?: HTMLElement; private iconContainerHover?: ICustomHover; protected twistiesContainer?: HTMLElement; @@ -523,7 +525,7 @@ export abstract class ViewPane extends Pane implements IView { const calculatedTitle = this.calculateTitle(title); this.titleContainer = append(container, $('h3.title', {}, calculatedTitle)); - setupCustomHover(getDefaultHoverDelegate('mouse'), this.titleContainer, calculatedTitle); + this.titleContainerHover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.titleContainer, calculatedTitle)); if (this._titleDescription) { this.setTitleDescription(this._titleDescription); @@ -537,7 +539,7 @@ export abstract class ViewPane extends Pane implements IView { const calculatedTitle = this.calculateTitle(title); if (this.titleContainer) { this.titleContainer.textContent = calculatedTitle; - this.titleContainer.setAttribute('title', calculatedTitle); + this.titleContainerHover?.update(calculatedTitle); } if (this.iconContainer) { @@ -552,10 +554,11 @@ export abstract class ViewPane extends Pane implements IView { private setTitleDescription(description: string | undefined) { if (this.titleDescriptionContainer) { this.titleDescriptionContainer.textContent = description ?? ''; - this.titleDescriptionContainer.setAttribute('title', description ?? ''); + this.titleDescriptionContainerHover?.update(description ?? ''); } else if (description && this.titleContainer) { - this.titleDescriptionContainer = after(this.titleContainer, $('span.description', { title: description }, description)); + this.titleDescriptionContainer = after(this.titleContainer, $('span.description', {}, description)); + this.titleDescriptionContainerHover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.titleDescriptionContainer, description)); } } From e25f4217893a7cda4c4422a5ea7883ea796c8714 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 28 Feb 2024 10:44:26 +0100 Subject: [PATCH 1658/1897] fix #82524 (#206415) --- package.json | 4 ++-- remote/package.json | 2 +- remote/yarn.lock | 8 ++++++++ src/vs/base/node/zip.ts | 4 ++-- yarn.lock | 15 +++++++++++++++ 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 30ed28ac516..947ec6bd4ee 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.0.0", - "yauzl": "^2.9.2", + "yauzl": "^3.0.0", "yazl": "^2.4.3" }, "devDependencies": { @@ -127,7 +127,7 @@ "@types/wicg-file-system-access": "^2020.9.6", "@types/windows-foreground-love": "^0.3.0", "@types/winreg": "^1.2.30", - "@types/yauzl": "^2.9.1", + "@types/yauzl": "^2.10.0", "@types/yazl": "^2.4.2", "@typescript-eslint/eslint-plugin": "^5.57.0", "@typescript-eslint/experimental-utils": "^5.57.0", diff --git a/remote/package.json b/remote/package.json index 1d341b00a20..a3af2b99fe4 100644 --- a/remote/package.json +++ b/remote/package.json @@ -34,7 +34,7 @@ "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.0.0", - "yauzl": "^2.9.2", + "yauzl": "^3.0.0", "yazl": "^2.4.3" } } diff --git a/remote/yarn.lock b/remote/yarn.lock index 5fdbd0988ea..bcd7160cadc 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -643,6 +643,14 @@ yauzl@^2.9.2: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" +yauzl@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-3.1.1.tgz#d85503cc34933c0bcb3646ee2b97afedbebe32e7" + integrity sha512-MPxA7oN5cvGV0wzfkeHKF2/+Q4TkMpHSWGRy/96I4Cozljmx0ph91+Muxh6HegEtDC4GftJ8qYDE51vghFiEYA== + dependencies: + buffer-crc32 "~0.2.3" + pend "~1.2.0" + yazl@^2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.4.3.tgz#ec26e5cc87d5601b9df8432dbdd3cd2e5173a071" diff --git a/src/vs/base/node/zip.ts b/src/vs/base/node/zip.ts index 3cb7bc9795b..c0d9b4b8ecb 100644 --- a/src/vs/base/node/zip.ts +++ b/src/vs/base/node/zip.ts @@ -164,7 +164,7 @@ async function openZip(zipFile: string, lazy: boolean = false): Promise const { open } = await import('yauzl'); return new Promise((resolve, reject) => { - open(zipFile, lazy ? { lazyEntries: true } : undefined!, (error?: Error, zipfile?: ZipFile) => { + open(zipFile, lazy ? { lazyEntries: true } : undefined!, (error: Error | null, zipfile?: ZipFile) => { if (error) { reject(toExtractError(error)); } else { @@ -176,7 +176,7 @@ async function openZip(zipFile: string, lazy: boolean = false): Promise function openZipStream(zipFile: ZipFile, entry: Entry): Promise { return new Promise((resolve, reject) => { - zipFile.openReadStream(entry, (error?: Error, stream?: Readable) => { + zipFile.openReadStream(entry, (error: Error | null, stream?: Readable) => { if (error) { reject(toExtractError(error)); } else { diff --git a/yarn.lock b/yarn.lock index 056c1e1bce0..f434a0d1e6a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1255,6 +1255,13 @@ resolved "https://registry.yarnpkg.com/@types/winreg/-/winreg-1.2.30.tgz#91d6710e536d345b9c9b017c574cf6a8da64c518" integrity sha1-kdZxDlNtNFucmwF8V0z2qNpkxRg= +"@types/yauzl@^2.10.0": + version "2.10.3" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" + integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== + dependencies: + "@types/node" "*" + "@types/yauzl@^2.9.1": version "2.9.1" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" @@ -10464,6 +10471,14 @@ yauzl@^2.2.1: buffer-crc32 "~0.2.3" fd-slicer "~1.0.1" +yauzl@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-3.1.1.tgz#d85503cc34933c0bcb3646ee2b97afedbebe32e7" + integrity sha512-MPxA7oN5cvGV0wzfkeHKF2/+Q4TkMpHSWGRy/96I4Cozljmx0ph91+Muxh6HegEtDC4GftJ8qYDE51vghFiEYA== + dependencies: + buffer-crc32 "~0.2.3" + pend "~1.2.0" + yazl@^2.2.1, yazl@^2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.4.3.tgz#ec26e5cc87d5601b9df8432dbdd3cd2e5173a071" From 7a8f2191222169dfa6cfd871a135f006a42166d9 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 28 Feb 2024 11:10:57 +0100 Subject: [PATCH 1659/1897] retain telemetry (#206417) --- .../extensions/browser/extensionUrlHandler.ts | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index 6e885b79db8..65672a170be 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -24,6 +24,9 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { disposableWindowInterval } from 'vs/base/browser/dom'; import { mainWindow } from 'vs/base/browser/window'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { isCancellationError } from 'vs/base/common/errors'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; const FIVE_MINUTES = 5 * 60 * 1000; const THIRTY_SECONDS = 30 * 1000; @@ -84,6 +87,18 @@ type ExtensionUrlHandlerClassification = { comment: 'This is used to understand the drop funnel of extension URI handling by the OS & VS Code.'; }; +interface ExtensionUrlReloadHandlerEvent { + readonly extensionId: string; + readonly isRemote: boolean; +} + +type ExtensionUrlReloadHandlerClassification = { + owner: 'sandy081'; + readonly extensionId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The ID of the extension that should handle the URI' }; + readonly isRemote: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'Whether the current window is a remote window' }; + comment: 'This is used to understand the drop funnel of extension URI handling by the OS & VS Code.'; +}; + /** * This class handles URLs which are directed towards extensions. * If a URL is directed towards an inactive extension, it buffers it, @@ -111,7 +126,9 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { @IStorageService private readonly storageService: IStorageService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IProductService private readonly productService: IProductService + @INotificationService private readonly notificationService: INotificationService, + @IProductService private readonly productService: IProductService, + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService ) { this.userTrustedExtensionsStorage = new UserTrustedExtensionIdStorage(storageService); @@ -237,13 +254,26 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { private async handleUnhandledURL(uri: URI, extensionId: string, options?: IOpenURLOptions): Promise { - await this.commandService.executeCommand('workbench.extensions.installExtension', extensionId, { - justification: { - reason: `${localize('installDetail', "This extension wants to open a URI:")}\n${uri.toString()}`, - action: localize('openUri', "Open URI") - }, - enable: true - }); + this.telemetryService.publicLog2('uri_invoked/install_extension/start', { extensionId }); + + try { + await this.commandService.executeCommand('workbench.extensions.installExtension', extensionId, { + justification: { + reason: `${localize('installDetail', "This extension wants to open a URI:")}\n${uri.toString()}`, + action: localize('openUri', "Open URI") + }, + enable: true + }); + this.telemetryService.publicLog2('uri_invoked/install_extension/accept', { extensionId }); + } catch (error) { + if (isCancellationError(error)) { + this.telemetryService.publicLog2('uri_invoked/install_extension/cancel', { extensionId }); + } else { + this.telemetryService.publicLog2('uri_invoked/install_extension/error', { extensionId }); + this.notificationService.error(error); + } + return; + } const extension = await this.extensionService.getExtension(extensionId); @@ -253,18 +283,16 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { /* Extension cannot be added and require window reload */ else { - this.telemetryService.publicLog2('uri_invoked/activate_extension/start', { extensionId }); + this.telemetryService.publicLog2('uri_invoked/install_extension/reload', { extensionId, isRemote: !!this.workbenchEnvironmentService.remoteAuthority }); const result = await this.dialogService.confirm({ message: localize('reloadAndHandle', "Extension '{0}' is not loaded. Would you like to reload the window to load the extension and open the URL?", extensionId), primaryButton: localize({ key: 'reloadAndOpen', comment: ['&& denotes a mnemonic'] }, "&&Reload Window and Open") }); if (!result.confirmed) { - this.telemetryService.publicLog2('uri_invoked/activate_extension/cancel', { extensionId }); return; } - this.telemetryService.publicLog2('uri_invoked/activate_extension/accept', { extensionId }); this.storageService.store(URL_TO_HANDLE, JSON.stringify(uri.toJSON()), StorageScope.WORKSPACE, StorageTarget.MACHINE); await this.hostService.reload(); } From 729612b22ccb9324d1feb6579e85910d3f8b090e Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 28 Feb 2024 11:52:22 +0100 Subject: [PATCH 1660/1897] Enable pre-fetching of comments (#206421) * Revert (most of) "Commenting range resource change proposal (#206346)" This reverts commit 03bd0bb8d117dbe4ea13da19b03b82e8b70194a8. * Enable pre-fetching of comments Part of #185551 --- src/vs/editor/common/languages.ts | 8 ---- .../api/browser/mainThreadComments.ts | 8 ---- .../workbench/api/common/extHost.protocol.ts | 1 - .../workbench/api/common/extHostComments.ts | 17 ++------ .../comments/browser/commentService.ts | 41 +++++++++++-------- .../comments/browser/commentsController.ts | 13 ++++-- .../common/extensionsApiProposals.ts | 1 - ...posed.commentingRangeResourcesChanged.d.ts | 11 ----- 8 files changed, 38 insertions(+), 62 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.commentingRangeResourcesChanged.d.ts diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 01eb0df109e..16550bdef8d 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1888,14 +1888,6 @@ export interface CommentThreadChangedEvent { readonly changed: CommentThread[]; } -/** - * @internal - */ -export interface CommentingRangeResources { - schemes: string[]; - uris: URI[]; -} - export interface CodeLens { range: IRange; id?: string; diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index f885f89333a..538c754e1b3 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -550,14 +550,6 @@ export class MainThreadComments extends Disposable implements MainThreadComments provider.updateFeatures(features); } - async $onDidChangeResourcesWithCommentingRanges(handle: number, schemes: string[], uris: UriComponents[]): Promise { - const provider = this._commentControllers.get(handle); - if (!provider) { - return; - } - this._commentService.setResourcesWithCommentingRanges(provider.id, { schemes, uris: uris.map(uri => URI.revive(uri)) }); - } - $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 869fc7dc50d..d2ae7af630d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -146,7 +146,6 @@ export interface MainThreadCommentsShape extends IDisposable { $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, changes: CommentThreadChanges): void; $deleteCommentThread(handle: number, commentThreadHandle: number): void; $updateCommentingRanges(handle: number): void; - $onDidChangeResourcesWithCommentingRanges(handle: number, schemes: string[], uris: UriComponents[]): Promise; } export interface AuthenticationForceNewSessionOptions { diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 18764746b77..9db6a543d21 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -194,16 +194,16 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo commentController?.$deleteCommentThread(commentThreadHandle); } - $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise<{ ranges: IRange[]; fileComments: boolean } | undefined> { + async $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise<{ ranges: IRange[]; fileComments: boolean } | undefined> { const commentController = this._commentControllers.get(commentControllerHandle); if (!commentController || !commentController.commentingRangeProvider) { return Promise.resolve(undefined); } - const document = documents.getDocument(URI.revive(uriComponents)); + const document = await documents.ensureDocumentData(URI.revive(uriComponents)); return asPromise(async () => { - const rangesResult = await (commentController.commentingRangeProvider as vscode.CommentingRangeProvider2).provideCommentingRanges(document, token); + const rangesResult = await (commentController.commentingRangeProvider as vscode.CommentingRangeProvider2).provideCommentingRanges(document.document, token); let ranges: { ranges: vscode.Range[]; fileComments: boolean } | undefined; if (Array.isArray(rangesResult)) { ranges = { @@ -563,17 +563,8 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo return this._commentingRangeProvider; } - private _commentingRangeProviderResourcesChanged: vscode.Disposable | undefined; set commentingRangeProvider(provider: vscode.CommentingRangeProvider | undefined) { this._commentingRangeProvider = provider; - this._commentingRangeProviderResourcesChanged?.dispose(); - this._commentingRangeProviderResourcesChanged = undefined; - if (this._commentingRangeProvider?.onDidChangeResourcesWithCommentingRanges) { - checkProposedApiEnabled(this._extension, 'commentingRangeResourcesChanged'); - this._commentingRangeProviderResourcesChanged = this._commentingRangeProvider.onDidChangeResourcesWithCommentingRanges(e => { - proxy.$onDidChangeResourcesWithCommentingRanges(this.handle, e.schemes, e.resources); - }); - } proxy.$updateCommentingRanges(this.handle); } @@ -704,7 +695,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo this._threads.forEach(value => { value.dispose(); }); - this._commentingRangeProviderResourcesChanged?.dispose(); + this._localDisposables.forEach(disposable => disposable.dispose()); } } diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index 2d9dcdde5e6..c4747664064 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread, CommentOptions, PendingCommentThread, CommentingRangeResources } from 'vs/editor/common/languages'; +import { CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread, CommentOptions, PendingCommentThread } from 'vs/editor/common/languages'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; @@ -21,6 +21,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; import { ILogService } from 'vs/platform/log/common/log'; import { CommentsModel, ICommentsModel } from 'vs/workbench/contrib/comments/browser/commentsModel'; +import { IModelService } from 'vs/editor/common/services/model'; export const ICommentService = createDecorator('commentService'); @@ -112,7 +113,6 @@ export interface ICommentService { enableCommenting(enable: boolean): void; registerContinueOnCommentProvider(provider: IContinueOnCommentProvider): IDisposable; removeContinueOnComment(pendingComment: { range: IRange | undefined; uri: URI; owner: string; isReply?: boolean }): PendingCommentThread | undefined; - setResourcesWithCommentingRanges(owner: string, resources: CommentingRangeResources): void; resourceHasCommentingRanges(resource: URI): boolean; } @@ -171,7 +171,7 @@ export class CommentService extends Disposable implements ICommentService { private readonly _commentsModel: CommentsModel = this._register(new CommentsModel()); public readonly commentsModel: ICommentsModel = this._commentsModel; - private _commentingRangeResources = new Map(); // owner -> CommentingRangeResources + private _commentingRangeResources = new Set(); // URIs constructor( @IInstantiationService protected readonly instantiationService: IInstantiationService, @@ -179,7 +179,8 @@ export class CommentService extends Disposable implements ICommentService { @IConfigurationService private readonly configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, @IStorageService private readonly storageService: IStorageService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IModelService private readonly modelService: IModelService ) { super(); this._handleConfiguration(); @@ -222,6 +223,21 @@ export class CommentService extends Disposable implements ICommentService { } this._saveContinueOnComments(map); })); + + this._register(this.modelService.onModelAdded(model => { + // Allows comment providers to cause their commenting ranges to be prefetched by opening text documents in the background. + if (!this._commentingRangeResources.has(model.uri.toString())) { + this.getDocumentComments(model.uri); + } + })); + } + + private _updateResourcesWithCommentingRanges(resource: URI, commentInfos: (ICommentInfo | null)[]) { + for (const comments of commentInfos) { + if (comments && (comments.commentingRanges.ranges.length > 0 || comments.threads.length > 0)) { + this._commentingRangeResources.add(resource.toString()); + } + } } private _handleConfiguration() { @@ -437,7 +453,9 @@ export class CommentService extends Disposable implements ICommentService { })); } - return Promise.all(commentControlResult); + const commentInfos = await Promise.all(commentControlResult); + this._updateResourcesWithCommentingRanges(resource, commentInfos); + return commentInfos; } async getNotebookComments(resource: URI): Promise<(INotebookCommentInfo | null)[]> { @@ -499,18 +517,7 @@ export class CommentService extends Disposable implements ICommentService { return changedOwners; } - setResourcesWithCommentingRanges(owner: string, resources: CommentingRangeResources): void { - this._commentingRangeResources.set(owner, resources); - } - resourceHasCommentingRanges(resource: URI): boolean { - for (const resources of this._commentingRangeResources.values()) { - if (resources.schemes.includes(resource.scheme)) { - return true; - } else if (resources.uris.some(uri => uri.toString() === resource.toString())) { - return true; - } - } - return false; + return this._commentingRangeResources.has(resource.toString()); } } diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index 1f795367c89..1fdee1beb9f 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -13,7 +13,7 @@ import 'vs/css!./media/review'; import { ICodeEditor, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { EditorType, IDiffEditor, IEditorContribution } from 'vs/editor/common/editorCommon'; +import { EditorType, IDiffEditor, IEditorContribution, IModelChangedEvent } from 'vs/editor/common/editorCommon'; import { IModelDecorationOptions, IModelDeltaDecoration } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import * as languages from 'vs/editor/common/languages'; @@ -462,6 +462,7 @@ export class CommentController implements IEditorContribution { } })); + this.globalToDispose.add(this.editor.onWillChangeModel(e => this.onWillChangeModel(e))); this.globalToDispose.add(this.editor.onDidChangeModel(_ => this.onModelChanged())); this.globalToDispose.add(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('diffEditor.renderSideBySide')) { @@ -778,6 +779,12 @@ export class CommentController implements IEditorContribution { this.editor = null!; // Strict null override - nulling out in dispose } + private onWillChangeModel(e: IModelChangedEvent): void { + if (e.newModelUrl) { + this.tryUpdateReservedSpace(e.newModelUrl); + } + } + public onModelChanged(): void { this.localToDispose.clear(); this.tryUpdateReservedSpace(); @@ -1190,7 +1197,7 @@ export class CommentController implements IEditorContribution { }); } - private tryUpdateReservedSpace() { + private tryUpdateReservedSpace(uri?: URI) { if (!this.editor) { return; } @@ -1199,7 +1206,7 @@ export class CommentController implements IEditorContribution { const hasRanges = Boolean(info.commentingRanges && (Array.isArray(info.commentingRanges) ? info.commentingRanges : info.commentingRanges.ranges).length); return hasRanges || (info.threads.length > 0); }); - const uri = this.editor.getModel()?.uri; + uri = uri ?? this.editor.getModel()?.uri; const resourceHasCommentingRanges = uri ? this.commentService.resourceHasCommentingRanges(uri) : false; const hasCommentsOrRanges = hasCommentsOrRangesInInfo || resourceHasCommentingRanges; diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 7e70af867c3..7386529c446 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -21,7 +21,6 @@ export const allApiProposals = Object.freeze({ codeActionRanges: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts', codiconDecoration: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts', commentReactor: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentReactor.d.ts', - commentingRangeResourcesChanged: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentingRangeResourcesChanged.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', contribCommentPeekContext: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentPeekContext.d.ts', diff --git a/src/vscode-dts/vscode.proposed.commentingRangeResourcesChanged.d.ts b/src/vscode-dts/vscode.proposed.commentingRangeResourcesChanged.d.ts deleted file mode 100644 index 7a4f22aecf7..00000000000 --- a/src/vscode-dts/vscode.proposed.commentingRangeResourcesChanged.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 CommentingRangeProvider { - readonly onDidChangeResourcesWithCommentingRanges?: Event<{ schemes: string[]; resources: Uri[] }>; - } -} From 763df0e037e2a0fa251937a806191d2fd0d509ba Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 28 Feb 2024 11:55:19 +0100 Subject: [PATCH 1661/1897] add `ExtensionContent#languageModelAccessInformation` (#206420) * makes ExtHostAuth and ExtHostLM injectable * (hack) makes silents auth requests for all extension upon LM registration https://github.com/microsoft/vscode/issues/206265 --- .../api/browser/mainThreadLanguageModels.ts | 5 +- .../workbench/api/common/extHost.api.impl.ts | 18 ++-- .../api/common/extHost.common.services.ts | 4 + .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostAuthentication.ts | 16 +++- .../api/common/extHostExtensionService.ts | 4 + .../api/common/extHostLanguageModels.ts | 94 +++++++++++++++---- .../contrib/chat/common/languageModels.ts | 9 +- .../vscode.proposed.languageModels.d.ts | 46 +++++++-- 9 files changed, 153 insertions(+), 45 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts index f8167d4dc52..a4713d12239 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { coalesce } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -36,7 +37,7 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatProvider); - this._proxy.$updateLanguageModels({ added: _chatProviderService.getLanguageModelIds() }); + this._proxy.$updateLanguageModels({ added: coalesce(_chatProviderService.getLanguageModelIds().map(id => _chatProviderService.lookupLanguageModel(id))) }); this._store.add(_chatProviderService.onDidChangeLanguageModels(this._proxy.$updateLanguageModels, this._proxy)); } @@ -91,7 +92,7 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { await Promise.race([ activate, - Event.toPromise(Event.filter(this._chatProviderService.onDidChangeLanguageModels, e => Boolean(e.added?.includes(providerId)))) + Event.toPromise(Event.filter(this._chatProviderService.onDidChangeLanguageModels, e => Boolean(e.added?.some(value => value.identifier === providerId)))) ]); return this._chatProviderService.lookupLanguageModel(providerId); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 8a99d63cb4c..da678cb6efb 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -25,11 +25,11 @@ import { CandidatePortSource, ExtHostContext, ExtHostLogLevelServiceShape, MainC import { ExtHostRelatedInformation } from 'vs/workbench/api/common/extHostAiRelatedInformation'; import { ExtHostApiCommands } from 'vs/workbench/api/common/extHostApiCommands'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; -import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; +import { IExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits'; import { ExtHostChat } from 'vs/workbench/api/common/extHostChat'; import { ExtHostChatAgents2 } from 'vs/workbench/api/common/extHostChatAgents2'; -import { ExtHostLanguageModels } from 'vs/workbench/api/common/extHostLanguageModels'; +import { IExtHostLanguageModels } from 'vs/workbench/api/common/extHostLanguageModels'; import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariables'; import { ExtHostClipboard } from 'vs/workbench/api/common/extHostClipboard'; import { ExtHostEditorInsets } from 'vs/workbench/api/common/extHostCodeInsets'; @@ -143,6 +143,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostSecretState = accessor.get(IExtHostSecretState); const extHostEditorTabs = accessor.get(IExtHostEditorTabs); const extHostManagedSockets = accessor.get(IExtHostManagedSockets); + const extHostAuthentication = accessor.get(IExtHostAuthentication); + const extHostLanguageModels = accessor.get(IExtHostLanguageModels); // register addressable instances rpcProtocol.set(ExtHostContext.ExtHostFileSystemInfo, extHostFileSystemInfo); @@ -157,6 +159,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I rpcProtocol.set(ExtHostContext.ExtHostTelemetry, extHostTelemetry); rpcProtocol.set(ExtHostContext.ExtHostEditorTabs, extHostEditorTabs); rpcProtocol.set(ExtHostContext.ExtHostManagedSockets, extHostManagedSockets); + rpcProtocol.set(ExtHostContext.ExtHostAuthentication, extHostAuthentication); + rpcProtocol.set(ExtHostContext.ExtHostChatProvider, extHostLanguageModels); // automatically create and register addressable instances const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations)); @@ -196,7 +200,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress))); const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHostLabelService, new ExtHostLabelService(rpcProtocol)); const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol)); - const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol)); const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands)); const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.remote, extHostWorkspace, extHostLogService, extHostApiDeprecation)); const extHostWebviewPanels = rpcProtocol.set(ExtHostContext.ExtHostWebviewPanels, new ExtHostWebviewPanels(rpcProtocol, extHostWebviews, extHostWorkspace)); @@ -207,7 +210,6 @@ 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 ExtHostLanguageModels(rpcProtocol, extHostLogService, extHostAuthentication)); const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostLogService, extHostCommands)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol)); @@ -1413,7 +1415,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const chat: typeof vscode.chat = { registerChatResponseProvider(id: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata) { checkProposedApiEnabled(extension, 'chatProvider'); - return extHostChatProvider.registerLanguageModel(extension, id, provider, metadata); + return extHostLanguageModels.registerLanguageModel(extension, id, provider, metadata); }, registerChatVariableResolver(name: string, description: string, resolver: vscode.ChatVariableResolver) { checkProposedApiEnabled(extension, 'chatVariableResolver'); @@ -1433,15 +1435,15 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const lm: typeof vscode.lm = { get languageModels() { checkProposedApiEnabled(extension, 'languageModels'); - return extHostChatProvider.getLanguageModelIds(); + return extHostLanguageModels.getLanguageModelIds(); }, onDidChangeLanguageModels: (listener, thisArgs?, disposables?) => { checkProposedApiEnabled(extension, 'languageModels'); - return extHostChatProvider.onDidChangeProviders(listener, thisArgs, disposables); + return extHostLanguageModels.onDidChangeProviders(listener, thisArgs, disposables); }, sendChatRequest(languageModel: string, messages: vscode.LanguageModelChatMessage[], options: vscode.LanguageModelChatRequestOptions, token: vscode.CancellationToken) { checkProposedApiEnabled(extension, 'languageModels'); - return extHostChatProvider.sendChatRequest(extension, languageModel, messages, options, token); + return extHostLanguageModels.sendChatRequest(extension, languageModel, messages, options, token); } }; diff --git a/src/vs/workbench/api/common/extHost.common.services.ts b/src/vs/workbench/api/common/extHost.common.services.ts index faf45b596a7..d48b0d47391 100644 --- a/src/vs/workbench/api/common/extHost.common.services.ts +++ b/src/vs/workbench/api/common/extHost.common.services.ts @@ -28,11 +28,15 @@ import { ILoggerService } from 'vs/platform/log/common/log'; import { ExtHostVariableResolverProviderService, IExtHostVariableResolverProvider } from 'vs/workbench/api/common/extHostVariableResolverService'; import { ExtHostLocalizationService, IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService'; import { ExtHostManagedSockets, IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets'; +import { ExtHostAuthentication, IExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; +import { ExtHostLanguageModels, IExtHostLanguageModels } from 'vs/workbench/api/common/extHostLanguageModels'; registerSingleton(IExtHostLocalizationService, ExtHostLocalizationService, InstantiationType.Delayed); registerSingleton(ILoggerService, ExtHostLoggerService, InstantiationType.Delayed); registerSingleton(IExtHostApiDeprecationService, ExtHostApiDeprecationService, InstantiationType.Delayed); registerSingleton(IExtHostCommands, ExtHostCommands, InstantiationType.Eager); +registerSingleton(IExtHostAuthentication, ExtHostAuthentication, InstantiationType.Eager); +registerSingleton(IExtHostLanguageModels, ExtHostLanguageModels, InstantiationType.Eager); registerSingleton(IExtHostConfiguration, ExtHostConfiguration, InstantiationType.Eager); registerSingleton(IExtHostConsumerFileSystem, ExtHostConsumerFileSystem, InstantiationType.Eager); registerSingleton(IExtHostDebugService, WorkerExtHostDebugService, InstantiationType.Eager); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index d2ae7af630d..f57a727dd33 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1189,7 +1189,7 @@ export interface MainThreadLanguageModelsShape extends IDisposable { } export interface ExtHostLanguageModelsShape { - $updateLanguageModels(data: { added?: string[]; removed?: string[] }): void; + $updateLanguageModels(data: { added?: ILanguageModelChatMetadata[]; removed?: string[] }): void; $updateModelAccesslist(data: { from: ExtensionIdentifier; to: ExtensionIdentifier; enabled: boolean }[]): void; $provideLanguageModelResponse(handle: number, requestId: number, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise; diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index 84ddbd6fa55..1c562edf76a 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -5,10 +5,15 @@ import type * as vscode from 'vscode'; import { Emitter, Event } from 'vs/base/common/event'; -import { IMainContext, MainContext, MainThreadAuthenticationShape, ExtHostAuthenticationShape } from 'vs/workbench/api/common/extHost.protocol'; +import { MainContext, MainThreadAuthenticationShape, ExtHostAuthenticationShape } from 'vs/workbench/api/common/extHost.protocol'; import { Disposable } from 'vs/workbench/api/common/extHostTypes'; import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { INTERNAL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; + +export interface IExtHostAuthentication extends ExtHostAuthentication { } +export const IExtHostAuthentication = createDecorator('IExtHostAuthentication'); interface ProviderWithMetadata { label: string; @@ -17,6 +22,9 @@ interface ProviderWithMetadata { } export class ExtHostAuthentication implements ExtHostAuthenticationShape { + + declare _serviceBrand: undefined; + private _proxy: MainThreadAuthenticationShape; private _authenticationProviders: Map = new Map(); @@ -26,8 +34,10 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { private _getSessionTaskSingler = new TaskSingler(); private _getSessionsTaskSingler = new TaskSingler>(); - constructor(mainContext: IMainContext) { - this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication); + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService + ) { + this._proxy = extHostRpc.getProxy(MainContext.MainThreadAuthentication); } async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: readonly string[], options: vscode.AuthenticationGetSessionOptions & ({ createIfNone: true } | { forceNewSession: true } | { forceNewSession: vscode.AuthenticationForceNewSessionOptions })): Promise; diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 91f910aa4c8..56fa0132893 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -36,6 +36,7 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; +import { IExtHostLanguageModels } from 'vs/workbench/api/common/extHostLanguageModels'; import { Emitter, Event } from 'vs/base/common/event'; import { IExtensionActivationHost, checkActivateWorkspaceContainsExtension } from 'vs/workbench/services/extensions/common/workspaceContains'; import { ExtHostSecretState, IExtHostSecretState } from 'vs/workbench/api/common/extHostSecretState'; @@ -136,6 +137,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService, @IExtHostLocalizationService extHostLocalizationService: IExtHostLocalizationService, @IExtHostManagedSockets private readonly _extHostManagedSockets: IExtHostManagedSockets, + @IExtHostLanguageModels private readonly _extHostLanguageModels: IExtHostLanguageModels, ) { super(); this._hostUtils = hostUtils; @@ -489,6 +491,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise { + const lanuageModelAccessInformation = this._extHostLanguageModels.createLanguageModelAccessInformation(extensionDescription); const globalState = new ExtensionGlobalMemento(extensionDescription, this._storage); const workspaceState = new ExtensionMemento(extensionDescription.identifier.value, false, this._storage); const secrets = new ExtensionSecrets(extensionDescription, this._secretState); @@ -517,6 +520,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme workspaceState, secrets, subscriptions: [], + get languageModelAccessInformation() { return lanuageModelAccessInformation; }, get extensionUri() { return extensionDescription.extensionLocation; }, get extensionPath() { return extensionDescription.extensionLocation.fsPath; }, asAbsolutePath(relativePath: string) { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); }, diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index ea10161e75f..8221534013c 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -5,8 +5,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ILogService } from 'vs/platform/log/common/log'; -import { ExtHostLanguageModelsShape, IMainContext, MainContext, MainThreadLanguageModelsShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostLanguageModelsShape, MainContext, MainThreadLanguageModelsShape } from 'vs/workbench/api/common/extHost.protocol'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import { LanguageModelError } from 'vs/workbench/api/common/extHostTypes'; import type * as vscode from 'vscode'; @@ -14,13 +13,21 @@ import { Progress } from 'vs/platform/progress/common/progress'; import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata } from 'vs/workbench/contrib/chat/common/languageModels'; import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { AsyncIterableSource, Barrier } from 'vs/base/common/async'; -import { Emitter } from 'vs/base/common/event'; -import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; +import { Emitter, Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { INTERNAL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication'; import { CancellationError } from 'vs/base/common/errors'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; +import { ILogService } from 'vs/platform/log/common/log'; + +export interface IExtHostLanguageModels extends ExtHostLanguageModels { } + +export const IExtHostLanguageModels = createDecorator('IExtHostLanguageModels'); type LanguageModelData = { + readonly languageModelId: string; readonly extension: ExtensionIdentifier; readonly provider: vscode.ChatResponseProvider; }; @@ -106,6 +113,8 @@ class LanguageModelResponse { export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { + declare _serviceBrand: undefined; + private static _idPool = 1; private readonly _proxy: MainThreadLanguageModelsShape; @@ -114,17 +123,16 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { readonly onDidChangeProviders = this._onDidChangeProviders.event; private readonly _languageModels = new Map(); - private readonly _languageModelIds = new Set(); // these are ALL models, not just the one in this EH + private readonly _allLanguageModelData = new Map(); // these are ALL models, not just the one in this EH private readonly _modelAccessList = new ExtensionIdentifierMap(); private readonly _pendingRequest = new Map(); - constructor( - mainContext: IMainContext, - private readonly _logService: ILogService, - private readonly _extHostAuthentication: ExtHostAuthentication, + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @ILogService private readonly _logService: ILogService, + @IExtHostAuthentication private readonly _extHostAuthentication: IExtHostAuthentication, ) { - this._proxy = mainContext.getProxy(MainContext.MainThreadLanguageModels); + this._proxy = extHostRpc.getProxy(MainContext.MainThreadLanguageModels); } dispose(): void { @@ -135,7 +143,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { registerLanguageModel(extension: IExtensionDescription, identifier: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata): IDisposable { const handle = ExtHostLanguageModels._idPool++; - this._languageModels.set(handle, { extension: extension.identifier, provider }); + this._languageModels.set(handle, { extension: extension.identifier, provider, languageModelId: identifier }); let auth; if (metadata.auth) { auth = { @@ -145,6 +153,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { } this._proxy.$registerLanguageModelProvider(handle, identifier, { extension: extension.identifier, + identifier: identifier, model: metadata.name ?? '', auth }); @@ -173,19 +182,19 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { //#region --- making request - $updateLanguageModels(data: { added?: string[] | undefined; removed?: string[] | undefined }): void { + $updateLanguageModels(data: { added?: ILanguageModelChatMetadata[] | undefined; removed?: string[] | undefined }): void { const added: string[] = []; const removed: string[] = []; if (data.added) { - for (const id of data.added) { - this._languageModelIds.add(id); - added.push(id); + for (const metadata of data.added) { + this._allLanguageModelData.set(metadata.identifier, metadata); + added.push(metadata.model); } } if (data.removed) { for (const id of data.removed) { // clean up - this._languageModelIds.delete(id); + this._allLanguageModelData.delete(id); removed.push(id); // cancel pending requests for this model @@ -202,10 +211,13 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { added: Object.freeze(added), removed: Object.freeze(removed) })); + + // TODO@jrieken@TylerLeonhardt - this is a temporary hack to populate the auth providers + data.added?.forEach(this._fakeAuthPopulate, this); } getLanguageModelIds(): string[] { - return Array.from(this._languageModelIds); + return Array.from(this._allLanguageModelData.keys()); } $updateModelAccesslist(data: { from: ExtensionIdentifier; to: ExtensionIdentifier; enabled: boolean }[]): void { @@ -232,7 +244,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { const from = extension.identifier; const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, options.justification); - if (!metadata || !this._languageModelIds.has(languageModelId)) { + if (!metadata || !this._allLanguageModelData.has(languageModelId)) { throw LanguageModelError.NotFound(`Language model '${languageModelId}' is unknown.`); } @@ -323,4 +335,50 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { // And we're asking from a different extension && !ExtensionIdentifier.equals(toMetadata.extension, from); } + + private async _fakeAuthPopulate(metadata: ILanguageModelChatMetadata): Promise { + + for (const from of this._languageAccessInformationExtensions) { + try { + await this._getAuthAccess(from, { identifier: metadata.extension, displayName: '' }, undefined, true); + } catch (err) { + this._logService.error('Fake Auth request failed'); + this._logService.error(err); + } + } + + } + + private readonly _languageAccessInformationExtensions = new Set>(); + + createLanguageModelAccessInformation(from: Readonly): vscode.LanguageModelAccessInformation { + + this._languageAccessInformationExtensions.add(from); + + const that = this; + const _onDidChangeAccess = Event.signal(Event.filter(this._onDidChangeModelAccess.event, e => ExtensionIdentifier.equals(e.from, from.identifier))); + const _onDidAddRemove = Event.signal(this._onDidChangeProviders.event); + + return { + get onDidChange() { + return Event.any(_onDidChangeAccess, _onDidAddRemove); + }, + canSendRequest(languageModelId: string): boolean | undefined { + + const data = that._allLanguageModelData.get(languageModelId); + if (!data) { + return undefined; + } + if (!that._isUsingAuth(from.identifier, data)) { + return true; + } + + const list = that._modelAccessList.get(from.identifier); + if (!list) { + return undefined; + } + return list.has(data.extension); + } + }; + } } diff --git a/src/vs/workbench/contrib/chat/common/languageModels.ts b/src/vs/workbench/contrib/chat/common/languageModels.ts index 7f93594aee3..7304d002628 100644 --- a/src/vs/workbench/contrib/chat/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -28,6 +28,7 @@ export interface IChatResponseFragment { export interface ILanguageModelChatMetadata { readonly extension: ExtensionIdentifier; + readonly identifier: string; readonly model: string; readonly description?: string; readonly auth?: { @@ -47,7 +48,7 @@ export interface ILanguageModelsService { readonly _serviceBrand: undefined; - onDidChangeLanguageModels: Event<{ added?: string[]; removed?: string[] }>; + onDidChangeLanguageModels: Event<{ added?: ILanguageModelChatMetadata[]; removed?: string[] }>; getLanguageModelIds(): string[]; @@ -63,8 +64,8 @@ export class LanguageModelsService implements ILanguageModelsService { private readonly _providers: Map = new Map(); - private readonly _onDidChangeProviders = new Emitter<{ added?: string[]; removed?: string[] }>(); - readonly onDidChangeLanguageModels: Event<{ added?: string[]; removed?: string[] }> = this._onDidChangeProviders.event; + private readonly _onDidChangeProviders = new Emitter<{ added?: ILanguageModelChatMetadata[]; removed?: string[] }>(); + readonly onDidChangeLanguageModels: Event<{ added?: ILanguageModelChatMetadata[]; removed?: string[] }> = this._onDidChangeProviders.event; dispose() { this._onDidChangeProviders.dispose(); @@ -84,7 +85,7 @@ export class LanguageModelsService implements ILanguageModelsService { throw new Error(`Chat response provider with identifier ${identifier} is already registered.`); } this._providers.set(identifier, provider); - this._onDidChangeProviders.fire({ added: [identifier] }); + this._onDidChangeProviders.fire({ added: [provider.metadata] }); return toDisposable(() => { if (this._providers.delete(identifier)) { this._onDidChangeProviders.fire({ removed: [identifier] }); diff --git a/src/vscode-dts/vscode.proposed.languageModels.d.ts b/src/vscode-dts/vscode.proposed.languageModels.d.ts index 775e4d5e1dc..8732b0e67e0 100644 --- a/src/vscode-dts/vscode.proposed.languageModels.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModels.d.ts @@ -149,7 +149,7 @@ declare module 'vscode' { /** * Do not show the consent UI if the user has not yet granted access to the language model but fail the request instead. */ - // TODO@API refine/define + // TODO@API Revisit this, how do you do the first request? silent?: boolean; /** @@ -178,10 +178,10 @@ declare module 'vscode' { * @param messages An array of message instances. * @param options Objects that control the request. * @param token A cancellation token which controls the request. See {@link CancellationTokenSource} for how to create one. - * @returns A thenable that resolves to a {@link LanguageModelChatResponse}. The promise will reject when making the request failed. + * @returns A thenable that resolves to a {@link LanguageModelChatResponse}. The promise will reject when the request couldn't be made. */ // TODO@API refine doc - // TODO@API ExtensionContext#permission#languageModels: { languageModel: string: LanguageModelAccessInformation} + // TODO@API ✅ ExtensionContext#permission#languageModels: { languageModel: string: LanguageModelAccessInformation} // TODO@API ✅ define specific error types? // TODO@API ✅ NAME: sendChatRequest, fetchChatResponse, makeChatRequest, chat, chatRequest sendChatRequest // TODO@API ✅ NAME: LanguageModelChatXYZMessage @@ -201,11 +201,39 @@ declare module 'vscode' { export const onDidChangeLanguageModels: Event; } - // export function chatRequest2(languageModel: string, callback: (request: LanguageModelRequest) => R): Thenable; + /** + * Represents extension specific information about the access to language models. + */ + export interface LanguageModelAccessInformation { - // interface LanguageModelRequest { - // readonly quota: any; - // readonly permissions: any; - // makeRequest(messages: LanguageModelChatMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelChatResponse; - // } + /** + * An event that fires when access information changes. + */ + onDidChange: Event; + + /** + * Checks if a request can be made to a language model. + * + * *Note* that calling this function will not trigger a consent UI but just checks. + * + * @param languageModelId A language model identifier. + * @return `true` if a request can be made, `false` if not, `undefined` if the language + * model does not exist or consent hasn't been asked for. + */ + canSendRequest(languageModelId: string): boolean | undefined; + + // TODO@API SYNC or ASYNC? + // TODO@API future + // retrieveQuota(languageModelId: string): { remaining: number; resets: Date }; + } + + export interface ExtensionContext { + + /** + * An object that keeps information about how this extension can use language models. + * + * @see {@link lm.sendChatRequest} + */ + readonly languageModelAccessInformation: LanguageModelAccessInformation; + } } From 4a2905319baadc3a186f6950a82ac8d03c99ea6c Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 28 Feb 2024 12:03:51 +0100 Subject: [PATCH 1662/1897] codicons: Split list into Library and Derived --- src/vs/base/browser/ui/menu/menu.ts | 3 +- src/vs/base/common/codicons.ts | 637 +------------------ src/vs/base/common/codiconsLibrary.ts | 565 ++++++++++++++++ src/vs/base/common/codiconsUtil.ts | 28 + src/vs/platform/theme/common/iconRegistry.ts | 3 +- 5 files changed, 627 insertions(+), 609 deletions(-) create mode 100644 src/vs/base/common/codiconsLibrary.ts create mode 100644 src/vs/base/common/codiconsUtil.ts diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 4429e37687d..187ac848e8f 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -14,7 +14,8 @@ import { AnchorAlignment, layout, LayoutAnchorPosition } from 'vs/base/browser/u import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { EmptySubmenuAction, IAction, IActionRunner, Separator, SubmenuAction } from 'vs/base/common/actions'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { Codicon, getCodiconFontCharacters } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; +import { getCodiconFontCharacters } from 'vs/base/common/codiconsUtil'; import { ThemeIcon } from 'vs/base/common/themables'; import { Event } from 'vs/base/common/event'; import { stripIcons } from 'vs/base/common/iconLabels'; diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 27423d734fd..0cde23be0e8 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -3,28 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { ThemeIcon } from 'vs/base/common/themables'; -import { isString } from 'vs/base/common/types'; +import { register } from 'vs/base/common/codiconsUtil'; +import { codiconsLibrary } from 'vs/base/common/codiconsLibrary'; -const _codiconFontCharacters: { [id: string]: number } = Object.create(null); - -function register(id: string, fontCharacter: number | string): ThemeIcon { - if (isString(fontCharacter)) { - const val = _codiconFontCharacters[fontCharacter]; - if (val === undefined) { - throw new Error(`${id} references an unknown codicon: ${fontCharacter}`); - } - fontCharacter = val; - } - _codiconFontCharacters[id] = fontCharacter; - return { id }; -} - -/** - * Only to be used by the iconRegistry. - */ -export function getCodiconFontCharacters(): { [id: string]: number } { - return _codiconFontCharacters; -} /** * Only to be used by the iconRegistry. @@ -33,6 +14,32 @@ export function getAllCodicons(): ThemeIcon[] { return Object.values(Codicon); } +/** + * Derived icons, that could become separate icons. + * These mappings should be moved into the mapping file in the vscode-codicons repo at some point. + */ +export const codiconsDerived = { + dialogError: register('dialog-error', 'error'), + dialogWarning: register('dialog-warning', 'warning'), + dialogInfo: register('dialog-info', 'info'), + dialogClose: register('dialog-close', 'close'), + treeItemExpanded: register('tree-item-expanded', 'chevron-down'), // collapsed is done with rotation + treeFilterOnTypeOn: register('tree-filter-on-type-on', 'list-filter'), + treeFilterOnTypeOff: register('tree-filter-on-type-off', 'list-selection'), + treeFilterClear: register('tree-filter-clear', 'close'), + treeItemLoading: register('tree-item-loading', 'loading'), + menuSelection: register('menu-selection', 'check'), + menuSubmenu: register('menu-submenu', 'chevron-right'), + menuBarMore: register('menubar-more', 'more'), + scrollbarButtonLeft: register('scrollbar-button-left', 'triangle-left'), + scrollbarButtonRight: register('scrollbar-button-right', 'triangle-right'), + scrollbarButtonUp: register('scrollbar-button-up', 'triangle-up'), + scrollbarButtonDown: register('scrollbar-button-down', 'triangle-down'), + toolBarMore: register('toolbar-more', 'more'), + quickInputBack: register('quick-input-back', 'arrow-left') + +} as const; + /** * The Codicon library is a set of default icons that are built-in in VS Code. * @@ -41,591 +48,7 @@ export function getAllCodicons(): ThemeIcon[] { * In that call a Codicon can be named as default. */ export const Codicon = { - - // built-in icons, with image name - add: register('add', 0xea60), - plus: register('plus', 0xea60), - gistNew: register('gist-new', 0xea60), - repoCreate: register('repo-create', 0xea60), - lightbulb: register('lightbulb', 0xea61), - lightBulb: register('light-bulb', 0xea61), - repo: register('repo', 0xea62), - repoDelete: register('repo-delete', 0xea62), - gistFork: register('gist-fork', 0xea63), - repoForked: register('repo-forked', 0xea63), - gitPullRequest: register('git-pull-request', 0xea64), - gitPullRequestAbandoned: register('git-pull-request-abandoned', 0xea64), - recordKeys: register('record-keys', 0xea65), - keyboard: register('keyboard', 0xea65), - tag: register('tag', 0xea66), - tagAdd: register('tag-add', 0xea66), - tagRemove: register('tag-remove', 0xea66), - gitPullRequestLabel: register('git-pull-request-label', 0xea66), - person: register('person', 0xea67), - personFollow: register('person-follow', 0xea67), - personOutline: register('person-outline', 0xea67), - personFilled: register('person-filled', 0xea67), - gitBranch: register('git-branch', 0xea68), - gitBranchCreate: register('git-branch-create', 0xea68), - gitBranchDelete: register('git-branch-delete', 0xea68), - sourceControl: register('source-control', 0xea68), - mirror: register('mirror', 0xea69), - mirrorPublic: register('mirror-public', 0xea69), - star: register('star', 0xea6a), - starAdd: register('star-add', 0xea6a), - starDelete: register('star-delete', 0xea6a), - starEmpty: register('star-empty', 0xea6a), - comment: register('comment', 0xea6b), - commentAdd: register('comment-add', 0xea6b), - alert: register('alert', 0xea6c), - warning: register('warning', 0xea6c), - search: register('search', 0xea6d), - searchSave: register('search-save', 0xea6d), - logOut: register('log-out', 0xea6e), - signOut: register('sign-out', 0xea6e), - logIn: register('log-in', 0xea6f), - signIn: register('sign-in', 0xea6f), - eye: register('eye', 0xea70), - eyeUnwatch: register('eye-unwatch', 0xea70), - eyeWatch: register('eye-watch', 0xea70), - circleFilled: register('circle-filled', 0xea71), - primitiveDot: register('primitive-dot', 0xea71), - closeDirty: register('close-dirty', 0xea71), - debugBreakpoint: register('debug-breakpoint', 0xea71), - debugBreakpointDisabled: register('debug-breakpoint-disabled', 0xea71), - debugBreakpointPending: register('debug-breakpoint-pending', 0xebd9), - debugHint: register('debug-hint', 0xea71), - primitiveSquare: register('primitive-square', 0xea72), - edit: register('edit', 0xea73), - pencil: register('pencil', 0xea73), - info: register('info', 0xea74), - issueOpened: register('issue-opened', 0xea74), - gistPrivate: register('gist-private', 0xea75), - gitForkPrivate: register('git-fork-private', 0xea75), - lock: register('lock', 0xea75), - mirrorPrivate: register('mirror-private', 0xea75), - close: register('close', 0xea76), - removeClose: register('remove-close', 0xea76), - x: register('x', 0xea76), - repoSync: register('repo-sync', 0xea77), - sync: register('sync', 0xea77), - clone: register('clone', 0xea78), - desktopDownload: register('desktop-download', 0xea78), - beaker: register('beaker', 0xea79), - microscope: register('microscope', 0xea79), - vm: register('vm', 0xea7a), - deviceDesktop: register('device-desktop', 0xea7a), - file: register('file', 0xea7b), - fileText: register('file-text', 0xea7b), - more: register('more', 0xea7c), - ellipsis: register('ellipsis', 0xea7c), - kebabHorizontal: register('kebab-horizontal', 0xea7c), - mailReply: register('mail-reply', 0xea7d), - reply: register('reply', 0xea7d), - organization: register('organization', 0xea7e), - organizationFilled: register('organization-filled', 0xea7e), - organizationOutline: register('organization-outline', 0xea7e), - newFile: register('new-file', 0xea7f), - fileAdd: register('file-add', 0xea7f), - newFolder: register('new-folder', 0xea80), - fileDirectoryCreate: register('file-directory-create', 0xea80), - trash: register('trash', 0xea81), - trashcan: register('trashcan', 0xea81), - history: register('history', 0xea82), - clock: register('clock', 0xea82), - folder: register('folder', 0xea83), - fileDirectory: register('file-directory', 0xea83), - symbolFolder: register('symbol-folder', 0xea83), - logoGithub: register('logo-github', 0xea84), - markGithub: register('mark-github', 0xea84), - github: register('github', 0xea84), - terminal: register('terminal', 0xea85), - console: register('console', 0xea85), - repl: register('repl', 0xea85), - zap: register('zap', 0xea86), - symbolEvent: register('symbol-event', 0xea86), - error: register('error', 0xea87), - stop: register('stop', 0xea87), - variable: register('variable', 0xea88), - symbolVariable: register('symbol-variable', 0xea88), - array: register('array', 0xea8a), - symbolArray: register('symbol-array', 0xea8a), - symbolModule: register('symbol-module', 0xea8b), - symbolPackage: register('symbol-package', 0xea8b), - symbolNamespace: register('symbol-namespace', 0xea8b), - symbolObject: register('symbol-object', 0xea8b), - symbolMethod: register('symbol-method', 0xea8c), - symbolFunction: register('symbol-function', 0xea8c), - symbolConstructor: register('symbol-constructor', 0xea8c), - symbolBoolean: register('symbol-boolean', 0xea8f), - symbolNull: register('symbol-null', 0xea8f), - symbolNumeric: register('symbol-numeric', 0xea90), - symbolNumber: register('symbol-number', 0xea90), - symbolStructure: register('symbol-structure', 0xea91), - symbolStruct: register('symbol-struct', 0xea91), - symbolParameter: register('symbol-parameter', 0xea92), - symbolTypeParameter: register('symbol-type-parameter', 0xea92), - symbolKey: register('symbol-key', 0xea93), - symbolText: register('symbol-text', 0xea93), - symbolReference: register('symbol-reference', 0xea94), - goToFile: register('go-to-file', 0xea94), - symbolEnum: register('symbol-enum', 0xea95), - symbolValue: register('symbol-value', 0xea95), - symbolRuler: register('symbol-ruler', 0xea96), - symbolUnit: register('symbol-unit', 0xea96), - activateBreakpoints: register('activate-breakpoints', 0xea97), - archive: register('archive', 0xea98), - arrowBoth: register('arrow-both', 0xea99), - arrowDown: register('arrow-down', 0xea9a), - arrowLeft: register('arrow-left', 0xea9b), - arrowRight: register('arrow-right', 0xea9c), - arrowSmallDown: register('arrow-small-down', 0xea9d), - arrowSmallLeft: register('arrow-small-left', 0xea9e), - arrowSmallRight: register('arrow-small-right', 0xea9f), - arrowSmallUp: register('arrow-small-up', 0xeaa0), - arrowUp: register('arrow-up', 0xeaa1), - bell: register('bell', 0xeaa2), - bold: register('bold', 0xeaa3), - book: register('book', 0xeaa4), - bookmark: register('bookmark', 0xeaa5), - debugBreakpointConditionalUnverified: register('debug-breakpoint-conditional-unverified', 0xeaa6), - debugBreakpointConditional: register('debug-breakpoint-conditional', 0xeaa7), - debugBreakpointConditionalDisabled: register('debug-breakpoint-conditional-disabled', 0xeaa7), - debugBreakpointDataUnverified: register('debug-breakpoint-data-unverified', 0xeaa8), - debugBreakpointData: register('debug-breakpoint-data', 0xeaa9), - debugBreakpointDataDisabled: register('debug-breakpoint-data-disabled', 0xeaa9), - debugBreakpointLogUnverified: register('debug-breakpoint-log-unverified', 0xeaaa), - debugBreakpointLog: register('debug-breakpoint-log', 0xeaab), - debugBreakpointLogDisabled: register('debug-breakpoint-log-disabled', 0xeaab), - briefcase: register('briefcase', 0xeaac), - broadcast: register('broadcast', 0xeaad), - browser: register('browser', 0xeaae), - bug: register('bug', 0xeaaf), - calendar: register('calendar', 0xeab0), - caseSensitive: register('case-sensitive', 0xeab1), - check: register('check', 0xeab2), - checklist: register('checklist', 0xeab3), - chevronDown: register('chevron-down', 0xeab4), - dropDownButton: register('drop-down-button', 0xeab4), - chevronLeft: register('chevron-left', 0xeab5), - chevronRight: register('chevron-right', 0xeab6), - chevronUp: register('chevron-up', 0xeab7), - chromeClose: register('chrome-close', 0xeab8), - chromeMaximize: register('chrome-maximize', 0xeab9), - chromeMinimize: register('chrome-minimize', 0xeaba), - chromeRestore: register('chrome-restore', 0xeabb), - circle: register('circle', 0xeabc), - circleOutline: register('circle-outline', 0xeabc), - debugBreakpointUnverified: register('debug-breakpoint-unverified', 0xeabc), - circleSlash: register('circle-slash', 0xeabd), - circuitBoard: register('circuit-board', 0xeabe), - clearAll: register('clear-all', 0xeabf), - clippy: register('clippy', 0xeac0), - closeAll: register('close-all', 0xeac1), - cloudDownload: register('cloud-download', 0xeac2), - cloudUpload: register('cloud-upload', 0xeac3), - code: register('code', 0xeac4), - collapseAll: register('collapse-all', 0xeac5), - colorMode: register('color-mode', 0xeac6), - commentDiscussion: register('comment-discussion', 0xeac7), - compareChanges: register('compare-changes', 0xeafd), - creditCard: register('credit-card', 0xeac9), - dash: register('dash', 0xeacc), - dashboard: register('dashboard', 0xeacd), - database: register('database', 0xeace), - debugContinue: register('debug-continue', 0xeacf), - debugDisconnect: register('debug-disconnect', 0xead0), - debugPause: register('debug-pause', 0xead1), - debugRestart: register('debug-restart', 0xead2), - debugStart: register('debug-start', 0xead3), - debugStepInto: register('debug-step-into', 0xead4), - debugStepOut: register('debug-step-out', 0xead5), - debugStepOver: register('debug-step-over', 0xead6), - debugStop: register('debug-stop', 0xead7), - debug: register('debug', 0xead8), - deviceCameraVideo: register('device-camera-video', 0xead9), - deviceCamera: register('device-camera', 0xeada), - deviceMobile: register('device-mobile', 0xeadb), - diffAdded: register('diff-added', 0xeadc), - diffIgnored: register('diff-ignored', 0xeadd), - diffModified: register('diff-modified', 0xeade), - diffRemoved: register('diff-removed', 0xeadf), - diffRenamed: register('diff-renamed', 0xeae0), - diff: register('diff', 0xeae1), - discard: register('discard', 0xeae2), - editorLayout: register('editor-layout', 0xeae3), - emptyWindow: register('empty-window', 0xeae4), - exclude: register('exclude', 0xeae5), - extensions: register('extensions', 0xeae6), - eyeClosed: register('eye-closed', 0xeae7), - fileBinary: register('file-binary', 0xeae8), - fileCode: register('file-code', 0xeae9), - fileMedia: register('file-media', 0xeaea), - filePdf: register('file-pdf', 0xeaeb), - fileSubmodule: register('file-submodule', 0xeaec), - fileSymlinkDirectory: register('file-symlink-directory', 0xeaed), - fileSymlinkFile: register('file-symlink-file', 0xeaee), - fileZip: register('file-zip', 0xeaef), - files: register('files', 0xeaf0), - filter: register('filter', 0xeaf1), - flame: register('flame', 0xeaf2), - foldDown: register('fold-down', 0xeaf3), - foldUp: register('fold-up', 0xeaf4), - fold: register('fold', 0xeaf5), - folderActive: register('folder-active', 0xeaf6), - folderOpened: register('folder-opened', 0xeaf7), - gear: register('gear', 0xeaf8), - gift: register('gift', 0xeaf9), - gistSecret: register('gist-secret', 0xeafa), - gist: register('gist', 0xeafb), - gitCommit: register('git-commit', 0xeafc), - gitCompare: register('git-compare', 0xeafd), - gitMerge: register('git-merge', 0xeafe), - githubAction: register('github-action', 0xeaff), - githubAlt: register('github-alt', 0xeb00), - globe: register('globe', 0xeb01), - grabber: register('grabber', 0xeb02), - graph: register('graph', 0xeb03), - gripper: register('gripper', 0xeb04), - heart: register('heart', 0xeb05), - home: register('home', 0xeb06), - horizontalRule: register('horizontal-rule', 0xeb07), - hubot: register('hubot', 0xeb08), - inbox: register('inbox', 0xeb09), - issueClosed: register('issue-closed', 0xeba4), - issueReopened: register('issue-reopened', 0xeb0b), - issues: register('issues', 0xeb0c), - italic: register('italic', 0xeb0d), - jersey: register('jersey', 0xeb0e), - json: register('json', 0xeb0f), - bracket: register('bracket', 0xeb0f), - kebabVertical: register('kebab-vertical', 0xeb10), - key: register('key', 0xeb11), - law: register('law', 0xeb12), - lightbulbAutofix: register('lightbulb-autofix', 0xeb13), - linkExternal: register('link-external', 0xeb14), - link: register('link', 0xeb15), - listOrdered: register('list-ordered', 0xeb16), - listUnordered: register('list-unordered', 0xeb17), - liveShare: register('live-share', 0xeb18), - loading: register('loading', 0xeb19), - location: register('location', 0xeb1a), - mailRead: register('mail-read', 0xeb1b), - mail: register('mail', 0xeb1c), - markdown: register('markdown', 0xeb1d), - megaphone: register('megaphone', 0xeb1e), - mention: register('mention', 0xeb1f), - milestone: register('milestone', 0xeb20), - gitPullRequestMilestone: register('git-pull-request-milestone', 0xeb20), - mortarBoard: register('mortar-board', 0xeb21), - move: register('move', 0xeb22), - multipleWindows: register('multiple-windows', 0xeb23), - mute: register('mute', 0xeb24), - noNewline: register('no-newline', 0xeb25), - note: register('note', 0xeb26), - octoface: register('octoface', 0xeb27), - openPreview: register('open-preview', 0xeb28), - package: register('package', 0xeb29), - paintcan: register('paintcan', 0xeb2a), - pin: register('pin', 0xeb2b), - play: register('play', 0xeb2c), - run: register('run', 0xeb2c), - plug: register('plug', 0xeb2d), - preserveCase: register('preserve-case', 0xeb2e), - preview: register('preview', 0xeb2f), - project: register('project', 0xeb30), - pulse: register('pulse', 0xeb31), - question: register('question', 0xeb32), - quote: register('quote', 0xeb33), - radioTower: register('radio-tower', 0xeb34), - reactions: register('reactions', 0xeb35), - references: register('references', 0xeb36), - refresh: register('refresh', 0xeb37), - regex: register('regex', 0xeb38), - remoteExplorer: register('remote-explorer', 0xeb39), - remote: register('remote', 0xeb3a), - remove: register('remove', 0xeb3b), - replaceAll: register('replace-all', 0xeb3c), - replace: register('replace', 0xeb3d), - repoClone: register('repo-clone', 0xeb3e), - repoForcePush: register('repo-force-push', 0xeb3f), - repoPull: register('repo-pull', 0xeb40), - repoPush: register('repo-push', 0xeb41), - report: register('report', 0xeb42), - requestChanges: register('request-changes', 0xeb43), - rocket: register('rocket', 0xeb44), - rootFolderOpened: register('root-folder-opened', 0xeb45), - rootFolder: register('root-folder', 0xeb46), - rss: register('rss', 0xeb47), - ruby: register('ruby', 0xeb48), - saveAll: register('save-all', 0xeb49), - saveAs: register('save-as', 0xeb4a), - save: register('save', 0xeb4b), - screenFull: register('screen-full', 0xeb4c), - screenNormal: register('screen-normal', 0xeb4d), - searchStop: register('search-stop', 0xeb4e), - server: register('server', 0xeb50), - settingsGear: register('settings-gear', 0xeb51), - settings: register('settings', 0xeb52), - shield: register('shield', 0xeb53), - smiley: register('smiley', 0xeb54), - sortPrecedence: register('sort-precedence', 0xeb55), - splitHorizontal: register('split-horizontal', 0xeb56), - splitVertical: register('split-vertical', 0xeb57), - squirrel: register('squirrel', 0xeb58), - starFull: register('star-full', 0xeb59), - starHalf: register('star-half', 0xeb5a), - symbolClass: register('symbol-class', 0xeb5b), - symbolColor: register('symbol-color', 0xeb5c), - symbolCustomColor: register('symbol-customcolor', 0xeb5c), - symbolConstant: register('symbol-constant', 0xeb5d), - symbolEnumMember: register('symbol-enum-member', 0xeb5e), - symbolField: register('symbol-field', 0xeb5f), - symbolFile: register('symbol-file', 0xeb60), - symbolInterface: register('symbol-interface', 0xeb61), - symbolKeyword: register('symbol-keyword', 0xeb62), - symbolMisc: register('symbol-misc', 0xeb63), - symbolOperator: register('symbol-operator', 0xeb64), - symbolProperty: register('symbol-property', 0xeb65), - wrench: register('wrench', 0xeb65), - wrenchSubaction: register('wrench-subaction', 0xeb65), - symbolSnippet: register('symbol-snippet', 0xeb66), - tasklist: register('tasklist', 0xeb67), - telescope: register('telescope', 0xeb68), - textSize: register('text-size', 0xeb69), - threeBars: register('three-bars', 0xeb6a), - thumbsdown: register('thumbsdown', 0xeb6b), - thumbsup: register('thumbsup', 0xeb6c), - tools: register('tools', 0xeb6d), - triangleDown: register('triangle-down', 0xeb6e), - triangleLeft: register('triangle-left', 0xeb6f), - triangleRight: register('triangle-right', 0xeb70), - triangleUp: register('triangle-up', 0xeb71), - twitter: register('twitter', 0xeb72), - unfold: register('unfold', 0xeb73), - unlock: register('unlock', 0xeb74), - unmute: register('unmute', 0xeb75), - unverified: register('unverified', 0xeb76), - verified: register('verified', 0xeb77), - versions: register('versions', 0xeb78), - vmActive: register('vm-active', 0xeb79), - vmOutline: register('vm-outline', 0xeb7a), - vmRunning: register('vm-running', 0xeb7b), - watch: register('watch', 0xeb7c), - whitespace: register('whitespace', 0xeb7d), - wholeWord: register('whole-word', 0xeb7e), - window: register('window', 0xeb7f), - wordWrap: register('word-wrap', 0xeb80), - zoomIn: register('zoom-in', 0xeb81), - zoomOut: register('zoom-out', 0xeb82), - listFilter: register('list-filter', 0xeb83), - listFlat: register('list-flat', 0xeb84), - listSelection: register('list-selection', 0xeb85), - selection: register('selection', 0xeb85), - listTree: register('list-tree', 0xeb86), - debugBreakpointFunctionUnverified: register('debug-breakpoint-function-unverified', 0xeb87), - debugBreakpointFunction: register('debug-breakpoint-function', 0xeb88), - debugBreakpointFunctionDisabled: register('debug-breakpoint-function-disabled', 0xeb88), - debugStackframeActive: register('debug-stackframe-active', 0xeb89), - circleSmallFilled: register('circle-small-filled', 0xeb8a), - debugStackframeDot: register('debug-stackframe-dot', 0xeb8a), - debugStackframe: register('debug-stackframe', 0xeb8b), - debugStackframeFocused: register('debug-stackframe-focused', 0xeb8b), - debugBreakpointUnsupported: register('debug-breakpoint-unsupported', 0xeb8c), - symbolString: register('symbol-string', 0xeb8d), - debugReverseContinue: register('debug-reverse-continue', 0xeb8e), - debugStepBack: register('debug-step-back', 0xeb8f), - debugRestartFrame: register('debug-restart-frame', 0xeb90), - callIncoming: register('call-incoming', 0xeb92), - callOutgoing: register('call-outgoing', 0xeb93), - menu: register('menu', 0xeb94), - expandAll: register('expand-all', 0xeb95), - feedback: register('feedback', 0xeb96), - gitPullRequestReviewer: register('git-pull-request-reviewer', 0xeb96), - groupByRefType: register('group-by-ref-type', 0xeb97), - ungroupByRefType: register('ungroup-by-ref-type', 0xeb98), - account: register('account', 0xeb99), - gitPullRequestAssignee: register('git-pull-request-assignee', 0xeb99), - bellDot: register('bell-dot', 0xeb9a), - debugConsole: register('debug-console', 0xeb9b), - library: register('library', 0xeb9c), - output: register('output', 0xeb9d), - runAll: register('run-all', 0xeb9e), - syncIgnored: register('sync-ignored', 0xeb9f), - pinned: register('pinned', 0xeba0), - githubInverted: register('github-inverted', 0xeba1), - debugAlt: register('debug-alt', 0xeb91), - serverProcess: register('server-process', 0xeba2), - serverEnvironment: register('server-environment', 0xeba3), - pass: register('pass', 0xeba4), - stopCircle: register('stop-circle', 0xeba5), - playCircle: register('play-circle', 0xeba6), - record: register('record', 0xeba7), - debugAltSmall: register('debug-alt-small', 0xeba8), - vmConnect: register('vm-connect', 0xeba9), - cloud: register('cloud', 0xebaa), - merge: register('merge', 0xebab), - exportIcon: register('export', 0xebac), - graphLeft: register('graph-left', 0xebad), - magnet: register('magnet', 0xebae), - notebook: register('notebook', 0xebaf), - redo: register('redo', 0xebb0), - checkAll: register('check-all', 0xebb1), - pinnedDirty: register('pinned-dirty', 0xebb2), - passFilled: register('pass-filled', 0xebb3), - circleLargeFilled: register('circle-large-filled', 0xebb4), - circleLarge: register('circle-large', 0xebb5), - circleLargeOutline: register('circle-large-outline', 0xebb5), - combine: register('combine', 0xebb6), - gather: register('gather', 0xebb6), - table: register('table', 0xebb7), - variableGroup: register('variable-group', 0xebb8), - typeHierarchy: register('type-hierarchy', 0xebb9), - typeHierarchySub: register('type-hierarchy-sub', 0xebba), - typeHierarchySuper: register('type-hierarchy-super', 0xebbb), - gitPullRequestCreate: register('git-pull-request-create', 0xebbc), - runAbove: register('run-above', 0xebbd), - runBelow: register('run-below', 0xebbe), - notebookTemplate: register('notebook-template', 0xebbf), - debugRerun: register('debug-rerun', 0xebc0), - workspaceTrusted: register('workspace-trusted', 0xebc1), - workspaceUntrusted: register('workspace-untrusted', 0xebc2), - workspaceUnspecified: register('workspace-unspecified', 0xebc3), - terminalCmd: register('terminal-cmd', 0xebc4), - terminalDebian: register('terminal-debian', 0xebc5), - terminalLinux: register('terminal-linux', 0xebc6), - terminalPowershell: register('terminal-powershell', 0xebc7), - terminalTmux: register('terminal-tmux', 0xebc8), - terminalUbuntu: register('terminal-ubuntu', 0xebc9), - terminalBash: register('terminal-bash', 0xebca), - arrowSwap: register('arrow-swap', 0xebcb), - copy: register('copy', 0xebcc), - personAdd: register('person-add', 0xebcd), - filterFilled: register('filter-filled', 0xebce), - wand: register('wand', 0xebcf), - debugLineByLine: register('debug-line-by-line', 0xebd0), - inspect: register('inspect', 0xebd1), - layers: register('layers', 0xebd2), - layersDot: register('layers-dot', 0xebd3), - layersActive: register('layers-active', 0xebd4), - compass: register('compass', 0xebd5), - compassDot: register('compass-dot', 0xebd6), - compassActive: register('compass-active', 0xebd7), - azure: register('azure', 0xebd8), - issueDraft: register('issue-draft', 0xebd9), - gitPullRequestClosed: register('git-pull-request-closed', 0xebda), - gitPullRequestDraft: register('git-pull-request-draft', 0xebdb), - debugAll: register('debug-all', 0xebdc), - debugCoverage: register('debug-coverage', 0xebdd), - runErrors: register('run-errors', 0xebde), - folderLibrary: register('folder-library', 0xebdf), - debugContinueSmall: register('debug-continue-small', 0xebe0), - beakerStop: register('beaker-stop', 0xebe1), - graphLine: register('graph-line', 0xebe2), - graphScatter: register('graph-scatter', 0xebe3), - pieChart: register('pie-chart', 0xebe4), - bracketDot: register('bracket-dot', 0xebe5), - bracketError: register('bracket-error', 0xebe6), - lockSmall: register('lock-small', 0xebe7), - azureDevops: register('azure-devops', 0xebe8), - verifiedFilled: register('verified-filled', 0xebe9), - newLine: register('newline', 0xebea), - layout: register('layout', 0xebeb), - layoutActivitybarLeft: register('layout-activitybar-left', 0xebec), - layoutActivitybarRight: register('layout-activitybar-right', 0xebed), - layoutPanelLeft: register('layout-panel-left', 0xebee), - layoutPanelCenter: register('layout-panel-center', 0xebef), - layoutPanelJustify: register('layout-panel-justify', 0xebf0), - layoutPanelRight: register('layout-panel-right', 0xebf1), - layoutPanel: register('layout-panel', 0xebf2), - layoutSidebarLeft: register('layout-sidebar-left', 0xebf3), - layoutSidebarRight: register('layout-sidebar-right', 0xebf4), - layoutStatusbar: register('layout-statusbar', 0xebf5), - layoutMenubar: register('layout-menubar', 0xebf6), - layoutCentered: register('layout-centered', 0xebf7), - layoutSidebarRightOff: register('layout-sidebar-right-off', 0xec00), - layoutPanelOff: register('layout-panel-off', 0xec01), - layoutSidebarLeftOff: register('layout-sidebar-left-off', 0xec02), - target: register('target', 0xebf8), - indent: register('indent', 0xebf9), - recordSmall: register('record-small', 0xebfa), - errorSmall: register('error-small', 0xebfb), - arrowCircleDown: register('arrow-circle-down', 0xebfc), - arrowCircleLeft: register('arrow-circle-left', 0xebfd), - arrowCircleRight: register('arrow-circle-right', 0xebfe), - arrowCircleUp: register('arrow-circle-up', 0xebff), - heartFilled: register('heart-filled', 0xec04), - map: register('map', 0xec05), - mapFilled: register('map-filled', 0xec06), - circleSmall: register('circle-small', 0xec07), - bellSlash: register('bell-slash', 0xec08), - bellSlashDot: register('bell-slash-dot', 0xec09), - commentUnresolved: register('comment-unresolved', 0xec0a), - gitPullRequestGoToChanges: register('git-pull-request-go-to-changes', 0xec0b), - gitPullRequestNewChanges: register('git-pull-request-new-changes', 0xec0c), - searchFuzzy: register('search-fuzzy', 0xec0d), - commentDraft: register('comment-draft', 0xec0e), - send: register('send', 0xec0f), - sparkle: register('sparkle', 0xec10), - insert: register('insert', 0xec11), - mic: register('mic', 0xec12), - thumbsDownFilled: register('thumbsdown-filled', 0xec13), - thumbsUpFilled: register('thumbsup-filled', 0xec14), - coffee: register('coffee', 0xec15), - snake: register('snake', 0xec16), - game: register('game', 0xec17), - vr: register('vr', 0xec18), - chip: register('chip', 0xec19), - piano: register('piano', 0xec1a), - music: register('music', 0xec1b), - micFilled: register('mic-filled', 0xec1c), - gitFetch: register('git-fetch', 0xec1d), - copilot: register('copilot', 0xec1e), - lightbulbSparkle: register('lightbulb-sparkle', 0xec1f), - lightbulbSparkleAutofix: register('lightbulb-sparkle-autofix', 0xec1f), - robot: register('robot', 0xec20), - sparkleFilled: register('sparkle-filled', 0xec21), - diffSingle: register('diff-single', 0xec22), - diffMultiple: register('diff-multiple', 0xec23), - surroundWith: register('surround-with', 0xec24), - gitStash: register('git-stash', 0xec26), - gitStashApply: register('git-stash-apply', 0xec27), - gitStashPop: register('git-stash-pop', 0xec28), - runAllCoverage: register('run-all-coverage', 0xec2d), - runCoverage: register('run-all-coverage', 0xec2c), - coverage: register('coverage', 0xec2e), - githubProject: register('github-project', 0xec2f), - - // derived icons, that could become separate icons - // TODO: These mappings should go in the vscode-codicons mapping file - - dialogError: register('dialog-error', 'error'), - dialogWarning: register('dialog-warning', 'warning'), - dialogInfo: register('dialog-info', 'info'), - dialogClose: register('dialog-close', 'close'), - - treeItemExpanded: register('tree-item-expanded', 'chevron-down'), // collapsed is done with rotation - - treeFilterOnTypeOn: register('tree-filter-on-type-on', 'list-filter'), - treeFilterOnTypeOff: register('tree-filter-on-type-off', 'list-selection'), - treeFilterClear: register('tree-filter-clear', 'close'), - - treeItemLoading: register('tree-item-loading', 'loading'), - - menuSelection: register('menu-selection', 'check'), - menuSubmenu: register('menu-submenu', 'chevron-right'), - - menuBarMore: register('menubar-more', 'more'), - - scrollbarButtonLeft: register('scrollbar-button-left', 'triangle-left'), - scrollbarButtonRight: register('scrollbar-button-right', 'triangle-right'), - - scrollbarButtonUp: register('scrollbar-button-up', 'triangle-up'), - scrollbarButtonDown: register('scrollbar-button-down', 'triangle-down'), - - toolBarMore: register('toolbar-more', 'more'), - - quickInputBack: register('quick-input-back', 'arrow-left') + ...codiconsLibrary, + ...codiconsDerived } as const; - diff --git a/src/vs/base/common/codiconsLibrary.ts b/src/vs/base/common/codiconsLibrary.ts new file mode 100644 index 00000000000..a350213cc76 --- /dev/null +++ b/src/vs/base/common/codiconsLibrary.ts @@ -0,0 +1,565 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { register } from 'vs/base/common/codiconsUtil'; + + +// This list is automatically generated by the vscode-codicons repo. +// Please don't edit it, as your changes will be overwritten. +// If you want to create a mapping, add it to the codiconsDerived list in codicons.ts. +export const codiconsLibrary = { + add: register('add', 0xea60), + plus: register('plus', 0xea60), + gistNew: register('gist-new', 0xea60), + repoCreate: register('repo-create', 0xea60), + lightbulb: register('lightbulb', 0xea61), + lightBulb: register('light-bulb', 0xea61), + repo: register('repo', 0xea62), + repoDelete: register('repo-delete', 0xea62), + gistFork: register('gist-fork', 0xea63), + repoForked: register('repo-forked', 0xea63), + gitPullRequest: register('git-pull-request', 0xea64), + gitPullRequestAbandoned: register('git-pull-request-abandoned', 0xea64), + recordKeys: register('record-keys', 0xea65), + keyboard: register('keyboard', 0xea65), + tag: register('tag', 0xea66), + tagAdd: register('tag-add', 0xea66), + tagRemove: register('tag-remove', 0xea66), + gitPullRequestLabel: register('git-pull-request-label', 0xea66), + person: register('person', 0xea67), + personFollow: register('person-follow', 0xea67), + personOutline: register('person-outline', 0xea67), + personFilled: register('person-filled', 0xea67), + gitBranch: register('git-branch', 0xea68), + gitBranchCreate: register('git-branch-create', 0xea68), + gitBranchDelete: register('git-branch-delete', 0xea68), + sourceControl: register('source-control', 0xea68), + mirror: register('mirror', 0xea69), + mirrorPublic: register('mirror-public', 0xea69), + star: register('star', 0xea6a), + starAdd: register('star-add', 0xea6a), + starDelete: register('star-delete', 0xea6a), + starEmpty: register('star-empty', 0xea6a), + comment: register('comment', 0xea6b), + commentAdd: register('comment-add', 0xea6b), + alert: register('alert', 0xea6c), + warning: register('warning', 0xea6c), + search: register('search', 0xea6d), + searchSave: register('search-save', 0xea6d), + logOut: register('log-out', 0xea6e), + signOut: register('sign-out', 0xea6e), + logIn: register('log-in', 0xea6f), + signIn: register('sign-in', 0xea6f), + eye: register('eye', 0xea70), + eyeUnwatch: register('eye-unwatch', 0xea70), + eyeWatch: register('eye-watch', 0xea70), + circleFilled: register('circle-filled', 0xea71), + primitiveDot: register('primitive-dot', 0xea71), + closeDirty: register('close-dirty', 0xea71), + debugBreakpoint: register('debug-breakpoint', 0xea71), + debugBreakpointDisabled: register('debug-breakpoint-disabled', 0xea71), + debugBreakpointPending: register('debug-breakpoint-pending', 0xebd9), + debugHint: register('debug-hint', 0xea71), + primitiveSquare: register('primitive-square', 0xea72), + edit: register('edit', 0xea73), + pencil: register('pencil', 0xea73), + info: register('info', 0xea74), + issueOpened: register('issue-opened', 0xea74), + gistPrivate: register('gist-private', 0xea75), + gitForkPrivate: register('git-fork-private', 0xea75), + lock: register('lock', 0xea75), + mirrorPrivate: register('mirror-private', 0xea75), + close: register('close', 0xea76), + removeClose: register('remove-close', 0xea76), + x: register('x', 0xea76), + repoSync: register('repo-sync', 0xea77), + sync: register('sync', 0xea77), + clone: register('clone', 0xea78), + desktopDownload: register('desktop-download', 0xea78), + beaker: register('beaker', 0xea79), + microscope: register('microscope', 0xea79), + vm: register('vm', 0xea7a), + deviceDesktop: register('device-desktop', 0xea7a), + file: register('file', 0xea7b), + fileText: register('file-text', 0xea7b), + more: register('more', 0xea7c), + ellipsis: register('ellipsis', 0xea7c), + kebabHorizontal: register('kebab-horizontal', 0xea7c), + mailReply: register('mail-reply', 0xea7d), + reply: register('reply', 0xea7d), + organization: register('organization', 0xea7e), + organizationFilled: register('organization-filled', 0xea7e), + organizationOutline: register('organization-outline', 0xea7e), + newFile: register('new-file', 0xea7f), + fileAdd: register('file-add', 0xea7f), + newFolder: register('new-folder', 0xea80), + fileDirectoryCreate: register('file-directory-create', 0xea80), + trash: register('trash', 0xea81), + trashcan: register('trashcan', 0xea81), + history: register('history', 0xea82), + clock: register('clock', 0xea82), + folder: register('folder', 0xea83), + fileDirectory: register('file-directory', 0xea83), + symbolFolder: register('symbol-folder', 0xea83), + logoGithub: register('logo-github', 0xea84), + markGithub: register('mark-github', 0xea84), + github: register('github', 0xea84), + terminal: register('terminal', 0xea85), + console: register('console', 0xea85), + repl: register('repl', 0xea85), + zap: register('zap', 0xea86), + symbolEvent: register('symbol-event', 0xea86), + error: register('error', 0xea87), + stop: register('stop', 0xea87), + variable: register('variable', 0xea88), + symbolVariable: register('symbol-variable', 0xea88), + array: register('array', 0xea8a), + symbolArray: register('symbol-array', 0xea8a), + symbolModule: register('symbol-module', 0xea8b), + symbolPackage: register('symbol-package', 0xea8b), + symbolNamespace: register('symbol-namespace', 0xea8b), + symbolObject: register('symbol-object', 0xea8b), + symbolMethod: register('symbol-method', 0xea8c), + symbolFunction: register('symbol-function', 0xea8c), + symbolConstructor: register('symbol-constructor', 0xea8c), + symbolBoolean: register('symbol-boolean', 0xea8f), + symbolNull: register('symbol-null', 0xea8f), + symbolNumeric: register('symbol-numeric', 0xea90), + symbolNumber: register('symbol-number', 0xea90), + symbolStructure: register('symbol-structure', 0xea91), + symbolStruct: register('symbol-struct', 0xea91), + symbolParameter: register('symbol-parameter', 0xea92), + symbolTypeParameter: register('symbol-type-parameter', 0xea92), + symbolKey: register('symbol-key', 0xea93), + symbolText: register('symbol-text', 0xea93), + symbolReference: register('symbol-reference', 0xea94), + goToFile: register('go-to-file', 0xea94), + symbolEnum: register('symbol-enum', 0xea95), + symbolValue: register('symbol-value', 0xea95), + symbolRuler: register('symbol-ruler', 0xea96), + symbolUnit: register('symbol-unit', 0xea96), + activateBreakpoints: register('activate-breakpoints', 0xea97), + archive: register('archive', 0xea98), + arrowBoth: register('arrow-both', 0xea99), + arrowDown: register('arrow-down', 0xea9a), + arrowLeft: register('arrow-left', 0xea9b), + arrowRight: register('arrow-right', 0xea9c), + arrowSmallDown: register('arrow-small-down', 0xea9d), + arrowSmallLeft: register('arrow-small-left', 0xea9e), + arrowSmallRight: register('arrow-small-right', 0xea9f), + arrowSmallUp: register('arrow-small-up', 0xeaa0), + arrowUp: register('arrow-up', 0xeaa1), + bell: register('bell', 0xeaa2), + bold: register('bold', 0xeaa3), + book: register('book', 0xeaa4), + bookmark: register('bookmark', 0xeaa5), + debugBreakpointConditionalUnverified: register('debug-breakpoint-conditional-unverified', 0xeaa6), + debugBreakpointConditional: register('debug-breakpoint-conditional', 0xeaa7), + debugBreakpointConditionalDisabled: register('debug-breakpoint-conditional-disabled', 0xeaa7), + debugBreakpointDataUnverified: register('debug-breakpoint-data-unverified', 0xeaa8), + debugBreakpointData: register('debug-breakpoint-data', 0xeaa9), + debugBreakpointDataDisabled: register('debug-breakpoint-data-disabled', 0xeaa9), + debugBreakpointLogUnverified: register('debug-breakpoint-log-unverified', 0xeaaa), + debugBreakpointLog: register('debug-breakpoint-log', 0xeaab), + debugBreakpointLogDisabled: register('debug-breakpoint-log-disabled', 0xeaab), + briefcase: register('briefcase', 0xeaac), + broadcast: register('broadcast', 0xeaad), + browser: register('browser', 0xeaae), + bug: register('bug', 0xeaaf), + calendar: register('calendar', 0xeab0), + caseSensitive: register('case-sensitive', 0xeab1), + check: register('check', 0xeab2), + checklist: register('checklist', 0xeab3), + chevronDown: register('chevron-down', 0xeab4), + dropDownButton: register('drop-down-button', 0xeab4), + chevronLeft: register('chevron-left', 0xeab5), + chevronRight: register('chevron-right', 0xeab6), + chevronUp: register('chevron-up', 0xeab7), + chromeClose: register('chrome-close', 0xeab8), + chromeMaximize: register('chrome-maximize', 0xeab9), + chromeMinimize: register('chrome-minimize', 0xeaba), + chromeRestore: register('chrome-restore', 0xeabb), + circle: register('circle', 0xeabc), + circleOutline: register('circle-outline', 0xeabc), + debugBreakpointUnverified: register('debug-breakpoint-unverified', 0xeabc), + circleSlash: register('circle-slash', 0xeabd), + circuitBoard: register('circuit-board', 0xeabe), + clearAll: register('clear-all', 0xeabf), + clippy: register('clippy', 0xeac0), + closeAll: register('close-all', 0xeac1), + cloudDownload: register('cloud-download', 0xeac2), + cloudUpload: register('cloud-upload', 0xeac3), + code: register('code', 0xeac4), + collapseAll: register('collapse-all', 0xeac5), + colorMode: register('color-mode', 0xeac6), + commentDiscussion: register('comment-discussion', 0xeac7), + compareChanges: register('compare-changes', 0xeafd), + creditCard: register('credit-card', 0xeac9), + dash: register('dash', 0xeacc), + dashboard: register('dashboard', 0xeacd), + database: register('database', 0xeace), + debugContinue: register('debug-continue', 0xeacf), + debugDisconnect: register('debug-disconnect', 0xead0), + debugPause: register('debug-pause', 0xead1), + debugRestart: register('debug-restart', 0xead2), + debugStart: register('debug-start', 0xead3), + debugStepInto: register('debug-step-into', 0xead4), + debugStepOut: register('debug-step-out', 0xead5), + debugStepOver: register('debug-step-over', 0xead6), + debugStop: register('debug-stop', 0xead7), + debug: register('debug', 0xead8), + deviceCameraVideo: register('device-camera-video', 0xead9), + deviceCamera: register('device-camera', 0xeada), + deviceMobile: register('device-mobile', 0xeadb), + diffAdded: register('diff-added', 0xeadc), + diffIgnored: register('diff-ignored', 0xeadd), + diffModified: register('diff-modified', 0xeade), + diffRemoved: register('diff-removed', 0xeadf), + diffRenamed: register('diff-renamed', 0xeae0), + diff: register('diff', 0xeae1), + discard: register('discard', 0xeae2), + editorLayout: register('editor-layout', 0xeae3), + emptyWindow: register('empty-window', 0xeae4), + exclude: register('exclude', 0xeae5), + extensions: register('extensions', 0xeae6), + eyeClosed: register('eye-closed', 0xeae7), + fileBinary: register('file-binary', 0xeae8), + fileCode: register('file-code', 0xeae9), + fileMedia: register('file-media', 0xeaea), + filePdf: register('file-pdf', 0xeaeb), + fileSubmodule: register('file-submodule', 0xeaec), + fileSymlinkDirectory: register('file-symlink-directory', 0xeaed), + fileSymlinkFile: register('file-symlink-file', 0xeaee), + fileZip: register('file-zip', 0xeaef), + files: register('files', 0xeaf0), + filter: register('filter', 0xeaf1), + flame: register('flame', 0xeaf2), + foldDown: register('fold-down', 0xeaf3), + foldUp: register('fold-up', 0xeaf4), + fold: register('fold', 0xeaf5), + folderActive: register('folder-active', 0xeaf6), + folderOpened: register('folder-opened', 0xeaf7), + gear: register('gear', 0xeaf8), + gift: register('gift', 0xeaf9), + gistSecret: register('gist-secret', 0xeafa), + gist: register('gist', 0xeafb), + gitCommit: register('git-commit', 0xeafc), + gitCompare: register('git-compare', 0xeafd), + gitMerge: register('git-merge', 0xeafe), + githubAction: register('github-action', 0xeaff), + githubAlt: register('github-alt', 0xeb00), + globe: register('globe', 0xeb01), + grabber: register('grabber', 0xeb02), + graph: register('graph', 0xeb03), + gripper: register('gripper', 0xeb04), + heart: register('heart', 0xeb05), + home: register('home', 0xeb06), + horizontalRule: register('horizontal-rule', 0xeb07), + hubot: register('hubot', 0xeb08), + inbox: register('inbox', 0xeb09), + issueClosed: register('issue-closed', 0xeba4), + issueReopened: register('issue-reopened', 0xeb0b), + issues: register('issues', 0xeb0c), + italic: register('italic', 0xeb0d), + jersey: register('jersey', 0xeb0e), + json: register('json', 0xeb0f), + bracket: register('bracket', 0xeb0f), + kebabVertical: register('kebab-vertical', 0xeb10), + key: register('key', 0xeb11), + law: register('law', 0xeb12), + lightbulbAutofix: register('lightbulb-autofix', 0xeb13), + linkExternal: register('link-external', 0xeb14), + link: register('link', 0xeb15), + listOrdered: register('list-ordered', 0xeb16), + listUnordered: register('list-unordered', 0xeb17), + liveShare: register('live-share', 0xeb18), + loading: register('loading', 0xeb19), + location: register('location', 0xeb1a), + mailRead: register('mail-read', 0xeb1b), + mail: register('mail', 0xeb1c), + markdown: register('markdown', 0xeb1d), + megaphone: register('megaphone', 0xeb1e), + mention: register('mention', 0xeb1f), + milestone: register('milestone', 0xeb20), + gitPullRequestMilestone: register('git-pull-request-milestone', 0xeb20), + mortarBoard: register('mortar-board', 0xeb21), + move: register('move', 0xeb22), + multipleWindows: register('multiple-windows', 0xeb23), + mute: register('mute', 0xeb24), + noNewline: register('no-newline', 0xeb25), + note: register('note', 0xeb26), + octoface: register('octoface', 0xeb27), + openPreview: register('open-preview', 0xeb28), + package: register('package', 0xeb29), + paintcan: register('paintcan', 0xeb2a), + pin: register('pin', 0xeb2b), + play: register('play', 0xeb2c), + run: register('run', 0xeb2c), + plug: register('plug', 0xeb2d), + preserveCase: register('preserve-case', 0xeb2e), + preview: register('preview', 0xeb2f), + project: register('project', 0xeb30), + pulse: register('pulse', 0xeb31), + question: register('question', 0xeb32), + quote: register('quote', 0xeb33), + radioTower: register('radio-tower', 0xeb34), + reactions: register('reactions', 0xeb35), + references: register('references', 0xeb36), + refresh: register('refresh', 0xeb37), + regex: register('regex', 0xeb38), + remoteExplorer: register('remote-explorer', 0xeb39), + remote: register('remote', 0xeb3a), + remove: register('remove', 0xeb3b), + replaceAll: register('replace-all', 0xeb3c), + replace: register('replace', 0xeb3d), + repoClone: register('repo-clone', 0xeb3e), + repoForcePush: register('repo-force-push', 0xeb3f), + repoPull: register('repo-pull', 0xeb40), + repoPush: register('repo-push', 0xeb41), + report: register('report', 0xeb42), + requestChanges: register('request-changes', 0xeb43), + rocket: register('rocket', 0xeb44), + rootFolderOpened: register('root-folder-opened', 0xeb45), + rootFolder: register('root-folder', 0xeb46), + rss: register('rss', 0xeb47), + ruby: register('ruby', 0xeb48), + saveAll: register('save-all', 0xeb49), + saveAs: register('save-as', 0xeb4a), + save: register('save', 0xeb4b), + screenFull: register('screen-full', 0xeb4c), + screenNormal: register('screen-normal', 0xeb4d), + searchStop: register('search-stop', 0xeb4e), + server: register('server', 0xeb50), + settingsGear: register('settings-gear', 0xeb51), + settings: register('settings', 0xeb52), + shield: register('shield', 0xeb53), + smiley: register('smiley', 0xeb54), + sortPrecedence: register('sort-precedence', 0xeb55), + splitHorizontal: register('split-horizontal', 0xeb56), + splitVertical: register('split-vertical', 0xeb57), + squirrel: register('squirrel', 0xeb58), + starFull: register('star-full', 0xeb59), + starHalf: register('star-half', 0xeb5a), + symbolClass: register('symbol-class', 0xeb5b), + symbolColor: register('symbol-color', 0xeb5c), + symbolCustomColor: register('symbol-customcolor', 0xeb5c), + symbolConstant: register('symbol-constant', 0xeb5d), + symbolEnumMember: register('symbol-enum-member', 0xeb5e), + symbolField: register('symbol-field', 0xeb5f), + symbolFile: register('symbol-file', 0xeb60), + symbolInterface: register('symbol-interface', 0xeb61), + symbolKeyword: register('symbol-keyword', 0xeb62), + symbolMisc: register('symbol-misc', 0xeb63), + symbolOperator: register('symbol-operator', 0xeb64), + symbolProperty: register('symbol-property', 0xeb65), + wrench: register('wrench', 0xeb65), + wrenchSubaction: register('wrench-subaction', 0xeb65), + symbolSnippet: register('symbol-snippet', 0xeb66), + tasklist: register('tasklist', 0xeb67), + telescope: register('telescope', 0xeb68), + textSize: register('text-size', 0xeb69), + threeBars: register('three-bars', 0xeb6a), + thumbsdown: register('thumbsdown', 0xeb6b), + thumbsup: register('thumbsup', 0xeb6c), + tools: register('tools', 0xeb6d), + triangleDown: register('triangle-down', 0xeb6e), + triangleLeft: register('triangle-left', 0xeb6f), + triangleRight: register('triangle-right', 0xeb70), + triangleUp: register('triangle-up', 0xeb71), + twitter: register('twitter', 0xeb72), + unfold: register('unfold', 0xeb73), + unlock: register('unlock', 0xeb74), + unmute: register('unmute', 0xeb75), + unverified: register('unverified', 0xeb76), + verified: register('verified', 0xeb77), + versions: register('versions', 0xeb78), + vmActive: register('vm-active', 0xeb79), + vmOutline: register('vm-outline', 0xeb7a), + vmRunning: register('vm-running', 0xeb7b), + watch: register('watch', 0xeb7c), + whitespace: register('whitespace', 0xeb7d), + wholeWord: register('whole-word', 0xeb7e), + window: register('window', 0xeb7f), + wordWrap: register('word-wrap', 0xeb80), + zoomIn: register('zoom-in', 0xeb81), + zoomOut: register('zoom-out', 0xeb82), + listFilter: register('list-filter', 0xeb83), + listFlat: register('list-flat', 0xeb84), + listSelection: register('list-selection', 0xeb85), + selection: register('selection', 0xeb85), + listTree: register('list-tree', 0xeb86), + debugBreakpointFunctionUnverified: register('debug-breakpoint-function-unverified', 0xeb87), + debugBreakpointFunction: register('debug-breakpoint-function', 0xeb88), + debugBreakpointFunctionDisabled: register('debug-breakpoint-function-disabled', 0xeb88), + debugStackframeActive: register('debug-stackframe-active', 0xeb89), + circleSmallFilled: register('circle-small-filled', 0xeb8a), + debugStackframeDot: register('debug-stackframe-dot', 0xeb8a), + debugStackframe: register('debug-stackframe', 0xeb8b), + debugStackframeFocused: register('debug-stackframe-focused', 0xeb8b), + debugBreakpointUnsupported: register('debug-breakpoint-unsupported', 0xeb8c), + symbolString: register('symbol-string', 0xeb8d), + debugReverseContinue: register('debug-reverse-continue', 0xeb8e), + debugStepBack: register('debug-step-back', 0xeb8f), + debugRestartFrame: register('debug-restart-frame', 0xeb90), + callIncoming: register('call-incoming', 0xeb92), + callOutgoing: register('call-outgoing', 0xeb93), + menu: register('menu', 0xeb94), + expandAll: register('expand-all', 0xeb95), + feedback: register('feedback', 0xeb96), + gitPullRequestReviewer: register('git-pull-request-reviewer', 0xeb96), + groupByRefType: register('group-by-ref-type', 0xeb97), + ungroupByRefType: register('ungroup-by-ref-type', 0xeb98), + account: register('account', 0xeb99), + gitPullRequestAssignee: register('git-pull-request-assignee', 0xeb99), + bellDot: register('bell-dot', 0xeb9a), + debugConsole: register('debug-console', 0xeb9b), + library: register('library', 0xeb9c), + output: register('output', 0xeb9d), + runAll: register('run-all', 0xeb9e), + syncIgnored: register('sync-ignored', 0xeb9f), + pinned: register('pinned', 0xeba0), + githubInverted: register('github-inverted', 0xeba1), + debugAlt: register('debug-alt', 0xeb91), + serverProcess: register('server-process', 0xeba2), + serverEnvironment: register('server-environment', 0xeba3), + pass: register('pass', 0xeba4), + stopCircle: register('stop-circle', 0xeba5), + playCircle: register('play-circle', 0xeba6), + record: register('record', 0xeba7), + debugAltSmall: register('debug-alt-small', 0xeba8), + vmConnect: register('vm-connect', 0xeba9), + cloud: register('cloud', 0xebaa), + merge: register('merge', 0xebab), + exportIcon: register('export', 0xebac), + graphLeft: register('graph-left', 0xebad), + magnet: register('magnet', 0xebae), + notebook: register('notebook', 0xebaf), + redo: register('redo', 0xebb0), + checkAll: register('check-all', 0xebb1), + pinnedDirty: register('pinned-dirty', 0xebb2), + passFilled: register('pass-filled', 0xebb3), + circleLargeFilled: register('circle-large-filled', 0xebb4), + circleLarge: register('circle-large', 0xebb5), + circleLargeOutline: register('circle-large-outline', 0xebb5), + combine: register('combine', 0xebb6), + gather: register('gather', 0xebb6), + table: register('table', 0xebb7), + variableGroup: register('variable-group', 0xebb8), + typeHierarchy: register('type-hierarchy', 0xebb9), + typeHierarchySub: register('type-hierarchy-sub', 0xebba), + typeHierarchySuper: register('type-hierarchy-super', 0xebbb), + gitPullRequestCreate: register('git-pull-request-create', 0xebbc), + runAbove: register('run-above', 0xebbd), + runBelow: register('run-below', 0xebbe), + notebookTemplate: register('notebook-template', 0xebbf), + debugRerun: register('debug-rerun', 0xebc0), + workspaceTrusted: register('workspace-trusted', 0xebc1), + workspaceUntrusted: register('workspace-untrusted', 0xebc2), + workspaceUnspecified: register('workspace-unspecified', 0xebc3), + terminalCmd: register('terminal-cmd', 0xebc4), + terminalDebian: register('terminal-debian', 0xebc5), + terminalLinux: register('terminal-linux', 0xebc6), + terminalPowershell: register('terminal-powershell', 0xebc7), + terminalTmux: register('terminal-tmux', 0xebc8), + terminalUbuntu: register('terminal-ubuntu', 0xebc9), + terminalBash: register('terminal-bash', 0xebca), + arrowSwap: register('arrow-swap', 0xebcb), + copy: register('copy', 0xebcc), + personAdd: register('person-add', 0xebcd), + filterFilled: register('filter-filled', 0xebce), + wand: register('wand', 0xebcf), + debugLineByLine: register('debug-line-by-line', 0xebd0), + inspect: register('inspect', 0xebd1), + layers: register('layers', 0xebd2), + layersDot: register('layers-dot', 0xebd3), + layersActive: register('layers-active', 0xebd4), + compass: register('compass', 0xebd5), + compassDot: register('compass-dot', 0xebd6), + compassActive: register('compass-active', 0xebd7), + azure: register('azure', 0xebd8), + issueDraft: register('issue-draft', 0xebd9), + gitPullRequestClosed: register('git-pull-request-closed', 0xebda), + gitPullRequestDraft: register('git-pull-request-draft', 0xebdb), + debugAll: register('debug-all', 0xebdc), + debugCoverage: register('debug-coverage', 0xebdd), + runErrors: register('run-errors', 0xebde), + folderLibrary: register('folder-library', 0xebdf), + debugContinueSmall: register('debug-continue-small', 0xebe0), + beakerStop: register('beaker-stop', 0xebe1), + graphLine: register('graph-line', 0xebe2), + graphScatter: register('graph-scatter', 0xebe3), + pieChart: register('pie-chart', 0xebe4), + bracketDot: register('bracket-dot', 0xebe5), + bracketError: register('bracket-error', 0xebe6), + lockSmall: register('lock-small', 0xebe7), + azureDevops: register('azure-devops', 0xebe8), + verifiedFilled: register('verified-filled', 0xebe9), + newLine: register('newline', 0xebea), + layout: register('layout', 0xebeb), + layoutActivitybarLeft: register('layout-activitybar-left', 0xebec), + layoutActivitybarRight: register('layout-activitybar-right', 0xebed), + layoutPanelLeft: register('layout-panel-left', 0xebee), + layoutPanelCenter: register('layout-panel-center', 0xebef), + layoutPanelJustify: register('layout-panel-justify', 0xebf0), + layoutPanelRight: register('layout-panel-right', 0xebf1), + layoutPanel: register('layout-panel', 0xebf2), + layoutSidebarLeft: register('layout-sidebar-left', 0xebf3), + layoutSidebarRight: register('layout-sidebar-right', 0xebf4), + layoutStatusbar: register('layout-statusbar', 0xebf5), + layoutMenubar: register('layout-menubar', 0xebf6), + layoutCentered: register('layout-centered', 0xebf7), + layoutSidebarRightOff: register('layout-sidebar-right-off', 0xec00), + layoutPanelOff: register('layout-panel-off', 0xec01), + layoutSidebarLeftOff: register('layout-sidebar-left-off', 0xec02), + target: register('target', 0xebf8), + indent: register('indent', 0xebf9), + recordSmall: register('record-small', 0xebfa), + errorSmall: register('error-small', 0xebfb), + arrowCircleDown: register('arrow-circle-down', 0xebfc), + arrowCircleLeft: register('arrow-circle-left', 0xebfd), + arrowCircleRight: register('arrow-circle-right', 0xebfe), + arrowCircleUp: register('arrow-circle-up', 0xebff), + heartFilled: register('heart-filled', 0xec04), + map: register('map', 0xec05), + mapFilled: register('map-filled', 0xec06), + circleSmall: register('circle-small', 0xec07), + bellSlash: register('bell-slash', 0xec08), + bellSlashDot: register('bell-slash-dot', 0xec09), + commentUnresolved: register('comment-unresolved', 0xec0a), + gitPullRequestGoToChanges: register('git-pull-request-go-to-changes', 0xec0b), + gitPullRequestNewChanges: register('git-pull-request-new-changes', 0xec0c), + searchFuzzy: register('search-fuzzy', 0xec0d), + commentDraft: register('comment-draft', 0xec0e), + send: register('send', 0xec0f), + sparkle: register('sparkle', 0xec10), + insert: register('insert', 0xec11), + mic: register('mic', 0xec12), + thumbsDownFilled: register('thumbsdown-filled', 0xec13), + thumbsUpFilled: register('thumbsup-filled', 0xec14), + coffee: register('coffee', 0xec15), + snake: register('snake', 0xec16), + game: register('game', 0xec17), + vr: register('vr', 0xec18), + chip: register('chip', 0xec19), + piano: register('piano', 0xec1a), + music: register('music', 0xec1b), + micFilled: register('mic-filled', 0xec1c), + gitFetch: register('git-fetch', 0xec1d), + copilot: register('copilot', 0xec1e), + lightbulbSparkle: register('lightbulb-sparkle', 0xec1f), + lightbulbSparkleAutofix: register('lightbulb-sparkle-autofix', 0xec1f), + robot: register('robot', 0xec20), + sparkleFilled: register('sparkle-filled', 0xec21), + diffSingle: register('diff-single', 0xec22), + diffMultiple: register('diff-multiple', 0xec23), + surroundWith: register('surround-with', 0xec24), + gitStash: register('git-stash', 0xec26), + gitStashApply: register('git-stash-apply', 0xec27), + gitStashPop: register('git-stash-pop', 0xec28), + runAllCoverage: register('run-all-coverage', 0xec2d), + runCoverage: register('run-all-coverage', 0xec2c), + coverage: register('coverage', 0xec2e), + githubProject: register('github-project', 0xec2f), + +} as const; diff --git a/src/vs/base/common/codiconsUtil.ts b/src/vs/base/common/codiconsUtil.ts new file mode 100644 index 00000000000..ce7f9b2dafb --- /dev/null +++ b/src/vs/base/common/codiconsUtil.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { ThemeIcon } from 'vs/base/common/themables'; +import { isString } from 'vs/base/common/types'; + + +const _codiconFontCharacters: { [id: string]: number } = Object.create(null); + +export function register(id: string, fontCharacter: number | string): ThemeIcon { + if (isString(fontCharacter)) { + const val = _codiconFontCharacters[fontCharacter]; + if (val === undefined) { + throw new Error(`${id} references an unknown codicon: ${fontCharacter}`); + } + fontCharacter = val; + } + _codiconFontCharacters[id] = fontCharacter; + return { id }; +} + +/** + * Only to be used by the iconRegistry. + */ +export function getCodiconFontCharacters(): { [id: string]: number } { + return _codiconFontCharacters; +} diff --git a/src/vs/platform/theme/common/iconRegistry.ts b/src/vs/platform/theme/common/iconRegistry.ts index 282230adee5..214f4846dcc 100644 --- a/src/vs/platform/theme/common/iconRegistry.ts +++ b/src/vs/platform/theme/common/iconRegistry.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { RunOnceScheduler } from 'vs/base/common/async'; -import { Codicon, getCodiconFontCharacters } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; +import { getCodiconFontCharacters } from 'vs/base/common/codiconsUtil'; import { ThemeIcon, IconIdentifier } from 'vs/base/common/themables'; import { Emitter, Event } from 'vs/base/common/event'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; From 1d5c97906e0f4e38cb5b51825bd2943560ab5741 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 28 Feb 2024 13:16:21 +0100 Subject: [PATCH 1663/1897] select outline element when double clicking (#206429) fixes https://github.com/microsoft/vscode/issues/206426 re https://github.com/microsoft/vscode/issues/206424 --- .../browser/parts/editor/breadcrumbsControl.ts | 4 ++-- .../browser/parts/editor/breadcrumbsPicker.ts | 2 +- .../browser/outline/documentSymbolsOutline.ts | 4 ++-- .../browser/quickaccess/gotoSymbolQuickAccess.ts | 2 +- .../contrib/outline/browser/outlinePane.ts | 16 ++++++++++++++-- .../services/outline/browser/outline.ts | 2 +- 6 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index b97793d6ddd..1802e2eac64 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -517,7 +517,7 @@ export class BreadcrumbsControl { this._widget.setSelection(items[idx + 1], BreadcrumbsControl.Payload_Pick); } } else { - element.outline.reveal(element, { pinned }, group === SIDE_GROUP); + element.outline.reveal(element, { pinned }, group === SIDE_GROUP, false); } } @@ -860,7 +860,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ return (>input).reveal(element, { pinned: true, preserveFocus: false - }, true); + }, true, false); } } }); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 9ca0b4310a5..13c2df31119 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -507,7 +507,7 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { protected async _revealElement(element: any, options: IEditorOptions, sideBySide: boolean): Promise { this._onWillPickElement.fire(); const outline: IOutline = this._tree.getInput(); - await outline.reveal(element, options, sideBySide); + await outline.reveal(element, options, sideBySide, false); return true; } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts index 557d9a025ec..dc43b9557b7 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts @@ -219,7 +219,7 @@ class DocumentSymbolsOutline implements IOutline { return this._outlineModel?.uri; } - async reveal(entry: DocumentSymbolItem, options: IEditorOptions, sideBySide: boolean): Promise { + async reveal(entry: DocumentSymbolItem, options: IEditorOptions, sideBySide: boolean, select: boolean): Promise { const model = OutlineModel.get(entry); if (!model || !(entry instanceof OutlineElement)) { return; @@ -228,7 +228,7 @@ class DocumentSymbolsOutline implements IOutline { resource: model.uri, options: { ...options, - selection: Range.collapseToStart(entry.symbol.selectionRange), + selection: select ? entry.symbol.range : Range.collapseToStart(entry.symbol.selectionRange), selectionRevealType: TextEditorSelectionRevealType.NearTopIfOutsideViewport, } }, this._editor, sideBySide); diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts index 6788e70b877..71a1b593698 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts @@ -183,7 +183,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess picker.hide(); const [entry] = picker.selectedItems; if (entry && entries[entry.index]) { - outline.reveal(entries[entry.index].element, {}, false); + outline.reveal(entries[entry.index].element, {}, false, false); } })); diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index 6756a2dd0fe..d00db5854fb 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -6,7 +6,7 @@ import 'vs/css!./outlinePane'; import * as dom from 'vs/base/browser/dom'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { TimeoutTimer } from 'vs/base/common/async'; +import { TimeoutTimer, timeout } from 'vs/base/common/async'; import { IDisposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { LRUCache } from 'vs/base/common/map'; import { localize } from 'vs/nls'; @@ -304,7 +304,19 @@ export class OutlinePane extends ViewPane implements IOutlinePane { // feature: reveal outline selection in editor // on change -> reveal/select defining range - this._editorControlDisposables.add(tree.onDidOpen(e => newOutline.reveal(e.element, e.editorOptions, e.sideBySide))); + let idPool = 0; + this._editorControlDisposables.add(tree.onDidOpen(async e => { + const myId = ++idPool; + const isDoubleClick = e.browserEvent?.type === 'dblclick'; + if (!isDoubleClick) { + // workaround for https://github.com/microsoft/vscode/issues/206424 + await timeout(150); + if (myId !== idPool) { + return; + } + } + await newOutline.reveal(e.element, e.editorOptions, e.sideBySide, isDoubleClick); + })); // feature: reveal editor selection in outline const revealActiveElement = () => { if (!this._outlineViewState.followCursor || !newOutline.activeElement) { diff --git a/src/vs/workbench/services/outline/browser/outline.ts b/src/vs/workbench/services/outline/browser/outline.ts index 8447b30d8fe..4a30a3a206d 100644 --- a/src/vs/workbench/services/outline/browser/outline.ts +++ b/src/vs/workbench/services/outline/browser/outline.ts @@ -83,7 +83,7 @@ export interface IOutline { readonly activeElement: E | undefined; readonly onDidChange: Event; - reveal(entry: E, options: IEditorOptions, sideBySide: boolean): Promise | void; + reveal(entry: E, options: IEditorOptions, sideBySide: boolean, select: boolean): Promise | void; preview(entry: E): IDisposable; captureViewState(): IDisposable; dispose(): void; From 4a050aaac8f5a89c7be997db92a0cbceb7db2f2f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 28 Feb 2024 15:29:29 +0100 Subject: [PATCH 1664/1897] support enabling builtin extensions (#206437) * support enabling builtin extensions * fix tests --- .../browser/extensionsWorkbenchService.ts | 83 ++++++++++--------- .../extensionsWorkbenchService.test.ts | 3 - 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index b2e33dd00ae..c92d36d3ecf 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -52,7 +52,7 @@ import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUti import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { mainWindow } from 'vs/base/browser/window'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs'; interface IExtensionStateProvider { (extension: Extension): T; @@ -1650,17 +1650,19 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } async install(arg: string | URI | IExtension, installOptions: InstallExtensionOptions = {}, progressLocation?: ProgressLocation): Promise { - let installable: URI | { extension: IExtension; gallery: IGalleryExtension }; + let installable: URI | IGalleryExtension | undefined; + let extension: IExtension | undefined; if (arg instanceof URI) { installable = arg; } else { let installableInfo: IExtensionInfo | undefined; let gallery: IGalleryExtension | undefined; - let extension: IExtension | undefined; if (isString(arg)) { - installableInfo = { id: arg, version: installOptions.version, preRelease: installOptions?.installPreReleaseVersion ?? this.preferPreReleases }; extension = this.local.find(e => areSameExtensions(e.identifier, { id: arg })); + if (!extension?.isBuiltin) { + installableInfo = { id: arg, version: installOptions.version, preRelease: installOptions.installPreReleaseVersion ?? this.preferPreReleases }; + } } else { extension = arg; gallery = arg.gallery; @@ -1672,47 +1674,44 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension const targetPlatform = extension?.server ? await extension.server.extensionManagementService.getTargetPlatform() : undefined; gallery = firstOrDefault(await this.galleryService.getExtensions([installableInfo], { targetPlatform }, CancellationToken.None)); } - if (!gallery) { - const id = isString(arg) ? arg : (arg).identifier.id; - if (installOptions.version) { - throw new Error(nls.localize('not found version', "Unable to install extension '{0}' because the requested version '{1}' is not found.", id, installOptions.version)); - } else { - throw new Error(nls.localize('not found', "Unable to install extension '{0}' because it is not found.", id)); - } - } - if (!extension) { + if (!extension && gallery) { extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getReloadStatus(ext), undefined, undefined, gallery); + Extensions.updateExtensionFromControlManifest(extension as Extension, await this.extensionManagementService.getExtensionsControlManifest()); } if (extension?.isMalicious) { throw new Error(nls.localize('malicious', "This extension is reported to be problematic.")); } - installable = { extension, gallery }; - if (installOptions.version) { - installOptions.installGivenVersion = true; + // Do not install if requested to enable and extension is already installed + if (!(installOptions.enable && extension?.local)) { + if (!gallery) { + const id = isString(arg) ? arg : (arg).identifier.id; + if (installOptions.version) { + throw new Error(nls.localize('not found version', "Unable to install extension '{0}' because the requested version '{1}' is not found.", id, installOptions.version)); + } else { + throw new Error(nls.localize('not found', "Unable to install extension '{0}' because it is not found.", id)); + } + } + installable = gallery; + if (installOptions.version) { + installOptions.installGivenVersion = true; + } } } - let extension: IExtension; - if (installable instanceof URI || !(installOptions.enable && installable.extension.local)) { + if (installable) { if (installOptions.justification) { const syncCheck = isUndefined(installOptions.isMachineScoped) && this.userDataSyncEnablementService.isEnabled() && this.userDataSyncEnablementService.isResourceEnabled(SyncResource.Extensions); + const buttons: IPromptButton[] = []; + buttons.push({ label: isString(installOptions.justification) ? nls.localize({ key: 'installButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Install Extension") : nls.localize({ key: 'installButtonLabelWithAction', comment: ['&& denotes a mnemonic'] }, "&&Install Extension and {0}", installOptions.justification.action), run: () => true }); + if (!extension) { + buttons.push({ label: nls.localize('open', "Open Extension"), run: () => { this.open(extension!); return false; } }); + } const result = await this.dialogService.prompt({ title: nls.localize('installExtensionTitle', "Install Extension"), - message: installable instanceof URI ? nls.localize('installVSIXMessage', "Would you like to install the extension?") : nls.localize('installExtensionMessage', "Would you like to install '{0}' extension from '{1}'?", installable.extension.displayName, installable.extension.publisherDisplayName), + message: extension ? nls.localize('installExtensionMessage', "Would you like to install '{0}' extension from '{1}'?", extension.displayName, extension.publisherDisplayName) : nls.localize('installVSIXMessage', "Would you like to install the extension?"), detail: isString(installOptions.justification) ? installOptions.justification : installOptions.justification.reason, cancelButton: true, - buttons: [{ - label: isString(installOptions.justification) ? nls.localize({ key: 'installButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Install Extension") : nls.localize({ key: 'installButtonLabelWithAction', comment: ['&& denotes a mnemonic'] }, "&&Install Extension and {0}", installOptions.justification.action), - run: () => true - }, { - label: nls.localize('open', "Open Extension"), - run: () => { - if (!(installable instanceof URI)) { - this.open(installable.extension); - } - return false; - } - }], + buttons, checkbox: syncCheck ? { label: nls.localize('sync extension', "Sync this extension"), checked: true, @@ -1725,11 +1724,15 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension installOptions.isMachineScoped = !result.checkboxChecked; } } - extension = await this.doInstall(installable instanceof URI ? installable : installable.extension, - () => installable instanceof URI ? this.installFromVSIX(installable, installOptions) : this.installFromGallery(installable.extension, installable.gallery, installOptions), - progressLocation); - } else { - extension = installable.extension; + if (installable instanceof URI) { + extension = await this.doInstall(undefined, () => this.installFromVSIX(installable, installOptions), progressLocation); + } else if (extension) { + extension = await this.doInstall(extension, () => this.installFromGallery(extension!, installable, installOptions), progressLocation); + } + } + + if (!extension) { + throw new Error(nls.localize('unknown', "Unable to install extension")); } if (installOptions.version) { @@ -1902,21 +1905,21 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return extension; } - private doInstall(extension: IExtension | URI, installTask: () => Promise, progressLocation?: ProgressLocation): Promise { - const title = extension instanceof URI ? nls.localize('installing extension', 'Installing extension....') : nls.localize('installing named extension', "Installing '{0}' extension....", extension.displayName); + private doInstall(extension: IExtension | undefined, installTask: () => Promise, progressLocation?: ProgressLocation): Promise { + const title = extension ? nls.localize('installing named extension', "Installing '{0}' extension....", extension.displayName) : nls.localize('installing extension', 'Installing extension....'); return this.withProgress({ location: progressLocation ?? ProgressLocation.Extensions, title }, async () => { try { - if (!(extension instanceof URI)) { + if (extension) { this.installing.push(extension); this._onChange.fire(extension); } const local = await installTask(); return await this.waitAndGetInstalledExtension(local.identifier); } finally { - if (!(extension instanceof URI)) { + if (extension) { this.installing = this.installing.filter(e => e !== extension); // Trigger the change without passing the extension because it is replaced by a new instance. this._onChange.fire(undefined); diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts index ab080153b5e..39d5869cd3c 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts @@ -365,7 +365,6 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extension = page.firstPage[0]; assert.strictEqual(ExtensionState.Uninstalled, extension.state); - testObject.install(extension); const identifier = gallery.identifier; // Installing @@ -450,7 +449,6 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extension = page.firstPage[0]; assert.strictEqual(ExtensionState.Uninstalled, extension.state); - testObject.install(extension); installEvent.fire({ identifier: gallery.identifier, source: gallery }); const promise = Event.toPromise(testObject.onChange); @@ -470,7 +468,6 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extension = page.firstPage[0]; assert.strictEqual(ExtensionState.Uninstalled, extension.state); - testObject.install(extension); disposableStore.add(testObject.onChange(target)); // Installing From 85a26649de3502073cfcc86511d316761f025d4a Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 28 Feb 2024 16:01:25 +0100 Subject: [PATCH 1665/1897] Add resource hint API for commenting range provider (#206444) Part of #185551 --- src/vs/editor/common/languages.ts | 8 +++++ .../api/browser/mainThreadComments.ts | 8 ++--- .../workbench/api/common/extHost.protocol.ts | 2 +- .../workbench/api/common/extHostComments.ts | 5 ++- .../comments/browser/commentService.ts | 14 +++++--- .../comments/browser/commentsController.ts | 33 +++++++++++++++---- .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.commentingRangeHint.d.ts | 16 +++++++++ 8 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.commentingRangeHint.d.ts diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 16550bdef8d..57f11ed3e4e 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1710,6 +1710,14 @@ export interface CommentInfo { commentingRanges: CommentingRanges; } + +/** + * @internal + */ +export interface CommentingRangeResourceHint { + schemes: readonly string[]; +} + /** * @internal */ diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 538c754e1b3..4e369eb19da 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -370,8 +370,8 @@ export class MainThreadCommentController implements ICommentController { } } - updateCommentingRanges() { - this._commentService.updateCommentingRanges(this._uniqueId); + updateCommentingRanges(resourceHints?: languages.CommentingRangeResourceHint) { + this._commentService.updateCommentingRanges(this._uniqueId, resourceHints); } private getKnownThread(commentThreadHandle: number): MainThreadCommentThread { @@ -591,14 +591,14 @@ export class MainThreadComments extends Disposable implements MainThreadComments return provider.deleteCommentThread(commentThreadHandle); } - $updateCommentingRanges(handle: number) { + $updateCommentingRanges(handle: number, resourceHints?: languages.CommentingRangeResourceHint) { const provider = this._commentControllers.get(handle); if (!provider) { return; } - provider.updateCommentingRanges(); + provider.updateCommentingRanges(resourceHints); } private registerView(commentsViewAlreadyRegistered: boolean) { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f57a727dd33..dda01854f21 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -145,7 +145,7 @@ export interface MainThreadCommentsShape extends IDisposable { $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange | ICellRange | undefined, extensionId: ExtensionIdentifier, isTemplate: boolean): languages.CommentThread | undefined; $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, changes: CommentThreadChanges): void; $deleteCommentThread(handle: number, commentThreadHandle: number): void; - $updateCommentingRanges(handle: number): void; + $updateCommentingRanges(handle: number, resourceHints?: languages.CommentingRangeResourceHint): void; } export interface AuthenticationForceNewSessionOptions { diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 9db6a543d21..3e20208c247 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -565,7 +565,10 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo set commentingRangeProvider(provider: vscode.CommentingRangeProvider | undefined) { this._commentingRangeProvider = provider; - proxy.$updateCommentingRanges(this.handle); + if (provider?.resourceHints) { + checkProposedApiEnabled(this._extension, 'commentingRangeHint'); + } + proxy.$updateCommentingRanges(this.handle, provider?.resourceHints); } private _reactionHandler?: ReactionHandler; diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index c4747664064..da253044635 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread, CommentOptions, PendingCommentThread } from 'vs/editor/common/languages'; +import { CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread, CommentOptions, PendingCommentThread, CommentingRangeResourceHint } from 'vs/editor/common/languages'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; @@ -104,7 +104,7 @@ export interface ICommentService { disposeCommentThread(ownerId: string, threadId: string): void; getDocumentComments(resource: URI): Promise<(ICommentInfo | null)[]>; getNotebookComments(resource: URI): Promise<(INotebookCommentInfo | null)[]>; - updateCommentingRanges(ownerId: string): void; + updateCommentingRanges(ownerId: string, resourceHints?: CommentingRangeResourceHint): void; hasReactionHandler(owner: string): boolean; toggleReaction(owner: string, resource: URI, thread: CommentThread, comment: Comment, reaction: CommentReaction): Promise; setActiveEditingCommentThread(commentThread: CommentThread | null): void; @@ -172,6 +172,7 @@ export class CommentService extends Disposable implements ICommentService { public readonly commentsModel: ICommentsModel = this._commentsModel; private _commentingRangeResources = new Set(); // URIs + private _commentingRangeResourceHintSchemes = new Set(); // schemes constructor( @IInstantiationService protected readonly instantiationService: IInstantiationService, @@ -406,7 +407,12 @@ export class CommentService extends Disposable implements ICommentService { this._onDidUpdateNotebookCommentThreads.fire(evt); } - updateCommentingRanges(ownerId: string) { + updateCommentingRanges(ownerId: string, resourceHints?: CommentingRangeResourceHint) { + if (resourceHints?.schemes && resourceHints.schemes.length > 0) { + for (const scheme of resourceHints.schemes) { + this._commentingRangeResourceHintSchemes.add(scheme); + } + } this._workspaceHasCommenting.set(true); this._onDidUpdateCommentingRanges.fire({ owner: ownerId }); } @@ -518,6 +524,6 @@ export class CommentService extends Disposable implements ICommentService { } resourceHasCommentingRanges(resource: URI): boolean { - return this._commentingRangeResources.has(resource.toString()); + return this._commentingRangeResourceHintSchemes.has(resource.scheme) || this._commentingRangeResources.has(resource.toString()); } } diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index 1fdee1beb9f..35c0cd6eb7f 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -376,6 +376,7 @@ export class CommentController implements IEditorContribution { private _commentThreadRangeDecorator!: CommentThreadRangeDecorator; private mouseDownInfo: { lineNumber: number } | null = null; private _commentingRangeSpaceReserved = false; + private _commentingRangeAmountReserved = 0; private _computePromise: CancelablePromise> | null; private _addInProgress!: boolean; private _emptyThreadsToAddQueue: [Range | undefined, IEditorMouseEvent | undefined][] = []; @@ -1179,15 +1180,20 @@ export class CommentController implements IEditorContribution { return { extraEditorClassName, lineDecorationsWidth }; } - private getWithCommentsEditorOptions(editor: ICodeEditor, extraEditorClassName: string[], startingLineDecorationsWidth: number) { + private getWithCommentsLineDecorationWidth(editor: ICodeEditor, startingLineDecorationsWidth: number) { let lineDecorationsWidth = startingLineDecorationsWidth; const options = editor.getOptions(); if (options.get(EditorOption.folding) && options.get(EditorOption.showFoldingControls) !== 'never') { lineDecorationsWidth -= 11; } lineDecorationsWidth += 24; + this._commentingRangeAmountReserved = lineDecorationsWidth; + return this._commentingRangeAmountReserved; + } + + private getWithCommentsEditorOptions(editor: ICodeEditor, extraEditorClassName: string[], startingLineDecorationsWidth: number) { extraEditorClassName.push('inline-comment'); - return { lineDecorationsWidth, extraEditorClassName }; + return { lineDecorationsWidth: this.getWithCommentsLineDecorationWidth(editor, startingLineDecorationsWidth), extraEditorClassName }; } private updateEditorLayoutOptions(editor: ICodeEditor, extraEditorClassName: string[], lineDecorationsWidth: number) { @@ -1197,6 +1203,15 @@ export class CommentController implements IEditorContribution { }); } + private ensureCommentingRangeReservedAmount(editor: ICodeEditor) { + const existing = this.getExistingCommentEditorOptions(editor); + if (existing.lineDecorationsWidth !== this._commentingRangeAmountReserved) { + editor.updateOptions({ + lineDecorationsWidth: this.getWithCommentsLineDecorationWidth(editor, existing.lineDecorationsWidth) + }); + } + } + private tryUpdateReservedSpace(uri?: URI) { if (!this.editor) { return; @@ -1211,11 +1226,15 @@ export class CommentController implements IEditorContribution { const hasCommentsOrRanges = hasCommentsOrRangesInInfo || resourceHasCommentingRanges; - if (hasCommentsOrRanges && !this._commentingRangeSpaceReserved && this.commentService.isCommentingEnabled) { - this._commentingRangeSpaceReserved = true; - const { lineDecorationsWidth, extraEditorClassName } = this.getExistingCommentEditorOptions(this.editor); - const newOptions = this.getWithCommentsEditorOptions(this.editor, extraEditorClassName, lineDecorationsWidth); - this.updateEditorLayoutOptions(this.editor, newOptions.extraEditorClassName, newOptions.lineDecorationsWidth); + if (hasCommentsOrRanges && this.commentService.isCommentingEnabled) { + if (!this._commentingRangeSpaceReserved) { + this._commentingRangeSpaceReserved = true; + const { lineDecorationsWidth, extraEditorClassName } = this.getExistingCommentEditorOptions(this.editor); + const newOptions = this.getWithCommentsEditorOptions(this.editor, extraEditorClassName, lineDecorationsWidth); + this.updateEditorLayoutOptions(this.editor, newOptions.extraEditorClassName, newOptions.lineDecorationsWidth); + } else { + this.ensureCommentingRangeReservedAmount(this.editor); + } } else if ((!hasCommentsOrRanges || !this.commentService.isCommentingEnabled) && this._commentingRangeSpaceReserved) { this._commentingRangeSpaceReserved = false; const { lineDecorationsWidth, extraEditorClassName } = this.getExistingCommentEditorOptions(this.editor); diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 7386529c446..071545be93b 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -21,6 +21,7 @@ export const allApiProposals = Object.freeze({ codeActionRanges: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts', codiconDecoration: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts', commentReactor: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentReactor.d.ts', + commentingRangeHint: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentingRangeHint.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', contribCommentPeekContext: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentPeekContext.d.ts', diff --git a/src/vscode-dts/vscode.proposed.commentingRangeHint.d.ts b/src/vscode-dts/vscode.proposed.commentingRangeHint.d.ts new file mode 100644 index 00000000000..595e4f0f62a --- /dev/null +++ b/src/vscode-dts/vscode.proposed.commentingRangeHint.d.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * 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' { + + // @alexr00 https://github.com/microsoft/vscode/issues/185551 + + /** + * Commenting range provider for a {@link CommentController comment controller}. + */ + export interface CommentingRangeProvider { + readonly resourceHints?: { schemes: readonly string[] }; + } +} From cff275ae6414cf8ea7e4266b7e84e9eea4459161 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 28 Feb 2024 16:37:07 +0100 Subject: [PATCH 1666/1897] Adopt custom hover in settings and keybindings editor (#206440) * adopt custom hover in settings and keybindings editor * fiy smoketests --- .../ui/highlightedlabel/highlightedLabel.ts | 2 +- .../browser/ui/iconLabel/simpleIconLabel.ts | 17 ++++++++-- .../ui/keybindingLabel/keybindingLabel.ts | 17 ++++++---- src/vs/base/browser/ui/toggle/toggle.ts | 1 + .../editor/contrib/find/browser/findWidget.ts | 1 + .../browser/inlineCompletionsHintsWidget.ts | 2 +- .../browser/inlineEditHintsWidget.ts | 2 +- .../actionWidget/browser/actionList.ts | 2 +- .../quickinput/browser/quickInputList.ts | 1 + .../parts/editor/editorGroupWatermark.ts | 7 ++-- .../browser/parts/editor/editorPlaceholder.ts | 2 +- .../browser/parts/statusbar/statusbarItem.ts | 2 +- .../emptyTextEditorHint.ts | 6 ++-- .../browser/extensionFeaturesTab.ts | 13 +++++--- .../browser/view/cellParts/cellStatusPart.ts | 2 +- .../preferences/browser/keybindingWidgets.ts | 6 ++-- .../preferences/browser/keybindingsEditor.ts | 32 ++++++++++++------- .../preferences/browser/preferencesWidgets.ts | 8 +++-- .../settingsEditorSettingIndicators.ts | 20 ++++++------ .../preferences/browser/settingsTree.ts | 15 +++++---- .../preferences/browser/settingsWidgets.ts | 20 ++++++------ .../contrib/preferences/browser/tocTree.ts | 12 +++++-- test/automation/src/keybindings.ts | 6 ++-- 23 files changed, 127 insertions(+), 69 deletions(-) diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index 5aaa8bcc551..4847da97d2f 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -140,7 +140,7 @@ export class HighlightedLabel extends Disposable { } else { if (!this.customHover && this.title !== '') { const hoverDelegate = this.options?.hoverDelegate ?? getDefaultHoverDelegate('mouse'); - this.customHover = this._store.add(setupCustomHover(hoverDelegate, this.domNode, this.title)); + this.customHover = this._register(setupCustomHover(hoverDelegate, this.domNode, this.title)); } else if (this.customHover) { this.customHover.update(this.title); } diff --git a/src/vs/base/browser/ui/iconLabel/simpleIconLabel.ts b/src/vs/base/browser/ui/iconLabel/simpleIconLabel.ts index 659572d4ff8..dc35bd8a9ab 100644 --- a/src/vs/base/browser/ui/iconLabel/simpleIconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/simpleIconLabel.ts @@ -4,9 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { reset } from 'vs/base/browser/dom'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { IDisposable } from 'vs/base/common/lifecycle'; -export class SimpleIconLabel { +export class SimpleIconLabel implements IDisposable { + + private hover?: ICustomHover; constructor( private readonly _container: HTMLElement @@ -17,6 +22,14 @@ export class SimpleIconLabel { } set title(title: string) { - this._container.title = title; + if (!this.hover && title) { + this.hover = setupCustomHover(getDefaultHoverDelegate('mouse'), this._container, title); + } else if (this.hover) { + this.hover.update(title); + } + } + + dispose(): void { + this.hover?.dispose(); } } diff --git a/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts b/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts index 431e33048cd..20eea5d8850 100644 --- a/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts +++ b/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts @@ -4,8 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { UILabelProvider } from 'vs/base/common/keybindingLabels'; import { ResolvedKeybinding, ResolvedChord } from 'vs/base/common/keybindings'; +import { Disposable } from 'vs/base/common/lifecycle'; import { equals } from 'vs/base/common/objects'; import { OperatingSystem } from 'vs/base/common/platform'; import 'vs/css!./keybindingLabel'; @@ -50,18 +53,21 @@ export const unthemedKeybindingLabelOptions: KeybindingLabelOptions = { keybindingLabelShadow: undefined }; -export class KeybindingLabel { +export class KeybindingLabel extends Disposable { private domNode: HTMLElement; private options: KeybindingLabelOptions; private readonly keyElements = new Set(); + private hover: ICustomHover; private keybinding: ResolvedKeybinding | undefined; private matches: Matches | undefined; private didEverRender: boolean; constructor(container: HTMLElement, private os: OperatingSystem, options?: KeybindingLabelOptions) { + super(); + this.options = options || Object.create(null); const labelForeground = this.options.keybindingLabelForeground; @@ -71,6 +77,8 @@ export class KeybindingLabel { this.domNode.style.color = labelForeground; } + this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.domNode, '')); + this.didEverRender = false; container.appendChild(this.domNode); } @@ -102,11 +110,8 @@ export class KeybindingLabel { this.renderChord(this.domNode, chords[i], this.matches ? this.matches.chordPart : null); } const title = (this.options.disableTitle ?? false) ? undefined : this.keybinding.getAriaLabel() || undefined; - if (title !== undefined) { - this.domNode.title = title; - } else { - this.domNode.removeAttribute('title'); - } + this.hover.update(title); + this.domNode.setAttribute('aria-label', title || ''); } else if (this.options && this.options.renderUnboundKeybindings) { this.renderUnbound(this.domNode); } diff --git a/src/vs/base/browser/ui/toggle/toggle.ts b/src/vs/base/browser/ui/toggle/toggle.ts index 53e0e2b9a30..22d60e98508 100644 --- a/src/vs/base/browser/ui/toggle/toggle.ts +++ b/src/vs/base/browser/ui/toggle/toggle.ts @@ -59,6 +59,7 @@ export class ToggleActionViewItem extends BaseActionViewItem { inputActiveOptionBackground: options.toggleStyles?.inputActiveOptionBackground, inputActiveOptionBorder: options.toggleStyles?.inputActiveOptionBorder, inputActiveOptionForeground: options.toggleStyles?.inputActiveOptionForeground, + hoverDelegate: options.hoverDelegate })); this._register(this.toggle.onChange(() => this._action.checked = !!this.toggle && this.toggle.checked)); } diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index 424375e8cae..8d80095419d 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -1051,6 +1051,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL icon: findSelectionIcon, title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand), isChecked: false, + hoverDelegate: hoverDelegate, inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground), inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder), inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground), diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget.ts index 30f622e01fc..e847839c042 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget.ts @@ -302,7 +302,7 @@ class StatusBarViewItem extends MenuEntryActionViewItem { if (this.label) { const div = h('div.keybinding').root; - const k = new KeybindingLabel(div, OS, { disableTitle: true, ...unthemedKeybindingLabelOptions }); + const k = this._register(new KeybindingLabel(div, OS, { disableTitle: true, ...unthemedKeybindingLabelOptions })); k.set(kb); this.label.textContent = this._action.label; this.label.appendChild(div); diff --git a/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.ts b/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.ts index 73824bd5e6c..59553805863 100644 --- a/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.ts +++ b/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.ts @@ -175,7 +175,7 @@ class StatusBarViewItem extends MenuEntryActionViewItem { if (this.label) { const div = h('div.keybinding').root; - const k = new KeybindingLabel(div, OS, { disableTitle: true, ...unthemedKeybindingLabelOptions }); + const k = this._register(new KeybindingLabel(div, OS, { disableTitle: true, ...unthemedKeybindingLabelOptions })); k.set(kb); this.label.textContent = this._action.label; this.label.appendChild(div); diff --git a/src/vs/platform/actionWidget/browser/actionList.ts b/src/vs/platform/actionWidget/browser/actionList.ts index 4b0b93f695d..80f8e74664e 100644 --- a/src/vs/platform/actionWidget/browser/actionList.ts +++ b/src/vs/platform/actionWidget/browser/actionList.ts @@ -140,7 +140,7 @@ class ActionItemRenderer implements IListRenderer, IAction } disposeTemplate(_templateData: IActionMenuTemplateData): void { - // noop + _templateData.keybinding.dispose(); } } diff --git a/src/vs/platform/quickinput/browser/quickInputList.ts b/src/vs/platform/quickinput/browser/quickInputList.ts index 26e32890647..98993dd9070 100644 --- a/src/vs/platform/quickinput/browser/quickInputList.ts +++ b/src/vs/platform/quickinput/browser/quickInputList.ts @@ -277,6 +277,7 @@ class ListElementRenderer implements IListRenderer { clearNode(container); - this.renderTable(data, container); + tableDisposable.value = this.renderTable(data, container); })); } - this.renderTable(tableData.data, container); + tableDisposable.value = this.renderTable(tableData.data, container); } - private renderTable(tableData: ITableData, container: HTMLElement): void { + private renderTable(tableData: ITableData, container: HTMLElement): IDisposable { + const disposables = new DisposableStore(); append(container, $('table', undefined, $('tr', undefined, @@ -478,7 +480,7 @@ class ExtensionFeatureView extends Disposable { result.push(element); } else if (item instanceof ResolvedKeybinding) { const element = $(''); - const kbl = new KeybindingLabel(element, OS, defaultKeybindingLabelStyles); + const kbl = disposables.add(new KeybindingLabel(element, OS, defaultKeybindingLabelStyles)); kbl.set(item); result.push(element); } else if (item instanceof Color) { @@ -490,6 +492,7 @@ class ExtensionFeatureView extends Disposable { }) ); }))); + return disposables; } private renderMarkdownData(container: HTMLElement, renderer: IExtensionFeatureMarkdownRenderer): void { diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts index b5c306d0b41..88c4252fd82 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts @@ -294,7 +294,7 @@ class CellStatusBarItem extends Disposable { this._itemDisposables.clear(); if (!this._currentItem || this._currentItem.text !== item.text) { - new SimpleIconLabel(this.container).text = item.text.replace(/\n/g, ' '); + this._itemDisposables.add(new SimpleIconLabel(this.container)).text = item.text.replace(/\n/g, ' '); } const resolveColor = (color: ThemeColor | string) => { diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts index 70920e27b23..2f92489d4b8 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts @@ -141,6 +141,7 @@ export class DefineKeybindingWidget extends Widget { private _keybindingInputWidget: KeybindingsSearchWidget; private _outputNode: HTMLElement; private _showExistingKeybindingsNode: HTMLElement; + private _keybindingDisposables = this._register(new DisposableStore()); private _chords: ResolvedKeybinding[] | null = null; private _isVisible: boolean = false; @@ -238,17 +239,18 @@ export class DefineKeybindingWidget extends Widget { } private onKeybinding(keybinding: ResolvedKeybinding[] | null): void { + this._keybindingDisposables.clear(); this._chords = keybinding; dom.clearNode(this._outputNode); dom.clearNode(this._showExistingKeybindingsNode); - const firstLabel = new KeybindingLabel(this._outputNode, OS, defaultKeybindingLabelStyles); + const firstLabel = this._keybindingDisposables.add(new KeybindingLabel(this._outputNode, OS, defaultKeybindingLabelStyles)); firstLabel.set(this._chords?.[0] ?? undefined); if (this._chords) { for (let i = 1; i < this._chords.length; i++) { this._outputNode.appendChild(document.createTextNode(nls.localize('defineKeybinding.chordsTo', "chord to"))); - const chordLabel = new KeybindingLabel(this._outputNode, OS, defaultKeybindingLabelStyles); + const chordLabel = this._keybindingDisposables.add(new KeybindingLabel(this._outputNode, OS, defaultKeybindingLabelStyles)); chordLabel.set(this._chords[i]); } } diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index b673830c1e9..f9b434adf8f 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -58,6 +58,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands'; import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; const $ = DOM.$; @@ -897,6 +899,7 @@ class ActionsColumnRenderer implements ITableRenderer(extensionContainer, $('a.extension-label', { tabindex: 0 })); const extensionId = new HighlightedLabel(DOM.append(extensionContainer, $('.extension-id-container.code'))); - return { sourceColumn, sourceLabel, extensionLabel, extensionContainer, extensionId, disposables: new DisposableStore() }; + return { sourceColumn, sourceColumnHover, sourceLabel, extensionLabel, extensionContainer, extensionId, disposables: new DisposableStore() }; } renderElement(keybindingItemEntry: IKeybindingItemEntry, index: number, templateData: ISourceColumnTemplateData, height: number | undefined): void { @@ -1039,14 +1049,14 @@ class SourceColumnRenderer implements ITableRenderer { this.extensionsWorkbenchService.open(extension.identifier.value); @@ -1062,6 +1072,7 @@ class SourceColumnRenderer implements ITableRenderer DOM.EventHelper.stop(e))); this._register(DOM.addDisposableListener(this.anchorElement, DOM.EventType.CLICK, e => this.onClick(e))); this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_UP, e => this.onKeyUp(e))); @@ -145,7 +149,7 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem { const workspace = this.contextService.getWorkspace(); if (this._folder) { this.labelElement.textContent = this._folder.name; - this.anchorElement.title = this._folder.name; + this.anchorElementHover.update(this._folder.name); const detailsText = this.labelWithCount(this._action.label, total); this.detailsElement.textContent = detailsText; this.dropDownElement.classList.toggle('hide', workspace.folders.length === 1 || !this._action.checked); @@ -153,7 +157,7 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem { const labelText = this.labelWithCount(this._action.label, total); this.labelElement.textContent = labelText; this.detailsElement.textContent = ''; - this.anchorElement.title = this._action.label; + this.anchorElementHover.update(this._action.label); this.dropDownElement.classList.remove('hide'); } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index cf98a39a26e..fd8f621714f 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -135,12 +135,12 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { } private createWorkspaceTrustIndicator(): SettingIndicator { + const disposables = new DisposableStore(); const workspaceTrustElement = $('span.setting-indicator.setting-item-workspace-trust'); - const workspaceTrustLabel = new SimpleIconLabel(workspaceTrustElement); + const workspaceTrustLabel = disposables.add(new SimpleIconLabel(workspaceTrustElement)); workspaceTrustLabel.text = '$(warning) ' + localize('workspaceUntrustedLabel', "Setting value not applied"); const content = localize('trustLabel', "The setting value can only be applied in a trusted workspace."); - const disposables = new DisposableStore(); const showHover = (focus: boolean) => { return this.hoverService.showHover({ ...this.defaultHoverOptions, @@ -164,23 +164,24 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { } private createScopeOverridesIndicator(): SettingIndicator { + const disposables = new DisposableStore(); // Don't add .setting-indicator class here, because it gets conditionally added later. const otherOverridesElement = $('span.setting-item-overrides'); - const otherOverridesLabel = new SimpleIconLabel(otherOverridesElement); + const otherOverridesLabel = disposables.add(new SimpleIconLabel(otherOverridesElement)); return { element: otherOverridesElement, label: otherOverridesLabel, - disposables: new DisposableStore() + disposables }; } private createSyncIgnoredIndicator(): SettingIndicator { + const disposables = new DisposableStore(); const syncIgnoredElement = $('span.setting-indicator.setting-item-ignored'); - const syncIgnoredLabel = new SimpleIconLabel(syncIgnoredElement); + const syncIgnoredLabel = disposables.add(new SimpleIconLabel(syncIgnoredElement)); syncIgnoredLabel.text = localize('extensionSyncIgnoredLabel', 'Not synced'); const syncIgnoredHoverContent = localize('syncIgnoredTitle', "This setting is ignored during sync"); - const disposables = new DisposableStore(); const showHover = (focus: boolean) => { return this.hoverService.showHover({ ...this.defaultHoverOptions, @@ -193,19 +194,20 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { return { element: syncIgnoredElement, label: syncIgnoredLabel, - disposables: new DisposableStore() + disposables }; } private createDefaultOverrideIndicator(): SettingIndicator { + const disposables = new DisposableStore(); const defaultOverrideIndicator = $('span.setting-indicator.setting-item-default-overridden'); - const defaultOverrideLabel = new SimpleIconLabel(defaultOverrideIndicator); + const defaultOverrideLabel = disposables.add(new SimpleIconLabel(defaultOverrideIndicator)); defaultOverrideLabel.text = localize('defaultOverriddenLabel', "Default value changed"); return { element: defaultOverrideIndicator, label: defaultOverrideLabel, - disposables: new DisposableStore() + disposables }; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 8a24222eac3..9b7e714c4ce 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -69,6 +69,8 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { getInvalidTypeError } from 'vs/workbench/services/preferences/common/preferencesValidation'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; const $ = DOM.$; @@ -796,13 +798,13 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const labelCategoryContainer = DOM.append(titleElement, $('.setting-item-cat-label-container')); const categoryElement = DOM.append(labelCategoryContainer, $('span.setting-item-category')); const labelElementContainer = DOM.append(labelCategoryContainer, $('span.setting-item-label')); - const labelElement = new SimpleIconLabel(labelElementContainer); + const labelElement = toDispose.add(new SimpleIconLabel(labelElementContainer)); const indicatorsLabel = this._instantiationService.createInstance(SettingsTreeIndicatorsLabel, titleElement); toDispose.add(indicatorsLabel); const descriptionElement = DOM.append(container, $('.setting-item-description')); const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator')); - modifiedIndicatorElement.title = localize('modified', "The setting has been configured in the current scope."); + toDispose.add(setupCustomHover(getDefaultHoverDelegate('mouse'), modifiedIndicatorElement, () => localize('modified', "The setting has been configured in the current scope."))); const valueElement = DOM.append(container, $('.setting-item-value')); const controlElement = DOM.append(valueElement, $('div.setting-item-control')); @@ -889,7 +891,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const titleTooltip = setting.key + (element.isConfigured ? ' - Modified' : ''); template.categoryElement.textContent = element.displayCategory ? (element.displayCategory + ': ') : ''; - template.categoryElement.title = titleTooltip; + template.elementDisposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), template.categoryElement, titleTooltip)); template.labelElement.text = element.displayLabel; template.labelElement.title = titleTooltip; @@ -1817,24 +1819,25 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre _container.classList.add('setting-item'); _container.classList.add('setting-item-bool'); + const toDispose = new DisposableStore(); + const container = DOM.append(_container, $(AbstractSettingRenderer.CONTENTS_SELECTOR)); container.classList.add('settings-row-inner-container'); const titleElement = DOM.append(container, $('.setting-item-title')); const categoryElement = DOM.append(titleElement, $('span.setting-item-category')); const labelElementContainer = DOM.append(titleElement, $('span.setting-item-label')); - const labelElement = new SimpleIconLabel(labelElementContainer); + const labelElement = toDispose.add(new SimpleIconLabel(labelElementContainer)); const indicatorsLabel = this._instantiationService.createInstance(SettingsTreeIndicatorsLabel, titleElement); const descriptionAndValueElement = DOM.append(container, $('.setting-item-value-description')); const controlElement = DOM.append(descriptionAndValueElement, $('.setting-item-bool-control')); const descriptionElement = DOM.append(descriptionAndValueElement, $('.setting-item-description')); const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator')); - modifiedIndicatorElement.title = localize('modified', "The setting has been configured in the current scope."); + toDispose.add(setupCustomHover(getDefaultHoverDelegate('mouse'), modifiedIndicatorElement, localize('modified', "The setting has been configured in the current scope."))); const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message')); - const toDispose = new DisposableStore(); const checkbox = new Toggle({ icon: Codicon.check, actionClassName: 'setting-value-checkbox', isChecked: true, title: '', ...unthemedToggleStyles }); controlElement.appendChild(checkbox.domNode); toDispose.add(checkbox); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index 6675881177f..3cf230fa6ee 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -27,6 +27,8 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { settingsDiscardIcon, settingsEditIcon, settingsRemoveIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; import { settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { defaultButtonStyles, getInputBoxStyle, getSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; const $ = DOM.$; @@ -673,8 +675,8 @@ export class ListSettingWidget extends AbstractListSettingWidget : localize('listSiblingHintLabel', "List item `{0}` with sibling `${1}`", value.data, sibling); const { rowElement } = rowElementGroup; - rowElement.title = title; - rowElement.setAttribute('aria-label', rowElement.title); + this.listDisposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), rowElement, title)); + rowElement.setAttribute('aria-label', title); } protected getLocalizedStrings() { @@ -733,8 +735,8 @@ export class ExcludeSettingWidget extends ListSettingWidget { : localize('excludeSiblingHintLabel', "Exclude files matching `{0}`, only when a file matching `{1}` is present", value.data, sibling); const { rowElement } = rowElementGroup; - rowElement.title = title; - rowElement.setAttribute('aria-label', rowElement.title); + this.listDisposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), rowElement, title)); + rowElement.setAttribute('aria-label', title); } protected override getLocalizedStrings() { @@ -763,8 +765,8 @@ export class IncludeSettingWidget extends ListSettingWidget { : localize('includeSiblingHintLabel', "Include files matching `{0}`, only when a file matching `{1}` is present", value.data, sibling); const { rowElement } = rowElementGroup; - rowElement.title = title; - rowElement.setAttribute('aria-label', rowElement.title); + this.listDisposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), rowElement, title)); + rowElement.setAttribute('aria-label', title); } protected override getLocalizedStrings() { @@ -1161,10 +1163,10 @@ export class ObjectSettingDropdownWidget extends AbstractListSettingWidget { @@ -111,17 +115,20 @@ export class TOCRenderer implements ITreeRenderer, index: number, template: ITOCEntryTemplate): void { + template.elementDisposables.clear(); + const element = node.element; const count = element.count; const label = element.label; template.labelElement.textContent = label; - template.labelElement.title = label; + template.elementDisposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), template.labelElement, label)); if (count) { template.countElement.textContent = ` (${count})`; @@ -131,6 +138,7 @@ export class TOCRenderer implements ITreeRenderer Date: Wed, 28 Feb 2024 08:57:08 -0800 Subject: [PATCH 1667/1897] Clear terminal markers when link is opened --- .../links/browser/terminalLinkQuickpick.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts index bfa7bbfb687..c35df5218e3 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts @@ -154,6 +154,16 @@ export class TerminalLinkQuickpick extends DisposableStore { r(); })); disposables.add(Event.once(pick.onDidAccept)(() => { + // Restore terminal scroll state + if (this._terminalScrollStateSaved) { + const markTracker = this._instance?.xterm?.markTracker; + if (markTracker) { + markTracker.restoreScrollState(); + markTracker.clear(); + this._terminalScrollStateSaved = false; + } + } + accepted = true; const event = new TerminalLinkQuickPickEvent(EventType.CLICK); const activeItem = pick.activeItems?.[0]; From 3f78333d3f31fc631c7c74ac188c42b0404de3e4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 28 Feb 2024 19:02:06 +0100 Subject: [PATCH 1668/1897] chore - update distro (#206458) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 947ec6bd4ee..f9142db514f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.88.0", - "distro": "de07f23454d5352cc3711ca34d51278767e6eb0a", + "distro": "a5b6daf94540aab9d17335c2c2533e629d750123", "author": { "name": "Microsoft Corporation" }, From 29fe7ad5294d9acdccb0b703ea606c0782b5597b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 28 Feb 2024 10:10:57 -0800 Subject: [PATCH 1669/1897] check if event is undefined --- src/vs/workbench/contrib/tasks/common/problemCollectors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts index d07edd2679c..9f47ca8564e 100644 --- a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts +++ b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts @@ -441,7 +441,7 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement }, 500, false, true)(async (markerEvent) => { markerChanged?.dispose(); markerChanged = undefined; - if (!markerEvent.includes(modelEvent.uri) || (this.markerService.read({ resource: modelEvent.uri }).length !== 0)) { + if (!markerEvent || !markerEvent.includes(modelEvent.uri) || (this.markerService.read({ resource: modelEvent.uri }).length !== 0)) { return; } const oldLines = Array.from(this.lines); From ca48da35e18e56366a44672e9dd3e531a6186cb7 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 28 Feb 2024 10:31:31 -0800 Subject: [PATCH 1670/1897] cue -> signal --- .../browser/accessibilitySignalService.ts | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts index d2689b002d7..782de469f5b 100644 --- a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts +++ b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts @@ -17,15 +17,15 @@ export const IAccessibilitySignalService = createDecorator; - playAccessibilitySignals(cues: (AccessibilitySignal | { cue: AccessibilitySignal; source: string })[]): Promise; - isSoundEnabled(cue: AccessibilitySignal): boolean; - isAnnouncementEnabled(cue: AccessibilitySignal): boolean; - onSoundEnabledChanged(cue: AccessibilitySignal): Event; - onAnnouncementEnabledChanged(cue: AccessibilitySignal): Event; + playSignal(signal: AccessibilitySignal, options?: IAccessbilitySignalOptions): Promise; + playAccessibilitySignals(signals: (AccessibilitySignal | { signal: AccessibilitySignal; source: string })[]): Promise; + isSoundEnabled(signal: AccessibilitySignal): boolean; + isAnnouncementEnabled(signal: AccessibilitySignal): boolean; + onSoundEnabledChanged(signal: AccessibilitySignal): Event; + onAnnouncementEnabledChanged(signal: AccessibilitySignal): Event; - playSound(cue: Sound, allowManyInParallel?: boolean): Promise; - playSignalLoop(cue: AccessibilitySignal, milliseconds: number): IDisposable; + playSound(signal: Sound, allowManyInParallel?: boolean): Promise; + playSignalLoop(signal: AccessibilitySignal, milliseconds: number): IDisposable; } export interface IAccessbilitySignalOptions { @@ -68,26 +68,26 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi } } - public async playAccessibilitySignals(cues: (AccessibilitySignal | { cue: AccessibilitySignal; source: string })[]): Promise { - for (const cue of cues) { - this.sendSignalTelemetry('cue' in cue ? cue.cue : cue, 'source' in cue ? cue.source : undefined); + public async playAccessibilitySignals(signals: (AccessibilitySignal | { signal: AccessibilitySignal; source: string })[]): Promise { + for (const signal of signals) { + this.sendSignalTelemetry('signal' in signal ? signal.signal : signal, 'source' in signal ? signal.source : undefined); } - const cueArray = cues.map(c => 'cue' in c ? c.cue : c); - const alerts = cueArray.filter(cue => this.isAnnouncementEnabled(cue)).map(c => c.announcementMessage); + const signalArray = signals.map(s => 'signal' in s ? s.signal : s); + const alerts = signalArray.filter(signal => this.isAnnouncementEnabled(signal)).map(s => s.announcementMessage); if (alerts.length) { this.accessibilityService.status(alerts.join(', ')); } // Some sounds are reused. Don't play the same sound twice. - const sounds = new Set(cueArray.filter(cue => this.isSoundEnabled(cue)).map(cue => cue.sound.getSound())); + const sounds = new Set(signalArray.filter(signal => this.isSoundEnabled(signal)).map(signal => signal.sound.getSound())); await Promise.all(Array.from(sounds).map(sound => this.playSound(sound, true))); } - private sendSignalTelemetry(cue: AccessibilitySignal, source: string | undefined): void { + private sendSignalTelemetry(signal: AccessibilitySignal, source: string | undefined): void { const isScreenReaderOptimized = this.accessibilityService.isScreenReaderOptimized(); - const key = cue.name + (source ? `::${source}` : '') + (isScreenReaderOptimized ? '{screenReaderOptimized}' : ''); + const key = signal.name + (source ? `::${source}` : '') + (isScreenReaderOptimized ? '{screenReaderOptimized}' : ''); // Only send once per user session if (this.sentTelemetry.has(key) || this.getVolumeInPercent() === 0) { return; @@ -107,7 +107,7 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi comment: 'This data is collected to understand how signals are used and if more signals should be added.'; }>('signal.played', { - signal: cue.name, + signal: signal.name, source: source ?? '', isScreenReaderOptimized, }); @@ -240,8 +240,8 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi return Event.fromObservableLight(this.isSoundEnabledCache.get({ signal })); } - public onAnnouncementEnabledChanged(cue: AccessibilitySignal): Event { - return Event.fromObservableLight(this.isAnnouncementEnabledCache.get({ signal: cue })); + public onAnnouncementEnabledChanged(signal: AccessibilitySignal): Event { + return Event.fromObservableLight(this.isAnnouncementEnabledCache.get({ signal })); } } From 819d26fe01fd2c6aaf03e17ca426a3a95d95f2a3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 28 Feb 2024 10:50:58 -0800 Subject: [PATCH 1671/1897] alert -> announcement --- .../browser/accessibilitySignalService.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts index 782de469f5b..b8078d3182c 100644 --- a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts +++ b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts @@ -57,9 +57,9 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi } public async playSignal(signal: AccessibilitySignal, options: IAccessbilitySignalOptions = {}): Promise { - const alertMessage = signal.announcementMessage; - if (this.isAnnouncementEnabled(signal, options.userGesture) && alertMessage) { - this.accessibilityService.status(alertMessage); + const announcementMessage = signal.announcementMessage; + if (this.isAnnouncementEnabled(signal, options.userGesture) && announcementMessage) { + this.accessibilityService.status(announcementMessage); } if (this.isSoundEnabled(signal, options.userGesture)) { @@ -73,9 +73,9 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi this.sendSignalTelemetry('signal' in signal ? signal.signal : signal, 'source' in signal ? signal.source : undefined); } const signalArray = signals.map(s => 'signal' in s ? s.signal : s); - const alerts = signalArray.filter(signal => this.isAnnouncementEnabled(signal)).map(s => s.announcementMessage); - if (alerts.length) { - this.accessibilityService.status(alerts.join(', ')); + const announcements = signalArray.filter(signal => this.isAnnouncementEnabled(signal)).map(s => s.announcementMessage); + if (announcements.length) { + this.accessibilityService.status(announcements.join(', ')); } // Some sounds are reused. Don't play the same sound twice. @@ -214,7 +214,7 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi () => event.signal.announcementMessage ? this.configurationService.getValue<'auto' | 'off' | 'userGesture' | 'always' | 'never'>(event.signal.settingsKey + '.announcement') : false ); return derived(reader => { - /** @description alert enabled */ + /** @description announcement enabled */ const setting = settingObservable.read(reader); if ( !this.screenReaderAttached.read(reader) From eb4e516a8a6fed349066479cb0abffbf9259c663 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Wed, 28 Feb 2024 11:28:09 -0800 Subject: [PATCH 1672/1897] Folded cells run-in-section (#205315) * initial functionality, needs css tweaks * update button css + add executing spinner * mutable disposable to avoid leaking listeners --- .../browser/media/notebookFolding.css | 18 +++++++ .../browser/view/cellParts/foldedCellHint.ts | 52 ++++++++++++++++++- .../browser/view/renderers/cellRenderer.ts | 7 ++- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookFolding.css b/src/vs/workbench/contrib/notebook/browser/media/notebookFolding.css index 7433c9a7eb2..88bfc96b8f6 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebookFolding.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookFolding.css @@ -46,6 +46,8 @@ .monaco-workbench .notebookOverlay > .cell-list-container .notebook-folded-hint { position: absolute; user-select: none; + display: flex; + align-items: center; } .monaco-workbench .notebookOverlay > .cell-list-container .notebook-folded-hint-label { @@ -55,6 +57,22 @@ opacity: 0.7; } +.monaco-workbench .notebookOverlay > .cell-list-container .folded-cell-run-section-button { + position: relative; + left: 0px; + padding: 2px; + border-radius: 5px; + margin-right: 4px; + height: 16px; + width: 16px; + z-index: var(--z-index-notebook-cell-expand-part-button); +} + +.monaco-workbench .notebookOverlay > .cell-list-container .folded-cell-run-section-button:hover { + background-color: var(--vscode-editorStickyScrollHover-background); + cursor: pointer; +} + .monaco-workbench .notebookOverlay .cell-editor-container .monaco-editor .margin-view-overlays .codicon-folding-expanded, .monaco-workbench .notebookOverlay .cell-editor-container .monaco-editor .margin-view-overlays .codicon-folding-collapsed { margin-left: 0; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts index 2fe72e05af8..211e85e9a62 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts @@ -11,12 +11,21 @@ import { FoldingController } from 'vs/workbench/contrib/notebook/browser/control import { CellEditState, CellFoldingState, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; +import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; +import { executingStateIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { MutableDisposable } from 'vs/base/common/lifecycle'; export class FoldedCellHint extends CellContentPart { + private readonly _runButtonListener = this._register(new MutableDisposable()); + private readonly _cellExecutionListener = this._register(new MutableDisposable()); + constructor( private readonly _notebookEditor: INotebookEditor, private readonly _container: HTMLElement, + @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService ) { super(); } @@ -27,20 +36,27 @@ export class FoldedCellHint extends CellContentPart { private update(element: MarkupCellViewModel) { if (!this._notebookEditor.hasModel()) { + this._cellExecutionListener.clear(); + this._runButtonListener.clear(); return; } if (element.isInputCollapsed || element.getEditState() === CellEditState.Editing) { + this._cellExecutionListener.clear(); + this._runButtonListener.clear(); DOM.hide(this._container); } else if (element.foldingState === CellFoldingState.Collapsed) { const idx = this._notebookEditor.getViewModel().getCellIndex(element); const length = this._notebookEditor.getViewModel().getFoldedLength(idx); - DOM.reset(this._container, this.getHiddenCellsLabel(length), this.getHiddenCellHintButton(element)); + + DOM.reset(this._container, this.getRunFoldedSectionButton({ start: idx, end: idx + length }), this.getHiddenCellsLabel(length), this.getHiddenCellHintButton(element)); DOM.show(this._container); const foldHintTop = element.layoutInfo.previewHeight; this._container.style.top = `${foldHintTop}px`; } else { + this._cellExecutionListener.clear(); + this._runButtonListener.clear(); DOM.hide(this._container); } } @@ -67,6 +83,40 @@ export class FoldedCellHint extends CellContentPart { return expandIcon; } + private getRunFoldedSectionButton(range: ICellRange): HTMLElement { + const runAllContainer = DOM.$('span.folded-cell-run-section-button'); + const cells = this._notebookEditor.getCellsInRange(range); + + const isRunning = cells.some(cell => { + const cellExecution = this._notebookExecutionStateService.getCellExecution(cell.uri); + return cellExecution && cellExecution.state === NotebookCellExecutionState.Executing; + }); + + const runAllIcon = isRunning ? + ThemeIcon.modify(executingStateIcon, 'spin') : + Codicon.play; + runAllContainer.classList.add(...ThemeIcon.asClassNameArray(runAllIcon)); + + this._runButtonListener.value = DOM.addDisposableListener(runAllContainer, DOM.EventType.CLICK, () => { + this._notebookEditor.executeNotebookCells(cells); + }); + + this._cellExecutionListener.value = this._notebookExecutionStateService.onDidChangeExecution(() => { + const isRunning = cells.some(cell => { + const cellExecution = this._notebookExecutionStateService.getCellExecution(cell.uri); + return cellExecution && cellExecution.state === NotebookCellExecutionState.Executing; + }); + + const runAllIcon = isRunning ? + ThemeIcon.modify(executingStateIcon, 'spin') : + Codicon.play; + runAllContainer.className = ''; + runAllContainer.classList.add('folded-cell-run-section-button', ...ThemeIcon.asClassNameArray(runAllIcon)); + }); + + return runAllContainer; + } + override updateInternalLayoutNow(element: MarkupCellViewModel) { this.update(element); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 25a1310af43..9f6f5b8dac5 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -49,6 +49,7 @@ import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; const $ = DOM.$; @@ -109,6 +110,8 @@ abstract class AbstractCellRenderer { export class MarkupCellRenderer extends AbstractCellRenderer implements IListRenderer { static readonly TEMPLATE_ID = 'markdown_cell'; + private _notebookExecutionStateService: INotebookExecutionStateService; + constructor( notebookEditor: INotebookEditorDelegate, dndController: CellDragAndDropController, @@ -120,8 +123,10 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen @IMenuService menuService: IMenuService, @IKeybindingService keybindingService: IKeybindingService, @INotificationService notificationService: INotificationService, + @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService ) { super(instantiationService, notebookEditor, contextMenuService, menuService, configurationService, keybindingService, notificationService, contextKeyServiceProvider, 'markdown', dndController); + this._notebookExecutionStateService = notebookExecutionStateService; } get templateId() { @@ -169,7 +174,7 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen templateDisposables.add(scopedInstaService.createInstance(CellChatPart, this.notebookEditor, cellChatPart)), templateDisposables.add(scopedInstaService.createInstance(CellEditorStatusBar, this.notebookEditor, container, editorPart, undefined)), templateDisposables.add(new CellFocusIndicator(this.notebookEditor, titleToolbar, focusIndicatorTop, focusIndicatorLeft, focusIndicatorRight, focusIndicatorBottom)), - templateDisposables.add(new FoldedCellHint(this.notebookEditor, DOM.append(container, $('.notebook-folded-hint')))), + templateDisposables.add(new FoldedCellHint(this.notebookEditor, DOM.append(container, $('.notebook-folded-hint')), this._notebookExecutionStateService)), templateDisposables.add(new CellDecorations(rootContainer, decorationContainer)), templateDisposables.add(scopedInstaService.createInstance(CellComments, this.notebookEditor, cellCommentPartContainer)), templateDisposables.add(new CollapsedCellInput(this.notebookEditor, cellInputCollapsedContainer)), From f99981ed56f3adca0d446e9e2ec69fdff563d2c0 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 28 Feb 2024 11:47:31 -0800 Subject: [PATCH 1673/1897] cli: fix compressor not draining and leading to truncated responses (#206464) * cli: fix compressor not draining and leading to truncated responses Fixes https://github.com/microsoft/vscode-remote-release/issues/9594 * fix lint --- cli/src/tunnels/socket_signal.rs | 115 +++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 36 deletions(-) diff --git a/cli/src/tunnels/socket_signal.rs b/cli/src/tunnels/socket_signal.rs index 9036c6ae3f9..2227f323852 100644 --- a/cli/src/tunnels/socket_signal.rs +++ b/cli/src/tunnels/socket_signal.rs @@ -94,41 +94,42 @@ impl ServerMessageSink { async fn server_message_or_closed( &mut self, - body: Option<&[u8]>, + body_or_end: Option<&[u8]>, ) -> Result<(), mpsc::error::SendError> { let i = self.id; let mut tx = self.tx.take().unwrap(); - let msg = body - .map(|b| self.get_server_msg_content(b)) - .map(|body| RefServerMessageParams { i, body }); - let r = match &mut tx { - ServerMessageDestination::Channel(tx) => { - tx.send(SocketSignal::from_message(&ToClientRequest { - id: None, - params: match msg { - Some(msg) => ClientRequestMethod::servermsg(msg), - None => ClientRequestMethod::serverclose(ServerClosedParams { i }), - }, - })) - .await - } - ServerMessageDestination::Rpc(caller) => { - match msg { - Some(msg) => caller.notify("servermsg", msg), - None => caller.notify("serverclose", ServerClosedParams { i }), - }; - Ok(()) - } - }; + if let Some(b) = body_or_end { + let body = self.get_server_msg_content(b, false); + let r = + send_data_or_close_if_none(i, &mut tx, Some(RefServerMessageParams { i, body })) + .await; + self.tx = Some(tx); + return r; + } + let tail = self.get_server_msg_content(&[], true); + if !tail.is_empty() { + let _ = send_data_or_close_if_none( + i, + &mut tx, + Some(RefServerMessageParams { i, body: tail }), + ) + .await; + } + + let r = send_data_or_close_if_none(i, &mut tx, None).await; self.tx = Some(tx); r } - pub(crate) fn get_server_msg_content<'a: 'b, 'b>(&'a mut self, body: &'b [u8]) -> &'b [u8] { + pub(crate) fn get_server_msg_content<'a: 'b, 'b>( + &'a mut self, + body: &'b [u8], + finish: bool, + ) -> &'b [u8] { if let Some(flate) = &mut self.flate { - if let Ok(compressed) = flate.process(body) { + if let Ok(compressed) = flate.process(body, finish) { return compressed; } } @@ -137,6 +138,32 @@ impl ServerMessageSink { } } +async fn send_data_or_close_if_none( + i: u16, + tx: &mut ServerMessageDestination, + msg: Option>, +) -> Result<(), mpsc::error::SendError> { + match tx { + ServerMessageDestination::Channel(tx) => { + tx.send(SocketSignal::from_message(&ToClientRequest { + id: None, + params: match msg { + Some(msg) => ClientRequestMethod::servermsg(msg), + None => ClientRequestMethod::serverclose(ServerClosedParams { i }), + }, + })) + .await + } + ServerMessageDestination::Rpc(caller) => { + match msg { + Some(msg) => caller.notify("servermsg", msg), + None => caller.notify("serverclose", ServerClosedParams { i }), + }; + Ok(()) + } + } +} + impl Drop for ServerMessageSink { fn drop(&mut self) { self.multiplexer.remove(self.id); @@ -162,7 +189,8 @@ impl ClientMessageDecoder { pub fn decode<'a: 'b, 'b>(&'a mut self, message: &'b [u8]) -> std::io::Result<&'b [u8]> { match &mut self.dec { - Some(d) => d.process(message), + // todo@connor4312 do we ever need to actually 'finish' the client message stream? + Some(d) => d.process(message, false), None => Ok(message), } } @@ -175,6 +203,7 @@ trait FlateAlgorithm { &mut self, contents: &[u8], output: &mut [u8], + finish: bool, ) -> Result; } @@ -193,9 +222,15 @@ impl FlateAlgorithm for DecompressFlateAlgorithm { &mut self, contents: &[u8], output: &mut [u8], + finish: bool, ) -> Result { + let mode = match finish { + true => flate2::FlushDecompress::Finish, + false => flate2::FlushDecompress::None, + }; + self.0 - .decompress(contents, output, flate2::FlushDecompress::None) + .decompress(contents, output, mode) .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e)) } } @@ -215,9 +250,15 @@ impl FlateAlgorithm for CompressFlateAlgorithm { &mut self, contents: &[u8], output: &mut [u8], + finish: bool, ) -> Result { + let mode = match finish { + true => flate2::FlushCompress::Finish, + false => flate2::FlushCompress::Sync, + }; + self.0 - .compress(contents, output, flate2::FlushCompress::Sync) + .compress(contents, output, mode) .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e)) } } @@ -241,23 +282,25 @@ where } } - pub fn process(&mut self, contents: &[u8]) -> std::io::Result<&[u8]> { + pub fn process(&mut self, contents: &[u8], finish: bool) -> std::io::Result<&[u8]> { let mut out_offset = 0; let mut in_offset = 0; loop { let in_before = self.flate.total_in(); let out_before = self.flate.total_out(); - match self - .flate - .process(&contents[in_offset..], &mut self.output[out_offset..]) - { + match self.flate.process( + &contents[in_offset..], + &mut self.output[out_offset..], + finish, + ) { Ok(flate2::Status::Ok | flate2::Status::BufError) => { let processed_len = in_offset + (self.flate.total_in() - in_before) as usize; let output_len = out_offset + (self.flate.total_out() - out_before) as usize; - if processed_len < contents.len() { + if processed_len < contents.len() || output_len == self.output.len() { // If we filled the output buffer but there's more data to compress, - // extend the output buffer and keep compressing. + // or the output got filled after processing all input, extend + // the output buffer and keep compressing. out_offset = output_len; in_offset = processed_len; if output_len == self.output.len() { @@ -298,7 +341,7 @@ mod tests { // 3000 and 30000 test resizing the buffer for msg_len in [3, 30, 300, 3000, 30000] { let vals = (0..msg_len).map(|v| v as u8).collect::>(); - let compressed = sink.get_server_msg_content(&vals); + let compressed = sink.get_server_msg_content(&vals, false); assert_ne!(compressed, vals); let decompressed = decompress.decode(compressed).unwrap(); assert_eq!(decompressed.len(), vals.len()); From a629412f21715c62de57df845a3477322bc3eb5c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 28 Feb 2024 11:48:22 -0800 Subject: [PATCH 1674/1897] #201901 --- .../browser/accessibilitySignalService.ts | 8 ++++++++ .../browser/media/voiceRecordingStarted.mp3 | Bin 0 -> 42112 bytes .../browser/accessibilityConfiguration.ts | 10 ++++++++++ .../contrib/speech/browser/speechService.ts | 6 ++++-- 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 src/vs/platform/accessibilitySignal/browser/media/voiceRecordingStarted.mp3 diff --git a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts index d2689b002d7..469f8fbf019 100644 --- a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts +++ b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts @@ -314,6 +314,7 @@ export class Sound { public static readonly clear = Sound.register({ fileName: 'clear.mp3' }); public static readonly save = Sound.register({ fileName: 'save.mp3' }); public static readonly format = Sound.register({ fileName: 'format.mp3' }); + public static readonly voiceRecordingStarted = Sound.register({ fileName: 'voiceRecordingStarted.mp3' }); private constructor(public readonly fileName: string) { } } @@ -582,6 +583,13 @@ export class AccessibilitySignal { settingsKey: 'accessibility.signals.format' }); + public static readonly voiceRecordingStarted = AccessibilitySignal.register({ + name: localize('accessibilitySignals.voiceRecordingStarted', 'Voice Recording Started'), + sound: Sound.voiceRecordingStarted, + legacySoundSettingsKey: 'audioCues.voiceRecordingStarted', + settingsKey: 'accessibility.signals.voiceRecordingStarted' + }); + private constructor( public readonly sound: SoundSource, public readonly name: string, diff --git a/src/vs/platform/accessibilitySignal/browser/media/voiceRecordingStarted.mp3 b/src/vs/platform/accessibilitySignal/browser/media/voiceRecordingStarted.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..cc959d00a46cc72bb6fb6a6763e36d129c5e317e GIT binary patch literal 42112 zcmeFYXH-*NxA(nw8X*J-J@ilw2)GkKiUJ8uO6V34l+cSH-G*MI&;_I^0Z~JhrXu#9 z08$i@CU!+pM6gg*P{jHQQ#j1 z{!!o`1^!Xs9|itV;2#D4QQ#j1{!!o`1^)k{fZ+FK%HP*w#`ybyn)&+xy!qEbnfI^b zf7@3teoCIZOzARC3#*>~?8>|8a=3`P&F;gMUWGTcWEjZ60RrfNye=MA5GT zsO$cwBCbY7XFab=(qvf`n5YEJ8jb;IXtd637_K&&XRd_U7OCOL z70a`%FXK#X1AK{a;&r0f;&q*-D_3VU%dW>8`y{IOjXh7yhV_qRKT+WCKK7Iu9o1VgJUkiKLsmfMP9g*jC#tBX#lo z7Vwhyjfd8_JaLu#84bT;)=0Z;lu_zpk|tQir*8LXj*AnlmVh%l;{?wU zt$*=@Tr@C)|7oXR|9E07wVWn1)A~hnUX+!3LY2811c??FB;hmM*ap|pN$_+G90tJ0 zrTY}5f+VqEp+p|*?nWMZ{M{^Co0q}uAmH7}MrsNi2oZ#v#23;jc;c(gdU8N+UN$4v zFp0y(WfU{$0X+J)yN?+=M8s1vIvEb}yW0iaaRQ~&Ct#1E>~Zfft2YiyTG1QwtPI<0 zMNdornST(NYSSV3{kRKXJ@NUaK-kJ4GuUR*w*p7#m1qj-l4m+}0M-ckQT~Fw89zy4 zd^EI`k*3cegggf?oU9kcW2O2+z3m-iTySLI*+Z5sJtvb9E>2_}4FMK^1=HJil{GN5 zH;YRFTnf$#t@uJWqcazWX_wEtxwrht(KmHd=L6Ga>1gk!Z;3m7&fn;&|7Am-E@@p& zdHWFWJrwx#)AH!Sq|aYIHGO|BoZavUWS}^oT#*4KOO2yW+MDEx`k*OnC<7eEa`{jS zdKk$d%U@3%sW^_p!R10Uf?T0UCc~W(X#sSpwAbaIABeQC^Zr$pfGs z>U3jaPuu-zACSz$Qcc>O%=!#?3%p2x(Sc>!eV~H5&uaU);n(ZG-0s7;blc_U+<7^JXJV7-~fHXtlRD>Ht1$|=JtWjG0~KI5dtp=gV04#1A-UCSb!;UJ&CS$ zC~u=JC86Ep7Oy6A?A37quqRk6$l!s1ABrw_Y=3Q3bN#UCtnY$!wVE_ImY6^E${liX zgjK@`-5Mfx<$YDQqbIlJx4v^o3mE#H<#8_LPujFqk6HtCRkoz<$7|zT2_;fra?NaX zl3)~ZXg#b1Mfe4smaRckCWs=W=ZRx@f2V;QqA@)=uhQ^$z#d||9mDRZ{PH!loy<7; zpyKh}lE-vB*U2R+H{hmsoHI%{(F$!@QIJkH!d`Hmx>0q-vM@gP+%Uq$vEpY)SKPhZ zaVzaZ#E)NBpsVu3=MN|v+a>y7pg#GBt5);M&x{?<1E1^U`{Q@j-dur+73@Jks?%6IjZ}`ZCN;^x#cvB1~f1RuA zEUUtv)mM~^rS-K)86L3tnE`IsAms9qsCM}@f#CCJXjnGnF0EG`=$ZblDCl#3&+6 zgoF+c0e}R+e*-kUjN2OD_f;c+pBaN6Zr(!c7f4mMKL-BrhOn(XYfy!!hHUBljZEil z0!-@0svSdaE20B}DJAP5V9XO0PUx7nP=X_w?cLi9pY%XlSi8NOA^y?G`Nz(Fdmh_V zRuL7nnL~BY2F|w~^CRsD7bI2Sn%q!K>abC?kWKb)0#LC3Pkk4av&FvU1>n zLpz(NR0lt88g9ZaY2RS9_55MM>(C8d`wi~Zp0;6m#LO$klRtfJi1t45S$E}^Q8fDW z<9b;I!uPwW@Aort(lQJH#-N{Lvm!T}OoSz41A@9q&%$pB`JtBp$UIBR$v=n2cGo-` z-tXZe=O%@Yo1JE!snB}#>QwwmSWp1Yo*v26rzfI9h26dB8(4ek^_i7)51>7Xn?PX{ z7Vd9#W&ur?d4wPh(F`o%-QgSO+sIFASZ%#;aCB9s)7PQvk9OaQTxd)4UT(K@eYHDH zM|ea7l~gD87h5|zDu-65^QsWU4#?%1>)lat1e zA4ML;p+P%=VD1P3fWhHhE4K3X9Ja+mc?shAN{zvXb#}?b1h~ADk`^Mskd1*v;jlvV zFgUU+3E3iZDHWEUK`#vLF0K|ar(L8Yu&Btk5@*%qF-iwu?uj&@t~#kqUBwUodU12)P94T z(K+u(X5DXM#lJ+1#B*Cd{s_w_<3oZs6CwC$Tr9hBi~b?A?|)u7AM-!ctt#bU z_sLESgTwCe5KLYx0pI|THW7UI&%_?4zoT}@Os31P^{CG1zrr=w_<v63Oi{e-3>J zMD-dcAp_(bs92A$&4UF>+WD{L;(zJuib76~;$tv?4yUQ`$TTY`vAY;e$zp)5x|}~J z#*WWR!VM;qvIm|=D?YfbD)i{&5SaQNd#dXf{RwvE{qK^o9>DNkBu042J&8KwPXtoC)CRRob8o^~3Y|$j+`aT`Rwt zqA&-uoqdfbKi=a;Ev>e(u038JdfFv++09n}T=2cif9}03v)yOSqW;v?9>gWuf7l`z z3@rp;cBsQ9aQ_#UY-nqD2^##tmKxDw({lpZ@E%h;1V_wHgo91s$>f1Ecd!lvv5y;6 zMjv_h4cPayYrCjeI&GBa0`ltF_rKdVNg=KwZ^xAd(V4vXkU+&_RxU3FB-H;lZ$I1E1#WN*izvG<9Z%#nnCPK< z*i3r}u}~c>KWMVw>+lh?ls@0VdW{dyKa!o!j+FJE9kdxeXk9EmBoO>~(to+sLFiP3 zZG{f_{6X?UNa{5v*_ULWb(~`k>rEL0jCkj06Y|Xv*lZ>tn-+va(Ju6vSB*)DIPS&z zTCD4x+dGhVrTId{-jB1-!+#6(zduObd2H9<@YlB)tFOzdUY6gKcvZXjJ54x5^Q!0v z3e?7Hwj!gvjWeXzLq{7`NR6l)qJu)~gJVx0#?MA1WJY`WFa_xg?srNP>|-!6qZh3NDxiWxi^b+;UO9o97=Ixijn zHc9jM{atdaix%5%`P>suS+KwW*7NACwsC#Id+;j3xnn*9l^S-6!J>HWtmB~k-BFQ_ zt|N#l(}Z&go|6~7u5WB@D4f*SOPRldG`C#+oZ+OYyH&enlKvdQuGL=Mx3YAh^wiH! zQkL6hJ_g-+bLO?+uNu(<&f^1b6zxK{UL9Bd7CTPG71&|R{^kcjGA-KSe~QO~!u;U- zb7}Ivvna_qSLQM4@%*pnh==SD&jR5v74s6w9$7Z98976dm>>g(&*qwx`{sr*qVf@% z`F1{%wv%ibDF*evJ*cSgB&`H_)+^=Q%le2Sr|cP$RYck*3{nMajQB33Jh3WX8|a~TLN5yc9(>2lKADc-n84aIKP9MG zm3g;$$~ibR$@*oRJ!`D+beu38(BAe6Abw+@w7Q?#v+EXZELc))p8% zRJgH~Fr2<~&e*9o6!-{S*_mOa%`hF<-2lAHI4`xih5IV_#nqoRBWLp5qMXn5_&u0= zk@j3rQwxYG*~6*PE>g*_B|JVi zGnpk?&fiRleVfi6rD@{?eel*@G-=9)J``k#g^x+o)PUoUKery^4rpYkBwcbUJy@?+ zSNgzYNH?&&j6YLzd+gm_MC_#r1)SWbVC}*-45vgvwrV3L8II?13omaVOxUJADy=*| z{b|FxaNSkf-Zu8)nfY}COL2mpISMca!i@FbkCQ~@_(gRlTwq*Va~pXWo+JWfK@Y+B zcx4nQPD1JKKaq(vqHhJl3Y8LxCFOpcV6;kD2A zsJf#j{(Jt&-5YtM;utqpu)}8Mn2n(5QsDPJfq(cXjc>(GR&V*8CVYN`HiQuZTpjS_N7bz%OVM+*r<24MHH2#Ew6DE`xMzXCOAjDlrS(!(Kr+q^>C_ zBl&jm>)um&ZAZyX8>(U^zGy@)MJ-03sIayXIcpSfva7)BaLTpXQ#S5ey&pCT1Vbb| z{LbdrV~b|84oRd5A9)Kc}SaUj3}6%`#pD0dYNh=E!0Wm7%D-70zAu2 zzirX=LAI95ICdif8brBeVTUBzTs{F{?H(P=$=zrpx>t- ziyHI$dzT}Ni@p>JR|~=?{N6H83tpH`W_XPgSa?Xw-*ca}qIuT4R-Pu_iCL9%0E-s~ zoeJ$ZG?|2#p|6Juq-y7;M>6e#&IYc=b+=X62zrt^hq*j;udM0{&)nO1Rg{7%oad%^bM}KO()KKV zTh%*`GB*v6XQ<*;>993^&N7fq))e?k@mTCcLs9CMfD;F;SJ&d7@-=?s$Q-J@sQM^6 zEI6uyk%>kYiRBr6k=5la^o7hI4PbPj!kKtGcF?m1jhqdF+-mnj9BNOYZLBnc&lMA` zlWQePZu_C#UmKr}))cG%sFRVH|6o9LIZOv=O+PHQCtP#TCnCr`uUR_ivsu7t%kx)t z&n%pI@b{ATORJn8|n-gTUpoG;t;!@PKxLy)<^?Z235xarWTZr-wHbFak>b zJF>+eANeAE8qbSp4fb-zZbLgFYRoXTO&1&PzuQvj@am|s^08ZzR?c@fO}*JsA`nbL zHcU&?^Fy-a@LV$}@_~u50+2@X?DjU%XFP-A+BZP<3=|{+$U`uSH3VnBw=oM}sgXuV zvy^rPohiWIz_lXoETK20`CT>+A836WJb&Yj1p3+@i{=KZ<*$>qBo{<>?`&P&QqQ{>m~uz z(3JNb=qnKNLpi}fn47n=SG>paw%{pgu>n%(=V&26ERD(_aMd)P!SAH{;5TzsIU-zD zJceS%5qcq7Ve8_FodxC= zP8b(;TYhl}jDFi&&F=H{p(ux(u#?>({bi}D!;jn!^$H9YE0@CWE(=x**dSzJZ2kG@ zu(b(I3xi<5H!xs!5(W+PHh|19JWtIBhy+UjZCDME4QRo?Xv0rR#7lrf#Y&r^?x*S`46O6l0Ahp6f^ddIcY z%5ydhZ}R(65i}a}CT_}jv2H%r(nc@>S=gFDOxaHIUBCv~k+5H-s6P4ry&?#6R=I?F!L8%a;CNJ4c|Isza1rWfywwMTi7cqE+S$ z1#$PO=`P)KH@C?FVQ9}vCxv5yyR(#{R_#5MTH1e4ZlzF7`9Su1_Q73~ii#l7@r0Gzpv}4F;i^yqz4)D(TbN-UhDn zqS+K%hA~N-_Qim5@m*)5d#3XvJQog7w}bo!OEdViiL`%X(_QZF8b^tEgCV!R5|!RO zDqKz1cE7)bjBe43D;vV@%!7}I)-N?LW%d0PRXSL+@-rHuu|&x`J${nzIVwVKkED#( z_$g)}Y|Vx7v-jB1Zag6^KL9uR^lyF)E4j>7y?7XS4(cy9G)}jbgX~yeAO#)?Lh$?{ zBx5(E2aH*nqa%X@Gyscn=*2?&n3hQ9_RzjTbkPO!>n#f!u$};`FctS3)3)7CsjwLEk_s^$3^0G;qP&QTiOkc%*&%P)b z<38otpuErAJ{oc(vCo&%KtnNN`s8O+oP>TxnV}BBJ5o#(^9blmvORc!WRK3K1LAB( ztITqcXZ8T;=?%^ORM6Mow`4B#(yUuVgbuQ6qizWWA58egKx@-25A@j?+9ptA+dt{A z+wcA3erLc??W&Q7#qC{lE4KtID%p@5wbZygR~LG>Wey_XNaV}RZfIKcw<$<@2wB4N zAay_o0w_VUI(a|?cE1#yQ8-4SfZB~2_s#K_A$yHI3mYbhl76{&Z+BvM=We`Z6&+=& zI2&u4S@C?R>9uHZpLy%8b(x%fPb5b2ad;%nZDA*Y3YzZz)%#l$r zi35U@i^#Y6jGt5EqX?;HW$&{&BY2d{nLW=^RJ1mIx6-_ZI9ba*=e^~W#s>SFH0$ik z(RO!2^>kWlrVsvkw_)haeEui@;W*M0F7Gmk=zobGdvHR*fY)!cu*Oe5fH2nQvsVt$ z*%3nfBjktv6eYAj%wH!p*6dG@1n{Lp4c|Ka5r*a?&=B7Mi01*IffAg_Bx7e^B0AgO z5yV!TX}$$S3YHY!qW`jKG&U z&@n*ZwbHY7IY$@(%dI}_WnnK`%g)p_wOMS_zDLiGUnr^9peceZ@2q2zlD#5)^90*@ zbE4-V)-BJ~rl~Z^TY7N{ha1yKf6n`_=}?aB8!1p+7`UL4%<1YcBw(xR>FKm#3RtOq)2CDQZ*C|N58Lb6b2YH;RNZke;f@Tjy;+?C5=qE2A zf0hI@4vG-3F(xq;<`i#=_X(B6T2}S_xqv6Z0aH#VC`H9eS*tf%UiwMaCcpe!kih%$OD5 zw>{R6JF(>RG0?wRu$aO@QGJzy8ZGgUGeht?HL2yfyc~`n%b{ZLm@!@hI43L8M}{vk z(BLfNhZ6sd44l=KodxRv9%+nBw1TT-BBTbZ!-2Zru41OuPc_#5rhXI+djFMA?3BN{<>T910F7 zSyI0|8d7O^WOUCb^^vp(SFG;|RxiOILz#^U*NiQRJQ>l#q!NQd#JC79y@qC@!UjE+ zYfxX{T&`F(B#ssHWE9{rc^cpo*s#ku5;Lv_mx0+j4C=F};K}}h>04evBz9_})1g(Y z-|{yWG0p=y%mAs+KRUJAkr`VVV+Ygabe*K+fcNbIXQXhaeE3q4 z3x^%W{-TTdFIvf6iMUKmxTa3zax5Rc>U_`UxRw_Thha-2^TO~qd_L5SEWBQn`mJ|+ z{EwhA&U|GNuc|@AM;mA;nFdkudgIJ;G|wF`<8V!Jfc_jW$~%cy9@EDI47n0uH+?fx zIuX*hSC>SbbKAzOr^`w~CNSGqcaUhN`PTEhB=Q*FV_e0y5>sIT?N{!TPk5^AVR$`& zu8&p5tRrVsm>&(=OCA`V*ZnB@C4cs<_sc{j3bc-KiEQhdPrl2thmyq}l9G6}Q2KB* znFtm`hWwA3C>|Zb;7Rm}5_BcRQMn+7p<>*ojTFl zKb8*MyFL5JhVWaai;lMp9b$|o0K6NNE8JtaAyv$ua@T0L$+W7pvYwb8juB{Be*FAw zg-S*s-V}MT+=rLW0UziqX}q(+!^g()(p+OHz=^KGaHpGOh94jt3L*jqnoNq0JW0Dr z&6UkcY}L0>Qy6su)vU}NrP$7E(p>o{>T%IPNbF$cNS zP<-B8>ub%+k$quZ-=_4IM(#MAPX1ZBZ#hpq-ry0Usu9bav!p3$RnQtBTA$hR3ULy_ zGEd@P@elmDvCZG{^Ew5dkRPe+4?iRqn_240fouM&^%R8?!vPptS_c^|%3ed`Ad@G` z8k2UxGFb3JCo1Htd~kUYn{^cNuI~|_&A7bzb7zPBgjr|Y;_ z4mXlRK2hIR45pDO&$xyBD$muzc`wt8<0G|E(K@NBuKjhNyywqYGzSWrTlnd4?r0M1 z`oO%lXZz>%w|lfvXnt7$y6c|Eb>2x7npX^Rd3a$M9|UUic1xLbeVl29xZ5F|tJkxf zT!#$L*Pq3SChqnIWqhBjEbc_q_BU58MGm%1m*I|E*zS#v7kBJ(s2vYdcDfA77Rwmv zezSJ_s(p74dBpjrpU6nal;NV%^0Db79zr_^yuv3LM85DboU|GijsM&J2;*mW^E>cA z(ZcxIj4SkCk_+z_B)>K@7gbL4h4HhD5f;V97?_9b_=OpUm*SG(Dljuo-e#bd$Cjil z%P|SucOq_5*ivod16}FTxm4S2dX(p31|OaSC!0eo1CZ3_r?2{95GL9#(?D8Vv@gx{51UtqnOTN=+a>f3AzDLPv4yhCAG$=DCB_6 zZn88H;|>vFvLGvZA%-ZK14hm3GuD;V?YN^DvQ1{9?n;wq*_%^8Hqmr-7O*s|sW zYxalp{@a@Ufh(Q{SFgllD`?^OpiL9VXP7S0NklM!>_al}H0;1Si3N0@A|P|cZ`7H+ zg-C_r5t@B}W&nA4(W*q}eQ?6?5%Hn0WE||qHG{B}%}S?47e-+X>GbLJ569O@bcEUm zjb6;o{+9To#>RWS9p%8)2Zr-G%lStf?j|hm7c6p=Kv~XNvCMKE9ZCGU+WVaS3~T(B zNOQ$FUI<=+9)aJ$kia7VeY^-A&A|a}s#9noJErVjv;xQQ<($tuPfDbdQOJi|CM-~RBpNaZW8`x0<u24%*UU z7oDLmbvdVo@skCL`{p;Cx#0PB^zxmDft4Pk{!=#1_XHPO_$8R^?pFV2L;GYpJBa$X z`Mcp`{A*U`11Dh5cmj~f-2?}4NZuHx4+Tt0iYcbE<>fVD4|~W*v8;MHTr&OmAF(W_ zDv1Yb_;+ZMsac3)f z9lLD2JVtyLPaOV{cSf*`l_KcU9t`Hw?&0dz_$gu__FCLp<0mI;9es@-!iTA~_*ogR zN)`GqtTHJI`?ggZi_UWGTjEvs!Ey9F=|T}207>-08Pa`-!s-5j2yKA4{(Om}+787+ z_pM?r5{W>(d5fWlduNz-)z1?;f4#PM^n1GJuO2G&#p&||##YH;uDBy~7AhthI1*d- zX+lGObKhHrPFy;rQ*`@t>WKd*@1>OrnhK+k_I3VA-=8)GnrVUhD0A^E2?Y)rL3{(g9D@@d1NMcSRPFK*ch5|8c) zCLwd4g0Vwp9XS#C1IiS6Wqg>D3LQ}ICZkwK>2#Oyh+(Qnb;a9BXaU?s7 zZ3qxd!)`u@ARbpM&O3Dsb*0>(+HqIueCgi$VCGHk4b$Re%8awmGr&FrO+;1xCH;^f z_dE}|C^o9}y8DQ{D@XTgkKIQHwVF76|BpYSAU~?R$ze7YQp~T0wqnjf`>1*lvs1~` zj!_~k=JSVA0C5NeoZvt_eN(PeoI9_ukU_Yve@bM78v4)y4aCB;yb<59qi(~F-S&Ph zZ3VB}c@C+E?m;f18P#9LERShluB5xU1Vw3aDf{vZTs*9u7rm>x%K~cVjvkC?xHgie zRV)xt84MAx5^H~ub=hi-pF98oSy;k6 z&*>%~bF!~WJ2S>4KxQ@~B&P;7&)tN(Ez%vw|a(1$w{vuK0zNTM!G0G zyJA(j)6R%DLkCk(5iQdF->i2AKOKFtVO7<7SZqS$-ZzWYiNNK!+?yJZCG|G6Rb0h* z9nk)7ekekIR(6i1qq-?+%>g*>#0SYmSC&>~B~!=`bXbf<-a*ZUo|g@qyrZf@_TWBY z@(>BZc^VLgvK>-qsaeQsBSi>wi&9HnG^buS!=4b_?9OLWo;zq|Hn>UW6Fyp}END$-E$3T_{IzM)-+vmivz;XU z_VPBqkNE|i%r+$XQga~vfg2{7V3JU*JCF!NFEtNI5bML@)E~1@-RV^XdOBcqkeO|f z%@|}*0`B@r!$5ng_vx}1*%C1E%S}IaH~4H_K6i zAJn~Z8$F(qxFzht?wT~OWA)xkf3{o`3<(K2Mrw3%AggyxLxHrLqz^nCG*$FyqXF+b zWDMG?AwqeI0F5UJ7UGnPdLP0Fxrusm+aSA^E@s4_yDl9-w;s?_yr`Iy!8W>`w0>|F zrz=tWSdUq#>s5U4CjIm&+Q{u}HyTbQG=zRDU~IxZzHfUS{?DR>_jkQRxz?BBEMq^Z z?!JT&F5*%71YpUjwVwfimc6aU?uSs3kr2gDXe!SFn`^j@3vL*XdN3$uILaF0Yn}~{+%&%9_ z-%~?%eZ-0nS3X*|XV;u);OI2@!au{~PA$fJhAi`XuZ`|XN%%*VLzAbMpH-PrcD-4k1W6TW9o}h^LZifxF>A9Snb4|TqPul zK`+ZGL0}Iu;!!!w?SLo)1NbqVz+OhUvffLKHsii~ep#Oh-FQasPRP(QrNQ?A{?oH& zvOXAm(drY+-}=yF1K-1$+q;R|cpCtZ z0|4ab9qz^xSyagJm?ku9YYlxy{W8Bo?Ge`IH-ktrU6QfY1$L3~O}c2DIS_v}!21b5 zQ%MHq;>1-u*EUgLX>bXDXcN*|#5Ua7?)W)0B~0p#*&X1Vd&LC(rIy_B3J&Y98Y+)m zm2)B(>asm__&%bxIpz|wuA$_41om*>i3RkVPIaeiqp8|-2-FOJ!r2{TBT5W9jCm3A zi#bK_+ITg@3E@cJ1n#6)F&yb=VO%c*{6$v~ZHtyEXEM7a!CK9opl;@7nXO^$zof| z(AkEA3-xc9X^jr*=4T#moO~%*(B>H+0(wwNmmig2u>5YLwfeEO|G(lko2|t^1)p?h z{>~qMc(dlev`;JZh4zOhi6*umwm$IUE6S6{L6jmSoEc=0$`FQ!j34NzcZ_nVw9DJj zCVd^lTy=}=^f3w@*veJkUGnpj!p``IOSkG6;$78eZU~=Zr>0{jKFB)RE!EZZT~0Pi zs<@VlZcLU;StCYitLLP$v#LFvgR*O9vskp(OLK8)TGzdwy^D|7t8KA3Je!F zjHWui6&475U!g#FVSN$av4WJGSqq`FQ4j-o2Kg1Mm`qTIptT)lFk3Ak$`T7NtnA+` z+Qq70Y`g%6JGZl9MMW2bwPjH##^>jaH>EwT1AVN+I|5#{33kmzvGbWV$4uR1%SEM zan@s=V*W|sGkj7bC6TdMcS-IU{Z|9Ns43mMxDt;kg~i2MZ$)sEo41pO$aXir2gKHn zbk&LmbmS7ZK?8SHvPF{dy%WtYN4|4^9u^cFDoNhDu;KL%;Z^%F8ZeFQ%?^5bJ+|eH zYNx7f9`YU2WSC!%kpdaD*hwM=2f+XKUkrHHM)S4&^Px{>Kw47(E_>qDTKp`da&nCy zHUKgaX4XYp5%LZhd81f&1h^#+0p$DK1{223QycZ7it6OQB^^ftBbR=KRBRr6>9pJt zbF*!0FKf;-D`|(sF6|rzeDwCDl}}AWX2MiL?q8Sh`cGTsZrU%L=Mde9RUayccaK~Y zdl9q^M1>HT$wAthLt(#|SLq2tQ&+%np-VINxd4NVFCv34>5NvkdLXF;**%je7p`&% zg#_NwbMLx1x*K#=L;Qi!qb44O3GkHVb!%W0mtFa%hfK_`3O>5!RC(u* zxE~BvIRMJ5Dj`Z7!%^M$Yh9``%jeJ4+_PACQq?(o!*;FlQwbn+EUv+STaG1$kItml1xRjwA>_xEdA0l{|7&A>G1AVC=k(QR zs}`_NaA4l#Ys}lU3Xjiy$QbO6Zhh9*4yhJ}YibTVNIUQCnZ4HdS6iTWoOkS&J)`tc z)I^w>b)?}&M-;EmO2l#FW9w9X5l2a;c91tv4{-H0U|i9yYQ5EF=O-(MNlr7lPU4q_ zwqzo+nP;PSRj8sG_p7FSWTu@dA=ES`i*{xfs=ROKDk^i2N8aeI^Edo)wf5AWxj=6> zx7RnhieOB~D-mw{W0`c`5iy{nAFSlR+)~&PjEv_UglY5E0Td2G2E(HLc_Mm87QU_V z@}6*$8$*6g^ocyZOZ3t3xH)EP1Vs?oeXOc6-mwIUl?_Ydsmeq$a02cUcJ1 zE*yM4aOTm{kqRsV0u4WWZzf&Dei-(j)4-W>O;p#9pPpkxYo97L}jmf0K*X_TI1> zd-3}(ttJxBA8?bfUN3!4TQKqa@LTnq%fi|mVU<(s|8SG&|HTg>5bi^$o;EF&l@XyN zldF+63O6pBZxmsp3lWeM&`yw&06G39iO~D4Lv|aO8eWtg8&{{cp10_0`}xKEk9cJc zTUN9Cf`0$z&X_s8@Fd{{ocFn(XzxshggOv17leZA+A}hP!|#enQ?T?Q6c~?j%=kt>#XQ}SzdhG_8Rp2g zHGd+kaI2B;_KS_ltsVj8cB`x2M4W#1XS`ZgG|JI`%Y)B7Uk50NF7GH@XyMrjRQp(P z7upfbI}m$F4h2$Fw1qXAR(}?a%LT{g|EvPMAvhMbR95Toylb(hIMC zNuxg7Wy2VRSBN))6V6nO$=^D&b5#0v_9?lJ-*VAEZ{0D|bZ77vE7o%=i=I@tTugc# zn`^W8>+|pT)_k*I*#%JPXmHG{7un34(dct!%C>h#J-Pv2=5=D^wO~>8K0GD!;VF+N zimGauf}cCHPMW1HJV)M=RC|H6+IVQdCA7+Km#SRUvNaptx*Muc8Q)Q*0&&IQ;*c$5 zx|>ao&lO|f%|&I#Zcbp!IM-!R_LI7f4qE((pAy@*p|_jIB|slP^cq#Ke=;6B`|0%H z^N%|Olfu1i|EJo&`Prp*js42fb8F3snbcetQDV#+$(Tke>6nlo)&JJ~fIx4SuQE!1 zmUX~}6A3Iaml`S_B19Pt;-veqUAh|&xJUnV_i-zb>?lhdefBCVP@u2zF7Wh&@)uzV zA85UC3rEzq2=DVGq=t}@90(!H82TV)2B0lK3F3(w$q&f}JVhkH``N4{>349Od(wC8 zyD;y1EAP2RC#5~Qv;Mu(r0#e9rw_)jIBjsbh<8IdAB{hT7l$vg>6#qiOjK1N#HLUe zeIM4|zacp;?)M~t63O4w(mLhi(-`9KPd&$Z+CShOrDU@V8+EV?OU%n5 zYD8_dq!vS5K(425u|$nE z?3FWPzp&t$u_yO^VZ&2J>;w0RREC;4yeIsbLD`=5{OM30A};+^O-YLyeJ!4Xc| zcO(11-$yuU+0U1^F}8_+d-f-4!u@!1lhQ4p;O!UD=3R7S>D}cO$(*MM52_OKauvt!d=cE%Z2MQp2zaeOk(VOh z%MD)c`{5cKAr<~4(tTbO@mNkAYL!-kUJ`cepZ0!u91&Klf1wG~mHU$&J7KA^__6k> zmhb=ka{fQpTVNLw>OyWoRqlmJCE zbHzUZL!qle!A5Kc?r|EhKm5byp1a)h$Ik4|?w;@O_xsH6aqjHhn*4Hg*E+2EDKX^A zlGnSryT_YM4N1Q0P%bZcx!SL#a$qv)*Vu;*jhk9xN(*1IwF~9j_{Yi2OhEr??Y@>f zhGZfq<-X5(=c{2K*N%K|rL7(&hY$DUx)r_QVE3S@Gdm=RpZUCFrmV6wTRYL+px%G% zLfP8F97%*YVMSD_yuklZ`;zI7DI>e#ABNAw>cor-{4)}a__2Zfly7!*ROgZVk2v$r zI%-TF`V;vu|Fq%FPhNQBFM<7!injOGeR}!*$~@xus-fEhiS7G}8~?Jn9tz@mWxdOi z3fpo=;xx92>s^};vW{L?AQiE||ttB!T7ajh~cNzXd6!-t!H->YFp(OjUsxVNY% z+`P`yShnYraM_gWri?=Y>-XIIdxCgIPS>Naq4)vA#vYO^FIQW?GBPFEnLF;HF&+&h zlJl(9t;ey>KbNn2pZrYfu$)u>&YH+UjiHv(c` zT&oZY(_49DxJ&oV7shNuk}oxF-3HEF<9V8V{=qVJPqR`tD!cNw(E-y+i^|uwrv+!f zDSFDi*tb3D0mM%k?rkQ$n=T95< z{}_63m(XIFB^C2XCiossX0~5OHp*BBWSb zarwIaBVO8=Zbos!>D3UdrRt?P#E&&l5)OdN9XLSpYXHcE_wVJyyMl16RptcNcCVh8 zslr;Io{K~L0N^pp33tGf1fp4!=kEW^C!$nEr^TcjSVL;RS*{s&sg z5mEnN5J>SOoULDYubZ)7<1J9n)ggXNQ2s&up#BHoU%zCES{5K08AbdUQ~3t~D*w2k z`h`C~Bk;6=UG#YX@niV7|DpI{PX6hp&voq1&;y7cG`~O2Kd66ZK>g}h)Uk1-(E|(k z8KCly4Lbh;*!Ryp?C3=!Jx}Bx#1E=pJpAXczM5iPjFeUvF6zxu{xLxP^X&OET+vkj z%p;O+|Xy(_i z1^n>wuU}p{lZmuCaFK7mfFF4MeK!AyVg4b2p#YStT=900WzMh#-U{_w9pXnw)h`b0 ze;_|%3)nvs0Px`X&r-bY&q&2vpT4X6@BU{g#Sa6{pY=UHxZAg&et~)O7t0Us;iFw2 z%Ma}#V6*_1A4Z3dcJVAfw1 1) { this.logService.warn(`Multiple speech providers registered. Picking first one: ${provider.metadata.displayName}`); } - const language = speechLanguageConfigToLanguage(this.configurationService.getValue(SPEECH_LANGUAGE_CONFIG)); const session = this._activeSpeechToTextSession = provider.createSpeechToTextSession(token, typeof language === 'string' ? { language } : undefined); @@ -127,6 +128,7 @@ export class SpeechService extends Disposable implements ISpeechService { if (session === this._activeSpeechToTextSession) { this.speechToTextInProgress.set(true); this._onDidStartSpeechToTextSession.fire(); + this.accessibilitySignalService.playSignal(AccessibilitySignal.voiceRecordingStarted); } break; case SpeechToTextStatus.Recognizing: From 275d365b86c9fecb5c813bf691b3b94cd488cae2 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 28 Feb 2024 21:57:49 +0100 Subject: [PATCH 1675/1897] debouncedObservable2 (#206470) --- src/vs/base/common/observableInternal/base.ts | 4 +- .../base/common/observableInternal/utils.ts | 45 +++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/vs/base/common/observableInternal/base.ts b/src/vs/base/common/observableInternal/base.ts index 74b03df1438..4e738e63344 100644 --- a/src/vs/base/common/observableInternal/base.ts +++ b/src/vs/base/common/observableInternal/base.ts @@ -389,12 +389,12 @@ export class ObservableValue constructor( private readonly _owner: Owner, private readonly _debugName: string | undefined, - initialValue: T + initialValue: T, ) { super(); this._value = initialValue; } - public get(): T { + public override get(): T { return this._value; } diff --git a/src/vs/base/common/observableInternal/utils.ts b/src/vs/base/common/observableInternal/utils.ts index 5831de89add..0ac31f7f881 100644 --- a/src/vs/base/common/observableInternal/utils.ts +++ b/src/vs/base/common/observableInternal/utils.ts @@ -253,6 +253,9 @@ class ObservableSignal extends BaseObservable implements } } +/** + * @deprecated Use `debouncedObservable2` instead. + */ export function debouncedObservable(observable: IObservable, debounceMs: number, disposableStore: DisposableStore): IObservable { const debouncedObservable = observableValue('debounced', undefined); @@ -276,6 +279,48 @@ export function debouncedObservable(observable: IObservable, debounceMs: n return debouncedObservable; } +/** + * Creates an observable that debounces the input observable. + */ +export function debouncedObservable2(observable: IObservable, debounceMs: number): IObservable { + let hasValue = false; + let lastValue: T | undefined; + + let timeout: any = undefined; + + return observableFromEvent(cb => { + const d = autorun(reader => { + const value = observable.read(reader); + + if (!hasValue) { + hasValue = true; + lastValue = value; + } else { + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(() => { + lastValue = value; + cb(); + }, debounceMs); + } + }); + return { + dispose() { + d.dispose(); + hasValue = false; + lastValue = undefined; + }, + }; + }, () => { + if (hasValue) { + return lastValue!; + } else { + return observable.get(); + } + }); +} + export function wasEventTriggeredRecently(event: Event, timeoutMs: number, disposableStore: DisposableStore): IObservable { const observable = observableValue('triggeredRecently', false); From 435f3ce9d897e40278f6ec287cb7bd68acd37018 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 28 Feb 2024 21:58:18 +0100 Subject: [PATCH 1676/1897] Fixes #206166 (#206472) --- .../browser/widget/multiDiffEditor/diffEditorItemTemplate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts index 84cffd7e94e..83a4a22aee7 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts @@ -285,6 +285,6 @@ function isFocused(editor: ICodeEditor): IObservable { store.add(editor.onDidBlurEditorWidget(() => h(false))); return store; }, - () => editor.hasWidgetFocus() + () => editor.hasTextFocus() ); } From 4f08e9ca03574a6731abfb78189e229e4ca7fd54 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 28 Feb 2024 22:01:06 +0100 Subject: [PATCH 1677/1897] Fixes #206355 (#206473) --- .../browser/widget/multiDiffEditor/diffEditorItemTemplate.ts | 2 +- src/vs/editor/browser/widget/multiDiffEditor/style.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts index 83a4a22aee7..c6f3ca9de9e 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts @@ -236,7 +236,7 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< }); } - private readonly _headerHeight = /*this._elements.header.clientHeight*/ 48; + private readonly _headerHeight = /*this._elements.header.clientHeight*/ 40; private _lastScrollTop = -1; private _isSettingScrollTop = false; diff --git a/src/vs/editor/browser/widget/multiDiffEditor/style.css b/src/vs/editor/browser/widget/multiDiffEditor/style.css index 44f0703d580..c540a46b3f1 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/style.css +++ b/src/vs/editor/browser/widget/multiDiffEditor/style.css @@ -37,7 +37,7 @@ .header-content { margin: 8px 8px 0px 8px; - padding: 8px 5px; + padding: 4px 5px; border-top: 1px solid var(--vscode-multiDiffEditor-border); border-right: 1px solid var(--vscode-multiDiffEditor-border); From 0685fc347032f4c5f244ec3601e9a8f9ec95f96c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Cie=C5=9Blak?= Date: Wed, 28 Feb 2024 22:01:41 +0100 Subject: [PATCH 1678/1897] Inline edit - make sure we cancel in-progress request on blur (#206430) --- .../editor/contrib/inlineEdit/browser/inlineEditController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts b/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts index 8355ef312cf..646c6f20c22 100644 --- a/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts +++ b/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts @@ -121,7 +121,7 @@ export class InlineEditController extends Disposable { if (this._configurationService.getValue('editor.experimentalInlineEdit.keepOnBlur') || editor.getOption(EditorOption.inlineEdit).keepOnBlur) { return; } - this._currentRequestCts?.dispose(); + this._currentRequestCts?.dispose(true); this._currentRequestCts = undefined; this.clear(false); })); From 3b2b4cb5eb27b0c1656e67da26f9460d1544b4dc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 28 Feb 2024 13:51:12 -0800 Subject: [PATCH 1679/1897] Forward wheel events to main xterm instance Fixes #198541 --- .../stickyScroll/browser/terminalStickyScrollOverlay.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts index 4dc0908e54e..77856d9eda1 100644 --- a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts @@ -376,6 +376,9 @@ export class TerminalStickyScrollOverlay extends Disposable { } })); + // Forward mouse events to the terminal + this._register(addStandardDisposableListener(hoverOverlay, 'wheel', e => this._xterm?.raw.element?.dispatchEvent(new WheelEvent(e.type, e)))); + // Context menu - stop propagation on mousedown because rightClickBehavior listens on // mousedown, not contextmenu this._register(addDisposableListener(hoverOverlay, 'mousedown', e => { From eea2590f508761a247ab80e082843b7bc642bd85 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:00:54 -0800 Subject: [PATCH 1680/1897] Hide sticky scroll when the prompt is trimmed Fixes #199554 --- .../stickyScroll/browser/terminalStickyScrollOverlay.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts index 4dc0908e54e..51e88210aad 100644 --- a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts @@ -245,6 +245,12 @@ export class TerminalStickyScrollOverlay extends Disposable { return; } + // Hide sticky scroll if the prompt has been trimmed from the buffer + if (command.promptStartMarker?.line === -1) { + this._setVisible(false); + return; + } + // Determine sticky scroll line count const buffer = xterm.buffer.active; const promptRowCount = command.getPromptRowCount(); From 2c08528d7ccce38528b26bc65119b77b26ed67da Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 28 Feb 2024 14:21:46 -0800 Subject: [PATCH 1681/1897] #199119 --- .../browser/accessibilityConfiguration.ts | 2 + .../accessibility/browser/accessibleView.ts | 54 ++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 33a1379d3bd..bf0c372877a 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -22,6 +22,8 @@ export const accessibleViewVerbosityEnabled = new RawContextKey('access export const accessibleViewGoToSymbolSupported = new RawContextKey('accessibleViewGoToSymbolSupported', false, true); export const accessibleViewOnLastLine = new RawContextKey('accessibleViewOnLastLine', false, true); export const accessibleViewCurrentProviderId = new RawContextKey('accessibleViewCurrentProviderId', undefined, undefined); +export const accessibleViewInCodeBlock = new RawContextKey('accessibleViewInCodeBlock', undefined, undefined); +export const accessibleViewContainsCodeBlocks = new RawContextKey('accessibleViewContainsCodeBlocks', undefined, undefined); /** * Miscellaneous settings tagged with accessibility and implemented in the accessibility contrib but diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 07fccadd92b..8c59b8fd4cd 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -40,7 +40,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 { AccessibilityVerbositySettingId, AccessibilityWorkbenchSettingId, AccessibleViewProviderId, accessibilityHelpIsShown, accessibleViewCurrentProviderId, accessibleViewGoToSymbolSupported, accessibleViewIsShown, accessibleViewOnLastLine, accessibleViewSupportsNavigation, accessibleViewVerbosityEnabled } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVerbositySettingId, AccessibilityWorkbenchSettingId, AccessibleViewProviderId, accessibilityHelpIsShown, accessibleViewContainsCodeBlocks, accessibleViewCurrentProviderId, accessibleViewGoToSymbolSupported, accessibleViewInCodeBlock, accessibleViewIsShown, accessibleViewOnLastLine, accessibleViewSupportsNavigation, accessibleViewVerbosityEnabled } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; @@ -127,6 +127,12 @@ export interface IAccessibleViewOptions { id?: AccessibleViewProviderId; } +interface ICodeBlock { + startLine: number; + endLine: number; + code: string; +} + export class AccessibleView extends Disposable { private _editorWidget: CodeEditorWidget; @@ -137,6 +143,9 @@ export class AccessibleView extends Disposable { private _accessibleViewVerbosityEnabled: IContextKey; private _accessibleViewGoToSymbolSupported: IContextKey; private _accessibleViewCurrentProviderId: IContextKey; + private _accessibleViewInCodeBlock: IContextKey; + private _accessibleViewContainsCodeBlocks: IContextKey; + private _codeBlocks?: ICodeBlock[]; get editorWidget() { return this._editorWidget; } private _container: HTMLElement; @@ -169,6 +178,8 @@ export class AccessibleView extends Disposable { this._accessibleViewVerbosityEnabled = accessibleViewVerbosityEnabled.bindTo(this._contextKeyService); this._accessibleViewGoToSymbolSupported = accessibleViewGoToSymbolSupported.bindTo(this._contextKeyService); this._accessibleViewCurrentProviderId = accessibleViewCurrentProviderId.bindTo(this._contextKeyService); + this._accessibleViewInCodeBlock = accessibleViewInCodeBlock.bindTo(this._contextKeyService); + this._accessibleViewContainsCodeBlocks = accessibleViewContainsCodeBlocks.bindTo(this._contextKeyService); this._onLastLine = accessibleViewOnLastLine.bindTo(this._contextKeyService); this._container = document.createElement('div'); @@ -229,6 +240,13 @@ export class AccessibleView extends Disposable { this._register(this._editorWidget.onDidChangeCursorPosition(() => { this._onLastLine.set(this._editorWidget.getPosition()?.lineNumber === this._editorWidget.getModel()?.getLineCount()); })); + this._register(this._editorWidget.onDidChangeCursorPosition(() => { + const cursorPosition = this._editorWidget.getPosition()?.lineNumber; + if (this._codeBlocks && cursorPosition !== undefined) { + const inCodeBlock = this._codeBlocks.find(c => c.startLine <= cursorPosition && c.endLine >= cursorPosition) !== undefined; + this._accessibleViewInCodeBlock.set(inCodeBlock); + } + })); } private _resetContextKeys(): void { @@ -328,6 +346,33 @@ export class AccessibleView extends Disposable { this._instantiationService.createInstance(AccessibleViewSymbolQuickPick, this).show(this._currentProvider); } + calculateCodeBlocks(markdown: string): void { + if (!this._currentProvider) { + return; + } + if (this._currentProvider.options.language && this._currentProvider.options.language !== 'markdown') { + // Symbols haven't been provided and we cannot parse this language + return; + } + const lines = markdown.split('\n'); + this._codeBlocks = []; + let inBlock = false; + let startLine = 0; + + lines.forEach((line, i) => { + if (!inBlock && line.startsWith('```')) { + inBlock = true; + startLine = i + 1; + } else if (inBlock && line.startsWith('```')) { + inBlock = false; + const endLine = i; + const code = lines.slice(startLine, endLine).join('\n'); + this._codeBlocks?.push({ startLine, endLine, code }); + } + }); + this._accessibleViewContainsCodeBlocks.set(this._codeBlocks.length > 0); + } + getSymbols(): IAccessibleViewSymbol[] | undefined { if (!this._currentProvider || !this._currentContent) { return; @@ -459,7 +504,11 @@ export class AccessibleView extends Disposable { } const verbose = this._configurationService.getValue(provider.verbositySettingKey); const exitThisDialogHint = verbose && !provider.options.position ? localize('exit', '\n\nExit this dialog (Escape).') : ''; - this._currentContent = message + provider.provideContent() + readMoreLink + disableHelpHint + exitThisDialogHint; + const newContent = message + provider.provideContent() + readMoreLink + disableHelpHint + exitThisDialogHint; + if (newContent && newContent !== this._currentContent && provider.options.type !== AccessibleViewType.Help && !provider.options.language || provider.options.language === 'markdown') { + this.calculateCodeBlocks(newContent); + } + this._currentContent = newContent; this._updateContextKeys(provider, true); const widgetIsFocused = this._editorWidget.hasTextFocus() || this._editorWidget.hasWidgetFocus(); this._getTextModel(URI.from({ path: `accessible-view-${provider.verbositySettingKey}`, scheme: 'accessible-view', fragment: this._currentContent })).then((model) => { @@ -775,6 +824,7 @@ export interface IAccessibleViewSymbol extends IPickerQuickAccessItem { markdownToParse?: string; firstListItem?: string; lineNumber?: number; + endLineNumber?: number; } function shouldHide(event: KeyboardEvent, keybindingService: IKeybindingService, configurationService: IConfigurationService): boolean { From 95f8e9de34e5a3b95c75257edd141d7dcf62d7dd Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 28 Feb 2024 14:24:37 -0800 Subject: [PATCH 1682/1897] use voice recording started cue --- .../browser/media/voiceRecordingStarted.mp3 | Bin 42112 -> 30592 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/platform/accessibilitySignal/browser/media/voiceRecordingStarted.mp3 b/src/vs/platform/accessibilitySignal/browser/media/voiceRecordingStarted.mp3 index cc959d00a46cc72bb6fb6a6763e36d129c5e317e..3bfbced34a36c38dc0e173ea5def13e0aa219867 100644 GIT binary patch literal 30592 zcmeHucUV(Tx9+6U0)!rVK)QfCbPzNYsX+meCQ3k>G?Aj9C<(nP-B6S&f}ny5VnITY zB1KS8v7!_a8-gfB$PN1Y?(>}UoOAy4e#guF!Jb``^{#i`y=JYMU}2&T2h1jU#D4c~ z0Qdmukk7#bc$<(zcqcpoQq@q^G2UejfG_Y0@;PW99<+0poi)HiznZDef9j|cG=5)E zCm8+i5TJja`Bz6>(@6K%Ep-io)_-~iGKIh|Ycc<_w%gjq=wH?#==bAlgnv4QMmv6A z`HcoLR#R8|zZZUWG&Obpd6xOef4sH+ng8L+Iwu=#gMkeOHW=7oV1t1T1~wSjU|@rR z4F)zC*kE9Tfxm?T#>#c#FJH7NzYgfxUkC8&_d%5Y`?&FUgMt5&fpz`R*YEr<)sF;! z?3f7$g)Ya#i7RqG?ewMYtMiUjHYZ{o)l5*T4c}8@UF-~S)fdL&v;3E>x!Db8rBQS; zA8SI|PUWuJqfaAh%kIV1;+=@)saP(ySTeXVtV~uW4P-iq9-SwzGqchD2L}F2{h%gx zzCzG^Kr>q*JrpzcKYaZE`7N!Q@d00uaLIU6sP19{0f`-QD$4NJ3(bjAyPHTb5&{=tuxJ{&qs~_0(Oss)bV( z9_0f%^(aZ>=HWMWSTI_;aJoJ@3x*LdaKWF7^xxO`J)G|cgTa{J{-SHogAr!0ee;*Q zPh%b&OT_LNj<@*6;xwCL-)Y@Etp!p)KD%;Sw*)Ryi?*BzQ!atUqF`J|i=4~*w(S1G zW$HOlw)w@L`%H)-r1(v{V$vhKJN5`Cez3cw(03{1WMZB9`<1N+HXWBS4|v~|9yC`B z=~xzu%gIFycfap=ASxonB}Y{ycaGP|Q@vfaHI5MbXhOj)XT*j>3q>SDFFcOVbz>|) zdFglUXbWR-RI_;y(hM)GbR}4lZ|0vcX7$KIbDqK7#K9zRxT-}lCtHx5&ECex7xPA0 z-$~=ZKncKv;^W0njvC?L?X1SK3(2~Avt9^Gmv_oAqRO68QquwEUJ$M?~HtCwIyHN zz|i3FgU8v;$2YH49xa^-G?)2!L*MhT$!KQ5W3>q=Rwy0Qm#u&`v^Y&IkaH?hIj72F zq)|c2vzlz5t{vyQyj$Shcp5vNN6FpX6t@k2jeu`GeTxDVKT?GHxCXe|n|Vu2>&}aeG$7bZwqq5WSJo_o^U7*S=zzcWN^%tM609f`f@(Vd~Y%hKsapqU_a05l?QbNcVfv+BgW9^)8T^|M^gU>ZhA&mMCDzvh=Q2oYTI#;(uonTy9 zi)Jv^PBLCIm300_681#N&aVq2AI&Wz_i^c)_jxsoc@%Yz81Mb?GCi;~x78W7P;GwD zj|}N&Jc|D+MXtgQK1FOw;Q6k}Z=ut_(vY`C-xiQlJ7KN+%GQB5u5zHiLs4TTJ^F4- zQN?A?i;mZlToQMC$!eBilE;A(W$1{0{#B@obH^Z&%|jr=ep5i&s}?2_CZv==PHEi* z-50H2p;?oe;Nj|+8Ph)X1S}G7yz3z5t=N{dn~SDRM@F2o1!G`YZ+2Hc(?bp&b539ZvK;hY6KfkykK+ z*;8zR2+iI_?vn=HO^0}@T>Wn^gjZ`tkpXA8=UQljJznEiB zJI8gvulHU>G=uT;(^-6V{L@U4bD8h4BRF|PX`%~+TS4$ptJtU%G90|2AA2aQpCzn9 z8Y@Ng|E6)9flQhuL8DmZfHTi7uQy3CdhsEQgj zG<8WsaYz?c4v0K%a+ngm^F8c$amo9vbM|^2U8cuW)|urKE7yH&Rv_m zeL#MTu(+ypd1F=Cv*(q5(~4(95tp3eJaV`B8PnZ3zYRTYy?_cfuzS=sI_{U8?fQiY zL0$e$He0NsT347cE69hmu-9xN3e{4uk5IvOkRTXwgd$;KEK;(Oczn8Pj#$y*^IK~t zr5t~ju3Q86#}1;uSU1)6%Zh9I`G@kp4&5y$|6#X*S5U=KDBQ=kue-j%2rD?+Lyindha&3>$lcaJ!$ zy$?}6VNQwE_xP=^a=%3DfKQRZ8k3(BcS??z&`nNqQ)DaJ>Krg6QTjNs(F@wa)4B^{@0rTMq6iMcVY|7d&ZO@Yc_ z%j%itegt)sgHT}-ny@G57U2p!46-7=gM>i}gri48IuxV=+@3B3`1z$`-{)AtqwJnW z`YelGEuDE)nI8W9_*B*Tkj5>C@<)n>?`nN|*RA$NV(<$>&%5(VBxB{tkI=4ryIsnZ zMGqa8&N;5@_v7xzZen><@ziy%*cK)blHss0Q~=5}cy`zk0!{8gw4_QkV%{3zD$VGHY*&)ayHIU-p}%j znNSvs+o$-NbkVMFO}w`{?e0Y4bG?uI@2%8~*txEsT!17JZp1Df8%yy%G8=8@%5!P| z4EI}=HcjD|uvLrTDV7pZQv{7)4ve9u5H@frAS4tl0wO7<$R3Ix;H>P9x;9LPt_`Z_dH^(ezmC8KxUdEGDU^L9^rUaLk`hEu-cS$>DUhjE|M7j+_K-s1 zh3FY|?eH_h)M{?QA7L2o(4x2g;T##y+K!{r;GzKf7NI30^>v#ub`uxt}4O7<~u<&MN^DnTK+f985XJ1pr+aNSGfLK_sVi zziKUwtx4TovGTwq`^wh$Jc&ykW4HUp4H#J=p_5v62Xm*J7G=-nCl*hs#tNFhD-*|! zDijM*E`}oPX{R~Re&_R33Q8X^F*I7`%z3leSekC?Wku@|Srd40`_Oqj4?8&*x1$jm zyxy<~6l(`Ll%kqWrJ!mKI#wOgDgXX}1)+lTGvsVIc#OW2PEfe8Kk@U0nUg<$zPP>Z zNU!6`Lj|hi&m9vP6|cWP_^BbWX0i{puL8hzqm%0^>zto_pO@^8W-wMCo-jK`!r>?T zUN@%n$=Otr*zhO&wL&iSiH$jsQ0w|hqagVR`msw{V^LG`CDFTFv4>rkxmz!_apb$f zRyBjM+-ZP_Aps&U22%$*DC`h|!X*n>>1a3`j%D6(7DaC>^L7*-y6a(iy7aV30zQbq zEs<^2iwf23bF=33o~rRuapCT}c%Z!Ujb_+=!xHhCml-A}*kdB!+%9MGv))RFUq6m& z^~QedKXHP;BNc;ewLy7~7^B46A0mI!Z-XW}5v0+#z@}+@Z+lsQ*!}~_5wzPkcvEv= z0=VW)if2@4$BK{oUEg_@MjW-8Pn&#Dnz!32yP$#pgHxAcnJ=|`inI1Y-DCF(0o$g# z0aBE}0(sjh=U_Nhuj9qu(Cy4@VMRv&hZ9t0NYYeJ%uw?;q(csHxaM;p22~o`KzPbl z2uZ|2Jd`Xh)ImEMoQ$L~%Lf|Am?{khVA&RIA32izh*m=!gBHqn#qDtBJojwfB&;fT z%yHPK$>jM`HHTz$UTRk@wJ3!1_7ffZ{UqIEPSbMf6`a+PM-KC0n=TO@1lIMk-o6aK zyjfh)R?g--BB4*~R%ExKzlA}MmNhHd>k+K<4uHcToGBToHxzpql@;bm;cA(JHF3lP zJdZ^0b~Ej&y?ho*Ki-(1eeCf~O|xFLxU_})P{D1M6X&`ZYoD&Hww(EK=IF{wVuOGN zitCg!#(px}eNpK_&gUQzN}!6(*k*r6#%lC0BqJYY3~VWOcxsx&2Uzy3a4F?G?`RdT zeQA6E+q!?|mEr_|);N+QiW6``p*wMXk?l$W-Ek()J)t-o(R1u^tg$%2vsG%SV zC|dwPUJgXjcY>l4Sg?t-gzeohj-G_e5@Jo;6De;>`M8$&ly%90)0nrrWwV>TFP)CR zGvnGau*o%a*RU-o)f-Jd!`1OYkMq2Q{U1Q*!{PDqMJ8o3MR$;uQ z#D-Yd`B7%PM}51_=*=^DU2Xp(2N=@_8N*una}An&{=KYE_!U(LbYV97VTGWNGnqfn zsYn}%krN&g-FEFYW4*RwOouP^&)YwSE+f9EukIxfGUJ4yA|s;4Rql7t3qy8D8$?2a zL=lJ=lt3cP->ASTY)A^4#0<-NNH7bdMh^-P+V_?`&3wiAIjGaqx9Op5vK^~QhWvm~ z)3VY*-a|Q~)fplJg<1!Dxl0Jo5{dgs`5NdDzv?G;Z>SbWD&-E*q8U%tk*r<5!O*U4 zd&ciN$bCwzU4c)gEgintyXeQ?u@6?)iif3PSPxSI;H5Em7?pwqNojCu2_g+mloG^Y z1y~Lr3jrEwoaG+^Q)6H}hkTErKRc*>s-Z=$9g$pdJ$Ah0L`(G7(JeZM$|TEbxJst# zV_Gq2#ITF=Jys{uezK%#-}8Tz^x@mF<@BR3En<~~BhO6SVLPYCy{@0r6r`y`YwTQh zaLUY6{E7J>?ALwl-1n{9HrJFIE?GBKC#1ovOV`eoCh()D>vp(<1RVRACK~Q%4hVR} z8#)gSf!}%l>D~lvhQL+8spAY@!RNfP^t~*kRl_u;%lX(vSf7UUM`q3S7L^e8*^BGn zUBbnidc5&V91Lcw8s=sl8FrpVHTpQM2t>84GsbKe23i)1=QMFU-k|bdC+6ml;SbOb z)8FAL0!g?i3YsQ2CQ0J~+&FIhE}A%i(cna+VlWvog-C**U*h;|n&_lVe;4<2=a{OT zuZ%I0MO{8sswy`_DTh`4dRXpf536sxo@u-c*R9+&k61Q}eJpO65aTt*r!kMT&(Dxj zyp^ZP;_ut?3ku`)jbg_3_aJQxk3-tWiv<||A*D2AFdkFbRYkJRkRfpqMbc0ZKM6+U zAn}%g5`8-VraY2MsrOhH)j~DppvkqjHrBKG&6()kUyVq9vR#zlnn*)GCBghg5Xohme z_&hRMptF3(HzeGU6`UceB6dEimW8*0lvWclpJYQJB`zBpMt$IVm(Gxt*!9qw$>N1C zU#>(4=`R*$UR~r1yVgvQy#HW``TZ$ByKIwlBoB8Bsla-FiVr1UWUoKFYq>tLik*j| z%uG_+z#EDJqQ}X|6xe{t2M{-aLtE)wa)7|p4>^T`6mT@*_~IYD7tK0;EZtRL$6i+M zx9P1#)d$jNG$YpLczHtho0}1+-Jt`y@|l6f#>wXi#>4^V&V5a%9`-G04eGwsRvefq zO446_wDae?=8K;FsSb)99Ex5!_TI*zhq_rkhx@8G-q$cU(_D0LtF<%t>X&21SD3F> z%at3(XEi1cdy3s=6$HQ>y8f8fI6zabvjcmH~t`$XZT(HVzD7qOblmER%Q@}D9D_s zO;O0;g!5AnE1d(91+Itljf#~O7mq*8D3`&v)V{U4>Gq_|=|w|`+Vhj&CtrQ8@_Mn_ z#hB0Pjs9W$`byEKir!&bgiA&Od$#c%1SG9Uz%8U;Md^MsoUM7~L01?p>v-JU&VHB| zpTADCiM7oazPX>~gV;seRo>P(X7nQAQOE-ak+X5wF^CCD0>`o*e@X!|(!m6=SQwni-W?px7sPA07t4J=u+4h2 z2kfVHQwbMMlE@JU!2WrFfyKg$;5jAe5(6YzqcsLgIqt5#xw4J6D_dizZF= z@`uZolD;38m#CjJm~*$c^uN5_Ph?_`6@*k!m9ybWL%X;{Mm(f$W4#*r^dR=Mtv_S! zHIqih!1BJ6`+ItEq_GAh4?!(XQI4tjLk9J4zdf5bBsTQVnXJNDIjwJB@JMd zj{|sg66kP{uA1mj40m7nt|e{b)O#w`%5zw))zIbI;SaIXE%MS&KG&XGTYC^$({I&s zYyX;F<@M%HZAc9ItjudaiTsX1jHc}-4TX(c-rD3nGe9l*pXbqZ($lJ zR>LkGVVefzts%+ej7g3PqKd6e&w;U{0$)QT<&Mi2T=n#rTe0`vTkZM$fW7;j4wkGP zgRBuzQ~k2x=#N3?ox&Y!pD><$T8Hxd($bf?CnasNe7JiNS-9>=KHt`p=x=>Dd}>== zVHo-$W{QXb0d*`9IFJpP#YH@Xq@y8rVrHcAEsMCOp3^0tQ^o}3^V%2t69+r4Dn1(C@in}crPlGNk-{MmR%*5>ZI*#RL z?vux@z0nzNXos52b^W9P=KR%xm`@E(y!bTZq)HHXR68&CXR8?5-}*^#9Slcvn+r}E z)o6CIC~$Zq0GxWK4- zUYcY{VA_JFJ0~Bx7PHv~>RXw*5)1cdMaj^efec*-A;S&;667X{!7V5i$W2=Nm7+2Z znxQ)_i3w-Ews
      6qIKg@@+pkFGsG#hBjg_T>K3@+pI2M)cX6^LI`SZ|k|9=gB!C zkq-?UirnwMIevr`0`JPM-CjN8xK0z3t(r$are2jmFXAFKko$Qw{SXZ3Jbh8^_9j~t z$Pt4n5MgM56|87gkPnEWD*+t63EV48sIy0GL+c#P21*$LSO+`!un}5XO&h~%glozZ z&Ek(s&5E1T74Lr2(EHdq^H7G0Cpp^}K_0jCq^Pj2Ib6M8GH&)!rn8_odOZzmVXQ9i zkN$b%J#+=Rs&-^jTs;T*74mPb(>S{K01D|sW}#dpvjB5@geL}lGfX5y0i_P>W7)$d zJUbs3CoH358M6@IoORzeqlR;_`?@5bSf+=}-m3lE?34uO9t}s4=X`7nw&XmUh~=#~X8zx$B=nwIqlOZ{0u6$8NSCN52p?vagx~06 zAV&$bsnGz14aZBAZ7Q8mN{o;wSKb~^;fRpUAf6P3oyqeR#%Ah_mt6NsHB**Ouo-`& z>J-&8YG?a3EH7Bfbhyn)XCfr8?lC6YUgDw4>E9q2Bg3x;Bu4S>VM*7$=sHdZgA+B_ zMPwcwaDCCP4D+qU*x`3z;K5pbm^#GZbU7J|Ah8Cs?>CZ=2wsbKisG~pCEhGEv=lNi z-}c!gj`;Kgm#xk1Q|-$=e5Ca8ikEXM4wFH~O)um{GCZj7`C!`c1{MAMeTJ_EZZ>#i z%k00#Trgb+vex)+`Oeh+&5-Lo6N0{fAED#k3}irGP?PvaKk^Q3vCR4_QR%78$=yNN z)xBuu{MEX~)X$HGCby5!L%J+8Cm=D`tK=aTiXjVPk*R$4crUjRhrUuI>2M+VcDAQwgMak{< z=g;wAXVcvoW;zVU%sSTZUxtsj*!mBWg;0H-yp0har7UUWj@V;9d+`+VJL-M14!BOn zQas35Fhe$^;ITk&S_cKlc?WWkjV4^JdU%wOg7Wd~xc=JCZQVYDv9R3Ie*zEnQVwf!*|};F z;2BUw>yX;%%68~A(BXya*TQE3mmDh{D02=m>n=G6q8!gj;KJ(o5x<=>bmg3an@Bv= z*Gm9fh(20|~EVXHT`uZ+UL~7aI%ygRiMPr?0&QExZ)K8H`?Kr#4+%^Fg z@YIu~SCX-y%~)+&ZN9bbc==Xx1@CM zHqVh;(&u|F^ySQCpUzL-tS~L>HDct#dgq*vwx34?(&SdGjMjj}dIV&)?b?Z?S=*9KZKF_eJ9r&3^_cLz(rc=Yed^@dgL z4@bS@UJW>vm{QvP#|T=n(F{$o#8Twv{Ysn`-hfI~mO$X>f`~r)Cx8zIjr=g2p45+* z7ca{}Bu#EB<0K=xTJ-qXY?n-6@F+h5ntkufsFFYM3E#(uR=10E;K9}n3;?ro>lY2rQVS4|MmH3};s z`y2<9N-2Ug=!TFqMG4}hXG5|SDGnfZ(Hr0qXe1dEFib@knIVRKNYrDT(2#i^8XwP@ zBK3{A3Z$B3oqSSY7=izTwA>)4fPvmv(;tFFUBuWxtX`>;HoVYEjvMdAZ8d?WZ z{`h6ohf9suSSL8kwWs5zVHHtoR_Ck1k#o=d>-f z>rV7ql0snO@;MD`SQ}1%L5sVM491Oh7(ar}INFYLys(Ics$Y)p&e=ZBnt{$1`zQYB z?`c`D-{aYtDY;q@kCekQvwm+KKl8eERTJuDA7?Q^^1*@;2tbC_5xGk#cjEy19tI4e z`q*s5MBQ(R@R&=MmsFz0RCT4PZ8r8M-CqSUs;+twKFJ=ZRJD6iNW=dV63(1p3(~}xBZc*V63M(A zPL!VCsk3$f&Ck9Yte41fh~0q9k_~qSMllw_fzh|#etg}bNRweFFGJU&XdQsoOjl?VpxJ2=Yk$;k|=kM#~_C& zyl@L|3=Ak65|`AqE9hJxfL;#kdCJMZ7p@?5SO^bOM6#NfQ8TL0FM?weWC^x!{Ppeb zJ(y6mE8Ch{`sV7VkoPqgtsA_{(BXODlCUavY?Dzit{H{EM0Pv@q5l z^dDR9J+1(~N9@t?Bx!1$pk5)c8)^~sCL9PD;<|ovDabtz&9Th*lkE4Tj+y`PWV+08 zzYT0tR(M5a#-IC}ktIluNJWDfm zan$^w-{vFV+6%RR1}-sVvUf&zGuHG+Th>l~U-Pi8m+yW9pN6LxYQUc(R(iurjH#o; z67zm4zvItBc#HAb?a$Aue6Ebe=coWam5?qsH_5-;le`8}UA3@$%<%bw6O!64t-|0PgVmsha z0y)RoLbVk_gvZEt5HB?hTF!sY)RQ?RPB(@FGMR&VgscGs-OxwKtN=;Y2ECZndPqm0 z!kr#C`_!60tFybX-x}$3xluAP8xs-m#`m>KNGn^vsG7n3W@8p7@>yej?sNWCed>B0 zABKwU06+LlOnm>4&m)`a~&v(^zIDNX+~l1IG&WaoT`B zFb3y%Ohy9{GJ!JOlMtrgs>J5s#KgWoN}>yus2yMHL!7OJ`}IAHFy zYVqV}ZLEJ{(_R;Z1nY>icSV=E?zvL!&32OO4Ezc^OP8OKl?#J$NiIC4h znGC;g@p0~sXs*Cgk&k5S*qvk;%tR0-c7uXHlt_^xx z+wJ?ccQWO}T)yCM4EW6ZH#4%l9M__Mv@MRllT=q3p3qKxLwN-1-j?|y^^Gd~Cc7#( zQx96%wa;q~yD%2kVJsYGXr!&(Sxl0wipMFG;M<6v-J zG8c7@Y!B1~fQ7g*FoC2&qWz{>UwOm1?Io-MJc`>Y@|0j_(m7gVtP*$081XfviJP$< z2*@0jO73-~U;iN=>1iKYGMeMZucMlCdFSJjF9|+QUay$T-yhat{CFR^b~$);Pr&*K zW0;(jlHn=-JlG>J^#yqgyf4V_j21Z*)-A?Tq(cJ2M8koZcMuW?L&C(1;Yr|-6gMxD zN)n)7k13NYca7sFzi1MytqS+}7RY~5;HtAiL~}^Z_(Wz@|Gd$)7iq_N+E6a1s4N6~ z5htZd?~w1b{qPCRraHl;D~y_7Rr|_zv!9o)ru2!@JJMmeHOzC^wlvK;N78!y$)X^A z9Q0!63WF2)yqr$;W!fUV*bsyFGQ3th%CDiKn%2lr>qW^Dn=>TR*GH<@#$h@k4R#V zwDPrkL+pZSuES>tWrRz49HuvqXTF~|d$_&#Q+{fYr_fk!Pm&Qw(wgmM8NQyo8hMI3A8D_zF-h{5njYI5U0+ zW5$v9(FLC>%VEA-u1Oreoy`FZ<`K05>-srGLF(*jTF-wHQl8+M@rUcI!vuH3r8cR} zuCT?{rg5PiJI2{+k>zk3U}J;r1Q~oXL#UG@M_`O>L|#@~hT~z~7_9aC2Ph3PE%0 zh{_!a>75F4Vu}~~)TD%B3G84GWXW8O z69w!L2eF;GiMmAzXjlT~?X)^WAOrc7ON%GkP z@eP8hqg#h0B5U5CdyzT*9yLv|JV#fHnXy*=j8xq56&Cm}CLEbe9I+vPPyFe=pziBy z7k}Md7R~RsHD-=H2>(gmN_k5r024AdMN6M6xDLrW<<3ew+1>d9SI-J3tJUln*s=F@ zn&>uqWTUmgYTL+z#V$s#=~AIX*s)3ZK^1?6M$GHh9725Lk>}kmGQDz-sqZklcH2AZGo8Bhl!wXd73KJ9l4kds$G^X`-zk8-f`}%b(0_cYUp&TYtk; zdu!cyiC@3KPMnYgKJDgY*aAGP=Iyrfs!So*0ZJ@$Jx{ zFN&oh=|AZHnv*b^Xu*oKx;b>=j-cZCSzD@V6xJll>J2hiByj&s!-J*uOI$EF>?s zr}q__OzHp5DF0q;zx4xfup87j0KmtzX7%v^=pbLADFC1;h4i|AZ{Lxw>X`I@Z-{?2 zx_|V;1=FUC0l?hRniXUED+7N8r!e=xDSPR7=IOt2+q3827~KB{(Eia65-^Kmrn?lu zStRp-g}(w6W;gE z+R)D*BKj{iv!S2AbYTCGTO0cMLqz|jW;XQmmk#V7a%)3Be~9S6)Xau{{?dW{LvC&8 t=MNG6mzvqo&tE#Qf5@#3{rn-K|57s>`uR%-_7AzWp`SlQ^j~V`{{R?qE$jdQ literal 42112 zcmeFYXH-*NxA(nw8X*J-J@ilw2)GkKiUJ8uO6V34l+cSH-G*MI&;_I^0Z~JhrXu#9 z08$i@CU!+pM6gg*P{jHQQ#j1 z{!!o`1^!Xs9|itV;2#D4QQ#j1{!!o`1^)k{fZ+FK%HP*w#`ybyn)&+xy!qEbnfI^b zf7@3teoCIZOzARC3#*>~?8>|8a=3`P&F;gMUWGTcWEjZ60RrfNye=MA5GT zsO$cwBCbY7XFab=(qvf`n5YEJ8jb;IXtd637_K&&XRd_U7OCOL z70a`%FXK#X1AK{a;&r0f;&q*-D_3VU%dW>8`y{IOjXh7yhV_qRKT+WCKK7Iu9o1VgJUkiKLsmfMP9g*jC#tBX#lo z7Vwhyjfd8_JaLu#84bT;)=0Z;lu_zpk|tQir*8LXj*AnlmVh%l;{?wU zt$*=@Tr@C)|7oXR|9E07wVWn1)A~hnUX+!3LY2811c??FB;hmM*ap|pN$_+G90tJ0 zrTY}5f+VqEp+p|*?nWMZ{M{^Co0q}uAmH7}MrsNi2oZ#v#23;jc;c(gdU8N+UN$4v zFp0y(WfU{$0X+J)yN?+=M8s1vIvEb}yW0iaaRQ~&Ct#1E>~Zfft2YiyTG1QwtPI<0 zMNdornST(NYSSV3{kRKXJ@NUaK-kJ4GuUR*w*p7#m1qj-l4m+}0M-ckQT~Fw89zy4 zd^EI`k*3cegggf?oU9kcW2O2+z3m-iTySLI*+Z5sJtvb9E>2_}4FMK^1=HJil{GN5 zH;YRFTnf$#t@uJWqcazWX_wEtxwrht(KmHd=L6Ga>1gk!Z;3m7&fn;&|7Am-E@@p& zdHWFWJrwx#)AH!Sq|aYIHGO|BoZavUWS}^oT#*4KOO2yW+MDEx`k*OnC<7eEa`{jS zdKk$d%U@3%sW^_p!R10Uf?T0UCc~W(X#sSpwAbaIABeQC^Zr$pfGs z>U3jaPuu-zACSz$Qcc>O%=!#?3%p2x(Sc>!eV~H5&uaU);n(ZG-0s7;blc_U+<7^JXJV7-~fHXtlRD>Ht1$|=JtWjG0~KI5dtp=gV04#1A-UCSb!;UJ&CS$ zC~u=JC86Ep7Oy6A?A37quqRk6$l!s1ABrw_Y=3Q3bN#UCtnY$!wVE_ImY6^E${liX zgjK@`-5Mfx<$YDQqbIlJx4v^o3mE#H<#8_LPujFqk6HtCRkoz<$7|zT2_;fra?NaX zl3)~ZXg#b1Mfe4smaRckCWs=W=ZRx@f2V;QqA@)=uhQ^$z#d||9mDRZ{PH!loy<7; zpyKh}lE-vB*U2R+H{hmsoHI%{(F$!@QIJkH!d`Hmx>0q-vM@gP+%Uq$vEpY)SKPhZ zaVzaZ#E)NBpsVu3=MN|v+a>y7pg#GBt5);M&x{?<1E1^U`{Q@j-dur+73@Jks?%6IjZ}`ZCN;^x#cvB1~f1RuA zEUUtv)mM~^rS-K)86L3tnE`IsAms9qsCM}@f#CCJXjnGnF0EG`=$ZblDCl#3&+6 zgoF+c0e}R+e*-kUjN2OD_f;c+pBaN6Zr(!c7f4mMKL-BrhOn(XYfy!!hHUBljZEil z0!-@0svSdaE20B}DJAP5V9XO0PUx7nP=X_w?cLi9pY%XlSi8NOA^y?G`Nz(Fdmh_V zRuL7nnL~BY2F|w~^CRsD7bI2Sn%q!K>abC?kWKb)0#LC3Pkk4av&FvU1>n zLpz(NR0lt88g9ZaY2RS9_55MM>(C8d`wi~Zp0;6m#LO$klRtfJi1t45S$E}^Q8fDW z<9b;I!uPwW@Aort(lQJH#-N{Lvm!T}OoSz41A@9q&%$pB`JtBp$UIBR$v=n2cGo-` z-tXZe=O%@Yo1JE!snB}#>QwwmSWp1Yo*v26rzfI9h26dB8(4ek^_i7)51>7Xn?PX{ z7Vd9#W&ur?d4wPh(F`o%-QgSO+sIFASZ%#;aCB9s)7PQvk9OaQTxd)4UT(K@eYHDH zM|ea7l~gD87h5|zDu-65^QsWU4#?%1>)lat1e zA4ML;p+P%=VD1P3fWhHhE4K3X9Ja+mc?shAN{zvXb#}?b1h~ADk`^Mskd1*v;jlvV zFgUU+3E3iZDHWEUK`#vLF0K|ar(L8Yu&Btk5@*%qF-iwu?uj&@t~#kqUBwUodU12)P94T z(K+u(X5DXM#lJ+1#B*Cd{s_w_<3oZs6CwC$Tr9hBi~b?A?|)u7AM-!ctt#bU z_sLESgTwCe5KLYx0pI|THW7UI&%_?4zoT}@Os31P^{CG1zrr=w_<v63Oi{e-3>J zMD-dcAp_(bs92A$&4UF>+WD{L;(zJuib76~;$tv?4yUQ`$TTY`vAY;e$zp)5x|}~J z#*WWR!VM;qvIm|=D?YfbD)i{&5SaQNd#dXf{RwvE{qK^o9>DNkBu042J&8KwPXtoC)CRRob8o^~3Y|$j+`aT`Rwt zqA&-uoqdfbKi=a;Ev>e(u038JdfFv++09n}T=2cif9}03v)yOSqW;v?9>gWuf7l`z z3@rp;cBsQ9aQ_#UY-nqD2^##tmKxDw({lpZ@E%h;1V_wHgo91s$>f1Ecd!lvv5y;6 zMjv_h4cPayYrCjeI&GBa0`ltF_rKdVNg=KwZ^xAd(V4vXkU+&_RxU3FB-H;lZ$I1E1#WN*izvG<9Z%#nnCPK< z*i3r}u}~c>KWMVw>+lh?ls@0VdW{dyKa!o!j+FJE9kdxeXk9EmBoO>~(to+sLFiP3 zZG{f_{6X?UNa{5v*_ULWb(~`k>rEL0jCkj06Y|Xv*lZ>tn-+va(Ju6vSB*)DIPS&z zTCD4x+dGhVrTId{-jB1-!+#6(zduObd2H9<@YlB)tFOzdUY6gKcvZXjJ54x5^Q!0v z3e?7Hwj!gvjWeXzLq{7`NR6l)qJu)~gJVx0#?MA1WJY`WFa_xg?srNP>|-!6qZh3NDxiWxi^b+;UO9o97=Ixijn zHc9jM{atdaix%5%`P>suS+KwW*7NACwsC#Id+;j3xnn*9l^S-6!J>HWtmB~k-BFQ_ zt|N#l(}Z&go|6~7u5WB@D4f*SOPRldG`C#+oZ+OYyH&enlKvdQuGL=Mx3YAh^wiH! zQkL6hJ_g-+bLO?+uNu(<&f^1b6zxK{UL9Bd7CTPG71&|R{^kcjGA-KSe~QO~!u;U- zb7}Ivvna_qSLQM4@%*pnh==SD&jR5v74s6w9$7Z98976dm>>g(&*qwx`{sr*qVf@% z`F1{%wv%ibDF*evJ*cSgB&`H_)+^=Q%le2Sr|cP$RYck*3{nMajQB33Jh3WX8|a~TLN5yc9(>2lKADc-n84aIKP9MG zm3g;$$~ibR$@*oRJ!`D+beu38(BAe6Abw+@w7Q?#v+EXZELc))p8% zRJgH~Fr2<~&e*9o6!-{S*_mOa%`hF<-2lAHI4`xih5IV_#nqoRBWLp5qMXn5_&u0= zk@j3rQwxYG*~6*PE>g*_B|JVi zGnpk?&fiRleVfi6rD@{?eel*@G-=9)J``k#g^x+o)PUoUKery^4rpYkBwcbUJy@?+ zSNgzYNH?&&j6YLzd+gm_MC_#r1)SWbVC}*-45vgvwrV3L8II?13omaVOxUJADy=*| z{b|FxaNSkf-Zu8)nfY}COL2mpISMca!i@FbkCQ~@_(gRlTwq*Va~pXWo+JWfK@Y+B zcx4nQPD1JKKaq(vqHhJl3Y8LxCFOpcV6;kD2A zsJf#j{(Jt&-5YtM;utqpu)}8Mn2n(5QsDPJfq(cXjc>(GR&V*8CVYN`HiQuZTpjS_N7bz%OVM+*r<24MHH2#Ew6DE`xMzXCOAjDlrS(!(Kr+q^>C_ zBl&jm>)um&ZAZyX8>(U^zGy@)MJ-03sIayXIcpSfva7)BaLTpXQ#S5ey&pCT1Vbb| z{LbdrV~b|84oRd5A9)Kc}SaUj3}6%`#pD0dYNh=E!0Wm7%D-70zAu2 zzirX=LAI95ICdif8brBeVTUBzTs{F{?H(P=$=zrpx>t- ziyHI$dzT}Ni@p>JR|~=?{N6H83tpH`W_XPgSa?Xw-*ca}qIuT4R-Pu_iCL9%0E-s~ zoeJ$ZG?|2#p|6Juq-y7;M>6e#&IYc=b+=X62zrt^hq*j;udM0{&)nO1Rg{7%oad%^bM}KO()KKV zTh%*`GB*v6XQ<*;>993^&N7fq))e?k@mTCcLs9CMfD;F;SJ&d7@-=?s$Q-J@sQM^6 zEI6uyk%>kYiRBr6k=5la^o7hI4PbPj!kKtGcF?m1jhqdF+-mnj9BNOYZLBnc&lMA` zlWQePZu_C#UmKr}))cG%sFRVH|6o9LIZOv=O+PHQCtP#TCnCr`uUR_ivsu7t%kx)t z&n%pI@b{ATORJn8|n-gTUpoG;t;!@PKxLy)<^?Z235xarWTZr-wHbFak>b zJF>+eANeAE8qbSp4fb-zZbLgFYRoXTO&1&PzuQvj@am|s^08ZzR?c@fO}*JsA`nbL zHcU&?^Fy-a@LV$}@_~u50+2@X?DjU%XFP-A+BZP<3=|{+$U`uSH3VnBw=oM}sgXuV zvy^rPohiWIz_lXoETK20`CT>+A836WJb&Yj1p3+@i{=KZ<*$>qBo{<>?`&P&QqQ{>m~uz z(3JNb=qnKNLpi}fn47n=SG>paw%{pgu>n%(=V&26ERD(_aMd)P!SAH{;5TzsIU-zD zJceS%5qcq7Ve8_FodxC= zP8b(;TYhl}jDFi&&F=H{p(ux(u#?>({bi}D!;jn!^$H9YE0@CWE(=x**dSzJZ2kG@ zu(b(I3xi<5H!xs!5(W+PHh|19JWtIBhy+UjZCDME4QRo?Xv0rR#7lrf#Y&r^?x*S`46O6l0Ahp6f^ddIcY z%5ydhZ}R(65i}a}CT_}jv2H%r(nc@>S=gFDOxaHIUBCv~k+5H-s6P4ry&?#6R=I?F!L8%a;CNJ4c|Isza1rWfywwMTi7cqE+S$ z1#$PO=`P)KH@C?FVQ9}vCxv5yyR(#{R_#5MTH1e4ZlzF7`9Su1_Q73~ii#l7@r0Gzpv}4F;i^yqz4)D(TbN-UhDn zqS+K%hA~N-_Qim5@m*)5d#3XvJQog7w}bo!OEdViiL`%X(_QZF8b^tEgCV!R5|!RO zDqKz1cE7)bjBe43D;vV@%!7}I)-N?LW%d0PRXSL+@-rHuu|&x`J${nzIVwVKkED#( z_$g)}Y|Vx7v-jB1Zag6^KL9uR^lyF)E4j>7y?7XS4(cy9G)}jbgX~yeAO#)?Lh$?{ zBx5(E2aH*nqa%X@Gyscn=*2?&n3hQ9_RzjTbkPO!>n#f!u$};`FctS3)3)7CsjwLEk_s^$3^0G;qP&QTiOkc%*&%P)b z<38otpuErAJ{oc(vCo&%KtnNN`s8O+oP>TxnV}BBJ5o#(^9blmvORc!WRK3K1LAB( ztITqcXZ8T;=?%^ORM6Mow`4B#(yUuVgbuQ6qizWWA58egKx@-25A@j?+9ptA+dt{A z+wcA3erLc??W&Q7#qC{lE4KtID%p@5wbZygR~LG>Wey_XNaV}RZfIKcw<$<@2wB4N zAay_o0w_VUI(a|?cE1#yQ8-4SfZB~2_s#K_A$yHI3mYbhl76{&Z+BvM=We`Z6&+=& zI2&u4S@C?R>9uHZpLy%8b(x%fPb5b2ad;%nZDA*Y3YzZz)%#l$r zi35U@i^#Y6jGt5EqX?;HW$&{&BY2d{nLW=^RJ1mIx6-_ZI9ba*=e^~W#s>SFH0$ik z(RO!2^>kWlrVsvkw_)haeEui@;W*M0F7Gmk=zobGdvHR*fY)!cu*Oe5fH2nQvsVt$ z*%3nfBjktv6eYAj%wH!p*6dG@1n{Lp4c|Ka5r*a?&=B7Mi01*IffAg_Bx7e^B0AgO z5yV!TX}$$S3YHY!qW`jKG&U z&@n*ZwbHY7IY$@(%dI}_WnnK`%g)p_wOMS_zDLiGUnr^9peceZ@2q2zlD#5)^90*@ zbE4-V)-BJ~rl~Z^TY7N{ha1yKf6n`_=}?aB8!1p+7`UL4%<1YcBw(xR>FKm#3RtOq)2CDQZ*C|N58Lb6b2YH;RNZke;f@Tjy;+?C5=qE2A zf0hI@4vG-3F(xq;<`i#=_X(B6T2}S_xqv6Z0aH#VC`H9eS*tf%UiwMaCcpe!kih%$OD5 zw>{R6JF(>RG0?wRu$aO@QGJzy8ZGgUGeht?HL2yfyc~`n%b{ZLm@!@hI43L8M}{vk z(BLfNhZ6sd44l=KodxRv9%+nBw1TT-BBTbZ!-2Zru41OuPc_#5rhXI+djFMA?3BN{<>T910F7 zSyI0|8d7O^WOUCb^^vp(SFG;|RxiOILz#^U*NiQRJQ>l#q!NQd#JC79y@qC@!UjE+ zYfxX{T&`F(B#ssHWE9{rc^cpo*s#ku5;Lv_mx0+j4C=F};K}}h>04evBz9_})1g(Y z-|{yWG0p=y%mAs+KRUJAkr`VVV+Ygabe*K+fcNbIXQXhaeE3q4 z3x^%W{-TTdFIvf6iMUKmxTa3zax5Rc>U_`UxRw_Thha-2^TO~qd_L5SEWBQn`mJ|+ z{EwhA&U|GNuc|@AM;mA;nFdkudgIJ;G|wF`<8V!Jfc_jW$~%cy9@EDI47n0uH+?fx zIuX*hSC>SbbKAzOr^`w~CNSGqcaUhN`PTEhB=Q*FV_e0y5>sIT?N{!TPk5^AVR$`& zu8&p5tRrVsm>&(=OCA`V*ZnB@C4cs<_sc{j3bc-KiEQhdPrl2thmyq}l9G6}Q2KB* znFtm`hWwA3C>|Zb;7Rm}5_BcRQMn+7p<>*ojTFl zKb8*MyFL5JhVWaai;lMp9b$|o0K6NNE8JtaAyv$ua@T0L$+W7pvYwb8juB{Be*FAw zg-S*s-V}MT+=rLW0UziqX}q(+!^g()(p+OHz=^KGaHpGOh94jt3L*jqnoNq0JW0Dr z&6UkcY}L0>Qy6su)vU}NrP$7E(p>o{>T%IPNbF$cNS zP<-B8>ub%+k$quZ-=_4IM(#MAPX1ZBZ#hpq-ry0Usu9bav!p3$RnQtBTA$hR3ULy_ zGEd@P@elmDvCZG{^Ew5dkRPe+4?iRqn_240fouM&^%R8?!vPptS_c^|%3ed`Ad@G` z8k2UxGFb3JCo1Htd~kUYn{^cNuI~|_&A7bzb7zPBgjr|Y;_ z4mXlRK2hIR45pDO&$xyBD$muzc`wt8<0G|E(K@NBuKjhNyywqYGzSWrTlnd4?r0M1 z`oO%lXZz>%w|lfvXnt7$y6c|Eb>2x7npX^Rd3a$M9|UUic1xLbeVl29xZ5F|tJkxf zT!#$L*Pq3SChqnIWqhBjEbc_q_BU58MGm%1m*I|E*zS#v7kBJ(s2vYdcDfA77Rwmv zezSJ_s(p74dBpjrpU6nal;NV%^0Db79zr_^yuv3LM85DboU|GijsM&J2;*mW^E>cA z(ZcxIj4SkCk_+z_B)>K@7gbL4h4HhD5f;V97?_9b_=OpUm*SG(Dljuo-e#bd$Cjil z%P|SucOq_5*ivod16}FTxm4S2dX(p31|OaSC!0eo1CZ3_r?2{95GL9#(?D8Vv@gx{51UtqnOTN=+a>f3AzDLPv4yhCAG$=DCB_6 zZn88H;|>vFvLGvZA%-ZK14hm3GuD;V?YN^DvQ1{9?n;wq*_%^8Hqmr-7O*s|sW zYxalp{@a@Ufh(Q{SFgllD`?^OpiL9VXP7S0NklM!>_al}H0;1Si3N0@A|P|cZ`7H+ zg-C_r5t@B}W&nA4(W*q}eQ?6?5%Hn0WE||qHG{B}%}S?47e-+X>GbLJ569O@bcEUm zjb6;o{+9To#>RWS9p%8)2Zr-G%lStf?j|hm7c6p=Kv~XNvCMKE9ZCGU+WVaS3~T(B zNOQ$FUI<=+9)aJ$kia7VeY^-A&A|a}s#9noJErVjv;xQQ<($tuPfDbdQOJi|CM-~RBpNaZW8`x0<u24%*UU z7oDLmbvdVo@skCL`{p;Cx#0PB^zxmDft4Pk{!=#1_XHPO_$8R^?pFV2L;GYpJBa$X z`Mcp`{A*U`11Dh5cmj~f-2?}4NZuHx4+Tt0iYcbE<>fVD4|~W*v8;MHTr&OmAF(W_ zDv1Yb_;+ZMsac3)f z9lLD2JVtyLPaOV{cSf*`l_KcU9t`Hw?&0dz_$gu__FCLp<0mI;9es@-!iTA~_*ogR zN)`GqtTHJI`?ggZi_UWGTjEvs!Ey9F=|T}207>-08Pa`-!s-5j2yKA4{(Om}+787+ z_pM?r5{W>(d5fWlduNz-)z1?;f4#PM^n1GJuO2G&#p&||##YH;uDBy~7AhthI1*d- zX+lGObKhHrPFy;rQ*`@t>WKd*@1>OrnhK+k_I3VA-=8)GnrVUhD0A^E2?Y)rL3{(g9D@@d1NMcSRPFK*ch5|8c) zCLwd4g0Vwp9XS#C1IiS6Wqg>D3LQ}ICZkwK>2#Oyh+(Qnb;a9BXaU?s7 zZ3qxd!)`u@ARbpM&O3Dsb*0>(+HqIueCgi$VCGHk4b$Re%8awmGr&FrO+;1xCH;^f z_dE}|C^o9}y8DQ{D@XTgkKIQHwVF76|BpYSAU~?R$ze7YQp~T0wqnjf`>1*lvs1~` zj!_~k=JSVA0C5NeoZvt_eN(PeoI9_ukU_Yve@bM78v4)y4aCB;yb<59qi(~F-S&Ph zZ3VB}c@C+E?m;f18P#9LERShluB5xU1Vw3aDf{vZTs*9u7rm>x%K~cVjvkC?xHgie zRV)xt84MAx5^H~ub=hi-pF98oSy;k6 z&*>%~bF!~WJ2S>4KxQ@~B&P;7&)tN(Ez%vw|a(1$w{vuK0zNTM!G0G zyJA(j)6R%DLkCk(5iQdF->i2AKOKFtVO7<7SZqS$-ZzWYiNNK!+?yJZCG|G6Rb0h* z9nk)7ekekIR(6i1qq-?+%>g*>#0SYmSC&>~B~!=`bXbf<-a*ZUo|g@qyrZf@_TWBY z@(>BZc^VLgvK>-qsaeQsBSi>wi&9HnG^buS!=4b_?9OLWo;zq|Hn>UW6Fyp}END$-E$3T_{IzM)-+vmivz;XU z_VPBqkNE|i%r+$XQga~vfg2{7V3JU*JCF!NFEtNI5bML@)E~1@-RV^XdOBcqkeO|f z%@|}*0`B@r!$5ng_vx}1*%C1E%S}IaH~4H_K6i zAJn~Z8$F(qxFzht?wT~OWA)xkf3{o`3<(K2Mrw3%AggyxLxHrLqz^nCG*$FyqXF+b zWDMG?AwqeI0F5UJ7UGnPdLP0Fxrusm+aSA^E@s4_yDl9-w;s?_yr`Iy!8W>`w0>|F zrz=tWSdUq#>s5U4CjIm&+Q{u}HyTbQG=zRDU~IxZzHfUS{?DR>_jkQRxz?BBEMq^Z z?!JT&F5*%71YpUjwVwfimc6aU?uSs3kr2gDXe!SFn`^j@3vL*XdN3$uILaF0Yn}~{+%&%9_ z-%~?%eZ-0nS3X*|XV;u);OI2@!au{~PA$fJhAi`XuZ`|XN%%*VLzAbMpH-PrcD-4k1W6TW9o}h^LZifxF>A9Snb4|TqPul zK`+ZGL0}Iu;!!!w?SLo)1NbqVz+OhUvffLKHsii~ep#Oh-FQasPRP(QrNQ?A{?oH& zvOXAm(drY+-}=yF1K-1$+q;R|cpCtZ z0|4ab9qz^xSyagJm?ku9YYlxy{W8Bo?Ge`IH-ktrU6QfY1$L3~O}c2DIS_v}!21b5 zQ%MHq;>1-u*EUgLX>bXDXcN*|#5Ua7?)W)0B~0p#*&X1Vd&LC(rIy_B3J&Y98Y+)m zm2)B(>asm__&%bxIpz|wuA$_41om*>i3RkVPIaeiqp8|-2-FOJ!r2{TBT5W9jCm3A zi#bK_+ITg@3E@cJ1n#6)F&yb=VO%c*{6$v~ZHtyEXEM7a!CK9opl;@7nXO^$zof| z(AkEA3-xc9X^jr*=4T#moO~%*(B>H+0(wwNmmig2u>5YLwfeEO|G(lko2|t^1)p?h z{>~qMc(dlev`;JZh4zOhi6*umwm$IUE6S6{L6jmSoEc=0$`FQ!j34NzcZ_nVw9DJj zCVd^lTy=}=^f3w@*veJkUGnpj!p``IOSkG6;$78eZU~=Zr>0{jKFB)RE!EZZT~0Pi zs<@VlZcLU;StCYitLLP$v#LFvgR*O9vskp(OLK8)TGzdwy^D|7t8KA3Je!F zjHWui6&475U!g#FVSN$av4WJGSqq`FQ4j-o2Kg1Mm`qTIptT)lFk3Ak$`T7NtnA+` z+Qq70Y`g%6JGZl9MMW2bwPjH##^>jaH>EwT1AVN+I|5#{33kmzvGbWV$4uR1%SEM zan@s=V*W|sGkj7bC6TdMcS-IU{Z|9Ns43mMxDt;kg~i2MZ$)sEo41pO$aXir2gKHn zbk&LmbmS7ZK?8SHvPF{dy%WtYN4|4^9u^cFDoNhDu;KL%;Z^%F8ZeFQ%?^5bJ+|eH zYNx7f9`YU2WSC!%kpdaD*hwM=2f+XKUkrHHM)S4&^Px{>Kw47(E_>qDTKp`da&nCy zHUKgaX4XYp5%LZhd81f&1h^#+0p$DK1{223QycZ7it6OQB^^ftBbR=KRBRr6>9pJt zbF*!0FKf;-D`|(sF6|rzeDwCDl}}AWX2MiL?q8Sh`cGTsZrU%L=Mde9RUayccaK~Y zdl9q^M1>HT$wAthLt(#|SLq2tQ&+%np-VINxd4NVFCv34>5NvkdLXF;**%je7p`&% zg#_NwbMLx1x*K#=L;Qi!qb44O3GkHVb!%W0mtFa%hfK_`3O>5!RC(u* zxE~BvIRMJ5Dj`Z7!%^M$Yh9``%jeJ4+_PACQq?(o!*;FlQwbn+EUv+STaG1$kItml1xRjwA>_xEdA0l{|7&A>G1AVC=k(QR zs}`_NaA4l#Ys}lU3Xjiy$QbO6Zhh9*4yhJ}YibTVNIUQCnZ4HdS6iTWoOkS&J)`tc z)I^w>b)?}&M-;EmO2l#FW9w9X5l2a;c91tv4{-H0U|i9yYQ5EF=O-(MNlr7lPU4q_ zwqzo+nP;PSRj8sG_p7FSWTu@dA=ES`i*{xfs=ROKDk^i2N8aeI^Edo)wf5AWxj=6> zx7RnhieOB~D-mw{W0`c`5iy{nAFSlR+)~&PjEv_UglY5E0Td2G2E(HLc_Mm87QU_V z@}6*$8$*6g^ocyZOZ3t3xH)EP1Vs?oeXOc6-mwIUl?_Ydsmeq$a02cUcJ1 zE*yM4aOTm{kqRsV0u4WWZzf&Dei-(j)4-W>O;p#9pPpkxYo97L}jmf0K*X_TI1> zd-3}(ttJxBA8?bfUN3!4TQKqa@LTnq%fi|mVU<(s|8SG&|HTg>5bi^$o;EF&l@XyN zldF+63O6pBZxmsp3lWeM&`yw&06G39iO~D4Lv|aO8eWtg8&{{cp10_0`}xKEk9cJc zTUN9Cf`0$z&X_s8@Fd{{ocFn(XzxshggOv17leZA+A}hP!|#enQ?T?Q6c~?j%=kt>#XQ}SzdhG_8Rp2g zHGd+kaI2B;_KS_ltsVj8cB`x2M4W#1XS`ZgG|JI`%Y)B7Uk50NF7GH@XyMrjRQp(P z7upfbI}m$F4h2$Fw1qXAR(}?a%LT{g|EvPMAvhMbR95Toylb(hIMC zNuxg7Wy2VRSBN))6V6nO$=^D&b5#0v_9?lJ-*VAEZ{0D|bZ77vE7o%=i=I@tTugc# zn`^W8>+|pT)_k*I*#%JPXmHG{7un34(dct!%C>h#J-Pv2=5=D^wO~>8K0GD!;VF+N zimGauf}cCHPMW1HJV)M=RC|H6+IVQdCA7+Km#SRUvNaptx*Muc8Q)Q*0&&IQ;*c$5 zx|>ao&lO|f%|&I#Zcbp!IM-!R_LI7f4qE((pAy@*p|_jIB|slP^cq#Ke=;6B`|0%H z^N%|Olfu1i|EJo&`Prp*js42fb8F3snbcetQDV#+$(Tke>6nlo)&JJ~fIx4SuQE!1 zmUX~}6A3Iaml`S_B19Pt;-veqUAh|&xJUnV_i-zb>?lhdefBCVP@u2zF7Wh&@)uzV zA85UC3rEzq2=DVGq=t}@90(!H82TV)2B0lK3F3(w$q&f}JVhkH``N4{>349Od(wC8 zyD;y1EAP2RC#5~Qv;Mu(r0#e9rw_)jIBjsbh<8IdAB{hT7l$vg>6#qiOjK1N#HLUe zeIM4|zacp;?)M~t63O4w(mLhi(-`9KPd&$Z+CShOrDU@V8+EV?OU%n5 zYD8_dq!vS5K(425u|$nE z?3FWPzp&t$u_yO^VZ&2J>;w0RREC;4yeIsbLD`=5{OM30A};+^O-YLyeJ!4Xc| zcO(11-$yuU+0U1^F}8_+d-f-4!u@!1lhQ4p;O!UD=3R7S>D}cO$(*MM52_OKauvt!d=cE%Z2MQp2zaeOk(VOh z%MD)c`{5cKAr<~4(tTbO@mNkAYL!-kUJ`cepZ0!u91&Klf1wG~mHU$&J7KA^__6k> zmhb=ka{fQpTVNLw>OyWoRqlmJCE zbHzUZL!qle!A5Kc?r|EhKm5byp1a)h$Ik4|?w;@O_xsH6aqjHhn*4Hg*E+2EDKX^A zlGnSryT_YM4N1Q0P%bZcx!SL#a$qv)*Vu;*jhk9xN(*1IwF~9j_{Yi2OhEr??Y@>f zhGZfq<-X5(=c{2K*N%K|rL7(&hY$DUx)r_QVE3S@Gdm=RpZUCFrmV6wTRYL+px%G% zLfP8F97%*YVMSD_yuklZ`;zI7DI>e#ABNAw>cor-{4)}a__2Zfly7!*ROgZVk2v$r zI%-TF`V;vu|Fq%FPhNQBFM<7!injOGeR}!*$~@xus-fEhiS7G}8~?Jn9tz@mWxdOi z3fpo=;xx92>s^};vW{L?AQiE||ttB!T7ajh~cNzXd6!-t!H->YFp(OjUsxVNY% z+`P`yShnYraM_gWri?=Y>-XIIdxCgIPS>Naq4)vA#vYO^FIQW?GBPFEnLF;HF&+&h zlJl(9t;ey>KbNn2pZrYfu$)u>&YH+UjiHv(c` zT&oZY(_49DxJ&oV7shNuk}oxF-3HEF<9V8V{=qVJPqR`tD!cNw(E-y+i^|uwrv+!f zDSFDi*tb3D0mM%k?rkQ$n=T95< z{}_63m(XIFB^C2XCiossX0~5OHp*BBWSb zarwIaBVO8=Zbos!>D3UdrRt?P#E&&l5)OdN9XLSpYXHcE_wVJyyMl16RptcNcCVh8 zslr;Io{K~L0N^pp33tGf1fp4!=kEW^C!$nEr^TcjSVL;RS*{s&sg z5mEnN5J>SOoULDYubZ)7<1J9n)ggXNQ2s&up#BHoU%zCES{5K08AbdUQ~3t~D*w2k z`h`C~Bk;6=UG#YX@niV7|DpI{PX6hp&voq1&;y7cG`~O2Kd66ZK>g}h)Uk1-(E|(k z8KCly4Lbh;*!Ryp?C3=!Jx}Bx#1E=pJpAXczM5iPjFeUvF6zxu{xLxP^X&OET+vkj z%p;O+|Xy(_i z1^n>wuU}p{lZmuCaFK7mfFF4MeK!AyVg4b2p#YStT=900WzMh#-U{_w9pXnw)h`b0 ze;_|%3)nvs0Px`X&r-bY&q&2vpT4X6@BU{g#Sa6{pY=UHxZAg&et~)O7t0Us;iFw2 z%Ma}#V6*_1A4Z3dcJVAfw1 Date: Wed, 28 Feb 2024 14:43:43 -0800 Subject: [PATCH 1683/1897] Adjust sticky scroll height based on endMarker Fixes #199305 --- .../browser/terminalStickyScrollOverlay.ts | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts index 0a08eb195f0..969a81c5ad6 100644 --- a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts @@ -8,7 +8,7 @@ import type { IBufferLine, IMarker, ITerminalOptions, ITheme, Terminal as RawXte import { importAMDNodeModule } from 'vs/amdX'; import { $, addDisposableListener, addStandardDisposableListener, getWindow } from 'vs/base/browser/dom'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; -import { debounce, memoize, throttle } from 'vs/base/common/decorators'; +import { memoize, throttle } from 'vs/base/common/decorators'; import { Event } from 'vs/base/common/event'; import { Disposable, MutableDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { removeAnsiEscapeCodes } from 'vs/base/common/strings'; @@ -193,17 +193,18 @@ export class TerminalStickyScrollOverlay extends Disposable { if (command && this._currentStickyCommand !== command) { this._throttledRefresh(); } else { - this._debouncedRefresh(); + // If it's the same command, do not throttle as the sticky scroll overlay height may + // need to be adjusted. This would cause a flicker if throttled. + this._refreshNow(); } } - @debounce(20) - private _debouncedRefresh(): void { - this._throttledRefresh(); - } - @throttle(0) private _throttledRefresh(): void { + this._refreshNow(); + } + + private _refreshNow(): void { const command = this._commandDetection.getCommandForLine(this._xterm.raw.buffer.active.viewportY); // The command from viewportY + 1 is used because this one will not be obscured by sticky @@ -255,7 +256,7 @@ export class TerminalStickyScrollOverlay extends Disposable { const buffer = xterm.buffer.active; const promptRowCount = command.getPromptRowCount(); const commandRowCount = command.getCommandRowCount(); - const stickyScrollLineStart = startMarker.line - (promptRowCount - 1); + let stickyScrollLineStart = startMarker.line - (promptRowCount - 1); // Calculate the row offset, this is the number of rows that will be clipped from the top // of the sticky overlay because we do not want to show any content above the bounds of the @@ -264,7 +265,18 @@ export class TerminalStickyScrollOverlay extends Disposable { const isPartialCommand = !('getOutput' in command); const rowOffset = !isPartialCommand && command.endMarker ? Math.max(buffer.viewportY - command.endMarker.line + 1, 0) : 0; const maxLineCount = Math.min(this._rawMaxLineCount, Math.floor(xterm.rows * Constants.StickyScrollPercentageCap)); - const stickyScrollLineCount = Math.min(promptRowCount + commandRowCount - 1, maxLineCount) - rowOffset; + let stickyScrollLineCount = Math.min(promptRowCount + commandRowCount - 1, maxLineCount) - rowOffset; + + // TODO: This needs to not be throttled + // Adjust sticky scroll content if it would below the end of the command, obscuring the + // following command. + if (!isPartialCommand && command.endMarker && command.endMarker.line !== -1) { + if (buffer.viewportY + stickyScrollLineCount > command.endMarker.line) { + const diff = buffer.viewportY + stickyScrollLineCount - command.endMarker.line; + stickyScrollLineStart += diff; + stickyScrollLineCount -= diff; + } + } // Hide sticky scroll if it's currently on a line that contains it if (buffer.viewportY <= stickyScrollLineStart) { @@ -287,7 +299,7 @@ export class TerminalStickyScrollOverlay extends Disposable { } } - // Clear attrs, reset cursor position, clear right + // Get the line content of the command from the terminal const content = this._serializeAddon.serialize({ range: { start: stickyScrollLineStart + rowOffset, @@ -305,6 +317,7 @@ export class TerminalStickyScrollOverlay extends Disposable { // Write content if it differs if (content && this._currentContent !== content) { this._stickyScrollOverlay.resize(this._stickyScrollOverlay.cols, stickyScrollLineCount); + // Clear attrs, reset cursor position, clear right this._stickyScrollOverlay.write('\x1b[0m\x1b[H\x1b[2J'); this._stickyScrollOverlay.write(content); this._currentContent = content; From 50d6e94b3f5d55c42fdc0e2302905a7541fbc2e7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:46:59 -0800 Subject: [PATCH 1684/1897] Move sticky scroll endMarker clipping to element position This means we no longer need to resize the canvas which reduces flicker. Fixes #199305 --- .../browser/terminalStickyScrollOverlay.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts index 969a81c5ad6..04f14751372 100644 --- a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts @@ -256,7 +256,7 @@ export class TerminalStickyScrollOverlay extends Disposable { const buffer = xterm.buffer.active; const promptRowCount = command.getPromptRowCount(); const commandRowCount = command.getCommandRowCount(); - let stickyScrollLineStart = startMarker.line - (promptRowCount - 1); + const stickyScrollLineStart = startMarker.line - (promptRowCount - 1); // Calculate the row offset, this is the number of rows that will be clipped from the top // of the sticky overlay because we do not want to show any content above the bounds of the @@ -265,18 +265,7 @@ export class TerminalStickyScrollOverlay extends Disposable { const isPartialCommand = !('getOutput' in command); const rowOffset = !isPartialCommand && command.endMarker ? Math.max(buffer.viewportY - command.endMarker.line + 1, 0) : 0; const maxLineCount = Math.min(this._rawMaxLineCount, Math.floor(xterm.rows * Constants.StickyScrollPercentageCap)); - let stickyScrollLineCount = Math.min(promptRowCount + commandRowCount - 1, maxLineCount) - rowOffset; - - // TODO: This needs to not be throttled - // Adjust sticky scroll content if it would below the end of the command, obscuring the - // following command. - if (!isPartialCommand && command.endMarker && command.endMarker.line !== -1) { - if (buffer.viewportY + stickyScrollLineCount > command.endMarker.line) { - const diff = buffer.viewportY + stickyScrollLineCount - command.endMarker.line; - stickyScrollLineStart += diff; - stickyScrollLineCount -= diff; - } - } + const stickyScrollLineCount = Math.min(promptRowCount + commandRowCount - 1, maxLineCount) - rowOffset; // Hide sticky scroll if it's currently on a line that contains it if (buffer.viewportY <= stickyScrollLineStart) { @@ -336,7 +325,18 @@ export class TerminalStickyScrollOverlay extends Disposable { const termBox = xterm.element.getBoundingClientRect(); const rowHeight = termBox.height / xterm.rows; const overlayHeight = stickyScrollLineCount * rowHeight; - this._element.style.bottom = `${termBox.height - overlayHeight + 1}px`; + + // Adjust sticky scroll content if it would below the end of the command, obscuring the + // following command. + let endMarkerOffset = 0; + if (!isPartialCommand && command.endMarker && command.endMarker.line !== -1) { + if (buffer.viewportY + stickyScrollLineCount > command.endMarker.line) { + const diff = buffer.viewportY + stickyScrollLineCount - command.endMarker.line; + endMarkerOffset = diff * rowHeight; + } + } + + this._element.style.bottom = `${termBox.height - overlayHeight + 1 + endMarkerOffset}px`; } } else { this._setVisible(false); From 1ee4bfbda26215fcbf0e89f0a132bda2ffd0ca47 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:55:52 -0800 Subject: [PATCH 1685/1897] More aggressively clear viewport commands on Windows Fixes #198278 --- .../common/capabilities/commandDetectionCapability.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index df3cb4ce9ee..685bae340bb 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -515,8 +515,6 @@ class WindowsPtyHeuristics extends Disposable { private _onCursorMoveListener = this._register(new MutableDisposable()); - private _recentlyPerformedCsiJ = false; - private _tryAdjustCommandStartMarkerScheduler?: RunOnceScheduler; private _tryAdjustCommandStartMarkerScannedLineCount: number = 0; private _tryAdjustCommandStartMarkerPollCount: number = 0; @@ -530,8 +528,8 @@ class WindowsPtyHeuristics extends Disposable { super(); this._register(_terminal.parser.registerCsiHandler({ final: 'J' }, params => { + // Clear commands when the viewport is cleared if (params.length >= 1 && (params[0] === 2 || params[0] === 3)) { - this._recentlyPerformedCsiJ = true; this._hooks.clearCommandsInViewport(); } // We don't want to override xterm.js' default behavior, just augment it @@ -539,11 +537,6 @@ class WindowsPtyHeuristics extends Disposable { })); this._register(this._capability.onBeforeCommandFinished(command => { - if (this._recentlyPerformedCsiJ) { - this._recentlyPerformedCsiJ = false; - return; - } - // For older Windows backends we cannot listen to CSI J, instead we assume running clear // or cls will clear all commands in the viewport. This is not perfect but it's right // most of the time. From 5ee040ec3a8381d4aaef2458c64a41c9e122df99 Mon Sep 17 00:00:00 2001 From: gjsjohnmurray Date: Thu, 29 Feb 2024 02:34:52 +0000 Subject: [PATCH 1686/1897] Use action submenu instead of existing quickpick --- src/vs/platform/log/common/log.ts | 13 +++ .../contrib/logs/common/defaultLogLevels.ts | 27 +++++- .../contrib/logs/common/logs.contribution.ts | 4 +- .../contrib/logs/common/logsActions.ts | 21 +--- .../output/browser/output.contribution.ts | 97 +++++++++++++------ .../contrib/output/browser/outputServices.ts | 34 ++++++- .../services/output/common/output.ts | 4 + 7 files changed, 148 insertions(+), 52 deletions(-) diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index e2d69c0fe30..b0342da2653 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter, Event } from 'vs/base/common/event'; import { hash } from 'vs/base/common/hash'; @@ -12,6 +13,7 @@ import { isWindows } from 'vs/base/common/platform'; import { joinPath } from 'vs/base/common/resources'; import { Mutable, isNumber, isString } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; +import { ILocalizedString } from 'vs/platform/action/common/action'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -745,6 +747,17 @@ export function LogLevelToString(logLevel: LogLevel): string { } } +export function LogLevelToLocalizedString(logLevel: LogLevel): ILocalizedString { + switch (logLevel) { + case LogLevel.Trace: return { original: 'trace', value: nls.localize('trace', "Trace") }; + case LogLevel.Debug: return { original: 'debug', value: nls.localize('debug', "Debug") }; + case LogLevel.Info: return { original: 'info', value: nls.localize('info', "Info") }; + case LogLevel.Warning: return { original: 'warn', value: nls.localize('warn', "Warning") }; + case LogLevel.Error: return { original: 'error', value: nls.localize('error', "Error") }; + case LogLevel.Off: return { original: 'off', value: nls.localize('off', "Off") }; + } +} + export function parseLogLevel(logLevel: string): LogLevel | undefined { switch (logLevel) { case 'trace': diff --git a/src/vs/workbench/contrib/logs/common/defaultLogLevels.ts b/src/vs/workbench/contrib/logs/common/defaultLogLevels.ts index 9feccec6965..522b64a8cd6 100644 --- a/src/vs/workbench/contrib/logs/common/defaultLogLevels.ts +++ b/src/vs/workbench/contrib/logs/common/defaultLogLevels.ts @@ -12,6 +12,8 @@ import { isString, isUndefined } from 'vs/base/common/types'; import { EXTENSION_IDENTIFIER_WITH_LOG_REGEX } from 'vs/platform/environment/common/environmentService'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { parse } from 'vs/base/common/json'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Emitter, Event } from 'vs/base/common/event'; interface ParsedArgvLogLevels { default?: LogLevel; @@ -26,17 +28,27 @@ export interface IDefaultLogLevelsService { readonly _serviceBrand: undefined; + /** + * An event which fires when default log levels are changed + */ + readonly onDidChangeDefaultLogLevels: Event; + getDefaultLogLevels(): Promise; + getDefaultLogLevel(extensionId?: string): Promise; + setDefaultLogLevel(logLevel: LogLevel, extensionId?: string): Promise; migrateLogLevels(): void; } -class DefaultLogLevelsService implements IDefaultLogLevelsService { +class DefaultLogLevelsService extends Disposable implements IDefaultLogLevelsService { _serviceBrand: undefined; + private _onDidChangeDefaultLogLevels = this._register(new Emitter); + readonly onDidChangeDefaultLogLevels = this._onDidChangeDefaultLogLevels.event; + constructor( @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IFileService private readonly fileService: IFileService, @@ -44,6 +56,7 @@ class DefaultLogLevelsService implements IDefaultLogLevelsService { @ILogService private readonly logService: ILogService, @ILoggerService private readonly loggerService: ILoggerService, ) { + super(); } async getDefaultLogLevels(): Promise { @@ -54,11 +67,20 @@ class DefaultLogLevelsService implements IDefaultLogLevelsService { }; } + async getDefaultLogLevel(extensionId?: string): Promise { + const argvLogLevel = await this._parseLogLevelsFromArgv() ?? {}; + if (extensionId) { + extensionId = extensionId.toLowerCase(); + return this._getDefaultLogLevel(argvLogLevel, extensionId); + } else { + return this._getDefaultLogLevel(argvLogLevel); + } + } + async setDefaultLogLevel(defaultLogLevel: LogLevel, extensionId?: string): Promise { const argvLogLevel = await this._parseLogLevelsFromArgv() ?? {}; if (extensionId) { extensionId = extensionId.toLowerCase(); - const argvLogLevel = await this._parseLogLevelsFromArgv() ?? {}; const currentDefaultLogLevel = this._getDefaultLogLevel(argvLogLevel, extensionId); argvLogLevel.extensions = argvLogLevel.extensions ?? []; const extension = argvLogLevel.extensions.find(([extension]) => extension === extensionId); @@ -82,6 +104,7 @@ class DefaultLogLevelsService implements IDefaultLogLevelsService { this.loggerService.setLogLevel(defaultLogLevel); } } + this._onDidChangeDefaultLogLevels.fire(); } private _getDefaultLogLevel(argvLogLevels: ParsedArgvLogLevels, extension?: string): LogLevel { diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index 2b95a4341ce..19257411641 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -36,8 +36,8 @@ registerAction2(class extends Action2 { f1: true }); } - run(servicesAccessor: ServicesAccessor, channelId?: string): Promise { - return servicesAccessor.get(IInstantiationService).createInstance(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.TITLE.value).run(channelId); + run(servicesAccessor: ServicesAccessor): Promise { + return servicesAccessor.get(IInstantiationService).createInstance(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.TITLE.value).run(); } }); diff --git a/src/vs/workbench/contrib/logs/common/logsActions.ts b/src/vs/workbench/contrib/logs/common/logsActions.ts index f295d14a03c..3ececbb1223 100644 --- a/src/vs/workbench/contrib/logs/common/logsActions.ts +++ b/src/vs/workbench/contrib/logs/common/logsActions.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { ILoggerService, LogLevel, isLogLevel } from 'vs/platform/log/common/log'; +import { ILoggerService, LogLevel, LogLevelToLocalizedString, isLogLevel } from 'vs/platform/log/common/log'; import { IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; @@ -36,8 +36,8 @@ export class SetLogLevelAction extends Action { super(id, label); } - override async run(channelId?: string): Promise { - const logLevelOrChannel = await this.getLogLevelOrChannel(channelId); + override async run(): Promise { + const logLevelOrChannel = await this.selectLogLevelOrChannel(); if (logLevelOrChannel !== null) { if (isLogLevel(logLevelOrChannel)) { this.loggerService.setLogLevel(logLevelOrChannel); @@ -47,7 +47,7 @@ export class SetLogLevelAction extends Action { } } - private async getLogLevelOrChannel(channelId?: string): Promise { + private async selectLogLevelOrChannel(): Promise { const defaultLogLevels = await this.defaultLogLevelsService.getDefaultLogLevels(); const extensionLogs: LogChannelQuickPickItem[] = [], logs: LogChannelQuickPickItem[] = []; const logLevel = this.loggerService.getLogLevel(); @@ -57,9 +57,6 @@ export class SetLogLevelAction extends Action { } const channelLogLevel = this.loggerService.getLogLevel(channel.file) ?? logLevel; const item: LogChannelQuickPickItem = { id: channel.id, resource: channel.file, label: channel.label, description: channelLogLevel !== logLevel ? this.getLabel(channelLogLevel) : undefined, extensionId: channel.extensionId }; - if (channelId && channel.id === channelId) { - return item; - } if (channel.extensionId) { extensionLogs.push(item); } else { @@ -148,15 +145,7 @@ export class SetLogLevelAction extends Action { } private getLabel(level: LogLevel, current?: LogLevel): string { - let label: string; - switch (level) { - case LogLevel.Trace: label = nls.localize('trace', "Trace"); break; - case LogLevel.Debug: label = nls.localize('debug', "Debug"); break; - case LogLevel.Info: label = nls.localize('info', "Info"); break; - case LogLevel.Warning: label = nls.localize('warn', "Warning"); break; - case LogLevel.Error: label = nls.localize('err', "Error"); break; - case LogLevel.Off: label = nls.localize('off', "Off"); break; - } + const label = LogLevelToLocalizedString(level).value; return level === current ? `$(check) ${label}` : label; } diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 4133ae522b2..595c458f2f4 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { MenuId, registerAction2, Action2, MenuRegistry } from 'vs/platform/actions/common/actions'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { OutputService } from 'vs/workbench/contrib/output/browser/outputServices'; -import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_MODE_ID, LOG_MIME, CONTEXT_ACTIVE_FILE_OUTPUT, CONTEXT_OUTPUT_SCROLL_LOCK, IOutputChannelDescriptor, IFileOutputChannelDescriptor, ACTIVE_OUTPUT_CHANNEL_CONTEXT, CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE, IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output'; +import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_MODE_ID, LOG_MIME, CONTEXT_ACTIVE_FILE_OUTPUT, CONTEXT_OUTPUT_SCROLL_LOCK, IOutputChannelDescriptor, IFileOutputChannelDescriptor, ACTIVE_OUTPUT_CHANNEL_CONTEXT, CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE, IOutputChannelRegistry, Extensions, CONTEXT_ACTIVE_OUTPUT_LEVEL, CONTEXT_ACTIVE_OUTPUT_LEVEL_IS_DEFAULT } from 'vs/workbench/services/output/common/output'; import { OutputViewPane } from 'vs/workbench/contrib/output/browser/outputView'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -30,8 +30,8 @@ import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { SetLogLevelAction } from 'vs/workbench/contrib/logs/common/logsActions'; +import { ILoggerService, LogLevel, LogLevelToLocalizedString } from 'vs/platform/log/common/log'; +import { IDefaultLogLevelsService } from 'vs/workbench/contrib/logs/common/defaultLogLevels'; // Register Service registerSingleton(IOutputService, OutputService, InstantiationType.Delayed); @@ -339,34 +339,71 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { private registerConfigureActiveOutputLogLevelAction(): void { const that = this; - this._register(registerAction2(class extends Action2 { - constructor() { - super({ - id: `workbench.action.configureActiveOutputLogLevel`, - title: nls.localize2('configureActiveOutputLogLevel', "Configure Log Level..."), - menu: [{ - id: MenuId.ViewTitle, - when: ContextKeyExpr.equals('view', OUTPUT_VIEW_ID), - group: 'navigation', - order: 6, - isHiddenByDefault: true - }], - icon: Codicon.gear, - precondition: CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE - }); - } - async run(accessor: ServicesAccessor): Promise { - const commandService = accessor.get(ICommandService); - that.configureActiveOutputLogLevel(commandService); - } + const logLevelMenu = new MenuId('workbench.output.menu.logLevel'); + this._register(MenuRegistry.appendMenuItem(MenuId.ViewTitle, { + submenu: logLevelMenu, + title: nls.localize('logLevel.label', "Set Log Level..."), + group: 'navigation', + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', OUTPUT_VIEW_ID), CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE), + icon: Codicon.gear, + order: 6 })); - } - - private async configureActiveOutputLogLevel(commandService: ICommandService): Promise { - const channel = this.outputService.getActiveChannel(); - if (channel) { - await commandService.executeCommand(SetLogLevelAction.ID, channel.id); - } + const registeredLogLevels = new Map(); + this._register(toDisposable(() => dispose(registeredLogLevels.values()))); + const registerLogLevels = (logLevels: LogLevel[]) => { + let order = 0; + for (const logLevel of logLevels) { + const { original, value } = LogLevelToLocalizedString(logLevel); + registeredLogLevels.set(logLevel, registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.action.output.activeOutputLogLevel.${logLevel}`, + title: value, + toggled: CONTEXT_ACTIVE_OUTPUT_LEVEL.isEqualTo(original), + menu: { + id: logLevelMenu, + order: order++, + group: '0_level' + } + }); + } + async run(accessor: ServicesAccessor): Promise { + const channel = that.outputService.getActiveChannel(); + if (channel) { + const channelDescriptor = that.outputService.getChannelDescriptor(channel.id); + if (channelDescriptor?.log && channelDescriptor.file) { + return accessor.get(ILoggerService).setLogLevel(channelDescriptor.file, logLevel); + } + } + } + })); + } + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.action.output.activeOutputLogLevelDefault`, + title: nls.localize('logLevelDefault.label', "Set As Default"), + menu: { + id: logLevelMenu, + order, + group: '1_default' + }, + precondition: CONTEXT_ACTIVE_OUTPUT_LEVEL_IS_DEFAULT.negate() + }); + } + async run(accessor: ServicesAccessor): Promise { + const channel = that.outputService.getActiveChannel(); + if (channel) { + const channelDescriptor = that.outputService.getChannelDescriptor(channel.id); + if (channelDescriptor?.log && channelDescriptor.file) { + const logLevel = accessor.get(ILoggerService).getLogLevel(channelDescriptor.file); + return await accessor.get(IDefaultLogLevelsService).setDefaultLogLevel(logLevel, channelDescriptor.extensionId); + } + } + } + })); + }; + registerLogLevels([LogLevel.Trace, LogLevel.Debug, LogLevel.Info, LogLevel.Warning, LogLevel.Error, LogLevel.Off]); } private registerShowLogsAction(): void { diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index e004aaa86bc..718be38cf42 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -9,11 +9,11 @@ import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IOutputChannel, IOutputService, OUTPUT_VIEW_ID, OUTPUT_SCHEME, LOG_MIME, OUTPUT_MIME, OutputChannelUpdateMode, IOutputChannelDescriptor, Extensions, IOutputChannelRegistry, ACTIVE_OUTPUT_CHANNEL_CONTEXT, CONTEXT_ACTIVE_FILE_OUTPUT, CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE } from 'vs/workbench/services/output/common/output'; +import { IOutputChannel, IOutputService, OUTPUT_VIEW_ID, OUTPUT_SCHEME, LOG_MIME, OUTPUT_MIME, OutputChannelUpdateMode, IOutputChannelDescriptor, Extensions, IOutputChannelRegistry, ACTIVE_OUTPUT_CHANNEL_CONTEXT, CONTEXT_ACTIVE_FILE_OUTPUT, CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE, CONTEXT_ACTIVE_OUTPUT_LEVEL, CONTEXT_ACTIVE_OUTPUT_LEVEL_IS_DEFAULT } from 'vs/workbench/services/output/common/output'; import { OutputLinkProvider } from 'vs/workbench/contrib/output/browser/outputLinkProvider'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { ITextModel } from 'vs/editor/common/model'; -import { ILogService } from 'vs/platform/log/common/log'; +import { ILogService, ILoggerService, LogLevelToString } from 'vs/platform/log/common/log'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IOutputChannelModel } from 'vs/workbench/contrib/output/common/outputChannelModel'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; @@ -22,6 +22,7 @@ import { IOutputChannelModelService } from 'vs/workbench/contrib/output/common/o import { ILanguageService } from 'vs/editor/common/languages/language'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SetLogLevelAction } from 'vs/workbench/contrib/logs/common/logsActions'; +import { IDefaultLogLevelsService } from 'vs/workbench/contrib/logs/common/defaultLogLevels'; const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel'; @@ -76,15 +77,19 @@ export class OutputService extends Disposable implements IOutputService, ITextMo private readonly activeOutputChannelContext: IContextKey; private readonly activeFileOutputChannelContext: IContextKey; private readonly activeOutputChannelLevelSettableContext: IContextKey; + private readonly activeOutputChannelLevelContext: IContextKey; + private readonly activeOutputChannelLevelIsDefaultContext: IContextKey; constructor( @IStorageService private readonly storageService: IStorageService, @IInstantiationService private readonly instantiationService: IInstantiationService, @ITextModelService textModelResolverService: ITextModelService, @ILogService private readonly logService: ILogService, + @ILoggerService private readonly loggerService: ILoggerService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IViewsService private readonly viewsService: IViewsService, @IContextKeyService contextKeyService: IContextKeyService, + @IDefaultLogLevelsService private readonly defaultLogLevelsService: IDefaultLogLevelsService ) { super(); this.activeChannelIdInStorage = this.storageService.get(OUTPUT_ACTIVE_CHANNEL_KEY, StorageScope.WORKSPACE, ''); @@ -94,6 +99,8 @@ export class OutputService extends Disposable implements IOutputService, ITextMo this.activeFileOutputChannelContext = CONTEXT_ACTIVE_FILE_OUTPUT.bindTo(contextKeyService); this.activeOutputChannelLevelSettableContext = CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE.bindTo(contextKeyService); + this.activeOutputChannelLevelContext = CONTEXT_ACTIVE_OUTPUT_LEVEL.bindTo(contextKeyService); + this.activeOutputChannelLevelIsDefaultContext = CONTEXT_ACTIVE_OUTPUT_LEVEL_IS_DEFAULT.bindTo(contextKeyService); // Register as text model content provider for output textModelResolverService.registerTextModelContentProvider(OUTPUT_SCHEME, this); @@ -202,6 +209,29 @@ export class OutputService extends Disposable implements IOutputService, ITextMo const descriptor = channel?.outputChannelDescriptor; this.activeFileOutputChannelContext.set(!!descriptor?.file); this.activeOutputChannelLevelSettableContext.set(descriptor !== undefined && SetLogLevelAction.isLevelSettable(descriptor)); + const setLevelContext = (): void => { + const channelLogLevel = descriptor?.log ? this.loggerService.getLogLevel(descriptor.file) : undefined; + this.activeOutputChannelLevelContext.set(channelLogLevel !== undefined ? LogLevelToString(channelLogLevel) : ''); + }; + const setLevelIsDefaultContext = async (): Promise => { + const descriptor = this.activeChannel?.outputChannelDescriptor; + if (descriptor?.log) { + const channelLogLevel = this.loggerService.getLogLevel(descriptor.file); + const channelDefaultLogLevel = await this.defaultLogLevelsService.getDefaultLogLevel(descriptor.extensionId); + this.activeOutputChannelLevelIsDefaultContext.set(channelDefaultLogLevel === channelLogLevel); + } else { + this.activeOutputChannelLevelIsDefaultContext.set(false); + } + }; + this._register(this.defaultLogLevelsService.onDidChangeDefaultLogLevels(() => { + setLevelIsDefaultContext(); + })); + setLevelIsDefaultContext(); + this._register(this.loggerService.onDidChangeLogLevel(_level => { + setLevelContext(); + setLevelIsDefaultContext(); + })); + setLevelContext(); if (this.activeChannel) { this.storageService.store(OUTPUT_ACTIVE_CHANNEL_KEY, this.activeChannel.id, StorageScope.WORKSPACE, StorageTarget.MACHINE); diff --git a/src/vs/workbench/services/output/common/output.ts b/src/vs/workbench/services/output/common/output.ts index 716bb3bcfd6..25ec20fe32d 100644 --- a/src/vs/workbench/services/output/common/output.ts +++ b/src/vs/workbench/services/output/common/output.ts @@ -45,6 +45,10 @@ export const CONTEXT_ACTIVE_FILE_OUTPUT = new RawContextKey('activeLogO export const CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE = new RawContextKey('activeLogOutput.levelSettable', false); +export const CONTEXT_ACTIVE_OUTPUT_LEVEL = new RawContextKey('activeLogOutput.level', ''); + +export const CONTEXT_ACTIVE_OUTPUT_LEVEL_IS_DEFAULT = new RawContextKey('activeLogOutput.levelIsDefault', false); + export const CONTEXT_OUTPUT_SCROLL_LOCK = new RawContextKey(`outputView.scrollLock`, false); export const IOutputService = createDecorator('outputService'); From 4e81df7ea90350b1c2205e39c4e5a1a4197432c0 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 29 Feb 2024 07:41:11 -0600 Subject: [PATCH 1687/1897] Tear down the Authentication monolith (#206495) * Tear down the Authentication monolith Major changes: * Turn the usage functions into a proper service `AuthenticationUsageService` * Pull out the access data stuff into its own service `AuthenticationAccessService` * Pull out things that make sense as actions `ManageTrustedExtensionsForAccount` `SignOutOfAccount` * Pull out random registry stuff into a proper authentication contribution * Pull out everything else that is extension specific into its own class (and eventually it should be in MainThreadAuthentication) * Have the new `AuthenticationService` return a provider instead of having specific methods for getting the `label` or `supportsMultipleAccounts` * fix tests * fix tests --- build/lib/i18n.resources.json | 4 + .../api/browser/mainThreadAuthentication.ts | 58 +- .../api/browser/mainThreadLanguageModels.ts | 26 +- .../extHostAuthentication.integrationTest.ts | 8 +- .../browser/parts/globalCompositeBar.ts | 30 +- ...manageTrustedExtensionsForAccountAction.ts | 168 +++ .../browser/actions/signOutOfAccountAction.ts | 54 + .../browser/authentication.contribution.ts | 197 ++++ .../browser/editSessionsStorageService.ts | 6 +- .../remoteTunnel.contribution.ts | 8 +- .../userDataSync/browser/userDataSync.ts | 2 +- .../browser/authenticationAccessService.ts | 101 ++ .../authenticationExtensionsService.ts | 418 ++++++++ .../browser/authenticationService.ts | 972 ++---------------- .../browser/authenticationUsageService.ts | 70 ++ .../authentication/common/authentication.ts | 118 ++- .../browser/authenticationService.test.ts | 209 ++++ .../browser/userDataSyncWorkbenchService.ts | 7 +- src/vs/workbench/workbench.common.main.ts | 6 + 19 files changed, 1485 insertions(+), 977 deletions(-) create mode 100644 src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts create mode 100644 src/vs/workbench/contrib/authentication/browser/actions/signOutOfAccountAction.ts create mode 100644 src/vs/workbench/contrib/authentication/browser/authentication.contribution.ts create mode 100644 src/vs/workbench/services/authentication/browser/authenticationAccessService.ts create mode 100644 src/vs/workbench/services/authentication/browser/authenticationExtensionsService.ts create mode 100644 src/vs/workbench/services/authentication/browser/authenticationUsageService.ts create mode 100644 src/vs/workbench/services/authentication/test/browser/authenticationService.test.ts diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 654a4445848..3c804f13816 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -553,6 +553,10 @@ { "name": "vs/workbench/contrib/accountEntitlements", "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/authentication", + "project": "vscode-workbench" } ] } diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 22c82811bb4..3bb8ef8fbe4 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -6,18 +6,18 @@ import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { getAuthenticationProviderActivationEvent, addAccountUsage } from 'vs/workbench/services/authentication/browser/authenticationService'; -import { IAuthenticationCreateSessionOptions, AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; +import { IAuthenticationCreateSessionOptions, AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationService, IAuthenticationExtensionsService } from 'vs/workbench/services/authentication/common/authentication'; import { ExtHostAuthenticationShape, ExtHostContext, MainContext, MainThreadAuthenticationShape } from '../common/extHost.protocol'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import Severity from 'vs/base/common/severity'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import type { AuthenticationGetSessionOptions } from 'vscode'; import { Emitter, Event } from 'vs/base/common/event'; - +import { IAuthenticationAccessService } from 'vs/workbench/services/authentication/browser/authenticationAccessService'; +import { IAuthenticationUsageService } from 'vs/workbench/services/authentication/browser/authenticationUsageService'; +import { getAuthenticationProviderActivationEvent } from 'vs/workbench/services/authentication/browser/authenticationService'; export class MainThreadAuthenticationProvider extends Disposable implements IAuthenticationProvider { @@ -58,8 +58,10 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu constructor( extHostContext: IExtHostContext, @IAuthenticationService private readonly authenticationService: IAuthenticationService, + @IAuthenticationExtensionsService private readonly authenticationExtensionsService: IAuthenticationExtensionsService, + @IAuthenticationAccessService private readonly authenticationAccessService: IAuthenticationAccessService, + @IAuthenticationUsageService private readonly authenticationUsageService: IAuthenticationUsageService, @IDialogService private readonly dialogService: IDialogService, - @IStorageService private readonly storageService: IStorageService, @INotificationService private readonly notificationService: INotificationService, @IExtensionService private readonly extensionService: IExtensionService, @ITelemetryService private readonly telemetryService: ITelemetryService @@ -116,7 +118,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu private async doGetSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: AuthenticationGetSessionOptions): Promise { const sessions = await this.authenticationService.getSessions(providerId, scopes, true); - const supportsMultipleAccounts = this.authenticationService.supportsMultipleAccounts(providerId); + const provider = this.authenticationService.getProvider(providerId); // Error cases if (options.forceNewSession && options.createIfNone) { @@ -131,22 +133,22 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu // Check if the sessions we have are valid if (!options.forceNewSession && sessions.length) { - if (supportsMultipleAccounts) { + if (provider.supportsMultipleAccounts) { if (options.clearSessionPreference) { // Clearing the session preference is usually paired with createIfNone, so just remove the preference and // defer to the rest of the logic in this function to choose the session. - this.authenticationService.removeSessionPreference(providerId, extensionId, scopes); + this.authenticationExtensionsService.removeSessionPreference(providerId, extensionId, scopes); } else { // If we have an existing session preference, use that. If not, we'll return any valid session at the end of this function. - const existingSessionPreference = this.authenticationService.getSessionPreference(providerId, extensionId, scopes); + const existingSessionPreference = this.authenticationExtensionsService.getSessionPreference(providerId, extensionId, scopes); if (existingSessionPreference) { const matchingSession = sessions.find(session => session.id === existingSessionPreference); - if (matchingSession && this.authenticationService.isAccessAllowed(providerId, matchingSession.account.label, extensionId)) { + if (matchingSession && this.authenticationAccessService.isAccessAllowed(providerId, matchingSession.account.label, extensionId)) { return matchingSession; } } } - } else if (this.authenticationService.isAccessAllowed(providerId, sessions[0].account.label, extensionId)) { + } else if (this.authenticationAccessService.isAccessAllowed(providerId, sessions[0].account.label, extensionId)) { return sessions[0]; } } @@ -154,51 +156,41 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu // We may need to prompt because we don't have a valid session // modal flows if (options.createIfNone || options.forceNewSession) { - const providerName = this.authenticationService.getLabel(providerId); const detail = (typeof options.forceNewSession === 'object') ? options.forceNewSession.detail : undefined; // We only want to show the "recreating session" prompt if we are using forceNewSession & there are sessions // that we will be "forcing through". const recreatingSession = !!(options.forceNewSession && sessions.length); - const isAllowed = await this.loginPrompt(providerName, extensionName, recreatingSession, detail); + const isAllowed = await this.loginPrompt(provider.label, extensionName, recreatingSession, detail); if (!isAllowed) { throw new Error('User did not consent to login.'); } let session; if (sessions?.length && !options.forceNewSession) { - session = supportsMultipleAccounts - ? await this.authenticationService.selectSession(providerId, extensionId, extensionName, scopes, sessions) + session = provider.supportsMultipleAccounts + ? await this.authenticationExtensionsService.selectSession(providerId, extensionId, extensionName, scopes, sessions) : sessions[0]; } else { let sessionToRecreate: AuthenticationSession | undefined; if (typeof options.forceNewSession === 'object' && options.forceNewSession.sessionToRecreate) { sessionToRecreate = options.forceNewSession.sessionToRecreate as AuthenticationSession; } else { - const sessionIdToRecreate = this.authenticationService.getSessionPreference(providerId, extensionId, scopes); + const sessionIdToRecreate = this.authenticationExtensionsService.getSessionPreference(providerId, extensionId, scopes); sessionToRecreate = sessionIdToRecreate ? sessions.find(session => session.id === sessionIdToRecreate) : undefined; } session = await this.authenticationService.createSession(providerId, scopes, { activateImmediate: true, sessionToRecreate }); } - this.authenticationService.updateAllowedExtension(providerId, session.account.label, extensionId, extensionName, true); - this.authenticationService.updateSessionPreference(providerId, extensionId, session); + this.authenticationAccessService.updateAllowedExtensions(providerId, session.account.label, [{ id: extensionId, name: extensionName, allowed: true }]); + this.authenticationExtensionsService.updateSessionPreference(providerId, extensionId, session); return session; } // For the silent flows, if we have a session, even though it may not be the user's preference, we'll return it anyway because it might be for a specific // set of scopes. - const validSession = sessions.find(session => this.authenticationService.isAccessAllowed(providerId, session.account.label, extensionId)); + const validSession = sessions.find(session => this.authenticationAccessService.isAccessAllowed(providerId, session.account.label, extensionId)); if (validSession) { - // Migration. If we have a valid session, but no preference, we'll set the preference to the valid session. - // TODO: Remove this after in a few releases. - if (!this.authenticationService.getSessionPreference(providerId, extensionId, scopes)) { - if (this.storageService.get(`${extensionName}-${providerId}`, StorageScope.APPLICATION)) { - this.storageService.remove(`${extensionName}-${providerId}`, StorageScope.APPLICATION); - } - this.authenticationService.updateAllowedExtension(providerId, validSession.account.label, extensionId, extensionName, true); - this.authenticationService.updateSessionPreference(providerId, extensionId, validSession); - } return validSession; } @@ -207,8 +199,8 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu // If there is a potential session, but the extension doesn't have access to it, use the "grant access" flow, // otherwise request a new one. sessions.length - ? this.authenticationService.requestSessionAccess(providerId, extensionId, extensionName, scopes, sessions) - : await this.authenticationService.requestNewSession(providerId, scopes, extensionId, extensionName); + ? this.authenticationExtensionsService.requestSessionAccess(providerId, extensionId, extensionName, scopes, sessions) + : await this.authenticationExtensionsService.requestNewSession(providerId, scopes, extensionId, extensionName); } return undefined; } @@ -218,7 +210,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu if (session) { this.sendProviderUsageTelemetry(extensionId, providerId); - addAccountUsage(this.storageService, providerId, session.account.label, extensionId, extensionName); + this.authenticationUsageService.addAccountUsage(providerId, session.account.label, extensionId, extensionName); } return session; @@ -226,11 +218,11 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu async $getSessions(providerId: string, scopes: readonly string[], extensionId: string, extensionName: string): Promise { const sessions = await this.authenticationService.getSessions(providerId, [...scopes], true); - const accessibleSessions = sessions.filter(s => this.authenticationService.isAccessAllowed(providerId, s.account.label, extensionId)); + const accessibleSessions = sessions.filter(s => this.authenticationAccessService.isAccessAllowed(providerId, s.account.label, extensionId)); if (accessibleSessions.length) { this.sendProviderUsageTelemetry(extensionId, providerId); for (const session of accessibleSessions) { - addAccountUsage(this.storageService, providerId, session.account.label, extensionId, extensionName); + this.authenticationUsageService.addAccountUsage(providerId, session.account.label, extensionId, extensionName); } } return accessibleSessions; diff --git a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts index a4713d12239..9a886a6713a 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts @@ -14,6 +14,7 @@ import { IProgress, Progress } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; import { ExtHostLanguageModelsShape, ExtHostContext, MainContext, MainThreadLanguageModelsShape } from 'vs/workbench/api/common/extHost.protocol'; import { ILanguageModelChatMetadata, IChatResponseFragment, ILanguageModelsService, IChatMessage } from 'vs/workbench/contrib/chat/common/languageModels'; +import { IAuthenticationAccessService } from 'vs/workbench/services/authentication/browser/authenticationAccessService'; import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationProviderCreateSessionOptions, IAuthenticationService, INTERNAL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication'; import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; @@ -33,6 +34,7 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { @IExtensionFeaturesManagementService private readonly _extensionFeaturesManagementService: IExtensionFeaturesManagementService, @ILogService private readonly _logService: ILogService, @IAuthenticationService private readonly _authenticationService: IAuthenticationService, + @IAuthenticationAccessService private readonly _authenticationAccessService: IAuthenticationAccessService, @IExtensionService private readonly _extensionService: IExtensionService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatProvider); @@ -132,28 +134,8 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { disposables.add(toDisposable(() => { this._authenticationService.unregisterAuthenticationProvider(authProviderId); })); - disposables.add(this._authenticationService.onDidChangeSessions(async (e) => { - if (e.providerId === authProviderId) { - if (e.event.removed?.length) { - const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, accountLabel); - const extensionsToUpdateAccess = []; - for (const allowed of allowedExtensions) { - const from = await this._extensionService.getExtension(allowed.id); - this._authenticationService.updateAllowedExtension(authProviderId, authProviderId, allowed.id, allowed.name, false); - if (from) { - extensionsToUpdateAccess.push({ - from: from.identifier, - to: extension, - enabled: false - }); - } - } - this._proxy.$updateModelAccesslist(extensionsToUpdateAccess); - } - } - })); - disposables.add(this._authenticationService.onDidChangeExtensionSessionAccess(async (e) => { - const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, accountLabel); + disposables.add(this._authenticationAccessService.onDidChangeExtensionSessionAccess(async (e) => { + const allowedExtensions = this._authenticationAccessService.readAllowedExtensions(authProviderId, accountLabel); const accessList = []; for (const allowedExtension of allowedExtensions) { const from = await this._extensionService.getExtension(allowedExtension.id); diff --git a/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts b/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts index 5e4e8ed1510..dd77886bbf0 100644 --- a/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts +++ b/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts @@ -19,7 +19,7 @@ import { ExtHostContext, MainContext } from 'vs/workbench/api/common/extHost.pro import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; import { IActivityService } from 'vs/workbench/services/activity/common/activity'; import { AuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; -import { IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; +import { IAuthenticationExtensionsService, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; import { IExtensionService, nullExtensionDescription as extensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { TestRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; @@ -28,6 +28,9 @@ import { TestActivityService, TestExtensionService, TestProductService, TestStor import type { AuthenticationProvider, AuthenticationSession } from 'vscode'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IProductService } from 'vs/platform/product/common/productService'; +import { AuthenticationAccessService, IAuthenticationAccessService } from 'vs/workbench/services/authentication/browser/authenticationAccessService'; +import { AuthenticationUsageService, IAuthenticationUsageService } from 'vs/workbench/services/authentication/browser/authenticationUsageService'; +import { AuthenticationExtensionsService } from 'vs/workbench/services/authentication/browser/authenticationExtensionsService'; class AuthQuickPick { private listener: ((e: IQuickPickDidAcceptEvent) => any) | undefined; @@ -113,9 +116,12 @@ suite('ExtHostAuthentication', () => { instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IBrowserWorkbenchEnvironmentService, TestEnvironmentService); instantiationService.stub(IProductService, TestProductService); + instantiationService.stub(IAuthenticationAccessService, instantiationService.createInstance(AuthenticationAccessService)); + instantiationService.stub(IAuthenticationUsageService, instantiationService.createInstance(AuthenticationUsageService)); const rpcProtocol = new TestRPCProtocol(); instantiationService.stub(IAuthenticationService, instantiationService.createInstance(AuthenticationService)); + instantiationService.stub(IAuthenticationExtensionsService, instantiationService.createInstance(AuthenticationExtensionsService)); rpcProtocol.set(MainContext.MainThreadAuthentication, instantiationService.createInstance(MainThreadAuthentication, rpcProtocol)); extHostAuthentication = new ExtHostAuthentication(rpcProtocol); rpcProtocol.set(ExtHostContext.ExtHostAuthentication, extHostAuthentication); diff --git a/src/vs/workbench/browser/parts/globalCompositeBar.ts b/src/vs/workbench/browser/parts/globalCompositeBar.ts index 3490432e312..8301e27c643 100644 --- a/src/vs/workbench/browser/parts/globalCompositeBar.ts +++ b/src/vs/workbench/browser/parts/globalCompositeBar.ts @@ -43,6 +43,7 @@ import { isString } from 'vs/base/common/types'; import { KeyCode } from 'vs/base/common/keyCodes'; import { ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND } from 'vs/workbench/common/theme'; import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { ICommandService } from 'vs/platform/commands/common/commands'; export class GlobalCompositeBar extends Disposable { @@ -309,6 +310,7 @@ export class AccountsActivityActionViewItem extends AbstractGlobalActivityAction @ILogService private readonly logService: ILogService, @IActivityService activityService: IActivityService, @IInstantiationService instantiationService: IInstantiationService, + @ICommandService private readonly commandService: ICommandService ) { const action = instantiationService.createInstance(CompositeBarAction, { id: ACCOUNTS_ACTIVITY_ID, @@ -391,7 +393,7 @@ export class AccountsActivityActionViewItem extends AbstractGlobalActivityAction menus.push(noAccountsAvailableAction); break; } - const providerLabel = this.authenticationService.getLabel(providerId); + const providerLabel = this.authenticationService.getProvider(providerId).label; const accounts = this.groupedAccounts.get(providerId); if (!accounts) { if (this.problematicProviders.has(providerId)) { @@ -408,19 +410,22 @@ export class AccountsActivityActionViewItem extends AbstractGlobalActivityAction } for (const account of accounts) { - const manageExtensionsAction = disposables.add(new Action(`configureSessions${account.label}`, localize('manageTrustedExtensions', "Manage Trusted Extensions"), undefined, true, () => { - return this.authenticationService.manageTrustedExtensionsForAccount(providerId, account.label); - })); + const manageExtensionsAction = toAction({ + id: `configureSessions${account.label}`, + label: localize('manageTrustedExtensions', "Manage Trusted Extensions"), + enabled: true, + run: () => this.commandService.executeCommand('_manageTrustedExtensionsForAccount', { providerId, accountLabel: account.label }) + }); - const providerSubMenuActions: Action[] = [manageExtensionsAction]; + const providerSubMenuActions: IAction[] = [manageExtensionsAction]; if (account.canSignOut) { - const signOutAction = disposables.add(new Action('signOut', localize('signOut', "Sign Out"), undefined, true, async () => { - const allSessions = await this.authenticationService.getSessions(providerId); - const sessionsForAccount = allSessions.filter(s => s.account.label === account.label); - return await this.authenticationService.removeAccountSessions(providerId, account.label, sessionsForAccount); + providerSubMenuActions.push(toAction({ + id: 'signOut', + label: localize('signOut', "Sign Out"), + enabled: true, + run: () => this.commandService.executeCommand('_signOutOfAccount', { providerId, accountLabel: account.label }) })); - providerSubMenuActions.push(signOutAction); } const providerSubMenu = new SubmenuAction('activitybar.submenu', `${account.label} (${providerLabel})`, providerSubMenuActions); @@ -628,7 +633,8 @@ export class SimpleAccountActivityActionViewItem extends AccountsActivityActionV @ISecretStorageService secretStorageService: ISecretStorageService, @ILogService logService: ILogService, @IActivityService activityService: IActivityService, - @IInstantiationService instantiationService: IInstantiationService + @IInstantiationService instantiationService: IInstantiationService, + @ICommandService commandService: ICommandService ) { super(() => [], { ...options, @@ -638,7 +644,7 @@ export class SimpleAccountActivityActionViewItem extends AccountsActivityActionV }), hoverOptions, compact: true, - }, () => undefined, actions => actions, themeService, lifecycleService, hoverService, contextMenuService, menuService, contextKeyService, authenticationService, environmentService, productService, configurationService, keybindingService, secretStorageService, logService, activityService, instantiationService); + }, () => undefined, actions => actions, themeService, lifecycleService, hoverService, contextMenuService, menuService, contextKeyService, authenticationService, environmentService, productService, configurationService, keybindingService, secretStorageService, logService, activityService, instantiationService, commandService); } } diff --git a/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts b/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts new file mode 100644 index 00000000000..6559535304c --- /dev/null +++ b/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts @@ -0,0 +1,168 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { fromNow } from 'vs/base/common/date'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; +import { Action2 } from 'vs/platform/actions/common/actions'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IAuthenticationAccessService } from 'vs/workbench/services/authentication/browser/authenticationAccessService'; +import { IAuthenticationUsageService } from 'vs/workbench/services/authentication/browser/authenticationUsageService'; +import { AllowedExtension } from 'vs/workbench/services/authentication/common/authentication'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; + +export class ManageTrustedExtensionsForAccountAction extends Action2 { + constructor() { + super({ + id: '_manageTrustedExtensionsForAccount', + title: localize('manageTrustedExtensionsForAccount', "Manage Trusted Extensions For Account"), + f1: false + }); + } + + override async run(accessor: ServicesAccessor, { providerId, accountLabel }: { providerId: string; accountLabel: string }): Promise { + const productService = accessor.get(IProductService); + const extensionService = accessor.get(IExtensionService); + const dialogService = accessor.get(IDialogService); + const quickInputService = accessor.get(IQuickInputService); + const authenticationUsageService = accessor.get(IAuthenticationUsageService); + const authenticationAccessService = accessor.get(IAuthenticationAccessService); + + if (!providerId || !accountLabel) { + throw new Error('Invalid arguments. Expected: { providerId: string; accountLabel: string }'); + } + + const allowedExtensions = authenticationAccessService.readAllowedExtensions(providerId, accountLabel); + const trustedExtensionAuthAccess = productService.trustedExtensionAuthAccess; + const trustedExtensionIds = + // Case 1: trustedExtensionAuthAccess is an array + Array.isArray(trustedExtensionAuthAccess) + ? trustedExtensionAuthAccess + // Case 2: trustedExtensionAuthAccess is an object + : typeof trustedExtensionAuthAccess === 'object' + ? trustedExtensionAuthAccess[providerId] ?? [] + : []; + for (const extensionId of trustedExtensionIds) { + const allowedExtension = allowedExtensions.find(ext => ext.id === extensionId); + if (!allowedExtension) { + // Add the extension to the allowedExtensions list + const extension = await extensionService.getExtension(extensionId); + if (extension) { + allowedExtensions.push({ + id: extensionId, + name: extension.displayName || extension.name, + allowed: true, + trusted: true + }); + } + } else { + // Update the extension to be allowed + allowedExtension.allowed = true; + allowedExtension.trusted = true; + } + } + + if (!allowedExtensions.length) { + dialogService.info(localize('noTrustedExtensions', "This account has not been used by any extensions.")); + return; + } + + interface TrustedExtensionsQuickPickItem extends IQuickPickItem { + extension: AllowedExtension; + lastUsed?: number; + } + + const disposableStore = new DisposableStore(); + const quickPick = disposableStore.add(quickInputService.createQuickPick()); + quickPick.canSelectMany = true; + quickPick.customButton = true; + quickPick.customLabel = localize('manageTrustedExtensions.cancel', 'Cancel'); + const usages = authenticationUsageService.readAccountUsages(providerId, accountLabel); + const trustedExtensions = []; + const otherExtensions = []; + for (const extension of allowedExtensions) { + const usage = usages.find(usage => extension.id === usage.extensionId); + extension.lastUsed = usage?.lastUsed; + if (extension.trusted) { + trustedExtensions.push(extension); + } else { + otherExtensions.push(extension); + } + } + + const sortByLastUsed = (a: AllowedExtension, b: AllowedExtension) => (b.lastUsed || 0) - (a.lastUsed || 0); + const toQuickPickItem = function (extension: AllowedExtension) { + const lastUsed = extension.lastUsed; + const description = lastUsed + ? localize({ key: 'accountLastUsedDate', comment: ['The placeholder {0} is a string with time information, such as "3 days ago"'] }, "Last used this account {0}", fromNow(lastUsed, true)) + : localize('notUsed', "Has not used this account"); + let tooltip: string | undefined; + if (extension.trusted) { + tooltip = localize('trustedExtensionTooltip', "This extension is trusted by Microsoft and\nalways has access to this account"); + } + return { + label: extension.name, + extension, + description, + tooltip + }; + }; + const items: Array = [ + ...otherExtensions.sort(sortByLastUsed).map(toQuickPickItem), + { type: 'separator', label: localize('trustedExtensions', "Trusted by Microsoft") }, + ...trustedExtensions.sort(sortByLastUsed).map(toQuickPickItem) + ]; + + quickPick.items = items; + quickPick.selectedItems = items.filter((item): item is TrustedExtensionsQuickPickItem => item.type !== 'separator' && (item.extension.allowed === undefined || item.extension.allowed)); + quickPick.title = localize('manageTrustedExtensions', "Manage Trusted Extensions"); + quickPick.placeholder = localize('manageExtensions', "Choose which extensions can access this account"); + + disposableStore.add(quickPick.onDidAccept(() => { + const updatedAllowedList = quickPick.items + .filter((item): item is TrustedExtensionsQuickPickItem => item.type !== 'separator') + .map(i => i.extension); + authenticationAccessService.updateAllowedExtensions(providerId, accountLabel, updatedAllowedList); + quickPick.hide(); + })); + + disposableStore.add(quickPick.onDidChangeSelection((changed) => { + const trustedItems = new Set(); + quickPick.items.forEach(item => { + const trustItem = item as TrustedExtensionsQuickPickItem; + if (trustItem.extension) { + if (trustItem.extension.trusted) { + trustedItems.add(trustItem); + } else { + trustItem.extension.allowed = false; + } + } + }); + changed.forEach((item) => { + item.extension.allowed = true; + trustedItems.delete(item); + }); + + // reselect trusted items if a user tried to unselect one since quick pick doesn't support forcing selection + if (trustedItems.size) { + quickPick.selectedItems = [...changed, ...trustedItems]; + } + })); + + disposableStore.add(quickPick.onDidHide(() => { + disposableStore.dispose(); + })); + + disposableStore.add(quickPick.onDidCustom(() => { + quickPick.hide(); + })); + + quickPick.show(); + } + +} diff --git a/src/vs/workbench/contrib/authentication/browser/actions/signOutOfAccountAction.ts b/src/vs/workbench/contrib/authentication/browser/actions/signOutOfAccountAction.ts new file mode 100644 index 00000000000..87afd379e24 --- /dev/null +++ b/src/vs/workbench/contrib/authentication/browser/actions/signOutOfAccountAction.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import Severity from 'vs/base/common/severity'; +import { localize } from 'vs/nls'; +import { Action2 } from 'vs/platform/actions/common/actions'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IAuthenticationAccessService } from 'vs/workbench/services/authentication/browser/authenticationAccessService'; +import { IAuthenticationUsageService } from 'vs/workbench/services/authentication/browser/authenticationUsageService'; +import { IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; + +export class SignOutOfAccountAction extends Action2 { + constructor() { + super({ + id: '_signOutOfAccount', + title: localize('signOutOfAccount', "Sign out of account"), + f1: false + }); + } + + override async run(accessor: ServicesAccessor, { providerId, accountLabel }: { providerId: string; accountLabel: string }): Promise { + const authenticationService = accessor.get(IAuthenticationService); + const authenticationUsageService = accessor.get(IAuthenticationUsageService); + const authenticationAccessService = accessor.get(IAuthenticationAccessService); + const dialogService = accessor.get(IDialogService); + + if (!providerId || !accountLabel) { + throw new Error('Invalid arguments. Expected: { providerId: string; accountLabel: string }'); + } + + const allSessions = await authenticationService.getSessions(providerId); + const sessions = allSessions.filter(s => s.account.label === accountLabel); + + const accountUsages = authenticationUsageService.readAccountUsages(providerId, accountLabel); + + const { confirmed } = await dialogService.confirm({ + type: Severity.Info, + message: accountUsages.length + ? localize('signOutMessage', "The account '{0}' has been used by: \n\n{1}\n\n Sign out from these extensions?", accountLabel, accountUsages.map(usage => usage.extensionName).join('\n')) + : localize('signOutMessageSimple', "Sign out of '{0}'?", accountLabel), + primaryButton: localize({ key: 'signOut', comment: ['&& denotes a mnemonic'] }, "&&Sign Out") + }); + + if (confirmed) { + const removeSessionPromises = sessions.map(session => authenticationService.removeSession(providerId, session.id)); + await Promise.all(removeSessionPromises); + authenticationUsageService.removeAccountUsage(providerId, accountLabel); + authenticationAccessService.removeAllowedExtensions(providerId, accountLabel); + } + } +} diff --git a/src/vs/workbench/contrib/authentication/browser/authentication.contribution.ts b/src/vs/workbench/contrib/authentication/browser/authentication.contribution.ts new file mode 100644 index 00000000000..90649d2f358 --- /dev/null +++ b/src/vs/workbench/contrib/authentication/browser/authentication.contribution.ts @@ -0,0 +1,197 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { isFalsyOrWhitespace } from 'vs/base/common/strings'; +import { localize } from 'vs/nls'; +import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { SignOutOfAccountAction } from 'vs/workbench/contrib/authentication/browser/actions/signOutOfAccountAction'; +import { AuthenticationProviderInformation, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; +import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; +import { Extensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, IRenderedData, IRowData, ITableData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { ManageTrustedExtensionsForAccountAction } from './actions/manageTrustedExtensionsForAccountAction'; + +const codeExchangeProxyCommand = CommandsRegistry.registerCommand('workbench.getCodeExchangeProxyEndpoints', function (accessor, _) { + const environmentService = accessor.get(IBrowserWorkbenchEnvironmentService); + return environmentService.options?.codeExchangeProxyEndpoints; +}); + +const authenticationDefinitionSchema: IJSONSchema = { + type: 'object', + additionalProperties: false, + properties: { + id: { + type: 'string', + description: localize('authentication.id', 'The id of the authentication provider.') + }, + label: { + type: 'string', + description: localize('authentication.label', 'The human readable name of the authentication provider.'), + } + } +}; + +const authenticationExtPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'authentication', + jsonSchema: { + description: localize({ key: 'authenticationExtensionPoint', comment: [`'Contributes' means adds here`] }, 'Contributes authentication'), + type: 'array', + items: authenticationDefinitionSchema + }, + activationEventsGenerator: (authenticationProviders, result) => { + for (const authenticationProvider of authenticationProviders) { + if (authenticationProvider.id) { + result.push(`onAuthenticationRequest:${authenticationProvider.id}`); + } + } + } +}); + +class AuthenticationDataRenderer extends Disposable implements IExtensionFeatureTableRenderer { + + readonly type = 'table'; + + shouldRender(manifest: IExtensionManifest): boolean { + return !!manifest.contributes?.authentication; + } + + render(manifest: IExtensionManifest): IRenderedData { + const authentication = manifest.contributes?.authentication || []; + if (!authentication.length) { + return { data: { headers: [], rows: [] }, dispose: () => { } }; + } + + const headers = [ + localize('authenticationlabel', "Label"), + localize('authenticationid', "ID"), + ]; + + const rows: IRowData[][] = authentication + .sort((a, b) => a.label.localeCompare(b.label)) + .map(auth => { + return [ + auth.label, + auth.id, + ]; + }); + + return { + data: { + headers, + rows + }, + dispose: () => { } + }; + } +} + +const extensionFeature = Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'authentication', + label: localize('authentication', "Authentication"), + access: { + canToggle: false + }, + renderer: new SyncDescriptor(AuthenticationDataRenderer), +}); + +export class AuthenticationContribution extends Disposable implements IWorkbenchContribution { + static ID = 'workbench.contrib.authentication'; + + private _placeholderMenuItem: IDisposable | undefined = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { + command: { + id: 'noAuthenticationProviders', + title: localize('authentication.Placeholder', "No accounts requested yet..."), + precondition: ContextKeyExpr.false() + }, + }); + + constructor( + @IAuthenticationService private readonly _authenticationService: IAuthenticationService, + @IBrowserWorkbenchEnvironmentService private readonly _environmentService: IBrowserWorkbenchEnvironmentService + ) { + super(); + this._register(codeExchangeProxyCommand); + this._register(extensionFeature); + + this._registerHandlers(); + this._registerAuthenticationExtentionPointHandler(); + this._registerEnvContributedAuthenticationProviders(); + this._registerActions(); + } + + private _registerAuthenticationExtentionPointHandler(): void { + authenticationExtPoint.setHandler((extensions, { added, removed }) => { + added.forEach(point => { + for (const provider of point.value) { + if (isFalsyOrWhitespace(provider.id)) { + point.collector.error(localize('authentication.missingId', 'An authentication contribution must specify an id.')); + continue; + } + + if (isFalsyOrWhitespace(provider.label)) { + point.collector.error(localize('authentication.missingLabel', 'An authentication contribution must specify a label.')); + continue; + } + + if (!this._authenticationService.declaredProviders.some(p => p.id === provider.id)) { + this._authenticationService.registerDeclaredAuthenticationProvider(provider); + } else { + point.collector.error(localize('authentication.idConflict', "This authentication id '{0}' has already been registered", provider.id)); + } + } + }); + + const removedExtPoints = removed.flatMap(r => r.value); + removedExtPoints.forEach(point => { + const provider = this._authenticationService.declaredProviders.find(provider => provider.id === point.id); + if (provider) { + this._authenticationService.unregisterDeclaredAuthenticationProvider(provider.id); + } + }); + }); + } + + private _registerEnvContributedAuthenticationProviders(): void { + if (!this._environmentService.options?.authenticationProviders?.length) { + return; + } + for (const provider of this._environmentService.options.authenticationProviders) { + this._authenticationService.registerAuthenticationProvider(provider.id, provider); + } + } + + private _registerHandlers(): void { + this._register(this._authenticationService.onDidRegisterAuthenticationProvider(_e => { + this._placeholderMenuItem?.dispose(); + this._placeholderMenuItem = undefined; + })); + this._register(this._authenticationService.onDidUnregisterAuthenticationProvider(_e => { + if (!this._authenticationService.getProviderIds().length) { + this._placeholderMenuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { + command: { + id: 'noAuthenticationProviders', + title: localize('loading', "Loading..."), + precondition: ContextKeyExpr.false() + } + }); + } + })); + } + + private _registerActions(): void { + registerAction2(SignOutOfAccountAction); + registerAction2(ManageTrustedExtensionsForAccountAction); + } +} + +registerWorkbenchContribution2(AuthenticationContribution.ID, AuthenticationContribution, WorkbenchPhase.AfterRestored); diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts b/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts index 6ba3511f0ee..f612c22c3e3 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts @@ -346,8 +346,8 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes for (const authenticationProvider of (await this.getAuthenticationProviders())) { const signedInForProvider = sessions.some(account => account.session.providerId === authenticationProvider.id); - if (!signedInForProvider || this.authenticationService.supportsMultipleAccounts(authenticationProvider.id)) { - const providerName = this.authenticationService.getLabel(authenticationProvider.id); + if (!signedInForProvider || this.authenticationService.getProvider(authenticationProvider.id).supportsMultipleAccounts) { + const providerName = this.authenticationService.getProvider(authenticationProvider.id).label; options.push({ label: localize('sign in using account', "Sign in with {0}", providerName), provider: authenticationProvider }); } } @@ -370,7 +370,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes for (const session of sessions) { const item = { label: session.account.label, - description: this.authenticationService.getLabel(provider.id), + description: this.authenticationService.getProvider(provider.id).label, session: { ...session, providerId: provider.id } }; accounts.set(item.session.account.id, item); diff --git a/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts b/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts index 305f9bae1c5..2afacd74992 100644 --- a/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts +++ b/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts @@ -395,7 +395,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo private createExistingSessionItem(session: AuthenticationSession, providerId: string): ExistingSessionItem { return { label: session.account.label, - description: this.authenticationService.getLabel(providerId), + description: this.authenticationService.getProvider(providerId).label, session, providerId }; @@ -412,9 +412,9 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo for (const authenticationProvider of (await this.getAuthenticationProviders())) { const signedInForProvider = sessions.some(account => account.providerId === authenticationProvider.id); - if (!signedInForProvider || this.authenticationService.supportsMultipleAccounts(authenticationProvider.id)) { - const providerName = this.authenticationService.getLabel(authenticationProvider.id); - options.push({ label: localize({ key: 'sign in using account', comment: ['{0} will be a auth provider (e.g. Github)'] }, "Sign in with {0}", providerName), provider: authenticationProvider }); + const provider = this.authenticationService.getProvider(authenticationProvider.id); + if (!signedInForProvider || provider.supportsMultipleAccounts) { + options.push({ label: localize({ key: 'sign in using account', comment: ['{0} will be a auth provider (e.g. Github)'] }, "Sign in with {0}", provider.label), provider: authenticationProvider }); } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 0787368edba..08c30bd1f30 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -918,7 +918,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo items.push({ id: syncNowCommand.id, label: `${SYNC_TITLE.value}: ${syncNowCommand.title.original}`, description: syncNowCommand.description(that.userDataSyncService) }); if (that.userDataSyncEnablementService.canToggleEnablement()) { const account = that.userDataSyncWorkbenchService.current; - items.push({ id: turnOffSyncCommand.id, label: `${SYNC_TITLE.value}: ${turnOffSyncCommand.title.original}`, description: account ? `${account.accountName} (${that.authenticationService.getLabel(account.authenticationProviderId)})` : undefined }); + items.push({ id: turnOffSyncCommand.id, label: `${SYNC_TITLE.value}: ${turnOffSyncCommand.title.original}`, description: account ? `${account.accountName} (${that.authenticationService.getProvider(account.authenticationProviderId).label})` : undefined }); } quickPick.items = items; disposables.add(quickPick.onDidAccept(() => { diff --git a/src/vs/workbench/services/authentication/browser/authenticationAccessService.ts b/src/vs/workbench/services/authentication/browser/authenticationAccessService.ts new file mode 100644 index 00000000000..565821fcb50 --- /dev/null +++ b/src/vs/workbench/services/authentication/browser/authenticationAccessService.ts @@ -0,0 +1,101 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { AllowedExtension } from 'vs/workbench/services/authentication/common/authentication'; + +export const IAuthenticationAccessService = createDecorator('IAuthenticationAccessService'); +export interface IAuthenticationAccessService { + readonly _serviceBrand: undefined; + + readonly onDidChangeExtensionSessionAccess: Event<{ providerId: string; accountName: string }>; + + /** + * Check extension access to an account + * @param providerId The id of the authentication provider + * @param accountName The account name that access is checked for + * @param extensionId The id of the extension requesting access + * @returns Returns true or false if the user has opted to permanently grant or disallow access, and undefined + * if they haven't made a choice yet + */ + isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean | undefined; + readAllowedExtensions(providerId: string, accountName: string): AllowedExtension[]; + updateAllowedExtensions(providerId: string, accountName: string, extensions: AllowedExtension[]): void; + removeAllowedExtensions(providerId: string, accountName: string): void; +} + +// TODO@TylerLeonhardt: Move this class to MainThreadAuthentication +export class AuthenticationAccessService extends Disposable implements IAuthenticationAccessService { + _serviceBrand: undefined; + + private _onDidChangeExtensionSessionAccess: Emitter<{ providerId: string; accountName: string }> = this._register(new Emitter<{ providerId: string; accountName: string }>()); + readonly onDidChangeExtensionSessionAccess: Event<{ providerId: string; accountName: string }> = this._onDidChangeExtensionSessionAccess.event; + + constructor( + @IStorageService private readonly _storageService: IStorageService, + @IProductService private readonly _productService: IProductService + ) { + super(); + } + + isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean | undefined { + const trustedExtensionAuthAccess = this._productService.trustedExtensionAuthAccess; + if (Array.isArray(trustedExtensionAuthAccess)) { + if (trustedExtensionAuthAccess.includes(extensionId)) { + return true; + } + } else if (trustedExtensionAuthAccess?.[providerId]?.includes(extensionId)) { + return true; + } + + const allowList = this.readAllowedExtensions(providerId, accountName); + const extensionData = allowList.find(extension => extension.id === extensionId); + if (!extensionData) { + return undefined; + } + // This property didn't exist on this data previously, inclusion in the list at all indicates allowance + return extensionData.allowed !== undefined + ? extensionData.allowed + : true; + } + + readAllowedExtensions(providerId: string, accountName: string): AllowedExtension[] { + let trustedExtensions: AllowedExtension[] = []; + try { + const trustedExtensionSrc = this._storageService.get(`${providerId}-${accountName}`, StorageScope.APPLICATION); + if (trustedExtensionSrc) { + trustedExtensions = JSON.parse(trustedExtensionSrc); + } + } catch (err) { } + + return trustedExtensions; + } + + updateAllowedExtensions(providerId: string, accountName: string, extensions: AllowedExtension[]): void { + const allowList = this.readAllowedExtensions(providerId, accountName); + for (const extension of extensions) { + const index = allowList.findIndex(e => e.id === extension.id); + if (index === -1) { + allowList.push(extension); + } else { + allowList[index].allowed = extension.allowed; + } + } + this._storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.APPLICATION, StorageTarget.USER); + this._onDidChangeExtensionSessionAccess.fire({ providerId, accountName }); + } + + removeAllowedExtensions(providerId: string, accountName: string): void { + this._storageService.remove(`${providerId}-${accountName}`, StorageScope.APPLICATION); + this._onDidChangeExtensionSessionAccess.fire({ providerId, accountName }); + } +} + +registerSingleton(IAuthenticationAccessService, AuthenticationAccessService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/authentication/browser/authenticationExtensionsService.ts b/src/vs/workbench/services/authentication/browser/authenticationExtensionsService.ts new file mode 100644 index 00000000000..eaec30e9f18 --- /dev/null +++ b/src/vs/workbench/services/authentication/browser/authenticationExtensionsService.ts @@ -0,0 +1,418 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, dispose, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import * as nls from 'vs/nls'; +import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Severity } from 'vs/platform/notification/common/notification'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; +import { IAuthenticationAccessService } from 'vs/workbench/services/authentication/browser/authenticationAccessService'; +import { IAuthenticationUsageService } from 'vs/workbench/services/authentication/browser/authenticationUsageService'; +import { AuthenticationSession, IAuthenticationProvider, IAuthenticationService, IAuthenticationExtensionsService } from 'vs/workbench/services/authentication/common/authentication'; + +// OAuth2 spec prohibits space in a scope, so use that to join them. +const SCOPESLIST_SEPARATOR = ' '; + +interface SessionRequest { + disposables: IDisposable[]; + requestingExtensionIds: string[]; +} + +interface SessionRequestInfo { + [scopesList: string]: SessionRequest; +} + +// TODO@TylerLeonhardt: This should all go in MainThreadAuthentication +export class AuthenticationExtensionsService extends Disposable implements IAuthenticationExtensionsService { + declare readonly _serviceBrand: undefined; + private _signInRequestItems = new Map(); + private _sessionAccessRequestItems = new Map(); + private _accountBadgeDisposable = this._register(new MutableDisposable()); + + constructor( + @IActivityService private readonly activityService: IActivityService, + @IStorageService private readonly storageService: IStorageService, + @IDialogService private readonly dialogService: IDialogService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IAuthenticationService private readonly _authenticationService: IAuthenticationService, + @IAuthenticationUsageService private readonly _authenticationUsageService: IAuthenticationUsageService, + @IAuthenticationAccessService private readonly _authenticationAccessService: IAuthenticationAccessService + ) { + super(); + this.registerListeners(); + } + + private registerListeners() { + this._register(this._authenticationService.onDidChangeSessions(async e => { + if (e.event.added?.length) { + await this.updateNewSessionRequests(e.providerId, e.event.added); + } + if (e.event.removed?.length) { + await this.updateAccessRequests(e.providerId, e.event.removed); + } + this.updateBadgeCount(); + })); + + this._register(this._authenticationService.onDidUnregisterAuthenticationProvider(e => { + const accessRequests = this._sessionAccessRequestItems.get(e.id) || {}; + Object.keys(accessRequests).forEach(extensionId => { + this.removeAccessRequest(e.id, extensionId); + }); + })); + } + + private async updateNewSessionRequests(providerId: string, addedSessions: readonly AuthenticationSession[]): Promise { + const existingRequestsForProvider = this._signInRequestItems.get(providerId); + if (!existingRequestsForProvider) { + return; + } + + Object.keys(existingRequestsForProvider).forEach(requestedScopes => { + if (addedSessions.some(session => session.scopes.slice().join(SCOPESLIST_SEPARATOR) === requestedScopes)) { + const sessionRequest = existingRequestsForProvider[requestedScopes]; + sessionRequest?.disposables.forEach(item => item.dispose()); + + delete existingRequestsForProvider[requestedScopes]; + if (Object.keys(existingRequestsForProvider).length === 0) { + this._signInRequestItems.delete(providerId); + } else { + this._signInRequestItems.set(providerId, existingRequestsForProvider); + } + } + }); + } + + private async updateAccessRequests(providerId: string, removedSessions: readonly AuthenticationSession[]) { + const providerRequests = this._sessionAccessRequestItems.get(providerId); + if (providerRequests) { + Object.keys(providerRequests).forEach(extensionId => { + removedSessions.forEach(removed => { + const indexOfSession = providerRequests[extensionId].possibleSessions.findIndex(session => session.id === removed.id); + if (indexOfSession) { + providerRequests[extensionId].possibleSessions.splice(indexOfSession, 1); + } + }); + + if (!providerRequests[extensionId].possibleSessions.length) { + this.removeAccessRequest(providerId, extensionId); + } + }); + } + } + + private updateBadgeCount(): void { + this._accountBadgeDisposable.clear(); + + let numberOfRequests = 0; + this._signInRequestItems.forEach(providerRequests => { + Object.keys(providerRequests).forEach(request => { + numberOfRequests += providerRequests[request].requestingExtensionIds.length; + }); + }); + + this._sessionAccessRequestItems.forEach(accessRequest => { + numberOfRequests += Object.keys(accessRequest).length; + }); + + if (numberOfRequests > 0) { + const badge = new NumberBadge(numberOfRequests, () => nls.localize('sign in', "Sign in requested")); + this._accountBadgeDisposable.value = this.activityService.showAccountsActivity({ badge }); + } + } + + private removeAccessRequest(providerId: string, extensionId: string): void { + const providerRequests = this._sessionAccessRequestItems.get(providerId) || {}; + if (providerRequests[extensionId]) { + dispose(providerRequests[extensionId].disposables); + delete providerRequests[extensionId]; + this.updateBadgeCount(); + } + } + + //#region Session Preference + + updateSessionPreference(providerId: string, extensionId: string, session: AuthenticationSession): void { + // The 3 parts of this key are important: + // * Extension id: The extension that has a preference + // * Provider id: The provider that the preference is for + // * The scopes: The subset of sessions that the preference applies to + const key = `${extensionId}-${providerId}-${session.scopes.join(' ')}`; + + // Store the preference in the workspace and application storage. This allows new workspaces to + // have a preference set already to limit the number of prompts that are shown... but also allows + // a specific workspace to override the global preference. + this.storageService.store(key, session.id, StorageScope.WORKSPACE, StorageTarget.MACHINE); + this.storageService.store(key, session.id, StorageScope.APPLICATION, StorageTarget.MACHINE); + } + + getSessionPreference(providerId: string, extensionId: string, scopes: string[]): string | undefined { + // The 3 parts of this key are important: + // * Extension id: The extension that has a preference + // * Provider id: The provider that the preference is for + // * The scopes: The subset of sessions that the preference applies to + const key = `${extensionId}-${providerId}-${scopes.join(' ')}`; + + // If a preference is set in the workspace, use that. Otherwise, use the global preference. + return this.storageService.get(key, StorageScope.WORKSPACE) ?? this.storageService.get(key, StorageScope.APPLICATION); + } + + removeSessionPreference(providerId: string, extensionId: string, scopes: string[]): void { + // The 3 parts of this key are important: + // * Extension id: The extension that has a preference + // * Provider id: The provider that the preference is for + // * The scopes: The subset of sessions that the preference applies to + const key = `${extensionId}-${providerId}-${scopes.join(' ')}`; + + // This won't affect any other workspaces that have a preference set, but it will remove the preference + // for this workspace and the global preference. This is only paired with a call to updateSessionPreference... + // so we really don't _need_ to remove them as they are about to be overridden anyway... but it's more correct + // to remove them first... and in case this gets called from somewhere else in the future. + this.storageService.remove(key, StorageScope.WORKSPACE); + this.storageService.remove(key, StorageScope.APPLICATION); + } + + //#endregion + + private async showGetSessionPrompt(provider: IAuthenticationProvider, accountName: string, extensionId: string, extensionName: string): Promise { + enum SessionPromptChoice { + Allow = 0, + Deny = 1, + Cancel = 2 + } + const { result } = await this.dialogService.prompt({ + type: Severity.Info, + message: nls.localize('confirmAuthenticationAccess', "The extension '{0}' wants to access the {1} account '{2}'.", extensionName, provider.label, accountName), + buttons: [ + { + label: nls.localize({ key: 'allow', comment: ['&& denotes a mnemonic'] }, "&&Allow"), + run: () => SessionPromptChoice.Allow + }, + { + label: nls.localize({ key: 'deny', comment: ['&& denotes a mnemonic'] }, "&&Deny"), + run: () => SessionPromptChoice.Deny + } + ], + cancelButton: { + run: () => SessionPromptChoice.Cancel + } + }); + + if (result !== SessionPromptChoice.Cancel) { + this._authenticationAccessService.updateAllowedExtensions(provider.id, accountName, [{ id: extensionId, name: extensionName, allowed: result === SessionPromptChoice.Allow }]); + this.removeAccessRequest(provider.id, extensionId); + } + + return result === SessionPromptChoice.Allow; + } + + async selectSession(providerId: string, extensionId: string, extensionName: string, scopes: string[], availableSessions: AuthenticationSession[]): Promise { + return new Promise((resolve, reject) => { + // This function should be used only when there are sessions to disambiguate. + if (!availableSessions.length) { + reject('No available sessions'); + return; + } + + const quickPick = this.quickInputService.createQuickPick<{ label: string; session?: AuthenticationSession }>(); + quickPick.ignoreFocusOut = true; + const items: { label: string; session?: AuthenticationSession }[] = availableSessions.map(session => { + return { + label: session.account.label, + session: session + }; + }); + + items.push({ + label: nls.localize('useOtherAccount', "Sign in to another account") + }); + + quickPick.items = items; + + quickPick.title = nls.localize( + { + key: 'selectAccount', + comment: ['The placeholder {0} is the name of an extension. {1} is the name of the type of account, such as Microsoft or GitHub.'] + }, + "The extension '{0}' wants to access a {1} account", + extensionName, + this._authenticationService.getProvider(providerId).label); + quickPick.placeholder = nls.localize('getSessionPlateholder', "Select an account for '{0}' to use or Esc to cancel", extensionName); + + quickPick.onDidAccept(async _ => { + const session = quickPick.selectedItems[0].session ?? await this._authenticationService.createSession(providerId, scopes); + const accountName = session.account.label; + + this._authenticationAccessService.updateAllowedExtensions(providerId, accountName, [{ id: extensionId, name: extensionName, allowed: true }]); + this.updateSessionPreference(providerId, extensionId, session); + this.removeAccessRequest(providerId, extensionId); + + quickPick.dispose(); + resolve(session); + }); + + quickPick.onDidHide(_ => { + if (!quickPick.selectedItems[0]) { + reject('User did not consent to account access'); + } + + quickPick.dispose(); + }); + + quickPick.show(); + }); + } + + private async completeSessionAccessRequest(provider: IAuthenticationProvider, extensionId: string, extensionName: string, scopes: string[]): Promise { + const providerRequests = this._sessionAccessRequestItems.get(provider.id) || {}; + const existingRequest = providerRequests[extensionId]; + if (!existingRequest) { + return; + } + + if (!provider) { + return; + } + const possibleSessions = existingRequest.possibleSessions; + + let session: AuthenticationSession | undefined; + if (provider.supportsMultipleAccounts) { + try { + session = await this.selectSession(provider.id, extensionId, extensionName, scopes, possibleSessions); + } catch (_) { + // ignore cancel + } + } else { + const approved = await this.showGetSessionPrompt(provider, possibleSessions[0].account.label, extensionId, extensionName); + if (approved) { + session = possibleSessions[0]; + } + } + + if (session) { + this._authenticationUsageService.addAccountUsage(provider.id, session.account.label, extensionId, extensionName); + } + } + + requestSessionAccess(providerId: string, extensionId: string, extensionName: string, scopes: string[], possibleSessions: AuthenticationSession[]): void { + const providerRequests = this._sessionAccessRequestItems.get(providerId) || {}; + const hasExistingRequest = providerRequests[extensionId]; + if (hasExistingRequest) { + return; + } + + const provider = this._authenticationService.getProvider(providerId); + const menuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { + group: '3_accessRequests', + command: { + id: `${providerId}${extensionId}Access`, + title: nls.localize({ + key: 'accessRequest', + comment: [`The placeholder {0} will be replaced with an authentication provider''s label. {1} will be replaced with an extension name. (1) is to indicate that this menu item contributes to a badge count`] + }, + "Grant access to {0} for {1}... (1)", + provider.label, + extensionName) + } + }); + + const accessCommand = CommandsRegistry.registerCommand({ + id: `${providerId}${extensionId}Access`, + handler: async (accessor) => { + this.completeSessionAccessRequest(provider, extensionId, extensionName, scopes); + } + }); + + providerRequests[extensionId] = { possibleSessions, disposables: [menuItem, accessCommand] }; + this._sessionAccessRequestItems.set(providerId, providerRequests); + this.updateBadgeCount(); + } + + async requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise { + if (!this._authenticationService.isAuthenticationProviderRegistered(providerId)) { + // Activate has already been called for the authentication provider, but it cannot block on registering itself + // since this is sync and returns a disposable. So, wait for registration event to fire that indicates the + // provider is now in the map. + await new Promise((resolve, _) => { + const dispose = this._authenticationService.onDidRegisterAuthenticationProvider(e => { + if (e.id === providerId) { + dispose.dispose(); + resolve(); + } + }); + }); + } + + let provider: IAuthenticationProvider; + try { + provider = this._authenticationService.getProvider(providerId); + } catch (_e) { + return; + } + + const providerRequests = this._signInRequestItems.get(providerId); + const scopesList = scopes.join(SCOPESLIST_SEPARATOR); + const extensionHasExistingRequest = providerRequests + && providerRequests[scopesList] + && providerRequests[scopesList].requestingExtensionIds.includes(extensionId); + + if (extensionHasExistingRequest) { + return; + } + + // Construct a commandId that won't clash with others generated here, nor likely with an extension's command + const commandId = `${providerId}:${extensionId}:signIn${Object.keys(providerRequests || []).length}`; + const menuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { + group: '2_signInRequests', + command: { + id: commandId, + title: nls.localize({ + key: 'signInRequest', + comment: [`The placeholder {0} will be replaced with an authentication provider's label. {1} will be replaced with an extension name. (1) is to indicate that this menu item contributes to a badge count.`] + }, + "Sign in with {0} to use {1} (1)", + provider.label, + extensionName) + } + }); + + const signInCommand = CommandsRegistry.registerCommand({ + id: commandId, + handler: async (accessor) => { + const authenticationService = accessor.get(IAuthenticationService); + const session = await authenticationService.createSession(providerId, scopes); + + this._authenticationAccessService.updateAllowedExtensions(providerId, session.account.label, [{ id: extensionId, name: extensionName, allowed: true }]); + this.updateSessionPreference(providerId, extensionId, session); + } + }); + + + if (providerRequests) { + const existingRequest = providerRequests[scopesList] || { disposables: [], requestingExtensionIds: [] }; + + providerRequests[scopesList] = { + disposables: [...existingRequest.disposables, menuItem, signInCommand], + requestingExtensionIds: [...existingRequest.requestingExtensionIds, extensionId] + }; + this._signInRequestItems.set(providerId, providerRequests); + } else { + this._signInRequestItems.set(providerId, { + [scopesList]: { + disposables: [menuItem, signInCommand], + requestingExtensionIds: [extensionId] + } + }); + } + + this.updateBadgeCount(); + } +} + +registerSingleton(IAuthenticationExtensionsService, AuthenticationExtensionsService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 524b7402f04..6c22b70cd63 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -3,85 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { fromNow } from 'vs/base/common/date'; import { Emitter, Event } from 'vs/base/common/event'; -import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { Disposable, DisposableMap, DisposableStore, dispose, IDisposable, isDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap, DisposableStore, IDisposable, isDisposable } from 'vs/base/common/lifecycle'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { isString } from 'vs/base/common/types'; -import * as nls from 'vs/nls'; -import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { localize } from 'vs/nls'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { Severity } from 'vs/platform/notification/common/notification'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { Registry } from 'vs/platform/registry/common/platform'; import { ISecretStorageService } from 'vs/platform/secrets/common/secrets'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; -import { IAuthenticationCreateSessionOptions, AuthenticationProviderInformation, AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationService, AllowedExtension } from 'vs/workbench/services/authentication/common/authentication'; -import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; -import { IExtensionFeatureTableRenderer, IRenderedData, ITableData, IRowData, IExtensionFeaturesRegistry, Extensions } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { IAuthenticationAccessService } from 'vs/workbench/services/authentication/browser/authenticationAccessService'; +import { AuthenticationProviderInformation, AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationCreateSessionOptions, IAuthenticationProvider, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; export function getAuthenticationProviderActivationEvent(id: string): string { return `onAuthenticationRequest:${id}`; } -interface IAccountUsage { - extensionId: string; - extensionName: string; - lastUsed: number; -} - -// TODO: make this account usage stuff a service - -function readAccountUsages(storageService: IStorageService, providerId: string, accountName: string,): IAccountUsage[] { - const accountKey = `${providerId}-${accountName}-usages`; - const storedUsages = storageService.get(accountKey, StorageScope.APPLICATION); - let usages: IAccountUsage[] = []; - if (storedUsages) { - try { - usages = JSON.parse(storedUsages); - } catch (e) { - // ignore - } - } - - return usages; -} - -function removeAccountUsage(storageService: IStorageService, providerId: string, accountName: string): void { - const accountKey = `${providerId}-${accountName}-usages`; - storageService.remove(accountKey, StorageScope.APPLICATION); -} - -export function addAccountUsage(storageService: IStorageService, providerId: string, accountName: string, extensionId: string, extensionName: string) { - const accountKey = `${providerId}-${accountName}-usages`; - const usages = readAccountUsages(storageService, providerId, accountName); - - const existingUsageIndex = usages.findIndex(usage => usage.extensionId === extensionId); - if (existingUsageIndex > -1) { - usages.splice(existingUsageIndex, 1, { - extensionId, - extensionName, - lastUsed: Date.now() - }); - } else { - usages.push({ - extensionId, - extensionName, - lastUsed: Date.now() - }); - } - - storageService.store(accountKey, JSON.stringify(usages), StorageScope.APPLICATION, StorageTarget.MACHINE); -} - // TODO: pull this out into its own service export type AuthenticationSessionInfo = { readonly id: string; readonly accessToken: string; readonly providerId: string; readonly canSignOut?: boolean }; export async function getCurrentAuthenticationSessionInfo( @@ -107,122 +42,8 @@ export async function getCurrentAuthenticationSessionInfo( return undefined; } -// OAuth2 spec prohibits space in a scope, so use that to join them. -const SCOPESLIST_SEPARATOR = ' '; - -interface SessionRequest { - disposables: IDisposable[]; - requestingExtensionIds: string[]; -} - -interface SessionRequestInfo { - [scopesList: string]: SessionRequest; -} - -CommandsRegistry.registerCommand('workbench.getCodeExchangeProxyEndpoints', function (accessor, _) { - const environmentService = accessor.get(IBrowserWorkbenchEnvironmentService); - return environmentService.options?.codeExchangeProxyEndpoints; -}); - -const authenticationDefinitionSchema: IJSONSchema = { - type: 'object', - additionalProperties: false, - properties: { - id: { - type: 'string', - description: nls.localize('authentication.id', 'The id of the authentication provider.') - }, - label: { - type: 'string', - description: nls.localize('authentication.label', 'The human readable name of the authentication provider.'), - } - } -}; - -const authenticationExtPoint = ExtensionsRegistry.registerExtensionPoint({ - extensionPoint: 'authentication', - jsonSchema: { - description: nls.localize({ key: 'authenticationExtensionPoint', comment: [`'Contributes' means adds here`] }, 'Contributes authentication'), - type: 'array', - items: authenticationDefinitionSchema - }, - activationEventsGenerator: (authenticationProviders, result) => { - for (const authenticationProvider of authenticationProviders) { - if (authenticationProvider.id) { - result.push(`onAuthenticationRequest:${authenticationProvider.id}`); - } - } - } -}); - -class AuthenticationDataRenderer extends Disposable implements IExtensionFeatureTableRenderer { - - readonly type = 'table'; - - shouldRender(manifest: IExtensionManifest): boolean { - return !!manifest.contributes?.authentication; - } - - render(manifest: IExtensionManifest): IRenderedData { - const authentication = manifest.contributes?.authentication || []; - if (!authentication.length) { - return { data: { headers: [], rows: [] }, dispose: () => { } }; - } - - const headers = [ - nls.localize('authenticationlabel', "Label"), - nls.localize('authenticationid', "ID"), - ]; - - const rows: IRowData[][] = authentication - .sort((a, b) => a.label.localeCompare(b.label)) - .map(auth => { - return [ - auth.label, - auth.id, - ]; - }); - - return { - data: { - headers, - rows - }, - dispose: () => { } - }; - } -} - -Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ - id: 'authentication', - label: nls.localize('authentication', "Authentication"), - access: { - canToggle: false - }, - renderer: new SyncDescriptor(AuthenticationDataRenderer), -}); - -let placeholderMenuItem: IDisposable | undefined = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { - command: { - id: 'noAuthenticationProviders', - title: nls.localize('authentication.Placeholder', "No accounts requested yet..."), - precondition: ContextKeyExpr.false() - }, -}); - export class AuthenticationService extends Disposable implements IAuthenticationService { declare readonly _serviceBrand: undefined; - private _signInRequestItems = new Map(); - private _sessionAccessRequestItems = new Map(); - private _accountBadgeDisposable = this._register(new MutableDisposable()); - - private _authenticationProviders: Map = new Map(); - private _authenticationProviderDisposables: DisposableMap = this._register(new DisposableMap()); - - /** - * All providers that have been statically declared by extensions. These may not be registered. - */ - declaredProviders: AuthenticationProviderInformation[] = []; private _onDidRegisterAuthenticationProvider: Emitter = this._register(new Emitter()); readonly onDidRegisterAuthenticationProvider: Event = this._onDidRegisterAuthenticationProvider.event; @@ -233,55 +54,86 @@ export class AuthenticationService extends Disposable implements IAuthentication private _onDidChangeSessions: Emitter<{ providerId: string; label: string; event: AuthenticationSessionsChangeEvent }> = this._register(new Emitter<{ providerId: string; label: string; event: AuthenticationSessionsChangeEvent }>()); readonly onDidChangeSessions: Event<{ providerId: string; label: string; event: AuthenticationSessionsChangeEvent }> = this._onDidChangeSessions.event; - private _onDidChangeDeclaredProviders: Emitter = this._register(new Emitter()); - readonly onDidChangeDeclaredProviders: Event = this._onDidChangeDeclaredProviders.event; + private _onDidChangeDeclaredProviders: Emitter = this._register(new Emitter()); + readonly onDidChangeDeclaredProviders: Event = this._onDidChangeDeclaredProviders.event; - private _onDidChangeExtensionSessionAccess: Emitter<{ providerId: string; accountName: string }> = this._register(new Emitter<{ providerId: string; accountName: string }>()); - readonly onDidChangeExtensionSessionAccess: Event<{ providerId: string; accountName: string }> = this._onDidChangeExtensionSessionAccess.event; + private _authenticationProviders: Map = new Map(); + private _authenticationProviderDisposables: DisposableMap = this._register(new DisposableMap()); constructor( - @IActivityService private readonly activityService: IActivityService, - @IExtensionService private readonly extensionService: IExtensionService, - @IStorageService private readonly storageService: IStorageService, - @IDialogService private readonly dialogService: IDialogService, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IProductService private readonly productService: IProductService, - @IBrowserWorkbenchEnvironmentService environmentService: IBrowserWorkbenchEnvironmentService, + @IExtensionService private readonly _extensionService: IExtensionService, + @IAuthenticationAccessService authenticationAccessService: IAuthenticationAccessService ) { super(); - environmentService.options?.authenticationProviders?.forEach(provider => this.registerAuthenticationProvider(provider.id, provider)); - authenticationExtPoint.setHandler((extensions, { added, removed }) => { - added.forEach(point => { - for (const provider of point.value) { - if (isFalsyOrWhitespace(provider.id)) { - point.collector.error(nls.localize('authentication.missingId', 'An authentication contribution must specify an id.')); - continue; - } - - if (isFalsyOrWhitespace(provider.label)) { - point.collector.error(nls.localize('authentication.missingLabel', 'An authentication contribution must specify a label.')); - continue; - } - - if (!this.declaredProviders.some(p => p.id === provider.id)) { - this.declaredProviders.push(provider); - } else { - point.collector.error(nls.localize('authentication.idConflict', "This authentication id '{0}' has already been registered", provider.id)); - } + this._register(authenticationAccessService.onDidChangeExtensionSessionAccess(e => { + // The access has changed, not the actual session itself but extensions depend on this event firing + // when they have gained access to an account so this fires that event. + this._onDidChangeSessions.fire({ + providerId: e.providerId, + label: e.accountName, + event: { + added: [], + changed: [], + removed: [] } }); + })); + } - const removedExtPoints = removed.flatMap(r => r.value); - removedExtPoints.forEach(point => { - const index = this.declaredProviders.findIndex(provider => provider.id === point.id); - if (index > -1) { - this.declaredProviders.splice(index, 1); - } - }); + private _declaredProviders: AuthenticationProviderInformation[] = []; + get declaredProviders(): AuthenticationProviderInformation[] { + return this._declaredProviders; + } - this._onDidChangeDeclaredProviders.fire(this.declaredProviders); - }); + registerDeclaredAuthenticationProvider(provider: AuthenticationProviderInformation): void { + if (isFalsyOrWhitespace(provider.id)) { + throw new Error(localize('authentication.missingId', 'An authentication contribution must specify an id.')); + } + if (isFalsyOrWhitespace(provider.label)) { + throw new Error(localize('authentication.missingLabel', 'An authentication contribution must specify a label.')); + } + if (this.declaredProviders.some(p => p.id === provider.id)) { + throw new Error(localize('authentication.idConflict', "This authentication id '{0}' has already been registered", provider.id)); + } + this._declaredProviders.push(provider); + this._onDidChangeDeclaredProviders.fire(); + } + + unregisterDeclaredAuthenticationProvider(id: string): void { + const index = this.declaredProviders.findIndex(provider => provider.id === id); + if (index > -1) { + this.declaredProviders.splice(index, 1); + } + this._onDidChangeDeclaredProviders.fire(); + } + + isAuthenticationProviderRegistered(id: string): boolean { + return this._authenticationProviders.has(id); + } + + registerAuthenticationProvider(id: string, authenticationProvider: IAuthenticationProvider): void { + this._authenticationProviders.set(id, authenticationProvider); + const disposableStore = new DisposableStore(); + disposableStore.add(authenticationProvider.onDidChangeSessions(e => this._onDidChangeSessions.fire({ + providerId: id, + label: authenticationProvider.label, + event: e + }))); + if (isDisposable(authenticationProvider)) { + disposableStore.add(authenticationProvider); + } + this._authenticationProviderDisposables.set(id, disposableStore); + this._onDidRegisterAuthenticationProvider.fire({ id, label: authenticationProvider.label }); + } + + unregisterAuthenticationProvider(id: string): void { + const provider = this._authenticationProviders.get(id); + if (provider) { + this._authenticationProviders.delete(id); + this._onDidUnregisterAuthenticationProvider.fire({ id, label: provider.label }); + } + this._authenticationProviderDisposables.deleteAndDispose(id); } getProviderIds(): string[] { @@ -292,495 +144,11 @@ export class AuthenticationService extends Disposable implements IAuthentication return providerIds; } - isAuthenticationProviderRegistered(id: string): boolean { - return this._authenticationProviders.has(id); - } - - registerAuthenticationProvider(id: string, authenticationProvider: IAuthenticationProvider): void { - this._authenticationProviders.set(id, authenticationProvider); - const disposableStore = new DisposableStore(); - disposableStore.add(authenticationProvider.onDidChangeSessions(e => this.sessionsUpdate(authenticationProvider, e))); - if (isDisposable(authenticationProvider)) { - disposableStore.add(authenticationProvider); + getProvider(id: string): IAuthenticationProvider { + if (this._authenticationProviders.has(id)) { + return this._authenticationProviders.get(id)!; } - this._authenticationProviderDisposables.set(id, disposableStore); - this._onDidRegisterAuthenticationProvider.fire({ id, label: authenticationProvider.label }); - - if (placeholderMenuItem) { - placeholderMenuItem.dispose(); - placeholderMenuItem = undefined; - } - } - - unregisterAuthenticationProvider(id: string): void { - const provider = this._authenticationProviders.get(id); - if (provider) { - this._authenticationProviders.delete(id); - this._onDidUnregisterAuthenticationProvider.fire({ id, label: provider.label }); - - const accessRequests = this._sessionAccessRequestItems.get(id) || {}; - Object.keys(accessRequests).forEach(extensionId => { - this.removeAccessRequest(id, extensionId); - }); - } - this._authenticationProviderDisposables.deleteAndDispose(id); - - if (!this._authenticationProviders.size) { - placeholderMenuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { - command: { - id: 'noAuthenticationProviders', - title: nls.localize('loading', "Loading..."), - precondition: ContextKeyExpr.false() - }, - }); - } - } - - private async sessionsUpdate(provider: IAuthenticationProvider, event: AuthenticationSessionsChangeEvent): Promise { - this._onDidChangeSessions.fire({ providerId: provider.id, label: provider.label, event }); - if (event.added?.length) { - await this.updateNewSessionRequests(provider, event.added); - } - if (event.removed?.length) { - await this.updateAccessRequests(provider.id, event.removed); - } - this.updateBadgeCount(); - } - - private async updateNewSessionRequests(provider: IAuthenticationProvider, addedSessions: readonly AuthenticationSession[]): Promise { - const existingRequestsForProvider = this._signInRequestItems.get(provider.id); - if (!existingRequestsForProvider) { - return; - } - - Object.keys(existingRequestsForProvider).forEach(requestedScopes => { - if (addedSessions.some(session => session.scopes.slice().join(SCOPESLIST_SEPARATOR) === requestedScopes)) { - const sessionRequest = existingRequestsForProvider[requestedScopes]; - sessionRequest?.disposables.forEach(item => item.dispose()); - - delete existingRequestsForProvider[requestedScopes]; - if (Object.keys(existingRequestsForProvider).length === 0) { - this._signInRequestItems.delete(provider.id); - } else { - this._signInRequestItems.set(provider.id, existingRequestsForProvider); - } - } - }); - } - - private async updateAccessRequests(providerId: string, removedSessions: readonly AuthenticationSession[]) { - const providerRequests = this._sessionAccessRequestItems.get(providerId); - if (providerRequests) { - Object.keys(providerRequests).forEach(extensionId => { - removedSessions.forEach(removed => { - const indexOfSession = providerRequests[extensionId].possibleSessions.findIndex(session => session.id === removed.id); - if (indexOfSession) { - providerRequests[extensionId].possibleSessions.splice(indexOfSession, 1); - } - }); - - if (!providerRequests[extensionId].possibleSessions.length) { - this.removeAccessRequest(providerId, extensionId); - } - }); - } - } - - private updateBadgeCount(): void { - this._accountBadgeDisposable.clear(); - - let numberOfRequests = 0; - this._signInRequestItems.forEach(providerRequests => { - Object.keys(providerRequests).forEach(request => { - numberOfRequests += providerRequests[request].requestingExtensionIds.length; - }); - }); - - this._sessionAccessRequestItems.forEach(accessRequest => { - numberOfRequests += Object.keys(accessRequest).length; - }); - - if (numberOfRequests > 0) { - const badge = new NumberBadge(numberOfRequests, () => nls.localize('sign in', "Sign in requested")); - this._accountBadgeDisposable.value = this.activityService.showAccountsActivity({ badge }); - } - } - - private removeAccessRequest(providerId: string, extensionId: string): void { - const providerRequests = this._sessionAccessRequestItems.get(providerId) || {}; - if (providerRequests[extensionId]) { - dispose(providerRequests[extensionId].disposables); - delete providerRequests[extensionId]; - this.updateBadgeCount(); - } - } - - /** - * Check extension access to an account - * @param providerId The id of the authentication provider - * @param accountName The account name that access is checked for - * @param extensionId The id of the extension requesting access - * @returns Returns true or false if the user has opted to permanently grant or disallow access, and undefined - * if they haven't made a choice yet - */ - isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean | undefined { - const trustedExtensionAuthAccess = this.productService.trustedExtensionAuthAccess; - if (Array.isArray(trustedExtensionAuthAccess)) { - if (trustedExtensionAuthAccess.includes(extensionId)) { - return true; - } - } else if (trustedExtensionAuthAccess?.[providerId]?.includes(extensionId)) { - return true; - } - - const allowList = this.readAllowedExtensions(providerId, accountName); - const extensionData = allowList.find(extension => extension.id === extensionId); - if (!extensionData) { - return undefined; - } - // This property didn't exist on this data previously, inclusion in the list at all indicates allowance - return extensionData.allowed !== undefined - ? extensionData.allowed - : true; - } - - updateAllowedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string, isAllowed: boolean): void { - const allowList = this.readAllowedExtensions(providerId, accountName); - const index = allowList.findIndex(extension => extension.id === extensionId); - if (index === -1) { - allowList.push({ id: extensionId, name: extensionName, allowed: isAllowed }); - } else { - allowList[index].allowed = isAllowed; - } - - this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.APPLICATION, StorageTarget.USER); - } - - //#region Session Preference - - updateSessionPreference(providerId: string, extensionId: string, session: AuthenticationSession): void { - // The 3 parts of this key are important: - // * Extension id: The extension that has a preference - // * Provider id: The provider that the preference is for - // * The scopes: The subset of sessions that the preference applies to - const key = `${extensionId}-${providerId}-${session.scopes.join(' ')}`; - - // Store the preference in the workspace and application storage. This allows new workspaces to - // have a preference set already to limit the number of prompts that are shown... but also allows - // a specific workspace to override the global preference. - this.storageService.store(key, session.id, StorageScope.WORKSPACE, StorageTarget.MACHINE); - this.storageService.store(key, session.id, StorageScope.APPLICATION, StorageTarget.MACHINE); - } - - getSessionPreference(providerId: string, extensionId: string, scopes: string[]): string | undefined { - // The 3 parts of this key are important: - // * Extension id: The extension that has a preference - // * Provider id: The provider that the preference is for - // * The scopes: The subset of sessions that the preference applies to - const key = `${extensionId}-${providerId}-${scopes.join(' ')}`; - - // If a preference is set in the workspace, use that. Otherwise, use the global preference. - return this.storageService.get(key, StorageScope.WORKSPACE) ?? this.storageService.get(key, StorageScope.APPLICATION); - } - - removeSessionPreference(providerId: string, extensionId: string, scopes: string[]): void { - // The 3 parts of this key are important: - // * Extension id: The extension that has a preference - // * Provider id: The provider that the preference is for - // * The scopes: The subset of sessions that the preference applies to - const key = `${extensionId}-${providerId}-${scopes.join(' ')}`; - - // This won't affect any other workspaces that have a preference set, but it will remove the preference - // for this workspace and the global preference. This is only paired with a call to updateSessionPreference... - // so we really don't _need_ to remove them as they are about to be overridden anyway... but it's more correct - // to remove them first... and in case this gets called from somewhere else in the future. - this.storageService.remove(key, StorageScope.WORKSPACE); - this.storageService.remove(key, StorageScope.APPLICATION); - } - - //#endregion - - async showGetSessionPrompt(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise { - const providerName = this.getLabel(providerId); - enum SessionPromptChoice { - Allow = 0, - Deny = 1, - Cancel = 2 - } - const { result } = await this.dialogService.prompt({ - type: Severity.Info, - message: nls.localize('confirmAuthenticationAccess', "The extension '{0}' wants to access the {1} account '{2}'.", extensionName, providerName, accountName), - buttons: [ - { - label: nls.localize({ key: 'allow', comment: ['&& denotes a mnemonic'] }, "&&Allow"), - run: () => SessionPromptChoice.Allow - }, - { - label: nls.localize({ key: 'deny', comment: ['&& denotes a mnemonic'] }, "&&Deny"), - run: () => SessionPromptChoice.Deny - } - ], - cancelButton: { - run: () => SessionPromptChoice.Cancel - } - }); - - if (result !== SessionPromptChoice.Cancel) { - this.updateAllowedExtension(providerId, accountName, extensionId, extensionName, result === SessionPromptChoice.Allow); - this.removeAccessRequest(providerId, extensionId); - } - - return result === SessionPromptChoice.Allow; - } - - async selectSession(providerId: string, extensionId: string, extensionName: string, scopes: string[], availableSessions: AuthenticationSession[]): Promise { - return new Promise((resolve, reject) => { - // This function should be used only when there are sessions to disambiguate. - if (!availableSessions.length) { - reject('No available sessions'); - } - - const quickPick = this.quickInputService.createQuickPick<{ label: string; session?: AuthenticationSession }>(); - quickPick.ignoreFocusOut = true; - const items: { label: string; session?: AuthenticationSession }[] = availableSessions.map(session => { - return { - label: session.account.label, - session: session - }; - }); - - items.push({ - label: nls.localize('useOtherAccount', "Sign in to another account") - }); - - const providerName = this.getLabel(providerId); - - quickPick.items = items; - - quickPick.title = nls.localize( - { - key: 'selectAccount', - comment: ['The placeholder {0} is the name of an extension. {1} is the name of the type of account, such as Microsoft or GitHub.'] - }, - "The extension '{0}' wants to access a {1} account", - extensionName, - providerName); - quickPick.placeholder = nls.localize('getSessionPlateholder', "Select an account for '{0}' to use or Esc to cancel", extensionName); - - quickPick.onDidAccept(async _ => { - const session = quickPick.selectedItems[0].session ?? await this.createSession(providerId, scopes); - const accountName = session.account.label; - - this.updateAllowedExtension(providerId, accountName, extensionId, extensionName, true); - this.updateSessionPreference(providerId, extensionId, session); - this.removeAccessRequest(providerId, extensionId); - - quickPick.dispose(); - resolve(session); - }); - - quickPick.onDidHide(_ => { - if (!quickPick.selectedItems[0]) { - reject('User did not consent to account access'); - } - - quickPick.dispose(); - }); - - quickPick.show(); - }); - } - - async completeSessionAccessRequest(providerId: string, extensionId: string, extensionName: string, scopes: string[]): Promise { - const providerRequests = this._sessionAccessRequestItems.get(providerId) || {}; - const existingRequest = providerRequests[extensionId]; - if (!existingRequest) { - return; - } - - const possibleSessions = existingRequest.possibleSessions; - const supportsMultipleAccounts = this.supportsMultipleAccounts(providerId); - - let session: AuthenticationSession | undefined; - if (supportsMultipleAccounts) { - try { - session = await this.selectSession(providerId, extensionId, extensionName, scopes, possibleSessions); - } catch (_) { - // ignore cancel - } - } else { - const approved = await this.showGetSessionPrompt(providerId, possibleSessions[0].account.label, extensionId, extensionName); - if (approved) { - session = possibleSessions[0]; - } - } - - if (session) { - addAccountUsage(this.storageService, providerId, session.account.label, extensionId, extensionName); - const providerName = this.getLabel(providerId); - this._onDidChangeSessions.fire({ providerId, label: providerName, event: { added: [], removed: [], changed: [session] } }); - } - } - - requestSessionAccess(providerId: string, extensionId: string, extensionName: string, scopes: string[], possibleSessions: AuthenticationSession[]): void { - const providerRequests = this._sessionAccessRequestItems.get(providerId) || {}; - const hasExistingRequest = providerRequests[extensionId]; - if (hasExistingRequest) { - return; - } - - const menuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { - group: '3_accessRequests', - command: { - id: `${providerId}${extensionId}Access`, - title: nls.localize({ - key: 'accessRequest', - comment: [`The placeholder {0} will be replaced with an authentication provider''s label. {1} will be replaced with an extension name. (1) is to indicate that this menu item contributes to a badge count`] - }, - "Grant access to {0} for {1}... (1)", - this.getLabel(providerId), - extensionName) - } - }); - - const accessCommand = CommandsRegistry.registerCommand({ - id: `${providerId}${extensionId}Access`, - handler: async (accessor) => { - const authenticationService = accessor.get(IAuthenticationService); - authenticationService.completeSessionAccessRequest(providerId, extensionId, extensionName, scopes); - } - }); - - providerRequests[extensionId] = { possibleSessions, disposables: [menuItem, accessCommand] }; - this._sessionAccessRequestItems.set(providerId, providerRequests); - this.updateBadgeCount(); - } - - async requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise { - let provider = this._authenticationProviders.get(providerId); - if (!provider) { - // Activate has already been called for the authentication provider, but it cannot block on registering itself - // since this is sync and returns a disposable. So, wait for registration event to fire that indicates the - // provider is now in the map. - await new Promise((resolve, _) => { - const dispose = this.onDidRegisterAuthenticationProvider(e => { - if (e.id === providerId) { - provider = this._authenticationProviders.get(providerId); - dispose.dispose(); - resolve(); - } - }); - }); - } - - if (!provider) { - return; - } - - const providerRequests = this._signInRequestItems.get(providerId); - const scopesList = scopes.join(SCOPESLIST_SEPARATOR); - const extensionHasExistingRequest = providerRequests - && providerRequests[scopesList] - && providerRequests[scopesList].requestingExtensionIds.includes(extensionId); - - if (extensionHasExistingRequest) { - return; - } - - // Construct a commandId that won't clash with others generated here, nor likely with an extension's command - const commandId = `${providerId}:${extensionId}:signIn${Object.keys(providerRequests || []).length}`; - const menuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { - group: '2_signInRequests', - command: { - id: commandId, - title: nls.localize({ - key: 'signInRequest', - comment: [`The placeholder {0} will be replaced with an authentication provider's label. {1} will be replaced with an extension name. (1) is to indicate that this menu item contributes to a badge count.`] - }, - "Sign in with {0} to use {1} (1)", - provider.label, - extensionName) - } - }); - - const signInCommand = CommandsRegistry.registerCommand({ - id: commandId, - handler: async (accessor) => { - const authenticationService = accessor.get(IAuthenticationService); - const session = await authenticationService.createSession(providerId, scopes); - - this.updateAllowedExtension(providerId, session.account.label, extensionId, extensionName, true); - this.updateSessionPreference(providerId, extensionId, session); - } - }); - - - if (providerRequests) { - const existingRequest = providerRequests[scopesList] || { disposables: [], requestingExtensionIds: [] }; - - providerRequests[scopesList] = { - disposables: [...existingRequest.disposables, menuItem, signInCommand], - requestingExtensionIds: [...existingRequest.requestingExtensionIds, extensionId] - }; - this._signInRequestItems.set(providerId, providerRequests); - } else { - this._signInRequestItems.set(providerId, { - [scopesList]: { - disposables: [menuItem, signInCommand], - requestingExtensionIds: [extensionId] - } - }); - } - - this.updateBadgeCount(); - } - getLabel(id: string): string { - const authProvider = this._authenticationProviders.get(id); - if (authProvider) { - return authProvider.label; - } else { - throw new Error(`No authentication provider '${id}' is currently registered.`); - } - } - - supportsMultipleAccounts(id: string): boolean { - const authProvider = this._authenticationProviders.get(id); - if (authProvider) { - return authProvider.supportsMultipleAccounts; - } else { - throw new Error(`No authentication provider '${id}' is currently registered.`); - } - } - - private async tryActivateProvider(providerId: string, activateImmediate: boolean): Promise { - await this.extensionService.activateByEvent(getAuthenticationProviderActivationEvent(providerId), activateImmediate ? ActivationKind.Immediate : ActivationKind.Normal); - let provider = this._authenticationProviders.get(providerId); - if (provider) { - return provider; - } - - // When activate has completed, the extension has made the call to `registerAuthenticationProvider`. - // However, activate cannot block on this, so the renderer may not have gotten the event yet. - const didRegister: Promise = new Promise((resolve, _) => { - this.onDidRegisterAuthenticationProvider(e => { - if (e.id === providerId) { - provider = this._authenticationProviders.get(providerId); - if (provider) { - resolve(provider); - } else { - throw new Error(`No authentication provider '${providerId}' is currently registered.`); - } - } - }); - }); - - const didTimeout: Promise = new Promise((_, reject) => { - setTimeout(() => { - reject('Timed out waiting for authentication provider to register'); - }, 5000); - }); - - return Promise.race([didRegister, didTimeout]); + throw new Error(`No authentication provider '${id}' is currently registered.`); } async getSessions(id: string, scopes?: string[], activateImmediate: boolean = false): Promise> { @@ -812,177 +180,35 @@ export class AuthenticationService extends Disposable implements IAuthentication } } - // TODO: pull this stuff out into its own service - readAllowedExtensions(providerId: string, accountName: string): AllowedExtension[] { - let trustedExtensions: AllowedExtension[] = []; - try { - const trustedExtensionSrc = this.storageService.get(`${providerId}-${accountName}`, StorageScope.APPLICATION); - if (trustedExtensionSrc) { - trustedExtensions = JSON.parse(trustedExtensionSrc); - } - } catch (err) { } - - return trustedExtensions; - } - - // TODO: pull this out into an Action in a contribution - async manageTrustedExtensionsForAccount(id: string, accountName: string): Promise { - const authProvider = this._authenticationProviders.get(id); - if (!authProvider) { - throw new Error(`No authentication provider '${id}' is currently registered.`); + private async tryActivateProvider(providerId: string, activateImmediate: boolean): Promise { + await this._extensionService.activateByEvent(getAuthenticationProviderActivationEvent(providerId), activateImmediate ? ActivationKind.Immediate : ActivationKind.Normal); + let provider = this._authenticationProviders.get(providerId); + if (provider) { + return provider; } - const allowedExtensions = this.readAllowedExtensions(authProvider.id, accountName); - const trustedExtensionAuthAccess = this.productService.trustedExtensionAuthAccess; - const trustedExtensionIds = - // Case 1: trustedExtensionAuthAccess is an array - Array.isArray(trustedExtensionAuthAccess) - ? trustedExtensionAuthAccess - // Case 2: trustedExtensionAuthAccess is an object - : typeof trustedExtensionAuthAccess === 'object' - ? trustedExtensionAuthAccess[authProvider.id] ?? [] - : []; - for (const extensionId of trustedExtensionIds) { - const allowedExtension = allowedExtensions.find(ext => ext.id === extensionId); - if (!allowedExtension) { - // Add the extension to the allowedExtensions list - const extension = await this.extensionService.getExtension(extensionId); - if (extension) { - allowedExtensions.push({ - id: extensionId, - name: extension.displayName || extension.name, - allowed: true, - trusted: true - }); - } - } else { - // Update the extension to be allowed - allowedExtension.allowed = true; - allowedExtension.trusted = true; - } - } - - if (!allowedExtensions.length) { - this.dialogService.info(nls.localize('noTrustedExtensions', "This account has not been used by any extensions.")); - return; - } - - interface TrustedExtensionsQuickPickItem extends IQuickPickItem { - extension: AllowedExtension; - lastUsed?: number; - } - - const disposableStore = new DisposableStore(); - const quickPick = disposableStore.add(this.quickInputService.createQuickPick()); - quickPick.canSelectMany = true; - quickPick.customButton = true; - quickPick.customLabel = nls.localize('manageTrustedExtensions.cancel', 'Cancel'); - const usages = readAccountUsages(this.storageService, authProvider.id, accountName); - const trustedExtensions = []; - const otherExtensions = []; - for (const extension of allowedExtensions) { - const usage = usages.find(usage => extension.id === usage.extensionId); - extension.lastUsed = usage?.lastUsed; - if (extension.trusted) { - trustedExtensions.push(extension); - } else { - otherExtensions.push(extension); - } - } - - const sortByLastUsed = (a: AllowedExtension, b: AllowedExtension) => (b.lastUsed || 0) - (a.lastUsed || 0); - const toQuickPickItem = function (extension: AllowedExtension) { - const lastUsed = extension.lastUsed; - const description = lastUsed - ? nls.localize({ key: 'accountLastUsedDate', comment: ['The placeholder {0} is a string with time information, such as "3 days ago"'] }, "Last used this account {0}", fromNow(lastUsed, true)) - : nls.localize('notUsed', "Has not used this account"); - let tooltip: string | undefined; - if (extension.trusted) { - tooltip = nls.localize('trustedExtensionTooltip', "This extension is trusted by Microsoft and\nalways has access to this account"); - } - return { - label: extension.name, - extension, - description, - tooltip - }; - }; - const items: Array = [ - ...otherExtensions.sort(sortByLastUsed).map(toQuickPickItem), - { type: 'separator', label: nls.localize('trustedExtensions', "Trusted by Microsoft") }, - ...trustedExtensions.sort(sortByLastUsed).map(toQuickPickItem) - ]; - - quickPick.items = items; - quickPick.selectedItems = items.filter((item): item is TrustedExtensionsQuickPickItem => item.type !== 'separator' && (item.extension.allowed === undefined || item.extension.allowed)); - quickPick.title = nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"); - quickPick.placeholder = nls.localize('manageExtensions', "Choose which extensions can access this account"); - - disposableStore.add(quickPick.onDidAccept(() => { - const updatedAllowedList = quickPick.items - .filter((item): item is TrustedExtensionsQuickPickItem => item.type !== 'separator') - .map(i => i.extension); - this.storageService.store(`${authProvider.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.APPLICATION, StorageTarget.USER); - this._onDidChangeExtensionSessionAccess.fire({ providerId: authProvider.id, accountName }); - quickPick.hide(); - })); - - disposableStore.add(quickPick.onDidChangeSelection((changed) => { - const trustedItems = new Set(); - quickPick.items.forEach(item => { - const trustItem = item as TrustedExtensionsQuickPickItem; - if (trustItem.extension) { - if (trustItem.extension.trusted) { - trustedItems.add(trustItem); + // When activate has completed, the extension has made the call to `registerAuthenticationProvider`. + // However, activate cannot block on this, so the renderer may not have gotten the event yet. + const didRegister: Promise = new Promise((resolve, _) => { + this.onDidRegisterAuthenticationProvider(e => { + if (e.id === providerId) { + provider = this._authenticationProviders.get(providerId); + if (provider) { + resolve(provider); } else { - trustItem.extension.allowed = false; + throw new Error(`No authentication provider '${providerId}' is currently registered.`); } } }); - changed.forEach((item) => { - item.extension.allowed = true; - trustedItems.delete(item); - }); - - // reselect trusted items if a user tried to unselect one since quick pick doesn't support forcing selection - if (trustedItems.size) { - quickPick.selectedItems = [...changed, ...trustedItems]; - } - })); - - disposableStore.add(quickPick.onDidHide(() => { - disposableStore.dispose(); - })); - - disposableStore.add(quickPick.onDidCustom(() => { - quickPick.hide(); - })); - - quickPick.show(); - } - - async removeAccountSessions(id: string, accountName: string, sessions: AuthenticationSession[]): Promise { - const authProvider = this._authenticationProviders.get(id); - if (!authProvider) { - throw new Error(`No authentication provider '${id}' is currently registered.`); - } - - const accountUsages = readAccountUsages(this.storageService, authProvider.id, accountName); - - const { confirmed } = await this.dialogService.confirm({ - type: Severity.Info, - message: accountUsages.length - ? nls.localize('signOutMessage', "The account '{0}' has been used by: \n\n{1}\n\n Sign out from these extensions?", accountName, accountUsages.map(usage => usage.extensionName).join('\n')) - : nls.localize('signOutMessageSimple', "Sign out of '{0}'?", accountName), - primaryButton: nls.localize({ key: 'signOut', comment: ['&& denotes a mnemonic'] }, "&&Sign Out") }); - if (confirmed) { - const removeSessionPromises = sessions.map(session => authProvider.removeSession(session.id)); - await Promise.all(removeSessionPromises); - removeAccountUsage(this.storageService, authProvider.id, accountName); - this.storageService.remove(`${authProvider.id}-${accountName}`, StorageScope.APPLICATION); - } + const didTimeout: Promise = new Promise((_, reject) => { + setTimeout(() => { + reject('Timed out waiting for authentication provider to register'); + }, 5000); + }); + + return Promise.race([didRegister, didTimeout]); } } diff --git a/src/vs/workbench/services/authentication/browser/authenticationUsageService.ts b/src/vs/workbench/services/authentication/browser/authenticationUsageService.ts new file mode 100644 index 00000000000..8a40ac36958 --- /dev/null +++ b/src/vs/workbench/services/authentication/browser/authenticationUsageService.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 { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; + +export interface IAccountUsage { + extensionId: string; + extensionName: string; + lastUsed: number; +} + +export const IAuthenticationUsageService = createDecorator('IAuthenticationUsageService'); +export interface IAuthenticationUsageService { + readonly _serviceBrand: undefined; + readAccountUsages(providerId: string, accountName: string,): IAccountUsage[]; + removeAccountUsage(providerId: string, accountName: string): void; + addAccountUsage(providerId: string, accountName: string, extensionId: string, extensionName: string): void; +} + +export class AuthenticationUsageService implements IAuthenticationUsageService { + _serviceBrand: undefined; + + constructor(@IStorageService private readonly _storageService: IStorageService) { } + + readAccountUsages(providerId: string, accountName: string): IAccountUsage[] { + const accountKey = `${providerId}-${accountName}-usages`; + const storedUsages = this._storageService.get(accountKey, StorageScope.APPLICATION); + let usages: IAccountUsage[] = []; + if (storedUsages) { + try { + usages = JSON.parse(storedUsages); + } catch (e) { + // ignore + } + } + + return usages; + } + removeAccountUsage(providerId: string, accountName: string): void { + const accountKey = `${providerId}-${accountName}-usages`; + this._storageService.remove(accountKey, StorageScope.APPLICATION); + } + addAccountUsage(providerId: string, accountName: string, extensionId: string, extensionName: string): void { + const accountKey = `${providerId}-${accountName}-usages`; + const usages = this.readAccountUsages(providerId, accountName); + + const existingUsageIndex = usages.findIndex(usage => usage.extensionId === extensionId); + if (existingUsageIndex > -1) { + usages.splice(existingUsageIndex, 1, { + extensionId, + extensionName, + lastUsed: Date.now() + }); + } else { + usages.push({ + extensionId, + extensionName, + lastUsed: Date.now() + }); + } + + this._storageService.store(accountKey, JSON.stringify(usages), StorageScope.APPLICATION, StorageTarget.MACHINE); + } +} + +registerSingleton(IAuthenticationUsageService, AuthenticationUsageService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/authentication/common/authentication.ts b/src/vs/workbench/services/authentication/common/authentication.ts index eaeaffc56ac..6da6e530237 100644 --- a/src/vs/workbench/services/authentication/common/authentication.ts +++ b/src/vs/workbench/services/authentication/common/authentication.ts @@ -58,40 +58,108 @@ export const IAuthenticationService = createDecorator('I export interface IAuthenticationService { readonly _serviceBrand: undefined; + /** + * Fires when an authentication provider has been registered + */ + readonly onDidRegisterAuthenticationProvider: Event; + /** + * Fires when an authentication provider has been unregistered + */ + readonly onDidUnregisterAuthenticationProvider: Event; + + /** + * Fires when the list of sessions for a provider has been added, removed or changed + */ + readonly onDidChangeSessions: Event<{ providerId: string; label: string; event: AuthenticationSessionsChangeEvent }>; + + /** + * Fires when the list of declaredProviders has changed + */ + readonly onDidChangeDeclaredProviders: Event; + + /** + * All providers that have been statically declared by extensions. These may not actually be registered or active yet. + */ + readonly declaredProviders: AuthenticationProviderInformation[]; + + /** + * Registers that an extension has declared an authentication provider in their package.json + * @param provider The provider information to register + */ + registerDeclaredAuthenticationProvider(provider: AuthenticationProviderInformation): void; + + /** + * Unregisters a declared authentication provider + * @param id The id of the provider to unregister + */ + unregisterDeclaredAuthenticationProvider(id: string): void; + + /** + * Checks if an authentication provider has been registered + * @param id The id of the provider to check + */ isAuthenticationProviderRegistered(id: string): boolean; - getProviderIds(): string[]; + + /** + * Registers an authentication provider + * @param id The id of the provider + * @param provider The implementation of the provider + */ registerAuthenticationProvider(id: string, provider: IAuthenticationProvider): void; + + /** + * Unregisters an authentication provider + * @param id The id of the provider to unregister + */ unregisterAuthenticationProvider(id: string): void; - isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean | undefined; - updateAllowedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string, isAllowed: boolean): void; + + /** + * Gets the provider ids of all registered authentication providers + */ + getProviderIds(): string[]; + + /** + * Gets the provider with the given id. + * @param id The id of the provider to get + * @throws if the provider is not registered + */ + getProvider(id: string): IAuthenticationProvider; + + /** + * Gets all sessions that satisfy the given scopes from the provider with the given id + * @param id The id of the provider to ask for a session + * @param scopes The scopes for the session + * @param activateImmediate If true, the provider should activate immediately if it is not already + */ + getSessions(id: string, scopes?: string[], activateImmediate?: boolean): Promise>; + + /** + * Creates an AuthenticationSession with the given provider and scopes + * @param providerId The id of the provider + * @param scopes The scopes to request + * @param options Additional options for creating the session + */ + createSession(providerId: string, scopes: string[], options?: IAuthenticationCreateSessionOptions): Promise; + + /** + * Removes the session with the given id from the provider with the given id + * @param providerId The id of the provider + * @param sessionId The id of the session to remove + */ + removeSession(providerId: string, sessionId: string): Promise; +} + +// TODO: Move this into MainThreadAuthentication +export const IAuthenticationExtensionsService = createDecorator('IAuthenticationExtensionsService'); +export interface IAuthenticationExtensionsService { + readonly _serviceBrand: undefined; + updateSessionPreference(providerId: string, extensionId: string, session: AuthenticationSession): void; getSessionPreference(providerId: string, extensionId: string, scopes: string[]): string | undefined; removeSessionPreference(providerId: string, extensionId: string, scopes: string[]): void; - showGetSessionPrompt(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise; selectSession(providerId: string, extensionId: string, extensionName: string, scopes: string[], possibleSessions: readonly AuthenticationSession[]): Promise; requestSessionAccess(providerId: string, extensionId: string, extensionName: string, scopes: string[], possibleSessions: readonly AuthenticationSession[]): void; - completeSessionAccessRequest(providerId: string, extensionId: string, extensionName: string, scopes: string[]): Promise; requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise; - - readonly onDidRegisterAuthenticationProvider: Event; - readonly onDidUnregisterAuthenticationProvider: Event; - - readonly onDidChangeSessions: Event<{ providerId: string; label: string; event: AuthenticationSessionsChangeEvent }>; - readonly onDidChangeExtensionSessionAccess: Event<{ providerId: string; accountName: string }>; - - // TODO completely remove this property - declaredProviders: AuthenticationProviderInformation[]; - readonly onDidChangeDeclaredProviders: Event; - - getSessions(id: string, scopes?: string[], activateImmediate?: boolean): Promise>; - getLabel(providerId: string): string; - supportsMultipleAccounts(providerId: string): boolean; - createSession(providerId: string, scopes: string[], options?: IAuthenticationCreateSessionOptions): Promise; - removeSession(providerId: string, sessionId: string): Promise; - - manageTrustedExtensionsForAccount(providerId: string, accountName: string): Promise; - readAllowedExtensions(providerId: string, accountName: string): AllowedExtension[]; - removeAccountSessions(providerId: string, accountName: string, sessions: AuthenticationSession[]): Promise; } export interface IAuthenticationProviderCreateSessionOptions { diff --git a/src/vs/workbench/services/authentication/test/browser/authenticationService.test.ts b/src/vs/workbench/services/authentication/test/browser/authenticationService.test.ts new file mode 100644 index 00000000000..180c1ae8a3a --- /dev/null +++ b/src/vs/workbench/services/authentication/test/browser/authenticationService.test.ts @@ -0,0 +1,209 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Emitter, Event } from 'vs/base/common/event'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { AuthenticationAccessService } from 'vs/workbench/services/authentication/browser/authenticationAccessService'; +import { AuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; +import { AuthenticationProviderInformation, AuthenticationSessionsChangeEvent, IAuthenticationProvider } from 'vs/workbench/services/authentication/common/authentication'; +import { TestExtensionService, TestProductService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; + +function createSession() { + return { id: 'session1', accessToken: 'token1', account: { id: 'account', label: 'Account' }, scopes: ['test'] }; +} + +function createProvider(overrides: Partial = {}): IAuthenticationProvider { + return { + supportsMultipleAccounts: false, + onDidChangeSessions: new Emitter().event, + id: 'test', + label: 'Test', + getSessions: async () => [], + createSession: async () => createSession(), + removeSession: async () => { }, + ...overrides + }; +} + +suite('AuthenticationService', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + + let authenticationService: AuthenticationService; + + setup(() => { + const storageService = disposables.add(new TestStorageService()); + const authenticationAccessService = disposables.add(new AuthenticationAccessService(storageService, TestProductService)); + authenticationService = disposables.add(new AuthenticationService(new TestExtensionService(), authenticationAccessService)); + }); + + teardown(() => { + // Dispose the authentication service after each test + authenticationService.dispose(); + }); + + suite('declaredAuthenticationProviders', () => { + test('registerDeclaredAuthenticationProvider', async () => { + const changed = Event.toPromise(authenticationService.onDidChangeDeclaredProviders); + const provider: AuthenticationProviderInformation = { + id: 'github', + label: 'GitHub' + }; + authenticationService.registerDeclaredAuthenticationProvider(provider); + + // Assert that the provider is added to the declaredProviders array and the event fires + assert.equal(authenticationService.declaredProviders.length, 1); + assert.deepEqual(authenticationService.declaredProviders[0], provider); + await changed; + }); + + test('unregisterDeclaredAuthenticationProvider', async () => { + const provider: AuthenticationProviderInformation = { + id: 'github', + label: 'GitHub' + }; + authenticationService.registerDeclaredAuthenticationProvider(provider); + const changed = Event.toPromise(authenticationService.onDidChangeDeclaredProviders); + authenticationService.unregisterDeclaredAuthenticationProvider(provider.id); + + // Assert that the provider is removed from the declaredProviders array and the event fires + assert.equal(authenticationService.declaredProviders.length, 0); + await changed; + }); + }); + + suite('authenticationProviders', () => { + test('isAuthenticationProviderRegistered', async () => { + const registered = Event.toPromise(authenticationService.onDidRegisterAuthenticationProvider); + const provider = createProvider(); + assert.equal(authenticationService.isAuthenticationProviderRegistered(provider.id), false); + authenticationService.registerAuthenticationProvider(provider.id, provider); + assert.equal(authenticationService.isAuthenticationProviderRegistered(provider.id), true); + const result = await registered; + assert.deepEqual(result, { id: provider.id, label: provider.label }); + }); + + test('unregisterAuthenticationProvider', async () => { + const unregistered = Event.toPromise(authenticationService.onDidUnregisterAuthenticationProvider); + const provider = createProvider(); + authenticationService.registerAuthenticationProvider(provider.id, provider); + assert.equal(authenticationService.isAuthenticationProviderRegistered(provider.id), true); + authenticationService.unregisterAuthenticationProvider(provider.id); + assert.equal(authenticationService.isAuthenticationProviderRegistered(provider.id), false); + const result = await unregistered; + assert.deepEqual(result, { id: provider.id, label: provider.label }); + }); + + test('getProviderIds', () => { + const provider1 = createProvider({ + id: 'provider1', + label: 'Provider 1' + }); + const provider2 = createProvider({ + id: 'provider2', + label: 'Provider 2' + }); + + authenticationService.registerAuthenticationProvider(provider1.id, provider1); + authenticationService.registerAuthenticationProvider(provider2.id, provider2); + + const providerIds = authenticationService.getProviderIds(); + + // Assert that the providerIds array contains the registered provider ids + assert.deepEqual(providerIds, [provider1.id, provider2.id]); + }); + + test('getProvider', () => { + const provider = createProvider(); + + authenticationService.registerAuthenticationProvider(provider.id, provider); + + const retrievedProvider = authenticationService.getProvider(provider.id); + + // Assert that the retrieved provider is the same as the registered provider + assert.deepEqual(retrievedProvider, provider); + }); + }); + + suite('authenticationSessions', () => { + test('getSessions', async () => { + let isCalled = false; + const provider = createProvider({ + getSessions: async () => { + isCalled = true; + return [createSession()]; + }, + }); + authenticationService.registerAuthenticationProvider(provider.id, provider); + const sessions = await authenticationService.getSessions(provider.id); + + assert.equal(sessions.length, 1); + assert.ok(isCalled); + }); + + test('createSession', async () => { + const emitter = new Emitter(); + const provider = createProvider({ + onDidChangeSessions: emitter.event, + createSession: async () => { + const session = createSession(); + emitter.fire({ added: [session], removed: [], changed: [] }); + return session; + }, + }); + const changed = Event.toPromise(authenticationService.onDidChangeSessions); + authenticationService.registerAuthenticationProvider(provider.id, provider); + const session = await authenticationService.createSession(provider.id, ['repo']); + + // Assert that the created session matches the expected session and the event fires + assert.ok(session); + const result = await changed; + assert.deepEqual(result, { + providerId: provider.id, + label: provider.label, + event: { added: [session], removed: [], changed: [] } + }); + }); + + test('removeSession', async () => { + const emitter = new Emitter(); + const session = createSession(); + const provider = createProvider({ + onDidChangeSessions: emitter.event, + removeSession: async () => emitter.fire({ added: [], removed: [session], changed: [] }) + }); + const changed = Event.toPromise(authenticationService.onDidChangeSessions); + authenticationService.registerAuthenticationProvider(provider.id, provider); + await authenticationService.removeSession(provider.id, session.id); + + const result = await changed; + assert.deepEqual(result, { + providerId: provider.id, + label: provider.label, + event: { added: [], removed: [session], changed: [] } + }); + }); + + test('onDidChangeSessions', async () => { + const emitter = new Emitter(); + const provider = createProvider({ + onDidChangeSessions: emitter.event, + getSessions: async () => [] + }); + authenticationService.registerAuthenticationProvider(provider.id, provider); + + const changed = Event.toPromise(authenticationService.onDidChangeSessions); + const session = createSession(); + emitter.fire({ added: [], removed: [], changed: [session] }); + + const result = await changed; + assert.deepEqual(result, { + providerId: provider.id, + label: provider.label, + event: { added: [], removed: [], changed: [session] } + }); + }); + }); +}); diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index 8b833a0ce2a..40cea524423 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -641,7 +641,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat quickPickItems.push({ type: 'separator', label: localize('signed in', "Signed in") }); for (const authenticationProvider of authenticationProviders) { const accounts = (allAccounts.get(authenticationProvider.id) || []).sort(({ sessionId }) => sessionId === this.currentSessionId ? -1 : 1); - const providerName = this.authenticationService.getLabel(authenticationProvider.id); + const providerName = this.authenticationService.getProvider(authenticationProvider.id).label; for (const account of accounts) { quickPickItems.push({ label: `${account.accountName} (${providerName})`, @@ -656,8 +656,9 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat // Account Providers for (const authenticationProvider of authenticationProviders) { - if (!allAccounts.has(authenticationProvider.id) || this.authenticationService.supportsMultipleAccounts(authenticationProvider.id)) { - const providerName = this.authenticationService.getLabel(authenticationProvider.id); + const provider = this.authenticationService.getProvider(authenticationProvider.id); + if (!allAccounts.has(authenticationProvider.id) || provider.supportsMultipleAccounts) { + const providerName = provider.label; quickPickItems.push({ label: localize('sign in using account', "Sign in with {0}", providerName), authenticationProvider }); } } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 61028e388dd..cdc45b0880b 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -104,6 +104,9 @@ import 'vs/workbench/services/views/browser/viewsService'; import 'vs/workbench/services/quickinput/browser/quickInputService'; import 'vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService'; import 'vs/workbench/services/authentication/browser/authenticationService'; +import 'vs/workbench/services/authentication/browser/authenticationExtensionsService'; +import 'vs/workbench/services/authentication/browser/authenticationUsageService'; +import 'vs/workbench/services/authentication/browser/authenticationAccessService'; import 'vs/editor/browser/services/hoverService/hoverService'; import 'vs/workbench/services/assignment/common/assignmentService'; import 'vs/workbench/services/outline/browser/outlineService'; @@ -341,6 +344,9 @@ import 'vs/workbench/contrib/languageDetection/browser/languageDetection.contrib // Language Status import 'vs/workbench/contrib/languageStatus/browser/languageStatus.contribution'; +// Authentication +import 'vs/workbench/contrib/authentication/browser/authentication.contribution'; + // User Data Sync import 'vs/workbench/contrib/userDataSync/browser/userDataSync.contribution'; From 00abefa3e27ee1866cf6a6ac825578c1ddafd32c Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 29 Feb 2024 11:52:12 -0300 Subject: [PATCH 1688/1897] Add chatParticipant contribution point (#206474) * Add package.json registration for chat agents * Update for tests * Separate static and dynamic chat agent parts * Handle participant registration correctly * Fix tests * Fix test * Remove commented code * Fix more tests * Pluralize * Pluralize test contribution --- extensions/vscode-api-tests/package.json | 13 ++ .../src/singlefolder-tests/chat.test.ts | 10 -- .../api/browser/mainThreadChatAgents2.ts | 54 +++--- .../workbench/api/common/extHost.protocol.ts | 12 +- .../api/common/extHostChatAgents2.ts | 70 +------- .../contrib/chat/browser/chat.contribution.ts | 56 +++--- .../browser/chatContributionServiceImpl.ts | 99 ++++++++++- .../contrib/chat/browser/chatListRenderer.ts | 4 +- .../browser/contrib/chatInputEditorContrib.ts | 23 +-- .../contrib/chat/common/chatAgents.ts | 166 +++++++++++++----- .../chat/common/chatContributionService.ts | 25 +++ .../contrib/chat/common/chatModel.ts | 7 +- .../contrib/chat/common/chatParserTypes.ts | 4 +- .../contrib/chat/common/chatRequestParser.ts | 5 +- .../contrib/chat/common/chatServiceImpl.ts | 17 +- .../contrib/chat/common/voiceChat.ts | 15 +- ..._agent_and_subcommand_after_newline.0.snap | 8 +- ..._subcommand_with_leading_whitespace.0.snap | 8 +- ...uestParser_agent_with_question_mark.0.snap | 8 +- ...er_agent_with_subcommand_after_text.0.snap | 8 +- ...hatRequestParser_agents__subCommand.0.snap | 8 +- ..._agents_and_variables_and_multiline.0.snap | 8 +- ..._and_variables_and_multiline__part2.0.snap | 8 +- .../__snapshots__/Chat_can_deserialize.0.snap | 5 +- .../__snapshots__/Chat_can_serialize.1.snap | 5 +- .../test/common/chatRequestParser.test.ts | 20 +-- .../chat/test/common/chatService.test.ts | 32 ++-- .../common/mockChatContributionService.ts | 32 ++++ .../chat/test/common/voiceChat.test.ts | 15 +- .../browser/inlineChatController.ts | 4 +- .../vscode.proposed.chatParticipant.d.ts | 53 ------ ...ode.proposed.chatParticipantAdditions.d.ts | 31 ++-- 32 files changed, 477 insertions(+), 356 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/test/common/mockChatContributionService.ts diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index e9323fc9c43..b0b56a3fdd9 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -64,6 +64,19 @@ }, "icon": "media/icon.png", "contributes": { + "chatParticipants": [ + { + "name": "participant", + "description": "test", + "isDefault": true, + "commands": [ + { + "name": "hello", + "description": "Hello" + } + ] + } + ], "configuration": { "type": "object", "title": "Test Config", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index 6fb0262e132..9877eb1dd92 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -45,11 +45,6 @@ suite('chat', () => { return null; }); participant.isDefault = true; - participant.commandProvider = { - provideCommands: (_token) => { - return [{ name: 'hello', description: 'Hello' }]; - } - }; disposables.push(participant); return emitter.event; } @@ -102,11 +97,6 @@ suite('chat', () => { return { metadata: { key: 'value' } }; }); participant.isDefault = true; - participant.commandProvider = { - provideCommands: (_token) => { - return [{ name: 'hello', description: 'Hello' }]; - } - }; participant.followupProvider = { provideFollowups(result, _token) { deferred.complete(result); diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index c143be65078..cf3d1e134dd 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -19,8 +19,8 @@ import { ExtHostChatAgentsShape2, ExtHostContext, IChatProgressDto, IExtensionCh import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { AddDynamicVariableAction, IAddDynamicVariableContext } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables'; -import { IChatAgentCommand, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatAgentImplementation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { ChatRequestAgentPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatFollowup, IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; @@ -29,7 +29,6 @@ import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/ext type AgentData = { dispose: () => void; name: string; - hasSlashCommands?: boolean; hasFollowups?: boolean; }; @@ -49,6 +48,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IChatContributionService private readonly _chatContributionService: IChatContributionService, ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatAgents2); @@ -76,12 +76,13 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA this._agents.deleteAndDispose(handle); } - $registerAgent(handle: number, extension: ExtensionIdentifier, name: string, metadata: IExtensionChatAgentMetadata): void { - const lastSlashCommands: WeakMap = new WeakMap(); - const d = this._chatAgentService.registerAgent({ - id: name, - extensionId: extension, - metadata: revive(metadata), + $registerAgent(handle: number, extension: ExtensionIdentifier, name: string, metadata: IExtensionChatAgentMetadata, allowDynamic: boolean): void { + const staticAgentRegistration = this._chatContributionService.registeredParticipants.find(p => p.extensionId.value === extension.value && p.name === name); + if (!staticAgentRegistration && !allowDynamic) { + throw new Error(`chatParticipant must be declared in package.json: ${name}`); + } + + const impl: IChatAgentImplementation = { invoke: async (request, progress, history, token) => { this._pendingProgress.set(request.requestId, progress); try { @@ -97,31 +98,31 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA return this._proxy.$provideFollowups(request, handle, result, token); }, - getLastSlashCommands: (model: IChatModel) => { - return lastSlashCommands.get(model); - }, - provideSlashCommands: async (model, history, token) => { - if (!this._agents.get(handle)?.hasSlashCommands) { - return []; // save an IPC call - } - const commands = await this._proxy.$provideSlashCommands(handle, { history }, token); - if (model) { - lastSlashCommands.set(model, commands); - } - - return commands; - }, provideWelcomeMessage: (token: CancellationToken) => { return this._proxy.$provideWelcomeMessage(handle, token); }, provideSampleQuestions: (token: CancellationToken) => { return this._proxy.$provideSampleQuestions(handle, token); } - }); + }; + + let disposable: IDisposable; + if (!staticAgentRegistration && allowDynamic) { + disposable = this._chatAgentService.registerDynamicAgent( + { + id: name, + extensionId: extension, + metadata: revive(metadata), + slashCommands: [], + }, + impl); + } else { + disposable = this._chatAgentService.registerAgent(name, impl); + } + this._agents.set(handle, { name, - dispose: d.dispose, - hasSlashCommands: metadata.hasSlashCommands, + dispose: disposable.dispose, hasFollowups: metadata.hasFollowups }); } @@ -131,7 +132,6 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA if (!data) { throw new Error(`No agent with handle ${handle} registered`); } - data.hasSlashCommands = metadataUpdate.hasSlashCommands; data.hasFollowups = metadataUpdate.hasFollowups; this._chatAgentService.updateAgent(data.name, revive(metadataUpdate)); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index dda01854f21..d64dbc0af3f 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -50,12 +50,12 @@ import * as tasks from 'vs/workbench/api/common/shared/tasks'; import { SaveReason } from 'vs/workbench/common/editor'; import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; -import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata } from 'vs/workbench/contrib/chat/common/languageModels'; -import { IChatDynamicRequest, IChatProgress, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatDynamicRequest, IChatFollowup, IChatProgress, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue, IChatVariableData, IChatVariableResolverProgress } from 'vs/workbench/contrib/chat/common/chatVariables'; -import { DebugConfigurationProviderTriggerKind, MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; +import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata } from 'vs/workbench/contrib/chat/common/languageModels'; +import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem, MainThreadDebugVisualization } from 'vs/workbench/contrib/debug/common/debug'; import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatFollowup, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; @@ -1196,12 +1196,11 @@ export interface ExtHostLanguageModelsShape { } export interface IExtensionChatAgentMetadata extends Dto { - hasSlashCommands?: boolean; hasFollowups?: boolean; } export interface MainThreadChatAgentsShape2 extends IDisposable { - $registerAgent(handle: number, extension: ExtensionIdentifier, name: string, metadata: IExtensionChatAgentMetadata): void; + $registerAgent(handle: number, extension: ExtensionIdentifier, name: string, metadata: IExtensionChatAgentMetadata, allowDynamic: boolean): void; $registerAgentCompletionsProvider(handle: number, triggerCharacters: string[]): void; $unregisterAgentCompletionsProvider(handle: number): void; $updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void; @@ -1228,7 +1227,6 @@ export type IChatAgentHistoryEntryDto = { export interface ExtHostChatAgentsShape2 { $invokeAgent(handle: number, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; - $provideSlashCommands(handle: number, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, token: CancellationToken): Promise; $acceptFeedback(handle: number, result: IChatAgentResult, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void; $acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 8166c1e7d33..13bedf2b1e8 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -21,7 +21,7 @@ import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEnt import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { IChatAgentCommand, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatFollowup, IChatProgress, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; @@ -171,7 +171,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const agent = new ExtHostChatAgent(extension, name, this._proxy, handle, handler); this._agents.set(handle, agent); - this._proxy.$registerAgent(handle, extension.identifier, name, {}); + this._proxy.$registerAgent(handle, extension.identifier, name, {}, isProposedApiEnabled(extension, 'chatParticipantAdditions')); return agent.apiAgent; } @@ -245,23 +245,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { this._sessionDisposables.deleteAndDispose(sessionId); } - async $provideSlashCommands(handle: number, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise { - const agent = this._agents.get(handle); - if (!agent) { - // this is OK, the agent might have disposed while the request was in flight - return []; - } - - const convertedHistory = await this.prepareHistoryTurns(agent.id, context); - try { - return await agent.provideSlashCommands({ history: convertedHistory }, token); - } catch (err) { - const msg = toErrorMessage(err); - this._logService.error(`[${agent.extension.identifier.value}] [@${agent.id}] Error while providing slash commands: ${msg}`); - return []; - } - } - async $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { @@ -276,7 +259,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { this._agents.values(), a => a.id === f.participant && ExtensionIdentifier.equals(a.extension.identifier, agent.extension.identifier)); if (!isValid) { - this._logService.warn(`[@${agent.id}] ChatFollowup refers to an invalid participant: ${f.participant}`); + this._logService.warn(`[@${agent.id}] ChatFollowup refers to an unknown participant: ${f.participant}`); } return isValid; }) @@ -352,7 +335,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { class ExtHostChatAgent { - private _commandProvider: vscode.ChatCommandProvider | undefined; private _followupProvider: vscode.ChatFollowupProvider | undefined; private _description: string | undefined; private _fullName: string | undefined; @@ -394,30 +376,6 @@ class ExtHostChatAgent { return await this._agentVariableProvider.provider.provideCompletionItems(query, token) ?? []; } - async provideSlashCommands(context: vscode.ChatContext, token: CancellationToken): Promise { - if (!this._commandProvider) { - return []; - } - const result = await this._commandProvider.provideCommands(context, token); - if (!result) { - return []; - } - return result - .map(c => { - if ('isSticky2' in c) { - checkProposedApiEnabled(this.extension, 'chatParticipantAdditions'); - } - - return { - name: c.name, - description: c.description ?? '', - followupPlaceholder: c.isSticky2?.placeholder, - isSticky: c.isSticky2?.isSticky ?? c.isSticky, - sampleRequest: c.sampleRequest - } satisfies IChatAgentCommand; - }); - } - async provideFollowups(result: vscode.ChatResult, token: CancellationToken): Promise { if (!this._followupProvider) { return []; @@ -485,9 +443,7 @@ class ExtHostChatAgent { 'dark' in this._iconPath ? this._iconPath.dark : undefined, themeIcon: this._iconPath instanceof extHostTypes.ThemeIcon ? this._iconPath : undefined, - hasSlashCommands: this._commandProvider !== undefined, hasFollowups: this._followupProvider !== undefined, - isDefault: this._isDefault, isSecondary: this._isSecondary, helpTextPrefix: (!this._helpTextPrefix || typeof this._helpTextPrefix === 'string') ? this._helpTextPrefix : typeConvert.MarkdownString.from(this._helpTextPrefix), helpTextVariablesPrefix: (!this._helpTextVariablesPrefix || typeof this._helpTextVariablesPrefix === 'string') ? this._helpTextVariablesPrefix : typeConvert.MarkdownString.from(this._helpTextVariablesPrefix), @@ -535,13 +491,6 @@ class ExtHostChatAgent { assertType(typeof v === 'function', 'Invalid request handler'); that._requestHandler = v; }, - get commandProvider() { - return that._commandProvider; - }, - set commandProvider(v) { - that._commandProvider = v; - updateMetadataSoon(); - }, get followupProvider() { return that._followupProvider; }, @@ -564,10 +513,6 @@ class ExtHostChatAgent { }, set helpTextPrefix(v) { checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); - if (!that._isDefault) { - throw new Error('helpTextPrefix is only available on the default chat agent'); - } - that._helpTextPrefix = v; updateMetadataSoon(); }, @@ -577,10 +522,6 @@ class ExtHostChatAgent { }, set helpTextVariablesPrefix(v) { checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); - if (!that._isDefault) { - throw new Error('helpTextVariablesPrefix is only available on the default chat agent'); - } - that._helpTextVariablesPrefix = v; updateMetadataSoon(); }, @@ -590,10 +531,6 @@ class ExtHostChatAgent { }, set helpTextPostfix(v) { checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); - if (!that._isDefault) { - throw new Error('helpTextPostfix is only available on the default chat agent'); - } - that._helpTextPostfix = v; updateMetadataSoon(); }, @@ -664,7 +601,6 @@ class ExtHostChatAgent { }, dispose() { disposed = true; - that._commandProvider = undefined; that._followupProvider = undefined; that._onDidReceiveFeedback.dispose(); that._proxy.$unregisterAgent(that._handle); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 0d34b49aff8..000c78ced65 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -3,62 +3,61 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IMarkdownString, MarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isMacintosh } from 'vs/base/common/platform'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import * as nls from 'vs/nls'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { IWorkbenchContributionsRegistry, WorkbenchPhase, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { alertFocusChange } from 'vs/workbench/contrib/accessibility/browser/accessibilityContributions'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { registerChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; +import { ACTION_ID_NEW_CHAT, registerNewChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; import { registerChatCodeBlockActions } from 'vs/workbench/contrib/chat/browser/actions/chatCodeblockActions'; import { registerChatCopyActions } from 'vs/workbench/contrib/chat/browser/actions/chatCopyActions'; import { IChatExecuteActionContext, SubmitAction, registerChatExecuteActions } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; +import { registerChatFileTreeActions } from 'vs/workbench/contrib/chat/browser/actions/chatFileTreeActions'; +import { registerChatExportActions } from 'vs/workbench/contrib/chat/browser/actions/chatImportExport'; +import { registerMoveActions } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions'; import { registerQuickChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions'; import { registerChatTitleActions } from 'vs/workbench/contrib/chat/browser/actions/chatTitleActions'; -import { registerChatExportActions } from 'vs/workbench/contrib/chat/browser/actions/chatImportExport'; import { IChatAccessibilityService, IChatWidget, IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat'; +import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chatAccessibilityService'; import { ChatContributionService } from 'vs/workbench/contrib/chat/browser/chatContributionServiceImpl'; import { ChatEditor, IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; import { ChatEditorInput, ChatEditorInputSerializer } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; +import { QuickChatService } from 'vs/workbench/contrib/chat/browser/chatQuick'; +import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; import { ChatWidgetService } from 'vs/workbench/contrib/chat/browser/chatWidget'; -import 'vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib'; import 'vs/workbench/contrib/chat/browser/contrib/chatHistoryVariables'; +import 'vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib'; +import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; +import { ChatWelcomeMessageModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; +import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; +import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { ChatWidgetHistoryService, IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService'; +import { ILanguageModelsService, LanguageModelsService } from 'vs/workbench/contrib/chat/common/languageModels'; +import { IVoiceChatService, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import '../common/chatColors'; -import { registerMoveActions } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions'; -import { ACTION_ID_NEW_CHAT, registerNewChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; -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'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { ChatWelcomeMessageModel } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IMarkdownString, MarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; -import { LanguageModelsService, ILanguageModelsService } from 'vs/workbench/contrib/chat/common/languageModels'; -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'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; -import { registerChatFileTreeActions } from 'vs/workbench/contrib/chat/browser/actions/chatFileTreeActions'; -import { QuickChatService } from 'vs/workbench/contrib/chat/browser/chatQuick'; -import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; -import { chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IVoiceChatService, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -246,7 +245,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { executeImmediately: true }, async (prompt, progress) => { const defaultAgent = chatAgentService.getDefaultAgent(); - const agents = chatAgentService.getAgents(); + const agents = chatAgentService.getRegisteredAgents(); // Report prefix if (defaultAgent?.metadata.helpTextPrefix) { @@ -266,8 +265,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { const actionArg: IChatExecuteActionContext = { inputValue: `${agentWithLeader} ${a.metadata.sampleRequest}` }; const urlSafeArg = encodeURIComponent(JSON.stringify(actionArg)); const agentLine = `* [\`${agentWithLeader}\`](command:${SubmitAction.ID}?${urlSafeArg}) - ${a.metadata.description}`; - const commands = await a.provideSlashCommands(undefined, [], CancellationToken.None); - const commandText = commands.map(c => { + const commandText = a.slashCommands.map(c => { const actionArg: IChatExecuteActionContext = { inputValue: `${agentWithLeader} ${chatSubcommandLeader}${c.name} ${c.sampleRequest ?? ''}` }; const urlSafeArg = encodeURIComponent(JSON.stringify(actionArg)); return `\t* [\`${chatSubcommandLeader}${c.name}\`](command:${SubmitAction.ID}?${urlSafeArg}) - ${c.description}`; diff --git a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts index 8498292395a..3e70653b2f0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts @@ -18,10 +18,9 @@ import { getNewChatAction } from 'vs/workbench/contrib/chat/browser/actions/chat import { getMoveToEditorAction, getMoveToNewWindowAction } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions'; import { getQuickChatActionForProvider } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions'; import { CHAT_SIDEBAR_PANEL_ID, ChatViewPane, IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane'; -import { IChatContributionService, IChatProviderContribution, IRawChatProviderContribution } from 'vs/workbench/contrib/chat/common/chatContributionService'; +import { IChatContributionService, IChatParticipantContribution, IChatProviderContribution, IRawChatParticipantContribution, IRawChatProviderContribution } from 'vs/workbench/contrib/chat/common/chatContributionService'; import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry'; - const chatExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'interactiveSession', jsonSchema: { @@ -59,6 +58,71 @@ const chatExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensi }, }); +const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'chatParticipants', + jsonSchema: { + description: localize('vscode.extension.contributes.chatParticipant', 'Contributes a Chat Participant'), + type: 'array', + items: { + additionalProperties: false, + type: 'object', + defaultSnippets: [{ body: { name: '', description: '' } }], + required: ['name'], + properties: { + name: { + description: localize('chatParticipantName', "Unique name for this Chat Participant."), + type: 'string' + }, + description: { + description: localize('chatParticipantDescription', "A description of this Chat Participant, shown in the UI."), + type: 'string' + }, + isDefault: { + markdownDescription: localize('chatParticipantIsDefaultDescription', "**Only** allowed for extensions that have the `defaultChatParticipant` proposal."), + type: 'boolean', + }, + commands: { + markdownDescription: localize('chatCommandsDescription', "Commands available for this Chat Participant, which the user can invoke with a `/`."), + type: 'array', + items: { + additionalProperties: false, + type: 'object', + defaultSnippets: [{ body: { name: '', description: '' } }], + required: ['name', 'description'], + properties: { + name: { + description: localize('chatCommand', "A short name by which this command is referred to in the UI, e.g. `fix` or * `explain` for commands that fix an issue or explain code. The name should be unique among the commands provided by this participant."), + type: 'string' + }, + description: { + description: localize('chatCommandDescription', "A description of this command."), + type: 'string' + }, + when: { + description: localize('chatCommandDescription', "A description of this command."), + type: 'string' + }, + sampleRequest: { + description: localize('chatCommandDescription', "A description of this command."), + type: 'string' + }, + isSticky: { + description: localize('chatCommandDescription', "A description of this command."), + type: 'boolean' + }, + } + } + } + } + } + }, + activationEventsGenerator: (contributions: IRawChatParticipantContribution[], result: { push(item: string): void }) => { + for (const contrib of contributions) { + result.push(`onChatParticipant:${contrib.name}`); + } + }, +}); + export class ChatExtensionPointHandler implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.chatExtensionPointHandler'; @@ -96,6 +160,20 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { } } }); + + chatParticipantExtensionPoint.setHandler((extensions, delta) => { + for (const extension of delta.added) { + for (const providerDescriptor of extension.value) { + this._chatContributionService.registerChatParticipant({ ...providerDescriptor, extensionId: extension.description.identifier }); + } + } + + for (const extension of delta.removed) { + for (const providerDescriptor of extension.value) { + this._chatContributionService.deregisterChatParticipant({ ...providerDescriptor, extensionId: extension.description.identifier }); + } + } + }); } private registerViewContainer(): ViewContainer { @@ -156,10 +234,15 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchPhase.BlockStartup); +function getParticipantKey(participant: IChatParticipantContribution): string { + return `${participant.extensionId.value}_${participant.name}`; +} + export class ChatContributionService implements IChatContributionService { declare _serviceBrand: undefined; private _registeredProviders = new Map(); + private _registeredParticipants = new Map(); constructor( ) { } @@ -176,7 +259,19 @@ export class ChatContributionService implements IChatContributionService { this._registeredProviders.delete(providerId); } + public registerChatParticipant(participant: IChatParticipantContribution): void { + this._registeredParticipants.set(getParticipantKey(participant), participant); + } + + public deregisterChatParticipant(participant: IChatParticipantContribution): void { + this._registeredParticipants.delete(getParticipantKey(participant)); + } + public get registeredProviders(): IChatProviderContribution[] { return Array.from(this._registeredProviders.values()); } + + public get registeredParticipants(): IChatParticipantContribution[] { + return Array.from(this._registeredParticipants.values()); + } } diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 93b651276e5..94b0a9b2dc1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -360,7 +360,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer !a.metadata.isDefault); + const agents = this.chatAgentService.getRegisteredAgents() + .filter(a => !a.isDefault); return { suggestions: agents.map((c, i) => { const withAt = `@${c.id}`; @@ -399,10 +397,8 @@ class AgentCompletions extends Disposable { } const usedAgent = parsedRequest[usedAgentIdx] as ChatRequestAgentPart; - const commands = await usedAgent.agent.provideSlashCommands(widget.viewModel.model, getHistoryEntriesFromModel(widget.viewModel.model), token); // Refresh the cache here - return { - suggestions: commands.map((c, i) => { + suggestions: usedAgent.agent.slashCommands.map((c, i) => { const withSlash = `/${c.name}`; return { label: withSlash, @@ -432,16 +428,9 @@ class AgentCompletions extends Disposable { return null; } - const agents = this.chatAgentService.getAgents(); - const all = agents.map(agent => agent.provideSlashCommands(viewModel.model, getHistoryEntriesFromModel(viewModel.model), token)); - const commands = await raceCancellation(Promise.all(all), token); - - if (!commands) { - return; - } - + const agents = this.chatAgentService.getRegisteredAgents(); const justAgents: CompletionItem[] = agents - .filter(a => !a.metadata.isDefault) + .filter(a => !a.isDefault) .map(agent => { const agentLabel = `${chatAgentLeader}${agent.id}`; return { @@ -456,7 +445,7 @@ class AgentCompletions extends Disposable { return { suggestions: justAgents.concat( - agents.flatMap((agent, i) => commands[i].map((c, i) => { + agents.flatMap(agent => agent.slashCommands.map((c, i) => { const agentLabel = `${chatAgentLeader}${agent.id}`; const withSlash = `${chatSubcommandLeader}${c.name}`; return { diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index b1ef64dae9d..b9c3a5d4508 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -11,9 +11,11 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { ProviderResult } from 'vs/editor/common/languages'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IChatModel, IChatProgressResponseContent, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; +import { IChatProgressResponseContent, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; //#region agent service, commands etc @@ -27,18 +29,21 @@ export interface IChatAgentHistoryEntry { export interface IChatAgentData { id: string; extensionId: ExtensionIdentifier; + /** The agent invoked when no agent is specified */ + isDefault?: boolean; metadata: IChatAgentMetadata; + slashCommands: IChatAgentCommand[]; } -export interface IChatAgent extends IChatAgentData { +export interface IChatAgentImplementation { invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; provideFollowups?(request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise; - getLastSlashCommands(model: IChatModel): IChatAgentCommand[] | undefined; - provideSlashCommands(model: IChatModel | undefined, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>; provideSampleQuestions?(token: CancellationToken): ProviderResult; } +export type IChatAgent = IChatAgentData & IChatAgentImplementation; + export interface IChatAgentCommand { name: string; description: string; @@ -68,7 +73,6 @@ export interface IChatAgentCommand { export interface IChatAgentMetadata { description?: string; - isDefault?: boolean; // The agent invoked when no agent is specified helpTextPrefix?: string | IMarkdownString; helpTextVariablesPrefix?: string | IMarkdownString; helpTextPostfix?: string | IMarkdownString; @@ -108,17 +112,18 @@ export const IChatAgentService = createDecorator('chatAgentSe export interface IChatAgentService { _serviceBrand: undefined; /** - * undefined when an agent was removed + * undefined when an agent was removed IChatAgent */ readonly onDidChangeAgents: Event; - registerAgent(agent: IChatAgent): IDisposable; + registerAgent(name: string, agent: IChatAgentImplementation): IDisposable; + registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable; invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise; - getAgents(): Array; - getAgent(id: string): IChatAgent | undefined; + getRegisteredAgents(): Array; + getActivatedAgents(): Array; + getRegisteredAgent(id: string): IChatAgentData | undefined; getDefaultAgent(): IChatAgent | undefined; - getSecondaryAgent(): IChatAgent | undefined; - hasAgent(id: string): boolean; + getSecondaryAgent(): IChatAgentData | undefined; updateAgent(id: string, updateMetadata: IChatAgentMetadata): void; } @@ -128,79 +133,160 @@ export class ChatAgentService extends Disposable implements IChatAgentService { declare _serviceBrand: undefined; - private readonly _agents = new Map(); + private readonly _agents = new Map(); private readonly _onDidChangeAgents = this._register(new Emitter()); readonly onDidChangeAgents: Event = this._onDidChangeAgents.event; + constructor( + @IChatContributionService private chatContributionService: IChatContributionService, + @IContextKeyService private contextKeyService: IContextKeyService, + ) { + super(); + } + override dispose(): void { super.dispose(); this._agents.clear(); } - registerAgent(agent: IChatAgent): IDisposable { - if (this._agents.has(agent.id)) { - throw new Error(`Already registered an agent with id ${agent.id}`); + registerAgent(name: string, agentImpl: IChatAgentImplementation): IDisposable { + if (this._agents.has(name)) { + // TODO not keyed by name, dupes allowed between extensions + throw new Error(`Already registered an agent with id ${name}`); } - this._agents.set(agent.id, { agent }); - this._onDidChangeAgents.fire(agent); + + const data = this.getRegisteredAgent(name); + if (!data) { + throw new Error(`Unknown agent: ${name}`); + } + + const agent = { data: data, impl: agentImpl }; + this._agents.set(name, agent); + this._onDidChangeAgents.fire(new MergedChatAgent(data, agentImpl)); return toDisposable(() => { - if (this._agents.delete(agent.id)) { + if (this._agents.delete(name)) { + this._onDidChangeAgents.fire(undefined); + } + }); + } + + registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable { + const agent = { data, impl: agentImpl }; + this._agents.set(data.id, agent); + this._onDidChangeAgents.fire(new MergedChatAgent(data, agentImpl)); + + return toDisposable(() => { + if (this._agents.delete(data.id)) { this._onDidChangeAgents.fire(undefined); } }); } updateAgent(id: string, updateMetadata: IChatAgentMetadata): void { - const data = this._agents.get(id); - if (!data) { - throw new Error(`No agent with id ${id} registered`); + const agent = this._agents.get(id); + if (!agent?.impl) { + throw new Error(`No activated agent with id ${id} registered`); } - data.agent.metadata = { ...data.agent.metadata, ...updateMetadata }; - this._onDidChangeAgents.fire(data.agent); + agent.data.metadata = { ...agent.data.metadata, ...updateMetadata }; + this._onDidChangeAgents.fire(new MergedChatAgent(agent.data, agent.impl)); } getDefaultAgent(): IChatAgent | undefined { - return Iterable.find(this._agents.values(), a => !!a.agent.metadata.isDefault)?.agent; + return this.getActivatedAgents().find(a => !!a.isDefault); } - getSecondaryAgent(): IChatAgent | undefined { - return Iterable.find(this._agents.values(), a => !!a.agent.metadata.isSecondary)?.agent; + getSecondaryAgent(): IChatAgentData | undefined { + // TODO also static + return Iterable.find(this._agents.values(), a => !!a.data.metadata.isSecondary)?.data; } - getAgents(): Array { - return Array.from(this._agents.values(), v => v.agent); + getRegisteredAgents(): Array { + const that = this; + return this.chatContributionService.registeredParticipants.map(p => ( + { + extensionId: p.extensionId, + id: p.name, + metadata: { description: p.description }, + isDefault: p.isDefault, + get slashCommands() { + const commands = p.commands ?? []; + return commands.filter(c => !c.when || that.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(c.when))); + } + } satisfies IChatAgentData)); } - hasAgent(id: string): boolean { - return this._agents.has(id); + getActivatedAgents(): IChatAgent[] { + return Array.from(this._agents.values()) + .filter(a => !!a.impl) + .map(a => new MergedChatAgent(a.data, a.impl!)); } - getAgent(id: string): IChatAgent | undefined { - const data = this._agents.get(id); - return data?.agent; + getRegisteredAgent(id: string): IChatAgentData | undefined { + return this.getRegisteredAgents().find(a => a.id === id); } async invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { const data = this._agents.get(id); - if (!data) { - throw new Error(`No agent with id ${id}`); + if (!data?.impl) { + throw new Error(`No activated agent with id ${id}`); } - return await data.agent.invoke(request, progress, history, token); + return await data.impl.invoke(request, progress, history, token); } async getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise { const data = this._agents.get(id); - if (!data) { - throw new Error(`No agent with id ${id}`); + if (!data?.impl) { + throw new Error(`No activated agent with id ${id}`); } - if (!data.agent.provideFollowups) { + if (!data.impl?.provideFollowups) { return []; } - return data.agent.provideFollowups(request, result, token); + return data.impl.provideFollowups(request, result, token); + } +} + +export class MergedChatAgent implements IChatAgent { + constructor( + private readonly data: IChatAgentData, + private readonly impl: IChatAgentImplementation + ) { } + + get id(): string { return this.data.id; } + get extensionId(): ExtensionIdentifier { return this.data.extensionId; } + get isDefault(): boolean | undefined { return this.data.isDefault; } + get metadata(): IChatAgentMetadata { return this.data.metadata; } + get slashCommands(): IChatAgentCommand[] { return this.data.slashCommands; } + + async invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { + return this.impl.invoke(request, progress, history, token); + } + + async provideFollowups(request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise { + if (this.impl.provideFollowups) { + return this.impl.provideFollowups(request, result, token); + } + + return []; + } + + provideWelcomeMessage(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined> { + if (this.impl.provideWelcomeMessage) { + return this.impl.provideWelcomeMessage(token); + } + + return undefined; + } + + provideSampleQuestions(token: CancellationToken): ProviderResult { + if (this.impl.provideSampleQuestions) { + return this.impl.provideSampleQuestions(token); + } + + return undefined; } } diff --git a/src/vs/workbench/contrib/chat/common/chatContributionService.ts b/src/vs/workbench/contrib/chat/common/chatContributionService.ts index 2c43c2af703..ca022f35aa0 100644 --- a/src/vs/workbench/contrib/chat/common/chatContributionService.ts +++ b/src/vs/workbench/contrib/chat/common/chatContributionService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export interface IChatProviderContribution { @@ -18,6 +19,10 @@ export interface IChatContributionService { registerChatProvider(provider: IChatProviderContribution): void; deregisterChatProvider(providerId: string): void; getViewIdForProvider(providerId: string): string; + + readonly registeredParticipants: IChatParticipantContribution[]; + registerChatParticipant(participant: IChatParticipantContribution): void; + deregisterChatParticipant(participant: IChatParticipantContribution): void; } export interface IRawChatProviderContribution { @@ -26,3 +31,23 @@ export interface IRawChatProviderContribution { icon?: string; when?: string; } + +export interface IRawChatCommandContribution { + name: string; + description: string; + sampleRequest?: string; + isSticky?: boolean; + when?: string; +} + +export interface IRawChatParticipantContribution { + name: string; + description?: string; + isDefault?: boolean; + commands?: IRawChatCommandContribution[]; +} + +export interface IChatParticipantContribution extends IRawChatParticipantContribution { + // Participant id is extensionId + name + extensionId: ExtensionIdentifier; +} diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index c8e47cb8b16..ed06c32958f 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -683,7 +683,7 @@ export class ChatModel extends Disposable implements IChatModel { } else if (progress.kind === 'usedContext' || progress.kind === 'reference') { request.response.applyReference(progress); } else if (progress.kind === 'agentDetection') { - const agent = this.chatAgentService.getAgent(progress.agentName); + const agent = this.chatAgentService.getRegisteredAgent(progress.agentName); if (agent) { request.response.setAgent(agent, progress.command); } @@ -780,7 +780,10 @@ export class ChatModel extends Disposable implements IChatModel { followups: r.response?.followups, isCanceled: r.response?.isCanceled, vote: r.response?.vote, - agent: r.response?.agent ? { id: r.response.agent.id, extensionId: r.response.agent.extensionId, metadata: r.response.agent.metadata } : undefined, // May actually be the full IChatAgent instance, just take the data props + agent: r.response?.agent ? + // May actually be the full IChatAgent instance, just take the data props. slashCommands don't matter here. + { id: r.response.agent.id, extensionId: r.response.agent.extensionId, metadata: r.response.agent.metadata, slashCommands: [] } + : undefined, slashCommand: r.response?.slashCommand, usedContext: r.response?.usedContext, contentReferences: r.response?.contentReferences diff --git a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts index 4f7654fd56a..e6bb4e56bf9 100644 --- a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts +++ b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts @@ -6,7 +6,7 @@ import { revive } from 'vs/base/common/marshalling'; import { IOffsetRange, OffsetRange } from 'vs/editor/common/core/offsetRange'; import { IRange } from 'vs/editor/common/core/range'; -import { IChatAgent, IChatAgentCommand } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatSlashData } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -72,7 +72,7 @@ export class ChatRequestVariablePart implements IParsedChatRequestPart { export class ChatRequestAgentPart implements IParsedChatRequestPart { static readonly Kind = 'agent'; readonly kind = ChatRequestAgentPart.Kind; - constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly agent: IChatAgent) { } + constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly agent: IChatAgentData) { } get text(): string { return `${chatAgentLeader}${this.agent.id}`; diff --git a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts index aa05f52b6d8..ca2057b6342 100644 --- a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts +++ b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts @@ -99,7 +99,7 @@ export class ChatRequestParser { const varRange = new OffsetRange(offset, offset + full.length); const varEditorRange = new Range(position.lineNumber, position.column, position.lineNumber, position.column + full.length); - const agent = this.agentService.getAgent(name); + const agent = this.agentService.getRegisteredAgent(name); if (!agent) { return; } @@ -171,8 +171,7 @@ export class ChatRequestParser { return; } - const subCommands = usedAgent.agent.getLastSlashCommands(model); - const subCommand = subCommands?.find(c => c.name === command); + const subCommand = usedAgent.agent.slashCommands.find(c => c.name === command); if (subCommand) { // Valid agent subcommand return new ChatRequestAgentSubcommandPart(slashRange, slashEditorRange, subCommand); diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index e28b1e14e28..a4a77bb0ba2 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -20,15 +20,15 @@ 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 { IChatAgent, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, ISerializableChatData, ISerializableChatsData, getHistoryEntriesFromModel, updateRanges } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/languageModels'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { ChatCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/languageModels'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; const serializedChatKey = 'interactive.sessions'; @@ -193,12 +193,6 @@ export class ChatService extends Disposable implements IChatService { } this._register(storageService.onWillSaveState(() => this.saveState())); - - this._register(Event.debounce(this.chatAgentService.onDidChangeAgents, () => { }, 500)(() => { - for (const model of this._sessionModels.values()) { - this.warmSlashCommandCache(model); - } - })); } private saveState(): void { @@ -364,15 +358,9 @@ export class ChatService extends Disposable implements IChatService { this.initializeSession(model, CancellationToken.None); } - private warmSlashCommandCache(model: IChatModel, agent?: IChatAgent) { - const agents = agent ? [agent] : this.chatAgentService.getAgents(); - agents.forEach(agent => agent.provideSlashCommands(model, [], CancellationToken.None)); - } - private async initializeSession(model: ChatModel, token: CancellationToken): Promise { try { this.trace('initializeSession', `Initialize session ${model.sessionId}`); - this.warmSlashCommandCache(model); model.startInitialize(); await this.extensionService.activateByEvent(`onInteractiveSession:${model.providerId}`); @@ -543,6 +531,7 @@ export class ChatService extends Disposable implements IChatService { const defaultAgent = this.chatAgentService.getDefaultAgent(); if (agentPart || (defaultAgent && !commandPart)) { const agent = (agentPart?.agent ?? defaultAgent)!; + await this.extensionService.activateByEvent(`onChatParticipant:${agent.id}`); const history = getHistoryEntriesFromModel(model); const initVariableData: IChatRequestVariableData = { variables: [] }; diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index 9c92a44354a..5f7208cdd79 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -87,19 +87,16 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { private createPhrases(model?: IChatModel): Map { const phrases = new Map(); - for (const agent of this.chatAgentService.getAgents()) { + for (const agent of this.chatAgentService.getActivatedAgents()) { const agentPhrase = `${VoiceChatService.PHRASES_LOWER[VoiceChatService.AGENT_PREFIX]} ${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase(); phrases.set(agentPhrase, { agent: agent.id }); - const commands = model && agent.getLastSlashCommands(model); - if (commands) { - for (const slashCommand of commands) { - const slashCommandPhrase = `${VoiceChatService.PHRASES_LOWER[VoiceChatService.COMMAND_PREFIX]} ${slashCommand.name}`.toLowerCase(); - phrases.set(slashCommandPhrase, { agent: agent.id, command: slashCommand.name }); + for (const slashCommand of agent.slashCommands) { + const slashCommandPhrase = `${VoiceChatService.PHRASES_LOWER[VoiceChatService.COMMAND_PREFIX]} ${slashCommand.name}`.toLowerCase(); + phrases.set(slashCommandPhrase, { agent: agent.id, command: slashCommand.name }); - const agentSlashCommandPhrase = `${agentPhrase} ${slashCommandPhrase}`.toLowerCase(); - phrases.set(agentSlashCommandPhrase, { agent: agent.id, command: slashCommand.name }); - } + const agentSlashCommandPhrase = `${agentPhrase} ${slashCommandPhrase}`.toLowerCase(); + phrases.set(agentSlashCommandPhrase, { agent: agent.id, command: slashCommand.name }); } } diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap index 4a241114279..ae1818689d0 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap @@ -28,8 +28,12 @@ agent: { id: "agent", metadata: { description: "" }, - provideSlashCommands: [Function provideSlashCommands], - getLastSlashCommands: [Function getLastSlashCommands] + slashCommands: [ + { + name: "subCommand", + description: "" + } + ] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap index becd9bf6f31..190c3555054 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap @@ -28,8 +28,12 @@ agent: { id: "agent", metadata: { description: "" }, - provideSlashCommands: [Function provideSlashCommands], - getLastSlashCommands: [Function getLastSlashCommands] + slashCommands: [ + { + name: "subCommand", + description: "" + } + ] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap index 50c67ea58d0..65d00490273 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap @@ -14,8 +14,12 @@ agent: { id: "agent", metadata: { description: "" }, - provideSlashCommands: [Function provideSlashCommands], - getLastSlashCommands: [Function getLastSlashCommands] + slashCommands: [ + { + name: "subCommand", + description: "" + } + ] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap index 345e8c874de..6585ff1e0e5 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap @@ -14,8 +14,12 @@ agent: { id: "agent", metadata: { description: "" }, - provideSlashCommands: [Function provideSlashCommands], - getLastSlashCommands: [Function getLastSlashCommands] + slashCommands: [ + { + name: "subCommand", + description: "" + } + ] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap index 406e20cfe55..fc2a622c9ac 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap @@ -14,8 +14,12 @@ agent: { id: "agent", metadata: { description: "" }, - provideSlashCommands: [Function provideSlashCommands], - getLastSlashCommands: [Function getLastSlashCommands] + slashCommands: [ + { + name: "subCommand", + description: "" + } + ] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap index 31fd0b94e96..6f9eaa531cf 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap @@ -14,8 +14,12 @@ agent: { id: "agent", metadata: { description: "" }, - provideSlashCommands: [Function provideSlashCommands], - getLastSlashCommands: [Function getLastSlashCommands] + slashCommands: [ + { + name: "subCommand", + description: "" + } + ] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap index 85bc82a3136..fee5731f3e3 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap @@ -14,8 +14,12 @@ agent: { id: "agent", metadata: { description: "" }, - provideSlashCommands: [Function provideSlashCommands], - getLastSlashCommands: [Function getLastSlashCommands] + slashCommands: [ + { + name: "subCommand", + description: "" + } + ] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap index d58da8b3744..9d4646eb6a9 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap @@ -23,7 +23,7 @@ }, agent: { id: "ChatProviderWithUsedContext", - metadata: { } + metadata: { description: undefined } } }, { @@ -54,7 +54,8 @@ value: "nullExtensionDescription", _lower: "nullextensiondescription" }, - metadata: { } + metadata: { description: undefined }, + slashCommands: [ ] }, slashCommand: undefined, usedContext: { diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap index 3a6b248a792..98d57a6bd2b 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap @@ -22,7 +22,7 @@ }, agent: { id: "ChatProviderWithUsedContext", - metadata: { } + metadata: { description: undefined } } }, { @@ -54,7 +54,8 @@ value: "nullExtensionDescription", _lower: "nullextensiondescription" }, - metadata: { } + metadata: { description: undefined }, + slashCommands: [ ] }, slashCommand: undefined, usedContext: { diff --git a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index ba397535bff..e82c0f84940 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -9,7 +9,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { ChatAgentService, IChatAgent, IChatAgentCommand, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatAgentService, IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; @@ -112,12 +112,12 @@ suite('ChatRequestParser', () => { }); const getAgentWithSlashCommands = (slashCommands: IChatAgentCommand[]) => { - return >{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => [], getLastSlashCommands: () => slashCommands }; + return { id: 'agent', metadata: { description: '' }, slashCommands }; }; test('agent with subcommand after text', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); + agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -127,7 +127,7 @@ suite('ChatRequestParser', () => { test('agents, subCommand', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); + agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -137,7 +137,7 @@ suite('ChatRequestParser', () => { test('agent with question mark', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); + agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -147,7 +147,7 @@ suite('ChatRequestParser', () => { test('agent and subcommand with leading whitespace', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); + agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -157,7 +157,7 @@ suite('ChatRequestParser', () => { test('agent and subcommand after newline', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); + agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -167,7 +167,7 @@ suite('ChatRequestParser', () => { test('agent not first', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); + agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -177,7 +177,7 @@ suite('ChatRequestParser', () => { test('agents and variables and multiline', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); + agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); varService.hasVariable.returns(true); @@ -189,7 +189,7 @@ suite('ChatRequestParser', () => { test('agents and variables and multiline, part2', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); + agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); varService.hasVariable.returns(true); 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 3a2b6abc998..0cd1c90c3ce 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -21,7 +21,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; -import { ChatAgentService, IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatAgentService, IChatAgent, IChatAgentImplementation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChat, IChatFollowup, IChatProgress, IChatProvider, IChatRequest, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; @@ -32,6 +32,7 @@ import { MockChatVariablesService } from 'vs/workbench/contrib/chat/test/common/ import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { TestContextService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; import { MockChatService } from 'vs/workbench/contrib/chat/test/common/mockChatService'; +import { MockChatContributionService } from 'vs/workbench/contrib/chat/test/common/mockChatContributionService'; class SimpleTestProvider extends Disposable implements IChatProvider { private static sessionId = 0; @@ -60,12 +61,7 @@ const chatAgentWithUsedContext: IChatAgent = { id: chatAgentWithUsedContextId, extensionId: nullExtensionDescription.identifier, metadata: {}, - getLastSlashCommands() { - return undefined; - }, - async provideSlashCommands() { - return []; - }, + slashCommands: [], async invoke(request, progress, history, token) { progress({ documents: [ @@ -109,25 +105,21 @@ suite('Chat', () => { instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(IChatSlashCommandService, testDisposables.add(instantiationService.createInstance(ChatSlashCommandService))); instantiationService.stub(IChatService, new MockChatService()); + instantiationService.stub(IChatContributionService, new MockChatContributionService( + [ + { extensionId: nullExtensionDescription.identifier, name: 'testAgent', isDefault: true }, + { extensionId: nullExtensionDescription.identifier, name: chatAgentWithUsedContextId, isDefault: true }, + ])); chatAgentService = testDisposables.add(instantiationService.createInstance(ChatAgentService)); instantiationService.stub(IChatAgentService, chatAgentService); const agent = { - id: 'testAgent', - extensionId: nullExtensionDescription.identifier, - metadata: { isDefault: true }, async invoke(request, progress, history, token) { return {}; }, - getLastSlashCommands() { - return undefined; - }, - async provideSlashCommands(token) { - return []; - }, - } as IChatAgent; - testDisposables.add(chatAgentService.registerAgent(agent)); + } satisfies IChatAgentImplementation; + testDisposables.add(chatAgentService.registerAgent('testAgent', agent)); }); test('retrieveSession', async () => { @@ -229,7 +221,7 @@ suite('Chat', () => { }); test('can serialize', async () => { - testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContext)); + testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContext.id, chatAgentWithUsedContext)); const testService = testDisposables.add(instantiationService.createInstance(ChatService)); testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider')))); @@ -249,7 +241,7 @@ suite('Chat', () => { test('can deserialize', async () => { let serializedChatData: ISerializableChatData; - testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContext)); + testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContext.id, chatAgentWithUsedContext)); // create the first service, send request, get response, and serialize the state { // serapate block to not leak variables in outer scope diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatContributionService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatContributionService.ts new file mode 100644 index 00000000000..e1adddb2dec --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/mockChatContributionService.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IChatContributionService, IChatParticipantContribution, IChatProviderContribution } from 'vs/workbench/contrib/chat/common/chatContributionService'; + +export class MockChatContributionService implements IChatContributionService { + _serviceBrand: undefined; + + constructor( + public readonly registeredParticipants: IChatParticipantContribution[] = [] + ) { } + + registeredProviders: IChatProviderContribution[] = []; + registerChatParticipant(participant: IChatParticipantContribution): void { + throw new Error('Method not implemented.'); + } + deregisterChatParticipant(participant: IChatParticipantContribution): void { + throw new Error('Method not implemented.'); + } + + registerChatProvider(provider: IChatProviderContribution): void { + throw new Error('Method not implemented.'); + } + deregisterChatProvider(providerId: string): void { + throw new Error('Method not implemented.'); + } + getViewIdForProvider(providerId: string): string { + throw new Error('Method not implemented.'); + } +} diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index c85e0056d12..864ba72ac0d 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -11,7 +11,7 @@ import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifec import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ProviderResult } from 'vs/editor/common/languages'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { IChatAgent, IChatAgentCommand, IChatAgentHistoryEntry, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgent, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatProgress, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IVoiceChatSessionOptions, IVoiceChatTextEvent, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; @@ -28,10 +28,8 @@ suite('VoiceChat', () => { extensionId: ExtensionIdentifier = nullExtensionDescription.identifier; - constructor(readonly id: string, private readonly lastSlashCommands: IChatAgentCommand[]) { } - getLastSlashCommands(model: IChatModel): IChatAgentCommand[] | undefined { return this.lastSlashCommands; } + constructor(readonly id: string, readonly slashCommands: IChatAgentCommand[]) { } invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error('Method not implemented.'); } - provideSlashCommands(model: IChatModel | undefined, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error('Method not implemented.'); } provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined> { throw new Error('Method not implemented.'); } metadata = {}; } @@ -49,14 +47,15 @@ suite('VoiceChat', () => { class TestChatAgentService implements IChatAgentService { _serviceBrand: undefined; readonly onDidChangeAgents = Event.None; - registerAgent(agent: IChatAgent): IDisposable { throw new Error(); } + registerAgent(name: string, agent: IChatAgentImplementation): IDisposable { throw new Error(); } + registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable { throw new Error('Method not implemented.'); } invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error(); } getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise { throw new Error(); } - getAgents(): Array { return agents; } - getAgent(id: string): IChatAgent | undefined { throw new Error(); } + getRegisteredAgents(): Array { return agents; } + getActivatedAgents(): IChatAgent[] { return agents; } + getRegisteredAgent(id: string): IChatAgent | undefined { throw new Error(); } getDefaultAgent(): IChatAgent | undefined { throw new Error(); } getSecondaryAgent(): IChatAgent | undefined { throw new Error(); } - hasAgent(id: string): boolean { throw new Error(); } updateAgent(id: string, updateMetadata: IChatAgentMetadata): void { throw new Error(); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 775b62fee68..cfb18aa49f0 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -520,8 +520,8 @@ export class InlineChatController implements IEditorContribution { const withoutSubCommandLeader = input.slice(1); const cts = new CancellationTokenSource(); this._sessionStore.add(cts); - for (const agent of this._chatAgentService.getAgents()) { - const commands = await agent.provideSlashCommands(undefined, [], cts.token); + for (const agent of this._chatAgentService.getActivatedAgents()) { + const commands = agent.slashCommands; if (commands.find((command) => withoutSubCommandLeader.startsWith(command.name))) { massagedInput = `${chatAgentLeader}${agent.id} ${input}`; break; diff --git a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts index 5fcc77cb7b5..b6794145df2 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -143,49 +143,6 @@ declare module 'vscode' { readonly kind: ChatResultFeedbackKind; } - export interface ChatCommand { - /** - * A short name by which this command is referred to in the UI, e.g. `fix` or - * `explain` for commands that fix an issue or explain code. - * - * **Note**: The name should be unique among the commands provided by this participant. - */ - readonly name: string; - - /** - * Human-readable description explaining what this command does. - */ - readonly description: string; - - /** - * When the user clicks this command in `/help`, this text will be submitted to this command - */ - readonly sampleRequest?: string; - - /** - * Whether executing the command puts the chat into a persistent mode, where the command is automatically added to the chat input for the next message. - */ - readonly isSticky?: boolean; - } - - /** - * A ChatCommandProvider returns {@link ChatCommands commands} that can be invoked on a chat participant using `/`. For example, `@participant /command`. - * These can be used as shortcuts to let the user explicitly invoke different functionalities provided by the participant. - */ - export interface ChatCommandProvider { - /** - * Returns a list of commands that its participant is capable of handling. A command - * can be selected by the user and will then be passed to the {@link ChatRequestHandler handler} - * via the {@link ChatRequest.command command} property. - * - * - * @param token A cancellation token. - * @returns A list of commands. The lack of a result can be signaled by returning `undefined`, `null`, or - * an empty array. - */ - provideCommands(context: ChatContext, token: CancellationToken): ProviderResult; - } - /** * A followup question suggested by the participant. */ @@ -239,11 +196,6 @@ declare module 'vscode' { */ readonly name: string; - /** - * A human-readable description explaining what this participant does. - */ - description?: string; - /** * Icon for the participant shown in UI. */ @@ -263,11 +215,6 @@ declare module 'vscode' { */ requestHandler: ChatRequestHandler; - /** - * This provider will be called to retrieve the participant's commands. - */ - commandProvider?: ChatCommandProvider; - /** * This provider will be called once after each request to retrieve suggested followup questions. */ diff --git a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts index f9ed2a208bd..3319ce9ca8a 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts @@ -22,9 +22,18 @@ declare module 'vscode' { markdownContent: MarkdownString; } + /** + * Now only used for the "intent detection" API below + */ + export interface ChatCommand { + readonly name: string; + readonly description: string; + } + // TODO@API fit this into the stream export interface ChatDetectedParticipant { participant: string; + // TODO@API validate this against statically-declared slash commands? command?: ChatCommand; } @@ -231,20 +240,6 @@ declare module 'vscode' { kind?: string; } - export interface ChatCommand { - readonly isSticky2?: { - /** - * Indicates that the command should be automatically repopulated. - */ - isSticky: true; - - /** - * This can be set to a string to use a different placeholder message in the input box when the command has been repopulated. - */ - placeholder?: string; - }; - } - export interface ChatVariableResolverResponseStream { /** * Push a progress part to this stream. Short-hand for @@ -285,4 +280,12 @@ declare module 'vscode' { */ resolve2?(name: string, context: ChatVariableContext, stream: ChatVariableResolverResponseStream, token: CancellationToken): ProviderResult; } + + export interface ChatParticipant { + /** + * A human-readable description explaining what this participant does. + * Only allow a static description for normal participants. Here where dynamic participants are allowed, the description must be able to be set as well. + */ + description?: string; + } } From dff6d9f391dd448ca92463e755ca34f46fcc5843 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Thu, 29 Feb 2024 10:09:03 -0600 Subject: [PATCH 1689/1897] Start adding UI to AI text provider API (#206388) the first steps to integrating the UI for the AI text results --- .../search/browser/searchActionsTopBar.ts | 54 +++++++- .../contrib/search/browser/searchIcons.ts | 3 + .../contrib/search/browser/searchModel.ts | 123 +++++++++++++----- .../contrib/search/browser/searchView.ts | 39 +++++- .../contrib/search/common/constants.ts | 3 + .../search/test/browser/searchActions.test.ts | 4 +- .../browser/searchNotebookHelpers.test.ts | 1 + .../search/test/browser/searchResult.test.ts | 5 +- .../search/test/browser/searchTestCommon.ts | 2 +- .../search/test/browser/searchViewlet.test.ts | 19 +-- .../services/search/common/searchService.ts | 3 + 11 files changed, 205 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts b/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts index e7dbb128b7b..dd533aee173 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts @@ -8,7 +8,7 @@ import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; -import { searchClearIcon, searchCollapseAllIcon, searchExpandAllIcon, searchRefreshIcon, searchShowAsList, searchShowAsTree, searchStopIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; +import { searchClearIcon, searchCollapseAllIcon, searchExpandAllIcon, searchRefreshIcon, searchShowAsList, searchShowAsTree, searchSparkleEmpty, searchSparkleFilled, searchStopIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { ISearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { FileMatch, FolderMatch, FolderMatchNoRoot, FolderMatchWorkspaceRoot, Match, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; @@ -100,7 +100,7 @@ registerAction2(class CollapseDeepestExpandedLevelAction extends Action2 { menu: [{ id: MenuId.ViewTitle, group: 'navigation', - order: 3, + order: 4, when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), ContextKeyExpr.or(Constants.SearchContext.HasSearchResults.negate(), Constants.SearchContext.ViewHasSomeCollapsibleKey)), }] }); @@ -122,7 +122,7 @@ registerAction2(class ExpandAllAction extends Action2 { menu: [{ id: MenuId.ViewTitle, group: 'navigation', - order: 3, + order: 4, when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), Constants.SearchContext.HasSearchResults, Constants.SearchContext.ViewHasSomeCollapsibleKey.toNegated()), }] }); @@ -205,6 +205,54 @@ registerAction2(class ViewAsListAction extends Action2 { } }); +registerAction2(class ViewAIResultsAction extends Action2 { + constructor() { + super({ + id: Constants.SearchCommandIds.ShowAIResultsActionId, + title: nls.localize2('ViewAIResultsAction.label', "Show AI Results"), + category, + icon: searchSparkleEmpty, + precondition: Constants.SearchContext.AIResultsVisibleKey.toNegated(), + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + order: 3, + when: ContextKeyExpr.false(), // disabled for now + }] + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + const searchView = getSearchView(accessor.get(IViewsService)); + if (searchView) { + searchView.setAIResultsVisible(true); + } + } +}); + +registerAction2(class HideAIResultsAction extends Action2 { + constructor() { + super({ + id: Constants.SearchCommandIds.HideAIResultsActionId, + title: nls.localize2('HideAIResultsAction.label', "Hide AI Results"), + category, + icon: searchSparkleFilled, + precondition: Constants.SearchContext.AIResultsVisibleKey, + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + order: 3, + when: ContextKeyExpr.false(), // disabled for now + }] + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + const searchView = getSearchView(accessor.get(IViewsService)); + if (searchView) { + searchView.setAIResultsVisible(false); + } + } +}); + //#endregion //#region Helpers diff --git a/src/vs/workbench/contrib/search/browser/searchIcons.ts b/src/vs/workbench/contrib/search/browser/searchIcons.ts index f81dc87d394..066fbb8c836 100644 --- a/src/vs/workbench/contrib/search/browser/searchIcons.ts +++ b/src/vs/workbench/contrib/search/browser/searchIcons.ts @@ -29,3 +29,6 @@ export const searchViewIcon = registerIcon('search-view-icon', Codicon.search, l export const searchNewEditorIcon = registerIcon('search-new-editor', Codicon.newFile, localize('searchNewEditorIcon', 'Icon for the action to open a new search editor.')); export const searchOpenInFileIcon = registerIcon('search-open-in-file', Codicon.goToFile, localize('searchOpenInFile', 'Icon for the action to go to the file of the current search result.')); + +export const searchSparkleFilled = registerIcon('search-sparkle-filled', Codicon.sparkleFilled, localize('searchSparkleFilled', 'Icon to show AI results in search.')); +export const searchSparkleEmpty = registerIcon('search-sparkle-empty', Codicon.sparkle, localize('searchSparkleEmpty', 'Icon to hide AI results in search.')); diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 0f280b7f1d0..00f9a268b17 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -41,7 +41,7 @@ import { contentMatchesToTextSearchMatches, webviewMatchesToTextSearchMatches, I import { INotebookSearchService } from 'vs/workbench/contrib/search/common/notebookSearch'; import { rawCellPrefix, INotebookCellMatchNoModel, isINotebookFileMatchNoModel } from 'vs/workbench/contrib/search/common/searchNotebookHelpers'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; -import { IFileMatch, IPatternInfo, ISearchComplete, ISearchConfigurationProperties, ISearchProgressItem, ISearchRange, ISearchService, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, OneLineRange, resultIsMatch, SearchCompletionExitCode, SearchSortOrder } from 'vs/workbench/services/search/common/search'; +import { IFileMatch, IPatternInfo, ISearchComplete, ISearchConfigurationProperties, ISearchProgressItem, ISearchRange, ISearchService, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, OneLineRange, QueryType, resultIsMatch, SearchCompletionExitCode, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { getTextSearchMatchWithModelContext, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; import { CellSearchModel } from 'vs/workbench/contrib/search/common/cellSearchModel'; @@ -55,7 +55,7 @@ export class Match { // For replace private _fullPreviewRange: ISearchRange; - constructor(protected _parent: FileMatch, private _fullPreviewLines: string[], _fullPreviewRange: ISearchRange, _documentRange: ISearchRange) { + constructor(protected _parent: FileMatch, private _fullPreviewLines: string[], _fullPreviewRange: ISearchRange, _documentRange: ISearchRange, public readonly aiContributed: boolean) { this._oneLinePreviewText = _fullPreviewLines[_fullPreviewRange.startLineNumber]; const adjustedEndCol = _fullPreviewRange.startLineNumber === _fullPreviewRange.endLineNumber ? _fullPreviewRange.endColumn : @@ -289,7 +289,7 @@ export class MatchInNotebook extends Match { private _webviewIndex: number | undefined; constructor(private readonly _cellParent: CellMatch, _fullPreviewLines: string[], _fullPreviewRange: ISearchRange, _documentRange: ISearchRange, webviewIndex?: number) { - super(_cellParent.parent, _fullPreviewLines, _fullPreviewRange, _documentRange); + super(_cellParent.parent, _fullPreviewLines, _fullPreviewRange, _documentRange, false); this._id = this._parent.id() + '>' + this._cellParent.cellIndex + (webviewIndex ? '_' + webviewIndex : '') + '_' + this.notebookMatchTypeString() + this._range + this.getMatchString(); this._webviewIndex = webviewIndex; } @@ -426,7 +426,6 @@ export class FileMatch extends Disposable implements IFileMatch { this._name = new Lazy(() => labelService.getUriBasenameLabel(this.resource)); this._cellMatches = new Map(); this._notebookUpdateScheduler = new RunOnceScheduler(this.updateMatchesForEditorWidget.bind(this), 250); - this.createMatches(); } addWebviewMatchesToCell(cellID: string, webviewMatches: ITextSearchMatch[]) { @@ -462,9 +461,14 @@ export class FileMatch extends Disposable implements IFileMatch { return this.matches().some(m => m instanceof MatchInNotebook && m.isReadonly()); } - createMatches(): void { + hasDownstreamNonAIResults(): boolean { + return this.matches().some(m => !m.aiContributed); + } + + createMatches(isAiContributed: boolean): void { const model = this.modelService.getModel(this._resource); - if (model) { + if (model && !isAiContributed) { + // todo: handle better when ai contributed results has model, currently, createMatches does not work for this this.bindModel(model); this.updateMatchesForModel(); } else { @@ -477,7 +481,7 @@ export class FileMatch extends Disposable implements IFileMatch { this.rawMatch.results .filter(resultIsMatch) .forEach(rawMatch => { - textSearchResultToMatches(rawMatch, this) + textSearchResultToMatches(rawMatch, this, isAiContributed) .forEach(m => this.add(m)); }); } @@ -529,7 +533,7 @@ export class FileMatch extends Disposable implements IFileMatch { const matches = this._model .findMatches(this._query.pattern, this._model.getFullModelRange(), !!this._query.isRegExp, !!this._query.isCaseSensitive, wordSeparators, false, this._maxResults ?? Number.MAX_SAFE_INTEGER); - this.updateMatches(matches, true, this._model); + this.updateMatches(matches, true, this._model, false); } @@ -549,17 +553,17 @@ export class FileMatch extends Disposable implements IFileMatch { const wordSeparators = this._query.isWordMatch && this._query.wordSeparators ? this._query.wordSeparators : null; const matches = this._model.findMatches(this._query.pattern, range, !!this._query.isRegExp, !!this._query.isCaseSensitive, wordSeparators, false, this._maxResults ?? Number.MAX_SAFE_INTEGER); - this.updateMatches(matches, modelChange, this._model); + this.updateMatches(matches, modelChange, this._model, false); // await this.updateMatchesForEditorWidget(); } - private updateMatches(matches: FindMatch[], modelChange: boolean, model: ITextModel): void { + private updateMatches(matches: FindMatch[], modelChange: boolean, model: ITextModel, isAiContributed: boolean): void { const textSearchResults = editorMatchesToTextSearchResults(matches, model, this._previewOptions); textSearchResults.forEach(textSearchResult => { - textSearchResultToMatches(textSearchResult, this).forEach(match => { + textSearchResultToMatches(textSearchResult, this, isAiContributed).forEach(match => { if (!this._removedTextMatches.has(match.id())) { this.add(match); if (this.isMatchSelected(match)) { @@ -1012,6 +1016,19 @@ export class FolderMatch extends Disposable { } } + hasDownstreamNonAIResults(): boolean { + let recursiveChildren: FileMatch[] = []; + const iterator = this.folderMatchesIterator(); + for (const elem of iterator) { + recursiveChildren = recursiveChildren.concat(elem.allDownstreamFileMatches()); + if (recursiveChildren.some(fileMatch => fileMatch.hasDownstreamNonAIResults())) { + return true; + } + } + + return false; + } + async bindNotebookEditorWidget(editor: NotebookEditorWidget, resource: URI) { const fileMatch = this._fileMatches.get(resource); @@ -1142,7 +1159,7 @@ export class FolderMatch extends Disposable { return this._query; } - addFileMatch(raw: IFileMatch[], silent: boolean, searchInstanceID: string): void { + addFileMatch(raw: IFileMatch[], silent: boolean, searchInstanceID: string, isAiContributed: boolean): void { // when adding a fileMatch that has intermediate directories const added: FileMatch[] = []; const updated: FileMatch[] = []; @@ -1156,7 +1173,7 @@ export class FolderMatch extends Disposable { .results .filter(resultIsMatch) .forEach(m => { - textSearchResultToMatches(m, existingFileMatch) + textSearchResultToMatches(m, existingFileMatch, isAiContributed) .forEach(m => existingFileMatch.add(m)); }); } @@ -1181,7 +1198,7 @@ export class FolderMatch extends Disposable { } } else { if (this instanceof FolderMatchWorkspaceRoot || this instanceof FolderMatchNoRoot) { - const fileMatch = this.createAndConfigureFileMatch(rawFileMatch, searchInstanceID); + const fileMatch = this.createAndConfigureFileMatch(rawFileMatch, searchInstanceID, isAiContributed); added.push(fileMatch); } } @@ -1367,7 +1384,7 @@ export class FolderMatchWorkspaceRoot extends FolderMatchWithResource { return this.uriIdentityService.extUri.isEqual(uri1, ur2); } - private createFileMatch(query: IPatternInfo, previewOptions: ITextSearchPreviewOptions | undefined, maxResults: number | undefined, parent: FolderMatch, rawFileMatch: IFileMatch, closestRoot: FolderMatchWorkspaceRoot | null, searchInstanceID: string): FileMatch { + private createFileMatch(query: IPatternInfo, previewOptions: ITextSearchPreviewOptions | undefined, maxResults: number | undefined, parent: FolderMatch, rawFileMatch: IFileMatch, closestRoot: FolderMatchWorkspaceRoot | null, searchInstanceID: string, isAiContributed: boolean): FileMatch { const fileMatch = this.instantiationService.createInstance( FileMatch, @@ -1379,13 +1396,14 @@ export class FolderMatchWorkspaceRoot extends FolderMatchWithResource { closestRoot, searchInstanceID ); + fileMatch.createMatches(isAiContributed); parent.doAddFile(fileMatch); const disposable = fileMatch.onChange(({ didRemove }) => parent.onFileChange(fileMatch, didRemove)); this._register(fileMatch.onDispose(() => disposable.dispose())); return fileMatch; } - createAndConfigureFileMatch(rawFileMatch: IFileMatch, searchInstanceID: string): FileMatch { + createAndConfigureFileMatch(rawFileMatch: IFileMatch, searchInstanceID: string, ai: boolean): FileMatch { if (!this.uriHasParent(this.resource, rawFileMatch.resource)) { throw Error(`${rawFileMatch.resource} is not a descendant of ${this.resource}`); @@ -1413,7 +1431,7 @@ export class FolderMatchWorkspaceRoot extends FolderMatchWithResource { parent = folderMatch; } - return this.createFileMatch(this._query.contentPattern, this._query.previewOptions, this._query.maxResults, parent, rawFileMatch, root, searchInstanceID); + return this.createFileMatch(this._query.contentPattern, this._query.previewOptions, this._query.maxResults, parent, rawFileMatch, root, searchInstanceID, ai); } } @@ -1432,7 +1450,7 @@ export class FolderMatchNoRoot extends FolderMatch { super(null, _id, _index, _query, _parent, _parent, null, replaceService, instantiationService, labelService, uriIdentityService); } - createAndConfigureFileMatch(rawFileMatch: IFileMatch, searchInstanceID: string): FileMatch { + createAndConfigureFileMatch(rawFileMatch: IFileMatch, searchInstanceID: string, isAiContributed: boolean): FileMatch { const fileMatch = this._register(this.instantiationService.createInstance( FileMatch, this._query.contentPattern, @@ -1441,6 +1459,7 @@ export class FolderMatchNoRoot extends FolderMatch { this, rawFileMatch, null, searchInstanceID)); + fileMatch.createMatches(isAiContributed); this.doAddFile(fileMatch); const disposable = fileMatch.onChange(({ didRemove }) => this.onFileChange(fileMatch, didRemove)); this._register(fileMatch.onDispose(() => disposable.dispose())); @@ -1750,7 +1769,7 @@ export class SearchResult extends Disposable { } - add(allRaw: IFileMatch[], searchInstanceID: string, silent: boolean = false): void { + add(allRaw: IFileMatch[], searchInstanceID: string, ai: boolean, silent: boolean = false): void { // Split up raw into a list per folder so we can do a batch add per folder. const { byFolder, other } = this.groupFilesByFolder(allRaw); @@ -1760,10 +1779,10 @@ export class SearchResult extends Disposable { } const folderMatch = this.getFolderMatch(raw[0].resource); - folderMatch?.addFileMatch(raw, silent, searchInstanceID); + folderMatch?.addFileMatch(raw, silent, searchInstanceID, ai); }); - this._otherFilesMatch?.addFileMatch(other, silent, searchInstanceID); + this._otherFilesMatch?.addFileMatch(other, silent, searchInstanceID, ai); this.disposePastResults(); } @@ -1951,6 +1970,7 @@ export class SearchModel extends Disposable { private _preserveCase: boolean = false; private _startStreamDelay: Promise = Promise.resolve(); private readonly _resultQueue: IFileMatch[] = []; + private _aiResultsEnabled = false; private readonly _onReplaceTermChanged: Emitter = this._register(new Emitter()); readonly onReplaceTermChanged: Event = this._onReplaceTermChanged.event; @@ -2013,6 +2033,37 @@ export class SearchModel extends Disposable { return this._searchResult; } + async disableAIResults() { + this._aiResultsEnabled = false; + } + async addAIResults(onProgress?: (result: ISearchProgressItem) => void) { + if (this._aiResultsEnabled) { + return; + } else { + if (this._searchQuery) { + const start = Date.now(); + const searchInstanceID = Date.now().toString(); + const asyncAIResults = this.searchService.aiTextSearch( + { ...this._searchQuery, contentPattern: this._searchQuery.contentPattern.pattern, type: QueryType.aiText }, + this.currentCancelTokenSource?.token, async (p: ISearchProgressItem) => { + this.onSearchProgress(p, searchInstanceID, false); + onProgress?.(p); + }); + + + await asyncAIResults.then( + value => { + this.onSearchCompleted(value, Date.now() - start, searchInstanceID, true); + return value; + }, + e => { + this.onSearchError(e, Date.now() - start); + throw e; + }); + } + this._aiResultsEnabled = true; + } + } private doSearch(query: ITextQuery, progressEmitter: Emitter, searchQuery: ITextQuery, searchInstanceID: string, onProgress?: (result: ISearchProgressItem) => void, callerToken?: CancellationToken): { asyncResults: Promise; @@ -2020,7 +2071,7 @@ export class SearchModel extends Disposable { } { const asyncGenerateOnProgress = async (p: ISearchProgressItem) => { progressEmitter.fire(); - this.onSearchProgress(p, searchInstanceID, false); + this.onSearchProgress(p, searchInstanceID, false, false); onProgress?.(p); }; @@ -2039,6 +2090,15 @@ export class SearchModel extends Disposable { notebookResult.allScannedFiles, ); + const asyncAIResults = this._aiResultsEnabled ? this.searchService.aiTextSearch( + { ...searchQuery, contentPattern: searchQuery.contentPattern.pattern, type: QueryType.aiText }, + this.currentCancelTokenSource.token, async (p: ISearchProgressItem) => { + progressEmitter.fire(); + this.onSearchProgress(p, searchInstanceID, false, true); + onProgress?.(p); + }) : Promise.resolve(undefined); + + const syncResults = textResult.syncResults.results; syncResults.forEach(p => { if (p) { syncGenerateOnProgress(p); } }); @@ -2048,10 +2108,11 @@ export class SearchModel extends Disposable { // resolve async parts of search const allClosedEditorResults = await textResult.asyncResults; const resolvedNotebookResults = await notebookResult.completeData; + const aiResults = await asyncAIResults; tokenSource.dispose(); const searchLength = Date.now() - searchStart; const resolvedResult = { - results: [...allClosedEditorResults.results, ...resolvedNotebookResults.results], + results: [...allClosedEditorResults.results, ...resolvedNotebookResults.results, ...aiResults?.results ?? []], messages: [...allClosedEditorResults.messages, ...resolvedNotebookResults.messages], limitHit: allClosedEditorResults.limitHit || resolvedNotebookResults.limitHit, exit: allClosedEditorResults.exit, @@ -2141,12 +2202,12 @@ export class SearchModel extends Disposable { } } - private onSearchCompleted(completed: ISearchComplete | undefined, duration: number, searchInstanceID: string): ISearchComplete | undefined { + private onSearchCompleted(completed: ISearchComplete | undefined, duration: number, searchInstanceID: string, ai = false): ISearchComplete | undefined { if (!this._searchQuery) { throw new Error('onSearchCompleted must be called after a search is started'); } - this._searchResult.add(this._resultQueue, searchInstanceID); + this._searchResult.add(this._resultQueue, searchInstanceID, ai); this._resultQueue.length = 0; const options: IPatternInfo = Object.assign({}, this._searchQuery.contentPattern); @@ -2195,18 +2256,18 @@ export class SearchModel extends Disposable { } } - private onSearchProgress(p: ISearchProgressItem, searchInstanceID: string, sync = true) { + private onSearchProgress(p: ISearchProgressItem, searchInstanceID: string, sync = true, ai = false) { if ((p).resource) { this._resultQueue.push(p); if (sync) { if (this._resultQueue.length) { - this._searchResult.add(this._resultQueue, searchInstanceID, true); + this._searchResult.add(this._resultQueue, searchInstanceID, ai, true); this._resultQueue.length = 0; } } else { this._startStreamDelay.then(() => { if (this._resultQueue.length) { - this._searchResult.add(this._resultQueue, searchInstanceID, true); + this._searchResult.add(this._resultQueue, searchInstanceID, ai, true); this._resultQueue.length = 0; } }); @@ -2354,16 +2415,16 @@ export class RangeHighlightDecorations implements IDisposable { -function textSearchResultToMatches(rawMatch: ITextSearchMatch, fileMatch: FileMatch): Match[] { +function textSearchResultToMatches(rawMatch: ITextSearchMatch, fileMatch: FileMatch, isAiContributed: boolean): Match[] { const previewLines = rawMatch.preview.text.split('\n'); if (Array.isArray(rawMatch.ranges)) { return rawMatch.ranges.map((r, i) => { const previewRange: ISearchRange = (rawMatch.preview.matches)[i]; - return new Match(fileMatch, previewLines, previewRange, r); + return new Match(fileMatch, previewLines, previewRange, r, isAiContributed); }); } else { const previewRange = rawMatch.preview.matches; - const match = new Match(fileMatch, previewLines, previewRange, rawMatch.ranges); + const match = new Match(fileMatch, previewLines, previewRange, rawMatch.ranges, isAiContributed); return [match]; } } diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 6ea0cb4542e..17c961be050 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -158,6 +158,7 @@ export class SearchView extends ViewPane { private treeAccessibilityProvider: SearchAccessibilityProvider; private treeViewKey: IContextKey; + private aiResultsVisibleKey: IContextKey; private _visibleMatches: number = 0; @@ -218,6 +219,7 @@ export class SearchView extends ViewPane { this.hasFilePatternKey = Constants.SearchContext.ViewHasFilePatternKey.bindTo(this.contextKeyService); this.hasSomeCollapsibleResultKey = Constants.SearchContext.ViewHasSomeCollapsibleKey.bindTo(this.contextKeyService); this.treeViewKey = Constants.SearchContext.InTreeViewKey.bindTo(this.contextKeyService); + this.aiResultsVisibleKey = Constants.SearchContext.AIResultsVisibleKey.bindTo(this.contextKeyService); // scoped this.contextKeyService = this._register(this.contextKeyService.createScoped(this.container)); @@ -294,6 +296,14 @@ export class SearchView extends ViewPane { this.treeViewKey.set(visible); } + get aiResultsVisible(): boolean { + return this.aiResultsVisibleKey.get() ?? false; + } + + private set aiResultsVisible(visible: boolean) { + this.aiResultsVisibleKey.set(visible); + } + setTreeView(visible: boolean): void { if (visible === this.isTreeLayoutViewVisible) { return; @@ -303,6 +313,19 @@ export class SearchView extends ViewPane { this.refreshTree(); } + async setAIResultsVisible(visible: boolean): Promise { + if (visible === this.aiResultsVisible) { + return; + } + this.aiResultsVisible = visible; + if (visible) { + this.model.addAIResults(); + } else { + this.model.disableAIResults(); + } + this.refreshTree(); + } + private get state(): SearchUIState { return this.searchStateKey.get() ?? SearchUIState.Idle; } @@ -693,7 +716,7 @@ export class SearchView extends ViewPane { private createResultIterator(collapseResults: ISearchConfigurationProperties['collapseResults']): Iterable> { const folderMatches = this.searchResult.folderMatches() - .filter(fm => !fm.isEmpty()) + .filter(fm => !fm.isEmpty() && (this.aiResultsVisible || fm.hasDownstreamNonAIResults())) .sort(searchMatchComparer); if (folderMatches.length === 1) { @@ -712,7 +735,11 @@ export class SearchView extends ViewPane { const matchArray = this.isTreeLayoutViewVisible ? folderMatch.matches() : folderMatch.allDownstreamFileMatches(); const matches = matchArray.sort((a, b) => searchMatchComparer(a, b, sortOrder)); - return Iterable.map(matches, match => { + return Iterable.filter(Iterable.map(matches, match => { + + if (!this.aiResultsVisible && !match.hasDownstreamNonAIResults()) { + return undefined; + } let children; if (match instanceof FileMatch) { children = this.createFileIterator(match); @@ -723,11 +750,15 @@ export class SearchView extends ViewPane { const collapsed = (collapseResults === 'alwaysCollapse' || (match.count() > 10 && collapseResults !== 'alwaysExpand')) ? ObjectTreeElementCollapseState.PreserveOrCollapsed : ObjectTreeElementCollapseState.PreserveOrExpanded; return >{ element: match, children, collapsed, incompressible: (match instanceof FileMatch) ? true : childFolderIncompressible }; - }); + }), (item): item is ICompressedTreeElement => !!item); } private createFileIterator(fileMatch: FileMatch): Iterable> { - const matches = fileMatch.matches().sort(searchMatchComparer); + let matches = fileMatch.matches().sort(searchMatchComparer); + + if (!this.aiResultsVisible) { + matches = matches.filter(e => !e.aiContributed); + } return Iterable.map(matches, r => (>{ element: r, incompressible: true })); } diff --git a/src/vs/workbench/contrib/search/common/constants.ts b/src/vs/workbench/contrib/search/common/constants.ts index a8f1bc84311..1b5d8a06a93 100644 --- a/src/vs/workbench/contrib/search/common/constants.ts +++ b/src/vs/workbench/contrib/search/common/constants.ts @@ -41,6 +41,8 @@ export const enum SearchCommandIds { ClearSearchResultsActionId = 'search.action.clearSearchResults', ViewAsTreeActionId = 'search.action.viewAsTree', ViewAsListActionId = 'search.action.viewAsList', + ShowAIResultsActionId = 'search.action.showAIResults', + HideAIResultsActionId = 'search.action.hideAIResults', ToggleQueryDetailsActionId = 'workbench.action.search.toggleQueryDetails', ExcludeFolderFromSearchId = 'search.action.excludeFromSearch', FocusNextInputActionId = 'search.focus.nextInputBox', @@ -74,4 +76,5 @@ export const SearchContext = { ViewHasFilePatternKey: new RawContextKey('viewHasFilePattern', false), ViewHasSomeCollapsibleKey: new RawContextKey('viewHasSomeCollapsibleResult', false), InTreeViewKey: new RawContextKey('inTreeView', false), + AIResultsVisibleKey: new RawContextKey('AIResultsVisibleKey', false), }; diff --git a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts index 5e39773a0cc..9b3b9e4f4dd 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts @@ -125,6 +125,7 @@ suite('Search Actions', () => { const fileMatch = instantiationService.createInstance(FileMatch, { pattern: '' }, undefined, undefined, folderMatch, rawMatch, null, ''); + fileMatch.createMatches(false); store.add(fileMatch); return fileMatch; } @@ -145,7 +146,8 @@ suite('Search Actions', () => { startColumn: 0, endLineNumber: line, endColumn: 2 - } + }, + false ); fileMatch.add(match); return match; diff --git a/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts b/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts index 0a591e21457..a90875c1384 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts @@ -217,6 +217,7 @@ suite('searchNotebookHelpers', () => { const fileMatch = instantiationService.createInstance(FileMatch, { pattern: '' }, undefined, undefined, folderMatch, rawMatch, null, ''); + fileMatch.createMatches(false); store.add(folderMatch); store.add(fileMatch); diff --git a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts index c6d94a0ebf3..e71fab4b131 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts @@ -66,7 +66,7 @@ suite('SearchResult', () => { test('Line Match', function () { const fileMatch = aFileMatch('folder/file.txt', null!); - const lineMatch = new Match(fileMatch, ['0 foo bar'], new OneLineRange(0, 2, 5), new OneLineRange(1, 0, 5)); + const lineMatch = new Match(fileMatch, ['0 foo bar'], new OneLineRange(0, 2, 5), new OneLineRange(1, 0, 5), false); assert.strictEqual(lineMatch.text(), '0 foo bar'); assert.strictEqual(lineMatch.range().startLineNumber, 2); assert.strictEqual(lineMatch.range().endLineNumber, 2); @@ -174,7 +174,7 @@ suite('SearchResult', () => { const searchResult = instantiationService.createInstance(SearchResult, searchModel); store.add(searchResult); const fileMatch = aFileMatch('far/boo', searchResult); - const lineMatch = new Match(fileMatch, ['foo bar'], new OneLineRange(0, 0, 3), new OneLineRange(1, 0, 3)); + const lineMatch = new Match(fileMatch, ['foo bar'], new OneLineRange(0, 0, 3), new OneLineRange(1, 0, 3), false); assert(lineMatch.parent() === fileMatch); assert(fileMatch.parent() === searchResult.folderMatches()[0]); @@ -532,6 +532,7 @@ suite('SearchResult', () => { const fileMatch = instantiationService.createInstance(FileMatch, { pattern: '' }, undefined, undefined, root, rawMatch, null, ''); + fileMatch.createMatches(false); store.add(fileMatch); return fileMatch; diff --git a/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts b/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts index 5c5fcd10aab..b6e7dc04bbb 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts @@ -66,5 +66,5 @@ export function stubNotebookEditorService(instantiationService: TestInstantiatio } export function addToSearchResult(searchResult: SearchResult, allRaw: IFileMatch[], searchInstanceID = '') { - searchResult.add(allRaw, searchInstanceID); + searchResult.add(allRaw, searchInstanceID, false); } diff --git a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts index 76b49f5f2bf..4feb3d343ed 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts @@ -76,7 +76,7 @@ suite('Search - Viewlet', () => { endColumn: 1 } }] - }], ''); + }], '', false); const fileMatch = result.matches()[0]; const lineMatch = fileMatch.matches()[0]; @@ -89,9 +89,9 @@ suite('Search - Viewlet', () => { const fileMatch1 = aFileMatch('/foo'); const fileMatch2 = aFileMatch('/with/path'); const fileMatch3 = aFileMatch('/with/path/foo'); - const lineMatch1 = new Match(fileMatch1, ['bar'], new OneLineRange(0, 1, 1), new OneLineRange(0, 1, 1)); - const lineMatch2 = new Match(fileMatch1, ['bar'], new OneLineRange(0, 1, 1), new OneLineRange(2, 1, 1)); - const lineMatch3 = new Match(fileMatch1, ['bar'], new OneLineRange(0, 1, 1), new OneLineRange(2, 1, 1)); + const lineMatch1 = new Match(fileMatch1, ['bar'], new OneLineRange(0, 1, 1), new OneLineRange(0, 1, 1), false); + const lineMatch2 = new Match(fileMatch1, ['bar'], new OneLineRange(0, 1, 1), new OneLineRange(2, 1, 1), false); + const lineMatch3 = new Match(fileMatch1, ['bar'], new OneLineRange(0, 1, 1), new OneLineRange(2, 1, 1), false); assert(searchMatchComparer(fileMatch1, fileMatch2) < 0); assert(searchMatchComparer(fileMatch2, fileMatch1) > 0); @@ -127,13 +127,13 @@ suite('Search - Viewlet', () => { const fileMatch2 = aFileMatch('/with/path.c', folderMatch2); const fileMatch3 = aFileMatch('/with/path/bar.b', folderMatch2); - const lineMatch1 = new Match(fileMatch1, ['bar'], new OneLineRange(0, 1, 1), new OneLineRange(0, 1, 1)); - const lineMatch2 = new Match(fileMatch1, ['bar'], new OneLineRange(0, 1, 1), new OneLineRange(2, 1, 1)); + const lineMatch1 = new Match(fileMatch1, ['bar'], new OneLineRange(0, 1, 1), new OneLineRange(0, 1, 1), false); + const lineMatch2 = new Match(fileMatch1, ['bar'], new OneLineRange(0, 1, 1), new OneLineRange(2, 1, 1), false); - const lineMatch3 = new Match(fileMatch2, ['barfoo'], new OneLineRange(0, 1, 1), new OneLineRange(0, 1, 1)); - const lineMatch4 = new Match(fileMatch2, ['fooooo'], new OneLineRange(0, 1, 1), new OneLineRange(2, 1, 1)); + const lineMatch3 = new Match(fileMatch2, ['barfoo'], new OneLineRange(0, 1, 1), new OneLineRange(0, 1, 1), false); + const lineMatch4 = new Match(fileMatch2, ['fooooo'], new OneLineRange(0, 1, 1), new OneLineRange(2, 1, 1), false); - const lineMatch5 = new Match(fileMatch3, ['foobar'], new OneLineRange(0, 1, 1), new OneLineRange(2, 1, 1)); + const lineMatch5 = new Match(fileMatch3, ['foobar'], new OneLineRange(0, 1, 1), new OneLineRange(2, 1, 1), false); /*** * Structure would take the following form: @@ -180,6 +180,7 @@ suite('Search - Viewlet', () => { const fileMatch = instantiation.createInstance(FileMatch, { pattern: '' }, undefined, undefined, parentFolder ?? aFolderMatch('', 0), rawMatch, null, ''); + fileMatch.createMatches(false); store.add(fileMatch); return fileMatch; } diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index 42ffb1111ed..bb45e53d87b 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -275,6 +275,9 @@ export class SearchService extends Disposable implements ISearchService { return this.getSearchProvider(query.type).has(scheme); }); + if (query.type === QueryType.aiText && !someSchemeHasProvider) { + return []; + } await Promise.all([...fqs.keys()].map(async scheme => { const schemeFQs = fqs.get(scheme)!; let provider = this.getSearchProvider(query.type).get(scheme); From f348847d77b94e2166f94189e30289b517bc6de3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 29 Feb 2024 08:46:51 -0800 Subject: [PATCH 1690/1897] Update xterm Fixes #204690 --- package.json | 16 +++++----- remote/package.json | 16 +++++----- remote/web/package.json | 14 ++++----- remote/web/yarn.lock | 60 ++++++++++++++++++------------------ remote/yarn.lock | 68 ++++++++++++++++++++--------------------- yarn.lock | 68 ++++++++++++++++++++--------------------- 6 files changed, 121 insertions(+), 121 deletions(-) diff --git a/package.json b/package.json index f9142db514f..03245f8347a 100644 --- a/package.json +++ b/package.json @@ -80,14 +80,14 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-canvas": "0.6.0-beta.31", - "@xterm/addon-image": "0.7.0-beta.29", - "@xterm/addon-search": "0.14.0-beta.31", - "@xterm/addon-serialize": "0.12.0-beta.31", - "@xterm/addon-unicode11": "0.7.0-beta.31", - "@xterm/addon-webgl": "0.17.0-beta.31", - "@xterm/headless": "5.4.0-beta.31", - "@xterm/xterm": "5.4.0-beta.31", + "@xterm/addon-canvas": "0.6.0-beta.33", + "@xterm/addon-image": "0.7.0-beta.31", + "@xterm/addon-search": "0.14.0-beta.33", + "@xterm/addon-serialize": "0.12.0-beta.33", + "@xterm/addon-unicode11": "0.7.0-beta.33", + "@xterm/addon-webgl": "0.17.0-beta.33", + "@xterm/headless": "5.4.0-beta.33", + "@xterm/xterm": "5.4.0-beta.33", "graceful-fs": "4.2.11", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", diff --git a/remote/package.json b/remote/package.json index a3af2b99fe4..1070eac356f 100644 --- a/remote/package.json +++ b/remote/package.json @@ -13,14 +13,14 @@ "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-canvas": "0.6.0-beta.31", - "@xterm/addon-image": "0.7.0-beta.29", - "@xterm/addon-search": "0.14.0-beta.31", - "@xterm/addon-serialize": "0.12.0-beta.31", - "@xterm/addon-unicode11": "0.7.0-beta.31", - "@xterm/addon-webgl": "0.17.0-beta.31", - "@xterm/headless": "5.4.0-beta.31", - "@xterm/xterm": "5.4.0-beta.31", + "@xterm/addon-canvas": "0.6.0-beta.33", + "@xterm/addon-image": "0.7.0-beta.31", + "@xterm/addon-search": "0.14.0-beta.33", + "@xterm/addon-serialize": "0.12.0-beta.33", + "@xterm/addon-unicode11": "0.7.0-beta.33", + "@xterm/addon-webgl": "0.17.0-beta.33", + "@xterm/headless": "5.4.0-beta.33", + "@xterm/xterm": "5.4.0-beta.33", "cookie": "^0.4.0", "graceful-fs": "4.2.11", "http-proxy-agent": "^7.0.0", diff --git a/remote/web/package.json b/remote/web/package.json index e891be054dd..16b4ef21ba4 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -7,13 +7,13 @@ "@microsoft/1ds-post-js": "^3.2.13", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-canvas": "0.6.0-beta.31", - "@xterm/addon-image": "0.7.0-beta.29", - "@xterm/addon-search": "0.14.0-beta.31", - "@xterm/addon-serialize": "0.12.0-beta.31", - "@xterm/addon-unicode11": "0.7.0-beta.31", - "@xterm/addon-webgl": "0.17.0-beta.31", - "@xterm/xterm": "5.4.0-beta.31", + "@xterm/addon-canvas": "0.6.0-beta.33", + "@xterm/addon-image": "0.7.0-beta.31", + "@xterm/addon-search": "0.14.0-beta.33", + "@xterm/addon-serialize": "0.12.0-beta.33", + "@xterm/addon-unicode11": "0.7.0-beta.33", + "@xterm/addon-webgl": "0.17.0-beta.33", + "@xterm/xterm": "5.4.0-beta.33", "jschardet": "3.0.0", "tas-client-umd": "0.1.8", "vscode-oniguruma": "1.7.0", diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 1af0f2be779..7ce1173e61e 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -48,40 +48,40 @@ resolved "https://registry.yarnpkg.com/@vscode/vscode-languagedetection/-/vscode-languagedetection-1.0.21.tgz#89b48f293f6aa3341bb888c1118d16ff13b032d3" integrity sha512-zSUH9HYCw5qsCtd7b31yqkpaCU6jhtkKLkvOOA8yTrIRfBSOFb8PPhgmMicD7B/m+t4PwOJXzU1XDtrM9Fd3/g== -"@xterm/addon-canvas@0.6.0-beta.31": - version "0.6.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.31.tgz#17cc7d9968ede411fb23db11813b495435c068a0" - integrity sha512-jm/7FWZOgnAGG7MXjr0W4SnuIzsag+oVpyf6wAD9UlCgq5HBuk/3kJ5mYGiGR7CpdTxqXmzyBk3OhQe8npZ1aQ== +"@xterm/addon-canvas@0.6.0-beta.33": + version "0.6.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.33.tgz#5fe1547f74fbb9538ccc98c299fb1342bf739fb5" + integrity sha512-SJBoCJf62D315IduJwgHwCS2B0RzTc34GTJC5kNBzn/Y7Jr1IdvTbWwf4epleOZo+NkT0/mhj3OUO6zKeljDKA== -"@xterm/addon-image@0.7.0-beta.29": - version "0.7.0-beta.29" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.29.tgz#276b56007c9009e7a59605dc3809c280e7d637ed" - integrity sha512-Z5JCuhl0AcwQA+DE/kQMeSSHZbfwJVLUUBodDeujVItQrcpc9vA8mxf/qIwS3XTA/tPbFihfc/CE9zL7OFdbaw== - -"@xterm/addon-search@0.14.0-beta.31": - version "0.14.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.31.tgz#e6edcd257f5a66bca7e92e62684132b604fb817d" - integrity sha512-SS4CdgciLT98Uc4Dq0IjJegHcGIjGaASTcMtVkNBx9dOat9xt6lCXmtgUUj5w0KlB8nUfKrcy5T6fHgzrOzvrw== - -"@xterm/addon-serialize@0.12.0-beta.31": - version "0.12.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.31.tgz#daec32b94d45afcd662351d7689cb1b19eb24db7" - integrity sha512-MZ24pw33qOJrHdA6tlvwE4dSSpmIp/H9ZKtbiWZvuxVsY/hfYYPOluBQiCsOiYT7bZ8gQub2OOBX3jyMoZVxnQ== - -"@xterm/addon-unicode11@0.7.0-beta.31": +"@xterm/addon-image@0.7.0-beta.31": version "0.7.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.31.tgz#e1a6e965638ee6cb59b8b0777387037c42582d4b" - integrity sha512-wrZLt2s6Yjmpe4nh0Sp6DKji0EoHod7V6ABfWBf8krjmEGSleE+GSb+ZwDOMsNzLJLmxoq1e6glHcVixG1z7WQ== + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.31.tgz#1fc22cfddfc8e0a178324b5945c1882af50afe12" + integrity sha512-Ofm3igHyOATnEbc6QBxWfq2M5dZDLlByMOqzGA/2nOWv0LWtjb1u2DzAcXC3d0GIsGvlqI4342la8BZjWj2ALg== -"@xterm/addon-webgl@0.17.0-beta.31": - version "0.17.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.31.tgz#15dfea4583ff9b65f1a442e5cdba1d1638adb05f" - integrity sha512-wqbBDDppwQ4R8o0YgnyFL8Pai2mVZqHb3E097vkFLB5Fw2hNx2dys3MgiXriSGXaUABKM3usVdZyouL6QgWdxQ== +"@xterm/addon-search@0.14.0-beta.33": + version "0.14.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.33.tgz#84af247dde45c35e90bd334ecb1caf1d73683a14" + integrity sha512-I88tfCnac5CLmIn6alMCI+bXh3rTq40KOnfPiyM3IyGMSEj56jiL1xU2pctPGX4tD8fr2X22ICHnFzLCREOoEg== -"@xterm/xterm@5.4.0-beta.31": - version "5.4.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.31.tgz#ff0bb3af9b00b0dfc73e84075f4218440c9886be" - integrity sha512-EpCtaYqMhJSyZrGY2sJVZeRCIRrANKtv1GGTj+IQPvk6hTiJHGrFHLM0tZ0dj0l3z65tLoOdj6EzJnjzX3Pqjw== +"@xterm/addon-serialize@0.12.0-beta.33": + version "0.12.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.33.tgz#4f6491dab94490bb2dafb310439f6351fdb5d620" + integrity sha512-V4UgqKhvYC+6qMjJsBRAzgnvroPE4Boqs75AVOPlhmAxSTlttuVJQUEwGEd2/kcu8ptEo6CcPsfCFTFQJgFZlw== + +"@xterm/addon-unicode11@0.7.0-beta.33": + version "0.7.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.33.tgz#01bb9ec1ba00d2cfd32c16600bac33c8e239adca" + integrity sha512-vJPiyadR83n0H2OMkZlLS5af5+9o6oavUadDLLV4SlDbf1t3U+mqePYZFvr9wCbZrtEQ/P9SuJ7HehTHLZidwQ== + +"@xterm/addon-webgl@0.17.0-beta.33": + version "0.17.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.33.tgz#6b859fc2393e483f13cf391b0bd00af1e9086ed0" + integrity sha512-brR0BAvS5I92z5UiFOFPn8w8RboBM5Gzjl/zpVDSY+iVYeqcQy7d5uSt/G6yXWVv8E3NaNWHmwWmB71gj9YvVg== + +"@xterm/xterm@5.4.0-beta.33": + version "5.4.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.33.tgz#9c656a2b1799c9b98349a232b206f6bfff9401ee" + integrity sha512-eeKjd5TpyUqRGmiqFl6PZjIlDhP7eNWU1uD6zHC12CfrarJH7Lc993PQpfnfuL8pe4QJGBMIEBU6VgEQ7LKtBw== jschardet@3.0.0: version "3.0.0" diff --git a/remote/yarn.lock b/remote/yarn.lock index bcd7160cadc..63fb04013f6 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -114,45 +114,45 @@ resolved "https://registry.yarnpkg.com/@vscode/windows-registry/-/windows-registry-1.1.0.tgz#03dace7c29c46f658588b9885b9580e453ad21f9" integrity sha512-5AZzuWJpGscyiMOed0IuyEwt6iKmV5Us7zuwCDCFYMIq7tsvooO9BUiciywsvuthGz6UG4LSpeDeCxvgMVhnIw== -"@xterm/addon-canvas@0.6.0-beta.31": - version "0.6.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.31.tgz#17cc7d9968ede411fb23db11813b495435c068a0" - integrity sha512-jm/7FWZOgnAGG7MXjr0W4SnuIzsag+oVpyf6wAD9UlCgq5HBuk/3kJ5mYGiGR7CpdTxqXmzyBk3OhQe8npZ1aQ== +"@xterm/addon-canvas@0.6.0-beta.33": + version "0.6.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.33.tgz#5fe1547f74fbb9538ccc98c299fb1342bf739fb5" + integrity sha512-SJBoCJf62D315IduJwgHwCS2B0RzTc34GTJC5kNBzn/Y7Jr1IdvTbWwf4epleOZo+NkT0/mhj3OUO6zKeljDKA== -"@xterm/addon-image@0.7.0-beta.29": - version "0.7.0-beta.29" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.29.tgz#276b56007c9009e7a59605dc3809c280e7d637ed" - integrity sha512-Z5JCuhl0AcwQA+DE/kQMeSSHZbfwJVLUUBodDeujVItQrcpc9vA8mxf/qIwS3XTA/tPbFihfc/CE9zL7OFdbaw== - -"@xterm/addon-search@0.14.0-beta.31": - version "0.14.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.31.tgz#e6edcd257f5a66bca7e92e62684132b604fb817d" - integrity sha512-SS4CdgciLT98Uc4Dq0IjJegHcGIjGaASTcMtVkNBx9dOat9xt6lCXmtgUUj5w0KlB8nUfKrcy5T6fHgzrOzvrw== - -"@xterm/addon-serialize@0.12.0-beta.31": - version "0.12.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.31.tgz#daec32b94d45afcd662351d7689cb1b19eb24db7" - integrity sha512-MZ24pw33qOJrHdA6tlvwE4dSSpmIp/H9ZKtbiWZvuxVsY/hfYYPOluBQiCsOiYT7bZ8gQub2OOBX3jyMoZVxnQ== - -"@xterm/addon-unicode11@0.7.0-beta.31": +"@xterm/addon-image@0.7.0-beta.31": version "0.7.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.31.tgz#e1a6e965638ee6cb59b8b0777387037c42582d4b" - integrity sha512-wrZLt2s6Yjmpe4nh0Sp6DKji0EoHod7V6ABfWBf8krjmEGSleE+GSb+ZwDOMsNzLJLmxoq1e6glHcVixG1z7WQ== + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.31.tgz#1fc22cfddfc8e0a178324b5945c1882af50afe12" + integrity sha512-Ofm3igHyOATnEbc6QBxWfq2M5dZDLlByMOqzGA/2nOWv0LWtjb1u2DzAcXC3d0GIsGvlqI4342la8BZjWj2ALg== -"@xterm/addon-webgl@0.17.0-beta.31": - version "0.17.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.31.tgz#15dfea4583ff9b65f1a442e5cdba1d1638adb05f" - integrity sha512-wqbBDDppwQ4R8o0YgnyFL8Pai2mVZqHb3E097vkFLB5Fw2hNx2dys3MgiXriSGXaUABKM3usVdZyouL6QgWdxQ== +"@xterm/addon-search@0.14.0-beta.33": + version "0.14.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.33.tgz#84af247dde45c35e90bd334ecb1caf1d73683a14" + integrity sha512-I88tfCnac5CLmIn6alMCI+bXh3rTq40KOnfPiyM3IyGMSEj56jiL1xU2pctPGX4tD8fr2X22ICHnFzLCREOoEg== -"@xterm/headless@5.4.0-beta.31": - version "5.4.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.31.tgz#7727c5c79d3b1b8e59526cf51c75148e13f61694" - integrity sha512-AIMP0ZZozxtvilVTKqquNPYDE5RuKINTsYjOcWzYvjpg7sS75/Tn/RBx20KfZN8Z2oCCwVgj+1mudrV0W4JmMw== +"@xterm/addon-serialize@0.12.0-beta.33": + version "0.12.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.33.tgz#4f6491dab94490bb2dafb310439f6351fdb5d620" + integrity sha512-V4UgqKhvYC+6qMjJsBRAzgnvroPE4Boqs75AVOPlhmAxSTlttuVJQUEwGEd2/kcu8ptEo6CcPsfCFTFQJgFZlw== -"@xterm/xterm@5.4.0-beta.31": - version "5.4.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.31.tgz#ff0bb3af9b00b0dfc73e84075f4218440c9886be" - integrity sha512-EpCtaYqMhJSyZrGY2sJVZeRCIRrANKtv1GGTj+IQPvk6hTiJHGrFHLM0tZ0dj0l3z65tLoOdj6EzJnjzX3Pqjw== +"@xterm/addon-unicode11@0.7.0-beta.33": + version "0.7.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.33.tgz#01bb9ec1ba00d2cfd32c16600bac33c8e239adca" + integrity sha512-vJPiyadR83n0H2OMkZlLS5af5+9o6oavUadDLLV4SlDbf1t3U+mqePYZFvr9wCbZrtEQ/P9SuJ7HehTHLZidwQ== + +"@xterm/addon-webgl@0.17.0-beta.33": + version "0.17.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.33.tgz#6b859fc2393e483f13cf391b0bd00af1e9086ed0" + integrity sha512-brR0BAvS5I92z5UiFOFPn8w8RboBM5Gzjl/zpVDSY+iVYeqcQy7d5uSt/G6yXWVv8E3NaNWHmwWmB71gj9YvVg== + +"@xterm/headless@5.4.0-beta.33": + version "5.4.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.33.tgz#8accf68a0e58ba03c8a4627aa108df2d690d8713" + integrity sha512-DlGM2qPdaDmeznUSk9doOQfoi4x8uTTfBysSb+Llxm/SyfxBqnNZEV6N1j494RoBJlawZh2UugmV1itQD+Wlzg== + +"@xterm/xterm@5.4.0-beta.33": + version "5.4.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.33.tgz#9c656a2b1799c9b98349a232b206f6bfff9401ee" + integrity sha512-eeKjd5TpyUqRGmiqFl6PZjIlDhP7eNWU1uD6zHC12CfrarJH7Lc993PQpfnfuL8pe4QJGBMIEBU6VgEQ7LKtBw== agent-base@^7.0.1, agent-base@^7.0.2, agent-base@^7.1.0: version "7.1.0" diff --git a/yarn.lock b/yarn.lock index f434a0d1e6a..1aa5e794d31 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1700,45 +1700,45 @@ resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.1.tgz#34bdc31727a1889198855913db2f270ace6d7bf8" integrity sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw== -"@xterm/addon-canvas@0.6.0-beta.31": - version "0.6.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.31.tgz#17cc7d9968ede411fb23db11813b495435c068a0" - integrity sha512-jm/7FWZOgnAGG7MXjr0W4SnuIzsag+oVpyf6wAD9UlCgq5HBuk/3kJ5mYGiGR7CpdTxqXmzyBk3OhQe8npZ1aQ== +"@xterm/addon-canvas@0.6.0-beta.33": + version "0.6.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.33.tgz#5fe1547f74fbb9538ccc98c299fb1342bf739fb5" + integrity sha512-SJBoCJf62D315IduJwgHwCS2B0RzTc34GTJC5kNBzn/Y7Jr1IdvTbWwf4epleOZo+NkT0/mhj3OUO6zKeljDKA== -"@xterm/addon-image@0.7.0-beta.29": - version "0.7.0-beta.29" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.29.tgz#276b56007c9009e7a59605dc3809c280e7d637ed" - integrity sha512-Z5JCuhl0AcwQA+DE/kQMeSSHZbfwJVLUUBodDeujVItQrcpc9vA8mxf/qIwS3XTA/tPbFihfc/CE9zL7OFdbaw== - -"@xterm/addon-search@0.14.0-beta.31": - version "0.14.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.31.tgz#e6edcd257f5a66bca7e92e62684132b604fb817d" - integrity sha512-SS4CdgciLT98Uc4Dq0IjJegHcGIjGaASTcMtVkNBx9dOat9xt6lCXmtgUUj5w0KlB8nUfKrcy5T6fHgzrOzvrw== - -"@xterm/addon-serialize@0.12.0-beta.31": - version "0.12.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.31.tgz#daec32b94d45afcd662351d7689cb1b19eb24db7" - integrity sha512-MZ24pw33qOJrHdA6tlvwE4dSSpmIp/H9ZKtbiWZvuxVsY/hfYYPOluBQiCsOiYT7bZ8gQub2OOBX3jyMoZVxnQ== - -"@xterm/addon-unicode11@0.7.0-beta.31": +"@xterm/addon-image@0.7.0-beta.31": version "0.7.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.31.tgz#e1a6e965638ee6cb59b8b0777387037c42582d4b" - integrity sha512-wrZLt2s6Yjmpe4nh0Sp6DKji0EoHod7V6ABfWBf8krjmEGSleE+GSb+ZwDOMsNzLJLmxoq1e6glHcVixG1z7WQ== + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.31.tgz#1fc22cfddfc8e0a178324b5945c1882af50afe12" + integrity sha512-Ofm3igHyOATnEbc6QBxWfq2M5dZDLlByMOqzGA/2nOWv0LWtjb1u2DzAcXC3d0GIsGvlqI4342la8BZjWj2ALg== -"@xterm/addon-webgl@0.17.0-beta.31": - version "0.17.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.31.tgz#15dfea4583ff9b65f1a442e5cdba1d1638adb05f" - integrity sha512-wqbBDDppwQ4R8o0YgnyFL8Pai2mVZqHb3E097vkFLB5Fw2hNx2dys3MgiXriSGXaUABKM3usVdZyouL6QgWdxQ== +"@xterm/addon-search@0.14.0-beta.33": + version "0.14.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.33.tgz#84af247dde45c35e90bd334ecb1caf1d73683a14" + integrity sha512-I88tfCnac5CLmIn6alMCI+bXh3rTq40KOnfPiyM3IyGMSEj56jiL1xU2pctPGX4tD8fr2X22ICHnFzLCREOoEg== -"@xterm/headless@5.4.0-beta.31": - version "5.4.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.31.tgz#7727c5c79d3b1b8e59526cf51c75148e13f61694" - integrity sha512-AIMP0ZZozxtvilVTKqquNPYDE5RuKINTsYjOcWzYvjpg7sS75/Tn/RBx20KfZN8Z2oCCwVgj+1mudrV0W4JmMw== +"@xterm/addon-serialize@0.12.0-beta.33": + version "0.12.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.33.tgz#4f6491dab94490bb2dafb310439f6351fdb5d620" + integrity sha512-V4UgqKhvYC+6qMjJsBRAzgnvroPE4Boqs75AVOPlhmAxSTlttuVJQUEwGEd2/kcu8ptEo6CcPsfCFTFQJgFZlw== -"@xterm/xterm@5.4.0-beta.31": - version "5.4.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.31.tgz#ff0bb3af9b00b0dfc73e84075f4218440c9886be" - integrity sha512-EpCtaYqMhJSyZrGY2sJVZeRCIRrANKtv1GGTj+IQPvk6hTiJHGrFHLM0tZ0dj0l3z65tLoOdj6EzJnjzX3Pqjw== +"@xterm/addon-unicode11@0.7.0-beta.33": + version "0.7.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.33.tgz#01bb9ec1ba00d2cfd32c16600bac33c8e239adca" + integrity sha512-vJPiyadR83n0H2OMkZlLS5af5+9o6oavUadDLLV4SlDbf1t3U+mqePYZFvr9wCbZrtEQ/P9SuJ7HehTHLZidwQ== + +"@xterm/addon-webgl@0.17.0-beta.33": + version "0.17.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.33.tgz#6b859fc2393e483f13cf391b0bd00af1e9086ed0" + integrity sha512-brR0BAvS5I92z5UiFOFPn8w8RboBM5Gzjl/zpVDSY+iVYeqcQy7d5uSt/G6yXWVv8E3NaNWHmwWmB71gj9YvVg== + +"@xterm/headless@5.4.0-beta.33": + version "5.4.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.33.tgz#8accf68a0e58ba03c8a4627aa108df2d690d8713" + integrity sha512-DlGM2qPdaDmeznUSk9doOQfoi4x8uTTfBysSb+Llxm/SyfxBqnNZEV6N1j494RoBJlawZh2UugmV1itQD+Wlzg== + +"@xterm/xterm@5.4.0-beta.33": + version "5.4.0-beta.33" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.33.tgz#9c656a2b1799c9b98349a232b206f6bfff9401ee" + integrity sha512-eeKjd5TpyUqRGmiqFl6PZjIlDhP7eNWU1uD6zHC12CfrarJH7Lc993PQpfnfuL8pe4QJGBMIEBU6VgEQ7LKtBw== "@xtuc/ieee754@^1.2.0": version "1.2.0" From 32c96cea668ac0cb8bccf6eccf1401ace17cd277 Mon Sep 17 00:00:00 2001 From: mkasenberg Date: Thu, 29 Feb 2024 19:01:09 +0100 Subject: [PATCH 1691/1897] searchEditor: Add option to peek with a single click (#204413) searchEditor: Add option to peek with single click The option "search.searchEditor.singleClickBehaviour": "peekDefinition" will allow to open a Peek Window in Search Editor with a single click. Co-authored-by: @mkasenberg --- .../contrib/search/browser/search.contribution.ts | 10 ++++++++++ .../contrib/searchEditor/browser/searchEditor.ts | 12 +++++++++++- src/vs/workbench/services/search/common/search.ts | 1 + 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 0531e3a3776..5d2e48914c0 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -308,6 +308,16 @@ configurationRegistry.registerConfiguration({ ], markdownDescription: nls.localize('search.searchEditor.doubleClickBehaviour', "Configure effect of double-clicking a result in a search editor.") }, + 'search.searchEditor.singleClickBehaviour': { + type: 'string', + enum: ['default', 'peekDefinition',], + default: 'default', + enumDescriptions: [ + nls.localize('search.searchEditor.singleClickBehaviour.default', "Single-clicking does nothing."), + nls.localize('search.searchEditor.singleClickBehaviour.peekDefinition', "Single-clicking opens a Peek Definition window."), + ], + markdownDescription: nls.localize('search.searchEditor.singleClickBehaviour', "Configure effect of single-clicking a result in a search editor.") + }, 'search.searchEditor.reusePriorSearchConfiguration': { type: 'boolean', default: false, diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 449b0d60787..ffc69643037 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -248,7 +248,17 @@ export class SearchEditor extends AbstractTextCodeEditor private registerEditorListeners() { this.searchResultEditor.onMouseUp(e => { - if (e.event.detail === 2) { + if (e.event.detail === 1) { + const behaviour = this.searchConfig.searchEditor.singleClickBehaviour; + const position = e.target.position; + if (position && behaviour === 'peekDefinition') { + const line = this.searchResultEditor.getModel()?.getLineContent(position.lineNumber) ?? ''; + if (line.match(FILE_LINE_REGEX) || line.match(RESULT_LINE_REGEX)) { + this.searchResultEditor.setSelection(Range.fromPositions(position)); + this.commandService.executeCommand('editor.action.peekDefinition'); + } + } + } else if (e.event.detail === 2) { const behaviour = this.searchConfig.searchEditor.doubleClickBehaviour; const position = e.target.position; if (position && behaviour !== 'selectWord') { diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 0ebb5a9f69d..c621e4dc2c9 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -426,6 +426,7 @@ export interface ISearchConfigurationProperties { mode: 'view' | 'reuseEditor' | 'newEditor'; searchEditor: { doubleClickBehaviour: 'selectWord' | 'goToLocation' | 'openLocationToSide'; + singleClickBehaviour: 'default' | 'peekDefinition'; reusePriorSearchConfiguration: boolean; defaultNumberOfContextLines: number | null; experimental: {}; From ec2ef287fc2de3d1f307b4aec435d4f21ce79a88 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 29 Feb 2024 10:02:30 -0800 Subject: [PATCH 1692/1897] Align check for env var collection on remote Checked all causes before applyToProcessEnvironment was used. Fixes #197187 --- src/vs/platform/terminal/common/terminalEnvironment.ts | 9 +++++++++ src/vs/server/node/remoteTerminalChannel.ts | 3 ++- .../contrib/terminal/browser/terminalProcessManager.ts | 3 ++- .../terminal/electron-sandbox/localTerminalBackend.ts | 3 ++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/terminal/common/terminalEnvironment.ts b/src/vs/platform/terminal/common/terminalEnvironment.ts index 38e8fa2c669..d2925819862 100644 --- a/src/vs/platform/terminal/common/terminalEnvironment.ts +++ b/src/vs/platform/terminal/common/terminalEnvironment.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { OperatingSystem, OS } from 'vs/base/common/platform'; +import type { IShellLaunchConfig } from 'vs/platform/terminal/common/terminal'; /** * Aggressively escape non-windows paths to prepare for being sent to a shell. This will do some @@ -59,3 +60,11 @@ export function sanitizeCwd(cwd: string): string { } return cwd; } + +/** + * Determines whether the given shell launch config should use the environment variable collection. + * @param slc The shell launch config to check. + */ +export function shouldUseEnvironmentVariableCollection(slc: IShellLaunchConfig): boolean { + return !slc.strictEnv && !slc.hideFromUser; +} diff --git a/src/vs/server/node/remoteTerminalChannel.ts b/src/vs/server/node/remoteTerminalChannel.ts index caec44f7f3a..657d3e8238a 100644 --- a/src/vs/server/node/remoteTerminalChannel.ts +++ b/src/vs/server/node/remoteTerminalChannel.ts @@ -32,6 +32,7 @@ import { IExtensionManagementService } from 'vs/platform/extensionManagement/com import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; import { promiseWithResolvers } from 'vs/base/common/async'; +import { shouldUseEnvironmentVariableCollection } from 'vs/platform/terminal/common/terminalEnvironment'; class CustomVariableResolver extends AbstractVariableResolverService { constructor( @@ -235,7 +236,7 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel< ); // Apply extension environment variable collections to the environment - if (!shellLaunchConfig.strictEnv) { + if (shouldUseEnvironmentVariableCollection(shellLaunchConfig)) { const entries: [string, IEnvironmentVariableCollection][] = []; for (const [k, v, d] of args.envVariableCollections) { entries.push([k, { map: deserializeEnvironmentVariableCollection(v), descriptionMap: deserializeEnvironmentDescriptionMap(d) }]); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 81770243f91..b731bf456fd 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -40,6 +40,7 @@ import { IEnvironmentVariableCollection, IMergedEnvironmentVariableCollection } import { generateUuid } from 'vs/base/common/uuid'; import { getActiveWindow, runWhenWindowIdle } from 'vs/base/browser/dom'; import { mainWindow } from 'vs/base/browser/window'; +import { shouldUseEnvironmentVariableCollection } from 'vs/platform/terminal/common/terminalEnvironment'; const enum ProcessConstants { /** @@ -436,7 +437,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce baseEnv = await this._terminalProfileResolverService.getEnvironment(this.remoteAuthority); } const env = await terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, envFromConfigValue, variableResolver, this._productService.version, this._configHelper.config.detectLocale, baseEnv); - if (!this._isDisposed && !shellLaunchConfig.strictEnv && !shellLaunchConfig.hideFromUser) { + if (!this._isDisposed && shouldUseEnvironmentVariableCollection(shellLaunchConfig)) { this._extEnvironmentVariableCollection = this._environmentVariableService.mergedCollection; this._register(this._environmentVariableService.onDidChangeCollections(newCollection => this._onEnvironmentVariableCollectionChange(newCollection))); diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts index 06779c99e21..cb35e8698c2 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts @@ -37,6 +37,7 @@ import { IStatusbarService } from 'vs/workbench/services/statusbar/browser/statu import { memoize } from 'vs/base/common/decorators'; import { StopWatch } from 'vs/base/common/stopwatch'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { shouldUseEnvironmentVariableCollection } from 'vs/platform/terminal/common/terminalEnvironment'; export class LocalTerminalBackendContribution implements IWorkbenchContribution { @@ -373,7 +374,7 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke const envFromConfigValue = this._configurationService.getValue(`terminal.integrated.env.${platformKey}`); const baseEnv = await (shellLaunchConfig.useShellEnvironment ? this.getShellEnvironment() : this.getEnvironment()); const env = await terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, envFromConfigValue, variableResolver, this._productService.version, this._configurationService.getValue(TerminalSettingId.DetectLocale), baseEnv); - if (!shellLaunchConfig.strictEnv && !shellLaunchConfig.hideFromUser) { + if (shouldUseEnvironmentVariableCollection(shellLaunchConfig)) { const workspaceFolder = terminalEnvironment.getWorkspaceForTerminal(shellLaunchConfig.cwd, this._workspaceContextService, this._historyService); await this._environmentVariableService.mergedCollection.applyToProcessEnvironment(env, { workspaceFolder }, variableResolver); } From 7d49edbe555d9756ac01d5e55e302399f4df94ba Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 29 Feb 2024 10:14:07 -0800 Subject: [PATCH 1693/1897] Make sticky scroll do something on click for partial cmd Fixes #206544 --- src/vs/workbench/contrib/terminal/browser/terminal.ts | 3 ++- .../stickyScroll/browser/terminalStickyScrollOverlay.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index db2791f9bda..229e1b0b124 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -28,6 +28,7 @@ import { ScrollPosition } from 'vs/workbench/contrib/terminal/browser/xterm/mark import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { GroupIdentifier } from 'vs/workbench/common/editor'; import { ACTIVE_GROUP_TYPE, AUX_WINDOW_GROUP_TYPE, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; +import type { ICurrentPartialCommand } from 'vs/platform/terminal/common/capabilities/commandDetection/terminalCommand'; export const ITerminalService = createDecorator('terminalService'); export const ITerminalEditorService = createDecorator('terminalEditorService'); @@ -117,7 +118,7 @@ export interface IMarkTracker { scrollToClosestMarker(startMarkerId: string, endMarkerId?: string, highlight?: boolean | undefined): void; scrollToLine(line: number, position: ScrollPosition): void; - revealCommand(command: ITerminalCommand, position?: ScrollPosition): void; + revealCommand(command: ITerminalCommand | ICurrentPartialCommand, position?: ScrollPosition): void; revealRange(range: IBufferRange): void; registerTemporaryDecoration(marker: IMarker, endMarker: IMarker | undefined, showOutline: boolean): void; showCommandGuide(command: ITerminalCommand | undefined): void; diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts index 04f14751372..8b8ded275ba 100644 --- a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts @@ -389,7 +389,7 @@ export class TerminalStickyScrollOverlay extends Disposable { // Scroll to the command on click this._register(addStandardDisposableListener(hoverOverlay, 'click', () => { - if (this._xterm && this._currentStickyCommand && 'getOutput' in this._currentStickyCommand) { + if (this._xterm && this._currentStickyCommand) { this._xterm.markTracker.revealCommand(this._currentStickyCommand); this._instance.focus(); } From b95d96c7c3ca0bd5ccd203720030cb629a48b0e0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 29 Feb 2024 10:28:19 -0800 Subject: [PATCH 1694/1897] Refresh sticky scroll content when dimensions differ Fixes #205578 --- .../browser/terminalStickyScrollOverlay.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts index 04f14751372..8848b6715ff 100644 --- a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts @@ -112,6 +112,10 @@ export class TerminalStickyScrollOverlay extends Disposable { this._register(this._themeService.onDidColorThemeChange(() => { this._syncOptions(); })); + this._register(this._xterm.raw.onResize(() => { + this._syncOptions(); + this._throttledRefresh(); + })); this._getSerializeAddonConstructor().then(SerializeAddon => { this._serializeAddon = this._register(new SerializeAddon()); @@ -150,7 +154,7 @@ export class TerminalStickyScrollOverlay extends Disposable { this._xterm.raw.onLineFeed, // Rarely an update may be required after just a cursor move, like when // scrolling horizontally in a pager - this._xterm.raw.onCursorMove + this._xterm.raw.onCursorMove, )(() => this._refresh()), addStandardDisposableListener(this._xterm.raw.element!.querySelector('.xterm-viewport')!, 'scroll', () => this._refresh()), ); @@ -304,7 +308,11 @@ export class TerminalStickyScrollOverlay extends Disposable { } // Write content if it differs - if (content && this._currentContent !== content) { + if ( + content && this._currentContent !== content || + this._stickyScrollOverlay.cols !== xterm.cols || + this._stickyScrollOverlay.rows !== stickyScrollLineCount + ) { this._stickyScrollOverlay.resize(this._stickyScrollOverlay.cols, stickyScrollLineCount); // Clear attrs, reset cursor position, clear right this._stickyScrollOverlay.write('\x1b[0m\x1b[H\x1b[2J'); From be6ae260ba87ea707de66780ab01de01f74ffcf9 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 29 Feb 2024 10:48:11 -0800 Subject: [PATCH 1695/1897] Update SECURITY.md to latest version (#206550) --- SECURITY.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 4fa5946a867..82db58aa7c8 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,34 +1,34 @@ - + ## Security -Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin). -If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. ## Reporting Security Issues **Please do not report security vulnerabilities through public GitHub issues.** -Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). -If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: -* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) -* Full paths of source file(s) related to the manifestation of the issue -* The location of the affected source code (tag/branch/commit or direct URL) -* Any special configuration required to reproduce the issue -* Step-by-step instructions to reproduce the issue -* Proof-of-concept or exploit code (if possible) -* Impact of the issue, including how an attacker might exploit the issue + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue This information will help us triage your report more quickly. -If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. ## Preferred Languages @@ -36,6 +36,6 @@ We prefer all communications to be in English. ## Policy -Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). From 29aad12b6e9e2737cf6ce008bac943defd88730c Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Thu, 29 Feb 2024 11:59:08 -0800 Subject: [PATCH 1696/1897] Fix stuck highlights when occurrenceHighlight set 'off' (#206557) fix for #206486 --- .../contrib/wordHighlighter/browser/wordHighlighter.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts index 8b9790bce21..cae43742902 100644 --- a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts @@ -310,6 +310,11 @@ class WordHighlighter { this._onPositionChanged(e); })); this.toUnhook.add(editor.onDidFocusEditorText((e) => { + if (this.occurrencesHighlight === 'off') { + // Early exit if nothing needs to be done + return; + } + if (!this.workerRequest) { this._run(); } From fb6e06a8cf7e6536989321f90aecd08b066b5997 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 29 Feb 2024 12:13:22 -0800 Subject: [PATCH 1697/1897] fix #203576 --- .../widget/diffEditor/diffEditorWidget.ts | 13 +++++++++++++ .../codeEditor/browser/diffEditorHelper.ts | 16 ++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index 7499fa8e0bc..d7eba7472f9 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -280,6 +280,19 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { } })); + this._register(Event.runAndSubscribe(this._editors.original.onDidChangeCursorPosition, (e) => { + if (e?.reason === CursorChangeReason.Explicit) { + const diff = this._diffModel.get()?.diff.get()?.mappings.find(m => m.lineRangeMapping.original.contains(e.position.lineNumber)); + if (diff?.lineRangeMapping.modified.isEmpty) { + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineDeleted, { source: 'diffEditor.cursorPositionChanged' }); + } else if (diff?.lineRangeMapping.original.isEmpty) { + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineInserted, { source: 'diffEditor.cursorPositionChanged' }); + } else if (diff) { + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineModified, { source: 'diffEditor.cursorPositionChanged' }); + } + } + })); + const isInitializingDiff = this._diffModel.map(this, (m, reader) => { /** @isInitializingDiff isDiffUpToDate */ if (!m) { return undefined; } diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index 902b335ba4f..6aeddc30e35 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -91,9 +91,6 @@ function createScreenReaderHelp(): IDisposable { const keybindingService = accessor.get(IKeybindingService); const contextKeyService = accessor.get(IContextKeyService); - const next = keybindingService.lookupKeybinding(AccessibleDiffViewerNext.id)?.getAriaLabel(); - const previous = keybindingService.lookupKeybinding(AccessibleDiffViewerPrev.id)?.getAriaLabel(); - if (!(editorService.activeTextEditorControl instanceof DiffEditorWidget)) { return; } @@ -103,11 +100,22 @@ function createScreenReaderHelp(): IDisposable { return; } + const next = keybindingService.lookupKeybinding(AccessibleDiffViewerNext.id)?.getAriaLabel(); + const previous = keybindingService.lookupKeybinding(AccessibleDiffViewerPrev.id)?.getAriaLabel(); + let switchSides; + const switchSidesKb = keybindingService.lookupKeybinding('diffEditor.switchSide')?.getAriaLabel(); + if (switchSidesKb) { + switchSides = localize('msg3', "Run the command Diff Editor: Switch Side ({0}) to toggle between the original and modified editors.", switchSidesKb); + } else { + switchSides = localize('switchSidesNoKb', "Run the command Diff Editor: Switch Side, which is currently not triggerable via keybinding, to toggle between the original and modified editors."); + } + const keys = ['accessibility.signals.diffLineDeleted', 'accessibility.signals.diffLineInserted', 'accessibility.signals.diffLineModified']; const content = [ localize('msg1', "You are in a diff editor."), localize('msg2', "View the next ({0}) or previous ({1}) diff in diff review mode, which is optimized for screen readers.", next, previous), - localize('msg3', "To control which accessibility signals should be played, the following settings can be configured: {0}.", keys.join(', ')), + switchSides, + localize('msg4', "To control which accessibility signals should be played, the following settings can be configured: {0}.", keys.join(', ')), ]; const commentCommandInfo = getCommentCommandInfo(keybindingService, contextKeyService, codeEditor); if (commentCommandInfo) { From 5d3b37490ee4bb49bd184910031ee2f1cf809228 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 29 Feb 2024 12:20:06 -0800 Subject: [PATCH 1698/1897] clean up --- .../widget/diffEditor/diffEditorWidget.ts | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index d7eba7472f9..417d75045a3 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -30,7 +30,7 @@ import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IDimension } from 'vs/editor/common/core/dimension'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; +import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents'; import { IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; import { LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { EditorType, IDiffEditorModel, IDiffEditorViewModel, IDiffEditorViewState } from 'vs/editor/common/editorCommon'; @@ -267,31 +267,9 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { ), })); - this._register(Event.runAndSubscribe(this._editors.modified.onDidChangeCursorPosition, (e) => { - if (e?.reason === CursorChangeReason.Explicit) { - const diff = this._diffModel.get()?.diff.get()?.mappings.find(m => m.lineRangeMapping.modified.contains(e.position.lineNumber)); - if (diff?.lineRangeMapping.modified.isEmpty) { - this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineDeleted, { source: 'diffEditor.cursorPositionChanged' }); - } else if (diff?.lineRangeMapping.original.isEmpty) { - this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineInserted, { source: 'diffEditor.cursorPositionChanged' }); - } else if (diff) { - this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineModified, { source: 'diffEditor.cursorPositionChanged' }); - } - } - })); + this._register(Event.runAndSubscribe(this._editors.modified.onDidChangeCursorPosition, e => this._handleCursorPositionChange(e, true))); + this._register(Event.runAndSubscribe(this._editors.original.onDidChangeCursorPosition, e => this._handleCursorPositionChange(e, false))); - this._register(Event.runAndSubscribe(this._editors.original.onDidChangeCursorPosition, (e) => { - if (e?.reason === CursorChangeReason.Explicit) { - const diff = this._diffModel.get()?.diff.get()?.mappings.find(m => m.lineRangeMapping.original.contains(e.position.lineNumber)); - if (diff?.lineRangeMapping.modified.isEmpty) { - this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineDeleted, { source: 'diffEditor.cursorPositionChanged' }); - } else if (diff?.lineRangeMapping.original.isEmpty) { - this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineInserted, { source: 'diffEditor.cursorPositionChanged' }); - } else if (diff) { - this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineModified, { source: 'diffEditor.cursorPositionChanged' }); - } - } - })); const isInitializingDiff = this._diffModel.map(this, (m, reader) => { /** @isInitializingDiff isDiffUpToDate */ @@ -621,6 +599,19 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { } }); } + + private _handleCursorPositionChange(e: ICursorPositionChangedEvent | undefined, isModifiedEditor: boolean): void { + if (e?.reason === CursorChangeReason.Explicit) { + const diff = this._diffModel.get()?.diff.get()?.mappings.find(m => isModifiedEditor ? m.lineRangeMapping.modified.contains(e.position.lineNumber) : m.lineRangeMapping.original.contains(e.position.lineNumber)); + if (diff?.lineRangeMapping.modified.isEmpty) { + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineDeleted, { source: 'diffEditor.cursorPositionChanged' }); + } else if (diff?.lineRangeMapping.original.isEmpty) { + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineInserted, { source: 'diffEditor.cursorPositionChanged' }); + } else if (diff) { + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineModified, { source: 'diffEditor.cursorPositionChanged' }); + } + } + } } function toLineChanges(state: DiffState): ILineChange[] { From 3f74fb2e33b6a7e622a505117e1b773b722138c0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 29 Feb 2024 12:39:00 -0800 Subject: [PATCH 1699/1897] enable by default --- .../accessibility/browser/accessibilityConfiguration.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 88493cde471..7ff7922c187 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -550,7 +550,8 @@ const configuration: IConfigurationNode = { 'properties': { 'sound': { 'description': localize('accessibility.signals.voiceRecordingStarted.sound', "Plays a sound when the voice recording has started."), - ...soundFeatureBase + ...soundFeatureBase, + 'default': 'on' }, } }, From 7ed38cae11b5d9bdf91524e8e527fd4aa1176e04 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 29 Feb 2024 12:56:19 -0800 Subject: [PATCH 1700/1897] replace more instances of audio cues --- .../browser/accessibility.contribution.ts | 4 ++-- ...{saveAudioCue.ts => saveAccessibilitySignal.ts} | 8 +++----- .../accessibilitySignals/browser/commands.ts | 2 +- .../chat/browser/chatAccessibilityService.ts | 14 +++++++------- 4 files changed, 13 insertions(+), 15 deletions(-) rename src/vs/workbench/contrib/accessibility/browser/{saveAudioCue.ts => saveAccessibilitySignal.ts} (73%) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index fb097ebecaa..faa741438ed 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -13,7 +13,7 @@ import { UnfocusedViewDimmingContribution } from 'vs/workbench/contrib/accessibi import { HoverAccessibleViewContribution, InlineCompletionsAccessibleViewContribution, NotificationAccessibleViewContribution } from 'vs/workbench/contrib/accessibility/browser/accessibilityContributions'; import { AccessibilityStatus } from 'vs/workbench/contrib/accessibility/browser/accessibilityStatus'; import { EditorAccessibilityHelpContribution } from 'vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp'; -import { SaveAudioCueContribution } from 'vs/workbench/contrib/accessibility/browser/saveAudioCue'; +import { SaveAccessibilitySignalContribution } from 'vs/workbench/contrib/accessibility/browser/saveAccessibilitySignal'; import { CommentsAccessibilityHelpContribution } from 'vs/workbench/contrib/comments/browser/commentsAccessibility'; registerAccessibilityConfiguration(); @@ -29,5 +29,5 @@ workbenchRegistry.registerWorkbenchContribution(NotificationAccessibleViewContri workbenchRegistry.registerWorkbenchContribution(InlineCompletionsAccessibleViewContribution, LifecyclePhase.Eventually); registerWorkbenchContribution2(AccessibilityStatus.ID, AccessibilityStatus, WorkbenchPhase.BlockRestore); -registerWorkbenchContribution2(SaveAudioCueContribution.ID, SaveAudioCueContribution, WorkbenchPhase.AfterRestored); +registerWorkbenchContribution2(SaveAccessibilitySignalContribution.ID, SaveAccessibilitySignalContribution, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(DynamicSpeechAccessibilityConfiguration.ID, DynamicSpeechAccessibilityConfiguration, WorkbenchPhase.AfterRestored); diff --git a/src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts b/src/vs/workbench/contrib/accessibility/browser/saveAccessibilitySignal.ts similarity index 73% rename from src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts rename to src/vs/workbench/contrib/accessibility/browser/saveAccessibilitySignal.ts index 87abfd5cb8d..e4df2fcc74e 100644 --- a/src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts +++ b/src/vs/workbench/contrib/accessibility/browser/saveAccessibilitySignal.ts @@ -9,17 +9,15 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { SaveReason } from 'vs/workbench/common/editor'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -export class SaveAudioCueContribution extends Disposable implements IWorkbenchContribution { +export class SaveAccessibilitySignalContribution extends Disposable implements IWorkbenchContribution { - static readonly ID = 'workbench.contrib.saveAudioCues'; + static readonly ID = 'workbench.contrib.saveAccessibilitySignal'; constructor( @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, ) { super(); - this._register(this._workingCopyService.onDidSave((e) => { - this._accessibilitySignalService.playSignal(AccessibilitySignal.save, { userGesture: e.reason === SaveReason.EXPLICIT }); - })); + this._register(this._workingCopyService.onDidSave(e => this._accessibilitySignalService.playSignal(AccessibilitySignal.save, { userGesture: e.reason === SaveReason.EXPLICIT }))); } } diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 329cabe4bf3..ec79963a27f 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -76,7 +76,7 @@ export class ShowSignalSoundHelp extends Action2 { qp.onDidChangeActive(() => { accessibilitySignalService.playSound(qp.activeItems[0].signal.sound.getSound(true), true); }); - qp.placeholder = localize('audioCues.help.placeholder', 'Select a sound to play and configure'); + qp.placeholder = localize('sounds.help.placeholder', 'Select a sound to play and configure'); qp.canSelectMany = true; await qp.show(); } diff --git a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts index b6c66797247..73a0ebd76a9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts @@ -15,7 +15,7 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi declare readonly _serviceBrand: undefined; - private _pendingCueMap: DisposableMap = this._register(new DisposableMap()); + private _pendingSignalMap: DisposableMap = this._register(new DisposableMap()); private _requestId: number = 0; @@ -25,11 +25,11 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi acceptRequest(): number { this._requestId++; this._accessibilitySignalService.playSignal(AccessibilitySignal.chatRequestSent, { allowManyInParallel: true }); - this._pendingCueMap.set(this._requestId, this._instantiationService.createInstance(AudioCueScheduler)); + this._pendingSignalMap.set(this._requestId, this._instantiationService.createInstance(AccessibilitySignalScheduler)); return this._requestId; } acceptResponse(response: IChatResponseViewModel | string | undefined, requestId: number): void { - this._pendingCueMap.deleteAndDispose(requestId); + this._pendingSignalMap.deleteAndDispose(requestId); const isPanelChat = typeof response !== 'string'; const responseContent = typeof response === 'string' ? response : response?.response.asString(); this._accessibilitySignalService.playSignal(AccessibilitySignal.chatResponseReceived, { allowManyInParallel: true }); @@ -46,19 +46,19 @@ const CHAT_RESPONSE_PENDING_ALLOWANCE_MS = 4000; /** * Schedules an audio cue to play when a chat response is pending for too long. */ -class AudioCueScheduler extends Disposable { +class AccessibilitySignalScheduler extends Disposable { private _scheduler: RunOnceScheduler; - private _audioCueLoop: IDisposable | undefined; + private _signalLoop: IDisposable | undefined; constructor(@IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService) { super(); this._scheduler = new RunOnceScheduler(() => { - this._audioCueLoop = this._accessibilitySignalService.playSignalLoop(AccessibilitySignal.chatResponsePending, CHAT_RESPONSE_PENDING_AUDIO_CUE_LOOP_MS); + this._signalLoop = this._accessibilitySignalService.playSignalLoop(AccessibilitySignal.chatResponsePending, CHAT_RESPONSE_PENDING_AUDIO_CUE_LOOP_MS); }, CHAT_RESPONSE_PENDING_ALLOWANCE_MS); this._scheduler.schedule(); } override dispose(): void { super.dispose(); - this._audioCueLoop?.dispose(); + this._signalLoop?.dispose(); this._scheduler.cancel(); this._scheduler.dispose(); } From a685b549ce8248bca22a2cf9aeddf39166413661 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 29 Feb 2024 12:59:31 -0800 Subject: [PATCH 1701/1897] Check detected link suffix when opening search links This pulls in some of the smarts from verified links into search links by detecting links with suffixes, seeing if it matches and then using the suffix when opening the quick pick. This allows opening the link `foo` on the line `'foo', line 10` and it will open a search for `foo:10`. Fixes #190847 --- .../terminalContrib/links/browser/links.ts | 5 +++++ .../links/browser/terminalLinkOpeners.ts | 19 ++++++++++++++++++- .../links/browser/terminalWordLinkDetector.ts | 3 ++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/links.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/links.ts index b680864e23d..389f40afc6e 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/links.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/links.ts @@ -83,6 +83,11 @@ export interface ITerminalSimpleLink { */ uri?: URI; + /** + * An optional full line to be used for context when resolving. + */ + contextLine?: string; + /** * The location or selection range of the link. */ diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkOpeners.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkOpeners.ts index 6b5af707012..d4247bbd835 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkOpeners.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkOpeners.ts @@ -22,7 +22,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder'; import { ISearchService } from 'vs/workbench/services/search/common/search'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { getLinkSuffix } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing'; +import { detectLinks, getLinkSuffix } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing'; import { ITerminalLogService } from 'vs/platform/terminal/common/terminal'; export class TerminalLocalFileLinkOpener implements ITerminalLinkOpener { @@ -98,10 +98,27 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener { async open(link: ITerminalSimpleLink): Promise { const osPath = osPathModule(this._getOS()); const pathSeparator = osPath.sep; + // Remove file:/// and any leading ./ or ../ since quick access doesn't understand that format let text = link.text.replace(/^file:\/\/\/?/, ''); text = osPath.normalize(text).replace(/^(\.+[\\/])+/, ''); + // Try extract any trailing line and column numbers by matching the text against parsed + // links. This will give a search link `foo` on a line like `"foo", line 10` to open the + // quick pick with `foo:10` as the contents. + if (link.contextLine) { + const parsedLinks = detectLinks(link.contextLine, this._getOS()); + const matchingParsedLink = parsedLinks.find(parsedLink => parsedLink.suffix && link.text === parsedLink.path.text); + if (matchingParsedLink) { + if (matchingParsedLink.suffix?.row !== undefined) { + text += `:${matchingParsedLink.suffix.row}`; + if (matchingParsedLink.suffix?.col !== undefined) { + text += `:${matchingParsedLink.suffix.col}`; + } + } + } + } + // Remove `:` from the end of the link. // Examples: // - Ruby stack traces: :in ... diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalWordLinkDetector.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalWordLinkDetector.ts index 1675d0fac73..02a9b2cc39f 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalWordLinkDetector.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalWordLinkDetector.ts @@ -103,7 +103,8 @@ export class TerminalWordLinkDetector extends Disposable implements ITerminalLin links.push({ text: word.text, bufferRange, - type: TerminalBuiltinLinkType.Search + type: TerminalBuiltinLinkType.Search, + contextLine: text }); } From dbc6c2972fae62097f133815c50fe5d16ea2ac83 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 29 Feb 2024 13:24:40 -0800 Subject: [PATCH 1702/1897] Support new ' FILE ...' link format Fixes #200166 --- .../links/browser/terminalLocalLinkDetector.ts | 2 ++ .../links/test/browser/terminalLocalLinkDetector.test.ts | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLocalLinkDetector.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLocalLinkDetector.ts index 259df08a249..adee3ee9f1f 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLocalLinkDetector.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLocalLinkDetector.ts @@ -37,6 +37,8 @@ const enum Constants { const fallbackMatchers: RegExp[] = [ // Python style error: File "", line /^ *File (?"(?.+)"(, line (?\d+))?)/, + // Unknown tool #200166: FILE :: + /^ +FILE +(?(?.+)(?::(?\d+)(?::(?\d+))?)?)/, // Some C++ compile error formats: // C:\foo\bar baz(339) : error ... // C:\foo\bar baz(339,12) : error ... diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLocalLinkDetector.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLocalLinkDetector.test.ts index faae5d685ee..785ad9658ab 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLocalLinkDetector.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLocalLinkDetector.test.ts @@ -138,6 +138,10 @@ const supportedFallbackLinkFormats: LinkFormatInfo[] = [ // Python style error: File "", line { urlFormat: 'File "{0}"', linkCellStartOffset: 5 }, { urlFormat: 'File "{0}", line {1}', line: '5', linkCellStartOffset: 5 }, + // Unknown tool #200166: FILE :: + { urlFormat: ' FILE {0}', linkCellStartOffset: 7 }, + { urlFormat: ' FILE {0}:{1}', line: '5', linkCellStartOffset: 7 }, + { urlFormat: ' FILE {0}:{1}:{2}', line: '5', column: '3', linkCellStartOffset: 7 }, // Some C++ compile error formats { urlFormat: '{0}({1}) :', line: '5', linkCellEndOffset: -2 }, { urlFormat: '{0}({1},{2}) :', line: '5', column: '3', linkCellEndOffset: -2 }, From 9f00156541273d204ae6c246b84db9dd01f25294 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 29 Feb 2024 14:01:13 -0800 Subject: [PATCH 1703/1897] fix #205601 --- src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 43246667cfe..0b5277e586d 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -2417,6 +2417,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private async _computeTasksForSingleConfig(workspaceFolder: IWorkspaceFolder, config: TaskConfig.IExternalTaskRunnerConfiguration | undefined, runSource: TaskRunSource, custom: CustomTask[], customized: IStringDictionary, source: TaskConfig.TaskConfigSource, isRecentTask: boolean = false): Promise { if (!config) { return false; + } else if (!workspaceFolder) { + this._logService.trace('TaskService.computeTasksForSingleConfig: no workspace folder'); + return false; } const taskSystemInfo: ITaskSystemInfo | undefined = this._getTaskSystemInfo(workspaceFolder.uri.scheme); const problemReporter = new ProblemReporter(this._outputChannel); From 82af505ffd2378e10eb782b5108d2a39bf354562 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 29 Feb 2024 14:05:08 -0800 Subject: [PATCH 1704/1897] improve log --- src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 0b5277e586d..c32972eee7b 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -2418,7 +2418,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!config) { return false; } else if (!workspaceFolder) { - this._logService.trace('TaskService.computeTasksForSingleConfig: no workspace folder'); + this._logService.trace('TaskService.computeTasksForSingleConfig: no workspace folder for worskspace', this._workspace?.id); return false; } const taskSystemInfo: ITaskSystemInfo | undefined = this._getTaskSystemInfo(workspaceFolder.uri.scheme); From 1943dda2d976b5ee3b5c976bbcea7fdf373027dd Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Thu, 29 Feb 2024 16:04:15 -0800 Subject: [PATCH 1705/1897] further delay IW contribution registration (#206574) --- .../interactive/browser/interactive.contribution.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index 6d300714340..9f3899d7961 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -253,9 +253,13 @@ class InteractiveWindowWorkingCopyEditorHandler extends Disposable implements IW } } -registerWorkbenchContribution2(InteractiveDocumentContribution.ID, InteractiveDocumentContribution, WorkbenchPhase.BlockRestore); -registerWorkbenchContribution2(InteractiveInputContentProvider.ID, InteractiveInputContentProvider, WorkbenchPhase.BlockRestore); -registerWorkbenchContribution2(InteractiveWindowWorkingCopyEditorHandler.ID, InteractiveWindowWorkingCopyEditorHandler, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(InteractiveDocumentContribution.ID, InteractiveDocumentContribution, WorkbenchPhase.AfterRestored); +registerWorkbenchContribution2(InteractiveInputContentProvider.ID, InteractiveInputContentProvider, { + editorTypeId: INTERACTIVE_WINDOW_EDITOR_ID +}); +registerWorkbenchContribution2(InteractiveWindowWorkingCopyEditorHandler.ID, InteractiveWindowWorkingCopyEditorHandler, { + editorTypeId: INTERACTIVE_WINDOW_EDITOR_ID +}); type interactiveEditorInputData = { resource: URI; inputResource: URI; name: string; language: string }; From 31a6c35865768eedec770e03c08dea1f74cd50c2 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Thu, 29 Feb 2024 17:05:48 -0800 Subject: [PATCH 1706/1897] add label in issue reporter when custom URI added (#206577) add label when there is custom uri --- .../issue/issueReporterService.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index 067f09fd0bd..851f5c38798 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -501,6 +501,24 @@ export class IssueReporter extends Disposable { this.previewButton.enabled = false; this.previewButton.label = localize('loadingData', "Loading data..."); } + + const selectedExtension = this.issueReporterModel.getData().selectedExtension; + if (selectedExtension && selectedExtension.uri) { + const extensionLink = document.createElement('a'); + extensionLink.href = URI.revive(selectedExtension.uri).toString(); + extensionLink.textContent = selectedExtension.id; + const issueReporterElement = this.getElementById('issue-reporter')!; + Object.assign(extensionLink.style, { + alignSelf: 'flex-end', + display: 'block', + fontSize: '13px', + marginBottom: '10px', + padding: '4px 0px', + textDecoration: 'none', + width: 'auto' + }); + issueReporterElement.appendChild(extensionLink); + } } private isPreviewEnabled() { From d52acfa1b4a4e92cf225964f7a944096077cdfab Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Thu, 29 Feb 2024 19:08:15 -0600 Subject: [PATCH 1707/1897] add onWillHide to quickpick (#206576) --- src/vs/platform/quickinput/browser/quickInput.ts | 6 ++++++ .../quickinput/browser/quickInputController.ts | 1 + src/vs/platform/quickinput/common/quickInput.ts | 11 +++++++++++ 3 files changed, 18 insertions(+) diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 73c3a23a305..2bfd12c91cb 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -162,6 +162,7 @@ class QuickInput extends Disposable implements IQuickInput { private _lastSeverity: Severity | undefined; private readonly onDidTriggerButtonEmitter = this._register(new Emitter()); private readonly onDidHideEmitter = this._register(new Emitter()); + private readonly onWillHideEmitter = this._register(new Emitter()); private readonly onDisposeEmitter = this._register(new Emitter()); protected readonly visibleDisposables = this._register(new DisposableStore()); @@ -352,6 +353,11 @@ class QuickInput extends Disposable implements IQuickInput { readonly onDidHide = this.onDidHideEmitter.event; + willHide(reason = QuickInputHideReason.Other): void { + this.onWillHideEmitter.fire({ reason }); + } + readonly onWillHide = this.onWillHideEmitter.event; + protected update() { if (!this.visible) { return; diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index d5a23ae66e8..10529b5b903 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -614,6 +614,7 @@ export class QuickInputController extends Disposable { if (!controller) { return; } + controller.willHide(reason); const container = this.ui?.container; const focusChanged = container && !dom.isAncestorOfActiveElement(container); diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index 4ccce2c7120..ef907680e5c 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -209,6 +209,11 @@ export interface IQuickInput extends IDisposable { */ readonly onDidHide: Event; + /** + * An event that is fired when the quick input will be hidden. + */ + readonly onWillHide: Event; + /** * An event that is fired when the quick input is disposed. */ @@ -285,6 +290,12 @@ export interface IQuickInput extends IDisposable { * @param reason The reason why the quick input was hidden. */ didHide(reason?: QuickInputHideReason): void; + + /** + * Notifies that the quick input will be hidden. + * @param reason The reason why the quick input will be hidden. + */ + willHide(reason?: QuickInputHideReason): void; } export interface IQuickWidget extends IQuickInput { From 19ecb4b8337d0871f0a204853003a609d716b04e Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 29 Feb 2024 22:23:07 -0300 Subject: [PATCH 1708/1897] Use input.foreground for chat input (#206564) Fix microsoft/vscode-copilot-release#958 --- build/lib/stylelint/vscode-known-variables.json | 3 ++- src/vs/workbench/contrib/chat/browser/media/chat.css | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index ffbd6ae9edd..51d8c95ae17 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -47,6 +47,7 @@ "--vscode-chat-requestBorder", "--vscode-chat-slashCommandBackground", "--vscode-chat-slashCommandForeground", + "--vscode-chat-list-background", "--vscode-checkbox-background", "--vscode-checkbox-border", "--vscode-checkbox-foreground", @@ -827,4 +828,4 @@ "--zoom-factor", "--test-bar-width" ] -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 20f6f5d621f..793c8f1032a 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -359,6 +359,10 @@ border-color: var(--vscode-focusBorder); } +.interactive-session .interactive-input-and-execute-toolbar .monaco-editor .mtk1 { + color: var(--vscode-input-foreground); +} + .interactive-session .interactive-input-and-execute-toolbar .monaco-editor, .interactive-session .interactive-input-and-execute-toolbar .monaco-editor .monaco-editor-background { background-color: var(--vscode-input-background) !important; From 8f58a72088e36c8aa052c27dd7322f87359b71d7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 1 Mar 2024 11:03:20 +0100 Subject: [PATCH 1709/1897] . --- src/vs/workbench/contrib/speech/browser/speechService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/speech/browser/speechService.ts b/src/vs/workbench/contrib/speech/browser/speechService.ts index a4e23053798..fb511647f02 100644 --- a/src/vs/workbench/contrib/speech/browser/speechService.ts +++ b/src/vs/workbench/contrib/speech/browser/speechService.ts @@ -81,6 +81,7 @@ export class SpeechService extends Disposable implements ISpeechService { } else if (this.providers.size > 1) { this.logService.warn(`Multiple speech providers registered. Picking first one: ${provider.metadata.displayName}`); } + const language = speechLanguageConfigToLanguage(this.configurationService.getValue(SPEECH_LANGUAGE_CONFIG)); const session = this._activeSpeechToTextSession = provider.createSpeechToTextSession(token, typeof language === 'string' ? { language } : undefined); From be59ec51ee54d0e305fd73c092449a5d839ab362 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 1 Mar 2024 05:02:41 -0800 Subject: [PATCH 1710/1897] Add learnMore proposed API to forceNewSession (#206588) ref https://github.com/microsoft/vscode/issues/206587 --- .../api/browser/mainThreadAuthentication.ts | 58 +++++++++++++++---- .../workbench/api/common/extHost.api.impl.ts | 3 + .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.authLearnMore.d.ts | 16 +++++ 4 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.authLearnMore.d.ts diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 3bb8ef8fbe4..b3ebdd940c3 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -8,16 +8,30 @@ import * as nls from 'vs/nls'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { IAuthenticationCreateSessionOptions, AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationService, IAuthenticationExtensionsService } from 'vs/workbench/services/authentication/common/authentication'; import { ExtHostAuthenticationShape, ExtHostContext, MainContext, MainThreadAuthenticationShape } from '../common/extHost.protocol'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import type { AuthenticationGetSessionOptions } from 'vscode'; import { Emitter, Event } from 'vs/base/common/event'; import { IAuthenticationAccessService } from 'vs/workbench/services/authentication/browser/authenticationAccessService'; import { IAuthenticationUsageService } from 'vs/workbench/services/authentication/browser/authenticationUsageService'; import { getAuthenticationProviderActivationEvent } from 'vs/workbench/services/authentication/browser/authenticationService'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; + +interface AuthenticationForceNewSessionOptions { + detail?: string; + learnMore?: UriComponents; + sessionToRecreate?: AuthenticationSession; +} + +interface AuthenticationGetSessionOptions { + clearSessionPreference?: boolean; + createIfNone?: boolean; + forceNewSession?: boolean | AuthenticationForceNewSessionOptions; + silent?: boolean; +} export class MainThreadAuthenticationProvider extends Disposable implements IAuthenticationProvider { @@ -64,7 +78,8 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu @IDialogService private readonly dialogService: IDialogService, @INotificationService private readonly notificationService: INotificationService, @IExtensionService private readonly extensionService: IExtensionService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IOpenerService private readonly openerService: IOpenerService ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication); @@ -102,18 +117,38 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu $removeSession(providerId: string, sessionId: string): Promise { return this.authenticationService.removeSession(providerId, sessionId); } - private async loginPrompt(providerName: string, extensionName: string, recreatingSession: boolean, detail?: string): Promise { + private async loginPrompt(providerName: string, extensionName: string, recreatingSession: boolean, options?: AuthenticationForceNewSessionOptions): Promise { const message = recreatingSession ? nls.localize('confirmRelogin', "The extension '{0}' wants you to sign in again using {1}.", extensionName, providerName) : nls.localize('confirmLogin', "The extension '{0}' wants to sign in using {1}.", extensionName, providerName); - const { confirmed } = await this.dialogService.confirm({ + + const buttons: IPromptButton[] = [ + { + label: nls.localize({ key: 'allow', comment: ['&& denotes a mnemonic'] }, "&&Allow"), + run() { + return true; + }, + } + ]; + if (options?.learnMore) { + buttons.push({ + label: nls.localize('learnMore', "Learn more"), + run: async () => { + const result = this.loginPrompt(providerName, extensionName, recreatingSession, options); + await this.openerService.open(URI.revive(options.learnMore!), { allowCommands: true }); + return await result; + } + }); + } + const { result } = await this.dialogService.prompt({ type: Severity.Info, message, - detail, - primaryButton: nls.localize({ key: 'allow', comment: ['&& denotes a mnemonic'] }, "&&Allow") + buttons, + detail: options?.detail, + cancelButton: true, }); - return confirmed; + return result ?? false; } private async doGetSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: AuthenticationGetSessionOptions): Promise { @@ -156,12 +191,15 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu // We may need to prompt because we don't have a valid session // modal flows if (options.createIfNone || options.forceNewSession) { - const detail = (typeof options.forceNewSession === 'object') ? options.forceNewSession.detail : undefined; + let uiOptions: AuthenticationForceNewSessionOptions | undefined; + if (typeof options.forceNewSession === 'object') { + uiOptions = options.forceNewSession; + } // We only want to show the "recreating session" prompt if we are using forceNewSession & there are sessions // that we will be "forcing through". const recreatingSession = !!(options.forceNewSession && sessions.length); - const isAllowed = await this.loginPrompt(provider.label, extensionName, recreatingSession, detail); + const isAllowed = await this.loginPrompt(provider.label, extensionName, recreatingSession, uiOptions); if (!isAllowed) { throw new Error('User did not consent to login.'); } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index da678cb6efb..05d95911654 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -286,6 +286,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const authentication: typeof vscode.authentication = { getSession(providerId: string, scopes: readonly string[], options?: vscode.AuthenticationGetSessionOptions) { + if (typeof options?.forceNewSession === 'object' && options.forceNewSession.learnMore) { + checkProposedApiEnabled(extension, 'authLearnMore'); + } return extHostAuthentication.getSession(extension, providerId, scopes, options as any); }, getSessions(providerId: string, scopes: readonly string[]) { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 071545be93b..8571e00fc4d 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({ aiRelatedInformation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiRelatedInformation.d.ts', aiTextSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts', authGetSessions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authGetSessions.d.ts', + authLearnMore: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authLearnMore.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', chatParticipant: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipant.d.ts', diff --git a/src/vscode-dts/vscode.proposed.authLearnMore.d.ts b/src/vscode-dts/vscode.proposed.authLearnMore.d.ts new file mode 100644 index 00000000000..c324e24f1a6 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.authLearnMore.d.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * 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/206587 + + export interface AuthenticationForceNewSessionOptions { + /** + * An optional Uri to open in the browser to learn more about this authentication request. + */ + learnMore?: Uri; + } +} From bd79cb3a4671c6981533e308d18d4935ae847037 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 1 Mar 2024 11:32:52 -0300 Subject: [PATCH 1711/1897] Change FollowupProvider to take a ChatContext. (#206611) * Change FollowupProvider to take a ChatContext. Also fix #205761 * Update test --- .../src/singlefolder-tests/chat.test.ts | 2 +- .../workbench/api/browser/mainThreadChatAgents2.ts | 4 ++-- src/vs/workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostChatAgents2.ts | 11 +++++++---- src/vs/workbench/contrib/chat/common/chatAgents.ts | 12 ++++++------ .../workbench/contrib/chat/common/chatServiceImpl.ts | 2 +- .../contrib/chat/test/common/voiceChat.test.ts | 2 +- src/vscode-dts/vscode.proposed.chatParticipant.d.ts | 4 ++-- 8 files changed, 21 insertions(+), 18 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index 9877eb1dd92..2ed59099b34 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -98,7 +98,7 @@ suite('chat', () => { }); participant.isDefault = true; participant.followupProvider = { - provideFollowups(result, _token) { + provideFollowups(result, _context, _token) { deferred.complete(result); return []; }, diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index cf3d1e134dd..445e29220e7 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -91,12 +91,12 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA this._pendingProgress.delete(request.requestId); } }, - provideFollowups: async (request, result, token): Promise => { + provideFollowups: async (request, result, history, token): Promise => { if (!this._agents.get(handle)?.hasFollowups) { return []; } - return this._proxy.$provideFollowups(request, handle, result, token); + return this._proxy.$provideFollowups(request, handle, result, { history }, token); }, provideWelcomeMessage: (token: CancellationToken) => { return this._proxy.$provideWelcomeMessage(handle, token); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index d64dbc0af3f..485c62e477d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1227,7 +1227,7 @@ export type IChatAgentHistoryEntryDto = { export interface ExtHostChatAgentsShape2 { $invokeAgent(handle: number, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; - $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, token: CancellationToken): Promise; + $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; $acceptFeedback(handle: number, result: IChatAgentResult, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void; $acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void; $invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 13bedf2b1e8..07a808d31b9 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -245,14 +245,16 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { this._sessionDisposables.deleteAndDispose(sessionId); } - async $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, token: CancellationToken): Promise { + async $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { return Promise.resolve([]); } + const convertedHistory = await this.prepareHistoryTurns(agent.id, context); + const ehResult = typeConvert.ChatAgentResult.to(result); - return (await agent.provideFollowups(ehResult, token)) + return (await agent.provideFollowups(ehResult, { history: convertedHistory }, token)) .filter(f => { // The followup must refer to a participant that exists from the same extension const isValid = !f.participant || Iterable.some( @@ -376,11 +378,12 @@ class ExtHostChatAgent { return await this._agentVariableProvider.provider.provideCompletionItems(query, token) ?? []; } - async provideFollowups(result: vscode.ChatResult, token: CancellationToken): Promise { + async provideFollowups(result: vscode.ChatResult, context: vscode.ChatContext, token: CancellationToken): Promise { if (!this._followupProvider) { return []; } - const followups = await this._followupProvider.provideFollowups(result, token); + + const followups = await this._followupProvider.provideFollowups(result, context, token); if (!followups) { return []; } diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index b9c3a5d4508..2d9318102b8 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -37,7 +37,7 @@ export interface IChatAgentData { export interface IChatAgentImplementation { invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; - provideFollowups?(request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise; + provideFollowups?(request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>; provideSampleQuestions?(token: CancellationToken): ProviderResult; } @@ -118,7 +118,7 @@ export interface IChatAgentService { registerAgent(name: string, agent: IChatAgentImplementation): IDisposable; registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable; invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; - getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise; + getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; getRegisteredAgents(): Array; getActivatedAgents(): Array; getRegisteredAgent(id: string): IChatAgentData | undefined; @@ -236,7 +236,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService { return await data.impl.invoke(request, progress, history, token); } - async getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise { + async getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { const data = this._agents.get(id); if (!data?.impl) { throw new Error(`No activated agent with id ${id}`); @@ -246,7 +246,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService { return []; } - return data.impl.provideFollowups(request, result, token); + return data.impl.provideFollowups(request, result, history, token); } } @@ -266,9 +266,9 @@ export class MergedChatAgent implements IChatAgent { return this.impl.invoke(request, progress, history, token); } - async provideFollowups(request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise { + async provideFollowups(request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { if (this.impl.provideFollowups) { - return this.impl.provideFollowups(request, result, token); + return this.impl.provideFollowups(request, result, history, token); } return []; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index a4a77bb0ba2..c5835557350 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -551,7 +551,7 @@ export class ChatService extends Disposable implements IChatService { const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); rawResult = agentResult; - agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, followupsCancelToken); + agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, history, followupsCancelToken); } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { request = model.addRequest(parsedRequest, { variables: [] }); // contributed slash commands diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index 864ba72ac0d..dba9fb7a613 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -50,7 +50,7 @@ suite('VoiceChat', () => { registerAgent(name: string, agent: IChatAgentImplementation): IDisposable { throw new Error(); } registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable { throw new Error('Method not implemented.'); } invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error(); } - getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise { throw new Error(); } + getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error(); } getRegisteredAgents(): Array { return agents; } getActivatedAgents(): IChatAgent[] { return agents; } getRegisteredAgent(id: string): IChatAgent | undefined { throw new Error(); } diff --git a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts index b6794145df2..f3b59ac9e84 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -178,7 +178,7 @@ declare module 'vscode' { * @param result This instance has the same properties as the result returned from the participant callback, including `metadata`, but is not the same instance. * @param token A cancellation token. */ - provideFollowups(result: ChatResult, token: CancellationToken): ProviderResult; + provideFollowups(result: ChatResult, context: ChatContext, token: CancellationToken): ProviderResult; } /** @@ -273,7 +273,7 @@ declare module 'vscode' { /** * The prompt as entered by the user. * - * Information about variables used in this request are is stored in {@link ChatRequest.variables}. + * Information about variables used in this request is stored in {@link ChatRequest.variables}. * * *Note* that the {@link ChatParticipant.name name} of the participant and the {@link ChatCommand.name command} * are not part of the prompt. From 2752932c0798c092be0d18209114c0289de8d492 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 1 Mar 2024 12:41:22 -0300 Subject: [PATCH 1712/1897] Remove `sendInteractiveRequestToProvider` from interactive API (#206623) * Remove `sendInteractiveRequestToProvider` from interactive API * Fixes --- .../src/singlefolder-tests/chat.test.ts | 10 +++++----- src/vs/workbench/api/browser/mainThreadChat.ts | 9 +-------- src/vs/workbench/api/common/extHost.api.impl.ts | 4 ---- src/vs/workbench/api/common/extHost.protocol.ts | 3 +-- src/vs/workbench/api/common/extHostChat.ts | 4 ---- src/vs/workbench/contrib/chat/common/chatService.ts | 1 - .../workbench/contrib/chat/common/chatServiceImpl.ts | 7 +------ .../contrib/chat/test/common/chatService.test.ts | 12 ------------ .../contrib/chat/test/common/mockChatService.ts | 9 +++------ src/vscode-dts/vscode.proposed.interactive.d.ts | 4 ---- 10 files changed, 11 insertions(+), 52 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index 2ed59099b34..1a07b475074 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import 'mocha'; -import { CancellationToken, ChatContext, ChatRequest, ChatResult, ChatVariableLevel, Disposable, Event, EventEmitter, InteractiveSession, ProviderResult, chat, interactive } from 'vscode'; +import { commands, CancellationToken, ChatContext, ChatRequest, ChatResult, ChatVariableLevel, Disposable, Event, EventEmitter, InteractiveSession, ProviderResult, chat, interactive } from 'vscode'; import { DeferredPromise, assertNoRpc, closeAllEditors, disposeAll } from '../utils'; suite('chat', () => { @@ -51,7 +51,7 @@ suite('chat', () => { test('participant and slash command', async () => { const onRequest = setupParticipant(); - interactive.sendInteractiveRequestToProvider('provider', { message: '@participant /hello friend' }); + commands.executeCommand('workbench.action.chat.open', { query: '@participant /hello friend' }); let i = 0; onRequest(request => { @@ -59,7 +59,7 @@ suite('chat', () => { assert.deepStrictEqual(request.request.command, 'hello'); assert.strictEqual(request.request.prompt, 'friend'); i++; - interactive.sendInteractiveRequestToProvider('provider', { message: '@participant /hello friend' }); + commands.executeCommand('workbench.action.chat.open', { query: '@participant /hello friend' }); } else { assert.strictEqual(request.context.history.length, 1); assert.strictEqual(request.context.history[0].participant.name, 'participant'); @@ -76,7 +76,7 @@ suite('chat', () => { })); const deferred = getDeferredForRequest(); - interactive.sendInteractiveRequestToProvider('provider', { message: '@participant hi #myVar' }); + commands.executeCommand('workbench.action.chat.open', { query: '@participant hi #myVar' }); const request = await deferred.p; assert.strictEqual(request.prompt, 'hi #myVar'); assert.strictEqual(request.variables[0].values[0].value, 'myValue'); @@ -105,7 +105,7 @@ suite('chat', () => { }; disposables.push(participant); - interactive.sendInteractiveRequestToProvider('provider', { message: '@participant /hello friend' }); + commands.executeCommand('workbench.action.chat.open', { query: '@participant /hello friend' }); const result = await deferred.p; assert.deepStrictEqual(result.metadata, { key: 'value' }); }); diff --git a/src/vs/workbench/api/browser/mainThreadChat.ts b/src/vs/workbench/api/browser/mainThreadChat.ts index c7155e716a9..e70553d866f 100644 --- a/src/vs/workbench/api/browser/mainThreadChat.ts +++ b/src/vs/workbench/api/browser/mainThreadChat.ts @@ -9,7 +9,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtHostChatShape, ExtHostContext, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; -import { IChatDynamicRequest, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; @extHostNamedCustomer(MainContext.MainThreadChat) @@ -83,13 +83,6 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { this._stateEmitters.get(sessionId)?.fire(state); } - async $sendRequestToProvider(providerId: string, message: IChatDynamicRequest): Promise { - const widget = await this._chatWidgetService.revealViewForProvider(providerId); - if (widget && widget.viewModel) { - this._chatService.sendRequestToProvider(widget.viewModel.sessionId, message); - } - } - async $unregisterChatProvider(handle: number): Promise { this._providerRegistrations.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 05d95911654..a719b6131d3 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1388,10 +1388,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'interactive'); return extHostChat.registerChatProvider(extension, id, provider); }, - sendInteractiveRequestToProvider(providerId: string, message: vscode.InteractiveSessionDynamicRequest) { - checkProposedApiEnabled(extension, 'interactive'); - return extHostChat.sendInteractiveRequestToProvider(providerId, message); - }, transferChatSession(session: vscode.InteractiveSession, toWorkspace: vscode.Uri) { checkProposedApiEnabled(extension, 'interactive'); return extHostChat.transferChatSession(session, toWorkspace); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 485c62e477d..b67fd890e78 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -52,7 +52,7 @@ import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatDynamicRequest, IChatFollowup, IChatProgress, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatProgress, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue, IChatVariableData, IChatVariableResolverProgress } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata } from 'vs/workbench/contrib/chat/common/languageModels'; import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem, MainThreadDebugVisualization } from 'vs/workbench/contrib/debug/common/debug'; @@ -1313,7 +1313,6 @@ export type IChatProgressDto = export interface MainThreadChatShape extends IDisposable { $registerChatProvider(handle: number, id: string): Promise; $acceptChatState(sessionId: number, state: any): Promise; - $sendRequestToProvider(providerId: string, message: IChatDynamicRequest): void; $unregisterChatProvider(handle: number): Promise; $transferChatSession(sessionId: number, toWorkspace: UriComponents): void; } diff --git a/src/vs/workbench/api/common/extHostChat.ts b/src/vs/workbench/api/common/extHostChat.ts index d125a8b8f79..9b806f07947 100644 --- a/src/vs/workbench/api/common/extHostChat.ts +++ b/src/vs/workbench/api/common/extHostChat.ts @@ -58,10 +58,6 @@ export class ExtHostChat implements ExtHostChatShape { this._proxy.$transferChatSession(sessionId, newWorkspace); } - sendInteractiveRequestToProvider(providerId: string, message: vscode.InteractiveSessionDynamicRequest): void { - this._proxy.$sendRequestToProvider(providerId, message); - } - async $prepareChat(handle: number, token: CancellationToken): Promise { const entry = this._chatProvider.get(handle); if (!entry) { diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 5da8d9b747b..360d8ea0e34 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -293,7 +293,6 @@ export interface IChatService { cancelCurrentRequestForSession(sessionId: string): void; clearSession(sessionId: string): void; addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, response: IChatCompleteResponse): void; - sendRequestToProvider(sessionId: string, message: IChatDynamicRequest): void; getHistory(): IChatDetail[]; clearAllHistoryEntries(): void; removeHistoryEntry(sessionId: string): void; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index c5835557350..e254395407f 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -25,7 +25,7 @@ import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatCo import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, ISerializableChatData, ISerializableChatsData, getHistoryEntriesFromModel, updateRanges } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { ChatCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/languageModels'; @@ -634,11 +634,6 @@ export class ChatService extends Disposable implements IChatService { model.removeRequest(requestId); } - async sendRequestToProvider(sessionId: string, message: IChatDynamicRequest): Promise<{ responseCompletePromise: Promise } | undefined> { - this.trace('sendRequestToProvider', `sessionId: ${sessionId}`); - return await this.sendRequest(sessionId, message.message); - } - getProviders(): string[] { return Array.from(this._providers.keys()); } 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 0cd1c90c3ce..ab9ef913673 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -195,18 +195,6 @@ suite('Chat', () => { }, 'Expected to throw for dupe provider'); }); - test('sendRequestToProvider', async () => { - const testService = testDisposables.add(instantiationService.createInstance(ChatService)); - testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider')))); - - const model = testDisposables.add(testService.startSession('testProvider', CancellationToken.None)); - assert.strictEqual(model.getRequests().length, 0); - - const response = await testService.sendRequestToProvider(model.sessionId, { message: 'test request' }); - await response?.responseCompletePromise; - assert.strictEqual(model.getRequests().length, 1); - }); - test('addCompleteRequest', async () => { const testService = testDisposables.add(instantiationService.createInstance(ChatService)); testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider')))); diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts index 35c17753392..d961bbb74c5 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { ChatModel, IChatModel, IChatRequestVariableData, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; +import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; +import { ChatModel, IChatModel, IChatRequestVariableData, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { IChatCompleteResponse, IChatDetail, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; export class MockChatService implements IChatService { _serviceBrand: undefined; @@ -60,9 +60,6 @@ export class MockChatService implements IChatService { addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, response: IChatCompleteResponse): void { throw new Error('Method not implemented.'); } - sendRequestToProvider(sessionId: string, message: IChatDynamicRequest): void { - throw new Error('Method not implemented.'); - } getHistory(): IChatDetail[] { throw new Error('Method not implemented.'); } diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index 5644d00ce83..852f8830de2 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -125,8 +125,6 @@ declare module 'vscode' { inputPlaceholder?: string; } - export type InteractiveWelcomeMessageContent = string | MarkdownString | ChatFollowup[]; - export interface InteractiveSessionProvider { prepareSession(token: CancellationToken): ProviderResult; } @@ -144,8 +142,6 @@ declare module 'vscode' { export function registerInteractiveSessionProvider(id: string, provider: InteractiveSessionProvider): Disposable; - export function sendInteractiveRequestToProvider(providerId: string, message: InteractiveSessionDynamicRequest): void; - export function registerInteractiveEditorSessionProvider(provider: InteractiveEditorSessionProvider, metadata?: InteractiveEditorSessionProviderMetadata): Disposable; export function transferChatSession(session: InteractiveSession, toWorkspace: Uri): void; From 45fb8ce098c88a060b2a776a7af6a4a889c88d3a Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Fri, 1 Mar 2024 07:53:24 -0800 Subject: [PATCH 1713/1897] Remove check for featured extensions (#206625) --- .../contrib/welcomeGettingStarted/browser/gettingStarted.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 99d6a260886..2f4054f1e28 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -829,7 +829,7 @@ export class GettingStartedPage extends EditorPane { }; const layoutRecentList = () => { - if (this.container.classList.contains('noWalkthroughs') && this.container.classList.contains('noExtensions')) { + if (this.container.classList.contains('noWalkthroughs')) { recentList.setLimit(10); reset(leftColumn, startList.getDomElement()); reset(rightColumn, recentList.getDomElement()); From 0b208e353d259b25ac2921d408d45823562c9bb2 Mon Sep 17 00:00:00 2001 From: Yusuke Yamada Date: Sat, 2 Mar 2024 01:43:52 +0900 Subject: [PATCH 1714/1897] Fixed to show files in deepest directory in search results (#206609) Fixed to search for FileMatch in addition to the for loop of FolderMatch to check for the presence of AIResults --- src/vs/workbench/contrib/search/browser/searchModel.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 00f9a268b17..4aa0ad102e4 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -1026,6 +1026,15 @@ export class FolderMatch extends Disposable { } } + // FolderMatch on a leaf node does not always run the for-loop of folderMatch because it does not have a FolderMatch as a child. + // Therefore, FileMatch is also searched to check for the existence of AIResults + const fileIterator = this.fileMatchesIterator(); + for (const elem of fileIterator) { + if (elem.hasDownstreamNonAIResults()) { + return true; + } + } + return false; } From bb171489789c9a49e985a4a2c8694138d70d42c1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 1 Mar 2024 18:05:34 +0100 Subject: [PATCH 1715/1897] Editor pane: make `window` accessor that always returns a result (fix #206467) (#206451) --- src/vs/workbench/browser/editor.ts | 10 ++--- .../browser/parts/editor/binaryDiffEditor.ts | 5 ++- .../browser/parts/editor/binaryEditor.ts | 4 +- .../browser/parts/editor/editorPane.ts | 17 ++++--- .../browser/parts/editor/editorPanes.ts | 14 +++--- .../browser/parts/editor/editorPlaceholder.ts | 19 ++++---- .../parts/editor/editorWithViewState.ts | 17 +++---- .../browser/parts/editor/sideBySideEditor.ts | 15 ++++--- .../browser/parts/editor/textCodeEditor.ts | 5 +-- .../browser/parts/editor/textDiffEditor.ts | 15 ++++--- .../browser/parts/editor/textEditor.ts | 13 +++--- .../parts/editor/textResourceEditor.ts | 8 ++-- src/vs/workbench/common/editor.ts | 5 +-- .../contrib/chat/browser/chatEditor.ts | 4 +- .../contrib/debug/browser/disassemblyView.ts | 9 ++-- .../abstractRuntimeExtensionsEditor.ts | 4 +- .../extensions/browser/extensionEditor.ts | 4 +- .../runtimeExtensionsEditor.ts | 4 +- .../files/browser/editors/binaryFileEditor.ts | 11 ++--- .../files/browser/editors/textFileEditor.ts | 10 ++--- .../files/test/browser/editorAutoSave.test.ts | 2 +- .../interactive/browser/interactiveEditor.ts | 34 ++++++-------- .../mergeEditor/browser/view/mergeEditor.ts | 9 ++-- .../browser/multiDiffEditor.ts | 4 +- .../browser/diff/notebookDiffEditor.ts | 25 +++++------ .../notebook/browser/notebookEditor.ts | 45 +++++++++---------- .../contrib/output/browser/outputView.ts | 10 +++-- .../preferences/browser/keybindingsEditor.ts | 4 +- .../preferences/browser/settingsEditor2.ts | 21 +++++---- .../searchEditor/browser/searchEditor.ts | 7 +-- .../terminal/browser/terminalEditor.ts | 15 ++++--- .../webviewPanel/browser/webviewEditor.ts | 13 +++--- .../browser/gettingStarted.ts | 13 +++--- .../browser/walkThroughPart.ts | 31 ++++++------- .../workspace/browser/workspaceTrustEditor.ts | 4 +- .../editor/test/browser/editorService.test.ts | 34 +++++++------- .../history/browser/historyService.ts | 6 +-- .../test/browser/historyService.test.ts | 22 ++++----- .../browser/parts/editor/editorPane.test.ts | 34 +++++++------- .../parts/editor/textEditorPane.test.ts | 4 +- .../test/browser/workbenchTestServices.ts | 4 +- 41 files changed, 274 insertions(+), 260 deletions(-) diff --git a/src/vs/workbench/browser/editor.ts b/src/vs/workbench/browser/editor.ts index 7d0b333bece..d5e1a7f3391 100644 --- a/src/vs/workbench/browser/editor.ts +++ b/src/vs/workbench/browser/editor.ts @@ -59,23 +59,23 @@ export class EditorPaneDescriptor implements IEditorPaneDescriptor { static readonly onWillInstantiateEditorPane = EditorPaneDescriptor._onWillInstantiateEditorPane.event; static create( - ctor: { new(...services: Services): EditorPane }, + ctor: { new(group: IEditorGroup, ...services: Services): EditorPane }, typeId: string, name: string ): EditorPaneDescriptor { - return new EditorPaneDescriptor(ctor as IConstructorSignature, typeId, name); + return new EditorPaneDescriptor(ctor as IConstructorSignature, typeId, name); } private constructor( - private readonly ctor: IConstructorSignature, + private readonly ctor: IConstructorSignature, readonly typeId: string, readonly name: string ) { } - instantiate(instantiationService: IInstantiationService): EditorPane { + instantiate(instantiationService: IInstantiationService, group: IEditorGroup): EditorPane { EditorPaneDescriptor._onWillInstantiateEditorPane.fire({ typeId: this.typeId }); - const pane = instantiationService.createInstance(this.ctor); + const pane = instantiationService.createInstance(this.ctor, group); EditorPaneDescriptor.instantiatedEditorPanes.add(this.typeId); return pane; diff --git a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts index bcd00491191..f4314ae0dbb 100644 --- a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts @@ -13,7 +13,7 @@ import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/bina import { IStorageService } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; /** @@ -24,6 +24,7 @@ export class BinaryResourceDiffEditor extends SideBySideEditor { static override readonly ID = BINARY_DIFF_EDITOR_ID; constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @@ -33,7 +34,7 @@ export class BinaryResourceDiffEditor extends SideBySideEditor { @IEditorService editorService: IEditorService, @IEditorGroupsService editorGroupService: IEditorGroupsService ) { - super(telemetryService, instantiationService, themeService, storageService, configurationService, textResourceConfigurationService, editorService, editorGroupService); + super(group, telemetryService, instantiationService, themeService, storageService, configurationService, textResourceConfigurationService, editorService, editorGroupService); } getMetadata(): string | undefined { diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index cba829c7be5..52a01908818 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -13,6 +13,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { ByteSize } from 'vs/platform/files/common/files'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { EditorPlaceholder, IEditorPlaceholderContents } from 'vs/workbench/browser/parts/editor/editorPlaceholder'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; export interface IOpenCallbacks { openInternal: (input: EditorInput, options: IEditorOptions | undefined) => Promise; @@ -33,12 +34,13 @@ export abstract class BaseBinaryResourceEditor extends EditorPlaceholder { constructor( id: string, + group: IEditorGroup, private readonly callbacks: IOpenCallbacks, telemetryService: ITelemetryService, themeService: IThemeService, @IStorageService storageService: IStorageService ) { - super(id, telemetryService, themeService, storageService); + super(id, group, telemetryService, themeService, storageService); } override getTitle(): string { diff --git a/src/vs/workbench/browser/parts/editor/editorPane.ts b/src/vs/workbench/browser/parts/editor/editorPane.ts index 0f26fa1aa20..7a73fe46fc5 100644 --- a/src/vs/workbench/browser/parts/editor/editorPane.ts +++ b/src/vs/workbench/browser/parts/editor/editorPane.ts @@ -24,6 +24,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { ITextResourceConfigurationChangeEvent, ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; +import { getWindowById } from 'vs/base/browser/dom'; /** * The base class of editors in the workbench. Editors register themselves for specific editor inputs. @@ -70,8 +71,7 @@ export abstract class EditorPane extends Composite implements IEditorPane { protected _options: IEditorOptions | undefined; get options(): IEditorOptions | undefined { return this._options; } - private _group: IEditorGroup | undefined; - get group(): IEditorGroup | undefined { return this._group; } + get window() { return getWindowById(this.group.windowId, true).window; } /** * Should be overridden by editors that have their own ScopedContextKeyService @@ -80,6 +80,7 @@ export abstract class EditorPane extends Composite implements IEditorPane { constructor( id: string, + readonly group: IEditorGroup, telemetryService: ITelemetryService, themeService: IThemeService, storageService: IStorageService @@ -145,22 +146,20 @@ export abstract class EditorPane extends Composite implements IEditorPane { this._options = options; } - override setVisible(visible: boolean, group?: IEditorGroup): void { + override setVisible(visible: boolean): void { super.setVisible(visible); // Propagate to Editor - this.setEditorVisible(visible, group); + this.setEditorVisible(visible); } /** - * Indicates that the editor control got visible or hidden in a specific group. A - * editor instance will only ever be visible in one editor group. + * Indicates that the editor control got visible or hidden. * * @param visible the state of visibility of this editor - * @param group the editor group this editor is in. */ - protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - this._group = group; + protected setEditorVisible(visible: boolean): void { + // Subclasses can implement } setBoundarySashes(_sashes: IBoundarySashes) { diff --git a/src/vs/workbench/browser/parts/editor/editorPanes.ts b/src/vs/workbench/browser/parts/editor/editorPanes.ts index 1094f315842..28e059443e7 100644 --- a/src/vs/workbench/browser/parts/editor/editorPanes.ts +++ b/src/vs/workbench/browser/parts/editor/editorPanes.ts @@ -10,7 +10,7 @@ import Severity from 'vs/base/common/severity'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { EditorExtensions, EditorInputCapabilities, IEditorOpenContext, IVisibleEditorPane, createEditorOpenError, isEditorOpenError } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { Dimension, show, hide, IDomNodePagePosition, isAncestor, getWindow, getActiveElement } from 'vs/base/browser/dom'; +import { Dimension, show, hide, IDomNodePagePosition, isAncestor, getActiveElement, getWindowById } from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; import { IEditorPaneRegistry, IEditorPaneDescriptor } from 'vs/workbench/browser/editor'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -131,7 +131,7 @@ export class EditorPanes extends Disposable { try { // Assert the `EditorInputCapabilities.AuxWindowUnsupported` condition - if (getWindow(this.editorGroupParent) !== mainWindow && editor.hasCapability(EditorInputCapabilities.AuxWindowUnsupported)) { + if (getWindowById(this.groupView.windowId, true).window !== mainWindow && editor.hasCapability(EditorInputCapabilities.AuxWindowUnsupported)) { return await this.doShowError(createEditorOpenError(localize('editorUnsupportedInAuxWindow', "This type of editor cannot be opened in other windows yet."), [ toAction({ id: 'workbench.editor.action.closeEditor', label: localize('openFolder', "Close Editor"), run: async () => { @@ -277,7 +277,7 @@ export class EditorPanes extends Disposable { if (focus && this.shouldRestoreFocus(activeElement)) { pane.focus(); } else if (!internalOptions?.preserveWindowOrder) { - this.hostService.moveTop(getWindow(this.editorGroupParent)); + this.hostService.moveTop(getWindowById(this.groupView.windowId, true).window); } } @@ -353,7 +353,7 @@ export class EditorPanes extends Disposable { show(container); // Indicate to editor that it is now visible - editorPane.setVisible(true, this.groupView); + editorPane.setVisible(true); // Layout if (this.pagePosition) { @@ -393,7 +393,7 @@ export class EditorPanes extends Disposable { } // Otherwise instantiate new - const editorPane = this._register(descriptor.instantiate(this.instantiationService)); + const editorPane = this._register(descriptor.instantiate(this.instantiationService, this.groupView)); this.editorPanes.push(editorPane); return editorPane; @@ -472,7 +472,7 @@ export class EditorPanes extends Disposable { // the DOM to give a chance to persist certain state that // might depend on still being the active DOM element. this.safeRun(() => this._activeEditorPane?.clearInput()); - this.safeRun(() => this._activeEditorPane?.setVisible(false, this.groupView)); + this.safeRun(() => this._activeEditorPane?.setVisible(false)); // Remove editor pane from parent const editorPaneContainer = this._activeEditorPane.getContainer(); @@ -492,7 +492,7 @@ export class EditorPanes extends Disposable { } setVisible(visible: boolean): void { - this.safeRun(() => this._activeEditorPane?.setVisible(visible, this.groupView)); + this.safeRun(() => this._activeEditorPane?.setVisible(visible)); } layout(pagePosition: IDomNodePagePosition): void { diff --git a/src/vs/workbench/browser/parts/editor/editorPlaceholder.ts b/src/vs/workbench/browser/parts/editor/editorPlaceholder.ts index a42032f3c16..7a7691b3850 100644 --- a/src/vs/workbench/browser/parts/editor/editorPlaceholder.ts +++ b/src/vs/workbench/browser/parts/editor/editorPlaceholder.ts @@ -29,6 +29,7 @@ import { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel'; import { FileChangeType, FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; export interface IEditorPlaceholderContents { icon: string; @@ -55,11 +56,12 @@ export abstract class EditorPlaceholder extends EditorPane { constructor( id: string, + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService ) { - super(id, telemetryService, themeService, storageService); + super(id, group, telemetryService, themeService, storageService); } protected createEditor(parent: HTMLElement): void { @@ -186,13 +188,14 @@ export class WorkspaceTrustRequiredPlaceholderEditor extends EditorPlaceholder { static readonly DESCRIPTOR = EditorPaneDescriptor.create(WorkspaceTrustRequiredPlaceholderEditor, WorkspaceTrustRequiredPlaceholderEditor.ID, WorkspaceTrustRequiredPlaceholderEditor.LABEL); constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @ICommandService private readonly commandService: ICommandService, @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, @IStorageService storageService: IStorageService ) { - super(WorkspaceTrustRequiredPlaceholderEditor.ID, telemetryService, themeService, storageService); + super(WorkspaceTrustRequiredPlaceholderEditor.ID, group, telemetryService, themeService, storageService); } override getTitle(): string { @@ -223,18 +226,18 @@ export class ErrorPlaceholderEditor extends EditorPlaceholder { static readonly DESCRIPTOR = EditorPaneDescriptor.create(ErrorPlaceholderEditor, ErrorPlaceholderEditor.ID, ErrorPlaceholderEditor.LABEL); constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService, @IFileService private readonly fileService: IFileService, @IDialogService private readonly dialogService: IDialogService ) { - super(ErrorPlaceholderEditor.ID, telemetryService, themeService, storageService); + super(ErrorPlaceholderEditor.ID, group, telemetryService, themeService, storageService); } protected async getContents(input: EditorInput, options: IErrorEditorPlaceholderOptions, disposables: DisposableStore): Promise { const resource = input.resource; - const group = this.group; const error = options.error; const isFileNotFound = (error)?.fileOperationResult === FileOperationResult.FILE_NOT_FOUND; @@ -274,20 +277,20 @@ export class ErrorPlaceholderEditor extends EditorPlaceholder { } }; }); - } else if (group) { + } else { actions = [ { label: localize('retry', "Try Again"), - run: () => group.openEditor(input, { ...options, source: EditorOpenSource.USER /* explicit user gesture */ }) + run: () => this.group.openEditor(input, { ...options, source: EditorOpenSource.USER /* explicit user gesture */ }) } ]; } // Auto-reload when file is added - if (group && isFileNotFound && resource && this.fileService.hasProvider(resource)) { + if (isFileNotFound && resource && this.fileService.hasProvider(resource)) { disposables.add(this.fileService.onDidFilesChange(e => { if (e.contains(resource, FileChangeType.ADDED, FileChangeType.UPDATED)) { - group.openEditor(input, options); + this.group.openEditor(input, options); } })); } diff --git a/src/vs/workbench/browser/parts/editor/editorWithViewState.ts b/src/vs/workbench/browser/parts/editor/editorWithViewState.ts index f01bedd8f59..e31756007e9 100644 --- a/src/vs/workbench/browser/parts/editor/editorWithViewState.ts +++ b/src/vs/workbench/browser/parts/editor/editorWithViewState.ts @@ -31,6 +31,7 @@ export abstract class AbstractEditorWithViewState extends Edit constructor( id: string, + group: IEditorGroup, viewStateStorageKey: string, @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService protected readonly instantiationService: IInstantiationService, @@ -40,17 +41,17 @@ export abstract class AbstractEditorWithViewState extends Edit @IEditorService protected readonly editorService: IEditorService, @IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService ) { - super(id, telemetryService, themeService, storageService); + super(id, group, telemetryService, themeService, storageService); this.viewState = this.getEditorMemento(editorGroupService, textResourceConfigurationService, viewStateStorageKey, 100); } - protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { + protected override setEditorVisible(visible: boolean): void { // Listen to close events to trigger `onWillCloseEditorInGroup` - this.groupListener.value = group?.onWillCloseEditor(e => this.onWillCloseEditor(e)); + this.groupListener.value = this.group.onWillCloseEditor(e => this.onWillCloseEditor(e)); - super.setEditorVisible(visible, group); + super.setEditorVisible(visible); } private onWillCloseEditor(e: IEditorCloseEvent): void { @@ -110,7 +111,7 @@ export abstract class AbstractEditorWithViewState extends Edit // - the user configured to not restore view state unless the editor is still opened in the group if ( (input.isDisposed() && !this.tracksDisposedEditorViewState()) || - (!this.shouldRestoreEditorViewState(input) && (!this.group || !this.group.contains(input))) + (!this.shouldRestoreEditorViewState(input) && !this.group.contains(input)) ) { this.clearEditorViewState(resource, this.group); } @@ -147,10 +148,6 @@ export abstract class AbstractEditorWithViewState extends Edit } private saveEditorViewState(resource: URI): void { - if (!this.group) { - return; - } - const editorViewState = this.computeEditorViewState(resource); if (!editorViewState) { return; @@ -160,7 +157,7 @@ export abstract class AbstractEditorWithViewState extends Edit } protected loadEditorViewState(input: EditorInput | undefined, context?: IEditorOpenContext): T | undefined { - if (!input || !this.group) { + if (!input) { return undefined; // we need valid input } diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index becfd13cac2..c3d7a0cce9d 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -122,6 +122,7 @@ export class SideBySideEditor extends AbstractEditorWithViewState extends return this.editorControl?.hasTextFocus() || super.hasFocus(); } - protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - super.setEditorVisible(visible, group); + protected override setEditorVisible(visible: boolean): void { + super.setEditorVisible(visible); if (visible) { this.editorControl?.onVisible(); diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 21429e37334..3c298c26b1e 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -58,6 +58,7 @@ export class TextDiffEditor extends AbstractTextEditor imp } constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, @@ -68,7 +69,7 @@ export class TextDiffEditor extends AbstractTextEditor imp @IFileService fileService: IFileService, @IPreferencesService private readonly preferencesService: IPreferencesService ) { - super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService, fileService); + super(TextDiffEditor.ID, group, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService, fileService); } override getTitle(): string { @@ -171,7 +172,7 @@ export class TextDiffEditor extends AbstractTextEditor imp } // Handle case where a file is too large to open without confirmation - if ((error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE && this.group) { + if ((error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE) { let message: string; if (error instanceof TooLargeFileOperationError) { message = localize('fileTooLargeForHeapErrorWithSize', "At least one file is not displayed in the text compare editor because it is very large ({0}).", ByteSize.formatSize(error.size)); @@ -222,7 +223,7 @@ export class TextDiffEditor extends AbstractTextEditor imp } // Replace this editor with the binary one - (this.group ?? this.editorGroupService.activeGroup).replaceEditors([{ + this.group.replaceEditors([{ editor: input, replacement: binaryDiffInput, options: { @@ -232,8 +233,8 @@ export class TextDiffEditor extends AbstractTextEditor imp // and do not control the initial intent that resulted // in us now opening as binary. activation: EditorActivation.PRESERVE, - pinned: this.group?.isPinned(input), - sticky: this.group?.isSticky(input) + pinned: this.group.isPinned(input), + sticky: this.group.isSticky(input) } }]); } @@ -365,8 +366,8 @@ export class TextDiffEditor extends AbstractTextEditor imp return this.diffEditorControl?.hasTextFocus() || super.hasFocus(); } - protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - super.setEditorVisible(visible, group); + protected override setEditorVisible(visible: boolean): void { + super.setEditorVisible(visible); if (visible) { this.diffEditorControl?.onVisible(); diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 563f12a1be7..f1b5b3a91a8 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -22,7 +22,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITextResourceConfigurationChangeEvent, ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorOptions, ITextEditorOptions, TextEditorSelectionRevealType, TextEditorSelectionSource } from 'vs/platform/editor/common/editor'; @@ -62,6 +62,7 @@ export abstract class AbstractTextEditor extends Abs constructor( id: string, + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, @@ -71,7 +72,7 @@ export abstract class AbstractTextEditor extends Abs @IEditorGroupsService editorGroupService: IEditorGroupsService, @IFileService protected readonly fileService: IFileService ) { - super(id, AbstractTextEditor.VIEW_STATE_PREFERENCE_KEY, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService); + super(id, group, AbstractTextEditor.VIEW_STATE_PREFERENCE_KEY, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService); // Listen to configuration changes this._register(this.textResourceConfigurationService.onDidChangeConfiguration(e => this.handleConfigurationChangeEvent(e))); @@ -127,8 +128,8 @@ export abstract class AbstractTextEditor extends Abs return editorConfiguration; } - private computeAriaLabel(): string { - return this._input ? computeEditorAriaLabel(this._input, undefined, this.group, this.editorGroupService.count) : localize('editor', "Editor"); + protected computeAriaLabel(): string { + return this.input ? computeEditorAriaLabel(this.input, undefined, this.group, this.editorGroupService.count) : localize('editor', "Editor"); } private onDidChangeFileSystemProvider(scheme: string): void { @@ -255,12 +256,12 @@ export abstract class AbstractTextEditor extends Abs super.clearInput(); } - protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { + protected override setEditorVisible(visible: boolean): void { if (visible) { this.consumePendingConfigurationChangeEvent(); } - super.setEditorVisible(visible, group); + super.setEditorVisible(visible); } protected override toEditorViewStateResource(input: EditorInput): URI | undefined { diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 7a16fa667b8..0a3b885e01d 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -18,7 +18,7 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/tex import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ScrollType, ICodeEditorViewState } from 'vs/editor/common/editorCommon'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IModelService } from 'vs/editor/common/services/model'; @@ -37,6 +37,7 @@ export abstract class AbstractTextResourceEditor extends AbstractTextCodeEditor< constructor( id: string, + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, @@ -46,7 +47,7 @@ export abstract class AbstractTextResourceEditor extends AbstractTextCodeEditor< @IEditorService editorService: IEditorService, @IFileService fileService: IFileService ) { - super(id, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService, fileService); + super(id, group, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService, fileService); } override async setInput(input: AbstractTextResourceEditorInput, options: ITextEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { @@ -130,6 +131,7 @@ export class TextResourceEditor extends AbstractTextResourceEditor { static readonly ID = 'workbench.editors.textResourceEditor'; constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, @@ -141,7 +143,7 @@ export class TextResourceEditor extends AbstractTextResourceEditor { @ILanguageService private readonly languageService: ILanguageService, @IFileService fileService: IFileService ) { - super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, editorService, fileService); + super(TextResourceEditor.ID, group, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, editorService, fileService); } protected override createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): void { diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index c27b43d382c..6cb1ce9b115 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -74,7 +74,7 @@ export interface IEditorDescriptor { /** * Instantiates the editor pane using the provided services. */ - instantiate(instantiationService: IInstantiationService): T; + instantiate(instantiationService: IInstantiationService, group: IEditorGroup): T; /** * Whether the descriptor is for the provided editor pane. @@ -119,7 +119,7 @@ export interface IEditorPane extends IComposite { /** * The assigned group this editor is showing in. */ - readonly group: IEditorGroup | undefined; + readonly group: IEditorGroup; /** * The minimum width of this editor. @@ -327,7 +327,6 @@ export function findViewStateForEditor(input: EditorInput, group: GroupIdentifie */ export interface IVisibleEditorPane extends IEditorPane { readonly input: EditorInput; - readonly group: IEditorGroup; } /** diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index 4651778b165..d184cb7525c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -20,6 +20,7 @@ import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInp import { IChatViewState, ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { IChatModel, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; import { clearChatEditor } from 'vs/workbench/contrib/chat/browser/actions/chatClear'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; export interface IChatEditorOptions extends IEditorOptions { target: { sessionId: string } | { providerId: string } | { data: ISerializableChatData }; @@ -37,13 +38,14 @@ export class ChatEditor extends EditorPane { private _viewState: IChatViewState | undefined; constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IStorageService private readonly storageService: IStorageService, @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { - super(ChatEditorInput.EditorID, telemetryService, themeService, storageService); + super(ChatEditorInput.EditorID, group, telemetryService, themeService, storageService); } public async clear() { diff --git a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts index e092df64537..92f139a3721 100644 --- a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts +++ b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { PixelRatio } from 'vs/base/browser/pixelRatio'; -import { $, Dimension, addStandardDisposableListener, append, getWindowById } from 'vs/base/browser/dom'; +import { $, Dimension, addStandardDisposableListener, append } from 'vs/base/browser/dom'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { ITableRenderer, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table'; import { binarySearch2 } from 'vs/base/common/arrays'; @@ -42,6 +42,7 @@ import { InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugMo import { getUriFromSource } from 'vs/workbench/contrib/debug/common/debugSource'; import { isUri, sourcesEqual } from 'vs/workbench/contrib/debug/common/debugUtils'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; interface IDisassembledInstructionEntry { allowBreakpoint: boolean; @@ -92,6 +93,7 @@ export class DisassemblyView extends EditorPane { private readonly _referenceToMemoryAddress = new Map(); constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService, @@ -99,7 +101,7 @@ export class DisassemblyView extends EditorPane { @IInstantiationService private readonly _instantiationService: IInstantiationService, @IDebugService private readonly _debugService: IDebugService, ) { - super(DISASSEMBLY_VIEW_ID, telemetryService, themeService, storageService); + super(DISASSEMBLY_VIEW_ID, group, telemetryService, themeService, storageService); this._disassembledInstructions = undefined; this._onDidChangeStackFrame = this._register(new Emitter({ leakWarningThreshold: 1000 })); @@ -133,8 +135,7 @@ export class DisassemblyView extends EditorPane { } private createFontInfo() { - const window = getWindowById(this.group?.windowId, true).window; - return BareFontInfo.createFromRawSettings(this._configurationService.getValue('editor'), PixelRatio.getInstance(window).value); + return BareFontInfo.createFromRawSettings(this._configurationService.getValue('editor'), PixelRatio.getInstance(this.window).value); } get currentInstructionAddresses() { diff --git a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index 1c278f159d3..7c7fbac3a95 100644 --- a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -38,6 +38,7 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { errorIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; @@ -77,6 +78,7 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { private _updateSoon: RunOnceScheduler; constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IContextKeyService contextKeyService: IContextKeyService, @@ -91,7 +93,7 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { @IClipboardService private readonly _clipboardService: IClipboardService, @IExtensionFeaturesManagementService private readonly _extensionFeaturesManagementService: IExtensionFeaturesManagementService, ) { - super(AbstractRuntimeExtensionsEditor.ID, telemetryService, themeService, storageService); + super(AbstractRuntimeExtensionsEditor.ID, group, telemetryService, themeService, storageService); this._list = null; this._elements = null; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 8f4ca1d9d6a..b2f9fcbd754 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -81,6 +81,7 @@ import { ExtensionsInput, IExtensionEditorOptions } from 'vs/workbench/contrib/e import { DEFAULT_MARKDOWN_STYLES, renderMarkdownDocument } from 'vs/workbench/contrib/markdown/browser/markdownDocumentRenderer'; import { ShowCurrentReleaseNotesActionId } from 'vs/workbench/contrib/update/common/update'; import { IWebview, IWebviewService, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/webview/browser/webview'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -228,6 +229,7 @@ export class ExtensionEditor extends EditorPane { private showPreReleaseVersionContextKey: IContextKey | undefined; constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService, @@ -244,7 +246,7 @@ export class ExtensionEditor extends EditorPane { @IContextMenuService private readonly contextMenuService: IContextMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { - super(ExtensionEditor.ID, telemetryService, themeService, storageService); + super(ExtensionEditor.ID, group, telemetryService, themeService, storageService); this.extensionReadme = null; this.extensionChangelog = null; this.extensionManifest = null; diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts index 11dc035fb5b..14cb766b00d 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts @@ -30,6 +30,7 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { Schemas } from 'vs/base/common/network'; import { joinPath } from 'vs/base/common/resources'; import { IExtensionFeaturesManagementService } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; export const IExtensionHostProfileService = createDecorator('extensionHostProfileService'); export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey('profileSessionState', 'none'); @@ -65,6 +66,7 @@ export class RuntimeExtensionsEditor extends AbstractRuntimeExtensionsEditor { private _profileSessionState: IContextKey; constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IContextKeyService contextKeyService: IContextKeyService, @@ -80,7 +82,7 @@ export class RuntimeExtensionsEditor extends AbstractRuntimeExtensionsEditor { @IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService, @IExtensionFeaturesManagementService extensionFeaturesManagementService: IExtensionFeaturesManagementService, ) { - super(telemetryService, themeService, contextKeyService, extensionsWorkbenchService, extensionService, notificationService, contextMenuService, instantiationService, storageService, labelService, environmentService, clipboardService, extensionFeaturesManagementService); + super(group, telemetryService, themeService, contextKeyService, extensionsWorkbenchService, extensionService, notificationService, contextMenuService, instantiationService, storageService, labelService, environmentService, clipboardService, extensionFeaturesManagementService); this._profileInfo = this._extensionHostProfileService.lastProfile; this._extensionsHostRecorded = CONTEXT_EXTENSION_HOST_PROFILE_RECORDED.bindTo(contextKeyService); this._profileSessionState = CONTEXT_PROFILE_SESSION_STATE.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts index ad722bacc27..1b6b279b308 100644 --- a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts @@ -15,7 +15,7 @@ import { EditorResolution, IEditorOptions } from 'vs/platform/editor/common/edit import { IEditorResolverService, ResolvedStatus, ResolvedEditor } from 'vs/workbench/services/editor/common/editorResolverService'; import { isEditorInputWithOptions } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; /** * An implementation of editor for binary files that cannot be displayed. @@ -25,14 +25,15 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { static readonly ID = BINARY_FILE_EDITOR_ID; constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService, - @IStorageService storageService: IStorageService, - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService + @IStorageService storageService: IStorageService ) { super( BinaryFileEditor.ID, + group, { openInternal: (input, options) => this.openInternal(input, options) }, @@ -43,7 +44,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { } private async openInternal(input: EditorInput, options: IEditorOptions | undefined): Promise { - if (input instanceof FileEditorInput && this.group?.activeEditor) { + if (input instanceof FileEditorInput && this.group.activeEditor) { // We operate on the active editor here to support re-opening // diff editors where `input` may just be one side of the @@ -84,7 +85,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { } // Replace the active editor with the picked one - await (this.group ?? this.editorGroupService.activeGroup).replaceEditors([{ + await this.group.replaceEditors([{ editor: activeEditor, replacement: resolvedEditor?.editor ?? input, options: { diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index c0f1c912060..7c01103959b 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -46,6 +46,7 @@ export class TextFileEditor extends AbstractTextCodeEditor static readonly ID = TEXT_FILE_EDITOR_ID; constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IFileService fileService: IFileService, @IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService, @@ -65,7 +66,7 @@ export class TextFileEditor extends AbstractTextCodeEditor @IHostService private readonly hostService: IHostService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService ) { - super(TextFileEditor.ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService, fileService); + super(TextFileEditor.ID, group, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService, fileService); // Clear view state for deleted files this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e))); @@ -192,7 +193,7 @@ export class TextFileEditor extends AbstractTextCodeEditor } // Handle case where a file is too large to open without confirmation - if ((error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE && this.group) { + if ((error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE) { let message: string; if (error instanceof TooLargeFileOperationError) { message = localize('fileTooLargeForHeapErrorWithSize', "The file is not displayed in the text editor because it is very large ({0}).", ByteSize.formatSize(error.size)); @@ -240,7 +241,6 @@ export class TextFileEditor extends AbstractTextCodeEditor private openAsBinary(input: FileEditorInput, options: ITextEditorOptions | undefined): void { const defaultBinaryEditor = this.configurationService.getValue('workbench.editor.defaultBinaryEditor'); - const group = this.group ?? this.editorGroupService.activeGroup; const editorOptions = { ...options, @@ -259,9 +259,9 @@ export class TextFileEditor extends AbstractTextCodeEditor // and avoid enforcing binary or text on the file editor input. if (defaultBinaryEditor && defaultBinaryEditor !== '' && defaultBinaryEditor !== DEFAULT_EDITOR_ASSOCIATION.id) { - this.doOpenAsBinaryInDifferentEditor(group, defaultBinaryEditor, input, editorOptions); + this.doOpenAsBinaryInDifferentEditor(this.group, defaultBinaryEditor, input, editorOptions); } else { - this.doOpenAsBinaryInSameEditor(group, defaultBinaryEditor, input, editorOptions); + this.doOpenAsBinaryInSameEditor(this.group, defaultBinaryEditor, input, editorOptions); } } diff --git a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts index e1ecd72a649..f104ba762eb 100644 --- a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts @@ -104,7 +104,7 @@ suite('EditorAutoSave', () => { assert.strictEqual(model.isDirty(), false); - await editorPane?.group?.closeAllEditors(); + await editorPane?.group.closeAllEditors(); }); function awaitModelSaved(model: ITextFileEditorModel): Promise { diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index e0f91695461..6793200e5e9 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { ICodeEditorViewState, IDecorationOptions } from 'vs/editor/common/editorCommon'; @@ -63,7 +63,6 @@ import { INTERACTIVE_WINDOW_EDITOR_ID } from 'vs/workbench/contrib/notebook/comm import 'vs/css!./interactiveEditor'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { deepClone } from 'vs/base/common/objects'; -import { mainWindow } from 'vs/base/browser/window'; const DECORATION_KEY = 'interactiveInputDecoration'; const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState'; @@ -108,7 +107,7 @@ export class InteractiveEditor extends EditorPane { private _editorOptions: IEditorOptions; private _notebookOptions: NotebookOptions; private _editorMemento: IEditorMemento; - private _groupListener = this._register(new DisposableStore()); + private _groupListener = this._register(new MutableDisposable()); private _runbuttonToolbar: ToolBar | undefined; private _onDidFocusWidget = this._register(new Emitter()); @@ -117,6 +116,7 @@ export class InteractiveEditor extends EditorPane { readonly onDidChangeSelection = this._onDidChangeSelection.event; constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService, @@ -137,6 +137,7 @@ export class InteractiveEditor extends EditorPane { ) { super( INTERACTIVE_WINDOW_EDITOR_ID, + group, telemetryService, themeService, storageService @@ -160,7 +161,7 @@ export class InteractiveEditor extends EditorPane { this._editorOptions = this._computeEditorOptions(); } })); - this._notebookOptions = new NotebookOptions(DOM.getWindowById(this.group?.windowId, true).window ?? mainWindow, configurationService, notebookExecutionStateService, codeEditorService, true, { cellToolbarInteraction: 'hover', globalToolbar: true, stickyScrollEnabled: false, dragAndDropEnabled: false }); + this._notebookOptions = new NotebookOptions(this.window, configurationService, notebookExecutionStateService, codeEditorService, true, { cellToolbarInteraction: 'hover', globalToolbar: true, stickyScrollEnabled: false, dragAndDropEnabled: false }); //TODO@bpasero might crash this._editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY); codeEditorService.registerDecorationType('interactive-decoration', DECORATION_KEY, {}); @@ -313,7 +314,7 @@ export class InteractiveEditor extends EditorPane { } private _saveEditorViewState(input: EditorInput | undefined): void { - if (this.group && this._notebookWidget.value && input instanceof InteractiveEditorInput) { + if (this._notebookWidget.value && input instanceof InteractiveEditorInput) { if (this._notebookWidget.value.isDisposed) { return; } @@ -328,10 +329,7 @@ export class InteractiveEditor extends EditorPane { } private _loadNotebookEditorViewState(input: InteractiveEditorInput): InteractiveEditorViewState | undefined { - let result: InteractiveEditorViewState | undefined; - if (this.group) { - result = this._editorMemento.loadEditorState(this.group, input.notebookEditorInput.resource); - } + const result = this._editorMemento.loadEditorState(this.group, input.notebookEditorInput.resource); if (result) { return result; } @@ -351,7 +349,6 @@ export class InteractiveEditor extends EditorPane { } override async setInput(input: InteractiveEditorInput, options: InteractiveEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { - const group = this.group!; const notebookInput = input.notebookEditorInput; // there currently is a widget which we still own so @@ -362,9 +359,7 @@ export class InteractiveEditor extends EditorPane { this._widgetDisposableStore.clear(); - const codeWindow = this.group ? DOM.getWindowById(group.windowId, true).window : mainWindow; - - this._notebookWidget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, group, notebookInput, { + this._notebookWidget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, this.group, notebookInput, { isEmbedded: true, isReadOnly: true, contributions: NotebookEditorExtensionsRegistry.getSomeEditorContributions([ @@ -388,8 +383,8 @@ export class InteractiveEditor extends EditorPane { MarkerController.ID ]), options: this._notebookOptions, - codeWindow: codeWindow - }, undefined, this._rootElement ? DOM.getWindow(this._rootElement) : mainWindow); + codeWindow: this.window + }, undefined, this.window); this._codeEditorWidget = this._instantiationService.createInstance(CodeEditorWidget, this._inputEditorContainer, this._editorOptions, { ...{ @@ -681,12 +676,9 @@ export class InteractiveEditor extends EditorPane { this._notebookWidget.value!.focus(); } - protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - super.setEditorVisible(visible, group); - if (group) { - this._groupListener.clear(); - this._groupListener.add(group.onWillCloseEditor(e => this._saveEditorViewState(e.editor))); - } + protected override setEditorVisible(visible: boolean): void { + super.setEditorVisible(visible); + this._groupListener.value = this.group.onWillCloseEditor(e => this._saveEditorViewState(e.editor)); if (!visible) { this._saveEditorViewState(this.input); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index 45af28b94f0..3609b0046fa 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -108,6 +108,7 @@ export class MergeEditor extends AbstractTextEditor { private readonly scrollSynchronizer = this._register(new ScrollSynchronizer(this._viewModel, this.input1View, this.input2View, this.baseView, this.inputResultView, this._layoutModeObs)); constructor( + group: IEditorGroup, @IInstantiationService instantiation: IInstantiationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @ITelemetryService telemetryService: ITelemetryService, @@ -121,7 +122,7 @@ export class MergeEditor extends AbstractTextEditor { @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, @IConfigurationService private readonly configurationService: IConfigurationService ) { - super(MergeEditor.ID, telemetryService, instantiation, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService, fileService); + super(MergeEditor.ID, group, telemetryService, instantiation, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService, fileService); } override dispose(): void { @@ -354,7 +355,7 @@ export class MergeEditor extends AbstractTextEditor { // all empty -> replace this editor with a normal editor for result that.editorService.replaceEditors( [{ editor: input, replacement: { resource: input.result, options: { preserveFocus: true } }, forceReplaceDirty: true }], - that.group ?? that.editorGroupService.activeGroup + that.group ); } }); @@ -467,8 +468,8 @@ export class MergeEditor extends AbstractTextEditor { return super.hasFocus(); } - protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - super.setEditorVisible(visible, group); + protected override setEditorVisible(visible: boolean): void { + super.setEditorVisible(visible); for (const { editor } of [this.input1View, this.input2View, this.inputResultView]) { if (visible) { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 6f66dbec274..ffe7a8738f5 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -19,7 +19,7 @@ import { ICompositeControl } from 'vs/workbench/common/composite'; import { IEditorOpenContext } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { MultiDiffEditorInput } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { URI } from 'vs/base/common/uri'; import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel'; @@ -39,6 +39,7 @@ export class MultiDiffEditor extends AbstractEditorWithViewState('editor'); - return FontMeasurements.readFontInfo(window, BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(window).value)); + return FontMeasurements.readFontInfo(this.window, BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(this.window).value)); } private isOverviewRulerEnabled(): boolean { @@ -271,7 +270,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD NotebookTextDiffList, 'NotebookTextDiff', this._listViewContainer, - this.instantiationService.createInstance(NotebookCellTextDiffListDelegate, DOM.getWindow(this._listViewContainer)), + this.instantiationService.createInstance(NotebookCellTextDiffListDelegate, this.window), renderers, this.contextKeyService, { @@ -462,7 +461,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD private _attachModel() { this._eventDispatcher = new NotebookDiffEditorEventDispatcher(); const updateInsets = () => { - DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._listViewContainer), () => { + DOM.scheduleAtNextAnimationFrame(this.window, () => { if (this._isDisposed) { return; } @@ -499,7 +498,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD }, undefined) as BackLayerWebView; // attach the webview container to the DOM tree first this._list.rowsContainer.insertAdjacentElement('afterbegin', this._modifiedWebview.element); - this._modifiedWebview.createWebview(DOM.getActiveWindow()); + this._modifiedWebview.createWebview(this.window); this._modifiedWebview.element.style.width = `calc(50% - 16px)`; this._modifiedWebview.element.style.left = `calc(50%)`; } @@ -516,7 +515,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD }, undefined) as BackLayerWebView; // attach the webview container to the DOM tree first this._list.rowsContainer.insertAdjacentElement('afterbegin', this._originalWebview.element); - this._originalWebview.createWebview(DOM.getActiveWindow()); + this._originalWebview.createWebview(this.window); this._originalWebview.element.style.width = `calc(50% - 16px)`; this._originalWebview.element.style.left = `16px`; } @@ -776,7 +775,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD const webview = diffSide === DiffSide.Modified ? this._modifiedWebview : this._originalWebview; - DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._listViewContainer), () => { + DOM.scheduleAtNextAnimationFrame(this.window, () => { webview?.ackHeight([{ cellId: cellInfo.cellId, outputId, height }]); }, 10); } @@ -794,7 +793,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } let r: () => void; - const layoutDisposable = DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._listViewContainer), () => { + const layoutDisposable = DOM.scheduleAtNextAnimationFrame(this.window, () => { this.pendingLayouts.delete(cell); relayout(cell, height); @@ -978,10 +977,6 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD return this; } - protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - super.setEditorVisible(visible, group); - } - override clearInput(): void { super.clearInput(); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 0a4cc62f4ed..dacf253ffed 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -76,6 +76,7 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { readonly onDidChangeSelection = this._onDidChangeSelection.event; constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -94,7 +95,7 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { @INotebookEditorWorkerService private readonly _notebookEditorWorkerService: INotebookEditorWorkerService, @IPreferencesService private readonly _preferencesService: IPreferencesService ) { - super(NotebookEditor.ID, telemetryService, themeService, storageService); + super(NotebookEditor.ID, group, telemetryService, themeService, storageService); this._editorMemento = this.getEditorMemento(_editorGroupService, configurationService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY); this._register(this._fileService.onDidChangeFileSystemProviderCapabilities(e => this._onDidChangeFileSystemProvider(e.scheme))); @@ -150,24 +151,22 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { return this._widget.value; } - override setVisible(visible: boolean, group?: IEditorGroup | undefined): void { - super.setVisible(visible, group); + override setVisible(visible: boolean): void { + super.setVisible(visible); if (!visible) { this._widget.value?.onWillHide(); } } - protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - super.setEditorVisible(visible, group); - if (group) { - this._groupListener.clear(); - this._groupListener.add(group.onWillCloseEditor(e => this._saveEditorViewState(e.editor))); - this._groupListener.add(group.onDidModelChange(() => { - if (this._editorGroupService.activeGroup !== group) { - this._widget?.value?.updateEditorFocus(); - } - })); - } + protected override setEditorVisible(visible: boolean): void { + super.setEditorVisible(visible); + this._groupListener.clear(); + this._groupListener.add(this.group.onWillCloseEditor(e => this._saveEditorViewState(e.editor))); + this._groupListener.add(this.group.onDidModelChange(() => { + if (this._editorGroupService.activeGroup !== this.group) { + this._widget?.value?.updateEditorFocus(); + } + })); if (!visible) { this._saveEditorViewState(this.input); @@ -203,7 +202,6 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { const perf = new NotebookPerfMarks(); perf.mark('startTime'); - const group = this.group!; this._inputListener.value = input.onDidChangeCapabilities(() => this._onDidChangeInputCapabilities(input)); @@ -213,7 +211,7 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { // we need to hide it before getting a new widget this._widget.value?.onWillHide(); - this._widget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, group, input, undefined, this._pagePosition?.dimension, DOM.getWindowById(group.windowId, true).window); + this._widget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, this.group, input, undefined, this._pagePosition?.dimension, this.window); if (this._rootElement && this._widget.value!.getDomNode()) { this._rootElement.setAttribute('aria-flowto', this._widget.value!.getDomNode().id || ''); @@ -319,7 +317,7 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { this._widgetDisposableStore.add(this._widget.value.onDidBlurWidget(() => this._onDidBlurWidget.fire())); this._widgetDisposableStore.add(this._editorGroupService.createEditorDropTarget(this._widget.value.getDomNode(), { - containsGroup: (group) => this.group?.id === group.id + containsGroup: (group) => this.group.id === group.id })); perf.mark('editorLoaded'); @@ -338,7 +336,7 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { } // Handle case where a file is too large to open without confirmation - if ((e).fileOperationResult === FileOperationResult.FILE_TOO_LARGE && this.group) { + if ((e).fileOperationResult === FileOperationResult.FILE_TOO_LARGE) { let message: string; if (e instanceof TooLargeFileOperationError) { message = localize('notebookTooLargeForHeapErrorWithSize', "The notebook is not displayed in the notebook editor because it is very large ({0}).", ByteSize.formatSize(e.size)); @@ -512,7 +510,7 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { private _saveEditorViewState(input: EditorInput | undefined): void { - if (this.group && this._widget.value && input instanceof NotebookEditorInput) { + if (this._widget.value && input instanceof NotebookEditorInput) { if (this._widget.value.isDisposed) { return; } @@ -523,10 +521,7 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { } private _loadNotebookEditorViewState(input: NotebookEditorInput): INotebookEditorViewState | undefined { - let result: INotebookEditorViewState | undefined; - if (this.group) { - result = this._editorMemento.loadEditorState(this.group, input.resource); - } + const result = this._editorMemento.loadEditorState(this.group, input.resource); if (result) { return result; } @@ -545,11 +540,11 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { this._rootElement.classList.toggle('narrow-width', dimension.width < 600); this._pagePosition = { dimension, position }; - if (!this._widget.value || !(this._input instanceof NotebookEditorInput)) { + if (!this._widget.value || !(this.input instanceof NotebookEditorInput)) { return; } - if (this._input.resource.toString() !== this.textModel?.uri.toString() && this._widget.value?.hasModel()) { + if (this.input.resource.toString() !== this.textModel?.uri.toString() && this._widget.value?.hasModel()) { // input and widget mismatch // this happens when // 1. open document A, pin the document diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index b7484919efa..53206dacd2e 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -33,6 +33,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { ResourceContextKey } from 'vs/workbench/common/contextkeys'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; +import { computeEditorAriaLabel } from 'vs/workbench/browser/editor'; export class OutputViewPane extends ViewPane { @@ -159,10 +160,9 @@ class OutputEditor extends AbstractTextResourceEditor { @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, - @IFileService fileService: IFileService, - @IContextKeyService contextKeyService: IContextKeyService, + @IFileService fileService: IFileService ) { - super(OUTPUT_VIEW_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, editorService, fileService); + super(OUTPUT_VIEW_ID, editorGroupService.activeGroup /* TODO@bpasero this is wrong */, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, editorService, fileService); this.resourceContext = this._register(instantiationService.createInstance(ResourceContextKey)); } @@ -213,6 +213,10 @@ class OutputEditor extends AbstractTextResourceEditor { return this.input ? this.input.getAriaLabel() : nls.localize('outputViewAriaLabel', "Output panel"); } + protected override computeAriaLabel(): string { + return this.input ? computeEditorAriaLabel(this.input, undefined, undefined, this.editorGroupService.count) : this.getAriaLabel(); + } + override async setInput(input: TextResourceEditorInput, options: ITextEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { const focus = !(options && options.preserveFocus); if (this.input && input.matches(this.input)) { diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index f9b434adf8f..480d9b5ab89 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -60,6 +60,7 @@ import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetN import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; const $ = DOM.$; @@ -108,6 +109,7 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP readonly overflowWidgetsDomNode: HTMLElement; constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IKeybindingService private readonly keybindingsService: IKeybindingService, @@ -121,7 +123,7 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP @IStorageService storageService: IStorageService, @IConfigurationService private readonly configurationService: IConfigurationService ) { - super(KeybindingsEditor.ID, telemetryService, themeService, storageService); + super(KeybindingsEditor.ID, group, telemetryService, themeService, storageService); this.delayedFiltering = new Delayer(300); this._register(keybindingsService.onDidUpdateKeybindings(() => this.render(!!this.keybindingFocusContextKey.get()))); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 33c9e99303b..3a5a03a9718 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -66,6 +66,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { CodeWindow } from 'vs/base/browser/window'; export const enum SettingsFocusContext { @@ -219,6 +220,7 @@ export class SettingsEditor2 extends EditorPane { private installedExtensionIds: string[] = []; constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @@ -240,7 +242,7 @@ export class SettingsEditor2 extends EditorPane { @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IEditorProgressService private readonly editorProgressService: IEditorProgressService, ) { - super(SettingsEditor2.ID, telemetryService, themeService, storageService); + super(SettingsEditor2.ID, group, telemetryService, themeService, storageService); this.delayedFilterLogging = new Delayer(1000); this.localSearchDelayer = new Delayer(300); this.remoteSearchThrottle = new ThrottledDelayer(200); @@ -398,7 +400,7 @@ export class SettingsEditor2 extends EditorPane { } private restoreCachedState(): ISettingsEditor2State | null { - const cachedState = this.group && this.input && this.editorMemento.loadEditorState(this.group, this.input); + const cachedState = this.input && this.editorMemento.loadEditorState(this.group, this.input); if (cachedState && typeof cachedState.target === 'object') { cachedState.target = URI.revive(cachedState.target); } @@ -499,8 +501,8 @@ export class SettingsEditor2 extends EditorPane { } } - protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - super.setEditorVisible(visible, group); + protected override setEditorVisible(visible: boolean): void { + super.setEditorVisible(visible); if (!visible) { // Wait for editor to be removed from DOM #106303 @@ -645,7 +647,7 @@ export class SettingsEditor2 extends EditorPane { })); if (this.userDataSyncWorkbenchService.enabled && this.userDataSyncEnablementService.canToggleEnablement()) { - const syncControls = this._register(this.instantiationService.createInstance(SyncControls, headerControlsContainer)); + const syncControls = this._register(this.instantiationService.createInstance(SyncControls, this.window, headerControlsContainer)); this._register(syncControls.onDidChangeLastSyncedLabel(lastSyncedLabel => { this.lastSyncedLabel = lastSyncedLabel; this.updateInputAriaLabel(); @@ -1426,7 +1428,7 @@ export class SettingsEditor2 extends EditorPane { // If the context view is focused, delay rendering settings if (this.contextViewFocused()) { - const element = DOM.getWindow(this.settingsTree.getHTMLElement()).document.querySelector('.context-view'); + const element = this.window.document.querySelector('.context-view'); if (element) { this.scheduleRefresh(element as HTMLElement, key); } @@ -1830,10 +1832,10 @@ export class SettingsEditor2 extends EditorPane { if (this.isVisible()) { const searchQuery = this.searchWidget.getValue().trim(); const target = this.settingsTargetsWidget.settingsTarget as SettingsTarget; - if (this.group && this.input) { + if (this.input) { this.editorMemento.saveEditorState(this.group, this.input, { searchQuery, target }); } - } else if (this.group && this.input) { + } else if (this.input) { this.editorMemento.clearEditorState(this.input, this.group); } @@ -1849,6 +1851,7 @@ class SyncControls extends Disposable { public readonly onDidChangeLastSyncedLabel = this._onDidChangeLastSyncedLabel.event; constructor( + window: CodeWindow, container: HTMLElement, @ICommandService private readonly commandService: ICommandService, @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, @@ -1881,7 +1884,7 @@ class SyncControls extends Disposable { })); const updateLastSyncedTimer = this._register(new DOM.WindowIntervalTimer()); - updateLastSyncedTimer.cancelAndSet(() => this.updateLastSyncedTime(), 60 * 1000, DOM.getWindow(container)); + updateLastSyncedTimer.cancelAndSet(() => this.updateLastSyncedTime(), 60 * 1000, window); this.update(); this._register(this.userDataSyncService.onDidChangeStatus(() => { diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index ffc69643037..84bb2199629 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -47,7 +47,7 @@ import { SearchModel, SearchResult } from 'vs/workbench/contrib/search/browser/s import { InSearchEditor, SearchEditorID, SearchEditorInputTypeId } from 'vs/workbench/contrib/searchEditor/browser/constants'; import type { SearchConfiguration, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { serializeSearchResultForEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPatternInfo, ISearchComplete, ISearchConfigurationProperties, ITextQuery, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { searchDetailsIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; @@ -97,6 +97,7 @@ export class SearchEditor extends AbstractTextCodeEditor private updatingModelForSearch: boolean = false; constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService, @@ -116,7 +117,7 @@ export class SearchEditor extends AbstractTextCodeEditor @IFileService fileService: IFileService, @ILogService private readonly logService: ILogService ) { - super(SearchEditor.ID, telemetryService, instantiationService, storageService, textResourceService, themeService, editorService, editorGroupService, fileService); + super(SearchEditor.ID, group, telemetryService, instantiationService, storageService, textResourceService, themeService, editorService, editorGroupService, fileService); this.container = DOM.$('.search-editor'); this.searchOperation = this._register(new LongRunningOperation(progressService)); @@ -668,7 +669,7 @@ export class SearchEditor extends AbstractTextCodeEditor } private getInput(): SearchEditorInput | undefined { - return this._input as SearchEditorInput; + return this.input as SearchEditorInput; } private priorConfig: Partial> | undefined; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts index 6835b5e9c35..0ea8218fcce 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts @@ -47,6 +47,7 @@ export class TerminalEditor extends EditorPane { private _cancelContextMenu: boolean = false; constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService, @@ -61,7 +62,7 @@ export class TerminalEditor extends EditorPane { @ITerminalProfileService private readonly _terminalProfileService: ITerminalProfileService, @IWorkbenchLayoutService private readonly _workbenchLayoutService: IWorkbenchLayoutService ) { - super(terminalEditorId, telemetryService, themeService, storageService); + super(terminalEditorId, group, telemetryService, themeService, storageService); this._dropdownMenu = this._register(menuService.createMenu(MenuId.TerminalNewDropdownContext, contextKeyService)); this._instanceMenu = this._register(menuService.createMenu(MenuId.TerminalInstanceContext, contextKeyService)); } @@ -74,7 +75,7 @@ export class TerminalEditor extends EditorPane { if (this._lastDimension) { this.layout(this._lastDimension); } - this._editorInput.terminalInstance?.setVisible(this.isVisible() && this._workbenchLayoutService.isVisible(Parts.EDITOR_PART, dom.getWindow(this._editorInstanceElement))); + this._editorInput.terminalInstance?.setVisible(this.isVisible() && this._workbenchLayoutService.isVisible(Parts.EDITOR_PART, this.window)); if (this._editorInput.terminalInstance) { // since the editor does not monitor focus changes, for ex. between the terminal // panel and the editors, this is needed so that the active instance gets set @@ -143,7 +144,7 @@ export class TerminalEditor extends EditorPane { // copyPaste: Shift+right click should open context menu if (rightClickBehavior === 'copyPaste' && event.shiftKey) { - openContextMenu(dom.getWindow(this._editorInstanceElement), event, this._editorInput?.terminalInstance, this._instanceMenu, this._contextMenuService); + openContextMenu(this.window, event, this._editorInput?.terminalInstance, this._instanceMenu, this._contextMenuService); return; } @@ -181,7 +182,7 @@ export class TerminalEditor extends EditorPane { else if (!this._cancelContextMenu && rightClickBehavior !== 'copyPaste' && rightClickBehavior !== 'paste') { if (!this._cancelContextMenu) { - openContextMenu(dom.getWindow(this._editorInstanceElement), event, this._editorInput?.terminalInstance, this._instanceMenu, this._contextMenuService); + openContextMenu(this.window, event, this._editorInput?.terminalInstance, this._instanceMenu, this._contextMenuService); } event.preventDefault(); event.stopImmediatePropagation(); @@ -199,9 +200,9 @@ export class TerminalEditor extends EditorPane { this._lastDimension = dimension; } - override setVisible(visible: boolean, group?: IEditorGroup): void { - super.setVisible(visible, group); - this._editorInput?.terminalInstance?.setVisible(visible && this._workbenchLayoutService.isVisible(Parts.EDITOR_PART, dom.getWindow(this._editorInstanceElement))); + override setVisible(visible: boolean): void { + super.setVisible(visible); + this._editorInput?.terminalInstance?.setVisible(visible && this._workbenchLayoutService.isVisible(Parts.EDITOR_PART, this.window)); } override getActionViewItem(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts index f9b3f0c34f3..33ad2d64e7d 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts @@ -53,6 +53,7 @@ export class WebviewEditor extends EditorPane { private readonly _scopedContextKeyService = this._register(new MutableDisposable()); constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService, @@ -62,7 +63,7 @@ export class WebviewEditor extends EditorPane { @IHostService private readonly _hostService: IHostService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, ) { - super(WebviewEditor.ID, telemetryService, themeService, storageService); + super(WebviewEditor.ID, group, telemetryService, themeService, storageService); this._register(Event.any( _editorGroupsService.activePart.onDidScroll, @@ -122,7 +123,7 @@ export class WebviewEditor extends EditorPane { this.webview?.focus(); } - protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { + protected override setEditorVisible(visible: boolean): void { this._visible = visible; if (this.input instanceof WebviewInput && this.webview) { if (visible) { @@ -131,7 +132,7 @@ export class WebviewEditor extends EditorPane { this.webview.release(this); } } - super.setEditorVisible(visible, group); + super.setEditorVisible(visible); } public override clearInput() { @@ -161,9 +162,7 @@ export class WebviewEditor extends EditorPane { } if (input instanceof WebviewInput) { - if (this.group) { - input.updateGroup(this.group.id); - } + input.updateGroup(this.group.id); if (!alreadyOwnsWebview) { this.claimWebview(input); @@ -186,7 +185,7 @@ export class WebviewEditor extends EditorPane { // Webviews are not part of the normal editor dom, so we have to register our own drag and drop handler on them. this._webviewVisibleDisposables.add(this._editorGroupsService.createEditorDropTarget(input.webview.container, { - containsGroup: (group) => this.group?.id === group.id + containsGroup: (group) => this.group.id === group.id })); this._webviewVisibleDisposables.add(new WebviewWindowDragMonitor(() => this.webview)); diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 2f4054f1e28..78a07e62ce2 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, Dimension, addDisposableListener, append, clearNode, getWindow, reset } from 'vs/base/browser/dom'; +import { $, Dimension, addDisposableListener, append, clearNode, reset } from 'vs/base/browser/dom'; import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Button } from 'vs/base/browser/ui/button/button'; @@ -65,7 +65,7 @@ import { GettingStartedInput } from 'vs/workbench/contrib/welcomeGettingStarted/ import { IResolvedWalkthrough, IResolvedWalkthroughStep, IWalkthroughsService, hiddenEntriesConfigurationKey } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService'; import { RestoreWalkthroughsConfigurationValue, restoreWalkthroughsConfigurationKey } from 'vs/workbench/contrib/welcomeGettingStarted/browser/startupPage'; import { startEntries } from 'vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent'; -import { GroupDirection, GroupsOrder, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { GroupDirection, GroupsOrder, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ThemeSettings } from 'vs/workbench/services/themes/common/workbenchThemeService'; @@ -162,6 +162,7 @@ export class GettingStartedPage extends EditorPane { private categoriesSlideDisposables: DisposableStore; constructor( + group: IEditorGroup, @ICommandService private readonly commandService: ICommandService, @IProductService private readonly productService: IProductService, @IKeybindingService private readonly keybindingService: IKeybindingService, @@ -186,7 +187,7 @@ export class GettingStartedPage extends EditorPane { @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService) { - super(GettingStartedPage.ID, telemetryService, themeService, storageService); + super(GettingStartedPage.ID, group, telemetryService, themeService, storageService); this.container = $('.gettingStartedContainer', { @@ -266,7 +267,7 @@ export class GettingStartedPage extends EditorPane { ourStep.done = step.done; if (category.id === this.currentWalkthrough?.id) { - const badgeelements = assertIsDefined(getWindow(this.container).document.querySelectorAll(`[data-done-step-id="${step.id}"]`)); + const badgeelements = assertIsDefined(this.window.document.querySelectorAll(`[data-done-step-id="${step.id}"]`)); badgeelements.forEach(badgeelement => { if (step.done) { badgeelement.setAttribute('aria-checked', 'true'); @@ -1117,7 +1118,7 @@ export class GettingStartedPage extends EditorPane { } private updateCategoryProgress() { - getWindow(this.container).document.querySelectorAll('.category-progress').forEach(element => { + this.window.document.querySelectorAll('.category-progress').forEach(element => { const categoryID = element.getAttribute('x-data-category-id'); const category = this.gettingStartedCategories.find(category => category.id === categoryID); if (!category) { throw Error('Could not find category with ID ' + categoryID); } @@ -1170,7 +1171,7 @@ export class GettingStartedPage extends EditorPane { } private focusSideEditorGroup() { - const fullSize = this.group ? this.groupsService.getPart(this.group).contentDimension : undefined; + const fullSize = this.groupsService.getPart(this.group).contentDimension; if (!fullSize || fullSize.width <= 700) { return; } if (this.groupsService.count === 1) { const sideGroup = this.groupsService.addGroup(this.groupsService.groups[0], GroupDirection.RIGHT); diff --git a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts index 50efd6cfc84..dd11b080f6b 100644 --- a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts @@ -32,8 +32,8 @@ import { UILabelProvider } from 'vs/base/common/keybindingLabels'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { deepClone } from 'vs/base/common/objects'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { addDisposableListener, Dimension, getWindow, safeInnerHtml, size } from 'vs/base/browser/dom'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { addDisposableListener, Dimension, safeInnerHtml, size } from 'vs/base/browser/dom'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; @@ -66,6 +66,7 @@ export class WalkThroughPart extends EditorPane { private editorMemento: IEditorMemento; constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @@ -79,7 +80,7 @@ export class WalkThroughPart extends EditorPane { @IExtensionService private readonly extensionService: IExtensionService, @IEditorGroupsService editorGroupService: IEditorGroupsService, ) { - super(WalkThroughPart.ID, telemetryService, themeService, storageService); + super(WalkThroughPart.ID, group, telemetryService, themeService, storageService); this.editorFocus = WALK_THROUGH_FOCUS.bindTo(this.contextKeyService); this.editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, WALK_THROUGH_EDITOR_VIEW_STATE_PREFERENCE_KEY); } @@ -156,7 +157,7 @@ export class WalkThroughPart extends EditorPane { this.content.addEventListener('click', event => { for (let node = event.target as HTMLElement; node; node = node.parentNode as HTMLElement) { if (node instanceof HTMLAnchorElement && node.href) { - const baseElement = node.ownerDocument.getElementsByTagName('base')[0] || getWindow(node).location; + const baseElement = node.ownerDocument.getElementsByTagName('base')[0] || this.window.location; if (baseElement && node.href.indexOf(baseElement.href) >= 0 && node.hash) { const scrollTarget = this.content.querySelector(node.hash); const innerContent = this.content.firstElementChild; @@ -441,22 +442,18 @@ export class WalkThroughPart extends EditorPane { private saveTextEditorViewState(input: WalkThroughInput): void { const scrollPosition = this.scrollbar.getScrollPosition(); - if (this.group) { - this.editorMemento.saveEditorState(this.group, input, { - viewState: { - scrollTop: scrollPosition.scrollTop, - scrollLeft: scrollPosition.scrollLeft - } - }); - } + this.editorMemento.saveEditorState(this.group, input, { + viewState: { + scrollTop: scrollPosition.scrollTop, + scrollLeft: scrollPosition.scrollLeft + } + }); } private loadTextEditorViewState(input: WalkThroughInput) { - if (this.group) { - const state = this.editorMemento.loadEditorState(this.group, input); - if (state) { - this.scrollbar.setScrollPosition(state.viewState); - } + const state = this.editorMemento.loadEditorState(this.group, input); + if (state) { + this.scrollbar.setScrollPosition(state.viewState); } } diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts index fe7f625a784..51339f65077 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts @@ -59,6 +59,7 @@ import { isMacintosh } from 'vs/base/common/platform'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { basename, dirname } from 'vs/base/common/resources'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; export const shieldIcon = registerIcon('workspace-trust-banner', Codicon.shield, localize('shieldIcon', 'Icon for workspace trust ion the banner.')); @@ -685,6 +686,7 @@ export class WorkspaceTrustEditor extends EditorPane { private workspaceTrustedUrisTable!: WorkspaceTrustedUrisTable; constructor( + group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService, @@ -697,7 +699,7 @@ export class WorkspaceTrustEditor extends EditorPane { @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @IProductService private readonly productService: IProductService, @IKeybindingService private readonly keybindingService: IKeybindingService, - ) { super(WorkspaceTrustEditor.ID, telemetryService, themeService, storageService); } + ) { super(WorkspaceTrustEditor.ID, group, telemetryService, themeService, storageService); } protected createEditor(parent: HTMLElement): void { this.rootElement = append(parent, $('.workspace-trust-editor', { tabindex: '0' })); diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index a940bd1a309..7671e480d79 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -143,7 +143,7 @@ suite('EditorService', () => { assert.strictEqual(willInstantiateEditorPaneListenerCounter, 1); // Close input - await editor?.group?.closeEditor(input); + await editor?.group.closeEditor(input); assert.strictEqual(0, editorService.count); assert.strictEqual(0, editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length); @@ -1399,15 +1399,15 @@ suite('EditorService', () => { const rootPane = await openEditor(untypedEditor1); const sidePane = await openEditor(untypedEditor2, SIDE_GROUP); - assert.strictEqual(rootPane?.group?.count, 1); - assert.strictEqual(sidePane?.group?.count, 1); + assert.strictEqual(rootPane?.group.count, 1); + assert.strictEqual(sidePane?.group.count, 1); accessor.editorGroupService.activateGroup(sidePane.group); await openEditor(untypedEditor1); - assert.strictEqual(rootPane?.group?.count, 1); - assert.strictEqual(sidePane?.group?.count, 1); + assert.strictEqual(rootPane?.group.count, 1); + assert.strictEqual(sidePane?.group.count, 1); await resetTestState(); } @@ -1419,18 +1419,18 @@ suite('EditorService', () => { const rootPane = await openEditor(untypedEditor1); await openEditor(untypedEditor2); - assert.strictEqual(rootPane?.group?.activeEditor?.resource?.toString(), untypedEditor2.resource.toString()); + assert.strictEqual(rootPane?.group.activeEditor?.resource?.toString(), untypedEditor2.resource.toString()); const sidePane = await openEditor(untypedEditor2, SIDE_GROUP); - assert.strictEqual(rootPane?.group?.count, 2); - assert.strictEqual(sidePane?.group?.count, 1); + assert.strictEqual(rootPane?.group.count, 2); + assert.strictEqual(sidePane?.group.count, 1); accessor.editorGroupService.activateGroup(sidePane.group); await openEditor(untypedEditor1); - assert.strictEqual(rootPane?.group?.count, 2); - assert.strictEqual(sidePane?.group?.count, 1); + assert.strictEqual(rootPane?.group.count, 2); + assert.strictEqual(sidePane?.group.count, 1); await resetTestState(); } @@ -1458,7 +1458,7 @@ suite('EditorService', () => { assert.strictEqual(pane?.options?.sticky, true); assert.strictEqual(pane?.options?.preserveFocus, true); - await pane.group?.closeAllEditors(); + await pane.group.closeAllEditors(); // Untyped editor (without registered editor) pane = await service.openEditor({ resource: URI.file('resource-openEditors') }); @@ -1499,7 +1499,7 @@ suite('EditorService', () => { assert.strictEqual(service.isOpened({ resource: input.resource, typeId: input.typeId, editorId: input.editorId }), true); assert.strictEqual(service.isOpened({ resource: otherInput.resource, typeId: otherInput.typeId, editorId: otherInput.editorId }), true); - await editor2?.group?.closeEditor(input); + await editor2?.group.closeEditor(input); assert.strictEqual(part.activeGroup.count, 1); assert.strictEqual(service.isOpened(input), false); @@ -1507,7 +1507,7 @@ suite('EditorService', () => { assert.strictEqual(service.isOpened({ resource: input.resource, typeId: input.typeId, editorId: input.editorId }), false); assert.strictEqual(service.isOpened({ resource: otherInput.resource, typeId: otherInput.typeId, editorId: otherInput.editorId }), true); - await editor1?.group?.closeEditor(sideBySideInput); + await editor1?.group.closeEditor(sideBySideInput); assert.strictEqual(service.isOpened(input), false); assert.strictEqual(service.isOpened(otherInput), false); @@ -2343,7 +2343,7 @@ suite('EditorService', () => { assert.strictEqual(accessor.fileService.watches.length, 1); assert.strictEqual(accessor.fileService.watches[0].toString(), input2.resource.toString()); - await editor?.group?.closeAllEditors(); + await editor?.group.closeAllEditors(); assert.strictEqual(accessor.fileService.watches.length, 0); }); @@ -2608,14 +2608,14 @@ suite('EditorService', () => { const found1 = service.findEditors(input.resource); assert.strictEqual(found1.length, 2); assert.strictEqual(found1[0].editor, input); - assert.strictEqual(found1[0].groupId, sideEditor?.group?.id); + assert.strictEqual(found1[0].groupId, sideEditor?.group.id); assert.strictEqual(found1[1].editor, input); assert.strictEqual(found1[1].groupId, rootGroup.id); const found2 = service.findEditors(input); assert.strictEqual(found2.length, 2); assert.strictEqual(found2[0].editor, input); - assert.strictEqual(found2[0].groupId, sideEditor?.group?.id); + assert.strictEqual(found2[0].groupId, sideEditor?.group.id); assert.strictEqual(found2[1].editor, input); assert.strictEqual(found2[1].groupId, rootGroup.id); } @@ -2642,7 +2642,7 @@ suite('EditorService', () => { // Check we don't find editors after closing them await rootGroup.closeAllEditors(); - await sideEditor?.group?.closeAllEditors(); + await sideEditor?.group.closeAllEditors(); { const found1 = service.findEditors(input.resource); assert.strictEqual(found1.length, 0); diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts index efaf3103c6c..6b67a036cb5 100644 --- a/src/vs/workbench/services/history/browser/historyService.ts +++ b/src/vs/workbench/services/history/browser/historyService.ts @@ -182,7 +182,7 @@ export class HistoryService extends Disposable implements IHistoryService { } // Remember as last active editor (can be undefined if none opened) - this.lastActiveEditor = activeEditorPane?.input && activeEditorPane.group ? { editor: activeEditorPane.input, groupId: activeEditorPane.group.id } : undefined; + this.lastActiveEditor = activeEditorPane?.input ? { editor: activeEditorPane.input, groupId: activeEditorPane.group.id } : undefined; // Dispose old listeners this.activeEditorListeners.clear(); @@ -1522,7 +1522,7 @@ ${entryLabels.join('\n')} this.trace('notifyNavigation()', editorPane?.input, event); const isSelectionAwareEditorPane = isEditorPaneWithSelection(editorPane); - const hasValidEditor = editorPane?.group && editorPane.input && !editorPane.input.isDisposed(); + const hasValidEditor = editorPane?.input && !editorPane.input.isDisposed(); // Treat editor changes that happen as part of stack navigation specially // we do not want to add a new stack entry as a matter of navigating the @@ -1893,7 +1893,7 @@ ${entryLabels.join('\n')} return false; // we need an active editor pane with selection support } - if (pane.group?.id !== this.current.groupId) { + if (pane.group.id !== this.current.groupId) { return false; // we need matching groups } diff --git a/src/vs/workbench/services/history/test/browser/historyService.test.ts b/src/vs/workbench/services/history/test/browser/historyService.test.ts index 28f269d3bee..82dfcd4434d 100644 --- a/src/vs/workbench/services/history/test/browser/historyService.test.ts +++ b/src/vs/workbench/services/history/test/browser/historyService.test.ts @@ -109,28 +109,28 @@ suite('HistoryService', function () { // [index.txt] | [>index.txt<] [other.html] - assert.strictEqual(part.activeGroup.id, pane2?.group?.id); + assert.strictEqual(part.activeGroup.id, pane2?.group.id); assert.strictEqual(part.activeGroup.activeEditor?.resource?.toString(), resource.toString()); await historyService.goBack(); // [>index.txt<] | [index.txt] [other.html] - assert.strictEqual(part.activeGroup.id, pane1?.group?.id); + assert.strictEqual(part.activeGroup.id, pane1?.group.id); assert.strictEqual(part.activeGroup.activeEditor?.resource?.toString(), resource.toString()); await historyService.goForward(); // [index.txt] | [>index.txt<] [other.html] - assert.strictEqual(part.activeGroup.id, pane2?.group?.id); + assert.strictEqual(part.activeGroup.id, pane2?.group.id); assert.strictEqual(part.activeGroup.activeEditor?.resource?.toString(), resource.toString()); await historyService.goForward(); // [index.txt] | [index.txt] [>other.html<] - assert.strictEqual(part.activeGroup.id, pane2?.group?.id); + assert.strictEqual(part.activeGroup.id, pane2?.group.id); assert.strictEqual(part.activeGroup.activeEditor?.resource?.toString(), otherResource.toString()); return workbenchTeardown(instantiationService); @@ -313,7 +313,7 @@ suite('HistoryService', function () { // [one.txt] [>two.html<] | const editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); - pane1?.group?.moveEditor(pane1.input!, sideGroup); + pane1?.group.moveEditor(pane1.input!, sideGroup); await editorChangePromise; // [one.txt] | [>two.html<] @@ -322,7 +322,7 @@ suite('HistoryService', function () { // [>one.txt<] | [two.html] - assert.strictEqual(part.activeGroup.id, pane1?.group?.id); + assert.strictEqual(part.activeGroup.id, pane1?.group.id); assert.strictEqual(part.activeGroup.activeEditor?.resource?.toString(), resource1.toString()); return workbenchTeardown(instantiationService); @@ -341,7 +341,7 @@ suite('HistoryService', function () { assert.notStrictEqual(pane1, pane2); - await pane1?.group?.closeAllEditors(); + await pane1?.group.closeAllEditors(); // [>two.html<] @@ -349,7 +349,7 @@ suite('HistoryService', function () { // [>two.html<] - assert.strictEqual(part.activeGroup.id, pane2?.group?.id); + assert.strictEqual(part.activeGroup.id, pane2?.group.id); assert.strictEqual(part.activeGroup.activeEditor?.resource?.toString(), resource2.toString()); return workbenchTeardown(instantiationService); @@ -545,7 +545,7 @@ suite('HistoryService', function () { await historyService.goBack(); await historyService.goBack(); - assert.strictEqual(part.activeGroup.id, pane2?.group?.id); + assert.strictEqual(part.activeGroup.id, pane2?.group.id); assert.strictEqual(part.activeGroup.activeEditor?.resource?.toString(), resource1.toString()); // [one.txt] [two.html] [>three.html<] | [>one.txt<] [two.html] [three.html] @@ -556,7 +556,7 @@ suite('HistoryService', function () { await historyService.goBack(); await historyService.goBack(); - assert.strictEqual(part.activeGroup.id, pane1?.group?.id); + assert.strictEqual(part.activeGroup.id, pane1?.group.id); assert.strictEqual(part.activeGroup.activeEditor?.resource?.toString(), resource1.toString()); return workbenchTeardown(instantiationService); @@ -631,7 +631,7 @@ suite('HistoryService', function () { const resource = toResource.call(this, '/path/index.txt'); const pane = await editorService.openEditor({ resource }); - await pane?.group?.closeAllEditors(); + await pane?.group.closeAllEditors(); const onDidActiveEditorChange = new DeferredPromise(); disposables.add(editorService.onDidActiveEditorChange(e => { diff --git a/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts b/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts index 142d49dadc1..3d817b86fea 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts @@ -23,7 +23,7 @@ import { TestStorageService, TestWorkspaceTrustManagementService } from 'vs/work import { extUri } from 'vs/base/common/resources'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -36,9 +36,9 @@ const editorInputRegistry: IEditorFactoryRegistry = Registry.as(EditorExtensions class TestEditor extends EditorPane { - constructor() { + constructor(group: IEditorGroup,) { const disposables = new DisposableStore(); - super('TestEditor', NullTelemetryService, NullThemeService, disposables.add(new TestStorageService())); + super('TestEditor', group, NullTelemetryService, NullThemeService, disposables.add(new TestStorageService())); this._register(disposables); } @@ -50,9 +50,9 @@ class TestEditor extends EditorPane { class OtherTestEditor extends EditorPane { - constructor() { + constructor(group: IEditorGroup,) { const disposables = new DisposableStore(); - super('testOtherEditor', NullTelemetryService, NullThemeService, disposables.add(new TestStorageService())); + super('testOtherEditor', group, NullTelemetryService, NullThemeService, disposables.add(new TestStorageService())); this._register(disposables); } @@ -118,7 +118,9 @@ suite('EditorPane', () => { }); test('EditorPane API', async () => { - const editor = new TestEditor(); + const group = new TestEditorGroupView(1); + const editor = new TestEditor(group); + assert.ok(editor.group); const input = disposables.add(new OtherTestInput()); const options = {}; @@ -127,13 +129,11 @@ suite('EditorPane', () => { await editor.setInput(input, options, Object.create(null), CancellationToken.None); assert.strictEqual(input, editor.input); - const group = new TestEditorGroupView(1); - editor.setVisible(true, group); + editor.setVisible(true); assert(editor.isVisible()); - assert.strictEqual(editor.group, group); editor.dispose(); editor.clearInput(); - editor.setVisible(false, group); + editor.setVisible(false); assert(!editor.isVisible()); assert(!editor.input); assert(!editor.getControl()); @@ -174,18 +174,22 @@ suite('EditorPane', () => { const inst = workbenchInstantiationService(undefined, disposables); - const editor = disposables.add(editorRegistry.getEditorPane(disposables.add(inst.createInstance(TestResourceEditorInput, URI.file('/fake'), 'fake', '', undefined, undefined)))!.instantiate(inst)); + const group = new TestEditorGroupView(1); + + const editor = disposables.add(editorRegistry.getEditorPane(disposables.add(inst.createInstance(TestResourceEditorInput, URI.file('/fake'), 'fake', '', undefined, undefined)))!.instantiate(inst, group)); assert.strictEqual(editor.getId(), 'testEditor'); - const otherEditor = disposables.add(editorRegistry.getEditorPane(disposables.add(inst.createInstance(TextResourceEditorInput, URI.file('/fake'), 'fake', '', undefined, undefined)))!.instantiate(inst)); + const otherEditor = disposables.add(editorRegistry.getEditorPane(disposables.add(inst.createInstance(TextResourceEditorInput, URI.file('/fake'), 'fake', '', undefined, undefined)))!.instantiate(inst, group)); assert.strictEqual(otherEditor.getId(), 'workbench.editors.textResourceEditor'); }); test('Editor Pane Lookup favors specific class over superclass (match on super class)', function () { const inst = workbenchInstantiationService(undefined, disposables); + const group = new TestEditorGroupView(1); + disposables.add(registerTestResourceEditor()); - const editor = disposables.add(editorRegistry.getEditorPane(disposables.add(inst.createInstance(TestResourceEditorInput, URI.file('/fake'), 'fake', '', undefined, undefined)))!.instantiate(inst)); + const editor = disposables.add(editorRegistry.getEditorPane(disposables.add(inst.createInstance(TestResourceEditorInput, URI.file('/fake'), 'fake', '', undefined, undefined)))!.instantiate(inst, group)); assert.strictEqual('workbench.editors.textResourceEditor', editor.getId()); }); @@ -453,8 +457,8 @@ suite('EditorPane', () => { test('WorkspaceTrustRequiredEditor', async function () { class TrustRequiredTestEditor extends EditorPane { - constructor(@ITelemetryService telemetryService: ITelemetryService) { - super('TestEditor', NullTelemetryService, NullThemeService, disposables.add(new TestStorageService())); + constructor(group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService) { + super('TestEditor', group, NullTelemetryService, NullThemeService, disposables.add(new TestStorageService())); } override getId(): string { return 'trustRequiredTestEditor'; } diff --git a/src/vs/workbench/test/browser/parts/editor/textEditorPane.test.ts b/src/vs/workbench/test/browser/parts/editor/textEditorPane.test.ts index c8bcb2d24a1..60ab56b329a 100644 --- a/src/vs/workbench/test/browser/parts/editor/textEditorPane.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/textEditorPane.test.ts @@ -74,7 +74,7 @@ suite('TextEditorPane', () => { pane.setSelection(new Selection(1, 1, 1, 1), EditorPaneSelectionChangeReason.USER); const selection = pane.getSelection(); assert.ok(selection); - await pane.group?.closeAllEditors(); + await pane.group.closeAllEditors(); const options = selection.restore({}); pane = (await accessor.editorService.openEditor({ resource, options }) as TestTextFileEditor); @@ -85,7 +85,7 @@ suite('TextEditorPane', () => { assert.strictEqual(newSelection.compare(selection), EditorPaneSelectionCompareResult.IDENTICAL); await model.revert(); - await pane.group?.closeAllEditors(); + await pane.group.closeAllEditors(); }); test('TextEditorPaneSelection', function () { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 4a05917faec..9cb63bb2b63 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1563,8 +1563,8 @@ export function registerTestEditor(id: string, inputs: SyncDescriptor Date: Fri, 1 Mar 2024 09:32:36 -0800 Subject: [PATCH 1716/1897] debug: clear prompt if a terminal is reused betwen debug sessions (#206632) Fixes #106743 --- src/vs/workbench/api/node/extHostDebugService.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index f38194be406..995d4f8a17b 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -91,7 +91,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { const terminalName = args.title || nls.localize('debug.terminal.title', "Debug Process"); const termKey = createKeyForShell(shell, shellArgs, args); - let terminal = await this._integratedTerminalInstances.checkout(termKey, terminalName); + let terminal = await this._integratedTerminalInstances.checkout(termKey, terminalName, true); let cwdForPrepareCommand: string | undefined; let giveShellTimeToInitialize = false; @@ -127,6 +127,10 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { // give a new terminal some time to initialize the shell await new Promise(resolve => setTimeout(resolve, 1000)); } else { + if (terminal.state.isInteractedWith) { + terminal.sendText('\u0003'); // Ctrl+C for #106743. Not part of the same command for #107969 + } + if (configProvider.getConfiguration('debug.terminal').get('clearBeforeReusing')) { // clear terminal before reusing it if (shell.indexOf('powershell') >= 0 || shell.indexOf('pwsh') >= 0 || shell.indexOf('cmd.exe') >= 0) { @@ -195,7 +199,7 @@ class DebugTerminalCollection { private _terminalInstances = new Map(); - public async checkout(config: string, name: string) { + public async checkout(config: string, name: string, cleanupOthersByName = false) { const entries = [...this._terminalInstances.entries()]; const promises = entries.map(([terminal, termInfo]) => createCancelablePromise(async ct => { @@ -215,6 +219,9 @@ class DebugTerminalCollection { } if (termInfo.config !== config) { + if (cleanupOthersByName) { + terminal.dispose(); + } return null; } From 0e959d149fdc275e1f118280008343b857d2426f Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 1 Mar 2024 10:56:03 -0800 Subject: [PATCH 1717/1897] debug: fix selected session not expanded if call stack not visible (#206635) Fixes #202683 --- src/vs/workbench/contrib/debug/browser/callStackView.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index b9d319bad85..db948476aef 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -348,6 +348,7 @@ export class CallStackView extends ViewPane { } if (!this.isBodyVisible()) { this.needsRefresh = true; + this.selectionNeedsUpdate = true; return; } if (this.onCallStackChangeScheduler.isScheduled()) { From d75907639453ab82e49a2c40a464fcfe628d4971 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 1 Mar 2024 10:59:39 -0800 Subject: [PATCH 1718/1897] pipe through the rest of the properties (#206636) --- src/vs/workbench/api/common/extHost.protocol.ts | 2 ++ src/vs/workbench/api/common/extHostNotebookKernels.ts | 2 ++ .../browser/contrib/notebookVariables/notebookVariablesView.ts | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b67fd890e78..d7e5580443b 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1126,6 +1126,8 @@ export interface VariablesResult { name: string; value: string; type?: string; + language?: string; + expression?: string; hasNamedChildren: boolean; indexedChildrenCount: number; extensionId: string; diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index f2a201e9e06..998e624a195 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -475,6 +475,8 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { name: result.variable.name, value: result.variable.value, type: result.variable.type, + language: result.variable.language, + expression: result.variable.expression, hasNamedChildren: result.hasNamedChildren, indexedChildrenCount: result.indexedChildrenCount, extensionId: obj.extensionId.value, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index 1c3c61d25ea..bab9b8086c5 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -34,7 +34,7 @@ import { ICellExecutionStateChangedEvent, IExecutionStateChangedEvent, INotebook import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -export type contextMenuArg = { source?: string; type?: string; value?: string; expression?: string; language?: string; extensionId?: string }; +export type contextMenuArg = { source: string; name: string; type?: string; value?: string; expression?: string; language?: string; extensionId?: string }; export class NotebookVariablesView extends ViewPane { @@ -109,6 +109,7 @@ export class NotebookVariablesView extends ViewPane { const arg: contextMenuArg = { source: element.notebook.uri.toString(), + name: element.name, value: element.value, type: element.type, expression: element.expression, From 313c78eeadbce621c3a5e890b4d1760bb21d7a46 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Fri, 1 Mar 2024 11:26:12 -0800 Subject: [PATCH 1719/1897] fix: added repo name in issue reporter (#206638) fix repo name in issue reporter --- .../issue/issueReporterService.ts | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index 851f5c38798..56623913d7d 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -78,6 +78,10 @@ export class IssueReporter extends Disposable { const issueReporterElement = this.getElementById('issue-reporter'); if (issueReporterElement) { this.previewButton = new Button(issueReporterElement, unthemedButtonStyles); + const issueRepoName = document.createElement('a'); + issueReporterElement.appendChild(issueRepoName); + issueRepoName.id = 'show-repo-name'; + issueRepoName.classList.add('hidden'); this.updatePreviewButtonState(); } @@ -502,13 +506,16 @@ export class IssueReporter extends Disposable { this.previewButton.label = localize('loadingData', "Loading data..."); } + const issueRepoName = this.getElementById('show-repo-name')! as HTMLAnchorElement; const selectedExtension = this.issueReporterModel.getData().selectedExtension; if (selectedExtension && selectedExtension.uri) { - const extensionLink = document.createElement('a'); - extensionLink.href = URI.revive(selectedExtension.uri).toString(); - extensionLink.textContent = selectedExtension.id; - const issueReporterElement = this.getElementById('issue-reporter')!; - Object.assign(extensionLink.style, { + const urlString = URI.revive(selectedExtension.uri).toString(); + issueRepoName.href = urlString; + issueRepoName.addEventListener('click', (e) => this.openLink(e)); + issueRepoName.addEventListener('auxclick', (e) => this.openLink(e)); + const gitHubInfo = this.parseGitHubUrl(urlString); + issueRepoName.textContent = gitHubInfo ? gitHubInfo.owner + '/' + gitHubInfo.repositoryName : urlString; + Object.assign(issueRepoName.style, { alignSelf: 'flex-end', display: 'block', fontSize: '13px', @@ -517,7 +524,11 @@ export class IssueReporter extends Disposable { textDecoration: 'none', width: 'auto' }); - issueReporterElement.appendChild(extensionLink); + show(issueRepoName); + } else { + // clear styles + issueRepoName.removeAttribute('style'); + hide(issueRepoName); } } From eb4ebc2247669a72ec9b5ffaf4847283b2283901 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 1 Mar 2024 13:39:48 -0800 Subject: [PATCH 1720/1897] prevent outer scroll if inner region recently scrolled (#206642) * prevent outer scroll if inner region recently scrolled * remove NodeJS reference --- .../browser/view/renderers/webviewPreloads.ts | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index c08eb70c296..59beaaf2ef9 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -455,15 +455,30 @@ async function webviewPreloads(ctx: PreloadContext) { } }; - function scrollWillGoToParent(event: WheelEvent) { + let scrollTimeout: any /* NodeJS.Timeout */ | undefined; + let scrolledElement: Element | undefined; + function flagRecentlyScrolled(node: Element) { + scrolledElement = node; + node.setAttribute('recentlyScrolled', 'true'); + clearTimeout(scrollTimeout); + scrollTimeout = setTimeout(() => { scrolledElement?.removeAttribute('recentlyScrolled'); }, 300); + } + + function eventTargetShouldHandleScroll(event: WheelEvent) { for (let node = event.target as Node | null; node; node = node.parentNode) { if (!(node instanceof Element) || node.id === 'container' || node.classList.contains('cell_container') || node.classList.contains('markup') || node.classList.contains('output_container')) { return false; } + if (node.hasAttribute('recentlyScrolled') && scrolledElement === node) { + flagRecentlyScrolled(node); + return true; + } + // scroll up if (event.deltaY < 0 && node.scrollTop > 0) { // there is still some content to scroll + flagRecentlyScrolled(node); return true; } @@ -481,6 +496,7 @@ async function webviewPreloads(ctx: PreloadContext) { continue; } + flagRecentlyScrolled(node); return true; } } @@ -489,7 +505,7 @@ async function webviewPreloads(ctx: PreloadContext) { } const handleWheel = (event: WheelEvent & { wheelDeltaX?: number; wheelDeltaY?: number; wheelDelta?: number }) => { - if (event.defaultPrevented || scrollWillGoToParent(event)) { + if (event.defaultPrevented || eventTargetShouldHandleScroll(event)) { return; } postNotebookMessage('did-scroll-wheel', { From df809f53dd50e003a0dfdab295ea6091a0235795 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 1 Mar 2024 14:40:46 -0800 Subject: [PATCH 1721/1897] cli: use a better permission for keychain fallback (#206650) Fixes https://github.com/microsoft/vscode-remote-release/issues/9619 --- cli/src/auth.rs | 5 ++++- cli/src/state.rs | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/cli/src/auth.rs b/cli/src/auth.rs index ee7117330be..2ee4f73c919 100644 --- a/cli/src/auth.rs +++ b/cli/src/auth.rs @@ -404,7 +404,10 @@ impl Auth { let mut keyring_storage = KeyringStorage::default(); #[cfg(target_os = "linux")] let mut keyring_storage = ThreadKeyringStorage::default(); - let mut file_storage = FileStorage(PersistedState::new(self.file_storage_path.clone())); + let mut file_storage = FileStorage(PersistedState::new_with_mode( + self.file_storage_path.clone(), + 0o600, + )); let native_storage_result = if std::env::var("VSCODE_CLI_USE_FILE_KEYCHAIN").is_ok() || self.file_storage_path.exists() diff --git a/cli/src/state.rs b/cli/src/state.rs index 8815e2df40c..534c1556763 100644 --- a/cli/src/state.rs +++ b/cli/src/state.rs @@ -6,7 +6,8 @@ extern crate dirs; use std::{ - fs::{create_dir_all, read_to_string, remove_dir_all, write}, + fs::{self, create_dir_all, read_to_string, remove_dir_all}, + io::Write, path::{Path, PathBuf}, sync::{Arc, Mutex}, }; @@ -34,6 +35,8 @@ where { path: PathBuf, state: Option, + #[allow(dead_code)] + mode: u32, } impl PersistedStateContainer @@ -58,13 +61,28 @@ where fn save(&mut self, state: T) -> Result<(), WrappedError> { let s = serde_json::to_string(&state).unwrap(); self.state = Some(state); - write(&self.path, s).map_err(|e| { + self.write_state(s).map_err(|e| { wrap( e, format!("error saving launcher state into {}", self.path.display()), ) }) } + + fn write_state(&mut self, s: String) -> std::io::Result<()> { + #[cfg(not(windows))] + use std::os::unix::fs::OpenOptionsExt; + + let mut f = fs::OpenOptions::new(); + f.create(true); + f.write(true); + f.truncate(true); + #[cfg(not(windows))] + f.mode(self.mode); + + let mut f = f.open(&self.path)?; + f.write_all(s.as_bytes()) + } } /// Container that holds some state value that is persisted to disk. @@ -82,8 +100,17 @@ where { /// Creates a new state container that persists to the given path. pub fn new(path: PathBuf) -> PersistedState { + Self::new_with_mode(path, 0o644) + } + + /// Creates a new state container that persists to the given path. + pub fn new_with_mode(path: PathBuf, mode: u32) -> PersistedState { PersistedState { - container: Arc::new(Mutex::new(PersistedStateContainer { path, state: None })), + container: Arc::new(Mutex::new(PersistedStateContainer { + path, + state: None, + mode, + })), } } @@ -217,5 +244,4 @@ impl LauncherPaths { pub fn web_server_storage(&self) -> PathBuf { self.root.join("serve-web") } - } From 2ea3428bf70a60bbd5e1b49e98888a53143092e3 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 1 Mar 2024 16:18:29 -0800 Subject: [PATCH 1722/1897] Bump distro (#206652) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 03245f8347a..04e74dfabb8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.88.0", - "distro": "a5b6daf94540aab9d17335c2c2533e629d750123", + "distro": "c628288b553c076290c08f74482e6b71c337e4a8", "author": { "name": "Microsoft Corporation" }, From 1f94e5cd54ce0a7bc503a3f95a3742ddc5980151 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Fri, 1 Mar 2024 18:23:57 -0600 Subject: [PATCH 1723/1897] replace CancellationTokenSource().token (#206651) * replace CancellationTokenSource().token * remove unused import --- .../services/search/test/node/textSearchManager.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/search/test/node/textSearchManager.test.ts b/src/vs/workbench/services/search/test/node/textSearchManager.test.ts index f4203933951..693c4e9f0c0 100644 --- a/src/vs/workbench/services/search/test/node/textSearchManager.test.ts +++ b/src/vs/workbench/services/search/test/node/textSearchManager.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Progress } from 'vs/platform/progress/common/progress'; @@ -35,7 +35,7 @@ suite('NativeTextSearchManager', () => { }; const m = new NativeTextSearchManager(query, provider); - await m.search(() => { }, new CancellationTokenSource().token); + await m.search(() => { }, CancellationToken.None); assert.ok(correctEncoding); }); From f838112f066283bcce562b56766b3816112ee77c Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Sat, 2 Mar 2024 16:49:38 +0100 Subject: [PATCH 1724/1897] Relayout on tab bar change (#206669) relayout on tab bar change --- .../parts/editor/multiRowEditorTabsControl.ts | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts index 1a6b5ca985e..24d85415eb8 100644 --- a/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts @@ -36,19 +36,23 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont this.stickyEditorTabsControl = this._register(this.instantiationService.createInstance(MultiEditorTabsControl, this.parent, editorPartsView, this.groupsView, this.groupView, stickyModel)); this.unstickyEditorTabsControl = this._register(this.instantiationService.createInstance(MultiEditorTabsControl, this.parent, editorPartsView, this.groupsView, this.groupView, unstickyModel)); - this.handlePinnedTabsSeparateRowToolbars(); + this.handlePinnedTabsLayoutChange(); } - private handlePinnedTabsSeparateRowToolbars(): void { + private handlePinnedTabsLayoutChange(): void { if (this.groupView.count === 0) { // Do nothing as no tab bar is visible return; } + + const hadTwoTabBars = this.parent.classList.contains('two-tab-bars'); + const hasTwoTabBars = this.groupView.count !== this.groupView.stickyCount && this.groupView.stickyCount > 0; + // Ensure action toolbar is only visible once - if (this.groupView.count === this.groupView.stickyCount) { - this.parent.classList.toggle('two-tab-bars', false); - } else { - this.parent.classList.toggle('two-tab-bars', true); + this.parent.classList.toggle('two-tab-bars', hasTwoTabBars); + + if (hadTwoTabBars !== hasTwoTabBars) { + this.groupView.relayout(); } } @@ -85,7 +89,7 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont } private handleOpenedEditors(): void { - this.handlePinnedTabsSeparateRowToolbars(); + this.handlePinnedTabsLayoutChange(); } beforeCloseEditor(editor: EditorInput): void { @@ -111,7 +115,7 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont } private handleClosedEditors(): void { - this.handlePinnedTabsSeparateRowToolbars(); + this.handlePinnedTabsLayoutChange(); } moveEditor(editor: EditorInput, fromIndex: number, targetIndex: number, stickyStateChange: boolean): void { @@ -125,7 +129,7 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont this.unstickyEditorTabsControl.openEditor(editor); } - this.handlePinnedTabsSeparateRowToolbars(); + this.handlePinnedTabsLayoutChange(); } else { if (this.model.isSticky(editor)) { @@ -144,14 +148,14 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont this.unstickyEditorTabsControl.closeEditor(editor); this.stickyEditorTabsControl.openEditor(editor); - this.handlePinnedTabsSeparateRowToolbars(); + this.handlePinnedTabsLayoutChange(); } unstickEditor(editor: EditorInput): void { this.stickyEditorTabsControl.closeEditor(editor); this.unstickyEditorTabsControl.openEditor(editor); - this.handlePinnedTabsSeparateRowToolbars(); + this.handlePinnedTabsLayoutChange(); } setActive(isActive: boolean): void { From b56aee50fe2dd709e68161559e9af97c7d1e8fa3 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Sat, 2 Mar 2024 13:21:43 -0800 Subject: [PATCH 1725/1897] Support buttons in separators in Quick Access (#206701) * WIP buttons on separators working * revert changes to textSearchQuickAccess * better check --- .../quickinput/browser/pickerQuickAccess.ts | 91 ++++++++++++------- 1 file changed, 56 insertions(+), 35 deletions(-) diff --git a/src/vs/platform/quickinput/browser/pickerQuickAccess.ts b/src/vs/platform/quickinput/browser/pickerQuickAccess.ts index acde1e461d1..86160c9e26a 100644 --- a/src/vs/platform/quickinput/browser/pickerQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/pickerQuickAccess.ts @@ -6,7 +6,7 @@ import { timeout } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { IKeyMods, IQuickPickDidAcceptEvent, IQuickPickSeparator, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IKeyMods, IQuickPickDidAcceptEvent, IQuickPickSeparator, IQuickPick, IQuickPickItem, IQuickInputButton } from 'vs/platform/quickinput/common/quickInput'; import { IQuickAccessProvider, IQuickAccessProviderRunOptions } from 'vs/platform/quickinput/common/quickAccess'; import { isFunction } from 'vs/base/common/types'; @@ -59,6 +59,22 @@ export interface IPickerQuickAccessItem extends IQuickPickItem { trigger?(buttonIndex: number, keyMods: IKeyMods): TriggerAction | Promise; } +export interface IPickerQuickAccessSeparator extends IQuickPickSeparator { + /** + * A method that will be executed when a button of the pick item was + * clicked on. + * + * @param buttonIndex index of the button of the item that + * was clicked. + * + * @param the state of modifier keys when the button was triggered. + * + * @returns a value that indicates what should happen after the trigger + * which can be a `Promise` for long running operations. + */ + trigger?(buttonIndex: number, keyMods: IKeyMods): TriggerAction | Promise; +} + export interface IPickerQuickAccessProviderOptions { /** @@ -320,47 +336,52 @@ export abstract class PickerQuickAccessProvider { - if (typeof item.trigger === 'function') { - const buttonIndex = item.buttons?.indexOf(button) ?? -1; - if (buttonIndex >= 0) { - const result = item.trigger(buttonIndex, picker.keyMods); - const action = (typeof result === 'number') ? result : await result; + const buttonTrigger = async (button: IQuickInputButton, item: T | IPickerQuickAccessSeparator) => { + if (typeof item.trigger !== 'function') { + return; + } - if (token.isCancellationRequested) { - return; - } + const buttonIndex = item.buttons?.indexOf(button) ?? -1; + if (buttonIndex >= 0) { + const result = item.trigger(buttonIndex, picker.keyMods); + const action = (typeof result === 'number') ? result : await result; - switch (action) { - case TriggerAction.NO_ACTION: - break; - case TriggerAction.CLOSE_PICKER: - picker.hide(); - break; - case TriggerAction.REFRESH_PICKER: - updatePickerItems(); - break; - case TriggerAction.REMOVE_ITEM: { - const index = picker.items.indexOf(item); - if (index !== -1) { - const items = picker.items.slice(); - const removed = items.splice(index, 1); - const activeItems = picker.activeItems.filter(activeItem => activeItem !== removed[0]); - const keepScrollPositionBefore = picker.keepScrollPosition; - picker.keepScrollPosition = true; - picker.items = items; - if (activeItems) { - picker.activeItems = activeItems; - } - picker.keepScrollPosition = keepScrollPositionBefore; + if (token.isCancellationRequested) { + return; + } + + switch (action) { + case TriggerAction.NO_ACTION: + break; + case TriggerAction.CLOSE_PICKER: + picker.hide(); + break; + case TriggerAction.REFRESH_PICKER: + updatePickerItems(); + break; + case TriggerAction.REMOVE_ITEM: { + const index = picker.items.indexOf(item); + if (index !== -1) { + const items = picker.items.slice(); + const removed = items.splice(index, 1); + const activeItems = picker.activeItems.filter(activeItem => activeItem !== removed[0]); + const keepScrollPositionBefore = picker.keepScrollPosition; + picker.keepScrollPosition = true; + picker.items = items; + if (activeItems) { + picker.activeItems = activeItems; } - break; + picker.keepScrollPosition = keepScrollPositionBefore; } + break; } } } - })); + }; + + // Trigger the pick with button index if button triggered + disposables.add(picker.onDidTriggerItemButton(({ button, item }) => buttonTrigger(button, item))); + disposables.add(picker.onDidTriggerSeparatorButton(({ button, separator }) => buttonTrigger(button, separator))); return disposables; } From bfefd56dbe16b2880ab8d42803687deb40f9c262 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 3 Mar 2024 10:04:21 +0100 Subject: [PATCH 1726/1897] Revealing a view causes VS Code to steal focus (fix #205766) (#206712) --- src/vs/base/browser/dom.ts | 17 +++++++++++++++-- src/vs/workbench/browser/window.ts | 14 +++----------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 82459f97178..62b509ccda2 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -921,11 +921,24 @@ export function getActiveWindow(): CodeWindow { return (document.defaultView?.window ?? mainWindow) as CodeWindow; } -export function focusWindow(element: Node): void { +export function focusWindow(element: Node, options?: { force: boolean }): void { const window = getWindow(element); - if (!window.document.hasFocus()) { + + // Force: always focus the element window + if (options?.force) { window.focus(); } + + // Not forced: only focus the element window if another + // window in the same workspace group has focus (when auxiliary + // windows are opened). + // This prevents stealing focus from another workspace window. + else { + const activeWindow = getActiveWindow(); + if (activeWindow.document.hasFocus() && activeWindow !== window) { + window.focus(); + } + } } const globalStylesheets = new Map>(); diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index 17354e5f403..69a5fe4dee2 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isSafari, setFullscreen } from 'vs/base/browser/browser'; -import { addDisposableListener, EventHelper, EventType, getActiveWindow, getWindow, getWindowById, getWindows, getWindowsCount, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom'; +import { addDisposableListener, EventHelper, EventType, focusWindow, getWindowById, getWindows, getWindowsCount, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { HidDeviceData, requestHidDevice, requestSerialPort, requestUsbDevice, SerialPortData, UsbDeviceData } from 'vs/base/browser/deviceAccess'; import { timeout } from 'vs/base/common/async'; @@ -56,16 +56,8 @@ export abstract class BaseWindow extends Disposable { targetWindow.HTMLElement.prototype.focus = function (this: HTMLElement, options?: FocusOptions | undefined): void { - // If the active focused window is not the same as the - // window of the element to focus, make sure to focus - // that window first before focusing the element. - const activeWindow = getActiveWindow(); - if (activeWindow.document.hasFocus()) { - const elementWindow = getWindow(this); - if (activeWindow !== elementWindow) { - elementWindow.focus(); - } - } + // Ensure elements window is focused + focusWindow(this); // Pass to original focus() method originalFocus.apply(this, [options]); From 161d5010003b829d0d54b1c2a4c502421cc09461 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 3 Mar 2024 16:26:34 +0100 Subject: [PATCH 1727/1897] check for extension updates while checking for VS Code update (#206704) * check for extension updates while checking for VS Code update * fix tests --- .../extensions/browser/extensionsWorkbenchService.ts | 7 +++++++ .../extensionRecommendationsService.test.ts | 2 ++ .../test/electron-sandbox/extensionsActions.test.ts | 2 ++ .../test/electron-sandbox/extensionsViews.test.ts | 2 ++ .../electron-sandbox/extensionsWorkbenchService.test.ts | 2 ++ 5 files changed, 15 insertions(+) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index c92d36d3ecf..12460b0406d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -53,6 +53,7 @@ import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecyc import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { mainWindow } from 'vs/base/browser/window'; import { IDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs'; +import { IUpdateService, StateType } from 'vs/platform/update/common/update'; interface IExtensionStateProvider { (extension: Extension): T; @@ -783,6 +784,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension @IStorageService private readonly storageService: IStorageService, @IDialogService private readonly dialogService: IDialogService, @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, + @IUpdateService private readonly updateService: IUpdateService, ) { super(); const preferPreReleasesValue = configurationService.getValue('_extensions.preferPreReleases'); @@ -858,6 +860,11 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } })); this._register(Event.debounce(this.onChange, () => undefined, 100)(() => this.hasOutdatedExtensionsContextKey.set(this.outdated.length > 0))); + this._register(this.updateService.onStateChange(e => { + if ((e.type === StateType.AvailableForDownload || e.type === StateType.Downloading) && this.isAutoUpdateEnabled()) { + this.checkForUpdates(); + } + })); // Update AutoUpdate Contexts this.hasOutdatedExtensionsContextKey.set(this.outdated.length > 0); diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts index 1e9e11baa19..0db4f047c60 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts @@ -63,6 +63,7 @@ import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { timeout } from 'vs/base/common/async'; +import { IUpdateService } from 'vs/platform/update/common/update'; const mockExtensionGallery: IGalleryExtension[] = [ aGalleryExtension('MockExtension1', { @@ -274,6 +275,7 @@ suite('ExtensionRecommendationsService Test', () => { }, }); + instantiationService.stub(IUpdateService, { onStateChange: Event.None }); instantiationService.set(IExtensionsWorkbenchService, disposableStore.add(instantiationService.createInstance(ExtensionsWorkbenchService))); instantiationService.stub(IExtensionTipsService, disposableStore.add(instantiationService.createInstance(TestExtensionTipsService))); diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts index 0762678249d..ea44eb82a58 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts @@ -56,6 +56,7 @@ import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/envi import { platform } from 'vs/base/common/platform'; import { arch } from 'vs/base/common/process'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { IUpdateService } from 'vs/platform/update/common/update'; let instantiationService: TestInstantiationService; let installEvent: Emitter, @@ -136,6 +137,7 @@ function setupTest(disposables: Pick) { instantiationService.stub(IUserDataSyncEnablementService, disposables.add(instantiationService.createInstance(UserDataSyncEnablementService))); + instantiationService.stub(IUpdateService, { onStateChange: Event.None }); instantiationService.set(IExtensionsWorkbenchService, disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService))); instantiationService.stub(IWorkspaceTrustManagementService, disposables.add(new TestWorkspaceTrustManagementService())); } diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts index 4a06d92c0e8..a349dbbf69c 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts @@ -48,6 +48,7 @@ import { arch } from 'vs/base/common/process'; import { IProductService } from 'vs/platform/product/common/productService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { IUpdateService } from 'vs/platform/update/common/update'; suite('ExtensionsViews Tests', () => { @@ -187,6 +188,7 @@ suite('ExtensionsViews Tests', () => { await (instantiationService.get(IWorkbenchExtensionEnablementService)).setEnablement([localDisabledTheme], EnablementState.DisabledGlobally); await (instantiationService.get(IWorkbenchExtensionEnablementService)).setEnablement([localDisabledLanguage], EnablementState.DisabledGlobally); + instantiationService.stub(IUpdateService, { onStateChange: Event.None }); instantiationService.set(IExtensionsWorkbenchService, disposableStore.add(instantiationService.createInstance(ExtensionsWorkbenchService))); testableView = disposableStore.add(instantiationService.createInstance(ExtensionsListView, {}, { id: '', title: '' })); }); diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts index 39d5869cd3c..7d56ee6d115 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts @@ -51,6 +51,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { toDisposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Mutable } from 'vs/base/common/types'; +import { IUpdateService } from 'vs/platform/update/common/update'; suite('ExtensionsWorkbenchServiceTest', () => { @@ -131,6 +132,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', []); instantiationService.stubPromise(INotificationService, 'prompt', 0); (instantiationService.get(IWorkbenchExtensionEnablementService)).reset(); + instantiationService.stub(IUpdateService, { onStateChange: Event.None }); }); test('test gallery extension', async () => { From c0fddb67b9b84dca476e6fd685bdfd4a85a13639 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 3 Mar 2024 23:35:38 +0100 Subject: [PATCH 1728/1897] window - try to improve focus handling (#206721) --- src/vs/base/browser/dom.ts | 20 -------- src/vs/workbench/browser/composite.ts | 10 +--- src/vs/workbench/browser/layout.ts | 5 +- .../browser/parts/editor/editorGroupView.ts | 5 +- .../workbench/browser/parts/views/viewPane.ts | 4 +- src/vs/workbench/browser/window.ts | 51 +++++++++++++++---- src/vs/workbench/electron-sandbox/window.ts | 41 ++++----------- .../browser/auxiliaryWindowService.ts | 29 +++++------ .../auxiliaryWindowService.ts | 36 +++---------- src/vs/workbench/test/browser/window.test.ts | 5 +- 10 files changed, 81 insertions(+), 125 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 62b509ccda2..ff113c9baa9 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -921,26 +921,6 @@ export function getActiveWindow(): CodeWindow { return (document.defaultView?.window ?? mainWindow) as CodeWindow; } -export function focusWindow(element: Node, options?: { force: boolean }): void { - const window = getWindow(element); - - // Force: always focus the element window - if (options?.force) { - window.focus(); - } - - // Not forced: only focus the element window if another - // window in the same workspace group has focus (when auxiliary - // windows are opened). - // This prevents stealing focus from another workspace window. - else { - const activeWindow = getActiveWindow(); - if (activeWindow.document.hasFocus() && activeWindow !== window) { - window.focus(); - } - } -} - const globalStylesheets = new Map>(); export function isGlobalStylesheet(node: Node): boolean { diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index 59eba8e11ff..424fcfa19e5 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -10,7 +10,7 @@ import { IComposite, ICompositeControl } from 'vs/workbench/common/composite'; import { Event, Emitter } from 'vs/base/common/event'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConstructorSignature, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { trackFocus, Dimension, IDomPosition, focusWindow } from 'vs/base/browser/dom'; +import { trackFocus, Dimension, IDomPosition } from 'vs/base/browser/dom'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { Disposable } from 'vs/base/common/lifecycle'; import { assertIsDefined } from 'vs/base/common/types'; @@ -149,13 +149,7 @@ export abstract class Composite extends Component implements IComposite { * Called when this composite should receive keyboard focus. */ focus(): void { - const container = this.getContainer(); - if (container) { - // Make sure to focus the window of the container - // because it is possible that the composite is - // opened in a auxiliary window that is not focused. - focusWindow(container); - } + // Subclasses can implement } /** diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 797a1713dd8..b71d9dd9477 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { EventType, addDisposableListener, getClientArea, position, size, IDimension, isAncestorUsingFlowTo, computeScreenAwareSize, getActiveDocument, getWindows, getActiveWindow, focusWindow, isActiveDocument, getWindow, getWindowId, getActiveElement } from 'vs/base/browser/dom'; +import { EventType, addDisposableListener, getClientArea, position, size, IDimension, isAncestorUsingFlowTo, computeScreenAwareSize, getActiveDocument, getWindows, getActiveWindow, isActiveDocument, getWindow, getWindowId, getActiveElement } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen, isWCOEnabled } from 'vs/base/browser/browser'; import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { isWindows, isLinux, isMacintosh, isWeb, isIOS } from 'vs/base/common/platform'; @@ -1124,9 +1124,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi focusPart(part: SINGLE_WINDOW_PARTS): void; focusPart(part: Parts, targetWindow: Window = mainWindow): void { const container = this.getContainer(targetWindow, part) ?? this.mainContainer; - if (container) { - focusWindow(container); - } switch (part) { case Parts.EDITOR_PART: diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index faddd4e7b06..69bfb90f5d4 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -11,7 +11,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { Emitter, Relay } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Dimension, trackFocus, addDisposableListener, EventType, EventHelper, findParentWithClass, isAncestor, IDomNodePagePosition, isMouseEvent, isActiveElement, focusWindow, getWindow, getActiveElement } from 'vs/base/browser/dom'; +import { Dimension, trackFocus, addDisposableListener, EventType, EventHelper, findParentWithClass, isAncestor, IDomNodePagePosition, isMouseEvent, isActiveElement, getWindow, getActiveElement } from 'vs/base/browser/dom'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; @@ -976,9 +976,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView { focus(): void { - // Ensure window focus - focusWindow(this.element); - // Pass focus to editor panes if (this.activeEditorPane) { this.activeEditorPane.focus(); diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index 84e11baf381..6ef0b18cdb1 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { asCssVariable, foreground } from 'vs/platform/theme/common/colorRegistry'; import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -import { after, append, $, trackFocus, EventType, addDisposableListener, createCSSRule, asCSSUrl, Dimension, reset, asCssValueWithDefault, focusWindow } from 'vs/base/browser/dom'; +import { after, append, $, trackFocus, EventType, addDisposableListener, createCSSRule, asCSSUrl, Dimension, reset, asCssValueWithDefault } from 'vs/base/browser/dom'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { Action, IAction, IActionRunner } from 'vs/base/common/actions'; import { ActionsOrientation, IActionViewItem, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -630,8 +630,6 @@ export abstract class ViewPane extends Pane implements IView { } focus(): void { - focusWindow(this.element); - if (this.viewWelcomeController.enabled) { this.viewWelcomeController.focus(); } else if (this.element) { diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index 69a5fe4dee2..9f98ac1c680 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { isSafari, setFullscreen } from 'vs/base/browser/browser'; -import { addDisposableListener, EventHelper, EventType, focusWindow, getWindowById, getWindows, getWindowsCount, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom'; +import { addDisposableListener, EventHelper, EventType, getActiveWindow, getWindow, getWindowById, getWindows, getWindowsCount, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { HidDeviceData, requestHidDevice, requestSerialPort, requestUsbDevice, SerialPortData, UsbDeviceData } from 'vs/base/browser/deviceAccess'; import { timeout } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { matchesScheme, Schemas } from 'vs/base/common/network'; -import { isIOS, isMacintosh } from 'vs/base/common/platform'; +import { isIOS, isMacintosh, isNative } from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; @@ -30,6 +30,7 @@ import { registerWindowDriver } from 'vs/workbench/services/driver/browser/drive import { CodeWindow, isAuxiliaryWindow, mainWindow } from 'vs/base/browser/window'; import { createSingleCallFunction } from 'vs/base/common/functional'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export abstract class BaseWindow extends Disposable { @@ -39,11 +40,16 @@ export abstract class BaseWindow extends Disposable { constructor( targetWindow: CodeWindow, dom = { getWindowsCount, getWindows }, /* for testing */ - @IHostService protected readonly hostService: IHostService + @IHostService protected readonly hostService: IHostService, + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService ) { super(); + if (isNative) { + this.enableNativeWindowFocus(targetWindow); + } this.enableWindowFocusOnElementFocus(targetWindow); + this.enableMultiWindowAwareTimeout(targetWindow, dom); this.registerFullScreenListeners(targetWindow.vscodeWindowId); @@ -51,13 +57,40 @@ export abstract class BaseWindow extends Disposable { //#region focus handling in multi-window applications + protected enableNativeWindowFocus(targetWindow: CodeWindow): void { + const originalWindowFocus = targetWindow.focus.bind(targetWindow); + + const that = this; + targetWindow.focus = function () { + originalWindowFocus(); + + if ( + !that.environmentService.extensionTestsLocationURI && // never steal focus when running tests + !targetWindow.document.hasFocus() // skip when already having focus + ) { + // Enable `window.focus()` to work in Electron by + // asking the main process to focus the window. + // https://github.com/electron/electron/issues/25578 + that.hostService.focus(targetWindow); + } + }; + } + protected enableWindowFocusOnElementFocus(targetWindow: CodeWindow): void { const originalFocus = HTMLElement.prototype.focus; targetWindow.HTMLElement.prototype.focus = function (this: HTMLElement, options?: FocusOptions | undefined): void { - // Ensure elements window is focused - focusWindow(this); + // If the active focused window is not the same as the + // window of the element to focus, make sure to focus + // that window first before focusing the element. + const activeWindow = getActiveWindow(); + if (activeWindow.document.hasFocus()) { + const elementWindow = getWindow(this); + if (activeWindow !== elementWindow) { + elementWindow.focus(); + } + } // Pass to original focus() method originalFocus.apply(this, [options]); @@ -178,12 +211,12 @@ export class BrowserWindow extends BaseWindow { @IDialogService private readonly dialogService: IDialogService, @ILabelService private readonly labelService: ILabelService, @IProductService private readonly productService: IProductService, - @IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService, + @IBrowserWorkbenchEnvironmentService private readonly browserEnvironmentService: IBrowserWorkbenchEnvironmentService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IHostService hostService: IHostService ) { - super(mainWindow, undefined, hostService); + super(mainWindow, undefined, hostService, browserEnvironmentService); this.registerListeners(); this.create(); @@ -280,8 +313,8 @@ export class BrowserWindow extends BaseWindow { this.openerService.setDefaultExternalOpener({ openExternal: async (href: string) => { let isAllowedOpener = false; - if (this.environmentService.options?.openerAllowedExternalUrlPrefixes) { - for (const trustedPopupPrefix of this.environmentService.options.openerAllowedExternalUrlPrefixes) { + if (this.browserEnvironmentService.options?.openerAllowedExternalUrlPrefixes) { + for (const trustedPopupPrefix of this.browserEnvironmentService.options.openerAllowedExternalUrlPrefixes) { if (href.startsWith(trustedPopupPrefix)) { isAllowedOpener = true; break; diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index f207e15834f..eb2159272be 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { onUnexpectedError } from 'vs/base/common/errors'; import { equals } from 'vs/base/common/objects'; -import { EventType, EventHelper, addDisposableListener, ModifierKeyEmitter, getActiveElement, hasWindow, getWindow, getWindowById, getWindowId, getWindows } from 'vs/base/browser/dom'; +import { EventType, EventHelper, addDisposableListener, ModifierKeyEmitter, getActiveElement, hasWindow, getWindow, getWindowById, getWindows } from 'vs/base/browser/dom'; import { Action, Separator, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; import { EditorResourceAccessor, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors, IResourceDiffEditorInput, IUntypedEditorInput, IEditorPane, isResourceEditorInput, IResourceMergeEditorInput } from 'vs/workbench/common/editor'; @@ -107,7 +107,7 @@ export class NativeWindow extends BaseWindow { @IMenuService private readonly menuService: IMenuService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IIntegrityService private readonly integrityService: IIntegrityService, - @INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, + @INativeWorkbenchEnvironmentService private readonly nativeEnvironmentService: INativeWorkbenchEnvironmentService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IOpenerService private readonly openerService: IOpenerService, @@ -131,7 +131,7 @@ export class NativeWindow extends BaseWindow { @IUtilityProcessWorkerWorkbenchService private readonly utilityProcessWorkerWorkbenchService: IUtilityProcessWorkerWorkbenchService, @IHostService hostService: IHostService ) { - super(mainWindow, undefined, hostService); + super(mainWindow, undefined, hostService, nativeEnvironmentService); this.mainPartEditorService = editorService.createScoped('main', this._store); @@ -353,7 +353,7 @@ export class NativeWindow extends BaseWindow { this._register(Event.debounce(this.editorService.onDidVisibleEditorsChange, () => undefined, 0, undefined, undefined, undefined, this._store)(() => this.maybeCloseWindow())); // Listen to editor closing (if we run with --wait) - const filesToWait = this.environmentService.filesToWait; + const filesToWait = this.nativeEnvironmentService.filesToWait; if (filesToWait) { this.trackClosedWaitFiles(filesToWait.waitMarkerFileUri, coalesce(filesToWait.paths.map(path => path.fileUri))); } @@ -412,7 +412,7 @@ export class NativeWindow extends BaseWindow { Event.map(Event.filter(this.nativeHostService.onDidMaximizeWindow, windowId => !!hasWindow(windowId)), windowId => ({ maximized: true, windowId })), Event.map(Event.filter(this.nativeHostService.onDidUnmaximizeWindow, windowId => !!hasWindow(windowId)), windowId => ({ maximized: false, windowId })) )(e => this.layoutService.updateWindowMaximizedState(getWindowById(e.windowId)!.window, e.maximized))); - this.layoutService.updateWindowMaximizedState(mainWindow, this.environmentService.window.maximized ?? false); + this.layoutService.updateWindowMaximizedState(mainWindow, this.nativeEnvironmentService.window.maximized ?? false); // Detect panel position to determine minimum width this._register(this.layoutService.onDidChangePanelPosition(pos => this.onDidChangePanelPosition(positionFromString(pos)))); @@ -582,7 +582,7 @@ export class NativeWindow extends BaseWindow { } private maybeCloseWindow(): void { - const closeWhenEmpty = this.configurationService.getValue('window.closeWhenEmpty') || this.environmentService.args.wait; + const closeWhenEmpty = this.configurationService.getValue('window.closeWhenEmpty') || this.nativeEnvironmentService.args.wait; if (!closeWhenEmpty) { return; // return early if configured to not close when empty } @@ -671,29 +671,6 @@ export class NativeWindow extends BaseWindow { if (this.environmentService.enableSmokeTestDriver) { this.setupDriver(); } - - // Patch methods that we need to work properly - this.patchMethods(); - } - - private patchMethods(): void { - - // Enable `window.focus()` to work in Electron by - // asking the main process to focus the window. - // https://github.com/electron/electron/issues/25578 - const that = this; - const originalWindowFocus = mainWindow.focus.bind(mainWindow); - mainWindow.focus = function () { - if (that.environmentService.extensionTestsLocationURI) { - return; // no focus when we are running tests from CLI - } - - originalWindowFocus(); - - if (!mainWindow.document.hasFocus()) { - that.nativeHostService.focusWindow({ targetWindowId: getWindowId(mainWindow) }); - } - }; } private async handleWarnings(): Promise { @@ -731,11 +708,11 @@ export class NativeWindow extends BaseWindow { let installLocationUri: URI; if (isMacintosh) { // appRoot = /Applications/Visual Studio Code - Insiders.app/Contents/Resources/app - installLocationUri = dirname(dirname(dirname(URI.file(this.environmentService.appRoot)))); + installLocationUri = dirname(dirname(dirname(URI.file(this.nativeEnvironmentService.appRoot)))); } else { // appRoot = C:\Users\\AppData\Local\Programs\Microsoft VS Code Insiders\resources\app // appRoot = /usr/share/code-insiders/resources/app - installLocationUri = dirname(dirname(URI.file(this.environmentService.appRoot))); + installLocationUri = dirname(dirname(URI.file(this.nativeEnvironmentService.appRoot))); } for (const folder of this.contextService.getWorkspace().folders) { @@ -753,7 +730,7 @@ export class NativeWindow extends BaseWindow { // macOS 10.13 and 10.14 warning if (isMacintosh) { - const majorVersion = this.environmentService.os.release.split('.')[0]; + const majorVersion = this.nativeEnvironmentService.os.release.split('.')[0]; const eolReleases = new Map([ ['17', 'macOS High Sierra'], ['18', 'macOS Mojave'], diff --git a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts index ce6f32b0914..6864585e014 100644 --- a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts @@ -22,6 +22,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Barrier } from 'vs/base/common/async'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export const IAuxiliaryWindowService = createDecorator('auxiliaryWindowService'); @@ -84,9 +85,10 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { readonly container: HTMLElement, stylesHaveLoaded: Barrier, @IConfigurationService private readonly configurationService: IConfigurationService, - @IHostService hostService: IHostService + @IHostService hostService: IHostService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService ) { - super(window, undefined, hostService); + super(window, undefined, hostService, environmentService); this.whenStylesHaveLoaded = stylesHaveLoaded.wait().then(() => { }); this.registerListeners(); @@ -182,7 +184,8 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili @IDialogService private readonly dialogService: IDialogService, @IConfigurationService protected readonly configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IHostService protected readonly hostService: IHostService + @IHostService protected readonly hostService: IHostService, + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService ) { super(); } @@ -237,7 +240,7 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili } protected createAuxiliaryWindow(targetWindow: CodeWindow, container: HTMLElement, stylesLoaded: Barrier): AuxiliaryWindow { - return new AuxiliaryWindow(targetWindow, container, stylesLoaded, this.configurationService, this.hostService); + return new AuxiliaryWindow(targetWindow, container, stylesLoaded, this.configurationService, this.hostService, this.environmentService); } private async openWindow(options?: IAuxiliaryWindowOpenOptions): Promise { @@ -293,24 +296,20 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili } protected createContainer(auxiliaryWindow: CodeWindow, disposables: DisposableStore, options?: IAuxiliaryWindowOpenOptions): { stylesLoaded: Barrier; container: HTMLElement } { - this.patchMethods(auxiliaryWindow); + auxiliaryWindow.document.createElement = function () { + // Disallow `createElement` because it would create + // HTML Elements in the "wrong" context and break + // code that does "instanceof HTMLElement" etc. + throw new Error('Not allowed to create elements in child window JavaScript context. Always use the main window so that "xyz instanceof HTMLElement" continues to work.'); + }; this.applyMeta(auxiliaryWindow); const { stylesLoaded } = this.applyCSS(auxiliaryWindow, disposables); const container = this.applyHTML(auxiliaryWindow, disposables); + return { stylesLoaded, container }; } - protected patchMethods(auxiliaryWindow: CodeWindow): void { - - // Disallow `createElement` because it would create - // HTML Elements in the "wrong" context and break - // code that does "instanceof HTMLElement" etc. - auxiliaryWindow.document.createElement = function () { - throw new Error('Not allowed to create elements in child window JavaScript context. Always use the main window so that "xyz instanceof HTMLElement" continues to work.'); - }; - } - private applyMeta(auxiliaryWindow: CodeWindow): void { for (const metaTag of ['meta[charset="utf-8"]', 'meta[http-equiv="Content-Security-Policy"]', 'meta[name="viewport"]', 'meta[name="theme-color"]']) { const metaElement = mainWindow.document.querySelector(metaTag); diff --git a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts index d07c6d8addf..4a687027faf 100644 --- a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts @@ -17,11 +17,11 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Barrier } from 'vs/base/common/async'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { applyZoom } from 'vs/platform/window/electron-sandbox/window'; import { getZoomLevel } from 'vs/base/browser/browser'; import { getActiveWindow } from 'vs/base/browser/dom'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; type NativeCodeWindow = CodeWindow & { readonly vscode: ISandboxGlobals; @@ -38,9 +38,10 @@ export class NativeAuxiliaryWindow extends AuxiliaryWindow { @IConfigurationService configurationService: IConfigurationService, @INativeHostService private readonly nativeHostService: INativeHostService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IHostService hostService: IHostService + @IHostService hostService: IHostService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService ) { - super(window, container, stylesHaveLoaded, configurationService, hostService); + super(window, container, stylesHaveLoaded, configurationService, hostService, environmentService); } protected override async confirmBeforeClose(e: BeforeUnloadEvent): Promise { @@ -68,10 +69,10 @@ export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService @IDialogService dialogService: IDialogService, @IInstantiationService private readonly instantiationService: IInstantiationService, @ITelemetryService telemetryService: ITelemetryService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IHostService hostService: IHostService + @IHostService hostService: IHostService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService ) { - super(layoutService, dialogService, configurationService, telemetryService, hostService); + super(layoutService, dialogService, configurationService, telemetryService, hostService, environmentService); } protected override async resolveWindowId(auxiliaryWindow: NativeCodeWindow): Promise { @@ -97,29 +98,8 @@ export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService return super.createContainer(auxiliaryWindow, disposables); } - protected override patchMethods(auxiliaryWindow: NativeCodeWindow): void { - super.patchMethods(auxiliaryWindow); - - // Enable `window.focus()` to work in Electron by - // asking the main process to focus the window. - // https://github.com/electron/electron/issues/25578 - const that = this; - const originalWindowFocus = auxiliaryWindow.focus.bind(auxiliaryWindow); - auxiliaryWindow.focus = function () { - if (that.environmentService.extensionTestsLocationURI) { - return; // no focus when we are running tests from CLI - } - - originalWindowFocus(); - - if (!auxiliaryWindow.document.hasFocus()) { - that.nativeHostService.focusWindow({ targetWindowId: auxiliaryWindow.vscodeWindowId }); - } - }; - } - protected override createAuxiliaryWindow(targetWindow: CodeWindow, container: HTMLElement, stylesHaveLoaded: Barrier,): AuxiliaryWindow { - return new NativeAuxiliaryWindow(targetWindow, container, stylesHaveLoaded, this.configurationService, this.nativeHostService, this.instantiationService, this.hostService); + return new NativeAuxiliaryWindow(targetWindow, container, stylesHaveLoaded, this.configurationService, this.nativeHostService, this.instantiationService, this.hostService, this.environmentService); } } diff --git a/src/vs/workbench/test/browser/window.test.ts b/src/vs/workbench/test/browser/window.test.ts index 6d9b702cea6..4a395ee47b3 100644 --- a/src/vs/workbench/test/browser/window.test.ts +++ b/src/vs/workbench/test/browser/window.test.ts @@ -10,7 +10,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { BaseWindow } from 'vs/workbench/browser/window'; -import { TestHostService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEnvironmentService, TestHostService } from 'vs/workbench/test/browser/workbenchTestServices'; suite('Window', () => { @@ -19,9 +19,10 @@ suite('Window', () => { class TestWindow extends BaseWindow { constructor(window: CodeWindow, dom: { getWindowsCount: () => number; getWindows: () => Iterable }) { - super(window, dom, new TestHostService()); + super(window, dom, new TestHostService(), TestEnvironmentService); } + protected override enableNativeWindowFocus(): void { } protected override enableWindowFocusOnElementFocus(): void { } } From d374d10eea0154ce74b5cafe64d3e0096dd58717 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 4 Mar 2024 08:41:30 +0100 Subject: [PATCH 1729/1897] windows - some better handling of focus also for `webview` (#206757) * :lipstick: * fix focus issues with webview and aux window --- src/vs/base/browser/dom.ts | 29 +++++++++++++++++++ src/vs/workbench/browser/composite.ts | 16 ++-------- src/vs/workbench/browser/part.ts | 4 --- src/vs/workbench/browser/window.ts | 15 +++------- src/vs/workbench/common/component.ts | 1 - .../extensions/browser/extensionEditor.ts | 2 +- .../interactive/browser/interactiveEditor.ts | 2 +- .../browser/diff/notebookDiffEditor.ts | 2 +- .../contrib/webview/browser/webviewElement.ts | 6 +++- 9 files changed, 44 insertions(+), 33 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index ff113c9baa9..907f89d911e 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -921,6 +921,35 @@ export function getActiveWindow(): CodeWindow { return (document.defaultView?.window ?? mainWindow) as CodeWindow; } +/** + * Given an element, will attempt to pass focus() to the window it belongs + * to, depending on the options passed in: + * - force: always focus the element's window + * - otherwise: only focus the element's window if another window in the same + * workspace group has focus (when auxiliary windows are opened). + * + * @param element used to figure out the window the element belongs to + */ +export function focusWindow(element: Node, options?: { force: boolean }): void { + const window = getWindow(element); + + // Force: always focus the element window + if (options?.force) { + window.focus(); + } + + // Not forced: only focus the element window if another + // window in the same workspace group has focus (when auxiliary + // windows are opened). + // This prevents stealing focus from another workspace window. + else { + const activeWindow = getActiveWindow(); + if (activeWindow !== window && activeWindow.document.hasFocus()) { + window.focus(); + } + } +} + const globalStylesheets = new Map>(); export function isGlobalStylesheet(node: Node): boolean { diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index 424fcfa19e5..d56a31212ed 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -36,7 +36,7 @@ export abstract class Composite extends Component implements IComposite { private readonly _onTitleAreaUpdate = this._register(new Emitter()); readonly onTitleAreaUpdate = this._onTitleAreaUpdate.event; - private _onDidFocus: Emitter | undefined; + protected _onDidFocus: Emitter | undefined; get onDidFocus(): Event { if (!this._onDidFocus) { this._onDidFocus = this.registerFocusTrackEvents().onDidFocus; @@ -45,10 +45,6 @@ export abstract class Composite extends Component implements IComposite { return this._onDidFocus.event; } - protected fireOnDidFocus(): void { - this._onDidFocus?.fire(); - } - private _onDidBlur: Emitter | undefined; get onDidBlur(): Event { if (!this._onDidBlur) { @@ -86,22 +82,16 @@ export abstract class Composite extends Component implements IComposite { protected actionRunner: IActionRunner | undefined; - private _telemetryService: ITelemetryService; - protected get telemetryService(): ITelemetryService { return this._telemetryService; } - - private visible: boolean; + private visible = false; private parent: HTMLElement | undefined; constructor( id: string, - telemetryService: ITelemetryService, + protected readonly telemetryService: ITelemetryService, themeService: IThemeService, storageService: IStorageService ) { super(id, themeService, storageService); - - this._telemetryService = telemetryService; - this.visible = false; } getTitle(): string | undefined { diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index 7ae271c65cc..e78f59839be 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -61,10 +61,6 @@ export abstract class Part extends Component implements ISerializableView { } } - override updateStyles(): void { - super.updateStyles(); - } - /** * Note: Clients should not call this method, the workbench calls this * method. Calling it otherwise may result in unexpected behavior. diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index 9f98ac1c680..203275338eb 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isSafari, setFullscreen } from 'vs/base/browser/browser'; -import { addDisposableListener, EventHelper, EventType, getActiveWindow, getWindow, getWindowById, getWindows, getWindowsCount, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom'; +import { addDisposableListener, EventHelper, EventType, focusWindow, getWindowById, getWindows, getWindowsCount, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { HidDeviceData, requestHidDevice, requestSerialPort, requestUsbDevice, SerialPortData, UsbDeviceData } from 'vs/base/browser/deviceAccess'; import { timeout } from 'vs/base/common/async'; @@ -81,16 +81,9 @@ export abstract class BaseWindow extends Disposable { targetWindow.HTMLElement.prototype.focus = function (this: HTMLElement, options?: FocusOptions | undefined): void { - // If the active focused window is not the same as the - // window of the element to focus, make sure to focus - // that window first before focusing the element. - const activeWindow = getActiveWindow(); - if (activeWindow.document.hasFocus()) { - const elementWindow = getWindow(this); - if (activeWindow !== elementWindow) { - elementWindow.focus(); - } - } + // Ensure the window the element belongs to is focused + // in scenarios where auxiliary windows are present + focusWindow(this); // Pass to original focus() method originalFocus.apply(this, [options]); diff --git a/src/vs/workbench/common/component.ts b/src/vs/workbench/common/component.ts index 6c25dc9d977..f8dd0115412 100644 --- a/src/vs/workbench/common/component.ts +++ b/src/vs/workbench/common/component.ts @@ -20,7 +20,6 @@ export class Component extends Themable { ) { super(themeService); - this.id = id; this.memento = new Memento(this.id, storageService); this._register(storageService.onWillSaveState(() => { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index b2f9fcbd754..34c163c0b47 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -683,7 +683,7 @@ export class ExtensionEditor extends EditorPane { webview.setHtml(body); webview.claim(this, undefined); - this.contentDisposables.add(webview.onDidFocus(() => this.fireOnDidFocus())); + this.contentDisposables.add(webview.onDidFocus(() => this._onDidFocus?.fire())); this.contentDisposables.add(webview.onDidScroll(() => this.initialScrollProgress.set(webviewIndex, webview.initialScrollProgress))); diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index 6793200e5e9..d6192e7c946 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -161,7 +161,7 @@ export class InteractiveEditor extends EditorPane { this._editorOptions = this._computeEditorOptions(); } })); - this._notebookOptions = new NotebookOptions(this.window, configurationService, notebookExecutionStateService, codeEditorService, true, { cellToolbarInteraction: 'hover', globalToolbar: true, stickyScrollEnabled: false, dragAndDropEnabled: false }); //TODO@bpasero might crash + this._notebookOptions = new NotebookOptions(this.window, configurationService, notebookExecutionStateService, codeEditorService, true, { cellToolbarInteraction: 'hover', globalToolbar: true, stickyScrollEnabled: false, dragAndDropEnabled: false }); this._editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY); codeEditorService.registerDecorationType('interactive-decoration', DECORATION_KEY, {}); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts index 6be73e321c5..39617722b2b 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts @@ -154,7 +154,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD @ICodeEditorService codeEditorService: ICodeEditorService ) { super(NotebookTextDiffEditor.ID, group, telemetryService, themeService, storageService); - this._notebookOptions = new NotebookOptions(this.window, this.configurationService, notebookExecutionStateService, codeEditorService, false);//TODO@bpasero will crash + this._notebookOptions = new NotebookOptions(this.window, this.configurationService, notebookExecutionStateService, codeEditorService, false); this._register(this._notebookOptions); this._revealFirst = true; } diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 6514979da3d..f9baf25d202 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isFirefox } from 'vs/base/browser/browser'; -import { addDisposableListener, EventType, getActiveWindow } from 'vs/base/browser/dom'; +import { addDisposableListener, EventType, focusWindow, getActiveWindow } from 'vs/base/browser/dom'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { promiseWithResolvers, ThrottledDelayer } from 'vs/base/common/async'; import { streamToBuffer, VSBufferReadableStream } from 'vs/base/common/buffer'; @@ -803,6 +803,10 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD return; } + // Ensure the window the element belongs to is focused + // in scenarios where auxiliary windows are present + focusWindow(this.element); + try { this.element.contentWindow?.focus(); } catch { From 355fa9491c359a8c0e2288ff5aec4538e7e69b10 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 4 Mar 2024 11:55:15 +0100 Subject: [PATCH 1730/1897] Comments filter wording feedback (#206776) Fixes #206756 --- .../workbench/contrib/comments/browser/commentsViewActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts b/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts index e6fd43f4b91..7a0f4d21531 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts @@ -123,7 +123,7 @@ registerAction2(class extends ViewAction { constructor() { super({ id: `workbench.actions.${COMMENTS_VIEW_ID}.toggleUnResolvedComments`, - title: localize('toggle unresolved', "Toggle Unresolved Comments"), + title: localize('toggle unresolved', "Show Unresolved"), category: localize('comments', "Comments"), toggled: { condition: CONTEXT_KEY_SHOW_UNRESOLVED, @@ -148,7 +148,7 @@ registerAction2(class extends ViewAction { constructor() { super({ id: `workbench.actions.${COMMENTS_VIEW_ID}.toggleResolvedComments`, - title: localize('toggle resolved', "Toggle Resolved Comments"), + title: localize('toggle resolved', "Show Resolved"), category: localize('comments', "Comments"), toggled: { condition: CONTEXT_KEY_SHOW_RESOLVED, From 5f354675ad908a6cb49820a8bba2dfda9781ef70 Mon Sep 17 00:00:00 2001 From: Anthony Stewart <150152+a-stewart@users.noreply.github.com> Date: Mon, 4 Mar 2024 12:17:47 +0100 Subject: [PATCH 1731/1897] Export ILocalizedString in nls.mock.ts (#206449) --- src/vs/nls.mock.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/nls.mock.ts b/src/vs/nls.mock.ts index d9ee1ecd2c6..5323c6c6340 100644 --- a/src/vs/nls.mock.ts +++ b/src/vs/nls.mock.ts @@ -8,7 +8,7 @@ export interface ILocalizeInfo { comment: string[]; } -interface ILocalizedString { +export interface ILocalizedString { original: string; value: string; } From 595efea6480d30fff1bb503df5bf80c950d3661f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 4 Mar 2024 12:28:36 +0100 Subject: [PATCH 1732/1897] use document object identity and not uri equality when checking if a document belongs to a notebook (#206778) https://github.com/microsoft/vscode/issues/206487 --- src/vs/workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHostNotebookDocument.ts | 12 +----------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index a719b6131d3..7344da8ee5e 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -541,7 +541,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const interalSelector = typeConverters.LanguageSelector.from(selector); let notebook: vscode.NotebookDocument | undefined; if (targetsNotebooks(interalSelector)) { - notebook = extHostNotebook.notebookDocuments.find(value => Boolean(value.getCell(document.uri)))?.apiNotebook; + notebook = extHostNotebook.notebookDocuments.find(value => value.apiNotebook.getCells().find(c => c.document === document))?.apiNotebook; } return score(interalSelector, document.uri, document.languageId, true, notebook?.uri, notebook?.notebookType); }, diff --git a/src/vs/workbench/api/common/extHostNotebookDocument.ts b/src/vs/workbench/api/common/extHostNotebookDocument.ts index 2cc7a200edc..8f74a0a4b69 100644 --- a/src/vs/workbench/api/common/extHostNotebookDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookDocument.ts @@ -442,17 +442,7 @@ export class ExtHostNotebookDocument { return this._cells[index]; } - getCell(cellHandle: number | URI): ExtHostCell | undefined { - if (URI.isUri(cellHandle)) { - const data = notebookCommon.CellUri.parse(cellHandle); - if (!data) { - return undefined; - } - if (data.notebook.toString() !== this.uri.toString()) { - return undefined; - } - cellHandle = data.handle; - } + getCell(cellHandle: number): ExtHostCell | undefined { return this._cells.find(cell => cell.handle === cellHandle); } From 467e1971fc471b5f6c82cf826ab1bcfb12c59cd5 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 4 Mar 2024 12:28:56 +0100 Subject: [PATCH 1733/1897] Maximum call stack size exceeded (#206781) Fixes #205093 --- src/vs/workbench/contrib/remote/browser/remoteExplorer.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index 37948b2bf60..492516a79f7 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -269,8 +269,10 @@ export class AutomaticPortForwarding extends Disposable implements IWorkbenchCon this.outputForwarder?.dispose(); this.outputForwarder = undefined; if (environment?.os !== OperatingSystem.Linux) { - Registry.as(ConfigurationExtensions.Configuration) - .registerDefaultConfigurations([{ overrides: { 'remote.autoForwardPortsSource': PORT_AUTO_SOURCE_SETTING_OUTPUT } }]); + if (this.configurationService.inspect(PORT_AUTO_SOURCE_SETTING).default?.value !== PORT_AUTO_SOURCE_SETTING_OUTPUT) { + Registry.as(ConfigurationExtensions.Configuration) + .registerDefaultConfigurations([{ overrides: { 'remote.autoForwardPortsSource': PORT_AUTO_SOURCE_SETTING_OUTPUT } }]); + } this.outputForwarder = this._register(new OutputAutomaticPortForwarding(this.terminalService, this.notificationService, this.openerService, this.externalOpenerService, this.remoteExplorerService, this.configurationService, this.debugService, this.tunnelService, this.hostService, this.logService, this.contextKeyService, () => false)); } else { From 9bbdceef42f1235bf4c64b4eaac0162cce42f482 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 4 Mar 2024 13:12:03 +0100 Subject: [PATCH 1734/1897] Comments filter looks unbalanced (#206785) * Comments filter looks unbalanced Fixes #206755 * PR feedback * More PR feedback --- src/vs/workbench/browser/parts/views/media/views.css | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index 15c4a1aae00..130fd60fe85 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -268,7 +268,7 @@ } .viewpane-filter-container > .viewpane-filter > .viewpane-filter-controls > .viewpane-filter-badge { - margin: 4px 0px; + margin: 4px 2px 4px 0px; padding: 0px 8px; border-radius: 2px; } @@ -278,10 +278,6 @@ display: none; } -.viewpane-filter > .viewpane-filter-controls > .monaco-action-bar .action-item .action-label.codicon.filter { - padding: 2px; -} - .panel > .title .monaco-action-bar .action-item.viewpane-filter-container { max-width: 400px; min-width: 150px; From 25f30add73696a20fa9b2266748159e711ad7985 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 4 Mar 2024 13:26:58 +0100 Subject: [PATCH 1735/1897] lm-access - update docs (#206788) --- .../vscode.proposed.languageModels.d.ts | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.languageModels.d.ts b/src/vscode-dts/vscode.proposed.languageModels.d.ts index 8732b0e67e0..9cd0f8c1ccb 100644 --- a/src/vscode-dts/vscode.proposed.languageModels.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModels.d.ts @@ -15,7 +15,7 @@ declare module 'vscode' { /** * An async iterable that is a stream of text chunks forming the overall response. * - * *Note* that this stream will error when during receiving an error occurrs. + * *Note* that this stream will error when during data receiving an error occurrs. */ stream: AsyncIterable; } @@ -87,9 +87,11 @@ declare module 'vscode' { constructor(content: string); } + /** + * Different types of language model messages. + */ export type LanguageModelChatMessage = LanguageModelChatSystemMessage | LanguageModelChatUserMessage | LanguageModelChatAssistantMessage; - /** * An event describing the change in the set of available language models. */ @@ -109,7 +111,8 @@ declare module 'vscode' { * * Consumers of language models should check the code property to determine specific * failure causes, like `if(someError.code === vscode.LanguageModelError.NotFound.name) {...}` - * for the case of referring to an unknown language model. + * for the case of referring to an unknown language model. For unspecified errors the `cause`-property + * will contain the actual error. */ export class LanguageModelError extends Error { @@ -167,27 +170,25 @@ declare module 'vscode' { /** * Make a chat request using a language model. * - * *Note* that language model use may be subject to access restrictions and user consent. This function will return a rejected promise - * if access to the language model is not possible. Reasons for this can be: + * - *Note 1:* language model use may be subject to access restrictions and user consent. * - * - user consent not given - * - quote limits exceeded - * - model does not exist + * - *Note 2:* language models are contributed by other extensions and as they evolve and change, + * the set of available language models may change over time. Therefore it is strongly recommend to check + * {@link languageModels} for aviailable values and handle missing language models gracefully. * - * @param languageModel A language model identifier. See {@link languageModels} for aviailable values. + * This function will return a rejected promise if making a request to the language model is not + * possible. Reasons for this can be: + * + * - user consent not given, see {@link LanguageModelError.NoPermissions `NoPermissions`} + * - model does not exist, see {@link LanguageModelError.NotFound `NotFound`} + * - quota limits exceeded, see {@link LanguageModelError.cause `LanguageModelError.cause`} + * + * @param languageModel A language model identifier. * @param messages An array of message instances. - * @param options Objects that control the request. + * @param options Options that control the request. * @param token A cancellation token which controls the request. See {@link CancellationTokenSource} for how to create one. * @returns A thenable that resolves to a {@link LanguageModelChatResponse}. The promise will reject when the request couldn't be made. */ - // TODO@API refine doc - // TODO@API ✅ ExtensionContext#permission#languageModels: { languageModel: string: LanguageModelAccessInformation} - // TODO@API ✅ define specific error types? - // TODO@API ✅ NAME: sendChatRequest, fetchChatResponse, makeChatRequest, chat, chatRequest sendChatRequest - // TODO@API ✅ NAME: LanguageModelChatXYZMessage - // TODO@API ✅ errors on everything that prevents us to make the actual request - // TODO@API ✅ double auth - // TODO@API ✅ NAME: LanguageModelChatResponse, ChatResponse, ChatRequestResponse export function sendChatRequest(languageModel: string, messages: LanguageModelChatMessage[], options: LanguageModelChatRequestOptions, token: CancellationToken): Thenable; /** From eaf5342bb323c0d64b6013b2a272dab2f0a8a664 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 4 Mar 2024 13:29:14 +0100 Subject: [PATCH 1736/1897] AutoSave is triggering editor.formatOnSave even when files.autoSave is afterDelay (fix #206475) (#206789) --- .../browser/parts/editor/editorAutoSave.ts | 15 ++++++++------- .../common/filesConfigurationService.ts | 16 +++++++++++++--- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts index ddcce4134c0..66940e29b77 100644 --- a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts +++ b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts @@ -80,7 +80,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution if (workingCopyResult?.condition === condition) { if ( workingCopyResult.workingCopy.isDirty() && - this.filesConfigurationService.getAutoSaveMode(workingCopyResult.workingCopy.resource).mode !== AutoSaveMode.OFF + this.filesConfigurationService.getAutoSaveMode(workingCopyResult.workingCopy.resource, workingCopyResult.reason).mode !== AutoSaveMode.OFF ) { this.discardAutoSave(workingCopyResult.workingCopy); @@ -96,7 +96,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution editorResult?.condition === condition && !editorResult.editor.editor.isDisposed() && editorResult.editor.editor.isDirty() && - this.filesConfigurationService.getAutoSaveMode(editorResult.editor.editor).mode !== AutoSaveMode.OFF + this.filesConfigurationService.getAutoSaveMode(editorResult.editor.editor, editorResult.reason).mode !== AutoSaveMode.OFF ) { this.waitingOnConditionAutoSaveEditors.delete(resource); @@ -151,7 +151,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution return; // no auto save for non-dirty, readonly or untitled editors } - const autoSaveMode = this.filesConfigurationService.getAutoSaveMode(editorIdentifier.editor); + const autoSaveMode = this.filesConfigurationService.getAutoSaveMode(editorIdentifier.editor, reason); if (autoSaveMode.mode !== AutoSaveMode.OFF) { // Determine if we need to save all. In case of a window focus change we also save if // auto save mode is configured to be ON_FOCUS_CHANGE (editor focus change) @@ -198,7 +198,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution continue; // we never auto save untitled working copies } - const autoSaveMode = this.filesConfigurationService.getAutoSaveMode(workingCopy.resource); + const autoSaveMode = this.filesConfigurationService.getAutoSaveMode(workingCopy.resource, reason); if (autoSaveMode.mode !== AutoSaveMode.OFF) { workingCopy.save({ reason }); } else if (autoSaveMode.reason === AutoSaveDisabledReason.ERRORS || autoSaveMode.reason === AutoSaveDisabledReason.DISABLED) { @@ -257,12 +257,13 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution // Save if dirty and unless prevented by other conditions such as error markers if (workingCopy.isDirty()) { - const autoSaveMode = this.filesConfigurationService.getAutoSaveMode(workingCopy.resource); + const reason = SaveReason.AUTO; + const autoSaveMode = this.filesConfigurationService.getAutoSaveMode(workingCopy.resource, reason); if (autoSaveMode.mode !== AutoSaveMode.OFF) { this.logService.trace(`[editor auto save] running auto save`, workingCopy.resource.toString(), workingCopy.typeId); - workingCopy.save({ reason: SaveReason.AUTO }); + workingCopy.save({ reason }); } else if (autoSaveMode.reason === AutoSaveDisabledReason.ERRORS || autoSaveMode.reason === AutoSaveDisabledReason.DISABLED) { - this.waitingOnConditionAutoSaveWorkingCopies.set(workingCopy.resource, { workingCopy, reason: SaveReason.AUTO, condition: autoSaveMode.reason }); + this.waitingOnConditionAutoSaveWorkingCopies.set(workingCopy.resource, { workingCopy, reason, condition: autoSaveMode.reason }); } } }, autoSaveAfterDelay); diff --git a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts index a2c2076994b..d4c2c6f025e 100644 --- a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts +++ b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts @@ -22,7 +22,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { LRUCache, ResourceMap } from 'vs/base/common/map'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; +import { EditorResourceAccessor, SaveReason, SideBySideEditor } from 'vs/workbench/common/editor'; import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { IStringDictionary } from 'vs/base/common/collections'; @@ -88,7 +88,7 @@ export interface IFilesConfigurationService { hasShortAutoSaveDelay(resourceOrEditor: EditorInput | URI | undefined): boolean; - getAutoSaveMode(resourceOrEditor: EditorInput | URI | undefined): IAutoSaveMode; + getAutoSaveMode(resourceOrEditor: EditorInput | URI | undefined, saveReason?: SaveReason): IAutoSaveMode; toggleAutoSave(): Promise; @@ -384,7 +384,7 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi return false; } - getAutoSaveMode(resourceOrEditor: EditorInput | URI | undefined): IAutoSaveMode { + getAutoSaveMode(resourceOrEditor: EditorInput | URI | undefined, saveReason?: SaveReason): IAutoSaveMode { const resource = this.toResource(resourceOrEditor); if (resource && this.autoSaveDisabledOverrides.has(resource)) { return { mode: AutoSaveMode.OFF, reason: AutoSaveDisabledReason.DISABLED }; @@ -395,6 +395,16 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi return { mode: AutoSaveMode.OFF, reason: AutoSaveDisabledReason.SETTINGS }; } + if (typeof saveReason === 'number') { + if ( + (autoSaveConfiguration.autoSave === 'afterDelay' && saveReason !== SaveReason.AUTO) || + (autoSaveConfiguration.autoSave === 'onFocusChange' && saveReason !== SaveReason.FOCUS_CHANGE && saveReason !== SaveReason.WINDOW_CHANGE) || + (autoSaveConfiguration.autoSave === 'onWindowChange' && saveReason !== SaveReason.WINDOW_CHANGE) + ) { + return { mode: AutoSaveMode.OFF, reason: AutoSaveDisabledReason.SETTINGS }; + } + } + if (resource) { if (autoSaveConfiguration.autoSaveWorkspaceFilesOnly && autoSaveConfiguration.isOutOfWorkspace) { return { mode: AutoSaveMode.OFF, reason: AutoSaveDisabledReason.OUT_OF_WORKSPACE }; From ad1373ca5a576a0ce3374779266393db655f3caa Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 4 Mar 2024 06:26:29 -0800 Subject: [PATCH 1737/1897] Update xterm --- package.json | 16 +++++------ remote/package.json | 16 +++++------ remote/web/package.json | 14 ++++----- remote/web/yarn.lock | 56 ++++++++++++++++++------------------ remote/yarn.lock | 64 ++++++++++++++++++++--------------------- yarn.lock | 64 ++++++++++++++++++++--------------------- 6 files changed, 115 insertions(+), 115 deletions(-) diff --git a/package.json b/package.json index 04e74dfabb8..3c44945826b 100644 --- a/package.json +++ b/package.json @@ -80,14 +80,14 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-canvas": "0.6.0-beta.33", - "@xterm/addon-image": "0.7.0-beta.31", - "@xterm/addon-search": "0.14.0-beta.33", - "@xterm/addon-serialize": "0.12.0-beta.33", - "@xterm/addon-unicode11": "0.7.0-beta.33", - "@xterm/addon-webgl": "0.17.0-beta.33", - "@xterm/headless": "5.4.0-beta.33", - "@xterm/xterm": "5.4.0-beta.33", + "@xterm/addon-canvas": "0.7.0-beta.3", + "@xterm/addon-image": "0.8.0-beta.3", + "@xterm/addon-search": "0.15.0-beta.3", + "@xterm/addon-serialize": "0.13.0-beta.3", + "@xterm/addon-unicode11": "0.8.0-beta.3", + "@xterm/addon-webgl": "0.18.0-beta.3", + "@xterm/headless": "5.5.0-beta.3", + "@xterm/xterm": "5.5.0-beta.3", "graceful-fs": "4.2.11", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", diff --git a/remote/package.json b/remote/package.json index 1070eac356f..2dcfab99b56 100644 --- a/remote/package.json +++ b/remote/package.json @@ -13,14 +13,14 @@ "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-canvas": "0.6.0-beta.33", - "@xterm/addon-image": "0.7.0-beta.31", - "@xterm/addon-search": "0.14.0-beta.33", - "@xterm/addon-serialize": "0.12.0-beta.33", - "@xterm/addon-unicode11": "0.7.0-beta.33", - "@xterm/addon-webgl": "0.17.0-beta.33", - "@xterm/headless": "5.4.0-beta.33", - "@xterm/xterm": "5.4.0-beta.33", + "@xterm/addon-canvas": "0.7.0-beta.3", + "@xterm/addon-image": "0.8.0-beta.3", + "@xterm/addon-search": "0.15.0-beta.3", + "@xterm/addon-serialize": "0.13.0-beta.3", + "@xterm/addon-unicode11": "0.8.0-beta.3", + "@xterm/addon-webgl": "0.18.0-beta.3", + "@xterm/headless": "5.5.0-beta.3", + "@xterm/xterm": "5.5.0-beta.3", "cookie": "^0.4.0", "graceful-fs": "4.2.11", "http-proxy-agent": "^7.0.0", diff --git a/remote/web/package.json b/remote/web/package.json index 16b4ef21ba4..377df190971 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -7,13 +7,13 @@ "@microsoft/1ds-post-js": "^3.2.13", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-canvas": "0.6.0-beta.33", - "@xterm/addon-image": "0.7.0-beta.31", - "@xterm/addon-search": "0.14.0-beta.33", - "@xterm/addon-serialize": "0.12.0-beta.33", - "@xterm/addon-unicode11": "0.7.0-beta.33", - "@xterm/addon-webgl": "0.17.0-beta.33", - "@xterm/xterm": "5.4.0-beta.33", + "@xterm/addon-canvas": "0.7.0-beta.3", + "@xterm/addon-image": "0.8.0-beta.3", + "@xterm/addon-search": "0.15.0-beta.3", + "@xterm/addon-serialize": "0.13.0-beta.3", + "@xterm/addon-unicode11": "0.8.0-beta.3", + "@xterm/addon-webgl": "0.18.0-beta.3", + "@xterm/xterm": "5.5.0-beta.3", "jschardet": "3.0.0", "tas-client-umd": "0.1.8", "vscode-oniguruma": "1.7.0", diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 7ce1173e61e..b69b9a96e3f 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -48,40 +48,40 @@ resolved "https://registry.yarnpkg.com/@vscode/vscode-languagedetection/-/vscode-languagedetection-1.0.21.tgz#89b48f293f6aa3341bb888c1118d16ff13b032d3" integrity sha512-zSUH9HYCw5qsCtd7b31yqkpaCU6jhtkKLkvOOA8yTrIRfBSOFb8PPhgmMicD7B/m+t4PwOJXzU1XDtrM9Fd3/g== -"@xterm/addon-canvas@0.6.0-beta.33": - version "0.6.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.33.tgz#5fe1547f74fbb9538ccc98c299fb1342bf739fb5" - integrity sha512-SJBoCJf62D315IduJwgHwCS2B0RzTc34GTJC5kNBzn/Y7Jr1IdvTbWwf4epleOZo+NkT0/mhj3OUO6zKeljDKA== +"@xterm/addon-canvas@0.7.0-beta.3": + version "0.7.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.7.0-beta.3.tgz#271054deee3828b38d4ac8abfa5802c19295aaeb" + integrity sha512-pvq1h45Xhi0wAHGlXmy1tK4x/kxDmkSRtHwoCu81fplHgxa2vgIrGSwSKzRWhD3ro6ccDQhFDhpdJUDNVP4Y+w== -"@xterm/addon-image@0.7.0-beta.31": - version "0.7.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.31.tgz#1fc22cfddfc8e0a178324b5945c1882af50afe12" - integrity sha512-Ofm3igHyOATnEbc6QBxWfq2M5dZDLlByMOqzGA/2nOWv0LWtjb1u2DzAcXC3d0GIsGvlqI4342la8BZjWj2ALg== +"@xterm/addon-image@0.8.0-beta.3": + version "0.8.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.8.0-beta.3.tgz#1fe6f872a88f6cf04596f5e8a0166da94c429ef6" + integrity sha512-5FZRF4avxTedw5f41RQ9Z7A31H0YB33tjV5aQAzSlOiwcQr5m5Q8YYWHdj/vdjfW/dbECJJlckLY3VwyNMPQuQ== -"@xterm/addon-search@0.14.0-beta.33": - version "0.14.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.33.tgz#84af247dde45c35e90bd334ecb1caf1d73683a14" - integrity sha512-I88tfCnac5CLmIn6alMCI+bXh3rTq40KOnfPiyM3IyGMSEj56jiL1xU2pctPGX4tD8fr2X22ICHnFzLCREOoEg== +"@xterm/addon-search@0.15.0-beta.3": + version "0.15.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.15.0-beta.3.tgz#f7701e0374805e1abfce167e696f9321020e198a" + integrity sha512-2otjNh5hkSvMvwZ6m9uEijhAmW+XE/xfDawteLLoM0GV8Pmt8C1EUa3/aZF7axKv7U1WmYy0Oh+TJ5mQwcBHHA== -"@xterm/addon-serialize@0.12.0-beta.33": - version "0.12.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.33.tgz#4f6491dab94490bb2dafb310439f6351fdb5d620" - integrity sha512-V4UgqKhvYC+6qMjJsBRAzgnvroPE4Boqs75AVOPlhmAxSTlttuVJQUEwGEd2/kcu8ptEo6CcPsfCFTFQJgFZlw== +"@xterm/addon-serialize@0.13.0-beta.3": + version "0.13.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.13.0-beta.3.tgz#665adc0830a3c2cede399c660121650924907da9" + integrity sha512-88putapu36cKM0DBZpJ0k4Hk09JVF1B3kKtj9utXlOWNsriX5WeUH/yEWr+T8iqsnYcUsROOuC12rtoW92+uvg== -"@xterm/addon-unicode11@0.7.0-beta.33": - version "0.7.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.33.tgz#01bb9ec1ba00d2cfd32c16600bac33c8e239adca" - integrity sha512-vJPiyadR83n0H2OMkZlLS5af5+9o6oavUadDLLV4SlDbf1t3U+mqePYZFvr9wCbZrtEQ/P9SuJ7HehTHLZidwQ== +"@xterm/addon-unicode11@0.8.0-beta.3": + version "0.8.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.8.0-beta.3.tgz#083092a40a7cad8ed03a41f67ad21f33048b6398" + integrity sha512-zPg5ItGawDTSayuxxIxGcLeNYPEq8bpY999/cVjckt02KxD2TJ097URWAnS0Hr7OYO9OxR4NPOOjbSNSw29OFg== -"@xterm/addon-webgl@0.17.0-beta.33": - version "0.17.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.33.tgz#6b859fc2393e483f13cf391b0bd00af1e9086ed0" - integrity sha512-brR0BAvS5I92z5UiFOFPn8w8RboBM5Gzjl/zpVDSY+iVYeqcQy7d5uSt/G6yXWVv8E3NaNWHmwWmB71gj9YvVg== +"@xterm/addon-webgl@0.18.0-beta.3": + version "0.18.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.18.0-beta.3.tgz#c592be94c2230a03cb0c0501a4aafbbeac49e691" + integrity sha512-M36K2QhZl/HKVNRXftxJbn7YMaqWVqWwgW1lxyHefn2uZx1+jfSXM8EQo+PpntPuGJaUWZ3zoLv8TGz9rNJEFg== -"@xterm/xterm@5.4.0-beta.33": - version "5.4.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.33.tgz#9c656a2b1799c9b98349a232b206f6bfff9401ee" - integrity sha512-eeKjd5TpyUqRGmiqFl6PZjIlDhP7eNWU1uD6zHC12CfrarJH7Lc993PQpfnfuL8pe4QJGBMIEBU6VgEQ7LKtBw== +"@xterm/xterm@5.5.0-beta.3": + version "5.5.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.5.0-beta.3.tgz#40b9017cbac37f7f55f227a10e37b3519ed3f39f" + integrity sha512-ukbnGJxJTFVCI6voThi04ePPtJ3NLEQSTRDskxTwgjIxfUw1s/LwGhAG2SZnQcgqtDLXjIXAslrgVRiVBQ3yXg== jschardet@3.0.0: version "3.0.0" diff --git a/remote/yarn.lock b/remote/yarn.lock index 63fb04013f6..b068bf655bb 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -114,45 +114,45 @@ resolved "https://registry.yarnpkg.com/@vscode/windows-registry/-/windows-registry-1.1.0.tgz#03dace7c29c46f658588b9885b9580e453ad21f9" integrity sha512-5AZzuWJpGscyiMOed0IuyEwt6iKmV5Us7zuwCDCFYMIq7tsvooO9BUiciywsvuthGz6UG4LSpeDeCxvgMVhnIw== -"@xterm/addon-canvas@0.6.0-beta.33": - version "0.6.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.33.tgz#5fe1547f74fbb9538ccc98c299fb1342bf739fb5" - integrity sha512-SJBoCJf62D315IduJwgHwCS2B0RzTc34GTJC5kNBzn/Y7Jr1IdvTbWwf4epleOZo+NkT0/mhj3OUO6zKeljDKA== +"@xterm/addon-canvas@0.7.0-beta.3": + version "0.7.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.7.0-beta.3.tgz#271054deee3828b38d4ac8abfa5802c19295aaeb" + integrity sha512-pvq1h45Xhi0wAHGlXmy1tK4x/kxDmkSRtHwoCu81fplHgxa2vgIrGSwSKzRWhD3ro6ccDQhFDhpdJUDNVP4Y+w== -"@xterm/addon-image@0.7.0-beta.31": - version "0.7.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.31.tgz#1fc22cfddfc8e0a178324b5945c1882af50afe12" - integrity sha512-Ofm3igHyOATnEbc6QBxWfq2M5dZDLlByMOqzGA/2nOWv0LWtjb1u2DzAcXC3d0GIsGvlqI4342la8BZjWj2ALg== +"@xterm/addon-image@0.8.0-beta.3": + version "0.8.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.8.0-beta.3.tgz#1fe6f872a88f6cf04596f5e8a0166da94c429ef6" + integrity sha512-5FZRF4avxTedw5f41RQ9Z7A31H0YB33tjV5aQAzSlOiwcQr5m5Q8YYWHdj/vdjfW/dbECJJlckLY3VwyNMPQuQ== -"@xterm/addon-search@0.14.0-beta.33": - version "0.14.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.33.tgz#84af247dde45c35e90bd334ecb1caf1d73683a14" - integrity sha512-I88tfCnac5CLmIn6alMCI+bXh3rTq40KOnfPiyM3IyGMSEj56jiL1xU2pctPGX4tD8fr2X22ICHnFzLCREOoEg== +"@xterm/addon-search@0.15.0-beta.3": + version "0.15.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.15.0-beta.3.tgz#f7701e0374805e1abfce167e696f9321020e198a" + integrity sha512-2otjNh5hkSvMvwZ6m9uEijhAmW+XE/xfDawteLLoM0GV8Pmt8C1EUa3/aZF7axKv7U1WmYy0Oh+TJ5mQwcBHHA== -"@xterm/addon-serialize@0.12.0-beta.33": - version "0.12.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.33.tgz#4f6491dab94490bb2dafb310439f6351fdb5d620" - integrity sha512-V4UgqKhvYC+6qMjJsBRAzgnvroPE4Boqs75AVOPlhmAxSTlttuVJQUEwGEd2/kcu8ptEo6CcPsfCFTFQJgFZlw== +"@xterm/addon-serialize@0.13.0-beta.3": + version "0.13.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.13.0-beta.3.tgz#665adc0830a3c2cede399c660121650924907da9" + integrity sha512-88putapu36cKM0DBZpJ0k4Hk09JVF1B3kKtj9utXlOWNsriX5WeUH/yEWr+T8iqsnYcUsROOuC12rtoW92+uvg== -"@xterm/addon-unicode11@0.7.0-beta.33": - version "0.7.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.33.tgz#01bb9ec1ba00d2cfd32c16600bac33c8e239adca" - integrity sha512-vJPiyadR83n0H2OMkZlLS5af5+9o6oavUadDLLV4SlDbf1t3U+mqePYZFvr9wCbZrtEQ/P9SuJ7HehTHLZidwQ== +"@xterm/addon-unicode11@0.8.0-beta.3": + version "0.8.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.8.0-beta.3.tgz#083092a40a7cad8ed03a41f67ad21f33048b6398" + integrity sha512-zPg5ItGawDTSayuxxIxGcLeNYPEq8bpY999/cVjckt02KxD2TJ097URWAnS0Hr7OYO9OxR4NPOOjbSNSw29OFg== -"@xterm/addon-webgl@0.17.0-beta.33": - version "0.17.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.33.tgz#6b859fc2393e483f13cf391b0bd00af1e9086ed0" - integrity sha512-brR0BAvS5I92z5UiFOFPn8w8RboBM5Gzjl/zpVDSY+iVYeqcQy7d5uSt/G6yXWVv8E3NaNWHmwWmB71gj9YvVg== +"@xterm/addon-webgl@0.18.0-beta.3": + version "0.18.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.18.0-beta.3.tgz#c592be94c2230a03cb0c0501a4aafbbeac49e691" + integrity sha512-M36K2QhZl/HKVNRXftxJbn7YMaqWVqWwgW1lxyHefn2uZx1+jfSXM8EQo+PpntPuGJaUWZ3zoLv8TGz9rNJEFg== -"@xterm/headless@5.4.0-beta.33": - version "5.4.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.33.tgz#8accf68a0e58ba03c8a4627aa108df2d690d8713" - integrity sha512-DlGM2qPdaDmeznUSk9doOQfoi4x8uTTfBysSb+Llxm/SyfxBqnNZEV6N1j494RoBJlawZh2UugmV1itQD+Wlzg== +"@xterm/headless@5.5.0-beta.3": + version "5.5.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.5.0-beta.3.tgz#d58d07b5d5e08987cc0cd5888f28f4750627c286" + integrity sha512-F5AdR4VPBmCQGcc57zGTHTT5JZyAUWpBxxY+vclrH/AVxnf9/5uRcSdCmXc8Y558FtdVynG31k48c6fd9n1vVw== -"@xterm/xterm@5.4.0-beta.33": - version "5.4.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.33.tgz#9c656a2b1799c9b98349a232b206f6bfff9401ee" - integrity sha512-eeKjd5TpyUqRGmiqFl6PZjIlDhP7eNWU1uD6zHC12CfrarJH7Lc993PQpfnfuL8pe4QJGBMIEBU6VgEQ7LKtBw== +"@xterm/xterm@5.5.0-beta.3": + version "5.5.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.5.0-beta.3.tgz#40b9017cbac37f7f55f227a10e37b3519ed3f39f" + integrity sha512-ukbnGJxJTFVCI6voThi04ePPtJ3NLEQSTRDskxTwgjIxfUw1s/LwGhAG2SZnQcgqtDLXjIXAslrgVRiVBQ3yXg== agent-base@^7.0.1, agent-base@^7.0.2, agent-base@^7.1.0: version "7.1.0" diff --git a/yarn.lock b/yarn.lock index 1aa5e794d31..4dac8ed481a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1700,45 +1700,45 @@ resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.1.tgz#34bdc31727a1889198855913db2f270ace6d7bf8" integrity sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw== -"@xterm/addon-canvas@0.6.0-beta.33": - version "0.6.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.33.tgz#5fe1547f74fbb9538ccc98c299fb1342bf739fb5" - integrity sha512-SJBoCJf62D315IduJwgHwCS2B0RzTc34GTJC5kNBzn/Y7Jr1IdvTbWwf4epleOZo+NkT0/mhj3OUO6zKeljDKA== +"@xterm/addon-canvas@0.7.0-beta.3": + version "0.7.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.7.0-beta.3.tgz#271054deee3828b38d4ac8abfa5802c19295aaeb" + integrity sha512-pvq1h45Xhi0wAHGlXmy1tK4x/kxDmkSRtHwoCu81fplHgxa2vgIrGSwSKzRWhD3ro6ccDQhFDhpdJUDNVP4Y+w== -"@xterm/addon-image@0.7.0-beta.31": - version "0.7.0-beta.31" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.31.tgz#1fc22cfddfc8e0a178324b5945c1882af50afe12" - integrity sha512-Ofm3igHyOATnEbc6QBxWfq2M5dZDLlByMOqzGA/2nOWv0LWtjb1u2DzAcXC3d0GIsGvlqI4342la8BZjWj2ALg== +"@xterm/addon-image@0.8.0-beta.3": + version "0.8.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.8.0-beta.3.tgz#1fe6f872a88f6cf04596f5e8a0166da94c429ef6" + integrity sha512-5FZRF4avxTedw5f41RQ9Z7A31H0YB33tjV5aQAzSlOiwcQr5m5Q8YYWHdj/vdjfW/dbECJJlckLY3VwyNMPQuQ== -"@xterm/addon-search@0.14.0-beta.33": - version "0.14.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.33.tgz#84af247dde45c35e90bd334ecb1caf1d73683a14" - integrity sha512-I88tfCnac5CLmIn6alMCI+bXh3rTq40KOnfPiyM3IyGMSEj56jiL1xU2pctPGX4tD8fr2X22ICHnFzLCREOoEg== +"@xterm/addon-search@0.15.0-beta.3": + version "0.15.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.15.0-beta.3.tgz#f7701e0374805e1abfce167e696f9321020e198a" + integrity sha512-2otjNh5hkSvMvwZ6m9uEijhAmW+XE/xfDawteLLoM0GV8Pmt8C1EUa3/aZF7axKv7U1WmYy0Oh+TJ5mQwcBHHA== -"@xterm/addon-serialize@0.12.0-beta.33": - version "0.12.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.33.tgz#4f6491dab94490bb2dafb310439f6351fdb5d620" - integrity sha512-V4UgqKhvYC+6qMjJsBRAzgnvroPE4Boqs75AVOPlhmAxSTlttuVJQUEwGEd2/kcu8ptEo6CcPsfCFTFQJgFZlw== +"@xterm/addon-serialize@0.13.0-beta.3": + version "0.13.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.13.0-beta.3.tgz#665adc0830a3c2cede399c660121650924907da9" + integrity sha512-88putapu36cKM0DBZpJ0k4Hk09JVF1B3kKtj9utXlOWNsriX5WeUH/yEWr+T8iqsnYcUsROOuC12rtoW92+uvg== -"@xterm/addon-unicode11@0.7.0-beta.33": - version "0.7.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.33.tgz#01bb9ec1ba00d2cfd32c16600bac33c8e239adca" - integrity sha512-vJPiyadR83n0H2OMkZlLS5af5+9o6oavUadDLLV4SlDbf1t3U+mqePYZFvr9wCbZrtEQ/P9SuJ7HehTHLZidwQ== +"@xterm/addon-unicode11@0.8.0-beta.3": + version "0.8.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.8.0-beta.3.tgz#083092a40a7cad8ed03a41f67ad21f33048b6398" + integrity sha512-zPg5ItGawDTSayuxxIxGcLeNYPEq8bpY999/cVjckt02KxD2TJ097URWAnS0Hr7OYO9OxR4NPOOjbSNSw29OFg== -"@xterm/addon-webgl@0.17.0-beta.33": - version "0.17.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.33.tgz#6b859fc2393e483f13cf391b0bd00af1e9086ed0" - integrity sha512-brR0BAvS5I92z5UiFOFPn8w8RboBM5Gzjl/zpVDSY+iVYeqcQy7d5uSt/G6yXWVv8E3NaNWHmwWmB71gj9YvVg== +"@xterm/addon-webgl@0.18.0-beta.3": + version "0.18.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.18.0-beta.3.tgz#c592be94c2230a03cb0c0501a4aafbbeac49e691" + integrity sha512-M36K2QhZl/HKVNRXftxJbn7YMaqWVqWwgW1lxyHefn2uZx1+jfSXM8EQo+PpntPuGJaUWZ3zoLv8TGz9rNJEFg== -"@xterm/headless@5.4.0-beta.33": - version "5.4.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.33.tgz#8accf68a0e58ba03c8a4627aa108df2d690d8713" - integrity sha512-DlGM2qPdaDmeznUSk9doOQfoi4x8uTTfBysSb+Llxm/SyfxBqnNZEV6N1j494RoBJlawZh2UugmV1itQD+Wlzg== +"@xterm/headless@5.5.0-beta.3": + version "5.5.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.5.0-beta.3.tgz#d58d07b5d5e08987cc0cd5888f28f4750627c286" + integrity sha512-F5AdR4VPBmCQGcc57zGTHTT5JZyAUWpBxxY+vclrH/AVxnf9/5uRcSdCmXc8Y558FtdVynG31k48c6fd9n1vVw== -"@xterm/xterm@5.4.0-beta.33": - version "5.4.0-beta.33" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.33.tgz#9c656a2b1799c9b98349a232b206f6bfff9401ee" - integrity sha512-eeKjd5TpyUqRGmiqFl6PZjIlDhP7eNWU1uD6zHC12CfrarJH7Lc993PQpfnfuL8pe4QJGBMIEBU6VgEQ7LKtBw== +"@xterm/xterm@5.5.0-beta.3": + version "5.5.0-beta.3" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.5.0-beta.3.tgz#40b9017cbac37f7f55f227a10e37b3519ed3f39f" + integrity sha512-ukbnGJxJTFVCI6voThi04ePPtJ3NLEQSTRDskxTwgjIxfUw1s/LwGhAG2SZnQcgqtDLXjIXAslrgVRiVBQ3yXg== "@xtuc/ieee754@^1.2.0": version "1.2.0" From 4b7845ab8c1fb7810ffa3e39c8bf493e4b12ac4d Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 4 Mar 2024 15:44:41 +0100 Subject: [PATCH 1738/1897] reload file icon theme when language configuration changes (#206794) --- .../workbench/services/themes/browser/workbenchThemeService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index cfb4bd79404..a9cb06c4565 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -116,7 +116,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { @ILogService private readonly logService: ILogService, @IHostColorSchemeService private readonly hostColorService: IHostColorSchemeService, @IUserDataInitializationService private readonly userDataInitializationService: IUserDataInitializationService, - @ILanguageService languageService: ILanguageService + @ILanguageService private readonly languageService: ILanguageService ) { this.container = layoutService.mainContainer; this.settings = new ThemeConfiguration(configurationService); @@ -378,6 +378,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { await this.setProductIconTheme(DEFAULT_PRODUCT_ICON_THEME_ID, 'auto'); } }); + this.languageService.onDidChange(() => this.reloadCurrentFileIconTheme()); return Promise.all([this.getColorThemes(), this.getFileIconThemes(), this.getProductIconThemes()]).then(([ct, fit, pit]) => { updateColorThemeConfigurationSchemas(ct); From 9e8295eafa599dc2f9088cba2ba1b70f6f022e7d Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 4 Mar 2024 15:51:36 +0100 Subject: [PATCH 1739/1897] Properly update comment avatar (#206799) Part of microsoft/vscode-pull-request-github#5762 --- .../contrib/comments/browser/commentNode.ts | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 58e376f9561..95ef19e971e 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -9,7 +9,7 @@ import * as languages from 'vs/editor/common/languages'; import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action, IActionRunner, IAction, Separator, ActionRunner } from 'vs/base/common/actions'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { ILanguageService } from 'vs/editor/common/languages/language'; @@ -60,6 +60,7 @@ class CommentsActionRunner extends ActionRunner { export class CommentNode extends Disposable { private _domNode: HTMLElement; private _body: HTMLElement; + private _avatar: HTMLElement; private _md: HTMLElement | undefined; private _plainText: HTMLElement | undefined; private _clearTimeout: any; @@ -129,12 +130,9 @@ export class CommentNode extends Disposable { this._commentMenus = this.commentService.getCommentMenus(this.owner); this._domNode.tabIndex = -1; - const avatar = dom.append(this._domNode, dom.$('div.avatar-container')); - if (comment.userIconPath) { - const img = dom.append(avatar, dom.$('img.avatar')); - img.src = FileAccess.uriToBrowserUri(URI.revive(comment.userIconPath)).toString(true); - img.onerror = _ => img.remove(); - } + this._avatar = dom.append(this._domNode, dom.$('div.avatar-container')); + this.updateCommentUserIcon(this.comment.userIconPath); + this._commentDetailsContainer = dom.append(this._domNode, dom.$('.review-comment-contents')); this.createHeader(this._commentDetailsContainer); @@ -223,6 +221,15 @@ export class CommentNode extends Disposable { } } + private updateCommentUserIcon(userIconPath: UriComponents | undefined) { + this._avatar.textContent = ''; + if (userIconPath) { + const img = dom.append(this._avatar, dom.$('img.avatar')); + img.src = FileAccess.uriToBrowserUri(URI.revive(userIconPath)).toString(true); + img.onerror = _ => img.remove(); + } + } + public get onDidClick(): Event> { return this._onDidClick.event; } @@ -701,6 +708,10 @@ export class CommentNode extends Disposable { this.updateCommentBody(newComment.body); } + if (this.comment.userIconPath && newComment.userIconPath && (URI.from(this.comment.userIconPath).toString() !== URI.from(newComment.userIconPath).toString())) { + this.updateCommentUserIcon(newComment.userIconPath); + } + const isChangingMode: boolean = newComment.mode !== undefined && newComment.mode !== this.comment.mode; this.comment = newComment; From 0b60cd45e43a39acde273feb3f7bd591fd4d740c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 4 Mar 2024 07:57:47 -0800 Subject: [PATCH 1740/1897] monaco-editor -> monaco-workbench --- .../contrib/inlineChat/browser/inlineChat.css | 169 +++++++++--------- 1 file changed, 85 insertions(+), 84 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css index 60eebc0598d..e1b35651f1c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css @@ -3,15 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-editor .zone-widget.inline-chat-widget { +.monaco-workbench .zone-widget.inline-chat-widget { z-index: 3; } -.monaco-editor .zone-widget-container.inside-selection { +.monaco-workbench .zone-widget-container.inside-selection { background-color: var(--vscode-inlineChat-regionHighlight); } -.inline-chat { +.monaco-workbench .inline-chat { color: inherit; padding: 6px; margin-top: 6px; @@ -23,11 +23,11 @@ /* body */ -.inline-chat .body { +.monaco-workbench .inline-chat .body { display: flex; } -.inline-chat .body .content { +.monaco-workbench .inline-chat .body .content { display: flex; box-sizing: border-box; outline: 1px solid var(--vscode-inlineChatInput-border); @@ -35,11 +35,11 @@ border-radius: 2px; } -.inline-chat .body .content.synthetic-focus { +.monaco-workbench .inline-chat .body .content.synthetic-focus { outline: 1px solid var(--vscode-inlineChatInput-focusBorder); } -.inline-chat .body .content .input { +.monaco-workbench .inline-chat .body .content .input { display: flex; align-items: center; justify-content: space-between; @@ -48,11 +48,11 @@ cursor: text; } -.inline-chat .body .content .input .monaco-editor-background { +.monaco-workbench .inline-chat .body .content .input .monaco-editor-background { background-color: var(--vscode-inlineChatInput-background); } -.inline-chat .body .content .input .editor-placeholder { +.monaco-workbench .inline-chat .body .content .input .editor-placeholder { position: absolute; z-index: 1; color: var(--vscode-inlineChatInput-placeholderForeground); @@ -61,14 +61,15 @@ text-overflow: ellipsis; } -.inline-chat .body .content .input .editor-placeholder.hidden { +.monaco-workbench .inline-chat .body .content .input .editor-placeholder.hidden { display: none; } -.inline-chat .body .content .input .editor-container { +.monaco-workbench .inline-chat .body .content .input .editor-container { vertical-align: middle; } -.inline-chat .body .toolbar { + +.monaco-workbench .inline-chat .body .toolbar { display: flex; flex-direction: column; align-self: stretch; @@ -78,47 +79,47 @@ background: var(--vscode-inlineChatInput-background); } -.inline-chat .body .toolbar .actions-container { +.monaco-workbench .inline-chat .body .toolbar .actions-container { display: flex; flex-direction: row; gap: 4px; } -.inline-chat .body > .widget-toolbar { +.monaco-workbench .inline-chat .body > .widget-toolbar { padding-left: 4px; } /* progress bit */ -.inline-chat .progress { +.monaco-workbench .inline-chat .progress { position: relative; width: calc(100% - 18px); left: 19px; } /* UGLY - fighting against workbench styles */ -.monaco-workbench .part.editor > .content .inline-chat .progress .monaco-progress-container { +.monaco-workbench .part.editor > .content .monaco-editor .inline-chat .progress .monaco-progress-container { top: 0; } /* status */ -.inline-chat .status { +.monaco-workbench .inline-chat .status { margin-top: 4px; display: flex; justify-content: space-between; align-items: center; } -.inline-chat .status.actions { +.monaco-workbench .inline-chat .status.actions { margin-top: 4px; } -.inline-chat .status .actions.hidden { +.monaco-workbench .inline-chat .status .actions.hidden { display: none; } -.inline-chat .status .label { +.monaco-workbench .inline-chat .status .label { overflow: hidden; color: var(--vscode-descriptionForeground); font-size: 11px; @@ -126,60 +127,60 @@ display: inline-flex; } -.inline-chat .status .label.hidden { +.monaco-workbench .inline-chat .status .label.hidden { display: none; } -.inline-chat .status .label.info { +.monaco-workbench .inline-chat .status .label.info { margin-right: auto; padding-left: 2px; } -.inline-chat .status .label.info > .codicon { +.monaco-workbench .inline-chat .status .label.info > .codicon { padding: 0 5px; font-size: 12px; line-height: 18px; } -.inline-chat .status .label.status { +.monaco-workbench .inline-chat .status .label.status { padding-left: 10px; padding-right: 4px; margin-left: auto; } -.inline-chat .status .label .slash-command-pill CODE { +.monaco-workbench .inline-chat .status .label .slash-command-pill CODE { border-radius: 3px; padding: 0 1px; background-color: var(--vscode-chat-slashCommandBackground); color: var(--vscode-chat-slashCommandForeground); } -.inline-chat .detectedIntent { +.monaco-workbench .inline-chat .detectedIntent { color: var(--vscode-descriptionForeground); padding: 5px 0px 5px 5px; } -.inline-chat .detectedIntent.hidden { +.monaco-workbench .inline-chat .detectedIntent.hidden { display: none; } -.inline-chat .detectedIntent .slash-command-pill CODE { +.monaco-workbench .inline-chat .detectedIntent .slash-command-pill CODE { border-radius: 3px; padding: 0 1px; background-color: var(--vscode-chat-slashCommandBackground); color: var(--vscode-chat-slashCommandForeground); } -.inline-chat .detectedIntent .slash-command-pill a { +.monaco-workbench .inline-chat .detectedIntent .slash-command-pill a { color: var(--vscode-textLink-foreground); cursor: pointer; } -/* .inline-chat .markdownMessage .message * { +/* .monaco-editor .inline-chat .markdownMessage .message * { margin: unset; } -.inline-chat .markdownMessage .message code { +.monaco-editor .inline-chat .markdownMessage .message code { font-family: var(--monaco-monospace-font); font-size: 12px; color: var(--vscode-textPreformat-foreground); @@ -189,7 +190,7 @@ } */ -.inline-chat .chatMessage .chatMessageContent .value { +.monaco-workbench .inline-chat .chatMessage .chatMessageContent .value { -webkit-line-clamp: initial; -webkit-box-orient: vertical; overflow: hidden; @@ -198,131 +199,131 @@ user-select: text; } -.inline-chat .chatMessage .chatMessageContent[state="cropped"] .value { +.monaco-workbench .inline-chat .chatMessage .chatMessageContent[state="cropped"] .value { -webkit-line-clamp: var(--vscode-inline-chat-cropped, 3); } -.inline-chat .chatMessage .chatMessageContent[state="expanded"] .value { +.monaco-workbench .inline-chat .chatMessage .chatMessageContent[state="expanded"] .value { -webkit-line-clamp: var(--vscode-inline-chat-expanded, 10); } -.inline-chat .followUps { +.monaco-workbench .inline-chat .followUps { padding: 5px 5px; } -.inline-chat .followUps .interactive-session-followups .monaco-button { +.monaco-workbench .inline-chat .followUps .interactive-session-followups .monaco-button { display: block; color: var(--vscode-textLink-foreground); font-size: 12px; } -.inline-chat .followUps.hidden { +.monaco-workbench .inline-chat .followUps.hidden { display: none; } -.inline-chat .chatMessage { +.monaco-workbench .inline-chat .chatMessage { padding: 8px 3px; } -.inline-chat .chatMessage .chatMessageContent { +.monaco-workbench .inline-chat .chatMessage .chatMessageContent { padding: 2px 2px; } -.inline-chat .chatMessage.hidden { +.monaco-workbench .inline-chat .chatMessage.hidden { display: none; } -.inline-chat .status .label A { +.monaco-workbench .inline-chat .status .label A { color: var(--vscode-textLink-foreground); cursor: pointer; } -.inline-chat .status .label.error { +.monaco-workbench .inline-chat .status .label.error { color: var(--vscode-errorForeground); } -.inline-chat .status .label.warn { +.monaco-workbench .inline-chat .status .label.warn { color: var(--vscode-editorWarning-foreground); } -.inline-chat .status .actions { +.monaco-workbench .inline-chat .status .actions { display: flex; } -.inline-chat .status .actions > .monaco-button, -.inline-chat .status .actions > .monaco-button-dropdown { +.monaco-workbench .inline-chat .status .actions > .monaco-button, +.monaco-workbench .inline-chat .status .actions > .monaco-button-dropdown { margin-right: 6px; } -.inline-chat .status .actions > .monaco-button-dropdown > .monaco-dropdown-button { +.monaco-workbench .inline-chat .status .actions > .monaco-button-dropdown > .monaco-dropdown-button { display: flex; align-items: center; padding: 0 4px; } -.inline-chat .status .actions > .monaco-button.codicon { +.monaco-workbench .inline-chat .status .actions > .monaco-button.codicon { display: flex; } -.inline-chat .status .actions > .monaco-button.codicon::before { +.monaco-workbench .inline-chat .status .actions > .monaco-button.codicon::before { align-self: center; } -.inline-chat .status .actions .monaco-text-button { +.monaco-workbench .inline-chat .status .actions .monaco-text-button { padding: 2px 4px; white-space: nowrap; } -.inline-chat .status .monaco-toolbar .action-item { +.monaco-workbench .inline-chat .status .monaco-toolbar .action-item { padding: 0 2px; } /* TODO@jrieken not needed? */ -.inline-chat .status .monaco-toolbar .action-label.checked { +.monaco-workbench .inline-chat .status .monaco-toolbar .action-label.checked { color: var(--vscode-inputOption-activeForeground); background-color: var(--vscode-inputOption-activeBackground); outline: 1px solid var(--vscode-inputOption-activeBorder); } -.inline-chat .status .monaco-toolbar .action-item.button-item .action-label:is(:hover, :focus) { +.monaco-workbench .inline-chat .status .monaco-toolbar .action-item.button-item .action-label:is(:hover, :focus) { background-color: var(--vscode-button-hoverBackground); } /* preview */ -.inline-chat .preview { +.monaco-workbench .inline-chat .preview { display: none; } -.inline-chat .previewDiff, -.inline-chat .previewCreate { +.monaco-workbench .inline-chat .previewDiff, +.monaco-workbench .inline-chat .previewCreate { display: inherit; border: 1px solid var(--vscode-inlineChat-border); border-radius: 2px; margin: 6px 0px; } -.inline-chat .previewCreateTitle { +.monaco-workbench .inline-chat .previewCreateTitle { padding-top: 6px; } -.inline-chat .diff-review.hidden, -.inline-chat .previewDiff.hidden, -.inline-chat .previewCreate.hidden, -.inline-chat .previewCreateTitle.hidden { +.monaco-workbench .inline-chat .diff-review.hidden, +.monaco-workbench .inline-chat .previewDiff.hidden, +.monaco-workbench .inline-chat .previewCreate.hidden, +.monaco-workbench .inline-chat .previewCreateTitle.hidden { display: none; } -.inline-chat-toolbar { +.monaco-workbench .inline-chat-toolbar { display: flex; } -.inline-chat-toolbar > .monaco-button{ +.monaco-workbench .inline-chat-toolbar > .monaco-button { margin-right: 6px; } -.inline-chat-toolbar .action-label.checked { +.monaco-workbench .inline-chat-toolbar .action-label.checked { color: var(--vscode-inputOption-activeForeground); background-color: var(--vscode-inputOption-activeBackground); outline: 1px solid var(--vscode-inputOption-activeBorder); @@ -330,65 +331,65 @@ /* decoration styles */ -.inline-chat-inserted-range { +.monaco-workbench .inline-chat-inserted-range { background-color: var(--vscode-inlineChatDiff-inserted); } -.inline-chat-inserted-range-linehighlight { +.monaco-workbench .inline-chat-inserted-range-linehighlight { background-color: var(--vscode-diffEditor-insertedLineBackground); } -.inline-chat-original-zone2 { +.monaco-workbench .inline-chat-original-zone2 { background-color: var(--vscode-diffEditor-removedLineBackground); opacity: 0.8; } -.inline-chat-lines-inserted-range { +.monaco-workbench .inline-chat-lines-inserted-range { background-color: var(--vscode-diffEditor-insertedTextBackground); } -.inline-chat-block-selection { +.monaco-workbench .inline-chat-block-selection { background-color: var(--vscode-inlineChat-regionHighlight); } -.inline-chat-slash-command { +.monaco-workbench .inline-chat-slash-command { opacity: 0; } -.inline-chat-slash-command-detail { +.monaco-workbench .inline-chat-slash-command-detail { opacity: 0.5; } /* diff zone */ -.inline-chat-diff-widget .monaco-diff-editor .monaco-editor-background, -.inline-chat-diff-widget .monaco-diff-editor .monaco-editor .margin-view-overlays { +.monaco-workbench .inline-chat-diff-widget .monaco-diff-editor .monaco-editor-background, +.monaco-workbench .inline-chat-diff-widget .monaco-diff-editor .monaco-workbench .margin-view-overlays { background-color: var(--vscode-inlineChat-regionHighlight); } /* create zone */ -.inline-chat-newfile-widget { +.monaco-workbench .inline-chat-newfile-widget { background-color: var(--vscode-inlineChat-regionHighlight); } -.inline-chat-newfile-widget .title { +.monaco-workbench .inline-chat-newfile-widget .title { display: flex; align-items: center; justify-content: space-between; } -.inline-chat-newfile-widget .title .detail { +.monaco-workbench .inline-chat-newfile-widget .title .detail { margin-left: 4px; } -.inline-chat-newfile-widget .buttonbar-widget { +.monaco-workbench .inline-chat-newfile-widget .buttonbar-widget { display: flex; margin-left: auto; margin-right: 8px; } -.inline-chat-newfile-widget .buttonbar-widget > .monaco-button { +.monaco-workbench .inline-chat-newfile-widget .buttonbar-widget > .monaco-button { display: inline-flex; white-space: nowrap; margin-left: 4px; @@ -396,22 +397,22 @@ /* gutter decoration */ -.monaco-editor .glyph-margin-widgets .cgmr.codicon-inline-chat-opaque, -.monaco-editor .glyph-margin-widgets .cgmr.codicon-inline-chat-transparent { +.monaco-workbench .glyph-margin-widgets .cgmr.codicon-inline-chat-opaque, +.monaco-workbench .glyph-margin-widgets .cgmr.codicon-inline-chat-transparent { display: block; cursor: pointer; transition: opacity .2s ease-in-out; } -.monaco-editor .glyph-margin-widgets .cgmr.codicon-inline-chat-opaque { +.monaco-workbench .glyph-margin-widgets .cgmr.codicon-inline-chat-opaque { opacity: 0.5; } -.monaco-editor .glyph-margin-widgets .cgmr.codicon-inline-chat-transparent { +.monaco-workbench .glyph-margin-widgets .cgmr.codicon-inline-chat-transparent { opacity: 0; } -.monaco-editor .glyph-margin-widgets .cgmr.codicon-inline-chat-opaque:hover, -.monaco-editor .glyph-margin-widgets .cgmr.codicon-inline-chat-transparent:hover { +.monaco-workbench .glyph-margin-widgets .cgmr.codicon-inline-chat-opaque:hover, +.monaco-workbench .glyph-margin-widgets .cgmr.codicon-inline-chat-transparent:hover { opacity: 1; } From 6cd902f14117929dd3df0caf3cd23229a7c3de30 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 4 Mar 2024 08:00:57 -0800 Subject: [PATCH 1741/1897] get progress style to work --- src/vs/workbench/contrib/inlineChat/browser/inlineChat.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css index e1b35651f1c..31fe615709b 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css @@ -98,7 +98,7 @@ } /* UGLY - fighting against workbench styles */ -.monaco-workbench .part.editor > .content .monaco-editor .inline-chat .progress .monaco-progress-container { +.monaco-workbench .part.editor > .content .inline-chat .progress .monaco-progress-container { top: 0; } From 0bfc1dbb62d51ec68e1e8d7eae14231d51f59d92 Mon Sep 17 00:00:00 2001 From: Marcus Revaj Date: Mon, 4 Mar 2024 17:05:28 +0100 Subject: [PATCH 1742/1897] # Add partial accept kind to inline completion handle (#202668) * # Add partial accept kind to inline completion handle --------- Co-authored-by: Henning Dieterichs --- src/vs/editor/common/languages.ts | 18 ++++++++++++++- .../common/standalone/standaloneEnums.ts | 9 ++++++++ .../browser/inlineCompletionsModel.ts | 14 +++++++---- .../standalone/browser/standaloneLanguages.ts | 1 + src/vs/monaco.d.ts | 18 ++++++++++++++- .../api/browser/mainThreadLanguageFeatures.ts | 4 ++-- .../workbench/api/common/extHost.api.impl.ts | 1 + .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostLanguageFeatures.ts | 9 ++++---- .../api/common/extHostTypeConverters.ts | 23 +++++++++++++++++++ src/vs/workbench/api/common/extHostTypes.ts | 11 +++++++++ ...e.proposed.inlineCompletionsAdditions.d.ts | 18 +++++++++++++++ 12 files changed, 115 insertions(+), 13 deletions(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 57f11ed3e4e..4d157bf5788 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -547,6 +547,22 @@ export interface CompletionList { duration?: number; } +/** + * Info provided on partial acceptance. + */ +export interface PartialAcceptInfo { + kind: PartialAcceptTriggerKind; +} + +/** + * How a partial acceptance was triggered. + */ +export const enum PartialAcceptTriggerKind { + Word = 0, + Line = 1, + Suggest = 2, +} + /** * How a suggest provider was triggered. */ @@ -718,7 +734,7 @@ export interface InlineCompletionsProvider { @@ -389,10 +389,10 @@ export class InlineCompletionsModel extends Disposable { return m.index + 1; } return text.length; - }); + }, PartialAcceptTriggerKind.Line); } - private async _acceptNext(editor: ICodeEditor, getAcceptUntilIndex: (position: Position, text: string) => number): Promise { + private async _acceptNext(editor: ICodeEditor, getAcceptUntilIndex: (position: Position, text: string) => number, kind: PartialAcceptTriggerKind): Promise { if (editor.getModel() !== this.textModel) { throw new BugIndicatingError(); } @@ -448,6 +448,9 @@ export class InlineCompletionsModel extends Disposable { completion.source.inlineCompletions, completion.sourceInlineCompletion, text.length, + { + kind, + } ); } } finally { @@ -465,6 +468,9 @@ export class InlineCompletionsModel extends Disposable { inlineCompletion.source.inlineCompletions, inlineCompletion.sourceInlineCompletion, itemEdit.text.length, + { + kind: PartialAcceptTriggerKind.Suggest, + } ); } } diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index 8466fbf9f99..5ade938e7c2 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -809,6 +809,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { InlineEditTriggerKind: standaloneEnums.InlineEditTriggerKind, CodeActionTriggerType: standaloneEnums.CodeActionTriggerType, NewSymbolNameTag: standaloneEnums.NewSymbolNameTag, + PartialAcceptTriggerKind: standaloneEnums.PartialAcceptTriggerKind, // classes FoldingRangeKind: languages.FoldingRangeKind, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index c094df337af..3391b58aa2e 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6956,6 +6956,22 @@ declare namespace monaco.languages { dispose?(): void; } + /** + * Info provided on partial acceptance. + */ + export interface PartialAcceptInfo { + kind: PartialAcceptTriggerKind; + } + + /** + * How a partial acceptance was triggered. + */ + export enum PartialAcceptTriggerKind { + Word = 0, + Line = 1, + Suggest = 2 + } + /** * How a suggest provider was triggered. */ @@ -7102,7 +7118,7 @@ declare namespace monaco.languages { /** * Will be called when an item is partially accepted. */ - handlePartialAccept?(completions: T, item: T['items'][number], acceptedCharacters: number): void; + handlePartialAccept?(completions: T, item: T['items'][number], acceptedCharacters: number, info: PartialAcceptInfo): void; /** * Will be called when a completions list is no longer in use and can be garbage-collected. */ diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 80d13f11362..b7ecbfdb9a2 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -601,9 +601,9 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread await this._proxy.$handleInlineCompletionDidShow(handle, completions.pid, item.idx, updatedInsertText); } }, - handlePartialAccept: async (completions, item, acceptedCharacters): Promise => { + handlePartialAccept: async (completions, item, acceptedCharacters, info: languages.PartialAcceptInfo): Promise => { if (supportsHandleEvents) { - await this._proxy.$handleInlineCompletionPartialAccept(handle, completions.pid, item.idx, acceptedCharacters); + await this._proxy.$handleInlineCompletionPartialAccept(handle, completions.pid, item.idx, acceptedCharacters, info); } }, freeInlineCompletions: (completions: IdentifiableInlineCompletions): void => { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 7344da8ee5e..9ac80463738 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1677,6 +1677,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ThreadFocus: extHostTypes.ThreadFocus, RelatedInformationType: extHostTypes.RelatedInformationType, SpeechToTextStatus: extHostTypes.SpeechToTextStatus, + PartialAcceptTriggerKind: extHostTypes.PartialAcceptTriggerKind, KeywordRecognitionStatus: extHostTypes.KeywordRecognitionStatus, ChatResponseMarkdownPart: extHostTypes.ChatResponseMarkdownPart, ChatResponseFileTreePart: extHostTypes.ChatResponseFileTreePart, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index d7e5580443b..575c58a4d24 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2128,7 +2128,7 @@ export interface ExtHostLanguageFeaturesShape { $releaseCompletionItems(handle: number, id: number): void; $provideInlineCompletions(handle: number, resource: UriComponents, position: IPosition, context: languages.InlineCompletionContext, token: CancellationToken): Promise; $handleInlineCompletionDidShow(handle: number, pid: number, idx: number, updatedInsertText: string): void; - $handleInlineCompletionPartialAccept(handle: number, pid: number, idx: number, acceptedCharacters: number): void; + $handleInlineCompletionPartialAccept(handle: number, pid: number, idx: number, acceptedCharacters: number, info: languages.PartialAcceptInfo): void; $freeInlineCompletionsList(handle: number, pid: number): void; $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: languages.SignatureHelpContext, token: CancellationToken): Promise; $releaseSignatureHelp(handle: number, id: number): void; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index d6bc4f793aa..1c532bcc8ee 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1230,7 +1230,7 @@ class InlineCompletionAdapterBase { handleDidShowCompletionItem(pid: number, idx: number, updatedInsertText: string): void { } - handlePartialAccept(pid: number, idx: number, acceptedCharacters: number): void { } + handlePartialAccept(pid: number, idx: number, acceptedCharacters: number, info: languages.PartialAcceptInfo): void { } } class InlineCompletionAdapter extends InlineCompletionAdapterBase { @@ -1345,11 +1345,12 @@ class InlineCompletionAdapter extends InlineCompletionAdapterBase { } } - override handlePartialAccept(pid: number, idx: number, acceptedCharacters: number): void { + override handlePartialAccept(pid: number, idx: number, acceptedCharacters: number, info: languages.PartialAcceptInfo): void { const completionItem = this._references.get(pid)?.items[idx]; if (completionItem) { if (this._provider.handleDidPartiallyAcceptCompletionItem && this._isAdditionsProposedApiEnabled) { this._provider.handleDidPartiallyAcceptCompletionItem(completionItem, acceptedCharacters); + this._provider.handleDidPartiallyAcceptCompletionItem(completionItem, typeConvert.PartialAcceptInfo.to(info)); } } } @@ -2489,9 +2490,9 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF }, undefined, undefined); } - $handleInlineCompletionPartialAccept(handle: number, pid: number, idx: number, acceptedCharacters: number): void { + $handleInlineCompletionPartialAccept(handle: number, pid: number, idx: number, acceptedCharacters: number, info: languages.PartialAcceptInfo): void { this._withAdapter(handle, InlineCompletionAdapterBase, async adapter => { - adapter.handlePartialAccept(pid, idx, acceptedCharacters); + adapter.handlePartialAccept(pid, idx, acceptedCharacters, info); }, undefined, undefined); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 68cbe330492..13077072ff3 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2672,6 +2672,29 @@ export namespace TerminalQuickFix { } } +export namespace PartialAcceptInfo { + export function to(info: languages.PartialAcceptInfo): types.PartialAcceptInfo { + return { + kind: PartialAcceptTriggerKind.to(info.kind), + }; + } +} + +export namespace PartialAcceptTriggerKind { + export function to(kind: languages.PartialAcceptTriggerKind): types.PartialAcceptTriggerKind { + switch (kind) { + case languages.PartialAcceptTriggerKind.Word: + return types.PartialAcceptTriggerKind.Word; + case languages.PartialAcceptTriggerKind.Line: + return types.PartialAcceptTriggerKind.Line; + case languages.PartialAcceptTriggerKind.Suggest: + return types.PartialAcceptTriggerKind.Suggest; + default: + return types.PartialAcceptTriggerKind.Unknown; + } + } +} + export namespace DebugTreeItem { export function from(item: vscode.DebugTreeItem, id: number): IDebugVisualizationTreeItem { return { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 2e1ff171d1f..8660835e2d6 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -1795,6 +1795,17 @@ export class InlineSuggestionList implements vscode.InlineCompletionList { } } +export interface PartialAcceptInfo { + kind: PartialAcceptTriggerKind; +} + +export enum PartialAcceptTriggerKind { + Unknown = 0, + Word = 1, + Line = 2, + Suggest = 3, +} + export enum ViewColumn { Active = -1, Beside = -2, diff --git a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts index 9e3ead9be40..2715014a0a8 100644 --- a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts @@ -53,6 +53,24 @@ declare module 'vscode' { */ // eslint-disable-next-line local/vscode-dts-provider-naming handleDidPartiallyAcceptCompletionItem?(completionItem: InlineCompletionItem, acceptedLength: number): void; + + /** + * Is called when an inline completion item was accepted partially. + * @param info Additional info for the partial accepted trigger. + */ + // eslint-disable-next-line local/vscode-dts-provider-naming + handleDidPartiallyAcceptCompletionItem?(completionItem: InlineCompletionItem, info: PartialAcceptInfo): void; + } + + export interface PartialAcceptInfo { + kind: PartialAcceptTriggerKind; + } + + export enum PartialAcceptTriggerKind { + Unknown = 0, + Word = 1, + Line = 2, + Suggest = 3, } // When finalizing `commands`, make sure to add a corresponding constructor parameter. From 5124bd41330d1dd6bd1a9452048fcddacbd20175 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 4 Mar 2024 17:23:57 +0100 Subject: [PATCH 1743/1897] adding telemetry for when the bulk edit pane has been opened --- .../contrib/bulkEdit/browser/preview/bulkEditPane.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index ba85dee93b2..6f4e6cce624 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -100,6 +100,12 @@ export class BulkEditPane extends ViewPane { this._ctxHasCategories = BulkEditPane.ctxHasCategories.bindTo(contextKeyService); this._ctxGroupByFile = BulkEditPane.ctxGroupByFile.bindTo(contextKeyService); this._ctxHasCheckedChanges = BulkEditPane.ctxHasCheckedChanges.bindTo(contextKeyService); + // telemetry + type BulkEditPaneOpened = { + owner: 'aiday-mar'; + comment: 'Report when the bulk edit pane has been opened'; + }; + this.telemetryService.publicLog2<{}, BulkEditPaneOpened>('views.bulkEditPane'); } override dispose(): void { From eb6000d5249366a60d7547be2f3fc6f2276ae0c4 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 28 Feb 2024 16:49:43 +0100 Subject: [PATCH 1744/1897] breakpoints: Tweak tooltip string --- .../contrib/debug/browser/breakpointEditorContribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 1c36e83c55c..710d0d1d783 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -54,7 +54,7 @@ const breakpointHelperDecoration: IModelDecorationOptions = { description: 'breakpoint-helper-decoration', glyphMarginClassName: ThemeIcon.asClassName(icons.debugBreakpointHint), glyphMargin: { position: GlyphMarginLane.Right }, - glyphMarginHoverMessage: new MarkdownString().appendText(nls.localize('breakpointHelper', "Click to add a breakpoint.")), + glyphMarginHoverMessage: new MarkdownString().appendText(nls.localize('breakpointHelper', "Click to add a breakpoint")), stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges }; From 0f5be7c29fc276f70cfd4cf4140079aaef649823 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 4 Mar 2024 17:35:10 +0100 Subject: [PATCH 1745/1897] Decorations - use the color of the first decoration that has the color set (#206813) --- .../services/decorations/browser/decorationsService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index 31f1057999d..edf7fb27300 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -87,7 +87,7 @@ class DecorationRule { private _appendForMany(data: IDecorationData[], element: HTMLStyleElement): void { // label - const { color } = data[0]; + const { color } = data.find(d => !!d.color) ?? data[0]; createCSSRule(`.${this.itemColorClassName}`, `color: ${getColor(color)};`, element); // badge or icon From 3e670468cacc479c19c81a72f6c88e14a67aa7a9 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 4 Mar 2024 17:36:13 +0100 Subject: [PATCH 1746/1897] Bump distro (#206812) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3c44945826b..44e075ebd81 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.88.0", - "distro": "c628288b553c076290c08f74482e6b71c337e4a8", + "distro": "aa80bd2351f35faf630bcb132a08b10281943951", "author": { "name": "Microsoft Corporation" }, From f02db619a48dcb301f263f19faed87e8d92ed8f7 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 4 Mar 2024 17:56:08 +0100 Subject: [PATCH 1747/1897] Added footer area to composite parts and enabled activity bar positioning at the bottom --- src/vs/workbench/browser/layout.ts | 10 +- src/vs/workbench/browser/media/part.css | 9 +- src/vs/workbench/browser/part.ts | 31 ++++- .../parts/activitybar/activitybarPart.ts | 32 ++++- .../parts/auxiliarybar/auxiliaryBarPart.ts | 32 ++++- .../workbench/browser/parts/compositePart.ts | 8 ++ .../browser/parts/media/compositepart.css | 1 + .../browser/parts/media/paneCompositePart.css | 116 +++++++++++----- .../browser/parts/paneCompositePart.ts | 126 +++++++++++++++--- .../browser/parts/panel/panelPart.ts | 6 +- .../browser/parts/sidebar/sidebarPart.ts | 19 ++- .../browser/parts/titlebar/titlebarActions.ts | 3 +- .../browser/parts/titlebar/titlebarPart.ts | 3 +- .../browser/workbench.contribution.ts | 3 +- .../services/layout/browser/layoutService.ts | 4 +- 15 files changed, 329 insertions(+), 74 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 797a1713dd8..4eac33fbb1a 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -359,9 +359,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE, ].some(setting => e.affectsConfiguration(setting))) { // Show Custom TitleBar if actions moved to the titlebar - const activityBarMovedToTop = e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION) && this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP; const editorActionsMovedToTitlebar = e.affectsConfiguration(LayoutSettings.EDITOR_ACTIONS_LOCATION) && this.configurationService.getValue(LayoutSettings.EDITOR_ACTIONS_LOCATION) === EditorActionsLocation.TITLEBAR; - if (activityBarMovedToTop || editorActionsMovedToTitlebar) { + + let activityBarMovedToTopOrBottom = false; + if (e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION)) { + const activityBarPosition = this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION); + activityBarMovedToTopOrBottom = activityBarPosition === ActivityBarPosition.TOP || activityBarPosition === ActivityBarPosition.BOTTOM; + } + + if (activityBarMovedToTopOrBottom || editorActionsMovedToTitlebar) { if (this.configurationService.getValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY) === CustomTitleBarVisibility.NEVER) { this.configurationService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.AUTO); } diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css index 17182b6fa91..c9e200132b9 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -22,18 +22,21 @@ z-index: 12; } -.monaco-workbench .part > .title { +.monaco-workbench .part > .title, +.monaco-workbench .part > .footer { display: none; /* Parts have to opt in to show title area */ } -.monaco-workbench .part > .title { +.monaco-workbench .part > .title, +.monaco-workbench .part > .footer { height: 35px; display: flex; box-sizing: border-box; overflow: hidden; } -.monaco-workbench .part > .title { +.monaco-workbench .part > .title, +.monaco-workbench .part > .footer { padding-left: 8px; padding-right: 8px; } diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index 7ae271c65cc..0fe2060e6a4 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -16,12 +16,14 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; export interface IPartOptions { readonly hasTitle?: boolean; + readonly hasFooter?: () => boolean; readonly borderWidth?: () => number; } export interface ILayoutContentResult { readonly titleSize: IDimension; readonly contentSize: IDimension; + readonly footerSize: IDimension; } /** @@ -39,6 +41,7 @@ export abstract class Part extends Component implements ISerializableView { private parent: HTMLElement | undefined; private titleArea: HTMLElement | undefined; private contentArea: HTMLElement | undefined; + private footerArea: HTMLElement | undefined; private partLayout: PartLayout | undefined; constructor( @@ -75,6 +78,7 @@ export abstract class Part extends Component implements ISerializableView { this.parent = parent; this.titleArea = this.createTitleArea(parent, options); this.contentArea = this.createContentArea(parent, options); + this.footerArea = this.createFooterArea(parent, options); this.partLayout = new PartLayout(this.options, this.contentArea); @@ -116,6 +120,20 @@ export abstract class Part extends Component implements ISerializableView { return this.contentArea; } + /** + * Subclasses override to provide a footer area implementation. + */ + protected createFooterArea(parent: HTMLElement, options?: object): HTMLElement | undefined { + return undefined; + } + + /** + * Returns the footer area container. + */ + protected getFooterArea(): HTMLElement | undefined { + return this.footerArea; + } + /** * Layout title and content area in the given dimension. */ @@ -153,6 +171,7 @@ export abstract class Part extends Component implements ISerializableView { class PartLayout { private static readonly TITLE_HEIGHT = 35; + private static readonly FOOTER_HEIGHT = 35; constructor(private options: IPartOptions, private contentArea: HTMLElement | undefined) { } @@ -166,20 +185,28 @@ class PartLayout { titleSize = Dimension.None; } + // Footer Size: Width (Fill), Height (Variable) + let footerSize: Dimension; + if (this.options.hasFooter?.()) { + footerSize = new Dimension(width, Math.min(height, PartLayout.FOOTER_HEIGHT)); + } else { + footerSize = Dimension.None; + } + let contentWidth = width; if (this.options && typeof this.options.borderWidth === 'function') { contentWidth -= this.options.borderWidth(); // adjust for border size } // Content Size: Width (Fill), Height (Variable) - const contentSize = new Dimension(contentWidth, height - titleSize.height); + const contentSize = new Dimension(contentWidth, height - titleSize.height - footerSize.height); // Content if (this.contentArea) { size(this.contentArea, contentSize.width, contentSize.height); } - return { titleSize, contentSize }; + return { titleSize, contentSize, footerSize }; } } diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 2e2047e8f1d..2fc1c87d42f 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -388,7 +388,7 @@ registerAction2(class extends Action2 { toggled: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.SIDE), menu: [{ id: MenuId.ActivityBarPositionMenu, - order: 1 + order: 2 }, { id: MenuId.CommandPalette, when: ContextKeyExpr.notEquals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.SIDE), @@ -414,7 +414,7 @@ registerAction2(class extends Action2 { toggled: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP), menu: [{ id: MenuId.ActivityBarPositionMenu, - order: 2 + order: 1 }, { id: MenuId.CommandPalette, when: ContextKeyExpr.notEquals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP), @@ -427,6 +427,32 @@ registerAction2(class extends Action2 { } }); +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.activityBarLocation.bottom', + title: { + ...localize2('positionActivityBarBottom', 'Move Activity Bar to Bottom'), + mnemonicTitle: localize({ key: 'miBottomActivityBar', comment: ['&& denotes a mnemonic'] }, "&&Bottom"), + }, + shortTitle: localize('bottom', "Bottom"), + category: Categories.View, + toggled: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.BOTTOM), + menu: [{ + id: MenuId.ActivityBarPositionMenu, + order: 3 + }, { + id: MenuId.CommandPalette, + when: ContextKeyExpr.notEquals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.BOTTOM), + }] + }); + } + run(accessor: ServicesAccessor): void { + const configurationService = accessor.get(IConfigurationService); + configurationService.updateValue(LayoutSettings.ACTIVITY_BAR_LOCATION, ActivityBarPosition.BOTTOM); + } +}); + registerAction2(class extends Action2 { constructor() { super({ @@ -440,7 +466,7 @@ registerAction2(class extends Action2 { toggled: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.HIDDEN), menu: [{ id: MenuId.ActivityBarPositionMenu, - order: 3 + order: 4 }, { id: MenuId.CommandPalette, when: ContextKeyExpr.notEquals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.HIDDEN), diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts index 5ad1c648d6f..777b1586efe 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts @@ -17,7 +17,7 @@ import { ActiveAuxiliaryContext, AuxiliaryBarFocusContext } from 'vs/workbench/c import { ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_DRAG_AND_DROP_BORDER, PANEL_INACTIVE_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_BORDER, SIDE_BAR_FOREGROUND } from 'vs/workbench/common/theme'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; +import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { IAction, Separator, toAction } from 'vs/base/common/actions'; import { ToggleAuxiliaryBarAction } from 'vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions'; @@ -29,6 +29,7 @@ import { AbstractPaneCompositePart } from 'vs/workbench/browser/parts/paneCompos import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { IPaneCompositeBarOptions } from 'vs/workbench/browser/parts/paneCompositeBar'; import { IMenuService } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class AuxiliaryBarPart extends AbstractPaneCompositePart { @@ -79,11 +80,13 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { @IExtensionService extensionService: IExtensionService, @ICommandService private commandService: ICommandService, @IMenuService menuService: IMenuService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super( Parts.AUXILIARYBAR_PART, { hasTitle: true, + hasFooter: () => this.shouldShowFooterCompositeBar(), borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0, }, AuxiliaryBarPart.activePanelSettingsKey, @@ -104,6 +107,23 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { extensionService, menuService, ); + + this._register(configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION)) { + this.onDidChangeActivityBarLocation(); + } + })); + } + + private onDidChangeActivityBarLocation(): void { + this.removeCompositeBar(); + this.updateTitleArea(); + this.updateFooterArea(); + + const id = this.getActiveComposite()?.getId(); + if (id) { + this.onTitleAreaUpdate(id); + } } override updateStyles(): void { @@ -136,7 +156,7 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { orientation: ActionsOrientation.HORIZONTAL, recomputeSizes: true, activityHoverOptions: { - position: () => HoverPosition.BELOW, + position: () => this.shouldShowFooterCompositeBar() ? HoverPosition.ABOVE : HoverPosition.BELOW, }, fillExtraContextMenuActions: actions => this.fillExtraContextMenuActions(actions), compositeSize: 0, @@ -170,8 +190,12 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { ]); } - protected shouldShowCompositeBar(): boolean { - return true; + protected shouldShowTitleCompositeBar(): boolean { + return !this.shouldShowFooterCompositeBar(); + } + + protected shouldShowFooterCompositeBar(): boolean { + return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.BOTTOM; } override toJSON(): object { diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index f4a0bb4fe28..69dc973cad1 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -438,6 +438,14 @@ export abstract class CompositePart extends Part { }; } + protected override createFooterArea(parent: HTMLElement): HTMLElement { + const footerArea = append(parent, $('.composite')); + footerArea.classList.add('footer'); + + hide(footerArea); + return footerArea; + } + override updateStyles(): void { super.updateStyles(); diff --git a/src/vs/workbench/browser/parts/media/compositepart.css b/src/vs/workbench/browser/parts/media/compositepart.css index fde7cdb706c..9dc8e5b560f 100644 --- a/src/vs/workbench/browser/parts/media/compositepart.css +++ b/src/vs/workbench/browser/parts/media/compositepart.css @@ -7,6 +7,7 @@ height: 100%; } +.monaco-workbench .part > .composite.footer, .monaco-workbench .part > .composite.title { display: flex; } diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index 9250cd44de1..b35ecf91e49 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -20,11 +20,17 @@ display: none; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container { +.monaco-workbench .pane-composite-part > .footer{ + border-top: 1px solid var(--vscode-sideBarSectionHeader-border); +} + +.monaco-workbench .pane-composite-part > .title > .composite-bar-container, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container { display: flex; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-label.codicon-more { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-label.codicon-more, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-label.codicon-more { display: flex; align-items: center; justify-content: center; @@ -33,12 +39,14 @@ color: inherit !important; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar { line-height: 27px; /* matches panel titles in settings */ height: 35px; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item { text-transform: uppercase; padding-left: 10px; padding-right: 10px; @@ -48,22 +56,27 @@ display: flex; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item.icon { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item.icon, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item.icon { height: 24px; padding: 0 5px; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container >.composite-bar .monaco-action-bar .action-label.codicon { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container >.composite-bar .monaco-action-bar .action-label.codicon, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container >.composite-bar .monaco-action-bar .action-label.codicon { font-size: 18px; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label:not(.codicon) { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label:not(.codicon), +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label:not(.codicon) { width: 16px; height: 16px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before, -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after { content: ''; width: 2px; height: 24px; @@ -77,26 +90,33 @@ } .monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::before, -.monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::after { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::after, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::before, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::after { display: block; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before { left: 1px; margin-left: -2px; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after { right: 1px; margin-right: -2px; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:first-of-type::before { + +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:first-of-type::before, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:first-of-type::before { left: 2px; margin-left: -2px; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type::after { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type::after, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type::after { right: 2px; margin-right: -2px; } @@ -104,7 +124,11 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right::before, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::after, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right::after { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right::after, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right::before, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::after, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right::after { transition-delay: 0s; } @@ -112,39 +136,52 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type.right::after, .monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over-head > .composite-bar > .monaco-action-bar .action-item:first-of-type::before, -.monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over-tail > .composite-bar > .monaco-action-bar .action-item:last-of-type::after { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over-tail > .composite-bar > .monaco-action-bar .action-item:last-of-type::after, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right + .action-item::before, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type.right::after, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container.dragged-over-head > .composite-bar > .monaco-action-bar .action-item:first-of-type::before, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container.dragged-over-tail > .composite-bar > .monaco-action-bar .action-item:last-of-type::after { opacity: 1; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label { margin-right: 0; padding: 2px; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label { border-radius: 0; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.icon) .action-label, -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label.codicon { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label.codicon, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.icon) .action-label, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label.codicon { background: none !important; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label { margin-right: 0; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .badge { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .badge, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .badge { margin-left: 8px; display: flex; align-items: center; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge { margin-left: 0px; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .badge .badge-content { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .badge .badge-content, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .badge .badge-content { padding: 3px 5px; border-radius: 11px; font-size: 11px; @@ -158,7 +195,8 @@ position: relative; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact { position: absolute; top: 0; bottom: 0; @@ -170,7 +208,8 @@ z-index: 2; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact .badge-content { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact .badge-content, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact .badge-content { position: absolute; top: 11px; right: 0px; @@ -184,7 +223,8 @@ text-align: center; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact.progress-badge .badge-content::before { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact.progress-badge .badge-content::before, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact.progress-badge .badge-content::before { mask-size: 11px; -webkit-mask-size: 11px; top: 3px; @@ -192,7 +232,8 @@ } /* active item indicator */ -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator { position: absolute; z-index: 1; bottom: 0; @@ -201,20 +242,24 @@ height: 100%; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator { top: -4px; left: 10px; width: calc(100% - 20px); } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .active-item-indicator { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .active-item-indicator, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .active-item-indicator { top: 1px; left: 2px; width: calc(100% - 4px); } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .active-item-indicator:before, -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .active-item-indicator:before, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { content: ""; position: absolute; z-index: 1; @@ -225,20 +270,25 @@ border-top-style: solid; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.clicked:not(.checked):focus .active-item-indicator:before { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.clicked:not(.checked):focus .active-item-indicator:before, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.clicked:not(.checked):focus .active-item-indicator:before { border-top-color: transparent !important; /* hides border on clicked state */ } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { border-top-color: var(--vscode-focusBorder) !important; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label { outline: var(--vscode-contrastActiveBorder, unset) solid 1px !important; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label, +.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label { outline: var(--vscode-contrastActiveBorder, unset) dashed 1px !important; } diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index 3a1cb5382e4..8010742b79a 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -15,7 +15,7 @@ import { IView } from 'vs/base/browser/ui/grid/grid'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { CompositePart, ICompositeTitleLabel } from 'vs/workbench/browser/parts/compositePart'; import { IPaneCompositeBarOptions, PaneCompositeBar } from 'vs/workbench/browser/parts/paneCompositeBar'; -import { Dimension, EventHelper, trackFocus, $, addDisposableListener, EventType, prepend, getWindow } from 'vs/base/browser/dom'; +import { Dimension, EventHelper, trackFocus, $, addDisposableListener, EventType, prepend, getWindow, hide, show, IDomPosition } from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -108,8 +108,12 @@ export abstract class AbstractPaneCompositePart extends CompositePart()); + private titleCompositeBarVisible: boolean = false; + private footerCompositeBarVisible: boolean = false; private emptyPaneMessageElement: HTMLElement | undefined; private globalToolBar: ToolBar | undefined; @@ -117,6 +121,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart { + this.onFooterAreaContextMenu(new StandardMouseEvent(getWindow(this.footerContainer), e)); + })); + this.footerDispoables.add(Gesture.addTarget(this.footerContainer)); + this.footerDispoables.add(addDisposableListener(this.footerContainer, GestureEventType.Contextmenu, e => { + this.onFooterAreaContextMenu(new StandardMouseEvent(getWindow(this.footerContainer), e)); + })); + } else { + hide(this.footerContainer); + + // Dispose any footer area listener + this.footerDispoables.clear(); + } + + if (this.contentDimension && this.contentPosition) { + this.layout(this.contentDimension.width, this.contentDimension.height, this.contentPosition.top, this.contentPosition.left); + } + } + + protected createCompositeBar(): PaneCompositeBar { return this.instantiationService.createInstance(PaneCompositeBar, this.getCompositeBarOptions(), this.partId, this); } @@ -444,6 +518,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart event, - getActions: () => actions, - skipTelemetry: true - }); - } + if (this.shouldShowTitleCompositeBar()) { + return this.onCompositeBarContextMenu(event); } else { const activePaneComposite = this.getActivePaneComposite() as PaneComposite; const activePaneCompositeActions = activePaneComposite ? activePaneComposite.getContextMenuActions() : []; @@ -512,6 +580,25 @@ export abstract class AbstractPaneCompositePart extends CompositePart event, + getActions: () => actions, + skipTelemetry: true + }); + } + } + } + protected getViewsSubmenuAction(): SubmenuAction | undefined { const viewPaneContainer = (this.getActivePaneComposite() as PaneComposite)?.getViewPaneContainer(); if (viewPaneContainer) { @@ -527,7 +614,8 @@ export abstract class AbstractPaneCompositePart extends CompositePart (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0 }, + { hasTitle: true, hasFooter: () => this.shouldShowFooterCompositeBar(), borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0 }, SidebarPart.activeViewletSettingsKey, ActiveViewletContext.bindTo(contextKeyService), SidebarFocusContext.bindTo(contextKeyService), @@ -112,7 +112,10 @@ export class SidebarPart extends AbstractPaneCompositePart { } private onDidChangeActivityBarLocation(): void { + this.removeCompositeBar(); this.updateTitleArea(); + this.updateFooterArea(); + const id = this.getActiveComposite()?.getId(); if (id) { this.onTitleAreaUpdate(id); @@ -153,7 +156,7 @@ export class SidebarPart extends AbstractPaneCompositePart { return this.layoutService.getSideBarPosition() === SideBarPosition.LEFT ? AnchorAlignment.LEFT : AnchorAlignment.RIGHT; } - protected override createCompisteBar(): ActivityBarCompositeBar { + protected override createCompositeBar(): ActivityBarCompositeBar { return this.instantiationService.createInstance(ActivityBarCompositeBar, this.getCompositeBarOptions(), this.partId, this, false); } @@ -167,7 +170,7 @@ export class SidebarPart extends AbstractPaneCompositePart { orientation: ActionsOrientation.HORIZONTAL, recomputeSizes: true, activityHoverOptions: { - position: () => HoverPosition.BELOW, + position: () => this.shouldShowFooterCompositeBar() ? HoverPosition.ABOVE : HoverPosition.BELOW, }, fillExtraContextMenuActions: actions => { const viewsSubmenuAction = this.getViewsSubmenuAction(); @@ -194,9 +197,18 @@ export class SidebarPart extends AbstractPaneCompositePart { } protected shouldShowCompositeBar(): boolean { + const activityBarPosition = this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION); + return activityBarPosition === ActivityBarPosition.TOP || activityBarPosition === ActivityBarPosition.BOTTOM; + } + + protected shouldShowTitleCompositeBar(): boolean { return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP; } + protected shouldShowFooterCompositeBar(): boolean { + return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.BOTTOM; + } + private shouldShowActivityBar(): boolean { if (this.shouldShowCompositeBar()) { return false; @@ -215,6 +227,7 @@ export class SidebarPart extends AbstractPaneCompositePart { const activityBarPosition = this.storageService.get(LayoutSettings.ACTIVITY_BAR_LOCATION, StorageScope.PROFILE); switch (activityBarPosition) { case ActivityBarPosition.SIDE: return ActivityBarPosition.SIDE; + case ActivityBarPosition.BOTTOM: return ActivityBarPosition.BOTTOM; default: return ActivityBarPosition.TOP; } } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index 8b9ec83840d..632bf55e2e2 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -116,7 +116,8 @@ class ToggleCustomTitleBar extends Action2 { ContextKeyExpr.equals('config.workbench.layoutControl.enabled', false), ContextKeyExpr.equals('config.window.commandCenter', false), ContextKeyExpr.notEquals('config.workbench.editor.editorActionsLocation', 'titleBar'), - ContextKeyExpr.notEquals('config.workbench.activityBar.location', 'top') + ContextKeyExpr.notEquals('config.workbench.activityBar.location', 'top'), + ContextKeyExpr.notEquals('config.workbench.activityBar.location', 'bottom') )?.negate() ), IsMainWindowFullscreenContext diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 548f4d36caf..f07a3ef9858 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -728,7 +728,8 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } private get activityActionsEnabled(): boolean { - return !this.isAuxiliary && this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP; + const activityBarPosition = this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION); + return !this.isAuxiliary && (activityBarPosition === ActivityBarPosition.TOP || activityBarPosition === ActivityBarPosition.BOTTOM); } get hasZoomableElements(): boolean { diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index b715436c0d4..fc4af190dca 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -497,12 +497,13 @@ const registry = Registry.as(ConfigurationExtensions.Con }, [LayoutSettings.ACTIVITY_BAR_LOCATION]: { 'type': 'string', - 'enum': ['side', 'top', 'hidden'], + 'enum': ['side', 'top', 'bottom', 'hidden'], 'default': 'side', 'markdownDescription': localize({ comment: ['This is the description for a setting'], key: 'activityBarLocation' }, "Controls the location of the Activity Bar. It can either show to the `side` or `top` of the Primary Side Bar or `hidden`."), 'enumDescriptions': [ localize('workbench.activityBar.location.side', "Show the Activity Bar to the side of the Primary Side Bar."), localize('workbench.activityBar.location.top', "Show the Activity Bar on top of the Primary Side Bar."), + localize('workbench.activityBar.location.bottom', "Show the Activity Bar at the bottom of the Primary Side Bar."), localize('workbench.activityBar.location.hide', "Hide the Activity Bar.") ], }, diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 0a397839220..b25b1a66c14 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -50,6 +50,7 @@ export const enum LayoutSettings { export const enum ActivityBarPosition { SIDE = 'side', TOP = 'top', + BOTTOM = 'bottom', HIDDEN = 'hidden' } @@ -371,7 +372,8 @@ function isTitleBarEmpty(configurationService: IConfigurationService): boolean { } // with the activity bar on top, we should always show - if (configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP) { + const activityBarPosition = configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION); + if (activityBarPosition === ActivityBarPosition.TOP || activityBarPosition === ActivityBarPosition.BOTTOM) { return false; } From 052fbcb851edf39fb74b7d8bea5d818ba81068cb Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:11:32 +0100 Subject: [PATCH 1748/1897] Git - re-enable incoming files decoration (#206815) --- extensions/git/src/decorationProvider.ts | 165 +++++++++++------------ 1 file changed, 80 insertions(+), 85 deletions(-) diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index 5167b1eb95e..3aae16f6baf 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { window, workspace, Uri, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, ThemeColor } from 'vscode'; +import { window, workspace, Uri, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, ThemeColor, l10n } from 'vscode'; import * as path from 'path'; import { Repository, GitResourceGroup } from './repository'; import { Model } from './model'; import { debounce } from './decorators'; import { filterEvent, dispose, anyEvent, fireEvent, PromiseSource, combinedDisposable } from './util'; -import { GitErrorCodes, Status } from './api/git'; +import { Change, GitErrorCodes, Status } from './api/git'; class GitIgnoreDecorationProvider implements FileDecorationProvider { @@ -153,100 +153,95 @@ class GitDecorationProvider implements FileDecorationProvider { } } -// class GitIncomingChangesFileDecorationProvider implements FileDecorationProvider { +class GitIncomingChangesFileDecorationProvider implements FileDecorationProvider { -// private readonly _onDidChangeDecorations = new EventEmitter(); -// readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; + private readonly _onDidChangeDecorations = new EventEmitter(); + readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; -// private decorations = new Map(); -// private readonly disposables: Disposable[] = []; + private decorations = new Map(); + private readonly disposables: Disposable[] = []; -// constructor(private readonly repository: Repository) { -// this.disposables.push(window.registerFileDecorationProvider(this)); -// repository.historyProvider.onDidChangeCurrentHistoryItemGroup(this.onDidChangeCurrentHistoryItemGroup, this, this.disposables); -// } + constructor(private readonly repository: Repository) { + this.disposables.push(window.registerFileDecorationProvider(this)); + repository.historyProvider.onDidChangeCurrentHistoryItemGroup(this.onDidChangeCurrentHistoryItemGroup, this, this.disposables); + } -// private async onDidChangeCurrentHistoryItemGroup(): Promise { -// const newDecorations = new Map(); -// await this.collectIncomingChangesFileDecorations(newDecorations); -// const uris = new Set([...this.decorations.keys()].concat([...newDecorations.keys()])); + private async onDidChangeCurrentHistoryItemGroup(): Promise { + const newDecorations = new Map(); + await this.collectIncomingChangesFileDecorations(newDecorations); + const uris = new Set([...this.decorations.keys()].concat([...newDecorations.keys()])); -// this.decorations = newDecorations; -// this._onDidChangeDecorations.fire([...uris.values()].map(value => Uri.parse(value, true))); -// } + this.decorations = newDecorations; + this._onDidChangeDecorations.fire([...uris.values()].map(value => Uri.parse(value, true))); + } -// private async collectIncomingChangesFileDecorations(bucket: Map): Promise { -// for (const change of await this.getIncomingChanges()) { -// switch (change.status) { -// case Status.INDEX_ADDED: -// bucket.set(change.uri.toString(), { -// badge: '↓A', -// color: new ThemeColor('gitDecoration.incomingAddedForegroundColor'), -// tooltip: l10n.t('Incoming Changes (added)'), -// }); -// break; -// case Status.DELETED: -// bucket.set(change.uri.toString(), { -// badge: '↓D', -// color: new ThemeColor('gitDecoration.incomingDeletedForegroundColor'), -// tooltip: l10n.t('Incoming Changes (deleted)'), -// }); -// break; -// case Status.INDEX_RENAMED: -// bucket.set(change.originalUri.toString(), { -// badge: '↓R', -// color: new ThemeColor('gitDecoration.incomingRenamedForegroundColor'), -// tooltip: l10n.t('Incoming Changes (renamed)'), -// }); -// break; -// case Status.MODIFIED: -// bucket.set(change.uri.toString(), { -// badge: '↓M', -// color: new ThemeColor('gitDecoration.incomingModifiedForegroundColor'), -// tooltip: l10n.t('Incoming Changes (modified)'), -// }); -// break; -// default: { -// bucket.set(change.uri.toString(), { -// badge: '↓~', -// color: new ThemeColor('gitDecoration.incomingModifiedForegroundColor'), -// tooltip: l10n.t('Incoming Changes'), -// }); -// break; -// } -// } -// } -// } + private async collectIncomingChangesFileDecorations(bucket: Map): Promise { + for (const change of await this.getIncomingChanges()) { + switch (change.status) { + case Status.INDEX_ADDED: + bucket.set(change.uri.toString(), { + badge: '↓A', + tooltip: l10n.t('Incoming Changes (added)'), + }); + break; + case Status.DELETED: + bucket.set(change.uri.toString(), { + badge: '↓D', + tooltip: l10n.t('Incoming Changes (deleted)'), + }); + break; + case Status.INDEX_RENAMED: + bucket.set(change.originalUri.toString(), { + badge: '↓R', + tooltip: l10n.t('Incoming Changes (renamed)'), + }); + break; + case Status.MODIFIED: + bucket.set(change.uri.toString(), { + badge: '↓M', + tooltip: l10n.t('Incoming Changes (modified)'), + }); + break; + default: { + bucket.set(change.uri.toString(), { + badge: '↓~', + tooltip: l10n.t('Incoming Changes'), + }); + break; + } + } + } + } -// private async getIncomingChanges(): Promise { -// try { -// const historyProvider = this.repository.historyProvider; -// const currentHistoryItemGroup = historyProvider.currentHistoryItemGroup; + private async getIncomingChanges(): Promise { + try { + const historyProvider = this.repository.historyProvider; + const currentHistoryItemGroup = historyProvider.currentHistoryItemGroup; -// if (!currentHistoryItemGroup?.base) { -// return []; -// } + if (!currentHistoryItemGroup?.base) { + return []; + } -// const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(currentHistoryItemGroup.id, currentHistoryItemGroup.base.id); -// if (!ancestor) { -// return []; -// } + const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(currentHistoryItemGroup.id, currentHistoryItemGroup.base.id); + if (!ancestor) { + return []; + } -// const changes = await this.repository.diffBetween(ancestor.id, currentHistoryItemGroup.base.id); -// return changes; -// } catch (err) { -// return []; -// } -// } + const changes = await this.repository.diffBetween(ancestor.id, currentHistoryItemGroup.base.id); + return changes; + } catch (err) { + return []; + } + } -// provideFileDecoration(uri: Uri): FileDecoration | undefined { -// return this.decorations.get(uri.toString()); -// } + provideFileDecoration(uri: Uri): FileDecoration | undefined { + return this.decorations.get(uri.toString()); + } -// dispose(): void { -// dispose(this.disposables); -// } -// } + dispose(): void { + dispose(this.disposables); + } +} export class GitDecorations { @@ -287,7 +282,7 @@ export class GitDecorations { private onDidOpenRepository(repository: Repository): void { const providers = combinedDisposable([ new GitDecorationProvider(repository), - // new GitIncomingChangesFileDecorationProvider(repository) + new GitIncomingChangesFileDecorationProvider(repository) ]); this.providers.set(repository, providers); From c0b99c07feb342b3584694afa09faccad84f03d5 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 4 Mar 2024 09:40:00 -0800 Subject: [PATCH 1749/1897] Update src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts --- .../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 620a12b50af..36663230871 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -53,7 +53,7 @@ export const enum AccessibilityVerbositySettingId { Hover = 'accessibility.verbosity.hover', Notification = 'accessibility.verbosity.notification', EmptyEditorHint = 'accessibility.verbosity.emptyEditorHint', - Comments = 'accessibility.verbosity.comments', + Comments = 'accessibility.verbosity.comments' } export const enum AccessibleViewProviderId { From 7a7b5ac3550ba3d2ae5df97fcf9ece3b436eef9e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 4 Mar 2024 09:45:46 -0800 Subject: [PATCH 1750/1897] rm extra provider id --- .../contrib/accessibility/browser/accessibilityConfiguration.ts | 1 - .../terminalContrib/chat/browser/terminalChatAccessibleView.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 36663230871..e3b3bb1ef7a 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -64,7 +64,6 @@ export const enum AccessibleViewProviderId { Chat = 'panelChat', InlineChat = 'inlineChat', InlineCompletions = 'inlineCompletions', - TerminalInlineChat = 'terminalInlineChat', KeybindingsEditor = 'keybindingsEditor', Notebook = 'notebook', Editor = 'editor', diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts index 798c0bf9774..c8b11add5be 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts @@ -26,7 +26,7 @@ export class TerminalInlineChatAccessibleViewContribution extends Disposable { } const responseContent = controller.lastResponseContent; accessibleViewService.show({ - id: AccessibleViewProviderId.TerminalInlineChat, + id: AccessibleViewProviderId.TerminalChat, verbositySettingKey: AccessibilityVerbositySettingId.InlineChat, provideContent(): string { return responseContent; }, onClose() { From 317d126ddf226bfcab3729ea6e01a3c21943af74 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 4 Mar 2024 09:47:10 -0800 Subject: [PATCH 1751/1897] use new function call, fix error --- .../terminalContrib/chat/browser/terminalChatController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 9ddc1e54349..c6f624dd865 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -99,9 +99,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._responseSupportsIssueReportingContextKey = TerminalChatContextKeys.responseSupportsIssueReporting.bindTo(this._contextKeyService); this._sessionResponseVoteContextKey = TerminalChatContextKeys.sessionResponseVote.bindTo(this._contextKeyService); - if (!this._chatAgentService.hasAgent(this._terminalAgentId)) { + if (!this._chatAgentService.getRegisteredAgent(this._terminalAgentId)) { this._register(this._chatAgentService.onDidChangeAgents(() => { - if (this._chatAgentService.getAgent(this._terminalAgentId)) { + if (this._chatAgentService.getRegisteredAgent(this._terminalAgentId)) { this._terminalAgentRegisteredContextKey.set(true); } })); From c19255de9261c3d203c9e707f828eb590c41d2b3 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 4 Mar 2024 15:55:39 -0300 Subject: [PATCH 1752/1897] Also keep focus in editor for "go to top/bottom of callstack" (#206817) Fix #205922 --- src/vs/workbench/contrib/debug/browser/debugCommands.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 3bb555a1a53..f2f839ff912 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -237,7 +237,7 @@ async function goToBottomOfCallStack(debugService: IDebugService) { if (callStack.length > 0) { const nextVisibleFrame = findNextVisibleFrame(false, callStack, 0); // must consider the next frame up first, which will be the last frame if (nextVisibleFrame) { - debugService.focusStackFrame(nextVisibleFrame); + debugService.focusStackFrame(nextVisibleFrame, undefined, undefined, { preserveFocus: false }); } } } @@ -247,7 +247,7 @@ function goToTopOfCallStack(debugService: IDebugService) { const thread = debugService.getViewModel().focusedThread; if (thread) { - debugService.focusStackFrame(thread.getTopStackFrame()); + debugService.focusStackFrame(thread.getTopStackFrame(), undefined, undefined, { preserveFocus: false }); } } From 069f4d91f377c207f4857e93c0965358985e9ca3 Mon Sep 17 00:00:00 2001 From: Robo Date: Tue, 5 Mar 2024 03:56:04 +0900 Subject: [PATCH 1753/1897] chore: bump electron@28.2.5 (#206822) * chore: bump electron@28.2.5 * chore: bump distro --- .yarnrc | 4 +- build/checksums/electron.txt | 150 +++++++++--------- cgmanifest.json | 8 +- package.json | 4 +- .../windows/electron-main/windowImpl.ts | 7 + yarn.lock | 8 +- 6 files changed, 94 insertions(+), 87 deletions(-) diff --git a/.yarnrc b/.yarnrc index 8675f7ab83c..dc10cd8fae6 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,5 +1,5 @@ disturl "https://electronjs.org/headers" -target "28.2.2" -ms_build_id "26836304" +target "28.2.5" +ms_build_id "27336930" runtime "electron" build_from_source "true" diff --git a/build/checksums/electron.txt b/build/checksums/electron.txt index 35feee9b876..72d5349d425 100644 --- a/build/checksums/electron.txt +++ b/build/checksums/electron.txt @@ -1,75 +1,75 @@ -b1478a79a80e453e9ee39ad4a0b0e0d871f3e369ec519e3ac5a1da20bb7bdbf3 *chromedriver-v28.2.2-darwin-arm64.zip -4e7c4651d610c70de883b9ceef633f1a2bf90e0f3a732eae7a6d7bcad11bb2df *chromedriver-v28.2.2-darwin-x64.zip -7cee31da7d90c2a24338a10046386517bb93c69c79bd44cfcc9372a551fc7d01 *chromedriver-v28.2.2-linux-arm64.zip -2056e41f713d1a6c83d1f0260c0f2b8addc3c49887ae85ca7e92267eb53951e8 *chromedriver-v28.2.2-linux-armv7l.zip -19503257092605e21bd3798f5ffd0049d8420a504ececef7b1e95d3733846874 *chromedriver-v28.2.2-linux-x64.zip -ec09eeb8a7040c7402a8a5f54491b33e5dc95ea0535b55381a3ec405014f08db *chromedriver-v28.2.2-mas-arm64.zip -1dd5cb2a113c74ae84f2ac98f6f40da2c367014381a547788fea9ae220e6fc9f *chromedriver-v28.2.2-mas-x64.zip -fd505e1f1c2f72266c48914690a48918fc7920877215a508ea5325cf0353f72c *chromedriver-v28.2.2-win32-arm64.zip -c0226c0fb260d6812185eeea718c8c0054d0fcac995bb1ccb333f852206372c8 *chromedriver-v28.2.2-win32-ia32.zip -b6daccad5bcd3046d0678c927f6b97ed91f2242f716deb0de95a0ee2303af818 *chromedriver-v28.2.2-win32-x64.zip -76a88da92b950c882d90c3dcb26e0c2ca5e5a52ad7a066ec0b3cbf9cc4d04563 *electron-api.json -6ad08d733c95de3c30560e8289d0e657ed5ee03bc8ba9d1f11d528851e5b7fba *electron-v28.2.2-darwin-arm64-dsym-snapshot.zip -8f0d450f3d2392cbe7a6cb274ec0f3bf63da66c98fa0baaa2355e69f1c93b151 *electron-v28.2.2-darwin-arm64-dsym.zip -262036eb86b767db0d199df022b8b432aa3714e451b9ac656af7ef031581b44a *electron-v28.2.2-darwin-arm64-symbols.zip -23119b333c47a5ea9e36e04cdc3b8c5955cfccfeb90994f1fecea4722bfb8dcc *electron-v28.2.2-darwin-arm64.zip -384015a3e49a6846ebefc78f9f01ce6d47c2ec109e6223907298aa6382b0d072 *electron-v28.2.2-darwin-x64-dsym-snapshot.zip -434838821de746ff71baafdf9e0df07cb3766dd73eb7fcd253aee0571bd0cd59 *electron-v28.2.2-darwin-x64-dsym.zip -470087b5d631dc0032611048d5fc23faed9a71ec2c36a528c5a50c2e357d1716 *electron-v28.2.2-darwin-x64-symbols.zip -48f3424b3cbdf602a13f451361ade2f7f2896a354a51f78da4239dbdf2d1218b *electron-v28.2.2-darwin-x64.zip -d5bf835ba4b2eaa4435946f97ad7ac3e7243564037423cfaadaf5cb03af4ddbc *electron-v28.2.2-linux-arm64-debug.zip -90550f29b1f032ebcf467dc81f4915c322f93855a4658cf74261f68a3ccdc21e *electron-v28.2.2-linux-arm64-symbols.zip -746284eb1d8029b0f6b02281543ab2ecf45f071da21407f45b2b32d1ff268310 *electron-v28.2.2-linux-arm64.zip -d5bf835ba4b2eaa4435946f97ad7ac3e7243564037423cfaadaf5cb03af4ddbc *electron-v28.2.2-linux-armv7l-debug.zip -80cc8d9333156caaee59c7ddf3bd77712be8379b51f4218063e6c176a4ec2c26 *electron-v28.2.2-linux-armv7l-symbols.zip -f4580e8877481c0526110feaa78372ed3045bfbf5a6ba4b14e8cd155b9965f5e *electron-v28.2.2-linux-armv7l.zip -da33d92871768e4cf95b143c6022830d97b0ec2d4120463ab71b48597f940f07 *electron-v28.2.2-linux-x64-debug.zip -18dce5283513abd94b79a1636d25e3453f5c33d335425a234b9967dd4e5ce942 *electron-v28.2.2-linux-x64-symbols.zip -1eeb6ebc3b0699cae1fb171bbf7c9105e716db833f6e73a90f4ca161f17ffb15 *electron-v28.2.2-linux-x64.zip -e74da15d90e52cddf0f0f14663f6313df585b486b002966f6016c1b148cdd70d *electron-v28.2.2-mas-arm64-dsym-snapshot.zip -498357eb2e784bff54c5ac59fd3eada814d130f12a5e77d47c468f2305377717 *electron-v28.2.2-mas-arm64-dsym.zip -849fa891d072d06b1e929eb1acfbe7ac83f0238483039f8e1102e01e5223c3f5 *electron-v28.2.2-mas-arm64-symbols.zip -621fd91d70cb33ec58543fc57762e692dfa0e272a53f3316fd215ffa88bd075b *electron-v28.2.2-mas-arm64.zip -0f9e2ab79bca99f44c1e9a140929fad6d2cd37def60303974f5a82ca95dd9a69 *electron-v28.2.2-mas-x64-dsym-snapshot.zip -51c29e047ba7d8669030cc9615f70ecaa5c9519cd04ab5e62822c0d4f21f5fbb *electron-v28.2.2-mas-x64-dsym.zip -25da93f45b095a3669475416832647a01f2a02a95dcc064dfabdf9c621045106 *electron-v28.2.2-mas-x64-symbols.zip -3b6931362f1b7f377624ea7c6ccf069f291e4e675a28f12a56e3e75355c13fbd *electron-v28.2.2-mas-x64.zip -cece93c232c65bf4e1b918b9645f5a2e247bd3f8bb2dd9e6e889a402060a103b *electron-v28.2.2-win32-arm64-pdb.zip -4a46e1ead0de7b6f757c1194add6467b3375a8dcfb02d903e481c0d8db5c7e5d *electron-v28.2.2-win32-arm64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v28.2.2-win32-arm64-toolchain-profile.zip -083f95abbce97cab70e77b86e39cff01ff1df121f36b9da581ead960ae329f69 *electron-v28.2.2-win32-arm64.zip -f9b4633bc03fe7c77db4b335121e7e3e05f6788c6752ccb3f68267e664d4323a *electron-v28.2.2-win32-ia32-pdb.zip -d20f70ea8dc86477f723d104d42fe78e2508577ef3b1eb6ec812366f18ad80d8 *electron-v28.2.2-win32-ia32-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v28.2.2-win32-ia32-toolchain-profile.zip -c57691b73592632829cef136be6dd356a82331450920fd024ac3589654d23550 *electron-v28.2.2-win32-ia32.zip -b8a14fc75b9205a4b82aa008e23a020e9fac694399b47390a163c3100ac7946d *electron-v28.2.2-win32-x64-pdb.zip -780089dde95ce1ab5da176ad53d9c7cd122085809622852a3132b80b93faac9b *electron-v28.2.2-win32-x64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v28.2.2-win32-x64-toolchain-profile.zip -5fbc76585891b0d7b09c938f7be25b7ab36b3768491021b053dc99bc70a8aa29 *electron-v28.2.2-win32-x64.zip -225268475350fa71d9fdea966160fc91379ced2f2353a902addf65d5f9b0dbf1 *electron.d.ts -59a8d6b81d93bc99ecf099fac6492eb67ba601386cce07261a009a5b99e75479 *ffmpeg-v28.2.2-darwin-arm64.zip -15386f238dce9ba40714336265422cc41a1ef0608041f562a8fd42e3813ddc64 *ffmpeg-v28.2.2-darwin-x64.zip -8e108e533811febcc51f377ac8604d506663453e41c02dc818517e1ea9a4e8d5 *ffmpeg-v28.2.2-linux-arm64.zip -51ecd03435f56a2ced31b1c9dbf281955ba82a814ca0214a4292bdc711e5a45c *ffmpeg-v28.2.2-linux-armv7l.zip -acc9dc3765f68b7563045e2d0df11bbef6b41be0a1c34bbf9fa778f36eefb42f *ffmpeg-v28.2.2-linux-x64.zip -e71aac5c02f67bd5ba5d650160ff4edb122f697ab6bd8e686eae78426c439733 *ffmpeg-v28.2.2-mas-arm64.zip -3d0bb26cc9b751dad883750972fddec72aa936ecaa0d9bd198ba9b47203410e8 *ffmpeg-v28.2.2-mas-x64.zip -035b24a44f09587092e7db4e28400139901cec6378b3c828ce9f90a60f4f3a9a *ffmpeg-v28.2.2-win32-arm64.zip -38b25e225fd028f1f3f2c551f3b42d62d9e5c4ef388e0b0e019e9c8d93a85b07 *ffmpeg-v28.2.2-win32-ia32.zip -41849e779371dc0c35899341ae658b883ef0124296787ad96b7d5e4d9b69f1b9 *ffmpeg-v28.2.2-win32-x64.zip -8830364f8050164b1736246c30e96ae7ac876bcec5af1bf6344edbd66ed45353 *hunspell_dictionaries.zip -fc417873289fa9c947598ed73a27a28c4b5a07ce90ef998bb56550c4e10a034b *libcxx-objects-v28.2.2-linux-arm64.zip -06e9cdb2e8785a0835f66d34e9518c47ef220e32646e5b43e599339836e9e7b1 *libcxx-objects-v28.2.2-linux-armv7l.zip -ac098a006a8f84d0bb19088b2dec3ee3068b19208c5611194e831b1e5878fb2d *libcxx-objects-v28.2.2-linux-x64.zip -56414a1e809874949c1a1111b8e68b8d4f40d55cb481ad4869e920e47fe1b71b *libcxx_headers.zip -36e46cbed397cc1fe34d8dc477d3a87613acb9936f811535c1300e138e1a7008 *libcxxabi_headers.zip -94b01f4dd6bd56dec39a0be9ac14bb8c9a73db22cb579d6093f4f4c95a4a8896 *mksnapshot-v28.2.2-darwin-arm64.zip -ea768087b4fedf09c38eb093beb744c1a3b5b2a54025a83f1e2301ea03539500 *mksnapshot-v28.2.2-darwin-x64.zip -b9a01ba90abb69877838515d8273532e4aeea6d66c49b8aac3267e26546fc8b3 *mksnapshot-v28.2.2-linux-arm64-x64.zip -60005160b5e9db4a3847c63893f44e18ca86657a3ec97b6c13a90e43291bdb65 *mksnapshot-v28.2.2-linux-armv7l-x64.zip -81bf5ec59e7c33c642b79582fc5b775ec635ce0c52f3f5c30315cb45fdbffd12 *mksnapshot-v28.2.2-linux-x64.zip -7bfbe3cf02713b1a09aa19b75b876e158ed167b0d4345ec3b429061b53fc4b8f *mksnapshot-v28.2.2-mas-arm64.zip -91f7d34a05fa9c7cda4f36a44309f51e7defea2134d5bcc818a3eb4537979870 *mksnapshot-v28.2.2-mas-x64.zip -3f7163a34aae864cd44ebec086d4fab30132924680f20136cf19348811bace50 *mksnapshot-v28.2.2-win32-arm64-x64.zip -ac64fbfb78a1f6f389dac96ad7c655e2ea6fb2289e38a8fd516dbbda6bea42a3 *mksnapshot-v28.2.2-win32-ia32.zip -1bcd03747ce3eee6dd94b0608a0812268dacf77bac5541c581c22b92f700b303 *mksnapshot-v28.2.2-win32-x64.zip +23d9bca1abd1c64d0bd47b9528b8db1b1f28c31e81188ecbed4e9cd18eab3545 *chromedriver-v28.2.5-darwin-arm64.zip +9215cf2196988c5f0e0a01fe1bdd827ab25f3a0895b6e9ff96185fed45be24d9 *chromedriver-v28.2.5-darwin-x64.zip +a27c39a8a9f02a630f4ea1218954e768791e44319ce34e99bb524d45aa956376 *chromedriver-v28.2.5-linux-arm64.zip +658bef49300d3183a34609391f64f3df6c9b07eb55886fa1378249e1170ac68e *chromedriver-v28.2.5-linux-armv7l.zip +14a285843587f251455a3ac69be5bebca7e7c3e934151a69dc8c10c943aaac49 *chromedriver-v28.2.5-linux-x64.zip +173112b71f363f1c434eb4bfe8356a5a4592a0580d8c434c2141f3a04de7695b *chromedriver-v28.2.5-mas-arm64.zip +b72902d8f4d886fef3f945e4a9dd707e18d52201a57e421a555cc166689955a7 *chromedriver-v28.2.5-mas-x64.zip +723cc0db4299d23c6be611b723187c857102749de2f2294bae09047b0d99cfd2 *chromedriver-v28.2.5-win32-arm64.zip +1c549de92e2d784cc2a2618d129e368d74e8da6497df7f5bcabfd2f834981f5d *chromedriver-v28.2.5-win32-ia32.zip +2df3c811c3ed8f22f28e740ffe0abf7c6d0c29d1874efa5290b75575a23d292b *chromedriver-v28.2.5-win32-x64.zip +a6e536d48e399f0961cb5de1e9cb0d3e534c4686fdf6fc79080e66516fdd5b6e *electron-api.json +746c5867227538235cff139e174a7b85fa49230a69350414bed7d1e6ae664cba *electron-v28.2.5-darwin-arm64-dsym-snapshot.zip +b5d00927dead894355c26cc581443735c252a71a53a363f3909f02b39ba1a38f *electron-v28.2.5-darwin-arm64-dsym.zip +6bb1356b72b5d3f8c3d25ef3f42a9ab8574498ab79299af056d8ac93972de72d *electron-v28.2.5-darwin-arm64-symbols.zip +87b17c403d355ba2eee43ee3a955c02069571617ef081b951272c1337ed5a2bb *electron-v28.2.5-darwin-arm64.zip +ff8b7d3073bbc1f26d83a224a79def7cfa652318b98d513603ac3d6e3ea56905 *electron-v28.2.5-darwin-x64-dsym-snapshot.zip +26057699098b6a4173c3f2550ec9a08d3edf4ce3aab5b351ca41c056f8f5ea5f *electron-v28.2.5-darwin-x64-dsym.zip +a467b38526c2c6c677dfe71898eaa8f3c8fccd7675bddfbfce6b095ebd66ea62 *electron-v28.2.5-darwin-x64-symbols.zip +a1ed37b654c48afe0fa8411d09b8644e121fa448d9cafa2ee6a3d2f5d8d36a4d *electron-v28.2.5-darwin-x64.zip +e28c071288258dd55ce0ad6c8582ad1db894a7e981dc2f84534d942289ba0a8b *electron-v28.2.5-linux-arm64-debug.zip +cebd2961e8af1600ca307f530c8b89dafa934cfed356830f4b5c04c048ffc204 *electron-v28.2.5-linux-arm64-symbols.zip +2a24355c27b5d43a424caedfcfe3fc42aeea80e13977fd708537519d6850c372 *electron-v28.2.5-linux-arm64.zip +e28c071288258dd55ce0ad6c8582ad1db894a7e981dc2f84534d942289ba0a8b *electron-v28.2.5-linux-armv7l-debug.zip +de46cbbe2c3eb4cd7d761ec57e85cd2077592b88586e1d9f45606df69a1e5dab *electron-v28.2.5-linux-armv7l-symbols.zip +2707f9fb7b7c6ea8038f8c67054cdfee4fb0bbaec36c20f3ffcca05cd5bbcd6c *electron-v28.2.5-linux-armv7l.zip +2567949ac356f53a5f145e6efaef1e1bc07dabfafa549765ebca54b33b0c298b *electron-v28.2.5-linux-x64-debug.zip +b067d8dfd0345129172628575684c7bc3d739843662aecc33ce4221a96fb7d48 *electron-v28.2.5-linux-x64-symbols.zip +1c1e972fa7daa54e1e54b642b2828495020367df5dcf1e3a4d4fc170980a8d6e *electron-v28.2.5-linux-x64.zip +23068f0cad14769f715147d1778da27a1850cdeea20c1ff7c7b1eb729b4d87b0 *electron-v28.2.5-mas-arm64-dsym-snapshot.zip +04640b64a0132f4606d31b89f8fa063b12be8936f0a2c546e05a992f23d00260 *electron-v28.2.5-mas-arm64-dsym.zip +cf1787c50932ef5c5309a8d80d7647495ddac4c71b6e0feb8ea547299c114ed4 *electron-v28.2.5-mas-arm64-symbols.zip +7f2339a92defc1808714bcdd0561430898e6a9f0cb8788a91bf178c10228fb2a *electron-v28.2.5-mas-arm64.zip +26eca4afd370e422c911c68268cf668ca43c10617c4e9cd53eaa564e698efd50 *electron-v28.2.5-mas-x64-dsym-snapshot.zip +ab928fcd3851651d9ef62fe4b62b48471a6673c603b70ca7f4049e72ad3bc2c2 *electron-v28.2.5-mas-x64-dsym.zip +c64232513b56b5b82f76b637a04f68fcfb7231ea8076d6dc1375aac8dac4a02c *electron-v28.2.5-mas-x64-symbols.zip +8d3a0988e699a42482079676ffba2ac50fbd74f1097d40cee0029ff5bc189144 *electron-v28.2.5-mas-x64.zip +7fcc54dc77dedbf4cb9eeaba1678ebf265649479c478e344c6dff27b1ec63b0b *electron-v28.2.5-win32-arm64-pdb.zip +31a69a6ed4e71b4fb047d19522dc32f9ff0f4b617536dcb7e8b562c9c583adbf *electron-v28.2.5-win32-arm64-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v28.2.5-win32-arm64-toolchain-profile.zip +68a6fc3411daaf604da0009df506a655587cb6e5cc19d6a1c47ce0b62fdb4ac6 *electron-v28.2.5-win32-arm64.zip +e74519411a678a9885bfb07acb5df85632f3de67d2fc54ccbd5ebd548edf84c8 *electron-v28.2.5-win32-ia32-pdb.zip +798261cfed077ec4afc965ab1a8e3fe4c976533ba86fa8f17cd69107bd1be3be *electron-v28.2.5-win32-ia32-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v28.2.5-win32-ia32-toolchain-profile.zip +41f23f86bf7aa19f67025af7db221b727a38ffc0b1c5661b305be7250ecb7abc *electron-v28.2.5-win32-ia32.zip +ee882c550a2889dd18f58bf0f5c5ee9a1dd0eb6ed29c4f9a359b0db5314d7965 *electron-v28.2.5-win32-x64-pdb.zip +197b7ab2ba961ded3260ae91cf57502c43c2cdaca3fc18b90ec6f4d4e08ba9ac *electron-v28.2.5-win32-x64-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v28.2.5-win32-x64-toolchain-profile.zip +9235e039183fd62a6d37f70ae39a0f3a3ddb4e00e1474e6258343d1ad955c995 *electron-v28.2.5-win32-x64.zip +981a6c4d1030af6949405c3818b7332a16a959bd30970f5660e4975ccdf31789 *electron.d.ts +90b6c39e1ba7bbf0bccc3e009bcdbd4d8a57821f669229ab7847fd3d56cc8a66 *ffmpeg-v28.2.5-darwin-arm64.zip +3a736fed82b232624aeba8a33c02e1ce589214db0faf5984e216f8a72cbf713a *ffmpeg-v28.2.5-darwin-x64.zip +1674cdc15b72fb421ae4fd5afb217ef8968fb879db391343519764e2e77edb41 *ffmpeg-v28.2.5-linux-arm64.zip +6db75c7fe794f2a2edf3ff9b0d8ad6157d132c89e36e42580594a26f56658ae5 *ffmpeg-v28.2.5-linux-armv7l.zip +7fed2646cf8cce5c6c1afe4214b2d1ea12c89ed379c4b2cdb06cdadb14ceb4f6 *ffmpeg-v28.2.5-linux-x64.zip +fb3649690c496f4c6f884ddf94a7ff518278f6140c2487dd652256f53ed2e3fa *ffmpeg-v28.2.5-mas-arm64.zip +c1fee5ef3a550a5e9a652e251e6ec3677d156610670b54489b5da0c6b4007179 *ffmpeg-v28.2.5-mas-x64.zip +6745a8816159bc980f1ccb4d28c2f02f70cb5e2faf6423e0924d890ca6353dd8 *ffmpeg-v28.2.5-win32-arm64.zip +e1046c0280a7833227963b43d468973d646bd38ae100a298bb28d6a229e723b2 *ffmpeg-v28.2.5-win32-ia32.zip +a189c9c2317f011735e6d1cb743a78536f45a41f18a16c81d5294ca933d9519a *ffmpeg-v28.2.5-win32-x64.zip +29a594a16cdf3585299e7c585bae2ea007e108a72a96b9e2a95d149e7dc381d6 *hunspell_dictionaries.zip +7c1263edb062c07c2fb589812788f70772629c1798abd1481205cf8cdb999120 *libcxx-objects-v28.2.5-linux-arm64.zip +fc75baa4308a58048fe846b9fe78bba362a34becc5bf32a776f15933f4beaedf *libcxx-objects-v28.2.5-linux-armv7l.zip +1bedd5f8f3c897b0ca3cb263620cc4a8fff7001fcd6318a12c2c4cd9e922b35f *libcxx-objects-v28.2.5-linux-x64.zip +f6f37ee5bf297959c4fdec9bb77637310e6c8a85c7defadcbd660507a9e63728 *libcxx_headers.zip +8849467a7e670355b9cab854d66a09d57e9d91e8881034b07eb71f9d9928eb18 *libcxxabi_headers.zip +ff875bb59ecc8bf01b618d61d4a8378e133ea2c2571653828c9ea08773f5d776 *mksnapshot-v28.2.5-darwin-arm64.zip +30c1f135220d783b08a70bc7992877431e320543837ce0d90102039a945023aa *mksnapshot-v28.2.5-darwin-x64.zip +289e6b5feabe9ea22c10fc0fd0afcde59670506df1af6f1e87dc4dab5cbada29 *mksnapshot-v28.2.5-linux-arm64-x64.zip +e857fe518df1063308514224f82d13ffc24bb661b22d9a8a10a915a69830037b *mksnapshot-v28.2.5-linux-armv7l-x64.zip +a14af21de32fbfdf5d40402b52e7ff4858682cf3958fd6898ea30df331164004 *mksnapshot-v28.2.5-linux-x64.zip +8531b3ed3b47ed0a34a317be2fd03a573ee38e719aeddeeb0a6b3d5c36268ffa *mksnapshot-v28.2.5-mas-arm64.zip +71c978fffa8cb4a3f13842a8eadcb29e0782a648492204ebba08edb23b1fa297 *mksnapshot-v28.2.5-mas-x64.zip +b1ce8db39866860a6853c9a8874224c757a2b3086a451b7b1c30511615457166 *mksnapshot-v28.2.5-win32-arm64-x64.zip +c9a7f82fcd320c52f111d18b7fe6aa9ec739f94a13a7e8e04d22e6706a889c4b *mksnapshot-v28.2.5-win32-ia32.zip +493d0eabacf33c9d51305cf40bf7590901eb4e38d53308a76ae05db5af0a8468 *mksnapshot-v28.2.5-win32-x64.zip diff --git a/cgmanifest.json b/cgmanifest.json index 4b4d49573e4..e0a02fcc97d 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "01303e423c41f9fefe7ff777744a4c549c0c6d8c" + "commitHash": "14d11e5bb9b5b1cd51f7b19546e74a73cab42084" } }, "licenseDetail": [ @@ -40,7 +40,7 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "120.0.6099.276" + "version": "120.0.6099.291" }, { "component": { @@ -528,12 +528,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "16adf2a26358e3fc2297832e867c942b6df35844" + "commitHash": "6544cec6864be60f577c1fcd41fa646c4d0192aa" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "28.2.2" + "version": "28.2.5" }, { "component": { diff --git a/package.json b/package.json index 44e075ebd81..2afb762ff12 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.88.0", - "distro": "aa80bd2351f35faf630bcb132a08b10281943951", + "distro": "4623345215aabf2cde23e144a9d4d3ef7803360e", "author": { "name": "Microsoft Corporation" }, @@ -149,7 +149,7 @@ "cssnano": "^6.0.3", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "28.2.2", + "electron": "28.2.5", "eslint": "8.36.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^46.5.0", diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 807bb601136..e46474de06c 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -975,6 +975,13 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { const proxyBypassRules = newNoProxy ? `${newNoProxy},` : ''; this.logService.trace(`Setting proxy to '${proxyRules}', bypassing '${proxyBypassRules}'`); this._win.webContents.session.setProxy({ proxyRules, proxyBypassRules, pacScript: '' }); + type appWithProxySupport = Electron.App & { + setProxy(config: Electron.Config): Promise; + resolveProxy(url: string): Promise; + }; + if (typeof (app as appWithProxySupport).setProxy === 'function') { + (app as appWithProxySupport).setProxy({ proxyRules, proxyBypassRules, pacScript: '' }); + } } } } diff --git a/yarn.lock b/yarn.lock index 4dac8ed481a..c96688f58da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3563,10 +3563,10 @@ electron-to-chromium@^1.4.648: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.648.tgz#c7b46c9010752c37bb4322739d6d2dd82354fbe4" integrity sha512-EmFMarXeqJp9cUKu/QEciEApn0S/xRcpZWuAm32U7NgoZCimjsilKXHRO9saeEW55eHZagIDg6XTUOv32w9pjg== -electron@28.2.2: - version "28.2.2" - resolved "https://registry.yarnpkg.com/electron/-/electron-28.2.2.tgz#d5aa4a33c00927d83ca893f8726f7c62aad98c41" - integrity sha512-8UcvIGFcjplHdjPFNAHVFg5bS0atDyT3Zx21WwuE4iLfxcAMsyMEOgrQX3im5LibA8srwsUZs7Cx0JAUfcQRpw== +electron@28.2.5: + version "28.2.5" + resolved "https://registry.yarnpkg.com/electron/-/electron-28.2.5.tgz#d8e85306e8c51456042223a51f560f6ada565dc8" + integrity sha512-qlvQkDNVAzN647NpiJJw7GYJqE0NwK4+1evkhrQ0Xv6Qgab1EtN50G4oDr4/x/+O5pGUG2P5d3isXu+37O3RDw== dependencies: "@electron/get" "^2.0.0" "@types/node" "^18.11.18" From f022b12d428258e06c5f0fa5b3fad5f9559dea73 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 4 Mar 2024 11:48:33 -0800 Subject: [PATCH 1754/1897] Move export from terminalContrib lint suppress into terminal/ --- .../chat/electron-sandbox/actions/voiceChatActions.ts | 5 +---- .../contrib/terminal/browser/terminalContribExports.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 src/vs/workbench/contrib/terminal/browser/terminalContribExports.ts diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index c64b66f6574..4f52090cc6e 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -52,11 +52,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ThemeIcon } from 'vs/base/common/themables'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; - -// This is a one-off/safe import, changing the eslint rules would require duplicating/complicating the rules -// eslint-disable-next-line local/code-import-patterns -import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; import { ProgressLocation } from 'vs/platform/progress/common/progress'; +import { TerminalChatController } from 'vs/workbench/contrib/terminal/browser/terminalContribExports'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalContribExports.ts b/src/vs/workbench/contrib/terminal/browser/terminalContribExports.ts new file mode 100644 index 00000000000..86677966a9b --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/terminalContribExports.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. + *--------------------------------------------------------------------------------------------*/ + +// This is a one-off/safe import, to expose to outside contfibs as in general we don't want them +// to touch terminalContrib either. +// eslint-disable-next-line local/code-import-patterns +export { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; From bd603172efb3d8bd03d7c9233dff2dc17248dc06 Mon Sep 17 00:00:00 2001 From: Sam Denty Date: Mon, 4 Mar 2024 20:17:04 +0000 Subject: [PATCH 1755/1897] feat(web/lifecycleService): correct startupKind (#206563) * fixes #206345 * fixes #206296 * fix: don't hard check isWeb * chore: review * cleanup * cleanup --------- Co-authored-by: Benjamin Pasero --- src/vs/workbench/browser/layout.ts | 4 ++-- .../lifecycle/browser/lifecycleService.ts | 16 +++++++++++++++- .../lifecycle/common/lifecycleService.ts | 13 ++++++++----- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index b71d9dd9477..6cb0a546797 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -671,7 +671,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Only restore last viewlet if window was reloaded or we are in development mode let viewContainerToRestore: string | undefined; - if (!this.environmentService.isBuilt || lifecycleService.startupKind === StartupKind.ReloadedWindow || isWeb) { + if (!this.environmentService.isBuilt || lifecycleService.startupKind === StartupKind.ReloadedWindow) { viewContainerToRestore = this.storageService.get(SidebarPart.activeViewletSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id); } else { viewContainerToRestore = this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id; @@ -2608,7 +2608,7 @@ class LayoutStateModel extends Disposable { LayoutStateKeys.GRID_SIZE.defaultValue = { height: workbenchDimensions.height, width: workbenchDimensions.width }; LayoutStateKeys.SIDEBAR_SIZE.defaultValue = Math.min(300, workbenchDimensions.width / 4); LayoutStateKeys.AUXILIARYBAR_SIZE.defaultValue = Math.min(300, workbenchDimensions.width / 4); - LayoutStateKeys.PANEL_SIZE.defaultValue = (this.stateCache.get(LayoutStateKeys.PANEL_POSITION.name) ?? LayoutStateKeys.PANEL_POSITION.defaultValue) === 'bottom' ? workbenchDimensions.height / 3 : workbenchDimensions.width / 4; + LayoutStateKeys.PANEL_SIZE.defaultValue = (this.stateCache.get(LayoutStateKeys.PANEL_POSITION.name) ?? LayoutStateKeys.PANEL_POSITION.defaultValue) === Position.BOTTOM ? workbenchDimensions.height / 3 : workbenchDimensions.width / 4; LayoutStateKeys.SIDEBAR_HIDDEN.defaultValue = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY; // Apply all defaults diff --git a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts index c257d6ccc42..351cad96bc7 100644 --- a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ShutdownReason, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { ShutdownReason, ILifecycleService, StartupKind } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { AbstractLifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycleService'; import { localize } from 'vs/nls'; @@ -13,6 +13,7 @@ import { addDisposableListener, EventType } from 'vs/base/browser/dom'; import { IStorageService, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { CancellationToken } from 'vs/base/common/cancellation'; import { mainWindow } from 'vs/base/browser/window'; +import { firstOrDefault } from 'vs/base/common/arrays'; export class BrowserLifecycleService extends AbstractLifecycleService { @@ -200,6 +201,19 @@ export class BrowserLifecycleService extends AbstractLifecycleService { // Refs: https://github.com/microsoft/vscode/issues/136035 this.withExpectedShutdown({ disableShutdownHandling: true }, () => mainWindow.location.reload()); } + + protected override doResolveStartupKind(): StartupKind | undefined { + let startupKind = super.doResolveStartupKind(); + if (typeof startupKind !== 'number') { + const timing = firstOrDefault(performance.getEntriesByType('navigation')) as PerformanceNavigationTiming | undefined; + if (timing?.type === 'reload') { + // MDN: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming/type#value + startupKind = StartupKind.ReloadedWindow; + } + } + + return startupKind; + } } registerSingleton(ILifecycleService, BrowserLifecycleService, InstantiationType.Eager); diff --git a/src/vs/workbench/services/lifecycle/common/lifecycleService.ts b/src/vs/workbench/services/lifecycle/common/lifecycleService.ts index 6aa7358deda..62aa7db5201 100644 --- a/src/vs/workbench/services/lifecycle/common/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/common/lifecycleService.ts @@ -60,13 +60,20 @@ export abstract class AbstractLifecycleService extends Disposable implements ILi } private resolveStartupKind(): StartupKind { + const startupKind = this.doResolveStartupKind() ?? StartupKind.NewWindow; + this.logService.trace(`[lifecycle] starting up (startup kind: ${startupKind})`); + + return startupKind; + } + + protected doResolveStartupKind(): StartupKind | undefined { // Retrieve and reset last shutdown reason const lastShutdownReason = this.storageService.getNumber(AbstractLifecycleService.LAST_SHUTDOWN_REASON_KEY, StorageScope.WORKSPACE); this.storageService.remove(AbstractLifecycleService.LAST_SHUTDOWN_REASON_KEY, StorageScope.WORKSPACE); // Convert into startup kind - let startupKind: StartupKind; + let startupKind: StartupKind | undefined = undefined; switch (lastShutdownReason) { case ShutdownReason.RELOAD: startupKind = StartupKind.ReloadedWindow; @@ -74,12 +81,8 @@ export abstract class AbstractLifecycleService extends Disposable implements ILi case ShutdownReason.LOAD: startupKind = StartupKind.ReopenedWindow; break; - default: - startupKind = StartupKind.NewWindow; } - this.logService.trace(`[lifecycle] starting up (startup kind: ${startupKind})`); - return startupKind; } From 08185051c341a02e3eb09ad559f573752ff255cf Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 4 Mar 2024 12:37:57 -0800 Subject: [PATCH 1756/1897] rm merge marker, fix css bug --- .../contrib/chat/electron-sandbox/actions/voiceChatActions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 4f52090cc6e..82b91bb2073 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -826,7 +826,6 @@ registerThemingParticipant((theme, collector) => { // Show a "microphone" icon when recording is in progress that glows via outline. collector.addRule(` .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), -<<<<<<< HEAD .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), .monaco-workbench:not(.reduce-motion) .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { color: ${activeRecordingColor}; From 62b22bbe6103f6a35de6cf5141ae26450ae4d507 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 4 Mar 2024 21:59:51 +0100 Subject: [PATCH 1757/1897] `code --install-extension` not working with roaming profiles (fix #205924) (#206834) --- src/vs/code/electron-main/main.ts | 16 ++++++++++++++-- src/vs/code/node/cliProcessMain.ts | 16 ++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index c5466e20b45..8548fa7ce4d 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -71,6 +71,7 @@ import { LogService } from 'vs/platform/log/common/logService'; import { massageMessageBoxOptions } from 'vs/platform/dialogs/common/dialogs'; import { SaveStrategy, StateService } from 'vs/platform/state/node/stateService'; import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataProvider'; +import { addUNCHostToAllowlist, getUNCHost } from 'vs/base/node/unc'; /** * The main VS Code entry point. @@ -249,8 +250,8 @@ class CodeMain { // Environment service (paths) Promise.all([ - environmentMainService.extensionsPath, - environmentMainService.codeCachePath, + this.allowWindowsUNCPath(environmentMainService.extensionsPath), // enable extension paths on UNC drives... + environmentMainService.codeCachePath, // ...other user-data-derived paths should already be enlisted from `main.js` environmentMainService.logsHome.with({ scheme: Schemas.file }).fsPath, userDataProfilesMainService.defaultProfile.globalStorageHome.with({ scheme: Schemas.file }).fsPath, environmentMainService.workspaceStorageHome.with({ scheme: Schemas.file }).fsPath, @@ -269,6 +270,17 @@ class CodeMain { userDataProfilesMainService.init(); } + private allowWindowsUNCPath(path: string): string { + if (isWindows) { + const host = getUNCHost(path); + if (host) { + addUNCHostToAllowlist(host); + } + } + + return path; + } + private async claimInstance(logService: ILogService, environmentMainService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, productService: IProductService, retry: boolean): Promise { // Try to setup a server for running. If that succeeds it means diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index b91367f1fc2..aea83578ee0 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -63,6 +63,7 @@ import { LogService } from 'vs/platform/log/common/logService'; import { LoggerService } from 'vs/platform/log/node/loggerService'; import { localize } from 'vs/nls'; import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataProvider'; +import { addUNCHostToAllowlist, getUNCHost } from 'vs/base/node/unc'; class CliMain extends Disposable { @@ -121,8 +122,8 @@ class CliMain extends Disposable { // Init folders await Promise.all([ - environmentService.appSettingsHome.with({ scheme: Schemas.file }).fsPath, - environmentService.extensionsPath + this.allowWindowsUNCPath(environmentService.appSettingsHome.with({ scheme: Schemas.file }).fsPath), + this.allowWindowsUNCPath(environmentService.extensionsPath) ].map(path => path ? Promises.mkdir(path, { recursive: true }) : undefined)); // Logger @@ -233,6 +234,17 @@ class CliMain extends Disposable { return [new InstantiationService(services), appenders]; } + private allowWindowsUNCPath(path: string): string { + if (isWindows) { + const host = getUNCHost(path); + if (host) { + addUNCHostToAllowlist(host); + } + } + + return path; + } + private registerErrorHandler(logService: ILogService): void { // Install handler for unexpected errors From e2f64dd1ab1abac713ce996b3ceedf805a7e7005 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 4 Mar 2024 22:16:13 +0100 Subject: [PATCH 1758/1897] Aux window: investigate possibly memory leaks (#195889) (#206791) --- src/vs/workbench/browser/layout.ts | 7 +++++-- src/vs/workbench/browser/part.ts | 2 +- .../workbench/browser/parts/banner/bannerPart.ts | 2 +- .../browser/parts/statusbar/statusbarPart.ts | 2 +- .../browser/parts/titlebar/titlebarPart.ts | 4 ++-- .../browser/parts/titlebar/windowTitle.ts | 14 ++++++++++---- .../contrib/webviewPanel/browser/webviewEditor.ts | 2 +- .../parts/titlebar/titlebarPart.ts | 4 ++-- .../services/layout/browser/layoutService.ts | 3 ++- .../test/browser/workbenchTestServices.ts | 2 +- 10 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 6cb0a546797..9e397b31a21 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -1089,8 +1089,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi }); } - registerPart(part: Part): void { - this.parts.set(part.getId(), part); + registerPart(part: Part): IDisposable { + const id = part.getId(); + this.parts.set(id, part); + + return toDisposable(() => this.parts.delete(id)); } protected getPart(key: Parts): Part { diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index e78f59839be..f015fdbdf53 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -50,7 +50,7 @@ export abstract class Part extends Component implements ISerializableView { ) { super(id, themeService, storageService); - layoutService.registerPart(this); + this._register(layoutService.registerPart(this)); } protected override onThemeChange(theme: IColorTheme): void { diff --git a/src/vs/workbench/browser/parts/banner/bannerPart.ts b/src/vs/workbench/browser/parts/banner/bannerPart.ts index 3725e594c97..91c8e9902bb 100644 --- a/src/vs/workbench/browser/parts/banner/bannerPart.ts +++ b/src/vs/workbench/browser/parts/banner/bannerPart.ts @@ -86,7 +86,7 @@ export class BannerPart extends Part implements IBannerService { })); // Track focus - const scopedContextKeyService = this.contextKeyService.createScoped(this.element); + const scopedContextKeyService = this._register(this.contextKeyService.createScoped(this.element)); BannerFocused.bindTo(scopedContextKeyService).set(true); return this.element; diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 664a333bb16..06548d13508 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -338,7 +338,7 @@ class StatusbarPart extends Part implements IStatusbarEntryContainer { this.element = parent; // Track focus within container - const scopedContextKeyService = this.contextKeyService.createScoped(this.element); + const scopedContextKeyService = this._register(this.contextKeyService.createScoped(this.element)); StatusBarFocused.bindTo(scopedContextKeyService).set(true); // Left items container diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 548f4d36caf..110d04fba11 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -49,7 +49,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { EditorCommandsContextActionRunner } from 'vs/workbench/browser/parts/editor/editorTabsControl'; import { IEditorCommandsContext, IEditorPartOptionsChangeEvent, IToolbarActions } from 'vs/workbench/common/editor'; -import { mainWindow } from 'vs/base/browser/window'; +import { CodeWindow, mainWindow } from 'vs/base/browser/window'; import { ACCOUNTS_ACTIVITY_TILE_ACTION, GLOBAL_ACTIVITY_TITLE_ACTION } from 'vs/workbench/browser/parts/titlebar/titlebarActions'; import { IView } from 'vs/base/browser/ui/grid/grid'; import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; @@ -258,7 +258,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { constructor( id: string, - targetWindow: Window, + targetWindow: CodeWindow, editorGroupsContainer: IEditorGroupsContainer | 'main', @IContextMenuService private readonly contextMenuService: IContextMenuService, @IConfigurationService protected readonly configurationService: IConfigurationService, diff --git a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts index 3ec39eeafc2..e025f5c4d6c 100644 --- a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts +++ b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts @@ -27,6 +27,8 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { getWindowById } from 'vs/base/browser/dom'; +import { CodeWindow } from 'vs/base/browser/window'; const enum WindowSettingNames { titleSeparator = 'window.titleSeparator', @@ -79,8 +81,10 @@ export class WindowTitle extends Disposable { private readonly editorService: IEditorService; + private readonly windowId: number; + constructor( - private readonly targetWindow: Window, + targetWindow: CodeWindow, editorGroupsContainer: IEditorGroupsContainer | 'main', @IConfigurationService protected readonly configurationService: IConfigurationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @@ -95,6 +99,7 @@ export class WindowTitle extends Disposable { super(); this.editorService = editorService.createScoped(editorGroupsContainer, this._store); + this.windowId = targetWindow.vscodeWindowId; this.updateTitleIncludesFocusedView(); this.registerListeners(); @@ -177,7 +182,8 @@ export class WindowTitle extends Disposable { nativeTitle = this.productService.nameLong; } - if (!this.targetWindow.document.title && isMacintosh && nativeTitle === this.productService.nameLong) { + const window = getWindowById(this.windowId, true).window; + if (!window.document.title && isMacintosh && nativeTitle === this.productService.nameLong) { // TODO@electron macOS: if we set a window title for // the first time and it matches the one we set in // `windowImpl.ts` somehow the window does not appear @@ -185,10 +191,10 @@ export class WindowTitle extends Disposable { // briefly to something different to ensure macOS // recognizes we have a window. // See: https://github.com/microsoft/vscode/issues/191288 - this.targetWindow.document.title = `${this.productService.nameLong} ${WindowTitle.TITLE_DIRTY}`; + window.document.title = `${this.productService.nameLong} ${WindowTitle.TITLE_DIRTY}`; } - this.targetWindow.document.title = nativeTitle; + window.document.title = nativeTitle; this.title = title; this.onDidChangeEmitter.fire(); diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts index 33ad2d64e7d..67fbfb9e4dc 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts @@ -91,7 +91,7 @@ export class WebviewEditor extends EditorPane { this._element.id = `webview-editor-element-${generateUuid()}`; parent.appendChild(element); - this._scopedContextKeyService.value = this._contextKeyService.createScoped(element); + this._scopedContextKeyService.value = this._register(this._contextKeyService.createScoped(element)); } public override dispose(): void { diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index 5d989fc5e1b..4e4e8f5b7d0 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -26,7 +26,7 @@ import { NativeMenubarControl } from 'vs/workbench/electron-sandbox/parts/titleb import { IEditorGroupsContainer, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { mainWindow } from 'vs/base/browser/window'; +import { CodeWindow, mainWindow } from 'vs/base/browser/window'; export class NativeTitlebarPart extends BrowserTitlebarPart { @@ -59,7 +59,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { constructor( id: string, - targetWindow: Window, + targetWindow: CodeWindow, editorGroupsContainer: IEditorGroupsContainer | 'main', @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 0a397839220..68dcd16ab14 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -14,6 +14,7 @@ import { isAuxiliaryWindow } from 'vs/base/browser/window'; import { CustomTitleBarVisibility, TitleBarSetting, getMenuBarVisibility, hasCustomTitlebar, hasNativeTitlebar } from 'vs/platform/window/common/window'; import { isFullscreen, isWCOEnabled } from 'vs/base/browser/browser'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IDisposable } from 'vs/base/common/lifecycle'; export const IWorkbenchLayoutService = refineServiceDecorator(ILayoutService); @@ -291,7 +292,7 @@ export interface IWorkbenchLayoutService extends ILayoutService { /** * Register a part to participate in the layout. */ - registerPart(part: Part): void; + registerPart(part: Part): IDisposable; /** * Returns whether the target window is maximized. diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 9cb63bb2b63..6fcf99d03f3 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -638,7 +638,7 @@ export class TestLayoutService implements IWorkbenchLayoutService { isMainEditorLayoutCentered(): boolean { return false; } centerMainEditorLayout(_active: boolean): void { } resizePart(_part: Parts, _sizeChangeWidth: number, _sizeChangeHeight: number): void { } - registerPart(part: Part): void { } + registerPart(part: Part): IDisposable { return Disposable.None; } isWindowMaximized(targetWindow: Window) { return false; } updateWindowMaximizedState(targetWindow: Window, maximized: boolean): void { } getVisibleNeighborPart(part: Parts, direction: Direction): Parts | undefined { return undefined; } From 8f062388605e48a3b8d5cbe5eb30c7cc7f2e8558 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 4 Mar 2024 13:33:43 -0800 Subject: [PATCH 1759/1897] fix context key issue for accessible view --- .../chat/browser/terminalChatAccessibleView.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts index c8b11add5be..f5bbe82de03 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts @@ -4,11 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; -import { CTX_INLINE_CHAT_RESPONSE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; @@ -35,6 +33,6 @@ export class TerminalInlineChatAccessibleViewContribution extends Disposable { options: { type: AccessibleViewType.View } }); return true; - }, ContextKeyExpr.and(TerminalChatContextKeys.focused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); + }, TerminalChatContextKeys.focused)); } } From a5abe07cea11b188843780685b47b0de874b48bf Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 4 Mar 2024 13:40:39 -0800 Subject: [PATCH 1760/1897] Remove buffers that shouldn't be validated from `geterr` request (#206843) Fixes #206644 --- .../src/tsServer/bufferSyncSupport.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts index dc5948314d9..9f5d76f5ac3 100644 --- a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts +++ b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts @@ -725,6 +725,13 @@ export default class BufferSyncSupport extends Disposable { orderedFileSet.set(buffer.resource, undefined); } + for (const { resource } of orderedFileSet.entries()) { + const buffer = this.syncedBuffers.get(resource); + if (buffer && !this.shouldValidate(buffer)) { + orderedFileSet.delete(resource); + } + } + if (orderedFileSet.size) { const getErr = this.pendingGetErr = GetErrRequest.executeGetErrRequest(this.client, orderedFileSet, () => { if (this.pendingGetErr === getErr) { From 544094579bac7296ac14ab42c03d5790507f33c3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 4 Mar 2024 22:46:40 +0100 Subject: [PATCH 1761/1897] shared process logging (for #206522) (#206844) --- .../node/sharedProcess/sharedProcessMain.ts | 19 ++++++++++---- .../electron-main/sharedProcess.ts | 25 +++++++++++-------- .../electron-main/utilityProcess.ts | 11 +++++--- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/vs/code/node/sharedProcess/sharedProcessMain.ts b/src/vs/code/node/sharedProcess/sharedProcessMain.ts index 3c0de38db43..81faf87e87b 100644 --- a/src/vs/code/node/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/node/sharedProcess/sharedProcessMain.ts @@ -519,15 +519,24 @@ export async function main(configuration: ISharedProcessConfiguration): Promise< // create shared process and signal back to main that we are // ready to accept message ports as client connections - const sharedProcess = new SharedProcessMain(configuration); - process.parentPort.postMessage(SharedProcessLifecycle.ipcReady); + try { + const sharedProcess = new SharedProcessMain(configuration); + process.parentPort.postMessage(SharedProcessLifecycle.ipcReady); - // await initialization and signal this back to electron-main - await sharedProcess.init(); + // await initialization and signal this back to electron-main + await sharedProcess.init(); - process.parentPort.postMessage(SharedProcessLifecycle.initDone); + process.parentPort.postMessage(SharedProcessLifecycle.initDone); + } catch (error) { + process.parentPort.postMessage({ error: error.toString() }); + } } +const handle = setTimeout(() => { + process.parentPort.postMessage({ warning: '[SharedProcess] did not receive configuration within 30s...' }); +}, 30000); + process.parentPort.once('message', (e: Electron.MessageEvent) => { + clearTimeout(handle); main(e.data as ISharedProcessConfiguration); }); diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index 66defd005d1..087f5858b48 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -6,7 +6,7 @@ import { IpcMainEvent, MessagePortMain } from 'electron'; import { validatedIpcMain } from 'vs/base/parts/ipc/electron-main/ipcMain'; import { Barrier, DeferredPromise } from 'vs/base/common/async'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { ILogService } from 'vs/platform/log/common/log'; @@ -25,6 +25,7 @@ export class SharedProcess extends Disposable { private readonly firstWindowConnectionBarrier = new Barrier(); private utilityProcess: UtilityProcess | undefined = undefined; + private utilityProcessLogListener: IDisposable | undefined = undefined; constructor( private readonly machineId: string, @@ -104,13 +105,10 @@ export class SharedProcess extends Disposable { // all services within have been created. const whenReady = new DeferredPromise(); - if (this.utilityProcess) { - this.utilityProcess.once(SharedProcessLifecycle.initDone, () => whenReady.complete()); - } else { - validatedIpcMain.once(SharedProcessLifecycle.initDone, () => whenReady.complete()); - } + this.utilityProcess?.once(SharedProcessLifecycle.initDone, () => whenReady.complete()); await whenReady.p; + this.utilityProcessLogListener?.dispose(); this.logService.trace('[SharedProcess] Overall ready'); })(); } @@ -131,11 +129,7 @@ export class SharedProcess extends Disposable { // Wait for shared process indicating that IPC connections are accepted const sharedProcessIpcReady = new DeferredPromise(); - if (this.utilityProcess) { - this.utilityProcess.once(SharedProcessLifecycle.ipcReady, () => sharedProcessIpcReady.complete()); - } else { - validatedIpcMain.once(SharedProcessLifecycle.ipcReady, () => sharedProcessIpcReady.complete()); - } + this.utilityProcess?.once(SharedProcessLifecycle.ipcReady, () => sharedProcessIpcReady.complete()); await sharedProcessIpcReady.p; this.logService.trace('[SharedProcess] IPC ready'); @@ -148,6 +142,15 @@ export class SharedProcess extends Disposable { private createUtilityProcess(): void { this.utilityProcess = this._register(new UtilityProcess(this.logService, NullTelemetryService, this.lifecycleMainService)); + // Install a log listener for very early shared process warnings and errors + this.utilityProcessLogListener = this.utilityProcess.onMessage((e: any) => { + if (typeof e.warning === 'string') { + this.logService.warn(e.warning); + } else if (typeof e.error === 'string') { + this.logService.error(e.error); + } + }); + const inspectParams = parseSharedProcessDebugPort(this.environmentMainService.args, this.environmentMainService.isBuilt); let execArgv: string[] | undefined = undefined; if (inspectParams.port) { diff --git a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts index 6bd1d52b986..8386e94dab2 100644 --- a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts +++ b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts @@ -212,7 +212,10 @@ export class UtilityProcess extends Disposable { const started = this.doStart(configuration); if (started && configuration.payload) { - this.postMessage(configuration.payload); + const posted = this.postMessage(configuration.payload); + if (posted) { + this.log('payload sent via postMessage()', Severity.Info); + } } return started; @@ -363,12 +366,14 @@ export class UtilityProcess extends Disposable { })); } - postMessage(message: unknown, transfer?: Electron.MessagePortMain[]): void { + postMessage(message: unknown, transfer?: Electron.MessagePortMain[]): boolean { if (!this.process) { - return; // already killed, crashed or never started + return false; // already killed, crashed or never started } this.process.postMessage(message, transfer); + + return true; } connect(payload?: unknown): Electron.MessagePortMain { From 663376e32d06bf1bf201eb0a01ea5b370ad12b59 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 4 Mar 2024 19:01:13 -0300 Subject: [PATCH 1762/1897] Move more stuff from interactive to participants (#206647) * Remove 'inputPlaceholder' from InteractiveSession * Get rid of 'responder' on InteractiveSession * Remove 'requester' details from interactive session provider, move to default agent * Set up default chat participant to make this test pass * Update error --- .../workbench/api/browser/mainThreadChat.ts | 8 --- .../workbench/api/common/extHost.protocol.ts | 5 -- src/vs/workbench/api/common/extHostChat.ts | 5 -- .../api/common/extHostChatAgents2.ts | 13 +++- .../contrib/chat/browser/chatListRenderer.ts | 7 +- .../browser/contrib/chatInputEditorContrib.ts | 5 +- .../contrib/chat/common/chatAgents.ts | 10 +++ .../contrib/chat/common/chatModel.ts | 64 +++++++++++-------- .../contrib/chat/common/chatService.ts | 5 -- .../contrib/chat/common/chatServiceImpl.ts | 4 +- .../contrib/chat/common/chatViewModel.ts | 25 ++++---- .../__snapshots__/Chat_can_deserialize.0.snap | 3 +- .../__snapshots__/Chat_can_serialize.0.snap | 4 +- .../__snapshots__/Chat_can_serialize.1.snap | 3 +- .../chat/test/common/chatService.test.ts | 6 +- .../inlineChat/browser/inlineChatWidget.ts | 2 +- .../test/browser/inlineChatController.test.ts | 15 +++++ ...scode.proposed.defaultChatParticipant.d.ts | 10 +++ .../vscode.proposed.interactive.d.ts | 12 ---- 19 files changed, 115 insertions(+), 91 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChat.ts b/src/vs/workbench/api/browser/mainThreadChat.ts index e70553d866f..1a4d657cd94 100644 --- a/src/vs/workbench/api/browser/mainThreadChat.ts +++ b/src/vs/workbench/api/browser/mainThreadChat.ts @@ -55,18 +55,10 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { return undefined; } - const responderAvatarIconUri = session.responderAvatarIconUri && - URI.revive(session.responderAvatarIconUri); - const emitter = new Emitter(); this._stateEmitters.set(session.id, emitter); return { id: session.id, - requesterUsername: session.requesterUsername, - requesterAvatarIconUri: URI.revive(session.requesterAvatarIconUri), - responderUsername: session.responderUsername, - responderAvatarIconUri, - inputPlaceholder: session.inputPlaceholder, dispose: () => { emitter.dispose(); this._stateEmitters.delete(session.id); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 575c58a4d24..fb881d05c4f 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1277,11 +1277,6 @@ export interface MainThreadUrlsShape extends IDisposable { export interface IChatDto { id: number; - requesterUsername: string; - requesterAvatarIconUri?: UriComponents; - responderUsername: string; - responderAvatarIconUri?: UriComponents; - inputPlaceholder?: string; } export interface IChatRequestDto { diff --git a/src/vs/workbench/api/common/extHostChat.ts b/src/vs/workbench/api/common/extHostChat.ts index 9b806f07947..036d64b14d2 100644 --- a/src/vs/workbench/api/common/extHostChat.ts +++ b/src/vs/workbench/api/common/extHostChat.ts @@ -74,11 +74,6 @@ export class ExtHostChat implements ExtHostChatShape { return { id, - requesterUsername: session.requester?.name, - requesterAvatarIconUri: session.requester?.icon, - responderUsername: session.responder?.name, - responderAvatarIconUri: session.responder?.icon, - inputPlaceholder: session.inputPlaceholder, }; } diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 07a808d31b9..7a4c69dc8f3 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -213,7 +213,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } catch (e) { this._logService.error(e, agent.extension); - return { errorDetails: { message: localize('errorResponse', "Error from provider: {0}", toErrorMessage(e)), responseIsIncomplete: true } }; + return { errorDetails: { message: localize('errorResponse', "Error from participant: {0}", toErrorMessage(e)), responseIsIncomplete: true } }; } finally { stream.close(); @@ -353,6 +353,7 @@ class ExtHostChatAgent { private _agentVariableProvider?: { provider: vscode.ChatParticipantCompletionItemProvider; triggerCharacters: string[] }; private _welcomeMessageProvider?: vscode.ChatWelcomeMessageProvider | undefined; private _isSticky: boolean | undefined; + private _requester: vscode.ChatRequesterInformation | undefined; constructor( public readonly extension: IExtensionDescription, @@ -436,7 +437,7 @@ class ExtHostChatAgent { updateScheduled = true; queueMicrotask(() => { this._proxy.$updateAgent(this._handle, { - description: this._description ?? '', + description: this._description, fullName: this._fullName, icon: !this._iconPath ? undefined : this._iconPath instanceof URI ? this._iconPath : @@ -454,6 +455,7 @@ class ExtHostChatAgent { sampleRequest: this._sampleRequest, supportIssueReporting: this._supportIssueReporting, isSticky: this._isSticky, + requester: this._requester }); updateScheduled = false; }); @@ -602,6 +604,13 @@ class ExtHostChatAgent { that._isSticky = v; updateMetadataSoon(); }, + set requester(v) { + that._requester = v; + updateMetadataSoon(); + }, + get requester() { + return that._requester; + }, dispose() { disposed = true; that._followupProvider = undefined; diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 94b0a9b2dc1..f97e96548ff 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -384,13 +384,14 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer('img.icon'); - avatarImgIcon.src = FileAccess.uriToBrowserUri(element.avatarIconUri).toString(true); + avatarImgIcon.src = FileAccess.uriToBrowserUri(element.avatarIcon).toString(true); templateData.avatarContainer.replaceChildren(dom.$('.avatar', undefined, avatarImgIcon)); } else { const defaultIcon = isRequestVM(element) ? Codicon.account : Codicon.copilot; - const avatarIcon = dom.$(ThemeIcon.asCSSSelector(defaultIcon)); + const icon = element.avatarIcon ?? defaultIcon; + const avatarIcon = dom.$(ThemeIcon.asCSSSelector(icon)); templateData.avatarContainer.replaceChildren(dom.$('.avatar.codicon-avatar', undefined, avatarIcon)); } diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 287db1631ac..0bc244b4cb4 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -124,8 +124,7 @@ class InputEditorDecorations extends Disposable { } if (!inputValue) { - const viewModelPlaceholder = this.widget.viewModel?.inputPlaceholder; - const placeholder = viewModelPlaceholder ?? ''; + const defaultAgent = this.chatAgentService.getDefaultAgent(); const decoration: IDecorationOptions[] = [ { range: { @@ -136,7 +135,7 @@ class InputEditorDecorations extends Disposable { }, renderOptions: { after: { - contentText: placeholder, + contentText: viewModel.inputPlaceholder ?? defaultAgent?.metadata.description ?? '', color: this.getPlaceholderColor() } } diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 2d9318102b8..46854de6a5e 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -71,6 +71,15 @@ export interface IChatAgentCommand { sampleRequest?: string; } +export interface IChatRequesterInformation { + name: string; + + /** + * A full URI for the icon of the requester. + */ + icon?: URI; +} + export interface IChatAgentMetadata { description?: string; helpTextPrefix?: string | IMarkdownString; @@ -85,6 +94,7 @@ export interface IChatAgentMetadata { supportIssueReporting?: boolean; followupPlaceholder?: string; isSticky?: boolean; + requester?: IChatRequesterInformation; } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index ed06c32958f..a2c05d68ca0 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -10,9 +10,11 @@ import { IMarkdownString, MarkdownString, isMarkdownString } from 'vs/base/commo import { Disposable } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; import { basename } from 'vs/base/common/resources'; -import { URI, UriComponents, UriDto } from 'vs/base/common/uri'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { URI, UriComponents, UriDto, isUriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IOffsetRange, OffsetRange } from 'vs/editor/common/core/offsetRange'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestTextPart, IParsedChatRequest, getPromptText, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; @@ -54,7 +56,7 @@ export interface IChatResponseModel { readonly providerId: string; readonly requestId: string; readonly username: string; - readonly avatarIconUri?: URI; + readonly avatarIcon?: ThemeIcon | URI; readonly session: IChatModel; readonly agent?: IChatAgentData; readonly usedContext: IChatUsedContext | undefined; @@ -234,8 +236,8 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this.session.responderUsername; } - public get avatarIconUri(): URI | undefined { - return this.session.responderAvatarIconUri; + public get avatarIcon(): ThemeIcon | URI | undefined { + return this.session.responderAvatarIcon; } private _followups?: IChatFollowup[]; @@ -392,7 +394,7 @@ export interface IExportableChatData { requesterUsername: string; responderUsername: string; requesterAvatarIconUri: UriComponents | undefined; - responderAvatarIconUri: UriComponents | undefined; + responderAvatarIconUri: ThemeIcon | UriComponents | undefined; // Keeping Uri name for backcompat } export interface ISerializableChatData extends IExportableChatData { @@ -405,8 +407,7 @@ export function isExportableSessionData(obj: unknown): obj is IExportableChatDat const data = obj as IExportableChatData; return typeof data === 'object' && typeof data.providerId === 'string' && - typeof data.requesterUsername === 'string' && - typeof data.responderUsername === 'string'; + typeof data.requesterUsername === 'string'; } export function isSerializableSessionData(obj: unknown): obj is ISerializableChatData { @@ -483,10 +484,6 @@ export class ChatModel extends Disposable implements IChatModel { return this._sessionId; } - get inputPlaceholder(): string | undefined { - return this._session?.inputPlaceholder; - } - get requestInProgress(): boolean { const lastRequest = this._requests[this._requests.length - 1]; return !!lastRequest && !!lastRequest.response && !lastRequest.response.isComplete; @@ -497,22 +494,34 @@ export class ChatModel extends Disposable implements IChatModel { return this._creationDate; } + private get _defaultAgent() { + return this.chatAgentService.getDefaultAgent(); + } + get requesterUsername(): string { - return this._session?.requesterUsername ?? this.initialData?.requesterUsername ?? ''; + return (this._defaultAgent ? + this._defaultAgent.metadata.requester?.name : + this.initialData?.requesterUsername) ?? ''; } get responderUsername(): string { - return this._session?.responderUsername ?? this.initialData?.responderUsername ?? ''; + return (this._defaultAgent ? + this._defaultAgent.metadata.fullName : + this.initialData?.responderUsername) ?? ''; } private readonly _initialRequesterAvatarIconUri: URI | undefined; get requesterAvatarIconUri(): URI | undefined { - return this._session ? this._session.requesterAvatarIconUri : this._initialRequesterAvatarIconUri; + return this._defaultAgent ? + this._defaultAgent.metadata.requester?.icon : + this._initialRequesterAvatarIconUri; } - private readonly _initialResponderAvatarIconUri: URI | undefined; - get responderAvatarIconUri(): URI | undefined { - return this._session ? this._session.responderAvatarIconUri : this._initialResponderAvatarIconUri; + private readonly _initialResponderAvatarIconUri: ThemeIcon | URI | undefined; + get responderAvatarIcon(): ThemeIcon | URI | undefined { + return this._defaultAgent ? + this._defaultAgent?.metadata.themeIcon : + this._initialResponderAvatarIconUri; } get initState(): ChatModelInitState { @@ -533,6 +542,7 @@ export class ChatModel extends Disposable implements IChatModel { private readonly initialData: ISerializableChatData | IExportableChatData | undefined, @ILogService private readonly logService: ILogService, @IChatAgentService private readonly chatAgentService: IChatAgentService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); @@ -542,7 +552,7 @@ export class ChatModel extends Disposable implements IChatModel { this._creationDate = (isSerializableSessionData(initialData) && initialData.creationDate) || Date.now(); this._initialRequesterAvatarIconUri = initialData?.requesterAvatarIconUri && URI.revive(initialData.requesterAvatarIconUri); - this._initialResponderAvatarIconUri = initialData?.responderAvatarIconUri && URI.revive(initialData.responderAvatarIconUri); + this._initialResponderAvatarIconUri = isUriComponents(initialData?.responderAvatarIconUri) ? URI.revive(initialData.responderAvatarIconUri) : initialData?.responderAvatarIconUri; } private _deserialize(obj: IExportableChatData): ChatRequestModel[] { @@ -554,7 +564,7 @@ export class ChatModel extends Disposable implements IChatModel { if (obj.welcomeMessage) { const content = obj.welcomeMessage.map(item => typeof item === 'string' ? new MarkdownString(item) : item); - this._welcomeMessage = new ChatWelcomeMessageModel(this, content, []); + this._welcomeMessage = this.instantiationService.createInstance(ChatWelcomeMessageModel, content, []); } try { @@ -748,7 +758,7 @@ export class ChatModel extends Disposable implements IChatModel { requesterUsername: this.requesterUsername, requesterAvatarIconUri: this.requesterAvatarIconUri, responderUsername: this.responderUsername, - responderAvatarIconUri: this.responderAvatarIconUri, + responderAvatarIconUri: this.responderAvatarIcon, welcomeMessage: this._welcomeMessage?.content.map(c => { if (Array.isArray(c)) { return c; @@ -782,7 +792,7 @@ export class ChatModel extends Disposable implements IChatModel { vote: r.response?.vote, agent: r.response?.agent ? // May actually be the full IChatAgent instance, just take the data props. slashCommands don't matter here. - { id: r.response.agent.id, extensionId: r.response.agent.extensionId, metadata: r.response.agent.metadata, slashCommands: [] } + { id: r.response.agent.id, extensionId: r.response.agent.extensionId, metadata: r.response.agent.metadata, slashCommands: [], isDefault: r.response.agent.isDefault } : undefined, slashCommand: r.response?.slashCommand, usedContext: r.response?.usedContext, @@ -818,7 +828,7 @@ export interface IChatWelcomeMessageModel { readonly content: IChatWelcomeMessageContent[]; readonly sampleQuestions: IChatFollowup[]; readonly username: string; - readonly avatarIconUri?: URI; + readonly avatarIcon?: ThemeIcon; } @@ -831,19 +841,19 @@ export class ChatWelcomeMessageModel implements IChatWelcomeMessageModel { } constructor( - private readonly session: ChatModel, public readonly content: IChatWelcomeMessageContent[], - public readonly sampleQuestions: IChatFollowup[] + public readonly sampleQuestions: IChatFollowup[], + @IChatAgentService private readonly chatAgentService: IChatAgentService, ) { this._id = 'welcome_' + ChatWelcomeMessageModel.nextId++; } public get username(): string { - return this.session.responderUsername; + return this.chatAgentService.getDefaultAgent()?.metadata.fullName ?? ''; } - public get avatarIconUri(): URI | undefined { - return this.session.responderAvatarIconUri; + public get avatarIcon(): ThemeIcon | undefined { + return this.chatAgentService.getDefaultAgent()?.metadata.themeIcon; } } diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 360d8ea0e34..c7b1a1eec48 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -19,11 +19,6 @@ import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chat export interface IChat { id: number; // TODO Maybe remove this and move to a subclass that only the provider knows about - requesterUsername: string; - requesterAvatarIconUri?: URI; - responderUsername: string; - responderAvatarIconUri?: URI; - inputPlaceholder?: string; dispose?(): void; } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index e254395407f..ac232bc4dba 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -388,8 +388,8 @@ export class ChatService extends Disposable implements IChatService { } const welcomeMessage = model.welcomeMessage ? undefined : await defaultAgent.provideWelcomeMessage?.(token) ?? undefined; - const welcomeModel = welcomeMessage && new ChatWelcomeMessageModel( - model, + const welcomeModel = welcomeMessage && this.instantiationService.createInstance( + ChatWelcomeMessageModel, welcomeMessage.map(item => typeof item === 'string' ? new MarkdownString(item) : item), await defaultAgent.provideSampleQuestions?.(token) ?? [] ); diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index fc1328ca803..fa329a908bd 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -6,6 +6,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { marked } from 'vs/base/common/marked/marked'; +import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; import { ILanguageService } from 'vs/editor/common/languages/language'; @@ -65,7 +66,7 @@ export interface IChatRequestViewModel { /** This ID updates every time the underlying data changes */ readonly dataId: string; readonly username: string; - readonly avatarIconUri?: URI; + readonly avatarIcon?: URI | ThemeIcon; readonly message: IParsedChatRequest | IChatFollowup; readonly messageText: string; currentRenderedHeight: number | undefined; @@ -115,7 +116,7 @@ export interface IChatResponseViewModel { /** The ID of the associated IChatRequestViewModel */ readonly requestId: string; readonly username: string; - readonly avatarIconUri?: URI; + readonly avatarIcon?: URI | ThemeIcon; readonly agent?: IChatAgentData; readonly slashCommand?: IChatAgentCommand; readonly response: IResponse; @@ -150,7 +151,7 @@ export class ChatViewModel extends Disposable implements IChatViewModel { private _inputPlaceholder: string | undefined = undefined; get inputPlaceholder(): string | undefined { - return this._inputPlaceholder ?? this._model.inputPlaceholder; + return this._inputPlaceholder; } get model(): IChatModel { @@ -192,7 +193,7 @@ export class ChatViewModel extends Disposable implements IChatViewModel { super(); _model.getRequests().forEach((request, i) => { - const requestModel = new ChatRequestViewModel(request); + const requestModel = this.instantiationService.createInstance(ChatRequestViewModel, request); this._items.push(requestModel); this.updateCodeBlockTextModels(requestModel); @@ -204,7 +205,7 @@ export class ChatViewModel extends Disposable implements IChatViewModel { this._register(_model.onDidDispose(() => this._onDidDisposeModel.fire())); this._register(_model.onDidChange(e => { if (e.kind === 'addRequest') { - const requestModel = new ChatRequestViewModel(e.request); + const requestModel = this.instantiationService.createInstance(ChatRequestViewModel, e.request); this._items.push(requestModel); this.updateCodeBlockTextModels(requestModel); @@ -348,7 +349,7 @@ export class ChatRequestViewModel implements IChatRequestViewModel { return this._model.username; } - get avatarIconUri() { + get avatarIcon() { return this._model.avatarIconUri; } @@ -362,7 +363,9 @@ export class ChatRequestViewModel implements IChatRequestViewModel { currentRenderedHeight: number | undefined; - constructor(readonly _model: IChatRequestModel) { } + constructor( + readonly _model: IChatRequestModel, + ) { } } export class ChatResponseViewModel extends Disposable implements IChatResponseViewModel { @@ -391,8 +394,8 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.username; } - get avatarIconUri() { - return this._model.avatarIconUri; + get avatarIcon() { + return this._model.avatarIcon; } get agent() { @@ -484,7 +487,7 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi constructor( private readonly _model: IChatResponseModel, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, ) { super(); @@ -535,7 +538,7 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi export interface IChatWelcomeMessageViewModel { readonly id: string; readonly username: string; - readonly avatarIconUri?: URI; + readonly avatarIcon?: URI | ThemeIcon; readonly content: IChatWelcomeMessageContent[]; readonly sampleQuestions: IChatFollowup[]; currentRenderedHeight?: number; diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap index 9d4646eb6a9..cbd61e5811d 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap @@ -55,7 +55,8 @@ _lower: "nullextensiondescription" }, metadata: { description: undefined }, - slashCommands: [ ] + slashCommands: [ ], + isDefault: undefined }, slashCommand: undefined, usedContext: { diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.0.snap index 0939983222f..75c5fa71f40 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.0.snap @@ -1,7 +1,7 @@ { - requesterUsername: "", + requesterUsername: "test", requesterAvatarIconUri: undefined, - responderUsername: "", + responderUsername: "test", responderAvatarIconUri: undefined, welcomeMessage: undefined, requests: [ ], diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap index 98d57a6bd2b..f231c781190 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap @@ -55,7 +55,8 @@ _lower: "nullextensiondescription" }, metadata: { description: undefined }, - slashCommands: [ ] + slashCommands: [ ], + isDefault: undefined }, slashCommand: undefined, usedContext: { 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 ab9ef913673..eb6bee1c3fa 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -46,8 +46,6 @@ class SimpleTestProvider extends Disposable implements IChatProvider { async prepareSession(): Promise { return { id: SimpleTestProvider.sessionId++, - responderUsername: 'test', - requesterUsername: 'test', }; } @@ -108,7 +106,7 @@ suite('Chat', () => { instantiationService.stub(IChatContributionService, new MockChatContributionService( [ { extensionId: nullExtensionDescription.identifier, name: 'testAgent', isDefault: true }, - { extensionId: nullExtensionDescription.identifier, name: chatAgentWithUsedContextId, isDefault: true }, + { extensionId: nullExtensionDescription.identifier, name: chatAgentWithUsedContextId }, ])); chatAgentService = testDisposables.add(instantiationService.createInstance(ChatAgentService)); @@ -120,6 +118,7 @@ suite('Chat', () => { }, } satisfies IChatAgentImplementation; testDisposables.add(chatAgentService.registerAgent('testAgent', agent)); + chatAgentService.updateAgent('testAgent', { requester: { name: 'test' }, fullName: 'test' }); }); test('retrieveSession', async () => { @@ -210,6 +209,7 @@ suite('Chat', () => { test('can serialize', async () => { testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContext.id, chatAgentWithUsedContext)); + chatAgentService.updateAgent(chatAgentWithUsedContextId, { requester: { name: 'test' }, fullName: 'test' }); const testService = testDisposables.add(instantiationService.createInstance(ChatService)); testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider')))); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 5af8f2acc53..6f1cd922882 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -615,7 +615,7 @@ export class InlineChatWidget { this._ctxMessageCropState.reset(); expansionState = ExpansionState.NOT_CROPPED; } else { - const sessionModel = this._chatMessageDisposables.add(new ChatModel(message.providerId, undefined, this._logService, this._chatAgentService)); + const sessionModel = this._chatMessageDisposables.add(new ChatModel(message.providerId, undefined, this._logService, this._chatAgentService, this._instantiationService)); const responseModel = this._chatMessageDisposables.add(new ChatResponseModel(message.message, sessionModel, undefined, undefined, message.requestId, !isIncomplete, false, undefined)); const viewModel = this._chatMessageDisposables.add(new ChatResponseViewModel(responseModel, this._logService)); const renderOptions: IChatListItemRendererOptions = { renderStyle: 'compact', noHeader: true, noPadding: true }; diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 6fd5722bc72..276c86cb858 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -44,6 +44,10 @@ import { TestWorkerService } from './testWorkerService'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { Schemas } from 'vs/base/common/network'; import { MarkdownString } from 'vs/base/common/htmlContent'; +import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; +import { MockChatContributionService } from 'vs/workbench/contrib/chat/test/common/mockChatContributionService'; +import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { ChatAgentService, IChatAgentImplementation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; suite('InteractiveChatController', function () { class TestController extends InlineChatController { @@ -113,6 +117,9 @@ suite('InteractiveChatController', function () { const serviceCollection = new ServiceCollection( [IEditorWorkerService, new SyncDescriptor(TestWorkerService)], [IContextKeyService, contextKeyService], + [IChatContributionService, new MockChatContributionService( + [{ extensionId: nullExtensionDescription.identifier, name: 'testAgent', isDefault: true }])], + [IChatAgentService, new SyncDescriptor(ChatAgentService)], [IInlineChatService, inlineChatService], [IDiffProviderFactoryService, new SyncDescriptor(TestDiffProviderFactoryService)], [IInlineChatSessionService, new SyncDescriptor(InlineChatSessionServiceImpl)], @@ -146,6 +153,14 @@ suite('InteractiveChatController', function () { ); instaService = store.add(workbenchInstantiationService(undefined, store).createChild(serviceCollection)); + const chatAgentService = instaService.get(IChatAgentService); + const agent = { + async invoke(request, progress, history, token) { + return {}; + }, + } satisfies IChatAgentImplementation; + store.add(chatAgentService.registerAgent('testAgent', agent)); + inlineChatSessionService = store.add(instaService.get(IInlineChatSessionService)); model = store.add(instaService.get(IModelService).createModel('Hello\nWorld\nHello Again\nHello World\n', null)); diff --git a/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts b/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts index 07a2b6f5c42..377187539be 100644 --- a/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts @@ -12,6 +12,15 @@ declare module 'vscode' { provideSampleQuestions?(token: CancellationToken): ProviderResult; } + export interface ChatRequesterInformation { + name: string; + + /** + * A full URI for the icon of the request. + */ + icon?: Uri; + } + export interface ChatParticipant { /** * When true, this participant is invoked by default when no other participant is being invoked @@ -45,5 +54,6 @@ declare module 'vscode' { helpTextPostfix?: string | MarkdownString; welcomeMessageProvider?: ChatWelcomeMessageProvider; + requester?: ChatRequesterInformation; } } diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index 852f8830de2..bf9d7d26045 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -110,19 +110,7 @@ declare module 'vscode' { handleInteractiveEditorResponseFeedback?(session: S, response: R, kind: InteractiveEditorResponseFeedbackKind): void; } - export interface InteractiveSessionParticipantInformation { - name: string; - - /** - * A full URI for the icon of the participant. - */ - icon?: Uri; - } - export interface InteractiveSession { - requester: InteractiveSessionParticipantInformation; - responder: InteractiveSessionParticipantInformation; - inputPlaceholder?: string; } export interface InteractiveSessionProvider { From 5abb3084473d96ce0ef7ace38ccd3d8dd8e6a9f3 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:26:18 -0600 Subject: [PATCH 1763/1897] Fix go to file button on quick search (#206846) --- .../browser/quickTextSearch/textSearchQuickAccess.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index c4e31c3c903..2c9474be1e9 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -15,9 +15,9 @@ import { ITextEditorSelection } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { WorkbenchCompressibleObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService'; -import { FastAndSlowPicks, IPickerQuickAccessItem, PickerQuickAccessProvider, Picks, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; +import { FastAndSlowPicks, IPickerQuickAccessItem, IPickerQuickAccessSeparator, PickerQuickAccessProvider, Picks, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { DefaultQuickAccessFilterValue, IQuickAccessProviderRunOptions } from 'vs/platform/quickinput/common/quickAccess'; -import { IKeyMods, IQuickPick, IQuickPickItem, IQuickPickSeparator, QuickInputHideReason } from 'vs/platform/quickinput/common/quickInput'; +import { IKeyMods, IQuickPick, IQuickPickItem, QuickInputHideReason } from 'vs/platform/quickinput/common/quickInput'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; import { searchDetailsIcon, searchOpenInFileIcon, searchActivityBarIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; @@ -217,11 +217,11 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider limit ? matches.slice(0, limit) : matches; - const picks: Array = []; + const picks: Array = []; for (let fileIndex = 0; fileIndex < matches.length; fileIndex++) { if (fileIndex === limit) { @@ -254,6 +254,10 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider => { + await this.handleAccept(fileMatch, {}); + return TriggerAction.CLOSE_PICKER; + }, }); const results: Match[] = fileMatch.matches() ?? []; From 191be39e5ac872e03f9d79cc859d9917f40ad935 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Mon, 4 Mar 2024 15:01:51 -0800 Subject: [PATCH 1764/1897] Make sure the same GitHub account is used until we support multiple GH accounts (#206847) Fixes https://github.com/microsoft/vscode/issues/203850 --- .../src/common/errors.ts | 4 ++ extensions/github-authentication/src/flows.ts | 13 ++++++- .../github-authentication/src/github.ts | 37 +++++++++++++++++-- .../github-authentication/src/githubServer.ts | 11 ++---- 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/extensions/github-authentication/src/common/errors.ts b/extensions/github-authentication/src/common/errors.ts index 3ba3dfc006a..f60b7233499 100644 --- a/extensions/github-authentication/src/common/errors.ts +++ b/extensions/github-authentication/src/common/errors.ts @@ -8,3 +8,7 @@ export const TIMED_OUT_ERROR = 'Timed out'; // These error messages are internal and should not be shown to the user in any way. export const USER_CANCELLATION_ERROR = 'User Cancelled'; export const NETWORK_ERROR = 'network error'; + +// This is the error message that we throw if the login was cancelled for any reason. Extensions +// calling `getSession` can handle this error to know that the user cancelled the login. +export const CANCELLATION_ERROR = 'Cancelled'; diff --git a/extensions/github-authentication/src/flows.ts b/extensions/github-authentication/src/flows.ts index 3641ffb3a36..7498a2b2202 100644 --- a/extensions/github-authentication/src/flows.ts +++ b/extensions/github-authentication/src/flows.ts @@ -68,6 +68,7 @@ interface IFlowTriggerOptions { callbackUri: Uri; uriHandler: UriEventHandler; enterpriseUri?: Uri; + existingLogin?: string; } interface IFlow { @@ -149,7 +150,8 @@ const allFlows: IFlow[] = [ nonce, callbackUri, uriHandler, - enterpriseUri + enterpriseUri, + existingLogin }: IFlowTriggerOptions): Promise { logger.info(`Trying without local server... (${scopes})`); return await window.withProgress({ @@ -169,6 +171,9 @@ const allFlows: IFlow[] = [ ['scope', scopes], ['state', encodeURIComponent(callbackUri.toString(true))] ]); + if (existingLogin) { + searchParams.append('login', existingLogin); + } // The extra toString, parse is apparently needed for env.openExternal // to open the correct URL. @@ -215,7 +220,8 @@ const allFlows: IFlow[] = [ baseUri, redirectUri, logger, - enterpriseUri + enterpriseUri, + existingLogin }: IFlowTriggerOptions): Promise { logger.info(`Trying with local server... (${scopes})`); return await window.withProgress({ @@ -232,6 +238,9 @@ const allFlows: IFlow[] = [ ['redirect_uri', redirectUri.toString(true)], ['scope', scopes], ]); + if (existingLogin) { + searchParams.append('login', existingLogin); + } const loginUrl = baseUri.with({ path: '/login/oauth/authorize', diff --git a/extensions/github-authentication/src/github.ts b/extensions/github-authentication/src/github.ts index 71aa17bd5cc..6c9b1f20294 100644 --- a/extensions/github-authentication/src/github.ts +++ b/extensions/github-authentication/src/github.ts @@ -11,7 +11,7 @@ import { PromiseAdapter, arrayEquals, promiseFromEvent } from './common/utils'; import { ExperimentationTelemetry } from './common/experimentationService'; import { Log } from './common/logger'; import { crypto } from './node/crypto'; -import { TIMED_OUT_ERROR, USER_CANCELLATION_ERROR } from './common/errors'; +import { CANCELLATION_ERROR, TIMED_OUT_ERROR, USER_CANCELLATION_ERROR } from './common/errors'; interface SessionData { id: string; @@ -296,13 +296,44 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid scopes: JSON.stringify(scopes), }); + const sessions = await this._sessionsPromise; const scopeString = sortedScopes.join(' '); - const token = await this._githubServer.login(scopeString); + const existingLogin = sessions[0]?.account.label; + const token = await this._githubServer.login(scopeString, existingLogin); const session = await this.tokenToSession(token, scopes); this.afterSessionLoad(session); - const sessions = await this._sessionsPromise; + if (sessions.some(s => s.id !== session.id)) { + const otherAccountsIndexes = new Array(); + const otherAccountsLabels = new Set(); + for (let i = 0; i < sessions.length; i++) { + if (sessions[i].id !== session.id) { + otherAccountsIndexes.push(i); + otherAccountsLabels.add(sessions[i].account.label); + } + } + const proceed = vscode.l10n.t("Continue"); + const labelstr = [...otherAccountsLabels].join(', '); + const result = await vscode.window.showInformationMessage( + vscode.l10n.t({ + message: "You are logged into another account already ({0}).\n\nDo you want to log out of that account and log in to '{1}' instead?", + comment: ['{0} is a comma-separated list of account names. {1} is the account name to log into.'], + args: [labelstr, session.account.label] + }), + { modal: true }, + proceed + ); + if (result !== proceed) { + throw new Error(CANCELLATION_ERROR); + } + + // Remove other accounts + for (const i of otherAccountsIndexes) { + sessions.splice(i, 1); + } + } + const sessionIndex = sessions.findIndex(s => s.id === session.id || arrayEquals([...s.scopes].sort(), sortedScopes)); if (sessionIndex > -1) { sessions.splice(sessionIndex, 1, session); diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index 0729c4c5077..af2cf22724f 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -11,19 +11,15 @@ import { isSupportedClient, isSupportedTarget } from './common/env'; import { crypto } from './node/crypto'; import { fetching } from './node/fetch'; import { ExtensionHost, GitHubTarget, getFlows } from './flows'; -import { NETWORK_ERROR, USER_CANCELLATION_ERROR } from './common/errors'; +import { CANCELLATION_ERROR, NETWORK_ERROR, USER_CANCELLATION_ERROR } from './common/errors'; import { Config } from './config'; import { base64Encode } from './node/buffer'; -// This is the error message that we throw if the login was cancelled for any reason. Extensions -// calling `getSession` can handle this error to know that the user cancelled the login. -const CANCELLATION_ERROR = 'Cancelled'; - const REDIRECT_URL_STABLE = 'https://vscode.dev/redirect'; const REDIRECT_URL_INSIDERS = 'https://insiders.vscode.dev/redirect'; export interface IGitHubServer { - login(scopes: string): Promise; + login(scopes: string, existingLogin?: string): Promise; logout(session: vscode.AuthenticationSession): Promise; getUserInfo(token: string): Promise<{ id: string; accountName: string }>; sendAdditionalTelemetryInfo(session: vscode.AuthenticationSession): Promise; @@ -91,7 +87,7 @@ export class GitHubServer implements IGitHubServer { return this._isNoCorsEnvironment; } - public async login(scopes: string): Promise { + public async login(scopes: string, existingLogin?: string): Promise { this._logger.info(`Logging in for the following scopes: ${scopes}`); // Used for showing a friendlier message to the user when the explicitly cancel a flow. @@ -143,6 +139,7 @@ export class GitHubServer implements IGitHubServer { uriHandler: this._uriHandler, enterpriseUri: this._ghesUri, redirectUri: vscode.Uri.parse(await this.getRedirectEndpoint()), + existingLogin }); } catch (e) { userCancelled = this.processLoginError(e); From 7c9b58252948d0e73619f9accf78b69441d9068d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 4 Mar 2024 15:02:30 -0800 Subject: [PATCH 1765/1897] get all actions to work --- .../accessibility/browser/accessibleView.ts | 22 +++++++- .../browser/actions/chatCodeblockActions.ts | 52 ++++++++++++------- .../contrib/chat/browser/codeBlockPart.ts | 2 +- 3 files changed, 55 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 8c59b8fd4cd..294cc709daf 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -42,6 +42,7 @@ import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQui import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { AccessibilityVerbositySettingId, AccessibilityWorkbenchSettingId, AccessibleViewProviderId, accessibilityHelpIsShown, accessibleViewContainsCodeBlocks, accessibleViewCurrentProviderId, accessibleViewGoToSymbolSupported, accessibleViewInCodeBlock, accessibleViewIsShown, accessibleViewOnLastLine, accessibleViewSupportsNavigation, accessibleViewVerbosityEnabled } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; +import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; const enum DIMENSIONS { @@ -91,6 +92,7 @@ export interface IAccessibleViewService { * @param verbositySettingKey The setting key for the verbosity of the feature */ getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string | null; + getCodeBlockContext(): ICodeBlockActionContext | undefined; } export const enum AccessibleViewType { @@ -131,6 +133,7 @@ interface ICodeBlock { startLine: number; endLine: number; code: string; + languageId?: string; } export class AccessibleView extends Disposable { @@ -272,6 +275,18 @@ export class AccessibleView extends Disposable { } } + getCodeBlockContext(): ICodeBlockActionContext | undefined { + const position = this._editorWidget.getPosition(); + if (!this._codeBlocks?.length || !position) { + return; + } + const codeBlockIndex = this._codeBlocks?.findIndex(c => c.startLine <= position?.lineNumber && c.endLine >= position?.lineNumber); + const codeBlock = codeBlockIndex !== undefined && codeBlockIndex > -1 ? this._codeBlocks[codeBlockIndex] : undefined; + if (!codeBlock || codeBlockIndex === undefined) { + return; + } + return { code: codeBlock.code, languageId: codeBlock.languageId, codeBlockIndex, element: undefined }; + } showLastProvider(id: AccessibleViewProviderId): void { if (!this._lastProvider || this._lastProvider.options.id !== id) { @@ -360,14 +375,16 @@ export class AccessibleView extends Disposable { let startLine = 0; lines.forEach((line, i) => { + let languageId: string | undefined; if (!inBlock && line.startsWith('```')) { inBlock = true; startLine = i + 1; + languageId = line.substring(3).trim(); } else if (inBlock && line.startsWith('```')) { inBlock = false; const endLine = i; const code = lines.slice(startLine, endLine).join('\n'); - this._codeBlocks?.push({ startLine, endLine, code }); + this._codeBlocks?.push({ startLine, endLine, code, languageId }); } }); this._accessibleViewContainsCodeBlocks.set(this._codeBlocks.length > 0); @@ -783,6 +800,9 @@ export class AccessibleViewService extends Disposable implements IAccessibleView editorWidget?.revealLine(position.lineNumber); } } + getCodeBlockContext(): ICodeBlockActionContext | undefined { + return this._accessibleView?.getCodeBlockContext(); + } } class AccessibleViewSymbolQuickPick { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 27f5c9f07f8..0db49de0bf8 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -24,6 +24,8 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TerminalLocation } from 'vs/platform/terminal/common/terminal'; import { IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor'; +import { accessibleViewInCodeBlock } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; @@ -93,7 +95,11 @@ export function registerChatCodeBlockActions() { } run(accessor: ServicesAccessor, ...args: any[]) { - const context = args[0]; + const accessibleViewService = accessor.get(IAccessibleViewService); + let context = args[0]; + if (!context) { + context = accessibleViewService.getCodeBlockContext(); + } if (!isCodeBlockActionContext(context) || isResponseFiltered(context)) { return; } @@ -147,21 +153,24 @@ export function registerChatCodeBlockActions() { // Report copy to extensions const chatService = accessor.get(IChatService); - chatService.notifyUserAction({ - providerId: context.element.providerId, - agentId: context.element.agent?.id, - sessionId: context.element.sessionId, - requestId: context.element.requestId, - result: context.element.result, - action: { - kind: 'copy', - codeBlockIndex: context.codeBlockIndex, - copyKind: ChatCopyKind.Action, - copiedText, - copiedCharacters: copiedText.length, - totalCharacters, - } - }); + const element = context.element as IChatResponseViewModel | undefined; + if (element) { + chatService.notifyUserAction({ + providerId: element.providerId, + agentId: element.agent?.id, + sessionId: element.sessionId, + requestId: element.requestId, + result: element.result, + action: { + kind: 'copy', + codeBlockIndex: context.codeBlockIndex, + copyKind: ChatCopyKind.Action, + copiedText, + copiedCharacters: copiedText.length, + totalCharacters, + } + }); + } // Copy full cell if no selection, otherwise fall back on normal editor implementation if (noSelection) { @@ -187,7 +196,7 @@ export function registerChatCodeBlockActions() { when: CONTEXT_IN_CHAT_SESSION, }, keybinding: { - when: ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_CHAT_INPUT.negate()), + when: ContextKeyExpr.or(ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_CHAT_INPUT.negate()), accessibleViewInCodeBlock), primary: KeyMod.CtrlCmd | KeyCode.Enter, mac: { primary: KeyMod.WinCtrl | KeyCode.Enter }, weight: KeybindingWeight.WorkbenchContrib @@ -431,7 +440,7 @@ export function registerChatCodeBlockActions() { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Enter }, weight: KeybindingWeight.EditorContrib, - when: CONTEXT_IN_CHAT_SESSION, + when: ContextKeyExpr.or(CONTEXT_IN_CHAT_SESSION, accessibleViewInCodeBlock), }] }); } @@ -557,8 +566,13 @@ export function registerChatCodeBlockActions() { }); } -function getContextFromEditor(editor: ICodeEditor, accessor: ServicesAccessor): IChatCodeBlockActionContext | undefined { +function getContextFromEditor(editor: ICodeEditor, accessor: ServicesAccessor): ICodeBlockActionContext | undefined { const chatWidgetService = accessor.get(IChatWidgetService); + const accessibleViewService = accessor.get(IAccessibleViewService); + const accessibleViewCodeBlock = accessibleViewService.getCodeBlockContext(); + if (accessibleViewCodeBlock) { + return accessibleViewCodeBlock; + } const model = editor.getModel(); if (!model) { return; diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index b6d4500e448..d1531d7db44 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -101,8 +101,8 @@ export function parseLocalFileData(text: string) { export interface ICodeBlockActionContext { code: string; - languageId: string; codeBlockIndex: number; + languageId?: string; element: unknown; } From 5f511caaaf94354770a9e74ca9daa408c4a8ee62 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 4 Mar 2024 15:05:26 -0800 Subject: [PATCH 1766/1897] reorder --- src/vs/workbench/contrib/chat/browser/codeBlockPart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index d1531d7db44..a39f69dbac8 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -101,8 +101,8 @@ export function parseLocalFileData(text: string) { export interface ICodeBlockActionContext { code: string; - codeBlockIndex: number; languageId?: string; + codeBlockIndex: number; element: unknown; } From d73fa8b14a6c873958d00a7d7ad13fcb540a052c Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 4 Mar 2024 20:08:00 -0800 Subject: [PATCH 1767/1897] debug: support data breakpoints by address (#206855) This creates a new "Add Data Breakpoint at Address" action in the breakpoints view when a debugger that supports the capability is available. It prompts the user to enter their address in a quickpick, and then allows them to choose appropriate data access settings. The editor side of https://github.com/microsoft/debug-adapter-protocol/issues/455 --- .../api/browser/mainThreadDebugService.ts | 20 +- .../contrib/debug/browser/breakpointsView.ts | 171 +++++++++++++++++- .../contrib/debug/browser/debugIcons.ts | 1 + .../contrib/debug/browser/debugService.ts | 16 +- .../contrib/debug/browser/debugSession.ts | 52 ++++-- .../contrib/debug/browser/variablesView.ts | 8 +- .../workbench/contrib/debug/common/debug.ts | 25 ++- .../contrib/debug/common/debugModel.ts | 38 +++- .../contrib/debug/common/debugProtocol.d.ts | 53 ++---- .../contrib/debug/common/debugViewModel.ts | 23 ++- .../debug/test/browser/breakpoints.test.ts | 10 +- .../contrib/debug/test/common/mockDebug.ts | 8 +- 12 files changed, 325 insertions(+), 100 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 3178df6d093..94641115abb 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -5,7 +5,7 @@ import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI as uri, UriComponents } from 'vs/base/common/uri'; -import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDataBreakpoint, IDebugSessionOptions, IInstructionBreakpoint, DebugConfigurationProviderTriggerKind, IDebugVisualization } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDataBreakpoint, IDebugSessionOptions, IInstructionBreakpoint, DebugConfigurationProviderTriggerKind, IDebugVisualization, DataBreakpointSetType } from 'vs/workbench/contrib/debug/common/debug'; import { ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto, IDataBreakpointDto, IStartDebuggingOptions, IDebugConfiguration, IThreadFocusDto, IStackFrameFocusDto @@ -225,7 +225,14 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb } else if (dto.type === 'function') { this.debugService.addFunctionBreakpoint(dto.functionName, dto.id, dto.mode); } else if (dto.type === 'data') { - this.debugService.addDataBreakpoint(dto.label, dto.dataId, dto.canPersist, dto.accessTypes, dto.accessType, dto.mode); + this.debugService.addDataBreakpoint({ + description: dto.label, + src: { type: DataBreakpointSetType.Variable, dataId: dto.dataId }, + canPersist: dto.canPersist, + accessTypes: dto.accessTypes, + accessType: dto.accessType, + mode: dto.mode + }); } } return Promise.resolve(); @@ -436,19 +443,20 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb logMessage: fbp.logMessage, functionName: fbp.name }; - } else if ('dataId' in bp) { + } else if ('src' in bp) { const dbp = bp; - return { + return { type: 'data', id: dbp.getId(), - dataId: dbp.dataId, + dataId: dbp.src.type === DataBreakpointSetType.Variable ? dbp.src.dataId : dbp.src.address, enabled: dbp.enabled, condition: dbp.condition, hitCondition: dbp.hitCondition, logMessage: dbp.logMessage, + accessType: dbp.accessType, label: dbp.description, canPersist: dbp.canPersist - }; + } satisfies IDataBreakpointDto; } else { const sbp = bp; return { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 13b72018035..ddfb63625fd 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -51,10 +51,11 @@ import { IEditorPane } from 'vs/workbench/common/editor'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; import { DisassemblyView } from 'vs/workbench/contrib/debug/browser/disassemblyView'; -import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_BREAKPOINT_HAS_MODES, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_BREAKPOINT_ITEM_TYPE, CONTEXT_BREAKPOINT_SUPPORTS_CONDITION, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_IN_DEBUG_MODE, DEBUG_SCHEME, DebuggerString, IBaseBreakpoint, IBreakpoint, IBreakpointEditorContribution, IBreakpointUpdateData, IDataBreakpoint, IDebugModel, IDebugService, IEnablement, IExceptionBreakpoint, IFunctionBreakpoint, IInstructionBreakpoint, State } from 'vs/workbench/contrib/debug/common/debug'; +import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_BREAKPOINT_HAS_MODES, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_BREAKPOINT_ITEM_IS_DATA_BYTES, CONTEXT_BREAKPOINT_ITEM_TYPE, CONTEXT_BREAKPOINT_SUPPORTS_CONDITION, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_IN_DEBUG_MODE, CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED, DEBUG_SCHEME, DataBreakpointSetType, DataBreakpointSource, DebuggerString, IBaseBreakpoint, IBreakpoint, IBreakpointEditorContribution, IBreakpointUpdateData, IDataBreakpoint, IDataBreakpointInfoResponse, IDebugModel, IDebugService, IEnablement, IExceptionBreakpoint, IFunctionBreakpoint, IInstructionBreakpoint, State } from 'vs/workbench/contrib/debug/common/debug'; import { Breakpoint, DataBreakpoint, ExceptionBreakpoint, FunctionBreakpoint, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { INotificationService } from 'vs/platform/notification/common/notification'; const $ = dom.$; @@ -87,6 +88,7 @@ export class BreakpointsView extends ViewPane { private ignoreLayout = false; private menu: IMenu; private breakpointItemType: IContextKey; + private breakpointIsDataBytes: IContextKey; private breakpointHasMultipleModes: IContextKey; private breakpointSupportsCondition: IContextKey; private _inputBoxData: InputBoxData | undefined; @@ -120,6 +122,7 @@ export class BreakpointsView extends ViewPane { this.menu = menuService.createMenu(MenuId.DebugBreakpointsContext, contextKeyService); this._register(this.menu); this.breakpointItemType = CONTEXT_BREAKPOINT_ITEM_TYPE.bindTo(contextKeyService); + this.breakpointIsDataBytes = CONTEXT_BREAKPOINT_ITEM_IS_DATA_BYTES.bindTo(contextKeyService); this.breakpointHasMultipleModes = CONTEXT_BREAKPOINT_HAS_MODES.bindTo(contextKeyService); this.breakpointSupportsCondition = CONTEXT_BREAKPOINT_SUPPORTS_CONDITION.bindTo(contextKeyService); this.breakpointInputFocused = CONTEXT_BREAKPOINT_INPUT_FOCUSED.bindTo(contextKeyService); @@ -142,7 +145,7 @@ export class BreakpointsView extends ViewPane { new ExceptionBreakpointInputRenderer(this, this.debugService, this.contextViewService), this.instantiationService.createInstance(FunctionBreakpointsRenderer, this.menu, this.breakpointSupportsCondition, this.breakpointItemType), new FunctionBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.labelService), - this.instantiationService.createInstance(DataBreakpointsRenderer, this.menu, this.breakpointHasMultipleModes, this.breakpointSupportsCondition, this.breakpointItemType), + this.instantiationService.createInstance(DataBreakpointsRenderer, this.menu, this.breakpointHasMultipleModes, this.breakpointSupportsCondition, this.breakpointItemType, this.breakpointIsDataBytes), new DataBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.labelService), this.instantiationService.createInstance(InstructionBreakpointsRenderer), ], { @@ -266,6 +269,7 @@ export class BreakpointsView extends ViewPane { const session = this.debugService.getViewModel().focusedSession; const conditionSupported = element instanceof ExceptionBreakpoint ? element.supportsCondition : (!session || !!session.capabilities.supportsConditionalBreakpoints); this.breakpointSupportsCondition.set(conditionSupported); + this.breakpointIsDataBytes.set(element instanceof DataBreakpoint && element.src.type === DataBreakpointSetType.Address); const secondary: IAction[] = []; createAndFillInContextMenuActions(this.menu, { arg: e.element, shouldForwardArgs: false }, { primary: [], secondary }, 'inline'); @@ -740,6 +744,7 @@ class DataBreakpointsRenderer implements IListRenderer, private breakpointSupportsCondition: IContextKey, private breakpointItemType: IContextKey, + private breakpointIsDataBytes: IContextKey, @IDebugService private readonly debugService: IDebugService, @ILabelService private readonly labelService: ILabelService ) { @@ -816,10 +821,12 @@ class DataBreakpointsRenderer implements IListRenderer 1); this.breakpointItemType.set('dataBreakpoint'); + this.breakpointIsDataBytes.set(dataBreakpoint.src.type === DataBreakpointSetType.Address); createAndFillInActionBarActions(this.menu, { arg: dataBreakpoint, shouldForwardArgs: true }, { primary, secondary: [] }, 'inline'); data.actionBar.clear(); data.actionBar.push(primary, { icon: true, label: false }); breakpointIdToActionBarDomeNode.set(dataBreakpoint.getId(), data.actionBar.domNode); + this.breakpointIsDataBytes.reset(); } disposeTemplate(templateData: IBaseBreakpointWithIconTemplateData): void { @@ -1421,6 +1428,166 @@ registerAction2(class extends Action2 { } }); +abstract class MemoryBreakpointAction extends Action2 { + async run(accessor: ServicesAccessor, existingBreakpoint?: IDataBreakpoint): Promise { + const debugService = accessor.get(IDebugService); + const session = debugService.getViewModel().focusedSession; + if (!session) { + return; + } + + let defaultValue = undefined; + if (existingBreakpoint && existingBreakpoint.src.type === DataBreakpointSetType.Address) { + defaultValue = `${existingBreakpoint.src.address} + ${existingBreakpoint.src.bytes}`; + } + + const quickInput = accessor.get(IQuickInputService); + const notifications = accessor.get(INotificationService); + const range = await this.getRange(quickInput, defaultValue); + if (!range) { + return; + } + + let info: IDataBreakpointInfoResponse | undefined; + try { + info = await session.dataBytesBreakpointInfo(range.address, range.bytes); + } catch (e) { + notifications.error(localize('dataBreakpointError', "Failed to set data breakpoint at {0}: {1}", range.address, e.message)); + } + + if (!info?.dataId) { + return; + } + + let accessType: DebugProtocol.DataBreakpointAccessType = 'write'; + if (info.accessTypes && info.accessTypes?.length > 1) { + const accessTypes = info.accessTypes.map(type => ({ label: type })); + const selectedAccessType = await quickInput.pick(accessTypes, { placeHolder: localize('dataBreakpointAccessType', "Select the access type to monitor") }); + if (!selectedAccessType) { + return; + } + + accessType = selectedAccessType.label; + } + + const src: DataBreakpointSource = { type: DataBreakpointSetType.Address, ...range }; + if (existingBreakpoint) { + await debugService.removeDataBreakpoints(existingBreakpoint.getId()); + } + + await debugService.addDataBreakpoint({ + description: info.description, + src, + canPersist: true, + accessTypes: info.accessTypes, + accessType: accessType, + initialSessionData: { session, dataId: info.dataId } + }); + } + + private getRange(quickInput: IQuickInputService, defaultValue?: string) { + return new Promise<{ address: string; bytes: number } | undefined>(resolve => { + const input = quickInput.createInputBox(); + input.prompt = localize('dataBreakpointMemoryRangePrompt', "Enter a memory range in which to break"); + input.placeholder = localize('dataBreakpointMemoryRangePlaceholder', 'Absolute range (0x1234 - 0x1300) or range of bytes after an address (0x1234 + 0xff)'); + if (defaultValue) { + input.value = defaultValue; + input.valueSelection = [0, defaultValue.length]; + } + input.onDidChangeValue(e => { + const err = this.parseAddress(e, false); + input.validationMessage = err?.error; + }); + input.onDidAccept(() => { + const r = this.parseAddress(input.value, true); + if ('error' in r) { + input.validationMessage = r.error; + } else { + resolve(r); + } + input.dispose(); + }); + input.onDidHide(() => { + resolve(undefined); + input.dispose(); + }); + input.ignoreFocusOut = true; + input.show(); + }); + } + + private parseAddress(range: string, isFinal: false): { error: string } | undefined; + private parseAddress(range: string, isFinal: true): { error: string } | { address: string; bytes: number }; + private parseAddress(range: string, isFinal: boolean): { error: string } | { address: string; bytes: number } | undefined { + const parts = /^(\S+)\s*(?:([+-])\s*(\S+))?/.exec(range); + if (!parts) { + return { error: localize('dataBreakpointAddrFormat', 'Address should be a range of numbers the form "[Start] - [End]" or "[Start] + [Bytes]"') }; + } + + const isNum = (e: string) => isFinal ? /^0x[0-9a-f]*|[0-9]*$/i.test(e) : /^0x[0-9a-f]+|[0-9]+$/i.test(e); + const [, startStr, sign = '+', endStr = '1'] = parts; + + for (const n of [startStr, endStr]) { + if (!isNum(n)) { + return { error: localize('dataBreakpointAddrStartEnd', 'Number must be a decimal integer or hex value starting with \"0x\", got {0}', n) }; + } + } + + if (!isFinal) { + return; + } + + const start = BigInt(startStr); + const end = BigInt(endStr); + const address = `0x${start.toString(16)}`; + if (sign === '-') { + return { address, bytes: Number(start - end) }; + } + + return { address, bytes: Number(end) }; + } +} + +registerAction2(class extends MemoryBreakpointAction { + constructor() { + super({ + id: 'workbench.debug.viewlet.action.addDataBreakpointOnAddress', + title: { + ...localize2('addDataBreakpointOnAddress', "Add Data Breakpoint at Address"), + mnemonicTitle: localize({ key: 'miDataBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Data Breakpoint..."), + }, + f1: true, + icon: icons.watchExpressionsAddDataBreakpoint, + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + order: 11, + when: ContextKeyExpr.and(CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED, ContextKeyExpr.equals('view', BREAKPOINTS_VIEW_ID)) + }, { + id: MenuId.MenubarNewBreakpointMenu, + group: '1_breakpoints', + order: 4, + when: CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED + }] + }); + } +}); + +registerAction2(class extends MemoryBreakpointAction { + constructor() { + super({ + id: 'workbench.debug.viewlet.action.editDataBreakpointOnAddress', + title: localize2('editDataBreakpointOnAddress', "Edit Address..."), + menu: [{ + id: MenuId.DebugBreakpointsContext, + when: ContextKeyExpr.and(CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED, CONTEXT_BREAKPOINT_ITEM_IS_DATA_BYTES), + group: 'navigation', + order: 15, + }] + }); + } +}); + registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/debug/browser/debugIcons.ts b/src/vs/workbench/contrib/debug/browser/debugIcons.ts index b1a9a4a0789..12376d1a83f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugIcons.ts +++ b/src/vs/workbench/contrib/debug/browser/debugIcons.ts @@ -79,6 +79,7 @@ export const watchExpressionsRemoveAll = registerIcon('watch-expressions-remove- export const watchExpressionRemove = registerIcon('watch-expression-remove', Codicon.removeClose, localize('watchExpressionRemove', 'Icon for the Remove action in the watch view.')); export const watchExpressionsAdd = registerIcon('watch-expressions-add', Codicon.add, localize('watchExpressionsAdd', 'Icon for the add action in the watch view.')); export const watchExpressionsAddFuncBreakpoint = registerIcon('watch-expressions-add-function-breakpoint', Codicon.add, localize('watchExpressionsAddFuncBreakpoint', 'Icon for the add function breakpoint action in the watch view.')); +export const watchExpressionsAddDataBreakpoint = registerIcon('watch-expressions-add-data-breakpoint', Codicon.variableGroup, localize('watchExpressionsAddDataBreakpoint', 'Icon for the add data breakpoint action in the breakpoints view.')); export const breakpointsRemoveAll = registerIcon('breakpoints-remove-all', Codicon.closeAll, localize('breakpointsRemoveAll', 'Icon for the Remove All action in the breakpoints view.')); export const breakpointsActivate = registerIcon('breakpoints-activate', Codicon.activateBreakpoints, localize('breakpointsActivate', 'Icon for the activate action in the breakpoints view.')); diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 5478398dfb6..84e946ecf70 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -6,7 +6,7 @@ import * as aria from 'vs/base/browser/ui/aria/aria'; import { Action, IAction } from 'vs/base/common/actions'; import { distinct } from 'vs/base/common/arrays'; -import { raceTimeout, RunOnceScheduler } from 'vs/base/common/async'; +import { RunOnceScheduler, raceTimeout } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { isErrorWithActions } from 'vs/base/common/errorMessage'; import * as errors from 'vs/base/common/errors'; @@ -24,7 +24,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files'; +import { FileChangeType, FileChangesEvent, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; @@ -34,22 +34,21 @@ import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/work import { EditorsOrder } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; -import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { AdapterManager } from 'vs/workbench/contrib/debug/browser/debugAdapterManager'; import { DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { ConfigurationManager } from 'vs/workbench/contrib/debug/browser/debugConfigurationManager'; import { DebugMemoryFileSystemProvider } from 'vs/workbench/contrib/debug/browser/debugMemory'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; import { DebugTaskRunner, TaskRunResult } from 'vs/workbench/contrib/debug/browser/debugTaskRunner'; -import { CALLSTACK_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_HAS_DEBUGGED, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_UX, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_IN_DEBUG_MODE, debuggerDisabledMessage, DEBUG_MEMORY_SCHEME, getStateLabel, IAdapterManager, IBreakpoint, IBreakpointData, ICompound, IConfig, IConfigurationManager, IDebugConfiguration, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IEnablement, IExceptionBreakpoint, IGlobalConfig, ILaunch, IStackFrame, IThread, IViewModel, REPL_VIEW_ID, State, VIEWLET_ID, DEBUG_SCHEME, IBreakpointUpdateData } from 'vs/workbench/contrib/debug/common/debug'; +import { CALLSTACK_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_UX, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_HAS_DEBUGGED, CONTEXT_IN_DEBUG_MODE, DEBUG_MEMORY_SCHEME, DEBUG_SCHEME, IAdapterManager, IBreakpoint, IBreakpointData, IBreakpointUpdateData, ICompound, IConfig, IConfigurationManager, IDebugConfiguration, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IEnablement, IExceptionBreakpoint, IGlobalConfig, ILaunch, IStackFrame, IThread, IViewModel, REPL_VIEW_ID, State, VIEWLET_ID, debuggerDisabledMessage, getStateLabel } from 'vs/workbench/contrib/debug/common/debug'; import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; -import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; -import { Breakpoint, DataBreakpoint, DebugModel, FunctionBreakpoint, IInstructionBreakpointOptions, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Breakpoint, DataBreakpoint, DebugModel, FunctionBreakpoint, IDataBreakpointOptions, IInstructionBreakpointOptions, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage'; import { DebugTelemetry } from 'vs/workbench/contrib/debug/common/debugTelemetry'; import { getExtensionHostDebugSession, saveAllBeforeDebugStart } from 'vs/workbench/contrib/debug/common/debugUtils'; import { ViewModel } from 'vs/workbench/contrib/debug/common/debugViewModel'; +import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; import { VIEWLET_ID as EXPLORER_VIEWLET_ID } from 'vs/workbench/contrib/files/common/files'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; @@ -58,6 +57,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; export class DebugService implements IDebugService { declare readonly _serviceBrand: undefined; @@ -1081,8 +1081,8 @@ export class DebugService implements IDebugService { await this.sendFunctionBreakpoints(); } - async addDataBreakpoint(description: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, accessType: DebugProtocol.DataBreakpointAccessType, mode: string | undefined): Promise { - this.model.addDataBreakpoint({ description, dataId, canPersist, accessTypes, accessType, mode }); + async addDataBreakpoint(opts: IDataBreakpointOptions): Promise { + this.model.addDataBreakpoint(opts); this.debugStorage.storeBreakpoints(this.model); await this.sendDataBreakpoints(); this.debugStorage.storeBreakpoints(this.model); diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 861135c6f6a..0d56d661268 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -29,7 +29,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ViewContainerLocation } from 'vs/workbench/common/views'; import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession'; -import { AdapterEndEvent, IBreakpoint, IConfig, IDataBreakpoint, IDebugConfiguration, IDebugService, IDebugSession, IDebugSessionOptions, IDebugger, IExceptionBreakpoint, IExceptionInfo, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IReplElement, IStackFrame, IThread, LoadedSourceEvent, State, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { AdapterEndEvent, IBreakpoint, IConfig, IDataBreakpoint, IDataBreakpointInfoResponse, IDebugConfiguration, IDebugService, IDebugSession, IDebugSessionOptions, IDebugger, IExceptionBreakpoint, IExceptionInfo, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IReplElement, IStackFrame, IThread, LoadedSourceEvent, State, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug'; import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; import { DebugModel, ExpressionContainer, MemoryRegion, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; @@ -41,6 +41,7 @@ import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecy import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { getActiveWindow } from 'vs/base/browser/dom'; import { mainWindow } from 'vs/base/browser/window'; +import { isDefined } from 'vs/base/common/types'; const TRIGGERED_BREAKPOINT_MAX_DELAY = 1500; @@ -461,7 +462,7 @@ export class DebugSession implements IDebugSession, IDisposable { breakpoints: breakpointsToSend.map(bp => bp.toDAP()), sourceModified }); - if (response && response.body) { + if (response?.body) { const data = new Map(); for (let i = 0; i < breakpointsToSend.length; i++) { data.set(breakpointsToSend[i].getId(), response.body.breakpoints[i]); @@ -478,7 +479,7 @@ export class DebugSession implements IDebugSession, IDisposable { if (this.raw.readyForBreakpoints) { const response = await this.raw.setFunctionBreakpoints({ breakpoints: fbpts.map(bp => bp.toDAP()) }); - if (response && response.body) { + if (response?.body) { const data = new Map(); for (let i = 0; i < fbpts.length; i++) { data.set(fbpts[i].getId(), response.body.breakpoints[i]); @@ -506,7 +507,7 @@ export class DebugSession implements IDebugSession, IDisposable { } : { filters: exbpts.map(exb => exb.filter) }; const response = await this.raw.setExceptionBreakpoints(args); - if (response && response.body && response.body.breakpoints) { + if (response?.body && response.body.breakpoints) { const data = new Map(); for (let i = 0; i < exbpts.length; i++) { data.set(exbpts[i].getId(), response.body.breakpoints[i]); @@ -517,7 +518,19 @@ export class DebugSession implements IDebugSession, IDisposable { } } - async dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null; description: string; canPersist?: boolean } | undefined> { + dataBytesBreakpointInfo(address: string, bytes: number): Promise { + if (this.raw?.capabilities.supportsDataBreakpointBytes === false) { + throw new Error(localize('sessionDoesNotSupporBytesBreakpoints', "Session does not support breakpoints with bytes")); + } + + return this._dataBreakpointInfo({ name: address, bytes, asAddress: true }); + } + + dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null; description: string; canPersist?: boolean } | undefined> { + return this._dataBreakpointInfo({ name, variablesReference }); + } + + private async _dataBreakpointInfo(args: DebugProtocol.DataBreakpointInfoArguments): Promise<{ dataId: string | null; description: string; canPersist?: boolean } | undefined> { if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'data breakpoints info')); } @@ -525,7 +538,7 @@ export class DebugSession implements IDebugSession, IDisposable { throw new Error(localize('sessionNotReadyForBreakpoints', "Session is not ready for breakpoints")); } - const response = await this.raw.dataBreakpointInfo({ name, variablesReference }); + const response = await this.raw.dataBreakpointInfo(args); return response?.body; } @@ -535,11 +548,24 @@ export class DebugSession implements IDebugSession, IDisposable { } if (this.raw.readyForBreakpoints) { - const response = await this.raw.setDataBreakpoints({ breakpoints: dataBreakpoints.map(bp => bp.toDAP()) }); - if (response && response.body) { + const converted = await Promise.all(dataBreakpoints.map(async bp => { + try { + const dap = await bp.toDAP(this); + return { dap, bp }; + } catch (e) { + return { bp, message: e.message }; + } + })); + const response = await this.raw.setDataBreakpoints({ breakpoints: converted.map(d => d.dap).filter(isDefined) }); + if (response?.body) { const data = new Map(); - for (let i = 0; i < dataBreakpoints.length; i++) { - data.set(dataBreakpoints[i].getId(), response.body.breakpoints[i]); + let i = 0; + for (const dap of converted) { + if (!dap.dap) { + data.set(dap.bp.getId(), dap.message); + } else if (i < response.body.breakpoints.length) { + data.set(dap.bp.getId(), response.body.breakpoints[i++]); + } } this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); } @@ -553,7 +579,7 @@ export class DebugSession implements IDebugSession, IDisposable { if (this.raw.readyForBreakpoints) { const response = await this.raw.setInstructionBreakpoints({ breakpoints: instructionBreakpoints.map(ib => ib.toDAP()) }); - if (response && response.body) { + if (response?.body) { const data = new Map(); for (let i = 0; i < instructionBreakpoints.length; i++) { data.set(instructionBreakpoints[i].getId(), response.body.breakpoints[i]); @@ -790,7 +816,7 @@ export class DebugSession implements IDebugSession, IDisposable { } const response = await this.raw.loadedSources({}); - if (response && response.body && response.body.sources) { + if (response?.body && response.body.sources) { return response.body.sources.map(src => this.getSource(src)); } else { return []; @@ -959,7 +985,7 @@ export class DebugSession implements IDebugSession, IDisposable { private async fetchThreads(stoppedDetails?: IRawStoppedDetails): Promise { if (this.raw) { const response = await this.raw.threads(); - if (response && response.body && response.body.threads) { + if (response?.body && response.body.threads) { this.model.rawUpdate({ sessionId: this.getId(), threads: response.body.threads, diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index cce34ecbbe6..56582689c12 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -39,7 +39,7 @@ import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewl import { IViewDescriptorService } from 'vs/workbench/common/views'; import { AbstractExpressionDataSource, AbstractExpressionsRenderer, IExpressionTemplateData, IInputBoxOptions, renderExpressionValue, renderVariable, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; -import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_VARIABLES_FOCUSED, DebugVisualizationType, IDataBreakpointInfoResponse, IDebugService, IExpression, IScope, IStackFrame, IViewModel, VARIABLES_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_VARIABLES_FOCUSED, DataBreakpointSetType, DebugVisualizationType, IDataBreakpointInfoResponse, IDebugService, IExpression, IScope, IStackFrame, IViewModel, VARIABLES_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; import { getContextForVariable } from 'vs/workbench/contrib/debug/common/debugContext'; import { ErrorScope, Expression, Scope, StackFrame, Variable, VisualizedExpression, getUriForDebugMemory } from 'vs/workbench/contrib/debug/common/debugModel'; import { DebugVisualizer, IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; @@ -766,7 +766,7 @@ CommandsRegistry.registerCommand({ handler: async (accessor: ServicesAccessor) => { const debugService = accessor.get(IDebugService); if (dataBreakpointInfoResponse) { - await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'write', undefined); + await debugService.addDataBreakpoint({ description: dataBreakpointInfoResponse.description, src: { type: DataBreakpointSetType.Variable, dataId: dataBreakpointInfoResponse.dataId! }, canPersist: !!dataBreakpointInfoResponse.canPersist, accessTypes: dataBreakpointInfoResponse.accessTypes, accessType: 'write' }); } } }); @@ -777,7 +777,7 @@ CommandsRegistry.registerCommand({ handler: async (accessor: ServicesAccessor) => { const debugService = accessor.get(IDebugService); if (dataBreakpointInfoResponse) { - await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'readWrite', undefined); + await debugService.addDataBreakpoint({ description: dataBreakpointInfoResponse.description, src: { type: DataBreakpointSetType.Variable, dataId: dataBreakpointInfoResponse.dataId! }, canPersist: !!dataBreakpointInfoResponse.canPersist, accessTypes: dataBreakpointInfoResponse.accessTypes, accessType: 'readWrite' }); } } }); @@ -788,7 +788,7 @@ CommandsRegistry.registerCommand({ handler: async (accessor: ServicesAccessor) => { const debugService = accessor.get(IDebugService); if (dataBreakpointInfoResponse) { - await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'read', undefined); + await debugService.addDataBreakpoint({ description: dataBreakpointInfoResponse.description, src: { type: DataBreakpointSetType.Variable, dataId: dataBreakpointInfoResponse.dataId! }, canPersist: !!dataBreakpointInfoResponse.canPersist, accessTypes: dataBreakpointInfoResponse.accessTypes, accessType: 'read' }); } } }); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 86d0e94b826..eefbb082ad6 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -24,7 +24,7 @@ import { ITelemetryEndpoint } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IEditorPane } from 'vs/workbench/common/editor'; import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; -import { IInstructionBreakpointOptions } from 'vs/workbench/contrib/debug/common/debugModel'; +import { IDataBreakpointOptions, IInstructionBreakpointOptions } from 'vs/workbench/contrib/debug/common/debugModel'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { ITaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -62,6 +62,7 @@ export const CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD = new RawContextKey('watchItemType', undefined, { type: 'string', description: nls.localize('watchItemType', "Represents the item type of the focused element in the WATCH view. For example: 'expression', 'variable'") }); export const CONTEXT_CAN_VIEW_MEMORY = new RawContextKey('canViewMemory', undefined, { type: 'boolean', description: nls.localize('canViewMemory', "Indicates whether the item in the view has an associated memory refrence.") }); export const CONTEXT_BREAKPOINT_ITEM_TYPE = new RawContextKey('breakpointItemType', undefined, { type: 'string', description: nls.localize('breakpointItemType', "Represents the item type of the focused element in the BREAKPOINTS view. For example: 'breakpoint', 'exceptionBreakppint', 'functionBreakpoint', 'dataBreakpoint'") }); +export const CONTEXT_BREAKPOINT_ITEM_IS_DATA_BYTES = new RawContextKey('breakpointItemBytes', undefined, { type: 'boolean', description: nls.localize('breakpointItemIsDataBytes', "Whether the breakpoint item is a data breakpoint on a byte range.") }); export const CONTEXT_BREAKPOINT_HAS_MODES = new RawContextKey('breakpointHasModes', false, { type: 'boolean', description: nls.localize('breakpointHasModes', "Whether the breakpoint has multiple modes it can switch to.") }); export const CONTEXT_BREAKPOINT_SUPPORTS_CONDITION = new RawContextKey('breakpointSupportsCondition', false, { type: 'boolean', description: nls.localize('breakpointSupportsCondition', "True when the focused breakpoint supports conditions.") }); export const CONTEXT_LOADED_SCRIPTS_SUPPORTED = new RawContextKey('loadedScriptsSupported', false, { type: 'boolean', description: nls.localize('loadedScriptsSupported', "True when the focused sessions supports the LOADED SCRIPTS view") }); @@ -78,6 +79,7 @@ export const CONTEXT_DEBUGGERS_AVAILABLE = new RawContextKey('debuggers export const CONTEXT_DEBUG_EXTENSION_AVAILABLE = new RawContextKey('debugExtensionAvailable', true, { type: 'boolean', description: nls.localize('debugExtensionsAvailable', "True when there is at least one debug extension installed and enabled.") }); export const CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT = new RawContextKey('debugProtocolVariableMenuContext', undefined, { type: 'string', description: nls.localize('debugProtocolVariableMenuContext', "Represents the context the debug adapter sets on the focused variable in the VARIABLES view.") }); export const CONTEXT_SET_VARIABLE_SUPPORTED = new RawContextKey('debugSetVariableSupported', false, { type: 'boolean', description: nls.localize('debugSetVariableSupported', "True when the focused session supports 'setVariable' request.") }); +export const CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED = new RawContextKey('debugSetDataBreakpointAddressSupported', false, { type: 'boolean', description: nls.localize('debugSetDataBreakpointAddressSupported', "True when the focused session supports 'getBreakpointInfo' request on an address.") }); export const CONTEXT_SET_EXPRESSION_SUPPORTED = new RawContextKey('debugSetExpressionSupported', false, { type: 'boolean', description: nls.localize('debugSetExpressionSupported', "True when the focused session supports 'setExpression' request.") }); export const CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED = new RawContextKey('breakWhenValueChangesSupported', false, { type: 'boolean', description: nls.localize('breakWhenValueChangesSupported', "True when the focused session supports to break when value changes.") }); export const CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED = new RawContextKey('breakWhenValueIsAccessedSupported', false, { type: 'boolean', description: nls.localize('breakWhenValueIsAccessedSupported', "True when the focused breakpoint supports to break when value is accessed.") }); @@ -404,6 +406,7 @@ export interface IDebugSession extends ITreeElement { sendBreakpoints(modelUri: uri, bpts: IBreakpoint[], sourceModified: boolean): Promise; sendFunctionBreakpoints(fbps: IFunctionBreakpoint[]): Promise; dataBreakpointInfo(name: string, variablesReference?: number): Promise; + dataBytesBreakpointInfo(address: string, bytes: number): Promise; sendDataBreakpoints(dbps: IDataBreakpoint[]): Promise; sendInstructionBreakpoints(dbps: IInstructionBreakpoint[]): Promise; sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise; @@ -607,12 +610,26 @@ export interface IExceptionBreakpoint extends IBaseBreakpoint { readonly description: string | undefined; } +export const enum DataBreakpointSetType { + Variable, + Address, +} + +/** + * Source for a data breakpoint. A data breakpoint on a variable always has a + * `dataId` because it cannot reference that variable globally, but addresses + * can request info repeated and use session-specific data. + */ +export type DataBreakpointSource = + | { type: DataBreakpointSetType.Variable; dataId: string } + | { type: DataBreakpointSetType.Address; address: string; bytes: number }; + export interface IDataBreakpoint extends IBaseBreakpoint { readonly description: string; - readonly dataId: string; readonly canPersist: boolean; + readonly src: DataBreakpointSource; readonly accessType: DebugProtocol.DataBreakpointAccessType; - toDAP(): DebugProtocol.DataBreakpoint; + toDAP(session: IDebugSession): Promise; } export interface IInstructionBreakpoint extends IBaseBreakpoint { @@ -1144,7 +1161,7 @@ export interface IDebugService { /** * Adds a new data breakpoint. */ - addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, accessType: DebugProtocol.DataBreakpointAccessType, mode: string | undefined): Promise; + addDataBreakpoint(opts: IDataBreakpointOptions): Promise; /** * Updates an already existing data breakpoint. diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 8098d1ce57b..b12e9b726ff 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -22,7 +22,7 @@ import * as nls from 'vs/nls'; import { ILogService } from 'vs/platform/log/common/log'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IEditorPane } from 'vs/workbench/common/editor'; -import { DEBUG_MEMORY_SCHEME, DebugTreeItemCollapsibleState, IBaseBreakpoint, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IBreakpointsChangeEvent, IDataBreakpoint, IDebugModel, IDebugSession, IDebugVisualizationTreeItem, IEnablement, IExceptionBreakpoint, IExceptionInfo, IExpression, IExpressionContainer, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryInvalidationEvent, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IScope, IStackFrame, IThread, ITreeElement, MemoryRange, MemoryRangeType, State } from 'vs/workbench/contrib/debug/common/debug'; +import { DEBUG_MEMORY_SCHEME, DataBreakpointSetType, DataBreakpointSource, DebugTreeItemCollapsibleState, IBaseBreakpoint, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IBreakpointsChangeEvent, IDataBreakpoint, IDebugModel, IDebugSession, IDebugVisualizationTreeItem, IEnablement, IExceptionBreakpoint, IExceptionInfo, IExpression, IExpressionContainer, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryInvalidationEvent, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IScope, IStackFrame, IThread, ITreeElement, MemoryRange, MemoryRangeType, State } from 'vs/workbench/contrib/debug/common/debug'; import { Source, UNKNOWN_SOURCE_LABEL, getUriFromSource } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage'; import { IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; @@ -1150,15 +1150,18 @@ export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreak export interface IDataBreakpointOptions extends IBaseBreakpointOptions { description: string; - dataId: string; + src: DataBreakpointSource; canPersist: boolean; + initialSessionData?: { session: IDebugSession; dataId: string }; accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined; accessType: DebugProtocol.DataBreakpointAccessType; } export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { + private readonly sessionDataIdForAddr = new WeakMap(); + public readonly description: string; - public readonly dataId: string; + public readonly src: DataBreakpointSource; public readonly canPersist: boolean; public readonly accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined; public readonly accessType: DebugProtocol.DataBreakpointAccessType; @@ -1169,15 +1172,36 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { ) { super(id, opts); this.description = opts.description; - this.dataId = opts.dataId; + if ('dataId' in opts) { // back compat with old saved variables in 1.87 + opts.src = { type: DataBreakpointSetType.Variable, dataId: opts.dataId as string }; + } + this.src = opts.src; this.canPersist = opts.canPersist; this.accessTypes = opts.accessTypes; this.accessType = opts.accessType; + if (opts.initialSessionData) { + this.sessionDataIdForAddr.set(opts.initialSessionData.session, opts.initialSessionData.dataId); + } } - toDAP(): DebugProtocol.DataBreakpoint { + async toDAP(session: IDebugSession): Promise { + let dataId: string; + if (this.src.type === DataBreakpointSetType.Variable) { + dataId = this.src.dataId; + } else { + let sessionDataId = this.sessionDataIdForAddr.get(session); + if (!sessionDataId) { + sessionDataId = (await session.dataBytesBreakpointInfo(this.src.address, this.src.bytes))?.dataId; + if (!sessionDataId) { + return undefined; + } + this.sessionDataIdForAddr.set(session, sessionDataId); + } + dataId = sessionDataId; + } + return { - dataId: this.dataId, + dataId, accessType: this.accessType, condition: this.condition, hitCondition: this.hitCondition, @@ -1188,7 +1212,7 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { return { ...super.toJSON(), description: this.description, - dataId: this.dataId, + src: this.src, accessTypes: this.accessTypes, accessType: this.accessType, canPersist: this.canPersist, diff --git a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts index b00a4fd466a..50eacfd65e2 100644 --- a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts @@ -813,11 +813,22 @@ declare module DebugProtocol { /** Reference to the variable container if the data breakpoint is requested for a child of the container. The `variablesReference` must have been obtained in the current suspended state. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference?: number; /** The name of the variable's child to obtain data breakpoint information for. - If `variablesReference` isn't specified, this can be an expression. + If `variablesReference` isn't specified, this can be an expression, or an address if `asAddress` is also true. */ name: string; /** When `name` is an expression, evaluate it in the scope of this stack frame. If not specified, the expression is evaluated in the global scope. When `variablesReference` is specified, this property has no effect. */ frameId?: number; + /** If specified, a debug adapter should return information for the range of memory extending `bytes` number of bytes from the address or variable specified by `name`. Breakpoints set using the resulting data ID should pause on data access anywhere within that range. + + Clients may set this property only if the `supportsDataBreakpointBytes` capability is true. + */ + bytes?: number; + /** If `true`, the `name` is a memory address and the debugger should interpret it as a decimal value, or hex value if it is prefixed with `0x`. + + Clients may set this property only if the `supportsDataBreakpointBytes` + capability is true. + */ + asAddress?: boolean; /** The mode of the desired breakpoint. If defined, this must be one of the `breakpointModes` the debug adapter advertised in its `Capabilities`. */ mode?: string; } @@ -1680,42 +1691,6 @@ declare module DebugProtocol { }; } - /** DataAddressBreakpointInfo request; value of command field is 'DataAddressBreakpointInfo'. - Obtains information on a possible data breakpoint that could be set on a memory address or memory address range. - - Clients should only call this request if the corresponding capability `supportsDataAddressInfo` is true. - */ - interface DataAddressBreakpointInfoRequest extends Request { - // command: 'DataAddressBreakpointInfo'; - arguments: DataAddressBreakpointInfoArguments; - } - - /** Arguments for `dataAddressBreakpointInfo` request. */ - interface DataAddressBreakpointInfoArguments { - /** The address of the data for which to obtain breakpoint information. - Treated as a hex value if prefixed with `0x`, or as a decimal value otherwise. - */ - address?: string; - /** If passed, requests breakpoint information for an exclusive byte range rather than a single address. The range extends the given number of `bytes` from the start `address`. - Treated as a hex value if prefixed with `0x`, or as a decimal value otherwise. - */ - bytes?: string; - } - - /** Response to `dataAddressBreakpointInfo` request. */ - interface DataAddressBreakpointInfoResponse extends Response { - body: { - /** An identifier for the data on which a data breakpoint can be registered with the `setDataBreakpoints` request or null if no data breakpoint is available. If a `variablesReference` or `frameId` is passed, the `dataId` is valid in the current suspended state, otherwise it's valid indefinitely. See 'Lifetime of Object References' in the Overview section for details. Breakpoints set using the `dataId` in the `setDataBreakpoints` request may outlive the lifetime of the associated `dataId`. */ - dataId: string | null; - /** UI string that describes on what data the breakpoint is set on or why a data breakpoint is not available. */ - description: string; - /** Attribute lists the available access types for a potential data breakpoint. A UI client could surface this information. */ - accessTypes?: DataBreakpointAccessType[]; - /** Attribute indicates that a potential data breakpoint could be persisted across sessions. */ - canPersist?: boolean; - }; - } - /** Information about the capabilities of a debug adapter. */ interface Capabilities { /** The debug adapter supports the `configurationDone` request. */ @@ -1788,8 +1763,6 @@ declare module DebugProtocol { supportsBreakpointLocationsRequest?: boolean; /** The debug adapter supports the `clipboard` context value in the `evaluate` request. */ supportsClipboardContext?: boolean; - /** The debug adapter supports the `dataAddressBreakpointInfo` request. */ - supportsDataAddressInfo?: boolean; /** The debug adapter supports stepping granularities (argument `granularity`) for the stepping requests. */ supportsSteppingGranularity?: boolean; /** The debug adapter supports adding breakpoints based on instruction references. */ @@ -1798,6 +1771,8 @@ declare module DebugProtocol { supportsExceptionFilterOptions?: boolean; /** The debug adapter supports the `singleThread` property on the execution requests (`continue`, `next`, `stepIn`, `stepOut`, `reverseContinue`, `stepBack`). */ supportsSingleThreadExecutionRequests?: boolean; + /** The debug adapter supports the `asAddress` and `bytes` fields in the `dataBreakpointInfo` request. */ + supportsDataBreakpointBytes?: boolean; /** Modes of breakpoints supported by the debug adapter, such as 'hardware' or 'software'. If present, the client may allow the user to select a mode and include it in its `setBreakpoints` request. Clients may present the first applicable mode in this array as the 'default' mode in gestures that set breakpoints. diff --git a/src/vs/workbench/contrib/debug/common/debugViewModel.ts b/src/vs/workbench/contrib/debug/common/debugViewModel.ts index 4b0959a97a8..7221f390771 100644 --- a/src/vs/workbench/contrib/debug/common/debugViewModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugViewModel.ts @@ -5,7 +5,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED, CONTEXT_EXPRESSION_SELECTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, IDebugSession, IExpression, IExpressionContainer, IStackFrame, IThread, IViewModel } from 'vs/workbench/contrib/debug/common/debug'; +import { CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED, CONTEXT_EXPRESSION_SELECTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, IDebugSession, IExpression, IExpressionContainer, IStackFrame, IThread, IViewModel } from 'vs/workbench/contrib/debug/common/debug'; import { isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils'; export class ViewModel implements IViewModel { @@ -34,6 +34,7 @@ export class ViewModel implements IViewModel { private stepIntoTargetsSupported!: IContextKey; private jumpToCursorSupported!: IContextKey; private setVariableSupported!: IContextKey; + private setDataBreakpointAtByteSupported!: IContextKey; private setExpressionSupported!: IContextKey; private multiSessionDebug!: IContextKey; private terminateDebuggeeSupported!: IContextKey; @@ -52,6 +53,7 @@ export class ViewModel implements IViewModel { this.stepIntoTargetsSupported = CONTEXT_STEP_INTO_TARGETS_SUPPORTED.bindTo(contextKeyService); this.jumpToCursorSupported = CONTEXT_JUMP_TO_CURSOR_SUPPORTED.bindTo(contextKeyService); this.setVariableSupported = CONTEXT_SET_VARIABLE_SUPPORTED.bindTo(contextKeyService); + this.setDataBreakpointAtByteSupported = CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED.bindTo(contextKeyService); this.setExpressionSupported = CONTEXT_SET_EXPRESSION_SUPPORTED.bindTo(contextKeyService); this.multiSessionDebug = CONTEXT_MULTI_SESSION_DEBUG.bindTo(contextKeyService); this.terminateDebuggeeSupported = CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED.bindTo(contextKeyService); @@ -88,15 +90,16 @@ export class ViewModel implements IViewModel { this._focusedSession = session; this.contextKeyService.bufferChangeEvents(() => { - this.loadedScriptsSupportedContextKey.set(session ? !!session.capabilities.supportsLoadedSourcesRequest : false); - this.stepBackSupportedContextKey.set(session ? !!session.capabilities.supportsStepBack : false); - this.restartFrameSupportedContextKey.set(session ? !!session.capabilities.supportsRestartFrame : false); - this.stepIntoTargetsSupported.set(session ? !!session.capabilities.supportsStepInTargetsRequest : false); - this.jumpToCursorSupported.set(session ? !!session.capabilities.supportsGotoTargetsRequest : false); - this.setVariableSupported.set(session ? !!session.capabilities.supportsSetVariable : false); - this.setExpressionSupported.set(session ? !!session.capabilities.supportsSetExpression : false); - this.terminateDebuggeeSupported.set(session ? !!session.capabilities.supportTerminateDebuggee : false); - this.suspendDebuggeeSupported.set(session ? !!session.capabilities.supportSuspendDebuggee : false); + this.loadedScriptsSupportedContextKey.set(!!session?.capabilities.supportsLoadedSourcesRequest); + this.stepBackSupportedContextKey.set(!!session?.capabilities.supportsStepBack); + this.restartFrameSupportedContextKey.set(!!session?.capabilities.supportsRestartFrame); + this.stepIntoTargetsSupported.set(!!session?.capabilities.supportsStepInTargetsRequest); + this.jumpToCursorSupported.set(!!session?.capabilities.supportsGotoTargetsRequest); + this.setVariableSupported.set(!!session?.capabilities.supportsSetVariable); + this.setDataBreakpointAtByteSupported.set(!!session?.capabilities.supportsDataBreakpointBytes); + this.setExpressionSupported.set(!!session?.capabilities.supportsSetExpression); + this.terminateDebuggeeSupported.set(!!session?.capabilities.supportTerminateDebuggee); + this.suspendDebuggeeSupported.set(!!session?.capabilities.supportSuspendDebuggee); this.disassembleRequestSupported.set(!!session?.capabilities.supportsDisassembleRequest); this.focusedStackFrameHasInstructionPointerReference.set(!!stackFrame?.instructionPointerReference); const attach = !!session && isSessionAttach(session); diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index b85e544f9bb..61599c36ce9 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -19,7 +19,7 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { createBreakpointDecorations } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution'; import { getBreakpointMessageAndIcon, getExpandedBodySize } from 'vs/workbench/contrib/debug/browser/breakpointsView'; -import { IBreakpointData, IBreakpointUpdateData, IDebugService, State } from 'vs/workbench/contrib/debug/common/debug'; +import { DataBreakpointSetType, IBreakpointData, IBreakpointUpdateData, IDebugService, State } from 'vs/workbench/contrib/debug/common/debug'; import { Breakpoint, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; import { createTestSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test'; import { createMockDebugModel, mockUriIdentityService } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; @@ -313,13 +313,13 @@ suite('Debug - Breakpoints', () => { let eventCount = 0; disposables.add(model.onDidChangeBreakpoints(() => eventCount++)); - model.addDataBreakpoint({ description: 'label', dataId: 'id', canPersist: true, accessTypes: ['read'], accessType: 'read' }, '1'); - model.addDataBreakpoint({ description: 'second', dataId: 'secondId', canPersist: false, accessTypes: ['readWrite'], accessType: 'readWrite' }, '2'); + model.addDataBreakpoint({ description: 'label', src: { type: DataBreakpointSetType.Variable, dataId: 'id' }, canPersist: true, accessTypes: ['read'], accessType: 'read' }, '1'); + model.addDataBreakpoint({ description: 'second', src: { type: DataBreakpointSetType.Variable, dataId: 'secondId' }, canPersist: false, accessTypes: ['readWrite'], accessType: 'readWrite' }, '2'); model.updateDataBreakpoint('1', { condition: 'aCondition' }); model.updateDataBreakpoint('2', { hitCondition: '10' }); const dataBreakpoints = model.getDataBreakpoints(); assert.strictEqual(dataBreakpoints[0].canPersist, true); - assert.strictEqual(dataBreakpoints[0].dataId, 'id'); + assert.deepStrictEqual(dataBreakpoints[0].src, { type: DataBreakpointSetType.Variable, dataId: 'id' }); assert.strictEqual(dataBreakpoints[0].accessType, 'read'); assert.strictEqual(dataBreakpoints[0].condition, 'aCondition'); assert.strictEqual(dataBreakpoints[1].canPersist, false); @@ -374,7 +374,7 @@ suite('Debug - Breakpoints', () => { assert.strictEqual(result.message, 'Disabled Logpoint'); assert.strictEqual(result.icon.id, 'debug-breakpoint-log-disabled'); - model.addDataBreakpoint({ description: 'label', canPersist: true, accessTypes: ['read'], accessType: 'read', dataId: 'id' }); + model.addDataBreakpoint({ description: 'label', canPersist: true, accessTypes: ['read'], accessType: 'read', src: { type: DataBreakpointSetType.Variable, dataId: 'id' } }); const dataBreakpoints = model.getDataBreakpoints(); result = getBreakpointMessageAndIcon(State.Stopped, true, dataBreakpoints[0], ls, model); assert.strictEqual(result.message, 'Data Breakpoint'); diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index 617f46d449f..464a4794def 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -13,7 +13,7 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; -import { AdapterEndEvent, IAdapterManager, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IConfig, IConfigurationManager, IDataBreakpoint, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IDebugger, IExceptionBreakpoint, IExceptionInfo, IFunctionBreakpoint, IInstructionBreakpoint, ILaunch, IMemoryRegion, INewReplElementData, IRawModelUpdate, IRawStoppedDetails, IReplElement, IStackFrame, IThread, IViewModel, LoadedSourceEvent, State } from 'vs/workbench/contrib/debug/common/debug'; +import { AdapterEndEvent, IAdapterManager, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IConfig, IConfigurationManager, IDataBreakpoint, IDataBreakpointInfoResponse, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IDebugger, IExceptionBreakpoint, IExceptionInfo, IFunctionBreakpoint, IInstructionBreakpoint, ILaunch, IMemoryRegion, INewReplElementData, IRawModelUpdate, IRawStoppedDetails, IReplElement, IStackFrame, IThread, IViewModel, LoadedSourceEvent, State } from 'vs/workbench/contrib/debug/common/debug'; import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; import { IInstructionBreakpointOptions } from 'vs/workbench/contrib/debug/common/debugModel'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; @@ -114,7 +114,7 @@ export class MockDebugService implements IDebugService { throw new Error('not implemented'); } - addDataBreakpoint(label: string, dataId: string, canPersist: boolean): Promise { + addDataBreakpoint(): Promise { throw new Error('Method not implemented.'); } @@ -223,6 +223,10 @@ export class MockSession implements IDebugSession { throw new Error('Method not implemented.'); } + dataBytesBreakpointInfo(address: string, bytes: number): Promise { + throw new Error('Method not implemented.'); + } + dataBreakpointInfo(name: string, variablesReference?: number | undefined): Promise<{ dataId: string | null; description: string; canPersist?: boolean | undefined } | undefined> { throw new Error('Method not implemented.'); } From d0780ec1c1e32bb9de38eb953ac8bf36718106ee Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 5 Mar 2024 09:02:19 +0100 Subject: [PATCH 1768/1897] Fix Instant Custom Hover Issue (#206870) fixes #206762 --- src/vs/platform/hover/browser/hover.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 3dd6ec94d02..0a60a0b4556 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -236,7 +236,7 @@ export interface IHoverTarget extends IDisposable { export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate { - private lastHoverHideTime = Number.MAX_VALUE; + private lastHoverHideTime = 0; private timeLimit = 200; private _delay: number; From fd98a6877ab0429ac204c8b8e7ebde96d260dee9 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Fri, 1 Mar 2024 20:20:15 +0100 Subject: [PATCH 1769/1897] codicons: Move out aliases --- src/vs/base/common/codicons.ts | 12 +++++- src/vs/base/common/codiconsLibrary.ts | 58 +++++++++++++-------------- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 0cde23be0e8..6919e4934a2 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -36,7 +36,17 @@ export const codiconsDerived = { scrollbarButtonUp: register('scrollbar-button-up', 'triangle-up'), scrollbarButtonDown: register('scrollbar-button-down', 'triangle-down'), toolBarMore: register('toolbar-more', 'more'), - quickInputBack: register('quick-input-back', 'arrow-left') + quickInputBack: register('quick-input-back', 'arrow-left'), + dropDownButton: register('drop-down-button', 0xeab4), + symbolCustomColor: register('symbol-customcolor', 0xeb5c), + exportIcon: register('export', 0xebac), + workspaceUnspecified: register('workspace-unspecified', 0xebc3), + newLine: register('newline', 0xebea), + thumbsDownFilled: register('thumbsdown-filled', 0xec13), + thumbsUpFilled: register('thumbsup-filled', 0xec14), + gitFetch: register('git-fetch', 0xec1d), + lightbulbSparkleAutofix: register('lightbulb-sparkle-autofix', 0xec1f), + debugBreakpointPending: register('debug-breakpoint-pending', 0xebd9), } as const; diff --git a/src/vs/base/common/codiconsLibrary.ts b/src/vs/base/common/codiconsLibrary.ts index a350213cc76..4d25fed1023 100644 --- a/src/vs/base/common/codiconsLibrary.ts +++ b/src/vs/base/common/codiconsLibrary.ts @@ -1,13 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { register } from 'vs/base/common/codiconsUtil'; +import { register } from 'vs/base/common/codiconsUtil' - -// This list is automatically generated by the vscode-codicons repo. +// This file is automatically generated by (microsoft/vscode-codicon)/scripts/export-to-ts.js // Please don't edit it, as your changes will be overwritten. -// If you want to create a mapping, add it to the codiconsDerived list in codicons.ts. +// Instead, add mappings to codiconsDerived in codicons.ts. export const codiconsLibrary = { add: register('add', 0xea60), plus: register('plus', 0xea60), @@ -24,9 +19,9 @@ export const codiconsLibrary = { recordKeys: register('record-keys', 0xea65), keyboard: register('keyboard', 0xea65), tag: register('tag', 0xea66), + gitPullRequestLabel: register('git-pull-request-label', 0xea66), tagAdd: register('tag-add', 0xea66), tagRemove: register('tag-remove', 0xea66), - gitPullRequestLabel: register('git-pull-request-label', 0xea66), person: register('person', 0xea67), personFollow: register('person-follow', 0xea67), personOutline: register('person-outline', 0xea67), @@ -59,8 +54,8 @@ export const codiconsLibrary = { closeDirty: register('close-dirty', 0xea71), debugBreakpoint: register('debug-breakpoint', 0xea71), debugBreakpointDisabled: register('debug-breakpoint-disabled', 0xea71), - debugBreakpointPending: register('debug-breakpoint-pending', 0xebd9), debugHint: register('debug-hint', 0xea71), + terminalDecorationSuccess: register('terminal-decoration-success', 0xea71), primitiveSquare: register('primitive-square', 0xea72), edit: register('edit', 0xea73), pencil: register('pencil', 0xea73), @@ -172,7 +167,6 @@ export const codiconsLibrary = { check: register('check', 0xeab2), checklist: register('checklist', 0xeab3), chevronDown: register('chevron-down', 0xeab4), - dropDownButton: register('drop-down-button', 0xeab4), chevronLeft: register('chevron-left', 0xeab5), chevronRight: register('chevron-right', 0xeab6), chevronUp: register('chevron-up', 0xeab7), @@ -180,9 +174,10 @@ export const codiconsLibrary = { chromeMaximize: register('chrome-maximize', 0xeab9), chromeMinimize: register('chrome-minimize', 0xeaba), chromeRestore: register('chrome-restore', 0xeabb), - circle: register('circle', 0xeabc), circleOutline: register('circle-outline', 0xeabc), + circle: register('circle', 0xeabc), debugBreakpointUnverified: register('debug-breakpoint-unverified', 0xeabc), + terminalDecorationIncomplete: register('terminal-decoration-incomplete', 0xeabc), circleSlash: register('circle-slash', 0xeabd), circuitBoard: register('circuit-board', 0xeabe), clearAll: register('clear-all', 0xeabf), @@ -194,7 +189,6 @@ export const codiconsLibrary = { collapseAll: register('collapse-all', 0xeac5), colorMode: register('color-mode', 0xeac6), commentDiscussion: register('comment-discussion', 0xeac7), - compareChanges: register('compare-changes', 0xeafd), creditCard: register('credit-card', 0xeac9), dash: register('dash', 0xeacc), dashboard: register('dashboard', 0xeacd), @@ -218,6 +212,7 @@ export const codiconsLibrary = { diffRemoved: register('diff-removed', 0xeadf), diffRenamed: register('diff-renamed', 0xeae0), diff: register('diff', 0xeae1), + diffSidebyside: register('diff-sidebyside', 0xeae1), discard: register('discard', 0xeae2), editorLayout: register('editor-layout', 0xeae3), emptyWindow: register('empty-window', 0xeae4), @@ -246,6 +241,7 @@ export const codiconsLibrary = { gist: register('gist', 0xeafb), gitCommit: register('git-commit', 0xeafc), gitCompare: register('git-compare', 0xeafd), + compareChanges: register('compare-changes', 0xeafd), gitMerge: register('git-merge', 0xeafe), githubAction: register('github-action', 0xeaff), githubAlt: register('github-alt', 0xeb00), @@ -258,13 +254,11 @@ export const codiconsLibrary = { horizontalRule: register('horizontal-rule', 0xeb07), hubot: register('hubot', 0xeb08), inbox: register('inbox', 0xeb09), - issueClosed: register('issue-closed', 0xeba4), issueReopened: register('issue-reopened', 0xeb0b), issues: register('issues', 0xeb0c), italic: register('italic', 0xeb0d), jersey: register('jersey', 0xeb0e), json: register('json', 0xeb0f), - bracket: register('bracket', 0xeb0f), kebabVertical: register('kebab-vertical', 0xeb10), key: register('key', 0xeb11), law: register('law', 0xeb12), @@ -343,7 +337,6 @@ export const codiconsLibrary = { starHalf: register('star-half', 0xeb5a), symbolClass: register('symbol-class', 0xeb5b), symbolColor: register('symbol-color', 0xeb5c), - symbolCustomColor: register('symbol-customcolor', 0xeb5c), symbolConstant: register('symbol-constant', 0xeb5d), symbolEnumMember: register('symbol-enum-member', 0xeb5e), symbolField: register('symbol-field', 0xeb5f), @@ -395,6 +388,7 @@ export const codiconsLibrary = { debugStackframeActive: register('debug-stackframe-active', 0xeb89), circleSmallFilled: register('circle-small-filled', 0xeb8a), debugStackframeDot: register('debug-stackframe-dot', 0xeb8a), + terminalDecorationMark: register('terminal-decoration-mark', 0xeb8a), debugStackframe: register('debug-stackframe', 0xeb8b), debugStackframeFocused: register('debug-stackframe-focused', 0xeb8b), debugBreakpointUnsupported: register('debug-breakpoint-unsupported', 0xeb8c), @@ -402,6 +396,7 @@ export const codiconsLibrary = { debugReverseContinue: register('debug-reverse-continue', 0xeb8e), debugStepBack: register('debug-step-back', 0xeb8f), debugRestartFrame: register('debug-restart-frame', 0xeb90), + debugAlt: register('debug-alt', 0xeb91), callIncoming: register('call-incoming', 0xeb92), callOutgoing: register('call-outgoing', 0xeb93), menu: register('menu', 0xeb94), @@ -420,10 +415,10 @@ export const codiconsLibrary = { syncIgnored: register('sync-ignored', 0xeb9f), pinned: register('pinned', 0xeba0), githubInverted: register('github-inverted', 0xeba1), - debugAlt: register('debug-alt', 0xeb91), serverProcess: register('server-process', 0xeba2), serverEnvironment: register('server-environment', 0xeba3), pass: register('pass', 0xeba4), + issueClosed: register('issue-closed', 0xeba4), stopCircle: register('stop-circle', 0xeba5), playCircle: register('play-circle', 0xeba6), record: register('record', 0xeba7), @@ -431,7 +426,7 @@ export const codiconsLibrary = { vmConnect: register('vm-connect', 0xeba9), cloud: register('cloud', 0xebaa), merge: register('merge', 0xebab), - exportIcon: register('export', 0xebac), + export: register('export', 0xebac), graphLeft: register('graph-left', 0xebad), magnet: register('magnet', 0xebae), notebook: register('notebook', 0xebaf), @@ -456,7 +451,7 @@ export const codiconsLibrary = { debugRerun: register('debug-rerun', 0xebc0), workspaceTrusted: register('workspace-trusted', 0xebc1), workspaceUntrusted: register('workspace-untrusted', 0xebc2), - workspaceUnspecified: register('workspace-unspecified', 0xebc3), + workspaceUnknown: register('workspace-unknown', 0xebc3), terminalCmd: register('terminal-cmd', 0xebc4), terminalDebian: register('terminal-debian', 0xebc5), terminalLinux: register('terminal-linux', 0xebc6), @@ -490,12 +485,13 @@ export const codiconsLibrary = { graphLine: register('graph-line', 0xebe2), graphScatter: register('graph-scatter', 0xebe3), pieChart: register('pie-chart', 0xebe4), + bracket: register('bracket', 0xeb0f), bracketDot: register('bracket-dot', 0xebe5), bracketError: register('bracket-error', 0xebe6), lockSmall: register('lock-small', 0xebe7), azureDevops: register('azure-devops', 0xebe8), verifiedFilled: register('verified-filled', 0xebe9), - newLine: register('newline', 0xebea), + newline: register('newline', 0xebea), layout: register('layout', 0xebeb), layoutActivitybarLeft: register('layout-activitybar-left', 0xebec), layoutActivitybarRight: register('layout-activitybar-right', 0xebed), @@ -509,17 +505,19 @@ export const codiconsLibrary = { layoutStatusbar: register('layout-statusbar', 0xebf5), layoutMenubar: register('layout-menubar', 0xebf6), layoutCentered: register('layout-centered', 0xebf7), - layoutSidebarRightOff: register('layout-sidebar-right-off', 0xec00), - layoutPanelOff: register('layout-panel-off', 0xec01), - layoutSidebarLeftOff: register('layout-sidebar-left-off', 0xec02), target: register('target', 0xebf8), indent: register('indent', 0xebf9), recordSmall: register('record-small', 0xebfa), errorSmall: register('error-small', 0xebfb), + terminalDecorationError: register('terminal-decoration-error', 0xebfb), arrowCircleDown: register('arrow-circle-down', 0xebfc), arrowCircleLeft: register('arrow-circle-left', 0xebfd), arrowCircleRight: register('arrow-circle-right', 0xebfe), arrowCircleUp: register('arrow-circle-up', 0xebff), + layoutSidebarRightOff: register('layout-sidebar-right-off', 0xec00), + layoutPanelOff: register('layout-panel-off', 0xec01), + layoutSidebarLeftOff: register('layout-sidebar-left-off', 0xec02), + blank: register('blank', 0xec03), heartFilled: register('heart-filled', 0xec04), map: register('map', 0xec05), mapFilled: register('map-filled', 0xec06), @@ -535,8 +533,8 @@ export const codiconsLibrary = { sparkle: register('sparkle', 0xec10), insert: register('insert', 0xec11), mic: register('mic', 0xec12), - thumbsDownFilled: register('thumbsdown-filled', 0xec13), - thumbsUpFilled: register('thumbsup-filled', 0xec14), + thumbsdownFilled: register('thumbsdown-filled', 0xec13), + thumbsupFilled: register('thumbsup-filled', 0xec14), coffee: register('coffee', 0xec15), snake: register('snake', 0xec16), game: register('game', 0xec17), @@ -545,21 +543,23 @@ export const codiconsLibrary = { piano: register('piano', 0xec1a), music: register('music', 0xec1b), micFilled: register('mic-filled', 0xec1c), - gitFetch: register('git-fetch', 0xec1d), + repoFetch: register('repo-fetch', 0xec1d), copilot: register('copilot', 0xec1e), lightbulbSparkle: register('lightbulb-sparkle', 0xec1f), - lightbulbSparkleAutofix: register('lightbulb-sparkle-autofix', 0xec1f), robot: register('robot', 0xec20), sparkleFilled: register('sparkle-filled', 0xec21), diffSingle: register('diff-single', 0xec22), diffMultiple: register('diff-multiple', 0xec23), surroundWith: register('surround-with', 0xec24), + share: register('share', 0xec25), gitStash: register('git-stash', 0xec26), gitStashApply: register('git-stash-apply', 0xec27), gitStashPop: register('git-stash-pop', 0xec28), + vscode: register('vscode', 0xec29), + vscodeInsiders: register('vscode-insiders', 0xec2a), + codeOss: register('code-oss', 0xec2b), + runCoverage: register('run-coverage', 0xec2c), runAllCoverage: register('run-all-coverage', 0xec2d), - runCoverage: register('run-all-coverage', 0xec2c), coverage: register('coverage', 0xec2e), githubProject: register('github-project', 0xec2f), - } as const; From e09633b182cd5703001d170c98df4c5756cf52a4 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Fri, 1 Mar 2024 20:56:21 +0100 Subject: [PATCH 1770/1897] codicons: Add back copyright header --- src/vs/base/common/codiconsLibrary.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/base/common/codiconsLibrary.ts b/src/vs/base/common/codiconsLibrary.ts index 4d25fed1023..95886fa6017 100644 --- a/src/vs/base/common/codiconsLibrary.ts +++ b/src/vs/base/common/codiconsLibrary.ts @@ -1,4 +1,8 @@ -import { register } from 'vs/base/common/codiconsUtil' +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { register } from 'vs/base/common/codiconsUtil'; // This file is automatically generated by (microsoft/vscode-codicon)/scripts/export-to-ts.js // Please don't edit it, as your changes will be overwritten. From 6c9d5b3df6767cb0c87d97b0d755b63304b7ac68 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 5 Mar 2024 09:27:28 +0100 Subject: [PATCH 1771/1897] Custom hovers for input box except when focused (#206873) Customhovers for inputbox but not when focused --- src/vs/base/browser/ui/hover/updatableHoverWidget.ts | 11 +++++++++-- src/vs/base/browser/ui/inputbox/inputBox.ts | 9 ++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/ui/hover/updatableHoverWidget.ts b/src/vs/base/browser/ui/hover/updatableHoverWidget.ts index c3c505cef39..d36ebab0d95 100644 --- a/src/vs/base/browser/ui/hover/updatableHoverWidget.ts +++ b/src/vs/base/browser/ui/hover/updatableHoverWidget.ts @@ -270,7 +270,14 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM toDispose.add(triggerShowHover(hoverDelegate.delay, false, target)); hoverPreparation = toDispose; }; - const focusDomEmitter = dom.addDisposableListener(htmlElement, dom.EventType.FOCUS, onFocus, true); + + // Do not show hover when focusing an input or textarea + let focusDomEmitter: undefined | IDisposable; + const tagName = htmlElement.tagName.toLowerCase(); + if (tagName !== 'input' && tagName !== 'textarea') { + focusDomEmitter = dom.addDisposableListener(htmlElement, dom.EventType.FOCUS, onFocus, true); + } + const hover: ICustomHover = { show: focus => { hideHover(false, true); // terminate a ongoing mouse over preparation @@ -288,7 +295,7 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM mouseLeaveEmitter.dispose(); mouseDownEmitter.dispose(); mouseUpEmitter.dispose(); - focusDomEmitter.dispose(); + focusDomEmitter?.dispose(); hideHover(true, true); } }; diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index e4c89dd3aff..0a06ece485f 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -11,6 +11,8 @@ import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { AnchorAlignment, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Widget } from 'vs/base/browser/ui/widget'; import { IAction } from 'vs/base/common/actions'; @@ -111,6 +113,7 @@ export class InputBox extends Widget { private cachedContentHeight: number | undefined; private maxHeight: number = Number.POSITIVE_INFINITY; private scrollableElement: ScrollableElement | undefined; + private hover: ICustomHover | undefined; private _onDidChange = this._register(new Emitter()); public readonly onDidChange: Event = this._onDidChange.event; @@ -230,7 +233,11 @@ export class InputBox extends Widget { public setTooltip(tooltip: string): void { this.tooltip = tooltip; - this.input.title = tooltip; + if (!this.hover) { + this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.input, tooltip)); + } else { + this.hover.update(tooltip); + } } public setAriaLabel(label: string): void { From 8aca9a53311890fa54f0459c6ef2e8994d4d8ae4 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 5 Mar 2024 10:01:55 +0100 Subject: [PATCH 1772/1897] JSON Language Server output channel appears twice (#206877) --- .../client/src/browser/jsonClientMain.ts | 9 ++-- .../client/src/jsonClient.ts | 18 ++++--- .../client/src/node/jsonClientMain.ts | 48 ++++--------------- 3 files changed, 23 insertions(+), 52 deletions(-) diff --git a/extensions/json-language-features/client/src/browser/jsonClientMain.ts b/extensions/json-language-features/client/src/browser/jsonClientMain.ts index f7c87fbf9fa..f78f494d727 100644 --- a/extensions/json-language-features/client/src/browser/jsonClientMain.ts +++ b/extensions/json-language-features/client/src/browser/jsonClientMain.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, ExtensionContext, Uri, l10n } from 'vscode'; +import { Disposable, ExtensionContext, Uri, l10n, window } from 'vscode'; import { LanguageClientOptions } from 'vscode-languageclient'; -import { startClient, LanguageClientConstructor, SchemaRequestService, AsyncDisposable } from '../jsonClient'; +import { startClient, LanguageClientConstructor, SchemaRequestService, AsyncDisposable, languageServerDescription } from '../jsonClient'; import { LanguageClient } from 'vscode-languageclient/browser'; declare const Worker: { @@ -43,7 +43,10 @@ export async function activate(context: ExtensionContext) { } }; - client = await startClient(context, newLanguageClient, { schemaRequests, timer }); + const logOutputChannel = window.createOutputChannel(languageServerDescription, { log: true }); + context.subscriptions.push(logOutputChannel); + + client = await startClient(context, newLanguageClient, { schemaRequests, timer, logOutputChannel }); } catch (e) { console.log(e); diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index ce81dcb4c9e..ceb081403dc 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -6,7 +6,7 @@ export type JSONLanguageStatus = { schemas: string[] }; import { - workspace, window, languages, commands, OutputChannel, ExtensionContext, extensions, Uri, ColorInformation, + workspace, window, languages, commands, LogOutputChannel, ExtensionContext, extensions, Uri, ColorInformation, Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken, FoldingRange, ProviderResult, TextEdit, Range, Position, Disposable, CompletionItem, CompletionList, CompletionContext, Hover, MarkdownString, FoldingContext, DocumentSymbol, SymbolInformation, l10n } from 'vscode'; @@ -130,6 +130,7 @@ export interface Runtime { readonly timer: { setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable; }; + logOutputChannel: LogOutputChannel; } export interface SchemaRequestService { @@ -150,12 +151,10 @@ export interface AsyncDisposable { } export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { - const outputChannel = window.createOutputChannel(languageServerDescription); - const languageParticipants = getLanguageParticipants(); context.subscriptions.push(languageParticipants); - let client: Disposable | undefined = await startClientWithParticipants(context, languageParticipants, newLanguageClient, outputChannel, runtime); + let client: Disposable | undefined = await startClientWithParticipants(context, languageParticipants, newLanguageClient, runtime); let restartTrigger: Disposable | undefined; languageParticipants.onDidChange(() => { @@ -164,12 +163,12 @@ export async function startClient(context: ExtensionContext, newLanguageClient: } restartTrigger = runtime.timer.setTimeout(async () => { if (client) { - outputChannel.appendLine('Extensions have changed, restarting JSON server...'); - outputChannel.appendLine(''); + runtime.logOutputChannel.info('Extensions have changed, restarting JSON server...'); + runtime.logOutputChannel.info(''); const oldClient = client; client = undefined; await oldClient.dispose(); - client = await startClientWithParticipants(context, languageParticipants, newLanguageClient, outputChannel, runtime); + client = await startClientWithParticipants(context, languageParticipants, newLanguageClient, runtime); } }, 2000); }); @@ -178,12 +177,11 @@ export async function startClient(context: ExtensionContext, newLanguageClient: dispose: async () => { restartTrigger?.dispose(); await client?.dispose(); - outputChannel.dispose(); } }; } -async function startClientWithParticipants(context: ExtensionContext, languageParticipants: LanguageParticipants, newLanguageClient: LanguageClientConstructor, outputChannel: OutputChannel, runtime: Runtime): Promise { +async function startClientWithParticipants(context: ExtensionContext, languageParticipants: LanguageParticipants, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { const toDispose: Disposable[] = []; @@ -348,7 +346,7 @@ async function startClientWithParticipants(context: ExtensionContext, languagePa } }; - clientOptions.outputChannel = outputChannel; + clientOptions.outputChannel = runtime.logOutputChannel; // Create the language client and start the client. const client = newLanguageClient('json', languageServerDescription, clientOptions); client.registerProposedFeatures(); diff --git a/extensions/json-language-features/client/src/node/jsonClientMain.ts b/extensions/json-language-features/client/src/node/jsonClientMain.ts index 79d66e32dda..d57ebf80834 100644 --- a/extensions/json-language-features/client/src/node/jsonClientMain.ts +++ b/extensions/json-language-features/client/src/node/jsonClientMain.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, ExtensionContext, OutputChannel, window, workspace, l10n, env } from 'vscode'; +import { Disposable, ExtensionContext, LogOutputChannel, window, l10n, env, LogLevel } from 'vscode'; import { startClient, LanguageClientConstructor, SchemaRequestService, languageServerDescription, AsyncDisposable } from '../jsonClient'; import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node'; @@ -14,15 +14,16 @@ import { xhr, XHRResponse, getErrorStatusDescription, Headers } from 'request-li import TelemetryReporter from '@vscode/extension-telemetry'; import { JSONSchemaCache } from './schemaCache'; -let telemetry: TelemetryReporter | undefined; let client: AsyncDisposable | undefined; // this method is called when vs code is activated export async function activate(context: ExtensionContext) { const clientPackageJSON = await getPackageInfo(context); - telemetry = new TelemetryReporter(clientPackageJSON.aiKey); + const telemetry = new TelemetryReporter(clientPackageJSON.aiKey); + context.subscriptions.push(telemetry); - const outputChannel = window.createOutputChannel(languageServerDescription); + const logOutputChannel = window.createOutputChannel(languageServerDescription, { log: true }); + context.subscriptions.push(logOutputChannel); const serverMain = `./server/${clientPackageJSON.main.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/node/jsonServerMain`; const serverModule = context.asAbsolutePath(serverMain); @@ -38,11 +39,8 @@ export async function activate(context: ExtensionContext) { }; const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => { - clientOptions.outputChannel = outputChannel; return new LanguageClient(id, name, serverOptions, clientOptions); }; - const log = getLog(outputChannel); - context.subscriptions.push(log); const timer = { setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable { @@ -54,9 +52,9 @@ export async function activate(context: ExtensionContext) { // pass the location of the localization bundle to the server process.env['VSCODE_L10N_BUNDLE_LOCATION'] = l10n.uri?.toString() ?? ''; - const schemaRequests = await getSchemaRequestService(context, log); + const schemaRequests = await getSchemaRequestService(context, logOutputChannel); - client = await startClient(context, newLanguageClient, { schemaRequests, telemetry, timer }); + client = await startClient(context, newLanguageClient, { schemaRequests, telemetry, timer, logOutputChannel }); } export async function deactivate(): Promise { @@ -64,7 +62,6 @@ export async function deactivate(): Promise { await client.dispose(); client = undefined; } - telemetry?.dispose(); } interface IPackageInfo { @@ -84,36 +81,9 @@ async function getPackageInfo(context: ExtensionContext): Promise } } -interface Log { - trace(message: string): void; - isTrace(): boolean; - dispose(): void; -} - -const traceSetting = 'json.trace.server'; -function getLog(outputChannel: OutputChannel): Log { - let trace = workspace.getConfiguration().get(traceSetting) === 'verbose'; - const configListener = workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(traceSetting)) { - trace = workspace.getConfiguration().get(traceSetting) === 'verbose'; - } - }); - return { - trace(message: string) { - if (trace) { - outputChannel.appendLine(message); - } - }, - isTrace() { - return trace; - }, - dispose: () => configListener.dispose() - }; -} - const retryTimeoutInHours = 2 * 24; // 2 days -async function getSchemaRequestService(context: ExtensionContext, log: Log): Promise { +async function getSchemaRequestService(context: ExtensionContext, log: LogOutputChannel): Promise { let cache: JSONSchemaCache | undefined = undefined; const globalStorage = context.globalStorageUri; @@ -191,7 +161,7 @@ async function getSchemaRequestService(context: ExtensionContext, log: Log): Pro if (cache && /^https?:\/\/json\.schemastore\.org\//.test(uri)) { const content = await cache.getSchemaIfUpdatedSince(uri, retryTimeoutInHours); if (content) { - if (log.isTrace()) { + if (log.logLevel === LogLevel.Trace) { log.trace(`[json schema cache] Schema ${uri} from cache without request (last accessed ${cache.getLastUpdatedInHours(uri)} hours ago)`); } From 65cfaf8ad1e1f5a58e5a8d11b8ed619b7a10da44 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Tue, 5 Mar 2024 11:51:22 +0100 Subject: [PATCH 1773/1897] css --- .../auxiliarybar/media/auxiliaryBarPart.css | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css b/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css index 3d382b3e39d..ad3e53ded5a 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css +++ b/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css @@ -14,6 +14,22 @@ background-color: var(--vscode-sideBar-background); } +.monaco-workbench .part.auxiliarybar .title-actions .actions-container { + justify-content: flex-end; +} + +.monaco-workbench .part.auxiliarybar .title-actions .action-item { + margin-right: 4px; +} + +.monaco-workbench .part.auxiliarybar > .title > .title-label { + flex: 1; +} + +.monaco-workbench .part.auxiliarybar > .title > .title-label h2 { + text-transform: uppercase; +} + .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container { flex: 1; } @@ -37,7 +53,7 @@ outline: var(--vscode-contrastActiveBorder, unset) dashed 1px !important; } -.monaco-workbench .auxiliarybar.part.pane-composite-part > .composite.title.has-composite-bar > .title-actions { +.monaco-workbench .auxiliarybar.part.pane-composite-part > .composite.title > .title-actions { flex: inherit; } From 936a283cf93052193361af54aa60d029e0545d77 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 5 Mar 2024 14:54:23 +0100 Subject: [PATCH 1774/1897] fix #206764 (#206887) --- .../extensions/browser/extensionsViews.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index adcd04b081f..0d7b219e152 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -42,7 +42,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { SeverityIcon } from 'vs/platform/severityIcon/browser/severityIcon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; @@ -183,7 +183,20 @@ export class ExtensionsListView extends ViewPane { const messageBox = append(messageContainer, $('.message')); const delegate = new Delegate(); const extensionsViewState = new ExtensionsViewState(); - const renderer = this.instantiationService.createInstance(Renderer, extensionsViewState, { hoverOptions: { position: () => { return this.layoutService.getSideBarPosition() === Position.LEFT ? HoverPosition.RIGHT : HoverPosition.LEFT; } } }); + const renderer = this.instantiationService.createInstance(Renderer, extensionsViewState, { + hoverOptions: { + position: () => { + const viewLocation = this.viewDescriptorService.getViewLocationById(this.id); + if (viewLocation === ViewContainerLocation.Sidebar) { + return this.layoutService.getSideBarPosition() === Position.LEFT ? HoverPosition.RIGHT : HoverPosition.LEFT; + } + if (viewLocation === ViewContainerLocation.AuxiliaryBar) { + return this.layoutService.getSideBarPosition() === Position.LEFT ? HoverPosition.LEFT : HoverPosition.RIGHT; + } + return HoverPosition.RIGHT; + } + } + }); this.list = this.instantiationService.createInstance(WorkbenchPagedList, 'Extensions', extensionsList, delegate, [renderer], { multipleSelectionSupport: false, setRowLineHeight: false, From e3e0fe00a3ab862fab4c0ead4a148eca3064c704 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 5 Mar 2024 20:05:33 +0100 Subject: [PATCH 1775/1897] integrity - polish warnings (#206896) --- .../electron-sandbox/integrityService.ts | 166 +++++++++++------- 1 file changed, 98 insertions(+), 68 deletions(-) diff --git a/src/vs/workbench/services/integrity/electron-sandbox/integrityService.ts b/src/vs/workbench/services/integrity/electron-sandbox/integrityService.ts index 10caaf7ed6a..1834c7f3f53 100644 --- a/src/vs/workbench/services/integrity/electron-sandbox/integrityService.ts +++ b/src/vs/workbench/services/integrity/electron-sandbox/integrityService.ts @@ -15,20 +15,22 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { FileAccess, AppResourcePath } from 'vs/base/common/network'; import { IChecksumService } from 'vs/platform/checksum/common/checksumService'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IBannerService } from 'vs/workbench/services/banner/browser/bannerService'; +import { Codicon } from 'vs/base/common/codicons'; interface IStorageData { - dontShowPrompt: boolean; - commit: string | undefined; + readonly dontShowPrompt: boolean; + readonly commit: string | undefined; } class IntegrityStorage { + private static readonly KEY = 'integrityService'; - private storageService: IStorageService; private value: IStorageData | null; - constructor(storageService: IStorageService) { - this.storageService = storageService; + constructor(private readonly storageService: IStorageService) { this.value = this._read(); } @@ -37,6 +39,7 @@ class IntegrityStorage { if (!jsonValue) { return null; } + try { return JSON.parse(jsonValue); } catch (err) { @@ -58,36 +61,108 @@ export class IntegrityService implements IIntegrityService { declare readonly _serviceBrand: undefined; - private _storage: IntegrityStorage; - private _isPurePromise: Promise; + private readonly _storage = new IntegrityStorage(this.storageService); + + private readonly _isPurePromise = this._isPure(); + isPure(): Promise { + return this._isPurePromise; + } constructor( @INotificationService private readonly notificationService: INotificationService, - @IStorageService storageService: IStorageService, + @IStorageService private readonly storageService: IStorageService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IOpenerService private readonly openerService: IOpenerService, @IProductService private readonly productService: IProductService, - @IChecksumService private readonly checksumService: IChecksumService + @IChecksumService private readonly checksumService: IChecksumService, + @ILogService private readonly logService: ILogService, + @IBannerService private readonly bannerService: IBannerService ) { - this._storage = new IntegrityStorage(storageService); - - this._isPurePromise = this._isPure(); - - this.isPure().then(r => { - if (r.isPure) { - return; // all is good - } - - this._prompt(); - }); + this._compute(); } - private _prompt(): void { + private async _compute(): Promise { + const { isPure } = await this.isPure(); + if (isPure) { + return; // all is good + } + + this.logService.warn(` + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +!!! Installation has been modified on disk and is UNSUPPORTED. Please reinstall !!! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +`); + const storedData = this._storage.get(); if (storedData?.dontShowPrompt && storedData.commit === this.productService.commit) { return; // Do not prompt } + this._showBanner(); + this._showNotification(); + } + + private async _isPure(): Promise { + const expectedChecksums = this.productService.checksums || {}; + + await this.lifecycleService.when(LifecyclePhase.Eventually); + + const allResults = await Promise.all(Object.keys(expectedChecksums).map(filename => this._resolve(filename, expectedChecksums[filename]))); + + let isPure = true; + for (let i = 0, len = allResults.length; i < len; i++) { + if (!allResults[i].isPure) { + isPure = false; + break; + } + } + + return { + isPure, + proof: allResults + }; + } + + private async _resolve(filename: AppResourcePath, expected: string): Promise { + const fileUri = FileAccess.asFileUri(filename); + + try { + const checksum = await this.checksumService.checksum(fileUri); + + return IntegrityService._createChecksumPair(fileUri, checksum, expected); + } catch (error) { + return IntegrityService._createChecksumPair(fileUri, '', expected); + } + } + + private static _createChecksumPair(uri: URI, actual: string, expected: string): ChecksumPair { + return { + uri: uri, + actual: actual, + expected: expected, + isPure: (actual === expected) + }; + } + + private _showBanner(): void { + const checksumFailMoreInfoUrl = this.productService.checksumFailMoreInfoUrl; + + this.bannerService.show({ + id: 'installation.corrupt', + message: localize('integrity.banner', "Your {0} installation appears to be corrupt. Please reinstall.", this.productService.nameShort), + icon: Codicon.warning, + actions: checksumFailMoreInfoUrl ? [ + { + label: localize('integrity.moreInformation', "More Information"), + href: checksumFailMoreInfoUrl + } + ] : undefined + }); + } + + private _showNotification(): void { const checksumFailMoreInfoUrl = this.productService.checksumFailMoreInfoUrl; const message = localize('integrity.prompt', "Your {0} installation appears to be corrupt. Please reinstall.", this.productService.nameShort); if (checksumFailMoreInfoUrl) { @@ -114,56 +189,11 @@ export class IntegrityService implements IIntegrityService { this.notificationService.notify({ severity: Severity.Warning, message, - sticky: true + sticky: true, + priority: NotificationPriority.URGENT }); } } - - isPure(): Promise { - return this._isPurePromise; - } - - private async _isPure(): Promise { - const expectedChecksums = this.productService.checksums || {}; - - await this.lifecycleService.when(LifecyclePhase.Eventually); - - const allResults = await Promise.all(Object.keys(expectedChecksums).map(filename => this._resolve(filename, expectedChecksums[filename]))); - - let isPure = true; - for (let i = 0, len = allResults.length; i < len; i++) { - if (!allResults[i].isPure) { - isPure = false; - break; - } - } - - return { - isPure: isPure, - proof: allResults - }; - } - - private async _resolve(filename: AppResourcePath, expected: string): Promise { - const fileUri = FileAccess.asFileUri(filename); - - try { - const checksum = await this.checksumService.checksum(fileUri); - - return IntegrityService._createChecksumPair(fileUri, checksum, expected); - } catch (error) { - return IntegrityService._createChecksumPair(fileUri, '', expected); - } - } - - private static _createChecksumPair(uri: URI, actual: string, expected: string): ChecksumPair { - return { - uri: uri, - actual: actual, - expected: expected, - isPure: (actual === expected) - }; - } } registerSingleton(IIntegrityService, IntegrityService, InstantiationType.Delayed); From 8374b219a7ebff8f750abc5df9da9bfc2dc4c067 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Tue, 5 Mar 2024 11:41:24 -0800 Subject: [PATCH 1776/1897] Fix open walkthrough command for selecting a specific step (#206909) --- .../browser/gettingStarted.contribution.ts | 2 +- .../contrib/welcomeGettingStarted/browser/gettingStarted.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts index fc6c589ca16..66b9062611f 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts @@ -60,7 +60,7 @@ registerAction2(class extends Action2 { if (walkthroughID) { const selectedCategory = typeof walkthroughID === 'string' ? walkthroughID : walkthroughID.category; - const selectedStep = typeof walkthroughID === 'string' ? undefined : walkthroughID.step; + const selectedStep = typeof walkthroughID === 'string' ? undefined : walkthroughID.category + '#' + walkthroughID.step; // We're trying to open the welcome page from the Help menu if (!selectedCategory && !selectedStep) { diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 78a07e62ce2..a34712f15cb 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -1159,7 +1159,7 @@ export class GettingStartedPage extends EditorPane { this.editorInput.selectedCategory = categoryID; this.editorInput.selectedStep = stepId; this.currentWalkthrough = ourCategory; - this.buildCategorySlide(categoryID); + this.buildCategorySlide(categoryID, stepId); this.setSlide('details'); }); } From 957ccc60506e170450794caceca70f560f4d14cc Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 5 Mar 2024 11:42:06 -0800 Subject: [PATCH 1777/1897] Set session id as chat code block authority (#206912) Set session id and chat code block authority --- .../contrib/chat/browser/chatListRenderer.ts | 3 ++- .../workbench/contrib/chat/common/chatViewModel.ts | 2 +- .../chat/common/codeBlockModelCollection.ts | 14 +++++++------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index f97e96548ff..f06f0a9e5a4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -873,7 +873,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { languageId ??= ''; const newText = this.fixCodeText(value, languageId); - const textModel = this.codeBlockModelCollection.getOrCreate(model.id, codeBlockIndex++); + const textModel = this.codeBlockModelCollection.getOrCreate(this._model.sessionId, model.id, codeBlockIndex++); textModel.then(ref => { const model = ref.object.textEditorModel; if (languageId) { diff --git a/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts b/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts index edf5b4ae445..763773d71c2 100644 --- a/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts +++ b/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts @@ -25,18 +25,18 @@ export class CodeBlockModelCollection extends Disposable { this.clear(); } - get(responseId: string, codeBlockIndex: number): Promise> | undefined { - const uri = this.getUri(responseId, codeBlockIndex); + get(sessionId: string, responseId: string, codeBlockIndex: number): Promise> | undefined { + const uri = this.getUri(sessionId, responseId, codeBlockIndex); return this._models.get(uri); } - getOrCreate(responseId: string, codeBlockIndex: number): Promise> { - const existing = this.get(responseId, codeBlockIndex); + getOrCreate(sessionId: string, responseId: string, codeBlockIndex: number): Promise> { + const existing = this.get(sessionId, responseId, codeBlockIndex); if (existing) { return existing; } - const uri = this.getUri(responseId, codeBlockIndex); + const uri = this.getUri(sessionId, responseId, codeBlockIndex); const ref = this.textModelService.createModelReference(uri); this._models.set(uri, ref); return ref; @@ -47,7 +47,7 @@ export class CodeBlockModelCollection extends Disposable { this._models.clear(); } - private getUri(responseId: string, index: number): URI { - return URI.from({ scheme: Schemas.vscodeChatCodeBlock, path: `/${responseId}/${index}` }); + private getUri(sessionId: string, responseId: string, index: number): URI { + return URI.from({ scheme: Schemas.vscodeChatCodeBlock, authority: sessionId, path: `/${responseId}/${index}` }); } } From c0300af60aefe6fe121d3bf7111d30ade08f77d6 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 5 Mar 2024 11:48:38 -0800 Subject: [PATCH 1778/1897] Trigger notebook list rerender when whitespace is dismissed. --- .../browser/view/notebookCellListView.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts index e7f7d018a80..399934d7c49 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts @@ -295,8 +295,20 @@ export class NotebookCellListView extends ListView { } removeWhitespace(id: string): void { - this.notebookRangeMap.removeWhitespace(id); - this.eventuallyUpdateScrollDimensions(); + const scrollTop = this.scrollTop; + const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); + const currentPosition = this.notebookRangeMap.getWhitespacePosition(id); + + if (currentPosition > scrollTop) { + this.notebookRangeMap.removeWhitespace(id); + this.render(previousRenderRange, scrollTop, this.lastRenderHeight, undefined, undefined, false); + this._rerender(scrollTop, this.renderHeight, false); + this.eventuallyUpdateScrollDimensions(); + } else { + this.notebookRangeMap.removeWhitespace(id); + this.eventuallyUpdateScrollDimensions(); + } + } getWhitespacePosition(id: string): number { From defc1d52b7f9ca3a7799b722ec3f4d0f1cd4d438 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 5 Mar 2024 12:23:09 -0800 Subject: [PATCH 1779/1897] Focus stays in generated cell. --- .../controller/chat/cellChatActions.ts | 2 +- .../controller/chat/notebookChatController.ts | 43 +++++++++---------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 430062261c9..faca219b69c 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -209,7 +209,7 @@ registerAction2(class extends NotebookAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { - NotebookChatController.get(context.notebookEditor)?.dismiss(); + NotebookChatController.get(context.notebookEditor)?.dismiss(false); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 93a98b32d60..d4b59189404 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -702,7 +702,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._inlineChatSessionService.releaseSession(this._activeSession); } catch (_err) { } - this.dismiss(); + this.dismiss(false); } async focusAbove() { @@ -772,7 +772,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._strategy?.cancel(); this._activeRequestCts?.cancel(); this._widget?.discardChange(); - this.dismiss(); + this.dismiss(true); } async feedbackLast(kind: InlineChatResponseFeedbackKind) { @@ -783,25 +783,25 @@ export class NotebookChatController extends Disposable implements INotebookEdito } - dismiss() { - // move focus back to the cell above - if (this._widget) { - const widgetIndex = this._widget.afterModelPosition; - const currentFocus = this._notebookEditor.getFocus(); + dismiss(discard: boolean) { + const widget = this._widget; + const widgetIndex = widget?.afterModelPosition; + const currentFocus = this._notebookEditor.getFocus(); + const isWidgetFocused = currentFocus.start === widgetIndex && currentFocus.end === widgetIndex; - if (currentFocus.start === widgetIndex && currentFocus.end === widgetIndex) { - // focus is on the widget - if (widgetIndex === 0) { - // on top of all cells - if (this._notebookEditor.getLength() > 0) { - this._notebookEditor.focusNotebookCell(this._notebookEditor.cellAt(0)!, 'container'); - } - } else { - const cell = this._notebookEditor.cellAt(widgetIndex - 1); - if (cell) { - this._notebookEditor.focusNotebookCell(cell, 'container'); - } - } + if (widget && isWidgetFocused) { + // change focus only when the widget is focused + const editingCell = widget.getEditingCell(); + const shouldFocusEditingCell = editingCell && !discard; + const shouldFocusTopCell = widgetIndex === 0 && this._notebookEditor.getLength() > 0; + const shouldFocusAboveCell = widgetIndex !== 0 && this._notebookEditor.cellAt(widgetIndex - 1); + + if (shouldFocusEditingCell) { + this._notebookEditor.focusNotebookCell(editingCell, 'container'); + } else if (shouldFocusTopCell) { + this._notebookEditor.focusNotebookCell(this._notebookEditor.cellAt(0)!, 'container'); + } else if (shouldFocusAboveCell) { + this._notebookEditor.focusNotebookCell(this._notebookEditor.cellAt(widgetIndex - 1)!, 'container'); } } @@ -815,8 +815,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito } public override dispose(): void { - this.dismiss(); - + this.dismiss(false); super.dispose(); } } From 1be73b4a517ad5c4b6361b1fdbeedb8fc6d0d3a1 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 6 Mar 2024 01:18:59 +0100 Subject: [PATCH 1780/1897] Update extensions compatible with newly available VS Code version (#206924) #125417 update extensions compatible with newly available VS Code version --- .../abstractExtensionManagementService.ts | 38 ++--- .../common/extensionGalleryService.ts | 21 +-- .../common/extensionManagement.ts | 14 +- .../common/extensionManagementIpc.ts | 8 +- .../common/extensionsProfileScannerService.ts | 20 ++- .../common/extensionsScannerService.ts | 28 ++-- .../node/extensionManagementService.ts | 27 ++-- .../node/installGalleryExtensionTask.test.ts | 2 +- src/vs/platform/update/common/update.ts | 1 + .../electron-main/updateService.darwin.ts | 4 +- .../extensions/browser/extensionsActions.ts | 45 +++++- .../extensions/browser/extensionsViewlet.ts | 12 +- .../extensions/browser/extensionsViews.ts | 10 +- .../extensions/browser/extensionsWidgets.ts | 8 +- .../browser/extensionsWorkbenchService.ts | 142 ++++++++++++------ .../contrib/extensions/common/extensions.ts | 11 +- .../extensionRecommendationsService.test.ts | 4 +- .../extensionsActions.test.ts | 20 +-- .../electron-sandbox/extensionsViews.test.ts | 4 +- .../extensionsWorkbenchService.test.ts | 4 +- .../extensionManagementChannelClient.ts | 6 +- .../common/extensionManagementService.ts | 7 +- .../common/webExtensionManagementService.ts | 6 +- .../remoteExtensionManagementService.ts | 2 +- 24 files changed, 280 insertions(+), 164 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index d0cedb8d962..f7c4a89d735 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -16,7 +16,8 @@ import * as nls from 'vs/nls'; import { ExtensionManagementError, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementParticipant, IGalleryExtension, ILocalExtension, InstallOperation, IExtensionsControlManifest, StatisticType, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode, - InstallOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService, InstallExtensionInfo, EXTENSION_INSTALL_DEP_PACK_CONTEXT, ExtensionGalleryError + InstallOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService, InstallExtensionInfo, EXTENSION_INSTALL_DEP_PACK_CONTEXT, ExtensionGalleryError, + IProductVersion } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, ExtensionKey, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, IExtensionManifest, isApplicationScopedExtension, TargetPlatform } from 'vs/platform/extensions/common/extensions'; @@ -29,7 +30,7 @@ import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/use export type ExtensionVerificationStatus = boolean | string; export type InstallableExtension = { readonly manifest: IExtensionManifest; extension: IGalleryExtension | URI; options: InstallOptions }; -export type InstallExtensionTaskOptions = InstallOptions & { readonly profileLocation: URI }; +export type InstallExtensionTaskOptions = InstallOptions & { readonly profileLocation: URI; readonly productVersion: IProductVersion }; export interface IInstallExtensionTask { readonly identifier: IExtensionIdentifier; readonly source: IGalleryExtension | URI; @@ -124,7 +125,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl await Promise.allSettled(extensions.map(async ({ extension, options }) => { try { - const compatible = await this.checkAndGetCompatibleVersion(extension, !!options?.installGivenVersion, !!options?.installPreReleaseVersion); + const compatible = await this.checkAndGetCompatibleVersion(extension, !!options?.installGivenVersion, !!options?.installPreReleaseVersion, options.productVersion ?? { version: this.productService.version, date: this.productService.date }); installableExtensions.push({ ...compatible, options }); } catch (error) { results.push({ identifier: extension.identifier, operation: InstallOperation.Install, source: extension, error }); @@ -230,7 +231,8 @@ export abstract class AbstractExtensionManagementService extends Disposable impl ...options, installOnlyNewlyAddedFromExtensionPack: options.installOnlyNewlyAddedFromExtensionPack ?? !URI.isUri(extension) /* always true for gallery extensions */, isApplicationScoped, - profileLocation: isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation() + profileLocation: isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation(), + productVersion: options.productVersion ?? { version: this.productService.version, date: this.productService.date } }; const existingInstallExtensionTask = !URI.isUri(extension) ? this.installingExtensions.get(getInstallExtensionTaskKey(extension, installExtensionTaskOptions.profileLocation)) : undefined; @@ -248,8 +250,8 @@ export abstract class AbstractExtensionManagementService extends Disposable impl this.logService.info('Installing the extension without checking dependencies and pack', task.identifier.id); } else { try { - const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensions(task.identifier, manifest, !!task.options.installOnlyNewlyAddedFromExtensionPack, !!task.options.installPreReleaseVersion, task.options.profileLocation); - const installed = await this.getInstalled(undefined, task.options.profileLocation); + const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensions(task.identifier, manifest, !!task.options.installOnlyNewlyAddedFromExtensionPack, !!task.options.installPreReleaseVersion, task.options.profileLocation, task.options.productVersion); + const installed = await this.getInstalled(undefined, task.options.profileLocation, task.options.productVersion); const options: InstallExtensionTaskOptions = { ...task.options, donotIncludePackAndDependencies: true, context: { ...task.options.context, [EXTENSION_INSTALL_DEP_PACK_CONTEXT]: true } }; for (const { gallery, manifest } of distinct(allDepsAndPackExtensionsToInstall, ({ gallery }) => gallery.identifier.id)) { if (installingExtensionsMap.has(`${gallery.identifier.id.toLowerCase()}-${options.profileLocation.toString()}`)) { @@ -405,12 +407,12 @@ export abstract class AbstractExtensionManagementService extends Disposable impl return results; } - private async getAllDepsAndPackExtensions(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, getOnlyNewlyAddedFromExtensionPack: boolean, installPreRelease: boolean, profile: URI | undefined): Promise<{ gallery: IGalleryExtension; manifest: IExtensionManifest }[]> { + private async getAllDepsAndPackExtensions(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, getOnlyNewlyAddedFromExtensionPack: boolean, installPreRelease: boolean, profile: URI | undefined, productVersion: IProductVersion): Promise<{ gallery: IGalleryExtension; manifest: IExtensionManifest }[]> { if (!this.galleryService.isEnabled()) { return []; } - const installed = await this.getInstalled(undefined, profile); + const installed = await this.getInstalled(undefined, profile, productVersion); const knownIdentifiers: IExtensionIdentifier[] = []; const allDependenciesAndPacks: { gallery: IGalleryExtension; manifest: IExtensionManifest }[] = []; @@ -442,7 +444,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl const isDependency = dependecies.some(id => areSameExtensions({ id }, galleryExtension.identifier)); let compatible; try { - compatible = await this.checkAndGetCompatibleVersion(galleryExtension, false, installPreRelease); + compatible = await this.checkAndGetCompatibleVersion(galleryExtension, false, installPreRelease, productVersion); } catch (error) { if (!isDependency) { this.logService.info('Skipping the packed extension as it cannot be installed', galleryExtension.identifier.id, getErrorMessage(error)); @@ -462,7 +464,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl return allDependenciesAndPacks; } - private async checkAndGetCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, installPreRelease: boolean): Promise<{ extension: IGalleryExtension; manifest: IExtensionManifest }> { + private async checkAndGetCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, installPreRelease: boolean, productVersion: IProductVersion): Promise<{ extension: IGalleryExtension; manifest: IExtensionManifest }> { let compatibleExtension: IGalleryExtension | null; const extensionsControlManifest = await this.getExtensionsControlManifest(); @@ -473,7 +475,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl const deprecationInfo = extensionsControlManifest.deprecated[extension.identifier.id.toLowerCase()]; if (deprecationInfo?.extension?.autoMigrate) { this.logService.info(`The '${extension.identifier.id}' extension is deprecated, fetching the compatible '${deprecationInfo.extension.id}' extension instead.`); - compatibleExtension = (await this.galleryService.getExtensions([{ id: deprecationInfo.extension.id, preRelease: deprecationInfo.extension.preRelease }], { targetPlatform: await this.getTargetPlatform(), compatible: true }, CancellationToken.None))[0]; + compatibleExtension = (await this.galleryService.getExtensions([{ id: deprecationInfo.extension.id, preRelease: deprecationInfo.extension.preRelease }], { targetPlatform: await this.getTargetPlatform(), compatible: true, productVersion }, CancellationToken.None))[0]; if (!compatibleExtension) { throw new ExtensionManagementError(nls.localize('notFoundDeprecatedReplacementExtension', "Can't install '{0}' extension since it was deprecated and the replacement extension '{1}' can't be found.", extension.identifier.id, deprecationInfo.extension.id), ExtensionManagementErrorCode.Deprecated); } @@ -485,7 +487,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl throw new ExtensionManagementError(nls.localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform)), ExtensionManagementErrorCode.IncompatibleTargetPlatform); } - compatibleExtension = await this.getCompatibleVersion(extension, sameVersion, installPreRelease); + compatibleExtension = await this.getCompatibleVersion(extension, sameVersion, installPreRelease, productVersion); if (!compatibleExtension) { /** If no compatible release version is found, check if the extension has a release version or not and throw relevant error */ if (!installPreRelease && extension.properties.isPreReleaseVersion && (await this.galleryService.getExtensions([extension.identifier], CancellationToken.None))[0]) { @@ -508,23 +510,23 @@ export abstract class AbstractExtensionManagementService extends Disposable impl return { extension: compatibleExtension, manifest }; } - protected async getCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, includePreRelease: boolean): Promise { + protected async getCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, includePreRelease: boolean, productVersion: IProductVersion): Promise { const targetPlatform = await this.getTargetPlatform(); let compatibleExtension: IGalleryExtension | null = null; if (!sameVersion && extension.hasPreReleaseVersion && extension.properties.isPreReleaseVersion !== includePreRelease) { - compatibleExtension = (await this.galleryService.getExtensions([{ ...extension.identifier, preRelease: includePreRelease }], { targetPlatform, compatible: true }, CancellationToken.None))[0] || null; + compatibleExtension = (await this.galleryService.getExtensions([{ ...extension.identifier, preRelease: includePreRelease }], { targetPlatform, compatible: true, productVersion }, CancellationToken.None))[0] || null; } - if (!compatibleExtension && await this.galleryService.isExtensionCompatible(extension, includePreRelease, targetPlatform)) { + if (!compatibleExtension && await this.galleryService.isExtensionCompatible(extension, includePreRelease, targetPlatform, productVersion)) { compatibleExtension = extension; } if (!compatibleExtension) { if (sameVersion) { - compatibleExtension = (await this.galleryService.getExtensions([{ ...extension.identifier, version: extension.version }], { targetPlatform, compatible: true }, CancellationToken.None))[0] || null; + compatibleExtension = (await this.galleryService.getExtensions([{ ...extension.identifier, version: extension.version }], { targetPlatform, compatible: true, productVersion }, CancellationToken.None))[0] || null; } else { - compatibleExtension = await this.galleryService.getCompatibleExtension(extension, includePreRelease, targetPlatform); + compatibleExtension = await this.galleryService.getCompatibleExtension(extension, includePreRelease, targetPlatform, productVersion); } } @@ -718,7 +720,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl abstract install(vsix: URI, options?: InstallOptions): Promise; abstract installFromLocation(location: URI, profileLocation: URI): Promise; abstract installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise; - abstract getInstalled(type?: ExtensionType, profileLocation?: URI): Promise; + abstract getInstalled(type?: ExtensionType, profileLocation?: URI, productVersion?: IProductVersion): Promise; abstract copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise; abstract download(extension: IGalleryExtension, operation: InstallOperation, donotVerifySignature: boolean): Promise; abstract reinstallFromGallery(extension: ILocalExtension): Promise; diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 8833103e59c..1bec23931b3 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -15,7 +15,7 @@ import { URI } from 'vs/base/common/uri'; import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions, IDeprecationInfo, ISearchPrefferedResults, ExtensionGalleryError, ExtensionGalleryErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions, IDeprecationInfo, ISearchPrefferedResults, ExtensionGalleryError, ExtensionGalleryErrorCode, IProductVersion } from 'vs/platform/extensionManagement/common/extensionManagement'; import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; @@ -295,6 +295,7 @@ type GalleryServiceAdditionalQueryEvent = { }; interface IExtensionCriteria { + readonly productVersion: IProductVersion; readonly targetPlatform: TargetPlatform; readonly compatible: boolean; readonly includePreRelease: boolean | (IExtensionIdentifier & { includePreRelease: boolean })[]; @@ -662,14 +663,14 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi query = query.withSource(options.source); } - const { extensions } = await this.queryGalleryExtensions(query, { targetPlatform: options.targetPlatform ?? CURRENT_TARGET_PLATFORM, includePreRelease: includePreReleases, versions, compatible: !!options.compatible }, token); + const { extensions } = await this.queryGalleryExtensions(query, { targetPlatform: options.targetPlatform ?? CURRENT_TARGET_PLATFORM, includePreRelease: includePreReleases, versions, compatible: !!options.compatible, productVersion: options.productVersion ?? { version: this.productService.version, date: this.productService.date } }, token); if (options.source) { extensions.forEach((e, index) => setTelemetry(e, index, options.source)); } return extensions; } - async getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise { + async getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise { if (isNotWebExtensionInWebTargetPlatform(extension.allTargetPlatforms, targetPlatform)) { return null; } @@ -680,11 +681,11 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi .withFlags(Flags.IncludeVersions) .withPage(1, 1) .withFilter(FilterType.ExtensionId, extension.identifier.uuid); - const { extensions } = await this.queryGalleryExtensions(query, { targetPlatform, compatible: true, includePreRelease }, CancellationToken.None); + const { extensions } = await this.queryGalleryExtensions(query, { targetPlatform, compatible: true, includePreRelease, productVersion }, CancellationToken.None); return extensions[0] || null; } - async isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise { + async isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise { if (!isTargetPlatformCompatible(extension.properties.targetPlatform, extension.allTargetPlatforms, targetPlatform)) { return false; } @@ -702,10 +703,10 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi } engine = manifest.engines.vscode; } - return isEngineValid(engine, this.productService.version, this.productService.date); + return isEngineValid(engine, productVersion.version, productVersion.date); } - private async isValidVersion(rawGalleryExtensionVersion: IRawGalleryExtensionVersion, versionType: 'release' | 'prerelease' | 'any', compatible: boolean, allTargetPlatforms: TargetPlatform[], targetPlatform: TargetPlatform): Promise { + private async isValidVersion(rawGalleryExtensionVersion: IRawGalleryExtensionVersion, versionType: 'release' | 'prerelease' | 'any', compatible: boolean, allTargetPlatforms: TargetPlatform[], targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise { if (!isTargetPlatformCompatible(getTargetPlatformForExtensionVersion(rawGalleryExtensionVersion), allTargetPlatforms, targetPlatform)) { return false; } @@ -717,7 +718,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi if (compatible) { try { const engine = await this.getEngine(rawGalleryExtensionVersion); - if (!isEngineValid(engine, this.productService.version, this.productService.date)) { + if (!isEngineValid(engine, productVersion.version, productVersion.date)) { return false; } } catch (error) { @@ -784,7 +785,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi } const runQuery = async (query: Query, token: CancellationToken) => { - const { extensions, total } = await this.queryGalleryExtensions(query, { targetPlatform: CURRENT_TARGET_PLATFORM, compatible: false, includePreRelease: !!options.includePreRelease }, token); + const { extensions, total } = await this.queryGalleryExtensions(query, { targetPlatform: CURRENT_TARGET_PLATFORM, compatible: false, includePreRelease: !!options.includePreRelease, productVersion: options.productVersion ?? { version: this.productService.version, date: this.productService.date } }, token); extensions.forEach((e, index) => setTelemetry(e, ((query.pageNumber - 1) * query.pageSize) + index, options.source)); return { extensions, total }; }; @@ -913,7 +914,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi continue; } // Allow any version if includePreRelease flag is set otherwise only release versions are allowed - if (await this.isValidVersion(rawGalleryExtensionVersion, includePreRelease ? 'any' : 'release', criteria.compatible, allTargetPlatforms, criteria.targetPlatform)) { + if (await this.isValidVersion(rawGalleryExtensionVersion, includePreRelease ? 'any' : 'release', criteria.compatible, allTargetPlatforms, criteria.targetPlatform, criteria.productVersion)) { return toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, queryContext); } if (version && rawGalleryExtensionVersion.version === version) { diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 09a2b1dddcd..30c4f1c51eb 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -20,6 +20,11 @@ export const EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT = 'skipWalkthrough'; export const EXTENSION_INSTALL_SYNC_CONTEXT = 'extensionsSync'; export const EXTENSION_INSTALL_DEP_PACK_CONTEXT = 'dependecyOrPackExtensionInstall'; +export interface IProductVersion { + readonly version: string; + readonly date?: string; +} + export function TargetPlatformToString(targetPlatform: TargetPlatform) { switch (targetPlatform) { case TargetPlatform.WIN32_X64: return 'Windows 64 bit'; @@ -281,6 +286,7 @@ export interface IQueryOptions { sortOrder?: SortOrder; source?: string; includePreRelease?: boolean; + productVersion?: IProductVersion; } export const enum StatisticType { @@ -330,6 +336,7 @@ export interface IExtensionInfo extends IExtensionIdentifier { export interface IExtensionQueryOptions { targetPlatform?: TargetPlatform; + productVersion?: IProductVersion; compatible?: boolean; queryAllVersions?: boolean; source?: string; @@ -347,8 +354,8 @@ export interface IExtensionGalleryService { query(options: IQueryOptions, token: CancellationToken): Promise>; getExtensions(extensionInfos: ReadonlyArray, token: CancellationToken): Promise; getExtensions(extensionInfos: ReadonlyArray, options: IExtensionQueryOptions, token: CancellationToken): Promise; - isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise; - getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise; + isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion?: IProductVersion): Promise; + getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion?: IProductVersion): Promise; getAllCompatibleVersions(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise; download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise; downloadSignatureArchive(extension: IGalleryExtension, location: URI): Promise; @@ -454,6 +461,7 @@ export type InstallOptions = { operation?: InstallOperation; profileLocation?: URI; installOnlyNewlyAddedFromExtensionPack?: boolean; + productVersion?: IProductVersion; /** * Context passed through to InstallExtensionResult */ @@ -490,7 +498,7 @@ export interface IExtensionManagementService { uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise; toggleAppliationScope(extension: ILocalExtension, fromProfileLocation: URI): Promise; reinstallFromGallery(extension: ILocalExtension): Promise; - getInstalled(type?: ExtensionType, profileLocation?: URI): Promise; + getInstalled(type?: ExtensionType, profileLocation?: URI, productVersion?: IProductVersion): Promise; getExtensionsControlManifest(): Promise; copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise; updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise; diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index c4403134b93..b5f0317af55 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -9,7 +9,7 @@ import { cloneAndChange } from 'vs/base/common/objects'; import { URI, UriComponents } from 'vs/base/common/uri'; import { DefaultURITransformer, IURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IExtensionIdentifier, IExtensionTipsService, IGalleryExtension, ILocalExtension, IExtensionsControlManifest, isTargetPlatformCompatible, InstallOptions, UninstallOptions, Metadata, IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallOperation, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionIdentifier, IExtensionTipsService, IGalleryExtension, ILocalExtension, IExtensionsControlManifest, isTargetPlatformCompatible, InstallOptions, UninstallOptions, Metadata, IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallOperation, InstallExtensionInfo, IProductVersion } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionType, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI; @@ -139,7 +139,7 @@ export class ExtensionManagementChannel implements IServerChannel { return this.service.reinstallFromGallery(transformIncomingExtension(args[0], uriTransformer)); } case 'getInstalled': { - const extensions = await this.service.getInstalled(args[0], transformIncomingURI(args[1], uriTransformer)); + const extensions = await this.service.getInstalled(args[0], transformIncomingURI(args[1], uriTransformer), args[2]); return extensions.map(e => transformOutgoingExtension(e, uriTransformer)); } case 'toggleAppliationScope': { @@ -271,8 +271,8 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt return Promise.resolve(this.channel.call('reinstallFromGallery', [extension])).then(local => transformIncomingExtension(local, null)); } - getInstalled(type: ExtensionType | null = null, extensionsProfileResource?: URI): Promise { - return Promise.resolve(this.channel.call('getInstalled', [type, extensionsProfileResource])) + getInstalled(type: ExtensionType | null = null, extensionsProfileResource?: URI, productVersion?: IProductVersion): Promise { + return Promise.resolve(this.channel.call('getInstalled', [type, extensionsProfileResource, productVersion])) .then(extensions => extensions.map(extension => transformIncomingExtension(extension, null))); } diff --git a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts index 4fcf403d999..fddaf329af0 100644 --- a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts @@ -83,7 +83,7 @@ export interface IExtensionsProfileScannerService { readonly onDidRemoveExtensions: Event; scanProfileExtensions(profileLocation: URI, options?: IProfileExtensionsScanOptions): Promise; - addExtensionsToProfile(extensions: [IExtension, Metadata | undefined][], profileLocation: URI): Promise; + addExtensionsToProfile(extensions: [IExtension, Metadata | undefined][], profileLocation: URI, keepExistingVersions?: boolean): Promise; updateMetadata(extensions: [IExtension, Metadata | undefined][], profileLocation: URI): Promise; removeExtensionFromProfile(extension: IExtension, profileLocation: URI): Promise; } @@ -120,18 +120,22 @@ export abstract class AbstractExtensionsProfileScannerService extends Disposable return this.withProfileExtensions(profileLocation, undefined, options); } - async addExtensionsToProfile(extensions: [IExtension, Metadata | undefined][], profileLocation: URI): Promise { + async addExtensionsToProfile(extensions: [IExtension, Metadata | undefined][], profileLocation: URI, keepExistingVersions?: boolean): Promise { const extensionsToRemove: IScannedProfileExtension[] = []; const extensionsToAdd: IScannedProfileExtension[] = []; try { await this.withProfileExtensions(profileLocation, existingExtensions => { const result: IScannedProfileExtension[] = []; - for (const existing of existingExtensions) { - if (extensions.some(([e]) => areSameExtensions(e.identifier, existing.identifier) && e.manifest.version !== existing.version)) { - // Remove the existing extension with different version - extensionsToRemove.push(existing); - } else { - result.push(existing); + if (keepExistingVersions) { + result.push(...existingExtensions); + } else { + for (const existing of existingExtensions) { + if (extensions.some(([e]) => areSameExtensions(e.identifier, existing.identifier) && e.manifest.version !== existing.version)) { + // Remove the existing extension with different version + extensionsToRemove.push(existing); + } else { + result.push(existing); + } } } for (const [extension, metadata] of extensions) { diff --git a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts index 4ac28d17b5f..1174b510b51 100644 --- a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts @@ -22,7 +22,7 @@ import { isEmptyObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { Metadata } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IProductVersion, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, computeTargetPlatform, ExtensionKey, getExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, ExtensionIdentifier, IExtensionManifest, TargetPlatform, IExtensionIdentifier, IRelaxedExtensionManifest, UNDEFINED_PUBLISHER, IExtensionDescription, BUILTIN_MANIFEST_CACHE_FILE, USER_MANIFEST_CACHE_FILE, ExtensionIdentifierMap } from 'vs/platform/extensions/common/extensions'; import { validateExtensionManifest } from 'vs/platform/extensions/common/extensionValidator'; @@ -108,6 +108,7 @@ export type ScanOptions = { readonly checkControlFile?: boolean; readonly language?: string; readonly useCache?: boolean; + readonly productVersion?: IProductVersion; }; export const IExtensionsScannerService = createDecorator('IExtensionsScannerService'); @@ -195,7 +196,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem const location = scanOptions.profileLocation ?? this.userExtensionsLocation; this.logService.trace('Started scanning user extensions', location); const profileScanOptions: IProfileExtensionsScanOptions | undefined = this.uriIdentityService.extUri.isEqual(scanOptions.profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource) ? { bailOutWhenFileNotFound: true } : undefined; - const extensionsScannerInput = await this.createExtensionScannerInput(location, !!scanOptions.profileLocation, ExtensionType.User, !scanOptions.includeUninstalled, scanOptions.language, true, profileScanOptions); + const extensionsScannerInput = await this.createExtensionScannerInput(location, !!scanOptions.profileLocation, ExtensionType.User, !scanOptions.includeUninstalled, scanOptions.language, true, profileScanOptions, scanOptions.productVersion ?? this.getProductVersion()); const extensionsScanner = scanOptions.useCache && !extensionsScannerInput.devMode && extensionsScannerInput.excludeObsolete ? this.userExtensionsCachedScanner : this.extensionsScanner; let extensions: IRelaxedScannedExtension[]; try { @@ -217,7 +218,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem if (this.environmentService.isExtensionDevelopment && this.environmentService.extensionDevelopmentLocationURI) { const extensions = (await Promise.all(this.environmentService.extensionDevelopmentLocationURI.filter(extLoc => extLoc.scheme === Schemas.file) .map(async extensionDevelopmentLocationURI => { - const input = await this.createExtensionScannerInput(extensionDevelopmentLocationURI, false, ExtensionType.User, true, scanOptions.language, false /* do not validate */, undefined); + const input = await this.createExtensionScannerInput(extensionDevelopmentLocationURI, false, ExtensionType.User, true, scanOptions.language, false /* do not validate */, undefined, scanOptions.productVersion ?? this.getProductVersion()); const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(input); return extensions.map(extension => { // Override the extension type from the existing extensions @@ -233,7 +234,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem } async scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise { - const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, true, scanOptions.language, true, undefined); + const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, true, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion()); const extension = await this.extensionsScanner.scanExtension(extensionsScannerInput); if (!extension) { return null; @@ -245,7 +246,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem } async scanOneOrMultipleExtensions(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise { - const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, true, scanOptions.language, true, undefined); + const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, true, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion()); const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(extensionsScannerInput); return this.applyScanOptions(extensions, extensionType, scanOptions, true); } @@ -392,7 +393,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem private async scanDefaultSystemExtensions(useCache: boolean, language: string | undefined): Promise { this.logService.trace('Started scanning system extensions'); - const extensionsScannerInput = await this.createExtensionScannerInput(this.systemExtensionsLocation, false, ExtensionType.System, true, language, true, undefined); + const extensionsScannerInput = await this.createExtensionScannerInput(this.systemExtensionsLocation, false, ExtensionType.System, true, language, true, undefined, this.getProductVersion()); const extensionsScanner = useCache && !extensionsScannerInput.devMode ? this.systemExtensionsCachedScanner : this.extensionsScanner; const result = await extensionsScanner.scanExtensions(extensionsScannerInput); this.logService.trace('Scanned system extensions:', result.length); @@ -422,7 +423,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem break; } } - const result = await Promise.all(devSystemExtensionsLocations.map(async location => this.extensionsScanner.scanExtension((await this.createExtensionScannerInput(location, false, ExtensionType.System, true, language, true, undefined))))); + const result = await Promise.all(devSystemExtensionsLocations.map(async location => this.extensionsScanner.scanExtension((await this.createExtensionScannerInput(location, false, ExtensionType.System, true, language, true, undefined, this.getProductVersion()))))); this.logService.trace('Scanned dev system extensions:', result.length); return coalesce(result); } @@ -436,7 +437,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem } } - private async createExtensionScannerInput(location: URI, profile: boolean, type: ExtensionType, excludeObsolete: boolean, language: string | undefined, validate: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined): Promise { + private async createExtensionScannerInput(location: URI, profile: boolean, type: ExtensionType, excludeObsolete: boolean, language: string | undefined, validate: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined, productVersion: IProductVersion): Promise { const translations = await this.getTranslations(language ?? platform.language); const mtime = await this.getMtime(location); const applicationExtensionsLocation = profile && !this.uriIdentityService.extUri.isEqual(location, this.userDataProfilesService.defaultProfile.extensionsResource) ? this.userDataProfilesService.defaultProfile.extensionsResource : undefined; @@ -451,8 +452,8 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem type, excludeObsolete, validate, - this.productService.version, - this.productService.date, + productVersion.version, + productVersion.date, this.productService.commit, !this.environmentService.isBuilt, language, @@ -472,6 +473,13 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem return undefined; } + private getProductVersion(): IProductVersion { + return { + version: this.productService.version, + date: this.productService.date, + }; + } + } export class ExtensionScannerInput { diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index bb2828a935f..3d7c9420e50 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -28,7 +28,8 @@ import { INativeEnvironmentService } from 'vs/platform/environment/common/enviro import { AbstractExtensionManagementService, AbstractExtensionTask, ExtensionVerificationStatus, IInstallExtensionTask, InstallExtensionTaskOptions, IUninstallExtensionTask, joinErrors, toExtensionManagementError, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService'; import { ExtensionManagementError, ExtensionManagementErrorCode, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOperation, - Metadata, InstallOptions + Metadata, InstallOptions, + IProductVersion } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, computeTargetPlatform, ExtensionKey, getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionsProfileScannerService, IScannedProfileExtension } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; @@ -128,8 +129,8 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi } } - getInstalled(type?: ExtensionType, profileLocation: URI = this.userDataProfilesService.defaultProfile.extensionsResource): Promise { - return this.extensionsScanner.scanExtensions(type ?? null, profileLocation); + getInstalled(type?: ExtensionType, profileLocation: URI = this.userDataProfilesService.defaultProfile.extensionsResource, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise { + return this.extensionsScanner.scanExtensions(type ?? null, profileLocation, productVersion); } scanAllUserInstalledExtensions(): Promise { @@ -179,7 +180,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi async installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise { this.logService.trace('ExtensionManagementService#installExtensionsFromProfile', extensions, fromProfileLocation.toString(), toProfileLocation.toString()); - const extensionsToInstall = (await this.extensionsScanner.scanExtensions(ExtensionType.User, fromProfileLocation)).filter(e => extensions.some(id => areSameExtensions(id, e.identifier))); + const extensionsToInstall = (await this.getInstalled(ExtensionType.User, fromProfileLocation)).filter(e => extensions.some(id => areSameExtensions(id, e.identifier))); if (extensionsToInstall.length) { const metadata = await Promise.all(extensionsToInstall.map(e => this.extensionsScanner.scanMetadata(e, fromProfileLocation))); await this.addExtensionsToProfile(extensionsToInstall.map((e, index) => [e, metadata[index]]), toProfileLocation); @@ -236,7 +237,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi } copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise { - return this.extensionsScanner.copyExtensions(fromProfileLocation, toProfileLocation); + return this.extensionsScanner.copyExtensions(fromProfileLocation, toProfileLocation, { version: this.productService.version, date: this.productService.date }); } markAsUninstalled(...extensions: IExtension[]): Promise { @@ -333,7 +334,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi } } if (added) { - const extensions = await this.extensionsScanner.scanExtensions(ExtensionType.User, added.profileLocation); + const extensions = await this.getInstalled(ExtensionType.User, added.profileLocation); const addedExtensions = extensions.filter(e => added.extensions.some(identifier => areSameExtensions(identifier, e.identifier))); this._onDidInstallExtensions.fire(addedExtensions.map(local => { this.logService.info('Extensions added from another source', local.identifier.id, added.profileLocation.toString()); @@ -449,8 +450,8 @@ export class ExtensionsScanner extends Disposable { await this.removeUninstalledExtensions(); } - async scanExtensions(type: ExtensionType | null, profileLocation: URI): Promise { - const userScanOptions: ScanOptions = { includeInvalid: true, profileLocation }; + async scanExtensions(type: ExtensionType | null, profileLocation: URI, productVersion: IProductVersion): Promise { + const userScanOptions: ScanOptions = { includeInvalid: true, profileLocation, productVersion }; let scannedExtensions: IScannedExtension[] = []; if (type === null || type === ExtensionType.System) { scannedExtensions.push(...await this.extensionsScannerService.scanAllExtensions({ includeInvalid: true }, userScanOptions, false)); @@ -613,8 +614,8 @@ export class ExtensionsScanner extends Disposable { return this.scanLocalExtension(extension.location, extension.type, toProfileLocation); } - async copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise { - const fromExtensions = await this.scanExtensions(ExtensionType.User, fromProfileLocation); + async copyExtensions(fromProfileLocation: URI, toProfileLocation: URI, productVersion: IProductVersion): Promise { + const fromExtensions = await this.scanExtensions(ExtensionType.User, fromProfileLocation, productVersion); const extensions: [ILocalExtension, Metadata | undefined][] = await Promise.all(fromExtensions .filter(e => !e.isApplicationScoped) /* remove application scoped extensions */ .map(async e => ([e, await this.scanMetadata(e, fromProfileLocation)]))); @@ -819,7 +820,7 @@ abstract class InstallExtensionTask extends AbstractExtensionTask { let installed; try { - installed = await this.extensionsScanner.scanExtensions(null, this.options.profileLocation); + installed = await this.extensionsScanner.scanExtensions(null, this.options.profileLocation, this.options.productVersion); } catch (error) { throw new ExtensionManagementError(error, ExtensionManagementErrorCode.Scanning); } @@ -969,7 +970,7 @@ class InstallVSIXTask extends InstallExtensionTask { protected async install(token: CancellationToken): Promise<[ILocalExtension, Metadata]> { const extensionKey = new ExtensionKey(this.identifier, this.manifest.version); - const installedExtensions = await this.extensionsScanner.scanExtensions(ExtensionType.User, this.options.profileLocation); + const installedExtensions = await this.extensionsScanner.scanExtensions(ExtensionType.User, this.options.profileLocation, this.options.productVersion); const existing = installedExtensions.find(i => areSameExtensions(this.identifier, i.identifier)); const metadata: Metadata = { isApplicationScoped: this.options.isApplicationScoped || existing?.isApplicationScoped, diff --git a/src/vs/platform/extensionManagement/test/node/installGalleryExtensionTask.test.ts b/src/vs/platform/extensionManagement/test/node/installGalleryExtensionTask.test.ts index 1767e8931cb..7c40a4af114 100644 --- a/src/vs/platform/extensionManagement/test/node/installGalleryExtensionTask.test.ts +++ b/src/vs/platform/extensionManagement/test/node/installGalleryExtensionTask.test.ts @@ -102,7 +102,7 @@ class TestInstallGalleryExtensionTask extends InstallGalleryExtensionTask { engines: { vscode: '*' }, }, extension, - { profileLocation: userDataProfilesService.defaultProfile.extensionsResource }, + { profileLocation: userDataProfilesService.defaultProfile.extensionsResource, productVersion: { version: '' } }, extensionDownloader, new TestExtensionsScanner(), uriIdentityService, diff --git a/src/vs/platform/update/common/update.ts b/src/vs/platform/update/common/update.ts index 4cc8994bd01..02aeac681b5 100644 --- a/src/vs/platform/update/common/update.ts +++ b/src/vs/platform/update/common/update.ts @@ -9,6 +9,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' export interface IUpdate { version: string; productVersion: string; + timestamp?: number; url?: string; sha256hash?: string; } diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index 329488abb51..d79c9b6927a 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -24,8 +24,8 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau @memoize private get onRawError(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'error', (_, message) => message); } @memoize private get onRawUpdateNotAvailable(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-not-available'); } - @memoize private get onRawUpdateAvailable(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-available', (_, url, version) => ({ url, version, productVersion: version })); } - @memoize private get onRawUpdateDownloaded(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-downloaded', (_, releaseNotes, version, date) => ({ releaseNotes, version, productVersion: version, date })); } + @memoize private get onRawUpdateAvailable(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-available', (_, url, version, timestamp) => ({ url, version, productVersion: version, timestamp })); } + @memoize private get onRawUpdateDownloaded(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-downloaded', (_, version, timestamp) => ({ version, productVersion: version, timestamp })); } constructor( @ILifecycleMainService lifecycleMainService: ILifecycleMainService, diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 1ff80d2256d..8a4056cdf9b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -12,7 +12,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import * as json from 'vs/base/common/json'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { disposeIfDisposable } from 'vs/base/common/lifecycle'; -import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, UPDATE_ACTIONS_GROUP, AutoUpdateConfigurationKey, AutoUpdateConfigurationValue, ExtensionEditorTab } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, UPDATE_ACTIONS_GROUP, AutoUpdateConfigurationKey, AutoUpdateConfigurationValue, ExtensionEditorTab, ExtensionRuntimeActionType } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, TargetPlatformToString, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -73,6 +73,7 @@ import { showWindowLogActionId } from 'vs/workbench/services/log/common/logConst import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { Registry } from 'vs/platform/registry/common/platform'; +import { IUpdateService } from 'vs/platform/update/common/update'; export class PromptExtensionInstallFailureAction extends Action { @@ -1562,7 +1563,9 @@ export class ReloadAction extends ExtensionAction { constructor( @IHostService private readonly hostService: IHostService, + @IUpdateService private readonly updateService: IUpdateService, @IExtensionService private readonly extensionService: IExtensionService, + @IProductService private readonly productService: IProductService, ) { super('extensions.reload', localize('reloadAction', "Reload"), ReloadAction.DisabledClass, false); this._register(this.extensionService.onDidChangeExtensions(() => this.update())); @@ -1572,27 +1575,53 @@ export class ReloadAction extends ExtensionAction { update(): void { this.enabled = false; this.tooltip = ''; + this.class = ReloadAction.DisabledClass; + if (!this.extension) { return; } + const state = this.extension.state; if (state === ExtensionState.Installing || state === ExtensionState.Uninstalling) { return; } + if (this.extension.local && this.extension.local.manifest && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.localizations && this.extension.local.manifest.contributes.localizations.length > 0) { return; } - const reloadTooltip = this.extension.reloadRequiredStatus; - this.enabled = reloadTooltip !== undefined; - this.label = reloadTooltip !== undefined ? localize('reload required', 'Reload Required') : ''; - this.tooltip = reloadTooltip !== undefined ? reloadTooltip : ''; + const runtimeState = this.extension.runtimeState; + if (!runtimeState) { + return; + } - this.class = this.enabled ? ReloadAction.EnabledClass : ReloadAction.DisabledClass; + this.enabled = true; + this.class = ReloadAction.EnabledClass; + this.tooltip = runtimeState.reason; + this.label = runtimeState.action === ExtensionRuntimeActionType.Reload ? localize('reload required', 'Reload {0}', this.productService.nameShort) + : runtimeState.action === ExtensionRuntimeActionType.QuitAndInstall ? localize('restart product', 'Restart {0}', this.productService.nameShort) + : runtimeState.action === ExtensionRuntimeActionType.ApplyUpdate || runtimeState.action === ExtensionRuntimeActionType.DownloadUpdate ? localize('update product', 'Update {0}', this.productService.nameShort) : ''; } - override run(): Promise { - return Promise.resolve(this.hostService.reload()); + override async run(): Promise { + const runtimeState = this.extension?.runtimeState; + + if (runtimeState?.action === ExtensionRuntimeActionType.Reload) { + return this.hostService.reload(); + } + + else if (runtimeState?.action === ExtensionRuntimeActionType.DownloadUpdate) { + return this.updateService.downloadUpdate(); + } + + else if (runtimeState?.action === ExtensionRuntimeActionType.ApplyUpdate) { + return this.updateService.applyUpdate(); + } + + else if (runtimeState?.action === ExtensionRuntimeActionType.QuitAndInstall) { + return this.updateService.quitAndInstall(); + } + } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 38c0e666af1..43c231d7c45 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -853,19 +853,19 @@ export class StatusUpdater extends Disposable implements IWorkbenchContribution private onServiceChange(): void { this.badgeHandle.clear(); - const extensionsReloadRequired = this.extensionsWorkbenchService.installed.filter(e => e.reloadRequiredStatus !== undefined); - const outdated = this.extensionsWorkbenchService.outdated.reduce((r, e) => r + (this.extensionEnablementService.isEnabled(e.local!) && !extensionsReloadRequired.includes(e) ? 1 : 0), 0); - const newBadgeNumber = outdated + extensionsReloadRequired.length; + const actionRequired = this.extensionsWorkbenchService.installed.filter(e => e.runtimeState !== undefined); + const outdated = this.extensionsWorkbenchService.outdated.reduce((r, e) => r + (this.extensionEnablementService.isEnabled(e.local!) && !actionRequired.includes(e) ? 1 : 0), 0); + const newBadgeNumber = outdated + actionRequired.length; if (newBadgeNumber > 0) { let msg = ''; if (outdated) { msg += outdated === 1 ? localize('extensionToUpdate', '{0} requires update', outdated) : localize('extensionsToUpdate', '{0} require update', outdated); } - if (outdated > 0 && extensionsReloadRequired.length > 0) { + if (outdated > 0 && actionRequired.length > 0) { msg += ', '; } - if (extensionsReloadRequired.length) { - msg += extensionsReloadRequired.length === 1 ? localize('extensionToReload', '{0} requires reload', extensionsReloadRequired.length) : localize('extensionsToReload', '{0} require reload', extensionsReloadRequired.length); + if (actionRequired.length) { + msg += actionRequired.length === 1 ? localize('extensionToReload', '{0} requires restart', actionRequired.length) : localize('extensionsToReload', '{0} require restart', actionRequired.length); } const badge = new NumberBadge(newBadgeNumber, () => msg); this.badgeHandle.value = this.activityService.showViewContainerActivity(VIEWLET_ID, { badge }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 0d7b219e152..8e64f3bacaa 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -522,7 +522,7 @@ export class ExtensionsListView extends ViewPane { result = local.filter(e => !e.isBuiltin && matchingText(e)); result = this.sortExtensions(result, options); } else { - result = local.filter(e => (!e.isBuiltin || e.outdated || e.reloadRequiredStatus !== undefined) && matchingText(e)); + result = local.filter(e => (!e.isBuiltin || e.outdated || e.runtimeState !== undefined) && matchingText(e)); const runningExtensionsById = runningExtensions.reduce((result, e) => { result.set(e.identifier.value, e); return result; }, new ExtensionIdentifierMap()); const defaultSort = (e1: IExtension, e2: IExtension) => { @@ -551,21 +551,21 @@ export class ExtensionsListView extends ViewPane { }; const outdated: IExtension[] = []; - const reloadRequired: IExtension[] = []; + const actionRequired: IExtension[] = []; const noActionRequired: IExtension[] = []; result.forEach(e => { if (e.outdated) { outdated.push(e); } - else if (e.reloadRequiredStatus) { - reloadRequired.push(e); + else if (e.runtimeState) { + actionRequired.push(e); } else { noActionRequired.push(e); } }); - result = [...outdated.sort(defaultSort), ...reloadRequired.sort(defaultSort), ...noActionRequired.sort(defaultSort)]; + result = [...outdated.sort(defaultSort), ...actionRequired.sort(defaultSort), ...noActionRequired.sort(defaultSort)]; } return result; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 9245e2b8b4f..01bd5f4b89b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -616,10 +616,10 @@ export class ExtensionHoverWidget extends ExtensionWidget { const preReleaseMessage = ExtensionHoverWidget.getPreReleaseMessage(this.extension); const extensionRuntimeStatus = this.extensionsWorkbenchService.getExtensionStatus(this.extension); const extensionStatus = this.extensionStatusAction.status; - const reloadRequiredMessage = this.extension.reloadRequiredStatus; + const runtimeState = this.extension.runtimeState; const recommendationMessage = this.getRecommendationMessage(this.extension); - if (extensionRuntimeStatus || extensionStatus || reloadRequiredMessage || recommendationMessage || preReleaseMessage) { + if (extensionRuntimeStatus || extensionStatus || runtimeState || recommendationMessage || preReleaseMessage) { markdown.appendMarkdown(`---`); markdown.appendText(`\n`); @@ -656,9 +656,9 @@ export class ExtensionHoverWidget extends ExtensionWidget { markdown.appendText(`\n`); } - if (reloadRequiredMessage) { + if (runtimeState) { markdown.appendMarkdown(`$(${infoIcon.id}) `); - markdown.appendMarkdown(`${reloadRequiredMessage}`); + markdown.appendMarkdown(`${runtimeState.reason}`); markdown.appendText(`\n`); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 12460b0406d..bab4b2cc8c1 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -16,7 +16,7 @@ import { IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, InstallExtensionEvent, DidUninstallExtensionEvent, InstallOperation, WEB_EXTENSION_TAG, InstallExtensionResult, IExtensionsControlManifest, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo, isTargetPlatformCompatible, InstallExtensionInfo, EXTENSION_IDENTIFIER_REGEX, - InstallOptions + InstallOptions, IProductVersion } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService, DefaultIconPath } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, groupByExtension, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -24,7 +24,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { URI } from 'vs/base/common/uri'; -import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, HasOutdatedExtensionsContext, AutoUpdateConfigurationValue, InstallExtensionOptions } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, HasOutdatedExtensionsContext, AutoUpdateConfigurationValue, InstallExtensionOptions, ExtensionRuntimeState, ExtensionRuntimeActionType } from 'vs/workbench/contrib/extensions/common/extensions'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url'; import { ExtensionsInput, IExtensionEditorOptions } from 'vs/workbench/contrib/extensions/common/extensionsInput'; @@ -54,6 +54,7 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { mainWindow } from 'vs/base/browser/window'; import { IDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs'; import { IUpdateService, StateType } from 'vs/platform/update/common/update'; +import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; interface IExtensionStateProvider { (extension: Extension): T; @@ -76,7 +77,7 @@ export class Extension implements IExtension { constructor( private stateProvider: IExtensionStateProvider, - private runtimeStateProvider: IExtensionStateProvider, + private runtimeStateProvider: IExtensionStateProvider, public readonly server: IExtensionManagementServer | undefined, public local: ILocalExtension | undefined, public gallery: IGalleryExtension | undefined, @@ -274,7 +275,7 @@ export class Extension implements IExtension { && semver.eq(this.latestVersion, this.version); } - get reloadRequiredStatus(): string | undefined { + get runtimeState(): ExtensionRuntimeState | undefined { return this.runtimeStateProvider(this); } @@ -463,7 +464,7 @@ class Extensions extends Disposable { constructor( readonly server: IExtensionManagementServer, private readonly stateProvider: IExtensionStateProvider, - private readonly runtimeStateProvider: IExtensionStateProvider, + private readonly runtimeStateProvider: IExtensionStateProvider, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @ITelemetryService private readonly telemetryService: ITelemetryService, @@ -496,15 +497,14 @@ class Extensions extends Disposable { return this._local; } - async queryInstalled(): Promise { - await this.fetchInstalledExtensions(); + async queryInstalled(productVersion: IProductVersion): Promise { + await this.fetchInstalledExtensions(productVersion); this._onChange.fire(undefined); return this.local; } - async syncInstalledExtensionsWithGallery(galleryExtensions: IGalleryExtension[]): Promise { - let hasChanged: boolean = false; - const extensions = await this.mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions); + async syncInstalledExtensionsWithGallery(galleryExtensions: IGalleryExtension[], productVersion: IProductVersion): Promise { + const extensions = await this.mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions, productVersion); for (const [extension, gallery] of extensions) { // update metadata of the extension if it does not exist if (extension.local && !extension.local.identifier.uuid) { @@ -513,20 +513,18 @@ class Extensions extends Disposable { if (!extension.gallery || extension.gallery.version !== gallery.version || extension.gallery.properties.targetPlatform !== gallery.properties.targetPlatform) { extension.gallery = gallery; this._onChange.fire({ extension }); - hasChanged = true; } } - return hasChanged; } - private async mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions: IGalleryExtension[]): Promise<[Extension, IGalleryExtension][]> { + private async mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions: IGalleryExtension[], productVersion: IProductVersion): Promise<[Extension, IGalleryExtension][]> { const mappedExtensions = this.mapInstalledExtensionWithGalleryExtension(galleryExtensions); const targetPlatform = await this.server.extensionManagementService.getTargetPlatform(); const compatibleGalleryExtensions: IGalleryExtension[] = []; const compatibleGalleryExtensionsToFetch: IExtensionInfo[] = []; await Promise.allSettled(mappedExtensions.map(async ([extension, gallery]) => { if (extension.local) { - if (await this.galleryService.isExtensionCompatible(gallery, extension.local.preRelease, targetPlatform)) { + if (await this.galleryService.isExtensionCompatible(gallery, extension.local.preRelease, targetPlatform, productVersion)) { compatibleGalleryExtensions.push(gallery); } else { compatibleGalleryExtensionsToFetch.push({ ...extension.local.identifier, preRelease: extension.local.preRelease }); @@ -534,7 +532,7 @@ class Extensions extends Disposable { } })); if (compatibleGalleryExtensionsToFetch.length) { - const result = await this.galleryService.getExtensions(compatibleGalleryExtensionsToFetch, { targetPlatform, compatible: true, queryAllVersions: true }, CancellationToken.None); + const result = await this.galleryService.getExtensions(compatibleGalleryExtensionsToFetch, { targetPlatform, compatible: true, queryAllVersions: true, productVersion }, CancellationToken.None); compatibleGalleryExtensions.push(...result); } return this.mapInstalledExtensionWithGalleryExtension(compatibleGalleryExtensions); @@ -591,9 +589,9 @@ class Extensions extends Disposable { } } - private async fetchInstalledExtensions(): Promise { + private async fetchInstalledExtensions(productVersion?: IProductVersion): Promise { const extensionsControlManifest = await this.server.extensionManagementService.getExtensionsControlManifest(); - const all = await this.server.extensionManagementService.getInstalled(); + const all = await this.server.extensionManagementService.getInstalled(undefined, undefined, productVersion); // dedup user and system extensions by giving priority to user extensions. const installed = groupByExtension(all, r => r.identifier).reduce((result, extensions) => { @@ -793,19 +791,19 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } this.hasOutdatedExtensionsContextKey = HasOutdatedExtensionsContext.bindTo(contextKeyService); if (extensionManagementServerService.localExtensionManagementServer) { - this.localExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.localExtensionManagementServer, ext => this.getExtensionState(ext), ext => this.getReloadStatus(ext))); + this.localExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.localExtensionManagementServer, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext))); this._register(this.localExtensions.onChange(e => this.onDidChangeExtensions(e?.extension))); this._register(this.localExtensions.onReset(e => this.reset())); this.extensionsServers.push(this.localExtensions); } if (extensionManagementServerService.remoteExtensionManagementServer) { - this.remoteExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.remoteExtensionManagementServer, ext => this.getExtensionState(ext), ext => this.getReloadStatus(ext))); + this.remoteExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.remoteExtensionManagementServer, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext))); this._register(this.remoteExtensions.onChange(e => this.onDidChangeExtensions(e?.extension))); this._register(this.remoteExtensions.onReset(e => this.reset())); this.extensionsServers.push(this.remoteExtensions); } if (extensionManagementServerService.webExtensionManagementServer) { - this.webExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.webExtensionManagementServer, ext => this.getExtensionState(ext), ext => this.getReloadStatus(ext))); + this.webExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.webExtensionManagementServer, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext))); this._register(this.webExtensions.onChange(e => this.onDidChangeExtensions(e?.extension))); this._register(this.webExtensions.onReset(e => this.reset())); this.extensionsServers.push(this.webExtensions); @@ -861,8 +859,11 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension })); this._register(Event.debounce(this.onChange, () => undefined, 100)(() => this.hasOutdatedExtensionsContextKey.set(this.outdated.length > 0))); this._register(this.updateService.onStateChange(e => { - if ((e.type === StateType.AvailableForDownload || e.type === StateType.Downloading) && this.isAutoUpdateEnabled()) { - this.checkForUpdates(); + if (!this.isAutoUpdateEnabled()) { + return; + } + if ((e.type === StateType.CheckingForUpdates && e.explicit) || e.type === StateType.AvailableForDownload || e.type === StateType.Downloading) { + this.eventuallyCheckForUpdates(true); } })); @@ -964,19 +965,19 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension async queryLocal(server?: IExtensionManagementServer): Promise { if (server) { if (this.localExtensions && this.extensionManagementServerService.localExtensionManagementServer === server) { - return this.localExtensions.queryInstalled(); + return this.localExtensions.queryInstalled(this.getProductVersion()); } if (this.remoteExtensions && this.extensionManagementServerService.remoteExtensionManagementServer === server) { - return this.remoteExtensions.queryInstalled(); + return this.remoteExtensions.queryInstalled(this.getProductVersion()); } if (this.webExtensions && this.extensionManagementServerService.webExtensionManagementServer === server) { - return this.webExtensions.queryInstalled(); + return this.webExtensions.queryInstalled(this.getProductVersion()); } } if (this.localExtensions) { try { - await this.localExtensions.queryInstalled(); + await this.localExtensions.queryInstalled(this.getProductVersion()); } catch (error) { this.logService.error(error); @@ -984,7 +985,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } if (this.remoteExtensions) { try { - await this.remoteExtensions.queryInstalled(); + await this.remoteExtensions.queryInstalled(this.getProductVersion()); } catch (error) { this.logService.error(error); @@ -992,7 +993,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } if (this.webExtensions) { try { - await this.webExtensions.queryInstalled(); + await this.webExtensions.queryInstalled(this.getProductVersion()); } catch (error) { this.logService.error(error); @@ -1068,7 +1069,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private fromGallery(gallery: IGalleryExtension, extensionsControlManifest: IExtensionsControlManifest): IExtension { let extension = this.getInstalledExtensionMatchingGallery(gallery); if (!extension) { - extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getReloadStatus(ext), undefined, undefined, gallery); + extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, gallery); Extensions.updateExtensionFromControlManifest(extension, extensionsControlManifest); } return extension; @@ -1110,7 +1111,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return undefined; } - private getReloadStatus(extension: IExtension): string | undefined { + private getRuntimeState(extension: IExtension): ExtensionRuntimeState | undefined { const isUninstalled = extension.state === ExtensionState.Uninstalled; const runningExtension = this.extensionService.extensions.find(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier)); @@ -1118,7 +1119,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension const canRemoveRunningExtension = runningExtension && this.extensionService.canRemoveExtension(runningExtension); const isSameExtensionRunning = runningExtension && (!extension.server || extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension))); if (!canRemoveRunningExtension && isSameExtensionRunning && !runningExtension.isUnderDevelopment) { - return nls.localize('postUninstallTooltip', "Please reload Visual Studio Code to complete the uninstallation of this extension."); + return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postUninstallTooltip', "Please reload {0} to complete the uninstallation of this extension.", this.productService.nameLong) }; } return undefined; } @@ -1138,7 +1139,25 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (isSameExtensionRunning) { // Different version or target platform of same extension is running. Requires reload to run the current version if (!runningExtension.isUnderDevelopment && (extension.version !== runningExtension.version || extension.local.targetPlatform !== runningExtension.targetPlatform)) { - return nls.localize('postUpdateTooltip', "Please reload Visual Studio Code to enable the updated extension."); + const productCurrentVersion = this.getProductCurrentVersion(); + const productUpdateVersion = this.getProductUpdateVersion(); + if (productUpdateVersion + && !isEngineValid(extension.local.manifest.engines.vscode, productCurrentVersion.version, productCurrentVersion.date) + && isEngineValid(extension.local.manifest.engines.vscode, productUpdateVersion.version, productUpdateVersion.date) + ) { + const state = this.updateService.state; + if (state.type === StateType.AvailableForDownload) { + return { action: ExtensionRuntimeActionType.DownloadUpdate, reason: nls.localize('postUpdateDownloadTooltip', "Please update {0} to enable the updated extension.", this.productService.nameLong) }; + } + if (state.type === StateType.Downloaded) { + return { action: ExtensionRuntimeActionType.ApplyUpdate, reason: nls.localize('postUpdateUpdateTooltip', "Please update {0} to enable the updated extension.", this.productService.nameLong) }; + } + if (state.type === StateType.Ready) { + return { action: ExtensionRuntimeActionType.QuitAndInstall, reason: nls.localize('postUpdateRestartTooltip', "Please restart {0} to enable the updated extension.", this.productService.nameLong) }; + } + return undefined; + } + return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postUpdateTooltip', "Please reload {0} to enable the updated extension.", this.productService.nameLong) }; } if (this.extensionsServers.length > 1) { @@ -1146,12 +1165,12 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (extensionInOtherServer) { // This extension prefers to run on UI/Local side but is running in remote if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.localExtensionManagementServer) { - return nls.localize('enable locally', "Please reload Visual Studio Code to enable this extension locally."); + return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('enable locally', "Please reload {0} to enable this extension locally.", this.productService.nameLong) }; } // This extension prefers to run on Workspace/Remote side but is running in local if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.remoteExtensionManagementServer) { - return nls.localize('enable remote', "Please reload Visual Studio Code to enable this extension in {0}.", this.extensionManagementServerService.remoteExtensionManagementServer?.label); + return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('enable remote', "Please reload {0} to enable this extension in {1}.", this.productService.nameLong, this.extensionManagementServerService.remoteExtensionManagementServer?.label) }; } } } @@ -1161,20 +1180,20 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { // This extension prefers to run on UI/Local side but is running in remote if (this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest)) { - return nls.localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension."); + return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postEnableTooltip', "Please reload {0} to enable this extension.", this.productService.nameLong) }; } } if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { // This extension prefers to run on Workspace/Remote side but is running in local if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest)) { - return nls.localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension."); + return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postEnableTooltip', "Please reload {0} to enable this extension.", this.productService.nameLong) }; } } } return undefined; } else { if (isSameExtensionRunning) { - return nls.localize('postDisableTooltip', "Please reload Visual Studio Code to disable this extension."); + return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postDisableTooltip', "Please reload {0} to disable this extension.", this.productService.nameLong) }; } } return undefined; @@ -1183,7 +1202,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension // Extension is not running else { if (isEnabled && !this.extensionService.canAddExtension(toExtensionDescription(extension.local))) { - return nls.localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension."); + return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postEnableTooltip', "Please reload {0} to enable this extension.", this.productService.nameLong) }; } const otherServer = extension.server ? extension.server === this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer : null; @@ -1191,7 +1210,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension const extensionInOtherServer = this.local.filter(e => areSameExtensions(e.identifier, extension.identifier) && e.server === otherServer)[0]; // Same extension in other server exists and if (extensionInOtherServer && extensionInOtherServer.local && this.extensionEnablementService.isEnabled(extensionInOtherServer.local)) { - return nls.localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension."); + return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postEnableTooltip', "Please reload {0} to enable this extension.", this.productService.nameLong) }; } } } @@ -1376,7 +1395,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.telemetryService.publicLog2('galleryService:checkingForUpdates', { count: infos.length, }); - const galleryExtensions = await this.galleryService.getExtensions(infos, { targetPlatform, compatible: true }, CancellationToken.None); + const galleryExtensions = await this.galleryService.getExtensions(infos, { targetPlatform, compatible: true, productVersion: this.getProductVersion() }, CancellationToken.None); if (galleryExtensions.length) { await this.syncInstalledExtensionsWithGallery(galleryExtensions); } @@ -1414,8 +1433,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (!extensions.length) { return; } - const result = await Promise.allSettled(extensions.map(extensions => extensions.syncInstalledExtensionsWithGallery(gallery))); - if (this.isAutoUpdateEnabled() && result.some(r => r.status === 'fulfilled' && r.value)) { + await Promise.allSettled(extensions.map(extensions => extensions.syncInstalledExtensionsWithGallery(gallery, this.getProductVersion()))); + if (this.isAutoUpdateEnabled()) { this.eventuallyAutoUpdateExtensions(); } } @@ -1434,12 +1453,20 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } private eventuallyCheckForUpdates(immediate = false): void { + this.updatesCheckDelayer.cancel(); this.updatesCheckDelayer.trigger(async () => { if (this.isAutoUpdateEnabled() || this.isAutoCheckUpdatesEnabled()) { await this.checkForUpdates(); } this.eventuallyCheckForUpdates(); - }, immediate ? 0 : ExtensionsWorkbenchService.UpdatesCheckInterval).then(undefined, err => null); + }, immediate ? 0 : this.getUpdatesCheckInterval()).then(undefined, err => null); + } + + private getUpdatesCheckInterval(): number { + if (this.productService.quality === 'insider' && this.getProductUpdateVersion()) { + return 1000 * 60 * 60 * 1; // 1 hour + } + return ExtensionsWorkbenchService.UpdatesCheckInterval; } private eventuallyAutoUpdateExtensions(): void { @@ -1474,8 +1501,32 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } const toUpdate = this.outdated.filter(e => !e.local?.pinned && this.shouldAutoUpdateExtension(e)); + if (!toUpdate.length) { + return; + } - await Promises.settled(toUpdate.map(e => this.install(e, e.local?.preRelease ? { installPreReleaseVersion: true } : undefined))); + const productVersion = this.getProductVersion(); + await Promises.settled(toUpdate.map(e => this.install(e, e.local?.preRelease ? { installPreReleaseVersion: true, productVersion } : { productVersion }))); + } + + private getProductVersion(): IProductVersion { + return this.getProductUpdateVersion() ?? this.getProductCurrentVersion(); + } + + private getProductCurrentVersion(): IProductVersion { + return { version: this.productService.version, date: this.productService.date }; + } + + private getProductUpdateVersion(): IProductVersion | undefined { + switch (this.updateService.state.type) { + case StateType.AvailableForDownload: + case StateType.Downloading: + case StateType.Downloaded: + case StateType.Updating: + case StateType.Ready: + return { version: this.updateService.state.update.version, date: this.updateService.state.update.timestamp ? new Date(this.updateService.state.update.timestamp).toISOString() : undefined }; + } + return undefined; } private async updateExtensionsPinnedState(): Promise { @@ -1682,7 +1733,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension gallery = firstOrDefault(await this.galleryService.getExtensions([installableInfo], { targetPlatform }, CancellationToken.None)); } if (!extension && gallery) { - extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getReloadStatus(ext), undefined, undefined, gallery); + extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, gallery); Extensions.updateExtensionFromControlManifest(extension as Extension, await this.extensionManagementService.getExtensionsControlManifest()); } if (extension?.isMalicious) { @@ -1953,6 +2004,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension installOptions = installOptions ?? {}; installOptions.pinned = extension.local?.pinned || !this.shouldAutoUpdateExtension(extension); if (extension.local) { + installOptions.productVersion = this.getProductVersion(); return this.extensionManagementService.updateFromGallery(gallery, extension.local, installOptions); } else { return this.extensionManagementService.installFromGallery(gallery, installOptions); diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 0158101cc31..b523d97d6b2 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -39,6 +39,15 @@ export const enum ExtensionState { Uninstalled } +export const enum ExtensionRuntimeActionType { + Reload = 'reload', + DownloadUpdate = 'downloadUpdate', + ApplyUpdate = 'applyUpdate', + QuitAndInstall = 'quitAndInstall', +} + +export type ExtensionRuntimeState = { action: ExtensionRuntimeActionType; reason: string }; + export interface IExtension { readonly type: ExtensionType; readonly isBuiltin: boolean; @@ -69,7 +78,7 @@ export interface IExtension { readonly ratingCount?: number; readonly outdated: boolean; readonly outdatedTargetPlatform: boolean; - readonly reloadRequiredStatus?: string; + readonly runtimeState: ExtensionRuntimeState | undefined; readonly enablementState: EnablementState; readonly tags: readonly string[]; readonly categories: readonly string[]; diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts index 0db4f047c60..d0bb47b7529 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts @@ -63,7 +63,7 @@ import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { timeout } from 'vs/base/common/async'; -import { IUpdateService } from 'vs/platform/update/common/update'; +import { IUpdateService, State } from 'vs/platform/update/common/update'; const mockExtensionGallery: IGalleryExtension[] = [ aGalleryExtension('MockExtension1', { @@ -275,7 +275,7 @@ suite('ExtensionRecommendationsService Test', () => { }, }); - instantiationService.stub(IUpdateService, { onStateChange: Event.None }); + instantiationService.stub(IUpdateService, { onStateChange: Event.None, state: State.Uninitialized }); instantiationService.set(IExtensionsWorkbenchService, disposableStore.add(instantiationService.createInstance(ExtensionsWorkbenchService))); instantiationService.stub(IExtensionTipsService, disposableStore.add(instantiationService.createInstance(TestExtensionTipsService))); diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts index ea44eb82a58..3ed7d4a1b94 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts @@ -56,7 +56,7 @@ import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/envi import { platform } from 'vs/base/common/platform'; import { arch } from 'vs/base/common/process'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { IUpdateService } from 'vs/platform/update/common/update'; +import { IUpdateService, State } from 'vs/platform/update/common/update'; let instantiationService: TestInstantiationService; let installEvent: Emitter, @@ -137,7 +137,7 @@ function setupTest(disposables: Pick) { instantiationService.stub(IUserDataSyncEnablementService, disposables.add(instantiationService.createInstance(UserDataSyncEnablementService))); - instantiationService.stub(IUpdateService, { onStateChange: Event.None }); + instantiationService.stub(IUpdateService, { onStateChange: Event.None, state: State.Uninitialized }); instantiationService.set(IExtensionsWorkbenchService, disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService))); instantiationService.stub(IWorkspaceTrustManagementService, disposables.add(new TestWorkspaceTrustManagementService())); } @@ -1010,7 +1010,7 @@ suite('ReloadAction', () => { didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }]); await promise; assert.ok(testObject.enabled); - assert.strictEqual(testObject.tooltip, 'Please reload Visual Studio Code to enable this extension.'); + assert.strictEqual(testObject.tooltip, `Please reload ${instantiationService.get(IProductService).nameLong} to enable this extension.`); }); test('Test ReloadAction when extension is newly installed and reload is not required', async () => { @@ -1078,7 +1078,7 @@ suite('ReloadAction', () => { uninstallEvent.fire({ identifier: local.identifier }); didUninstallEvent.fire({ identifier: local.identifier }); assert.ok(testObject.enabled); - assert.strictEqual(testObject.tooltip, 'Please reload Visual Studio Code to complete the uninstallation of this extension.'); + assert.strictEqual(testObject.tooltip, `Please reload ${instantiationService.get(IProductService).nameLong} to complete the uninstallation of this extension.`); }); test('Test ReloadAction when extension is uninstalled and can be removed', async () => { @@ -1146,7 +1146,7 @@ suite('ReloadAction', () => { return new Promise(c => { disposables.add(testObject.onDidChange(() => { - if (testObject.enabled && testObject.tooltip === 'Please reload Visual Studio Code to enable the updated extension.') { + if (testObject.enabled && testObject.tooltip === `Please reload ${instantiationService.get(IProductService).nameLong} to enable the updated extension.`) { c(); } })); @@ -1200,7 +1200,7 @@ suite('ReloadAction', () => { await testObject.update(); assert.ok(testObject.enabled); - assert.strictEqual('Please reload Visual Studio Code to disable this extension.', testObject.tooltip); + assert.strictEqual(`Please reload ${instantiationService.get(IProductService).nameLong} to disable this extension.`, testObject.tooltip); }); test('Test ReloadAction when extension enablement is toggled when running', async () => { @@ -1243,7 +1243,7 @@ suite('ReloadAction', () => { await workbenchService.setEnablement(extensions[0], EnablementState.EnabledGlobally); await testObject.update(); assert.ok(testObject.enabled); - assert.strictEqual('Please reload Visual Studio Code to enable this extension.', testObject.tooltip); + assert.strictEqual(`Please reload ${instantiationService.get(IProductService).nameLong} to enable this extension.`, testObject.tooltip); }); test('Test ReloadAction when extension enablement is toggled when not running', async () => { @@ -1290,7 +1290,7 @@ suite('ReloadAction', () => { await workbenchService.setEnablement(extensions[0], EnablementState.EnabledGlobally); await testObject.update(); assert.ok(testObject.enabled); - assert.strictEqual('Please reload Visual Studio Code to enable this extension.', testObject.tooltip); + assert.strictEqual(`Please reload ${instantiationService.get(IProductService).nameLong} to enable this extension.`, testObject.tooltip); }); test('Test ReloadAction when a localization extension is newly installed', async () => { @@ -1441,7 +1441,7 @@ suite('ReloadAction', () => { await promise; assert.ok(testObject.enabled); - assert.strictEqual(testObject.tooltip, 'Please reload Visual Studio Code to enable this extension.'); + assert.strictEqual(testObject.tooltip, `Please reload ${instantiationService.get(IProductService).nameLong} to enable this extension.`); }); test('Test ReloadAction when ui extension is disabled on remote server and installed in local server', async () => { @@ -1480,7 +1480,7 @@ suite('ReloadAction', () => { await promise; assert.ok(testObject.enabled); - assert.strictEqual(testObject.tooltip, 'Please reload Visual Studio Code to enable this extension.'); + assert.strictEqual(testObject.tooltip, `Please reload ${instantiationService.get(IProductService).nameLong} to enable this extension.`); }); test('Test ReloadAction for remote ui extension is disabled when it is installed and enabled in local server', async () => { diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts index a349dbbf69c..d37a7df8775 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts @@ -48,7 +48,7 @@ import { arch } from 'vs/base/common/process'; import { IProductService } from 'vs/platform/product/common/productService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { IUpdateService } from 'vs/platform/update/common/update'; +import { IUpdateService, State } from 'vs/platform/update/common/update'; suite('ExtensionsViews Tests', () => { @@ -188,7 +188,7 @@ suite('ExtensionsViews Tests', () => { await (instantiationService.get(IWorkbenchExtensionEnablementService)).setEnablement([localDisabledTheme], EnablementState.DisabledGlobally); await (instantiationService.get(IWorkbenchExtensionEnablementService)).setEnablement([localDisabledLanguage], EnablementState.DisabledGlobally); - instantiationService.stub(IUpdateService, { onStateChange: Event.None }); + instantiationService.stub(IUpdateService, { onStateChange: Event.None, state: State.Uninitialized }); instantiationService.set(IExtensionsWorkbenchService, disposableStore.add(instantiationService.createInstance(ExtensionsWorkbenchService))); testableView = disposableStore.add(instantiationService.createInstance(ExtensionsListView, {}, { id: '', title: '' })); }); diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts index 7d56ee6d115..701a823db7d 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts @@ -51,7 +51,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { toDisposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Mutable } from 'vs/base/common/types'; -import { IUpdateService } from 'vs/platform/update/common/update'; +import { IUpdateService, State } from 'vs/platform/update/common/update'; suite('ExtensionsWorkbenchServiceTest', () => { @@ -132,7 +132,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', []); instantiationService.stubPromise(INotificationService, 'prompt', 0); (instantiationService.get(IWorkbenchExtensionEnablementService)).reset(); - instantiationService.stub(IUpdateService, { onStateChange: Event.None }); + instantiationService.stub(IUpdateService, { onStateChange: Event.None, state: State.Uninitialized }); }); test('test gallery extension', async () => { diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts index 600b2380d39..8c86a741081 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ILocalExtension, IGalleryExtension, InstallOptions, UninstallOptions, Metadata, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ILocalExtension, IGalleryExtension, InstallOptions, UninstallOptions, Metadata, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallExtensionInfo, IProductVersion } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier, ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionManagementChannelClient as BaseExtensionManagementChannelClient, ExtensionEventResult } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; @@ -89,8 +89,8 @@ export abstract class ProfileAwareExtensionManagementChannelClient extends BaseE return super.uninstall(extension, options); } - override async getInstalled(type: ExtensionType | null = null, extensionsProfileResource?: URI): Promise { - return super.getInstalled(type, await this.getProfileLocation(extensionsProfileResource)); + override async getInstalled(type: ExtensionType | null = null, extensionsProfileResource?: URI, productVersion?: IProductVersion): Promise { + return super.getInstalled(type, await this.getProfileLocation(extensionsProfileResource), productVersion); } override async updateMetadata(local: ILocalExtension, metadata: Partial, extensionsProfileResource?: URI): Promise { diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 45978737960..c60c940c4d5 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -5,7 +5,8 @@ import { Event, EventMultiplexer } from 'vs/base/common/event'; import { - ILocalExtension, IGalleryExtension, IExtensionIdentifier, IExtensionsControlManifest, IExtensionGalleryService, InstallOptions, UninstallOptions, InstallExtensionResult, ExtensionManagementError, ExtensionManagementErrorCode, Metadata, InstallOperation, EXTENSION_INSTALL_SYNC_CONTEXT, InstallExtensionInfo + ILocalExtension, IGalleryExtension, IExtensionIdentifier, IExtensionsControlManifest, IExtensionGalleryService, InstallOptions, UninstallOptions, InstallExtensionResult, ExtensionManagementError, ExtensionManagementErrorCode, Metadata, InstallOperation, EXTENSION_INSTALL_SYNC_CONTEXT, InstallExtensionInfo, + IProductVersion } from 'vs/platform/extensionManagement/common/extensionManagement'; import { DidChangeProfileForServerEvent, DidUninstallExtensionOnServerEvent, IExtensionManagementServer, IExtensionManagementServerService, InstallExtensionOnServerEvent, IWorkbenchExtensionManagementService, UninstallExtensionOnServerEvent } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionType, isLanguagePackExtension, IExtensionManifest, getWorkspaceSupportTypeMessage, TargetPlatform } from 'vs/platform/extensions/common/extensions'; @@ -80,8 +81,8 @@ export class ExtensionManagementService extends Disposable implements IWorkbench this.onDidChangeProfile = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { this._register(emitter.add(Event.map(server.extensionManagementService.onDidChangeProfile, e => ({ ...e, server })))); return emitter; }, this._register(new EventMultiplexer()))).event; } - async getInstalled(type?: ExtensionType, profileLocation?: URI): Promise { - const result = await Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.getInstalled(type, profileLocation))); + async getInstalled(type?: ExtensionType, profileLocation?: URI, productVersion?: IProductVersion): Promise { + const result = await Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.getInstalled(type, profileLocation, productVersion))); return flatten(result); } diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index e58051fda3f..80ff4f879d8 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ExtensionIdentifier, ExtensionType, IExtension, IExtensionIdentifier, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; -import { ILocalExtension, IGalleryExtension, InstallOperation, IExtensionGalleryService, Metadata, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ILocalExtension, IGalleryExtension, InstallOperation, IExtensionGalleryService, Metadata, InstallOptions, IProductVersion } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; import { Emitter, Event } from 'vs/base/common/event'; import { areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -170,8 +170,8 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe await this.webExtensionsScannerService.copyExtensions(fromProfileLocation, toProfileLocation, e => !e.metadata?.isApplicationScoped); } - protected override async getCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, includePreRelease: boolean): Promise { - const compatibleExtension = await super.getCompatibleVersion(extension, sameVersion, includePreRelease); + protected override async getCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, includePreRelease: boolean, productVersion: IProductVersion): Promise { + const compatibleExtension = await super.getCompatibleVersion(extension, sameVersion, includePreRelease, productVersion); if (compatibleExtension) { return compatibleExtension; } diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts index d5e8f029682..5e78b841010 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts @@ -87,7 +87,7 @@ export class NativeRemoteExtensionManagementService extends RemoteExtensionManag this.logService.info(`Downloading the '${extension.identifier.id}' extension locally and install`); const compatible = await this.checkAndGetCompatible(extension, !!installOptions.installPreReleaseVersion); installOptions = { ...installOptions, donotIncludePackAndDependencies: true }; - const installed = await this.getInstalled(ExtensionType.User); + const installed = await this.getInstalled(ExtensionType.User, undefined, installOptions.productVersion); const workspaceExtensions = await this.getAllWorkspaceDependenciesAndPackedExtensions(compatible, CancellationToken.None); if (workspaceExtensions.length) { this.logService.info(`Downloading the workspace dependencies and packed extensions of '${compatible.identifier.id}' locally and install`); From ea142b5ccdcb797b1de6b1a46fecbf25dea2e229 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Wed, 6 Mar 2024 04:15:43 +0100 Subject: [PATCH 1781/1897] fix: memory leak in notebook baseCellViewModel (#205499) * fix: memory leak in notebook baseCellViewModel * fix: dispose when detaching text editor * use mutabledisposable --- .../notebook/browser/viewModel/baseCellViewModel.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index 548a5a7c043..dd136ac471b 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, dispose, IDisposable, IReference } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, IReference, MutableDisposable, dispose } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -19,11 +19,11 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { IWordWrapTransientState, readTransientState, writeTransientState } from 'vs/workbench/contrib/codeEditor/browser/toggleWordWrap'; import { CellEditState, CellFocusMode, CursorAtBoundary, CursorAtLineBoundary, IEditableCellViewModel, INotebookCellDecorationOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookOptionsChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind, INotebookCellStatusBarItem, INotebookSearchOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotebookOptionsChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; export abstract class BaseCellViewModel extends Disposable { @@ -103,6 +103,7 @@ export abstract class BaseCellViewModel extends Disposable { private _editorViewStates: editorCommon.ICodeEditorViewState | null = null; private _editorTransientState: IWordWrapTransientState | null = null; private _resolvedCellDecorations = new Map(); + private _textModelRefChangeDisposable = this._register(new MutableDisposable()); private readonly _cellDecorationsChanged = this._register(new Emitter<{ added: INotebookCellDecorationOptions[]; removed: INotebookCellDecorationOptions[] }>()); onCellDecorationsChanged: Event<{ added: INotebookCellDecorationOptions[]; removed: INotebookCellDecorationOptions[] }> = this._cellDecorationsChanged.event; @@ -299,6 +300,7 @@ export abstract class BaseCellViewModel extends Disposable { this._textModelRef.dispose(); this._textModelRef = undefined; } + this._textModelRefChangeDisposable.clear(); } getText(): string { @@ -618,8 +620,7 @@ export abstract class BaseCellViewModel extends Disposable { if (!this._textModelRef) { throw new Error(`Cannot resolve text model for ${this.uri}`); } - - this._register(this.textModel!.onDidChangeContent(() => this.onDidChangeTextModelContent())); + this._textModelRefChangeDisposable.value = this.textModel!.onDidChangeContent(() => this.onDidChangeTextModelContent()); } return this.textModel!; From 982998a94cb37107c291e30458eb17c6fb759f96 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 6 Mar 2024 11:00:50 +0100 Subject: [PATCH 1782/1897] fix populating update properties for darwin (#206947) --- src/vs/platform/update/electron-main/updateService.darwin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index d79c9b6927a..bdc6e2d6766 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -25,7 +25,7 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau @memoize private get onRawError(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'error', (_, message) => message); } @memoize private get onRawUpdateNotAvailable(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-not-available'); } @memoize private get onRawUpdateAvailable(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-available', (_, url, version, timestamp) => ({ url, version, productVersion: version, timestamp })); } - @memoize private get onRawUpdateDownloaded(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-downloaded', (_, version, timestamp) => ({ version, productVersion: version, timestamp })); } + @memoize private get onRawUpdateDownloaded(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-downloaded', (_, releaseNotes, version, timestamp) => ({ version, productVersion: version, timestamp })); } constructor( @ILifecycleMainService lifecycleMainService: ILifecycleMainService, From bb71ca5a59c66d882b5bf7509212990a99c02d14 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Wed, 6 Mar 2024 11:57:24 +0100 Subject: [PATCH 1783/1897] design 2 --- src/vs/workbench/browser/media/part.css | 8 +- src/vs/workbench/browser/part.ts | 75 ++++++--- .../parts/auxiliarybar/auxiliaryBarPart.ts | 36 ++-- .../workbench/browser/parts/compositePart.ts | 8 +- .../browser/parts/media/compositepart.css | 4 +- .../browser/parts/media/paneCompositePart.css | 93 +++++----- .../browser/parts/paneCompositePart.ts | 159 ++++++++---------- .../browser/parts/panel/panelPart.ts | 8 +- .../browser/parts/sidebar/sidebarPart.ts | 29 ++-- .../browser/parts/views/viewPaneContainer.ts | 10 +- 10 files changed, 226 insertions(+), 204 deletions(-) diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css index c9e200132b9..b299dfa5e4a 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -23,12 +23,12 @@ } .monaco-workbench .part > .title, -.monaco-workbench .part > .footer { - display: none; /* Parts have to opt in to show title area */ +.monaco-workbench .part > .composite-bar-area { + display: none; /* Parts have to opt in to show and composite bar area */ } .monaco-workbench .part > .title, -.monaco-workbench .part > .footer { +.monaco-workbench .part > .composite-bar-area { height: 35px; display: flex; box-sizing: border-box; @@ -36,7 +36,7 @@ } .monaco-workbench .part > .title, -.monaco-workbench .part > .footer { +.monaco-workbench .part > .composite-bar-area { padding-left: 8px; padding-right: 8px; } diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index 0fe2060e6a4..0141d6c2707 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/part'; import { Component } from 'vs/workbench/common/component'; import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; -import { Dimension, size, IDimension, getActiveDocument } from 'vs/base/browser/dom'; +import { Dimension, size, IDimension, getActiveDocument, prepend, IDomPosition } from 'vs/base/browser/dom'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ISerializableView, IViewSize } from 'vs/base/browser/ui/grid/grid'; import { Event, Emitter } from 'vs/base/common/event'; @@ -16,14 +16,13 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; export interface IPartOptions { readonly hasTitle?: boolean; - readonly hasFooter?: () => boolean; readonly borderWidth?: () => number; } export interface ILayoutContentResult { readonly titleSize: IDimension; readonly contentSize: IDimension; - readonly footerSize: IDimension; + readonly compositeBarSize: IDimension; } /** @@ -35,13 +34,16 @@ export abstract class Part extends Component implements ISerializableView { private _dimension: Dimension | undefined; get dimension(): Dimension | undefined { return this._dimension; } + private _contentPosition: IDomPosition | undefined; + get contentPosition(): IDomPosition | undefined { return this._contentPosition; } + protected _onDidVisibilityChange = this._register(new Emitter()); readonly onDidVisibilityChange = this._onDidVisibilityChange.event; private parent: HTMLElement | undefined; private titleArea: HTMLElement | undefined; private contentArea: HTMLElement | undefined; - private footerArea: HTMLElement | undefined; + private compositeBarArea: HTMLElement | undefined; private partLayout: PartLayout | undefined; constructor( @@ -78,7 +80,6 @@ export abstract class Part extends Component implements ISerializableView { this.parent = parent; this.titleArea = this.createTitleArea(parent, options); this.contentArea = this.createContentArea(parent, options); - this.footerArea = this.createFooterArea(parent, options); this.partLayout = new PartLayout(this.options, this.contentArea); @@ -121,19 +122,44 @@ export abstract class Part extends Component implements ISerializableView { } /** - * Subclasses override to provide a footer area implementation. + * Creates the composite bar and sets the proper position. */ - protected createFooterArea(parent: HTMLElement, options?: object): HTMLElement | undefined { - return undefined; + protected setCompositeBarArea(compositeBarContainer: HTMLElement, position: 'top' | 'bottom'): void { + if (this.compositeBarArea) { + throw new Error('Composite bar already exists'); + } + + if (!this.parent || !this.titleArea) { + return; + } + + if (position === 'top') { + prepend(this.parent, compositeBarContainer); + compositeBarContainer.classList.add('top'); + } else { + this.parent.appendChild(compositeBarContainer); + compositeBarContainer.classList.add('bottom'); + } + + this.compositeBarArea = compositeBarContainer; + this.partLayout?.setCompositeBarVisibility(true); + this.relayout(); } - /** - * Returns the footer area container. - */ - protected getFooterArea(): HTMLElement | undefined { - return this.footerArea; + protected removeCompositeBarArea(): void { + if (this.compositeBarArea) { + this.compositeBarArea.remove(); + this.compositeBarArea = undefined; + this.partLayout?.setCompositeBarVisibility(false); + this.relayout(); + } } + private relayout() { + if (this.dimension && this.contentPosition) { + this.layout(this.dimension.width, this.dimension.height, this.contentPosition.top, this.contentPosition.left); + } + } /** * Layout title and content area in the given dimension. */ @@ -155,8 +181,9 @@ export abstract class Part extends Component implements ISerializableView { abstract minimumHeight: number; abstract maximumHeight: number; - layout(width: number, height: number, _top: number, _left: number): void { + layout(width: number, height: number, top: number, left: number): void { this._dimension = new Dimension(width, height); + this._contentPosition = { top, left }; } setVisible(visible: boolean) { @@ -171,7 +198,9 @@ export abstract class Part extends Component implements ISerializableView { class PartLayout { private static readonly TITLE_HEIGHT = 35; - private static readonly FOOTER_HEIGHT = 35; + private static readonly COMPOSITE_BAR_HEIGHT = 35; + + private compositeBarAreaVisible: boolean = false; constructor(private options: IPartOptions, private contentArea: HTMLElement | undefined) { } @@ -186,11 +215,11 @@ class PartLayout { } // Footer Size: Width (Fill), Height (Variable) - let footerSize: Dimension; - if (this.options.hasFooter?.()) { - footerSize = new Dimension(width, Math.min(height, PartLayout.FOOTER_HEIGHT)); + let compositeBarSize: Dimension; + if (this.compositeBarAreaVisible) { + compositeBarSize = new Dimension(width, Math.min(height, PartLayout.COMPOSITE_BAR_HEIGHT)); } else { - footerSize = Dimension.None; + compositeBarSize = Dimension.None; } let contentWidth = width; @@ -199,14 +228,18 @@ class PartLayout { } // Content Size: Width (Fill), Height (Variable) - const contentSize = new Dimension(contentWidth, height - titleSize.height - footerSize.height); + const contentSize = new Dimension(contentWidth, height - titleSize.height - compositeBarSize.height); // Content if (this.contentArea) { size(this.contentArea, contentSize.width, contentSize.height); } - return { titleSize, contentSize, footerSize }; + return { titleSize, contentSize, compositeBarSize }; + } + + setCompositeBarVisibility(visible: boolean): void { + this.compositeBarAreaVisible = visible; } } diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts index 777b1586efe..e01dc69a69d 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts @@ -19,17 +19,18 @@ import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; -import { IAction, Separator, toAction } from 'vs/base/common/actions'; +import { IAction, Separator, SubmenuAction, toAction } from 'vs/base/common/actions'; import { ToggleAuxiliaryBarAction } from 'vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions'; import { assertIsDefined } from 'vs/base/common/types'; import { LayoutPriority } from 'vs/base/browser/ui/splitview/splitview'; import { ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { AbstractPaneCompositePart } from 'vs/workbench/browser/parts/paneCompositePart'; +import { AbstractPaneCompositePart, CompositeBarPosition } from 'vs/workbench/browser/parts/paneCompositePart'; import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { IPaneCompositeBarOptions } from 'vs/workbench/browser/parts/paneCompositeBar'; -import { IMenuService } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; export class AuxiliaryBarPart extends AbstractPaneCompositePart { @@ -86,7 +87,6 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { Parts.AUXILIARYBAR_PART, { hasTitle: true, - hasFooter: () => this.shouldShowFooterCompositeBar(), borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0, }, AuxiliaryBarPart.activePanelSettingsKey, @@ -116,9 +116,7 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { } private onDidChangeActivityBarLocation(): void { - this.removeCompositeBar(); - this.updateTitleArea(); - this.updateFooterArea(); + this.updateCompositeBar(); const id = this.getActiveComposite()?.getId(); if (id) { @@ -156,7 +154,7 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { orientation: ActionsOrientation.HORIZONTAL, recomputeSizes: true, activityHoverOptions: { - position: () => this.shouldShowFooterCompositeBar() ? HoverPosition.ABOVE : HoverPosition.BELOW, + position: () => this.getCompositeBarPosition() === CompositeBarPosition.BOTTOM ? HoverPosition.ABOVE : HoverPosition.BELOW, }, fillExtraContextMenuActions: actions => this.fillExtraContextMenuActions(actions), compositeSize: 0, @@ -183,19 +181,33 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { actions.push(new Separator()); actions.push(viewsSubmenuAction); } + + const activityBarPositionMenu = this.menuService.createMenu(MenuId.ActivityBarPositionMenu, this.contextKeyService); + const positionActions: IAction[] = []; + createAndFillInContextMenuActions(activityBarPositionMenu, { shouldForwardArgs: true, renderShortTitle: true }, { primary: [], secondary: positionActions }); + activityBarPositionMenu.dispose(); + actions.push(...[ new Separator(), + new SubmenuAction('workbench.action.panel.position', localize('activity bar position', "Activity Bar Position"), positionActions), toAction({ id: ToggleSidebarPositionAction.ID, label: currentPositionRight ? localize('move second side bar left', "Move Secondary Side Bar Left") : localize('move second side bar right', "Move Secondary Side Bar Right"), run: () => this.commandService.executeCommand(ToggleSidebarPositionAction.ID) }), toAction({ id: ToggleAuxiliaryBarAction.ID, label: localize('hide second side bar', "Hide Secondary Side Bar"), run: () => this.commandService.executeCommand(ToggleAuxiliaryBarAction.ID) }) ]); } - protected shouldShowTitleCompositeBar(): boolean { - return !this.shouldShowFooterCompositeBar(); + protected shouldShowCompositeBar(): boolean { + return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) !== ActivityBarPosition.HIDDEN; } - protected shouldShowFooterCompositeBar(): boolean { - return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.BOTTOM; + protected getCompositeBarPosition(): CompositeBarPosition { + const activityBarPosition = this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION); + switch (activityBarPosition) { + case ActivityBarPosition.BOTTOM: return CompositeBarPosition.BOTTOM; + case ActivityBarPosition.HIDDEN: return CompositeBarPosition.TITLE; + case ActivityBarPosition.SIDE: return CompositeBarPosition.TITLE; + case ActivityBarPosition.TOP: + default: return CompositeBarPosition.TOP; + } } override toJSON(): object { diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 69dc973cad1..146a58620b1 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -438,12 +438,8 @@ export abstract class CompositePart extends Part { }; } - protected override createFooterArea(parent: HTMLElement): HTMLElement { - const footerArea = append(parent, $('.composite')); - footerArea.classList.add('footer'); - - hide(footerArea); - return footerArea; + protected createCompositeBarArea(): HTMLElement { + return $('.composite.composite-bar-area'); } override updateStyles(): void { diff --git a/src/vs/workbench/browser/parts/media/compositepart.css b/src/vs/workbench/browser/parts/media/compositepart.css index 9dc8e5b560f..4f7afbfab50 100644 --- a/src/vs/workbench/browser/parts/media/compositepart.css +++ b/src/vs/workbench/browser/parts/media/compositepart.css @@ -7,7 +7,7 @@ height: 100%; } -.monaco-workbench .part > .composite.footer, +.monaco-workbench .part > .composite.composite-bar-area, .monaco-workbench .part > .composite.title { display: flex; } @@ -15,4 +15,4 @@ .monaco-workbench .part > .composite.title > .title-actions { flex: 1; padding-left: 5px; -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index b35ecf91e49..a324f08d4b4 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -20,17 +20,18 @@ display: none; } -.monaco-workbench .pane-composite-part > .footer{ +.monaco-workbench .pane-composite-part > .composite-bar-area.bottom { border-top: 1px solid var(--vscode-sideBarSectionHeader-border); } + .monaco-workbench .pane-composite-part > .title > .composite-bar-container, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container { display: flex; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-label.codicon-more, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-label.codicon-more { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-label.codicon-more { display: flex; align-items: center; justify-content: center; @@ -40,13 +41,13 @@ } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar { line-height: 27px; /* matches panel titles in settings */ height: 35px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item { text-transform: uppercase; padding-left: 10px; padding-right: 10px; @@ -57,26 +58,26 @@ } .monaco-workbench .pane-composite-part > .title > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item.icon, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item.icon { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item.icon { height: 24px; padding: 0 5px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container >.composite-bar .monaco-action-bar .action-label.codicon, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container >.composite-bar .monaco-action-bar .action-label.codicon { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container >.composite-bar .monaco-action-bar .action-label.codicon { font-size: 18px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label:not(.codicon), -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label:not(.codicon) { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label:not(.codicon) { width: 16px; height: 16px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before, +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after { content: ''; width: 2px; height: 24px; @@ -91,32 +92,32 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::before, .monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::after, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::before, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::after { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::before, +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::after { display: block; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before { left: 1px; margin-left: -2px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after { right: 1px; margin-right: -2px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:first-of-type::before, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:first-of-type::before { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:first-of-type::before { left: 2px; margin-left: -2px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type::after, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type::after { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type::after { right: 2px; margin-right: -2px; } @@ -125,10 +126,10 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::after, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right::after, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right::before, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::after, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right::after { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right::before, +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::after, +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right::after { transition-delay: 0s; } @@ -137,51 +138,51 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type.right::after, .monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over-head > .composite-bar > .monaco-action-bar .action-item:first-of-type::before, .monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over-tail > .composite-bar > .monaco-action-bar .action-item:last-of-type::after, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right + .action-item::before, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type.right::after, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container.dragged-over-head > .composite-bar > .monaco-action-bar .action-item:first-of-type::before, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container.dragged-over-tail > .composite-bar > .monaco-action-bar .action-item:last-of-type::after { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right + .action-item::before, +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type.right::after, +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container.dragged-over-head > .composite-bar > .monaco-action-bar .action-item:first-of-type::before, +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container.dragged-over-tail > .composite-bar > .monaco-action-bar .action-item:last-of-type::after { opacity: 1; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label { margin-right: 0; padding: 2px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label { border-radius: 0; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.icon) .action-label, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label.codicon, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.icon) .action-label, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label.codicon { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.icon) .action-label, +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label.codicon { background: none !important; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label { margin-right: 0; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .badge, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .badge { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .badge { margin-left: 8px; display: flex; align-items: center; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge { margin-left: 0px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .badge .badge-content, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .badge .badge-content { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .badge .badge-content { padding: 3px 5px; border-radius: 11px; font-size: 11px; @@ -196,7 +197,7 @@ } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact { position: absolute; top: 0; bottom: 0; @@ -209,7 +210,7 @@ } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact .badge-content, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact .badge-content { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact .badge-content { position: absolute; top: 11px; right: 0px; @@ -224,7 +225,7 @@ } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact.progress-badge .badge-content::before, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact.progress-badge .badge-content::before { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact.progress-badge .badge-content::before { mask-size: 11px; -webkit-mask-size: 11px; top: 3px; @@ -233,7 +234,7 @@ /* active item indicator */ .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator { position: absolute; z-index: 1; bottom: 0; @@ -243,14 +244,14 @@ } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator { top: -4px; left: 10px; width: calc(100% - 20px); } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .active-item-indicator, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .active-item-indicator { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .active-item-indicator { top: 1px; left: 2px; width: calc(100% - 4px); @@ -258,8 +259,8 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .active-item-indicator:before, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .active-item-indicator:before, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .active-item-indicator:before, +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { content: ""; position: absolute; z-index: 1; @@ -271,24 +272,24 @@ } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.clicked:not(.checked):focus .active-item-indicator:before, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.clicked:not(.checked):focus .active-item-indicator:before { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.clicked:not(.checked):focus .active-item-indicator:before { border-top-color: transparent !important; /* hides border on clicked state */ } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { border-top-color: var(--vscode-focusBorder) !important; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label { outline: var(--vscode-contrastActiveBorder, unset) solid 1px !important; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label, -.monaco-workbench .pane-composite-part > .footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label { +.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label { outline: var(--vscode-contrastActiveBorder, unset) dashed 1px !important; } diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index 8010742b79a..876e85c3aea 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -15,7 +15,7 @@ import { IView } from 'vs/base/browser/ui/grid/grid'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { CompositePart, ICompositeTitleLabel } from 'vs/workbench/browser/parts/compositePart'; import { IPaneCompositeBarOptions, PaneCompositeBar } from 'vs/workbench/browser/parts/paneCompositeBar'; -import { Dimension, EventHelper, trackFocus, $, addDisposableListener, EventType, prepend, getWindow, hide, show, IDomPosition } from 'vs/base/browser/dom'; +import { Dimension, EventHelper, trackFocus, $, addDisposableListener, EventType, prepend, getWindow } from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -40,6 +40,12 @@ import { Composite } from 'vs/workbench/browser/composite'; import { ViewsSubMenu } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +export enum CompositeBarPosition { + TOP, + TITLE, + BOTTOM +} + export interface IPaneCompositePart extends IView { readonly partId: Parts.PANEL_PART | Parts.AUXILIARYBAR_PART | Parts.SIDEBAR_PART; @@ -108,12 +114,11 @@ export abstract class AbstractPaneCompositePart extends CompositePart()); - private titleCompositeBarVisible: boolean = false; - private footerCompositeBarVisible: boolean = false; + private compositeBarPosition: CompositeBarPosition | undefined = undefined; private emptyPaneMessageElement: HTMLElement | undefined; private globalToolBar: ToolBar | undefined; @@ -121,7 +126,6 @@ export abstract class AbstractPaneCompositePart extends CompositePart this.paneFocusContextKey.set(true))); this._register(focusTracker.onDidBlur(() => this.paneFocusContextKey.set(false))); @@ -331,96 +337,78 @@ export abstract class AbstractPaneCompositePart extends CompositePart { + this.onCompositeBarAreaContextMenu(new StandardMouseEvent(getWindow(compositeBarArea), e)); + })); + this.separateCompositeBarDispoables.add(Gesture.addTarget(compositeBarArea)); + this.separateCompositeBarDispoables.add(addDisposableListener(compositeBarArea, GestureEventType.Contextmenu, e => { + this.onCompositeBarAreaContextMenu(new StandardMouseEvent(getWindow(compositeBarArea), e)); + })); + + return compositeBarArea; } - private setFooterVisibility(visible: boolean): void { - if (!this.footerContainer) { - return; - } - - if (visible) { - show(this.footerContainer); - - // Only add listener if the footer is visible - this.footerDispoables.add(addDisposableListener(this.footerContainer, EventType.CONTEXT_MENU, e => { - this.onFooterAreaContextMenu(new StandardMouseEvent(getWindow(this.footerContainer), e)); - })); - this.footerDispoables.add(Gesture.addTarget(this.footerContainer)); - this.footerDispoables.add(addDisposableListener(this.footerContainer, GestureEventType.Contextmenu, e => { - this.onFooterAreaContextMenu(new StandardMouseEvent(getWindow(this.footerContainer), e)); - })); - } else { - hide(this.footerContainer); - - // Dispose any footer area listener - this.footerDispoables.clear(); - } - - if (this.contentDimension && this.contentPosition) { - this.layout(this.contentDimension.width, this.contentDimension.height, this.contentPosition.top, this.contentPosition.left); - } + protected override removeCompositeBarArea(): void { + this.separateCompositeBarContainer = undefined; + this.separateCompositeBarDispoables.clear(); + super.removeCompositeBarArea(); } protected createCompositeBar(): PaneCompositeBar { @@ -518,7 +506,6 @@ export abstract class AbstractPaneCompositePart extends CompositePart this.shouldShowFooterCompositeBar(), borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0 }, + { hasTitle: true, borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0 }, SidebarPart.activeViewletSettingsKey, ActiveViewletContext.bindTo(contextKeyService), SidebarFocusContext.bindTo(contextKeyService), @@ -112,9 +112,7 @@ export class SidebarPart extends AbstractPaneCompositePart { } private onDidChangeActivityBarLocation(): void { - this.removeCompositeBar(); - this.updateTitleArea(); - this.updateFooterArea(); + this.updateCompositeBar(); const id = this.getActiveComposite()?.getId(); if (id) { @@ -170,7 +168,7 @@ export class SidebarPart extends AbstractPaneCompositePart { orientation: ActionsOrientation.HORIZONTAL, recomputeSizes: true, activityHoverOptions: { - position: () => this.shouldShowFooterCompositeBar() ? HoverPosition.ABOVE : HoverPosition.BELOW, + position: () => this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.BOTTOM ? HoverPosition.ABOVE : HoverPosition.BELOW, }, fillExtraContextMenuActions: actions => { const viewsSubmenuAction = this.getViewsSubmenuAction(); @@ -201,14 +199,6 @@ export class SidebarPart extends AbstractPaneCompositePart { return activityBarPosition === ActivityBarPosition.TOP || activityBarPosition === ActivityBarPosition.BOTTOM; } - protected shouldShowTitleCompositeBar(): boolean { - return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP; - } - - protected shouldShowFooterCompositeBar(): boolean { - return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.BOTTOM; - } - private shouldShowActivityBar(): boolean { if (this.shouldShowCompositeBar()) { return false; @@ -216,6 +206,17 @@ export class SidebarPart extends AbstractPaneCompositePart { return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) !== ActivityBarPosition.HIDDEN; } + protected getCompositeBarPosition(): CompositeBarPosition { + const activityBarPosition = this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION); + switch (activityBarPosition) { + case ActivityBarPosition.TOP: return CompositeBarPosition.TOP; + case ActivityBarPosition.BOTTOM: return CompositeBarPosition.BOTTOM; + case ActivityBarPosition.HIDDEN: + case ActivityBarPosition.SIDE: + default: return CompositeBarPosition.TITLE; + } + } + private rememberActivityBarVisiblePosition(): void { const activityBarPosition = this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION); if (activityBarPosition !== ActivityBarPosition.HIDDEN) { diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index f575fa95242..2b152911946 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -20,7 +20,7 @@ import * as nls from 'vs/nls'; import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { Action2, IAction2Options, IMenuService, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -39,7 +39,7 @@ import { IAddedViewDescriptorRef, ICustomViewDescriptor, IView, IViewContainerMo import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { FocusedViewContext } from 'vs/workbench/common/contextkeys'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService, LayoutSettings, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; export const ViewsSubMenu = new MenuId('Views'); @@ -47,7 +47,6 @@ MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { submenu: ViewsSubMenu, title: nls.localize('views', "Views"), order: 1, - when: ContextKeyExpr.and(ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar)), ContextKeyExpr.notEquals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP)), }); export interface IViewPaneContainerOptions extends IPaneViewOptions { @@ -1091,11 +1090,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } isViewMergedWithContainer(): boolean { - const location = this.viewDescriptorService.getViewContainerLocation(this.viewContainer); - // Do not merge views in side bar when activity bar is on top because the view title is not shown - if (location === ViewContainerLocation.Sidebar && !this.layoutService.isVisible(Parts.ACTIVITYBAR_PART) && this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP) { - return false; - } if (!(this.options.mergeViewWithContainerWhenSingleView && this.paneItems.length === 1)) { return false; } From ab3fbb5baa23595f47d0c8bd2ef05461116ebd78 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Wed, 6 Mar 2024 12:49:26 +0100 Subject: [PATCH 1784/1897] header / footer --- src/vs/workbench/browser/media/part.css | 8 +- src/vs/workbench/browser/part.ts | 110 +++++++++++++----- .../workbench/browser/parts/compositePart.ts | 2 +- .../browser/parts/media/compositepart.css | 2 +- .../browser/parts/media/paneCompositePart.css | 92 +++++++-------- .../browser/parts/paneCompositePart.ts | 44 ++++--- 6 files changed, 157 insertions(+), 101 deletions(-) diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css index b299dfa5e4a..f2787c2bf3b 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -23,12 +23,12 @@ } .monaco-workbench .part > .title, -.monaco-workbench .part > .composite-bar-area { - display: none; /* Parts have to opt in to show and composite bar area */ +.monaco-workbench .part > .header-or-footer { + display: none; /* Parts have to opt in to show area */ } .monaco-workbench .part > .title, -.monaco-workbench .part > .composite-bar-area { +.monaco-workbench .part > .header-or-footer { height: 35px; display: flex; box-sizing: border-box; @@ -36,7 +36,7 @@ } .monaco-workbench .part > .title, -.monaco-workbench .part > .composite-bar-area { +.monaco-workbench .part > .header-or-footer { padding-left: 8px; padding-right: 8px; } diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index 0141d6c2707..3889641d222 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -20,9 +20,10 @@ export interface IPartOptions { } export interface ILayoutContentResult { + readonly headerSize: IDimension; readonly titleSize: IDimension; readonly contentSize: IDimension; - readonly compositeBarSize: IDimension; + readonly footerSize: IDimension; } /** @@ -41,9 +42,10 @@ export abstract class Part extends Component implements ISerializableView { readonly onDidVisibilityChange = this._onDidVisibilityChange.event; private parent: HTMLElement | undefined; + private headerArea: HTMLElement | undefined; private titleArea: HTMLElement | undefined; private contentArea: HTMLElement | undefined; - private compositeBarArea: HTMLElement | undefined; + private footerArea: HTMLElement | undefined; private partLayout: PartLayout | undefined; constructor( @@ -122,35 +124,67 @@ export abstract class Part extends Component implements ISerializableView { } /** - * Creates the composite bar and sets the proper position. + * Sets the header area */ - protected setCompositeBarArea(compositeBarContainer: HTMLElement, position: 'top' | 'bottom'): void { - if (this.compositeBarArea) { - throw new Error('Composite bar already exists'); + protected setHeaderArea(headerContainer: HTMLElement): void { + if (this.headerArea) { + throw new Error('Header already exists'); } if (!this.parent || !this.titleArea) { return; } - if (position === 'top') { - prepend(this.parent, compositeBarContainer); - compositeBarContainer.classList.add('top'); - } else { - this.parent.appendChild(compositeBarContainer); - compositeBarContainer.classList.add('bottom'); - } + prepend(this.parent, headerContainer); + headerContainer.classList.add('header-or-footer'); + headerContainer.classList.add('header'); - this.compositeBarArea = compositeBarContainer; - this.partLayout?.setCompositeBarVisibility(true); + this.headerArea = headerContainer; + this.partLayout?.setHeaderVisibility(true); this.relayout(); } - protected removeCompositeBarArea(): void { - if (this.compositeBarArea) { - this.compositeBarArea.remove(); - this.compositeBarArea = undefined; - this.partLayout?.setCompositeBarVisibility(false); + /** + * Sets the footer area + */ + protected setFooterArea(footerContainer: HTMLElement): void { + if (this.footerArea) { + throw new Error('Footer already exists'); + } + + if (!this.parent || !this.titleArea) { + return; + } + + this.parent.appendChild(footerContainer); + footerContainer.classList.add('header-or-footer'); + footerContainer.classList.add('footer'); + + this.footerArea = footerContainer; + this.partLayout?.setFooterVisibility(true); + this.relayout(); + } + + /** + * removes the header area + */ + protected removeHeaderArea(): void { + if (this.headerArea) { + this.headerArea.remove(); + this.headerArea = undefined; + this.partLayout?.setHeaderVisibility(false); + this.relayout(); + } + } + + /** + * removes the footer area + */ + protected removeFooterArea(): void { + if (this.footerArea) { + this.footerArea.remove(); + this.footerArea = undefined; + this.partLayout?.setFooterVisibility(false); this.relayout(); } } @@ -197,10 +231,12 @@ export abstract class Part extends Component implements ISerializableView { class PartLayout { + private static readonly HEADER_HEIGHT = 35; private static readonly TITLE_HEIGHT = 35; - private static readonly COMPOSITE_BAR_HEIGHT = 35; + private static readonly Footer_HEIGHT = 35; - private compositeBarAreaVisible: boolean = false; + private headerVisible: boolean = false; + private footerVisible: boolean = false; constructor(private options: IPartOptions, private contentArea: HTMLElement | undefined) { } @@ -214,12 +250,20 @@ class PartLayout { titleSize = Dimension.None; } - // Footer Size: Width (Fill), Height (Variable) - let compositeBarSize: Dimension; - if (this.compositeBarAreaVisible) { - compositeBarSize = new Dimension(width, Math.min(height, PartLayout.COMPOSITE_BAR_HEIGHT)); + // Header Size: Width (Fill), Height (Variable) + let headerSize: Dimension; + if (this.headerVisible) { + headerSize = new Dimension(width, Math.min(height, PartLayout.HEADER_HEIGHT)); } else { - compositeBarSize = Dimension.None; + headerSize = Dimension.None; + } + + // Footer Size: Width (Fill), Height (Variable) + let footerSize: Dimension; + if (this.footerVisible) { + footerSize = new Dimension(width, Math.min(height, PartLayout.Footer_HEIGHT)); + } else { + footerSize = Dimension.None; } let contentWidth = width; @@ -228,18 +272,22 @@ class PartLayout { } // Content Size: Width (Fill), Height (Variable) - const contentSize = new Dimension(contentWidth, height - titleSize.height - compositeBarSize.height); + const contentSize = new Dimension(contentWidth, height - titleSize.height - headerSize.height - footerSize.height); // Content if (this.contentArea) { size(this.contentArea, contentSize.width, contentSize.height); } - return { titleSize, contentSize, compositeBarSize }; + return { headerSize, titleSize, contentSize, footerSize }; } - setCompositeBarVisibility(visible: boolean): void { - this.compositeBarAreaVisible = visible; + setFooterVisibility(visible: boolean): void { + this.footerVisible = visible; + } + + setHeaderVisibility(visible: boolean): void { + this.headerVisible = visible; } } diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 146a58620b1..4adf1f7d162 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -438,7 +438,7 @@ export abstract class CompositePart extends Part { }; } - protected createCompositeBarArea(): HTMLElement { + protected createHeaderFooterCompositeBarArea(): HTMLElement { return $('.composite.composite-bar-area'); } diff --git a/src/vs/workbench/browser/parts/media/compositepart.css b/src/vs/workbench/browser/parts/media/compositepart.css index 4f7afbfab50..98f07d9cf18 100644 --- a/src/vs/workbench/browser/parts/media/compositepart.css +++ b/src/vs/workbench/browser/parts/media/compositepart.css @@ -7,7 +7,7 @@ height: 100%; } -.monaco-workbench .part > .composite.composite-bar-area, +.monaco-workbench .part > .composite.header-or-footer, .monaco-workbench .part > .composite.title { display: flex; } diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index a324f08d4b4..f2b2b178b81 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -20,18 +20,18 @@ display: none; } -.monaco-workbench .pane-composite-part > .composite-bar-area.bottom { +.monaco-workbench .pane-composite-part > .header-or-footer.bottom { border-top: 1px solid var(--vscode-sideBarSectionHeader-border); } .monaco-workbench .pane-composite-part > .title > .composite-bar-container, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container { display: flex; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-label.codicon-more, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-label.codicon-more { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-label.codicon-more { display: flex; align-items: center; justify-content: center; @@ -41,13 +41,13 @@ } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar { line-height: 27px; /* matches panel titles in settings */ height: 35px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item { text-transform: uppercase; padding-left: 10px; padding-right: 10px; @@ -58,26 +58,26 @@ } .monaco-workbench .pane-composite-part > .title > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item.icon, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item.icon { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item.icon { height: 24px; padding: 0 5px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container >.composite-bar .monaco-action-bar .action-label.codicon, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container >.composite-bar .monaco-action-bar .action-label.codicon { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container >.composite-bar .monaco-action-bar .action-label.codicon { font-size: 18px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label:not(.codicon), -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label:not(.codicon) { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label:not(.codicon) { width: 16px; height: 16px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after { content: ''; width: 2px; height: 24px; @@ -92,32 +92,32 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::before, .monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::after, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::before, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::after { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::before, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::after { display: block; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before { left: 1px; margin-left: -2px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after { right: 1px; margin-right: -2px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:first-of-type::before, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:first-of-type::before { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:first-of-type::before { left: 2px; margin-left: -2px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type::after, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type::after { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type::after { right: 2px; margin-right: -2px; } @@ -126,10 +126,10 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::after, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right::after, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right::before, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::after, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right::after { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right::before, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::after, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right::after { transition-delay: 0s; } @@ -138,51 +138,51 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type.right::after, .monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over-head > .composite-bar > .monaco-action-bar .action-item:first-of-type::before, .monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over-tail > .composite-bar > .monaco-action-bar .action-item:last-of-type::after, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right + .action-item::before, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type.right::after, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container.dragged-over-head > .composite-bar > .monaco-action-bar .action-item:first-of-type::before, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container.dragged-over-tail > .composite-bar > .monaco-action-bar .action-item:last-of-type::after { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right + .action-item::before, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type.right::after, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container.dragged-over-head > .composite-bar > .monaco-action-bar .action-item:first-of-type::before, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container.dragged-over-tail > .composite-bar > .monaco-action-bar .action-item:last-of-type::after { opacity: 1; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label { margin-right: 0; padding: 2px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label { border-radius: 0; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.icon) .action-label, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label.codicon, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.icon) .action-label, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label.codicon { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.icon) .action-label, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label.codicon { background: none !important; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label { margin-right: 0; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .badge, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .badge { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .badge { margin-left: 8px; display: flex; align-items: center; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge { margin-left: 0px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .badge .badge-content, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .badge .badge-content { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .badge .badge-content { padding: 3px 5px; border-radius: 11px; font-size: 11px; @@ -197,7 +197,7 @@ } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact { position: absolute; top: 0; bottom: 0; @@ -210,7 +210,7 @@ } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact .badge-content, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact .badge-content { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact .badge-content { position: absolute; top: 11px; right: 0px; @@ -225,7 +225,7 @@ } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact.progress-badge .badge-content::before, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact.progress-badge .badge-content::before { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact.progress-badge .badge-content::before { mask-size: 11px; -webkit-mask-size: 11px; top: 3px; @@ -234,7 +234,7 @@ /* active item indicator */ .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator { position: absolute; z-index: 1; bottom: 0; @@ -244,14 +244,14 @@ } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator { top: -4px; left: 10px; width: calc(100% - 20px); } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .active-item-indicator, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .active-item-indicator { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .active-item-indicator { top: 1px; left: 2px; width: calc(100% - 4px); @@ -259,8 +259,8 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .active-item-indicator:before, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .active-item-indicator:before, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .active-item-indicator:before, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { content: ""; position: absolute; z-index: 1; @@ -272,24 +272,24 @@ } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.clicked:not(.checked):focus .active-item-indicator:before, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.clicked:not(.checked):focus .active-item-indicator:before { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.clicked:not(.checked):focus .active-item-indicator:before { border-top-color: transparent !important; /* hides border on clicked state */ } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { border-top-color: var(--vscode-focusBorder) !important; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label { outline: var(--vscode-contrastActiveBorder, unset) solid 1px !important; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label, -.monaco-workbench .pane-composite-part > .composite-bar-area > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label { +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label { outline: var(--vscode-contrastActiveBorder, unset) dashed 1px !important; } diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index 876e85c3aea..fea45721bc8 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -114,8 +114,8 @@ export abstract class AbstractPaneCompositePart extends CompositePart()); private compositeBarPosition: CompositeBarPosition | undefined = undefined; @@ -353,7 +353,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart { + this.headerFooterCompositeBarDispoables.add(addDisposableListener(compositeBarArea, EventType.CONTEXT_MENU, e => { this.onCompositeBarAreaContextMenu(new StandardMouseEvent(getWindow(compositeBarArea), e)); })); - this.separateCompositeBarDispoables.add(Gesture.addTarget(compositeBarArea)); - this.separateCompositeBarDispoables.add(addDisposableListener(compositeBarArea, GestureEventType.Contextmenu, e => { + this.headerFooterCompositeBarDispoables.add(Gesture.addTarget(compositeBarArea)); + this.headerFooterCompositeBarDispoables.add(addDisposableListener(compositeBarArea, GestureEventType.Contextmenu, e => { this.onCompositeBarAreaContextMenu(new StandardMouseEvent(getWindow(compositeBarArea), e)); })); return compositeBarArea; } - protected override removeCompositeBarArea(): void { - this.separateCompositeBarContainer = undefined; - this.separateCompositeBarDispoables.clear(); - super.removeCompositeBarArea(); + private removeFooterHeaderArea(header: boolean): void { + this.headerFooterCompositeBarContainer = undefined; + this.headerFooterCompositeBarDispoables.clear(); + if (header) { + this.removeHeaderArea(); + } else { + this.removeFooterArea(); + } } protected createCompositeBar(): PaneCompositeBar { From 9626fb0cd29113bc7bbaf7035e0b0cbe96baae59 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 6 Mar 2024 12:51:50 +0100 Subject: [PATCH 1785/1897] Inline chat widget tweaks (#206954) * make status menu configurable https://github.com/microsoft/vscode/issues/206940 * pull inline chat zone widget into its own file * :lipstick: * remove expand/crop feature * make chat message contents scrollable --- .../lib/stylelint/vscode-known-variables.json | 2 - .../browser/inlineChat.contribution.ts | 2 - .../contrib/inlineChat/browser/inlineChat.css | 24 -- .../inlineChat/browser/inlineChatActions.ts | 42 +-- .../browser/inlineChatController.ts | 16 +- .../inlineChat/browser/inlineChatSession.ts | 14 - .../browser/inlineChatStrategies.ts | 2 +- .../inlineChat/browser/inlineChatWidget.ts | 332 ++++-------------- .../browser/inlineChatZoneWidget.ts | 187 ++++++++++ .../contrib/inlineChat/common/inlineChat.ts | 2 - .../controller/chat/notebookChatController.ts | 4 +- 11 files changed, 267 insertions(+), 360 deletions(-) create mode 100644 src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 51d8c95ae17..a965cfcdb9f 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -790,8 +790,6 @@ "--vscode-hover-maxWidth", "--vscode-hover-sourceWhiteSpace", "--vscode-hover-whiteSpace", - "--vscode-inline-chat-cropped", - "--vscode-inline-chat-expanded", "--vscode-inline-chat-quick-voice-height", "--vscode-inline-chat-quick-voice-width", "--vscode-editor-dictation-widget-height", diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index ea70b146a8c..c6a996a178c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -51,8 +51,6 @@ registerAction2(InlineChatActions.FocusInlineChat); registerAction2(InlineChatActions.PreviousFromHistory); registerAction2(InlineChatActions.NextFromHistory); registerAction2(InlineChatActions.ViewInChatAction); -registerAction2(InlineChatActions.ExpandMessageAction); -registerAction2(InlineChatActions.ContractMessageAction); registerAction2(InlineChatActions.ToggleDiffForChange); registerAction2(InlineChatActions.FeebackHelpfulCommand); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css index 7d299bbc1e5..21c2b268b5e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css @@ -175,37 +175,13 @@ cursor: pointer; } -/* .monaco-editor .inline-chat .markdownMessage .message * { - margin: unset; -} - -.monaco-editor .inline-chat .markdownMessage .message code { - font-family: var(--monaco-monospace-font); - font-size: 12px; - color: var(--vscode-textPreformat-foreground); - background-color: var(--vscode-textPreformat-background); - padding: 1px 3px; - border-radius: 4px; -} */ - .monaco-editor .inline-chat .chatMessage .chatMessageContent .value { - -webkit-line-clamp: initial; - -webkit-box-orient: vertical; overflow: hidden; - display: -webkit-box; -webkit-user-select: text; user-select: text; } -.monaco-editor .inline-chat .chatMessage .chatMessageContent[state="cropped"] .value { - -webkit-line-clamp: var(--vscode-inline-chat-cropped, 3); -} - -.monaco-editor .inline-chat .chatMessage .chatMessageContent[state="expanded"] .value { - -webkit-line-clamp: var(--vscode-inline-chat-expanded, 10); -} - .monaco-editor .inline-chat .followUps { padding: 5px 5px; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 75ca16a1f24..7a6b95e0767 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -11,7 +11,7 @@ import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/em import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_EDIT_MODE, EditMode, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, InlineChatResponseFeedbackKind, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, MENU_INLINE_CHAT_WIDGET } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_EDIT_MODE, EditMode, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, InlineChatResponseFeedbackKind, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, MENU_INLINE_CHAT_WIDGET } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { localize, localize2 } from 'vs/nls'; import { Action2, IAction2Options, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -737,46 +737,6 @@ export class ViewInChatAction extends AbstractInlineChatAction { } } -export class ExpandMessageAction extends AbstractInlineChatAction { - constructor() { - super({ - id: 'inlineChat.expandMessageAction', - title: localize('expandMessage', 'Show More'), - icon: Codicon.chevronDown, - precondition: CTX_INLINE_CHAT_VISIBLE, - menu: { - id: MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, - when: ContextKeyExpr.and(ContextKeyExpr.or(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages), CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Mixed)), CTX_INLINE_CHAT_MESSAGE_CROP_STATE.isEqualTo('cropped')), - group: '2_expandOrContract', - order: 1 - } - }); - } - override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): void { - ctrl.updateExpansionState(true); - } -} - -export class ContractMessageAction extends AbstractInlineChatAction { - constructor() { - super({ - id: 'inlineChat.contractMessageAction', - title: localize('contractMessage', 'Show Less'), - icon: Codicon.chevronUp, - precondition: CTX_INLINE_CHAT_VISIBLE, - menu: { - id: MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, - when: ContextKeyExpr.and(ContextKeyExpr.or(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages), CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Mixed)), CTX_INLINE_CHAT_MESSAGE_CROP_STATE.isEqualTo('expanded')), - group: '2_expandOrContract', - order: 1 - } - }); - } - override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): void { - ctrl.updateExpansionState(false); - } -} - export class InlineAccessibilityHelpContribution extends Disposable { constructor() { super(); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index cfb18aa49f0..e2feff8c52f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -39,10 +39,11 @@ import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IInlineChatSavingService } from './inlineChatSavingService'; -import { EmptyResponse, ErrorResponse, ExpansionState, ReplyResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { EmptyResponse, ErrorResponse, ReplyResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { IInlineChatSessionService } from './inlineChatSessionService'; import { EditModeStrategy, IEditObserver, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; -import { IInlineChatMessageAppender, InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; +import { IInlineChatMessageAppender } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; +import { InlineChatZoneWidget } from './inlineChatZoneWidget'; import { CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatProgressItem, IInlineChatRequest, IInlineChatResponse, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { StashedSession } from './inlineChatSession'; @@ -379,7 +380,6 @@ export class InlineChatController implements IEditorContribution { this._zone.value.widget.updateSlashCommands(this._session.session.slashCommands ?? []); this._updatePlaceholder(); this._zone.value.widget.updateInfo(this._session.session.message ?? localize('welcome.1', "AI-generated code may be incorrect")); - this._zone.value.widget.preferredExpansionState = this._session.lastExpansionState; this._zone.value.widget.value = this._session.session.input ?? this._session.lastInput?.value ?? this._zone.value.widget.value; if (this._session.session.input) { this._zone.value.widget.selectAll(); @@ -795,8 +795,6 @@ export class InlineChatController implements IEditorContribution { const message = { message: response.mdContent, providerId: this._session.provider.debugName, requestId: response.requestId }; this._zone.value.widget.updateChatMessage(message); - //this._zone.value.widget.updateMarkdownMessage(response.mdContent); - this._session.lastExpansionState = this._zone.value.widget.expansionState; this._zone.value.widget.updateToolbar(true); newPosition = await this._strategy.renderChanges(response); @@ -1088,14 +1086,6 @@ export class InlineChatController implements IEditorContribution { } } - updateExpansionState(expand: boolean) { - if (this._session) { - const expansionState = expand ? ExpansionState.EXPANDED : ExpansionState.CROPPED; - this._zone.value.widget.updateChatMessageExpansionState(expansionState); - this._session.lastExpansionState = expansionState; - } - } - toggleDiff() { this._strategy?.toggleDiff?.(); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index a3e3865631d..109875de35f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -67,11 +67,6 @@ export type TelemetryDataClassification = { responseTypes: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Comma separated list of response types like edits, message, mixed' }; }; -export enum ExpansionState { - EXPANDED = 'expanded', - CROPPED = 'cropped', - NOT_CROPPED = 'not_cropped' -} export class SessionWholeRange { @@ -142,7 +137,6 @@ export class SessionWholeRange { export class Session { private _lastInput: SessionPrompt | undefined; - private _lastExpansionState: ExpansionState | undefined; private _isUnstashed: boolean = false; private readonly _exchange: SessionExchange[] = []; private readonly _startTime = new Date(); @@ -204,14 +198,6 @@ export class Session { this._isUnstashed = true; } - get lastExpansionState(): ExpansionState | undefined { - return this._lastExpansionState; - } - - set lastExpansionState(state: ExpansionState) { - this._lastExpansionState = state; - } - get textModelNSnapshotAltVersion(): number | undefined { return this._textModelNSnapshotAltVersion; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 30aa3f46b10..500d978c389 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -30,7 +30,7 @@ import { SaveReason } from 'vs/workbench/common/editor'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { InlineChatFileCreatePreviewWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatFileCreationWidget'; import { HunkInformation, ReplyResponse, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; +import { InlineChatZoneWidget } from './inlineChatZoneWidget'; import { CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, InlineChatConfigKeys, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { HunkState } from './inlineChatSession'; import { assertType } from 'vs/base/common/types'; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 6f1cd922882..67cb9f527c0 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -13,7 +13,6 @@ import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { Lazy } from 'vs/base/common/lazy'; import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ISettableObservable, constObservable, derived, observableValue } from 'vs/base/common/observable'; -import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./inlineChat'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; @@ -23,10 +22,10 @@ import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/co import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer'; import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; -import { EditorLayoutInfo, EditorOption, IComputedEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { EditorOption, IComputedEditorOptions } from 'vs/editor/common/config/editorOptions'; import { LineRange } from 'vs/editor/common/core/lineRange'; import { Position } from 'vs/editor/common/core/position'; -import { IRange, Range } from 'vs/editor/common/core/range'; +import { Range } from 'vs/editor/common/core/range'; import { DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; import { LanguageSelector } from 'vs/editor/common/languageSelector'; @@ -37,7 +36,6 @@ import { IModelService } from 'vs/editor/common/services/model'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; -import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; import { localize } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; @@ -63,11 +61,12 @@ import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CodeBlockModelCollection } from 'vs/workbench/contrib/chat/common/codeBlockModelCollection'; -import { ExpansionState, HunkData, HunkInformation, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { HunkData, HunkInformation, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; -import { ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_VISIBLE, IInlineChatFollowup, IInlineChatSlashCommand, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_FEEDBACK, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_RESPONSE_FOCUSED, IInlineChatFollowup, IInlineChatSlashCommand } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -137,9 +136,25 @@ export interface InlineChatWidgetViewState { } export interface IInlineChatWidgetConstructionOptions { - menuId: MenuId; + /** + * The telemetry source for all commands of this widget + */ + telemetrySource: string; + /** + * The menu that is inside the input editor, use for send, dictation + */ + inputMenuId: MenuId; + /** + * The menu that next to the input editor, use for close, config etc + */ widgetMenuId: MenuId; - statusMenuId: MenuId; + /** + * The menu that rendered as button bar, use for accept, discard etc + */ + statusMenuId: MenuId | { menu: MenuId; options: IWorkbenchButtonBarOptions }; + /** + * The men that rendered in the lower right corner, use for feedback + */ feedbackMenuId: MenuId; } @@ -177,10 +192,7 @@ export class InlineChatWidget { h('div.previewDiff.hidden@previewDiff'), h('div.previewCreateTitle.show-file-icons@previewCreateTitle'), h('div.previewCreate.hidden@previewCreate'), - h('div.chatMessage.hidden@chatMessage', [ - h('div.chatMessageContent@chatMessageContent'), - h('div.messageActions@messageActions') - ]), + h('div.chatMessage.hidden@chatMessage'), h('div.followUps.hidden@followUps'), h('div.accessibleViewer@accessibleViewer'), h('div.status@status', [ @@ -192,13 +204,15 @@ export class InlineChatWidget { ] ); + private readonly _chatMessageContents: HTMLDivElement; + private readonly _chatMessageScrollable: DomScrollableElement; + private readonly _store = new DisposableStore(); private readonly _slashCommands = this._store.add(new DisposableStore()); private readonly _inputEditor: IActiveCodeEditor; private readonly _inputModel: ITextModel; private readonly _ctxInputEmpty: IContextKey; - private readonly _ctxMessageCropState: IContextKey<'cropped' | 'not_cropped' | 'expanded'>; private readonly _ctxInnerCursorFirst: IContextKey; private readonly _ctxInnerCursorLast: IContextKey; private readonly _ctxInnerCursorStart: IContextKey; @@ -229,8 +243,6 @@ export class InlineChatWidget { private _lastDim: Dimension | undefined; private _isLayouting: boolean = false; - private _preferredExpansionState: ExpansionState | undefined; - private _expansionState: ExpansionState = ExpansionState.NOT_CROPPED; private _slashCommandDetails: { command: string; detail: string }[] = []; private _slashCommandContentWidget: SlashCommandContentWidget; @@ -244,8 +256,8 @@ export class InlineChatWidget { private readonly _codeBlockModelCollection: CodeBlockModelCollection; constructor( - private readonly parentEditor: ICodeEditor, - _options: IInlineChatWidgetConstructionOptions, + private readonly _parentEditor: ICodeEditor, + options: IInlineChatWidgetConstructionOptions, @IModelService private readonly _modelService: IModelService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @@ -268,20 +280,13 @@ export class InlineChatWidget { ]) }; - this._inputEditor = this._instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.editor, _inputEditorOptions, codeEditorWidgetOptions, this.parentEditor); + this._inputEditor = this._instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.editor, _inputEditorOptions, codeEditorWidgetOptions, this._parentEditor); this._updateAriaLabel(); this._store.add(this._inputEditor); this._store.add(this._inputEditor.onDidChangeModelContent(() => this._onDidChangeInput.fire(this))); this._store.add(this._inputEditor.onDidLayoutChange(() => this._onDidChangeHeight.fire())); this._store.add(this._inputEditor.onDidContentSizeChange(() => this._onDidChangeHeight.fire())); - this._store.add(addDisposableListener(this._elements.chatMessageContent, 'focus', () => this._ctxResponseFocused.set(true))); - this._store.add(addDisposableListener(this._elements.chatMessageContent, 'blur', () => this._ctxResponseFocused.reset())); - this._store.add(this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(AccessibilityVerbositySettingId.InlineChat)) { - this._updateAriaLabel(); - } - })); const uri = URI.from({ scheme: 'vscode', authority: 'inline-chat', path: `/inline-chat/model${InlineChatWidget._modelPool++}.txt` }); this._inputModel = this._store.add(this._modelService.getModel(uri) ?? this._modelService.createModel('', null, uri)); @@ -289,10 +294,24 @@ export class InlineChatWidget { this._editorOptions = this._store.add(_instantiationService.createInstance(ChatEditorOptions, undefined, editorForeground, inputBackground, editorBackground)); + this._chatMessageContents = document.createElement('div'); + this._chatMessageContents.className = 'chatMessageContent'; + this._chatMessageContents.tabIndex = 0; + this._chatMessageContents.ariaLabel = this._accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.InlineChat); + this._chatMessageScrollable = new DomScrollableElement(this._chatMessageContents, { alwaysConsumeMouseWheel: true }); + this._store.add(this._chatMessageScrollable); + this._elements.chatMessage.appendChild(this._chatMessageScrollable.getDomNode()); + this._store.add(addDisposableListener(this._chatMessageContents, 'focus', () => this._ctxResponseFocused.set(true))); + this._store.add(addDisposableListener(this._chatMessageContents, 'blur', () => this._ctxResponseFocused.reset())); + + this._store.add(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(AccessibilityVerbositySettingId.InlineChat)) { + this._updateAriaLabel(); + } + })); // --- context keys - this._ctxMessageCropState = CTX_INLINE_CHAT_MESSAGE_CROP_STATE.bindTo(this._contextKeyService); this._ctxInputEmpty = CTX_INLINE_CHAT_EMPTY.bindTo(this._contextKeyService); this._ctxInnerCursorFirst = CTX_INLINE_CHAT_INNER_CURSOR_FIRST.bindTo(this._contextKeyService); @@ -381,8 +400,8 @@ export class InlineChatWidget { // toolbars - this._store.add(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.editorToolbar, _options.menuId, { - telemetrySource: 'interactiveEditorWidget-toolbar', + this._store.add(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.editorToolbar, options.inputMenuId, { + telemetrySource: options.telemetrySource, toolbarOptions: { primaryGroup: 'main' }, hiddenItemStrategy: HiddenItemStrategy.Ignore, // keep it lean when hiding items and avoid a "..." overflow menu hoverDelegate @@ -392,25 +411,17 @@ export class InlineChatWidget { this._store.add(this._progressBar); - this._store.add(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.widgetToolbar, _options.widgetMenuId, { - telemetrySource: 'interactiveEditorWidget-toolbar', + this._store.add(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.widgetToolbar, options.widgetMenuId, { + telemetrySource: options.telemetrySource, toolbarOptions: { primaryGroup: 'main' }, hoverDelegate })); - const workbenchMenubarOptions: IWorkbenchButtonBarOptions = { - telemetrySource: 'interactiveEditorWidget-toolbar', - buttonConfigProvider: action => { - if (action.id === ACTION_REGENERATE_RESPONSE) { - return { showIcon: true, showLabel: false, isSecondary: true }; - } else if (action.id === ACTION_VIEW_IN_CHAT || action.id === ACTION_ACCEPT_CHANGES) { - return { isSecondary: false }; - } else { - return { isSecondary: true }; - } - } - }; - const statusButtonBar = this._instantiationService.createInstance(MenuWorkbenchButtonBar, this._elements.statusToolbar, _options.statusMenuId, workbenchMenubarOptions); + + const statusMenuId = options.statusMenuId instanceof MenuId ? options.statusMenuId : options.statusMenuId.menu; + const statusMenuOptions = options.statusMenuId instanceof MenuId ? undefined : options.statusMenuId.options; + + const statusButtonBar = this._instantiationService.createInstance(MenuWorkbenchButtonBar, this._elements.statusToolbar, statusMenuId, statusMenuOptions); this._store.add(statusButtonBar.onDidChange(() => this._onDidChangeHeight.fire())); this._store.add(statusButtonBar); @@ -423,7 +434,7 @@ export class InlineChatWidget { } }; - const feedbackToolbar = this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.feedbackToolbar, _options.feedbackMenuId, { ...workbenchToolbarOptions, hiddenItemStrategy: HiddenItemStrategy.Ignore }); + const feedbackToolbar = this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.feedbackToolbar, options.feedbackMenuId, { ...workbenchToolbarOptions, hiddenItemStrategy: HiddenItemStrategy.Ignore }); this._store.add(feedbackToolbar.onDidChangeMenuItems(() => this._onDidChangeHeight.fire())); this._store.add(feedbackToolbar); @@ -432,24 +443,19 @@ export class InlineChatWidget { useInlineViewWhenSpaceIsLimited: false, ..._previewEditorEditorOptions, onlyShowAccessibleDiffViewer: this._accessibilityService.isScreenReaderOptimized(), - }, { modifiedEditor: codeEditorWidgetOptions, originalEditor: codeEditorWidgetOptions }, parentEditor))); + }, { modifiedEditor: codeEditorWidgetOptions, originalEditor: codeEditorWidgetOptions }, _parentEditor))); this._previewCreateTitle = this._store.add(_instantiationService.createInstance(ResourceLabel, this._elements.previewCreateTitle, { supportIcons: true })); - this._previewCreateEditor = new Lazy(() => this._store.add(_instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.previewCreate, _previewEditorEditorOptions, codeEditorWidgetOptions, parentEditor))); + this._previewCreateEditor = new Lazy(() => this._store.add(_instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.previewCreate, _previewEditorEditorOptions, codeEditorWidgetOptions, _parentEditor))); - this._elements.chatMessageContent.tabIndex = 0; - this._elements.chatMessageContent.ariaLabel = this._accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.InlineChat); this._elements.followUps.tabIndex = 0; this._elements.followUps.ariaLabel = this._accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.InlineChat); this._elements.statusLabel.tabIndex = 0; - const markdownMessageToolbar = this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.messageActions, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, workbenchToolbarOptions); - this._store.add(markdownMessageToolbar.onDidChangeMenuItems(() => this._onDidChangeHeight.fire())); - this._store.add(markdownMessageToolbar); this._store.add(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(AccessibilityVerbositySettingId.InlineChat)) { - this._elements.chatMessageContent.ariaLabel = this._accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.InlineChat); + this._chatMessageContents.ariaLabel = this._accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.InlineChat); this._elements.followUps.ariaLabel = this._accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.InlineChat); } })); @@ -475,7 +481,6 @@ export class InlineChatWidget { dispose(): void { this._store.dispose(); this._ctxInputEmpty.reset(); - this._ctxMessageCropState.reset(); } get domNode(): HTMLElement { @@ -488,6 +493,9 @@ export class InlineChatWidget { if (this._accessibleViewer.value) { this._accessibleViewer.value.width = _dim.width - 12; } + this._chatMessageContents.style.width = `${_dim.width - 10}px`; + this._chatMessageContents.style.maxHeight = `270px`; + const widgetToolbarWidth = getTotalWidth(this._elements.widgetToolbar); const editorToolbarWidth = getTotalWidth(this._elements.editorToolbar) + 8 /* L/R-padding */; const innerEditorWidth = _dim.width - editorToolbarWidth - widgetToolbarWidth; @@ -510,12 +518,6 @@ export class InlineChatWidget { this._elements.previewCreate.style.height = `${previewCreateDim.height}px`; } - - const lineHeight = this.parentEditor.getOption(EditorOption.lineHeight); - const editorHeight = this.parentEditor.getLayoutInfo().height; - const editorHeightInLines = Math.floor(editorHeight / lineHeight); - this._elements.root.style.setProperty('--vscode-inline-chat-cropped', String(Math.floor(editorHeightInLines / 5))); - this._elements.root.style.setProperty('--vscode-inline-chat-expanded', String(Math.floor(editorHeightInLines / 3))); this._onDidChangeLayout.fire(); } } finally { @@ -524,16 +526,17 @@ export class InlineChatWidget { } getHeight(): number { - const base = getTotalHeight(this._elements.progress) + getTotalHeight(this._elements.status); const editorHeight = this._inputEditor.getContentHeight() + 12 /* padding and border */; + const progressHeight = getTotalHeight(this._elements.progress); const detectedIntentHeight = getTotalHeight(this._elements.detectedIntent); + const chatResponseHeight = getTotalHeight(this._chatMessageContents) + 16 /*padding*/; const followUpsHeight = getTotalHeight(this._elements.followUps); - const chatResponseHeight = getTotalHeight(this._elements.chatMessage); const previewDiffHeight = this._previewDiffEditor.hasValue && this._previewDiffEditor.value.getModel() ? 12 + Math.min(300, Math.max(0, this._previewDiffEditor.value.getContentHeight())) : 0; const previewCreateTitleHeight = getTotalHeight(this._elements.previewCreateTitle); const previewCreateHeight = this._previewCreateEditor.hasValue && this._previewCreateEditor.value.getModel() ? 18 + Math.min(300, Math.max(0, this._previewCreateEditor.value.getContentHeight())) : 0; const accessibleViewHeight = this._accessibleViewer.value?.height ?? 0; - return base + editorHeight + detectedIntentHeight + followUpsHeight + chatResponseHeight + previewDiffHeight + previewCreateTitleHeight + previewCreateHeight + accessibleViewHeight + 18 /* padding */ + 8 /*shadow*/; + const statusHeight = getTotalHeight(this._elements.status); + return progressHeight + editorHeight + detectedIntentHeight + followUpsHeight + chatResponseHeight + previewDiffHeight + previewCreateTitleHeight + previewCreateHeight + accessibleViewHeight + statusHeight + 18 /* padding */ + 8 /*shadow*/; } updateProgress(show: boolean) { @@ -589,14 +592,6 @@ export class InlineChatWidget { this._onDidChangeHeight.fire(); } - get expansionState(): ExpansionState { - return this._expansionState; - } - - set preferredExpansionState(expansionState: ExpansionState | undefined) { - this._preferredExpansionState = expansionState; - } - get responseContent(): string | undefined { return this._chatMessage?.value; } @@ -604,42 +599,31 @@ export class InlineChatWidget { updateChatMessage(message: IInlineChatMessage, isIncomplete: true): IInlineChatMessageAppender; updateChatMessage(message: IInlineChatMessage | undefined): void; updateChatMessage(message: IInlineChatMessage | undefined, isIncomplete?: boolean): IInlineChatMessageAppender | undefined { - let expansionState: ExpansionState; + this._chatMessageDisposables.clear(); this._chatMessage = message ? new MarkdownString(message.message.value) : undefined; const hasMessage = message?.message.value; this._elements.chatMessage.classList.toggle('hidden', !hasMessage); - reset(this._elements.chatMessageContent); + reset(this._chatMessageContents); let resultingAppender: IInlineChatMessageAppender | undefined; - if (!hasMessage) { - this._ctxMessageCropState.reset(); - expansionState = ExpansionState.NOT_CROPPED; - } else { + if (hasMessage) { const sessionModel = this._chatMessageDisposables.add(new ChatModel(message.providerId, undefined, this._logService, this._chatAgentService, this._instantiationService)); const responseModel = this._chatMessageDisposables.add(new ChatResponseModel(message.message, sessionModel, undefined, undefined, message.requestId, !isIncomplete, false, undefined)); const viewModel = this._chatMessageDisposables.add(new ChatResponseViewModel(responseModel, this._logService)); const renderOptions: IChatListItemRendererOptions = { renderStyle: 'compact', noHeader: true, noPadding: true }; const chatRendererDelegate: IChatRendererDelegate = { getListLength() { return 1; } }; const renderer = this._chatMessageDisposables.add(this._instantiationService.createInstance(ChatListItemRenderer, this._editorOptions, renderOptions, chatRendererDelegate, this._codeBlockModelCollection, undefined)); - renderer.layout(this._elements.chatMessageContent.clientWidth - 4); // 2 for the padding used for the tab index border + renderer.layout(this._chatMessageContents.clientWidth - 4); // 2 for the padding used for the tab index border this._chatMessageDisposables.add(this._onDidChangeLayout.event(() => { - renderer.layout(this._elements.chatMessageContent.clientWidth - 4); + renderer.layout(this._chatMessageContents.clientWidth - 4); + this._chatMessageScrollable.scanDomNode(); })); - const template = renderer.renderTemplate(this._elements.chatMessageContent); + const template = renderer.renderTemplate(this._chatMessageContents); this._chatMessageDisposables.add(template.elementDisposables); this._chatMessageDisposables.add(template.templateDisposables); renderer.renderChatTreeItem(viewModel, 0, template); this._chatMessageDisposables.add(renderer.onDidChangeItemHeight(() => this._onDidChangeHeight.fire())); - if (this._preferredExpansionState) { - expansionState = this._preferredExpansionState; - this._preferredExpansionState = undefined; - } else { - this._updateLineClamp(ExpansionState.CROPPED); - expansionState = template.value.scrollHeight > template.value.clientHeight ? ExpansionState.CROPPED : ExpansionState.NOT_CROPPED; - } - this._ctxMessageCropState.set(expansionState); - this._updateLineClamp(expansionState); resultingAppender = isIncomplete ? { cancel: () => responseModel.cancel(), complete: () => responseModel.complete(), @@ -649,7 +633,6 @@ export class InlineChatWidget { } } : undefined; } - this._expansionState = expansionState; this._onDidChangeHeight.fire(); return resultingAppender; } @@ -667,21 +650,6 @@ export class InlineChatWidget { this._onDidChangeHeight.fire(); } - updateChatMessageExpansionState(expansionState: ExpansionState) { - this._ctxMessageCropState.set(expansionState); - const heightBefore = this._elements.chatMessageContent.scrollHeight; - this._updateLineClamp(expansionState); - const heightAfter = this._elements.chatMessageContent.scrollHeight; - if (heightBefore === heightAfter) { - this._ctxMessageCropState.set(ExpansionState.NOT_CROPPED); - } - this._onDidChangeHeight.fire(); - } - - private _updateLineClamp(expansionState: ExpansionState) { - this._elements.chatMessageContent.setAttribute('state', expansionState); - } - updateSlashCommandUsed(command: string): void { const details = this._slashCommandDetails.find(candidate => candidate.command === command); if (!details) { @@ -948,7 +916,7 @@ export class InlineChatWidget { this._elements.accessibleViewer, session, hunkData, - new AccessibleHunk(this.parentEditor, session, hunkData) + new AccessibleHunk(this._parentEditor, session, hunkData) ); this._onDidChangeHeight.fire(); @@ -956,160 +924,6 @@ export class InlineChatWidget { } } -export class InlineChatZoneWidget extends ZoneWidget { - - readonly widget: InlineChatWidget; - - private readonly _ctxVisible: IContextKey; - private readonly _ctxCursorPosition: IContextKey<'above' | 'below' | ''>; - private _dimension?: Dimension; - private _indentationWidth: number | undefined; - - constructor( - editor: ICodeEditor, - @IInstantiationService private readonly _instaService: IInstantiationService, - @IContextKeyService contextKeyService: IContextKeyService, - ) { - super(editor, { showFrame: false, showArrow: false, isAccessible: true, className: 'inline-chat-widget', keepEditorSelection: true, showInHiddenAreas: true, ordinal: 10000 }); - - this._ctxVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(contextKeyService); - this._ctxCursorPosition = CTX_INLINE_CHAT_OUTER_CURSOR_POSITION.bindTo(contextKeyService); - - this._disposables.add(toDisposable(() => { - this._ctxVisible.reset(); - this._ctxCursorPosition.reset(); - })); - - this.widget = this._instaService.createInstance(InlineChatWidget, this.editor, { - menuId: MENU_INLINE_CHAT_INPUT, - widgetMenuId: MENU_INLINE_CHAT_WIDGET, - statusMenuId: MENU_INLINE_CHAT_WIDGET_STATUS, - feedbackMenuId: MENU_INLINE_CHAT_WIDGET_FEEDBACK - }); - this._disposables.add(this.widget.onDidChangeHeight(() => this._relayout())); - this._disposables.add(this.widget); - this.create(); - - - this._disposables.add(addDisposableListener(this.domNode, 'click', e => { - if (!this.widget.hasFocus()) { - this.widget.focus(); - } - }, true)); - - // todo@jrieken listen ONLY when showing - const updateCursorIsAboveContextKey = () => { - if (!this.position || !this.editor.hasModel()) { - this._ctxCursorPosition.reset(); - } else if (this.position.lineNumber === this.editor.getPosition().lineNumber) { - this._ctxCursorPosition.set('above'); - } else if (this.position.lineNumber + 1 === this.editor.getPosition().lineNumber) { - this._ctxCursorPosition.set('below'); - } else { - this._ctxCursorPosition.reset(); - } - }; - this._disposables.add(this.editor.onDidChangeCursorPosition(e => updateCursorIsAboveContextKey())); - this._disposables.add(this.editor.onDidFocusEditorText(e => updateCursorIsAboveContextKey())); - updateCursorIsAboveContextKey(); - } - - protected override _fillContainer(container: HTMLElement): void { - container.appendChild(this.widget.domNode); - } - - - protected override _doLayout(heightInPixel: number): void { - - const maxWidth = !this.widget.showsAnyPreview() ? 640 : Number.MAX_SAFE_INTEGER; - const width = Math.min(maxWidth, this._availableSpaceGivenIndentation(this._indentationWidth)); - this._dimension = new Dimension(width, heightInPixel); - this.widget.domNode.style.width = `${width}px`; - this.widget.layout(this._dimension); - } - - private _availableSpaceGivenIndentation(indentationWidth: number | undefined): number { - const info = this.editor.getLayoutInfo(); - return info.contentWidth - (info.glyphMarginWidth + info.decorationsWidth + (indentationWidth ?? 0)); - } - - private _computeHeightInLines(): number { - const lineHeight = this.editor.getOption(EditorOption.lineHeight); - return this.widget.getHeight() / lineHeight; - } - - protected override _relayout() { - if (this._dimension) { - this._doLayout(this._dimension.height); - } - super._relayout(this._computeHeightInLines()); - } - - override show(position: Position): void { - super.show(position, this._computeHeightInLines()); - this.widget.focus(); - this._ctxVisible.set(true); - } - - protected override _getWidth(info: EditorLayoutInfo): number { - return info.width - info.minimap.minimapWidth; - } - - updateBackgroundColor(newPosition: Position, wholeRange: IRange) { - assertType(this.container); - const widgetLineNumber = newPosition.lineNumber; - this.container.classList.toggle('inside-selection', widgetLineNumber > wholeRange.startLineNumber && widgetLineNumber < wholeRange.endLineNumber); - } - - private _calculateIndentationWidth(position: Position): number { - const viewModel = this.editor._getViewModel(); - if (!viewModel) { - return 0; - } - const visibleRange = viewModel.getCompletelyVisibleViewRange(); - const startLineVisibleRange = visibleRange.startLineNumber; - const positionLine = position.lineNumber; - let indentationLineNumber: number | undefined; - let indentationLevel: number | undefined; - for (let lineNumber = positionLine; lineNumber >= startLineVisibleRange; lineNumber--) { - const currentIndentationLevel = viewModel.getLineFirstNonWhitespaceColumn(lineNumber); - if (currentIndentationLevel !== 0) { - indentationLineNumber = lineNumber; - indentationLevel = currentIndentationLevel; - break; - } - } - return this.editor.getOffsetForColumn(indentationLineNumber ?? positionLine, indentationLevel ?? viewModel.getLineFirstNonWhitespaceColumn(positionLine)); - } - - setContainerMargins(): void { - assertType(this.container); - - const info = this.editor.getLayoutInfo(); - const marginWithoutIndentation = info.glyphMarginWidth + info.decorationsWidth + info.lineNumbersWidth; - this.container.style.marginLeft = `${marginWithoutIndentation}px`; - } - - setWidgetMargins(position: Position): void { - const indentationWidth = this._calculateIndentationWidth(position); - if (this._indentationWidth === indentationWidth) { - return; - } - this._indentationWidth = this._availableSpaceGivenIndentation(indentationWidth) > 400 ? indentationWidth : 0; - this.widget.domNode.style.marginLeft = `${this._indentationWidth}px`; - this.widget.domNode.style.marginRight = `${this.editor.getLayoutInfo().minimap.minimapWidth}px`; - } - - override hide(): void { - this.container!.classList.remove('inside-selection'); - this._ctxVisible.reset(); - this._ctxCursorPosition.reset(); - this.widget.reset(); - super.hide(); - aria.status(localize('inlineChatClosed', 'Closed inline chat widget')); - } -} - class HunkAccessibleDiffViewer extends AccessibleDiffViewer { readonly height: number; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts new file mode 100644 index 00000000000..f9853b40261 --- /dev/null +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts @@ -0,0 +1,187 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { Dimension, addDisposableListener } from 'vs/base/browser/dom'; +import * as aria from 'vs/base/browser/ui/aria/aria'; +import { toDisposable } from 'vs/base/common/lifecycle'; +import { assertType } from 'vs/base/common/types'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Position } from 'vs/editor/common/core/position'; +import { IRange } from 'vs/editor/common/core/range'; +import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; +import { localize } from 'vs/nls'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_FEEDBACK, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { InlineChatWidget } from './inlineChatWidget'; + + +export class InlineChatZoneWidget extends ZoneWidget { + + readonly widget: InlineChatWidget; + + private readonly _ctxVisible: IContextKey; + private readonly _ctxCursorPosition: IContextKey<'above' | 'below' | ''>; + private _dimension?: Dimension; + private _indentationWidth: number | undefined; + + constructor( + editor: ICodeEditor, + @IInstantiationService private readonly _instaService: IInstantiationService, + @IContextKeyService contextKeyService: IContextKeyService + ) { + super(editor, { showFrame: false, showArrow: false, isAccessible: true, className: 'inline-chat-widget', keepEditorSelection: true, showInHiddenAreas: true, ordinal: 10000 }); + + this._ctxVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(contextKeyService); + this._ctxCursorPosition = CTX_INLINE_CHAT_OUTER_CURSOR_POSITION.bindTo(contextKeyService); + + this._disposables.add(toDisposable(() => { + this._ctxVisible.reset(); + this._ctxCursorPosition.reset(); + })); + + this.widget = this._instaService.createInstance(InlineChatWidget, this.editor, { + telemetrySource: 'interactiveEditorWidget-toolbar', + inputMenuId: MENU_INLINE_CHAT_INPUT, + widgetMenuId: MENU_INLINE_CHAT_WIDGET, + feedbackMenuId: MENU_INLINE_CHAT_WIDGET_FEEDBACK, + statusMenuId: { + menu: MENU_INLINE_CHAT_WIDGET_STATUS, + options: { + buttonConfigProvider: action => { + if (action.id === ACTION_REGENERATE_RESPONSE) { + return { showIcon: true, showLabel: false, isSecondary: true }; + } else if (action.id === ACTION_VIEW_IN_CHAT || action.id === ACTION_ACCEPT_CHANGES) { + return { isSecondary: false }; + } else { + return { isSecondary: true }; + } + } + } + } + }); + this._disposables.add(this.widget.onDidChangeHeight(() => this._relayout())); + this._disposables.add(this.widget); + this.create(); + + + this._disposables.add(addDisposableListener(this.domNode, 'click', e => { + if (!this.widget.hasFocus()) { + this.widget.focus(); + } + }, true)); + + // todo@jrieken listen ONLY when showing + const updateCursorIsAboveContextKey = () => { + if (!this.position || !this.editor.hasModel()) { + this._ctxCursorPosition.reset(); + } else if (this.position.lineNumber === this.editor.getPosition().lineNumber) { + this._ctxCursorPosition.set('above'); + } else if (this.position.lineNumber + 1 === this.editor.getPosition().lineNumber) { + this._ctxCursorPosition.set('below'); + } else { + this._ctxCursorPosition.reset(); + } + }; + this._disposables.add(this.editor.onDidChangeCursorPosition(e => updateCursorIsAboveContextKey())); + this._disposables.add(this.editor.onDidFocusEditorText(e => updateCursorIsAboveContextKey())); + updateCursorIsAboveContextKey(); + } + + protected override _fillContainer(container: HTMLElement): void { + container.appendChild(this.widget.domNode); + } + + + protected override _doLayout(heightInPixel: number): void { + + const maxWidth = !this.widget.showsAnyPreview() ? 640 : Number.MAX_SAFE_INTEGER; + const width = Math.min(maxWidth, this._availableSpaceGivenIndentation(this._indentationWidth)); + this._dimension = new Dimension(width, heightInPixel); + this.widget.domNode.style.width = `${width}px`; + this.widget.layout(this._dimension); + } + + private _availableSpaceGivenIndentation(indentationWidth: number | undefined): number { + const info = this.editor.getLayoutInfo(); + return info.contentWidth - (info.glyphMarginWidth + info.decorationsWidth + (indentationWidth ?? 0)); + } + + private _computeHeightInLines(): number { + const lineHeight = this.editor.getOption(EditorOption.lineHeight); + return this.widget.getHeight() / lineHeight; + } + + protected override _relayout() { + if (this._dimension) { + this._doLayout(this._dimension.height); + } + super._relayout(this._computeHeightInLines()); + } + + override show(position: Position): void { + super.show(position, this._computeHeightInLines()); + this.widget.focus(); + this._ctxVisible.set(true); + } + + protected override _getWidth(info: EditorLayoutInfo): number { + return info.width - info.minimap.minimapWidth; + } + + updateBackgroundColor(newPosition: Position, wholeRange: IRange) { + assertType(this.container); + const widgetLineNumber = newPosition.lineNumber; + this.container.classList.toggle('inside-selection', widgetLineNumber > wholeRange.startLineNumber && widgetLineNumber < wholeRange.endLineNumber); + } + + private _calculateIndentationWidth(position: Position): number { + const viewModel = this.editor._getViewModel(); + if (!viewModel) { + return 0; + } + const visibleRange = viewModel.getCompletelyVisibleViewRange(); + const startLineVisibleRange = visibleRange.startLineNumber; + const positionLine = position.lineNumber; + let indentationLineNumber: number | undefined; + let indentationLevel: number | undefined; + for (let lineNumber = positionLine; lineNumber >= startLineVisibleRange; lineNumber--) { + const currentIndentationLevel = viewModel.getLineFirstNonWhitespaceColumn(lineNumber); + if (currentIndentationLevel !== 0) { + indentationLineNumber = lineNumber; + indentationLevel = currentIndentationLevel; + break; + } + } + return this.editor.getOffsetForColumn(indentationLineNumber ?? positionLine, indentationLevel ?? viewModel.getLineFirstNonWhitespaceColumn(positionLine)); + } + + setContainerMargins(): void { + assertType(this.container); + + const info = this.editor.getLayoutInfo(); + const marginWithoutIndentation = info.glyphMarginWidth + info.decorationsWidth + info.lineNumbersWidth; + this.container.style.marginLeft = `${marginWithoutIndentation}px`; + } + + setWidgetMargins(position: Position): void { + const indentationWidth = this._calculateIndentationWidth(position); + if (this._indentationWidth === indentationWidth) { + return; + } + this._indentationWidth = this._availableSpaceGivenIndentation(indentationWidth) > 400 ? indentationWidth : 0; + this.widget.domNode.style.marginLeft = `${this._indentationWidth}px`; + this.widget.domNode.style.marginRight = `${this.editor.getLayoutInfo().minimap.minimapWidth}px`; + } + + override hide(): void { + this.container!.classList.remove('inside-selection'); + this._ctxVisible.reset(); + this._ctxCursorPosition.reset(); + this.widget.reset(); + super.hide(); + aria.status(localize('inlineChatClosed', 'Closed inline chat widget')); + } +} diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 10e6097adea..d44c8e11d1e 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -156,7 +156,6 @@ export const CTX_INLINE_CHAT_INNER_CURSOR_FIRST = new RawContextKey('in export const CTX_INLINE_CHAT_INNER_CURSOR_LAST = new RawContextKey('inlineChatInnerCursorLast', false, localize('inlineChatInnerCursorLast', "Whether the cursor of the iteractive editor input is on the last line")); export const CTX_INLINE_CHAT_INNER_CURSOR_START = new RawContextKey('inlineChatInnerCursorStart', false, localize('inlineChatInnerCursorStart', "Whether the cursor of the iteractive editor input is on the start of the input")); export const CTX_INLINE_CHAT_INNER_CURSOR_END = new RawContextKey('inlineChatInnerCursorEnd', false, localize('inlineChatInnerCursorEnd', "Whether the cursor of the iteractive editor input is on the end of the input")); -export const CTX_INLINE_CHAT_MESSAGE_CROP_STATE = new RawContextKey<'cropped' | 'not_cropped' | 'expanded'>('inlineChatMarkdownMessageCropState', 'not_cropped', localize('inlineChatMarkdownMessageCropState', "Whether the interactive editor message is cropped, not cropped or expanded")); export const CTX_INLINE_CHAT_OUTER_CURSOR_POSITION = new RawContextKey<'above' | 'below' | ''>('inlineChatOuterCursorPosition', '', localize('inlineChatOuterCursorPosition', "Whether the cursor of the outer editor is above or below the interactive editor input")); export const CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey('inlineChatHasActiveRequest', false, localize('inlineChatHasActiveRequest', "Whether interactive editor has an active request")); export const CTX_INLINE_CHAT_HAS_STASHED_SESSION = new RawContextKey('inlineChatHasStashedSession', false, localize('inlineChatHasStashedSession', "Whether interactive editor has kept a session for quick restore")); @@ -181,7 +180,6 @@ export const ACTION_VIEW_IN_CHAT = 'inlineChat.viewInChat'; export const MENU_INLINE_CHAT_INPUT = MenuId.for('inlineChatInput'); export const MENU_INLINE_CHAT_WIDGET = MenuId.for('inlineChatWidget'); -export const MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE = MenuId.for('inlineChatWidget.markdownMessage'); export const MENU_INLINE_CHAT_WIDGET_STATUS = MenuId.for('inlineChatWidget.status'); export const MENU_INLINE_CHAT_WIDGET_FEEDBACK = MenuId.for('inlineChatWidget.feedback'); export const MENU_INLINE_CHAT_WIDGET_DISCARD = MenuId.for('inlineChatWidget.undo'); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index d4b59189404..d97a8f09aa7 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -292,7 +292,8 @@ export class NotebookChatController extends Disposable implements INotebookEdito InlineChatWidget, fakeParentEditor, { - menuId: MENU_CELL_CHAT_INPUT, + telemetrySource: 'notebook-generate-cell', + inputMenuId: MENU_CELL_CHAT_INPUT, widgetMenuId: MENU_CELL_CHAT_WIDGET, statusMenuId: MENU_CELL_CHAT_WIDGET_STATUS, feedbackMenuId: MENU_CELL_CHAT_WIDGET_FEEDBACK @@ -895,4 +896,3 @@ export class EditStrategy { registerNotebookContribution(NotebookChatController.id, NotebookChatController); - From 6bdee9d1e3f11e53fff148198054dc63a77b55d9 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 6 Mar 2024 13:12:18 +0100 Subject: [PATCH 1786/1897] do not use downloading state (#206955) --- .../contrib/extensions/browser/extensionsWorkbenchService.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index bab4b2cc8c1..903dcac8f25 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -862,7 +862,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (!this.isAutoUpdateEnabled()) { return; } - if ((e.type === StateType.CheckingForUpdates && e.explicit) || e.type === StateType.AvailableForDownload || e.type === StateType.Downloading) { + if ((e.type === StateType.CheckingForUpdates && e.explicit) || e.type === StateType.AvailableForDownload || e.type === StateType.Downloaded) { this.eventuallyCheckForUpdates(true); } })); @@ -1520,7 +1520,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private getProductUpdateVersion(): IProductVersion | undefined { switch (this.updateService.state.type) { case StateType.AvailableForDownload: - case StateType.Downloading: case StateType.Downloaded: case StateType.Updating: case StateType.Ready: From b92a5dd3f04979ab0da420a8ff861be47ba76c93 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 6 Mar 2024 14:01:19 +0100 Subject: [PATCH 1787/1897] relayout when zone widget width changes (#206963) * inline - change the input editor to be a normal editor, not an embedded one https://github.com/microsoft/vscode/issues/206940 * relayout when zone widget width changes --- .../inlineChat/browser/inlineChatActions.ts | 23 +++++++++++++++++-- .../inlineChat/browser/inlineChatWidget.ts | 4 ++-- .../browser/inlineChatZoneWidget.ts | 6 +++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 7a6b95e0767..596d14c9eac 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -5,7 +5,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; @@ -31,6 +31,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { ILogService } from 'vs/platform/log/common/log'; CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); @@ -129,10 +130,28 @@ export abstract class AbstractInlineChatAction extends EditorAction2 { } override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { + const editorService = accessor.get(IEditorService); + const logService = accessor.get(ILogService); + + let ctrl = InlineChatController.get(editor); + if (!ctrl) { + const { activeTextEditorControl } = editorService; + if (isCodeEditor(activeTextEditorControl)) { + editor = activeTextEditorControl; + } else if (isDiffEditor(activeTextEditorControl)) { + editor = activeTextEditorControl.getModifiedEditor(); + } + ctrl = InlineChatController.get(editor); + } + + if (!ctrl) { + logService.warn('[IE] NO controller found for action', this.desc.id, editor.getModel()?.uri); + return; + } + if (editor instanceof EmbeddedCodeEditorWidget) { editor = editor.getParentEditor(); } - const ctrl = InlineChatController.get(editor); if (!ctrl) { for (const diffEditor of accessor.get(ICodeEditorService).listDiffEditors()) { if (diffEditor.getOriginalEditor() === editor || diffEditor.getModifiedEditor() === editor) { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 67cb9f527c0..2b9b26cf68f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -18,7 +18,7 @@ import 'vs/css!./inlineChat'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer'; import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; @@ -280,7 +280,7 @@ export class InlineChatWidget { ]) }; - this._inputEditor = this._instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.editor, _inputEditorOptions, codeEditorWidgetOptions, this._parentEditor); + this._inputEditor = this._instantiationService.createInstance(CodeEditorWidget, this._elements.editor, _inputEditorOptions, codeEditorWidgetOptions); this._updateAriaLabel(); this._store.add(this._inputEditor); this._store.add(this._inputEditor.onDidChangeModelContent(() => this._onDidChangeInput.fire(this))); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts index f9853b40261..a6cdc9e5a29 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts @@ -114,6 +114,12 @@ export class InlineChatZoneWidget extends ZoneWidget { return this.widget.getHeight() / lineHeight; } + protected override _onWidth(_widthInPixel: number): void { + if (this._dimension) { + this._doLayout(this._dimension.height); + } + } + protected override _relayout() { if (this._dimension) { this._doLayout(this._dimension.height); From 639b71bdceaeda0b1b95ec2e0aa826cbba5f5e91 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 6 Mar 2024 14:28:46 +0100 Subject: [PATCH 1788/1897] fix downloading state in darwin (#206960) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix downloading state in darwin * do not show user forced update notification in specific cases --------- Co-authored-by: João Moreno --- src/vs/platform/update/common/update.ts | 4 ++-- .../update/electron-main/updateService.darwin.ts | 6 +++--- .../update/electron-main/updateService.win32.ts | 15 ++++++--------- src/vs/workbench/contrib/update/browser/update.ts | 7 ++++++- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/vs/platform/update/common/update.ts b/src/vs/platform/update/common/update.ts index 02aeac681b5..87c40596916 100644 --- a/src/vs/platform/update/common/update.ts +++ b/src/vs/platform/update/common/update.ts @@ -64,7 +64,7 @@ export type Disabled = { type: StateType.Disabled; reason: DisablementReason }; export type Idle = { type: StateType.Idle; updateType: UpdateType; error?: string }; export type CheckingForUpdates = { type: StateType.CheckingForUpdates; explicit: boolean }; export type AvailableForDownload = { type: StateType.AvailableForDownload; update: IUpdate }; -export type Downloading = { type: StateType.Downloading; update: IUpdate }; +export type Downloading = { type: StateType.Downloading }; export type Downloaded = { type: StateType.Downloaded; update: IUpdate }; export type Updating = { type: StateType.Updating; update: IUpdate }; export type Ready = { type: StateType.Ready; update: IUpdate }; @@ -77,7 +77,7 @@ export const State = { Idle: (updateType: UpdateType, error?: string) => ({ type: StateType.Idle, updateType, error }) as Idle, CheckingForUpdates: (explicit: boolean) => ({ type: StateType.CheckingForUpdates, explicit } as CheckingForUpdates), AvailableForDownload: (update: IUpdate) => ({ type: StateType.AvailableForDownload, update } as AvailableForDownload), - Downloading: (update: IUpdate) => ({ type: StateType.Downloading, update } as Downloading), + Downloading: { type: StateType.Downloading } as Downloading, Downloaded: (update: IUpdate) => ({ type: StateType.Downloaded, update } as Downloaded), Updating: (update: IUpdate) => ({ type: StateType.Updating, update } as Updating), Ready: (update: IUpdate) => ({ type: StateType.Ready, update } as Ready), diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index bdc6e2d6766..27a2fcbeadf 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -24,7 +24,7 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau @memoize private get onRawError(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'error', (_, message) => message); } @memoize private get onRawUpdateNotAvailable(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-not-available'); } - @memoize private get onRawUpdateAvailable(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-available', (_, url, version, timestamp) => ({ url, version, productVersion: version, timestamp })); } + @memoize private get onRawUpdateAvailable(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-available'); } @memoize private get onRawUpdateDownloaded(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-downloaded', (_, releaseNotes, version, timestamp) => ({ version, productVersion: version, timestamp })); } constructor( @@ -96,12 +96,12 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau electron.autoUpdater.checkForUpdates(); } - private onUpdateAvailable(update: IUpdate): void { + private onUpdateAvailable(): void { if (this.state.type !== StateType.CheckingForUpdates) { return; } - this.setState(State.Downloading(update)); + this.setState(State.Downloading); } private onUpdateDownloaded(update: IUpdate): void { diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index ff8bbb0e559..4c49a758185 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -135,7 +135,7 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun return Promise.resolve(null); } - this.setState(State.Downloading(update)); + this.setState(State.Downloading); return this.cleanup(update.version).then(() => { return this.getUpdatePackagePath(update.version).then(updatePackagePath => { @@ -153,15 +153,13 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun .then(() => updatePackagePath); }); }).then(packagePath => { - const fastUpdatesEnabled = this.configurationService.getValue('update.enableWindowsBackgroundUpdates'); - this.availableUpdate = { packagePath }; + this.setState(State.Downloaded(update)); + const fastUpdatesEnabled = this.configurationService.getValue('update.enableWindowsBackgroundUpdates'); if (fastUpdatesEnabled) { if (this.productService.target === 'user') { this.doApplyUpdate(); - } else { - this.setState(State.Downloaded(update)); } } else { this.setState(State.Ready(update)); @@ -209,7 +207,7 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun } protected override async doApplyUpdate(): Promise { - if (this.state.type !== StateType.Downloaded && this.state.type !== StateType.Downloading) { + if (this.state.type !== StateType.Downloaded) { return Promise.resolve(undefined); } @@ -273,14 +271,13 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun const fastUpdatesEnabled = this.configurationService.getValue('update.enableWindowsBackgroundUpdates'); const update: IUpdate = { version: 'unknown', productVersion: 'unknown' }; - this.setState(State.Downloading(update)); + this.setState(State.Downloading); this.availableUpdate = { packagePath }; + this.setState(State.Downloaded(update)); if (fastUpdatesEnabled) { if (this.productService.target === 'user') { this.doApplyUpdate(); - } else { - this.setState(State.Downloaded(update)); } } else { this.setState(State.Ready(update)); diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 969c563d5de..45a8227909b 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -173,6 +173,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu @IContextKeyService private readonly contextKeyService: IContextKeyService, @IProductService private readonly productService: IProductService, @IOpenerService private readonly openerService: IOpenerService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IHostService private readonly hostService: IHostService ) { super(); @@ -316,8 +317,12 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu ); } - // windows fast updates (target === system) + // windows fast updates private onUpdateDownloaded(update: IUpdate): void { + if (this.configurationService.getValue('update.enableWindowsBackgroundUpdates') && this.productService.target === 'user') { + return; + } + if (!this.shouldShowNotification()) { return; } From b4e63addcbdea632775adf095773664b204f6d1d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 6 Mar 2024 14:47:45 +0100 Subject: [PATCH 1789/1897] Reduce memory leaks (fix #195889) (#206950) * leaks - fix group listener leak in open editors * leaks - stop patching `window.focus()` * fix more leaks * fix more leaks --- src/vs/base/browser/dom.ts | 29 --------- src/vs/workbench/browser/part.ts | 5 +- .../browser/parts/editor/editorStatus.ts | 2 +- src/vs/workbench/browser/window.ts | 61 +++++++++++-------- .../actions/voiceChatActions.ts | 2 +- .../emptyTextEditorHint.ts | 6 +- .../files/browser/views/openEditorsView.ts | 7 +-- .../contrib/webview/browser/webviewElement.ts | 9 +-- src/vs/workbench/test/browser/window.test.ts | 1 - 9 files changed, 49 insertions(+), 73 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 907f89d911e..ff113c9baa9 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -921,35 +921,6 @@ export function getActiveWindow(): CodeWindow { return (document.defaultView?.window ?? mainWindow) as CodeWindow; } -/** - * Given an element, will attempt to pass focus() to the window it belongs - * to, depending on the options passed in: - * - force: always focus the element's window - * - otherwise: only focus the element's window if another window in the same - * workspace group has focus (when auxiliary windows are opened). - * - * @param element used to figure out the window the element belongs to - */ -export function focusWindow(element: Node, options?: { force: boolean }): void { - const window = getWindow(element); - - // Force: always focus the element window - if (options?.force) { - window.focus(); - } - - // Not forced: only focus the element window if another - // window in the same workspace group has focus (when auxiliary - // windows are opened). - // This prevents stealing focus from another workspace window. - else { - const activeWindow = getActiveWindow(); - if (activeWindow !== window && activeWindow.document.hasFocus()) { - window.focus(); - } - } -} - const globalStylesheets = new Map>(); export function isGlobalStylesheet(node: Node): boolean { diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index f015fdbdf53..48278fe16b9 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -193,7 +193,10 @@ export abstract class MultiWindowParts extends Compo registerPart(part: T): IDisposable { this._parts.add(part); - return this._register(toDisposable(() => this.unregisterPart(part))); + return this._register(toDisposable(() => { + this.unregisterPart(part); + part = undefined!; // helps to avoid a memory leak with closures where part is captured + })); } protected unregisterPart(part: T): void { diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index d34a58122cd..acf60d208b5 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -366,7 +366,7 @@ class EditorStatus extends Disposable { } private registerCommands(): void { - CommandsRegistry.registerCommand({ id: `changeEditorIndentation${this.targetWindowId}`, handler: () => this.showIndentationPicker() }); + this._register(CommandsRegistry.registerCommand({ id: `changeEditorIndentation${this.targetWindowId}`, handler: () => this.showIndentationPicker() })); } private async showIndentationPicker(): Promise { diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index 203275338eb..121f3528fe4 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { isSafari, setFullscreen } from 'vs/base/browser/browser'; -import { addDisposableListener, EventHelper, EventType, focusWindow, getWindowById, getWindows, getWindowsCount, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom'; +import { addDisposableListener, EventHelper, EventType, getActiveWindow, getWindow, getWindowById, getWindows, getWindowsCount, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { HidDeviceData, requestHidDevice, requestSerialPort, requestUsbDevice, SerialPortData, UsbDeviceData } from 'vs/base/browser/deviceAccess'; import { timeout } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { matchesScheme, Schemas } from 'vs/base/common/network'; -import { isIOS, isMacintosh, isNative } from 'vs/base/common/platform'; +import { isIOS, isMacintosh } from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; @@ -45,11 +45,7 @@ export abstract class BaseWindow extends Disposable { ) { super(); - if (isNative) { - this.enableNativeWindowFocus(targetWindow); - } this.enableWindowFocusOnElementFocus(targetWindow); - this.enableMultiWindowAwareTimeout(targetWindow, dom); this.registerFullScreenListeners(targetWindow.vscodeWindowId); @@ -57,39 +53,50 @@ export abstract class BaseWindow extends Disposable { //#region focus handling in multi-window applications - protected enableNativeWindowFocus(targetWindow: CodeWindow): void { - const originalWindowFocus = targetWindow.focus.bind(targetWindow); + protected enableWindowFocusOnElementFocus(targetWindow: CodeWindow): void { + const originalFocus = targetWindow.HTMLElement.prototype.focus; const that = this; - targetWindow.focus = function () { - originalWindowFocus(); - - if ( - !that.environmentService.extensionTestsLocationURI && // never steal focus when running tests - !targetWindow.document.hasFocus() // skip when already having focus - ) { - // Enable `window.focus()` to work in Electron by - // asking the main process to focus the window. - // https://github.com/electron/electron/issues/25578 - that.hostService.focus(targetWindow); - } - }; - } - - protected enableWindowFocusOnElementFocus(targetWindow: CodeWindow): void { - const originalFocus = HTMLElement.prototype.focus; - targetWindow.HTMLElement.prototype.focus = function (this: HTMLElement, options?: FocusOptions | undefined): void { // Ensure the window the element belongs to is focused // in scenarios where auxiliary windows are present - focusWindow(this); + that.onElementFocus(getWindow(this)); // Pass to original focus() method originalFocus.apply(this, [options]); }; } + private onElementFocus(targetWindow: CodeWindow): void { + const activeWindow = getActiveWindow(); + if (activeWindow !== targetWindow && activeWindow.document.hasFocus()) { + + // Call original focus() + targetWindow.focus(); + + // In Electron, `window.focus()` fails to bring the window + // to the front if multiple windows exist in the same process + // group (floating windows). As such, we ask the host service + // to focus the window which can take care of bringin the + // window to the front. + // + // To minimise disruption by bringing windows to the front + // by accident, we only do this if the window is not already + // focused and the active window is not the target window + // but has focus. This is an indication that multiple windows + // are opened in the same process group while the target window + // is not focused. + + if ( + !this.environmentService.extensionTestsLocationURI && + !targetWindow.document.hasFocus() + ) { + this.hostService.focus(targetWindow); + } + } + } + //#endregion //#region timeout handling in multi-window applications diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index b0f09ffdf75..2a7077c746b 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -980,7 +980,7 @@ class KeywordActivationStatusEntry extends Disposable { ) { super(); - CommandsRegistry.registerCommand(KeywordActivationStatusEntry.STATUS_COMMAND, () => this.commandService.executeCommand('workbench.action.openSettings', KEYWORD_ACTIVIATION_SETTING_ID)); + this._register(CommandsRegistry.registerCommand(KeywordActivationStatusEntry.STATUS_COMMAND, () => this.commandService.executeCommand('workbench.action.openSettings', KEYWORD_ACTIVIATION_SETTING_ID))); this.registerListeners(); this.updateStatusEntry(); diff --git a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts index 10e517dc4cb..ab46e943663 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts @@ -226,7 +226,7 @@ class EmptyTextEditorHintContentWidget implements IContentWidget { id: 'inlineChat.hintAction', from: 'hint' }); - void this.commandService.executeCommand(inlineChatId, { from: 'hint' }); + this.commandService.executeCommand(inlineChatId, { from: 'hint' }); }; const hintHandler: IContentActionHandler = { @@ -254,7 +254,7 @@ class EmptyTextEditorHintContentWidget implements IContentWidget { const hintPart = $('a', undefined, fragment); hintPart.style.fontStyle = 'italic'; hintPart.style.cursor = 'pointer'; - hintPart.onclick = handleClick; + this.toDispose.add(dom.addDisposableListener(hintPart, dom.EventType.CLICK, handleClick)); return hintPart; } else { const hintPart = $('span', undefined, fragment); @@ -272,7 +272,7 @@ class EmptyTextEditorHintContentWidget implements IContentWidget { if (this.options.clickable) { label.element.style.cursor = 'pointer'; - label.element.onclick = handleClick; + this.toDispose.add(dom.addDisposableListener(label.element, dom.EventType.CLICK, handleClick)); } hintElement.appendChild(after); diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index c6fd9a02c0d..ed7dcd5a06a 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -26,7 +26,7 @@ import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListDragAn import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableMap, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { MenuId, Action2, registerAction2, MenuRegistry } from 'vs/platform/actions/common/actions'; import { OpenEditorsDirtyEditorContext, OpenEditorsGroupContext, OpenEditorsReadonlyEditorContext, SAVE_ALL_LABEL, SAVE_ALL_COMMAND_ID, NEW_UNTITLED_FILE_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileConstants'; import { ResourceContextKey, MultipleEditorGroupsContext } from 'vs/workbench/common/contextkeys'; @@ -118,7 +118,7 @@ export class OpenEditorsView extends ViewPane { this.listRefreshScheduler?.schedule(this.structuralRefreshDelay); }; - const groupDisposables = new Map(); + const groupDisposables = this._register(new DisposableMap()); const addGroupListener = (group: IEditorGroup) => { const groupModelChangeListener = group.onDidModelChange(e => { if (this.listRefreshScheduler?.isScheduled()) { @@ -156,7 +156,6 @@ export class OpenEditorsView extends ViewPane { } }); groupDisposables.set(group.id, groupModelChangeListener); - this._register(groupDisposables.get(group.id)!); }; this.editorGroupService.groups.forEach(g => addGroupListener(g)); @@ -167,7 +166,7 @@ export class OpenEditorsView extends ViewPane { this._register(this.editorGroupService.onDidMoveGroup(() => updateWholeList())); this._register(this.editorGroupService.onDidChangeActiveGroup(() => this.focusActiveEditor())); this._register(this.editorGroupService.onDidRemoveGroup(group => { - dispose(groupDisposables.get(group.id)); + groupDisposables.deleteAndDispose(group.id); updateWholeList(); })); } diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index f9baf25d202..3a258267b1f 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isFirefox } from 'vs/base/browser/browser'; -import { addDisposableListener, EventType, focusWindow, getActiveWindow } from 'vs/base/browser/dom'; +import { addDisposableListener, EventType, getActiveWindow } from 'vs/base/browser/dom'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { promiseWithResolvers, ThrottledDelayer } from 'vs/base/common/async'; import { streamToBuffer, VSBufferReadableStream } from 'vs/base/common/buffer'; @@ -803,12 +803,9 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD return; } - // Ensure the window the element belongs to is focused - // in scenarios where auxiliary windows are present - focusWindow(this.element); - try { - this.element.contentWindow?.focus(); + this.element.parentElement?.focus(); // this helps to move floating windows to the front if any... + this.element.contentWindow?.focus(); // ...because `contentWindow` is not able to do so } catch { // noop } diff --git a/src/vs/workbench/test/browser/window.test.ts b/src/vs/workbench/test/browser/window.test.ts index 4a395ee47b3..f65d50c3aea 100644 --- a/src/vs/workbench/test/browser/window.test.ts +++ b/src/vs/workbench/test/browser/window.test.ts @@ -22,7 +22,6 @@ suite('Window', () => { super(window, dom, new TestHostService(), TestEnvironmentService); } - protected override enableNativeWindowFocus(): void { } protected override enableWindowFocusOnElementFocus(): void { } } From 1837fba9dc3ba3c77fb95093cfbd772b941a85e4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Mar 2024 06:42:33 -0800 Subject: [PATCH 1790/1897] Add todo for inline chat context key --- .../contrib/terminalContrib/chat/browser/terminalChatActions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 709b4ac8f24..ff650600398 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -30,6 +30,7 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.and(TerminalContextKeys.processSupported, TerminalContextKeys.focusInAny), + // TODO: This needs to change to check for a terminal location capable agent CTX_INLINE_CHAT_HAS_PROVIDER ), run: (_xterm, _accessor, activeInstance) => { From cdc99038120157bdd0fc30c50ff1f3639ad8a75e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 6 Mar 2024 15:57:01 +0100 Subject: [PATCH 1791/1897] update reload action name (#206971) --- .../workbench/contrib/extensions/browser/extensionsActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 8a4056cdf9b..2f2833049ba 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1598,7 +1598,7 @@ export class ReloadAction extends ExtensionAction { this.enabled = true; this.class = ReloadAction.EnabledClass; this.tooltip = runtimeState.reason; - this.label = runtimeState.action === ExtensionRuntimeActionType.Reload ? localize('reload required', 'Reload {0}', this.productService.nameShort) + this.label = runtimeState.action === ExtensionRuntimeActionType.Reload ? localize('reload window', 'Reload Window') : runtimeState.action === ExtensionRuntimeActionType.QuitAndInstall ? localize('restart product', 'Restart {0}', this.productService.nameShort) : runtimeState.action === ExtensionRuntimeActionType.ApplyUpdate || runtimeState.action === ExtensionRuntimeActionType.DownloadUpdate ? localize('update product', 'Update {0}', this.productService.nameShort) : ''; } From 5fd0172e1c150c7f6c6fcd4737ebbf2e5a139f97 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Mar 2024 07:04:18 -0800 Subject: [PATCH 1792/1897] Fix compile issues --- .../terminalContrib/chat/browser/terminalChatController.ts | 2 +- .../terminalContrib/chat/browser/terminalChatWidget.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index c6f624dd865..e6a95220291 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -183,7 +183,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } private _getPlaceholderText(): string { - return this._forcedPlaceholder ?? this._model?.inputPlaceholder ?? ''; + return this._forcedPlaceholder ?? ''; } setPlaceholder(text: string): void { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 3bf19460298..320199ad859 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -68,10 +68,11 @@ export class TerminalChatWidget extends Disposable { InlineChatWidget, fakeParentEditor, { - menuId: MENU_TERMINAL_CHAT_INPUT, + inputMenuId: MENU_TERMINAL_CHAT_INPUT, widgetMenuId: MENU_TERMINAL_CHAT_WIDGET, statusMenuId: MENU_TERMINAL_CHAT_WIDGET_STATUS, - feedbackMenuId: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK + feedbackMenuId: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, + telemetrySource: 'terminal-inline-chat', } ); this._reset(); From 469b7f85233665dbda0f684bde7fac72a810e62c Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Wed, 6 Mar 2024 16:53:15 +0100 Subject: [PATCH 1793/1897] use footer --- src/vs/workbench/browser/parts/media/paneCompositePart.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index f2b2b178b81..4b9f6670205 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -20,11 +20,10 @@ display: none; } -.monaco-workbench .pane-composite-part > .header-or-footer.bottom { +.monaco-workbench .pane-composite-part > .header-or-footer.footer { border-top: 1px solid var(--vscode-sideBarSectionHeader-border); } - .monaco-workbench .pane-composite-part > .title > .composite-bar-container, .monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container { display: flex; From 904d3707a803375b8abd79eeaffc9f57429f3dfb Mon Sep 17 00:00:00 2001 From: gjsjohnmurray Date: Wed, 6 Mar 2024 15:58:17 +0000 Subject: [PATCH 1794/1897] Refactor for clarity --- .../output/browser/output.contribution.ts | 82 ++++++++++--------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 595c458f2f4..01fdffa1233 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -348,47 +348,21 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { icon: Codicon.gear, order: 6 })); - const registeredLogLevels = new Map(); - this._register(toDisposable(() => dispose(registeredLogLevels.values()))); - const registerLogLevels = (logLevels: LogLevel[]) => { - let order = 0; - for (const logLevel of logLevels) { - const { original, value } = LogLevelToLocalizedString(logLevel); - registeredLogLevels.set(logLevel, registerAction2(class extends Action2 { - constructor() { - super({ - id: `workbench.action.output.activeOutputLogLevel.${logLevel}`, - title: value, - toggled: CONTEXT_ACTIVE_OUTPUT_LEVEL.isEqualTo(original), - menu: { - id: logLevelMenu, - order: order++, - group: '0_level' - } - }); - } - async run(accessor: ServicesAccessor): Promise { - const channel = that.outputService.getActiveChannel(); - if (channel) { - const channelDescriptor = that.outputService.getChannelDescriptor(channel.id); - if (channelDescriptor?.log && channelDescriptor.file) { - return accessor.get(ILoggerService).setLogLevel(channelDescriptor.file, logLevel); - } - } - } - })); - } + + let order = 0; + const registerLogLevel = (logLevel: LogLevel) => { + const { original, value } = LogLevelToLocalizedString(logLevel); this._register(registerAction2(class extends Action2 { constructor() { super({ - id: `workbench.action.output.activeOutputLogLevelDefault`, - title: nls.localize('logLevelDefault.label', "Set As Default"), + id: `workbench.action.output.activeOutputLogLevel.${logLevel}`, + title: value, + toggled: CONTEXT_ACTIVE_OUTPUT_LEVEL.isEqualTo(original), menu: { id: logLevelMenu, - order, - group: '1_default' - }, - precondition: CONTEXT_ACTIVE_OUTPUT_LEVEL_IS_DEFAULT.negate() + order: order++, + group: '0_level' + } }); } async run(accessor: ServicesAccessor): Promise { @@ -396,14 +370,44 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { if (channel) { const channelDescriptor = that.outputService.getChannelDescriptor(channel.id); if (channelDescriptor?.log && channelDescriptor.file) { - const logLevel = accessor.get(ILoggerService).getLogLevel(channelDescriptor.file); - return await accessor.get(IDefaultLogLevelsService).setDefaultLogLevel(logLevel, channelDescriptor.extensionId); + return accessor.get(ILoggerService).setLogLevel(channelDescriptor.file, logLevel); } } } })); }; - registerLogLevels([LogLevel.Trace, LogLevel.Debug, LogLevel.Info, LogLevel.Warning, LogLevel.Error, LogLevel.Off]); + + registerLogLevel(LogLevel.Trace); + registerLogLevel(LogLevel.Debug); + registerLogLevel(LogLevel.Info); + registerLogLevel(LogLevel.Warning); + registerLogLevel(LogLevel.Error); + registerLogLevel(LogLevel.Off); + + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.action.output.activeOutputLogLevelDefault`, + title: nls.localize('logLevelDefault.label', "Set As Default"), + menu: { + id: logLevelMenu, + order, + group: '1_default' + }, + precondition: CONTEXT_ACTIVE_OUTPUT_LEVEL_IS_DEFAULT.negate() + }); + } + async run(accessor: ServicesAccessor): Promise { + const channel = that.outputService.getActiveChannel(); + if (channel) { + const channelDescriptor = that.outputService.getChannelDescriptor(channel.id); + if (channelDescriptor?.log && channelDescriptor.file) { + const logLevel = accessor.get(ILoggerService).getLogLevel(channelDescriptor.file); + return await accessor.get(IDefaultLogLevelsService).setDefaultLogLevel(logLevel, channelDescriptor.extensionId); + } + } + } + })); } private registerShowLogsAction(): void { From 6536c8b0450a00cfc0e0eb361d0bf5dee70e447f Mon Sep 17 00:00:00 2001 From: gjsjohnmurray Date: Wed, 6 Mar 2024 15:59:13 +0000 Subject: [PATCH 1795/1897] Relocate listeners --- .../contrib/output/browser/outputServices.ts | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index 718be38cf42..8c05bd54823 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -125,6 +125,14 @@ export class OutputService extends Disposable implements IOutputService, ITextMo } })); + this._register(this.loggerService.onDidChangeLogLevel(_level => { + this.setLevelContext(); + this.setLevelIsDefaultContext(); + })); + this._register(this.defaultLogLevelsService.onDidChangeDefaultLogLevels(() => { + this.setLevelIsDefaultContext(); + })); + this._register(this.lifecycleService.onDidShutdown(() => this.dispose())); } @@ -204,34 +212,30 @@ export class OutputService extends Disposable implements IOutputService, ITextMo return this.instantiationService.createInstance(OutputChannel, channelData); } + private setLevelContext(): void { + const descriptor = this.activeChannel?.outputChannelDescriptor; + const channelLogLevel = descriptor?.log ? this.loggerService.getLogLevel(descriptor.file) : undefined; + this.activeOutputChannelLevelContext.set(channelLogLevel !== undefined ? LogLevelToString(channelLogLevel) : ''); + } + + private async setLevelIsDefaultContext(): Promise { + const descriptor = this.activeChannel?.outputChannelDescriptor; + if (descriptor?.log) { + const channelLogLevel = this.loggerService.getLogLevel(descriptor.file); + const channelDefaultLogLevel = await this.defaultLogLevelsService.getDefaultLogLevel(descriptor.extensionId); + this.activeOutputChannelLevelIsDefaultContext.set(channelDefaultLogLevel === channelLogLevel); + } else { + this.activeOutputChannelLevelIsDefaultContext.set(false); + } + } + private setActiveChannel(channel: OutputChannel | undefined): void { this.activeChannel = channel; const descriptor = channel?.outputChannelDescriptor; this.activeFileOutputChannelContext.set(!!descriptor?.file); this.activeOutputChannelLevelSettableContext.set(descriptor !== undefined && SetLogLevelAction.isLevelSettable(descriptor)); - const setLevelContext = (): void => { - const channelLogLevel = descriptor?.log ? this.loggerService.getLogLevel(descriptor.file) : undefined; - this.activeOutputChannelLevelContext.set(channelLogLevel !== undefined ? LogLevelToString(channelLogLevel) : ''); - }; - const setLevelIsDefaultContext = async (): Promise => { - const descriptor = this.activeChannel?.outputChannelDescriptor; - if (descriptor?.log) { - const channelLogLevel = this.loggerService.getLogLevel(descriptor.file); - const channelDefaultLogLevel = await this.defaultLogLevelsService.getDefaultLogLevel(descriptor.extensionId); - this.activeOutputChannelLevelIsDefaultContext.set(channelDefaultLogLevel === channelLogLevel); - } else { - this.activeOutputChannelLevelIsDefaultContext.set(false); - } - }; - this._register(this.defaultLogLevelsService.onDidChangeDefaultLogLevels(() => { - setLevelIsDefaultContext(); - })); - setLevelIsDefaultContext(); - this._register(this.loggerService.onDidChangeLogLevel(_level => { - setLevelContext(); - setLevelIsDefaultContext(); - })); - setLevelContext(); + this.setLevelIsDefaultContext(); + this.setLevelContext(); if (this.activeChannel) { this.storageService.store(OUTPUT_ACTIVE_CHANNEL_KEY, this.activeChannel.id, StorageScope.WORKSPACE, StorageTarget.MACHINE); From ee36c8c2dfe2e279c4b58af4d47fdf6fc85bb9f5 Mon Sep 17 00:00:00 2001 From: gjsjohnmurray Date: Wed, 6 Mar 2024 16:21:58 +0000 Subject: [PATCH 1796/1897] Use ILocalizedString correctly --- src/vs/platform/log/common/log.ts | 12 ++++++------ .../contrib/output/browser/output.contribution.ts | 7 +++---- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index b0342da2653..28fc419bbaf 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -749,12 +749,12 @@ export function LogLevelToString(logLevel: LogLevel): string { export function LogLevelToLocalizedString(logLevel: LogLevel): ILocalizedString { switch (logLevel) { - case LogLevel.Trace: return { original: 'trace', value: nls.localize('trace', "Trace") }; - case LogLevel.Debug: return { original: 'debug', value: nls.localize('debug', "Debug") }; - case LogLevel.Info: return { original: 'info', value: nls.localize('info', "Info") }; - case LogLevel.Warning: return { original: 'warn', value: nls.localize('warn', "Warning") }; - case LogLevel.Error: return { original: 'error', value: nls.localize('error', "Error") }; - case LogLevel.Off: return { original: 'off', value: nls.localize('off', "Off") }; + case LogLevel.Trace: return { original: 'Trace', value: nls.localize('trace', "Trace") }; + case LogLevel.Debug: return { original: 'Debug', value: nls.localize('debug', "Debug") }; + case LogLevel.Info: return { original: 'Info', value: nls.localize('info', "Info") }; + case LogLevel.Warning: return { original: 'Warning', value: nls.localize('warn', "Warning") }; + case LogLevel.Error: return { original: 'Error', value: nls.localize('error', "Error") }; + case LogLevel.Off: return { original: 'Off', value: nls.localize('off', "Off") }; } } diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 01fdffa1233..6d3f4d9ad31 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -30,7 +30,7 @@ import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; -import { ILoggerService, LogLevel, LogLevelToLocalizedString } from 'vs/platform/log/common/log'; +import { ILoggerService, LogLevel, LogLevelToLocalizedString, LogLevelToString } from 'vs/platform/log/common/log'; import { IDefaultLogLevelsService } from 'vs/workbench/contrib/logs/common/defaultLogLevels'; // Register Service @@ -351,13 +351,12 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { let order = 0; const registerLogLevel = (logLevel: LogLevel) => { - const { original, value } = LogLevelToLocalizedString(logLevel); this._register(registerAction2(class extends Action2 { constructor() { super({ id: `workbench.action.output.activeOutputLogLevel.${logLevel}`, - title: value, - toggled: CONTEXT_ACTIVE_OUTPUT_LEVEL.isEqualTo(original), + title: LogLevelToLocalizedString(logLevel).value, + toggled: CONTEXT_ACTIVE_OUTPUT_LEVEL.isEqualTo(LogLevelToString(logLevel)), menu: { id: logLevelMenu, order: order++, From 550954c442dd76f6bf81a731d6953ee46d34fcb7 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Wed, 6 Mar 2024 17:57:45 +0100 Subject: [PATCH 1797/1897] max width --- src/vs/workbench/browser/parts/media/paneCompositePart.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index 4b9f6670205..9e6e83c9645 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -29,6 +29,10 @@ display: flex; } +.monaco-workbench .pane-composite-part > .header-or-footer .composite-bar-container { + width: 100%; +} + .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-label.codicon-more, .monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-label.codicon-more { display: flex; From 182fd057426da528736789d69e48bb01ac07ae8d Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Wed, 6 Mar 2024 18:09:53 +0100 Subject: [PATCH 1798/1897] Settings description --- src/vs/workbench/browser/workbench.contribution.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index fc4af190dca..f7e95d8343a 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -499,12 +499,12 @@ const registry = Registry.as(ConfigurationExtensions.Con 'type': 'string', 'enum': ['side', 'top', 'bottom', 'hidden'], 'default': 'side', - 'markdownDescription': localize({ comment: ['This is the description for a setting'], key: 'activityBarLocation' }, "Controls the location of the Activity Bar. It can either show to the `side` or `top` of the Primary Side Bar or `hidden`."), + 'markdownDescription': localize({ comment: ['This is the description for a setting'], key: 'activityBarLocation' }, "Controls the location of the Activity Bar. It can either show to the `side` or `top` / `bottom` of the Primary and Secondary Side Bar or `hidden`."), 'enumDescriptions': [ - localize('workbench.activityBar.location.side', "Show the Activity Bar to the side of the Primary Side Bar."), - localize('workbench.activityBar.location.top', "Show the Activity Bar on top of the Primary Side Bar."), - localize('workbench.activityBar.location.bottom', "Show the Activity Bar at the bottom of the Primary Side Bar."), - localize('workbench.activityBar.location.hide', "Hide the Activity Bar.") + localize('workbench.activityBar.location.side', "Show the Activity Bar of the Primary Side Bar on the side."), + localize('workbench.activityBar.location.top', "Show the Activity Bar on top of the Primary and Secondary Side Bar."), + localize('workbench.activityBar.location.bottom', "Show the Activity Bar at the bottom of the Primary and Secondary Side Bar."), + localize('workbench.activityBar.location.hide', "Hide the Activity Bar in the Primary and Secondary Side Bar.") ], }, 'workbench.activityBar.iconClickBehavior': { From b37060a43a78ee4122a56c93ca63bf47be9a7ab6 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 6 Mar 2024 18:28:31 +0100 Subject: [PATCH 1799/1897] Have `InlineChatWidget` and `EditorBasedInlineChatWidget` so that the former can be reuse outside of an editor context (#206986) https://github.com/microsoft/vscode/issues/206940 --- .../inlineChat/browser/inlineChatWidget.ts | 319 ++++++++++-------- .../browser/inlineChatZoneWidget.ts | 6 +- .../controller/chat/notebookChatController.ts | 8 +- .../chat/browser/terminalChatWidget.ts | 13 - 4 files changed, 187 insertions(+), 159 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 2b9b26cf68f..25d8dbb6cfa 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -116,6 +116,14 @@ export const _inputEditorOptions: IEditorConstructionOptions = { lineHeight: 20 }; +const _codeEditorWidgetOptions: ICodeEditorWidgetOptions = { + isSimpleWidget: true, + contributions: EditorExtensionsRegistry.getSomeEditorContributions([ + SnippetController2.ID, + SuggestController.ID + ]) +}; + const _previewEditorEditorOptions: IDiffEditorConstructionOptions = { scrollbar: { useShadows: false, alwaysConsumeMouseWheel: false, ignoreHorizontalScrollbarInContentHeight: true, }, renderMarginRevertIcon: false, @@ -174,7 +182,7 @@ export class InlineChatWidget { private static _modelPool: number = 1; - private readonly _elements = h( + protected readonly _elements = h( 'div.inline-chat@root', [ h('div.body', [ @@ -207,7 +215,7 @@ export class InlineChatWidget { private readonly _chatMessageContents: HTMLDivElement; private readonly _chatMessageScrollable: DomScrollableElement; - private readonly _store = new DisposableStore(); + protected readonly _store = new DisposableStore(); private readonly _slashCommands = this._store.add(new DisposableStore()); private readonly _inputEditor: IActiveCodeEditor; @@ -222,16 +230,8 @@ export class InlineChatWidget { private readonly _progressBar: ProgressBar; - private readonly _previewDiffEditor: Lazy; - private readonly _previewDiffModel = this._store.add(new MutableDisposable()); - private readonly _accessibleViewer = this._store.add(new MutableDisposable()); - - private readonly _previewCreateTitle: ResourceLabel; - private readonly _previewCreateEditor: Lazy; - private readonly _previewCreateDispoable = this._store.add(new MutableDisposable()); - - private readonly _onDidChangeHeight = this._store.add(new MicrotaskEmitter()); + protected readonly _onDidChangeHeight = this._store.add(new MicrotaskEmitter()); readonly onDidChangeHeight: Event = Event.filter(this._onDidChangeHeight.event, _ => !this._isLayouting); private readonly _onDidChangeLayout = this._store.add(new MicrotaskEmitter()); @@ -256,31 +256,22 @@ export class InlineChatWidget { private readonly _codeBlockModelCollection: CodeBlockModelCollection; constructor( - private readonly _parentEditor: ICodeEditor, options: IInlineChatWidgetConstructionOptions, + @IInstantiationService protected readonly _instantiationService: IInstantiationService, @IModelService private readonly _modelService: IModelService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @IKeybindingService private readonly _keybindingService: IKeybindingService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService, @ILogService private readonly _logService: ILogService, - @ITextModelService private readonly _textModelResolverService: ITextModelService, + @ITextModelService protected readonly _textModelResolverService: ITextModelService, @IChatAgentService private readonly _chatAgentService: IChatAgentService, ) { // input editor logic - const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { - isSimpleWidget: true, - contributions: EditorExtensionsRegistry.getSomeEditorContributions([ - SnippetController2.ID, - SuggestController.ID - ]) - }; - - this._inputEditor = this._instantiationService.createInstance(CodeEditorWidget, this._elements.editor, _inputEditorOptions, codeEditorWidgetOptions); + this._inputEditor = this._instantiationService.createInstance(CodeEditorWidget, this._elements.editor, _inputEditorOptions, _codeEditorWidgetOptions); this._updateAriaLabel(); this._store.add(this._inputEditor); this._store.add(this._inputEditor.onDidChangeModelContent(() => this._onDidChangeInput.fire(this))); @@ -438,15 +429,6 @@ export class InlineChatWidget { this._store.add(feedbackToolbar.onDidChangeMenuItems(() => this._onDidChangeHeight.fire())); this._store.add(feedbackToolbar); - // preview editors - this._previewDiffEditor = new Lazy(() => this._store.add(_instantiationService.createInstance(EmbeddedDiffEditorWidget, this._elements.previewDiff, { - useInlineViewWhenSpaceIsLimited: false, - ..._previewEditorEditorOptions, - onlyShowAccessibleDiffViewer: this._accessibilityService.isScreenReaderOptimized(), - }, { modifiedEditor: codeEditorWidgetOptions, originalEditor: codeEditorWidgetOptions }, _parentEditor))); - - this._previewCreateTitle = this._store.add(_instantiationService.createInstance(ResourceLabel, this._elements.previewCreateTitle, { supportIcons: true })); - this._previewCreateEditor = new Lazy(() => this._store.add(_instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.previewCreate, _previewEditorEditorOptions, codeEditorWidgetOptions, _parentEditor))); this._elements.followUps.tabIndex = 0; this._elements.followUps.ariaLabel = this._accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.InlineChat); @@ -487,36 +469,16 @@ export class InlineChatWidget { return this._elements.root; } - layout(_dim: Dimension) { + layout(widgetDim: Dimension) { this._isLayouting = true; try { - if (this._accessibleViewer.value) { - this._accessibleViewer.value.width = _dim.width - 12; - } - this._chatMessageContents.style.width = `${_dim.width - 10}px`; - this._chatMessageContents.style.maxHeight = `270px`; - const widgetToolbarWidth = getTotalWidth(this._elements.widgetToolbar); const editorToolbarWidth = getTotalWidth(this._elements.editorToolbar) + 8 /* L/R-padding */; - const innerEditorWidth = _dim.width - editorToolbarWidth - widgetToolbarWidth; - const dim = new Dimension(innerEditorWidth, _dim.height); - if (!this._lastDim || !Dimension.equals(this._lastDim, dim)) { - this._lastDim = dim; - this._inputEditor.layout(new Dimension(innerEditorWidth, this._inputEditor.getContentHeight())); - this._elements.placeholder.style.width = `${innerEditorWidth /* input-padding*/}px`; - - if (this._previewDiffEditor.hasValue) { - const previewDiffDim = new Dimension(_dim.width - 12, Math.min(300, Math.max(0, this._previewDiffEditor.value.getContentHeight()))); - this._elements.previewDiff.style.width = `${previewDiffDim.width}px`; - this._elements.previewDiff.style.height = `${previewDiffDim.height}px`; - this._previewDiffEditor.value.layout(previewDiffDim); - } - - if (this._previewCreateEditor.hasValue) { - const previewCreateDim = new Dimension(dim.width, Math.min(300, Math.max(0, this._previewCreateEditor.value.getContentHeight()))); - this._previewCreateEditor.value.layout(previewCreateDim); - this._elements.previewCreate.style.height = `${previewCreateDim.height}px`; - } + const innerEditorWidth = widgetDim.width - editorToolbarWidth - widgetToolbarWidth; + const inputDim = new Dimension(innerEditorWidth, widgetDim.height); + if (!this._lastDim || !Dimension.equals(this._lastDim, inputDim)) { + this._lastDim = inputDim; + this._doLayout(widgetDim, inputDim); this._onDidChangeLayout.fire(); } @@ -525,18 +487,22 @@ export class InlineChatWidget { } } + protected _doLayout(widgetDimension: Dimension, inputDimension: Dimension): void { + this._chatMessageContents.style.width = `${widgetDimension.width - 10}px`; + this._chatMessageContents.style.maxHeight = `270px`; + this._inputEditor.layout(new Dimension(inputDimension.width, this._inputEditor.getContentHeight())); + this._elements.placeholder.style.width = `${inputDimension.width}px`; + } + getHeight(): number { const editorHeight = this._inputEditor.getContentHeight() + 12 /* padding and border */; const progressHeight = getTotalHeight(this._elements.progress); const detectedIntentHeight = getTotalHeight(this._elements.detectedIntent); const chatResponseHeight = getTotalHeight(this._chatMessageContents) + 16 /*padding*/; const followUpsHeight = getTotalHeight(this._elements.followUps); - const previewDiffHeight = this._previewDiffEditor.hasValue && this._previewDiffEditor.value.getModel() ? 12 + Math.min(300, Math.max(0, this._previewDiffEditor.value.getContentHeight())) : 0; - const previewCreateTitleHeight = getTotalHeight(this._elements.previewCreateTitle); - const previewCreateHeight = this._previewCreateEditor.hasValue && this._previewCreateEditor.value.getModel() ? 18 + Math.min(300, Math.max(0, this._previewCreateEditor.value.getContentHeight())) : 0; - const accessibleViewHeight = this._accessibleViewer.value?.height ?? 0; + const statusHeight = getTotalHeight(this._elements.status); - return progressHeight + editorHeight + detectedIntentHeight + followUpsHeight + chatResponseHeight + previewDiffHeight + previewCreateTitleHeight + previewCreateHeight + accessibleViewHeight + statusHeight + 18 /* padding */ + 8 /*shadow*/; + return progressHeight + editorHeight + detectedIntentHeight + followUpsHeight + chatResponseHeight + statusHeight + 18 /* padding */ + 8 /*shadow*/; } updateProgress(show: boolean) { @@ -724,10 +690,7 @@ export class InlineChatWidget { this._elements.statusToolbar.classList.add('hidden'); this._elements.feedbackToolbar.classList.add('hidden'); this.updateInfo(''); - this.hideCreatePreview(); - this.hideEditsPreview(); - this._accessibleViewer.clear(); this._elements.accessibleViewer.classList.toggle('hidden', true); this._onDidChangeHeight.fire(); @@ -741,79 +704,6 @@ export class InlineChatWidget { return this.domNode.contains(getActiveElement()); } - // --- preview - - showEditsPreview(hunks: HunkData, textModel0: ITextModel, textModelN: ITextModel) { - - if (hunks.size === 0) { - this.hideEditsPreview(); - return; - } - - this._elements.previewDiff.classList.remove('hidden'); - - this._previewDiffEditor.value.setModel({ original: textModel0, modified: textModelN }); - - // joined ranges - let originalLineRange: LineRange | undefined; - let modifiedLineRange: LineRange | undefined; - for (const item of hunks.getInfo()) { - const [first0] = item.getRanges0(); - const [firstN] = item.getRangesN(); - - originalLineRange = !originalLineRange ? LineRange.fromRangeInclusive(first0) : originalLineRange.join(LineRange.fromRangeInclusive(first0)); - modifiedLineRange = !modifiedLineRange ? LineRange.fromRangeInclusive(firstN) : modifiedLineRange.join(LineRange.fromRangeInclusive(firstN)); - } - - if (!originalLineRange || !modifiedLineRange) { - this.hideEditsPreview(); - return; - } - - const hiddenOriginal = invertLineRange(originalLineRange, textModel0); - const hiddenModified = invertLineRange(modifiedLineRange, textModelN); - this._previewDiffEditor.value.getOriginalEditor().setHiddenAreas(hiddenOriginal.map(lr => asRange(lr, textModel0)), 'diff-hidden'); - this._previewDiffEditor.value.getModifiedEditor().setHiddenAreas(hiddenModified.map(lr => asRange(lr, textModelN)), 'diff-hidden'); - this._previewDiffEditor.value.revealLine(modifiedLineRange.startLineNumber, ScrollType.Immediate); - - this._onDidChangeHeight.fire(); - } - - hideEditsPreview() { - this._elements.previewDiff.classList.add('hidden'); - if (this._previewDiffEditor.hasValue) { - this._previewDiffEditor.value.setModel(null); - } - this._previewDiffModel.clear(); - this._onDidChangeHeight.fire(); - } - - async showCreatePreview(model: IUntitledTextEditorModel): Promise { - this._elements.previewCreateTitle.classList.remove('hidden'); - this._elements.previewCreate.classList.remove('hidden'); - - const ref = await this._textModelResolverService.createModelReference(model.resource); - this._previewCreateDispoable.value = ref; - this._previewCreateTitle.element.setFile(model.resource, { fileKind: FileKind.FILE }); - - this._previewCreateEditor.value.setModel(ref.object.textEditorModel); - this._onDidChangeHeight.fire(); - } - - hideCreatePreview() { - this._elements.previewCreateTitle.classList.add('hidden'); - this._elements.previewCreate.classList.add('hidden'); - this._previewCreateEditor.rawValue?.setModel(null); - this._previewCreateDispoable.clear(); - this._previewCreateTitle.element.clear(); - this._onDidChangeHeight.fire(); - } - - showsAnyPreview() { - return !this._elements.previewDiff.classList.contains('hidden') || - !this._elements.previewCreate.classList.contains('hidden'); - } - // --- slash commands updateSlashCommands(commands: IInlineChatSlashCommand[]) { @@ -903,7 +793,85 @@ export class InlineChatWidget { this._slashCommands.add(this._inputEditor.onDidChangeModelContent(updateSlashDecorations)); updateSlashDecorations(); } +} +export class EditorBasedInlineChatWidget extends InlineChatWidget { + + private readonly _accessibleViewer = this._store.add(new MutableDisposable()); + + private readonly _previewDiffEditor: Lazy; + private readonly _previewDiffModel = this._store.add(new MutableDisposable()); + + private readonly _previewCreateTitle: ResourceLabel; + private readonly _previewCreateEditor: Lazy; + private readonly _previewCreateDispoable = this._store.add(new MutableDisposable()); + + constructor( + private readonly _parentEditor: ICodeEditor, + options: IInlineChatWidgetConstructionOptions, + @IModelService modelService: IModelService, + @IContextKeyService contextKeyService: IContextKeyService, + @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + @IKeybindingService keybindingService: IKeybindingService, + @IInstantiationService instantiationService: IInstantiationService, + @IAccessibilityService accessibilityService: IAccessibilityService, + @IConfigurationService configurationService: IConfigurationService, + @IAccessibleViewService accessibleViewService: IAccessibleViewService, + @ILogService logService: ILogService, + @ITextModelService textModelResolverService: ITextModelService, + @IChatAgentService chatAgentService: IChatAgentService, + ) { + super(options, instantiationService, modelService, contextKeyService, languageFeaturesService, keybindingService, accessibilityService, configurationService, accessibleViewService, logService, textModelResolverService, chatAgentService,); + + // preview editors + this._previewDiffEditor = new Lazy(() => this._store.add(instantiationService.createInstance(EmbeddedDiffEditorWidget, this._elements.previewDiff, { + useInlineViewWhenSpaceIsLimited: false, + ..._previewEditorEditorOptions, + onlyShowAccessibleDiffViewer: accessibilityService.isScreenReaderOptimized(), + }, { modifiedEditor: _codeEditorWidgetOptions, originalEditor: _codeEditorWidgetOptions }, _parentEditor))); + + this._previewCreateTitle = this._store.add(instantiationService.createInstance(ResourceLabel, this._elements.previewCreateTitle, { supportIcons: true })); + this._previewCreateEditor = new Lazy(() => this._store.add(instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.previewCreate, _previewEditorEditorOptions, _codeEditorWidgetOptions, _parentEditor))); + } + + // --- layout + + override getHeight(): number { + const result = super.getHeight(); + const previewDiffHeight = this._previewDiffEditor.hasValue && this._previewDiffEditor.value.getModel() ? 12 + Math.min(300, Math.max(0, this._previewDiffEditor.value.getContentHeight())) : 0; + const previewCreateTitleHeight = getTotalHeight(this._elements.previewCreateTitle); + const previewCreateHeight = this._previewCreateEditor.hasValue && this._previewCreateEditor.value.getModel() ? 18 + Math.min(300, Math.max(0, this._previewCreateEditor.value.getContentHeight())) : 0; + const accessibleViewHeight = this._accessibleViewer.value?.height ?? 0; + return result + previewDiffHeight + previewCreateTitleHeight + previewCreateHeight + accessibleViewHeight; + } + + protected override _doLayout(widgetDimension: Dimension, inputDimension: Dimension): void { + super._doLayout(widgetDimension, inputDimension); + + if (this._accessibleViewer.value) { + this._accessibleViewer.value.width = widgetDimension.width - 12; + } + + if (this._previewDiffEditor.hasValue) { + const previewDiffDim = new Dimension(widgetDimension.width - 12, Math.min(300, Math.max(0, this._previewDiffEditor.value.getContentHeight()))); + this._elements.previewDiff.style.width = `${previewDiffDim.width}px`; + this._elements.previewDiff.style.height = `${previewDiffDim.height}px`; + this._previewDiffEditor.value.layout(previewDiffDim); + } + + if (this._previewCreateEditor.hasValue) { + const previewCreateDim = new Dimension(inputDimension.width, Math.min(300, Math.max(0, this._previewCreateEditor.value.getContentHeight()))); + this._previewCreateEditor.value.layout(previewCreateDim); + this._elements.previewCreate.style.height = `${previewCreateDim.height}px`; + } + } + + override reset() { + this.hideCreatePreview(); + this.hideEditsPreview(); + this._accessibleViewer.clear(); + super.reset(); + } // --- accessible viewer @@ -922,6 +890,79 @@ export class InlineChatWidget { this._onDidChangeHeight.fire(); } + + // --- preview + + showEditsPreview(hunks: HunkData, textModel0: ITextModel, textModelN: ITextModel) { + + if (hunks.size === 0) { + this.hideEditsPreview(); + return; + } + + this._elements.previewDiff.classList.remove('hidden'); + + this._previewDiffEditor.value.setModel({ original: textModel0, modified: textModelN }); + + // joined ranges + let originalLineRange: LineRange | undefined; + let modifiedLineRange: LineRange | undefined; + for (const item of hunks.getInfo()) { + const [first0] = item.getRanges0(); + const [firstN] = item.getRangesN(); + + originalLineRange = !originalLineRange ? LineRange.fromRangeInclusive(first0) : originalLineRange.join(LineRange.fromRangeInclusive(first0)); + modifiedLineRange = !modifiedLineRange ? LineRange.fromRangeInclusive(firstN) : modifiedLineRange.join(LineRange.fromRangeInclusive(firstN)); + } + + if (!originalLineRange || !modifiedLineRange) { + this.hideEditsPreview(); + return; + } + + const hiddenOriginal = invertLineRange(originalLineRange, textModel0); + const hiddenModified = invertLineRange(modifiedLineRange, textModelN); + this._previewDiffEditor.value.getOriginalEditor().setHiddenAreas(hiddenOriginal.map(lr => asRange(lr, textModel0)), 'diff-hidden'); + this._previewDiffEditor.value.getModifiedEditor().setHiddenAreas(hiddenModified.map(lr => asRange(lr, textModelN)), 'diff-hidden'); + this._previewDiffEditor.value.revealLine(modifiedLineRange.startLineNumber, ScrollType.Immediate); + + this._onDidChangeHeight.fire(); + } + + hideEditsPreview() { + this._elements.previewDiff.classList.add('hidden'); + if (this._previewDiffEditor.hasValue) { + this._previewDiffEditor.value.setModel(null); + } + this._previewDiffModel.clear(); + this._onDidChangeHeight.fire(); + } + + async showCreatePreview(model: IUntitledTextEditorModel): Promise { + this._elements.previewCreateTitle.classList.remove('hidden'); + this._elements.previewCreate.classList.remove('hidden'); + + const ref = await this._textModelResolverService.createModelReference(model.resource); + this._previewCreateDispoable.value = ref; + this._previewCreateTitle.element.setFile(model.resource, { fileKind: FileKind.FILE }); + + this._previewCreateEditor.value.setModel(ref.object.textEditorModel); + this._onDidChangeHeight.fire(); + } + + hideCreatePreview() { + this._elements.previewCreateTitle.classList.add('hidden'); + this._elements.previewCreate.classList.add('hidden'); + this._previewCreateEditor.rawValue?.setModel(null); + this._previewCreateDispoable.clear(); + this._previewCreateTitle.element.clear(); + this._onDidChangeHeight.fire(); + } + + showsAnyPreview() { + return !this._elements.previewDiff.classList.contains('hidden') || + !this._elements.previewCreate.classList.contains('hidden'); + } } class HunkAccessibleDiffViewer extends AccessibleDiffViewer { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts index a6cdc9e5a29..6a23b3de343 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts @@ -15,12 +15,12 @@ import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_FEEDBACK, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { InlineChatWidget } from './inlineChatWidget'; +import { EditorBasedInlineChatWidget } from './inlineChatWidget'; export class InlineChatZoneWidget extends ZoneWidget { - readonly widget: InlineChatWidget; + readonly widget: EditorBasedInlineChatWidget; private readonly _ctxVisible: IContextKey; private readonly _ctxCursorPosition: IContextKey<'above' | 'below' | ''>; @@ -42,7 +42,7 @@ export class InlineChatZoneWidget extends ZoneWidget { this._ctxCursorPosition.reset(); })); - this.widget = this._instaService.createInstance(InlineChatWidget, this.editor, { + this.widget = this._instaService.createInstance(EditorBasedInlineChatWidget, this.editor, { telemetrySource: 'interactiveEditorWidget-toolbar', inputMenuId: MENU_INLINE_CHAT_INPUT, widgetMenuId: MENU_INLINE_CHAT_WIDGET, diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index d97a8f09aa7..66e1969d4f3 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -35,7 +35,7 @@ import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browse import { EmptyResponse, ErrorResponse, ReplyResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; import { ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; -import { IInlineChatMessageAppender, InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; +import { EditorBasedInlineChatWidget, IInlineChatMessageAppender } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; @@ -79,7 +79,7 @@ class NotebookChatWidget extends Disposable implements INotebookViewZone { readonly notebookViewZone: INotebookViewZone, readonly domNode: HTMLElement, readonly widgetContainer: HTMLElement, - readonly inlineChatWidget: InlineChatWidget, + readonly inlineChatWidget: EditorBasedInlineChatWidget, readonly parentEditor: CodeEditorWidget, private readonly _languageService: ILanguageService, ) { @@ -145,7 +145,7 @@ class NotebookChatWidget extends Disposable implements INotebookViewZone { } } - private _layoutWidget(inlineChatWidget: InlineChatWidget, widgetContainer: HTMLElement) { + private _layoutWidget(inlineChatWidget: EditorBasedInlineChatWidget, widgetContainer: HTMLElement) { const layoutConfiguration = this._notebookEditor.notebookOptions.getLayoutConfiguration(); const rightMargin = layoutConfiguration.cellRightMargin; const leftMargin = this._notebookEditor.notebookOptions.getCellEditorContainerLeftMargin(); @@ -289,7 +289,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito fakeParentEditor.setModel(result); const inlineChatWidget = this._widgetDisposableStore.add(this._instantiationService.createInstance( - InlineChatWidget, + EditorBasedInlineChatWidget, fakeParentEditor, { telemetrySource: 'notebook-generate-cell', diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 320199ad859..85523a67b57 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -52,21 +52,8 @@ export class TerminalChatWidget extends Disposable { this._container.classList.add('terminal-inline-chat'); terminalElement.appendChild(this._container); - // The inline chat widget requires a parent editor that it bases the diff view on, since the - // terminal doesn't use that feature we can just pass in an unattached editor instance. - const fakeParentEditorElement = document.createElement('div'); - const fakeParentEditor = this._instantiationService.createInstance( - CodeEditorWidget, - fakeParentEditorElement, - { - extraEditorClassName: 'ignore-panel-bg' - }, - { isSimpleWidget: true } - ); - this._inlineChatWidget = this._instantiationService.createInstance( InlineChatWidget, - fakeParentEditor, { inputMenuId: MENU_TERMINAL_CHAT_INPUT, widgetMenuId: MENU_TERMINAL_CHAT_WIDGET, From cd6bd0a01ab2a918e4219c0f51345a7515b3a84c Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 6 Mar 2024 10:02:46 -0800 Subject: [PATCH 1800/1897] Bug fix: check account id not session id (#206990) This should be checking the account id not the session id... otherwise the user will get a modal every time they go through the login flow. --- extensions/github-authentication/src/github.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/github-authentication/src/github.ts b/extensions/github-authentication/src/github.ts index 6c9b1f20294..3d73bfb7656 100644 --- a/extensions/github-authentication/src/github.ts +++ b/extensions/github-authentication/src/github.ts @@ -304,11 +304,11 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid const session = await this.tokenToSession(token, scopes); this.afterSessionLoad(session); - if (sessions.some(s => s.id !== session.id)) { + if (sessions.some(s => s.account.id !== session.account.id)) { const otherAccountsIndexes = new Array(); const otherAccountsLabels = new Set(); for (let i = 0; i < sessions.length; i++) { - if (sessions[i].id !== session.id) { + if (sessions[i].account.id !== session.account.id) { otherAccountsIndexes.push(i); otherAccountsLabels.add(sessions[i].account.label); } From ead483888530303c5914f7078bcf0ed6ce17cf0d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Mar 2024 10:30:28 -0800 Subject: [PATCH 1801/1897] Run SI env changes even if it gets disabled Fixes #205133 --- .../terminal/browser/media/shellIntegration-rc.zsh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh index cc2cb83e0d2..d54b124e69a 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh @@ -32,12 +32,6 @@ if [[ "$VSCODE_INJECTION" == "1" ]]; then fi fi -# Shell integration was disabled by the shell, exit without warning assuming either the shell has -# explicitly disabled shell integration as it's incompatible or it implements the protocol. -if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then - builtin return -fi - # Apply EnvironmentVariableCollections if needed if [ -n "${VSCODE_ENV_REPLACE:-}" ]; then IFS=':' read -rA ADDR <<< "$VSCODE_ENV_REPLACE" @@ -64,6 +58,12 @@ if [ -n "${VSCODE_ENV_APPEND:-}" ]; then unset VSCODE_ENV_APPEND fi +# Shell integration was disabled by the shell, exit without warning assuming either the shell has +# explicitly disabled shell integration as it's incompatible or it implements the protocol. +if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then + builtin return +fi + # The property (P) and command (E) codes embed values which require escaping. # Backslashes are doubled. Non-alphanumeric characters are converted to escaped hex. __vsc_escape_value() { From 828d3c030ec37dcfbe6668be80cb7243735e25b1 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 6 Mar 2024 10:48:18 -0800 Subject: [PATCH 1802/1897] don't use copy action --- .../contrib/chat/browser/actions/chatCodeblockActions.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 0db49de0bf8..266c30bfe98 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -95,11 +95,7 @@ export function registerChatCodeBlockActions() { } run(accessor: ServicesAccessor, ...args: any[]) { - const accessibleViewService = accessor.get(IAccessibleViewService); - let context = args[0]; - if (!context) { - context = accessibleViewService.getCodeBlockContext(); - } + const context = args[0]; if (!isCodeBlockActionContext(context) || isResponseFiltered(context)) { return; } From 7bbdb593f4b3f8d0efeccf5c195531b8510a70c0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 6 Mar 2024 10:50:37 -0800 Subject: [PATCH 1803/1897] only calculate for chat provider --- .../workbench/contrib/accessibility/browser/accessibleView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 294cc709daf..e07c6a84c1b 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -362,7 +362,7 @@ export class AccessibleView extends Disposable { } calculateCodeBlocks(markdown: string): void { - if (!this._currentProvider) { + if (!this._currentProvider || this._currentProvider.options.id !== AccessibleViewProviderId.Chat) { return; } if (this._currentProvider.options.language && this._currentProvider.options.language !== 'markdown') { From 14faed3b68c7a2739bf7172397d40101634fb3d0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 6 Mar 2024 11:13:14 -0800 Subject: [PATCH 1804/1897] add to accessibiliity help dialog --- .../accessibility/browser/accessibleView.ts | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index e07c6a84c1b..b8411ddadfe 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -492,11 +492,8 @@ export class AccessibleView extends Disposable { } private _render(provider: IAccessibleContentProvider, container: HTMLElement, showAccessibleViewHelp?: boolean): IDisposable { - if (!showAccessibleViewHelp) { - // don't overwrite the current provider - this._currentProvider = provider; - this._accessibleViewCurrentProviderId.set(provider.id); - } + this._currentProvider = provider; + this._accessibleViewCurrentProviderId.set(provider.id); const value = this._configurationService.getValue(provider.verbositySettingKey); const readMoreLink = provider.options.readMoreUrl ? localize("openDoc", "\n\nOpen a browser window with more information related to accessibility (H).") : ''; let disableHelpHint = ''; @@ -684,6 +681,7 @@ export class AccessibleView extends Disposable { const navigationHint = this._getNavigationHint(); const goToSymbolHint = this._getGoToSymbolHint(providerHasSymbols); const toolbarHint = localize('toolbar', "Navigate to the toolbar (Shift+Tab))."); + const chatHints = this._getChatHints(); let hint = localize('intro', "In the accessible view, you can:\n"); if (navigationHint) { @@ -695,6 +693,37 @@ export class AccessibleView extends Disposable { if (toolbarHint) { hint += ' - ' + toolbarHint + '\n'; } + if (chatHints) { + hint += chatHints; + } + return hint; + } + + private _getChatHints(): string | undefined { + if (this._currentProvider?.id !== AccessibleViewProviderId.Chat) { + return; + } + let hint = ''; + const insertAtCursorKb = this._keybindingService.lookupKeybinding('workbench.action.chat.insertCodeBlock')?.getAriaLabel(); + const insertIntoNewFileKb = this._keybindingService.lookupKeybinding('workbench.action.chat.insertIntoNewFile')?.getAriaLabel(); + const runInTerminalKb = this._keybindingService.lookupKeybinding('workbench.action.chat.runInTerminal')?.getAriaLabel(); + + if (insertAtCursorKb) { + hint += localize('insertAtCursor', " - Insert the code block at the cursor ({0}).\n", insertAtCursorKb); + } else { + hint += localize('insertAtCursorNoKb', " - Insert the code block at the cursor by configuring a keybinding for the Chat: Insert Code Block command.\n"); + } + if (insertIntoNewFileKb) { + hint += localize('insertIntoNewFile', " - Insert the code block into a new file ({0}).\n", insertIntoNewFileKb); + } else { + hint += localize('insertIntoNewFileNoKb', " - Insert the code block into a new file by configuring a keybinding for the Chat: Insert at Cursor command.\n"); + } + if (runInTerminalKb) { + hint += localize('runInTerminal', " - Run the code block in the terminal ({0}).\n", runInTerminalKb); + } else { + hint += localize('runInTerminalNoKb', " - Run the coe block in the terminal by configuring a keybinding for the Chat: Insert into Terminal command.\n"); + } + return hint; } From a381c88fd3f3b5b2d57e52c2d0198a3c8b6e3ab7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 6 Mar 2024 20:26:43 +0100 Subject: [PATCH 1805/1897] speech - fix telemetry props (#206993) --- .../notifications/notificationsCommands.ts | 2 +- .../notifications/notificationsTelemetry.ts | 2 +- .../contrib/speech/browser/speechService.ts | 20 +++++++++---------- .../browser/auxiliaryWindowService.ts | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index 6f205b9d81f..84507d095dc 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -354,7 +354,7 @@ type NotificationActionMetricsClassification = { id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the action that was run from a notification.' }; actionLabel: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The label of the action that was run from a notification.' }; source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source of the notification where an action was run.' }; - silent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the notification where an action was run is silent or not.' }; + silent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether the notification where an action was run is silent or not.' }; owner: 'bpasero'; comment: 'Tracks when actions are fired from notifcations and how they were fired.'; }; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsTelemetry.ts b/src/vs/workbench/browser/parts/notifications/notificationsTelemetry.ts index 97d1d6a18c8..1a149ef7173 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsTelemetry.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsTelemetry.ts @@ -17,7 +17,7 @@ export interface NotificationMetrics { export type NotificationMetricsClassification = { id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the source of the notification.' }; - silent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the notification is silent or not.' }; + silent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether the notification is silent or not.' }; source?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source of the notification.' }; owner: 'bpasero'; comment: 'Helps us gain insights to what notifications are being shown, how many, and if they are silent or not.'; diff --git a/src/vs/workbench/contrib/speech/browser/speechService.ts b/src/vs/workbench/contrib/speech/browser/speechService.ts index fb511647f02..36186b1458e 100644 --- a/src/vs/workbench/contrib/speech/browser/speechService.ts +++ b/src/vs/workbench/contrib/speech/browser/speechService.ts @@ -99,19 +99,19 @@ export class SpeechService extends Disposable implements ISpeechService { type SpeechToTextSessionClassification = { owner: 'bpasero'; comment: 'An event that fires when a speech to text session is created'; - context: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Context of the session.' }; - duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Duration of the session.' }; - recognized: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'If speech was recognized.' }; + context: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Context of the session.' }; + sessionDuration: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Duration of the session.' }; + sessionRecognized: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'If speech was recognized.' }; }; type SpeechToTextSessionEvent = { context: string; - duration: number; - recognized: boolean; + sessionDuration: number; + sessionRecognized: boolean; }; this.telemetryService.publicLog2('speechToTextSession', { context, - duration: Date.now() - sessionStart, - recognized: sessionRecognized + sessionDuration: Date.now() - sessionStart, + sessionRecognized }); } @@ -204,13 +204,13 @@ export class SpeechService extends Disposable implements ISpeechService { type KeywordRecognitionClassification = { owner: 'bpasero'; comment: 'An event that fires when a speech keyword detection is started'; - recognized: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'If the keyword was recognized.' }; + keywordRecognized: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'If the keyword was recognized.' }; }; type KeywordRecognitionEvent = { - recognized: boolean; + keywordRecognized: boolean; }; this.telemetryService.publicLog2('keywordRecognition', { - recognized: status === KeywordRecognitionStatus.Recognized + keywordRecognized: status === KeywordRecognitionStatus.Recognized }); return status; diff --git a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts index 6864585e014..21d43f66568 100644 --- a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts @@ -229,7 +229,7 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili type AuxiliaryWindowClassification = { owner: 'bpasero'; comment: 'An event that fires when an auxiliary window is opened'; - bounds: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Has window bounds provided.' }; + bounds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Has window bounds provided.' }; }; type AuxiliaryWindowOpenEvent = { bounds: boolean; From 586b9561d8afef737c436726e5a428d841c506f7 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 6 Mar 2024 17:07:53 -0300 Subject: [PATCH 1806/1897] Show dynamically registered participants in suggest widget (#206989) * Show dynamically registered participants in suggest widget * Fix --- .../browser/chatContributionServiceImpl.ts | 2 +- .../browser/contrib/chatInputEditorContrib.ts | 6 ++--- .../contrib/chat/common/chatAgents.ts | 24 +++++++++++++++---- .../contrib/chat/common/chatModel.ts | 2 +- .../contrib/chat/common/chatRequestParser.ts | 2 +- .../test/common/chatRequestParser.test.ts | 16 ++++++------- .../chat/test/common/voiceChat.test.ts | 3 ++- .../chat/browser/terminalChatController.ts | 5 ++-- 8 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts index 3e70653b2f0..fca2fe47519 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts @@ -88,7 +88,7 @@ const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.regi additionalProperties: false, type: 'object', defaultSnippets: [{ body: { name: '', description: '' } }], - required: ['name', 'description'], + required: ['name'], properties: { name: { description: localize('chatCommand', "A short name by which this command is referred to in the UI, e.g. `fix` or * `explain` for commands that fix an issue or explain code. The name should be unique among the commands provided by this participant."), diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 0bc244b4cb4..473f1286eef 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -344,7 +344,7 @@ class AgentCompletions extends Disposable { return null; } - const agents = this.chatAgentService.getRegisteredAgents() + const agents = this.chatAgentService.getAgents() .filter(a => !a.isDefault); return { suggestions: agents.map((c, i) => { @@ -427,7 +427,7 @@ class AgentCompletions extends Disposable { return null; } - const agents = this.chatAgentService.getRegisteredAgents(); + const agents = this.chatAgentService.getAgents(); const justAgents: CompletionItem[] = agents .filter(a => !a.isDefault) .map(agent => { @@ -452,7 +452,7 @@ class AgentCompletions extends Disposable { filterText: `${chatSubcommandLeader}${agent.id}${c.name}`, commitCharacters: [' '], insertText: `${agentLabel} ${withSlash} `, - detail: `(${agentLabel}) ${c.description}`, + detail: `(${agentLabel}) ${c.description ?? ''}`, range: new Range(1, 1, 1, 1), kind: CompletionItemKind.Text, // The icons are disabled here anyway sortText: `${chatSubcommandLeader}${agent.id}${c.name}`, diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 46854de6a5e..04b2e454a99 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { distinct } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; @@ -129,9 +130,10 @@ export interface IChatAgentService { registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable; invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; + getAgents(): IChatAgentData[]; getRegisteredAgents(): Array; getActivatedAgents(): Array; - getRegisteredAgent(id: string): IChatAgentData | undefined; + getAgent(id: string): IChatAgentData | undefined; getDefaultAgent(): IChatAgent | undefined; getSecondaryAgent(): IChatAgentData | undefined; updateAgent(id: string, updateMetadata: IChatAgentMetadata): void; @@ -166,7 +168,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService { throw new Error(`Already registered an agent with id ${name}`); } - const data = this.getRegisteredAgent(name); + const data = this.getAgent(name); if (!data) { throw new Error(`Unknown agent: ${name}`); } @@ -227,14 +229,28 @@ export class ChatAgentService extends Disposable implements IChatAgentService { } satisfies IChatAgentData)); } + /** + * Returns all agent datas that exist- static registered and dynamic ones. + */ + getAgents(): IChatAgentData[] { + const registeredAgents = this.getRegisteredAgents(); + const dynamicAgents = Array.from(this._agents.values()).map(a => a.data); + const all = [ + ...registeredAgents, + ...dynamicAgents + ]; + + return distinct(all, a => a.id); + } + getActivatedAgents(): IChatAgent[] { return Array.from(this._agents.values()) .filter(a => !!a.impl) .map(a => new MergedChatAgent(a.data, a.impl!)); } - getRegisteredAgent(id: string): IChatAgentData | undefined { - return this.getRegisteredAgents().find(a => a.id === id); + getAgent(id: string): IChatAgentData | undefined { + return this.getAgents().find(a => a.id === id); } async invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index a2c05d68ca0..2e0bab18e3d 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -693,7 +693,7 @@ export class ChatModel extends Disposable implements IChatModel { } else if (progress.kind === 'usedContext' || progress.kind === 'reference') { request.response.applyReference(progress); } else if (progress.kind === 'agentDetection') { - const agent = this.chatAgentService.getRegisteredAgent(progress.agentName); + const agent = this.chatAgentService.getAgent(progress.agentName); if (agent) { request.response.setAgent(agent, progress.command); } diff --git a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts index ca2057b6342..52c683f60a4 100644 --- a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts +++ b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts @@ -99,7 +99,7 @@ export class ChatRequestParser { const varRange = new OffsetRange(offset, offset + full.length); const varEditorRange = new Range(position.lineNumber, position.column, position.lineNumber, position.column + full.length); - const agent = this.agentService.getRegisteredAgent(name); + const agent = this.agentService.getAgent(name); if (!agent) { return; } diff --git a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index e82c0f84940..b1d4231aeb2 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -117,7 +117,7 @@ suite('ChatRequestParser', () => { test('agent with subcommand after text', async () => { const agentsService = mockObject()({}); - agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -127,7 +127,7 @@ suite('ChatRequestParser', () => { test('agents, subCommand', async () => { const agentsService = mockObject()({}); - agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -137,7 +137,7 @@ suite('ChatRequestParser', () => { test('agent with question mark', async () => { const agentsService = mockObject()({}); - agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -147,7 +147,7 @@ suite('ChatRequestParser', () => { test('agent and subcommand with leading whitespace', async () => { const agentsService = mockObject()({}); - agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -157,7 +157,7 @@ suite('ChatRequestParser', () => { test('agent and subcommand after newline', async () => { const agentsService = mockObject()({}); - agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -167,7 +167,7 @@ suite('ChatRequestParser', () => { test('agent not first', async () => { const agentsService = mockObject()({}); - agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -177,7 +177,7 @@ suite('ChatRequestParser', () => { test('agents and variables and multiline', async () => { const agentsService = mockObject()({}); - agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); varService.hasVariable.returns(true); @@ -189,7 +189,7 @@ suite('ChatRequestParser', () => { test('agents and variables and multiline, part2', async () => { const agentsService = mockObject()({}); - agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); varService.hasVariable.returns(true); diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index dba9fb7a613..391561501ef 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -53,7 +53,8 @@ suite('VoiceChat', () => { getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error(); } getRegisteredAgents(): Array { return agents; } getActivatedAgents(): IChatAgent[] { return agents; } - getRegisteredAgent(id: string): IChatAgent | undefined { throw new Error(); } + getAgents(): IChatAgent[] { return agents; } + getAgent(id: string): IChatAgent | undefined { throw new Error(); } getDefaultAgent(): IChatAgent | undefined { throw new Error(); } getSecondaryAgent(): IChatAgent | undefined { throw new Error(); } updateAgent(id: string, updateMetadata: IChatAgentMetadata): void { throw new Error(); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index e6a95220291..94a66a2bdd2 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -99,9 +99,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._responseSupportsIssueReportingContextKey = TerminalChatContextKeys.responseSupportsIssueReporting.bindTo(this._contextKeyService); this._sessionResponseVoteContextKey = TerminalChatContextKeys.sessionResponseVote.bindTo(this._contextKeyService); - if (!this._chatAgentService.getRegisteredAgent(this._terminalAgentId)) { + if (!this._chatAgentService.getAgent(this._terminalAgentId)) { this._register(this._chatAgentService.onDidChangeAgents(() => { - if (this._chatAgentService.getRegisteredAgent(this._terminalAgentId)) { + if (this._chatAgentService.getAgent(this._terminalAgentId)) { this._terminalAgentRegisteredContextKey.set(true); } })); @@ -370,4 +370,3 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.dispose(); } } - From 25f1b18fbcec633b68b02f8ec648811c49b2c1ab Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 6 Mar 2024 22:02:10 +0100 Subject: [PATCH 1807/1897] fix #204573 (#207008) --- .../browser/webExtensionsScannerService.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts index 9e0eab4f88f..17c6ac2cb10 100644 --- a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts @@ -375,9 +375,13 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten const result = new Map(); const extensionInfos: IExtensionInfo[] = []; await Promise.all(extensionGalleryResources.map(async extensionGalleryResource => { - const webExtension = await this.toWebExtensionFromExtensionGalleryResource(extensionGalleryResource); - result.set(webExtension.identifier.id.toLowerCase(), webExtension); - extensionInfos.push({ id: webExtension.identifier.id, version: webExtension.version }); + try { + const webExtension = await this.toWebExtensionFromExtensionGalleryResource(extensionGalleryResource); + result.set(webExtension.identifier.id.toLowerCase(), webExtension); + extensionInfos.push({ id: webExtension.identifier.id, version: webExtension.version }); + } catch (error) { + this.logService.info(`Ignoring additional builtin extension from gallery resource ${extensionGalleryResource.toString()} because there is an error while converting it into web extension`, getErrorMessage(error)); + } })); const galleryExtensions = await this.galleryService.getExtensions(extensionInfos, CancellationToken.None); for (const galleryExtension of galleryExtensions) { From c36f459a6c36f82c8fe6c7bee5e3bc26154464eb Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 6 Mar 2024 13:41:49 -0800 Subject: [PATCH 1808/1897] fix #202876 --- .../browser/accessibility.contribution.ts | 2 + .../browser/accessibilityConfiguration.ts | 7 ++- .../browser/openDiffEditorAnnouncement.ts | 56 +++++++++++++++++++ .../codeEditor/browser/diffEditorHelper.ts | 3 + 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/vs/workbench/contrib/accessibility/browser/openDiffEditorAnnouncement.ts diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index faa741438ed..945637c5dfb 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -15,6 +15,7 @@ import { AccessibilityStatus } from 'vs/workbench/contrib/accessibility/browser/ import { EditorAccessibilityHelpContribution } from 'vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp'; import { SaveAccessibilitySignalContribution } from 'vs/workbench/contrib/accessibility/browser/saveAccessibilitySignal'; import { CommentsAccessibilityHelpContribution } from 'vs/workbench/contrib/comments/browser/commentsAccessibility'; +import { DiffEditorActiveAnnouncementContribution } from 'vs/workbench/contrib/accessibility/browser/openDiffEditorAnnouncement'; registerAccessibilityConfiguration(); registerSingleton(IAccessibleViewService, AccessibleViewService, InstantiationType.Delayed); @@ -30,4 +31,5 @@ workbenchRegistry.registerWorkbenchContribution(InlineCompletionsAccessibleViewC registerWorkbenchContribution2(AccessibilityStatus.ID, AccessibilityStatus, WorkbenchPhase.BlockRestore); registerWorkbenchContribution2(SaveAccessibilitySignalContribution.ID, SaveAccessibilitySignalContribution, WorkbenchPhase.AfterRestored); +registerWorkbenchContribution2(DiffEditorActiveAnnouncementContribution.ID, DiffEditorActiveAnnouncementContribution, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(DynamicSpeechAccessibilityConfiguration.ID, DynamicSpeechAccessibilityConfiguration, WorkbenchPhase.AfterRestored); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index e3b3bb1ef7a..5151b2869d9 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -53,7 +53,8 @@ export const enum AccessibilityVerbositySettingId { Hover = 'accessibility.verbosity.hover', Notification = 'accessibility.verbosity.notification', EmptyEditorHint = 'accessibility.verbosity.emptyEditorHint', - Comments = 'accessibility.verbosity.comments' + Comments = 'accessibility.verbosity.comments', + DiffEditorActive = 'accessibility.verbosity.diffEditorActive' } export const enum AccessibleViewProviderId { @@ -170,6 +171,10 @@ const configuration: IConfigurationNode = { description: localize('verbosity.comments', 'Provide information about actions that can be taken in the comment widget or in a file which contains comments.'), ...baseVerbosityProperty }, + [AccessibilityVerbositySettingId.DiffEditorActive]: { + description: localize('verbosity.diffEditorActive', 'Indicate when a diff editor becomes the active editor.'), + ...baseVerbosityProperty + }, [AccessibilityAlertSettingId.Save]: { 'markdownDescription': localize('announcement.save', "Indicates when a file is saved. Also see {0}.", '`#audioCues.save#`'), 'enum': ['userGesture', 'always', 'never'], diff --git a/src/vs/workbench/contrib/accessibility/browser/openDiffEditorAnnouncement.ts b/src/vs/workbench/contrib/accessibility/browser/openDiffEditorAnnouncement.ts new file mode 100644 index 00000000000..ed3d0f75cb0 --- /dev/null +++ b/src/vs/workbench/contrib/accessibility/browser/openDiffEditorAnnouncement.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { localize } from 'vs/nls'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { Event } from 'vs/base/common/event'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; + +export class DiffEditorActiveAnnouncementContribution extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.diffEditorActiveAnnouncement'; + + private _onDidActiveEditorChangeListener?: IDisposable; + + constructor( + @IEditorService private readonly _editorService: IEditorService, + @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, + @IConfigurationService private readonly _configurationService: IConfigurationService + ) { + super(); + this._register(Event.runAndSubscribe(_accessibilityService.onDidChangeScreenReaderOptimized, () => this._updateListener())); + this._register(_configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(AccessibilityVerbositySettingId.DiffEditorActive)) { + this._updateListener(); + } + })); + } + + private _updateListener(): void { + const announcementEnabled = this._configurationService.getValue(AccessibilityVerbositySettingId.DiffEditorActive); + const screenReaderOptimized = this._accessibilityService.isScreenReaderOptimized(); + + if (!announcementEnabled || !screenReaderOptimized) { + this._onDidActiveEditorChangeListener?.dispose(); + this._onDidActiveEditorChangeListener = undefined; + return; + } + + if (this._onDidActiveEditorChangeListener) { + return; + } + + this._onDidActiveEditorChangeListener = this._register(this._editorService.onDidActiveEditorChange(() => { + if (isDiffEditor(this._editorService.activeTextEditorControl)) { + this._accessibilityService.alert(localize('openDiffEditorAnnouncement', "Diff editor")); + } + })); + } +} diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index 6aeddc30e35..b47c9bc5642 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -110,11 +110,14 @@ function createScreenReaderHelp(): IDisposable { switchSides = localize('switchSidesNoKb', "Run the command Diff Editor: Switch Side, which is currently not triggerable via keybinding, to toggle between the original and modified editors."); } + const diffEditorActiveAnnouncement = localize('msg5', "The setting, accessibility.verbosity.diffEditorActive, controls if a diff editor announcement is made when it becomes the active editor."); + const keys = ['accessibility.signals.diffLineDeleted', 'accessibility.signals.diffLineInserted', 'accessibility.signals.diffLineModified']; const content = [ localize('msg1', "You are in a diff editor."), localize('msg2', "View the next ({0}) or previous ({1}) diff in diff review mode, which is optimized for screen readers.", next, previous), switchSides, + diffEditorActiveAnnouncement, localize('msg4', "To control which accessibility signals should be played, the following settings can be configured: {0}.", keys.join(', ')), ]; const commentCommandInfo = getCommentCommandInfo(keybindingService, contextKeyService, codeEditor); From 5cbe2b96460a9bda1bf408228dea8bd829e5e154 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 6 Mar 2024 13:58:09 -0800 Subject: [PATCH 1809/1897] debug: tighten down debugFocus API proposal (#207006) * debug: tighten down debugFocus API proposal Some bug fixes and reworking to align better with our APIs * fix getter error --- .../api/browser/mainThreadDebugService.ts | 37 ++++++++-------- .../workbench/api/common/extHost.api.impl.ts | 15 ++++--- .../workbench/api/common/extHost.protocol.ts | 6 +-- .../api/common/extHostDebugService.ts | 44 +++++++++---------- src/vs/workbench/api/common/extHostTypes.ts | 13 +++--- .../vscode.proposed.debugFocus.d.ts | 38 +++++++--------- 6 files changed, 72 insertions(+), 81 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 94641115abb..e5dfc1a814e 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -18,6 +18,7 @@ import { convertToVSCPaths, convertToDAPaths, isSessionAttach } from 'vs/workben import { ErrorNoTelemetry } from 'vs/base/common/errors'; import { IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { Event } from 'vs/base/common/event'; @extHostNamedCustomer(MainContext.MainThreadDebugService) export class MainThreadDebugService implements MainThreadDebugServiceShape, IDebugAdapterFactory { @@ -88,28 +89,28 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb this._debugAdapterDescriptorFactories = new Map(); this._extHostKnownSessions = new Set(); - this._toDispose.add(this.debugService.getViewModel().onDidFocusThread(({ thread, explicit, session }) => { - if (session) { - const dto: IThreadFocusDto = { + const viewModel = this.debugService.getViewModel(); + this._toDispose.add(Event.any(viewModel.onDidFocusStackFrame, viewModel.onDidFocusThread)(() => { + const stackFrame = viewModel.focusedStackFrame; + const thread = viewModel.focusedThread; + if (stackFrame) { + this._proxy.$acceptStackFrameFocus({ + kind: 'stackFrame', + threadId: stackFrame.thread.threadId, + frameId: stackFrame.frameId, + sessionId: stackFrame.thread.session.getId(), + } satisfies IStackFrameFocusDto); + } else if (thread) { + this._proxy.$acceptStackFrameFocus({ kind: 'thread', - threadId: thread?.threadId, - sessionId: session.getId(), - }; - this._proxy.$acceptStackFrameFocus(dto); + threadId: thread.threadId, + sessionId: thread.session.getId(), + } satisfies IThreadFocusDto); + } else { + this._proxy.$acceptStackFrameFocus(undefined); } })); - this._toDispose.add(this.debugService.getViewModel().onDidFocusStackFrame(({ stackFrame, explicit, session }) => { - if (session) { - const dto: IStackFrameFocusDto = { - kind: 'stackFrame', - threadId: stackFrame?.thread.threadId, - frameId: stackFrame?.frameId, - sessionId: session.getId(), - }; - this._proxy.$acceptStackFrameFocus(dto); - } - })); this.sendBreakpointsAndListen(); } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 9ac80463738..20f8ebcdad9 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1241,8 +1241,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I get breakpoints() { return extHostDebugService.breakpoints; }, - get stackFrameFocus() { - return extHostDebugService.stackFrameFocus; + get activeStackItem() { + if (!isProposedApiEnabled(extension, 'debugFocus')) { + return undefined; + } + return extHostDebugService.activeStackItem; }, registerDebugVisualizationProvider(id, provider) { checkProposedApiEnabled(extension, 'debugVisualization'); @@ -1267,9 +1270,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I onDidChangeBreakpoints(listener, thisArgs?, disposables?) { return _asExtensionEvent(extHostDebugService.onDidChangeBreakpoints)(listener, thisArgs, disposables); }, - onDidChangeStackFrameFocus(listener, thisArg?, disposables?) { + onDidChangeActiveStackItem(listener, thisArg?, disposables?) { checkProposedApiEnabled(extension, 'debugFocus'); - return _asExtensionEvent(extHostDebugService.onDidChangeStackFrameFocus)(listener, thisArg, disposables); + return _asExtensionEvent(extHostDebugService.onDidChangeActiveStackItem)(listener, thisArg, disposables); }, registerDebugConfigurationProvider(debugType: string, provider: vscode.DebugConfigurationProvider, triggerKind?: vscode.DebugConfigurationProviderTriggerKind) { return extHostDebugService.registerDebugConfigurationProvider(debugType, provider, triggerKind || DebugConfigurationProviderTriggerKind.Initial); @@ -1673,8 +1676,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I InteractiveSessionVoteDirection: extHostTypes.InteractiveSessionVoteDirection, ChatCopyKind: extHostTypes.ChatCopyKind, InteractiveEditorResponseFeedbackKind: extHostTypes.InteractiveEditorResponseFeedbackKind, - StackFrameFocus: extHostTypes.StackFrameFocus, - ThreadFocus: extHostTypes.ThreadFocus, + StackFrame: extHostTypes.StackFrame, + Thread: extHostTypes.Thread, RelatedInformationType: extHostTypes.RelatedInformationType, SpeechToTextStatus: extHostTypes.SpeechToTextStatus, PartialAcceptTriggerKind: extHostTypes.PartialAcceptTriggerKind, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index fb881d05c4f..e002c997721 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2342,14 +2342,14 @@ export type IDebugSessionDto = IDebugSessionFullDto | DebugSessionUUID; export interface IThreadFocusDto { kind: 'thread'; sessionId: string; - threadId: number | undefined; + threadId: number; } export interface IStackFrameFocusDto { kind: 'stackFrame'; sessionId: string; - threadId: number | undefined; - frameId: number | undefined; + threadId: number; + frameId: number; } diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index 38d5f2a3205..e38d3ee11b1 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -15,7 +15,7 @@ import { DebugSessionUUID, ExtHostDebugServiceShape, IBreakpointsDeltaDto, IThre import { IExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import { Breakpoint, DataBreakpoint, DebugAdapterExecutable, DebugAdapterInlineImplementation, DebugAdapterNamedPipeServer, DebugAdapterServer, DebugConsoleMode, Disposable, FunctionBreakpoint, Location, Position, setBreakpointId, SourceBreakpoint, ThreadFocus, StackFrameFocus, ThemeIcon } from 'vs/workbench/api/common/extHostTypes'; +import { Breakpoint, DataBreakpoint, DebugAdapterExecutable, DebugAdapterInlineImplementation, DebugAdapterNamedPipeServer, DebugAdapterServer, DebugConsoleMode, Disposable, FunctionBreakpoint, Location, Position, setBreakpointId, SourceBreakpoint, Thread, StackFrame, ThemeIcon } from 'vs/workbench/api/common/extHostTypes'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; import { MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugAdapter, IDebugAdapterExecutable, IDebugAdapterNamedPipeServer, IDebugAdapterServer, IDebugVisualization, IDebugVisualizationContext, IDebuggerContribution, DebugVisualizationType, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; @@ -44,8 +44,8 @@ export interface IExtHostDebugService extends ExtHostDebugServiceShape { onDidReceiveDebugSessionCustomEvent: Event; onDidChangeBreakpoints: Event; breakpoints: vscode.Breakpoint[]; - onDidChangeStackFrameFocus: Event; - stackFrameFocus: vscode.ThreadFocus | vscode.StackFrameFocus | undefined; + onDidChangeActiveStackItem: Event; + activeStackItem: vscode.Thread | vscode.StackFrame | undefined; addBreakpoints(breakpoints0: readonly vscode.Breakpoint[]): Promise; removeBreakpoints(breakpoints0: readonly vscode.Breakpoint[]): Promise; @@ -97,8 +97,8 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E private readonly _onDidChangeBreakpoints: Emitter; - private _stackFrameFocus: vscode.ThreadFocus | vscode.StackFrameFocus | undefined; - private readonly _onDidChangeStackFrameFocus: Emitter; + private _activeStackItem: vscode.Thread | vscode.StackFrame | undefined; + private readonly _onDidChangeActiveStackItem: Emitter; private _debugAdapters: Map; private _debugAdaptersTrackers: Map; @@ -144,7 +144,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E this._onDidChangeBreakpoints = new Emitter(); - this._onDidChangeStackFrameFocus = new Emitter(); + this._onDidChangeActiveStackItem = new Emitter(); this._activeDebugConsole = new ExtHostDebugConsole(this._debugServiceProxy); @@ -278,12 +278,12 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E // extension debug API - get stackFrameFocus(): vscode.ThreadFocus | vscode.StackFrameFocus | undefined { - return this._stackFrameFocus; + get activeStackItem(): vscode.Thread | vscode.StackFrame | undefined { + return this._activeStackItem; } - get onDidChangeStackFrameFocus(): Event { - return this._onDidChangeStackFrameFocus.event; + get onDidChangeActiveStackItem(): Event { + return this._onDidChangeActiveStackItem.event; } get onDidChangeBreakpoints(): Event { @@ -768,21 +768,19 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E this.fireBreakpointChanges(a, r, c); } - public async $acceptStackFrameFocus(focusDto: IThreadFocusDto | IStackFrameFocusDto): Promise { - let focus: ThreadFocus | StackFrameFocus; - const session = focusDto.sessionId ? await this.getSession(focusDto.sessionId) : undefined; - if (!session) { - throw new Error('no DebugSession found for debug focus context'); + public async $acceptStackFrameFocus(focusDto: IThreadFocusDto | IStackFrameFocusDto | undefined): Promise { + let focus: vscode.Thread | vscode.StackFrame | undefined; + if (focusDto) { + const session = await this.getSession(focusDto.sessionId); + if (focusDto.kind === 'thread') { + focus = new Thread(session.api, focusDto.threadId); + } else { + focus = new StackFrame(session.api, focusDto.threadId, focusDto.frameId); + } } - if (focusDto.kind === 'thread') { - focus = new ThreadFocus(session.api, focusDto.threadId); - } else { - focus = new StackFrameFocus(session.api, focusDto.threadId, focusDto.frameId); - } - - this._stackFrameFocus = focus; - this._onDidChangeStackFrameFocus.fire(this._stackFrameFocus); + this._activeStackItem = focus; + this._onDidChangeActiveStackItem.fire(this._activeStackItem); } public $provideDebugConfigurations(configProviderHandle: number, folderUri: UriComponents | undefined, token: CancellationToken): Promise { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 8660835e2d6..c2cd247e7dd 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3035,23 +3035,20 @@ export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInli } -@es5ClassCompat -export class StackFrameFocus { +export class StackFrame implements vscode.StackFrame { constructor( public readonly session: vscode.DebugSession, - readonly threadId?: number, - readonly frameId?: number) { } + readonly threadId: number, + readonly frameId: number) { } } -@es5ClassCompat -export class ThreadFocus { +export class Thread implements vscode.Thread { constructor( public readonly session: vscode.DebugSession, - readonly threadId?: number) { } + readonly threadId: number) { } } - @es5ClassCompat export class EvaluatableExpression implements vscode.EvaluatableExpression { readonly range: vscode.Range; diff --git a/src/vscode-dts/vscode.proposed.debugFocus.d.ts b/src/vscode-dts/vscode.proposed.debugFocus.d.ts index 73abb814bbe..92c9ad01f2b 100644 --- a/src/vscode-dts/vscode.proposed.debugFocus.d.ts +++ b/src/vscode-dts/vscode.proposed.debugFocus.d.ts @@ -7,17 +7,13 @@ declare module 'vscode' { // See https://github.com/microsoft/vscode/issues/63943 - export class ThreadFocus { + export class Thread { /** * Create a ThreadFocus * @param session * @param threadId - * @param frameId */ - constructor( - session: DebugSession, - threadId?: number); - + constructor(session: DebugSession, threadId: number); /** * Debug session for thread. @@ -25,23 +21,19 @@ declare module 'vscode' { readonly session: DebugSession; /** - * Id of the associated thread (DAP id). May be undefined if thread has become unselected. + * ID of the associated thread in the debug protocol. */ - readonly threadId: number | undefined; + readonly threadId: number; } - export class StackFrameFocus { + export class StackFrame { /** * Create a StackFrameFocus * @param session * @param threadId * @param frameId */ - constructor( - session: DebugSession, - threadId?: number, - frameId?: number); - + constructor(session: DebugSession, threadId?: number, frameId?: number); /** * Debug session for thread. @@ -49,26 +41,26 @@ declare module 'vscode' { readonly session: DebugSession; /** - * Id of the associated thread (DAP id). May be undefined if a frame is unselected. + * Id of the associated thread in the debug protocol. */ - readonly threadId: number | undefined; + readonly threadId: number; /** - * Id of the stack frame (DAP id). May be undefined if a frame is unselected. + * Id of the stack frame in the debug protocol. */ - readonly frameId: number | undefined; + readonly frameId: number; } export namespace debug { /** - * The currently focused thread or stack frame id, or `undefined` if this has not been set. (e.g. not in debug mode). + * The currently focused thread or stack frame, or `undefined` if no + * thread or stack is focused. */ - export let stackFrameFocus: ThreadFocus | StackFrameFocus | undefined; + export const activeStackItem: Thread | StackFrame | undefined; /** - * An {@link Event} which fires when the {@link debug.stackFrameFocus} changes. Provides a sessionId. threadId is not undefined - * when a thread of frame has gained focus. frameId is defined when a stackFrame has gained focus. + * An event which fires when the {@link debug.activeStackItem} has changed. */ - export const onDidChangeStackFrameFocus: Event; + export const onDidChangeActiveStackItem: Event; } } From d3e035f00ff914ba4a291b32add459796cd9690a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:39:55 -0800 Subject: [PATCH 1810/1897] Revert "Add change to missed file" This reverts commit 1802552fc5512cc4c86f074affb23bc91fed511a. --- src/vs/platform/terminal/common/terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 1e0dfb3581f..ea960462a60 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -569,7 +569,7 @@ export interface IShellLaunchConfig { * until `Terminal.show` is called. The typical usage for this is when you need to run * something that may need interactivity but only want to tell the user about it when * interaction is needed. Note that the terminals will still be exposed to all extensions - * as normal and they will remain hidden when the workspace is reloaded. + * as normal. And the hidden terminals won't be restored when the workspace is next opened. */ hideFromUser?: boolean; From 046a9980ec167750a98401794820eaf9e4faa235 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:39:58 -0800 Subject: [PATCH 1811/1897] Revert "Tweak wording" This reverts commit 55e64beda6de2d3a4cd626fff5251ab90f2cd010. --- src/vscode-dts/vscode.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 614dcc68cf1..b1ae2b22271 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -11741,7 +11741,7 @@ declare module 'vscode' { * until `Terminal.show` is called. The typical usage for this is when you need to run * something that may need interactivity but only want to tell the user about it when * interaction is needed. Note that the terminals will still be exposed to all extensions - * as normal and they will remain hidden when the workspace is reloaded. + * as normal. And the hidden terminals won't be restored when the workspace is next opened. */ hideFromUser?: boolean; From 4b48b419419502f3a415dfdddfb82f793bda29f6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:41:18 -0800 Subject: [PATCH 1812/1897] Tweak wording --- src/vs/platform/terminal/common/terminal.ts | 2 +- src/vscode-dts/vscode.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index ea960462a60..211eefedc36 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -569,7 +569,7 @@ export interface IShellLaunchConfig { * until `Terminal.show` is called. The typical usage for this is when you need to run * something that may need interactivity but only want to tell the user about it when * interaction is needed. Note that the terminals will still be exposed to all extensions - * as normal. And the hidden terminals won't be restored when the workspace is next opened. + * as normal. The hidden terminals will not be restored when the workspace is next opened. */ hideFromUser?: boolean; diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index b1ae2b22271..d70cd4885ae 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -11741,7 +11741,7 @@ declare module 'vscode' { * until `Terminal.show` is called. The typical usage for this is when you need to run * something that may need interactivity but only want to tell the user about it when * interaction is needed. Note that the terminals will still be exposed to all extensions - * as normal. And the hidden terminals won't be restored when the workspace is next opened. + * as normal. The hidden terminals will not be restored when the workspace is next opened. */ hideFromUser?: boolean; From 5bdc31357d65c16d96c06248afa15082c1eb7bde Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 6 Mar 2024 16:10:10 -0800 Subject: [PATCH 1813/1897] cli: update dev tunnel sdk (#207018) --- cli/Cargo.lock | 2 +- cli/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 553df3f8f53..80f4c3bfe32 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -2526,7 +2526,7 @@ dependencies = [ [[package]] name = "tunnels" version = "0.1.0" -source = "git+https://github.com/microsoft/dev-tunnels?rev=4de1ff7979b5758c69218a3f45f6d9784b165072#4de1ff7979b5758c69218a3f45f6d9784b165072" +source = "git+https://github.com/microsoft/dev-tunnels?rev=8cae9b2a24c65c6c1958f5a0e77d72b23b5c6c30#8cae9b2a24c65c6c1958f5a0e77d72b23b5c6c30" dependencies = [ "async-trait", "chrono", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index f51f31e9fb5..db058cd9f7c 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -34,7 +34,7 @@ serde_bytes = "0.11.9" chrono = { version = "0.4.26", features = ["serde", "std", "clock"], default-features = false } gethostname = "0.4.3" libc = "0.2.144" -tunnels = { git = "https://github.com/microsoft/dev-tunnels", rev = "4de1ff7979b5758c69218a3f45f6d9784b165072", default-features = false, features = ["connections"] } +tunnels = { git = "https://github.com/microsoft/dev-tunnels", rev = "8cae9b2a24c65c6c1958f5a0e77d72b23b5c6c30", default-features = false, features = ["connections"] } keyring = { version = "2.0.3", default-features = false, features = ["linux-secret-service-rt-tokio-crypto-openssl"] } dialoguer = "0.10.4" hyper = { version = "0.14.26", features = ["server", "http1", "runtime"] } From 59ec734843767b100e4bd476903e0c363772a1f9 Mon Sep 17 00:00:00 2001 From: Andy Schoenberger Date: Wed, 6 Mar 2024 19:10:33 -0500 Subject: [PATCH 1814/1897] Only one subscriber for kernels for onDidChangeSelectedNotebooks (#204417) Co-authored-by: Peng Lyu --- .../api/browser/mainThreadNotebookKernels.ts | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index 678825ac6a5..c7d0f3a6611 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -7,7 +7,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { combinedDisposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -151,6 +151,16 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape this._proxy.$cellExecutionChanged(e.notebook, e.cellHandle, e.changed?.state); } })); + + this._disposables.add(this._notebookKernelService.onDidChangeSelectedNotebooks(e => { + for (const [handle, [kernel,]] of this._kernels) { + if (e.oldKernel === kernel.id) { + this._proxy.$acceptNotebookAssociation(handle, e.notebook, false); + } else if (e.newKernel === kernel.id) { + this._proxy.$acceptNotebookAssociation(handle, e.notebook, true); + } + } + })); } dispose(): void { @@ -262,16 +272,8 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape } }(data, this._languageService); - const listener = this._notebookKernelService.onDidChangeSelectedNotebooks(e => { - if (e.oldKernel === kernel.id) { - this._proxy.$acceptNotebookAssociation(handle, e.notebook, false); - } else if (e.newKernel === kernel.id) { - this._proxy.$acceptNotebookAssociation(handle, e.notebook, true); - } - }); - const registration = this._notebookKernelService.registerKernel(kernel); - this._kernels.set(handle, [kernel, combinedDisposable(listener, registration)]); + this._kernels.set(handle, [kernel, registration]); } $updateKernel(handle: number, data: Partial): void { From c8024cf91f72507ac80d17f62938efa6438e2f36 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 6 Mar 2024 16:13:47 -0800 Subject: [PATCH 1815/1897] Reserve focus for cell chat widget. (#207000) --- .../controller/chat/notebookChatController.ts | 51 ++++++++++++++----- .../browser/controller/executeActions.ts | 22 +++++++- .../notebook/browser/notebookBrowser.ts | 5 ++ .../notebook/browser/notebookEditorWidget.ts | 6 +-- .../notebook/browser/view/notebookCellList.ts | 8 ++- 5 files changed, 73 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 66e1969d4f3..506c09be8ab 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -40,7 +40,7 @@ import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/in import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; -import { INotebookEditor, INotebookEditorContribution, INotebookViewZone, ScrollToRevealBehavior } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookEditor, INotebookEditorContribution, INotebookViewZone } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -96,21 +96,35 @@ class NotebookChatWidget extends Disposable implements INotebookViewZone { this._layoutWidget(inlineChatWidget, widgetContainer); } + hasFocus() { + return this.inlineChatWidget.hasFocus(); + } + focus() { + this.updateNotebookEditorFocusNSelections(); this.inlineChatWidget.focus(); } + updateNotebookEditorFocusNSelections() { + this._notebookEditor.focusContainer(true); + this._notebookEditor.setFocus({ start: this.afterModelPosition, end: this.afterModelPosition }); + this._notebookEditor.setSelections([{ + start: this.afterModelPosition, + end: this.afterModelPosition + }]); + } + getEditingCell() { return this._editingCell; } async getOrCreateEditingCell(): Promise<{ cell: CellViewModel; editor: IActiveCodeEditor } | undefined> { if (this._editingCell) { - await this._notebookEditor.focusNotebookCell(this._editingCell, 'editor'); - if (this._notebookEditor.activeCodeEditor?.hasModel()) { + const codeEditor = this._notebookEditor.codeEditors.find(ce => ce[0] === this._editingCell)?.[1]; + if (codeEditor?.hasModel()) { return { cell: this._editingCell, - editor: this._notebookEditor.activeCodeEditor + editor: codeEditor }; } else { return undefined; @@ -121,17 +135,25 @@ class NotebookChatWidget extends Disposable implements INotebookViewZone { return undefined; } + const widgetHasFocus = this.inlineChatWidget.hasFocus(); + this._editingCell = insertCell(this._languageService, this._notebookEditor, this.afterModelPosition, CellKind.Code, 'above'); if (!this._editingCell) { return undefined; } - await this._notebookEditor.focusNotebookCell(this._editingCell, 'editor', { revealBehavior: ScrollToRevealBehavior.firstLine }); - if (this._notebookEditor.activeCodeEditor?.hasModel()) { + await this._notebookEditor.revealFirstLineIfOutsideViewport(this._editingCell); + + if (widgetHasFocus) { + this.focus(); + } + + const codeEditor = this._notebookEditor.codeEditors.find(ce => ce[0] === this._editingCell)?.[1]; + if (codeEditor?.hasModel()) { return { cell: this._editingCell, - editor: this._notebookEditor.activeCodeEditor + editor: codeEditor }; } @@ -390,12 +412,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito return; } - this._notebookEditor.focusContainer(true); - this._notebookEditor.setFocus({ start: this._widget.afterModelPosition, end: this._widget.afterModelPosition }); - this._notebookEditor.setSelections([{ - start: this._widget.afterModelPosition, - end: this._widget.afterModelPosition - }]); + this._widget.updateNotebookEditorFocusNSelections(); } async acceptInput() { @@ -739,6 +756,10 @@ export class NotebookChatController extends Disposable implements INotebookEdito await this._notebookEditor.focusNotebookCell(cell, 'editor'); } + hasFocus() { + return this._widget?.hasFocus() ?? false; + } + focus() { this._focusWidget(); } @@ -769,6 +790,10 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._activeRequestCts?.cancel(); } + getEditingCell() { + return this._widget?.getEditingCell(); + } + discard() { this._strategy?.cancel(); this._activeRequestCts?.cancel(); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index 2ca62a92f6f..ce41420deba 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -20,6 +20,8 @@ import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; +import { NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController'; import { CELL_TITLE_CELL_GROUP_ID, CellToolbarOrder, INotebookActionContext, INotebookCellActionContext, INotebookCellToolbarActionContext, INotebookCommandContext, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, NotebookAction, NotebookCellAction, NotebookMultiCellAction, cellExecutionArgs, executeNotebookCondition, getContextFromActiveEditor, getContextFromUri, parseMultiCellExecutionArgs } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, IFocusNotebookCellOptions, ScrollToRevealBehavior } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; @@ -198,7 +200,10 @@ registerAction2(class ExecuteCell extends NotebookMultiCellAction { precondition: executeThisCellCondition, title: localize('notebookActions.execute', "Execute Cell"), keybinding: { - when: NOTEBOOK_CELL_LIST_FOCUSED, + when: ContextKeyExpr.or( + NOTEBOOK_CELL_LIST_FOCUSED, + ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED) + ), primary: KeyMod.WinCtrl | KeyCode.Enter, win: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter @@ -229,6 +234,21 @@ registerAction2(class ExecuteCell extends NotebookMultiCellAction { await context.notebookEditor.focusNotebookCell(context.cell, 'container', { skipReveal: true }); } + const chatController = NotebookChatController.get(context.notebookEditor); + const editingCell = chatController?.getEditingCell(); + if (chatController?.hasFocus() && editingCell) { + const group = editorGroupsService.activeGroup; + + if (group) { + if (group.activeEditor) { + group.pinEditor(group.activeEditor); + } + } + + await context.notebookEditor.executeNotebookCells([editingCell]); + return; + } + await runCell(editorGroupsService, context); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 4f6205e00cc..8c8d3a7f64b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -629,6 +629,11 @@ export interface INotebookEditor { */ revealInCenterIfOutsideViewport(cell: ICellViewModel): Promise; + /** + * Reveal the first line of the cell into the view if the cell is outside of the viewport. + */ + revealFirstLineIfOutsideViewport(cell: ICellViewModel): Promise; + /** * Reveal a line in notebook cell into viewport with minimal scrolling. */ diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index cdffe911b5d..8ce50f30af0 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2122,8 +2122,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD await this._list.revealCell(cell, CellRevealType.CenterIfOutsideViewport); } - revealFirstLineIfOutsideViewport(cell: ICellViewModel) { - this._list.revealCell(cell, CellRevealType.FirstLineIfOutsideViewport); + async revealFirstLineIfOutsideViewport(cell: ICellViewModel) { + await this._list.revealCell(cell, CellRevealType.FirstLineIfOutsideViewport); } async revealLineInViewAsync(cell: ICellViewModel, line: number): Promise { @@ -2446,7 +2446,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._cursorNavMode.set(true); await this.revealInView(cell); } else if (options?.revealBehavior === ScrollToRevealBehavior.firstLine) { - this.revealFirstLineIfOutsideViewport(cell); + await this.revealFirstLineIfOutsideViewport(cell); } else if (options?.revealBehavior === ScrollToRevealBehavior.fullCell) { await this.revealInView(cell); } else { diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index bd47eb879e9..377857dd4ce 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -925,8 +925,12 @@ export class NotebookCellList extends WorkbenchList implements ID break; } - // wait for the editor to be created only if the cell is in editing mode (meaning it has an editor and will focus the editor) - if (cell.getEditState() === CellEditState.Editing && !cell.editorAttached) { + if (( + // wait for the editor to be created if the cell is in editing mode + cell.getEditState() === CellEditState.Editing + // wait for the editor to be created if we are revealing the first line of the cell + || revealType === CellRevealType.FirstLineIfOutsideViewport + ) && !cell.editorAttached) { return getEditorAttachedPromise(cell); } From d990bac0cf35ca7eb57581d74346735214a4427c Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 6 Mar 2024 19:02:16 -0800 Subject: [PATCH 1816/1897] Add keybinding for navigating separators (#207028) Fixes https://github.com/microsoft/vscode/issues/70861 --- .../platform/quickinput/browser/quickInput.ts | 15 +++-- .../browser/quickInputController.ts | 1 + .../quickinput/browser/quickInputList.ts | 67 +++++++++++++++---- 3 files changed, 64 insertions(+), 19 deletions(-) diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 2bfd12c91cb..a3902157e53 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -21,7 +21,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { isIOS } from 'vs/base/common/platform'; +import { isIOS, isMacintosh } from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; import { ThemeIcon } from 'vs/base/common/themables'; import 'vs/css!./media/quickInput'; @@ -826,20 +826,25 @@ export class QuickPick extends QuickInput implements I this.ui.inputBox.onDidChange(value => { this.doSetValue(value, true /* skip update since this originates from the UI */); })); + // Keybindings for the input box or list if there is no input box this.visibleDisposables.add((this._hideInput ? this.ui.list : this.ui.inputBox).onKeyDown((event: KeyboardEvent | StandardKeyboardEvent) => { switch (event.keyCode) { case KeyCode.DownArrow: - this.ui.list.focus(QuickInputListFocus.Next); + if (isMacintosh ? event.metaKey : event.ctrlKey) { + this.ui.list.focus(QuickInputListFocus.NextSeparator); + } else { + this.ui.list.focus(QuickInputListFocus.Next); + } if (this.canSelectMany) { this.ui.list.domFocus(); } dom.EventHelper.stop(event, true); break; case KeyCode.UpArrow: - if (this.ui.list.getFocusedElements().length) { - this.ui.list.focus(QuickInputListFocus.Previous); + if (isMacintosh ? event.metaKey : event.ctrlKey) { + this.ui.list.focus(QuickInputListFocus.PreviousSeparator); } else { - this.ui.list.focus(QuickInputListFocus.Last); + this.ui.list.focus(QuickInputListFocus.Previous); } if (this.canSelectMany) { this.ui.list.domFocus(); diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index 10529b5b903..67afe5550b3 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -219,6 +219,7 @@ export class QuickInputController extends Disposable { inputBox.setFocus(); })); // TODO: Turn into commands instead of handling KEY_DOWN + // Keybindings for the quickinput widget as a whole this._register(dom.addStandardDisposableListener(container, dom.EventType.KEY_DOWN, (event) => { if (dom.isAncestor(event.target, widget)) { return; // Ignore event if target is inside widget to allow the widget to handle the event. diff --git a/src/vs/platform/quickinput/browser/quickInputList.ts b/src/vs/platform/quickinput/browser/quickInputList.ts index 98993dd9070..f82c848d50e 100644 --- a/src/vs/platform/quickinput/browser/quickInputList.ts +++ b/src/vs/platform/quickinput/browser/quickInputList.ts @@ -430,7 +430,9 @@ export enum QuickInputListFocus { Next, Previous, NextPage, - PreviousPage + PreviousPage, + NextSeparator, + PreviousSeparator } export class QuickInputList { @@ -499,6 +501,7 @@ export class QuickInputList { } as IListOptions); this.list.getHTMLElement().id = id; this.disposables.push(this.list); + // Keybindings for the list itself this.disposables.push(this.list.onKeyDown(e => { const event = new StandardKeyboardEvent(e); switch (event.keyCode) { @@ -510,6 +513,7 @@ export class QuickInputList { this.list.setFocus(range(this.list.length)); } break; + // When we hit the top of the list, we fire the onLeave event. case KeyCode.UpArrow: { const focus1 = this.list.getFocus(); if (focus1.length === 1 && focus1[0] === 0) { @@ -517,6 +521,7 @@ export class QuickInputList { } break; } + // When we hit the bottom of the list, we fire the onLeave event. case KeyCode.DownArrow: { const focus2 = this.list.getFocus(); if (focus2.length === 1 && focus2[0] === this.list.length - 1) { @@ -810,33 +815,67 @@ export class QuickInputList { this.list.scrollTop = this.list.scrollHeight; this.list.focusLast(undefined, (e) => !!e.item); break; - case QuickInputListFocus.Next: { + case QuickInputListFocus.Next: this.list.focusNext(undefined, true, undefined, (e) => !!e.item); - const index = this.list.getFocus()[0]; - if (index !== 0 && !this.elements[index - 1].item && this.list.firstVisibleIndex > index - 1) { - this.list.reveal(index - 1); - } break; - } - case QuickInputListFocus.Previous: { + case QuickInputListFocus.Previous: this.list.focusPrevious(undefined, true, undefined, (e) => !!e.item); - const index = this.list.getFocus()[0]; - if (index !== 0 && !this.elements[index - 1].item && this.list.firstVisibleIndex > index - 1) { - this.list.reveal(index - 1); - } break; - } case QuickInputListFocus.NextPage: this.list.focusNextPage(undefined, (e) => !!e.item); break; case QuickInputListFocus.PreviousPage: this.list.focusPreviousPage(undefined, (e) => !!e.item); break; + case QuickInputListFocus.NextSeparator: { + let foundSeparatorAsItem = false; + this.list.focusNext(undefined, true, undefined, (e) => { + if (foundSeparatorAsItem) { + // This should be the index right after the separator so it + // is the item we want to focus. + return true; + } + if (e.separator) { + if (e.item) { + return true; + } else { + foundSeparatorAsItem = true; + } + } + return false; + }); + break; + } + case QuickInputListFocus.PreviousSeparator: { + let foundSeparatorAsItem = false; + this.list.focusPrevious(undefined, true, undefined, (e) => { + if (foundSeparatorAsItem) { + // This should be the index right before the separator so it + // is the item we want to focus. + return true; + } + if (e.separator) { + if (e.item) { + // This would be an inline-separator so we should + // focus this item. + return true; + } else { + foundSeparatorAsItem = true; + } + } + return false; + }); + break; + } } const focused = this.list.getFocus()[0]; if (typeof focused === 'number') { - this.list.reveal(focused); + if (focused !== 0 && !this.elements[focused - 1].item && this.list.firstVisibleIndex > focused - 1) { + this.list.reveal(focused - 1); + } else { + this.list.reveal(focused); + } } } From cc1f5e3acfad98ed519befc1ebfd199777d135f4 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 6 Mar 2024 19:35:23 -0800 Subject: [PATCH 1817/1897] History in notebook cell chat (#207029) --- .../controller/chat/cellChatActions.ts | 40 ++++++++++++ .../controller/chat/notebookChatController.ts | 62 +++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index faca219b69c..329ff39bf74 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -573,3 +573,43 @@ registerAction2(class extends NotebookAction { NotebookChatController.get(context.notebookEditor)?.focusAbove(); } }); + +registerAction2(class extends NotebookAction { + constructor() { + super( + { + id: 'notebook.cell.chat.previousFromHistory', + title: localize2('notebook.cell.chat.previousFromHistory', "Previous From History"), + precondition: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED), + keybinding: { + when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED), + weight: KeybindingWeight.EditorCore + 10, + primary: KeyCode.UpArrow, + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { + NotebookChatController.get(context.notebookEditor)?.populateHistory(true); + } +}); + +registerAction2(class extends NotebookAction { + constructor() { + super( + { + id: 'notebook.cell.chat.nextFromHistory', + title: localize2('notebook.cell.chat.nextFromHistory', "Next From History"), + precondition: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED), + keybinding: { + when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED), + weight: KeybindingWeight.EditorCore + 10, + primary: KeyCode.DownArrow + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { + NotebookChatController.get(context.notebookEditor)?.populateHistory(false); + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 506c09be8ab..626f496569f 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -29,6 +29,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { AsyncProgress } from 'vs/platform/progress/common/progress'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { SaveReason } from 'vs/workbench/common/editor'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingService'; @@ -195,6 +196,15 @@ export class NotebookChatController extends Disposable implements INotebookEdito public static get(editor: INotebookEditor): NotebookChatController | null { return editor.getContribution(NotebookChatController.id); } + + // History + private static _storageKey = 'inline-chat-history'; + private static _promptHistory: string[] = []; + private _historyOffset: number = -1; + private _historyCandidate: string = ''; + private _historyUpdate: (prompt: string) => void; + + private _strategy: EditStrategy | undefined; private _sessionCtor: CancelablePromise | undefined; private _activeSession?: Session; @@ -220,6 +230,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito @IModelService private readonly _modelService: IModelService, @ILanguageService private readonly _languageService: ILanguageService, @INotebookExecutionStateService private _executionStateService: INotebookExecutionStateService, + @IStorageService private readonly _storageService: IStorageService, ) { super(); @@ -230,6 +241,18 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._ctxOuterFocusPosition = CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.bindTo(this._contextKeyService); this._registerFocusTracker(); + + NotebookChatController._promptHistory = JSON.parse(this._storageService.get(NotebookChatController._storageKey, StorageScope.PROFILE, '[]')); + this._historyUpdate = (prompt: string) => { + const idx = NotebookChatController._promptHistory.indexOf(prompt); + if (idx >= 0) { + NotebookChatController._promptHistory.splice(idx, 1); + } + NotebookChatController._promptHistory.unshift(prompt); + this._historyOffset = -1; + this._historyCandidate = ''; + this._storageService.store(NotebookChatController._storageKey, JSON.stringify(NotebookChatController._promptHistory), StorageScope.PROFILE, StorageTarget.USER); + }; } private _registerFocusTracker() { @@ -263,6 +286,9 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._widget = undefined; this._widgetDisposableStore.clear(); + this._historyOffset = -1; + this._historyCandidate = ''; + scheduleAtNextAnimationFrame(window, () => { this._createWidget(index, input, autoSend); }); @@ -425,6 +451,9 @@ export class NotebookChatController extends Disposable implements INotebookEdito assertType(this._activeSession.lastInput); const value = this._activeSession.lastInput.value; + + this._historyUpdate(value); + const editor = this._widget.parentEditor; const model = editor.getModel(); @@ -781,6 +810,39 @@ export class NotebookChatController extends Disposable implements INotebookEdito } } + populateHistory(up: boolean) { + if (!this._widget) { + return; + } + + const len = NotebookChatController._promptHistory.length; + if (len === 0) { + return; + } + + if (this._historyOffset === -1) { + // remember the current value + this._historyCandidate = this._widget.inlineChatWidget.value; + } + + const newIdx = this._historyOffset + (up ? 1 : -1); + if (newIdx >= len) { + // reached the end + return; + } + + let entry: string; + if (newIdx < 0) { + entry = this._historyCandidate; + this._historyOffset = -1; + } else { + entry = NotebookChatController._promptHistory[newIdx]; + this._historyOffset = newIdx; + } + + this._widget.inlineChatWidget.value = entry; + this._widget.inlineChatWidget.selectAll(); + } async cancelCurrentRequest(discard: boolean) { if (discard) { From 07aa0914693d64bd8a00f10377779d4562b66909 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 7 Mar 2024 08:36:05 +0100 Subject: [PATCH 1818/1897] debt - fix more memory leaks (#207034) --- src/vs/workbench/browser/parts/editor/editorStatus.ts | 2 +- src/vs/workbench/browser/parts/titlebar/menubarControl.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index acf60d208b5..dc58c035fed 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -328,7 +328,7 @@ class EditorStatus extends Disposable { private readonly metadataElement = this._register(new MutableDisposable()); private readonly currentMarkerStatus = this._register(this.instantiationService.createInstance(ShowCurrentMarkerInStatusbarContribution)); - private readonly tabFocusMode = this.instantiationService.createInstance(TabFocusMode); + private readonly tabFocusMode = this._register(this.instantiationService.createInstance(TabFocusMode)); private readonly state = new State(); private toRender: StateChange | undefined = undefined; diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index a3d09742994..1bfc3526c2e 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -189,7 +189,7 @@ export abstract class MenubarControl extends Disposable { this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); // Listen to update service - this.updateService.onStateChange(() => this.onUpdateStateChange()); + this._register(this.updateService.onStateChange(() => this.onUpdateStateChange())); // Listen for changes in recently opened menu this._register(this.workspacesService.onDidChangeRecentlyOpened(() => { this.onDidChangeRecentlyOpened(); })); From d2599af6b93e90e8f83eb4797d669d196e611d27 Mon Sep 17 00:00:00 2001 From: Miroma <136986257+its-miroma@users.noreply.github.com> Date: Thu, 7 Mar 2024 09:42:35 +0100 Subject: [PATCH 1819/1897] Change default YAML extension (#206447) --- extensions/yaml/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/yaml/package.json b/extensions/yaml/package.json index 96e02e37da6..5223f71c52b 100644 --- a/extensions/yaml/package.json +++ b/extensions/yaml/package.json @@ -37,10 +37,10 @@ "yaml" ], "extensions": [ + ".yaml", ".yml", ".eyaml", ".eyml", - ".yaml", ".cff", ".yaml-tmlanguage", ".yaml-tmpreferences", From 93d99f5898717edcfcf89094d79cb438d860c50f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 7 Mar 2024 11:33:49 +0100 Subject: [PATCH 1820/1897] aux window - reduce leak workaround (#207049) --- src/vs/workbench/browser/part.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index 48278fe16b9..d1bb516e825 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -193,10 +193,7 @@ export abstract class MultiWindowParts extends Compo registerPart(part: T): IDisposable { this._parts.add(part); - return this._register(toDisposable(() => { - this.unregisterPart(part); - part = undefined!; // helps to avoid a memory leak with closures where part is captured - })); + return toDisposable(() => this.unregisterPart(part)); } protected unregisterPart(part: T): void { From 314cf4ba4c15014c93684225783c9375708e3cec Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 7 Mar 2024 12:12:00 +0100 Subject: [PATCH 1821/1897] integrity - remove banner (#206874) (#207050) --- .../electron-sandbox/integrityService.ts | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/services/integrity/electron-sandbox/integrityService.ts b/src/vs/workbench/services/integrity/electron-sandbox/integrityService.ts index 1834c7f3f53..3789a7c1e97 100644 --- a/src/vs/workbench/services/integrity/electron-sandbox/integrityService.ts +++ b/src/vs/workbench/services/integrity/electron-sandbox/integrityService.ts @@ -16,8 +16,6 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { FileAccess, AppResourcePath } from 'vs/base/common/network'; import { IChecksumService } from 'vs/platform/checksum/common/checksumService'; import { ILogService } from 'vs/platform/log/common/log'; -import { IBannerService } from 'vs/workbench/services/banner/browser/bannerService'; -import { Codicon } from 'vs/base/common/codicons'; interface IStorageData { readonly dontShowPrompt: boolean; @@ -75,8 +73,7 @@ export class IntegrityService implements IIntegrityService { @IOpenerService private readonly openerService: IOpenerService, @IProductService private readonly productService: IProductService, @IChecksumService private readonly checksumService: IChecksumService, - @ILogService private readonly logService: ILogService, - @IBannerService private readonly bannerService: IBannerService + @ILogService private readonly logService: ILogService ) { this._compute(); } @@ -89,9 +86,9 @@ export class IntegrityService implements IIntegrityService { this.logService.warn(` -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!!! Installation has been modified on disk and is UNSUPPORTED. Please reinstall !!! -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +---------------------------------------------- +*** Installation has been modified on disk *** +---------------------------------------------- `); @@ -100,7 +97,6 @@ export class IntegrityService implements IIntegrityService { return; // Do not prompt } - this._showBanner(); this._showNotification(); } @@ -146,22 +142,6 @@ export class IntegrityService implements IIntegrityService { }; } - private _showBanner(): void { - const checksumFailMoreInfoUrl = this.productService.checksumFailMoreInfoUrl; - - this.bannerService.show({ - id: 'installation.corrupt', - message: localize('integrity.banner', "Your {0} installation appears to be corrupt. Please reinstall.", this.productService.nameShort), - icon: Codicon.warning, - actions: checksumFailMoreInfoUrl ? [ - { - label: localize('integrity.moreInformation', "More Information"), - href: checksumFailMoreInfoUrl - } - ] : undefined - }); - } - private _showNotification(): void { const checksumFailMoreInfoUrl = this.productService.checksumFailMoreInfoUrl; const message = localize('integrity.prompt', "Your {0} installation appears to be corrupt. Please reinstall.", this.productService.nameShort); From e563009edb2ba8cc4d9019cf17d2b232c4c36a8e Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 7 Mar 2024 12:14:18 +0100 Subject: [PATCH 1822/1897] Remove padding due to using compact (#207053) remove padding due to compact --- .../contrib/languageStatus/browser/media/languageStatus.css | 1 - src/vs/workbench/electron-sandbox/media/window.css | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css b/src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css index f7fb23f59c6..4354ad022df 100644 --- a/src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css +++ b/src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css @@ -55,7 +55,6 @@ .monaco-workbench .hover-language-status { display: flex; - padding: 4px 8px; } .monaco-workbench .hover-language-status:not(:last-child) { diff --git a/src/vs/workbench/electron-sandbox/media/window.css b/src/vs/workbench/electron-sandbox/media/window.css index 8bf36659380..b51743e9b57 100644 --- a/src/vs/workbench/electron-sandbox/media/window.css +++ b/src/vs/workbench/electron-sandbox/media/window.css @@ -5,10 +5,6 @@ .monaco-workbench .zoom-status { display: flex; - padding-top: 2px; - padding-bottom: 2px; - padding-left: 5px; - padding-right: 5px; } .monaco-workbench .zoom-status .monaco-action-bar .action-label.codicon::before { From 1517ee858a4308abe3735341e84190709fb6647d Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 7 Mar 2024 12:14:31 +0100 Subject: [PATCH 1823/1897] Use default hideOnHover behaviour in WorkbenchHoverDelegate (#207051) hideOnHover default behaviour WorkbenchHoverDelegate --- src/vs/platform/hover/browser/hover.ts | 1 - src/vs/workbench/browser/parts/views/treeView.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 0a60a0b4556..9213e1ea02f 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -286,7 +286,6 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate ...options, ...overrideOptions, persistence: { - hideOnHover: true, hideOnKeyDown: true, ...overrideOptions.persistence }, diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 2af2a8be2e5..661638fa561 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -1106,7 +1106,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer this.rerender())); this._register(this.themeService.onDidColorThemeChange(() => this.rerender())); this._register(checkboxStateHandler.onDidChangeCheckboxState(items => { From c8cdd19acf1befb7e49bc9d5104b74715b18986e Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 7 Mar 2024 12:24:10 +0100 Subject: [PATCH 1824/1897] Fix exception when hover is not defined before setting timestamp (#207055) --- src/vs/workbench/contrib/comments/browser/timestamp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/comments/browser/timestamp.ts b/src/vs/workbench/contrib/comments/browser/timestamp.ts index 16b3969d2c3..dbfad43dfd0 100644 --- a/src/vs/workbench/contrib/comments/browser/timestamp.ts +++ b/src/vs/workbench/contrib/comments/browser/timestamp.ts @@ -24,8 +24,8 @@ export class TimestampWidget extends Disposable { this._date = dom.append(container, dom.$('span.timestamp')); this._date.style.display = 'none'; this._useRelativeTime = this.useRelativeTimeSetting; - this.setTimestamp(timeStamp); this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this._date, '')); + this.setTimestamp(timeStamp); } private get useRelativeTimeSetting(): boolean { From 54eb843be2182ed2452a7975f5b31147f2442dea Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 7 Mar 2024 12:49:39 +0100 Subject: [PATCH 1825/1897] restart extension host for updating extensions instead of reload (#206997) * #125417 restart extension host for updating extensions instead of reload * :lipstick: * team feedback * fix enum const --- .../extensions/browser/extensionEditor.ts | 4 +- .../extensions/browser/extensionsActions.ts | 20 +-- .../extensions/browser/extensionsList.ts | 4 +- .../browser/extensionsWorkbenchService.ts | 51 +++++-- .../contrib/extensions/common/extensions.ts | 3 +- .../extensionsActions.test.ts | 134 +++++++++--------- .../common/abstractExtensionService.ts | 6 +- .../services/extensions/common/extensions.ts | 4 +- 8 files changed, 132 insertions(+), 94 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 34c163c0b47..b9de3cbbb0c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -61,7 +61,7 @@ import { InstallDropdownAction, InstallingLabelAction, LocalInstallAction, MigrateDeprecatedExtensionAction, - ReloadAction, + ExtensionRuntimeStateAction, RemoteInstallAction, SetColorThemeAction, SetFileIconThemeAction, @@ -315,7 +315,7 @@ export class ExtensionEditor extends EditorPane { const installAction = this.instantiationService.createInstance(InstallDropdownAction); const actions = [ - this.instantiationService.createInstance(ReloadAction), + this.instantiationService.createInstance(ExtensionRuntimeStateAction), this.instantiationService.createInstance(ExtensionStatusLabelAction), this.instantiationService.createInstance(ActionWithDropDownAction, 'extensions.updateActions', '', [[this.instantiationService.createInstance(UpdateAction, true)], [this.instantiationService.createInstance(ToggleAutoUpdateForExtensionAction, true, [true, 'onlyEnabledExtensions'])]]), diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 2f2833049ba..43e8a7c086d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1554,20 +1554,20 @@ export class DisableDropDownAction extends ActionWithDropDownAction { } -export class ReloadAction extends ExtensionAction { +export class ExtensionRuntimeStateAction extends ExtensionAction { private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} reload`; - private static readonly DisabledClass = `${ReloadAction.EnabledClass} disabled`; + private static readonly DisabledClass = `${ExtensionRuntimeStateAction.EnabledClass} disabled`; updateWhenCounterExtensionChanges: boolean = true; constructor( - @IHostService private readonly hostService: IHostService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IUpdateService private readonly updateService: IUpdateService, @IExtensionService private readonly extensionService: IExtensionService, @IProductService private readonly productService: IProductService, ) { - super('extensions.reload', localize('reloadAction', "Reload"), ReloadAction.DisabledClass, false); + super('extensions.runtimeState', '', ExtensionRuntimeStateAction.DisabledClass, false); this._register(this.extensionService.onDidChangeExtensions(() => this.update())); this.update(); } @@ -1575,7 +1575,7 @@ export class ReloadAction extends ExtensionAction { update(): void { this.enabled = false; this.tooltip = ''; - this.class = ReloadAction.DisabledClass; + this.class = ExtensionRuntimeStateAction.DisabledClass; if (!this.extension) { return; @@ -1596,18 +1596,18 @@ export class ReloadAction extends ExtensionAction { } this.enabled = true; - this.class = ReloadAction.EnabledClass; + this.class = ExtensionRuntimeStateAction.EnabledClass; this.tooltip = runtimeState.reason; - this.label = runtimeState.action === ExtensionRuntimeActionType.Reload ? localize('reload window', 'Reload Window') - : runtimeState.action === ExtensionRuntimeActionType.QuitAndInstall ? localize('restart product', 'Restart {0}', this.productService.nameShort) + this.label = runtimeState.action === ExtensionRuntimeActionType.RestartExtensions ? localize('restart extensions', 'Restart Extensions') + : runtimeState.action === ExtensionRuntimeActionType.QuitAndInstall ? localize('restart product', 'Restart to Update') : runtimeState.action === ExtensionRuntimeActionType.ApplyUpdate || runtimeState.action === ExtensionRuntimeActionType.DownloadUpdate ? localize('update product', 'Update {0}', this.productService.nameShort) : ''; } override async run(): Promise { const runtimeState = this.extension?.runtimeState; - if (runtimeState?.action === ExtensionRuntimeActionType.Reload) { - return this.hostService.reload(); + if (runtimeState?.action === ExtensionRuntimeActionType.RestartExtensions) { + return this.extensionsWorkbenchService.updateRunningExtensions(); } else if (runtimeState?.action === ExtensionRuntimeActionType.DownloadUpdate) { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index d5058c51b43..2595d6010cc 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -13,7 +13,7 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { Event } from 'vs/base/common/event'; import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; -import { ManageExtensionAction, ReloadAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction, MigrateDeprecatedExtensionAction, SetLanguageAction, ClearLanguageAction, UpdateAction, ToggleAutoUpdateForExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { ManageExtensionAction, ExtensionRuntimeStateAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction, MigrateDeprecatedExtensionAction, SetLanguageAction, ClearLanguageAction, UpdateAction, ToggleAutoUpdateForExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget, SyncIgnoredWidget, ExtensionHoverWidget, ExtensionActivationStatusWidget, PreReleaseBookmarkWidget, extensionVerifiedPublisherIconColor, VerifiedPublisherWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions'; @@ -117,7 +117,7 @@ export class Renderer implements IPagedRenderer { const actions = [ this.instantiationService.createInstance(ExtensionStatusLabelAction), this.instantiationService.createInstance(MigrateDeprecatedExtensionAction, true), - this.instantiationService.createInstance(ReloadAction), + this.instantiationService.createInstance(ExtensionRuntimeStateAction), this.instantiationService.createInstance(ActionWithDropDownAction, 'extensions.updateActions', '', [[this.instantiationService.createInstance(UpdateAction, false)], [this.instantiationService.createInstance(ToggleAutoUpdateForExtensionAction, true, [true, 'onlyEnabledExtensions'])]]), this.instantiationService.createInstance(InstallDropdownAction), diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 903dcac8f25..ec74cf72646 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -1111,6 +1111,39 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return undefined; } + async updateRunningExtensions(): Promise { + const toAdd: ILocalExtension[] = []; + const toRemove: string[] = []; + for (const extension of this.local) { + const runtimeState = extension.runtimeState; + if (!runtimeState || runtimeState.action !== ExtensionRuntimeActionType.RestartExtensions) { + continue; + } + if (extension.state === ExtensionState.Uninstalled) { + toRemove.push(extension.identifier.id); + continue; + } + if (!extension.local) { + continue; + } + const isEnabled = this.extensionEnablementService.isEnabled(extension.local); + if (isEnabled) { + const runningExtension = this.extensionService.extensions.find(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier)); + if (runningExtension) { + toRemove.push(runningExtension.identifier.value); + } + toAdd.push(extension.local); + } else { + toRemove.push(extension.identifier.id); + } + } + if (toAdd.length || toRemove.length) { + if (await this.extensionService.stopExtensionHosts(nls.localize('restart', "Enable or Disable extensions"))) { + await this.extensionService.startExtensionHosts({ toAdd, toRemove }); + } + } + } + private getRuntimeState(extension: IExtension): ExtensionRuntimeState | undefined { const isUninstalled = extension.state === ExtensionState.Uninstalled; const runningExtension = this.extensionService.extensions.find(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier)); @@ -1119,7 +1152,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension const canRemoveRunningExtension = runningExtension && this.extensionService.canRemoveExtension(runningExtension); const isSameExtensionRunning = runningExtension && (!extension.server || extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension))); if (!canRemoveRunningExtension && isSameExtensionRunning && !runningExtension.isUnderDevelopment) { - return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postUninstallTooltip', "Please reload {0} to complete the uninstallation of this extension.", this.productService.nameLong) }; + return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('postUninstallTooltip', "Please restart extensions to complete the uninstallation of this extension.") }; } return undefined; } @@ -1157,7 +1190,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } return undefined; } - return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postUpdateTooltip', "Please reload {0} to enable the updated extension.", this.productService.nameLong) }; + return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('postUpdateTooltip', "Please restart extensions to enable the updated extension.") }; } if (this.extensionsServers.length > 1) { @@ -1165,12 +1198,12 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (extensionInOtherServer) { // This extension prefers to run on UI/Local side but is running in remote if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.localExtensionManagementServer) { - return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('enable locally', "Please reload {0} to enable this extension locally.", this.productService.nameLong) }; + return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('enable locally', "Please restart extensions to enable this extension locally.") }; } // This extension prefers to run on Workspace/Remote side but is running in local if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.remoteExtensionManagementServer) { - return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('enable remote', "Please reload {0} to enable this extension in {1}.", this.productService.nameLong, this.extensionManagementServerService.remoteExtensionManagementServer?.label) }; + return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('enable remote', "Please restart extensions to enable this extension in {1}.", this.extensionManagementServerService.remoteExtensionManagementServer?.label) }; } } } @@ -1180,20 +1213,20 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { // This extension prefers to run on UI/Local side but is running in remote if (this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest)) { - return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postEnableTooltip', "Please reload {0} to enable this extension.", this.productService.nameLong) }; + return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('postEnableTooltip', "Please restart extensions to enable this extension.") }; } } if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { // This extension prefers to run on Workspace/Remote side but is running in local if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest)) { - return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postEnableTooltip', "Please reload {0} to enable this extension.", this.productService.nameLong) }; + return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('postEnableTooltip', "Please restart extensions to enable this extension.") }; } } } return undefined; } else { if (isSameExtensionRunning) { - return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postDisableTooltip', "Please reload {0} to disable this extension.", this.productService.nameLong) }; + return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('postDisableTooltip', "Please restart extensions to disable this extension.") }; } } return undefined; @@ -1202,7 +1235,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension // Extension is not running else { if (isEnabled && !this.extensionService.canAddExtension(toExtensionDescription(extension.local))) { - return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postEnableTooltip', "Please reload {0} to enable this extension.", this.productService.nameLong) }; + return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('postEnableTooltip', "Please restart extensions to enable this extension.") }; } const otherServer = extension.server ? extension.server === this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer : null; @@ -1210,7 +1243,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension const extensionInOtherServer = this.local.filter(e => areSameExtensions(e.identifier, extension.identifier) && e.server === otherServer)[0]; // Same extension in other server exists and if (extensionInOtherServer && extensionInOtherServer.local && this.extensionEnablementService.isEnabled(extensionInOtherServer.local)) { - return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postEnableTooltip', "Please reload {0} to enable this extension.", this.productService.nameLong) }; + return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('postEnableTooltip', "Please restart extensions to enable this extension.") }; } } } diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index b523d97d6b2..8be5639f119 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -40,7 +40,7 @@ export const enum ExtensionState { } export const enum ExtensionRuntimeActionType { - Reload = 'reload', + RestartExtensions = 'restartExtensions', DownloadUpdate = 'downloadUpdate', ApplyUpdate = 'applyUpdate', QuitAndInstall = 'quitAndInstall', @@ -139,6 +139,7 @@ export interface IExtensionsWorkbenchService { checkForUpdates(): Promise; getExtensionStatus(extension: IExtension): IExtensionsStatus | undefined; updateAll(): Promise; + updateRunningExtensions(): Promise; // Sync APIs isExtensionIgnoredToSync(extension: IExtension): boolean; diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts index 3ed7d4a1b94..f8c7f267f13 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts @@ -950,21 +950,21 @@ suite('ExtensionsActions', () => { }); -suite('ReloadAction', () => { +suite('ExtensionRuntimeStateAction', () => { const disposables = ensureNoDisposablesAreLeakedInTestSuite(); setup(() => setupTest(disposables)); - test('Test ReloadAction when there is no extension', () => { - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + test('Test Runtime State when there is no extension', () => { + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); assert.ok(!testObject.enabled); }); - test('Test ReloadAction when extension state is installing', async () => { - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + test('Test Runtime State when extension state is installing', async () => { + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); const gallery = aGalleryExtension('a'); @@ -976,8 +976,8 @@ suite('ReloadAction', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction when extension state is uninstalling', async () => { - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + test('Test Runtime State when extension state is uninstalling', async () => { + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -988,7 +988,7 @@ suite('ReloadAction', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction when extension is newly installed', async () => { + test('Test Runtime State when extension is newly installed', async () => { const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('b'))], @@ -996,7 +996,7 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -1010,10 +1010,10 @@ suite('ReloadAction', () => { didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }]); await promise; assert.ok(testObject.enabled); - assert.strictEqual(testObject.tooltip, `Please reload ${instantiationService.get(IProductService).nameLong} to enable this extension.`); + assert.strictEqual(testObject.tooltip, `Please restart extensions to enable this extension.`); }); - test('Test ReloadAction when extension is newly installed and reload is not required', async () => { + test('Test Runtime State when extension is newly installed and ext host restart is not required', async () => { const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('b'))], @@ -1021,7 +1021,7 @@ suite('ReloadAction', () => { canAddExtension: (extension) => true, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -1035,7 +1035,7 @@ suite('ReloadAction', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction when extension is installed and uninstalled', async () => { + test('Test Runtime State when extension is installed and uninstalled', async () => { instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('b'))], onDidChangeExtensions: Event.None, @@ -1043,7 +1043,7 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -1059,7 +1059,7 @@ suite('ReloadAction', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction when extension is uninstalled', async () => { + test('Test Runtime State when extension is uninstalled', async () => { instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('a', { version: '1.0.0' }))], onDidChangeExtensions: Event.None, @@ -1068,7 +1068,7 @@ suite('ReloadAction', () => { whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); instantiationService.set(IExtensionsWorkbenchService, disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService))); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -1078,10 +1078,10 @@ suite('ReloadAction', () => { uninstallEvent.fire({ identifier: local.identifier }); didUninstallEvent.fire({ identifier: local.identifier }); assert.ok(testObject.enabled); - assert.strictEqual(testObject.tooltip, `Please reload ${instantiationService.get(IProductService).nameLong} to complete the uninstallation of this extension.`); + assert.strictEqual(testObject.tooltip, `Please restart extensions to complete the uninstallation of this extension.`); }); - test('Test ReloadAction when extension is uninstalled and can be removed', async () => { + test('Test Runtime State when extension is uninstalled and can be removed', async () => { const local = aLocalExtension('a'); instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(local)], @@ -1090,7 +1090,7 @@ suite('ReloadAction', () => { canAddExtension: (extension) => true, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); const extensions = await instantiationService.get(IExtensionsWorkbenchService).queryLocal(); @@ -1101,7 +1101,7 @@ suite('ReloadAction', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction when extension is uninstalled and installed', async () => { + test('Test Runtime State when extension is uninstalled and installed', async () => { instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('a', { version: '1.0.0' }))], onDidChangeExtensions: Event.None, @@ -1109,7 +1109,7 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -1127,7 +1127,7 @@ suite('ReloadAction', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction when extension is updated while running', async () => { + test('Test Runtime State when extension is updated while running', async () => { instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('a', { version: '1.0.1' }))], onDidChangeExtensions: Event.None, @@ -1136,7 +1136,7 @@ suite('ReloadAction', () => { whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); instantiationService.set(IExtensionsWorkbenchService, disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService))); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a', { version: '1.0.1' }); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); @@ -1146,7 +1146,7 @@ suite('ReloadAction', () => { return new Promise(c => { disposables.add(testObject.onDidChange(() => { - if (testObject.enabled && testObject.tooltip === `Please reload ${instantiationService.get(IProductService).nameLong} to enable the updated extension.`) { + if (testObject.enabled && testObject.tooltip === `Please restart extensions to enable the updated extension.`) { c(); } })); @@ -1156,7 +1156,7 @@ suite('ReloadAction', () => { }); }); - test('Test ReloadAction when extension is updated when not running', async () => { + test('Test Runtime State when extension is updated when not running', async () => { instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('b'))], onDidChangeExtensions: Event.None, @@ -1166,7 +1166,7 @@ suite('ReloadAction', () => { }); const local = aLocalExtension('a', { version: '1.0.1' }); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -1180,7 +1180,7 @@ suite('ReloadAction', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction when extension is disabled when running', async () => { + test('Test Runtime State when extension is disabled when running', async () => { instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('a'))], onDidChangeExtensions: Event.None, @@ -1189,7 +1189,7 @@ suite('ReloadAction', () => { whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); instantiationService.set(IExtensionsWorkbenchService, disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService))); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a'); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); @@ -1200,10 +1200,10 @@ suite('ReloadAction', () => { await testObject.update(); assert.ok(testObject.enabled); - assert.strictEqual(`Please reload ${instantiationService.get(IProductService).nameLong} to disable this extension.`, testObject.tooltip); + assert.strictEqual(`Please restart extensions to disable this extension.`, testObject.tooltip); }); - test('Test ReloadAction when extension enablement is toggled when running', async () => { + test('Test Runtime State when extension enablement is toggled when running', async () => { instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('a', { version: '1.0.0' }))], onDidChangeExtensions: Event.None, @@ -1212,7 +1212,7 @@ suite('ReloadAction', () => { whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); instantiationService.set(IExtensionsWorkbenchService, disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService))); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a'); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); @@ -1224,7 +1224,7 @@ suite('ReloadAction', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction when extension is enabled when not running', async () => { + test('Test Runtime State when extension is enabled when not running', async () => { instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('b'))], onDidChangeExtensions: Event.None, @@ -1234,7 +1234,7 @@ suite('ReloadAction', () => { }); const local = aLocalExtension('a'); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -1243,10 +1243,10 @@ suite('ReloadAction', () => { await workbenchService.setEnablement(extensions[0], EnablementState.EnabledGlobally); await testObject.update(); assert.ok(testObject.enabled); - assert.strictEqual(`Please reload ${instantiationService.get(IProductService).nameLong} to enable this extension.`, testObject.tooltip); + assert.strictEqual(`Please restart extensions to enable this extension.`, testObject.tooltip); }); - test('Test ReloadAction when extension enablement is toggled when not running', async () => { + test('Test Runtime State when extension enablement is toggled when not running', async () => { instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('b'))], onDidChangeExtensions: Event.None, @@ -1256,7 +1256,7 @@ suite('ReloadAction', () => { }); const local = aLocalExtension('a'); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -1267,7 +1267,7 @@ suite('ReloadAction', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction when extension is updated when not running and enabled', async () => { + test('Test Runtime State when extension is updated when not running and enabled', async () => { instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('a'))], onDidChangeExtensions: Event.None, @@ -1277,7 +1277,7 @@ suite('ReloadAction', () => { }); const local = aLocalExtension('a', { version: '1.0.1' }); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -1290,10 +1290,10 @@ suite('ReloadAction', () => { await workbenchService.setEnablement(extensions[0], EnablementState.EnabledGlobally); await testObject.update(); assert.ok(testObject.enabled); - assert.strictEqual(`Please reload ${instantiationService.get(IProductService).nameLong} to enable this extension.`, testObject.tooltip); + assert.strictEqual(`Please restart extensions to enable this extension.`, testObject.tooltip); }); - test('Test ReloadAction when a localization extension is newly installed', async () => { + test('Test Runtime State when a localization extension is newly installed', async () => { instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('b'))], onDidChangeExtensions: Event.None, @@ -1301,7 +1301,7 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -1315,7 +1315,7 @@ suite('ReloadAction', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction when a localization extension is updated while running', async () => { + test('Test Runtime State when a localization extension is updated while running', async () => { instantiationService.stub(IExtensionService, { extensions: [toExtensionDescription(aLocalExtension('a', { version: '1.0.1' }))], onDidChangeExtensions: Event.None, @@ -1323,7 +1323,7 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a', { version: '1.0.1', contributes: { localizations: [{ languageId: 'de', translations: [] }] } }); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); @@ -1337,7 +1337,7 @@ suite('ReloadAction', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction when extension is not installed but extension from different server is installed and running', async () => { + test('Test Runtime State when extension is not installed but extension from different server is installed and running', async () => { // multi server setup const gallery = aGalleryExtension('a'); const localExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a') }); @@ -1355,7 +1355,7 @@ suite('ReloadAction', () => { const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -1366,7 +1366,7 @@ suite('ReloadAction', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction when extension is uninstalled but extension from different server is installed and running', async () => { + test('Test Runtime State when extension is uninstalled but extension from different server is installed and running', async () => { // multi server setup const gallery = aGalleryExtension('a'); const localExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a') }); @@ -1389,7 +1389,7 @@ suite('ReloadAction', () => { const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -1405,7 +1405,7 @@ suite('ReloadAction', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction when workspace extension is disabled on local server and installed in remote server', async () => { + test('Test Runtime State when workspace extension is disabled on local server and installed in remote server', async () => { // multi server setup const gallery = aGalleryExtension('a'); const remoteExtensionManagementService = createExtensionManagementService([]); @@ -1425,7 +1425,7 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -1441,10 +1441,10 @@ suite('ReloadAction', () => { await promise; assert.ok(testObject.enabled); - assert.strictEqual(testObject.tooltip, `Please reload ${instantiationService.get(IProductService).nameLong} to enable this extension.`); + assert.strictEqual(testObject.tooltip, `Please restart extensions to enable this extension.`); }); - test('Test ReloadAction when ui extension is disabled on remote server and installed in local server', async () => { + test('Test Runtime State when ui extension is disabled on remote server and installed in local server', async () => { // multi server setup const gallery = aGalleryExtension('a'); const localExtensionManagementService = createExtensionManagementService([]); @@ -1464,7 +1464,7 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -1480,10 +1480,10 @@ suite('ReloadAction', () => { await promise; assert.ok(testObject.enabled); - assert.strictEqual(testObject.tooltip, `Please reload ${instantiationService.get(IProductService).nameLong} to enable this extension.`); + assert.strictEqual(testObject.tooltip, `Please restart extensions to enable this extension.`); }); - test('Test ReloadAction for remote ui extension is disabled when it is installed and enabled in local server', async () => { + test('Test Runtime State for remote ui extension is disabled when it is installed and enabled in local server', async () => { // multi server setup const gallery = aGalleryExtension('a'); const localExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file('pub.a') }); @@ -1504,7 +1504,7 @@ suite('ReloadAction', () => { const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -1515,7 +1515,7 @@ suite('ReloadAction', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction for remote workspace+ui extension is enabled when it is installed and enabled in local server', async () => { + test('Test Runtime State for remote workspace+ui extension is enabled when it is installed and enabled in local server', async () => { // multi server setup const gallery = aGalleryExtension('a'); const localExtension = aLocalExtension('a', { extensionKind: ['workspace', 'ui'] }, { location: URI.file('pub.a') }); @@ -1536,7 +1536,7 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -1547,7 +1547,7 @@ suite('ReloadAction', () => { assert.ok(testObject.enabled); }); - test('Test ReloadAction for local ui+workspace extension is enabled when it is installed and enabled in remote server', async () => { + test('Test Runtime State for local ui+workspace extension is enabled when it is installed and enabled in remote server', async () => { // multi server setup const gallery = aGalleryExtension('a'); const localExtension = aLocalExtension('a', { extensionKind: ['ui', 'workspace'] }, { location: URI.file('pub.a') }); @@ -1568,7 +1568,7 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -1579,7 +1579,7 @@ suite('ReloadAction', () => { assert.ok(testObject.enabled); }); - test('Test ReloadAction for local workspace+ui extension is enabled when it is installed in both servers but running in local server', async () => { + test('Test Runtime State for local workspace+ui extension is enabled when it is installed in both servers but running in local server', async () => { // multi server setup const gallery = aGalleryExtension('a'); const localExtension = aLocalExtension('a', { extensionKind: ['workspace', 'ui'] }, { location: URI.file('pub.a') }); @@ -1600,7 +1600,7 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -1611,7 +1611,7 @@ suite('ReloadAction', () => { assert.ok(testObject.enabled); }); - test('Test ReloadAction for remote ui+workspace extension is enabled when it is installed on both servers but running in remote server', async () => { + test('Test Runtime State for remote ui+workspace extension is enabled when it is installed on both servers but running in remote server', async () => { // multi server setup const gallery = aGalleryExtension('a'); const localExtension = aLocalExtension('a', { extensionKind: ['ui', 'workspace'] }, { location: URI.file('pub.a') }); @@ -1632,7 +1632,7 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -1643,7 +1643,7 @@ suite('ReloadAction', () => { assert.ok(testObject.enabled); }); - test('Test ReloadAction when ui+workspace+web extension is installed in web and remote and running in remote', async () => { + test('Test Runtime State when ui+workspace+web extension is installed in web and remote and running in remote', async () => { // multi server setup const gallery = aGalleryExtension('a'); const webExtension = aLocalExtension('a', { extensionKind: ['ui', 'workspace'], 'browser': 'browser.js' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeUserData }) }); @@ -1660,7 +1660,7 @@ suite('ReloadAction', () => { const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -1671,7 +1671,7 @@ suite('ReloadAction', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction when workspace+ui+web extension is installed in web and local and running in local', async () => { + test('Test Runtime State when workspace+ui+web extension is installed in web and local and running in local', async () => { // multi server setup const gallery = aGalleryExtension('a'); const webExtension = aLocalExtension('a', { extensionKind: ['workspace', 'ui'], 'browser': 'browser.js' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeUserData }) }); @@ -1688,7 +1688,7 @@ suite('ReloadAction', () => { const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); - const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + const testObject: ExtensionsActions.ExtensionRuntimeStateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ExtensionRuntimeStateAction)); disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 2d93e41d0f0..0336227d2b7 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -864,9 +864,13 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } } - public async startExtensionHosts(): Promise { + public async startExtensionHosts(updates?: { toAdd: IExtension[]; toRemove: string[] }): Promise { this._doStopExtensionHosts(); + if (updates) { + await this._handleDeltaExtensions(new DeltaExtensionsQueueItem(updates.toAdd, updates.toRemove)); + } + const lock = await this._registry.acquireLock('startExtensionHosts'); try { this._startExtensionHostsIfNecessary(false, Array.from(this._allRequestedActivateEvents.keys())); diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 5c68e7480b7..1b9f047f5c0 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -519,9 +519,9 @@ export interface IExtensionService { stopExtensionHosts(reason: string): Promise; /** - * Starts the extension hosts. + * Starts the extension hosts. If updates are provided, the extension hosts are started with the given updates. */ - startExtensionHosts(): Promise; + startExtensionHosts(updates?: { readonly toAdd: readonly IExtension[]; readonly toRemove: readonly string[] }): Promise; /** * Modify the environment of the remote extension host From 351654be4d3201e07088593adbbfa1176ec4a3ba Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 7 Mar 2024 12:52:03 +0100 Subject: [PATCH 1826/1897] Close Action in header and hide title label when empty --- src/vs/platform/actions/common/actions.ts | 1 + .../parts/auxiliarybar/auxiliaryBarPart.ts | 42 ++++++++++++++- .../workbench/browser/parts/compositePart.ts | 10 ++-- .../browser/parts/media/paneCompositePart.css | 2 +- .../browser/parts/paneCompositePart.ts | 54 +++++++++++++------ .../browser/parts/panel/panelActions.ts | 9 +++- 6 files changed, 96 insertions(+), 22 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 98cd2259f98..3a99d5d8a9a 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -194,6 +194,7 @@ export class MenuId { static readonly SidebarTitle = new MenuId('SidebarTitle'); static readonly PanelTitle = new MenuId('PanelTitle'); static readonly AuxiliaryBarTitle = new MenuId('AuxiliaryBarTitle'); + static readonly AuxiliaryBarHeader = new MenuId('AuxiliaryBarHeader'); static readonly TerminalInstanceContext = new MenuId('TerminalInstanceContext'); static readonly TerminalEditorInstanceContext = new MenuId('TerminalEditorInstanceContext'); static readonly TerminalNewDropdownContext = new MenuId('TerminalNewDropdownContext'); diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts index e01dc69a69d..675f19df25e 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts @@ -26,11 +26,15 @@ import { LayoutPriority } from 'vs/base/browser/ui/splitview/splitview'; import { ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { AbstractPaneCompositePart, CompositeBarPosition } from 'vs/workbench/browser/parts/paneCompositePart'; -import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionsOrientation, IActionViewItem, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { IPaneCompositeBarOptions } from 'vs/workbench/browser/parts/paneCompositeBar'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { $ } from 'vs/base/browser/dom'; +import { HiddenItemStrategy, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { ActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { CompositeMenuActions } from 'vs/workbench/browser/actions'; export class AuxiliaryBarPart extends AbstractPaneCompositePart { @@ -210,6 +214,42 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { } } + protected override createHeaderArea() { + const headerArea = super.createHeaderArea(); + const globalHeaderContainer = $('.auxiliary-bar-global-header'); + + // Add auxillary header action + const menu = this.headerFooterCompositeBarDispoables.add(this.instantiationService.createInstance(CompositeMenuActions, MenuId.AuxiliaryBarHeader, undefined, undefined)); + + const toolBar = this.headerFooterCompositeBarDispoables.add(this.instantiationService.createInstance(WorkbenchToolBar, globalHeaderContainer, { + actionViewItemProvider: (action, options) => this.headerActionViewItemProvider(action, options), + orientation: ActionsOrientation.HORIZONTAL, + hiddenItemStrategy: HiddenItemStrategy.NoHide, + getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), + })); + + toolBar.setActions(prepareActions(menu.getPrimaryActions())); + this.headerFooterCompositeBarDispoables.add(menu.onDidChange(() => toolBar.setActions(prepareActions(menu.getPrimaryActions())))); + + headerArea.appendChild(globalHeaderContainer); + return headerArea; + } + + protected override getToolbarWidth(): number { + if (this.getCompositeBarPosition() === CompositeBarPosition.TOP) { + return 22; + } + return super.getToolbarWidth(); + } + + private headerActionViewItemProvider(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined { + if (action.id === ToggleAuxiliaryBarAction.ID) { + return this.instantiationService.createInstance(ActionViewItem, undefined, action, options); + } + + return undefined; + } + override toJSON(): object { return { type: Parts.AUXILIARYBAR_PART diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 4adf1f7d162..d1617f55cb5 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -70,7 +70,7 @@ export abstract class CompositePart extends Part { private activeComposite: Composite | undefined; private lastActiveCompositeId: string; private readonly instantiatedCompositeItems = new Map(); - private titleLabel: ICompositeTitleLabel | undefined; + protected titleLabel: ICompositeTitleLabel | undefined; private progressBar: ProgressBar | undefined; private contentAreaSize: Dimension | undefined; private readonly actionsListener = this._register(new MutableDisposable()); @@ -438,8 +438,12 @@ export abstract class CompositePart extends Part { }; } - protected createHeaderFooterCompositeBarArea(): HTMLElement { - return $('.composite.composite-bar-area'); + protected createHeaderArea(): HTMLElement { + return $('.composite'); + } + + protected createFooterArea(): HTMLElement { + return $('.composite'); } override updateStyles(): void { diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index 9e6e83c9645..2155798ed82 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -30,7 +30,7 @@ } .monaco-workbench .pane-composite-part > .header-or-footer .composite-bar-container { - width: 100%; + flex: 1; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-label.codicon-more, diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index fea45721bc8..d914587e771 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -115,7 +115,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart()); private compositeBarPosition: CompositeBarPosition | undefined = undefined; @@ -372,7 +372,12 @@ export abstract class AbstractPaneCompositePart extends CompositePart { - this.onCompositeBarAreaContextMenu(new StandardMouseEvent(getWindow(compositeBarArea), e)); + protected override createFooterArea(): HTMLElement { + const footerArea = super.createFooterArea(); + return this.createHeaderFooterCompositeBarArea(footerArea); + } + + protected createHeaderFooterCompositeBarArea(area: HTMLElement): HTMLElement { + if (this.headerFooterCompositeBarContainer) { + // A pane composite part has either a header or a footer, but not both + throw new Error('Header or Footer composite bar already exists'); + } + this.headerFooterCompositeBarContainer = area; + + this.headerFooterCompositeBarDispoables.add(addDisposableListener(area, EventType.CONTEXT_MENU, e => { + this.onCompositeBarAreaContextMenu(new StandardMouseEvent(getWindow(area), e)); })); - this.headerFooterCompositeBarDispoables.add(Gesture.addTarget(compositeBarArea)); - this.headerFooterCompositeBarDispoables.add(addDisposableListener(compositeBarArea, GestureEventType.Contextmenu, e => { - this.onCompositeBarAreaContextMenu(new StandardMouseEvent(getWindow(compositeBarArea), e)); + this.headerFooterCompositeBarDispoables.add(Gesture.addTarget(area)); + this.headerFooterCompositeBarDispoables.add(addDisposableListener(area, GestureEventType.Contextmenu, e => { + this.onCompositeBarAreaContextMenu(new StandardMouseEvent(getWindow(area), e)); })); - return compositeBarArea; + return area; } private removeFooterHeaderArea(header: boolean): void { @@ -528,15 +546,17 @@ export abstract class AbstractPaneCompositePart extends CompositePart Date: Thu, 7 Mar 2024 13:40:24 +0100 Subject: [PATCH 1827/1897] Add context menu to comments in comments panel to un/resolve conversation (#207056) Part of #206898 --- src/vs/editor/common/languages.ts | 2 +- src/vs/monaco.d.ts | 2 +- src/vs/platform/actions/common/actions.ts | 1 + .../api/browser/mainThreadComments.ts | 12 +- .../comments/browser/commentService.ts | 138 ++++++++-------- .../browser/commentThreadZoneWidget.ts | 14 +- .../comments/browser/commentsController.ts | 88 +++++----- .../contrib/comments/browser/commentsModel.ts | 30 ++-- .../comments/browser/commentsTreeViewer.ts | 150 ++++++++++++++++-- .../contrib/comments/browser/commentsView.ts | 32 ---- .../contrib/comments/browser/media/panel.css | 36 +++++ .../contrib/comments/common/commentModel.ts | 39 +++-- .../test/browser/commentsView.test.ts | 1 + .../browser/view/cellParts/cellComments.ts | 2 +- .../actions/common/menusExtensionPoint.ts | 6 + .../common/extensionsApiProposals.ts | 1 + ...oposed.contribCommentsViewThreadMenus.d.ts | 6 + 17 files changed, 353 insertions(+), 207 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.contribCommentsViewThreadMenus.d.ts diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 4d157bf5788..5af870d82ff 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1883,7 +1883,7 @@ export interface PendingCommentThread { body: string; range: IRange | undefined; uri: URI; - owner: string; + uniqueOwner: string; isReply: boolean; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 3391b58aa2e..7553f3a2623 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7838,7 +7838,7 @@ declare namespace monaco.languages { body: string; range: IRange | undefined; uri: Uri; - owner: string; + uniqueOwner: string; isReply: boolean; } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 98cd2259f98..b14c7d0b536 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -162,6 +162,7 @@ export class MenuId { static readonly CommentThreadCommentContext = new MenuId('CommentThreadCommentContext'); static readonly CommentTitle = new MenuId('CommentTitle'); static readonly CommentActions = new MenuId('CommentActions'); + static readonly CommentsViewThreadActions = new MenuId('CommentsViewThreadActions'); static readonly InteractiveToolbar = new MenuId('InteractiveToolbar'); static readonly InteractiveCellTitle = new MenuId('InteractiveCellTitle'); static readonly InteractiveCellDelete = new MenuId('InteractiveCellDelete'); diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 4e369eb19da..8896e0b6e1c 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -248,6 +248,10 @@ export class MainThreadCommentController implements ICommentController { return this._features; } + get owner() { + return this._id; + } + constructor( private readonly _proxy: ExtHostCommentsShape, private readonly _commentService: ICommentService, @@ -385,7 +389,7 @@ export class MainThreadCommentController implements ICommentController { async getDocumentComments(resource: URI, token: CancellationToken) { if (resource.scheme === Schemas.vscodeNotebookCell) { return { - owner: this._uniqueId, + uniqueOwner: this._uniqueId, label: this.label, threads: [], commentingRanges: { @@ -407,7 +411,7 @@ export class MainThreadCommentController implements ICommentController { const commentingRanges = await this._proxy.$provideCommentingRanges(this.handle, resource, token); return { - owner: this._uniqueId, + uniqueOwner: this._uniqueId, label: this.label, threads: ret, commentingRanges: { @@ -421,7 +425,7 @@ export class MainThreadCommentController implements ICommentController { async getNotebookComments(resource: URI, token: CancellationToken) { if (resource.scheme !== Schemas.vscodeNotebookCell) { return { - owner: this._uniqueId, + uniqueOwner: this._uniqueId, label: this.label, threads: [] }; @@ -436,7 +440,7 @@ export class MainThreadCommentController implements ICommentController { } return { - owner: this._uniqueId, + uniqueOwner: this._uniqueId, label: this.label, threads: ret }; diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index da253044635..accc000bdce 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -31,14 +31,14 @@ interface IResourceCommentThreadEvent { } export interface ICommentInfo extends CommentInfo { - owner: string; + uniqueOwner: string; label?: string; } export interface INotebookCommentInfo { extensionId?: string; threads: CommentThread[]; - owner: string; + uniqueOwner: string; label?: string; } @@ -49,7 +49,7 @@ export interface IWorkspaceCommentThreadsEvent { } export interface INotebookCommentThreadChangedEvent extends CommentThreadChangedEvent { - owner: string; + uniqueOwner: string; } export interface ICommentController { @@ -62,6 +62,7 @@ export interface ICommentController { }; options?: CommentOptions; contextValue?: string; + owner: string; createCommentThreadTemplate(resource: UriComponents, range: IRange | undefined): Promise; updateCommentThreadTemplate(threadHandle: number, range: IRange): Promise; deleteCommentThreadMain(commentThreadId: string): void; @@ -83,7 +84,7 @@ export interface ICommentService { readonly onDidUpdateNotebookCommentThreads: Event; readonly onDidChangeActiveEditingCommentThread: Event; readonly onDidChangeCurrentCommentThread: Event; - readonly onDidUpdateCommentingRanges: Event<{ owner: string }>; + readonly onDidUpdateCommentingRanges: Event<{ uniqueOwner: string }>; readonly onDidChangeActiveCommentingRange: Event<{ range: Range; commentingRangesInfo: CommentingRanges }>; readonly onDidSetDataProvider: Event; readonly onDidDeleteDataProvider: Event; @@ -91,28 +92,28 @@ export interface ICommentService { readonly isCommentingEnabled: boolean; readonly commentsModel: ICommentsModel; setDocumentComments(resource: URI, commentInfos: ICommentInfo[]): void; - setWorkspaceComments(owner: string, commentsByResource: CommentThread[]): void; - removeWorkspaceComments(owner: string): void; - registerCommentController(owner: string, commentControl: ICommentController): void; - unregisterCommentController(owner?: string): void; - getCommentController(owner: string): ICommentController | undefined; - createCommentThreadTemplate(owner: string, resource: URI, range: Range | undefined): Promise; - updateCommentThreadTemplate(owner: string, threadHandle: number, range: Range): Promise; - getCommentMenus(owner: string): CommentMenus; + setWorkspaceComments(uniqueOwner: string, commentsByResource: CommentThread[]): void; + removeWorkspaceComments(uniqueOwner: string): void; + registerCommentController(uniqueOwner: string, commentControl: ICommentController): void; + unregisterCommentController(uniqueOwner?: string): void; + getCommentController(uniqueOwner: string): ICommentController | undefined; + createCommentThreadTemplate(uniqueOwner: string, resource: URI, range: Range | undefined): Promise; + updateCommentThreadTemplate(uniqueOwner: string, threadHandle: number, range: Range): Promise; + getCommentMenus(uniqueOwner: string): CommentMenus; updateComments(ownerId: string, event: CommentThreadChangedEvent): void; updateNotebookComments(ownerId: string, event: CommentThreadChangedEvent): void; disposeCommentThread(ownerId: string, threadId: string): void; getDocumentComments(resource: URI): Promise<(ICommentInfo | null)[]>; getNotebookComments(resource: URI): Promise<(INotebookCommentInfo | null)[]>; updateCommentingRanges(ownerId: string, resourceHints?: CommentingRangeResourceHint): void; - hasReactionHandler(owner: string): boolean; - toggleReaction(owner: string, resource: URI, thread: CommentThread, comment: Comment, reaction: CommentReaction): Promise; + hasReactionHandler(uniqueOwner: string): boolean; + toggleReaction(uniqueOwner: string, resource: URI, thread: CommentThread, comment: Comment, reaction: CommentReaction): Promise; setActiveEditingCommentThread(commentThread: CommentThread | null): void; setCurrentCommentThread(commentThread: CommentThread | undefined): void; - setActiveCommentAndThread(owner: string, commentInfo: { thread: CommentThread; comment?: Comment } | undefined): Promise; + setActiveCommentAndThread(uniqueOwner: string, commentInfo: { thread: CommentThread; comment?: Comment } | undefined): Promise; enableCommenting(enable: boolean): void; registerContinueOnCommentProvider(provider: IContinueOnCommentProvider): IDisposable; - removeContinueOnComment(pendingComment: { range: IRange | undefined; uri: URI; owner: string; isReply?: boolean }): PendingCommentThread | undefined; + removeContinueOnComment(pendingComment: { range: IRange | undefined; uri: URI; uniqueOwner: string; isReply?: boolean }): PendingCommentThread | undefined; resourceHasCommentingRanges(resource: URI): boolean; } @@ -139,8 +140,8 @@ export class CommentService extends Disposable implements ICommentService { private readonly _onDidUpdateNotebookCommentThreads: Emitter = this._register(new Emitter()); readonly onDidUpdateNotebookCommentThreads: Event = this._onDidUpdateNotebookCommentThreads.event; - private readonly _onDidUpdateCommentingRanges: Emitter<{ owner: string }> = this._register(new Emitter<{ owner: string }>()); - readonly onDidUpdateCommentingRanges: Event<{ owner: string }> = this._onDidUpdateCommentingRanges.event; + private readonly _onDidUpdateCommentingRanges: Emitter<{ uniqueOwner: string }> = this._register(new Emitter<{ uniqueOwner: string }>()); + readonly onDidUpdateCommentingRanges: Event<{ uniqueOwner: string }> = this._onDidUpdateCommentingRanges.event; private readonly _onDidChangeActiveEditingCommentThread = this._register(new Emitter()); readonly onDidChangeActiveEditingCommentThread = this._onDidChangeActiveEditingCommentThread.event; @@ -165,7 +166,7 @@ export class CommentService extends Disposable implements ICommentService { private _isCommentingEnabled: boolean = true; private _workspaceHasCommenting: IContextKey; - private _continueOnComments = new Map(); // owner -> PendingCommentThread[] + private _continueOnComments = new Map(); // uniqueOwner -> PendingCommentThread[] private _continueOnCommentProviders = new Set(); private readonly _commentsModel: CommentsModel = this._register(new CommentsModel()); @@ -200,15 +201,16 @@ export class CommentService extends Disposable implements ICommentService { } this.logService.debug(`Comments: URIs of continue on comments from storage ${commentsToRestore.map(thread => thread.uri.toString()).join(', ')}.`); const changedOwners = this._addContinueOnComments(commentsToRestore, this._continueOnComments); - for (const owner of changedOwners) { - const control = this._commentControls.get(owner); + for (const uniqueOwner of changedOwners) { + const control = this._commentControls.get(uniqueOwner); if (!control) { continue; } const evt: ICommentThreadChangedEvent = { - owner, + uniqueOwner: uniqueOwner, + owner: control.owner, ownerLabel: control.label, - pending: this._continueOnComments.get(owner) || [], + pending: this._continueOnComments.get(uniqueOwner) || [], added: [], removed: [], changed: [] @@ -294,8 +296,8 @@ export class CommentService extends Disposable implements ICommentService { } private _lastActiveCommentController: ICommentController | undefined; - async setActiveCommentAndThread(owner: string, commentInfo: { thread: CommentThread; comment?: Comment } | undefined) { - const commentController = this._commentControls.get(owner); + async setActiveCommentAndThread(uniqueOwner: string, commentInfo: { thread: CommentThread; comment?: Comment } | undefined) { + const commentController = this._commentControls.get(uniqueOwner); if (!commentController) { return; @@ -312,8 +314,8 @@ export class CommentService extends Disposable implements ICommentService { this._onDidSetResourceCommentInfos.fire({ resource, commentInfos }); } - private setModelThreads(ownerId: string, ownerLabel: string, commentThreads: CommentThread[]) { - this._commentsModel.setCommentThreads(ownerId, ownerLabel, commentThreads); + private setModelThreads(ownerId: string, owner: string, ownerLabel: string, commentThreads: CommentThread[]) { + this._commentsModel.setCommentThreads(ownerId, owner, ownerLabel, commentThreads); this._onDidSetAllCommentThreads.fire({ ownerId, ownerLabel, commentThreads }); } @@ -322,45 +324,45 @@ export class CommentService extends Disposable implements ICommentService { this._onDidUpdateCommentThreads.fire(event); } - setWorkspaceComments(owner: string, commentsByResource: CommentThread[]): void { + setWorkspaceComments(uniqueOwner: string, commentsByResource: CommentThread[]): void { if (commentsByResource.length) { this._workspaceHasCommenting.set(true); } - const control = this._commentControls.get(owner); + const control = this._commentControls.get(uniqueOwner); if (control) { - this.setModelThreads(owner, control.label, commentsByResource); + this.setModelThreads(uniqueOwner, control.owner, control.label, commentsByResource); } } - removeWorkspaceComments(owner: string): void { - const control = this._commentControls.get(owner); + removeWorkspaceComments(uniqueOwner: string): void { + const control = this._commentControls.get(uniqueOwner); if (control) { - this.setModelThreads(owner, control.label, []); + this.setModelThreads(uniqueOwner, control.owner, control.label, []); } } - registerCommentController(owner: string, commentControl: ICommentController): void { - this._commentControls.set(owner, commentControl); + registerCommentController(uniqueOwner: string, commentControl: ICommentController): void { + this._commentControls.set(uniqueOwner, commentControl); this._onDidSetDataProvider.fire(); } - unregisterCommentController(owner?: string): void { - if (owner) { - this._commentControls.delete(owner); + unregisterCommentController(uniqueOwner?: string): void { + if (uniqueOwner) { + this._commentControls.delete(uniqueOwner); } else { this._commentControls.clear(); } - this._commentsModel.deleteCommentsByOwner(owner); - this._onDidDeleteDataProvider.fire(owner); + this._commentsModel.deleteCommentsByOwner(uniqueOwner); + this._onDidDeleteDataProvider.fire(uniqueOwner); } - getCommentController(owner: string): ICommentController | undefined { - return this._commentControls.get(owner); + getCommentController(uniqueOwner: string): ICommentController | undefined { + return this._commentControls.get(uniqueOwner); } - async createCommentThreadTemplate(owner: string, resource: URI, range: Range | undefined): Promise { - const commentController = this._commentControls.get(owner); + async createCommentThreadTemplate(uniqueOwner: string, resource: URI, range: Range | undefined): Promise { + const commentController = this._commentControls.get(uniqueOwner); if (!commentController) { return; @@ -369,8 +371,8 @@ export class CommentService extends Disposable implements ICommentService { return commentController.createCommentThreadTemplate(resource, range); } - async updateCommentThreadTemplate(owner: string, threadHandle: number, range: Range) { - const commentController = this._commentControls.get(owner); + async updateCommentThreadTemplate(uniqueOwner: string, threadHandle: number, range: Range) { + const commentController = this._commentControls.get(uniqueOwner); if (!commentController) { return; @@ -379,31 +381,31 @@ export class CommentService extends Disposable implements ICommentService { await commentController.updateCommentThreadTemplate(threadHandle, range); } - disposeCommentThread(owner: string, threadId: string) { - const controller = this.getCommentController(owner); + disposeCommentThread(uniqueOwner: string, threadId: string) { + const controller = this.getCommentController(uniqueOwner); controller?.deleteCommentThreadMain(threadId); } - getCommentMenus(owner: string): CommentMenus { - if (this._commentMenus.get(owner)) { - return this._commentMenus.get(owner)!; + getCommentMenus(uniqueOwner: string): CommentMenus { + if (this._commentMenus.get(uniqueOwner)) { + return this._commentMenus.get(uniqueOwner)!; } const menu = this.instantiationService.createInstance(CommentMenus); - this._commentMenus.set(owner, menu); + this._commentMenus.set(uniqueOwner, menu); return menu; } updateComments(ownerId: string, event: CommentThreadChangedEvent): void { const control = this._commentControls.get(ownerId); if (control) { - const evt: ICommentThreadChangedEvent = Object.assign({}, event, { owner: ownerId, ownerLabel: control.label }); + const evt: ICommentThreadChangedEvent = Object.assign({}, event, { uniqueOwner: ownerId, ownerLabel: control.label, owner: control.owner }); this.updateModelThreads(evt); } } updateNotebookComments(ownerId: string, event: CommentThreadChangedEvent): void { - const evt: INotebookCommentThreadChangedEvent = Object.assign({}, event, { owner: ownerId }); + const evt: INotebookCommentThreadChangedEvent = Object.assign({}, event, { uniqueOwner: ownerId }); this._onDidUpdateNotebookCommentThreads.fire(evt); } @@ -414,11 +416,11 @@ export class CommentService extends Disposable implements ICommentService { } } this._workspaceHasCommenting.set(true); - this._onDidUpdateCommentingRanges.fire({ owner: ownerId }); + this._onDidUpdateCommentingRanges.fire({ uniqueOwner: ownerId }); } - async toggleReaction(owner: string, resource: URI, thread: CommentThread, comment: Comment, reaction: CommentReaction): Promise { - const commentController = this._commentControls.get(owner); + async toggleReaction(uniqueOwner: string, resource: URI, thread: CommentThread, comment: Comment, reaction: CommentReaction): Promise { + const commentController = this._commentControls.get(uniqueOwner); if (commentController) { return commentController.toggleReaction(resource, thread, comment, reaction, CancellationToken.None); @@ -427,8 +429,8 @@ export class CommentService extends Disposable implements ICommentService { } } - hasReactionHandler(owner: string): boolean { - const commentProvider = this._commentControls.get(owner); + hasReactionHandler(uniqueOwner: string): boolean { + const commentProvider = this._commentControls.get(uniqueOwner); if (commentProvider) { return !!commentProvider.features.reactionHandler; @@ -447,10 +449,10 @@ export class CommentService extends Disposable implements ICommentService { // This can happen because continue on comments are stored separately from local un-submitted comments. for (const documentCommentThread of documentComments.threads) { if (documentCommentThread.comments?.length === 0 && documentCommentThread.range) { - this.removeContinueOnComment({ range: documentCommentThread.range, uri: resource, owner: documentComments.owner }); + this.removeContinueOnComment({ range: documentCommentThread.range, uri: resource, uniqueOwner: documentComments.uniqueOwner }); } } - const pendingComments = this._continueOnComments.get(documentComments.owner); + const pendingComments = this._continueOnComments.get(documentComments.uniqueOwner); documentComments.pendingCommentThreads = pendingComments?.filter(pendingComment => pendingComment.uri.toString() === resource.toString()); return documentComments; }) @@ -495,8 +497,8 @@ export class CommentService extends Disposable implements ICommentService { this.storageService.store(CONTINUE_ON_COMMENTS, commentsToSave, StorageScope.WORKSPACE, StorageTarget.USER); } - removeContinueOnComment(pendingComment: { range: IRange; uri: URI; owner: string; isReply?: boolean }): PendingCommentThread | undefined { - const pendingComments = this._continueOnComments.get(pendingComment.owner); + removeContinueOnComment(pendingComment: { range: IRange; uri: URI; uniqueOwner: string; isReply?: boolean }): PendingCommentThread | undefined { + const pendingComments = this._continueOnComments.get(pendingComment.uniqueOwner); if (pendingComments) { const commentIndex = pendingComments.findIndex(comment => comment.uri.toString() === pendingComment.uri.toString() && Range.equalsRange(comment.range, pendingComment.range) && (pendingComment.isReply === undefined || comment.isReply === pendingComment.isReply)); if (commentIndex > -1) { @@ -509,14 +511,14 @@ export class CommentService extends Disposable implements ICommentService { private _addContinueOnComments(pendingComments: PendingCommentThread[], map: Map): Set { const changedOwners = new Set(); for (const pendingComment of pendingComments) { - if (!map.has(pendingComment.owner)) { - map.set(pendingComment.owner, [pendingComment]); - changedOwners.add(pendingComment.owner); + if (!map.has(pendingComment.uniqueOwner)) { + map.set(pendingComment.uniqueOwner, [pendingComment]); + changedOwners.add(pendingComment.uniqueOwner); } else { - const commentsForOwner = map.get(pendingComment.owner)!; + const commentsForOwner = map.get(pendingComment.uniqueOwner)!; if (commentsForOwner.every(comment => (comment.uri.toString() !== pendingComment.uri.toString()) || !Range.equalsRange(comment.range, pendingComment.range))) { commentsForOwner.push(pendingComment); - changedOwners.add(pendingComment.owner); + changedOwners.add(pendingComment.uniqueOwner); } } } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index e5ae9040d50..baa3438345d 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -105,8 +105,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private _contextKeyService: IContextKeyService; private _scopedInstantiationService: IInstantiationService; - public get owner(): string { - return this._owner; + public get uniqueOwner(): string { + return this._uniqueOwner; } public get commentThread(): languages.CommentThread { return this._commentThread; @@ -120,7 +120,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget constructor( editor: ICodeEditor, - private _owner: string, + private _uniqueOwner: string, private _commentThread: languages.CommentThread, private _pendingComment: string | undefined, private _pendingEdits: { [key: number]: string } | undefined, @@ -137,7 +137,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget [IContextKeyService, this._contextKeyService] )); - const controller = this.commentService.getCommentController(this._owner); + const controller = this.commentService.getCommentController(this._uniqueOwner); if (controller) { this._commentOptions = controller.options; } @@ -229,7 +229,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget CommentThreadWidget, container, this.editor, - this._owner, + this._uniqueOwner, this.editor.getModel()!.uri, this._contextKeyService, this._scopedInstantiationService, @@ -258,7 +258,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } else { range = new Range(originalRange.startLineNumber, originalRange.startColumn, originalRange.endLineNumber, originalRange.endColumn); } - await this.commentService.updateCommentThreadTemplate(this.owner, this._commentThread.commentThreadHandle, range); + await this.commentService.updateCommentThreadTemplate(this.uniqueOwner, this._commentThread.commentThreadHandle, range); } } }, @@ -281,7 +281,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private deleteCommentThread(): void { this.dispose(); - this.commentService.disposeCommentThread(this.owner, this._commentThread.threadId); + this.commentService.disposeCommentThread(this.uniqueOwner, this._commentThread.threadId); } public collapse() { diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index 35c0cd6eb7f..e83cb560c6c 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -203,10 +203,10 @@ class CommentingRangeDecorator { intersectingEmphasisRange = new Range(intersectingSelectionRange.endLineNumber, 1, intersectingSelectionRange.endLineNumber, 1); intersectingSelectionRange = new Range(intersectingSelectionRange.startLineNumber, 1, intersectingSelectionRange.endLineNumber - 1, 1); } - commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, intersectingSelectionRange, this.multilineDecorationOptions, info.commentingRanges, true)); + commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.uniqueOwner, info.extensionId, info.label, intersectingSelectionRange, this.multilineDecorationOptions, info.commentingRanges, true)); if (!this._lineHasThread(editor, intersectingEmphasisRange)) { - commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, intersectingEmphasisRange, this.hoverDecorationOptions, info.commentingRanges, true)); + commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.uniqueOwner, info.extensionId, info.label, intersectingEmphasisRange, this.hoverDecorationOptions, info.commentingRanges, true)); } const beforeRangeEndLine = Math.min(intersectingEmphasisRange.startLineNumber, intersectingSelectionRange.startLineNumber) - 1; @@ -215,27 +215,27 @@ class CommentingRangeDecorator { const hasAfterRange = rangeObject.endLineNumber >= afterRangeStartLine; if (hasBeforeRange) { const beforeRange = new Range(range.startLineNumber, 1, beforeRangeEndLine, 1); - commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, beforeRange, this.decorationOptions, info.commentingRanges, true)); + commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.uniqueOwner, info.extensionId, info.label, beforeRange, this.decorationOptions, info.commentingRanges, true)); } if (hasAfterRange) { const afterRange = new Range(afterRangeStartLine, 1, range.endLineNumber, 1); - commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, afterRange, this.decorationOptions, info.commentingRanges, true)); + commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.uniqueOwner, info.extensionId, info.label, afterRange, this.decorationOptions, info.commentingRanges, true)); } } else if ((rangeObject.startLineNumber <= emphasisLine) && (emphasisLine <= rangeObject.endLineNumber)) { if (rangeObject.startLineNumber < emphasisLine) { const beforeRange = new Range(range.startLineNumber, 1, emphasisLine - 1, 1); - commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, beforeRange, this.decorationOptions, info.commentingRanges, true)); + commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.uniqueOwner, info.extensionId, info.label, beforeRange, this.decorationOptions, info.commentingRanges, true)); } const emphasisRange = new Range(emphasisLine, 1, emphasisLine, 1); if (!this._lineHasThread(editor, emphasisRange)) { - commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, emphasisRange, this.hoverDecorationOptions, info.commentingRanges, true)); + commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.uniqueOwner, info.extensionId, info.label, emphasisRange, this.hoverDecorationOptions, info.commentingRanges, true)); } if (emphasisLine < rangeObject.endLineNumber) { const afterRange = new Range(emphasisLine + 1, 1, range.endLineNumber, 1); - commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, afterRange, this.decorationOptions, info.commentingRanges, true)); + commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.uniqueOwner, info.extensionId, info.label, afterRange, this.decorationOptions, info.commentingRanges, true)); } } else { - commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, this.decorationOptions, info.commentingRanges)); + commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.uniqueOwner, info.extensionId, info.label, range, this.decorationOptions, info.commentingRanges)); } }); } @@ -274,7 +274,7 @@ class CommentingRangeDecorator { return foundInfos.map(foundInfo => { return { action: { - ownerId: foundInfo.owner, + ownerId: foundInfo.uniqueOwner, extensionId: foundInfo.extensionId, label: foundInfo.label, commentingRangesInfo: foundInfo.commentingRanges @@ -290,7 +290,7 @@ class CommentingRangeDecorator { for (const decoration of this.commentingRangeDecorations) { const range = decoration.getActiveRange(); if (range && this.areRangesIntersectingOrTouchingByLine(range, commentRange)) { - // We can have several commenting ranges that match from the same owner because of how + // We can have several commenting ranges that match from the same uniqueOwner because of how // the line hover and selection decoration is done. // The ranges must be merged so that we can see if the new commentRange fits within them. const action = decoration.getCommentAction(); @@ -383,7 +383,7 @@ export class CommentController implements IEditorContribution { private _computeCommentingRangePromise!: CancelablePromise | null; private _computeCommentingRangeScheduler!: Delayer> | null; private _pendingNewCommentCache: { [key: string]: { [key: string]: string } }; - private _pendingEditsCache: { [key: string]: { [key: string]: { [key: number]: string } } }; // owner -> threadId -> uniqueIdInThread -> pending comment + private _pendingEditsCache: { [key: string]: { [key: string]: { [key: number]: string } } }; // uniqueOwner -> threadId -> uniqueIdInThread -> pending comment private _inProcessContinueOnComments: Map = new Map(); private _editorDisposables: IDisposable[] = []; private _activeCursorHasCommentingRange: IContextKey; @@ -496,7 +496,7 @@ export class CommentController implements IEditorContribution { if (pendingNewComment !== lastCommentBody) { pendingComments.push({ - owner: zone.owner, + uniqueOwner: zone.uniqueOwner, uri: zone.editor.getModel()!.uri, range: zone.commentThread.range, body: pendingNewComment, @@ -824,7 +824,7 @@ export class CommentController implements IEditorContribution { await this._computePromise; } - const commentInfo = this._commentInfos.filter(info => info.owner === e.owner); + const commentInfo = this._commentInfos.filter(info => info.uniqueOwner === e.uniqueOwner); if (!commentInfo || !commentInfo.length) { return; } @@ -835,14 +835,14 @@ export class CommentController implements IEditorContribution { const pending = e.pending.filter(pending => pending.uri.toString() === editorURI.toString()); removed.forEach(thread => { - const matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId && zoneWidget.commentThread.threadId !== ''); + const matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.uniqueOwner === e.uniqueOwner && zoneWidget.commentThread.threadId === thread.threadId && zoneWidget.commentThread.threadId !== ''); if (matchedZones.length) { const matchedZone = matchedZones[0]; const index = this._commentWidgets.indexOf(matchedZone); this._commentWidgets.splice(index, 1); matchedZone.dispose(); } - const infosThreads = this._commentInfos.filter(info => info.owner === e.owner)[0].threads; + const infosThreads = this._commentInfos.filter(info => info.uniqueOwner === e.uniqueOwner)[0].threads; for (let i = 0; i < infosThreads.length; i++) { if (infosThreads[i] === thread) { infosThreads.splice(i, 1); @@ -852,7 +852,7 @@ export class CommentController implements IEditorContribution { }); changed.forEach(thread => { - const matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId); + const matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.uniqueOwner === e.uniqueOwner && zoneWidget.commentThread.threadId === thread.threadId); if (matchedZones.length) { const matchedZone = matchedZones[0]; matchedZone.update(thread); @@ -860,19 +860,19 @@ export class CommentController implements IEditorContribution { } }); for (const thread of added) { - const matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId); + const matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.uniqueOwner === e.uniqueOwner && zoneWidget.commentThread.threadId === thread.threadId); if (matchedZones.length) { return; } - const matchedNewCommentThreadZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.commentThreadHandle === -1 && Range.equalsRange(zoneWidget.commentThread.range, thread.range)); + const matchedNewCommentThreadZones = this._commentWidgets.filter(zoneWidget => zoneWidget.uniqueOwner === e.uniqueOwner && zoneWidget.commentThread.commentThreadHandle === -1 && Range.equalsRange(zoneWidget.commentThread.range, thread.range)); if (matchedNewCommentThreadZones.length) { matchedNewCommentThreadZones[0].update(thread); return; } - const continueOnCommentIndex = this._inProcessContinueOnComments.get(e.owner)?.findIndex(pending => { + const continueOnCommentIndex = this._inProcessContinueOnComments.get(e.uniqueOwner)?.findIndex(pending => { if (pending.range === undefined) { return thread.range === undefined; } else { @@ -881,14 +881,14 @@ export class CommentController implements IEditorContribution { }); let continueOnCommentText: string | undefined; if ((continueOnCommentIndex !== undefined) && continueOnCommentIndex >= 0) { - continueOnCommentText = this._inProcessContinueOnComments.get(e.owner)?.splice(continueOnCommentIndex, 1)[0].body; + continueOnCommentText = this._inProcessContinueOnComments.get(e.uniqueOwner)?.splice(continueOnCommentIndex, 1)[0].body; } - const pendingCommentText = (this._pendingNewCommentCache[e.owner] && this._pendingNewCommentCache[e.owner][thread.threadId]) + const pendingCommentText = (this._pendingNewCommentCache[e.uniqueOwner] && this._pendingNewCommentCache[e.uniqueOwner][thread.threadId]) ?? continueOnCommentText; - const pendingEdits = this._pendingEditsCache[e.owner] && this._pendingEditsCache[e.owner][thread.threadId]; - this.displayCommentThread(e.owner, thread, pendingCommentText, pendingEdits); - this._commentInfos.filter(info => info.owner === e.owner)[0].threads.push(thread); + const pendingEdits = this._pendingEditsCache[e.uniqueOwner] && this._pendingEditsCache[e.uniqueOwner][thread.threadId]; + this.displayCommentThread(e.uniqueOwner, thread, pendingCommentText, pendingEdits); + this._commentInfos.filter(info => info.uniqueOwner === e.uniqueOwner)[0].threads.push(thread); this.tryUpdateReservedSpace(); } @@ -902,12 +902,12 @@ export class CommentController implements IEditorContribution { } private async resumePendingComment(editorURI: URI, thread: languages.PendingCommentThread) { - const matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === thread.owner && Range.lift(zoneWidget.commentThread.range)?.equalsRange(thread.range)); + const matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.uniqueOwner === thread.uniqueOwner && Range.lift(zoneWidget.commentThread.range)?.equalsRange(thread.range)); if (thread.isReply && matchedZones.length) { - this.commentService.removeContinueOnComment({ owner: thread.owner, uri: editorURI, range: thread.range, isReply: true }); + this.commentService.removeContinueOnComment({ uniqueOwner: thread.uniqueOwner, uri: editorURI, range: thread.range, isReply: true }); matchedZones[0].setPendingComment(thread.body); } else if (matchedZones.length) { - this.commentService.removeContinueOnComment({ owner: thread.owner, uri: editorURI, range: thread.range, isReply: false }); + this.commentService.removeContinueOnComment({ uniqueOwner: thread.uniqueOwner, uri: editorURI, range: thread.range, isReply: false }); const existingPendingComment = matchedZones[0].getPendingComments().newComment; // We need to try to reconcile the existing pending comment with the incoming pending comment let pendingComment: string; @@ -920,15 +920,15 @@ export class CommentController implements IEditorContribution { } matchedZones[0].setPendingComment(pendingComment); } else if (!thread.isReply) { - const threadStillAvailable = this.commentService.removeContinueOnComment({ owner: thread.owner, uri: editorURI, range: thread.range, isReply: false }); + const threadStillAvailable = this.commentService.removeContinueOnComment({ uniqueOwner: thread.uniqueOwner, uri: editorURI, range: thread.range, isReply: false }); if (!threadStillAvailable) { return; } - if (!this._inProcessContinueOnComments.has(thread.owner)) { - this._inProcessContinueOnComments.set(thread.owner, []); + if (!this._inProcessContinueOnComments.has(thread.uniqueOwner)) { + this._inProcessContinueOnComments.set(thread.uniqueOwner, []); } - this._inProcessContinueOnComments.get(thread.owner)?.push(thread); - await this.commentService.createCommentThreadTemplate(thread.owner, thread.uri, thread.range ? Range.lift(thread.range) : undefined); + this._inProcessContinueOnComments.get(thread.uniqueOwner)?.push(thread); + await this.commentService.createCommentThreadTemplate(thread.uniqueOwner, thread.uri, thread.range ? Range.lift(thread.range) : undefined); } } @@ -968,7 +968,7 @@ export class CommentController implements IEditorContribution { return undefined; } - private displayCommentThread(owner: string, thread: languages.CommentThread, pendingComment: string | undefined, pendingEdits: { [key: number]: string } | undefined): void { + private displayCommentThread(uniqueOwner: string, thread: languages.CommentThread, pendingComment: string | undefined, pendingEdits: { [key: number]: string } | undefined): void { const editor = this.editor?.getModel(); if (!editor) { return; @@ -979,9 +979,9 @@ export class CommentController implements IEditorContribution { let continueOnCommentReply: languages.PendingCommentThread | undefined; if (thread.range && !pendingComment) { - continueOnCommentReply = this.commentService.removeContinueOnComment({ owner, uri: editor.uri, range: thread.range, isReply: true }); + continueOnCommentReply = this.commentService.removeContinueOnComment({ uniqueOwner, uri: editor.uri, range: thread.range, isReply: true }); } - const zoneWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, owner, thread, pendingComment ?? continueOnCommentReply?.body, pendingEdits); + const zoneWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, uniqueOwner, thread, pendingComment ?? continueOnCommentReply?.body, pendingEdits); zoneWidget.display(thread.range); this._commentWidgets.push(zoneWidget); this.openCommentsView(thread); @@ -1259,8 +1259,8 @@ export class CommentController implements IEditorContribution { hasCommentingRanges = true; } - const providerCacheStore = this._pendingNewCommentCache[info.owner]; - const providerEditsCacheStore = this._pendingEditsCache[info.owner]; + const providerCacheStore = this._pendingNewCommentCache[info.uniqueOwner]; + const providerEditsCacheStore = this._pendingEditsCache[info.uniqueOwner]; info.threads = info.threads.filter(thread => !thread.isDisposed); info.threads.forEach(thread => { let pendingComment: string | undefined = undefined; @@ -1273,7 +1273,7 @@ export class CommentController implements IEditorContribution { pendingEdits = providerEditsCacheStore[thread.threadId]; } - this.displayCommentThread(info.owner, thread, pendingComment, pendingEdits); + this.displayCommentThread(info.uniqueOwner, thread, pendingComment, pendingEdits); }); for (const thread of info.pendingCommentThreads ?? []) { this.resumePendingComment(this.editor!.getModel()!.uri, thread); @@ -1303,7 +1303,7 @@ export class CommentController implements IEditorContribution { this._commentWidgets.forEach(zone => { const pendingComments = zone.getPendingComments(); const pendingNewComment = pendingComments.newComment; - const providerNewCommentCacheStore = this._pendingNewCommentCache[zone.owner]; + const providerNewCommentCacheStore = this._pendingNewCommentCache[zone.uniqueOwner]; let lastCommentBody; if (zone.commentThread.comments && zone.commentThread.comments.length) { @@ -1316,10 +1316,10 @@ export class CommentController implements IEditorContribution { } if (pendingNewComment && (pendingNewComment !== lastCommentBody)) { if (!providerNewCommentCacheStore) { - this._pendingNewCommentCache[zone.owner] = {}; + this._pendingNewCommentCache[zone.uniqueOwner] = {}; } - this._pendingNewCommentCache[zone.owner][zone.commentThread.threadId] = pendingNewComment; + this._pendingNewCommentCache[zone.uniqueOwner][zone.commentThread.threadId] = pendingNewComment; } else { if (providerNewCommentCacheStore) { delete providerNewCommentCacheStore[zone.commentThread.threadId]; @@ -1327,12 +1327,12 @@ export class CommentController implements IEditorContribution { } const pendingEdits = pendingComments.edits; - const providerEditsCacheStore = this._pendingEditsCache[zone.owner]; + const providerEditsCacheStore = this._pendingEditsCache[zone.uniqueOwner]; if (Object.keys(pendingEdits).length > 0) { if (!providerEditsCacheStore) { - this._pendingEditsCache[zone.owner] = {}; + this._pendingEditsCache[zone.uniqueOwner] = {}; } - this._pendingEditsCache[zone.owner][zone.commentThread.threadId] = pendingEdits; + this._pendingEditsCache[zone.uniqueOwner][zone.commentThread.threadId] = pendingEdits; } else if (providerEditsCacheStore) { delete providerEditsCacheStore[zone.commentThread.threadId]; } diff --git a/src/vs/workbench/contrib/comments/browser/commentsModel.ts b/src/vs/workbench/contrib/comments/browser/commentsModel.ts index 6d345350e83..d0701d5f344 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsModel.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsModel.ts @@ -43,15 +43,15 @@ export class CommentsModel extends Disposable implements ICommentsModel { }); } - public setCommentThreads(owner: string, ownerLabel: string, commentThreads: CommentThread[]): void { - this.commentThreadsMap.set(owner, { ownerLabel, resourceWithCommentThreads: this.groupByResource(owner, commentThreads) }); + public setCommentThreads(uniqueOwner: string, owner: string, ownerLabel: string, commentThreads: CommentThread[]): void { + this.commentThreadsMap.set(uniqueOwner, { ownerLabel, resourceWithCommentThreads: this.groupByResource(uniqueOwner, owner, commentThreads) }); this.updateResourceCommentThreads(); } - public deleteCommentsByOwner(owner?: string): void { - if (owner) { - const existingOwner = this.commentThreadsMap.get(owner); - this.commentThreadsMap.set(owner, { ownerLabel: existingOwner?.ownerLabel, resourceWithCommentThreads: [] }); + public deleteCommentsByOwner(uniqueOwner?: string): void { + if (uniqueOwner) { + const existingOwner = this.commentThreadsMap.get(uniqueOwner); + this.commentThreadsMap.set(uniqueOwner, { ownerLabel: existingOwner?.ownerLabel, resourceWithCommentThreads: [] }); } else { this.commentThreadsMap.clear(); } @@ -59,9 +59,9 @@ export class CommentsModel extends Disposable implements ICommentsModel { } public updateCommentThreads(event: ICommentThreadChangedEvent): boolean { - const { owner, ownerLabel, removed, changed, added } = event; + const { uniqueOwner, owner, ownerLabel, removed, changed, added } = event; - const threadsForOwner = this.commentThreadsMap.get(owner)?.resourceWithCommentThreads || []; + const threadsForOwner = this.commentThreadsMap.get(uniqueOwner)?.resourceWithCommentThreads || []; removed.forEach(thread => { // Find resource that has the comment thread @@ -91,9 +91,9 @@ export class CommentsModel extends Disposable implements ICommentsModel { // Find comment node on resource that is that thread and replace it const index = matchingResourceData.commentThreads.findIndex((commentThread) => commentThread.threadId === thread.threadId); if (index >= 0) { - matchingResourceData.commentThreads[index] = ResourceWithCommentThreads.createCommentNode(owner, URI.parse(matchingResourceData.id), thread); + matchingResourceData.commentThreads[index] = ResourceWithCommentThreads.createCommentNode(uniqueOwner, owner, URI.parse(matchingResourceData.id), thread); } else if (thread.comments && thread.comments.length) { - matchingResourceData.commentThreads.push(ResourceWithCommentThreads.createCommentNode(owner, URI.parse(matchingResourceData.id), thread)); + matchingResourceData.commentThreads.push(ResourceWithCommentThreads.createCommentNode(uniqueOwner, owner, URI.parse(matchingResourceData.id), thread)); } }); @@ -102,14 +102,14 @@ export class CommentsModel extends Disposable implements ICommentsModel { if (existingResource.length) { const resource = existingResource[0]; if (thread.comments && thread.comments.length) { - resource.commentThreads.push(ResourceWithCommentThreads.createCommentNode(owner, resource.resource, thread)); + resource.commentThreads.push(ResourceWithCommentThreads.createCommentNode(uniqueOwner, owner, resource.resource, thread)); } } else { - threadsForOwner.push(new ResourceWithCommentThreads(owner, URI.parse(thread.resource!), [thread])); + threadsForOwner.push(new ResourceWithCommentThreads(uniqueOwner, owner, URI.parse(thread.resource!), [thread])); } }); - this.commentThreadsMap.set(owner, { ownerLabel, resourceWithCommentThreads: threadsForOwner }); + this.commentThreadsMap.set(uniqueOwner, { ownerLabel, resourceWithCommentThreads: threadsForOwner }); this.updateResourceCommentThreads(); return removed.length > 0 || changed.length > 0 || added.length > 0; @@ -127,11 +127,11 @@ export class CommentsModel extends Disposable implements ICommentsModel { } } - private groupByResource(owner: string, commentThreads: CommentThread[]): ResourceWithCommentThreads[] { + private groupByResource(uniqueOwner: string, owner: string, commentThreads: CommentThread[]): ResourceWithCommentThreads[] { const resourceCommentThreads: ResourceWithCommentThreads[] = []; const commentThreadsByResource = new Map(); for (const group of groupBy(commentThreads, CommentsModel._compareURIs)) { - commentThreadsByResource.set(group[0].resource!, new ResourceWithCommentThreads(owner, URI.parse(group[0].resource!), group)); + commentThreadsByResource.set(group[0].resource!, new ResourceWithCommentThreads(uniqueOwner, owner, URI.parse(group[0].resource!), group)); } commentThreadsByResource.forEach((v, i, m) => { diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 451bc210789..df131acad4b 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -10,7 +10,7 @@ import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; import { CommentNode, ResourceWithCommentThreads } from 'vs/workbench/contrib/comments/common/commentModel'; -import { ITreeFilter, ITreeNode, TreeFilterResult, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; +import { ITreeContextMenuEvent, ITreeFilter, ITreeNode, TreeFilterResult, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -34,6 +34,14 @@ import { ILocalizedString } from 'vs/platform/action/common/action'; import { CommentsModel } from 'vs/workbench/contrib/comments/browser/commentsModel'; import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; +import { createActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IAction } from 'vs/base/common/actions'; +import { MarshalledId } from 'vs/base/common/marshallingIds'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; export const COMMENTS_VIEW_ID = 'workbench.panel.comments'; export const COMMENTS_VIEW_STORAGE_ID = 'Comments'; @@ -62,6 +70,7 @@ interface ICommentThreadTemplateData { separator: HTMLElement; timestamp: TimestampWidget; }; + actionBar: ActionBar; disposables: IDisposable[]; } @@ -124,10 +133,59 @@ export class ResourceWithCommentsRenderer implements IListRenderer, ICommentThreadTemplateData> { templateId: string = 'comment-node'; constructor( + private actionViewItemProvider: IActionViewItemProvider, + private menus: CommentsMenus, @IOpenerService private readonly openerService: IOpenerService, @IConfigurationService private readonly configurationService: IConfigurationService, @IThemeService private themeService: IThemeService @@ -137,16 +195,22 @@ export class CommentNodeRenderer implements IListRenderer const threadContainer = dom.append(container, dom.$('.comment-thread-container')); const metadataContainer = dom.append(threadContainer, dom.$('.comment-metadata-container')); + const metadata = dom.append(metadataContainer, dom.$('.comment-metadata')); const threadMetadata = { - icon: dom.append(metadataContainer, dom.$('.icon')), - userNames: dom.append(metadataContainer, dom.$('.user')), - timestamp: new TimestampWidget(this.configurationService, dom.append(metadataContainer, dom.$('.timestamp-container'))), - separator: dom.append(metadataContainer, dom.$('.separator')), - commentPreview: dom.append(metadataContainer, dom.$('.text')), - range: dom.append(metadataContainer, dom.$('.range')) + icon: dom.append(metadata, dom.$('.icon')), + userNames: dom.append(metadata, dom.$('.user')), + timestamp: new TimestampWidget(this.configurationService, dom.append(metadata, dom.$('.timestamp-container'))), + separator: dom.append(metadata, dom.$('.separator')), + commentPreview: dom.append(metadata, dom.$('.text')), + range: dom.append(metadata, dom.$('.range')) }; threadMetadata.separator.innerText = '\u00b7'; + const actionsContainer = dom.append(metadataContainer, dom.$('.actions')); + const actionBar = new ActionBar(actionsContainer, { + actionViewItemProvider: this.actionViewItemProvider + }); + const snippetContainer = dom.append(threadContainer, dom.$('.comment-snippet-container')); const repliesMetadata = { container: snippetContainer, @@ -158,9 +222,9 @@ export class CommentNodeRenderer implements IListRenderer }; repliesMetadata.separator.innerText = '\u00b7'; repliesMetadata.icon.classList.add(...ThemeIcon.asClassNameArray(Codicon.indent)); - const disposables = [threadMetadata.timestamp, repliesMetadata.timestamp]; - return { threadMetadata, repliesMetadata, disposables }; + const disposables = [threadMetadata.timestamp, repliesMetadata.timestamp]; + return { threadMetadata, repliesMetadata, actionBar, disposables }; } private getCountString(commentCount: number): string { @@ -198,6 +262,8 @@ export class CommentNodeRenderer implements IListRenderer } renderElement(node: ITreeNode, index: number, templateData: ICommentThreadTemplateData, height: number | undefined): void { + templateData.actionBar.clear(); + const commentCount = node.element.replies.length + 1; templateData.threadMetadata.icon.classList.remove(...Array.from(templateData.threadMetadata.icon.classList.values()) .filter(value => value.startsWith('codicon'))); @@ -232,6 +298,14 @@ export class CommentNodeRenderer implements IListRenderer } } + const menuActions = this.menus.getResourceActions(node.element); + templateData.actionBar.push(menuActions.actions, { icon: true, label: false }); + templateData.actionBar.context = { + commentControlHandle: node.element.controllerHandle, + commentThreadHandle: node.element.threadHandle, + $mid: MarshalledId.CommentThread + }; + if (!node.element.hasReply()) { templateData.repliesMetadata.container.style.display = 'none'; return; @@ -250,6 +324,7 @@ export class CommentNodeRenderer implements IListRenderer disposeTemplate(templateData: ICommentThreadTemplateData): void { templateData.disposables.forEach(disposeable => disposeable.dispose()); + templateData.actionBar.dispose(); } } @@ -347,6 +422,8 @@ export class Filter implements ITreeFilter { + private readonly menus: CommentsMenus; + constructor( labels: ResourceLabels, container: HTMLElement, @@ -355,12 +432,16 @@ export class CommentsList extends WorkbenchObjectTree this.commentsOnContextMenu(e))); + } + + private commentsOnContextMenu(treeEvent: ITreeContextMenuEvent): void { + const node: CommentsModel | ResourceWithCommentThreads | CommentNode | null = treeEvent.element; + if (!(node instanceof CommentNode)) { + return; + } + const event: UIEvent = treeEvent.browserEvent; + + event.preventDefault(); + event.stopPropagation(); + + this.setFocus([node]); + const actions = this.menus.getResourceContextActions(node); + if (!actions.length) { + return; + } + this.contextMenuService.showContextMenu({ + getAnchor: () => treeEvent.anchor, + getActions: () => actions, + getActionViewItem: (action) => { + const keybinding = this.keybindingService.lookupKeybinding(action.id); + if (keybinding) { + return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() }); + } + return undefined; + }, + onHide: (wasCancelled?: boolean) => { + if (wasCancelled) { + this.domFocus(); + } + }, + getActionsContext: () => ({ + commentControlHandle: node.controllerHandle, + commentThreadHandle: node.threadHandle, + $mid: MarshalledId.CommentThread + }) + }); } filterComments(): void { diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index 385ac16e1dc..2530cf96e7c 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -13,7 +13,6 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { CommentNode, ResourceWithCommentThreads, ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; import { IWorkspaceCommentThreadsEvent, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { textLinkForeground, textLinkActiveForeground, focusBorder, textPreformatForeground } from 'vs/platform/theme/common/colorRegistry'; import { ResourceLabels } from 'vs/workbench/browser/labels'; import { CommentsList, COMMENTS_VIEW_TITLE, Filter } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; import { IViewPaneOptions, FilterViewPane } from 'vs/workbench/browser/parts/views/viewPane'; @@ -192,10 +191,6 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { this._register(this.commentService.onDidUpdateCommentThreads(this.onCommentsUpdated, this)); this._register(this.commentService.onDidDeleteDataProvider(this.onDataProviderDeleted, this)); - const styleElement = dom.createStyleSheet(container); - this.applyStyles(styleElement); - this._register(this.themeService.onDidColorThemeChange(_ => this.applyStyles(styleElement))); - this._register(this.onDidChangeBodyVisibility(visible => { if (visible) { this.refresh(); @@ -220,33 +215,6 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { } } - private applyStyles(styleElement: HTMLStyleElement) { - const content: string[] = []; - - const theme = this.themeService.getColorTheme(); - const linkColor = theme.getColor(textLinkForeground); - if (linkColor) { - content.push(`.comments-panel .comments-panel-container a { color: ${linkColor}; }`); - } - - const linkActiveColor = theme.getColor(textLinkActiveForeground); - if (linkActiveColor) { - content.push(`.comments-panel .comments-panel-container a:hover, a:active { color: ${linkActiveColor}; }`); - } - - const focusColor = theme.getColor(focusBorder); - if (focusColor) { - content.push(`.comments-panel .comments-panel-container a:focus { outline-color: ${focusColor}; }`); - } - - const codeTextForegroundColor = theme.getColor(textPreformatForeground); - if (codeTextForegroundColor) { - content.push(`.comments-panel .comments-panel-container .text code { color: ${codeTextForegroundColor}; }`); - } - - styleElement.textContent = content.join('\n'); - } - private async renderComments(): Promise { this.treeContainer.classList.toggle('hidden', !this.commentService.commentsModel.hasCommentThreads()); this.renderMessage(); diff --git a/src/vs/workbench/contrib/comments/browser/media/panel.css b/src/vs/workbench/contrib/comments/browser/media/panel.css index a349ec52490..a1132e43d49 100644 --- a/src/vs/workbench/contrib/comments/browser/media/panel.css +++ b/src/vs/workbench/contrib/comments/browser/media/panel.css @@ -36,6 +36,11 @@ overflow: hidden; } +.comments-panel .comments-panel-container .tree-container .comment-thread-container .comment-metadata { + flex: 1; + display: flex; +} + .comments-panel .count, .comments-panel .user { padding-right: 5px; @@ -117,3 +122,34 @@ .comments-panel .hide { display: none; } + +.comments-panel .comments-panel-container .text a { + color: var(--vscode-textLink-foreground); +} + +.comments-panel .comments-panel-container .text a:hover, +.comments-panel .comments-panel-container a:active { + color: var(--vscode-textLink-activeForeground); +} + +.comments-panel .comments-panel-container .text a:focus { + outline-color: var(--vscode-focusBorder); +} + +.comments-panel .comments-panel-container .text code { + color: var(--vscode-textPreformat-foreground); +} + +.comments-panel .comments-panel-container .actions { + display: none; +} + +.comments-panel .comments-panel-container .actions .action-label { + padding: 2px; +} + +.comments-panel .monaco-list .monaco-list-row:hover .comment-metadata-container .actions, +.comments-panel .monaco-list .monaco-list-row.selected .comment-metadata-container .actions, +.comments-panel .monaco-list .monaco-list-row.focused .comment-metadata-container .actions { + display: block; +} diff --git a/src/vs/workbench/contrib/comments/common/commentModel.ts b/src/vs/workbench/contrib/comments/common/commentModel.ts index 9a6d8786372..e4418a28dfc 100644 --- a/src/vs/workbench/contrib/comments/common/commentModel.ts +++ b/src/vs/workbench/contrib/comments/common/commentModel.ts @@ -8,29 +8,26 @@ import { IRange } from 'vs/editor/common/core/range'; import { Comment, CommentThread, CommentThreadChangedEvent, CommentThreadState } from 'vs/editor/common/languages'; export interface ICommentThreadChangedEvent extends CommentThreadChangedEvent { + uniqueOwner: string; owner: string; ownerLabel: string; } export class CommentNode { - owner: string; - threadId: string; - range: IRange | undefined; - comment: Comment; + isRoot: boolean = false; replies: CommentNode[] = []; - resource: URI; - isRoot: boolean; - threadState?: CommentThreadState; - constructor(owner: string, threadId: string, resource: URI, comment: Comment, range: IRange | undefined, threadState: CommentThreadState | undefined) { - this.owner = owner; - this.threadId = threadId; - this.comment = comment; - this.resource = resource; - this.range = range; - this.isRoot = false; - this.threadState = threadState; - } + constructor( + public readonly uniqueOwner: string, + public readonly threadId: string, + public readonly resource: URI, + public readonly comment: Comment, + public readonly range: IRange | undefined, + public readonly threadState: CommentThreadState | undefined, + public readonly contextValue: string | undefined, + public readonly owner: string, + public readonly controllerHandle: number, + public readonly threadHandle: number) { } hasReply(): boolean { return this.replies && this.replies.length !== 0; @@ -39,21 +36,23 @@ export class CommentNode { export class ResourceWithCommentThreads { id: string; + uniqueOwner: string; owner: string; ownerLabel: string | undefined; commentThreads: CommentNode[]; // The top level comments on the file. Replys are nested under each node. resource: URI; - constructor(owner: string, resource: URI, commentThreads: CommentThread[]) { + constructor(uniqueOwner: string, owner: string, resource: URI, commentThreads: CommentThread[]) { + this.uniqueOwner = uniqueOwner; this.owner = owner; this.id = resource.toString(); this.resource = resource; - this.commentThreads = commentThreads.filter(thread => thread.comments && thread.comments.length).map(thread => ResourceWithCommentThreads.createCommentNode(owner, resource, thread)); + this.commentThreads = commentThreads.filter(thread => thread.comments && thread.comments.length).map(thread => ResourceWithCommentThreads.createCommentNode(uniqueOwner, owner, resource, thread)); } - public static createCommentNode(owner: string, resource: URI, commentThread: CommentThread): CommentNode { + public static createCommentNode(uniqueOwner: string, owner: string, resource: URI, commentThread: CommentThread): CommentNode { const { threadId, comments, range } = commentThread; - const commentNodes: CommentNode[] = comments!.map(comment => new CommentNode(owner, threadId, resource, comment, range, commentThread.state)); + const commentNodes: CommentNode[] = comments!.map(comment => new CommentNode(uniqueOwner, threadId, resource, comment, range, commentThread.state, commentThread.contextValue, owner, commentThread.controllerHandle, commentThread.commentThreadHandle)); if (commentNodes.length > 1) { commentNodes[0].replies = commentNodes.slice(1, commentNodes.length); } diff --git a/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts b/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts index a3e171b9d40..cd5f0ddf60c 100644 --- a/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts +++ b/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts @@ -49,6 +49,7 @@ class TestCommentThread implements CommentThread { class TestCommentController implements ICommentController { id: string = 'test'; label: string = 'Test Comments'; + owner: string = 'test'; features = {}; createCommentThreadTemplate(resource: UriComponents, range: IRange | undefined): Promise { throw new Error('Method not implemented.'); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts index bf0fe703109..1c42939cab4 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts @@ -141,7 +141,7 @@ export class CellComments extends CellContentPart { if (this.notebookEditor.hasModel()) { const commentInfos = coalesce(await this.commentService.getNotebookComments(element.uri)); if (commentInfos.length && commentInfos[0].threads.length) { - return { owner: commentInfos[0].owner, thread: commentInfos[0].threads[0] }; + return { owner: commentInfos[0].uniqueOwner, thread: commentInfos[0].threads[0] }; } } diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 31c61213f4d..c43a6eb3bed 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -290,6 +290,12 @@ const apiMenus: IAPIMenu[] = [ description: localize('comment.commentContext', "The contributed comment context menu, rendered as a right click menu on the an individual comment in the comment thread's peek view."), proposed: 'contribCommentPeekContext' }, + { + key: 'commentsView/commentThread/context', + id: MenuId.CommentsViewThreadActions, + description: localize('commentsView.threadActions', "The contributed comment thread context menu in the comments view"), + proposed: 'contribCommentsViewThreadMenus' + }, { key: 'notebook/toolbar', id: MenuId.NotebookToolbar, diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 8571e00fc4d..67c26906df3 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -27,6 +27,7 @@ export const allApiProposals = Object.freeze({ contribCommentEditorActionsMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentEditorActionsMenu.d.ts', contribCommentPeekContext: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentPeekContext.d.ts', contribCommentThreadAdditionalMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentThreadAdditionalMenu.d.ts', + contribCommentsViewThreadMenus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentsViewThreadMenus.d.ts', contribEditSessions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditSessions.d.ts', contribEditorContentMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditorContentMenu.d.ts', contribIssueReporter: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts', diff --git a/src/vscode-dts/vscode.proposed.contribCommentsViewThreadMenus.d.ts b/src/vscode-dts/vscode.proposed.contribCommentsViewThreadMenus.d.ts new file mode 100644 index 00000000000..9dc199c51cc --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribCommentsViewThreadMenus.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `commentsView/commentThread/context` menu contribution point From 08845e942beb23084e51ffac00f2f40f80a23b95 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 7 Mar 2024 13:40:51 +0100 Subject: [PATCH 1828/1897] fix #206988 (#207057) --- .../workbench/contrib/extensions/browser/media/extension.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/media/extension.css b/src/vs/workbench/contrib/extensions/browser/media/extension.css index 136ce1af0f7..985b5511c05 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extension.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extension.css @@ -214,10 +214,6 @@ text-overflow: ellipsis; } -.extension-list-item > .details > .footer > .monaco-action-bar > .actions-container { - flex-wrap: wrap-reverse; -} - .extension-list-item > .details > .footer > .monaco-action-bar > .actions-container .action-label:not(.icon) { border-radius: 2px; } From 68f565d32a8248b1c5677426621c34a875c1a8da Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 7 Mar 2024 14:13:08 +0100 Subject: [PATCH 1829/1897] emit downloaded event in mac (#207052) --- src/vs/platform/menubar/electron-main/menubar.ts | 2 +- src/vs/platform/update/electron-main/updateService.darwin.ts | 2 ++ src/vs/workbench/browser/parts/titlebar/menubarControl.ts | 2 +- src/vs/workbench/contrib/update/browser/update.ts | 5 ++++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 2ab5bcecbfa..f11b38cb19d 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -647,7 +647,7 @@ export class Menubar { return [new MenuItem({ label: nls.localize('miDownloadingUpdate', "Downloading Update..."), enabled: false })]; case StateType.Downloaded: - return [new MenuItem({ + return isMacintosh ? [] : [new MenuItem({ label: this.mnemonicLabel(nls.localize('miInstallUpdate', "Install &&Update...")), click: () => { this.reportMenuActionTelemetry('InstallUpdate'); this.updateService.applyUpdate(); diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index 27a2fcbeadf..183c69da906 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -109,6 +109,8 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau return; } + this.setState(State.Downloaded(update)); + type UpdateDownloadedClassification = { owner: 'joaomoreno'; version: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The version number of the new VS Code that has been downloaded.' }; diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 1bfc3526c2e..1453c7d8eeb 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -474,7 +474,7 @@ export class CustomMenubarControl extends MenubarControl { return new Action('update.downloading', localize('DownloadingUpdate', "Downloading Update..."), undefined, false); case StateType.Downloaded: - return new Action('update.install', localize({ key: 'installUpdate...', comment: ['&& denotes a mnemonic'] }, "Install &&Update..."), undefined, true, () => + return isMacintosh ? null : new Action('update.install', localize({ key: 'installUpdate...', comment: ['&& denotes a mnemonic'] }, "Install &&Update..."), undefined, true, () => this.updateService.applyUpdate()); case StateType.Updating: diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 45a8227909b..e1020e6e771 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -17,7 +17,7 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { ReleaseNotesManager } from 'vs/workbench/contrib/update/browser/releaseNotesEditor'; -import { isWeb, isWindows } from 'vs/base/common/platform'; +import { isMacintosh, isWeb, isWindows } from 'vs/base/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { RawContextKey, IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { MenuRegistry, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; @@ -319,6 +319,9 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu // windows fast updates private onUpdateDownloaded(update: IUpdate): void { + if (isMacintosh) { + return; + } if (this.configurationService.getValue('update.enableWindowsBackgroundUpdates') && this.productService.target === 'user') { return; } From 4a564dbe2cfd16f38baedc6f0f47973ab4ef0631 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 7 Mar 2024 05:14:11 -0800 Subject: [PATCH 1830/1897] Spread out terminal group names for easier use by exts Fixes #207065 --- .../contrib/terminal/browser/terminalMenus.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index 7f119914b09..a000c5e8465 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -20,17 +20,17 @@ import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/ed const enum ContextMenuGroup { Create = '1_create', - Edit = '2_edit', - Clear = '3_clear', - Kill = '4_kill', - Config = '5_config' + Edit = '3_edit', + Clear = '5_clear', + Kill = '7_kill', + Config = '9_config' } export const enum TerminalMenuBarGroup { Create = '1_create', - Run = '2_run', - Manage = '3_manage', - Configure = '4_configure' + Run = '3_run', + Manage = '5_manage', + Configure = '7_configure' } export function setupTerminalMenus(): void { From 75dd427e0970cecaef5ff100e25d62233de2d310 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 7 Mar 2024 05:38:19 -0800 Subject: [PATCH 1831/1897] Replace ext suggest command with experimental inline chat keybinding Fixes #4256 --- .../workbench/contrib/terminal/common/terminal.ts | 13 ++++++++++++- .../chat/browser/terminalChatActions.ts | 3 ++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index fc925b646bb..60c5b601e8a 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -649,7 +649,18 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ 'workbench.action.quickOpenView', 'workbench.action.toggleMaximizedPanel', 'notification.acceptPrimaryAction', - 'runCommands' + 'runCommands', + 'workbench.action.terminal.chat.start', + 'workbench.action.terminal.chat.close', + 'workbench.action.terminal.chat.discard', + 'workbench.action.terminal.chat.makeRequest', + 'workbench.action.terminal.chat.cancel', + 'workbench.action.terminal.chat.feedbackHelpful', + 'workbench.action.terminal.chat.feedbackUnhelpful', + 'workbench.action.terminal.chat.feedbackReportIssue', + 'workbench.action.terminal.chat.runCommand', + 'workbench.action.terminal.chat.insertCommand', + 'workbench.action.terminal.chat.viewInChat', ]; export const terminalContributionsDescriptor: IExtensionPointDescriptor = { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index ff650600398..7f3ecc81464 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -23,7 +23,8 @@ registerActiveXtermAction({ keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, when: ContextKeyExpr.and(TerminalContextKeys.focusInAny), - weight: KeybindingWeight.WorkbenchContrib, + // HACK: Force weight to be higher than the extension contributed keybinding to override it until it gets replaced + weight: KeybindingWeight.ExternalExtension + 1, // KeybindingWeight.WorkbenchContrib, }, f1: true, category: AbstractInlineChatAction.category, From b1cf638f7de5e7d9c0f57120fb0b6c7c71aa85a8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 7 Mar 2024 05:40:26 -0800 Subject: [PATCH 1832/1897] Allow using terminal inline chat without focus Part of #4256 --- .../contrib/terminalContrib/chat/browser/terminalChatActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index ff650600398..0420ede6250 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -29,7 +29,7 @@ registerActiveXtermAction({ category: AbstractInlineChatAction.category, precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - ContextKeyExpr.and(TerminalContextKeys.processSupported, TerminalContextKeys.focusInAny), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), // TODO: This needs to change to check for a terminal location capable agent CTX_INLINE_CHAT_HAS_PROVIDER ), From 51883a73b818226db55879262b34bb80d3bcedff Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 7 Mar 2024 05:44:01 -0800 Subject: [PATCH 1833/1897] Ensure context keys are created on chat contribution init Fixes #207037 --- .../chat/browser/terminalChatController.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 94a66a2bdd2..cbdd7454d2d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -52,11 +52,11 @@ export class TerminalChatController extends Disposable implements ITerminalContr private _chatWidget: Lazy | undefined; get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } - private readonly _requestActiveContextKey!: IContextKey; - private readonly _terminalAgentRegisteredContextKey!: IContextKey; - private readonly _responseTypeContextKey!: IContextKey; - private readonly _responseSupportsIssueReportingContextKey!: IContextKey; - private readonly _sessionResponseVoteContextKey!: IContextKey; + private readonly _requestActiveContextKey: IContextKey; + private readonly _terminalAgentRegisteredContextKey: IContextKey; + private readonly _responseTypeContextKey: IContextKey; + private readonly _responseSupportsIssueReportingContextKey: IContextKey; + private readonly _sessionResponseVoteContextKey: IContextKey; private _messages = this._store.add(new Emitter()); @@ -90,15 +90,16 @@ export class TerminalChatController extends Disposable implements ITerminalContr ) { super(); - if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { - return; - } this._requestActiveContextKey = TerminalChatContextKeys.requestActive.bindTo(this._contextKeyService); this._terminalAgentRegisteredContextKey = TerminalChatContextKeys.agentRegistered.bindTo(this._contextKeyService); this._responseTypeContextKey = TerminalChatContextKeys.responseType.bindTo(this._contextKeyService); this._responseSupportsIssueReportingContextKey = TerminalChatContextKeys.responseSupportsIssueReporting.bindTo(this._contextKeyService); this._sessionResponseVoteContextKey = TerminalChatContextKeys.sessionResponseVote.bindTo(this._contextKeyService); + if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { + return; + } + if (!this._chatAgentService.getAgent(this._terminalAgentId)) { this._register(this._chatAgentService.onDidChangeAgents(() => { if (this._chatAgentService.getAgent(this._terminalAgentId)) { From 5962739ad59ea0f61d128b48ae01816e2a11edd5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 7 Mar 2024 05:54:51 -0800 Subject: [PATCH 1834/1897] Don't get rid of model early, get history from it Fixes #4263 --- .../chat/browser/terminalChatController.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 94a66a2bdd2..bd720107fd0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -24,7 +24,7 @@ import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/te import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; -import { ChatModel, ChatRequestModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatModel, ChatRequestModel, IChatRequestVariableData, getHistoryEntriesFromModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { TerminalChatContextKeys, TerminalChatResponseTypes } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; const enum Message { @@ -209,16 +209,19 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._requestActiveContextKey.reset(); } - private updateModel(): void { + async acceptInput(): Promise { const providerInfo = this._chatService.getProviderInfos()?.[0]; if (!providerInfo) { return; } - this._model ??= this._chatService.startSession(providerInfo.id, CancellationToken.None); - } + if (!this._model) { + this._model = this._chatService.startSession(providerInfo.id, CancellationToken.None); + if (!this._model) { + throw new Error('Could not start chat session'); + } + } + const model = this._model; - async acceptInput(): Promise { - this.updateModel(); this._lastInput = this._chatWidget?.rawValue?.input(); if (!this._lastInput) { return; @@ -258,7 +261,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } }; - await this._model?.waitForInitialization(); + await model.waitForInitialization(); const request: IParsedChatRequest = { text: this._lastInput, parts: [] @@ -266,16 +269,16 @@ export class TerminalChatController extends Disposable implements ITerminalContr const requestVarData: IChatRequestVariableData = { variables: [] }; - this._currentRequest = this._model?.addRequest(request, requestVarData); + this._currentRequest = model.addRequest(request, requestVarData); const requestProps: IChatAgentRequest = { - sessionId: this._model!.sessionId, + sessionId: model.sessionId, requestId: this._currentRequest!.id, agentId: this._terminalAgentId, message: this._lastInput, variables: { variables: [] }, }; try { - const task = this._chatAgentService.invokeAgent(this._terminalAgentId, requestProps, progressCallback, [], cancellationToken); + const task = this._chatAgentService.invokeAgent(this._terminalAgentId, requestProps, progressCallback, getHistoryEntriesFromModel(model), cancellationToken); this._chatWidget?.rawValue?.inlineChatWidget.updateChatMessage(undefined); this._chatWidget?.rawValue?.inlineChatWidget.updateFollowUps(undefined); this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(true); From aa2cd708efcb5da1b44720723d977415a38eee55 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 7 Mar 2024 06:00:14 -0800 Subject: [PATCH 1835/1897] Use mutable disposable for terminal chat model --- .../chat/browser/terminalChatController.ts | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index bd720107fd0..b523f69eb5d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -7,7 +7,7 @@ import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { marked } from 'vs/base/common/marked/marked'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -73,7 +73,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr private _terminalAgentId = 'terminal'; - private _model: ChatModel | undefined; + private _model: MutableDisposable = new MutableDisposable(); constructor( private readonly _instance: ITerminalInstance, @@ -137,7 +137,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr acceptFeedback(helpful?: boolean): void { const providerId = this._chatService.getProviderInfos()?.[0]?.id; - if (!providerId || !this._currentRequest || !this._model) { + const model = this._model.value; + if (!providerId || !this._currentRequest || !model) { return; } let action: ChatUserAction; @@ -148,7 +149,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr action = { kind: 'vote', direction: helpful ? InteractiveSessionVoteDirection.Up : InteractiveSessionVoteDirection.Down }; } // TODO:extract into helper method - for (const request of this._model.getRequests()) { + for (const request of model.getRequests()) { if (request.response?.response.value || request.response?.result) { this._chatService.notifyUserAction({ providerId, @@ -165,7 +166,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr cancel(): void { if (this._currentRequest) { - this._model?.cancelRequest(this._currentRequest); + this._model.value?.cancelRequest(this._currentRequest); } this._requestActiveContextKey.set(false); this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(false); @@ -198,10 +199,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr clear(): void { if (this._currentRequest) { - this._model?.cancelRequest(this._currentRequest); + this._model.value?.cancelRequest(this._currentRequest); } - this._model?.dispose(); - this._model = undefined; + this._model.clear(); this._chatWidget?.rawValue?.hide(); this._chatWidget?.rawValue?.setValue(undefined); this._responseTypeContextKey.reset(); @@ -214,13 +214,13 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (!providerInfo) { return; } - if (!this._model) { - this._model = this._chatService.startSession(providerInfo.id, CancellationToken.None); - if (!this._model) { + if (!this._model.value) { + this._model.value = this._chatService.startSession(providerInfo.id, CancellationToken.None); + if (!this._model.value) { throw new Error('Could not start chat session'); } } - const model = this._model; + const model = this._model.value; this._lastInput = this._chatWidget?.rawValue?.input(); if (!this._lastInput) { @@ -241,7 +241,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr responseContent += progress.content; } if (this._currentRequest) { - this._model?.acceptResponseProgress(this._currentRequest, progress); + model.acceptResponseProgress(this._currentRequest, progress); } if (!firstCodeBlock) { const firstCodeBlockContent = marked.lexer(responseContent).filter(token => token.type === 'code')?.[0]?.raw; @@ -292,7 +292,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(''); this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); if (this._currentRequest) { - this._model?.completeResponse(this._currentRequest); + model.completeResponse(this._currentRequest); } this._lastResponseContent = responseContent; if (!firstCodeBlock && this._currentRequest) { @@ -344,9 +344,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (!providerInfo) { return; } + const model = this._model.value; const widget = await this._chatWidgetService.revealViewForProvider(providerInfo.id); - if (widget && widget.viewModel && this._model) { - for (const request of this._model.getRequests()) { + if (widget && widget.viewModel && model) { + for (const request of model.getRequests()) { if (request.response?.response.value || request.response?.result) { this._chatService.addCompleteRequest(widget.viewModel.sessionId, request.message as IParsedChatRequest, @@ -359,14 +360,14 @@ export class TerminalChatController extends Disposable implements ITerminalContr } } widget.focusLastMessage(); - } else if (!this._model) { + } else if (!model) { widget?.focusInput(); } } override dispose() { if (this._currentRequest) { - this._model?.cancelRequest(this._currentRequest); + this._model.value?.cancelRequest(this._currentRequest); } super.dispose(); this.clear(); From e4825758526ac66de29635f7d18495ac3ed6887a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 7 Mar 2024 06:04:15 -0800 Subject: [PATCH 1836/1897] Register model --- .../terminalContrib/chat/browser/terminalChatController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index b523f69eb5d..ce08af1e3f0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -73,7 +73,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr private _terminalAgentId = 'terminal'; - private _model: MutableDisposable = new MutableDisposable(); + private _model: MutableDisposable = this._register(new MutableDisposable()); constructor( private readonly _instance: ITerminalInstance, From 465e40226b7105d183535fccc61a6c018142f172 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 7 Mar 2024 06:11:05 -0800 Subject: [PATCH 1837/1897] Clean up usage of Lazy, prevent double dispose Fixes #207073 --- .../chat/browser/terminalChatController.ts | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 94a66a2bdd2..4ee64950d9e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -39,17 +39,27 @@ const enum Message { } export class TerminalChatController extends Disposable implements ITerminalContribution { - static readonly ID = 'terminal.Chat'; + static readonly ID = 'terminal.chat'; static get(instance: ITerminalInstance): TerminalChatController | null { return instance.getContribution(TerminalChatController.ID); } /** - * Currently focused chat widget. This is used to track action context since - * 'active terminals' are only tracked for non-detached terminal instanecs. + * Currently focused chat widget. This is used to track action context since 'active terminals' + * are only tracked for non-detached terminal instanecs. */ static activeChatWidget?: TerminalChatController; + + /** + * The chat widget for the controller, this is lazy as we don't want to instantiate it until + * both it's required and xterm is ready. + */ private _chatWidget: Lazy | undefined; + + /** + * The chat widget for the controller, this will be undefined if xterm is not ready yet (ie. the + * terminal is still initializing). + */ get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } private readonly _requestActiveContextKey!: IContextKey; @@ -115,7 +125,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr return; } this._chatWidget = new Lazy(() => { - const chatWidget = this._register(this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance)); this._register(chatWidget.focusTracker.onDidFocus(() => { TerminalChatController.activeChatWidget = this; @@ -130,7 +139,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (!this._instance.domElement) { throw new Error('FindWidget expected terminal DOM to be initialized'); } - return chatWidget; }); } @@ -160,7 +168,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr }); } } - this._chatWidget?.rawValue?.inlineChatWidget.updateStatus('Thank you for your feedback!', { resetAfter: 1250 }); + this._chatWidget?.value.inlineChatWidget.updateStatus('Thank you for your feedback!', { resetAfter: 1250 }); } cancel(): void { @@ -168,15 +176,15 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._model?.cancelRequest(this._currentRequest); } this._requestActiveContextKey.set(false); - this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(false); - this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(''); - this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); + this._chatWidget?.value.inlineChatWidget.updateProgress(false); + this._chatWidget?.value.inlineChatWidget.updateInfo(''); + this._chatWidget?.value.inlineChatWidget.updateToolbar(true); } private _forcedPlaceholder: string | undefined = undefined; private _updatePlaceholder(): void { - const inlineChatWidget = this._chatWidget?.rawValue?.inlineChatWidget; + const inlineChatWidget = this._chatWidget?.value.inlineChatWidget; if (inlineChatWidget) { inlineChatWidget.placeholder = this._getPlaceholderText(); } @@ -202,8 +210,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr } this._model?.dispose(); this._model = undefined; - this._chatWidget?.rawValue?.hide(); - this._chatWidget?.rawValue?.setValue(undefined); + this._chatWidget?.value.hide(); + this._chatWidget?.value.setValue(undefined); this._responseTypeContextKey.reset(); this._sessionResponseVoteContextKey.reset(); this._requestActiveContextKey.reset(); @@ -219,7 +227,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr async acceptInput(): Promise { this.updateModel(); - this._lastInput = this._chatWidget?.rawValue?.input(); + this._lastInput = this._chatWidget?.value.input(); if (!this._lastInput) { return; } @@ -248,10 +256,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr firstCodeBlock = match?.groups?.content.trim(); shellType = match?.groups?.language; if (firstCodeBlock) { - this._chatWidget?.rawValue?.renderTerminalCommand(firstCodeBlock, shellType); + this._chatWidget?.value.renderTerminalCommand(firstCodeBlock, shellType); this._chatAccessibilityService.acceptResponse(firstCodeBlock, accessibilityRequestId); this._responseTypeContextKey.set(TerminalChatResponseTypes.TerminalCommand); - this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); + this._chatWidget?.value.inlineChatWidget.updateToolbar(true); this._messages.fire(Message.ACCEPT_INPUT); } } @@ -276,27 +284,27 @@ export class TerminalChatController extends Disposable implements ITerminalContr }; try { const task = this._chatAgentService.invokeAgent(this._terminalAgentId, requestProps, progressCallback, [], cancellationToken); - this._chatWidget?.rawValue?.inlineChatWidget.updateChatMessage(undefined); - this._chatWidget?.rawValue?.inlineChatWidget.updateFollowUps(undefined); - this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(true); - this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(localize('thinking', "Thinking\u2026")); + this._chatWidget?.value.inlineChatWidget.updateChatMessage(undefined); + this._chatWidget?.value.inlineChatWidget.updateFollowUps(undefined); + this._chatWidget?.value.inlineChatWidget.updateProgress(true); + this._chatWidget?.value.inlineChatWidget.updateInfo(localize('thinking', "Thinking\u2026")); await task; } catch (e) { } finally { this._requestActiveContextKey.set(false); - this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(false); - this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(''); - this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); + this._chatWidget?.value.inlineChatWidget.updateProgress(false); + this._chatWidget?.value.inlineChatWidget.updateInfo(''); + this._chatWidget?.value.inlineChatWidget.updateToolbar(true); if (this._currentRequest) { this._model?.completeResponse(this._currentRequest); } this._lastResponseContent = responseContent; if (!firstCodeBlock && this._currentRequest) { this._chatAccessibilityService.acceptResponse(responseContent, accessibilityRequestId); - this._chatWidget?.rawValue?.renderMessage(responseContent, this._currentRequest.id); + this._chatWidget?.value.renderMessage(responseContent, this._currentRequest.id); this._responseTypeContextKey.set(TerminalChatResponseTypes.Message); - this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); + this._chatWidget?.value.inlineChatWidget.updateToolbar(true); this._messages.fire(Message.ACCEPT_INPUT); } const supportIssueReporting = this._currentRequest?.response?.agent?.metadata?.supportIssueReporting; @@ -307,7 +315,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } updateInput(text: string, selectAll = true): void { - const widget = this._chatWidget?.rawValue?.inlineChatWidget; + const widget = this._chatWidget?.value.inlineChatWidget; if (widget) { widget.value = text; if (selectAll) { @@ -317,23 +325,23 @@ export class TerminalChatController extends Disposable implements ITerminalContr } getInput(): string { - return this._chatWidget?.rawValue?.input() ?? ''; + return this._chatWidget?.value.input() ?? ''; } focus(): void { - this._chatWidget?.rawValue?.focus(); + this._chatWidget?.value.focus(); } hasFocus(): boolean { - return !!this._chatWidget?.rawValue?.hasFocus(); + return !!this._chatWidget?.value.hasFocus(); } acceptCommand(shouldExecute: boolean): void { - this._chatWidget?.rawValue?.acceptCommand(shouldExecute); + this._chatWidget?.value.acceptCommand(shouldExecute); } reveal(): void { - this._chatWidget?.rawValue?.reveal(); + this._chatWidget?.value.reveal(); } async viewInChat(): Promise { @@ -367,6 +375,5 @@ export class TerminalChatController extends Disposable implements ITerminalContr } super.dispose(); this.clear(); - this._chatWidget?.rawValue?.dispose(); } } From 9a7ce5b4e74ab4ee7e225f5024c51ade14c65818 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 7 Mar 2024 15:21:06 +0100 Subject: [PATCH 1838/1897] voice - implement lazy activation (Voice: figure out usage numbers (microsoft/vscode-internalbacklog#4877)) (#207060) * voice - implement lazy activation (Voice: figure out usage numbers (microsoft/vscode-internalbacklog#4877)) * . * . --- .../browser/accessibilityConfiguration.ts | 2 +- .../contrib/chat/common/voiceChat.ts | 6 +- .../actions/voiceChatActions.ts | 10 +- .../chat/test/common/voiceChat.test.ts | 41 ++++---- .../browser/dictation/editorDictation.ts | 4 +- .../electron-sandbox/inlineChatQuickVoice.ts | 4 +- .../speech/browser/speech.contribution.ts | 2 +- .../contrib/speech/browser/speechService.ts | 99 ++++++++++++++++--- .../contrib/speech/common/speechService.ts | 5 +- .../contrib/terminal/browser/terminalVoice.ts | 4 +- 10 files changed, 122 insertions(+), 55 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 5151b2869d9..d9eb9ac7e40 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -695,7 +695,7 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen ) { super(); - this._register(Event.runAndSubscribe(speechService.onDidRegisterSpeechProvider, () => this.updateConfiguration())); + this._register(Event.runAndSubscribe(speechService.onDidChangeHasSpeechProvider, () => this.updateConfiguration())); } private updateConfiguration(): void { diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index 5f7208cdd79..1c93007b8cf 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -30,7 +30,7 @@ export interface IVoiceChatService { * if the user says "at workspace slash fix this problem", the result * will be "@workspace /fix this problem". */ - createVoiceChatSession(token: CancellationToken, options: IVoiceChatSessionOptions): IVoiceChatSession; + createVoiceChatSession(token: CancellationToken, options: IVoiceChatSessionOptions): Promise; } export interface IVoiceChatTextEvent extends ISpeechToTextEvent { @@ -114,7 +114,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { } } - createVoiceChatSession(token: CancellationToken, options: IVoiceChatSessionOptions): IVoiceChatSession { + async createVoiceChatSession(token: CancellationToken, options: IVoiceChatSessionOptions): Promise { const disposables = new DisposableStore(); disposables.add(token.onCancellationRequested(() => disposables.dispose())); @@ -122,7 +122,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { let detectedSlashCommand = false; const emitter = disposables.add(new Emitter()); - const session = this.speechService.createSpeechToTextSession(token, 'chat'); + const session = await this.speechService.createSpeechToTextSession(token, 'chat'); const phrases = this.createPhrases(options.model); disposables.add(session.onDidChange(e => { diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 47909f02faa..aa1fad15b1d 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -314,7 +314,7 @@ class VoiceChatSessions { @IConfigurationService private readonly configurationService: IConfigurationService ) { } - start(controller: IVoiceChatSessionController, context?: IChatExecuteActionContext): IVoiceChatSession { + async start(controller: IVoiceChatSessionController, context?: IChatExecuteActionContext): Promise { this.stop(); let disableTimeout = false; @@ -339,7 +339,7 @@ class VoiceChatSessions { this.voiceChatGettingReadyKey.set(true); - const voiceChatSession = this.voiceChatService.createVoiceChatSession(cts.token, { usesAgents: controller.context !== 'inline', model: context?.widget?.viewModel?.model }); + const voiceChatSession = await this.voiceChatService.createVoiceChatSession(cts.token, { usesAgents: controller.context !== 'inline', model: context?.widget?.viewModel?.model }); let inputValue = controller.getInput(); @@ -474,7 +474,7 @@ async function startVoiceChatWithHoldMode(id: string, accessor: ServicesAccessor return; } - const session = VoiceChatSessions.getInstance(instantiationService).start(controller, context); + const session = await VoiceChatSessions.getInstance(instantiationService).start(controller, context); await holdMode; handle.dispose(); @@ -545,7 +545,7 @@ export class HoldToVoiceChatInChatViewAction extends Action2 { const handle = disposableTimeout(async () => { const controller = await VoiceChatSessionControllerFactory.create(accessor, 'view'); if (controller) { - session = VoiceChatSessions.getInstance(instantiationService).start(controller, context); + session = await VoiceChatSessions.getInstance(instantiationService).start(controller, context); session.setTimeoutDisabled(true); } }, VOICE_KEY_HOLD_THRESHOLD); @@ -921,7 +921,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe } private registerListeners(): void { - this._register(Event.runAndSubscribe(this.speechService.onDidRegisterSpeechProvider, () => { + this._register(Event.runAndSubscribe(this.speechService.onDidChangeHasSpeechProvider, () => { this.updateConfiguration(); this.handleKeywordActivation(); })); diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index 391561501ef..419146d5ba1 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -63,8 +63,7 @@ suite('VoiceChat', () => { class TestSpeechService implements ISpeechService { _serviceBrand: undefined; - onDidRegisterSpeechProvider = Event.None; - onDidUnregisterSpeechProvider = Event.None; + onDidChangeHasSpeechProvider = Event.None; readonly hasSpeechProvider = true; readonly hasActiveSpeechToTextSession = false; @@ -74,7 +73,7 @@ suite('VoiceChat', () => { onDidStartSpeechToTextSession = Event.None; onDidEndSpeechToTextSession = Event.None; - createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { + async createSpeechToTextSession(token: CancellationToken): Promise { return { onDidChange: emitter.event }; @@ -91,10 +90,10 @@ suite('VoiceChat', () => { let service: VoiceChatService; let event: IVoiceChatTextEvent | undefined; - function createSession(options: IVoiceChatSessionOptions) { + async function createSession(options: IVoiceChatSessionOptions) { const cts = new CancellationTokenSource(); disposables.add(toDisposable(() => cts.dispose(true))); - const session = service.createVoiceChatSession(cts.token, options); + const session = await service.createVoiceChatSession(cts.token, options); disposables.add(session.onDidChange(e => { event = e; })); @@ -110,17 +109,17 @@ suite('VoiceChat', () => { }); test('Agent and slash command detection (useAgents: false)', async () => { - testAgentsAndSlashCommandsDetection({ usesAgents: false, model: {} as IChatModel }); + await testAgentsAndSlashCommandsDetection({ usesAgents: false, model: {} as IChatModel }); }); test('Agent and slash command detection (useAgents: true)', async () => { - testAgentsAndSlashCommandsDetection({ usesAgents: true, model: {} as IChatModel }); + await testAgentsAndSlashCommandsDetection({ usesAgents: true, model: {} as IChatModel }); }); - function testAgentsAndSlashCommandsDetection(options: IVoiceChatSessionOptions) { + async function testAgentsAndSlashCommandsDetection(options: IVoiceChatSessionOptions) { // Nothing to detect - createSession(options); + await createSession(options); emitter.fire({ status: SpeechToTextStatus.Started }); assert.strictEqual(event?.status, SpeechToTextStatus.Started); @@ -141,7 +140,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.waitingForInput, undefined); // Agent - createSession(options); + await createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); @@ -168,7 +167,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.waitingForInput, false); // Agent with punctuation - createSession(options); + await createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); @@ -180,7 +179,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.text, options.usesAgents ? '@workspace help' : 'At workspace, help'); assert.strictEqual(event?.waitingForInput, false); - createSession(options); + await createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At Workspace. help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); @@ -193,7 +192,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.waitingForInput, false); // Slash Command - createSession(options); + await createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'Slash fix' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); @@ -206,7 +205,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.waitingForInput, true); // Agent + Slash Command - createSession(options); + await createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code slash search help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); @@ -219,7 +218,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.waitingForInput, false); // Agent + Slash Command with punctuation - createSession(options); + await createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code, slash search, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); @@ -231,7 +230,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.text, options.usesAgents ? '@vscode /search help' : 'At code, slash search, help'); assert.strictEqual(event?.waitingForInput, false); - createSession(options); + await createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code. slash, search help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); @@ -244,7 +243,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.waitingForInput, false); // Agent not detected twice - createSession(options); + await createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace, for at workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); @@ -258,7 +257,7 @@ suite('VoiceChat', () => { // Slash command detected after agent recognized if (options.usesAgents) { - createSession(options); + await createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); @@ -280,7 +279,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.text, '/fix'); assert.strictEqual(event?.waitingForInput, true); - createSession(options); + await createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); @@ -297,7 +296,7 @@ suite('VoiceChat', () => { test('waiting for input', async () => { // Agent - createSession({ usesAgents: true, model: {} as IChatModel }); + await createSession({ usesAgents: true, model: {} as IChatModel }); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); @@ -310,7 +309,7 @@ suite('VoiceChat', () => { assert.strictEqual(event.waitingForInput, true); // Slash Command - createSession({ usesAgents: true, model: {} as IChatModel }); + await createSession({ usesAgents: true, model: {} as IChatModel }); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace slash explain' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); diff --git a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts index c1263ccce50..91727c3a14c 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts @@ -193,7 +193,7 @@ export class EditorDictation extends Disposable implements IEditorContribution { super(); } - start() { + async start(): Promise { const disposables = new DisposableStore(); this.sessionDisposables.value = disposables; @@ -249,7 +249,7 @@ export class EditorDictation extends Disposable implements IEditorContribution { const cts = new CancellationTokenSource(); disposables.add(toDisposable(() => cts.dispose(true))); - const session = this.speechService.createSpeechToTextSession(cts.token); + const session = await this.speechService.createSpeechToTextSession(cts.token); disposables.add(session.onDidChange(e => { if (cts.token.isCancellationRequested) { return; diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index b75bb758c6c..7f24f989723 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -226,7 +226,7 @@ export class InlineChatQuickVoice implements IEditorContribution { this._store.dispose(); } - start() { + async start() { this._finishCallback?.(true); @@ -236,7 +236,7 @@ export class InlineChatQuickVoice implements IEditorContribution { let message: string | undefined; let preview: string | undefined; - const session = this._voiceChatService.createVoiceChatSession(cts.token, { usesAgents: false }); + const session = await this._voiceChatService.createVoiceChatSession(cts.token, { usesAgents: false }); const listener = session.onDidChange(e => { if (cts.token.isCancellationRequested) { diff --git a/src/vs/workbench/contrib/speech/browser/speech.contribution.ts b/src/vs/workbench/contrib/speech/browser/speech.contribution.ts index 03a6035fb80..7184018cd98 100644 --- a/src/vs/workbench/contrib/speech/browser/speech.contribution.ts +++ b/src/vs/workbench/contrib/speech/browser/speech.contribution.ts @@ -7,4 +7,4 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; import { SpeechService } from 'vs/workbench/contrib/speech/browser/speechService'; -registerSingleton(ISpeechService, SpeechService, InstantiationType.Delayed); +registerSingleton(ISpeechService, SpeechService, InstantiationType.Eager /* Reads Extension Points */); diff --git a/src/vs/workbench/contrib/speech/browser/speechService.ts b/src/vs/workbench/contrib/speech/browser/speechService.ts index 36186b1458e..d24c324812b 100644 --- a/src/vs/workbench/contrib/speech/browser/speechService.ts +++ b/src/vs/workbench/contrib/speech/browser/speechService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { firstOrDefault } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; @@ -15,20 +16,49 @@ import { ISpeechService, ISpeechProvider, HasSpeechProvider, ISpeechToTextSessio import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; + +export interface ISpeechProviderDescriptor { + readonly name: string; + readonly description?: string; +} + +const speechProvidersExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'speechProviders', + jsonSchema: { + description: localize('vscode.extension.contributes.speechProvider', 'Contributes a Speech Provider'), + type: 'array', + items: { + additionalProperties: false, + type: 'object', + defaultSnippets: [{ body: { name: '', description: '' } }], + required: ['name'], + properties: { + name: { + description: localize('speechProviderName', "Unique name for this Speech Provider."), + type: 'string' + }, + description: { + description: localize('speechProviderDescription', "A description of this Speech Provider, shown in the UI."), + type: 'string' + } + } + } + } +}); export class SpeechService extends Disposable implements ISpeechService { readonly _serviceBrand: undefined; - private readonly _onDidRegisterSpeechProvider = this._register(new Emitter()); - readonly onDidRegisterSpeechProvider = this._onDidRegisterSpeechProvider.event; + private readonly _onDidChangeHasSpeechProvider = this._register(new Emitter()); + readonly onDidChangeHasSpeechProvider = this._onDidChangeHasSpeechProvider.event; - private readonly _onDidUnregisterSpeechProvider = this._register(new Emitter()); - readonly onDidUnregisterSpeechProvider = this._onDidUnregisterSpeechProvider.event; - - get hasSpeechProvider() { return this.providers.size > 0; } + get hasSpeechProvider() { return this.providerDescriptors.size > 0 || this.providers.size > 0; } private readonly providers = new Map(); + private readonly providerDescriptors = new Map(); private readonly hasSpeechProviderContext = HasSpeechProvider.bindTo(this.contextKeyService); @@ -38,9 +68,34 @@ export class SpeechService extends Disposable implements ISpeechService { @IHostService private readonly hostService: IHostService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IAccessibilitySignalService private readonly accessibilitySignalService: IAccessibilitySignalService + @IAccessibilitySignalService private readonly accessibilitySignalService: IAccessibilitySignalService, + @IExtensionService private readonly extensionService: IExtensionService ) { super(); + + this.handleAndRegisterSpeechExtensions(); + } + + private handleAndRegisterSpeechExtensions(): void { + speechProvidersExtensionPoint.setHandler((extensions, delta) => { + const oldHasSpeechProvider = this.hasSpeechProvider; + + for (const extension of delta.removed) { + for (const descriptor of extension.value) { + this.providerDescriptors.delete(descriptor.name); + } + } + + for (const extension of delta.added) { + for (const descriptor of extension.value) { + this.providerDescriptors.set(descriptor.name, descriptor); + } + } + + if (oldHasSpeechProvider !== this.hasSpeechProvider) { + this.handleHasSpeechProviderChange(); + } + }); } registerSpeechProvider(identifier: string, provider: ISpeechProvider): IDisposable { @@ -48,21 +103,31 @@ export class SpeechService extends Disposable implements ISpeechService { throw new Error(`Speech provider with identifier ${identifier} is already registered.`); } - this.providers.set(identifier, provider); - this.hasSpeechProviderContext.set(true); + const oldHasSpeechProvider = this.hasSpeechProvider; - this._onDidRegisterSpeechProvider.fire(provider); + this.providers.set(identifier, provider); + + if (oldHasSpeechProvider !== this.hasSpeechProvider) { + this.handleHasSpeechProviderChange(); + } return toDisposable(() => { - this.providers.delete(identifier); - this._onDidUnregisterSpeechProvider.fire(provider); + const oldHasSpeechProvider = this.hasSpeechProvider; - if (this.providers.size === 0) { - this.hasSpeechProviderContext.set(false); + this.providers.delete(identifier); + + if (oldHasSpeechProvider !== this.hasSpeechProvider) { + this.handleHasSpeechProviderChange(); } }); } + private handleHasSpeechProviderChange(): void { + this.hasSpeechProviderContext.set(this.hasSpeechProvider); + + this._onDidChangeHasSpeechProvider.fire(); + } + private readonly _onDidStartSpeechToTextSession = this._register(new Emitter()); readonly onDidStartSpeechToTextSession = this._onDidStartSpeechToTextSession.event; @@ -74,7 +139,11 @@ export class SpeechService extends Disposable implements ISpeechService { private readonly speechToTextInProgress = SpeechToTextInProgress.bindTo(this.contextKeyService); - createSpeechToTextSession(token: CancellationToken, context: string = 'speech'): ISpeechToTextSession { + async createSpeechToTextSession(token: CancellationToken, context: string = 'speech'): Promise { + + // Send out extension activation to ensure providers can register + await this.extensionService.activateByEvent('onSpeech'); + const provider = firstOrDefault(Array.from(this.providers.values())); if (!provider) { throw new Error(`No Speech provider is registered.`); diff --git a/src/vs/workbench/contrib/speech/common/speechService.ts b/src/vs/workbench/contrib/speech/common/speechService.ts index e1bbf0f4f8e..6fdd365f39c 100644 --- a/src/vs/workbench/contrib/speech/common/speechService.ts +++ b/src/vs/workbench/contrib/speech/common/speechService.ts @@ -68,8 +68,7 @@ export interface ISpeechService { readonly _serviceBrand: undefined; - readonly onDidRegisterSpeechProvider: Event; - readonly onDidUnregisterSpeechProvider: Event; + readonly onDidChangeHasSpeechProvider: Event; readonly hasSpeechProvider: boolean; @@ -84,7 +83,7 @@ export interface ISpeechService { * Starts to transcribe speech from the default microphone. The returned * session object provides an event to subscribe for transcribed text. */ - createSpeechToTextSession(token: CancellationToken, context?: string): ISpeechToTextSession; + createSpeechToTextSession(token: CancellationToken, context?: string): Promise; readonly onDidStartKeywordRecognition: Event; readonly onDidEndKeywordRecognition: Event; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts b/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts index 9ff52e29fc5..795b1a7de3f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts @@ -77,7 +77,7 @@ export class TerminalVoiceSession extends Disposable { this._disposables = this._register(new DisposableStore()); } - start(): void { + async start(): Promise { this.stop(); let voiceTimeout = this.configurationService.getValue(AccessibilityVoiceSettingId.SpeechTimeout); if (!isNumber(voiceTimeout) || voiceTimeout < 0) { @@ -89,7 +89,7 @@ export class TerminalVoiceSession extends Disposable { }, voiceTimeout)); this._cancellationTokenSource = new CancellationTokenSource(); this._register(toDisposable(() => this._cancellationTokenSource?.dispose(true))); - const session = this._speechService.createSpeechToTextSession(this._cancellationTokenSource?.token); + const session = await this._speechService.createSpeechToTextSession(this._cancellationTokenSource?.token); this._disposables.add(session.onDidChange((e) => { if (this._cancellationTokenSource?.token.isCancellationRequested) { From 855c3e38523689e77d45d3ed786222d27ad325a4 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 7 Mar 2024 15:22:27 +0100 Subject: [PATCH 1839/1897] Bump distro (#207074) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2afb762ff12..24bb6558dee 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.88.0", - "distro": "4623345215aabf2cde23e144a9d4d3ef7803360e", + "distro": "9cacfa07a0a807c640d14bda9eac544fd3295a0f", "author": { "name": "Microsoft Corporation" }, From a01eacaac2e41d1b5faa9ba432b5f0799cc99f88 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 7 Mar 2024 15:31:54 +0100 Subject: [PATCH 1840/1897] rename and migration --- src/vs/workbench/browser/layout.ts | 2 +- .../parts/activitybar/activitybarPart.ts | 14 +++++++------- .../parts/auxiliarybar/auxiliaryBarPart.ts | 6 +++--- .../browser/parts/sidebar/sidebarPart.ts | 6 +++--- .../browser/workbench.contribution.ts | 19 +++++++++++++++---- .../services/layout/browser/layoutService.ts | 2 +- 6 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 4eac33fbb1a..ad210e00396 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -2707,7 +2707,7 @@ class LayoutStateModel extends Disposable { if (oldValue !== undefined) { return !oldValue; } - return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) !== ActivityBarPosition.SIDE; + return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) !== ActivityBarPosition.DEFAULT; } private setRuntimeValueAndFire(key: RuntimeStateKey, value: T): void { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 2fc1c87d42f..4ee5b07e211 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -378,26 +378,26 @@ export class ActivityBarCompositeBar extends PaneCompositeBar { registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.activityBarLocation.side', + id: 'workbench.action.activityBarLocation.default', title: { - ...localize2('positionActivityBarSide', 'Move Activity Bar to Side'), - mnemonicTitle: localize({ key: 'miSideActivityBar', comment: ['&& denotes a mnemonic'] }, "&&Side"), + ...localize2('positionActivityBarDefault', 'Move Activity Bar to Side'), + mnemonicTitle: localize({ key: 'miDefaultActivityBar', comment: ['&& denotes a mnemonic'] }, "&&Default"), }, - shortTitle: localize('side', "Side"), + shortTitle: localize('default', "Default"), category: Categories.View, - toggled: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.SIDE), + toggled: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.DEFAULT), menu: [{ id: MenuId.ActivityBarPositionMenu, order: 2 }, { id: MenuId.CommandPalette, - when: ContextKeyExpr.notEquals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.SIDE), + when: ContextKeyExpr.notEquals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.DEFAULT), }] }); } run(accessor: ServicesAccessor): void { const configurationService = accessor.get(IConfigurationService); - configurationService.updateValue(LayoutSettings.ACTIVITY_BAR_LOCATION, ActivityBarPosition.SIDE); + configurationService.updateValue(LayoutSettings.ACTIVITY_BAR_LOCATION, ActivityBarPosition.DEFAULT); } }); diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts index 675f19df25e..895b0ab0a6e 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts @@ -206,11 +206,11 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { protected getCompositeBarPosition(): CompositeBarPosition { const activityBarPosition = this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION); switch (activityBarPosition) { + case ActivityBarPosition.TOP: return CompositeBarPosition.TOP; case ActivityBarPosition.BOTTOM: return CompositeBarPosition.BOTTOM; case ActivityBarPosition.HIDDEN: return CompositeBarPosition.TITLE; - case ActivityBarPosition.SIDE: return CompositeBarPosition.TITLE; - case ActivityBarPosition.TOP: - default: return CompositeBarPosition.TOP; + case ActivityBarPosition.DEFAULT: return CompositeBarPosition.TITLE; + default: return CompositeBarPosition.TITLE; } } diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index b6beaac649d..73734d5dad5 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -212,7 +212,7 @@ export class SidebarPart extends AbstractPaneCompositePart { case ActivityBarPosition.TOP: return CompositeBarPosition.TOP; case ActivityBarPosition.BOTTOM: return CompositeBarPosition.BOTTOM; case ActivityBarPosition.HIDDEN: - case ActivityBarPosition.SIDE: + case ActivityBarPosition.DEFAULT: // noop default: return CompositeBarPosition.TITLE; } } @@ -227,9 +227,9 @@ export class SidebarPart extends AbstractPaneCompositePart { private getRememberedActivityBarVisiblePosition(): ActivityBarPosition { const activityBarPosition = this.storageService.get(LayoutSettings.ACTIVITY_BAR_LOCATION, StorageScope.PROFILE); switch (activityBarPosition) { - case ActivityBarPosition.SIDE: return ActivityBarPosition.SIDE; + case ActivityBarPosition.TOP: return ActivityBarPosition.TOP; case ActivityBarPosition.BOTTOM: return ActivityBarPosition.BOTTOM; - default: return ActivityBarPosition.TOP; + default: return ActivityBarPosition.DEFAULT; } } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index f7e95d8343a..6ed828c45d6 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -497,11 +497,11 @@ const registry = Registry.as(ConfigurationExtensions.Con }, [LayoutSettings.ACTIVITY_BAR_LOCATION]: { 'type': 'string', - 'enum': ['side', 'top', 'bottom', 'hidden'], - 'default': 'side', - 'markdownDescription': localize({ comment: ['This is the description for a setting'], key: 'activityBarLocation' }, "Controls the location of the Activity Bar. It can either show to the `side` or `top` / `bottom` of the Primary and Secondary Side Bar or `hidden`."), + 'enum': ['default', 'top', 'bottom', 'hidden'], + 'default': 'default', + 'markdownDescription': localize({ comment: ['This is the description for a setting'], key: 'activityBarLocation' }, "Controls the location of the Activity Bar. It can either show to the `default` or `top` / `bottom` of the Primary and Secondary Side Bar or `hidden`."), 'enumDescriptions': [ - localize('workbench.activityBar.location.side', "Show the Activity Bar of the Primary Side Bar on the side."), + localize('workbench.activityBar.location.default', "Show the Activity Bar of the Primary Side Bar on the side."), localize('workbench.activityBar.location.top', "Show the Activity Bar on top of the Primary and Secondary Side Bar."), localize('workbench.activityBar.location.bottom', "Show the Activity Bar at the bottom of the Primary and Secondary Side Bar."), localize('workbench.activityBar.location.hide', "Hide the Activity Bar in the Primary and Secondary Side Bar.") @@ -811,6 +811,17 @@ Registry.as(Extensions.ConfigurationMigration) } }]); +Registry.as(Extensions.ConfigurationMigration) + .registerConfigurationMigrations([{ + key: LayoutSettings.ACTIVITY_BAR_LOCATION, migrateFn: (value: any) => { + const results: ConfigurationKeyValuePairs = []; + if (value === 'side') { + results.push([LayoutSettings.ACTIVITY_BAR_LOCATION, { value: ActivityBarPosition.DEFAULT }]); + } + return results; + } + }]); + Registry.as(Extensions.ConfigurationMigration) .registerConfigurationMigrations([{ key: 'workbench.editor.doubleClickTabToToggleEditorGroupSizes', migrateFn: (value: any) => { diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index b25b1a66c14..321727120df 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -48,7 +48,7 @@ export const enum LayoutSettings { } export const enum ActivityBarPosition { - SIDE = 'side', + DEFAULT = 'default', TOP = 'top', BOTTOM = 'bottom', HIDDEN = 'hidden' From 78f2051e25d020b34a1480d3bc08249a88064a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Cie=C5=9Blak?= Date: Thu, 7 Mar 2024 16:39:59 +0100 Subject: [PATCH 1841/1897] Inline Edit - make sure we finalize accepting before requesting new edit (#206525) * Inline Edit - make sure we finalize accepting before requesting new edit --- .../contrib/inlineEdit/browser/commands.ts | 4 +-- .../browser/inlineEditController.ts | 36 ++++++++++++------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/contrib/inlineEdit/browser/commands.ts b/src/vs/editor/contrib/inlineEdit/browser/commands.ts index 11d6dfa2f22..d7e1f4fe8cb 100644 --- a/src/vs/editor/contrib/inlineEdit/browser/commands.ts +++ b/src/vs/editor/contrib/inlineEdit/browser/commands.ts @@ -37,7 +37,7 @@ export class AcceptInlineEdit extends EditorAction { public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { const controller = InlineEditController.get(editor); - controller?.accept(); + await controller?.accept(); } } @@ -147,7 +147,7 @@ export class RejectInlineEdit extends EditorAction { public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { const controller = InlineEditController.get(editor); - controller?.clear(); + await controller?.clear(); } } diff --git a/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts b/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts index 646c6f20c22..4e0c10eb335 100644 --- a/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts +++ b/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { ISettableObservable, autorun, constObservable, disposableObservableValue, observableFromEvent, observableSignalFromEvent } from 'vs/base/common/observable'; +import { ISettableObservable, autorun, constObservable, disposableObservableValue, observableFromEvent, observableSignalFromEvent, observableValue, transaction } from 'vs/base/common/observable'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; @@ -21,6 +21,7 @@ import { InlineEditHintsWidget } from 'vs/editor/contrib/inlineEdit/browser/inli import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { createStyleSheet2 } from 'vs/base/browser/dom'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { onUnexpectedExternalError } from 'vs/base/common/errors'; export class InlineEditWidget implements IDisposable { constructor(public readonly widget: GhostTextWidget, public readonly edit: IInlineEdit) { } @@ -49,7 +50,7 @@ export class InlineEditController extends Disposable { private _currentRequestCts: CancellationTokenSource | undefined; private _jumpBackPosition: Position | undefined; - private _isAccepting: boolean = false; + private _isAccepting: ISettableObservable = observableValue(this, false); private readonly _enabled = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineEdit).enabled); private readonly _fontFamily = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineEdit).fontFamily); @@ -76,6 +77,9 @@ export class InlineEditController extends Disposable { return; } modelChangedSignal.read(reader); + if (this._isAccepting.read(reader)) { + return; + } this.getInlineEdit(editor, true); })); @@ -111,7 +115,7 @@ export class InlineEditController extends Disposable { //Clear suggestions on lost focus const editorBlurSingal = observableSignalFromEvent('InlineEditController.editorBlurSignal', editor.onDidBlurEditorWidget); - this._register(autorun(reader => { + this._register(autorun(async reader => { /** @description InlineEditController.editorBlur */ if (!this._enabled.read(reader)) { return; @@ -123,7 +127,7 @@ export class InlineEditController extends Disposable { } this._currentRequestCts?.dispose(true); this._currentRequestCts = undefined; - this.clear(false); + await this.clear(false); })); //Invoke provider on focus @@ -222,8 +226,7 @@ export class InlineEditController extends Disposable { private async getInlineEdit(editor: ICodeEditor, auto: boolean) { this._isCursorAtInlineEditContext.set(false); - this.clear(); - this._isAccepting = false; + await this.clear(); const edit = await this.fetchInlineEdit(editor, auto); if (!edit) { return; @@ -254,8 +257,8 @@ export class InlineEditController extends Disposable { this.editor.revealPositionInCenterIfOutsideViewport(this._jumpBackPosition); } - public accept(): void { - this._isAccepting = true; + public async accept() { + this._isAccepting.set(true, undefined); const data = this._currentEdit.get()?.edit; if (!data) { return; @@ -269,10 +272,15 @@ export class InlineEditController extends Disposable { this.editor.pushUndoStop(); this.editor.executeEdits('acceptCurrent', [EditOperation.replace(Range.lift(data.range), text)]); if (data.accepted) { - this._commandService.executeCommand(data.accepted.id, ...data.accepted.arguments || []); + await this._commandService + .executeCommand(data.accepted.id, ...(data.accepted.arguments || [])) + .then(undefined, onUnexpectedExternalError); } this.freeEdit(data); - this._currentEdit.set(undefined, undefined); + transaction((tx) => { + this._currentEdit.set(undefined, tx); + this._isAccepting.set(false, tx); + }); } public jumpToCurrent(): void { @@ -288,10 +296,12 @@ export class InlineEditController extends Disposable { this.editor.revealPositionInCenterIfOutsideViewport(position); } - public clear(sendRejection: boolean = true) { + public async clear(sendRejection: boolean = true) { const edit = this._currentEdit.get()?.edit; - if (edit && edit?.rejected && !this._isAccepting && sendRejection) { - this._commandService.executeCommand(edit.rejected.id, ...edit.rejected.arguments || []); + if (edit && edit?.rejected && sendRejection) { + await this._commandService + .executeCommand(edit.rejected.id, ...(edit.rejected.arguments || [])) + .then(undefined, onUnexpectedExternalError); } if (edit) { this.freeEdit(edit); From 757b6b2556c08f0cc0774aa10a4d9d32606fd810 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Sun, 18 Feb 2024 12:40:24 +0200 Subject: [PATCH 1842/1897] Tunnel: Add unit tests for port mapping --- .../tunnel/test/common/tunnel.test.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/vs/platform/tunnel/test/common/tunnel.test.ts diff --git a/src/vs/platform/tunnel/test/common/tunnel.test.ts b/src/vs/platform/tunnel/test/common/tunnel.test.ts new file mode 100644 index 00000000000..4884acb05d4 --- /dev/null +++ b/src/vs/platform/tunnel/test/common/tunnel.test.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as assert from 'assert'; +import { URI } from 'vs/base/common/uri'; +import { extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/tunnel/common/tunnel'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; + + +suite('Tunnel', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + function portMappingDoTest(uri: string, + expectedAddress?: string, + expectedPort?: number) { + const res = extractLocalHostUriMetaDataForPortMapping(URI.parse(uri)); + assert.strictEqual(!expectedAddress, !res); + assert.strictEqual(res?.address, expectedAddress); + assert.strictEqual(res?.port, expectedPort); + } + + function portMappingTest(uri: string, expectedAddress?: string, expectedPort?: number) { + portMappingDoTest(uri, expectedAddress, expectedPort); + } + + test('portMapping', () => { + portMappingTest('file:///foo.bar/baz'); + portMappingTest('http://foo.bar:1234'); + portMappingTest('http://localhost:8080', 'localhost', 8080); + portMappingTest('https://localhost:443', 'localhost', 443); + portMappingTest('http://127.0.0.1:3456', '127.0.0.1', 3456); + portMappingTest('http://0.0.0.0:7654', '0.0.0.0', 7654); + portMappingTest('http://localhost:8080/path?foo=bar', 'localhost', 8080); + portMappingTest('http://localhost:8080/path?foo=http%3A%2F%2Flocalhost%3A8081', 'localhost', 8080); + }); +}); From 84e5f9a43a03e97929749f41fa15a025f837bd24 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Sun, 18 Feb 2024 12:45:46 +0200 Subject: [PATCH 1843/1897] Window: Factor out resolveExternalUri ...and add some unit tests. --- src/vs/workbench/electron-sandbox/window.ts | 92 ++++++++++--------- .../electron-sandbox/resolveExternal.test.ts | 71 ++++++++++++++ 2 files changed, 121 insertions(+), 42 deletions(-) create mode 100644 src/vs/workbench/test/electron-sandbox/resolveExternal.test.ts diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index eb2159272be..946e3a9ad5e 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -42,11 +42,11 @@ import { coalesce } from 'vs/base/common/arrays'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { assertIsDefined } from 'vs/base/common/types'; -import { IOpenerService, OpenOptions } from 'vs/platform/opener/common/opener'; +import { IOpenerService, IResolvedExternalUri, OpenOptions } from 'vs/platform/opener/common/opener'; import { Schemas } from 'vs/base/common/network'; import { INativeHostService } from 'vs/platform/native/common/native'; import { posix } from 'vs/base/common/path'; -import { ITunnelService, extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/tunnel/common/tunnel'; +import { ITunnelService, RemoteTunnel, extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/tunnel/common/tunnel'; import { IWorkbenchLayoutService, Parts, positionFromString, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; @@ -784,6 +784,53 @@ export class NativeWindow extends BaseWindow { }); } + private async openTunnel(address: string, port: number): Promise { + const remoteAuthority = this.environmentService.remoteAuthority; + const addressProvider: IAddressProvider | undefined = remoteAuthority ? { + getAddress: async (): Promise => { + return (await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority)).authority; + } + } : undefined; + const tunnel = await this.tunnelService.getExistingTunnel(address, port); + if (!tunnel || (typeof tunnel === 'string')) { + return this.tunnelService.openTunnel(addressProvider, address, port); + } + return tunnel; + } + + async resolveExternalUri(uri: URI, options?: OpenOptions): Promise { + if (options?.allowTunneling) { + const portMappingRequest = extractLocalHostUriMetaDataForPortMapping(uri); + if (portMappingRequest) { + const tunnel = await this.openTunnel(portMappingRequest.address, portMappingRequest.port); + if (tunnel && (typeof tunnel !== 'string')) { + const addressAsUri = URI.parse(tunnel.localAddress); + const resolved = addressAsUri.scheme.startsWith(uri.scheme) ? addressAsUri : uri.with({ authority: tunnel.localAddress }); + return { + resolved, + dispose: () => tunnel.dispose(), + }; + } + } + } + + if (!options?.openExternal) { + const canHandleResource = await this.fileService.canHandleResource(uri); + if (canHandleResource) { + return { + resolved: URI.from({ + scheme: this.productService.urlProtocol, + path: 'workspace', + query: uri.toString() + }), + dispose() { } + }; + } + } + + return undefined; + } + private setupOpenHandlers(): void { // Handle external open() calls @@ -805,46 +852,7 @@ export class NativeWindow extends BaseWindow { // Register external URI resolver this.openerService.registerExternalUriResolver({ resolveExternalUri: async (uri: URI, options?: OpenOptions) => { - if (options?.allowTunneling) { - const portMappingRequest = extractLocalHostUriMetaDataForPortMapping(uri); - if (portMappingRequest) { - const remoteAuthority = this.environmentService.remoteAuthority; - const addressProvider: IAddressProvider | undefined = remoteAuthority ? { - getAddress: async (): Promise => { - return (await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority)).authority; - } - } : undefined; - let tunnel = await this.tunnelService.getExistingTunnel(portMappingRequest.address, portMappingRequest.port); - if (!tunnel || (typeof tunnel === 'string')) { - tunnel = await this.tunnelService.openTunnel(addressProvider, portMappingRequest.address, portMappingRequest.port); - } - if (tunnel && (typeof tunnel !== 'string')) { - const constTunnel = tunnel; - const addressAsUri = URI.parse(constTunnel.localAddress); - const resolved = addressAsUri.scheme.startsWith(uri.scheme) ? addressAsUri : uri.with({ authority: constTunnel.localAddress }); - return { - resolved, - dispose: () => constTunnel.dispose(), - }; - } - } - } - - if (!options?.openExternal) { - const canHandleResource = await this.fileService.canHandleResource(uri); - if (canHandleResource) { - return { - resolved: URI.from({ - scheme: this.productService.urlProtocol, - path: 'workspace', - query: uri.toString() - }), - dispose() { } - }; - } - } - - return undefined; + return this.resolveExternalUri(uri, options); } }); } diff --git a/src/vs/workbench/test/electron-sandbox/resolveExternal.test.ts b/src/vs/workbench/test/electron-sandbox/resolveExternal.test.ts new file mode 100644 index 00000000000..f5ab4a4a8fa --- /dev/null +++ b/src/vs/workbench/test/electron-sandbox/resolveExternal.test.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; +import { RemoteTunnel } from 'vs/platform/tunnel/common/tunnel'; +import { URI } from 'vs/base/common/uri'; + +type PortMap = Record; + +class TunnelMock { + private assignedPorts: PortMap = {}; + + reset(ports: PortMap) { + this.assignedPorts = ports; + } + + openTunnel(_address: string, port: number): Promise { + if (!this.assignedPorts[port]) { + return Promise.reject(new Error('Unexpected tunnel request')); + } + const res: RemoteTunnel = { + localAddress: `localhost:${this.assignedPorts[port]}`, + tunnelRemoteHost: '4.3.2.1', + tunnelRemotePort: this.assignedPorts[port], + privacy: '', + dispose: () => { + return Promise.resolve(); + } + }; + delete this.assignedPorts[port]; + return Promise.resolve(res); + } + + validate() { + assert(Object.keys(this.assignedPorts).length === 0, 'Expected tunnel to be used'); + } +} + +suite('NativeWindow:resolveExternal', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const tunnelMock = new TunnelMock(); + + async function doTest(uri: string, ports: PortMap = {}, expectedUri?: string) { + tunnelMock.reset(ports); + const res = await NativeWindow.prototype.resolveExternalUri.call(tunnelMock, URI.parse(uri), { + allowTunneling: true, + openExternal: true + }); + assert.strictEqual(!expectedUri, !res, `Expected URI ${expectedUri} but got ${res}`); + if (expectedUri && res) { + assert.strictEqual(res.resolved.toString(), URI.parse(expectedUri).toString()); + } + tunnelMock.validate(); + } + test('invalid', async () => { + await doTest('file:///foo.bar/baz'); + await doTest('http://foo.bar/path'); + }); + test('simple', async () => { + await doTest('http://localhost:1234/path', { 1234: 1234 }, 'http://localhost:1234/path'); + }); + test('all interfaces', async () => { + await doTest('http://0.0.0.0:1234/path', { 1234: 1234 }, 'http://localhost:1234/path'); + }); + test('changed port', async () => { + await doTest('http://localhost:1234/path', { 1234: 1235 }, 'http://localhost:1235/path'); + }); +}); From 308c48ea0e1788f5edcf6c7a34ecad28e817b1c6 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Fri, 9 Feb 2024 10:44:55 +0200 Subject: [PATCH 1844/1897] Tunnel: Extend port mapping lookup also for querystring (take 2) When running az login, the URL has localhost in redirect_uri query param. This should trigger automatic port mapping. Improve localhost port mapping to cover this case as well. This is a revised and tested version of #203908 which was reverted in #205370. Fixes #203869 --- src/vs/platform/tunnel/common/tunnel.ts | 17 ++++++++++ .../tunnel/test/common/tunnel.test.ts | 17 ++++++++-- src/vs/workbench/electron-sandbox/window.ts | 30 +++++++++++++++-- .../electron-sandbox/resolveExternal.test.ts | 32 ++++++++++++++++++- 4 files changed, 90 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/tunnel/common/tunnel.ts b/src/vs/platform/tunnel/common/tunnel.ts index 62c9059d2f8..86b4da4b409 100644 --- a/src/vs/platform/tunnel/common/tunnel.ts +++ b/src/vs/platform/tunnel/common/tunnel.ts @@ -155,6 +155,23 @@ export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: }; } +export function extractQueryLocalHostUriMetaDataForPortMapping(uri: URI): { address: string; port: number } | undefined { + if (uri.scheme !== 'http' && uri.scheme !== 'https' || !uri.query) { + return undefined; + } + const keyvalues = uri.query.split('&'); + for (const keyvalue of keyvalues) { + const value = keyvalue.split('=')[1]; + if (/^https?:/.exec(value)) { + const result = extractLocalHostUriMetaDataForPortMapping(URI.parse(value)); + if (result) { + return result; + } + } + } + return undefined; +} + export const LOCALHOST_ADDRESSES = ['localhost', '127.0.0.1', '0:0:0:0:0:0:0:1', '::1']; export function isLocalhost(host: string): boolean { return LOCALHOST_ADDRESSES.indexOf(host) >= 0; diff --git a/src/vs/platform/tunnel/test/common/tunnel.test.ts b/src/vs/platform/tunnel/test/common/tunnel.test.ts index 4884acb05d4..d86d3f47bd7 100644 --- a/src/vs/platform/tunnel/test/common/tunnel.test.ts +++ b/src/vs/platform/tunnel/test/common/tunnel.test.ts @@ -4,7 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/tunnel/common/tunnel'; +import { + extractLocalHostUriMetaDataForPortMapping, + extractQueryLocalHostUriMetaDataForPortMapping +} from 'vs/platform/tunnel/common/tunnel'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; @@ -12,16 +15,21 @@ suite('Tunnel', () => { ensureNoDisposablesAreLeakedInTestSuite(); function portMappingDoTest(uri: string, + func: (uri: URI) => { address: string; port: number } | undefined, expectedAddress?: string, expectedPort?: number) { - const res = extractLocalHostUriMetaDataForPortMapping(URI.parse(uri)); + const res = func(URI.parse(uri)); assert.strictEqual(!expectedAddress, !res); assert.strictEqual(res?.address, expectedAddress); assert.strictEqual(res?.port, expectedPort); } function portMappingTest(uri: string, expectedAddress?: string, expectedPort?: number) { - portMappingDoTest(uri, expectedAddress, expectedPort); + portMappingDoTest(uri, extractLocalHostUriMetaDataForPortMapping, expectedAddress, expectedPort); + } + + function portMappingTestQuery(uri: string, expectedAddress?: string, expectedPort?: number) { + portMappingDoTest(uri, extractQueryLocalHostUriMetaDataForPortMapping, expectedAddress, expectedPort); } test('portMapping', () => { @@ -33,5 +41,8 @@ suite('Tunnel', () => { portMappingTest('http://0.0.0.0:7654', '0.0.0.0', 7654); portMappingTest('http://localhost:8080/path?foo=bar', 'localhost', 8080); portMappingTest('http://localhost:8080/path?foo=http%3A%2F%2Flocalhost%3A8081', 'localhost', 8080); + portMappingTestQuery('http://foo.bar/path?url=http%3A%2F%2Flocalhost%3A8081', 'localhost', 8081); + portMappingTestQuery('http://foo.bar/path?url=http%3A%2F%2Flocalhost%3A8081&url2=http%3A%2F%2Flocalhost%3A8082', 'localhost', 8081); + portMappingTestQuery('http://foo.bar/path?url=http%3A%2F%2Fmicrosoft.com%2Fbad&url2=http%3A%2F%2Flocalhost%3A8081', 'localhost', 8081); }); }); diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 946e3a9ad5e..9f9877a34ff 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -46,7 +46,7 @@ import { IOpenerService, IResolvedExternalUri, OpenOptions } from 'vs/platform/o import { Schemas } from 'vs/base/common/network'; import { INativeHostService } from 'vs/platform/native/common/native'; import { posix } from 'vs/base/common/path'; -import { ITunnelService, RemoteTunnel, extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/tunnel/common/tunnel'; +import { ITunnelService, RemoteTunnel, extractLocalHostUriMetaDataForPortMapping, extractQueryLocalHostUriMetaDataForPortMapping } from 'vs/platform/tunnel/common/tunnel'; import { IWorkbenchLayoutService, Parts, positionFromString, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; @@ -799,8 +799,29 @@ export class NativeWindow extends BaseWindow { } async resolveExternalUri(uri: URI, options?: OpenOptions): Promise { + let queryTunnel: RemoteTunnel | string | undefined; if (options?.allowTunneling) { const portMappingRequest = extractLocalHostUriMetaDataForPortMapping(uri); + const queryPortMapping = extractQueryLocalHostUriMetaDataForPortMapping(uri); + if (queryPortMapping) { + queryTunnel = await this.openTunnel(queryPortMapping.address, queryPortMapping.port); + if (queryTunnel && (typeof queryTunnel !== 'string')) { + // If the tunnel was mapped to a different port, dispose it, because some services + // validate the port number in the query string. + if (queryTunnel.tunnelRemotePort !== queryPortMapping.port) { + queryTunnel.dispose(); + queryTunnel = undefined; + } else { + if (!portMappingRequest) { + const tunnel = queryTunnel; + return { + resolved: uri, + dispose: () => tunnel.dispose() + }; + } + } + } + } if (portMappingRequest) { const tunnel = await this.openTunnel(portMappingRequest.address, portMappingRequest.port); if (tunnel && (typeof tunnel !== 'string')) { @@ -808,7 +829,12 @@ export class NativeWindow extends BaseWindow { const resolved = addressAsUri.scheme.startsWith(uri.scheme) ? addressAsUri : uri.with({ authority: tunnel.localAddress }); return { resolved, - dispose: () => tunnel.dispose(), + dispose() { + tunnel.dispose(); + if (queryTunnel && (typeof queryTunnel !== 'string')) { + queryTunnel.dispose(); + } + } }; } } diff --git a/src/vs/workbench/test/electron-sandbox/resolveExternal.test.ts b/src/vs/workbench/test/electron-sandbox/resolveExternal.test.ts index f5ab4a4a8fa..821367aea2f 100644 --- a/src/vs/workbench/test/electron-sandbox/resolveExternal.test.ts +++ b/src/vs/workbench/test/electron-sandbox/resolveExternal.test.ts @@ -12,11 +12,16 @@ type PortMap = Record; class TunnelMock { private assignedPorts: PortMap = {}; + private expectedDispose = false; reset(ports: PortMap) { this.assignedPorts = ports; } + expectDispose() { + this.expectedDispose = true; + } + openTunnel(_address: string, port: number): Promise { if (!this.assignedPorts[port]) { return Promise.reject(new Error('Unexpected tunnel request')); @@ -27,6 +32,8 @@ class TunnelMock { tunnelRemotePort: this.assignedPorts[port], privacy: '', dispose: () => { + assert(this.expectedDispose, 'Unexpected dispose'); + this.expectedDispose = false; return Promise.resolve(); } }; @@ -35,7 +42,12 @@ class TunnelMock { } validate() { - assert(Object.keys(this.assignedPorts).length === 0, 'Expected tunnel to be used'); + try { + assert(Object.keys(this.assignedPorts).length === 0, 'Expected tunnel to be used'); + assert(!this.expectedDispose, 'Expected dispose to be called'); + } finally { + this.expectedDispose = false; + } } } @@ -68,4 +80,22 @@ suite('NativeWindow:resolveExternal', () => { test('changed port', async () => { await doTest('http://localhost:1234/path', { 1234: 1235 }, 'http://localhost:1235/path'); }); + test('query', async () => { + await doTest('http://foo.bar/path?a=b&c=http%3a%2f%2flocalhost%3a4455', { 4455: 4455 }, 'http://foo.bar/path?a=b&c=http%3a%2f%2flocalhost%3a4455'); + }); + test('query with different port', async () => { + tunnelMock.expectDispose(); + await doTest('http://foo.bar/path?a=b&c=http%3a%2f%2flocalhost%3a4455', { 4455: 4567 }); + }); + test('both url and query', async () => { + await doTest('http://localhost:1234/path?a=b&c=http%3a%2f%2flocalhost%3a4455', + { 1234: 4321, 4455: 4455 }, + 'http://localhost:4321/path?a=b&c=http%3a%2f%2flocalhost%3a4455'); + }); + test('both url and query, query rejected', async () => { + tunnelMock.expectDispose(); + await doTest('http://localhost:1234/path?a=b&c=http%3a%2f%2flocalhost%3a4455', + { 1234: 4321, 4455: 5544 }, + 'http://localhost:4321/path?a=b&c=http%3a%2f%2flocalhost%3a4455'); + }); }); From aaaa801e8a5befc744516103ba225a947668d21e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 7 Mar 2024 17:19:06 +0100 Subject: [PATCH 1845/1897] Joh/husky-aardvark (#207088) * chore - pull-out input editor part of the inline chat widget * cleanup --- .../contrib/inlineChat/browser/inlineChat.css | 9 +- .../browser/inlineChatInputWidget.ts | 394 ++++++++++++++++++ .../inlineChat/browser/inlineChatWidget.ts | 385 +++-------------- 3 files changed, 444 insertions(+), 344 deletions(-) create mode 100644 src/vs/workbench/contrib/inlineChat/browser/inlineChatInputWidget.ts diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css index 70ff20e6432..1b5ea26167f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css @@ -14,7 +14,6 @@ .monaco-workbench .inline-chat { color: inherit; padding: 6px; - margin-top: 6px; border-radius: 6px; border: 1px solid var(--vscode-inlineChat-border); box-shadow: 0 4px 8px var(--vscode-inlineChat-shadow); @@ -105,16 +104,12 @@ /* status */ .monaco-workbench .inline-chat .status { - margin-top: 4px; + padding-top: 4px; display: flex; justify-content: space-between; align-items: center; } -.monaco-workbench .inline-chat .status.actions { - margin-top: 4px; -} - .monaco-workbench .inline-chat .status .actions.hidden { display: none; } @@ -198,7 +193,7 @@ } .monaco-workbench .inline-chat .chatMessage { - padding: 8px 3px; + padding: 0 3px; } .monaco-workbench .inline-chat .chatMessage .chatMessageContent { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatInputWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatInputWidget.ts new file mode 100644 index 00000000000..f32ce39084e --- /dev/null +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatInputWidget.ts @@ -0,0 +1,394 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as aria from 'vs/base/browser/ui/aria/aria'; +import { Dimension, addDisposableListener, getTotalWidth, h } from 'vs/base/browser/dom'; +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; +import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; +import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; +import { IModelService } from 'vs/editor/common/services/model'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; +import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; +import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; +import { Range } from 'vs/editor/common/core/range'; +import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, IInlineChatSlashCommand } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { LanguageSelector } from 'vs/editor/common/languageSelector'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { Position } from 'vs/editor/common/core/position'; +import { CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { localize } from 'vs/nls'; + + +export class InlineChatInputWidget { + + private readonly _elements = h( + 'div.content@content', + [ + h('div.input@input', [ + h('div.editor-placeholder@placeholder'), + h('div.editor-container@editor'), + ]), + h('div.toolbar@editorToolbar') + ] + ); + + private readonly _store = new DisposableStore(); + + private readonly _ctxInputEmpty: IContextKey; + private readonly _ctxInnerCursorFirst: IContextKey; + private readonly _ctxInnerCursorLast: IContextKey; + private readonly _ctxInnerCursorStart: IContextKey; + private readonly _ctxInnerCursorEnd: IContextKey; + private readonly _ctxInputEditorFocused: IContextKey; + // private readonly _ctxResponseFocused: IContextKey; + + private readonly _inputEditor: IActiveCodeEditor; + private readonly _inputModel: ITextModel; + + private readonly _slashCommandContentWidget: SlashCommandContentWidget; + private readonly _slashCommands = this._store.add(new DisposableStore()); + private _slashCommandDetails: { command: string; detail: string }[] = []; + + protected readonly _onDidChangeHeight = this._store.add(new Emitter()); + readonly onDidChangeHeight: Event = this._onDidChangeHeight.event; + + private readonly _onDidChangeInput = this._store.add(new Emitter()); + readonly onDidChangeInput: Event = this._onDidChangeInput.event; + + constructor( + options: { menuId: MenuId; telemetrySource: string; hoverDelegate: IHoverDelegate }, + @IInstantiationService instantiationService: IInstantiationService, + @IModelService modelService: IModelService, + @IContextKeyService contextKeyService: IContextKeyService, + @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + ) { + + this._inputEditor = instantiationService.createInstance(CodeEditorWidget, this._elements.editor, inputEditorOptions, codeEditorWidgetOptions); + this._store.add(this._inputEditor); + this._store.add(this._inputEditor.onDidChangeModelContent(() => this._onDidChangeInput.fire(this))); + this._store.add(this._inputEditor.onDidLayoutChange(() => this._onDidChangeHeight.fire())); + this._store.add(this._inputEditor.onDidContentSizeChange(() => this._onDidChangeHeight.fire())); + + const uri = URI.from({ scheme: 'vscode', authority: 'inline-chat', path: `/inline-chat/model${generateUuid()}.txt` }); + this._inputModel = this._store.add(modelService.getModel(uri) ?? modelService.createModel('', null, uri)); + this._inputEditor.setModel(this._inputModel); + + // placeholder + this._elements.placeholder.style.fontSize = `${this._inputEditor.getOption(EditorOption.fontSize)}px`; + this._elements.placeholder.style.lineHeight = `${this._inputEditor.getOption(EditorOption.lineHeight)}px`; + this._store.add(addDisposableListener(this._elements.placeholder, 'click', () => this._inputEditor.focus())); + + // slash command content widget + this._slashCommandContentWidget = new SlashCommandContentWidget(this._inputEditor); + this._store.add(this._slashCommandContentWidget); + + // toolbar + this._store.add(instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.editorToolbar, options.menuId, { + telemetrySource: options.telemetrySource, + toolbarOptions: { primaryGroup: 'main' }, + hiddenItemStrategy: HiddenItemStrategy.Ignore, // keep it lean when hiding items and avoid a "..." overflow menu + hoverDelegate: options.hoverDelegate + })); + + + this._ctxInputEmpty = CTX_INLINE_CHAT_EMPTY.bindTo(contextKeyService); + this._ctxInnerCursorFirst = CTX_INLINE_CHAT_INNER_CURSOR_FIRST.bindTo(contextKeyService); + this._ctxInnerCursorLast = CTX_INLINE_CHAT_INNER_CURSOR_LAST.bindTo(contextKeyService); + this._ctxInnerCursorStart = CTX_INLINE_CHAT_INNER_CURSOR_START.bindTo(contextKeyService); + this._ctxInnerCursorEnd = CTX_INLINE_CHAT_INNER_CURSOR_END.bindTo(contextKeyService); + this._ctxInputEditorFocused = CTX_INLINE_CHAT_FOCUSED.bindTo(contextKeyService); + + // (1) inner cursor position (last/first line selected) + const updateInnerCursorFirstLast = () => { + const selection = this._inputEditor.getSelection(); + const fullRange = this._inputModel.getFullModelRange(); + let onFirst = false; + let onLast = false; + if (selection.isEmpty()) { + const selectionTop = this._inputEditor.getTopForPosition(selection.startLineNumber, selection.startColumn); + const firstViewLineTop = this._inputEditor.getTopForPosition(fullRange.startLineNumber, fullRange.startColumn); + const lastViewLineTop = this._inputEditor.getTopForPosition(fullRange.endLineNumber, fullRange.endColumn); + + if (selectionTop === firstViewLineTop) { + onFirst = true; + } + if (selectionTop === lastViewLineTop) { + onLast = true; + } + } + this._ctxInnerCursorFirst.set(onFirst); + this._ctxInnerCursorLast.set(onLast); + this._ctxInnerCursorStart.set(fullRange.getStartPosition().equals(selection.getStartPosition())); + this._ctxInnerCursorEnd.set(fullRange.getEndPosition().equals(selection.getEndPosition())); + }; + this._store.add(this._inputEditor.onDidChangeCursorPosition(updateInnerCursorFirstLast)); + updateInnerCursorFirstLast(); + + // (2) input editor focused or not + const updateFocused = () => { + const hasFocus = this._inputEditor.hasWidgetFocus(); + this._ctxInputEditorFocused.set(hasFocus); + this._elements.content.classList.toggle('synthetic-focus', hasFocus); + this.readPlaceholder(); + }; + this._store.add(this._inputEditor.onDidFocusEditorWidget(updateFocused)); + this._store.add(this._inputEditor.onDidBlurEditorWidget(updateFocused)); + this._store.add(toDisposable(() => { + this._ctxInnerCursorFirst.reset(); + this._ctxInnerCursorLast.reset(); + this._ctxInputEditorFocused.reset(); + })); + updateFocused(); + + + // show/hide placeholder depending on text model being empty + // content height + const currentContentHeight = 0; + const togglePlaceholder = () => { + const hasText = this._inputModel.getValueLength() > 0; + this._elements.placeholder.classList.toggle('hidden', hasText); + this._ctxInputEmpty.set(!hasText); + this.readPlaceholder(); + + const contentHeight = this._inputEditor.getContentHeight(); + if (contentHeight !== currentContentHeight) { + this._onDidChangeHeight.fire(); + } + }; + this._store.add(this._inputModel.onDidChangeContent(togglePlaceholder)); + togglePlaceholder(); + } + + dispose(): void { + this.reset(); + this._store.dispose(); + } + + get domNode() { + return this._elements.content; + } + + layout(dim: Dimension) { + const toolbarWidth = getTotalWidth(this._elements.editorToolbar) + 8 /* L/R-padding */; + const editorWidth = dim.width - toolbarWidth; + this._inputEditor.layout({ height: dim.height, width: editorWidth }); + this._elements.placeholder.style.width = `${editorWidth}px`; + } + + getPreferredHeight(): number { + return this._inputEditor.getContentHeight(); + } + + reset() { + this._ctxInputEmpty.reset(); + this._ctxInnerCursorFirst.reset(); + this._ctxInnerCursorLast.reset(); + this._ctxInnerCursorStart.reset(); + this._ctxInnerCursorEnd.reset(); + this._ctxInputEditorFocused.reset(); + this.value = ''; + } + + focus() { + this._inputEditor.focus(); + } + + get value(): string { + return this._inputModel.getValue(); + } + + set value(value: string) { + this._inputModel.setValue(value); + this._inputEditor.setPosition(this._inputModel.getFullModelRange().getEndPosition()); + } + + selectAll(includeSlashCommand: boolean = true) { + let selection = this._inputModel.getFullModelRange(); + + if (!includeSlashCommand) { + const firstLine = this._inputModel.getLineContent(1); + const slashCommand = this._slashCommandDetails.find(c => firstLine.startsWith(`/${c.command} `)); + selection = slashCommand ? new Range(1, slashCommand.command.length + 3, selection.endLineNumber, selection.endColumn) : selection; + } + + this._inputEditor.setSelection(selection); + } + + set ariaLabel(label: string) { + this._inputEditor.updateOptions({ ariaLabel: label }); + } + + set placeholder(value: string) { + this._elements.placeholder.innerText = value; + } + + readPlaceholder(): void { + const slashCommand = this._slashCommandDetails.find(c => `${c.command} ` === this._inputModel.getValue().substring(1)); + const hasText = this._inputModel.getValueLength() > 0; + if (!hasText) { + aria.status(this._elements.placeholder.innerText); + } else if (slashCommand) { + aria.status(slashCommand.detail); + } + } + + updateSlashCommands(commands: IInlineChatSlashCommand[]) { + + this._slashCommands.clear(); + this._slashCommandDetails = commands.filter(c => c.command && c.detail).map(c => { return { command: c.command, detail: c.detail! }; }); + + if (this._slashCommandDetails.length === 0) { + return; + } + + const selector: LanguageSelector = { scheme: this._inputModel.uri.scheme, pattern: this._inputModel.uri.path, language: this._inputModel.getLanguageId() }; + this._slashCommands.add(this._languageFeaturesService.completionProvider.register(selector, new class implements CompletionItemProvider { + + _debugDisplayName: string = 'InlineChatSlashCommandProvider'; + + readonly triggerCharacters?: string[] = ['/']; + + provideCompletionItems(_model: ITextModel, position: Position): ProviderResult { + if (position.lineNumber !== 1 && position.column !== 1) { + return undefined; + } + + const suggestions: CompletionItem[] = commands.map(command => { + + const withSlash = `/${command.command}`; + + return { + label: { label: withSlash, description: command.detail }, + insertText: `${withSlash} $0`, + insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet, + kind: CompletionItemKind.Text, + range: new Range(1, 1, 1, 1), + command: command.executeImmediately ? { id: 'inlineChat.accept', title: withSlash } : undefined + }; + }); + + return { suggestions }; + } + })); + + const decorations = this._inputEditor.createDecorationsCollection(); + + const updateSlashDecorations = () => { + this._slashCommandContentWidget.hide(); + // TODO@jrieken + // this._elements.detectedIntent.classList.toggle('hidden', true); + + const newDecorations: IModelDeltaDecoration[] = []; + for (const command of commands) { + const withSlash = `/${command.command}`; + const firstLine = this._inputModel.getLineContent(1); + if (firstLine.startsWith(withSlash)) { + newDecorations.push({ + range: new Range(1, 1, 1, withSlash.length + 1), + options: { + description: 'inline-chat-slash-command', + inlineClassName: 'inline-chat-slash-command', + after: { + // Force some space between slash command and placeholder + content: ' ' + } + } + }); + + this._slashCommandContentWidget.setCommandText(command.command); + this._slashCommandContentWidget.show(); + + // inject detail when otherwise empty + if (firstLine === `/${command.command}`) { + newDecorations.push({ + range: new Range(1, withSlash.length + 1, 1, withSlash.length + 2), + options: { + description: 'inline-chat-slash-command-detail', + after: { + content: `${command.detail}`, + inlineClassName: 'inline-chat-slash-command-detail' + } + } + }); + } + break; + } + } + decorations.set(newDecorations); + }; + + this._slashCommands.add(this._inputEditor.onDidChangeModelContent(updateSlashDecorations)); + updateSlashDecorations(); + } +} + +export const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); + +export const inputEditorOptions: IEditorConstructionOptions = { + padding: { top: 2, bottom: 2 }, + overviewRulerLanes: 0, + glyphMargin: false, + lineNumbers: 'off', + folding: false, + hideCursorInOverviewRuler: true, + selectOnLineNumbers: false, + selectionHighlight: false, + scrollbar: { + useShadows: false, + vertical: 'hidden', + horizontal: 'auto', + alwaysConsumeMouseWheel: false + }, + lineDecorationsWidth: 0, + overviewRulerBorder: false, + scrollBeyondLastLine: false, + renderLineHighlight: 'none', + fixedOverflowWidgets: true, + dragAndDrop: false, + revealHorizontalRightPadding: 5, + minimap: { enabled: false }, + guides: { indentation: false }, + rulers: [], + cursorWidth: 1, + cursorStyle: 'line', + cursorBlinking: 'blink', + wrappingStrategy: 'advanced', + wrappingIndent: 'none', + renderWhitespace: 'none', + dropIntoEditor: { enabled: true }, + quickSuggestions: false, + suggest: { + showIcons: false, + showSnippets: false, + showWords: true, + showStatusBar: false, + }, + wordWrap: 'on', + ariaLabel: defaultAriaLabel, + fontFamily: DEFAULT_FONT_FAMILY, + fontSize: 13, + lineHeight: 20 +}; + +export const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { + isSimpleWidget: true, + contributions: EditorExtensionsRegistry.getSomeEditorContributions([ + SnippetController2.ID, + SuggestController.ID + ]) +}; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 25d8dbb6cfa..fb7f2c67237 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -5,20 +5,15 @@ import { Dimension, addDisposableListener, getActiveElement, getTotalHeight, getTotalWidth, h, reset } from 'vs/base/browser/dom'; import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; -import * as aria from 'vs/base/browser/ui/aria/aria'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { Lazy } from 'vs/base/common/lazy'; -import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { ISettableObservable, constObservable, derived, observableValue } from 'vs/base/common/observable'; -import { URI } from 'vs/base/common/uri'; import 'vs/css!./inlineChat'; -import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; -import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; -import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; +import { ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer'; import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; @@ -28,14 +23,8 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; -import { LanguageSelector } from 'vs/editor/common/languageSelector'; -import { CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages'; -import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { IModelService } from 'vs/editor/common/services/model'; +import { ITextModel } from 'vs/editor/common/model'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; -import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; import { localize } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; @@ -49,80 +38,24 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ILogService } from 'vs/platform/log/common/log'; import { editorBackground, editorForeground, inputBackground } from 'vs/platform/theme/common/colorRegistry'; import { ResourceLabel } from 'vs/workbench/browser/labels'; -import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; import { ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; -import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CodeBlockModelCollection } from 'vs/workbench/contrib/chat/common/codeBlockModelCollection'; import { HunkData, HunkInformation, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; -import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_RESPONSE_FOCUSED, IInlineChatFollowup, IInlineChatSlashCommand } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_RESPONSE_FOCUSED, IInlineChatFollowup, IInlineChatSlashCommand } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { inputEditorOptions, codeEditorWidgetOptions, InlineChatInputWidget, defaultAriaLabel } from './inlineChatInputWidget'; -const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); - -export const _inputEditorOptions: IEditorConstructionOptions = { - padding: { top: 2, bottom: 2 }, - overviewRulerLanes: 0, - glyphMargin: false, - lineNumbers: 'off', - folding: false, - hideCursorInOverviewRuler: true, - selectOnLineNumbers: false, - selectionHighlight: false, - scrollbar: { - useShadows: false, - vertical: 'hidden', - horizontal: 'auto', - alwaysConsumeMouseWheel: false - }, - lineDecorationsWidth: 0, - overviewRulerBorder: false, - scrollBeyondLastLine: false, - renderLineHighlight: 'none', - fixedOverflowWidgets: true, - dragAndDrop: false, - revealHorizontalRightPadding: 5, - minimap: { enabled: false }, - guides: { indentation: false }, - rulers: [], - cursorWidth: 1, - cursorStyle: 'line', - cursorBlinking: 'blink', - wrappingStrategy: 'advanced', - wrappingIndent: 'none', - renderWhitespace: 'none', - dropIntoEditor: { enabled: true }, - quickSuggestions: false, - suggest: { - showIcons: false, - showSnippets: false, - showWords: true, - showStatusBar: false, - }, - wordWrap: 'on', - ariaLabel: defaultAriaLabel, - fontFamily: DEFAULT_FONT_FAMILY, - fontSize: 13, - lineHeight: 20 -}; - -const _codeEditorWidgetOptions: ICodeEditorWidgetOptions = { - isSimpleWidget: true, - contributions: EditorExtensionsRegistry.getSomeEditorContributions([ - SnippetController2.ID, - SuggestController.ID - ]) -}; const _previewEditorEditorOptions: IDiffEditorConstructionOptions = { scrollbar: { useShadows: false, alwaysConsumeMouseWheel: false, ignoreHorizontalScrollbarInContentHeight: true, }, @@ -180,25 +113,17 @@ export interface IInlineChatMessageAppender { export class InlineChatWidget { - private static _modelPool: number = 1; - protected readonly _elements = h( 'div.inline-chat@root', [ - h('div.body', [ - h('div.content@content', [ - h('div.input@input', [ - h('div.editor-placeholder@placeholder'), - h('div.editor-container@editor'), - ]), - h('div.toolbar@editorToolbar'), - ]), + h('div.body@body', [ + h('div.content@content'), h('div.widget-toolbar@widgetToolbar') ]), h('div.progress@progress'), h('div.detectedIntent.hidden@detectedIntent'), h('div.previewDiff.hidden@previewDiff'), - h('div.previewCreateTitle.show-file-icons@previewCreateTitle'), + h('div.previewCreateTitle.show-file-icons.hidden@previewCreateTitle'), h('div.previewCreate.hidden@previewCreate'), h('div.chatMessage.hidden@chatMessage'), h('div.followUps.hidden@followUps'), @@ -216,16 +141,9 @@ export class InlineChatWidget { private readonly _chatMessageScrollable: DomScrollableElement; protected readonly _store = new DisposableStore(); - private readonly _slashCommands = this._store.add(new DisposableStore()); - private readonly _inputEditor: IActiveCodeEditor; - private readonly _inputModel: ITextModel; - private readonly _ctxInputEmpty: IContextKey; - private readonly _ctxInnerCursorFirst: IContextKey; - private readonly _ctxInnerCursorLast: IContextKey; - private readonly _ctxInnerCursorStart: IContextKey; - private readonly _ctxInnerCursorEnd: IContextKey; - private readonly _ctxInputEditorFocused: IContextKey; + private readonly _inputWidget: InlineChatInputWidget; + private readonly _ctxResponseFocused: IContextKey; private readonly _progressBar: ProgressBar; @@ -243,9 +161,7 @@ export class InlineChatWidget { private _lastDim: Dimension | undefined; private _isLayouting: boolean = false; - private _slashCommandDetails: { command: string; detail: string }[] = []; - private _slashCommandContentWidget: SlashCommandContentWidget; private readonly _editorOptions: ChatEditorOptions; private _chatMessageDisposables = this._store.add(new DisposableStore()); @@ -258,9 +174,7 @@ export class InlineChatWidget { constructor( options: IInlineChatWidgetConstructionOptions, @IInstantiationService protected readonly _instantiationService: IInstantiationService, - @IModelService private readonly _modelService: IModelService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -269,20 +183,16 @@ export class InlineChatWidget { @ITextModelService protected readonly _textModelResolverService: ITextModelService, @IChatAgentService private readonly _chatAgentService: IChatAgentService, ) { + // Share hover delegates between toolbars to support instant hover between both + const hoverDelegate = this._store.add(createInstantHoverDelegate()); // input editor logic - this._inputEditor = this._instantiationService.createInstance(CodeEditorWidget, this._elements.editor, _inputEditorOptions, _codeEditorWidgetOptions); - this._updateAriaLabel(); - this._store.add(this._inputEditor); - this._store.add(this._inputEditor.onDidChangeModelContent(() => this._onDidChangeInput.fire(this))); - this._store.add(this._inputEditor.onDidLayoutChange(() => this._onDidChangeHeight.fire())); - this._store.add(this._inputEditor.onDidContentSizeChange(() => this._onDidChangeHeight.fire())); + this._inputWidget = this._instantiationService.createInstance(InlineChatInputWidget, { menuId: options.inputMenuId, telemetrySource: options.telemetrySource, hoverDelegate }); + this._elements.body.replaceChild(this._inputWidget.domNode, this._elements.content); + this._store.add(this._inputWidget); + this._store.add(this._inputWidget.onDidChangeHeight(() => this._onDidChangeHeight.fire())); - const uri = URI.from({ scheme: 'vscode', authority: 'inline-chat', path: `/inline-chat/model${InlineChatWidget._modelPool++}.txt` }); - this._inputModel = this._store.add(this._modelService.getModel(uri) ?? this._modelService.createModel('', null, uri)); - this._inputEditor.setModel(this._inputModel); - this._editorOptions = this._store.add(_instantiationService.createInstance(ChatEditorOptions, undefined, editorForeground, inputBackground, editorBackground)); this._chatMessageContents = document.createElement('div'); @@ -301,103 +211,10 @@ export class InlineChatWidget { } })); - // --- context keys - - this._ctxInputEmpty = CTX_INLINE_CHAT_EMPTY.bindTo(this._contextKeyService); - - this._ctxInnerCursorFirst = CTX_INLINE_CHAT_INNER_CURSOR_FIRST.bindTo(this._contextKeyService); - this._ctxInnerCursorLast = CTX_INLINE_CHAT_INNER_CURSOR_LAST.bindTo(this._contextKeyService); - this._ctxInnerCursorStart = CTX_INLINE_CHAT_INNER_CURSOR_START.bindTo(this._contextKeyService); - this._ctxInnerCursorEnd = CTX_INLINE_CHAT_INNER_CURSOR_END.bindTo(this._contextKeyService); - this._ctxInputEditorFocused = CTX_INLINE_CHAT_FOCUSED.bindTo(this._contextKeyService); + // context keys this._ctxResponseFocused = CTX_INLINE_CHAT_RESPONSE_FOCUSED.bindTo(this._contextKeyService); - // (1) inner cursor position (last/first line selected) - const updateInnerCursorFirstLast = () => { - const selection = this._inputEditor.getSelection(); - const fullRange = this._inputModel.getFullModelRange(); - let onFirst = false; - let onLast = false; - if (selection.isEmpty()) { - const selectionTop = this._inputEditor.getTopForPosition(selection.startLineNumber, selection.startColumn); - const firstViewLineTop = this._inputEditor.getTopForPosition(fullRange.startLineNumber, fullRange.startColumn); - const lastViewLineTop = this._inputEditor.getTopForPosition(fullRange.endLineNumber, fullRange.endColumn); - - if (selectionTop === firstViewLineTop) { - onFirst = true; - } - if (selectionTop === lastViewLineTop) { - onLast = true; - } - } - this._ctxInnerCursorFirst.set(onFirst); - this._ctxInnerCursorLast.set(onLast); - this._ctxInnerCursorStart.set(fullRange.getStartPosition().equals(selection.getStartPosition())); - this._ctxInnerCursorEnd.set(fullRange.getEndPosition().equals(selection.getEndPosition())); - }; - this._store.add(this._inputEditor.onDidChangeCursorPosition(updateInnerCursorFirstLast)); - updateInnerCursorFirstLast(); - - // (2) input editor focused or not - const updateFocused = () => { - const hasFocus = this._inputEditor.hasWidgetFocus(); - this._ctxInputEditorFocused.set(hasFocus); - this._elements.content.classList.toggle('synthetic-focus', hasFocus); - this.readPlaceholder(); - }; - this._store.add(this._inputEditor.onDidFocusEditorWidget(updateFocused)); - this._store.add(this._inputEditor.onDidBlurEditorWidget(updateFocused)); - this._store.add(toDisposable(() => { - this._ctxInnerCursorFirst.reset(); - this._ctxInnerCursorLast.reset(); - this._ctxInputEditorFocused.reset(); - })); - updateFocused(); - - // placeholder - - this._elements.placeholder.style.fontSize = `${this._inputEditor.getOption(EditorOption.fontSize)}px`; - this._elements.placeholder.style.lineHeight = `${this._inputEditor.getOption(EditorOption.lineHeight)}px`; - this._store.add(addDisposableListener(this._elements.placeholder, 'click', () => this._inputEditor.focus())); - - // show/hide placeholder depending on text model being empty - // content height - - const currentContentHeight = 0; - - const togglePlaceholder = () => { - const hasText = this._inputModel.getValueLength() > 0; - this._elements.placeholder.classList.toggle('hidden', hasText); - this._ctxInputEmpty.set(!hasText); - this.readPlaceholder(); - - const contentHeight = this._inputEditor.getContentHeight(); - if (contentHeight !== currentContentHeight && this._lastDim) { - this._lastDim = this._lastDim.with(undefined, contentHeight); - this._inputEditor.layout(this._lastDim); - this._onDidChangeHeight.fire(); - } - }; - this._store.add(this._inputModel.onDidChangeContent(togglePlaceholder)); - togglePlaceholder(); - - // slash command content widget - - this._slashCommandContentWidget = new SlashCommandContentWidget(this._inputEditor); - this._store.add(this._slashCommandContentWidget); - - // Share hover delegates between toolbars to support instant hover between both - const hoverDelegate = this._store.add(createInstantHoverDelegate()); - // toolbars - - this._store.add(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.editorToolbar, options.inputMenuId, { - telemetrySource: options.telemetrySource, - toolbarOptions: { primaryGroup: 'main' }, - hiddenItemStrategy: HiddenItemStrategy.Ignore, // keep it lean when hiding items and avoid a "..." overflow menu - hoverDelegate - })); - this._progressBar = new ProgressBar(this._elements.progress); this._store.add(this._progressBar); @@ -446,7 +263,6 @@ export class InlineChatWidget { this._codeBlockModelCollection = this._store.add(this._instantiationService.createInstance(CodeBlockModelCollection)); } - private _updateAriaLabel(): void { if (!this._accessibilityService.isScreenReaderOptimized()) { return; @@ -456,13 +272,12 @@ export class InlineChatWidget { 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; - this._inputEditor.updateOptions({ ariaLabel: label }); + inputEditorOptions.ariaLabel = label; + this._inputWidget.ariaLabel = label; } dispose(): void { this._store.dispose(); - this._ctxInputEmpty.reset(); } get domNode(): HTMLElement { @@ -473,16 +288,14 @@ export class InlineChatWidget { this._isLayouting = true; try { const widgetToolbarWidth = getTotalWidth(this._elements.widgetToolbar); - const editorToolbarWidth = getTotalWidth(this._elements.editorToolbar) + 8 /* L/R-padding */; - const innerEditorWidth = widgetDim.width - editorToolbarWidth - widgetToolbarWidth; - const inputDim = new Dimension(innerEditorWidth, widgetDim.height); + const innerEditorWidth = widgetDim.width - widgetToolbarWidth; + const inputDim = new Dimension(innerEditorWidth, this._inputWidget.getPreferredHeight()); if (!this._lastDim || !Dimension.equals(this._lastDim, inputDim)) { this._lastDim = inputDim; this._doLayout(widgetDim, inputDim); - - this._onDidChangeLayout.fire(); } } finally { + this._onDidChangeLayout.fire(); this._isLayouting = false; } } @@ -490,19 +303,18 @@ export class InlineChatWidget { protected _doLayout(widgetDimension: Dimension, inputDimension: Dimension): void { this._chatMessageContents.style.width = `${widgetDimension.width - 10}px`; this._chatMessageContents.style.maxHeight = `270px`; - this._inputEditor.layout(new Dimension(inputDimension.width, this._inputEditor.getContentHeight())); - this._elements.placeholder.style.width = `${inputDimension.width}px`; + + this._inputWidget.layout(inputDimension); } getHeight(): number { - const editorHeight = this._inputEditor.getContentHeight() + 12 /* padding and border */; + const editorHeight = this._inputWidget.getPreferredHeight() + 4 /*padding*/; const progressHeight = getTotalHeight(this._elements.progress); const detectedIntentHeight = getTotalHeight(this._elements.detectedIntent); - const chatResponseHeight = getTotalHeight(this._chatMessageContents) + 16 /*padding*/; + const chatResponseHeight = getTotalHeight(this._chatMessageContents); const followUpsHeight = getTotalHeight(this._elements.followUps); - const statusHeight = getTotalHeight(this._elements.status); - return progressHeight + editorHeight + detectedIntentHeight + followUpsHeight + chatResponseHeight + statusHeight + 18 /* padding */ + 8 /*shadow*/; + return progressHeight + editorHeight + detectedIntentHeight + followUpsHeight + chatResponseHeight + statusHeight + 12 /* padding */ + 2 /*border*/ + 12 /*shadow*/; } updateProgress(show: boolean) { @@ -516,38 +328,23 @@ export class InlineChatWidget { } get value(): string { - return this._inputModel.getValue(); + return this._inputWidget.value; } set value(value: string) { - this._inputModel.setValue(value); - this._inputEditor.setPosition(this._inputModel.getFullModelRange().getEndPosition()); + this._inputWidget.value = value; } selectAll(includeSlashCommand: boolean = true) { - let selection = this._inputModel.getFullModelRange(); - - if (!includeSlashCommand) { - const firstLine = this._inputModel.getLineContent(1); - const slashCommand = this._slashCommandDetails.find(c => firstLine.startsWith(`/${c.command} `)); - selection = slashCommand ? new Range(1, slashCommand.command.length + 3, selection.endLineNumber, selection.endColumn) : selection; - } - - this._inputEditor.setSelection(selection); + this._inputWidget.selectAll(includeSlashCommand); } set placeholder(value: string) { - this._elements.placeholder.innerText = value; + this._inputWidget.placeholder = value; } readPlaceholder(): void { - const slashCommand = this._slashCommandDetails.find(c => `${c.command} ` === this._inputModel.getValue().substring(1)); - const hasText = this._inputModel.getValueLength() > 0; - if (!hasText) { - aria.status(this._elements.placeholder.innerText); - } else if (slashCommand) { - aria.status(slashCommand.detail); - } + this._inputWidget.readPlaceholder(); } updateToolbar(show: boolean) { @@ -596,6 +393,9 @@ export class InlineChatWidget { appendContent: (fragment: string) => { responseModel.updateContent({ kind: 'markdownContent', content: new MarkdownString(fragment) }); this._chatMessage?.appendMarkdown(fragment); + renderer.layout(this._chatMessageContents.clientWidth - 4); + this._chatMessageScrollable.scanDomNode(); + this._onDidChangeHeight.fire(); } } : undefined; } @@ -616,8 +416,15 @@ export class InlineChatWidget { this._onDidChangeHeight.fire(); } + private _currentSlashCommands: IInlineChatSlashCommand[] = []; + + updateSlashCommands(commands: IInlineChatSlashCommand[]) { + this._currentSlashCommands = commands; + this._inputWidget.updateSlashCommands(commands); + } + updateSlashCommandUsed(command: string): void { - const details = this._slashCommandDetails.find(candidate => candidate.command === command); + const details = this._currentSlashCommands.find(candidate => candidate.command === command); if (!details) { return; } @@ -675,12 +482,7 @@ export class InlineChatWidget { } reset() { - this._ctxInputEmpty.reset(); - this._ctxInnerCursorFirst.reset(); - this._ctxInnerCursorLast.reset(); - this._ctxInputEditorFocused.reset(); - - this.value = ''; + this._inputWidget.reset(); this.updateChatMessage(undefined); this.updateFollowUps(undefined); @@ -697,102 +499,13 @@ export class InlineChatWidget { } focus() { - this._inputEditor.focus(); + this._inputWidget.focus(); } hasFocus() { return this.domNode.contains(getActiveElement()); } - // --- slash commands - - updateSlashCommands(commands: IInlineChatSlashCommand[]) { - - this._slashCommands.clear(); - - if (commands.length === 0) { - return; - } - this._slashCommandDetails = commands.filter(c => c.command && c.detail).map(c => { return { command: c.command, detail: c.detail! }; }); - - const selector: LanguageSelector = { scheme: this._inputModel.uri.scheme, pattern: this._inputModel.uri.path, language: this._inputModel.getLanguageId() }; - this._slashCommands.add(this._languageFeaturesService.completionProvider.register(selector, new class implements CompletionItemProvider { - - _debugDisplayName: string = 'InlineChatSlashCommandProvider'; - - readonly triggerCharacters?: string[] = ['/']; - - provideCompletionItems(_model: ITextModel, position: Position): ProviderResult { - if (position.lineNumber !== 1 && position.column !== 1) { - return undefined; - } - - const suggestions: CompletionItem[] = commands.map(command => { - - const withSlash = `/${command.command}`; - - return { - label: { label: withSlash, description: command.detail }, - insertText: `${withSlash} $0`, - insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet, - kind: CompletionItemKind.Text, - range: new Range(1, 1, 1, 1), - command: command.executeImmediately ? { id: 'inlineChat.accept', title: withSlash } : undefined - }; - }); - - return { suggestions }; - } - })); - - const decorations = this._inputEditor.createDecorationsCollection(); - - const updateSlashDecorations = () => { - this._slashCommandContentWidget.hide(); - this._elements.detectedIntent.classList.toggle('hidden', true); - - const newDecorations: IModelDeltaDecoration[] = []; - for (const command of commands) { - const withSlash = `/${command.command}`; - const firstLine = this._inputModel.getLineContent(1); - if (firstLine.startsWith(withSlash)) { - newDecorations.push({ - range: new Range(1, 1, 1, withSlash.length + 1), - options: { - description: 'inline-chat-slash-command', - inlineClassName: 'inline-chat-slash-command', - after: { - // Force some space between slash command and placeholder - content: ' ' - } - } - }); - - this._slashCommandContentWidget.setCommandText(command.command); - this._slashCommandContentWidget.show(); - - // inject detail when otherwise empty - if (firstLine === `/${command.command}`) { - newDecorations.push({ - range: new Range(1, withSlash.length + 1, 1, withSlash.length + 2), - options: { - description: 'inline-chat-slash-command-detail', - after: { - content: `${command.detail}`, - inlineClassName: 'inline-chat-slash-command-detail' - } - } - }); - } - break; - } - } - decorations.set(newDecorations); - }; - - this._slashCommands.add(this._inputEditor.onDidChangeModelContent(updateSlashDecorations)); - updateSlashDecorations(); - } } export class EditorBasedInlineChatWidget extends InlineChatWidget { @@ -809,9 +522,7 @@ export class EditorBasedInlineChatWidget extends InlineChatWidget { constructor( private readonly _parentEditor: ICodeEditor, options: IInlineChatWidgetConstructionOptions, - @IModelService modelService: IModelService, @IContextKeyService contextKeyService: IContextKeyService, - @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, @IAccessibilityService accessibilityService: IAccessibilityService, @@ -821,17 +532,17 @@ export class EditorBasedInlineChatWidget extends InlineChatWidget { @ITextModelService textModelResolverService: ITextModelService, @IChatAgentService chatAgentService: IChatAgentService, ) { - super(options, instantiationService, modelService, contextKeyService, languageFeaturesService, keybindingService, accessibilityService, configurationService, accessibleViewService, logService, textModelResolverService, chatAgentService,); + super(options, instantiationService, contextKeyService, keybindingService, accessibilityService, configurationService, accessibleViewService, logService, textModelResolverService, chatAgentService,); // preview editors this._previewDiffEditor = new Lazy(() => this._store.add(instantiationService.createInstance(EmbeddedDiffEditorWidget, this._elements.previewDiff, { useInlineViewWhenSpaceIsLimited: false, ..._previewEditorEditorOptions, onlyShowAccessibleDiffViewer: accessibilityService.isScreenReaderOptimized(), - }, { modifiedEditor: _codeEditorWidgetOptions, originalEditor: _codeEditorWidgetOptions }, _parentEditor))); + }, { modifiedEditor: codeEditorWidgetOptions, originalEditor: codeEditorWidgetOptions }, _parentEditor))); this._previewCreateTitle = this._store.add(instantiationService.createInstance(ResourceLabel, this._elements.previewCreateTitle, { supportIcons: true })); - this._previewCreateEditor = new Lazy(() => this._store.add(instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.previewCreate, _previewEditorEditorOptions, _codeEditorWidgetOptions, _parentEditor))); + this._previewCreateEditor = new Lazy(() => this._store.add(instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.previewCreate, _previewEditorEditorOptions, codeEditorWidgetOptions, _parentEditor))); } // --- layout From 5c90480e9b67fe2682d489cdb887f1a80cc8b937 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 7 Mar 2024 18:50:48 +0100 Subject: [PATCH 1846/1897] Add border bottom to activity bar top (#207094) add border bottom when activity bar top --- src/vs/workbench/browser/parts/media/paneCompositePart.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index 2155798ed82..455b38249d9 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -20,6 +20,10 @@ display: none; } +.monaco-workbench .pane-composite-part > .header-or-footer.header { + border-bottom: 1px solid var(--vscode-sideBarSectionHeader-border); +} + .monaco-workbench .pane-composite-part > .header-or-footer.footer { border-top: 1px solid var(--vscode-sideBarSectionHeader-border); } From 46ff95b5b10f62743a78174a64b5c4b16ee19707 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 7 Mar 2024 10:01:56 -0800 Subject: [PATCH 1847/1897] Pick up latest stable TS release (#207095) --- extensions/package.json | 2 +- extensions/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/package.json b/extensions/package.json index 7066412c3f8..ab93f194b51 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -4,7 +4,7 @@ "license": "MIT", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "5.4.1-rc" + "typescript": "5.4" }, "scripts": { "postinstall": "node ./postinstall.mjs" diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 4b22ef50a82..23ab31c1254 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -234,10 +234,10 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -typescript@5.4.1-rc: - version "5.4.1-rc" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.1-rc.tgz#1ecdd897df1d9ef5bd1f844bad64691ecc23314d" - integrity sha512-gInURzaO0bbfzfQAc3mfcHxh8qev+No4QOFUZHajo9vBgOLaljELJ3wuzyoGo/zHIzMSezdhtrsRdqL6E9SvNA== +typescript@5.4: + version "5.4.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.2.tgz#0ae9cebcfae970718474fe0da2c090cad6577372" + integrity sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ== vscode-grammar-updater@^1.1.0: version "1.1.0" From 3c318f2facf9a8fd28617b6a8d52b7acf5c45c4d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 7 Mar 2024 11:31:23 -0800 Subject: [PATCH 1848/1897] wip move inline chat response inside of widget --- .../chat/browser/media/terminalChatWidget.css | 23 --- .../chat/browser/terminalChatController.ts | 29 +-- .../chat/browser/terminalChatWidget.ts | 179 +----------------- 3 files changed, 8 insertions(+), 223 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css index 64253f67db5..ad8623e881c 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css @@ -19,29 +19,6 @@ visibility: hidden; } -.terminal-inline-chat-response.hide { - visibility: hidden; -} - .terminal-inline-chat .chatMessageContent { width: 400px !important; } - -.terminal-inline-chat .terminal-inline-chat-response { - border: 1px solid var(--vscode-input-border, transparent); - background-color: var(--vscode-panel-background); -} - -.terminal-inline-chat .terminal-inline-chat-response:has(.monaco-editor.focused) { - border-color: var(--vscode-focusBorder, transparent); -} - -.terminal-inline-chat .terminal-inline-chat-response, -.terminal-inline-chat .terminal-inline-chat-response .monaco-editor, -.terminal-inline-chat .terminal-inline-chat-response .monaco-editor .overflow-guard { - border-radius: 4px; -} - -.terminal-inline-chat .terminal-inline-chat-response { - padding-left: 8px; -} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 2a505e3a1d3..36988724bbb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -8,7 +8,6 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { Emitter, Event } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; -import { marked } from 'vs/base/common/marked/marked'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -22,10 +21,9 @@ import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTermi import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; - - import { ChatModel, ChatRequestModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; import { TerminalChatContextKeys, TerminalChatResponseTypes } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { MarkdownString } from 'vs/base/common/htmlContent'; const enum Message { NONE = 0, @@ -236,8 +234,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._requestActiveContextKey.set(true); const cancellationToken = new CancellationTokenSource().token; let responseContent = ''; - let firstCodeBlock: string | undefined; - let shellType: string | undefined; const progressCallback = (progress: IChatProgress) => { if (cancellationToken.isCancellationRequested) { return; @@ -249,22 +245,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (this._currentRequest) { this._model?.acceptResponseProgress(this._currentRequest, progress); } - if (!firstCodeBlock) { - const firstCodeBlockContent = marked.lexer(responseContent).filter(token => token.type === 'code')?.[0]?.raw; - if (firstCodeBlockContent) { - const regex = /```(?[\w\n]+)\n(?[\s\S]*?)```/g; - const match = regex.exec(firstCodeBlockContent); - firstCodeBlock = match?.groups?.content.trim(); - shellType = match?.groups?.language; - if (firstCodeBlock) { - this._chatWidget?.value.renderTerminalCommand(firstCodeBlock, shellType); - this._chatAccessibilityService.acceptResponse(firstCodeBlock, accessibilityRequestId); - this._responseTypeContextKey.set(TerminalChatResponseTypes.TerminalCommand); - this._chatWidget?.value.inlineChatWidget.updateToolbar(true); - this._messages.fire(Message.ACCEPT_INPUT); - } - } - } }; await this._model?.waitForInitialization(); @@ -301,10 +281,11 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._model?.completeResponse(this._currentRequest); } this._lastResponseContent = responseContent; - if (!firstCodeBlock && this._currentRequest) { + if (this._currentRequest) { this._chatAccessibilityService.acceptResponse(responseContent, accessibilityRequestId); - this._chatWidget?.value.renderMessage(responseContent, this._currentRequest.id); - this._responseTypeContextKey.set(TerminalChatResponseTypes.Message); + this._chatWidget?.value.inlineChatWidget.updateChatMessage({ message: new MarkdownString(responseContent), requestId: this._currentRequest.id, providerId: 'terminal' }); + const containsCode = responseContent.includes('```'); + this._responseTypeContextKey.set(containsCode ? TerminalChatResponseTypes.TerminalCommand : TerminalChatResponseTypes.Message); this._chatWidget?.value.inlineChatWidget.updateToolbar(true); this._messages.fire(Message.ACCEPT_INPUT); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 85523a67b57..dcc6e9d5d55 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -3,21 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Dimension, IFocusTracker, hide, show, trackFocus } from 'vs/base/browser/dom'; -import { Event } from 'vs/base/common/event'; -import { MarkdownString } from 'vs/base/common/htmlContent'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; +import { Dimension, IFocusTracker, trackFocus } from 'vs/base/browser/dom'; +import { Disposable } from 'vs/base/common/lifecycle'; import 'vs/css!./media/terminalChatWidget'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; -import { ILanguageService } from 'vs/editor/common/languages/language'; -import { ITextModel } from 'vs/editor/common/model'; -import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; -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 { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -30,8 +21,6 @@ export class TerminalChatWidget extends Disposable { private readonly _inlineChatWidget: InlineChatWidget; public get inlineChatWidget(): InlineChatWidget { return this._inlineChatWidget; } - private _responseEditor: TerminalChatResponseEditor | undefined; - private readonly _focusTracker: IFocusTracker; private readonly _focusedContextKey: IContextKey; @@ -73,19 +62,6 @@ export class TerminalChatWidget extends Disposable { this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated commands may be incorrect")); } - async renderTerminalCommand(command: string, shellType?: string): Promise { - this._responseEditor?.show(); - if (!this._responseEditor) { - this._responseEditor = this._instantiationService.createInstance(TerminalChatResponseEditor, command, shellType, this._container, this._instance); - } - this._responseEditor.setValue(command); - } - - renderMessage(message: string, requestId: string): void { - this._responseEditor?.hide(); - this._inlineChatWidget.updateChatMessage({ message: new MarkdownString(message), requestId, providerId: 'terminal' }); - } - reveal(): void { this._inlineChatWidget.layout(new Dimension(640, 150)); this._container.classList.remove('hide'); @@ -118,8 +94,6 @@ export class TerminalChatWidget extends Disposable { hide(): void { this._container.classList.add('hide'); this._reset(); - this._responseEditor?.dispose(); - this._responseEditor = undefined; this._inlineChatWidget.updateChatMessage(undefined); this._inlineChatWidget.updateFollowUps(undefined); this._inlineChatWidget.updateProgress(false); @@ -140,13 +114,10 @@ export class TerminalChatWidget extends Disposable { } setValue(value?: string) { this._inlineChatWidget.value = value ?? ''; - if (!value) { - this._responseEditor?.hide(); - } } acceptCommand(shouldExecute: boolean): void { // Trim command to remove any whitespace, otherwise this may execute the command - const value = this._responseEditor?.getValue().trim(); + const value = this._inlineChatWidget?.responseContent?.trim(); if (!value) { return; } @@ -161,147 +132,3 @@ export class TerminalChatWidget extends Disposable { } } -class TerminalChatResponseEditor extends Disposable { - private readonly _editorContainer: HTMLElement; - private readonly _editor: CodeEditorWidget; - - private readonly _responseEditorFocusedContextKey: IContextKey; - - readonly model: Promise; - - constructor( - initialCommandResponse: string, - shellType: string | undefined, - private readonly _container: HTMLElement, - private readonly _instance: ITerminalInstance, - @IConfigurationService private readonly _configurationService: IConfigurationService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @ILanguageService private readonly _languageService: ILanguageService, - @IModelService private readonly _modelService: IModelService, - ) { - super(); - - this._responseEditorFocusedContextKey = TerminalChatContextKeys.responseEditorFocused.bindTo(this._contextKeyService); - - this._editorContainer = document.createElement('div'); - this._editorContainer.classList.add('terminal-inline-chat-response'); - this._container.prepend(this._editorContainer); - this._register(toDisposable(() => this._editorContainer.remove())); - const editor = this._register(this._instantiationService.createInstance(CodeEditorWidget, this._editorContainer, { - readOnly: false, - ariaLabel: this._getAriaLabel(), - fontSize: 13, - lineHeight: 20, - padding: { top: 8, bottom: 8 }, - overviewRulerLanes: 0, - glyphMargin: false, - lineNumbers: 'off', - folding: false, - hideCursorInOverviewRuler: true, - selectOnLineNumbers: false, - selectionHighlight: false, - scrollbar: { - useShadows: false, - vertical: 'hidden', - horizontal: 'hidden', - alwaysConsumeMouseWheel: false - }, - lineDecorationsWidth: 0, - overviewRulerBorder: false, - scrollBeyondLastLine: false, - renderLineHighlight: 'none', - fixedOverflowWidgets: true, - dragAndDrop: false, - revealHorizontalRightPadding: 5, - minimap: { enabled: false }, - guides: { indentation: false }, - rulers: [], - renderWhitespace: 'none', - dropIntoEditor: { enabled: true }, - quickSuggestions: false, - suggest: { - showIcons: false, - showSnippets: false, - showWords: true, - showStatusBar: false, - }, - wordWrap: 'on' - }, { isSimpleWidget: true })); - this._editor = editor; - this._register(editor.onDidFocusEditorText(() => this._responseEditorFocusedContextKey.set(true))); - this._register(editor.onDidBlurEditorText(() => this._responseEditorFocusedContextKey.set(false))); - this._register(Event.any(editor.onDidChangeModelContent, editor.onDidChangeModelDecorations)(() => { - const height = editor.getContentHeight(); - editor.layout(new Dimension(640, height)); - })); - - this.model = this._getTextModel(URI.from({ - path: `terminal-inline-chat-${this._instance.instanceId}`, - scheme: 'terminal-inline-chat', - fragment: initialCommandResponse - })); - this.model.then(model => { - if (model) { - // Initial layout - editor.layout(new Dimension(640, 0)); - editor.setModel(model); - const height = editor.getContentHeight(); - editor.layout(new Dimension(640, height)); - - // Initialize language - const languageId = this._getLanguageFromShell(shellType); - model.setLanguage(languageId); - } - }); - } - - private _getAriaLabel(): string { - const verbose = this._configurationService.getValue(AccessibilityVerbositySettingId.Chat); - if (verbose) { - // TODO: Add verbose description - } - return localize('terminalResponseEditor', "Terminal Response Editor"); - } - - private async _getTextModel(resource: URI): Promise { - const existing = this._modelService.getModel(resource); - if (existing && !existing.isDisposed()) { - return existing; - } - return this._modelService.createModel(resource.fragment, null, resource, false); - } - - private _getLanguageFromShell(shell?: string): string { - switch (shell) { - case 'fish': - return this._languageService.isRegisteredLanguageId('fish') ? 'fish' : 'shellscript'; - case 'zsh': - return this._languageService.isRegisteredLanguageId('zsh') ? 'zsh' : 'shellscript'; - case 'bash': - return this._languageService.isRegisteredLanguageId('bash') ? 'bash' : 'shellscript'; - case 'sh': - return 'shellscript'; - case 'pwsh': - return 'powershell'; - default: - return 'plaintext'; - } - } - - setValue(value: string) { - this._editor.setValue(value); - } - - getValue(): string { - return this._editor.getValue(); - } - - hide() { - hide(this._editorContainer); - } - - show() { - show(this._editorContainer); - } -} From b79e884dc16ef639963192597fab46294608217a Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 7 Mar 2024 11:54:38 -0800 Subject: [PATCH 1849/1897] Revert "Tunnel: Add unit tests for port mapping" (#207097) delete only test --- .../electron-sandbox/resolveExternal.test.ts | 101 ------------------ 1 file changed, 101 deletions(-) delete mode 100644 src/vs/workbench/test/electron-sandbox/resolveExternal.test.ts diff --git a/src/vs/workbench/test/electron-sandbox/resolveExternal.test.ts b/src/vs/workbench/test/electron-sandbox/resolveExternal.test.ts deleted file mode 100644 index 821367aea2f..00000000000 --- a/src/vs/workbench/test/electron-sandbox/resolveExternal.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; -import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; -import { RemoteTunnel } from 'vs/platform/tunnel/common/tunnel'; -import { URI } from 'vs/base/common/uri'; - -type PortMap = Record; - -class TunnelMock { - private assignedPorts: PortMap = {}; - private expectedDispose = false; - - reset(ports: PortMap) { - this.assignedPorts = ports; - } - - expectDispose() { - this.expectedDispose = true; - } - - openTunnel(_address: string, port: number): Promise { - if (!this.assignedPorts[port]) { - return Promise.reject(new Error('Unexpected tunnel request')); - } - const res: RemoteTunnel = { - localAddress: `localhost:${this.assignedPorts[port]}`, - tunnelRemoteHost: '4.3.2.1', - tunnelRemotePort: this.assignedPorts[port], - privacy: '', - dispose: () => { - assert(this.expectedDispose, 'Unexpected dispose'); - this.expectedDispose = false; - return Promise.resolve(); - } - }; - delete this.assignedPorts[port]; - return Promise.resolve(res); - } - - validate() { - try { - assert(Object.keys(this.assignedPorts).length === 0, 'Expected tunnel to be used'); - assert(!this.expectedDispose, 'Expected dispose to be called'); - } finally { - this.expectedDispose = false; - } - } -} - -suite('NativeWindow:resolveExternal', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - const tunnelMock = new TunnelMock(); - - async function doTest(uri: string, ports: PortMap = {}, expectedUri?: string) { - tunnelMock.reset(ports); - const res = await NativeWindow.prototype.resolveExternalUri.call(tunnelMock, URI.parse(uri), { - allowTunneling: true, - openExternal: true - }); - assert.strictEqual(!expectedUri, !res, `Expected URI ${expectedUri} but got ${res}`); - if (expectedUri && res) { - assert.strictEqual(res.resolved.toString(), URI.parse(expectedUri).toString()); - } - tunnelMock.validate(); - } - test('invalid', async () => { - await doTest('file:///foo.bar/baz'); - await doTest('http://foo.bar/path'); - }); - test('simple', async () => { - await doTest('http://localhost:1234/path', { 1234: 1234 }, 'http://localhost:1234/path'); - }); - test('all interfaces', async () => { - await doTest('http://0.0.0.0:1234/path', { 1234: 1234 }, 'http://localhost:1234/path'); - }); - test('changed port', async () => { - await doTest('http://localhost:1234/path', { 1234: 1235 }, 'http://localhost:1235/path'); - }); - test('query', async () => { - await doTest('http://foo.bar/path?a=b&c=http%3a%2f%2flocalhost%3a4455', { 4455: 4455 }, 'http://foo.bar/path?a=b&c=http%3a%2f%2flocalhost%3a4455'); - }); - test('query with different port', async () => { - tunnelMock.expectDispose(); - await doTest('http://foo.bar/path?a=b&c=http%3a%2f%2flocalhost%3a4455', { 4455: 4567 }); - }); - test('both url and query', async () => { - await doTest('http://localhost:1234/path?a=b&c=http%3a%2f%2flocalhost%3a4455', - { 1234: 4321, 4455: 4455 }, - 'http://localhost:4321/path?a=b&c=http%3a%2f%2flocalhost%3a4455'); - }); - test('both url and query, query rejected', async () => { - tunnelMock.expectDispose(); - await doTest('http://localhost:1234/path?a=b&c=http%3a%2f%2flocalhost%3a4455', - { 1234: 4321, 4455: 5544 }, - 'http://localhost:4321/path?a=b&c=http%3a%2f%2flocalhost%3a4455'); - }); -}); From a4be57ba959d172ed5fed3e3e586d4369641c022 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 7 Mar 2024 20:57:25 +0100 Subject: [PATCH 1850/1897] watcher - fix crash when rewatching a file recursively (#207102) --- .../node/watcher/parcel/parcelWatcher.ts | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts index d1b978043ed..c51b58db863 100644 --- a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts +++ b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts @@ -476,15 +476,17 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { // Watcher path came back! Restart watching... for (const { resource, type } of changes) { if (resource.fsPath === watcher.request.path && (type === FileChangeType.ADDED || type === FileChangeType.UPDATED)) { - this.warn('Watcher restarts because watched path got created again', watcher); + if (this.isPathValid(watcher.request.path)) { + this.warn('Watcher restarts because watched path got created again', watcher); - // Stop watching that parent folder - nodeWatcher.dispose(); + // Stop watching that parent folder + nodeWatcher.dispose(); - // Restart the file watching - this.restartWatching(watcher); + // Restart the file watching + this.restartWatching(watcher); - break; + break; + } } } }, msg => this._onDidLogMessage.fire(msg), this.verboseLogging); @@ -628,19 +630,8 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { } // Check for invalid paths - if (validatePaths) { - try { - const stat = statSync(request.path); - if (!stat.isDirectory()) { - this.trace(`ignoring a path for watching that is a file and not a folder: ${request.path}`); - - continue; - } - } catch (error) { - this.trace(`ignoring a path for watching who's stat info failed to resolve: ${request.path} (error: ${error})`); - - continue; - } + if (validatePaths && !this.isPathValid(request.path)) { + continue; } requestTrie.set(request.path, request); @@ -652,6 +643,23 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { return normalizedRequests; } + private isPathValid(path: string): boolean { + try { + const stat = statSync(path); + if (!stat.isDirectory()) { + this.trace(`ignoring a path for watching that is a file and not a folder: ${path}`); + + return false; + } + } catch (error) { + this.trace(`ignoring a path for watching who's stat info failed to resolve: ${path} (error: ${error})`); + + return false; + } + + return true; + } + async setVerboseLogging(enabled: boolean): Promise { this.verboseLogging = enabled; } From 1f0e830c2bf936ad2bf5767b0777b7a6fb3f943b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 7 Mar 2024 12:19:21 -0800 Subject: [PATCH 1851/1897] fix #207089 --- src/vs/workbench/contrib/terminal/browser/terminalEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts index 0ea8218fcce..900fbaf9fed 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts @@ -103,7 +103,7 @@ export class TerminalEditor extends EditorPane { override focus() { super.focus(); - this._editorInput?.terminalInstance?.focus(); + this._editorInput?.terminalInstance?.focus(true); } // eslint-disable-next-line @typescript-eslint/naming-convention From 978d141c99311ba856ff81aeeca6b5426f084a02 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 7 Mar 2024 12:39:46 -0800 Subject: [PATCH 1852/1897] get code block to render --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index f06f0a9e5a4..37b54b0c01f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -874,7 +874,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer Date: Thu, 7 Mar 2024 12:59:59 -0800 Subject: [PATCH 1853/1897] Pick up latest TS for building VS Code (#207096) --- build/azure-pipelines/common/retry.js | 3 +- build/azure-pipelines/common/sign.js | 4 +- build/lib/asar.js | 3 +- build/lib/builtInExtensions.js | 5 +- build/lib/bundle.js | 3 +- build/lib/compilation.js | 8 ++-- build/lib/dependencies.js | 3 +- build/lib/extensions.js | 17 ++++--- build/lib/fetch.js | 7 ++- build/lib/getVersion.js | 3 +- build/lib/git.js | 3 +- build/lib/i18n.js | 16 +++---- build/lib/monaco-api.js | 6 +-- build/lib/nls.js | 3 +- build/lib/optimize.js | 9 ++-- build/lib/reporter.js | 3 +- build/lib/standalone.js | 5 +- build/lib/stats.js | 3 +- build/lib/stylelint/validateVariableNames.js | 3 +- build/lib/task.js | 7 ++- build/lib/treeshaking.js | 6 +-- build/lib/tsb/builder.js | 4 +- build/lib/tsb/index.js | 3 +- build/lib/util.js | 47 +++++++++---------- build/linux/debian/calculate-deps.js | 3 +- build/linux/debian/install-sysroot.js | 5 +- build/linux/debian/types.js | 3 +- build/linux/dependencies-generator.js | 3 +- build/linux/libcxx-fetcher.js | 5 +- build/linux/rpm/calculate-deps.js | 3 +- build/linux/rpm/types.js | 3 +- build/win32/explorer-appx-fetcher.js | 3 +- .../src/tsServer/protocol/protocol.d.ts | 4 +- package.json | 2 +- yarn.lock | 8 ++-- 35 files changed, 95 insertions(+), 121 deletions(-) diff --git a/build/azure-pipelines/common/retry.js b/build/azure-pipelines/common/retry.js index 7b90b0cac5b..91f60bf24b2 100644 --- a/build/azure-pipelines/common/retry.js +++ b/build/azure-pipelines/common/retry.js @@ -4,7 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.retry = void 0; +exports.retry = retry; async function retry(fn) { let lastError; for (let run = 1; run <= 10; run++) { @@ -24,5 +24,4 @@ async function retry(fn) { console.error(`Too many retries, aborting.`); throw lastError; } -exports.retry = retry; //# sourceMappingURL=retry.js.map \ No newline at end of file diff --git a/build/azure-pipelines/common/sign.js b/build/azure-pipelines/common/sign.js index 4dba4765ff6..32996a7db03 100644 --- a/build/azure-pipelines/common/sign.js +++ b/build/azure-pipelines/common/sign.js @@ -4,7 +4,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.main = exports.Temp = void 0; +exports.Temp = void 0; +exports.main = main; const cp = require("child_process"); const fs = require("fs"); const crypto = require("crypto"); @@ -164,7 +165,6 @@ function main([esrpCliPath, type, cert, username, password, folderPath, pattern] process.exit(1); } } -exports.main = main; if (require.main === module) { main(process.argv.slice(2)); process.exit(0); diff --git a/build/lib/asar.js b/build/lib/asar.js index cadb9ab974d..31845f2f2dd 100644 --- a/build/lib/asar.js +++ b/build/lib/asar.js @@ -4,7 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.createAsar = void 0; +exports.createAsar = createAsar; const path = require("path"); const es = require("event-stream"); const pickle = require('chromium-pickle-js'); @@ -115,5 +115,4 @@ function createAsar(folderPath, unpackGlobs, destFilename) { } }); } -exports.createAsar = createAsar; //# sourceMappingURL=asar.js.map \ No newline at end of file diff --git a/build/lib/builtInExtensions.js b/build/lib/builtInExtensions.js index 1b0adc48d4c..463ce16e18d 100644 --- a/build/lib/builtInExtensions.js +++ b/build/lib/builtInExtensions.js @@ -4,7 +4,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.getBuiltInExtensions = exports.getExtensionStream = void 0; +exports.getExtensionStream = getExtensionStream; +exports.getBuiltInExtensions = getBuiltInExtensions; const fs = require("fs"); const path = require("path"); const os = require("os"); @@ -58,7 +59,6 @@ function getExtensionStream(extension) { } return getExtensionDownloadStream(extension); } -exports.getExtensionStream = getExtensionStream; function syncMarketplaceExtension(extension) { const galleryServiceUrl = productjson.extensionsGallery?.serviceUrl; const source = ansiColors.blue(galleryServiceUrl ? '[marketplace]' : '[github]'); @@ -127,7 +127,6 @@ function getBuiltInExtensions() { .on('end', resolve); }); } -exports.getBuiltInExtensions = getBuiltInExtensions; if (require.main === module) { getBuiltInExtensions().then(() => process.exit(0)).catch(err => { console.error(err); diff --git a/build/lib/bundle.js b/build/lib/bundle.js index 5d3ee9d5118..61d9f015624 100644 --- a/build/lib/bundle.js +++ b/build/lib/bundle.js @@ -4,7 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.bundle = void 0; +exports.bundle = bundle; const fs = require("fs"); const path = require("path"); const vm = require("vm"); @@ -78,7 +78,6 @@ function bundle(entryPoints, config, callback) { }); }, (err) => callback(err, null)); } -exports.bundle = bundle; function emitEntryPoints(modules, entryPoints) { const modulesMap = {}; modules.forEach((m) => { diff --git a/build/lib/compilation.js b/build/lib/compilation.js index e7a460de7d0..85cd722dbf3 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -4,7 +4,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.watchApiProposalNamesTask = exports.compileApiProposalNamesTask = exports.watchTask = exports.compileTask = exports.transpileTask = void 0; +exports.watchApiProposalNamesTask = exports.compileApiProposalNamesTask = void 0; +exports.transpileTask = transpileTask; +exports.compileTask = compileTask; +exports.watchTask = watchTask; const es = require("event-stream"); const fs = require("fs"); const gulp = require("gulp"); @@ -96,7 +99,6 @@ function transpileTask(src, out, swc) { task.taskName = `transpile-${path.basename(src)}`; return task; } -exports.transpileTask = transpileTask; function compileTask(src, out, build, options = {}) { const task = () => { if (os.totalmem() < 4_000_000_000) { @@ -137,7 +139,6 @@ function compileTask(src, out, build, options = {}) { task.taskName = `compile-${path.basename(src)}`; return task; } -exports.compileTask = compileTask; function watchTask(out, build) { const task = () => { const compile = createCompile('src', build, false, false); @@ -153,7 +154,6 @@ function watchTask(out, build) { task.taskName = `watch-${path.basename(out)}`; return task; } -exports.watchTask = watchTask; const REPO_SRC_FOLDER = path.join(__dirname, '../../src'); class MonacoGenerator { _isWatch; diff --git a/build/lib/dependencies.js b/build/lib/dependencies.js index 64087a9ac17..1f2dd75d68c 100644 --- a/build/lib/dependencies.js +++ b/build/lib/dependencies.js @@ -4,7 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.getProductionDependencies = void 0; +exports.getProductionDependencies = getProductionDependencies; const fs = require("fs"); const path = require("path"); const cp = require("child_process"); @@ -69,7 +69,6 @@ function getProductionDependencies(folderPath) { } return [...new Set(result)]; } -exports.getProductionDependencies = getProductionDependencies; if (require.main === module) { console.log(JSON.stringify(getProductionDependencies(root), null, ' ')); } diff --git a/build/lib/extensions.js b/build/lib/extensions.js index c81568c7275..6a6c0a7b4cd 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -4,7 +4,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.buildExtensionMedia = exports.webpackExtensions = exports.translatePackageJSON = exports.scanBuiltinExtensions = exports.packageMarketplaceExtensionsStream = exports.packageLocalExtensionsStream = exports.fromGithub = exports.fromMarketplace = void 0; +exports.fromMarketplace = fromMarketplace; +exports.fromGithub = fromGithub; +exports.packageLocalExtensionsStream = packageLocalExtensionsStream; +exports.packageMarketplaceExtensionsStream = packageMarketplaceExtensionsStream; +exports.scanBuiltinExtensions = scanBuiltinExtensions; +exports.translatePackageJSON = translatePackageJSON; +exports.webpackExtensions = webpackExtensions; +exports.buildExtensionMedia = buildExtensionMedia; const es = require("event-stream"); const fs = require("fs"); const cp = require("child_process"); @@ -213,7 +220,6 @@ function fromMarketplace(serviceUrl, { name: extensionName, version, sha256, met .pipe(json({ __metadata: metadata })) .pipe(packageJsonFilter.restore); } -exports.fromMarketplace = fromMarketplace; function fromGithub({ name, version, repo, sha256, metadata }) { const json = require('gulp-json-editor'); fancyLog('Downloading extension from GH:', ansiColors.yellow(`${name}@${version}`), '...'); @@ -232,7 +238,6 @@ function fromGithub({ name, version, repo, sha256, metadata }) { .pipe(json({ __metadata: metadata })) .pipe(packageJsonFilter.restore); } -exports.fromGithub = fromGithub; const excludedExtensions = [ 'vscode-api-tests', 'vscode-colorize-tests', @@ -306,7 +311,6 @@ function packageLocalExtensionsStream(forWeb, disableMangle) { return (result .pipe(util2.setExecutableBit(['**/*.sh']))); } -exports.packageLocalExtensionsStream = packageLocalExtensionsStream; function packageMarketplaceExtensionsStream(forWeb) { const marketplaceExtensionsDescriptions = [ ...builtInExtensions.filter(({ name }) => (forWeb ? !marketplaceWebExtensionsExclude.has(name) : true)), @@ -325,7 +329,6 @@ function packageMarketplaceExtensionsStream(forWeb) { return (marketplaceExtensionsStream .pipe(util2.setExecutableBit(['**/*.sh']))); } -exports.packageMarketplaceExtensionsStream = packageMarketplaceExtensionsStream; function scanBuiltinExtensions(extensionsRoot, exclude = []) { const scannedExtensions = []; try { @@ -361,7 +364,6 @@ function scanBuiltinExtensions(extensionsRoot, exclude = []) { return scannedExtensions; } } -exports.scanBuiltinExtensions = scanBuiltinExtensions; function translatePackageJSON(packageJSON, packageNLSPath) { const CharCode_PC = '%'.charCodeAt(0); const packageNls = JSON.parse(fs.readFileSync(packageNLSPath).toString()); @@ -385,7 +387,6 @@ function translatePackageJSON(packageJSON, packageNLSPath) { translate(packageJSON); return packageJSON; } -exports.translatePackageJSON = translatePackageJSON; const extensionsPath = path.join(root, 'extensions'); // Additional projects to run esbuild on. These typically build code for webviews const esbuildMediaScripts = [ @@ -459,7 +460,6 @@ async function webpackExtensions(taskName, isWatch, webpackConfigLocations) { } }); } -exports.webpackExtensions = webpackExtensions; async function esbuildExtensions(taskName, isWatch, scripts) { function reporter(stdError, script) { const matches = (stdError || '').match(/\> (.+): error: (.+)?/g); @@ -500,5 +500,4 @@ async function buildExtensionMedia(isWatch, outputRoot) { outputRoot: outputRoot ? path.join(root, outputRoot, path.dirname(p)) : undefined }))); } -exports.buildExtensionMedia = buildExtensionMedia; //# sourceMappingURL=extensions.js.map \ No newline at end of file diff --git a/build/lib/fetch.js b/build/lib/fetch.js index ba23e78257c..2fed63bca0e 100644 --- a/build/lib/fetch.js +++ b/build/lib/fetch.js @@ -4,7 +4,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.fetchGithub = exports.fetchUrl = exports.fetchUrls = void 0; +exports.fetchUrls = fetchUrls; +exports.fetchUrl = fetchUrl; +exports.fetchGithub = fetchGithub; const es = require("event-stream"); const VinylFile = require("vinyl"); const log = require("fancy-log"); @@ -30,7 +32,6 @@ function fetchUrls(urls, options) { }); })); } -exports.fetchUrls = fetchUrls; async function fetchUrl(url, options, retries = 10, retryDelay = 1000) { const verbose = !!options.verbose ?? (!!process.env['CI'] || !!process.env['BUILD_ARTIFACTSTAGINGDIRECTORY']); try { @@ -94,7 +95,6 @@ async function fetchUrl(url, options, retries = 10, retryDelay = 1000) { throw e; } } -exports.fetchUrl = fetchUrl; const ghApiHeaders = { Accept: 'application/vnd.github.v3+json', 'User-Agent': 'VSCode Build', @@ -135,5 +135,4 @@ function fetchGithub(repo, options) { } })); } -exports.fetchGithub = fetchGithub; //# sourceMappingURL=fetch.js.map \ No newline at end of file diff --git a/build/lib/getVersion.js b/build/lib/getVersion.js index abf05e93210..b50ead538a2 100644 --- a/build/lib/getVersion.js +++ b/build/lib/getVersion.js @@ -4,7 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.getVersion = void 0; +exports.getVersion = getVersion; const git = require("./git"); function getVersion(root) { let version = process.env['BUILD_SOURCEVERSION']; @@ -13,5 +13,4 @@ function getVersion(root) { } return version; } -exports.getVersion = getVersion; //# sourceMappingURL=getVersion.js.map \ No newline at end of file diff --git a/build/lib/git.js b/build/lib/git.js index a8e712ed070..798a408bdb9 100644 --- a/build/lib/git.js +++ b/build/lib/git.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.getVersion = void 0; +exports.getVersion = getVersion; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. @@ -51,5 +51,4 @@ function getVersion(repo) { } return refs[ref]; } -exports.getVersion = getVersion; //# sourceMappingURL=git.js.map \ No newline at end of file diff --git a/build/lib/i18n.js b/build/lib/i18n.js index 1844af139c5..c33994987f0 100644 --- a/build/lib/i18n.js +++ b/build/lib/i18n.js @@ -4,7 +4,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.prepareIslFiles = exports.prepareI18nPackFiles = exports.createXlfFilesForIsl = exports.createXlfFilesForExtensions = exports.EXTERNAL_EXTENSIONS = exports.createXlfFilesForCoreBundle = exports.getResource = exports.processNlsFiles = exports.XLF = exports.Line = exports.extraLanguages = exports.defaultLanguages = void 0; +exports.EXTERNAL_EXTENSIONS = exports.XLF = exports.Line = exports.extraLanguages = exports.defaultLanguages = void 0; +exports.processNlsFiles = processNlsFiles; +exports.getResource = getResource; +exports.createXlfFilesForCoreBundle = createXlfFilesForCoreBundle; +exports.createXlfFilesForExtensions = createXlfFilesForExtensions; +exports.createXlfFilesForIsl = createXlfFilesForIsl; +exports.prepareI18nPackFiles = prepareI18nPackFiles; +exports.prepareIslFiles = prepareIslFiles; const path = require("path"); const fs = require("fs"); const event_stream_1 = require("event-stream"); @@ -423,7 +430,6 @@ function processNlsFiles(opts) { this.queue(file); }); } -exports.processNlsFiles = processNlsFiles; const editorProject = 'vscode-editor', workbenchProject = 'vscode-workbench', extensionsProject = 'vscode-extensions', setupProject = 'vscode-setup', serverProject = 'vscode-server'; function getResource(sourceFile) { let resource; @@ -458,7 +464,6 @@ function getResource(sourceFile) { } throw new Error(`Could not identify the XLF bundle for ${sourceFile}`); } -exports.getResource = getResource; function createXlfFilesForCoreBundle() { return (0, event_stream_1.through)(function (file) { const basename = path.basename(file.path); @@ -506,7 +511,6 @@ function createXlfFilesForCoreBundle() { } }); } -exports.createXlfFilesForCoreBundle = createXlfFilesForCoreBundle; function createL10nBundleForExtension(extensionFolderName, prefixWithBuildFolder) { const prefix = prefixWithBuildFolder ? '.build/' : ''; return gulp @@ -653,7 +657,6 @@ function createXlfFilesForExtensions() { } }); } -exports.createXlfFilesForExtensions = createXlfFilesForExtensions; function createXlfFilesForIsl() { return (0, event_stream_1.through)(function (file) { let projectName, resourceFile; @@ -704,7 +707,6 @@ function createXlfFilesForIsl() { this.queue(xlfFile); }); } -exports.createXlfFilesForIsl = createXlfFilesForIsl; function createI18nFile(name, messages) { const result = Object.create(null); result[''] = [ @@ -793,7 +795,6 @@ function prepareI18nPackFiles(resultingTranslationPaths) { }); }); } -exports.prepareI18nPackFiles = prepareI18nPackFiles; function prepareIslFiles(language, innoSetupConfig) { const parsePromises = []; return (0, event_stream_1.through)(function (xlf) { @@ -816,7 +817,6 @@ function prepareIslFiles(language, innoSetupConfig) { }); }); } -exports.prepareIslFiles = prepareIslFiles; function createIslFile(name, messages, language, innoSetup) { const content = []; let originalContent; diff --git a/build/lib/monaco-api.js b/build/lib/monaco-api.js index 6512b6ae886..2052806c46b 100644 --- a/build/lib/monaco-api.js +++ b/build/lib/monaco-api.js @@ -4,7 +4,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.execute = exports.run3 = exports.DeclarationResolver = exports.FSProvider = exports.RECIPE_PATH = void 0; +exports.DeclarationResolver = exports.FSProvider = exports.RECIPE_PATH = void 0; +exports.run3 = run3; +exports.execute = execute; const fs = require("fs"); const path = require("path"); const fancyLog = require("fancy-log"); @@ -559,7 +561,6 @@ function run3(resolver) { const sourceFileGetter = (moduleId) => resolver.getDeclarationSourceFile(moduleId); return _run(resolver.ts, sourceFileGetter); } -exports.run3 = run3; class TypeScriptLanguageServiceHost { _ts; _libs; @@ -623,5 +624,4 @@ function execute() { } return r; } -exports.execute = execute; //# sourceMappingURL=monaco-api.js.map \ No newline at end of file diff --git a/build/lib/nls.js b/build/lib/nls.js index 982f74bcf4d..48ca84f2433 100644 --- a/build/lib/nls.js +++ b/build/lib/nls.js @@ -4,7 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.nls = void 0; +exports.nls = nls; const lazy = require("lazy.js"); const event_stream_1 = require("event-stream"); const File = require("vinyl"); @@ -74,7 +74,6 @@ function nls() { })); return (0, event_stream_1.duplex)(input, output); } -exports.nls = nls; function isImportNode(ts, node) { return node.kind === ts.SyntaxKind.ImportDeclaration || node.kind === ts.SyntaxKind.ImportEqualsDeclaration; } diff --git a/build/lib/optimize.js b/build/lib/optimize.js index 9dff0859acc..237f2bc20e8 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -4,7 +4,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.minifyTask = exports.optimizeTask = exports.optimizeLoaderTask = exports.loaderConfig = void 0; +exports.loaderConfig = loaderConfig; +exports.optimizeLoaderTask = optimizeLoaderTask; +exports.optimizeTask = optimizeTask; +exports.minifyTask = minifyTask; const es = require("event-stream"); const gulp = require("gulp"); const concat = require("gulp-concat"); @@ -33,7 +36,6 @@ function loaderConfig() { result['vs/css'] = { inlineResources: true }; return result; } -exports.loaderConfig = loaderConfig; const IS_OUR_COPYRIGHT_REGEXP = /Copyright \(C\) Microsoft Corporation/i; function loaderPlugin(src, base, amdModuleId) { return (gulp @@ -223,7 +225,6 @@ function optimizeManualTask(options) { function optimizeLoaderTask(src, out, bundleLoader, bundledFileHeader = '', externalLoaderInfo) { return () => loader(src, bundledFileHeader, bundleLoader, externalLoaderInfo).pipe(gulp.dest(out)); } -exports.optimizeLoaderTask = optimizeLoaderTask; function optimizeTask(opts) { return function () { const optimizers = [optimizeAMDTask(opts.amd)]; @@ -236,7 +237,6 @@ function optimizeTask(opts) { return es.merge(...optimizers).pipe(gulp.dest(opts.out)); }; } -exports.optimizeTask = optimizeTask; function minifyTask(src, sourceMapBaseUrl) { const esbuild = require('esbuild'); const sourceMappingURL = sourceMapBaseUrl ? ((f) => `${sourceMapBaseUrl}/${f.relative}.map`) : undefined; @@ -284,5 +284,4 @@ function minifyTask(src, sourceMapBaseUrl) { }), gulp.dest(src + '-min'), (err) => cb(err)); }; } -exports.minifyTask = minifyTask; //# sourceMappingURL=optimize.js.map \ No newline at end of file diff --git a/build/lib/reporter.js b/build/lib/reporter.js index 305d7364287..9d4a1b4fd79 100644 --- a/build/lib/reporter.js +++ b/build/lib/reporter.js @@ -4,7 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.createReporter = void 0; +exports.createReporter = createReporter; const es = require("event-stream"); const fancyLog = require("fancy-log"); const ansiColors = require("ansi-colors"); @@ -99,5 +99,4 @@ function createReporter(id) { }; return result; } -exports.createReporter = createReporter; //# sourceMappingURL=reporter.js.map \ No newline at end of file diff --git a/build/lib/standalone.js b/build/lib/standalone.js index 4ddf88ed223..dbc47db0833 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -4,7 +4,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.createESMSourcesAndResources2 = exports.extractEditor = void 0; +exports.extractEditor = extractEditor; +exports.createESMSourcesAndResources2 = createESMSourcesAndResources2; const fs = require("fs"); const path = require("path"); const tss = require("./treeshaking"); @@ -111,7 +112,6 @@ function extractEditor(options) { 'vs/nls.mock.ts', ].forEach(copyFile); } -exports.extractEditor = extractEditor; function createESMSourcesAndResources2(options) { const ts = require('typescript'); const SRC_FOLDER = path.join(REPO_ROOT, options.srcFolder); @@ -251,7 +251,6 @@ function createESMSourcesAndResources2(options) { } } } -exports.createESMSourcesAndResources2 = createESMSourcesAndResources2; function transportCSS(module, enqueue, write) { if (!/\.css/.test(module)) { return false; diff --git a/build/lib/stats.js b/build/lib/stats.js index d923bb809da..e089cb0c1b4 100644 --- a/build/lib/stats.js +++ b/build/lib/stats.js @@ -4,7 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.createStatsStream = void 0; +exports.createStatsStream = createStatsStream; const es = require("event-stream"); const fancyLog = require("fancy-log"); const ansiColors = require("ansi-colors"); @@ -73,5 +73,4 @@ function createStatsStream(group, log) { this.emit('end'); }); } -exports.createStatsStream = createStatsStream; //# sourceMappingURL=stats.js.map \ No newline at end of file diff --git a/build/lib/stylelint/validateVariableNames.js b/build/lib/stylelint/validateVariableNames.js index 2367fb94c2e..57b2aad957f 100644 --- a/build/lib/stylelint/validateVariableNames.js +++ b/build/lib/stylelint/validateVariableNames.js @@ -4,7 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.getVariableNameValidator = void 0; +exports.getVariableNameValidator = getVariableNameValidator; const fs_1 = require("fs"); const path = require("path"); const RE_VAR_PROP = /var\(\s*(--([\w\-\.]+))/g; @@ -30,5 +30,4 @@ function getVariableNameValidator() { } }; } -exports.getVariableNameValidator = getVariableNameValidator; //# sourceMappingURL=validateVariableNames.js.map \ No newline at end of file diff --git a/build/lib/task.js b/build/lib/task.js index 6b040a75698..597b2a0d397 100644 --- a/build/lib/task.js +++ b/build/lib/task.js @@ -4,7 +4,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.define = exports.parallel = exports.series = void 0; +exports.series = series; +exports.parallel = parallel; +exports.define = define; const fancyLog = require("fancy-log"); const ansiColors = require("ansi-colors"); function _isPromise(p) { @@ -67,7 +69,6 @@ function series(...tasks) { result._tasks = tasks; return result; } -exports.series = series; function parallel(...tasks) { const result = async () => { await Promise.all(tasks.map(t => _execute(t))); @@ -75,7 +76,6 @@ function parallel(...tasks) { result._tasks = tasks; return result; } -exports.parallel = parallel; function define(name, task) { if (task._tasks) { // This is a composite task @@ -94,5 +94,4 @@ function define(name, task) { task.displayName = name; return task; } -exports.define = define; //# sourceMappingURL=task.js.map \ No newline at end of file diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index 51c610ecda2..c8e95511877 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -4,7 +4,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.shake = exports.toStringShakeLevel = exports.ShakeLevel = void 0; +exports.ShakeLevel = void 0; +exports.toStringShakeLevel = toStringShakeLevel; +exports.shake = shake; const fs = require("fs"); const path = require("path"); const TYPESCRIPT_LIB_FOLDER = path.dirname(require.resolve('typescript/lib/lib.d.ts')); @@ -24,7 +26,6 @@ function toStringShakeLevel(shakeLevel) { return 'ClassMembers (2)'; } } -exports.toStringShakeLevel = toStringShakeLevel; function printDiagnostics(options, diagnostics) { for (const diag of diagnostics) { let result = ''; @@ -61,7 +62,6 @@ function shake(options) { markNodes(ts, languageService, options); return generateResult(ts, languageService, options.shakeLevel); } -exports.shake = shake; //#region Discovery, LanguageService & Setup function createTypeScriptLanguageService(ts, options) { // Discover referenced files diff --git a/build/lib/tsb/builder.js b/build/lib/tsb/builder.js index e87945ea9cc..fc74bfa8acc 100644 --- a/build/lib/tsb/builder.js +++ b/build/lib/tsb/builder.js @@ -4,7 +4,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.createTypeScriptBuilder = exports.CancellationToken = void 0; +exports.CancellationToken = void 0; +exports.createTypeScriptBuilder = createTypeScriptBuilder; const fs = require("fs"); const path = require("path"); const crypto = require("crypto"); @@ -364,7 +365,6 @@ function createTypeScriptBuilder(config, projectFile, cmd) { languageService: service }; } -exports.createTypeScriptBuilder = createTypeScriptBuilder; class ScriptSnapshot { _text; _mtime; diff --git a/build/lib/tsb/index.js b/build/lib/tsb/index.js index 47f26bc8178..8b8116d5a49 100644 --- a/build/lib/tsb/index.js +++ b/build/lib/tsb/index.js @@ -4,7 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.create = void 0; +exports.create = create; const Vinyl = require("vinyl"); const through = require("through"); const builder = require("./builder"); @@ -132,5 +132,4 @@ function create(projectPath, existingOptions, config, onError = _defaultOnError) }; return result; } -exports.create = create; //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/build/lib/util.js b/build/lib/util.js index 388ef5df948..ed52776c2c0 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -4,7 +4,29 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.buildWebNodePaths = exports.createExternalLoaderConfig = exports.acquireWebNodePaths = exports.getElectronVersion = exports.streamToPromise = exports.versionStringToNumber = exports.filter = exports.rebase = exports.ensureDir = exports.rreddir = exports.rimraf = exports.rewriteSourceMappingURL = exports.appendOwnPathSourceURL = exports.$if = exports.stripSourceMappingURL = exports.loadSourcemaps = exports.cleanNodeModules = exports.skipDirectories = exports.toFileUri = exports.setExecutableBit = exports.fixWin32DirectoryPermissions = exports.debounce = exports.incremental = void 0; +exports.incremental = incremental; +exports.debounce = debounce; +exports.fixWin32DirectoryPermissions = fixWin32DirectoryPermissions; +exports.setExecutableBit = setExecutableBit; +exports.toFileUri = toFileUri; +exports.skipDirectories = skipDirectories; +exports.cleanNodeModules = cleanNodeModules; +exports.loadSourcemaps = loadSourcemaps; +exports.stripSourceMappingURL = stripSourceMappingURL; +exports.$if = $if; +exports.appendOwnPathSourceURL = appendOwnPathSourceURL; +exports.rewriteSourceMappingURL = rewriteSourceMappingURL; +exports.rimraf = rimraf; +exports.rreddir = rreddir; +exports.ensureDir = ensureDir; +exports.rebase = rebase; +exports.filter = filter; +exports.versionStringToNumber = versionStringToNumber; +exports.streamToPromise = streamToPromise; +exports.getElectronVersion = getElectronVersion; +exports.acquireWebNodePaths = acquireWebNodePaths; +exports.createExternalLoaderConfig = createExternalLoaderConfig; +exports.buildWebNodePaths = buildWebNodePaths; const es = require("event-stream"); const _debounce = require("debounce"); const _filter = require("gulp-filter"); @@ -54,7 +76,6 @@ function incremental(streamProvider, initial, supportsCancellation) { }); return es.duplex(input, output); } -exports.incremental = incremental; function debounce(task, duration = 500) { const input = es.through(); const output = es.through(); @@ -83,7 +104,6 @@ function debounce(task, duration = 500) { }); return es.duplex(input, output); } -exports.debounce = debounce; function fixWin32DirectoryPermissions() { if (!/win32/.test(process.platform)) { return es.through(); @@ -95,7 +115,6 @@ function fixWin32DirectoryPermissions() { return f; }); } -exports.fixWin32DirectoryPermissions = fixWin32DirectoryPermissions; function setExecutableBit(pattern) { const setBit = es.mapSync(f => { if (!f.stat) { @@ -115,7 +134,6 @@ function setExecutableBit(pattern) { .pipe(filter.restore); return es.duplex(input, output); } -exports.setExecutableBit = setExecutableBit; function toFileUri(filePath) { const match = filePath.match(/^([a-z])\:(.*)$/i); if (match) { @@ -123,7 +141,6 @@ function toFileUri(filePath) { } return 'file://' + filePath.replace(/\\/g, '/'); } -exports.toFileUri = toFileUri; function skipDirectories() { return es.mapSync(f => { if (!f.isDirectory()) { @@ -131,7 +148,6 @@ function skipDirectories() { } }); } -exports.skipDirectories = skipDirectories; function cleanNodeModules(rulePath) { const rules = fs.readFileSync(rulePath, 'utf8') .split(/\r?\n/g) @@ -143,7 +159,6 @@ function cleanNodeModules(rulePath) { const output = es.merge(input.pipe(_filter(['**', ...excludes])), input.pipe(_filter(includes))); return es.duplex(input, output); } -exports.cleanNodeModules = cleanNodeModules; function loadSourcemaps() { const input = es.through(); const output = input @@ -185,7 +200,6 @@ function loadSourcemaps() { })); return es.duplex(input, output); } -exports.loadSourcemaps = loadSourcemaps; function stripSourceMappingURL() { const input = es.through(); const output = input @@ -196,7 +210,6 @@ function stripSourceMappingURL() { })); return es.duplex(input, output); } -exports.stripSourceMappingURL = stripSourceMappingURL; /** Splits items in the stream based on the predicate, sending them to onTrue if true, or onFalse otherwise */ function $if(test, onTrue, onFalse = es.through()) { if (typeof test === 'boolean') { @@ -204,7 +217,6 @@ function $if(test, onTrue, onFalse = es.through()) { } return ternaryStream(test, onTrue, onFalse); } -exports.$if = $if; /** Operator that appends the js files' original path a sourceURL, so debug locations map */ function appendOwnPathSourceURL() { const input = es.through(); @@ -218,7 +230,6 @@ function appendOwnPathSourceURL() { })); return es.duplex(input, output); } -exports.appendOwnPathSourceURL = appendOwnPathSourceURL; function rewriteSourceMappingURL(sourceMappingURLBase) { const input = es.through(); const output = input @@ -230,7 +241,6 @@ function rewriteSourceMappingURL(sourceMappingURLBase) { })); return es.duplex(input, output); } -exports.rewriteSourceMappingURL = rewriteSourceMappingURL; function rimraf(dir) { const result = () => new Promise((c, e) => { let retries = 0; @@ -250,7 +260,6 @@ function rimraf(dir) { result.taskName = `clean-${path.basename(dir).toLowerCase()}`; return result; } -exports.rimraf = rimraf; function _rreaddir(dirPath, prepend, result) { const entries = fs.readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { @@ -267,7 +276,6 @@ function rreddir(dirPath) { _rreaddir(dirPath, '', result); return result; } -exports.rreddir = rreddir; function ensureDir(dirPath) { if (fs.existsSync(dirPath)) { return; @@ -275,14 +283,12 @@ function ensureDir(dirPath) { ensureDir(path.dirname(dirPath)); fs.mkdirSync(dirPath); } -exports.ensureDir = ensureDir; function rebase(count) { return rename(f => { const parts = f.dirname ? f.dirname.split(/[\/\\]/) : []; f.dirname = parts.slice(count).join(path.sep); }); } -exports.rebase = rebase; function filter(fn) { const result = es.through(function (data) { if (fn(data)) { @@ -295,7 +301,6 @@ function filter(fn) { result.restore = es.through(); return result; } -exports.filter = filter; function versionStringToNumber(versionStr) { const semverRegex = /(\d+)\.(\d+)\.(\d+)/; const match = versionStr.match(semverRegex); @@ -304,21 +309,18 @@ function versionStringToNumber(versionStr) { } return parseInt(match[1], 10) * 1e4 + parseInt(match[2], 10) * 1e2 + parseInt(match[3], 10); } -exports.versionStringToNumber = versionStringToNumber; function streamToPromise(stream) { return new Promise((c, e) => { stream.on('error', err => e(err)); stream.on('end', () => c()); }); } -exports.streamToPromise = streamToPromise; function getElectronVersion() { const yarnrc = fs.readFileSync(path.join(root, '.yarnrc'), 'utf8'); const electronVersion = /^target "(.*)"$/m.exec(yarnrc)[1]; const msBuildId = /^ms_build_id "(.*)"$/m.exec(yarnrc)[1]; return { electronVersion, msBuildId }; } -exports.getElectronVersion = getElectronVersion; function acquireWebNodePaths() { const root = path.join(__dirname, '..', '..'); const webPackageJSON = path.join(root, '/remote/web', 'package.json'); @@ -367,7 +369,6 @@ function acquireWebNodePaths() { nodePaths['@microsoft/applicationinsights-core-js'] = 'browser/applicationinsights-core-js.min.js'; return nodePaths; } -exports.acquireWebNodePaths = acquireWebNodePaths; function createExternalLoaderConfig(webEndpoint, commit, quality) { if (!webEndpoint || !commit || !quality) { return undefined; @@ -384,7 +385,6 @@ function createExternalLoaderConfig(webEndpoint, commit, quality) { }; return externalLoaderConfig; } -exports.createExternalLoaderConfig = createExternalLoaderConfig; function buildWebNodePaths(outDir) { const result = () => new Promise((resolve, _) => { const root = path.join(__dirname, '..', '..'); @@ -405,5 +405,4 @@ function buildWebNodePaths(outDir) { result.taskName = 'build-web-node-paths'; return result; } -exports.buildWebNodePaths = buildWebNodePaths; //# sourceMappingURL=util.js.map \ No newline at end of file diff --git a/build/linux/debian/calculate-deps.js b/build/linux/debian/calculate-deps.js index 6304df9edda..bbcb6bfc3de 100644 --- a/build/linux/debian/calculate-deps.js +++ b/build/linux/debian/calculate-deps.js @@ -4,7 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.generatePackageDeps = void 0; +exports.generatePackageDeps = generatePackageDeps; const child_process_1 = require("child_process"); const fs_1 = require("fs"); const os_1 = require("os"); @@ -17,7 +17,6 @@ function generatePackageDeps(files, arch, chromiumSysroot, vscodeSysroot) { dependencies.push(additionalDepsSet); return dependencies; } -exports.generatePackageDeps = generatePackageDeps; // Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/calculate_package_deps.py. function calculatePackageDeps(binaryPath, arch, chromiumSysroot, vscodeSysroot) { try { diff --git a/build/linux/debian/install-sysroot.js b/build/linux/debian/install-sysroot.js index d637fce3ca6..feca7d3fa9d 100644 --- a/build/linux/debian/install-sysroot.js +++ b/build/linux/debian/install-sysroot.js @@ -4,7 +4,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.getChromiumSysroot = exports.getVSCodeSysroot = void 0; +exports.getVSCodeSysroot = getVSCodeSysroot; +exports.getChromiumSysroot = getChromiumSysroot; const child_process_1 = require("child_process"); const os_1 = require("os"); const fs = require("fs"); @@ -156,7 +157,6 @@ async function getVSCodeSysroot(arch) { fs.writeFileSync(stamp, expectedName); return result; } -exports.getVSCodeSysroot = getVSCodeSysroot; async function getChromiumSysroot(arch) { const sysrootJSONUrl = `https://raw.githubusercontent.com/electron/electron/v${getElectronVersion().electronVersion}/script/sysroots.json`; const sysrootDictLocation = `${(0, os_1.tmpdir)()}/sysroots.json`; @@ -214,5 +214,4 @@ async function getChromiumSysroot(arch) { fs.writeFileSync(stamp, url); return sysroot; } -exports.getChromiumSysroot = getChromiumSysroot; //# sourceMappingURL=install-sysroot.js.map \ No newline at end of file diff --git a/build/linux/debian/types.js b/build/linux/debian/types.js index 2cd177c34a8..ce21d50e1a9 100644 --- a/build/linux/debian/types.js +++ b/build/linux/debian/types.js @@ -4,9 +4,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.isDebianArchString = void 0; +exports.isDebianArchString = isDebianArchString; function isDebianArchString(s) { return ['amd64', 'armhf', 'arm64'].includes(s); } -exports.isDebianArchString = isDebianArchString; //# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/build/linux/dependencies-generator.js b/build/linux/dependencies-generator.js index 58db0f4af51..80c247d1129 100644 --- a/build/linux/dependencies-generator.js +++ b/build/linux/dependencies-generator.js @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); -exports.getDependencies = void 0; +exports.getDependencies = getDependencies; const child_process_1 = require("child_process"); const path = require("path"); const install_sysroot_1 = require("./debian/install-sysroot"); @@ -92,7 +92,6 @@ async function getDependencies(packageType, buildDir, applicationName, arch) { } return sortedDependencies; } -exports.getDependencies = getDependencies; // Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py. function mergePackageDeps(inputDeps) { const requires = new Set(); diff --git a/build/linux/libcxx-fetcher.js b/build/linux/libcxx-fetcher.js index 1e195ba1fac..cfdc9498502 100644 --- a/build/linux/libcxx-fetcher.js +++ b/build/linux/libcxx-fetcher.js @@ -4,7 +4,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.downloadLibcxxObjects = exports.downloadLibcxxHeaders = void 0; +exports.downloadLibcxxHeaders = downloadLibcxxHeaders; +exports.downloadLibcxxObjects = downloadLibcxxObjects; // Can be removed once https://github.com/electron/electron-rebuild/pull/703 is available. const fs = require("fs"); const path = require("path"); @@ -29,7 +30,6 @@ async function downloadLibcxxHeaders(outDir, electronVersion, lib_name) { d(`unpacking ${lib_name}_headers from ${headers}`); await extract(headers, { dir: outDir }); } -exports.downloadLibcxxHeaders = downloadLibcxxHeaders; async function downloadLibcxxObjects(outDir, electronVersion, targetArch = 'x64') { if (await fs.existsSync(path.resolve(outDir, 'libc++.a'))) { return; @@ -47,7 +47,6 @@ async function downloadLibcxxObjects(outDir, electronVersion, targetArch = 'x64' d(`unpacking libcxx-objects from ${objects}`); await extract(objects, { dir: outDir }); } -exports.downloadLibcxxObjects = downloadLibcxxObjects; async function main() { const libcxxObjectsDirPath = process.env['VSCODE_LIBCXX_OBJECTS_DIR']; const libcxxHeadersDownloadDir = process.env['VSCODE_LIBCXX_HEADERS_DIR']; diff --git a/build/linux/rpm/calculate-deps.js b/build/linux/rpm/calculate-deps.js index ac870e4a546..b19e26f1854 100644 --- a/build/linux/rpm/calculate-deps.js +++ b/build/linux/rpm/calculate-deps.js @@ -4,7 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.generatePackageDeps = void 0; +exports.generatePackageDeps = generatePackageDeps; const child_process_1 = require("child_process"); const fs_1 = require("fs"); const dep_lists_1 = require("./dep-lists"); @@ -14,7 +14,6 @@ function generatePackageDeps(files) { dependencies.push(additionalDepsSet); return dependencies; } -exports.generatePackageDeps = generatePackageDeps; // Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/calculate_package_deps.py. function calculatePackageDeps(binaryPath) { try { diff --git a/build/linux/rpm/types.js b/build/linux/rpm/types.js index 6dba7cf38d1..a20b9c2fe02 100644 --- a/build/linux/rpm/types.js +++ b/build/linux/rpm/types.js @@ -4,9 +4,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.isRpmArchString = void 0; +exports.isRpmArchString = isRpmArchString; function isRpmArchString(s) { return ['x86_64', 'armv7hl', 'aarch64'].includes(s); } -exports.isRpmArchString = isRpmArchString; //# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/build/win32/explorer-appx-fetcher.js b/build/win32/explorer-appx-fetcher.js index d618c21674a..554b449d872 100644 --- a/build/win32/explorer-appx-fetcher.js +++ b/build/win32/explorer-appx-fetcher.js @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); -exports.downloadExplorerAppx = void 0; +exports.downloadExplorerAppx = downloadExplorerAppx; const fs = require("fs"); const debug = require("debug"); const extract = require("extract-zip"); @@ -36,7 +36,6 @@ async function downloadExplorerAppx(outDir, quality = 'stable', targetArch = 'x6 d(`unpacking from ${fileName}`); await extract(artifact, { dir: fs.realpathSync(outDir) }); } -exports.downloadExplorerAppx = downloadExplorerAppx; async function main(outputDir) { const arch = process.env['VSCODE_ARCH']; if (!outputDir) { diff --git a/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts b/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts index b8d863ebb1a..45e09d63481 100644 --- a/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts +++ b/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as ts from 'typescript/lib/tsserverlibrary'; +import type ts from '../../../../node_modules/typescript/lib/typescript'; export = ts.server.protocol; @@ -11,7 +11,7 @@ declare enum ServerType { Semantic = 'semantic', } -declare module 'typescript/lib/tsserverlibrary' { +declare module '../../../../node_modules/typescript/lib/typescript' { namespace server.protocol { type TextInsertion = ts.TextInsertion; type ScriptElementKind = ts.ScriptElementKind; diff --git a/package.json b/package.json index 24bb6558dee..fae4fcc57a8 100644 --- a/package.json +++ b/package.json @@ -209,7 +209,7 @@ "ts-loader": "^9.4.2", "ts-node": "^10.9.1", "tsec": "0.2.7", - "typescript": "^5.5.0-dev.20240226", + "typescript": "^5.5.0-dev.20240307", "typescript-formatter": "7.1.0", "util": "^0.12.4", "vscode-nls-dev": "^3.3.1", diff --git a/yarn.lock b/yarn.lock index c96688f58da..a7e82c60be5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9643,10 +9643,10 @@ typescript@^4.7.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== -typescript@^5.5.0-dev.20240226: - version "5.5.0-dev.20240226" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.0-dev.20240226.tgz#b571688666f07e4d7db4c9863f3ee1401e161a7a" - integrity sha512-mLY9/pjzSCr7JLkMKHS3KQUKX+LPO9WWjiR+mRcWKcskSdMBZ0j1TPhk/zUyuBklOf3YX4orkvamNiZWZEK0CQ== +typescript@^5.5.0-dev.20240307: + version "5.5.0-dev.20240307" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.0-dev.20240307.tgz#dcd10b4a4d5a274f40cdfa91c75ae7a940ade469" + integrity sha512-9QgBAVHg1keeajSXIqq1ngFy3yTA0um5YWoLNVSfPtIwqqYoVqRjmh4oJKJL4YM15C2HY8IXL/UEUBIjXm/tjg== typical@^4.0.0: version "4.0.0" From 5ae204d5ad57beb70812a65bfd47b84499372753 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 7 Mar 2024 22:06:39 +0100 Subject: [PATCH 1854/1897] Fix Drag And Drop in Activity Bar (#207109) fixes #206878 --- src/vs/workbench/browser/parts/compositeBar.ts | 3 ++- src/vs/workbench/browser/parts/paneCompositeBar.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 27d0b3738ea..a9fc39c84ed 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -37,6 +37,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { constructor( private viewDescriptorService: IViewDescriptorService, private targetContainerLocation: ViewContainerLocation, + private orientation: ActionsOrientation, private openComposite: (id: string, focus?: boolean) => Promise, private moveComposite: (from: string, to: string, before?: Before2D) => void, private getItems: () => ICompositeBarItem[] @@ -93,7 +94,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { } const items = this.getItems(); - const before = this.targetContainerLocation === ViewContainerLocation.Panel ? before2d?.horizontallyBefore : before2d?.verticallyBefore; + const before = this.orientation === ActionsOrientation.HORIZONTAL ? before2d?.horizontallyBefore : before2d?.verticallyBefore; return items.filter(item => item.visible).findIndex(item => item.id === targetId) + (before ? 0 : 1); } diff --git a/src/vs/workbench/browser/parts/paneCompositeBar.ts b/src/vs/workbench/browser/parts/paneCompositeBar.ts index 8fe901376cc..94dce01b958 100644 --- a/src/vs/workbench/browser/parts/paneCompositeBar.ts +++ b/src/vs/workbench/browser/parts/paneCompositeBar.ts @@ -111,7 +111,7 @@ export class PaneCompositeBar extends Disposable { ? ViewContainerLocation.Panel : paneCompositePart.partId === Parts.AUXILIARYBAR_PART ? ViewContainerLocation.AuxiliaryBar : ViewContainerLocation.Sidebar; - this.dndHandler = new CompositeDragAndDrop(this.viewDescriptorService, this.location, + this.dndHandler = new CompositeDragAndDrop(this.viewDescriptorService, this.location, this.options.orientation, async (id: string, focus?: boolean) => { return await this.paneCompositePart.openPaneComposite(id, focus) ?? null; }, (from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, this.options.orientation === ActionsOrientation.VERTICAL ? before?.verticallyBefore : before?.horizontallyBefore), () => this.compositeBar.getCompositeBarItems(), From 2356b7b15a04dcf1162683392348d6a8e11e7080 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 7 Mar 2024 13:15:31 -0800 Subject: [PATCH 1855/1897] fix acc view actions --- .../contrib/accessibility/browser/accessibleView.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index b8411ddadfe..86f6c05bbd1 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -362,7 +362,7 @@ export class AccessibleView extends Disposable { } calculateCodeBlocks(markdown: string): void { - if (!this._currentProvider || this._currentProvider.options.id !== AccessibleViewProviderId.Chat) { + if (this._currentProvider?.id !== AccessibleViewProviderId.Chat) { return; } if (this._currentProvider.options.language && this._currentProvider.options.language !== 'markdown') { @@ -519,9 +519,7 @@ export class AccessibleView extends Disposable { const verbose = this._configurationService.getValue(provider.verbositySettingKey); const exitThisDialogHint = verbose && !provider.options.position ? localize('exit', '\n\nExit this dialog (Escape).') : ''; const newContent = message + provider.provideContent() + readMoreLink + disableHelpHint + exitThisDialogHint; - if (newContent && newContent !== this._currentContent && provider.options.type !== AccessibleViewType.Help && !provider.options.language || provider.options.language === 'markdown') { - this.calculateCodeBlocks(newContent); - } + this.calculateCodeBlocks(newContent); this._currentContent = newContent; this._updateContextKeys(provider, true); const widgetIsFocused = this._editorWidget.hasTextFocus() || this._editorWidget.hasWidgetFocus(); @@ -577,6 +575,7 @@ export class AccessibleView extends Disposable { this._contextViewService.hideContextView(); this._updateContextKeys(provider, false); this._lastProvider = undefined; + this._currentContent = undefined; }; const disposableStore = new DisposableStore(); disposableStore.add(this._editorWidget.onKeyDown((e) => { From c087be970518726e3627163e7f193f95bdd22498 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 7 Mar 2024 13:35:56 -0800 Subject: [PATCH 1856/1897] Adopt module 'Preserve' for TS 5.4+ (#206478) --- extensions/typescript-language-features/src/tsconfig.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/typescript-language-features/src/tsconfig.ts b/extensions/typescript-language-features/src/tsconfig.ts index 196cf185170..04f08a128bc 100644 --- a/extensions/typescript-language-features/src/tsconfig.ts +++ b/extensions/typescript-language-features/src/tsconfig.ts @@ -26,8 +26,8 @@ export function inferredProjectCompilerOptions( serviceConfig: TypeScriptServiceConfiguration, ): Proto.ExternalProjectCompilerOptions { const projectConfig: Proto.ExternalProjectCompilerOptions = { - module: 'ESNext' as Proto.ModuleKind, - moduleResolution: 'Node' as Proto.ModuleResolutionKind, + module: (version.gte(API.v540) ? 'Preserve' : 'ESNext') as Proto.ModuleKind, + moduleResolution: (version.gte(API.v540) ? 'Bundler' : 'Node') as Proto.ModuleResolutionKind, target: 'ES2022' as Proto.ScriptTarget, jsx: 'react' as Proto.JsxEmit, }; From ea874924d938f9a2d0c85f3af9f17788474b96e5 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 7 Mar 2024 13:39:02 -0800 Subject: [PATCH 1857/1897] trap language for acc view codeblock --- .../workbench/contrib/accessibility/browser/accessibleView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 86f6c05bbd1..9e40c9cc8ed 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -374,8 +374,8 @@ export class AccessibleView extends Disposable { let inBlock = false; let startLine = 0; + let languageId: string | undefined; lines.forEach((line, i) => { - let languageId: string | undefined; if (!inBlock && line.startsWith('```')) { inBlock = true; startLine = i + 1; From 44f0428a895c024aad80c36369672b4ec9b4af4d Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 7 Mar 2024 23:06:14 +0100 Subject: [PATCH 1858/1897] Reorder Activity Bar Position Menu (#207113) Activity Bar Position Menu Order --- src/vs/workbench/browser/parts/activitybar/activitybarPart.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 4ee5b07e211..15e58a58179 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -388,7 +388,7 @@ registerAction2(class extends Action2 { toggled: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.DEFAULT), menu: [{ id: MenuId.ActivityBarPositionMenu, - order: 2 + order: 1 }, { id: MenuId.CommandPalette, when: ContextKeyExpr.notEquals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.DEFAULT), @@ -414,7 +414,7 @@ registerAction2(class extends Action2 { toggled: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP), menu: [{ id: MenuId.ActivityBarPositionMenu, - order: 1 + order: 2 }, { id: MenuId.CommandPalette, when: ContextKeyExpr.notEquals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP), From 2bad96c0857c4310b4e112c9fb1b700609a6db17 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 7 Mar 2024 14:17:13 -0800 Subject: [PATCH 1859/1897] add navigation actions, rm toolbars, add to help dialog --- .../browser/actions/chatCodeblockActions.ts | 2 + .../chat/browser/terminalChat.ts | 2 + .../browser/terminalChatAccessibilityHelp.ts | 8 ++-- .../chat/browser/terminalChatActions.ts | 46 +++++++++++++++++++ .../chat/browser/terminalChatWidget.ts | 18 +++++++- 5 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 266c30bfe98..d923b51fa6d 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -90,6 +90,7 @@ export function registerChatCodeBlockActions() { menu: { id: MenuId.ChatCodeBlock, group: 'navigation', + when: ContextKeyExpr.not('terminalChatFocus') } }); } @@ -357,6 +358,7 @@ export function registerChatCodeBlockActions() { id: MenuId.ChatCodeBlock, group: 'navigation', isHiddenByDefault: true, + when: ContextKeyExpr.not('terminalChatFocus') } }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index 89893d6d31e..48fa65a5f50 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -10,6 +10,8 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const enum TerminalChatCommandId { Start = 'workbench.action.terminal.chat.start', Close = 'workbench.action.terminal.chat.close', + FocusResponse = 'workbench.action.terminal.chat.focusResponse', + FocusInput = 'workbench.action.terminal.chat.focusInput', Discard = 'workbench.action.terminal.chat.discard', MakeRequest = 'workbench.action.terminal.chat.makeRequest', Cancel = 'workbench.action.terminal.chat.cancel', diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts index dad7747e7f1..584bdc753d8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -49,12 +49,14 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor): string { const insertCommandKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.InsertCommand)?.getAriaLabel(); const makeRequestKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.MakeRequest)?.getAriaLabel(); const startChatKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.Start)?.getAriaLabel(); + const focusResponseKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.FocusResponse)?.getAriaLabel(); + const focusInputKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.FocusInput)?.getAriaLabel(); content.push(localize('inlineChat.overview', "Inline chat occurs within a terminal. It is useful for suggesting terminal commands. Keep in mind that AI generated code may be incorrect.")); content.push(localize('inlineChat.access', "It can be activated using the command: Terminal: Start Chat ({0}), which will focus the input box.", startChatKeybinding)); content.push(makeRequestKeybinding ? localize('inlineChat.input', "The input box is where the user can type a request and can make the request ({0}). The widget will be closed and all content will be discarded when the Escape key is pressed and the terminal will regain focus.", makeRequestKeybinding) : localize('inlineChat.inputNoKb', "The input box is where the user can type a request and can make the request by tabbing to the Make Request button, which is not currently triggerable via keybindings. The widget will be closed and all content will be discarded when the Escape key is pressed and the terminal will regain focus.")); - content.push(localize('inlineChat.results', "A result may contain a terminal command or just a message. In either case, the result will be announced.")); - content.push(openAccessibleViewKeybinding ? localize('inlineChat.inspectResponseMessage', 'If just a message comes back, it can be inspected in the accessible view ({0}).', openAccessibleViewKeybinding) : localize('inlineChat.inspectResponseNoKb', 'With the input box focused, inspect the response in the accessible view via the Open Accessible View command, which is currently not triggerable by a keybinding.')); - content.push(localize('inlineChat.inspectTerminalCommand', 'If a terminal command comes back, it can be inspected in an editor reached via Shift+Tab.')); + content.push(openAccessibleViewKeybinding ? localize('inlineChat.inspectResponseMessage', 'The response can be inspected in the accessible view ({0}).', openAccessibleViewKeybinding) : localize('inlineChat.inspectResponseNoKb', 'With the input box focused, inspect the response in the accessible view via the Open Accessible View command, which is currently not triggerable by a keybinding.')); + content.push(focusResponseKeybinding ? localize('inlineChat.focusResponse', 'Reach the response from the input box ({0}).', focusResponseKeybinding) : localize('inlineChat.focusResponseNoKb', 'Reach the response from the input box by tabbing or assigning a keybinding for the command: Focus Terminal Response.')); + content.push(focusInputKeybinding ? localize('inlineChat.focusInput', 'Reach the input box from the response ({0}).', focusInputKeybinding) : localize('inlineChat.focusInputNoKb', 'Reach the response from the input box by shift+tabbing or assigning a keybinding for the command: Focus Terminal Input.')); content.push(runCommandKeybinding ? localize('inlineChat.runCommand', 'With focus in the input box or command editor, the Terminal: Run Chat Command ({0}) action.', runCommandKeybinding) : localize('inlineChat.runCommandNoKb', 'Run a command by tabbing to the button as the action is currently not triggerable by a keybinding.')); content.push(insertCommandKeybinding ? localize('inlineChat.insertCommand', 'With focus in the input box command editor, the Terminal: Insert Chat Command ({0}) action.', insertCommandKeybinding) : localize('inlineChat.insertCommandNoKb', 'Insert a command by tabbing to the button as the action is currently not triggerable by a keybinding.')); content.push(localize('inlineChat.toolbar', "Use tab to reach conditional parts like commands, status, message responses and more.")); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 3a46d2929c4..e0ba2d732c7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -72,6 +72,52 @@ registerActiveXtermAction({ } }); +registerActiveXtermAction({ + id: TerminalChatCommandId.FocusResponse, + title: localize2('focusTerminalResponse', 'Focus Terminal Response'), + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + when: TerminalChatContextKeys.focused, + weight: KeybindingWeight.WorkbenchContrib, + }, + f1: true, + category: AbstractInlineChatAction.category, + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalChatContextKeys.focused + ), + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.chatWidget?.focusResponse(); + } +}); + +registerActiveXtermAction({ + id: TerminalChatCommandId.FocusInput, + title: localize2('focusTerminalInput', 'Focus Terminal Input'), + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + when: TerminalChatContextKeys.focused, + weight: KeybindingWeight.WorkbenchContrib, + }, + f1: true, + category: AbstractInlineChatAction.category, + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalChatContextKeys.focused + ), + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.chatWidget?.focus(); + } +}); + registerActiveXtermAction({ id: TerminalChatCommandId.Discard, diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index dcc6e9d5d55..0e5593f71ab 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -106,6 +106,12 @@ export class TerminalChatWidget extends Disposable { focus(): void { this._inlineChatWidget.focus(); } + focusResponse(): void { + const responseElement = this._inlineChatWidget.domNode.querySelector(ChatElementSelectors.ResponseEditor) || this._inlineChatWidget.domNode.querySelector(ChatElementSelectors.ResponseMessage); + if (responseElement instanceof HTMLElement) { + responseElement.focus(); + } + } hasFocus(): boolean { return this._inlineChatWidget.hasFocus(); } @@ -117,7 +123,7 @@ export class TerminalChatWidget extends Disposable { } acceptCommand(shouldExecute: boolean): void { // Trim command to remove any whitespace, otherwise this may execute the command - const value = this._inlineChatWidget?.responseContent?.trim(); + const value = parseCodeFromBlock(this._inlineChatWidget?.responseContent?.trim()); if (!value) { return; } @@ -132,3 +138,13 @@ export class TerminalChatWidget extends Disposable { } } +function parseCodeFromBlock(block?: string): string | undefined { + const match = block?.match(/```.*?\n([\s\S]*?)```/); + return match ? match[1].trim() : undefined; +} + + +const enum ChatElementSelectors { + ResponseEditor = 'div.chatMessageContent .interactive-result-editor .inputarea.monaco-mouse-cursor-text', + ResponseMessage = '.chatMessageContent', +} From 6a371bb1fcdd7a935302b594d83903ec627eba7d Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 7 Mar 2024 23:18:46 +0100 Subject: [PATCH 1860/1897] Fix Activity Bar Icon Clickability in Fullscreen (#207114) fixes #204688 --- src/vs/workbench/browser/parts/media/paneCompositePart.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index 455b38249d9..66bc8d9067e 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -66,7 +66,7 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item.icon, .monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item.icon { - height: 24px; + height: 35px; /* matches height of composite container */ padding: 0 5px; } From d6fb91c889af15e5c9eece516ab520798da33c66 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Thu, 7 Mar 2024 14:27:22 -0800 Subject: [PATCH 1861/1897] show contributed issues in quick access (#206303) * show contributed issues in quick accesss * moved some files around * cleanup unused import * quick access overhaul: add all extensions, filter no duplicates, contributed at bottom, add button to open extension page, add experimental setting * fix to turn on by default * switch to info button icon instad * added issue file on product and marketplace * differentiate btween insiders and stable * some fixes for now * address comments * review fixes --- .../issue/issueReporterModel.ts | 1 + .../issue/issueReporterService.ts | 10 +- src/vs/platform/issue/common/issue.ts | 7 + .../browser/extensions.contribution.ts | 5 + .../issue/browser/issue.contribution.ts | 6 +- .../contrib/issue/browser/issueQuickAccess.ts | 146 ++++++++++++++++++ .../issue/common/issue.contribution.ts | 4 +- .../electron-sandbox/issue.contribution.ts | 39 ++++- 8 files changed, 211 insertions(+), 7 deletions(-) create mode 100644 src/vs/workbench/contrib/issue/browser/issueQuickAccess.ts diff --git a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts index 74f993903e1..1541f98c812 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts @@ -29,6 +29,7 @@ export interface IssueReporterData { extensionsDisabled?: boolean; fileOnExtension?: boolean; fileOnMarketplace?: boolean; + fileOnProduct?: boolean; selectedExtension?: IssueReporterExtensionData; actualSearchResults?: ISettingSearchResult[]; query?: string; diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index 56623913d7d..052bb8adc84 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -74,6 +74,10 @@ export class IssueReporter extends Disposable { selectedExtension: targetExtension }); + const fileOnMarketplace = configuration.data.issueSource === IssueSource.Marketplace; + const fileOnProduct = configuration.data.issueSource === IssueSource.VSCode; + this.issueReporterModel.update({ fileOnMarketplace, fileOnProduct }); + //TODO: Handle case where extension is not activated const issueReporterElement = this.getElementById('issue-reporter'); if (issueReporterElement) { @@ -772,13 +776,17 @@ export class IssueReporter extends Disposable { private setSourceOptions(): void { const sourceSelect = this.getElementById('issue-source')! as HTMLSelectElement; - const { issueType, fileOnExtension, selectedExtension } = this.issueReporterModel.getData(); + const { issueType, fileOnExtension, selectedExtension, fileOnMarketplace, fileOnProduct } = this.issueReporterModel.getData(); let selected = sourceSelect.selectedIndex; if (selected === -1) { if (fileOnExtension !== undefined) { selected = fileOnExtension ? 2 : 1; } else if (selectedExtension?.isBuiltin) { selected = 1; + } else if (fileOnMarketplace) { + selected = 3; + } else if (fileOnProduct) { + selected = 1; } } diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index d1c4e29bb63..df2a1e27599 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -25,6 +25,12 @@ export const enum IssueType { FeatureRequest } +export enum IssueSource { + VSCode = 'vscode', + Extension = 'extension', + Marketplace = 'marketplace' +} + export interface IssueReporterStyles extends WindowStyles { textLinkColor?: string; textLinkActiveForeground?: string; @@ -65,6 +71,7 @@ export interface IssueReporterData extends WindowData { styles: IssueReporterStyles; enabledExtensions: IssueReporterExtensionData[]; issueType?: IssueType; + issueSource?: IssueSource; extensionId?: string; experiments?: string; restrictedMode: boolean; diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 571b1041e79..6d09f44869a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -256,6 +256,11 @@ Registry.as(ConfigurationExtensions.Configuration) type: 'boolean', description: localize('extensionsDeferredStartupFinishedActivation', "When enabled, extensions which declare the `onStartupFinished` activation event will be activated after a timeout."), default: false + }, + 'extensions.experimental.issueQuickAccess': { + type: 'boolean', + description: localize('extensionsInQuickAccess', "When enabled, extensions can be searched for via Quick Access and report issues from there."), + default: true } } }); diff --git a/src/vs/workbench/contrib/issue/browser/issue.contribution.ts b/src/vs/workbench/contrib/issue/browser/issue.contribution.ts index 7d19d1cd3c6..28751d1c2c8 100644 --- a/src/vs/workbench/contrib/issue/browser/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/browser/issue.contribution.ts @@ -13,10 +13,12 @@ import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common import { WebIssueService } from 'vs/workbench/services/issue/browser/issueService'; import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; import { BaseIssueContribution } from 'vs/workbench/contrib/issue/common/issue.contribution'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; + class WebIssueContribution extends BaseIssueContribution { - constructor(@IProductService productService: IProductService) { - super(productService); + constructor(@IProductService productService: IProductService, @IConfigurationService configurationService: IConfigurationService) { + super(productService, configurationService); } } diff --git a/src/vs/workbench/contrib/issue/browser/issueQuickAccess.ts b/src/vs/workbench/contrib/issue/browser/issueQuickAccess.ts new file mode 100644 index 00000000000..baa6325a1ae --- /dev/null +++ b/src/vs/workbench/contrib/issue/browser/issueQuickAccess.ts @@ -0,0 +1,146 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { PickerQuickAccessProvider, IPickerQuickAccessItem, FastAndSlowPicks, Picks, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { matchesFuzzy } from 'vs/base/common/filters'; +import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { localize } from 'vs/nls'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { Codicon } from 'vs/base/common/codicons'; +import { IssueSource } from 'vs/platform/issue/common/issue'; +import { IProductService } from 'vs/platform/product/common/productService'; + +export class IssueQuickAccess extends PickerQuickAccessProvider { + + static PREFIX = 'issue '; + + constructor( + @IMenuService private readonly menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @ICommandService private readonly commandService: ICommandService, + @IExtensionService private readonly extensionService: IExtensionService, + @IProductService private readonly productService: IProductService + ) { + super(IssueQuickAccess.PREFIX, { canAcceptInBackground: true }); + } + + protected override _getPicks(filter: string): Picks | FastAndSlowPicks | Promise | FastAndSlowPicks> | null { + const issuePicks = new Array(); + const extensionIdSet = new Set(); + + // add regular open issue reporter button + const productLabel = this.productService.nameLong; + issuePicks.push({ + label: productLabel, + ariaLabel: productLabel, + accept: () => this.commandService.executeCommand('workbench.action.openIssueReporter', { issueSource: IssueSource.VSCode }) + }); + + issuePicks.push({ type: 'separator' }); + + const marketPlaceLabel = localize("workbench.action.openIssueReporter2", "Extension Marketplace"); + issuePicks.push({ + label: marketPlaceLabel, + ariaLabel: marketPlaceLabel, + accept: () => this.commandService.executeCommand('workbench.action.openIssueReporter', { issueSource: IssueSource.Marketplace }) + }); + + issuePicks.push({ type: 'separator', label: localize('extensions', "Extensions: Custom Reporting") }); + + // creates menu from contributed + const menu = this.menuService.createMenu(MenuId.IssueReporter, this.contextKeyService); + + // render menu and dispose + const actions = menu.getActions({ renderShortTitle: true }).flatMap(entry => entry[1]); + + // create picks from contributed menu + actions.forEach(action => { + if ('source' in action.item && action.item.source) { + extensionIdSet.add(action.item.source.id); + } + + const pick = this._createPick(filter, action); + if (pick) { + issuePicks.push(pick); + } + }); + + menu.dispose(); + + issuePicks.push({ type: 'separator', label: localize('otherExtensions', "Other Extensions") }); + + // create picks from extensions + this.extensionService.extensions.forEach(extension => { + if (!extension.isBuiltin) { + const pick = this._createPick(filter, undefined, extension); + const id = extension.identifier.value; + if (pick) { + if (extensionIdSet.has(id)) { + return; + } + else { + issuePicks.push(pick); + } + } + extensionIdSet.add(id); + } + }); + + return issuePicks; + } + + private _createPick(filter: string, action?: MenuItemAction | SubmenuItemAction | undefined, extension?: IRelaxedExtensionDescription): IPickerQuickAccessItem | undefined { + if (action && 'source' in action.item && action.item.source) { + const label = action.item.source?.title; + const highlights = matchesFuzzy(filter, label, true); + if (highlights) { + return { + label, + highlights: { label: highlights }, + buttons: [{ + iconClass: ThemeIcon.asClassName(Codicon.info), + tooltip: localize('contributedIssuePage', "Open Extension Page") + }], + trigger: () => { + if ('source' in action.item && action.item.source) { + this.commandService.executeCommand('extension.open', action.item.source.id); + } + return TriggerAction.CLOSE_PICKER; + }, + accept: (keyMod, event) => { + action.run(); + } + }; + } + } else if (extension) { + const label = extension.displayName ?? extension.name; + const highlights = matchesFuzzy(filter, label, true); + if (highlights) { + return { + label: label, + highlights: { label: highlights }, + buttons: [{ + iconClass: ThemeIcon.asClassName(Codicon.info), + tooltip: localize('contributedIssuePage', "Open Extension Page") + }], + trigger: () => { + this.commandService.executeCommand('extension.open', extension.identifier.value); + return TriggerAction.CLOSE_PICKER; + }, + accept: (keyMod, event) => { + this.commandService.executeCommand('workbench.action.openIssueReporter', extension.identifier.value); + } + + }; + } + } + return undefined; + } +} diff --git a/src/vs/workbench/contrib/issue/common/issue.contribution.ts b/src/vs/workbench/contrib/issue/common/issue.contribution.ts index 05518d0f4aa..2a48ce274b0 100644 --- a/src/vs/workbench/contrib/issue/common/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/common/issue.contribution.ts @@ -12,6 +12,7 @@ import { IssueReporterData } from 'vs/platform/issue/common/issue'; import { IProductService } from 'vs/platform/product/common/productService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; const OpenIssueReporterActionId = 'workbench.action.openIssueReporter'; const OpenIssueReporterApiId = 'vscode.openIssueReporter'; @@ -59,7 +60,8 @@ interface OpenIssueReporterArgs { export class BaseIssueContribution implements IWorkbenchContribution { constructor( - @IProductService productService: IProductService + @IProductService productService: IProductService, + @IConfigurationService configurationService: IConfigurationService, ) { if (!productService.reportIssueUrl) { return; diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts index 05bc0632624..76ddd71c146 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts @@ -19,19 +19,53 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INativeHostService } from 'vs/platform/native/common/native'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IIssueMainService, IssueType } from 'vs/platform/issue/common/issue'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; +import { IssueQuickAccess } from 'vs/workbench/contrib/issue/browser/issueQuickAccess'; + //#region Issue Contribution class NativeIssueContribution extends BaseIssueContribution { constructor( - @IProductService productService: IProductService + @IProductService productService: IProductService, + @IConfigurationService configurationService: IConfigurationService ) { - super(productService); + super(productService, configurationService); if (productService.reportIssueUrl) { registerAction2(ReportPerformanceIssueUsingReporterAction); } + + let disposable: IDisposable | undefined; + + const registerQuickAccessProvider = () => { + disposable = Registry.as(QuickAccessExtensions.Quickaccess).registerQuickAccessProvider({ + ctor: IssueQuickAccess, + prefix: IssueQuickAccess.PREFIX, + contextKey: 'inReportIssuePicker', + placeholder: localize('tasksQuickAccessPlaceholder', "Type the name of an extension to report on."), + helpEntries: [{ + description: localize('openIssueReporter', "Open Issue Reporter"), + commandId: 'workbench.action.openIssueReporter' + }] + }); + }; + + configurationService.onDidChangeConfiguration(e => { + if (!configurationService.getValue('extensions.experimental.issueQuickAccess') && disposable) { + disposable.dispose(); + disposable = undefined; + } else if (!disposable) { + registerQuickAccessProvider(); + } + }); + + if (configurationService.getValue('extensions.experimental.issueQuickAccess')) { + registerQuickAccessProvider(); + } } } Registry.as(Extensions.Workbench).registerWorkbenchContribution(NativeIssueContribution, LifecyclePhase.Restored); @@ -133,5 +167,4 @@ registerAction2(StopTracing); CommandsRegistry.registerCommand('_issues.getSystemStatus', (accessor) => { return accessor.get(IIssueMainService).getSystemStatus(); }); - //#endregion From 091987d9431154a3624ba20bc1d6de65d71d6472 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 7 Mar 2024 16:04:36 -0800 Subject: [PATCH 1862/1897] Update paste and drop proposals (#206496) * Update paste and drop proposals Reworks the document paste and drop API proposals. Main highlights: - Align more with code action api - Allow a single paste provider to return multiple edits - Allow resolving applied edits lazily - Switch from using ids to scoped kinds like used for code actions * Adding paste context * Add context * Update test --- extensions/ipynb/src/notebookImagePaste.ts | 15 ++- .../copyFiles/dropOrPasteResource.ts | 22 ++-- .../copyFiles/pasteUrlProvider.ts | 11 +- .../singlefolder-tests/documentPaste.test.ts | 18 +-- src/vs/base/common/hierarchicalKind.ts | 28 ++++ src/vs/base/common/jsonSchema.ts | 19 +++ src/vs/editor/common/languages.ts | 41 ++++-- .../browser/copyPasteContribution.ts | 41 +++--- .../browser/copyPasteController.ts | 108 ++++++++++----- .../browser/defaultProviders.ts | 58 +++++---- .../browser/dropIntoEditorController.ts | 18 +-- .../contrib/dropOrPasteInto/browser/edit.ts | 21 ++- .../dropOrPasteInto/browser/postEditWidget.ts | 34 +++-- .../test/browser/editSort.test.ts | 40 +++--- .../api/browser/mainThreadLanguageFeatures.ts | 65 +++++---- .../workbench/api/common/extHost.api.impl.ts | 2 + .../workbench/api/common/extHost.protocol.ts | 27 +++- .../api/common/extHostLanguageFeatures.ts | 95 +++++++++----- src/vs/workbench/api/common/extHostTypes.ts | 44 ++++++- .../vscode.proposed.documentPaste.d.ts | 123 ++++++++++++++---- .../vscode.proposed.dropMetadata.d.ts | 33 +++-- 21 files changed, 585 insertions(+), 278 deletions(-) create mode 100644 src/vs/base/common/hierarchicalKind.ts diff --git a/extensions/ipynb/src/notebookImagePaste.ts b/extensions/ipynb/src/notebookImagePaste.ts index 94292c26a74..263a610da85 100644 --- a/extensions/ipynb/src/notebookImagePaste.ts +++ b/extensions/ipynb/src/notebookImagePaste.ts @@ -48,14 +48,15 @@ function getImageMimeType(uri: vscode.Uri): string | undefined { class DropOrPasteEditProvider implements vscode.DocumentPasteEditProvider, vscode.DocumentDropEditProvider { - public readonly id = 'insertAttachment'; + public static readonly kind = vscode.DocumentPasteEditKind.Empty.append('markdown', 'image', 'attachment'); async provideDocumentPasteEdits( document: vscode.TextDocument, _ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, + _context: vscode.DocumentPasteEditContext, token: vscode.CancellationToken, - ): Promise { + ): Promise { const enabled = vscode.workspace.getConfiguration('ipynb', document).get('pasteImagesAsAttachments.enabled', true); if (!enabled) { return; @@ -66,10 +67,10 @@ class DropOrPasteEditProvider implements vscode.DocumentPasteEditProvider, vscod return; } - const pasteEdit = new vscode.DocumentPasteEdit(insert.insertText, vscode.l10n.t('Insert Image as Attachment')); + const pasteEdit = new vscode.DocumentPasteEdit(insert.insertText, vscode.l10n.t('Insert Image as Attachment'), DropOrPasteEditProvider.kind); pasteEdit.yieldTo = [{ mimeType: MimeType.plain }]; pasteEdit.additionalEdit = insert.additionalEdit; - return pasteEdit; + return [pasteEdit]; } async provideDocumentDropEdits( @@ -86,7 +87,7 @@ class DropOrPasteEditProvider implements vscode.DocumentPasteEditProvider, vscod const dropEdit = new vscode.DocumentDropEdit(insert.insertText); dropEdit.yieldTo = [{ mimeType: MimeType.plain }]; dropEdit.additionalEdit = insert.additionalEdit; - dropEdit.label = vscode.l10n.t('Insert Image as Attachment'); + dropEdit.title = vscode.l10n.t('Insert Image as Attachment'); return dropEdit; } @@ -299,14 +300,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, + providedPasteEditKinds: [DropOrPasteEditProvider.kind], pasteMimeTypes: [ MimeType.png, MimeType.uriList, ], }), vscode.languages.registerDocumentDropEditProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, provider, { - id: provider.id, + providedDropEditKinds: [DropOrPasteEditProvider.kind], dropMimeTypes: [ ...Object.values(imageExtToMime), MimeType.uriList, diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropOrPasteResource.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropOrPasteResource.ts index 41c8bd13146..20ea5a17743 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropOrPasteResource.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropOrPasteResource.ts @@ -22,7 +22,7 @@ import { createInsertUriListEdit, createUriListSnippet, getSnippetLabel } from ' */ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, vscode.DocumentDropEditProvider { - public static readonly id = 'insertResource'; + public static readonly kind = vscode.DocumentPasteEditKind.Empty.append('markdown', 'link'); public static readonly mimeTypes = [ Mime.textUriList, @@ -32,7 +32,7 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v private readonly _yieldTo = [ { mimeType: 'text/plain' }, - { extensionId: 'vscode.ipynb', providerId: 'insertAttachment' }, + { kind: vscode.DocumentPasteEditKind.Empty.append('markdown', 'image', 'attachment') }, ]; public async provideDocumentDropEdits( @@ -62,8 +62,9 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v document: vscode.TextDocument, ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, + _context: vscode.DocumentPasteEditContext, token: vscode.CancellationToken, - ): Promise { + ): Promise { const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.filePaste.enabled', true); if (!enabled) { return; @@ -71,14 +72,15 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v const createEdit = await this._getMediaFilesPasteEdit(document, dataTransfer, token); if (createEdit) { - return createEdit; + return [createEdit]; } if (token.isCancellationRequested) { return; } - return this._createEditFromUriListData(document, ranges, dataTransfer, token); + const edit = await this._createEditFromUriListData(document, ranges, dataTransfer, token); + return edit ? [edit] : undefined; } private async _createEditFromUriListData( @@ -97,7 +99,7 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v return; } - const uriEdit = new vscode.DocumentPasteEdit('', pasteEdit.label); + const uriEdit = new vscode.DocumentPasteEdit('', pasteEdit.label, ResourcePasteOrDropProvider.kind); const edit = new vscode.WorkspaceEdit(); edit.set(document.uri, pasteEdit.edits); uriEdit.additionalEdit = edit; @@ -124,7 +126,7 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v return; } - const pasteEdit = new vscode.DocumentPasteEdit(edit.snippet, edit.label); + const pasteEdit = new vscode.DocumentPasteEdit(edit.snippet, edit.label, ResourcePasteOrDropProvider.kind); pasteEdit.additionalEdit = edit.additionalEdits; pasteEdit.yieldTo = this._yieldTo; return pasteEdit; @@ -150,7 +152,7 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v } const dropEdit = new vscode.DocumentDropEdit(edit.snippet); - dropEdit.label = edit.label; + dropEdit.title = edit.label; dropEdit.additionalEdit = edit.additionalEdits; dropEdit.yieldTo = this._yieldTo; return dropEdit; @@ -226,11 +228,11 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v export function registerResourceDropOrPasteSupport(selector: vscode.DocumentSelector): vscode.Disposable { return vscode.Disposable.from( vscode.languages.registerDocumentPasteEditProvider(selector, new ResourcePasteOrDropProvider(), { - id: ResourcePasteOrDropProvider.id, + providedPasteEditKinds: [ResourcePasteOrDropProvider.kind], pasteMimeTypes: ResourcePasteOrDropProvider.mimeTypes, }), vscode.languages.registerDocumentDropEditProvider(selector, new ResourcePasteOrDropProvider(), { - id: ResourcePasteOrDropProvider.id, + providedDropEditKinds: [ResourcePasteOrDropProvider.kind], dropMimeTypes: ResourcePasteOrDropProvider.mimeTypes, }), ); diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts index a57f0d39005..193112e9630 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts @@ -29,7 +29,7 @@ function getPasteUrlAsFormattedLinkSetting(document: vscode.TextDocument): Paste */ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { - public static readonly id = 'insertMarkdownLink'; + public static readonly kind = vscode.DocumentPasteEditKind.Empty.append('markdown', 'link'); public static readonly pasteMimeTypes = [Mime.textPlain]; @@ -41,8 +41,9 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { document: vscode.TextDocument, ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, + _context: vscode.DocumentPasteEditContext, token: vscode.CancellationToken, - ): Promise { + ): Promise { const pasteUrlSetting = getPasteUrlAsFormattedLinkSetting(document); if (pasteUrlSetting === PasteUrlAsMarkdownLink.Never) { return; @@ -64,7 +65,7 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { return; } - const pasteEdit = new vscode.DocumentPasteEdit('', edit.label); + const pasteEdit = new vscode.DocumentPasteEdit('', edit.label, PasteUrlEditProvider.kind); const workspaceEdit = new vscode.WorkspaceEdit(); workspaceEdit.set(document.uri, edit.edits); pasteEdit.additionalEdit = workspaceEdit; @@ -73,13 +74,13 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { pasteEdit.yieldTo = [{ mimeType: Mime.textPlain }]; } - return pasteEdit; + return [pasteEdit]; } } export function registerPasteUrlSupport(selector: vscode.DocumentSelector, parser: IMdParser) { return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteUrlEditProvider(parser), { - id: PasteUrlEditProvider.id, + providedPasteEditKinds: [PasteUrlEditProvider.kind], pasteMimeTypes: PasteUrlEditProvider.pasteMimeTypes, }); } 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 c2cdd073d74..e2145d4ee28 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)); } } - }, { id: 'test', copyMimeTypes: [textPlain] })); + }, { providedPasteEditKinds: [vscode.DocumentPasteEditKind.Empty.append('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')); } } - }, { id: 'test', copyMimeTypes: [textPlain] })); + }, { providedPasteEditKinds: [vscode.DocumentPasteEditKind.Empty.append('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(' ')}`)); } } - }, { id: 'test', copyMimeTypes: [textPlain] })); + }, { providedPasteEditKinds: [vscode.DocumentPasteEditKind.Empty.append('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(); } - }, { id: 'test', copyMimeTypes: [textPlain] })); + }, { providedPasteEditKinds: [vscode.DocumentPasteEditKind.Empty.append('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')); } - }, { id: 'test', copyMimeTypes: [textPlain] })); + }, { providedPasteEditKinds: [vscode.DocumentPasteEditKind.Empty.append('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(); } - }, { id: 'test', copyMimeTypes: [textPlain] })); + }, { providedPasteEditKinds: [vscode.DocumentPasteEditKind.Empty.append('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))); } - }, { id: 'test', copyMimeTypes: [textPlain] })); + }, { providedPasteEditKinds: [vscode.DocumentPasteEditKind.Empty.append('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')); } - }, { id: 'test', copyMimeTypes: [textPlain] })); + }, { providedPasteEditKinds: [vscode.DocumentPasteEditKind.Empty.append('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'); } - }, { id: 'test', copyMimeTypes: [textPlain] })); + }, { providedPasteEditKinds: [vscode.DocumentPasteEditKind.Empty.append('test')], copyMimeTypes: [textPlain] })); await vscode.commands.executeCommand('editor.action.clipboardCopyAction'); const newDocContent = getNextDocumentText(testDisposables, doc); diff --git a/src/vs/base/common/hierarchicalKind.ts b/src/vs/base/common/hierarchicalKind.ts new file mode 100644 index 00000000000..4df722d8b99 --- /dev/null +++ b/src/vs/base/common/hierarchicalKind.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export class HierarchicalKind { + public static readonly sep = '.'; + + constructor( + public readonly value: string + ) { } + + public equals(other: HierarchicalKind): boolean { + return this.value === other.value; + } + + public contains(other: HierarchicalKind): boolean { + return this.equals(other) || this.value === '' || other.value.startsWith(this.value + HierarchicalKind.sep); + } + + public intersects(other: HierarchicalKind): boolean { + return this.contains(other) || other.contains(this); + } + + public append(...parts: string[]): HierarchicalKind { + return new HierarchicalKind((this.value ? [this.value, ...parts] : parts).join(HierarchicalKind.sep)); + } +} diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts index 81262c2f46a..4216b0e5c0d 100644 --- a/src/vs/base/common/jsonSchema.ts +++ b/src/vs/base/common/jsonSchema.ts @@ -99,3 +99,22 @@ export interface IJSONSchemaSnippet { body?: any; // a object that will be JSON stringified bodyText?: string; // an already stringified JSON object that can contain new lines (\n) and tabs (\t) } + +/** + * Converts a basic JSON schema to a TypeScript type. + * + * TODO: only supports basic schemas. Doesn't support all JSON schema features. + */ +export type SchemaToType = T extends { type: 'string' } + ? string + : T extends { type: 'number' } + ? number + : T extends { type: 'boolean' } + ? boolean + : T extends { type: 'null' } + ? null + : T extends { type: 'object'; properties: infer P } + ? { [K in keyof P]: SchemaToType } + : T extends { type: 'array'; items: infer I } + ? Array> + : never; diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 5af870d82ff..8f8709f5d05 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -25,6 +25,7 @@ import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IMarkerData } from 'vs/platform/markers/common/markers'; import { LanguageFilter } from 'vs/editor/common/languageSelector'; +import { HierarchicalKind } from 'vs/base/common/hierarchicalKind'; /** * @internal @@ -821,35 +822,52 @@ export interface CodeActionProvider { * @internal */ export interface DocumentPasteEdit { - readonly label: string; - readonly detail: string; + readonly title: string; + readonly kind: HierarchicalKind; readonly handledMimeType?: string; readonly yieldTo?: readonly DropYieldTo[]; insertText: string | { readonly snippet: string }; additionalEdit?: WorkspaceEdit; } +/** + * @internal + */ +export enum DocumentPasteTriggerKind { + Automatic = 0, + PasteAs = 1, +} + /** * @internal */ export interface DocumentPasteContext { - readonly only?: string; - readonly trigger: 'explicit' | 'implicit'; + readonly only?: HierarchicalKind; + readonly triggerKind: DocumentPasteTriggerKind; +} + +/** + * @internal + */ +export interface DocumentPasteEditsSession { + edits: readonly DocumentPasteEdit[]; + dispose(): void; } /** * @internal */ export interface DocumentPasteEditProvider { - - readonly id: string; - + readonly id?: string; readonly copyMimeTypes?: readonly string[]; readonly pasteMimeTypes?: readonly string[]; + readonly providedPasteEditKinds?: readonly HierarchicalKind[]; prepareDocumentPaste?(model: model.ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise; - provideDocumentPasteEdits?(model: model.ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise; + provideDocumentPasteEdits?(model: model.ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise; + + resolveDocumentPasteEdit?(edit: DocumentPasteEdit, token: CancellationToken): Promise; } /** @@ -2114,13 +2132,14 @@ export enum ExternalUriOpenerPriority { /** * @internal */ -export type DropYieldTo = { readonly providerId: string } | { readonly mimeType: string }; +export type DropYieldTo = { readonly kind: string } | { readonly mimeType: string }; /** * @internal */ export interface DocumentOnDropEdit { - readonly label: string; + readonly title: string; + readonly kind: HierarchicalKind | undefined; readonly handledMimeType?: string; readonly yieldTo?: readonly DropYieldTo[]; insertText: string | { readonly snippet: string }; @@ -2134,7 +2153,7 @@ export interface DocumentOnDropEditProvider { readonly id?: string; readonly dropMimeTypes?: readonly string[]; - provideDocumentOnDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): ProviderResult; + provideDocumentOnDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): ProviderResult; } export interface DocumentContextItem { diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts index fbdd84d88d4..c291012f89f 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { HierarchicalKind } from 'vs/base/common/hierarchicalKind'; +import { IJSONSchema, SchemaToType } from 'vs/base/common/jsonSchema'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorCommand, EditorContributionInstantiation, ServicesAccessor, registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; @@ -34,7 +36,19 @@ registerEditorCommand(new class extends EditorCommand { } }); -registerEditorAction(class extends EditorAction { + + +registerEditorAction(class PasteAsAction extends EditorAction { + private static readonly argsSchema = { + type: 'object', + properties: { + kind: { + type: 'string', + description: nls.localize('pasteAs.kind', "The kind of the paste edit to try applying. If not provided or there are multiple edits for this kind, the editor will show a picker."), + } + }, + } as const satisfies IJSONSchema; + constructor() { super({ id: 'editor.action.pasteAs', @@ -45,23 +59,20 @@ registerEditorAction(class extends EditorAction { description: 'Paste as', args: [{ name: 'args', - schema: { - type: 'object', - properties: { - 'id': { - type: 'string', - description: nls.localize('pasteAs.id', "The id of the paste edit to try applying. If not provided, the editor will show a picker."), - } - }, - } + schema: PasteAsAction.argsSchema }] } }); } - public override run(_accessor: ServicesAccessor, editor: ICodeEditor, args: any) { - const id = typeof args?.id === 'string' ? args.id : undefined; - return CopyPasteController.get(editor)?.pasteAs(id); + public override run(_accessor: ServicesAccessor, editor: ICodeEditor, args?: SchemaToType) { + let kind = typeof args?.kind === 'string' ? args.kind : undefined; + if (!kind && args) { + // Support old id property + // TODO: remove this in the future + kind = typeof (args as any).id === 'string' ? (args as any).id : undefined; + } + return CopyPasteController.get(editor)?.pasteAs(kind ? { kind: new HierarchicalKind(kind) } : undefined); } }); @@ -75,7 +86,7 @@ registerEditorAction(class extends EditorAction { }); } - public override run(_accessor: ServicesAccessor, editor: ICodeEditor, args: any) { - return CopyPasteController.get(editor)?.pasteAs('text'); + public override run(_accessor: ServicesAccessor, editor: ICodeEditor) { + return CopyPasteController.get(editor)?.pasteAs({ providerId: 'text' }); } }); diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts index adc0684cfca..783cd613daa 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts @@ -20,7 +20,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IRange, Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { Handler, IEditorContribution, PastePayload } from 'vs/editor/common/editorCommon'; -import { DocumentPasteContext, DocumentPasteEdit, DocumentPasteEditProvider } from 'vs/editor/common/languages'; +import { DocumentPasteContext, DocumentPasteEdit, DocumentPasteEditProvider, DocumentPasteTriggerKind } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { createCombinedWorkspaceEdit, sortEditsByYieldTo } from 'vs/editor/contrib/dropOrPasteInto/browser/edit'; @@ -34,6 +34,7 @@ import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/ import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { PostEditWidgetManager } from './postEditWidget'; import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; +import { HierarchicalKind } from 'vs/base/common/hierarchicalKind'; export const changePasteTypeCommandId = 'editor.changePasteType'; @@ -48,6 +49,14 @@ interface CopyMetadata { readonly defaultPastePayload: Omit; } +type PasteEditWithProvider = DocumentPasteEdit & { + provider: DocumentPasteEditProvider; +}; + +type PastePreference = + | { kind: HierarchicalKind } + | { providerId: string }; + export class CopyPasteController extends Disposable implements IEditorContribution { public static readonly ID = 'editor.contrib.copyPasteActionController'; @@ -71,10 +80,10 @@ export class CopyPasteController extends Disposable implements IEditorContributi private readonly _editor: ICodeEditor; private _currentPasteOperation?: CancelablePromise; - private _pasteAsActionContext?: { readonly preferredId: string | undefined }; + private _pasteAsActionContext?: { readonly preferred?: PastePreference }; private readonly _pasteProgressManager: InlineProgressManager; - private readonly _postPasteWidgetManager: PostEditWidgetManager; + private readonly _postPasteWidgetManager: PostEditWidgetManager; constructor( editor: ICodeEditor, @@ -103,10 +112,10 @@ export class CopyPasteController extends Disposable implements IEditorContributi this._postPasteWidgetManager.tryShowSelector(); } - public pasteAs(preferredId?: string) { + public pasteAs(preferred?: { kind: HierarchicalKind } | { providerId: string }) { this._editor.focus(); try { - this._pasteAsActionContext = { preferredId }; + this._pasteAsActionContext = { preferred: preferred }; getActiveDocument().execCommand('paste'); } finally { this._pasteAsActionContext = undefined; @@ -253,17 +262,20 @@ export class CopyPasteController extends Disposable implements IEditorContributi const allProviders = this._languageFeaturesService.documentPasteEditProvider .ordered(model) .filter(provider => { - if (this._pasteAsActionContext?.preferredId) { - if (this._pasteAsActionContext.preferredId !== provider.id) { + // Filter out providers that don't match the requested paste types + const preference = this._pasteAsActionContext?.preferred; + if (preference) { + if (provider.providedPasteEditKinds && !matchesPreference(provider, preference)) { return false; } } + // And providers that don't handle any of mime types in the clipboard return provider.pasteMimeTypes?.some(type => matchesMimeType(type, allPotentialMimeTypes)); }); if (!allProviders.length) { - if (this._pasteAsActionContext?.preferredId) { - this.showPasteAsNoEditMessage(selections, this._pasteAsActionContext?.preferredId); + if (this._pasteAsActionContext?.preferred) { + this.showPasteAsNoEditMessage(selections, this._pasteAsActionContext.preferred); } return; } @@ -275,17 +287,17 @@ export class CopyPasteController extends Disposable implements IEditorContributi e.stopImmediatePropagation(); if (this._pasteAsActionContext) { - this.showPasteAsPick(this._pasteAsActionContext.preferredId, allProviders, selections, dataTransfer, metadata, { trigger: 'explicit', only: this._pasteAsActionContext.preferredId }); + this.showPasteAsPick(this._pasteAsActionContext.preferred, allProviders, selections, dataTransfer, metadata); } else { - this.doPasteInline(allProviders, selections, dataTransfer, metadata, { trigger: 'implicit' }); + this.doPasteInline(allProviders, selections, dataTransfer, metadata); } } - private showPasteAsNoEditMessage(selections: readonly Selection[], editId: string) { - MessageController.get(this._editor)?.showMessage(localize('pasteAsError', "No paste edits for '{0}' found", editId), selections[0].getStartPosition()); + private showPasteAsNoEditMessage(selections: readonly Selection[], preference: PastePreference) { + MessageController.get(this._editor)?.showMessage(localize('pasteAsError', "No paste edits for '{0}' found", 'kind' in preference ? preference.kind.value : preference.providerId), selections[0].getStartPosition()); } - private doPasteInline(allProviders: readonly DocumentPasteEditProvider[], selections: readonly Selection[], dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, context: DocumentPasteContext): void { + private doPasteInline(allProviders: readonly DocumentPasteEditProvider[], selections: readonly Selection[], dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined): void { const p = createCancelablePromise(async (token) => { const editor = this._editor; if (!editor.hasModel()) { @@ -301,6 +313,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi } // Filter out any providers the don't match the full data transfer we will send them. + // TODO: also filter based on kinds const supportedProviders = allProviders.filter(provider => isSupportedPasteProvider(provider, dataTransfer)); if (!supportedProviders.length || (supportedProviders.length === 1 && supportedProviders[0].id === 'text') // Only our default text provider is active @@ -309,20 +322,29 @@ export class CopyPasteController extends Disposable implements IEditorContributi return; } + const context = { + triggerKind: DocumentPasteTriggerKind.Automatic, + }; const providerEdits = await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, tokenSource.token); if (tokenSource.token.isCancellationRequested) { return; } // If the only edit returned is a text edit, use the default paste handler - if (providerEdits.length === 1 && providerEdits[0].providerId === 'text') { + if (providerEdits.length === 1 && providerEdits[0].kind.value === '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); + return this._postPasteWidgetManager.applyEditAndShowIfNeeded(selections, { activeEditIndex: 0, allEdits: providerEdits }, canShowWidget, async (edit, token) => { + const resolved = await edit.provider.resolveDocumentPasteEdit?.(edit, token); + if (resolved) { + edit.additionalEdit = resolved.additionalEdit; + } + return edit; + }, tokenSource.token); } await this.applyDefaultPasteHandler(dataTransfer, metadata, tokenSource.token); @@ -338,7 +360,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi this._currentPasteOperation = p; } - private showPasteAsPick(preferredId: string | undefined, allProviders: readonly DocumentPasteEditProvider[], selections: readonly Selection[], dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, context: DocumentPasteContext): void { + private showPasteAsPick(preference: PastePreference | undefined, allProviders: readonly DocumentPasteEditProvider[], selections: readonly Selection[], dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined): void { const p = createCancelablePromise(async (token) => { const editor = this._editor; if (!editor.hasModel()) { @@ -355,32 +377,46 @@ export class CopyPasteController extends Disposable implements IEditorContributi // Filter out any providers the don't match the full data transfer we will send them. let supportedProviders = allProviders.filter(provider => isSupportedPasteProvider(provider, dataTransfer)); - if (preferredId) { + if (preference) { // We are looking for a specific edit - supportedProviders = supportedProviders.filter(edit => edit.id === preferredId); + supportedProviders = supportedProviders.filter(provider => matchesPreference(provider, preference)); } - const providerEdits = await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, tokenSource.token); + const context = { + triggerKind: DocumentPasteTriggerKind.PasteAs, + only: preference && 'kind' in preference ? preference.kind : undefined, + }; + let providerEdits = await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, tokenSource.token); if (tokenSource.token.isCancellationRequested) { return; } + // Filter out any edits that don't match the requested kind + if (preference) { + providerEdits = providerEdits.filter(edit => { + if ('kind' in preference) { + return preference.kind.contains(edit.kind); + } else { + return preference.providerId === edit.provider.id; + } + }); + } + if (!providerEdits.length) { if (context.only) { - this.showPasteAsNoEditMessage(selections, context.only); + this.showPasteAsNoEditMessage(selections, { kind: context.only }); } return; } let pickedEdit: DocumentPasteEdit | undefined; - if (preferredId) { + if (preference) { pickedEdit = providerEdits.at(0); } else { const selected = await this._quickInputService.pick( providerEdits.map((edit): IQuickPickItem & { edit: DocumentPasteEdit } => ({ - label: edit.label, - description: edit.providerId, - detail: edit.detail, + label: edit.title, + description: edit.kind?.value, edit, })), { placeHolder: localize('pasteAsPickerPlaceholder', "Select Paste Action"), @@ -466,21 +502,20 @@ export class CopyPasteController extends Disposable implements IEditorContributi } } - private async getPasteEdits(providers: readonly DocumentPasteEditProvider[], dataTransfer: VSDataTransfer, model: ITextModel, selections: readonly Selection[], context: DocumentPasteContext, token: CancellationToken): Promise> { + private async getPasteEdits(providers: readonly DocumentPasteEditProvider[], dataTransfer: VSDataTransfer, model: ITextModel, selections: readonly Selection[], context: DocumentPasteContext, token: CancellationToken): Promise { const results = await raceCancellation( Promise.all(providers.map(async provider => { try { - const edit = await provider.provideDocumentPasteEdits?.(model, selections, dataTransfer, context, token); - if (edit) { - return { ...edit, providerId: provider.id }; - } + const edits = await provider.provideDocumentPasteEdits?.(model, selections, dataTransfer, context, token); + // TODO: dispose of edits + return edits?.edits?.map(edit => ({ ...edit, provider })); } catch (err) { console.error(err); } return undefined; })), token); - const edits = coalesce(results ?? []); + const edits = coalesce(results ?? []).flat(); return sortEditsByYieldTo(edits); } @@ -508,3 +543,14 @@ export class CopyPasteController extends Disposable implements IEditorContributi function isSupportedPasteProvider(provider: DocumentPasteEditProvider, dataTransfer: VSDataTransfer): boolean { return Boolean(provider.pasteMimeTypes?.some(type => dataTransfer.matches(type))); } + +function matchesPreference(provider: DocumentPasteEditProvider, preference: PastePreference): boolean { + if ('kind' in preference) { + if (!provider.providedPasteEditKinds) { + return true; + } + return provider.providedPasteEditKinds.some(providedKind => preference.kind.contains(providedKind)); + } else { + return provider.id === preference.providerId; + } +} diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts index 27812236c50..ca2d17065c3 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts @@ -6,6 +6,7 @@ import { coalesce } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IReadonlyVSDataTransfer, UriList } from 'vs/base/common/dataTransfer'; +import { HierarchicalKind } from 'vs/base/common/hierarchicalKind'; import { Disposable } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import { Schemas } from 'vs/base/common/network'; @@ -13,28 +14,34 @@ import { relativePath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange } from 'vs/editor/common/core/range'; -import { DocumentOnDropEdit, DocumentOnDropEditProvider, DocumentPasteContext, DocumentPasteEdit, DocumentPasteEditProvider } from 'vs/editor/common/languages'; +import { DocumentOnDropEdit, DocumentOnDropEditProvider, DocumentPasteContext, DocumentPasteEdit, DocumentPasteEditProvider, DocumentPasteEditsSession, DocumentPasteTriggerKind } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { localize } from 'vs/nls'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -const builtInLabel = localize('builtIn', 'Built-in'); abstract class SimplePasteAndDropProvider implements DocumentOnDropEditProvider, DocumentPasteEditProvider { - abstract readonly id: string; + abstract readonly kind: string; abstract readonly dropMimeTypes: readonly string[] | undefined; abstract readonly pasteMimeTypes: readonly string[]; - async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise { + async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise { const edit = await this.getEdit(dataTransfer, token); - return edit ? { insertText: edit.insertText, label: edit.label, detail: edit.detail, handledMimeType: edit.handledMimeType, yieldTo: edit.yieldTo } : undefined; + if (!edit) { + return undefined; + } + + return { + dispose() { }, + edits: [{ insertText: edit.insertText, title: edit.title, kind: edit.kind, handledMimeType: edit.handledMimeType, yieldTo: edit.yieldTo }] + }; } - async provideDocumentOnDropEdits(_model: ITextModel, _position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise { + async provideDocumentOnDropEdits(_model: ITextModel, _position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise { const edit = await this.getEdit(dataTransfer, token); - return edit ? { insertText: edit.insertText, label: edit.label, handledMimeType: edit.handledMimeType, yieldTo: edit.yieldTo } : undefined; + return edit ? [{ insertText: edit.insertText, title: edit.title, kind: edit.kind, handledMimeType: edit.handledMimeType, yieldTo: edit.yieldTo }] : undefined; } protected abstract getEdit(dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise; @@ -42,7 +49,7 @@ abstract class SimplePasteAndDropProvider implements DocumentOnDropEditProvider, class DefaultTextProvider extends SimplePasteAndDropProvider { - readonly id = 'text'; + readonly kind = 'text'; readonly dropMimeTypes = [Mimes.text]; readonly pasteMimeTypes = [Mimes.text]; @@ -61,16 +68,16 @@ class DefaultTextProvider extends SimplePasteAndDropProvider { const insertText = await textEntry.asString(); return { handledMimeType: Mimes.text, - label: localize('text.label', "Insert Plain Text"), - detail: builtInLabel, - insertText + title: localize('text.label', "Insert Plain Text"), + insertText, + kind: new HierarchicalKind(this.kind), }; } } class PathProvider extends SimplePasteAndDropProvider { - readonly id = 'uri'; + readonly kind = 'uri'; readonly dropMimeTypes = [Mimes.uriList]; readonly pasteMimeTypes = [Mimes.uriList]; @@ -108,15 +115,15 @@ class PathProvider extends SimplePasteAndDropProvider { return { handledMimeType: Mimes.uriList, insertText, - label, - detail: builtInLabel, + title: label, + kind: new HierarchicalKind(this.kind), }; } } class RelativePathProvider extends SimplePasteAndDropProvider { - readonly id = 'relativePath'; + readonly kind = 'relativePath'; readonly dropMimeTypes = [Mimes.uriList]; readonly pasteMimeTypes = [Mimes.uriList]; @@ -144,24 +151,24 @@ class RelativePathProvider extends SimplePasteAndDropProvider { return { handledMimeType: Mimes.uriList, insertText: relativeUris.join(' '), - label: entries.length > 1 + title: entries.length > 1 ? localize('defaultDropProvider.uriList.relativePaths', "Insert Relative Paths") : localize('defaultDropProvider.uriList.relativePath', "Insert Relative Path"), - detail: builtInLabel, + kind: new HierarchicalKind(this.kind), }; } } class PasteHtmlProvider implements DocumentPasteEditProvider { - public readonly id = 'html'; + public readonly kind = new HierarchicalKind('html'); public readonly pasteMimeTypes = ['text/html']; private readonly _yieldTo = [{ mimeType: Mimes.text }]; - async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise { - if (context.trigger !== 'explicit' && context.only !== this.id) { + async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise { + if (context.triggerKind !== DocumentPasteTriggerKind.PasteAs && context.only?.contains(this.kind)) { return; } @@ -172,10 +179,13 @@ class PasteHtmlProvider implements DocumentPasteEditProvider { } return { - insertText: htmlText, - yieldTo: this._yieldTo, - label: localize('pasteHtmlLabel', 'Insert HTML'), - detail: builtInLabel, + dispose() { }, + edits: [{ + insertText: htmlText, + yieldTo: this._yieldTo, + title: localize('pasteHtmlLabel', 'Insert HTML'), + kind: this.kind, + }], }; } } diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts index 48dae75565c..32e64cff650 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts @@ -6,6 +6,7 @@ import { coalesce } from 'vs/base/common/arrays'; import { CancelablePromise, createCancelablePromise, raceCancellation } from 'vs/base/common/async'; import { VSDataTransfer, matchesMimeType } from 'vs/base/common/dataTransfer'; +import { HierarchicalKind } from 'vs/base/common/hierarchicalKind'; import { Disposable } from 'vs/base/common/lifecycle'; import { toExternalVSDataTransfer } from 'vs/editor/browser/dnd'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -45,7 +46,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr private _currentOperation?: CancelablePromise; private readonly _dropProgressManager: InlineProgressManager; - private readonly _postDropWidgetManager: PostEditWidgetManager; + private readonly _postDropWidgetManager: PostEditWidgetManager; private readonly treeItemsTransfer = LocalSelectionTransfer.getInstance(); @@ -115,7 +116,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr const activeEditIndex = this.getInitialActiveEditIndex(model, edits); const canShowWidget = editor.getOption(EditorOption.dropIntoEditor).showDropSelector === 'afterDrop'; // Pass in the parent token here as it tracks cancelling the entire drop operation - await this._postDropWidgetManager.applyEditAndShowIfNeeded([Range.fromPositions(position)], { activeEditIndex, allEdits: edits }, canShowWidget, token); + await this._postDropWidgetManager.applyEditAndShowIfNeeded([Range.fromPositions(position)], { activeEditIndex, allEdits: edits }, canShowWidget, async edit => edit, token); } } finally { tokenSource.dispose(); @@ -132,25 +133,24 @@ export class DropIntoEditorController extends Disposable implements IEditorContr private async getDropEdits(providers: readonly DocumentOnDropEditProvider[], model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, tokenSource: EditorStateCancellationTokenSource) { 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 }; - } + const edits = await provider.provideDocumentOnDropEdits(model, position, dataTransfer, tokenSource.token); + return edits?.map(edit => ({ ...edit, providerId: provider.id })); } catch (err) { console.error(err); } return undefined; })), tokenSource.token); - const edits = coalesce(results ?? []); + const edits = coalesce(results ?? []).flat(); return sortEditsByYieldTo(edits); } private getInitialActiveEditIndex(model: ITextModel, edits: ReadonlyArray) { const preferredProviders = this._configService.getValue>(defaultProviderConfig, { resource: model.uri }); - for (const [configMime, desiredId] of Object.entries(preferredProviders)) { + for (const [configMime, desiredKindStr] of Object.entries(preferredProviders)) { + const desiredKind = new HierarchicalKind(desiredKindStr); const editIndex = edits.findIndex(edit => - desiredId === edit.providerId + desiredKind.value === edit.providerId && edit.handledMimeType && matchesMimeType(configMime, [edit.handledMimeType])); if (editIndex >= 0) { return editIndex; diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/edit.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/edit.ts index 55d8dff9e7b..252c1863cba 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/edit.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/edit.ts @@ -5,21 +5,16 @@ import { URI } from 'vs/base/common/uri'; import { ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; -import { DropYieldTo, WorkspaceEdit } from 'vs/editor/common/languages'; +import { DocumentOnDropEdit, DocumentPasteEdit, DropYieldTo, WorkspaceEdit } from 'vs/editor/common/languages'; import { Range } from 'vs/editor/common/core/range'; import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; - -export interface DropOrPasteEdit { - readonly label: string; - readonly insertText: string | { readonly snippet: string }; - readonly additionalEdit?: WorkspaceEdit; -} +import { HierarchicalKind } from 'vs/base/common/hierarchicalKind'; /** * Given a {@link DropOrPasteEdit} and set of ranges, creates a {@link WorkspaceEdit} that applies the insert text from * the {@link DropOrPasteEdit} at each range plus any additional edits. */ -export function createCombinedWorkspaceEdit(uri: URI, ranges: readonly Range[], edit: DropOrPasteEdit): WorkspaceEdit { +export function createCombinedWorkspaceEdit(uri: URI, ranges: readonly Range[], edit: DocumentPasteEdit | DocumentOnDropEdit): WorkspaceEdit { // If the edit insert text is empty, skip applying at each range if (typeof edit.insertText === 'string' ? edit.insertText === '' : edit.insertText.snippet === '') { return { @@ -39,13 +34,15 @@ export function createCombinedWorkspaceEdit(uri: URI, ranges: readonly Range[], } export function sortEditsByYieldTo(edits: readonly T[]): T[] { function yieldsTo(yTo: DropYieldTo, other: T): boolean { - return ('providerId' in yTo && yTo.providerId === other.providerId) - || ('mimeType' in yTo && yTo.mimeType === other.handledMimeType); + if ('mimeType' in yTo) { + return yTo.mimeType === other.handledMimeType; + } + return !!other.kind && other.kind.contains(new HierarchicalKind(yTo.kind)); } // Build list of nodes each node yields to @@ -84,7 +81,7 @@ export function sortEditsByYieldTo { readonly activeEditIndex: number; - readonly allEdits: ReadonlyArray<{ - readonly label: string; - readonly insertText: string | { readonly snippet: string }; - readonly additionalEdit?: WorkspaceEdit; - }>; + readonly allEdits: ReadonlyArray; } interface ShowCommand { @@ -36,7 +32,7 @@ interface ShowCommand { readonly label: string; } -class PostEditWidget extends Disposable implements IContentWidget { +class PostEditWidget extends Disposable implements IContentWidget { private static readonly baseId = 'editor.widget.postEditWidget'; readonly allowEditorOverflow = true; @@ -53,7 +49,7 @@ class PostEditWidget extends Disposable implements IContentWidget { visibleContext: RawContextKey, private readonly showCommand: ShowCommand, private readonly range: Range, - private readonly edits: EditSet, + private readonly edits: EditSet, private readonly onSelectNewEdit: (editIndex: number) => void, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IContextKeyService contextKeyService: IContextKeyService, @@ -123,7 +119,7 @@ class PostEditWidget extends Disposable implements IContentWidget { getActions: () => { return this.edits.allEdits.map((edit, i) => toAction({ id: '', - label: edit.label, + label: edit.title, checked: i === this.edits.activeEditIndex, run: () => { if (i !== this.edits.activeEditIndex) { @@ -136,9 +132,9 @@ class PostEditWidget extends Disposable implements IContentWidget { } } -export class PostEditWidgetManager extends Disposable { +export class PostEditWidgetManager extends Disposable { - private readonly _currentWidget = this._register(new MutableDisposable()); + private readonly _currentWidget = this._register(new MutableDisposable>()); constructor( private readonly _id: string, @@ -156,18 +152,20 @@ export class PostEditWidgetManager extends Disposable { )(() => this.clear())); } - public async applyEditAndShowIfNeeded(ranges: readonly Range[], edits: EditSet, canShowWidget: boolean, token: CancellationToken) { + public async applyEditAndShowIfNeeded(ranges: readonly Range[], edits: EditSet, canShowWidget: boolean, resolve: (edit: T, token: CancellationToken) => Promise, token: CancellationToken) { const model = this._editor.getModel(); if (!model || !ranges.length) { return; } - const edit = edits.allEdits[edits.activeEditIndex]; + const edit = edits.allEdits.at(edits.activeEditIndex); if (!edit) { return; } - const combinedWorkspaceEdit = createCombinedWorkspaceEdit(model.uri, ranges, edit); + const resolvedEdit = await resolve(edit, token); + + const combinedWorkspaceEdit = createCombinedWorkspaceEdit(model.uri, ranges, resolvedEdit); // Use a decoration to track edits around the trigger range const primaryRange = ranges[0]; @@ -193,16 +191,16 @@ export class PostEditWidgetManager extends Disposable { } await model.undo(); - this.applyEditAndShowIfNeeded(ranges, { activeEditIndex: newEditIndex, allEdits: edits.allEdits }, canShowWidget, token); + this.applyEditAndShowIfNeeded(ranges, { activeEditIndex: newEditIndex, allEdits: edits.allEdits }, canShowWidget, resolve, token); }); } } - public show(range: Range, edits: EditSet, onDidSelectEdit: (newIndex: number) => void) { + public show(range: Range, edits: EditSet, onDidSelectEdit: (newIndex: number) => void) { this.clear(); if (this._editor.hasModel()) { - this._currentWidget.value = this._instantiationService.createInstance(PostEditWidget, this._id, this._editor, this._visibleContext, this._showCommand, range, edits, onDidSelectEdit); + this._currentWidget.value = this._instantiationService.createInstance(PostEditWidget, this._id, this._editor, this._visibleContext, this._showCommand, range, edits, onDidSelectEdit); } } diff --git a/src/vs/editor/contrib/dropOrPasteInto/test/browser/editSort.test.ts b/src/vs/editor/contrib/dropOrPasteInto/test/browser/editSort.test.ts index f41f6866982..884b7bac151 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/test/browser/editSort.test.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/test/browser/editSort.test.ts @@ -3,17 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { HierarchicalKind } from 'vs/base/common/hierarchicalKind'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { DocumentOnDropEdit } from 'vs/editor/common/languages'; import { sortEditsByYieldTo } from 'vs/editor/contrib/dropOrPasteInto/browser/edit'; -type DropEdit = DocumentOnDropEdit & { providerId: string | undefined }; -function createTestEdit(providerId: string, args?: Partial): DropEdit { +function createTestEdit(kind: string, args?: Partial): DocumentOnDropEdit { return { - label: '', + title: '', insertText: '', - providerId, + kind: new HierarchicalKind(kind), ...args, }; } @@ -21,48 +21,48 @@ function createTestEdit(providerId: string, args?: Partial): DropEdit suite('sortEditsByYieldTo', () => { test('Should noop for empty edits', () => { - const edits: DropEdit[] = []; + const edits: DocumentOnDropEdit[] = []; assert.deepStrictEqual(sortEditsByYieldTo(edits), []); }); test('Yielded to edit should get sorted after target', () => { - const edits: DropEdit[] = [ - createTestEdit('a', { yieldTo: [{ providerId: 'b' }] }), + const edits: DocumentOnDropEdit[] = [ + createTestEdit('a', { yieldTo: [{ kind: 'b' }] }), createTestEdit('b'), ]; - assert.deepStrictEqual(sortEditsByYieldTo(edits).map(x => x.providerId), ['b', 'a']); + assert.deepStrictEqual(sortEditsByYieldTo(edits).map(x => x.kind?.value), ['b', 'a']); }); test('Should handle chain of yield to', () => { { - const edits: DropEdit[] = [ - createTestEdit('c', { yieldTo: [{ providerId: 'a' }] }), - createTestEdit('a', { yieldTo: [{ providerId: 'b' }] }), + const edits: DocumentOnDropEdit[] = [ + createTestEdit('c', { yieldTo: [{ kind: 'a' }] }), + createTestEdit('a', { yieldTo: [{ kind: 'b' }] }), createTestEdit('b'), ]; - assert.deepStrictEqual(sortEditsByYieldTo(edits).map(x => x.providerId), ['b', 'a', 'c']); + assert.deepStrictEqual(sortEditsByYieldTo(edits).map(x => x.kind?.value), ['b', 'a', 'c']); } { - const edits: DropEdit[] = [ - createTestEdit('a', { yieldTo: [{ providerId: 'b' }] }), - createTestEdit('c', { yieldTo: [{ providerId: 'a' }] }), + const edits: DocumentOnDropEdit[] = [ + createTestEdit('a', { yieldTo: [{ kind: 'b' }] }), + createTestEdit('c', { yieldTo: [{ kind: 'a' }] }), createTestEdit('b'), ]; - assert.deepStrictEqual(sortEditsByYieldTo(edits).map(x => x.providerId), ['b', 'a', 'c']); + assert.deepStrictEqual(sortEditsByYieldTo(edits).map(x => x.kind?.value), ['b', 'a', 'c']); } }); test(`Should not reorder when yield to isn't used`, () => { - const edits: DropEdit[] = [ - createTestEdit('c', { yieldTo: [{ providerId: 'x' }] }), - createTestEdit('a', { yieldTo: [{ providerId: 'y' }] }), + const edits: DocumentOnDropEdit[] = [ + createTestEdit('c', { yieldTo: [{ kind: 'x' }] }), + createTestEdit('a', { yieldTo: [{ kind: 'y' }] }), createTestEdit('b'), ]; - assert.deepStrictEqual(sortEditsByYieldTo(edits).map(x => x.providerId), ['c', 'a', 'b']); + assert.deepStrictEqual(sortEditsByYieldTo(edits).map(x => x.kind?.value), ['c', 'a', 'b']); }); ensureNoDisposablesAreLeakedInTestSuite(); diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index b7ecbfdb9a2..a41310b7ce8 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -32,9 +32,10 @@ import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy' import * as search from 'vs/workbench/contrib/search/common/search'; import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IdentifiableInlineEdit, IDocumentDropEditProviderMetadata, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IPasteEditProviderMetadataDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape } from '../common/extHost.protocol'; +import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IdentifiableInlineEdit, IDocumentDropEditProviderMetadata, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IPasteEditDto, IPasteEditProviderMetadataDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape } from '../common/extHost.protocol'; import { ResourceMap } from 'vs/base/common/map'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; +import { HierarchicalKind } from 'vs/base/common/hierarchicalKind'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures extends Disposable implements MainThreadLanguageFeaturesShape { @@ -398,8 +399,8 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread private readonly _pasteEditProviders = new Map(); - $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], id: string, metadata: IPasteEditProviderMetadataDto): void { - const provider = new MainThreadPasteEditProvider(handle, this._proxy, id, metadata, this._uriIdentService); + $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], metadata: IPasteEditProviderMetadataDto): void { + const provider = new MainThreadPasteEditProvider(handle, this._proxy, metadata, this._uriIdentService); this._pasteEditProviders.set(handle, provider); this._registrations.set(handle, combinedDisposable( this._languageFeaturesService.documentPasteEditProvider.register(selector, provider), @@ -963,8 +964,8 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread private readonly _documentOnDropEditProviders = new Map(); - $registerDocumentOnDropEditProvider(handle: number, selector: IDocumentFilterDto[], id: string | undefined, metadata: IDocumentDropEditProviderMetadata): void { - const provider = new MainThreadDocumentOnDropEditProvider(handle, this._proxy, id, metadata, this._uriIdentService); + $registerDocumentOnDropEditProvider(handle: number, selector: IDocumentFilterDto[], metadata: IDocumentDropEditProviderMetadata): void { + const provider = new MainThreadDocumentOnDropEditProvider(handle, this._proxy, metadata, this._uriIdentService); this._documentOnDropEditProviders.set(handle, provider); this._registrations.set(handle, combinedDisposable( this._languageFeaturesService.documentOnDropEditProvider.register(selector, provider), @@ -992,23 +993,23 @@ class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider private readonly dataTransfers = new DataTransferFileCache(); - public readonly id: string; public readonly copyMimeTypes?: readonly string[]; public readonly pasteMimeTypes?: readonly string[]; + public readonly providedPasteEditKinds?: readonly HierarchicalKind[]; readonly prepareDocumentPaste?: languages.DocumentPasteEditProvider['prepareDocumentPaste']; readonly provideDocumentPasteEdits?: languages.DocumentPasteEditProvider['provideDocumentPasteEdits']; + readonly resolveDocumentPasteEdit?: languages.DocumentPasteEditProvider['resolveDocumentPasteEdit']; constructor( 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; + this.providedPasteEditKinds = metadata.providedPasteEditKinds?.map(kind => new HierarchicalKind(kind)); if (metadata.supportsCopy) { this.prepareDocumentPaste = async (model: ITextModel, selections: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise => { @@ -1039,20 +1040,40 @@ class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider return; } - const result = await this._proxy.$providePasteEdits(this._handle, request.id, model.uri, selections, dataTransferDto, token); - if (!result) { + const edits = await this._proxy.$providePasteEdits(this._handle, request.id, model.uri, selections, dataTransferDto, { + only: context.only?.value, + triggerKind: context.triggerKind, + }, token); + if (!edits) { return; } return { - ...result, - additionalEdit: result.additionalEdit ? reviveWorkspaceEditDto(result.additionalEdit, this._uriIdentService, dataId => this.resolveFileData(request.id, dataId)) : undefined, + edits: edits.map((edit): languages.DocumentPasteEdit => { + return { + ...edit, + kind: edit.kind ? new HierarchicalKind(edit.kind.value) : new HierarchicalKind(''), + additionalEdit: edit.additionalEdit ? reviveWorkspaceEditDto(edit.additionalEdit, this._uriIdentService, dataId => this.resolveFileData(request.id, dataId)) : undefined, + }; + }), + dispose: () => { + this._proxy.$releasePasteEdits(this._handle, request.id); + }, }; } finally { request.dispose(); } }; } + if (metadata.supportsResolve) { + this.resolveDocumentPasteEdit = async (edit: languages.DocumentPasteEdit, token: CancellationToken) => { + const resolved = await this._proxy.$resolvePasteEdit(this._handle, (edit)._cacheId!, token); + if (resolved.additionalEdit) { + edit.additionalEdit = reviveWorkspaceEditDto(resolved.additionalEdit, this._uriIdentService); + } + return edit; + }; + } } resolveFileData(requestId: number, dataId: string): Promise { @@ -1064,21 +1085,18 @@ class MainThreadDocumentOnDropEditProvider implements languages.DocumentOnDropEd private readonly dataTransfers = new DataTransferFileCache(); - readonly id: string | undefined; readonly dropMimeTypes?: readonly string[]; constructor( 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 ?? ['*/*']; } - async provideDocumentOnDropEdits(model: ITextModel, position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise { + async provideDocumentOnDropEdits(model: ITextModel, position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise { const request = this.dataTransfers.add(dataTransfer); try { const dataTransferDto = await typeConvert.DataTransfer.from(dataTransfer); @@ -1086,15 +1104,18 @@ class MainThreadDocumentOnDropEditProvider implements languages.DocumentOnDropEd return; } - const edit = await this._proxy.$provideDocumentOnDropEdits(this._handle, request.id, model.uri, position, dataTransferDto, token); - if (!edit) { + const edits = await this._proxy.$provideDocumentOnDropEdits(this._handle, request.id, model.uri, position, dataTransferDto, token); + if (!edits) { return; } - return { - ...edit, - additionalEdit: reviveWorkspaceEditDto(edit.additionalEdit, this._uriIdentService, dataId => this.resolveDocumentOnDropFileData(request.id, dataId)), - }; + return edits.map(edit => { + return { + ...edit, + kind: edit.kind ? new HierarchicalKind(edit.kind) : undefined, + additionalEdit: reviveWorkspaceEditDto(edit.additionalEdit, this._uriIdentService, dataId => this.resolveDocumentOnDropFileData(request.id, dataId)), + }; + }); } finally { request.dispose(); } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 20f8ebcdad9..f1a443f47e3 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1613,7 +1613,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ViewColumn: extHostTypes.ViewColumn, WorkspaceEdit: extHostTypes.WorkspaceEdit, // proposed api types + DocumentPasteTriggerKind: extHostTypes.DocumentPasteTriggerKind, DocumentDropEdit: extHostTypes.DocumentDropEdit, + DocumentPasteEditKind: extHostTypes.DocumentPasteEditKind, DocumentPasteEdit: extHostTypes.DocumentPasteEdit, InlayHint: extHostTypes.InlayHint, InlayHintLabelPart: extHostTypes.InlayHintLabelPart, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index e002c997721..9055bfa9aea 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -414,7 +414,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[], id: string, metadata: IPasteEditProviderMetadataDto): void; + $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], 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; @@ -437,7 +437,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[], id: string | undefined, metadata?: IDocumentDropEditProviderMetadata): void; + $registerDocumentOnDropEditProvider(handle: number, selector: IDocumentFilterDto[], 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; @@ -2061,13 +2061,23 @@ export type ITypeHierarchyItemDto = Dto; export interface IPasteEditProviderMetadataDto { readonly supportsCopy: boolean; readonly supportsPaste: boolean; + readonly supportsResolve: boolean; + + readonly providedPasteEditKinds?: readonly string[]; readonly copyMimeTypes?: readonly string[]; readonly pasteMimeTypes?: readonly string[]; } +export interface IDocumentPasteContextDto { + readonly only: string | undefined; + readonly triggerKind: languages.DocumentPasteTriggerKind; + +} + export interface IPasteEditDto { - label: string; - detail: string; + _cacheId?: ChainedCacheId; + title: string; + kind: { value: string } | undefined; insertText: string | { snippet: string }; additionalEdit?: IWorkspaceEditDto; yieldTo?: readonly languages.DropYieldTo[]; @@ -2078,7 +2088,8 @@ export interface IDocumentDropEditProviderMetadata { } export interface IDocumentOnDropEditDto { - label: string; + title: string; + kind: string | undefined; insertText: string | { snippet: string }; additionalEdit?: IWorkspaceEditDto; yieldTo?: readonly languages.DropYieldTo[]; @@ -2104,7 +2115,9 @@ export interface ExtHostLanguageFeaturesShape { $resolveCodeAction(handle: number, id: ChainedCacheId, token: CancellationToken): Promise<{ edit?: IWorkspaceEditDto; command?: ICommandDto }>; $releaseCodeActions(handle: number, cacheId: number): void; $prepareDocumentPaste(handle: number, uri: UriComponents, ranges: readonly IRange[], dataTransfer: DataTransferDTO, token: CancellationToken): Promise; - $providePasteEdits(handle: number, requestId: number, uri: UriComponents, ranges: IRange[], dataTransfer: DataTransferDTO, token: CancellationToken): Promise; + $providePasteEdits(handle: number, requestId: number, uri: UriComponents, ranges: IRange[], dataTransfer: DataTransferDTO, context: IDocumentPasteContextDto, token: CancellationToken): Promise; + $resolvePasteEdit(handle: number, id: ChainedCacheId, token: CancellationToken): Promise<{ additionalEdit?: IWorkspaceEditDto }>; + $releasePasteEdits(handle: number, cacheId: number): void; $provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: languages.FormattingOptions, token: CancellationToken): Promise; $provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: languages.FormattingOptions, token: CancellationToken): Promise; $provideDocumentRangesFormattingEdits(handle: number, resource: UriComponents, range: IRange[], options: languages.FormattingOptions, token: CancellationToken): Promise; @@ -2146,7 +2159,7 @@ export interface ExtHostLanguageFeaturesShape { $provideTypeHierarchySupertypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; $provideTypeHierarchySubtypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; $releaseTypeHierarchy(handle: number, sessionId: string): void; - $provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise; + $provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise; $provideMappedEdits(handle: number, document: UriComponents, codeBlocks: string[], context: IMappedEditsContextDto, token: CancellationToken): Promise; $provideInlineEdit(handle: number, document: UriComponents, context: languages.IInlineEditContext, token: CancellationToken): Promise; $freeInlineEdit(handle: number, pid: number): void; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 1c532bcc8ee..19f6cd063c4 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { coalesce, isFalsyOrEmpty, isNonEmptyArray } from 'vs/base/common/arrays'; +import { asArray, coalesce, isFalsyOrEmpty, isNonEmptyArray } from 'vs/base/common/arrays'; import { raceCancellationError } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -32,7 +32,7 @@ import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostTelemetry, IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { CodeActionKind, CompletionList, Disposable, DocumentSymbol, InlineCompletionTriggerKind, InternalDataTransferItem, Location, Range, SemanticTokens, SemanticTokensEdit, SemanticTokensEdits, SnippetString, SymbolInformation, SyntaxTokenType, InlineEditTriggerKind } from 'vs/workbench/api/common/extHostTypes'; +import { CodeActionKind, CompletionList, Disposable, DocumentPasteEditKind, DocumentSymbol, InlineCompletionTriggerKind, InlineEditTriggerKind, InternalDataTransferItem, Location, Range, SemanticTokens, SemanticTokensEdit, SemanticTokensEdits, SnippetString, SymbolInformation, SyntaxTokenType } from 'vs/workbench/api/common/extHostTypes'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import type * as vscode from 'vscode'; import { Cache } from './cache'; @@ -537,9 +537,7 @@ class CodeActionAdapter { class DocumentPasteEditProvider { - public static toInternalProviderId(extId: string, editId: string): string { - return extId + '.' + editId; - } + private readonly _cache = new Cache('DocumentPasteEdit'); constructor( private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape, @@ -570,9 +568,9 @@ class DocumentPasteEditProvider { return typeConvert.DataTransfer.from(entries); } - async providePasteEdits(requestId: number, resource: URI, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise { + async providePasteEdits(requestId: number, resource: URI, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, context: extHostProtocol.IDocumentPasteContextDto, token: CancellationToken): Promise { if (!this._provider.provideDocumentPasteEdits) { - return; + return []; } const doc = this._documents.getDocument(resource); @@ -582,20 +580,44 @@ class DocumentPasteEditProvider { return (await this._proxy.$resolvePasteFileData(this._handle, requestId, id)).buffer; }); - const edit = await this._provider.provideDocumentPasteEdits(doc, vscodeRanges, dataTransfer, token); - if (!edit) { - return; + const edits = await this._provider.provideDocumentPasteEdits(doc, vscodeRanges, dataTransfer, { + only: context.only ? new DocumentPasteEditKind(context.only) : undefined, + triggerKind: context.triggerKind, + }, token); + if (!edits || token.isCancellationRequested) { + return []; } - return { - label: edit.label ?? localize('defaultPasteLabel', "Paste using '{0}' extension", this._extension.displayName || this._extension.name), + const cacheId = this._cache.add(edits); + + return edits.map((edit, i) => ({ + _cacheId: [cacheId, i], + title: edit.title ?? localize('defaultPasteLabel', "Paste using '{0}' extension", this._extension.displayName || this._extension.name), + kind: edit.kind, detail: this._extension.displayName || this._extension.name, yieldTo: edit.yieldTo?.map(yTo => { - return 'mimeType' in yTo ? yTo : { providerId: DocumentPasteEditProvider.toInternalProviderId(yTo.extensionId, yTo.providerId) }; + return 'mimeType' in yTo ? yTo : { kind: yTo.kind.value }; }), insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value }, additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined) : undefined, - }; + })); + } + + + async resolvePasteEdit(id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise<{ additionalEdit?: extHostProtocol.IWorkspaceEditDto }> { + const [sessionId, itemId] = id; + const item = this._cache.get(sessionId, itemId); + if (!item || !this._provider.resolveDocumentPasteEdit) { + return {}; // this should not happen... + } + + const resolvedItem = (await this._provider.resolveDocumentPasteEdit(item, token)) ?? item; + const additionalEdit = resolvedItem.additionalEdit ? typeConvert.WorkspaceEdit.from(resolvedItem.additionalEdit, undefined) : undefined; + return { additionalEdit }; + } + + releasePasteEdits(id: number): any { + this._cache.delete(id); } } @@ -1940,10 +1962,6 @@ 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, @@ -1952,25 +1970,30 @@ class DocumentOnDropEditAdapter { private readonly _extension: IExtensionDescription, ) { } - async provideDocumentOnDropEdits(requestId: number, uri: URI, position: IPosition, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise { + async provideDocumentOnDropEdits(requestId: number, uri: URI, position: IPosition, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise { const doc = this._documents.getDocument(uri); const pos = typeConvert.Position.to(position); const dataTransfer = typeConvert.DataTransfer.toDataTransfer(dataTransferDto, async (id) => { return (await this._proxy.$resolveDocumentOnDropFileData(this._handle, requestId, id)).buffer; }); - const edit = await this._provider.provideDocumentDropEdits(doc, pos, dataTransfer, token); - if (!edit) { + const edits = await this._provider.provideDocumentDropEdits(doc, pos, dataTransfer, token); + if (!edits) { return undefined; } - return { - label: edit.label ?? localize('defaultDropLabel', "Drop using '{0}' extension", this._extension.displayName || this._extension.name), + + return asArray(edits).map(edit => ({ + title: edit.title ?? localize('defaultDropLabel', "Drop using '{0}' extension", this._extension.displayName || this._extension.name), + kind: edit.kind?.value, yieldTo: edit.yieldTo?.map(yTo => { - return 'mimeType' in yTo ? yTo : { providerId: DocumentOnDropEditAdapter.toInternalProviderId(yTo.extensionId, yTo.providerId) }; + if ('mimeType' in yTo) { + return yTo; + } + return { kind: yTo.kind.value }; }), insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value }, additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined) : undefined, - }; + })); } } @@ -2692,13 +2715,12 @@ 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)); - 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); + this._proxy.$registerDocumentOnDropEditProvider(handle, this._transformDocumentSelector(selector, extension), isProposedApiEnabled(extension, 'dropMetadata') ? metadata : undefined); return this._createDisposable(handle); } - $provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise { + $provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise { return this._withAdapter(handle, DocumentOnDropEditAdapter, adapter => Promise.resolve(adapter.provideDocumentOnDropEdits(requestId, URI.revive(resource), position, dataTransferDto, token)), undefined, undefined); } @@ -2721,10 +2743,11 @@ 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)); - const internalId = DocumentPasteEditProvider.toInternalProviderId(extension.identifier.value, metadata.id); - this._proxy.$registerPasteEditProvider(handle, this._transformDocumentSelector(selector, extension), internalId, { + this._proxy.$registerPasteEditProvider(handle, this._transformDocumentSelector(selector, extension), { supportsCopy: !!provider.prepareDocumentPaste, supportsPaste: !!provider.provideDocumentPasteEdits, + supportsResolve: !!provider.resolveDocumentPasteEdit, + providedPasteEditKinds: metadata.providedPasteEditKinds?.map(x => x.value), copyMimeTypes: metadata.copyMimeTypes, pasteMimeTypes: metadata.pasteMimeTypes, }); @@ -2735,8 +2758,16 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.prepareDocumentPaste(URI.revive(resource), ranges, dataTransfer, token), undefined, token); } - $providePasteEdits(handle: number, requestId: number, resource: UriComponents, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise { - return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.providePasteEdits(requestId, URI.revive(resource), ranges, dataTransferDto, token), undefined, token); + $providePasteEdits(handle: number, requestId: number, resource: UriComponents, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, context: extHostProtocol.IDocumentPasteContextDto, token: CancellationToken): Promise { + return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.providePasteEdits(requestId, URI.revive(resource), ranges, dataTransferDto, context, token), undefined, token); + } + + $resolvePasteEdit(handle: number, id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise<{ additionalEdit?: extHostProtocol.IWorkspaceEditDto }> { + return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.resolvePasteEdit(id, token), {}, undefined); + } + + $releasePasteEdits(handle: number, cacheId: number): void { + this._withAdapter(handle, DocumentPasteEditProvider, adapter => Promise.resolve(adapter.releasePasteEdits(cacheId)), undefined, undefined); } // --- configuration diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index c2cd247e7dd..058582f76ab 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2786,27 +2786,63 @@ export class DataTransfer implements vscode.DataTransfer { @es5ClassCompat export class DocumentDropEdit { + title?: string; + id: string | undefined; insertText: string | SnippetString; additionalEdit?: WorkspaceEdit; - constructor(insertText: string | SnippetString) { + kind?: DocumentPasteEditKind; + + constructor(insertText: string | SnippetString, title?: string, kind?: DocumentPasteEditKind) { this.insertText = insertText; + this.title = title; + this.kind = kind; } } +export enum DocumentPasteTriggerKind { + Automatic = 0, + PasteAs = 1, +} + +export class DocumentPasteEditKind { + static Empty: DocumentPasteEditKind; + + private static sep = '.'; + + constructor( + public readonly value: string + ) { } + + public append(...parts: string[]): DocumentPasteEditKind { + return new DocumentPasteEditKind((this.value ? [this.value, ...parts] : parts).join(DocumentPasteEditKind.sep)); + } + + public intersects(other: DocumentPasteEditKind): boolean { + return this.contains(other) || other.contains(this); + } + + public contains(other: DocumentPasteEditKind): boolean { + return this.value === other.value || other.value.startsWith(this.value + DocumentPasteEditKind.sep); + } +} +DocumentPasteEditKind.Empty = new DocumentPasteEditKind(''); + @es5ClassCompat export class DocumentPasteEdit { - label: string; + title: string; insertText: string | SnippetString; additionalEdit?: WorkspaceEdit; + kind: DocumentPasteEditKind; - constructor(insertText: string | SnippetString, label: string) { - this.label = label; + constructor(insertText: string | SnippetString, title: string, kind: DocumentPasteEditKind) { + this.title = title; this.insertText = insertText; + this.kind = kind; } } diff --git a/src/vscode-dts/vscode.proposed.documentPaste.d.ts b/src/vscode-dts/vscode.proposed.documentPaste.d.ts index 4b5e49ab330..4abe25ad160 100644 --- a/src/vscode-dts/vscode.proposed.documentPaste.d.ts +++ b/src/vscode-dts/vscode.proposed.documentPaste.d.ts @@ -7,10 +7,38 @@ declare module 'vscode' { // https://github.com/microsoft/vscode/issues/30066/ + /** + * The reason why paste edits were requested. + */ + export enum DocumentPasteTriggerKind { + /** + * Pasting was requested as part of a normal paste operation. + */ + Automatic = 0, + + /** + * Pasting was requested by the user with the 'paste as' command. + */ + PasteAs = 1, + } + + /** + * Additional information about the paste operation. + */ + + export interface DocumentPasteEditContext { + readonly only: DocumentPasteEditKind | undefined; + + /** + * The reason why paste edits were requested. + */ + readonly triggerKind: DocumentPasteTriggerKind; + } + /** * Provider invoked when the user copies and pastes code. */ - interface DocumentPasteEditProvider { + interface DocumentPasteEditProvider { /** * Optional method invoked after the user copies text in a file. @@ -19,44 +47,60 @@ declare module 'vscode' { * a {@link DataTransfer} and is passed back to the provider in {@link provideDocumentPasteEdits}. * * @param document Document where the copy took place. - * @param ranges Ranges being copied in the `document`. + * @param ranges Ranges being copied in `document`. * @param dataTransfer The data transfer associated with the copy. You can store additional values on this for later use in {@link provideDocumentPasteEdits}. + * This object is only valid for the duration of this method. * @param token A cancellation token. + * + * @return Optional thenable that resolves when all changes to the `dataTransfer` are complete. */ prepareDocumentPaste?(document: TextDocument, ranges: readonly Range[], dataTransfer: DataTransfer, token: CancellationToken): void | Thenable; /** * Invoked before the user pastes into a document. * - * In this method, extensions can return a workspace edit that replaces the standard pasting behavior. + * Returned edits can replace the standard pasting behavior. * * @param document Document being pasted into * @param ranges Currently selected ranges in the document. * @param dataTransfer The data transfer associated with the paste. + * @param context Additional context for the paste. * @param token A cancellation token. * - * @return Optional workspace edit that applies the paste. Return undefined to use standard pasting. + * @return Set of potential {@link DocumentPasteEdit edits} that apply the paste. Return `undefined` to use standard pasting. */ - provideDocumentPasteEdits?(document: TextDocument, ranges: readonly Range[], dataTransfer: DataTransfer, token: CancellationToken): ProviderResult; + provideDocumentPasteEdits?(document: TextDocument, ranges: readonly Range[], dataTransfer: DataTransfer, context: DocumentPasteEditContext, token: CancellationToken): ProviderResult; + + /** + * Optional method which fills in the {@linkcode DocumentPasteEdit.additionalEdit} before the edit is applied. + * + * This should be used if generating the `additionalEdit` may take a long time. + * + * @param pasteEdit The {@linkcode DocumentPasteEdit} to resolve. + * @param token A cancellation token. + * + * @returns The resolved paste edit or a thenable that resolves to such. It is OK to return the given + * `item`. When no result is returned, the given `item` will be used. + */ + resolveDocumentPasteEdit?(pasteEdit: T, token: CancellationToken): ProviderResult; } /** - * An operation applied on paste + * An edit applied on paste */ class DocumentPasteEdit { /** * Human readable label that describes the edit. */ - label: string; + title: string; /** - * Controls the ordering or multiple paste edits. If this provider yield to edits, it will be shown lower in the list. + * {@link DocumentPasteEditKind Kind} of the edit. + * + * Used to identify specific types of edits. */ - yieldTo?: ReadonlyArray< - | { readonly extensionId: string; readonly providerId: string } - | { readonly mimeType: string } - >; + kind: DocumentPasteEditKind; /** * The text or snippet to insert at the pasted locations. @@ -69,37 +113,60 @@ declare module 'vscode' { additionalEdit?: WorkspaceEdit; /** - * @param insertText The text or snippet to insert at the pasted locations. - * - * TODO: Reverse args, but this will break existing consumers :( + * List of mime types that this edit handles. */ - constructor(insertText: string | SnippetString, label: string); + handledMimeTypes?: readonly string[]; + + /** + * Controls the ordering of paste edits provided by multiple providers. + * + * If this edit yields to another, it will be shown lower in the list of paste edit. + */ + yieldTo?: ReadonlyArray<{ readonly kind: DocumentPasteEditKind } | { readonly mimeType: string }>; + + /** + * Create a new paste edit. + * + * @param insertText The text or snippet to insert at the pasted locations. + * @param title Human readable label that describes the edit. + * @param kind {@link DocumentPasteEditKind Kind} of the edit. + */ + constructor(insertText: string | SnippetString, title: string, kind: DocumentPasteEditKind); + } + + + /** + * TODO: Share with code action kind? + */ + class DocumentPasteEditKind { + static readonly Empty: DocumentPasteEditKind; + private constructor(value: string); + + readonly value: string; + + append(...parts: string[]): CodeActionKind; + intersects(other: CodeActionKind): boolean; + contains(other: CodeActionKind): boolean; } 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; + // TODO + readonly providedPasteEditKinds?: readonly DocumentPasteEditKind[]; /** - * Mime types that {@link DocumentPasteEditProvider.prepareDocumentPaste provideDocumentPasteEdits} may add on copy. + * Mime types that {@linkcode DocumentPasteEditProvider.prepareDocumentPaste prepareDocumentPaste} may add on copy. */ readonly copyMimeTypes?: readonly string[]; /** - * Mime types that {@link DocumentPasteEditProvider.provideDocumentPasteEdits provideDocumentPasteEdits} should be invoked for. + * Mime types that {@linkcode DocumentPasteEditProvider.provideDocumentPasteEdits provideDocumentPasteEdits} should be invoked for. * * This can either be an exact mime type such as `image/png`, or a wildcard pattern such as `image/*`. * * Use `text/uri-list` for resources dropped from the explorer or other tree views in the workbench. * - * Use `files` to indicate that the provider should be invoked if any {@link DataTransferFile files} are present in the {@link DataTransfer}. - * Note that {@link DataTransferFile} entries are only created when dropping content from outside the editor, such as + * Use `files` to indicate that the provider should be invoked if any {@link DataTransferFile files} are present in the {@linkcode DataTransfer}. + * Note that {@linkcode DataTransferFile} entries are only created when dropping content from outside the editor, such as * from the operating system. */ readonly pasteMimeTypes?: readonly string[]; diff --git a/src/vscode-dts/vscode.proposed.dropMetadata.d.ts b/src/vscode-dts/vscode.proposed.dropMetadata.d.ts index b78790d85a6..c851f13f9df 100644 --- a/src/vscode-dts/vscode.proposed.dropMetadata.d.ts +++ b/src/vscode-dts/vscode.proposed.dropMetadata.d.ts @@ -7,11 +7,27 @@ declare module 'vscode' { // https://github.com/microsoft/vscode/issues/179430 + + /** + * TODO: + * - Add ctor(insertText: string | SnippetString, title?: string, kind?: DocumentPasteEditKind); + * - Update provide to return multiple edits + */ + export interface DocumentDropEdit { /** * Human readable label that describes the edit. */ - label?: string; + title?: string; + + /** + * {@link DocumentPasteEditKind Kind} of the edit. + * + * Used to identify specific types of edits. + * + * TODO: use own type? + */ + kind: DocumentPasteEditKind; /** * The mime type from the {@link DataTransfer} that this edit applies. @@ -23,22 +39,11 @@ 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< - // TODO: what about built-in providers? - | { readonly extensionId: string; readonly providerId: string } - | { readonly mimeType: string } - >; + yieldTo?: ReadonlyArray<{ readonly kind: DocumentPasteEditKind } | { readonly mimeType: string }>; } export interface DocumentDropEditProviderMetadata { - /** - * 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; + readonly providedDropEditKinds?: readonly DocumentPasteEditKind[]; /** * List of {@link DataTransfer} mime types that the provider can handle. From dcee2edef1e64642004fc3825f4aeea60b9aa17c Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 7 Mar 2024 19:08:21 -0800 Subject: [PATCH 1863/1897] testing: additional context keys and state around test results (#207119) For explorations with copilot later --- src/vs/workbench/api/common/extHostTesting.ts | 6 ++- .../testing/browser/testingOutputPeek.ts | 40 ++++++++++--------- .../contrib/testing/common/testTypes.ts | 12 +++++- .../testing/common/testingContextKeys.ts | 4 ++ .../actions/common/menusExtensionPoint.ts | 5 +++ 5 files changed, 46 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTesting.ts b/src/vs/workbench/api/common/extHostTesting.ts index 597865e61c0..5d0ca1cb8b9 100644 --- a/src/vs/workbench/api/common/extHostTesting.ts +++ b/src/vs/workbench/api/common/extHostTesting.ts @@ -74,9 +74,11 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { return controller?.collection.tree.get(targetTest)?.actual ?? toItemFromContext(arg); } case MarshalledId.TestMessageMenuArgs: { - const { extId, message } = arg as ITestMessageMenuArgs; + const { test, message } = arg as ITestMessageMenuArgs; + const extId = test.item.extId; return { - test: this.controllers.get(TestId.root(extId))?.collection.tree.get(extId)?.actual, + test: this.controllers.get(TestId.root(extId))?.collection.tree.get(extId)?.actual + ?? toItemFromContext({ $mid: MarshalledId.TestItemContext, tests: [test] }), message: Convert.TestMessage.to(message as ITestErrorMessage.Serialized), }; } diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 901d0a5d5da..1d38f8d8389 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -98,13 +98,19 @@ import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testPro import { ITaskRawOutput, ITestResult, ITestRunTaskResults, LiveTestResult, TestResultItemChange, TestResultItemChangeReason, maxCountPriority, resultItemParents } from 'vs/workbench/contrib/testing/common/testResult'; import { ITestResultService, ResultChangeEvent } from 'vs/workbench/contrib/testing/common/testResultService'; import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; -import { IRichLocation, ITestErrorMessage, ITestItem, ITestMessage, ITestMessageMenuArgs, ITestRunTask, ITestTaskState, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset, getMarkId } from 'vs/workbench/contrib/testing/common/testTypes'; +import { IRichLocation, ITestErrorMessage, ITestItem, ITestItemContext, ITestMessage, ITestMessageMenuArgs, ITestRunTask, ITestTaskState, InternalTestItem, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset, getMarkId, testResultStateToContextValues } 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'; import { ParsedTestUri, TestUriType, buildTestUri, parseTestUri } from 'vs/workbench/contrib/testing/common/testingUri'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +const getMessageArgs = (test: TestResultItem, message: ITestMessage): ITestMessageMenuArgs => ({ + $mid: MarshalledId.TestMessageMenuArgs, + test: InternalTestItem.serialize(test), + message: ITestMessage.serialize(message), +}); + class MessageSubject { public readonly test: ITestItem; public readonly message: ITestMessage; @@ -112,6 +118,7 @@ class MessageSubject { public readonly actualUri: URI; public readonly messageUri: URI; public readonly revealLocation: IRichLocation | undefined; + public readonly context: ITestMessageMenuArgs | undefined; public get isDiffable() { return this.message.type === TestMessageType.Error && isDiffable(this.message); @@ -121,14 +128,6 @@ class MessageSubject { return this.message.type === TestMessageType.Error ? this.message.contextValue : undefined; } - public get context(): ITestMessageMenuArgs { - return { - $mid: MarshalledId.TestMessageMenuArgs, - extId: this.test.extId, - message: ITestMessage.serialize(this.message), - }; - } - constructor(public readonly result: ITestResult, test: TestResultItem, public readonly taskIndex: number, public readonly messageIndex: number) { this.test = test.item; const messages = test.tasks[taskIndex].messages; @@ -140,6 +139,7 @@ class MessageSubject { this.messageUri = buildTestUri({ ...parts, type: TestUriType.ResultMessage }); const message = this.message = messages[this.messageIndex]; + this.context = getMessageArgs(test, message); this.revealLocation = message.location ?? (test.item.uri && test.item.range ? { uri: test.item.uri, range: Range.lift(test.item.range) } : undefined); } } @@ -1745,7 +1745,10 @@ class CoverageElement implements ITreeElement { class TestCaseElement implements ITreeElement { public readonly type = 'test'; - public readonly context = this.test.item.extId; + public readonly context: ITestItemContext = { + $mid: MarshalledId.TestItemContext, + tests: [InternalTestItem.serialize(this.test)], + }; public readonly id = `${this.results.id}/${this.test.item.extId}`; public readonly description?: string; @@ -1826,13 +1829,12 @@ class TestMessageElement implements ITreeElement { } public get context(): ITestMessageMenuArgs { - return { - $mid: MarshalledId.TestMessageMenuArgs, - extId: this.test.item.extId, - message: ITestMessage.serialize(this.message), - }; + return getMessageArgs(this.test, this.message); } + public get outputSubject() { + return new TestOutputSubject(this.result, this.taskIndex, this.test); + } constructor( public readonly result: ITestResult, @@ -2357,11 +2359,10 @@ class TreeActionsProvider { if (element instanceof TestCaseElement || element instanceof TestMessageElement) { contextKeys.push( [TestingContextKeys.testResultOutdated.key, element.test.retired], + [TestingContextKeys.testResultState.key, testResultStateToContextValues[element.test.ownComputedState]], ...getTestItemContextOverlay(element.test, capabilities), ); - } - if (element instanceof TestCaseElement) { const extId = element.test.item.extId; if (element.test.tasks[element.taskIndex].messages.some(m => m.type === TestMessageType.Output)) { primary.push(new Action( @@ -2401,12 +2402,15 @@ class TreeActionsProvider { )); } + } + + if (element instanceof TestMessageElement) { primary.push(new Action( 'testing.outputPeek.goToFile', localize('testing.goToFile', "Go to Source"), ThemeIcon.asClassName(Codicon.goToFile), undefined, - () => this.commandService.executeCommand('vscode.revealTest', extId), + () => this.commandService.executeCommand('vscode.revealTest', element.test.item.extId), )); } diff --git a/src/vs/workbench/contrib/testing/common/testTypes.ts b/src/vs/workbench/contrib/testing/common/testTypes.ts index 7737ef78020..c760e14bef2 100644 --- a/src/vs/workbench/contrib/testing/common/testTypes.ts +++ b/src/vs/workbench/contrib/testing/common/testTypes.ts @@ -21,6 +21,16 @@ export const enum TestResultState { Errored = 6 } +export const testResultStateToContextValues: { [K in TestResultState]: string } = { + [TestResultState.Unset]: 'unset', + [TestResultState.Queued]: 'queued', + [TestResultState.Running]: 'running', + [TestResultState.Passed]: 'passed', + [TestResultState.Failed]: 'failed', + [TestResultState.Skipped]: 'skipped', + [TestResultState.Errored]: 'errored', +}; + /** note: keep in sync with TestRunProfileKind in vscode.d.ts */ export const enum ExtTestRunProfileKind { Run = 1, @@ -755,7 +765,7 @@ export interface ITestMessageMenuArgs { /** Marshalling marker */ $mid: MarshalledId.TestMessageMenuArgs; /** Tests ext ID */ - extId: string; + test: InternalTestItem.Serialized; /** Serialized test message */ message: ITestMessage.Serialized; } diff --git a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts index cc7821a4e27..ddef4fcdc15 100644 --- a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts +++ b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts @@ -67,4 +67,8 @@ export namespace TestingContextKeys { type: 'boolean', description: localize('testing.testResultOutdated', 'Value available in editor/content and testing/message/context when the result is outdated') }); + export const testResultState = new RawContextKey('testResultState', undefined, { + type: 'string', + description: localize('testing.testResultState', 'Value available testing/item/result indicating the state of the item.') + }); } diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index c43a6eb3bed..0f0346f9fb3 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -343,6 +343,11 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.TestItemGutter, description: localize('testing.item.gutter.title', "The menu for a gutter decoration for a test item"), }, + { + key: 'testing/item/result', + id: MenuId.TestPeekElement, + description: localize('testing.item.result.title', "The menu for an item in the Test Results view or peek."), + }, { key: 'testing/message/context', id: MenuId.TestMessageContext, From 1c2151afda3afebd46d4e1d86c5d2f5d79363acc Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 8 Mar 2024 00:27:09 -0300 Subject: [PATCH 1864/1897] Fix expected errors in telemetry (#207128) * Fix #206108 * Fix #206109 --- src/vs/workbench/contrib/chat/common/chatServiceImpl.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index ac232bc4dba..ca303ca277d 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { ErrorNoTelemetry } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Iterable } from 'vs/base/common/iterator'; @@ -366,7 +367,7 @@ export class ChatService extends Disposable implements IChatService { const provider = this._providers.get(model.providerId); if (!provider) { - throw new Error(`Unknown provider: ${model.providerId}`); + throw new ErrorNoTelemetry(`Unknown provider: ${model.providerId}`); } let session: IChat | undefined; @@ -384,7 +385,7 @@ export class ChatService extends Disposable implements IChatService { const defaultAgent = this.chatAgentService.getDefaultAgent(); if (!defaultAgent) { - throw new Error('No default agent'); + throw new ErrorNoTelemetry('No default agent'); } const welcomeMessage = model.welcomeMessage ? undefined : await defaultAgent.provideWelcomeMessage?.(token) ?? undefined; From 6c19c003574b02afc604c0c1c471b1441b33a3d3 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 7 Mar 2024 20:28:31 -0800 Subject: [PATCH 1865/1897] Revert "Only one subscriber for kernels for onDidChangeSelectedNotebooks (#204417)" (#207132) This reverts commit 59ec734843767b100e4bd476903e0c363772a1f9. --- .../api/browser/mainThreadNotebookKernels.ts | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index c7d0f3a6611..678825ac6a5 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -7,7 +7,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { combinedDisposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -151,16 +151,6 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape this._proxy.$cellExecutionChanged(e.notebook, e.cellHandle, e.changed?.state); } })); - - this._disposables.add(this._notebookKernelService.onDidChangeSelectedNotebooks(e => { - for (const [handle, [kernel,]] of this._kernels) { - if (e.oldKernel === kernel.id) { - this._proxy.$acceptNotebookAssociation(handle, e.notebook, false); - } else if (e.newKernel === kernel.id) { - this._proxy.$acceptNotebookAssociation(handle, e.notebook, true); - } - } - })); } dispose(): void { @@ -272,8 +262,16 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape } }(data, this._languageService); + const listener = this._notebookKernelService.onDidChangeSelectedNotebooks(e => { + if (e.oldKernel === kernel.id) { + this._proxy.$acceptNotebookAssociation(handle, e.notebook, false); + } else if (e.newKernel === kernel.id) { + this._proxy.$acceptNotebookAssociation(handle, e.notebook, true); + } + }); + const registration = this._notebookKernelService.registerKernel(kernel); - this._kernels.set(handle, [kernel, registration]); + this._kernels.set(handle, [kernel, combinedDisposable(listener, registration)]); } $updateKernel(handle: number, data: Partial): void { From 59857a62e6e1705a3a3d493e7557cb032a63a1c2 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 8 Mar 2024 12:39:44 +0100 Subject: [PATCH 1866/1897] Add reply to the comments view context menu (#207156) Fixes microsoft/vscode-pull-request-github#5698 --- .../api/browser/mainThreadComments.ts | 3 +- .../workbench/api/common/extHostComments.ts | 10 +-- src/vs/workbench/common/comments.ts | 17 +++++ .../contrib/comments/browser/commentNode.ts | 3 +- .../comments/browser/commentThreadHeader.ts | 3 +- .../comments/browser/commentThreadWidget.ts | 2 +- .../browser/commentThreadZoneWidget.ts | 16 ++++- .../comments/browser/comments.contribution.ts | 28 +++++++- .../comments/browser/commentsController.ts | 67 +++++++++++++++++-- .../comments/browser/commentsTreeViewer.ts | 11 +-- .../contrib/comments/browser/commentsView.ts | 63 ++--------------- .../contrib/comments/common/commentModel.ts | 26 ++++--- 12 files changed, 161 insertions(+), 88 deletions(-) create mode 100644 src/vs/workbench/common/comments.ts diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 8896e0b6e1c..a5bfd4f0d49 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -26,6 +26,7 @@ import { MarshalledId } from 'vs/base/common/marshallingIds'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { Schemas } from 'vs/base/common/network'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { MarshalledCommentThread } from 'vs/workbench/common/comments'; export class MainThreadCommentThread implements languages.CommentThread { private _input?: languages.CommentInput; @@ -197,7 +198,7 @@ export class MainThreadCommentThread implements languages.CommentThread { this._onDidChangeState.dispose(); } - toJSON(): any { + toJSON(): MarshalledCommentThread { return { $mid: MarshalledId.CommentThread, commentControlHandle: this.controllerHandle, diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 3e20208c247..eb85e826f5f 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -20,6 +20,7 @@ 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 { MarshalledCommentThread } from 'vs/workbench/common/comments'; type ProviderHandle = number; @@ -53,16 +54,17 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo return commentController.value; } else if (arg && arg.$mid === MarshalledId.CommentThread) { - const commentController = this._commentControllers.get(arg.commentControlHandle); + const marshalledCommentThread: MarshalledCommentThread = arg; + const commentController = this._commentControllers.get(marshalledCommentThread.commentControlHandle); if (!commentController) { - return arg; + return marshalledCommentThread; } - const commentThread = commentController.getCommentThread(arg.commentThreadHandle); + const commentThread = commentController.getCommentThread(marshalledCommentThread.commentThreadHandle); if (!commentThread) { - return arg; + return marshalledCommentThread; } return commentThread.value; diff --git a/src/vs/workbench/common/comments.ts b/src/vs/workbench/common/comments.ts new file mode 100644 index 00000000000..038819d8f99 --- /dev/null +++ b/src/vs/workbench/common/comments.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { MarshalledId } from 'vs/base/common/marshallingIds'; +import { CommentThread } from 'vs/editor/common/languages'; + +export interface MarshalledCommentThread { + $mid: MarshalledId.CommentThread; + commentControlHandle: number; + commentThreadHandle: number; +} + +export interface MarshalledCommentThreadInternal extends MarshalledCommentThread { + thread: CommentThread; +} diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 95ef19e971e..df2afb078e5 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -50,6 +50,7 @@ import { COMMENTS_SECTION, ICommentsConfiguration } from 'vs/workbench/contrib/c import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { MarshalledCommentThread } from 'vs/workbench/common/comments'; class CommentsActionRunner extends ActionRunner { protected override async runAction(action: IAction, context: any[]): Promise { @@ -293,7 +294,7 @@ export class CommentNode extends Disposable { return result; } - private get commentNodeContext() { + private get commentNodeContext(): [any, MarshalledCommentThread] { return [{ thread: this.commentThread, commentUniqueId: this.comment.uniqueIdInThread, diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts b/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts index b206d26b011..9784625cd2f 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts @@ -22,6 +22,7 @@ import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { MarshalledId } from 'vs/base/common/marshallingIds'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { MarshalledCommentThread } from 'vs/workbench/common/comments'; const collapseIcon = registerIcon('review-comment-collapse', Codicon.chevronUp, nls.localize('collapseIcon', 'Icon to collapse a review comment.')); const COLLAPSE_ACTION_CLASS = 'expand-review-action ' + ThemeIcon.asClassName(collapseIcon); @@ -122,7 +123,7 @@ export class CommentThreadHeader extends Disposable { getAnchor: () => event, getActions: () => actions, actionRunner: new ActionRunner(), - getActionsContext: () => { + getActionsContext: (): MarshalledCommentThread => { return { commentControlHandle: this._commentThread.controllerHandle, commentThreadHandle: this._commentThread.commentThreadHandle, diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index bcd9366e524..e09cd4f5167 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -351,7 +351,7 @@ export class CommentThreadWidget extends } focusCommentEditor() { - this._commentReply?.focusCommentEditor(); + this._commentReply?.expandReplyAreaAndFocusCommentEditor(); } focus() { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index baa3438345d..a3c6f70f09a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -31,6 +31,12 @@ function getCommentThreadWidgetStateColor(thread: languages.CommentThreadState | return getCommentThreadStateBorderColor(thread, theme) ?? theme.getColor(peekViewBorder); } +export enum CommentWidgetFocus { + None = 0, + Widget = 1, + Editor = 2 +} + export function parseMouseDownInfoFromEvent(e: IEditorMouseEvent) { const range = e.target.range; @@ -181,7 +187,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget // we don't do anything here as we always do the reveal ourselves. } - public reveal(commentUniqueId?: number, focus: boolean = false) { + public reveal(commentUniqueId?: number, focus: CommentWidgetFocus = CommentWidgetFocus.None) { if (!this._isExpanded) { this.show(this.arrowPosition(this._commentThread.range), 2); } @@ -197,16 +203,20 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget scrollTop = this.editor.getTopForLineNumber(this._commentThread.range.startLineNumber) - height / 2 + commentCoords.top - commentThreadCoords.top; } this.editor.setScrollTop(scrollTop); - if (focus) { + if (focus === CommentWidgetFocus.Widget) { this._commentThreadWidget.focus(); + } else if (focus === CommentWidgetFocus.Editor) { + this._commentThreadWidget.focusCommentEditor(); } return; } } this.editor.revealRangeInCenter(this._commentThread.range ?? new Range(1, 1, 1, 1)); - if (focus) { + if (focus === CommentWidgetFocus.Widget) { this._commentThreadWidget.focus(); + } else if (focus === CommentWidgetFocus.Editor) { + this._commentThreadWidget.focusCommentEditor(); } } diff --git a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts index b47cdb2b883..e57e7c315e2 100644 --- a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts +++ b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts @@ -17,10 +17,14 @@ import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/co import { COMMENTS_VIEW_ID } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; import { CommentThreadState } from 'vs/editor/common/languages'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { CONTEXT_KEY_HAS_COMMENTS, CONTEXT_KEY_SOME_COMMENTS_EXPANDED, CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsView'; import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { Codicon } from 'vs/base/common/codicons'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { revealCommentThread } from 'vs/workbench/contrib/comments/browser/commentsController'; +import { MarshalledCommentThreadInternal } from 'vs/workbench/common/comments'; registerAction2(class Collapse extends ViewAction { constructor() { @@ -64,6 +68,28 @@ registerAction2(class Expand extends ViewAction { } }); +registerAction2(class Reply extends Action2 { + constructor() { + super({ + id: 'comments.reply', + title: nls.localize('reply', "Reply"), + icon: Codicon.reply, + menu: { + id: MenuId.CommentsViewThreadActions, + order: 100, + when: ContextKeyExpr.equals('canReply', true) + }, + }); + } + + override run(accessor: ServicesAccessor, marshalledCommentThread: MarshalledCommentThreadInternal): void { + const commentService = accessor.get(ICommentService); + const editorService = accessor.get(IEditorService); + const uriIdentityService = accessor.get(IUriIdentityService); + revealCommentThread(commentService, editorService, uriIdentityService, marshalledCommentThread.thread, marshalledCommentThread.thread.comments![marshalledCommentThread.thread.comments!.length - 1], true); + } +}); + Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ id: 'comments', order: 20, diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index e83cb560c6c..7abfde48305 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -10,12 +10,12 @@ import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/com import { onUnexpectedError } from 'vs/base/common/errors'; import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import 'vs/css!./media/review'; -import { ICodeEditor, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IEditorMouseEvent, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { EditorType, IDiffEditor, IEditorContribution, IModelChangedEvent } from 'vs/editor/common/editorCommon'; +import { EditorType, IDiffEditor, IEditor, IEditorContribution, IModelChangedEvent } from 'vs/editor/common/editorCommon'; import { IModelDecorationOptions, IModelDeltaDecoration } from 'vs/editor/common/model'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; +import { ModelDecorationOptions, TextModel } from 'vs/editor/common/model/textModel'; import * as languages from 'vs/editor/common/languages'; import * as nls from 'vs/nls'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -23,8 +23,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { CommentGlyphWidget } from 'vs/workbench/contrib/comments/browser/commentGlyphWidget'; import { ICommentInfo, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; -import { isMouseUpEventDragFromMouseDown, parseMouseDownInfoFromEvent, ReviewZoneWidget } from 'vs/workbench/contrib/comments/browser/commentThreadZoneWidget'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { CommentWidgetFocus, isMouseUpEventDragFromMouseDown, parseMouseDownInfoFromEvent, ReviewZoneWidget } from 'vs/workbench/contrib/comments/browser/commentThreadZoneWidget'; +import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; @@ -45,6 +45,8 @@ import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/commo import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { URI } from 'vs/base/common/uri'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; export const ID = 'editor.contrib.review'; @@ -366,6 +368,57 @@ class CommentingRangeDecorator { } } +export function revealCommentThread(commentService: ICommentService, editorService: IEditorService, uriIdentityService: IUriIdentityService, + commentThread: languages.CommentThread, comment: languages.Comment, focusReply?: boolean, pinned?: boolean, preserveFocus?: boolean, sideBySide?: boolean): void { + if (!commentThread.resource) { + return; + } + if (!commentService.isCommentingEnabled) { + commentService.enableCommenting(true); + } + + const range = commentThread.range; + const focus = focusReply ? CommentWidgetFocus.Editor : (preserveFocus ? CommentWidgetFocus.None : CommentWidgetFocus.Widget); + + const activeEditor = editorService.activeTextEditorControl; + // If the active editor is a diff editor where one of the sides has the comment, + // then we try to reveal the comment in the diff editor. + const currentActiveResources: IEditor[] = isDiffEditor(activeEditor) ? [activeEditor.getOriginalEditor(), activeEditor.getModifiedEditor()] + : (activeEditor ? [activeEditor] : []); + const threadToReveal = commentThread.threadId; + const commentToReveal = comment.uniqueIdInThread; + const resource = URI.parse(commentThread.resource); + + for (const editor of currentActiveResources) { + const model = editor.getModel(); + if ((model instanceof TextModel) && uriIdentityService.extUri.isEqual(resource, model.uri)) { + + if (threadToReveal && isCodeEditor(editor)) { + const controller = CommentController.get(editor); + controller?.revealCommentThread(threadToReveal, commentToReveal, true, focus); + } + return; + } + } + + editorService.openEditor({ + resource, + options: { + pinned: pinned, + preserveFocus: preserveFocus, + selection: range ?? new Range(1, 1, 1, 1) + } + } as ITextResourceEditorInput, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => { + if (editor) { + const control = editor.getControl(); + if (threadToReveal && isCodeEditor(control)) { + const controller = CommentController.get(control); + controller?.revealCommentThread(threadToReveal, commentToReveal, true, focus); + } + } + }); +} + export class CommentController implements IEditorContribution { private readonly globalToDispose = new DisposableStore(); private readonly localToDispose = new DisposableStore(); @@ -630,7 +683,7 @@ export class CommentController implements IEditorContribution { return editor.getContribution(ID); } - public revealCommentThread(threadId: string, commentUniqueId: number, fetchOnceIfNotExist: boolean, focus: boolean): void { + public revealCommentThread(threadId: string, commentUniqueId: number, fetchOnceIfNotExist: boolean, focus: CommentWidgetFocus): void { const commentThreadWidget = this._commentWidgets.filter(widget => widget.commentThread.threadId === threadId); if (commentThreadWidget.length === 1) { commentThreadWidget[0].reveal(commentUniqueId, focus); @@ -734,7 +787,7 @@ export class CommentController implements IEditorContribution { nextWidget = sortedWidgets[idx]; } this.editor.setSelection(nextWidget.commentThread.range ?? new Range(1, 1, 1, 1)); - nextWidget.reveal(undefined, true); + nextWidget.reveal(undefined, CommentWidgetFocus.Widget); } public previousCommentThread(): void { diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index df131acad4b..147d7ed6fab 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -42,6 +42,7 @@ import { MarshalledId } from 'vs/base/common/marshallingIds'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { MarshalledCommentThread, MarshalledCommentThreadInternal } from 'vs/workbench/common/comments'; export const COMMENTS_VIEW_ID = 'workbench.panel.comments'; export const COMMENTS_VIEW_STORAGE_ID = 'Comments'; @@ -161,7 +162,8 @@ class CommentsMenus implements IDisposable { const overlay: [string, any][] = [ ['commentController', element.owner], ['resourceScheme', element.resource.scheme], - ['commentThread', element.contextValue] + ['commentThread', element.contextValue], + ['canReply', element.thread.canReply] ]; const contextKeyService = this.contextKeyService.createOverlay(overlay); @@ -304,7 +306,7 @@ export class CommentNodeRenderer implements IListRenderer commentControlHandle: node.element.controllerHandle, commentThreadHandle: node.element.threadHandle, $mid: MarshalledId.CommentThread - }; + } as MarshalledCommentThread; if (!node.element.hasReply()) { templateData.repliesMetadata.container.style.display = 'none'; @@ -511,10 +513,11 @@ export class CommentsList extends WorkbenchObjectTree ({ + getActionsContext: (): MarshalledCommentThreadInternal => ({ commentControlHandle: node.controllerHandle, commentThreadHandle: node.threadHandle, - $mid: MarshalledId.CommentThread + $mid: MarshalledId.CommentThread, + thread: node.thread }) }); } diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index 2530cf96e7c..b45f12a0cb7 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -7,12 +7,11 @@ import 'vs/css!./media/panel'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { basename } from 'vs/base/common/resources'; -import { isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { CommentNode, ResourceWithCommentThreads, ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; import { IWorkspaceCommentThreadsEvent, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; -import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ResourceLabels } from 'vs/workbench/browser/labels'; import { CommentsList, COMMENTS_VIEW_TITLE, Filter } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; import { IViewPaneOptions, FilterViewPane } from 'vs/workbench/browser/parts/views/viewPane'; @@ -24,8 +23,6 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { IEditor } from 'vs/editor/common/editorCommon'; -import { TextModel } from 'vs/editor/common/model/textModel'; import { CommentsViewFilterFocusContextKey, ICommentsView } from 'vs/workbench/contrib/comments/browser/comments'; import { CommentsFilters, CommentsFiltersChangeEvent } from 'vs/workbench/contrib/comments/browser/commentsViewActions'; import { Memento, MementoObject } from 'vs/workbench/common/memento'; @@ -34,8 +31,7 @@ import { FilterOptions } from 'vs/workbench/contrib/comments/browser/commentsFil import { CommentThreadState } from 'vs/editor/common/languages'; import { ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { Iterable } from 'vs/base/common/iterator'; -import { CommentController } from 'vs/workbench/contrib/comments/browser/commentsController'; -import { Range } from 'vs/editor/common/core/range'; +import { revealCommentThread } from 'vs/workbench/contrib/comments/browser/commentsController'; import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands'; import { CommentsModel, ICommentsModel } from 'vs/workbench/contrib/comments/browser/commentsModel'; @@ -323,62 +319,17 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { })); } - private openFile(element: any, pinned?: boolean, preserveFocus?: boolean, sideBySide?: boolean): boolean { + private openFile(element: any, pinned?: boolean, preserveFocus?: boolean, sideBySide?: boolean): void { if (!element) { - return false; + return; } if (!(element instanceof ResourceWithCommentThreads || element instanceof CommentNode)) { - return false; + return; } - - if (!this.commentService.isCommentingEnabled) { - this.commentService.enableCommenting(true); - } - - const range = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].range : element.range; - - const activeEditor = this.editorService.activeTextEditorControl; - // If the active editor is a diff editor where one of the sides has the comment, - // then we try to reveal the comment in the diff editor. - const currentActiveResources: IEditor[] = isDiffEditor(activeEditor) ? [activeEditor.getOriginalEditor(), activeEditor.getModifiedEditor()] - : (activeEditor ? [activeEditor] : []); - - for (const editor of currentActiveResources) { - const model = editor.getModel(); - if ((model instanceof TextModel) && this.uriIdentityService.extUri.isEqual(element.resource, model.uri)) { - const threadToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].threadId : element.threadId; - const commentToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].comment.uniqueIdInThread : element.comment.uniqueIdInThread; - if (threadToReveal && isCodeEditor(editor)) { - const controller = CommentController.get(editor); - controller?.revealCommentThread(threadToReveal, commentToReveal, true, !preserveFocus); - } - - return true; - } - } - - const threadToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].threadId : element.threadId; + const threadToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].thread : element.thread; const commentToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].comment : element.comment; - - this.editorService.openEditor({ - resource: element.resource, - options: { - pinned: pinned, - preserveFocus: preserveFocus, - selection: range ?? new Range(1, 1, 1, 1) - } - }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => { - if (editor) { - const control = editor.getControl(); - if (threadToReveal && isCodeEditor(control)) { - const controller = CommentController.get(control); - controller?.revealCommentThread(threadToReveal, commentToReveal.uniqueIdInThread, true, !preserveFocus); - } - } - }); - - return true; + return revealCommentThread(this.commentService, this.editorService, this.uriIdentityService, threadToReveal, commentToReveal, false, pinned, preserveFocus, sideBySide); } private async refresh(): Promise { diff --git a/src/vs/workbench/contrib/comments/common/commentModel.ts b/src/vs/workbench/contrib/comments/common/commentModel.ts index e4418a28dfc..7bf9efe417a 100644 --- a/src/vs/workbench/contrib/comments/common/commentModel.ts +++ b/src/vs/workbench/contrib/comments/common/commentModel.ts @@ -16,18 +16,26 @@ export interface ICommentThreadChangedEvent extends CommentThreadChangedEvent new CommentNode(uniqueOwner, threadId, resource, comment, range, commentThread.state, commentThread.contextValue, owner, commentThread.controllerHandle, commentThread.commentThreadHandle)); + const { comments } = commentThread; + const commentNodes: CommentNode[] = comments!.map(comment => new CommentNode(uniqueOwner, owner, resource, comment, commentThread)); if (commentNodes.length > 1) { commentNodes[0].replies = commentNodes.slice(1, commentNodes.length); } From c37ca48a96e37e30e13ed4d4679bca1c2e330ad4 Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 8 Mar 2024 21:48:21 +0900 Subject: [PATCH 1867/1897] chore: update node-pty@1.1.0-beta11 (#207153) --- package.json | 2 +- remote/package.json | 2 +- remote/yarn.lock | 8 ++++---- yarn.lock | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index fae4fcc57a8..caf83733802 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "native-is-elevated": "0.7.0", "native-keymap": "^3.3.4", "native-watchdog": "^1.4.1", - "node-pty": "1.1.0-beta6", + "node-pty": "1.1.0-beta11", "tas-client-umd": "0.1.8", "v8-inspect-profiler": "^0.1.0", "vscode-oniguruma": "1.7.0", diff --git a/remote/package.json b/remote/package.json index 2dcfab99b56..56e7f9e975e 100644 --- a/remote/package.json +++ b/remote/package.json @@ -29,7 +29,7 @@ "kerberos": "^2.0.1", "minimist": "^1.2.6", "native-watchdog": "^1.4.1", - "node-pty": "1.1.0-beta6", + "node-pty": "1.1.0-beta11", "tas-client-umd": "0.1.8", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", diff --git a/remote/yarn.lock b/remote/yarn.lock index b068bf655bb..63417331620 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -431,10 +431,10 @@ node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== -node-pty@1.1.0-beta6: - version "1.1.0-beta6" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta6.tgz#8b27ce40268e313868925e1b46f2af98cc677881" - integrity sha512-ZcuPz5wIbfF4rebVv8sl+nf2Cn5dVMqlEl9PtabCt4uIffGDnovOpmwh16Oh/MThrwSmeJL6gBwu6lIbBtW7DQ== +node-pty@1.1.0-beta11: + version "1.1.0-beta11" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta11.tgz#909d5dd8f9aa2a7857e7b632fd4d2d4768bdf69f" + integrity sha512-vTjF+VrvSCfPDILUkIT+YrG1Fdn06/eBRS2fc9a3JzYAvknMB1Ip8aoJhxl8hNpjWAbprmCEiV91mlfNpCD+GQ== dependencies: node-addon-api "^7.1.0" diff --git a/yarn.lock b/yarn.lock index a7e82c60be5..a97562951bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6939,10 +6939,10 @@ node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== -node-pty@1.1.0-beta6: - version "1.1.0-beta6" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta6.tgz#8b27ce40268e313868925e1b46f2af98cc677881" - integrity sha512-ZcuPz5wIbfF4rebVv8sl+nf2Cn5dVMqlEl9PtabCt4uIffGDnovOpmwh16Oh/MThrwSmeJL6gBwu6lIbBtW7DQ== +node-pty@1.1.0-beta11: + version "1.1.0-beta11" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta11.tgz#909d5dd8f9aa2a7857e7b632fd4d2d4768bdf69f" + integrity sha512-vTjF+VrvSCfPDILUkIT+YrG1Fdn06/eBRS2fc9a3JzYAvknMB1Ip8aoJhxl8hNpjWAbprmCEiV91mlfNpCD+GQ== dependencies: node-addon-api "^7.1.0" From 27714e45c10df52ac2d9c6bf9b79e10fef00fb51 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:03:06 +0100 Subject: [PATCH 1868/1897] Include pointer size in hover bounding box computation (#207092) Include pointer size in window/hover bounding box computation --- .../services/hoverService/hoverWidget.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index 4ec59ed09bd..24ae9cf483e 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -469,9 +469,11 @@ export class HoverWidget extends Widget implements IHoverWidget { return; } + const hoverPointerOffset = (this._hoverPointer ? Constants.PointerSize : 0); + // When force position is enabled, restrict max width if (this._forcePosition) { - const padding = (this._hoverPointer ? Constants.PointerSize : 0) + Constants.HoverBorderWidth; + const padding = hoverPointerOffset + Constants.HoverBorderWidth; if (this._hoverPosition === HoverPosition.RIGHT) { this._hover.containerDomNode.style.maxWidth = `${this._targetDocumentElement.clientWidth - target.right - padding}px`; } else if (this._hoverPosition === HoverPosition.LEFT) { @@ -484,10 +486,10 @@ export class HoverWidget extends Widget implements IHoverWidget { if (this._hoverPosition === HoverPosition.RIGHT) { const roomOnRight = this._targetDocumentElement.clientWidth - target.right; // Hover on the right is going beyond window. - if (roomOnRight < this._hover.containerDomNode.clientWidth) { + if (roomOnRight < this._hover.containerDomNode.clientWidth + hoverPointerOffset) { const roomOnLeft = target.left; // There's enough room on the left, flip the hover position - if (roomOnLeft >= this._hover.containerDomNode.clientWidth) { + if (roomOnLeft >= this._hover.containerDomNode.clientWidth + hoverPointerOffset) { this._hoverPosition = HoverPosition.LEFT; } // Hover on the left would go beyond window too @@ -501,10 +503,10 @@ export class HoverWidget extends Widget implements IHoverWidget { const roomOnLeft = target.left; // Hover on the left is going beyond window. - if (roomOnLeft < this._hover.containerDomNode.clientWidth) { + if (roomOnLeft < this._hover.containerDomNode.clientWidth + hoverPointerOffset) { const roomOnRight = this._targetDocumentElement.clientWidth - target.right; // There's enough room on the right, flip the hover position - if (roomOnRight >= this._hover.containerDomNode.clientWidth) { + if (roomOnRight >= this._hover.containerDomNode.clientWidth + hoverPointerOffset) { this._hoverPosition = HoverPosition.RIGHT; } // Hover on the right would go beyond window too @@ -513,7 +515,7 @@ export class HoverWidget extends Widget implements IHoverWidget { } } // Hover on the left is going beyond window. - if (target.left - this._hover.containerDomNode.clientWidth <= this._targetDocumentElement.clientLeft) { + if (target.left - this._hover.containerDomNode.clientWidth - hoverPointerOffset <= this._targetDocumentElement.clientLeft) { this._hoverPosition = HoverPosition.RIGHT; } } @@ -526,10 +528,12 @@ export class HoverWidget extends Widget implements IHoverWidget { return; } + const hoverPointerOffset = (this._hoverPointer ? Constants.PointerSize : 0); + // Position hover on top of the target if (this._hoverPosition === HoverPosition.ABOVE) { // Hover on top is going beyond window - if (target.top - this._hover.containerDomNode.clientHeight < 0) { + if (target.top - this._hover.containerDomNode.clientHeight - hoverPointerOffset < 0) { this._hoverPosition = HoverPosition.BELOW; } } @@ -537,7 +541,7 @@ export class HoverWidget extends Widget implements IHoverWidget { // Position hover below the target else if (this._hoverPosition === HoverPosition.BELOW) { // Hover on bottom is going beyond window - if (target.bottom + this._hover.containerDomNode.clientHeight > this._targetWindow.innerHeight) { + if (target.bottom + this._hover.containerDomNode.clientHeight + hoverPointerOffset > this._targetWindow.innerHeight) { this._hoverPosition = HoverPosition.ABOVE; } } From 5c6765c8f2abae365dcf256b46f6b1ae8ea8b9d2 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 8 Mar 2024 15:09:33 +0100 Subject: [PATCH 1869/1897] Fix active pane indicator issues (#207163) fix active pane indicator issues --- .../parts/auxiliarybar/media/auxiliaryBarPart.css | 9 ++++++++- .../workbench/browser/parts/media/paneCompositePart.css | 2 +- .../browser/parts/sidebar/media/sidebarpart.css | 4 ++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css b/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css index ad3e53ded5a..675a6507271 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css +++ b/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css @@ -34,21 +34,28 @@ flex: 1; } +.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, +.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked.clicked:focus .active-item-indicator:before, .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked.clicked:focus .active-item-indicator:before { - border-top-color: var(--vscode-panelTitle-activeBorder) !important; + border-top-color: var(--vscode-activityBarTop-activeBorder) !important; } +.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label, +.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .action-label, .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label, .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .action-label { color: var(--vscode-sideBarTitle-foreground) !important; } +.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, +.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label, .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label { outline: var(--vscode-contrastActiveBorder, unset) solid 1px !important; } +.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label, .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label { outline: var(--vscode-contrastActiveBorder, unset) dashed 1px !important; } diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index 66bc8d9067e..3b9e71b9182 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -271,7 +271,7 @@ content: ""; position: absolute; z-index: 1; - bottom: 0; + bottom: 2px; width: 100%; height: 0; border-top-width: 1px; diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index 65f962e8241..4721db76de4 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -60,11 +60,15 @@ height: 16px; } +.monaco-workbench .part.sidebar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, +.monaco-workbench .part.sidebar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked.clicked:focus .active-item-indicator:before, .monaco-workbench .part.sidebar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, .monaco-workbench .part.sidebar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked.clicked:focus .active-item-indicator:before { border-top-color: var(--vscode-activityBarTop-activeBorder) !important; } +.monaco-workbench .part.sidebar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label, +.monaco-workbench .part.sidebar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .action-label, .monaco-workbench .part.sidebar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label, .monaco-workbench .part.sidebar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .action-label { color: var(--vscode-activityBarTop-foreground) !important; From 9730949668f18f92ef2681ca87b4c017a95da8bf Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 8 Mar 2024 15:30:33 +0100 Subject: [PATCH 1870/1897] Fix badge position (#207165) fix badge position --- src/vs/workbench/browser/parts/media/paneCompositePart.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index 3b9e71b9182..e3502c3af86 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -219,7 +219,7 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact .badge-content, .monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact .badge-content { position: absolute; - top: 11px; + top: 17px; right: 0px; font-size: 9px; font-weight: 600; From a6e2b25575da90a1bcfabc0693277b0ad3407023 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 6 Mar 2024 17:57:50 +0100 Subject: [PATCH 1871/1897] Moves Permutation class to arrays.ts --- src/vs/base/common/arrays.ts | 33 +++++++++++++++++++ .../browser/inlineCompletionsModel.ts | 3 +- .../inlineCompletions/browser/utils.ts | 23 +------------ 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 9b510f82251..f8804af0fe7 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -859,3 +859,36 @@ export class CallbackIterable { return result; } } + +/** + * Represents a re-arrangement of items in an array. + */ +export class Permutation { + constructor(private readonly _indexMap: readonly number[]) { } + + /** + * Returns a permutation that sorts the given array according to the given compare function. + */ + public static createSortPermutation(arr: readonly T[], compareFn: (a: T, b: T) => number): Permutation { + const sortIndices = Array.from(arr.keys()).sort((index1, index2) => compareFn(arr[index1], arr[index2])); + return new Permutation(sortIndices); + } + + /** + * Returns a new array with the elements of the given array re-arranged according to this permutation. + */ + apply(arr: readonly T[]): T[] { + return arr.map((_, index) => arr[this._indexMap[index]]); + } + + /** + * Returns a new permutation that undoes the re-arrangement of this permutation. + */ + inverse(): Permutation { + const inverseIndexMap = this._indexMap.slice(); + for (let i = 0; i < this._indexMap.length; i++) { + inverseIndexMap[this._indexMap[i]] = i; + } + return new Permutation(inverseIndexMap); + } +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 5b26d770005..8bc30856f20 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Permutation } from 'vs/base/common/arrays'; import { mapFindFirst } from 'vs/base/common/arraysFind'; import { BugIndicatingError, onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -22,7 +23,7 @@ import { GhostText, GhostTextOrReplacement, ghostTextOrReplacementEquals, ghostT import { InlineCompletionWithUpdatedRange, InlineCompletionsSource } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource'; import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; import { SuggestItemInfo } from 'vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider'; -import { Permutation, addPositions, getNewRanges, lengthOfText, subtractPositions } from 'vs/editor/contrib/inlineCompletions/browser/utils'; +import { addPositions, getNewRanges, lengthOfText, subtractPositions } from 'vs/editor/contrib/inlineCompletions/browser/utils'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts index 4a9e78238b6..0dc96bb3945 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { compareBy } from 'vs/base/common/arrays'; +import { Permutation, compareBy } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IObservable, autorunOpts } from 'vs/base/common/observable'; @@ -161,24 +161,3 @@ export function inverseEdits(model: TextModel, edits: ISingleEditOperation[]): I } return inverseEdits; } - -export class Permutation { - constructor(private readonly _indexMap: number[]) { } - - public static createSortPermutation(arr: readonly T[], compareFn: (a: T, b: T) => number): Permutation { - const sortIndices = Array.from(arr.keys()).sort((index1, index2) => compareFn(arr[index1], arr[index2])); - return new Permutation(sortIndices); - } - - apply(arr: readonly T[]): T[] { - return arr.map((_, index) => arr[this._indexMap[index]]); - } - - inverse(): Permutation { - const inverseIndexMap = this._indexMap.slice(); - for (let i = 0; i < this._indexMap.length; i++) { - inverseIndexMap[this._indexMap[i]] = i; - } - return new Permutation(inverseIndexMap); - } -} From 655a48b67ed279ad765c91381ba109340515f8c3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 7 Mar 2024 14:56:56 +0100 Subject: [PATCH 1872/1897] Introduces RangeLength --- .../editor/browser/widget/diffEditor/utils.ts | 10 +-- src/vs/editor/common/core/rangeLength.ts | 76 +++++++++++++++++ .../beforeEditPositionMapper.ts | 11 +-- .../bracketPairsTree/length.ts | 84 ++----------------- .../mergeEditor/browser/model/rangeUtils.ts | 16 ++-- .../mergeEditor/browser/view/lineAlignment.ts | 6 +- .../mergeEditor/test/browser/mapping.test.ts | 12 +-- 7 files changed, 112 insertions(+), 103 deletions(-) create mode 100644 src/vs/editor/common/core/rangeLength.ts diff --git a/src/vs/editor/browser/widget/diffEditor/utils.ts b/src/vs/editor/browser/widget/diffEditor/utils.ts index b71da8bb55d..547392b4ba4 100644 --- a/src/vs/editor/browser/widget/diffEditor/utils.ts +++ b/src/vs/editor/browser/widget/diffEditor/utils.ts @@ -15,7 +15,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; -import { LengthObj } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; +import { RangeLength } from 'vs/editor/common/core/rangeLength'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyValue, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -425,15 +425,15 @@ export function translatePosition(posInOriginal: Position, mappings: DetailedLin } } -function lengthBetweenPositions(position1: Position, position2: Position): LengthObj { +function lengthBetweenPositions(position1: Position, position2: Position): RangeLength { if (position1.lineNumber === position2.lineNumber) { - return new LengthObj(0, position2.column - position1.column); + return new RangeLength(0, position2.column - position1.column); } else { - return new LengthObj(position2.lineNumber - position1.lineNumber, position2.column - 1); + return new RangeLength(position2.lineNumber - position1.lineNumber, position2.column - 1); } } -function addLength(position: Position, length: LengthObj): Position { +function addLength(position: Position, length: RangeLength): Position { if (length.lineCount === 0) { return new Position(position.lineNumber, position.column + length.columnCount); } else { diff --git a/src/vs/editor/common/core/rangeLength.ts b/src/vs/editor/common/core/rangeLength.ts new file mode 100644 index 00000000000..0e505318b05 --- /dev/null +++ b/src/vs/editor/common/core/rangeLength.ts @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Represents a non-negative length of text in terms of line and column count. +*/ +export class RangeLength { + public static zero = new RangeLength(0, 0); + + public static lengthDiffNonNegative(start: RangeLength, end: RangeLength): RangeLength { + if (end.isLessThan(start)) { + return RangeLength.zero; + } + if (start.lineCount === end.lineCount) { + return new RangeLength(0, end.columnCount - start.columnCount); + } else { + return new RangeLength(end.lineCount - start.lineCount, end.columnCount); + } + } + + constructor( + public readonly lineCount: number, + public readonly columnCount: number + ) { } + + public isZero() { + return this.lineCount === 0 && this.columnCount === 0; + } + + public isLessThan(other: RangeLength): boolean { + if (this.lineCount !== other.lineCount) { + return this.lineCount < other.lineCount; + } + return this.columnCount < other.columnCount; + } + + public isGreaterThan(other: RangeLength): boolean { + if (this.lineCount !== other.lineCount) { + return this.lineCount > other.lineCount; + } + return this.columnCount > other.columnCount; + } + + public equals(other: RangeLength): boolean { + return this.lineCount === other.lineCount && this.columnCount === other.columnCount; + } + + public compare(other: RangeLength): number { + if (this.lineCount !== other.lineCount) { + return this.lineCount - other.lineCount; + } + return this.columnCount - other.columnCount; + } + + public add(other: RangeLength): RangeLength { + if (other.lineCount === 0) { + return new RangeLength(this.lineCount, this.columnCount + other.columnCount); + } else { + return new RangeLength(this.lineCount + other.lineCount, other.columnCount); + } + } + + public createRange(startPosition: Position): Range { + if (this.lineCount === 0) { + return new Range(startPosition.lineNumber, startPosition.column, startPosition.lineNumber, startPosition.column + this.columnCount); + } else { + return new Range(startPosition.lineNumber, startPosition.column, startPosition.lineNumber + this.lineCount, this.columnCount + 1); + } + } + + toString() { + return `${this.lineCount},${this.columnCount}`; + } +} diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper.ts index 501aa07c39b..e596c00e983 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Range } from 'vs/editor/common/core/range'; -import { Length, lengthAdd, lengthDiffNonNegative, lengthLessThanEqual, LengthObj, lengthOfString, lengthToObj, positionToLength, toLength } from './length'; +import { Length, lengthAdd, lengthDiffNonNegative, lengthLessThanEqual, lengthOfString, lengthToObj, positionToLength, toLength } from './length'; +import { RangeLength } from 'vs/editor/common/core/rangeLength'; import { IModelContentChange } from 'vs/editor/common/textModelEvents'; export class TextEditInfo { @@ -73,7 +74,7 @@ export class BeforeEditPositionMapper { return lengthDiffNonNegative(offset, nextChangeOffset); } - private translateOldToCur(oldOffsetObj: LengthObj): Length { + private translateOldToCur(oldOffsetObj: RangeLength): Length { if (oldOffsetObj.lineCount === this.deltaLineIdxInOld) { return toLength(oldOffsetObj.lineCount + this.deltaOldToNewLineCount, oldOffsetObj.columnCount + this.deltaOldToNewColumnCount); } else { @@ -126,9 +127,9 @@ class TextEditInfoCache { return new TextEditInfoCache(edit.startOffset, edit.endOffset, edit.newLength); } - public readonly endOffsetBeforeObj: LengthObj; - public readonly endOffsetAfterObj: LengthObj; - public readonly offsetObj: LengthObj; + public readonly endOffsetBeforeObj: RangeLength; + public readonly endOffsetAfterObj: RangeLength; + public readonly offsetObj: RangeLength; constructor( startOffset: Length, diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts index 40cb0255688..0ff10813857 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts @@ -6,75 +6,7 @@ import { splitLines } from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; - -/** - * Represents a non-negative length in terms of line and column count. - * Prefer using {@link Length} for performance reasons. -*/ -export class LengthObj { - public static zero = new LengthObj(0, 0); - - public static lengthDiffNonNegative(start: LengthObj, end: LengthObj): LengthObj { - if (end.isLessThan(start)) { - return LengthObj.zero; - } - if (start.lineCount === end.lineCount) { - return new LengthObj(0, end.columnCount - start.columnCount); - } else { - return new LengthObj(end.lineCount - start.lineCount, end.columnCount); - } - } - - constructor( - public readonly lineCount: number, - public readonly columnCount: number - ) { } - - public isZero() { - return this.lineCount === 0 && this.columnCount === 0; - } - - public toLength(): Length { - return toLength(this.lineCount, this.columnCount); - } - - public isLessThan(other: LengthObj): boolean { - if (this.lineCount !== other.lineCount) { - return this.lineCount < other.lineCount; - } - return this.columnCount < other.columnCount; - } - - public isGreaterThan(other: LengthObj): boolean { - if (this.lineCount !== other.lineCount) { - return this.lineCount > other.lineCount; - } - return this.columnCount > other.columnCount; - } - - public equals(other: LengthObj): boolean { - return this.lineCount === other.lineCount && this.columnCount === other.columnCount; - } - - public compare(other: LengthObj): number { - if (this.lineCount !== other.lineCount) { - return this.lineCount - other.lineCount; - } - return this.columnCount - other.columnCount; - } - - public add(other: LengthObj): LengthObj { - if (other.lineCount === 0) { - return new LengthObj(this.lineCount, this.columnCount + other.columnCount); - } else { - return new LengthObj(this.lineCount + other.lineCount, other.columnCount); - } - } - - toString() { - return `${this.lineCount},${this.columnCount}`; - } -} +import { RangeLength } from 'vs/editor/common/core/rangeLength'; /** * The end must be greater than or equal to the start. @@ -117,11 +49,11 @@ export function toLength(lineCount: number, columnCount: number): Length { return (lineCount * factor + columnCount) as any as Length; } -export function lengthToObj(length: Length): LengthObj { +export function lengthToObj(length: Length): RangeLength { const l = length as any as number; const lineCount = Math.floor(l / factor); const columnCount = l - lineCount * factor; - return new LengthObj(lineCount, columnCount); + return new RangeLength(lineCount, columnCount); } export function lengthGetLineCount(length: Length): number { @@ -216,11 +148,11 @@ export function lengthsToRange(lengthStart: Length, lengthEnd: Length): Range { return new Range(lineCount + 1, colCount + 1, lineCount2 + 1, colCount2 + 1); } -export function lengthOfRange(range: Range): LengthObj { +export function lengthOfRange(range: Range): RangeLength { if (range.startLineNumber === range.endLineNumber) { - return new LengthObj(0, range.endColumn - range.startColumn); + return new RangeLength(0, range.endColumn - range.startColumn); } else { - return new LengthObj(range.endLineNumber - range.startLineNumber, range.endColumn - 1); + return new RangeLength(range.endLineNumber - range.startLineNumber, range.endColumn - 1); } } @@ -235,9 +167,9 @@ export function lengthOfString(str: string): Length { return toLength(lines.length - 1, lines[lines.length - 1].length); } -export function lengthOfStringObj(str: string): LengthObj { +export function lengthOfStringObj(str: string): RangeLength { const lines = splitLines(str); - return new LengthObj(lines.length - 1, lines[lines.length - 1].length); + return new RangeLength(lines.length - 1, lines[lines.length - 1].length); } /** diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/rangeUtils.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/rangeUtils.ts index 92f242a2597..fef5aaad5d7 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/rangeUtils.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/rangeUtils.ts @@ -5,7 +5,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { LengthObj } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; +import { RangeLength } from 'vs/editor/common/core/rangeLength'; export function rangeContainsPosition(range: Range, position: Position): boolean { if (position.lineNumber < range.startLineNumber || position.lineNumber > range.endLineNumber) { @@ -20,23 +20,23 @@ export function rangeContainsPosition(range: Range, position: Position): boolean return true; } -export function lengthOfRange(range: Range): LengthObj { +export function lengthOfRange(range: Range): RangeLength { if (range.startLineNumber === range.endLineNumber) { - return new LengthObj(0, range.endColumn - range.startColumn); + return new RangeLength(0, range.endColumn - range.startColumn); } else { - return new LengthObj(range.endLineNumber - range.startLineNumber, range.endColumn - 1); + return new RangeLength(range.endLineNumber - range.startLineNumber, range.endColumn - 1); } } -export function lengthBetweenPositions(position1: Position, position2: Position): LengthObj { +export function lengthBetweenPositions(position1: Position, position2: Position): RangeLength { if (position1.lineNumber === position2.lineNumber) { - return new LengthObj(0, position2.column - position1.column); + return new RangeLength(0, position2.column - position1.column); } else { - return new LengthObj(position2.lineNumber - position1.lineNumber, position2.column - 1); + return new RangeLength(position2.lineNumber - position1.lineNumber, position2.column - 1); } } -export function addLength(position: Position, length: LengthObj): Position { +export function addLength(position: Position, length: RangeLength): Position { if (length.lineCount === 0) { return new Position(position.lineNumber, position.column + length.columnCount); } else { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/lineAlignment.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/lineAlignment.ts index f6ccc0cf7a3..6d4f28beb1e 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/lineAlignment.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/lineAlignment.ts @@ -8,7 +8,7 @@ import { assertFn, checkAdjacentItems } from 'vs/base/common/assert'; import { isDefined } from 'vs/base/common/types'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { LengthObj } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; +import { RangeLength } from 'vs/editor/common/core/rangeLength'; import { RangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; import { ModifiedBaseRange } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange'; import { addLength, lengthBetweenPositions, lengthOfRange } from 'vs/workbench/contrib/mergeEditor/browser/model/rangeUtils'; @@ -49,7 +49,7 @@ export function getAlignments(m: ModifiedBaseRange): LineAlignment[] { if (shouldAdd) { result.push(lineAlignment); } else { - if (m.length.isGreaterThan(new LengthObj(1, 0))) { + if (m.length.isGreaterThan(new RangeLength(1, 0))) { result.push([ m.output1Pos ? m.output1Pos.lineNumber + 1 : undefined, m.inputPos.lineNumber + 1, @@ -75,7 +75,7 @@ interface CommonRangeMapping { output1Pos: Position | undefined; output2Pos: Position | undefined; inputPos: Position; - length: LengthObj; + length: RangeLength; } function toEqualRangeMappings(diffs: RangeMapping[], inputRange: Range, outputRange: Range): RangeMapping[] { diff --git a/src/vs/workbench/contrib/mergeEditor/test/browser/mapping.test.ts b/src/vs/workbench/contrib/mergeEditor/test/browser/mapping.test.ts index ae714c10766..9d824d4c7fa 100644 --- a/src/vs/workbench/contrib/mergeEditor/test/browser/mapping.test.ts +++ b/src/vs/workbench/contrib/mergeEditor/test/browser/mapping.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { LengthObj } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; +import { RangeLength } from 'vs/editor/common/core/rangeLength'; import { DocumentRangeMap, RangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; suite('merge editor mapping', () => { @@ -53,19 +53,19 @@ function parsePos(str: string): Position { return new Position(parseInt(lineCount, 10), parseInt(columnCount, 10)); } -function parseLengthObj(str: string): LengthObj { +function parseLengthObj(str: string): RangeLength { const [lineCount, columnCount] = str.split(':'); - return new LengthObj(parseInt(lineCount, 10), parseInt(columnCount, 10)); + return new RangeLength(parseInt(lineCount, 10), parseInt(columnCount, 10)); } -function toPosition(length: LengthObj): Position { +function toPosition(length: RangeLength): Position { return new Position(length.lineCount + 1, length.columnCount + 1); } function createDocumentRangeMap(items: ([string, string] | string)[]) { const mappings: RangeMapping[] = []; - let lastLen1 = new LengthObj(0, 0); - let lastLen2 = new LengthObj(0, 0); + let lastLen1 = new RangeLength(0, 0); + let lastLen2 = new RangeLength(0, 0); for (const item of items) { if (typeof item === 'string') { const len = parseLengthObj(item); From a278ef641dae4291f6d403eaf55acdc973fcf168 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 7 Mar 2024 16:44:35 +0100 Subject: [PATCH 1873/1897] Moves utilities into RangeLength --- .../editor/browser/widget/diffEditor/utils.ts | 10 +----- src/vs/editor/common/core/rangeLength.ts | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/utils.ts b/src/vs/editor/browser/widget/diffEditor/utils.ts index 547392b4ba4..ebe47afd423 100644 --- a/src/vs/editor/browser/widget/diffEditor/utils.ts +++ b/src/vs/editor/browser/widget/diffEditor/utils.ts @@ -421,7 +421,7 @@ export function translatePosition(posInOriginal: Position, mappings: DetailedLin return innerMapping.modifiedRange; } else { const l = lengthBetweenPositions(innerMapping.originalRange.getEndPosition(), posInOriginal); - return Range.fromPositions(addLength(innerMapping.modifiedRange.getEndPosition(), l)); + return Range.fromPositions(l.addToPosition(innerMapping.modifiedRange.getEndPosition())); } } @@ -433,14 +433,6 @@ function lengthBetweenPositions(position1: Position, position2: Position): Range } } -function addLength(position: Position, length: RangeLength): Position { - if (length.lineCount === 0) { - return new Position(position.lineNumber, position.column + length.columnCount); - } else { - return new Position(position.lineNumber + length.lineCount, length.columnCount + 1); - } -} - export function bindContextKey(key: RawContextKey, service: IContextKeyService, computeValue: (reader: IReader) => T): IDisposable { const boundKey = key.bindTo(service); return autorunOpts({ debugName: () => `Set Context Key "${key.key}"` }, reader => { diff --git a/src/vs/editor/common/core/rangeLength.ts b/src/vs/editor/common/core/rangeLength.ts index 0e505318b05..90c47e092c9 100644 --- a/src/vs/editor/common/core/rangeLength.ts +++ b/src/vs/editor/common/core/rangeLength.ts @@ -2,6 +2,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; /** * Represents a non-negative length of text in terms of line and column count. @@ -20,6 +22,32 @@ export class RangeLength { } } + public static betweenPositions(position1: Position, position2: Position): RangeLength { + if (position1.lineNumber === position2.lineNumber) { + return new RangeLength(0, position2.column - position1.column); + } else { + return new RangeLength(position2.lineNumber - position1.lineNumber, position2.column - 1); + } + } + + public static ofRange(range: Range) { + return RangeLength.betweenPositions(range.getStartPosition(), range.getEndPosition()); + } + + public static ofText(text: string): RangeLength { + let line = 0; + let column = 0; + for (const c of text) { + if (c === '\n') { + line++; + column = 0; + } else { + column++; + } + } + return new RangeLength(line, column); + } + constructor( public readonly lineCount: number, public readonly columnCount: number @@ -70,6 +98,14 @@ export class RangeLength { } } + public addToPosition(position: Position): Position { + if (this.lineCount === 0) { + return new Position(position.lineNumber, position.column + this.columnCount); + } else { + return new Position(position.lineNumber + this.lineCount, this.columnCount + 1); + } + } + toString() { return `${this.lineCount},${this.columnCount}`; } From a9cd99e83e039da90e915e9e27485a089a9b4449 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 7 Mar 2024 16:50:02 +0100 Subject: [PATCH 1874/1897] Introduces core TextEdit --- src/vs/editor/common/core/positionToOffset.ts | 48 ++++ src/vs/editor/common/core/rangeMapping.ts | 73 ++++++ src/vs/editor/common/core/textEdit.ts | 194 +++++++++++++++ src/vs/editor/common/model/textModel.ts | 10 +- src/vs/editor/common/model/textModelText.ts | 23 ++ .../inlineCompletions/browser/ghostText.ts | 43 ++-- .../browser/inlineCompletionsModel.ts | 23 +- .../browser/inlineCompletionsSource.ts | 5 +- .../browser/provideInlineCompletions.ts | 2 +- .../browser/singleTextEdit.ts | 223 +++++++++--------- .../suggestWidgetInlineCompletionProvider.ts | 10 +- .../inlineCompletions/browser/utils.ts | 105 +-------- .../browser/inlineCompletionsModel.test.ts | 2 +- .../browser/inlineCompletionsProvider.test.ts | 5 +- .../test/browser/utils.test.ts | 33 --- .../inlineCompletions/test/browser/utils.ts | 20 -- .../core/positionOffsetTransformer.test.ts | 59 +++++ .../editor/test/common/core/textEdit.test.ts | 62 +++++ .../combineTextEditInfos.test.ts | 5 +- 19 files changed, 622 insertions(+), 323 deletions(-) create mode 100644 src/vs/editor/common/core/positionToOffset.ts create mode 100644 src/vs/editor/common/core/rangeMapping.ts create mode 100644 src/vs/editor/common/core/textEdit.ts create mode 100644 src/vs/editor/common/model/textModelText.ts delete mode 100644 src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts create mode 100644 src/vs/editor/test/common/core/positionOffsetTransformer.test.ts create mode 100644 src/vs/editor/test/common/core/textEdit.test.ts diff --git a/src/vs/editor/common/core/positionToOffset.ts b/src/vs/editor/common/core/positionToOffset.ts new file mode 100644 index 00000000000..c4abee8a332 --- /dev/null +++ b/src/vs/editor/common/core/positionToOffset.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { findLastIdxMonotonous } from 'vs/base/common/arraysFind'; +import { OffsetRange } from 'vs/editor/common/core/offsetRange'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; + +export class PositionOffsetTransformer { + private readonly lineStartOffsetByLineIdx: number[]; + + constructor(text: string) { + this.lineStartOffsetByLineIdx = []; + this.lineStartOffsetByLineIdx.push(0); + for (let i = 0; i < text.length; i++) { + if (text.charAt(i) === '\n') { + this.lineStartOffsetByLineIdx.push(i + 1); + } + } + } + + getOffset(position: Position): number { + return this.lineStartOffsetByLineIdx[position.lineNumber - 1] + position.column - 1; + } + + getOffsetRange(range: Range): OffsetRange { + return new OffsetRange( + this.getOffset(range.getStartPosition()), + this.getOffset(range.getEndPosition()) + ); + } + + getPosition(offset: number): Position { + const idx = findLastIdxMonotonous(this.lineStartOffsetByLineIdx, i => i <= offset); + const lineNumber = idx + 1; + const column = offset - this.lineStartOffsetByLineIdx[idx] + 1; + return new Position(lineNumber, column); + } + + getRange(offsetRange: OffsetRange): Range { + return Range.fromPositions( + this.getPosition(offsetRange.start), + this.getPosition(offsetRange.endExclusive) + ); + } +} diff --git a/src/vs/editor/common/core/rangeMapping.ts b/src/vs/editor/common/core/rangeMapping.ts new file mode 100644 index 00000000000..6e87efee7f7 --- /dev/null +++ b/src/vs/editor/common/core/rangeMapping.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { findLastMonotonous } from 'vs/base/common/arraysFind'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { RangeLength } from 'vs/editor/common/core/rangeLength'; + +/** + * Represents a list of mappings of ranges from one document to another. + */ +export class RangeMapping { + constructor(public readonly mappings: readonly SingleRangeMapping[]) { + } + + mapPosition(position: Position): PositionOrRange { + const mapping = findLastMonotonous(this.mappings, m => m.original.getStartPosition().isBeforeOrEqual(position)); + if (!mapping) { + return PositionOrRange.position(position); + } + if (mapping.original.containsPosition(position)) { + return PositionOrRange.range(mapping.modified); + } + const l = RangeLength.betweenPositions(mapping.original.getEndPosition(), position); + return PositionOrRange.position(l.addToPosition(mapping.modified.getEndPosition())); + } + + mapRange(range: Range): Range { + const start = this.mapPosition(range.getStartPosition()); + const end = this.mapPosition(range.getEndPosition()); + return Range.fromPositions( + start.range?.getStartPosition() ?? start.position!, + end.range?.getEndPosition() ?? end.position!, + ); + } + + reverse(): RangeMapping { + return new RangeMapping(this.mappings.map(mapping => mapping.reverse())); + } +} + +export class SingleRangeMapping { + constructor( + public readonly original: Range, + public readonly modified: Range, + ) { + } + + reverse(): SingleRangeMapping { + return new SingleRangeMapping(this.modified, this.original); + } + + toString() { + return `${this.original.toString()} -> ${this.modified.toString()}`; + } +} + +export class PositionOrRange { + public static position(position: Position): PositionOrRange { + return new PositionOrRange(position, undefined); + } + + public static range(range: Range): PositionOrRange { + return new PositionOrRange(undefined, range); + } + + private constructor( + public readonly position: Position | undefined, + public readonly range: Range | undefined, + ) { } +} diff --git a/src/vs/editor/common/core/textEdit.ts b/src/vs/editor/common/core/textEdit.ts new file mode 100644 index 00000000000..42e94c281d7 --- /dev/null +++ b/src/vs/editor/common/core/textEdit.ts @@ -0,0 +1,194 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { assertFn, checkAdjacentItems } from 'vs/base/common/assert'; +import { BugIndicatingError } from 'vs/base/common/errors'; +import { Position } from 'vs/editor/common/core/position'; +import { PositionOffsetTransformer } from 'vs/editor/common/core/positionToOffset'; +import { Range } from 'vs/editor/common/core/range'; +import { RangeLength } from 'vs/editor/common/core/rangeLength'; + +export class TextEdit { + constructor(public readonly edits: readonly SingleTextEdit[]) { + assertFn(() => checkAdjacentItems(edits, (a, b) => a.range.getEndPosition().isBeforeOrEqual(b.range.getStartPosition()))); + } + + mapPosition(position: Position): Position | Range { + let lineDelta = 0; + let curLine = 0; + let columnDeltaInCurLine = 0; + + for (const edit of this.edits) { + const start = edit.range.getStartPosition(); + const end = edit.range.getEndPosition(); + + if (position.isBeforeOrEqual(start)) { + break; + } + + const len = RangeLength.ofText(edit.text); + if (position.isBefore(end)) { + const startPos = new Position(start.lineNumber + lineDelta, start.column + (start.lineNumber + lineDelta === curLine ? columnDeltaInCurLine : 0)); + const endPos = len.addToPosition(startPos); + return rangeFromPositions(startPos, endPos); + } + + lineDelta += len.lineCount - (edit.range.endLineNumber - edit.range.startLineNumber); + + if (len.lineCount === 0) { + if (end.lineNumber !== start.lineNumber) { + columnDeltaInCurLine += len.columnCount - (end.column - 1); + } else { + columnDeltaInCurLine += len.columnCount - (end.column - start.column); + } + } else { + columnDeltaInCurLine = len.columnCount; + } + curLine = end.lineNumber + lineDelta; + } + + return new Position(position.lineNumber + lineDelta, position.column + (position.lineNumber + lineDelta === curLine ? columnDeltaInCurLine : 0)); + } + + mapRange(range: Range): Range { + function getStart(p: Position | Range) { + return p instanceof Position ? p : p.getStartPosition(); + } + + function getEnd(p: Position | Range) { + return p instanceof Position ? p : p.getEndPosition(); + } + + const start = getStart(this.mapPosition(range.getStartPosition())); + const end = getEnd(this.mapPosition(range.getEndPosition())); + + return rangeFromPositions(start, end); + } + + // TODO: `doc` is not needed for this! + inverseMapPosition(positionAfterEdit: Position, doc: IText): Position | Range { + const reversed = this.inverse(doc); + return reversed.mapPosition(positionAfterEdit); + } + + inverseMapRange(range: Range, doc: IText): Range { + const reversed = this.inverse(doc); + return reversed.mapRange(range); + } + + apply(text: IText): string { + let result = ''; + let lastEditEnd = new Position(1, 1); + for (const edit of this.edits) { + const editRange = edit.range; + const editStart = editRange.getStartPosition(); + const editEnd = editRange.getEndPosition(); + + const r = rangeFromPositions(lastEditEnd, editStart); + if (!r.isEmpty()) { + result += text.getValue(r); + } + result += edit.text; + lastEditEnd = editEnd; + } + const r = rangeFromPositions(lastEditEnd, text.endPositionExclusive); + if (!r.isEmpty()) { + result += text.getValue(r); + } + return result; + } + + applyToString(str: string): string { + const strText = new StringText(str); + return this.apply(strText); + } + + inverse(doc: IText): TextEdit { + const ranges = this.getNewRanges(); + return new TextEdit(this.edits.map((e, idx) => new SingleTextEdit(ranges[idx], doc.getValue(e.range)))); + } + + getNewRanges(): Range[] { + const newRanges: Range[] = []; + let previousEditEndLineNumber = 0; + let lineOffset = 0; + let columnOffset = 0; + for (const edit of this.edits) { + const textLength = RangeLength.ofText(edit.text); + const newRangeStart = Position.lift({ + lineNumber: edit.range.startLineNumber + lineOffset, + column: edit.range.startColumn + (edit.range.startLineNumber === previousEditEndLineNumber ? columnOffset : 0) + }); + const newRange = textLength.createRange(newRangeStart); + newRanges.push(newRange); + lineOffset = newRange.endLineNumber - edit.range.endLineNumber; + columnOffset = newRange.endColumn - edit.range.endColumn; + previousEditEndLineNumber = edit.range.endLineNumber; + } + return newRanges; + } +} + +export class SingleTextEdit { + constructor( + public readonly range: Range, + public readonly text: string, + ) { + } + + static equals(first: SingleTextEdit, second: SingleTextEdit) { + return first.range.equalsRange(second.range) && first.text === second.text; + } +} + +function rangeFromPositions(start: Position, end: Position): Range { + if (!start.isBeforeOrEqual(end)) { + throw new BugIndicatingError('start must be before end'); + } + return new Range(start.lineNumber, start.column, end.lineNumber, end.column); +} + +export interface IText { + getValue(range: Range): string; + readonly endPositionExclusive: Position; +} + +export class LineBasedText implements IText { + constructor( + private readonly _getLineContent: (lineNumber: number) => string, + private readonly _lineCount: number, + ) { } + + getValue(range: Range): string { + if (range.startLineNumber === range.endLineNumber) { + return this._getLineContent(range.startLineNumber).substring(range.startColumn - 1, range.endColumn - 1); + } + let result = this._getLineContent(range.startLineNumber).substring(range.startColumn - 1); + for (let i = range.startLineNumber + 1; i < range.endLineNumber; i++) { + result += '\n' + this._getLineContent(i); + } + result += '\n' + this._getLineContent(range.endLineNumber).substring(0, range.endColumn - 1); + return result; + } + + get endPositionExclusive(): Position { + const lastLine = this._getLineContent(this._lineCount); + return new Position(this._lineCount, lastLine.length + 1); + } +} + +export class StringText implements IText { + private readonly _t = new PositionOffsetTransformer(this.str); + + constructor(private readonly str: string) { } + + getValue(range: Range): string { + return this._t.getOffsetRange(range).substring(this.str); + } + + get endPositionExclusive(): Position { + return this._t.getPosition(this.str.length); + } +} diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 7117b8240af..10002234dde 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -1256,7 +1256,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati ); } - private _validateEditOperations(rawOperations: model.IIdentifiedSingleEditOperation[]): model.ValidAnnotatedEditOperation[] { + private _validateEditOperations(rawOperations: readonly model.IIdentifiedSingleEditOperation[]): model.ValidAnnotatedEditOperation[] { const result: model.ValidAnnotatedEditOperation[] = []; for (let i = 0, len = rawOperations.length; i < len; i++) { result[i] = this._validateEditOperation(rawOperations[i]); @@ -1406,10 +1406,10 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati } } - public applyEdits(operations: model.IIdentifiedSingleEditOperation[]): void; - public applyEdits(operations: model.IIdentifiedSingleEditOperation[], computeUndoEdits: false): void; - public applyEdits(operations: model.IIdentifiedSingleEditOperation[], computeUndoEdits: true): model.IValidEditOperation[]; - public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[], computeUndoEdits: boolean = false): void | model.IValidEditOperation[] { + public applyEdits(operations: readonly model.IIdentifiedSingleEditOperation[]): void; + public applyEdits(operations: readonly model.IIdentifiedSingleEditOperation[], computeUndoEdits: false): void; + public applyEdits(operations: readonly model.IIdentifiedSingleEditOperation[], computeUndoEdits: true): model.IValidEditOperation[]; + public applyEdits(rawOperations: readonly model.IIdentifiedSingleEditOperation[], computeUndoEdits: boolean = false): void | model.IValidEditOperation[] { try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); diff --git a/src/vs/editor/common/model/textModelText.ts b/src/vs/editor/common/model/textModelText.ts new file mode 100644 index 00000000000..40433478d4b --- /dev/null +++ b/src/vs/editor/common/model/textModelText.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { IText } from 'vs/editor/common/core/textEdit'; +import { ITextModel } from 'vs/editor/common/model'; + +export class TextModelText implements IText { + constructor(private readonly _textModel: ITextModel) { } + + getValue(range: Range): string { + return this._textModel.getValueInRange(range); + } + + get endPositionExclusive(): Position { + const lastLineNumber = this._textModel.getLineCount(); + const lastLineLen = this._textModel.getLineLength(lastLineNumber); + return new Position(lastLineNumber, lastLineLen + 1); + } +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts b/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts index 42d0404984f..21abd0b7093 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts @@ -5,8 +5,10 @@ import { equals } from 'vs/base/common/arrays'; import { splitLines } from 'vs/base/common/strings'; +import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { ColumnRange, applyEdits } from 'vs/editor/contrib/inlineCompletions/browser/utils'; +import { SingleTextEdit, TextEdit } from 'vs/editor/common/core/textEdit'; +import { ColumnRange } from 'vs/editor/contrib/inlineCompletions/browser/utils'; export class GhostText { constructor( @@ -25,13 +27,12 @@ export class GhostText { * Only used for testing/debugging. */ render(documentText: string, debug: boolean = false): string { - const l = this.lineNumber; - return applyEdits(documentText, [ - ...this.parts.map(p => ({ - range: { startLineNumber: l, endLineNumber: l, startColumn: p.column, endColumn: p.column }, - text: debug ? `[${p.lines.join('\n')}]` : p.lines.join('\n') - })), - ]); + return new TextEdit([ + ...this.parts.map(p => new SingleTextEdit( + Range.fromPositions(new Position(this.lineNumber, p.column)), + debug ? `[${p.lines.join('\n')}]` : p.lines.join('\n') + )), + ]).applyToString(documentText); } renderForScreenReader(lineText: string): string { @@ -41,12 +42,12 @@ export class GhostText { const lastPart = this.parts[this.parts.length - 1]; const cappedLineText = lineText.substr(0, lastPart.column - 1); - const text = applyEdits(cappedLineText, - this.parts.map(p => ({ - range: { startLineNumber: 1, endLineNumber: 1, startColumn: p.column, endColumn: p.column }, - text: p.lines.join('\n') - })) - ); + const text = new TextEdit([ + ...this.parts.map(p => new SingleTextEdit( + Range.fromPositions(new Position(1, p.column)), + p.lines.join('\n') + )), + ]).applyToString(cappedLineText); return text.substring(this.parts[0].column - 1); } @@ -106,14 +107,14 @@ export class GhostTextReplacement { const replaceRange = this.columnRange.toRange(this.lineNumber); if (debug) { - return applyEdits(documentText, [ - { range: Range.fromPositions(replaceRange.getStartPosition()), text: `(` }, - { range: Range.fromPositions(replaceRange.getEndPosition()), text: `)[${this.newLines.join('\n')}]` } - ]); + return new TextEdit([ + new SingleTextEdit(Range.fromPositions(replaceRange.getStartPosition()), '('), + new SingleTextEdit(Range.fromPositions(replaceRange.getEndPosition()), `)[${this.newLines.join('\n')}]`), + ]).applyToString(documentText); } else { - return applyEdits(documentText, [ - { range: replaceRange, text: this.newLines.join('\n') } - ]); + return new TextEdit([ + new SingleTextEdit(replaceRange, this.newLines.join('\n')), + ]).applyToString(documentText); } } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 8bc30856f20..57357fd47e1 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -21,12 +21,14 @@ import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { IFeatureDebounceInformation } from 'vs/editor/common/services/languageFeatureDebounce'; import { GhostText, GhostTextOrReplacement, ghostTextOrReplacementEquals, ghostTextsOrReplacementsEqual } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; import { InlineCompletionWithUpdatedRange, InlineCompletionsSource } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource'; -import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; +import { SingleTextEdit, TextEdit } from 'vs/editor/common/core/textEdit'; import { SuggestItemInfo } from 'vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider'; -import { addPositions, getNewRanges, lengthOfText, subtractPositions } from 'vs/editor/contrib/inlineCompletions/browser/utils'; +import { addPositions, subtractPositions } from 'vs/editor/contrib/inlineCompletions/browser/utils'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { singleTextEditAugments, computeGhostText, singleTextRemoveCommonPrefix } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; +import { RangeLength } from 'vs/editor/common/core/rangeLength'; export enum VersionIdChangeReason { Undo, @@ -213,7 +215,7 @@ export class InlineCompletionsModel extends Disposable { const suggestItem = this.selectedSuggestItem.read(reader); if (suggestItem) { - const suggestCompletionEdit = suggestItem.toSingleTextEdit().removeCommonPrefix(model); + const suggestCompletionEdit = singleTextRemoveCommonPrefix(suggestItem.toSingleTextEdit(), model); const augmentation = this._computeAugmentation(suggestCompletionEdit, reader); const isSuggestionPreviewEnabled = this._suggestPreviewEnabled.read(reader); @@ -226,7 +228,7 @@ export class InlineCompletionsModel extends Disposable { const positions = this._positions.read(reader); const edits = [fullEdit, ...getSecondaryEdits(this.textModel, positions, fullEdit)]; const ghostTexts = edits - .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], fullEditPreviewLength)) + .map((edit, idx) => computeGhostText(edit, model, mode, positions[idx], fullEditPreviewLength)) .filter(isDefined); const primaryGhostText = ghostTexts[0] ?? new GhostText(fullEdit.range.endLineNumber, []); return { edits, primaryGhostText, ghostTexts, inlineCompletion: augmentation?.completion, suggestItem }; @@ -240,7 +242,7 @@ export class InlineCompletionsModel extends Disposable { const positions = this._positions.read(reader); const edits = [replacement, ...getSecondaryEdits(this.textModel, positions, replacement)]; const ghostTexts = edits - .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], 0)) + .map((edit, idx) => computeGhostText(edit, model, mode, positions[idx], 0)) .filter(isDefined); if (!ghostTexts[0]) { return undefined; } return { edits, primaryGhostText: ghostTexts[0], ghostTexts, inlineCompletion, suggestItem: undefined }; @@ -256,8 +258,8 @@ export class InlineCompletionsModel extends Disposable { const augmentedCompletion = mapFindFirst(candidateInlineCompletions, completion => { let r = completion.toSingleTextEdit(reader); - r = r.removeCommonPrefix(model, Range.fromPositions(r.range.getStartPosition(), suggestCompletion.range.getEndPosition())); - return r.augments(suggestCompletion) ? { completion, edit: r } : undefined; + r = singleTextRemoveCommonPrefix(r, model, Range.fromPositions(r.range.getStartPosition(), suggestCompletion.range.getEndPosition())); + return singleTextEditAugments(r, suggestCompletion) ? { completion, edit: r } : undefined; }); return augmentedCompletion; @@ -442,7 +444,7 @@ export class InlineCompletionsModel extends Disposable { } if (completion.source.provider.handlePartialAccept) { - const acceptedRange = Range.fromPositions(completion.range.getStartPosition(), addPositions(ghostTextPos, lengthOfText(partialGhostTextVal))); + const acceptedRange = Range.fromPositions(completion.range.getStartPosition(), RangeLength.ofText(partialGhostTextVal).addToPosition(ghostTextPos)); // This assumes that the inline completion and the model use the same EOL style. const text = editor.getModel()!.getValueInRange(acceptedRange, EndOfLinePreference.LF); completion.source.provider.handlePartialAccept( @@ -460,7 +462,7 @@ export class InlineCompletionsModel extends Disposable { } public handleSuggestAccepted(item: SuggestItemInfo) { - const itemEdit = item.toSingleTextEdit().removeCommonPrefix(this.textModel); + const itemEdit = singleTextRemoveCommonPrefix(item.toSingleTextEdit(), this.textModel); const augmentedCompletion = this._computeAugmentation(itemEdit, undefined); if (!augmentedCompletion) { return; } @@ -519,7 +521,8 @@ function substringPos(text: string, pos: Position): string { function getEndPositionsAfterApplying(edits: readonly SingleTextEdit[]): Position[] { const sortPerm = Permutation.createSortPermutation(edits, (edit1, edit2) => Range.compareRangesUsingStarts(edit1.range, edit2.range)); - const sortedNewRanges = getNewRanges(sortPerm.apply(edits)); + const edit = new TextEdit(sortPerm.apply(edits)); + const sortedNewRanges = edit.getNewRanges(); const newRanges = sortPerm.inverse().apply(sortedNewRanges); return newRanges.map(range => range.getEndPosition()); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts index 3e38e9e8161..94a8b33d477 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts @@ -15,7 +15,8 @@ import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { IFeatureDebounceInformation } from 'vs/editor/common/services/languageFeatureDebounce'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { InlineCompletionItem, InlineCompletionProviderResult, provideInlineCompletions } from 'vs/editor/contrib/inlineCompletions/browser/provideInlineCompletions'; -import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; +import { SingleTextEdit } from 'vs/editor/common/core/textEdit'; +import { singleTextRemoveCommonPrefix } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; export class InlineCompletionsSource extends Disposable { private readonly _updateOperation = this._register(new MutableDisposable()); @@ -282,7 +283,7 @@ export class InlineCompletionWithUpdatedRange { } public isVisible(model: ITextModel, cursorPosition: Position, reader: IReader | undefined): boolean { - const minimizedReplacement = this._toFilterTextReplacement(reader).removeCommonPrefix(model); + const minimizedReplacement = singleTextRemoveCommonPrefix(this._toFilterTextReplacement(reader), model); if ( !this._isValid diff --git a/src/vs/editor/contrib/inlineCompletions/browser/provideInlineCompletions.ts b/src/vs/editor/contrib/inlineCompletions/browser/provideInlineCompletions.ts index 9d91e0ade1e..28052040c32 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/provideInlineCompletions.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/provideInlineCompletions.ts @@ -17,7 +17,7 @@ import { Command, InlineCompletion, InlineCompletionContext, InlineCompletionPro import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ITextModel } from 'vs/editor/common/model'; import { fixBracketsInLine } from 'vs/editor/common/model/bracketPairsTextModelPart/fixBrackets'; -import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; +import { SingleTextEdit } from 'vs/editor/common/core/textEdit'; import { getReadonlyEmptyArray } from 'vs/editor/contrib/inlineCompletions/browser/utils'; import { SnippetParser, Text } from 'vs/editor/contrib/snippet/browser/snippetParser'; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts index 8517f24ec85..d38d1e41f57 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts @@ -7,147 +7,136 @@ import { IDiffChange, LcsDiff } from 'vs/base/common/diff/diff'; import { commonPrefixLength, getLeadingWhitespace } from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { RangeLength } from 'vs/editor/common/core/rangeLength'; +import { SingleTextEdit } from 'vs/editor/common/core/textEdit'; import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { GhostText, GhostTextPart } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; -import { addPositions, lengthOfText } from 'vs/editor/contrib/inlineCompletions/browser/utils'; -export class SingleTextEdit { - constructor( - public readonly range: Range, - public readonly text: string - ) { +export function singleTextRemoveCommonPrefix(edit: SingleTextEdit, model: ITextModel, validModelRange?: Range): SingleTextEdit { + const modelRange = validModelRange ? edit.range.intersectRanges(validModelRange) : edit.range; + if (!modelRange) { + return edit; + } + const valueToReplace = model.getValueInRange(modelRange, EndOfLinePreference.LF); + const commonPrefixLen = commonPrefixLength(valueToReplace, edit.text); + const start = RangeLength.ofText(valueToReplace.substring(0, commonPrefixLen)).addToPosition(edit.range.getStartPosition()); + const text = edit.text.substring(commonPrefixLen); + const range = Range.fromPositions(start, edit.range.getEndPosition()); + return new SingleTextEdit(range, text); +} + +export function singleTextEditAugments(edit: SingleTextEdit, base: SingleTextEdit): boolean { + // The augmented completion must replace the base range, but can replace even more + return edit.text.startsWith(base.text) && rangeExtends(edit.range, base.range); +} + +/** + * @param previewSuffixLength Sets where to split `inlineCompletion.text`. + * If the text is `hello` and the suffix length is 2, the non-preview part is `hel` and the preview-part is `lo`. +*/ +export function computeGhostText( + edit: SingleTextEdit, + model: ITextModel, + mode: 'prefix' | 'subword' | 'subwordSmart', + cursorPosition?: Position, + previewSuffixLength = 0 +): GhostText | undefined { + let e = singleTextRemoveCommonPrefix(edit, model); + + if (e.range.endLineNumber !== e.range.startLineNumber) { + // This edit might span multiple lines, but the first lines must be a common prefix. + return undefined; } - static equals(first: SingleTextEdit, second: SingleTextEdit) { - return first.range.equalsRange(second.range) && first.text === second.text; + const sourceLine = model.getLineContent(e.range.startLineNumber); + const sourceIndentationLength = getLeadingWhitespace(sourceLine).length; + const suggestionTouchesIndentation = e.range.startColumn - 1 <= sourceIndentationLength; + if (suggestionTouchesIndentation) { + // source: ··········[······abc] + // ^^^^^^^^^ inlineCompletion.range + // ^^^^^^^^^^ ^^^^^^ sourceIndentationLength + // ^^^^^^ replacedIndentation.length + // ^^^ rangeThatDoesNotReplaceIndentation + + // inlineCompletion.text: '··foo' + // ^^ suggestionAddedIndentationLength + + const suggestionAddedIndentationLength = getLeadingWhitespace(e.text).length; + + const replacedIndentation = sourceLine.substring(e.range.startColumn - 1, sourceIndentationLength); + + const [startPosition, endPosition] = [e.range.getStartPosition(), e.range.getEndPosition()]; + const newStartPosition = + startPosition.column + replacedIndentation.length <= endPosition.column + ? startPosition.delta(0, replacedIndentation.length) + : endPosition; + const rangeThatDoesNotReplaceIndentation = Range.fromPositions(newStartPosition, endPosition); + + const suggestionWithoutIndentationChange = + e.text.startsWith(replacedIndentation) + // Adds more indentation without changing existing indentation: We can add ghost text for this + ? e.text.substring(replacedIndentation.length) + // Changes or removes existing indentation. Only add ghost text for the non-indentation part. + : e.text.substring(suggestionAddedIndentationLength); + + e = new SingleTextEdit(rangeThatDoesNotReplaceIndentation, suggestionWithoutIndentationChange); } - removeCommonPrefix(model: ITextModel, validModelRange?: Range): SingleTextEdit { - const modelRange = validModelRange ? this.range.intersectRanges(validModelRange) : this.range; - if (!modelRange) { - return this; + // This is a single line string + const valueToBeReplaced = model.getValueInRange(e.range); + + const changes = cachingDiff(valueToBeReplaced, e.text); + + if (!changes) { + // No ghost text in case the diff would be too slow to compute + return undefined; + } + + const lineNumber = e.range.startLineNumber; + + const parts = new Array(); + + if (mode === 'prefix') { + const filteredChanges = changes.filter(c => c.originalLength === 0); + if (filteredChanges.length > 1 || filteredChanges.length === 1 && filteredChanges[0].originalStart !== valueToBeReplaced.length) { + // Prefixes only have a single change. + return undefined; } - const valueToReplace = model.getValueInRange(modelRange, EndOfLinePreference.LF); - const commonPrefixLen = commonPrefixLength(valueToReplace, this.text); - const start = addPositions(this.range.getStartPosition(), lengthOfText(valueToReplace.substring(0, commonPrefixLen))); - const text = this.text.substring(commonPrefixLen); - const range = Range.fromPositions(start, this.range.getEndPosition()); - return new SingleTextEdit(range, text); } - augments(base: SingleTextEdit): boolean { - // The augmented completion must replace the base range, but can replace even more - return this.text.startsWith(base.text) && rangeExtends(this.range, base.range); - } + const previewStartInCompletionText = e.text.length - previewSuffixLength; - /** - * @param previewSuffixLength Sets where to split `inlineCompletion.text`. - * If the text is `hello` and the suffix length is 2, the non-preview part is `hel` and the preview-part is `lo`. - */ - computeGhostText( - model: ITextModel, - mode: 'prefix' | 'subword' | 'subwordSmart', - cursorPosition?: Position, - previewSuffixLength = 0 - ): GhostText | undefined { - let edit = this.removeCommonPrefix(model); + for (const c of changes) { + const insertColumn = e.range.startColumn + c.originalStart + c.originalLength; - if (edit.range.endLineNumber !== edit.range.startLineNumber) { - // This edit might span multiple lines, but the first lines must be a common prefix. + if (mode === 'subwordSmart' && cursorPosition && cursorPosition.lineNumber === e.range.startLineNumber && insertColumn < cursorPosition.column) { + // No ghost text before cursor return undefined; } - const sourceLine = model.getLineContent(edit.range.startLineNumber); - const sourceIndentationLength = getLeadingWhitespace(sourceLine).length; - - const suggestionTouchesIndentation = edit.range.startColumn - 1 <= sourceIndentationLength; - if (suggestionTouchesIndentation) { - // source: ··········[······abc] - // ^^^^^^^^^ inlineCompletion.range - // ^^^^^^^^^^ ^^^^^^ sourceIndentationLength - // ^^^^^^ replacedIndentation.length - // ^^^ rangeThatDoesNotReplaceIndentation - - // inlineCompletion.text: '··foo' - // ^^ suggestionAddedIndentationLength - - const suggestionAddedIndentationLength = getLeadingWhitespace(edit.text).length; - - const replacedIndentation = sourceLine.substring(edit.range.startColumn - 1, sourceIndentationLength); - - const [startPosition, endPosition] = [edit.range.getStartPosition(), edit.range.getEndPosition()]; - const newStartPosition = - startPosition.column + replacedIndentation.length <= endPosition.column - ? startPosition.delta(0, replacedIndentation.length) - : endPosition; - const rangeThatDoesNotReplaceIndentation = Range.fromPositions(newStartPosition, endPosition); - - const suggestionWithoutIndentationChange = - edit.text.startsWith(replacedIndentation) - // Adds more indentation without changing existing indentation: We can add ghost text for this - ? edit.text.substring(replacedIndentation.length) - // Changes or removes existing indentation. Only add ghost text for the non-indentation part. - : edit.text.substring(suggestionAddedIndentationLength); - - edit = new SingleTextEdit(rangeThatDoesNotReplaceIndentation, suggestionWithoutIndentationChange); - } - - // This is a single line string - const valueToBeReplaced = model.getValueInRange(edit.range); - - const changes = cachingDiff(valueToBeReplaced, edit.text); - - if (!changes) { - // No ghost text in case the diff would be too slow to compute + if (c.originalLength > 0) { return undefined; } - const lineNumber = edit.range.startLineNumber; - - const parts = new Array(); - - if (mode === 'prefix') { - const filteredChanges = changes.filter(c => c.originalLength === 0); - if (filteredChanges.length > 1 || filteredChanges.length === 1 && filteredChanges[0].originalStart !== valueToBeReplaced.length) { - // Prefixes only have a single change. - return undefined; - } + if (c.modifiedLength === 0) { + continue; } - const previewStartInCompletionText = edit.text.length - previewSuffixLength; + const modifiedEnd = c.modifiedStart + c.modifiedLength; + const nonPreviewTextEnd = Math.max(c.modifiedStart, Math.min(modifiedEnd, previewStartInCompletionText)); + const nonPreviewText = e.text.substring(c.modifiedStart, nonPreviewTextEnd); + const italicText = e.text.substring(nonPreviewTextEnd, Math.max(c.modifiedStart, modifiedEnd)); - for (const c of changes) { - const insertColumn = edit.range.startColumn + c.originalStart + c.originalLength; - - if (mode === 'subwordSmart' && cursorPosition && cursorPosition.lineNumber === edit.range.startLineNumber && insertColumn < cursorPosition.column) { - // No ghost text before cursor - return undefined; - } - - if (c.originalLength > 0) { - return undefined; - } - - if (c.modifiedLength === 0) { - continue; - } - - const modifiedEnd = c.modifiedStart + c.modifiedLength; - const nonPreviewTextEnd = Math.max(c.modifiedStart, Math.min(modifiedEnd, previewStartInCompletionText)); - const nonPreviewText = edit.text.substring(c.modifiedStart, nonPreviewTextEnd); - const italicText = edit.text.substring(nonPreviewTextEnd, Math.max(c.modifiedStart, modifiedEnd)); - - if (nonPreviewText.length > 0) { - parts.push(new GhostTextPart(insertColumn, nonPreviewText, false)); - } - if (italicText.length > 0) { - parts.push(new GhostTextPart(insertColumn, italicText, true)); - } + if (nonPreviewText.length > 0) { + parts.push(new GhostTextPart(insertColumn, nonPreviewText, false)); + } + if (italicText.length > 0) { + parts.push(new GhostTextPart(insertColumn, italicText, true)); } - - return new GhostText(lineNumber, parts); } + + return new GhostText(lineNumber, parts); } function rangeExtends(extendingRange: Range, rangeToExtend: Range): boolean { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider.ts b/src/vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider.ts index 90d53b26c8f..5f98b45033a 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider.ts @@ -14,10 +14,11 @@ import { SnippetSession } from 'vs/editor/contrib/snippet/browser/snippetSession import { CompletionItem } from 'vs/editor/contrib/suggest/browser/suggest'; import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; import { IObservable, ITransaction, observableValue, transaction } from 'vs/base/common/observable'; -import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; +import { SingleTextEdit } from 'vs/editor/common/core/textEdit'; import { ITextModel } from 'vs/editor/common/model'; import { compareBy, numberComparator } from 'vs/base/common/arrays'; import { findFirstMaxBy } from 'vs/base/common/arraysFind'; +import { singleTextEditAugments, singleTextRemoveCommonPrefix } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; export class SuggestWidgetAdaptor extends Disposable { private isSuggestWidgetVisible: boolean = false; @@ -66,7 +67,8 @@ export class SuggestWidgetAdaptor extends Disposable { return -1; } - const itemToPreselect = this.suggestControllerPreselector()?.removeCommonPrefix(textModel); + const i = this.suggestControllerPreselector(); + const itemToPreselect = i ? singleTextRemoveCommonPrefix(i, textModel) : undefined; if (!itemToPreselect) { return -1; } @@ -75,8 +77,8 @@ export class SuggestWidgetAdaptor extends Disposable { const candidates = suggestItems .map((suggestItem, index) => { const suggestItemInfo = SuggestItemInfo.fromSuggestion(suggestController, textModel, position, suggestItem, this.isShiftKeyPressed); - const suggestItemTextEdit = suggestItemInfo.toSingleTextEdit().removeCommonPrefix(textModel); - const valid = itemToPreselect.augments(suggestItemTextEdit); + const suggestItemTextEdit = singleTextRemoveCommonPrefix(suggestItemInfo.toSingleTextEdit(), textModel); + const valid = singleTextEditAugments(itemToPreselect, suggestItemTextEdit); return { index, valid, prefixLength: suggestItemTextEdit.text.length, suggestItem }; }) .filter(item => item && item.valid && item.prefixLength > 0); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts index 0dc96bb3945..20236aade3b 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts @@ -3,54 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Permutation, compareBy } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IObservable, autorunOpts } from 'vs/base/common/observable'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; -import { IRange, Range } from 'vs/editor/common/core/range'; +import { Range } from 'vs/editor/common/core/range'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; -import { TextModel } from 'vs/editor/common/model/textModel'; - -export function applyEdits(text: string, edits: { range: IRange; text: string }[]): string { - const transformer = new PositionOffsetTransformer(text); - const offsetEdits = edits.map(e => { - const range = Range.lift(e.range); - return ({ - startOffset: transformer.getOffset(range.getStartPosition()), - endOffset: transformer.getOffset(range.getEndPosition()), - text: e.text - }); - }); - - offsetEdits.sort((a, b) => b.startOffset - a.startOffset); - - for (const edit of offsetEdits) { - text = text.substring(0, edit.startOffset) + edit.text + text.substring(edit.endOffset); - } - - return text; -} - -class PositionOffsetTransformer { - private readonly lineStartOffsetByLineIdx: number[]; - - constructor(text: string) { - this.lineStartOffsetByLineIdx = []; - this.lineStartOffsetByLineIdx.push(0); - for (let i = 0; i < text.length; i++) { - if (text.charAt(i) === '\n') { - this.lineStartOffsetByLineIdx.push(i + 1); - } - } - } - - getOffset(position: Position): number { - return this.lineStartOffsetByLineIdx[position.lineNumber - 1] + position.column - 1; - } -} const array: ReadonlyArray = []; export function getReadonlyEmptyArray(): readonly T[] { @@ -99,65 +58,3 @@ export function addPositions(pos1: Position, pos2: Position): Position { export function subtractPositions(pos1: Position, pos2: Position): Position { return new Position(pos1.lineNumber - pos2.lineNumber + 1, pos1.lineNumber - pos2.lineNumber === 0 ? pos1.column - pos2.column + 1 : pos1.column); } - -export function lengthOfText(text: string): Position { - let line = 1; - let column = 1; - for (const c of text) { - if (c === '\n') { - line++; - column = 1; - } else { - column++; - } - } - return new Position(line, column); -} - -/** - * Given some text edits, this function finds the new ranges of the editted text post application of all edits. - * Assumes that the edit ranges are disjoint and they are sorted in the order of the ranges - * @param edits edits applied - * @returns new ranges post edits for every edit - */ -export function getNewRanges(edits: ISingleEditOperation[]): Range[] { - const newRanges: Range[] = []; - let previousEditEndLineNumber = 0; - let lineOffset = 0; - let columnOffset = 0; - - for (const edit of edits) { - const text = edit.text ?? ''; - const textLength = lengthOfText(text); - const newRangeStart = Position.lift({ - lineNumber: edit.range.startLineNumber + lineOffset, - column: edit.range.startColumn + (edit.range.startLineNumber === previousEditEndLineNumber ? columnOffset : 0) - }); - const newRangeEnd = addPositions( - newRangeStart, - textLength - ); - newRanges.push(Range.fromPositions(newRangeStart, newRangeEnd)); - lineOffset += textLength.lineNumber - edit.range.endLineNumber + edit.range.startLineNumber - 1; - columnOffset = newRangeEnd.column - edit.range.endColumn; - previousEditEndLineNumber = edit.range.endLineNumber; - } - return newRanges; -} - -/** - * Given a text model and edits, this function finds the inverse text edits - * @param model model on which to apply the edits - * @param edits edits applied - * @returns inverse edits - */ -export function inverseEdits(model: TextModel, edits: ISingleEditOperation[]): ISingleEditOperation[] { - const sortPerm = Permutation.createSortPermutation(edits, compareBy(e => e.range, Range.compareRangesUsingStarts)); - const sortedRanges = getNewRanges(sortPerm.apply(edits)); - const newRanges = sortPerm.inverse().apply(sortedRanges); - const inverseEdits: ISingleEditOperation[] = []; - for (let i = 0; i < edits.length; i++) { - inverseEdits.push({ range: newRanges[i], text: model.getValueInRange(edits[i].range) }); - } - return inverseEdits; -} diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts index 67c56bb3945..648f5940596 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Position } from 'vs/editor/common/core/position'; import { getSecondaryEdits } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel'; -import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; +import { SingleTextEdit } from 'vs/editor/common/core/textEdit'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; import { Range } from 'vs/editor/common/core/range'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts index 4c846025949..e160c3daa01 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts @@ -15,13 +15,14 @@ import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeatu import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel'; -import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; +import { SingleTextEdit } from 'vs/editor/common/core/textEdit'; import { GhostTextContext, MockInlineCompletionsProvider } from 'vs/editor/contrib/inlineCompletions/test/browser/utils'; import { ITestCodeEditor, TestCodeEditorInstantiationOptions, withAsyncTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { Selection } from 'vs/editor/common/core/selection'; +import { computeGhostText } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; suite('Inline Completions', () => { ensureNoDisposablesAreLeakedInTestSuite(); @@ -37,7 +38,7 @@ suite('Inline Completions', () => { const options = ['prefix', 'subword'] as const; const result = {} as any; for (const option of options) { - result[option] = new SingleTextEdit(range, suggestion).computeGhostText(tempModel, option)?.render(cleanedText, true); + result[option] = computeGhostText(new SingleTextEdit(range, suggestion), tempModel, option)?.render(cleanedText, true); } tempModel.dispose(); diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts deleted file mode 100644 index 16c918fbe18..00000000000 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { createTextModel } from 'vs/editor/test/common/testTextModel'; -import { MersenneTwister, getRandomEditInfos, toEdit, } from 'vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test'; -import { inverseEdits } from 'vs/editor/contrib/inlineCompletions/browser/utils'; -import { generateRandomMultilineString } from 'vs/editor/contrib/inlineCompletions/test/browser/utils'; - -suite('getNewRanges', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - for (let seed = 0; seed < 20; seed++) { - test(`test ${seed}`, () => { - const rng = new MersenneTwister(seed); - const randomText = generateRandomMultilineString(rng, 10); - const model = createTextModel(randomText); - - const edits = getRandomEditInfos(model, rng.nextIntRange(1, 4), rng, true).map(e => toEdit(e)); - const invEdits = inverseEdits(model, edits); - - model.applyEdits(edits); - model.applyEdits(invEdits); - - assert.deepStrictEqual(model.getValue(), randomText); - model.dispose(); - }); - } - -}); diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts index 49e09d4e4a7..50250883440 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts @@ -133,23 +133,3 @@ export class GhostTextContext extends Disposable { } } -export function generateRandomMultilineString(rng: MersenneTwister, numberOfLines: number, maximumLengthOfLines: number = 20): string { - let randomText: string = ''; - for (let i = 0; i < numberOfLines; i++) { - const lengthOfLine = rng.nextIntRange(0, maximumLengthOfLines + 1); - randomText += generateRandomSimpleString(rng, lengthOfLine) + '\n'; - } - return randomText; -} - -function generateRandomSimpleString(rng: MersenneTwister, stringLength: number): string { - const possibleCharacters: string = ' abcdefghijklmnopqrstuvwxyz0123456789'; - let randomText: string = ''; - for (let i = 0; i < stringLength; i++) { - const characterIndex = rng.nextIntRange(0, possibleCharacters.length); - randomText += possibleCharacters.charAt(characterIndex); - - } - return randomText; -} - diff --git a/src/vs/editor/test/common/core/positionOffsetTransformer.test.ts b/src/vs/editor/test/common/core/positionOffsetTransformer.test.ts new file mode 100644 index 00000000000..39aead0a848 --- /dev/null +++ b/src/vs/editor/test/common/core/positionOffsetTransformer.test.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { OffsetRange } from 'vs/editor/common/core/offsetRange'; +import { PositionOffsetTransformer } from 'vs/editor/common/core/positionToOffset'; + +suite('PositionOffsetTransformer', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + const str = '123456\nabcdef\nghijkl\nmnopqr'; + + const t = new PositionOffsetTransformer(str); + test('getPosition', () => { + assert.deepStrictEqual( + new OffsetRange(0, str.length + 2).map(i => t.getPosition(i).toString()), + [ + "(1,1)", + "(1,2)", + "(1,3)", + "(1,4)", + "(1,5)", + "(1,6)", + "(1,7)", + "(2,1)", + "(2,2)", + "(2,3)", + "(2,4)", + "(2,5)", + "(2,6)", + "(2,7)", + "(3,1)", + "(3,2)", + "(3,3)", + "(3,4)", + "(3,5)", + "(3,6)", + "(3,7)", + "(4,1)", + "(4,2)", + "(4,3)", + "(4,4)", + "(4,5)", + "(4,6)", + "(4,7)", + "(4,8)" + ] + ); + }); + + test('getOffset', () => { + for (let i = 0; i < str.length + 2; i++) { + assert.strictEqual(t.getOffset(t.getPosition(i)), i); + } + }); +}); diff --git a/src/vs/editor/test/common/core/textEdit.test.ts b/src/vs/editor/test/common/core/textEdit.test.ts new file mode 100644 index 00000000000..1e9c342ebcd --- /dev/null +++ b/src/vs/editor/test/common/core/textEdit.test.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. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { createTextModel } from 'vs/editor/test/common/testTextModel'; +import { MersenneTwister, getRandomEditInfos, toEdit, } from 'vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test'; +import { TextEdit } from 'vs/editor/common/core/textEdit'; +import { TextModelText } from 'vs/editor/common/model/textModelText'; + +suite('TextEdit', () => { + suite('inverse', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + function runTest(seed: number): void { + const rng = new MersenneTwister(seed); + const randomText = generateRandomMultilineString(rng, 10); + const model = createTextModel(randomText); + + const edits = new TextEdit(getRandomEditInfos(model, rng.nextIntRange(1, 4), rng, true).map(e => toEdit(e))); + const invEdits = edits.inverse(new TextModelText(model)); + + model.applyEdits(edits.edits); + model.applyEdits(invEdits.edits); + + assert.deepStrictEqual(model.getValue(), randomText); + model.dispose(); + } + + test.skip('brute-force', () => { + for (let i = 0; i < 10_000; i++) { + runTest(i); + } + }); + + for (let seed = 0; seed < 20; seed++) { + test(`test ${seed}`, () => runTest(seed)); + } + }); +}); + +function generateRandomMultilineString(rng: MersenneTwister, numberOfLines: number, maximumLengthOfLines: number = 20): string { + let randomText: string = ''; + for (let i = 0; i < numberOfLines; i++) { + const lengthOfLine = rng.nextIntRange(0, maximumLengthOfLines + 1); + randomText += generateRandomSimpleString(rng, lengthOfLine) + '\n'; + } + return randomText; +} + +function generateRandomSimpleString(rng: MersenneTwister, stringLength: number): string { + const possibleCharacters: string = ' abcdefghijklmnopqrstuvwxyz0123456789'; + let randomText: string = ''; + for (let i = 0; i < stringLength; i++) { + const characterIndex = rng.nextIntRange(0, possibleCharacters.length); + randomText += possibleCharacters.charAt(characterIndex); + + } + return randomText; +} diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts index 611ba267d5b..05f2063baf1 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts @@ -5,8 +5,8 @@ import * as assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; +import { SingleTextEdit } from 'vs/editor/common/core/textEdit'; import { TextEditInfo } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper'; import { combineTextEditInfos } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/combineTextEditInfos'; import { lengthAdd, lengthToObj, lengthToPosition, positionToLength, toLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; @@ -14,7 +14,6 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; suite('combineTextEditInfos', () => { - ensureNoDisposablesAreLeakedInTestSuite(); for (let seed = 0; seed < 50; seed++) { @@ -79,7 +78,7 @@ function getRandomEdit(textModel: TextModel, rangeOffsetStart: number, rng: Mers return new TextEditInfo(positionToLength(textModel.getPositionAt(offsetStart)), positionToLength(textModel.getPositionAt(offsetEnd)), toLength(lineCount, columnCount)); } -export function toEdit(editInfo: TextEditInfo): ISingleEditOperation { +export function toEdit(editInfo: TextEditInfo): SingleTextEdit { const l = lengthToObj(editInfo.newLength); let text = ''; From af2b2ecbbd0136bde36fc13544aae7c9f22d0b49 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 8 Mar 2024 15:14:46 +0100 Subject: [PATCH 1875/1897] Introduces test utility "Random" for deterministic random-like data generation --- src/vs/editor/common/core/positionToOffset.ts | 12 +- src/vs/editor/common/core/rangeLength.ts | 4 + src/vs/editor/common/core/textEdit.ts | 82 +++++++++---- src/vs/editor/common/model/textModelText.ts | 16 +-- .../inlineCompletions/test/browser/utils.ts | 1 - src/vs/editor/test/common/core/random.ts | 113 ++++++++++++++++++ .../editor/test/common/core/textEdit.test.ts | 45 ++----- .../combineTextEditInfos.test.ts | 60 ++-------- 8 files changed, 214 insertions(+), 119 deletions(-) create mode 100644 src/vs/editor/test/common/core/random.ts diff --git a/src/vs/editor/common/core/positionToOffset.ts b/src/vs/editor/common/core/positionToOffset.ts index c4abee8a332..e181b90173c 100644 --- a/src/vs/editor/common/core/positionToOffset.ts +++ b/src/vs/editor/common/core/positionToOffset.ts @@ -7,11 +7,12 @@ import { findLastIdxMonotonous } from 'vs/base/common/arraysFind'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { RangeLength } from 'vs/editor/common/core/rangeLength'; export class PositionOffsetTransformer { private readonly lineStartOffsetByLineIdx: number[]; - constructor(text: string) { + constructor(public readonly text: string) { this.lineStartOffsetByLineIdx = []; this.lineStartOffsetByLineIdx.push(0); for (let i = 0; i < text.length; i++) { @@ -45,4 +46,13 @@ export class PositionOffsetTransformer { this.getPosition(offsetRange.endExclusive) ); } + + getTextLength(offsetRange: OffsetRange): RangeLength { + return RangeLength.ofRange(this.getRange(offsetRange)); + } + + get textLength(): RangeLength { + const lineIdx = this.lineStartOffsetByLineIdx.length - 1; + return new RangeLength(lineIdx, this.text.length - this.lineStartOffsetByLineIdx[lineIdx]); + } } diff --git a/src/vs/editor/common/core/rangeLength.ts b/src/vs/editor/common/core/rangeLength.ts index 90c47e092c9..d01fca41c1f 100644 --- a/src/vs/editor/common/core/rangeLength.ts +++ b/src/vs/editor/common/core/rangeLength.ts @@ -98,6 +98,10 @@ export class RangeLength { } } + public toRange(): Range { + return new Range(1, 1, this.lineCount + 1, this.columnCount + 1); + } + public addToPosition(position: Position): Position { if (this.lineCount === 0) { return new Position(position.lineNumber, position.column + this.columnCount); diff --git a/src/vs/editor/common/core/textEdit.ts b/src/vs/editor/common/core/textEdit.ts index 42e94c281d7..ade6f7bf16a 100644 --- a/src/vs/editor/common/core/textEdit.ts +++ b/src/vs/editor/common/core/textEdit.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { assertFn, checkAdjacentItems } from 'vs/base/common/assert'; +import { assert, assertFn, checkAdjacentItems } from 'vs/base/common/assert'; import { BugIndicatingError } from 'vs/base/common/errors'; import { Position } from 'vs/editor/common/core/position'; import { PositionOffsetTransformer } from 'vs/editor/common/core/positionToOffset'; @@ -15,6 +15,22 @@ export class TextEdit { assertFn(() => checkAdjacentItems(edits, (a, b) => a.range.getEndPosition().isBeforeOrEqual(b.range.getStartPosition()))); } + /** + * Joins touching edits and removes empty edits. + */ + normalize(): TextEdit { + const edits: SingleTextEdit[] = []; + for (const edit of this.edits) { + if (edits.length > 0 && edits[edits.length - 1].range.getEndPosition().equals(edit.range.getStartPosition())) { + const last = edits[edits.length - 1]; + edits[edits.length - 1] = new SingleTextEdit(last.range.plusRange(edit.range), last.text + edit.text); + } else if (!edit.isEmpty) { + edits.push(edit); + } + } + return new TextEdit(edits); + } + mapPosition(position: Position): Position | Range { let lineDelta = 0; let curLine = 0; @@ -68,17 +84,17 @@ export class TextEdit { } // TODO: `doc` is not needed for this! - inverseMapPosition(positionAfterEdit: Position, doc: IText): Position | Range { + inverseMapPosition(positionAfterEdit: Position, doc: AbstractText): Position | Range { const reversed = this.inverse(doc); return reversed.mapPosition(positionAfterEdit); } - inverseMapRange(range: Range, doc: IText): Range { + inverseMapRange(range: Range, doc: AbstractText): Range { const reversed = this.inverse(doc); return reversed.mapRange(range); } - apply(text: IText): string { + apply(text: AbstractText): string { let result = ''; let lastEditEnd = new Position(1, 1); for (const edit of this.edits) { @@ -88,14 +104,14 @@ export class TextEdit { const r = rangeFromPositions(lastEditEnd, editStart); if (!r.isEmpty()) { - result += text.getValue(r); + result += text.getValueOfRange(r); } result += edit.text; lastEditEnd = editEnd; } const r = rangeFromPositions(lastEditEnd, text.endPositionExclusive); if (!r.isEmpty()) { - result += text.getValue(r); + result += text.getValueOfRange(r); } return result; } @@ -105,9 +121,9 @@ export class TextEdit { return this.apply(strText); } - inverse(doc: IText): TextEdit { + inverse(doc: AbstractText): TextEdit { const ranges = this.getNewRanges(); - return new TextEdit(this.edits.map((e, idx) => new SingleTextEdit(ranges[idx], doc.getValue(e.range)))); + return new TextEdit(this.edits.map((e, idx) => new SingleTextEdit(ranges[idx], doc.getValueOfRange(e.range)))); } getNewRanges(): Range[] { @@ -138,6 +154,10 @@ export class SingleTextEdit { ) { } + get isEmpty(): boolean { + return this.range.isEmpty() && this.text.length === 0; + } + static equals(first: SingleTextEdit, second: SingleTextEdit) { return first.range.equalsRange(second.range) && first.text === second.text; } @@ -150,18 +170,30 @@ function rangeFromPositions(start: Position, end: Position): Range { return new Range(start.lineNumber, start.column, end.lineNumber, end.column); } -export interface IText { - getValue(range: Range): string; - readonly endPositionExclusive: Position; +export abstract class AbstractText { + abstract getValueOfRange(range: Range): string; + abstract readonly length: RangeLength; + + get endPositionExclusive(): Position { + return this.length.addToPosition(new Position(1, 1)); + } + + getValue() { + return this.getValueOfRange(this.length.toRange()); + } } -export class LineBasedText implements IText { +export class LineBasedText extends AbstractText { constructor( private readonly _getLineContent: (lineNumber: number) => string, private readonly _lineCount: number, - ) { } + ) { + assert(_lineCount >= 1); - getValue(range: Range): string { + super(); + } + + getValueOfRange(range: Range): string { if (range.startLineNumber === range.endLineNumber) { return this._getLineContent(range.startLineNumber).substring(range.startColumn - 1, range.endColumn - 1); } @@ -173,22 +205,24 @@ export class LineBasedText implements IText { return result; } - get endPositionExclusive(): Position { + get length(): RangeLength { const lastLine = this._getLineContent(this._lineCount); - return new Position(this._lineCount, lastLine.length + 1); + return new RangeLength(this._lineCount - 1, lastLine.length); } } -export class StringText implements IText { - private readonly _t = new PositionOffsetTransformer(this.str); +export class StringText extends AbstractText { + private readonly _t = new PositionOffsetTransformer(this.value); - constructor(private readonly str: string) { } - - getValue(range: Range): string { - return this._t.getOffsetRange(range).substring(this.str); + constructor(public readonly value: string) { + super(); } - get endPositionExclusive(): Position { - return this._t.getPosition(this.str.length); + getValueOfRange(range: Range): string { + return this._t.getOffsetRange(range).substring(this.value); + } + + get length(): RangeLength { + return this._t.textLength; } } diff --git a/src/vs/editor/common/model/textModelText.ts b/src/vs/editor/common/model/textModelText.ts index 40433478d4b..36f73a63d67 100644 --- a/src/vs/editor/common/model/textModelText.ts +++ b/src/vs/editor/common/model/textModelText.ts @@ -3,21 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { IText } from 'vs/editor/common/core/textEdit'; +import { AbstractText } from 'vs/editor/common/core/textEdit'; +import { RangeLength } from 'vs/editor/common/core/rangeLength'; import { ITextModel } from 'vs/editor/common/model'; -export class TextModelText implements IText { - constructor(private readonly _textModel: ITextModel) { } +export class TextModelText extends AbstractText { + constructor(private readonly _textModel: ITextModel) { + super(); + } - getValue(range: Range): string { + getValueOfRange(range: Range): string { return this._textModel.getValueInRange(range); } - get endPositionExclusive(): Position { + get length(): RangeLength { const lastLineNumber = this._textModel.getLineCount(); const lastLineLen = this._textModel.getLineLength(lastLineNumber); - return new Position(lastLineNumber, lastLineLen + 1); + return new RangeLength(lastLineNumber - 1, lastLineLen); } } diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts index 50250883440..11c24b0b0e6 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts @@ -13,7 +13,6 @@ import { InlineCompletion, InlineCompletionContext, InlineCompletionsProvider } import { ITestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel'; import { autorun } from 'vs/base/common/observable'; -import { MersenneTwister } from 'vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test'; export class MockInlineCompletionsProvider implements InlineCompletionsProvider { private returnValue: InlineCompletion[] = []; diff --git a/src/vs/editor/test/common/core/random.ts b/src/vs/editor/test/common/core/random.ts new file mode 100644 index 00000000000..d48f4173f82 --- /dev/null +++ b/src/vs/editor/test/common/core/random.ts @@ -0,0 +1,113 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { numberComparator } from 'vs/base/common/arrays'; +import { OffsetRange } from 'vs/editor/common/core/offsetRange'; +import { Position } from 'vs/editor/common/core/position'; +import { PositionOffsetTransformer } from 'vs/editor/common/core/positionToOffset'; +import { Range } from 'vs/editor/common/core/range'; +import { AbstractText, SingleTextEdit, TextEdit } from 'vs/editor/common/core/textEdit'; + +export abstract class Random { + public static basicAlphabet: string = ' abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + public static basicAlphabetMultiline: string = ' \n\n\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + public static create(seed: number): Random { + return new MersenneTwister(seed); + } + + public abstract nextIntRange(start: number, endExclusive: number): number; + + public nextString(length: number, alphabet = Random.basicAlphabet): string { + let randomText: string = ''; + for (let i = 0; i < length; i++) { + const characterIndex = this.nextIntRange(0, alphabet.length); + randomText += alphabet.charAt(characterIndex); + } + return randomText; + } + + public nextMultiLineString(lineCount: number, lineLengthRange: OffsetRange, alphabet = Random.basicAlphabet): string { + const lines: string[] = []; + for (let i = 0; i < lineCount; i++) { + const lineLength = this.nextIntRange(lineLengthRange.start, lineLengthRange.endExclusive); + lines.push(this.nextString(lineLength, alphabet)); + } + return lines.join('\n'); + } + + public nextConsecutivePositions(source: AbstractText, count: number): Position[] { + const t = new PositionOffsetTransformer(source.getValue()); + const offsets = OffsetRange.ofLength(count).map(() => this.nextIntRange(0, t.text.length)); + offsets.sort(numberComparator); + return offsets.map(offset => t.getPosition(offset)); + } + + public nextRange(source: AbstractText): Range { + const [start, end] = this.nextConsecutivePositions(source, 2); + return Range.fromPositions(start, end); + } + + public nextTextEdit(target: AbstractText, singleTextEditCount: number): TextEdit { + const singleTextEdits: SingleTextEdit[] = []; + + const positions = this.nextConsecutivePositions(target, singleTextEditCount * 2); + + for (let i = 0; i < singleTextEditCount; i++) { + const start = positions[i * 2]; + const end = positions[i * 2 + 1]; + const newText = this.nextString(end.column - start.column, Random.basicAlphabetMultiline); + singleTextEdits.push(new SingleTextEdit(Range.fromPositions(start, end), newText)); + } + + return new TextEdit(singleTextEdits).normalize(); + } +} + +class MersenneTwister extends Random { + private readonly mt = new Array(624); + private index = 0; + + constructor(seed: number) { + super(); + + this.mt[0] = seed >>> 0; + for (let i = 1; i < 624; i++) { + const s = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30); + this.mt[i] = (((((s & 0xffff0000) >>> 16) * 0x6c078965) << 16) + (s & 0x0000ffff) * 0x6c078965 + i) >>> 0; + } + } + + private _nextInt() { + if (this.index === 0) { + this.generateNumbers(); + } + + let y = this.mt[this.index]; + y = y ^ (y >>> 11); + y = y ^ ((y << 7) & 0x9d2c5680); + y = y ^ ((y << 15) & 0xefc60000); + y = y ^ (y >>> 18); + + this.index = (this.index + 1) % 624; + + return y >>> 0; + } + + public nextIntRange(start: number, endExclusive: number) { + const range = endExclusive - start; + return Math.floor(this._nextInt() / (0x100000000 / range)) + start; + } + + private generateNumbers() { + for (let i = 0; i < 624; i++) { + const y = (this.mt[i] & 0x80000000) + (this.mt[(i + 1) % 624] & 0x7fffffff); + this.mt[i] = this.mt[(i + 397) % 624] ^ (y >>> 1); + if ((y % 2) !== 0) { + this.mt[i] = this.mt[i] ^ 0x9908b0df; + } + } + } +} diff --git a/src/vs/editor/test/common/core/textEdit.test.ts b/src/vs/editor/test/common/core/textEdit.test.ts index 1e9c342ebcd..f02e8a9bd50 100644 --- a/src/vs/editor/test/common/core/textEdit.test.ts +++ b/src/vs/editor/test/common/core/textEdit.test.ts @@ -5,32 +5,29 @@ import * as assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { createTextModel } from 'vs/editor/test/common/testTextModel'; -import { MersenneTwister, getRandomEditInfos, toEdit, } from 'vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test'; -import { TextEdit } from 'vs/editor/common/core/textEdit'; -import { TextModelText } from 'vs/editor/common/model/textModelText'; +import { OffsetRange } from 'vs/editor/common/core/offsetRange'; +import { StringText } from 'vs/editor/common/core/textEdit'; +import { Random } from 'vs/editor/test/common/core/random'; suite('TextEdit', () => { suite('inverse', () => { ensureNoDisposablesAreLeakedInTestSuite(); function runTest(seed: number): void { - const rng = new MersenneTwister(seed); - const randomText = generateRandomMultilineString(rng, 10); - const model = createTextModel(randomText); + const rand = Random.create(seed); + const source = new StringText(rand.nextMultiLineString(10, new OffsetRange(0, 10))); - const edits = new TextEdit(getRandomEditInfos(model, rng.nextIntRange(1, 4), rng, true).map(e => toEdit(e))); - const invEdits = edits.inverse(new TextModelText(model)); + const edit = rand.nextTextEdit(source, rand.nextIntRange(1, 5)); + const invEdit = edit.inverse(source); - model.applyEdits(edits.edits); - model.applyEdits(invEdits.edits); + const s1 = edit.apply(source); + const s2 = invEdit.applyToString(s1); - assert.deepStrictEqual(model.getValue(), randomText); - model.dispose(); + assert.deepStrictEqual(s2, source.value); } test.skip('brute-force', () => { - for (let i = 0; i < 10_000; i++) { + for (let i = 0; i < 100_000; i++) { runTest(i); } }); @@ -40,23 +37,3 @@ suite('TextEdit', () => { } }); }); - -function generateRandomMultilineString(rng: MersenneTwister, numberOfLines: number, maximumLengthOfLines: number = 20): string { - let randomText: string = ''; - for (let i = 0; i < numberOfLines; i++) { - const lengthOfLine = rng.nextIntRange(0, maximumLengthOfLines + 1); - randomText += generateRandomSimpleString(rng, lengthOfLine) + '\n'; - } - return randomText; -} - -function generateRandomSimpleString(rng: MersenneTwister, stringLength: number): string { - const possibleCharacters: string = ' abcdefghijklmnopqrstuvwxyz0123456789'; - let randomText: string = ''; - for (let i = 0; i < stringLength; i++) { - const characterIndex = rng.nextIntRange(0, possibleCharacters.length); - randomText += possibleCharacters.charAt(characterIndex); - - } - return randomText; -} diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts index 05f2063baf1..9155b32a9ca 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts @@ -11,6 +11,7 @@ import { TextEditInfo } from 'vs/editor/common/model/bracketPairsTextModelPart/b import { combineTextEditInfos } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/combineTextEditInfos'; import { lengthAdd, lengthToObj, lengthToPosition, positionToLength, toLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; import { TextModel } from 'vs/editor/common/model/textModel'; +import { Random } from 'vs/editor/test/common/core/random'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; suite('combineTextEditInfos', () => { @@ -24,7 +25,7 @@ suite('combineTextEditInfos', () => { }); function runTest(seed: number) { - const rng = new MersenneTwister(seed); + const rng = Random.create(seed); const str = 'abcde\nfghij\nklmno\npqrst\n'; const textModelS0 = createTextModel(str); @@ -57,7 +58,7 @@ function runTest(seed: number) { textModelS2.dispose(); } -export function getRandomEditInfos(textModel: TextModel, count: number, rng: MersenneTwister, disjoint: boolean = false): TextEditInfo[] { +export function getRandomEditInfos(textModel: TextModel, count: number, rng: Random, disjoint: boolean = false): TextEditInfo[] { const edits: TextEditInfo[] = []; let i = 0; for (let j = 0; j < count; j++) { @@ -67,7 +68,7 @@ export function getRandomEditInfos(textModel: TextModel, count: number, rng: Mer return edits; } -function getRandomEdit(textModel: TextModel, rangeOffsetStart: number, rng: MersenneTwister): TextEditInfo { +function getRandomEdit(textModel: TextModel, rangeOffsetStart: number, rng: Random): TextEditInfo { const textModelLength = textModel.getValueLength(); const offsetStart = rng.nextIntRange(rangeOffsetStart, textModelLength); const offsetEnd = rng.nextIntRange(offsetStart, textModelLength); @@ -78,7 +79,7 @@ function getRandomEdit(textModel: TextModel, rangeOffsetStart: number, rng: Mers return new TextEditInfo(positionToLength(textModel.getPositionAt(offsetStart)), positionToLength(textModel.getPositionAt(offsetEnd)), toLength(lineCount, columnCount)); } -export function toEdit(editInfo: TextEditInfo): SingleTextEdit { +function toEdit(editInfo: TextEditInfo): SingleTextEdit { const l = lengthToObj(editInfo.newLength); let text = ''; @@ -89,56 +90,11 @@ export function toEdit(editInfo: TextEditInfo): SingleTextEdit { text += 'C'; } - return { - range: Range.fromPositions( + return new SingleTextEdit( + Range.fromPositions( lengthToPosition(editInfo.startOffset), lengthToPosition(editInfo.endOffset) ), text - }; -} - -// Generated by copilot -export class MersenneTwister { - private readonly mt = new Array(624); - private index = 0; - - constructor(seed: number) { - this.mt[0] = seed >>> 0; - for (let i = 1; i < 624; i++) { - const s = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30); - this.mt[i] = (((((s & 0xffff0000) >>> 16) * 0x6c078965) << 16) + (s & 0x0000ffff) * 0x6c078965 + i) >>> 0; - } - } - - public nextInt() { - if (this.index === 0) { - this.generateNumbers(); - } - - let y = this.mt[this.index]; - y = y ^ (y >>> 11); - y = y ^ ((y << 7) & 0x9d2c5680); - y = y ^ ((y << 15) & 0xefc60000); - y = y ^ (y >>> 18); - - this.index = (this.index + 1) % 624; - - return y >>> 0; - } - - public nextIntRange(start: number, endExclusive: number) { - const range = endExclusive - start; - return Math.floor(this.nextInt() / (0x100000000 / range)) + start; - } - - private generateNumbers() { - for (let i = 0; i < 624; i++) { - const y = (this.mt[i] & 0x80000000) + (this.mt[(i + 1) % 624] & 0x7fffffff); - this.mt[i] = this.mt[(i + 397) % 624] ^ (y >>> 1); - if ((y % 2) !== 0) { - this.mt[i] = this.mt[i] ^ 0x9908b0df; - } - } - } + ); } From fb865521ceb96845e578c3f296663eacc1425921 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 8 Mar 2024 15:15:35 +0100 Subject: [PATCH 1876/1897] Renames RangeLength to TextLength --- .../editor/browser/widget/diffEditor/utils.ts | 8 ++-- src/vs/editor/common/core/positionToOffset.ts | 10 ++--- src/vs/editor/common/core/rangeMapping.ts | 4 +- src/vs/editor/common/core/textEdit.ts | 14 +++---- .../core/{rangeLength.ts => textLength.ts} | 38 +++++++++---------- .../beforeEditPositionMapper.ts | 10 ++--- .../bracketPairsTree/length.ts | 16 ++++---- src/vs/editor/common/model/textModelText.ts | 6 +-- .../browser/inlineCompletionsModel.ts | 4 +- .../browser/singleTextEdit.ts | 4 +- .../mergeEditor/browser/model/rangeUtils.ts | 16 ++++---- .../mergeEditor/browser/view/lineAlignment.ts | 6 +-- .../mergeEditor/test/browser/mapping.test.ts | 12 +++--- 13 files changed, 74 insertions(+), 74 deletions(-) rename src/vs/editor/common/core/{rangeLength.ts => textLength.ts} (70%) diff --git a/src/vs/editor/browser/widget/diffEditor/utils.ts b/src/vs/editor/browser/widget/diffEditor/utils.ts index ebe47afd423..a1e263948f2 100644 --- a/src/vs/editor/browser/widget/diffEditor/utils.ts +++ b/src/vs/editor/browser/widget/diffEditor/utils.ts @@ -15,7 +15,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; -import { RangeLength } from 'vs/editor/common/core/rangeLength'; +import { TextLength } from 'vs/editor/common/core/textLength'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyValue, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -425,11 +425,11 @@ export function translatePosition(posInOriginal: Position, mappings: DetailedLin } } -function lengthBetweenPositions(position1: Position, position2: Position): RangeLength { +function lengthBetweenPositions(position1: Position, position2: Position): TextLength { if (position1.lineNumber === position2.lineNumber) { - return new RangeLength(0, position2.column - position1.column); + return new TextLength(0, position2.column - position1.column); } else { - return new RangeLength(position2.lineNumber - position1.lineNumber, position2.column - 1); + return new TextLength(position2.lineNumber - position1.lineNumber, position2.column - 1); } } diff --git a/src/vs/editor/common/core/positionToOffset.ts b/src/vs/editor/common/core/positionToOffset.ts index e181b90173c..484c0a3265f 100644 --- a/src/vs/editor/common/core/positionToOffset.ts +++ b/src/vs/editor/common/core/positionToOffset.ts @@ -7,7 +7,7 @@ import { findLastIdxMonotonous } from 'vs/base/common/arraysFind'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { RangeLength } from 'vs/editor/common/core/rangeLength'; +import { TextLength } from 'vs/editor/common/core/textLength'; export class PositionOffsetTransformer { private readonly lineStartOffsetByLineIdx: number[]; @@ -47,12 +47,12 @@ export class PositionOffsetTransformer { ); } - getTextLength(offsetRange: OffsetRange): RangeLength { - return RangeLength.ofRange(this.getRange(offsetRange)); + getTextLength(offsetRange: OffsetRange): TextLength { + return TextLength.ofRange(this.getRange(offsetRange)); } - get textLength(): RangeLength { + get textLength(): TextLength { const lineIdx = this.lineStartOffsetByLineIdx.length - 1; - return new RangeLength(lineIdx, this.text.length - this.lineStartOffsetByLineIdx[lineIdx]); + return new TextLength(lineIdx, this.text.length - this.lineStartOffsetByLineIdx[lineIdx]); } } diff --git a/src/vs/editor/common/core/rangeMapping.ts b/src/vs/editor/common/core/rangeMapping.ts index 6e87efee7f7..379e046357d 100644 --- a/src/vs/editor/common/core/rangeMapping.ts +++ b/src/vs/editor/common/core/rangeMapping.ts @@ -6,7 +6,7 @@ import { findLastMonotonous } from 'vs/base/common/arraysFind'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { RangeLength } from 'vs/editor/common/core/rangeLength'; +import { TextLength } from 'vs/editor/common/core/textLength'; /** * Represents a list of mappings of ranges from one document to another. @@ -23,7 +23,7 @@ export class RangeMapping { if (mapping.original.containsPosition(position)) { return PositionOrRange.range(mapping.modified); } - const l = RangeLength.betweenPositions(mapping.original.getEndPosition(), position); + const l = TextLength.betweenPositions(mapping.original.getEndPosition(), position); return PositionOrRange.position(l.addToPosition(mapping.modified.getEndPosition())); } diff --git a/src/vs/editor/common/core/textEdit.ts b/src/vs/editor/common/core/textEdit.ts index ade6f7bf16a..e353361d953 100644 --- a/src/vs/editor/common/core/textEdit.ts +++ b/src/vs/editor/common/core/textEdit.ts @@ -8,7 +8,7 @@ import { BugIndicatingError } from 'vs/base/common/errors'; import { Position } from 'vs/editor/common/core/position'; import { PositionOffsetTransformer } from 'vs/editor/common/core/positionToOffset'; import { Range } from 'vs/editor/common/core/range'; -import { RangeLength } from 'vs/editor/common/core/rangeLength'; +import { TextLength } from 'vs/editor/common/core/textLength'; export class TextEdit { constructor(public readonly edits: readonly SingleTextEdit[]) { @@ -44,7 +44,7 @@ export class TextEdit { break; } - const len = RangeLength.ofText(edit.text); + const len = TextLength.ofText(edit.text); if (position.isBefore(end)) { const startPos = new Position(start.lineNumber + lineDelta, start.column + (start.lineNumber + lineDelta === curLine ? columnDeltaInCurLine : 0)); const endPos = len.addToPosition(startPos); @@ -132,7 +132,7 @@ export class TextEdit { let lineOffset = 0; let columnOffset = 0; for (const edit of this.edits) { - const textLength = RangeLength.ofText(edit.text); + const textLength = TextLength.ofText(edit.text); const newRangeStart = Position.lift({ lineNumber: edit.range.startLineNumber + lineOffset, column: edit.range.startColumn + (edit.range.startLineNumber === previousEditEndLineNumber ? columnOffset : 0) @@ -172,7 +172,7 @@ function rangeFromPositions(start: Position, end: Position): Range { export abstract class AbstractText { abstract getValueOfRange(range: Range): string; - abstract readonly length: RangeLength; + abstract readonly length: TextLength; get endPositionExclusive(): Position { return this.length.addToPosition(new Position(1, 1)); @@ -205,9 +205,9 @@ export class LineBasedText extends AbstractText { return result; } - get length(): RangeLength { + get length(): TextLength { const lastLine = this._getLineContent(this._lineCount); - return new RangeLength(this._lineCount - 1, lastLine.length); + return new TextLength(this._lineCount - 1, lastLine.length); } } @@ -222,7 +222,7 @@ export class StringText extends AbstractText { return this._t.getOffsetRange(range).substring(this.value); } - get length(): RangeLength { + get length(): TextLength { return this._t.textLength; } } diff --git a/src/vs/editor/common/core/rangeLength.ts b/src/vs/editor/common/core/textLength.ts similarity index 70% rename from src/vs/editor/common/core/rangeLength.ts rename to src/vs/editor/common/core/textLength.ts index d01fca41c1f..632895c55fd 100644 --- a/src/vs/editor/common/core/rangeLength.ts +++ b/src/vs/editor/common/core/textLength.ts @@ -8,33 +8,33 @@ import { Range } from 'vs/editor/common/core/range'; /** * Represents a non-negative length of text in terms of line and column count. */ -export class RangeLength { - public static zero = new RangeLength(0, 0); +export class TextLength { + public static zero = new TextLength(0, 0); - public static lengthDiffNonNegative(start: RangeLength, end: RangeLength): RangeLength { + public static lengthDiffNonNegative(start: TextLength, end: TextLength): TextLength { if (end.isLessThan(start)) { - return RangeLength.zero; + return TextLength.zero; } if (start.lineCount === end.lineCount) { - return new RangeLength(0, end.columnCount - start.columnCount); + return new TextLength(0, end.columnCount - start.columnCount); } else { - return new RangeLength(end.lineCount - start.lineCount, end.columnCount); + return new TextLength(end.lineCount - start.lineCount, end.columnCount); } } - public static betweenPositions(position1: Position, position2: Position): RangeLength { + public static betweenPositions(position1: Position, position2: Position): TextLength { if (position1.lineNumber === position2.lineNumber) { - return new RangeLength(0, position2.column - position1.column); + return new TextLength(0, position2.column - position1.column); } else { - return new RangeLength(position2.lineNumber - position1.lineNumber, position2.column - 1); + return new TextLength(position2.lineNumber - position1.lineNumber, position2.column - 1); } } public static ofRange(range: Range) { - return RangeLength.betweenPositions(range.getStartPosition(), range.getEndPosition()); + return TextLength.betweenPositions(range.getStartPosition(), range.getEndPosition()); } - public static ofText(text: string): RangeLength { + public static ofText(text: string): TextLength { let line = 0; let column = 0; for (const c of text) { @@ -45,7 +45,7 @@ export class RangeLength { column++; } } - return new RangeLength(line, column); + return new TextLength(line, column); } constructor( @@ -57,36 +57,36 @@ export class RangeLength { return this.lineCount === 0 && this.columnCount === 0; } - public isLessThan(other: RangeLength): boolean { + public isLessThan(other: TextLength): boolean { if (this.lineCount !== other.lineCount) { return this.lineCount < other.lineCount; } return this.columnCount < other.columnCount; } - public isGreaterThan(other: RangeLength): boolean { + public isGreaterThan(other: TextLength): boolean { if (this.lineCount !== other.lineCount) { return this.lineCount > other.lineCount; } return this.columnCount > other.columnCount; } - public equals(other: RangeLength): boolean { + public equals(other: TextLength): boolean { return this.lineCount === other.lineCount && this.columnCount === other.columnCount; } - public compare(other: RangeLength): number { + public compare(other: TextLength): number { if (this.lineCount !== other.lineCount) { return this.lineCount - other.lineCount; } return this.columnCount - other.columnCount; } - public add(other: RangeLength): RangeLength { + public add(other: TextLength): TextLength { if (other.lineCount === 0) { - return new RangeLength(this.lineCount, this.columnCount + other.columnCount); + return new TextLength(this.lineCount, this.columnCount + other.columnCount); } else { - return new RangeLength(this.lineCount + other.lineCount, other.columnCount); + return new TextLength(this.lineCount + other.lineCount, other.columnCount); } } diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper.ts index e596c00e983..1f95f84df48 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper.ts @@ -5,7 +5,7 @@ import { Range } from 'vs/editor/common/core/range'; import { Length, lengthAdd, lengthDiffNonNegative, lengthLessThanEqual, lengthOfString, lengthToObj, positionToLength, toLength } from './length'; -import { RangeLength } from 'vs/editor/common/core/rangeLength'; +import { TextLength } from 'vs/editor/common/core/textLength'; import { IModelContentChange } from 'vs/editor/common/textModelEvents'; export class TextEditInfo { @@ -74,7 +74,7 @@ export class BeforeEditPositionMapper { return lengthDiffNonNegative(offset, nextChangeOffset); } - private translateOldToCur(oldOffsetObj: RangeLength): Length { + private translateOldToCur(oldOffsetObj: TextLength): Length { if (oldOffsetObj.lineCount === this.deltaLineIdxInOld) { return toLength(oldOffsetObj.lineCount + this.deltaOldToNewLineCount, oldOffsetObj.columnCount + this.deltaOldToNewColumnCount); } else { @@ -127,9 +127,9 @@ class TextEditInfoCache { return new TextEditInfoCache(edit.startOffset, edit.endOffset, edit.newLength); } - public readonly endOffsetBeforeObj: RangeLength; - public readonly endOffsetAfterObj: RangeLength; - public readonly offsetObj: RangeLength; + public readonly endOffsetBeforeObj: TextLength; + public readonly endOffsetAfterObj: TextLength; + public readonly offsetObj: TextLength; constructor( startOffset: Length, diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts index 0ff10813857..d41a62233e5 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts @@ -6,7 +6,7 @@ import { splitLines } from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { RangeLength } from 'vs/editor/common/core/rangeLength'; +import { TextLength } from 'vs/editor/common/core/textLength'; /** * The end must be greater than or equal to the start. @@ -49,11 +49,11 @@ export function toLength(lineCount: number, columnCount: number): Length { return (lineCount * factor + columnCount) as any as Length; } -export function lengthToObj(length: Length): RangeLength { +export function lengthToObj(length: Length): TextLength { const l = length as any as number; const lineCount = Math.floor(l / factor); const columnCount = l - lineCount * factor; - return new RangeLength(lineCount, columnCount); + return new TextLength(lineCount, columnCount); } export function lengthGetLineCount(length: Length): number { @@ -148,11 +148,11 @@ export function lengthsToRange(lengthStart: Length, lengthEnd: Length): Range { return new Range(lineCount + 1, colCount + 1, lineCount2 + 1, colCount2 + 1); } -export function lengthOfRange(range: Range): RangeLength { +export function lengthOfRange(range: Range): TextLength { if (range.startLineNumber === range.endLineNumber) { - return new RangeLength(0, range.endColumn - range.startColumn); + return new TextLength(0, range.endColumn - range.startColumn); } else { - return new RangeLength(range.endLineNumber - range.startLineNumber, range.endColumn - 1); + return new TextLength(range.endLineNumber - range.startLineNumber, range.endColumn - 1); } } @@ -167,9 +167,9 @@ export function lengthOfString(str: string): Length { return toLength(lines.length - 1, lines[lines.length - 1].length); } -export function lengthOfStringObj(str: string): RangeLength { +export function lengthOfStringObj(str: string): TextLength { const lines = splitLines(str); - return new RangeLength(lines.length - 1, lines[lines.length - 1].length); + return new TextLength(lines.length - 1, lines[lines.length - 1].length); } /** diff --git a/src/vs/editor/common/model/textModelText.ts b/src/vs/editor/common/model/textModelText.ts index 36f73a63d67..0a603fa1ed2 100644 --- a/src/vs/editor/common/model/textModelText.ts +++ b/src/vs/editor/common/model/textModelText.ts @@ -5,7 +5,7 @@ import { Range } from 'vs/editor/common/core/range'; import { AbstractText } from 'vs/editor/common/core/textEdit'; -import { RangeLength } from 'vs/editor/common/core/rangeLength'; +import { TextLength } from 'vs/editor/common/core/textLength'; import { ITextModel } from 'vs/editor/common/model'; export class TextModelText extends AbstractText { @@ -17,9 +17,9 @@ export class TextModelText extends AbstractText { return this._textModel.getValueInRange(range); } - get length(): RangeLength { + get length(): TextLength { const lastLineNumber = this._textModel.getLineCount(); const lastLineLen = this._textModel.getLineLength(lastLineNumber); - return new RangeLength(lastLineNumber - 1, lastLineLen); + return new TextLength(lastLineNumber - 1, lastLineLen); } } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 57357fd47e1..996471bde48 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -28,7 +28,7 @@ import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetCon import { ICommandService } from 'vs/platform/commands/common/commands'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { singleTextEditAugments, computeGhostText, singleTextRemoveCommonPrefix } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; -import { RangeLength } from 'vs/editor/common/core/rangeLength'; +import { TextLength } from 'vs/editor/common/core/textLength'; export enum VersionIdChangeReason { Undo, @@ -444,7 +444,7 @@ export class InlineCompletionsModel extends Disposable { } if (completion.source.provider.handlePartialAccept) { - const acceptedRange = Range.fromPositions(completion.range.getStartPosition(), RangeLength.ofText(partialGhostTextVal).addToPosition(ghostTextPos)); + const acceptedRange = Range.fromPositions(completion.range.getStartPosition(), TextLength.ofText(partialGhostTextVal).addToPosition(ghostTextPos)); // This assumes that the inline completion and the model use the same EOL style. const text = editor.getModel()!.getValueInRange(acceptedRange, EndOfLinePreference.LF); completion.source.provider.handlePartialAccept( diff --git a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts index d38d1e41f57..750eb459829 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts @@ -7,7 +7,7 @@ import { IDiffChange, LcsDiff } from 'vs/base/common/diff/diff'; import { commonPrefixLength, getLeadingWhitespace } from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { RangeLength } from 'vs/editor/common/core/rangeLength'; +import { TextLength } from 'vs/editor/common/core/textLength'; import { SingleTextEdit } from 'vs/editor/common/core/textEdit'; import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { GhostText, GhostTextPart } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; @@ -19,7 +19,7 @@ export function singleTextRemoveCommonPrefix(edit: SingleTextEdit, model: ITextM } const valueToReplace = model.getValueInRange(modelRange, EndOfLinePreference.LF); const commonPrefixLen = commonPrefixLength(valueToReplace, edit.text); - const start = RangeLength.ofText(valueToReplace.substring(0, commonPrefixLen)).addToPosition(edit.range.getStartPosition()); + const start = TextLength.ofText(valueToReplace.substring(0, commonPrefixLen)).addToPosition(edit.range.getStartPosition()); const text = edit.text.substring(commonPrefixLen); const range = Range.fromPositions(start, edit.range.getEndPosition()); return new SingleTextEdit(range, text); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/rangeUtils.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/rangeUtils.ts index fef5aaad5d7..dd224530145 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/rangeUtils.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/rangeUtils.ts @@ -5,7 +5,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { RangeLength } from 'vs/editor/common/core/rangeLength'; +import { TextLength } from 'vs/editor/common/core/textLength'; export function rangeContainsPosition(range: Range, position: Position): boolean { if (position.lineNumber < range.startLineNumber || position.lineNumber > range.endLineNumber) { @@ -20,23 +20,23 @@ export function rangeContainsPosition(range: Range, position: Position): boolean return true; } -export function lengthOfRange(range: Range): RangeLength { +export function lengthOfRange(range: Range): TextLength { if (range.startLineNumber === range.endLineNumber) { - return new RangeLength(0, range.endColumn - range.startColumn); + return new TextLength(0, range.endColumn - range.startColumn); } else { - return new RangeLength(range.endLineNumber - range.startLineNumber, range.endColumn - 1); + return new TextLength(range.endLineNumber - range.startLineNumber, range.endColumn - 1); } } -export function lengthBetweenPositions(position1: Position, position2: Position): RangeLength { +export function lengthBetweenPositions(position1: Position, position2: Position): TextLength { if (position1.lineNumber === position2.lineNumber) { - return new RangeLength(0, position2.column - position1.column); + return new TextLength(0, position2.column - position1.column); } else { - return new RangeLength(position2.lineNumber - position1.lineNumber, position2.column - 1); + return new TextLength(position2.lineNumber - position1.lineNumber, position2.column - 1); } } -export function addLength(position: Position, length: RangeLength): Position { +export function addLength(position: Position, length: TextLength): Position { if (length.lineCount === 0) { return new Position(position.lineNumber, position.column + length.columnCount); } else { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/lineAlignment.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/lineAlignment.ts index 6d4f28beb1e..c6d9664b4be 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/lineAlignment.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/lineAlignment.ts @@ -8,7 +8,7 @@ import { assertFn, checkAdjacentItems } from 'vs/base/common/assert'; import { isDefined } from 'vs/base/common/types'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { RangeLength } from 'vs/editor/common/core/rangeLength'; +import { TextLength } from 'vs/editor/common/core/textLength'; import { RangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; import { ModifiedBaseRange } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange'; import { addLength, lengthBetweenPositions, lengthOfRange } from 'vs/workbench/contrib/mergeEditor/browser/model/rangeUtils'; @@ -49,7 +49,7 @@ export function getAlignments(m: ModifiedBaseRange): LineAlignment[] { if (shouldAdd) { result.push(lineAlignment); } else { - if (m.length.isGreaterThan(new RangeLength(1, 0))) { + if (m.length.isGreaterThan(new TextLength(1, 0))) { result.push([ m.output1Pos ? m.output1Pos.lineNumber + 1 : undefined, m.inputPos.lineNumber + 1, @@ -75,7 +75,7 @@ interface CommonRangeMapping { output1Pos: Position | undefined; output2Pos: Position | undefined; inputPos: Position; - length: RangeLength; + length: TextLength; } function toEqualRangeMappings(diffs: RangeMapping[], inputRange: Range, outputRange: Range): RangeMapping[] { diff --git a/src/vs/workbench/contrib/mergeEditor/test/browser/mapping.test.ts b/src/vs/workbench/contrib/mergeEditor/test/browser/mapping.test.ts index 9d824d4c7fa..85c475e3c2f 100644 --- a/src/vs/workbench/contrib/mergeEditor/test/browser/mapping.test.ts +++ b/src/vs/workbench/contrib/mergeEditor/test/browser/mapping.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { RangeLength } from 'vs/editor/common/core/rangeLength'; +import { TextLength } from 'vs/editor/common/core/textLength'; import { DocumentRangeMap, RangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; suite('merge editor mapping', () => { @@ -53,19 +53,19 @@ function parsePos(str: string): Position { return new Position(parseInt(lineCount, 10), parseInt(columnCount, 10)); } -function parseLengthObj(str: string): RangeLength { +function parseLengthObj(str: string): TextLength { const [lineCount, columnCount] = str.split(':'); - return new RangeLength(parseInt(lineCount, 10), parseInt(columnCount, 10)); + return new TextLength(parseInt(lineCount, 10), parseInt(columnCount, 10)); } -function toPosition(length: RangeLength): Position { +function toPosition(length: TextLength): Position { return new Position(length.lineCount + 1, length.columnCount + 1); } function createDocumentRangeMap(items: ([string, string] | string)[]) { const mappings: RangeMapping[] = []; - let lastLen1 = new RangeLength(0, 0); - let lastLen2 = new RangeLength(0, 0); + let lastLen1 = new TextLength(0, 0); + let lastLen2 = new TextLength(0, 0); for (const item of items) { if (typeof item === 'string') { const len = parseLengthObj(item); From 16064ae9ebee0722d9f9ad65e3496dc6b5d85837 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 8 Mar 2024 12:30:52 -0300 Subject: [PATCH 1877/1897] Allow empty 'values' array for variables (#207168) Allow an empty 'values' array for variables --- src/vs/workbench/contrib/chat/browser/chatVariables.ts | 4 +--- src/vscode-dts/vscode.proposed.chatParticipant.d.ts | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatVariables.ts b/src/vs/workbench/contrib/chat/browser/chatVariables.ts index 147b267245c..3de4fb0c209 100644 --- a/src/vs/workbench/contrib/chat/browser/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/chatVariables.ts @@ -40,9 +40,7 @@ export class ChatVariablesService implements IChatVariablesService { const data = this._resolver.get(part.variableName.toLowerCase()); if (data) { jobs.push(data.resolver(prompt.text, part.variableArg, model, progress, token).then(values => { - if (values?.length) { - resolvedVariables[i] = { name: part.variableName, range: part.range, values }; - } + resolvedVariables[i] = { name: part.variableName, range: part.range, values: values ?? [] }; }).catch(onUnexpectedExternalError)); } } else if (part instanceof ChatRequestDynamicVariablePart) { diff --git a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts index f3b59ac9e84..0f872032de2 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -266,6 +266,9 @@ declare module 'vscode' { readonly range: [start: number, end: number]; // TODO@API decouple of resolve API, use `value: string | Uri | (maybe) unknown?` + /** + * The values of the variable. Can be an empty array if the variable doesn't currently have a value. + */ readonly values: ChatVariableValue[]; } From a9ab31da9cfa2d45bac4bda59a980cc701d497e5 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 8 Mar 2024 13:59:12 +0100 Subject: [PATCH 1878/1897] Refactors: Reduces assumptions about line height. This contains the parts of https://github.com/microsoft/vscode/pull/194609 that we can already merge. While it does not implement dynamic line heights, it is a first step in that direction. Co-authored-by: Remco Haszing --- src/vs/editor/browser/view/viewLayer.ts | 10 +++--- src/vs/editor/browser/view/viewOverlays.ts | 30 +++++----------- .../currentLineHighlight.css | 6 +++- .../currentLineHighlight.ts | 7 ++-- .../viewParts/decorations/decorations.css | 3 +- .../viewParts/decorations/decorations.ts | 23 +++++-------- .../viewParts/indentGuides/indentGuides.css | 1 + .../viewParts/indentGuides/indentGuides.ts | 6 +--- .../viewParts/lineNumbers/lineNumbers.css | 2 +- .../browser/viewParts/lines/viewLine.ts | 8 ++--- .../browser/viewParts/lines/viewLines.css | 5 +++ .../viewParts/selections/selections.ts | 34 +++++++------------ .../viewParts/whitespace/whitespace.ts | 2 +- .../browser/widget/codeEditor/editor.css | 9 +++++ .../editor/common/viewLayout/linesLayout.ts | 3 +- .../viewLayout/viewLinesViewportData.ts | 3 ++ src/vs/editor/common/viewModel.ts | 5 +++ 17 files changed, 75 insertions(+), 82 deletions(-) diff --git a/src/vs/editor/browser/view/viewLayer.ts b/src/vs/editor/browser/view/viewLayer.ts index c15239ec8b1..bbbb0dd9d73 100644 --- a/src/vs/editor/browser/view/viewLayer.ts +++ b/src/vs/editor/browser/view/viewLayer.ts @@ -22,12 +22,12 @@ export interface IVisibleLine extends ILine { * Return null if the HTML should not be touched. * Return the new HTML otherwise. */ - renderLine(lineNumber: number, deltaTop: number, viewportData: ViewportData, sb: StringBuilder): boolean; + renderLine(lineNumber: number, deltaTop: number, lineHeight: number, viewportData: ViewportData, sb: StringBuilder): boolean; /** * Layout the line. */ - layoutLine(lineNumber: number, deltaTop: number): void; + layoutLine(lineNumber: number, deltaTop: number, lineHeight: number): void; } export interface ILine { @@ -465,7 +465,7 @@ class ViewLayerRenderer { for (let i = startIndex; i <= endIndex; i++) { const lineNumber = rendLineNumberStart + i; - lines[i].layoutLine(lineNumber, deltaTop[lineNumber - deltaLN]); + lines[i].layoutLine(lineNumber, deltaTop[lineNumber - deltaLN], this.viewportData.lineHeight); } } @@ -573,7 +573,7 @@ class ViewLayerRenderer { continue; } - const renderResult = line.renderLine(i + rendLineNumberStart, deltaTop[i], this.viewportData, sb); + const renderResult = line.renderLine(i + rendLineNumberStart, deltaTop[i], this.viewportData.lineHeight, this.viewportData, sb); if (!renderResult) { // line does not need rendering continue; @@ -603,7 +603,7 @@ class ViewLayerRenderer { continue; } - const renderResult = line.renderLine(i + rendLineNumberStart, deltaTop[i], this.viewportData, sb); + const renderResult = line.renderLine(i + rendLineNumberStart, deltaTop[i], this.viewportData.lineHeight, this.viewportData, sb); if (!renderResult) { // line does not need rendering continue; diff --git a/src/vs/editor/browser/view/viewOverlays.ts b/src/vs/editor/browser/view/viewOverlays.ts index 83a3cc05d6f..1041fd58a58 100644 --- a/src/vs/editor/browser/view/viewOverlays.ts +++ b/src/vs/editor/browser/view/viewOverlays.ts @@ -9,7 +9,6 @@ import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay'; import { IVisibleLine, IVisibleLinesHost, VisibleLinesCollection } from 'vs/editor/browser/view/viewLayer'; import { ViewPart } from 'vs/editor/browser/view/viewPart'; import { StringBuilder } from 'vs/editor/common/core/stringBuilder'; -import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration'; import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext'; import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; import * as viewEvents from 'vs/editor/common/viewEvents'; @@ -71,7 +70,7 @@ export class ViewOverlays extends ViewPart implements IVisibleLinesHost | null; private _renderedContent: string | null; - private _lineHeight: number; - constructor(configuration: IEditorConfiguration, dynamicOverlays: DynamicViewOverlay[]) { - this._configuration = configuration; - this._lineHeight = this._configuration.options.get(EditorOption.lineHeight); + constructor(dynamicOverlays: DynamicViewOverlay[]) { this._dynamicOverlays = dynamicOverlays; this._domNode = null; @@ -180,11 +169,8 @@ export class ViewOverlayLine implements IVisibleLine { public onTokensChanged(): void { // Nothing } - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): void { - this._lineHeight = this._configuration.options.get(EditorOption.lineHeight); - } - public renderLine(lineNumber: number, deltaTop: number, viewportData: ViewportData, sb: StringBuilder): boolean { + public renderLine(lineNumber: number, deltaTop: number, lineHeight: number, viewportData: ViewportData, sb: StringBuilder): boolean { let result = ''; for (let i = 0, len = this._dynamicOverlays.length; i < len; i++) { const dynamicOverlay = this._dynamicOverlays[i]; @@ -198,10 +184,10 @@ export class ViewOverlayLine implements IVisibleLine { this._renderedContent = result; - sb.appendString('
      '); sb.appendString(result); sb.appendString('
      '); @@ -209,10 +195,10 @@ export class ViewOverlayLine implements IVisibleLine { return true; } - public layoutLine(lineNumber: number, deltaTop: number): void { + public layoutLine(lineNumber: number, deltaTop: number, lineHeight: number): void { if (this._domNode) { this._domNode.setTop(deltaTop); - this._domNode.setHeight(this._lineHeight); + this._domNode.setHeight(lineHeight); } } } diff --git a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.css b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.css index 2a0e39dffa7..403e255fac8 100644 --- a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.css +++ b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.css @@ -9,6 +9,7 @@ left: 0; top: 0; box-sizing: border-box; + height: 100%; } .monaco-editor .margin-view-overlays .current-line { @@ -17,8 +18,11 @@ left: 0; top: 0; box-sizing: border-box; + height: 100%; } -.monaco-editor .margin-view-overlays .current-line.current-line-margin.current-line-margin-both { +.monaco-editor + .margin-view-overlays + .current-line.current-line-margin.current-line-margin-both { border-right: 0; } diff --git a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts index 64649e0b835..b35970ee373 100644 --- a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts +++ b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts @@ -18,7 +18,6 @@ import { Position } from 'vs/editor/common/core/position'; export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay { private readonly _context: ViewContext; - protected _lineHeight: number; protected _renderLineHighlight: 'none' | 'gutter' | 'line' | 'all'; protected _wordWrap: boolean; protected _contentLeft: number; @@ -39,7 +38,6 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay { const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); - this._lineHeight = options.get(EditorOption.lineHeight); this._renderLineHighlight = options.get(EditorOption.renderLineHighlight); this._renderLineHighlightOnlyWhenFocus = options.get(EditorOption.renderLineHighlightOnlyWhenFocus); this._wordWrap = layoutInfo.isViewportWrapping; @@ -89,7 +87,6 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay { public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); - this._lineHeight = options.get(EditorOption.lineHeight); this._renderLineHighlight = options.get(EditorOption.renderLineHighlight); this._renderLineHighlightOnlyWhenFocus = options.get(EditorOption.renderLineHighlightOnlyWhenFocus); this._wordWrap = layoutInfo.isViewportWrapping; @@ -208,7 +205,7 @@ export class CurrentLineHighlightOverlay extends AbstractLineHighlightOverlay { protected _renderOne(ctx: RenderingContext, exact: boolean): string { const className = 'current-line' + (this._shouldRenderInMargin() ? ' current-line-both' : '') + (exact ? ' current-line-exact' : ''); - return `
      `; + return `
      `; } protected _shouldRenderThis(): boolean { return this._shouldRenderInContent(); @@ -221,7 +218,7 @@ export class CurrentLineHighlightOverlay extends AbstractLineHighlightOverlay { export class CurrentLineMarginHighlightOverlay extends AbstractLineHighlightOverlay { protected _renderOne(ctx: RenderingContext, exact: boolean): string { const className = 'current-line' + (this._shouldRenderInMargin() ? ' current-line-margin' : '') + (this._shouldRenderOther() ? ' current-line-margin-both' : '') + (this._shouldRenderInMargin() && exact ? ' current-line-exact-margin' : ''); - return `
      `; + return `
      `; } protected _shouldRenderThis(): boolean { return true; diff --git a/src/vs/editor/browser/viewParts/decorations/decorations.css b/src/vs/editor/browser/viewParts/decorations/decorations.css index 37c39f620e8..4c755e2dbf8 100644 --- a/src/vs/editor/browser/viewParts/decorations/decorations.css +++ b/src/vs/editor/browser/viewParts/decorations/decorations.css @@ -9,4 +9,5 @@ */ .monaco-editor .lines-content .cdr { position: absolute; -} \ No newline at end of file + height: 100%; +} diff --git a/src/vs/editor/browser/viewParts/decorations/decorations.ts b/src/vs/editor/browser/viewParts/decorations/decorations.ts index fe495466b1d..a3baa510464 100644 --- a/src/vs/editor/browser/viewParts/decorations/decorations.ts +++ b/src/vs/editor/browser/viewParts/decorations/decorations.ts @@ -15,7 +15,6 @@ import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; export class DecorationsOverlay extends DynamicViewOverlay { private readonly _context: ViewContext; - private _lineHeight: number; private _typicalHalfwidthCharacterWidth: number; private _renderResult: string[] | null; @@ -23,7 +22,6 @@ export class DecorationsOverlay extends DynamicViewOverlay { super(); this._context = context; const options = this._context.configuration.options; - this._lineHeight = options.get(EditorOption.lineHeight); this._typicalHalfwidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; this._renderResult = null; @@ -40,7 +38,6 @@ export class DecorationsOverlay extends DynamicViewOverlay { public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; - this._lineHeight = options.get(EditorOption.lineHeight); this._typicalHalfwidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; return true; } @@ -116,7 +113,6 @@ export class DecorationsOverlay extends DynamicViewOverlay { } private _renderWholeLineDecorations(ctx: RenderingContext, decorations: ViewModelDecoration[], output: string[]): void { - const lineHeight = String(this._lineHeight); const visibleStartLineNumber = ctx.visibleRange.startLineNumber; const visibleEndLineNumber = ctx.visibleRange.endLineNumber; @@ -130,9 +126,7 @@ export class DecorationsOverlay extends DynamicViewOverlay { const decorationOutput = ( '
      ' + + '" style="left:0;width:100%;">' ); const startLineNumber = Math.max(d.range.startLineNumber, visibleStartLineNumber); @@ -145,7 +139,6 @@ export class DecorationsOverlay extends DynamicViewOverlay { } private _renderNormalDecorations(ctx: RenderingContext, decorations: ViewModelDecoration[], output: string[]): void { - const lineHeight = String(this._lineHeight); const visibleStartLineNumber = ctx.visibleRange.startLineNumber; let prevClassName: string | null = null; @@ -176,7 +169,7 @@ export class DecorationsOverlay extends DynamicViewOverlay { // flush previous decoration if (prevClassName !== null) { - this._renderNormalDecoration(ctx, prevRange!, prevClassName, prevShouldFillLineOnLineBreak, prevShowIfCollapsed, lineHeight, visibleStartLineNumber, output); + this._renderNormalDecoration(ctx, prevRange!, prevClassName, prevShouldFillLineOnLineBreak, prevShowIfCollapsed, visibleStartLineNumber, output); } prevClassName = className; @@ -186,11 +179,11 @@ export class DecorationsOverlay extends DynamicViewOverlay { } if (prevClassName !== null) { - this._renderNormalDecoration(ctx, prevRange!, prevClassName, prevShouldFillLineOnLineBreak, prevShowIfCollapsed, lineHeight, visibleStartLineNumber, output); + this._renderNormalDecoration(ctx, prevRange!, prevClassName, prevShouldFillLineOnLineBreak, prevShowIfCollapsed, visibleStartLineNumber, output); } } - private _renderNormalDecoration(ctx: RenderingContext, range: Range, className: string, shouldFillLineOnLineBreak: boolean, showIfCollapsed: boolean, lineHeight: string, visibleStartLineNumber: number, output: string[]): void { + private _renderNormalDecoration(ctx: RenderingContext, range: Range, className: string, shouldFillLineOnLineBreak: boolean, showIfCollapsed: boolean, visibleStartLineNumber: number, output: string[]): void { const linesVisibleRanges = ctx.linesVisibleRangesForRange(range, /*TODO@Alex*/className === 'findMatch'); if (!linesVisibleRanges) { return; @@ -222,12 +215,12 @@ export class DecorationsOverlay extends DynamicViewOverlay { + className + '" style="left:' + String(visibleRange.left) + + 'px;width:' + (expandToLeft ? - 'px;width:100%;height:' : - ('px;width:' + String(visibleRange.width) + 'px;height:') + '100%;' : + (String(visibleRange.width) + 'px;') ) - + lineHeight - + 'px;">' + + '">' ); output[lineIndex] += decorationOutput; } diff --git a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.css b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.css index ed132669757..6aacf7c2126 100644 --- a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.css +++ b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.css @@ -6,4 +6,5 @@ .monaco-editor .lines-content .core-guide { position: absolute; box-sizing: border-box; + height: 100%; } diff --git a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts index a93cf75a530..50b0b2b8661 100644 --- a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts +++ b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts @@ -22,7 +22,6 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { private readonly _context: ViewContext; private _primaryPosition: Position | null; - private _lineHeight: number; private _spaceWidth: number; private _renderResult: string[] | null; private _maxIndentLeft: number; @@ -37,7 +36,6 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { const wrappingInfo = options.get(EditorOption.wrappingInfo); const fontInfo = options.get(EditorOption.fontInfo); - this._lineHeight = options.get(EditorOption.lineHeight); this._spaceWidth = fontInfo.spaceWidth; this._maxIndentLeft = wrappingInfo.wrappingColumn === -1 ? -1 : (wrappingInfo.wrappingColumn * fontInfo.typicalHalfwidthCharacterWidth); this._bracketPairGuideOptions = options.get(EditorOption.guides); @@ -60,7 +58,6 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { const wrappingInfo = options.get(EditorOption.wrappingInfo); const fontInfo = options.get(EditorOption.fontInfo); - this._lineHeight = options.get(EditorOption.lineHeight); this._spaceWidth = fontInfo.spaceWidth; this._maxIndentLeft = wrappingInfo.wrappingColumn === -1 ? -1 : (wrappingInfo.wrappingColumn * fontInfo.typicalHalfwidthCharacterWidth); this._bracketPairGuideOptions = options.get(EditorOption.guides); @@ -114,7 +111,6 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { const visibleStartLineNumber = ctx.visibleRange.startLineNumber; const visibleEndLineNumber = ctx.visibleRange.endLineNumber; const scrollWidth = ctx.scrollWidth; - const lineHeight = this._lineHeight; const activeCursorPosition = this._primaryPosition; @@ -150,7 +146,7 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { )?.left ?? (left + this._spaceWidth)) - left : this._spaceWidth; - result += `
      `; + result += `
      `; } output[lineIndex] = result; } diff --git a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.css b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.css index 774ffef273d..2961137b032 100644 --- a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.css +++ b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.css @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ .monaco-editor .margin-view-overlays .line-numbers { + bottom: 0; font-variant-numeric: tabular-nums; position: absolute; text-align: right; @@ -11,7 +12,6 @@ vertical-align: middle; box-sizing: border-box; cursor: default; - height: 100%; } .monaco-editor .relative-current-line-number { diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index e4174a2f286..9a5d2f556bf 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -151,7 +151,7 @@ export class ViewLine implements IVisibleLine { return false; } - public renderLine(lineNumber: number, deltaTop: number, viewportData: ViewportData, sb: StringBuilder): boolean { + public renderLine(lineNumber: number, deltaTop: number, lineHeight: number, viewportData: ViewportData, sb: StringBuilder): boolean { if (this._isMaybeInvalid === false) { // it appears that nothing relevant has changed return false; @@ -222,7 +222,7 @@ export class ViewLine implements IVisibleLine { sb.appendString('
      '); @@ -255,10 +255,10 @@ export class ViewLine implements IVisibleLine { return true; } - public layoutLine(lineNumber: number, deltaTop: number): void { + public layoutLine(lineNumber: number, deltaTop: number, lineHeight: number): void { if (this._renderedViewLine && this._renderedViewLine.domNode) { this._renderedViewLine.domNode.setTop(deltaTop); - this._renderedViewLine.domNode.setHeight(this._options.lineHeight); + this._renderedViewLine.domNode.setHeight(lineHeight); } } diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.css b/src/vs/editor/browser/viewParts/lines/viewLines.css index fe686d3e441..5bb02a5338c 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.css +++ b/src/vs/editor/browser/viewParts/lines/viewLines.css @@ -63,6 +63,11 @@ width: 100%; } +.monaco-editor .view-line > span { + bottom: 0; + position: absolute; +} + .monaco-editor .mtkw { color: var(--vscode-editorWhitespace-foreground) !important; } diff --git a/src/vs/editor/browser/viewParts/selections/selections.ts b/src/vs/editor/browser/viewParts/selections/selections.ts index efceef0e5c3..d53a5126e62 100644 --- a/src/vs/editor/browser/viewParts/selections/selections.ts +++ b/src/vs/editor/browser/viewParts/selections/selections.ts @@ -68,7 +68,6 @@ export class SelectionsOverlay extends DynamicViewOverlay { private static readonly ROUNDED_PIECE_WIDTH = 10; private readonly _context: ViewContext; - private _lineHeight: number; private _roundedSelection: boolean; private _typicalHalfwidthCharacterWidth: number; private _selections: Range[]; @@ -78,7 +77,6 @@ export class SelectionsOverlay extends DynamicViewOverlay { super(); this._context = context; const options = this._context.configuration.options; - this._lineHeight = options.get(EditorOption.lineHeight); this._roundedSelection = options.get(EditorOption.roundedSelection); this._typicalHalfwidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; this._selections = []; @@ -96,7 +94,6 @@ export class SelectionsOverlay extends DynamicViewOverlay { public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; - this._lineHeight = options.get(EditorOption.lineHeight); this._roundedSelection = options.get(EditorOption.roundedSelection); this._typicalHalfwidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; return true; @@ -255,19 +252,16 @@ export class SelectionsOverlay extends DynamicViewOverlay { return linesVisibleRanges; } - private _createSelectionPiece(top: number, height: string, className: string, left: number, width: number): string { + private _createSelectionPiece(top: number, bottom: number, className: string, left: number, width: number): string { return ( '
      ' + + '" style="' + + 'top:' + top.toString() + 'px;' + + 'bottom:' + bottom.toString() + 'px;' + + 'left:' + left.toString() + 'px;' + + 'width:' + width.toString() + 'px;' + + '">
      ' ); } @@ -277,8 +271,6 @@ export class SelectionsOverlay extends DynamicViewOverlay { } const visibleRangesHaveStyle = !!visibleRanges[0].ranges[0].startStyle; - const fullLineHeight = (this._lineHeight).toString(); - const reducedLineHeight = (this._lineHeight - 1).toString(); const firstLineNumber = visibleRanges[0].lineNumber; const lastLineNumber = visibleRanges[visibleRanges.length - 1].lineNumber; @@ -288,8 +280,8 @@ export class SelectionsOverlay extends DynamicViewOverlay { const lineNumber = lineVisibleRanges.lineNumber; const lineIndex = lineNumber - visibleStartLineNumber; - const lineHeight = hasMultipleSelections ? (lineNumber === lastLineNumber || lineNumber === firstLineNumber ? reducedLineHeight : fullLineHeight) : fullLineHeight; const top = hasMultipleSelections ? (lineNumber === firstLineNumber ? 1 : 0) : 0; + const bottom = hasMultipleSelections ? (lineNumber !== firstLineNumber && lineNumber === lastLineNumber ? 1 : 0) : 0; let innerCornerOutput = ''; let restOfSelectionOutput = ''; @@ -304,7 +296,7 @@ export class SelectionsOverlay extends DynamicViewOverlay { // Reverse rounded corner to the left // First comes the selection (blue layer) - innerCornerOutput += this._createSelectionPiece(top, lineHeight, SelectionsOverlay.SELECTION_CLASS_NAME, visibleRange.left - SelectionsOverlay.ROUNDED_PIECE_WIDTH, SelectionsOverlay.ROUNDED_PIECE_WIDTH); + innerCornerOutput += this._createSelectionPiece(top, bottom, SelectionsOverlay.SELECTION_CLASS_NAME, visibleRange.left - SelectionsOverlay.ROUNDED_PIECE_WIDTH, SelectionsOverlay.ROUNDED_PIECE_WIDTH); // Second comes the background (white layer) with inverse border radius let className = SelectionsOverlay.EDITOR_BACKGROUND_CLASS_NAME; @@ -314,13 +306,13 @@ export class SelectionsOverlay extends DynamicViewOverlay { if (startStyle.bottom === CornerStyle.INTERN) { className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_RIGHT; } - innerCornerOutput += this._createSelectionPiece(top, lineHeight, className, visibleRange.left - SelectionsOverlay.ROUNDED_PIECE_WIDTH, SelectionsOverlay.ROUNDED_PIECE_WIDTH); + innerCornerOutput += this._createSelectionPiece(top, bottom, className, visibleRange.left - SelectionsOverlay.ROUNDED_PIECE_WIDTH, SelectionsOverlay.ROUNDED_PIECE_WIDTH); } if (endStyle.top === CornerStyle.INTERN || endStyle.bottom === CornerStyle.INTERN) { // Reverse rounded corner to the right // First comes the selection (blue layer) - innerCornerOutput += this._createSelectionPiece(top, lineHeight, SelectionsOverlay.SELECTION_CLASS_NAME, visibleRange.left + visibleRange.width, SelectionsOverlay.ROUNDED_PIECE_WIDTH); + innerCornerOutput += this._createSelectionPiece(top, bottom, SelectionsOverlay.SELECTION_CLASS_NAME, visibleRange.left + visibleRange.width, SelectionsOverlay.ROUNDED_PIECE_WIDTH); // Second comes the background (white layer) with inverse border radius let className = SelectionsOverlay.EDITOR_BACKGROUND_CLASS_NAME; @@ -330,7 +322,7 @@ export class SelectionsOverlay extends DynamicViewOverlay { if (endStyle.bottom === CornerStyle.INTERN) { className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_LEFT; } - innerCornerOutput += this._createSelectionPiece(top, lineHeight, className, visibleRange.left + visibleRange.width, SelectionsOverlay.ROUNDED_PIECE_WIDTH); + innerCornerOutput += this._createSelectionPiece(top, bottom, className, visibleRange.left + visibleRange.width, SelectionsOverlay.ROUNDED_PIECE_WIDTH); } } @@ -351,7 +343,7 @@ export class SelectionsOverlay extends DynamicViewOverlay { className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_RIGHT; } } - restOfSelectionOutput += this._createSelectionPiece(top, lineHeight, className, visibleRange.left, visibleRange.width); + restOfSelectionOutput += this._createSelectionPiece(top, bottom, className, visibleRange.left, visibleRange.width); } output2[lineIndex][0] += innerCornerOutput; diff --git a/src/vs/editor/browser/viewParts/whitespace/whitespace.ts b/src/vs/editor/browser/viewParts/whitespace/whitespace.ts index 489293a01b8..3bd29fc5e1e 100644 --- a/src/vs/editor/browser/viewParts/whitespace/whitespace.ts +++ b/src/vs/editor/browser/viewParts/whitespace/whitespace.ts @@ -235,7 +235,7 @@ export class WhitespaceOverlay extends DynamicViewOverlay { if (USE_SVG) { maxLeft = Math.round(maxLeft + spaceWidth); return ( - `` + `` + result + `` ); diff --git a/src/vs/editor/browser/widget/codeEditor/editor.css b/src/vs/editor/browser/widget/codeEditor/editor.css index 1d60940158a..09c4a32f141 100644 --- a/src/vs/editor/browser/widget/codeEditor/editor.css +++ b/src/vs/editor/browser/widget/codeEditor/editor.css @@ -56,6 +56,15 @@ top: 0; } +.monaco-editor .view-overlays > div, .monaco-editor .margin-view-overlays > div { + position: absolute; + width: 100%; +} + +.monaco-editor .view-overlays > div > div, .monaco-editor .margin-view-overlays > div > div { + bottom: 0; +} + /* .monaco-editor .auto-closed-character { opacity: 0.3; diff --git a/src/vs/editor/common/viewLayout/linesLayout.ts b/src/vs/editor/common/viewLayout/linesLayout.ts index 7bb55aeef6e..71bf9d5b956 100644 --- a/src/vs/editor/common/viewLayout/linesLayout.ts +++ b/src/vs/editor/common/viewLayout/linesLayout.ts @@ -727,7 +727,8 @@ export class LinesLayout { relativeVerticalOffset: linesOffsets, centeredLineNumber: centeredLineNumber, completelyVisibleStartLineNumber: completelyVisibleStartLineNumber, - completelyVisibleEndLineNumber: completelyVisibleEndLineNumber + completelyVisibleEndLineNumber: completelyVisibleEndLineNumber, + lineHeight: this._lineHeight, }; } diff --git a/src/vs/editor/common/viewLayout/viewLinesViewportData.ts b/src/vs/editor/common/viewLayout/viewLinesViewportData.ts index 8ddcfddb99d..6e072c52648 100644 --- a/src/vs/editor/common/viewLayout/viewLinesViewportData.ts +++ b/src/vs/editor/common/viewLayout/viewLinesViewportData.ts @@ -46,6 +46,8 @@ export class ViewportData { private readonly _model: IViewModel; + public readonly lineHeight: number; + constructor( selections: Selection[], partialData: IPartialViewLinesViewportData, @@ -57,6 +59,7 @@ export class ViewportData { this.endLineNumber = partialData.endLineNumber | 0; this.relativeVerticalOffset = partialData.relativeVerticalOffset; this.bigNumbersDelta = partialData.bigNumbersDelta | 0; + this.lineHeight = partialData.lineHeight | 0; this.whitespaceViewportData = whitespaceViewportData; this._model = model; diff --git a/src/vs/editor/common/viewModel.ts b/src/vs/editor/common/viewModel.ts index 4f92417e89b..29a01bcf904 100644 --- a/src/vs/editor/common/viewModel.ts +++ b/src/vs/editor/common/viewModel.ts @@ -181,6 +181,11 @@ export interface IPartialViewLinesViewportData { * The last completely visible line number. */ readonly completelyVisibleEndLineNumber: number; + + /** + * The height of a line. + */ + readonly lineHeight: number; } export interface IViewWhitespaceViewportData { From 967170aa0a0f746338dbd080b4a0a04a7a67a7fb Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:41:40 +0100 Subject: [PATCH 1879/1897] Git - add the capability to filter git log based on the author (#207169) --- extensions/git/src/api/git.d.ts | 1 + extensions/git/src/git.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 6a53d893352..cf462435a2c 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -143,6 +143,7 @@ export interface LogOptions { readonly reverse?: boolean; readonly sortByAuthorDate?: boolean; readonly shortStats?: boolean; + readonly author?: string; } export interface CommitOptions { diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 738295bdec1..710d7a4d110 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1161,6 +1161,10 @@ export class Repository { args.push(`-n${options?.maxEntries ?? 32}`); } + if (options?.author) { + args.push(`--author="${options.author}"`); + } + if (options?.path) { args.push('--', options.path); } From cb79a8f9228fd08081f8050522b864c31f683fe5 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 8 Mar 2024 16:58:47 +0100 Subject: [PATCH 1880/1897] watcher - allow non-existent paths when correlating (#207145) --- src/vs/platform/files/common/files.ts | 8 - src/vs/platform/files/common/watcher.ts | 2 +- .../files/node/watcher/baseWatcher.ts | 173 ++++++++++++++++++ .../files/node/watcher/nodejs/nodejsClient.ts | 2 +- .../node/watcher/nodejs/nodejsWatcher.ts | 33 ++-- .../node/watcher/parcel/parcelWatcher.ts | 37 ++-- .../node/nodejsWatcher.integrationTest.ts | 76 +++++++- .../node/parcelWatcher.integrationTest.ts | 58 +++++- 8 files changed, 340 insertions(+), 49 deletions(-) create mode 100644 src/vs/platform/files/node/watcher/baseWatcher.ts diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 0bc285f082e..e8bcce418a8 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -243,14 +243,6 @@ export interface IFileService { */ createWatcher(resource: URI, options: IWatchOptionsWithoutCorrelation): IFileSystemWatcher; - /** - * Allows to start a watcher that reports file/folder change events on the provided resource. - * - * The watcher runs correlated and thus, file events will be reported on the returned - * `IFileSystemWatcher` and not on the generic `IFileService.onDidFilesChange` event. - */ - watch(resource: URI, options: IWatchOptionsWithCorrelation): IFileSystemWatcher; - /** * Allows to start a watcher that reports file/folder change events on the provided resource. * diff --git a/src/vs/platform/files/common/watcher.ts b/src/vs/platform/files/common/watcher.ts index ae97f833078..95b3b3363f6 100644 --- a/src/vs/platform/files/common/watcher.ts +++ b/src/vs/platform/files/common/watcher.ts @@ -71,7 +71,7 @@ export function isRecursiveWatchRequest(request: IWatchRequest): request is IRec export type IUniversalWatchRequest = IRecursiveWatchRequest | INonRecursiveWatchRequest; -interface IWatcher { +export interface IWatcher { /** * A normalized file change event from the raw events diff --git a/src/vs/platform/files/node/watcher/baseWatcher.ts b/src/vs/platform/files/node/watcher/baseWatcher.ts new file mode 100644 index 00000000000..845d7bbe7c8 --- /dev/null +++ b/src/vs/platform/files/node/watcher/baseWatcher.ts @@ -0,0 +1,173 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { watchFile, unwatchFile, Stats } from 'fs'; +import { Disposable, DisposableMap, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ILogMessage, IUniversalWatchRequest, IWatcher } from 'vs/platform/files/common/watcher'; +import { Emitter, Event } from 'vs/base/common/event'; +import { FileChangeType, IFileChange } from 'vs/platform/files/common/files'; +import { URI } from 'vs/base/common/uri'; + +export abstract class BaseWatcher extends Disposable implements IWatcher { + + protected readonly _onDidChangeFile = this._register(new Emitter()); + readonly onDidChangeFile = this._onDidChangeFile.event; + + protected readonly _onDidLogMessage = this._register(new Emitter()); + readonly onDidLogMessage = this._onDidLogMessage.event; + + private mapWatchMissingRequestPathToCorrelationId = this._register(new DisposableMap()); + + private allWatchRequests = new Set(); + private suspendedWatchRequests = new Set(); + + protected readonly missingRequestPathPollingInterval: number | undefined; + + async watch(requests: IUniversalWatchRequest[]): Promise { + this.allWatchRequests = new Set([...requests]); + + const correlationIds = new Set(); + for (const request of requests) { + + // Request with correlation: watch request path to support + // watching paths that do not exist yet or are potentially + // being deleted and recreated. + // + // We are not doing this for all watch requests yet to see + // how it goes, thus its limitd to correlated requests. + + if (typeof request.correlationId === 'number') { + correlationIds.add(request.correlationId); + + if (!this.mapWatchMissingRequestPathToCorrelationId.has(request.correlationId)) { + this.mapWatchMissingRequestPathToCorrelationId.set(request.correlationId, this.watchMissingRequestPath(request)); + } + } + } + + // Remove all watched correlated paths that are no longer + // needed because the request is no longer there + for (const [correlationId] of this.mapWatchMissingRequestPathToCorrelationId) { + if (!correlationIds.has(correlationId)) { + this.mapWatchMissingRequestPathToCorrelationId.deleteAndDispose(correlationId); + } + } + + // Remove all suspended requests that are no longer needed + for (const request of this.suspendedWatchRequests) { + if (!this.allWatchRequests.has(request)) { + this.suspendedWatchRequests.delete(request); + } + } + + return this.updateWatchers(); + } + + private updateWatchers(): Promise { + return this.doWatch(Array.from(this.allWatchRequests).filter(request => !this.suspendedWatchRequests.has(request))); + } + + private watchMissingRequestPath(request: IUniversalWatchRequest): IDisposable { + if (typeof request.correlationId !== 'number') { + return Disposable.None; // for now limit this to correlated watch requests only (reduces surface) + } + + const that = this; + const resource = URI.file(request.path); + + let disposed = false; + let pathNotFound = false; + + const watchFileCallback: (curr: Stats, prev: Stats) => void = (curr, prev) => { + if (disposed) { + return; // return early if already disposed + } + + const currentPathNotFound = this.isPathNotFound(curr); + const previousPathNotFound = this.isPathNotFound(prev); + const oldPathNotFound = pathNotFound; + pathNotFound = currentPathNotFound; + + // Watch path created: resume watching request + if ( + (previousPathNotFound && !currentPathNotFound) || // file was created + (oldPathNotFound && !currentPathNotFound && !previousPathNotFound) // file was created from a rename + ) { + this.trace(`fs.watchFile() detected ${request.path} exists again, resuming watcher (correlationId: ${request.correlationId})`); + + // Emit as event + const event: IFileChange = { resource, type: FileChangeType.ADDED, cId: request.correlationId }; + that._onDidChangeFile.fire([event]); + this.traceEvent(event, request); + + this.suspendedWatchRequests.delete(request); + this.updateWatchers(); + } + + // Watch path deleted or never existed: suspend watching request + else if (currentPathNotFound) { + this.trace(`fs.watchFile() detected ${request.path} not found, suspending watcher (correlationId: ${request.correlationId})`); + + if (!previousPathNotFound) { + const event: IFileChange = { resource, type: FileChangeType.DELETED, cId: request.correlationId }; + that._onDidChangeFile.fire([event]); + this.traceEvent(event, request); + } + + this.suspendedWatchRequests.add(request); + this.updateWatchers(); + } + }; + + this.trace(`starting fs.watchFile() on ${request.path} (correlationId: ${request.correlationId})`); + try { + watchFile(request.path, { persistent: false, interval: this.missingRequestPathPollingInterval }, watchFileCallback); + } catch (error) { + this.warn(`fs.watchFile() failed with error ${error} on path ${request.path} (correlationId: ${request.correlationId})`); + + return Disposable.None; + } + + return toDisposable(() => { + this.trace(`stopping fs.watchFile() on ${request.path} (correlationId: ${request.correlationId})`); + + disposed = true; + + this.suspendedWatchRequests.delete(request); + + try { + unwatchFile(request.path, watchFileCallback); + } catch (error) { + this.warn(`fs.unwatchFile() failed with error ${error} on path ${request.path} (correlationId: ${request.correlationId})`); + } + }); + } + + private isPathNotFound(stats: Stats): boolean { + return stats.ctimeMs === 0 && stats.ino === 0; + } + + async stop(): Promise { + this.mapWatchMissingRequestPathToCorrelationId.clearAndDisposeAll(); + this.suspendedWatchRequests.clear(); + } + + protected shouldRestartWatching(request: IUniversalWatchRequest): boolean { + return typeof request.correlationId !== 'number'; + } + + protected traceEvent(event: IFileChange, request: IUniversalWatchRequest): void { + const traceMsg = ` >> normalized ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.resource.fsPath}`; + this.trace(typeof request.correlationId === 'number' ? `${traceMsg} (correlationId: ${request.correlationId})` : traceMsg); + } + + protected abstract doWatch(requests: IUniversalWatchRequest[]): Promise; + + protected abstract trace(message: string): void; + protected abstract warn(message: string): void; + + abstract onDidError: Event; + abstract setVerboseLogging(enabled: boolean): Promise; +} diff --git a/src/vs/platform/files/node/watcher/nodejs/nodejsClient.ts b/src/vs/platform/files/node/watcher/nodejs/nodejsClient.ts index 11eb6b8a109..2a662eb7e05 100644 --- a/src/vs/platform/files/node/watcher/nodejs/nodejsClient.ts +++ b/src/vs/platform/files/node/watcher/nodejs/nodejsClient.ts @@ -21,6 +21,6 @@ export class NodeJSWatcherClient extends AbstractNonRecursiveWatcherClient { } protected override createWatcher(disposables: DisposableStore): INonRecursiveWatcher { - return disposables.add(new NodeJSWatcher()); + return disposables.add(new NodeJSWatcher()) satisfies INonRecursiveWatcher; } } diff --git a/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts b/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts index dac55a138c5..1cbbe49f025 100644 --- a/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts +++ b/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts @@ -3,12 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event, Emitter } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { patternsEquals } from 'vs/base/common/glob'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { BaseWatcher } from 'vs/platform/files/node/watcher/baseWatcher'; import { isLinux } from 'vs/base/common/platform'; -import { IFileChange } from 'vs/platform/files/common/files'; -import { ILogMessage, INonRecursiveWatchRequest, INonRecursiveWatcher } from 'vs/platform/files/common/watcher'; +import { INonRecursiveWatchRequest, INonRecursiveWatcher } from 'vs/platform/files/common/watcher'; import { NodeJSFileWatcherLibrary } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcherLib'; export interface INodeJSWatcherInstance { @@ -24,13 +23,7 @@ export interface INodeJSWatcherInstance { readonly request: INonRecursiveWatchRequest; } -export class NodeJSWatcher extends Disposable implements INonRecursiveWatcher { - - private readonly _onDidChangeFile = this._register(new Emitter()); - readonly onDidChangeFile = this._onDidChangeFile.event; - - private readonly _onDidLogMessage = this._register(new Emitter()); - readonly onDidLogMessage = this._onDidLogMessage.event; +export class NodeJSWatcher extends BaseWatcher implements INonRecursiveWatcher { readonly onDidError = Event.None; @@ -38,7 +31,7 @@ export class NodeJSWatcher extends Disposable implements INonRecursiveWatcher { private verboseLogging = false; - async watch(requests: INonRecursiveWatchRequest[]): Promise { + protected override async doWatch(requests: INonRecursiveWatchRequest[]): Promise { // Figure out duplicates to remove from the requests const normalizedRequests = this.normalizeRequests(requests); @@ -90,7 +83,9 @@ export class NodeJSWatcher extends Disposable implements INonRecursiveWatcher { this.watchers.set(request.path, watcher); } - async stop(): Promise { + override async stop(): Promise { + await super.stop(); + for (const [path] of this.watchers) { this.stopWatching(path); } @@ -134,13 +129,19 @@ export class NodeJSWatcher extends Disposable implements INonRecursiveWatcher { } } - private trace(message: string): void { + protected trace(message: string): void { if (this.verboseLogging) { this._onDidLogMessage.fire({ type: 'trace', message: this.toMessage(message) }); } } - private toMessage(message: string, watcher?: INodeJSWatcherInstance): string { - return watcher ? `[File Watcher (node.js)] ${message} (path: ${watcher.request.path})` : `[File Watcher (node.js)] ${message}`; + protected warn(message: string): void { + if (this.verboseLogging) { + this._onDidLogMessage.fire({ type: 'warn', message: this.toMessage(message) }); + } + } + + private toMessage(message: string): string { + return `[File Watcher (node.js)] ${message}`; } } diff --git a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts index c51b58db863..8a4b6404da2 100644 --- a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts +++ b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts @@ -13,7 +13,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; import { randomPath } from 'vs/base/common/extpath'; import { GLOBSTAR, ParsedPattern, patternsEquals } from 'vs/base/common/glob'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { BaseWatcher } from 'vs/platform/files/node/watcher/baseWatcher'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { normalizeNFC } from 'vs/base/common/normalization'; import { dirname, normalize } from 'vs/base/common/path'; @@ -21,7 +21,7 @@ import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { realcaseSync, realpathSync } from 'vs/base/node/extpath'; import { NodeJSFileWatcherLibrary } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcherLib'; import { FileChangeType, IFileChange } from 'vs/platform/files/common/files'; -import { ILogMessage, coalesceEvents, IRecursiveWatchRequest, IRecursiveWatcher, parseWatcherPatterns } from 'vs/platform/files/common/watcher'; +import { coalesceEvents, IRecursiveWatchRequest, IRecursiveWatcher, parseWatcherPatterns } from 'vs/platform/files/common/watcher'; export interface IParcelWatcherInstance { @@ -58,7 +58,7 @@ export interface IParcelWatcherInstance { stop(): Promise; } -export class ParcelWatcher extends Disposable implements IRecursiveWatcher { +export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcher { private static readonly MAP_PARCEL_WATCHER_ACTION_TO_FILE_CHANGE = new Map( [ @@ -70,12 +70,6 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { private static readonly PARCEL_WATCHER_BACKEND = isWindows ? 'windows' : isLinux ? 'inotify' : 'fs-events'; - private readonly _onDidChangeFile = this._register(new Emitter()); - readonly onDidChangeFile = this._onDidChangeFile.event; - - private readonly _onDidLogMessage = this._register(new Emitter()); - readonly onDidLogMessage = this._onDidLogMessage.event; - private readonly _onDidError = this._register(new Emitter()); readonly onDidError = this._onDidError.event; @@ -120,7 +114,7 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { process.on('unhandledRejection', error => this.onUnexpectedError(error)); } - async watch(requests: IRecursiveWatchRequest[]): Promise { + protected override async doWatch(requests: IRecursiveWatchRequest[]): Promise { // Figure out duplicates to remove from the requests const normalizedRequests = this.normalizeRequests(requests); @@ -370,8 +364,7 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { // Logging if (this.verboseLogging) { for (const event of events) { - const traceMsg = ` >> normalized ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.resource.fsPath}`; - this.trace(typeof watcher.request.correlationId === 'number' ? `${traceMsg} (correlationId: ${watcher.request.correlationId})` : traceMsg); + this.traceEvent(event, watcher.request); } } @@ -383,7 +376,7 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { this.warn(`started ignoring events due to too many file change events at once (incoming: ${events.length}, most recent change: ${events[0].resource.fsPath}). Use 'files.watcherExclude' setting to exclude folders with lots of changing files (e.g. compilation output).`); } else { if (this.throttledFileChangesEmitter.pending > 0) { - this.trace(`started throttling events due to large amount of file change events at once (pending: ${this.throttledFileChangesEmitter.pending}, most recent change: ${events[0].resource.fsPath}). Use 'files.watcherExclude' setting to exclude folders with lots of changing files (e.g. compilation output).`); + this.trace(`started throttling events due to large amount of file change events at once (pending: ${this.throttledFileChangesEmitter.pending}, most recent change: ${events[0].resource.fsPath}). Use 'files.watcherExclude' setting to exclude folders with lots of changing files (e.g. compilation output).`, watcher); } } } @@ -466,8 +459,14 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { private onWatchedPathDeleted(watcher: IParcelWatcherInstance): void { this.warn('Watcher shutdown because watched path got deleted', watcher); + if (!this.shouldRestartWatching(watcher.request)) { + return; // return if this deletion is handled outside + } + const parentPath = dirname(watcher.request.path); if (existsSync(parentPath)) { + this.trace('Trying to watch on the parent path to restart the watcher...', watcher); + const nodeWatcher = new NodeJSFileWatcherLibrary({ path: parentPath, excludes: [], recursive: false, correlationId: watcher.request.correlationId }, changes => { if (watcher.token.isCancellationRequested) { return; // return early when disposed @@ -522,7 +521,9 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { } } - async stop(): Promise { + override async stop(): Promise { + await super.stop(); + for (const [path] of this.watchers) { await this.stopWatching(path); } @@ -559,7 +560,7 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { private async stopWatching(path: string): Promise { const watcher = this.watchers.get(path); if (watcher) { - this.trace(`stopping file watcher on ${watcher.request.path}`); + this.trace(`stopping file watcher`, watcher); this.watchers.delete(path); @@ -664,13 +665,13 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { this.verboseLogging = enabled; } - private trace(message: string) { + protected trace(message: string, watcher?: IParcelWatcherInstance): void { if (this.verboseLogging) { - this._onDidLogMessage.fire({ type: 'trace', message: this.toMessage(message) }); + this._onDidLogMessage.fire({ type: 'trace', message: this.toMessage(message, watcher) }); } } - private warn(message: string, watcher?: IParcelWatcherInstance) { + protected warn(message: string, watcher?: IParcelWatcherInstance) { this._onDidLogMessage.fire({ type: 'warn', message: this.toMessage(message, watcher) }); } diff --git a/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts b/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts index 74dbb343c97..28e9ded4c54 100644 --- a/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts +++ b/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts @@ -20,6 +20,7 @@ import { FileAccess } from 'vs/base/common/network'; import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { addUNCHostToAllowlist } from 'vs/base/node/unc'; +import { Emitter, Event } from 'vs/base/common/event'; // this suite has shown flaky runs in Azure pipelines where // tasks would just hang and timeout after a while (not in @@ -30,9 +31,16 @@ import { addUNCHostToAllowlist } from 'vs/base/node/unc'; class TestNodeJSWatcher extends NodeJSWatcher { - override async watch(requests: INonRecursiveWatchRequest[]): Promise { - await super.watch(requests); + protected override readonly missingRequestPathPollingInterval = 100; + + private readonly _onDidWatch = this._register(new Emitter()); + readonly onDidWatch = this._onDidWatch.event; + + protected override async doWatch(requests: INonRecursiveWatchRequest[]): Promise { + await super.doWatch(requests); await this.whenReady(); + + this._onDidWatch.fire(); } async whenReady(): Promise { @@ -432,7 +440,7 @@ import { addUNCHostToAllowlist } from 'vs/base/node/unc'; return basicCrudTest(join(link, 'newFile.txt')); }); - async function basicCrudTest(filePath: string, skipAdd?: boolean, correlationId?: number | null, expectedCount?: number): Promise { + async function basicCrudTest(filePath: string, skipAdd?: boolean, correlationId?: number | null, expectedCount?: number, awaitWatchAfterAdd?: boolean): Promise { let changeFuture: Promise; // New file @@ -440,6 +448,9 @@ import { addUNCHostToAllowlist } from 'vs/base/node/unc'; changeFuture = awaitEvent(watcher, filePath, FileChangeType.ADDED, correlationId, expectedCount); await Promises.writeFile(filePath, 'Hello World'); await changeFuture; + if (awaitWatchAfterAdd) { + await Event.toPromise(watcher.onDidWatch); + } } // Change file @@ -559,4 +570,63 @@ import { addUNCHostToAllowlist } from 'vs/base/node/unc'; await basicCrudTest(join(testDir, 'newFile.txt'), undefined, null, 3); await basicCrudTest(join(testDir, 'otherNewFile.txt'), undefined, null, 3); }); + + test('correlated watch requests support suspend/resume (file, does not exist in beginning)', async function () { + const filePath = join(testDir, 'not-found.txt'); + await watcher.watch([{ path: filePath, excludes: [], recursive: false, correlationId: 1 }]); + + await basicCrudTest(filePath, undefined, 1, undefined, true); + await basicCrudTest(filePath, undefined, 1, undefined, true); + }); + + test('correlated watch requests support suspend/resume (file, exists in beginning)', async function () { + const filePath = join(testDir, 'lorem.txt'); + await watcher.watch([{ path: filePath, excludes: [], recursive: false, correlationId: 1 }]); + + await basicCrudTest(filePath, true, 1); + await basicCrudTest(filePath, undefined, 1, undefined, true); + }); + + test('correlated watch requests support suspend/resume (folder, does not exist in beginning)', async function () { + const folderPath = join(testDir, 'not-found'); + await watcher.watch([{ path: folderPath, excludes: [], recursive: false, correlationId: 1 }]); + + let changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED, 1); + await Promises.mkdir(folderPath); + await changeFuture; + await Event.toPromise(watcher.onDidWatch); + + const filePath = join(folderPath, 'newFile.txt'); + await basicCrudTest(filePath, undefined, 1); + + changeFuture = awaitEvent(watcher, folderPath, FileChangeType.DELETED, 1); + await Promises.rmdir(folderPath); + await changeFuture; + + changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED, 1); + await Promises.mkdir(folderPath); + await changeFuture; + await Event.toPromise(watcher.onDidWatch); + + await basicCrudTest(filePath, undefined, 1); + }); + + test('correlated watch requests support suspend/resume (folder, exists in beginning)', async function () { + const folderPath = join(testDir, 'deep'); + await watcher.watch([{ path: folderPath, excludes: [], recursive: false, correlationId: 1 }]); + + const filePath = join(folderPath, 'newFile.txt'); + await basicCrudTest(filePath, undefined, 1); + + let changeFuture = awaitEvent(watcher, folderPath, FileChangeType.DELETED, 1); + await Promises.rm(folderPath); + await changeFuture; + + changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED, 1); + await Promises.mkdir(folderPath); + await changeFuture; + await Event.toPromise(watcher.onDidWatch); + + await basicCrudTest(filePath, undefined, 1); + }); }); diff --git a/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts b/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts index 42e8b4ad730..d01abbc9cf1 100644 --- a/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts +++ b/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts @@ -20,6 +20,7 @@ import { FileAccess } from 'vs/base/common/network'; import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { addUNCHostToAllowlist } from 'vs/base/node/unc'; +import { Emitter, Event } from 'vs/base/common/event'; // this suite has shown flaky runs in Azure pipelines where // tasks would just hang and timeout after a while (not in @@ -30,6 +31,11 @@ import { addUNCHostToAllowlist } from 'vs/base/node/unc'; class TestParcelWatcher extends ParcelWatcher { + protected override readonly missingRequestPathPollingInterval = 100; + + private readonly _onDidWatch = this._register(new Emitter()); + readonly onDidWatch = this._onDidWatch.event; + testNormalizePaths(paths: string[], excludes: string[] = []): string[] { // Work with strings as paths to simplify testing @@ -40,9 +46,11 @@ import { addUNCHostToAllowlist } from 'vs/base/node/unc'; return this.normalizeRequests(requests, false /* validate paths skipped for tests */).map(request => request.path); } - override async watch(requests: IRecursiveWatchRequest[]): Promise { - await super.watch(requests); + protected override async doWatch(requests: IRecursiveWatchRequest[]): Promise { + await super.doWatch(requests); await this.whenReady(); + + this._onDidWatch.fire(); } async whenReady(): Promise { @@ -646,4 +654,50 @@ import { addUNCHostToAllowlist } from 'vs/base/node/unc'; await basicCrudTest(join(testDir, 'deep', 'newFile.txt'), null, 3); await basicCrudTest(join(testDir, 'deep', 'otherNewFile.txt'), null, 3); }); + + test('correlated watch requests support suspend/resume (folder, does not exist in beginning)', async () => { + const folderPath = join(testDir, 'not-found'); + await watcher.watch([{ path: folderPath, excludes: [], recursive: true, correlationId: 1 }]); + + let changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED, undefined, 1); + let onDidWatch = Event.toPromise(watcher.onDidWatch); + await Promises.mkdir(folderPath); + await changeFuture; + await onDidWatch; + + await basicCrudTest(join(folderPath, 'newFile.txt'), 1); + + changeFuture = awaitEvent(watcher, folderPath, FileChangeType.DELETED, undefined, 1); + onDidWatch = Event.toPromise(watcher.onDidWatch); + await Promises.rm(folderPath); + await changeFuture; + await onDidWatch; + + changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED, undefined, 1); + onDidWatch = Event.toPromise(watcher.onDidWatch); + await Promises.mkdir(folderPath); + await changeFuture; + await onDidWatch; + + await basicCrudTest(join(folderPath, 'newFile.txt'), 1); + }); + + test('correlated watch requests support suspend/resume (folder, exist in beginning)', async () => { + const folderPath = join(testDir, 'deep'); + await watcher.watch([{ path: folderPath, excludes: [], recursive: true, correlationId: 1 }]); + + let changeFuture = awaitEvent(watcher, folderPath, FileChangeType.DELETED, undefined, 1); + let onDidWatch = Event.toPromise(watcher.onDidWatch); + await Promises.rm(folderPath); + await changeFuture; + await onDidWatch; + + changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED, undefined, 1); + onDidWatch = Event.toPromise(watcher.onDidWatch); + await Promises.mkdir(folderPath); + await changeFuture; + await onDidWatch; + + await basicCrudTest(join(folderPath, 'newFile.txt'), 1); + }); }); From fffd7f14ace850eb79c1f667ef633a2fc2193362 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 8 Mar 2024 08:02:22 -0800 Subject: [PATCH 1881/1897] simplify selector --- .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 0e5593f71ab..4eb09223731 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -143,8 +143,7 @@ function parseCodeFromBlock(block?: string): string | undefined { return match ? match[1].trim() : undefined; } - const enum ChatElementSelectors { - ResponseEditor = 'div.chatMessageContent .interactive-result-editor .inputarea.monaco-mouse-cursor-text', + ResponseEditor = '.chatMessageContent textarea', ResponseMessage = '.chatMessageContent', } From cacdb7d87f138e66bc09d666c5395242bcbe6363 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 8 Mar 2024 13:05:27 -0300 Subject: [PATCH 1882/1897] Use 'command?' to match ChatResponseTurn (#207170) --- src/vscode-dts/vscode.proposed.chatParticipant.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts index 0f872032de2..351cf42e267 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -28,7 +28,7 @@ declare module 'vscode' { /** * The name of the {@link ChatCommand command} that was selected for this request. */ - readonly command: string | undefined; + readonly command?: string; /** * The variables that were referenced in this message. From 43558b2a2d56cf1582fe53324939362a11f96279 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 8 Mar 2024 17:13:15 +0100 Subject: [PATCH 1883/1897] Activity bar top theme colors (#207172) --- .../lib/stylelint/vscode-known-variables.json | 2 ++ .../browser/parts/media/paneCompositePart.css | 12 +++++++---- src/vs/workbench/common/theme.ts | 21 +++++++++++++++---- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index a965cfcdb9f..a3b6d090d94 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -15,6 +15,7 @@ "--vscode-activityBarTop-dropBorder", "--vscode-activityBarTop-foreground", "--vscode-activityBarTop-inactiveForeground", + "--vscode-activityBarTop-background", "--vscode-badge-background", "--vscode-badge-foreground", "--vscode-banner-background", @@ -560,6 +561,7 @@ "--vscode-sideBarSectionHeader-border", "--vscode-sideBarSectionHeader-foreground", "--vscode-sideBarTitle-foreground", + "--vscode-sideBarActivityBarTop-border", "--vscode-sideBySideEditor-horizontalBorder", "--vscode-sideBySideEditor-verticalBorder", "--vscode-simpleFindWidget-sashBorder", diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index e3502c3af86..332f9489175 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -20,12 +20,16 @@ display: none; } -.monaco-workbench .pane-composite-part > .header-or-footer.header { - border-bottom: 1px solid var(--vscode-sideBarSectionHeader-border); +.monaco-workbench .pane-composite-part > .header-or-footer { + background-color: var(--vscode-activityBarTop-background); } -.monaco-workbench .pane-composite-part > .header-or-footer.footer { - border-top: 1px solid var(--vscode-sideBarSectionHeader-border); +.monaco-workbench .pane-composite-part > .header { + border-bottom: 1px solid var(--vscode-sideBarActivityBarTop-border); +} + +.monaco-workbench .pane-composite-part > .footer { + border-top: 1px solid var(--vscode-sideBarActivityBarTop-border); } .monaco-workbench .pane-composite-part > .title > .composite-bar-container, diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index febf755414e..651ddce192c 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -700,28 +700,35 @@ export const ACTIVITY_BAR_TOP_FOREGROUND = registerColor('activityBarTop.foregro light: '#424242', hcDark: Color.white, hcLight: editorForeground -}, localize('activityBarTop', "Active foreground color of the item in the Activity bar when it is on top. The activity allows to switch between views of the side bar.")); +}, localize('activityBarTop', "Active foreground color of the item in the Activity bar when it is on top / bottom. The activity allows to switch between views of the side bar.")); export const ACTIVITY_BAR_TOP_ACTIVE_BORDER = registerColor('activityBarTop.activeBorder', { dark: ACTIVITY_BAR_TOP_FOREGROUND, light: ACTIVITY_BAR_TOP_FOREGROUND, hcDark: contrastBorder, hcLight: '#B5200D' -}, localize('activityBarTopActiveFocusBorder', "Focus border color for the active item in the Activity bar when it is on top. The activity allows to switch between views of the side bar.")); +}, localize('activityBarTopActiveFocusBorder', "Focus border color for the active item in the Activity bar when it is on top / bottom. The activity allows to switch between views of the side bar.")); export const ACTIVITY_BAR_TOP_INACTIVE_FOREGROUND = registerColor('activityBarTop.inactiveForeground', { dark: transparent(ACTIVITY_BAR_TOP_FOREGROUND, 0.6), light: transparent(ACTIVITY_BAR_TOP_FOREGROUND, 0.75), hcDark: Color.white, hcLight: editorForeground -}, localize('activityBarTopInActiveForeground', "Inactive foreground color of the item in the Activity bar when it is on top. The activity allows to switch between views of the side bar.")); +}, localize('activityBarTopInActiveForeground', "Inactive foreground color of the item in the Activity bar when it is on top / bottom. The activity allows to switch between views of the side bar.")); export const ACTIVITY_BAR_TOP_DRAG_AND_DROP_BORDER = registerColor('activityBarTop.dropBorder', { dark: ACTIVITY_BAR_TOP_FOREGROUND, light: ACTIVITY_BAR_TOP_FOREGROUND, hcDark: ACTIVITY_BAR_TOP_FOREGROUND, hcLight: ACTIVITY_BAR_TOP_FOREGROUND -}, localize('activityBarTopDragAndDropBorder', "Drag and drop feedback color for the items in the Activity bar when it is on top. The activity allows to switch between views of the side bar.")); +}, localize('activityBarTopDragAndDropBorder', "Drag and drop feedback color for the items in the Activity bar when it is on top / bottom. The activity allows to switch between views of the side bar.")); + +export const ACTIVITY_BAR_TOP_BACKGROUND = registerColor('activityBarTop.background', { + dark: null, + light: null, + hcDark: null, + hcLight: null, +}, localize('activityBarTopBackground', "Background color of the activity bar when set to top / bottom.")); // < --- Profiles --- > @@ -871,6 +878,12 @@ export const SIDE_BAR_SECTION_HEADER_BORDER = registerColor('sideBarSectionHeade hcLight: contrastBorder }, localize('sideBarSectionHeaderBorder', "Side bar section header border color. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar.")); +export const ACTIVITY_BAR_TOP_BORDER = registerColor('sideBarActivityBarTop.border', { + dark: SIDE_BAR_SECTION_HEADER_BORDER, + light: SIDE_BAR_SECTION_HEADER_BORDER, + hcDark: SIDE_BAR_SECTION_HEADER_BORDER, + hcLight: SIDE_BAR_SECTION_HEADER_BORDER +}, localize('sideBarActivityBarTopBorder', "Border color between the activity bar at the top/bottom and the views.")); // < --- Title Bar --- > From b2bdefefd9d2245e3ba3418eda9e38d80f722762 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 8 Mar 2024 08:20:20 -0800 Subject: [PATCH 1884/1897] Make sure paste as html is disabled for implicit pastes (#207173) --- .../editor/contrib/dropOrPasteInto/browser/defaultProviders.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts index ca2d17065c3..5c86f71a94f 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts @@ -168,7 +168,7 @@ class PasteHtmlProvider implements DocumentPasteEditProvider { private readonly _yieldTo = [{ mimeType: Mimes.text }]; async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise { - if (context.triggerKind !== DocumentPasteTriggerKind.PasteAs && context.only?.contains(this.kind)) { + if (context.triggerKind !== DocumentPasteTriggerKind.PasteAs && !context.only?.contains(this.kind)) { return; } From 465c98e0f2e9a08ad9bf3697b7e26a1f346ff677 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 8 Mar 2024 08:50:00 -0800 Subject: [PATCH 1885/1897] fix vertical position --- .../contrib/chat/browser/actions/chatCodeblockActions.ts | 6 ++---- .../chat/browser/terminalChatController.ts | 2 ++ .../terminalContrib/chat/browser/terminalChatWidget.ts | 8 ++++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index d923b51fa6d..a22490bbfda 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -89,8 +89,7 @@ export function registerChatCodeBlockActions() { icon: Codicon.copy, menu: { id: MenuId.ChatCodeBlock, - group: 'navigation', - when: ContextKeyExpr.not('terminalChatFocus') + group: 'navigation' } }); } @@ -357,8 +356,7 @@ export function registerChatCodeBlockActions() { menu: { id: MenuId.ChatCodeBlock, group: 'navigation', - isHiddenByDefault: true, - when: ContextKeyExpr.not('terminalChatFocus') + isHiddenByDefault: true } }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index cae129aca56..509d0867e73 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -288,6 +288,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (this._currentRequest) { this._chatAccessibilityService.acceptResponse(responseContent, accessibilityRequestId); this._chatWidget?.value.inlineChatWidget.updateChatMessage({ message: new MarkdownString(responseContent), requestId: this._currentRequest.id, providerId: 'terminal' }); + // the message grows in height, be sure to update top position so it doesn't go below the terminal + this._chatWidget?.value.layoutVertically(); const containsCode = responseContent.includes('```'); this._responseTypeContextKey.set(containsCode ? TerminalChatResponseTypes.TerminalCommand : TerminalChatResponseTypes.Message); this._chatWidget?.value.inlineChatWidget.updateToolbar(true); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 4eb09223731..1743043d9e3 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -68,6 +68,12 @@ export class TerminalChatWidget extends Disposable { this._focusedContextKey.set(true); this._visibleContextKey.set(true); this._inlineChatWidget.focus(); + this.layoutVertically(); + this._updateWidth(); + this._register(this._instance.onDimensionsChanged(() => this._updateWidth())); + } + + layoutVertically(): void { const font = this._instance.xterm?.getFont(); if (!font?.charHeight) { return; @@ -80,8 +86,6 @@ export class TerminalChatWidget extends Disposable { if (terminalHeight && top > terminalHeight - this._inlineChatWidget.getHeight()) { this._container.style.top = ''; } - this._updateWidth(); - this._register(this._instance.onDimensionsChanged(() => this._updateWidth())); } private _updateWidth() { From 673b05c784f0f5ca6b3e161b8acd040c76d40c78 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 8 Mar 2024 08:56:29 -0800 Subject: [PATCH 1886/1897] simplify context key --- .../chat/browser/terminalChat.ts | 8 ++--- .../chat/browser/terminalChatActions.ts | 31 +++++++++---------- .../chat/browser/terminalChatController.ts | 10 +++--- 3 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index 48fa65a5f50..1b54cf3fd62 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -36,15 +36,11 @@ export const enum TerminalChatContextKeyStrings { ChatInputHasText = 'terminalChatInputHasText', ChatAgentRegistered = 'terminalChatAgentRegistered', ChatResponseEditorFocused = 'terminalChatResponseEditorFocused', - ChatResponseType = 'terminalChatResponseType', + ChatResponseContainsCodeBlock = 'terminalChatResponseContainsCodeBlock', ChatResponseSupportsIssueReporting = 'terminalChatResponseSupportsIssueReporting', ChatSessionResponseVote = 'terminalChatSessionResponseVote', } -export const enum TerminalChatResponseTypes { - Message = 'message', - TerminalCommand = 'terminalCommand' -} export namespace TerminalChatContextKeys { @@ -67,7 +63,7 @@ export namespace TerminalChatContextKeys { export const responseEditorFocused = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseEditorFocusedContextKey', "Whether the chat response editor is focused.")); /** The type of chat response, if any */ - export const responseType = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseType, undefined, localize('chatResponseTypeContextKey', "The type of chat response, if any")); + export const responseContainsCodeBlock = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseContainsCodeBlock, false, localize('chatResponseContainsCodeBlockContextKey', "Whether the chat response contains a code block.")); /** Whether the response supports issue reporting */ export const responseSupportsIssueReporting = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseSupportsIssueReporting, false, localize('chatResponseSupportsIssueReportingContextKey', "Whether the response supports issue reporting")); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index e0ba2d732c7..36e07a4f19a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -14,7 +14,7 @@ import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PRO import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId, TerminalChatContextKeys, TerminalChatResponseTypes } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId, TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; registerActiveXtermAction({ @@ -127,15 +127,14 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 2, - when: ContextKeyExpr.and(TerminalChatContextKeys.focused, - TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand)) + when: ContextKeyExpr.and(TerminalChatContextKeys.focused, TerminalChatContextKeys.responseContainsCodeBlock) }, f1: true, precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalChatContextKeys.focused, - TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) + TerminalChatContextKeys.responseContainsCodeBlock ), run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -156,7 +155,7 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalChatContextKeys.requestActive.negate(), TerminalChatContextKeys.agentRegistered, - TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) + TerminalChatContextKeys.responseContainsCodeBlock ), icon: Codicon.check, keybinding: { @@ -169,7 +168,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 0, - when: ContextKeyExpr.and(TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.requestActive.negate()), + when: ContextKeyExpr.and(TerminalChatContextKeys.responseContainsCodeBlock, TerminalChatContextKeys.requestActive.negate()), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -189,7 +188,7 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalChatContextKeys.requestActive.negate(), TerminalChatContextKeys.agentRegistered, - TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) + TerminalChatContextKeys.responseContainsCodeBlock ), icon: Codicon.check, keybinding: { @@ -201,7 +200,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 1, - when: ContextKeyExpr.and(TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.requestActive.negate()), + when: ContextKeyExpr.and(TerminalChatContextKeys.responseContainsCodeBlock, TerminalChatContextKeys.requestActive.negate()), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -226,13 +225,13 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 1, - when: ContextKeyExpr.and(TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.Message), TerminalChatContextKeys.requestActive.negate()), + when: ContextKeyExpr.and(TerminalChatContextKeys.responseContainsCodeBlock.negate(), TerminalChatContextKeys.requestActive.negate()), }, { id: MENU_TERMINAL_CHAT_WIDGET, group: 'main', order: 1, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_EMPTY.negate(), TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.requestActive.negate()), + when: ContextKeyExpr.and(CTX_INLINE_CHAT_EMPTY.negate(), TerminalChatContextKeys.responseContainsCodeBlock, TerminalChatContextKeys.requestActive.negate()), }], run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -302,7 +301,7 @@ registerActiveXtermAction({ title: localize2('feedbackHelpful', 'Helpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalChatContextKeys.responseType.notEqualsTo(undefined) + TerminalChatContextKeys.responseContainsCodeBlock.notEqualsTo(undefined) ), icon: Codicon.thumbsup, toggled: TerminalChatContextKeys.sessionResponseVote.isEqualTo('up'), @@ -310,7 +309,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', order: 1, - when: TerminalChatContextKeys.responseType.notEqualsTo(undefined), + when: TerminalChatContextKeys.responseContainsCodeBlock.notEqualsTo(undefined), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -326,7 +325,7 @@ registerActiveXtermAction({ title: localize2('feedbackUnhelpful', 'Unhelpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalChatContextKeys.responseType.notEqualsTo(undefined), + TerminalChatContextKeys.responseContainsCodeBlock.notEqualsTo(undefined), ), toggled: TerminalChatContextKeys.sessionResponseVote.isEqualTo('down'), icon: Codicon.thumbsdown, @@ -334,7 +333,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', order: 2, - when: TerminalChatContextKeys.responseType.notEqualsTo(undefined), + when: TerminalChatContextKeys.responseContainsCodeBlock.notEqualsTo(undefined), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -351,13 +350,13 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalChatContextKeys.requestActive.negate(), - TerminalChatContextKeys.responseType.notEqualsTo(undefined), + TerminalChatContextKeys.responseContainsCodeBlock.notEqualsTo(undefined), TerminalChatContextKeys.responseSupportsIssueReporting ), icon: Codicon.report, menu: [{ id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, - when: ContextKeyExpr.and(TerminalChatContextKeys.responseType.notEqualsTo(undefined), TerminalChatContextKeys.responseSupportsIssueReporting), + when: ContextKeyExpr.and(TerminalChatContextKeys.responseContainsCodeBlock.notEqualsTo(undefined), TerminalChatContextKeys.responseSupportsIssueReporting), group: 'inline', order: 3 }], diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 509d0867e73..6a601c42313 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -23,7 +23,7 @@ import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/te import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; import { ChatModel, ChatRequestModel, IChatRequestVariableData, getHistoryEntriesFromModel } from 'vs/workbench/contrib/chat/common/chatModel'; -import { TerminalChatContextKeys, TerminalChatResponseTypes } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { MarkdownString } from 'vs/base/common/htmlContent'; const enum Message { @@ -63,7 +63,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _requestActiveContextKey: IContextKey; private readonly _terminalAgentRegisteredContextKey: IContextKey; - private readonly _responseTypeContextKey: IContextKey; + private readonly _responseContainsCodeBlockContextKey: IContextKey; private readonly _responseSupportsIssueReportingContextKey: IContextKey; private readonly _sessionResponseVoteContextKey: IContextKey; @@ -101,7 +101,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._requestActiveContextKey = TerminalChatContextKeys.requestActive.bindTo(this._contextKeyService); this._terminalAgentRegisteredContextKey = TerminalChatContextKeys.agentRegistered.bindTo(this._contextKeyService); - this._responseTypeContextKey = TerminalChatContextKeys.responseType.bindTo(this._contextKeyService); + this._responseContainsCodeBlockContextKey = TerminalChatContextKeys.responseContainsCodeBlock.bindTo(this._contextKeyService); this._responseSupportsIssueReportingContextKey = TerminalChatContextKeys.responseSupportsIssueReporting.bindTo(this._contextKeyService); this._sessionResponseVoteContextKey = TerminalChatContextKeys.sessionResponseVote.bindTo(this._contextKeyService); @@ -212,7 +212,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._model.clear(); this._chatWidget?.value.hide(); this._chatWidget?.value.setValue(undefined); - this._responseTypeContextKey.reset(); + this._responseContainsCodeBlockContextKey.reset(); this._sessionResponseVoteContextKey.reset(); this._requestActiveContextKey.reset(); } @@ -291,7 +291,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr // the message grows in height, be sure to update top position so it doesn't go below the terminal this._chatWidget?.value.layoutVertically(); const containsCode = responseContent.includes('```'); - this._responseTypeContextKey.set(containsCode ? TerminalChatResponseTypes.TerminalCommand : TerminalChatResponseTypes.Message); + this._responseContainsCodeBlockContextKey.set(containsCode); this._chatWidget?.value.inlineChatWidget.updateToolbar(true); this._messages.fire(Message.ACCEPT_INPUT); } From 2974c9b49c72967184bffdf6ebc0361cd5275eb0 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:13:29 -0800 Subject: [PATCH 1887/1897] Fix name (#207181) --- src/vs/workbench/contrib/speech/common/speechService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/speech/common/speechService.ts b/src/vs/workbench/contrib/speech/common/speechService.ts index 6fdd365f39c..6aced99f16e 100644 --- a/src/vs/workbench/contrib/speech/common/speechService.ts +++ b/src/vs/workbench/contrib/speech/common/speechService.ts @@ -168,7 +168,8 @@ export const SPEECH_LANGUAGES = { name: localize('speechLanguage.sv-SE', "Swedish (Sweden)") }, ['tr-TR']: { - name: localize('speechLanguage.tr-TR', "Turkish (Turkey)") + // allow-any-unicode-next-line + name: localize('speechLanguage.tr-TR', "Turkish (Türkiye)") }, ['zh-CN']: { name: localize('speechLanguage.zh-CN', "Chinese (Simplified, China)") From 42ddf6570308290a5d825e6347ffab223f147045 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Fri, 8 Mar 2024 11:30:40 -0800 Subject: [PATCH 1888/1897] Force walkthrough image rebuild on selectStep (#207183) --- .../contrib/welcomeGettingStarted/browser/gettingStarted.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index a34712f15cb..725248d0031 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -511,13 +511,13 @@ export class GettingStartedPage extends EditorPane { private currentMediaComponent: string | undefined = undefined; private currentMediaType: string | undefined = undefined; - private async buildMediaComponent(stepId: string) { + private async buildMediaComponent(stepId: string, forceRebuild: boolean = false) { if (!this.currentWalkthrough) { throw Error('no walkthrough selected'); } const stepToExpand = assertIsDefined(this.currentWalkthrough.steps.find(step => step.id === stepId)); - if (this.currentMediaComponent === stepId) { return; } + if (!forceRebuild && this.currentMediaComponent === stepId) { return; } this.currentMediaComponent = stepId; this.stepDisposables.clear(); @@ -726,7 +726,7 @@ export class GettingStartedPage extends EditorPane { stepElement.classList.add('expanded'); stepElement.setAttribute('aria-expanded', 'true'); - this.buildMediaComponent(id); + this.buildMediaComponent(id, true); this.gettingStartedService.progressByEvent('stepSelected:' + id); } else { this.editorInput.selectedStep = undefined; From e5393d4e0b6a9464a773bcff29b48024bbca0f20 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 8 Mar 2024 20:42:16 +0100 Subject: [PATCH 1889/1897] fix #207177 (#207185) --- .../extensions/browser/extensionsActions.ts | 14 +++++++++---- .../browser/extensionsWorkbenchService.ts | 20 ++++++++++--------- .../contrib/extensions/common/extensions.ts | 1 + .../extensionsActions.test.ts | 4 ++-- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 43e8a7c086d..9768c107db0 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1562,6 +1562,7 @@ export class ExtensionRuntimeStateAction extends ExtensionAction { updateWhenCounterExtensionChanges: boolean = true; constructor( + @IHostService private readonly hostService: IHostService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IUpdateService private readonly updateService: IUpdateService, @IExtensionService private readonly extensionService: IExtensionService, @@ -1598,15 +1599,20 @@ export class ExtensionRuntimeStateAction extends ExtensionAction { this.enabled = true; this.class = ExtensionRuntimeStateAction.EnabledClass; this.tooltip = runtimeState.reason; - this.label = runtimeState.action === ExtensionRuntimeActionType.RestartExtensions ? localize('restart extensions', 'Restart Extensions') - : runtimeState.action === ExtensionRuntimeActionType.QuitAndInstall ? localize('restart product', 'Restart to Update') - : runtimeState.action === ExtensionRuntimeActionType.ApplyUpdate || runtimeState.action === ExtensionRuntimeActionType.DownloadUpdate ? localize('update product', 'Update {0}', this.productService.nameShort) : ''; + this.label = runtimeState.action === ExtensionRuntimeActionType.ReloadWindow ? localize('reload window', 'Reload Window') + : runtimeState.action === ExtensionRuntimeActionType.RestartExtensions ? localize('restart extensions', 'Restart Extensions') + : runtimeState.action === ExtensionRuntimeActionType.QuitAndInstall ? localize('restart product', 'Restart to Update') + : runtimeState.action === ExtensionRuntimeActionType.ApplyUpdate || runtimeState.action === ExtensionRuntimeActionType.DownloadUpdate ? localize('update product', 'Update {0}', this.productService.nameShort) : ''; } override async run(): Promise { const runtimeState = this.extension?.runtimeState; - if (runtimeState?.action === ExtensionRuntimeActionType.RestartExtensions) { + if (runtimeState?.action === ExtensionRuntimeActionType.ReloadWindow) { + return this.hostService.reload(); + } + + else if (runtimeState?.action === ExtensionRuntimeActionType.RestartExtensions) { return this.extensionsWorkbenchService.updateRunningExtensions(); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index ec74cf72646..5846f2bb915 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -1147,12 +1147,14 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private getRuntimeState(extension: IExtension): ExtensionRuntimeState | undefined { const isUninstalled = extension.state === ExtensionState.Uninstalled; const runningExtension = this.extensionService.extensions.find(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier)); + const reloadAction = this.extensionManagementServerService.remoteExtensionManagementServer ? ExtensionRuntimeActionType.ReloadWindow : ExtensionRuntimeActionType.RestartExtensions; + const reloadActionLabel = reloadAction === ExtensionRuntimeActionType.ReloadWindow ? nls.localize('reload', "reload window") : nls.localize('restart extensions', "restart extensions"); if (isUninstalled) { const canRemoveRunningExtension = runningExtension && this.extensionService.canRemoveExtension(runningExtension); const isSameExtensionRunning = runningExtension && (!extension.server || extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension))); if (!canRemoveRunningExtension && isSameExtensionRunning && !runningExtension.isUnderDevelopment) { - return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('postUninstallTooltip', "Please restart extensions to complete the uninstallation of this extension.") }; + return { action: reloadAction, reason: nls.localize('postUninstallTooltip', "Please {0} to complete the uninstallation of this extension.", reloadActionLabel) }; } return undefined; } @@ -1190,7 +1192,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } return undefined; } - return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('postUpdateTooltip', "Please restart extensions to enable the updated extension.") }; + return { action: reloadAction, reason: nls.localize('postUpdateTooltip', "Please {0} to enable the updated extension.", reloadActionLabel) }; } if (this.extensionsServers.length > 1) { @@ -1198,12 +1200,12 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (extensionInOtherServer) { // This extension prefers to run on UI/Local side but is running in remote if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.localExtensionManagementServer) { - return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('enable locally', "Please restart extensions to enable this extension locally.") }; + return { action: reloadAction, reason: nls.localize('enable locally', "Please {0} to enable this extension locally.", reloadActionLabel) }; } // This extension prefers to run on Workspace/Remote side but is running in local if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.remoteExtensionManagementServer) { - return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('enable remote', "Please restart extensions to enable this extension in {1}.", this.extensionManagementServerService.remoteExtensionManagementServer?.label) }; + return { action: reloadAction, reason: nls.localize('enable remote', "Please {0} to enable this extension in {1}.", reloadActionLabel, this.extensionManagementServerService.remoteExtensionManagementServer?.label) }; } } } @@ -1213,20 +1215,20 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { // This extension prefers to run on UI/Local side but is running in remote if (this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest)) { - return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('postEnableTooltip', "Please restart extensions to enable this extension.") }; + return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) }; } } if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { // This extension prefers to run on Workspace/Remote side but is running in local if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest)) { - return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('postEnableTooltip', "Please restart extensions to enable this extension.") }; + return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) }; } } } return undefined; } else { if (isSameExtensionRunning) { - return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('postDisableTooltip', "Please restart extensions to disable this extension.") }; + return { action: reloadAction, reason: nls.localize('postDisableTooltip', "Please {0} to disable this extension.", reloadActionLabel) }; } } return undefined; @@ -1235,7 +1237,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension // Extension is not running else { if (isEnabled && !this.extensionService.canAddExtension(toExtensionDescription(extension.local))) { - return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('postEnableTooltip', "Please restart extensions to enable this extension.") }; + return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) }; } const otherServer = extension.server ? extension.server === this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer : null; @@ -1243,7 +1245,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension const extensionInOtherServer = this.local.filter(e => areSameExtensions(e.identifier, extension.identifier) && e.server === otherServer)[0]; // Same extension in other server exists and if (extensionInOtherServer && extensionInOtherServer.local && this.extensionEnablementService.isEnabled(extensionInOtherServer.local)) { - return { action: ExtensionRuntimeActionType.RestartExtensions, reason: nls.localize('postEnableTooltip', "Please restart extensions to enable this extension.") }; + return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) }; } } } diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 8be5639f119..8bae2eeae25 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -40,6 +40,7 @@ export const enum ExtensionState { } export const enum ExtensionRuntimeActionType { + ReloadWindow = 'reloadWindow', RestartExtensions = 'restartExtensions', DownloadUpdate = 'downloadUpdate', ApplyUpdate = 'applyUpdate', diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts index f8c7f267f13..f7c858e86d9 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts @@ -1441,7 +1441,7 @@ suite('ExtensionRuntimeStateAction', () => { await promise; assert.ok(testObject.enabled); - assert.strictEqual(testObject.tooltip, `Please restart extensions to enable this extension.`); + assert.strictEqual(testObject.tooltip, `Please reload window to enable this extension.`); }); test('Test Runtime State when ui extension is disabled on remote server and installed in local server', async () => { @@ -1480,7 +1480,7 @@ suite('ExtensionRuntimeStateAction', () => { await promise; assert.ok(testObject.enabled); - assert.strictEqual(testObject.tooltip, `Please restart extensions to enable this extension.`); + assert.strictEqual(testObject.tooltip, `Please reload window to enable this extension.`); }); test('Test Runtime State for remote ui extension is disabled when it is installed and enabled in local server', async () => { From 93f537f06f8084f018578eee0983d31041bad168 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 8 Mar 2024 23:26:11 +0100 Subject: [PATCH 1890/1897] dispose RangeProvider after usage (#207207) --- .../browser/stickyScrollModelProvider.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollModelProvider.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollModelProvider.ts index f1f50f9ced7..7f7b99c5a9d 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollModelProvider.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollModelProvider.ts @@ -397,9 +397,13 @@ class StickyModelFromCandidateIndentationFoldingProvider extends StickyModelFrom return null; } - protected createModelFromProvider(textModel: TextModel, modelVersionId: number, token: CancellationToken): Promise { + protected async createModelFromProvider(textModel: TextModel, modelVersionId: number, token: CancellationToken): Promise { const provider = new IndentRangeProvider(textModel, this._languageConfigurationService, this._foldingLimitReporter); - return provider.compute(token); + try { + return await provider.compute(token); + } finally { + provider.dispose(); + } } } @@ -422,6 +426,10 @@ class StickyModelFromCandidateSyntaxFoldingProvider extends StickyModelFromCandi protected createModelFromProvider(textModel: TextModel, modelVersionId: number, token: CancellationToken): Promise { const selectedProviders = FoldingController.getFoldingRangeProviders(this._languageFeaturesService, textModel); const provider = new SyntaxRangeProvider(textModel, selectedProviders, () => this.createModelFromProvider(textModel, modelVersionId, token), this._foldingLimitReporter, undefined); - return provider.compute(token); + try { + return provider.compute(token); + } finally { + provider.dispose(); + } } } From cf4c04e7fd1149bd7df45925b32239d68b58289b Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 8 Mar 2024 15:13:55 -0800 Subject: [PATCH 1891/1897] Add the new API endpoint overload to include error location for diagnostics (#207208) * new API overload to provide an error location in a notebook cell * use IRange * more error details all in one place --- .../api/common/extHostNotebookKernels.ts | 17 ++++++- .../notebookExecutionStateServiceImpl.ts | 1 + .../contrib/notebook/common/notebookCommon.ts | 2 + .../common/notebookExecutionStateService.ts | 10 ++++- .../common/extensionsApiProposals.ts | 1 + ...vscode.proposed.notebookCellExecution.d.ts | 44 +++++++++++++++++++ 6 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.notebookCellExecution.d.ts diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index 998e624a195..a52ad806577 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -702,7 +702,7 @@ class NotebookCellExecutionTask extends Disposable { }); }, - end(success: boolean | undefined, endTime?: number): void { + end(success: boolean | undefined, endTime?: number, executionError?: vscode.CellExecutionError): void { if (that._state === NotebookCellExecutionTaskState.Resolved) { throw new Error('Cannot call resolve twice'); } @@ -714,9 +714,22 @@ class NotebookCellExecutionTask extends Disposable { // so we use updateSoon and immediately flush. that._collector.flush(); + const error = executionError ? { + message: executionError.message, + stack: executionError.stack, + location: executionError?.location ? { + startLineNumber: executionError.location.start.line, + startColumn: executionError.location.start.character, + endLineNumber: executionError.location.end.line, + endColumn: executionError.location.end.character + } : undefined, + uri: executionError.uri + } : undefined; + that._proxy.$completeExecution(that._handle, new SerializableObjectWithBuffers({ runEndTime: endTime, - lastRunSuccess: success + lastRunSuccess: success, + error })); }, diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts index dbc9e88e9a4..52157f4ae29 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts @@ -527,6 +527,7 @@ class CellExecution extends Disposable implements INotebookCellExecution { lastRunSuccess: completionData.lastRunSuccess, runStartTime: this._didPause ? null : cellModel.internalMetadata.runStartTime, runEndTime: this._didPause ? null : completionData.runEndTime, + error: completionData.error } }; this._applyExecutionEdits([edit]); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index c673ae8ad79..02c59967a70 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -32,6 +32,7 @@ import { IWorkingCopyBackupMeta, IWorkingCopySaveEvent } from 'vs/workbench/serv import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IFileReadLimits } from 'vs/platform/files/common/files'; import { parse as parseUri, generate as generateUri } from 'vs/workbench/services/notebook/common/notebookDocumentService'; +import { ICellExecutionError } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; export const NOTEBOOK_EDITOR_ID = 'workbench.editor.notebook'; export const NOTEBOOK_DIFF_EDITOR_ID = 'workbench.editor.notebookTextDiffEditor'; @@ -120,6 +121,7 @@ export interface NotebookCellInternalMetadata { runStartTimeAdjustment?: number; runEndTime?: number; renderDuration?: { [key: string]: number }; + error?: ICellExecutionError; } export interface NotebookCellCollapseState { diff --git a/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts b/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts index 98a24d28adf..5b98e7ca262 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts @@ -5,7 +5,8 @@ import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { IRange } from 'vs/editor/common/core/range'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { NotebookCellExecutionState, NotebookExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellExecutionUpdateType, ICellExecuteOutputEdit, ICellExecuteOutputItemEdit } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; @@ -20,9 +21,16 @@ export interface ICellExecutionStateUpdate { isPaused?: boolean; } +export interface ICellExecutionError { + message: string; + stack: string | undefined; + uri: UriComponents; + location: IRange | undefined; +} export interface ICellExecutionComplete { runEndTime?: number; lastRunSuccess?: boolean; + error?: ICellExecutionError; } export enum NotebookExecutionType { cell, diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 67c26906df3..548f66575c0 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -77,6 +77,7 @@ export const allApiProposals = Object.freeze({ mappedEditsProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts', multiDocumentHighlightProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.multiDocumentHighlightProvider.d.ts', newSymbolNamesProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts', + notebookCellExecution: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecution.d.ts', notebookCellExecutionState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts', notebookControllerAffinityHidden: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookControllerAffinityHidden.d.ts', notebookDeprecated: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookDeprecated.d.ts', diff --git a/src/vscode-dts/vscode.proposed.notebookCellExecution.d.ts b/src/vscode-dts/vscode.proposed.notebookCellExecution.d.ts new file mode 100644 index 00000000000..944f68f5fe3 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.notebookCellExecution.d.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * 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 NotebookCellExecution { + /** + * Signal that execution has ended. + * + * @param success If true, a green check is shown on the cell status bar. + * If false, a red X is shown. + * If undefined, no check or X icon is shown. + * @param endTime The time that execution finished, in milliseconds in the Unix epoch. + * @param error Details about an error that occurred during execution if any. + */ + end(success: boolean | undefined, endTime?: number, error?: CellExecutionError): void; + } + + export interface CellExecutionError { + /** + * The error message. + */ + readonly message: string; + + /** + * The error stack trace. + */ + readonly stack: string | undefined; + + /** + * The cell resource which had the error. + */ + uri: Uri; + + /** + * The location within the resource where the error occurred. + */ + readonly location: Range | undefined; + + + } +} From 877a181582952eb2b2262a4663c81355a0e6bb4c Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 8 Mar 2024 15:40:04 -0800 Subject: [PATCH 1892/1897] testing: fix proposed obsever API not rebuilding test tree (#207206) Use the new prefix tree implementation. --- src/vs/workbench/api/common/extHostTesting.ts | 3 +- .../api/common/extHostTypeConverters.ts | 53 +++++++++++-------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTesting.ts b/src/vs/workbench/api/common/extHostTesting.ts index 5d0ca1cb8b9..dcae2c2c45c 100644 --- a/src/vs/workbench/api/common/extHostTesting.ts +++ b/src/vs/workbench/api/common/extHostTesting.ts @@ -13,7 +13,6 @@ import { createSingleCallFunction } from 'vs/base/common/functional'; import { hash } from 'vs/base/common/hash'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { MarshalledId } from 'vs/base/common/marshallingIds'; -import { deepFreeze } from 'vs/base/common/objects'; import { isDefined } from 'vs/base/common/types'; import { generateUuid } from 'vs/base/common/uuid'; import { IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -296,7 +295,7 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { public $publishTestResults(results: ISerializedTestResults[]): void { this.results = Object.freeze( results - .map(r => deepFreeze(Convert.TestResults.to(r))) + .map(Convert.TestResults.to) .concat(this.results) .sort((a, b) => b.completedAt - a.completedAt) .slice(0, 32), diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 13077072ff3..eb1e89cf5c6 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -14,8 +14,9 @@ import { marked } from 'vs/base/common/marked/marked'; import { parse, revive } from 'vs/base/common/marshalling'; import { Mimes } from 'vs/base/common/mime'; import { cloneAndChange } from 'vs/base/common/objects'; +import { IPrefixTreeNode, WellDefinedPrefixTree } from 'vs/base/common/prefixTree'; import { basename } from 'vs/base/common/resources'; -import { isEmptyObject, isNumber, isString, isUndefinedOrNull } from 'vs/base/common/types'; +import { isDefined, isEmptyObject, isNumber, isString, isUndefinedOrNull } from 'vs/base/common/types'; import { URI, UriComponents, isUriComponents } from 'vs/base/common/uri'; import { IURITransformer } from 'vs/base/common/uriIpc'; import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; @@ -38,15 +39,15 @@ import { getPrivateApiFor } from 'vs/workbench/api/common/extHostTestingPrivateA import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/editor'; import { IViewBadge } from 'vs/workbench/common/views'; import { IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; -import * as chatProvider from 'vs/workbench/contrib/chat/common/languageModels'; import { IChatCommandButton, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTreeData, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; +import * as chatProvider from 'vs/workbench/contrib/chat/common/languageModels'; import { DebugTreeItemCollapsibleState, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; import { IInlineChatCommandFollowup, IInlineChatFollowup, IInlineChatReplyFollowup, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import * as search from 'vs/workbench/contrib/search/common/search'; -import { TestId, TestPosition } from 'vs/workbench/contrib/testing/common/testId'; +import { TestId } from 'vs/workbench/contrib/testing/common/testId'; import { CoverageDetails, DetailType, ICoveredCount, IFileCoverage, ISerializedTestResults, ITestErrorMessage, ITestItem, ITestTag, TestMessageType, TestResultItem, denamespaceTestTag, namespaceTestTag } from 'vs/workbench/contrib/testing/common/testTypes'; import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; @@ -1956,18 +1957,15 @@ export namespace TestTag { } export namespace TestResults { - const convertTestResultItem = (item: TestResultItem.Serialized, byInternalId: Map): vscode.TestResultSnapshot => { - const children: TestResultItem.Serialized[] = []; - for (const [id, item] of byInternalId) { - if (TestId.compare(item.item.extId, id) === TestPosition.IsChild) { - byInternalId.delete(id); - children.push(item); - } + const convertTestResultItem = (node: IPrefixTreeNode, parent?: vscode.TestResultSnapshot): vscode.TestResultSnapshot | undefined => { + const item = node.value; + if (!item) { + return undefined; // should be unreachable } const snapshot: vscode.TestResultSnapshot = ({ ...TestItem.toPlain(item.item), - parent: undefined, + parent, taskStates: item.tasks.map(t => ({ state: t.state as number as types.TestResultState, duration: t.duration, @@ -1975,30 +1973,43 @@ export namespace TestResults { .filter((m): m is ITestErrorMessage.Serialized => m.type === TestMessageType.Error) .map(TestMessage.to), })), - children: children.map(c => convertTestResultItem(c, byInternalId)) + children: [], }); - for (const child of snapshot.children) { - (child as any).parent = snapshot; + if (node.children) { + for (const child of node.children.values()) { + const c = convertTestResultItem(child, snapshot); + if (c) { + snapshot.children.push(c); + } + } } return snapshot; }; export function to(serialized: ISerializedTestResults): vscode.TestRunResult { - const roots: TestResultItem.Serialized[] = []; - const byInternalId = new Map(); + const tree = new WellDefinedPrefixTree(); for (const item of serialized.items) { - byInternalId.set(item.item.extId, item); - const controllerId = TestId.root(item.item.extId); - if (serialized.request.targets.some(t => t.controllerId === controllerId && t.testIds.includes(item.item.extId))) { - roots.push(item); + tree.insert(TestId.fromString(item.item.extId).path, item); + } + + // Get the first node with a value in each subtree of IDs. + const queue = [tree.nodes]; + const roots: IPrefixTreeNode[] = []; + while (queue.length) { + for (const node of queue.pop()!) { + if (node.value) { + roots.push(node); + } else if (node.children) { + queue.push(node.children.values()); + } } } return { completedAt: serialized.completedAt, - results: roots.map(r => convertTestResultItem(r, byInternalId)), + results: roots.map(r => convertTestResultItem(r)).filter(isDefined), }; } } From 33cd6f1001b92a912898996be69b6928eda1a682 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 8 Mar 2024 16:21:04 -0800 Subject: [PATCH 1893/1897] Experiment in-mem cache for cell generation prompts (#207213) --- .../controller/chat/cellChatActions.ts | 47 ++++++- .../controller/chat/notebookChatController.ts | 123 +++++++++++++++--- .../browser/view/cellParts/cellContextKeys.ts | 27 +++- .../notebook/common/notebookContextKeys.ts | 1 + 4 files changed, 176 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 329ff39bf74..e14960efc0a 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -16,10 +16,10 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; import { NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController'; -import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, getEditorFromArgsOrActivePane } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; +import { CELL_TITLE_CELL_GROUP_ID, INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, getEditorFromArgsOrActivePane } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { NOTEBOOK_CELL_GENERATED_BY_CHAT, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; registerAction2(class extends NotebookAction { @@ -613,3 +613,46 @@ registerAction2(class extends NotebookAction { NotebookChatController.get(context.notebookEditor)?.populateHistory(false); } }); + +registerAction2(class extends NotebookCellAction { + constructor() { + super( + { + id: 'notebook.cell.chat.restore', + title: localize2('notebookActions.restoreCellprompt', "Generate"), + icon: Codicon.sparkle, + menu: { + id: MenuId.NotebookCellTitle, + group: CELL_TITLE_CELL_GROUP_ID, + order: 0, + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true), + CTX_INLINE_CHAT_HAS_PROVIDER, + NOTEBOOK_CELL_GENERATED_BY_CHAT, + ContextKeyExpr.equals(`config.${NotebookSetting.cellChat}`, true) + ) + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + const cell = context.cell; + + if (!cell) { + return; + } + + const notebookEditor = context.notebookEditor; + const controller = NotebookChatController.get(notebookEditor); + + if (!controller) { + return; + } + + const prompt = controller.getPromptFromCache(cell); + + if (prompt) { + controller.restore(cell, prompt); + } + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 626f496569f..341593052f0 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -6,13 +6,15 @@ import { Dimension, IFocusTracker, WindowIntervalTimer, getWindow, scheduleAtNextAnimationFrame, trackFocus } from 'vs/base/browser/dom'; import { CancelablePromise, Queue, createCancelablePromise, disposableTimeout, raceCancellationError } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { Event } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { LRUCache } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { MovingAverage } from 'vs/base/common/numbers'; import { StopWatch } from 'vs/base/common/stopwatch'; import { assertType } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; @@ -41,9 +43,8 @@ import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/in import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; -import { INotebookEditor, INotebookEditorContribution, INotebookViewZone } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ICellViewModel, INotebookEditor, INotebookEditorContribution, INotebookViewZone } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; -import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; @@ -68,7 +69,7 @@ class NotebookChatWidget extends Disposable implements INotebookViewZone { return this.notebookViewZone.heightInPx; } - private _editingCell: CellViewModel | null = null; + private _editingCell: ICellViewModel | null = null; get editingCell() { return this._editingCell; @@ -97,6 +98,10 @@ class NotebookChatWidget extends Disposable implements INotebookViewZone { this._layoutWidget(inlineChatWidget, widgetContainer); } + restoreEditingCell(initEditingCell: ICellViewModel) { + this._editingCell = initEditingCell; + } + hasFocus() { return this.inlineChatWidget.hasFocus(); } @@ -119,7 +124,7 @@ class NotebookChatWidget extends Disposable implements INotebookViewZone { return this._editingCell; } - async getOrCreateEditingCell(): Promise<{ cell: CellViewModel; editor: IActiveCodeEditor } | undefined> { + async getOrCreateEditingCell(): Promise<{ cell: ICellViewModel; editor: IActiveCodeEditor } | undefined> { if (this._editingCell) { const codeEditor = this._notebookEditor.codeEditors.find(ce => ce[0] === this._editingCell)?.[1]; if (codeEditor?.hasModel()) { @@ -189,6 +194,20 @@ class NotebookChatWidget extends Disposable implements INotebookViewZone { } } +export interface INotebookCellTextModelLike { uri: URI; viewType: string } +class NotebookCellTextModelLikeId { + static str(k: INotebookCellTextModelLike): string { + return `${k.viewType}/${k.uri.toString()}`; + } + static obj(s: string): INotebookCellTextModelLike { + const idx = s.indexOf('/'); + return { + viewType: s.substring(0, idx), + uri: URI.parse(s.substring(idx + 1)) + }; + } +} + export class NotebookChatController extends Disposable implements INotebookEditorContribution { static id: string = 'workbench.notebook.chatController'; static counter: number = 0; @@ -203,7 +222,9 @@ export class NotebookChatController extends Disposable implements INotebookEdito private _historyOffset: number = -1; private _historyCandidate: string = ''; private _historyUpdate: (prompt: string) => void; - + private _promptCache = new LRUCache(1000, 0.7); + private readonly _onDidChangePromptCache = this._register(new Emitter<{ cell: URI }>()); + readonly onDidChangePromptCache = this._onDidChangePromptCache.event; private _strategy: EditStrategy | undefined; private _sessionCtor: CancelablePromise | undefined; @@ -277,31 +298,60 @@ export class NotebookChatController extends Disposable implements INotebookEdito run(index: number, input: string | undefined, autoSend: boolean | undefined): void { if (this._widget) { - if (this._widget.afterModelPosition === index) { - // this._chatZone - // chatZone focus - } else { + if (this._widget.afterModelPosition !== index) { + this._disposeWidget(); const window = getWindow(this._widget.domNode); - this._widget.dispose(); - this._widget = undefined; - this._widgetDisposableStore.clear(); - - this._historyOffset = -1; - this._historyCandidate = ''; scheduleAtNextAnimationFrame(window, () => { - this._createWidget(index, input, autoSend); + this._createWidget(index, input, autoSend, undefined); }); } return; } - this._createWidget(index, input, autoSend); + this._createWidget(index, input, autoSend, undefined); // TODO: reveal widget to the center if it's out of the viewport } - private _createWidget(index: number, input: string | undefined, autoSend: boolean | undefined) { + restore(editingCell: ICellViewModel, input: string) { + if (!this._notebookEditor.hasModel()) { + return; + } + + const index = this._notebookEditor.textModel.cells.indexOf(editingCell.model); + + if (index < 0) { + return; + } + + if (this._widget) { + if (this._widget.afterModelPosition !== index) { + this._disposeWidget(); + const window = getWindow(this._widget.domNode); + + scheduleAtNextAnimationFrame(window, () => { + this._createWidget(index, input, false, editingCell); + }); + } + + return; + } + + this._createWidget(index, input, false, editingCell); + } + + private _disposeWidget() { + this._widget?.dispose(); + this._widget = undefined; + this._widgetDisposableStore.clear(); + + this._historyOffset = -1; + this._historyCandidate = ''; + } + + + private _createWidget(index: number, input: string | undefined, autoSend: boolean | undefined, initEditingCell: ICellViewModel | undefined) { if (!this._notebookEditor.hasModel()) { return; } @@ -376,6 +426,11 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._languageService ); + if (initEditingCell) { + this._widget.restoreEditingCell(initEditingCell); + this._updateUserEditingState(); + } + this._ctxCellWidgetFocused.set(true); disposableTimeout(() => { @@ -744,6 +799,15 @@ export class NotebookChatController extends Disposable implements INotebookEdito return; } + const editingCell = this._widget?.getEditingCell(); + + if (editingCell && this._notebookEditor.hasModel() && this._activeSession.lastInput) { + const cellId = NotebookCellTextModelLikeId.str({ uri: editingCell.uri, viewType: this._notebookEditor.textModel.viewType }); + const prompt = this._activeSession.lastInput.value; + this._promptCache.set(cellId, prompt); + this._onDidChangePromptCache.fire({ cell: editingCell.uri }); + } + try { await this._strategy.apply(editor); this._inlineChatSessionService.releaseSession(this._activeSession); @@ -902,6 +966,27 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._widgetDisposableStore.clear(); } + // check if a cell is generated by prompt by checking prompt cache + isCellGeneratedByChat(cell: ICellViewModel) { + if (!this._notebookEditor.hasModel()) { + // no model attached yet + return false; + } + + const cellId = NotebookCellTextModelLikeId.str({ uri: cell.uri, viewType: this._notebookEditor.textModel.viewType }); + return this._promptCache.has(cellId); + } + + // get prompt from cache + getPromptFromCache(cell: ICellViewModel) { + if (!this._notebookEditor.hasModel()) { + // no model attached yet + return undefined; + } + + const cellId = NotebookCellTextModelLikeId.str({ uri: cell.uri, viewType: this._notebookEditor.textModel.viewType }); + return this._promptCache.get(cellId); + } public override dispose(): void { this.dismiss(false); super.dispose(); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts index eb2c45f2f8a..7bc1ba28b3a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts @@ -6,13 +6,14 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController'; import { CellEditState, CellFocusMode, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; import { NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotebookCellExecutionStateContext, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LINE_NUMBERS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_RESOURCE, NOTEBOOK_CELL_TYPE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { NotebookCellExecutionStateContext, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LINE_NUMBERS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_RESOURCE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CELL_GENERATED_BY_CHAT } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; export class CellContextKeyPart extends CellContentPart { @@ -45,6 +46,7 @@ export class CellContextKeyManager extends Disposable { private cellOutputCollapsed!: IContextKey; private cellLineNumbers!: IContextKey<'on' | 'off' | 'inherit'>; private cellResource!: IContextKey; + private cellGeneratedByChat!: IContextKey; private markdownEditMode!: IContextKey; @@ -70,6 +72,7 @@ export class CellContextKeyManager extends Disposable { this.cellContentCollapsed = NOTEBOOK_CELL_INPUT_COLLAPSED.bindTo(this._contextKeyService); this.cellOutputCollapsed = NOTEBOOK_CELL_OUTPUT_COLLAPSED.bindTo(this._contextKeyService); this.cellLineNumbers = NOTEBOOK_CELL_LINE_NUMBERS.bindTo(this._contextKeyService); + this.cellGeneratedByChat = NOTEBOOK_CELL_GENERATED_BY_CHAT.bindTo(this._contextKeyService); this.cellResource = NOTEBOOK_CELL_RESOURCE.bindTo(this._contextKeyService); if (element) { @@ -112,10 +115,21 @@ export class CellContextKeyManager extends Disposable { this.updateForEditState(); this.updateForCollapseState(); this.updateForOutputs(); + this.updateForChat(); this.cellLineNumbers.set(this.element!.lineNumbers); this.cellResource.set(this.element!.uri.toString()); }); + + const chatController = NotebookChatController.get(this.notebookEditor); + + if (chatController) { + this.elementDisposables.add(chatController.onDidChangePromptCache(e => { + if (e.cell.toString() === this.element!.uri.toString()) { + this.updateForChat(); + } + })); + } } private onDidChangeState(e: CellViewModelStateChangeEvent) { @@ -216,4 +230,15 @@ export class CellContextKeyManager extends Disposable { this.cellHasOutputs.set(false); } } + + private updateForChat() { + const chatController = NotebookChatController.get(this.notebookEditor); + + if (!chatController || !this.element) { + this.cellGeneratedByChat.set(false); + return; + } + + this.cellGeneratedByChat.set(chatController.isCellGeneratedByChat(this.element)); + } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts index 42b5d294c13..8345a520e5c 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts @@ -46,6 +46,7 @@ export const NOTEBOOK_CELL_HAS_OUTPUTS = new RawContextKey('notebookCel export const NOTEBOOK_CELL_INPUT_COLLAPSED = new RawContextKey('notebookCellInputIsCollapsed', false); export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey('notebookCellOutputIsCollapsed', false); export const NOTEBOOK_CELL_RESOURCE = new RawContextKey('notebookCellResource', ''); +export const NOTEBOOK_CELL_GENERATED_BY_CHAT = new RawContextKey('notebookCellGenerateByChat', false); // Kernels export const NOTEBOOK_KERNEL = new RawContextKey('notebookKernel', undefined); From 5486af8f384ac1fff9b349469a08e1f3c38aef03 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 9 Mar 2024 10:08:42 +0100 Subject: [PATCH 1894/1897] editors - improve swap diff editor sides enablement (#207225) --- src/vs/workbench/browser/contextkeys.ts | 12 +++++++----- .../browser/parts/editor/diffEditorCommands.ts | 5 +++-- .../browser/parts/editor/editor.contribution.ts | 4 ++-- src/vs/workbench/common/contextkeys.ts | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index a1663743d76..71ec7e27232 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from 'vs/platform/contextkey/common/contextkeys'; -import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsMainEditorCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, MainEditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, TitleBarVisibleContext, TitleBarStyleContext, MultipleEditorGroupsContext, IsAuxiliaryWindowFocusedContext, ActiveCompareEditorOriginalWriteableContext } from 'vs/workbench/common/contextkeys'; +import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsMainEditorCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, MainEditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, TitleBarVisibleContext, TitleBarStyleContext, MultipleEditorGroupsContext, IsAuxiliaryWindowFocusedContext, ActiveCompareEditorCanSwapContext } from 'vs/workbench/common/contextkeys'; import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType, onDidRegisterWindow, getActiveWindow } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -29,6 +29,7 @@ import { getTitleBarStyle } from 'vs/platform/window/common/window'; import { mainWindow } from 'vs/base/browser/window'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { isFullscreen, onDidChangeFullscreen } from 'vs/base/browser/browser'; +import { Schemas } from 'vs/base/common/network'; export class WorkbenchContextKeysHandler extends Disposable { private inputFocusedContext: IContextKey; @@ -41,7 +42,7 @@ export class WorkbenchContextKeysHandler extends Disposable { private activeEditorAvailableEditorIds: IContextKey; private activeEditorIsReadonly: IContextKey; - private activeCompareEditorOriginalWritable: IContextKey; + private activeCompareEditorCanSwap: IContextKey; private activeEditorCanToggleReadonly: IContextKey; private activeEditorGroupEmpty: IContextKey; @@ -130,7 +131,7 @@ export class WorkbenchContextKeysHandler extends Disposable { // Editors this.activeEditorContext = ActiveEditorContext.bindTo(this.contextKeyService); this.activeEditorIsReadonly = ActiveEditorReadonlyContext.bindTo(this.contextKeyService); - this.activeCompareEditorOriginalWritable = ActiveCompareEditorOriginalWriteableContext.bindTo(this.contextKeyService); + this.activeCompareEditorCanSwap = ActiveCompareEditorCanSwapContext.bindTo(this.contextKeyService); this.activeEditorCanToggleReadonly = ActiveEditorCanToggleReadonlyContext.bindTo(this.contextKeyService); this.activeEditorCanRevert = ActiveEditorCanRevertContext.bindTo(this.contextKeyService); this.activeEditorCanSplitInGroup = ActiveEditorCanSplitInGroupContext.bindTo(this.contextKeyService); @@ -318,13 +319,14 @@ export class WorkbenchContextKeysHandler extends Disposable { this.activeEditorCanSplitInGroup.set(activeEditorPane.input.hasCapability(EditorInputCapabilities.CanSplitInGroup)); applyAvailableEditorIds(this.activeEditorAvailableEditorIds, activeEditorPane.input, this.editorResolverService); this.activeEditorIsReadonly.set(!!activeEditorPane.input.isReadonly()); - this.activeCompareEditorOriginalWritable.set(activeEditorPane.input instanceof DiffEditorInput && !activeEditorPane.input.original.isReadonly()); const primaryEditorResource = EditorResourceAccessor.getOriginalUri(activeEditorPane.input, { supportSideBySide: SideBySideEditor.PRIMARY }); + const secondaryEditorResource = EditorResourceAccessor.getOriginalUri(activeEditorPane.input, { supportSideBySide: SideBySideEditor.SECONDARY }); + this.activeCompareEditorCanSwap.set(activeEditorPane.input instanceof DiffEditorInput && !activeEditorPane.input.original.isReadonly() && !!primaryEditorResource && (this.fileService.hasProvider(primaryEditorResource) || primaryEditorResource.scheme === Schemas.untitled) && !!secondaryEditorResource && (this.fileService.hasProvider(secondaryEditorResource) || secondaryEditorResource.scheme === Schemas.untitled)); this.activeEditorCanToggleReadonly.set(!!primaryEditorResource && this.fileService.hasProvider(primaryEditorResource) && !this.fileService.hasCapability(primaryEditorResource, FileSystemProviderCapabilities.Readonly)); } else { this.activeEditorContext.reset(); this.activeEditorIsReadonly.reset(); - this.activeCompareEditorOriginalWritable.reset(); + this.activeCompareEditorCanSwap.reset(); this.activeEditorCanToggleReadonly.reset(); this.activeEditorCanRevert.reset(); this.activeEditorCanSplitInGroup.reset(); diff --git a/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts b/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts index 550ed81628f..7a782b9ee3e 100644 --- a/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts @@ -7,10 +7,11 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { localize2, localize } from 'vs/nls'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; -import { TextCompareEditorVisibleContext, TextCompareEditorActiveContext } from 'vs/workbench/common/contextkeys'; +import { TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveCompareEditorCanSwapContext } from 'vs/workbench/common/contextkeys'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -226,6 +227,6 @@ export function registerDiffEditorCommands(): void { title: localize2('swapDiffSides', "Swap Left and Right Editor Side"), category: localize('compare', "Compare") }, - when: TextCompareEditorActiveContext + when: ContextKeyExpr.and(TextCompareEditorActiveContext, ActiveCompareEditorCanSwapContext) }); } diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index a60e8e59d62..1611076ea51 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -11,7 +11,7 @@ import { TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, EditorPartMultipleEditorGroupsContext, ActiveEditorDirtyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, EditorTabsVisibleContext, ActiveEditorLastInGroupContext, EditorPartMaximizedEditorGroupContext, MultipleEditorGroupsContext, InEditorZenModeContext, - IsAuxiliaryEditorPartContext, ActiveCompareEditorOriginalWriteableContext + IsAuxiliaryEditorPartContext, ActiveCompareEditorCanSwapContext } from 'vs/workbench/common/contextkeys'; import { SideBySideEditorInput, SideBySideEditorInputSerializer } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; @@ -606,7 +606,7 @@ appendEditorToolItem( title: localize('swapDiffSides', "Swap Left and Right Side"), icon: Codicon.arrowSwap }, - ContextKeyExpr.and(TextCompareEditorActiveContext, ActiveCompareEditorOriginalWriteableContext), + ContextKeyExpr.and(TextCompareEditorActiveContext, ActiveCompareEditorCanSwapContext), 15, undefined, undefined diff --git a/src/vs/workbench/common/contextkeys.ts b/src/vs/workbench/common/contextkeys.ts index a6464aeacd9..612f006a88c 100644 --- a/src/vs/workbench/common/contextkeys.ts +++ b/src/vs/workbench/common/contextkeys.ts @@ -52,7 +52,7 @@ export const ActiveEditorFirstInGroupContext = new RawContextKey('activ export const ActiveEditorLastInGroupContext = new RawContextKey('activeEditorIsLastInGroup', false, localize('activeEditorIsLastInGroup', "Whether the active editor is the last one in its group")); export const ActiveEditorStickyContext = new RawContextKey('activeEditorIsPinned', false, localize('activeEditorIsPinned', "Whether the active editor is pinned")); export const ActiveEditorReadonlyContext = new RawContextKey('activeEditorIsReadonly', false, localize('activeEditorIsReadonly', "Whether the active editor is read-only")); -export const ActiveCompareEditorOriginalWriteableContext = new RawContextKey('activeCompareEditorOriginalWritable', false, localize('activeCompareEditorOriginalWritable', "Whether the active compare editor has a writable original side")); +export const ActiveCompareEditorCanSwapContext = new RawContextKey('activeCompareEditorCanSwap', false, localize('activeCompareEditorCanSwap', "Whether the active compare editor can swap sides")); export const ActiveEditorCanToggleReadonlyContext = new RawContextKey('activeEditorCanToggleReadonly', true, localize('activeEditorCanToggleReadonly', "Whether the active editor can toggle between being read-only or writeable")); export const ActiveEditorCanRevertContext = new RawContextKey('activeEditorCanRevert', false, localize('activeEditorCanRevert', "Whether the active editor can revert")); export const ActiveEditorCanSplitInGroupContext = new RawContextKey('activeEditorCanSplitInGroup', true); From 0a82e795aba44c6f64e8993b499a9d8618c83fb3 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 11 Mar 2024 03:00:03 +0100 Subject: [PATCH 1895/1897] Fix activity icon size and border in high contrast theme (#207261) fix #197648 --- .../auxiliarybar/media/auxiliaryBarPart.css | 17 +++++++++++++++++ .../browser/parts/media/paneCompositePart.css | 1 + .../browser/parts/sidebar/media/sidebarpart.css | 17 +++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css b/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css index 675a6507271..21f22875e1f 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css +++ b/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css @@ -34,6 +34,23 @@ flex: 1; } +.monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus, +.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus { + outline: 0 !important; /* activity bar indicates focus custom */ +} + +.monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label, +.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label { + border-radius: 0px; + outline-offset: 2px; +} + +.monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label::before, +.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label::before { + position: absolute; + left: 6px; /* place icon in center */ +} + .monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, .monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked.clicked:focus .active-item-indicator:before, .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index 332f9489175..153ba9ef298 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -290,6 +290,7 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before, .monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { border-top-color: var(--vscode-focusBorder) !important; + border-top-width: 2px; } .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index 4721db76de4..0f2312e1f19 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -60,6 +60,23 @@ height: 16px; } +.monaco-workbench .part.sidebar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus, +.monaco-workbench .part.sidebar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus { + outline: 0 !important; /* activity bar indicates focus custom */ +} + +.monaco-workbench .part.sidebar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label, +.monaco-workbench .part.sidebar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label { + border-radius: 0px; + outline-offset: 2px; +} + +.monaco-workbench .part.sidebar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label::before, +.monaco-workbench .part.sidebar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label::before { + position: absolute; + left: 6px; /* place icon in center */ +} + .monaco-workbench .part.sidebar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, .monaco-workbench .part.sidebar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked.clicked:focus .active-item-indicator:before, .monaco-workbench .part.sidebar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, From 12fc36731bd0424dfc53e9f456ac793c1eb1d6d0 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 11 Mar 2024 03:00:38 +0100 Subject: [PATCH 1896/1897] Increase activity bar space at top and bottom (#207262) Give more space to activity bar when top / bottom --- src/vs/workbench/browser/media/part.css | 3 +-- src/vs/workbench/browser/parts/media/paneCompositePart.css | 2 ++ src/vs/workbench/browser/parts/paneCompositePart.ts | 5 +++-- src/vs/workbench/browser/parts/panel/panelPart.ts | 4 ++++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css index f2787c2bf3b..f17465a5e4a 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -35,8 +35,7 @@ overflow: hidden; } -.monaco-workbench .part > .title, -.monaco-workbench .part > .header-or-footer { +.monaco-workbench .part > .title { padding-left: 8px; padding-right: 8px; } diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index 153ba9ef298..bc9ad38c3b8 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -21,6 +21,8 @@ } .monaco-workbench .pane-composite-part > .header-or-footer { + padding-left: 4px; + padding-right: 4px; background-color: var(--vscode-activityBarTop-background); } diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index d914587e771..634760a8213 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -545,7 +545,8 @@ export abstract class AbstractPaneCompositePart extends CompositePart Date: Mon, 11 Mar 2024 00:27:12 -0700 Subject: [PATCH 1897/1897] Feature - Surface video tutorials on Welcome Page (#207293) * Initial support for video tutorials * Experiment for surfacing video tutorials --- .../browser/gettingStarted.ts | 138 ++++++++++++++++-- 1 file changed, 122 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 725248d0031..1695dfa8ce5 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -70,6 +70,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ThemeSettings } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { GettingStartedIndexList } from './gettingStartedList'; +import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; const SLIDE_TRANSITION_TIME_MS = 250; const configurationKey = 'workbench.startupEditor'; @@ -148,6 +149,7 @@ export class GettingStartedPage extends EditorPane { private recentlyOpenedList?: GettingStartedIndexList; private startList?: GettingStartedIndexList; private gettingStartedList?: GettingStartedIndexList; + private videoList?: GettingStartedIndexList; private stepsSlide!: HTMLElement; private categoriesSlide!: HTMLElement; @@ -160,6 +162,7 @@ export class GettingStartedPage extends EditorPane { private detailsRenderer: GettingStartedDetailsRenderer; private categoriesSlideDisposables: DisposableStore; + private showFeaturedWalkthrough = true; constructor( group: IEditorGroup, @@ -185,7 +188,9 @@ export class GettingStartedPage extends EditorPane { @IHostService private readonly hostService: IHostService, @IWebviewService private readonly webviewService: IWebviewService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IAccessibilityService private readonly accessibilityService: IAccessibilityService) { + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, + @IWorkbenchAssignmentService private readonly tasExperimentService: IWorkbenchAssignmentService + ) { super(GettingStartedPage.ID, group, telemetryService, themeService, storageService); @@ -345,7 +350,13 @@ export class GettingStartedPage extends EditorPane { this.dispatchListeners.clear(); this.container.querySelectorAll('[x-dispatch]').forEach(element => { - const [command, argument] = (element.getAttribute('x-dispatch') ?? '').split(':'); + const dispatch = element.getAttribute('x-dispatch') ?? ''; + let command, argument; + if (dispatch.startsWith('openLink:https')) { + [command, argument] = ['openLink', dispatch.replace('openLink:', '')]; + } else { + [command, argument] = dispatch.split(':'); + } if (command) { this.dispatchListeners.add(addDisposableListener(element, 'click', (e) => { e.stopPropagation(); @@ -433,12 +444,12 @@ export class GettingStartedPage extends EditorPane { } break; } - case 'openExtensionPage': { - this.commandService.executeCommand('extension.open', argument); + case 'hideVideos': { + this.hideVideos(); break; } - case 'hideExtension': { - this.hideExtension(argument); + case 'openLink': { + this.openerService.open(argument); break; } default: { @@ -455,9 +466,9 @@ export class GettingStartedPage extends EditorPane { this.gettingStartedList?.rerender(); } - private hideExtension(extensionId: string) { - this.setHiddenCategories([...this.getHiddenCategories().add(extensionId)]); - this.registerDispatchListeners(); + private hideVideos() { + this.setHiddenCategories([...this.getHiddenCategories().add('getting-started-videos')]); + this.videoList?.setEntries(undefined); } private markAllStepsComplete() { @@ -807,6 +818,29 @@ export class GettingStartedPage extends EditorPane { const startList = this.buildStartList(); const recentList = this.buildRecentlyOpenedList(); + + const showVideoTutorials = await Promise.race([ + this.tasExperimentService?.getTreatment('gettingStarted.showVideoTutorials'), + new Promise(resolve => setTimeout(() => resolve(false), 200)) + ]); + + let videoList: GettingStartedIndexList; + if (showVideoTutorials === true) { + this.showFeaturedWalkthrough = false; + videoList = this.buildVideosList(); + const layoutVideos = () => { + if (videoList?.itemCount > 0) { + reset(rightColumn, videoList?.getDomElement(), gettingStartedList.getDomElement()); + } + else { + reset(rightColumn, gettingStartedList.getDomElement()); + } + setTimeout(() => this.categoriesPageScrollbar?.scanDomNode(), 50); + layoutRecentList(); + }; + videoList.onDidChange(layoutVideos); + } + const gettingStartedList = this.buildGettingStartedWalkthroughsList(); const footer = $('.footer', {}, @@ -818,19 +852,27 @@ export class GettingStartedPage extends EditorPane { const layoutLists = () => { if (gettingStartedList.itemCount) { this.container.classList.remove('noWalkthroughs'); - reset(rightColumn, gettingStartedList.getDomElement()); + if (videoList?.itemCount > 0) { + reset(rightColumn, videoList?.getDomElement(), gettingStartedList.getDomElement()); + } else { + reset(rightColumn, gettingStartedList.getDomElement()); + } } else { this.container.classList.add('noWalkthroughs'); - reset(rightColumn); - + if (videoList?.itemCount > 0) { + reset(rightColumn, videoList?.getDomElement()); + } + else { + reset(rightColumn); + } } setTimeout(() => this.categoriesPageScrollbar?.scanDomNode(), 50); layoutRecentList(); }; const layoutRecentList = () => { - if (this.container.classList.contains('noWalkthroughs')) { + if (this.container.classList.contains('noWalkthroughs') && videoList?.itemCount === 0) { recentList.setLimit(10); reset(leftColumn, startList.getDomElement()); reset(rightColumn, recentList.getDomElement()); @@ -873,7 +915,7 @@ export class GettingStartedPage extends EditorPane { const telemetryNotice = $('p.telemetry-notice'); this.buildTelemetryFooter(telemetryNotice); footer.appendChild(telemetryNotice); - } else if (!this.productService.openToWelcomeMainPage && !someStepsComplete && !this.hasScrolledToFirstCategory) { + } else if (!this.productService.openToWelcomeMainPage && !someStepsComplete && !this.hasScrolledToFirstCategory && this.showFeaturedWalkthrough) { const firstSessionDateString = this.storageService.get(firstSessionDateStorageKey, StorageScope.APPLICATION) || new Date().toUTCString(); const daysSinceFirstSession = ((+new Date()) - (+new Date(firstSessionDateString))) / 1000 / 60 / 60 / 24; const fistContentBehaviour = daysSinceFirstSession < 1 ? 'openToFirstCategory' : 'index'; @@ -1018,7 +1060,7 @@ export class GettingStartedPage extends EditorPane { const featuredBadge = $('.featured-badge', {}); const descriptionContent = $('.description-content', {},); - if (category.isFeatured) { + if (category.isFeatured && this.showFeaturedWalkthrough) { reset(featuredBadge, $('.featured', {}, $('span.featured-icon.codicon.codicon-star-full'))); reset(descriptionContent, ...renderLabelWithIcons(category.description)); } @@ -1026,7 +1068,7 @@ export class GettingStartedPage extends EditorPane { const titleContent = $('h3.category-title.max-lines-3', { 'x-category-title-for': category.id }); reset(titleContent, ...renderLabelWithIcons(category.title)); - return $('button.getting-started-category' + (category.isFeatured ? '.featured' : ''), + return $('button.getting-started-category' + (category.isFeatured && this.showFeaturedWalkthrough ? '.featured' : ''), { 'x-dispatch': 'selectCategory:' + category.id, 'title': category.description @@ -1090,6 +1132,69 @@ export class GettingStartedPage extends EditorPane { return gettingStartedList; } + private buildVideosList(): GettingStartedIndexList { + + const renderFeaturedExtensions = (entry: IWelcomePageStartEntry): HTMLElement => { + + const featuredBadge = $('.featured-badge', {}); + const descriptionContent = $('.description-content', {},); + + reset(featuredBadge, $('.featured', {}, $('span.featured-icon.codicon.codicon-star-full'))); + reset(descriptionContent, ...renderLabelWithIcons(entry.description)); + + const titleContent = $('h3.category-title.max-lines-3', { 'x-category-title-for': entry.id }); + reset(titleContent, ...renderLabelWithIcons(entry.title)); + + return $('button.getting-started-category' + '.featured', + { + 'x-dispatch': 'openLink:' + entry.command, + 'title': entry.title + }, + featuredBadge, + $('.main-content', {}, + this.iconWidgetFor(entry), + titleContent, + $('a.codicon.codicon-close.hide-category-button', { + 'tabindex': 0, + 'x-dispatch': 'hideVideos', + 'title': localize('close', "Hide"), + 'role': 'button', + 'aria-label': localize('closeAriaLabel', "Hide"), + }), + ), + descriptionContent); + }; + + if (this.videoList) { + this.videoList.dispose(); + } + const videoList = this.videoList = new GettingStartedIndexList( + { + title: '', + klass: 'getting-started-videos', + limit: 1, + renderElement: renderFeaturedExtensions, + contextService: this.contextService, + }); + + if (this.getHiddenCategories().has('getting-started-videos')) { + return videoList; + } + + videoList.setEntries([{ + id: 'getting-started-videos', + title: localize('videos-title', 'Discover Getting Started Tutorials'), + description: localize('videos-description', 'Learn VS Code\'s must-have features in short and practical videos'), + command: 'https://aka.ms/vscode-getting-started-tutorials', + order: 0, + icon: { type: 'icon', icon: Codicon.play }, + when: ContextKeyExpr.true(), + }]); + videoList.onDidChange(() => this.registerDispatchListeners()); + + return videoList; + } + layout(size: Dimension) { this.detailsScrollbar?.scanDomNode(); @@ -1099,6 +1204,7 @@ export class GettingStartedPage extends EditorPane { this.startList?.layout(size); this.gettingStartedList?.layout(size); this.recentlyOpenedList?.layout(size); + this.videoList?.layout(size); if (this.editorInput?.selectedStep && this.currentMediaType) { this.mediaDisposables.clear();

    wWB30if2p0D&qO7e(QkkQ)r$ms@J=*Pf*bKp>k$bC z`;x7J9?sh2Mj?a(wCl6F#l`oqSIren687Z6Wjv(W8c*=@NpYKS&Pm6QJcvDHz#x8Qx|1`4V#w0n>Wtv;>r+gw%t-PM zqI8)s-;D#(#FP*YP5Jp&sG+p+bF2_hV1yH8mCOBcZBcJ9oEEkZ&JTTYCJ2-0hqFSM zOn;mW!ngu(Uf3p?TMeVvC3DpdmNGFZ#W>O$1Pw^D6GF#zO z1$;K5FVH@a>W72>e|rur44bCw0!=ap|G`3w<|Ii5fncJQf8Tf(a?Sv)GBS2WSd^U6 zP!8FLT;%r~6%$6@?^mN=-^tp_AM!mkZn##mx_@68{%k!X4k7GBCAp$^F&Jq6_`k_t znrEBk!ld=k^CBLS{8gowz>Am5S#5j~G!ZfwRj~itK9+B7?q5=#Ytf&0)P)4E0~`SO zzs<{DVow(|(Av|*qfhS4EP;H!~2Sc(;ME6j>Sw0u~0&t!}+9#O)lfSRI8Ih%V88cv9OZjR^mgYFW(fSzu5PhsZE?gfT9v%@M86Fi*_vwd($A-s6=p(`-A|fIqq9URrVj_k_ z#74wL>LbG=BO)UsqavdtV7&AmfB65gi#F z6&)QN6Fnq4HaadwkBj;dF_AG*G0`zGF+*ZvW8#MBhlCG_z(xM3A<;u(h71`JJ0vbv z9~&MU5gQpB6&oEJ6FVd}Ha0E}D8?cCI3yj1XmM~FZ?%|=C6n=iPgv8;CJTF+oCp2^ z97fvm@k}#9Ln7G_uQrxI_TWiDc6vE%Z`8=suzg^^2%Gxi%cB1V5#A)ie_!Vztp?a` zMMBZDu-|IhVM?R(67rl2Tq4nw6JQSn!N#!XmWp;I zY}&_G!KSgfLbO-H28rTcgiUq!3T#T}P1uyq`=Tx3-wF7q!#(x6ngAIRR=taH}HU zUQ}QNyV;IwR?wsYrrhL`BJh-j-8@s_%cN$Ltzaoy;LY-(vXY^5f;5MTmj5l>Y5jjE zYw&9*%dOVn6jxfS$?3Yhj3jS|iI_z9Gbgel#>YcCVtu4%AZF{ zMzTtH+E)J8*nvwaQ^F$g{69cvrM0IV?kR&`hd;qKGg=nHq!1ppnq-=Sjmk z^x-g_E@%Mp_;t!afqx8fFY)vcyk$F&c@}BW`u<7S#DiQ1o6eY}JZ@lsQyod_X;F22 zic?U49UpHo&xhX`#34Db|GJ!_f*0*d13S0>3~3TBH(z=$%r}vC9J{$d)VcLvXDz$x z&y^wnvB)7zM~%iDtn^84R>ZL% zE+WugSr8*3s)}nt7OX+gpw<#PXITrp==}Cmj8vj0Ids30E{hSplMV_Ys+*52vx(5M zQ$p6lyfvF)ViF~IiOrU0vs&^WZ04=miJzl?LD)-){6tXxTr=Gl#Vt)}L3x0{*{VTq9Bc?#lCU)T#n zbt=ht|5^M3d@(~vfQ|?Se9Mu}AjBtnjO7jHPzPyYrg%aVi5oP#dY(gj)x@~k*$P@|3?NA zL_v)6z_O%nAtj1WxmciJVlLgv%rBya4WTdTjbz_Hu{V9G0^C>n0uhrtW~nvHMPs!r z`+kn}jUk~T*e;oP&V#HL+#n?>By8icnV_$x*zwULh&h8pY07sN%KQqf)*=dk3(4%; zNK|9OnP!MNEH{j|mlUEw=3>xTi~3_r4k0Y5P)yEA5c-ymf%7dzIL(E;8gA=j+)2*V zgJ|?X4S~}k^e|drLWW(!i*;~(4`wbcl5zZtH#V#&4;i)(IIRvN#z8igO=c5Aph*Y} zZGmsDV3=e%?InWXds7n3&4juko<3tYLBeDn)Fq;-5mr7sVen`j(48cv`JYVnwPa3YINu9DMmUqPgT(AY!VG+f)hVh$*h5xuFDH3YtFhZK4iLuH#w*QJ!(A(@@x9Vd-I# z+adH5!e4@~1MVw>{{T#JB!6GDZ$B9v)CzV1CfYBBF9l5JiW2+`U^=gs;N`90RjtDB zYUTe)EBGBTJn>9!_mi)U6F-t`G79Hu|WUeSUX^E$3e7hwK_ER_}FG$68}v?{}w(x8|&v*p5P7(QK9 z02Ra_gGN$Nltl_Bnz9Y^-GDsrM?M)DByO0&G`VI#RLzEW2|3XZG`qDA&wiu>R>Fjb zun#GyfzvE1(}-6D8a4$s^`DudZ6E5i%(Y-KFuYl(sk9~{UK=aL_%RbQ(k5dKck7ks zO`rG>2yF&^9xRMia7HeejHWQCcnD!%#IRt~fu(WZzT&D<2r5Kt9mG|Bt;dfwQ@K|39-} z$TGGNHMc=Yme1Yy1!EcOFk=Zd*D{tGW-?<<=8EiPl2D}Sn?i{ZvShiUNXjUcr80^v zkxOA{%>3WaXPNoTb(gUu{a?Qi&pe;=oaa2}dCq>$J?DI8V?Ks?GNypM%~|dkmIL6n z3w$oj$6U{2uC}oi6G!%sK8kTt@h#w{3a;hCI_xxH(sdp%lBekVYC>J`TjG^YtRCT` zbt%K?14CF}#LwuoeUV?*;}=v>6^aTs3h4&O?+p3*NE_-qCL=Q1zapQPreaP?c43;$ zs9w_grrfQ-edR6y)m>>#h1-KR>PsOU1)(xP_nKEIHkY51@lKPeUoyU@mnQ6yZuA%~ zO~HaDe`1m9kyJOvyx*Gdv&FP4k|Z3I6i+=;+r;}cD)OaSROCF#3% zrO|m{|G`6&l2iG*od;RXAZGydrG8p+4}tq(nVamPI^2<_)qT66J#8s9p`EB_kuF6C zOOcE!2t#&sPD$sl&WS6Uzs2y=w^l{dw{F7hO-{h%tsN$B-HZ3=b{6JtGD6$1f1`Tveezq2e$yy8HcK_D&k;=55G&SQ7Yf-ou z>2tENX~pkFL~!u?=DT6u(FIeoHbpbC;jk-N{pI(+VS6(jTI&zu{5I+-(xV1 zN}y>}myzi`>AJq&m_)@3GdsEo3K7PO6Q|-By3&-XwK!uUazaWb%;iRn^n z{+uwi7sNr77;&=HiuM{NrN!{Gr8I=?ge9PkG&>TNq5#xZwh%u>+7Zb_QDA0-Qveup z^7EnY#q*)=Mf0K7DT821mqDHQNl+JzvPX26Pk;KQ4sALtttozpZc4v+H0765nkFlo zs+w}FCob*jGMsV{b8xtxNoqM(bj71eCnw!dN#%p=LBd68r&kJn$|mTsiwo|KVk$zv-#xJp@hf3pP;NQ~ww!j5K-14+sD112u2+k$wd6 zjd5{xi6@*MS&YaXEP3Jd2Q4PhOk@By_|yGGbY@dQ-~Z*;?!+|`{uV>(hN)BLZ$jdS zarnUqE1~$EE{zTRNH{Mp!uTQZGpSEeWCkE@zXi277M?y(R^Si4l#=L zRfvktj$qFxJ}8Gi8SzlxDajm;cdC1mdm`R-GWUyk=jSe>PE$p>~N^$NOV4_hG!#zPRGXh~Kh_H8f$g zLxueS;-K}GK;>587UqC?;BN^2P5|noQQeuiT9wc-9_fdVOCn2{X07mBWN^8{?zF^b zlK8`0aR^8AbedyQeoAgiJBp9)SCD8~B299)hBB1*h24isgIn}kTiJsubU?VK2zM4h=~wids_>1Ek^N~3%O5R}Cd6q>`Ym)525&Tv z4j3nTa*8Q=ozT8~0{*%bMs$CVce~7e6z?$r`hIsn!QzbRGsvMfD-vne5m<85_XerV zN^UwUMZX1L!!M2!s8tMi>L&2NKExF7)L^*fBc5MPrU6JCe)$-R;Zqtvo&bTo6R-ds zhjk%UPc}S#U~)3+oZbq{(dkJYC~I0%&LEDWToS(qFAcr04)AgCa!SX%v%sf-zNyfg z>PqMIZb|$nxMlH3JaooE;Gy9Hx>_GRE|I6W4q-$=SKgxaqOL>u?S+~ST6g9*bqFO4 zC8hRyVN4$=pM@#8RCO<*b~ z#rpD#F#K&VCG<-g!FGd}#x+uWwB|=Lx}a4R*KY7aQDO_usGIv8tbxh;A|w?exf`X8kZ2qjd%aRxc7-Cw6KP-$7xuD!O&=j1xC)LlLi!bduH- zXoak=AZJ9{c*IG%Rz==ufh9L?KruW45A{30C{gR*2X^63n_{ppUYLkSYF$E9E>y$EZBu3OY*-j)W zTI2O@nKS}> zrb^IA28HmJ%hRAw(R^$f(gI1#Ka@}OPV-1ApS1oyX#R|MQAzLT5Q zmdTHJX^cndP3!KoPE5R1{%BrH@`y%$#7{D5%tmVsRKAHuJk&OkT#AFJ8UrkVSU^ueGGGi~5?}$~L%?Rh9>7n4uq^D$2P6QV0gMK?05bq@ z09FI`01g690ImW;@LNhfKx2Rj5C=#CqynY@UIi=$kUeAvr6r{e0i_|irSu?wvWe1# zZx2LF{!$)LdXW86dXrpAOA1FIr7!uDeDbF-f(i*IIUs&a>!dU$>bfj@piL^eBJubgu9h5&~g$zm=(Xrn++!D$E zj1Z(Nx^wwKea#IQJfKhEJLiqiKM8toLE)gbDS`HmVE-xlMM^{r1n5X9y5^#33??`H z?6Y{kDhv|%=;w5-zzPmz63spMEvwB)Y_U_C$o8j$;!*^dlcZoEPuHHHW295SqGz+I z+l0NnGl7lJQF^2{T#heeJT1AYZKsFuBG4r_hO7KTxORSFq2%W42WWEaIapZq#b9bb z=ppPJTTm#uX`PZD!tO1eLdji@r{avQ47-L7E|lCgc&tm>DY>b= zq=zt-vqr!a#&IF$4xSI8U$f+UzOkKwM($6KOdi^AI6hZ27Gr%g>&?cn`D`6K!2ZOz zg$wB%5``}v_hDn%0`@Wcj{U`|g(yS1gh+!s1%^vhprqLYzfup$r`*DQ`X`YI)P5K7 z<9q2hOgV%@=A;ml!u>6VYb6ymG;Y{kge5v0M5J#gil0#AcOawa4#c=71$Q7MV!;Ck z7x~td^y2+#f40~#^4@o@4tggl_5t8mJ>2+5-+xL}<`t7vMLXOPFh1ZjD$`j`_d0_Q!Dn z=i4FkDFm;l666(0k}78r;nrSyeeNY!6r+&E7asp@5|OFiKMUCWc^S5OLm475$&bbh zG!El-m0{e*l72K+NI*Wsz%NGh1L>xoWqVq);6Ez=ydM&LRN{00(dY;s8!SD!>KE1Y`lS0d7DJAQ#{Pu*(P!Pyjf9 z0}uyr0#X4kKqepykPUDHasasi4}e`kcz^=H0UUrhfD@1kZ~-y_S%7SS8;}FY1$Y1~ z58(j{00(dY;s8!SD!>KE1Y`lS0d7DJAQ#{Pu&W3UPyjf90}uyr0#X4kKqepykPUDH zasasi4}e`mcz^=H0UUrhfD@1kZ~-y_S%7SS8_+tu48IN)>I!wGqD(bGUMvDu0B?fd zWx((-2h7)ft^uE8LL>r0O*)zD&6dqg|Ru6VI!y%{iehsw5TTfMn@4K3D+<-xY5f9wR z4c4qxw}b}0TD2gaMqV@`HYrI`ntvAs$C1|m{JcAdFh!!teE6^MzX}r9c610xI zymlpNEh*lPC24%vxDvEDK5Rls8qe3O1TBH*>syk>^Ep9N%t2b=uo!Jtb?p?&qu_49 zr^y1)+Cd+YHiC4^1&!8-L|WPyAvo)WvOBPtcT`Gp`Y>`+d-)^EqsTvQ+&HWtfWBal zm{%jx2Q-Ln!RxuG1g$r(XO^6nAn+|IK@<3vfd;iumXgze&|a3|e-9>igWdwq3eeKg zAvP10RvasX@rLAXFoweQ1?@+o3FS;hG{Y}t`1W0-sfpGRG+BAn5N*hDqyYw2BA1Nz#QB zks@Jjn@6v`o>>0ZTS#29d$=4J?Uw za}Zt!?m3ZoPX*zP!*{JD-Wp$*Oc!rESVGGpUPlmK*O+47Mb?*m?P!OlOD-kuGucsGYVW>T1loRT(*LHM}H#>`EEE)HO+ zoLGW{iTk8j{*WNN*_#EOGJ#1x)u%;4!o)q=nol3kBLa>T-5ju_V{Q-`sauP6thhgL z8hEw|Ix=^08aRT4$q{)e-;%*gc`)89jFc~w=Kji4Cc-#~L3|=VE!KOdDEGiE$UWsH zH%F9P2JzJUM(Af^faQ-|Ux`jM_Jx=}c$MrRI@um3;$%@4xhapye->y`KXM!a;~>WY zy7V}`+^KL&<<^a`BqJw)j*>2tJmM!fjxY|12+UzmHpwO3YJXeOW{Hi$k-W&gf~`&w zJAfsf5`u(ve=USf1(w203lb(LM+oBqrZA+(<{)8G4+y-Jm#4r>`4D-aq+OCd@G5$U z=Ke{DkBCzG5nY~_22oZR=u%$x0!^Nm9>{UPzv#g}bw`l{`oJyOGs{aiiA~Zm66YNy z-VH%`Ju=?oL3p`eiseRrS2BIPbzmooc`ZSB9jA(UhXmnOoG#{_8-&+$u9(*ygja!c z%u<@24Z`cZSj=1d`@rdtnlJEDd58(Z%aw`YlQ9lh(q&ALFjqsr;Pi1@ig^bH;Z1E*%$pg6mvt)U-5iA1*`=8GR1jX) zy_mPg!N9h8dI-Ff_l_XET;CYJ{m2BSyr(efLBhoK6VlxUOkpUmmIVpJ4HM#V0#kdD z2`q&@9wcnsC?UR7U~21ffTggCLxIyGbDR*yA>v$M3QIcl4H8y?ef|>fv>?0=m%vNu zw>B8>i(*;;lY9zuDoB{zsX`crv)__V^$rKNoy`z<%RqiR@KQXfUSXuR8(u{Z(VVk{ zG$*3J{$+tEOWwb108QS%WJ8XFgouyZ%bf!^*+VkUdg&&Gk#zLDEa;RC%EIo1HC7sA`jF()AN0RSaT&(|+ zAiQx+h4FtXu#|2GgM?x43wm=R&IBgC-M~`1)%&TW?cO>%R|qm(z>-~YLBgc2D&`#@ zgg0_sG4HYt4i2)ZbMNf-J(dUB93j>Ce@PGCxt9AL6raYVqmBF+?Xs)!jd@pC`M@NK3G;V8~B zpyeS>UPf+7+obvjmZFDfalZ&U5RuZ7Fwsc{wHJijiL&JF^$^hH?R6I9IKV4C*(xq5SHZbETxVv>=%(9e>$G^p<~5kC3Vb(9ET(b*zM)c5#uQk(_ycO$$dCKhW!Dc zNAkxu2tK{YU7cYs6Cm#w$_3&1?MxZAf-A!eSVxzhzd$>yEW{oa#}`01Gkw+i{u z0PsC@rDrAL+5_kYNCylAOaoW|4*?+RStP53?yV}FJ~pv*mn+}JvUn~nz3 zHr$@99BC+Q26Wqu@B8kA(k)PuFfZ&qygt_boj;66Y7=yYS z8Q%*TmBdbjiTmkq@L3ujkHhu@h9sm25uk4KdE90wK1f1o1MA-x(*;KWlj$g_S`|OM zUn-8)r!)3en3y?g6(6K0d%Kv@ncx4+?ki4h!6e9B$YQYbI%P0UXyfZb{f6~qZOBTB zQFbY0%dujSV$X{@2r4Cqb;1@ZdZ+A(4{1~4z#Y!jhQ<#`87-VxVr&guBXPqBPJkkr zp268oTn3MW5Og~fV_ie(it?D`!Lhhkr>Ibk05x4CCh%swPNhf?*^P3#0=XgOne>!Y zDW-QQl;A)?Ma+E36E3n6(l>S>zA)uWAED^6@K8|&Buq~h;hl0V7;{Lqi*|g|IZ5`> z4Y4rxdnj#gr!xU9lW+@s@lJP^O=VFmFlURnXlEH6c*I>Q%;Y=8+79@3G-O`ggg9kUh;+Sbnif|JcOAGULZmMz_{`@Pp&C{u}p6rt_ ze)~I)5%aqzVM!~x3;G{k4%>n~M3p{~3w)n4)2>77D_LqF092TrYHl|pb7 zT57V{cs^%~P6o-21T7^US_$R3=u9;9xE_X_ZiUa_;3Gidxd)6nOBS7Y9FC*A*nX4J zPi%`M`au>mG8K31V5gv1q(X|08<8c&r;bsw5Vs(?4c$C3AQi0v_G{y982YSs^p=){Hm)tb4MT&2b~-V! z)tE7D=wc8-$q{P&`U0wWTU}z&c_Q46=7WlpF~}$EEz&ZPHE{^K0DSIJBOaPzJzC*# zO+sv^)=CX0Xx@?~rX>wY95gf;S&U#wI5P%HdSbs+TttX%%s7RdNCZj}Y-GlnYwU!n z!zi*rLkA;I5{Kj4F)5=Hv1e)&oxZ_sA=1eg$xC+81btH`aWLeIT)tQgf($4$47-(i zPI^yF!W}HQ`wXepkSxSYf}ral9AQHqmVu_JiHW2MI;^tAF6dPY8Vh0X$D0t^&JstU z0mDY=5sAXSbn)aNk|7aA1u-NJz^Q@$IK4yrghiVLRv~S9TXfDOrw*3Tf@5#7fNsOm<9C-X5%(nYMK0cSAjIK z)lC`E1_ugJMkT5IK=c3p+;j?LJJ31F9%sv9cZiHmuZ?!J<|+J5N&NjPaANNxroDx! z_y4-8r(g z3rVIwoQiWN^yHo>DT8Pu7YMdUc1xK2d_A3ib9~~9URv7fI@@9>4k=xm*<19WhMB*W z$>Auim^;K5&ad-ujJe7{B8CJmY++@Yz_o#w0RzdNa{T&`8+2O3IS3pE>;Vo3jtCLt zD1d2Q&;U$lJK}(WWKRe?jexjI#w8=-HH1HVMl>Y73^Vmh8`f-;GMerm9y$n`3@Ia8;Oh&xJ!b&*B-ZH0(F!^oV~y@4McJ%Jd@Bq$H0R^QF*@l>2DTpY3JN29qZ~)7G4)LvVQDUBAvLkf z91}>IeUOA=cX|skEqL;c<`8rF^3+U7ItUbR&4u}7S~FU~z-1aVzQ!OUEn46%N)sKW z2+~9#`%M_pdt`qMwQ+JjfiAGMM(eajZGRm;8lo96V32a)AW=#(jt`B*9kZ08d_X>t z%2OH$HkYumm@FiP1z+$?!$n7fQc77GRMA2TrqLHiahtA~a79Pu;zrv?ngHo!9wKq$f3e1Zfo?Qe#vP`VtymLnit9N5^rfP z*Td4h$Qp{=g0VM0S)tQ_r7P=d(K?hjI;w#25#2PACWJPPPE%QEnK#6`t_-I!C73yFZ-b_}O8YxMoO4L-=EnVd+<%`|0}i_Y+pX zx?s_=i?)$^rP^M8$P+(g%wIb<{oT*bZol0B;@@9f3uW<77XEPR{P3DBQwNMRZmi1I zo*T=`&K}ph=Jhk+8gi8u{HaWvSCT*!gy7d^$J|Fea1J&5t4+~hur>vR=Y)Bt7qBrwz&xO?fBid4|hz-==0N&uuxay z&*~H?^{dvtJcDJKE=`0Au*_J^1LI`+SEzdkc|I?L)+Fg0W0 zHj4a}J#`CKXInj~ZTtTkk~@|)Z2Vka!_x`h^o*_Y`49Uop>c0qoSJcV=e5w@ZCU$n zKX`V8gvM2v5>-CZRgKGUH<^vu<*EO0Xl|Vyvv-bZoZa@x!p_Hjd!x(eJzv;Um8E>@ zSyf{~kID@`yWd@XO4RFBS@YTxYAe`7Tt3&5tx)E3F{}yxw!zOx`Kd0}%b9m<@{?>+ z!K6c1KHlls@yh0?@H5wD&-rc6*A2Jy{2{T!CN86K$7U7ni(;c(=ibivW6hG;XReK{ znezIf7mrPCH8eE)xl`-kY~OqN@6CU-x4aey#u>F-LtAY-7xPWek=I{;`UrGRJ)6hU zufJBg?zJB(_IV*~{=Uq&W*nQErkqg0)A`vc8L5p=cYZSCp$pIaw#)Ml+f~k~T=@8A z&%^IkkIfj~qt=Ojd0nR&-U@41JtX$p)J+8|m+h;_oc?#L zJ91+4uG*8SEx5(-doY3bAD*r!nk8wk2ktMP~o-To^q|N>RHz?LfQL^ zLeJ3-ruZ=j8z~+;@s(p}{oDZ^B5nEUj~4F8XnK0Zwb^|Je6)6S*n2Oz25xK^v18ku z^R@b}Z20{C{<>9XzI|=lBiGNlPuI`P=n(h0XKKTY53lr8%&_bWYZo9GSEI_$do=Xf_2C6m!Ctxl zpB>$ns+o&F4nJSHphCga!qM~c4Re2L)#A_Axe4Fw_^n_4Z}T21XdAn2i1N?BFAUi* z`0rkx@l_44{1r9w`iY+lC!ar7_{iz{9VXQKvS16l8m)-USoV9*l;P#aS1CN-d+*mz zT&!RAZ`N*Oea{D@hLz8+Z0|53=45@=?T?7fTN`@J`Ax3A67zGdNt=_Decu1Q;pu1I zJD;~}P{Y(_rzefCTzEcz`Rs)J6|C&oo}u4lH}vRe%2fW;YpM*74Ca zSFJ@C>(|{DlG%GE>o8&9Ni+K$Jj*fLg zGS^=H_Cm)?F|LO8y{}bg%ioL0K0funq%w~89}X=X_(hfGd+u{3JRSc1oPw2~o&Dxo zOIM56Lo(aXbbq*{O7FcrzONNJ>$SRU`P_*7_?d_5beJ$EVH9hyud4HlDp&SYaUEBN zHo4k$vME$isjA1LzO=4EXE?`&=3GjP&FGal<s~bAX z7C`w9C$@ZW{%G&Lw#Jq&?)dP7)fC3Wkcb_H#&1XW>2R&n%qQ}m7{4K-u;bQf1%kAk z5SlqU?|jY0UuOJy_UNU$As>gg`?M{)SUIED#pZ*mtSVSKp}u<_+kdE>;;6@-c=7x% z`p{~9LSqKjb&cOqcx_Hbt)o?)9bAphhGwSZjc$H*^o0i@!th2XcClh%L5G4T$G3I# z@8GI`yozJ-vA4D!Kl)h)RLWJ14a*#ySB2!+LzByMPi$pNq6*e6Y2KDZM1>$3AK6{q z_1BJ!=BGQ(Jfu2yC1TGs*nGC(^X!`{h3h``9Br8Y(7xBIPF&hF#1l3&EOS)e)P{vW z9=DE9igC=beenDF1DB#!G!HLWa-ZYyv3={0AANUG#G=NQQD=X=)-t!l8*Isg1^G~K z&vf_nCH96-K9E?h-t;I}`i_)Oc-D5`toptdv;R_7@ssE8UKh__tTD0AuaAdZzP~v; zJ>}>Rb28?CcJ`C|*Qj57rtFV@8~5~#Rp&nwTWe+Efx=flOJ2Eh{zdDr%&_~y<1WpP z^BG?h&igF6S&z`vW}n~hYBwMxQc%?{OW_e_`RlbUyLc6Ic_q5G<`_>Z?;{V+PTON8sOzjwW`e%f1=-Q`1_ zFGjdSLY>ni+_gip+k`u(u`!NtCpwK5;Z7GD(=t4>PKakpgra_EZq=%eN5b8YN4qLU zxCY0t=~dbJX{=f_YZy^AumAIPmQ@L9)Oy3PHzE{`L*uGNyLLx7hMt+hns-&UVeJzp zgs*IP{iCtdD^&b!tTN(apS|-I&YwMg-PXBQYx_-e)+YaQb@q!2*6{KAq3h?YT)W_> zUWpM;MYDJRtTS=llDe_;d+&W{)~Yh*NY?{-)7h8#pGLZRA8eDm@Y9?%dz)7bSr+?q z#EOPZYhJ!mrOwuwzk4Us#}#$iaPQuH6@V zzQObJ)0cW4TV3wk4?;6r=QVKtu_C?)E!+!m9O1TU5JLX4(>~`zQ9{K!arTmCBBf}NyL}l$Gwv=AeqxyY6 zo7KK3urAFwvH9)s;qK`p`e&beYTvJs1#kR0MHR`W?s;kZ$9*omGW2MKE3J!iL*`#U zU+(|EI$zh#+r3sB8kf1Ua9MU^Lr<&npl*?V#m=UOPgixdTvv6{upj^TVBL^qj)ltF zzuteYRaMuXNTO}IFJxJ5<(T`{52@~I6FF)5;SH@9GR?Hw_1;<8XKi`MuhUv|Jn_^2 zIx$bilXlCAQ+>age&M?VpPy`cIGnADE^9o*`#sX%+3-4%UX2WE{>zVGPv!dOepdjY zET%=p_N$+tdHJn*wFbPzmN+WLMTBNPpmYrgS1gU3wDFhCoyr>?2+58t{O)OYiwKvt zKWi24uHN6ZH^S9(C#!>DR%BR%maKiNijh@A8r4y{pB-M_(R5nK!0p6^H5mG zvaZ%uJz802&uP~1r&%d8qS?CoWk396-rD^AVLc)o9j3K-{2>(IvW5MA$asaVol*P1 zsUH1K&HuX~ES&AkO5Rub(t8Cv!r8v6le%plw@hC%#67!e*z8dco$1t?Wk>aQ4n5y$ z%kl{J-hIjqkty-*hh2R8=Y(*!>)3ZsuYPbO@jEO#b8B~nm}u5+Qth*kpWzu!Bh7cb z{qt*=zU*3_U3}VHc726b83UewI4qQzhwXHJxYT|wD!KWIhM_F#%*^dumOP$hJFsvD zbHBB;_TKJ4ADP>+3JLUlIIHIe8;#_XRj;6b?jLa}jYD0kL!Y@X@v)rvvO4A%#JQ|P ze(f59K3gi!#$;q%JZ3Zfw$-kY!7KUP;$4pSPsOFYUl>y6K!u@WXW0yxkFnU+onuzE zbiRPsMgRxE1qfjZ{KX?%hY|??&A)rcu{b~$z#~E?u;RaIP3g3TeR7)%;r0b?;wzoR zd!MV6Hv3A4T!eK2WGkunzP+U3ZP@H99f+0%aNNt*{{uGrN{bxC;{@CjW#TT`>?<9p z&HPu|%YV^kU+F+J_di`P?m?SNq{TmBKWydzRK}>Rm5A?NbKfcZVV7ic?muW_4rIFl zE?9jg0c4}!@){^FZ^aLB z-7)(i%b(4;@FN>D0c0oHcuVE=R%{Fu2726<{bUD#?2>HE!aLa*2XF+6`(E&aj<;(+ z<>ei%lXo*+ZrA?1sp~%x*MHT1Dsuqm?WW0p(SFhP98q@= zgn_=4?~?7_dS}DWaWnG&OZEq`U7i-X;B^vzu(bHE*nbnY%hSR0|AYOvXgi+{|8@Is z$95{)|7H7c*Yw6;^)6}2UJ4_Z#7IQ-$#$Oy#aPp(8q*g$FEhQ9+qb_KH~ z2RsT%F5yyyx$EtpTtDd@dIm~^V0M$g&$jcwV!xPIw~_`W%Da@F68VI{i6cdJ;E69_~0t=KN50hJerFZrd>*;hC@tu!(M@yKQQ zdPmqmwv%m{f#l2lT;B3!eo|W9fW57&ANTbh75nUlkP?1 zcki(9D{UH(-7dgAXFqiEXL}C(6y7#_yTNlaX>c#vPj-6imE~<`Aorb2gFx|+pATKV zbr0zFB_A@W3}gZ*Eu6l1g3%}(AYk03@h?R@w-rY)`M2YLC(?Z->(1d zra`dy1KCy@zhLt3gg^9f06eA9$D1cu{NDUG>kcOWPWeMGYEy1H4T8mgv-W!P2a|s% z{ckc2vcd0ntRd;;ZO_fRB{_Es2T21eADI9}Y4y2Td%gKf8_%8OgAN>kY?b=1T)b0% z>36Q|FMgQ}N!Cqae{%n6I`pPA$ObrWC$9gh{kLPYJnaA3UsQ)~s@zNICQr-%;=5$W zEn@e7(Pp{Mw_rE5yO{umT+V;>{U+?s0q-sKG52!Xl*0Zj@Hp;e8*WE8Klamj!UeeL z@x<-u@^6IovfmAN&@uDBq31spC+xkg_Kfa@EzFC;A3W}IJSq^bSS|E0W+#G+G4jZV z4I#j}WD77M;5-q#1&qMV4mtuyfdzpP7T@_}VSJEWl0ZQT0oz5)BjQIqLlL0k>J$C3tI0}jY=atJ9{KMR#l7o1hcS`@er1K4S+>ZYLmd=Q`$c~%U{|@O)_0bK;1W>(n02HJPaFK4M zc`uFrki!8eE>CG>iaf+iI%WY}fH(kkNdX`5+!6gLEbt0(Q9io? z0qYti9C(}}e^J>ejk`pm%N(T%E9a2~C0(-Qet4(4=8*Hq-{E&lx>Fo-Ir4Y%yBUy& zrFVq&wt?iieDDR+JsaV2{ORH?PjdU?_eYn)$uW3++2979FHgY1bWbID;G-};(^DRw z{M{eFKe{}u{2lyRKEhCWa`~W3I_AQ!sP5zaU(=s#LHt<(bd_|LFqM}Q_5I)0U$h~$ z6gKeXMV^0R*nc(sC4B;=BmA5apEnK^&YM?qmm;mC{N}L~&L4)n(v+clGW|=byObXH zRQdO#doJiMfTXiGCjWoE{qduFQToE)8Nddzi7!vUZhw3M%By#p1ay1LL3~+0;=G}J zgD02`WN(fSS-d{p@k(xg{Ql@tIKtrZ);$-rqVgc;75RhN;5Y5OWl3&-ditYF;Y58> z!6&7e8}E^Fe_!w5xg{HX$r5$&YqKvKe8~Zy!WZ2i?N)3EAnUgE_a{3L{bp_OC(BnF z2cr3tb1U?lvB97GK)U+!^C#z4>7}s21D;HO@_p$Th~`hu?a=+$Ky4Y-^?+?cAYFa= z-Hx7t!iu_5UCahJ0`bcIeCZg7CJ%crya&?Nm*2ghvs?#XItHT2!`=(;fpqoqqkh(T zFX$}S!AnP9eOVTG9P%*t(z_r1sqJzB0=8ZEf*oG^yWx(z7xXHv4$znH-+&dR2}8Sv zCW`xySO&izDVJ5@F{6)C``iZx!L$#IG84O-XeSiE4~#JF10zg3k_cyr7>IXc8^VNIDoWI%OU%2 zG{|@Tp)wDkc81E*O_isAN?9+Re6@>2yQ}rJlsfs+H5XxBfXF+YUbmr>FFlDy<-l<} zdj11C-Jt8u>UW29qI|qT7hkl}=y!`cQT=oSG67Vk8~{#=5AV07AHv50vH%oMAX%hC zHh^@XFb+wFKp1?1WET0!WgrX($OL%2!sozEdXNrIfCF$_bqwK)bO`b;=|u4pkbbEE z(m^5D|4zO`cB%Edlkt{TR%!Jst-R9kAx>v$)BJAhhxoE&anidZkWDh)fcYFqMrr-L zbd$$N^~&i>&fU^4fNn*4{#*K$MmI{QJEb3FDFD>A6t(ldWD^ZM^72-cR-oVT?n{O* zK3_B`Ec~1@e-GZ{BtLHqzf$W)GQGo-TNc+1_Q?I@VSU7#DfbuU`qPbUBDf(wZyBPT z8~92S)`yNcpecgrMm!wA4Y(oRJE@-^Jt$qWeZ>tLr6=DP1K$<>{OIVNE~K+lo;G(= zzaTp1K$jcp_&uZ_rQHqn`)=xoxct>qc|Mn@-*-bl#1ZL3H_FdUfLu4}o%|GHdH6@` z2jU^!C_X7}!t(m*^zxJVJfOz`{xSU^+gmq^(@E(fe~1Q<^zz0O#+!CW`VTQ)Nq#Qe zH`Gr_Pm%WqJ$&g#G%1`M`{VVd%frw+{2hQuZ@RY|Jb}`&gzny9$?dI&Kew;2rKOct zM_=;%>EliJ74~LmrP0xsJa0YxxqXGbSy})cv*72m9Q)J9n|`yp_~V!9n2UE8AYd8x z*2ACMUwpShM;t!W%9kvE`gqfQg}oazZ$13E?}jeE;_#=Bj80|O;VbOjpvm-*x0R(T zyZ?fIZs;3#H{vaA9MFgQ(oBFNP#FAtl$!wtU>3#WJo^t%*~5rD@TA}%aE$-4^+w~N>{p7;@sfC~itYrcTD=LtC1BVdn0zj^5u%*+@c&w?w?{ju&1ORg)Y%8>3Ke1&{MDn=~Cnt(xot8NT9|y<+cmTfS-ZU*xx+9KAfD@2?r{eRL;~hWz6@XMgjyLZ=;imW@ zmjh%1a&IyYH~1xR;avg;-X#c@Cmvax?qKq~(<_);CflbT!NT9J|NpP?!>&j`4uJBH z^4(`$2Hj`6OY~bwUy2Jram!`%dU(t8;*s<7@80~u+}adVfZQJZtr*alMcDwGURR_VM-mp zcUWI;#1#q1k;f^2_YNcX^Y!k{Um7>$_|m}xy30GfuQ=s2Z~oG{WjgTjlG|5Ud0f8U zeTBJ`G+%MbX?HR%U$W$J$=<09y8u4Z%$J;7rOD!?df*1c-Kwlx35WQod}IO?zQSXd zY80+4EJv#eg}HS9A?{iPxBv_n71H_(4_IBm&4pNRslv|ZR$)8bRoHe{6|A#VVVoymG~(xmk3T^hX@L9n?)V{$)_TA+YjRQDZjLW5k3+?wvvrxSEM{F+_x;B zWC2LFTn1I=qQ}dxh_8sj&kMpSe8}d*%Yy`x55Ft`+3oZS!-@1Fec_u?(0d-K3e z@+JRF3J15hY>Gcl##b~46#1p_C9tFe!ntH&bMQ{`BwlhWyyZw?z3H-W-a0tte&RcC zk0eX(FVXG=`OxbhOCQM2lG%O7^H-El`RVjZQw}tFK1=z{V=vwkev-a&9$)X^Qvj&^ z+z?*Q@5`TPavp#0;EM!!$8ks62PsZj9F$&E79u4-!f@YUAJODIxAX2te(vqal!x__ z?}j^0?ti=QkVom32~fyo73P=2_$-7^D9SJwd6skIyp@|3ZU;;Rgt*zufXM)NRX59h zRseb@8u<|q&$}I%!WM1^?BahfqKr}iq;Z%d9FE&~U_+KadxD7-!+Mt$cd&5e?&dO5|oe1`X< z2k}zc$Zh&Z!Xpk}_DXRD)Za(gQm3C+*qhEjgr~SDjdB1tU4Eqe3xpxpZ`uS3C-?Is z!!73t_AbhxG|B`hh`;a{zzuL=dI)z}0A86JDUpj(LqP9DBR}FHUOo)MQtMKV5Z}sd zsYKY}5L}H~G8W1>NT3#I8E&r+V~sFDB-ap}kql=Fh3JPE%COp)IFjEq%ooeDNYHpr zv|8oxhKZ*5^bK{pLizVkn4ZTy*ie?nMzVe^8R6quI^G9@mI7BR{2j=Wz!$+XLm2A> zf8rSeS~AZcgTI4C*#ZiUyN^|1?;-@|w|HJ)-EpPQK*&#r)ZyS(u+qfYjL|>&6Tw`N zT^F(|v#O6WHVk?XfYfB*M8q)y+78CwB)HRL^W0 zrg~8IwrZp5chwi_U)2>fk7@d7W@}#4yr)^A*`nE@Ii@+O@n{TMoAzmKs&=e4Lwi_T zPS;#FROiwy)ekV#H)%{0O|O{VHmx=}%}M5&=1lV&=B4I!=FiM8Sf*KCvCOwDwyd(O zwQRL~Y5CrA&Z4(kt!=Cwt?}05)-%@2)_iN2ZK3UL+lRI_woSG}wkUhL{c}5%RNy-s zKo4^DIX!3PI&tw_Z!VD=%8lYCatpZC+^=d0Uk253iUr)w8z_iKO9=4yY_p3^qcb=8g2&C<=$E!Hj3eW1(M zZPM+~eXTpKyRNIPx9MZ_J@kF`Df*H6S^7EpxAocjZTjY>E~aNq+fC<9mCO&CIdcbd zx_N^63-j0JBj)4gKg{o1c3Jkq%3opUSnDe5XVyK|1J+~Kv(`s!O>Et5y={YSskRK; za@#7~2HRHKm$oCe2kp`JCU&De#y-yOvcG7jUN0ARSLf;}Ib~;Myz)uq)5?*`Im#u< z_m$g~yOh5uPbnL#G^%#0bkzjaG}Sd4pAEj@i*Xt`9su?;Mx*O6A^9^qs z)*12)4;#~rV~j5tXB*!zt}uq1IMWlRL{qA1q-mC^g4t{yY@TX<$^5SQee-(rPV+wV zN%IwRsHKL*WBJ`y(SE=EQM=XN$^NW;y8V6oTKi}ABlh3z^o!z;u&)x=fNRXP;-2D$ zaU+nQE4hu_A?`SLmb=W=R5nn)pq#BdudJl1uZmU0t6ozrRP9&&psJ{LsC%eKsozj% zsq1PU)(q4o=MoHHDf7wQB7X+5y_V$m=88Q`$eZ&2?5?if)x|o$j#i zxXy^YZKv<9cj|ZO3-s*_&l?sRRvGpiP8up1YZ?0*`x%EDpEJH_{KV)s?lJyg{KZ(u z)WS5xwA}Q)=~t809B&?EPBnM7q*!KK-bb0fWUX#{(ALHlXM<63j2#3z&Q(?pRi-Fi z$|=e>mG3AIC=V;ks8lM0>M7MQ)qK>qpHvrA71RyXCUqBes(QM5wtBVtXZ3M)Jx#R6 zsIhAXXokWrmu8vfBTbIxjOH&*H`Kpz+PT`r+OM@|wU@P#x(9SKbZd20^|kaWy+NO# zAEwtDdK<(EL$wcESD^Q zTPj+sSs$}Dw`N+m@}>2vHO02Xw#RnBcFk7X{_ z)V*umLrOL5+pi2!wL~sGp?XUdqi(NWsa~t@jWT#hb3t=i(^%UUraS<0<1G#!9B@rp73tZB3m_DW=(`C(X?*8cTbN6ZP@9rHOSN z+M9=L4N&j4*ml9XUu?|Y!rsOnXMfWE47E3@j2!^#!;R;TD07u(lvk9YDx0cNLy{4z8zh;hRzUFPsO3h}pHbcMeKUP)eFwgcc~8GWe^MWAsBCCrXk+MP z$TGZdNH8WE2O3{DE;hbnyk@Lss%0{mTAG}ubkhXW4AWaEktdSuQ#SFt&hU11B##m!K()|zPpT_IP zvZiV#hpC-undw8*CesenSEg`td1zD5{J426w3%jp&75UEWd7A$)6&+`9eu$&mJcnP zET37zt#<2M){|%(FQW&jY-?-lYIC7o+-S4eTcOqHZBMc zsK=`F)QOsfnx&dOn(z4Cy5a=~)Na-X%TwWYP4wU4!*b)5Bo))m$= zwkkHx7K_&YBinl0W?KYWLZ!WneKUHtFR83$3BPY>I2&5P1hjzzxXIkhuxTy#9ruoM zx$>eiPZ_2vuWE(X?|IcE^k*AY=Tv{H9#q#?cT&fz=d0gTXRAL}|DY~Z8`0)Z)vVH- z)-bJJo2Y$Vo2@;mt)h$7Id$`O+jLja#<$Twr5~iv&`;OT*RRsAgdgkn>L$1H|;YWGCg8GZ2sL` z+p@^=KlJN6EmN#>Z1d5Fzi(S>yJTx-H=x~5M}PK)-7WOx*^I@&Y%Mnw{n&iv+vv&G zDzjAYsSct*PhV-WJou*vBcY6vN1PfO(3HN z7sXXk>Xki|qm^@&UnsYzzE^qBmeyCtsT0*(QPXm@EQevF%vdW`f+|Th7Nub!>hQ-H z6YN(VQJqkoS6xMYuB5J|ei-FLt8SxCMq4{WGgtGX?iF1FJ7>3`1opJ|L(4GIo`L@B zRr{MLjjQdSpcnhze%$_tJ&*D+SFkIbtHyOuJqtbZ)qS;twBhEOW}~^4xs$o4xgW~T zNOOjHy7^V}n;03bHh*GvTTWOk*45V2wm)t3L)T=8+Kd+U5cey0j=REzDBn`8QhlPT zrM9bILR-33y<7c_x&>O98JhOmf!Y_*n{U!ubx-L2)QvJcXIO7IVXzx}8D|>Tpx@eI ztYNBWYGC@nTnjbsf5@McmKN5|XyLQ1`>lUl@3&b|HlMJ)V!L81Z~p)m(VQh6I)roO zxt82w&do(B+oD}}EB9$0)wR^M*Y(v6!)R%?E=y=R zoHJiHSF@NcyDdj7e^^4SHLQ(M8zx&{wz~O#CBjw{{p4i(JNB>ar|p;QEHZ@kLY(Ef zI-G^;!#%^z3G^v72$Oc8=P4`Zv8>y=&AcGT<2vOuJ1lp~YgBmX?l|PS$SLp0Hx6b(=NE z8fsro`Oby#c{V^fOIb~oqE1(5sy|m>R#(IUi4^pnwtZPb5? zG1Vx;G{b6?id_biv4?S}ak_DiakuenSbo&#L5tGVq&C$yw=wrJPc*-5o^KAf+;8cM zTvS+9R)hU1`(%t4zq22px*aE8?s3Go-$jxRrxL2Nv3Lw zG1NHqWcACaDLd8UHOtWpZ_s?E*@t%Ig63~cgtnTto3^U1mQJC2L^o156=RR(I=3!I zcSIM9lHf$!I8FbuzP%yNFvc*!u-LG~@FDcyY52=<&Cu4^30Cw)dw9QTh`9jTRe{2`l^On28H1fj0n2$T1_>)WH^R# zs>g8IkZ(9_tcnqV!t{vgaZ`VcPoFi7HjOu}F>N$`it_uFsk-@f%)-i9>RTSO=+FX` zwMJSWv_5JbZhgf%-@4D5XDzUnvsJP+xAn3;g`R4dZ8kqA*=766b{-?YhtOv=rE-=U z!lnUr!^|s%6I$<;$_>iJ>SY+Sey;vf{T)WGr_}iv-;Bbz<~3OJrS5>PxBdnFT>TdP z9{soaM8hz{>xK`|!+dABU?^vN)W{jrjbn|Kq5sRKS50-TX7rxVSv}Tpv`f!mj+zG9gs$SSZ(n>&tE7cA&L9$}weGr9$}#X0i^YQ#nXE4yAn^N_u_O zW2ynFp{jQ<7yMOqT6IyCr|PU8sD2tHWvTi@^%|6vZ`Fs?6*avyPocc5LcJ~2#A>^s zwRlVWK6=nUFmsUGGekd6zYOD`pD|9GWOxnvaLy2BEN^@QqqOD5RmM%2FPt})H&r%S zF-xKm=mLz+E}E{GLd+G+HOzK%jJY%VgZ>z$J&$=)rg@=xB}Nn+a%YfbvSqsERrIGz zEuUfzx!-aaJ;X&zJL^@>q30+CAC>m@%H$)W|-C}XT(tZjVQ*u*%}ILiz2Qg0B|}Z)1I9*1rO|H8GQDS7Wm<ate50wQxt;kVv(oY`MglYJEJx_8mvUcV#&sDp zl;$eC>JV0=mZEL>Sp5se>m z(Ep_mGn6+(qZXv07EDKPQD_)tnrX_zoU9SXTxr&M)`iwxR=ce=#>RWlvR<==*qJAU zZH7)Qxvkvi+(mAY@>k3^FF>bis)tnrF_hG6 zC_S+{z5##Ba}zLUc?oltk1@Oag?mgnLb(y6Av0zxyD@TYgSPe$^ zndM^b3hi3$HtjCd$qU*Bx(>PtDEBYvUey`&R(+tS?w!u+_BJ zwfC_1wa>FJ!Yt)StR)J0RSomYq1^jiHkZ$ZDmyB>VNG!W#)fYx4=Il-qg5}cbZWDD zxH=6h8#B~%)yvfzF^*cGS)y63`9w1dIrjxtMh{{ke z*HO}{n`)b)P0dludm~5Utxs4NVK%%E>z{en@|Y2~w6(KMz^r4wZL#egtoLrReTKUF zkiCI@gnf+t1^X<_4OZE=VwI3NLRm%h^&y;^GjfBmLih%^3ah=}b7!z_T~S#_*<5K> z_EJ8BIpA!}jrXW>R6nYYq20*CysjE*@r#<782uf^=<9EczUpA*dIDzluVD@98|@iw z1>HkB2i9Sp(Oto6Z&`gseO0{@28%r-W}xU0P>$@H9QDrU#;pr;Q*jE8ahJu7Da?`fV|71QtH#RDCS6_q z!}`X0PCp4NKNYa*(-1vm3-mxkOj9vp&#J{p4{7UFr%|^{|%sm?Dbh<>oFYlrssvnNrTB`p@zgvG!e+_N%tA=HUzQ#eu zRO1w5uJH^;WCpVfJ>!SgqiD&lK+nopiPzc8w)VCJjC+%9qioNiOs>Y*_fy*sG#|%k zidN$1*caH}#eC~L>7E+O_wP}h9doNkl}(j8rA3*BG1UvoX_#H@RPMn{W|bH)U_~^9jnPii+)t|Fh&CXwG*{@+BN9;&*&`r z-uh?oTf<`gd+7al>%aSd?VameR%PDzF9iyd5{ukLip3}v=GyPqy09U|O@)O;$`p>c znW6@hl8PSj9!xZy(8$n8vCyHi!XgJviww<*3Jr^K7Zw(o6;5bWl=o*XYM%SJd-MDO z&%E(X$9S#t{B6Fwv-HFIDg6Qz(6L4~6LA9w`iMQk8RtxL9&na`nzlO@tUijbxyAd; z|D8p>Ls|SWNH`=UgZ6g8j)qIuNV!s}R427aW90v$8V<{!%U{XU)i!mI_KLCF@G;v6 zv(p*s=D5$e*LhjqTJIp6Dh3tj?(lu#Qqb7j=y1J51^j<&!k^)oqv*Z|#hvV}1Zljq zM7mPGOZfxnxlnyv-J_ld_WwX{r;BXkc4os~uyU+vnRlWpZG!<`U=@S1-m%WLN7%Q) ze=F<*Z0|Ad9Jj?iJ0UE?JfOVy#d^D*!bM|3bwu7G_0%a?R25*WI4VDXNRvrhx-eP&XeI6_^vO& zZoLB?LK7yyEB*>9dO{eDzBE?M5O1ZwDp2FLhyzd)2cj`OD<8yXDOD7Fl|1zkwa&cN z$_3qq?eFZ7sDe$-PwxHR3*L`lt=^%@{QMV$+2V8J2-KnNQZKoly>J5+P>#3Oq&&(V z?1$6TfD+-M;S4axnO}1{ldJ-?)W!B5yUFh5?Ds-Vy#wv*XYG7_1U>1ZC`^a7-l;#R zFGJCL8pb%oj&QE^PI{r%-T`O+95gWsRepk$PA%R_CALZ@q#vb7xsN=E&hq5z(97n_ z56P?PvaQUA7Jl20@(^Vf*uNz}rk(09>JV)hd|%VXYB#}EXK8n7_i0Pfg;hB77X58K z-H5XWTRC>TqdOBoqYH8Ul3dNb!@URn=W%y~yVX79e&!~6qj0>Mywvan@K5hh1!wav zQ>(9ZrBo@^N`2%@2sIPU|h}Fx;z;eFV-P=a^0^eNzS7*a5#fQU}_Aec+tBzLM?=B@#~ZFc|Zw&Ois>8YNF=J%kt)q9n#`#xLdd>qFK z;Tu5TURQP5L2F#(BmtV-9EZBC}%;Zcn6jz&dD+!ham-Cc7`Y zZ@7EhA>I{mOLk|t2dHfzb(8Z{03ha%<+VXfD1^Vp$(vk!)RsS3D=8L z#U0Gb7UuUr{P1e?HM7}lGtcrjU1SZhhJk3tTYs`FY;ZO? zH6X~nfy;E-`Pu2sRy5i2neLz5a$Khm-6QU&?E3HB1aBnUzJOa;!lr-1s|Qt6ZbF1_ zJ%!E@`U*2R^N$5vodPEO8T1=twu38r*?rNIhk<(Uwilq7Ewjt)onVYc`+(hQpSFJt z6sbW@5}o%W8{r0ble^Px!0#H%ep~20?0w*U;VEd^IpOcYRuQ2Z{=PWW!VLKIOrZ|l z>K(C3yb*WxVrJZxpa&0^b#AaHPRqZ@(aIDw`Ay1mVAx%#S~qfcuc+^Vqb_3?kHf>7 z8fe1)JUGYbYg`N?8g0B}d;o4pM*myHi9To5{OS-5wne_;89UiY$4hw;52cSgiFt7u z=OdzXq;fjd-WiqRr?2OMjp8DBlTysNPCK@71FO}%T}PS-EAxY*DV9twc>P-V>fl+v1Q_JzeL}~ z*va;%_}Qm|(@l5gI$ND1PBtieuiMHmwY);F!n+~x2yVygsP|O>Fiu1;dqDIrPQ5@E{Mj z^O%=Rua|I|pZdQsFCx(Tdkg!iwPPUaP9cSDz7bt$B7Arozw%;vwER~*=|3xTlzE`9 zVs=%#qN#rZ9ro7J@RgndA$Du={JxL%WBLhraEg&>Y(i5>hh^58O`PQwY{um{=J(j4 zqKH6uI4gRfyMJ&B=i}qgXU_Wy+(TgX$55NA1CE=i`cwT!EkZwEACESBJ1Sxe+|d6! zLdJ0PPz`?gXX7py_aftAaQU0Y6my~Z64-P&uFLIqIn(hcdy4ZHXDu#cfA-EaRHZg> z_1N$&s1BWAnTX&&2KzbQ{jKY@`ZG3mdQ$Ds?Z!i8FeBDhZ-iCX*? zUiB_2e1zd~BhAK5frj}vb2)qGCG(#s`|p?s$O{;tn`Nk)YnW3%TYc=y$%!oHmi%3w zL65GW>MP;b+nf{54XAoEQ1u@3{(#r6(W4LgzppYPz;SC)>-LCGN=vEic6^2R)RkIa z{Sy6dD&u?73rmdk#)qUdmYStz3-x;k=l!KM&>rOq?zJcnFE9i8z`Cwy$JKxh^ze1y zorq8;W$+X%cAAt8N-bt`Z-Y=2|lS%OV--SO+CTCHKF47K@jl-3@8EvE*=FkLV zxCoVSoSKb_^*21@J#cwNn~EE-Q`-wuJguD#boFUqls$T*enkI_xsYg#GGutwB-G$i zuueQUeKoq4@7xc?ulftB>HSs_9^)|k5B62If`hz}X;*4*wTC!~=-4KEdk*Yrjk^O) zWgmL+k8TW#mkIv8)!XEKg8P_=`Ao>#d zF*M{)&^Q(;YuIYfsBgk8`)C^iH2RGg@X4);3Tvg3J7?SxrDoV+^jVJfxG&V zdDQIU`|q`?m^la7^ z-`Gv>o#~9I{4OClUsYf&xFU##zA-+4tnrEUwD&v z96WkP>=tj4Zj)}8@}ya)xcl(fKcxr$2w!ejMyX>|3+?S*JjK`4z3KrF{A+NX19}E( z!9&Ks*(H&v;1kU2&1q&f=x%{o^y{5hfbtewPcXIL$3t(mez7hmB{2zXS7x6jwK0$i zUy7gprBmrX3vzfLHgv=t1+Foe<9B&WQ4rttKJ&V{t-hpIW`=X&XRFyg`%(Pa?1Ty; z0**FNhzGrnCnr~lP8G=w>bQYf;$3johf#f=7hfkwcA895JbK+&X7c^gyV4iZ3Ufbm zB#M;DOmx9Vf=oxW-41&i@B9gzxeVmo;>^RJ-0nW%y+Ymm;<+$oKNaiy?R|vdf{L=7 zBizmGd=sS5ro^fPnV(^*Vv}}Mi_yp68?3`QcplDkH4a<8`Lc7^(Od_2^&sag$hFDFlUM(t)R`tIUdy!W@w59sPP^FC`e zNMaZ1s}FIhzYp?q)4}k!lOB5zZ|*6(8vo~2`%${KE=Wg$P=&}KgEU+i86>UNp)-CA z+wKs$aK__dy|;+hgSBT%g@LP;4oX`PWI?K#)4SwGQh<@)V=g2uP^vtOhS#XX zq2Aw$Ba#o!co-M=pX`#)R9m}_Bw((VuZ`4iLrHm_Oh}TU7^%i&BLfWj3j2IC?&kyM zqbT@8txhKMz2tdzgFU|?U2v&08jpMeK29@^mFiCM?m^f4l={3F-QaI{Qe0L_WZ)m( z$akC)e!`g+MN?c$4c4QXd``D5lto3yAsJ7O1KF$uS`X1q1d$|3Vq zOhr~$hw$e2*tYXJjHe0I{f*Th{91w1evX`?Oq6(^}-lU#Ze^SrUVl)#*H<*;9 zuZKOT>$uyy&@?_Y{$<2k1FYfJNb4qRh%ng_uLavy3x&4D=}#M?B@R7Kpe{?dN?A;%AD%S$`nQVu^jx{KElwzOTJ$y!aMMl z^!LH^W2lPRq!S{fzS4SSBR8>CIih@{bSW27yGzkp)}r1%rtQ&I>E-N^XJCh;j4_4< zqEJB+#kh^1gMm)7Il?%wvp^FI*&u7#ty@Sgy+cm!I1_gaK5QOU+=DBAvWJ2!(ws7| z;u+^ceCQPSxcgO*OTNI}*oMBh&p2eP!S(svJc;MChZL*ND$g>Jl5LfQo}a*Y5^OQb8RJ+^ z3U2f=&ZEUS$|m}jeCS0e3QOHba7HTGNoQPv{Lmcs(H3tnis@lA)863=QSp`VM7HfM zs3`No4}>2gGqsV#R71GQKP_~;p8H=aK1%-U8IsH&ibwFgzK6q|E5(xAi^q|>Q&Px0 zU5f^Eg1MU!D8EZ_uGH%RFQrH!rm=57HC!t^44oTftywteJSL zKJr}5$vlQzx!cZl_Bb=$x$Z;m3b)$5G;I0bQxh2~=Z~v}UQCaqAW3Vb@Iey~~W=bEkA6|4qkSVww z4g0rT!4GH==aEl(9t5xp7x9311f)BR33na(#%i4ZS3vD-|&-k4c}Cx%*k_OM37!IaxOG-DjaYRme}tW9X)jlmxKaICYA8 zGfwV&JfLE>-%&M!oXVH<&X4E;iA?pdzCwRANFG0@4~1Q>HC`ZNbG~^I`rNH#aPK4s z?@z_EV594-43f7mqGi;B*1ra;Pq44&>$2@adz<~b{SG{@kJHZ?;v_hZvmB=P5tF4G zKWshSa~N-NcaW|=f+mwnx8&k`Jsw_%M{>+}d+@7!u*x3<5f$MU^qId3%kUl72%7@m z@ojkfQ4$af(BC(KS1%wdX9S7Y&!uFtj5exn8E(S~d604`x|={kc|w2!>bciOUIlc3 z)I(|vnW#%hzupEan9q#aqP|Mv;{Z3^7c4WJF1lKu#oLEd`aJRuPvcB|;2rmV^7;p< zetu^MSPV{exsXKGeK9O*9f`qL#O-1uw{(J}*)Wvd@9{r6@AI@pBr-PyZx!}w$Fx)Ak^AFJ`L#Di zpFs~T(pQq1+CpZ0A43+#QLc?=Dqm(`z*E|U45f&)A6Kd)Qfz$b6PeQvW(kbk|NN6nw*q-DMRtGXOW6rsjOB0 zNTwu1EoDbOjmQ25$(J9%qKeQmXr-SI3_=ryfDdk3ZZBb?|qbo76d z=n0YZ?#F9_VfvN20vZVG6Z9Jc=Jq%0V2!?B->5&wN&k~n*PCDkpYz`Kh%_jmzQsV@Ma<+{1VhqBoMVoN1JykbJ}*`o>Ho6Eqt{w;i_MW(~my$_h47 znfW5?8F2sC;0T?r0)Ksos(jXK^y0%Q%#0Omo3G$O zOb&QLRLJ3v2;lmk0Hs?^Y+EmuZOoSbtV4|M?zG< zfnUXM{yNARog)qvFQ>j&QsK{$d^-(BnIcU`J1Li{Ngv+<245*xkyzP@{&-yejyc*} z>4)2zq-dbp6>y;nGG?_TX(H*iNu(#|XeGSBsRDhqXpia7qISQQT9&|Y zN=f^a^D3Z{%BsPGspmbzUf$OVgA>v`8%)v>dC&vGWpn>!4@`TRDXa zOw%TV=rd3bb4ZcpvJ3OI0xGhEBxbo*f#O*O8r{r`kXov<0Trp4lR2!lQl0JOOFOh^ zFlsy~rx@+4Q9r3GhJ$unZPW$IXvmDlP03(Zm!Ocgfpj`e#d3I=Fd0oZi(GFG8OdDK z-F!Giv$Ypi+Yu!C1;+sYrIR0C4(h4EA*w}1iFF6L3Y;{Jmul&3{yew9Ex~K`PjNGj zcRf0A3)8$E4XVqH_F_osC4hMiF9p3V69zjMC!zq|v6L)hHLo%1(EIm#E#Reg6pAk1 z1jL|FB!rXDs~nh0I;z!Nwm|{ja|w=2MYuX#6Rsl%*NC><8g2(glY)d#_0(sy5GxE4 zl7wWz0DYzjnaq>9e+}LGBli6;Ggjr+;*K;kM>@E<#9!~Il3S_?Zi(NJ9Tke>v2L3fDM0jlvKL0gifrb z|2ow?dZ&oKsiadHQQ;GLzbEhlEKASP^YsFHWI3vN6)H1UfH;J4@%*vgy4-xNR~0x15S9qX#QsB2_^r)^L|~sKNE9 zN3Fai>fk+T$cyy+j*Ru<=*oEdGLg7$>&9d6y|0mIn+c!p*l0zEIH)D^65CgN-Kq`K&c6dF=Ubj zYIQOhf^67*9yMD))fQ2=C2SHuvmQ-drI26EMSUv_>Zw-RK@Is8)JFYuN*Qu(aCdFN zy~UCNFC*CyX(r;SrLY6C&2lrD372P;!g0HB0i$_)Imn3zc_!ipCX*6V;B%==w+ttf z1VlFPB<4D~%(_BeGA-vG%5aYTyQtzt;AV8Fz1)T0E5*T<^-t12MgIf^PN@LbtB6x- zpnKcAPI8uU0q;u-Nlx2i$1%`iSrEEFn=3UzU20uw5o^Ql2kJ`CRJ7W@ugj^-WHo_vTF9!T;pU~ot};=Eve9VfD!KGVzES{ZF2b2AAs1T8 z%bjv|Y$b`cYF>iY@CL6QtlI>iZV7f<8#8SXdo2lN$AA;0;n(|ft%I!@ay4!)`DLL zc}_kxP#n~NfB!Y?y?QpE-;J%LAv@5PLT)5I>CerR^i|K6i~n6O)p;pwuWWW#9!_Wj zw;w8v3j7v>y_CUb$_hHg-%feFt}F)kmQfAyVsg+UIdn!Db4OSt$fl^7nP&*FeW5FUt(Pi>b);=x>{d}kFXE0Qgd%bqBKwejHO#eE*x6=^7Q7I&D)2QQg5LM3x$VH9F zr>YC7=t`=&#*7Z8|NlJ^zoxSTl$DQbQ%l7rQJoH`)JF=XoKsbBLWcw7)q@%eoft5W zk8kAune}RPi#dfyzi=Tl%L_ zrPOi`Nqm1PShWUR+NDPG&Glf&XkOM7>vej4umy!+%XJ$uRwEjP-(`pKV}9Rh(RNJG zZG(8v`oB9ah5eR>Lzm8$%Y?mTv+L%9zT9(jsAYk#namdP=ZC+= z8_^0|QGgTK+v((9vcb#w?CDCjawC(YiyT5CJ2yS})fMd3z3h)pl9tgR-^uKbY<6q~ zInlZR=~4+zd=GsQ!>&zWcTA=h{4HDgE2eGWJ3E9}kZd9^!h9^doJ~^Ai?BBOsAsok zknYc;lS)8nRp7H$|8-_iLCI{GOgS45*RP^V=1e2K)lD5K%#~DjO%DB4%0AhF8_-S$ zAdb#Tp|^6u!R2Zd{HKY{(XGbAanj&5Spk!&hPyPwSVDRaS8;eJFqb|n(<`YvpR;t4 z=12@sS0?+Uh}`t%0B!9x+L<)IbLj7Oh5pMXt-qXJtK(HsW58xYV69~SZ5vr^i844$ z6+PBK^>(lqLeyz8n;?ZMoQt|$z|B=Lm1;rgJxr$?bdbosrGo+rskCb5{SMH2n{zU# ztpqe)B|uw6pe&!^w1B7D13cwV{4^Znd}^+kOnn*n$iKrTxJ)-ZCJE&?gB+Rv3aJz< zU->I@sfW8XkwQoPZH)^4Wlk~lbt<=+0dDf|vjjxY3^Vclt!QpjAzzlkZH78}{&^p3 i3H`R0I literal 464896 zcmeFadtg-6)i-=58Nvic&Y%Hy7pRYuf6u#YwvUSkC#dDk|de&3kD@=9q#m(!><>A(K%H^)x!yX83D@16Z%C6(R1B}I9XRAA~OUHsgJ@=)Ai=}J?7(*V0v)JKxu zG4bHfM&j9q#~HX2R_HlSk`i#EztCMO>MM|lusA8FjwhtFl!4#FHwu(Ky(B^Epog3U zsj8VCy8rc+G98lC@l_vbYWFC-*M8kcO2$1M@j21)fYS!}di%THIcu>Kk?wnpc2Qf2 z_)&kUXzlgY3f4xX1J!3(5I#G-3 zKZrNpR3<Fg&f zYw%!BZ^%vQdzXJLo|mx{JKHu#s-zqxMf#IN`$)6fI`&DD=?i)7es3Ud_Q5BTB*_~X z_;8>+&A%yl_?tfR+U;52v>>-Myw)v={JAL!%9eAioj`v2Zk{7H zs)bs)0d1DF+!RaN$HhfxLg(9glHZRopg|p+aZ+>J32*FN#r5(qH=ZP^Vj0`T&ahm& znT@i0|7l)Vg1}AbxYQnz<(K!Q)OPigC%mnWvP*62Wd!GC?OKnU#10JUQVWpnyN2;W zZG5nLZJ?}sHS(}rB#bTpR^+dQpkPF&@kujfhn$IpST zTWxh)si9@}|Kz7zHc+-o^OJ}ier+E_n&tlQMEwbVqC_b1dAhmn%jq^b<=f3*sEv63 zb~8BYTMA5HM~JRFN8BMloezX_{(9@aOV{qI5bf&Z(V ze!E;=yZSn!Xqs1>c((2w@ecbtxF^CXb5qWOQ&P^cEsNezPCvLXOyMkgofqOC z`0y9yXOAg4mMrt@ft97LOe5 zY-fb{0^T-nz`TLTZkMw`>kl2$W~EDJ`x{Dv+Ec+rhXOgOty>v;B4vPokbfPK6&xeD z#UZsRr)74aVG=LlAM2?0w2s?3;bt3X=7cLQF$fnJ$G${!<)s&ax&0ZO!=Eu4Sil=- zWP2;Z=%dYPzLH{t&-G7#X;7Xj<1 z4^EB^4p&gDI_;L=!ldA!*Lfjk>HMO@i2(!gt8&7ETq(7v(KVDEVPEREjEa-wX0u&g zVmB+D=0$d;Gu}B&sj=|7&9QYWTP++x>M(V_c}j2^bqeK4RGh3lX-TY(SATMLN^Fjj z*jFmowJNZdP_pi}y?5WZA^wv?HwjFskR=cd7S$H4vglX5KvXeDA(7w*l*#IJdny`{ zg!C!uJr;GoHO-&3f8mwtJ!X@CO3`F}PAXB%YWfE&GjB^Uv-wumA?W%AJ3XaH-*XGa zTLONAFvjf%${!Xkzh9{Qo(fO6HB`8e56^(9*!ygcj=S_z4ebLAdes0kwN7rP_U77c z>Qs9&n{FS)Kn1z^i-rPw{Cd&^)X{I`49Ss9{|p{eTFuNG7_QxE^6Zv#cW4C`OW=~I zx41x`_25DYMUBeZB`+O|7q%yDXFSQ;_dN%lgXHGE+0*6a-s~IX<*k-B$nFsViw{=< zO?}h8l5V;7y~-wpwP9EU%C z)Em|kdeFrVzlOM7#Q7#EtZ{uxQv5FxBp?@QIODifeG&N!8$$0dN$S%L^+&mWOx|Gk6iD$c}f!m-6@-+ElPu zF1m=D6O<<%!jld}umv1uZJ2AffueIOKzuu@nr^>F|9Fo{s@qE3%Dc)drob1K`W}pZ zS95O1*q4pCiTIV{N6(Y-yC1*D@jH#53G})WKYEsNzZ}1@_=S2I^@nvYVuGOWzPOK6 zAWHo=n1I5uFpfCMOeo73u2qy-hzilF) z3HsMRa)~u9wM*=!X4hbKu6?@P+$ZT~hxA*Lyq;Fq*gzWVU>`%Alu~T%8MEx6v4wrb zwslK-NpR3Bkg>!B-}aTH-%|;dQmQ1)k&1$YRvS?V5k<|aLbKRh`&^)#fXuZ|uWUuR zbEFdeAe2IIlZIZ#!%EPQ8Iz0j^q-p$ZUeB%fZ}7c1QE$34L~9bJb8_^O7D@;i z0j7blOwNv%J<|wir#+J-I|O8^XS?ivj=~D;`BV&Lt*k|v`j8%SJCPFYPXQnCoSQ_> zYz}~-_szBEL<;!;DY}F3Gb&^%3YjYkIZf|?93zUXe2bS&KmL85^wt#s!haVg;ndLh z>KuZY{X-qAmYGX}m!MGP2|k!S!3UG4&UPC;Fl0xMn=G*D*zZv4Fe>#nl-fJhp6+K* zZH^^|WOESCl3AshIm|wX<)h_!l8?{8{G}+5z{`$vjj!zjmirT4y2C2FkDziT1TPD8 zgP48c---mDZ)MM$1SM%%>>IyEvU#*Uru-lET)u?h3zq+hQT{98K7`X_; zTas3KmaXQ3kWIDQHZ4XG%9(gIOPg#)fEh_hK27BmU35KqW|Dsp6?1;|>S+6>D+q$i zsnTnZNS$ofDm%6NEXwy(kQqJDfSzsT{%yyKT@|u~TgUC)sKN*G2k*rToildH1gHXEXr0b_@k{wdN z76mD%%?qrr#D%LL4xEfuHlJllcGaYK&AfanTZrzISInV88z3n31u+H7?lu(3d?>?; zE@$)XcFNso0|>Gao#WZ=lrf!)?lXwW_k4R^aPK`QKP2zE^_+Bsvk`2unN2kNCR$cf z&q0@gcI>pzHB{0i_UV%IVg!`N0#Ub>h5?~TTJ6B>5~XWy42FgR&e4i@IkVc8RuZIy z|Ixsch2tif<7MY1K)8Vg_BMpGr{i2O2()*{_hm>$_>LgEXG9;M0*Qi4h=Q``1r&#F zsx9Z^&iaNwIAorcI7Hy@2fU_NU~@kFyOp{0#aPJ5VF6L=S%qI zTPtMeMYR|iaPPB>-w%2#s>&vW1BFHU3xoLB z;b}DzgqkjUy1owv>(FnK&5~4O-L#%cNTcS$Zgo-{UlDaYLs3Z#elVj zpCC$hA3{LyTs0PH_$WFpEROwuqbkAgXawD<0d!BLJ207w>XN>aYmfqRGoO0b_?!y) z@8dIB2Aga~SP{BIHha{ zByld%*CLQTI)Y6eg@7f99?c$2XOlNZ7UpcQHcW4@$!neV0$6$)pxjQk7b55q z2!d4E-4~NtQqfQWmj{9k=fr_%K=d7I~>1@1B#+jF4s(r7lf^OfU#-F;peC z4a!$P|8n!3P`);t}!W{ncKa*aE1#TrlGvNiRA!E2TVE?(0R7_f!~ z`mOn|K*Aa|5Vz(T5IB|aw?JN7V1INB#5&|kA*?9&0@*VG-H&u2{ez(<;*?Z>Izpgt zDSrSo8{?nFIC2xcw$ke`yuNi6p8O-xCJNq7!4oL>N(xRw@W$gmH+rVO4_>K_@zoPS zfOIueEj0t>ng5C!1Zj{ND^NWrH5uzPMy(XMZ38`JYg@J2(O?VF_q?YnFW1dgufJe@ve@72L=Y!t6jCc=)an|WJ z^3W0jbhgE0ETDd!%Ep_0<1Hg-1OgDGi#XG`q2O|EUGbR%35qYN(3zx+lbqiK7o82m zv?p*3q7Y^l8l<|}S(I$M2+t2E7uK33_8bwBeO`nOs6E?>ZVPD>8|nWH!Rx7$ZLr{? z%L7S*L`dL23g(I++P{Zh!|3#H#VeOTfkZtQ6D|m!{p@pnyNycGuf(iE?mrK@?9CVb zd%tw#9sM8V&-@Sa{<|>$zkCr|X9NE^_~qi4k6!_PMfgp{uN1%O_|3qt9KTBZ=HjQE zNJVd^S)?8E-_rRI`cJOiN?nLq?P`9KT9nKV)6lSTP>uxQWTQa@?5w zJwac6Ii7N^!aTaLRHtf6-=gIlin9`uY=i5@8+2Q(e%6sOo`%jAe2?O?5=Tf-ALpTDoIrd=4{ZX1>6E5!*Glf2(Cx zs>7OgC~aR>=Yk&z;Hhw}kt$>dO7zduNn4uM954yjlqQhQcc5u(G|vwhau) z%?LT|{lrq+6(_roIv~B8$Fll#WR}-DY(9=?L6uyfocxKfYNXi4^O&~P{}chZ-A}hE!Dhm${$_?S zka5t=E{nQ?;0PqC^_=|tjTFn}4#Ikj=`2dY+mvgJ(v{L>-Rxn%LlpC^qsCRp^Y&zH z$F+6c=%3LXXkHuZtdJjQ$FEIkO|OvWVioME;`B%#TgEKomDbU9UA4&X`m%Qj3c?T& zR>SiN(#7|@rDvg)9^ipNKIDtAmvhhoNb+uF({wSXCiF+f#hZ5@CJbWOdsTajJ7~!;f zNbk#x_ZN-#=Z*K5jrWzt`)cETmGS;M-sKT}Rz3i;YW*t+WOZb9lGEy1aobkcjw>a2 z1qrC@Rj|5U6oM|*P`3w9zPb-^HS;GdT=PF)-ACc4!*~+0pN7M@c*fhdx)ZpXP;NfT zJ&9+OduK!4DLnb=NH){owz{wBCaS35$@-B^gBZ+6sEg8*haIZ_j-B*``EB@mt*Ld1iM=QCK-ukpz%Gl0)`reo0 zA>*wbK}l|WA_rC1z*gH^->DiO(r}@BMj4Vz5$+Q;<(R2_UycP=-1vaUw(#OpP&~?> z3R^VB5O=Hdp>6!dBt$()D}CRiW^T2%Oav}07_od*js(NdZLHAJ5j4q-AC3cJqcalf z_1_p7-+71dO-Jjqjk%y<_UJoDincV=y%n;c6jlEAt)c8045m9l$V=T5K^;rFe5# zzKsp=hgW(TOqr__9WQGyVDTTwxUe5zG# zp%K7W|2nSp=c|7u{It$^f&i;uiD$YmGw!v~GBK=44+|bl}v1iAIWPU49 z5d|5{;fRW1UFI{GS507(tiDM$!rrz?NhAWO@fBF|?(3O#ePiKwAu&RU+W&*kVeF>2oyb%r(>1no^%*#xic7U*a@ZOtYn*6B_v$kqA%m(_y~nCPmE;LJE98dP z(Zp!Pu)->6#ByjQ&Y8?@1JId_exX9k+=3M8e4fls>#I#PD;CQ+LSKG0jhm1+~E5RxO?Bzl$!y{`6uv*Qgv~vCn&p@66-bm9IT`|O4XcXmJp39U?ECFI|184H(Zf!^dW-T zmXhEtMS2!AU|=5tkFB8bhP7Du)rzZYT-_LFwQH8^UO$=rAjU~*%O1yR;P5V7eD#zC z|3O&FMt{C~%1VEMp-QliYw)^?nR6G&&3Bn^idVjvmFSh-q>E@gtJcD`Zq8;}6UhY8 z?5$yj6@`Q4hUPkKi1e2FL=Ck@%g8cwnv6AjB zB(+NPN=!TW43YX2W>i$C+SGt6tEr;-VS>u1wee;xb5V}OeJX;7)d5&E^52bK1XfXV z&URbl={AoDvC%WYr@&nVYdztf208!$jD|68;5s~|8UFBvFnrCeJ!5zP!h69mbvAer z1)Hg7osZp($-g8?majSTeVi^0kW+$7R=*n(Ydyit$_gTkp%zXT?HA*nz(RWbL||nJ zGOLamJ}%pYka8o4gp^ujxl=jXsTK6IXvO9CYE49=dnX5rtR%1+Nt0CTNq_WgX9>=E z>R2sF4h&IzaanuhyhBm*nXEnRkh2gDEZY4hck5#v*@vt5XP^W6amG-{;bTI@$e4&-? z^aRD=;l#pBe6;5jypCF-b%ullO&F;E3gN}LMoFU)w?(Lp)SDqk_|zQtM(S0pk*1bH zIyBUm;km+?*T?C(usA`1AvViS#I(GTn3D~Fi>3P+U_coQQKS&wIJ1%{Z$$#|owaXZ z5HZXSId88#Ov>89_R8*;5hphf(@IS4gO8o4J#)DFquMhkU3R2$6J%NmWpF=%@?Fr%=YEj=07-V0aO?#wpBo9bf!LpB=|m`3d%zgPx@01#37Yy zXn5Re*a|C)vpuI!O|N#g?46RD_Vau8*B?MP7G0rzom_~55<956?zHa-dVQ`V*yrh~uLWNop{ zb5M3u-&6C=Y%o+rwXifR-{KlJIoM1lhbj7l-Md?XI_K1KNqn<_tBn(DIJ)&-CG8kN#E=XGWHPQqJt(=Jw zu`pe&C%^;)`k%z&IIxsf0|L@Apm(0ttYFpcvdG{k56gj2u>}i}gk(0Y#GQ&>iQ`La zC^)dc_fGylH-$RqH8C>fmk4LRIoDL)_Y%$GAO4zav%Qy7e|@N&*iJqV;apf;Lm14l z(Cs{7(R~6kC#DR~-ZPvB2@4vB%`#9(1jBB+D|k z`yBZrKmnhlAOr0sV=q}cs2c``qQ7=mOT)crya}s3S|iQOLqdu;ddBl1LDn-z6aVYH zu_3*bghM?Qqt>_L%EUqpH)KciIO#wn=Y+(oLN$;cYNpa^fsp08-qhPHp$>3wa55=+ zkZ99C3`;a_3{sm&0Fp!_6DMmI6J^G&`58ebRDu-*i1)DB)aKc(HIYzfT+TACwrk58U{d9nPgE*%oTEtcTB$7WuF{Op8~-PIyuty%rDHK8n>iT zlWDkq0*gm*Fnc*HCxs*n{DKXR?O(F{G_b0b+5BX1^#onXe%e9@H7JA%nzoKdP;9>F ziRzkgzf6tk-@oSllI(g|EpjBGeDGK@8U#8M!blIJ_TkQz^N>Eqxc#1RC9G|K=Xx&Q zdCv=EMvJFhLXYr`u%jroEO|^>lAx^Wu&IS+X5Gh@>@!bfWyxYFQyYoxXn7ESS#8c% zr4=f5RUnV8p!uw^qSQDQiNv}R0YWwq{VTXB5Yc{%j1d@?sqj*;p%Bo|`Jh@#w7Xwh z+(A=7g8#y_TXO0=+BpU7R&}S{bMmlSf*Es1mLNuP-f$238wJv^{KXFFPjx|9#nIB? z0Jay-v0{-+q&ozmMB}e8XIznX$T`}3%B*ZL2QH2@r(&71#deSLGGz=6hYP~Pp}{~$ z5zl*(L~1;B_t^1IElgI+Y^o!P`qwOCt?YLRXaWVgd0BeJG5{+2xzKx_35yHRFM0qh z!vu+BMe=W8+uC9gQbEjo#1yb&*LcVZuFY_TC2EM|x_7kfARUQI3=10osUc(r=^X=v z;OG|-9Kf%bBr3j6*qjKLFjAAF&%ZJ?!u&$%GRf}0jU-B?D_wD5fE)|;)GF6FA_Q$2 zf;Bway@q;km1r8eqR`-!n$kqDp2H?D{VNdTzY{G$fpuO`aai3EfHh28oD{eM{CB=7 zfuQhN?r{E0ope?mMwm~C*sYvL$Y!;`rzQm2LS{KsE4zPlZD@dvRe+ZwoQ4JPR3;Yw z_|U-e%?7}nND*l3e+y`}VodmnJFJ*@3hO0!A)30zif2d#Wn2P;%t&EfcLixULf+Ga zU!U<~Ac5PR#In5F#24hd=9t6`x9Aw3;b!-hJ+-LNa}X{EsQ3IuTGe6Mu;=avZ1Ue) zA8xymwS9M#wQr#Q@6{8&JDTP-dDl@sc!Pa+U`T6X8;q4)g{rZsWl0e}z)A}IgSZLs z=@0OvK6Tdr4&4W^J zP1VuYs4K*BjnE!4S_4C&h;yVkyULqZP3b~HBvL5C$AAsp$7icyYYzX zZF_`Lhinj0{frx6y7x1(zDzL|CDavUk%pnRgd2)A$9!0xLu4ifdGlYWzLQrU))s_5 z-9$_hT{VGMWg2g=i(#iG=!>T4>xxD7Tu$hZr@~*DH=f)&xF&~LiWnZS2&nRj5yo{a z7tC+q+^#=YVJsB>wILpx2eC>x%?_znGwM7=Gs=RSe|4xo#jX>kBRpme07@%yTWFoo z$1C{yDn96nb;5Ku7AEWmV%F^XB8&dlX=0`D<7?0}uu_<=j`dMlv{rZ%W}SSk@J}cz zCTk*PU0`JGLiffZoN8gVKqiKHs*&vFF*yi8eY}z5QC^v-#~4X@&8Rbr)(J=J&tW4= z;5xN138S#*ARFMkB&J|RiI$((wV=?vwMwS&_KvC}+$0@W&O!s3EyBFa! z=5sGYOjPqNnDeoG%=}8RoCrHDDYXsxNpP4U#;9QVSi@?39FGn8ml{ionS4oc5$N4* zNihqJ#Ir5ch)0JdVeGs~2<%KQ18qos?HU+yic5X<Zy{0|$@` zozSwylHdXTh0kDG?Y|zqLCV;zKLIBd46b`%Ie4>z`bEa4(fwjQ&EPkXdautc1PGW8 z6<`A!%fw6syibLQuD$_sPpTwVF49ZJ^8PMH1>udx#0bHy!6Jj6QG@BeBi(-|EGOIs zK4o%=zUC8AyI2>8JfSu54Y{BuYiUOfYy~;zuoe4o-B$k*uJDXIgeyGb{(+m;NU7#n z^*vWnL2x^ycf-IpH#|(Er9CYJv-)zPKs}Yt>MQv(5uep3^JiibR-b~a##4qEgklAQ zv_>%qjmXgqLSrhH!@AoMX>eurQ+c7(nR>D*3V|5nms??A!rWAC8#lIzdp*4eo2ij{EWjl;U{PKX@>CxJGa(&7zGQX@Dx4e4Nf7gk!lpCNIkjQghw90?MbZ6qQCyANvdsm z6FK32dYG%QEAc|4dPUqDNkjD9l7%!Dh;$KX{d3KyLIm+;m%N0i|*#4vqls0JO5oA_#wt zo0^I=u7?)s_a5R51xFlVR^p~Vjn^d1kb(XxH<{_XmP0;u3H4gfy$soSDww8jM@Lq< ziAyX$sVm&5m~1=uT2N~~YGny9N+J;TlA}kFe&A!HUX8D62x|!A6Ai-=nhzEF&?{i7 zZ&^mz;MQ+X5DTgm>R`=$TVQ~)IWEg9=k3VyvK`LJa`R)WsRd4uwPWKGx;c`=Q?s_oCCf9%uW|Wt|IpYUh+q zMmwF?s>S(Qjg55$7Z&LSaQ7ys@MgzUt&w^#JKf+&(chS7qQ2w6&c>R;BBS>e+rY?$ z$+$Rd`Y(_|w82l~(sx_P>k^AE-;tu9iUTOV-GbwB{u?irHrf3W?pj&CR{sHw5se?= z>T}qtsP{Q&(7`PgR~r5(L79y?l7032@JyY|=OFO|zY1Nu6MWM|+0~ZAl+uw8cb?)= zMHqLAg3X^INPq0tV)TGzsW=Hbg~tWyrZti%(jNiT@H~Q1WmN3+Sg9#6nnRKK-69QA zVq)C@IAS9OIXNN!dKQFSflILYd32P}5i-m1EEc~T=}|AGa{#z+h_Mhz*0Gi66>J#G zxuDZO0Bu6@&6A4thiLV5aaf)`gRxc&{2ZzO_taChegu{}FvN8qQT=I6wXW3(ne>4j>i_=a_QJ?woD_?F$2x)XPBde?qU174!8p>e0Bw zdz+((x>LUYBFNPBselwJYui!u2WeVW`An=uJ7=XPygxGR4LUq2E%CT(f0ehHm@B zO(e%HtV@4%L9E6Q)@nWmto|tr5s>dz7MVChgSBn&OZx_|2Aj!W2pd`JOX+K z@zV(c#P=^I5Cw2lI_$sEn)g3$ z8ZD$q{MLjdB=eb|lVLF{M|tBozxU}KutD_awqgaSjVY{W_jcI1Xg-1+QKzbp^k zhg&k$;$(>YZI4PTtW~g8-G2&;YC$%*m$9aYQ3ivz7-zx=kUirtck~z1 z(tCn2<>BioVz#NjKmR;z8RLpLCBYwrQW4n`w!U-+_U?@Uw3Vr!!Nbe8pE{G-Z-?7F zu%KR{Ee^8%>QKdN(Z60s%U7F@g8cvPEO0g`Z&GG<>D$;4Rjup~ZH zf^n{)#PgV6AsNkbh&k!*FTqG?D97CT&k}mliN}6m=%#S^MJ~32SYBwuqFO@<9w#GR zNC|!gwe_R~lk%NN$~URO-IU-D_ZSuZfD%0T5-7pK`=@Cc8n3_cM=`s75BdSCKa3#O z^eL{ev>K|KMIX3|lM9$|*l@wx8Fvi=a)FLQF^)h5qT-rSKJcflpprB`1JbzS3wLsd z?d2ZSu2+zd>s;h3-(E95q;u&*#{&wUSL1Vdl`AePSD?{pg>yH$E;5rc=19@dlXn*B zpFGb=NV4JQXcUyL?#m6Na`n&+LVEKL32xfDZ_H%+VkY9hkr++Tw@bhI72=DjoMBqg zA|VZ5?B%h(5$Q=j23X#;sE=9hQI2Y!EhHJiy$acW!Vt+5LNqA>*s-hs znS>gspFoa8=33#os+sEyfgl#NxI`3wIhbYj&48?~Kg`=r4NUC?7a=PN;nV_?Gds_Z zFocW9b`VaYav*CZEQuj{7Z z-Zzj-8uSx_gEq6mc87k#&5?Y#U`-_6;Mm-Xh&lMDe+CB@9L0v^03x;QE=2_KJ}ub9 z4l(*1=m_L)mcA|)$ zt{;QLEYW-yI=E2B&$IX$>iI|4QV~C7>}#fB6~k}nxHB;z`*DH23JR2#Svh_rRFIV7+$v%dXWENglQc6 z(DC#e;4Z51UKu{C=x^_El&G-e*5o4%mj4X*3?fA)pC;xH2CnhP90i*B*Q};UGcRS1 zsj~a$6u1w^es~YZX(_b<3m%m%C!9-=MsA*T7VDCaYWJKFqdUlJ!tV}?*4RbOL|36D z3+gJwHw}E7C~^fG#>dHPCw*VROoGew#RbBZ=QkMel@>ya{&FI5TB>prm zGlR|0d?kH)CX(N1z20a!ha0Msq;FHYal z$LJc=K4c>#!AFXU;4EP{+;MB3|57vuswwn$(g1Aw?X&orhtS;pr%^y?j`-CcNKx+M z{cq0^TStpt@)t1G1?GtBam*2Q5%TDYnj`+MJPNeH4Wyfs{{DYdE5ZH-VOw=QMcOW> z{-6H{Rm71ldR=%9{j0B_*#8eMYH@*Dc!zK#W4r?ju4~|2sh@Z-)Y}5nR6fS3jgNsA z!d*~wRnbZPm+08*<<%cYI}I)s-IS&%=kqG*QW=gq!pCAQY`L z>*u!7(zJkG42!U{4cQZt6||pq3yn3-VszxmGeTXzM8EoFuEHB#NcOBJOLH48a(^s! zx4n&d{YX?$X@Pr!p1GZRCl2$CI*Q6IYF@!<#GOB{Y=>E?#%6Z*hoq#T>7S70Q}m6$ z=QSC^>pHx}bg;6pAQP(_z&t6Mv6zSIv0tx-apFJeM;GiwvfG5|Ua#rHL6}%j zmcjm<7K!_XCbRzQ08W&A`4BF88+i{T?jZ75{r>}eZDw z{_=Iu3i`tg1bcPq3-QKax1nQaXl1pHKamngc!{1r(VQjLQASVT_=F8Qo$OK;@GAJ% zPYO;19x>}<+|j?XlNx>&4Hpt5@FPfmqcIk}^9HlD{?ila6An~e`OYq+-3QbQ*;J%A zv~dE+*o>IOHnW}b>KzuZKiM0=iAR@`E>k?Pf|U(wfIjocbadiH;ZB^2^7O1NoTDP0 zZaJKi^(|-u?{skF5@%}f+G>Pw$CM4UD^34?bA%g7B82viKrzSuazy%{!&8WkVo%k8 zC^{Bb@pDHKU47L7`pPP9y^JrzNhF+(>H14;prfOT2x$YYHjw6=7)!_zM99TNNZK8Z!CvD)dt#C zr}C4)0Q=Vsv?tp3(=E}dbET;C8QVo-efIT)M;Kbr7lhxUH$)Qj1pn+vbTs?|^bk)w8R0YO0{AqV%Jow* zIo1#TB1D8`7s-+|4r?#v_|fw{xZjVT6ThhA-6H3^;UFL|EuHPAlREx~6S(^7J8^|v zhSB+JJQd+=H?6({Pod-68u`ob!7P|ZKaXu@o>rd&`a1GDMo_5FF^X=o`zDkwuT8F? zy?@u@IU~qFeBg8BKx`seE+z9h^2Lp$vV^p*k$#Py!e6ja3v+0va)P|J4ByyEd2AA< znW9+dLrQC0g>oj*IZ|G`M>(D9yh=HJrOU1sX6r3l&)0gH%eEcvqLS;iB2vo zc&vDIiT>Ij`6&Tfp*>@_D31$;UmuBw>M}}o*y$)E(sH0^HseVSy3$`nB=I<^a=O&JF%ewY6bh>Rc`{T&Am;e$2Q)I>B| z_N+!_D8(}t331ecT2_iZRAWW^2rL5uSnxkkr2p*?90h9es4>MOT!VlLkr2A1q(6)G ze#Ar}0uWo2#v`BvqUP>4+up3?-8-f_E!9eCePW2o6cM5H5O#Zt8-I(I( zu4^^;CgZ?}sIv6imJwdgD?}|UtzhQK6Ik&K{{SQVaLOK?_uS9A=fxhqN$1v>DtzY2 zS4xz*LV4<(B*VwkQ|c(%Q#hp5?H}3aNh41)}^SeLwd=!hYJ)9>5CRPyS2$ z&`|m5?~og*l+IK-dGP7C>D7^`6->4%hdWH0!J}+5j1=QLaKQ=cso1XTI;9q7!k@?U zGfE6MbZid-!v2Te%B)bFtCdb$^^cLCcpD`d#f1A~59L|1ysiPFEJD^1jP@WMFIzb* zM{_r14h~dmC*({Aa`xrBI#4TRT8&J|q8yP~VJ1Kf*T>o+PwqgD3DYI5$OQO-xKan7jTV7~GMgmgUmk37juh#trS*+J%#j#BrEYa6lDJxCSl9XwdyUOcF0y zhh$F$jVY1SFzcbmYuQ6R2LbImijwm?jpHz%F2Jjw(yPVR2uO4s<`8&Nf<{1ZiJtd6 zE@|Yog|i@%z}9keaXBoXs{lT5`w%-Mul89Q8vl%U+Mr)ZoQ4et!=lc>#F7DK>Y;7l z2gu%ZMeXT0*X-Ie@ji|h>|^Ywz2;tE}q@4q3(mOOHmq?=@_BZOeY(MtJ+_OjMPEko=$C#j0Z*f z%PYlDP%~;w%?KB?ayynTc`787bkBkR4TMIGm{Be)?KV?&*p!-FvdJtQeO?R>EihKt ziXnAN8AN;84AUW2Q?3sJh7|6w4dP%O93&}!i-xI@ju~E^zOf}w1WW$u&wuVGFa7>f zpxC^L0M}A-$jd!|klr_}+=Pj1H$Qi>4W%Ghv`#?VjK2K9qI0Mw;d2#3I1F7PCTSxvRh)R|_gpNaUwO!?|>6|Jfxi>i)ol~U0_m=4Lh4#Q7)E2I!tRMzUJA>V$0e)UU zQna|qq4NUnf|aNHnce)PfWT41&!T7h4m1I^7~qUz_0OXY*0hqYg_gh~wPuD|R*p(< zFe;V9^6Cfb18ash0bcIo#S+!dW1B#~NTjcx>ZL!?>BUp1(`zfJ`|$H?ao(@+)>j)T zmY-kyR`mI`@5|c$LA{(`oAf6$x@jpZp03pxWD(Wv<0k@9fn3Y0Ka3~(!yYvT0&;P{ zI4MYHw&egyq6jgEK5jTMF3yA{{3)GqFWSx@aFm$|?N**h!q&*|z}D(S>-y`itD3oC zX7z$uH(72ficiFGGGC(*5i_fLLv{81>QIbl3+1UNNu-J$|D2Bm!0W;Q6AZrk!?@C) z0c03DV&=jT#A0YhAx^r>rEMs>nuh5F-hsX zSa!dHij>Yivd0{5%0sT3@KJ>vv(hm;u}$_|f(X&obydftn)i@s2Id?62+7PbT3?3= zCC&kLWa{q~#UliBVr=nSo^+u;{WxNK+JyE1!Y{PD&tc<*-zo~{+xuFU6C78N$1>U7 z!yE5Ngu*XH(%UXKoJqh;s>O#OteZf6KTCsmVK(37cMSN6$=RQBHX43LuLM7p$4dLKS@?|QsMVm|01%uab!bd_^Bsk=N#6Vl%IezNga8CpXTE4)gm#gzfd-PTy6LJySqo zgMK@)saI@pQMr6p#fBPMI3~UaA@$BtA^Hut$Jn2_7t>sZwhG)jMt5sQ)Hcmmk+LU3 zzJTxx5mFrIIv=S}w%Dd=BNCFdjv)kzcD+xEAYp&doi=LPB2;Wd1~z~baX3ng$vc4Z zZXYW0dZ`Oo)UkA^D<|Ny$mz#cOVQPz$MjO^Omtl~C$Vxb%*Me5R(qBUi7Xjk`57@L`Oyfgy@O6&3r3Nf36RBB2p= zlU5-KB~GsiOQ?TbV>ZNU(!-&gXh4+MfR{Mlg*}n7(VxKzE0J07R8uDDABRtkmEBh&T&)?cVn&n6 z7fESfYaQo@imviYuvwr$hI#z7K%!lA$T%Ag1jag@57=pL7b5?mO zmYE-M&7J9!e)5wkvoExN_1bV9eE%x1;}V4Ny{nirKxWubCOt)i8++j7??7k)Xrz!v zgt0B)9TxAKXkG_&Z<}bjw)ZlYgv;Q2U^yU_R0g&pN)WyP#Ex?w9V1dZ|VAvAQGdWyjHsXx`i~jEhLXxzU@;ZVwL`|G!0Rdi^eE@?`OlY zz%UD?Hjo$Aa%H)`G#pS;!(0%0fl_4e!i{%(5o|>7tgMmYl>o z%RA**XZbOZ){QR=$vF?oivI)HOfaKA|6G>fLb8%XeM7qYJpHMi@sfWE4n@FF#Rn-h zKk8J3+d$~vh0SRway^Y7M-dJo52XR)2fSHv8ceA;6=8H_=qTkwNA1<;4IL{{XHP>% zpTF`_F?6U`VDLDGQ+8FMYaPJAa_eT{V093=*`CS~@8Sd%|888?6Iu0*SlsiEfZaj7uEwiDVUWu+84+x{xBVR3g@l6tS^#yYw@To~~Y=gO_rd8#=0z##2n$l`weG%5Cv}5B1n`9=} zxE-Ra6otDA2bPNg@kCvV{!nQBVcg*d${aX)upSkP!UGc{!wH@Lr-HWp{9bb;jvCOF!jviBpq86hlx%obuHaUqe-ax;Ae5T6O77`TD6R>7teAb6; zzIkT#ta+s3%gtLhB08?_OV_LgRT+oW6`$f2F-O&b>K(8-&D)E^e5ioJOx8s8XZR54 zV~6U#n)T3Qb8*%Xti?4=l%(#vS&z(LfY^ih<_NrE=WeF#AWNSPK6#k?0r1}@Z`*fN z-nPSz=k^NugPpkVtB@D$PRC`>O^{m$M9hMYixBc9zWZ!LImKHloSkf+}8p2CEu;sXjdf8R^Jx=0YCkaz2imCF4c1E= z3Nmpr&KB>{c-EE1iM-0>bM<>d&g7b zA^)%*nch)@CwK@KLQ^T=8un{Rzy_AH5){9wI=uoDDi~U80b;!3tHm*U1Yd>H8FZdl zk3lS$=KZdnZ63aZ4ffwps~v@AOeU|@%FwZc)EEM>Iy zrkgcfsj1|_<*|rUYMeZ{s#`GML;*ImIB{_4o2WK69uZ2*Y6MFZzpAHrEZB`wxtSGS zliFfB<;-WjpgnjcFAoFI^XHc*JPm$NDMCkr3nk!>tv`SqK4fGCEtbl+UJ=C`CAnf94}s%MI46BxkQFewpSx_-J0^GVb!bb{IjM zvAr(%XDm3d1M;(8?~yot*$Qs;AP+$fcQS$U1P>GeNfa=L2ZVgDo>|Gk4!L$cxqw`| zNRKgGyXaB4b`iLQpU8}|CsT|*lH-YH2b6&{bKb<>w0WV(V*(?bN2&D@?H&5PBgcQm zKS_tH--9TXPax21f?8uD9m8KRf` zk;qj?AA*9q`BTNjcUgLj7r6n*{tLzzX6x%B2@HRxnDSqUM2GyDV&Wg`IbPInsM5Oq zFSI_`o4*s4XTXbXPiD2q9IUeFGsaRLTFKR~&Z9^8lnH;bNcvFuTmSs8l^;sK{C}Cg z{9jDZy(p&ZH_z(@Uh;1$&eaEyE1}p&AWpVnN0`%Y##fguKC>OXDs9WPCra8> zyS4H#Hi7A@evAx-?N0bvK=jGozQp{LLMh&LQ z9j`>yK!>@D-~>2(Mk?MCh+{jkLujk5>?h)TrN;M3PmE@>KK12c>I`4Cyc09XQ7umo zd8ys4m-A7iGJVfP8q@~4F{z{3{uK~-T3(wkuT3gKp9>DE{5C z-}xz=FJVEKPzSYGx8D+ffywwANFpYlePSk_WZl#5Zu3cx%$z-sQN{0wLfc88>-R$q z!2xP{@33j@1QY&H9|>iBH0opCQ0?2Y;-&R77Uf9#iT$)1i8C8vKXhKN<$ba(?-LV} zp(JTNg=g$WJ(bJgy<4e&Kv-!j()Yeb62YnhLmV&gpWix~e{w^NkOY%{<@07KPz7i= zj5-b-DX-1WZ}0FiB98<)0^7NvK97ciFlrKCK*n1s4Wo^iZ1xdQ;cRfr<3D>HeNM=ViB+mz8&2}5za_wZcqq~h$0o{@l1$~#^ z^~ax!E&y|ZU2L=d-{>d2RD4L-teS9yy7Bp6bbb0nw?%BlEQGZhJvr*r!1$)R8EGtP zKlNTquqruNm85sxPpL!+CB^uNx0Ss|(Rr zMqX4wUa-0d+9# z?l-Xo=1XYxz>4zZo058Wt8+wM7n0UG(`f0Hv}mKFQXcl?NBUJr?*B8r$n1V|*2k_? zY{&evTcY<6uRi&afJ&9OnKL9u)>mwiW#KKH4FOn2@no+H*N2FepvGsTy9rfuy8i5c0^*{J{7(ErrCKTxOb z)5{);r*{y&(cLqnTtPttFM>Mr*W@ke$Hf7B9x-2DLJ&`v`SP)NbX9tv{edWBcEfBC zJ%CHHt>mz5yT3!WJ!F%&?P-v0!xUeRvZb>^KDSxk_E7`wJ6$k=ooh3}cvaa*SAWI6%#k^euht~1Z|j9po89<>U9;GaMXwM9=YK& zpz?py`K}V2bs9WH1FwGS=Y&FIkmAgg5rieiL6)|`_~AI-ynR}pV8g>iI3ulgcl(BQ zIFps-wQG6nuyByqhRfYB#|)gdu1J3l&I}Mk&%0emQ;Vd=Kq+)aracj01 z#700aZnz1>S32=j*^ZJ?Y>|F{6Cd|Dh#cU*Vksti$l&};ot~oq zV;MzIhQJz(3V~WkLe(Vd~$sJxTYC=Pb4Q|51 z1()rsJ0qb_8fxBxAvhns_LFhe*4mx54Q6}8bh~LI**XF;|^T40+5+uHOR_Mby|$^>cbh8KeT|C^Yi7Zt%0$?Anlc zT{rsZYn<-h^ zmf3+Pk}#1zdC9|Xm&`r~GA=Y{wOKKw;_+L!X$-SXPy7mFNI%|UXT-vJKIQBL<;aB=PC z^oAsJeqadOff5$A1U~DYm_w~O&87{6y)<75a9l|`i;h)#FsvAJ1WrWzt>|u}bVN#} zq*^e{EZpDVO)X4WCrJaPO(XGw`i`39T-|hldd#<8v+VX$nkDvB7~kgFEo_M$VV@!_ z8xPqud}H?De=`o{GD27~+VaK0wvO=DT-@yO`@!NR+6z}*Q`~>gsxVDja9s|I76x#1P?*-7Q0LE_G z7m?l-%Q6nuj%(GPGRZG(G5sUU=Q63EUt zY5JVd1}SAhkaHv!71Q=E1Y#_%X>xN}D&}V#D_!yCm)=>OBLxpb=RD&l!=GA{q%KS* z1tjRS+wrqZ)5%>7I`^yi45HW?F$LkHN`l4s>`bzMH|2ugb6FA&Bud8%!vKv#5>mH0 zQcY|>b#+~xm&`s76 z)#Q2r4M*uoc5>UZH58`?irAS+yH4#2;4rIsHUa)b$i-!MIR{Jwj7D)^1g?42RQElx zjPr`<=Aiw6#yIo@;1^C^4&f(lr&hW&-USA50*azd(dI9(o%JR^cqB0?IFDep*0_xK3BGNxLW*o0qtmW z9!*8+x~Oi=yleyANY`2G=A}9Puo)&5TxD!t_p3)70@6m_tTzq4pDYD$$4U1A{jpsWAIu)9qLbx)$%O zLw(eWZfedr$&&1*c5s{35l9L9KWh_A>`>Z=Y_sX@I;=K=ud5233&+3~<9tYN#-X?4y zg~@Ij?z9Q!K$7>91Z_fG;+eoQ?SY95XTyvwf5s zxC$4~N$17*dg?a|EVbD`YJD;xkdXC_D~`3pfRp!)oYxKkXGWR=b+%bKX9JcNOoi{# zx6UN?1(%<uJV)AqGSS8r&*!0HOe3$JU?l2dWtmTMmoj~ANMh0PP-?i4l^ zBV8$u1`-B1wKB_0(%my zrQpGh=6i#q+k#WuFejxWt3A)5-S7=j-aagQ27-ct61B>TI?B|=7JNF>5%eC5&p3HC zVus}~v%O-OnmZEi-B=eSWCTZ#jK+hj0J8%NLT?C8y+PFA%7WUG^-otDp$6{p(IN0BqH5f01s_*jLgm zT^*mY3MWPn11VOe%AOh`Ndk0`jij^QPRc1etsIaRT&HwCC@=m0sC)bPsH$skd?q=- z1P1p622Esyprd9qYDS~YNYDUzQ&EGwYNOJMbU><@%mB6slbi%{dYG14z1QA)?}NSC z+xFs9tVl=%l1U&FKp-I{1dvzXW;g{B(7ZzC`L2D=yab{3KELOmA0N$}{l50vYp=cb z+H0@wG?RM0qOCDfVo4BYeXIWX*+5}aGb!Fi$@wBX=-sdk# zhl=0PxxK2NjKNeTtIbq8jWY|XH zmFE;=@3mSyy~Ej*oe%i+@C{@>MK?Lkpnu(Y=nza^;}4tbwv6)uW%KDr6={oUV?dd5_`gF4FTsD_F;h1Zez zs>t`ogEvf?e1uEJd%)STa=3mf%j;1_?6u*(Du@cllWRqZ-;c>rtg7`!y%nCRBRN)otNAJ$UWDGo1+nu zve4WxG#8K9a8o9?y{kUOo!3>L?fzl;f#plO>*+|=l_(vq*|H>8w`gpDG>2fobOxdd zCNsz)rkWxImc^!IyWX#vucizOJR40ZKk(YR8jqTAT>z*H zSEd31xAq`xWp#BbQdFn2%hzDk9xK&GEhg@UvdBoP{Gl0T!1Yq)P1Y}{Vrf18vN?4u zvD{impZMLV{jS%3*J-~V{+nZ6LMo1V^rw*iWYeE?M<)|+28uW}tcF|-zLqM@w5EZ- zriIrYucgxagr3hLtol&4a|UdrLmGk#n+U9(f;ZzA(ryHenzlowAKX?gKSJcuLZjyg z{Rnc}x+$PV?{q!r=!7g-fpiklQfPl^4{_?Va7ir*{26d?8rz9V09=awXt(vj__T-E z8x8uMQrbh(vBZO@cd}!J)#`4+wBZOU!s-Cw09>l#EOkX{a*ac2v%Z5Ce~|1iP@Pw> zdxbJ7Z3|LNkkYZzd2@acax}0!*!N-bQ!UU_1#iO0aSYAprT*%5MUew!2|$h2iw9j% z4R{59%wB<$H$GGFYC6ZxY6K#yF;Rwwsf z^wUBp$JVMML|p-LZbSoSeKYOBZeRAAS4i{j=ZM8K*PcsPd6pIoWYxxUjojfIpi z^ex_LBSOk&F?mL;ip&L)sMPr|4JT|;n>-4S220He?W}x}5fQZZ!h{SLRgB<`CP~BX*Bx+YYeYGc{8lH$5(XJ6_xgZ3)Xg542dM9$TRM z%n(oE=$tK>TX+z0wAil~+er#CN#)j05r;kgjetCDJ7oaHVd=IG13ojHw4_6M7I^1p z9GMt?wUS<5Lpcu-U7(^Q6qeRnE%@=4TWKF2DgB&Q8Zr|N9iG<|kL#pO)+ID4%dMQV zthI7mlyWQNfM`jpS`rnKXz3*3guof8M&pRUp!1ho&1ekC^y$JceJ+2PRdsvOCkXs& zp?dbNRRO#ZSgD41qZa~85CTl_DN?umHV6sGM^z}3yH9AJtoBgg(P*yMj&!$Ey{xta zRZr-J{amE#(P~JmdI&=DR*qnzaP(m-p=hhM2wE6Usj~|MKX^?=>{^Zt$9}1a<->G6 zm91%p(KmLISytC0_5<*F5t***e6KwMVR&!dHxb+>X*ZV^~d!ydb?mkt1z?IDT zOA%`DmzPs2p(LARKY0Vk7O+lGICir9onjAG;U6O}Ja*P%%ZOH8cKIEFMjArpI|2<8 zUTb0bhs3%|sQtu)Gp)m3r|D|S21S~JQ<1@bpu0W!0Hv(>N(K|5N=Nc8xE_)eZ&n2= z#;Q*IVhdE-I;^kSlFrvxKdrB4UH9;!U?=MhK-J>%v}|QOfZoIjO`}$Ez*an7>I{C2 zEXXb$`%ZGnDTv#TLEQ4D@dd)Wl@C6U$mI#O{oD2JQmfdilZMffKz17sp`FgU+jzkBkEH z13T~GhZ7d^A72{I514g<%0c=6sOB#K3k&D}-;w-i6#mvI|5_{M&}bO6ISuPX{(i9+ z)R~DcpnYl)=qGsPH#uc(BMJ(y+Mo^#cIdR4Cn>36{MdK#Okfs7Su$#M><`y_5_k8o z-WjyTOVu~kxfaWt1ZoTP=n_&nF&+)tShom;$ocY6(@b;$=%_0spD{OSd%TO^AW zPEPWDoU-pj1>Ou!(+k#NTo=n*GYARxqZ3zT0KNv6e+*)3`BB$tPd~v|2l%=^)Zp8? zi3W^q-oADXkuGp03T?+Vojt;;_2a%}Wvs=&zUKtk)V79LN_$M5`qo{i6`|yhb zsmuYs(F7u8;(uVsG$C5sZ)@<;guf4b+3x^6|Yn7JWHjJ?*jhL)FrLu|JWO=c1IxOF-K5cpuXp z-BM?zIf>IJpwA}gr|MzK3EDQO`~<{jk;{#=3V}XzjA8op5wkq4By=Cv=n2!n9LxW) z#HJTwUD$z=g@Lq^Yb^ykF&bD3AWXVu3|KWA+LS!OHbZ{Mgf#bhT5tnK z>O1(?@Da(!)>a@}W<6W`I(|8s90X3V;d;TF!AZq_)VtW47R$e1)P)&14tsX;P2pFV zr$(^w>%mOpx+1+(PD@BOEg!11RGb^UGzcZoZ`Q>m-xj2Du@I7cf+xwt6)Eu0i%LK*)uH$odNWXbX^vAFM!MRdjLA%gQ|{%9o-{0#aLwf{PFfZcmhtvGDpZ zMfoVcOahO(?qs7km>e{(usjgSM>&rbbzx#`p%oz70r1CNt_kucBcI>k-`f0WhFLz% z54h(k9;wWQ{BW2!Zd}RZG#SMYQ5gyRTgb3JS{um^uuFO>+q2?^5MG@N;ZeHt(vbmy z+3FofmL+CGh}Rb`N}nDGzQU&yuWTAshJ6q?rHLxVCG0||iwX>-J4lS@yMl_Njl=A) zwnq{+MoN523GYV|KBI&OsDzmsUmHoVB3MKEBxeyNl!Oza(7r?Qk^V*F zS5SOwxEHD(I{a7F2boIyLSUwB&2PX~q=WD|^y~F#N!sr_JA^FHFt%RHU-mAMyz*pSoA<%VuRxO6iT$ z?(pGh0UnO`=R?XtuJQsN7X0}}HJr)AL;U$BHJr-BL;d+UhNt{Ar^Vi3{`@30Y~W#o zKcB_}={=w~u{Xh=pQ45XJUrZ=Z&kzXJUqgmpQ?tNc-ZLAPgBENczC2gKV1#~k%zDI z=Vz$lw|SWP^E1_OB@dhY`B`dsIS(iL^Rv~klZQw7^K;bjTpk|n&o5BJ1w3r_=TB3^ znLIqkpI@kkQ+fD$fBsB0oW#R7`19wgVFM2*`SXj^a1VaO-eiCNJT)BP;T!$=^VM)W z50CZdJJoO#4_o~Ci`DQJ9=^$+zeElHk%!0m^IdBAZ5|%)&tI;FD|tA@pTB~_t`%bM z1pgWjedC|&r(*BT{x#)lNauP^?7hXmhKkTX*OOwe)xTz~8q&G4#ok-}Yu1H>cCq(1 z|C%?$!JEM}{an#aF|N^&h5c*Z(NbB(DSXLQ>+x4SJsxUV=qL2&=%BR>QBYcOmisGw zV{alauDS$6;g0UZ;;Hcvt&P%|)-xdd5!in$dr>l1dGqwxZ{SmYwIHf=y>l%rJCiRH0 zy?*|O&%(||8Bz`Q8j9ga5C1gG#C}hlhw~B6d6vE$PDn)ul<;JZ=tIb>JGxH}(@nT&_Y=O=L+KpzmEhu$}7sivqqdui#S2bxrSk4*fJAz!_ zAsyU;d8rc*juu0jzM>9c-~>n|O`jD4SZYK|v1K83(C96thUR0+qZ zB&$6bXvaeV*qd|Zo?1D|(6q&FkhlxqrWH;P3{#;IFM}nXHlQV++vLevm=_6(NP&|5Xnhj34$~XW-bN0$Kc{eZ;KZ|d zBehp+m;I&5whv899quELDyXAeNwL;ihc+Qu`4OAko$gmRQh|4e~94fc?lOfsn_y zbVq2*dDL}+wsfONj+mk>hAy~0neW8TK}KWZKFBMv$^jR&uUE(~Vf{z!`2yDugkXG5 z`hi!m{gYO#P2tJ`F0ZHv!NHex2}av~6r9Ddz;S`C{h<({0ajZNyRONLk-U`?zP)bZ3X6qnz1UlIqX8nm#{AiWueS5R)~K1z$2F1ao%uJ~ijP0N+is~+i^w; zhB5aOFk20C#P4W4>ww^Y0MBEw_}YQIG4Nc8uPP!%I#f~dHBhgNj>Xl-99P1`O`P|# z@&o7{=-7!;u$-*eRcTIlJ>$*tq7lqhFIYe<1|#}UP5&6te`2Lpcdx_)eu_G!BJhBT z8CDGpR?!Q3QLd+a_Fb{@0s?VWfe?t+g2ZO*b=}93>&O=5=cuv#=vBdRz3hE5@ew!(}It{ zO)qFdfaD9F1S6M%!Nd58*^q9)<}X-=wyL9h(XEJ6UagMVgHpu2>ge9{Gm3b!I=bil zJ4N8|01Ygs25Lzl9gAueD{ z0Jay$*?Wf#zOM{_LPbcs$Z)kOzXwN)`0}&&E(pHQg>D}bQKW_C9`_bplG-R!*1scC z$KE?0$APHxP99*n7g%mT%k|-~9U72|F7{g=dw;;z)cQ4{4(Gg>2m;iUDYQ7>iN1|3 zg2Ho!bsl)dd|2mLoeOL}R{1SNdl)X(&~&rcwVvtn!CnbH;qwpulR74lMea1Ic|wna z&O8gGj^uV$W}q#>`~9t7qX1uA(N~t+1A-=F%Ov6-$UI+$d|zapw*cQxspA$(RV!X7Q`OQGOkAGRZzmhYo_BuTm73lnYrZpY@gis(I*|V@=JcZvh)Ji6J zE~};cYlJg-;hab{OVCqe_t(IU8GL0y{M&N@3HZ`c{cg3+Ow?IQ4X?}I?2b>W!bD7NV0v#j{5R?KC|L_flyfw}QG2LgRq zK?X%It3faWKc&C`0WsQT%|cLY)YIS39|k%tQU~O~!zfGRX=EStu=m?q&$piLYDj_e zdyN7>l4K8HMuALZHSk?`7SuvwTc|X|P1L4YGgy`985#<}P?9W$=9fGOBjeC;_8Xsf z<}N)t7+eh~N<-OGn64zNG_-!TUROhfx6>D!ash`k7)4L#5M^FzC;*;WGam)``;sxK z@yZk!-NJgPWL#;;^Dl4fYN*2P^o0tL(bG8$)hHI^!d9$?vTmoZm^u;&NjMt&}F6+oMs}Jqy8k}$JUa3Li7suXTUJ<8ClGSgZLld;I>`Z*W6D6lHd^hW?oJX8q*fsv)MjAsA8P#^gwNIYoE032 zN7JJ*>nCCYN_}GivpmaCDcmbvD9&<#WLY^`s#3dcHRM7jPipEu?K=}M_70a?O5Uo@ zhRIh=GEnJE$5$W`J5!NVsMMT{y8&G^=BLpnH;LmMs0E!jqw@v~8*GBafkbpIorzTt zI}mLF#MV1q*12OrXwn6-UXQv`ak1n8sEf1*2xf5P5{V%%@+a92<4Z{H!?%ER(?kH= zi{6;oZ|WTvQInO4=i^X+24y!pfc|9YI#}60L`r=a03nJ*X&+S+Z?q$DvQ z;51{c$pd%7{sr+ysfFq;CZ)6y=t@c6$I4vP4)HUf6}i)pW*TU+^(VLC7J}4?0%qL! z`3=Sn%ZCNI3{;2wIMD83;tZ5VK7taJ2-2bCeE^z?KLMi)PNJ$;Sj|)HyCB(NU?5Xv zQ>Vl}oPWJW!XcSV^i*TLQO#swN&}wa2T8!0na69H!uii}33??W9&F2qB)dpLKy%Y3GpQKQ@5-VK59$_S!JMKCRn=($wx}=fg^|%or|uwp^Uz9G;oQ#FYN%;WE)66 z%*q&gT8O5Q#G}{qeM>!P_LbvUX2ihR3 zk=?$4v4ZxpvpB`kp4?YV7Bbmk;Nh=PK+&0W>ZA;(6h9il7W>6-RPj6J%!oK))toRV zY^j-!e9m>t5&Ld9)|+E>L94*^;m{!#z`$@`Vc_PcwL&q%_XmB=?z!um); zR5Gulgf)?bsD$232`@(yqLTVaN|+T%=s>XME&@7*x{?)1*zrnOr7S>u7`P>z0K}xL zr(Z=>;CJC(lJp(EUqw^3Kx#{9WaT5l=}DkH2@Ydq6*nNrs-{_|;f%bM<+QV$dg+As z)srl@Q}X$~hH!k?gzQil%!ZT;0k(Ss!5#09`(0%wgxU=+S&&R zmcc6oOPfIZYMOG8ry)toH6%r{_rGoZ3KwC)0dP~Pr~6Bgx&e||D=6@nT&xs`2b!^- zeu)RIo1}x)bs$#oKs-XD?<2ts#;(n}c&MXEAXv;rBr`r$;fz>a;!XsmfITD~lTaHV zSP=g&Bq(mjJcaNe)e{ScsLtN+W*;1BJ8mVa2XKaMpXm^B^BbDNecZxXU6QpS@^)iEx{Xz*`tJnIl)CZzR80L z;QPlQ2*6xb>QkdzS#CRfzXy?OrSQaR9N|ID)zGl;>m|; z6i~{FnQ?}F5a63hjX1FbZb_(T6||9^=(5gaBKduj1uK_sAl3A8xyGQ$H9goVrqeWN z($c-tKG1p79+eJL2X7&{2xs)vp|C6iAx83h8qzB*_vX=fv5I2MKfW}SK$ejf)74)W!qD&4&+M7I3Y)6w8*r2Y7CsKh(Nir35STvqaZl9qH^%E-sp46KX zXp!XMEVPl%;l$!@JCxiA>9`XTQW}Jg^pY8l6UqCEA4j^yag43`5anqKgcW>!j=ipj zCPTk>M^wKT91!k?elJ+^hn_&nmAMxP1%vk0F7u}~Y<@W`pZxV}Ya%tz2qk0YI}K8+7nMIsi7z#zT_ zaN6$I#pzaNv#Jo}5UJnRTJnmmPu~ko?*h2!kJG_je_ty+5oMbR--v^WgH&Lt25W^R zCKaZXylQJxo@ccoI9>i5fPwdYFfGby1VifYK5A=|=9&f z)UwYtxl+}v{%nidZVI(qjir#P1i?y6uxEp0)WDxiR4!JpAWEKqbmuxk3#(p_A2nhJ zA*b!hFmhD;d=Iihh50u9lw0WpU!HF1s>gLUvi`&E8dlLok?E>Oh5=e-SXlwU;J4dq z$s>bm?*&y$XZP1pe+<5b!K?UV@Oh_RDjned7}{`KEqXI;vE>qUUzP!>;rshz7~uXG z;EREYjR2DTF~CVfL2wsAY=E`bR;ve}{F;}vC%X0I=QFtbtOzx8kZxsJxLbBGPggJY zGTh*w6Y{PKEjyuP%cd*pXpPo^`)Vk^EDz>%QrpdEjJ63yYnG=Vy(72~cXcv)|T5bH@|$2RSz z88xOFEcjyXnhoI-6uJVCxj`;ST6CQ=^XKq1x5TVoB8|9AiJ=FT5C-=hUj3t;r~NRi zYUkL4?x`IZz%yDL2|(j+yiLk9EFK<&gIlkPgMc1gDBZE{s_^Gd(+F1OzqHv)!Odmj zf!*TyVQfkdG_2*NAV}{sxZ8cn)(Q#DfcHA4;;|omIXam50#ey#V_f;JS1NrwI!Zo@7Fe*(lNG01{%*FT(fw^O zjyLcpG9M|ufXq?ZNP9pdG9*JH4>Vim-=y>s=Bi%blllb);_YhmR4%V%iFgNV@5 z4&oEui*NMwcDOQ2d%r=@J;~GmtCf!gebHQhiR3!zDlF|iOSzu%^sis>Qt)^9_B1T< z^rshJAN(Z+e4xxYwx*1}`|DnR-CGdn=qy>_$r@g}YMOU$yyIfY(*zFtHHL7F0frB_ZdYynnum5(M!aaajB;eHse9E&jRFue|O zo@%}ty^{Y%+D>O+JwCNVzu5T zwJQlVw*i2x;UxwmuJV}m%BXOTnp=@Tn(^ECbyhDDZEfnj4PR#sJPyUY2nQ}d3{Rl2 z_Zk%*yn(^MDwjUNuha?)FXUs5@SPjIu#Gq>7G1^Dzx=N51LUXPH+h%^ClCL$vIk}d+);d8LN6S@Da#Q?N`3KwXFX^ocpa6z70@D>=v zIwO)(HX3tCok#B0QgwgICMAEG)^0iv!%<~QL{g%QVfzw)nUL@bW>=F5cqT}uPjVX+Wk2X8q^l%!TcDHZw^|~uub_V z>)H3=#LKF#&51wB3yTiClPW!s@{nZBbjXewI0;9m0I?3wdEj1rHYqv{aH;_Wa~~84 zn!y?Dw0?M27;v=kW<5}3s=U5%I?4a2O!K^2UMGWc7Snb?!VuFbF&Vu(`j;k3$AWYb z)^S-<*c!62BoP`r8;jM3OOXbzF}dVEM?Wj?2ORbT5q+a4D{(k0{}_=|9L?^|;Ai-X z<>%g4n{;u??TV%q3i7{*d!~wXPPsAKBMJDywnBh|p8>~Z z*A(E^BpI!qOr0xvk>LE>3NY0xQ0|^U44R{}!`7^CakR0DSycIf;B5M-Q{p!2Me->K zvq+5}cyKjO+V_#(m00&kakPsHW;nsq*C zZgN`W$CDTw^fkp5J{59n;s~)3_s4QBZ3heHP;{Z{FvPtckr0~Bw*c;#Xo)t`g&}Am zu=@#;&KbUGQYde{vz_`F&Z{ygQjh~4s72%T-f7l&FVs2YV~;$_fp$Tt@Q8apBND;a#XD=O>;Ti5c~*fpMF}1aUg4xuJf${#9@y2q*W^^4d! zsO4M}I|Hvh`)G;(?hMS~2FWI@G^sQT;dx4Jl$Hn^&F-tssvxi;J2Al`qyb2<2xQ|*)D8nLsXo-H`}eJ(`F! zGbq}UkQ?F^YC423A1=nU*F1D9Tlxw8f!6k*uKTpAx0KrS`xDQhuI|stT%gEWr0+;1 zt(#d7UQta@;bcBZHSaLv_&cV4QVq?M4x)ud7)-Qt*!1V15=P}arB*deu+B@|6Na{( z9&Gd_)}yEihhD_NixMx_66`oep!&(eWhR-O0PYT)RD8_=Es_rM9_UY~z&G%=+tj#z z>H%&GqTPdRGj5Mdetmc1!FcpyG|s@u804T1ArnGIHN-abk7FCokMo@!2TgC%QmNhE z)qQH=p8n4=-TK`N_fs6Vp0VhC(n&I-D#U!dwJ`7nrvNxmfSM9`=TNKm;wYga81I-KQ5qGlMT2YygCP zDk%B}>11M`bXaTtMYZ`-oY@i~l^!FSD!x?(;AYjfFGFn}7_w;0o_2872{KL|fSvgm zT_@L<*!*z5IU(wG^X(19Xy?YrFSI75!*yN=mSuFZ4~Z!a7FKny;5ccIqiM^9)wk%9 zo0-@E5_VF+iEZ2~vu6Y*;VEU_vxnD#7C@m=mX&C3;m(n-eHL1XZT-XR^v(L6`V)!Y zIxjwh(z@)QnP$xi2viWn;UranQ)hbrZ-_j97ksQ%!9QP&1JYgdZB58%govQ~V#G}3 z+J#)z<13WuQUe`n40)-3zO8{SEZ~F&$$yR{H%5}lgA|?ZAX!E-o~4_QB|1fg0grWZ z_fklFIhq)+CJK}nDKn8G?bHf>jX-#pT&){FysvcEJLGrjFMA1b`d;`8C5Kx=Sg#k( zi+H$h{t>)3zub;=vcOb5O0k!*6)t~(dzWHAg7?E8L!J2YNyMqnt6(^TaDd@p0Ph_A z=_xbRUuqE{Z0Fo(w*PfQD*}{=;dw8`41tkbE`b@v&1e z`ugLwAR>)eii3a zhT!D)z@K;QZAD5uM9N^|K8N8@TEqF#4q?=pWQNZF^YN9vlxx9ERmgBDL&21ET*DN73qI z5KdWDgD()buud(Xi1KPVz#MioHf<7$3}7Orb6l2fQj3M#fpUdskR0LdM70?*j(Srw zoqS~7#pgKmmf=+!`g>Go!fTO}jqG z#(oiJVHnR8eK)E!5+t@p{XU$1SXnt4H?hshV3d4Ef~U?VPPd+J64W;VHHRb7t6LyCk zQR3K?=pWFKCs8z`+^J?8D*EDJunQ2SdgTUM{MC44f=}8V*$>m%A8g2Q2}G|8j6)BA zNLZJod06?4AVD&*{=pqN)(5h1KAV6DsRP=svPLusL+%?UW?4&&o)wdHb+3;I(zRw* z@!yoxg`^Cgbib~4)rjD~@T7J4t3O{j2V3Tua{zHsc$>&7UIV2;rX;lll!MY`bzgz* zBmo$c$NiI!JW2-c^CQ)aD?(==NSc66QtKpXzp66nDF~KUKCZ07ib_5G9v5Don`XW7 z!D-g9C9g^K7-3*}*&nF%Y9< zv(<43&aE*NwJRsv`jk4v*h5OS)NJc$* zv?x9J3`SZE6}fXLSQLB=LHcq9Cu?6X2Y;Y_6$kIa7x1XpxyDeYn}cK~_9uivO-B6v zquwdK~cjf$}lJj<|sHD85ETOQ?C7birThc?6hc&VDT4yxU)I}tzq?<0(cv9jKa?W zl`*c!O-tZ#PS|k^q&(Up53PJ+cw^p(;Uf3DD~49FGCc~BLtE_kAHt1LXDJ>9xCe30 zLuL3P50%a>h+CAZaD3kG&m(~!PXQ7kq5pZqb#ZESR7;$$avYsqD=a_#%Wk zpF`ZDw7@cy6L*-g_bMkB1i@0IdDoZ*Izy981970z-y;@_7v4L4W#l~Wc$_~wIWQ!f z2hI;E+oMDp7`pU{`@%Mr>@>7uP|={+syV8;Xr;p3T= zxjNt1BXEBOH}t0Xvh=Rwc{uctE=fA%6YCcxohtt@$@Z$iiE^L|_b}+)TDu zw;#e-){IA*VwN-Bnv41nYY+L(jSe26aoLDxDj>^tR9acRCfiIB|pQ)fT2U!AVu~XoqQ%yZoPpmU2 z;cwvtm2E`+5`N$gffTS^N`MIQKn{ZdOL?B-#I}ZdYMriYG%g z`%~P9yY8{f>)=wz{f+XY1?!d#bu>!@?oY6!zOw0NCKFFA5;d(o`;n`4LK_~n}n@HwrHbu?%W!2>aiULlaGA*`jL2_Hau+CO@=!ZKBQUNP;>G@dJak! z@L^ZUDv4UqhS-jmzoQ$ELGz7NLaFj{vz!fI+onag&w*fR9wh#of_~J5Y*y)%Y;w|! z4481`4nyV1vU=PzA%EVb%AZ2y>L`FQpcHkHP@!ry&0P3&|Ct2O77{#%L+~6?@}sEW zIlSZrwplPL%_Ll7K#Q!yuMjo^5H>aYg=4PUBl6~O*BvNmDV)PaP56)3M9m{X2Zj~h zf>em+)svnWVK4uV2ovJ(S%F!mg8NwEN^iqa>LV19?kxF9vpgqBItk@Wx_bI55D`!( zV;^iom`mP;f%U)V_<^iz&<>ay;C>dB&f%4&(2D|*^AJ3$t{XAkfJJM1QXzS+f69b* zpKjoCv8E5gF^OR11_nV9Elc&2_$f&ePCcx2NP>rYa7Kc+dLT4l!Ta$fE;8L7JtC>M z(g}$%_J9Pk;TGZvN(LQ}sHY?KgHK1kQz{N^#BZS?l};B1g8#w0sh*6e{M!P)orwi3 z12!!{Ic7)_9FMva`4Nx3OP$NZfjutzb9-6)0p77NsVHOB@%2u-o-X`1-3b!%W+h7d z94AU1!?seuIo@F?GeF;`GX_Iut2iz%BB%h<6Uk{TGk)M#99%yGr z0=iskTRLy+;I)bU0=mbM!OHd^1>)+MC0sIn_R8xVA%=9f@Z9kZ@fL(;1>XnQ5Ma0X< zaP$mh`)*VXahWpDhS?FN^Fq*PsfCb%s=tU`tqB#(-tVr&-%lMUw6}H?XxUcQBT$FO zL{`?h()H)PuABJ%CAOK_&wfH9m=D`xZk@~rfaNMI_b^nwM;zVmBeZ}(rZN$(f$~ql z4fu0A^oumQy`4EZ_-7InNJ`)Yq1I~eQpQISvS1xJgbI6wI~~m> zFG9=S(%oze>U}U97s;hA%)FcR%@`19TR%n^nTcA4gHOd}`btdRoSRUg=o3>dY;%QQ7?2S_$!lga4@l6l~mWU zswFY>d5%@3e}DSK6E**z=+l1GL16MX{5XlC3sEA?(Vqtv46)vOu|9Hoid!Bq zd}q1ma5)>KdV#JHxsPeY%K0b01>jK`g-d%Hg6wtN1q{6x!VP?-W$9R}(xqh?(kq$L zoGfb8Ka5Bi2scUjMrjTWhf#XPB+W5PPx8BLnzu4(S&H<$ReCa2++k70_#^PefU$FC zK+;sMbLP;`Fv;hfiI2eeTeNi&XFNxo0;P?2nki-0ZQ+y_q)?PH#A&6JJ>fE{z@e@W z2Dw-uRpOIG7I#RbiK&^7+~93+{UQ2crHp4WPcU%=GC&HLr1L%x6N=Z)VCzNZh#(u6 zP3(<#<-)TYtN0R~#YECUN}pN}qd99`mWNf~FbHpP#3>(nr>RN1vpU%31lPTjzCnun zeoybOR!$6(f-0KfBxRUAiJKoj?de^=Vo9(P89crvp5Ao0DqJbBthi;%J-v0Wmw2C# z^X5!~#bC5i_jBIo;}Ho9#AqZdzY}amnd&)p5;eNUDUQmWo}J@8m&ppTw|3=7CDl5R zbHD|;zgkj@pff;WT<5|L5C zkyut;ud3I}S=j^8LOY*XF>tW1hy=kAy~1XU4tXLLFH;PLLC(cJ+Tb4a$-XN{Y~O2s10EWXf+4jc zlMZ)4a}8|uSfmC26Mra8v$R2oTRsF|r!Nu$gC+C@&~!l>wQ6)u7kr++NTC*-g|AY6 zPo9-c(mJp}8`hV0KmQU&=SKA4^a2_lGE}23Trc(yb4^sQ#Iv%YR}_;bel-il#zu;f zoI=Ht@%}2S*ntpw4$Vch^JVdvPTr}Z+H}fV6zgCIIt1UTK0b`R8xbs_dfqo>!w zHXmVAPgE9Vn2Uw-F85~~aankZlwMaz@$_acO&9%ooRyc1-KYoN(oipn^{Mm=LN^8Z za9~o&v*)K&J_3o!-;fH+*>-FTbZ~xncTUZ&IVM`aw-nwr?#@Gol?sr zx|%2+))gn>6qUYl4BdMj*1~;5q>J9luFw#jcjG%y3J{1BI=vf{E{)U;>1ZFOn?SBW z1bY*~i(^x7PP|@8pj><#RIv;3*(?Xw0`h32I`xe>QG>e3Hn0@OFH=&GoMS(Vch54$ zK{3$5-#xpim`TYHXa}UqEa_ri%Q-x#*g=&}MJEOMQM3FrlMKI##H`1AkWKfPE0ON2 znIcatp2H1E=vMI%T_P3a6|O->>|%#+)Q4{@L&g)h6wTjHG(;t268_Ye6i3C>OXodb zs$QPpEJDBqRpsNDD~o?4h0-XCCPJ+H1Z?MB{Qp<{Pv4rVg8xMP@rZw)C+q2uMK+8m z5a4=wHd#Do6TsQ>^GTS97Sr2Y%aQ`IR!4#q;tX6jLL~AeJ>i8Suz$>^S(}{#JgR!L zR4Sm{Pfylj%l~{5Wu*FgRIN?8J}cr(AJ9&M z=9C)LIF5yPkd9EZXqHo#2(}DVNvA%Ud}bph3z0b-0hsd$Q@^LtgJ~#9mgfYxiPr_* zfiQK7)?-+pAN+MpAKZ_~d1e(9%Z9xwjyWC(1VJmPVmgPLwNb?!Q~2eWDGZdW6^7}l zhPaZ`KpNqHOV;Q>9dGEv#lNS~Y~Z#pc)-|;cw*zH zBgoq&e_t$k<@;j&^KHGg;rGSP*AljpyRY%Sm>%W=cu95@93&OLLl+hBumI3qv=+J@n_$O@#z{6)j76Qi)o3NI5)@c26N|2-geP1|qME1-4fb+SOOF)mcFD zbLrnbfK}EmwyD?L2Ak2d&8EsW6(6m2+OuPx&zT4IA5(Vb>DoPgtn$KD?{kggFhnkL z1+oCawJtGvZipSJ@4$EE{W}>K?@aE=xP9kd2_YR=WUKd~Bj5LS%dJv-@V|KeD=x5A zwnd3R`se*spaK3)4PkwcTAts3`(F5Zb}+t_2gBEIhQWACFkj{N&8_Ni3+C@!zG+@f zqm9?qJm{sm9gDs5mU#Vl(fz4*+)O@kk}&%Wa$eG+k9q>k45VlF&lU!b$;Yq;VBoL(A)6IywZA&(Wh$UD+1FZKPXvan6Te zK>`;R_|`jH522OtjXRzh?_*;}!W94scybb(sZfS@ql|1B-W~LV+)t6^d&F!d!p}j} z5DlT;@e`{C|09P7eZIgAurms+0$@#E;o;KV*o*0I#ZE{^t70>|d&qjQ#f|YLbD)G8 z`5wO6-AyG++1+*Q7bn2Vq@L}lRd*|jt-=+0t{27oL*0IekgHg3qu3MYx>4+jcPE$n z79p;yUeA?DFbam|=kASyatESt!I^G#BJL>JhkA6RXNzNRHozr>y~}oZYN>A_3Ip)D z*c%mr%gsjyV$t(BUo3(%GM9BRc|lu(p(qvx5S+;$Q)inyvT(j~9IjeTltOB4lU4kikzaf@6@0%5ho-oxt@{Edij_Lgf$Rnk|0Q_%30- zmY*b;m^^JUd8ig%9k*jZM&lN|9T?&}iUrvE*B)mDP3$j+OZ#gdLeJJXsLIC;xa_kX z_|=IGW8k2m9p@+rU`fZz#2u(|#q1e$>wvCN%nzJ`03z-%;~^-k&h7D_?{-!<`J_=)^iZk_rR*_j&2qMppd}eb6uhm!mgReOJT$G!660n5#F7u^`-HBS067 zNRFpB&ny7I6nF+h7+y9v#sz-}1DER0fO&pP&MDwzBH$kt;S@3ROBLi-(pH^(QdvHV zmwa9`s0>q#W)$1pEBWZr)>XRFT}0*Z@SL+-n_^d+V7q`Ksh&@;($jo~_31f5VTS!l zonUQZA5E}6nqYoD!F=kg@t8a`!9Jo1=EDTTmH80G9iXE#4;QpC!QjSWA12tLDW!fs z!Qdc+W)9F=gdw0|noGlk-poTh+zhU;IxCB;i#!bjd_RlxLl3t3Q+C6`{Yj~zCNj17 zEb=Kdzbu$n7x*y1gA1Nc!_?~4yB>wPGv<{01sY9AeE4hCD1%EfN;WS7;z+R{jZ79T zqc&Mm`MJv2m>RSh!V{<{4YKlM5M&AHD+R*~+rD3@#(5xXcviX|WSz>%It4^P_ej9S zl|t$1#HtJ!Eh!XJ5Fdsf+#RhByehQx%@@V}bvB4<% zzJVP>)r=lEc1{!f^zN@&)k}SJ8EN_1JdsJtPeKc2?MXMaay%kxl8RQhQeGJAPu*;gOqh9 zGUSezp5Ra3KP>H?+!Aa#3&+qa&GK_exfmJYaaSS+&NwIX_t!ptBN*0+cju zzZohqgh~XhFby>^ehiSDVa3i6Dnv4NV^#6AFt=jGc0b3erp7^T!DD~uC;E@n5wvys z1K|P=bSi$&<7UK__60EERG+$B!?l2XD3nn?`!0NARDEJ+JonNYv6qowf=lAb#kb2# zu_MmGnSZv^3B$fQp*Bb<1mAC|nVogwqD;^1M|9wc?b;% z1~$Rsn1r_2^#r3qBumqdGp4!VLUgBQuGB4E;O?u8zVl}GUcH`H6{aiwnye#8o*NIZ zA$a=}>J$^MO!i?F!ddMCF1204@>cp4oNld3XmU+~y~q;~z@z~PU!`!_f$VT0W8am~ zhC7kj&~}buIRp5_xWDORIc?H$`%zZaW!pEowDHq^Am3IBlc0`OuJILJ?!Q8uOO}pv z+9++@i)2*Ir8?XIjpvKV3hR83*H9G%4fimSc+@p=RWYwTXA%|D#dkRsX=~{;+};RM=MT@6n9ix zIbi<>Kmdnt5mB{{P2BuVa^@Cg=j_}v^V$)&InlC_zbj4Q@>Ao>UL^^e&t^JLek8h(JAY$%wl+VXhAc@Sy zQ(*wn1`B6sw|qg&$3{J7BF)EBd_I0f^U?diXmZh}>NqGW#Y<8VRJ^p0I*0)!`3^IZ zoL8o}9>OB}XjcQxO1>)!Ps|fAtw=boPE5<#=)|l?rM8wSr9M6*Z>KF3X5>v32i%)6 zBX216@u}Dl77S_UMe`AUoPnwD&PQ^3>!SHMEj%C5Ph62|%8KWfEU**B@>U2p%!jHY z0w1S^sKD1rvR-5_1+!Afd+-de$$RNs-pkO)CKLVFMdiKp7U?hup8N;kl`<7?CeM zEb^WCnA0&(cQujE0-hd2YX6EwWDq^^Nrd$P0DNaX(D4hs>UvNP8s+Q3mHMc`il}S& zb0Gc7HT)B8@gObiUFaH~b^gnJZKaeV44NpazKpg$(jNO+?^Y}SY>TY_I0t~n5UUt_ z?}!CFt9lNy@{=VMzWvySZy`C^(N1EV4pemkj>ZHTjtiEDDhooz+f;}HlS+|XLJ+@$ z4^D5h!v2r~-?|85nxWs->o{T|=ITyz0F#N4$YR@Z*%bP*?xPT_3R>Y(Y3bcCD1a;j zm4x@rQFJkuO=)AlY1nQ>-H<3))f@cQ8M<_vbVM1x{Wioy4k}6T>`t$onZfP+rHitKOUpJ^O9Ft$R+yBg{lVF7n$yTxW9ye6>2M5iVQ0j}v@trF~)V zFc*Nzg#+9>%=ys^ySaB5c(1vzuuFC9GWw&vL?a1Mid;}#C^I?gA))IHyJf%jri1=RCCwaajwWzKklG_K*=XJ&-N#sNtA71!+X zBPB6CR^?yfqS>+zW)JW*d>HGU>Nl)c^&9pcppEex7J00hThxKy*Vemb9mQ`)IOC3d*1)Ar@miYgP|FC%OKMdrPLr55{`VSKdAo}bE7ME>P zzKB7GK)GeJRULkpFvRBXKHxhuK6<Y!D8qyry0@>~HZISdJ>cf7sHzTeiA zykFJK_d+wz%4k0Dv6C#YlF{tbF}#9A*Eb1ua|#2WUW^Yb=sQ_e(04X&;aQHLvxzSq zn{PY(NmxODkxO~WSaLRT+|Z&Ly_o098bTWSVcunzq(jC3CexFrFDx@E_o>A5{S@{Y za9N@%>KInuD{d_kmPX9g8<#088gO z+63qIxCRi}^h$W>B;~T;95o2= zQ#7K0JnYZ|9Z1k&Su;92CQQ}^inbFvh+UGYFAPOo^FGqUk3bPEU$noo0`vr(>n5lZ z1osWpg5J3s?inJef$8l%YV=TYk`mEsHz-LeJSn(N8jiNWbCtA*92c2XA67)Kg|ZE9 z*)axe^W$UWXNB@zmGJH1TFCZDhoNvRJ;m^^^mF>_J0&FW*tpVD_u*gZ=k(WWl%+i_ z!P3uX;a}+~`s=kkZQpgr<8Z;-s#wQYgn)rK*CS+qFnmfnYK1hv>%O923QxMvD))wS zejdp=UV@A z8n`dlfKdq(X^aGb>!>i!lYbx&#ZcuHEYcT-#;pAjEUs1wI=I)!rx6_45i+<9@{@+Z z-9bGFR2RCxDI-r88nYD{dBN)o7U{HtAVO%GKE~m$@>hIpppVnh2DSE<8F;z0XoKIY z+UKaYPf_ixaJ3ljcXjD`crlKZZv)*H0E~46e~*5tiM%J`uo(JuR&f-Gk(}flHYO)4 z{{#_IV?`a*51WUW@6Xe{OFcw*bZE>*R7chSH}d=^MK3b(-fqz1e}i~`wt&AnKm9?# zKp;ttT6->M!E6mXfem;>KwfT;=NbvAu(S&-LJ8?1={Z4}SWU$OuZ;x3c386E#JQ${f_W=el0)LrgUpV3|7MZGbT7MAHy^a zpB8ahg0f(^OnkUp6JiEn=^gMMku8XRkQ+?6T`&&<9aU4#z@i&t(Xp&8T>hnSd3w%S z@7l|U@1<)G-x^>-9X?~s@VQF-I%-B6K#Z9Ro;yix{qH!PRz|AdC+Yha(Xng}#Y4Yo z5LXI1?@EI|PoRwhE;X|9Nf;B-S>2D{j!*)tNW({<<4ZjbsHkH^EHy?Nn#Dblq2U;W ze<7ULIoIM$La=^=BQ)%1dA<0hv83>z>j!~Hdt+;mc31NuH*59I#$fMq-Lm{Acr4Kx zCTbQYYS$Ewk=ct3ij5yR;ZDb3`j2M4LCZ?Csgk}+kS;vez|||#g~0E-368*sA3;Qo zTzCJJ%a2J@Tr0ql9})PaUayb<>*)Muv2O^Q+6P5z=qC#n3=KaM|3*E0H2Y4+0qSIV z9G;kl<9!Gega*PIDW4Prg$Nna=h##kp%QR6$3w?uz>dmDIuVm=bZIX}!{dT&qI+~; z={y2Tx{hC{r0XCJq)!N(?}|ko$N%?&_rL%A=i#yNW)FtF&i(%o-p1#z3h%?$ zhS&9v@C!`gbQP*>Jb0vndOe|5_~&rm^pD{jcXc@b_1g$uRrxVf+BZEkW;qzgyCyJK z^{}XdPPQ_cAA#io;F6dJP$odINqcZ;;r zvr#JeQ)DH0<;fQLHM4)R1y$<2#_WnZuklwzyLdnlF|hGb#;EW&8WDiYa-<9e#mcXI zLptXVsMxY^>~%PWLDP?$-M7ilQirBehi+6m)PN4TZkhp9M7AL7Izo@@+<%sW0r8Yx zr@Z?==UwUqZYyRAXNJnfxJipTafLiC78*cn&#> zp)lLJVem=*>%GEL-i+1Cb>aPi*l2v*Ws--|YIsq;F7W9|fPIDwiz`h!*LC)~)p;q9 zj*L3@cqLI>2|d-D(I{h-oZPc!hsL-tDC*|**@i$O?p9{-S3@-7D*sO?F#wej)Mcsu zM$s;&LX=B^sBLk!{|18gr(nH>fiJ<#MADfD_Q2=`BI)lN!QPd3An2MvA!d-RrRWRJ zol}VGBMjXv7!tTNcGzYNHJ%NGkGMD0Oi6VQOr}iyEHUt}*aT@jQk@@3Fl~dyBAkJ| zW>B4=6Y)O}{l0vMtn+?M(*RY|F|GmKymBIyff+&%zPVx(^3g2%1tK7Vci@osm-kXF zZ`C1X;#*ts*PZC~l4>K6*bi**1h4Taxq{-bF_g@e;EiahQt;jS6f)h-6Pg`O?vaQr$gZfQ zC>KNHd@bRV7^aK)2Kfg0d4Z-I`XXo34Qat!fQPl?m{^Y@@;I-^F(be);dhYopU}<$ z0jr3G4{f=v-8?^M{NQZYZBY)AP;vM4M91bjTEbw1Du*uMZ zu1=aZGy|dJh+}1bIQ0yEiv0F2deQqC)Q1@yrB3&tN!$RU4P{tW&Pq3U0*L4cU8nl9 zr3<(Ga&2(TQFzN)4>J_0!E;d`&y#bX8nJNdl$f{s%znYCK<^DM$f3RMn#S7 z)Fu{{C{vPRfQZ3Y&p1AKsJxW<|JFWdo`BVU{ePb=ne*6ZKh|Eaz4qE`okv~5|H8D|p}3(l zHj8WXB>P2QNT)-!;*5##PoG3@aQ~fs)J8?dE{>J+zl@hHNCoq@XyTi=(4 zDJ{G8wKWrz3&ywam4_^{^tK6Rgqfg3ax&nG;I?_-o_tn^M8(*ocU6!!Yuex0JzYca zryYgsf<4%w_e}dkE;qC%5iLO=YH`6ihq2J31F*JN)3EmG>(?RFN|~dE$E3%9{J`#@ z(rx9B*^8GM`g!O*H!iC<@40lmmv*?ilq!479vq8rf`39I}J(Xg*cz91(Lm*PgCE}2$& zDobbikw4rh)zDs=fkm_UVxGLQK3*)B{HKf1#b8Ub5>d(M3ENlIFq)G1xi%ScZRCXr zT}1w$^6p+SN~g!Y2a8xcPh{21Uj? zD|Q5L89rb|6VImgyr47x5h+f1$WC;$^R=)8h!|9=PS2*mf}{h`A6 zMx(#J*1{5^Jxz(sRbT3dqa!Rd8c|GPhxv1Mzl_WE0TPA+u7$m2uQ_R}Q}hB=E`CAi zP{H)m6bv?Z3w+f0<2@{#Nl*-?Q)yU;C3qJ5ISkX>3MWXmxrW2pSI`di2NuSE1E;Ow z8*lzHg*y7wlKdYkEUEGV!%Aat2?8WM8PRRjlp5FgfSwxde4m41vJF>R!e(yRcaZ+e zootAQeS7RX*&Yo0OzTeGSBWdeTwk|(!Ycuww!G$2g2?n~8Sfj(dG3p4&!Z>h( zcnPnCK{g;-BhSs{Zla|*87IrajHIz`Aj(5p^9AQgJ&y{t;q1^{YTc^OUkIEmRh&l^ zu|odn)<})zvJb|rwaX7%-Y|Kuh>TH;%N+lnnzsv40#!Wq5^zXJ3k-1^E%UjGxMoCL zvw?9os!X&@FVFG?xu{pDDGzwZ1c&q+L`u)>uV9qG*j4-p-TqgmS;g zJkE_i7a3L|3$Ebt4abrF(U;}GXJpI=`iM6OlkIFsqg89 z{Eez>E^^kIukl3InrEf>3+t`adh;txjB)Ena%OXxe41WwUR#9!_c$5n5Z0T4Q}!f! z1)tZqo0rghe6=;|HM08bd7Uf+dj%KAcc+({V`RP{v0_l^*wwYqUR~eCb|j-MKF;!b zd}_g2BP=Sl=)6uXI(uo&$|O3O+C{I)UUm*kBG%@%c)qc8Ap^@@e4d)t@aF-KYctoM zqh3<$PYb2XTSL#u6gq-EL(d7c)!Ii9kwhu+VmdFPIYD@qh*^r$vU3$_$%2#;HfhgK z((&9R30rjDbff^f5$_}M#~%qr&sUDFinMDiwlH6jHWZ&h?6>g>g=mttf&)IPaFr^| z=apPY?vDm&my>KAVRd@Q3BWyE_v3BBU&)D5poKe$OiLI0@+dOj_bpWcjTBoKVYOtX z+4;nHWC3%e2R%K=m9`?!;ylR@yy`-Dm31-SXIii3tEQzh13AfNeYGw5RjP1Tvao!` zpsuD*(vE7R1i@+z-FhSs5!Huu51V=DJFJBuGtz~sXxd=JRO^@P>DfLHUZSMtL+=JT`gj7s@N zaEWiE23`mk=l}Ae%X(4C4|iLvyVcg60G#$)_~E0S{IvX(y-gr)4DCI3Lc{n+#o2Ot zQDV9`F}?V@L=D`oL^i%=$?VRl>Z`g6(Tll;hS+263cvVQR`Ucyh&hgi$Df)`ugP_ zY6vTN3#(lwKQ-==p*IdjM)h>tUyr4-7V5M=XI($5N5F&JpCvVnF5sJGs$sOEZps<+ zRgXj*PPVESLEV9_B=!w8MDBs6$i&F-1SUhCqe!eT3JqK@FN*1h^irV0Ob=TxE<2;v zdlV9Rd8rV@Ev3S{aA1je*29Lq;a0TVB4ex@<+w}gPLa6N82KhujC@sn*eT89`N$1n zr}0n3DQp~A{J=zGwF)Vdm^d)h&Mv16kvV!*nt;V)wPD^lfUXRy``C|leEHNb5pB@^Q+Gg}Qwt6`sXRvNVr zBjz?LeFA!N5=5N~weh)0zOp(Yz2t4BaTC%g;KO`G^m)~rO6kc)ltW&tfsK0gOAJg7 ztN8e`q=uD)fIC%hF#s)+y1$w=N*IKcs_BzC$|+~2o4@_bz%|``!kXLp2ev1GtAbVGRSW20uWL%FCoWbZMCR{J&p5kj9r zpfb-nuUVPN*=nyA_Mv*Ja0V!jWqb9Fkx^^AjKM{riX^pw+=GQkt~oA^oJ%i^!ESx7 zrLV%(XpGWMC|iV}F<7YMnhfgTFug?`$3iiSG+wfx_~pqW3AK_MBkWsEcM>%zB9R$v zqT*xQ&LWUXY5QLi2ure>6HFv63k%Ai6Jx4i^sHp(n9{A8wDNcGS#4%0NR8SGBGz=p3VDFQ67_mBexjWy`oQeOCP-(eQQ=sd?1+q29O`jPimm3f;HBY zusYo+`H&lI8bK`eRqs%+O00{N;%KR$R5Vv962j>xDM?eBENBZYOFcv@5v8d4Wb5_g})(0 z!)i3SxU;2*;=9Pv8lni!@l41*MG45N@^F_XM4@n3CvF z+26ydMD5Cc$Tv$;-z+}nHGme)LRPh>GjmGIn6I=&^Uz$zyUe$m*T~*hekLT&#E7U| za*BxtU7{H-rWrl)1?M`wZBtBNAA0lr(2?_lC85Iw)7Oit zMj8ujSnP#hzlyI-t#>1{@)kRTmw+ub1OUOXCN{sDD)k+k-X%_1IKqeJ;tWtjR`Mg2 z`hM*vZG|sFh9%NfPoPRa$|BE=dGAUsEJ53OO0Rn8o9!+uQyd2lm9-E1g8quw_ddiZ z^)Y5PeFxo-iH8pT)I#TTk?yP7|F!IGUS3%CHr(vB<~(3eN4=82;o%(uwy@IRPi%1e z&GrA<;P!2lC(Gd0*HYdZn6YitZVL?VM5X3#Dg))Nn>+K`oyu_YV3xscYUBh4x4H&8 zs|;>h*ohHQZ515EWQO5wFTuBC?{S@p9{|a=zbPA}J%6_SZ78~{H`XQ=x7&ASTiibS zp4x{9{8q(>4;?u#_)W+d4@R-BEdXf^HzrOTH($Vgj-Zlxf=>Eymwt-OEASL4$Gn&5 zHKqFX;_uslJx+G}o&^7mseL5hpS%XK@9O9mc_5Et?s@&dCBm>|#NV@x%Om6tETqm)AJpF(HK!WJaqJYfGmqDxa5cu#T*aPE4jmg(XACU2Xl0@dbT9S(6uhH^POH zr|icap^f5ka9Qx<#d*O^F$u^<1P+iQmw})TDSq&8TI`D>qvbehCI5;teM6EA!?r5g zt|I57?6HgQ5yRs}TpKfrWi91C2@E~-ieRV~#y62!KaP2^%A2+=#+)*}Bl;%&5A?Rs z3wexpOyA%~0;a{Tz&W9#=V}j&=}eTZY_1OC*b{alTaKVthCOEmlfcX-r75P`8oJ38 zp2(gf(^e_c@@zh{WT*PRlFyjhA;Ek9T9CBnBrQ2gR`&lu?X=BG@0m5sVk4(vHGg7P zBK{Trfps$f@Efq&7{&az$^3I+IIaDI&^np_QAq@+UrZ?& zqVWFnm4YEC1(&Hoj>GyJ`wD$B|LZp=vJjsIJ|qde%1emjY}@b_5P%~+bkPw%eO_eP zSsr|dMp*V>i5Fq?JBz?P#)KSu@L85Uc!IJApXH8>vFyRai;WpcMq>2;a_U%7N_ea6qOymPMhPZII6e=(SXgX!FHah-7w4Bw{=|AA z9i)C)vC7)qRvwIzyN&DKiaiO*rQTXh3>!FtQ@IB^yv}7Nim_HJy(DzdrG+=s zSz>Q!&2MmlW`gpgHE)$``J&2Y{vyPdLYq8DIce3PdWhn{lqV26a+&R~Wm}VHN~f*& z$1y0gfo=P1rH#qruSMtFNmQ9yT(MDm_^eZ?r;_7_#iU@Bz*%m?gCU_<%=IPuzOuvm zi%Ek$+`(%G|DpCr?Mg;BC-kYsfdBxKlw9JthEhSu0S}X>oEs?!uI2dR%74+p&bcTR+S;^%rE*WSM zmtuwCQt+yYOY^iX7F(_qn(D82MCmmiFZdjY#|U#NXqUZT2c}6g6U__9gDGsApQ2xl zAE>?e@n>`_pfESNHJ0MQNuCV5yp7LQ8I#I0_-=~bViL7@S(+244C=`xi_LbKQz=TP z?zU=8GI6?4V?9YOiFz`Akqcs=o2*{J{PtK~NZI<6)$@6kRmQUZ*JFbayw;YF%o~^` z*ew}>eUmUanDsv4FE}m@DFI?NKy}t&y;gM`m{HZyZ?R)gdEaD@O00{FHJZ$Uf>*b+ zu-t0t^EmQIVZ}_bEY@VSP5chb3w#yq69mZJ2H6af*-l_d>=K5cFzgWyrN%~b&Rm}4 z;_9A+rZwM%=BHB2n|Hh<9upvqn?`IjsWB5{6`Cj|a|#RsC37y^GWK6w!vp)GErfSx zP|*aTqR}~2R0f@DWvl?SGHX$yg;%g+LS0^}Kh7#kE0r%w+1l1h=PNx@4E@qE5!kRi zhj#jWwQZuWRYuaNFX=*Vzl0NnseR(`Hl8wSsS_j_CRJNI zCTS7W(4F+$iT%va;R!`*Zk^V4APg%NZ!M$k>@0~x4ndE>J!!A-o%v1^0G@_`GP z)xxj*x2ib8hKJcaL>-F~3X>9?SN^(K7s4N;Hvij__?%2#|`g=UmMmX|A~6bgBU_xbU(8S%%acuJc|x*j~z z{5oy#hz+&Xw%Fke>N>D1_A?=D2G3=;{f5i0;m^VuU(}K=-p3w?+3<9~_48xOi52ic zzWcuTmfVAFW)ilz259rn*cGz)9_70hbf{v`lUdK)*@De5cCNe_?Bc#eTV7cV4V)Ky zm1mg>u|o|PZ(ccMWWLCOVj3wC*x^icgu^z*1P=;#Mdw+Yr(KBS6?>`YqaKiDy?9-uZ3>r3LVEikvL1gy2kR$>(IOYE`ejDQnJ>r)%R%g+PK(CHric8 z#&LIQ%`<>Gb-ACGSk2a2>+Eq=)a!7p1WO!Pj+`$_ zA(Hg&I*TmvTJxopE<6~!SZcf1_4J<}>>A5KA)qC(?%3!6B@gT_du7vy&R5z#>~UB0 zI|sH!9_T^5r!+`(*Jq>3hdT_E zK43->32;LcVIuFO%kSqpTnZzSI0cv~Q^eA@SC{s#aTR*%ISvqh0FtKGeSw+MA$X80m6_w5GwMG`t$CGZf+(gkDbKCQ_4=lN` zW{KbL_uAwEe!l_)oTLWB%z~#!q>FujXLoTi&dufKKRVMBh&pg4C@6*Y>2rp~V% z*NFvqNjWH^igJ;;syLG+n_rV#uizw0+8-qT9copJL#r6CWD@MkWd;P^JT>`||EOGw zQ+c=CS*s%6i?V3f{T46eD_WMi)FB%4yR9W6s*D z)A?3Bfj&fozD>GIlo0m0SxqnxJ^-Ntc9sHN88QFqyCq(rm)jD#1rT3%@~2e(e8Wlf zg-hNRLUfbC%cb-+7Z&l394qZ`k@u@wc8TA;dEXK1 z%)kP9Xf(@5#^2OO{x_U_p(sOrq5C`a+709vGpsL~)fZ2uzi4Iw9HD$9QjF3TQ8ntS zl&(tYsd~-)cd@7bO^O-Kz4cQ14?Qe&= z2uT|{n9xbT75+J&oEVThc&h+8sW@vg*cl?&O4MQ%l`ifahJUACy;O%3{oC|=J<-8z zk5g!{H_|MyFce6QzbTN`ZPAwOcQ;hg&6-HFjG@L{N5D3EsqDO=#+8J(PXt$IFZx1_ zo$B$1+{ays;F+PuZR+LDoR^8bzzl7b%ISRDr6PtXOn59w}B({`)s9IVy!bT_RVep4S{5yb3vAtM## z!T6gjP1w&|w6<9HZ?T&SE{HEu-$(sh1e}R87726g5CGxR#fDw%pZu|obRRZfWDp2B z|4#dX-q_yZe^YQ!&4s_!`~xN8=bB$(LjhqV1iz}7Ka^mo4)ak}L0Y}6pH~Zm4L$EM z6#g0uWcti)9H(NIjq06JyY6q-t6Q16y0z?ACCgWDFlsx0Q1^!U`&8kUx;L}RHqm0b z{7$3RZ9XW4tGmn@vsg$}uQqB|ns@Q2!~cfhM%~&}f0`QKbOd@C)vZQtyIDmM^%@o^ z@$W(UDwO)7gmWheT zLy4K&S2j`RUql7_QQ3*3g1x*_$tbNC@>Z1EsVddwEYGA~4egx!osf8i31$U)(o)jd zuANlS@Y(~(r`#unH@`AIj@IAcHm>ogR7QGd*|r03RBR7iYFsSwPjte~y4pT;#e1ps zP-tz@m_SizfFtm6V)~~k-tlt$9SIW=@FuQEJyYRRGC-+XP0GV10;5b}xD;Wi1mU-M zYJO^Jk)fUL$i{?QFch( z#`(rfw;s7&N;c2p?}=IVHa1o)mOU$rWw-LY`4heQ0VS)Ou$V&iVd3Mb2*N`?XGqm+@oX=at2v~~*94f3`j-lK(QCKpbt|);A%|T^>UvQBK{2pi{Ms3(=Ts)w*DR@dGBC~6ntC6=nRdXF` z+(Jl>>-2st`U5R4{pJ>TWTaEC4o^d1EzQPlBe+Zfb0M&s^A{92NfxX%oACVDXP~02 zmm~cK82&}hwe)dfqPw`HLF79$cdDH-a0am*E=)e^z`Y@?f4$$h0-HcRLQU zfX3~1zouW_NR&>}x_l*q+PgrWSDi#fe`{&ga^@NAR*dZ@#t zZ*J@EsRuWZ?0WW_EHWIC7Fm82a;&c{=4y-gXfscvxr7tNW`ALuendqvDI})gJ>;ou z?{Jb<@T8@rBB34UW_;F&YtgJL6aAM^(<(sH%|a9MiATyPDn@q@;M8*`sM)lc%*sy=_@QD6&NS z8sv3LQSnfj^`m;-5;ezbM6{pDAH7XJ;(O6M+hQKcBiq#ULN}>oBLe-FW~roQ0YTsr zz{Kz`*4xUqR~*tpDrfyJy>6%TC~43*MoP?36=R6pcf)sujudJ`w(C1oFgXqec@2hE z-J<*FDHN8>;ktirqG0*#x!gp;&^PSYFEsw~NQ2l7V-YjRFS|a!GH}(`=SJ54xd21d~E~q9@l=z!; zA-m_9@X!xrhp_4myewwU+1?CZ;Sz9n=*9XhSJdBq`+QV-1V}N-&bUyrQJ;;F4$90SU2W)isyU9fV1Ckn& z$q;j4;|6U-utmSmQ?K89YRlNuTW;{iT^)moNy^&q*NogTyIq)<5|>wzuL`37j5V+F za$Uo%#hi?~CLP$%9!; zD-XUd5B^6mUdd-;feb>Ai2T$S+{|C|u|%G7b32pv*~|K#9@RUXuf$IYb>vkXAqiu} z5q(!+thW4=$Ss84yN{sL-P7J|IwV6l5~zrb&Z{^SxUA_x8NwDMTBsvm-ydIReLZYH zSx%2?3)VXKHyzqDZ5abPA_H0<|D7yk6r<4>5F=E@xB7_B;qnoe_))?TDrX;ouQX|o ze5-)2y`Z&R%x@+_W~1XYW>BFcn3Tds-M>%^U(Xxo{&K7UE2U<=ZbQ5-5$sMClVD#Y zqz!v1XVi6%8#}?={(DxSoQBXG6bcv2v&2fNF5O*@-CmA@&lSMM}uQ#%V3v__cQuux;SBhXK48I9_a zIXrzmxYs<6Dv}YlwD8@4Kn*fBfXC`gYU~=WoZBsV7g-=Q9QC)T^ksh59->J-riQKL zFH93%A?k1K{_9Ybp7uy>iymx>`a9Kq^*n?~)IVQ7Ge0PXQRGt{jK+9Y&>lurYWSB$ zrn=&IC3zCnU2rE-JnT=*$mLGms&C?;aS2Kcd|>}nsX^Hw5BF3RT zOVtx+E03*J!lO@UPMHMM6hs;ve4d(9A=@#$Vz7pPCF7O1QGdHYLf#SLiz!$baaGDW z;TJ8%HLe+rO9v=I*TO%O5}2SGmoiE9h6Z!hmY=FEUlV?5mRD3_e|O{t@^QxmskG(y zIz!#(hu#^~bolid)#^N4UfVl8C>Pp2sBmx7;XTu?(Uyv?+C5UZ+pj6 zxRE5gTjkC*8%E`D%q-M{?0gUjyCm``>vBTki;B_~>p6R)g9b>@f}*2WQ^wiG+6H2~ zFJEumQbxG>yimfW4LLZQ*-i}7!V<|PGJa4VzN{^OM?wd0kXX=~N82T)5)_15G z(Klb0H6Op?l~KJ7Rb)iH?$-S+dgENnb*>aph1&Aibz7H~e)aRpUmegm*W#h|dcRpC z^j&2C(HrLl-x*Nbt%WzzbM&|6r)kUgF)0Ou5f;K~io&^zac-c)n(8&nsJuz787 z_6T(kDkOW^o@qB}%g5$30YFyY-}dHdOhh4>PIv0;`$@=u@)rh)ce}=hr}i*GeG~Dk zMcWoNtEH?$yiQWViPIGgoN7i3p=E`b=G`n4`0rU&zp+<`o`F) zl%W!%dM=ozSMN0ZVn1*u2yYi&%}6mh=tmB`+Tl9&=ny(k+b-U353dp!^gcCmqbm~l zwyOe_s(r=~J}ui;aZsDHjJKG7uGE8DA}+EQFz5Ihj%8NN{NE}LXVh+s47(E73?sP3 zd8orB#*@|Yo>&(4lBl#(-%gd)?b@7H5E9M%k@ftB{7XH~Z4-^gZmht6`d?K~sBxzQ zMe0**dO0NiJD$OHuti4LWl)fkfyMd=OnB}xgZ6W=skFx~;kope;qI7d-jIHWDW`)0 zI6Gtg1yqf#Ms+)(lhpZqXgpp}{=(md%8lC9@bl`OdTn=yt7ihO!@m%gB}IC`wWB{Op2Hl-KH9wl5x$%R!7Jc@tqUliIxAzj1+TDXXh8shUJD5M_q z3{WX)u$v_h-ug}Y0W#HLZdMQ2@oJ|Uvss}YJ6;HzNG-LsP{z^l(GTgz03sJJlR{m| zLTbUxr|RiNPbn~{F|Arix#Y)-xR%v`S)~lhZu%pAi{}wdXP-IFYJWS`j7MiO-~63j z4fcV()}z}}?FsAEcbMm^Z;aY*Rgvkn#w3-l5-c+li)4@_VL8HG6C3q}m*g{3J+|y~ zDSP80qjs@*^{lid&@u|y@o(Pion@S9#W+KJKI}6ljX7>jZdW9CDvaswis`Q4S>(y+ zaEis&(rNY1P%;lYn+Sb!$S}tn$7sad?6qnNEDybhS|0R7WdGtIBV?}?`|wWuY>C`3 z)J^b}j^Nh#eZ&=OzIg#Lcn)$FNTeaQpE~cruaT8}6jl!NN#ZS94^EB`pn})bO|WlM z2>3Ht9M!7S28xeFIBvUH+@H?`|DAs2w{D&D|QJ2 zT;ba)sn`73uhN9XF&>BcgPaEl`XtFuQLobC)3SGav5&8kb`ZSgs5=D%y~-B;zf`w^ zfB$X&6O6R%&%N1v>xY9!Uz{TP8+W>mv7U;TgO`xK$++KLc5uL8*EC;ru+X(5I#_x| zu7@Ih?>O_S4N|=;woe}2@2+@x`VB^*zSS7#(!0u#MzZG8`}xW~aK?;kmp?h(%q z2}CS04~y8YRC_z@P`h{`$F@B+=C2W3^h{1d5I5WvyaLT#!tr=~Ucu$&^M400*AWKj!%)#&F5~&b8y=$ew)D2Iq+-2iIN|0j#~aw+~`YCe_)PlUeY^| zGX?a`qHtCd2~HZuyz)ED*Qdk5R;nbI#Ezx?d(1) z2pS3%>L^wL9{VoYg-5Q)ZoG#Y#IIVxgM!jU@-wK84vb%xrucb) ztaPY8?9$w7M_nqs)OD$#N9cLU^IeQk+=a`u8d{(?+0)#?HbpZ#P&Pzr z?>74RsN2gzXbO8+Ma{3u;WwNjCb*-lv9O5uHNGYL7m+F1_N(WMt__?axElB<5lk-; zx5nQN4fHtz7v%`4_*u#tTK;PJ3s!T%9}&!av`fFm9cmw?%r(}wU=3}W4?Zc(F-2@# z@R7@KOK_u}>`c_S%p*8mXKcVE_d^!*d6vlSO74ePjsZzYd*azXFS!n%lx_R!!Sk%0 zXUV{FB2R#KlS|i}81OIgTtq~){F7*bOg`QZ@}uACj*jr$+=?dFC^}e}t4IF4ls95- z|1-Z4WmRXhGTJ-}GTZPh3X*#Cm|XdrRAv6Tl;kd~yny`e3)Q<$*=w-1DQvZoC#8Z& z^%7mJ$0I1;&B7BSXn#tVT+CP4UjOG;oR4m2>v@SIaG6oJRN_BjIu}FExWrdLBDGUR ze0b#~e2ke7U-ck6r6~Tb=4fV>&Hu>HMJ*k7Yz)>Xd0gI#fA|wzNYb=l@q3Ku%^=D9 z7?MSj*^~4goTatK7briW?;Q` zn_kU+RF@uXk4$juhic5dQv}YiWQ>x^PTO>ZY;TWKM~8oLk4Qvj%lOK~#0;0Y6<0Q< zgvG`z9)8lbxJRIuRlvMyA!n#CB20A~vpnYA_n@zlj(z`UP%Ul(uGu1y|R2) z`HO`IwB{YOfb)RudClTIDD-92brC=8vtLNjEpm@G*nBwyF}IHp0CmSMV35Zm9ETog{V(54XefH!c1GL8}(L`VT@(p<@=1V!Qtz;Hzz!2Ibr*` zVeUFqh&N~K3T`K{jND1ky4&DgjErI7dZLA=^NOd+*}Qg(3hJvY?08O(bZN~)r6P8Y zqC*r};?0V8t~?uDWZdTs-AB~;+HJOKg!Msy6Q0fcJ&B%L_@4lju500sWd3RFQ+_ic zr60|`?;x!f=F(9PJJUEJrM@_C9cp?($Yj43(AnBX!)pkd_E`Lq`q z?k#^+osVaQAMcwMe#&1~jJlcWCk%fVc2=Wyi&3|2XhmmWxY0M%>Oy?gZ9{i_1zYsy zH}vh;l!D!Xoft}R8|jvvV1HMH!0YX5hoV5NLa{T#t-F{`T7$K{Sl#hiOaxKxip_1CWB9HL&&E}u2}{&AlKZgg?|VL ztSQf)gP8(c%yw(_UeTehmCvwx80zlzMb0*4Pg5|xKDqu_G7JpUqsl7yTq|Uz-|B)a z^a5rK3Jd=nYmPlTu?_;png>~L3+Y|M%BACBVSLWmRkDh4akfV>A3&n`qJ8`BPh4w3b{YQnECovueq8X3hgzR8=PC%cwFDFzGK8 zQ5~!?|G;`!)F>?emramMaT3Zn!JjO@N@h~lXG?CfOW8_|${$h5vZto}Rg|&oANjcw z_K4UpUDdl$4vUqHclnfnNBxnmCDmEcA(f0D<||b~9t~1Sd2~vOsqCNVeErC`N(6ee z>{5P9_{t~3o%>?!dn+He1pEWOet4XqVfBY!R1MwZRDB`H?>f`G}eq1wp#uej29=hxY3>4r8oc>da6EDN){ zhjK@yfISjkJMqM8q@ojz4Wl>N`9Cxv}J53PEKgB&YW2&XRIVs(%Kx;#}_^} zCEuo65U_@3c^$#x&@7K^G)q+FOn$f4d|^q3QjET9``h+4YJEVe*bZ{mThK%Vp5J3s zi;Him3>If#$rN8?)Ryq$HEPun2%}aWO2%+EOKklOO1q{Wu5|FQ#HcOe)+e=1Byjx% z^YOicEk*jCOg`M7&P6(|{ij1)d7P7Dyg}T_})*+Do%$Q{juCT(Qxg zJ^o-HI#3iXKqSigxP)%?HYU3Cy=d8|kF{3x1!81S_>_f~87t!Z;8toyEJ!2v90_{r zqvm3URYJYJ+uq1e#;ahz_f(@I)aDMYIf(SzW8Cm*yxc@Xa=f8|>!9qxp|uBtAL)DX zLwV$8J}hGoLApV1jNa?0(G$NY#~wrC>r5)6ePxLGF&{cQ@Qgb9{bEMCD zBH9C-ii7$AJsv2Fox)Q)zdH8aEl&-03*uwQwFi9*EiJFhX9k0Z4z0 zAWEMrt%zvh7kDea9on3wQVzS%Vt&(|RsM}et)uK<*&1z*EbVkUKWx2AdX;QS63oVb z0z_Qq;1Obc(!z3>j@UDn0ZI#hk6TP5q$eQI2{sN#yg;s$ZHZ$DH`T4RZqv3UE@5QgL1WA^WFj6hxk`#{}Z}M{%7I08ViFSC=5v*`DKKaV$+d-Rc=P00$F#s0RC=e z2I|dU@P@zGLu<_2djR})7cQx%snZ!Zcl5}M4tL2>srU^7TrgN2)j&j@gop|b!-sPU zdjPf4$xtJXImMyId`o^;X)B`0N0cK|Df>SlZpREF)!)zny^rR7V5=PKq6Wm&& zin)!;jk_R(6d*AGf}#RUZO&y}2_@k7%ucU^EmY!kPtg_DIgjXjux;yg?)Zg(%?H@x zauS9M!QHBbOx?)})bbE;%it8SL9u@S4T@bl*G94Wd4Ht0&+<>mO8zD1!-Bt?zLqc; zA2xbpx(7QtM*gy+`2)(#iapcrHk?j#!o(W=MRQAw>RNQDn>rlPp-O4QC)xQi3fK;P zihH6t=~)p6m?esQ9C#1*%nx9JL&(I>lD}dv&(~Hw;BzuXu9m9&dVQ8tpXzGyB0{Xr zzdOO4L2@0sQ%XH_B=!K%u{)gP_u~=HoYog>$s&3Zac$CH!5d<|zNU;_`h%gvmD6UD z4(d7SXTA1eEB{^?qHhZAF3|jY%l1X4mqa`-%NTO_4^dFMHSaBZG;h6E>)70)dCv=V zR8nKTc65!_v9pEW^}+4@?R4^YYTE}~;-)#ZvsRw5VX#p@oUU_TemShaqAhrUt@HKe zpU~%F6gm~Mqk;K*N?^&^dpm^tM#)b7#uIFC37`haSgWPmT$w}+zc ziVyUT85c6%uNs#cB(|032P!_+hI|%EID;#i-w#X_ADY4zcl_^+(`&G~16eya;R83c zQ^27U&>v2(*9eE zuOHJjTIFy#)Y(E;iU_-(tDtb zm=z;jGW?}^YKIDT!#nI1dXSAdcVVYq5dW^4OSU~kMo=xe^jwISJ=z?_(Vc3BV0E#O zJx5m8Z2J`zDIUnCA|AY`qHE@b(GeD6jZlb1mU$S3F|mkLmfz-Z1H4xGs8z(LyNrT(t)cM_ zP+J?PBV@UvP&vJ_wj}61e@xB1=%F{+d#2I>=?MBe+2pkcpNBP!@aP{nx0UUx*ryHI zhZ}9M7o?5TsH4Rh?==A_r_WLF<)gmN{S|R-=xc0dAPMviCFpEHF&;s|93?6En2psm z?Y^WCH>mO$$^PQg_fAYqbtU>j6n&Ea%w>yYiO7CL z_HiNd;|kWY6xa7sw#eM@wv=P1S(4}S-^!1!`und|efy^mud?rd_Tg_ITlMSWvy1I} z-yIh|wCbuyH(zGo|KN!qKf0=HW!`G*KJ=cf2ur~!F%GyZ#h*sFECbRx&0JMv)D$Z# zlS_Grg3n-voEJ{2zKZqLm>(TE3WfUCuB@VlRj2a#aqE8o*PDN<{`txJ!_;u%`j`B> z_4iBGzbvPva0~2>qD*om_=lBVy5w21k@!wVbSABy%#Nzys8mGx} z#r5x+xt^Q-V<2^*RQULRro!J!g%?v-eAIuSdt0QoKGY_Q9FOAm&|g-*d!|X{xIeW) zs34hBUS?2qU!faIQR*0$%yHlKRkV;`&NVp{g(3-_EQ5pZ8a&^b7a5`2S*mVWuiyd9MZ`{A2T#3%alO8C_yd=9f2_iE2mK0r6lQx7@VWoP__2 zoi$Oa3;bE{=W9W|!d#e2QQJ@o?W^dVHRee6B|&IhMf>t4?JrfSX`fQ!#ny-WNoE7A z?qY-b13f@oxJjY>9HukONtMP~@zWa=szQWGRCYf#=yai-2BPB=^^b| z`K-i+C_A6kg~nYnAb&>w4~21Lr?I+95R;RMu#fUkXbjy)xTKB8o@q|ZU=K8N z@eq2jNVYy^fU5+L}X&zMn~_U*u^Wx1go`^CR11;{vP~P`8?k|cC zRhGmQ!g_q^eBVXZ`S9me{DRttOizXt4xVErT$B9e zfpaDEd1^zlo_yaHzRtfR8(&ia=CXs!fegv~YyXYn3tu>1fVq+s#`aEO?1Uu7s(jxO zqn|GuVP$nuWFzb`>Atm8d@#KEv+}L&WX&6Y7oWBQF@F?ghp3f9sy4Hv_7Y>Rnm(gH zPK!o^@Ce~=;<_aM9N}Kfprp8MUgTnAvXbk|$GN$(#jC>>>lXjUw$39W>l*jS2975; zA+$duA$0vbu`Wa?AF}g7*SR8hd#rrW=ZFtjshtnHE~vQlnCAEO2TM7<%qJ=}djYB0 zO&kwVa2(l#y?`R=!LrffjES2%$)8DT!?41HJ4mR3ewD|Jym__s+se!!>5a*dt;)&% zgerHK_fn4i7^x;*GF|p%7PPADM!Rgov1OB~lauxTGWpHp_BZ`b{7v%P^dY~NKPM<-+bBMWE+QA!>h6Fqi%tN9Ig8gwee;9lv_U8$J%5cN85cgVG4Fm0c*aJ zCBK<0>9b49X}#l?bS6t4ri7aR6P0`g&kzNnF1zHUiriP3cD!ud1lP_=#?kRRME#b?J2@CvUal$ zncJD{0|9^>n4*aDcIp9U;oBLE7{6x8;7b8J3@acG!&Tx~d_CI+gj<6>PeoP*#6&R= zF}6<1VP8=3VcEXXP95#lPM=Vsk1lN)-lxqhuGc4A+%o*qSclBfi8!Emv*&QYe5v>g zaX{gJXHoii{7))L@xQ5A{O=t)l;(f89FPBro|?t~IA_tP0D)CyKEesBQ2PT)r7^x` zhk89*$hF_%egJ&aWgOqe8OX9a7+E^u1U#jBh zK?cB9tQh_IaiFtEaJx!$mA;fKRq^q7In37D`blB)GTMOx(iy>5N+VB9v%6!dbJ(l$ z&?>Gph(S-Zm|^>(!x1hpmXYFbUnzg8*up&Z8u3PV;wGP|*4?tUW3VUBHf$y93%w^> zo@Dv-j%3}$S145c6-v)?s!sc}jj)V%nGwMPm-D>@qk|UYYt@HJ+rh^e&90z?7#OyZ z_yNORiQ%}Kdv95r4nJjnWr~blx+MB)x?MKcH?oLHw|)7sK6`=l^;suwDP=+wJCgaw zdy?(g!&mDsqm0dv|H59ZZyXaZNo#Ae-b(u=Y#M`8<7K%-KL+X4eMa@ zQ04$@ey9&q}LeZQC-^Ky1bjPjN{ityAi zqQYNwf1d{s|DJ?Hd-SVP%L3uy0~XBXZ}8NJ z1cY&zyMu5D^@5dv(JaplysL^@F!$0@3kN`2lu(|1`JgKwn|N&Y7`coEHf-@r$hGh)b?E%sz&&6`B@t6iUJa!-B(jMn+-$RmPdC z6A%9=x&JQm;3Z^PQXbsnip;R};ZLNJzP3JGjT=5zi!r*YRUC=`SH5~C1)2w za%jy@@hY}}Kg?>g5CdA98k{|_Ip2WM$~)<}s8-o;3+=Z(?6=33kRW&R{abYL7}YGR zrDyGutBzCh^S=z)Up9S#FLP_kz0V$BnO*aR|ACrwTl%v1***Iqs;9tJG;RGATRY8y z=iPRRNt8I=`)d0=I@odFUu?g>=mhUiw%_;U{cM)#md^Tws3xi(!u`!h2BE9kw}HJv zF``vfiGns~1Nvla1Ley<5jV9PsSk=xV`uiFZ7oQP4r%$m%aI*gX5a3|;$fW`YvQ z$qrL_e@jEoNv# z2c^f4-kGfpiN0Wz8}|X~w9=5JFnrDq<4%B`QV&EBm_Mw~PZ`c^8J(k#Y}H@<0@e3t z+jZ4EqOoLG-}l(+bM>7sQU8Wx>hHSanC@>_dC$=wwXXVek5}%u_6r5)2z*)PVs72+ zk&g9Z*0x@$5nJ+F_EE)GR@}q^jN_^%{!Bl)#icS{I${rVtGB60XVZ(@Vm*jT_ChNm z$vi&r*(q7M9PvA@lBjIczd_J4;+64AZ}SbJtf#4IH^rMsK;Wcw-b%QlBYtID?6juc z1x+vPQ6=T0RH6f4+=vU4|i^4Bn=z z^Mf^#loA8BE;|TU8~1P`E#*R%^2IyGY?37+iIdvI=&X0!#Zq4pwM1IvUT zd(zqPQ)g2F;&I^jjKC}th?($vMnLqrKveL1Mga09fk<3QW4PP6$Af3uwiy=~96H_Y zG{TnXaLUFaKX68lN_Pm%?ie&@5Aw-{5~n*MhEb?f@E8ZznTK~p#0z{yd6{E z#&@+2m%Zef<&b8o&mI8IbQYUUVF>y_`3Df=wy{Gn%=~VAk!|>5#36kN* zsduv&fzzgzHATJ}^LIO~t>X-Qmnix^jENFaztpG@ppd!(pu{S+&p4k2j59V47mEwO zlMNEfJCL^B3Lqu3k^pgA0C~;szo!X+j1+)C1ww%T2k>wi)M10;`^SJo`Da+9ZQhG0 z%qDI|9D1)eof22g5(O_qoJ%txhHR5wDzJ&OE0fJ%QJ8nvm{W*2j3-V8yP&=_{BQnZ z@Gn!f^tNhA(@QD3C*(@!#dD)C1dYE|>*o=)pciwa2)Ya*K)-8`&AfZ}r#VQ4{LfjQ zeGWkbESpuDyAe*G3r`vUmxAZ{JC1>;guk^ssT7O0ax_Su25jSFg$h%iDHZ`(@bj{} zk;dN>U~j`MpNqZURdx~~=l`47o1B06>M^rl^4$OH*-x%ti{K5V`pd*uyg_S`)03du zaiY`C;f*p-j7vqQMY|5)#bYDr(xYX&reA^T6m=giff~URL5m*SMOAX82|`$A*ZfYN8Ii2)>cp zeG+>{)4)p|fv@{l`3D^molA>Y*(>__6TMgBveKJOIkt+$gBnLAdK(WQi`@80q643p+w=lv$$? z?x|mj-(tnu>8HtLR;q{^_>jkB6I&t5yJc&YH8b!ntz`)hkAo_Y@c0;1Ara-71~uR!xZ~+Wx1SQKf2U3kvT9WYk5d#EXhuZ~SNeCt5CzWOebLb|R1OB>%_aKgg zfuaX&8_nBw726@=;cQdV-cTPJ_2&~eU z=S6OGhFkC3Gq8KwD+AvT7FVnfEQ$=tY2J8)pp9kBRYulp zavtc7-S!b;^AhzcJz=SM{;Q+|65z^HBDldRUK;_0KJ`w}xCj@4ij6Y{LaC=kdS7#3 zlXEZ8z#^V&jLDC?VHje7ejHVWqYIBm#y{>EdtHs$GEouBXnm%A6k;F5QEXmMFBFGM z(Z83Vn2_9&iQE7M1G&k?ZcLpQq>R*Shp^Zv1d6J_rng#PkE~ zk?VweC}8D!g)z}>jBy>1_|1vHOWKP5vlAENA{UI37jrft#H(P#p3wOwq4Q&b z3WDZ8Ptg2)&g(zVdHu5E#+#wVxzM&KFuf{(BKO6iPSoEgk>GVcz^g+tz|8$0<|aWC1!l9e5Uz3;{BqcMi4W- zpkD1`%4V##VqzZtf<@@G#)&0i7JAsqQ~`rsG$zqqxbRYuW2hf^v!mk4{O6D&JqXIF0E69 zE{K_wM+o*f^0Hp{5bKoF9PIo|u4hU{P)+&!12|e7+)3A<)*ta%@3Kw>hfr(2Hl$6E zTg}jC)&w2O3(VD4yubuCr~G9wcJ&UNO{{F7vV(}Jet-Nonf&em1pZlqVTL!W!H)O! zlt#t!?@-ecrGVTJb+vf{&{om;^bfS}WmXFSBd%K4OzKuz^)nVH5 z3HhNn3kZB0>TucdR@W77mI^%)glWu%tiyFCQuDe=aj!hI5@yDSoP8NGdN zZ}cF9&GMicHSuNN8C1BnaKFAQekyMY_vu^XmmB?j61#V6GDPeqjIa@c@ui~mzMG8J zyH29@*2BbUG)YZ>UrfsqTh)B$8DSg}h>~ts>hBVY()ogU8?kTFnvx~3CWP+egwV$0 zc+auF5IAE`VzeuGOzyCxu(RcnG1xAc|z^gX)9JQ_&Z)T1AJ8Bz9oL z0FkdNIl`GaZTV*NbwUPN&+HW4R}bQ}!-MQuUSd4xrGal4x6r^bW}0KHVxKb>IRIvX zLjASQSM(6S!2;tH^J3!nX7yl$pv-8GF~MsNX6Fj~lN2pvS$|7x5AU*ftKql$+pug- zRb=U-dqf{4u9ZKPcq|!TjE%S|FF+txS&{kT`^07vm;{M4c+&WO!!fQ0uF$KyJJd}w zQIfd=YaGB2KPfQegj6?mvxV(VW;U0^494&x0uO=Z!^J>CVvtkJc|<~pRBtgGe#Gk; zgys73fJLch?t~9UhkMl&Rsj%+%k(DJoD6l?Iu51cntRci1!!*3pZcuMBS`C(r{8)H zZ~EcX(6C_>8w=aWSy*HKeY_HinITnD@l(6aay|`Zs9tLRlJG9hg15s5yz7n&@B9pS z-})Z#zV=_kdj!Yw1m5#g@UF{(_ittQq?a6|z29qIM6d_eM~BU}AM$kie;CZluL(5z zwE#c44RI`(pQp#fay>qnuODZFIWr$tx4B6jK1{2&3cjNLApMMdi$Z#ty$AuPV3tI- zr(og6{T}>FRh-?MeoDU*E_9Maeh`1$Q-Wt2*Luw&L~8tGxpm9!N!%u;7u$ZJ)A4mJ zNrw)={7Tq>Y55$K&NXjO=fra97b-ba5o#~vS{I5?%ugqkLX^ap{#p3q6@2;E^m*yK zUyMG_WQRfU<(U+|WYgzNEOo~4J~n-dG1I?AC{@nz`#0D$>m4oj&FXe}wi@bv3FbhA zW^)}$FaG1y`*rD28m+91T*so-Jq(QSf5%5F=h$PYH?@8e(!ZCFkD=etAtiOLLF(iz z|1B`c!9$LD!5Qth(h<)}{gN$x&JEPl$x5-dt--^YCoC0;r z+ho2sck1nlzW?8%q$yYQ_u21>-w0@NjkrQxC#uU}`E!Zb$h7ycj9PIZ&@2+$;~|qfx%`@B*(tY~c;`K;Q9q>1)eBn${d9 ze?ldZ0<@vgrsuhNc}UY|+`K}N`{rL#22*h{P4(u$X<#r^q;l9aeMX>r5I@m>eefaa zz!`?;$(0`rmb|}tz_%oyT{Kj6IS}qk>`rU6ge@LZJ8^<}YL!Axp~|yF(fqbllGmT! z5;{g6lu`2QVh@~PzCFS&8!W1j_4O%Yvf`iE{)^(v*hxAgIb3rTi83%r7xly|XLh^H z6Sz^gcHVgatcrhE@-Oombd;U-RQ|Za2G?WBOmSZ*f1Isf+@8h1O5k7mi>MdGji(Ea z+<4){d4KN?PG0(-keJf(bJFn1VX*q^i7PFp45ajLz@;PAE6p=G&q+W?(gGO4R*4~e z1*6idJ0rE7k?MI-|3VY%NYu{}R`de@Qt1Inl_Wz>?L57DF*--2b|InS7oJhc`z2D$ z{0WYgp~ej=S5jy8+WW-owe@^@ter>;|5f_4R`M+j6+??~(^K1 zhS}MuCU4(Lqi(g3k1z#NYDCDrQ#}`=Y*pk#U{;yvCH#eaI>}L_*UlwrS){rvQu{>I zKhGkc#Zt^1|7Rf|E8lGU7bTw;`1E+>^JnQ#l&G-eTNd)^f_w^2Og=w3K{fYFH4+y; zMLwSs`S{LMCE4UNmgg4vTf+Mm{D5Kdzkw39x02H6YYaQaM}vqA`mOb35bgd=-pT}Is&>O3=5 zr`}kS&fMws_Y4#}; z`XN7n(*D!y*$1-dr&7_+A_4~z52gIo+kXl8!C6ztNv%v|^QE>)_lFIqagE#ACb{~C zyU8A+`{&2cPEY5&9QkATN%ZG|_=iScOQX5RUpg zCFqu3*An#;{YEHg!&F471aS59Y)k8?Vcd`-VW;DFl>|N^5j#;`Muy%%7tC`fg5XQ* zbv0`9+GF_V8Y0jXjd85rzeMs{?yaY4Xm07dOj~0YIp3RTXHxzo2bbbRM4;g7p?Y%s zah&5A#6Z#1n$J?)pv&A)D7>J{W{mKH0YWs@t#l$1kzmaD z8qQ|qFooPq85vu(5)tiqGS$!XT$y^ZqIcm3Vw_~Ha*FPMOb6AW*e0;F zct9rhs7$52+G6p5Z4m)Jh_=(>0SbrAkADvj2(}6vs3nq0k_UWnWs(Qn2@gmFaxG=K zJRta((fCBB@a!!-Knc%$;Q!RD>ZvMu}#k+>w>)^&E%$iVE3tR0=oY%mH^Ce; zPa2o?QdMYAUaw)kQ8>Ud^%8oi+Joh}s#BN&&lNYo{{5Kn0b-f)%xA~myIugveW~IC zV&m=R&a2n}wqNn$;J#Rx0X?YUdc3fWV;BPq92PVsN!B18onS^jRXvo2QI)vXh3C>e zSxSu2Rs?+2tX;05(*7rwoh>K8@ToFP;_9^R_7-l`mao?q9d)<;(_MH7H-v$O5+=tD zi0G_KWTN))Pv|m3snp+)tpuJ|DNIj;MuCAzs-Zvf-mB1kJOIEIje%A1tXQGE4FJ>m zJ8)hijy~WkVFBh#D~<>H*=VmdFQ(n*1m6k{IRaWrud~DzQ|HdsAKCuF2|rB(f;$|b zPSL@uHXUr{mxWYKeo&%_jQ@9|h!7Kwz_TA&+9z$Z{Dw~BscwoFxjMvhIKI^)97PP@ z8X)9?%uDKvv$Et}jo618#hw_c+j6_{Eg5?%Wwx%eDwe;1z%vREbP~+WKQDf!;!nBw zU%ezUSkfCKW+ILAMZowwMd}~}(E2;0{%u_ks#{k{LlOI50Di<8dst+S-0tvHDePZN;jHQ6Lvc z0-{`%C{*!&#zEzxBmvC)@4NRob4$S1ul@h$_mRvw`|Ru5Yp=ET+H0=`sljR_`I$P6 zkk~@bTcN$LZ9g^j5>cL1{l1sIFyh1fst;nFuw{2#J8Zc7k-Bgy8v7bVvF^Jpk+ zMf&apcXShBQPUSZ#O8UMlItDnmBd>Aio6ay?=${dfG5D!u#O{(=;Qb!J-~};)*5_y z8SAHLo6J6y*=OO8m2d?UW-uWRr`Z$AWbP3V_?7)zxs^m1Uq?MhBpaQJLm$HLS^~0hBEJ95DsY zhy-i@Ax_fX7 zyWLGgX~ASmxc`79Ow#{HU)ldg;r|1dB}{)ccYaN~$iK4K96A6QM+YEdorwQ6jRP8< z;;*Iw1M;15b*cjp`2QGd^Zyx6B^n08k;W7M!f28I7^5AW7;SN4w1|Zahc#od@-LzG z^+E7_N4f)$A&?~$?JZaeL0g*XvF1cepB8L{{g6u+2e=1uO5$X^NHv-Y=RB0D%yx>&nkHV?aQV3oy>W+c7$8wcf4jA;&Z za?GpPg8*ck@$`jUGGc{D`yL3KL-TkM>&K86x?C%?-C*RTaSFmFg&XjO=e*DcUvCy` z1Exkl!ygEX?v&+I$vLG}$sQQyNLyaTOMw!bdd=yHsY|eaMnXyMG^1XFTovT#ez$QG zY-qRuab{~PX+%^N+Ir^T7KX&eX1)692H1$+hKraVABFJ~ zdfIzb;Ru_rm$v>&iNabmfxz@%Lo`D$UHuMM$s6h-FAL+YiaQ4e&ylhmrv^*C&+`x0 zeV7$-Uv(AkXFQ4!L8zV**jSv`-;p@5pM)a#|3B9HKO=qp(*ckGUOM22%qH-@Mx&j9r(OwsaV02# zGXcOP1sF^U@PMA_0qBzB5diG|185Jy<}=nmyPaJBO3{W`ce(262iUy`IA5FQJx$GA z_UFO~0eK9uU_TV7l{8kk7ZC<4bHx%r32tgIk%&Vp*K`g5lxMnE@Nk*py^vkx__3yQ zu%-tATzQszgCf4xd*JS`J1z5pRM~%JrSA_rv`W{pZ%@bcBDIfKu9) z$a9=($m3BB_pyyTkVvVayFT8l|F#;RTDJ|{SW=JJLnFQ6#Z1V&@(qWDj!Su~72S0D z&%-QWKLAE|lH`xZUlg%Jz(W06UTCEidGt-TIui&B!yE-0aJVd&`O%TXa4anBSOdbN zwCM$9yHG3$fyT@FuQ7(gF|GD?iJ@)NUhj8!_Ij;OJ5UrYI^1KUzZ;?vI%mJ2r(I5G zgiZ&}92MVr@;>J5vrx~-lslMve~%lk)2(dmZWST!(muc17;b_FuQ0bcmqsgChq;vh zqB~EcQ&sWYka7uiDP|(3rX=h}{l=fIsY#)~;(!@6BKFHH+Y4s|G-v`bLbMrU#JQroV?j$vKf_=6Pc}gX#|XP*lJ5%vEv+Al zqZmD9p@N|ZY@Cb}=bxMap0xWl&Z;mSx}*#wbTI%3&IP{XNb(zSAScL#qjGl=jNb?( z5&VC=0OK+N#@}Oo59a#zW4G|nt?vgg^2GXnkXtFN?~z`3a2D%3`awu7^rUoF1ktgCZ|c#S2W69&3PTk*Po${EFC_Qp?zf zXAlU>$*ew%sVSU?C$xa40zBaXPd=0jJO*vgOX6HW1o4zQpFjwOaDEwy&?G{e_;6$j z&Q?~5js0svvH4gjKSVsJED~YR&Zh7nvk4qF;JBE_2DoJn-*;ES@txCPlh;5sF3v?J zT;hnVX7g81)*fMO->bARnRI*$7+(A(Hi|uLABB6^F{X0nNmQUUR5A?DZSdIsG@MBw zb0ys+V-t>eaih$Pe?`irC*Y47T#o%7Ivsh0fT%DEH%aWCXIUD+U&61yl9WHlqLdlHigUZ zOwgL3AG=h-os}3*{2He-qKwVwj=x0sT1q6TVanKyr<{x20V{a9Ay2&@qlPBYIv!^< zPnl;lOMz#Z2e-*ReNGougfB$jQcn;PCx>TZ2BUM(0OpQiFe4H6*y!~YKqtnpi?PU1 zq!jh3`od{yBtl0I&{S3~1!kAOCIDF83<%&DYpFJGndW~=dxUv->?L3xnYapYLW(@h zS|`9Y`yn1?Efa@XOaECwm1F9=7-i^uqytdlQEXNma)&u>U4SbE>K7fOvNKp2&$*|9 zUhr1Z@yta22=_m-N9pI>_aj|vl80Jqtn_#{tY#deijkrU+(?`w4NZcpr(jG1xwz!h zVFtAq%rm%35&LGrI?^+#Bp=%n8Db^%I>98)rad!e+UaGN3#~5g{EUYo5U|$)8{>`mpAhi>lOSNTc`V} zf#ia|P|iT4K>mh_6O)XdeKHsL*hj0&TW|+cFJF>M3NGo!^A zHo|a^V@OL#jKRN~F|fvfZe@>snBCW=t?>0p9YKTRb)2{F(pQtFSlxzs|3~-#tVQ%b z@)AK?f?&Pj@v_^HMJ0VFLiq~=hqKjR*V10?p}N94Umhda`X7$_Uo;*Zm~BHftPPZr zD`q|jQs8hmM0_z};Sw-FLjopRc@q$HY%TtPR`JDEzKXp-_1$>TQ2rvnhw&#o4~}y-3IL^%7fFb@@YVNcP%gqQ6V@UMv3#D|(6zE!FwrDhDZX!S-b0Drofe z@`w3-1Ha=aAALcr;@7HwhUH6|fckf!{%LO;<90HbD-IeE^&N{?UoF^>Aa#+)u1D60 zcy1D5iREHnWs_tApa8WHWCEre&E$LK%W(1`Io@(|yvW$4+G8A$<_8N4kkNsY5j)}Q z`K6oiL4QoJsqKL8Z8%O?0imu;vYqM%WQzzM4AN9A+hoyXn{8}UP_{^zeNFaK6TCMf zzL^bccblmT9S5@Jj=j=OgyczdAXU@!<1@-NndAHvR zGIsM#dA=swh#PL5c|1b)Ie^s_Y2Ai&tq|3mLTGv&{ONShGli=5O7t_GmmOre<5JZm zC#&d8QGKNeY6^Oo2EEWnux`c*Ob2=N4WwcYqmv+fq#OmFJAfPUBug~~qL!7)ADhPbkX5KF8F5WPYFd9pA?!zuOSV*!B(9T`mYBpXuN1RQxN# zzvuCf!`Nd!Z^Z8|{M(0rLNGS}$n(+#dvC%R)8yk>kdK8)YNjnJtY)&}eZ5=`47A;f zhPB2>_+iXKn4!Zw}eIMa65eWdW3cPXws=D^*3 z4xI4iIAtI3bvTYGD|A1Ow)z&EeRv3cf=Pblkr&x{qK7fF&Xi$aqjc(DN1tEakxhT( zQREpitwjyp4P?~I_r#VQdo9mF35ntZ)7J6~q-%}04Xi8$7FHNPK3#z6*!A>J)RVr} zy0IRv*@1+W*TJ7o5=e5nbtyQWgyE(jdeUAqie5!=35MPWN%fB1y(f@K4l^%pm>SX% z*Zig%d^a?Cb8fDx$^-qH1=>XlR^`K+6BpJ&x^Pqdzi!4bQf{gr$GXUsXf7QZZ5%zJ zktd!i5zFPE9!oAna4NDj2%Nme3S-iFasMW5vjhu_{@|e#SNg0- zM85NBYJbu^F1O?j}`!ooz1ipf}e@7xwUfS+wf}Ko! zhcGqU4Zdy1O~=H^=Yn-yt|>>KJVG;yP80M9K(f^gWI|p_LLL)TnZT7*!y?j1mj#Tk z`>}B1zDJFuS9^@zdq}TvVw$8^2oT{+e1FaQZqXBQ-j|Dgve0)zg>L!dq#9LK5OAshoWUor#h(LTg1YV$+q_`Dx|p zqoqO*RM zZBjq`V^cqSK^Sw;2fazr(Y^*HS#&qjaIP(Ki;f3l=y(@1N+M$_6xEf9Y3RuzZQyPZ4BvytbNEh2#GI znVwdKcm15EOg8;Dk|yxh+llnuD7DSEf>Bu7X5%#& z9+^5?Yj~m2;1p0Ox|1qKAcDIZapWqgsf`UW7NL~sZ8}jZ2IB{wn52~AVV**lcQ}rM z14yBt%tf@k3S*+lIeHmGP!*TTb!fCvRA!Hk$Fp&!`GLDL%eP}5o>TPULP36VhU>;> z(M2PiV~Rp>-6#~EApv8Y{HF>JcwY5E?Ax}Q%a%{LQ(7Fa+IiRD12uoYxVOi$K+Y(T z@oL&@eY|hM-T*)?qM19O`1hT8DapMWey=A|{qDegIP>Xun+-~O+fRXkzeDCLjsD%i zs$N452qpH)o*-P49*!6jDGy=vP1dBlJ+8r0Car^to;) zm-%7Zv0J_P*sbQ`lhvlyV0_Uh(W*GDzP8Li0gkk+kPiQ9LV`Sfk zCJYPHb_ncqjOTg{uJb8lSr_e%ZJ(f%L$nKlHHa{z`nzlGM*j{B9(6|T=&(@>>km=1 zUTZR*M0RBJy%T#6N3;@cDowPhV7){eZ)8qa{>+2lnUorhBBJNSlE%6ODB3{^tKzgf335;8~2h)pl^!aXM$giZK z$fdx(k|y6~&O^ddWSrB#MkD}|33HJVE?AtKwpnVp1AR)Gi>M(T?~`Edw?#V;{gC~| z6cz0fjJvDF7RL0qV}{U5j;0*k7aVg?fsM(+Wf(e#JPtjM{5^m-h}G%GyK4AVs6t_4 zMYF7xzY4L$HZ@#}M7%CDUw46aW5~Js0ty#gPsHs%;hE(CI{kNCvXNhjzfLb~=wT}m zY=Ds(_y3$-WONaR!Md+~vjq5tw*mWjixd-m_?HrZw-DnReQ)Yl92InHywhK=XA1+H zkuH6_BUqPX^#2fteW;$?N*EeISaBJHU*evXG&T>}Pm~TWki%GuosJ+B3n}jEzke8i zo5V97EVDG7G+A1opp!8dEHdyr1OlQO8bvYd7hiJ|{25DBB`ng$J3*b0gpOea3X@6ZB;3hsC(3zVBo3zQWAJ%BZ36>}xbcZ3B>S3%Qgq(Cbk zEMKe?H9QQZu&6e`a*=6K5v(0IkwG(ZMn;s40DT^C-=j_4sA#1d(UNmP-3koCxa-|` zTm-|5_QiCAR5&eLwwBfpO5PLR`y2?N@OMHGeyZ{hYWRS7T zeV3-JgtN<1QX?vX? zaQVr-PL#sCoIbdpYLELlfz+PU{%S({B$^X=30eYJCv-7^$1<7J4r7Ho_1T3m#434H zU9^yEe%K)CbJC$ZX?6qKfEsckI3cm&TS7^?1v0-~(QjR9HuMbWzAYc@2IY}e5!%Z@vbkj%|GTDqT z&Op{)h*?B?rXi3cO-DVCK#dpd$o7f5`NgGK@ECx32A~Qv5rkDklICVOYJh7A8w`T) z85For4WEkdCfrDyc@~Hc=QJ^-kWH*HZMf1n++i0>_D`q*OZF5rAN@Uf;A=3v5IrXk zyd1q1J#7@3snkd2=pke#9vYWlz_kH{OQGK(FVwZIVHkEdF$Tx*} zB8>64LhwNv;*Az#w6>W-9W{vey%>^yuze%65yV7L4*I;HVV^_$kSi9#n(^QYv0`0T zCfGm)n@&Q$TlFA3G{YhaaBy_vhHhOU0|8#b$O8#bjRj9iR3C> z=Ql3c(Q{4@b>*U8{tqS$_l1MP9VvIv8S%ZJ!i=)7VhI0Ad~bIEr}2#M{VZyzAu*8q z^NjkpeBb(Cyw9w^CA0o1z|g5+lEe^i*&jv!%N*zc48b&cB~DmeU@1jL(FoINzT#|? z7v|jNYffYoCs|lsV)pVPqYCiq5#}o{)n#;Y9h z3yq3DQI2*(EvTqr;r^DFc^s7iG!U47Xb06{Z|8516!ZaUp`hmB z6@eR35QSF2JykZez7G7$fe7lH({n`@uugc<);_3KJjR&-8g@!&YU|gYjp|}OS8rvH z&tuhwc6h-$BC$N(xxt13U0H;>*g_<;{427$$X~HM;jG?y9D)Wtn4S zu(9|G*vzspCXHu~P~AcOeY>$KaPI}KZoYp6@4djV`4AoCf{(kZ0qOe^>D!RGO~|zu ze4R)XUD1^bsEl&z&I|Mj7bwOLDK?i~fMdf8s76&Pk`a<*&(sk2c)@vavJ<=nPaH** zCSfp;e;dj%MsUy{4WPe(MEDT5QMsPrtHO8$Hn}el5*b~1VJUMT@)hW>Qh5WNW?H-o z+{qe;`-k1x&x5*ZOf`__^WdwGA}=j5UtpokDwiV-Rp#rjF2fVNgC(k5l35=uvsoWm zkEo9&GV7~Fnyil^#c}IIot#77TUo$d_`dA{pzIavA zZb3{dmV83SllVIbqRl9bRC+UXQmM6!Xo@{wWO6)G{4GDhz43>b$^pZY;JRf=u(a9c zcMJ_Sw)wx-wwAQ3i?^a+{$@2a1HX}i=GeIj-w#qnJ@- z4KF}0cBWKOLu1fbX|}qs48Np-?2O(YYW}Me;f2(Y8y}^snIpvW>yAbb^7rKYw*30p zb2Rp|>rLk-c61sUR4WLFt(ji367B$qR_AKuebS65O4gE_{4ZPg@8;kW_J*PUl36a z)_s}{n&C(ykFc-d8NE=(pBHR!0E^&neXov4!_DUnbC#^X>kM?*X;48%I5+|P@E`EP zDv=8}lY)M}4pa1dgd0o^+JUx-!1xN|ci&h7RDt~~m>SP@`ijMQh@xMZ-&WE%=WGxo zd)uUVl~Q5Ml$p^7dn>?LVE+a;;9wf$^uNg=Xm=`&-IdsnVNitTYY`8io~6%LfWk=TVPKB^O+ZAsdXf=Di`rqdNuI1ULO%$h*q?{a(^El+7X(q4 zy)L#Q72@DLk$uAie!B&Jqd4)~cR^G*5D~gs9I{P(PF#p}X@~Lcr08!^8zTtb|#Ozi9yyi;VY%%u+(|A(W!SqtViyaA}}yOBemeRI<=Ct9)jCSYpq zbqF^XyOen(b}xGaRsf0zmk&>kF|agNDL-;eh-6>?lJ!;vCtppmHBWon`0s-PAGGP# zwZjHn2HUg|NNpT4ks9ZZ52DpS zfxv~o*yP|Z-N6K|%4&)!hZ&A@5eWzu&AKbYsD z#C6861Oc$sFx{912xl6YP&|4?@OL=9cgI(D!Qs~k4j=cOtN%f8_!Mu0;4ck0k1^d} zAJlsp&+ZZYbB)E{S9ZbQ*9iU|2Y>&=GITDTe`eG-)vV9r?<-j%oqy_UYLSLxU(`o# zecbw-du0DC{=SkWGV2rkUDVf^zi0ICwfALx7Jpya1%FSmOBDPa6K<)$HbFRbfF8Ml zxGwDjV;=l)3;gQ|TqAZkrZ=Qv3*IeKE`Ei0>j>QKviVQfHll_hs6pVXko}c8P9UqV zJ1at~-`8tYY^~L(TyHt(ZlRDzrg2%3NkjohaFqhkLhhModR(y|Wf*T{2pU*XjGpa6 zDJN@q8Z}_6b9fT=^RBCn`pYO_fc`-OJ)*!r2goQrE&Y<&_T@KAbps1)jrP}}!*7UH zf`2HFtr|JZW;ar$1K(+}nJjMf|FdA*y<)J05p3H(A#fyH4GWXwsUTBB^xIS8isFP8 z)KAFE3%SpxQ%c6d2Dxzv#-1Ycj3PczEW`N4Br;`9WW|zkw6GP;1kD(uM7H^ zK{haLHMA07pa>Be=FqkaX0N2~Vc=LLs)l~WOhE_K`fF_Xliyg<3caFsz+SH|)bVY> zVJ54>GFJP>{)W2n0i+_9%pi3!eRDuuRp5c!GYMC+pV%w-oUG2i?^2f3MBV;}AUil< zcs~b4h@cJ%v-c3+z@v0{9vI{4p-ob>1KK!y%&EJ{_6BLglod*K7We2>3~P0jul*HB z*jL+H!>Z+5E_o@E4d($9MyPU-7L|#MzcLdo`7lTSJqNo&;fDAt^oF{5tTg>cSl;L( zr9QnfSKD)&awVJ@yk%|#DE>W%+&H}70;$P)n}WT9s{(RS?j*5mNXr>U&IL#896qRk z8YGY-cRGqTh|_JT!)Sk}uAI=@Hnb3tl|Tu$U8p?r05oJJulqlSuVj>0LU{G?qcBP{I++J-Dnh~tEcO(>_CGci^nSfLJ zdYu3ZhAS1Q7p`$22p9>VekG5-ocI6S-8j^p73j#S_IbL)-3u%*c*vD!vs02MZw8>? z^`N`@;4AnBdg#=OVL<`;d7^8`MEXA%3;zdS!w=a+1PT%U4{$sW=;UanwC#h9RW%fk z)s8sa#FQ87^9q{Ei#f7%uSmO*g1vKWK&rG^hP3s_QNFX#C|Jjf9^J}zk}#z+MmBrC+*L%priwV+l{=LX$I)) z;2<~e<8TU7_ih*8}Cb%||ThCRQw9e%Q~$kotvE~0i=ysrQqO*d!tvMrjk7DB!qt`r?Y z)s|98ECcfD*dRfF64vc5{SvBeutgqW=UG@?7IUGe?qEkZalc$^Q-{5|)zIpL9}Wl} z9^m)j!<@AwIna&Ln^cZvFC2Rzp}#{Vd0Ja!MizQqu-pztm+L>5HqWGM6<%%o))-7- z^VATvBXEq=M}63dmUhR zA>cyA>h&l)2G12naFwup9;4@IP1LVphr7#n9k67S_HNT~p?zr@E`%7v*no~-tPNmY z+(?}QgvXs47+K>%+i&E+ai+9@UIQaOQANGhXgrT%BHO1}3bCEW6R95`mLFNA8eY!~ zE02j(y^84YSlG-Ev)I?z=@OBaAnX@DjkGrgYqKI2)~hQa3^CM3gnI-xLGgmC^_iA8E<)jxhpcmKI9JpWWq}xJ2<=Nz~*dG4CZCc zz=;bH`i;zk^`lK`_8y1XI|A{kp<6M}34E0H?+nx^=eOid{}`eps?~>k^%vMFt+jRM zz^0PTzNyEk@l+Hy>18K-diH^pp&vm=dBKk{C)B5tJZbyn{Rd%B|GYiih~)*l-&5H4 zBytTwd5K&lg{|st9_V;wu za#^U=U82@%=rN;5fF8z;-BVEO78DbxwMU}Xh}4fikssN0HM~}!R%EzCVpoM}fjA>fz z_n~+DKx;`^Pq@z$+l+-$Ai7rE78!33w&S+da&B+fTd%S_3y$>o$Zf4xNfKPdXRZrw z;ErRo;{SR%%w+c`N;y$V+{%)roN~P;@;hbzx|X^!&LQ`EzCd z8xr|F>G?ew`8_g!Q6hgqdj5ip`~@<61DAiJbSQ=e#$O^In*}tENjSHZ1XaO$!^df=~m4zQ`iD3uP9eQa!y0QxdK4 zMHYdFNwWx*_~}KMk|@F#S>(P%k*7KpVM?M1Uu2Oni6YN-D#DaR5x&SGjQ1jDHt=Gn zB1}mX;fpNtDfpQz@>-`NOi2{si!8!JYFT7mry@*A6yb|35=a!;)Tszl5=HnTi;PVa z+0m&8QxZk^B8$MGrrELGor*9eQG_qD$Y*$xWBIsK5vC-H@I@9O>|~L>qKFz^Vx^Gs z2AG;C#upaz-x<446r#SbtSyB)bH5I0e&1J`U>GYS2d*0V`pP@eRC!e*Aw@^6mOUK{=73O4 z33wgrHTjX?f8c##r5$oTxY9e|O7A1sjCFr^yW#sbOJ4KIGo^*lwpJnTr(f~moMrYo~>F~o9|yg#AHf^Q}| zO6G~QQ=gv`85)PJ&?0CW>8Cy7&f!K0K9li-u#e_7oe2=659e>RJSPK!#m04S3k||} zZGH~LR6t+~g-)ol@_4OoR=R5BO4N^Hxc0@HCfqepml2rX1D7w&CHBL;xllYZHTyX(oub?6v$RQie9Wy$L{XJb7Bp1juM z(Kf12)#cQE?kLUjU86p=(SeW=@PpW+84w+WPQYjmJ8gU%fMJzpUdK5ZO`9qfUUFc- zltH|eV1`gxVQ(z+{JCj0O}$zv$cG57>h}+<;BdBXC2G`z*fs z>9D0jE+e=rXHcwyS|B6*bEy6WFz6hQN}_(?o$<|)^wuUOT2sT>$e`_ywn9QT<{i!w z%8=Q3X+&V^=&g8=VqP5Ao-zHiF#Q0Pn_aWF4u^P0<;iuZ16P0{-tF>zqO~O@G^y{| zW_Zks_lSz2Pua=lPBc^h2&o>hcku#F^bx#pIdygU2KNEp5oinTNga$>gmdS)Z*^9~ zyojZk=4*Hz5{%1snmGaUxdKDbj;U!W21tYw1wRz(=VHD+lz5S^(9*OFGBquw%xv6= zh7+0=so|IBofBY8f~Z5IT{^-TUh70{E`69I(4N&e%#~F=hMN6{D7fA0sT7MrX!mp3 zu+Z%PlHaZ#q`@wIrl(-Y)gEZ|ugEiO|A1Ei>OuPOJbE<-6@*ql7YYEk)a<(wn*HI( zgAmZ}rs1N_bj?2fi&3+0#yj{A>GOBgcGlFztB&wmw1oFXYzpU=pS->uJI zGKtt~>hnK(#?t3kTKfD71lme*DATn*zcNjqUkZJG2N8h!{BOjAsn35JT}sjC7gLR? z?l6~&UMltZpeItFe>ZaD)?wx@yFUO#ZTCopMB!|_>2vb*`v&pY-qh#wJR{*wM(Xo% zY?07+-ywX;MCnr|!s3=snZIskSE$b)Pus6SYG^P%lUjBMwY+5wvUb+zQvx)JF}Y>q zP`f?)Dw^-2=j3L|o|oY5cs)g#3y{{*OX(f<^Ez1e@; z3VSS{49Ekva4wKA5Aswl$(C5@P*$<$+^P>nYzQ7pN=3bX2I;m%$iJXEBgBcY#SvBs zwb@(C1ts@COI`ivLRa5L`AyRAUB*pcOUCf6rLX^*`ucH5HNEYKDGxP)s)Kf?>g(?{ z_4RIG6!i6{XBK%^lKZZTd~L~76~@=w1^l3|e-~Cs#IAIG{W>$1DomlpSMZRcmiG{! zFW+k7bIJ#WAI|RLgV5Lawmt}bz3F?$A#f4;`jl!uCVP2*i&_7_N0IMBI~nXn*wZ_@ zh)CieX`M3p#|P#Qw)wi@aO>n=e3p-<8ynAvl(ZDMBf`9Jd|1vX&n5RCm2d`z)C zF5n!zqyxSdU|wQ!Q)lMsxGPBy;jJQ#pKe-3ei~d?EY1tDE}RgjT%=c~*p&w$k$x2r zvJjML4MCM_TDTs!DRvEFW~1%(={%oXwkP>yfNjkjv^4SDg#?KHp5ZCc1|{I6-DB*grHmImQ3wUF@?E^MJN$@t6NKE?mbSb1uB50Hr8`=jC) zT}F(EJ`49-Z+U#9in(c_4KT<5c!Kvf@zl7`AriQ1Y}z95J1Y?rP|xr%*_|)nT^P{FaBUHe$+74ulv-RJG?e^Ar+31H36h|!T-I;2rP8q zE3bzlMAf1Y9?58ikQm(_Jx_)R)k{Rt-TMvgz5pL-Q4EY)8R{BK9c=tmtlPhm!Pm2^ux& z?Ul!)x3ByQ^mY<^*(C`&7QGc6j8=jM1?S^xexLYV88`Fw`5q`L^7QGsmVcKF``W3{ zVS(Pv&l9CHu1@On3sL$^efl8W!x#_%kjQV+?2&WL@&%&&pmaTxKEG6!FFt1Z-7m=Y z!GY2H;`H*9q76Wp$b(#&#_T0xLak{J;gAiwl;FBjQp)JV+pr^Ev=kZjH4o!&lX%8M z2KU*G6mlO#M-A{R`Dc?8mmMtrBLi5`iY79H_E}dX;@3?i{~4CTf9lD9&bTUr|CsTs zvN^Dn_;q7Jr`_MhaVK!Gbcq3_3647$DiU{s>s}=o5#!kXh@5B(Fq{X1W7+) zJ)mzC_%~dxRocTZg*Gepd2)@yf(B!LUTbgNdZ_L)|2};=>1RgGni1O>T=M`v@XFXq zl9hDC(=D6)vrU`)q&-_E{)_g5$Fs*z^_y$nKNC3HU|bIC7U3JokxoKGt3kXVKsZy( zc(8S$NI`WKHVk<(*q?N!=>U1hUJtVVm$2d@Neok}7X;ZJy_;JM$Ow=l=uF~RPEKe} z`5hs`R$w(|%GZ*gARZG<#}@&Rf~U;F)PuaQ!#i9=rtp-rNS!Bni{g4ut+{o}^2^jP zx2+U5Xct8{l1eN;Ih{)q_A-jz(;gccT%To?n}~AMMgIdB@)32>0n}mczqX@s@<}yZ zD=5FoTYx{fi{EB)6s^g>Ul%+j+~)5Sd64*EI3FM#Ax7x^Teo1nvS${f!!N3{*#Um~~LM-6Pz-Ss7J z`A_x^SrkCp>>UGN&;A3y#+pms_uT_6fWqzN!)7bkW&TD>F$PWSH>3Ar?eB|^gL&|l zY}5_*ryNXSf3G4@us>M#fc2%__ar`FkX%n=5E?A@c?x?KK_W0)r_r1NwcPSeB&?&;cfoW z+L1`v<*=Q3KQo)_6O>t_dp*V)r@3*sn2*jX}DOB{6_psS)XDQ(uk2~ zMa7g#fW7|!+ICu>aEq*F3^Djg2QGwRNH*<){~W)ViJj&DOMbBzm+OH4|02JTR}ZKm z_zwA<<`$OxJ{iC~ru;tZ*z$W-n*2T+E8;uk_gohE{_=YkT8OrwvqFBSnK}vB)t9Fb zFgh$T<#!U%PVzg?z>@O&lxG0r?~vdB8+6^0-`yhK@?00Nj&?<9`jQm+eFVurkh-%< z-eoFG!t5sQL8YtYPh+K|e&s_e$qo)OC z`R_|l_dYG#KL$O8mJ#y1;H>rQI^w1%|1us^S&Z;enh@omS^b@&JTDe~j5((ypPNdM zEO|Z`8MNz8XPnQCy)fMDAI{|EYI8F!+!4w1|M zE^M4L9Zf>xuUmV9^UXa$-_+SJ14d-X=VSgyj{lz#_Hnqfg2D9z{m6 zd0>fjUVUtNfBf!a$orA+miJqJ4O_>;C7pplh#EPeLfNPtJ*u;_at-QBJGD`uOloqglpO%^Sao} zg9UFY7rG^Qk&5g79O18Io-6fkqoCIr{#jo8qrjheLR;g{)YkYtsOC(4UT#ufl2QI% zv%JvP_%ro2{z6$kMPHLqzSJx)=)FHvU*j*8@Ks1dwVA6Kd;g|#1rv#|Nz~_soAC%_4n0rfhIw9nnoa+i0$PHMB}e@I zVwVw~f}Wh97{wz%Pa3kYO*%(wNueix+r;$MsC^@{`QL#Us%CrxY1Lm4ib~;Mhv@iS z^LKQa82_slJrM@%=`HG(%O3dG=}b>D;Q6dspG8l)@YmDHe@*n4I7`GHMTvQqP$wV= z>{;c2S^=-H!sTa{3Hwk8#K5GrKVU2e-(7nDOp5znY-1AR0bfWf4!x{fG4=#cu==81 zk(u3!d1DFm)s*+)BvdS-<+!VHH>DRWppUy)K<4_{j74~QYWZ%mIQI$k&GudEZ}Sp2 z=7!gHWBBJx`DfnO*;||z{43xoN2IMC!Ff&@Beyq7Ai=olNj|6{_B1lgq1QNF+>%G8 zwP+L9Y2}*`z9R_=(O+DKD@P~$5U=4Z^GDj)W_@Iy8Sm>hI3kIR-3>Vo+hcX**xiA4 zP>D+BEpK7m!tcm=8FFeZ`m`4PzGRgZlOpB2E8;s-s}uoe#%^idUh<9_&O<5Oi$u@W z#kFi}TFaG)>m-V4Z>ATdx>I{!$fH<-gY>bRv;(Ol#8F1Sp@j|N_V2+9m6v$?ceMZW z)-AA+3-_h)GI5?ZQ6M%yUp$>#RQ-|%-TJ4+Fg>I-BhRk!Si{V4i zhU+;Sb)5yY*X;+@h4B22+sNWF_Pl0waXVhKCpxmnZkhylg?Wj(9`|RDp&vRVliRr{6mB=9}2%TW+`& zU9Pl&J{bF+Czitw!n*X%tj2B6nW@QOcE;Jbo0}e*5rGBwp_4YFm;3~H+G>bG#^Gm0 zUXj2VGNLI79KX>b66AT86|a$lLU1I9el zRehuqzj#u-B1b3!8=&;$-9?Y%_6&-PL%Mt{02TY*` zni=QeyU6G>Gd|0V|AiOe8{le-os!Bfyd=0N&-l0Ev+)4LXH8uD8_IdQ-1aJmuZG9t zt*N_!@*D-`&tVEo#|H5*WbX>Z=dv1}5?dfP;j1-v31QrCN`dbs7(f!f+H^=V+Bj$9 z2mp2t0NYJ{@Q26&HLZX5zK(G@KE!d}tzXoF*@(=gJbym+ueZhF%d_y~a_oHHUIDyY zF~GkUaZu@DKfDiMGJf$K0QM=NmoTjHHkB{u$kQ#?BV(7FY9b$6)yFPR*Ak`Ku3wK? zMMyBl72bkqZ{4jH;oCbNdqDR;t9R2KG(SK^J@lb@O+)ke%&C6`l}E;|5Hg8^{kYx@ zSnul>oa?grPL9vYxCx+llhB1pLiq~(8M_K52VMn#MmYiDav<;9UN^4F+H8wY5G$@O z_(3+#c(k#b)G&`LfsF9FhK2nkdk|bVMQj5T>k%O)TYP);Lil2k{iP{9y&Hg{+Ml;C z8kZA*DUvEIlp;8xul4o8!8`2~*TYV+|KB>|vDcE29oAId+!bV-O~?v6(Xq|xknMdX zR=25s8@oGA{U$Vy*PzDOLqKSvnQtW|sS_EEipjZ^vxaAzO{zoU9z3Z!G_i^21yg{@~4C;Q8_o*Hsf#TXPOz`l)qtcInS6JEHZIf zWbCG6G_VZRE3M`&sWnd!H5-3@gAJgP$ix+>hg~yXSb#gq7BAgAIymnx7ah<G2&mi)R~qrbr3sqTxDZ(o0t z`tq~quc|LcYGnA)1s(A{-<+Vmt-icLeffbNOut2&nR^sbk8(S-hB+?<58rrKzWVaU zx@gv|8$>11KDY0`DgS`_@;3G5hfk6vCMX@j+74}F@NiExNL!*nePQ(kB|a)j;Y@cF z9g4i>3E**cA^vEuXTV=;u8S!(lkv%;miAPKy*0vCA8c2(cdFrc^}hI=_hVk&l~Kp` zWF1Sitth|hVw4Yjpa$Ae!}<}n=HOvfd$)R|sNo;00{EK0@6rYMsk&8{U>5GkCr!RB zt8(;`u5o6nWXJbdo^KbDX;1s)e`2JyqATsyn14<=H`^Bs@&mVT8>+g!9kpXSuIpXgcBWq*fW=Q}i5uP?d*C?bv;;VlCA zjt#vFv5D~GX01zDEBsucV6e@ByAe5X2z5%lYA{&|qd*rON&=hsHP~IGhFZ}55eTMt zAW9C9ngNalvV$KT&41l@QAvY3yamUJ1*ugm5#@>JBj9r2Kx9@M56w#sqUVmg)YvpL z0gM_6w|;KNf2q}bZF*_HqOY`$1x@jej*iy12e!601@NE$EZx^nDf&`tUC=1L>>Sw9 z+9hvsJG~lNx#JS-%7yGjc=&~{O91u zW8(j(@be+OVhEkE0U!GLx(oQlu1Eq8kwB+8&nx!K&LimNoJOoez@%aiZmi{17|P>> z8{9Yvll&BX489B)7j203Ab!GGT)9(IgQf@1R{$ysZrTh-ESeypmcN#^;NB^EiNP*S zrBFY^;F^!5X|t#K*n?mjfbm?HU}j-c6ev zC33|Yd@BvMpQ<%j@lOowX`>=0vD!?%F= z(zoU*e(7hF$uFVW6?XqcGKNbvf-4V7W?apTF5~o+j82&m_T_N=VO^;M3d7Tz@nkn_ z=D8Qri)lLS&<+^h6VXu`dCBFWkAqHYs;37!cur{7D%LN0qn2$Eaf51u&8`aLFM{wX z&)cypI8j$TMH`v`P@AoKilQ~g?ni4Hu1st1sq}L zs(~Xj5S$DN+B<=|EnqjZ&*anNf%em?P6#wS6YIm*wRly7HzHf0;UfgGg-M=ym9bJ-JcS#!FBSfxI+z9)zj&58x7pMn zRi)8hNJ2bzcsRiW(2tIK+~y4a4T?@&aN!ItvA;9j{Ho!p{DN4SqTJ6?%SD^<2~fkSeIIyI2COcYwE!)@u`mcs5)OcGj`2sRiA`8+K#o*EEQqsZ0SwrW0~mQ~m?mhb z#nTI>^^#^2BZ#4Dh@nm_EPeGPbA^jCwfP8O>^og+uQ3sRyw%nUsM$*a z7OW=YT1e^G!I!SEM+qsbm%e^ACI#r1TB@7OBG+W2SFuG8phbYNbgb{ZMA?&T3{nrp z_(vaWbj#3I(Kcf#PA0LkA4+8HSJNa{2~a9qi5g=<8i#w{5**Mr9jlmEoiK=5K>Qt@hF@>iCr%ABLjf> zKg7Q;!AUazMKaQFMm<@;^UQo*y)*BRkypyYdAgnVQ#4F{$ctKGIt;gf5P2P1T}e|F zS7M-a@C?5{GSZG{a`GVbUhzq237!m!5WrQjso=;7*E!wZJN*(3COM_p*`I~I= z zJQj{g?bed-Y!y3NW6LRo)$<_d+bKjrHZl3%O1##Pg-7%Gfs!d)*fP$dQyvqm^j|Oj&nIYJpLy1?q zSjdXR_~%cib1XWlU2?Up zJ?eZ#av~~>TmTf+aA^MPWf%L_+`di}uCNN@TNx4dD%|9<+VS`9V8GfnW<(UQNuLjF3U^#gQHi%JH&9*$VCQOgF4{ak&hspq|(Y=17o zFE*(wU$<{YZ>#Q)IY-Ejb4+kwh)^a&?J#PhLjh^K%`};1_Q%vMTc3`}! zvZb_#x|r&rp`lt7^Gzt(3JvedTJ9z(E~z8;r^Bn{P2XU#v!xKyW6mWtJz2Q*LlmCX z9s73sma5%EB5obz>WB?5szqONApoONvJ(5gs{XOFtT%MOC)FK(WhguTrhVV+?j>6= zwRfG=I2@G#S%~t|To9*hCziZjJT@>2wL@N=V@0fO~V}`>XUT*8l8rzd4&mZ)z}w;LzA{e`@F)4gR^3> zYEf-HcYWt0m)1-Bg`(Z=(5`lh$Q4qb@5L%YbfnFYp}mzhyx`o}a6ceMppVCHNNWbi z$3Al)+4o3?KAy2H>9Ps>*Bre&K6;P|N-(XN`hW0`di({j;R@3O9g7{8{{#GR|0U|m z)=+%zsl%Z9Y-f@x%qpWd0?v@n)s^?ygP#rves*~v{{CH;sw;2Hs{5>4@RQ4Xd>e@G zn>`%gvxA>@uluB1-KQrZSC6lHw94(V@9JQ^BCGV5L}}Yo_i49aeO8YSt0n)L(Qh%f zx=)?K`s^OxXkVEf2z`cBvwzx~Lrdzldtdab;VVfdB@rxG&`!cZsb}pdJghklf10r2 z2*}?&a@vTJrMW!-LZQ&$tHbMxzQEhyh!O$RY*HVHeg7&h2QCa-K0GGZYwyy7`mnlS z+iB|XMk$HSK3RJ=P`@Q`7^`D6stz=K4t)hB@r|%hTtvhMTn8br#PIcKVvxGfgIvV> zqHU2Wc7AI;NS=&!%Agd(sfBl_3k6F>w=c3mC60ahSMf`2j<4eC!cRaR7f=xcJ%bwB zi8od@1ie>7)JE`4+pI-P40TaET2J7wJ|R@9O$d2Ml{C*eXu>miEjj~LhH@W@Ikd6| zyy_yVkfZmZrqtzJ29VD56t5;gUL{dFJ!zSd9Fz&;eW*QpB){33wI z;vM*1!@dC0nh|X9#wO(B?2bd78#|OkWYQIlie2;{P7#i9Gn9%rnNYvVjwiRt? zh0>VYSulvk)`MDu{osNlR3Xj&5m?%LYu_4r#*PI15KObq)B0Kd+grc1zg_p`2@8%) z21VBn&iORfjr#}asG+0WR*q#BQC>n9Y{AB}u`Ri!^-vJFqyP!?0Bwnp>dS5R&7j3X22Ljif-UDIJ!Q|t$TCyxz5{qR1W@oIb7(#cn`Ziu7c1-LzKoN z5EXWT`zfG@s7VbjL@BV#bA@ z21GjbdArse{S8X-7AM10O^Yrn1W>*(Z@0$IN)m`AX_ee(RpNhDhaTT3Y46uk^#f`L zaDQ-|EBF?CAN$_0Hr`KsD+YijYht?Wa=)5S+0iT`jBg2P`)A~v`wcrfkw4#U_Q^Cr zllYOFqVM7HnW^uguE&Sb>F0v+cJq+tDbv-mo?4;xo8Xg9yD?=Qc5D22n~@ubd&Rs&ga;D7+Y4$uV9UNv|&W&v@oOS3@~PQx=!>41_$K=dF@S6vb) z9WujTj17(11v zf{k{(p)BZwc&xhE>U(m}db#MgLe8Rw>o6c>DcY5?Y2{I)RZI=>oKxG^!!QppOqy}H zT`W^s32lA>wYB(>5!&NJ-Jl+e02o0%)bO?V#49wWJcrDJ#K?Tpt$h9;mb7Xu{R}n8 zvwd_y(mSb;f8C-G_sOO~Ay&O$Hz{)x_R>B9lx3)6$WWo;%P#j_Mit)~)|iq?g<`8J z|4ZnS(f4ej=6i-2bHcm)tKi;t8GGXNctQJt4K(q^SEwt+spH8OWs0?#RLft5&ro1e zr|{E+JOM!ofmVl%wp5On2V-6K=cz9WkX^77t*zwSs&lbfIy+E$!3_T(G(icnT&r|++8=ZG zM`~C}R=nJ5R_WeA8;dy)#EYN`+vP;NIND=&R zF!>)FMVqD?+R{KbaD%)a+v9iDhZ-C)e{aG2%H`hpLG%?~T(KSIW>}7u)gam2GFLig zzbCiEMYXYaO*mLT5*~#@spq>2d#X2}=lOt4#cmX~im4Nt4m#>D)AmEPQlgJ_7wyX5 z->;!+DDs-zRY6{p0r`73l>;`kz{9yXwqn6`g@Pg>_c^lWw-2%A;+PR^KMhmVVyo&G zth<3HA{x$9CRZ3IOqRk_U91+y(us0^Ksl?Gr=^jW)&f-^+(EY3gyq)kxv2;bd33$D z6E;M&)w=pBV}2EU$<^IjyARt5&bz%0-gRGY&al9;%(e3Ot)U5*`f_ba#~KbueK};K zRaQS*u_!R)D!`wzf^L!WqqBblyJOR!OM5%5!mT&u!w3xjCr!G#!K5qL-V+=7y29!) zO7~GfhJ=mS7=@oPf5I3l;!_-gBBWYFHSQz)Tv}c958x@*T=Lh{=Z}JI6HH>fHhsKf z$argUd4b`l>Eo%VOQ`d&_{LhFIq=Xs%@D_tz4h)RsLzj`RCFkIqS(S{2ZTz0JXHGP z(QGtDpFavJmLA-j*k(}QxeO10u~WZV(T6)qw!lyWDbP(J5xa1=U8{14P5FEJbxtbv zhc7rH6!+7ixEB`;d|(wTeV+fr-kZQjRh^ChGszGpFmeYSG-{MsMu%YyY3xlDYTXvpnaV=XuU^p65Bw862OpCbDakcD)>w$gldzU)xaH zzr_p`>)S-lhYL*6g0kkS11}37$?}BQ>Ig@&1ptp5KT6}V7As_5sSY;_m5hZn2iBpewSzPegzo)a5#TRA?f6}jb+R?1Z?Z}Nfll~&4Kp%eM3 za)rx)XO!OEOO?4)&YL>|2m3K4z&>NBNhZgbt%bPM5&C~=Qla$GJsY)i=nM~Qw&CINOb#t}xo9oKoFI5ab^+;>tmlAZSpuGJ9j6!?VX_ciMUN@sw5MVN~2uZWPkmce6{T$G3TpT7mt+Q2z!(I)!Jd6^qgb8#CDSsV=Y1Z$YGm%ha-%8 z#aHT>=p}9$T$oBeojx|ThVv%f=T>=uc}9*)F=o)`!PwF zr=ugpf5e*6LwHn95RAqX@2Nf|QVgQ?ITn`j{EjGotnBaczPd(eWB|AFs7X0s^{Min zZ?3e8OG9hum5e`}R7U@9>7>eh-s;mOb6)aPr?8EB(=vsZS%t5F|4HEm>B3)ghWAJ- zvmmBr!0nI@f6eBV^e}d)VB=_jMF;YI(1DAZT_W{=k$>XzApfMS=;7q|Rr_~u>YK~7 zZ6p7=uxH7?)&7-d+2_f3`IUF)+4&s=Z*tmj(wz6~{Ep0#wf@)Z-NsO1_(bMY{-2m( z9!DznRlcG>uM8w8Y;obp7SP!Fhh~9%r|7%JW6%|AQFrqvgPKg+mQ>L6x2p5PUx#gZ z_2*055!ajc)we+|3s7Qu7F?pVV;AaAicY9;F3fdWy;-=})`wX$p0<;J>m7w{*2e4= z`kuwaALrFJdw1~Y2m{AiMH2Ur7XFdGNme&^r~Uz+w&pUh+*)J&F5^1Z+uP{27>kxk zUDMSCA4R8$;E81TJSI2UFW=9r3vo5Q|53XSaS!dVrPQhQ=5vjCu8Y=-jOu<~yKhJP z`*|0o)cJt-hhDO6Zs}HADt)B#Z%QgX7_8LpyY~!{OwmnxGm>Pe>M(@Vs>9=FBcB>o z!DOYtw?#nMv0D7{(Hu=oW{rvvJg$bSedM@36fS%;vY#T?yy034G46 z#%9IEoW>|s#cszg{Hb|_`f zp)y?9x5+nOkiAgld-@gL1w%2zNpuUK) zmRvN}7IxHRnBUoo2*Ah-8!=8F%ap`Mu&^hO0|ILj4{}7AI;V=zKvg!G?brV=kLcgMXs2Re((a!a6RZGqU%Bfi|6GmYwAP#aowIoI%kebjfcH(oJ6T|J z435=~fadt)p&aY&k9R}5y(1lI??ROAwC`0;c@Glxb35>NjOJmoH~E2TC7HB|Gd^#y_^Q@-fS73OyXJC&yv%Ny`kcr)7p0pp=-H zeF@m2KUZot4Iwzq>6Onc7}Z>L{F>`3@On_P!VL3e6hdgtw81u(zfBGE=W4 z_3)z-iRBl3b-y3kygLpS#LMJ3iyr|epsR~HPHImFOj0=rP>pST3G6bGBrWRKbO z!D()O!r#V-41@6l)V0RAqKm(l3I|g4{>VBk9ZcILppDe5w$jwj@)x9)J*qdm2UD@C zaZFA%<;i0$3?JrT_`KH?k*Mmkk()I#vTlSz?zpswB%vVHq9UwBwjUJ^NX_x!3PrTQvw((2xb##A|$>A7|>udj2$+we#(1cKm_34+)`5c|3=jxC%+%B)νDc0~ z!kY$hl8_u7+W2mFd+DaC~HEDs(zZlTvrQ=9s*3P_*8SW6+?c*Z94)s-Wq` z=AGw-i{ll?RQ|1L42wo78Y<6%!;AoiwS(HOJ&5UHSURg6j~_>E8y*IILFuJymdLHc zUfruVdXldKR$9Dmm}VY*h0_w)?LUS9 z9hHC6qQ91e)&i~dxoF#>!rJ6_SR!g9X^A`Xn)uUHy|Qq;F2{-%Dvz2s&nVnoMIfA} zDKPNJYXkAJX@6~Bt+h5kviXccEVl}ALo*_s9@xG&zNkBr8q~BRA!mBpUn}4?nZjMf z0!n|Wm-L$tSlEc7?Gkreic<3lc7RtcBt@_=3UL9$Akz6 zL4<=B49h0MK_YH@e8~+8gnQfqTqH4v(SVibvC&VPRs`as9EbzSWENfSbCEL;Hk+6D zCqDgI{c#iUU9%!PkGgWde9wb5Pl+S;ysm=TjVw+R3|RO)HF~ zN{C(}Y1=!cTXJ6+%$DcD*S4F1$YxDzZT4M1&P62e0s8iK)1Q-6AW6QQq=Ay;%}Me~ zk|!sr5bSuuElCeP5!qU7H-e&HwmUlp(;6;2bHI>J7}qsG<;$BL*6t%&9c;*(2cHn5 zcgy5Kowb7pDVpz)9)^+!$64$YB?rjQQC+oYG+?K-vYyb_+Ci>7t7s&3 z9YLQ+6mK%OYr$|<-^QhIyyJnP@!4fO4VR}?q+L8Bd4y!kb1gW5Yz0w1u9c5pi}AFM zN*d>oc5#{c6Gl0*E+CTKkCB!VOZgVR-oJZm>NNlEJT|e*Nu8%%)-_MNe4q2b!;yA^ zukdI0fNJ&f{(yS*J8!*K-!5p|K>qmke}3naRAl=A?z~X1^)G#rI<5ZYLDctk5*XRd zn@`(J33M?eiAIaO<<(T0vMXHb` z0EL5bb`?L|Q{zpEPpUd{IbM#GdlB7zC=hmg7lJ@h?r{DUx zy+nD+Y2&tOANh!?12u?#Wihv?2yZ8* zUn;Y>%l;{oivccDIS-Va2lGe1TXkT)0J3741FI7?Tu#=^;GoaaBAfU5hVekP@ zym(~g)7B%91_6z2gvTqeg}NP>s7kIhAE8?OB}9<;HGi~L78(HVu5h=E_Yorsl!cAAT){mnfYx-cOkF5*mb8%ID?TqH# zuV~9nGHI=g-FdP);+XnM^X?vPnQT7gB(HK>Wr@XU_0=;9cN9LJ5H?{!i_KjQz||Fp z26j~kNtRg_uk0dgucOcRV1Q5jk`L(W*m%un@Bk)CFep(=Cj{xW=u}eiVr$%}yx69p z^n%0*-n9t}<1Qqj5g);FqeBPa0=*3fWLMkb5A^j(ujEVSp?yT8 ztL)Ln_vj0Xbv%B@d%J|>UC9zWYYYFD^x{cdxLW}d8m5o);4=+Q%YTt|bxH_cx?9%P zaAD{8A?}0^EwpkBJmZR8~!25S}WKz%N-xX3D`kR2SqlNsOUVAwYNsr4${W1MgGzv z=a4ZztsAu*IX0H8X5@+Pd26JD98X7flB1FwZJRybwTo(65Foe6vPw5tR`Xq!{TGp# zG?56!&Q?_KoF@k-ikMBYazG11IaNRw_0+txOUISl1$Dq4e%nb)4C zA!|J9X}cErDcSVRG@acr9Tm3Zu>e4|i9hRmc}=%4;oK~IygpP$p>_KB-QY37NWR$} z;hbMFL-2s=pfv&(uCYRWXP%f+|@h+6gWXp}tCGh~}A2?z;a94ylB*Bhu z(UAC6?&K^rCi)Y}v$fVM+S;QHt85AKAwJ{rt2_v~ zBH)^Z&n5SVQsYvnXG* z*2_5@cx9f}dY!758s9jBwxRm=H+(`H?-gzM?XC0R_fI z*3~N-QTN=2M9M8c&&J#={pwskaL;Wf3d?lK*>Jv{)BG{;a9fJliOWH!Q6KS{-zq@_ ze(U&gF?0cO65zeK{KYXYSzrX^C?^rYgKaZw#Is-o>D8o~&xbPCudC&^M)1r0 zHqKMp;9ly5M};_gw35vXA!4{*Mh1iZVhb_8d0aWxmTv zWSGxc;kVRAUb*@d88G-gqY5zZc=F^pQ^B!p{W)luB3@LLp^&3p zdYVa%tP3U^>0xM&^>LHE?GTYh5+TG=d+Fd*JcW)5B<)`KJ;tN zSI{U$zxFQfpi8i{dn~UH8YubOs0G*Aq^fOhPdA0Qo5y}Vi}{!E+@au-}6a^_%)e& zWuHC38N80}^lZwKVHDs2(H44OWH0&5Hdv#T_o9^-TU`VPM$2HHL5+o($`&cGO!0z| zmM>Cu7*~8-|_HTnx?AvY;OXc5fS2H#9nIWk}~BpRwudo>lhEB_SQ=MRdWqYhM41r+ypsXVdwg62Y0(%GnCR3Q!|nz9!e?iMebwl4 z z&dp{{#%^2iqD6QRR7ezc>95UqEZ`KFi6US+kG}J=i49W=rCQcMhaFVt(2d!Gj%D z`>u^S{gvQYnb|^f`nok(v1eqDw^JlXi?MrQ?8o+RRk|~`S`nA6YKc-stvB+H=h76` znhO{;R{bgB$y6`GY#p7QL}a_|TJ%i5m2S_+EslXFmXf}+-kf07jBaf%Twr>zM)86w z{!&aYX3ZRmGXsCssSPresSFjQ)CA=#oJi#n*TYiAw+L7aP8&QDI%xGFGO>B)n;UGd z5E@8WomXW7Y%CdWEv^q+87vIPokw!uq!gOm5C6C9VY7ty_qK;!?X(rXi4|wXgUS}A zD^nQpDXeynb{u=o%5FD2QRNz%JVF?f${F4S5c}JU&JY%zaM(MV{V8@dd(9cX`fJh! zkC~TURfy;Ub1iWh)}x*hZa^{0g2@9j>}B3cHD>y!T5N-$0{j((novk-Ru@%mKhcKCa_ zP=f3l7|436DI=|z_u#e{_*+5hBz^gtjcZ4{6*!#DOkq#x4-{8gEnxP#pRH`y#FuGH6-X8C&cr;KBot%W1Jl zzvtx12s44Bn7|Q*J(Thgarsw{fKa(1bZFdjkTqXVAmm`vsy*`*&+zzI_V}UA$>kr@ z9|TD8t`=aZu8eV#j&Y{3U5>k)ims5^{)<&wT}4BYhy!S)e}|`Wnam#biu)gK|`YE2&g}vUF>5ko?tR&Af{sAi;8& z^dk4x>>F9;fqz<5e0a6iLy1lheR0u{+W43?k?jRdZzN=Rm3?lDI`6*gm4#!lzbWgS znBbas^)%J;JwLK#VEg6*E@wy4fMT&r-&y#A3Bs`~}B60JafY zBu|SSgPf_|S8&=lC-WOSlJt=&A!Esq*dV>R4+G@nypzuRCMC8`p2zW)DNcdVeB$;% z;M}gSKrEe9;oHfACJrpdd@&qo*JH z6Fu!)_SH9vrmClKZSoBI?4dqvK23))W5@*vDnfH#o*r`zxK@@r1J%i3DEQw2C#jTRC{uSJhifK3XH*Y2AhlwE$X zhQRYk$nCHC3pccH@fM<|TG9sYE(CQ@r~2IXiL`lCBYL`ODyjym8m{NiHc^tcRhteB z=Ci-H+UC4hPTRi$Gvx%2(>6V#&bszD{e^P&rAS)eAPbxrG!A3L;Zg0{@`HKeB49xqwB}!s%)JB4e#$TPmQ*5pVL_2MDb0$dweCjpRjhce z^%^Ja$Ie$4&emEd3L9j)f)GmZ3^%KqD7epxyg5*7ou-@$)M2Gy`&;`)wg@2=exPr{ z7y+P`(LswA(`JMr_rx1SNv$ob2Y-@fq8aWWDhMiH0)ceo!?l9~hZF@wuajH6(PbRl z7zPF^5cf3417oEVD!g*^L0s1HxtcB=g7Bq7P<@QtQk&L#j$<66L(@qLNYVDW!2m4s zoII>X2#^8k)Od!;L&wlY>)Ys$);bk7a7N(^IMGmhZl}ges~wRJPx5PmPSWwev4PY( zh-e_*7^L_LJL#4bouI{3pT&biRO=)352_=($7!*0NGZYL+?4(#VJ2dNAnp$K@at_H zh;sN^c5#0F1x2&XryQrnWLwHCm$8MbHko#l7C2WyU&GU{C&3Uu&7NG?Ys(kd;i9 zN$Y^|_D+C&ffy$OzDayXYLX*Z-sA?JiW(nf9h=&aT)>ic%O82X*qywP=N6um$Hpz; zGAlf$7dwF+9`h<9%tVx>aBE}FAEanrvW%je)pLHb1k2hgsj@yfjO1TQjc*~hu5EOo zu&8IN_7;(ORSk~*dOBTi1h`8LmoZ8f$iQIckpZg~>o^K9@F$Qg1z%Wt{rvOtuY^Lj zx;@&as1c0SV*61PaWF_hl-6BH+-z$C;D$%)l*CqzrUKPkYl1u@sJtu~edMYmFboZM zS&m4%aXvHID@HoIBfAGR9hkUX@YC{-+G-_gkvM50<}CXSZD6w<=x*M@>hlV36RN53 zudv>M?v}@z-vFgwW=$8AK7#|X+d)VyX2<7=bhfP-yw=G&A|DPWW+$XJakjESDEymk z6djfrOU)2H_NTw4?f*6~%Oo-&Vo(6~8PJwpK~4!UrQLWTPr{YH%WveT{uPIWsI^sj=-LNGOpuOq($9mkU|31OGMltazY4{8sX6H6GM} z8d$zGrsC(}q~z4m^p}xOwXj3%MIY>ctF+crIbltUE*5~5Ya?CaDyEIuMl4S%`eUBh zshfDx*N#2Q{ednP;{{!b^GTVvl5u7;0Vj(+MPGc}g~aJ7@BpPyacOtuGYcn!^B(3E z{(FE3g4&IDP_Pt2z@y?}vR*1NpTiRq--bP@^Ww$&%aUI{DV35Io6M)geCs>4VbhMC zOC|1?S>^(OEoh{wVbLHL*favV4-)(v>7F-V^NvO>=H`>akFk;p$e2pOk)42aIkiEB zKD{$uGtlkcK`>LgMZa~~zh#C@`a9p- ze75GEpzzPAlh{OPA?0F-BIePnzoKsvp0|vV)LI8dwg6X6br;tGg$%^EfT~ z0}?8CXwku(*W(gH!b-_@@Tw;;POB}#<};eD4d7LIZ1&VXPd0!x%6QEN5NT+WtdFuY zl&M`?_<^$rT(uLQuq!;Y8(xt-j*US4io8P37lFW60eJFYwtw*}@(TqeRO)v~wfRBp zT}}i88dTy*GNM|-XIP8=2`CiYqSGEDh|n~s`C3<-wqhDzpmG%#h20t{qe+R6$y0moiv)obKa;8e7R&xrj5-+z-$FmItsx52jqgi_4s;5;c#FNtn|mV%S1qJhTkq2;r`2sMod5>(pBL+U~qgVozhalBmq%)oX!M)g)C`W?Q@OEY~VI`nq(- zRF(K1iE7K15CsEQg}PSBx!K5$v0C&gQX)IToEYA&q!41xB|Ortm-P?DEkf>ZwHC*d z@VCkrX$|)Efvk%sXsu^^BAW+-qwSlOC)AUL>ywAAT1VZ@$>;!i3;gNp$+;aN!|0A~%5{ z;ZR*7!s99l4WAsa$~wX-AO`fo{Lp608BW!y(o z6ENk*LqqfQ;vnlV)g=$kFn?=(#X*Lf+#+`{h?Rfm%(@!ha~a}YD2Ij`#ldm$v%4jh z#WTrQlsp}FN8VBc3VR@U;#q2$z=v$Bu!j@1)^L90t%2>Y7U;WdN#k)n<9rgmf{BSd z>`Cq`!pOsUh1>AjddnAiIKS`-+JbsYtmd@Hx9M3z4C^Bwi9Hq865sw76ToiLz<44ip!|NcF{bUfeN&qk zRm8m%(oNV(S^MU}c-ahS(IO+6G+AgOpO$FLen5uE!%O9P`SmaDW%627_ z{XO`LEgVMa1o9bx6n;vF!vb7ud3=fTgEu3O+kW|-w?;Ze*oo}c^lb~f6UR_DBP)YQ zG-BCj9VjUvlP!$2Lp6!l_(SxVDl2FdOI;aDojCARNTd8(TyZ)sq1YC9E}1EhaKdVP`(CI5(b;hpkEu8+>hH&Yv+rwU<$lM^3)mTe>utGf6_RY#N?6c@g{jZSVmOA z?dLTi-lytgh_?pdo9>i$KS7n#CC_jtPgLc}Gs~Skgjs!5a>(tI%0p2Ncw``f7%;t_)6xF+zRg<(HgD)rC8d&dsXZ&oqoRCu-;}g{AAxSC(CIea zeYd;mkA%t6-^34UYD0R|R10a-x_XGbBim+ih%H(DD=r)}3bBq>%TY%8SLX$DJ-RQb zjHMFvj={2wrTVM&=B;;&>8&I^8PsUm>qXO5&6c7nj*=Ea^8fZR<>4l0I$aVDb5W7L znQbGVJmvXgMa`kS<>WJICH%-qA3Or8R1?$2@^mc{JB!YOzP9i!>q5Z~2WaKxu{V z1T(o@mt7Nc>$P#RvnE(5UWdNMdIl@`b*Efjqp_Fe=2OnR+X|IrNW@llDU9B?@b!8m zd@i7R*ocOM7+(urp+i2dWrufw<(4O6NE$N6rQAvu;U%4nD0QGD4`|%RwIxongz@Xp zH{x0G!?uVDbImBpc)^6#z?NGFdD3HNkEd6h_Xae++D%n`(z{jI96Cj=SoG!;cg~Z& z6Uez=__p9U4o3HJy)agWR)SbDK|97b?_h$|Rka^6H?lF0ZRer?OKBekU9C%L{Wf_Su*c+6|+##)>ySdB0UG%vVxLx!V zT7cs7pzZ>7Zn{_HSwKofRs(}``YobsSM@-v-=3xtZbNrn9%{T`j}B)5y=G59Ll>%! zqHL0m%FkzX^s+w6e^W1Fyl;atxI^}+t^(vl+o{V+YqgI*<|afJ z&R#ME)}5{9!&wOef_bl%kS<~kpA;~Eq(0g57x#rC?fF(q^Tt`5&BdS)Bzg`7CVqYxo5ig=s zMkSk(fmiCCPreu7(8TWH;pvfG0~Z```JzAn^k!>+{o%^G@GfI~wQ+gPuiT;1#1guc zDUQ8-Q)<|JS|`|-oyiLM7}+(Lc(uvfBD)U_pM%&qQx~q%A6n00cc*5mv7Rkz4$_R} zlas1{v!tsm$-4^fryiUeRBwP?8?({skG}T9 zmDNwbjTroQ_#8b(rSPtJMv?6-gxw>Ax1-JcC@;rY=HPRjV_UudI0OSf3)%s9)RbQe>7`3sp2Kc zEkOP=1 zJ?U!PQymX19r-Q?BQyS(k#^s>$|pnT8zYTcpE1XKBAq_5I9S*bdV?z`6(Y?Egr7%v z16A_%+9KSzBJD+{$DOA%qij!tjf);a#5WJ)qA=D!I7X1XLj;^cFe7L#ItUQ1my-&{ zA8z7rmwM*G757!g+TxSqF1nyaHy0%JNF?|Q{9>B=B?~W4G#Z?AOf!+h-iLC=BDnm zWdxDOia@L07opP!`@U{j_UKa4D#=B|>I(IcHbrpDzOh@RcdANX+dY+{G^)i;phz-i zR0Qw;&0TlhrT3JY+g`1FFH~XNCu4$lM>4dR?Z|BE7Q{xrOlX5<%(>F2XHwt7l zKQxAzWdirxku@^Cu8;>2a8P8846RGMsa=r90L7UC|8hkF3TKg$d>NVvPlb#^!a`4s ztgBHgk2$YC&m}bHP*HTXm>Dk(%4T8&B6l!r6S;Xco5+_~PU_7w%I!^{c?1>E%+EWo z7dmt&e{3!6!d=Zm0wd;J!u=?P%^w_V@2ij_OTZ#g{{_`W-!v&C!Fz@Di?JpAZtbjvRIMsbMSWG-A|3pDbuB;;~^c z2bEKDEy~Iz?@M{d(kk|lcJcv^PcD?dUH-_84Y4Q55FhXouhiX}FRkSxMwiwBoB1wW zYy%0#N$>E+iWp>UOE{8urv7(55IjpiR?>}Ougl-XfBGaefjnLVmurwsEM^{kke-Mn zL|~8X_AEFt@+s~AN}5P1*Wu(uBdV94N{W{S8Ox_Z4X)_Q^#Q0VDSU*u7-*xKZ zyFB{)kymh^T&)&rCqFyQEny4S9V-1TlPlAvycb;39731EpN{ktw*gb#G%~Ls z^Bxl#J)Q=^FrPUPY)%};n|T&1JU2*u0Btxk85LhmYu*)AFg+4()eo8YtUwv--ho0& z_>;Je28jykxq(>_?fU&CKom=@lAln4_Fz|}XLxD56A9r{W?hz^NW!g+S+70t*wb%f zJFR&?`c(WHPuQO!VJ$X*iqp6!#LW3*gJnRD`hqU3I@U1-78>PdjBBy4YpiH#UHVwo zBcHy&uU`^CCFOI43hA`p{1aCl0;8O#Es1n^Kh!SIx6f5AS#Q+Dg#Qy}ZT@-;Axj>K z`_61$+~ReGhQ>WD`oPZdp1g>?3#I+454G!Pkfl`4oE#)uxuZFO*^^q{1_0L`eLvO} z*JGtbiBXB@CtTMJ%S|lMZoHmMvdPpFD2di7U6nWa0}EB2!6KeGL1yST*q(*H-s7cH z&5=8?g=dQa6;X%Tf~T4`Smx*c*O`^owInt!ewRslJt=H#5xgTJ2JrjKUljBwLFCQ=P$!pEG;1dShe=$Dg;_HZidHs9fYt{cIzM`1@7mv>|+%jjJ<@-?c zkMl>~34AE0KIGP0AG}+fxjj0!&d;pquiej$Pj-8-3~A3kqq#?W)%^|P=WcJ9)xYLz zaT5qksTU-xPs%pP;#kQ+R@!uEXHV|+Pt%LPzj1RY{`2=oYaQn&)|SJ81E1Z(=dM4O zUdleZ_Af9#&rT0{gd#Q|I6X;hrieDC4U!b2q(0Rh8K#?R?)6HtajHK zt5-evgBDAlwd&!E!$+=O+V-oh_IveT5D7{V39NS!$X83+#ii!?KUB(j>w}G8gdY^i zr`%ZNX-FLqS?7sdhon#xxz1bIly5Bjq#;*>!W{q(W?H`zKTupTPB-RwW~TO&3a(KV zT%#(u?i20;%+}UKmIM&OxXL~^SybKDeCltA3E=hZOW&zO%rdS;j|25j3YCe`HugjI z3P^uN`c5$1JpLVQr0c}Hu}JN3Heg*!tP$U)lSJnhj|Q6=h_0wQzv9~P|4x-{3a>>X zSMti^ojl2ZJl>`r)Z=e6kD_Cd|HyEQ{948CmPh`B35o$s?6LLcZMc+)XeTb$AFfO% z>vpnVC1-t@Z-3ZEr#@V1{V!c=n*H&GtdDzSl6d3N;-CA-3p4fTU3KUKDZ) z_xGEw^iI3dWBvt|_Se|nrl*0@Y@lvt@0Hq=PTmfh$N*fIoBXPs+?bmzNF}+ai!s)KZPM4^Xorxbw9kY6R6jt!RIK#qnF2*v;;cI!zV)dtT> zl}(lWM{1A!T6E^MQyH;t66x$;`C*sRTYqsFYH*rUJ7}$Syz-JG@&y53MgY6ahxy0j z)TGF>bhw&dd*U$Lp7+tdzSG5+WYfRSyz)?eUdmdwwuAzr=i<;g>yLt2Ib+6xvla zFto^65tNal@|)B+@vtIj@{tE?Xt`%5DBEqgW({p|woz@WMZ0~InjlqVGexx46+uyZ z@SQ!d$|burTy#;R)!~aVQCW26q0Ksz+T?a+i_d-=B2Ua@tedZ^cHtr@9U13Sqe$g- zG6?n3e;F9|^HlAx6BW9GMGWJ#C$OO*lTR`vG^!HBq^;Pq7BlH{W}~V_pX1%U6lW2k z#*Msmj`z6}Yk8n?sVBGH6~WuAVNp%rq_{V^r1faos!fmr^+V@ovH#*cY_v2X^I&;Xdt9kl4%q{wQT%N*iXlMUwlV+992hNhp4*?mw? zsxUmjJck}KU>pwMFSnS|U>#5BHvx8q(r&8HGW9np&8W&(1mHq(YV=204=XsEVOLh7 zatmpyS_A_qwjwC*%{<%`4DtX4S~B0FbaS`#WRSMl9h3-hUn&2Y!RnT^lRA}QZOFYlnn5$aPpf18d5lAD0o)@Zs zq?>s%-~I*W4Qod1kFH!E^WT8r@EDuIqB|)mb0$|wFv61*C<2g0LKYAT;S~)HRpe&T zkRm$4d4Lyk=4mL=*aU0L<8ThDkgy(D?-Y-WzJxEXjWYsmMsE)GL`jHSGF0QrHK?De z+eH1e13FOZr#=z=3y?d_NGC1cQh?b(dfZuEHqbeWV%~fZYRxV|RU0|$&8Lr6$k~Gj zUhI1GXM_lCqaLN6HETy%TtY77P+VdZ4`#hpi?tl2E4m@8q83^Ec9>U6MT=$a`}Ab1 zqS>OYlZ%_CiK;FW-RhQAsNEI~b4&yFlyAFpclPP{+Za1@nv*G4eqQzd%kv_u|KCe9 zkGJIC24l2ee_X655`e(Gwg?;5?o?=4PinF!to4@0JJ9dprsU-JnrHo^{MLTS4@>7z zr*USrakH?x*am_OfE8WUCA^`D4$6VkO^6YZjv`O6iS zO>qX7h7CHv~ zR*z5zzu|C0Mb7YxbTM=^fBYg{3>5>lay1(&a)w_@sHIeyr``ILa%|EfG|6(gTR8o# z!ULx5DLcK(M`e?i-gnsCu+J1Z|Ug6pHCBKE%QL&FKVr^mE;p@zJUa$qbKFkZahJ1`JtHP zPyL}Vp779k{3d?Q!-SyJhq!O55tB3ncicb=gg{FzxF29qW%v!|weP#98JD zScK(8oB_H`(v1(vL zX{7pQ_iLF%{*Z41w`fIjzYem$7T90+%we(&N%_|SvA0$wx2}imoZ@cXJ7@3r+>%kd zqLjT>@ilagm~tT z{8$b`f0O#4;0$)=?p-=SS}sGgSgb7LCJ8;MAzDnQ3JJScBpf$!1ZPUjyUUQ`JDNYl zIlmq0i{~vc`I{+b=Y!0(OltVh5sug!4$aB_N6|YF~aYe>h*yVp& z9~ZGcnDd>FL|1LW#mu~XpfpTw97|r9QQv^MyvUqjh}$(nzTVzo{)Cwzhch=^f(dS$ z0~tKx20q5EH{F%H4}899jT~PexntxDg&V>Jge3Ge{jN73v?o!E`eZ~c{A{E*iMFumm1{`dIaBfJ%8L-+7X&G;Tqu-sX=&phqU68gH>&5#;O1;*VuVAxRj1&RR<6no$PxITtsWWViqR{LEFhPJ+7 zP$RL_lQR<56n&2QIkOLdPIBgl0m+%3dasT+y~vuL?i`qMrstjfT%bM&JpTl~Z0;YR zb8-Rta^UIdAD)f4C1r_lp97wWeazR%&U|S^@5z}kMNji|o1k29yM74jlpKJ*BzXM& z!*ds&xS0X^9PoTDd{)Ebj7~}a&=luDqwZsq^GAEb|Gqdp<^996!j(Nba-X{e&OhdV zllnlPQJSBPD3N_}->6aHS8Of_GnOzgcym-+Bww8aQZ_^0nQ~{%m#sfyfJ7tl*$c!J z3!-d)$IxjuLI+_$qu|IAl&>5Kq)a$-qr-&i1By4+=KyuixgGa=Va}_IghPMF0_#hK z=lsur=jq*Qm_GM><%~{xz8KrS<_kTFJzw*4K*=7M2^Jvsif*G1#+83CS>LoKt0a5* ztaZ2#lT{%Tgwh42vR)?rfvoQ~a3wNp zzxy;BnSk;C3ci(n!?#aw_`VeWbX(43=B&4iKgk`iF9n{v`-kV9UAgdN=x>zRo3)mw zndV+z&>4|&m_jnN*tUqKb6haIpW4q8WO=2kTiq2TKBgirkD6(Y+$r+182Iu(QyjJV zUvau}1YhV$iKD`8vbhXrP!M(I2lV+xdOm)}ZMhwW(TGA|b+MrAPTJQ7o8OuHX^Le_ zjlZzeDyo!db`q!0XG{7a^Lxd)k&6A2*2XI2FqZl;? zAQY@%Pb40Da<4`Sx+*z}456Bz4#A9Cp!{+rRDXvv6$7kJUq;N3%vvoh1^#vww(`e0 zU-$J~7oOBYj$H1AtB+AzEGk=W+$=U4O3YJ#kCtgk&{#NPW@>-7LXjoPeP~JF?n8U{ zA7#ZC9w9r(Ypl1LyZP+a-iL&UNDX8 z#=AdO*kcsvwRQTON&1|6V>qEbeJuqB7F5gDAPRlU)=(>wNVRy7$Tshagop5%|JTMeVjxW;QtRKeG^mZg-;Zco3_$0X5e zhRqzTTEz3rW;2G$qIU>r7KZOM58ozatwP_;oHEVaNT7DO3|<|E+c*=~e>9^iWN$;g zz*DE+ycrzOTE&#H{5c*;RS42@vpBIZs`|~ZldS(#G$1&`=?}dluMhR`H@?KLFaJnh zZ`>}gwRKX-GF>-YU=FQ?3k+38nj1IUpBY}z(ijS+%}mYrn9HA&#zCt& zp=PXG(cj^9WYo^5l7{p7GkF$&Y8!P`lhkch*FZP?lk2tnYQrSe0)WExp$&d6J3Nh^ zT-j24M#~)Ht@Vc;NxWPv?8s)@TJ&ksyQ+)66}n(XoPd0iM`T zMJGx{T}G{^kiUOm zlQd-kn>&6_ldi_E6gYuPe)c$Ro;43I_fyZqQcn@nsm6^{>z_55m!~Vq9l4pLH=j=X z>5Dp!nE|!ppO^nSPUZF;-8&3bo8 zZh5zQA9~{|JKtvO{pMfU>38$14jYB#z+UWNVIfmIam7)X?Rs-M_mqfYaE?DB_bD9! zB8b!4M;2VQ;tDbup?>J{+fzGIsf87(h2`cLu40E{HZKVcwBjU3n_yVL zrTE6=`fT7Dvw?%7WPpRSWPpR8485_L*7>bnq9YdzTVdOQA!8O6E@ zgR`{+pHrTHza~+iKr8nXTZvv4opG>OquT>=1KHYN6NGWHZ#K(rtR< zOyyYEo`1PD??cCy$V;0A=~!92mRmDDbS$jZS{V4fQKp%`O!V?6zjG{3f2Ucs_K8>{ zUzGmabC^agdm7WIb(>Y^jrNSDdHNzL+|0+s+bV}&At|wYtAXU^HY-i_hQ4PvWJ7+l z(~ym$Hf9Y?H)MCFA>B~!0DhOAr3Wy1k#yf0t=z_G*;=aXX04^lZZm7Ca#~|NSyh^s zaELvIIvwMJrqGR}<2%g9UjherLXN@nFhV3zQy5w|aGDPCH`!lowBJr!v z2sI6zEDCQ2`5Ht%o?NWY@i#B2E)WJBF!LlV7R`t~4wO@pp^+sork#}@+0QaEtVH?F zioiYJ?t~Z|%)9x3C8HpNsL`32SB!!z=qWpU;+VE?c@;G&h?aT|LUP z=~%9d?9foMzY$CJ2!7Npf9&*p?DW(X!gw51sNJ7j?J4GNznh>@)>cpgM=4hYbMvfE zl`W1^axOhBdc?~#B?(!ae9sC0ba0hNj*+0*!77CgmsB%YGoTkhw{8e!YN0tyiq z?Fk%WH?GDr4)Q0$16yRTx?!=GWR%%yRPQOhhRQ^D=M{AsYgVIZ8mL*FCabp8nxbt; zTjUg}DU$d;x3=VNwgJ(CZ!5jBn zm#pkUTwoy>15TJ1Kg8N+#odrodtij)F;&v@DGWspN?&gKN)?Kqlzl{>@VqtfikeCv zK1t&$yF(L<;!0zz&sZQqH@qj}9aq>Hdc_D;T7FXt0^z5Ono29~#@M1P_oQpG+>-*h zCnYZihY@ZSj6daXCalF+kkKz2b}-g|`z%hyU(Ej+$!+BHy`}i!M5Ktwx>2D1xgGio%Ja+oQNm_1qDKKvA)x+ze$+jty?uU)b9{bQQ(b1- zt@&PzKj4H4V@$bm@hIb)5=x@vhKs8^$5iJvf11~FX^<`2w-9N7f6NF@i~S{Nt=-^j zqmZJcm(VA!i;_p=6CP*DqpYUNC0YVif$qE#=~g9H&!ZpnUGARLK<&n25tyb){2BkL z(|qB<@rhn@=vL5Wn%@}9&`m|r#1dYlA{R5c`9DY@5vRvT-@ExO2sfZrQen=gQdlw!fi~+8l=?Mw!iJ|64yyr;$TJq=cv&E z8HVT%ga9=#`!F8jBlC7IEV zmm{bCE1WVhyUbbrmvPEEzfK*;I(0}qDS@$Y@9S{t>hITSgDc;x@p>7tTiOu!-(KU; zUmgG3=?;uXI52XsLBWVQ*YTUO=F7>s3y&c+`b=P#rhP?v~da87wW~(!(vC zpCaAaf39+PA|Q<8@$^8J$J2nyvS8C)#!ZS+)vmyH{tVSJqr?Lh>OPoys4sdAy_8#y z<}+x~)0q!ApBH|5>A)^b>!M0Mj6|00YO{X9?o>kE^4=eF97on(%u$_6-Ye#)fe>Az6dQblNE}i)t{BgISU4}pM=0eHhVVfJ!_G<|0 zlgnCa%iGPOd3wXhmRdq>EmlI0b;@((iv$K&{Yh<7gKLE^6ukV2a3TUDzS+lV2;wcN z^AO36=Dj#GSmMX{G0OmD^Kq~CaHrca{;)K zFt8lr(b-5M!%v--s;`KM;Q%75@@O$pypijC{f)+|c7p5?BB(w914w6Ru`+nz4}2ng zM5gR_<&!ekN?<$hT*~t+#vS=Bl9}MwFUC}U1M)6A)oJxpQ&WgDq;5+r7a%wyDnQt6 z5FaSh1|FJnj^k4KY#@)ePW4fj*WN?tv}II1Q!hbC1J6w7hsXit+<;jQ9#qh33BP{Q zN+=!Ne9u?~kwD6~5z}O-9f(ELj^L6 zG*ZovZ13fuK^1F0_Xrlro{m?az!6zn3|M{vm85{{*VVGmmn}-MJkOjW2u!>t2fPvi zZ@4s^9o3prIlP$U`HWc|V3n}jP$A_7a{#!{Ss~q` zNtpw-fxHJ@_hya1oUai51;A1_2e~Mjhh0 zasX#q!(+dwS}7~^Q)Hd8_6z@D0_0Xjf<{YF>45sjK4A)RM+kT`C!5zum;24{?>oOt zyHriAYWRSsBd5HyLqsQURy#T0v*95US6}d0`(^QrtT&l#*DNO9Cwl&M7BNi%fI5Qy z#fwGmBDmesH(5P~*QU=~Rc&x_rE{cXd-x-LD;M5eFE@6VK0zQ-e&fX(48Jf{ysp^n zQ@1~PbT4fkw+Lh_?|_8hgIqX4aTOq~7zxZ*p8{X$flMef8E%wu5phKpn)u}@Xx1CA zut6M|TKa|nIga(y2yArktXzAgZ*-({yZ&tWeS%0NKt!*;sq}Ge*#s$Gwh@nZI#;=! zYju+ZA%2S&dw*?sVn05AtcxpfeW}gTsMcETtvZ}YDv}3ip#P|Z*saRfmeueprvU^S zBaEv=R!kTzD(4_2E{wKJU0c5JlY|_C%xnggd|=Grg7MO|O;_sxUbE375IN1fLH6W- zLrETU#`oE^9j-;Mrq1L(LZ5DVy@Xe_*a2w`6yiz_l&4OYC$g^GqD}RhhZf%R9u#82 zH;Pu5Sq!7dBSbS)B^BFAw>_nJNse|Do>5}2B0)yMWuE+;EZqnUt7OT3gf~!C8+-?Y z`)B?gV*gY4PfRL65)UA`eYixS2Oz5>m_vCu(@Si;YK#exx@cPn4#VF-a0>SbbVt_t zWC<_Ikd=kMa-s1gMqVj(o0kleYwhKN>hjRpf=it;S1mJO_x&m&4^)cCjLU;KJ7}^0 zqM#C(xwQF&P14C`;_uiI8$>%T(f?=8NKGRsS!R)3$|9+6W|X`)S)-Vt76dcgL#a}4*2V(l7T4mFlIb)i6&R>xAB z^c_}}^#S?S$p~M-K{4?rldCvtXKlcZM$0?1AvLW!dpOP|2Q*wJ1JakEptJ3g(elXp zD=dO%@jjA5LF1BO0|WF7GcQ;!kJb}QQyctP)^7Zt*O=8!kcq-w8Is~Yv# z9h8?!b5?Ij1e(2)rS_~eJVh$3$v_#NLAxbVVQ}r9P&!?wP1l~4q4RCbJVmf#s z*Uv&l?j-jJPYP<1xQ~-qn8bZ#x+G2_(Z--cF6NY3)9!rID>uh(((62Wd2)mlKSqj| zI>nDj=iK0Jf7M&~xL(ffmyvZ9iiIeGP`7@VhdwPdVP0RAc60k2^eAz@d*N@!Fz}D* z@s6i5^f<^t!anJ-8I=Dg=pbLCqLVctwZXQ3=qP&8jF3+4VonAdU&-^0@25q9XT zlpf*a;)wuDEQTd|?6p!wtm0kmPxJArHcbLQK4oS!b zkrr__Of!#rOcAGS-DoXeTC*3ToYvU)c-Ry9$R`&ak04*!eD`Qs0mLWPWu{KSf-}Q^ z>I_*k&+?kDOrUo&{6xU)wPqs6SR0j^^|CoYt++m*lTqTbux7rK9+l2nHb1VymeWUrfQ7d?bJr6FhCR1Ha@G-9ut zIRip^&hXEWb(2%zPOUNnv7_u-orP1?x{BeFT5To7=dbliDiBzm)stGo^JqO^YVB+C zvIj&ajra6`D7ecraLZ^!<)o-AeaFkvcdWDYl_t5Yd{S_*Bo;b}1FZFMl6GhIdMGRB zbjdl1oc0=<$(g;zIu=l=8AP29qS~wXA;~+0@06*r2_hml#Rh45M2V*8Q;B=#c_-)6 z=NaQai$0ZESJ7ulhCY={ORy4qID|g+H!{>|4at1I17%r%P@-+)vr-z_w5jwb8Tn0XNxLv13nO2>D1_tW1>GJc}`2^)N zbSZU)FZiqp%Nh!qDyA(x6bk0D49qeP(W`8#>?M#|RM|`N?Nds%sIu4IDV;NyDlK_d zs1n?q4sP01c_37I&&VvQl%1>fpCjM?@6+d60+q1e{EE;AW)&;5wxf!oiQM~^40`zq1nIcvWdJ=!KWiyn_J&!tDL`akse z)uBh3s(+vLGII2nOpkZ8eU<2OBiFjI*FrS?eDZ7g`&sMbt!26NcpWE7`rlvvM}D>9 zUn5>9<-lIa#Q%i+DpU3ELysRG`6biiz7Kwt=+$R@#r_Kly-v?`Vu`#2_ zxHh2t>rQUDAEkUQ)1G(AgCOmBXEh}gLo<70rBpsSSSblz ziR>Y@DmVy8^I-dVMB)M;<+l>OAIG(BnmJE=8%Aw$ydv}Fy4OjtIhsur+RX5dz~ zN;!xq#>2g}RgUxaav(TLP_7SCrDV*uR63zC`m$91!Ymx7DAl%4{$J9o_M5L1y`KE* zFGjE3Cuh;?-~)2$HF@NJ==G~cuQFl(KJ@yHp;`3m_!T(6>3EFxdOJ>nUvQ0%z1+gm zWtmmp@#SB5v6Yq980Xhw*^BMeUw(Cnw|<&=_0PW;@!ox67V%ErFPC_aJN!Sy`&A=e znXrE!;{EYHxx{PvA5`J5HywYySvcgMk9+NGJU!>yF18i8tcma5C_0&Q9!0_z{4@UV zKij+g>x%y^|GnC@Y>bQT#@n;WwO`M;J!E6?k4yUp-Kyp8U#}Yux%TT{>&V8OA`<^S zahJr$mQ%k8IjC;AGxqJjHy0W}6s+h+I%cB!m5eFYr~h8PkQIAP%eU5&VdXg5UP{J& z`tJ?3>&*5K`t{z+#eLTKAmw}c?|p+l^*dpoF%*0F@5u~^-=RSpOha<9$g914_T;q6 z9x|^Kv?X4xwXLw5n4Efn9AW84w(jMvXBAhjxl(++ULYq@IVV=7&Z}gqfigMT(x%+} zv^#ft+u!mQZclcbU4?0){_^~Zw%odR^!w%b-wyt1HvR6s|E+y?*zzZOWpFP2t~vN~ z(68km{~!Nb#SWn$4*x@#y?uKB3I7F|s_cb!5BmK|`QM&cplH&OhkKU3)6JMjd~)$8 z*MIRB{&uN&qMUgpKKVM{+lWuDPo5oL-o}f5^DX?HYwW{c0!O#W%g^MwtM+{BXRiFj zC!c?NJ#o?qk7i6h-)NY-bO%vzpV7My`P(0V;?`Ci!&H_M5!hTx0jd~w2`YpQn}t1&Q)$)K z-s-JZd#~-ay=XBc0c1CUYTpaB$;fb8%4oS98_L#WjL{`lR`=NFir znKS48Jm)#jdEQRmqnE&ffkQP1GQWQYoZ-Q8vTTij!^+@!B|F3Gj}YJVjTUy-F}K_8 zJYXQVRje?dT(D&~-V|68H&eNbqVJ{Xvv~Bz(ZUWb`d$NTL-gO0i-Y;5QbXyMN00(O zL6vAq(<;8%a0APpRHF9RO8%e;=VUj&>TkwOc+h)db|UL{gCI&kP`vQIYF)jjp_ zW(yi6t)dF`eQT7mXF1yu^JSJDCvi8qtqLW|h7ZHcs9*LBGAwADCqMHU8t*5EH1(_h zn0eT%jPZ7l5WK8Qsx+VfoV9p+Mj}jT5VkL9n8yld)t@f^uw0&=@EL2?J`d%e%NqXn zD;Sh}z1?Gk*7}a80AdW+kKgnO=2_Ty3J7MrP*=ZRZ*y?KhQePcC}Cp~^d>c63x#_n#I`93-y(K1ejy z>g*83>O^`xAm>jI*Ke=t{w0=XaaA&YqjrG{#2J?Uad_Q8z*9@0le}XTb*m06*wIY> z6~@9_=Aljpyky6*7V*v9qw5J&yh?=N1H51`&}+xJr?cM#2Lk@gSpx?C+#z>ihH#MO zW%k08X{PeIOxYsCmS9el>y>ju!M(s`V#Vf}%3FOo{Lf$i`qvHQX9>fNY;MrAiJ?lh zel8pDPxirMQidGgE#=LTDl^Zug5O)SP0TtbW;L_oS=HN?-VKlY=h~J|!Z5>aAj``@ zf#(jXlTx>pxc1cYj(gE7S{u$E1PBV<;(Dydv9Zky4$br97GcZE%akwLJB|zlhI#Tco-3z?)B9Ukr0JCcV!1Q?oV}t`5U>^JD zfEk&W9}bT_$&G+S;oySe%vS(|Y`6W!vIc^}C<4NX8Vr*0I%{#Z0|v?1jkU_N0fP+_ zsVA2Vsqzv0ao<7e@Kg`Fc(c~U9?{x!zD;Nr*YA!jp5<*v7dtBF7)17aWKz`;Kx!PL zfO7`%I#D?O4!L}QFF0>kY#n8g^H!w&=CbC{De(qPi5I+YiQW|?UOB-@}_nS~lsyd=HOw?jb2Hw_c%wybx*Bk(HLkU{o?T)QFPIdf? zA(~DtElKZzC&?ufCYRL15$-R_x_QNXe69wlV(ggsLgQZyqfzvW9#b2;Al7tM`KW$c z(u%F<8vE_{l+^!HIOmPDTgtqB9j&#@bI!|~)*#mGscPW8xc(4e698}aWoIEPEFC%c z#cev>>eFC08oNWR$%`W^X)nzTZ+l#9vz(VSUjTUKOO>YCaDqHj&d$nL-Wfs8RN|%L zBbu>BVQXRW?FbmGJ}#wzCBRPLE$lJGb3eZNtM$vNHRa|opEpQ?EUBPNs-Dy z^X4VdvE$=WQK{JCtQ-f+*Z;MF#U1~({Ul=Ke}bGE1chPm zzCim0a09bZLXJU9&W^JU3oOYBcc0QP`w)i^-@^i?8K8%sK`;p6CE^oUj(luPiCF0= z5f^z1@Ze=QkL5nNwYbvh6CG&cKsjzB$ZjN$j@sb{&%xtVQ>N+rQg#u;31voUl?R_R zVk{`yNIhIfR)qdV=qQfOSau4lOk>@cLaB#++0eX%gO=S(Zq#T#dXw`~QuoYO{`wo* zxfrGF6sdSTVhe4U9XJ6X{nva)BV1qz&Q42igPXgG#IWS-(ELbczMh^Er!-}aiP%)) zGmiOkv$HW$;sY)HiUu0FuvKg6;aBVMtHxgz8!<%xQ-g9NO$YeN7Pg#|`#d<3q+^5RHZ z{0V$eviDy>O7^SQsNTP#XUI80sBD)%TZpShRO(^I)0o`rTcy-OIBqvdOGO3odV!^ zRU0qLUAc*W1uZF#jcR|)YN&gy6RsYU@Cnr}!?Q1Y-XOe1JvZ@Y@)TI=%U*_8P~%IR zRypXY)Pq>+q+oiA7oN2K`-qk7rNk_6DF|Sex2&!OiGmq);kmTyOROK+59cedzsP$W zRZcvEs(^Q9D194Gis>c#0Eaxv(XNXyn(A{oRI*0%*Pcf3z&BP>UW!zhro8tkN9%JN z`@YxHG!90JQ*J8MV+17^y8Mwl^hzPwZjM+}WXS?y3Wr7Tkv$r|ChapF7h)?$NwZDq zvyEakO?D}!$rzWSuE2*SrD9E^bE; z5FD5r)tNkR$Av+QV}ltF56d_-+l77EPT|=WOIZ)Hcx-Sd#L28y5DRMzxNJZ(Wo00@FTm%OX$LZJQY zD7IHzze~1X?1PG|aYlf~xM=ou#ad_2V@r}jb7F&4=Fu`4hD?&TgH&Y=eQBnrMLU}= zIeP{z&UG}TxM!usk=IMn!WKuVW&cJ_vyoCS5;}!9*jMt5CYO~_s8Lxfrs~oR&kk;r^aDed| zNBEK)KF~=OIbc&~0b!0Jg!z5=@$H$Z*s+^Ja@Og00Z-9MM#F)Tw0Y<^-L5zj2ZV#N*OR*s`<; z)>KiW&#diWVwG*Sq}voSsYIyr4}qyGHQYWsJ;GM`?3p zlln1cp&olWk1gFt_=_V9d7;sm+?0GU{j|6e^eO$?QtE~z zH2o?t647WOUB)p|TUf8@{{PTlABFbU*0OJSTfZE!z=!_Q`Xza5Sbv=g_G1alKz}X# zclztT-u@~G!$t`~|Ae%BCh7R-)-ZfDnTzB?@llof)>ZX&d1kPmzjB4Zx7O9d=^#ED z$Hgn!60`5MAbk7h@D+s?q|Lp7>fgiGlW&FhAHf^-{}1nd@V>D6-+*^u^#7Ca9*WJc z0^VnE5!Sy4??&fU!u!Xs4gl}FdjosnT>-ZpyNKQ;8t$uEvlJ`k8;NY|fZnXJek=&< z`9fe3c*RN)_#&Vnds7e@0GiGu#nCyFM|&>PvTMNzi3sg{j_J=vib)aPYu=Lb8G#w&kZNjtROzNH7Ee{kHL^neK}8S&VB?BYb$C@ETKgF`IkrYhdQpsb2b z8u~g-Q*vjzBCdQ5J4Y!}=%lj$+o6^H1zT+LAEednUIABI(=W+x2SWZ`;C+VPhOy2!xRkh9Ou&}gKd29Q$arETd16+lQQ~e z7>imV&#mfE7E>bbpJr5tAtd#b1ed0z`thSd=U)=$yMRcOjp|7nMhL=|S*CjxQ`HF? z4?1pP7bXQ{8+6Qu>juEWCfbvhI4=w;7x~TN9F)9_xfquoGgzz3f9SH97ye_d<;K6r_VuTsk7Cf*;bC1>5Ec4P{Nlh8J3d zE}^aLFgPv-cE;NiAvCbgWK3-D7h}9#V}zFa7cBSuMmDF&)zLGut}~qfT&^BO4>-%F zE5iFZV#x7ZQE@|Z3&ei(r6h8rp&7gp|8G%4_W8L6fo7HH@}S4PUz)sMnADLW!Q_^o z2geb(%=S-!q(p1}xkj86^ejHJW_GCRgu&r(c>QrV8&Fl8qZ8XER@2$J;IQqjE!zT2 zT#6rfI`0;S%xv2b53o zlIr=FQGo_^^|U+zCzMQhg<}J8^&co;9iNf)e0x5jc(}Nl7%~)x5JElR&rq?1UI``C zo9Oiy^h!vf9xX6b%%N983-v%yhKhUWl@LQcVYrIB=#@|d_aa+y8@&>8s3+*ENTgRn z5A|rbp<+nt9tz;N>30o)*Xh!@4>zJ4alk+=-GJ?%)UoZ8Xbw7Nx}V{+Uv`0Yd)0*V z{HRF+Rnc_{84`p7CjFgw*2a)2Z(+g_fM5{Mc$K*GUK&5qbvtUyTK$=HBh)aR;=M|F zSdCJFDY=CPVAUCPfL2KDqF-Y7k)MkQ%MI-8`;+kmxoGVp2o|$C9T&{v)YBy_?OLbR z6u~FAe-A+bpGX|s>{G;=5AJbxo5a;$4mB7ejdB|%E2=yz&|bG-`$G#beL65rlxqNp zDL7148Qh8EcArNt>tJKgl7NA>rS9>6L!iBjPw)~fuh>X7o%0Iz$*yy(Q*@ssusMa6 zqN&=sxU9RLK-7N_U(j!WcaB`!JY6vRl6vSY*YY2O(9DA+g>`YG19jYa;0rq=LT>T8v`d`5h ze_eGBPvZKC~o~+z!6;$v`mUMXIKvn5>`q-9pp<$0q6(R0Z{UHL<#Sj zL-0YxG94GZJGcahpyHP_6$uHvJE-CjdL|6;Zfr#XJ=64m*Q8B$n)~m@R7|9Bn)vTV zSESN2&H8tvDsHA{n)2@&D@M^X&G&aB`D9PmCc9&gKwJ^6k^U{1klz{0ak6Ivp}LX@ z>q7C|iyWY+o46r}=hpF7ZV2MJb-bY)f_QEn$8#Hkcy1kU?gj!_!gK3* zi#GuD420*_@kVb5;<3UxN0R~H87oLo20al2;~vt zLHH{)>ti5(j^N9(dwN`ixPE8}GsiihOaR`l{XE$E1o5jL;0rt)+zyI(xf8&piah*r zgc}+um@}p=gRs^MO6^yC^R2B&slhy^XnC;H(l|~g=+r@h!nElA7{p|I-!WGowha?& z_BwmawpfP>Gp`+-jPEYkr~f;b&9?;1(w_ID3qCa(HU?Z#@T%fK#l6Fiyi* z$l<*ja0AMb=JoJ2wlV?_xL>t_C68SM(4O~Gleaw~wllVcFCFY!O@irv3i$%N(b|jV z9X#9Dyn4^ypcL1MIs$9uG?D-M8Tqh9+vB9OCiKevE|MxYmRIiEaSa!H_7K&!SnE|| zOb*;vuHO;DY&J%NDJI=(l*&z;2~yZzu-vkYCLk+gGM%Ea5`0CzZ>xW+pbX~%J0tMQbZL>vKMf!1oJX&pN-K`{^Wk(5S2X}m+|&TxzrPZX-Y=r+ zwm!IQP;gC(!D#U{$;U?uGZ$8&7#14fSPXEQ|6_m(joO_wYN;5tG=B~Vb&cguE8AYL zI)u2JC(t;ZyZI3w>-Oe8iqlJ|BGFWl8nj8T$XYxG7bJX+qR$(F3iZ!q8(Q}PwsgFb z$Uc&?pNZbpv(Lq2Srf%*ZJziazyJLxab*?S<~nD)QQXu-Vvf<&KeLrHvPMno{qqp& z{jJ=_(~y6dmMqjbjhDFWV<9lwN+Pm06V}mN&d!GN7#qvf0eG^F^0bRpm$<}ZzqtOe zxc-&vHJm%xP&u| zY=`{>JIW4XMqH~mZ6+P9HA~&e&VacquKE%6()w@)Z}+_99)AozF#r4r^jd_dtk6msbN1P&Y36MO?XiDB%piKLcVSfS$sK=Q}tiz~s{fa$9wz;NJrnCW4!tVFwG8-CTNQ#0kg#C79_T z<06#}dKi|Y{O+r4S`?ZV{au=trx$jGrmcY1?vnfKPW%{KL#*MC3=bttf_E`EmNxmw@~tkRe*bS@?d759e;L|W%1W47Xd`0%ss{$rR#p%W&^ z6T9NXF;?9BPSjOQoXZA$JnKK;Y`2CmV$gTCO+S1lBqgAYjbC3n#pgd`DVZIrr}=q9f^OkCeeJ-vr_ z?dQW5?3tI`Dz4uavMO5QXwe+vXoT<`i>99rw8Li|%}wWvrhg6sL%QwC^CJR46!~3% zD3;6gjL=RUaDEU5UUGBUcHR#E2&ji9Lyp|QAW_zI1qevuc&jhN0ey0=FdV?T2@6bC zITjE;@^3nYH8E__Dr*c~p`7C}lR_80vWBqFU0wJbvSjxATu-05ls&lQ_RIchL|~nK zZTBis+J-ZxtoehTIIgM{Bet!o`#(luY@CBB;#rMMfa*HP1S*PR(H?A~7O4SrVffxP zJgeVRR;$MW?}7H1#og7v$gchv5t;_}Ye3`8*J!-2{k$)^+D6qf%Pdl9 zUOW_#RZB#@yT~$3B^3-Ew1VT+iS|%ZZ+2QDz9SNgps5o z-hmB>S(<8`JtJ@@PJX15>TuYj*=Uauo-w6*)UjA$%#I62J0utC^IFFUs^U+F$U0+~cjo}!Hp zRmOr=jHCUH1Vuj+H6Nt@xWJt_TvG1=Q&$El~1LURoE7Y{fauCH)`s<5Jct& zM!pRdO{k#^!Qo#^jXe9}hZ^gEpEQ=Qa^o*Z`qr<+*@dMek6Ljs_*gCV9jvMJhX_D^ zTOQ{5-N1aMnFztz%X-LQHnA-&+wjH_{GfBZKu!b@IH;0HPX*auEVxe z=}zexBPpZ)4)O9(wKZDx+vgh!SyN!-b9gNb9KiGesl;nqjo1GNdjHs-3Ca88 zEA-7y>KprY05Zo{K*Ux>)AWY$j8Ibp1iXAy)JU!G4+Qfb^H1|uTQT-%oFU{)^`wun z0N({L#JJ!@b<)p+zN*Q&l|V)5m{0H-22EHWHUBs#=Y#=MJAjE!aq5MqK46jzRpTMq z20$si`*%_unyf7t30@uLUZ|!^`Eks;&$Y)EE#<`D1NwldjQXSd0ixD*tY96>t@T-7 z>HHL0!PeJkrS)0grhMewopQkzX5A=TH~OrewdO1Xv%V=?U-4Pr0-zZ3XWXy+2jW8` zK+4_cSiU9F{xiwiESZ?K4acgY3KWaww$EVJBY`+{gJeCzSGXX82dqX**7nO0e8Aoo zzTlyN&Ood^O1Y;Ds_Vf~gRa1qIegrVI1)9J`q!-YZx!gAP-Ws^|I>F=L&|N3mIJgJ zls3>fRJGhTFbfIol724hW2NV{S@Aro)D_Fh2l$G+R@@6K1uHC==yxX_Nsp2!a&|!b4IR zJahsc*lyXXu>C;;Qh>%AG-Um7XNZBtv-qmv&!kZNxrED~3^;L>kJVCw6t*ZHQt2oU zkW;9|MMKFN)(CsUM#0%?C{OMbeM2aU{c{OP=9pNHxhXRNq)iafVXfq=JzIW++1qqz zP-3C8r6$$Uwu|edYhRnJCxjr#a(Qp>eBtAjyHA;sLV$fj8=v6$zWR7OmY*=#21~g| zq=GhOJR$}MG1z~ZVf|;gndVcRT%bs~=6Mp_^a4uls5VE|EO%d%btgeDg<}rXKa7KC z`_T#3b)XBZTkw;+5kCd%@|CrRxMqu#YpV9(wCCkJjrM6xLWFO!cG~k32#IVGa88(H zZMljmavP_C;83kyrGsa!J5h|FCSUDrG4;+7*6iGp$p?b#(^G%S)}8rE%UJ5CxVepe zIF*Xav+Xi*M_ZsiDCU4hyHbRM*=`YI+uf|y-ry563@0Sd#~BMBfWu5msEB3$f8aW{e~ z?R?5)Xo5=1$LeVfaR=cmE3ExVXg?ao+s~JL7Mz};V9xagyeRK>XS%`us81l$fr_@T zKu@sAMjy?{&=o3+Hw_;<117r(;I3rAC!+LE_vzALxD}! zHavFHS|R7|lM9-e6-x*SE%~D0;_4V!6q==ioyq|+TnSQseZUriuch1;g|?)8Ec#;f z!YehEzW>wt&pYG~!CbWQ!J0P;T3g20@<{2Pi!;KP6d+lsfeqrzivP@`-vkOz`XMB}SiK$r ztjXoE&%QE({x8JQ5OiD!f)=Y`O^zdxsX!oUJR_rECFkd0P2tp^lxKEU8VzzK%#t`J z_hl1?g~<+vHE%3o2HnAwZf|Ggp7b) ze4R*rxiSG@Fl=_9r86+p-o#ETyC420pl*_e*hokKn<|tkFC^ z>&_Mw>$A3K-Ef4O1nkAneFUQ7Z@fp8aSw(Qg#|jXIO`z|jQiJrT)+B1tku7h>aRUt zP5mF%>!0~=*MA{NxkJVzSpOG`2d@9?=$Fv`Coo0-dyb+0+H-jQ`@aR}WB7P1(%aL8 z-2c7tcxBGO?b$L{YfrHJoG^HBXys#X(pS0$3bOSG_AV!P5;UyUpn)$+01*NM+8611zJr;q?_l<6I~YpFa@(+jY1eiz*f(HzW8cS#gzGUAKorpO zav6)RABi%+3fQ;4VGESorte2`z&+>m$v%aMhcNo2{|ZL@r>c_oaZhmif6L zA>97PD^CrP^@hku7A9VVX!f0)ygClFx$}J>=xNU_w0bYB2aNm&6MK$<*~Of zk2S0n>1%(2^o|bE(TyCoW*eHFJt8~gp2l_$NP6`UAa8#venRb7sDXO2&D7aEt(;~|_RR)}loFOw_BGMoA+BnR0}-@MleeCp zOO4C%d1RrE_qFk^#s}p13f-!;VecL99SG#(O;mscqJ^p5}}3x2-t!u z1x~pcLSbk)_pi`AZQrd=rw(O;omF=^?3G~Bk!7PXKEP;9XzCiJm z3m}Ms^l6Y|6WpMcgz{tf{vGtYNm+o(XuE*W@4)_w@b^n$#^L?Hcl_<2e}8hewxoph zUm;;}wyR$oKP|khjk>)GmsMa^87Qvf603)MZKI74Xrv3!z*kjM$t>g_Xvt+uug zydO^BGfCSI6YR@~*tZ{EMjK~sKl~;1umFqU^cLrj$U~^v-`-5+B_@b(01)=4dt;DekB}H* zHDMsW3C+LqCbW-B7$#)_myil>%A_*%5^M#%yU!B7>2x+lvCiVgI*O?@K%{|j)c3~X z>bZm(AI6XN`Hk?;u6g*93eE3eawop4uW1`i+Q=&RLlaIM9JnXRCN%mcCnCt;^d?|0 zMUc|F2N`(&t;PIf+Xq00j;Dti?2ADW0(vP&Rv9*`e5R#V3!e)TDhZYA&vgP(wXpYrRU5pea}p9TA8d>B3Lq5c7jmAYUK zJD>|&z@!V3*Vblq9++72y=INhX1NCr z!-6c`dek!jqupjX*AuXBWcDM4`AP~VA5E_ss|SZ4+$_!YNXtQ3Z-FL1k{TqVOF1>w z5NT}=v}3W*+K11GRPC|HIxj_w)qh5Xk&dDxRcH!q*ISIxs(%Ot_mMYi++F@$3JK=t zs5GJWD=C|332t5O*gkMPhFvuHk~?+2E8Xw^n9!E}2yatvI|Ks{ z!bUl_-M^Egvwf~@@Sb3gSMLNDo%$~U?lZ95TF{-;m*tau(`W)1Y{SukUUK6RcMr!) ztfG&{+iS&T-PdG@0H5f!43 zHVNN<98qMhiwt>4u)l)*RBToX+7(#uY4~dmk!nYA-Hz)ET-d&GeUI6N;K1D?O*X-( z$d`Pee6-G%LSv$ZhS~S$D^qi{##F-I5yA!5pjGsRpXwD2#eRMK0Xpm@41`+tL}oF3lsY|VyI3=rZFd~^IC>nS>%UNl z+YC+k`;3>o?)#v4jOUjf9ah8mgKX|v(J3JU`1-mG2H@adiV@Rs5*6J87uKzS0?-+> zuzC;~E2_ai1JSB38RDRGMn7`yI(A5$(v+{fsae@Nm#;I}AaAHe-JVYG6j#qf-DJCm zjJoob6ySmca-Q%rpU+=W<0a{@}oGm2T%hI&}?Xie;Wto zTFJgqDj>-kX7_L|Dlqv$wLzykQmk=PgPq+GuMSA~OL35)@=`3;vaq zP3}h2H~k>?+@hqqjKbEE9ER$OVy@>^%54M)i|dzrfm7V~MwYOr1$P81H~KGvU5!q!k4J3e zW#+dnRK;GZs99qzah`(GHjP#$=H=F6r4E#;#==qHnbNg*RJL>YCYD|9+K|ev!8h6J z$=Bt>V6+A4S&|QPes>HX?{NNFp+Zf2aIH|)eLT`&sI*jdCt@2{IbMF}L>s2Sre=q0!R72qS+9Ab_@{^aUF2$k&;_45zZ%JqATdJqHv8sD0#GjR^RoyGDHyBnt zP}M#1T10%l0THXZMSK&h(E>cLNk0o_hV_XjaTol>b?f8vE5$P zJr4!iZc#Ih|A^u*9|36gRHD1y=$tGp;CH@wf)# z8jH(>D;C!vT+z59amC<@z%>TfFkBW~qj1IHO2l3;szRv5oys z_3vM={_hnb`F>j;y@9x3Df4>J2~_>_>i_=VR}rUwn*aF@X+rb3ntJ{3cPQhZjZJ9z z-{tvg>ili_{^Kgz6gt-5mfyegJqRQJERXf5ZzyiKQrzzX_qXY|=Sq1F6utuWapGEy z>j_*GN5b{I{!H&LggjFle;4vj-+zjWC_B=xNrRGLCbtBJ4>y<@V3EgC3*Jg3B0=}o zNOH9q`S;UpzF~G?WZNTF!$kUMkDnjF`@?uAMMEBDyUFEQIvB4emTg>XRL$6gwjcuD zQax^H?W7T%*^c}Z_d(4M_ax**_i=i;TwvM+tw@6mog7pk9vbr&FnbaaX)Hw|k-?un z;yZiT(!@gtC_y=MG(^bVOSI~;zP|k_`1Q5ZldzwiawoWLuUO{c89dyFaf3Gyv!&{S z079<(b_XZuHg6!ZZbg!#HXwpazkz;N3{O4msJ(Yp2>nmKx@O_>ou|)jFi|aH(hIBp#-y-4@ zew2o=C${7Mb&2@%W<38OiGTa~{u1$#ZMe=LY-9@ZEfLE*;_!k5<)7l=FzS4);jH)s zFI%OwO$bFkPy9g3|5%ik?#qu#M70y)QJFlSZ&15$u&aF{Dxbf9`3=hdgwp9{Q_Ux8 z-yZQG&#kE2MTFgK;PHQcq(ofuG3s`>MC{pu>YSkJ>_BzeP|!hCiFeMW9e66DhJSg3 zR-vEYpk+V_d7?)yAmLeDs6h`(ICcyf94!$q?G;_mgHFa%YkCWO?lydZ#|At!!};zd zw6$`F?dP)1Je~Oo5>$uiePJn|A}_lhNcaeX&8d94bNPJj8qDlfrO zDjc!FR)fGve4#;}HC#|PYkM;|BWZ%s9DI=sPCX0-aCqg#uBd7Y%^bcZ)1qkp z>+JK=loKGl3-FMH2W4@FzJCN%8tV-RB2euD)s4GJpYicD3-E5{>CO(^ROBCifJpssFe#C~it4*jhAN(J+k&?z=%MoXy&C6y*@vqF^q z)+D~e)+gq9|(;}anc}ZN>D#xn{7&yAo!?e27QC* z%j77^m#JaOOn8K*Z$q_Pf_CE^GGAsAy)}j~NgpOA>7RqWQ~3Hk&QG)@gm4O%+{ksX zu6KGs=+;pL<*Nrnxp@`Gg2|SLBonJwp)Af|yssaFvCmEKlFKz*Y>)a3R7jt8i(aR@ zd-;puIxTXxsR?Q=!Me=}tDES08kmK)Hd@W^!dG>Dh{j56dWak#$PY^hVa<1SdgaQRLr>i4h9 zcT%7`&`-W|b%LDp5Yqn>LC)<#<`DKXq57Cyp7YZVzD)LOFUfOKU;O9tnqlAa{dLUA z5X~ZO$sM@A47XfS{^A%*66W_^KnM?)imMj&Y<$4cz@46s2*V6DYM@IdcMpg};WJy%gve=Xzz?YWZwPI6@3o{QEKsHT&8 zx*0Z6f5TE4y4>v4A@E&uX?6dEp{`!~fyS{vzdM@0)OSi{J zk>bJ9UlQs35fVvI>1%-pp_QNEMaXStjq3mQ zk;o-$B;%{@=q!WK1=nfTcCjWC(iz+L{C}sU(AnDr8(stq^S_D!uu(rE+0C!o1+IxcA8E65hKBOD{N5k`Q~M}IrTfPH5;M(|$*q&G$PaoOo4JPivp zMMq$vH^MWw+Cgk?2$6?HtNTk6p?d!XAlO^Teq(tXJDZC0ZJ?cs(hTZD!6h0Ug@Wn`-q_qNte{yZXxMTO zPN48aic`E$1ch;YiL*z5QcIh`>50f!UYf}}pCnYvJOD(`Ox5Bw_!prPV$EW~*)hiX=_E(b zu_bp=C)E5j!ux5ovtv^1X-7|I`Q6Zx6>DZjdOM<>jYj9EG0x+Yyq^w_-Of(OwpKw^ z4#y0+)KvaCB6UyfX;eaVJ%YUbB7VT*$4MF9<7On#ql>HVr{?2)AncM2UjHB*<%3nS zYMTM7qE*`s=rW$i4V1?YKr5Q<44B}*qMl<$wQcS^ZC zoy*${O_Pm~SD2=^W4l7jmcf?Y&l*Af5q0*p7%bMKkD;=e;~Nao5gZY+6pUqv^)NUz zmG-fvkQ_m%+DDOq4+1ijwLzJ{wH50EdWs!X)E^L4rOf&rKZ&82Udi1@mNj>y04yIN zB8lL92&YdoF&{Wi)a+?Mpu9ni;KmguZsBmh`jF!(n43NT(IDX)&=Ns|8;$hpr9HyY z%$3Z=K#lb4ppcdZEkd$-^(zQf_1S;z*73y=<<@t^qC*PD;P?>`pjh3 z1$|n>;4H%%+H)|acAvjAF!C*MpK66n=(U*)kcJM7SoCCI4`IolVtZVA0N!OZf41i9 z(_6)9t$qG%(UqC9aT0vl3VUE3Izk%FG!M2w09W#0`&bms;46Bto%hm!9&A6ltsjjY z9PjY`Qhso}NnaJ1P#O1`37zdTqE5AOX z%;3Ww3d|S-zW*kpcka)4=%0un2=!-7!!s6xXl$Bn5;_C-80~|lbl43{gWUj`1VSgN zky{TqeF5~c&KuYmaoiXn z%Mwp01Mf-HV(>1Fa5G|pyMYA6i_>fYvMgq93;?DkMONbVfd>4smiHB)!}Fg)b+lPr z(};)t%qv{Nl4WYMXOK~2J$Jmt4+0!6v^w`2%5N_7r9aY8n!3l^96ixToW>U#Qa$_| z!1VmlQJ(5Owm9A}OrO^kB0|_uORgNf*3&7_RdQ=3AP_f z%iEf3R<_G2rr70gMe;60G`Gk*IqSh5=_E%d46xN}(yJ9rP=D*G(~a61*CeR=5tOA;Ez-)!&Egn1Q}X zz_D4c)8LQ>+iJ>%j+`=WCV*qb@#jBdIR4TouJPjOfN%^+0d&pwyRqlvUR<~j7h$jS zfT8@>GOLM$+_pZD^8(~{;G_5(@A+c@IS|biz=<5}aGF!TKK3}fYIdNN4?=OgUji?@ zZ;&&6+0&`Ta^Ca-23d41r`UPZ`%h6{(5R=I{A=fB){#dSxQX$hbvW%APvl9gx$YHY z9SJYtd!W~3LQQdgDV>s9IY<0Wwkh4q3A0-2zERzEFeWyPYK8wq(6}(cX}R!SOeKWb;*h2Su0JHS+P0x04q$0Js8_9zL*^+ZrVZ4=1z}+vpAs%~Z70lNq~r13lH#k7)JQJ%xL+w{`_?qYjS> zjM|gl%xlRi$Bt^#>f6O{JcCg!7`8!tWRWM!PNFfTheXju%0X~rn`gnvTb(OnTL)*4 zjrFoNsW=`gjTkFZ!ur)(ii9J{(0BZB9tkI;&yNEj$A}-=Z7-e5>Bn9`N*< z{=`-h*(qgP(pI>)dXz|u==db&T$|f3zVDGXM)TZWiRPr~pD*nF>|84=PGgnh@o62U zdnKAzqN`Qet02vvkL^9d4&wAYR+&Wh&xPX*&cd+n3)->;x*>Pb#(kR{X@a z`BxKN1sK~%uh}@~&B#_cD0^yIHGe?M-24G8dj${l=Uer%_JF4sd!N?mPi)n5c*4ma zd5Nsp;{FJYL(?oGstG6AuSE0gHb(PEg*ohya9I3X4}eg5F`A0RfxI}gZSUvjTKArC z`(yXg%a?FBFBOlKs-qngqcTpa92ed!@ozYPIFV&%;W)QGgt~?mk7bqPq)Ku}B^76h zZ&t<$;+qF>9m3Tp082=P*hXdu_F=v2LSRWsfWCJI2HRsmh(9_OixJ1Px@FVPzv2YDTv#OM zE%jwT%oqFcv*&;tvWqmV4S>T)FV&8m)7Jd#Wqi%hj-OM$cN47{v_8o1jiaXj-Gd~` zPn-C{5!^5K|1RalE8`I*xR7Z6Z|1<`!9pn-Iy?3XsnSA@O3tAII7*bHFEDaY;tz02 zicE2_!6k_wE3%LaDEL@~_aw1qp=AhIcZ=%30{sTJd@sJt<3m~9^%f#?f8%f9LHHv5 zyttB{;B;x6a5nk8{S`mF>XCBa6LPi@R8I<;LDWxLCOic)I1kFQEIOB&XapfN9um(K z`;#Qjzr5=Q#R~JC_%c)YShv0w`l?X}pw~ zAk8rcmc-M!f`Pr*A2IJ*f!3a8MDl-$A7qok3(V9D%+d?YK&D1uJ<8my-{X_CcaBXE zo`@C4&;)8OlIo4pEeIT|*Di-@gLks$pvd?261P|&NLsKR@J{U=Zzoz<^>iGd{vTKm+|ka(LY4`M4S z%5U|tvcda|8qw>Lqmj;rDm`)t#CpMvL1G=%tvd;msrsLwf~6LeUYhj3RLhkXiUMjq zNDA`1Mh{koKE zp>jBewy;pal$Ro;`mj6qgl(?mqR-Mx#F3ERv*FkOzvy*YUWP|XRj^M3-%S)?s3p%S z+=q&oWp#*Ixz&tkz+}px$wfIwyc5AQx4$8|c(yN5wvO%yNvq28jXL!Tm2)`TXI2k3 zL?H5%yWlAeUMaxvd$A!+Ftv!TYf%*l8zn)nPXTmqkAOuNu(v3$3=TGjY9?8$rGn?M z|A!}IckY(DX!L@V_}=)WEbuB@BIj-loN#)K%&Xog-z$I+$l5MXH<{ECA#V|C6z6~= zKJ>U(Mk9+zRBSg$>3Ha^W)z7pdNb~zLyTea->k;?G^1n5`W)B{uH{q-dmA2eUEt8` z6?RQSFt`tLrgnFY$GuQ#M@#uoyvOT47kZys^>^}$M<%{Ei&wnuU#WE))%sD8n~^Z{ zq*}w*b6BkjK*41pJym-P)tL&X3RlaU3h;1;1iV?w0o=+^!$vxx~k<>J1T&?r7VsoZqXrJ&v~-#qd60RtcHqR#T4JxV5o< zmBH|VJLmr8PBwQVa}i0(-pj|YWS89XEIc4>466`vyU*l)X+A>Sua?s9-^%c-^}PHN zB~(^JGA!r5EWbqUQl7#aSidhjI4}=_Pbq>D`#x$2n)~c8cS>_N!j_V2XX^PBysZ2Q zQf9Ip;;dv|A+5h8UM+meEgIL3>g+|{;TFWuy6lfY%I1COL@1bY^ya37F zKi`0!6J1SUy7@mN&f8_xvZqP|cB!K$g1JT@56a)H-Cai9?IL(XXxj;?z~etiP1U2t z@y|3Q6rEXL1XBo!b7lNkT}&Ss%uEM*YlAzt+CBGW_b-31^@zLRKit+oD09&%Sb3IW zE>&j&e}l$v5)P;%p#6}3L9`xSlYW666{~N?C$VOzT#RMmsgsWG_VS~S?o;+8#Ho(O zH#si?PPMDaf2pHV_+!ObXHz6Qt2SuaWSvbtjH72>Y81{ox;x9?PThudFL{L>>g#>JgD zUV8}ec7+~jR5yv$q+li1jFz(n;wu`e4?lI>aiM+ja7019xI@PDHqqLo?s8l>Wgm-R zyjFjJsB(4$JvQKxH)jf(!+kWePEyC4u0aQsAP`B1d9nRC6U zxovC*WR-q`3{B|q4k|JM5|DzRhXZjnLl8~pd!XlK-w3Z!Xi}gR3&}qOtYb^&N0t&r zf$%^<^FIV_jeA&H4l%|)be_`}nSLG)$ya>}V8a&eOlcQlEB|#JU#|QxpaaJgh+<#z zE;-)h3i7LLafSCIOR!h9byJ1H_PRpl*z4cJkJAL4@C!9dTMxbkp4hN}aYf{SJjJrk)(MARr;LH^Nrh^fNpg*C*x z$lb^egvjbbOhhJ&?kADFpVXyKZDj=mbO0^S$6wN$ST#tF| zt?Dpzk%~M?CPY^Q@)Os$RP_W7<6$-R&4lGZ$@k(xT>lfNFM^$hP$P-npjTzMh=-Nb zb2pQG18$z-zrKBt_c)Gp@*qIQMo}=tHo}J54|C*bcu_`q0W_xOY^;}`2@wk&xW}RH zwxvmcidfXz$4CiIjq>n&rM2BpJJ!DKV96R@ABbd7S`5`Eyam;F1CYe^=$m##35uEr zyRGdTP)mb4V~sADA4Tq1`MDFCGuLe%H|#~M9qJtn{@&pamLE|;LiUbH@b$XAo#gig z?e5$TxAk~veaM%CeJ9QmXcL{QFvoRZl(D*=f$hjlwtSy5Cmnh*%l82+{c{n8dfumb z4Z&BSQ*}rQ2A7`gAiKb;+Y;(u?>cM>kebAg)*~f|?jMg#u&hAQ!V1@5aX?PF3f7by zE`aG(d>GFsy`nDXf|ii|7S{*jOR=(xA5;*^qINuGj?n#6#RXEsC95RC)b?W*_5qG! zr!M{(v3%L*vz5i@U`}jdHv`H&0Tg@^wqRzqjJ9B%`UNa zm7l_8bb5_&K87gr6izxe$kPQJ=+G~umwd#|4lB*-zn~7uyM%+B6dSFRVhuzb8)8a#Qs-?ojtp-U6I~_YSh^Z2@XdTDM?EC-Ewy37{9-QEL#3 z4Mx@q68Am<8_}%Y^7KJO;`Y4->zMAZ7?u%@wa2-|qG8}OY6SELIhK0^OmvQ@O4ekf z;QMeb#YNAL;987}Ol7~qbpaP~(dn72Y_7$Xgo`#X+IS>jD>aU~7qr4evF5tXMTqLS zaEF+64Bn+67}4AyV;^V2hu)ng-p%xmbH*Xd^}}DJ;i_V$9`=)@4wOgDg8~&}y+A&a zz__>HdKDS4vwmt45=U(6t>{PO!t@wBYro}NbMSBCsqc++bfaj$v&(3=d`luf^`%&I zukn3eAkt;+a&|?b%gpvsS4gWqfCN#zjMLB~W;RQ_eOSM^>W>g78gbaJI?7qhLev;U zHQ8hJsOoS;iRmrZdYYwK0Mv2LE)$uX8N?}_&Px&HOK{MkSaaChVT#2`DLbmVi;$oF zVVsTDF%(8^V)e_2%lzy@|9H0}o>)yBgsSdIl%||)`&az|tBvEXJ0!Ekc8%j{;SO7z zyF?c;WW^ewRY%7$hj+81dz85Pzo=j@ zW`oh;{iCD%IxxRI9Z^jYQ7K;cy#iNw0+5NTzK0T7qqFgntwMe}0zN5H&y}nJ4D z&TsCpnVsJh*yYS4*7Lj8}HYZN=WZh(dluVQ8@qQq-HYRN9 zHmB#J?Hbf4ZIJU4Rp%0_GdaE4ehO_veocl*hC@knOsdCu3H`U4(tV0(M%yB>M&~18 zhl)X3W-)s^61<0LQO*lR_B$OK@L; z>HE>}VIsOJQNedun`4TDZxu%hNY%dph(7!D4eWp;fVT(mHvWRRdIs)d&1_TZHt7y| znhAI;9%n$((Fx)U-qdr>o@jacJu#xS6MkB;vOW96XiQ=ELd#$^Rf@Mr<#Ey!lUyjk z(_7YlIV&!LrV7cQJOBiLBr@+K7}nr$s0A%l|H3diPJP0V_&%34$ae>}XArQBJx&%MghcVi_e*<|c4psPrj_ z<9AWoUhyRKnWPom&2u5bQMfU3aUzZv?2S;e#<6Uqp~!akl=K7kuheUxKwmR`B#dhB zu-!(J@fHd{2|}{Rwa5NF)lb0a6L1=ktZT(KdW- zEKc*XU+-9MxZOTdwhR9-Iqtq4q#!?%fO@x6Xl0j(z48e)k7t=Nh-X@1WK9H~woLy4 z3@R|U?N&1FaJlftx|Lf13&hIZPOpB7Nqr}-`V|09wKIQT3$^ei6h3#L!82)tXTMM1 zKVWc#e+a<4En3P9N_i91TB(a^!(h!}caTjtmQtV|oGS({Zc}Drq!us;ijsWk$2g70 zI8k=|s5EJ|v2V=gn4o#27Gvg`S*KfIWkxo`q;Anmp{1Ta=_NG@G3s9=tRclD*_0Ox z(D%zaD;K4h)Wm^;L5*-iH+`?T`XA^YOk(ZSmdG?YA5{u`+6y*t5rBNNP12G$Igi8h zE;$SQJDzXoxTr~`nQ9FQyp!E*+yv*g*t+v>|p6K3OLpq;4OWQ0*ZqH9O95} zA%*4yLjmunkY6Gols2UF{17gBejV2VT!XMrq317fMd8_k>keFx>d)D@&&3tQFoa(i zPTz=S2XM;Zc1HsabvrbbbdGDT6{ofV7dIi~3#;Q2j+a)_RgQ$_+RLD4X-im(v9cFC zE=4YRV0^26X5=_I=?)CQC+PAC(@gd$(5N;BmKc@l80ro)KQrW(appgSE?=B((Q#Tu1YZ7R8qdizp7#s`DxHLJoNgJ9UkE#%eI)VO$)LMu5X&FK9EULc;ydIg zldx385ju5TjDR{avO&TddBRcH8;r%K;CA;s%Q*Ke%MV1?+o&Q*c76dE;JB&$$&xjx z+xq4%@jJzu#G$kXUW+x*HhgmWZu==PRJ$Qg7^+@_@#gzHutWwqy9Ijz5R5%vIGDa$ zT)7p4DC|n!ZU0+pK+WjIr+R$*t-{gdW44dOi_1bqGKLn}4pBK@YvtJX_cjV=6S^Ko zwqi{)Eaol>n}CH3?k4wf$VkRAvjse~QHM=Cs4ZAQEJ>hM>*5dv^6_f$^PZ>2nnuTk zfab6t3ma&5{pBIRBhthvKjj5@B8`@GpSbEY1uwL~7)$q8f;cJcbA?XCMLB7MMAt4v zVHa*IvZRPBPtda;T^+APYWc8Zlv87c&;J&_r3S#(6ssp-*X$?ZIIk+$qy1`j4SfpM z>~6g9n!SSu2!!>T&8A?gS?^+^(Q{h;>?mEY-|dJPtlw06*6R0|mRn!_ObAFlOaMV_ zOUjzYUvDp28S42R#aGsK8{ke{d77SmfYEsSe!mYd?oRYD7VUh7U7$P7f={#9N7R;h zjoySXiH^tN0$)3wZXBl^L^=#9PS~*khK>9LUn%j`gfuL(MV2{%6ToyPX*h^V^~p8b zGiU|cF6K+u$ul`V-iws5>>OcI^Fli$lTN8c+AGG7P)CQy=T>cfL2CqTYCS?T!NMmP zCF%|+XvHb<=q9BCdLkGU0t&jVCQbz|&Ur zfu3C@LW9|nKZOa27UYIJEB30tpaHTyqGjN~$Kd!7-NDnthbmNPxNUfmq+xrSRj4ks89GHE`xur98Zmgw=?`bWVeeo55RYf<3d#Ve9(WGKmxcB61Gbk>OmDSE zb0pm;+e|p%)ock)EgV@1 zn!Mfi$vZ^W+O8vd-r|^WyL}8g1Eoh|??BrIRiwZ{j%Ngnuh(%p7W9`%XkpQAC?etT zr$Um8bHFY&1A^74(nJE}>Ii+1QKwQgd=ai!anUo)RN@BE_yV5u*{-S{2i|pt1B{X4 zEqq<`Q%nYaE;mLRLH>QXvCV5>-uLnCd-aQ@8)`|F5cV|t(2-EC49tYBKMdJl zZ|5q8_JqcT}SRI3|Wld-{_b7hW ze;!!iec)?@YDQo?vgB`&II%;TRs@;b#A}#(ZoAG74>1qkf&!IQ05^=dn#Bf+vW}uW zrboGPz$kB0luRv3sKrj7Jwdzj2&xJ23Na?Y_|J5n%m9yMmL*HGLjzJYE;hSOd;{Ts z3k}mH1UIl9;Qd2tLH2k%JnjT{j%K>&cWRMn#*D|*$E?G;f+IsIobB+cX2*_pcS30L z6O$Y}9PT-SJ4ZL>n~EcWk4r4KT@M z`7J2&&L64>J4o%zD7okzp3kg1#cC{dkf*Gzi2q)<|=Yz(OtgXmVS6=i<=- zU&;_#(dOO*W9DqwqyO(bO z0@%&DNaC1&RCF!DtkOcAT~CWH;uy+l*SikaQn;AYsnt(hS%g==rfxwMRle8>^`C5W zg#Sa-3gS_{ejp%L!v_HB`4&96DRWL}9cf)Ft}l2GstM~Q`&-iTx1r7i>3w?%G=ki@ z`yoxjbRPyKpKvLU<3faeD#Th%ndS)AnGtC(kYc3!jIi@TX($B!+6K>s?Yif}c6c5n z6B^AppC2i+0E!OkLWsE_v}%5K`F{jWFw zK^q^OIU@f_&oH=31t$0TYM?NUep@Jw!rq+ia_zy~EVU1FJQHWI51s}NM5WPx3Lq*) z(njgO2`acio_zxJ@)b1WFlA{D(cPpwq2MAPywg_HP@p_mu4~tcE@FfrYe96CB8@mU z>fw1XLXAY+99^$)I?4R;o_~?@Hd<(Q;B{8ZTbTyp2N1qVpnRL4R~V_)1wmu=>^ z40_I2o_Vsjc?}5Bn#Z&nXw3s=D-BjcuR?y7)=G<3%(pf#KBPKSMPS$7D)B0YRY_U& z|FHKy;89iA-v3N8zywClph2TXiJDf@(v~Q#M1ls$3{go8e-h9Fa$BU4Qj0KySWTQT z3Fde_UhcixYj5>lywYC0r7c!PNU2RACJ|60phQIt+UglbE!b8ADD(TQea=iKA@t9C z-}iam-}C$Bc_!!IKKtyw_S$Q&z4qGQl`7KLD3Z~TAE7*kS4_VR_S}26A1f{@rPgqm z_woK|6t^f0D`j2`3moN~plfVnwL?hy%W{m8JY`zJ3PV6iJGf*nCCMiQ`i%TVkYucs zg`)tY%5h?zeJti6*CjF_aa`Op8dD`Ms!j`1rA6FMF@d78?F%YYSH7TpXddVD<(ZHG z+jz45J^5p8`us&s)O#we7r*aNCVZ~Eox^^L)w@xHk}xK?mk!_Tjw7IOhDELxTai->_#`Z`infRTKjL~X{jN#?Z?;B=*kIqf@*Tx z8(8S$aN^NhQ_Vh6B$q=TUh(9Y{0#)Ew0_x_!zbur##uy7HLmmOfCnsNMQl_4eNy3N z*djLNH`%9N=GmUAS(S;Iegv1;$OXsJ$b{(^!4*=xanj)&KOX5up8OeUrJ+_{RV$yg zEXkGFsI)HWE6Y-qqa%2vHgnS6=Z!%HPo;I}dr5jUT*lxQyM%21Cmb#fGDA2wRhL1V zh^CWTD^bC5BDHeG>e)x3M6k%)`VWM0jHmL&A_*IbR%k3F-UYZ&LqFN+(O(Olr@sb3 z(T?czUM9?EbPQFr33`T;GWxuyFkx=8nQw=VwWK2V+3`G3_Aq^C>tWBfxm-rx)gmW| z2l3)`*K5&UP48+dPnh3J-2Uda2u}f&LUI=zt3OuH)yj!ZX`1Cj`q8;nPGAV@v&BM0 zzt~-6NmM&)D4(lulM1Ll(j4K;j`2|VCpFIXcYv1YtW4u&eF3G|A6f0oU@4|LvD|MR z+ACud2tCQMNsUJiUP1qODJ>{>fN%rd^KxGGwEki}@NVLfFyCDfzx24F)l5t7t4%zJ9oJ1pLS(^BTCPK;OG1sD(t ze}+^ez67WPHwC0vYAmyZzZky82{EefY3$~J!ijSr1*9(G#}hz5J7~wWbi%<%>m{R@ z5ua0il^tmT$Dy1ZY;86o@Id{7yeh3c-lnYuEZKDt=ll8ngrAsnl5Hf5Eow)zKQX?) zUS~I>t)FjQL@&1TeoyUaaURFr&AcxxW!5_iKXI^ibv&N}J(B`$T4R)Q ziT@T3R8?%7(W^G-{Ks|$cP|kGZVR+FjzDy6}JKYm|h=k~r z&bYs?TYskIA>B-8ztaGduF)#Pp&*U*^BAsGfvhtaNqI5dpR2D@%+u6 zcqfvccn2I$ypBOV@iyX%YJ0hfXL@Xilw0D>E{UGF`}S)mh4+Lnfh0*XI8?E&y799F zG!SA`^+1oN+$(}4>mxv80-+zfoqx`HtBT1^^jE6xT6vz{%~V)yP`e|(mZreQrUj2H z^Huy3y;W}WFox}k$C2lIuu+w7YzjQywBWy$6^jq03cXT3Q^`Mj9>IjptXhwxxeu2D z94T!PBeod$X1!j%krtz;D=AY2sxl6RPpOM?qM|)O2n!K8){z^iUEpE7a2d7&55(@H z4WhMs)U>9vf9HcBd>ho~2Z)5mcjwvPwI!GOuKI{d1YnX(A+@0@u%C?ahftr} z?0jxx>;mqxy75V5EGTejif@u=UC{xp0Xffun-coThBsD^H0v(V$6IP~l?Z+Y#w!O&}+xseZKgeR`4Wx z?CkJ2cziZxR5#*dqsQ_vxE>oHzK}c_DYXXK>k!Ap>r{?)HrdgRXLrG?Tglf|bacfz zI0N|s`u=*P__7xLNUbOSHKs#M$_1%KHAtrp^g}>ABi*)Ts=iIco^6`y4DF*$gs$5T z%5;#=x@zX3Yg&g@H^q>mOhUNd7R4#j2>ocYNUfS|aYCt1rV)EnZdBr(c2IXzFFSH@*GrMi_ah^?7;H&{7(=}X8_;1+gR>H zCoTnhpPf;aiC2*)PvD2HHU1Y>(T}^|#y3OOT<4>-W#(N5O1~ z_Kbn&g#Q}+;)J{eeRC)dVK|n=NH__9gk||Nw0j`9?8qzA59_a0Qg^@gF?XPnRQ}hT zqSg&Xoy*;iZ#c(1sUqt_hsiISXW87#iF{BZ^MMr9s??PG<2W!=ocV1v9%Deh)xfO> z63Q-#qZn7#K8fVOI-q>_4JCAa+O|E(hdsCNwP-E=yE0 zQBYK!MN5(VVsV;9O3cZ>;NDRF7iInDQrhG~GR$7GuDBlJ=Bh;{t+W+a!-)~*u(bZf zx3o2Hb5vy`;|a_-CSU^#v}z4i+&8?FD3#W54d+cPiLgwNWzKZ7_B2eV=a0gzqG9hH zi_F_HqVJB;$7@^ea7W))AU?(BZJDj_50!g7eDCV4lLF%tHfMUG?+ydmN5?M@pUWwk z(VnZbj%4k$1yy)-OpJrA?+(p6k+z;1J#i@|%*?pG)F^)Bce6`fo990@zHqNBY-Qe+ zUzT{kaPL#0coF|^jbG0H74fg||Kj+i{J%7QQR}> zG%PONsn2&OMe+@w9~SZsr<17ph4u`^)c>e$+UIXQ?1wCkG3pg#gM$l2LR<3=HInwi zwmwFGt+}Q?;HnYPRC8brXJh1SEBBU;jekLsWw=&eMzWy|JQV9r&BcwJwUJ59Lhdac zXZfHP>k&wahQ0EodY+k4|H-3?$^MfWC{8jRM)LtK^ZREkQrixex8QDfa!84~_`{G& zbzIV^Rkn((xsUW_^yv@Cws(lOrdC~69{emZx$@27G&6G8)E+0Q@pJbqss5;=-1AhL~?E?Ww8EmTNjYk$PWbCZc3&v${7C*0t6o@2&f9T2?$e z{?hMeo_{wj&24Gtf9buy=b2!(4@_Unz2w|0eSSQy-^8ltZ=N4F%~kVb=B7+cf;mj{ z|J=o3&*pEnfm)_C;XO@(uJ*vo%$hr!KjkUognwSCt8%HwdgMQZ-9_E7E_)d639udM z0IJJ-B&+EmZ%MS;NmSLTCgGNKG_`Mqq=zNxx<^Y~bC(XY%I&0)+LzG|1Zr&;^=0$< z8w|RfpIIX1WheB4slL!SQQ@-iC|BN+OFe z-Th}P{R|T0RA)`DpC$Lxi9KQ+GwXDF1jYcD4yh0DOZEvQlE;8G1SHfeb`{(mY|YkA zf##Ibe?5-=+xfVV!%l^KQP1(XWS?PTf_c|r>Ndg50}WfMIE(z!S4?dCU4yjk!1$#q zTI~xwQPE4qWJjFE!K0UoWA9+mOP8$s|Kpeb<@mM#2jiEHsuZ>$gsrr$J&Q{ZVLR(> zXADWQo;nW1*E3n*OSxZ!hCMdN#@AbPBlvpudB4~9JZiCwo9Id1#5}C<^FqXvxW;4S z=XtVbGOo75=X@ZUD@I)xtM)Pkv~k!8~R1c8qIgjVC_3Pxn>!o9J`?>q4WWwID$L zDG~lGB?k_~e{aWTO-#s7-5f2sFitDt+ayW))AS$jjxgRS8{*fgs!I<$_Dy4|chXc} zNHdrF^%ZC=l|I~0!(MUJ#KihlWBk3#QPUU_jO9ei>o%5qo5JsC&y<|Ow#S~(7}@b$ z)vdernI8SN^w4y+h{7Q7Cd>C`@#S$Xx-)}`XJFsDqT~9$P&@I#aLjCIx#LK2&x&`8 zKMdy-9S?0br!f}}J&tSE`jwXg;;FwL@3LPkeVwT)d*FAtUoSqg;@8Pp?2hI^TbNR`_5B0A6BgN~(7Z!dPx$E-J zKMap%>(%9n=sRPcaJRnWkN*DZ*541$dL^33TyX^)5t;a5_;QXkf?K`0Ih^0{d?=4w zyh6U(ma+^j`Wy{|?(T_+X%|<|JB)Gp=8Va%_yy7DGH}WZyfT2FigxL*gl0n}267?z zA0YUCMetprVl$9Y)EWAEq!tK*OQX+a>WAYm+4+hguVb5GQTYqVgd;C?&nGz7-1lhz3~^N z649NX<8fzB|9rY_(b8pX3ffuDtluGq)T|exxbv{f`JI*oSFhAV0Ji2HwjSt4*Tsts z7%N&p|8qgN{$f$*iXsEkTX(e-rqb$QZ;BM=kFi^^R?$*k6m}mjZ4_q?#oAek`6cyiT^o*5UsWuJG`(uz+BX`w=MLk?cv6D^5O48r2>r z>lZ`$%Ch`i66Q627`lqYCA2WczOoC!$P7)w0B<$yHNNXHX8VnA=E&;Q9JbX=!8`al zNi53McA7e-Fgr>CEV>E+xR_|xk=41b(ARLjyAnRR#VGSL9{)PqeP+b=GE8L*kT@;@|K`!qrh{)9R=REyV!`?7+5ayiPGtjFkNEALcwg3<9 z>Iiyd70!0UuZq{nmnl{@Q>L8IS%q&vi3eJF-Pg3+oN<0eJd3J)Uzr4r8Wuco;D2yu z>${^z)ObhKWM{p()Kw)W6v08y<~J7e(Rdu>=83@UO@z*XiksPa)igAGRy@yfW|4qF zax0|G9w=gg`Bd9Do;PP?WyZ}ufgr+ksS4&rQXV=S#z!FoeFJa6+%E>Xzdx@+B@bF z=_PaWCqOXul3MGO^+q37Zx|3>15!;Gd!m4`&B0A9D7#TX2a~`>O>L^Q2L)ygEH~$CSQF?}^4#tp&iZ41zxkExQf5Km?W&OE zgw9arv2P~fBU#0JOp1c7ao&I zsMvC^euvkX;=}$qy;r}3X;n|V+Y|dw-p$N?u;$xL)!tR}lMQBO$pN3Wd}B#l-im+lepH9d_v1Wj%72Lr>Z7FSMNM{F3yP z{{45yE89nZ)Kg-8h#2z!RTef{nf6^uekgsPu3p+B;w}eUjAv(AIbA@&yr|e#+Bh4 zwP%8!ID1JmQG$CjLf48(;Ri6x5{Aq0ZLFH9WS2{_CAdpoz+UXHg}khMM0@9lbG2u{ zB$Ul)y=+E{f6ciZBF`@7g2eNk#9s^N3kN?_k|Fm<@wM<-sKYRC2Fp|;9AXhH;akNK zwGfZgr#P}Qxt0G&HrA%`zLbHPdu{fiQ&LP8|JD`f#K$J*=h|9k5cchrXPHkwfYpuV zO34*FKoRj9s@e*=3Jw%?tju-#0s(j{t~p&!vt;uage4LlSDOJ_e&UaqfF2-Aa?s{*0b z%34+7wW#>FszAfUo?s$%k8EHQ0J>Hhq+_ZqhPhT@E)7DtE5ldNAfEZ*@mpnhz-;90 zAv*9n&I{jgM%DJhz2fl6W?%$M>~;Qyg7KQ@G0%#tmqf3cA6^jcy;>T3ez-_lHQM`C zX}_yOS@AKF_toJX4nyaMz8=@qNtnGWpGDif@%z;47fMN_VZ+&C^=|iUl@d}CKP8E0 z6}E6Hj+Vxa9`i1@o5fsa0!1fQ-dwoXj?4hCI&nCX*y^KA-}#1k5U=kDY@p&a_C^7{ z+rX*IT(F)MV`SvejY>vFYaalM>LIP+7HTB`b6C|D?5{t@RRj+~4uy##Tr4)N{DS_n z)4YXyE3GS7D-#uxH!_SLcqy8UH@r+V@OF2L>AM4LtL~ZBP^=?>Oe64dh;VfUYTU2%GL8Lj}u>jv|wt9r|@XvmL!!V{Xjy^MD^qB zraxQa_*At3^G{!L>(Zr0WyG&wD0q@};HD|Vn3+9+`|%KlxF$7mw5OYcJISO(I%~yn zn9{;*LbC}CY7N(afOnUov9{(RO!={`{BvqD(6M;^ONvhMq9pBFZU5pN=RQc8;gLxy zO#8-X2^^5B9!}oa0nKduRJDFA9n-pSJ)~l&_A;_qqqtD!x8&F%M`t|#-I4kaHVZes zr~l$1IsWuv{@Z}YbT{y}g~7(lFkLa$ynj6xN@tpnOyFs+)Q?+t z{#hnMm3m!H^hm~t;GFRFk-B-rc9GqyMsU8qFo&^rs0_7hVOsU4S7gs*RIx`}*$I?m zJB-T$q&SRgQAue8vtvqj|9g{MDGcI{;2g=JBUmX1edtoR8LT7@gNoAGt3*%;FX%5X ztTaZRUoi)T&6w#iuJLM9yY+>+sG|kD^a@|m>)M(YgsTL0s?H_o9CJMy7ldHI=IVT# z-9SOdSJ>hsel2(Ot}i6UAUEc0bL9yxwR;y}8hSP8#N&7#1d;WWC^X+lGN;X&#xS6Q zjViBkt%nY0PGzi}V1MTf^63bwC1*AY>bp9E*{W`w^Wi#=Syve!YXrv+@{Lvg0UtTL zsM;a3v?;#Az1+TfgaRZq8a-?n?H7=p_THs|o@2X41n=M$TJ|3K5 z21_cf|M-(qRPYF?WQf%oek=KT8jmb5b!7&N!Iucm5gp7pn-D(}V&X--&ZwAUbw*(c zxtAA{t@h~_0j$=+net3)l#wb@R|%_#^FCR@CqQ^783p!%7staW6CFmG2q|nv$>0T> zQQno+v*7sHAvrGa>aQ)$vyRzI=EgLyG1~*5#g6eXIUX8yW#SpXS#jqKy|v&N+{ngg zxvnmi;U>;0UNiT6Ah*s2I~~O4N!4C`qSp95GQy11c(Z)1I)`bOS&^p|1Q+qET$(3M zbp>1S2WhH->`+TPffYW=~A7b3?#hgBpoqb3Oyxp4Hg`6Vv z173Fct@g&ou$+mE4pth$u?T6p_4@qjz{PlN@;@^zLWpyt5FfkQ4qI4vb_v9DiJvA!mjh|^tL^e|* zIK1|b*P+RVN{%`9dW(d*H!}1WSxBEcUVG-+3HrVcVKi(XZp@6)Ib7(Td|akmP@TSN zugERYo@;TVQ=etuI+y+@=!A`8JTULdUsO}cTS$8A&NgWwpYgMK+?ZRH82y=&{8~d7 zU$8SbE|7bCB7R;vfmy_?O6(IO$WKVRgbf4tar|l+C9Wa=e!0c>56bt$Bjl+llCIEh z>>q@Q(e4*10ZHcMFX#V^{MAN7z8ah6=P$w)yfnWC+s9&vh2u2;6~Kl6f&2)0U|Jme zN6mkq`ks7Da@Gmb`zrG;ZPVvo<>#bc&677P&4&DcCQ1CZq{4h=xUOR;cU-YqBA7h+ z$|JmT2}Z7=s&GNOaM$G`^+XA~ZX9KrCVN9>XPeW$kt)!2KK&Imd3^oV*Twomd8G_0 ziPyX@|5wnpMmVZ+rj@Z%5j#FArBHV`Ow;;%gw-2a@n~_rD>O?{M3A_sC!`=-=p+`& zMJn53_1q5~W~k4Ws?Uz2n({G~WJ2oY3{! zk6N@QG1xZ+-W}k{+z0K|| zUhN89za+XA4-WzP zLQM&aj@HGhV=)3HSxp_2_K!^zryt8z3d>b>!E+XmTnpi_#l~nk)vJ>BO#E9%vC*Kh zWphR-2fVFpWsEHKCjgFkuod8l2V3$wfrB;6AI1TL$^2|<&(BqPnI7S{yg`2*R%K^x zQ=kQC2LR%Y@=Zo@V@W;WoaOr!h=WW6vA!`YRCpIv`|-QzgL5A#2o3}9r4Ssdx(mQ< z_=A@9p^#`vL zWhA?CzLd%=&@2Pg!-lB)BJp6&DQ-rj(pIVu_CrSe%v0`p3xP-r?3q*!Qgv6Pwq*zh zw(?Xlw*~^5?YP#i|0WDTOLzfOYT9A0b?qjn1!^BQ0vn8~O{zAG$M;c;%-YVx`)vIi z0HL4J4?^FYgwU_@qT1i{&%>xWyBfk>MD!C1>K`Wv9J(X93)tb7`ZnP)dq!L96{T1# zVH6p>xqsx$XcRhAMDxK#oY)Zuj7Fii-Y7O2S=4;QezcfNs@>AmD2VXTO;^Sq;uz{G zx`D1j6c`Ft8q9i(o(UHa_H`Vs8#fcCp!VV6;eUW49f64GB^`kV{&SGw)kdwOI+R8K zc&>Hs&r&R_ABSm;1)FfoQT%GNC>*;{c%w^Ek0yduV^7oZ9C5pCIhi)gcdYMy@iVrT zYYh>GR-^nWv+7}4r!C7d%OfHmHz5~G7*hO!heb?oNQp@s`PzoPRt0XHPhI$gNL&7k zbXCC_II6mHRAeNFa&q;XLMSc*Pw}sc+((sfu&)25kRmcXbf? zq)8MbH@|D7^-4G!RVI*aQ(%TsC8LaM&VkMKswPvlBU~VDYrTLlBBp{AM>ZoiJVHA$ zg%XaAv;t(;S`s-1klhaS_|OC~kYo4G4Bu!3nrM=zjPeLe(LYCon-lh$6IPj};}el$ zcUJh{_)z>o;b$?%Hi^GKcZ#YSw8p7Y=<-O@f^JoSyC7NYQtmna($sH~Bs;%Ta!6v-_kF|iI>Jn=k6~CdqmFauBht7`7 zNF0(;w}V~lY9uX8VEB$b*_)Q(UEcHz??Ms`v13#SmD;p5Ye_6G)p1NkF8B?tN3&`p zgxPK2@BdETQed~uGB#PSvS`zPJyzo3wpd@imjbBTR$3RUs8}8{lF6`Av5^Ok|B#`^ z3PTMZRE{N#K;$KUfi$uB&_ z<@!dJ%XJMuIUmchl;177-^TIV46C-k4FPd$Pw&uYdJ^@CFI@A_9u{pxZ1mbbeYwVk=D6q0 zW#4nTUr6`tTbA#Qdy@ti{at3TK9+E%VQyfV8?-@unSdD#on^1vgz)AGXIrHg4)E+- zuoHKQY->9WlRjmz%d1e0ffY|;9OL#MIhKjTJpdB*P=I|VTV`E=hk@3xg3F0OaB26{ zaAu>oeW+y0y?0+|-tGkguIaA)48f`dWlr02y_Y$APW0GN@utyA|BS2m7E6~61`TJ@ zp2kfc0?~RKKhzpcnpKdo`(YNgPrtzZD>oJN*dP5PZ4(h33xN*QwlI*ena?LT@5;|1 zqAeaUABF{9?%Sw^0&RGtXy>YCXf_em`RXTN|2g@S*&B-wkC+s$^?sR0oMa$oK_rq} zeq$EW7}u9vb#ELVO_ytHOukV(T)*A}ybk>Ks|}JM^0plsf$N-i10@e$wsSm>y zARKE-NEkgfL~FQ0od7f(;31{_jgsbC;}6NQaBnP|$2bi6<7cMq&D7kc0ksa@maOr4 zq=$5NrAJ7m#UkJeYvg$gc`Cfg(tP5Jlf(nUa~n@#04y$-*#MLip6+U5ejx5e@5uwU z3o`XNB|b)4eWEm%Qy;8{c748+tWTJSeEUN*L20bQz4*yDT*K3tj173pVQN*-hPNtc z!;j24U-IHv=EqBOuxAE#%z%*M?8f9CBI8_wxl?I5($^j=n`lC%Q*}^8oyGC?S_8d~} z%ekDQ?v2u?g>_!WQN3)Q73e`M*s}xL#8C4rH zUjy)Yljv9-PdKA&lGcyfdHJAsyK7i{HATaZ%WGWYXN#fC=wFsngVpYhpLg148K}6I z^TkZI-OBd0xuRiTpQX^SIGa97uNK`pC~9AYyXitbn5|dj#7D?0b}J8$w#`!$ZZ@L+ zQGdPWVJYl+$-;u4zqe+)+!~g=wPc;_FPa5UJI|zqb%)7bC#u^ON(doO63T zDA8X6w1FyFRNVDV0RemW$D#yyB=Du;IU%c>p6SK;nYlmDxffOP&wqR1JBhXO0D0s2 z`a-5JUw;YY*AEAeeW&n=N);G196>kFV<^dk%-IYhNNgnaSpEwzFtS`q%~rOnmM~%# zyE3fvQR1Wh2Qo3ZswP6fJ?2H)PK;)BUvxsEXSXULe#Ogh5xWN>*eBnvq)@m zz;+%m7uJ~f%OJW~IL&3p4t5sGS(5~D#A~gg)fn)NV2xTRU#2z65`3g?IYW(@`s?dL z5fl&`!``S^ZUjU2_f(F@S#lwsu|5)(V(GBP<;qjJj!Wj6r*h2?FN@TTWldG!Yz!hA z^AgL`tO~b~4fiOokF`F(v!?2|7gYbu1A9RgUqIgT#rAVtS~gqI$6Y^@g8PiMs)CR4 zz?EF}F@lSXip7t+V8GBweOLWEJj>G>pW%RWC7q(8l7H1@{0q*DT$M>PUproImvA8o!QO4YDx1DtmyjyTT)&zh0cM^qIbnDb$R17cJHmJ1$clTp3PT z>|f>WpGmBXl$g9rZGGH)x>&@aVk9$|gJbwg>5CY?RlIax`g2)d9O*3gZ7x?`mNif- zj73JN@S@-MPwzKa6t&kQ?@qpe{+N6row+$r+LvY2>}OwIF1$?mJD;o)zH0Mv{qt0t zDOz8MQ!}uxBOrW>olDGz>@yC~KRN>IVO)5aH#$ryDO5fH z(28uY0Bh%MmvR&1CI4mW@8dmPJzN52 zIY(|jGN1o~B-7>iQ#UT=Mzq$)62~sSBu-dWd&CmonPNX~yE>-$?L&?n7&`N`v#uGh z-!Rcwut%R+tlv-a z7mYrpKhuX_Q2xl$&m71lg2)YUM%Ef+hG@%=b?PHMDhkl2%B@Cg_!8W&0<(#5Xvmem zfN!?iyaR^*sT~c)Mw{EeM@i{0^EH6rR3z_ju{XRM0PEX&9-psgz!>{$5)1)^8EtOWw z_a$Bb^)F}q`M}?=b4KW#My-!!+ZUY?Caq7Nb`h~iEg~-Ur3DcTeLn&<5p znkmFX;v@m5D2-wX+Z5sBsnhCK=}+Og5w^Ti{%831`K+&g&ml)j%An}jN(aHR1mCOY zpU~M3vES4B)lF(b$HGk-MAlB87;c=jcG8-h%Z|`IXtz@~2{XzMNWEm8EVo|G?^1r_ z`N{Vp6Eg|AntDH9-Y;yi_XiB}JSY8mlHL>h(R-cHdw~zPaHT-qBtGho@-?uZk%rE| zt~Gp-9*;YV{;K%J*0vyn`nww-|1pRQUbM^wASKL*vGZUvK68m`EO+?Mwpl;=rXr&3 z=Orpg_$x4;$fzD(!TK&XZ*9#HF5Km9`0PHBuArCT2=Iva0ZeYavRu{Ucn4h^`<1+m zQ~czG$~9sKkiOxyJ>_L&Tufo@!O()i4dMyPIQbd-2JAK{+UgN&pfsi?R_a;nJ{ zbbI3`B5ARh^^K>(f8wmV-|3%CIFOM$)L&hE;DKeKz`HY zsXNKPO{X;q&$HK*KRpDO`ixa7BwdU19H(XnSw? zPx{LQ*}Z2r5~|=Ot)UfWF>^baFw4$^Q*?-dtL`%J+%K;sg#XeFnjn}uXwF1_U{~({#DRxOe*Nm)=2sE;$802 zT^{|@CyB-3f1sk3J@ncLf{p-TELa?B+krU^iP+tyjS=*wPhvh;Pa{b zDMo%9zh|dWYL(vflX*sa6^?{GwN&+_EI)oB}8NZ-05cS>$zg!p;h&-5eppQy)O_3i3;yE&!z({@P{+B&J+uif)G4VeD*QW}hr z;wkxDz-Qh-5xf-PwQy|Oqy?n^IqB)+)Wo>WhgNkNHV*~bw^mx)nBpNQum}hJCF^`v z`j}t%Xxv$P7WA?hb9PH)G7JOc07RR zRdK8*W{LtKUkzVPt#~!I-cB^2gkvdqs)TH-QMp(vQm==`%c^$y3zS(_%gbM(n2BK& z{K3}P&&g42Sq1P%w6+T)*BH)HwG(*AxnARTU%}3TkKL_BudfSc+uJUtjO3wBjQU*fjwqrK`Sw( zclGd$;Bmy4ApP?Z0+j5* zRcY;1V1&LKY_&L5A;2rBO&)f%+~#rZ0T;uaa(EHL3)teQWSHl;z&s~IV0M0`Rg^8b z8@YY@wX@IJml$s?u)osxkxHSJ(9EHikqpB9e?P_k3Ckm`-=AW@J5nRr4xOjyWr#gH zjbiM<_UK~qglyT0Y}mkYu=VJNDy_tsLb<#?>g?H*xW*fD?{p^m1kaEtHtElk<6VC| zjh4SS-Z2%&n9%qh-N7jLGfy*@`#MT|$gxDUCE0=4FlmN6;f!jliFGh$eh8ON1j{)x zT`m`R%7hDNkMMbgEis*}a9a31OZ7v;Z<8S50=sJ!Jw>F>ca+pYmUfBAVNM{n0$b!9 zMC5ckBB#6m1md8`8}z$VHQ`eh|7AC|OohBe6|4-hrd1kS^K0_Wc`Rndg~ z<%3_%Uwf?9NvhhX$OrXYl;eZR`Tv!Y5Up>&^&v|hNI8Y*yvvMMd?d=N9e+62gpKzA#9UW*0WD56Qq;s_ScJKB8Nn6E#-J5k)<`#4-?ij$)~R-e?qD4tFNX!M{&aGOR$dw@qg|!R9XLt zM}an?I(kPoPy_f74cr|UJ(&oNK9Z>LgtYUOB4{Mbi9 ztJHTbgiT^V0h3vtsj_5RsIs)Wo^&Q+ir#%TJUha0KYNMt_d$$$t?yD8xORZQ5Vf;L z{o(Tjk}{<%rPeQym-77RM}PTw{X^0KDb2z@p*>(!+M{0`tC@HYsH4*e$?uR`Cg@;2uejFm-9V%o7=;)Ky zx3L(Z6Qk)*a5IR-a|_={nX6=J(MIT%z4QFBtIlTzWhfQSHp^S)vQwszb0{kw)Pgb5 zCWE1a;-NS|VN7&6gW&*cTDJ^_gG4Sm1;ry$yI+bK`ihDvCE5a0k|*7KYrOO+@z_v( zN|xrWKR}U~U}vXA#p_5`cm=Y8oEX-hB7~GX{)_bXRtpG8yo#}xJL4btA6VZN_ayCI zj{QMkQozl(zPE1h=M1LSZ8m+3B|ZizMm*_Vy>Nh1)fc<}EUKE!p(XV9y`n%^v{cAm@F9c=wed+b4qEy`pLiwMzQ-;+1 zr6hIx>qnmf^U$9Q2c1718=9scozfnz{$)$G$PbM4lBKoC@V;`(x+bp+OBD?8@{;%^JJ#68_F z{b!2IGS#+=Yvk-f)^El5Dn8YLCVAANQtL-Kui7j(wpuSTMH6eyP4L;H^B4&5M#r0i zE7Hna{$w_i@uq1POD^sv4)mxqwDaDj2QI;287QX=p zPsRM<>9P(Z!`wh|m9mwqbZYi>lag7bBC`UrS0jvRTV;5D9C@V9Ms<`Z8*Q{DhdRm# z!*=xW(v-A{w%w(;?=*7m6l0OfbbI`9YYNt_*kYJ++wVSrvO%Hj9+|6kW9_N7K2{E~ z%+>HQFlBGlL%0_FiRUTsn-< zUBry_U-yb;(tmuzUY=avEgR>^PwYQxs6#s%Fuuw7byUq42e&7gfv!sH2o}cE$ZPey zEBU-ne5A&|#r^t^pZ=rt9o2(m&6BD+8m4}fK6GBcJ~W*U*Lw%BleOs+yN5Wt zg9oHc?+<69cuIee)dAy-2H4i=4^5nwgugbhKkQIL1PgOD5fUVOgSTs155Xe#Vyw7d-ven}nI1Fpt5Sh=>d_B12gEGAUROT8 zzKrnsTSXc?nGv7&q+67A|2KC&ncJv=@wYMqHhnI3X-E71EW0<#`WsJmdDEkEHfG(Ww({~L>3}0Eq zJ!7GKDs9)mNPE{ncvQ442QvWvfpx&O$tl|ihFPC~c!D{d_bkf!JjhYo zGA!Er6|F%W*rUB))*8hD={ojLfDHS6ckF4EG<2)!8Ka+!pUsx)q8(b`)uena+M&N1 zA`q*(bnyVwz6r8$bQFGjs!q-xZWPP&V5pUY2`k?mBHMN*Nk!bvV}r+ z+{UW-$3+99MPh&(R1BTPj!T&ALP`BAuiyBQC*#Mf`b&S+St!lg(*k*IYmYZ=bysWH z$bE@P=hZV=WYnj5Bwia!7qe@YZz*-jEPk{LB>@ih>eD^#!#wdCpu4UkKrlXfM7*;t z!9<5+5!Hzid{Fd`+5z_99+?6U;;1!%1P%zv&;fo+d8!$0bCC2hzRQ1?bzi-beW?i) z^VOU_|D{{YL}KMAV@PWKNIl0gozf4+f_kImBmQ6bvZ)sa(tmtE`j5Bu-+{&<6|1n# zRjEu{5k<7{h$O(r>M(JV9hcUm`P`m#N~8zY*#-cAo{nDl8uyoAsR4^EjcwzFPCKy6 zmA5r-e@~RFwgz?R6shBwH;8~{`yF%%4U?q&hXJHa*QcnxRll}BsnefR`aXJgU+mD+ zYdxZd9>$vi^!(kC=m(2%zP`0gUoxX3I1jc@bOh&=*m_a066mEQaVsiq%NrO>erz&J z{S4OmfWIKC#tRseiX%z5PVsXKI%)NreEM83n}L}7>cjNjq(8IW7G|~AtQgx79KXB= z0tM~JpurNMMGB2}o?PkGWA%!OX2tmChgHgPO6d%$_^#Gv5hZVMij!&`Hc$>BKBNUH%xol4tO660XS8I@Mkj8}|Q-M)4q>BcTD6mVk*o?`oFA zr|-CT%}uPzV==gWIeD~#ho^TtkMxdPg87ol-Q|AitRNz!My^mf{*AQc^NpL^+{cW% z@r(7zLWyLL*%o1uqxoF!)Qsy!eb`Azbk04RDl3XARAU*|iK?7HfVQJsTSxSh3rt|u_$IK>a6>Vy# z-pQ_zZPDvIu2w7S(?hL?hec0hfch7G8^{DuwOInjY!&T-Z}6P-7rm~xp4+?P9KE}+ zOFv$?D{BV{!qbdEc2U5)Vp74rb6?sfjlXh8Og#Jqwvb0Vv}gyP)~Qek^yTd6&P4P; zLSN_sJ(0x9bB1Ur2S{G<_PMR8gX(< z+!8F7e2wY}1t2X3eXZ88TUykYc1)@UdeNuda|y|aza}v9C6v2h2dIeEUX^ESEKBcn z%8&`+4p4}Hl-hPGvVMAq4hdOrQB~EC7qk|(;K0H6w9-zkw42C=20Wowfb{M``bH=G zYOTL>K;Ovg2s-X?s06~|xoLePjl~Zjx{&A04J&e+%jhm~2B_TTrictrxxF27$P>PB zfFwJD`D*+Qj+Jb!jdzIT+x8I2n=j!;&34YLjATzu*Koz3kp8jBxV41RR9Y8Iar#fH ze~A6qvG@M=mNpDh?q-0K|A51Ua(WIU+Z?oFRefMKJ&U&7dP)K z|CZlQJM@+v7WYV-S@V)gyL54NT+=`1-M#j;Tkt@wm2;JqRjGx^!gCxL|K(PonCOgc zidCvCq?9=Je5Q1b15em!!N9d{wsK$$qhipfz}u1%T|~iXru2$KNx8Xam-1L ze&Kxw7J1_L^OAZ4Dl*_F!*RXZIDVXrFe^zsOtF z`DVp(@Fo}9iezxPQN5Z-ws#wKp?mA@0%{dd=30PNn<{Pu^KD+V&hNT+;W$|oW-~^_pn2nQl{IarO?QBbv)v#Bnhs@Y$aPDQmV0SUF|F6^eO^WR&SudJyvl?CY=NE-nd z4gwxtF|R53C1Og9jZc3j!|{1_2DA>R6k6jdDi;TcbCCUL113B7E*uVpnikIKPz^bb z33|uc2Pl;4>}wz5pgQ_m2?H&EcBvJotX=QiBcK9Jtk;$4!5PqHY*QVmfx(#+_)AT} z+2VX!2B2k*%`27GjRXOt$;GVxAQ_QtIE!!{!DW(vdvF8;;Jl9DV!3K)X$sbu!Qo~_ zDPVhMaG72)zazM40>#uG3|@k8H|0N=dn5ERjuQ6-wB_PPsg{z8O-hx>}@e1U^mT!W@&z8k7Gd6Zq(#@bQ}Z`@xd|9?1#{~f`QP_ZM3h9ylG z;RQdxjG2&w_W!4ZUJDPdTBOF8zlxH}6=xiU0D~u@(md`^j3?;<*-uUH)u% zXL(0t<7~LhX>BvOx)M)`#HySc*@Ks;_6b7g*XSgxRs*Y=*oPc2tC#C*r5rYMNiNpS z{6}Lk7Ur{l{IkTWaf#7AG^;dH_*)Th_y~+iI+)Am-1UD0=O+2JkB9p+W^3GG%+LgI zgEK&BuPYOYQ=uzarkOtT z7&)yMRA=?XSL?6YSmraF8{g?;mNb|C93W<`A2~@%PoHF!lf)U2gyjb2GTTnl-(jUh z^p~;V^|Ib=sDaoFmR4HWXKBiDn0aLVyx()9Vw_c`w&RXX;^Pc>J|30QdK#FjXd6jR zd7l9xwf{CT${vrv_if8@Ug)nrsP);%YF|d^Y&C-T6XW|cZ^{tjyqD51wr(CpzpMQ= z?JYV67w8!Lhlu=_F_ccu*}Aa^)!pXW^^)R1a+-3#T-y4nkr^SBg@%PLTC{l8FuXf! zFqj#$Q7K0-1G&xzXm5)#&96lL{Y{Y0Q1|xgO04NW6!Eh}`_>Md(5>P=r~$~tj!tMgVdZ`^C!&~m5(yWNS~Yh z84TUAR!oOoyxgJ2GBLR{Cg=!H4$=|cBpDK3-m~R4N|q8?8+c3p$U_}bZGI?cGiOOp z$gNBqP+j8%mZJ^zLF4`pcyWpLhZZ{_Qa`zqg__D3wS=R{YB?@P2%rCQ;YJ^R#C= z9h*n}u>>2Sw|$;)xo-alI9t;= zG4F#_-J-PGIjne=($Vs_&r>VpPKlX?E}TF>Ht>JfUdmf`7oh}b7e3fiDYO%$WECFw z4mWyOa6CxU5Kn8|gSd+w;GeVqO)Z`C5o+(`e*1d6Ha~)Qjo)TVBcyTg+qWnVe$`Ps&D{Re@bh6x{{IPnc2m)GelBLm;{PUo zuCMb+?ppMFBA6$;;6jmt{k2rsBe)11KL^fJ!nkD+Mi$s7gt6q{2;&OgDG_cpt4jt* z-aXtkf;C3PvSt~@`ecDxKUfmI3wV2TmOK1sQL$YyaY{~=^bA!}-``f!-a$%wxm{Ap z;WQ1Rm`wssp!ODtwt4=p<4cQVw2q8O?~w0 zc~FFDlc`2pODYtf@;}OA7ulkh8^JuIZXBkKlwQ8dP%{Tc|Il}hwGoQJRyG;k)aZ(A za2QL-tgl_Ku9KrDt7fPaJhAJ;Dz%^IDqrHMunnR+*XH4@X zXy9VL3xWfxxo9G`Q6s6Eqe1o2rdh_anh7+PmH3VbQ%~#{sw_$34b%j$sj*WjRpm4} zJ#FU{7q!%BgMry(*xAHIEj>+=x1C%>Ej>+=?RFBeMn@>M=%ab)qaP`Kly|5x=8hDkU#qv41hC;wAlqjLdeI8=?!n8-B*e z2!x`}9~8!z<~M$jjhmDn*l0}4c0gRj<#1|_ejI=0O6&D=mCsGIb~C$ROYwKro zV!K{0c*lVU#Ov)o@p@}uZBT^=HCALBKLERUv*0|>>?=rkjQV^?9wowCtxra&?@>mu z+r&vh|ClFptqc2Jp&-PVL7*7nr(}!iCyeRT$5^mcHul`%jmVzh|KioyZ>36tquis4 zZ78f~DV@KnK}vj|abt2zEc?${ME!wRP|MPNi}mnk348`3!EoaT9%BWYuGUApfSc_x z%Qw!&iMx7sr5S)wlb1@<=138)@zF^F8>NRN9f6~5%WZE2${7Z7p)FO^&;{EzGKFr= zrL427t!fUe?yf}|Es-U++P3Q zXgzw1Dofwmt4ukP^Nrs64vbQj4--RXjx|RJ zmFCaK(_|Uu;pG6NNid8Mz&PT`0O7rmrSf5|K!Vdi?t(oaRl7IfOLPHlzWQOmvJD})n@ zmLOoYEoV(LuXkUWeHC=_=r4s1WrB{EER?=GwnX?Av zZ#Z|LpU*5EmLUF7RO~<$uZ(hJOM9@aI43_>DyAPRAac}hPIJ^=*MgThm93E3=8RE^ zYq`iXuTPL#+It-LOE_k8Eaf}L;pcmAR{X4u2)~Pa+U$Fahe--;%cl*A zJ8sdBXkwnjm03>E_KEVlE-ya%1bamFTNaDzfiB!=WeQ#Lnp`oqs zoyjYinWWEpDeI*Ec07Nxn;`4(Sfh9x#SS1XTHhPWqnF}Yl+@e$o-gagCnSxJG+!Z2 z{YjT=6g;(wr1~+{YwAx%pw^1>6$CQR>1HcNN;rJGf4 zdSJU5*xnInaXnswhx#Tnzz8owhaf<+tS_990hhf^ZE9~*XWI4{mD3;qTq$6I(>p44 z_qTneC=zX}RO4VDJ^J|%J#^C6Ho$FJFhq!*=1-HS0QRjb6i1d)KDkPJq3YN;aDIdas-vD83AM>`3K&PA$h!br#wAAD52P#yABY#W!jeY6AciQyq*-zqOtxl!~|0kK= zF(>b-M%a24DwKZI!EFF`J3q%d3k)VWRvf63Y2_%({W7|S^8GTfGJ0jVzLVKENe6{$ zlu-m6Rdh!$OL0ex8$OE<`?a(^lh~u`LzQ!9TThQn=FWbq^!)qpPO|qefv}WH6a7FG z2Z?|^5j`oV>`uBVs+;hMk;Q;y&LkYKk6RwqJsGGu_bC|$sJgv?qykrGt9vL%ILqab zCSS7PIF#ia>5lk%fKzEF>h4-35di~4~S6kHz=T+PGE>YUnN`Z)Z0x7K)% zd?0X8^2Z~XjyYsK>5OUl^q5WfuOm|W^_U})vZ?%ClOSJ2LMdmHOZRg85L7HNx|#CO zoH@5T(WNBIL08zT&%aHoB;i!7A6NI2uxh^=Q0>Mtbn%eV?s7rEF>dk}bQf7GE;5F>yQ_$;wtJ3r zQ?`}rw@OfP;y~ZzY$H6Tzg}fk{ebpEUzBchlQ;f+iY09Sba6NzOPlA4A2ce20FdXK z<%uLjS@f7!KeA%UlxET9#Yn~PdA3pZ3?tl9!OLcKp6Pi($hl7Q&+5_qHy+Xa-|}ni z`Te-lDGXr#SQ*H%O&>SFDJ(ds=^=8NZbA%5YPEw6@)|RRzZ>8c-WgwB4)U;Eg`l z8~Tdu1>lZx>u1KQu_u?KJG3J)7OBuA^xBSK8~sWP6=Yc~w~lX{?Q_)rXRem1-+FvD<4r`emhT2sj~a zRhmD;oDD?Yo6-X55P8sV06yMp+C35_tzfs-Af3US(3!qi0X>VJ(;7u>2Z!$j4ycmm z&IDesN9y0FzGI>Z?~3m;r5c10N_W5p1+5MvB<1n{oF8_XzPCgJclg0C{Ll$M%wWn& zGsc)s9+!7tWq_~pkl6^-Xe8@w6qAi`X) zev20mX7ak}Gvgd`>s~3?>SfHY{QBY9)|t2S29qG}%@Ag}#T)+>@!Zu$f`&EnSt-gK zW=HeE^MAguWxD#_gx&7;qK|GLIVFlH@70flKcsIS(+>bqxaM)*GAGa$Yf-ifo->9P zSSuFlp55+cyFID_8t>*o{9OHTVnV+4PpedsosX-zCN(WpT&mp6ciNX3Y@$;5juy6q z92@=)8LaT{MyXp00FPS=0S|tsl~bY_5uK-02}CxxUDoTfkf$R+Pglr#%M{-#)n4%> z!?Q7k$$$9VH2Yo?`iXV1l1QX9W_oo`KJz3nd1SI`i4aLTNLwS)E`-0G_>cYP3r9qs z1nSF5(q+#4Uk#aE0`7>Qz6SYY9BOw=j`A(YxlvRJ=Bm1TkVNMeW| zyLsK$tV^*oh_9DSF4*Zf*ao*s>&z;rPG+Ei9h(omE+gQ2(J~z?WJjRE))||6sHDDN zonCccQ~3kVWF<9IVaI0+@VRK1>yMP6c5g66^WG>tBQupdKXPU&TTw6ta=<`;@6&3t zNG5O)DXmsKT*dQUYy1h1#7~q8C&W=5=#D`PRI^bH_j!0HF{W?gB0709|6Rs(77&M# zpM(2Bel9gyd-Om9@vh{yKvgrU{yAF=lVYILTZMAdowrU)z5TX)j@|AN5>w$8$sIb* zx9Q0HeRSL)bZif_ac83>1E9jJY7puy_|TzFLuPoc5ok)IjP!zZ%82a|T)%L)Ab5n% z=Q4VSH)}=asfbMjkUUab`UWGzqe3a;jHmbyMJSadnpK&Yna$AVP&OaRa@%FSbe7Fm zf~+AoPD`CF$OQUJ>;{y&Xpwuf`*uz8wSLi&By&?>x9nabR9MibKAB+iGka2)dwag# zdgp~N)PMYN=YO_l?a&%!=7cWM;xV#_U@be0x2w!%9ytk&wX{|VDGFP*2`@E2#O#_z zg(LN<7F0FnfhP9xzrLyqm_-?STXE*+bGw?SWT$a)jqJ*u!jr zuGt_S^X)%=%1}IC*Oz&3=qUCDwtMj@0_qc8kzRA)WOvgo}z5(cKjH zY?W8xp1jj@&-Ntu$m?eLR zACG?aoX@j%giD34-q>RlFIwcX;~sQ~kv_pVhtNKU$vNnpfg{5xp0zx75~T2MeTqi{ z9RDNx!x7Wkh^;3f+BEXMyY%Z4iu+x%HH0xd-Y~rD=X_-nb#6TLCX}Mk(#fp4q}OK zO>};rwa*MQfSP@O_xt@ne!qcz_E~4Y*WP>WwfA0oZF{nwc16x%Aq;bkk+tXPMNa3Iy$LOu^01dap0+ zbm^-rWL=c4FAP470z1`VHlg1$;Zto8RVFfmb4>?XnAV9ksqtU{nshf!f&bH$6qGo$*?Ab4Dv3&GIr1eBrBtnjSd z3>WU`x5|Z=`$sZb7t8$;oMXnA-h|uSxpM=J2ODWPkXsI$pJFD0Ns6qG2xm51sv}yM z!1_f?bwmNP$-$$JNY+Or#}{Icv|V33-+X=x zYKpGYeuZlr8*SOJ^3PflMp8RauF858hcX6Y~RcqKR~WO zFUX0x)mCjI*%=RQ26}j+6-onbW8%ETY+a`MC_F>*jgC%hG0nq#9kt_D+FO|r=F8zC zDsATU%?X?oE-;A)Y}8@nEKGxw?4$k#WdxQ)#7UBKmN4w0`f}!umlH!tKPpn-GrwNv zl>!FBcJgO66xMhWhEfUf8>Tg4s%zQPJ>P0}UL5_G?N4~kC*J~!IQxTGRE-lK-j3^( za0E@XY{iz>l8-@J>}Fk@JxJ$B&}D*aw~yAvh!^(Mt>Gm8#97D6i`@drA6!Z<9}X~*G)>PgT#JV|fcqQ`pMF9u^cW6_!HZvu zG0=!DPS%trdkb~Qiy@hXXp_>WVT@qu{G}Q1G1VMUlYainp7PpGwi7p{qw!5gi$?xH zMx4|NnPU7w&7+RN^9&NIR8x-5;elE_IC>B)yfbV$&%9)QG($ni|?$3 zRmCp)BBoqEP!f;EsS8(G)6qtEJErD2#!MI4$r9I^z|-px$ibd233H<+1IIonN<_Al z+4N?`8!`6s2^RW{!xLoThkNt#pf^2W78^8h=x91r5kDd>!nw0_9QMblC)po@$Ppt* zHx?t#{pZaN#~Im4(rZzu=Q8tv%gm47VuWCCB~pnO{Lu(&9a!7JDY2Ql&!?u@Q_@Sv zDl^1se5sE(NvJJ^n|x5qj+1Xgg(?ItecM3AB`5 z7YA8BZJi1ah=gl4BI6vB(!eP!92cbGRLPnNsuIHq0;~QFj54_z(3XH? z?W0Q@Ad|?7gcE<`9~mdbMUp2mF`MZ8Y9wvDdXKK;bp%0%#HkSPOy`i1s>Do0jY^^k zCiOc<%5+I{op$pR7m!97B0rSd6tjI%VR0E9ZXXkdV_fDWkemja+`NY41NJdt_LQDD3}3PXg4$F-UBcJm7=2wF zPUf~;vvAIv+_cBwMDcKEa2}QHx<3Ti<-5L-A<7MjJ3x|JFFBx#i{)o(DBaaF)V|0d z%U`!fg0pe7VI}P3*}*uymg=gX{U|ZO^#GB(y4O6sAX|?W-Zc9dIVI~*xF03aK=-56 zwX#DF&n+oGN=rxjieXiOfp zp{-XjiTs>q*C|1TpS0#$1^ z(D8G0mN7w0)sMSBm8>JJt8pN7qHH7cho7$aUZjiT z9EpN7?BY#l91iyix{?5Vl-oA$f?tkoyOa^s#v`07^fv;FpEXmI-j&m!+V<3xaC|fo zQpz|-8ioT?AY+EQDA%#p#DvPk_{IkAa>tm|Qj-={PDhHL%AFXFy1y3({JpzF0wE{d zIl#FO?zSo+$x?4y6oYP)3@T*N)pe57qy!;g?QDMSBE|sk_GMiZFIY&kWMVwV0h+MY z+R=LL0LLVN^gOqkDueY{bAUZ1QdEeegj%{48&}+${cP7JnN_xHeJrPLPr?_X<=uw* z&w$gNsQ=^cpv|bi5#Rp=^?z2Nc9w^B_Fts_DwLO~{|y$=ZK(f)-ET$x^$6O6^rQF4 z{f1UYsptCPvNJdJe+d-oPf-8P8V~hf#HjzTF6#fgoCb9i>90uXKTrMX>dEHR|9SKy zFX|r+>R*{SnxD-(5=SS`7QBf5A{5k%=&x*)AHjWX#kZsP%Rq=5>3uHId)+e+AR;36 zoe6V^hQTg!Pj_Kz@ku844^kfmm7d&4rD^mR$0UJDPmsln5kCQ` zjd~c(AY`;iz`(%RD2xmbSZMXm#y2Slgfq~+ukdW(RRiu5F0>3B*p0Jgpy>i85|kZ@jl4PYF_ku*nW(Kyt7 z9=eG;`k@qqa^22dCd877ZCW7;tammEk0Y1T%cPeiZ5dH_{wLnObd1;;> zDsQGI1GIn9B>pDl-Mp`{yv2v^uDtd!$&%)d@k0Y%hzp<7-0=%i*xklY8uZ-ZJA1;P z`reg4;A|--VXwm2TxJ0X`O`23F4MSJLF?B{Vf+*gW+%JQvxP8oTYE3YbjC4x81(T2 ztbbpoRcrLMzKx_?=FjEj&&E|{S^lUemX8x4WfkvQ!>#h1Ptz)itdiiK&>;g0gaSR( z*loxT)6f#2Js<({tOyVkg60pm5O#%A0**W!hm42;E@9MBV26_pffZaC&U9cJxY_({ z9|9g32N}w2Ov;b2NnfEq))GOrAWKy@elUhBXm=fqGdpFLm`XJ`dx1vrx`A@iIB~7V ziR(^PD#{4`L}=Pad`aUcN*&QL!Ew|DAnt0!jo>s#*ec(91boWsT!)$GiMue@W1{vB z!55N8BdTq`tDs;uYHtxO141C~ONv|f;+FE03UKFb?48O_D8x{vah8!6EPd9I^dteC z_#+Cc#fr=ujOX#vINMs*IBV8JDMf2(#cQ;RNx>0BWT?Jf@-t2EPf5m- z67-idk@bEoIg3kT(HyAzteoyAo%V=KAL~-=3i~MRW{@gT=C!#h(Yw?m+DuBpOW6Dt z_oZOb51StD`=4-~JJAyC!ToxWUcKbafq!v37T~fSaeA>*)^B@VXFTeruTXt-)I65- z+fHq&-!^36b@KqyZ+p!g3H`P!3&Kga?S;Bsq}%qoF5yDdVH~-7rGmdex@|W=Y{| zM@YD17v^`qO!|es*#nZ$xNX;*mRNLAG-{`KV%21Pa&;xH3=#v#P9%Az!f;H(sH~J4eB>T+i}Vw+#7d3(m{ubbDBK_1$3(uQh|yRL zYSdwg`gyf?l?jmFSJV5mjrumFt;sajH@l(seD${*aibZh!B9rWSP->+0Q3T67k`;{I=ahlTt zAJiwVY-)DDQXb-CE3^NZ`;|T}x%2%=FZ_Xfm)*{qDs?pJC`kMnMi z??oZ*+oxJyXf<|Z3xqdI)g87OX4@d(9WdLdt)y}P!|VPktWh!wEX&`5pCb7PNE=Aw zitmiys6TKo?{y3O{vFJ(ICdWBiElG3h%}vMGxMjB-Uq8Lyl~=}3LFwiMocP>@ zb)(3Vy@m5=Ka?FJim+#DN`5szVCQU3--0I|Y23aN_c?PM?s|=L8C2?rq|> z6n-5GPs>h!g)5_M4(%Z9VS!ju+*BNN$;EV>w4zISx83}8F(Ia5B1D=6oF>TuH9{M3 z&TnYC;zz-t+S4lma~zAD3b14dI=(~OmQwMQ0jacWmI{`YAsPUUVE$Nfz#Chf6*0Kc zzI!9%=^6nyknG?_5*?IBhah1x-3rR&jfIWXbw+TG5cP$c$_4uwgV#t# zQFX2vA|y~j*)>!C*dicv$S>uoT9h$(j}~b*aOIM75mb&bZE(C zk$tdh*O~8YT;Fo!IK|U@PTS*_4ctAitr)k?c7qoW&X;h6VQ=waQM=62w$4{PFTZ-9 zUokmUlvgwWGa3MDmmym`Aw7was}|R?IHj^Aw4@5_1>TeIVkmH@Q{w_eC^W>ldnr)e zk6UIk4R|C=uYz?)l-PA$fH<&Q|6(O8IgY2e>d|dMz5zYaKESR<%NUg8xu|b3W+NKM zSdH{7XlpYyjm&eKq(+Jch6lnXQ3)7wzrss6y_}R&p!&5z1BZH}{x@vPdT|=EyO(@L z6{-Y%^0Ik!(>{53k`@$wQ%Q@Wdx>T=OOe`7(V0@J{iR_m$tn(8LDsm?%UiGtwGr=^ zx6o=M=z~2|fJE$Rv9A30&QaA-`?^)P+c#MM0BYrs?GQBP=E2WqfSlM9lQ}D1II0`5 z3ezR>hUvuUHl$6uz6Prp*L0uPmj9~A`t>Qa=ki|z=UhtfcJ3?Z@k^?KH@~8=y3cn| zhi`XO_xbu>8@6B<`tzl}WQ+SUL~|Twa--waj>Qf6ueEMfw;9VoiLe8-i?UVztMKnh_`Eq1k1(BX^}*0M+|qE2(&Q>OU{8@7F|e9C z26{ld?KGvUEuHLi#5?o9sGAD0{3cPm%iA>m0SNqGiT?{L0(QGrbgc-s&h{#vt}?RY z@RO3E?sn9A(C=)9ZVvdl8d2gKBR&^8i z-Cy-9@D(CU6XJZv)%pwQZD`NMG_Fw7i}eU zvJSe@inmOGZt*_Rg*ut%CiY z&*Sm=^~TS~-Jj{;<7|~=m0dtKD6XH#wfN;79L#<~|4sHwq{LOAJ49`7s8RU-RAAxukBKV~Vq@6a9o5VolbC zx%8SECR{l!)|6jYhzG>)HlBDg=))_g%WwFI)#bmd%B9Zy7V&DiQ^g;&2Za&1jjx(h zm42m$KkZ21o&3S91IrK%T&^7AFNnJw69S#+q5L67oWfD8AD7Uc+xSx0$QQ4ib`%dH zNZe9>Re@V)_-aScHq;;2t=dr>LoU=9gbQdaN+HZv<_9fsSvy|BXp7~{Zrjno_MGOw zMkBNS-p|}d_t_TA*m{zX6o^~}6=X|YN4(O28MPy>FB>>F()?ccaye9F^SkK675M<0 zo8A(B#Bbcl8Cmor!_9V}V>>P|X@ZUNvz^Otsu)iA8~jZ|L><2+tDI2CSBqcsHRZo6 zvVI^`0Abs^)s$aF3FjP4?*{Rj&W^qsM=?)j{DR@4)=}Jt;i8;yQQMi{?$FA(*slAm zI5KO%bh&}MGwQODU661+U3(piBRsh7bt?9KTBF_Dp27$bVtb06kyzZ&Y=~&a7o%;+ z&x;J+r?%5hB*Z%Je90@U(ij;c*M$0oLVth;h_ZV961G}ggMW4I4p z1B+MGhyjP7@N8NO3*+8%uToD5ub>7xDRVNSaW~M&3fx#agxzM+)4i5JMPzLhs?bjp zo_$!`%i2OHh1P~GS85zfc>+rIuIsBC`L(UQq39av3!NV*QG$;ugB(jcAd!>n`c-5s zqun`TT)fS#zLEQ_zTfKG?fl;E`=5#9&-nfSVtTjB^X{~aIy)8)Gzo^SX4e>#8v zr>6IR@&7+Vqwmzd+{0`Wd2Tyhiyn`Yco4|3jKMI+q`UQ*=V&0i3+e7^aC1=a1bOWa znf!TAm{3S#yJJ&jp1I4|@|43lso?;Pr~=r){}x$shVdZs{I&+li;o%crwAk z9=Mobl?N^(_!2Zby6|<8;GaCOflEaBJ+PVJZ60_H!Jm5IBLu(gf%QD#S3Gbg!6hDe zJHZwY+(59&14k+VPxQd41RFi@a)SGL;EM!z^}q%t;9w72NU+KSR}*{*3sbK0hYNsz z^1u@aKH!0`61>d=_f-M@)B|S{{I&;PLGUXccss!*9{3``77x5!4cO#?w-P+j1J@I5 z^uVD$fctsiB!atoU^BtN9(X0eDi6G$;7eF6aFt)90sNB(?o03i4?LaVZ60_j!Jm5I z?F7H=fiDvLiU$t&1zh5RClGA$z*d4y9(XOm6FqPZ!A1`(_yO+cf#V78>VeG!2YcXk z1gkvo8GS{>cL; z5q!V{FCciE2VO()ryh7e!EbwDP7nAM51c@7i3iRj*y4d#5Nz_m`ar-FJ#YfSMh~1v za6b>cg5a(mIPxCA!5%o3V3h~vS_8fW7D_I^vugvEZ=GFFvF9e-3F7Qp#-1_wk}{i@ zet}gp0aSuH%B6PB}l{tNfEoRc4D$S+5 z9HMx0d?*GBUx5{)5%9lL(&8CiR>1@yq|aFBr4+hic2MRT6j~aGoS{77kb0Ry`p}Xw z5$hLNMocJKp!*v&cK3GI3Chff4Mm3 zz$znji8-AcNMxh-EFPI*Chhgn&Vdh_ zJETa-a=y%Zd;dwZ6_f0{aOE7LaUPV?m_PVxog#I2`O-N1ORFdWX&RpPtI`s(A|(qf zsGN$FD)Nb=(4wZ2fEZwJ4qc}!rNJG0_`F0{>`a;B%D=s`uAlwkGf<&N$8Dhw>I`_l zDN`!!CQ0&@gZHs6{DngV_ag##C_2hp?>bdFW!luoc2I4Q99xQX7!g2^uJk?Zozf8` zR%-^Pc8;gzm=OOmO9;52AiV3N_g*^EqFW?-L8Z^Y}5*d+n7|AKRjLweFoe$3cid z=&`*+*_G=1w%fOMk#89YFGAH23YpM&>?eM zqW9J%v+U`SJuQ4{nq_Z0vKRCM9(q^aw8&d44!t~w#M+N_T*vwsaKj#g;#^?k~wR;=TvRv=Z|LJeo}sZ@o*FE2e$eJ=^R39u(4 z^eKaUNvsU`VQ(xFuT$!3*YOY|w4bpK?usCsSs*wC`~27$Q9;d)WroyI&c^-Fw{vf1 zIDARJp>Gm>&}H@|@R$smd-N%*i?r=(3_(ou~&R@pm8oW=rQZt3Mc_R*VBSi|m9g)bNYm(SU+nFGn=_2$ zevbb)_JYvIcAJHgU6%W$myxP{d+ctD&lh9iSl0?){Q?3RmNU|amXJ6%;tkKk}fk03<_XvR6<92T0xD840U$G&X zo)T1UK*@(-(}amhf<)@a7^DD%BnGNNkREhSS8OPap+Lp-1geV8gn?R(pWW6OydCMH znpF0OHQ#odt(5+O=7F8toeFHWl#q})b`YgEQSHczSh5t7>7t{Qp07Z3!709JrRS#$ z==o(Ko*7GDS!fav);~T)(4reRy<#_0+!tmHp+TWPKnznr+G}T^3t;~~I9tX_^cQkx(pb7Ns#G+P zb)wJ>awi%Zf;;Y?Ft?iLZ~H<*6dWeX6@AJoGH*m-P-Dpvwo@jN4%Gog56&!@izCpu zb59B7@rgl$4XT&gnL7^dLROo>_mZU>%*FqeH*2O5MFUNg`l4Hlk@oq56v^HslBe8h z>pB_r&sB_1`Dz0u$N#2B?1h4Mli1;GKl^Ui!1|r7Qi#2fUyFf@{Faeldi;@JnQ?MG zGgZ+>F5St5BJH5?5L4AVV0s)MpE79`wQY2q=xtE{D>*G!?ZyTdU7!ju*30K_86H3& zF{JyU08UGgsCHWPPK!1r^WJ{MbRZgOJX$40mOP&ass zzFaBCoqe3H_$7R>Z5L!$1oyad8uJt6i_@F`61_u*ohsuQabi7D`G@OgtEb*q8Cz+Y zUABXfr=G|y^_f^kA;Ia^_Cm1QlPC%r0Chy_vLNhokInQIxH9?RG5T-IHHlfh z5@4-(UR4OoZsgv|X14HF9&CqE^VR}_8Of?rZD94lbF0YlEi<4hRVJR4@hVj|a|L}> z6}sML3zO%RaaGm@fT@OLSRQJ`(JGv?oLqjKv$Ju7;LLEaP79vS_u=E{ zoej47A!~}vL*5mDB#Y06=fH*RsCZX_c4F@SF3BU|;|uafJ@_g>X#C{G0LItSYQysk z+OUjSV0w0V?=MR4XSI+Y4pB}B3(KDeHACXA&4`h6XgMg@AF;>wyDucz#ow>&#bz}I z&erx;*cQb$Cp+Jb6m6Zt1OYJ}K&g8A~ zSC3FgC6sdDoj=M69cgHuL#7|OW&TK0a5HS|ME+XhWAvh1r}roEL3-fsD!uIgOnNV% zXWt?{1BTr&)V3_F2YhK<^-dB{gio64kga#s5|hS*jQG%7#Ru_tiZGlaBvEw|mN*%; z184Q(^>~m;q9#IWs}MDwkO9RLdPy^b@*s?o8NLAPChRF;)eS>-kawnK`I1cgdRfA$ zx_HMIF$iE^5oU#F`^#ZV$=VcFOjdT-0#OLtFbE!SZ#jy_8Y49m+k+&9q9L`!5RKAW z(vT&+cmmm_79X<$Az;U$^OKzfi`q7|^H8RD+pASg|2Uq5zMXKm^li~0Zf=o2%FCE zIho;;eDt+a<`U!#QDInyO2K)%aeAxvz4AZP-nw`t)5!DD`H2`%m<2e$G$Cv>glUpw zN@GBF4cuXd_b^vaj&$LjC=Jl(LPHR%0TdZ9RcI(f3zYxDPdI9VWO6Y)C zDlE{YaS;xzCIW4Kzk62-_2**!|3{3o# zq^B`b4zYvYR8%oO6~{xUe#A_TSf~{r(pL><6su}DBV_f?Ru&%!ZOy!S-AS6NWFcqy zT&aQbHwt<$JS9Z-P(%#a;rR>hbmVMSMuysR`e( z#^0BAOTqXRSG;rUJ|D;On`56s3c124bWx529^vra3I zpJ7m^Ekb#FeLOU%V1?N}P7uEZ=?ro_$zj` zR5y`?W4L}2M5||CU`GjRo%lqW3OR$$HW5c~%YRfdNle@IR{1@?=sNO62%cU!!A{wA z+qb8V-&bCzcu4o=e4n_Dj7uLTjti zi#2ukO&V{j(2tj%gFXmQT5wW^9vGQKqo(!>7>dn=Cb7bi=`5F6?Nr!qpmu)XuZ+v2 zEtE;p*v5d`9@qxJF`H1%4i&W}2yKT5F8D0eO%RE4;Z*J2-p!52KqEi_ErS9Crt-;{$7hl(y(Oa*xu!!@`Rdmp}p;u5i(A8}DO zZj;D9f{N|Rp_=Q4dLLkq=NyUk<)_e+Ooe-mkjPI4BxQKhyRXMVQqX%4# z#ioN}B(E#RjE%F#`EiB6y6!aq!+@LOa(@@=x@`e=LePqJpFeW7=rT&LKn5;HRpTik z8@RwC8}`{=_1FbMkW0GC8z*6ZtE;?#tQPEv&d}|@f)6(Ud>09pY_|)z6o4;Rh*3M6 zp-ZXF&`m;Id6yr`(1m{s1ONLkQ3O3GU~gQRaT#8(QPlWwDKH9XD>xU7ZwQ*V%ZWG; z&my$uyj{3tBj+YEwUZ+4M^%PgBHi#Cy2@*WK=`UW$5TqW%1g*@!JbIUCQG&8yJ$*? z<)VOP@*HInsZauU%w&@cdS&kp0gopVs0#u`bX%A`2X!snu|qt18PcR`n{?VO?e?i_8XFjCZbzK5>> z{pnKY<->gDf^O4g{%DuqtZ@T4U1@i)_ee*F@VFa(Q++M*`)Hb4^zI-iNX!(VMi%b^ zX5X#q*oArW*U8X^g-aQQZETuzyq+(&T{~-T-EE6Azu7r2B4<{cOYJPWijwZF*JS7p zj*g|L5KBE9^vJtcUy*w?niS}1PFc}8>LAp=i>`QjH}%z=9Z{FXxG?d%#=d?uuXr^l>IJMf@m9(wO!aLCrU zXshKc?%e2$Zhoa2gN&y9xDpxBGyS7s2z6p>h}DzMQ;cXk`>x4mF*QMB6ChqI0fJ`~$_TtE?kT zeNWYmj(nXu@=;yoP3p+|s3YHC$s_NSBFj#}pUZ8NoOKig%uH7=Gb)FSN;aQg;duVG$6y+ORu z?NYaMQQvksV7sB$Em6~y{xfZ?c*8Nj9_|0ifi4G2g;-pQRGQF9Z@mV#=PmR%gw(@Z z4_xiox>Vb`RKQz8YaHhr-GeV3-I^~==m8+1wSvAV@I`?y01{d&>5CFy5Md7h39X?Q zadZy>U!d5x2Y`guD*B?r7Ztt$NNBC5FKT>I;|qX<);{#b2VZ>f#i#Z;OvzjHUn(Dl zNNAw*BqOAitg3MTAeXph?N2qo=1&lu-xlHUb-anAs0dPEmv^+cFR(Y5G< z17)s7AFt&OT3ca@6ipU=l2bA-zGmbmv8U7t_eM8e@R9jdjkw!BN@E98i9<5mdkoX! za-bkASe=(fUj&Q$nSiAlD5o65DC)rP=NardT4DZt zBF{;34zE-ip^51!ZEYa@mt3wKp$8E?jg)9tjMk~@`ZPOc(@X^*XfGW-qbragE+8Lf zw)==D?D`aNecJKTukW(r>OKn%BYmDxM5svl!5T`W9!H**Y{nY}7c2R1FdUZG&Gl|D z;33}(9}E4RC^58mVu6*Z@1(o(1gY#F(C0=CF^rG9&yr=muGB5b2Y-#|1beJ~4h{}Y z#Co1!TS!_shr$KI5jdzEPJR|Q)EZzD22gIUF0ipOCgS2CzLd_$VmTWJlHo;BWn@<@ zF`vJHKnP*f;X3ejd{=}aqwBmpsboS7uDxHwzUB+Fd)O-yS9z>LzM z5n}=+toRX2HW-{dE0`_TE0aE?^~y{m~d!bCy(;AfitO7B33O59>S59-NK zSG)~t(O@m~rP$V>WkIiF(Gxz$4sbdi$rauPA3LrvmYkH2>Fcq4|H> z&F22y`bQ==xvwI!EB0L`uEAxZjrU&BawUynxUsEH=OF=f{RFp>7K!RwGkv}f9~bK( z_7tcq_Qw=cti)nm9Z4y*u`XQ0S&rA{pjCK+FSFgmU9BPHv)*=G$qw$@;G@dOZauB@ zbAPtH>iQ&o{mf0eY^&IaKsZSb&4DEpzRCwR=Cn@|a34(xbqDV@jrQEy)1FQ6-g3=Q zSNt$V(`3oeSmV+;fYDD?2DLt5EXu%>lo3y|g~}^dhUC8CsRp{?wuh%0hkXnT)({lja4 zInd!7Xva(m4V2lUJ=R`#)}F?j2Z6u}AzxY#HF{zd;Uy8UZ3w3;nSvJ=R^|(!{w1&7 z5i6ut`Bqp-p=i9Z;zEfC-s%=e@nnth^WOMLLCHM$5vOXzG1|&RAZI?1qy9H?$LXFU zWphA|rKbMom=JTA9oL%ls{ukCg&`1lyKlA=CygUdfkSW}T*uez)^)Z(j-ZAV?wF;& zOV^!sb3MA2Ah3faN~g1_rX?(@Gi6Iq-_1~6&6a|^6fRY|6)v4yH9b;R(KHDmaQCl| zoRs;MMtZ0F%($L}Ur87fwMKPT3-*ZB*>U2N{E?^HgLuQOSm*~;#tmp?)V|eJ+?S1; zk|A<{tGS%XdQbuT#0_X3?xsEsmFlIZYcd<|#D(OJoO4od2t?F&z$?B1WB5!G5>g0# zrZsQ6Eebc^-TES@B_N$_@&1eOokngWt_bHcOYtVvG6xKk%^j*`huU}H^|sOWkfW_F zh9}P|Ix<*kJVrM>;WH;)K8|Y?P0gH-k~57N(s$&X=$N<~_dNk&Os|!36BW72xSb-v z#bWSFh!iKT5OKTP!y9D9R>qY~ua$8t!_P-2h(6jU>fw%f*8BpR*d(4RW0}0+vXx>* zl+;?JLKe&geG!LH8{^>&5hCe_lZ`9{6_DwV)I#8q=hHcu#`|tj8~JpZ{+)9Wd9W3o)0O$qqm9gs2txWc%t|&LebP6;*(Cn z9eYO0T=}8+PqYlu=+3k(P`o?co?Hyle}KPF`kMHAdP)y6bb2#LY`xt;SMG_s%&88j z`~;suh{h-mHK)+TFy0wl4_OZA4HJbhiy14LICnk)dUM!Er+L0u6)&n+?F7vLp9hs* z=-K@UvRoi#YY4s$KI{54hCnpB;vys=mPeHhh3{4tUwd2_*#8Hi0;dWZ7@>a+LB@(# zZS11@3TzC6`JrKzawe7Hq4P)Nf-7>`Z`;@ahLOBnlI8>_g5~((+%9`v?&i=uh$@1C{`j79Yk-Rs?0JP{^ zs=|sgvKe=y_>N1}v`^Y@?UPhVf(ELVTPpFW#F*<}2wu1VHdMI=kVLlrQXLJfE~ zNDEJ?5Rc#bzSNWG5^Q{GQh^|k-t9y0Y9CG7hK98bEw#p`Z6k)Y4PR`Tv9_T{+iOt1`?m|;CZ<62MnU5a}SbW>hWD725Iqo|=+PYAW=YV10BIpIVNi;n++ZjR2? z)TLF92;?1;unmXhgg5&#hh5C(;D6CuQlZ- zG`i2q5C`YGT}%-~wbwEPz%hI&G;w*BA>90XNE7}&8Ta!t`GNpy(ju-!dy8JA4?h5x zXe9oDfoGu$ka6e>nla0s&Gk_-OBmw4x!#{x6mO0Y3))RpbcuOE-j~D4^K@AuGm$Q& z&&KDoKwX`|VLWo{7H}LrkF@{~Pf6pKd>`O(smnqd@}P^<^3liU?#xK zgINx<8s-z2jWE?PD-iA|?Dt@AggJxv%P{E_AB>f3{I=lR1lRj}ZRKy7?bW!1Qq7gZ zya=-nrVJ(=<}?h~LCxu5!eL@y@(pS(6?Qhv(=e~XjDf%XuqBuV7%j?H3V)HXAB33< zV}^MS<{g-gFneKw5bkgA_X*7Cc4}@t!VZGn3C0hmK19u(gsFzv0<#|GRTwKw4$Llu zOM=}WCKN^ia}nh_1hWZd5aM3}`#G3Gm<*Wxh&Ks#3`{soD2yLWLmQMAd8&s!8u>T{ z`wZfz-(JKs8TB>_rU8EFcN+1i@%>rY>tI&GY=y~&nF^DNyd)!D`aKkBWHvpqtBXe?c4dYE_ zOP*mOxmn2@W|WM4&^vyNzms3N)MkIZdgYtU`A_sJ$eL@(PBUla=D0k%tOAqyi5cl8 z^W5C@oV5JhbbO}R8_j0?yh9M(X=3=MzsjT=&Z$v1oSlPiI8Ul>&}NR++W@Op9DcQS z)^gU9D$Y7&2wc5>6gU06ZF-MnU+H083`6mv?mW z?9Gb&dHp$`k^kIr+;gA&*9hD0VSjFz=1|_o>XiT7?$&4IKR4{gox%M9wgBUXWp{cS zs1CSf^>C*Di2UTO!oe^vco|^&wk+MqOJboaP@@* z1{2RlTzEa;>Ftf|ozhx1A%Uw#cp7d9=KeL5<32?Eh&%ZM#J_GRSN%ThSCHx$FGds7yD;_yrcMJt`oThZ(FT;rYyFl+*A}nEd#-A8uz<2=nHgJE$ zl$K8Mc{|3ztu@@n=9$oaXL)Cif^w7LPC|@xbLW|+S@P1;%qI4|5Z{UUScY{-Y6y>* za?<7^0&-jqHwYQY`GNPffT<3L=UTGU4LP}H!z`28kVldwWSKJ!xfuovqb!I&6S*|y zQ#>bK;c_fRTjgmW|on)iS%4gZo}~n>%Wb8WkDh%BO?$+ zVj8M6-C)k8jLgh*MMo|yJ}wzyJ0mQkb1YF)zF{8672-SS1Q!_*g@42im?-jib6UQf zFNUk0h62QoVh%QN+)DUq4?i9}pj4);c_zb%i6ag3vvSgN=et_hurxHl{8^T{2+PX- z3&PS`?@-rAV}5>azB@4>CLmMBxw}3J@@J;bP0PxO$S)Y4n{JZPoMSSj7f@Mem<(w% z3Uae8W>bV=Bq%y?ZkUy89%P6{JiS;R-GS0`O$9j*m<=EgM0U&l9E+mNy|2jH(S!Pat$owa+{0r^OKQZ zfFAP%qRm3*L=wnPT8=rZ*SxHJOM#&vH^V$1&5HVJZ={}Y$w`|J?|J#TGff2rrh*7V za<(b00O9hVFyxyslF60zM6M+t5zoxT87?3Py%BMk&8E3|tS*s?oDDRWD@7Q%W0ma$`)p9U;gW>RT0EZL}>klAwIrztP} z3coQnj$H(^!N%+Hh-qeSJ{n16A?O>zQd)Di%3<7L-D#g{mbXiQ4e;-FC&ECmdu(Tv ziK-4Aj*a?;+&oi0Ls73@^Gx{#Adp`DjJg?j9C;u*<4&8k`#}ckAC`i&StiOKH%F#_ zF|gg`RN^xs*+m8N-Fb4m_k>@PPtJrPzqfOL6>h`}l{Sm}cK+XnU!sQ_?$ZB9F8|V9 z`o{@t7XR(i-wJmXOiXrDT-?ro%^$=^`==CN>0QR>3;(SAcj=$@Xvx2Hm;T$j{D;iF zyYlsN`A@n_|1mEAg9+VGwW;KP`k`UWruSv%0X=tSDve_KY4fMeGtI<6HZv_RZDyAF2?KbM{EY0}`3Tp5d=T$*9Cj#dx-^d3 zD|jzPer5WdpO$4dWX;XXHqn5}C=O8|OMVVWQCg0{M5AfTq%-E4ku3LEuFO-={+|JH zcQX`ZGFAq|Xq*AVyCFQQ0P%rG!r+Br+*HWEypC{Cq{S7(CjN`=&L&)?VJO15lbgfF zF;n^=g9-ehInD6kU_+!k7a+qXR(^0lsp(lyOhbXwSa!G*h_4Ic84Uxuow>AopXP|L_0YO+ zH?>wd33QrHn+(%rwo!EMZz&dU|+z1O@LFkyS9QAa|}QyqhOY z4B2-VPl06y;$hkLj>w&#CtUHk;c~##&fM@dyif3gf5$tG(QbDdGorm!z^ zc*C@H;tdBhgIhI&9|BA~xjQ}+VDI$*0yxaeeI8)%{MrCh{^)ni*nY5bcl)2qukXA` z@Yj{ESGB#F_x_RV`x!j;$fmCvKA!KqeEkT6wFl=6J^Mn+SAh+u7#!EIc-aS?s~tTX z&M?^a`oyU4)N%U~8!j^V(;e@8@ci1pyqVU}z~FD}zuh>!1-J2&kZa&swzC*HsDg{389UVZfD%MAWo#Al*F1ad<|YPz78PZh z*md2v*KTfQ@Y=;sRac%I^4B2eP6n%EdvxE5o0cM+`x(6N#e%HAhK0R1(s_izt-j`L z=RbYs#!Tla2G_L@d@)_~)22nvGYsBYnw;yqbJNnlIxjM~r>Wh^$!$O1^Dk!ugQ<;y z8n4(zr&wG+{oDH`$w}NO{bsD)R#*6el2LGs9lkGLEgU$cs1qCAu*?zcv*W2a*zi@oW|Ve%4z1{_2>$2j+5pS!~98UvGQ=17rVQ%EdAGf$LlMNBv{w zum9i@7`*0#?Mq`!N7sGHMiY)ZY_47=9x@vY~vZb!e#zjSz~ zXV$|n2Jq<&KJCbBpZ*(v=m9>P!K3D%ef_U*&HcwP-pt^VISbc6Rp@&@m0!T%`5`|% zvFg->_4D~+25;H+#$zu&{n*nl@=F;!`0-b_ZyVol`&#~G2LEg2Yrm(i@BT^^zk&Bv6aDZ7pCreW0zrPhGHj!kB#s0)P+aeEH6^*XYioN&aWN$bmhr6 z6h|2R>c4(W9lz)MHJ>R?F*q*apR2!L@W%CO#Tf?c6ITCo^yqp2u2WoOaQhYUpPhZ~ zeY;xOz~G+6lityv?Obh8vRz58Tht4+Kdsik5vyby$lROS6|1hlq5ox~k{wUwx?PN& z@x38@ZJv@HLgh+!|MSVI4<%Ub$}mtd4CgjWZ?=uze@Gd};CDOxxcE9-8yVlKS0?Zy#ZkV|1;v3YkoYzdPCx>{uA(>xbHOvhPYT@Hrt zl8XEK^;mbfXz*85HaFZKFy+?`XE%dMAJYgg_a6a2j<6w_wq&^UtilQ`zA;DD&6B&WUk}+vxTH=b(3;sI18?nZ7(+ctK6@D@D zN#lNte1dOJb>;8sK6!m|ST0!n`43ynYyp95Tqj7v33S4@+s;eN$s%40%Rhz_|B~JO zzlQ)c{LmbRe&oM?jc2aX$Y;udu`0>B2DcRFx;w(}A{Z*CJTu6(m}$LpMy@3%9ZP#` z^$1f4%peRi;6$+O5nq5;!~u&t0-NR%ED3lpV1i@GnaO4m=1gREW*SyI%visqbwoL@ z0{oL%`LB4c%izI-TjFT9&K(OiO+~nq1UCx8Kvq0M&u_Jk)R{o{NXkU&Y=4@Jn&l zz*8sKSRVf0`nluVi}-pWJ~teMj!E&m;SPXlZrGy41=(3MO{sHDb7yAequ4bI<#zA_ z{L&h-8+Oyl{Ir}|CIeg0bTuD?tD(s*v@Xe_q_&4{Gz+WlAvnHIb=e*Ey|87Urkr$F z45|1|`!8;HIlSCBSxVRxLF1uTi$DzE79b3j=SA4mH(m4=9SRiJ9W)=ysRW}(W7}^o zR`Rl*G@(r(Om~E7i3@XnDwgNXrhJ;Oai`#~GyDw$ZmB(a!&DA8oC0^6leyu3SfHmd z$_>wK2B*7V?y-o7h{tCYPowEg^9_H+!+XMr(mhWp=mo8Kzn2pmBo1LRpi9SvUq&?wtGw^h1~!V;822sl>!Ozl@Js!`2mOU;R|~)9thpvs zLLU6lYQWbpgj4sDjH{rQ8JK2+I-#)X7I}%cfOCiW2M7k$U%}jhEuR+TG{$uw5OE*A zN5%B*+kZgczL5i>2Sg6+*DtDnRDbun?J1;7ecl~cCc@BI_B-f2(U=zb#byt!xTogj zXJO@MMmA+0{tQJ%t}ARmm{2d6`c{7!j#sKwYPG^gt?}^<^b7TGr)?jgZ>7}h-L?~3cH8;YAsr|$=a zPuLy*h@3RpzI0jW53O_$jlSLx(R)bhL({B1m4pv zs_%e7v4cmBdc62KIIiFL@5--t?LPGrCj_)&u7hHYi6ci%O*a+4@Wz`vcI^%b?1oPx zCq0%rby~W~@xr?ZvTet)Q$N)Q1R6)Co20J88<)Y2tIwfBf0!o69S!z6)&A_OaBlmdp-WO z<&n>K-n)O7bIjOBAM?@Z^bhp@?QBl&fWbqC8JE2K*m4(z;p`r_?Fqd}1^` zR7;$JA)4NSy_8*aU3Au^!jjk8`3IN2tm>_b^-;76@wI++uUTt7*j}r$I#t$V+VgKJ z2ly7H23a>~tly~oLSmJE>KIL;MyodacTzqoO!2kaLPGu8_>LB=&#K>Ft!*nrtrCh3 z-S4ASsjTnli!S-_hVE*(It1%i$_`3h0H@}86i%V?@lj|rz6w8;zoM0(=K~e@s9FaG z@xh9=igp2^s*ak@d>B7ln4|be`LSY?Vz*+CV!!r)??J_PibMQy)d|Hff~5G(P%m6n z{I0ytYafVxaLm}}|Ms`PKV^UE)qi}l@#&A%KEC}2KbUf%dXEqk(tkk8q{Z)j_|cdB zj^Fc)_}t$b8zZ&FF=NrqKKr~wsE@|aKd5d0frHk(Tl2l|fMqYN@$rj&FeB^v7Xx#r zZT{`-qcd=bZ~TPU--zh_K={NrSFU<%^*`5quyNB?wZAsFw`mnIdj&ztx_=D*Y*DJ=$JuCqedr>pO`{zZ~9DA#+-t}g^QnE{oY6G%Bny7C@1%; zm!{tPlu9Y|Qf4Um-VxR%9hFhKP$A5>ld6X*L1@+8`kp#W2ou6Jef`G_FB;(6#!nLx zYaFPYsqu|$qw1pUpyK0WgojkU1wS8OpLoLqg4Vaca*(RMkD&EQ9yuU7AlfHF<5zV5 zBM-)7tu%h>fj)jk z{oDD(DnnCvU7rBk8yOaV>(*yS&kQJz)VF#5y(J@7eX(SqPj?|zeZOC#U%0CEl68-n z9ufxn1jbX7cZhYTM3$?rJ^uL6`=I>(Z4u-gs(ZydfBf)lNX%$ zBz1*JzU{2f6pc`p4ATV{CwEq>tp|Fj9_+&B^-{JM6h-l!0|%-2qU!ETezsoeHd^o# z6t=*G(Sxm9;?%q_QPrWZqNr6*Azho|XZ%O zh(Y1PHTRAwnxO57y!6+!f>&Q3>o;Bfiq*HJ;VxP}4ZHJcAmfoyeWLsJ>pvi7VA_nC z>86Yp;Y_q;AQSx1XXL&2z98*j3ph7C1Mf8EvoXhq!rqPV))YC>T9C=QLA=?e7;+^;w zV&H9pfqOwUoqN3XO75PvhR#~U^v-8{uI$l0($F*ao#Q3=KRlT>>CCAvQ>vv^Qw-d1Q|kG}k8*j~X6*%8Ed~DgB>%`@J#PXf zS15SlKE6|j$NUHR`tl(H?+e1G>Y<^BYMQDRDrzuDEwKhlqDpp zAUISp5czYb(veT%1s;*}8h#wF@X=~!@CsjlpOK0Vh>zz7wBnJX%AXJOpjc6v ziev>i9OS*7LdCzPXn#)tf4`=ke}pm;Wm6~~;D-bA3WXNs>di+ZR)s=^a(7o~_}>U^ zq)=I}*JJCJKgqwM;*=<*5RNsiw-G<5NLD8LM+r~y{dL_?PCsQ7;`HGME5lU0=0RSo z=<5s8!B0~XDuERKOYK1Za2nSOO-;cCGRuIdRY9s)hqqR{|Jcrut zt5VS$uhwxG%sGCDFb?0j2t`{QBvuM4jYi?qNqAYw4G{Wh_*Q%y6|Y0AfhQO z1`EJ|&s-mFy0u=WH8e*dSq?H_SGhx=CF)WmcO7;;%rTgxw1P|n<4-Y@Z??@@W0&_f zhYw1fh@HcNRLHgDKyV@*J2j4k+fo9NO_D!sW=JNHOfkZ{O#x(n1`SHIKo%(hyVE(e#asZ2pXNrXii>K5|{=A#a2?GRNOR8U88f zw;FcYtH9-&plO~@;)@_7>T<6x9?nfK9nR^O!r$|7LmbsG)|U__9dV=$ z=aOH8AC#v_yZdw~$GMDm-P60bKkxT4v|YDp9_Bw+S2UA{=#?qIS5!pbh-j}0TZX(5 zKIxVREEjQ1whwdK>{JptgA5@mRe=6Cniuzjp*A$$jQK7m-w@}*IsxMasEKjm@JI7? z**$?dGtOZWEh0u1PJug`1?P@$4&G^g?BY0NAvYvx0I{J2WNA&;iW2~f1nXL)`A5EJP3g9Nh=X($!IS>SNV^}U53E8C z4lG0h+$MCK9cZG%6eUGnF~ z7$H$I(;#OQMZtPmt|1$={`Q43KaqFa0-Fl4Aj4w2t(V56?&!ngD3`Xea*#T$>0!w} z!<2tpU!Hv2h3Cc`-L2oo^tq7DMvmHF2F9Zvu-)(%c<=88mo*N8eiS;alkabHV^MjQ_Dc# zyy4_8K)>)U=cUf>eHyyA$T?$V>ppvr!58m*)jTl4lEx{Ew9%U`N4X#!;D{tQaV((1=qN>{e z@xvnGj%B7QJn&lrOGH}tBEi&_lO1>! zi@?6ddlO&}z@EM63jpkuITIV6(ZY#!UOJ9IFO}jI(%9I}N2eO>`=R;a2jYDS zyfjB|=cQ8)^;EJ?z(@1X^sbX$HAUjYtrh+>Z%*mx1jx4S-!C$z&ndw28STl&y#Koi zYz)HJdQ0=zx}WTQj0(7kruQY~gYZjz+H?$kRyjQRIKowDPfVQ^eOKfW?;q1Q(8xYd zp4aNf9QS3_aVejRZuu~lf^`M)ErEFWyg0{kv{j5=%(UQRG!zx(V#-^{G!$TVM4FGM z!2>baH^pPktv6p67gi>S?H%8%NO9AT>I(CYW219%(n5T33CslER`6c`r1VTIa8p~A ziWv>PxD}mmt;DJT-l+mM!rQjaV^SkGyOfqYX|Fv1=g1sv|F>Bw6cvk6&(&v^(7Hkp z)=&_CXT-M|7*iO`3_J?Bz8We;kjfK_5Q}(RvO5L5)Rs?C?Y$m>( zU`DoHlNFkgsn=(RgoID$^J_)LSz^z&sI&kFRE+tV)6)wJ(@Vt%vlMYnKpK~$uu(bU zbQx%R=W#j>^gsoDs~oN@t8|`B{f@MsyOG;5xo`TN z&<{(i%JV0)Q24Pb$@V}et>t|y*STM0+frT|QJyi0ijuX#pFd;HTLwAG zo7(_*C9!=fJAf-%HjIbvlYlq?r?;VfqV|l_bcTZ3 zvpp|M?cFX_>A={m)7}d4ZN{= zk!zSvXDg_KhAN^@&&O*|I_HUMRE)_jL0py)CZ-PWABp&$McjeFR3B2|9u52lfcA?t zfi2Vlq>W?ZI==<_sUP?gfcj;WXT0xQ`FpI`HqR)@6`7Ntlium{>}U?9em{a*D)H1F zbwGlGMj|FG-l*pZ{tG?S7D>VOX#$zO&Md*EI=uEN0*SYukc;;Z-UUXZ!X-7*{RFg7 zbYzYguuMfg4D2WCC+<2iWM|{#JW<(s=`-N1ncGjuEX*Va`vq~*e8U+zy)`o3@_DIW zbPX_tcLSz88zB2f0n_|b+=RH$5bR4qPg6d@BA(h)+ybDyAwY-->yOr4PP}ZhxVNRK z#Aq}W;;c2FT*{I1Gcn%&hN6ebCz))cdwZ~y^$z6f1W@)VmDUTS9Ce~pHrPoh&vz%m zv;jzZ6iyh#r{g3H=Tr5Cv-Md{g7Gx%AyS!=^8uC5K~~jakqVSYZ2ax#cw$G6l0Q~= z@$&p~8?!y<+_H*!=$4D;rdvkjt6LrZGjhxKVH(xH3&1tgvnoF?qH8+ z>l*7{ywv?{X@i#)@z6T(-GKg7x0|dVPn)QvHaGbt={b7nQnmsQ4*d$-fOW+0%5-U{3lhJ?$2?_bsMCKiS0l8iV2P-K25SJ07#Th;`)Z z1o6~JF!a;dR^F0~iRXf%d|WkG67c;(+Kx)_uP4hYZ(%b(2-uM=ecixfTeA$_q#_$(2kG=3{)-xeX542si@nGqV z$G)~K-M{;>zDaMl*gXHu1Vy1R_0x8UOg-8j`JB8PJF7rZ`^dR4*T=`GBcyw{dKmD|9?Lb3I^X6 zkehTgR`+$`?K7VL@~F>{5qo?`U%0!??pYgJzWCSb4dHn!-x~JKr|%!op5L_QhrE3u zCDRWl>}vDP!CRugd~J^%#%DHq z_x4}fEbXoRtv~8b~@6+h-9?LRL|MsX~-n8HxSBz2=YkUp=5t+Vl66^tZwQx|K&vhH z2(3Tuxglrry1NZRi#^%zK6^C%e{Ihnx^3mB`)>|hSy{1u_1=%Kjb3-lqBiS;22bDo z&g*&anT5q0e|vk~)+btj|F+K)yWcDxG~(;yZ_L^K^fkQ>_%7~vu;{8*nLpe$IO~z2 zGxOW5o;vf6R=e6BeY&>A!#AyEE#G{m`JM3(x3lhk!GB54X|Ee=KJd%Eb#lg^pU=>n zZjZ?8{IlPzg4td6)L8m{b??+Wz75zp>bn_+!|$Fye{#!%aa-QG_Ui{Lbicgt%{L1k z{_?pUAMA^qaQg7LQ#0QGa>nFMFMa2?A!c;MTO+^i+h$)_4NnKHA}qjo+{B`{umJE^oJf^1dvNME`?Fge)>;Se8i7^ZucQyWSdctagLu`qP^?-}k|LpJ^Um zm-5mJ>w7edsC@5XzrA1FJo!-S)*1V|2i=jqZsVx*9o}7XZhfS$q0hiAt=2#HVC(H& z4mX?n-;>_`b!Y>qe@ySwE&xu`7?9>AIZf+O9ToI_aMS*09)3UEG}qm9YsrR!OtiN8 zygaNW2ZzY3{}`jCVfsyaM<0A$+TTI%ycPgXzXdnFqm|<%nses!cjEbzwDb)0+Z8L+ zxSwOKG@nWNK<%vZoQ0`&7(TTOz^J&;ifXk=|Z?iDrj^O#4;HL#hhmW z{FE~P0Zn}c9=-(bZVHeFjgRI#=)KS7Ho#5w zfz#{ZruhO+Q(DAzEIF?Xps9~@IiA&QjZ}ZGe^aVIlpdNZ;54N}Nr$NDsi3KEq{A7t zs3>t2M~5qWZ1+i^lJZ5HIl0B~rpZu=dAM|;g}zV+c^=2Oxgg9 zB?YCl6=a?wjQZ!)SL3|68A2=*oDKs$LJ^L=aH4J;q`*lWTy-I?1L5`xqA}lGQh?Dq zj;ly3PFa{p#&l3rRCb{uKdl5iRB?=e$YG}&13WYpwH9_3c5RJDyKaB zM$pvG&y&}BaKT4jZa${7#ca`M-8nB;+P5oU^`kg_?wtzRC6J}G#N331yCFjb0T($% zMa?V9$<3z)(vm`b_pH(aLFg+C7SNZb32JnrOA3n8v$2t6EagD%fq> zgK;U1X(;FC3BsLpF_(BL*F4Q!Zue)%RN@KB%bT4abUA8~v=J?1eF84*##Su=JvUD0 zz)f{XSw{paMS?B`k1mPw6w@O4#j#oGMxFkmDg3~&>G_y{Hdh5~4fp@;031RM$I4H!3k_~h6LV-v>?9~Ul(V>wcb8RX;(lR#&zjpmnbMq*?Bnu`(_~i^8k8}sO?bFI;{IE zY1+3+e#Q9U45d*EcQWzF`(r#=pRItbqdhLlc$~$1LXKCXinqbFxL)367UG4Jfm!|j zXd$uhI1gQy@St!p17#n)5_@mN{z>HBIy_5iOD<3RnEIP-z=ml18VoN}Ps(;Qw7<#M zsil&`XA|uEN*+Z+Kyx{Kp9#^_S2E!_(A+=ODdAe|Sppp()0(R#nujNMtV>%};kv8p zhL@?r%dvl28DDaTy0l4-pU0=|R5#psjYQKLwLgH#@o~CB*2ig!%rD6NW|^PU~w-iymXd(x)(ZgSkF7!A2a2DsRp z5eNea1F;tzppC@7Z-8zP!XRsgtPm2%$^jKa;2w>zR0D05O2JMeoR-4;w2p2`z*VN% z*ffT`<$Ja)c!t!6wi_Bn7kbXX(q2}f!DvKo<>Nw4+%}0zccn{5<%Z9G6W#R9cuzw| zCiZd^1Jn4qH84Fx>lE4}f;F+>C3$)FNZ2os*#>9{9+)7;pC)FM-8Q~|;GoQBL8pTH zMoss4T($4^`D9)Xa}c+(o(>1y`?A?%pKD&ZMS9}GgLAeHI`5)eWJ5hhXh+}*YY8>8^G)8Ldd%38z zXu5P`a?x~os-ArUJH|sdy;f6}mXveYewY z#ktUJb?=Aa_r&DO{`~~|zA)U0ffaGOwL!cOsOgHN*k)j$laB^;P~EdUufdKir0rX@ z3zWvOXxxPlq&_{h9W(~PK|e3T{KxO}#pyThC1RVuzt|kv!g!i-JksVz4x4|gy8iqw z1|0OW@#8X;U`+_lpoM9|H4&PDnn=x{U`=pva7b`ya9A+i@h~trGI&skCL}l{BqTH> zEF?T6B4l7lWXPaUO=xgvNN8wiSZH`?MCicK$k0Jyny}!okg(9Ou(0s3h_HcSkzs?v zHQ~YGA>pCnVd3H75#a;FBf|$pXmEK%NJMBvSVVY4M8v>|$cRA$H3NeOhTw9Euz}$N zBL)r}7&&lIq$V;rG9)rIGAuGYG9q$dWMt%^K`?O;qQ|K@C>jK&L4(E^3bN8mQ}L~A zpvn5I0@0D0gT0;b7+#o*zbWasS(Grg^`)0GX4)?8ySoRtIdbH`z%77R08@E)zq|!j`wU&AkJB^B@~a6-i7`9YJ^eg1tqw1WVk+~ zknosv%#p<69*w*)>7}De3iP8&@=9Y%W?=N3qQ^KqKSN(Q4!7k4jm32!MB;prh*6Jr zNNFzurZmq3Mzv%n8Q%>|W96m5^v--h#$~{MpjQA>o;?dpdH51A=~*jdZhvRk9|3=5 zT^H!P7l16B2QSRUmEH!rQ3_jmvh+qw)nG?^)?nO0gOhl<~|1t_m zH^k5Bb)acHp`?w65?a3Lo{)^aV6~Do$fY2?e-EU%{)pvkc}oiv+BVKo|Ad?gPFW_-SC8qvdgYhB&Csq0)%g8lwj; z#LKtlL`4Ir?cYtyoM*J2C?x&>s?F*o9eJI1}9B;sgc-y$zoFs*Fel{qU(dl*ZhtvPm zS5p7Dm$&qavcIU*jE-r~_<1A_bhrp|z6LCG_3*HuB77=Lxs|&-%`j?{V4>H9G%j{3ZH$axio=9H+IQqlLUML z!l*BYTyOC6bycobo^KL)XsNamd;<7roJ0C!GwIt7;<}L&QE8tSz0Pna7}Q2ZNjjsV z=u$ka)kyC?>ZaP3n$ok;yQH%?vW+(t(fj~bc*GN!)QV;3ak2p~(BeWvDb1C)+9{pS z`WQ8V>TfoUZ)tlywJ&^O2>W_+5oIPdY~rerba2R!DO!>xp9-Rxft*4^LGB&2sb9`7 zYI9d24(iX-a2*XUTG|PkUi+XA~V6zzMKSS`6V6`ez_db2Z#n zt^8~QCrnI9O&b;)H#&7fY+QmmAN2*ZsYKw6^cT{-ZasaZ{I}a7?WMFg^stU{3NrH{ z(+!x)3?I9zBfx%X>HVP3!ptQ~GaU>rq@^Ay-u2MYAM(_0;kD0j&m+=21D_$f0}+XN zD9zePPOJvuNn_8Gp|BLaGQPAX?VuEobzqr@Z?(03aRUVskrtMqIIAT- zc=0$@MoCdAwqfF1D-a<<^tGL9CJMtkbR>n_OAN*7c#=3hgasXZc?Q}GXtfJ2n?4*E zE5f&#FfAj9PhNT`G&H9&h>f>&PE1MyJsj@NEifX-umzZsgltE-#o;r3@f-s`vnZWx zRG5=zFc>L$xK~5`AQWBQFRkqL)(6c9!g5}|m;=OjGwoRJZz#Yq9EAqDeNna*rsbC8 z+rGhMA1|UqiWAym1bC9t<$pHP{@mtBpB@l17_|+bLrBk~I7{gqOi^(W?)8{Or;u`S z7D-MOzAIFKyW^4f@}+{aQKKjyxhEPYG#C^mW>^ZurCHfQNfmc{&`?LpBgHarjzL_6 zv@QE8glNBQaUD7vQDotRFZsDRl?b(2_~H~EuapLgTU_i-pieR21Y?#w*|qD7|;Y7|_(_7Vi{jDNi2nU=;u3SE}R(BFJ2o@p?iQu+(8fF{7$Or3sl z-xMDDYQ#nBDQ5vRCaBL>oI_=!5`6Ry8G&a(^LW_PFrEADLwO?|oUWDM!JPI&xu7!6 z>9(M0j+E2aspxXhfr{|upnECkCsg!Zpan(v-75LFRrEHM{3kMv$K>>iR=)*BR@H7sH|*rV7umN~Nxj_(`;a zVzp*Ie6vCym5-GuA5_+*xyKAlK+?sl`Y7r@b^1oyA5;EGyqE{*{xIQNlxN(lfc6&q zbLj3kw8MBg=Hcao{Wb<{tw4vMANAz$MLJv(tgG8uipFQf@^omb3x#w#fRsUp_ifPz zwH%2!ry!oRG}`Z$CSHY`hV4y-aC772FmzDVAwxlImm1nM`UD&0e#^bsX90O?!$JX^rg}x-xbIr;LoZ6L7?TuW zhNkz;l+UTq9}VonqB+?9Bo>6Hz)N+l9qKH_J-uY!ot5xIm0({1<8d<1(+xai@5!y3 zZGNx7kLtdQxNPmc|30aGoeX|zAEhoLeb^Fxd-3LQ^c&@A4C&p|k_~|jjc3LKQ#n<~ zqrBpLm|^Y^+LRPqP&yP(PVD*(g$&gzG|6IGgu`fa0Tlo<0p^p0N6;33gE@DM;d%S4 zJAyC(`$6Ih05$?DUXEvSFaM^S&}*~b&VJKP{REt0gPk<)uLf=h%s<@=5!Ag$ZtpwJwhi^L1u<&JJqDyrVnj<+NvFj}p4HD0BU#8{Vn8klra$ z_Y8Cpl|?J;qp^c5XUj0@0tWefvJ>In-zJD{XG@!uG%gg$sHpTjd`q!N+WXu4W*kaH zpGmxlf#T)$Gz&V!c2oY27kVY)P7%Dm@ZXk`q93}=83tOImAfdbAsYr=w5CpVLlA$A zoq#{RE2s?fcz9n(=r7p(0uD&^i0nCpc8K~$k`DPV-EajS+K&+h;OiH>tR&Hpclrdh zTf=x$HHKR7dX>OYVzM%3Ri-4VOo8 z@EwEsi6k1s#nBi}UlcD6;((-KTY_!aMl!=NPMaVP+Q?0Qq*=U)$~IyfPXo3DdAv5( zP@EzU)Ka8D4=sF8rcv%xjLuSNbcVFZhQ(uSCOZ>pXqG|)va!-wgCZL#er}gs(%>)& zgRBWKbsUCRcHekDZbIbZO{$4Su}VKXF`33s!|1&pD~+2H#9`AgaoCiMQIi~ZJl;dG z7&Jj)EFUisomeojNDt3AK2nk$7}CU2jnHSw3MSgO!;Zu_NkQ5rkh6NO2?{3Y#X(ZC zZIBc%j*$?Xct^wpedg>0DjO-{Fqtw^B-4$#{qu|ZV?DG#O{e!4XH@#<1@{l>uVIsM zTf#UoB^dsJnx!9BO6dTVf`%YZU$1y8`|8=!wL2{Jkn}G31MMC4JNh_$X(kRB$LD_R z16KRA30}|m1Rthv_>_;Kq$sbn<-O278al_~^jJJ*1d~vEBwYXfqqNN++h&z*8x9@a z0pn5xFZVxAALtXM*J(` ziO4{v3Sxh!dAh5DGe zSay-ijCgwhy5d;0Ghprt4QwUiqjr1^a1Y>_0LnY07&9K&RZjzV>aAlM0v|ipW@VW*9NJC}KU1NM}MQAF!D4kN#jnBY%8h#4*No z`gMN3^i#yh^ZHKs(R1eXBDmWt=*24fMbK0>I6u)t0E*`?RVzHMF(rA$xx?@bQVo;w zm5Vq`{Q=-DI{Xx1T z+*P@ixW%qK%L0E-=u84o-vrg&Wto>`rCrErv-IMLpK|D+eU7)tIxvw#H6S%91qBx$ z5=HT27Vywq0q4zxo5~`cOXYri{ejZ~Fox6gG7XYXCz#XHYzk?g20!H65 zcd1w&^<02nYKyA@R314^`=b&SG@T8gJj8ivY-tV+!KG@&sp4uvynUAd!%blJS8Xy@ zGIERbu|;AR2j%l#c`N$EY^>vY)+tDMjW;rSIxhoUH{R2 znWOt%_kmAjOa;weO_$;>ELsm4nuGL}pADzmgAR9~$I&pvKBlq{NUEVX67m%QsuMhf zifc+~K1#8)3#EuC)B^>TSb(dx#Lcslab++H#^m(8SrK8xI9&exSSqzI(z2u!0DWT& zStS@c;F4RJ7l(JK&j}@Bd>DP`hjTw-;i?nnimDNmSb!xJJ$lM?xqwOeD@{9BGJf!(1a-aP!f&B>B1Nac|HeeOtVZai=0zd&E6L1S42`~&0 z4!9Z63E&5)I+(z|2Ydtg9Izem9^ehYD!_jMivT5nOu#h27(gr_1ke@G0k8*i0nLG{ zf&T;?2PBKFgg8WD%6(zx4hgIrKz{TS%P!`m`Cc&$7SS_O?-eT$bWp|jimCVaPQV(x z+^3LR+r;>yCfrykzB20_tde^=xzb_tgcH)4X6h@^^W=0g+=CVLM7ZhQEe}=F#jOrP zx*)xm1#x;tkY^O6t#LGCPF;7JjTW~sh&vQY#Lpr&bax1!v69j-o#smAdqeyDCXEBm z!A9!mG&Tn1+8p1j%-62$?>T8%TX}igFR+8=9;u!v%MZ;nCZ*qjD^@&tM>Sm`aCqAQ zItTRDAN6|z)5Ffq7(`I}7>D5;Ce90rQ82Na18ea3hy{HFQ2rL6xZ@GywJf}M3^-$) zhQZ|wQh*>d-BW&Jmt>rZBOQoL@n{sE=a+=yP`iZz}%fC2HZ6Eza9HHOAX=+31N0^>- zZ(x!syB23eo9=8Rda}yB!PqN7J@lC+dAaEY_|#G<)~E+#USckLjBQ|ha8qA2|w)N;Jq*qqcgJh{C^Zc<>1rO8J!h)X7s+Vsl9-&p#NR_7Y1XswVqL4-uUbeDwv)7JZiR*k~w6w z@(lKs_fKFs;ltTRfEn>N^w&FhII}<>H9r9v33b)*MyIatMN`G+RC;9%9Tf3h_1Jq^ z$Cqf$hS4fH~yon+5~!s>__4Jhlfc+bEXB5jJA6-mJHAVi~tj$3{VcJ0GI)l z01LnhU_F|#0Du6{0JMN)fDT{;m;hyfazF*Z45$QH09F9&33-43&;Yc6WPlD}1egG2 zfO0?uzznDaSO8W4>jim$0MG!mfMkFUU<8-|Wq@)(1;7lb1Xuu80J{nD00E!@XaUIp z9l!`M0m=a7fC_*aPzkUAtN_*<@&Ey#0cZiq03E;xFagQ{<$wx+8BhtZ0IUFZGvomR zKm*VMk^wq^5zs8OnYiBUZSpofaITp+U%U}m!1KBTVQ&M1!kV!c051Vv0jvR>0-Og7 zjA+J20G@%a2`IqL`Zr_mBU;is0cG-rfz8-r=$eocpHelp`GT8@PY&O-e#e07Kljse z8H4<}r@|m(Rt8jy^{EFi0sT0pL5`_2(nUj0_?uw6fR~O)_sB0G-g4M|6q}GFzY+{q zXW?dd1n7{TNM{fZ0u`tC2q)fvjAr6{J4mC+%$|Yud(gh{4~mnG}qtmBs?>KjtmZy3d^t?MVs#@F#n z7G+1)^%MD$>iDIKe97>mmPE>zRFO{yKVdoiaG;7d*qY8rN`lU*I<~n{7-wyb}ER>tPA|SM-%^0E;=^j%ho=@Os!6uI&+33|*`-yYq z65V4i$sL8?L6VcoSqS-=JNSw30NF2;{Pw_4Q69s{?`TIq)&reNnJVE-R7vm`s~x+9G2C{uDQrvz)(O z$DRDNRy57>lvaFZW?)oprjc>wlajv{I|e&uqy;X!ecKgZ-IezKilI&@K)BcOJO{N}@t#uggQa8?OH z0s6F{`A-X)hwntVP8DtfO*VT&z^t;B>)gx#1N~+j)D`y3P;XSvzmCJKpnl-uZ5LO>CH$HmWd{XMkUvqzjnK>~tfe!(As7e;F{BVP39n*RGaibiiCD3^J4t@hVxKHwY5r zAg02VDcU$&GB; zWU1a7fhm0fQ>6EsAY%*ssVz3k;ad3fG<5QDo$ku-?0FOqUNPM&8J@3*!fAQPfsB?I zGMZC%-V<(kg<6T1o=+z~>G^1Zl|P<#wVxHT{O9cXjSO^LA)nH_d9jIzKzF)(x+@g`GFg4R2XX zJMT(&ysaf(Dr-C3@EY62i%Bp6^RiauMyC84NyZG!^RS=4>wFOeJMR=XyqfNI-n-oJ z21H4`RBkHV@al$2c9+Z81kCL};YQXnT;ioX?bO0`TFaB-#dgR9%=IU^kugrT^Uig{ zYo2cBUFC*Xn`!5*bi-SgW#_GS!)uu-@lw9`Zs|Hdl8thH$k+_b%UYHj+42&(d;*gU z#kbOp3|l0{Cje7@uQb_Z_qdTQdq9fM0!(dH1u%~{pq1-%8p|Y^av5uZZO`A0tPbCk z<-Bv<@LC?W^R9BkYh5AnQn{^k!^?1%kkTvwlPx6U+uC)Sg>p$o3rsTfeu#t&$?8qSdF9_$>ca}a${)`w?pOAXB+F?Er)Nhv46<5a{xgD? z^ix=lN-yW(`pY)j?SI4#FH4j9^OoDB{yN3~i94B1lHCF@x2f8VjCQNUOV74rIu-$N%NS=z?6O?FxC4C8MEdRT?SfAH!zK{1;|tTs|BXLB06Bo_i`C)AxE?c zG*4^lRj%_#+fw4C{5FA?>{+dn;q3x~Y(Mfdww2ODo@6Is@+TdX9>SHfE~2UYQn(d< z%J)ba^l0JFe-$da5;UcUZ0vEhWMdMr^5@LP%69H;ES7aCZF~fNN*k@vqg6y05Kx!a zfTmxKoDVct&guL5Vs9sa(}c0z$QHgm2>T5Irfd8}9{SVz20yk2Yl<7OX2XBi!S85# zGd5Sj*9(5Dz_&`l7XrUZ@Ks?Ai2sJbuLt;OU5Niq!;jW2uUF{%Uspdi5`G;NepdKt z;g_fI3lRL+O8B)__-Wu52|uRrn*hIlTY&cfm4K%pcL?}Z;P(J0;64Y)B7Fc8Vf;RT zU#jeW^;*d<=Q=-D3|I9G^a1!jGHN_LqPru#JGX>9W6rdVB^g9kce(=8!xB^fP*a64`+ytH>BixOdOhyGVxpgPAe4>p#zymG>GRFR!`K0lH2O@i8oBqh1Fc#fka}59l52cZ>6mN*c64xN_Np8c4k+i*Y3$ID6#3KcxH;5E zW(y4w6EvAUXZIN^9x`AR-YGeTInwz-e9Hh-3GUXJQy?A}#-U?eosQ!{bO#!4Nbsi1 z#pCm4B;v*@ThV!(N{*xer*(^2OQIn&tJq-VdS8H~%!9&5-%J2coOC&x^n4PtP_`V! zE@dS1^_C;Vqngb8)z2~(eFI)Q=-34v{AF*|i-oaYz3G4loogAU$9-S+(;lpX%CcPG zPT06;7a|?tW$aA~7pvtvzNv+!&Wa?{lO5Q|$jibbq7~A4OjIXm8qFpY=Z&Rf-^C?G zsfKuibh{lZ@^N$yux&H(968Q~(235;?5Lyih*KmZe3A_5sJj`3xG@m^# zON)5OA3Wdl`XinCXJ3#^g8h1kN%HLwliB^0Dt`0}wc=aibXxuoxCn)*?1UJOBl2{R zOm0~cd5ywh0W<=I#1|>nMk=aOO?r-(c5xFvV`IFf!)p9wjwUpMweiC7cHT^~_kG5f z=nG46zFgd^isz2c-jjN^sa^;kF1F1UWR|k|;xn`zIaeHjdzmbkORnv-J@Vs%51wfp zKF(88$dB-#$5*$Iq~KVm9_9Qep6ha}#r^lO_!1*-nL_=R3y+uWa2=)Jes(X24l*%Z zkWm;KBDpg%spXa2;;}r@N%u<`@x>>}f3^46VzDfV%B}-Nw+cv_htpjtnMTxdoLqn_ zA8(hDo?^;Z=ole7zOb&c;4h-3k zU1s4-W=+e%%OX!-7$zSPW0T@={46zbOhRy&MzVYuODodnq-E#k;mJWv&*I6aI1OKQ zPNPlCI6t389+V~6o{Xy_uqUcBqsX#zXW*Hn72wMjhB;~2JvW=qmf@-#e$I;rDXvAK zFI=R}fPR_F5lf_`0|w<|Kefn7?le7a1H!#@$hDr7LO4<-TeERs6VI?2EH$R3ktLb5 zquxfru4`ejB-;@#Njiq56{4xaZtlXgV#VPT%7!!)6~vH+0~_L5cG@j3r&(eZ&vNLo z7g@|Lx<*tq!b^%3yU8V+BI@J^PAia7@g76QhAQHhL-^MSu}?lN6ZafRR?zdNhg)dK z%f@#X>2x|}i1@z3&Z=y&N29#Bp|H5We0?U}+z^Z{`Bbb`9P~7m!Rm1F$h(q3B`@WG zl0mHjAM`fX34cRT$K{q2uctr7cY<^@FokX#K^H6wU%^f+iq~h9(6=*iTozXf7PBqB z6APp=ZSV7-mLW5+*??L(r@`%@O^(q}I2?y+QF6Ifv7`9^(?7xxysQ+ziotf;OW{w> z9)Bc=KH_a$;zd3nvH154W|yb?F|1+J3jbT}<2#^c=288+iw6aBOu>gpuFl9f_gc)AI{*?DXH*q^lTlbfeM zC?<9iHI3q>U|KCg{xAG|#rYKtrfixaIp`voX#S}TxD)VdU?9b>xj64)hCj{8>;tB` z4lA%PFusp2>JflxBo+xw^G(UXK#HFiI|jiV2mX3+nH>1jv%p<-CWR6>y;uuGT)`in zEl~0|JMph{;%{-{Z*}6&C==@Zq#E^Y2!BtM}6hLPCsL=fFBTpXSo&p^zh`L@hqO4BE9~gb9f*y z?avKV@DGWZo1aG)`O%fE(SgB1nn3A3dmJN*4iv9L2^5NO;ynu^!vcMDV5z<+FlK0* zmP63U#8b}{ZF%8yDf$`6_y97y+K=VoL0 zG)Wm7RJE|_O61qUDh&$KkWofyq;DvU#aAeBlT;?X4Wa}41PXMBFgkDyk1}XdN*uZt zL5bM$KT+E7A1F}Oj}FA~fY6XYVSpGXj*9ZHK@6RgB9M$A9S{&`=%_CoGC;{fX5u6P zhJ4f~CJlDFI9ARJ(YtO2Q-|_K$5GJ7p;LK9l62IZqLApooZ@0*)PMnUg$c4%m>^V` z0V!h=2ZU&XBL>I?f-8{Jwip5pQCY^^*f|(M;R7^9LouMkhiP&#u{0^9AMVLcr;s57 z>haN&g?C-E#6%8Iq!J!O2Jq@VRBAl#O&&XgWIWP34fIGco zZ}Q*#^nV|3^LF}J!Rq_c*Z^b9#BK)dA#LQMbva3qzBdQ>6#qHzy9*ayO?~s>l~1ic zJ+x$CaA-`6oLlxTd~E$A#V>w%V%xcl(|>>Zmp4nfwRZ0>Ckw6_X3Q)Z^lp2${nHvt_j?`Dj2%cH2cQuu43z7uVJlkiw^6-a;6PN^n*p$Jg-jgUwHEDHB9qIUoUpuaAvxv zJu6T5WjUSdljqi*Z6>T+`>dhnv$KPLEo=U#_xe@8KDKimpb>wvu{2Ao0pgN>vLUe-dtmNIXHnOzrOvRh10$};NxxT^I_+j z;DK+ffABt59(8KT!UG$I`Tu^^f@MSe*{jPJRDHaB>!y%F-lmyHZ>V{*@SWzqY+^;1 zn*8x;w>^Hnm-bY3>)L&rKe$hqduO-7?OBH}ezq=q{p^_|&o}>JR_VR0d`iudg->p! z$RGNwOU;`V(N^P#j32x#rL1S4c~w1+rS6`b*zTjfUk>q3e&Y0!g(r6Wng)p7q0D9EH?W8%=DAuIk8Lx3c$Y z7VrP_?H$(b4{hq|d;G6u%YRz_zn+^X?@b&1o@Qa6q@dO@PbGFW{r23#-`A~LcKol> zYYdO?zx&9NxLogwdB42#^r)$?{5s%Z%&@ukw2r;~o`uFf$Hv~e@WxZKe)`z@0{ghRF8Im5 zo2=blx;k-T!K7<{Os^WhH1b)Wkym>q{*3K+DRcMr7HTPjvF>FHM<;(|UD9*m>wiub?i;epXJpq!^QZkM zg1*7>e2R@~%$`#Zih zOTgYXt4p)*}eF8p|?_O6phYI_~)K6=6RpVw?==L3brg{yy^Y$#|kzg_Lg zsXPC7%jxcZf3uPAcDKGZJHJJB+nCV{;(zYWCj9QdX-iM*;Oc(oABsP8?cz=P;AyY^ z+Vj|~mrhoFoZZtHbZqhbwzVg#Us;w~y@vUfPWIki(bGDR*q_?UMs|b&ZycHN>9T(F z{j_f_HC_AE>F!;&dX-JRpN(FS_48o1J7Ce_4!;+_cFpJoE3I8w;?$i59iEvnIn#H_ z>Fr6Ky~@^~|LRoI*?3dWn4OPa&0cxQzvAeUR{CbzSG#%FW_{Xjz-O&YsWW}QSzfdD z!xOvz8fJ=p+^cNV{pQzKwVS$g(l^(7Kk#T5_R1sv)hYMy?>u_J+|=2u$FBCePuutM9IH~~xAIh1(|b!6 zT5sDu^N+39eDcv8_HCz{ZTGHv}ar+FX!ejv+lQ@d}*-Cwh5-aUQ}Wk+7$JZMSn zf-5-u4~&!3cJ`?ZPRi2V}Q~!nen$b14&L5%87;Wl)w4L^uBhPL*dicYIFv%j6`jpM6YDfBFyz^RUZrQ?Cb*r*znYDv%9gGZz1>*KY{hragf$vtPgt{LE4v#OQ$+atT)IePfTr~IGlGi3IO zgMSUPjD7;Ewl&o-@3VW&_pXZRdFyp)&9A?=tEqUq;eC;Hyk zlczf@n)X9quX7y-uw(Ze-n)F^V;`P)zvH^lyJz_w{Cm)6lM_R$XC+>{wsueLLm%d? zUHjPS=pR_*C#U+F!nU7W_-XBm5A%X1c^iX1>Zt2^Y}#FKg!Z2CX>G?q_Va@K&OQ1; zo8CV^nf7eks+K-te(y>XC5^(qaybTjYYymj~bn>%?`4ENP7WplN@I=nPueRU=_cbIQkXD{nL z{z7+eOZ)cPUcTnOfu`2}rWx_<-uCR|Qg&4!>*?RVD&x-1tJ`_?PS}|Lgul?oJNc?W z(QDe&eLbFVcE$?zJ(N zO|2O0JuCXciJERd>$lzLmGj}}{s;TCzOehm6S@cYE`H~wUw8G4&Ix{h-Ifm5Ubiqh z5?!t9+Ob_eu6iQ-b{+q#RpQAWcRp79vi0USn}7A1cUeMJ58d9a4;ODae>V4zXTO_y z{oU4IAAH$d-sisyud@C)Vo}<%ZTqGk7+*te>#dXW%pD zo4hMtJ{G)h_8Yf%7}jIo1an8PNb{-SedkVZTGYQ;p}J)I+(4{Y^#Vf;X0(Idm@L`Og3AQuWFDaPQ=@wY94&`b18S4&E2>)W9`6 z`hR<@y=mBn_KWimK7T_OuhrTogFF4u@wd44rq2S%Z*wcJ)t!Rpwt6S$YSZw5#jkw3 zG2uxTwzSjrFRY!mzJ>OOrLjpre)s$sW?gt|%#a^{nZEnpQ(y1-=;!|5`m#3y{RZt9 z!+K@tdR`!}hXZ^De18z^Moot0>l!F!@v*H(y?N*T=bl}0ZRUU3DsAgzfA6yEf=!ve z!pi}R-~E2mm==-Oc~u0|em&D1>u(CrU~#_Yt20bH{Y{g1u+Hda1^DzB#zw`p4ruSy zyK}Jl_JS7L{!6{ity@vt{n&*2_xpIQ9v}V2q_`o=mIw9x?g7Jnfowx}zt`VivA+6C zpGp4O(Mx0d-iYGsSDU_f;X`cweVz9FGAaF+$NsMI@nt*8^LDj8`%=wzU$(3L;t8AX zSUvC>FY~hYKFem`czjF(tLU1c%RL#l`4xZmQmf#N0fv-O`KO;dl-04K$<08W`6wH5-f0=PF!RoCKHh9_{tn&i zFUS1WHE+O=J-u1idCz>T zef5`Q!>hGk&Gxj+Eq!2UBn{gV6UN4`9j5yaoV@{BfC=Em1pLKMF%Knq{5${dyn`hJ z$^ljx%7BG`XKFmN8tGG}xfZk|FcY6=8n1jV57X=@A1shH0TeT-SAKfQ!bYUoQ9h7g zIY4_Qr~VS8*->6pA|4&!iYOD8Db0@Zf!fS}q`mwH)9ff8$j^MK>%|qA<~n(CDbkNL zYXDTnsI1kA?@FVaD*Z?oPqXC`rm+&b%>Wa?2+#r=mA{QiKjOX=X{P+6G?oDqw znVkr`tm#L5O_63R^iUeh0hGpMfW}!GUHL=qlBb_+1C#+Q7a1R=u>wG8)B&`BCVl@x z-z7;u^m>}+O7K!TDUC(|rO|15b@eP85{9^%Ed9{sEX@{#Q5wqtluk-xgUV||(&$PC zb~Gyelnwx;i>I+1Zc1Y^Kzk{7a$2DbqXiL)^>u$P`YPrKUg3WRAdM*qJl{ch5(%nJ*6 zb;R#3Fa9m*zlgLe^TGOmA^i;|&mUxEng1;4)2exY-jvbsTEi6CJL2)cQCL z8E1Jw>CNUlW&w+tajD-r3p`xZRc$ z9)~mRi4Iph&h&DB$hyh{Gs3k1N|(E|RDws~dI@_VbJ^QJrG4BDJ6+|0yL3~y!?yF^ zl79JFH6#z}ly{z=9RHir-+(-*SKfKLIsUh$U(N%Q2X>oz8eL%z^js13pQi!#Y5-KG zJ*o31F6Rt;qQ{vm_xD7HD;}=L9fppJNjuLUGvorC>2_ro4|68V{W)JlG1ub?Lw7^c z&hvoEi`J2TPi%H1r}XnghbtbXE=O+2x=K5xt<05vMVLvgUlGRhsvcI$sA*4hxZ+Xk zQ__%gmG(-63&aoX>ASH`eQ;$L4^!IX$j$j0iXG`BKj?Fn_Imvp@OzpER`6=&IO;ub zbvlU8)lbcTv9u%I(BUlYWV@~Zu9iiZr+Gl>HUX}<^us1+X|F_>piZ-z2G7Oh!IhYP zN~hYca&UGkrPHypv-lM{xUP%B&h%btf7nfVPyx_3Ca!;L`WusGrR@Jp zzpV~kRJrH*rp(KKqnoFrf!O?SOtaGF2Be$X-70fV#ve97zI=#4PL+&N((R{;3^rLB@AH}1Ahp21q*~B%UCt@5k)MdfkX)b z$H-Vj#E*E$AGoTkgssgbY@yvy@HflYBx87zKUs}<7Q#$mYYgln+!clZS2edt;XY<> zqHDZx`60Tk-VM!E!<@{1&F_+;lJ-4jQCcjJH30%H zSN`&N8ey}ebdVpFGcDJDDext**-=_55s$8^?7vJl*GorZ?Ej~1M!dFkT&(>~WHZ%A zGoTDW^->EE$QEFmZJxM2u^)Og0E)}%iB6e^c*(|cfC-QcpjVR1A9$L?eu@iF2C%r% zNj6ph$VMGN3utgV2iXSO4_Rk6S0aq;BpZzYvQcoEkqr!l-ixvy{M=@;kDv@dc9M;5 zpLYYzbSj28FV=poQ)T}^z{T4C518mO$}7cw*rAhcsQ^xPX|y8nO0ZvHyRs}>;7@hm zW!v;m+wa77Wjd_jDOY599p^u3zX!J4(ox4~1@TYWe=)X`jS7?NIGe(L=(wnM|6*;w zRQ5wJy}PKM(Q`HfT-G(pIPmCXep}h_gszk63c?dvC67XsY$;cU!A*5dtK?C-5!N8v zDGsF`rJKSo29&bohO9abq|fAl&zFk9V+`yaEP(t`NQUD(QP=9tP$ zo%;Sy+b^fV=s_C9@}hixvg|*a|J)u|`G_zb=Tl=>a%x^qdyrTBd5f6KIm6KBNg28# z^WQ_;d460`<==_z7WkV0+-5bV@PEAhabmkIe-W;8kp@bWSe}5*&iGuES9PAaq}6&5 zU%7)g>y>ZtxJv`2x6*+w(H?cYoOZ_V%%95<29Mfy3;b;5LCGugyGw)9yi@Dqv@<)M z`Exnh9wYd8o|)keP=-5lgQr1haHLDN!D*TurNNON@ClCmo%uB+4K8#w+J0xcUHM;Z z8l35Jl*g|8oat$Z|HY)inSNKcItp{9r=k8Hq`?ZFGH3c7+33p8nV!b@J4pkzWmMN) zwh69mbrjYZJ6*}jwo+ZJ0BBwDD#IMv=*mwidnLGC+3G0lO0ZdJgCiSV`6*?u1h*?& zRblj=)m;fTD{WBO=;&Qm4j!#i=1Ouqv7g#56ToHLbtR-jWxpA8@|9qhr#8S|@%;uY zcp^i)h8D^DkJv(SKaz#D6EUNYQajuS2F0`wj2Npi^2va50L9}< z7uiq&AR9}nIX@66?q1$8onlfHbb$M!^r}{kML!3HK^Zat#hxp1Bagtl>DoqMrm(SUi4o|~W zwkhMIdZlxu=W^NS!Zw?o|CD{6*hcx(RQ5ra0HC(T*3LW9O@81}mN#2o!M~o{kq$?E zj{LYR!gPvoE8NLEOpOudv2CPNEl;!}u6pTFhACwo#9O8em-RZcjnYI=FFv&nSx-Ga zPh=g~SP4JDjcvrE0hj^x;%!R%oY+D6QsF3W_)&g}?_%J~VxJQm)%ij;>y&wOIqh>} zVj&Z? z+bBLBH(_P{)TzQapB4VefJ{0t)tS!#?6*gMnHUV6gVYMC3XlL;? z#2;}u%qvH_oY|xHcO-i`{M2?hqc4Xoj^c1;kHVkIuGW$4KXD!c!HeP-C3 zd^zIvG!EE9@6s}W;7SI|N0xJm+xW=n4=KfZh z&mzm2Wj$Q)A96fMPhC4|O)|~xJXtN-DWwPbVxy&esjZgsskWN(jnAh$_SNV=O0suNdMN`G6)}xLe;R3)2s8sV_3Yy}FUJalOV7bUR%;4u> zf}4XDZVv9AiB%D&*_}Rhez~I+x*gi#PQJ0>|5xKjx&i=|0D6A(+#S|s_&dyZ?%$C7 zrMLhTw^FxghgzSCN69a`)%@;gCvn^Ks^u>xe((xvc_oc-4Zz|`r;?xCu6QYo^TSQg zkn+$BsP`=4M{!fQEv=5+Tp!1dc$9t$zH)^>#iLb(Ig6WYu450E;aDB6ra2#Bgeh$x zy`(=GpiZ~VK7=({{7!7BqaX2^0k-GU)bYzUP`cIeiupm|%AcA~8Afh(xSCe;%QF91 z{IIWFZKH}dgT5sB?;viX;;56q zig0H(IMk<*s}ruahw~9en4qN9ZiJH!7PSr~?Lfxk_|>wGXv7r&s8q(ObgN~QVUFBt zeotuVab$xP{wB4&qd1j*YJN{?g$-i7L_3mI#^uQENTw{AwDV}WdOmEJa(ye#g&E4X*QwOLiZoyu0?NLwX zI@3=@tkFNj?NGm`iXk5WptMpNDO~|dSy&oH!sSIM6N1 zD@9!CM_4(4(ydd;Xk>qzJ)muF6|c=-%>$bB^YAi~1FhCg@h23Ql@ zd8&b*@;US8En*dKoiJ{%lE;x7d;);VPd#}hzoT&SQ}Q@-gD(J}j-yH12Og&)4$3bo z3jsWgFz9;eBR?fiW86;kTN$^oDBsEef>Kv)b#wI3yu^&62WFzc z=4PC?GPBxkfJFc=GkXwl7r@-!%q+J{KyLD*Fyaw;w*ixE?KZ&2;(8H1C;`CxVOn1h zjrbuA<<8RMPOU7fE-Pktaun`Fmg~|X+ybC_P^WBqAWJ$ZO_TCN-vMgrTq?MJ_tC6$WvUD zN0oq!Ev|3CMY zlLIK7n_$f<%^RANnwG(RgFA;rglr4hAJQsxcj$@GtHVOVMu#m6do=8&ur*y9CB1?@d*e$vL(9=CVN6? zlrn0Pm}-oGi`}mxz7jq4G=eWO<+bnDV4DN|~hWRz6b>Do2$clp3sHK=}PL)Vb;+^(}R+ zx?Rmxe^h@}@75Y>O*B_)sddv*wdb@KwME)m?R~ATX_>vvE#_O+Dr<-JmDR@{VIQ@1 zXP~p%+30L@eAe@r+shs3PILFTd))%}d-sxi(JSW%ldA%^_GMv$FkM(6tQ3w4XN7yj z6tSoHwzyv0ChigQ#qv^uv{YIlZIE)LPo{RlUbILX4Z>6@{Ky9f$rA}67syoyV)qUzg^@RGDx>Gx)o!5%BKee*@X8pKc ztl!Z8)+-n_jSfb4W4H06anAVJxNbzuIJ2|ao%IbfUo_t_x0}1neDjD|!)k2F){oX* z_QUqmc0c=7dzO9DK5chno&DUA?pXITcfWhcJ>_0@D|%JD1h28Dcn^D>z3yIbZI3)ZaloIQTjm3w>h2mT6$h+cB zaj$q#JS|=mZ;DaU-BK;dl6$+9Ru5L@VVRvQylkC^+HTD+!2fLJ0-$`~fXO*+o+3M`3 zqW^a8cH6ll+?U;a_T#o2<(2p1spjF{JTKon=$-J2ykEU&znWjmzt0z_=v2SAKiJRk zC;9XI75-{}v;UF5&p#dPQO7&?vXW3!kOWKUMvadX-V)XdMM4=dPV~jD;y5u={G6&e zCY};6i3w7&^t7}?x-FHF)8x_etMVK2L}k75fpSHut2Wdg)P`u|wO6%8#&Y8w^Pt&` zn;NWffHlmTY3;E-w{BQ}Tld(m-Ns(V8b9OC{$e+C45ynj+L_?QxwhNMeb4=ll_hxf z!_%GOt@Ji}mHeju6#q58f204Ef7}lcULH#*D^wIZ3O$4|!YjgJAxqf8{k%=hHxe7j z56dg$HFA#pv3yuQA>X5jN(-f{GFW+B?V}D=^VG6hJMCF*thPW~p>@??)L+$?>Ff2r z#sXuFaonh4PBy2TUzyKYFIqdT-Bu>I@K5_bM|WK3F{g_&*csu}bsM^y+->dw`bv@e zv-^kJ)RVm~UQcg?_kuUaTi|W-wt2_A-@J%ln-0^~@9fX;m;3AdAN;d^@HgLOvW6(3 ztWZN}Cv*_{3qyn(!rwwK@nvzMxKvz8eeM>Ih$pGeKgBnsh0-$VqV%)$tJFe%LVijf z&k3xNcgqLm3-r;ul&VU+GKiZyRVk;|R~xBI)FxUl?R9;l{+AJJeZbz^_I$h0dB^>L z&T-Ox)*I!G@dLmT^o@o>6TufA7TyuI3cG|agl~mU#C@Fm1yPa=DOGw#TEK0%S6(Tf zl#4m{smg3R#9I2?b)}g)NzJ6kEm7Cg5zeTOXdSdCwEo%%Z8bgZS54F3)R*hI`gi(G zy{^&Jc)^%rd}ACnPSF>BH=39o%~#F0%?;*O^L=i|=jI_Z!K!C9u?(xX)!$0D##)Q5 zx2^ToHmc~HRmIlqcJ?59iv60s)IMRKvdcRUI!{wWW1QEV_nnWNGtLz!);;c)_ZoVt z*TWm&P4K4EC02Q#c>BC>z3;p#eoem~J>T^EQ&;=_qyFvSPRzb@C(3gt?q@$#;US^D zFi=PrCQ@B7V!Ak5Oq6y?*VGcVvQ|?|)83+MC+PKcSvU1Z^p1LO{aJmYK22YtuhGBJ zkLg!gah&mzG1)j`oG=Ov+w5ufHD58;nqQa$tO?c>Ylb!7T4t@WHqrqvT0dLm>Fn>i z7u|@bd5?MBxZ5v!)4b(gHr4c@x7Q20oa?voQ~b$%c^3CM&;Ks?_N+Vq4Ww1V2g0X9 zzVN+JL%c^+#iQc))X#0Pj8uvK)?a!+cI1KbRArRpn-PUU8&2(39!~Z{xf8s@Yuc@z?LGEq z?h%)Np&$}*__0OZqwZHPs8`inYK&GvtD)VaHPV`ChSpZ=p$*eAwAZx7+9vIiam$!u zPPG!8`<+N(B$C0uD~PSdQFO1@#RcMOaWl8*3wqdT@v3-BEGJc?kBM?;d6OKk3{Xd^ zrruibqW95<>SOe&dNb2BTbXO6#46CV*5 z6=}tfIpQMGQ(7xI>TZz03H7vkL%pp&pmo;v+uzu|-8$U8G2U!%9h2#@_lV!spTh(y z^#2IflNc3-ew(sSyM^mQyl9FaQmHLLBQbI#xi!;ezPv;(px2aE$|=2-p^B^;%-c+L zC%^5ox>YNsH`e>>=~U2H`b^^uW1*36oHuS5cbl!4sqZ;IIG3D}?nL)B_ojE-i}xS% z$NMw=t>C1n$T2=)itr`B*%VufcThcD+h;&Z+OX{c$QA(-zs?Vtl)Lm*_Em<3? zO=l15al;4ei}a85u10U;ZQ~2$vQgXAOxJwe>|xF{7n|Rjw@lSavHDnf_67S2^Fnnz zrxnv;lr!0R%h}}QIbS&6I)!{$S@&+Yj_ZLPWmB^ptE<%u+|$&aX>Vtz>M&1dIp^rw zo!stjx;xrk?``&e@`}B`y%_&qrsr_~dH)G~p&hF;6jwP+7n-EL&ipB- zEBbtWr~a|t*63@@FcujrjasZ~xw*}Zc4|0>ofA$i_BV;0ecqc1YI?_e57hKCH|;%t zr~e7q=s2_aV(@#?qe6^PMbHFC=qn5oGK9MH)1Kn9%z?Jj<8n{Aw>(XrDX*13VFuS! zexYtEt6AzAwSo4e)=!(GL=!r}TTYrL@eTfIwsY38g17w!ZX9&t`O zj{8u!bDP~3{u9iFoI5jNLR8SUBmKcgD}@~4icm$oUrZLC5I+ce;t}TiGHDI9^0Ac4 zY6i<0@+)#FB~~d>BI@(h#boVu5Yoq5p7y17oRw73p(Sy?eL#DESmo@Rwr!`{L+s7=4m%$d^}Suusp2F!jloh&+$?va`=R@} z`+=Vu?D_1dNC`jsf`ldt(}Xpkk-f~aKZM7{{$hss8jR~3@n_~)MX9dTMCu|vEByu< zY9n`)yMac_D{)F~r4fvBpfXI!P^N%U-chzv@t-LLN}qH0O0^hWiOCI_Unu6p!|5F~h-ti9M-Ocnc4aTCPF;*a7*W>~bOOPLnFP z4dfPdQ2eKa_C8@J?V zy+r?09|tyhoeDf?JZC-ztL_6{A7f3nW>I_ZTAx|bb_aX9J=NR3S4H0CLbm3-w*PK-8y^-+Hul#GlJui$3 z|HC*fg<--d;Z3+(Hg*5LaF-~FZKM>bo76`dDy6CYL-izI{YFjJB(1fUqV3ggX|ZAL z9AmdH=$bLu$Y2s*Gkmih)2k4TsGJor&;HgDYpvB1rZC^mvfs9g?ceM;I_Cq9;LLF5 zItkus_V|9k9kX;Wvt_maC3mryUFJR(M+M_8QbwpM+$(q>uJ%Gtp`S1s6)01f%kHlS zx7-$Lp(eBupAiR$W5h4T!(tL&{=D?2v_#qpmZ%^%mLHTg*@laDMp+q4Z=E48lC$OQ zOpMQ&89&Gu<-cS{>5k$uMae<|+YXz$4Jw_lo>MQWrQjG1G*x?um9N!yYP;!O^>j~f zr9Vp5^@MAT1W7LdOK;*n@8>3+0IJ>cnNCk6B})%nah;9PTlcPfKf8oM%G zE#T*d=XvA2sopa0ZSQ06Q}0XfxOXnZtSQW(bQFih{yYA!!Fi)@AL4&wgz`cwAw~EM zc2ZreEjAEE(GeeK2cH#3ps7s(r&o|FOH-vcq--f4jqtS8U7jeIEzu*wm9E7rM-%1OsU|oq240z6iB3!U&ELD z9^lyhek86`B$*m<;Zi%Lz0yJH3TWoGbeCLBPLh2priv0^>YHkm7OT~v?{w1!YZJA3 zbn&g)7uxsQ8F+P+UX^)VUw=@ym}eQNPqXN#AHuYQS~yK5M2r}tk`Zs*hZfb*=w{48 z-H0`l%(muEa9(BWO}gk=Sm!OP43oN%oeZ~b2?rQ%kFv*c!{^$&?Y;H^`!IUk74WRZ zoa^MII%AzzxJ8ScY-c<8;~IRZk=xYOz_G{Nd#H__VAap*uD|%#gY!=;71_p*RbY@m z;YBiisuQeenYdQmDt;$}l|_}JGGbvd&7=Y z;!g5j2U9-?_xHH{kD~x&_+$M={wfgl2O*xm5`42zDxf-%ib6GdfWdU=Efk|5#=`3A ziOs|g;vf{@cg6ioFjAyYew^`~~@n z+(K!ibY!-qqH?FJqt)^16z1d}^>ejAJpz}iqQz_VV7tj6usCMSB0UF8`?cPIUN;VQ z{yNos%=jVf)|E`d^vpJ9d$h%c=36k=cVLo#nUf>DzcPqDJOgjPYX2C z9@0GNN2!|pG_x{GUdQ?l%g5y+`KtUkD)LjxI^|s@Kq2ka4q%Yw%$&_?d+l-b?q{@> z^s_B!LshBRHF_x{)~JkL{2UyitXa*fPlsx5U9|?-FF09fpC_Cm=c@AzEc7wdoN=(C zDlmeF{SJN_-J6Nhu~ZmFh~}h{glDL-{lXxb`)#Wr)d!qLQwK2f%I8?$m2j^q-kRSGv=e;J{~spsnBa0jNMrjCYK!XkI0( zva%Tt?b466@z+BFBM_~jcL^dMf6qX)OO)R;aA~zL55#HC%!1wlv*&M#!0WC zB?{>ML2Vvj4qcY7$)@5fk5HB4!8&g#-zYaZxmVOW%$r7NBLhQK`;2}Ols49wgnC!s zOf&nNE2y_yW|Wn{nGLjtTQ9*{0=)bixVSp(_fhBZu+H9tsr8%uD8PbQ+0H)5dzzI%c)DpRpIy z5wF>|?Yo?6j_vfNQaid`-KX6l?hEcRH_yH1#(2q|>-B|gj`j}2Gb{K5@Rk<%A2UC` z3D!Eh6pm)#Q`|@08dO`l@RBe~ScDFL7*tnEtd3)$p~R<(2U%|&`LujnzE62b=|o42 z*QyxL8WZss%9)i=6{nb$P}a+_-y69vRoygqyIbg%=Ef|Ad;IO)<=-9r&a6_AF8nwx zSZMqk#Bt1_{Zcu(F1p}Ql!A5i-t+SBaw@9J`--EEL#_ITj$~-hYfH6P*o~h!aRHS( z%e%;*>cC+ln@i!#Mk0Oiof^PfK9Oqk=Y56BctB~b&QXu6^|UwjJ^C+tBXpWA+<=Ov zh!Y~Sm(x*p4_S?z<<2iolzX51sN2z&>D^IHy%4UwXNw=ul4~<-K?FVKCir3Tj zL(H*WPMXui?dg6_7dRgL*#)ISZJ{h~$UyGUELii0Li@0T1bSkoxC?}GP}HPSVCU;N zUhkWG%>w%EDKlavf;u0z+FQ?qlJl%bm`2NN1^4DN=3^z-a9y{Q*8%NhrynTt+ z{7`AA)=(d5+E!0I!;h>3))lLo-ON_)>+WA}RWF#w7Dy(|%V6$p1gRbK&d~KDpqU1~ zfs#BZSUZ`6fP3c)jl_pw4_iRu!=>@41E+8ZE1`C*me0vQ$=Bs5rGgUZE^U-R1)czM zouw>PRw>^rr9R_bs{?RA~l{GO2ZX6t5wpg>G$eQP}Dxs zKhg8?1up0Yx3Z5h5RLqt@jPfmuv>#d#-KXYavC{3;MUKA%(9)2QTH#Q6D7bn9zs2P z20ttVk8qB=+!-b~t=G0`A8Grv8=At&wS+ZxN6Fu-AJ7l$KQI~Y z0blk4Zx1tGWX_#6E*ifYx0!T?`J9=-+Fzri9y6<2@!@>etPE>AJU)+UaSZMGVfaTE z`$>C%{U*xF_b8~v_8;6I(`g9{=?)7y7fd@CNCUT-n+uvd=pGGyw}xI55c+y=E9y#s z=^pXB`+dMIU-`%Ub1*v2sd?#0K0jz%!ec^rklY3FGJ94U&$k0A(o@o4X>{nX1zOl{ z_U&}&{r&|W*HD*Im@$3iVK{EHc)*+ zuN+dkz@De7v%y{K!frBCdqaCrZvh&K0cj-|4;d-Olg1E~mf6O3<0JH+6UK3~HeWau zKPsDZsb{C4bPcxOC1Ftw#jcfe)QNXHyFJ|A?z3(VobPiuU!P!q(@KXrqlJIi9zQk> zt#qa!i8_kJBVrfa_8np=sVpq$Ua2vRC!c=TSne;6V{(6jcX2ShG0oK0Xt)d173wBb zeh=?5i{5=htB60-%SgkUdX6r>-#Bd;V2`sf{dRaW%dPiO8mr@$mvt&TwVejeP-lvB z+=;joP#4d*w_KgB``Yc%eLlAnP>mEcAJ*zddpDWRm_;1MkVu6&bxEy z91XHY(UA{Z57-?!alLLQd335J?Qo2WeLZTffJBx|{yfS6V53BHht9j>~7|rpm)` z_FSd8nx?K|?{2De*M6-|iG9K{PW_cJuMS238kX1U8 z&5yC-C*qY5!45`Y*$o=1D!cMFd8hoToG%}d%P^G!2fRKQ(E6vlO;lzm*YV(iyb`Wvs-4tn>PD2>tLkT3zSd8F0f*uQD#B>vGrD;>7|Sfa@U~T& zuH~ZujH~@~`3b9YV7_6F4aarNb;vIq`zC^CPuTozJe8(Vv934AV#HzrmZk8c*Q}?J+(#elv!XJ{kvC zUI};EYrerLzemF3JCX&zkddfv*R~tjqU|t`dY}uf$G13Ow}x{ab}ll3YP$8@<|v)L z@#$xh|9Fo(d=q`StQY6iL>FrfdmiA8^|pXOa_Nu-V33nu8L)Q}zWoUQ1@8DXe>N;& zsegndNJ(%$g{4DJvLd*1v~W!*5u(Jhp#EN9&5L3krqOM<&O+Onms&CI-HXz%c`Z@<2XhuX;3+YFH-CwrjbHy2J02Zs8B>KbxaimMm%54P zNI3n5lbIrqQNB~2qfh;*#%gzKvi7i+OFf;}9J}qv$8I>~-FtC++re#yxo@~j+#1x$3*MVv9$37@EA5vf z;qWxE-g2%R(vgJiWM`GgXnU@gbVf6F6`y{^?i^(C&|rf$*$J zR9$($a+rea<&R;dh5zIjqk~9Xq&!@DD?0lg^pQhCpoKgGF3gdKDch7~_~*Ht_GPoQ zy}|KF7Y69+cej$a+lvU%p+_=Ey1_IYq5P~YSM#-RwLkPIkVI>`*GMuw6FAM+(KrIn zBZr&zH94xn(36ic%aQ%7VKy|IfCihJkC1{H$Q@im(s4g&pDUbxH1*V+Dfxumn+)7K zdlqPawX@4QKJSKXApX@&cpH4@;o5p1S5FZ?e1gsYw;v<*4 z^c0z@shsUlQXo6Lo&?hpknRQjGX12qQPJoD29a>1p0oymmsWv7e&pm&*)5&+ocRr>vey(; zx&|HeM=#PbI-KY2h1z0Mx^LiSju(Fri%1^Egt?sdQcsxN9uWF!kj81$?Xo1elF=`^ z!=Hn%s)m2o223;rWI0J&!X*1h`x+-IMz4-S_^95A8CIgt!ACxBtYmtfH)F_9HL?Ut zM@tJ7rQTMO{W$n~7>>voZqY_3k`^6$_(87qfN)T#gU(e(dRh9L6h@$4HY0^`M(&L} zF%n)HqgGcRReP)7se{p)Uj<1<*oW!(T*YRI`7pTl_0ThP?OS#V3h^Yo&^K`6E-*Dq zoWAZ1*ymF6e22V9dUVL)0uIEgkSl$t6=#~s3~IPc}441M8$?f)KpOICE4n=UQH3aR1@cIHPE#~8U1 z%8Dvmax1x$oGK5(v7Z70`WmDar_@j$0IT*#i#!0PzM;HJ-uQ@m4HYku32{I>f^Ye= zRuj(l8_rqa`o4ufG{#D{r{aCwvJJRa(6##Ef{X-h%t6ySiK_KHiPNd>4EJr4Y1`W=zGaD zUO-=HFFk=9GF)nm_EZJz))?M$Tq&d8rzWWq>FGz*A?owweZGQ=eowA&YPhc@+Fu~J zN>sh9w}p9@!Ru;I{~3ppcZp=v-(;KWn-V#SF6JOq@fXdh<{Z*&tIhXtzz)J3is25P z^(-#XT6*^g`(=`Z+4d&;JrXVf^Sq1?G{#x)eD1u?EL+3b?DxLLFT9K<9vkM2hcd_K zg0L>%$qnX4hyF%Ap%HbXqguAX^(stD2Jk*}=@i`{TtH3h( zC@MF@#KqmpJ<5GbGT8+KWu-k?)xI$FbY-ma5}arQd9lCMrc9%6v=gLiE@;1LX?P(o z1_c z-d@zq^QhA2QKfG>eW;k}ZWB-R9Qf;0blpGDyzcB#8KE|v`FSD8sV^hp@in^cEviCC zsb8S3#3k4chC7V=SzfD-S~OqVLGGkANNx&V!923nAA{I0gXy|~=mwE!d%^hB_!33> zs!@jXPUpPGhr0B~WSLH(+!mVwuK3crW|iQxx8lB)CO^}Zq(BPE<6f|_Z09r`JH{=K zhuw~O6DZR2!Df--=ulH{N&{4np%nm1F?g^CVo!MR3N=u*RdtPewoUUaJA`8&sys z^bnQKrc(#I{E_`B-kZz*{^ZmFeGYM7Lho7tzb!;%E+GeA7RM&l>+e11O{B{%@K&H0 z>_l@ey2H;SF`@pEAk-&8DARGrgU410<8fSPi}OheY$e^@KzbW)ULalOWkwxY!rN$p zb2SflXBDdOX7XcypiqjaeuKdP?10&C*@~2uXp8Lbju<%%pfcDz&~pVQtZdjFE8-!&EzPzh+vkPz|FBhTCD;?~NMcOHL9;tYMyZx0gEij7H;V8z zEvT8^crFvEmlg6Bc@KF06mD6RQW^ay$ftKgb048hB*U|U8MucYeoDE@RH>}iQ$_UD zPE6kssHt<%NP>LqLB8`U4sm7NLJ^O+lh&Jr%|s^93f#3lC={o(tGL9K^?JGpdhJ9$ zc?9nG9I)dSdd5KoJug89`$uWw;?BEvEQ z9V?T(<2+cx5qeC2YYrV@14+Sy))DIr`TokNvFpf}mjbUPk!Mj*pPPesQ+Ta08ZfNmu8_h?7@#qB<0eE{q0F!w1QF%AFHR*ha~4jyr7-RC(Og|KvmId`A{QqU{4)E zR)f%{$MK$JIy%{WG|z1M>{hg=T%6dinK?hwg|3I4_b#ofb|2lhB}t4Yw5QQS2eZ>J z@P1$tFCJ!TZ*s?1Xlt~0wD-cCMlfv;g3bb;K1kWzK#7Uc%i;!B)obbZ@y=kevD(;c zd}T!Q;-@vNYdw7HEc)C1)(|V-`Uc)#8bsX99%;WqMshcEDM*yo1^G;O=5uGRJJGxa z8pK`6CE;|47Zc6NV!TckJ6AvG|cHa?+>pg>5YKJXZo9PLn9qy!t6$%ou;5c_ZL<$$>YWPys`-X+}2V> zxeE8Jv8?b)W-{(n5;H9D64TLM4&&R$s5QuGw8aIkuGK|PRx}SCqu9n@J`Ok@UkG>+F$iKM{=c)@IC zDG8j7D1F;-a(970^OSw$rH&~ll_GqN5~g6RS^CeOp1P<# z)Kr{~d1OG(g4Y$Tqm~YXm`w(In|7I;RU#bSA^(}FFC?E*qQ}F@)5xjj896B9g=P^r zIu^ukfY^ti90obOd1$f4Os;Bnf}O}4q2_j5bcr-@d^8waE}^ z48G)2I_ECb;*-pz+vKSd{ELda(T>Cfx=|Rk~EgVWQS{ZqqT0Sh`5c$R6d@{qc*~Z!2`KCLkbD7h*jiw##24@uKR^yx+ za8Aj5uk)Y2I5o`bXQ0SUa3_<+Tgo)eW*r+jw{5Iu7u_O{-rS1yq@g1YU{&d?D}${5 z1aES9VzY6#7Q)oBaM8ALZUr#5LK2BZ;NN2JI(o%zdP}q)>sRpO*ok;Q!B1pAl3?Tl zyV9I}X^X4ak=^OR3$y{8b_N|~0?K42`vvx#9TP+>BJm*QG+_YjV4IM~4s{_%w~-ua z64;}y+yiYQ1D3TAUoD3hmWRmC6yaUP(%<8i2J{&Ptn8o!wo=+EDfIj<_%D6wJELH$ zW58jPakplW1^7ST>|!!^!R?Ny(PYZ1al`-f?RM1W-0T8;&p8BKH;TD2hIgqm zm>WyE?b*0x8)50&)EvB)eQLgX3>Be}E>Ofzq453@hLu)FcQzYpIDZjS7O9}L*0jQW6`egdXJUYi(@+3K=uJ_@b7U+j~ zWpt7R?OEPkUWe<(k@iX>$(e49p|i|0Dwx&GgpdpMFefm%XQO;BsFv2C6O01!n-w@KC}_` z6mf#Sqrk8SICt(=9{1`p(<ac_gj14OOnZ9(IQS_Rn|9*nGybUZ+5B--D zY#w&-JZ9BdT>og?>jrf4R-8^>PG&NvlFgaq=}CrREG1a&II=*l{`~n;8<>s2_mTAp*SHCM%kQ1PhXOC|Yj)F?9pvC={YHdrM_Q1U#K#gYLEzKqil7)YqgM&E-M#YRe(lY zY-KPD7Q*<~+S{0$|IvYuF+B_IvrLKWRB|+xT+K*DFopZhi^V&)!XG#|LAH(S_w(G0o;*Y+>(4wyREU01mjtwh0XFf?+F42?2ew+hoFuNf1o12bW2o#j*ZGlDR>TUWhxpmN!c|C>Z;w`eXl8eInJpka|AJoAHPp z3&Re2aC3b6E>!b2DmjB$U5%-m_75(}29*^3<1xjAdF2!;YAKzg8ZRU#NVBCZQn$Gz zd;*4=2wP5NM+!*!+(u7KWDn+1Bga^MB51ccU86)zV6Bt6^TnKM15~)Rtn)HhCjOuA zJPVEuZs>JpOK?l0nJ>Xjtw!=Am@*Blr2l2obmYW?X_JPh@*fi?5e^hg8NpVVG!AQR z1*S^@)pY^arGkU|QX@lfBS(dJcmjGuragnIna2%ZO7cIrVcRZ0> z9VlamoH3x;Xi#hdZ_YwLt~iGOo=Sj8b3tuwxnG+}YUNU01)!5-sF#H(WkupfcxMCh z+R0%>Ww38W^1o0=HgBT?9+5}SEeH{OaAI+GJSaYi^#n*h)t$N7F-&s|pk~HUHQ98Y z0zAC4LbOaOZ-)G@E{u9Zu?E zx==JXu%p%&1T~7voX2F!WfongKI56xt#~Vu&fDIlRO&tu(@ADcG?PZ)rBNzKDHD{m zjeD5~qbMe^lt3R6!s$JRyBOTVL(H6D+9cDR9D36L@X#10OE!}w2c}-g`^f7dLuI1;1r8OE|$O?Nx>_fOm2Q2C%6snRT%QF+qn7-!0`dwN(=G(JTOBxeJ)7T z7jl-@-D;iyw@Lx6XMi8FNb%)|s5}A&Pv$foFn7n$3!99`xtYmwGC0k+GNGRl3kDMK z-`f3GUPX|h2 zGJhvPaK1x0-5h%Cp?^49EM3H)hosY8C(v25INMz2O`#kS$^?H!NyzgAzM~_UrU!az dfd4aKNDE0aFS_y5EW{2Q&4>}>!5 From b341631192428bf13e3131390c06fa9bd384c0ea Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 19 Feb 2024 16:22:26 +0100 Subject: [PATCH 1430/1897] rename suggestions: dispose listeners & clear focused-elements list on blur --- .../contrib/rename/browser/renameInputField.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 60049237806..e02e89b8f42 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -414,8 +414,12 @@ class CandidatesView { private _lineHeight: number; private _availableHeight: number; + private _disposables: DisposableStore; + constructor(parent: HTMLElement, opts: { fontInfo: FontInfo; onSelectionChange: () => void }) { + this._disposables = new DisposableStore(); + this._availableHeight = 0; this._lineHeight = opts.fontInfo.lineHeight; @@ -466,11 +470,15 @@ class CandidatesView { } ); - this._listWidget.onDidChangeSelection(e => { + this._disposables.add(this._listWidget.onDidChangeSelection(e => { if (e.elements.length > 0) { opts.onSelectionChange(); } - }); + })); + + this._disposables.add(this._listWidget.onDidBlur(e => { + this._listWidget.setFocus([]); + })); this._listWidget.style(defaultListStyles); } @@ -565,6 +573,7 @@ class CandidatesView { dispose() { this._listWidget.dispose(); + this._disposables.dispose(); } } From b6feb4ad668df02d175ae85ae9c87122380f0df4 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 19 Feb 2024 16:52:44 +0100 Subject: [PATCH 1431/1897] Update style of settings in release notes (#205551) * WIP for updating styling of release notes button * Keyboard nav + fix test --- .../browser/markdownSettingRenderer.ts | 10 +- .../browser/markdownSettingRenderer.test.ts | 7 +- .../update/browser/releaseNotesEditor.ts | 158 +++++++++--------- 3 files changed, 87 insertions(+), 88 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index 62bd755a1bf..1d767009ce2 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -14,7 +14,8 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IAction } from 'vs/base/common/actions'; -const codeSettingRegex = /^/; +const codeSettingRegex = /^/; +const codeFeatureRegex = /^/; export class SimpleSettingRenderer { private _defaultSettings: DefaultSettings; @@ -40,7 +41,7 @@ export class SimpleSettingRenderer { getHtmlRenderer(): (html: string) => string { return (html): string => { - const match = codeSettingRegex.exec(html); + const match = codeSettingRegex.exec(html) ?? codeFeatureRegex.exec(html); if (match && match.length === 4) { const settingId = match[2]; const rendered = this.render(settingId, match[3], match[1] === 'codefeature'); @@ -165,7 +166,10 @@ export class SimpleSettingRenderer { private renderSetting(setting: ISetting, newValue: string | undefined): string | undefined { const href = this.settingToUriString(setting.key, newValue); const title = nls.localize('changeSettingTitle', "Try feature"); - return ``; + return ` + + ${setting.key} + `; } private renderFeature(setting: ISetting, newValue: string): string | undefined { diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index ed9ac269577..884e3b50429 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -70,10 +70,13 @@ suite('Markdown Setting Renderer Test', () => { test('render code setting button with value', () => { const htmlRenderer = settingRenderer.getHtmlRenderer(); - const htmlNoValue = ''; + const htmlNoValue = ''; const renderedHtmlNoValue = htmlRenderer(htmlNoValue); assert.strictEqual(renderedHtmlNoValue, - ``); + ` + + example.booleanSetting + `); }); test('actions with no value', () => { diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index de8129bbdf0..3feabd2ea4e 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -258,48 +258,91 @@ export class ReleaseNotesManager { ${DEFAULT_MARKDOWN_STYLES} ${css} + /* codesetting */ + + code:has(.codesetting)+code { + display: none; + } + + code:has(.codesetting) { + background-color: var(--vscode-textPreformat-background); + color: var(--vscode-textPreformat-foreground); + padding-left: 1px; + margin-right: 3px; + padding-right: 0px; + } + + code:has(.codesetting):focus { + border: 1px solid var(--vscode-button-border, transparent); + } + .codesetting { - color: var(--vscode-button-foreground); - background-color: var(--vscode-button-background); - width: fit-content; + color: var(--vscode-textPreformat-foreground); padding: 0px 1px 1px 0px; - font-size: 12px; + font-size: 0px; overflow: hidden; text-overflow: ellipsis; outline-offset: 2px !important; box-sizing: border-box; - border-radius: 2px; text-align: center; cursor: pointer; - border: 1px solid var(--vscode-button-border, transparent); - line-height: 9px; - outline: 1px solid transparent; - display: inline-block; - margin-top: 3px; - margin-bottom: -4px !important; - } - .codesetting:hover { - background-color: var(--vscode-button-hoverBackground); - text-decoration: none !important; - color: var(--vscode-button-hoverForeground) !important; - } - .codesetting:focus { - outline: 0 !important; - text-decoration: none !important; - color: var(--vscode-button-hoverForeground) !important; - border: 1px solid var(--vscode-button-border, transparent); + display: inline; + margin-right: 3px; } .codesetting svg { + font-size: 12px; + text-align: center; + cursor: pointer; + border: 1px solid var(--vscode-button-secondaryBorder, transparent); + outline: 1px solid transparent; + line-height: 9px; + margin-bottom: -5px; + padding-left: 0px; + padding-top: 2px; + padding-bottom: 2px; + padding-right: 2px; display: inline-block; text-decoration: none; text-rendering: auto; - text-align: center; text-transform: none; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; user-select: none; -webkit-user-select: none; } + .codesetting .setting-name { + font-size: 13px; + padding-left: 2px; + padding-right: 3px; + padding-top: 1px; + padding-bottom: 1px; + margin-left: -5px; + margin-top: -3px; + } + .codesetting:hover { + color: var(--vscode-textPreformat-foreground) !important; + text-decoration: none !important; + } + code:has(.codesetting):hover { + filter: brightness(140%); + text-decoration: none !important; + } + .codesetting:focus { + outline: 0 !important; + text-decoration: none !important; + color: var(--vscode-button-hoverForeground) !important; + } + .codesetting .separator { + width: 1px; + height: 14px; + margin-bottom: -3px; + display: inline-block; + background-color: var(--vscode-editor-background); + font-size: 12px; + margin-right: 8px; + } + + /* codefeature */ .codefeature-container { display: flex; @@ -357,66 +400,6 @@ export class ReleaseNotesManager { content: "${nls.localize('enableFeature', "Enable this feature")}"; } - .codefeature-container { - display: flex; - } - - .codefeature { - position: relative; - display: inline-block; - width: 58px; - height: 30px; - } - - .codefeature-container input { - display: none; - } - - .toggle { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: var(--vscode-disabledForeground); - transition: .4s; - border-radius: 30px; - } - - .toggle:before { - position: absolute; - content: ""; - height: 22px; - width: 22px; - left: 4px; - bottom: 4px; - background-color: var(--vscode-editor-foreground); - transition: .4s; - border-radius: 50%; - } - - input:checked+.codefeature > .toggle:before { - transform: translateX(26px); - } - - input:checked+.codefeature > .toggle { - background-color: var(--vscode-button-background); - } - - .codefeature-container:has(input) .title { - line-height: 30px; - padding-left: 4px; - font-weight: bold; - } - - .codefeature-container:has(input:checked) .title:after { - content: "${nls.localize('disableFeature', "Disable this feature")}"; - } - .codefeature-container:has(input:not(:checked)) .title:after { - content: "${nls.localize('enableFeature', "Enable this feature")}"; - } - header { display: flex; align-items: center; padding-top: 1em; } @@ -486,6 +469,15 @@ export class ReleaseNotesManager { } }); + window.addEventListener('keypress', event => { + if (event.keyCode === 13) { + if (event.target.children.length > 0 && event.target.children[0].href) { + const clientRect = event.target.getBoundingClientRect(); + vscode.postMessage({ type: 'clickSetting', value: { uri: event.target.children[0].href, x: clientRect.right , y: clientRect.bottom }}); + } + } + }); + input.addEventListener('change', event => { vscode.postMessage({ type: 'showReleaseNotes', value: input.checked }, '*'); }); From 9f4d6cf8c51a4c2c4343e81045608e7cfeb8a5aa Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 19 Feb 2024 16:58:52 +0100 Subject: [PATCH 1432/1897] rename suggestions: fix overflowing candidate names - update list widget width --- .../rename/browser/renameInputField.ts | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index e02e89b8f42..877275cb25f 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -202,8 +202,7 @@ export class RenameInputField implements IContentWidget { const [accept, preview] = this._acceptKeybindings; this._label!.innerText = localize({ key: 'label', comment: ['placeholders are keybindings, e.g "F2 to Rename, Shift+F2 to Preview"'] }, "{0} to Rename, {1} to Preview", this._keybindingService.lookupKeybinding(accept)?.getLabel(), this._keybindingService.lookupKeybinding(preview)?.getLabel()); - this._domNode!.style.minWidth = `250px`; // to prevent from widening when candidates come in - this._domNode!.style.maxWidth = `400px`; // TODO@ulugbekna: what if we have a very long name? + this._domNode!.style.minWidth = `200px`; // to prevent from widening when candidates come in return null; } @@ -438,7 +437,7 @@ class CandidatesView { } getHeight(element: NewSymbolName): number { - return that.candidateViewHeight; + return that._candidateViewHeight; } }; @@ -483,9 +482,9 @@ class CandidatesView { this._listWidget.style(defaultListStyles); } - public get candidateViewHeight(): number { - const { totalHeight } = CandidateView.getLayoutInfo({ lineHeight: this._lineHeight }); - return totalHeight; + dispose() { + this._listWidget.dispose(); + this._disposables.dispose(); } // height - max height allowed by parent element @@ -496,18 +495,12 @@ class CandidatesView { } } - private _pickListHeight(nCandidates: number) { - const heightToFitAllCandidates = this.candidateViewHeight * nCandidates; - const height = Math.min(heightToFitAllCandidates, this._availableHeight, this.candidateViewHeight * 7 /* max # of candidates we want to show at once */); - return height; - } - public setCandidates(candidates: NewSymbolName[]): void { const height = this._pickListHeight(candidates.length); this._listWidget.splice(0, 0, candidates); - this._listWidget.layout(height); + this._listWidget.layout(height, this._pickListWidth(candidates)); this._listContainer.style.height = `${height}px`; } @@ -571,10 +564,21 @@ class CandidatesView { return focusedIx > 0; } - dispose() { - this._listWidget.dispose(); - this._disposables.dispose(); + private get _candidateViewHeight(): number { + const { totalHeight } = CandidateView.getLayoutInfo({ lineHeight: this._lineHeight }); + return totalHeight; } + + private _pickListHeight(nCandidates: number) { + const heightToFitAllCandidates = this._candidateViewHeight * nCandidates; + const height = Math.min(heightToFitAllCandidates, this._availableHeight, this._candidateViewHeight * 7 /* max # of candidates we want to show at once */); + return height; + } + + private _pickListWidth(candidates: NewSymbolName[]): number { + return Math.max(...candidates.map(c => c.newSymbolName.length)) * 7 /* approximate # of pixes taken by a single character */; + } + } class CandidateView { From 84ba0e1249f2015da173dd290f310f0844fc3fae Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Mon, 19 Feb 2024 16:32:49 +0100 Subject: [PATCH 1433/1897] Add github-project codicon to registry --- src/vs/base/common/codicons.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index c8ab637ebeb..27423d734fd 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -591,11 +591,13 @@ export const Codicon = { gitStash: register('git-stash', 0xec26), gitStashApply: register('git-stash-apply', 0xec27), gitStashPop: register('git-stash-pop', 0xec28), - coverage: register('coverage', 0xec2e), runAllCoverage: register('run-all-coverage', 0xec2d), runCoverage: register('run-all-coverage', 0xec2c), + coverage: register('coverage', 0xec2e), + githubProject: register('github-project', 0xec2f), // derived icons, that could become separate icons + // TODO: These mappings should go in the vscode-codicons mapping file dialogError: register('dialog-error', 'error'), dialogWarning: register('dialog-warning', 'warning'), From 4145304fd07bb82b1c7289ebda555058674b9a6c Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 19 Feb 2024 17:10:34 +0100 Subject: [PATCH 1434/1897] rename suggestions: fix not showing preview when input box is empty but a rename suggestion is focused --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 877275cb25f..dc5131bba43 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -298,9 +298,10 @@ export class RenameInputField implements IContentWidget { assertType(this._input !== undefined); assertType(this._candidatesView !== undefined); - const candidateName = this._candidatesView.focusedCandidate; - if ((candidateName === undefined && this._input.value === value) || this._input.value.trim().length === 0) { - this.cancelInput(true, '_currentAcceptInput (because candidateName is undefined or input.value is empty)'); + const newName = this._candidatesView.focusedCandidate ?? this._input.value; + + if (newName === value || newName.trim().length === 0 /* is just whitespace */) { + this.cancelInput(true, '_currentAcceptInput (because newName === value || newName.trim().length === 0)'); return; } @@ -309,7 +310,7 @@ export class RenameInputField implements IContentWidget { this._candidatesView.clearCandidates(); resolve({ - newName: candidateName ?? this._input.value, + newName, wantsPreview: supportPreview && wantsPreview }); }; From c5327e56eda3cc9cccaf85be6b9f0e30bbf9825b Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 19 Feb 2024 16:13:54 +0000 Subject: [PATCH 1435/1897] Show variables in help text (#205553) --- .../api/common/extHostChatAgents2.ts | 15 ++++++++++++ .../contrib/chat/browser/chat.contribution.ts | 23 ++++++++++++++++++- .../contrib/chat/common/chatAgents.ts | 1 + ...scode.proposed.defaultChatParticipant.d.ts | 5 ++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 59a5684877c..b3318d5c314 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -340,6 +340,7 @@ class ExtHostChatAgent { private _iconPath: vscode.Uri | { light: vscode.Uri; dark: vscode.Uri } | vscode.ThemeIcon | undefined; private _isDefault: boolean | undefined; private _helpTextPrefix: string | vscode.MarkdownString | undefined; + private _helpTextVariablesPrefix: string | vscode.MarkdownString | undefined; private _helpTextPostfix: string | vscode.MarkdownString | undefined; private _sampleRequest?: string; private _isSecondary: boolean | undefined; @@ -470,6 +471,7 @@ class ExtHostChatAgent { isDefault: this._isDefault, isSecondary: this._isSecondary, helpTextPrefix: (!this._helpTextPrefix || typeof this._helpTextPrefix === 'string') ? this._helpTextPrefix : typeConvert.MarkdownString.from(this._helpTextPrefix), + helpTextVariablesPrefix: (!this._helpTextVariablesPrefix || typeof this._helpTextVariablesPrefix === 'string') ? this._helpTextVariablesPrefix : typeConvert.MarkdownString.from(this._helpTextVariablesPrefix), helpTextPostfix: (!this._helpTextPostfix || typeof this._helpTextPostfix === 'string') ? this._helpTextPostfix : typeConvert.MarkdownString.from(this._helpTextPostfix), sampleRequest: this._sampleRequest, supportIssueReporting: this._supportIssueReporting, @@ -548,6 +550,19 @@ class ExtHostChatAgent { that._helpTextPrefix = v; updateMetadataSoon(); }, + get helpTextVariablesPrefix() { + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); + return that._helpTextVariablesPrefix; + }, + set helpTextVariablesPrefix(v) { + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); + if (!that._isDefault) { + throw new Error('helpTextVariablesPrefix is only available on the default chat agent'); + } + + that._helpTextVariablesPrefix = v; + updateMetadataSoon(); + }, get helpTextPostfix() { checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); return that._helpTextPostfix; diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index c07a0b1afb6..528810a99a0 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -56,7 +56,7 @@ import { registerChatFileTreeActions } from 'vs/workbench/contrib/chat/browser/a import { QuickChatService } from 'vs/workbench/contrib/chat/browser/chatQuick'; import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; -import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IVoiceChatService, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; @@ -228,6 +228,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { @IChatSlashCommandService slashCommandService: IChatSlashCommandService, @ICommandService commandService: ICommandService, @IChatAgentService chatAgentService: IChatAgentService, + @IChatVariablesService chatVariablesService: IChatVariablesService, ) { super(); this._store.add(slashCommandService.registerSlashCommand({ @@ -246,6 +247,8 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { }, async (prompt, progress) => { const defaultAgent = chatAgentService.getDefaultAgent(); const agents = chatAgentService.getAgents(); + + // Report prefix if (defaultAgent?.metadata.helpTextPrefix) { if (isMarkdownString(defaultAgent.metadata.helpTextPrefix)) { progress.report({ content: defaultAgent.metadata.helpTextPrefix, kind: 'markdownContent' }); @@ -255,6 +258,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { progress.report({ content: '\n\n', kind: 'content' }); } + // Report agent list const agentText = (await Promise.all(agents .filter(a => a.id !== defaultAgent?.id) .map(async a => { @@ -272,6 +276,23 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { return (agentLine + '\n' + commandText).trim(); }))).join('\n'); progress.report({ content: new MarkdownString(agentText, { isTrusted: { enabledCommands: [SubmitAction.ID] } }), kind: 'markdownContent' }); + + // Report variables + if (defaultAgent?.metadata.helpTextVariablesPrefix) { + progress.report({ content: '\n\n', kind: 'content' }); + if (isMarkdownString(defaultAgent.metadata.helpTextVariablesPrefix)) { + progress.report({ content: defaultAgent.metadata.helpTextVariablesPrefix, kind: 'markdownContent' }); + } else { + progress.report({ content: defaultAgent.metadata.helpTextVariablesPrefix, kind: 'content' }); + } + + const variableText = Array.from(chatVariablesService.getVariables()) + .map(v => `* \`${chatVariableLeader}${v.name}\` - ${v.description}`) + .join('\n'); + progress.report({ content: '\n' + variableText, kind: 'content' }); + } + + // Report help text ending if (defaultAgent?.metadata.helpTextPostfix) { progress.report({ content: '\n\n', kind: 'content' }); if (isMarkdownString(defaultAgent.metadata.helpTextPostfix)) { diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index a6ef4c6c359..3f9eb992697 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -70,6 +70,7 @@ export interface IChatAgentMetadata { description?: string; isDefault?: boolean; // The agent invoked when no agent is specified helpTextPrefix?: string | IMarkdownString; + helpTextVariablesPrefix?: string | IMarkdownString; helpTextPostfix?: string | IMarkdownString; isSecondary?: boolean; // Invoked by ctrl/cmd+enter fullName?: string; diff --git a/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts b/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts index 1c71b40e122..e1c026a6557 100644 --- a/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts @@ -29,6 +29,11 @@ declare module 'vscode' { */ helpTextPrefix?: string | MarkdownString; + /** + * A string that will be added before the listing of chat variables in `/help`. + */ + helpTextVariablesPrefix?: string | MarkdownString; + /** * A string that will be appended after the listing of chat participants in `/help`. */ From 683205493230f8d65208644527e0600e9ba19aee Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 19 Feb 2024 17:42:12 +0100 Subject: [PATCH 1436/1897] Add command that enables testing of release notes editor (#205556) Also fix double setting name --- .../browser/markdownSettingRenderer.ts | 2 +- .../browser/markdownSettingRenderer.test.ts | 2 +- .../update/browser/releaseNotesEditor.ts | 6 ++-- .../update/browser/update.contribution.ts | 32 +++++++++++++++++-- .../contrib/update/browser/update.ts | 8 ++--- .../workbench/contrib/update/common/update.ts | 1 + 6 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index 1d767009ce2..72f4dfd0e2e 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -169,7 +169,7 @@ export class SimpleSettingRenderer { return ` ${setting.key} - `; + `; } private renderFeature(setting: ISetting, newValue: string): string | undefined { diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index 884e3b50429..271cf0d2da5 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -76,7 +76,7 @@ suite('Markdown Setting Renderer Test', () => { ` example.booleanSetting - `); + `); }); test('actions with no value', () => { diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 3feabd2ea4e..a7303e1f8c9 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -33,6 +33,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { SimpleSettingRenderer } from 'vs/workbench/contrib/markdown/browser/markdownSettingRenderer'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Schemas } from 'vs/base/common/network'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; export class ReleaseNotesManager { private readonly _simpleSettingRenderer: SimpleSettingRenderer; @@ -44,6 +45,7 @@ export class ReleaseNotesManager { private readonly disposables = new DisposableStore(); public constructor( + private readonly _useCurrentFile: boolean, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @ILanguageService private readonly _languageService: ILanguageService, @@ -52,6 +54,7 @@ export class ReleaseNotesManager { @IConfigurationService private readonly _configurationService: IConfigurationService, @IEditorService private readonly _editorService: IEditorService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, + @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, @IExtensionService private readonly _extensionService: IExtensionService, @IProductService private readonly _productService: IProductService, @@ -196,7 +199,7 @@ export class ReleaseNotesManager { const fetchReleaseNotes = async () => { let text; try { - text = await asTextOrError(await this._requestService.request({ url }, CancellationToken.None)); + text = this._useCurrentFile ? this._codeEditorService.getActiveCodeEditor()?.getModel()?.getValue() : await asTextOrError(await this._requestService.request({ url }, CancellationToken.None)); } catch { throw new Error('Failed to fetch release notes'); } @@ -517,4 +520,3 @@ export class ReleaseNotesManager { } } } - diff --git a/src/vs/workbench/contrib/update/browser/update.contribution.ts b/src/vs/workbench/contrib/update/browser/update.contribution.ts index 46b1d76365a..fa3edab7d3a 100644 --- a/src/vs/workbench/contrib/update/browser/update.contribution.ts +++ b/src/vs/workbench/contrib/update/browser/update.contribution.ts @@ -17,7 +17,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { isWindows } from 'vs/base/common/platform'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; -import { ShowCurrentReleaseNotesActionId } from 'vs/workbench/contrib/update/common/update'; +import { ShowCurrentReleaseNotesActionId, ShowCurrentReleaseNotesFromCurrentFileActionId } from 'vs/workbench/contrib/update/common/update'; import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -59,7 +59,7 @@ export class ShowCurrentReleaseNotesAction extends Action2 { const openerService = accessor.get(IOpenerService); try { - await showReleaseNotesInEditor(instantiationService, productService.version); + await showReleaseNotesInEditor(instantiationService, productService.version, false); } catch (err) { if (productService.releaseNotesUrl) { await openerService.open(URI.parse(productService.releaseNotesUrl)); @@ -70,7 +70,35 @@ export class ShowCurrentReleaseNotesAction extends Action2 { } } +export class ShowCurrentReleaseNotesFromCurrentFileAction extends Action2 { + + constructor() { + super({ + id: ShowCurrentReleaseNotesFromCurrentFileActionId, + title: { + ...localize2('showReleaseNotesCurrentFile', "Open Current File as Release Notes"), + mnemonicTitle: localize({ key: 'mshowReleaseNotes', comment: ['&& denotes a mnemonic'] }, "Show &&Release Notes"), + }, + category: localize2('developerCategory', "Developer"), + f1: true, + precondition: RELEASE_NOTES_URL + }); + } + + async run(accessor: ServicesAccessor): Promise { + const instantiationService = accessor.get(IInstantiationService); + const productService = accessor.get(IProductService); + + try { + await showReleaseNotesInEditor(instantiationService, productService.version, true); + } catch (err) { + throw new Error(localize('releaseNotesFromFileNone', "Cannot open the current file as Release Notes")); + } + } +} + registerAction2(ShowCurrentReleaseNotesAction); +registerAction2(ShowCurrentReleaseNotesFromCurrentFileAction); // Update diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index d57afcd149a..5d811ef4399 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -38,9 +38,9 @@ export const DOWNLOAD_URL = new RawContextKey('downloadUrl', ''); let releaseNotesManager: ReleaseNotesManager | undefined = undefined; -export function showReleaseNotesInEditor(instantiationService: IInstantiationService, version: string) { +export function showReleaseNotesInEditor(instantiationService: IInstantiationService, version: string, useCurrentFile: boolean) { if (!releaseNotesManager) { - releaseNotesManager = instantiationService.createInstance(ReleaseNotesManager); + releaseNotesManager = instantiationService.createInstance(ReleaseNotesManager, useCurrentFile); } return releaseNotesManager.show(version); @@ -61,7 +61,7 @@ async function openLatestReleaseNotesInBrowser(accessor: ServicesAccessor) { async function showReleaseNotes(accessor: ServicesAccessor, version: string) { const instantiationService = accessor.get(IInstantiationService); try { - await showReleaseNotesInEditor(instantiationService, version); + await showReleaseNotesInEditor(instantiationService, version, false); } catch (err) { try { await instantiationService.invokeFunction(openLatestReleaseNotesInBrowser); @@ -135,7 +135,7 @@ export class ProductContribution implements IWorkbenchContribution { // was there a major/minor update? if so, open release notes if (shouldShowReleaseNotes && !environmentService.skipReleaseNotes && releaseNotesUrl && lastVersion && currentVersion && isMajorMinorUpdate(lastVersion, currentVersion)) { - showReleaseNotesInEditor(instantiationService, productService.version) + showReleaseNotesInEditor(instantiationService, productService.version, false) .then(undefined, () => { notificationService.prompt( severity.Info, diff --git a/src/vs/workbench/contrib/update/common/update.ts b/src/vs/workbench/contrib/update/common/update.ts index c224d76703a..a5798049ce0 100644 --- a/src/vs/workbench/contrib/update/common/update.ts +++ b/src/vs/workbench/contrib/update/common/update.ts @@ -4,3 +4,4 @@ *--------------------------------------------------------------------------------------------*/ export const ShowCurrentReleaseNotesActionId = 'update.showCurrentReleaseNotes'; +export const ShowCurrentReleaseNotesFromCurrentFileActionId = 'developer.showCurrentFileAsReleaseNotes'; From 2cec75a8583c72d66d5df1d079e08b6934bd44a3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 19 Feb 2024 17:43:23 +0100 Subject: [PATCH 1437/1897] Fixes #202837 --- src/vs/workbench/browser/parts/editor/textDiffEditor.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index fa7c515d416..455922bbcbf 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -195,6 +195,10 @@ export class TextDiffEditor extends AbstractTextEditor imp control.restoreViewState(editorViewState); + if (options?.revealIfVisible) { + control.revealFirstDiff(); + } + return true; } From 9b0d74345c40c29e6ceb4656123debb887714a41 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 19 Feb 2024 19:45:24 +0100 Subject: [PATCH 1438/1897] Git - remove git.experimental.inputValidation setting (#205550) * Git - remove git.experimental.inputValidation setting * Fix compilation error * Fix migration code --- extensions/git/package.json | 14 ++------ extensions/git/package.nls.json | 2 +- extensions/git/src/diagnostics.ts | 30 ++++++++++++++-- extensions/git/src/repository.ts | 59 ++----------------------------- 4 files changed, 33 insertions(+), 72 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index c2745213c68..be52c007a9c 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -2759,13 +2759,8 @@ "default": false }, "git.inputValidation": { - "type": "string", - "enum": [ - "always", - "warn", - "off" - ], - "default": "off", + "type": "boolean", + "default": false, "description": "%config.inputValidation%" }, "git.inputValidationLength": { @@ -2781,11 +2776,6 @@ "default": 50, "markdownDescription": "%config.inputValidationSubjectLength%" }, - "git.experimental.inputValidation": { - "type": "boolean", - "default": false, - "description": "%config.inputValidation%" - }, "git.detectSubmodules": { "type": "boolean", "scope": "resource", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index f6bbb18454d..6eba0f44d8f 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -198,7 +198,7 @@ "config.openAfterClone.prompt": "Always prompt for action.", "config.showInlineOpenFileAction": "Controls whether to show an inline Open File action in the Git changes view.", "config.showPushSuccessNotification": "Controls whether to show a notification when a push is successful.", - "config.inputValidation": "Controls when to show commit message input validation.", + "config.inputValidation": "Controls whether to show commit message input validation diagnostics.", "config.inputValidationLength": "Controls the commit message length threshold for showing a warning.", "config.inputValidationSubjectLength": "Controls the commit message subject length threshold for showing a warning. Unset it to inherit the value of `#git.inputValidationLength#`.", "config.detectSubmodules": "Controls whether to automatically detect Git submodules.", diff --git a/extensions/git/src/diagnostics.ts b/extensions/git/src/diagnostics.ts index 3d5cba205c5..9df39df5177 100644 --- a/extensions/git/src/diagnostics.ts +++ b/extensions/git/src/diagnostics.ts @@ -21,14 +21,38 @@ export class GitCommitInputBoxDiagnosticsManager { constructor(private readonly model: Model) { this.diagnostics = languages.createDiagnosticCollection(); - mapEvent(filterEvent(workspace.onDidChangeTextDocument, e => e.document.uri.scheme === 'vscode-scm'), e => e.document)(this.onDidChangeTextDocument, this, this.disposables); - filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.experimental.inputValidation'))(this.onDidChangeConfiguration, this, this.disposables); + this.migrateInputValidationSettings() + .then(() => { + mapEvent(filterEvent(workspace.onDidChangeTextDocument, e => e.document.uri.scheme === 'vscode-scm'), e => e.document)(this.onDidChangeTextDocument, this, this.disposables); + filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.inputValidation'))(this.onDidChangeConfiguration, this, this.disposables); + }); } public getDiagnostics(uri: Uri): ReadonlyArray { return this.diagnostics.get(uri) ?? []; } + private async migrateInputValidationSettings(): Promise { + try { + const config = workspace.getConfiguration('git'); + const inputValidation = config.inspect<'always' | 'warn' | 'off' | boolean>('inputValidation'); + + if (inputValidation === undefined) { + return; + } + + // Workspace setting + if (typeof inputValidation.workspaceValue === 'string') { + await config.update('inputValidation', inputValidation.workspaceValue !== 'off', false); + } + + // User setting + if (typeof inputValidation.globalValue === 'string') { + await config.update('inputValidation', inputValidation.workspaceValue !== 'off', true); + } + } catch { } + } + private onDidChangeConfiguration(): void { for (const repository of this.model.repositories) { this.onDidChangeTextDocument(repository.inputBox.document); @@ -37,7 +61,7 @@ export class GitCommitInputBoxDiagnosticsManager { private onDidChangeTextDocument(document: TextDocument): void { const config = workspace.getConfiguration('git'); - const inputValidation = config.get('experimental.inputValidation', false) === true; + const inputValidation = config.get('inputValidation', false); if (!inputValidation) { this.diagnostics.set(document.uri, undefined); return; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index cadb3c717f7..edd250797e0 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -975,10 +975,9 @@ export class Repository implements Disposable { this.setCountBadge(); } - validateInput(text: string, position: number): SourceControlInputBoxValidation | undefined { - let tooManyChangesWarning: SourceControlInputBoxValidation | undefined; + validateInput(text: string, _: number): SourceControlInputBoxValidation | undefined { if (this.isRepositoryHuge) { - tooManyChangesWarning = { + return { message: l10n.t('Too many changes were detected. Only the first {0} changes will be shown below.', this.isRepositoryHuge.limit), type: SourceControlInputBoxValidationType.Warning }; @@ -993,59 +992,7 @@ export class Repository implements Disposable { } } - const config = workspace.getConfiguration('git'); - const setting = config.get<'always' | 'warn' | 'off'>('inputValidation'); - - if (setting === 'off') { - return tooManyChangesWarning; - } - - if (/^\s+$/.test(text)) { - return { - message: l10n.t('Current commit message only contains whitespace characters'), - type: SourceControlInputBoxValidationType.Warning - }; - } - - let lineNumber = 0; - let start = 0; - let match: RegExpExecArray | null; - const regex = /\r?\n/g; - - while ((match = regex.exec(text)) && position > match.index) { - start = match.index + match[0].length; - lineNumber++; - } - - const end = match ? match.index : text.length; - - const line = text.substring(start, end); - - let threshold = config.get('inputValidationLength', 50); - - if (lineNumber === 0) { - const inputValidationSubjectLength = config.get('inputValidationSubjectLength', null); - - if (inputValidationSubjectLength !== null) { - threshold = inputValidationSubjectLength; - } - } - - if (line.length <= threshold) { - if (setting !== 'always') { - return tooManyChangesWarning; - } - - return { - message: l10n.t('{0} characters left in current line', threshold - line.length), - type: SourceControlInputBoxValidationType.Information - }; - } else { - return { - message: l10n.t('{0} characters over {1} in current line', line.length - threshold, threshold), - type: SourceControlInputBoxValidationType.Warning - }; - } + return undefined; } /** From 74724fbcb07547ade4c8602a3bf2cbb29172c738 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 19 Feb 2024 20:32:35 +0100 Subject: [PATCH 1439/1897] "Convert Indentation to {tabs,spaces}" only effects auxwindow (fix #205169) (#205577) --- src/vs/platform/hover/browser/hover.ts | 1 - .../parts/editor/breadcrumbsControl.ts | 3 +-- .../browser/parts/editor/editorStatus.ts | 5 ++--- .../browser/parts/statusbar/statusbarPart.ts | 21 ++++++++----------- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 93974adc1a9..c9edff5d2a3 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -238,7 +238,6 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate private lastHoverHideTime = Number.MAX_VALUE; private timeLimit = 200; - private _delay: number; get delay(): number { if (this.instantHover && Date.now() - this.lastHoverHideTime < this.timeLimit) { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 4d9e3906b4e..2a9d829b3ff 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -206,8 +206,7 @@ export class BreadcrumbsControl { @IEditorService private readonly _editorService: IEditorService, @ILabelService private readonly _labelService: ILabelService, @IConfigurationService configurationService: IConfigurationService, - @IBreadcrumbsService breadcrumbsService: IBreadcrumbsService, - @IInstantiationService instantiationService: IInstantiationService, + @IBreadcrumbsService breadcrumbsService: IBreadcrumbsService ) { this.domNode = document.createElement('div'); this.domNode.classList.add('breadcrumbs-control'); diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 968668456a4..e1ee2bc0d94 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -366,10 +366,9 @@ class EditorStatus extends Disposable { } private registerCommands(): void { - CommandsRegistry.registerCommand({ id: 'changeEditorIndentation', handler: () => this.showIndentationPicker() }); + CommandsRegistry.registerCommand({ id: `changeEditorIndentation${this.targetWindowId}`, handler: () => this.showIndentationPicker() }); } - private async showIndentationPicker(): Promise { const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); if (!activeTextEditorControl) { @@ -483,7 +482,7 @@ class EditorStatus extends Disposable { text, ariaLabel: text, tooltip: localize('selectIndentation', "Select Indentation"), - command: 'changeEditorIndentation' + command: `changeEditorIndentation${this.targetWindowId}` }; this.updateElement(this.indentationElement, props, 'status.editor.indentation', StatusbarAlignment.RIGHT, 100.4); diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index ae4fd401456..664a333bb16 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -136,8 +136,14 @@ class StatusbarPart extends Part implements IStatusbarEntryContainer { private leftItemsContainer: HTMLElement | undefined; private rightItemsContainer: HTMLElement | undefined; - - private readonly hoverDelegate: WorkbenchHoverDelegate; + private readonly hoverDelegate = this._register(this.instantiationService.createInstance(WorkbenchHoverDelegate, 'element', true, (_, focus?: boolean) => ( + { + persistence: { + hideOnKeyDown: true, + sticky: focus + } + } + ))); private readonly compactEntriesDisposable = this._register(new MutableDisposable()); private readonly styleOverrides = new Set(); @@ -149,20 +155,11 @@ class StatusbarPart extends Part implements IStatusbarEntryContainer { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IStorageService private readonly storageService: IStorageService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IContextMenuService private contextMenuService: IContextMenuService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { super(id, { hasTitle: false }, themeService, storageService, layoutService); - this.hoverDelegate = this._register(instantiationService.createInstance(WorkbenchHoverDelegate, 'element', true, (_, focus?: boolean) => ( - { - persistence: { - hideOnKeyDown: true, - sticky: focus - } - } - ))); - this.registerListeners(); } From efc04b885ecbd2e48d81c2f28cfe540e9e70cb8d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 19 Feb 2024 19:52:19 +0000 Subject: [PATCH 1440/1897] New proposal for chat variable resolver (#205572) * Tweak ChatFollowup * Remove API TODOs * New proposal for chat variable resolver * Bump distro * Enforce same-extension followup * Add participant proposal to integration test folder * Allow no participant for a followup --- extensions/vscode-api-tests/package.json | 1 + .../src/singlefolder-tests/chat.test.ts | 2 +- package.json | 2 +- .../workbench/api/common/extHost.api.impl.ts | 4 +- .../api/common/extHostChatAgents2.ts | 13 ++++- .../api/common/extHostTypeConverters.ts | 6 +- .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.chatParticipant.d.ts | 55 ++----------------- .../vscode.proposed.chatVariableResolver.d.ts | 52 ++++++++++++++++++ 9 files changed, 78 insertions(+), 58 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index adf3c5fae9f..e9323fc9c43 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -10,6 +10,7 @@ "chatParticipant", "languageModels", "defaultChatParticipant", + "chatVariableResolver", "contribViewsRemote", "contribStatusBarItems", "createFileSystemWatcher", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index 28621d1bb6b..44fa4396796 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -74,7 +74,7 @@ suite('chat', () => { }); test('participant and variable', async () => { - disposables.push(chat.registerVariable('myVar', 'My variable', { + disposables.push(chat.registerChatVariableResolver('myVar', 'My variable', { resolve(_name, _context, _token) { return [{ level: ChatVariableLevel.Full, value: 'myValue' }]; } diff --git a/package.json b/package.json index df171934c34..f734771d907 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "af73a537ea203329debad3df7ca7b42b4799473f", + "distro": "b314654a31bdba8cd2b0c7548e931916d03416bf", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 46292e1bb11..c83b4989782 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1409,8 +1409,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'chatProvider'); return extHostChatProvider.registerLanguageModel(extension, id, provider, metadata); }, - registerVariable(name: string, description: string, resolver: vscode.ChatVariableResolver) { - checkProposedApiEnabled(extension, 'chatParticipant'); + registerChatVariableResolver(name: string, description: string, resolver: vscode.ChatVariableResolver) { + checkProposedApiEnabled(extension, 'chatVariableResolver'); return extHostChatVariables.registerVariableResolver(extension, name, description, resolver); }, registerMappedEditsProvider(selector: vscode.DocumentSelector, provider: vscode.MappedEditsProvider) { diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index b3318d5c314..a4236ff42e6 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -9,12 +9,13 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { Iterable } from 'vs/base/common/iterator'; import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; @@ -261,6 +262,16 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const ehResult = typeConvert.ChatAgentResult.to(result); return (await agent.provideFollowups(ehResult, token)) + .filter(f => { + // The followup must refer to a participant that exists from the same extension + const isValid = !f.participant || Iterable.some( + this._agents.values(), + a => a.id === f.participant && ExtensionIdentifier.equals(a.extension.identifier, agent.extension.identifier)); + if (!isValid) { + this._logService.warn(`[@${agent.id}] ChatFollowup refers to an invalid participant: ${f.participant}`); + } + return isValid; + }) .map(f => typeConvert.ChatFollowup.from(f, request)); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 175a9213112..52ca8dfa1e2 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2201,18 +2201,16 @@ export namespace ChatFollowup { agentId: followup.participant ?? request?.agentId ?? '', subCommand: followup.command ?? request?.command, message: followup.prompt, - title: followup.title, - tooltip: followup.tooltip, + title: followup.label }; } export function to(followup: IChatFollowup): vscode.ChatFollowup { return { prompt: followup.message, - title: followup.title, + label: followup.title, participant: followup.agentId, command: followup.subCommand, - tooltip: followup.tooltip, }; } } diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 0ba3e2b1ea2..76ffed1feeb 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -15,6 +15,7 @@ export const allApiProposals = Object.freeze({ chatParticipantAdditions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts', chatProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts', chatTab: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatTab.d.ts', + chatVariableResolver: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts', codeActionAI: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionAI.d.ts', codeActionRanges: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts', codiconDecoration: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts', diff --git a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts index f4b2242ed46..61caeb78893 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -5,7 +5,6 @@ declare module 'vscode' { - // TODO@API name: Turn? export class ChatRequestTurn { /** @@ -36,7 +35,6 @@ declare module 'vscode' { private constructor(prompt: string, command: string | undefined, variables: ChatResolvedVariable[], participant: { extensionId: string; participant: string }); } - // TODO@API name: Turn? export class ChatResponseTurn { /** @@ -185,9 +183,14 @@ declare module 'vscode' { */ prompt: string; + /** + * A title to show the user, when it is different than the message. + */ + label?: string; + /** * By default, the followup goes to the same participant/command. But this property can be set to invoke a different participant. - * TODO@API do extensions need to specify the extensionID of the participant here as well? + * Followups can only invoke a participant that was contributed by the same extension. */ participant?: string; @@ -195,17 +198,6 @@ declare module 'vscode' { * By default, the followup goes to the same participant/command. But this property can be set to invoke a different command. */ command?: string; - - /** - * A tooltip to show when hovering over the followup. - */ - tooltip?: string; - - /** - * A title to show the user, when it is different than the message. - */ - // TODO@API title vs tooltip? - title?: string; } /** @@ -400,9 +392,6 @@ declare module 'vscode' { * @param value * @returns This stream. */ - // TODO@API is this always inline or not - // TODO@API is this markdown or string? - // TODO@API this influences the rendering, it inserts new lines which is likely a bug progress(value: string): ChatResponseStream; /** @@ -414,8 +403,6 @@ declare module 'vscode' { * @param value A uri or location * @returns This stream. */ - // TODO@API support non-file uris, like http://example.com - // TODO@API support mapped edits reference(value: Uri | Location): ChatResponseStream; /** @@ -426,8 +413,6 @@ declare module 'vscode' { push(part: ChatResponsePart): ChatResponseStream; } - // TODO@API should the name suffix differentiate between rendered items (XYZPart) - // and metadata like XYZItem export class ChatResponseTextPart { value: string; constructor(value: string); @@ -457,7 +442,6 @@ declare module 'vscode' { export class ChatResponseProgressPart { value: string; - // TODO@API inline constructor(value: string); } @@ -489,21 +473,11 @@ declare module 'vscode' { * @returns A new chat participant */ export function createChatParticipant(name: string, handler: ChatRequestHandler): ChatParticipant; - - /** - * Register a variable which can be used in a chat request to any participant. - * @param name The name of the variable, to be used in the chat input as `#name`. - * @param description A description of the variable for the chat input suggest widget. - * @param resolver Will be called to provide the chat variable's value when it is used. - */ - // TODO@API NAME: registerChatVariable, registerChatVariableResolver - export function registerVariable(name: string, description: string, resolver: ChatVariableResolver): Disposable; } /** * The detail level of this chat variable value. */ - // TODO@API maybe for round2 export enum ChatVariableLevel { Short = 1, Medium = 2, @@ -526,21 +500,4 @@ declare module 'vscode' { */ description?: string; } - - export interface ChatVariableContext { - /** - * The message entered by the user, which includes this variable. - */ - prompt: string; - } - - export interface ChatVariableResolver { - /** - * A callback to resolve the value of a chat variable. - * @param name The name of the variable. - * @param context Contextual information about this chat request. - * @param token A cancellation token. - */ - resolve(name: string, context: ChatVariableContext, token: CancellationToken): ProviderResult; - } } diff --git a/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts b/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts new file mode 100644 index 00000000000..32f081ecc5d --- /dev/null +++ b/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export namespace chat { + + /** + * Register a variable which can be used in a chat request to any participant. + * @param name The name of the variable, to be used in the chat input as `#name`. + * @param description A description of the variable for the chat input suggest widget. + * @param resolver Will be called to provide the chat variable's value when it is used. + */ + export function registerChatVariableResolver(name: string, description: string, resolver: ChatVariableResolver): Disposable; + } + + export interface ChatVariableValue { + /** + * The detail level of this chat variable value. If possible, variable resolvers should try to offer shorter values that will consume fewer tokens in an LLM prompt. + */ + level: ChatVariableLevel; + + /** + * The variable's value, which can be included in an LLM prompt as-is, or the chat participant may decide to read the value and do something else with it. + */ + value: string | Uri; + + /** + * A description of this value, which could be provided to the LLM as a hint. + */ + description?: string; + } + + export interface ChatVariableContext { + /** + * The message entered by the user, which includes this variable. + */ + prompt: string; + } + + export interface ChatVariableResolver { + /** + * A callback to resolve the value of a chat variable. + * @param name The name of the variable. + * @param context Contextual information about this chat request. + * @param token A cancellation token. + */ + resolve(name: string, context: ChatVariableContext, token: CancellationToken): ProviderResult; + } +} From ea9f1dacfb5094506524378e62a22eca7ba4a75b Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:23:05 -0800 Subject: [PATCH 1441/1897] fix quote type in issue reporter (#205582) changed quote type --- src/vs/platform/issue/electron-main/issueMainService.ts | 4 ++-- .../services/issue/electron-sandbox/issueService.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 32f2be6af45..0bcf0f31928 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -459,8 +459,8 @@ export class IssueMainService implements IIssueMainService { const replyChannel = `vscode:triggerReporterMenu`; const cts = new CancellationTokenSource(); window.sendWhenReady(replyChannel, cts.token, { replyChannel, extensionId, extensionName }); - const result = await raceTimeout(new Promise(resolve => validatedIpcMain.once('vscode:triggerReporterMenuResponse:${extensionId}', (_: unknown, data: IssueReporterData | undefined) => resolve(data))), 5000, () => { - this.logService.error('Error: Extension ${extensionId} timed out waiting for menu response'); + const result = await raceTimeout(new Promise(resolve => validatedIpcMain.once(`vscode:triggerReporterMenuResponse:${extensionId}`, (_: unknown, data: IssueReporterData | undefined) => resolve(data))), 5000, () => { + this.logService.error(`Error: Extension ${extensionId} timed out waiting for menu response`); cts.cancel(); }); return result as IssueReporterData | undefined; diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts index 039ce81f5a9..add5d98fc2e 100644 --- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts @@ -85,7 +85,7 @@ export class NativeIssueService implements IWorkbenchIssueService { } } const result = [this._providers.has(extensionId.toLowerCase()), this._handlers.has(extensionId.toLowerCase())]; - ipcRenderer.send('vscode:triggerReporterStatusResponse', result); + ipcRenderer.send(`vscode:triggerReporterStatusResponse`, result); }); ipcRenderer.on('vscode:triggerReporterMenu', async (event, arg) => { const extensionId = arg.extensionId; @@ -108,7 +108,7 @@ export class NativeIssueService implements IWorkbenchIssueService { if (!this.extensionIdentifierSet.has(extensionId)) { // send undefined to indicate no action was taken - ipcRenderer.send('vscode:triggerReporterMenuResponse:${extensionId}', undefined); + ipcRenderer.send(`vscode:triggerReporterMenuResponse:${extensionId}`, undefined); } menu.dispose(); }); @@ -186,7 +186,7 @@ export class NativeIssueService implements IWorkbenchIssueService { }, dataOverrides); if (issueReporterData.extensionId && this.extensionIdentifierSet.has(issueReporterData.extensionId)) { - ipcRenderer.send('vscode:triggerReporterMenuResponse:${issueReporterData.extensionId}', issueReporterData); + ipcRenderer.send(`vscode:triggerReporterMenuResponse:${issueReporterData.extensionId}`, issueReporterData); this.extensionIdentifierSet.delete(new ExtensionIdentifier(issueReporterData.extensionId)); } return this.issueMainService.openReporter(issueReporterData); From 232e9a2bef554254ec3d370cdbbe6074bbd422c8 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 19 Feb 2024 21:25:13 +0100 Subject: [PATCH 1442/1897] Git - comment out GitIncomingChangesFileDecorationProvider (#205583) --- extensions/git/package.json | 40 ------ extensions/git/src/decorationProvider.ts | 170 +++++++++++------------ 2 files changed, 85 insertions(+), 125 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index be52c007a9c..f1b45e74665 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -3198,46 +3198,6 @@ "highContrast": "#8db9e2", "highContrastLight": "#1258a7" } - }, - { - "id": "gitDecoration.incomingAddedForegroundColor", - "description": "%colors.incomingAdded%", - "defaults": { - "light": "#587c0c", - "dark": "#81b88b", - "highContrast": "#1b5225", - "highContrastLight": "#374e06" - } - }, - { - "id": "gitDecoration.incomingDeletedForegroundColor", - "description": "%colors.incomingDeleted%", - "defaults": { - "light": "#ad0707", - "dark": "#c74e39", - "highContrast": "#c74e39", - "highContrastLight": "#ad0707" - } - }, - { - "id": "gitDecoration.incomingRenamedForegroundColor", - "description": "%colors.incomingRenamed%", - "defaults": { - "light": "#007100", - "dark": "#73C991", - "highContrast": "#73C991", - "highContrastLight": "#007100" - } - }, - { - "id": "gitDecoration.incomingModifiedForegroundColor", - "description": "%colors.incomingModified%", - "defaults": { - "light": "#895503", - "dark": "#E2C08D", - "highContrast": "#E2C08D", - "highContrastLight": "#895503" - } } ], "configurationDefaults": { diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index 9e3e356628d..5167b1eb95e 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { window, workspace, Uri, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, ThemeColor, l10n } from 'vscode'; +import { window, workspace, Uri, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, ThemeColor } from 'vscode'; import * as path from 'path'; import { Repository, GitResourceGroup } from './repository'; import { Model } from './model'; import { debounce } from './decorators'; import { filterEvent, dispose, anyEvent, fireEvent, PromiseSource, combinedDisposable } from './util'; -import { Change, GitErrorCodes, Status } from './api/git'; +import { GitErrorCodes, Status } from './api/git'; class GitIgnoreDecorationProvider implements FileDecorationProvider { @@ -153,100 +153,100 @@ class GitDecorationProvider implements FileDecorationProvider { } } -class GitIncomingChangesFileDecorationProvider implements FileDecorationProvider { +// class GitIncomingChangesFileDecorationProvider implements FileDecorationProvider { - private readonly _onDidChangeDecorations = new EventEmitter(); - readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; +// private readonly _onDidChangeDecorations = new EventEmitter(); +// readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; - private decorations = new Map(); - private readonly disposables: Disposable[] = []; +// private decorations = new Map(); +// private readonly disposables: Disposable[] = []; - constructor(private readonly repository: Repository) { - this.disposables.push(window.registerFileDecorationProvider(this)); - repository.historyProvider.onDidChangeCurrentHistoryItemGroup(this.onDidChangeCurrentHistoryItemGroup, this, this.disposables); - } +// constructor(private readonly repository: Repository) { +// this.disposables.push(window.registerFileDecorationProvider(this)); +// repository.historyProvider.onDidChangeCurrentHistoryItemGroup(this.onDidChangeCurrentHistoryItemGroup, this, this.disposables); +// } - private async onDidChangeCurrentHistoryItemGroup(): Promise { - const newDecorations = new Map(); - await this.collectIncomingChangesFileDecorations(newDecorations); - const uris = new Set([...this.decorations.keys()].concat([...newDecorations.keys()])); +// private async onDidChangeCurrentHistoryItemGroup(): Promise { +// const newDecorations = new Map(); +// await this.collectIncomingChangesFileDecorations(newDecorations); +// const uris = new Set([...this.decorations.keys()].concat([...newDecorations.keys()])); - this.decorations = newDecorations; - this._onDidChangeDecorations.fire([...uris.values()].map(value => Uri.parse(value, true))); - } +// this.decorations = newDecorations; +// this._onDidChangeDecorations.fire([...uris.values()].map(value => Uri.parse(value, true))); +// } - private async collectIncomingChangesFileDecorations(bucket: Map): Promise { - for (const change of await this.getIncomingChanges()) { - switch (change.status) { - case Status.INDEX_ADDED: - bucket.set(change.uri.toString(), { - badge: '↓A', - color: new ThemeColor('gitDecoration.incomingAddedForegroundColor'), - tooltip: l10n.t('Incoming Changes (added)'), - }); - break; - case Status.DELETED: - bucket.set(change.uri.toString(), { - badge: '↓D', - color: new ThemeColor('gitDecoration.incomingDeletedForegroundColor'), - tooltip: l10n.t('Incoming Changes (deleted)'), - }); - break; - case Status.INDEX_RENAMED: - bucket.set(change.originalUri.toString(), { - badge: '↓R', - color: new ThemeColor('gitDecoration.incomingRenamedForegroundColor'), - tooltip: l10n.t('Incoming Changes (renamed)'), - }); - break; - case Status.MODIFIED: - bucket.set(change.uri.toString(), { - badge: '↓M', - color: new ThemeColor('gitDecoration.incomingModifiedForegroundColor'), - tooltip: l10n.t('Incoming Changes (modified)'), - }); - break; - default: { - bucket.set(change.uri.toString(), { - badge: '↓~', - color: new ThemeColor('gitDecoration.incomingModifiedForegroundColor'), - tooltip: l10n.t('Incoming Changes'), - }); - break; - } - } - } - } +// private async collectIncomingChangesFileDecorations(bucket: Map): Promise { +// for (const change of await this.getIncomingChanges()) { +// switch (change.status) { +// case Status.INDEX_ADDED: +// bucket.set(change.uri.toString(), { +// badge: '↓A', +// color: new ThemeColor('gitDecoration.incomingAddedForegroundColor'), +// tooltip: l10n.t('Incoming Changes (added)'), +// }); +// break; +// case Status.DELETED: +// bucket.set(change.uri.toString(), { +// badge: '↓D', +// color: new ThemeColor('gitDecoration.incomingDeletedForegroundColor'), +// tooltip: l10n.t('Incoming Changes (deleted)'), +// }); +// break; +// case Status.INDEX_RENAMED: +// bucket.set(change.originalUri.toString(), { +// badge: '↓R', +// color: new ThemeColor('gitDecoration.incomingRenamedForegroundColor'), +// tooltip: l10n.t('Incoming Changes (renamed)'), +// }); +// break; +// case Status.MODIFIED: +// bucket.set(change.uri.toString(), { +// badge: '↓M', +// color: new ThemeColor('gitDecoration.incomingModifiedForegroundColor'), +// tooltip: l10n.t('Incoming Changes (modified)'), +// }); +// break; +// default: { +// bucket.set(change.uri.toString(), { +// badge: '↓~', +// color: new ThemeColor('gitDecoration.incomingModifiedForegroundColor'), +// tooltip: l10n.t('Incoming Changes'), +// }); +// break; +// } +// } +// } +// } - private async getIncomingChanges(): Promise { - try { - const historyProvider = this.repository.historyProvider; - const currentHistoryItemGroup = historyProvider.currentHistoryItemGroup; +// private async getIncomingChanges(): Promise { +// try { +// const historyProvider = this.repository.historyProvider; +// const currentHistoryItemGroup = historyProvider.currentHistoryItemGroup; - if (!currentHistoryItemGroup?.base) { - return []; - } +// if (!currentHistoryItemGroup?.base) { +// return []; +// } - const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(currentHistoryItemGroup.id, currentHistoryItemGroup.base.id); - if (!ancestor) { - return []; - } +// const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(currentHistoryItemGroup.id, currentHistoryItemGroup.base.id); +// if (!ancestor) { +// return []; +// } - const changes = await this.repository.diffBetween(ancestor.id, currentHistoryItemGroup.base.id); - return changes; - } catch (err) { - return []; - } - } +// const changes = await this.repository.diffBetween(ancestor.id, currentHistoryItemGroup.base.id); +// return changes; +// } catch (err) { +// return []; +// } +// } - provideFileDecoration(uri: Uri): FileDecoration | undefined { - return this.decorations.get(uri.toString()); - } +// provideFileDecoration(uri: Uri): FileDecoration | undefined { +// return this.decorations.get(uri.toString()); +// } - dispose(): void { - dispose(this.disposables); - } -} +// dispose(): void { +// dispose(this.disposables); +// } +// } export class GitDecorations { @@ -287,7 +287,7 @@ export class GitDecorations { private onDidOpenRepository(repository: Repository): void { const providers = combinedDisposable([ new GitDecorationProvider(repository), - new GitIncomingChangesFileDecorationProvider(repository) + // new GitIncomingChangesFileDecorationProvider(repository) ]); this.providers.set(repository, providers); From 047cf8cc51e325ecbd082bb8de42b5f769dbc433 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 19 Feb 2024 21:54:02 +0100 Subject: [PATCH 1443/1897] Remove hover from elements using context view (#205586) fix #205526 --- .../base/browser/ui/actionbar/actionViewItems.ts | 15 ++++++++++----- .../browser/languageStatus.contribution.ts | 3 ++- src/vs/workbench/electron-sandbox/window.ts | 5 +++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index a714411e3a2..fd7424b0f72 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -226,12 +226,17 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { const title = this.getTooltip() ?? ''; this.updateAriaLabel(); - if (!this.customHover) { - const hoverDelegate = this.options.hoverDelegate ?? getDefaultHoverDelegate('element'); - this.customHover = setupCustomHover(hoverDelegate, this.element, title); - this._store.add(this.customHover); + if (this.options.hoverDelegate?.showNativeHover) { + /* While custom hover is not supported with context view */ + this.element.title = title; } else { - this.customHover.update(title); + if (!this.customHover) { + const hoverDelegate = this.options.hoverDelegate ?? getDefaultHoverDelegate('element'); + this.customHover = setupCustomHover(hoverDelegate, this.element, title); + this._store.add(this.customHover); + } else { + this.customHover.update(title); + } } } diff --git a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts index 45e898b8fc1..a444b099637 100644 --- a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts +++ b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts @@ -33,6 +33,7 @@ import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { nativeHoverDelegate } from 'vs/platform/hover/browser/hover'; class LanguageStatusViewModel { @@ -327,7 +328,7 @@ class LanguageStatus { } // -- pin - const actionBar = new ActionBar(right, {}); + const actionBar = new ActionBar(right, { hoverDelegate: nativeHoverDelegate }); store.add(actionBar); let action: Action; if (!isPinned) { diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 6880e360e02..f207e15834f 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -79,6 +79,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { ThemeIcon } from 'vs/base/common/themables'; import { getWorkbenchContribution } from 'vs/workbench/common/contributions'; import { DynamicWorkbenchSecurityConfiguration } from 'vs/workbench/common/configuration'; +import { nativeHoverDelegate } from 'vs/platform/hover/browser/hover'; export class NativeWindow extends BaseWindow { @@ -1163,7 +1164,7 @@ class ZoomStatusEntry extends Disposable { this.zoomLevelLabel = zoomLevelLabel; disposables.add(toDisposable(() => this.zoomLevelLabel = undefined)); - const actionBarLeft = disposables.add(new ActionBar(left)); + const actionBarLeft = disposables.add(new ActionBar(left, { hoverDelegate: nativeHoverDelegate })); actionBarLeft.push(zoomOutAction, { icon: true, label: false, keybinding: this.keybindingService.lookupKeybinding(zoomOutAction.id)?.getLabel() }); actionBarLeft.push(this.zoomLevelLabel, { icon: false, label: true }); actionBarLeft.push(zoomInAction, { icon: true, label: false, keybinding: this.keybindingService.lookupKeybinding(zoomInAction.id)?.getLabel() }); @@ -1172,7 +1173,7 @@ class ZoomStatusEntry extends Disposable { right.classList.add('zoom-status-right'); container.appendChild(right); - const actionBarRight = disposables.add(new ActionBar(right)); + const actionBarRight = disposables.add(new ActionBar(right, { hoverDelegate: nativeHoverDelegate })); actionBarRight.push(zoomResetAction, { icon: false, label: true }); actionBarRight.push(zoomSettingsAction, { icon: true, label: false, keybinding: this.keybindingService.lookupKeybinding(zoomSettingsAction.id)?.getLabel() }); From b44593a612337289c079425a5b2cc7010216eef4 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 19 Feb 2024 22:34:15 +0100 Subject: [PATCH 1444/1897] Fixes #204923 (#205605) fix #204923 --- .../browser/parts/editor/media/multieditortabscontrol.css | 2 ++ .../workbench/contrib/files/browser/views/media/openeditors.css | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css index 8a883475400..18e66d6a1df 100644 --- a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css @@ -386,11 +386,13 @@ .monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.sticky.dirty > .tab-actions .action-label:not(:hover)::before, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sticky.dirty > .tab-actions .action-label:not(:hover)::before { content: "\ebb2"; /* use `pinned-dirty` icon unicode for sticky-dirty indication */ + font-family: 'codicon'; } .monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-actions .action-label:not(:hover)::before, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-actions .action-label:not(:hover)::before { content: "\ea71"; /* use `circle-filled` icon unicode for dirty indication */ + font-family: 'codicon'; } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-actions .action-label, diff --git a/src/vs/workbench/contrib/files/browser/views/media/openeditors.css b/src/vs/workbench/contrib/files/browser/views/media/openeditors.css index 12a175f0ebc..cb154b83db9 100644 --- a/src/vs/workbench/contrib/files/browser/views/media/openeditors.css +++ b/src/vs/workbench/contrib/files/browser/views/media/openeditors.css @@ -38,10 +38,12 @@ .open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .codicon-pinned::before { content: "\ebb2"; /* use `pinned-dirty` icon unicode for sticky-dirty indication */ + font-family: 'codicon'; } .open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .codicon-close::before { content: "\ea71"; /* use `circle-filled` icon unicode for dirty indication */ + font-family: 'codicon'; } .open-editors .monaco-list .monaco-list-row > .monaco-action-bar .action-close-all-files, From d686f5f765f5c36ea11ac909309b6f1db0662de4 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 19 Feb 2024 22:04:48 +0000 Subject: [PATCH 1445/1897] Fix "Used reference" rendering (#205606) --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 609da660c84..c0f1827014a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -1270,14 +1270,14 @@ class ContentReferencesListRenderer implements IListRenderer Date: Mon, 19 Feb 2024 15:32:06 -0800 Subject: [PATCH 1446/1897] chore: run OSS tool (#205608) --- cglicenses.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cglicenses.json b/cglicenses.json index d61164acd3d..1d6851e38c8 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -589,5 +589,15 @@ "prependLicenseText": [ "Copyright (c) heap.js authors" ] + }, + { + // Reason: mono-repo where the individual packages are also dual-licensed under MIT and Apache-2.0 + "name": "system-configuration", + "fullLicenseTextUri": "https://github.com/mullvad/system-configuration-rs/blob/main/system-configuration/LICENSE-MIT" + }, + { + // Reason: mono-repo where the individual packages are also dual-licensed under MIT and Apache-2.0 + "name": "system-configuration-sys", + "fullLicenseTextUri": "https://github.com/mullvad/system-configuration-rs/blob/main/system-configuration-sys/LICENSE-MIT" } ] From 01ce8d869e4220577e194b2dd57bfa12437dfcf5 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 20 Feb 2024 01:02:24 +0000 Subject: [PATCH 1447/1897] More chat participant API comments (#205617) More docs --- .../workbench/api/common/extHost.api.impl.ts | 1 - .../api/common/extHostChatAgents2.ts | 4 +- .../api/common/extHostTypeConverters.ts | 29 ++++----- src/vs/workbench/api/common/extHostTypes.ts | 10 +-- .../vscode.proposed.chatParticipant.d.ts | 63 +++++++++++-------- 5 files changed, 56 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index c83b4989782..71ec5250fb9 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1671,7 +1671,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I RelatedInformationType: extHostTypes.RelatedInformationType, SpeechToTextStatus: extHostTypes.SpeechToTextStatus, KeywordRecognitionStatus: extHostTypes.KeywordRecognitionStatus, - ChatResponseTextPart: extHostTypes.ChatResponseTextPart, ChatResponseMarkdownPart: extHostTypes.ChatResponseMarkdownPart, ChatResponseFileTreePart: extHostTypes.ChatResponseFileTreePart, ChatResponseAnchorPart: extHostTypes.ChatResponseAnchorPart, diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index a4236ff42e6..9f4f87ac999 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -234,7 +234,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { res.push(new extHostTypes.ChatRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', participant: h.request.agentId })); // RESPONSE turn - const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))); + const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.fromContent(r, this.commands.converter))); res.push(new extHostTypes.ChatResponseTurn(parts, result, { extensionId: '', participant: h.request.agentId }, h.request.command)); } @@ -402,7 +402,7 @@ class ExtHostChatAgent { return { name: c.name, - description: c.description, + description: c.description ?? '', followupPlaceholder: c.isSticky2?.placeholder, isSticky: c.isSticky2?.isSticky ?? c.isSticky, sampleRequest: c.sampleRequest diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 52ca8dfa1e2..db879caea6b 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2330,18 +2330,6 @@ export namespace InteractiveEditorResponseFeedbackKind { } } -export namespace ChatResponseTextPart { - export function to(part: vscode.ChatResponseTextPart): Dto { - return { - kind: 'markdownContent', - content: MarkdownString.from(new types.MarkdownString().appendText(part.value)) - }; - } - export function from(part: Dto): vscode.ChatResponseTextPart { - return new types.ChatResponseTextPart(part.content.value); - } -} - export namespace ChatResponseMarkdownPart { export function to(part: vscode.ChatResponseMarkdownPart): Dto { return { @@ -2475,14 +2463,27 @@ export namespace ChatResponsePart { } export function from(part: extHostProtocol.IChatProgressDto, commandsConverter: CommandsConverter): vscode.ChatResponsePart | undefined { + switch (part.kind) { + case 'reference': return ChatResponseReferencePart.from(part); + case 'markdownContent': + case 'inlineReference': + case 'progressMessage': + case 'treeData': + case 'command': + return fromContent(part, commandsConverter); + } + return undefined; + } + + export function fromContent(part: extHostProtocol.IChatContentProgressDto, commandsConverter: CommandsConverter): vscode.ChatResponseMarkdownPart | vscode.ChatResponseFileTreePart | vscode.ChatResponseAnchorPart | vscode.ChatResponseCommandButtonPart | undefined { switch (part.kind) { case 'markdownContent': return ChatResponseMarkdownPart.from(part); case 'inlineReference': return ChatResponseAnchorPart.from(part); - case 'reference': return ChatResponseReferencePart.from(part); - case 'progressMessage': return ChatResponseProgressPart.from(part); + case 'progressMessage': return undefined; case 'treeData': return ChatResponseFilesPart.from(part); case 'command': return ChatResponseCommandButtonPart.from(part, commandsConverter); } + return undefined; } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index db038450765..4827cdfbfeb 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4205,14 +4205,6 @@ export enum ChatResultFeedbackKind { Helpful = 1, } - -export class ChatResponseTextPart { - value: string; - constructor(value: string) { - this.value = value; - } -} - export class ChatResponseMarkdownPart { value: vscode.MarkdownString; constructor(value: string | vscode.MarkdownString) { @@ -4272,7 +4264,7 @@ export class ChatRequestTurn implements vscode.ChatRequestTurn { export class ChatResponseTurn implements vscode.ChatResponseTurn { constructor( - readonly response: ReadonlyArray, + readonly response: ReadonlyArray, readonly result: vscode.ChatResult, readonly participant: { extensionId: string; participant: string }, readonly command?: string diff --git a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts index 61caeb78893..468f12ae690 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -5,12 +5,15 @@ declare module 'vscode' { + /** + * Represents a user request in chat history. + */ export class ChatRequestTurn { /** * The prompt as entered by the user. * - * Information about variables used in this request are is stored in {@link ChatRequest.variables}. + * Information about variables used in this request is stored in {@link ChatRequestTurn.variables}. * * *Note* that the {@link ChatParticipant.name name} of the participant and the {@link ChatCommand.name command} * are not part of the prompt. @@ -35,12 +38,15 @@ declare module 'vscode' { private constructor(prompt: string, command: string | undefined, variables: ChatResolvedVariable[], participant: { extensionId: string; participant: string }); } + /** + * Represents a chat participant's response in chat history. + */ export class ChatResponseTurn { /** - * The content that was received from the chat participant. Only the progress parts that represent actual content (not metadata) are represented. + * The content that was received from the chat participant. Only the stream parts that represent actual content (not metadata) are represented. */ - readonly response: ReadonlyArray; + readonly response: ReadonlyArray; /** * The result that was received from the chat participant. @@ -48,13 +54,16 @@ declare module 'vscode' { readonly result: ChatResult; /** - * The name of the chat participant and contributing extension to which this request was directed. + * The name of the chat participant and contributing extension that this response came from. */ readonly participant: { readonly extensionId: string; readonly participant: string }; + /** + * The name of the command that this response came from. + */ readonly command?: string; - private constructor(response: ReadonlyArray, result: ChatResult, participant: { extensionId: string; participant: string }); + private constructor(response: ReadonlyArray, result: ChatResult, participant: { extensionId: string; participant: string }); } export interface ChatContext { @@ -98,7 +107,7 @@ declare module 'vscode' { errorDetails?: ChatErrorDetails; /** - * Arbitrary metadata for this result. Can be anything but must be JSON-stringifyable. + * Arbitrary metadata for this result. Can be anything, but must be JSON-stringifyable. */ readonly metadata?: { readonly [key: string]: any }; } @@ -123,7 +132,8 @@ declare module 'vscode' { */ export interface ChatResultFeedback { /** - * This instance of ChatResult has the same properties as the result returned from the participant callback, including `metadata`, but is not the same instance. + * The ChatResult that the user is providing feedback for. + * This instance has the same properties as the result returned from the participant callback, including `metadata`, but is not the same instance. */ readonly result: ChatResult; @@ -158,8 +168,11 @@ declare module 'vscode' { readonly isSticky?: boolean; } + /** + * A ChatCommandProvider returns {@link ChatCommands commands} that can be invoked on a chat participant using `/`. For example, `@participant /command`. + * These can be used as shortcuts to let the user explicitly invoke different functionalities provided by the participant. + */ export interface ChatCommandProvider { - /** * Returns a list of commands that its participant is capable of handling. A command * can be selected by the user and will then be passed to the {@link ChatRequestHandler handler} @@ -175,7 +188,7 @@ declare module 'vscode' { } /** - * A followup question suggested by the model. + * A followup question suggested by the participant. */ export interface ChatFollowup { /** @@ -184,7 +197,7 @@ declare module 'vscode' { prompt: string; /** - * A title to show the user, when it is different than the message. + * A title to show the user. The prompt will be shown by default, when this is unspecified. */ label?: string; @@ -205,8 +218,8 @@ declare module 'vscode' { */ export interface ChatFollowupProvider { /** - * - * @param result The same instance of the result object that was returned by the chat participant, and it can be extended with arbitrary properties if needed. + * Provide followups for the given result. + * @param result This instance has the same properties as the result returned from the participant callback, including `metadata`, but is not the same instance. * @param token A cancellation token. */ provideFollowups(result: ChatResult, token: CancellationToken): ProviderResult; @@ -217,9 +230,11 @@ declare module 'vscode' { */ export type ChatRequestHandler = (request: ChatRequest, context: ChatContext, response: ChatResponseStream, token: CancellationToken) => ProviderResult; - + /** + * A chat participant can be invoked by the user in a chat session, using the `@` prefix. When it is invoked, it handles the chat request and is solely + * responsible for providing a response to the user. A ChatParticipant is created using {@link chat.createChatParticipant}. + */ export interface ChatParticipant { - /** * The short name by which this participant is referred to in the UI, e.g `workspace`. */ @@ -227,6 +242,7 @@ declare module 'vscode' { /** * The full name of this participant. + * TODO@API This is only used for the default participant, but it seems useful, so should we keep it so we can use it in the future? */ fullName: string; @@ -293,7 +309,6 @@ declare module 'vscode' { * A resolved variable value is a name-value pair as well as the range in the prompt where a variable was used. */ export interface ChatResolvedVariable { - /** * The name of the variable. * @@ -315,7 +330,6 @@ declare module 'vscode' { } export interface ChatRequest { - /** * The prompt as entered by the user. * @@ -336,7 +350,7 @@ declare module 'vscode' { * * *Note* that the prompt contains varibale references as authored and that it is up to the participant * to further modify the prompt, for instance by inlining variable values or creating links to - * headings which contain the resolved values. vvariables are sorted in reverse by their range + * headings which contain the resolved values. Variables are sorted in reverse by their range * in the prompt. That means the last variable in the prompt is the first in this list. This simplifies * string-manipulation of the prompt. */ @@ -344,8 +358,12 @@ declare module 'vscode' { readonly variables: readonly ChatResolvedVariable[]; } + /** + * The ChatResponseStream is how a participant is able to return content to the chat view. It provides several methods for streaming different types of content + * which will be rendered in an appropriate way in the chat view. A participant can use the helper method for the type of content it wants to return, or it + * can instantiate a {@link ChatResponsePart} and use the generic {@link ChatResponseStream.push} method to return it. + */ export interface ChatResponseStream { - /** * Push a markdown part to this stream. Short-hand for * `push(new ChatResponseMarkdownPart(value))`. @@ -359,6 +377,7 @@ declare module 'vscode' { /** * Push an anchor part to this stream. Short-hand for * `push(new ChatResponseAnchorPart(value, title))`. + * An anchor is an inline reference to some type of resource. * * @param value A uri or location * @param title An optional title that is rendered with value @@ -413,11 +432,6 @@ declare module 'vscode' { push(part: ChatResponsePart): ChatResponseStream; } - export class ChatResponseTextPart { - value: string; - constructor(value: string); - } - export class ChatResponseMarkdownPart { value: MarkdownString; constructor(value: string | MarkdownString); @@ -458,12 +472,11 @@ declare module 'vscode' { /** * Represents the different chat response types. */ - export type ChatResponsePart = ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart + export type ChatResponsePart = ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseProgressPart | ChatResponseReferencePart | ChatResponseCommandButtonPart; export namespace chat { - /** * Create a new {@link ChatParticipant chat participant} instance. * From 864f4f41cb18802a78da048fd81a9cffa392dd6c Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 20 Feb 2024 13:08:42 +0100 Subject: [PATCH 1448/1897] fix #205648 (#205653) --- .../files/browser/views/openEditorsView.ts | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 9f21e5014ba..c6fd9a02c0d 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -67,6 +67,7 @@ export class OpenEditorsView extends ViewPane { private dirtyCountElement!: HTMLElement; private listRefreshScheduler: RunOnceScheduler | undefined; private structuralRefreshDelay: number; + private dnd: OpenEditorsDragAndDrop | undefined; private list: WorkbenchList | undefined; private listLabels: ResourceLabels | undefined; private needsRefresh = false; @@ -198,13 +199,16 @@ export class OpenEditorsView extends ViewPane { if (this.listLabels) { this.listLabels.clear(); } + + this.dnd = new OpenEditorsDragAndDrop(this.sortOrder, this.instantiationService, this.editorGroupService); + this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this.list = this.instantiationService.createInstance(WorkbenchList, 'OpenEditors', container, delegate, [ new EditorGroupRenderer(this.keybindingService, this.instantiationService), new OpenEditorRenderer(this.listLabels, this.instantiationService, this.keybindingService, this.configurationService) ], { identityProvider: { getId: (element: OpenEditor | IEditorGroup) => element instanceof OpenEditor ? element.getId() : element.id.toString() }, - dnd: new OpenEditorsDragAndDrop(this.instantiationService, this.editorGroupService), + dnd: this.dnd, overrideStyles: { listBackground: this.getBackgroundColor() }, @@ -448,6 +452,9 @@ export class OpenEditorsView extends ViewPane { // Trigger a 'repaint' when decoration settings change or the sort order changed if (event.affectsConfiguration('explorer.decorations') || event.affectsConfiguration('explorer.openEditors.sortOrder')) { this.sortOrder = this.configurationService.getValue('explorer.openEditors.sortOrder'); + if (this.dnd) { + this.dnd.sortOrder = this.sortOrder; + } this.listRefreshScheduler?.schedule(); } } @@ -672,10 +679,18 @@ class OpenEditorRenderer implements IListRenderer { + private _sortOrder: 'editorOrder' | 'alphabetical' | 'fullPath'; + public set sortOrder(value: 'editorOrder' | 'alphabetical' | 'fullPath') { + this._sortOrder = value; + } + constructor( + sortOrder: 'editorOrder' | 'alphabetical' | 'fullPath', private instantiationService: IInstantiationService, private editorGroupService: IEditorGroupsService - ) { } + ) { + this._sortOrder = sortOrder; + } @memoize private get dropHandler(): ResourcesDropHandler { return this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: false }); @@ -724,6 +739,16 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop Date: Tue, 20 Feb 2024 13:53:54 +0100 Subject: [PATCH 1449/1897] Hover Delay Fix (#205665) fix #205658 --- src/vs/base/browser/ui/iconLabel/iconLabelHover.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts index 508a643abb4..20ad4662b0d 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts @@ -180,6 +180,7 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM } if (hadHover) { hoverDelegate.onDidHideHover?.(); + hoverWidget = undefined; } }; From 035e55cf4bf4b550bbe1f77558d41d74e09b0934 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Tue, 20 Feb 2024 16:09:56 +0100 Subject: [PATCH 1450/1897] Adjust width for rename suggestions (#205689) rename suggestions: adjust width so longer candidates don't overflow --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index dc5131bba43..cce562b9fb9 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -501,9 +501,11 @@ class CandidatesView { this._listWidget.splice(0, 0, candidates); - this._listWidget.layout(height, this._pickListWidth(candidates)); + const width = Math.max(200, 4 /* padding */ + 16 /* sparkle icon */ + 5 /* margin-left */ + this._pickListWidth(candidates)); // TODO@ulugbekna: approximate calc - clean this up + this._listWidget.layout(height, width); this._listContainer.style.height = `${height}px`; + this._listContainer.style.width = `${width}px`; } public clearCandidates(): void { @@ -577,7 +579,7 @@ class CandidatesView { } private _pickListWidth(candidates: NewSymbolName[]): number { - return Math.max(...candidates.map(c => c.newSymbolName.length)) * 7 /* approximate # of pixes taken by a single character */; + return Math.ceil(Math.max(...candidates.map(c => c.newSymbolName.length)) * 7.2) /* approximate # of pixes taken by a single character */; } } From f34d48a3cdf7187eabb63df86a6d2706e2bc0008 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 20 Feb 2024 16:10:30 +0100 Subject: [PATCH 1451/1897] Add telemetry for settings in release notes (#205673) --- .../markdown/browser/markdownSettingRenderer.ts | 15 ++++++++++++++- .../test/browser/markdownSettingRenderer.test.ts | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index 72f4dfd0e2e..5193d73e164 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -13,6 +13,7 @@ import { DefaultSettings } from 'vs/workbench/services/preferences/common/prefer import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IAction } from 'vs/base/common/actions'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const codeSettingRegex = /^/; const codeFeatureRegex = /^/; @@ -26,7 +27,8 @@ export class SimpleSettingRenderer { constructor( @IConfigurationService private readonly _configurationService: IConfigurationService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, - @IPreferencesService private readonly _preferencesService: IPreferencesService + @IPreferencesService private readonly _preferencesService: IPreferencesService, + @ITelemetryService private readonly _telemetryService: ITelemetryService ) { this._defaultSettings = new DefaultSettings([], ConfigurationTarget.USER); } @@ -291,6 +293,17 @@ export class SimpleSettingRenderer { async updateSetting(uri: URI, x: number, y: number) { if (uri.scheme === Schemas.codeSetting) { + type ReleaseNotesSettingUsedClassification = { + owner: 'alexr00'; + comment: 'Used to understand if the the action to update settings from the release notes is used.'; + settingId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The id of the setting that was clicked on in the release notes' }; + }; + type ReleaseNotesSettingUsed = { + settingId: string; + }; + this._telemetryService.publicLog2('releaseNotesSettingAction', { + settingId: uri.authority + }); return this.showContextMenu(uri, x, y); } else if (uri.scheme === Schemas.codeFeature) { return this.setFeatureState(uri); diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index 271cf0d2da5..fb609bd79b7 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -61,7 +61,7 @@ suite('Markdown Setting Renderer Test', () => { preferencesService = {}; contextMenuService = {}; Registry.as(Extensions.Configuration).registerConfiguration(configuration); - settingRenderer = new SimpleSettingRenderer(configurationService, contextMenuService, preferencesService); + settingRenderer = new SimpleSettingRenderer(configurationService, contextMenuService, preferencesService, { publicLog2: () => { } } as any); }); suiteTeardown(() => { From 1f3bd326290cdee8204942da8dc3e843e5753373 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 20 Feb 2024 16:55:40 +0100 Subject: [PATCH 1452/1897] adding option to serialize --- .../browser/parts/editor/editorsObserver.ts | 5 +++++ src/vs/workbench/common/editor.ts | 5 +++++ src/vs/workbench/common/editor/editorGroupModel.ts | 3 ++- .../bulkEdit/browser/preview/bulkEditPane.ts | 1 + .../multiDiffEditor/browser/multiDiffEditorInput.ts | 13 +++++++++++-- 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index ac30da415d2..bf55634f0c9 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -430,19 +430,24 @@ export class EditorsObserver extends Disposable { entries: coalesce(entries.map(({ editor, groupId }) => { // Find group for entry + console.log('editor: ', editor); + console.log('groupId : ', groupId); const group = this.editorGroupsContainer.getGroup(groupId); + console.log('group : ', group); if (!group) { return undefined; } // Find serializable editors of group let serializableEditorsOfGroup = mapGroupToSerializableEditorsOfGroup.get(group); + console.log('serializableEditorsOfGroup : ', serializableEditorsOfGroup); if (!serializableEditorsOfGroup) { serializableEditorsOfGroup = group.getEditors(EditorsOrder.SEQUENTIAL).filter(editor => { const editorSerializer = registry.getEditorSerializer(editor); return editorSerializer?.canSerialize(editor); }); + console.log('serializableEditorsOfGroup : ', serializableEditorsOfGroup); mapGroupToSerializableEditorsOfGroup.set(group, serializableEditorsOfGroup); } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index bf6785d9675..c6986bd0f57 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -505,6 +505,11 @@ export interface IResourceMultiDiffEditorInput extends IBaseUntypedEditorInput { * If not set, the resources are dynamically derived from the {@link multiDiffSource}. */ readonly resources?: IResourceDiffEditorInput[]; + + /** + * Whether the editor should be serialized and stored for subsequent sessions. + */ + readonly isTransient?: boolean; } export type IResourceMergeEditorInputSide = (IResourceEditorInput | ITextResourceEditorInput) & { detail?: string }; diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 497ef8daf46..017ac15184f 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -1032,9 +1032,10 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { let canSerializeEditor = false; const editorSerializer = registry.getEditorSerializer(editor); - if (editorSerializer) { + if (editorSerializer && editorSerializer.canSerialize(editor)) { const value = editorSerializer.serialize(editor); + console.log('value : ', value); // Editor can be serialized if (typeof value === 'string') { canSerializeEditor = true; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 8739a79bbbb..a543ab53b2c 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -351,6 +351,7 @@ export class BulkEditPane extends ViewPane { resources, label, options, + isTransient: true, description: label }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 1cb31624f06..041b8ac44a3 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -48,10 +48,12 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor resource.modified.resource, ); }), + input.isTransient ?? false ); } public static fromSerialized(data: ISerializedMultiDiffEditorInput, instantiationService: IInstantiationService): MultiDiffEditorInput { + console.log('inside of fromSerialized'); return instantiationService.createInstance( MultiDiffEditorInput, URI.parse(data.multiDiffSourceUri), @@ -59,7 +61,8 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor data.resources?.map(resource => new MultiDiffEditorItem( resource.originalUri ? URI.parse(resource.originalUri) : undefined, resource.modifiedUri ? URI.parse(resource.modifiedUri) : undefined, - )) + )), + false ); } @@ -80,12 +83,14 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor public readonly multiDiffSource: URI, public readonly label: string | undefined, public readonly initialResources: readonly MultiDiffEditorItem[] | undefined, + public readonly isTransient: boolean = false, @ITextModelService private readonly _textModelService: ITextModelService, @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IMultiDiffSourceResolverService private readonly _multiDiffSourceResolverService: IMultiDiffSourceResolverService, @ITextFileService private readonly _textFileService: ITextFileService, ) { + console.log('isTransient from the constructor : ', isTransient); super(); this._register(autorun((reader) => { @@ -361,14 +366,18 @@ interface ISerializedMultiDiffEditorInput { export class MultiDiffEditorSerializer implements IEditorSerializer { canSerialize(editor: EditorInput): boolean { - return editor instanceof MultiDiffEditorInput; + console.log('inside of can serialize'); + console.log('editor instanceof MultiDiffEditorInput && !editor.isTransient : ', editor instanceof MultiDiffEditorInput && !editor.isTransient); + return editor instanceof MultiDiffEditorInput && !editor.isTransient; } serialize(editor: MultiDiffEditorInput): string | undefined { + console.log('inside of serialize'); return JSON.stringify(editor.serialize()); } deserialize(instantiationService: IInstantiationService, serializedEditor: string): EditorInput | undefined { + console.log('inside of deserialize'); try { const data = parse(serializedEditor) as ISerializedMultiDiffEditorInput; return MultiDiffEditorInput.fromSerialized(data, instantiationService); From 5c2f9eb177601d09ac2a2bfd0e922c558fc8a6b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9A=B4=E8=BA=81=E6=9A=B4=E8=BA=81=E6=9C=80=E6=9A=B4?= =?UTF-8?q?=E8=BA=81/Bigforce?= Date: Tue, 20 Feb 2024 23:55:44 +0800 Subject: [PATCH 1453/1897] Fix browser host open additional files in merge mode (#205663) Co-authored-by: FANG.Ge --- src/vs/workbench/services/host/browser/browserHostService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 79c91e10dba..3ce96bba22a 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -307,7 +307,7 @@ export class BrowserHostService extends Disposable implements IHostService { } // Support diffMode - if (options?.diffMode && fileOpenables.length === 2) { + else if (options?.diffMode && fileOpenables.length === 2) { const editors = coalesce(await pathsToEditors(fileOpenables, this.fileService, this.logService)); if (editors.length !== 2 || !isResourceEditorInput(editors[0]) || !isResourceEditorInput(editors[1])) { return; // invalid resources From 194a8dd18e097fa71277eb998a77cf8e2df6bee6 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 20 Feb 2024 17:23:51 +0100 Subject: [PATCH 1454/1897] using the uri of the multi file editor instead of using the schema for individual files inside the multi diff editor --- .../contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts | 3 +-- .../workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts | 3 ++- .../contrib/bulkEdit/browser/preview/bulkEditPreview.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts index 1d7f842bebc..c3131b30d44 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts @@ -14,7 +14,6 @@ import { localize, localize2 } from 'vs/nls'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { RawContextKey, IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { BulkEditPreviewProvider } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; @@ -67,7 +66,7 @@ class UXState { for (const input of group.editors) { const resource = EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY }); - if (resource?.scheme === BulkEditPreviewProvider.Schema) { + if (resource?.scheme === BulkEditPane.Schema) { previewEditors.push(input); } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 8739a79bbbb..220a0bae8bd 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -48,6 +48,7 @@ const enum State { export class BulkEditPane extends ViewPane { static readonly ID = 'refactorPreview'; + static readonly Schema = 'vscode-bulkeditpreview-multieditor'; static readonly ctxHasCategories = new RawContextKey('refactorPreview.hasCategories', false); static readonly ctxGroupByFile = new RawContextKey('refactorPreview.groupByFile', true); @@ -344,7 +345,7 @@ export class BulkEditPane extends ViewPane { } } }; - const multiDiffSource = URI.from({ scheme: 'refactor-preview' }); + const multiDiffSource = URI.from({ scheme: BulkEditPane.Schema }); const label = 'Refactor Preview'; this._editorService.openEditor({ multiDiffSource, diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts index d9e8a1112a0..40fbb606f8b 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts @@ -351,7 +351,7 @@ export class BulkFileOperations { export class BulkEditPreviewProvider implements ITextModelContentProvider { - static readonly Schema = 'vscode-bulkeditpreview'; + private static readonly Schema = 'vscode-bulkeditpreview-editor'; static emptyPreview = URI.from({ scheme: BulkEditPreviewProvider.Schema, fragment: 'empty' }); From 73ad7fb895b9d5a2a24600f3071513cd28c72338 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 20 Feb 2024 17:26:51 +0100 Subject: [PATCH 1455/1897] voice - align hold to speak with inline chat (#205655) --- .../actions/voiceChatActions.ts | 95 ++++++++++++++----- .../electron-sandbox/chat.contribution.ts | 3 +- 2 files changed, 75 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index e56a11a9ca6..e1e9306cb1c 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -9,11 +9,10 @@ import { firstOrDefault } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize, localize2 } from 'vs/nls'; import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { spinningLoading } from 'vs/platform/theme/common/iconRegistry'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidget, IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat'; @@ -36,7 +35,7 @@ import { ColorScheme } from 'vs/platform/theme/common/theme'; import { Color } from 'vs/base/common/color'; import { contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { isNumber } from 'vs/base/common/types'; +import { assertIsDefined, isNumber } from 'vs/base/common/types'; import { AccessibilityVoiceSettingId, SpeechTimeoutDefault, accessibilityConfigurationNodeBase } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IChatExecuteActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -64,6 +63,7 @@ const CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS = new RawContextKey('voice const CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS = new RawContextKey('voiceChatInEditorInProgress', false, { type: 'boolean', description: localize('voiceChatInEditorInProgress', "True when voice recording from microphone is in progress in the chat editor.") }); const CanVoiceChat = ContextKeyExpr.and(CONTEXT_PROVIDER_EXISTS, HasSpeechProvider); +const FocusInChatInput = assertIsDefined(ContextKeyExpr.or(CTX_INLINE_CHAT_FOCUSED, CONTEXT_IN_CHAT_INPUT)); type VoiceChatSessionContext = 'inline' | 'quick' | 'view' | 'editor'; @@ -92,7 +92,6 @@ class VoiceChatSessionControllerFactory { static create(accessor: ServicesAccessor, context: 'inline' | 'quick' | 'view' | 'focused'): Promise; static async create(accessor: ServicesAccessor, context: 'inline' | 'quick' | 'view' | 'focused'): Promise { const chatWidgetService = accessor.get(IChatWidgetService); - const chatService = accessor.get(IChatService); const viewsService = accessor.get(IViewsService); const chatContributionService = accessor.get(IChatContributionService); const quickChatService = accessor.get(IQuickChatService); @@ -136,12 +135,9 @@ class VoiceChatSessionControllerFactory { // View Chat if (context === 'view' || context === 'focused' /* fallback in case 'focused' was not successful */) { - const provider = firstOrDefault(chatService.getProviderInfos()); - if (provider) { - const chatView = await chatWidgetService.revealViewForProvider(provider.id); - if (chatView) { - return VoiceChatSessionControllerFactory.doCreateForChatView(chatView, viewsService, chatContributionService); - } + const chatView = await VoiceChatSessionControllerFactory.revealChatView(accessor); + if (chatView) { + return VoiceChatSessionControllerFactory.doCreateForChatView(chatView, viewsService, chatContributionService); } } @@ -169,6 +165,18 @@ class VoiceChatSessionControllerFactory { return undefined; } + static async revealChatView(accessor: ServicesAccessor): Promise { + const chatWidgetService = accessor.get(IChatWidgetService); + const chatService = accessor.get(IChatService); + + const provider = firstOrDefault(chatService.getProviderInfos()); + if (provider) { + return chatWidgetService.revealViewForProvider(provider.id); + } + + return undefined; + } + private static doCreateForChatView(chatView: IChatWidget, viewsService: IViewsService, chatContributionService: IChatContributionService): IVoiceChatSessionController { return VoiceChatSessionControllerFactory.doCreateForChatViewOrEditor('view', chatView, viewsService, chatContributionService); } @@ -414,7 +422,7 @@ async function startVoiceChatWithHoldMode(id: string, accessor: ServicesAccessor let acceptVoice = false; const handle = disposableTimeout(() => { acceptVoice = true; - session.setTimeoutDisabled(true); // disable accept on timeout when hold mode runs for 250ms + session.setTimeoutDisabled(true); // disable accept on timeout when hold mode runs for VOICE_KEY_HOLD_THRESHOLD }, VOICE_KEY_HOLD_THRESHOLD); const controller = await VoiceChatSessionControllerFactory.create(accessor, target); @@ -459,6 +467,57 @@ export class VoiceChatInChatViewAction extends VoiceChatWithHoldModeAction { } } +export class HoldToVoiceChatInChatViewAction extends Action2 { + + static readonly ID = 'workbench.action.chat.holdToVoiceChatInChatView'; + + constructor() { + super({ + id: HoldToVoiceChatInChatViewAction.ID, + title: localize2('workbench.action.chat.holdToVoiceChatInChatView.label', "Hold to Voice Chat in View"), + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and( + CanVoiceChat, + FocusInChatInput.negate(), // when already in chat input, disable this action and prefer to start voice chat directly + EditorContextKeys.focus.negate() // do not steal the inline-chat keybinding + ), + primary: KeyMod.CtrlCmd | KeyCode.KeyI + } + }); + } + + override async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { + + // The intent of this action is to provide 2 modes to align with what `Ctrlcmd+I` in inline chat: + // - if the user press and holds, we start voice chat in the chat view + // - if the user press and releases quickly enough, we just open the chat view without voice chat + + const instantiationService = accessor.get(IInstantiationService); + const keybindingService = accessor.get(IKeybindingService); + + const holdMode = keybindingService.enableKeybindingHoldMode(HoldToVoiceChatInChatViewAction.ID); + + let session: IVoiceChatSession | undefined; + const handle = disposableTimeout(async () => { + const controller = await VoiceChatSessionControllerFactory.create(accessor, 'view'); + if (controller) { + session = VoiceChatSessions.getInstance(instantiationService).start(controller, context); + session.setTimeoutDisabled(true); + } + }, VOICE_KEY_HOLD_THRESHOLD); + + (await VoiceChatSessionControllerFactory.revealChatView(accessor))?.focusInput(); + + await holdMode; + handle.dispose(); + + if (session) { + session.accept(); + } + } +} + export class InlineVoiceChatAction extends VoiceChatWithHoldModeAction { static readonly ID = 'workbench.action.chat.inlineVoiceChat'; @@ -502,11 +561,8 @@ export class StartVoiceChatAction extends Action2 { keybinding: { weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and( - CanVoiceChat, - EditorContextKeys.focus.toNegated(), // do not steal the inline-chat keybinding - CONTEXT_VOICE_CHAT_GETTING_READY.negate(), - CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), - CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.negate(), + FocusInChatInput, // scope this action to chat input fields only + EditorContextKeys.focus.negate(), // do not steal the inline-chat keybinding CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS.negate(), CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS.negate(), CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS.negate(), @@ -629,7 +685,6 @@ class BaseStopListeningAction extends Action2 { category: CHAT_CATEGORY, keybinding: { weight: KeybindingWeight.WorkbenchContrib + 100, - when: ContextKeyExpr.and(CanVoiceChat, context), primary: KeyCode.Escape }, precondition: ContextKeyExpr.and(CanVoiceChat, context), @@ -704,11 +759,7 @@ export class StopListeningAndSubmitAction extends Action2 { f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and( - CanVoiceChat, - ContextKeyExpr.or(CTX_INLINE_CHAT_FOCUSED, CONTEXT_IN_CHAT_INPUT), - CONTEXT_VOICE_CHAT_IN_PROGRESS - ), + when: FocusInChatInput, primary: KeyMod.CtrlCmd | KeyCode.KeyI }, precondition: ContextKeyExpr.and(CanVoiceChat, CONTEXT_VOICE_CHAT_IN_PROGRESS) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts index 2f69e1b13f8..08b145350e1 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction, KeywordActivationContribution, InstallVoiceChatAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; +import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction, KeywordActivationContribution, InstallVoiceChatAction, HoldToVoiceChatInChatViewAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; @@ -11,6 +11,7 @@ registerAction2(StartVoiceChatAction); registerAction2(InstallVoiceChatAction); registerAction2(VoiceChatInChatViewAction); +registerAction2(HoldToVoiceChatInChatViewAction); registerAction2(QuickVoiceChatAction); registerAction2(InlineVoiceChatAction); From 4aac6ba4d9baf0c18201a8f2181d2d63f9431940 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 20 Feb 2024 17:27:47 +0100 Subject: [PATCH 1456/1897] window - fix window state validation to actually apply properly and add more logging (#205677) --- .../auxiliaryWindowsMainService.ts | 10 ++++--- .../platform/window/electron-main/window.ts | 26 ++++++++++++++++++- .../windows/electron-main/windowImpl.ts | 1 - .../platform/windows/electron-main/windows.ts | 25 +++++++++--------- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts index 786f2ef9639..1174386d1c2 100644 --- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts +++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts @@ -12,7 +12,7 @@ import { AuxiliaryWindow, IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/e import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWindowState } from 'vs/platform/window/electron-main/window'; +import { IWindowState, defaultAuxWindowState } from 'vs/platform/window/electron-main/window'; import { WindowStateValidator, defaultBrowserWindowOptions, getLastFocused } from 'vs/platform/windows/electron-main/windows'; export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliaryWindowsMainService { @@ -79,7 +79,7 @@ export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliar }); } - private validateWindowState(details: HandlerDetails): IWindowState | undefined { + private validateWindowState(details: HandlerDetails): IWindowState { const windowState: IWindowState = {}; const features = details.features.split(','); // for example: popup=yes,left=270,top=14.5,width=800,height=600 @@ -101,7 +101,11 @@ export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliar } } - return WindowStateValidator.validateWindowState(this.logService, windowState); + const state = WindowStateValidator.validateWindowState(this.logService, windowState) ?? defaultAuxWindowState(); + + this.logService.trace('[aux window] using window state', state); + + return state; } registerWindow(webContents: WebContents): void { diff --git a/src/vs/platform/window/electron-main/window.ts b/src/vs/platform/window/electron-main/window.ts index 6f744cf6454..04795036c44 100644 --- a/src/vs/platform/window/electron-main/window.ts +++ b/src/vs/platform/window/electron-main/window.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BrowserWindow, Rectangle } from 'electron'; +import { BrowserWindow, Rectangle, screen } from 'electron'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -145,6 +145,30 @@ export const defaultWindowState = function (mode = WindowMode.Normal): IWindowSt }; }; +export const defaultAuxWindowState = function (): IWindowState { + + // Auxiliary windows are being created from a `window.open` call + // that sets `windowFeatures` that encode the desired size and + // position of the new window (`top`, `left`). + // In order to truly override this to a good default window state + // we need to set not only width and height but also x and y to + // a good location on the primary display. + + const width = 800; + const height = 600; + const workArea = screen.getPrimaryDisplay().workArea; + const x = Math.max(workArea.x + (workArea.width / 2) - (width / 2), 0); + const y = Math.max(workArea.y + (workArea.height / 2) - (height / 2), 0); + + return { + x, + y, + width, + height, + mode: WindowMode.Normal + }; +}; + export const enum WindowMode { Maximized, Normal, diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 3041b4e0d77..807bb601136 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -570,7 +570,6 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { } }); - // Create the browser window mark('code/willCreateCodeBrowserWindow'); this._win = new BrowserWindow(options); diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index c5fd87557d7..0cccca7c810 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -115,7 +115,7 @@ export interface IOpenConfiguration extends IBaseOpenConfiguration { export interface IOpenEmptyConfiguration extends IBaseOpenConfiguration { } -export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowState?: IWindowState, overrides?: BrowserWindowConstructorOptions): BrowserWindowConstructorOptions & { experimentalDarkMode: boolean } { +export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowState: IWindowState, overrides?: BrowserWindowConstructorOptions): BrowserWindowConstructorOptions & { experimentalDarkMode: boolean } { const themeMainService = accessor.get(IThemeMainService); const productService = accessor.get(IProductService); const configurationService = accessor.get(IConfigurationService); @@ -129,10 +129,14 @@ export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowSt minHeight: WindowMinimumSize.HEIGHT, title: productService.nameLong, ...overrides, + x: windowState.x, + y: windowState.y, + width: windowState.width, + height: windowState.height, webPreferences: { enableWebSQL: false, spellcheck: false, - zoomFactor: zoomLevelToZoomFactor(windowState?.zoomLevel ?? windowSettings?.zoomLevel), + zoomFactor: zoomLevelToZoomFactor(windowState.zoomLevel ?? windowSettings?.zoomLevel), autoplayPolicy: 'user-gesture-required', // Enable experimental css highlight api https://chromestatus.com/feature/5436441440026624 // Refs https://github.com/microsoft/vscode/issues/140098 @@ -143,13 +147,6 @@ export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowSt experimentalDarkMode: true }; - if (windowState) { - options.x = windowState.x; - options.y = windowState.y; - options.width = windowState.width; - options.height = windowState.height; - } - if (isLinux) { options.icon = join(environmentMainService.appRoot, 'resources/linux/code.png'); // always on Linux } else if (isWindows && !environmentMainService.isBuilt) { @@ -246,8 +243,9 @@ export namespace WindowStateValidator { // some pixels (128) visible on the screen for the user to drag it back. if (displays.length === 1) { const displayWorkingArea = getWorkingArea(displays[0]); + logService.trace('window#validateWindowState: single monitor working area', displayWorkingArea); + if (displayWorkingArea) { - logService.trace('window#validateWindowState: 1 monitor working area', displayWorkingArea); function ensureStateInDisplayWorkingArea(): void { if (!state || typeof state.x !== 'number' || typeof state.y !== 'number' || !displayWorkingArea) { @@ -320,10 +318,13 @@ export namespace WindowStateValidator { try { display = screen.getDisplayMatching({ x: state.x, y: state.y, width: state.width, height: state.height }); displayWorkingArea = getWorkingArea(display); + + logService.trace('window#validateWindowState: multi-monitor working area', displayWorkingArea); } catch (error) { // Electron has weird conditions under which it throws errors // e.g. https://github.com/microsoft/vscode/issues/100334 when // large numbers are passed in + logService.error('window#validateWindowState: error finding display for window state', error); } if ( @@ -334,11 +335,11 @@ export namespace WindowStateValidator { state.x < displayWorkingArea.x + displayWorkingArea.width && // prevent window from falling out of the screen to the right state.y < displayWorkingArea.y + displayWorkingArea.height // prevent window from falling out of the screen to the bottom ) { - logService.trace('window#validateWindowState: multi-monitor working area', displayWorkingArea); - return state; } + logService.trace('window#validateWindowState: state is outside of the multi-monitor working area'); + return undefined; } From 81e1ca04f8552b588221d88d96ae00b94540bfef Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 20 Feb 2024 17:31:44 +0100 Subject: [PATCH 1457/1897] cleaning the code --- .../workbench/browser/parts/editor/editorsObserver.ts | 5 ----- src/vs/workbench/common/editor/editorGroupModel.ts | 3 +-- .../multiDiffEditor/browser/multiDiffEditorInput.ts | 11 ++++------- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index bf55634f0c9..ac30da415d2 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -430,24 +430,19 @@ export class EditorsObserver extends Disposable { entries: coalesce(entries.map(({ editor, groupId }) => { // Find group for entry - console.log('editor: ', editor); - console.log('groupId : ', groupId); const group = this.editorGroupsContainer.getGroup(groupId); - console.log('group : ', group); if (!group) { return undefined; } // Find serializable editors of group let serializableEditorsOfGroup = mapGroupToSerializableEditorsOfGroup.get(group); - console.log('serializableEditorsOfGroup : ', serializableEditorsOfGroup); if (!serializableEditorsOfGroup) { serializableEditorsOfGroup = group.getEditors(EditorsOrder.SEQUENTIAL).filter(editor => { const editorSerializer = registry.getEditorSerializer(editor); return editorSerializer?.canSerialize(editor); }); - console.log('serializableEditorsOfGroup : ', serializableEditorsOfGroup); mapGroupToSerializableEditorsOfGroup.set(group, serializableEditorsOfGroup); } diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 017ac15184f..497ef8daf46 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -1032,10 +1032,9 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { let canSerializeEditor = false; const editorSerializer = registry.getEditorSerializer(editor); - if (editorSerializer && editorSerializer.canSerialize(editor)) { + if (editorSerializer) { const value = editorSerializer.serialize(editor); - console.log('value : ', value); // Editor can be serialized if (typeof value === 'string') { canSerializeEditor = true; diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 041b8ac44a3..3d26c944df2 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -53,7 +53,6 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor } public static fromSerialized(data: ISerializedMultiDiffEditorInput, instantiationService: IInstantiationService): MultiDiffEditorInput { - console.log('inside of fromSerialized'); return instantiationService.createInstance( MultiDiffEditorInput, URI.parse(data.multiDiffSourceUri), @@ -90,7 +89,6 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor @IMultiDiffSourceResolverService private readonly _multiDiffSourceResolverService: IMultiDiffSourceResolverService, @ITextFileService private readonly _textFileService: ITextFileService, ) { - console.log('isTransient from the constructor : ', isTransient); super(); this._register(autorun((reader) => { @@ -365,19 +363,18 @@ interface ISerializedMultiDiffEditorInput { } export class MultiDiffEditorSerializer implements IEditorSerializer { + + // TODO@bpasero, @aiday-mar: following should be removed canSerialize(editor: EditorInput): boolean { - console.log('inside of can serialize'); - console.log('editor instanceof MultiDiffEditorInput && !editor.isTransient : ', editor instanceof MultiDiffEditorInput && !editor.isTransient); return editor instanceof MultiDiffEditorInput && !editor.isTransient; } serialize(editor: MultiDiffEditorInput): string | undefined { - console.log('inside of serialize'); - return JSON.stringify(editor.serialize()); + const shouldSerialize = editor instanceof MultiDiffEditorInput && !editor.isTransient; + return shouldSerialize ? JSON.stringify(editor.serialize()) : undefined; } deserialize(instantiationService: IInstantiationService, serializedEditor: string): EditorInput | undefined { - console.log('inside of deserialize'); try { const data = parse(serializedEditor) as ISerializedMultiDiffEditorInput; return MultiDiffEditorInput.fromSerialized(data, instantiationService); From 02359773c2255a1625ad72205729fa53cc2e0e8f Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 20 Feb 2024 17:32:48 +0100 Subject: [PATCH 1458/1897] adding more explicit message --- .../contrib/multiDiffEditor/browser/multiDiffEditorInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 3d26c944df2..3841a5d56aa 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -364,7 +364,7 @@ interface ISerializedMultiDiffEditorInput { export class MultiDiffEditorSerializer implements IEditorSerializer { - // TODO@bpasero, @aiday-mar: following should be removed + // TODO@bpasero, @aiday-mar: following canSerialize should be removed (debt item) canSerialize(editor: EditorInput): boolean { return editor instanceof MultiDiffEditorInput && !editor.isTransient; } From 5bc6776982eb94191421fd211edf316a6bf6cb4a Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:41:42 +0100 Subject: [PATCH 1459/1897] Gix - recompute diagnostics when settings change (#205711) --- extensions/git/src/diagnostics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/diagnostics.ts b/extensions/git/src/diagnostics.ts index 9df39df5177..16c29c00ac6 100644 --- a/extensions/git/src/diagnostics.ts +++ b/extensions/git/src/diagnostics.ts @@ -24,7 +24,7 @@ export class GitCommitInputBoxDiagnosticsManager { this.migrateInputValidationSettings() .then(() => { mapEvent(filterEvent(workspace.onDidChangeTextDocument, e => e.document.uri.scheme === 'vscode-scm'), e => e.document)(this.onDidChangeTextDocument, this, this.disposables); - filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.inputValidation'))(this.onDidChangeConfiguration, this, this.disposables); + filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.inputValidation') || e.affectsConfiguration('git.inputValidationLength') || e.affectsConfiguration('git.inputValidationSubjectLength'))(this.onDidChangeConfiguration, this, this.disposables); }); } From 94ad9a4199cde13241a9ddc25096979e6d88a297 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:45:25 +0100 Subject: [PATCH 1460/1897] Git - rename code action (#205712) --- extensions/git/src/diagnostics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/diagnostics.ts b/extensions/git/src/diagnostics.ts index 16c29c00ac6..c19fb563b19 100644 --- a/extensions/git/src/diagnostics.ts +++ b/extensions/git/src/diagnostics.ts @@ -123,7 +123,7 @@ export class GitCommitInputBoxCodeActionsProvider implements CodeActionProvider const workspaceEdit = new WorkspaceEdit(); workspaceEdit.delete(document.uri, diagnostic.range); - const codeAction = new CodeAction(l10n.t('Remove empty characters'), CodeActionKind.QuickFix); + const codeAction = new CodeAction(l10n.t('Clear whitespace characters'), CodeActionKind.QuickFix); codeAction.diagnostics = [diagnostic]; codeAction.edit = workspaceEdit; codeActions.push(codeAction); From 7560c3db8c90b1f7888e2348f792816b303697e1 Mon Sep 17 00:00:00 2001 From: RedCMD <33529441+RedCMD@users.noreply.github.com> Date: Wed, 21 Feb 2024 06:06:40 +1300 Subject: [PATCH 1461/1897] Improve extension `README` preview markdown codeblock language detection (#205329) --- .../contrib/markdown/browser/markdownDocumentRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts index fce923cad92..8ac086cada8 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts @@ -215,7 +215,7 @@ export async function renderMarkdownDocument( return; } - const languageId = languageService.getLanguageIdByLanguageName(lang); + const languageId = languageService.getLanguageIdByLanguageName(lang) ?? languageService.getLanguageIdByLanguageName(lang.split(/\s+|:|,|(?!^)\{|\?]/, 1)[0]); const html = await tokenizeToString(languageService, code, languageId); callback(null, `${html}`); }); From 3dc8040fe4999f0db1596740b85bd2d52d85702a Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 20 Feb 2024 18:17:27 +0100 Subject: [PATCH 1462/1897] Fix notebook actionItem hovers (#205719) fix notebook actionItem hovers --- .../browser/view/cellParts/cellActionView.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts index a50b67ca2b6..c2c7f3e740a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts @@ -13,6 +13,8 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ThemeIcon } from 'vs/base/common/themables'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export class CodiconActionViewItem extends MenuEntryActionViewItem { @@ -38,12 +40,12 @@ export class ActionViewWithLabel extends MenuEntryActionViewItem { if (this._actionLabel) { this._actionLabel.classList.add('notebook-label'); this._actionLabel.innerText = this._action.label; - this._actionLabel.title = this._action.tooltip.length ? this._action.tooltip : this._action.label; } } } export class UnifiedSubmenuActionView extends SubmenuEntryActionViewItem { private _actionLabel?: HTMLAnchorElement; + private _hover?: ICustomHover; constructor( action: SubmenuItemAction, @@ -63,6 +65,10 @@ export class UnifiedSubmenuActionView extends SubmenuEntryActionViewItem { container.classList.add('notebook-action-view-item'); this._actionLabel = document.createElement('a'); container.appendChild(this._actionLabel); + + const hoverDelegate = this.options.hoverDelegate ?? getDefaultHoverDelegate('element'); + this._hover = this._register(setupCustomHover(hoverDelegate, this._actionLabel, '')); + this.updateLabel(); } @@ -88,13 +94,13 @@ export class UnifiedSubmenuActionView extends SubmenuEntryActionViewItem { if (this.renderLabel) { this._actionLabel.classList.add('notebook-label'); this._actionLabel.innerText = this._action.label; - this._actionLabel.title = primaryAction.tooltip.length ? primaryAction.tooltip : primaryAction.label; + this._hover?.update(primaryAction.tooltip.length ? primaryAction.tooltip : primaryAction.label); } } else { if (this.renderLabel) { this._actionLabel.classList.add('notebook-label'); this._actionLabel.innerText = this._action.label; - this._actionLabel.title = this._action.tooltip.length ? this._action.tooltip : this._action.label; + this._hover?.update(this._action.tooltip.length ? this._action.tooltip : this._action.label); } } } From 673a1aeca1ed358222c25de98570f23060997b49 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 20 Feb 2024 09:35:54 -0800 Subject: [PATCH 1463/1897] fix #205445 (#205720) --- .../extensionManagement/common/extensionManagementCLI.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts index 05ee97927fe..2dd973658b9 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts @@ -137,7 +137,7 @@ export class ExtensionManagementCLI { } } - this.logger.trace(localize('updateExtensionsQuery', "Fetching latest versions for {0} extensions", installedExtensionsQuery.length)); + this.logger.trace(localize({ key: 'updateExtensionsQuery', comment: ['Placeholder is for the count of extensions'] }, "Fetching latest versions for {0} extensions", installedExtensionsQuery.length)); const availableVersions = await this.extensionGalleryService.getExtensions(installedExtensionsQuery, { compatible: true }, CancellationToken.None); const extensionsToUpdate: InstallExtensionInfo[] = []; From 025b4c9435a928cd38b317a3b72e52e5a69b4b1f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 20 Feb 2024 20:31:13 +0100 Subject: [PATCH 1464/1897] voice - allow dictation in SCM input and comments (#205738) --- .../workbench/contrib/comments/browser/simpleCommentEditor.ts | 2 ++ src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index cda25cbdfac..2a4f2c3a2bb 100644 --- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts +++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts @@ -13,6 +13,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; // Allowed Editor Contributions: import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; +import { EditorDictation } from 'vs/workbench/contrib/codeEditor/browser/dictation/editorDictation'; import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu'; import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; @@ -62,6 +63,7 @@ export class SimpleCommentEditor extends CodeEditorWidget { { id: SuggestController.ID, ctor: SuggestController, instantiation: EditorContributionInstantiation.Eager }, { id: SnippetController2.ID, ctor: SnippetController2, instantiation: EditorContributionInstantiation.Lazy }, { id: TabCompletionController.ID, ctor: TabCompletionController, instantiation: EditorContributionInstantiation.Eager }, // eager because it needs to define a context key + { id: EditorDictation.ID, ctor: EditorDictation, instantiation: EditorContributionInstantiation.Lazy } ] }; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index e075d69cca4..f91410f9b8d 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -51,6 +51,7 @@ import { IModelService } from 'vs/editor/common/services/model'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; +import { EditorDictation } from 'vs/workbench/contrib/codeEditor/browser/dictation/editorDictation'; import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu'; import * as platform from 'vs/base/common/platform'; import { compare, format } from 'vs/base/common/strings'; @@ -2444,7 +2445,8 @@ class SCMInputWidget { SuggestController.ID, InlineCompletionsController.ID, CodeActionController.ID, - FormatOnType.ID + FormatOnType.ID, + EditorDictation.ID ]) }; From 5e9e54773a6924b88d64dd61c64142bf355a74fb Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 20 Feb 2024 20:13:42 +0000 Subject: [PATCH 1465/1897] Don't rely on options from setInput (#205745) Fix microsoft/vscode-copilot-release#914 --- src/vs/workbench/contrib/chat/browser/chatEditor.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index 8401da7b172..4651778b165 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -81,7 +81,7 @@ export class ChatEditor extends EditorPane { super.clearInput(); } - override async setInput(input: ChatEditorInput, options: IChatEditorOptions, context: IEditorOpenContext, token: CancellationToken): Promise { + override async setInput(input: ChatEditorInput, options: IChatEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { super.setInput(input, options, context, token); const editorModel = await input.resolve(); @@ -93,7 +93,7 @@ export class ChatEditor extends EditorPane { throw new Error('ChatEditor lifecycle issue: no editor widget'); } - this.updateModel(editorModel.model, options.viewState); + this.updateModel(editorModel.model, options?.viewState ?? input.options.viewState); } private updateModel(model: IChatModel, viewState?: IChatViewState): void { From 926bf1a1f2b3d098f47383597b28fb1d5a4d7f2c Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Tue, 20 Feb 2024 15:44:06 -0800 Subject: [PATCH 1466/1897] code action ranges api doc improvements (#205771) doc improvements --- src/vscode-dts/vscode.proposed.codeActionRanges.d.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts b/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts index 704208454d7..350be2d5536 100644 --- a/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts +++ b/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts @@ -7,10 +7,8 @@ declare module 'vscode' { export interface CodeAction { /** - * - * The range to which this Code Action applies to, which will be highlighted. - * - * Ex: A refactoring action will highlight the range of text that will be affected. + * The ranges to which this Code Action applies to, which will be highlighted. + * For example: A refactoring action will highlight the range of text that will be affected. */ ranges?: Range[]; } From a1e1ff68449087c4a4dd59b593663505d9143543 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 21 Feb 2024 01:36:20 +0100 Subject: [PATCH 1467/1897] editors - drop generic `options` parameter in editor input `resolve` method (#205751) --- .../workbench/browser/parts/editor/binaryEditor.ts | 2 +- .../workbench/browser/parts/editor/textDiffEditor.ts | 2 +- .../browser/parts/editor/textResourceEditor.ts | 2 +- src/vs/workbench/common/editor/diffEditorInput.ts | 12 ++++++------ src/vs/workbench/common/editor/editorInput.ts | 3 +-- .../contrib/preferences/browser/settingsEditor2.ts | 2 +- .../contrib/webviewPanel/browser/webviewEditor.ts | 2 +- 7 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index 0fb42552e45..cba829c7be5 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -46,7 +46,7 @@ export abstract class BaseBinaryResourceEditor extends EditorPlaceholder { } protected async getContents(input: EditorInput, options: IEditorOptions): Promise { - const model = await input.resolve(options); + const model = await input.resolve(); // Assert Model instance if (!(model instanceof BinaryEditorModel)) { diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 455922bbcbf..21429e37334 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -106,7 +106,7 @@ export class TextDiffEditor extends AbstractTextEditor imp await super.setInput(input, options, context, token); try { - const resolvedModel = await input.resolve(options); + const resolvedModel = await input.resolve(); // Check for cancellation if (token.isCancellationRequested) { diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 34762256f40..7a16fa667b8 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -53,7 +53,7 @@ export abstract class AbstractTextResourceEditor extends AbstractTextCodeEditor< // Set input and resolve await super.setInput(input, options, context, token); - const resolvedModel = await input.resolve(options); + const resolvedModel = await input.resolve(); // Check for cancellation if (token.isCancellationRequested) { diff --git a/src/vs/workbench/common/editor/diffEditorInput.ts b/src/vs/workbench/common/editor/diffEditorInput.ts index 1d6e6118751..f9b9aeb58b2 100644 --- a/src/vs/workbench/common/editor/diffEditorInput.ts +++ b/src/vs/workbench/common/editor/diffEditorInput.ts @@ -14,7 +14,7 @@ import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorMo import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { shorten } from 'vs/base/common/labels'; -import { IEditorOptions, isResolvedEditorModel } from 'vs/platform/editor/common/editor'; +import { isResolvedEditorModel } from 'vs/platform/editor/common/editor'; interface IDiffEditorInputLabels { name: string; @@ -171,13 +171,13 @@ export class DiffEditorInput extends SideBySideEditorInput implements IDiffEdito } } - override async resolve(options?: IEditorOptions): Promise { + override async resolve(): Promise { // Create Model - we never reuse our cached model if refresh is true because we cannot // decide for the inputs within if the cached model can be reused or not. There may be // inputs that need to be loaded again and thus we always recreate the model and dispose // the previous one - if any. - const resolvedModel = await this.createModel(options); + const resolvedModel = await this.createModel(); this.cachedModel?.dispose(); this.cachedModel = resolvedModel; @@ -193,12 +193,12 @@ export class DiffEditorInput extends SideBySideEditorInput implements IDiffEdito return editorPanes.find(editorPane => editorPane.typeId === TEXT_DIFF_EDITOR_ID); } - private async createModel(options?: IEditorOptions): Promise { + private async createModel(): Promise { // Join resolve call over two inputs and build diff editor model const [originalEditorModel, modifiedEditorModel] = await Promise.all([ - this.original.resolve(options), - this.modified.resolve(options) + this.original.resolve(), + this.modified.resolve() ]); // If both are text models, return textdiffeditor model diff --git a/src/vs/workbench/common/editor/editorInput.ts b/src/vs/workbench/common/editor/editorInput.ts index 9bce607e387..8b80fe942a3 100644 --- a/src/vs/workbench/common/editor/editorInput.ts +++ b/src/vs/workbench/common/editor/editorInput.ts @@ -5,7 +5,6 @@ import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { firstOrDefault } from 'vs/base/common/arrays'; import { EditorInputCapabilities, Verbosity, GroupIdentifier, ISaveOptions, IRevertOptions, IMoveResult, IEditorDescriptor, IEditorPane, IUntypedEditorInput, EditorResourceAccessor, AbstractEditorInput, isEditorInput, IEditorIdentifier } from 'vs/workbench/common/editor'; import { isEqual } from 'vs/base/common/resources'; @@ -235,7 +234,7 @@ export abstract class EditorInput extends AbstractEditorInput { * The `options` parameter are passed down from the editor when the * input is resolved as part of it. */ - async resolve(options?: IEditorOptions): Promise { + async resolve(): Promise { return null; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 9df8e9b70d6..a06c8048351 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -355,7 +355,7 @@ export class SettingsEditor2 extends EditorPane { return; } - const model = await this.input.resolve(options); + const model = await this.input.resolve(); if (token.isCancellationRequested || !(model instanceof Settings2EditorModel)) { return; } diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts index 8a519349446..f9b3f0c34f3 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts @@ -154,7 +154,7 @@ export class WebviewEditor extends EditorPane { } await super.setInput(input, options, context, token); - await input.resolve(options); + await input.resolve(); if (token.isCancellationRequested || this._isDisposed) { return; From a7002f6b66713b57de06510d9b14b25249d3d96e Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Tue, 20 Feb 2024 17:18:34 -0800 Subject: [PATCH 1468/1897] Use NotebookOptions for statusbar instead of viewmodel options (#205791) fix nb indentation actions + get nb options instead of textmodel options for statusbar --- .../contrib/editorStatusBar/editorStatusBar.ts | 8 +++++--- .../controller/notebookIndentationActions.ts | 16 +++++++++------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index 9900e41b0c3..bbfe3a2609d 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -303,9 +303,11 @@ export class NotebookIndentationStatus extends Disposable implements IWorkbenchC this._accessor.clear(); return; } - const indentSize = cellOptions?.indentSize; - const tabSize = cellOptions?.tabSize; - const insertSpaces = cellOptions?.insertSpaces; + + const cellEditorOverridesRaw = editor.notebookOptions.getDisplayOptions().editorOptionsCustomizations; + const indentSize = cellEditorOverridesRaw['editor.indentSize'] ?? cellOptions?.indentSize; + const insertSpaces = cellEditorOverridesRaw['editor.insertSpaces'] ?? cellOptions?.tabSize; + const tabSize = cellEditorOverridesRaw['editor.tabSize'] ?? cellOptions?.insertSpaces; const width = typeof indentSize === 'number' ? indentSize : tabSize; diff --git a/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts index 58d4bf63b6d..c647ab969d7 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts @@ -24,7 +24,7 @@ export class NotebookIndentUsingTabs extends Action2 { constructor() { super({ - id: NotebookIndentUsingSpaces.ID, + id: NotebookIndentUsingTabs.ID, title: nls.localize('indentUsingTabs', "Indent Using Tabs"), precondition: undefined, }); @@ -56,7 +56,7 @@ export class NotebookChangeTabDisplaySize extends Action2 { constructor() { super({ - id: NotebookIndentUsingSpaces.ID, + id: NotebookChangeTabDisplaySize.ID, title: nls.localize('changeTabDisplaySize', "Change Tab Display Size"), precondition: undefined, }); @@ -72,7 +72,7 @@ export class NotebookIndentationToSpacesAction extends Action2 { constructor() { super({ - id: NotebookIndentUsingSpaces.ID, + id: NotebookIndentationToSpacesAction.ID, title: nls.localize('convertIndentationToSpaces', "Convert Indentation to Spaces"), precondition: undefined, }); @@ -80,7 +80,6 @@ export class NotebookIndentationToSpacesAction extends Action2 { override run(accessor: ServicesAccessor, ...args: any[]): void { convertNotebookIndentation(accessor, true); - } } @@ -89,7 +88,7 @@ export class NotebookIndentationToTabsAction extends Action2 { constructor() { super({ - id: NotebookIndentUsingSpaces.ID, + id: NotebookIndentationToTabsAction.ID, title: nls.localize('convertIndentationToTabs', "Convert Indentation to Tabs"), precondition: undefined, }); @@ -194,8 +193,7 @@ function convertNotebookIndentation(accessor: ServicesAccessor, tabsToSpaces: bo bulkEditService.apply(edits, { label: nls.localize('convertIndentation', "Convert Indentation"), code: 'undoredo.convertIndentation', }); - })).then((notebookEdits) => { - + })).then(() => { // store the initial values of the configuration const initialConfig = configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations) as any; const initialIndentSize = initialConfig['editor.indentSize']; @@ -255,3 +253,7 @@ function getIndentationEditOperations(model: ITextModel, tabSize: number, tabsTo } registerAction2(NotebookIndentUsingSpaces); +registerAction2(NotebookIndentUsingTabs); +registerAction2(NotebookChangeTabDisplaySize); +registerAction2(NotebookIndentationToSpacesAction); +registerAction2(NotebookIndentationToTabsAction); From ee69e2887fbe532588f74ae86560e7fdc1e59550 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 20 Feb 2024 18:35:32 -0800 Subject: [PATCH 1469/1897] Notebook chat editing enhancements. (#205799) * Notebook chat editing enhancements. * hide cell chat actions from f1. --- .../notebook/browser/controller/chat/cellChatActions.ts | 2 ++ .../browser/controller/chat/notebookChatController.ts | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 8c97251257e..430062261c9 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -382,6 +382,7 @@ registerAction2(class extends NotebookAction { } ] }, + f1: false, menu: [ { id: MenuId.NotebookCellBetween, @@ -454,6 +455,7 @@ registerAction2(class extends NotebookAction { original: '$(sparkle) Generate', }, tooltip: localize('notebookActions.menu.insertCodeCellWithChat.tooltip', "Generate Code Cell with Chat"), + f1: false, menu: [ { id: MenuId.NotebookCellListTop, diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 7686f52b8b3..cec4fe9242f 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -369,7 +369,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito const cellTop = this._notebookEditor.getAbsoluteTopOfElement(previousCell); const cellHeight = this._notebookEditor.getHeightOfElement(previousCell); - this._notebookEditor.revealOffsetInCenterIfOutsideViewport(cellTop + cellHeight); + this._notebookEditor.revealOffsetInCenterIfOutsideViewport(cellTop + cellHeight + 48 /** center of the dialog */); } } } @@ -412,6 +412,12 @@ export class NotebookChatController extends Disposable implements INotebookEdito return; } + if (this._widget.editingCell && this._widget.editingCell.textBuffer.getLength() > 0) { + // it already contains some text, clear it + const ref = await this._widget.editingCell.resolveTextModel(); + ref.setValue(''); + } + const editingCellIndex = this._widget.editingCell ? this._notebookEditor.getCellIndex(this._widget.editingCell) : undefined; if (editingCellIndex !== undefined) { this._notebookEditor.setSelections([{ From 9e92682062c60f6d175ceacd22e5a5d6eb82ebd8 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 21 Feb 2024 09:58:53 +0100 Subject: [PATCH 1470/1897] some API todos for variable resolver (#205828) --- src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts b/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts index 32f081ecc5d..eb6f0882d63 100644 --- a/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts +++ b/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts @@ -33,11 +33,15 @@ declare module 'vscode' { description?: string; } + // TODO@API align with ChatRequest export interface ChatVariableContext { /** * The message entered by the user, which includes this variable. */ + // TODO@API AS-IS, variables as types, agent/commands stripped prompt: string; + + // readonly variables: readonly ChatResolvedVariable[]; } export interface ChatVariableResolver { From 8895f460c69c8c9be55d280baedc39d82361079f Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:26:15 +0100 Subject: [PATCH 1471/1897] Git - better handle edge case when hard wrapping a line (#205831) --- extensions/git/src/diagnostics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/diagnostics.ts b/extensions/git/src/diagnostics.ts index c19fb563b19..a8c1a3deea3 100644 --- a/extensions/git/src/diagnostics.ts +++ b/extensions/git/src/diagnostics.ts @@ -190,7 +190,7 @@ export class GitCommitInputBoxCodeActionsProvider implements CodeActionProvider const lineLengthThreshold = line === 0 ? inputValidationSubjectLength ?? inputValidationLength : inputValidationLength; const lineSegments: string[] = []; - const lineText = document.lineAt(line).text; + const lineText = document.lineAt(line).text.trim(); let position = 0; while (lineText.length - position > lineLengthThreshold) { From f7f9fb09a3200246a55da9d6882d56b82eb4fe9a Mon Sep 17 00:00:00 2001 From: Andy Hippo Date: Wed, 21 Feb 2024 10:36:04 +0100 Subject: [PATCH 1472/1897] Fix memory leaks (#205589) * Fix memory leaks The activities in `treeView.ts` and `reportExplorer.ts` are not disposed when the parent object is disposed. Also fix the `contextKeyListener` disposable while we're here. The activity in `webviewViewPane.ts` is never disposed, because the return value is ignored. --- src/vs/workbench/browser/parts/views/treeView.ts | 13 ++++--------- .../contrib/remote/browser/remoteExplorer.ts | 16 ++++++---------- .../webviewView/browser/webviewViewPane.ts | 9 ++------- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index d142226edb7..a46d7bc51d6 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -22,7 +22,7 @@ import { isCancellationError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import { IMarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import { Schemas } from 'vs/base/common/network'; import { basename, dirname } from 'vs/base/common/resources'; @@ -426,7 +426,8 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { } private _badge: IViewBadge | undefined; - private _badgeActivity: IDisposable | undefined; + private readonly _activity = this._register(new MutableDisposable()); + get badge(): IViewBadge | undefined { return this._badge; } @@ -438,19 +439,13 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { return; } - if (this._badgeActivity) { - this._badgeActivity.dispose(); - this._badgeActivity = undefined; - } - this._badge = badge; - if (badge) { const activity = { badge: new NumberBadge(badge.value, () => badge.tooltip), priority: 50 }; - this._badgeActivity = this.activityService.showViewActivity(this.id, activity); + this._activity.value = this.activityService.showViewActivity(this.id, activity); } } diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index 880ccebd8e0..37948b2bf60 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Extensions, IViewContainersRegistry, IViewsRegistry, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { IRemoteExplorerService, PORT_AUTO_FALLBACK_SETTING, 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'; @@ -41,8 +41,8 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag export const VIEWLET_ID = 'workbench.view.remote'; export class ForwardedPortsView extends Disposable implements IWorkbenchContribution { - private contextKeyListener?: IDisposable; - private _activityBadge?: IDisposable; + private readonly contextKeyListener = this._register(new MutableDisposable()); + private readonly activityBadge = this._register(new MutableDisposable()); private entryAccessor: IStatusbarEntryAccessor | undefined; constructor( @@ -75,10 +75,7 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu } private async enableForwardedPortsView() { - if (this.contextKeyListener) { - this.contextKeyListener.dispose(); - this.contextKeyListener = undefined; - } + this.contextKeyListener.clear(); const viewEnabled: boolean = !!forwardedPortsViewEnabled.getValue(this.contextKeyService); @@ -91,7 +88,7 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu viewsRegistry.registerViews([tunnelPanelDescriptor], viewContainer); } } else { - this.contextKeyListener = this.contextKeyService.onDidChangeContext(e => { + this.contextKeyListener.value = this.contextKeyService.onDidChangeContext(e => { if (e.affectsSome(new Set(forwardedPortsViewEnabled.keys()))) { this.enableForwardedPortsView(); } @@ -119,9 +116,8 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu } private async updateActivityBadge() { - this._activityBadge?.dispose(); if (this.remoteExplorerService.tunnelModel.forwarded.size > 0) { - this._activityBadge = this.activityService.showViewActivity(TUNNEL_VIEW_ID, { + this.activityBadge.value = this.activityService.showViewActivity(TUNNEL_VIEW_ID, { badge: new NumberBadge(this.remoteExplorerService.tunnelModel.forwarded.size, n => n === 1 ? nls.localize('1forwardedPort', "1 forwarded port") : nls.localize('nForwardedPorts', "{0} forwarded ports", n)) }); } diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts index e44af5edc0c..41cc58d8652 100644 --- a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts +++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts @@ -57,7 +57,7 @@ export class WebviewViewPane extends ViewPane { private setTitle: string | undefined; private badge: IViewBadge | undefined; - private activity: IDisposable | undefined; + private readonly activity = this._register(new MutableDisposable()); private readonly memento: Memento; private readonly viewState: MementoObject; @@ -256,18 +256,13 @@ export class WebviewViewPane extends ViewPane { return; } - if (this.activity) { - this.activity.dispose(); - this.activity = undefined; - } - this.badge = badge; if (badge) { const activity = { badge: new NumberBadge(badge.value, () => badge.tooltip), priority: 150 }; - this.activityService.showViewActivity(this.id, activity); + this.activity.value = this.activityService.showViewActivity(this.id, activity); } } From 2411a9c9136657cd7b5558951021a4d9abd9a6b8 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 21 Feb 2024 11:06:38 +0100 Subject: [PATCH 1473/1897] add `inlineChat.accessibleDiffView` setting (#205837) fixes https://github.com/microsoft/vscode/issues/205714 --- .../inlineChat/browser/inlineChatStrategies.ts | 8 ++++++-- .../contrib/inlineChat/common/inlineChat.ts | 12 ++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 9659c4cdb28..63088a27ed5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -31,12 +31,13 @@ import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { InlineChatFileCreatePreviewWidget, InlineChatLivePreviewWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget'; import { HunkInformation, ReplyResponse, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; -import { CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, InlineChatConfigKeys, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { HunkState } from './inlineChatSession'; import { assertType } from 'vs/base/common/types'; import { IModelService } from 'vs/editor/common/services/model'; import { performAsyncTextEdit, asProgressiveEdit } from './utils'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export interface IEditObserver { start(): void; @@ -445,6 +446,7 @@ export class LiveStrategy extends EditModeStrategy { @IContextKeyService contextKeyService: IContextKeyService, @IEditorWorkerService protected readonly _editorWorkerService: IEditorWorkerService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, + @IConfigurationService private readonly _configService: IConfigurationService, @IInstantiationService protected readonly _instaService: IInstantiationService, ) { super(session, editor, zone); @@ -705,7 +707,9 @@ export class LiveStrategy extends EditModeStrategy { const remainingHunks = this._session.hunkData.pending; this._updateSummaryMessage(remainingHunks); - if (this._accessibilityService.isScreenReaderOptimized()) { + + const mode = this._configService.getValue<'on' | 'off' | 'auto'>(InlineChatConfigKeys.AccessibleDiffView); + if (mode === 'on' || mode === 'auto' && this._accessibilityService.isScreenReaderOptimized()) { this._zone.widget.showAccessibleHunk(this._session, widgetData.hunk); } diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index d339e9f5b07..dbf95c3bb0b 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -227,6 +227,7 @@ export const enum InlineChatConfigKeys { FinishOnType = 'inlineChat.finishOnType', AcceptedOrDiscardBeforeSave = 'inlineChat.acceptedOrDiscardBeforeSave', HoldToSpeech = 'inlineChat.holdToSpeech', + AccessibleDiffView = 'inlineChat.accessibleDiffView' } Registry.as(Extensions.Configuration).registerConfiguration({ @@ -257,6 +258,17 @@ Registry.as(Extensions.Configuration).registerConfigurat description: localize('holdToSpeech', "Whether holding the inline chat keybinding will automatically enable speech recognition."), default: true, type: 'boolean' + }, + [InlineChatConfigKeys.AccessibleDiffView]: { + description: localize('accessibleDiffView', "Whether the inline chat also renders an accessible diff viewer for its changes."), + default: 'auto', + type: 'string', + enum: ['auto', 'on', 'off'], + markdownEnumDescriptions: [ + localize('accessibleDiffView.auto', "The accessible diff viewer is based screen reader mode being enabled."), + localize('accessibleDiffView.on', "The accessible diff viewer is always enabled."), + localize('accessibleDiffView.off', "The accessible diff viewer is never enabled."), + ], } } }); From 7582df69014b762ee67c26dfa0a9935b9fe52849 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 21 Feb 2024 11:12:45 +0100 Subject: [PATCH 1474/1897] Dictated text goes off screen when using voice commands. (fix #205783) (#205838) --- .../contrib/codeEditor/browser/dictation/editorDictation.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts index b4ed9df731d..26d4b486350 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts @@ -199,10 +199,11 @@ export class EditorDictation extends Disposable implements IEditorContribution { previewStart = assertIsDefined(this.editor.getPosition()); } + const endPosition = new Position(previewStart.lineNumber, previewStart.column + text.length); this.editor.executeEdits(EditorDictation.ID, [ EditOperation.replace(Range.fromPositions(previewStart, previewStart.with(undefined, previewStart.column + lastReplaceTextLength)), text) ], [ - Selection.fromPositions(new Position(previewStart.lineNumber, previewStart.column + text.length)) + Selection.fromPositions(endPosition) ]); if (isPreview) { @@ -225,6 +226,7 @@ export class EditorDictation extends Disposable implements IEditorContribution { lastReplaceTextLength = 0; } + this.editor.revealPositionInCenterIfOutsideViewport(endPosition); this.widget.layout(); }; From f1e39630da255e29af3d71131f0801b4bf3b6e43 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 21 Feb 2024 11:13:04 +0100 Subject: [PATCH 1475/1897] fixing bug --- .../inlineCompletions/browser/inlineCompletionsModel.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 0b8bc138bf3..0be9f26bde9 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -472,15 +472,17 @@ export class InlineCompletionsModel extends Disposable { export function getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { const primaryPosition = positions[0]; const secondaryPositions = positions.slice(1); + const primaryEditStartPosition = primaryEdit.range.getStartPosition(); const primaryEditEndPosition = primaryEdit.range.getEndPosition(); const replacedTextAfterPrimaryCursor = textModel.getValueInRange( Range.fromPositions(primaryPosition, primaryEditEndPosition) ); - const positionWithinTextEdit = subtractPositions(primaryPosition, primaryEdit.range.getStartPosition()); + const positionWithinTextEdit = subtractPositions(primaryPosition, primaryEditStartPosition); const secondaryEditText = substringPos(primaryEdit.text, positionWithinTextEdit); return secondaryPositions.map(pos => { + const posEnd = addPositions(subtractPositions(pos, primaryEditStartPosition), primaryEditEndPosition); const textAfterSecondaryCursor = textModel.getValueInRange( - Range.fromPositions(pos, primaryEditEndPosition) + Range.fromPositions(pos, posEnd) ); const l = commonPrefixLength(replacedTextAfterPrimaryCursor, textAfterSecondaryCursor); const range = Range.fromPositions(pos, pos.delta(0, l)); From ef64ed413f4e16c4d1bdff4d9a2b03dae4acf1f2 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 21 Feb 2024 11:45:40 +0100 Subject: [PATCH 1476/1897] Aggressive caching with Open Current File as Release Notes (#205846) Fixes #205675 --- .../update/browser/releaseNotesEditor.ts | 18 +++++++++++++----- .../workbench/contrib/update/browser/update.ts | 4 ++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index a7303e1f8c9..b5e2de0ed52 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -45,7 +45,6 @@ export class ReleaseNotesManager { private readonly disposables = new DisposableStore(); public constructor( - private readonly _useCurrentFile: boolean, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @ILanguageService private readonly _languageService: ILanguageService, @@ -81,8 +80,8 @@ export class ReleaseNotesManager { } } - public async show(version: string): Promise { - const releaseNoteText = await this.loadReleaseNotes(version); + public async show(version: string, useCurrentFile: boolean): Promise { + const releaseNoteText = await this.loadReleaseNotes(version, useCurrentFile); this._lastText = releaseNoteText; const html = await this.renderBody(releaseNoteText); const title = nls.localize('releaseNotesInputName', "Release Notes: {0}", version); @@ -137,7 +136,7 @@ export class ReleaseNotesManager { return true; } - private async loadReleaseNotes(version: string): Promise { + private async loadReleaseNotes(version: string, useCurrentFile: boolean): Promise { const match = /^(\d+\.\d+)\./.exec(version); if (!match) { throw new Error('not found'); @@ -199,7 +198,12 @@ export class ReleaseNotesManager { const fetchReleaseNotes = async () => { let text; try { - text = this._useCurrentFile ? this._codeEditorService.getActiveCodeEditor()?.getModel()?.getValue() : await asTextOrError(await this._requestService.request({ url }, CancellationToken.None)); + if (useCurrentFile) { + const file = this._codeEditorService.getActiveCodeEditor()?.getModel()?.getValue(); + text = file ? file.substring(file.indexOf('#')) : undefined; + } else { + text = await asTextOrError(await this._requestService.request({ url }, CancellationToken.None)); + } } catch { throw new Error('Failed to fetch release notes'); } @@ -211,6 +215,10 @@ export class ReleaseNotesManager { return patchKeybindings(text); }; + // Don't cache the current file + if (useCurrentFile) { + return fetchReleaseNotes(); + } if (!this._releaseNotesCache.has(version)) { this._releaseNotesCache.set(version, (async () => { try { diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 5d811ef4399..969c563d5de 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -40,10 +40,10 @@ let releaseNotesManager: ReleaseNotesManager | undefined = undefined; export function showReleaseNotesInEditor(instantiationService: IInstantiationService, version: string, useCurrentFile: boolean) { if (!releaseNotesManager) { - releaseNotesManager = instantiationService.createInstance(ReleaseNotesManager, useCurrentFile); + releaseNotesManager = instantiationService.createInstance(ReleaseNotesManager); } - return releaseNotesManager.show(version); + return releaseNotesManager.show(version, useCurrentFile); } async function openLatestReleaseNotesInBrowser(accessor: ServicesAccessor) { From e8aed244b9f142aa8015c4a82fec01a4fb55453e Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 21 Feb 2024 11:29:08 +0100 Subject: [PATCH 1477/1897] Improves softAssert function --- src/vs/base/common/assert.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/base/common/assert.ts b/src/vs/base/common/assert.ts index 5c1bff27990..4ded48fb1de 100644 --- a/src/vs/base/common/assert.ts +++ b/src/vs/base/common/assert.ts @@ -35,9 +35,12 @@ export function assert(condition: boolean): void { } } +/** + * Like assert, but doesn't throw. + */ export function softAssert(condition: boolean): void { if (!condition) { - onUnexpectedError(new BugIndicatingError('Assertion Failed')); + onUnexpectedError(new BugIndicatingError('Soft Assertion Failed')); } } From 076e9df452fa0ee71d4fb1074896f74a0266ecd7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 21 Feb 2024 11:56:08 +0100 Subject: [PATCH 1478/1897] Something should happen when the flashing microphone is clicked (fix #205670) (#205842) * Something should happen when the flashing microphone is clicked (fix #205670) * switch to action bar * . --- .../browser/dictation/editorDictation.css | 7 ---- .../browser/dictation/editorDictation.ts | 41 ++++++++++++------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.css b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.css index 321f2b0a271..0a8d982123f 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.css +++ b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.css @@ -16,13 +16,6 @@ max-width: var(--vscode-editor-dictation-widget-width); } -.monaco-editor .editor-dictation-widget .codicon.codicon-mic-filled { - display: flex; - align-items: center; - width: 16px; - height: 16px; -} - .monaco-editor .editor-dictation-widget.recording .codicon.codicon-mic-filled { color: var(--vscode-activityBarBadge-background); animation: editor-dictation-animation 1s infinite; diff --git a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts index 26d4b486350..67863783054 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts @@ -4,15 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./editorDictation'; -import { localize2 } from 'vs/nls'; -import { IDimension, h, reset } from 'vs/base/browser/dom'; +import { localize, localize2 } from 'vs/nls'; +import { IDimension } from 'vs/base/browser/dom'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; -import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { Codicon } from 'vs/base/common/codicons'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { EditorAction2, EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; @@ -27,6 +26,9 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { assertIsDefined } from 'vs/base/common/types'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { toAction } from 'vs/base/common/actions'; +import { ThemeIcon } from 'vs/base/common/themables'; const EDITOR_DICTATION_IN_PROGRESS = new RawContextKey('editorDictation.inProgress', false); const VOICE_CATEGORY = localize2('voiceCategory', "Voice"); @@ -69,9 +71,11 @@ export class EditorDictationStartAction extends EditorAction2 { export class EditorDictationStopAction extends EditorAction2 { + static readonly ID = 'workbench.action.editorDictation.stop'; + constructor() { super({ - id: 'workbench.action.editorDictation.stop', + id: EditorDictationStopAction.ID, title: localize2('stopDictation', "Stop Dictation in Editor"), category: VOICE_CATEGORY, precondition: EDITOR_DICTATION_IN_PROGRESS, @@ -94,15 +98,21 @@ export class DictationWidget extends Disposable implements IContentWidget { readonly allowEditorOverflow = true; private readonly domNode = document.createElement('div'); - private readonly elements = h('.editor-dictation-widget@main', [h('span@mic')]); - constructor(private readonly editor: ICodeEditor) { + constructor(private readonly editor: ICodeEditor, keybindingService: IKeybindingService) { super(); - this.domNode.appendChild(this.elements.root); - this.domNode.style.zIndex = '1000'; + const actionBar = this._register(new ActionBar(this.domNode)); + const stopActionKeybinding = keybindingService.lookupKeybinding(EditorDictationStopAction.ID)?.getLabel(); + actionBar.push(toAction({ + id: EditorDictationStopAction.ID, + label: stopActionKeybinding ? localize('stopDictationShort1', "Stop Dictation ({0})", stopActionKeybinding) : localize('stopDictationShort2', "Stop Dictation"), + class: ThemeIcon.asClassName(Codicon.micFilled), + run: () => EditorDictation.get(editor)?.stop() + }), { icon: true, label: false, keybinding: stopActionKeybinding }); - reset(this.elements.mic, renderIcon(Codicon.micFilled)); + this.domNode.classList.add('editor-dictation-widget'); + this.domNode.appendChild(actionBar.domNode); } getId(): string { @@ -133,8 +143,8 @@ export class DictationWidget extends Disposable implements IContentWidget { const lineHeight = this.editor.getOption(EditorOption.lineHeight); const width = this.editor.getLayoutInfo().contentWidth * 0.7; - this.elements.main.style.setProperty('--vscode-editor-dictation-widget-height', `${lineHeight}px`); - this.elements.main.style.setProperty('--vscode-editor-dictation-widget-width', `${width}px`); + this.domNode.style.setProperty('--vscode-editor-dictation-widget-height', `${lineHeight}px`); + this.domNode.style.setProperty('--vscode-editor-dictation-widget-width', `${width}px`); return null; } @@ -148,11 +158,11 @@ export class DictationWidget extends Disposable implements IContentWidget { } active(): void { - this.elements.main.classList.add('recording'); + this.domNode.classList.add('recording'); } hide() { - this.elements.main.classList.remove('recording'); + this.domNode.classList.remove('recording'); this.editor.removeContentWidget(this); } } @@ -165,7 +175,7 @@ export class EditorDictation extends Disposable implements IEditorContribution { return editor.getContribution(EditorDictation.ID); } - private readonly widget = this._register(new DictationWidget(this.editor)); + private readonly widget = this._register(new DictationWidget(this.editor, this.keybindingService)); private readonly editorDictationInProgress = EDITOR_DICTATION_IN_PROGRESS.bindTo(this.contextKeyService); private sessionDisposables = this._register(new MutableDisposable()); @@ -173,7 +183,8 @@ export class EditorDictation extends Disposable implements IEditorContribution { constructor( private readonly editor: ICodeEditor, @ISpeechService private readonly speechService: ISpeechService, - @IContextKeyService private readonly contextKeyService: IContextKeyService + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IKeybindingService private readonly keybindingService: IKeybindingService ) { super(); } From a7ac7083e51d15475112c61f270e8c60f78e0096 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 21 Feb 2024 12:07:11 +0100 Subject: [PATCH 1479/1897] make sure LM provider error bubble all the way, reject response streams and result promise (#205849) fixes https://github.com/microsoft/vscode/issues/205722 --- .../api/browser/mainThreadChatProvider.ts | 1 + .../api/common/extHostChatProvider.ts | 31 ++++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index a3637ad0000..dd76d12f105 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -104,6 +104,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { task.catch(err => { this._logService.error('[CHAT] extension request ERRORED', err, extension.value, requestId); + throw err; }).finally(() => { this._logService.debug('[CHAT] extension request DONE', extension.value, requestId); }); diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index 3f35da68ace..8ad8318f9e1 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -17,6 +17,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; import { localize } from 'vs/nls'; import { INTERNAL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; type LanguageModelData = { readonly extension: ExtensionIdentifier; @@ -54,18 +55,32 @@ class LanguageModelRequest { // responses: AsyncIterable[] // FUTURE responses per N }; - promise.finally(() => { - this._isDone = true; - if (this._responseStreams.size > 0) { - for (const [, value] of this._responseStreams) { - value.stream.resolve(); - } - } else { - this._defaultStream.resolve(); + promise.then(() => { + for (const stream of this._streams()) { + stream.resolve(); } + }).catch(err => { + if (!(err instanceof Error)) { + err = new Error(toErrorMessage(err), { cause: err }); + } + for (const stream of this._streams()) { + stream.reject(err); + } + }).finally(() => { + this._isDone = true; }); } + private * _streams() { + if (this._responseStreams.size > 0) { + for (const [, value] of this._responseStreams) { + yield value.stream; + } + } else { + yield this._defaultStream; + } + } + handleFragment(fragment: IChatResponseFragment): void { if (this._isDone) { return; From 414f2cd189e6bf7b418d5e1d9c117826520470ed Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 21 Feb 2024 12:20:59 +0100 Subject: [PATCH 1480/1897] Git - fix reopen closed repositories action visibility issue (#205851) --- extensions/git/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index f1b45e74665..f000da7f3f2 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1462,7 +1462,7 @@ { "command": "git.reopenClosedRepositories", "group": "navigation@1", - "when": "scmProvider == git && git.closedRepositoryCount > 0" + "when": "git.closedRepositoryCount > 0" } ], "scm/sourceControl": [ From 6d57f57ec8ac105fb51e271de61a905de3d7ee6a Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 21 Feb 2024 12:22:53 +0100 Subject: [PATCH 1481/1897] Fix issue with settings not being respected in release notes (#205852) Settings with values separated are not respected in release notes Fixes #205678 Also fixes #205742 --- .../browser/markdownSettingRenderer.ts | 19 ++++++++++++++++--- .../browser/markdownSettingRenderer.test.ts | 9 +++++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index 5193d73e164..9fa69f9f0a4 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -14,9 +14,10 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IAction } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -const codeSettingRegex = /^/; -const codeFeatureRegex = /^/; +const codeSettingRegex = /^/; +const codeFeatureRegex = /^/; export class SimpleSettingRenderer { private _defaultSettings: DefaultSettings; @@ -28,7 +29,8 @@ export class SimpleSettingRenderer { @IConfigurationService private readonly _configurationService: IConfigurationService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IPreferencesService private readonly _preferencesService: IPreferencesService, - @ITelemetryService private readonly _telemetryService: ITelemetryService + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IClipboardService private readonly _clipboardService: IClipboardService ) { this._defaultSettings = new DefaultSettings([], ConfigurationTarget.USER); } @@ -258,6 +260,17 @@ export class SimpleSettingRenderer { } }); + actions.push({ + class: undefined, + enabled: true, + id: 'copySettingId', + tooltip: nls.localize('copySettingId', "Copy Setting ID"), + label: nls.localize('copySettingId', "Copy Setting ID"), + run: () => { + this._clipboardService.writeText(settingId); + } + }); + return actions; } diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index fb609bd79b7..834ff1b0a40 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -61,7 +61,7 @@ suite('Markdown Setting Renderer Test', () => { preferencesService = {}; contextMenuService = {}; Registry.as(Extensions.Configuration).registerConfiguration(configuration); - settingRenderer = new SimpleSettingRenderer(configurationService, contextMenuService, preferencesService, { publicLog2: () => { } } as any); + settingRenderer = new SimpleSettingRenderer(configurationService, contextMenuService, preferencesService, { publicLog2: () => { } } as any, { writeText: async () => { } } as any); }); suiteTeardown(() => { @@ -82,7 +82,7 @@ suite('Markdown Setting Renderer Test', () => { test('actions with no value', () => { const uri = URI.parse(settingRenderer.settingToUriString('example.booleanSetting')); const actions = settingRenderer.getActions(uri); - assert.strictEqual(actions?.length, 1); + assert.strictEqual(actions?.length, 2); assert.strictEqual(actions[0].label, 'View "Example: Boolean Setting" in Settings'); }); @@ -91,7 +91,7 @@ suite('Markdown Setting Renderer Test', () => { const uri = URI.parse(settingRenderer.settingToUriString('example.stringSetting', 'three')); const verifyOriginalState = (actions: IAction[] | undefined): actions is IAction[] => { - assert.strictEqual(actions?.length, 2); + assert.strictEqual(actions?.length, 3); assert.strictEqual(actions[0].label, 'Set "Example: String Setting" to "three"'); assert.strictEqual(actions[1].label, 'View in Settings'); assert.strictEqual(configurationService.getValue('example.stringSetting'), 'two'); @@ -104,9 +104,10 @@ suite('Markdown Setting Renderer Test', () => { await actions[0].run(); assert.strictEqual(configurationService.getValue('example.stringSetting'), 'three'); const actionsUpdated = settingRenderer.getActions(uri); - assert.strictEqual(actionsUpdated?.length, 2); + assert.strictEqual(actionsUpdated?.length, 3); assert.strictEqual(actionsUpdated[0].label, 'Restore value of "Example: String Setting"'); assert.strictEqual(actions[1].label, 'View in Settings'); + assert.strictEqual(actions[2].label, 'Copy Setting ID'); assert.strictEqual(configurationService.getValue('example.stringSetting'), 'three'); // Restore the value From 7b0593941809093055d3a69299ed127c557d4f92 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 21 Feb 2024 12:25:06 +0100 Subject: [PATCH 1482/1897] "Try Feature" tooltip sounds awkward (#205841) * "Try Feature" tooltip sounds awkward Fixes #205741 * Fix test --- .../contrib/markdown/browser/markdownSettingRenderer.ts | 2 +- .../markdown/test/browser/markdownSettingRenderer.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index 9fa69f9f0a4..c4cf0aa94fe 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -169,7 +169,7 @@ export class SimpleSettingRenderer { private renderSetting(setting: ISetting, newValue: string | undefined): string | undefined { const href = this.settingToUriString(setting.key, newValue); - const title = nls.localize('changeSettingTitle', "Try feature"); + const title = nls.localize('changeSettingTitle', "View or change setting"); return ` ${setting.key} diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index 834ff1b0a40..55e38f3d018 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -73,7 +73,7 @@ suite('Markdown Setting Renderer Test', () => { const htmlNoValue = ''; const renderedHtmlNoValue = htmlRenderer(htmlNoValue); assert.strictEqual(renderedHtmlNoValue, - ` + ` example.booleanSetting `); From 67f91582f588d1d37a94ce5b18e01ba535184967 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 21 Feb 2024 12:36:00 +0100 Subject: [PATCH 1483/1897] actionbar - `animated` is an obsolete property (#205854) --- src/vs/base/browser/ui/actionbar/actionbar.ts | 5 ----- src/vs/base/browser/ui/menu/menu.ts | 4 ---- src/vs/workbench/browser/parts/compositeBar.ts | 1 - src/vs/workbench/browser/parts/globalCompositeBar.ts | 1 - .../extensions/browser/abstractRuntimeExtensionsEditor.ts | 3 +-- .../workbench/contrib/extensions/browser/extensionEditor.ts | 3 +-- .../workbench/contrib/extensions/browser/extensionsList.ts | 1 - src/vs/workbench/contrib/markers/browser/markersTable.ts | 3 +-- .../contrib/preferences/browser/keybindingsEditor.ts | 2 +- .../contrib/preferences/browser/preferencesWidgets.ts | 1 - .../workbench/contrib/preferences/browser/settingsEditor2.ts | 1 - .../contrib/workspace/browser/workspaceTrustEditor.ts | 2 +- 12 files changed, 5 insertions(+), 22 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 9ba16500c8f..5f5ad352842 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -47,7 +47,6 @@ export interface IActionBarOptions { readonly actionRunner?: IActionRunner; readonly ariaLabel?: string; readonly ariaRole?: string; - readonly animated?: boolean; readonly triggerKeys?: ActionTrigger; readonly allowContextMenu?: boolean; readonly preventLoopNavigation?: boolean; @@ -137,10 +136,6 @@ export class ActionBar extends Disposable implements IActionRunner { this.domNode = document.createElement('div'); this.domNode.className = 'monaco-action-bar'; - if (options.animated !== false) { - this.domNode.classList.add('animated'); - } - let previousKeys: KeyCode[]; let nextKeys: KeyCode[]; diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 35343e4f878..4429e37687d 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -1056,10 +1056,6 @@ ${formatRule(Codicon.menuSubmenu)} cursor: default; } -.monaco-menu .monaco-action-bar.animated .action-item.active { - transform: scale(1.272019649, 1.272019649); /* 1.272019649 = √φ */ -} - .monaco-menu .monaco-action-bar .action-item .icon, .monaco-menu .monaco-action-bar .action-item .codicon { display: inline-block; diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index a2007e18299..76d6c0c1ee4 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -221,7 +221,6 @@ export class CompositeBar extends Widget implements ICompositeBar { orientation: this.options.orientation, ariaLabel: localize('activityBarAriaLabel', "Active View Switcher"), ariaRole: 'tablist', - animated: false, preventLoopNavigation: this.options.preventLoopNavigation, triggerKeys: { keyDown: true } })); diff --git a/src/vs/workbench/browser/parts/globalCompositeBar.ts b/src/vs/workbench/browser/parts/globalCompositeBar.ts index 550866d685b..db3471fcadc 100644 --- a/src/vs/workbench/browser/parts/globalCompositeBar.ts +++ b/src/vs/workbench/browser/parts/globalCompositeBar.ts @@ -97,7 +97,6 @@ export class GlobalCompositeBar extends Disposable { }, orientation: ActionsOrientation.VERTICAL, ariaLabel: localize('manage', "Manage"), - animated: false, preventLoopNavigation: true })); diff --git a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index 413da693a6a..991a3df035c 100644 --- a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -241,10 +241,9 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { const msgContainer = append(desc, $('div.msg')); - const actionbar = new ActionBar(desc, { animated: false }); + const actionbar = new ActionBar(desc); actionbar.onDidRun(({ error }) => error && this._notificationService.error(error)); - const timeContainer = append(element, $('.time')); const activationTime = append(timeContainer, $('div.activation-time')); const profileTime = append(timeContainer, $('div.profile-time')); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 658f429cff1..21986a4b709 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -99,7 +99,7 @@ class NavBar extends Disposable { super(); const element = append(container, $('.navbar')); this.actions = []; - this.actionbar = this._register(new ActionBar(element, { animated: false })); + this.actionbar = this._register(new ActionBar(element)); } push(id: string, label: string, tooltip: string): void { @@ -336,7 +336,6 @@ export class ExtensionEditor extends EditorPane { const actionsAndStatusContainer = append(details, $('.actions-status-container')); const extensionActionBar = this._register(new ActionBar(actionsAndStatusContainer, { - animated: false, actionViewItemProvider: (action: IAction) => { if (action instanceof ExtensionDropDownAction) { return action.createActionViewItem(); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 0f3b1b04d08..1bf769f4c66 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -98,7 +98,6 @@ export class Renderer implements IPagedRenderer { const verifiedPublisherWidget = this.instantiationService.createInstance(VerifiedPublisherWidget, append(publisher, $(`.verified-publisher`)), true); const publisherDisplayName = append(publisher, $('.publisher-name.ellipsis')); const actionbar = new ActionBar(footer, { - animated: false, actionViewItemProvider: (action: IAction) => { if (action instanceof ActionWithDropDownAction) { return new ExtensionActionWithDropdownActionViewItem(action, { icon: true, label: true, menuActionsOrProvider: { getActions: () => action.menuActions }, menuActionClassNames: (action.class || '').split(' ') }, this.contextMenuService); diff --git a/src/vs/workbench/contrib/markers/browser/markersTable.ts b/src/vs/workbench/contrib/markers/browser/markersTable.ts index d466b58a6b2..dc454d054ed 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTable.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTable.ts @@ -74,8 +74,7 @@ class MarkerSeverityColumnRenderer implements ITableRenderer action.id === QuickFixAction.ID ? this.instantiationService.createInstance(QuickFixActionViewItem, action) : undefined, - animated: false + actionViewItemProvider: (action: IAction) => action.id === QuickFixAction.ID ? this.instantiationService.createInstance(QuickFixActionViewItem, action) : undefined }); return { actionBar, icon }; diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 0b0c257a0e5..b0026da674c 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -851,7 +851,7 @@ class ActionsColumnRenderer implements ITableRenderer action.id === 'folderSettings' ? this.folderSettings : undefined })); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index a06c8048351..c0632e494bc 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -655,7 +655,6 @@ export class SettingsEditor2 extends EditorPane { this.controlsElement = DOM.append(searchContainer, DOM.$('.settings-clear-widget')); const actionBar = this._register(new ActionBar(this.controlsElement, { - animated: false, actionViewItemProvider: (action) => { if (action.id === filterAction.id) { return this.instantiationService.createInstance(SettingsSearchFilterDropdownMenuActionViewItem, action, this.actionRunner, this.searchWidget); diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts index 578fe2777d7..fe7f625a784 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts @@ -406,7 +406,7 @@ class TrustedUriActionsColumnRenderer implements ITableRenderer Date: Wed, 21 Feb 2024 12:44:54 +0100 Subject: [PATCH 1484/1897] Removed Copilot related code from TypeScript Refactor Provider --- .../src/languageFeatures/refactor.ts | 46 ++----------------- 1 file changed, 4 insertions(+), 42 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/refactor.ts b/extensions/typescript-language-features/src/languageFeatures/refactor.ts index bbc5aef39b7..a61758cc66d 100644 --- a/extensions/typescript-language-features/src/languageFeatures/refactor.ts +++ b/extensions/typescript-language-features/src/languageFeatures/refactor.ts @@ -21,7 +21,7 @@ import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService import { coalesce } from '../utils/arrays'; import { nulToken } from '../utils/cancellation'; import FormattingOptionsManager from './fileConfigurationManager'; -import { CompositeCommand, EditorChatFollowUp, EditorChatFollowUp_Args } from './util/copilot'; +import { CompositeCommand, EditorChatFollowUp } from './util/copilot'; import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration'; function toWorkspaceEdit(client: ITypeScriptServiceClient, edits: readonly Proto.FileCodeEdits[]): vscode.WorkspaceEdit { @@ -347,14 +347,10 @@ class InlinedCodeAction extends vscode.CodeAction { public readonly refactor: Proto.ApplicableRefactorInfo, public readonly action: Proto.RefactorActionInfo, public readonly range: vscode.Range, - public readonly copilotRename?: (info: Proto.RefactorEditInfo) => vscode.Command, ) { - const title = copilotRename ? action.description + ' and suggest a name with Copilot.' : action.description; + const title = action.description; super(title, InlinedCodeAction.getKind(action)); - if (copilotRename) { - this.isAI = true; - } if (action.notApplicableReason) { this.disabled = { reason: action.notApplicableReason }; } @@ -392,15 +388,12 @@ class InlinedCodeAction extends vscode.CodeAction { if (response.body.renameLocation) { // Disable renames in interactive playground https://github.com/microsoft/vscode/issues/75137 if (this.document.uri.scheme !== fileSchemes.walkThroughSnippet) { - if (this.copilotRename && this.command) { - this.command.title = 'Copilot: ' + this.command.title; - } this.command = { command: CompositeCommand.ID, title: '', arguments: coalesce([ this.command, - this.copilotRename ? this.copilotRename(response.body) : { + { command: 'editor.action.rename', arguments: [[ this.document.uri, @@ -635,38 +628,7 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider vscode.Command) = info => ({ - title: '', - command: EditorChatFollowUp.ID, - arguments: [{ - message: `Rename ${newName} to a better name based on usage.`, - expand: Extract_Constant.matches(action) ? { - kind: 'navtree-function', - pos: typeConverters.Position.fromLocation(info.renameLocation!), - } : { - kind: 'refactor-info', - refactor: info, - }, - action: { type: 'refactor', refactor: action }, - document, - } satisfies EditorChatFollowUp_Args] - }); - codeActions.push(new InlinedCodeAction(this.client, document, refactor, action, rangeOrSelection, copilotRename)); - } - } + codeActions.push(new InlinedCodeAction(this.client, document, refactor, action, rangeOrSelection)); } for (const codeAction of codeActions) { codeAction.isPreferred = TypeScriptRefactorProvider.isPreferred(action, allActions); From be34739ee85723404c11063dcca59377b6c358e8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 21 Feb 2024 12:53:06 +0100 Subject: [PATCH 1485/1897] editors - use `canSerialize` properly (#205856) --- .../workbench/common/editor/editorGroupModel.ts | 2 +- .../contrib/chat/browser/chatEditorInput.ts | 10 +++------- .../browser/interactive.contribution.ts | 17 +++++++++++------ .../browser/multiDiffEditorInput.ts | 10 ++++++---- .../browser/searchEditor.contribution.ts | 4 ++++ .../browser/terminalEditorSerializer.ts | 9 ++++----- .../browser/webviewEditorInputSerializer.ts | 2 +- .../common/untitledTextEditorHandler.ts | 2 +- 8 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 497ef8daf46..8a018234256 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -1033,7 +1033,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { const editorSerializer = registry.getEditorSerializer(editor); if (editorSerializer) { - const value = editorSerializer.serialize(editor); + const value = editorSerializer.canSerialize(editor) ? editorSerializer.serialize(editor) : undefined; // Editor can be serialized if (typeof value === 'string') { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index f59f81e2ffb..e6cc6c81ff0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -183,16 +183,12 @@ interface ISerializedChatEditorInput { } export class ChatEditorInputSerializer implements IEditorSerializer { - canSerialize(input: EditorInput): boolean { - return input instanceof ChatEditorInput; + canSerialize(input: EditorInput): input is ChatEditorInput & { readonly sessionId: string } { + return input instanceof ChatEditorInput && typeof input.sessionId === 'string'; } serialize(input: EditorInput): string | undefined { - if (!(input instanceof ChatEditorInput)) { - return undefined; - } - - if (typeof input.sessionId !== 'string') { + if (!this.canSerialize(input)) { return undefined; } diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index 3ba9491111e..05d7d253673 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -10,7 +10,6 @@ import { parse } from 'vs/base/common/marshalling'; import { Schemas } from 'vs/base/common/network'; import { extname, isEqual } from 'vs/base/common/resources'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; -import { assertType } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -263,13 +262,19 @@ type interactiveEditorInputData = { resource: URI; inputResource: URI; name: str export class InteractiveEditorSerializer implements IEditorSerializer { public static readonly ID = InteractiveEditorInput.ID; - canSerialize(editor: EditorInput): boolean { - const interactiveEditorInput = editor as InteractiveEditorInput; - return URI.isUri(interactiveEditorInput?.primary?.resource) && URI.isUri(interactiveEditorInput?.inputResource); + canSerialize(editor: EditorInput): editor is InteractiveEditorInput { + if (!(editor instanceof InteractiveEditorInput)) { + return false; + } + + return URI.isUri(editor.primary.resource) && URI.isUri(editor.inputResource); } - serialize(input: EditorInput): string { - assertType(input instanceof InteractiveEditorInput); + serialize(input: EditorInput): string | undefined { + if (!this.canSerialize(input)) { + return undefined; + } + return JSON.stringify({ resource: input.primary.resource, inputResource: input.inputResource, diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 3841a5d56aa..058373fd465 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -364,14 +364,16 @@ interface ISerializedMultiDiffEditorInput { export class MultiDiffEditorSerializer implements IEditorSerializer { - // TODO@bpasero, @aiday-mar: following canSerialize should be removed (debt item) - canSerialize(editor: EditorInput): boolean { + canSerialize(editor: EditorInput): editor is MultiDiffEditorInput { return editor instanceof MultiDiffEditorInput && !editor.isTransient; } serialize(editor: MultiDiffEditorInput): string | undefined { - const shouldSerialize = editor instanceof MultiDiffEditorInput && !editor.isTransient; - return shouldSerialize ? JSON.stringify(editor.serialize()) : undefined; + if (!this.canSerialize(editor)) { + return undefined; + } + + return JSON.stringify(editor.serialize()); } deserialize(instantiationService: IInstantiationService, serializedEditor: string): EditorInput | undefined { diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 020ae109fd0..be7cad04444 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -113,6 +113,10 @@ class SearchEditorInputSerializer implements IEditorSerializer { } serialize(input: SearchEditorInput) { + if (!this.canSerialize(input)) { + return undefined; + } + if (input.isDisposed()) { return JSON.stringify({ modelUri: undefined, dirty: false, config: input.tryReadConfigSync(), name: input.getName(), matchRanges: [], backingUri: input.backingUri?.toString() } as SerializedSearchEditor); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorSerializer.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorSerializer.ts index 5ad20c381c0..72200823f2e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorSerializer.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorSerializer.ts @@ -14,16 +14,15 @@ export class TerminalInputSerializer implements IEditorSerializer { @ITerminalEditorService private readonly _terminalEditorService: ITerminalEditorService ) { } - public canSerialize(editorInput: TerminalEditorInput): boolean { - return !!editorInput.terminalInstance?.persistentProcessId; + public canSerialize(editorInput: TerminalEditorInput): editorInput is TerminalEditorInput & { readonly terminalInstance: ITerminalInstance } { + return typeof editorInput.terminalInstance?.persistentProcessId === 'number' && editorInput.terminalInstance.shouldPersist; } public serialize(editorInput: TerminalEditorInput): string | undefined { - if (!editorInput.terminalInstance?.persistentProcessId || !editorInput.terminalInstance.shouldPersist) { + if (!this.canSerialize(editorInput)) { return; } - const term = JSON.stringify(this._toJson(editorInput.terminalInstance)); - return term; + return JSON.stringify(this._toJson(editorInput.terminalInstance)); } public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput | undefined { diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts index ade4299afe4..8a590bff082 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts @@ -58,7 +58,7 @@ export class WebviewEditorInputSerializer implements IEditorSerializer { } public serialize(input: WebviewInput): string | undefined { - if (!this._webviewWorkbenchService.shouldPersist(input)) { + if (!this.canSerialize(input)) { return undefined; } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts index f786bd25f04..b9357adb2c1 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts @@ -40,7 +40,7 @@ export class UntitledTextEditorInputSerializer implements IEditorSerializer { } serialize(editorInput: EditorInput): string | undefined { - if (!this.filesConfigurationService.isHotExitEnabled || editorInput.isDisposed()) { + if (!this.canSerialize(editorInput)) { return undefined; } From 19781f2c52ef5e6b721ce64f436f45126b1e11d1 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 21 Feb 2024 12:41:19 +0100 Subject: [PATCH 1486/1897] Sets focus border for focused editors --- .../multiDiffEditorWidget/diffEditorItemTemplate.ts | 4 +++- .../browser/widget/multiDiffEditorWidget/style.css | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts index 8c44f6e4181..0f3353c66bc 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts @@ -230,7 +230,8 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< this._elements.root.style.position = 'absolute'; // For sticky scroll - const delta = Math.max(0, Math.min(verticalRange.length - this._headerHeight, viewPort.start - verticalRange.start)); + const maxDelta = verticalRange.length - this._headerHeight; + const delta = Math.max(0, Math.min(viewPort.start - verticalRange.start, maxDelta)); this._elements.header.style.transform = `translateY(${delta}px)`; globalTransaction(tx => { @@ -242,6 +243,7 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< this.editor.getOriginalEditor().setScrollTop(editorScroll); this._elements.header.classList.toggle('shadow', delta > 0 || editorScroll > 0); + this._elements.header.classList.toggle('collapsed', delta === maxDelta); } public hide(): void { diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css b/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css index ca41df09a04..44f0703d580 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css @@ -7,6 +7,10 @@ background: var(--vscode-multiDiffEditor-background); overflow-y: hidden; + .focused { + --vscode-multiDiffEditor-border: var(--vscode-focusBorder); + } + .multiDiffEntry { display: flex; flex-direction: column; @@ -27,6 +31,10 @@ z-index: 1000; background: var(--vscode-editor-background); + &:not(.collapsed) .header-content { + border-bottom: 1px solid var(--vscode-sideBarSectionHeader-border); + } + .header-content { margin: 8px 8px 0px 8px; padding: 8px 5px; @@ -43,8 +51,6 @@ color: var(--vscode-foreground); background: var(--vscode-multiDiffEditor-headerBackground); - border-bottom: 1px solid var(--vscode-sideBarSectionHeader-border); - &.shadow { box-shadow: var(--vscode-scrollbar-shadow) 0px 6px 6px -6px; } From 2e99a31949532c4537b979c5ff6b794284f8ecae Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 21 Feb 2024 13:42:01 +0100 Subject: [PATCH 1487/1897] comparing bulk file operations --- .../contrib/bulkEdit/browser/preview/bulkEditPane.ts | 5 +++-- .../contrib/bulkEdit/browser/preview/bulkEditTree.ts | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 9728bfc1e2d..21cbba6bc42 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -5,7 +5,7 @@ import 'vs/css!./bulkEdit'; import { WorkbenchAsyncDataTree, IOpenEvent } from 'vs/platform/list/browser/listService'; -import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, CategoryElementRenderer, BulkEditNaviLabelProvider, CategoryElement, BulkEditSorter } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree'; +import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, CategoryElementRenderer, BulkEditNaviLabelProvider, CategoryElement, BulkEditSorter, compareBulkFileOperations } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -361,8 +361,9 @@ export class BulkEditPane extends ViewPane { if (this._fileOperations === fileOperations && this._resources) { return this._resources; } + const sortedFileOperations = fileOperations.sort(compareBulkFileOperations); const resources: IResourceDiffEditorInput[] = []; - for (const operation of fileOperations) { + for (const operation of sortedFileOperations) { const operationUri = operation.uri; const previewUri = this._currentProvider!.asPreviewUri(operationUri); // delete -> show single editor diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts index 5dda15aee3e..c5fd28d6a8e 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts @@ -286,7 +286,7 @@ export class BulkEditSorter implements ITreeSorter { compare(a: BulkEditElement, b: BulkEditElement): number { if (a instanceof FileElement && b instanceof FileElement) { - return compare(a.edit.uri.toString(), b.edit.uri.toString()); + return compareBulkFileOperations(a.edit, b.edit); } if (a instanceof TextEditElement && b instanceof TextEditElement) { @@ -297,6 +297,10 @@ export class BulkEditSorter implements ITreeSorter { } } +export function compareBulkFileOperations(a: BulkFileOperation, b: BulkFileOperation): number { + return compare(a.uri.toString(), b.uri.toString()); +} + // --- ACCESSI export class BulkEditAccessibilityProvider implements IListAccessibilityProvider { From a3b932f0d68dc2bd905233f31c62ab95b0cdf11d Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 21 Feb 2024 15:02:02 +0100 Subject: [PATCH 1488/1897] Fixes #205850 (#205862) * Sets focus border for focused editors * Fixes #205850 * Fixes CI and don't use focus --- .../multiDiffEditorWidget.ts | 9 +++- .../multiDiffEditorWidgetImpl.ts | 45 ++++++++++++------- .../bulkEdit/browser/preview/bulkEditPane.ts | 8 +++- .../browser/multiDiffEditor.ts | 12 +++-- 4 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index 5a869f83bd2..6867bb84ac7 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -46,8 +46,8 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public reveal(resource: IMultiDiffResource, range: Range): void { - this._widgetImpl.get().reveal(resource, range); + public reveal(resource: IMultiDiffResource, options?: RevealOptions): void { + this._widgetImpl.get().reveal(resource, options); } public createViewModel(model: IMultiDiffEditorModel): MultiDiffEditorViewModel { @@ -82,3 +82,8 @@ export class MultiDiffEditorWidget extends Disposable { return this._widgetImpl.get().tryGetCodeEditor(resource); } } + +export interface RevealOptions { + range?: Range; + highlight: boolean; +} diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index ca4e240c1da..a913cdad350 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -10,24 +10,24 @@ import { Disposable, IReference, toDisposable } from 'vs/base/common/lifecycle'; import { IObservable, IReader, autorun, autorunWithStore, derived, derivedObservableWithCache, derivedWithStore, observableFromEvent, observableValue } from 'vs/base/common/observable'; import { ITransaction, disposableObservableValue, globalTransaction, transaction } from 'vs/base/common/observableInternal/base'; import { Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable'; +import { URI } from 'vs/base/common/uri'; import 'vs/css!./style'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ObservableElementSizeObserver } from 'vs/editor/browser/widget/diffEditor/utils'; +import { RevealOptions } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget'; import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; +import { IRange } from 'vs/editor/common/core/range'; +import { ISelection, Selection } from 'vs/editor/common/core/selection'; +import { IDiffEditor } from 'vs/editor/common/editorCommon'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ContextKeyValue, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { DiffEditorItemTemplate, TemplateData } from './diffEditorItemTemplate'; import { DocumentDiffItemViewModel, MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { ObjectPool } from './objectPool'; -import { ContextKeyValue, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ISelection, Selection } from 'vs/editor/common/core/selection'; -import { URI } from 'vs/base/common/uri'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IDiffEditor } from 'vs/editor/common/editorCommon'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Range } from 'vs/editor/common/core/range'; -import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _elements = h('div.monaco-component.multiDiffEditor', [ @@ -107,7 +107,6 @@ export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _workbenchUIElementFactory: IWorkbenchUIElementFactory, @IContextKeyService private readonly _parentContextKeyService: IContextKeyService, @IInstantiationService private readonly _parentInstantiationService: IInstantiationService, - @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); @@ -190,8 +189,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { this._scrollableElement.setScrollPosition({ scrollLeft: scrollState.left, scrollTop: scrollState.top }); } - // todo@aiday-mar need to reveal the range instead of just the start line number - public reveal(resource: IMultiDiffResource, range: Range): void { + public reveal(resource: IMultiDiffResource, options?: RevealOptions): void { const viewItems = this._viewItems.get(); let searchCallback: (item: VirtualizedViewItem) => boolean; if ('original' in resource) { @@ -200,11 +198,18 @@ export class MultiDiffEditorWidgetImpl extends Disposable { searchCallback = (item) => item.viewModel.modifiedUri?.toString() === resource.modified.toString(); } const index = viewItems.findIndex(searchCallback); - let scrollTop = (range.startLineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); + let scrollTop = 0; for (let i = 0; i < index; i++) { scrollTop += viewItems[i].contentHeight.get() + this._spaceBetweenPx; } this._scrollableElement.setScrollPosition({ scrollTop }); + + const diffEditor = viewItems[index].template.get()?.editor; + const editor = 'original' in resource ? diffEditor?.getOriginalEditor() : diffEditor?.getModifiedEditor(); + if (editor && options?.range) { + editor.revealRangeInCenter(options.range); + highlightRange(editor, options.range); + } } public getViewState(): IMultiDiffEditorViewState { @@ -290,6 +295,16 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } } +function highlightRange(targetEditor: ICodeEditor, range: IRange) { + const modelNow = targetEditor.getModel(); + const decorations = targetEditor.createDecorationsCollection([{ range, options: { description: 'symbol-navigate-action-highlight', className: 'symbolHighlight' } }]); + setTimeout(() => { + if (targetEditor.getModel() === modelNow) { + decorations.clear(); + } + }, 350); +} + export interface IMultiDiffEditorViewState { scrollState: { top: number; left: number }; docStates?: Record; @@ -307,7 +322,7 @@ export interface IMultiDiffEditorOptions extends ITextEditorOptions { export interface IMultiDiffEditorOptionsViewState { revealData?: { resource: IMultiDiffResource; - range: Range; + range?: IRange; }; } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 9728bfc1e2d..07dc0d160fc 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -37,8 +37,8 @@ import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; import { IResourceDiffEditorInput } from 'vs/workbench/common/editor'; -import { Range } from 'vs/editor/common/core/range'; import { IMultiDiffEditorOptions } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IRange } from 'vs/editor/common/core/range'; const enum State { Data = 'data', @@ -325,11 +325,15 @@ export class BulkEditPane extends ViewPane { if (!fileOperations) { return; } + + let selection: IRange | undefined = undefined; let fileElement: FileElement; if (e.element instanceof TextEditElement) { fileElement = e.element.parent; + selection = e.element.edit.textEdit.textEdit.range; } else if (e.element instanceof FileElement) { fileElement = e.element; + selection = e.element.edit.textEdits[0]?.textEdit.textEdit.range; } else { // invalid event return; @@ -341,7 +345,7 @@ export class BulkEditPane extends ViewPane { viewState: { revealData: { resource: { original: fileElement.edit.uri }, - range: new Range(1, 1, 1, 1) + range: selection, } } }; diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index bb8bdc7c16e..c1491431828 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -26,6 +26,7 @@ import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEdit import { IMultiDiffEditorOptions, IMultiDiffEditorViewState } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; +import { Range } from 'vs/editor/common/core/range'; export class MultiDiffEditor extends AbstractEditorWithViewState { static readonly ID = 'multiDiffEditor'; @@ -80,19 +81,22 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { From 44ffd1a464fb3b976bc110ec768b8ec5fbcb7752 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 21 Feb 2024 06:21:13 -0800 Subject: [PATCH 1489/1897] fix #205789 (#205868) --- .../workbench/contrib/extensions/browser/media/extension.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/media/extension.css b/src/vs/workbench/contrib/extensions/browser/media/extension.css index c9622b3b43c..136ce1af0f7 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extension.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extension.css @@ -174,14 +174,16 @@ display: flex; justify-content: flex-end; padding-right: 7px; - height: 18px; + height: 24px; overflow: hidden; + align-items: center; } .extension-list-item > .details > .footer > .author { flex: 1; display: flex; align-items: center; + line-height: 24px; } .extension-list-item > .details > .footer > .author > .publisher-name { From e7a3ef849bcc870fe2546952a83e0428b42ba127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=93=E8=89=AF?= <1204183885@qq.com> Date: Wed, 21 Feb 2024 23:34:11 +0800 Subject: [PATCH 1490/1897] Add tips for debug views (#205861) * Add tips for debug views * Add tips for debug views --- src/vs/platform/environment/node/argv.ts | 2 +- src/vs/workbench/contrib/debug/browser/welcomeView.ts | 5 +++-- .../workbench/contrib/files/browser/explorerViewlet.ts | 10 +++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index a5d96361f08..0d3a9795ffb 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -214,7 +214,7 @@ export interface ErrorReporter { onEmptyValue(id: string): void; onDeprecatedOption(deprecatedId: string, message: string): void; - getSubcommandReporter?(commmand: string): ErrorReporter; + getSubcommandReporter?(command: string): ErrorReporter; } const ignoringReporter = { diff --git a/src/vs/workbench/contrib/debug/browser/welcomeView.ts b/src/vs/workbench/contrib/debug/browser/welcomeView.ts index b0c09a24c52..703f645f19e 100644 --- a/src/vs/workbench/contrib/debug/browser/welcomeView.ts +++ b/src/vs/workbench/contrib/debug/browser/welcomeView.ts @@ -165,8 +165,9 @@ viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { { key: 'customizeRunAndDebugOpenFolder', comment: [ - 'Please do not translate the word "commmand", it is part of our internal syntax which must not change', - '{Locked="](command:{0})"}' + 'Please do not translate the word "command", it is part of our internal syntax which must not change', + 'Please do not translate "launch.json", it is the specific configuration file name', + '{Locked="](command:{0})"}', ] }, "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", (isMacintosh && !isWeb) ? OpenFileFolderAction.ID : OpenFolderAction.ID), diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 010e42cb837..9785e04a5b1 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -283,7 +283,7 @@ const openRecentButton = `[${openRecent}](command:${OpenRecentAction.ID})`; const viewsRegistry = Registry.as(Extensions.ViewsRegistry); viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize({ key: 'noWorkspaceHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + content: localize({ key: 'noWorkspaceHelp', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] }, "You have not yet added a folder to the workspace.\n{0}", addRootFolderButton), when: ContextKeyExpr.and( // inside a .code-workspace @@ -296,7 +296,7 @@ viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { }); viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize({ key: 'noFolderHelpWeb', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + content: localize({ key: 'noFolderHelpWeb', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] }, "You have not yet opened a folder.\n{0}\n{1}", openFolderViaWorkspaceButton, openRecentButton), when: ContextKeyExpr.and( // inside a .code-workspace @@ -309,7 +309,7 @@ viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { }); viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize({ key: 'remoteNoFolderHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + content: localize({ key: 'remoteNoFolderHelp', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] }, "Connected to remote.\n{0}", openFolderButton), when: ContextKeyExpr.and( // not inside a .code-workspace @@ -323,7 +323,7 @@ viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { }); viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize({ key: 'noFolderButEditorsHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + content: localize({ key: 'noFolderButEditorsHelp', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] }, "You have not yet opened a folder.\n{0}\nOpening a folder will close all currently open editors. To keep them open, {1} instead.", openFolderButton, addAFolderButton), when: ContextKeyExpr.and( // editors are opened @@ -340,7 +340,7 @@ viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { }); viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize({ key: 'noFolderHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + content: localize({ key: 'noFolderHelp', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] }, "You have not yet opened a folder.\n{0}", openFolderButton), when: ContextKeyExpr.and( // no editor is open From 451333c31fe3cb1a39a08e63d94c2e8b06c9d26e Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 21 Feb 2024 15:56:15 +0000 Subject: [PATCH 1491/1897] Small chat API updates (#205880) * Fix #205812 * Fix #205810 * Fix #205809 --- src/vscode-dts/vscode.proposed.chatParticipant.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts index 468f12ae690..43bacd595dd 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -83,7 +83,7 @@ declare module 'vscode' { message: string; /** - * If partial markdown content was sent over the `progress` callback before the response terminated, then this flag + * If partial markdown content was sent over the {@link ChatRequestHandler handler}'s response stream before the response terminated, then this flag * can be set to true and it will be rendered with incomplete markdown features patched up. * * For example, if the response terminated after sending part of a triple-backtick code block, then the editor will @@ -281,7 +281,7 @@ declare module 'vscode' { followupProvider?: ChatFollowupProvider; /** - * When the user clicks this participant in `/help`, this text will be submitted to this command + * When the user clicks this participant in `/help`, this text will be submitted to this participant. */ sampleRequest?: string; @@ -408,7 +408,7 @@ declare module 'vscode' { * Push a progress part to this stream. Short-hand for * `push(new ChatResponseProgressPart(value))`. * - * @param value + * @param value A progress message * @returns This stream. */ progress(value: string): ChatResponseStream; From 63a3873d110168ac19028f63e1b07f6ac12149df Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 21 Feb 2024 17:12:34 +0100 Subject: [PATCH 1492/1897] SCM - fix an issue with window title variables (#205883) --- .../workbench/contrib/scm/browser/activity.ts | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index efd752e0f53..671a46f65a0 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -224,8 +224,9 @@ export class SCMActiveRepositoryContextKeyController implements IWorkbenchContri private activeRepositoryNameContextKey: IContextKey; private activeRepositoryBranchNameContextKey: IContextKey; + private focusedRepository: ISCMRepository | undefined = undefined; + private focusDisposable: IDisposable = Disposable.None; private readonly disposables = new DisposableStore(); - private readonly focusedRepositoryDisposables = new DisposableStore(); constructor( @IContextKeyService contextKeyService: IContextKeyService, @@ -254,28 +255,25 @@ export class SCMActiveRepositoryContextKeyController implements IWorkbenchContri return; } - const activeResourceRepository = Iterable.find( - this.scmViewService.visibleRepositories, + const repository = Iterable.find( + this.scmViewService.repositories, r => Boolean(r.provider.rootUri && this.uriIdentityService.extUri.isEqualOrParent(activeResource, r.provider.rootUri)) ); - this.onDidFocusRepository(activeResourceRepository); + this.onDidFocusRepository(repository); } private onDidFocusRepository(repository: ISCMRepository | undefined): void { - this.focusedRepositoryDisposables.clear(); - - if (!repository) { + if (!repository || this.focusedRepository === repository) { return; } - this.focusedRepositoryDisposables.add(repository.provider.onDidChangeHistoryProvider(() => { - if (repository.provider.historyProvider) { - this.focusedRepositoryDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => this.updateContextKeys(repository))); - } + this.focusDisposable.dispose(); + this.focusedRepository = repository; - this.updateContextKeys(repository); - })); + if (repository && repository.provider.onDidChangeStatusBarCommands) { + this.focusDisposable = repository.provider.onDidChangeStatusBarCommands(() => this.updateContextKeys(repository)); + } this.updateContextKeys(repository); } @@ -284,6 +282,11 @@ export class SCMActiveRepositoryContextKeyController implements IWorkbenchContri this.activeRepositoryNameContextKey.set(repository?.provider.name ?? ''); this.activeRepositoryBranchNameContextKey.set(repository?.provider.historyProvider?.currentHistoryItemGroup?.label ?? ''); } + + dispose(): void { + this.focusDisposable.dispose(); + this.disposables.dispose(); + } } export class SCMActiveResourceContextKeyController implements IWorkbenchContribution { From 2ad2a35cadcce552081b81f4ac9373b23939a8e2 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 21 Feb 2024 08:53:05 -0800 Subject: [PATCH 1493/1897] Make sure we don't report errors in chat code blocks (#204779) --- .../src/configuration/fileSchemes.ts | 3 +-- .../src/tsServer/bufferSyncSupport.ts | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/extensions/typescript-language-features/src/configuration/fileSchemes.ts b/extensions/typescript-language-features/src/configuration/fileSchemes.ts index 07dd38a5379..cfc3f66db17 100644 --- a/extensions/typescript-language-features/src/configuration/fileSchemes.ts +++ b/extensions/typescript-language-features/src/configuration/fileSchemes.ts @@ -16,8 +16,6 @@ export const azurerepos = 'azurerepos'; export const vsls = 'vsls'; export const walkThroughSnippet = 'walkThroughSnippet'; export const vscodeNotebookCell = 'vscode-notebook-cell'; -export const memFs = 'memfs'; -export const vscodeVfs = 'vscode-vfs'; export const officeScript = 'office-script'; export const chatCodeBlock = 'vscode-chat-code-block'; @@ -31,6 +29,7 @@ export function getSemanticSupportedSchemes() { untitled, walkThroughSnippet, vscodeNotebookCell, + chatCodeBlock, ]; } diff --git a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts index c95b2e473e3..dc5948314d9 100644 --- a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts +++ b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts @@ -745,6 +745,10 @@ export default class BufferSyncSupport extends Disposable { } private shouldValidate(buffer: SyncedBuffer): boolean { + if (buffer.resource.scheme === fileSchemes.chatCodeBlock) { + return false; + } + if (!this.client.configuration.enableProjectDiagnostics && !this._tabResources.has(buffer.resource)) { // Only validate resources that are showing to the user return false; } From 52d39c1fa7eddc73e58595b69702e942fb075253 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 21 Feb 2024 09:00:14 -0800 Subject: [PATCH 1494/1897] Fix high contrast light md images (#205888) Fixes #203686 --- extensions/github/markdown.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/github/markdown.css b/extensions/github/markdown.css index 84441f42b16..7cfc8ba75a5 100644 --- a/extensions/github/markdown.css +++ b/extensions/github/markdown.css @@ -5,7 +5,7 @@ .vscode-dark img[src$=\#gh-light-mode-only], .vscode-light img[src$=\#gh-dark-mode-only], -.vscode-high-contrast img[src$=\#gh-light-mode-only], +.vscode-high-contrast:not(.vscode-high-contrast-light) img[src$=\#gh-light-mode-only], .vscode-high-contrast-light img[src$=\#gh-dark-mode-only] { display: none; } From b111d8076b5aa45cf82114dfc0fb21ac459dfa12 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 21 Feb 2024 18:05:50 +0100 Subject: [PATCH 1495/1897] fixes https://github.com/microsoft/vscode/issues/203466 (#205889) --- .../workbench/contrib/inlineChat/browser/inlineChatController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 39b4563509b..33094375625 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -931,6 +931,7 @@ export class InlineChatController implements IEditorContribution { this._zone.value.setWidgetMargins(widgetPosition); this._zone.value.show(widgetPosition); } else { + this._zone.value.setWidgetMargins(widgetPosition); this._zone.value.updatePositionAndHeight(widgetPosition); } } From 02d0259c51f63dd3c3e3ffd58be46bc14ca58b74 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 21 Feb 2024 17:55:47 +0100 Subject: [PATCH 1496/1897] Update active recording color and animation --- .../actions/voiceChatActions.ts | 38 ++++--------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index e1e9306cb1c..987e8d663c8 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -776,7 +776,7 @@ registerThemingParticipant((theme, collector) => { let activeRecordingDimmedColor: Color | undefined; if (theme.type === ColorScheme.LIGHT || theme.type === ColorScheme.DARK) { activeRecordingColor = theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND) ?? theme.getColor(focusBorder); - activeRecordingDimmedColor = activeRecordingColor?.transparent(0.2); + activeRecordingDimmedColor = activeRecordingColor?.transparent(0.38); } else { activeRecordingColor = theme.getColor(contrastBorder); activeRecordingDimmedColor = theme.getColor(contrastBorder); @@ -786,45 +786,23 @@ registerThemingParticipant((theme, collector) => { collector.addRule(` .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { - color: ${activeRecordingColor}; - outline: 1px solid ${activeRecordingColor}; - outline-offset: -1px; - animation: pulseAnimation 1s infinite; + outline: 2px solid ${activeRecordingColor}; border-radius: 50%; - } - - .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, - .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { - position: absolute; - outline: 1px solid ${activeRecordingColor}; - outline-offset: 2px; - border-radius: 50%; - width: 16px; - height: 16px; - } - - .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after, - .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after { - content: ''; - position: absolute; - outline: 1px solid ${activeRecordingDimmedColor}; - outline-offset: 3px; - animation: pulseAnimation 1s infinite; - border-radius: 50%; - width: 16px; - height: 16px; + animation: pulseAnimation 1500ms ease-in-out infinite !important; + padding-left: 4px; + height: 17px; } @keyframes pulseAnimation { 0% { - outline-width: 1px; + outline-width: 2px; } 50% { - outline-width: 3px; + outline-width: 5px; outline-color: ${activeRecordingDimmedColor}; } 100% { - outline-width: 1px; + outline-width: 2px; } } `); From c7672504241058f8355b1a48937c0eb51bd9ea15 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 21 Feb 2024 09:46:39 -0800 Subject: [PATCH 1497/1897] fix #205895 (#205896) --- .../contrib/extensions/browser/extensionFeaturesTab.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts index 35491eaa620..8bd7b3cd177 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts @@ -53,10 +53,10 @@ class RuntimeStatusMarkdownRenderer extends Disposable implements IExtensionFeat shouldRender(manifest: IExtensionManifest): boolean { const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); - if (this.extensionService.extensions.some(e => ExtensionIdentifier.equals(e.identifier, extensionId))) { - return !!manifest.main || !!manifest.browser; + if (!this.extensionService.extensions.some(e => ExtensionIdentifier.equals(e.identifier, extensionId))) { + return false; } - return !!manifest.activationEvents; + return !!manifest.main || !!manifest.browser; } render(manifest: IExtensionManifest): IRenderedData { From 16ad3d4df85c5c5aed5bda147d6205f8a48d5af0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 21 Feb 2024 10:10:08 -0800 Subject: [PATCH 1498/1897] Fix #205170 (#205897) --- .../output/browser/output.contribution.ts | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 794a8e60d57..28eba6fc109 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -20,7 +20,7 @@ import { ViewContainer, IViewContainersRegistry, ViewContainerLocation, Extensio import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { IQuickPickItem, IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickPickItem, IQuickInputService, IQuickPickSeparator, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { AUX_WINDOW_GROUP, AUX_WINDOW_GROUP_TYPE, IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { assertIsDefined } from 'vs/base/common/types'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -408,16 +408,30 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { const editorService = accessor.get(IEditorService); const fileConfigurationService = accessor.get(IFilesConfigurationService); - const entries: IOutputChannelQuickPickItem[] = outputService.getChannelDescriptors().filter(c => c.file && c.log) - .map(channel => ({ id: channel.id, label: channel.label, channel })); - - const argName = args && typeof args === 'string' ? args : undefined; let entry: IOutputChannelQuickPickItem | undefined; - if (argName) { - entry = entries.find(e => e.id === argName); + const argName = args && typeof args === 'string' ? args : undefined; + const extensionChannels: IOutputChannelQuickPickItem[] = []; + const coreChannels: IOutputChannelQuickPickItem[] = []; + for (const c of outputService.getChannelDescriptors()) { + if (c.file && c.log) { + const e = { id: c.id, label: c.label, channel: c }; + if (c.extensionId) { + extensionChannels.push(e); + } else { + coreChannels.push(e); + } + if (e.id === argName) { + entry = e; + } + } } if (!entry) { - entry = await quickInputService.pick(entries, { placeHolder: nls.localize('selectlogFile', "Select Log File") }); + const entries: QuickPickInput[] = [...extensionChannels.sort((a, b) => a.label.localeCompare(b.label))]; + if (entries.length && coreChannels.length) { + entries.push({ type: 'separator' }); + entries.push(...coreChannels.sort((a, b) => a.label.localeCompare(b.label))); + } + entry = await quickInputService.pick(entries, { placeHolder: nls.localize('selectlogFile', "Select Log File") }); } if (entry) { const resource = assertIsDefined(entry.channel.file); From ad082bd32886f74ddb27168accbc9054f6115360 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 21 Feb 2024 10:17:14 -0800 Subject: [PATCH 1499/1897] Restore focus when the widget is dismissed. (#205900) * Restore focus when the widget is dismissed. * Only restore focus when the widget has focus. --- .../controller/chat/notebookChatController.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index cec4fe9242f..b22b51850d6 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -784,6 +784,27 @@ export class NotebookChatController extends Disposable implements INotebookEdito dismiss() { + // move focus back to the cell above + if (this._widget) { + const widgetIndex = this._widget.afterModelPosition; + const currentFocus = this._notebookEditor.getFocus(); + + if (currentFocus.start === widgetIndex && currentFocus.end === widgetIndex) { + // focus is on the widget + if (widgetIndex === 0) { + // on top of all cells + if (this._notebookEditor.getLength() > 0) { + this._notebookEditor.focusNotebookCell(this._notebookEditor.cellAt(0)!, 'container'); + } + } else { + const cell = this._notebookEditor.cellAt(widgetIndex - 1); + if (cell) { + this._notebookEditor.focusNotebookCell(cell, 'container'); + } + } + } + } + this._ctxCellWidgetFocused.set(false); this._ctxUserDidEdit.set(false); this._sessionCtor?.cancel(); From 7a40a10b24ea6b7dc8169f4716bfd460c448b7bd Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Wed, 21 Feb 2024 11:21:39 -0800 Subject: [PATCH 1500/1897] chore: bump proxy-agent and ip (#205903) --- build/npm/gyp/yarn.lock | 6 +++--- remote/yarn.lock | 12 ++++++------ yarn.lock | 12 ++++++------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/build/npm/gyp/yarn.lock b/build/npm/gyp/yarn.lock index 3716071611c..c444d89094f 100644 --- a/build/npm/gyp/yarn.lock +++ b/build/npm/gyp/yarn.lock @@ -367,9 +367,9 @@ inherits@2, inherits@^2.0.3: integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== ip@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" - integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" + integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== is-fullwidth-code-point@^3.0.0: version "3.0.0" diff --git a/remote/yarn.lock b/remote/yarn.lock index e660692a806..5fdbd0988ea 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -59,9 +59,9 @@ integrity sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg== "@vscode/proxy-agent@^0.19.0": - version "0.19.0" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.19.0.tgz#ea6571854abb8691ca24a95ba81a6850c54da5df" - integrity sha512-VKCEELuv/BFt1h9wAQE9zuKZM2UAJlJzucXFlvyUYrrPRG66Mgm2JQmClxkE5VRa0gCDRzUClZBT8Fptx8Ce7A== + version "0.19.1" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.19.1.tgz#d9640d85df1c48885580b68bb4b2b54e17f5332c" + integrity sha512-cs1VOx6d5n69HhgzK0cWeyfudJt+9LdJi/vtgRRxxwisWKg4h83B3+EUJ4udF5SEkJgMBp3oU0jheZVt43ImnQ== dependencies: "@tootallnate/once" "^3.0.0" agent-base "^7.0.1" @@ -314,9 +314,9 @@ ini@~1.3.0: integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== ip@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" - integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" + integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== is-extglob@^2.1.1: version "2.1.1" diff --git a/yarn.lock b/yarn.lock index fa86aaa2893..7dcbe6fe030 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1410,9 +1410,9 @@ node-addon-api "^6.0.0" "@vscode/proxy-agent@^0.19.0": - version "0.19.0" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.19.0.tgz#ea6571854abb8691ca24a95ba81a6850c54da5df" - integrity sha512-VKCEELuv/BFt1h9wAQE9zuKZM2UAJlJzucXFlvyUYrrPRG66Mgm2JQmClxkE5VRa0gCDRzUClZBT8Fptx8Ce7A== + version "0.19.1" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.19.1.tgz#d9640d85df1c48885580b68bb4b2b54e17f5332c" + integrity sha512-cs1VOx6d5n69HhgzK0cWeyfudJt+9LdJi/vtgRRxxwisWKg4h83B3+EUJ4udF5SEkJgMBp3oU0jheZVt43ImnQ== dependencies: "@tootallnate/once" "^3.0.0" agent-base "^7.0.1" @@ -5394,9 +5394,9 @@ invert-kv@^2.0.0: integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== ip@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" - integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" + integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== is-absolute@^1.0.0: version "1.0.0" From 0cae183880d27078528fd50fffd4e29b5de4a82c Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 21 Feb 2024 19:28:21 +0000 Subject: [PATCH 1501/1897] Include conversation history context to `provideCommands` (#205867) * Include conversation history context to `provideCommands` so that chat participant commands can be dynamic to the conversation #199908 * Fix tests * Fix test --- .../api/browser/mainThreadChatAgents2.ts | 17 ++-- .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostChatAgents2.ts | 17 ++-- .../contrib/chat/browser/chat.contribution.ts | 2 +- .../browser/contrib/chatInputEditorContrib.ts | 8 +- .../contrib/chat/common/chatAgents.ts | 7 +- .../contrib/chat/common/chatModel.ts | 38 ++++++++- .../contrib/chat/common/chatRequestParser.ts | 12 ++- .../contrib/chat/common/chatServiceImpl.ts | 34 +------- .../contrib/chat/common/chatViewModel.ts | 5 ++ .../contrib/chat/common/voiceChat.ts | 32 +++---- .../actions/voiceChatActions.ts | 2 +- .../chat/test/browser/chatVariables.test.ts | 3 + ..._agent_and_subcommand_after_newline.0.snap | 7 +- ..._subcommand_with_leading_whitespace.0.snap | 7 +- ...uestParser_agent_with_question_mark.0.snap | 7 +- ...er_agent_with_subcommand_after_text.0.snap | 7 +- ...hatRequestParser_agents__subCommand.0.snap | 7 +- ..._agents_and_variables_and_multiline.0.snap | 7 +- ..._and_variables_and_multiline__part2.0.snap | 7 +- .../test/common/chatRequestParser.test.ts | 23 ++--- .../chat/test/common/chatService.test.ts | 12 ++- .../chat/test/common/mockChatService.ts | 85 +++++++++++++++++++ .../chat/test/common/voiceChat.test.ts | 14 +-- .../browser/inlineChatController.ts | 2 +- .../vscode.proposed.chatParticipant.d.ts | 3 +- 26 files changed, 223 insertions(+), 144 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/test/common/mockChatService.ts diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index fa796bde948..c143be65078 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -20,6 +20,7 @@ import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { AddDynamicVariableAction, IAddDynamicVariableContext } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables'; import { IChatAgentCommand, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatFollowup, IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; @@ -76,7 +77,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA } $registerAgent(handle: number, extension: ExtensionIdentifier, name: string, metadata: IExtensionChatAgentMetadata): void { - let lastSlashCommands: IChatAgentCommand[] | undefined; + const lastSlashCommands: WeakMap = new WeakMap(); const d = this._chatAgentService.registerAgent({ id: name, extensionId: extension, @@ -96,15 +97,19 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA return this._proxy.$provideFollowups(request, handle, result, token); }, - get lastSlashCommands() { - return lastSlashCommands; + getLastSlashCommands: (model: IChatModel) => { + return lastSlashCommands.get(model); }, - provideSlashCommands: async (token) => { + provideSlashCommands: async (model, history, token) => { if (!this._agents.get(handle)?.hasSlashCommands) { return []; // save an IPC call } - lastSlashCommands = await this._proxy.$provideSlashCommands(handle, token); - return lastSlashCommands; + const commands = await this._proxy.$provideSlashCommands(handle, { history }, token); + if (model) { + lastSlashCommands.set(model, commands); + } + + return commands; }, provideWelcomeMessage: (token: CancellationToken) => { return this._proxy.$provideWelcomeMessage(handle, token); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 3481cd5a510..8dbc24affe7 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1227,7 +1227,7 @@ export type IChatAgentHistoryEntryDto = { export interface ExtHostChatAgentsShape2 { $invokeAgent(handle: number, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; - $provideSlashCommands(handle: number, token: CancellationToken): Promise; + $provideSlashCommands(handle: number, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, token: CancellationToken): Promise; $acceptFeedback(handle: number, result: IChatAgentResult, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void; $acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 9f4f87ac999..ad00314cac6 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -190,7 +190,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService, this.commands.converter, sessionDisposables); try { - const convertedHistory = await this.prepareHistoryTurns(request, context); + const convertedHistory = await this.prepareHistoryTurns(request.agentId, context); const task = agent.invoke( typeConvert.ChatAgentRequest.to(request), { history: convertedHistory }, @@ -220,13 +220,13 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } } - private async prepareHistoryTurns(request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise<(vscode.ChatRequestTurn | vscode.ChatResponseTurn)[]> { + private async prepareHistoryTurns(agentId: string, context: { history: IChatAgentHistoryEntryDto[] }): Promise<(vscode.ChatRequestTurn | vscode.ChatResponseTurn)[]> { const res: (vscode.ChatRequestTurn | vscode.ChatResponseTurn)[] = []; for (const h of context.history) { const ehResult = typeConvert.ChatAgentResult.to(h.result); - const result: vscode.ChatResult = request.agentId === h.request.agentId ? + const result: vscode.ChatResult = agentId === h.request.agentId ? ehResult : { ...ehResult, metadata: undefined }; @@ -245,13 +245,16 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { this._sessionDisposables.deleteAndDispose(sessionId); } - async $provideSlashCommands(handle: number, token: CancellationToken): Promise { + async $provideSlashCommands(handle: number, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { // this is OK, the agent might have disposed while the request was in flight return []; } - return agent.provideSlashCommands(token); + + const convertedHistory = await this.prepareHistoryTurns(agent.id, context); + + return agent.provideSlashCommands({ history: convertedHistory }, token); } async $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, token: CancellationToken): Promise { @@ -386,11 +389,11 @@ class ExtHostChatAgent { return await this._agentVariableProvider.provider.provideCompletionItems(query, token) ?? []; } - async provideSlashCommands(token: CancellationToken): Promise { + async provideSlashCommands(context: vscode.ChatContext, token: CancellationToken): Promise { if (!this._commandProvider) { return []; } - const result = await this._commandProvider.provideCommands(token); + const result = await this._commandProvider.provideCommands(context, token); if (!result) { return []; } diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 528810a99a0..d1cdc123a6b 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -266,7 +266,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { const actionArg: IChatExecuteActionContext = { inputValue: `${agentWithLeader} ${a.metadata.sampleRequest}` }; const urlSafeArg = encodeURIComponent(JSON.stringify(actionArg)); const agentLine = `* [\`${agentWithLeader}\`](command:${SubmitAction.ID}?${urlSafeArg}) - ${a.metadata.description}`; - const commands = await a.provideSlashCommands(CancellationToken.None); + const commands = await a.provideSlashCommands(undefined, [], CancellationToken.None); const commandText = commands.map(c => { const actionArg: IChatExecuteActionContext = { inputValue: `${agentWithLeader} ${chatSubcommandLeader}${c.name} ${c.sampleRequest ?? ''}` }; const urlSafeArg = encodeURIComponent(JSON.stringify(actionArg)); diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 12c466bfd6f..791f9e41fd3 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -27,6 +27,7 @@ import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { SelectAndInsertFileAction, dynamicVariableDecorationType } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables'; import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatSlashCommandBackground, chatSlashCommandForeground } from 'vs/workbench/contrib/chat/common/chatColors'; +import { getHistoryEntriesFromModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; @@ -398,7 +399,7 @@ class AgentCompletions extends Disposable { } const usedAgent = parsedRequest[usedAgentIdx] as ChatRequestAgentPart; - const commands = await usedAgent.agent.provideSlashCommands(token); // Refresh the cache here + const commands = await usedAgent.agent.provideSlashCommands(widget.viewModel.model, getHistoryEntriesFromModel(widget.viewModel.model), token); // Refresh the cache here return { suggestions: commands.map((c, i) => { @@ -421,7 +422,8 @@ class AgentCompletions extends Disposable { triggerCharacters: ['/'], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); - if (!widget) { + const viewModel = widget?.viewModel; + if (!widget || !viewModel) { return; } @@ -431,7 +433,7 @@ class AgentCompletions extends Disposable { } const agents = this.chatAgentService.getAgents(); - const all = agents.map(agent => agent.provideSlashCommands(token)); + const all = agents.map(agent => agent.provideSlashCommands(viewModel.model, getHistoryEntriesFromModel(viewModel.model), token)); const commands = await raceCancellation(Promise.all(all), token); if (!commands) { diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 3f9eb992697..4ee4c07f0cf 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { ProviderResult } from 'vs/editor/common/languages'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IChatProgressResponseContent, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatModel, IChatProgressResponseContent, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; //#region agent service, commands etc @@ -33,8 +33,8 @@ export interface IChatAgentData { export interface IChatAgent extends IChatAgentData { invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; provideFollowups?(request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise; - lastSlashCommands?: IChatAgentCommand[]; - provideSlashCommands(token: CancellationToken): Promise; + getLastSlashCommands(model: IChatModel): IChatAgentCommand[] | undefined; + provideSlashCommands(model: IChatModel | undefined, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>; provideSampleQuestions?(token: CancellationToken): ProviderResult; } @@ -155,7 +155,6 @@ export class ChatAgentService extends Disposable implements IChatAgentService { throw new Error(`No agent with id ${id} registered`); } data.agent.metadata = { ...data.agent.metadata, ...updateMetadata }; - data.agent.provideSlashCommands(CancellationToken.None); // Update the cached slash commands this._onDidChangeAgents.fire(); } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 64b50701e84..c8e47cb8b16 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -14,8 +14,8 @@ import { URI, UriComponents, UriDto } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IOffsetRange, OffsetRange } from 'vs/editor/common/core/offsetRange'; import { ILogService } from 'vs/platform/log/common/log'; -import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatRequestTextPart, IParsedChatRequest, getPromptText, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatCommandButton, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -843,3 +843,37 @@ export class ChatWelcomeMessageModel implements IChatWelcomeMessageModel { return this.session.responderAvatarIconUri; } } + +export function getHistoryEntriesFromModel(model: IChatModel): IChatAgentHistoryEntry[] { + const history: IChatAgentHistoryEntry[] = []; + for (const request of model.getRequests()) { + if (!request.response) { + continue; + } + + const promptTextResult = getPromptText(request.message); + const historyRequest: IChatAgentRequest = { + sessionId: model.sessionId, + requestId: request.id, + agentId: request.response.agent?.id ?? '', + message: promptTextResult.message, + command: request.response.slashCommand?.name, + variables: updateRanges(request.variableData, promptTextResult.diff) // TODO bit of a hack + }; + history.push({ request: historyRequest, response: request.response.response.value, result: request.response.result ?? {} }); + } + + return history; +} + +export function updateRanges(variableData: IChatRequestVariableData, diff: number): IChatRequestVariableData { + return { + variables: variableData.variables.map(v => ({ + ...v, + range: { + start: v.range.start - diff, + endExclusive: v.range.endExclusive - diff + } + })) + }; +} diff --git a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts index 007b8c8a191..aa05f52b6d8 100644 --- a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts +++ b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts @@ -7,7 +7,9 @@ import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicVariablePart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequest, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -19,12 +21,14 @@ export class ChatRequestParser { constructor( @IChatAgentService private readonly agentService: IChatAgentService, @IChatVariablesService private readonly variableService: IChatVariablesService, - @IChatSlashCommandService private readonly slashCommandService: IChatSlashCommandService + @IChatSlashCommandService private readonly slashCommandService: IChatSlashCommandService, + @IChatService private readonly chatService: IChatService ) { } parseChatRequest(sessionId: string, message: string): IParsedChatRequest { const parts: IParsedChatRequestPart[] = []; const references = this.variableService.getDynamicVariables(sessionId); // must access this list before any async calls + const model = this.chatService.getSession(sessionId)!; let lineNumber = 1; let column = 1; @@ -38,7 +42,7 @@ export class ChatRequestParser { } else if (char === chatAgentLeader) { newPart = this.tryToParseAgent(message.slice(i), message, i, new Position(lineNumber, column), parts); } else if (char === chatSubcommandLeader) { - newPart = this.tryToParseSlashCommand(message.slice(i), message, i, new Position(lineNumber, column), parts); + newPart = this.tryToParseSlashCommand(model, message.slice(i), message, i, new Position(lineNumber, column), parts); } if (!newPart) { @@ -138,7 +142,7 @@ export class ChatRequestParser { return; } - private tryToParseSlashCommand(remainingMessage: string, fullMessage: string, offset: number, position: IPosition, parts: ReadonlyArray): ChatRequestSlashCommandPart | ChatRequestAgentSubcommandPart | undefined { + private tryToParseSlashCommand(model: IChatModel, remainingMessage: string, fullMessage: string, offset: number, position: IPosition, parts: ReadonlyArray): ChatRequestSlashCommandPart | ChatRequestAgentSubcommandPart | undefined { const nextSlashMatch = remainingMessage.match(slashReg); if (!nextSlashMatch) { return; @@ -167,7 +171,7 @@ export class ChatRequestParser { return; } - const subCommands = usedAgent.agent.lastSlashCommands; + const subCommands = usedAgent.agent.getLastSlashCommands(model); const subCommand = subCommands?.find(c => c.name === command); if (subCommand) { // Valid agent subcommand diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 630e7e853e8..89081fe437d 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -20,9 +20,9 @@ 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 { IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, ISerializableChatData, ISerializableChatsData, getHistoryEntriesFromModel, updateRanges } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; @@ -531,23 +531,7 @@ export class ChatService extends Disposable implements IChatService { const defaultAgent = this.chatAgentService.getDefaultAgent(); if (agentPart || (defaultAgent && !commandPart)) { const agent = (agentPart?.agent ?? defaultAgent)!; - const history: IChatAgentHistoryEntry[] = []; - for (const request of model.getRequests()) { - if (!request.response) { - continue; - } - - const promptTextResult = getPromptText(request.message); - const historyRequest: IChatAgentRequest = { - sessionId, - requestId: request.id, - agentId: request.response.agent?.id ?? '', - message: promptTextResult.message, - command: request.response.slashCommand?.name, - variables: updateRanges(request.variableData, promptTextResult.diff) // TODO bit of a hack - }; - history.push({ request: historyRequest, response: request.response.response.value, result: request.response.result ?? {} }); - } + const history = getHistoryEntriesFromModel(model); const initVariableData: IChatRequestVariableData = { variables: [] }; request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); @@ -764,15 +748,3 @@ export class ChatService extends Disposable implements IChatService { this.trace('transferChatSession', `Transferred session ${model.sessionId} to workspace ${toWorkspace.toString()}`); } } - -function updateRanges(variableData: IChatRequestVariableData, diff: number): IChatRequestVariableData { - return { - variables: variableData.variables.map(v => ({ - ...v, - range: { - start: v.range.start - diff, - endExclusive: v.range.endExclusive - diff - } - })) - }; -} diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index 1a959494464..a91512a6840 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -41,6 +41,7 @@ export interface IChatSessionInitEvent { } export interface IChatViewModel { + readonly model: IChatModel; readonly initState: ChatModelInitState; readonly providerId: string; readonly sessionId: string; @@ -146,6 +147,10 @@ export class ChatViewModel extends Disposable implements IChatViewModel { return this._inputPlaceholder ?? this._model.inputPlaceholder; } + get model(): IChatModel { + return this._model; + } + setInputPlaceholder(text: string): void { this._inputPlaceholder = text; this._onDidChange.fire({ kind: 'changePlaceholder' }); diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index c0cf6daa574..9c92a44354a 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -9,6 +9,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { rtrim } from 'vs/base/common/strings'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ISpeechService, ISpeechToTextEvent, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; @@ -16,6 +17,7 @@ export const IVoiceChatService = createDecorator('voiceChatSe export interface IVoiceChatSessionOptions { readonly usesAgents?: boolean; + readonly model?: IChatModel; } export interface IVoiceChatService { @@ -75,37 +77,23 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { private static readonly CHAT_AGENT_ALIAS = new Map([['vscode', 'code']]); - private _phrases: Map | undefined = undefined; - private get phrases(): Map { - if (!this._phrases) { - this._phrases = this.createPhrases(); - } - - return this._phrases; - } - constructor( @ISpeechService private readonly speechService: ISpeechService, @IChatAgentService private readonly chatAgentService: IChatAgentService ) { super(); - - this.registerListeners(); } - private registerListeners(): void { - this._register(this.chatAgentService.onDidChangeAgents(() => this._phrases = undefined)); - } - - private createPhrases(): Map { + private createPhrases(model?: IChatModel): Map { const phrases = new Map(); for (const agent of this.chatAgentService.getAgents()) { const agentPhrase = `${VoiceChatService.PHRASES_LOWER[VoiceChatService.AGENT_PREFIX]} ${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase(); phrases.set(agentPhrase, { agent: agent.id }); - if (agent.lastSlashCommands) { - for (const slashCommand of agent.lastSlashCommands) { + const commands = model && agent.getLastSlashCommands(model); + if (commands) { + for (const slashCommand of commands) { const slashCommandPhrase = `${VoiceChatService.PHRASES_LOWER[VoiceChatService.COMMAND_PREFIX]} ${slashCommand.name}`.toLowerCase(); phrases.set(slashCommandPhrase, { agent: agent.id, command: slashCommand.name }); @@ -138,6 +126,8 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { const emitter = disposables.add(new Emitter()); const session = this.speechService.createSpeechToTextSession(token, 'chat'); + + const phrases = this.createPhrases(options.model); disposables.add(session.onDidChange(e => { switch (e.status) { case SpeechToTextStatus.Recognizing: @@ -153,7 +143,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { // Check for agent + slash command if (options.usesAgents && startsWithAgent && !detectedAgent && !detectedSlashCommand && originalWords.length >= 4) { - const phrase = this.phrases.get(originalWords.slice(0, 4).map(word => this.normalizeWord(word)).join(' ')); + const phrase = phrases.get(originalWords.slice(0, 4).map(word => this.normalizeWord(word)).join(' ')); if (phrase) { transformedWords = [this.toText(phrase, PhraseTextType.AGENT_AND_COMMAND), ...originalWords.slice(4)]; @@ -168,7 +158,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { // Check for agent (if not done already) if (options.usesAgents && startsWithAgent && !detectedAgent && !transformedWords && originalWords.length >= 2) { - const phrase = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); + const phrase = phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); if (phrase) { transformedWords = [this.toText(phrase, PhraseTextType.AGENT), ...originalWords.slice(2)]; @@ -182,7 +172,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { // Check for slash command (if not done already) if (startsWithSlashCommand && !detectedSlashCommand && !transformedWords && originalWords.length >= 2) { - const phrase = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); + const phrase = phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); if (phrase) { transformedWords = [this.toText(phrase, options.usesAgents && !detectedAgent ? PhraseTextType.AGENT_AND_COMMAND : // rewrite `/fix` to `@workspace /foo` in this case diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 987e8d663c8..831f3d48724 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -300,7 +300,7 @@ class VoiceChatSessions { this.voiceChatGettingReadyKey.set(true); - const voiceChatSession = this.voiceChatService.createVoiceChatSession(cts.token, { usesAgents: controller.context !== 'inline' }); + const voiceChatSession = this.voiceChatService.createVoiceChatSession(cts.token, { usesAgents: controller.context !== 'inline', model: context?.widget?.viewModel?.model }); let inputValue = controller.getInput(); diff --git a/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts index f835af908b4..35173312a8c 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts @@ -12,8 +12,10 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; +import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { MockChatWidgetService } from 'vs/workbench/contrib/chat/test/browser/mockChatWidget'; +import { MockChatService } from 'vs/workbench/contrib/chat/test/common/mockChatService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -30,6 +32,7 @@ suite('ChatVariables', function () { instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(IExtensionService, new TestExtensionService()); instantiationService.stub(IChatVariablesService, service); + instantiationService.stub(IChatService, new MockChatService()); instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService))); }); diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap index cc7ecaf508d..4a241114279 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap @@ -29,12 +29,7 @@ id: "agent", metadata: { description: "" }, provideSlashCommands: [Function provideSlashCommands], - lastSlashCommands: [ - { - name: "subCommand", - description: "" - } - ] + getLastSlashCommands: [Function getLastSlashCommands] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap index d46c197f633..becd9bf6f31 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap @@ -29,12 +29,7 @@ id: "agent", metadata: { description: "" }, provideSlashCommands: [Function provideSlashCommands], - lastSlashCommands: [ - { - name: "subCommand", - description: "" - } - ] + getLastSlashCommands: [Function getLastSlashCommands] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap index 4891a73e641..50c67ea58d0 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap @@ -15,12 +15,7 @@ id: "agent", metadata: { description: "" }, provideSlashCommands: [Function provideSlashCommands], - lastSlashCommands: [ - { - name: "subCommand", - description: "" - } - ] + getLastSlashCommands: [Function getLastSlashCommands] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap index d7889981915..345e8c874de 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap @@ -15,12 +15,7 @@ id: "agent", metadata: { description: "" }, provideSlashCommands: [Function provideSlashCommands], - lastSlashCommands: [ - { - name: "subCommand", - description: "" - } - ] + getLastSlashCommands: [Function getLastSlashCommands] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap index df42f889d05..406e20cfe55 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap @@ -15,12 +15,7 @@ id: "agent", metadata: { description: "" }, provideSlashCommands: [Function provideSlashCommands], - lastSlashCommands: [ - { - name: "subCommand", - description: "" - } - ] + getLastSlashCommands: [Function getLastSlashCommands] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap index d3f091a95e8..31fd0b94e96 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap @@ -15,12 +15,7 @@ id: "agent", metadata: { description: "" }, provideSlashCommands: [Function provideSlashCommands], - lastSlashCommands: [ - { - name: "subCommand", - description: "" - } - ] + getLastSlashCommands: [Function getLastSlashCommands] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap index c4b86b46fff..85bc82a3136 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap @@ -15,12 +15,7 @@ id: "agent", metadata: { description: "" }, provideSlashCommands: [Function provideSlashCommands], - lastSlashCommands: [ - { - name: "subCommand", - description: "" - } - ] + getLastSlashCommands: [Function getLastSlashCommands] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index 5585c30afa8..ba397535bff 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -11,8 +11,10 @@ import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ChatAgentService, IChatAgent, IChatAgentCommand, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; +import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { MockChatService } from 'vs/workbench/contrib/chat/test/common/mockChatService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -28,6 +30,7 @@ suite('ChatRequestParser', () => { instantiationService.stub(IStorageService, testDisposables.add(new TestStorageService())); instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(IExtensionService, new TestExtensionService()); + instantiationService.stub(IChatService, new MockChatService()); instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService))); varService = mockObject()({}); @@ -108,13 +111,13 @@ suite('ChatRequestParser', () => { await assertSnapshot(result); }); - const getAgentWithSlashcommands = (slashCommands: IChatAgentCommand[]) => { - return >{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => [], lastSlashCommands: slashCommands }; + const getAgentWithSlashCommands = (slashCommands: IChatAgentCommand[]) => { + return >{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => [], getLastSlashCommands: () => slashCommands }; }; test('agent with subcommand after text', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -124,7 +127,7 @@ suite('ChatRequestParser', () => { test('agents, subCommand', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -134,7 +137,7 @@ suite('ChatRequestParser', () => { test('agent with question mark', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -144,7 +147,7 @@ suite('ChatRequestParser', () => { test('agent and subcommand with leading whitespace', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -154,7 +157,7 @@ suite('ChatRequestParser', () => { test('agent and subcommand after newline', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -164,7 +167,7 @@ suite('ChatRequestParser', () => { test('agent not first', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -174,7 +177,7 @@ suite('ChatRequestParser', () => { test('agents and variables and multiline', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); varService.hasVariable.returns(true); @@ -186,7 +189,7 @@ suite('ChatRequestParser', () => { test('agents and variables and multiline, part2', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); varService.hasVariable.returns(true); 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 9f7e33e850a..3a2b6abc998 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -24,13 +24,14 @@ import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ChatAgentService, IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChat, IChatFollowup, IChatProgress, IChatProvider, IChatRequest } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatFollowup, IChatProgress, IChatProvider, IChatRequest, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { MockChatVariablesService } from 'vs/workbench/contrib/chat/test/common/mockChatVariables'; import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { TestContextService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; +import { MockChatService } from 'vs/workbench/contrib/chat/test/common/mockChatService'; class SimpleTestProvider extends Disposable implements IChatProvider { private static sessionId = 0; @@ -59,7 +60,10 @@ const chatAgentWithUsedContext: IChatAgent = { id: chatAgentWithUsedContextId, extensionId: nullExtensionDescription.identifier, metadata: {}, - async provideSlashCommands(token) { + getLastSlashCommands() { + return undefined; + }, + async provideSlashCommands() { return []; }, async invoke(request, progress, history, token) { @@ -104,6 +108,7 @@ suite('Chat', () => { instantiationService.stub(IChatContributionService, new TestExtensionService()); instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(IChatSlashCommandService, testDisposables.add(instantiationService.createInstance(ChatSlashCommandService))); + instantiationService.stub(IChatService, new MockChatService()); chatAgentService = testDisposables.add(instantiationService.createInstance(ChatAgentService)); instantiationService.stub(IChatAgentService, chatAgentService); @@ -115,6 +120,9 @@ suite('Chat', () => { async invoke(request, progress, history, token) { return {}; }, + getLastSlashCommands() { + return undefined; + }, async provideSlashCommands(token) { return []; }, diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts new file mode 100644 index 00000000000..35c17753392 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts @@ -0,0 +1,85 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { ChatModel, IChatModel, IChatRequestVariableData, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; + +export class MockChatService implements IChatService { + _serviceBrand: undefined; + transferredSessionData: IChatTransferredSessionData | undefined; + + hasSessions(providerId: string): boolean { + throw new Error('Method not implemented.'); + } + getProviderInfos(): IChatProviderInfo[] { + throw new Error('Method not implemented.'); + } + startSession(providerId: string, token: CancellationToken): ChatModel | undefined { + throw new Error('Method not implemented.'); + } + getSession(sessionId: string): IChatModel | undefined { + return {} as IChatModel; + } + getSessionId(sessionProviderId: number): string | undefined { + throw new Error('Method not implemented.'); + } + getOrRestoreSession(sessionId: string): IChatModel | undefined { + throw new Error('Method not implemented.'); + } + loadSessionFromContent(data: ISerializableChatData): IChatModel | undefined { + throw new Error('Method not implemented.'); + } + onDidRegisterProvider: Event<{ providerId: string }> = undefined!; + onDidUnregisterProvider: Event<{ providerId: string }> = undefined!; + registerProvider(provider: IChatProvider): IDisposable { + throw new Error('Method not implemented.'); + } + + /** + * Returns whether the request was accepted. + */ + sendRequest(sessionId: string, message: string): Promise { + throw new Error('Method not implemented.'); + } + removeRequest(sessionid: string, requestId: string): Promise { + throw new Error('Method not implemented.'); + } + cancelCurrentRequestForSession(sessionId: string): void { + throw new Error('Method not implemented.'); + } + clearSession(sessionId: string): void { + throw new Error('Method not implemented.'); + } + addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, response: IChatCompleteResponse): void { + throw new Error('Method not implemented.'); + } + sendRequestToProvider(sessionId: string, message: IChatDynamicRequest): void { + throw new Error('Method not implemented.'); + } + getHistory(): IChatDetail[] { + throw new Error('Method not implemented.'); + } + clearAllHistoryEntries(): void { + throw new Error('Method not implemented.'); + } + removeHistoryEntry(sessionId: string): void { + throw new Error('Method not implemented.'); + } + + onDidPerformUserAction: Event = undefined!; + notifyUserAction(event: IChatUserActionEvent): void { + throw new Error('Method not implemented.'); + } + onDidDisposeSession: Event<{ sessionId: string; providerId: string; reason: 'initializationFailed' | 'cleared' }> = undefined!; + + transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void { + throw new Error('Method not implemented.'); + } +} diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index eaaea9096dd..c85e0056d12 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -12,6 +12,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti import { ProviderResult } from 'vs/editor/common/languages'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IChatAgent, IChatAgentCommand, IChatAgentHistoryEntry, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatProgress, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IVoiceChatSessionOptions, IVoiceChatTextEvent, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; import { ISpeechProvider, ISpeechService, ISpeechToTextEvent, ISpeechToTextSession, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; @@ -27,9 +28,10 @@ suite('VoiceChat', () => { extensionId: ExtensionIdentifier = nullExtensionDescription.identifier; - constructor(readonly id: string, readonly lastSlashCommands: IChatAgentCommand[]) { } + constructor(readonly id: string, private readonly lastSlashCommands: IChatAgentCommand[]) { } + getLastSlashCommands(model: IChatModel): IChatAgentCommand[] | undefined { return this.lastSlashCommands; } invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error('Method not implemented.'); } - provideSlashCommands(token: CancellationToken): Promise { throw new Error('Method not implemented.'); } + provideSlashCommands(model: IChatModel | undefined, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error('Method not implemented.'); } provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined> { throw new Error('Method not implemented.'); } metadata = {}; } @@ -108,11 +110,11 @@ suite('VoiceChat', () => { }); test('Agent and slash command detection (useAgents: false)', async () => { - testAgentsAndSlashCommandsDetection({ usesAgents: false }); + testAgentsAndSlashCommandsDetection({ usesAgents: false, model: {} as IChatModel }); }); test('Agent and slash command detection (useAgents: true)', async () => { - testAgentsAndSlashCommandsDetection({ usesAgents: true }); + testAgentsAndSlashCommandsDetection({ usesAgents: true, model: {} as IChatModel }); }); function testAgentsAndSlashCommandsDetection(options: IVoiceChatSessionOptions) { @@ -295,7 +297,7 @@ suite('VoiceChat', () => { test('waiting for input', async () => { // Agent - createSession({ usesAgents: true }); + createSession({ usesAgents: true, model: {} as IChatModel }); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); @@ -308,7 +310,7 @@ suite('VoiceChat', () => { assert.strictEqual(event.waitingForInput, true); // Slash Command - createSession({ usesAgents: true }); + createSession({ usesAgents: true, model: {} as IChatModel }); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace slash explain' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 33094375625..38ea05687a1 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -524,7 +524,7 @@ export class InlineChatController implements IEditorContribution { const cts = new CancellationTokenSource(); this._sessionStore.add(cts); for (const agent of this._chatAgentService.getAgents()) { - const commands = await agent.provideSlashCommands(cts.token); + const commands = await agent.provideSlashCommands(undefined, [], cts.token); if (commands.find((command) => withoutSubCommandLeader.startsWith(command.name))) { massagedInput = `${chatAgentLeader}${agent.id} ${input}`; break; diff --git a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts index 43bacd595dd..ed62261761d 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -183,8 +183,7 @@ declare module 'vscode' { * @returns A list of commands. The lack of a result can be signaled by returning `undefined`, `null`, or * an empty array. */ - // TODO@API Q: should we provide the current history or last results for extra context? - provideCommands(token: CancellationToken): ProviderResult; + provideCommands(context: ChatContext, token: CancellationToken): ProviderResult; } /** From 52cb4e45c4c509e63441d81d096322dbe5e1cdf7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 21 Feb 2024 21:08:06 +0100 Subject: [PATCH 1502/1897] voice - add config for language (#205908) * voice - add config for language * . * . * . --- .../workbench/api/browser/mainThreadSpeech.ts | 4 +- .../workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostSpeech.ts | 4 +- .../browser/accessibilityConfiguration.ts | 96 ++++++++++++++++++- .../contrib/speech/browser/speechService.ts | 7 +- .../contrib/speech/common/speechService.ts | 6 +- src/vscode-dts/vscode.proposed.speech.d.ts | 6 +- 7 files changed, 115 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadSpeech.ts b/src/vs/workbench/api/browser/mainThreadSpeech.ts index 389fa1b8709..56ce1bca623 100644 --- a/src/vs/workbench/api/browser/mainThreadSpeech.ts +++ b/src/vs/workbench/api/browser/mainThreadSpeech.ts @@ -41,7 +41,7 @@ export class MainThreadSpeech implements MainThreadSpeechShape { const registration = this.speechService.registerSpeechProvider(identifier, { metadata, - createSpeechToTextSession: token => { + createSpeechToTextSession: (token, options) => { if (token.isCancellationRequested) { return { onDidChange: Event.None @@ -51,7 +51,7 @@ export class MainThreadSpeech implements MainThreadSpeechShape { const disposables = new DisposableStore(); const session = Math.random(); - this.proxy.$createSpeechToTextSession(handle, session); + this.proxy.$createSpeechToTextSession(handle, session, options?.language); const onDidChange = disposables.add(new Emitter()); this.speechToTextSessions.set(session, { onDidChange }); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 8dbc24affe7..5c18ab8657a 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1171,7 +1171,7 @@ export interface MainThreadSpeechShape extends IDisposable { } export interface ExtHostSpeechShape { - $createSpeechToTextSession(handle: number, session: number): Promise; + $createSpeechToTextSession(handle: number, session: number, language?: string): Promise; $cancelSpeechToTextSession(session: number): Promise; $createKeywordRecognitionSession(handle: number, session: number): Promise; diff --git a/src/vs/workbench/api/common/extHostSpeech.ts b/src/vs/workbench/api/common/extHostSpeech.ts index 8d230fd19f2..9093f63e3ab 100644 --- a/src/vs/workbench/api/common/extHostSpeech.ts +++ b/src/vs/workbench/api/common/extHostSpeech.ts @@ -24,7 +24,7 @@ export class ExtHostSpeech implements ExtHostSpeechShape { this.proxy = mainContext.getProxy(MainContext.MainThreadSpeech); } - async $createSpeechToTextSession(handle: number, session: number): Promise { + async $createSpeechToTextSession(handle: number, session: number, language?: string): Promise { const provider = this.providers.get(handle); if (!provider) { return; @@ -35,7 +35,7 @@ export class ExtHostSpeech implements ExtHostSpeechShape { const cts = new CancellationTokenSource(); this.sessions.set(session, cts); - const speechToTextSession = disposables.add(provider.provideSpeechToTextSession(cts.token)); + const speechToTextSession = disposables.add(provider.provideSpeechToTextSession(cts.token, language ? { language } : undefined)); disposables.add(speechToTextSession.onDidChange(e => { if (cts.token.isCancellationRequested) { return; diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index afd920ac1f9..43507114aa0 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -660,9 +660,11 @@ export function registerAccessibilityConfiguration() { } export const enum AccessibilityVoiceSettingId { - SpeechTimeout = 'accessibility.voice.speechTimeout' + SpeechTimeout = 'accessibility.voice.speechTimeout', + SpeechLanguage = 'accessibility.voice.speechLanguage' } export const SpeechTimeoutDefault = 1200; +const SpeechLanguageDefault = 'en-US'; export class DynamicSpeechAccessibilityConfiguration extends Disposable implements IWorkbenchContribution { @@ -681,6 +683,11 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen return; // these settings require a speech provider } + const languages = this.getLanguages(); + const languagesSorted = Object.keys(languages).sort((langA, langB) => { + return languages[langA].name.localeCompare(languages[langB].name); + }); + const registry = Registry.as(Extensions.Configuration); registry.registerConfiguration({ ...accessibilityConfigurationNodeBase, @@ -691,11 +698,98 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen 'default': SpeechTimeoutDefault, 'minimum': 0, 'tags': ['accessibility'] + }, + [AccessibilityVoiceSettingId.SpeechLanguage]: { + 'markdownDescription': localize('voice.speechLanguage', "The language that voice speech recognition should recognize."), + 'type': 'string', + 'enum': languagesSorted, + 'default': SpeechLanguageDefault, + 'tags': ['accessibility'], + 'enumDescriptions': languagesSorted.map(key => languages[key].name), + 'enumItemLabels': languagesSorted.map(key => languages[key].name) } } }); } + + private getLanguages(): { [locale: string]: { name: string } } { + return { + ['de-DE']: { + name: localize('speechLanguage.de-DE', "German (Germany)") + }, + ['en-AU']: { + name: localize('speechLanguage.en-AU', "English (Australia)") + }, + ['en-CA']: { + name: localize('speechLanguage.en-CA', "English (Canada)") + }, + ['en-GB']: { + name: localize('speechLanguage.en-GB', "English (United Kingdom)") + }, + ['en-IE']: { + name: localize('speechLanguage.en-IE', "English (Ireland)") + }, + ['en-IN']: { + name: localize('speechLanguage.en-IN', "English (India)") + }, + ['en-NZ']: { + name: localize('speechLanguage.en-NZ', "English (New Zealand)") + }, + [SpeechLanguageDefault]: { + name: localize('speechLanguage.en-US', "English (United States)") + }, + ['es-ES']: { + name: localize('speechLanguage.es-ES', "Spanish (Spain)") + }, + ['es-MX']: { + name: localize('speechLanguage.es-MX', "Spanish (Mexico)") + }, + ['fr-CA']: { + name: localize('speechLanguage.fr-CA', "French (Canada)") + }, + ['fr-FR']: { + name: localize('speechLanguage.fr-FR', "French (France)") + }, + ['hi-IN']: { + name: localize('speechLanguage.hi-IN', "Hindi (India)") + }, + ['it-IT']: { + name: localize('speechLanguage.it-IT', "Italian (Italy)") + }, + ['ja-JP']: { + name: localize('speechLanguage.ja-JP', "Japanese (Japan)") + }, + ['ko-KR']: { + name: localize('speechLanguage.ko-KR', "Korean (South Korea)") + }, + ['nl-NL']: { + name: localize('speechLanguage.nl-NL', "Dutch (Netherlands)") + }, + ['pt-BR']: { + name: localize('speechLanguage.pt-BR', "Portuguese (Brazil)") + }, + ['ru-RU']: { + name: localize('speechLanguage.ru-RU', "Russian (Russia)") + }, + ['sv-SE']: { + name: localize('speechLanguage.sv-SE', "Swedish (Sweden)") + }, + ['tr-TR']: { + name: localize('speechLanguage.tr-TR', "Turkish (Turkey)") + }, + ['zh-CN']: { + name: localize('speechLanguage.zh-CN', "Chinese (Simplified, China)") + }, + ['zh-HK']: { + name: localize('speechLanguage.zh-HK', "Chinese (Traditional, Hong Kong)") + }, + ['zh-TW']: { + name: localize('speechLanguage.zh-TW', "Chinese (Traditional, Taiwan)") + } + }; + } } + Registry.as(WorkbenchExtensions.ConfigurationMigration) .registerConfigurationMigrations([{ key: 'audioCues.volume', diff --git a/src/vs/workbench/contrib/speech/browser/speechService.ts b/src/vs/workbench/contrib/speech/browser/speechService.ts index e190ac66d36..00943f3262b 100644 --- a/src/vs/workbench/contrib/speech/browser/speechService.ts +++ b/src/vs/workbench/contrib/speech/browser/speechService.ts @@ -13,6 +13,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { DeferredPromise } from 'vs/base/common/async'; import { ISpeechService, ISpeechProvider, HasSpeechProvider, ISpeechToTextSession, SpeechToTextInProgress, IKeywordRecognitionSession, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class SpeechService extends Disposable implements ISpeechService { @@ -34,7 +35,8 @@ export class SpeechService extends Disposable implements ISpeechService { @ILogService private readonly logService: ILogService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IHostService private readonly hostService: IHostService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); } @@ -78,7 +80,8 @@ export class SpeechService extends Disposable implements ISpeechService { this.logService.warn(`Multiple speech providers registered. Picking first one: ${provider.metadata.displayName}`); } - const session = this._activeSpeechToTextSession = provider.createSpeechToTextSession(token); + const language = this.configurationService.getValue('accessibility.voice.speechLanguage'); + const session = this._activeSpeechToTextSession = provider.createSpeechToTextSession(token, typeof language === 'string' ? { language } : undefined); const sessionStart = Date.now(); let sessionRecognized = false; diff --git a/src/vs/workbench/contrib/speech/common/speechService.ts b/src/vs/workbench/contrib/speech/common/speechService.ts index 4e4ff9345e6..a594b4f3acf 100644 --- a/src/vs/workbench/contrib/speech/common/speechService.ts +++ b/src/vs/workbench/contrib/speech/common/speechService.ts @@ -52,10 +52,14 @@ export interface IKeywordRecognitionSession { readonly onDidChange: Event; } +export interface ISpeechToTextSessionOptions { + readonly language?: string; +} + export interface ISpeechProvider { readonly metadata: ISpeechProviderMetadata; - createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession; + createSpeechToTextSession(token: CancellationToken, options?: ISpeechToTextSessionOptions): ISpeechToTextSession; createKeywordRecognitionSession(token: CancellationToken): IKeywordRecognitionSession; } diff --git a/src/vscode-dts/vscode.proposed.speech.d.ts b/src/vscode-dts/vscode.proposed.speech.d.ts index 42fbbb03320..091c3995af3 100644 --- a/src/vscode-dts/vscode.proposed.speech.d.ts +++ b/src/vscode-dts/vscode.proposed.speech.d.ts @@ -7,6 +7,10 @@ declare module 'vscode' { // todo@bpasero work in progress speech API + export interface SpeechToTextOptions { + readonly language?: string; + } + export enum SpeechToTextStatus { Started = 1, Recognizing = 2, @@ -38,7 +42,7 @@ declare module 'vscode' { } export interface SpeechProvider { - provideSpeechToTextSession(token: CancellationToken): SpeechToTextSession; + provideSpeechToTextSession(token: CancellationToken, options?: SpeechToTextOptions): SpeechToTextSession; provideKeywordRecognitionSession(token: CancellationToken): KeywordRecognitionSession; } From 4e2aa982224429f161423cbdded9587e39766782 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Wed, 21 Feb 2024 14:08:34 -0600 Subject: [PATCH 1503/1897] findfiles2 doc clarity improvements (#205902) --- src/vscode-dts/vscode.proposed.findFiles2.d.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.findFiles2.d.ts b/src/vscode-dts/vscode.proposed.findFiles2.d.ts index f861c7930ce..145776110d4 100644 --- a/src/vscode-dts/vscode.proposed.findFiles2.d.ts +++ b/src/vscode-dts/vscode.proposed.findFiles2.d.ts @@ -54,13 +54,14 @@ declare module 'vscode' { /** * Whether symlinks should be followed while searching. - * See the vscode setting `"search.followSymlinks"`. + * Defaults to the value for `search.followSymlinks` in settings. + * For more info, see the setting listed above. */ followSymlinks?: boolean; /** * If set to true, the `filePattern` arg will be fuzzy-searched instead of glob-searched. - * If `filePattern` is a `GlobPattern`, then the fuzzy search will act on the `pattern` of the `RelativePattern` + * If `filePattern` is a {@link RelativePattern relative pattern}, then the fuzzy search will act on the `pattern` of the {@link RelativePattern RelativePattern} */ fuzzy?: boolean; } @@ -73,7 +74,7 @@ declare module 'vscode' { * Find files across all {@link workspace.workspaceFolders workspace folders} in the workspace. * * @example - * findFiles('**​/*.js', {exclude: '**​/out/**', useIgnoreFiles: true}, 10) + * findFiles('**​/*.js', {exclude: '**​/out/**', useIgnoreFiles: true, maxResults: 10}) * * @param filePattern A {@link GlobPattern glob pattern} that defines the files to search for. The glob pattern * will be matched against the file paths of resulting matches relative to their workspace. Use a {@link RelativePattern relative pattern} From ccb571b6eff269584a68b0d7d43e9a0550c9f7bc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 21 Feb 2024 12:18:50 -0800 Subject: [PATCH 1504/1897] fix bugs --- .../browser/accessibilityConfiguration.ts | 13 ++++++++----- .../accessibilitySignals/browser/commands.ts | 15 ++++++++++++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index afd920ac1f9..7710bbe3b73 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -115,7 +115,7 @@ export const announcementFeatureBase: IConfigurationPropertySchema = { const defaultNoAnnouncement: IConfigurationPropertySchema = { 'type': 'object', 'tags': ['accessibility'], - additionalProperties: true, + additionalProperties: false, 'default': { 'sound': 'auto', } @@ -536,7 +536,6 @@ const configuration: IConfigurationNode = { }, 'accessibility.signals.chatResponseReceived': { ...defaultNoAnnouncement, - additionalProperties: false, 'description': localize('accessibility.signals.chatResponseReceived', "Indicates when the response has been received."), 'properties': { 'sound': { @@ -562,7 +561,7 @@ const configuration: IConfigurationNode = { 'accessibility.signals.save': { 'type': 'object', 'tags': ['accessibility'], - additionalProperties: true, + additionalProperties: false, 'markdownDescription': localize('accessibility.signals.save', "Plays a signal when a file is saved."), 'properties': { 'sound': { @@ -596,7 +595,7 @@ const configuration: IConfigurationNode = { 'accessibility.signals.format': { 'type': 'object', 'tags': ['accessibility'], - additionalProperties: true, + additionalProperties: false, 'markdownDescription': localize('accessibility.signals.format', "Plays a signal when a file or notebook is formatted."), 'properties': { 'sound': { @@ -621,6 +620,10 @@ const configuration: IConfigurationNode = { localize('accessibility.signals.format.announcement.never', "Never announces.") ], }, + }, + default: { + 'sound': 'never', + 'announcement': 'never' } }, } @@ -737,7 +740,7 @@ Registry.as(WorkbenchExtensions.ConfigurationMi }))); Registry.as(WorkbenchExtensions.ConfigurationMigration) - .registerConfigurationMigrations(AccessibilitySignal.allAccessibilitySignals.filter(i => !!i.announcementMessage).map(item => ({ + .registerConfigurationMigrations(AccessibilitySignal.allAccessibilitySignals.filter(i => !!i.legacyAnnouncementSettingsKey).map(item => ({ key: item.legacyAnnouncementSettingsKey!, migrateFn: (announcement, accessor) => { const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 666516df128..8561b125cd0 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -12,6 +12,7 @@ import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/ac import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; export class ShowSignalSoundHelp extends Action2 { static readonly ID = 'signals.sounds.help'; @@ -32,6 +33,7 @@ export class ShowSignalSoundHelp extends Action2 { const quickInputService = accessor.get(IQuickInputService); const configurationService = accessor.get(IConfigurationService); const accessibilityService = accessor.get(IAccessibilityService); + const preferencesService = accessor.get(IPreferencesService); const userGestureSignals = [AccessibilitySignal.save, AccessibilitySignal.format]; const items: (IQuickPickItem & { signal: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.map((signal, idx) => ({ label: userGestureSignals.includes(signal) ? `${signal.name} (${configurationService.getValue(signal.settingsKey + '.sound')})` : signal.name, @@ -68,6 +70,9 @@ export class ShowSignalSoundHelp extends Action2 { } qp.hide(); }); + qp.onDidTriggerItemButton(e => { + preferencesService.openUserSettings({ jsonEditor: true, revealSetting: { key: e.item.signal.settingsKey, edit: true } }); + }); qp.onDidChangeActive(() => { accessibilitySignalService.playSound(qp.activeItems[0].signal.sound.getSound(true), true); }); @@ -96,14 +101,15 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { const quickInputService = accessor.get(IQuickInputService); const configurationService = accessor.get(IConfigurationService); const accessibilityService = accessor.get(IAccessibilityService); + const preferencesService = accessor.get(IPreferencesService); const userGestureSignals = [AccessibilitySignal.save, AccessibilitySignal.format]; - const items: (IQuickPickItem & { signal: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.filter(c => !!c.announcementMessage).map((signal, idx) => ({ + const items: (IQuickPickItem & { signal: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.filter(c => !!c.legacyAnnouncementSettingsKey).map((signal, idx) => ({ label: userGestureSignals.includes(signal) ? `${signal.name} (${configurationService.getValue(signal.settingsKey + '.announcement')})` : signal.name, signal, buttons: userGestureSignals.includes(signal) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), tooltip: localize('announcement.help.settings', 'Enable/Disable Announcement'), - alwaysVisible: true + alwaysVisible: true, }] : [] })); const qp = quickInputService.createQuickPick(); @@ -111,7 +117,7 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { qp.selectedItems = items.filter(i => accessibilitySignalService.isAnnouncementEnabled(i.signal) || userGestureSignals.includes(i.signal) && configurationService.getValue(i.signal.settingsKey + '.announcement') !== 'never'); qp.onDidAccept(() => { const enabledAnnouncements = qp.selectedItems.map(i => i.signal); - const disabledAnnouncements = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledAnnouncements.includes(cue)); + const disabledAnnouncements = AccessibilitySignal.allAccessibilitySignals.filter(cue => !!cue.legacyAnnouncementSettingsKey && !enabledAnnouncements.includes(cue)); for (const signal of enabledAnnouncements) { let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(signal.settingsKey); announcement = userGestureSignals.includes(signal) ? 'userGesture' : signal.announcementMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; @@ -124,6 +130,9 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { } qp.hide(); }); + qp.onDidTriggerItemButton(e => { + preferencesService.openUserSettings({ jsonEditor: true, revealSetting: { key: e.item.signal.settingsKey, edit: true } }); + }); qp.placeholder = localize('announcement.help.placeholder', 'Select an announcement to configure'); qp.canSelectMany = true; await qp.show(); From 92db21f4e0bf366ecfc24ae0144b426f009214a5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 21 Feb 2024 12:25:26 -0800 Subject: [PATCH 1505/1897] fix #204994 (#205909) --- src/vs/workbench/browser/parts/paneCompositeBar.ts | 6 ------ src/vs/workbench/browser/parts/paneCompositePart.ts | 4 ++-- src/vs/workbench/services/views/browser/viewsService.ts | 5 +++++ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/browser/parts/paneCompositeBar.ts b/src/vs/workbench/browser/parts/paneCompositeBar.ts index 4387df1a138..8fe901376cc 100644 --- a/src/vs/workbench/browser/parts/paneCompositeBar.ts +++ b/src/vs/workbench/browser/parts/paneCompositeBar.ts @@ -207,12 +207,6 @@ export class PaneCompositeBar extends Disposable { if (to === this.location) { this.onDidRegisterViewContainers([container]); - - // Open view container if part is visible and there is no other view container opened - const visibleComposites = this.compositeBar.getVisibleComposites(); - if (!this.paneCompositePart.getActivePaneComposite() && this.layoutService.isVisible(this.paneCompositePart.partId) && visibleComposites.length) { - this.paneCompositePart.openPaneComposite(visibleComposites[0].id); - } } } diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index 82d20fd9666..6ef4dfe09a3 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -180,7 +180,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart this.updateGlobalToolbarActions())); - this._register(this.registry.onDidDeregister(async (viewletDescriptor: PaneCompositeDescriptor) => { + this._register(this.registry.onDidDeregister((viewletDescriptor: PaneCompositeDescriptor) => { const activeContainers = this.viewDescriptorService.getViewContainersByLocation(this.location) .filter(container => this.viewDescriptorService.getViewContainerModel(container).activeViewDescriptors.length > 0); @@ -189,7 +189,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart c.id === defaultViewletId)[0] || activeContainers[0]; - await this.openPaneComposite(containerToOpen.id); + this.doOpenPaneComposite(containerToOpen.id); } } else { this.layoutService.setPartHidden(true, this.partId); diff --git a/src/vs/workbench/services/views/browser/viewsService.ts b/src/vs/workbench/services/views/browser/viewsService.ts index 49643efd588..c342e91da15 100644 --- a/src/vs/workbench/services/views/browser/viewsService.ts +++ b/src/vs/workbench/services/views/browser/viewsService.ts @@ -137,6 +137,11 @@ export class ViewsService extends Disposable implements IViewsService { private onDidChangeContainerLocation(viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation): void { this.deregisterPaneComposite(viewContainer, from); this.registerPaneComposite(viewContainer, to); + + // Open view container if part is visible and there is only one view container in location + if (this.layoutService.isVisible(getPartByLocation(to)) && this.viewDescriptorService.getViewContainersByLocation(to).length === 1) { + this.openViewContainer(viewContainer.id); + } } private onViewDescriptorsAdded(views: ReadonlyArray, container: ViewContainer): void { From 61f447b79af940ff0675b0a76bc5bd8b9c659c3a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 21 Feb 2024 20:57:18 +0000 Subject: [PATCH 1506/1897] More small chat API fixes (#205901) * Fix #205811 * Fix #205807 * Make description optional * Fix * fix test --- .../src/singlefolder-tests/chat.test.ts | 2 +- .../workbench/api/common/extHostChatAgents2.ts | 6 ++++-- src/vs/workbench/api/common/extHostTypes.ts | 4 ++-- .../vscode.proposed.chatParticipant.d.ts | 16 +++++----------- .../vscode.proposed.defaultChatParticipant.d.ts | 5 +++++ 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index 44fa4396796..6fb0262e132 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -67,7 +67,7 @@ suite('chat', () => { interactive.sendInteractiveRequestToProvider('provider', { message: '@participant /hello friend' }); } else { assert.strictEqual(request.context.history.length, 1); - assert.strictEqual(request.context.history[0].participant.participant, 'participant'); + assert.strictEqual(request.context.history[0].participant.name, 'participant'); assert.strictEqual(request.context.history[0].command, 'hello'); } }); diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index ad00314cac6..9f9266babba 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -231,11 +231,11 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { { ...ehResult, metadata: undefined }; // REQUEST turn - res.push(new extHostTypes.ChatRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', participant: h.request.agentId })); + res.push(new extHostTypes.ChatRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', name: h.request.agentId })); // RESPONSE turn const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.fromContent(r, this.commands.converter))); - res.push(new extHostTypes.ChatResponseTurn(parts, result, { extensionId: '', participant: h.request.agentId }, h.request.command)); + res.push(new extHostTypes.ChatResponseTurn(parts, result, { extensionId: '', name: h.request.agentId }, h.request.command)); } return res; @@ -508,9 +508,11 @@ class ExtHostChatAgent { updateMetadataSoon(); }, get fullName() { + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); return that._fullName ?? that.extension.displayName ?? that.extension.name; }, set fullName(v) { + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); that._fullName = v; updateMetadataSoon(); }, diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 4827cdfbfeb..4fb94e3f1c8 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4257,7 +4257,7 @@ export class ChatRequestTurn implements vscode.ChatRequestTurn { readonly prompt: string, readonly command: string | undefined, readonly variables: vscode.ChatResolvedVariable[], - readonly participant: { extensionId: string; participant: string }, + readonly participant: { extensionId: string; name: string }, ) { } } @@ -4266,7 +4266,7 @@ export class ChatResponseTurn implements vscode.ChatResponseTurn { constructor( readonly response: ReadonlyArray, readonly result: vscode.ChatResult, - readonly participant: { extensionId: string; participant: string }, + readonly participant: { extensionId: string; name: string }, readonly command?: string ) { } } diff --git a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts index ed62261761d..5fcc77cb7b5 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -23,7 +23,7 @@ declare module 'vscode' { /** * The name of the chat participant and contributing extension to which this request was directed. */ - readonly participant: { readonly extensionId: string; readonly participant: string }; + readonly participant: { readonly extensionId: string; readonly name: string }; /** * The name of the {@link ChatCommand command} that was selected for this request. @@ -35,7 +35,7 @@ declare module 'vscode' { */ readonly variables: ChatResolvedVariable[]; - private constructor(prompt: string, command: string | undefined, variables: ChatResolvedVariable[], participant: { extensionId: string; participant: string }); + private constructor(prompt: string, command: string | undefined, variables: ChatResolvedVariable[], participant: { extensionId: string; name: string }); } /** @@ -56,14 +56,14 @@ declare module 'vscode' { /** * The name of the chat participant and contributing extension that this response came from. */ - readonly participant: { readonly extensionId: string; readonly participant: string }; + readonly participant: { readonly extensionId: string; readonly name: string }; /** * The name of the command that this response came from. */ readonly command?: string; - private constructor(response: ReadonlyArray, result: ChatResult, participant: { extensionId: string; participant: string }); + private constructor(response: ReadonlyArray, result: ChatResult, participant: { extensionId: string; name: string }); } export interface ChatContext { @@ -239,16 +239,10 @@ declare module 'vscode' { */ readonly name: string; - /** - * The full name of this participant. - * TODO@API This is only used for the default participant, but it seems useful, so should we keep it so we can use it in the future? - */ - fullName: string; - /** * A human-readable description explaining what this participant does. */ - description: string; + description?: string; /** * Icon for the participant shown in UI. diff --git a/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts b/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts index e1c026a6557..07a2b6f5c42 100644 --- a/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts @@ -18,6 +18,11 @@ declare module 'vscode' { */ isDefault?: boolean; + /** + * The full name of this participant. + */ + fullName?: string; + /** * When true, this participant is invoked when the user submits their query using ctrl/cmd+enter * TODO@API name From 006e4f9dd3f549590f71ecd1fd53b5369f927442 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 21 Feb 2024 21:14:10 +0000 Subject: [PATCH 1507/1897] Catch an error from agent (#205913) --- src/vs/workbench/api/common/extHostChatAgents2.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 9f9266babba..8166c1e7d33 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -253,8 +253,13 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } const convertedHistory = await this.prepareHistoryTurns(agent.id, context); - - return agent.provideSlashCommands({ history: convertedHistory }, token); + try { + return await agent.provideSlashCommands({ history: convertedHistory }, token); + } catch (err) { + const msg = toErrorMessage(err); + this._logService.error(`[${agent.extension.identifier.value}] [@${agent.id}] Error while providing slash commands: ${msg}`); + return []; + } } async $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, token: CancellationToken): Promise { From 4665ce61312f31f22607b5c72a4492b5ecaf7a98 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Wed, 21 Feb 2024 17:04:09 -0600 Subject: [PATCH 1508/1897] quick search esc leads to closing edited files (#205917) Fixes #205907 --- src/vs/workbench/browser/quickaccess.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/quickaccess.ts b/src/vs/workbench/browser/quickaccess.ts index b0a88a28142..6b9e07abe7a 100644 --- a/src/vs/workbench/browser/quickaccess.ts +++ b/src/vs/workbench/browser/quickaccess.ts @@ -84,7 +84,7 @@ export class EditorViewState { if (shouldCloseCurrEditor) { const activeEditorPane = this.editorService.activeEditorPane; const currEditor = activeEditorPane?.input; - if (currEditor && currEditor !== this._editorViewState.editor) { + if (currEditor && currEditor !== this._editorViewState.editor && activeEditorPane?.group.isPinned(currEditor) !== true) { await activeEditorPane.group.closeEditor(currEditor); } } From 91f71cdc378f4f41f29ef1b533d9d93529ac3203 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 21 Feb 2024 15:47:38 -0800 Subject: [PATCH 1509/1897] debug: bump js-debug 1.87 (#205919) --- product.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/product.json b/product.json index b17eeda9610..60765ed5470 100644 --- a/product.json +++ b/product.json @@ -50,8 +50,8 @@ }, { "name": "ms-vscode.js-debug", - "version": "1.86.1", - "sha256": "e382de75b63a57d3419bbb110e17551d40d88b4bb0a49452dab2c7278b815e72", + "version": "1.87.0", + "sha256": "155ba715cc1045835951e70a663010c8f91d773d90a3ec504a041fd53b5658b0", "repo": "https://github.com/microsoft/vscode-js-debug", "metadata": { "id": "25629058-ddac-4e17-abba-74678e126c5d", From 5ca03d67eea4b9067d8ef4f97565b6270571e86a Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:18:30 -0800 Subject: [PATCH 1510/1897] console error when issue reporter fails (#205920) * throw some console errors here and there * error checking --- src/vs/code/electron-sandbox/issue/issueReporterService.ts | 5 +++++ .../services/issue/electron-sandbox/issueService.ts | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index 6bfb60db1f7..067f09fd0bd 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -926,6 +926,7 @@ export class IssueReporter extends Disposable { const response = await fetch(url, init); if (!response.ok) { + console.error('Invalid GitHub URL provided.'); return false; } const result = await response.json(); @@ -984,6 +985,7 @@ export class IssueReporter extends Disposable { let issueUrl = hasUri ? this.getExtensionBugsUrl() : this.getIssueUrl(); if (!issueUrl) { + console.error('No issue url found'); return false; } @@ -1004,6 +1006,7 @@ export class IssueReporter extends Disposable { try { url = await this.writeToClipboard(baseUrl, issueBody); } catch (_) { + console.error('Writing to clipboard failed'); return false; } } @@ -1040,6 +1043,8 @@ export class IssueReporter extends Disposable { owner: match[1], repositoryName: match[2] }; + } else { + console.error('No GitHub match'); } return undefined; diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts index add5d98fc2e..b1d728b506a 100644 --- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts @@ -185,6 +185,13 @@ export class NativeIssueService implements IWorkbenchIssueService { githubAccessToken }, dataOverrides); + if (issueReporterData.extensionId) { + const extensionExists = extensionData.some(extension => extension.id === issueReporterData.extensionId); + if (!extensionExists) { + console.error(`Extension with ID ${issueReporterData.extensionId} does not exist.`); + } + } + if (issueReporterData.extensionId && this.extensionIdentifierSet.has(issueReporterData.extensionId)) { ipcRenderer.send(`vscode:triggerReporterMenuResponse:${issueReporterData.extensionId}`, issueReporterData); this.extensionIdentifierSet.delete(new ExtensionIdentifier(issueReporterData.extensionId)); From a0b90ac5e04ea9a0733b68062253a469994e170e Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 21 Feb 2024 16:25:42 -0800 Subject: [PATCH 1511/1897] Fix #205818. Fix race condition. (#205921) --- .../contrib/notebook/browser/view/cellParts/codeCell.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts index a74983b6b5f..23332732628 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts @@ -137,7 +137,6 @@ export class CodeCell extends Disposable { this._register(Event.runAndSubscribe(viewCell.onDidChangeLayout, this.updateForLayout.bind(this))); this._cellEditorOptions.setLineNumbers(this.viewCell.lineNumbers); - this._register(this._cellEditorOptions.onDidChange(() => this.updateCodeCellOptions(templateData))); templateData.editor.updateOptions(this._cellEditorOptions.getUpdatedValue(this.viewCell.internalMetadata, this.viewCell.uri)); } @@ -231,6 +230,8 @@ export class CodeCell extends Disposable { focusEditorIfNeeded(); } + + this._register(this._cellEditorOptions.onDidChange(() => this.updateCodeCellOptions(this.templateData))); }); } From eb278bd53bf8c7f67de33e8bd1a3a7725edfc010 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 22 Feb 2024 07:41:54 +0100 Subject: [PATCH 1512/1897] voice - add missing languages (#205935) --- .../accessibility/browser/accessibilityConfiguration.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 164dd8331d5..567bcc87a8d 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -717,6 +717,9 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen private getLanguages(): { [locale: string]: { name: string } } { return { + ['da-DK']: { + name: localize('speechLanguage.da-DK', "Danish (Denmark)") + }, ['de-DE']: { name: localize('speechLanguage.de-DE', "German (Germany)") }, @@ -768,6 +771,9 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen ['nl-NL']: { name: localize('speechLanguage.nl-NL', "Dutch (Netherlands)") }, + ['pt-PT']: { + name: localize('speechLanguage.pt-PT', "Portuguese (Portugal)") + }, ['pt-BR']: { name: localize('speechLanguage.pt-BR', "Portuguese (Brazil)") }, From 801f96365665352513d6971d78557f75aa5d193c Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 22 Feb 2024 09:32:21 +0100 Subject: [PATCH 1513/1897] prevent double discard of inline chat edits (#205945) maybe https://github.com/microsoft/vscode/issues/204592 --- .../workbench/contrib/inlineChat/browser/inlineChatSession.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 693fde05bab..a3e3865631d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -723,7 +723,9 @@ export class HunkData { discardAll() { const edits: ISingleEditOperation[][] = []; for (const item of this.getInfo()) { - edits.push(this._discardEdits(item)); + if (item.getState() !== HunkState.Rejected) { + edits.push(this._discardEdits(item)); + } } const undoEdits: IValidEditOperation[][] = []; this._textModelN.pushEditOperations(null, edits.flat(), (_undoEdits) => { From e46c089ef43742a696924b4142d79628b865cbc1 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 22 Feb 2024 09:45:00 +0100 Subject: [PATCH 1514/1897] Remove double hover in notebookKernelView.ts (#205948) remove double hover --- .../contrib/notebook/browser/viewParts/notebookKernelView.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts index 64bd903fae6..34958f1e7c0 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts @@ -166,7 +166,6 @@ export class NotebooKernelActionViewItem extends ActionViewItem { if (this._kernelLabel) { this._kernelLabel.classList.add('kernel-label'); this._kernelLabel.innerText = this._action.label; - this._kernelLabel.title = this._action.tooltip; } } From a6ba9af63c85385681c87c431d0ce4dc6c543313 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 22 Feb 2024 01:33:23 -0800 Subject: [PATCH 1515/1897] Add gap to extension `more info` table (#205923) * Add gap to extension `more info` table Fixes #165239 Switches to use a grid layout to simplify the code and also add a small gap between the label and content --- .../extensions/browser/media/extensionEditor.css | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index ce78e8c7f02..59831680770 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -494,15 +494,9 @@ .extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry { font-size: 90%; - display: flex; -} - -.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry :first-child { - width: 40%; -} - -.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry :last-child { - width: 60%; + display: grid; + grid-template-columns: 40% 60%; + gap: 4px; } .extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry code { From a4f9d3286675d83a390b1a92de3e5ea4aa0d4ab3 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Thu, 22 Feb 2024 10:42:51 +0100 Subject: [PATCH 1516/1897] Add telemetry reporting to rename suggestions (#205869) rename suggestions: add telemetry reporting --- .../editor/contrib/rename/browser/rename.ts | 38 ++++++++++++++++++- .../rename/browser/renameInputField.ts | 24 +++++++++++- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index fcf0db314c2..13d42c42e28 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -36,7 +36,8 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; -import { CONTEXT_RENAME_INPUT_FOCUSED, CONTEXT_RENAME_INPUT_VISIBLE, RenameInputField } from './renameInputField'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { CONTEXT_RENAME_INPUT_FOCUSED, CONTEXT_RENAME_INPUT_VISIBLE, RenameInputField, RenameInputFieldResult } from './renameInputField'; class RenameSkeleton { @@ -149,6 +150,7 @@ class RenameController implements IEditorContribution { @ILogService private readonly _logService: ILogService, @ITextResourceConfigurationService private readonly _configService: ITextResourceConfigurationService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, ) { this._renameInputField = this._disposableStore.add(this._instaService.createInstance(RenameInputField, this.editor, ['acceptRenameInput', 'acceptRenameInputWithPreview'])); } @@ -240,6 +242,8 @@ class RenameController implements IEditorContribution { const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, newSymbolNameProvidersResults, renameCandidatesCts); trace('received response from rename input field'); + this._reportTelemetry(inputFieldResult); + // no result, only hint to focus the editor or not if (typeof inputFieldResult === 'boolean') { trace(`returning early - rename input field response - ${inputFieldResult}`); @@ -325,6 +329,38 @@ class RenameController implements IEditorContribution { focusPreviousRenameSuggestion(): void { this._renameInputField.focusPreviousRenameSuggestion(); } + + private _reportTelemetry(inputFieldResult: boolean | RenameInputFieldResult) { + type RenameInvokedEvent = + { + kind: 'accepted' | 'cancelled'; + /** provided only if kind = 'accepted' */ + wantsPreview?: boolean; + /** provided only if kind = 'accepted' */ + source?: RenameInputFieldResult['source']; + /** provided only if kind = 'accepted' */ + hadRenameSuggestions?: boolean; + }; + + type RenameInvokedClassification = { + owner: 'ulugbekna'; + comment: 'A rename operation was invoked.'; + kind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the rename operation was cancelled or accepted.' }; + wantsPreview?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'If user wanted preview.'; isMeasurement: true }; + source?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the new name came from the input field or rename suggestions.' }; + hadRenameSuggestions?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user had rename suggestions.'; isMeasurement: true }; + }; + + this._telemetryService.publicLog2( + 'renameInvokedEvent', + typeof inputFieldResult === 'boolean' ? { kind: 'cancelled' } : { + kind: 'accepted', + wantsPreview: inputFieldResult.wantsPreview, + source: inputFieldResult.source, + hadRenameSuggestions: inputFieldResult.hadRenameSuggestions, + } + ); + } } // ---- action implementation diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index cce562b9fb9..794ef418d9c 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -49,6 +49,8 @@ export const CONTEXT_RENAME_INPUT_FOCUSED = new RawContextKey('renameIn export interface RenameInputFieldResult { newName: string; wantsPreview?: boolean; + source: 'inputField' | 'renameSuggestion'; + hadRenameSuggestions: boolean; } export class RenameInputField implements IContentWidget { @@ -298,7 +300,19 @@ export class RenameInputField implements IContentWidget { assertType(this._input !== undefined); assertType(this._candidatesView !== undefined); - const newName = this._candidatesView.focusedCandidate ?? this._input.value; + const hadRenameSuggestions = this._candidatesView.hasCandidates(); + + let newName: string; + let source: 'inputField' | 'renameSuggestion'; + if (this._candidatesView.focusedCandidate !== undefined) { + this._trace('using new name from renameSuggestion'); + newName = this._candidatesView.focusedCandidate; + source = 'renameSuggestion'; + } else { + this._trace('using new name from inputField'); + newName = this._input.value; + source = 'inputField'; + } if (newName === value || newName.trim().length === 0 /* is just whitespace */) { this.cancelInput(true, '_currentAcceptInput (because newName === value || newName.trim().length === 0)'); @@ -311,7 +325,9 @@ export class RenameInputField implements IContentWidget { resolve({ newName, - wantsPreview: supportPreview && wantsPreview + wantsPreview: supportPreview && wantsPreview, + source, + hadRenameSuggestions, }); }; @@ -513,6 +529,10 @@ class CandidatesView { this._listWidget.splice(0, this._listWidget.length, []); } + public hasCandidates() { + return this._listWidget.length > 0; + } + public get focusedCandidate(): string | undefined { if (this._listWidget.length === 0) { return; From d0e118cb72ead940697f3f84ff5cbe1a859f8fc7 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 22 Feb 2024 10:52:02 +0100 Subject: [PATCH 1517/1897] fix #165239 (#205954) --- .../contrib/extensions/browser/media/extensionEditor.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 59831680770..44f8b720581 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -496,7 +496,12 @@ font-size: 90%; display: grid; grid-template-columns: 40% 60%; - gap: 4px; + gap: 6px; + padding: 2px 4px; +} + +.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry:nth-child(odd) { + background-color: rgba(130, 130, 130, 0.04); } .extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry code { From 30d4de8b06905dd0170ca40452c443261920da1f Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 22 Feb 2024 11:10:05 +0100 Subject: [PATCH 1518/1897] adding on did resize call inside of the constructor --- .../contrib/stickyScroll/browser/stickyScrollController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts index fac1f9d3d51..492524e64b7 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts @@ -85,6 +85,7 @@ export class StickyScrollController extends Disposable implements IEditorContrib this._register(this._stickyLineCandidateProvider); this._widgetState = new StickyScrollWidgetState([], [], 0); + this._onDidResize(); this._readConfiguration(); const stickyScrollDomNode = this._stickyScrollWidget.getDomNode(); this._register(this._editor.onDidChangeConfiguration(e => { From 6eec64e575f957402a2e2f042b603470c020cd58 Mon Sep 17 00:00:00 2001 From: Peter V Date: Thu, 22 Feb 2024 05:12:36 -0500 Subject: [PATCH 1519/1897] Fix `IRawGalleryExtension.shortDescription` can be undefined. (#202780) `IRawGalleryExtension.shortDescription` can be undefined, some extensions doesn't have a "shortDescription" like: "temakulakov.hackjb" --- .../extensionManagement/common/extensionGalleryService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 07195962b1c..8833103e59c 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -68,7 +68,7 @@ interface IRawGalleryExtension { readonly extensionId: string; readonly extensionName: string; readonly displayName: string; - readonly shortDescription: string; + readonly shortDescription?: string; readonly publisher: IRawGalleryExtensionPublisher; readonly versions: IRawGalleryExtensionVersion[]; readonly statistics: IRawGalleryExtensionStatistics[]; @@ -532,7 +532,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller publisherDisplayName: galleryExtension.publisher.displayName, publisherDomain: galleryExtension.publisher.domain ? { link: galleryExtension.publisher.domain, verified: !!galleryExtension.publisher.isDomainVerified } : undefined, publisherSponsorLink: getSponsorLink(latestVersion), - description: galleryExtension.shortDescription || '', + description: galleryExtension.shortDescription ?? '', installCount: getStatistic(galleryExtension.statistics, 'install'), rating: getStatistic(galleryExtension.statistics, 'averagerating'), ratingCount: getStatistic(galleryExtension.statistics, 'ratingcount'), From 10171ed10f9439f19adee0ae94679979aa65da99 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 22 Feb 2024 11:28:33 +0100 Subject: [PATCH 1520/1897] Synchronizes in-editor scrolling back to virtual list scrolling. (#205959) Fixes revealing issues. --- .../diffEditorItemTemplate.ts | 35 ++++++++++++++++--- .../multiDiffEditorWidgetImpl.ts | 7 ++-- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts index 0f3353c66bc..f5433ba99d7 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts @@ -24,6 +24,7 @@ import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActio export class TemplateData implements IObjectData { constructor( public readonly viewModel: DocumentDiffItemViewModel, + public readonly deltaScrollVertical: (delta: number) => void, ) { } @@ -116,15 +117,15 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< this._elements.editor.style.display = this._collapsed.read(reader) ? 'none' : 'block'; })); - this.editor.getModifiedEditor().onDidLayoutChange(e => { + this._register(this.editor.getModifiedEditor().onDidLayoutChange(e => { const width = this.editor.getModifiedEditor().getLayoutInfo().contentWidth; this._modifiedWidth.set(width, undefined); - }); + })); - this.editor.getOriginalEditor().onDidLayoutChange(e => { + this._register(this.editor.getOriginalEditor().onDidLayoutChange(e => { const width = this.editor.getOriginalEditor().getLayoutInfo().contentWidth; this._originalWidth.set(width, undefined); - }); + })); this._register(this.editor.onDidContentSizeChange(e => { globalTransaction(tx => { @@ -134,6 +135,18 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< }); })); + this._register(this.editor.getOriginalEditor().onDidScrollChange(e => { + if (this._isSettingScrollTop) { + return; + } + + if (!e.scrollTopChanged || !this._data) { + return; + } + const delta = e.scrollTop - this._lastScrollTop; + this._data.deltaScrollVertical(delta); + })); + this._register(autorun(reader => { const isFocused = this.isFocused.read(reader); this._elements.root.classList.toggle('focused', isFocused); @@ -162,7 +175,10 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< private readonly _dataStore = new DisposableStore(); + private _data: TemplateData | undefined; + public setData(data: TemplateData): void { + this._data = data; function updateOptions(options: IDiffEditorOptions): IDiffEditorOptions { return { ...options, @@ -222,6 +238,9 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< private readonly _headerHeight = /*this._elements.header.clientHeight*/ 48; + private _lastScrollTop = -1; + private _isSettingScrollTop = false; + public render(verticalRange: OffsetRange, width: number, editorScroll: number, viewPort: OffsetRange): void { this._elements.root.style.visibility = 'visible'; this._elements.root.style.top = `${verticalRange.start}px`; @@ -240,7 +259,13 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< height: verticalRange.length - this._outerEditorHeight, }); }); - this.editor.getOriginalEditor().setScrollTop(editorScroll); + try { + this._isSettingScrollTop = true; + this._lastScrollTop = editorScroll; + this.editor.getOriginalEditor().setScrollTop(editorScroll); + } finally { + this._isSettingScrollTop = false; + } this._elements.header.classList.toggle('shadow', delta > 0 || editorScroll > 0); this._elements.header.classList.toggle('collapsed', delta === maxDelta); diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index a913cdad350..36a568f2524 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -76,7 +76,9 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } const items = vm.items.read(reader); return items.map(d => { - const item = store.add(new VirtualizedViewItem(d, this._objectPool, this.scrollLeft)); + const item = store.add(new VirtualizedViewItem(d, this._objectPool, this.scrollLeft, delta => { + this._scrollableElement.setScrollPosition({ scrollTop: this._scrollableElement.getScrollPosition().scrollTop + delta }); + })); const data = this._lastDocStates?.[item.getKey()]; if (data) { transaction(tx => { @@ -344,6 +346,7 @@ class VirtualizedViewItem extends Disposable { public readonly viewModel: DocumentDiffItemViewModel, private readonly _objectPool: ObjectPool, private readonly _scrollLeft: IObservable, + private readonly _deltaScrollVertical: (delta: number) => void, ) { super(); @@ -434,7 +437,7 @@ class VirtualizedViewItem extends Disposable { let ref = this._templateRef.get(); if (!ref) { - ref = this._objectPool.getUnusedObj(new TemplateData(this.viewModel)); + ref = this._objectPool.getUnusedObj(new TemplateData(this.viewModel, this._deltaScrollVertical)); this._templateRef.set(ref, undefined); const selections = this.viewModel.lastTemplateData.get().selections; From 078596ba3bee4e1ba65e242b7bb4aa8e3f9aa2c0 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 22 Feb 2024 12:49:20 +0100 Subject: [PATCH 1521/1897] Fix TreeView Mouse Hover Persistence (#205970) * fix treeview mouse hover persistence * fix persistence --- src/vs/workbench/browser/parts/views/treeView.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index a46d7bc51d6..0245752c809 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -62,7 +62,7 @@ import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme import { Extensions, ITreeItem, ITreeItemLabel, ITreeView, ITreeViewDataProvider, ITreeViewDescriptor, ITreeViewDragAndDropController, IViewBadge, IViewDescriptorService, IViewsRegistry, ResolvableTreeItem, TreeCommand, TreeItemCollapsibleState, TreeViewItemHandleArg, TreeViewPaneHandleArg, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { IHoverService, WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover'; import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService'; import { CodeDataTransfers, LocalSelectionTransfer } from 'vs/platform/dnd/browser/dnd'; import { toExternalVSDataTransfer } from 'vs/editor/browser/dnd'; @@ -73,7 +73,6 @@ import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUti import { ITreeViewsDnDService } from 'vs/editor/common/services/treeViewsDndService'; import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd'; import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export class TreeViewPane extends ViewPane { @@ -1104,9 +1103,10 @@ class TreeRenderer extends Disposable implements ITreeRenderer this.rerender())); this._register(this.themeService.onDidColorThemeChange(() => this.rerender())); this._register(checkboxStateHandler.onDidChangeCheckboxState(items => { From 5347f55550c2ae17e7e95d4781dccfdb97d509c8 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 22 Feb 2024 13:50:56 +0100 Subject: [PATCH 1522/1897] adding wip --- .../test/browser/stickyScroll.test.ts | 24 +++++++++++++++---- .../test/browser/config/testConfiguration.ts | 18 +++++++------- src/vs/editor/test/browser/testCodeEditor.ts | 19 ++++++++++++--- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/contrib/stickyScroll/test/browser/stickyScroll.test.ts b/src/vs/editor/contrib/stickyScroll/test/browser/stickyScroll.test.ts index 9f15b6ad681..04e852765ca 100644 --- a/src/vs/editor/contrib/stickyScroll/test/browser/stickyScroll.test.ts +++ b/src/vs/editor/contrib/stickyScroll/test/browser/stickyScroll.test.ts @@ -137,7 +137,11 @@ suite('Sticky Scroll Tests', () => { enabled: true, maxLineCount: 5, defaultModel: 'outlineModel' - }, serviceCollection: serviceCollection + }, + envConfig: { + outerHeight: 500 + }, + serviceCollection: serviceCollection }, async (editor, _viewModel, instantiationService) => { const languageService = instantiationService.get(ILanguageFeaturesService); const languageConfigurationService = instantiationService.get(ILanguageConfigurationService); @@ -162,7 +166,11 @@ suite('Sticky Scroll Tests', () => { enabled: true, maxLineCount: 5, defaultModel: 'outlineModel' - }, serviceCollection + }, + envConfig: { + outerHeight: 500 + }, + serviceCollection }, async (editor, _viewModel, instantiationService) => { const stickyScrollController: StickyScrollController = editor.registerAndInstantiateContribution(StickyScrollController.ID, StickyScrollController); @@ -211,7 +219,11 @@ suite('Sticky Scroll Tests', () => { enabled: true, maxLineCount: 5, defaultModel: 'outlineModel' - }, serviceCollection + }, + envConfig: { + outerHeight: 500 + }, + serviceCollection }, async (editor, viewModel, instantiationService) => { const stickyScrollController: StickyScrollController = editor.registerAndInstantiateContribution(StickyScrollController.ID, StickyScrollController); @@ -305,7 +317,11 @@ suite('Sticky Scroll Tests', () => { enabled: true, maxLineCount: 5, defaultModel: 'outlineModel' - }, serviceCollection + }, + envConfig: { + outerHeight: 500 + }, + serviceCollection }, async (editor, _viewModel, instantiationService) => { const stickyScrollController: StickyScrollController = editor.registerAndInstantiateContribution(StickyScrollController.ID, StickyScrollController); diff --git a/src/vs/editor/test/browser/config/testConfiguration.ts b/src/vs/editor/test/browser/config/testConfiguration.ts index 71f1c4d0e0c..b5d42908ded 100644 --- a/src/vs/editor/test/browser/config/testConfiguration.ts +++ b/src/vs/editor/test/browser/config/testConfiguration.ts @@ -4,25 +4,27 @@ *--------------------------------------------------------------------------------------------*/ import { EditorConfiguration, IEnvConfiguration } from 'vs/editor/browser/config/editorConfiguration'; -import { EditorFontLigatures, EditorFontVariations, IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { EditorFontLigatures, EditorFontVariations } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; +import { TestCodeEditorCreationOptions } from 'vs/editor/test/browser/testCodeEditor'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { TestAccessibilityService } from 'vs/platform/accessibility/test/common/testAccessibilityService'; export class TestConfiguration extends EditorConfiguration { - constructor(opts: IEditorOptions) { + constructor(opts: Readonly) { super(false, opts, null, new TestAccessibilityService()); } protected override _readEnvConfiguration(): IEnvConfiguration { + const envConfig = (this.getRawOptions() as TestCodeEditorCreationOptions).envConfig; return { - extraEditorClassName: '', - outerWidth: 100, - outerHeight: 100, - emptySelectionClipboard: true, - pixelRatio: 1, - accessibilitySupport: AccessibilitySupport.Unknown + extraEditorClassName: envConfig?.extraEditorClassName ?? '', + outerWidth: envConfig?.outerWidth ?? 100, + outerHeight: envConfig?.outerHeight ?? 100, + emptySelectionClipboard: envConfig?.emptySelectionClipboard ?? true, + pixelRatio: envConfig?.pixelRatio ?? 1, + accessibilitySupport: envConfig?.accessibilitySupport ?? AccessibilitySupport.Unknown }; } diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index 507dbe2cc6f..b29e05c8053 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -5,7 +5,7 @@ import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { mock } from 'vs/base/test/common/mock'; -import { EditorConfiguration, IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; +import { EditorConfiguration } from 'vs/editor/browser/config/editorConfiguration'; import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { View } from 'vs/editor/browser/view'; @@ -30,7 +30,7 @@ import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/te import { TestEditorWorkerService } from 'vs/editor/test/common/services/testEditorWorkerService'; import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/testTextResourcePropertiesService'; import { instantiateTextModel } from 'vs/editor/test/common/testTextModel'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { AccessibilitySupport, IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { TestAccessibilityService } from 'vs/platform/accessibility/test/common/testAccessibilityService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { TestClipboardService } from 'vs/platform/clipboard/test/common/testClipboardService'; @@ -68,7 +68,7 @@ export interface ITestCodeEditor extends IActiveCodeEditor { export class TestCodeEditor extends CodeEditorWidget implements ICodeEditor { //#region testing overrides - protected override _createConfiguration(isSimpleWidget: boolean, options: Readonly): EditorConfiguration { + protected override _createConfiguration(isSimpleWidget: boolean, options: Readonly): EditorConfiguration { return new TestConfiguration(options); } protected override _createView(viewModel: ViewModel): [View, boolean] { @@ -116,6 +116,10 @@ export interface TestCodeEditorCreationOptions extends editorOptions.IEditorOpti * Defaults to true. */ hasTextFocus?: boolean; + /** + * Env configuration + */ + envConfig?: ITestEnvConfiguration; } export interface TestCodeEditorInstantiationOptions extends TestCodeEditorCreationOptions { @@ -125,6 +129,15 @@ export interface TestCodeEditorInstantiationOptions extends TestCodeEditorCreati serviceCollection?: ServiceCollection; } +export interface ITestEnvConfiguration { + extraEditorClassName?: string; + outerWidth?: number; + outerHeight?: number; + emptySelectionClipboard?: boolean; + pixelRatio?: number; + accessibilitySupport?: AccessibilitySupport; +} + export function withTestCodeEditor(text: ITextModel | string | string[] | ITextBufferFactory, options: TestCodeEditorInstantiationOptions, callback: (editor: ITestCodeEditor, viewModel: ViewModel, instantiationService: TestInstantiationService) => void): void { return _withTestCodeEditor(text, options, callback); } From fa7b64426e10dd85be598048842bc5d47aed7a0b Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 22 Feb 2024 14:49:14 +0100 Subject: [PATCH 1523/1897] voice chat: Fix mic icon centering in all contexts (#205978) * voice chat: Fix mic icon centering in all contexts --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 79572 -> 79568 bytes .../actions/voiceChatActions.ts | 6 ++---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index b34cfdc245c6a94f79b0f22da57ca8fe64b9721d..373f406613faa7f8633273ad735116e0f7a5e212 100644 GIT binary patch delta 638 zcmccemgT}*mI)3#lhx-QWMELYVPN=jV4{yHv&B!9i7iS((tj8j7=IMx7nj_6|5}!T zQTEHk9R@5{|G)Cz_~yJy{S5m<_WvBr9LgM4Is9_eag1`D;<(B2o0FW=5~q945zdQT z+FbUzd~gkOt#Uo#rsdY;w$Gi#J;MEhhl)p;M~}xIPbN<*&mhk#&sm*EmR;hAhaxWRp^s2y|A9JePMUPCBm0P@I`b)T!=J?To!pR zN+PN#>RGf-^o$sVm}jvpu}X0QaZYg&admMg;`!qB;uGSRB$y4wrTWoBg)$~Kg}E0-!SDW6mkR&lH{qw)bul}c5@ zW*c?my83^N0u0;?{0zzr4CW?gMq;vzB5X=(rsAf?qUvmVjHbpSa*WoDMs^?;AER=d zqMjlU=?e)-X-f)=D2WP*Dv1b7YD?V^S5y@DmQYlb=v7lwV+6{ID2o7*q_!jj0|N^X zGcf#Plmgl%!=S)m0<_H(WZmRISrhXKo^nCELZzB#W_Kg<4z1A~KwLxsZ{hd+*bjxmnY9Je_Ba8htu=Jdch%6W-P zhsyz%Pp%QJHLj=Jblh6p4!EM3_8RA*vImh#lmx0$VZwc=z z?={|kd^CLeeD?V~@wM@d@J;hm@N4s1<*(wu!2el5SU_LEu|T;%pTL^>AfBMKphLkj z!7jlW!9BrGLRdmHLTW;8gbIZQg;s>F34Io35Y`uVAnabaRQR$8frze%OOZyADgP622uWCAuV5 zB?%a^5LX<}&+X;ac}rK_dar7uc|ARJf_|PLWp8 zhGL)Mn&NXM3?()tlS;0XDwH~uZYup&W>Ge&Y*X2Xa+&h7@+lP&6(=gQDj%^_sZ}Ly zwox~(tN+3%z`)JG&!Eh}U@j`crle-eXkun0#%^jXs?MgzXlg7X$7s!HWCvpLF)ANb z)K>%|Ct+bpZAl>!Wf5Tzmei5F;Vq%4DB&%xsQ6w@O^s1XTM{S%L_lT0q`(5iz!btL z1++_sL4m;pXq%~t9i!OfcT4p( { collector.addRule(` .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { - outline: 2px solid ${activeRecordingColor}; border-radius: 50%; + outline: 2px solid ${activeRecordingColor}; animation: pulseAnimation 1500ms ease-in-out infinite !important; - padding-left: 4px; - height: 17px; } @keyframes pulseAnimation { 0% { outline-width: 2px; } - 50% { + 62% { outline-width: 5px; outline-color: ${activeRecordingDimmedColor}; } From 71b0a729fea632cc36b581abd788d5ab317dcf09 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 22 Feb 2024 14:03:19 +0000 Subject: [PATCH 1524/1897] Avoid losing chat input after accidentally navigating history (#205977) Fix microsoft/vscode-copilot-release#661 --- .../workbench/contrib/chat/browser/chatInputPart.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 04b6ffc3810..f4692878598 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -86,6 +86,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private historyNavigationBackwardsEnablement!: IContextKey; private historyNavigationForewardsEnablement!: IContextKey; private onHistoryEntry = false; + private inHistoryNavigation = false; private inputModel: ITextModel | undefined; private inputEditorHasText: IContextKey; private chatCursorAtTop: IContextKey; @@ -160,7 +161,11 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.onHistoryEntry = previous || this.history.current() !== null; aria.status(historyEntry.text); + + this.inHistoryNavigation = true; this.setValue(historyEntry.text); + this.inHistoryNavigation = false; + this._onDidLoadInputState.fire(historyEntry.state); if (previous) { this._inputEditor.setPosition({ lineNumber: 1, column: 1 }); @@ -269,6 +274,12 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const model = this._inputEditor.getModel(); const inputHasText = !!model && model.getValueLength() > 0; this.inputEditorHasText.set(inputHasText); + + // If the user is typing on a history entry, then reset the onHistoryEntry flag so that history navigation can be disabled + if (!this.inHistoryNavigation) { + this.onHistoryEntry = false; + } + if (!this.onHistoryEntry) { this.historyNavigationForewardsEnablement.set(!inputHasText); this.historyNavigationBackwardsEnablement.set(!inputHasText); From 0b5864063ccf09844f84bb4f0cfcf5aff7297e9b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 22 Feb 2024 15:12:44 +0100 Subject: [PATCH 1525/1897] voice - default keybinding for editor dictation (#205981) --- .../codeEditor/browser/dictation/editorDictation.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts index 67863783054..842953efd0d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts @@ -16,7 +16,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { EditorAction2, EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { KeyCode } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -41,7 +41,11 @@ export class EditorDictationStartAction extends EditorAction2 { title: localize2('startDictation', "Start Dictation in Editor"), category: VOICE_CATEGORY, precondition: ContextKeyExpr.and(HasSpeechProvider, EDITOR_DICTATION_IN_PROGRESS.toNegated(), EditorContextKeys.readOnly.toNegated()), - f1: true + f1: true, + keybinding: { + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyV, + weight: KeybindingWeight.WorkbenchContrib + } }); } From 4c57ecb33c6f2c9fec1d7853aaf8e8069b7ff474 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Thu, 22 Feb 2024 14:48:29 +0100 Subject: [PATCH 1526/1897] rename suggestions: try no. 3 at fixing width to not hide parts of long suggested names --- .../rename/browser/renameInputField.ts | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 794ef418d9c..5b30d72e900 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { addDisposableListener, getClientArea, getDomNodePagePosition, getTotalHeight } from 'vs/base/browser/dom'; +import { addDisposableListener, getClientArea, getDomNodePagePosition, getTotalHeight, getTotalWidth } from 'vs/base/browser/dom'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; @@ -238,7 +238,10 @@ export class RenameInputField implements IContentWidget { totalHeightAvailable = this._nPxAvailableAbove; } - this._candidatesView!.layout({ height: totalHeightAvailable - labelHeight - inputBoxHeight }); + this._candidatesView!.layout({ + height: totalHeightAvailable - labelHeight - inputBoxHeight, + width: getTotalWidth(this._input!), + }); } @@ -429,6 +432,7 @@ class CandidatesView { private _lineHeight: number; private _availableHeight: number; + private _minimumWidth: number; private _disposables: DisposableStore; @@ -437,6 +441,7 @@ class CandidatesView { this._disposables = new DisposableStore(); this._availableHeight = 0; + this._minimumWidth = 0; this._lineHeight = opts.fontInfo.lineHeight; @@ -505,27 +510,31 @@ class CandidatesView { } // height - max height allowed by parent element - public layout({ height }: { height: number }): void { + public layout({ height, width }: { height: number; width: number }): void { this._availableHeight = height; - if (this._listWidget.length > 0) { // candidates have been set - this._listWidget.layout(this._pickListHeight(this._listWidget.length)); - } + this._minimumWidth = width; + this._listContainer.style.width = `${this._minimumWidth}px`; } public setCandidates(candidates: NewSymbolName[]): void { - const height = this._pickListHeight(candidates.length); + // insert candidates into list widget this._listWidget.splice(0, 0, candidates); - const width = Math.max(200, 4 /* padding */ + 16 /* sparkle icon */ + 5 /* margin-left */ + this._pickListWidth(candidates)); // TODO@ulugbekna: approximate calc - clean this up + // adjust list widget layout + const height = this._pickListHeight(candidates.length); + const width = this._pickListWidth(candidates); + this._listWidget.layout(height, width); + // adjust list container layout this._listContainer.style.height = `${height}px`; this._listContainer.style.width = `${width}px`; } public clearCandidates(): void { this._listContainer.style.height = '0px'; + this._listContainer.style.width = '0px'; this._listWidget.splice(0, this._listWidget.length, []); } @@ -599,7 +608,13 @@ class CandidatesView { } private _pickListWidth(candidates: NewSymbolName[]): number { - return Math.ceil(Math.max(...candidates.map(c => c.newSymbolName.length)) * 7.2) /* approximate # of pixes taken by a single character */; + const APPROXIMATE_CHAR_WIDTH = 7.2; // approximate # of pixes taken by a single character + const longestCandidateWidth = Math.ceil(Math.max(...candidates.map(c => c.newSymbolName.length)) * APPROXIMATE_CHAR_WIDTH); // TODO@ulugbekna: use editor#typicalCharacterWidth or something + const width = Math.max( + this._minimumWidth, + 4 /* padding */ + 16 /* sparkle icon */ + 5 /* margin-left */ + longestCandidateWidth + 10 /* (possibly visible) scrollbar width */ // TODO@ulugbekna: approximate calc - clean this up + ); + return width; } } From fa6c148ea26f0e6e1644f04f8f0eac144527ee4c Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Thu, 22 Feb 2024 14:55:28 +0100 Subject: [PATCH 1527/1897] rename suggestions: extract magic number into a var --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 5b30d72e900..93d0d23fdd8 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -603,7 +603,8 @@ class CandidatesView { private _pickListHeight(nCandidates: number) { const heightToFitAllCandidates = this._candidateViewHeight * nCandidates; - const height = Math.min(heightToFitAllCandidates, this._availableHeight, this._candidateViewHeight * 7 /* max # of candidates we want to show at once */); + const MAX_N_CANDIDATES = 7; // @ulugbekna: max # of candidates we want to show at once + const height = Math.min(heightToFitAllCandidates, this._availableHeight, this._candidateViewHeight * MAX_N_CANDIDATES); return height; } From 17f02a45a399235084bdcd55d2a7bfce16f71e8d Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Thu, 22 Feb 2024 15:11:15 +0100 Subject: [PATCH 1528/1897] rename suggestions: notify when rename suggestions arrive --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 93d0d23fdd8..f7021dd6897 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { addDisposableListener, getClientArea, getDomNodePagePosition, getTotalHeight, getTotalWidth } from 'vs/base/browser/dom'; +import * as aria from 'vs/base/browser/ui/aria/aria'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; @@ -530,6 +531,8 @@ class CandidatesView { // adjust list container layout this._listContainer.style.height = `${height}px`; this._listContainer.style.width = `${width}px`; + + aria.status(localize('renameSuggestionsReceivedAria', "Received {0} rename suggestions", candidates.length)); } public clearCandidates(): void { From 0b82c6e374fc6bb1a6a1d3145e7cce90c85d7ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Cie=C5=9Blak?= Date: Thu, 22 Feb 2024 16:06:26 +0100 Subject: [PATCH 1529/1897] Inline edit - don't send reject callback on blur (#205976) --- .../contrib/inlineEdit/browser/inlineEditController.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts b/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts index 81d6fdb9d54..8355ef312cf 100644 --- a/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts +++ b/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts @@ -123,7 +123,7 @@ export class InlineEditController extends Disposable { } this._currentRequestCts?.dispose(); this._currentRequestCts = undefined; - this.clear(); + this.clear(false); })); //Invoke provider on focus @@ -288,9 +288,9 @@ export class InlineEditController extends Disposable { this.editor.revealPositionInCenterIfOutsideViewport(position); } - public clear() { + public clear(sendRejection: boolean = true) { const edit = this._currentEdit.get()?.edit; - if (edit && edit?.rejected && !this._isAccepting) { + if (edit && edit?.rejected && !this._isAccepting && sendRejection) { this._commandService.executeCommand(edit.rejected.id, ...edit.rejected.arguments || []); } if (edit) { From 838a59f24088acf14e61ecef2639e7384c46ef60 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 22 Feb 2024 16:39:24 +0100 Subject: [PATCH 1530/1897] send `onLanguageModelAccess` activation event (#205995) https://github.com/microsoft/vscode/issues/205726 --- .../api/browser/mainThreadChatProvider.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index dd76d12f105..4b7665abdb6 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { timeout } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -82,15 +81,20 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { } async $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise { + + const activate = this._extensionService.activateByEvent(`onLanguageModelAccess:${providerId}`); const metadata = this._chatProviderService.lookupChatResponseProvider(providerId); - // TODO: This should use a real activation event. Perhaps following what authentication does. - for (let i = 0; i < 3; i++) { - if (metadata) { - return metadata; - } - await timeout(2000); + + if (metadata) { + return metadata; } - return undefined; + + await Promise.race([ + activate, + Event.toPromise(Event.filter(this._chatProviderService.onDidChangeProviders, e => Boolean(e.added?.includes(providerId)))) + ]); + + return this._chatProviderService.lookupChatResponseProvider(providerId); } async $fetchResponse(extension: ExtensionIdentifier, providerId: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise { From 9c05ffbbbd6dab21dea4dba825276d5c97a2c0ff Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Thu, 22 Feb 2024 17:10:20 +0100 Subject: [PATCH 1531/1897] rename suggestions: include more telemetry --- .../editor/contrib/rename/browser/rename.ts | 47 +++++++++++++------ .../rename/browser/renameInputField.ts | 15 +++--- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index 13d42c42e28..8b7bc9b51b8 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -242,7 +242,9 @@ class RenameController implements IEditorContribution { const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, newSymbolNameProvidersResults, renameCandidatesCts); trace('received response from rename input field'); - this._reportTelemetry(inputFieldResult); + if (newSymbolNamesProviders.length > 0) { // @ulugbekna: we're interested only in telemetry for rename suggestions currently + this._reportTelemetry(newSymbolNamesProviders.length, model.getLanguageId(), inputFieldResult); + } // no result, only hint to focus the editor or not if (typeof inputFieldResult === 'boolean') { @@ -330,36 +332,51 @@ class RenameController implements IEditorContribution { this._renameInputField.focusPreviousRenameSuggestion(); } - private _reportTelemetry(inputFieldResult: boolean | RenameInputFieldResult) { + private _reportTelemetry(nRenameSuggestionProviders: number, languageId: string, inputFieldResult: boolean | RenameInputFieldResult) { type RenameInvokedEvent = { kind: 'accepted' | 'cancelled'; - /** provided only if kind = 'accepted' */ - wantsPreview?: boolean; + languageId: string; + nRenameSuggestionProviders: number; + /** provided only if kind = 'accepted' */ source?: RenameInputFieldResult['source']; /** provided only if kind = 'accepted' */ - hadRenameSuggestions?: boolean; + nRenameSuggestions?: number; + /** provided only if kind = 'accepted' */ + wantsPreview?: boolean; }; type RenameInvokedClassification = { owner: 'ulugbekna'; comment: 'A rename operation was invoked.'; + kind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the rename operation was cancelled or accepted.' }; - wantsPreview?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'If user wanted preview.'; isMeasurement: true }; + languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Document language ID.' }; + nRenameSuggestionProviders: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of rename providers for this document.'; isMeasurement: true }; + source?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the new name came from the input field or rename suggestions.' }; - hadRenameSuggestions?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user had rename suggestions.'; isMeasurement: true }; + nRenameSuggestions?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of rename suggestions user has got'; isMeasurement: true }; + wantsPreview?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'If user wanted preview.'; isMeasurement: true }; }; - this._telemetryService.publicLog2( - 'renameInvokedEvent', - typeof inputFieldResult === 'boolean' ? { kind: 'cancelled' } : { - kind: 'accepted', - wantsPreview: inputFieldResult.wantsPreview, - source: inputFieldResult.source, - hadRenameSuggestions: inputFieldResult.hadRenameSuggestions, + const value: RenameInvokedEvent = typeof inputFieldResult === 'boolean' + ? { + kind: 'cancelled', + languageId, + nRenameSuggestionProviders, } - ); + : { + kind: 'accepted', + languageId, + nRenameSuggestionProviders, + + source: inputFieldResult.source, + nRenameSuggestions: inputFieldResult.nRenameSuggestions, + wantsPreview: inputFieldResult.wantsPreview, + }; + + this._telemetryService.publicLog2('renameInvokedEvent', value); } } diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index f7021dd6897..cf76d531a2a 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -51,7 +51,7 @@ export interface RenameInputFieldResult { newName: string; wantsPreview?: boolean; source: 'inputField' | 'renameSuggestion'; - hadRenameSuggestions: boolean; + nRenameSuggestions: number; } export class RenameInputField implements IContentWidget { @@ -304,13 +304,14 @@ export class RenameInputField implements IContentWidget { assertType(this._input !== undefined); assertType(this._candidatesView !== undefined); - const hadRenameSuggestions = this._candidatesView.hasCandidates(); + const nRenameSuggestions = this._candidatesView.nCandidates; let newName: string; let source: 'inputField' | 'renameSuggestion'; - if (this._candidatesView.focusedCandidate !== undefined) { + const focusedCandidate = this._candidatesView.focusedCandidate; + if (focusedCandidate !== undefined) { this._trace('using new name from renameSuggestion'); - newName = this._candidatesView.focusedCandidate; + newName = focusedCandidate; source = 'renameSuggestion'; } else { this._trace('using new name from inputField'); @@ -331,7 +332,7 @@ export class RenameInputField implements IContentWidget { newName, wantsPreview: supportPreview && wantsPreview, source, - hadRenameSuggestions, + nRenameSuggestions, }); }; @@ -541,8 +542,8 @@ class CandidatesView { this._listWidget.splice(0, this._listWidget.length, []); } - public hasCandidates() { - return this._listWidget.length > 0; + public get nCandidates() { + return this._listWidget.length; } public get focusedCandidate(): string | undefined { From a6671b6ce40a75438455b10e0d888d49203bfc55 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 22 Feb 2024 10:35:48 -0800 Subject: [PATCH 1532/1897] fix #205964 --- .../accessibility/browser/accessibilityConfiguration.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 567bcc87a8d..1011f2b8743 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -834,6 +834,7 @@ Registry.as(WorkbenchExtensions.ConfigurationMi announcement = announcement ? 'auto' : 'off'; } } + configurationKeyValuePairs.push([`${item.legacySoundSettingsKey}`, { value: undefined }]); configurationKeyValuePairs.push([`${item.settingsKey}`, { value: announcement !== undefined ? { announcement, sound } : { sound } }]); return configurationKeyValuePairs; } @@ -844,11 +845,13 @@ Registry.as(WorkbenchExtensions.ConfigurationMi key: item.legacyAnnouncementSettingsKey!, migrateFn: (announcement, accessor) => { const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; - const sound = accessor(item.legacySoundSettingsKey); + const sound = accessor(item.settingsKey)?.sound || accessor(item.legacySoundSettingsKey); if (announcement !== undefined && typeof announcement !== 'string') { announcement = announcement ? 'auto' : 'off'; } configurationKeyValuePairs.push([`${item.settingsKey}`, { value: announcement !== undefined ? { announcement, sound } : { sound } }]); + configurationKeyValuePairs.push([`${item.legacyAnnouncementSettingsKey}`, { value: undefined }]); + configurationKeyValuePairs.push([`${item.legacySoundSettingsKey}`, { value: undefined }]); return configurationKeyValuePairs; } }))); From f5583237a0caba961e61bcf8075d23c85da971e0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 22 Feb 2024 10:52:27 -0800 Subject: [PATCH 1533/1897] tweak tooltip description --- .../contrib/accessibilitySignals/browser/commands.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 8561b125cd0..329cabe4bf3 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -40,7 +40,7 @@ export class ShowSignalSoundHelp extends Action2 { signal, buttons: userGestureSignals.includes(signal) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), - tooltip: localize('sounds.help.settings', 'Enable/Disable Sound'), + tooltip: localize('sounds.help.settings', 'Configure Sound'), alwaysVisible: true }] : [] })); @@ -108,7 +108,7 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { signal, buttons: userGestureSignals.includes(signal) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), - tooltip: localize('announcement.help.settings', 'Enable/Disable Announcement'), + tooltip: localize('announcement.help.settings', 'Configure Announcement'), alwaysVisible: true, }] : [] })); From 96d451b00e77cf7bab89b1956142320bc525bbae Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 22 Feb 2024 11:10:55 -0800 Subject: [PATCH 1534/1897] Fix #205987. (#206012) --- .../controller/chat/notebookChatController.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index b22b51850d6..61810983632 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -177,6 +177,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito private _sessionCtor: CancelablePromise | undefined; private _activeSession?: Session; private _warmupRequestCts?: CancellationTokenSource; + private _activeRequestCts?: CancellationTokenSource; private readonly _ctxHasActiveRequest: IContextKey; private readonly _ctxCellWidgetFocused: IContextKey; private readonly _ctxUserDidEdit: IContextKey; @@ -397,8 +398,9 @@ export class NotebookChatController extends Disposable implements INotebookEdito } async acceptInput() { - assertType(this._activeSession); assertType(this._widget); + await this._sessionCtor; + assertType(this._activeSession); this._warmupRequestCts?.dispose(true); this._warmupRequestCts = undefined; this._activeSession.addInput(new SessionPrompt(this._widget.inlineChatWidget.value)); @@ -449,18 +451,19 @@ export class NotebookChatController extends Disposable implements INotebookEdito //TODO: update progress in a newly inserted cell below the widget instead of the fake editor - const requestCts = new CancellationTokenSource(); + this._activeRequestCts?.cancel(); + this._activeRequestCts = new CancellationTokenSource(); const progressEdits: TextEdit[][] = []; const progressiveEditsQueue = new Queue(); const progressiveEditsClock = StopWatch.create(); const progressiveEditsAvgDuration = new MovingAverage(); - const progressiveEditsCts = new CancellationTokenSource(requestCts.token); + const progressiveEditsCts = new CancellationTokenSource(this._activeRequestCts.token); let progressiveChatResponse: IInlineChatMessageAppender | undefined; const progress = new AsyncProgress(async data => { // console.log('received chunk', data, request); - if (requestCts.token.isCancellationRequested) { + if (this._activeRequestCts?.token.isCancellationRequested) { return; } @@ -502,7 +505,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito } }); - const task = this._activeSession.provider.provideResponse(this._activeSession.session, request, progress, requestCts.token); + const task = this._activeSession.provider.provideResponse(this._activeSession.session, request, progress, this._activeRequestCts.token); let response: ReplyResponse | ErrorResponse | EmptyResponse; try { @@ -512,7 +515,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._widget?.inlineChatWidget.updateInfo(!this._activeSession.lastExchange ? localize('thinking', "Thinking\u2026") : ''); this._ctxHasActiveRequest.set(true); - const reply = await raceCancellationError(Promise.resolve(task), requestCts.token); + const reply = await raceCancellationError(Promise.resolve(task), this._activeRequestCts.token); if (progressiveEditsQueue.size > 0) { // we must wait for all edits that came in via progress to complete await Event.toPromise(progressiveEditsQueue.onDrained); @@ -575,7 +578,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._widget?.inlineChatWidget.updateInfo(''); this._widget?.inlineChatWidget.updateToolbar(true); - this._activeSession.addExchange(new SessionExchange(this._activeSession.lastInput, response)); + this._activeSession?.addExchange(new SessionExchange(this._activeSession.lastInput, response)); this._ctxLastResponseType.set(response instanceof ReplyResponse ? response.raw.type : undefined); } @@ -762,15 +765,12 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._strategy?.cancel(); } - if (this._activeSession) { - this._inlineChatSessionService.releaseSession(this._activeSession); - } - - this._activeSession = undefined; + this._activeRequestCts?.cancel(); } discard() { this._strategy?.cancel(); + this._activeRequestCts?.cancel(); this._widget?.discardChange(); this.dismiss(); } From a85dd8c427b6030d45b2ce5ba8b1965a565f3f8b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 22 Feb 2024 11:32:25 -0800 Subject: [PATCH 1535/1897] fix #205974 --- src/vs/platform/terminal/common/terminal.ts | 1 + .../terminal/browser/terminalInstance.ts | 2 +- .../terminal/common/terminalConfiguration.ts | 25 ++++++++++++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index a0b94d2a7f7..2a2df0a7a44 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -79,6 +79,7 @@ export const enum TerminalSettingId { ConfirmOnExit = 'terminal.integrated.confirmOnExit', ConfirmOnKill = 'terminal.integrated.confirmOnKill', EnableBell = 'terminal.integrated.enableBell', + EnableVisualBell = 'terminal.integrated.enableVisualBell', CommandsToSkipShell = 'terminal.integrated.commandsToSkipShell', AllowChords = 'terminal.integrated.allowChords', AllowMnemonics = 'terminal.integrated.allowMnemonics', diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 8b11cc8d9e8..84bde0d8006 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -754,7 +754,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // starts up or reconnects disposableTimeout(() => { this._register(xterm.raw.onBell(() => { - if (this._configHelper.config.enableBell) { + if (this._configurationService.getValue(TerminalSettingId.EnableBell) || this._configurationService.getValue(TerminalSettingId.EnableVisualBell)) { this.statusList.add({ id: TerminalStatus.Bell, severity: Severity.Warning, diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index ac0903b8ae3..4557474f63e 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -12,6 +12,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Codicon } from 'vs/base/common/codicons'; import { terminalColorSchema, terminalIconSchema } from 'vs/platform/terminal/common/terminalPlatformConfiguration'; import product from 'vs/platform/product/common/product'; +import { Extensions as WorkbenchExtensions, IConfigurationMigrationRegistry, ConfigurationKeyValuePairs } from 'vs/workbench/common/configuration'; const terminalDescriptors = '\n- ' + [ '`\${cwd}`: ' + localize("cwd", "the terminal's current working directory"), @@ -364,7 +365,12 @@ const terminalConfiguration: IConfigurationNode = { default: 'editor' }, [TerminalSettingId.EnableBell]: { - description: localize('terminal.integrated.enableBell', "Controls whether the terminal bell is enabled. This shows up as a visual bell next to the terminal's name."), + markdownDeprecationMessage: localize('terminal.integrated.enableBell', "This is now deprecated. Instead use the `terminal.integrated.enableVisualBell` and `accessibility.signals.terminalBell` settings."), + type: 'boolean', + default: false + }, + [TerminalSettingId.EnableVisualBell]: { + description: localize('terminal.integrated.enableVisualBell', "Controls whether the visual terminal bell is enabled. This shows up next to the terminal's name."), type: 'boolean', default: false }, @@ -660,3 +666,20 @@ export function registerTerminalConfiguration() { const configurationRegistry = Registry.as(Extensions.Configuration); configurationRegistry.registerConfiguration(terminalConfiguration); } + +Registry.as(WorkbenchExtensions.ConfigurationMigration) + .registerConfigurationMigrations([{ + key: TerminalSettingId.EnableBell, + migrateFn: (enableBell, accessor) => { + const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; + configurationKeyValuePairs.push([TerminalSettingId.EnableBell, { value: undefined }]); + let announcement = accessor('accessibility.signals.terminalBell')?.announcement ?? accessor('accessibility.alert.terminalBell'); + if (announcement !== undefined && typeof announcement !== 'string') { + announcement = announcement ? 'auto' : 'off'; + } + configurationKeyValuePairs.push(['accessibility.signals.terminalBell', { value: { sound: enableBell ? 'on' : 'off', announcement } }]); + configurationKeyValuePairs.push([TerminalSettingId.EnableBell, { value: undefined }]); + configurationKeyValuePairs.push([TerminalSettingId.EnableVisualBell, { value: enableBell }]); + return configurationKeyValuePairs; + } + }]); From 4e291018c1fb79a0ebe7f19b521b665ea7af4dea Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 22 Feb 2024 12:39:00 -0800 Subject: [PATCH 1536/1897] fix #206019 --- .../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 1011f2b8743..9a42093991c 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -546,7 +546,7 @@ const configuration: IConfigurationNode = { }, 'accessibility.signals.clear': { ...signalFeatureBase, - 'description': localize('accessibility.signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), + 'description': localize('accessibility.signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel)."), 'properties': { 'sound': { 'description': localize('accessibility.signals.clear.sound', "Plays a sound when a feature is cleared."), From 9e9b9fd43025f08ee637b8b8425c414cf75ac004 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 22 Feb 2024 13:22:04 -0800 Subject: [PATCH 1537/1897] rm duplicated line --- .../workbench/contrib/terminal/common/terminalConfiguration.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 4557474f63e..0df9dc1171c 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -672,7 +672,6 @@ Registry.as(WorkbenchExtensions.ConfigurationMi key: TerminalSettingId.EnableBell, migrateFn: (enableBell, accessor) => { const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; - configurationKeyValuePairs.push([TerminalSettingId.EnableBell, { value: undefined }]); let announcement = accessor('accessibility.signals.terminalBell')?.announcement ?? accessor('accessibility.alert.terminalBell'); if (announcement !== undefined && typeof announcement !== 'string') { announcement = announcement ? 'auto' : 'off'; From 709da0f7b2d481e82f6fd634d74a581979b7e1d8 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 23 Feb 2024 09:39:09 +0100 Subject: [PATCH 1538/1897] setting default enablement state to true --- src/vs/editor/common/config/editorOptions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index f0b1c9e4f8a..bd51c9ec27d 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2811,7 +2811,7 @@ export type EditorStickyScrollOptions = Readonly { constructor() { - const defaults: EditorStickyScrollOptions = { enabled: false, maxLineCount: 5, defaultModel: 'outlineModel', scrollWithEditor: true }; + const defaults: EditorStickyScrollOptions = { enabled: true, maxLineCount: 5, defaultModel: 'outlineModel', scrollWithEditor: true }; super( EditorOption.stickyScroll, 'stickyScroll', defaults, { From 63d967f8c8251c95cf4d14a780b7ca320b007acd Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 23 Feb 2024 09:48:12 +0100 Subject: [PATCH 1539/1897] Fix tooltips in quick pick actions (#206043) fix #206039 --- src/vs/platform/quickinput/browser/quickInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index f23f8447a02..96dfeee7ea8 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -1264,7 +1264,7 @@ export class QuickInputHoverDelegate extends WorkbenchHoverDelegate { @IConfigurationService configurationService: IConfigurationService, @IHoverService hoverService: IHoverService ) { - super('mouse', true, (options) => this.getOverrideOptions(options), configurationService, hoverService); + super('element', false, (options) => this.getOverrideOptions(options), configurationService, hoverService); } private getOverrideOptions(options: IHoverDelegateOptions): Partial { From 0f282e2a59809a32606fb6f2720c82a034c0856e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Fri, 23 Feb 2024 11:16:40 +0100 Subject: [PATCH 1540/1897] Fix fullscreen container dimension detection when not directly on body (#205884) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix container dimension detection when not directly on body * Add some comments Co-authored-by: Benjamin Pasero --------- Co-authored-by: Loïc Mangeonjean Co-authored-by: Benjamin Pasero --- src/vs/workbench/browser/layout.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index a2b6007fb79..797a1713dd8 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -1541,7 +1541,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi layout(): void { if (!this.disposed) { - this._mainContainerDimension = getClientArea(this.parent); + this._mainContainerDimension = getClientArea(this.state.runtime.mainWindowFullscreen ? + mainWindow.document.body : // in fullscreen mode, make sure to use element because + this.parent // in that case the workbench will span the entire site + ); this.logService.trace(`Layout#layout, height: ${this._mainContainerDimension.height}, width: ${this._mainContainerDimension.width}`); position(this.mainContainer, 0, 0, 0, 0, 'relative'); From b60b4d70b7a808a5fcf7b14ba8b9be179ffc9fc6 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 23 Feb 2024 11:20:48 +0100 Subject: [PATCH 1541/1897] doing checks on length and line number --- .../browser/inlineCompletionsModel.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 0be9f26bde9..a35ce5f9108 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -421,6 +421,9 @@ export class InlineCompletionsModel extends Disposable { const partialGhostTextVal = ghostTextVal.substring(0, acceptUntilIndexExclusive); const positions = this._positions.get(); + if (positions.length === 0) { + return; + } const cursorPosition = positions[0]; // Executing the edit might free the completion, so we have to hold a reference on it. @@ -470,6 +473,10 @@ export class InlineCompletionsModel extends Disposable { } export function getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { + if (positions.length === 1) { + // No secondary cursor positions + return []; + } const primaryPosition = positions[0]; const secondaryPositions = positions.slice(1); const primaryEditStartPosition = primaryEdit.range.getStartPosition(); @@ -478,6 +485,9 @@ export function getSecondaryEdits(textModel: ITextModel, positions: readonly Pos Range.fromPositions(primaryPosition, primaryEditEndPosition) ); const positionWithinTextEdit = subtractPositions(primaryPosition, primaryEditStartPosition); + if (positionWithinTextEdit.lineNumber < 1) { + return []; + } const secondaryEditText = substringPos(primaryEdit.text, positionWithinTextEdit); return secondaryPositions.map(pos => { const posEnd = addPositions(subtractPositions(pos, primaryEditStartPosition), primaryEditEndPosition); From 3658a5124f2b9d58cefd62f1826e4e180f7c337d Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 23 Feb 2024 11:33:33 +0100 Subject: [PATCH 1542/1897] More hover adoptions and debt (#205972) * Hover Fixes * Updated hover behavior and accessibility attributes across multiple components --- .../ui/dropdown/dropdownActionViewItem.ts | 2 +- src/vs/base/browser/ui/hover/hoverDelegate.ts | 2 +- .../browser/ui/iconLabel/iconLabelHover.ts | 30 +++++++++-- src/vs/platform/hover/browser/hover.ts | 10 ++-- .../browser/parts/compositeBarActions.ts | 4 +- .../workbench/browser/parts/compositePart.ts | 10 ++-- .../notifications/notificationsViewer.ts | 28 +++++----- .../browser/parts/paneCompositePart.ts | 2 +- .../workbench/browser/parts/views/viewPane.ts | 10 ++-- .../contrib/comments/browser/commentReply.ts | 4 +- .../comments/browser/commentsTreeViewer.ts | 4 +- .../contrib/comments/browser/timestamp.ts | 9 ++-- .../contrib/debug/browser/baseDebugView.ts | 7 +-- .../contrib/debug/browser/breakpointsView.ts | 29 +++++----- .../contrib/debug/browser/callStackView.ts | 29 ++++++---- .../debug/browser/debugActionViewItems.ts | 7 ++- .../contrib/debug/browser/replViewer.ts | 4 +- .../files/browser/views/explorerView.ts | 2 +- .../notebook/browser/notebookEditor.ts | 5 +- .../browser/view/cellParts/cellActionView.ts | 5 +- .../browser/view/cellParts/cellToolbars.ts | 14 +++-- .../viewParts/notebookEditorToolbar.ts | 53 +++++++++++-------- .../browser/viewParts/notebookKernelView.ts | 5 +- .../contrib/remote/browser/tunnelView.ts | 2 +- 24 files changed, 173 insertions(+), 104 deletions(-) diff --git a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index 419658a21bb..995b8f40817 100644 --- a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -93,7 +93,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { this.element.setAttribute('aria-haspopup', 'true'); this.element.setAttribute('aria-expanded', 'false'); if (this._action.label) { - this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.element, this._action.label)); + this._register(setupCustomHover(this.options.hoverDelegate ?? getDefaultHoverDelegate('mouse'), this.element, this._action.label)); } this.element.ariaLabel = this._action.label || ''; diff --git a/src/vs/base/browser/ui/hover/hoverDelegate.ts b/src/vs/base/browser/ui/hover/hoverDelegate.ts index 6682d739c26..6d2dfef371a 100644 --- a/src/vs/base/browser/ui/hover/hoverDelegate.ts +++ b/src/vs/base/browser/ui/hover/hoverDelegate.ts @@ -21,7 +21,7 @@ export function setHoverDelegateFactory(hoverDelegateProvider: ((placement: 'mou } export function getDefaultHoverDelegate(placement: 'mouse' | 'element'): IHoverDelegate; -export function getDefaultHoverDelegate(placement: 'mouse' | 'element', enableInstantHover: true): IScopedHoverDelegate; +export function getDefaultHoverDelegate(placement: 'element', enableInstantHover: true): IScopedHoverDelegate; export function getDefaultHoverDelegate(placement: 'mouse' | 'element', enableInstantHover?: boolean): IHoverDelegate | IScopedHoverDelegate { if (enableInstantHover) { // If instant hover is enabled, the consumer is responsible for disposing the hover delegate diff --git a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts index 20ad4662b0d..bdcdfa7c7da 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts @@ -163,9 +163,24 @@ class UpdatableHoverWidget implements IDisposable { } } -export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, content: IHoverContentOrFactory, options?: IUpdatableHoverOptions): ICustomHover { - let hoverPreparation: IDisposable | undefined; +function getHoverTargetElement(element: HTMLElement, stopElement?: HTMLElement): HTMLElement { + stopElement = stopElement ?? dom.getWindow(element).document.body; + while (!element.hasAttribute('custom-hover') && element !== stopElement) { + element = element.parentElement!; + } + return element; +} +export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, content: IHoverContentOrFactory, options?: IUpdatableHoverOptions): ICustomHover { + htmlElement.setAttribute('custom-hover', 'true'); + + if (htmlElement.title !== '') { + console.warn('HTML element already has a title attribute, which will conflict with the custom hover. Please remove the title attribute.'); + console.trace('Stack trace:', htmlElement.title); + htmlElement.title = ''; + } + + let hoverPreparation: IDisposable | undefined; let hoverWidget: UpdatableHoverWidget | undefined; const hideHover = (disposeWidget: boolean, disposePreparation: boolean) => { @@ -206,7 +221,7 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM hideHover(false, (e).fromElement === htmlElement); }, true); - const onMouseOver = () => { + const onMouseOver = (e: MouseEvent) => { if (hoverPreparation) { return; } @@ -221,15 +236,20 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM // track the mouse position const onMouseMove = (e: MouseEvent) => { target.x = e.x + 10; - if ((e.target instanceof HTMLElement) && e.target.classList.contains('action-label')) { + if ((e.target instanceof HTMLElement) && getHoverTargetElement(e.target, htmlElement) !== htmlElement) { hideHover(true, true); } }; toDispose.add(dom.addDisposableListener(htmlElement, dom.EventType.MOUSE_MOVE, onMouseMove, true)); } - toDispose.add(triggerShowHover(hoverDelegate.delay, false, target)); hoverPreparation = toDispose; + + if ((e.target instanceof HTMLElement) && getHoverTargetElement(e.target as HTMLElement, htmlElement) !== htmlElement) { + return; // Do not show hover when the mouse is over another hover target + } + + toDispose.add(triggerShowHover(hoverDelegate.delay, false, target)); }; const mouseOverDomEmitter = dom.addDisposableListener(htmlElement, dom.EventType.MOUSE_OVER, onMouseOver, true); diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index c9edff5d2a3..82d9574ca06 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -282,14 +282,18 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate return this.hoverService.showHover({ ...options, persistence: { - hideOnHover: true + hideOnHover: true, + hideOnKeyDown: true, }, ...overrideOptions }, focus); } - setOptions(options: Partial | ((options: IHoverDelegateOptions, focus?: boolean) => Partial)): void { - this.overrideOptions = options; + setInstantHoverTimeLimit(timeLimit: number): void { + if (!this.instantHover) { + throw new Error('Instant hover is not enabled'); + } + this.timeLimit = timeLimit; } onDidHideHover(): void { diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index fb06c28c67d..cfa2d348aa0 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -706,12 +706,12 @@ export class CompositeActionViewItem extends CompositeBarActionViewItem { protected override updateChecked(): void { if (this.action.checked) { this.container.classList.add('checked'); - this.container.setAttribute('aria-label', this.container.title); + this.container.setAttribute('aria-label', this.getTooltip() ?? this.container.title); this.container.setAttribute('aria-expanded', 'true'); this.container.setAttribute('aria-selected', 'true'); } else { this.container.classList.remove('checked'); - this.container.setAttribute('aria-label', this.container.title); + this.container.setAttribute('aria-label', this.getTooltip() ?? this.container.title); this.container.setAttribute('aria-expanded', 'false'); this.container.setAttribute('aria-selected', 'false'); } diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index b4d406a6364..81a034ffa91 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -35,6 +35,7 @@ import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; export interface ICompositeTitleLabel { @@ -62,7 +63,7 @@ export abstract class CompositePart extends Part { protected toolBar: WorkbenchToolBar | undefined; protected titleLabelElement: HTMLElement | undefined; - protected readonly hoverDelegate: IHoverDelegate; + protected readonly toolbarHoverDelegate: IHoverDelegate; private readonly mapCompositeToCompositeContainer = new Map(); private readonly mapActionsBindingToComposite = new Map void>(); @@ -96,7 +97,7 @@ export abstract class CompositePart extends Part { super(id, options, themeService, storageService, layoutService); this.lastActiveCompositeId = storageService.get(activeCompositeSettingsKey, StorageScope.WORKSPACE, this.defaultCompositeId); - this.hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + this.toolbarHoverDelegate = this._register(getDefaultHoverDelegate('element', true)); } protected openComposite(id: string, focus?: boolean): Composite | undefined { @@ -407,7 +408,7 @@ export abstract class CompositePart extends Part { anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment(), toggleMenuTitle: localize('viewsAndMoreActions', "Views and More Actions..."), telemetrySource: this.nameForTelemetry, - hoverDelegate: this.hoverDelegate + hoverDelegate: this.toolbarHoverDelegate })); this.collectCompositeActions()(); @@ -419,6 +420,7 @@ export abstract class CompositePart extends Part { const titleContainer = append(parent, $('.title-label')); const titleLabel = append(titleContainer, $('h2')); this.titleLabelElement = titleLabel; + const hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), titleLabel, '')); const $this = this; return { @@ -426,7 +428,7 @@ export abstract class CompositePart extends Part { // The title label is shared for all composites in the base CompositePart if (!this.activeComposite || this.activeComposite.getId() === id) { titleLabel.innerText = title; - titleLabel.title = keybinding ? localize('titleTooltip', "{0} ({1})", title, keybinding) : title; + hover.update(keybinding ? localize('titleTooltip', "{0} ({1})", title, keybinding) : title); } }, diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 484ee51adb4..e66dc53ad61 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -29,6 +29,8 @@ import { Event } from 'vs/base/common/event'; import { defaultButtonStyles, defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export class NotificationsListDelegate implements IListVirtualDelegate { @@ -235,7 +237,7 @@ export class NotificationRenderer implements IListRenderer { + actionViewItemProvider: (action, options) => { if (action instanceof ConfigureNotificationAction) { return data.toDispose.add(new DropdownMenuActionViewItem(action, { getActions() { @@ -262,6 +264,7 @@ export class NotificationRenderer implements IListRenderer this.openerService.open(URI.parse(link), { allowCommands: true }), @@ -425,11 +430,8 @@ export class NotificationTemplateRenderer extends Disposable { })); const messageOverflows = notification.canCollapse && !notification.expanded && this.template.message.scrollWidth > this.template.message.clientWidth; - if (messageOverflows) { - this.template.message.title = this.template.message.textContent + ''; - } else { - this.template.message.removeAttribute('title'); - } + + customHover.update(messageOverflows ? this.template.message.textContent + '' : ''); return messageOverflows; } @@ -470,13 +472,13 @@ export class NotificationTemplateRenderer extends Disposable { actions.forEach(action => this.template.toolbar.push(action, { icon: true, label: false, keybinding: this.getKeybindingLabel(action) })); } - private renderSource(notification: INotificationViewItem): void { + private renderSource(notification: INotificationViewItem, sourceCustomHover: ICustomHover): void { if (notification.expanded && notification.source) { this.template.source.textContent = localize('notificationSource', "Source: {0}", notification.source); - this.template.source.title = notification.source; + sourceCustomHover.update(notification.source); } else { this.template.source.textContent = ''; - this.template.source.removeAttribute('title'); + sourceCustomHover.update(''); } } diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index 6ef4dfe09a3..3a1cb5382e4 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -307,7 +307,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart this.keybindingService.lookupKeybinding(action.id), anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment(), toggleMenuTitle: localize('moreActions', "More Actions..."), - hoverDelegate: this.hoverDelegate + hoverDelegate: this.toolbarHoverDelegate })); this.updateGlobalToolbarActions(); diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index 978baf437cd..9cc1b35c354 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -47,6 +47,8 @@ import { FilterWidget, IFilterWidgetOptions } from 'vs/workbench/browser/parts/v import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { defaultButtonStyles, defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export enum ViewPaneShowActions { /** Show the actions when the view is hovered. This is the default behavior. */ @@ -342,6 +344,7 @@ export abstract class ViewPane extends Pane implements IView { private titleContainer?: HTMLElement; private titleDescriptionContainer?: HTMLElement; private iconContainer?: HTMLElement; + private iconContainerHover?: ICustomHover; protected twistiesContainer?: HTMLElement; private viewWelcomeController!: ViewWelcomeController; @@ -519,13 +522,14 @@ export abstract class ViewPane extends Pane implements IView { } const calculatedTitle = this.calculateTitle(title); - this.titleContainer = append(container, $('h3.title', { title: calculatedTitle }, calculatedTitle)); + this.titleContainer = append(container, $('h3.title', {}, calculatedTitle)); + setupCustomHover(getDefaultHoverDelegate('mouse'), this.titleContainer, calculatedTitle); if (this._titleDescription) { this.setTitleDescription(this._titleDescription); } - this.iconContainer.title = calculatedTitle; + this.iconContainerHover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.iconContainer, calculatedTitle)); this.iconContainer.setAttribute('aria-label', calculatedTitle); } @@ -537,7 +541,7 @@ export abstract class ViewPane extends Pane implements IView { } if (this.iconContainer) { - this.iconContainer.title = calculatedTitle; + this.iconContainerHover?.update(calculatedTitle); this.iconContainer.setAttribute('aria-label', calculatedTitle); } diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts index 21aeb3f22d5..4d2a9e4b887 100644 --- a/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -30,6 +30,8 @@ import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/comme import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { LayoutableEditor, MIN_EDITOR_HEIGHT, SimpleCommentEditor, calculateEditorHeight } from './simpleCommentEditor'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; const COMMENT_SCHEME = 'comment'; let INMEM_MODEL_ID = 0; @@ -355,7 +357,7 @@ export class CommentReply extends Disposable { private createReplyButton(commentEditor: ICodeEditor, commentForm: HTMLElement) { this._reviewThreadReplyButton = dom.append(commentForm, dom.$(`button.review-thread-reply-button.${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`)); - this._reviewThreadReplyButton.title = this._commentOptions?.prompt || nls.localize('reply', "Reply..."); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this._reviewThreadReplyButton, this._commentOptions?.prompt || nls.localize('reply', "Reply..."))); this._reviewThreadReplyButton.textContent = this._commentOptions?.prompt || nls.localize('reply', "Reply..."); // bind click/escape actions for reviewThreadReplyButton and textArea diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 69ffc3d3f6d..585d253442c 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -32,6 +32,8 @@ import { IStyleOverride } from 'vs/platform/theme/browser/defaultStyles'; import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { CommentsModel } from 'vs/workbench/contrib/comments/browser/commentsModel'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export const COMMENTS_VIEW_ID = 'workbench.panel.comments'; export const COMMENTS_VIEW_STORAGE_ID = 'Comments'; @@ -219,7 +221,7 @@ export class CommentNodeRenderer implements IListRenderer const renderedComment = this.getRenderedComment(originalComment.comment.body, disposables); templateData.disposables.push(renderedComment); templateData.threadMetadata.commentPreview.appendChild(renderedComment.element.firstElementChild ?? renderedComment.element); - templateData.threadMetadata.commentPreview.title = renderedComment.element.textContent ?? ''; + templateData.disposables.push(setupCustomHover(getDefaultHoverDelegate('mouse'), templateData.threadMetadata.commentPreview, renderedComment.element.textContent ?? '')); } if (node.element.range) { diff --git a/src/vs/workbench/contrib/comments/browser/timestamp.ts b/src/vs/workbench/contrib/comments/browser/timestamp.ts index 2b9f79d4a88..2d1fcf15b48 100644 --- a/src/vs/workbench/contrib/comments/browser/timestamp.ts +++ b/src/vs/workbench/contrib/comments/browser/timestamp.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { fromNow } from 'vs/base/common/date'; import { Disposable } from 'vs/base/common/lifecycle'; import { language } from 'vs/base/common/platform'; @@ -15,12 +17,15 @@ export class TimestampWidget extends Disposable { private _timestamp: Date | undefined; private _useRelativeTime: boolean; + private hover: ICustomHover; + constructor(private configurationService: IConfigurationService, container: HTMLElement, timeStamp?: Date) { super(); this._date = dom.append(container, dom.$('span.timestamp')); this._date.style.display = 'none'; this._useRelativeTime = this.useRelativeTimeSetting; this.setTimestamp(timeStamp); + this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this._date, '')); } private get useRelativeTimeSetting(): boolean { @@ -52,9 +57,7 @@ export class TimestampWidget extends Disposable { } this._date.textContent = textContent; - if (tooltip) { - this._date.title = tooltip; - } + this.hover.update(tooltip ?? ''); } } diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index ac626b82f40..90aba19a7f2 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -7,6 +7,8 @@ import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { IAsyncDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Codicon } from 'vs/base/common/codicons'; @@ -187,19 +189,18 @@ export abstract class AbstractExpressionsRenderer implements IT abstract get templateId(): string; renderTemplate(container: HTMLElement): IExpressionTemplateData { + const templateDisposable = new DisposableStore(); const expression = dom.append(container, $('.expression')); const name = dom.append(expression, $('span.name')); const lazyButton = dom.append(expression, $('span.lazy-button')); lazyButton.classList.add(...ThemeIcon.asClassNameArray(Codicon.eye)); - lazyButton.title = localize('debug.lazyButton.tooltip', "Click to expand"); + templateDisposable.add(setupCustomHover(getDefaultHoverDelegate('mouse'), lazyButton, localize('debug.lazyButton.tooltip', "Click to expand"))); const value = dom.append(expression, $('span.value')); const label = new HighlightedLabel(name); const inputBoxContainer = dom.append(expression, $('.inputBoxContainer')); - const templateDisposable = new DisposableStore(); - let actionBar: ActionBar | undefined; if (this.renderActionBar) { dom.append(expression, $('.span.actionbar-spacer')); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index e60c8079a14..5de52259df4 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -8,7 +8,9 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Gesture } from 'vs/base/browser/touch'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { AriaRole } from 'vs/base/browser/ui/aria/aria'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { IListContextMenuEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; @@ -549,7 +551,7 @@ class BreakpointsRenderer implements IListRenderer t.stopped); @@ -603,11 +608,11 @@ class SessionsRenderer implements ICompressibleTreeRenderer, _index: number, data: IThreadTemplateData): void { const thread = element.element; - data.thread.title = thread.name; + data.elementDisposable.add(setupCustomHover(getDefaultHoverDelegate('mouse'), data.thread, thread.name)); data.label.set(thread.name, createMatches(element.filterData)); data.stateLabel.textContent = thread.stateLabel; data.stateLabel.classList.toggle('exception', thread.stoppedDetails?.reason === 'exception'); @@ -743,10 +748,12 @@ class StackFramesRenderer implements ICompressibleTreeRenderer, index: number, data: IErrorTemplateData): void { const error = element.element; data.label.textContent = error; - data.label.title = error; + data.templateDisposable.add(setupCustomHover(getDefaultHoverDelegate('mouse'), data.label, error)); } renderCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: IErrorTemplateData, height: number | undefined): void { diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 433a562e4bf..2f31d9c5aaf 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -22,6 +22,8 @@ import { BaseActionViewItem, IBaseActionViewItemOptions, SelectActionViewItem } import { debugStart } from 'vs/workbench/contrib/debug/browser/debugIcons'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; const $ = dom.$; @@ -74,9 +76,10 @@ export class StartDebugActionViewItem extends BaseActionViewItem { this.start = dom.append(container, $(ThemeIcon.asCSSSelector(debugStart))); const keybinding = this.keybindingService.lookupKeybinding(this.action.id)?.getLabel(); const keybindingLabel = keybinding ? ` (${keybinding})` : ''; - this.start.title = this.action.label + keybindingLabel; + const title = this.action.label + keybindingLabel; + this.toDispose.push(setupCustomHover(getDefaultHoverDelegate('mouse'), this.start, title)); this.start.setAttribute('role', 'button'); - this.start.ariaLabel = this.start.title; + this.start.ariaLabel = title; this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.CLICK, () => { this.start.blur(); diff --git a/src/vs/workbench/contrib/debug/browser/replViewer.ts b/src/vs/workbench/contrib/debug/browser/replViewer.ts index 1ba443a95f1..64c3f3d6ccd 100644 --- a/src/vs/workbench/contrib/debug/browser/replViewer.ts +++ b/src/vs/workbench/contrib/debug/browser/replViewer.ts @@ -28,6 +28,8 @@ import { IDebugConfiguration, IDebugService, IDebugSession, IExpression, IExpres import { Variable } from 'vs/workbench/contrib/debug/common/debugModel'; import { RawObjectReplElement, ReplEvaluationInput, ReplEvaluationResult, ReplGroup, ReplOutputElement, ReplVariableElement } from 'vs/workbench/contrib/debug/common/replModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; const $ = dom.$; @@ -199,7 +201,7 @@ export class ReplOutputElementRenderer implements ITreeRenderer element.sourceData; } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 71960988d83..d7e695e1958 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -276,7 +276,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { const workspace = this.contextService.getWorkspace(); const title = workspace.folders.map(folder => folder.name).join(); titleElement.textContent = this.name; - titleElement.title = title; + this.updateTitle(title); this.ariaHeaderLabel = nls.localize('explorerSection', "Explorer Section: {0}", this.name); titleElement.setAttribute('aria-label', this.ariaHeaderLabel); }; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 10ae44a781d..0a4cc62f4ed 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -47,6 +47,7 @@ import { streamToBuffer } from 'vs/base/common/buffer'; import { ILogService } from 'vs/platform/log/common/log'; import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState'; @@ -137,10 +138,10 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { this._rootElement.id = `notebook-editor-element-${generateUuid()}`; } - override getActionViewItem(action: IAction): IActionViewItem | undefined { + override getActionViewItem(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined { if (action.id === SELECT_KERNEL_ID) { // this is being disposed by the consumer - return this._instantiationService.createInstance(NotebooKernelActionViewItem, action, this); + return this._instantiationService.createInstance(NotebooKernelActionViewItem, action, this, options); } return undefined; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts index c2c7f3e740a..35007bedeb8 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts @@ -57,7 +57,7 @@ export class UnifiedSubmenuActionView extends SubmenuEntryActionViewItem { @IContextMenuService _contextMenuService: IContextMenuService, @IThemeService _themeService: IThemeService ) { - super(action, options, _keybindingService, _contextMenuService, _themeService); + super(action, { ...options, hoverDelegate: options?.hoverDelegate ?? getDefaultHoverDelegate('element') }, _keybindingService, _contextMenuService, _themeService); } override render(container: HTMLElement): void { @@ -66,8 +66,7 @@ export class UnifiedSubmenuActionView extends SubmenuEntryActionViewItem { this._actionLabel = document.createElement('a'); container.appendChild(this._actionLabel); - const hoverDelegate = this.options.hoverDelegate ?? getDefaultHoverDelegate('element'); - this._hover = this._register(setupCustomHover(hoverDelegate, this._actionLabel, '')); + this._hover = this._register(setupCustomHover(this.options.hoverDelegate ?? getDefaultHoverDelegate('element'), this._actionLabel, '')); this.updateLabel(); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts index a7b79d85684..12b86db201e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts @@ -22,6 +22,8 @@ import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/vie import { CellOverlayPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; import { registerCellToolbarStickyScroll } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbarStickyScroll'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; export class BetweenCellToolbar extends CellOverlayPart { private _betweenCellToolbar: ToolBar | undefined; @@ -165,15 +167,16 @@ export class CellTitleToolbarPart extends CellOverlayPart { if (this._view) { return this._view; } - + const hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); const toolbar = this._register(this.instantiationService.createInstance(WorkbenchToolBar, this.toolbarContainer, { actionViewItemProvider: (action, options) => { return createActionViewItem(this.instantiationService, action, options); }, - renderDropdownAsChildElement: true + renderDropdownAsChildElement: true, + hoverDelegate })); - const deleteToolbar = this._register(this.instantiationService.invokeFunction(accessor => createDeleteToolbar(accessor, this.toolbarContainer, 'cell-delete-toolbar'))); + const deleteToolbar = this._register(this.instantiationService.invokeFunction(accessor => createDeleteToolbar(accessor, this.toolbarContainer, hoverDelegate, 'cell-delete-toolbar'))); if (model.deleteActions.primary.length !== 0 || model.deleteActions.secondary.length !== 0) { deleteToolbar.setActions(model.deleteActions.primary, model.deleteActions.secondary); } @@ -269,7 +272,7 @@ function getCellToolbarActions(menu: IMenu): { primary: IAction[]; secondary: IA return result; } -function createDeleteToolbar(accessor: ServicesAccessor, container: HTMLElement, elementClass?: string): ToolBar { +function createDeleteToolbar(accessor: ServicesAccessor, container: HTMLElement, hoverDelegate: IHoverDelegate, elementClass?: string): ToolBar { const contextMenuService = accessor.get(IContextMenuService); const keybindingService = accessor.get(IKeybindingService); const instantiationService = accessor.get(IInstantiationService); @@ -278,7 +281,8 @@ function createDeleteToolbar(accessor: ServicesAccessor, container: HTMLElement, actionViewItemProvider: (action, options) => { return createActionViewItem(instantiationService, action, options); }, - renderDropdownAsChildElement: true + renderDropdownAsChildElement: true, + hoverDelegate }); if (elementClass) { diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts index e2972e82e8a..969127bc917 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts @@ -28,6 +28,8 @@ import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookO import { IActionViewItem, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { disposableTimeout } from 'vs/base/common/async'; import { HiddenItemStrategy, IWorkbenchToolBarOptions, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover'; interface IActionModel { action: IAction; @@ -75,18 +77,18 @@ class WorkbenchAlwaysLabelStrategy implements IActionLayoutStrategy { readonly goToMenu: IMenu, readonly instantiationService: IInstantiationService) { } - actionProvider(action: IAction): IActionViewItem | undefined { + actionProvider(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined { if (action.id === SELECT_KERNEL_ID) { // this is being disposed by the consumer - return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor); + return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor, options); } if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(ActionViewWithLabel, action, undefined); + return this.instantiationService.createInstance(ActionViewWithLabel, action, { hoverDelegate: options.hoverDelegate }); } if (action instanceof SubmenuItemAction && action.item.submenu.id === MenuId.NotebookCellExecuteGoTo.id) { - return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, undefined, true, { + return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, { hoverDelegate: options.hoverDelegate }, true, { getActions: () => { return this.goToMenu.getActions().find(([group]) => group === 'navigation/execute')?.[1] ?? []; } @@ -115,25 +117,25 @@ class WorkbenchNeverLabelStrategy implements IActionLayoutStrategy { readonly goToMenu: IMenu, readonly instantiationService: IInstantiationService) { } - actionProvider(action: IAction): IActionViewItem | undefined { + actionProvider(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined { if (action.id === SELECT_KERNEL_ID) { // this is being disposed by the consumer - return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor); + return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor, options); } if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined); + return this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }); } if (action instanceof SubmenuItemAction) { if (action.item.submenu.id === MenuId.NotebookCellExecuteGoTo.id) { - return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, undefined, false, { + return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, { hoverDelegate: options.hoverDelegate }, false, { getActions: () => { return this.goToMenu.getActions().find(([group]) => group === 'navigation/execute')?.[1] ?? []; } }, this.actionProvider.bind(this)); } else { - return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, undefined); + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }); } } @@ -159,20 +161,20 @@ class WorkbenchDynamicLabelStrategy implements IActionLayoutStrategy { readonly goToMenu: IMenu, readonly instantiationService: IInstantiationService) { } - actionProvider(action: IAction): IActionViewItem | undefined { + actionProvider(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined { if (action.id === SELECT_KERNEL_ID) { // this is being disposed by the consumer - return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor); + return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor, options); } const a = this.editorToolbar.primaryActions.find(a => a.action.id === action.id); if (!a || a.renderLabel) { if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(ActionViewWithLabel, action, undefined); + return this.instantiationService.createInstance(ActionViewWithLabel, action, { hoverDelegate: options.hoverDelegate }); } if (action instanceof SubmenuItemAction && action.item.submenu.id === MenuId.NotebookCellExecuteGoTo.id) { - return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, undefined, true, { + return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, { hoverDelegate: options.hoverDelegate }, true, { getActions: () => { return this.goToMenu.getActions().find(([group]) => group === 'navigation/execute')?.[1] ?? []; } @@ -182,18 +184,18 @@ class WorkbenchDynamicLabelStrategy implements IActionLayoutStrategy { return undefined; } else { if (action instanceof MenuItemAction) { - this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined); + this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }); } if (action instanceof SubmenuItemAction) { if (action.item.submenu.id === MenuId.NotebookCellExecuteGoTo.id) { - return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, undefined, false, { + return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, { hoverDelegate: options.hoverDelegate }, false, { getActions: () => { return this.goToMenu.getActions().find(([group]) => group === 'navigation/execute')?.[1] ?? []; } }, this.actionProvider.bind(this)); } else { - return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, undefined); + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }); } } @@ -321,24 +323,29 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { notebookEditor: this.notebookEditor }; - const actionProvider = (action: IAction) => { + const actionProvider = (action: IAction, options: IActionViewItemOptions) => { if (action.id === SELECT_KERNEL_ID) { // this is being disposed by the consumer - return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor); + return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor, options); } if (this._renderLabel !== RenderLabel.Never) { const a = this._primaryActions.find(a => a.action.id === action.id); if (a && a.renderLabel) { - return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined; + return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, { hoverDelegate: options.hoverDelegate }) : undefined; } else { - return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined; + return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }) : undefined; } } else { - return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined; + return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }) : undefined; } }; + // Make sure both toolbars have the same hover delegate for instant hover to work + // Due to the elements being further apart than normal toolbars, the default time limit is to short and has to be increased + const hoverDelegate = this._register(this.instantiationService.createInstance(WorkbenchHoverDelegate, 'element', true, {})); + hoverDelegate.setInstantHoverTimeLimit(600); + const leftToolbarOptions: IWorkbenchToolBarOptions = { hiddenItemStrategy: HiddenItemStrategy.RenderInSecondaryGroup, resetMenu: MenuId.NotebookToolbar, @@ -347,6 +354,7 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { }, getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), renderDropdownAsChildElement: true, + hoverDelegate }; this._notebookLeftToolbar = this.instantiationService.createInstance( @@ -363,7 +371,8 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { this._notebookRightToolbar = new ToolBar(this._notebookTopRightToolbarContainer, this.contextMenuService, { getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), actionViewItemProvider: actionProvider, - renderDropdownAsChildElement: true + renderDropdownAsChildElement: true, + hoverDelegate }); this._register(this._notebookRightToolbar); this._notebookRightToolbar.context = context; diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts index 34958f1e7c0..8c3ecd82436 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { ActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { Action, IAction } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; import { localize, localize2 } from 'vs/nls'; @@ -136,13 +136,14 @@ export class NotebooKernelActionViewItem extends ActionViewItem { constructor( actualAction: IAction, private readonly _editor: { onDidChangeModel: Event; textModel: NotebookTextModel | undefined; scopedContextKeyService?: IContextKeyService } | INotebookEditor, + options: IActionViewItemOptions, @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, @INotebookKernelHistoryService private readonly _notebookKernelHistoryService: INotebookKernelHistoryService, ) { super( undefined, new Action('fakeAction', undefined, ThemeIcon.asClassName(selectKernelIcon), true, (event) => actualAction.run(event)), - { label: false, icon: true } + { ...options, label: false, icon: true } ); this._register(_editor.onDidChangeModel(this._update, this)); this._register(_notebookKernelService.onDidAddKernel(this._update, this)); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 102de6b52f8..8b0318487b6 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -355,7 +355,7 @@ class ActionBarRenderer extends Disposable implements ITableRenderer Date: Fri, 23 Feb 2024 11:33:58 +0100 Subject: [PATCH 1543/1897] chore - rename chatProvider-files to languageModel (#206054) chore - rename chatProvider-file to languageModel --- .../api/browser/extensionHost.contribution.ts | 2 +- ...rovider.ts => mainThreadLanguageModels.ts} | 30 ++++++++-------- .../workbench/api/common/extHost.api.impl.ts | 4 +-- .../workbench/api/common/extHost.protocol.ts | 14 ++++---- ...atProvider.ts => extHostLanguageModels.ts} | 16 ++++----- .../api/common/extHostTypeConverters.ts | 2 +- .../contrib/chat/browser/chat.contribution.ts | 4 +-- .../contrib/chat/common/chatServiceImpl.ts | 2 +- .../contrib/chat/common/chatSlashCommands.ts | 2 +- .../{chatProvider.ts => languageModels.ts} | 34 +++++++++---------- 10 files changed, 55 insertions(+), 55 deletions(-) rename src/vs/workbench/api/browser/{mainThreadChatProvider.ts => mainThreadLanguageModels.ts} (86%) rename src/vs/workbench/api/common/{extHostChatProvider.ts => extHostLanguageModels.ts} (93%) rename src/vs/workbench/contrib/chat/common/{chatProvider.ts => languageModels.ts} (62%) diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index c986c67b5e9..2b59166259d 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -17,7 +17,7 @@ import { StatusBarItemsExtensionPoint } from 'vs/workbench/api/browser/statusBar // --- mainThread participants import './mainThreadLocalization'; import './mainThreadBulkEdits'; -import './mainThreadChatProvider'; +import './mainThreadLanguageModels'; import './mainThreadChatAgents2'; import './mainThreadChatVariables'; import './mainThreadCodeInsets'; diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts similarity index 86% rename from src/vs/workbench/api/browser/mainThreadChatProvider.ts rename to src/vs/workbench/api/browser/mainThreadLanguageModels.ts index 4b7665abdb6..6e33d7da99d 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts @@ -11,24 +11,24 @@ 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 { Registry } from 'vs/platform/registry/common/platform'; -import { ExtHostChatProviderShape, ExtHostContext, MainContext, MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol'; -import { IChatResponseProviderMetadata, IChatResponseFragment, IChatProviderService, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { ExtHostLanguageModelsShape, ExtHostContext, MainContext, MainThreadLanguageModelsShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ILanguageModelChatMetadata, IChatResponseFragment, ILanguageModelsService, IChatMessage } from 'vs/workbench/contrib/chat/common/languageModels'; import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationProviderCreateSessionOptions, IAuthenticationService, INTERNAL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication'; import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -@extHostNamedCustomer(MainContext.MainThreadChatProvider) -export class MainThreadChatProvider implements MainThreadChatProviderShape { +@extHostNamedCustomer(MainContext.MainThreadLanguageModels) +export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { - private readonly _proxy: ExtHostChatProviderShape; + private readonly _proxy: ExtHostLanguageModelsShape; private readonly _store = new DisposableStore(); private readonly _providerRegistrations = new DisposableMap(); private readonly _pendingProgress = new Map>(); constructor( extHostContext: IExtHostContext, - @IChatProviderService private readonly _chatProviderService: IChatProviderService, + @ILanguageModelsService private readonly _chatProviderService: ILanguageModelsService, @IExtensionFeaturesManagementService private readonly _extensionFeaturesManagementService: IExtensionFeaturesManagementService, @ILogService private readonly _logService: ILogService, @IAuthenticationService private readonly _authenticationService: IAuthenticationService, @@ -36,8 +36,8 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatProvider); - this._proxy.$updateLanguageModels({ added: _chatProviderService.getProviders() }); - this._store.add(_chatProviderService.onDidChangeProviders(this._proxy.$updateLanguageModels, this._proxy)); + this._proxy.$updateLanguageModels({ added: _chatProviderService.getLanguageModelIds() }); + this._store.add(_chatProviderService.onDidChangeLanguageModels(this._proxy.$updateLanguageModels, this._proxy)); } dispose(): void { @@ -45,9 +45,9 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { this._store.dispose(); } - $registerProvider(handle: number, identifier: string, metadata: IChatResponseProviderMetadata): void { + $registerLanguageModelProvider(handle: number, identifier: string, metadata: ILanguageModelChatMetadata): void { const dipsosables = new DisposableStore(); - dipsosables.add(this._chatProviderService.registerChatResponseProvider(identifier, { + dipsosables.add(this._chatProviderService.registerLanguageModelChat(identifier, { metadata, provideChatResponse: async (messages, from, options, progress, token) => { const requestId = (Math.random() * 1e6) | 0; @@ -80,10 +80,10 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { this._providerRegistrations.deleteAndDispose(handle); } - async $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise { + async $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise { const activate = this._extensionService.activateByEvent(`onLanguageModelAccess:${providerId}`); - const metadata = this._chatProviderService.lookupChatResponseProvider(providerId); + const metadata = this._chatProviderService.lookupLanguageModel(providerId); if (metadata) { return metadata; @@ -91,10 +91,10 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { await Promise.race([ activate, - Event.toPromise(Event.filter(this._chatProviderService.onDidChangeProviders, e => Boolean(e.added?.includes(providerId)))) + Event.toPromise(Event.filter(this._chatProviderService.onDidChangeLanguageModels, e => Boolean(e.added?.includes(providerId)))) ]); - return this._chatProviderService.lookupChatResponseProvider(providerId); + return this._chatProviderService.lookupLanguageModel(providerId); } async $fetchResponse(extension: ExtensionIdentifier, providerId: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise { @@ -102,7 +102,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { this._logService.debug('[CHAT] extension request STARTED', extension.value, requestId); - const task = this._chatProviderService.fetchChatResponse(providerId, extension, messages, options, new Progress(value => { + const task = this._chatProviderService.makeLanguageModelChatRequest(providerId, extension, messages, options, new Progress(value => { this._proxy.$handleResponseFragment(requestId, value); }), token); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 71ec5250fb9..f89c741dd49 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -29,7 +29,7 @@ import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentica import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits'; import { ExtHostChat } from 'vs/workbench/api/common/extHostChat'; import { ExtHostChatAgents2 } from 'vs/workbench/api/common/extHostChatAgents2'; -import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; +import { ExtHostLanguageModels } from 'vs/workbench/api/common/extHostLanguageModels'; import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariables'; import { ExtHostClipboard } from 'vs/workbench/api/common/extHostClipboard'; import { ExtHostEditorInsets } from 'vs/workbench/api/common/extHostCodeInsets'; @@ -207,7 +207,7 @@ 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, extHostAuthentication)); + const extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostLanguageModels(rpcProtocol, extHostLogService, extHostAuthentication)); const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostLogService, extHostCommands)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5c18ab8657a..01c49f8d261 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -52,7 +52,7 @@ import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata } from 'vs/workbench/contrib/chat/common/languageModels'; import { IChatDynamicRequest, IChatProgress, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue, IChatVariableData, IChatVariableResolverProgress } from 'vs/workbench/contrib/chat/common/chatVariables'; import { DebugConfigurationProviderTriggerKind, MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; @@ -1178,16 +1178,16 @@ export interface ExtHostSpeechShape { $cancelKeywordRecognitionSession(session: number): Promise; } -export interface MainThreadChatProviderShape extends IDisposable { - $registerProvider(handle: number, identifier: string, metadata: IChatResponseProviderMetadata): void; +export interface MainThreadLanguageModelsShape extends IDisposable { + $registerLanguageModelProvider(handle: number, identifier: string, metadata: ILanguageModelChatMetadata): void; $unregisterProvider(handle: number): void; $handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise; - $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise; + $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise; $fetchResponse(extension: ExtensionIdentifier, provider: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise; } -export interface ExtHostChatProviderShape { +export interface ExtHostLanguageModelsShape { $updateLanguageModels(data: { added?: string[]; removed?: string[] }): void; $updateModelAccesslist(data: { from: ExtensionIdentifier; to: ExtensionIdentifier; enabled: boolean }[]): void; $provideLanguageModelResponse(handle: number, requestId: number, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; @@ -2765,7 +2765,7 @@ export interface MainThreadTestingShape { export const MainContext = { MainThreadAuthentication: createProxyIdentifier('MainThreadAuthentication'), MainThreadBulkEdits: createProxyIdentifier('MainThreadBulkEdits'), - MainThreadChatProvider: createProxyIdentifier('MainThreadChatProvider'), + MainThreadLanguageModels: createProxyIdentifier('MainThreadLanguageModels'), MainThreadChatAgents2: createProxyIdentifier('MainThreadChatAgents2'), MainThreadChatVariables: createProxyIdentifier('MainThreadChatVariables'), MainThreadClipboard: createProxyIdentifier('MainThreadClipboard'), @@ -2890,7 +2890,7 @@ export const ExtHostContext = { ExtHostChat: createProxyIdentifier('ExtHostChat'), ExtHostChatAgents2: createProxyIdentifier('ExtHostChatAgents'), ExtHostChatVariables: createProxyIdentifier('ExtHostChatVariables'), - ExtHostChatProvider: createProxyIdentifier('ExtHostChatProvider'), + ExtHostChatProvider: createProxyIdentifier('ExtHostChatProvider'), ExtHostSpeech: createProxyIdentifier('ExtHostSpeech'), ExtHostAiRelatedInformation: createProxyIdentifier('ExtHostAiRelatedInformation'), ExtHostAiEmbeddingVector: createProxyIdentifier('ExtHostAiEmbeddingVector'), diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts similarity index 93% rename from src/vs/workbench/api/common/extHostChatProvider.ts rename to src/vs/workbench/api/common/extHostLanguageModels.ts index 8ad8318f9e1..7baf1d9353d 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -6,11 +6,11 @@ import { CancellationToken, CancellationTokenSource } 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 { ExtHostLanguageModelsShape, IMainContext, MainContext, MainThreadLanguageModelsShape } 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, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata } from 'vs/workbench/contrib/chat/common/languageModels'; import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { AsyncIterableSource } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; @@ -100,11 +100,11 @@ class LanguageModelRequest { } -export class ExtHostChatProvider implements ExtHostChatProviderShape { +export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { private static _idPool = 1; - private readonly _proxy: MainThreadChatProviderShape; + private readonly _proxy: MainThreadLanguageModelsShape; private readonly _onDidChangeModelAccess = new Emitter<{ from: ExtensionIdentifier; to: ExtensionIdentifier }>(); private readonly _onDidChangeProviders = new Emitter(); readonly onDidChangeProviders = this._onDidChangeProviders.event; @@ -120,7 +120,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { private readonly _logService: ILogService, private readonly _extHostAuthentication: ExtHostAuthentication, ) { - this._proxy = mainContext.getProxy(MainContext.MainThreadChatProvider); + this._proxy = mainContext.getProxy(MainContext.MainThreadLanguageModels); } dispose(): void { @@ -130,7 +130,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { registerLanguageModel(extension: IExtensionDescription, identifier: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata): IDisposable { - const handle = ExtHostChatProvider._idPool++; + const handle = ExtHostLanguageModels._idPool++; this._languageModels.set(handle, { extension: extension.identifier, provider }); let auth; if (metadata.auth) { @@ -139,7 +139,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { accountLabel: typeof metadata.auth === 'object' ? metadata.auth.label : undefined }; } - this._proxy.$registerProvider(handle, identifier, { + this._proxy.$registerLanguageModelProvider(handle, identifier, { extension: extension.identifier, model: metadata.name ?? '', auth @@ -299,7 +299,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this.$updateModelAccesslist([{ from: from.identifier, to: to.identifier, enabled: true }]); } - private _isUsingAuth(from: ExtensionIdentifier, toMetadata: IChatResponseProviderMetadata): toMetadata is IChatResponseProviderMetadata & { auth: NonNullable } { + private _isUsingAuth(from: ExtensionIdentifier, toMetadata: ILanguageModelChatMetadata): toMetadata is ILanguageModelChatMetadata & { auth: NonNullable } { // If the 'to' extension uses an auth check return !!toMetadata.auth // And we're asking from a different extension diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index db879caea6b..816f0227361 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -38,7 +38,7 @@ import { getPrivateApiFor } from 'vs/workbench/api/common/extHostTestingPrivateA import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/editor'; import { IViewBadge } from 'vs/workbench/common/views'; import { IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; -import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; +import * as chatProvider from 'vs/workbench/contrib/chat/common/languageModels'; import { IChatCommandButton, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTreeData, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; import { DebugTreeItemCollapsibleState, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index d1cdc123a6b..0d34b49aff8 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -46,7 +46,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { ChatWelcomeMessageModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { IMarkdownString, MarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; -import { ChatProviderService, IChatProviderService } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { LanguageModelsService, ILanguageModelsService } from 'vs/workbench/contrib/chat/common/languageModels'; 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'; @@ -328,7 +328,7 @@ registerSingleton(IChatWidgetService, ChatWidgetService, InstantiationType.Delay registerSingleton(IQuickChatService, QuickChatService, InstantiationType.Delayed); registerSingleton(IChatAccessibilityService, ChatAccessibilityService, InstantiationType.Delayed); registerSingleton(IChatWidgetHistoryService, ChatWidgetHistoryService, InstantiationType.Delayed); -registerSingleton(IChatProviderService, ChatProviderService, InstantiationType.Delayed); +registerSingleton(ILanguageModelsService, LanguageModelsService, InstantiationType.Delayed); registerSingleton(IChatSlashCommandService, ChatSlashCommandService, InstantiationType.Delayed); registerSingleton(IChatAgentService, ChatAgentService, InstantiationType.Delayed); registerSingleton(IChatVariablesService, ChatVariablesService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 89081fe437d..6d89b981afc 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -24,7 +24,7 @@ import { IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workb import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, ISerializableChatData, ISerializableChatsData, getHistoryEntriesFromModel, updateRanges } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/languageModels'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { ChatCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; diff --git a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts index 3d758da3113..2d43f1c0396 100644 --- a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts +++ b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts @@ -8,7 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; 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 { IChatMessage } from 'vs/workbench/contrib/chat/common/languageModels'; import { IChatFollowup, IChatProgress, IChatResponseProgressFileTreeData } from 'vs/workbench/contrib/chat/common/chatService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; diff --git a/src/vs/workbench/contrib/chat/common/chatProvider.ts b/src/vs/workbench/contrib/chat/common/languageModels.ts similarity index 62% rename from src/vs/workbench/contrib/chat/common/chatProvider.ts rename to src/vs/workbench/contrib/chat/common/languageModels.ts index c393a73de98..7f93594aee3 100644 --- a/src/vs/workbench/contrib/chat/common/chatProvider.ts +++ b/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -26,7 +26,7 @@ export interface IChatResponseFragment { part: string; } -export interface IChatResponseProviderMetadata { +export interface ILanguageModelChatMetadata { readonly extension: ExtensionIdentifier; readonly model: string; readonly description?: string; @@ -36,50 +36,50 @@ export interface IChatResponseProviderMetadata { }; } -export interface IChatResponseProvider { - metadata: IChatResponseProviderMetadata; +export interface ILanguageModelChat { + metadata: ILanguageModelChatMetadata; provideChatResponse(messages: IChatMessage[], from: ExtensionIdentifier, options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; } -export const IChatProviderService = createDecorator('chatProviderService'); +export const ILanguageModelsService = createDecorator('ILanguageModelsService'); -export interface IChatProviderService { +export interface ILanguageModelsService { readonly _serviceBrand: undefined; - onDidChangeProviders: Event<{ added?: string[]; removed?: string[] }>; + onDidChangeLanguageModels: Event<{ added?: string[]; removed?: string[] }>; - getProviders(): string[]; + getLanguageModelIds(): string[]; - lookupChatResponseProvider(identifier: string): IChatResponseProviderMetadata | undefined; + lookupLanguageModel(identifier: string): ILanguageModelChatMetadata | undefined; - registerChatResponseProvider(identifier: string, provider: IChatResponseProvider): IDisposable; + registerLanguageModelChat(identifier: string, provider: ILanguageModelChat): IDisposable; - fetchChatResponse(identifier: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; + makeLanguageModelChatRequest(identifier: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; } -export class ChatProviderService implements IChatProviderService { +export class LanguageModelsService implements ILanguageModelsService { readonly _serviceBrand: undefined; - private readonly _providers: Map = new Map(); + private readonly _providers: Map = new Map(); private readonly _onDidChangeProviders = new Emitter<{ added?: string[]; removed?: string[] }>(); - readonly onDidChangeProviders: Event<{ added?: string[]; removed?: string[] }> = this._onDidChangeProviders.event; + readonly onDidChangeLanguageModels: Event<{ added?: string[]; removed?: string[] }> = this._onDidChangeProviders.event; dispose() { this._onDidChangeProviders.dispose(); this._providers.clear(); } - getProviders(): string[] { + getLanguageModelIds(): string[] { return Array.from(this._providers.keys()); } - lookupChatResponseProvider(identifier: string): IChatResponseProviderMetadata | undefined { + lookupLanguageModel(identifier: string): ILanguageModelChatMetadata | undefined { return this._providers.get(identifier)?.metadata; } - registerChatResponseProvider(identifier: string, provider: IChatResponseProvider): IDisposable { + registerLanguageModelChat(identifier: string, provider: ILanguageModelChat): IDisposable { if (this._providers.has(identifier)) { throw new Error(`Chat response provider with identifier ${identifier} is already registered.`); } @@ -92,7 +92,7 @@ export class ChatProviderService implements IChatProviderService { }); } - fetchChatResponse(identifier: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise { + makeLanguageModelChatRequest(identifier: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise { const provider = this._providers.get(identifier); if (!provider) { throw new Error(`Chat response provider with identifier ${identifier} is not registered.`); From 1ad43ea49ad9c3f325a2944d04e2f761a7882587 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 23 Feb 2024 11:39:20 +0100 Subject: [PATCH 1544/1897] adding telemetry when error happens --- .../inlineCompletions/browser/inlineCompletionsModel.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index a35ce5f9108..006c9873c52 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { mapFindFirst } from 'vs/base/common/arraysFind'; -import { BugIndicatingError, onUnexpectedExternalError } from 'vs/base/common/errors'; +import { BugIndicatingError, onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; import { IObservable, IReader, ITransaction, autorun, derived, derivedHandleChanges, derivedOpts, recomputeInitiallyAndOnChange, observableSignal, observableValue, subtransaction, transaction } from 'vs/base/common/observable'; import { commonPrefixLength, splitLinesIncludeSeparators } from 'vs/base/common/strings'; @@ -421,9 +421,6 @@ export class InlineCompletionsModel extends Disposable { const partialGhostTextVal = ghostTextVal.substring(0, acceptUntilIndexExclusive); const positions = this._positions.get(); - if (positions.length === 0) { - return; - } const cursorPosition = positions[0]; // Executing the edit might free the completion, so we have to hold a reference on it. @@ -486,6 +483,10 @@ export function getSecondaryEdits(textModel: ITextModel, positions: readonly Pos ); const positionWithinTextEdit = subtractPositions(primaryPosition, primaryEditStartPosition); if (positionWithinTextEdit.lineNumber < 1) { + onUnexpectedError(new BugIndicatingError( + `positionWithinTextEdit line number should be bigger than 0. + Invalid subtraction between ${primaryPosition.toString()} and ${primaryEditStartPosition.toString()}` + )); return []; } const secondaryEditText = substringPos(primaryEdit.text, positionWithinTextEdit); From 64355fa7f44db5bb01cbcc3bc7bbc704866f1879 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 11:55:32 +0100 Subject: [PATCH 1545/1897] Fixes #195384 (#206004) --- .../browser/widget/diffEditor/diffEditorWidget.ts | 15 +++++---------- .../diffEditor/features/revertButtonsFeature.ts | 12 ++++++++---- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index 6b57de66397..5ad6e0543ef 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -20,10 +20,11 @@ import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/wi import { AccessibleDiffViewer, AccessibleDiffViewerModelFromEditors } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer'; import { DiffEditorDecorations } from 'vs/editor/browser/widget/diffEditor/components/diffEditorDecorations'; import { DiffEditorSash } from 'vs/editor/browser/widget/diffEditor/components/diffEditorSash'; -import { HideUnchangedRegionsFeature } from 'vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature'; import { DiffEditorViewZones } from 'vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones'; +import { HideUnchangedRegionsFeature } from 'vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature'; import { MovedBlocksLinesFeature } from 'vs/editor/browser/widget/diffEditor/features/movedBlocksLinesFeature'; import { OverviewRulerFeature } from 'vs/editor/browser/widget/diffEditor/features/overviewRulerFeature'; +import { RevertButtonsFeature } from 'vs/editor/browser/widget/diffEditor/features/revertButtonsFeature'; import { CSSStyle, ObservableElementSizeObserver, applyStyle, applyViewZones, bindContextKey, readHotReloadableExport, translatePosition } from 'vs/editor/browser/widget/diffEditor/utils'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IDimension } from 'vs/editor/common/core/dimension'; @@ -31,7 +32,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; import { IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; -import { DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; +import { LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { EditorType, IDiffEditorModel, IDiffEditorViewModel, IDiffEditorViewState } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; @@ -40,11 +41,10 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; -import { DelegatingEditor } from './delegatingEditorImpl'; import { DiffEditorEditors } from './components/diffEditorEditors'; +import { DelegatingEditor } from './delegatingEditorImpl'; import { DiffEditorOptions } from './diffEditorOptions'; import { DiffEditorViewModel, DiffMapping, DiffState } from './diffEditorViewModel'; -import { RevertButtonsFeature } from 'vs/editor/browser/widget/diffEditor/features/revertButtonsFeature'; export interface IDiffCodeEditorWidgetOptions { originalEditor?: ICodeEditorWidgetOptions; @@ -477,12 +477,7 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { }; } - revert(diff: DetailedLineRangeMapping): void { - if (diff.innerChanges) { - this.revertRangeMappings(diff.innerChanges); - return; - } - + revert(diff: LineRangeMapping): void { const model = this._diffModel.get(); if (!model || !model.isDiffUpToDate.get()) { return; } diff --git a/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts index c82167c3d13..532786f2da1 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts @@ -15,7 +15,7 @@ import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditor/diffEdi import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { LineRange, LineRangeSet } from 'vs/editor/common/core/lineRange'; import { Range } from 'vs/editor/common/core/range'; -import { RangeMapping } from 'vs/editor/common/diff/rangeMapping'; +import { LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { GlyphMarginLane } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; @@ -62,7 +62,7 @@ export class RevertButtonsFeature extends Disposable { const btn = store.add(new RevertButton( m.lineRangeMapping.modified.startLineNumber, this._widget, - m.lineRangeMapping.innerChanges, + m.lineRangeMapping, false )); this._editors.modified.addGlyphMarginWidget(btn); @@ -122,7 +122,7 @@ export class RevertButton extends Disposable implements IGlyphMarginWidget { constructor( private readonly _lineNumber: number, private readonly _widget: DiffEditorWidget, - private readonly _diffs: RangeMapping[], + private readonly _diffs: RangeMapping[] | LineRangeMapping, private readonly _revertSelection: boolean, ) { super(); @@ -142,7 +142,11 @@ export class RevertButton extends Disposable implements IGlyphMarginWidget { })); this._register(addDisposableListener(this._domNode, EventType.CLICK, (e) => { - this._widget.revertRangeMappings(this._diffs); + if (this._diffs instanceof LineRangeMapping) { + this._widget.revert(this._diffs); + } else { + this._widget.revertRangeMappings(this._diffs); + } e.stopPropagation(); e.preventDefault(); })); From 3363acb98bf941b741e4d11e7ea4764142e6b3f2 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 12:28:04 +0100 Subject: [PATCH 1546/1897] Fixes revealing unchanged code bug (#206065) --- .../diffEditor/features/hideUnchangedRegionsFeature.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts index f2eb90c6d2f..6767a375a3c 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts @@ -349,9 +349,13 @@ class CollapsedCodeOverlayWidget extends ViewZoneOverlayWidget { didMove = didMove || Math.abs(delta) > 2; const lineDelta = Math.round(delta / editor.getOption(EditorOption.lineHeight)); const newVal = Math.max(0, Math.min(cur - lineDelta, this._unchangedRegion.getMaxVisibleLineCountBottom())); - const top = editor.getTopForLineNumber(this._unchangedRegionRange.endLineNumberExclusive); + const top = this._unchangedRegionRange.endLineNumberExclusive > editor.getModel()!.getLineCount() + ? editor.getContentHeight() + : editor.getTopForLineNumber(this._unchangedRegionRange.endLineNumberExclusive); this._unchangedRegion.visibleLineCountBottom.set(newVal, undefined); - const top2 = editor.getTopForLineNumber(this._unchangedRegionRange.endLineNumberExclusive); + const top2 = this._unchangedRegionRange.endLineNumberExclusive > editor.getModel()!.getLineCount() + ? editor.getContentHeight() + : editor.getTopForLineNumber(this._unchangedRegionRange.endLineNumberExclusive); editor.setScrollTop(editor.getScrollTop() + (top2 - top)); }); From 640131f6e32a65b82a65c390c53e868690c469cf Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 23 Feb 2024 13:28:00 +0100 Subject: [PATCH 1547/1897] bumping up the version from package json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f734771d907..1c3d3fc095f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "code-oss-dev", - "version": "1.87.0", + "version": "1.88.0", "distro": "b314654a31bdba8cd2b0c7548e931916d03416bf", "author": { "name": "Microsoft Corporation" From 7127151f30bdba6f87ce18bdd446e26d83b249a4 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 13:46:26 +0100 Subject: [PATCH 1548/1897] Move hoverWidget to hoverService (#206070) --- .../{widget/hoverWidget => services/hoverService}/hover.css | 0 .../editor/browser/services/{ => hoverService}/hoverService.ts | 2 +- .../hoverWidget => services/hoverService}/hoverWidget.ts | 0 src/vs/editor/standalone/browser/standaloneServices.ts | 2 +- src/vs/workbench/workbench.common.main.ts | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) rename src/vs/editor/browser/{widget/hoverWidget => services/hoverService}/hover.css (100%) rename src/vs/editor/browser/services/{ => hoverService}/hoverService.ts (99%) rename src/vs/editor/browser/{widget/hoverWidget => services/hoverService}/hoverWidget.ts (100%) diff --git a/src/vs/editor/browser/widget/hoverWidget/hover.css b/src/vs/editor/browser/services/hoverService/hover.css similarity index 100% rename from src/vs/editor/browser/widget/hoverWidget/hover.css rename to src/vs/editor/browser/services/hoverService/hover.css diff --git a/src/vs/editor/browser/services/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts similarity index 99% rename from src/vs/editor/browser/services/hoverService.ts rename to src/vs/editor/browser/services/hoverService/hoverService.ts index 9dea5dab812..f7338ae7b52 100644 --- a/src/vs/editor/browser/services/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -9,7 +9,7 @@ import { editorHoverBorder } from 'vs/platform/theme/common/colorRegistry'; import { IHoverService, IHoverOptions } from 'vs/platform/hover/browser/hover'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { HoverWidget } from 'vs/editor/browser/widget/hoverWidget/hoverWidget'; +import { HoverWidget } from 'vs/editor/browser/services/hoverService/hoverWidget'; import { IContextViewProvider, IDelegate } from 'vs/base/browser/ui/contextview/contextview'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { addDisposableListener, EventType, getActiveElement, isAncestorOfActiveElement, isAncestor, getWindow } from 'vs/base/browser/dom'; diff --git a/src/vs/editor/browser/widget/hoverWidget/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts similarity index 100% rename from src/vs/editor/browser/widget/hoverWidget/hoverWidget.ts rename to src/vs/editor/browser/services/hoverService/hoverWidget.ts diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 351f85537d8..c815e1b3d13 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -10,7 +10,7 @@ import 'vs/platform/undoRedo/common/undoRedoService'; import 'vs/editor/common/services/languageFeatureDebounce'; import 'vs/editor/common/services/semanticTokensStylingService'; import 'vs/editor/common/services/languageFeaturesService'; -import 'vs/editor/browser/services/hoverService'; +import 'vs/editor/browser/services/hoverService/hoverService'; import * as strings from 'vs/base/common/strings'; import * as dom from 'vs/base/browser/dom'; diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index a7d0a54f6dd..61028e388dd 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -104,7 +104,7 @@ import 'vs/workbench/services/views/browser/viewsService'; import 'vs/workbench/services/quickinput/browser/quickInputService'; import 'vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService'; import 'vs/workbench/services/authentication/browser/authenticationService'; -import 'vs/editor/browser/services/hoverService'; +import 'vs/editor/browser/services/hoverService/hoverService'; import 'vs/workbench/services/assignment/common/assignmentService'; import 'vs/workbench/services/outline/browser/outlineService'; import 'vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl'; From 22508a50008764da21bc5704712c3473d6a566f6 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 14:30:27 +0100 Subject: [PATCH 1549/1897] Moves testDiffProviderFactoryService (#206078) --- .../{ => test}/browser/diff/testDiffProviderFactoryService.ts | 2 +- .../inlineChat/test/browser/inlineChatController.test.ts | 2 +- .../contrib/inlineChat/test/browser/inlineChatSession.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/vs/editor/{ => test}/browser/diff/testDiffProviderFactoryService.ts (95%) diff --git a/src/vs/editor/browser/diff/testDiffProviderFactoryService.ts b/src/vs/editor/test/browser/diff/testDiffProviderFactoryService.ts similarity index 95% rename from src/vs/editor/browser/diff/testDiffProviderFactoryService.ts rename to src/vs/editor/test/browser/diff/testDiffProviderFactoryService.ts index 08ed249b8b9..275c4995988 100644 --- a/src/vs/editor/browser/diff/testDiffProviderFactoryService.ts +++ b/src/vs/editor/test/browser/diff/testDiffProviderFactoryService.ts @@ -18,7 +18,7 @@ export class TestDiffProviderFactoryService implements IDiffProviderFactoryServi } } -export class SyncDocumentDiffProvider implements IDocumentDiffProvider { +class SyncDocumentDiffProvider implements IDocumentDiffProvider { computeDiff(original: ITextModel, modified: ITextModel, options: IDocumentDiffProviderOptions, cancellationToken: CancellationToken): Promise { const result = linesDiffComputers.getDefault().computeDiff(original.getLinesContent(), modified.getLinesContent(), options); return Promise.resolve({ diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 932cb5056b1..f998c0969d7 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -11,7 +11,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { mock } from 'vs/base/test/common/mock'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { TestDiffProviderFactoryService } from 'vs/editor/browser/diff/testDiffProviderFactoryService'; +import { TestDiffProviderFactoryService } from 'vs/editor/test/browser/diff/testDiffProviderFactoryService'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffProviderFactoryService } from 'vs/editor/browser/widget/diffEditor/diffProviderFactoryService'; import { Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts index 53b0cb87519..16556bef773 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { mock } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { TestDiffProviderFactoryService } from 'vs/editor/browser/diff/testDiffProviderFactoryService'; +import { TestDiffProviderFactoryService } from 'vs/editor/test/browser/diff/testDiffProviderFactoryService'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffProviderFactoryService } from 'vs/editor/browser/widget/diffEditor/diffProviderFactoryService'; import { Range } from 'vs/editor/common/core/range'; From 1c16af45f1e08ad444b32ea044e49a129854683a Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 14:34:07 +0100 Subject: [PATCH 1550/1897] multiDiffEditorWidget -> multiDiffEditor (#206075) --- .../{multiDiffEditorWidget => multiDiffEditor}/colors.ts | 0 .../diffEditorItemTemplate.ts | 4 ++-- .../{multiDiffEditorWidget => multiDiffEditor}/model.ts | 0 .../multiDiffEditorViewModel.ts | 2 +- .../multiDiffEditorWidget.ts | 8 ++++---- .../multiDiffEditorWidgetImpl.ts | 4 ++-- .../objectPool.ts | 0 .../{multiDiffEditorWidget => multiDiffEditor}/style.css | 0 .../{multiDiffEditorWidget => multiDiffEditor}/utils.ts | 0 .../workbenchUIElementFactory.ts | 0 src/vs/editor/standalone/browser/standaloneEditor.ts | 2 +- .../contrib/bulkEdit/browser/preview/bulkEditPane.ts | 2 +- .../contrib/multiDiffEditor/browser/multiDiffEditor.ts | 8 ++++---- .../multiDiffEditor/browser/multiDiffEditorInput.ts | 4 ++-- 14 files changed, 17 insertions(+), 17 deletions(-) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/colors.ts (100%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/diffEditorItemTemplate.ts (99%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/model.ts (100%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/multiDiffEditorViewModel.ts (98%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/multiDiffEditorWidget.ts (95%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/multiDiffEditorWidgetImpl.ts (99%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/objectPool.ts (100%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/style.css (100%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/utils.ts (100%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/workbenchUIElementFactory.ts (100%) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/colors.ts b/src/vs/editor/browser/widget/multiDiffEditor/colors.ts similarity index 100% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/colors.ts rename to src/vs/editor/browser/widget/multiDiffEditor/colors.ts diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts similarity index 99% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts rename to src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts index f5433ba99d7..84cffd7e94e 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts @@ -10,8 +10,8 @@ import { autorun, derived, observableFromEvent } from 'vs/base/common/observable import { IObservable, globalTransaction, observableValue } from 'vs/base/common/observableInternal/base'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; -import { DocumentDiffItemViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; -import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory'; +import { DocumentDiffItemViewModel } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel'; +import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts b/src/vs/editor/browser/widget/multiDiffEditor/model.ts similarity index 100% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts rename to src/vs/editor/browser/widget/multiDiffEditor/model.ts diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel.ts similarity index 98% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts rename to src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel.ts index 3a26a82c526..5a225008e03 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel.ts @@ -8,7 +8,7 @@ import { observableFromEvent, observableValue, transaction } from 'vs/base/commo import { mapObservableArrayCached } from 'vs/base/common/observableInternal/utils'; import { DiffEditorOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorOptions'; import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; -import { IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; +import { IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditor/model'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Selection } from 'vs/editor/common/core/selection'; import { IDiffEditorViewModel } from 'vs/editor/common/editorCommon'; diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts similarity index 95% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts rename to src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts index 6867bb84ac7..af73b75af6a 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts @@ -7,13 +7,13 @@ import { Dimension } from 'vs/base/browser/dom'; import { Disposable } from 'vs/base/common/lifecycle'; import { derived, derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; -import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; -import { IMultiDiffEditorViewState, IMultiDiffResource, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditor/model'; +import { IMultiDiffEditorViewState, IMultiDiffResource, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl'; import { MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import './colors'; -import { DiffEditorItemTemplate } from 'vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate'; -import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory'; +import { DiffEditorItemTemplate } from 'vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate'; +import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts similarity index 99% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts rename to src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts index 36a568f2524..4e23c4e6cb6 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts @@ -14,8 +14,8 @@ import { URI } from 'vs/base/common/uri'; import 'vs/css!./style'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ObservableElementSizeObserver } from 'vs/editor/browser/widget/diffEditor/utils'; -import { RevealOptions } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget'; -import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory'; +import { RevealOptions } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget'; +import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { IRange } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/objectPool.ts b/src/vs/editor/browser/widget/multiDiffEditor/objectPool.ts similarity index 100% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/objectPool.ts rename to src/vs/editor/browser/widget/multiDiffEditor/objectPool.ts diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css b/src/vs/editor/browser/widget/multiDiffEditor/style.css similarity index 100% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/style.css rename to src/vs/editor/browser/widget/multiDiffEditor/style.css diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/utils.ts b/src/vs/editor/browser/widget/multiDiffEditor/utils.ts similarity index 100% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/utils.ts rename to src/vs/editor/browser/widget/multiDiffEditor/utils.ts diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory.ts b/src/vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory.ts similarity index 100% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory.ts rename to src/vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory.ts diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 059a4928862..0fa038765af 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -39,7 +39,7 @@ import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IMarker, IMarkerData, IMarkerService } from 'vs/platform/markers/common/markers'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { MultiDiffEditorWidget } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget'; +import { MultiDiffEditorWidget } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget'; /** * Create a new editor under `domElement`. diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 278de48c47c..ba85dee93b2 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -37,7 +37,7 @@ import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; import { IResourceDiffEditorInput } from 'vs/workbench/common/editor'; -import { IMultiDiffEditorOptions } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorOptions } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl'; import { IRange } from 'vs/editor/common/core/range'; const enum State { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index c1491431828..6f66dbec274 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -5,8 +5,8 @@ import * as DOM from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { MultiDiffEditorWidget } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget'; -import { IResourceLabel, IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory'; +import { MultiDiffEditorWidget } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget'; +import { IResourceLabel, IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; @@ -22,8 +22,8 @@ import { MultiDiffEditorInput } from 'vs/workbench/contrib/multiDiffEditor/brows import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { URI } from 'vs/base/common/uri'; -import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; -import { IMultiDiffEditorOptions, IMultiDiffEditorViewState } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel'; +import { IMultiDiffEditorOptions, IMultiDiffEditorViewState } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 058373fd465..4344d015873 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -16,8 +16,8 @@ import { constObservable, mapObservableArrayCached } from 'vs/base/common/observ import { ThemeIcon } from 'vs/base/common/themables'; import { isDefined, isObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { ConstLazyPromise, IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; -import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; +import { ConstLazyPromise, IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditor/model'; +import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; From af1f874650e7d7bde5513d091619f1202aed8e71 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 23 Feb 2024 14:34:41 +0100 Subject: [PATCH 1551/1897] add "Chat" as Category --- src/vs/platform/extensions/common/extensions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 413c1db06f1..62977ee07e3 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -255,6 +255,7 @@ export const EXTENSION_CATEGORIES = [ 'Testing', 'Themes', 'Visualization', + 'Chat', 'Other', ]; From 0bd70d48ad8b3e2fb1922aa54f87c786ff2b4bd8 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 14:35:45 +0100 Subject: [PATCH 1552/1897] code editor move (#206074) --- .../codeEditorContributions.ts | 0 .../{ => codeEditor}/codeEditorWidget.ts | 95 +++++++++---------- .../widget/{media => codeEditor}/editor.css | 0 .../embeddedCodeEditorWidget.ts | 54 +---------- .../components/diffEditorEditors.ts | 2 +- .../diffEditorViewZones.ts | 2 +- .../inlineDiffDeletedCodeMargin.ts | 2 +- .../widget/diffEditor/delegatingEditorImpl.ts | 2 +- .../widget/diffEditor/diffEditorWidget.ts | 2 +- .../diffEditor/embeddedDiffEditorWidget.ts | 55 +++++++++++ .../features/overviewRulerFeature.ts | 2 +- src/vs/editor/contrib/dnd/browser/dnd.ts | 2 +- .../gotoSymbol/browser/goToCommands.ts | 2 +- .../browser/peek/referencesWidget.ts | 2 +- .../contrib/peekView/browser/peekView.ts | 2 +- .../browser/stickyScrollWidget.ts | 2 +- .../contrib/suggest/browser/suggestWidget.ts | 2 +- src/vs/editor/editor.all.ts | 2 +- .../browser/standaloneCodeEditor.ts | 2 +- src/vs/editor/test/browser/testCodeEditor.ts | 2 +- src/vs/workbench/browser/codeeditor.ts | 2 +- .../browser/parts/editor/textCodeEditor.ts | 2 +- .../accessibility/browser/accessibleView.ts | 2 +- .../browser/callHierarchyPeek.ts | 2 +- .../contrib/chat/browser/chatInputPart.ts | 2 +- .../contrib/chat/browser/codeBlockPart.ts | 2 +- .../codeEditor/browser/diffEditorHelper.ts | 2 +- .../codeEditor/browser/simpleEditorOptions.ts | 2 +- .../suggestEnabledInput.ts | 2 +- .../comments/browser/commentsController.ts | 2 +- .../comments/browser/simpleCommentEditor.ts | 2 +- .../contrib/debug/browser/breakpointWidget.ts | 2 +- .../workbench/contrib/debug/browser/repl.ts | 2 +- .../contrib/files/browser/fileCommands.ts | 2 +- .../inlineChat/browser/inlineChatActions.ts | 3 +- .../browser/inlineChatLivePreviewWidget.ts | 3 +- .../inlineChat/browser/inlineChatWidget.ts | 5 +- .../browser/interactive.contribution.ts | 2 +- .../interactive/browser/interactiveEditor.ts | 2 +- .../contrib/mergeEditor/browser/utils.ts | 2 +- .../mergeEditor/browser/view/editorGutter.ts | 2 +- .../browser/view/editors/codeEditorView.ts | 2 +- .../browser/view/scrollSynchronizer.ts | 2 +- .../controller/chat/notebookChatController.ts | 2 +- .../notebook/browser/diff/diffComponents.ts | 2 +- .../browser/diff/notebookDiffEditorBrowser.ts | 2 +- .../notebook/browser/diff/notebookDiffList.ts | 2 +- .../browser/view/cellParts/markupCell.ts | 2 +- .../browser/view/renderers/cellRenderer.ts | 2 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 2 +- .../contrib/scm/browser/scmViewPane.ts | 2 +- .../contrib/search/browser/searchView.ts | 2 +- .../searchEditor/browser/searchEditor.ts | 2 +- .../testing/browser/testingOutputPeek.ts | 5 +- .../browser/typeHierarchyPeek.ts | 2 +- .../browser/walkThroughPart.ts | 2 +- .../dialogs/browser/fileDialogService.ts | 2 +- 57 files changed, 164 insertions(+), 152 deletions(-) rename src/vs/editor/browser/widget/{ => codeEditor}/codeEditorContributions.ts (100%) rename src/vs/editor/browser/widget/{ => codeEditor}/codeEditorWidget.ts (99%) rename src/vs/editor/browser/widget/{media => codeEditor}/editor.css (100%) rename src/vs/editor/browser/widget/{ => codeEditor}/embeddedCodeEditorWidget.ts (59%) create mode 100644 src/vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget.ts diff --git a/src/vs/editor/browser/widget/codeEditorContributions.ts b/src/vs/editor/browser/widget/codeEditor/codeEditorContributions.ts similarity index 100% rename from src/vs/editor/browser/widget/codeEditorContributions.ts rename to src/vs/editor/browser/widget/codeEditor/codeEditorContributions.ts diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts similarity index 99% rename from src/vs/editor/browser/widget/codeEditorWidget.ts rename to src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts index 5466f913d24..39cba20b268 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/editor/browser/services/markerDecorations'; - import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; @@ -14,7 +13,7 @@ import { Emitter, EmitterOptions, Event, EventDeliveryQueue, createEventDelivery import { hash } from 'vs/base/common/hash'; import { Disposable, DisposableStore, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; -import 'vs/css!./media/editor'; +import 'vs/css!./editor'; import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; import { EditorConfiguration, IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { TabFocus } from 'vs/editor/browser/config/tabFocus'; @@ -25,7 +24,7 @@ import { IContentWidgetData, IGlyphMarginWidgetData, IOverlayWidgetData, View } import { DOMLineBreaksComputerFactory } from 'vs/editor/browser/view/domLineBreaksComputer'; import { ICommandDelegate } from 'vs/editor/browser/view/viewController'; import { ViewUserInputEvents } from 'vs/editor/browser/view/viewUserInputEvents'; -import { CodeEditorContributions } from 'vs/editor/browser/widget/codeEditorContributions'; +import { CodeEditorContributions } from 'vs/editor/browser/widget/codeEditor/codeEditorContributions'; import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration'; import { ConfigurationChangedEvent, EditorLayoutInfo, EditorOption, FindComputedEditorOptionValueById, IComputedEditorOptions, IEditorOptions, filterValidationDecorations } from 'vs/editor/common/config/editorOptions'; import { CursorColumns } from 'vs/editor/common/core/cursorColumns'; @@ -61,51 +60,6 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { editorErrorForeground, editorHintForeground, editorInfoForeground, editorWarningForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -let EDITOR_ID = 0; - -export interface ICodeEditorWidgetOptions { - /** - * Is this a simple widget (not a real code editor)? - * Defaults to false. - */ - isSimpleWidget?: boolean; - - /** - * Contributions to instantiate. - * When provided, only the contributions included will be instantiated. - * To include the defaults, those must be provided as well via [...EditorExtensionsRegistry.getEditorContributions()] - * Defaults to EditorExtensionsRegistry.getEditorContributions(). - */ - contributions?: IEditorContributionDescription[]; - - /** - * Telemetry data associated with this CodeEditorWidget. - * Defaults to null. - */ - telemetryData?: object; -} - -class ModelData { - constructor( - public readonly model: ITextModel, - public readonly viewModel: ViewModel, - public readonly view: View, - public readonly hasRealView: boolean, - public readonly listenersToRemove: IDisposable[], - public readonly attachedView: IAttachedView, - ) { - } - - public dispose(): void { - dispose(this.listenersToRemove); - this.model.onBeforeDetached(this.attachedView); - if (this.hasRealView) { - this.view.dispose(); - } - this.viewModel.dispose(); - } -} - export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeEditor { private static readonly dropIntoEditorDecorationOptions = ModelDecorationOptions.register({ @@ -1932,6 +1886,51 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } } +let EDITOR_ID = 0; + +export interface ICodeEditorWidgetOptions { + /** + * Is this a simple widget (not a real code editor)? + * Defaults to false. + */ + isSimpleWidget?: boolean; + + /** + * Contributions to instantiate. + * When provided, only the contributions included will be instantiated. + * To include the defaults, those must be provided as well via [...EditorExtensionsRegistry.getEditorContributions()] + * Defaults to EditorExtensionsRegistry.getEditorContributions(). + */ + contributions?: IEditorContributionDescription[]; + + /** + * Telemetry data associated with this CodeEditorWidget. + * Defaults to null. + */ + telemetryData?: object; +} + +class ModelData { + constructor( + public readonly model: ITextModel, + public readonly viewModel: ViewModel, + public readonly view: View, + public readonly hasRealView: boolean, + public readonly listenersToRemove: IDisposable[], + public readonly attachedView: IAttachedView, + ) { + } + + public dispose(): void { + dispose(this.listenersToRemove); + this.model.onBeforeDetached(this.attachedView); + if (this.hasRealView) { + this.view.dispose(); + } + this.viewModel.dispose(); + } +} + const enum BooleanEventValue { NotSet, False, diff --git a/src/vs/editor/browser/widget/media/editor.css b/src/vs/editor/browser/widget/codeEditor/editor.css similarity index 100% rename from src/vs/editor/browser/widget/media/editor.css rename to src/vs/editor/browser/widget/codeEditor/editor.css diff --git a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget.ts similarity index 59% rename from src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts rename to src/vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget.ts index 4a5dffa5347..9fb3a8e69c2 100644 --- a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget.ts @@ -4,24 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import * as objects from 'vs/base/common/objects'; -import { ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; -import { DiffEditorWidget, IDiffCodeEditorWidgetOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; -import { ConfigurationChangedEvent, IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; +import { ConfigurationChangedEvent, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { IThemeService } from 'vs/platform/theme/common/themeService'; export class EmbeddedCodeEditorWidget extends CodeEditorWidget { - private readonly _parentEditor: ICodeEditor; private readonly _overwriteOptions: IEditorOptions; @@ -38,7 +34,7 @@ export class EmbeddedCodeEditorWidget extends CodeEditorWidget { @INotificationService notificationService: INotificationService, @IAccessibilityService accessibilityService: IAccessibilityService, @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, - @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService ) { super(domElement, { ...parentEditor.getRawOptions(), overflowWidgetsDomNode: parentEditor.getOverflowWidgetsDomNode() }, codeEditorWidgetOptions, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService, languageConfigurationService, languageFeaturesService); @@ -65,45 +61,3 @@ export class EmbeddedCodeEditorWidget extends CodeEditorWidget { super.updateOptions(this._overwriteOptions); } } - -export class EmbeddedDiffEditorWidget extends DiffEditorWidget { - - private readonly _parentEditor: ICodeEditor; - private readonly _overwriteOptions: IDiffEditorOptions; - - constructor( - domElement: HTMLElement, - options: Readonly, - codeEditorWidgetOptions: IDiffCodeEditorWidgetOptions, - parentEditor: ICodeEditor, - @IContextKeyService contextKeyService: IContextKeyService, - @IInstantiationService instantiationService: IInstantiationService, - @ICodeEditorService codeEditorService: ICodeEditorService, - @IAccessibilitySignalService accessibilitySignalService: IAccessibilitySignalService, - @IEditorProgressService editorProgressService: IEditorProgressService, - ) { - super(domElement, parentEditor.getRawOptions(), codeEditorWidgetOptions, contextKeyService, instantiationService, codeEditorService, accessibilitySignalService, editorProgressService); - - this._parentEditor = parentEditor; - this._overwriteOptions = options; - - // Overwrite parent's options - super.updateOptions(this._overwriteOptions); - - this._register(parentEditor.onDidChangeConfiguration(e => this._onParentConfigurationChanged(e))); - } - - getParentEditor(): ICodeEditor { - return this._parentEditor; - } - - private _onParentConfigurationChanged(e: ConfigurationChangedEvent): void { - super.updateOptions(this._parentEditor.getRawOptions()); - super.updateOptions(this._overwriteOptions); - } - - override updateOptions(newOptions: IEditorOptions): void { - objects.mixin(this._overwriteOptions, newOptions, true); - super.updateOptions(this._overwriteOptions); - } -} diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts index 79d6509b225..1818ead661f 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IObservable, IReader, autorunHandleChanges, derivedOpts, observableFromEvent } from 'vs/base/common/observable'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { OverviewRulerFeature } from 'vs/editor/browser/widget/diffEditor/features/overviewRulerFeature'; import { EditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IContentSizeChangedEvent } from 'vs/editor/common/editorCommon'; diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts index ac5b8886ac3..d6bcac91567 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts @@ -12,7 +12,7 @@ import { IObservable, autorun, derived, derivedWithStore, observableFromEvent, o import { ThemeIcon } from 'vs/base/common/themables'; import { assertIsDefined } from 'vs/base/common/types'; import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { diffDeleteDecoration, diffRemoveIcon } from 'vs/editor/browser/widget/diffEditor/registrations.contribution'; import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/components/diffEditorEditors'; import { DiffEditorViewModel, DiffMapping } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/inlineDiffDeletedCodeMargin.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/inlineDiffDeletedCodeMargin.ts index ced12b99f7b..f6d88c3afe7 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/inlineDiffDeletedCodeMargin.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/inlineDiffDeletedCodeMargin.ts @@ -10,7 +10,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { isIOS } from 'vs/base/common/platform'; import { ThemeIcon } from 'vs/base/common/themables'; import { IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; diff --git a/src/vs/editor/browser/widget/diffEditor/delegatingEditorImpl.ts b/src/vs/editor/browser/widget/diffEditor/delegatingEditorImpl.ts index ff3b66c5e09..96a85f35eef 100644 --- a/src/vs/editor/browser/widget/diffEditor/delegatingEditorImpl.ts +++ b/src/vs/editor/browser/widget/diffEditor/delegatingEditorImpl.ts @@ -5,7 +5,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IDimension } from 'vs/editor/common/core/dimension'; import { IPosition, Position } from 'vs/editor/common/core/position'; diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index 5ad6e0543ef..7499fa8e0bc 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -16,7 +16,7 @@ import { ICodeEditor, IDiffEditor, IDiffEditorConstructionOptions } from 'vs/edi import { EditorExtensionsRegistry, IDiffEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { AccessibleDiffViewer, AccessibleDiffViewerModelFromEditors } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer'; import { DiffEditorDecorations } from 'vs/editor/browser/widget/diffEditor/components/diffEditorDecorations'; import { DiffEditorSash } from 'vs/editor/browser/widget/diffEditor/components/diffEditorSash'; diff --git a/src/vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget.ts new file mode 100644 index 00000000000..9156c17ead3 --- /dev/null +++ b/src/vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as objects from 'vs/base/common/objects'; +import { ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { DiffEditorWidget, IDiffCodeEditorWidgetOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; +import { ConfigurationChangedEvent, IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IEditorProgressService } from 'vs/platform/progress/common/progress'; +export class EmbeddedDiffEditorWidget extends DiffEditorWidget { + + private readonly _parentEditor: ICodeEditor; + private readonly _overwriteOptions: IDiffEditorOptions; + + constructor( + domElement: HTMLElement, + options: Readonly, + codeEditorWidgetOptions: IDiffCodeEditorWidgetOptions, + parentEditor: ICodeEditor, + @IContextKeyService contextKeyService: IContextKeyService, + @IInstantiationService instantiationService: IInstantiationService, + @ICodeEditorService codeEditorService: ICodeEditorService, + @IAccessibilitySignalService accessibilitySignalService: IAccessibilitySignalService, + @IEditorProgressService editorProgressService: IEditorProgressService + ) { + super(domElement, parentEditor.getRawOptions(), codeEditorWidgetOptions, contextKeyService, instantiationService, codeEditorService, accessibilitySignalService, editorProgressService); + + this._parentEditor = parentEditor; + this._overwriteOptions = options; + + // Overwrite parent's options + super.updateOptions(this._overwriteOptions); + + this._register(parentEditor.onDidChangeConfiguration(e => this._onParentConfigurationChanged(e))); + } + + getParentEditor(): ICodeEditor { + return this._parentEditor; + } + + private _onParentConfigurationChanged(e: ConfigurationChangedEvent): void { + super.updateOptions(this._parentEditor.getRawOptions()); + super.updateOptions(this._overwriteOptions); + } + + override updateOptions(newOptions: IEditorOptions): void { + objects.mixin(this._overwriteOptions, newOptions, true); + super.updateOptions(this._overwriteOptions); + } +} diff --git a/src/vs/editor/browser/widget/diffEditor/features/overviewRulerFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/overviewRulerFeature.ts index bd1997491e1..8141cd9452c 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/overviewRulerFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/overviewRulerFeature.ts @@ -10,7 +10,7 @@ import { ScrollbarState } from 'vs/base/browser/ui/scrollbar/scrollbarState'; import { Color } from 'vs/base/common/color'; import { Disposable } from 'vs/base/common/lifecycle'; import { IObservable, autorun, autorunWithStore, derived, observableFromEvent, observableSignalFromEvent } from 'vs/base/common/observable'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/components/diffEditorEditors'; import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; import { appendRemoveOnDispose } from 'vs/editor/browser/widget/diffEditor/utils'; diff --git a/src/vs/editor/contrib/dnd/browser/dnd.ts b/src/vs/editor/contrib/dnd/browser/dnd.ts index d4576e66c8d..9e355410d48 100644 --- a/src/vs/editor/contrib/dnd/browser/dnd.ts +++ b/src/vs/editor/contrib/dnd/browser/dnd.ts @@ -11,7 +11,7 @@ import { isMacintosh } from 'vs/base/common/platform'; import 'vs/css!./dnd'; import { ICodeEditor, IEditorMouseEvent, IMouseTarget, IPartialEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; import { Position } from 'vs/editor/common/core/position'; diff --git a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts index b968f248904..aa6b7865439 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts @@ -13,7 +13,7 @@ import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/edit import { IActiveCodeEditor, ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorOption, GoToLocationValues } from 'vs/editor/common/config/editorOptions'; import * as corePosition from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts index d23e38b81ab..b80e75d47ec 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts @@ -16,7 +16,7 @@ import { Schemas } from 'vs/base/common/network'; import { basenameOrAuthority, dirname } from 'vs/base/common/resources'; import 'vs/css!./referencesWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; diff --git a/src/vs/editor/contrib/peekView/browser/peekView.ts b/src/vs/editor/contrib/peekView/browser/peekView.ts index 5c0882b8db4..a0f2dfd914e 100644 --- a/src/vs/editor/contrib/peekView/browser/peekView.ts +++ b/src/vs/editor/contrib/peekView/browser/peekView.ts @@ -17,7 +17,7 @@ import 'vs/css!./media/peekViewWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IOptions, IStyles, ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts index 06fb2ce428f..bdcaafb4891 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts @@ -11,7 +11,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import 'vs/css!./stickyScroll'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { getColumnOfNodeOffset } from 'vs/editor/browser/viewParts/lines/viewLine'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorLayoutInfo, EditorOption, RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { StringBuilder } from 'vs/editor/common/core/stringBuilder'; diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index fc41e265fc4..2eeb94d99b6 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -16,7 +16,7 @@ import { clamp } from 'vs/base/common/numbers'; import * as strings from 'vs/base/common/strings'; import 'vs/css!./media/suggest'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition } from 'vs/editor/common/core/position'; import { SuggestWidgetStatus } from 'vs/editor/contrib/suggest/browser/suggestWidgetStatus'; diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index 227595d6224..37ed9f9f4fd 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/editor/browser/coreCommands'; -import 'vs/editor/browser/widget/codeEditorWidget'; +import 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import 'vs/editor/browser/widget/diffEditor/diffEditor.contribution'; import 'vs/editor/contrib/anchorSelect/browser/anchorSelect'; import 'vs/editor/contrib/bracketMatching/browser/bracketMatching'; diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 479bb75745c..3e903d5bab9 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -7,7 +7,7 @@ import * as aria from 'vs/base/browser/ui/aria/aria'; import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor, IDiffEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { InternalEditorAction } from 'vs/editor/common/editorAction'; import { IModelChangedEvent } from 'vs/editor/common/editorCommon'; diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index b29e05c8053..c0a4b9d1cf3 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -9,7 +9,7 @@ import { EditorConfiguration } from 'vs/editor/browser/config/editorConfiguratio import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { View } from 'vs/editor/browser/view'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import * as editorOptions from 'vs/editor/common/config/editorOptions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ILanguageService } from 'vs/editor/common/languages/language'; diff --git a/src/vs/workbench/browser/codeeditor.ts b/src/vs/workbench/browser/codeeditor.ts index 33358b10156..dcce3224ba9 100644 --- a/src/vs/workbench/browser/codeeditor.ts +++ b/src/vs/workbench/browser/codeeditor.ts @@ -9,7 +9,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference, isCodeEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IRange } from 'vs/editor/common/core/range'; import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents'; diff --git a/src/vs/workbench/browser/parts/editor/textCodeEditor.ts b/src/vs/workbench/browser/parts/editor/textCodeEditor.ts index 2efc763e041..2edad61d83c 100644 --- a/src/vs/workbench/browser/parts/editor/textCodeEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textCodeEditor.ts @@ -12,7 +12,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { isEqual } from 'vs/base/common/resources'; import { IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { AbstractTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 5f3a2df9d06..cfbfa70a242 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -18,7 +18,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { Position } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index 6eb9f63cba9..3dada0c5698 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -19,7 +19,7 @@ import { SplitView, Orientation, Sizing } from 'vs/base/browser/ui/splitview/spl import { Dimension, isKeyboardEvent } from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index f4692878598..e149d81cadb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -14,7 +14,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { HoverController } from 'vs/editor/contrib/hover/browser/hover'; diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 17b169bc4f1..8669cadcce7 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -15,7 +15,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { EDITOR_FONT_DEFAULTS, EditorOption, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index 204ebdd1358..a19d88cca33 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -10,7 +10,7 @@ import { registerDiffEditorContribution } from 'vs/editor/browser/editorExtensio import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { AccessibleDiffViewerNext, AccessibleDiffViewerPrev } from 'vs/editor/browser/widget/diffEditor/diffEditor.contribution'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; -import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; import { IDiffEditorContribution } from 'vs/editor/common/editorCommon'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts index 6acfd4495b7..b4f68265026 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index 32ac5cb4dec..60e13398790 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -11,7 +11,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { mixin } from 'vs/base/common/objects'; import { isMacintosh } from 'vs/base/common/platform'; import { URI as uri } from 'vs/base/common/uri'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index d2fa2c78e8a..4faf49116c0 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -25,7 +25,7 @@ import { CommentGlyphWidget } from 'vs/workbench/contrib/comments/browser/commen import { ICommentInfo, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; import { isMouseUpEventDragFromMouseDown, parseMouseDownInfoFromEvent, ReviewZoneWidget } from 'vs/workbench/contrib/comments/browser/commentThreadZoneWidget'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { COMMENTS_VIEW_ID } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index 2a4f2c3a2bb..d41c334028d 100644 --- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts +++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts @@ -6,7 +6,7 @@ import { EditorOption, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { EditorAction, EditorContributionInstantiation, EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 26ae95ccf1d..7289d25ba29 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -16,7 +16,7 @@ import 'vs/css!./media/breakpointWidget'; import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorCommand, ServicesAccessor, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { EditorOption, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index ffcda601ff9..21f7ed8ba47 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -25,7 +25,7 @@ import 'vs/css!./media/repl'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { EDITOR_FONT_DEFAULTS, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 6fa04545a0e..da1e9c4dcd5 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -37,7 +37,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { coalesce } from 'vs/base/common/arrays'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { isCancellationError } from 'vs/base/common/errors'; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 8deb70a073e..75ca16a1f24 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -7,7 +7,8 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; -import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_EDIT_MODE, EditMode, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, InlineChatResponseFeedbackKind, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, MENU_INLINE_CHAT_WIDGET } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts index 1af9ecfa2f1..85d9762dc22 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts @@ -7,7 +7,8 @@ import { Dimension, getWindow, h, runAtThisOrScheduleAtNextAnimationFrame } from import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorOption, IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model'; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index f65363a736b..50bba01479e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -19,9 +19,10 @@ import 'vs/css!./inlineChat'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer'; -import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorLayoutInfo, EditorOption, IComputedEditorOptions } from 'vs/editor/common/config/editorOptions'; import { LineRange } from 'vs/editor/common/core/lineRange'; import { Position } from 'vs/editor/common/core/position'; diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index 05d7d253673..6d300714340 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -12,7 +12,7 @@ import { extname, isEqual } from 'vs/base/common/resources'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { ITextModel } from 'vs/editor/common/model'; diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index f9c766ca849..af62b50a84c 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -10,7 +10,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { ICodeEditorViewState, IDecorationOptions } from 'vs/editor/common/editorCommon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts index d2853645051..b43d06358f8 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts @@ -7,7 +7,7 @@ import { ArrayQueue, CompareResult } from 'vs/base/common/arrays'; import { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IObservable, autorunOpts, observableFromEvent } from 'vs/base/common/observable'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts index 9d349b594ec..b751b6bc0ad 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts @@ -6,7 +6,7 @@ import { h, reset } from 'vs/base/browser/dom'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { autorun, IReader, observableFromEvent, observableSignal, observableSignalFromEvent, transaction } from 'vs/base/common/observable'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; export class EditorGutter extends Disposable { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts index 38266ed0600..8cd243e3777 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts @@ -9,7 +9,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IObservable, autorun, derived, observableFromEvent } from 'vs/base/common/observable'; import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer.ts index bbbe978f302..94857ee4726 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer.ts @@ -5,7 +5,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { autorunWithStore, IObservable } from 'vs/base/common/observable'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { DocumentLineRangeMap } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; import { ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/utils'; diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 61810983632..93a98b32d60 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -15,7 +15,7 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import { assertType } from 'vs/base/common/types'; import { generateUuid } from 'vs/base/common/uuid'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index 23e8dec4478..c1218b15ce8 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -9,7 +9,7 @@ import { Schemas } from 'vs/base/common/network'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DiffElementViewModelBase, getFormattedMetadataJSON, getFormattedOutputJSON, OutputComparison, outputEqual, OUTPUT_EDITOR_HEIGHT_MAGIC, PropertyFoldingState, SideBySideDiffElementViewModel, SingleSideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; import { CellDiffSideBySideRenderTemplate, CellDiffSingleSideRenderTemplate, DiffSide, DIFF_CELL_MARGIN, INotebookTextDiffEditor, NOTEBOOK_DIFF_CELL_INPUT, NOTEBOOK_DIFF_CELL_PROPERTY, NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IModelService } from 'vs/editor/common/services/model'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { CellEditType, CellUri, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts index 6741f2a3ab8..031c2afdc7f 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts @@ -9,7 +9,7 @@ import { Event } from 'vs/base/common/event'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts index 11aa9bfb6b1..9248d1cee6e 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts @@ -17,7 +17,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { DiffElementViewModelBase, SideBySideDiffElementViewModel, SingleSideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; import { CellDiffSideBySideRenderTemplate, CellDiffSingleSideRenderTemplate, DIFF_CELL_MARGIN, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; import { DeletedElement, getOptimizedNestedCodeEditorWidgetOptions, InsertElement, ModifiedElement } from 'vs/workbench/contrib/notebook/browser/diff/diffComponents'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts index 5806dd97b5f..d334bb1db3f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts @@ -11,7 +11,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ILanguageService } from 'vs/editor/common/languages/language'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 69133337125..25a1310af43 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -9,7 +9,7 @@ import { FastDomNode } from 'vs/base/browser/fastDomNode'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 742c76b7d37..adcf99c35cd 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -29,7 +29,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { rot } from 'vs/base/common/numbers'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; import { IDiffEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Action, IAction, ActionRunner } from 'vs/base/common/actions'; import { IActionBarOptions } from 'vs/base/browser/ui/actionbar/actionbar'; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index f91410f9b8d..f35fe3e0eda 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -43,7 +43,7 @@ import { flatten } from 'vs/base/common/arrays'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { ITextModel } from 'vs/editor/common/model'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 649a025c5c3..6911cdad058 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -23,7 +23,7 @@ import * as network from 'vs/base/common/network'; import 'vs/css!./media/searchview'; import { getCodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Selection } from 'vs/editor/common/core/selection'; import { IEditor } from 'vs/editor/common/editorCommon'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 5f36473df15..dec67977c05 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -13,7 +13,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchEditor'; -import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index a64fc0274f3..7f5dcfb7920 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -37,9 +37,10 @@ import 'vs/css!./testingOutputPeek'; import { ICodeEditor, IDiffEditorConstructionOptions, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; -import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts index 8753d7436f5..d5bf22e671a 100644 --- a/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts +++ b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts @@ -15,7 +15,7 @@ import { FuzzyScore } from 'vs/base/common/filters'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts index 177b1a4a14e..50efd6cfc84 100644 --- a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts @@ -17,7 +17,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { WalkThroughInput } from 'vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughInput'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { localize } from 'vs/nls'; diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index c55daa6fdc3..de6a4ad8227 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -19,7 +19,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { extractFileListData } from 'vs/platform/dnd/browser/dnd'; import { Iterable } from 'vs/base/common/iterator'; import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; export class FileDialogService extends AbstractFileDialogService implements IFileDialogService { From c18d58d9629f4b45e0359fb14812526b96ebf67d Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 23 Feb 2024 14:36:18 +0100 Subject: [PATCH 1553/1897] Adopt Custom Hover for search/find/replace views/widgets (#206077) * Adopt Custom Hover for search/find/replace views * tree search filters --- src/vs/base/browser/ui/button/button.ts | 23 ++++++++++++++----- src/vs/base/browser/ui/findinput/findInput.ts | 6 +++++ .../browser/ui/findinput/findInputToggles.ts | 6 +++++ .../base/browser/ui/findinput/replaceInput.ts | 4 +++- src/vs/base/browser/ui/toggle/toggle.ts | 4 +++- src/vs/base/browser/ui/tree/abstractTree.ts | 10 ++++++-- .../contrib/find/browser/findOptionsWidget.ts | 6 +++++ .../editor/contrib/find/browser/findWidget.ts | 17 +++++++++++++- .../search/browser/patternInputWidget.ts | 3 +++ .../search/browser/searchResultsView.ts | 8 +++++-- .../contrib/search/browser/searchView.ts | 8 +++++-- .../contrib/search/browser/searchWidget.ts | 7 ++++-- .../searchEditor/browser/searchEditor.ts | 5 +++- 13 files changed, 89 insertions(+), 18 deletions(-) diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index 4675ee8c5ee..e7ca41dc8b3 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -10,6 +10,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { renderMarkdown, renderStringAsPlaintext } from 'vs/base/browser/markdownRenderer'; import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { Action, IAction, IActionRunner } from 'vs/base/common/actions'; @@ -29,6 +30,7 @@ export interface IButtonOptions extends Partial { readonly supportIcons?: boolean; readonly supportShortLabel?: boolean; readonly secondary?: boolean; + readonly hoverDelegate?: IHoverDelegate; } export interface IButtonStyles { @@ -115,6 +117,10 @@ export class Button extends Disposable implements IButton { this._element.classList.add('monaco-text-button-with-short-label'); } + if (typeof options.title === 'string') { + this.setTitle(options.title); + } + if (typeof options.ariaLabel === 'string') { this._element.setAttribute('aria-label', options.ariaLabel); } @@ -249,16 +255,13 @@ export class Button extends Disposable implements IButton { } else if (this.options.title) { title = renderStringAsPlaintext(value); } - if (!this._hover) { - this._hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this._element, title)); - } else { - this._hover.update(title); - } + + this.setTitle(title); if (typeof this.options.ariaLabel === 'string') { this._element.setAttribute('aria-label', this.options.ariaLabel); } else if (this.options.ariaLabel) { - this._element.setAttribute('aria-label', this._element.title); + this._element.setAttribute('aria-label', title); } this._label = value; @@ -299,6 +302,14 @@ export class Button extends Disposable implements IButton { return !this._element.classList.contains('disabled'); } + private setTitle(title: string) { + if (!this._hover) { + this._hover = this._register(setupCustomHover(this.options.hoverDelegate ?? getDefaultHoverDelegate('mouse'), this._element, title)); + } else { + this._hover.update(title); + } + } + focus(): void { this._element.focus(); } diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index 76af849c278..c119ad43779 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -16,6 +16,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import 'vs/css!./findInput'; import * as nls from 'vs/nls'; import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export interface IFindInputOptions { @@ -113,10 +114,13 @@ export class FindInput extends Widget { inputBoxStyles: options.inputBoxStyles, })); + const hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + if (this.showCommonFindToggles) { this.regex = this._register(new RegexToggle({ appendTitle: appendRegexLabel, isChecked: false, + hoverDelegate, ...options.toggleStyles })); this._register(this.regex.onChange(viaKeyboard => { @@ -133,6 +137,7 @@ export class FindInput extends Widget { this.wholeWords = this._register(new WholeWordsToggle({ appendTitle: appendWholeWordsLabel, isChecked: false, + hoverDelegate, ...options.toggleStyles })); this._register(this.wholeWords.onChange(viaKeyboard => { @@ -146,6 +151,7 @@ export class FindInput extends Widget { this.caseSensitive = this._register(new CaseSensitiveToggle({ appendTitle: appendCaseSensitiveLabel, isChecked: false, + hoverDelegate, ...options.toggleStyles })); this._register(this.caseSensitive.onChange(viaKeyboard => { diff --git a/src/vs/base/browser/ui/findinput/findInputToggles.ts b/src/vs/base/browser/ui/findinput/findInputToggles.ts index 591ab981577..8b3ea12580f 100644 --- a/src/vs/base/browser/ui/findinput/findInputToggles.ts +++ b/src/vs/base/browser/ui/findinput/findInputToggles.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Codicon } from 'vs/base/common/codicons'; import * as nls from 'vs/nls'; @@ -13,6 +15,7 @@ export interface IFindInputToggleOpts { readonly inputActiveOptionBorder: string | undefined; readonly inputActiveOptionForeground: string | undefined; readonly inputActiveOptionBackground: string | undefined; + readonly hoverDelegate?: IHoverDelegate; } const NLS_CASE_SENSITIVE_TOGGLE_LABEL = nls.localize('caseDescription', "Match Case"); @@ -25,6 +28,7 @@ export class CaseSensitiveToggle extends Toggle { icon: Codicon.caseSensitive, title: NLS_CASE_SENSITIVE_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, + hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground @@ -38,6 +42,7 @@ export class WholeWordsToggle extends Toggle { icon: Codicon.wholeWord, title: NLS_WHOLE_WORD_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, + hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground @@ -51,6 +56,7 @@ export class RegexToggle extends Toggle { icon: Codicon.regex, title: NLS_REGEX_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, + hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts index 6cd7d4fb1c6..8e20025d757 100644 --- a/src/vs/base/browser/ui/findinput/replaceInput.ts +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -16,6 +16,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import 'vs/css!./findInput'; import * as nls from 'vs/nls'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export interface IReplaceInputOptions { @@ -44,9 +45,10 @@ class PreserveCaseToggle extends Toggle { icon: Codicon.preserveCase, title: NLS_PRESERVE_CASE_LABEL + opts.appendTitle, isChecked: opts.isChecked, + hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, - inputActiveOptionBackground: opts.inputActiveOptionBackground + inputActiveOptionBackground: opts.inputActiveOptionBackground, }); } } diff --git a/src/vs/base/browser/ui/toggle/toggle.ts b/src/vs/base/browser/ui/toggle/toggle.ts index 54b9130d9e4..540bb17db2f 100644 --- a/src/vs/base/browser/ui/toggle/toggle.ts +++ b/src/vs/base/browser/ui/toggle/toggle.ts @@ -15,6 +15,7 @@ import 'vs/css!./toggle'; import { isActiveElement, $, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; export interface IToggleOpts extends IToggleStyles { readonly actionClassName?: string; @@ -22,6 +23,7 @@ export interface IToggleOpts extends IToggleStyles { readonly title: string; readonly isChecked: boolean; readonly notFocusable?: boolean; + readonly hoverDelegate?: IHoverDelegate; } export interface IToggleStyles { @@ -130,7 +132,7 @@ export class Toggle extends Widget { } this.domNode = document.createElement('div'); - this._hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.domNode, this._opts.title)); + this._hover = this._register(setupCustomHover(opts.hoverDelegate ?? getDefaultHoverDelegate('mouse'), this.domNode, this._opts.title)); this.domNode.classList.add(...classes); if (!this._opts.notFocusable) { this.domNode.tabIndex = 0; diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 16f985264d4..c42cd0ba336 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -33,6 +33,8 @@ import { ISpliceable } from 'vs/base/common/sequence'; import { isNumber } from 'vs/base/common/types'; import 'vs/css!./media/tree'; import { localize } from 'vs/nls'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; class TreeElementsDragAndDropData extends ElementsDragAndDropData { @@ -679,6 +681,7 @@ export interface ITreeFindToggleOpts { readonly inputActiveOptionBorder: string | undefined; readonly inputActiveOptionForeground: string | undefined; readonly inputActiveOptionBackground: string | undefined; + readonly hoverDelegate?: IHoverDelegate; } export class ModeToggle extends Toggle { @@ -687,6 +690,7 @@ export class ModeToggle extends Toggle { icon: Codicon.listFilter, title: localize('filter', "Filter"), isChecked: opts.isChecked ?? false, + hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground @@ -700,6 +704,7 @@ export class FuzzyToggle extends Toggle { icon: Codicon.searchFuzzy, title: localize('fuzzySearch', "Fuzzy Match"), isChecked: opts.isChecked ?? false, + hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground @@ -802,8 +807,9 @@ class FindWidget extends Disposable { this.elements.root.style.boxShadow = `0 0 8px 2px ${styles.listFilterWidgetShadow}`; } - this.modeToggle = this._register(new ModeToggle({ ...styles.toggleStyles, isChecked: mode === TreeFindMode.Filter })); - this.matchTypeToggle = this._register(new FuzzyToggle({ ...styles.toggleStyles, isChecked: matchType === TreeFindMatchType.Fuzzy })); + const toggleHoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + this.modeToggle = this._register(new ModeToggle({ ...styles.toggleStyles, isChecked: mode === TreeFindMode.Filter, hoverDelegate: toggleHoverDelegate })); + this.matchTypeToggle = this._register(new FuzzyToggle({ ...styles.toggleStyles, isChecked: matchType === TreeFindMatchType.Fuzzy, hoverDelegate: toggleHoverDelegate })); this.onDidChangeMode = Event.map(this.modeToggle.onChange, () => this.modeToggle.checked ? TreeFindMode.Filter : TreeFindMode.Highlight, this._store); this.onDidChangeMatchType = Event.map(this.matchTypeToggle.onChange, () => this.matchTypeToggle.checked ? TreeFindMatchType.Fuzzy : TreeFindMatchType.Contiguous, this._store); diff --git a/src/vs/editor/contrib/find/browser/findOptionsWidget.ts b/src/vs/editor/contrib/find/browser/findOptionsWidget.ts index 9ebf4167820..7b693a1f28c 100644 --- a/src/vs/editor/contrib/find/browser/findOptionsWidget.ts +++ b/src/vs/editor/contrib/find/browser/findOptionsWidget.ts @@ -13,6 +13,7 @@ import { FIND_IDS } from 'vs/editor/contrib/find/browser/findModel'; import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { asCssVariable, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export class FindOptionsWidget extends Widget implements IOverlayWidget { @@ -52,9 +53,12 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground), }; + const hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + this.caseSensitive = this._register(new CaseSensitiveToggle({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand), isChecked: this._state.matchCase, + hoverDelegate, ...toggleStyles })); this._domNode.appendChild(this.caseSensitive.domNode); @@ -67,6 +71,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this.wholeWords = this._register(new WholeWordsToggle({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleWholeWordCommand), isChecked: this._state.wholeWord, + hoverDelegate, ...toggleStyles })); this._domNode.appendChild(this.wholeWords.domNode); @@ -79,6 +84,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this.regex = this._register(new RegexToggle({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleRegexCommand), isChecked: this._state.isRegex, + hoverDelegate, ...toggleStyles })); this._domNode.appendChild(this.regex.domNode); diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index f89e3d81837..76189e64741 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -43,6 +43,9 @@ import { isHighContrast } from 'vs/platform/theme/common/theme'; import { assertIsDefined } from 'vs/base/common/types'; import { defaultInputBoxStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Selection } from 'vs/editor/common/core/selection'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; const findSelectionIcon = registerIcon('find-selection', Codicon.selection, nls.localize('findSelectionIcon', 'Icon for \'Find in Selection\' in the editor find widget.')); const findCollapsedIcon = registerIcon('find-collapsed', Codicon.chevronRight, nls.localize('findCollapsedIcon', 'Icon to indicate that the editor find widget is collapsed.')); @@ -1010,10 +1013,14 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._matchesCount.className = 'matchesCount'; this._updateMatchesCount(); + // Create a scoped hover delegate for all find related buttons + const hoverDelegate = getDefaultHoverDelegate('element', true); + // Previous button this._prevBtn = this._register(new SimpleButton({ label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.PreviousMatchFindAction), icon: findPreviousMatchIcon, + hoverDelegate, onTrigger: () => { assertIsDefined(this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction)).run().then(undefined, onUnexpectedError); } @@ -1023,6 +1030,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._nextBtn = this._register(new SimpleButton({ label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.NextMatchFindAction), icon: findNextMatchIcon, + hoverDelegate, onTrigger: () => { assertIsDefined(this._codeEditor.getAction(FIND_IDS.NextMatchFindAction)).run().then(undefined, onUnexpectedError); } @@ -1077,6 +1085,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._closeBtn = this._register(new SimpleButton({ label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand), icon: widgetClose, + hoverDelegate, onTrigger: () => { this._state.change({ isRevealed: false, searchScope: null }, false); }, @@ -1138,10 +1147,14 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL } })); + // Create scoped hover delegate for replace actions + const replaceHoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + // Replace one button this._replaceBtn = this._register(new SimpleButton({ label: NLS_REPLACE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceOneAction), icon: findReplaceIcon, + hoverDelegate: replaceHoverDelegate, onTrigger: () => { this._controller.replace(); }, @@ -1157,6 +1170,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._replaceAllBtn = this._register(new SimpleButton({ label: NLS_REPLACE_ALL_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceAllAction), icon: findReplaceAllIcon, + hoverDelegate: replaceHoverDelegate, onTrigger: () => { this._controller.replaceAll(); } @@ -1299,6 +1313,7 @@ export interface ISimpleButtonOpts { readonly label: string; readonly className?: string; readonly icon?: ThemeIcon; + readonly hoverDelegate?: IHoverDelegate; readonly onTrigger: () => void; readonly onKeyDown?: (e: IKeyboardEvent) => void; } @@ -1321,11 +1336,11 @@ export class SimpleButton extends Widget { } this._domNode = document.createElement('div'); - this._domNode.title = this._opts.label; this._domNode.tabIndex = 0; this._domNode.className = className; this._domNode.setAttribute('role', 'button'); this._domNode.setAttribute('aria-label', this._opts.label); + this._register(setupCustomHover(opts.hoverDelegate ?? getDefaultHoverDelegate('element'), this._domNode, this._opts.label)); this.onclick(this._domNode, (e) => { this._opts.onTrigger(); diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index 6d167046d37..c7dfc7eae60 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -19,6 +19,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export interface IOptions { placeholder?: string; @@ -219,6 +220,7 @@ export class IncludePatternInputWidget extends PatternInputWidget { icon: Codicon.book, title: nls.localize('onlySearchInOpenEditors', "Search only in Open Editors"), isChecked: false, + hoverDelegate: getDefaultHoverDelegate('element'), ...defaultToggleStyles })); this._register(this.useSearchInEditorsBox.onChange(viaKeyboard => { @@ -271,6 +273,7 @@ export class ExcludePatternInputWidget extends PatternInputWidget { actionClassName: 'useExcludesAndIgnoreFiles', title: nls.localize('useExcludesAndIgnoreFilesDescription', "Use Exclude Settings and Ignore Files"), isChecked: true, + hoverDelegate: getDefaultHoverDelegate('element'), ...defaultToggleStyles })); this._register(this.useExcludesAndIgnoreFilesBox.onChange(viaKeyboard => { diff --git a/src/vs/workbench/contrib/search/browser/searchResultsView.ts b/src/vs/workbench/contrib/search/browser/searchResultsView.ts index 3f71827f12c..38a4f536403 100644 --- a/src/vs/workbench/contrib/search/browser/searchResultsView.ts +++ b/src/vs/workbench/contrib/search/browser/searchResultsView.ts @@ -30,6 +30,8 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; import { SearchContext } from 'vs/workbench/contrib/search/common/constants'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; interface IFolderMatchTemplate { label: IResourceLabel; @@ -360,7 +362,9 @@ export class MatchRenderer extends Disposable implements ICompressibleTreeRender templateData.match.classList.toggle('replace', replace); templateData.replace.textContent = replace ? match.replaceString : ''; templateData.after.textContent = preview.after; - templateData.parent.title = (preview.fullBefore + (replace ? match.replaceString : preview.inside) + preview.after).trim().substr(0, 999); + + const title = (preview.fullBefore + (replace ? match.replaceString : preview.inside) + preview.after).trim().substr(0, 999); + templateData.disposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), templateData.parent, title)); SearchContext.IsEditableItemKey.bindTo(templateData.contextKeyService).set(!(match instanceof MatchInNotebook && match.isReadonly())); @@ -372,7 +376,7 @@ export class MatchRenderer extends Disposable implements ICompressibleTreeRender templateData.lineNumber.classList.toggle('show', (numLines > 0) || showLineNumbers); templateData.lineNumber.textContent = lineNumberStr + extraLinesStr; - templateData.lineNumber.setAttribute('title', this.getMatchTitle(match, showLineNumbers)); + templateData.disposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), templateData.lineNumber, this.getMatchTitle(match, showLineNumbers))); templateData.actions.context = { viewer: this.searchView.getControl(), element: match }; diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 6911cdad058..4a0a156fcf6 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -81,6 +81,8 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { ILogService } from 'vs/platform/log/common/log'; import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; const $ = dom.$; @@ -405,7 +407,8 @@ export class SearchView extends ViewPane { // Toggle query details button this.toggleQueryDetailsButton = dom.append(this.queryDetails, - $('.more' + ThemeIcon.asCSSSelector(searchDetailsIcon), { tabindex: 0, role: 'button', title: nls.localize('moreSearch', "Toggle Search Details") })); + $('.more' + ThemeIcon.asCSSSelector(searchDetailsIcon), { tabindex: 0, role: 'button' })); + this._register(setupCustomHover(getDefaultHoverDelegate('element'), this.toggleQueryDetailsButton, nls.localize('moreSearch', "Toggle Search Details"))); this._register(dom.addDisposableListener(this.toggleQueryDetailsButton, dom.EventType.CLICK, e => { dom.EventHelper.stop(e); @@ -2133,7 +2136,8 @@ class SearchLinkButton extends Disposable { constructor(label: string, handler: (e: dom.EventLike) => unknown, tooltip?: string) { super(); - this.element = $('a.pointer', { tabindex: 0, title: tooltip }, label); + this.element = $('a.pointer', { tabindex: 0 }, label); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.element, tooltip)); this.addEventHandlers(handler); } diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 26acd42d459..731653a4828 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -42,6 +42,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { GroupModelChangeKind } from 'vs/workbench/common/editor'; import { SearchFindInput } from 'vs/workbench/contrib/search/browser/searchFindInput'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; /** Specified in searchview.css */ const SingleLineInputHeight = 26; @@ -370,7 +371,9 @@ export class SearchWidget extends Widget { buttonSecondaryBackground: undefined, buttonSecondaryForeground: undefined, buttonSecondaryHoverBackground: undefined, - buttonSeparator: undefined + buttonSeparator: undefined, + title: nls.localize('search.replace.toggle.button.title', "Toggle Replace"), + hoverDelegate: getDefaultHoverDelegate('element'), }; this.toggleReplaceButton = this._register(new Button(parent, opts)); this.toggleReplaceButton.element.setAttribute('aria-expanded', 'false'); @@ -378,7 +381,6 @@ export class SearchWidget extends Widget { this.toggleReplaceButton.icon = searchHideReplaceIcon; // TODO@joao need to dispose this listener eventually this.toggleReplaceButton.onDidClick(() => this.onToggleReplaceButton()); - this.toggleReplaceButton.element.title = nls.localize('search.replace.toggle.button.title', "Toggle Replace"); } private renderSearchInput(parent: HTMLElement, options: ISearchWidgetOptions): void { @@ -441,6 +443,7 @@ export class SearchWidget extends Widget { isChecked: false, title: appendKeyBindingLabel(nls.localize('showContext', "Toggle Context Lines"), this.keybindingService.lookupKeybinding(ToggleSearchEditorContextLinesCommandId)), icon: searchShowContextIcon, + hoverDelegate: getDefaultHoverDelegate('element'), ...defaultToggleStyles }); this._register(this.showContextToggle.onChange(() => this.onContextLinesChanged())); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index dec67977c05..02b833d6737 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -62,6 +62,8 @@ import { UnusualLineTerminatorsDetector } from 'vs/editor/contrib/unusualLineTer import { defaultToggleStyles, getInputBoxStyle } from 'vs/platform/theme/browser/defaultStyles'; import { ILogService } from 'vs/platform/log/common/log'; import { SearchContext } from 'vs/workbench/contrib/search/common/constants'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; const RESULT_LINE_REGEX = /^(\s+)(\d+)(: | )(\s*)(.*)$/; const FILE_LINE_REGEX = /^(\S.*):$/; @@ -161,7 +163,8 @@ export class SearchEditor extends AbstractTextCodeEditor this.includesExcludesContainer = DOM.append(container, DOM.$('.includes-excludes')); // Toggle query details button - this.toggleQueryDetailsButton = DOM.append(this.includesExcludesContainer, DOM.$('.expand' + ThemeIcon.asCSSSelector(searchDetailsIcon), { tabindex: 0, role: 'button', title: localize('moreSearch', "Toggle Search Details") })); + this.toggleQueryDetailsButton = DOM.append(this.includesExcludesContainer, DOM.$('.expand' + ThemeIcon.asCSSSelector(searchDetailsIcon), { tabindex: 0, role: 'button' })); + this._register(setupCustomHover(getDefaultHoverDelegate('element'), this.toggleQueryDetailsButton, localize('moreSearch', "Toggle Search Details"))); this._register(DOM.addDisposableListener(this.toggleQueryDetailsButton, DOM.EventType.CLICK, e => { DOM.EventHelper.stop(e); this.toggleIncludesExcludes(); From 11a6b428f829f0b843f14fc278412d79ab4f2dc7 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 23 Feb 2024 15:06:44 +0100 Subject: [PATCH 1554/1897] Custom hover adoption for test explorer (#206086) ustom hover adoption for test explorer --- .../testing/browser/testingExplorerFilter.ts | 5 ++- .../testing/browser/testingExplorerView.ts | 42 +++++++++++-------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts index ba9050a610b..a7490c873cf 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts @@ -5,7 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { BaseActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { Action, IAction, IActionRunner, Separator } from 'vs/base/common/actions'; @@ -46,11 +46,12 @@ export class TestingExplorerFilter extends BaseActionViewItem { constructor( action: IAction, + options: IBaseActionViewItemOptions, @ITestExplorerFilterState private readonly state: ITestExplorerFilterState, @IInstantiationService private readonly instantiationService: IInstantiationService, @ITestService private readonly testService: ITestService, ) { - super(null, action); + super(null, action, options); this.updateFilterActiveState(); this._register(testService.excluded.onTestExclusionsChanged(this.updateFilterActiveState, this)); } diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index ba7f2800830..8eb58e118a3 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -5,8 +5,11 @@ import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { ActionBar, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button } from 'vs/base/browser/ui/button/button'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { DefaultKeyboardNavigationDelegate, IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; @@ -285,18 +288,18 @@ export class TestingExplorerView extends ViewPane { } /** @override */ - public override getActionViewItem(action: IAction): IActionViewItem | undefined { + public override getActionViewItem(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined { switch (action.id) { case TestCommandId.FilterAction: - this.filter.value = this.instantiationService.createInstance(TestingExplorerFilter, action); + this.filter.value = this.instantiationService.createInstance(TestingExplorerFilter, action, options); this.filterFocusListener.value = this.filter.value.onDidFocus(() => this.lastFocusState = LastFocusState.Input); return this.filter.value; case TestCommandId.RunSelectedAction: - return this.getRunGroupDropdown(TestRunProfileBitset.Run, action); + return this.getRunGroupDropdown(TestRunProfileBitset.Run, action, options); case TestCommandId.DebugSelectedAction: - return this.getRunGroupDropdown(TestRunProfileBitset.Debug, action); + return this.getRunGroupDropdown(TestRunProfileBitset.Debug, action, options); default: - return super.getActionViewItem(action); + return super.getActionViewItem(action, options); } } @@ -380,10 +383,10 @@ export class TestingExplorerView extends ViewPane { super.saveState(); } - private getRunGroupDropdown(group: TestRunProfileBitset, defaultAction: IAction) { + private getRunGroupDropdown(group: TestRunProfileBitset, defaultAction: IAction, options: IActionViewItemOptions) { const dropdownActions = this.getTestConfigGroupActions(group); if (dropdownActions.length < 2) { - return super.getActionViewItem(defaultAction); + return super.getActionViewItem(defaultAction, options); } const primaryAction = this.instantiationService.createInstance(MenuItemAction, { @@ -401,13 +404,13 @@ export class TestingExplorerView extends ViewPane { primaryAction, dropdownAction, dropdownActions, '', this.contextMenuService, - {} + options ); } private createFilterActionBar() { const bar = new ActionBar(this.treeHeader, { - actionViewItemProvider: action => this.getActionViewItem(action), + actionViewItemProvider: (action, options) => this.getActionViewItem(action, options), triggerKeys: { keyDown: false, keys: [] }, }); bar.push(new Action(TestCommandId.FilterAction)); @@ -442,6 +445,7 @@ class ResultSummaryView extends Disposable { private elementsWereAttached = false; private badgeType: TestingCountBadge; private lastBadge?: NumberBadge | IconBadge; + private countHover: ICustomHover; private readonly badgeDisposable = this._register(new MutableDisposable()); private readonly renderLoop = this._register(new RunOnceScheduler(() => this.render(), SUMMARY_RENDER_INTERVAL)); private readonly elements = dom.h('div.result-summary', [ @@ -472,6 +476,8 @@ class ResultSummaryView extends Disposable { } })); + this.countHover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.elements.count, '')); + const ab = this._register(new ActionBar(this.elements.rerun, { actionViewItemProvider: (action, options) => createActionViewItem(instantiationService, action, options), })); @@ -518,7 +524,7 @@ class ResultSummaryView extends Disposable { } count.textContent = `${counts.passed}/${counts.totalWillBeRun}`; - count.title = getTestProgressText(counts); + this.countHover.update(getTestProgressText(counts)); this.renderActivityBadge(counts); if (!this.elementsWereAttached) { @@ -1301,6 +1307,7 @@ class IdentityProvider implements IIdentityProvider { interface IErrorTemplateData { label: HTMLElement; + disposable: DisposableStore; } class ErrorRenderer implements ITreeRenderer { @@ -1318,7 +1325,7 @@ class ErrorRenderer implements ITreeRenderer, _: number, data: IErrorTemplateData): void { @@ -1330,12 +1337,11 @@ class ErrorRenderer implements ITreeRenderer + actionViewItemProvider: (action, options) => action instanceof MenuItemAction - ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) + ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }) : undefined })); @@ -1451,7 +1457,7 @@ class TestItemRenderer extends Disposable data.icon.className += ' retired'; } - data.label.title = getLabelForTestTreeElement(node.element); + data.elementDisposable.add(setupCustomHover(getDefaultHoverDelegate('mouse'), data.label, getLabelForTestTreeElement(node.element))); if (node.element.test.item.label.trim()) { dom.reset(data.label, ...renderLabelWithIcons(node.element.test.item.label)); } else { From 529e73d71b794d152671942cb3ae42a33e4c7d3b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 23 Feb 2024 15:15:06 +0100 Subject: [PATCH 1555/1897] api - sketch up `vscode.lm.makeChatRequest` alternative to requesting chat access (#206088) re https://github.com/microsoft/vscode/issues/205800 --- .../workbench/api/common/extHost.api.impl.ts | 16 ++++++++- .../api/common/extHostLanguageModels.ts | 36 +++++++++++++++++++ .../vscode.proposed.languageModels.d.ts | 26 ++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index f89c741dd49..3bf78d8ed24 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { combinedDisposable } from 'vs/base/common/lifecycle'; @@ -1436,6 +1436,20 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I onDidChangeLanguageModels: (listener, thisArgs?, disposables?) => { checkProposedApiEnabled(extension, 'languageModels'); return extHostChatProvider.onDidChangeProviders(listener, thisArgs, disposables); + }, + makeChatRequest(languageModel: string, messages: vscode.LanguageModelMessage[], optionsOrToken: { [name: string]: any } | vscode.CancellationToken, token?: vscode.CancellationToken) { + checkProposedApiEnabled(extension, 'languageModels'); + let options: Record; + if (CancellationToken.isCancellationToken(optionsOrToken)) { + options = {}; + token = optionsOrToken; + } else if (CancellationToken.isCancellationToken(token)) { + options = optionsOrToken; + token = token; + } else { + throw new Error('Invalid arguments'); + } + return extHostChatProvider.makeChatRequest(extension, languageModel, messages, options, token); } }; diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index 7baf1d9353d..aa45f8f58aa 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -38,6 +38,10 @@ class LanguageModelResponseStream { class LanguageModelRequest { + static fromError(err: Error): vscode.LanguageModelResponse { + return new LanguageModelRequest(Promise.reject(err), new CancellationTokenSource()).apiObject; + } + readonly apiObject: vscode.LanguageModelResponse; private readonly _responseStreams = new Map(); @@ -223,6 +227,38 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { } } + async makeChatRequest(extension: IExtensionDescription, languageModelId: string, messages: vscode.LanguageModelMessage[], options: Record, token: CancellationToken) { + + const from = extension.identifier; + // const justification = options?.justification; // TODO@jrieken + const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, undefined); + + if (!metadata || !this._languageModelIds.has(languageModelId)) { + return LanguageModelRequest.fromError(new Error(`Language model ${languageModelId} is unknown`)); + } + + if (this._isUsingAuth(from, metadata)) { + await this._getAuthAccess(extension, { identifier: metadata.extension, displayName: metadata.auth.providerLabel }, undefined); + + if (!this._modelAccessList.get(from)?.has(metadata.extension)) { + return LanguageModelRequest.fromError(new Error('Access to chat has been revoked')); + } + } + + const cts = new CancellationTokenSource(token); + const requestId = (Math.random() * 1e6) | 0; + const requestPromise = this._proxy.$fetchResponse(from, languageModelId, requestId, messages.map(typeConvert.LanguageModelMessage.from), options ?? {}, cts.token); + const res = new LanguageModelRequest(requestPromise, cts); + this._pendingRequest.set(requestId, { languageModelId, res }); + + requestPromise.finally(() => { + this._pendingRequest.delete(requestId); + cts.dispose(); + }); + + return res.apiObject; + } + async requestLanguageModelAccess(extension: IExtensionDescription, languageModelId: string, options?: vscode.LanguageModelAccessOptions): Promise { const from = extension.identifier; const justification = options?.justification; diff --git a/src/vscode-dts/vscode.proposed.languageModels.d.ts b/src/vscode-dts/vscode.proposed.languageModels.d.ts index 0e35351833b..bde1bd4aad4 100644 --- a/src/vscode-dts/vscode.proposed.languageModels.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModels.d.ts @@ -171,6 +171,32 @@ declare module 'vscode' { */ export function requestLanguageModelAccess(id: string, options?: LanguageModelAccessOptions): Thenable; + + + /** + * Make a chat request using a language model. + * + * *Note* that language model use may be subject to access restrictions and user consent. This function always returns a response-object + * but its {@link LanguageModelResponse.result `result`}-property may indicate failure, e.g. due to + * + * - user consent not given + * - quote limits exceeded + * - model does not exist + * + * @param languageModel A language model identifier. See {@link languageModels} for aviailable values. + * @param messages + * @param options + * @param token + */ + // TODO@API refine doc + // TODO@API define specific error types? + export function makeChatRequest(languageModel: string, messages: LanguageModelMessage[], options: { [name: string]: any }, token: CancellationToken): Thenable; + + /** + * @see {@link makeChatRequest} + */ + export function makeChatRequest(languageModel: string, messages: LanguageModelMessage[], token: CancellationToken): Thenable; + /** * The identifiers of all language models that are currently available. */ From aca9718d33e8f489cfd26903bf3eb2b7dedb714b Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 23 Feb 2024 15:17:44 +0100 Subject: [PATCH 1556/1897] Register treeview hover delegate (#206089) dispose treeview hover delegate --- src/vs/workbench/browser/parts/views/treeView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 0245752c809..0eb478a3544 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -1106,7 +1106,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer this.rerender())); this._register(this.themeService.onDidColorThemeChange(() => this.rerender())); this._register(checkboxStateHandler.onDidChangeCheckboxState(items => { From 95e10d938f4681a2d41df588a0b38788ebf5a43e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 23 Feb 2024 15:19:33 +0100 Subject: [PATCH 1557/1897] Remove `livePreview` and its implementation (#206073) * Remove `livePreview` and its implementation fixes https://github.com/microsoft/vscode/issues/205535 * make compiler happy --- .../browser/inlineChatController.ts | 5 +- .../browser/inlineChatFileCreationWidget.ts | 256 +++++++++ .../browser/inlineChatLivePreviewWidget.ts | 532 ------------------ .../browser/inlineChatStrategies.ts | 161 +----- .../contrib/inlineChat/common/inlineChat.ts | 4 +- .../test/browser/inlineChatController.test.ts | 9 +- 6 files changed, 262 insertions(+), 705 deletions(-) create mode 100644 src/vs/workbench/contrib/inlineChat/browser/inlineChatFileCreationWidget.ts delete mode 100644 src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 38ea05687a1..775b62fee68 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -41,7 +41,7 @@ import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IInlineChatSavingService } from './inlineChatSavingService'; import { EmptyResponse, ErrorResponse, ExpansionState, ReplyResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { IInlineChatSessionService } from './inlineChatSessionService'; -import { EditModeStrategy, IEditObserver, LivePreviewStrategy, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; +import { EditModeStrategy, IEditObserver, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; import { IInlineChatMessageAppender, InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatProgressItem, IInlineChatRequest, IInlineChatResponse, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -342,9 +342,6 @@ export class InlineChatController implements IEditorContribution { case EditMode.Preview: this._strategy = this._instaService.createInstance(PreviewStrategy, session, this._editor, this._zone.value); break; - case EditMode.LivePreview: - this._strategy = this._instaService.createInstance(LivePreviewStrategy, session, this._editor, this._zone.value); - break; case EditMode.Live: default: this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._zone.value); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatFileCreationWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatFileCreationWidget.ts new file mode 100644 index 00000000000..eca335cb375 --- /dev/null +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatFileCreationWidget.ts @@ -0,0 +1,256 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Dimension, h } from 'vs/base/browser/dom'; +import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Range } from 'vs/editor/common/core/range'; +import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; +import * as editorColorRegistry from 'vs/editor/common/core/editorColorRegistry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { INLINE_CHAT_ID, inlineChatRegionHighlight } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { Position } from 'vs/editor/common/core/position'; +import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { ResourceLabel } from 'vs/workbench/browser/labels'; +import { FileKind } from 'vs/platform/files/common/files'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ButtonBar, IButton } from 'vs/base/browser/ui/button/button'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { SaveReason, SideBySideEditor } from 'vs/workbench/common/editor'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IAction, toAction } from 'vs/base/common/actions'; +import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; +import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { Codicon } from 'vs/base/common/codicons'; +import { TAB_ACTIVE_MODIFIED_BORDER } from 'vs/workbench/common/theme'; +import { localize } from 'vs/nls'; +import { Event } from 'vs/base/common/event'; + +export class InlineChatFileCreatePreviewWidget extends ZoneWidget { + + private static TitleHeight = 35; + + private readonly _elements = h('div.inline-chat-newfile-widget@domNode', [ + h('div.title@title', [ + h('span.name.show-file-icons@name'), + h('span.detail@detail'), + ]), + h('div.editor@editor'), + ]); + + private readonly _name: ResourceLabel; + private readonly _previewEditor: ICodeEditor; + private readonly _previewStore = new MutableDisposable(); + private readonly _buttonBar: ButtonBarWidget; + private _dim: Dimension | undefined; + + constructor( + parentEditor: ICodeEditor, + @IInstantiationService instaService: IInstantiationService, + @IThemeService themeService: IThemeService, + @ITextModelService private readonly _textModelResolverService: ITextModelService, + @IEditorService private readonly _editorService: IEditorService, + ) { + super(parentEditor, { + showArrow: false, + showFrame: true, + frameColor: colorRegistry.asCssVariable(TAB_ACTIVE_MODIFIED_BORDER), + frameWidth: 1, + isResizeable: true, + isAccessible: true, + showInHiddenAreas: true, + ordinal: 10000 + 2 + }); + super.create(); + + this._name = instaService.createInstance(ResourceLabel, this._elements.name, { supportIcons: true }); + this._elements.detail.appendChild(renderIcon(Codicon.circleFilled)); + + const contributions = EditorExtensionsRegistry + .getEditorContributions() + .filter(c => c.id !== INLINE_CHAT_ID); + + this._previewEditor = instaService.createInstance(EmbeddedCodeEditorWidget, this._elements.editor, { + scrollBeyondLastLine: false, + stickyScroll: { enabled: false }, + minimap: { enabled: false }, + scrollbar: { alwaysConsumeMouseWheel: false, useShadows: true, ignoreHorizontalScrollbarInContentHeight: true, }, + }, { isSimpleWidget: true, contributions }, parentEditor); + + const doStyle = () => { + const theme = themeService.getColorTheme(); + const overrides: [target: string, source: string][] = [ + [colorRegistry.editorBackground, inlineChatRegionHighlight], + [editorColorRegistry.editorGutter, inlineChatRegionHighlight], + ]; + + for (const [target, source] of overrides) { + const value = theme.getColor(source); + if (value) { + this._elements.domNode.style.setProperty(colorRegistry.asCssVariableName(target), String(value)); + } + } + }; + doStyle(); + this._disposables.add(themeService.onDidColorThemeChange(doStyle)); + + this._buttonBar = instaService.createInstance(ButtonBarWidget); + this._elements.title.appendChild(this._buttonBar.domNode); + } + + override dispose(): void { + this._name.dispose(); + this._buttonBar.dispose(); + this._previewEditor.dispose(); + this._previewStore.dispose(); + super.dispose(); + } + + protected override _fillContainer(container: HTMLElement): void { + container.appendChild(this._elements.domNode); + } + + override show(): void { + throw new Error('Use showFileCreation'); + } + + async showCreation(where: Position, untitledTextModel: IUntitledTextEditorModel): Promise { + + const store = new DisposableStore(); + this._previewStore.value = store; + + this._name.element.setFile(untitledTextModel.resource, { + fileKind: FileKind.FILE, + fileDecorations: { badges: true, colors: true } + }); + + const actionSave = toAction({ + id: '1', + label: localize('save', "Create"), + run: () => untitledTextModel.save({ reason: SaveReason.EXPLICIT }) + }); + const actionSaveAs = toAction({ + id: '2', + label: localize('saveAs', "Create As"), + run: async () => { + const ids = this._editorService.findEditors(untitledTextModel.resource, { supportSideBySide: SideBySideEditor.ANY }); + await this._editorService.save(ids.slice(), { saveAs: true, reason: SaveReason.EXPLICIT }); + } + }); + + this._buttonBar.update([ + [actionSave, actionSaveAs], + [(toAction({ id: '3', label: localize('discard', "Discard"), run: () => untitledTextModel.revert() }))] + ]); + + store.add(Event.any( + untitledTextModel.onDidRevert, + untitledTextModel.onDidSave, + untitledTextModel.onDidChangeDirty, + untitledTextModel.onWillDispose + )(() => this.hide())); + + await untitledTextModel.resolve(); + + const ref = await this._textModelResolverService.createModelReference(untitledTextModel.resource); + store.add(ref); + + const model = ref.object.textEditorModel; + this._previewEditor.setModel(model); + + const lineHeight = this.editor.getOption(EditorOption.lineHeight); + + this._elements.title.style.height = `${InlineChatFileCreatePreviewWidget.TitleHeight}px`; + const titleHightInLines = InlineChatFileCreatePreviewWidget.TitleHeight / lineHeight; + + const maxLines = Math.max(4, Math.floor((this.editor.getLayoutInfo().height / lineHeight) * .33)); + const lines = Math.min(maxLines, model.getLineCount()); + + super.show(where, titleHightInLines + lines); + } + + override hide(): void { + this._previewStore.clear(); + super.hide(); + } + + // --- layout + + protected override revealRange(range: Range, isLastLine: boolean): void { + // ignore + } + + protected override _onWidth(widthInPixel: number): void { + if (this._dim) { + this._doLayout(this._dim.height, widthInPixel); + } + } + + protected override _doLayout(heightInPixel: number, widthInPixel: number): void { + + const { lineNumbersLeft } = this.editor.getLayoutInfo(); + this._elements.title.style.marginLeft = `${lineNumbersLeft}px`; + + const newDim = new Dimension(widthInPixel, heightInPixel); + if (!Dimension.equals(this._dim, newDim)) { + this._dim = newDim; + this._previewEditor.layout(this._dim.with(undefined, this._dim.height - InlineChatFileCreatePreviewWidget.TitleHeight)); + } + } +} + + +class ButtonBarWidget { + + private readonly _domNode = h('div.buttonbar-widget'); + private readonly _buttonBar: ButtonBar; + private readonly _store = new DisposableStore(); + + constructor( + @IContextMenuService private _contextMenuService: IContextMenuService, + ) { + this._buttonBar = new ButtonBar(this.domNode); + + } + + update(allActions: IAction[][]): void { + this._buttonBar.clear(); + let secondary = false; + for (const actions of allActions) { + let btn: IButton; + const [first, ...rest] = actions; + if (!first) { + continue; + } else if (rest.length === 0) { + // single action + btn = this._buttonBar.addButton({ ...defaultButtonStyles, secondary }); + } else { + btn = this._buttonBar.addButtonWithDropdown({ + ...defaultButtonStyles, + addPrimaryActionToDropdown: false, + actions: rest, + contextMenuProvider: this._contextMenuService + }); + } + btn.label = first.label; + this._store.add(btn.onDidClick(() => first.run())); + secondary = true; + } + } + + dispose(): void { + this._buttonBar.dispose(); + this._store.dispose(); + } + + get domNode() { + return this._domNode.root; + } +} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts deleted file mode 100644 index 85d9762dc22..00000000000 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts +++ /dev/null @@ -1,532 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Dimension, getWindow, h, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; -import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; -import { assertType } from 'vs/base/common/types'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; -import { EditorOption, IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { Range } from 'vs/editor/common/core/range'; -import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model'; -import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; -import * as editorColorRegistry from 'vs/editor/common/core/editorColorRegistry'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { INLINE_CHAT_ID, inlineChatDiffInserted, inlineChatDiffRemoved, inlineChatRegionHighlight } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { LineRange } from 'vs/editor/common/core/lineRange'; -import { Position } from 'vs/editor/common/core/position'; -import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; -import { ILogService } from 'vs/platform/log/common/log'; -import { invertLineRange, asRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; -import { ResourceLabel } from 'vs/workbench/browser/labels'; -import { FileKind } from 'vs/platform/files/common/files'; -import { HunkInformation, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { generateUuid } from 'vs/base/common/uuid'; -import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { ButtonBar, IButton } from 'vs/base/browser/ui/button/button'; -import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; -import { SaveReason, SideBySideEditor } from 'vs/workbench/common/editor'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IAction, toAction } from 'vs/base/common/actions'; -import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; -import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { Codicon } from 'vs/base/common/codicons'; -import { TAB_ACTIVE_MODIFIED_BORDER } from 'vs/workbench/common/theme'; -import { localize } from 'vs/nls'; -import { Event } from 'vs/base/common/event'; - -export class InlineChatLivePreviewWidget extends ZoneWidget { - - private readonly _hideId = `overlayDiff:${generateUuid()}`; - - private readonly _elements = h('div.inline-chat-diff-widget@domNode'); - - private readonly _decorationCollection: IEditorDecorationsCollection; - private readonly _diffEditor: DiffEditorWidget; - - private _dim: Dimension | undefined; - private _isVisible: boolean = false; - - constructor( - editor: ICodeEditor, - private readonly _session: Session, - options: IDiffEditorOptions, - onDidChangeDiff: (() => void) | undefined, - @IInstantiationService instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @ILogService private readonly _logService: ILogService, - @IAccessibilityService private readonly accessibilityService: IAccessibilityService, - ) { - super(editor, { showArrow: false, showFrame: false, isResizeable: false, isAccessible: true, allowUnlimitedHeight: true, showInHiddenAreas: true, keepEditorSelection: true, ordinal: 10000 + 1 }); - super.create(); - assertType(editor.hasModel()); - - this._decorationCollection = editor.createDecorationsCollection(); - - const diffContributions = EditorExtensionsRegistry - .getEditorContributions() - .filter(c => c.id !== INLINE_CHAT_ID && c.id !== FoldingController.ID); - - this._diffEditor = instantiationService.createInstance(EmbeddedDiffEditorWidget, this._elements.domNode, { - scrollbar: { useShadows: false, alwaysConsumeMouseWheel: false, ignoreHorizontalScrollbarInContentHeight: true, }, - scrollBeyondLastLine: false, - renderMarginRevertIcon: true, - renderOverviewRuler: false, - rulers: undefined, - overviewRulerBorder: undefined, - overviewRulerLanes: 0, - diffAlgorithm: 'advanced', - splitViewDefaultRatio: 0.35, - padding: { top: 0, bottom: 0 }, - folding: false, - diffCodeLens: false, - stickyScroll: { enabled: false }, - minimap: { enabled: false }, - isInEmbeddedEditor: true, - useInlineViewWhenSpaceIsLimited: false, - overflowWidgetsDomNode: editor.getOverflowWidgetsDomNode(), - onlyShowAccessibleDiffViewer: this.accessibilityService.isScreenReaderOptimized(), - ...options - }, { - originalEditor: { contributions: diffContributions }, - modifiedEditor: { contributions: diffContributions } - }, editor); - - this._disposables.add(this._diffEditor); - this._diffEditor.setModel({ original: this._session.textModel0, modified: editor.getModel() }); - this._diffEditor.updateOptions({ - lineDecorationsWidth: editor.getLayoutInfo().decorationsWidth - }); - - if (onDidChangeDiff) { - this._disposables.add(this._diffEditor.onDidUpdateDiff(() => { onDidChangeDiff(); })); - - const render = this._disposables.add(new MutableDisposable()); - this._disposables.add(this._diffEditor.onDidContentSizeChange(e => { - if (!this._isVisible || !e.contentHeightChanged) { - return; - } - render.value = runAtThisOrScheduleAtNextAnimationFrame(getWindow(this._diffEditor.getContainerDomNode()), () => { - const lineHeight = this.editor.getOption(EditorOption.lineHeight); - const heightInLines = e.contentHeight / lineHeight; - this._logService.debug(`[IE] relaying with ${heightInLines} lines height`); - this._relayout(heightInLines); - }); - })); - } - - - const doStyle = () => { - const theme = themeService.getColorTheme(); - const overrides: [target: string, source: string][] = [ - [colorRegistry.editorBackground, inlineChatRegionHighlight], - [editorColorRegistry.editorGutter, inlineChatRegionHighlight], - [colorRegistry.diffInsertedLine, inlineChatDiffInserted], - [colorRegistry.diffInserted, inlineChatDiffInserted], - [colorRegistry.diffRemovedLine, inlineChatDiffRemoved], - [colorRegistry.diffRemoved, inlineChatDiffRemoved], - ]; - - for (const [target, source] of overrides) { - const value = theme.getColor(source); - if (value) { - this._elements.domNode.style.setProperty(colorRegistry.asCssVariableName(target), String(value)); - } - } - }; - doStyle(); - this._disposables.add(themeService.onDidColorThemeChange(doStyle)); - } - - - protected override _fillContainer(container: HTMLElement): void { - container.appendChild(this._elements.domNode); - } - - // --- show / hide -------------------- - - get isVisible(): boolean { - return this._isVisible; - } - - override hide(): void { - this._decorationCollection.clear(); - this._cleanupFullDiff(); - super.hide(); - this._isVisible = false; - } - - override show(): void { - throw new Error('use showForChanges'); - } - - showForChanges(hunk: HunkInformation): void { - const hasFocus = this._diffEditor.hasTextFocus(); - this._isVisible = true; - - const onlyInserts = hunk.isInsertion(); - - if (onlyInserts || this._session.textModel0.getValueLength() === 0) { - // no change or changes to an empty file - this._logService.debug('[IE] livePreview-mode: no diff'); - this._cleanupFullDiff(); - this._renderInsertWithHighlight(hunk); - } else { - // complex changes - this._logService.debug('[IE] livePreview-mode: full diff'); - this._decorationCollection.clear(); - this._renderChangesWithFullDiff(hunk); - } - - // TODO@jrieken find a better fix for this. this is the challenge: - // the `_updateFromChanges` method invokes show of the zone widget which removes and adds the - // zone and overlay parts. this dettaches and reattaches the dom nodes which means they lose - // focus - if (hasFocus) { - this._diffEditor.focus(); - } - } - - private _renderInsertWithHighlight(hunk: HunkInformation) { - assertType(this.editor.hasModel()); - - const options: IModelDecorationOptions = { - description: 'inline-chat-insert', - showIfCollapsed: false, - isWholeLine: true, - className: 'inline-chat-lines-inserted-range', - }; - - this._decorationCollection.set([{ - range: hunk.getRangesN()[0], - options - }]); - } - - // --- full diff - - private _renderChangesWithFullDiff(hunk: HunkInformation) { - assertType(this.editor.hasModel()); - - const ranges = this._computeHiddenRanges(this._session.textModelN, hunk); - - this._hideEditorRanges(this.editor, [ranges.modifiedHidden]); - this._hideEditorRanges(this._diffEditor.getOriginalEditor(), ranges.originalDiffHidden); - this._hideEditorRanges(this._diffEditor.getModifiedEditor(), ranges.modifiedDiffHidden); - - // this._diffEditor.revealLine(ranges.modifiedHidden.startLineNumber, ScrollType.Immediate); - - const lineCountModified = ranges.modifiedHidden.length; - const lineCountOriginal = ranges.originalHidden.length; - - const heightInLines = Math.max(lineCountModified, lineCountOriginal); - - super.show(ranges.anchor, heightInLines); - this._logService.debug(`[IE] diff SHOWING at ${ranges.anchor} with ${heightInLines} (approx) lines height`); - } - - private _cleanupFullDiff() { - this.editor.setHiddenAreas([], this._hideId); - this._diffEditor.getOriginalEditor().setHiddenAreas([], this._hideId); - this._diffEditor.getModifiedEditor().setHiddenAreas([], this._hideId); - super.hide(); - this._isVisible = false; - } - - private _computeHiddenRanges(model: ITextModel, hunk: HunkInformation) { - - - const modifiedLineRange = LineRange.fromRangeInclusive(hunk.getRangesN()[0]); - let originalLineRange = LineRange.fromRangeInclusive(hunk.getRanges0()[0]); - if (originalLineRange.isEmpty) { - originalLineRange = new LineRange(originalLineRange.startLineNumber, originalLineRange.endLineNumberExclusive + 1); - } - - const originalDiffHidden = invertLineRange(originalLineRange, this._session.textModel0); - const modifiedDiffHidden = invertLineRange(modifiedLineRange, model); - - return { - originalHidden: originalLineRange, - originalDiffHidden, - modifiedHidden: modifiedLineRange, - modifiedDiffHidden, - anchor: new Position(modifiedLineRange.startLineNumber - 1, 1) - }; - } - - private _hideEditorRanges(editor: ICodeEditor, lineRanges: LineRange[]): void { - assertType(editor.hasModel()); - - lineRanges = lineRanges.filter(range => !range.isEmpty); - if (lineRanges.length === 0) { - // todo? - this._logService.debug(`[IE] diff NOTHING to hide for ${editor.getId()} with ${String(editor.getModel()?.uri)}`); - return; - } - - let hiddenRanges: Range[]; - const hiddenLinesCount = lineRanges.reduce((p, c) => p + c.length, 0); // assumes no overlap - if (hiddenLinesCount >= editor.getModel().getLineCount()) { - // TODO: not every line can be hidden, keep the first line around - hiddenRanges = [editor.getModel().getFullModelRange().delta(1)]; - } else { - hiddenRanges = lineRanges.map(lr => asRange(lr, editor.getModel())); - } - editor.setHiddenAreas(hiddenRanges, this._hideId); - this._logService.debug(`[IE] diff HIDING ${hiddenRanges} for ${editor.getId()} with ${String(editor.getModel()?.uri)}`); - } - - protected override revealRange(range: Range, isLastLine: boolean): void { - // ignore - } - - // --- layout ------------------------- - - protected override _onWidth(widthInPixel: number): void { - if (this._dim) { - this._doLayout(this._dim.height, widthInPixel); - } - } - - protected override _doLayout(heightInPixel: number, widthInPixel: number): void { - const newDim = new Dimension(widthInPixel, heightInPixel); - if (!Dimension.equals(this._dim, newDim)) { - this._dim = newDim; - this._diffEditor.layout(this._dim.with(undefined, this._dim.height)); - this._logService.debug('[IE] diff LAYOUT', this._dim); - } - } -} - - -export class InlineChatFileCreatePreviewWidget extends ZoneWidget { - - private static TitleHeight = 35; - - private readonly _elements = h('div.inline-chat-newfile-widget@domNode', [ - h('div.title@title', [ - h('span.name.show-file-icons@name'), - h('span.detail@detail'), - ]), - h('div.editor@editor'), - ]); - - private readonly _name: ResourceLabel; - private readonly _previewEditor: ICodeEditor; - private readonly _previewStore = new MutableDisposable(); - private readonly _buttonBar: ButtonBarWidget; - private _dim: Dimension | undefined; - - constructor( - parentEditor: ICodeEditor, - @IInstantiationService instaService: IInstantiationService, - @IThemeService themeService: IThemeService, - @ITextModelService private readonly _textModelResolverService: ITextModelService, - @IEditorService private readonly _editorService: IEditorService, - ) { - super(parentEditor, { - showArrow: false, - showFrame: true, - frameColor: colorRegistry.asCssVariable(TAB_ACTIVE_MODIFIED_BORDER), - frameWidth: 1, - isResizeable: true, - isAccessible: true, - showInHiddenAreas: true, - ordinal: 10000 + 2 - }); - super.create(); - - this._name = instaService.createInstance(ResourceLabel, this._elements.name, { supportIcons: true }); - this._elements.detail.appendChild(renderIcon(Codicon.circleFilled)); - - const contributions = EditorExtensionsRegistry - .getEditorContributions() - .filter(c => c.id !== INLINE_CHAT_ID); - - this._previewEditor = instaService.createInstance(EmbeddedCodeEditorWidget, this._elements.editor, { - scrollBeyondLastLine: false, - stickyScroll: { enabled: false }, - minimap: { enabled: false }, - scrollbar: { alwaysConsumeMouseWheel: false, useShadows: true, ignoreHorizontalScrollbarInContentHeight: true, }, - }, { isSimpleWidget: true, contributions }, parentEditor); - - const doStyle = () => { - const theme = themeService.getColorTheme(); - const overrides: [target: string, source: string][] = [ - [colorRegistry.editorBackground, inlineChatRegionHighlight], - [editorColorRegistry.editorGutter, inlineChatRegionHighlight], - ]; - - for (const [target, source] of overrides) { - const value = theme.getColor(source); - if (value) { - this._elements.domNode.style.setProperty(colorRegistry.asCssVariableName(target), String(value)); - } - } - }; - doStyle(); - this._disposables.add(themeService.onDidColorThemeChange(doStyle)); - - this._buttonBar = instaService.createInstance(ButtonBarWidget); - this._elements.title.appendChild(this._buttonBar.domNode); - } - - override dispose(): void { - this._name.dispose(); - this._buttonBar.dispose(); - this._previewEditor.dispose(); - this._previewStore.dispose(); - super.dispose(); - } - - protected override _fillContainer(container: HTMLElement): void { - container.appendChild(this._elements.domNode); - } - - override show(): void { - throw new Error('Use showFileCreation'); - } - - async showCreation(where: Position, untitledTextModel: IUntitledTextEditorModel): Promise { - - const store = new DisposableStore(); - this._previewStore.value = store; - - this._name.element.setFile(untitledTextModel.resource, { - fileKind: FileKind.FILE, - fileDecorations: { badges: true, colors: true } - }); - - const actionSave = toAction({ - id: '1', - label: localize('save', "Create"), - run: () => untitledTextModel.save({ reason: SaveReason.EXPLICIT }) - }); - const actionSaveAs = toAction({ - id: '2', - label: localize('saveAs', "Create As"), - run: async () => { - const ids = this._editorService.findEditors(untitledTextModel.resource, { supportSideBySide: SideBySideEditor.ANY }); - await this._editorService.save(ids.slice(), { saveAs: true, reason: SaveReason.EXPLICIT }); - } - }); - - this._buttonBar.update([ - [actionSave, actionSaveAs], - [(toAction({ id: '3', label: localize('discard', "Discard"), run: () => untitledTextModel.revert() }))] - ]); - - store.add(Event.any( - untitledTextModel.onDidRevert, - untitledTextModel.onDidSave, - untitledTextModel.onDidChangeDirty, - untitledTextModel.onWillDispose - )(() => this.hide())); - - await untitledTextModel.resolve(); - - const ref = await this._textModelResolverService.createModelReference(untitledTextModel.resource); - store.add(ref); - - const model = ref.object.textEditorModel; - this._previewEditor.setModel(model); - - const lineHeight = this.editor.getOption(EditorOption.lineHeight); - - this._elements.title.style.height = `${InlineChatFileCreatePreviewWidget.TitleHeight}px`; - const titleHightInLines = InlineChatFileCreatePreviewWidget.TitleHeight / lineHeight; - - const maxLines = Math.max(4, Math.floor((this.editor.getLayoutInfo().height / lineHeight) * .33)); - const lines = Math.min(maxLines, model.getLineCount()); - - super.show(where, titleHightInLines + lines); - } - - override hide(): void { - this._previewStore.clear(); - super.hide(); - } - - // --- layout - - protected override revealRange(range: Range, isLastLine: boolean): void { - // ignore - } - - protected override _onWidth(widthInPixel: number): void { - if (this._dim) { - this._doLayout(this._dim.height, widthInPixel); - } - } - - protected override _doLayout(heightInPixel: number, widthInPixel: number): void { - - const { lineNumbersLeft } = this.editor.getLayoutInfo(); - this._elements.title.style.marginLeft = `${lineNumbersLeft}px`; - - const newDim = new Dimension(widthInPixel, heightInPixel); - if (!Dimension.equals(this._dim, newDim)) { - this._dim = newDim; - this._previewEditor.layout(this._dim.with(undefined, this._dim.height - InlineChatFileCreatePreviewWidget.TitleHeight)); - } - } -} - - -class ButtonBarWidget { - - private readonly _domNode = h('div.buttonbar-widget'); - private readonly _buttonBar: ButtonBar; - private readonly _store = new DisposableStore(); - - constructor( - @IContextMenuService private _contextMenuService: IContextMenuService, - ) { - this._buttonBar = new ButtonBar(this.domNode); - - } - - update(allActions: IAction[][]): void { - this._buttonBar.clear(); - let secondary = false; - for (const actions of allActions) { - let btn: IButton; - const [first, ...rest] = actions; - if (!first) { - continue; - } else if (rest.length === 0) { - // single action - btn = this._buttonBar.addButton({ ...defaultButtonStyles, secondary }); - } else { - btn = this._buttonBar.addButtonWithDropdown({ - ...defaultButtonStyles, - addPrimaryActionToDropdown: false, - actions: rest, - contextMenuProvider: this._contextMenuService - }); - } - btn.label = first.label; - this._store.add(btn.onDidClick(() => first.run())); - secondary = true; - } - } - - dispose(): void { - this._buttonBar.dispose(); - this._store.dispose(); - } - - get domNode() { - return this._domNode.root; - } -} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 63088a27ed5..30aa3f46b10 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -28,7 +28,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { Progress } from 'vs/platform/progress/common/progress'; import { SaveReason } from 'vs/workbench/common/editor'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; -import { InlineChatFileCreatePreviewWidget, InlineChatLivePreviewWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget'; +import { InlineChatFileCreatePreviewWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatFileCreationWidget'; import { HunkInformation, ReplyResponse, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, InlineChatConfigKeys, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -231,166 +231,7 @@ export interface ProgressingEditsOptions { token: CancellationToken; } -export class LivePreviewStrategy extends EditModeStrategy { - private readonly _previewZone: Lazy; - private readonly _diffZonePool: InlineChatLivePreviewWidget[] = []; - - constructor( - session: Session, - editor: ICodeEditor, - zone: InlineChatZoneWidget, - @IInstantiationService private readonly _instaService: IInstantiationService, - ) { - super(session, editor, zone); - - this._previewZone = new Lazy(() => _instaService.createInstance(InlineChatFileCreatePreviewWidget, editor)); - } - - override dispose(): void { - for (const zone of this._diffZonePool) { - zone.hide(); - zone.dispose(); - } - this._previewZone.rawValue?.hide(); - this._previewZone.rawValue?.dispose(); - super.dispose(); - } - - async apply() { - if (this._editCount > 0) { - this._editor.pushUndoStop(); - } - if (!(this._session.lastExchange?.response instanceof ReplyResponse)) { - return; - } - const { untitledTextModel } = this._session.lastExchange.response; - if (untitledTextModel && !untitledTextModel.isDisposed() && untitledTextModel.isDirty()) { - await untitledTextModel.save({ reason: SaveReason.EXPLICIT }); - } - } - - override async undoChanges(altVersionId: number): Promise { - const { textModelN } = this._session; - await undoModelUntil(textModelN, altVersionId); - this._updateDiffZones(); - } - - override async makeChanges(edits: ISingleEditOperation[], obs: IEditObserver): Promise { - return this._makeChanges(edits, obs, undefined, undefined); - } - - override async makeProgressiveChanges(edits: ISingleEditOperation[], obs: IEditObserver, opts: ProgressingEditsOptions): Promise { - await this._makeChanges(edits, obs, opts, new Progress(() => { - this._updateDiffZones(); - })); - } - - override async renderChanges(response: ReplyResponse): Promise { - - if (response.untitledTextModel && !response.untitledTextModel.isDisposed()) { - this._previewZone.value.showCreation(this._session.wholeRange.value.getStartPosition().delta(-1), response.untitledTextModel); - } else { - this._previewZone.rawValue?.hide(); - } - - return this._updateDiffZones(); - } - - - protected _updateSummaryMessage(hunkCount: number) { - let message: string; - if (hunkCount === 0) { - message = localize('change.0', "Nothing changed"); - } else if (hunkCount === 1) { - message = localize('change.1', "1 change"); - } else { - message = localize('lines.NM', "{0} changes", hunkCount); - } - this._zone.widget.updateStatus(message); - } - - - private _updateDiffZones(): Position | undefined { - - const { hunkData } = this._session; - const hunks = hunkData.getInfo().filter(hunk => hunk.getState() === HunkState.Pending); - - if (hunks.length === 0) { - for (const zone of this._diffZonePool) { - zone.hide(); - } - - if (hunkData.getInfo().find(hunk => hunk.getState() === HunkState.Accepted)) { - this._onDidAccept.fire(); - } else { - this._onDidDiscard.fire(); - } - - return; - } - - this._updateSummaryMessage(hunks.length); - - // create enough zones - const handleDiff = () => this._updateDiffZones(); - - type Data = { position: Position; distance: number; accept: Function; discard: Function }; - let nearest: Data | undefined; - - // create enough zones - while (hunks.length > this._diffZonePool.length) { - this._diffZonePool.push(this._instaService.createInstance(InlineChatLivePreviewWidget, this._editor, this._session, {}, this._diffZonePool.length === 0 ? handleDiff : undefined)); - } - - for (let i = 0; i < hunks.length; i++) { - const hunk = hunks[i]; - this._diffZonePool[i].showForChanges(hunk); - - const modifiedRange = hunk.getRangesN()[0]; - const zoneLineNumber = this._zone.position!.lineNumber; - const distance = zoneLineNumber <= modifiedRange.startLineNumber - ? modifiedRange.startLineNumber - zoneLineNumber - : zoneLineNumber - modifiedRange.endLineNumber; - - if (!nearest || nearest.distance > distance) { - nearest = { - position: modifiedRange.getStartPosition().delta(-1), - distance, - accept: () => { - hunk.acceptChanges(); - handleDiff(); - }, - discard: () => { - hunk.discardChanges(); - handleDiff(); - } - }; - } - - } - // hide unused zones - for (let i = hunks.length; i < this._diffZonePool.length; i++) { - this._diffZonePool[i].hide(); - } - - this.acceptHunk = async () => nearest?.accept(); - this.discardHunk = async () => nearest?.discard(); - - if (nearest) { - this._zone.updatePositionAndHeight(nearest.position); - this._editor.revealPositionInCenterIfOutsideViewport(nearest.position); - } - - return nearest?.position; - } - - override hasFocus(): boolean { - return this._zone.widget.hasFocus() - || Boolean(this._previewZone.rawValue?.hasFocus()) - || this._diffZonePool.some(zone => zone.isVisible && zone.hasFocus()); - } -} type HunkDisplayData = { diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index dbf95c3bb0b..10e6097adea 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -209,9 +209,7 @@ export const overviewRulerInlineChatDiffRemoved = registerColor('editorOverviewR export const enum EditMode { Live = 'live', - Preview = 'preview', - /** @deprecated */ - LivePreview = 'livePreview', + Preview = 'preview' } Registry.as(ExtensionsMigration.ConfigurationMigration).registerConfigurationMigrations( diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index f998c0969d7..6fd5722bc72 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -512,18 +512,15 @@ suite('InteractiveChatController', function () { configurationService.setUserConfiguration('inlineChat', { mode: EditMode.Live }); await makeRequest(); - configurationService.setUserConfiguration('inlineChat', { mode: EditMode.LivePreview }); - await makeRequest(); configurationService.setUserConfiguration('inlineChat', { mode: EditMode.Preview }); await makeRequest(); - assert.strictEqual(requests.length, 3); + assert.strictEqual(requests.length, 2); assert.strictEqual(requests[0].previewDocument.toString(), model.uri.toString()); // live - assert.strictEqual(requests[1].previewDocument.toString(), model.uri.toString()); // live preview - assert.strictEqual(requests[2].previewDocument.scheme, Schemas.vscode); // preview - assert.strictEqual(requests[2].previewDocument.authority, 'inline-chat'); + assert.strictEqual(requests[1].previewDocument.scheme, Schemas.vscode); // preview + assert.strictEqual(requests[1].previewDocument.authority, 'inline-chat'); }); test('start with existing exchange', async function () { From 10ddce6696c7a66b8f6d9965529ff00c97c967db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Cie=C5=9Blak?= Date: Fri, 23 Feb 2024 16:24:50 +0100 Subject: [PATCH 1558/1897] Fix off-by-one error in rendering removals in inline edits (#205890) --- src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts b/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts index da0fd80b543..298fcd4452e 100644 --- a/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts +++ b/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts @@ -176,7 +176,7 @@ export class GhostTextWidget extends Disposable { } else { const lines = uiState.range.endLineNumber - uiState.range.startLineNumber; - for (let i = 0; i <= lines; i++) { + for (let i = 0; i < lines; i++) { const line = uiState.range.startLineNumber + i; const firstNonWhitespace = uiState.targetTextModel.getLineFirstNonWhitespaceColumn(line); const lastNonWhitespace = uiState.targetTextModel.getLineLastNonWhitespaceColumn(line); From 18e68638bdb98413760e1b7a04af30e66a0d66ea Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 16:47:24 +0100 Subject: [PATCH 1559/1897] Fixes #204948 --- .../heuristicSequenceOptimizations.ts | 2 +- .../node/diffing/fixtures/issue-204948/1.txt | 9 ++++++++ .../node/diffing/fixtures/issue-204948/2.txt | 9 ++++++++ .../issue-204948/advanced.expected.diff.json | 22 +++++++++++++++++++ .../issue-204948/legacy.expected.diff.json | 22 +++++++++++++++++++ 5 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/vs/editor/test/node/diffing/fixtures/issue-204948/1.txt create mode 100644 src/vs/editor/test/node/diffing/fixtures/issue-204948/2.txt create mode 100644 src/vs/editor/test/node/diffing/fixtures/issue-204948/advanced.expected.diff.json create mode 100644 src/vs/editor/test/node/diffing/fixtures/issue-204948/legacy.expected.diff.json diff --git a/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts b/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts index 08efd578136..fddfb1e0c61 100644 --- a/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts +++ b/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts @@ -247,7 +247,7 @@ export function extendDiffsToEntireWordIfAppropriate(sequence1: LinesSliceCharSe while (equalMappings.length > 0) { const next = equalMappings[0]; - const intersects = next.seq1Range.intersects(w1) || next.seq2Range.intersects(w2); + const intersects = next.seq1Range.intersects(w.seq1Range) || next.seq2Range.intersects(w.seq2Range); if (!intersects) { break; } diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-204948/1.txt b/src/vs/editor/test/node/diffing/fixtures/issue-204948/1.txt new file mode 100644 index 00000000000..42f5b92add2 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-204948/1.txt @@ -0,0 +1,9 @@ + "@babel/types" "^7.22.15" + +"@babel/traverse@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305" + integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-204948/2.txt b/src/vs/editor/test/node/diffing/fixtures/issue-204948/2.txt new file mode 100644 index 00000000000..2b5867f0165 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-204948/2.txt @@ -0,0 +1,9 @@ + "@babel/types" "^7.22.15" + +"@babel/traverse@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305" + integrity sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-204948/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/issue-204948/advanced.expected.diff.json new file mode 100644 index 00000000000..4dd454f4a45 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-204948/advanced.expected.diff.json @@ -0,0 +1,22 @@ +{ + "original": { + "content": " \"@babel/types\" \"^7.22.15\"\n\n\"@babel/traverse@^7.23.9\":\n version \"7.23.9\"\n resolved \"https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305\"\n integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==\n dependencies:\n \"@babel/code-frame\" \"^7.23.5\"\n \"@babel/generator\" \"^7.23.6\"", + "fileName": "./1.txt" + }, + "modified": { + "content": " \"@babel/types\" \"^7.22.15\"\n\n\"@babel/traverse@^7.23.9\":\n version \"7.23.9\"\n resolved \"https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305\"\n integrity sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==\n dependencies:\n \"@babel/code-frame\" \"^7.23.5\"\n \"@babel/generator\" \"^7.23.6\"", + "fileName": "./2.txt" + }, + "diffs": [ + { + "originalRange": "[6,7)", + "modifiedRange": "[6,7)", + "innerChanges": [ + { + "originalRange": "[6,20 -> 7,1]", + "modifiedRange": "[6,20 -> 7,1]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-204948/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/issue-204948/legacy.expected.diff.json new file mode 100644 index 00000000000..97e5b8d4e12 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-204948/legacy.expected.diff.json @@ -0,0 +1,22 @@ +{ + "original": { + "content": " \"@babel/types\" \"^7.22.15\"\n\n\"@babel/traverse@^7.23.9\":\n version \"7.23.9\"\n resolved \"https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305\"\n integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==\n dependencies:\n \"@babel/code-frame\" \"^7.23.5\"\n \"@babel/generator\" \"^7.23.6\"", + "fileName": "./1.txt" + }, + "modified": { + "content": " \"@babel/types\" \"^7.22.15\"\n\n\"@babel/traverse@^7.23.9\":\n version \"7.23.9\"\n resolved \"https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305\"\n integrity sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==\n dependencies:\n \"@babel/code-frame\" \"^7.23.5\"\n \"@babel/generator\" \"^7.23.6\"", + "fileName": "./2.txt" + }, + "diffs": [ + { + "originalRange": "[6,7)", + "modifiedRange": "[6,7)", + "innerChanges": [ + { + "originalRange": "[6,20 -> 6,105]", + "modifiedRange": "[6,20 -> 6,105]" + } + ] + } + ] +} \ No newline at end of file From c6eed5d1b50413f7e6de8c5837e7095321d27050 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 16:53:38 +0100 Subject: [PATCH 1560/1897] Stricter assert --- .../defaultLinesDiffComputer.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts b/src/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts index e2de212aa40..b7c34e07604 100644 --- a/src/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts +++ b/src/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts @@ -13,11 +13,11 @@ import { DateTimeout, ITimeout, InfiniteTimeout, SequenceDiff } from 'vs/editor/ import { DynamicProgrammingDiffing } from 'vs/editor/common/diff/defaultLinesDiffComputer/algorithms/dynamicProgrammingDiffing'; import { MyersDiffAlgorithm } from 'vs/editor/common/diff/defaultLinesDiffComputer/algorithms/myersDiffAlgorithm'; import { computeMovedLines } from 'vs/editor/common/diff/defaultLinesDiffComputer/computeMovedLines'; -import { extendDiffsToEntireWordIfAppropriate, optimizeSequenceDiffs, removeVeryShortMatchingLinesBetweenDiffs, removeVeryShortMatchingTextBetweenLongDiffs, removeShortMatches } from 'vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations'; +import { extendDiffsToEntireWordIfAppropriate, optimizeSequenceDiffs, removeShortMatches, removeVeryShortMatchingLinesBetweenDiffs, removeVeryShortMatchingTextBetweenLongDiffs } from 'vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations'; +import { LineSequence } from 'vs/editor/common/diff/defaultLinesDiffComputer/lineSequence'; +import { LinesSliceCharSequence } from 'vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence'; import { ILinesDiffComputer, ILinesDiffComputerOptions, LinesDiff, MovedText } from 'vs/editor/common/diff/linesDiffComputer'; import { DetailedLineRangeMapping, RangeMapping } from '../rangeMapping'; -import { LinesSliceCharSequence } from 'vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence'; -import { LineSequence } from 'vs/editor/common/diff/defaultLinesDiffComputer/lineSequence'; export class DefaultLinesDiffComputer implements ILinesDiffComputer { private readonly dynamicProgrammingDiffing = new DynamicProgrammingDiffing(); @@ -256,8 +256,11 @@ export function lineRangeMappingFromRangeMappings(alignments: RangeMapping[], or } assertFn(() => { - if (!dontAssertStartLine) { - if (changes.length > 0 && changes[0].original.startLineNumber !== changes[0].modified.startLineNumber) { + if (!dontAssertStartLine && changes.length > 0) { + if (changes[0].modified.startLineNumber !== changes[0].original.startLineNumber) { + return false; + } + if (modifiedLines.length - changes[changes.length - 1].modified.endLineNumberExclusive !== originalLines.length - changes[changes.length - 1].original.endLineNumberExclusive) { return false; } } From cdb1a962ccd40d6d03132e7974a835c98f6e82e6 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Fri, 23 Feb 2024 16:46:47 +0100 Subject: [PATCH 1561/1897] rename controller: don't throw on cancellation & more type-safety to avoid `MessageController#showMessage` throwing --- src/vs/editor/contrib/rename/browser/rename.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index 8b7bc9b51b8..cb4f58e1073 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -6,7 +6,8 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { onUnexpectedError } from 'vs/base/common/errors'; +import { CancellationError, onUnexpectedError } from 'vs/base/common/errors'; +import { isMarkdownString } from 'vs/base/common/htmlContent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; @@ -192,9 +193,15 @@ class RenameController implements IEditorContribution { this._progressService.showWhile(resolveLocationOperation, 250); loc = await resolveLocationOperation; trace('resolved rename location'); - } catch (e) { - trace('resolve rename location failed', JSON.stringify(e, null, '\t')); - MessageController.get(this.editor)?.showMessage(e || nls.localize('resolveRenameLocationFailed', "An unknown error occurred while resolving rename location"), position); + } catch (e: unknown) { + if (e instanceof CancellationError) { + trace('resolve rename location cancelled', JSON.stringify(e, null, '\t')); + } else { + trace('resolve rename location failed', e instanceof Error ? e : JSON.stringify(e, null, '\t')); + if (typeof e === 'string' || isMarkdownString(e)) { + MessageController.get(this.editor)?.showMessage(e || nls.localize('resolveRenameLocationFailed', "An unknown error occurred while resolving rename location"), position); + } + } return undefined; } finally { From 12997e68fd1620ff0cb2cbfae3f7390a0c1e9e13 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 18:14:21 +0100 Subject: [PATCH 1562/1897] Fixes #196084 (#206100) --- extensions/git/src/commands.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 7f7f769fec5..01a8434c448 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1194,7 +1194,7 @@ export class CommandCenter { const activeTextEditor = window.activeTextEditor; // Must extract these now because opening a new document will change the activeTextEditor reference - const previousVisibleRange = activeTextEditor?.visibleRanges[0]; + const previousVisibleRanges = activeTextEditor?.visibleRanges; const previousURI = activeTextEditor?.document.uri; const previousSelection = activeTextEditor?.selection; @@ -1225,8 +1225,13 @@ export class CommandCenter { opts.selection = previousSelection; const editor = await window.showTextDocument(document, opts); // This should always be defined but just in case - if (previousVisibleRange) { - editor.revealRange(previousVisibleRange); + if (previousVisibleRanges && previousVisibleRanges.length > 0) { + let rangeToReveal = previousVisibleRanges[0]; + if (previousSelection && previousVisibleRanges.length > 1) { + // In case of multiple visible ranges, find the one that intersects with the selection + rangeToReveal = previousVisibleRanges.find(r => r.intersection(previousSelection)) ?? rangeToReveal; + } + editor.revealRange(rangeToReveal); } } } From bd4ba72a33caa9f52882bb695880790796e44f51 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 18:52:11 +0100 Subject: [PATCH 1563/1897] Fixes #199290 (#206112) --- .../features/hideUnchangedRegionsFeature.ts | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts index 6767a375a3c..248f2b46d81 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts @@ -59,27 +59,25 @@ export class HideUnchangedRegionsFeature extends Disposable { super(); this._register(this._editors.original.onDidChangeCursorPosition(e => { - if (e.reason === CursorChangeReason.Explicit) { - const m = this._diffModel.get(); - transaction(tx => { - for (const s of this._editors.original.getSelections() || []) { - m?.ensureOriginalLineIsVisible(s.getStartPosition().lineNumber, RevealPreference.FromCloserSide, tx); - m?.ensureOriginalLineIsVisible(s.getEndPosition().lineNumber, RevealPreference.FromCloserSide, tx); - } - }); - } + if (e.reason === CursorChangeReason.ContentFlush) { return; } + const m = this._diffModel.get(); + transaction(tx => { + for (const s of this._editors.original.getSelections() || []) { + m?.ensureOriginalLineIsVisible(s.getStartPosition().lineNumber, RevealPreference.FromCloserSide, tx); + m?.ensureOriginalLineIsVisible(s.getEndPosition().lineNumber, RevealPreference.FromCloserSide, tx); + } + }); })); this._register(this._editors.modified.onDidChangeCursorPosition(e => { - if (e.reason === CursorChangeReason.Explicit) { - const m = this._diffModel.get(); - transaction(tx => { - for (const s of this._editors.modified.getSelections() || []) { - m?.ensureModifiedLineIsVisible(s.getStartPosition().lineNumber, RevealPreference.FromCloserSide, tx); - m?.ensureModifiedLineIsVisible(s.getEndPosition().lineNumber, RevealPreference.FromCloserSide, tx); - } - }); - } + if (e.reason === CursorChangeReason.ContentFlush) { return; } + const m = this._diffModel.get(); + transaction(tx => { + for (const s of this._editors.modified.getSelections() || []) { + m?.ensureModifiedLineIsVisible(s.getStartPosition().lineNumber, RevealPreference.FromCloserSide, tx); + m?.ensureModifiedLineIsVisible(s.getEndPosition().lineNumber, RevealPreference.FromCloserSide, tx); + } + }); })); const unchangedRegions = this._diffModel.map((m, reader) => { From 436af204d46a337b78b94423ba66fccefd93c7f3 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 23 Feb 2024 11:00:23 -0700 Subject: [PATCH 1564/1897] Bump tas client (#206106) --- extensions/github-authentication/package.json | 2 +- extensions/github-authentication/yarn.lock | 48 +++--------- .../typescript-language-features/package.json | 2 +- .../typescript-language-features/yarn.lock | 77 +++---------------- 4 files changed, 20 insertions(+), 109 deletions(-) diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index dc26d5c07e8..d55e8dcfd03 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -61,7 +61,7 @@ "dependencies": { "node-fetch": "2.6.7", "@vscode/extension-telemetry": "^0.9.0", - "vscode-tas-client": "^0.1.47" + "vscode-tas-client": "^0.1.84" }, "devDependencies": { "@types/mocha": "^9.1.1", diff --git a/extensions/github-authentication/yarn.lock b/extensions/github-authentication/yarn.lock index a1c68b8d5a6..724b304c53e 100644 --- a/extensions/github-authentication/yarn.lock +++ b/extensions/github-authentication/yarn.lock @@ -132,15 +132,6 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -axios@^1.6.1: - version "1.6.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" - integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== - dependencies: - follow-redirects "^1.15.0" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -153,11 +144,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -follow-redirects@^1.15.0: - version "1.15.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" - integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== - form-data@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" @@ -167,15 +153,6 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - mime-db@1.44.0: version "1.44.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" @@ -195,29 +172,22 @@ node-fetch@2.6.7: dependencies: whatwg-url "^5.0.0" -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -tas-client@0.1.73: - version "0.1.73" - resolved "https://registry.yarnpkg.com/tas-client/-/tas-client-0.1.73.tgz#2dacf68547a37989ef1554c6510dc108a1ea7a71" - integrity sha512-UDdUF9kV2hYdlv+7AgqP2kXarVSUhjK7tg1BUflIRGEgND0/QoNpN64rcEuhEcM8AIbW65yrCopJWqRhLZ3m8w== - dependencies: - axios "^1.6.1" +tas-client@0.2.33: + version "0.2.33" + resolved "https://registry.yarnpkg.com/tas-client/-/tas-client-0.2.33.tgz#451bf114a8a64748030ce4068ab7d079958402e6" + integrity sha512-V+uqV66BOQnWxvI6HjDnE4VkInmYZUQ4dgB7gzaDyFyFSK1i1nF/j7DpS9UbQAgV9NaF1XpcyuavnM1qOeiEIg== tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= -vscode-tas-client@^0.1.47: - version "0.1.75" - resolved "https://registry.yarnpkg.com/vscode-tas-client/-/vscode-tas-client-0.1.75.tgz#771780a9a178163028299f52d41973300060dd38" - integrity sha512-/+ALFWPI4U3obeRvLFSt39guT7P9bZQrkmcLoiS+2HtzJ/7iPKNt5Sj+XTiitGlPYVFGFc0plxX8AAp6Uxs0xQ== +vscode-tas-client@^0.1.84: + version "0.1.84" + resolved "https://registry.yarnpkg.com/vscode-tas-client/-/vscode-tas-client-0.1.84.tgz#906bdcfd8c9e1dc04321d6bc0335184f9119968e" + integrity sha512-rUTrUopV+70hvx1hW5ebdw1nd6djxubkLvVxjGdyD/r5v/wcVF41LIfiAtbm5qLZDtQdsMH1IaCuDoluoIa88w== dependencies: - tas-client "0.1.73" + tas-client "0.2.33" webidl-conversions@^3.0.0: version "3.0.1" diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 9a5f8ff1f86..34dad264553 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -45,7 +45,7 @@ "@vscode/ts-package-manager": "^0.0.2", "jsonc-parser": "^3.2.0", "semver": "7.5.2", - "vscode-tas-client": "^0.1.63", + "vscode-tas-client": "^0.1.84", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index 482239a3cbb..bc72fe4cb8b 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -140,46 +140,6 @@ resolved "https://registry.yarnpkg.com/@vscode/ts-package-manager/-/ts-package-manager-0.0.2.tgz#d1cade5ff0d01da8c5b5b00bf79d80e7156771cf" integrity sha512-cXPxGbPVTkEQI8mUiWYUwB6j3ga6M9i7yubUOCrjgZ01GeZPMSnaWRprfJ09uuy81wJjY2gfHgLsOgwrGvUBTw== -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -axios@^1.6.1: - version "1.6.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" - integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== - dependencies: - follow-redirects "^1.15.0" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -follow-redirects@^1.15.0: - version "1.15.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" - integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== - -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - jsonc-parser@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" @@ -192,23 +152,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - semver@7.5.2: version "7.5.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.2.tgz#5b851e66d1be07c1cdaf37dfc856f543325a2beb" @@ -216,19 +159,17 @@ semver@7.5.2: dependencies: lru-cache "^6.0.0" -tas-client@0.1.73: - version "0.1.73" - resolved "https://registry.yarnpkg.com/tas-client/-/tas-client-0.1.73.tgz#2dacf68547a37989ef1554c6510dc108a1ea7a71" - integrity sha512-UDdUF9kV2hYdlv+7AgqP2kXarVSUhjK7tg1BUflIRGEgND0/QoNpN64rcEuhEcM8AIbW65yrCopJWqRhLZ3m8w== - dependencies: - axios "^1.6.1" +tas-client@0.2.33: + version "0.2.33" + resolved "https://registry.yarnpkg.com/tas-client/-/tas-client-0.2.33.tgz#451bf114a8a64748030ce4068ab7d079958402e6" + integrity sha512-V+uqV66BOQnWxvI6HjDnE4VkInmYZUQ4dgB7gzaDyFyFSK1i1nF/j7DpS9UbQAgV9NaF1XpcyuavnM1qOeiEIg== -vscode-tas-client@^0.1.63: - version "0.1.75" - resolved "https://registry.yarnpkg.com/vscode-tas-client/-/vscode-tas-client-0.1.75.tgz#771780a9a178163028299f52d41973300060dd38" - integrity sha512-/+ALFWPI4U3obeRvLFSt39guT7P9bZQrkmcLoiS+2HtzJ/7iPKNt5Sj+XTiitGlPYVFGFc0plxX8AAp6Uxs0xQ== +vscode-tas-client@^0.1.84: + version "0.1.84" + resolved "https://registry.yarnpkg.com/vscode-tas-client/-/vscode-tas-client-0.1.84.tgz#906bdcfd8c9e1dc04321d6bc0335184f9119968e" + integrity sha512-rUTrUopV+70hvx1hW5ebdw1nd6djxubkLvVxjGdyD/r5v/wcVF41LIfiAtbm5qLZDtQdsMH1IaCuDoluoIa88w== dependencies: - tas-client "0.1.73" + tas-client "0.2.33" vscode-uri@3.0.3: version "3.0.3" From 3bee3f48a5880e65c7525be0ff77042b7d295bdf Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 23 Feb 2024 10:30:11 -0800 Subject: [PATCH 1565/1897] fix: cwd not escaping when running wt.exe externally (#206113) We actually already set the cwd when spawning wt.exe, so we can use just `.` to reference it and avoid dealing with escaping entirely. --- .../platform/externalTerminal/node/externalTerminalService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/externalTerminal/node/externalTerminalService.ts b/src/vs/platform/externalTerminal/node/externalTerminalService.ts index 9fd38929726..a8df823266a 100644 --- a/src/vs/platform/externalTerminal/node/externalTerminalService.ts +++ b/src/vs/platform/externalTerminal/node/externalTerminalService.ts @@ -107,7 +107,7 @@ export class WindowsExternalTerminalService extends ExternalTerminalService impl // prefer to use the window terminal to spawn if it's available instead // of start, since that allows ctrl+c handling (#81322) spawnExec = wt; - cmdArgs = ['-d', dir || '.', exec, '/c', command]; // default dir fixes #204039 + cmdArgs = ['-d', '.', exec, '/c', command]; } else { spawnExec = WindowsExternalTerminalService.CMD; cmdArgs = ['/c', 'start', title, '/wait', exec, '/c', command]; From 25bc3dfec4e6d38d4899b98135e0bb369b6db51f Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Fri, 23 Feb 2024 10:37:02 -0800 Subject: [PATCH 1566/1897] fix: use `CancellationToken.None` (#206115) --- src/vs/workbench/api/browser/mainThreadShare.ts | 4 ++-- .../contrib/editSessions/test/browser/editSessions.test.ts | 4 ++-- src/vs/workbench/contrib/share/browser/share.contribution.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadShare.ts b/src/vs/workbench/api/browser/mainThreadShare.ts index 1974180b331..d517c23c906 100644 --- a/src/vs/workbench/api/browser/mainThreadShare.ts +++ b/src/vs/workbench/api/browser/mainThreadShare.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ExtHostContext, ExtHostShareShape, IDocumentFilterDto, MainContext, MainThreadShareShape } from 'vs/workbench/api/common/extHost.protocol'; @@ -31,7 +31,7 @@ export class MainThreadShare implements MainThreadShareShape { selector, priority, provideShare: async (item: IShareableItem) => { - const result = await this.proxy.$provideShare(handle, item, new CancellationTokenSource().token); + const result = await this.proxy.$provideShare(handle, item, CancellationToken.None); return typeof result === 'string' ? result : URI.revive(result); } }; diff --git a/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts index 4ad6b2fe900..c6be96b073d 100644 --- a/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts +++ b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts @@ -37,7 +37,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IDialogService, IPrompt } from 'vs/platform/dialogs/common/dialogs'; import { IEditorService, ISaveAllEditorsOptions } from 'vs/workbench/services/editor/common/editorService'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -213,7 +213,7 @@ suite('Edit session sync', () => { // Create root folder await fileService.createFolder(folderUri); - await editSessionsContribution.storeEditSession(true, new CancellationTokenSource().token); + await editSessionsContribution.storeEditSession(true, CancellationToken.None); // Verify that we did not attempt to write the edit session assert.equal(writeStub.called, false); diff --git a/src/vs/workbench/contrib/share/browser/share.contribution.ts b/src/vs/workbench/contrib/share/browser/share.contribution.ts index ae071141610..5bdf93d7236 100644 --- a/src/vs/workbench/contrib/share/browser/share.contribution.ts +++ b/src/vs/workbench/contrib/share/browser/share.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./share'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -110,7 +110,7 @@ class ShareWorkbenchContribution { const result = await progressService.withProgress({ location: ProgressLocation.Window, detail: localize('generating link', 'Generating link...') - }, async () => shareService.provideShare({ resourceUri, selection }, new CancellationTokenSource().token)); + }, async () => shareService.provideShare({ resourceUri, selection }, CancellationToken.None)); if (result) { const uriText = result.toString(); From ec72162ee60247944ec2faa16a08ce0ac580de76 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 23 Feb 2024 11:45:43 -0700 Subject: [PATCH 1567/1897] Deregister before registering to prevent view collisions (#206117) --- src/vs/workbench/contrib/files/browser/explorerViewlet.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 9785e04a5b1..0a79bee3fc6 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -94,12 +94,12 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor } } - if (viewDescriptorsToRegister.length) { - viewsRegistry.registerViews(viewDescriptorsToRegister, VIEW_CONTAINER); - } if (viewDescriptorsToDeregister.length) { viewsRegistry.deregisterViews(viewDescriptorsToDeregister, VIEW_CONTAINER); } + if (viewDescriptorsToRegister.length) { + viewsRegistry.registerViews(viewDescriptorsToRegister, VIEW_CONTAINER); + } mark('code/didRegisterExplorerViews'); } From ef1836b20c47096ef173f82783cfb01568e82f77 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 23 Feb 2024 11:37:30 -0800 Subject: [PATCH 1568/1897] Exit early when object is disposed Fixes #206116 --- .../stickyScroll/browser/terminalStickyScrollOverlay.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts index 8868f63beef..4dc0908e54e 100644 --- a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts @@ -95,6 +95,9 @@ export class TerminalStickyScrollOverlay extends Disposable { // Eagerly create the overlay xtermCtor.then(ctor => { + if (this._store.isDisposed) { + return; + } this._stickyScrollOverlay = this._register(new ctor({ rows: 1, cols: this._xterm.raw.cols, From 45e763b40ca1e5d45e92b66ad04d02f320f0a64b Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 23 Feb 2024 21:52:10 +0100 Subject: [PATCH 1569/1897] Configure web server base path (#202491) * add support for custom root URL path through command line option `--route-base-url-path` * fix CI/CD errors * refine path joining in `RemoteAuthoritiesImpl` * revert changes to `.gitignore` * avoid RemotePaths global * polish. Option is now called `server-base-path` * revert uri changes * remove unnecessary file * remove unnecessary new line * revert address port change --------- Co-authored-by: sysadmin <> Co-authored-by: Jared C <28533997+oakaigh@users.noreply.github.com> Co-authored-by: sysadmin --- src/vs/base/common/network.ts | 19 ++++++++++++++++--- .../common/extensionResourceLoader.ts | 9 ++++----- .../browser/remoteAuthorityResolverService.ts | 5 +++-- .../remote/common/remoteAgentConnection.ts | 4 ++-- src/vs/platform/remote/common/remoteHosts.ts | 9 --------- .../remoteAuthorityResolverService.ts | 3 +-- .../node/remoteExtensionHostAgentServer.ts | 18 ++++++++++++------ .../server/node/serverEnvironmentService.ts | 7 +++++++ src/vs/server/node/webClientServer.ts | 10 ++++++---- src/vs/workbench/browser/web.api.ts | 7 +++++++ src/vs/workbench/browser/web.main.ts | 4 ++-- .../test/browser/configurationService.test.ts | 6 +++--- .../test/browser/extensionService.test.ts | 2 +- 13 files changed, 64 insertions(+), 39 deletions(-) diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 974d0c21743..5cbdad174b7 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -7,6 +7,7 @@ import * as errors from 'vs/base/common/errors'; import * as platform from 'vs/base/common/platform'; import { equalsIgnoreCase, startsWithIgnoreCase } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; +import * as paths from 'vs/base/common/path'; export namespace Schemas { @@ -144,7 +145,7 @@ class RemoteAuthoritiesImpl { private readonly _connectionTokens: { [authority: string]: string | undefined } = Object.create(null); private _preferredWebSchema: 'http' | 'https' = 'http'; private _delegate: ((uri: URI) => URI) | null = null; - private _remoteResourcesPath: string = `/${Schemas.vscodeRemoteResource}`; + private _serverRootPath: string = '/'; setPreferredWebSchema(schema: 'http' | 'https') { this._preferredWebSchema = schema; @@ -154,8 +155,16 @@ class RemoteAuthoritiesImpl { this._delegate = delegate; } - setServerRootPath(serverRootPath: string): void { - this._remoteResourcesPath = `${serverRootPath}/${Schemas.vscodeRemoteResource}`; + setServerRootPath(product: { quality?: string; commit?: string }, serverBasePath: string | undefined): void { + this._serverRootPath = getServerRootPath(product, serverBasePath); + } + + getServerRootPath(): string { + return this._serverRootPath; + } + + private get _remoteResourcesPath(): string { + return paths.posix.join(this._serverRootPath, Schemas.vscodeRemoteResource); } set(authority: string, host: string, port: number): void { @@ -202,6 +211,10 @@ class RemoteAuthoritiesImpl { export const RemoteAuthorities = new RemoteAuthoritiesImpl(); +export function getServerRootPath(product: { quality?: string; commit?: string }, basePath: string | undefined): string { + return paths.posix.join(basePath ?? '/', `${product.quality ?? 'oss'}-${product.commit ?? 'dev'}`); +} + /** * A string pointing to a path inside the app. It should not begin with ./ or ../ */ diff --git a/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts b/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts index e63c48d0c2f..f1660961c58 100644 --- a/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts +++ b/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts @@ -17,10 +17,9 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { RemoteAuthorities } from 'vs/base/common/network'; -import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; import { TargetPlatform } from 'vs/platform/extensions/common/extensions'; -const WEB_EXTENSION_RESOURCE_END_POINT = 'web-extension-resource'; +const WEB_EXTENSION_RESOURCE_END_POINT_SEGMENT = '/web-extension-resource/'; export const IExtensionResourceLoaderService = createDecorator('extensionResourceLoaderService'); @@ -67,7 +66,6 @@ export abstract class AbstractExtensionResourceLoaderService implements IExtensi readonly _serviceBrand: undefined; - private readonly _webExtensionResourceEndPoint: string; private readonly _extensionGalleryResourceUrlTemplate: string | undefined; private readonly _extensionGalleryAuthority: string | undefined; @@ -78,7 +76,6 @@ export abstract class AbstractExtensionResourceLoaderService implements IExtensi private readonly _environmentService: IEnvironmentService, private readonly _configurationService: IConfigurationService, ) { - this._webExtensionResourceEndPoint = `${getRemoteServerRootPath(_productService)}/${WEB_EXTENSION_RESOURCE_END_POINT}/`; if (_productService.extensionsGallery) { this._extensionGalleryResourceUrlTemplate = _productService.extensionsGallery.resourceUrlTemplate; this._extensionGalleryAuthority = this._extensionGalleryResourceUrlTemplate ? this._getExtensionGalleryAuthority(URI.parse(this._extensionGalleryResourceUrlTemplate)) : undefined; @@ -144,7 +141,9 @@ export abstract class AbstractExtensionResourceLoaderService implements IExtensi } protected _isWebExtensionResourceEndPoint(uri: URI): boolean { - return uri.path.startsWith(this._webExtensionResourceEndPoint); + const uriPath = uri.path, serverRootPath = RemoteAuthorities.getServerRootPath(); + // test if the path starts with the server root path followed by the web extension resource end point segment + return uriPath.startsWith(serverRootPath) && uriPath.startsWith(WEB_EXTENSION_RESOURCE_END_POINT_SEGMENT, serverRootPath.length); } } diff --git a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts index 529d9d74999..8b85c237151 100644 --- a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts @@ -15,7 +15,7 @@ import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRemoteAuthorityResolverService, IRemoteConnectionData, RemoteConnectionType, ResolvedAuthority, ResolvedOptions, ResolverResult, WebSocketRemoteConnection, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { getRemoteServerRootPath, parseAuthorityWithOptionalPort } from 'vs/platform/remote/common/remoteHosts'; +import { parseAuthorityWithOptionalPort } from 'vs/platform/remote/common/remoteHosts'; export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService { @@ -34,6 +34,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot isWorkbenchOptionsBasedResolution: boolean, connectionToken: Promise | string | undefined, resourceUriProvider: ((uri: URI) => URI) | undefined, + serverBasePath: string | undefined, @IProductService productService: IProductService, @ILogService private readonly _logService: ILogService, ) { @@ -44,7 +45,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot if (resourceUriProvider) { RemoteAuthorities.setDelegate(resourceUriProvider); } - RemoteAuthorities.setServerRootPath(getRemoteServerRootPath(productService)); + RemoteAuthorities.setServerRootPath(productService, serverBasePath); } async resolveAuthority(authority: string): Promise { diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index 9539650dec0..45ebbe8df04 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -9,6 +9,7 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { isCancellationError, onUnexpectedError } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { RemoteAuthorities } from 'vs/base/common/network'; import * as performance from 'vs/base/common/performance'; import { StopWatch } from 'vs/base/common/stopwatch'; import { generateUuid } from 'vs/base/common/uuid'; @@ -17,7 +18,6 @@ import { Client, ISocket, PersistentProtocol, SocketCloseEventType } from 'vs/ba import { ILogService } from 'vs/platform/log/common/log'; import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { RemoteAuthorityResolverError, RemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; import { ISignService } from 'vs/platform/sign/common/sign'; @@ -232,7 +232,7 @@ async function connectToRemoteExtensionHostAgent(opt let socket: ISocket; try { - socket = await createSocket(options.logService, options.remoteSocketFactoryService, options.connectTo, getRemoteServerRootPath(options), `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, connectionTypeToString(connectionType), `renderer-${connectionTypeToString(connectionType)}-${options.reconnectionToken}`, timeoutCancellationToken); + socket = await createSocket(options.logService, options.remoteSocketFactoryService, options.connectTo, RemoteAuthorities.getServerRootPath(), `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, connectionTypeToString(connectionType), `renderer-${connectionTypeToString(connectionType)}-${options.reconnectionToken}`, timeoutCancellationToken); } catch (error) { options.logService.error(`${logPrefix} socketFactory.connect() failed or timed out. Error:`); options.logService.error(error); diff --git a/src/vs/platform/remote/common/remoteHosts.ts b/src/vs/platform/remote/common/remoteHosts.ts index ccc99953c8d..ccf58f9accb 100644 --- a/src/vs/platform/remote/common/remoteHosts.ts +++ b/src/vs/platform/remote/common/remoteHosts.ts @@ -25,15 +25,6 @@ export function getRemoteName(authority: string | undefined): string | undefined return authority.substr(0, pos); } -/** - * The root path to use when accessing the remote server. The path contains the quality and commit of the current build. - * @param product - * @returns - */ -export function getRemoteServerRootPath(product: { quality?: string; commit?: string }): string { - return `/${product.quality ?? 'oss'}-${product.commit ?? 'dev'}`; -} - export function parseAuthorityWithPort(authority: string): { host: string; port: number } { const { host, port } = parseAuthority(authority); if (typeof port === 'undefined') { diff --git a/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts b/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts index debbe333ae8..9948495f898 100644 --- a/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts @@ -11,7 +11,6 @@ import { RemoteAuthorities } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRemoteAuthorityResolverService, IRemoteConnectionData, RemoteConnectionType, ResolvedAuthority, ResolvedOptions, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; import { ElectronRemoteResourceLoader } from 'vs/platform/remote/electron-sandbox/electronRemoteResourceLoader'; export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService { @@ -33,7 +32,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot this._canonicalURIRequests = new Map(); this._canonicalURIProvider = null; - RemoteAuthorities.setServerRootPath(getRemoteServerRootPath(productService)); + RemoteAuthorities.setServerRootPath(productService, undefined); // on the desktop we don't support custom server base paths } resolveAuthority(authority: string): Promise { diff --git a/src/vs/server/node/remoteExtensionHostAgentServer.ts b/src/vs/server/node/remoteExtensionHostAgentServer.ts index a90d28e82cf..84664bbb39a 100644 --- a/src/vs/server/node/remoteExtensionHostAgentServer.ts +++ b/src/vs/server/node/remoteExtensionHostAgentServer.ts @@ -15,7 +15,7 @@ import { CharCode } from 'vs/base/common/charCode'; import { isSigPipeError, onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { isEqualOrParent } from 'vs/base/common/extpath'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { connectionTokenQueryName, FileAccess, Schemas } from 'vs/base/common/network'; +import { connectionTokenQueryName, FileAccess, getServerRootPath, Schemas } from 'vs/base/common/network'; import { dirname, join } from 'vs/base/common/path'; import * as perf from 'vs/base/common/performance'; import * as platform from 'vs/base/common/platform'; @@ -33,7 +33,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { ConnectionType, ConnectionTypeRequest, ErrorMessage, HandshakeMessage, IRemoteExtensionHostStartParams, ITunnelConnectionStartParams, SignRequest } from 'vs/platform/remote/common/remoteAgentConnection'; import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; -import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ExtensionHostConnection } from 'vs/server/node/extensionHostConnection'; import { ManagementConnection } from 'vs/server/node/remoteExtensionManagement'; @@ -75,6 +74,7 @@ class RemoteExtensionHostAgentServer extends Disposable implements IServerAPI { private readonly _connectionToken: ServerConnectionToken, private readonly _vsdaMod: typeof vsda | null, hasWebClient: boolean, + serverBasePath: string | undefined, @IServerEnvironmentService private readonly _environmentService: IServerEnvironmentService, @IProductService private readonly _productService: IProductService, @ILogService private readonly _logService: ILogService, @@ -82,13 +82,13 @@ class RemoteExtensionHostAgentServer extends Disposable implements IServerAPI { ) { super(); - this._serverRootPath = getRemoteServerRootPath(_productService); + this._serverRootPath = getServerRootPath(_productService, serverBasePath); this._extHostConnections = Object.create(null); this._managementConnections = Object.create(null); this._allReconnectionTokens = new Set(); this._webClientServer = ( hasWebClient - ? this._instantiationService.createInstance(WebClientServer, this._connectionToken) + ? this._instantiationService.createInstance(WebClientServer, this._connectionToken, serverBasePath ?? '/', this._serverRootPath) : null ); this._logService.info(`Extension host agent started.`); @@ -665,6 +665,7 @@ export interface IServerAPI { } export async function createServer(address: string | net.AddressInfo | null, args: ServerParsedArgs, REMOTE_DATA_FOLDER: string): Promise { + const connectionToken = await determineServerConnectionToken(args); if (connectionToken instanceof ServerConnectionTokenParseError) { console.warn(connectionToken.message); @@ -774,15 +775,20 @@ export async function createServer(address: string | net.AddressInfo | null, arg return null; }); + let serverBasePath = args['server-base-path']; + if (serverBasePath && !serverBasePath.startsWith('/')) { + serverBasePath = `/${serverBasePath}`; + } + const hasWebClient = fs.existsSync(FileAccess.asFileUri('vs/code/browser/workbench/workbench.html').fsPath); if (hasWebClient && address && typeof address !== 'string') { // ships the web ui! const queryPart = (connectionToken.type !== ServerConnectionTokenType.None ? `?${connectionTokenQueryName}=${connectionToken.value}` : ''); - console.log(`Web UI available at http://localhost${address.port === 80 ? '' : `:${address.port}`}/${queryPart}`); + console.log(`Web UI available at http://localhost${address.port === 80 ? '' : `:${address.port}`}${serverBasePath ?? ''}${queryPart}`); } - const remoteExtensionHostAgentServer = instantiationService.createInstance(RemoteExtensionHostAgentServer, socketServer, connectionToken, vsdaMod, hasWebClient); + const remoteExtensionHostAgentServer = instantiationService.createInstance(RemoteExtensionHostAgentServer, socketServer, connectionToken, vsdaMod, hasWebClient, serverBasePath); perf.mark('code/server/ready'); const currentTime = performance.now(); diff --git a/src/vs/server/node/serverEnvironmentService.ts b/src/vs/server/node/serverEnvironmentService.ts index 900815a06d2..fce1842f1bd 100644 --- a/src/vs/server/node/serverEnvironmentService.ts +++ b/src/vs/server/node/serverEnvironmentService.ts @@ -19,6 +19,7 @@ export const serverOptions: OptionDescriptions> = { 'host': { type: 'string', cat: 'o', args: 'ip-address', description: nls.localize('host', "The host name or IP address the server should listen to. If not set, defaults to 'localhost'.") }, 'port': { type: 'string', cat: 'o', args: 'port | port range', description: nls.localize('port', "The port the server should listen to. If 0 is passed a random free port is picked. If a range in the format num-num is passed, a free port from the range (end inclusive) is selected.") }, 'socket-path': { type: 'string', cat: 'o', args: 'path', description: nls.localize('socket-path', "The path to a socket file for the server to listen to.") }, + 'server-base-path': { type: 'string', cat: 'o', args: 'path', description: nls.localize('server-base-path', "The path under which the web UI and the code server is provided. Defaults to '/'.`") }, 'connection-token': { type: 'string', cat: 'o', args: 'token', deprecates: ['connectionToken'], description: nls.localize('connection-token', "A secret that must be included with all requests.") }, 'connection-token-file': { type: 'string', cat: 'o', args: 'path', deprecates: ['connection-secret', 'connectionTokenFile'], description: nls.localize('connection-token-file', "Path to a file that contains the connection token.") }, 'without-connection-token': { type: 'boolean', cat: 'o', description: nls.localize('without-connection-token', "Run without a connection token. Only use this if the connection is secured by other means.") }, @@ -102,6 +103,12 @@ export interface ServerParsedArgs { port?: string; 'socket-path'?: string; + /** + * The path under which the web UI and the code server is provided. + * By defaults it is '/'.` + */ + 'server-base-path'?: string; + /** * A secret token that must be provided by the web client with all requests. * Use only `[0-9A-Za-z\-]`. diff --git a/src/vs/server/node/webClientServer.ts b/src/vs/server/node/webClientServer.ts index a46d6748d0b..feed129fc94 100644 --- a/src/vs/server/node/webClientServer.ts +++ b/src/vs/server/node/webClientServer.ts @@ -28,7 +28,6 @@ import { streamToBuffer } from 'vs/base/common/buffer'; import { IProductConfiguration } from 'vs/base/common/product'; import { isString } from 'vs/base/common/types'; import { CharCode } from 'vs/base/common/charCode'; -import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; const textMimeType = { @@ -104,13 +103,15 @@ export class WebClientServer { constructor( private readonly _connectionToken: ServerConnectionToken, + private readonly _basePath: string, + readonly serverRootPath: string, @IServerEnvironmentService private readonly _environmentService: IServerEnvironmentService, @ILogService private readonly _logService: ILogService, @IRequestService private readonly _requestService: IRequestService, @IProductService private readonly _productService: IProductService, ) { this._webExtensionResourceUrlTemplate = this._productService.extensionsGallery?.resourceUrlTemplate ? URI.parse(this._productService.extensionsGallery.resourceUrlTemplate) : undefined; - const serverRootPath = getRemoteServerRootPath(_productService); + this._staticRoute = `${serverRootPath}/static`; this._callbackRoute = `${serverRootPath}/callback`; this._webExtensionRoute = `${serverRootPath}/web-extension-resource`; @@ -128,7 +129,7 @@ export class WebClientServer { if (pathname.startsWith(this._staticRoute) && pathname.charCodeAt(this._staticRoute.length) === CharCode.Slash) { return this._handleStatic(req, res, parsedUrl); } - if (pathname === '/') { + if (pathname === this._basePath) { return this._handleRoot(req, res, parsedUrl); } if (pathname === this._callbackRoute) { @@ -262,7 +263,7 @@ export class WebClientServer { newQuery[key] = parsedUrl.query[key]; } } - const newLocation = url.format({ pathname: '/', query: newQuery }); + const newLocation = url.format({ pathname: parsedUrl.pathname, query: newQuery }); responseHeaders['Location'] = newLocation; res.writeHead(302, responseHeaders); @@ -326,6 +327,7 @@ export class WebClientServer { const workbenchWebConfiguration = { remoteAuthority, + remoteBaseUrl: this._basePath, _wrapWebWorkerExtHostInIframe, developmentOptions: { enableSmokeTestDriver: this._environmentService.args['enable-smoke-test-driver'] ? true : undefined, logLevel: this._logService.getLevel() }, settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined, diff --git a/src/vs/workbench/browser/web.api.ts b/src/vs/workbench/browser/web.api.ts index 50d76a2213c..196edf88796 100644 --- a/src/vs/workbench/browser/web.api.ts +++ b/src/vs/workbench/browser/web.api.ts @@ -142,6 +142,13 @@ export interface IWorkbenchConstructionOptions { */ readonly remoteAuthority?: string; + /** + * The server base path is the path where the workbench is served from. + * The path must be absolute (start with a slash). + * Corresponds to option `server-base-path` on the server side. + */ + readonly serverBasePath?: string; + /** * The connection token to send to the server. */ diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 9061af27859..b36400ec9f1 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -284,11 +284,11 @@ export class BrowserMain extends Disposable { // Register them early because they are needed for the profiles initialization await this.registerIndexedDBFileSystemProviders(environmentService, fileService, logService, loggerService, logsPath); - // Remote + const connectionToken = environmentService.options.connectionToken || getCookieValue(connectionTokenCookieName); const remoteResourceLoader = this.configuration.remoteResourceProvider ? new BrowserRemoteResourceLoader(fileService, this.configuration.remoteResourceProvider) : undefined; const resourceUriProvider = this.configuration.resourceUriProvider ?? remoteResourceLoader?.getResourceUriProvider(); - const remoteAuthorityResolverService = new RemoteAuthorityResolverService(!environmentService.expectsResolverExtension, connectionToken, resourceUriProvider, productService, logService); + const remoteAuthorityResolverService = new RemoteAuthorityResolverService(!environmentService.expectsResolverExtension, connectionToken, resourceUriProvider, this.configuration.serverBasePath, productService, logService); serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService); // Signing diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 0ca8625187e..a7ef7154baa 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -101,7 +101,7 @@ suite('WorkspaceContextService - Folder', () => { userDataProfileService, environmentService, TestProductService, - disposables.add(new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService)), + disposables.add(new RemoteAuthorityResolverService(false, undefined, undefined, undefined, TestProductService, logService)), new SignService(TestProductService), new NullLogService())), uriIdentityService, new NullLogService(), @@ -152,7 +152,7 @@ suite('WorkspaceContextService - Folder', () => { userDataProfileService, userDataProfilesService, fileService, - disposables.add(new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, disposables.add(new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService)), new SignService(TestProductService), new NullLogService())), + disposables.add(new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, disposables.add(new RemoteAuthorityResolverService(false, undefined, undefined, undefined, TestProductService, logService)), new SignService(TestProductService), new NullLogService())), uriIdentityService, new NullLogService(), new NullPolicyService())); @@ -184,7 +184,7 @@ suite('WorkspaceContextService - Folder', () => { userDataProfileService, userDataProfilesService, fileService, - disposables.add(new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, disposables.add(new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService)), new SignService(TestProductService), new NullLogService())), + disposables.add(new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, disposables.add(new RemoteAuthorityResolverService(false, undefined, undefined, undefined, TestProductService, logService)), new SignService(TestProductService), new NullLogService())), uriIdentityService, new NullLogService(), new NullPolicyService())); diff --git a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts index 84682289313..3ced43a5f42 100644 --- a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts +++ b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts @@ -250,7 +250,7 @@ suite('ExtensionService', () => { [IUserDataProfileService, TestUserDataProfileService], [IUriIdentityService, UriIdentityService], [IRemoteExtensionsScannerService, TestRemoteExtensionsScannerService], - [IRemoteAuthorityResolverService, new RemoteAuthorityResolverService(false, undefined, undefined, testProductService, new NullLogService())] + [IRemoteAuthorityResolverService, new RemoteAuthorityResolverService(false, undefined, undefined, undefined, testProductService, new NullLogService())] ])); extService = instantiationService.get(IExtensionService); }); From 9854e101dd46a652155d0af52224f377f5fa5c0a Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 23 Feb 2024 13:29:43 -0800 Subject: [PATCH 1570/1897] fix #204395 --- .../contrib/accessibility/browser/accessibleView.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index cfbfa70a242..3e90ba4b1a8 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -13,7 +13,7 @@ import { Event } from 'vs/base/common/event'; 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 { isMacintosh, isWindows } from 'vs/base/common/platform'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; @@ -458,7 +458,7 @@ export class AccessibleView extends Disposable { const exitThisDialogHint = verbose && !provider.options.position ? localize('exit', '\n\nExit this dialog (Escape).') : ''; this._currentContent = message + provider.provideContent() + readMoreLink + disableHelpHint + exitThisDialogHint; this._updateContextKeys(provider, true); - + const widgetIsFocused = this._editorWidget.hasTextFocus() || this._editorWidget.hasWidgetFocus(); this._getTextModel(URI.from({ path: `accessible-view-${provider.verbositySettingKey}`, scheme: 'accessible-view', fragment: this._currentContent })).then((model) => { if (!model) { return; @@ -483,6 +483,11 @@ export class AccessibleView extends Disposable { } else if (actionsHint) { ariaLabel = localize('accessibility-help-hint', "Accessibility Help, {0}", actionsHint); } + if (isWindows && widgetIsFocused) { + // prevent the screen reader on windows from reading + // the aria label again when it's refocused + ariaLabel = ''; + } this._editorWidget.updateOptions({ ariaLabel }); this._editorWidget.focus(); if (this._currentProvider?.options.position) { From e6132eaeff5fbf19b01f82cda7601c337e8d17ed Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:48:39 -0800 Subject: [PATCH 1571/1897] Preview selected detected link Fixes #206126 --- .../links/browser/terminalLink.ts | 4 ++ .../browser/terminalLinkDetectorAdapter.ts | 6 +- .../links/browser/terminalLinkQuickpick.ts | 69 ++++++++++++++++++- 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLink.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLink.ts index 4654d703f1d..84d9ca4fdd2 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLink.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLink.ts @@ -13,6 +13,8 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TerminalLinkType } from 'vs/workbench/contrib/terminalContrib/links/browser/links'; import { IHoverAction } from 'vs/platform/hover/browser/hover'; +import type { URI } from 'vs/base/common/uri'; +import type { IParsedLink } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing'; export class TerminalLink extends DisposableStore implements ILink { decorations: ILinkDecorations; @@ -30,6 +32,8 @@ export class TerminalLink extends DisposableStore implements ILink { private readonly _xterm: Terminal, readonly range: IBufferRange, readonly text: string, + readonly uri: URI | undefined, + readonly parsedLink: IParsedLink | undefined, readonly actions: IHoverAction[] | undefined, private readonly _viewportY: number, private readonly _activateCallback: (event: MouseEvent | undefined, uri: string) => Promise, diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkDetectorAdapter.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkDetectorAdapter.ts index 8226841a9a0..26c047ea42d 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkDetectorAdapter.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkDetectorAdapter.ts @@ -92,9 +92,7 @@ export class TerminalLinkDetectorAdapter extends Disposable implements ILinkProv const detectedLinks = await this._detector.detect(lines, startLine, endLine); for (const link of detectedLinks) { - links.push(this._createTerminalLink(link, async (event) => { - this._onDidActivateLink.fire({ link, event }); - })); + links.push(this._createTerminalLink(link, async (event) => this._onDidActivateLink.fire({ link, event }))); } return links; @@ -110,6 +108,8 @@ export class TerminalLinkDetectorAdapter extends Disposable implements ILinkProv this._detector.xterm, l.bufferRange, l.text, + l.uri, + l.parsedLink, l.actions, this._detector.xterm.buffer.active.viewportY, activateCallback, diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts index e954be537df..aafd2e45711 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts @@ -6,24 +6,37 @@ import { EventType } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; -import { QuickPickItem, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { QuickPickItem, IQuickInputService, IQuickPickItem, QuickInputHideReason } from 'vs/platform/quickinput/common/quickInput'; import { IDetectedLinks } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager'; import { TerminalLinkQuickPickEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; import type { ILink } from '@xterm/xterm'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import type { TerminalLink } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLink'; +import { Sequencer } from 'vs/base/common/async'; +import { EditorViewState } from 'vs/workbench/browser/quickaccess'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { getLinkSuffix } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing'; +import type { ITextEditorSelection } from 'vs/platform/editor/common/editor'; export class TerminalLinkQuickpick extends DisposableStore { + private readonly _editorSequencer = new Sequencer(); + private readonly _editorViewState: EditorViewState; + private readonly _onDidRequestMoreLinks = this.add(new Emitter()); readonly onDidRequestMoreLinks = this._onDidRequestMoreLinks.event; constructor( + @IEditorService private readonly _editorService: IEditorService, + @IHistoryService private readonly _historyService: IHistoryService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService ) { super(); + this._editorViewState = new EditorViewState(_editorService); } async show(links: { viewport: IDetectedLinks; all: Promise }): Promise { @@ -57,6 +70,9 @@ export class TerminalLinkQuickpick extends DisposableStore { pick.placeholder = localize('terminal.integrated.openDetectedLink', "Select the link to open, type to filter all links"); pick.sortByLabel = false; pick.show(); + if (pick.activeItems.length > 0) { + this._previewItem(pick.activeItems[0]); + } // Show all results only when filtering begins, this is done so the quick pick will show up // ASAP with only the viewport entries. @@ -93,8 +109,20 @@ export class TerminalLinkQuickpick extends DisposableStore { pick.items = picks; })); + disposables.add(pick.onDidChangeActive(async () => { + const [item] = pick.activeItems; + this._previewItem(item); + })); + return new Promise(r => { - disposables.add(pick.onDidHide(() => { + disposables.add(pick.onDidHide(({ reason }) => { + // Restore view state upon cancellation if we changed it + // but only when the picker was closed via explicit user + // gesture and not e.g. when focus was lost because that + // could mean the user clicked into the editor directly. + if (reason === QuickInputHideReason.Gesture) { + this._editorViewState.restore(true); + } disposables.dispose(); if (pick.selectedItems.length === 0) { this._accessibleViewService.showLastProvider(AccessibleViewProviderId.Terminal); @@ -132,10 +160,45 @@ export class TerminalLinkQuickpick extends DisposableStore { } return picks.length > 0 ? picks : undefined; } + + private _previewItem(item: ITerminalLinkQuickPickItem | IQuickPickItem) { + if (item && 'link' in item && item.link && 'uri' in item.link && item.link.uri) { + this._editorViewState.set(); + const link = item.link; + const uri = link.uri; + + + + const linkSuffix = link.parsedLink ? link.parsedLink.suffix : getLinkSuffix(link.text); + let selection: ITextEditorSelection | undefined;// = link.selection; + if (!selection) { + selection = linkSuffix?.row === undefined ? undefined : { + startLineNumber: linkSuffix.row ?? 1, + startColumn: linkSuffix.col ?? 1, + endLineNumber: linkSuffix.rowEnd, + endColumn: linkSuffix.colEnd + }; + } + + + this._editorSequencer.queue(async () => { + // disable and re-enable history service so that we can ignore this history entry + const disposable = this._historyService.suspendTracking(); + try { + await this._editorService.openEditor({ + resource: uri, + options: { preserveFocus: true, revealIfOpened: true, ignoreError: true, selection } + }); + } finally { + disposable.dispose(); + } + }); + } + } } export interface ITerminalLinkQuickPickItem extends IQuickPickItem { - link: ILink; + link: ILink | TerminalLink; } type LinkQuickPickItem = ITerminalLinkQuickPickItem | QuickPickItem; From 228a35f333e8ca1f3fe78b8d5a5ca558eab870f7 Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Fri, 23 Feb 2024 17:07:53 -0500 Subject: [PATCH 1572/1897] Fix accidental dedent for `in` and `when` dedent in Ruby comments --- extensions/ruby/language-configuration.json | 2 +- .../contrib/indentation/test/browser/indentation.test.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/extensions/ruby/language-configuration.json b/extensions/ruby/language-configuration.json index e61f3ac410f..e1125e0bf2b 100644 --- a/extensions/ruby/language-configuration.json +++ b/extensions/ruby/language-configuration.json @@ -26,6 +26,6 @@ ], "indentationRules": { "increaseIndentPattern": "^\\s*((begin|class|(private|protected)\\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|in|while|case)|([^#]*\\sdo\\b)|([^#]*=\\s*(case|if|unless)))\\b([^#\\{;]|(\"|'|\/).*\\4)*(#.*)?$", - "decreaseIndentPattern": "^\\s*([}\\]]([,)]?\\s*(#|$)|\\.[a-zA-Z_]\\w*\\b)|(end|rescue|ensure|else|elsif)\\b)|((in|when)\\s)" + "decreaseIndentPattern": "^\\s*([}\\]]([,)]?\\s*(#|$)|\\.[a-zA-Z_]\\w*\\b)|(end|rescue|ensure|else|elsif)\\b|(in|when)\\s)" } } diff --git a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts index 91666a05d19..516966c9477 100644 --- a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts +++ b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts @@ -373,16 +373,23 @@ suite('Editor Contrib - Auto Dedent On Type', () => { ['(', ')'] ], indentationRules: { - decreaseIndentPattern: /\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif)\b)|((in|when)\s)/, + decreaseIndentPattern: /^\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif)\b|(in|when)\s)/, increaseIndentPattern: /^\s*((begin|class|(private|protected)\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|in|while|case)|([^#]*\sdo\b)|([^#]*=\s*(case|if|unless)))\b([^#\{;]|(\"|'|\/).*\4)*(#.*)?$/, }, }); + viewModel.model.setValue(""); viewModel.type("def foo\n i"); viewModel.type("n", 'keyboard'); assert.strictEqual(model.getValue(), "def foo\n in"); viewModel.type(" ", 'keyboard'); assert.strictEqual(model.getValue(), "def foo\nin "); + + viewModel.model.setValue(""); + viewModel.type(" # in"); + assert.strictEqual(model.getValue(), " # in"); + viewModel.type(" ", 'keyboard'); + assert.strictEqual(model.getValue(), " # in "); improvedLanguageModel.dispose(); }); }); From c43cd02a59395b830e90ccaac720c0433c74371b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 23 Feb 2024 14:27:53 -0800 Subject: [PATCH 1573/1897] fix #203722 --- .../contrib/accessibility/browser/accessibleView.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index cfbfa70a242..1e7899de2d4 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -29,6 +29,7 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewDelegate, IContextViewService } from 'vs/platform/contextview/browser/contextView'; @@ -157,7 +158,8 @@ export class AccessibleView extends Disposable { @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @ILayoutService private readonly _layoutService: ILayoutService, - @IMenuService private readonly _menuService: IMenuService + @IMenuService private readonly _menuService: IMenuService, + @ICommandService private readonly _commandService: ICommandService ) { super(); @@ -252,6 +254,10 @@ export class AccessibleView extends Disposable { } } + activateLink(): void { + this._commandService.executeCommand('editor.action.openLink'); + } + showLastProvider(id: AccessibleViewProviderId): void { if (!this._lastProvider || this._lastProvider.options.id !== id) { return; @@ -509,7 +515,9 @@ export class AccessibleView extends Disposable { }; const disposableStore = new DisposableStore(); disposableStore.add(this._editorWidget.onKeyDown((e) => { - if (e.keyCode === KeyCode.Escape || shouldHide(e.browserEvent, this._keybindingService, this._configurationService)) { + if (e.keyCode === KeyCode.Enter) { + this.activateLink(); + } else if (e.keyCode === KeyCode.Escape || shouldHide(e.browserEvent, this._keybindingService, this._configurationService)) { hide(e); } else if (e.keyCode === KeyCode.KeyH && provider.options.readMoreUrl) { const url: string = provider.options.readMoreUrl; From db5fbe6fd930a7fe0a7fe03f432eb1aa71ca14bd Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 23 Feb 2024 14:31:18 -0800 Subject: [PATCH 1574/1897] simplify --- .../contrib/accessibility/browser/accessibleView.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 1e7899de2d4..130efbcc20c 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -254,9 +254,6 @@ export class AccessibleView extends Disposable { } } - activateLink(): void { - this._commandService.executeCommand('editor.action.openLink'); - } showLastProvider(id: AccessibleViewProviderId): void { if (!this._lastProvider || this._lastProvider.options.id !== id) { @@ -516,7 +513,7 @@ export class AccessibleView extends Disposable { const disposableStore = new DisposableStore(); disposableStore.add(this._editorWidget.onKeyDown((e) => { if (e.keyCode === KeyCode.Enter) { - this.activateLink(); + this._commandService.executeCommand('editor.action.openLink'); } else if (e.keyCode === KeyCode.Escape || shouldHide(e.browserEvent, this._keybindingService, this._configurationService)) { hide(e); } else if (e.keyCode === KeyCode.KeyH && provider.options.readMoreUrl) { From 9a07ceb9f7f1dc1803913105c816009cb4bfdec1 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 23 Feb 2024 15:02:22 -0800 Subject: [PATCH 1575/1897] cli: ensure the canonical snap exe is used for the CLI (#206133) Fixes #204907 --- cli/src/commands/tunnels.rs | 7 +--- cli/src/util/machine.rs | 81 ++++++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/cli/src/commands/tunnels.rs b/cli/src/commands/tunnels.rs index f9ae6883075..02a697c1793 100644 --- a/cli/src/commands/tunnels.rs +++ b/cli/src/commands/tunnels.rs @@ -50,10 +50,7 @@ use crate::{ AuthRequired, Next, ServeStreamParams, ServiceContainer, ServiceManager, }, util::{ - app_lock::AppMutex, - command::new_std_command, - errors::{wrap, AnyError, CodeError}, - prereqs::PreReqChecker, + app_lock::AppMutex, command::new_std_command, errors::{wrap, AnyError, CodeError}, machine::canonical_exe, prereqs::PreReqChecker }, }; use crate::{ @@ -231,7 +228,7 @@ pub async fn service( legal::require_consent(&ctx.paths, args.accept_server_license_terms)?; let current_exe = - std::env::current_exe().map_err(|e| wrap(e, "could not get current exe"))?; + canonical_exe().map_err(|e| wrap(e, "could not get current exe"))?; manager .register( diff --git a/cli/src/util/machine.rs b/cli/src/util/machine.rs index 4c7b6729e43..a573231c2b9 100644 --- a/cli/src/util/machine.rs +++ b/cli/src/util/machine.rs @@ -3,7 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -use std::{path::Path, time::Duration}; + use std::{ + ffi::OsString, + path::{Path, PathBuf}, + time::Duration, +}; use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt}; pub fn process_at_path_exists(pid: u32, name: &Path) -> bool { @@ -71,3 +75,78 @@ pub async fn wait_until_exe_deleted(current_exe: &Path, poll_ms: u64) { tokio::time::sleep(duration).await; } } + +/// Gets the canonical current exe location, referring to the "current" symlink +/// if running inside snap. +pub fn canonical_exe() -> std::io::Result { + canonical_exe_inner( + std::env::current_exe(), + std::env::var_os("SNAP"), + std::env::var_os("SNAP_REVISION"), + ) +} + +#[inline(always)] +#[allow(unused_variables)] +fn canonical_exe_inner( + exe: std::io::Result, + snap: Option, + rev: Option, +) -> std::io::Result { + let exe = exe?; + + #[cfg(target_os = "linux")] + if let (Some(snap), Some(rev)) = (snap, rev) { + if !exe.starts_with(snap) { + return Ok(exe); + } + + let mut out = PathBuf::new(); + for part in exe.iter() { + if part == rev { + out.push("current") + } else { + out.push(part) + } + } + + return Ok(out); + } + + Ok(exe) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + #[test] + #[cfg(target_os = "linux")] + fn test_canonical_exe_in_snap() { + let exe = canonical_exe_inner( + Ok(PathBuf::from("/snap/my-snap/1234/some/exe")), + Some("/snap/my-snap/1234".into()), + Some("1234".into()), + ) + .unwrap(); + assert_eq!(exe, PathBuf::from("/snap/my-snap/current/some/exe")); + } + + #[test] + fn test_canonical_exe_not_in_snap() { + let exe = canonical_exe_inner( + Ok(PathBuf::from("/not-in-snap")), + Some("/snap/my-snap/1234".into()), + Some("1234".into()), + ) + .unwrap(); + assert_eq!(exe, PathBuf::from("/not-in-snap")); + } + + #[test] + fn test_canonical_exe_not_in_snap2() { + let exe = canonical_exe_inner(Ok(PathBuf::from("/not-in-snap")), None, None).unwrap(); + assert_eq!(exe, PathBuf::from("/not-in-snap")); + } +} From 47002157e96daf36dfebb911ae5724a6e7710e60 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 23 Feb 2024 15:18:47 -0800 Subject: [PATCH 1576/1897] debug: fix loading state in triggered bp widget (#206140) Fixes #204699 --- .../contrib/debug/browser/breakpointWidget.ts | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 7289d25ba29..59a3a4dd4bf 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -265,30 +265,28 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi private createTriggerBreakpointInput(container: HTMLElement) { const breakpoints = this.debugService.getModel().getBreakpoints().filter(bp => bp !== this.breakpoint); + const breakpointOptions: ISelectOptionItem[] = [ + { text: nls.localize('noTriggerByBreakpoint', 'None'), isDisabled: true }, + ...breakpoints.map(bp => ({ + text: `${this.labelService.getUriLabel(bp.uri, { relative: true })}: ${bp.lineNumber}`, + description: nls.localize('triggerByLoading', 'Loading...') + })), + ]; const index = breakpoints.findIndex((bp) => this.breakpoint?.triggeredBy === bp.getId()); - let select = 0; - if (index > -1) { - select = index + 1; - } - - Promise.all(breakpoints.map(async (bp): Promise => ({ - text: `${this.labelService.getUriLabel(bp.uri, { relative: true })}: ${bp.lineNumber}`, - description: await this.textModelService.createModelReference(bp.uri).then(ref => { + for (const [i, bp] of breakpoints.entries()) { + this.textModelService.createModelReference(bp.uri).then(ref => { try { - return ref.object.textEditorModel.getLineContent(bp.lineNumber).trim(); + breakpointOptions[i + 1].description = ref.object.textEditorModel.getLineContent(bp.lineNumber).trim(); } finally { ref.dispose(); } - }, () => undefined), - }))).then(breakpoints => { - selectBreakpointBox.setOptions([ - { text: nls.localize('noTriggerByBreakpoint', 'None') }, - ...breakpoints - ], select); - }); + }).catch(() => { + breakpointOptions[i + 1].description = nls.localize('noBpSource', 'Could not load source.'); + }); + } - const selectBreakpointBox = this.selectBreakpointBox = new SelectBox([{ text: nls.localize('triggerByLoading', 'Loading...'), isDisabled: true }], 0, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('selectBreakpoint', 'Select breakpoint') }); + const selectBreakpointBox = this.selectBreakpointBox = new SelectBox(breakpointOptions, index + 1, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('selectBreakpoint', 'Select breakpoint') }); selectBreakpointBox.onDidSelect(e => { if (e.index === 0) { this.triggeredByBreakpointInput = undefined; From d63202a5382aa104f5515ea09053a2a21a2587c6 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 23 Feb 2024 16:05:26 -0800 Subject: [PATCH 1577/1897] Fix sharing copy paste data across editor groups (#206142) Fix sharing copy paste data across editor groups --- .../browser/copyPasteController.ts | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts index 10654d61a52..adc0684cfca 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts @@ -56,13 +56,20 @@ export class CopyPasteController extends Disposable implements IEditorContributi return editor.getContribution(CopyPasteController.ID); } - private readonly _editor: ICodeEditor; - - private _currentCopyOperation?: { + /** + * Global tracking the last copy operation. + * + * This is shared across all editors so that you can copy and paste between groups. + * + * TODO: figure out how to make this work with multiple windows + */ + private static _currentCopyOperation?: { readonly handle: string; readonly dataTransferPromise: CancelablePromise; }; + private readonly _editor: ICodeEditor; + private _currentPasteOperation?: CancelablePromise; private _pasteAsActionContext?: { readonly preferredId: string | undefined }; @@ -204,8 +211,8 @@ export class CopyPasteController extends Disposable implements IEditorContributi return dataTransfer; }); - this._currentCopyOperation?.dataTransferPromise.cancel(); - this._currentCopyOperation = { handle: handle, dataTransferPromise: promise }; + CopyPasteController._currentCopyOperation?.dataTransferPromise.cancel(); + CopyPasteController._currentCopyOperation = { handle: handle, dataTransferPromise: promise }; } private async handlePaste(e: ClipboardEvent) { @@ -436,8 +443,8 @@ export class CopyPasteController extends Disposable implements IEditorContributi } private async mergeInDataFromCopy(dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, token: CancellationToken): Promise { - if (metadata?.id && this._currentCopyOperation?.handle === metadata.id) { - const toMergeDataTransfer = await this._currentCopyOperation.dataTransferPromise; + if (metadata?.id && CopyPasteController._currentCopyOperation?.handle === metadata.id) { + const toMergeDataTransfer = await CopyPasteController._currentCopyOperation.dataTransferPromise; if (token.isCancellationRequested) { return; } From 660264a263fcfeb5e15604ecd8eaeb71ca98cd5f Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Sat, 24 Feb 2024 16:21:20 +0100 Subject: [PATCH 1578/1897] Adopt custom hover for extension and runtime view (#206154) adopt custom hover for extension and runtime view --- .../abstractRuntimeExtensionsEditor.ts | 12 ++++++--- .../extensions/browser/extensionEditor.ts | 24 ++++++++++++----- .../extensions/browser/extensionsWidgets.ts | 26 ++++++++++++++----- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index 991a3df035c..cb2c01bdf75 100644 --- a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -5,6 +5,8 @@ import { $, Dimension, addDisposableListener, append, clearNode } from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; @@ -364,13 +366,15 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { } else { title = nls.localize('extensionActivating', "Extension is activating..."); } - data.activationTime.title = title; + data.elementDisposables.push(setupCustomHover(getDefaultHoverDelegate('mouse'), data.activationTime, title)); clearNode(data.msgContainer); if (this._getUnresponsiveProfile(element.description.identifier)) { const el = $('span', undefined, ...renderLabelWithIcons(` $(alert) Unresponsive`)); - el.title = nls.localize('unresponsive.title', "Extension has caused the extension host to freeze."); + const extensionHostFreezTitle = nls.localize('unresponsive.title', "Extension has caused the extension host to freeze."); + data.elementDisposables.push(setupCustomHover(getDefaultHoverDelegate('mouse'), el, extensionHostFreezTitle)); + data.msgContainer.appendChild(el); } @@ -416,7 +420,9 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { } if (accessData?.current) { const element = $('span', undefined, nls.localize('requests count', "{0} Requests: {1} (Session)", feature.label, accessData.current.count)); - element.title = nls.localize('requests count title', "Last request was {0}. Overall Requests: {1}", fromNow(accessData.current.lastAccessed, true, true), accessData.totalCount); + const title = nls.localize('requests count title', "Last request was {0}. Overall Requests: {1}", fromNow(accessData.current.lastAccessed, true, true), accessData.totalCount); + data.elementDisposables.push(setupCustomHover(getDefaultHoverDelegate('mouse'), element, title)); + data.msgContainer.appendChild(element); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 21986a4b709..097f4a3c634 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -5,6 +5,8 @@ import { $, Dimension, addDisposableListener, append, setParentFlowTo } from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { CheckboxActionViewItem } from 'vs/base/browser/ui/toggle/toggle'; import { Action, IAction } from 'vs/base/common/actions'; @@ -188,7 +190,8 @@ class VersionWidget extends ExtensionWithDifferentGalleryVersionWidget { private readonly element: HTMLElement; constructor(container: HTMLElement) { super(); - this.element = append(container, $('code.version', { title: localize('extension version', "Extension Version") })); + this.element = append(container, $('code.version')); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.element, localize('extension version', "Extension Version"))); this.render(); } render(): void { @@ -268,25 +271,30 @@ export class ExtensionEditor extends EditorPane { const details = append(header, $('.details')); const title = append(details, $('.title')); - const name = append(title, $('span.name.clickable', { title: localize('name', "Extension name"), role: 'heading', tabIndex: 0 })); + const name = append(title, $('span.name.clickable', { role: 'heading', tabIndex: 0 })); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), name, localize('name', "Extension name"))); const versionWidget = new VersionWidget(title); - const preview = append(title, $('span.preview', { title: localize('preview', "Preview") })); + const preview = append(title, $('span.preview')); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), preview, localize('preview', "Preview"))); preview.textContent = localize('preview', "Preview"); const builtin = append(title, $('span.builtin')); builtin.textContent = localize('builtin', "Built-in"); const subtitle = append(details, $('.subtitle')); - const publisher = append(append(subtitle, $('.subtitle-entry')), $('.publisher.clickable', { title: localize('publisher', "Publisher"), tabIndex: 0 })); + const publisher = append(append(subtitle, $('.subtitle-entry')), $('.publisher.clickable', { tabIndex: 0 })); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), publisher, localize('publisher', "Publisher"))); publisher.setAttribute('role', 'button'); const publisherDisplayName = append(publisher, $('.publisher-name')); const verifiedPublisherWidget = this.instantiationService.createInstance(VerifiedPublisherWidget, append(publisher, $('.verified-publisher')), false); - const installCount = append(append(subtitle, $('.subtitle-entry')), $('span.install', { title: localize('install count', "Install count"), tabIndex: 0 })); + const installCount = append(append(subtitle, $('.subtitle-entry')), $('span.install', { tabIndex: 0 })); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), installCount, localize('install count', "Install count"))); const installCountWidget = this.instantiationService.createInstance(InstallCountWidget, installCount, false); - const rating = append(append(subtitle, $('.subtitle-entry')), $('span.rating.clickable', { title: localize('rating', "Rating"), tabIndex: 0 })); + const rating = append(append(subtitle, $('.subtitle-entry')), $('span.rating.clickable', { tabIndex: 0 })); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), rating, localize('rating', "Rating"))); rating.setAttribute('role', 'link'); // #132645 const ratingsWidget = this.instantiationService.createInstance(RatingsWidget, rating, false); @@ -914,7 +922,9 @@ export class ExtensionEditor extends EditorPane { append(extensionResourcesContainer, $('.additional-details-title', undefined, localize('resources', "Resources"))); const resourcesElement = append(extensionResourcesContainer, $('.resources')); for (const [label, uri] of resources) { - this.transientDisposables.add(onClick(append(resourcesElement, $('a.resource', { title: uri.toString(), tabindex: '0' }, label)), () => this.openerService.open(uri))); + const resource = append(resourcesElement, $('a.resource', { tabindex: '0' }, label)); + this.transientDisposables.add(onClick(resource, () => this.openerService.open(uri))); + this.transientDisposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), resource, uri.toString())); } } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index a4a887e596a..4ad3680b7d7 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -31,7 +31,7 @@ import { URI } from 'vs/base/common/uri'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import Severity from 'vs/base/common/severity'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { Color } from 'vs/base/common/color'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -41,6 +41,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export abstract class ExtensionWidget extends Disposable implements IExtensionContainer { private _extension: IExtension | null = null; @@ -124,6 +125,8 @@ export class InstallCountWidget extends ExtensionWidget { export class RatingsWidget extends ExtensionWidget { + private readonly containerHover: ICustomHover; + constructor( private container: HTMLElement, private small: boolean @@ -135,12 +138,13 @@ export class RatingsWidget extends ExtensionWidget { container.classList.add('small'); } + this.containerHover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), container, '')); + this.render(); } render(): void { this.container.innerText = ''; - this.container.title = ''; if (!this.extension) { return; @@ -159,7 +163,7 @@ export class RatingsWidget extends ExtensionWidget { } const rating = Math.round(this.extension.rating * 2) / 2; - this.container.title = localize('ratedLabel', "Average rating: {0} out of 5", rating); + this.containerHover.update(localize('ratedLabel', "Average rating: {0} out of 5", rating)); if (this.small) { append(this.container, $('span' + ThemeIcon.asCSSSelector(starFullIcon))); @@ -186,6 +190,7 @@ export class RatingsWidget extends ExtensionWidget { export class VerifiedPublisherWidget extends ExtensionWidget { private disposables = this._register(new DisposableStore()); + private readonly containerHover: ICustomHover; constructor( private container: HTMLElement, @@ -193,6 +198,7 @@ export class VerifiedPublisherWidget extends ExtensionWidget { @IOpenerService private readonly openerService: IOpenerService, ) { super(); + this.containerHover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), container, '')); this.render(); } @@ -209,7 +215,7 @@ export class VerifiedPublisherWidget extends ExtensionWidget { if (!this.small) { verifiedPublisher.tabIndex = 0; - verifiedPublisher.title = `Verified Domain: ${this.extension.publisherDomain.link}`; + this.containerHover.update(`Verified Domain: ${this.extension.publisherDomain.link}`); verifiedPublisher.setAttribute('role', 'link'); append(verifiedPublisher, $('span.extension-verified-publisher-domain', undefined, publisherDomainLink.authority.startsWith('www.') ? publisherDomainLink.authority.substring(4) : publisherDomainLink.authority)); @@ -239,7 +245,8 @@ export class SponsorWidget extends ExtensionWidget { return; } - const sponsor = append(this.container, $('span.sponsor.clickable', { tabIndex: 0, title: this.extension?.publisherSponsorLink })); + const sponsor = append(this.container, $('span.sponsor.clickable', { tabIndex: 0 })); + this.disposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), sponsor, this.extension?.publisherSponsorLink.toString() ?? '')); sponsor.setAttribute('role', 'link'); // #132645 const sponsorIconElement = renderIcon(sponsorIcon); const label = $('span', undefined, localize('sponsor', "Sponsor")); @@ -367,6 +374,7 @@ export class RemoteBadgeWidget extends ExtensionWidget { class RemoteBadge extends Disposable { readonly element: HTMLElement; + readonly elementHover: ICustomHover; constructor( private readonly tooltip: boolean, @@ -376,6 +384,7 @@ class RemoteBadge extends Disposable { ) { super(); this.element = $('div.extension-badge.extension-remote-badge'); + this.elementHover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.element, '')); this.render(); } @@ -397,7 +406,7 @@ class RemoteBadge extends Disposable { if (this.tooltip) { const updateTitle = () => { if (this.element && this.extensionManagementServerService.remoteExtensionManagementServer) { - this.element.title = localize('remote extension title', "Extension in {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label); + this.elementHover.update(localize('remote extension title', "Extension in {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label)); } }; this._register(this.labelService.onDidChangeFormatters(() => updateTitle())); @@ -435,6 +444,8 @@ export class ExtensionPackCountWidget extends ExtensionWidget { export class SyncIgnoredWidget extends ExtensionWidget { + private readonly disposables = this._register(new DisposableStore()); + constructor( private readonly container: HTMLElement, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -448,11 +459,12 @@ export class SyncIgnoredWidget extends ExtensionWidget { } render(): void { + this.disposables.clear(); this.container.innerText = ''; if (this.extension && this.extension.state === ExtensionState.Installed && this.userDataSyncEnablementService.isEnabled() && this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension)) { const element = append(this.container, $('span.extension-sync-ignored' + ThemeIcon.asCSSSelector(syncIgnoredIcon))); - element.title = localize('syncingore.label', "This extension is ignored during sync."); + this.disposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), element, localize('syncingore.label', "This extension is ignored during sync."))); element.classList.add(...ThemeIcon.asClassNameArray(syncIgnoredIcon)); } } From f10dd6906c031e1bfacfe6b1976883947688a636 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Feb 2024 07:35:39 -0800 Subject: [PATCH 1579/1897] Only preview detected links for files and when preview is on Part of #206126 --- .../links/browser/terminalLinkQuickpick.ts | 73 +++++++++++-------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts index aafd2e45711..6ceb6c3db32 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts @@ -20,6 +20,9 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { getLinkSuffix } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing'; import type { ITextEditorSelection } from 'vs/platform/editor/common/editor'; +import { TerminalBuiltinLinkType } from 'vs/workbench/contrib/terminalContrib/links/browser/links'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import type { IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; export class TerminalLinkQuickpick extends DisposableStore { @@ -30,6 +33,7 @@ export class TerminalLinkQuickpick extends DisposableStore { readonly onDidRequestMoreLinks = this._onDidRequestMoreLinks.event; constructor( + @IConfigurationService private readonly _configurationService: IConfigurationService, @IEditorService private readonly _editorService: IEditorService, @IHistoryService private readonly _historyService: IHistoryService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @@ -162,38 +166,45 @@ export class TerminalLinkQuickpick extends DisposableStore { } private _previewItem(item: ITerminalLinkQuickPickItem | IQuickPickItem) { - if (item && 'link' in item && item.link && 'uri' in item.link && item.link.uri) { - this._editorViewState.set(); - const link = item.link; - const uri = link.uri; - - - - const linkSuffix = link.parsedLink ? link.parsedLink.suffix : getLinkSuffix(link.text); - let selection: ITextEditorSelection | undefined;// = link.selection; - if (!selection) { - selection = linkSuffix?.row === undefined ? undefined : { - startLineNumber: linkSuffix.row ?? 1, - startColumn: linkSuffix.col ?? 1, - endLineNumber: linkSuffix.rowEnd, - endColumn: linkSuffix.colEnd - }; - } - - - this._editorSequencer.queue(async () => { - // disable and re-enable history service so that we can ignore this history entry - const disposable = this._historyService.suspendTracking(); - try { - await this._editorService.openEditor({ - resource: uri, - options: { preserveFocus: true, revealIfOpened: true, ignoreError: true, selection } - }); - } finally { - disposable.dispose(); - } - }); + if (!item || !('link' in item) || !item.link || !('uri' in item.link) || !item.link.uri) { + return; } + + const link = item.link; + if (link.type !== TerminalBuiltinLinkType.LocalFile) { + return; + } + + // Don't open if preview editors are disabled as it may open many editor + const config = this._configurationService.getValue(); + if (!config.workbench?.editor?.enablePreview) { + return; + } + + const linkSuffix = link.parsedLink ? link.parsedLink.suffix : getLinkSuffix(link.text); + let selection: ITextEditorSelection | undefined;// = link.selection; + if (!selection) { + selection = linkSuffix?.row === undefined ? undefined : { + startLineNumber: linkSuffix.row ?? 1, + startColumn: linkSuffix.col ?? 1, + endLineNumber: linkSuffix.rowEnd, + endColumn: linkSuffix.colEnd + }; + } + + this._editorViewState.set(); + this._editorSequencer.queue(async () => { + // disable and re-enable history service so that we can ignore this history entry + const disposable = this._historyService.suspendTracking(); + try { + await this._editorService.openEditor({ + resource: link.uri, + options: { preserveFocus: true, revealIfOpened: true, ignoreError: true, selection, } + }); + } finally { + disposable.dispose(); + } + }); } } From 523dd898669fd40d2587197655c33ee066f9f62a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Feb 2024 07:38:26 -0800 Subject: [PATCH 1580/1897] Simplify setting selection --- .../links/browser/terminalLinkQuickpick.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts index 6ceb6c3db32..0cea7b3f514 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts @@ -19,7 +19,6 @@ import { EditorViewState } from 'vs/workbench/browser/quickaccess'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { getLinkSuffix } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing'; -import type { ITextEditorSelection } from 'vs/platform/editor/common/editor'; import { TerminalBuiltinLinkType } from 'vs/workbench/contrib/terminalContrib/links/browser/links'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import type { IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; @@ -182,15 +181,12 @@ export class TerminalLinkQuickpick extends DisposableStore { } const linkSuffix = link.parsedLink ? link.parsedLink.suffix : getLinkSuffix(link.text); - let selection: ITextEditorSelection | undefined;// = link.selection; - if (!selection) { - selection = linkSuffix?.row === undefined ? undefined : { - startLineNumber: linkSuffix.row ?? 1, - startColumn: linkSuffix.col ?? 1, - endLineNumber: linkSuffix.rowEnd, - endColumn: linkSuffix.colEnd - }; - } + const selection = linkSuffix?.row === undefined ? undefined : { + startLineNumber: linkSuffix.row ?? 1, + startColumn: linkSuffix.col ?? 1, + endLineNumber: linkSuffix.rowEnd, + endColumn: linkSuffix.colEnd + }; this._editorViewState.set(); this._editorSequencer.queue(async () => { From d2f340649b56500692ae84c82b8fac41f4bf8832 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Sun, 25 Feb 2024 18:38:20 +0100 Subject: [PATCH 1581/1897] Adopt custom hover for inline chat widget (#206170) inline chat custom hover adoption --- src/vs/platform/actions/browser/buttonbar.ts | 19 ++++++++++++++----- .../inlineChat/browser/inlineChatWidget.ts | 10 ++++++++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/vs/platform/actions/browser/buttonbar.ts b/src/vs/platform/actions/browser/buttonbar.ts index 21d4c4c5fc6..94720d5b557 100644 --- a/src/vs/platform/actions/browser/buttonbar.ts +++ b/src/vs/platform/actions/browser/buttonbar.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { ButtonBar, IButton } from 'vs/base/browser/ui/button/button'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { ActionRunner, IAction, IActionRunner, SubmenuAction, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -29,6 +31,7 @@ export interface IWorkbenchButtonBarOptions { export class WorkbenchButtonBar extends ButtonBar { protected readonly _store = new DisposableStore(); + protected readonly _updateStore = new DisposableStore(); private readonly _actionRunner: IActionRunner; private readonly _onDidChange = new Emitter(); @@ -57,6 +60,7 @@ export class WorkbenchButtonBar extends ButtonBar { override dispose() { this._onDidChange.dispose(); + this._updateStore.dispose(); this._store.dispose(); super.dispose(); } @@ -65,8 +69,12 @@ export class WorkbenchButtonBar extends ButtonBar { const conifgProvider: IButtonConfigProvider = this._options?.buttonConfigProvider ?? (() => ({ showLabel: true })); + this._updateStore.clear(); this.clear(); + // Support instamt hover between buttons + const hoverDelegate = this._updateStore.add(getDefaultHoverDelegate('element', true)); + for (let i = 0; i < actions.length; i++) { const secondary = i > 0; @@ -107,15 +115,16 @@ export class WorkbenchButtonBar extends ButtonBar { } } const kb = this._keybindingService.lookupKeybinding(action.id); + let tooltip: string; if (kb) { - btn.element.title = localize('labelWithKeybinding', "{0} ({1})", action.label, kb.getLabel()); + tooltip = localize('labelWithKeybinding', "{0} ({1})", action.label, kb.getLabel()); } else { - btn.element.title = action.label; - + tooltip = action.label; } - btn.onDidClick(async () => { + this._updateStore.add(setupCustomHover(hoverDelegate, btn.element, tooltip)); + this._updateStore.add(btn.onDidClick(async () => { this._actionRunner.run(action); - }); + })); } this._onDidChange.fire(this); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 50bba01479e..084e20c5898 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -66,6 +66,7 @@ import { ExpansionState, HunkData, HunkInformation, Session } from 'vs/workbench import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_VISIBLE, IInlineChatFollowup, IInlineChatSlashCommand, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_FEEDBACK, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -373,12 +374,16 @@ export class InlineChatWidget { this._slashCommandContentWidget = new SlashCommandContentWidget(this._inputEditor); this._store.add(this._slashCommandContentWidget); + // Share hover delegates between toolbars to support instant hover between both + const hoverDelegate = this._store.add(getDefaultHoverDelegate('element', true)); + // toolbars this._store.add(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.editorToolbar, _options.menuId, { telemetrySource: 'interactiveEditorWidget-toolbar', toolbarOptions: { primaryGroup: 'main' }, - hiddenItemStrategy: HiddenItemStrategy.Ignore // keep it lean when hiding items and avoid a "..." overflow menu + hiddenItemStrategy: HiddenItemStrategy.Ignore, // keep it lean when hiding items and avoid a "..." overflow menu + hoverDelegate })); this._progressBar = new ProgressBar(this._elements.progress); @@ -387,7 +392,8 @@ export class InlineChatWidget { this._store.add(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.widgetToolbar, _options.widgetMenuId, { telemetrySource: 'interactiveEditorWidget-toolbar', - toolbarOptions: { primaryGroup: 'main' } + toolbarOptions: { primaryGroup: 'main' }, + hoverDelegate })); const workbenchMenubarOptions: IWorkbenchButtonBarOptions = { From 03f6a7894e4f3b21a2f4936eeb1dc762606a5bd4 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 26 Feb 2024 02:20:16 +0100 Subject: [PATCH 1582/1897] Updated actionViewItemProvider to include options parameter (#206188) Updated actionViewItemProvider to include options parameter in various components --- .../workbench/browser/parts/compositeBar.ts | 4 +-- .../browser/parts/globalCompositeBar.ts | 5 ++-- .../notifications/notificationsCenter.ts | 3 ++- .../parts/titlebar/commandCenterControl.ts | 11 +++++--- .../browser/parts/titlebar/titlebarPart.ts | 7 ++--- .../browser/parts/views/viewFilter.ts | 5 ++-- .../contrib/comments/browser/commentNode.ts | 26 +++++++++---------- .../extensions/browser/extensionEditor.ts | 8 +++--- .../extensions/browser/extensionsActions.ts | 10 ++++--- .../extensions/browser/extensionsList.ts | 7 ++--- .../contrib/markers/browser/markersTable.ts | 2 +- .../markers/browser/markersTreeViewer.ts | 6 ++--- .../markers/browser/markersViewActions.ts | 8 +++--- .../contrib/find/notebookFindReplaceWidget.ts | 8 +++--- .../notebook/browser/diff/diffComponents.ts | 4 +-- .../notebook/browser/diff/notebookDiffList.ts | 4 +-- .../browser/view/cellParts/cellToolbars.ts | 6 ++--- .../view/cellParts/codeCellRunToolbar.ts | 3 ++- .../viewParts/notebookTopCellToolbar.ts | 4 +-- .../preferences/browser/keybindingsEditor.ts | 5 ++-- .../preferences/browser/preferencesWidgets.ts | 4 +-- .../preferences/browser/settingsEditor2.ts | 4 +-- .../preferences/browser/settingsSearchMenu.ts | 3 +++ .../terminal/browser/terminalTabsList.ts | 4 +-- .../testing/browser/testingExplorerFilter.ts | 7 ++--- .../testing/browser/testingOutputPeek.ts | 4 +-- 26 files changed, 91 insertions(+), 71 deletions(-) diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 76d6c0c1ee4..27d0b3738ea 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -201,14 +201,14 @@ export class CompositeBar extends Widget implements ICompositeBar { create(parent: HTMLElement): HTMLElement { const actionBarDiv = parent.appendChild($('.composite-bar')); this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action instanceof CompositeOverflowActivityAction) { return this.compositeOverflowActionViewItem; } const item = this.model.findItem(action.id); return item && this.instantiationService.createInstance( CompositeActionViewItem, - { draggable: true, colors: this.options.colors, icon: this.options.icon, hoverOptions: this.options.activityHoverOptions, compact: this.options.compact }, + { ...options, draggable: true, colors: this.options.colors, icon: this.options.icon, hoverOptions: this.options.activityHoverOptions, compact: this.options.compact }, action as CompositeBarAction, item.pinnedAction, item.toggleBadgeAction, diff --git a/src/vs/workbench/browser/parts/globalCompositeBar.ts b/src/vs/workbench/browser/parts/globalCompositeBar.ts index db3471fcadc..3490432e312 100644 --- a/src/vs/workbench/browser/parts/globalCompositeBar.ts +++ b/src/vs/workbench/browser/parts/globalCompositeBar.ts @@ -72,15 +72,16 @@ export class GlobalCompositeBar extends Disposable { anchorAxisAlignment: AnchorAxisAlignment.HORIZONTAL }); this.globalActivityActionBar = this._register(new ActionBar(this.element, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action.id === GLOBAL_ACTIVITY_ID) { - return this.instantiationService.createInstance(GlobalActivityActionViewItem, this.contextMenuActionsProvider, { colors: this.colors, hoverOptions: this.activityHoverOptions }, contextMenuAlignmentOptions); + return this.instantiationService.createInstance(GlobalActivityActionViewItem, this.contextMenuActionsProvider, { ...options, colors: this.colors, hoverOptions: this.activityHoverOptions }, contextMenuAlignmentOptions); } if (action.id === ACCOUNTS_ACTIVITY_ID) { return this.instantiationService.createInstance(AccountsActivityActionViewItem, this.contextMenuActionsProvider, { + ...options, colors: this.colors, hoverOptions: this.activityHoverOptions }, diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index e4cbaff2272..40bcde5fdb4 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -173,7 +173,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente const notificationsToolBar = this._register(new ActionBar(toolbarContainer, { ariaLabel: localize('notificationsToolbar', "Notification Center Actions"), actionRunner, - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action.id === ConfigureDoNotDisturbAction.ID) { return this._register(this.instantiationService.createInstance(DropdownMenuActionViewItem, action, { getActions() { @@ -208,6 +208,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente return actions; }, }, this.contextMenuService, { + ...options, actionRunner, classNames: action.class, keybindingProvider: action => this.keybindingService.lookupKeybinding(action.id) diff --git a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts index 3843068e941..72eeea0b590 100644 --- a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts @@ -5,6 +5,7 @@ import { isActiveDocument, reset } from 'vs/base/browser/dom'; import { BaseActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; @@ -46,11 +47,11 @@ export class CommandCenterControl { primaryGroup: () => true, }, telemetrySource: 'commandCenter', - actionViewItemProvider: (action) => { + actionViewItemProvider: (action, options) => { if (action instanceof SubmenuItemAction && action.item.submenu === MenuId.CommandCenterCenter) { - return instantiationService.createInstance(CommandCenterCenterViewItem, action, windowTitle, hoverDelegate, {}); + return instantiationService.createInstance(CommandCenterCenterViewItem, action, windowTitle, { ...options, hoverDelegate }); } else { - return createActionViewItem(instantiationService, action, { hoverDelegate }); + return createActionViewItem(instantiationService, action, { ...options, hoverDelegate }); } } }); @@ -75,16 +76,18 @@ class CommandCenterCenterViewItem extends BaseActionViewItem { private static readonly _quickOpenCommandId = 'workbench.action.quickOpenWithModes'; + private readonly _hoverDelegate: IHoverDelegate; + constructor( private readonly _submenu: SubmenuItemAction, private readonly _windowTitle: WindowTitle, - private readonly _hoverDelegate: IHoverDelegate, options: IBaseActionViewItemOptions, @IKeybindingService private _keybindingService: IKeybindingService, @IInstantiationService private _instaService: IInstantiationService, @IEditorGroupsService private _editorGroupService: IEditorGroupsService, ) { super(undefined, _submenu.actions.find(action => action.id === 'workbench.action.quickOpenWithModes') ?? _submenu.actions[0], options); + this._hoverDelegate = options.hoverDelegate ?? getDefaultHoverDelegate('mouse'); } override render(container: HTMLElement): void { diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 05ad43933f8..176ac265172 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -532,7 +532,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { // --- Editor Actions const activeEditorPane = this.editorGroupsContainer.activeGroup?.activeEditorPane; if (activeEditorPane && activeEditorPane instanceof EditorPane) { - const result = activeEditorPane.getActionViewItem(action, { hoverDelegate: this.hoverDelegate }); + const result = activeEditorPane.getActionViewItem(action, options); if (result) { return result; @@ -540,7 +540,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } // Check extensions - return createActionViewItem(this.instantiationService, action, { ...options, hoverDelegate: this.hoverDelegate, menuAsChild: false }); + return createActionViewItem(this.instantiationService, action, { ...options, menuAsChild: false }); } private getKeybinding(action: IAction): ResolvedKeybinding | undefined { @@ -565,7 +565,8 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { anchorAlignmentProvider: () => AnchorAlignment.RIGHT, telemetrySource: 'titlePart', highlightToggledItems: this.editorActionsEnabled, // Only show toggled state for editor actions (Layout actions are not shown as toggled) - actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options) + actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options), + hoverDelegate: this.hoverDelegate })); if (this.editorActionsEnabled) { diff --git a/src/vs/workbench/browser/parts/views/viewFilter.ts b/src/vs/workbench/browser/parts/views/viewFilter.ts index 3e5763a2203..b6285e45c71 100644 --- a/src/vs/workbench/browser/parts/views/viewFilter.ts +++ b/src/vs/workbench/browser/parts/views/viewFilter.ts @@ -25,6 +25,7 @@ import { SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntr import { Widget } from 'vs/base/browser/ui/widget'; import { Emitter } from 'vs/base/common/event'; import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; const viewFilterMenu = new MenuId('menu.view.filter'); export const viewFilterSubmenu = new MenuId('submenu.view.filter'); @@ -196,9 +197,9 @@ export class FilterWidget extends Widget { return this.instantiationService.createInstance(MenuWorkbenchToolBar, container, viewFilterMenu, { hiddenItemStrategy: HiddenItemStrategy.NoHide, - actionViewItemProvider: (action: IAction) => { + actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => { if (action instanceof SubmenuItemAction && action.item.submenu.id === viewFilterSubmenu.id) { - this.moreFiltersActionViewItem = this.instantiationService.createInstance(MoreFiltersActionViewItem, action, undefined); + this.moreFiltersActionViewItem = this.instantiationService.createInstance(MoreFiltersActionViewItem, action, options); this.moreFiltersActionViewItem.checked = this.isMoreFiltersChecked; return this.moreFiltersActionViewItem; } diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 7085518f94a..58e376f9561 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -30,7 +30,7 @@ import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions'; import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor'; -import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { ActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; @@ -301,21 +301,22 @@ export class CommentNode extends Disposable { private createToolbar() { this.toolbar = new ToolBar(this._actionsToolbarContainer, this.contextMenuService, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action.id === ToggleReactionsAction.ID) { return new DropdownMenuActionViewItem( action, (action).menuActions, this.contextMenuService, { - actionViewItemProvider: action => this.actionViewItemProvider(action as Action), + ...options, + actionViewItemProvider: (action, options) => this.actionViewItemProvider(action as Action, options), actionRunner: this.actionRunner, classNames: ['toolbar-toggle-pickReactions', ...ThemeIcon.asClassNameArray(Codicon.reactions)], anchorAlignmentProvider: () => AnchorAlignment.RIGHT } ); } - return this.actionViewItemProvider(action as Action); + return this.actionViewItemProvider(action as Action, options); }, orientation: ActionsOrientation.HORIZONTAL }); @@ -357,8 +358,7 @@ export class CommentNode extends Disposable { } } - actionViewItemProvider(action: Action) { - let options = {}; + actionViewItemProvider(action: Action, options: IActionViewItemOptions) { if (action.id === ToggleReactionsAction.ID) { options = { label: false, icon: true }; } else { @@ -369,9 +369,9 @@ export class CommentNode extends Disposable { const item = new ReactionActionViewItem(action); return item; } else if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined); + return this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }); } else if (action instanceof SubmenuItemAction) { - return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, undefined); + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, options); } else { const item = new ActionViewItem({}, action, options); return item; @@ -413,11 +413,11 @@ export class CommentNode extends Disposable { (toggleReactionAction).menuActions, this.contextMenuService, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action.id === ToggleReactionsAction.ID) { return toggleReactionActionViewItem; } - return this.actionViewItemProvider(action as Action); + return this.actionViewItemProvider(action as Action, options); }, actionRunner: this.actionRunner, classNames: 'toolbar-toggle-pickReactions', @@ -431,21 +431,21 @@ export class CommentNode extends Disposable { private createReactionsContainer(commentDetailsContainer: HTMLElement): void { this._reactionActionsContainer = dom.append(commentDetailsContainer, dom.$('div.comment-reactions')); this._reactionsActionBar = new ActionBar(this._reactionActionsContainer, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action.id === ToggleReactionsAction.ID) { return new DropdownMenuActionViewItem( action, (action).menuActions, this.contextMenuService, { - actionViewItemProvider: action => this.actionViewItemProvider(action as Action), + actionViewItemProvider: (action, options) => this.actionViewItemProvider(action as Action, options), actionRunner: this.actionRunner, classNames: ['toolbar-toggle-pickReactions', ...ThemeIcon.asClassNameArray(Codicon.reactions)], anchorAlignmentProvider: () => AnchorAlignment.RIGHT } ); } - return this.actionViewItemProvider(action as Action); + return this.actionViewItemProvider(action as Action, options); } }); this._register(this._reactionsActionBar); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 097f4a3c634..11ee0d9a2b9 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -344,15 +344,15 @@ export class ExtensionEditor extends EditorPane { const actionsAndStatusContainer = append(details, $('.actions-status-container')); const extensionActionBar = this._register(new ActionBar(actionsAndStatusContainer, { - actionViewItemProvider: (action: IAction) => { + actionViewItemProvider: (action: IAction, options) => { if (action instanceof ExtensionDropDownAction) { - return action.createActionViewItem(); + return action.createActionViewItem(options); } if (action instanceof ActionWithDropDownAction) { - return new ExtensionActionWithDropdownActionViewItem(action, { icon: true, label: true, menuActionsOrProvider: { getActions: () => action.menuActions }, menuActionClassNames: (action.class || '').split(' ') }, this.contextMenuService); + return new ExtensionActionWithDropdownActionViewItem(action, { ...options, icon: true, label: true, menuActionsOrProvider: { getActions: () => action.menuActions }, menuActionClassNames: (action.class || '').split(' ') }, this.contextMenuService); } if (action instanceof ToggleAutoUpdateForExtensionAction) { - return new CheckboxActionViewItem(undefined, action, { icon: true, label: true, checkboxStyles: defaultCheckboxStyles }); + return new CheckboxActionViewItem(undefined, action, { ...options, icon: true, label: true, checkboxStyles: defaultCheckboxStyles }); } return undefined; }, diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 1fc58a9e0f5..6e0155f3249 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1021,8 +1021,8 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { } private _actionViewItem: DropDownMenuActionViewItem | null = null; - createActionViewItem(): DropDownMenuActionViewItem { - this._actionViewItem = this.instantiationService.createInstance(DropDownMenuActionViewItem, this); + createActionViewItem(options: IActionViewItemOptions): DropDownMenuActionViewItem { + this._actionViewItem = this.instantiationService.createInstance(DropDownMenuActionViewItem, this, options); return this._actionViewItem; } @@ -1034,10 +1034,12 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { export class DropDownMenuActionViewItem extends ActionViewItem { - constructor(action: ExtensionDropDownAction, + constructor( + action: ExtensionDropDownAction, + options: IActionViewItemOptions, @IContextMenuService private readonly contextMenuService: IContextMenuService ) { - super(null, action, { icon: true, label: true }); + super(null, action, { ...options, icon: true, label: true }); } public showMenu(menuActionGroups: IAction[][], disposeActionsOnHide: boolean): void { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 1bf769f4c66..d5058c51b43 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -26,6 +26,7 @@ import { WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { verifiedPublisherIcon as verifiedPublisherThemeIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; const EXTENSION_LIST_ELEMENT_HEIGHT = 72; @@ -98,12 +99,12 @@ export class Renderer implements IPagedRenderer { const verifiedPublisherWidget = this.instantiationService.createInstance(VerifiedPublisherWidget, append(publisher, $(`.verified-publisher`)), true); const publisherDisplayName = append(publisher, $('.publisher-name.ellipsis')); const actionbar = new ActionBar(footer, { - actionViewItemProvider: (action: IAction) => { + actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => { if (action instanceof ActionWithDropDownAction) { - return new ExtensionActionWithDropdownActionViewItem(action, { icon: true, label: true, menuActionsOrProvider: { getActions: () => action.menuActions }, menuActionClassNames: (action.class || '').split(' ') }, this.contextMenuService); + return new ExtensionActionWithDropdownActionViewItem(action, { ...options, icon: true, label: true, menuActionsOrProvider: { getActions: () => action.menuActions }, menuActionClassNames: (action.class || '').split(' ') }, this.contextMenuService); } if (action instanceof ExtensionDropDownAction) { - return action.createActionViewItem(); + return action.createActionViewItem(options); } return undefined; }, diff --git a/src/vs/workbench/contrib/markers/browser/markersTable.ts b/src/vs/workbench/contrib/markers/browser/markersTable.ts index dc454d054ed..bb63d92e9d2 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTable.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTable.ts @@ -74,7 +74,7 @@ class MarkerSeverityColumnRenderer implements ITableRenderer action.id === QuickFixAction.ID ? this.instantiationService.createInstance(QuickFixActionViewItem, action) : undefined + actionViewItemProvider: (action: IAction, options) => action.id === QuickFixAction.ID ? this.instantiationService.createInstance(QuickFixActionViewItem, action, options) : undefined }); return { actionBar, icon }; diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 4b7a07b3b17..180a378ac81 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -295,7 +295,7 @@ class MarkerWidget extends Disposable { ) { super(); this.actionBar = this._register(new ActionBar(dom.append(parent, dom.$('.actions')), { - actionViewItemProvider: (action: IAction) => action.id === QuickFixAction.ID ? _instantiationService.createInstance(QuickFixActionViewItem, action) : undefined + actionViewItemProvider: (action: IAction, options) => action.id === QuickFixAction.ID ? _instantiationService.createInstance(QuickFixActionViewItem, action, options) : undefined })); // wrap the icon in a container that get the icon color as foreground color. That way, if the @@ -342,9 +342,9 @@ class MarkerWidget extends Disposable { private renderMultilineActionbar(marker: Marker, parent: HTMLElement): void { const multilineActionbar = this.disposables.add(new ActionBar(dom.append(parent, dom.$('.multiline-actions')), { - actionViewItemProvider: (action) => { + actionViewItemProvider: (action, options) => { if (action.id === toggleMultilineAction) { - return new ToggleMultilineActionViewItem(undefined, action, { icon: true }); + return new ToggleMultilineActionViewItem(undefined, action, { ...options, icon: true }); } return undefined; } diff --git a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts index 818f2f56fe6..74fa00a81ae 100644 --- a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts @@ -13,7 +13,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; -import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { ActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { MarkersContextKeys } from 'vs/workbench/contrib/markers/common/markers'; import 'vs/css!./markersViewActions'; @@ -145,10 +145,12 @@ export class QuickFixAction extends Action { export class QuickFixActionViewItem extends ActionViewItem { - constructor(action: QuickFixAction, + constructor( + action: QuickFixAction, + options: IActionViewItemOptions, @IContextMenuService private readonly contextMenuService: IContextMenuService, ) { - super(null, action, { icon: true, label: false }); + super(null, action, { ...options, icon: true, label: false }); } public override onClick(event: DOM.EventLike): void { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts index 480033afb33..fd9a432d6c3 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts @@ -40,6 +40,7 @@ import { defaultInputBoxStyles, defaultProgressBarStyles, defaultToggleStyles } import { IToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; import { Disposable } from 'vs/base/common/lifecycle'; import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find"); @@ -63,11 +64,12 @@ const NOTEBOOK_FIND_IN_CODE_OUTPUT = nls.localize('notebook.find.filter.findInCo const NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH = 318; const NOTEBOOK_FIND_WIDGET_INITIAL_HORIZONTAL_PADDING = 4; class NotebookFindFilterActionViewItem extends DropdownMenuActionViewItem { - constructor(readonly filters: NotebookFindFilters, action: IAction, actionRunner: IActionRunner, @IContextMenuService contextMenuService: IContextMenuService) { + constructor(readonly filters: NotebookFindFilters, action: IAction, options: IActionViewItemOptions, actionRunner: IActionRunner, @IContextMenuService contextMenuService: IContextMenuService) { super(action, { getActions: () => this.getActions() }, contextMenuService, { + ...options, actionRunner, classNames: action.class, anchorAlignmentProvider: () => AnchorAlignment.RIGHT @@ -196,9 +198,9 @@ export class NotebookFindInputFilterButton extends Disposable { private createFilters(container: HTMLElement): void { this._actionbar = this._register(new ActionBar(container, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action.id === this._filtersAction.id) { - return this.instantiationService.createInstance(NotebookFindFilterActionViewItem, this.filters, action, new ActionRunner()); + return this.instantiationService.createInstance(NotebookFindFilterActionViewItem, this.filters, action, options, new ActionRunner()); } return undefined; } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index c1218b15ce8..43b387b9689 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -117,9 +117,9 @@ class PropertyHeader extends Disposable { const cellToolbarContainer = DOM.append(this.propertyHeaderContainer, DOM.$('div.property-toolbar')); this._toolbar = new WorkbenchToolBar(cellToolbarContainer, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action instanceof MenuItemAction) { - const item = new CodiconActionViewItem(action, undefined, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService, this.accessibilityService); + const item = new CodiconActionViewItem(action, { hoverDelegate: options.hoverDelegate }, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService, this.accessibilityService); return item; } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts index 9248d1cee6e..4e92fa8a42f 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts @@ -189,9 +189,9 @@ export class CellDiffSideBySideRenderer implements IListRenderer { + actionViewItemProvider: (action, options) => { if (action instanceof MenuItemAction) { - const item = new CodiconActionViewItem(action, undefined, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService, this.accessibilityService); + const item = new CodiconActionViewItem(action, { hoverDelegate: options.hoverDelegate }, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService, this.accessibilityService); return item; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts index 12b86db201e..f10530616f1 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts @@ -46,12 +46,12 @@ export class BetweenCellToolbar extends CellOverlayPart { } const betweenCellToolbar = this._register(new ToolBar(this._bottomCellToolbarContainer, this.contextMenuService, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action instanceof MenuItemAction) { if (this._notebookEditor.notebookOptions.getDisplayOptions().insertToolbarAlignment === 'center') { - return this.instantiationService.createInstance(CodiconActionViewItem, action, undefined); + return this.instantiationService.createInstance(CodiconActionViewItem, action, { hoverDelegate: options.hoverDelegate }); } else { - return this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined); + return this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }); } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts index 48974ad10a5..de2c0e912bf 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts @@ -84,7 +84,7 @@ export class RunToolbar extends CellContentPart { const executionContextKeyService = this._register(getCodeCellExecutionContextKeyService(contextKeyService)); this.toolbar = this._register(new ToolBar(container, this.contextMenuService, { getKeyBinding: keybindingProvider, - actionViewItemProvider: _action => { + actionViewItemProvider: (_action, _options) => { actionViewItemDisposables.clear(); const primary = this.getCellToolbarActions(this.primaryMenu).primary[0]; @@ -104,6 +104,7 @@ export class RunToolbar extends CellContentPart { 'notebook-cell-run-toolbar', this.contextMenuService, { + ..._options, getKeyBinding: keybindingProvider }); actionViewItemDisposables.add(item.onDidChangeDropdownVisibility(visible => { diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts index 9a558d0dd28..f606649ca03 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts @@ -96,9 +96,9 @@ export class ListTopCellToolbar extends Disposable { DOM.clearNode(this.topCellToolbar); const toolbar = this.instantiationService.createInstance(MenuWorkbenchToolBar, this.topCellToolbar, this.notebookEditor.creationOptions.menuIds.cellTopInsertToolbar, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action instanceof MenuItemAction) { - const item = this.instantiationService.createInstance(CodiconActionViewItem, action, undefined); + const item = this.instantiationService.createInstance(CodiconActionViewItem, action, { hoverDelegate: options.hoverDelegate }); return item; } diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index b0026da674c..eec7a8ce974 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -57,6 +57,7 @@ import { settingsTextInputBorder } from 'vs/workbench/contrib/preferences/common import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands'; +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; const $ = DOM.$; @@ -397,9 +398,9 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP const actions = [this.recordKeysAction, this.sortByPrecedenceAction, clearInputAction]; const toolBar = this._register(new ToolBar(this.actionsContainer, this.contextMenuService, { - actionViewItemProvider: (action: IAction) => { + actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => { if (action.id === this.sortByPrecedenceAction.id || action.id === this.recordKeysAction.id) { - return new ToggleActionViewItem(null, action, { keybinding: this.keybindingsService.lookupKeybinding(action.id)?.getLabel(), toggleStyles: defaultToggleStyles }); + return new ToggleActionViewItem(null, action, { ...options, keybinding: this.keybindingsService.lookupKeybinding(action.id)?.getLabel(), toggleStyles: defaultToggleStyles }); } return undefined; }, diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index 1be32db1a39..30542844cc9 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -6,7 +6,7 @@ import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; -import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { BaseActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { HistoryInputBox, IHistoryInputOptions } from 'vs/base/browser/ui/inputbox/inputBox'; import { Widget } from 'vs/base/browser/ui/widget'; import { Action, IAction } from 'vs/base/common/actions'; @@ -252,7 +252,7 @@ export class SettingsTargetsWidget extends Widget { orientation: ActionsOrientation.HORIZONTAL, focusOnlyEnabledItems: true, ariaLabel: localize('settingsSwitcherBarAriaLabel', "Settings Switcher"), - actionViewItemProvider: (action: IAction) => action.id === 'folderSettings' ? this.folderSettings : undefined + actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => action.id === 'folderSettings' ? this.folderSettings : undefined })); this.userLocalSettings = new Action('userSettings', '', '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_LOCAL)); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index c0632e494bc..33c9e99303b 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -655,9 +655,9 @@ export class SettingsEditor2 extends EditorPane { this.controlsElement = DOM.append(searchContainer, DOM.$('.settings-clear-widget')); const actionBar = this._register(new ActionBar(this.controlsElement, { - actionViewItemProvider: (action) => { + actionViewItemProvider: (action, options) => { if (action.id === filterAction.id) { - return this.instantiationService.createInstance(SettingsSearchFilterDropdownMenuActionViewItem, action, this.actionRunner, this.searchWidget); + return this.instantiationService.createInstance(SettingsSearchFilterDropdownMenuActionViewItem, action, options, this.actionRunner, this.searchWidget); } return undefined; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts index 93e13c0234d..d119cd97f69 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { IAction, IActionRunner } from 'vs/base/common/actions'; @@ -17,6 +18,7 @@ export class SettingsSearchFilterDropdownMenuActionViewItem extends DropdownMenu constructor( action: IAction, + options: IActionViewItemOptions, actionRunner: IActionRunner | undefined, private readonly searchWidget: SuggestEnabledInput, @IContextMenuService contextMenuService: IContextMenuService @@ -25,6 +27,7 @@ export class SettingsSearchFilterDropdownMenuActionViewItem extends DropdownMenu { getActions: () => this.getActions() }, contextMenuService, { + ...options, actionRunner, classNames: action.class, anchorAlignmentProvider: () => AnchorAlignment.RIGHT, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index e9bf671e021..e2fd9619a69 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -279,9 +279,9 @@ class TerminalTabsRenderer implements IListRenderer + actionViewItemProvider: (action, options) => action instanceof MenuItemAction - ? this._instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) + ? this._instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }) : undefined }); diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts index a7490c873cf..16b2f5f1e92 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts @@ -5,7 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { BaseActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { BaseActionViewItem, IActionViewItemOptions, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { Action, IAction, IActionRunner, Separator } from 'vs/base/common/actions'; @@ -121,9 +121,9 @@ export class TestingExplorerFilter extends BaseActionViewItem { }))); const actionbar = this._register(new ActionBar(container, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action.id === this.filtersAction.id) { - return this.instantiationService.createInstance(FiltersDropdownMenuActionViewItem, action, this.state, this.actionRunner); + return this.instantiationService.createInstance(FiltersDropdownMenuActionViewItem, action, options, this.state, this.actionRunner); } return undefined; }, @@ -176,6 +176,7 @@ class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem { constructor( action: IAction, + options: IActionViewItemOptions, private readonly filters: ITestExplorerFilterState, actionRunner: IActionRunner, @IContextMenuService contextMenuService: IContextMenuService, diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 7f5dcfb7920..901d0a5d5da 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -2227,9 +2227,9 @@ class TestRunElementRenderer implements ICompressibleTreeRenderer + actionViewItemProvider: (action, options) => action instanceof MenuItemAction - ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) + ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }) : undefined }); From 13a2ba89a6159246bdf9b9d220c30a57eecfbd79 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Sun, 25 Feb 2024 20:49:24 +0100 Subject: [PATCH 1583/1897] rename suggestions: fix width overflow --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index cf76d531a2a..1b3728c30c6 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -515,7 +515,6 @@ class CandidatesView { public layout({ height, width }: { height: number; width: number }): void { this._availableHeight = height; this._minimumWidth = width; - this._listContainer.style.width = `${this._minimumWidth}px`; } public setCandidates(candidates: NewSymbolName[]): void { From a389ff1c805a8cb4b8044a5d88a24323ae268ecd Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 26 Feb 2024 10:28:28 +0100 Subject: [PATCH 1584/1897] Use compact hover by default for workbench (#206224) Use a compact hover by default for workbench --- src/vs/platform/hover/browser/hover.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 82d9574ca06..fea45187b43 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -285,6 +285,9 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate hideOnHover: true, hideOnKeyDown: true, }, + appearance: { + compact: true, + }, ...overrideOptions }, focus); } From 2c045aee0a449ff5f400f893e8e97fa9522a09f9 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:33:40 +0100 Subject: [PATCH 1585/1897] Hoverservice has it's own contextview instance (#206151) Hoverservice with it's own context view --- .../services/hoverService/hoverService.ts | 20 +++++++++++-------- .../contextview/browser/contextViewService.ts | 20 +++++++++++-------- .../parts/editor/breadcrumbsControl.ts | 4 ++-- .../browser/parts/editor/breadcrumbsPicker.ts | 7 ++----- .../browser/outline/documentSymbolsTree.ts | 11 ++-------- 5 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index f7338ae7b52..47fddf11c86 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -7,11 +7,11 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { editorHoverBorder } from 'vs/platform/theme/common/colorRegistry'; import { IHoverService, IHoverOptions } from 'vs/platform/hover/browser/hover'; -import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { HoverWidget } from 'vs/editor/browser/services/hoverService/hoverWidget'; import { IContextViewProvider, IDelegate } from 'vs/base/browser/ui/contextview/contextview'; -import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { addDisposableListener, EventType, getActiveElement, isAncestorOfActiveElement, isAncestor, getWindow } from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -20,10 +20,12 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { mainWindow } from 'vs/base/browser/window'; import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { ContextViewHandler } from 'vs/platform/contextview/browser/contextViewService'; -export class HoverService implements IHoverService { +export class HoverService extends Disposable implements IHoverService { declare readonly _serviceBrand: undefined; + private _contextViewHandler: IContextViewProvider; private _currentHoverOptions: IHoverOptions | undefined; private _currentHover: HoverWidget | undefined; private _lastHoverOptions: IHoverOptions | undefined; @@ -32,13 +34,15 @@ export class HoverService implements IHoverService { constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IContextViewService private readonly _contextViewService: IContextViewService, @IContextMenuService contextMenuService: IContextMenuService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @ILayoutService private readonly _layoutService: ILayoutService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService ) { + super(); + contextMenuService.onDidShowContextMenu(() => this.hideHover()); + this._contextViewHandler = this._register(new ContextViewHandler(this._layoutService)); } showHover(options: IHoverOptions, focus?: boolean, skipLastFocusedUpdate?: boolean): IHoverWidget | undefined { @@ -84,12 +88,12 @@ export class HoverService implements IHoverService { const targetElement = options.target instanceof HTMLElement ? options.target : options.target.targetElements[0]; options.container = this._layoutService.getContainer(getWindow(targetElement)); } - const provider = this._contextViewService as IContextViewProvider; - provider.showContextView( + + this._contextViewHandler.showContextView( new HoverContextViewDelegate(hover, focus), options.container ); - hover.onRequestLayout(() => provider.layout()); + hover.onRequestLayout(() => this._contextViewHandler.layout()); if (options.persistence?.sticky) { hoverDisposables.add(addDisposableListener(getWindow(options.container).document, EventType.MOUSE_DOWN, e => { if (!isAncestor(e.target as HTMLElement, hover.domNode)) { @@ -136,7 +140,7 @@ export class HoverService implements IHoverService { private doHideHover(): void { this._currentHover = undefined; this._currentHoverOptions = undefined; - this._contextViewService.hideContextView(); + this._contextViewHandler.hideContextView(); } private _intersectionChange(entries: IntersectionObserverEntry[], hover: IDisposable): void { diff --git a/src/vs/platform/contextview/browser/contextViewService.ts b/src/vs/platform/contextview/browser/contextViewService.ts index f47285746fe..929cb32d5a8 100644 --- a/src/vs/platform/contextview/browser/contextViewService.ts +++ b/src/vs/platform/contextview/browser/contextViewService.ts @@ -3,18 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ContextView, ContextViewDOMPosition } from 'vs/base/browser/ui/contextview/contextview'; +import { ContextView, ContextViewDOMPosition, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IContextViewDelegate, IContextViewService } from './contextView'; import { getWindow } from 'vs/base/browser/dom'; -export class ContextViewService extends Disposable implements IContextViewService { - declare readonly _serviceBrand: undefined; +export class ContextViewHandler extends Disposable implements IContextViewProvider { private currentViewDisposable: IDisposable = Disposable.None; - private readonly contextView = this._register(new ContextView(this.layoutService.mainContainer, ContextViewDOMPosition.ABSOLUTE)); + protected readonly contextView = this._register(new ContextView(this.layoutService.mainContainer, ContextViewDOMPosition.ABSOLUTE)); constructor( @ILayoutService private readonly layoutService: ILayoutService @@ -55,10 +54,6 @@ export class ContextViewService extends Disposable implements IContextViewServic return disposable; } - getContextViewElement(): HTMLElement { - return this.contextView.getViewElement(); - } - layout(): void { this.contextView.layout(); } @@ -74,3 +69,12 @@ export class ContextViewService extends Disposable implements IContextViewServic this.currentViewDisposable = Disposable.None; } } + +export class ContextViewService extends ContextViewHandler implements IContextViewService { + + declare readonly _serviceBrand: undefined; + + getContextViewElement(): HTMLElement { + return this.contextView.getViewElement(); + } +} diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 2a9d829b3ff..bf46167248f 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -41,7 +41,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { defaultBreadcrumbsWidgetStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Emitter } from 'vs/base/common/event'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { nativeHoverDelegate } from 'vs/platform/hover/browser/hover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; class OutlineItem extends BreadcrumbsItem { @@ -229,7 +229,7 @@ export class BreadcrumbsControl { this._ckBreadcrumbsVisible = BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo(this._contextKeyService); this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService); - this._hoverDelegate = nativeHoverDelegate; + this._hoverDelegate = getDefaultHoverDelegate('mouse'); this._disposables.add(breadcrumbsService.register(this._editorGroup.id, this._widget)); this.hide(); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 26f77202d53..9ca0b4310a5 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -31,8 +31,6 @@ import { IOutline, IOutlineComparator } from 'vs/workbench/services/outline/brow import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { nativeHoverDelegate } from 'vs/platform/hover/browser/hover'; interface ILayoutInfo { maxHeight: number; @@ -215,13 +213,12 @@ class FileRenderer implements ITreeRenderer, index: number, templateData: IResourceLabel): void { @@ -377,7 +374,7 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { 'BreadcrumbsFilePicker', container, new FileVirtualDelegate(), - [this._instantiationService.createInstance(FileRenderer, labels, nativeHoverDelegate)], + [this._instantiationService.createInstance(FileRenderer, labels)], this._instantiationService.createInstance(FileDataSource), { multipleSelectionSupport: false, diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index 4e8761ffb1d..c4f2a2def5a 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -24,9 +24,6 @@ import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IOutlineComparator, OutlineConfigKeys, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; import { ThemeIcon } from 'vs/base/common/themables'; import { mainWindow } from 'vs/base/browser/window'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { nativeHoverDelegate } from 'vs/platform/hover/browser/hover'; export type DocumentSymbolItem = OutlineGroup | OutlineElement; @@ -118,20 +115,16 @@ export class DocumentSymbolRenderer implements ITreeRenderer Date: Mon, 26 Feb 2024 20:11:47 +0900 Subject: [PATCH 1586/1897] chore: update to electron 28 (#203956) * chore: update electron@28.1.4 * ci: use latest Node.js v18 release 18.18.2 has npm version that has removed the node-gyp script which will cause native modules fail to build that rely on something like `install: node-gyp rebuild` Refs https://github.com/npm/cli/commit/c93edb55f52532e666a9ba2719bee0da725fe6f0 * chore: update rpm dependencies * chore: bump electron@28.2.1 * chore: bump nodejs@18.18.2 * chore: bump electron@28.2.2 * chore: bump distro --- .nvmrc | 2 +- .yarnrc | 4 +- build/azure-pipelines/linux/install.sh | 8 +- build/checksums/electron.txt | 150 ++++++++++++------------- build/checksums/nodejs.txt | 12 +- build/linux/dependencies-generator.js | 2 +- build/linux/dependencies-generator.ts | 2 +- build/linux/rpm/dep-lists.js | 3 - build/linux/rpm/dep-lists.ts | 3 - cgmanifest.json | 14 +-- package.json | 4 +- remote/.yarnrc | 4 +- yarn.lock | 8 +- 13 files changed, 105 insertions(+), 111 deletions(-) diff --git a/.nvmrc b/.nvmrc index 4a1f488b6c3..a9d087399d7 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.17.1 +18.19.0 diff --git a/.yarnrc b/.yarnrc index 19c5cb1eb8f..8675f7ab83c 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,5 +1,5 @@ disturl "https://electronjs.org/headers" -target "27.3.2" -ms_build_id "26836302" +target "28.2.2" +ms_build_id "26836304" runtime "electron" build_from_source "true" diff --git a/build/azure-pipelines/linux/install.sh b/build/azure-pipelines/linux/install.sh index 57f58763cca..c75100cac49 100755 --- a/build/azure-pipelines/linux/install.sh +++ b/build/azure-pipelines/linux/install.sh @@ -15,7 +15,7 @@ SYSROOT_ARCH="$SYSROOT_ARCH" node -e '(async () => { const { getVSCodeSysroot } if [ "$npm_config_arch" == "x64" ]; then # Download clang based on chromium revision used by vscode - curl -s https://raw.githubusercontent.com/chromium/chromium/118.0.5993.159/tools/clang/scripts/update.py | python - --output-dir=$PWD/.build/CR_Clang --host-os=linux + curl -s https://raw.githubusercontent.com/chromium/chromium/120.0.6099.268/tools/clang/scripts/update.py | python - --output-dir=$PWD/.build/CR_Clang --host-os=linux # Download libcxx headers and objects from upstream electron releases DEBUG=libcxx-fetcher \ @@ -27,9 +27,9 @@ if [ "$npm_config_arch" == "x64" ]; then # Set compiler toolchain # Flags for the client build are based on - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/118.0.5993.159:build/config/arm.gni - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/118.0.5993.159:build/config/compiler/BUILD.gn - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/118.0.5993.159:build/config/c++/BUILD.gn + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/120.0.6099.268:build/config/arm.gni + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/120.0.6099.268:build/config/compiler/BUILD.gn + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/120.0.6099.268:build/config/c++/BUILD.gn export CC="$PWD/.build/CR_Clang/bin/clang --gcc-toolchain=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu" export CXX="$PWD/.build/CR_Clang/bin/clang++ --gcc-toolchain=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu" export CXXFLAGS="-nostdinc++ -D__NO_INLINE__ -I$PWD/.build/libcxx_headers -isystem$PWD/.build/libcxx_headers/include -isystem$PWD/.build/libcxxabi_headers/include -fPIC -flto=thin -fsplit-lto-unit -D_LIBCPP_ABI_NAMESPACE=Cr --sysroot=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot" diff --git a/build/checksums/electron.txt b/build/checksums/electron.txt index a774dffc830..35feee9b876 100644 --- a/build/checksums/electron.txt +++ b/build/checksums/electron.txt @@ -1,75 +1,75 @@ -032e54843700736bf3566518ff88717b2dc70be41bdc43840993fcb4cd9c82e8 *chromedriver-v27.3.2-darwin-arm64.zip -7d693267bacc510b724b97db23e21e22983e9f500605a132ab519303ec2e4d94 *chromedriver-v27.3.2-darwin-x64.zip -5f3f417986667e4c82c492b30c14892b0fef3a6dcf07860e74f7d7ba29f0ca41 *chromedriver-v27.3.2-linux-arm64.zip -84364d9c1fc53ce6f29e41d08d12351a2a4a208646acf02551c6f9aa6029c163 *chromedriver-v27.3.2-linux-armv7l.zip -7d3965a5ca3217e16739153d2817fc292e7cb16f55034fde76f26bdc916e60d1 *chromedriver-v27.3.2-linux-x64.zip -068adc1ea9e1d21dcfef1468b2b789714c93465c1874dbd3bf2872a695a1279f *chromedriver-v27.3.2-mas-arm64.zip -0d4d4bb8971260cbc0058cab2a7972e556b83a19d6ea062ea226e7a8555bc369 *chromedriver-v27.3.2-mas-x64.zip -83ffc61b6b524ee0caa0e5cd02dcd00adcd166ba1e03e7fc50206a299a6fca11 *chromedriver-v27.3.2-win32-arm64.zip -df4e9f20681b3e7b65c41dd1df3aa8cb9bc0a061a24ddcffbe44a9191aa01e0c *chromedriver-v27.3.2-win32-ia32.zip -1ef67b7c06061e691176df5e3463f4d5f5f258946dac24ae62e3cc250b8b95d1 *chromedriver-v27.3.2-win32-x64.zip -f3c52d205572da71a23f436b4708dc89c721a74f0e0c5c51093e3e331b1dff67 *electron-api.json -1489dca88c89f6fef05bdc2c08b9623bb46eb8d0f43020985776daef08642061 *electron-v27.3.2-darwin-arm64-dsym-snapshot.zip -7ee895e81d695c1ed65378ff4514d4fc9c4015a1c3c67691765f92c08c8e0855 *electron-v27.3.2-darwin-arm64-dsym.zip -cbc1c9973b2a895aa2ebecdbd92b3fe8964590b12141a658a6d03ed97339fae6 *electron-v27.3.2-darwin-arm64-symbols.zip -0d4efeff14ac16744eef3d461b95fb59abd2c3affbf638af169698135db73e1f *electron-v27.3.2-darwin-arm64.zip -a77b52509213e67ae1e24172256479831ecbff55d1f49dc0e8bfd4818a5f393e *electron-v27.3.2-darwin-x64-dsym-snapshot.zip -9006386321c50aa7e0e02cd9bd9daef4b8c3ec0e9735912524802f31d02399ef *electron-v27.3.2-darwin-x64-dsym.zip -14fa8e76e519e1fb9e166e134d03f3df1ae1951c14dfd76db8a033a9627c0f13 *electron-v27.3.2-darwin-x64-symbols.zip -5105acce7d832a606fd11b0551d1ef00e0c49fc8b4cff4b53712c9efdddc27a2 *electron-v27.3.2-darwin-x64.zip -3bc20fb4f1d5effb2d882e7b587a337f910026aa50c22e7bc92522daa13f389c *electron-v27.3.2-linux-arm64-debug.zip -0d5d97a93938fa62d2659e2053dcc8d1cabc967878992b248bfec4dcc7763b8c *electron-v27.3.2-linux-arm64-symbols.zip -db9320d9ec6309145347fbba369ab7634139e80f15fff452be9b0171b2bd1823 *electron-v27.3.2-linux-arm64.zip -3bc20fb4f1d5effb2d882e7b587a337f910026aa50c22e7bc92522daa13f389c *electron-v27.3.2-linux-armv7l-debug.zip -6b9117419568c72542ab671301df05d46a662deab0bc37787b3dc9a907e68f8c *electron-v27.3.2-linux-armv7l-symbols.zip -72fd10c666dd810e9f961c2727ae44f5f6cf964cedb6860c1f09da7152e29a29 *electron-v27.3.2-linux-armv7l.zip -354209d48be01785d286eb80d691cdff476479db2d8cdbc6b6bd30652f5539fa *electron-v27.3.2-linux-x64-debug.zip -5f45a4b42f3b35ecea8a623338a6add35bb5220cb0ed02e3489b6d77fbe102ef *electron-v27.3.2-linux-x64-symbols.zip -2261aa0a5a293cf963487c050e9f6d05124da1f946f99bd1115f616f8730f286 *electron-v27.3.2-linux-x64.zip -54a4ad6e75e5a0001c32de18dbfec17f5edc17693663078076456ded525d65da *electron-v27.3.2-mas-arm64-dsym-snapshot.zip -5a5c85833ad7a6ef04337ed8acd131e5cf383a49638789dfd84e07c855b33ccc *electron-v27.3.2-mas-arm64-dsym.zip -16da4cc5a19a953c839093698f0532854e4d3fc839496a5c2b2405fd63c707f4 *electron-v27.3.2-mas-arm64-symbols.zip -8455b79826fe195124bee3f0661e08c14ca50858d376b09d03c79aace0082ea5 *electron-v27.3.2-mas-arm64.zip -00731db08a1bb66e51af0d26d03f8510221f4f6f92282c7baa0cd1c130e0cce6 *electron-v27.3.2-mas-x64-dsym-snapshot.zip -446f98f2d957e4ae487a6307b18be7b11edff35187b71143def4d00325943e42 *electron-v27.3.2-mas-x64-dsym.zip -d3455394eff02d463fdf89aabeee9c05d4980207ecf75a5eac27b35fb2aef874 *electron-v27.3.2-mas-x64-symbols.zip -dae434f52ff9b1055703aaf74b17ff3d93351646e9271a3b10e14b49969d4218 *electron-v27.3.2-mas-x64.zip -a598fcd1e20dcef9e7dccf7676ba276cd95ec7ff6799834fd090800fb15a6507 *electron-v27.3.2-win32-arm64-pdb.zip -7ba64940321ddff307910cc49077aa36c430d4b0797097975cb797cc0ab2b39d *electron-v27.3.2-win32-arm64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.2-win32-arm64-toolchain-profile.zip -692f264e9d13478ad9a42d06e2eead0ed67ab1b52fc3693ba536a6a441fd9010 *electron-v27.3.2-win32-arm64.zip -a74eee739ff26681f6696f7959ab8e8603bb57f8fcb7ddab305220f71d2c69f3 *electron-v27.3.2-win32-ia32-pdb.zip -c10b90b51d0292129dc5bba5e012c7e07c78d6c70b0980c36676d6abf8eef12f *electron-v27.3.2-win32-ia32-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.2-win32-ia32-toolchain-profile.zip -63e477332608d31afb965a4054b5d78165df1da65d57477ac1dbddf8ede0f1b9 *electron-v27.3.2-win32-ia32.zip -3d795150c0afd48f585c7d32685f726618825262cb76f4014567be9e3de88732 *electron-v27.3.2-win32-x64-pdb.zip -d5463f797d1eb9a57ac9b20caa6419c15c5f3b378a3cb2b45d338040d7124411 *electron-v27.3.2-win32-x64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.2-win32-x64-toolchain-profile.zip -e701b3023d4929f86736ae8a7ff6409134455da99b3fbdcea8d58555acbd9d46 *electron-v27.3.2-win32-x64.zip -3383cd44951cf763ddd36ba3ec91c930c9e8d33a175adfcb6dce4f667d60bc34 *electron.d.ts -db6df7bd0264c859009247276b35eda4ef20f22a7b2f41c2335a4609f5653cb7 *ffmpeg-v27.3.2-darwin-arm64.zip -3c0bb9740d6b95ff476ff7a5d4b442ccef7ec98e0fa3f2bad8d0e6a51329b511 *ffmpeg-v27.3.2-darwin-x64.zip -6fea38ce22bae4d23fb6b143e946c1c3d214ccecabf841883a2cb1b621161113 *ffmpeg-v27.3.2-linux-arm64.zip -926d0da25ffcea3d05a6cbcae15e5d7729d93bc43394ae4439747669d2210e1d *ffmpeg-v27.3.2-linux-armv7l.zip -6f9c0ef52af14828ad547a80b17f8c63cac51a18b8d5769a2f33e4fa6cccfc7e *ffmpeg-v27.3.2-linux-x64.zip -c75f62fc08d6c5e49fd1a805ca00b4191d5f04d26469448e3d4af48fb409b3a7 *ffmpeg-v27.3.2-mas-arm64.zip -acb8154c113ecbafb91aef5a294dc2c2bce61cbc4a261696681b723d292a5cb3 *ffmpeg-v27.3.2-mas-x64.zip -1665bdac6aa7264a6eb5f00a93110b718c7231010389bdda5ec7bf8275aab953 *ffmpeg-v27.3.2-win32-arm64.zip -3972d89c60a77f7955d7e8520adeae0c9f449a5ae3730cacf202f2baf2bae079 *ffmpeg-v27.3.2-win32-ia32.zip -37d2da723c2f2148c1c8f2ccf354b6dd933148c49dfc7f32aa57ecbd7063ffaf *ffmpeg-v27.3.2-win32-x64.zip -8828099c931c56981865fb9ff6fca85012dd05702a125858d6377c793760db1f *hunspell_dictionaries.zip -9e2126db472f66d3dde2d77eec63364e7071358f5591fc3c4dfb53d191ab5da8 *libcxx-objects-v27.3.2-linux-arm64.zip -530c3a92c4cd721e49e62d4fd97090c4e4d1b00c3ba821fd4f42c5f9186c98e7 *libcxx-objects-v27.3.2-linux-armv7l.zip -5b67f5e2a268bd1980a13b794013d4ac96e7ee40c4878d96f7c27da2c3f94923 *libcxx-objects-v27.3.2-linux-x64.zip -0d3086ccf9a050a88251a4382349f436f99d3d2b1842d87d854ea80667f6c423 *libcxx_headers.zip -ac02262548cb396051c683ad35fcbbed61b9a6f935c2a2bd3d568b209ce9e5a4 *libcxxabi_headers.zip -ba3b63a297b8be954a0ca1b8b83c3c856abaae85d17e6337d2b34e1c14f0d4b2 *mksnapshot-v27.3.2-darwin-arm64.zip -cb09a9e9e1fee567bf9e697eef30d143bd30627c0b189d0271cf84a72a03042e *mksnapshot-v27.3.2-darwin-x64.zip -014c5b621bbbc497bdc40dac47fac20143013fa1e905c0570b5cf92a51826354 *mksnapshot-v27.3.2-linux-arm64-x64.zip -f71407b9cc5c727de243a9e9e7fb56d2a0880e02187fa79982478853432ed5b7 *mksnapshot-v27.3.2-linux-armv7l-x64.zip -e5caa81f467d071756a4209f05f360055be7625a71a0dd9b2a8c95296c8415b5 *mksnapshot-v27.3.2-linux-x64.zip -fc33ec02a17fb58d48625c7b68517705dcd95b5a12e731d0072711a084dc65bd *mksnapshot-v27.3.2-mas-arm64.zip -961af5fc0ef80243d0e94036fb31b90f7e8458e392dd0e49613c11be89cb723f *mksnapshot-v27.3.2-mas-x64.zip -844a70ccef160921e0baeaefe9038d564db9a9476d98fab1eebb5c122ba9c22c *mksnapshot-v27.3.2-win32-arm64-x64.zip -3e723ca42794d43e16656599fbfec73880b964264f5057e38b865688c83ac905 *mksnapshot-v27.3.2-win32-ia32.zip -3e6fc056fa8cfb9940b26c4f066a9c9343056f053bcc53e1eada464bf5bc0d42 *mksnapshot-v27.3.2-win32-x64.zip +b1478a79a80e453e9ee39ad4a0b0e0d871f3e369ec519e3ac5a1da20bb7bdbf3 *chromedriver-v28.2.2-darwin-arm64.zip +4e7c4651d610c70de883b9ceef633f1a2bf90e0f3a732eae7a6d7bcad11bb2df *chromedriver-v28.2.2-darwin-x64.zip +7cee31da7d90c2a24338a10046386517bb93c69c79bd44cfcc9372a551fc7d01 *chromedriver-v28.2.2-linux-arm64.zip +2056e41f713d1a6c83d1f0260c0f2b8addc3c49887ae85ca7e92267eb53951e8 *chromedriver-v28.2.2-linux-armv7l.zip +19503257092605e21bd3798f5ffd0049d8420a504ececef7b1e95d3733846874 *chromedriver-v28.2.2-linux-x64.zip +ec09eeb8a7040c7402a8a5f54491b33e5dc95ea0535b55381a3ec405014f08db *chromedriver-v28.2.2-mas-arm64.zip +1dd5cb2a113c74ae84f2ac98f6f40da2c367014381a547788fea9ae220e6fc9f *chromedriver-v28.2.2-mas-x64.zip +fd505e1f1c2f72266c48914690a48918fc7920877215a508ea5325cf0353f72c *chromedriver-v28.2.2-win32-arm64.zip +c0226c0fb260d6812185eeea718c8c0054d0fcac995bb1ccb333f852206372c8 *chromedriver-v28.2.2-win32-ia32.zip +b6daccad5bcd3046d0678c927f6b97ed91f2242f716deb0de95a0ee2303af818 *chromedriver-v28.2.2-win32-x64.zip +76a88da92b950c882d90c3dcb26e0c2ca5e5a52ad7a066ec0b3cbf9cc4d04563 *electron-api.json +6ad08d733c95de3c30560e8289d0e657ed5ee03bc8ba9d1f11d528851e5b7fba *electron-v28.2.2-darwin-arm64-dsym-snapshot.zip +8f0d450f3d2392cbe7a6cb274ec0f3bf63da66c98fa0baaa2355e69f1c93b151 *electron-v28.2.2-darwin-arm64-dsym.zip +262036eb86b767db0d199df022b8b432aa3714e451b9ac656af7ef031581b44a *electron-v28.2.2-darwin-arm64-symbols.zip +23119b333c47a5ea9e36e04cdc3b8c5955cfccfeb90994f1fecea4722bfb8dcc *electron-v28.2.2-darwin-arm64.zip +384015a3e49a6846ebefc78f9f01ce6d47c2ec109e6223907298aa6382b0d072 *electron-v28.2.2-darwin-x64-dsym-snapshot.zip +434838821de746ff71baafdf9e0df07cb3766dd73eb7fcd253aee0571bd0cd59 *electron-v28.2.2-darwin-x64-dsym.zip +470087b5d631dc0032611048d5fc23faed9a71ec2c36a528c5a50c2e357d1716 *electron-v28.2.2-darwin-x64-symbols.zip +48f3424b3cbdf602a13f451361ade2f7f2896a354a51f78da4239dbdf2d1218b *electron-v28.2.2-darwin-x64.zip +d5bf835ba4b2eaa4435946f97ad7ac3e7243564037423cfaadaf5cb03af4ddbc *electron-v28.2.2-linux-arm64-debug.zip +90550f29b1f032ebcf467dc81f4915c322f93855a4658cf74261f68a3ccdc21e *electron-v28.2.2-linux-arm64-symbols.zip +746284eb1d8029b0f6b02281543ab2ecf45f071da21407f45b2b32d1ff268310 *electron-v28.2.2-linux-arm64.zip +d5bf835ba4b2eaa4435946f97ad7ac3e7243564037423cfaadaf5cb03af4ddbc *electron-v28.2.2-linux-armv7l-debug.zip +80cc8d9333156caaee59c7ddf3bd77712be8379b51f4218063e6c176a4ec2c26 *electron-v28.2.2-linux-armv7l-symbols.zip +f4580e8877481c0526110feaa78372ed3045bfbf5a6ba4b14e8cd155b9965f5e *electron-v28.2.2-linux-armv7l.zip +da33d92871768e4cf95b143c6022830d97b0ec2d4120463ab71b48597f940f07 *electron-v28.2.2-linux-x64-debug.zip +18dce5283513abd94b79a1636d25e3453f5c33d335425a234b9967dd4e5ce942 *electron-v28.2.2-linux-x64-symbols.zip +1eeb6ebc3b0699cae1fb171bbf7c9105e716db833f6e73a90f4ca161f17ffb15 *electron-v28.2.2-linux-x64.zip +e74da15d90e52cddf0f0f14663f6313df585b486b002966f6016c1b148cdd70d *electron-v28.2.2-mas-arm64-dsym-snapshot.zip +498357eb2e784bff54c5ac59fd3eada814d130f12a5e77d47c468f2305377717 *electron-v28.2.2-mas-arm64-dsym.zip +849fa891d072d06b1e929eb1acfbe7ac83f0238483039f8e1102e01e5223c3f5 *electron-v28.2.2-mas-arm64-symbols.zip +621fd91d70cb33ec58543fc57762e692dfa0e272a53f3316fd215ffa88bd075b *electron-v28.2.2-mas-arm64.zip +0f9e2ab79bca99f44c1e9a140929fad6d2cd37def60303974f5a82ca95dd9a69 *electron-v28.2.2-mas-x64-dsym-snapshot.zip +51c29e047ba7d8669030cc9615f70ecaa5c9519cd04ab5e62822c0d4f21f5fbb *electron-v28.2.2-mas-x64-dsym.zip +25da93f45b095a3669475416832647a01f2a02a95dcc064dfabdf9c621045106 *electron-v28.2.2-mas-x64-symbols.zip +3b6931362f1b7f377624ea7c6ccf069f291e4e675a28f12a56e3e75355c13fbd *electron-v28.2.2-mas-x64.zip +cece93c232c65bf4e1b918b9645f5a2e247bd3f8bb2dd9e6e889a402060a103b *electron-v28.2.2-win32-arm64-pdb.zip +4a46e1ead0de7b6f757c1194add6467b3375a8dcfb02d903e481c0d8db5c7e5d *electron-v28.2.2-win32-arm64-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v28.2.2-win32-arm64-toolchain-profile.zip +083f95abbce97cab70e77b86e39cff01ff1df121f36b9da581ead960ae329f69 *electron-v28.2.2-win32-arm64.zip +f9b4633bc03fe7c77db4b335121e7e3e05f6788c6752ccb3f68267e664d4323a *electron-v28.2.2-win32-ia32-pdb.zip +d20f70ea8dc86477f723d104d42fe78e2508577ef3b1eb6ec812366f18ad80d8 *electron-v28.2.2-win32-ia32-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v28.2.2-win32-ia32-toolchain-profile.zip +c57691b73592632829cef136be6dd356a82331450920fd024ac3589654d23550 *electron-v28.2.2-win32-ia32.zip +b8a14fc75b9205a4b82aa008e23a020e9fac694399b47390a163c3100ac7946d *electron-v28.2.2-win32-x64-pdb.zip +780089dde95ce1ab5da176ad53d9c7cd122085809622852a3132b80b93faac9b *electron-v28.2.2-win32-x64-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v28.2.2-win32-x64-toolchain-profile.zip +5fbc76585891b0d7b09c938f7be25b7ab36b3768491021b053dc99bc70a8aa29 *electron-v28.2.2-win32-x64.zip +225268475350fa71d9fdea966160fc91379ced2f2353a902addf65d5f9b0dbf1 *electron.d.ts +59a8d6b81d93bc99ecf099fac6492eb67ba601386cce07261a009a5b99e75479 *ffmpeg-v28.2.2-darwin-arm64.zip +15386f238dce9ba40714336265422cc41a1ef0608041f562a8fd42e3813ddc64 *ffmpeg-v28.2.2-darwin-x64.zip +8e108e533811febcc51f377ac8604d506663453e41c02dc818517e1ea9a4e8d5 *ffmpeg-v28.2.2-linux-arm64.zip +51ecd03435f56a2ced31b1c9dbf281955ba82a814ca0214a4292bdc711e5a45c *ffmpeg-v28.2.2-linux-armv7l.zip +acc9dc3765f68b7563045e2d0df11bbef6b41be0a1c34bbf9fa778f36eefb42f *ffmpeg-v28.2.2-linux-x64.zip +e71aac5c02f67bd5ba5d650160ff4edb122f697ab6bd8e686eae78426c439733 *ffmpeg-v28.2.2-mas-arm64.zip +3d0bb26cc9b751dad883750972fddec72aa936ecaa0d9bd198ba9b47203410e8 *ffmpeg-v28.2.2-mas-x64.zip +035b24a44f09587092e7db4e28400139901cec6378b3c828ce9f90a60f4f3a9a *ffmpeg-v28.2.2-win32-arm64.zip +38b25e225fd028f1f3f2c551f3b42d62d9e5c4ef388e0b0e019e9c8d93a85b07 *ffmpeg-v28.2.2-win32-ia32.zip +41849e779371dc0c35899341ae658b883ef0124296787ad96b7d5e4d9b69f1b9 *ffmpeg-v28.2.2-win32-x64.zip +8830364f8050164b1736246c30e96ae7ac876bcec5af1bf6344edbd66ed45353 *hunspell_dictionaries.zip +fc417873289fa9c947598ed73a27a28c4b5a07ce90ef998bb56550c4e10a034b *libcxx-objects-v28.2.2-linux-arm64.zip +06e9cdb2e8785a0835f66d34e9518c47ef220e32646e5b43e599339836e9e7b1 *libcxx-objects-v28.2.2-linux-armv7l.zip +ac098a006a8f84d0bb19088b2dec3ee3068b19208c5611194e831b1e5878fb2d *libcxx-objects-v28.2.2-linux-x64.zip +56414a1e809874949c1a1111b8e68b8d4f40d55cb481ad4869e920e47fe1b71b *libcxx_headers.zip +36e46cbed397cc1fe34d8dc477d3a87613acb9936f811535c1300e138e1a7008 *libcxxabi_headers.zip +94b01f4dd6bd56dec39a0be9ac14bb8c9a73db22cb579d6093f4f4c95a4a8896 *mksnapshot-v28.2.2-darwin-arm64.zip +ea768087b4fedf09c38eb093beb744c1a3b5b2a54025a83f1e2301ea03539500 *mksnapshot-v28.2.2-darwin-x64.zip +b9a01ba90abb69877838515d8273532e4aeea6d66c49b8aac3267e26546fc8b3 *mksnapshot-v28.2.2-linux-arm64-x64.zip +60005160b5e9db4a3847c63893f44e18ca86657a3ec97b6c13a90e43291bdb65 *mksnapshot-v28.2.2-linux-armv7l-x64.zip +81bf5ec59e7c33c642b79582fc5b775ec635ce0c52f3f5c30315cb45fdbffd12 *mksnapshot-v28.2.2-linux-x64.zip +7bfbe3cf02713b1a09aa19b75b876e158ed167b0d4345ec3b429061b53fc4b8f *mksnapshot-v28.2.2-mas-arm64.zip +91f7d34a05fa9c7cda4f36a44309f51e7defea2134d5bcc818a3eb4537979870 *mksnapshot-v28.2.2-mas-x64.zip +3f7163a34aae864cd44ebec086d4fab30132924680f20136cf19348811bace50 *mksnapshot-v28.2.2-win32-arm64-x64.zip +ac64fbfb78a1f6f389dac96ad7c655e2ea6fb2289e38a8fd516dbbda6bea42a3 *mksnapshot-v28.2.2-win32-ia32.zip +1bcd03747ce3eee6dd94b0608a0812268dacf77bac5541c581c22b92f700b303 *mksnapshot-v28.2.2-win32-x64.zip diff --git a/build/checksums/nodejs.txt b/build/checksums/nodejs.txt index 9ed8af5842a..13aa4c7e87b 100644 --- a/build/checksums/nodejs.txt +++ b/build/checksums/nodejs.txt @@ -1,6 +1,6 @@ -18ca716ea57522b90473777cb9f878467f77fdf826d37beb15a0889fdd74533e node-v18.17.1-darwin-arm64.tar.gz -b3e083d2715f07ec3f00438401fb58faa1e0bdf3c7bde9f38b75ed17809d92fa node-v18.17.1-darwin-x64.tar.gz -8f5203f5c6dc44ea50ac918b7ecbdb1c418e4f3d9376d8232a1ef9ff38f9c480 node-v18.17.1-linux-arm64.tar.gz -1ab79868859b2d37148c6d8ecee3abb5ee55b88731ab5df01928ed4f6f9bfbad node-v18.17.1-linux-armv7l.tar.gz -2cb75f2bc04b0a3498733fbee779b2f76fe3f655188b4ac69ef2887b6721da2d node-v18.17.1-linux-x64.tar.gz -afb45186ad4f4217c2fc1dfc2239ff5ab016ef0ba5fc329bc6aa8fd10c7ecc88 win-x64/node.exe +9f982cc91b28778dd8638e4f94563b0c2a1da7aba62beb72bd427721035ab553 node-v18.18.2-darwin-arm64.tar.gz +5bb8da908ed590e256a69bf2862238c8a67bc4600119f2f7721ca18a7c810c0f node-v18.18.2-darwin-x64.tar.gz +0c9a6502b66310cb26e12615b57304e91d92ac03d4adcb91c1906351d7928f0d node-v18.18.2-linux-arm64.tar.gz +7a3b34a6fdb9514bc2374114ec6df3c36113dc5075c38b22763aa8f106783737 node-v18.18.2-linux-armv7l.tar.gz +a44c3e7f8bf91e852c928e5d8bd67ca316b35e27eec1d8acbe3b9dbe03688dab node-v18.18.2-linux-x64.tar.gz +54884183ff5108874c091746465e8156ae0acc68af589cc10bc41b3927db0f4a win-x64/node.exe diff --git a/build/linux/dependencies-generator.js b/build/linux/dependencies-generator.js index e40ed70901c..58db0f4af51 100644 --- a/build/linux/dependencies-generator.js +++ b/build/linux/dependencies-generator.js @@ -23,7 +23,7 @@ const product = require("../../product.json"); // The reference dependencies, which one has to update when the new dependencies // are valid, are in dep-lists.ts const FAIL_BUILD_FOR_NEW_DEPENDENCIES = true; -// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/118.0.5993.159:chrome/installer/linux/BUILD.gn;l=64-80 +// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/120.0.6099.268:chrome/installer/linux/BUILD.gn;l=64-80 // and the Linux Archive build // Shared library dependencies that we already bundle. const bundledDeps = [ diff --git a/build/linux/dependencies-generator.ts b/build/linux/dependencies-generator.ts index 12bc3c08a64..9f1a068b8d7 100644 --- a/build/linux/dependencies-generator.ts +++ b/build/linux/dependencies-generator.ts @@ -25,7 +25,7 @@ import product = require('../../product.json'); // are valid, are in dep-lists.ts const FAIL_BUILD_FOR_NEW_DEPENDENCIES: boolean = true; -// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/118.0.5993.159:chrome/installer/linux/BUILD.gn;l=64-80 +// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/120.0.6099.268:chrome/installer/linux/BUILD.gn;l=64-80 // and the Linux Archive build // Shared library dependencies that we already bundle. const bundledDeps = [ diff --git a/build/linux/rpm/dep-lists.js b/build/linux/rpm/dep-lists.js index b9a6e80d5f3..bd84fc146dc 100644 --- a/build/linux/rpm/dep-lists.js +++ b/build/linux/rpm/dep-lists.js @@ -81,7 +81,6 @@ exports.referenceGeneratedDepsByArch = { 'libnss3.so(NSS_3.11)(64bit)', 'libnss3.so(NSS_3.12)(64bit)', 'libnss3.so(NSS_3.12.1)(64bit)', - 'libnss3.so(NSS_3.13)(64bit)', 'libnss3.so(NSS_3.2)(64bit)', 'libnss3.so(NSS_3.22)(64bit)', 'libnss3.so(NSS_3.3)(64bit)', @@ -173,7 +172,6 @@ exports.referenceGeneratedDepsByArch = { 'libnss3.so(NSS_3.11)', 'libnss3.so(NSS_3.12)', 'libnss3.so(NSS_3.12.1)', - 'libnss3.so(NSS_3.13)', 'libnss3.so(NSS_3.2)', 'libnss3.so(NSS_3.22)', 'libnss3.so(NSS_3.22)(64bit)', @@ -269,7 +267,6 @@ exports.referenceGeneratedDepsByArch = { 'libnss3.so(NSS_3.11)(64bit)', 'libnss3.so(NSS_3.12)(64bit)', 'libnss3.so(NSS_3.12.1)(64bit)', - 'libnss3.so(NSS_3.13)(64bit)', 'libnss3.so(NSS_3.2)(64bit)', 'libnss3.so(NSS_3.22)(64bit)', 'libnss3.so(NSS_3.3)(64bit)', diff --git a/build/linux/rpm/dep-lists.ts b/build/linux/rpm/dep-lists.ts index 275d88b95a8..82a4fe7698d 100644 --- a/build/linux/rpm/dep-lists.ts +++ b/build/linux/rpm/dep-lists.ts @@ -80,7 +80,6 @@ export const referenceGeneratedDepsByArch = { 'libnss3.so(NSS_3.11)(64bit)', 'libnss3.so(NSS_3.12)(64bit)', 'libnss3.so(NSS_3.12.1)(64bit)', - 'libnss3.so(NSS_3.13)(64bit)', 'libnss3.so(NSS_3.2)(64bit)', 'libnss3.so(NSS_3.22)(64bit)', 'libnss3.so(NSS_3.3)(64bit)', @@ -172,7 +171,6 @@ export const referenceGeneratedDepsByArch = { 'libnss3.so(NSS_3.11)', 'libnss3.so(NSS_3.12)', 'libnss3.so(NSS_3.12.1)', - 'libnss3.so(NSS_3.13)', 'libnss3.so(NSS_3.2)', 'libnss3.so(NSS_3.22)', 'libnss3.so(NSS_3.22)(64bit)', @@ -268,7 +266,6 @@ export const referenceGeneratedDepsByArch = { 'libnss3.so(NSS_3.11)(64bit)', 'libnss3.so(NSS_3.12)(64bit)', 'libnss3.so(NSS_3.12.1)(64bit)', - 'libnss3.so(NSS_3.13)(64bit)', 'libnss3.so(NSS_3.2)(64bit)', 'libnss3.so(NSS_3.22)(64bit)', 'libnss3.so(NSS_3.3)(64bit)', diff --git a/cgmanifest.json b/cgmanifest.json index 2673931fdb6..4b4d49573e4 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "b1f5594cf472956192e71c38ebfc22472d44a03d" + "commitHash": "01303e423c41f9fefe7ff777744a4c549c0c6d8c" } }, "licenseDetail": [ @@ -40,7 +40,7 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "118.0.5993.159" + "version": "120.0.6099.276" }, { "component": { @@ -48,7 +48,7 @@ "git": { "name": "ffmpeg", "repositoryUrl": "https://chromium.googlesource.com/chromium/third_party/ffmpeg", - "commitHash": "0ba37733400593b162e5ae9ff26b384cff49c250" + "commitHash": "e1ca3f06adec15150a171bc38f550058b4bbb23b" } }, "isOnlyProductionDependency": true, @@ -516,11 +516,11 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "2e414d5d1082233c3516fca923fe351d5186c80e" + "commitHash": "8a01b3dcb7d08a48bfd3e6bf85ef49faa1454839" } }, "isOnlyProductionDependency": true, - "version": "18.17.1" + "version": "18.18.2" }, { "component": { @@ -528,12 +528,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "077c4addd5faa3ad1d1c9e598284368394a97fdd" + "commitHash": "16adf2a26358e3fc2297832e867c942b6df35844" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "27.3.2" + "version": "28.2.2" }, { "component": { diff --git a/package.json b/package.json index 1c3d3fc095f..2f127741d55 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.88.0", - "distro": "b314654a31bdba8cd2b0c7548e931916d03416bf", + "distro": "de07f23454d5352cc3711ca34d51278767e6eb0a", "author": { "name": "Microsoft Corporation" }, @@ -149,7 +149,7 @@ "cssnano": "^6.0.3", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "27.3.2", + "electron": "28.2.2", "eslint": "8.36.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^46.5.0", diff --git a/remote/.yarnrc b/remote/.yarnrc index cac528fd2dd..4e7208cdf69 100644 --- a/remote/.yarnrc +++ b/remote/.yarnrc @@ -1,5 +1,5 @@ disturl "https://nodejs.org/dist" -target "18.17.1" -ms_build_id "255375" +target "18.18.2" +ms_build_id "256117" runtime "node" build_from_source "true" diff --git a/yarn.lock b/yarn.lock index 7dcbe6fe030..98368474549 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3556,10 +3556,10 @@ electron-to-chromium@^1.4.648: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.648.tgz#c7b46c9010752c37bb4322739d6d2dd82354fbe4" integrity sha512-EmFMarXeqJp9cUKu/QEciEApn0S/xRcpZWuAm32U7NgoZCimjsilKXHRO9saeEW55eHZagIDg6XTUOv32w9pjg== -electron@27.3.2: - version "27.3.2" - resolved "https://registry.yarnpkg.com/electron/-/electron-27.3.2.tgz#ff2caa6d09e013ec32bae3185c790d712cd02f54" - integrity sha512-FoLdHj2ON0jE8S0YntgNT4ABaHgK4oR4dqXixPQXnTK0JvXgrrrW5W7ls+c7oiFBGN/f9bm0Mabq8iKH+7wMYQ== +electron@28.2.2: + version "28.2.2" + resolved "https://registry.yarnpkg.com/electron/-/electron-28.2.2.tgz#d5aa4a33c00927d83ca893f8726f7c62aad98c41" + integrity sha512-8UcvIGFcjplHdjPFNAHVFg5bS0atDyT3Zx21WwuE4iLfxcAMsyMEOgrQX3im5LibA8srwsUZs7Cx0JAUfcQRpw== dependencies: "@electron/get" "^2.0.0" "@types/node" "^18.11.18" From 51772af23ced693d721de52b0b3ed90ed0635bc6 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 26 Feb 2024 11:41:18 +0100 Subject: [PATCH 1587/1897] rename suggestions: increase approximate font width --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 1b3728c30c6..6cb9f8a8c5a 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -612,7 +612,7 @@ class CandidatesView { } private _pickListWidth(candidates: NewSymbolName[]): number { - const APPROXIMATE_CHAR_WIDTH = 7.2; // approximate # of pixes taken by a single character + const APPROXIMATE_CHAR_WIDTH = 8; // approximate # of pixes taken by a single character const longestCandidateWidth = Math.ceil(Math.max(...candidates.map(c => c.newSymbolName.length)) * APPROXIMATE_CHAR_WIDTH); // TODO@ulugbekna: use editor#typicalCharacterWidth or something const width = Math.max( this._minimumWidth, From aa8ec9703cab7b592e0adce2f4d2bc6927b84127 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 26 Feb 2024 12:32:17 +0100 Subject: [PATCH 1588/1897] Fix some bogus uses of new CancellationTokenSource().token (#205994) Part of #205966 --- src/vs/workbench/api/browser/mainThreadQuickDiff.ts | 4 ++-- src/vs/workbench/api/common/extHostTunnelService.ts | 4 ++-- src/vs/workbench/browser/parts/views/treeView.ts | 2 +- src/vs/workbench/contrib/remote/browser/tunnelView.ts | 6 +++--- src/vs/workbench/services/remote/common/tunnelModel.ts | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadQuickDiff.ts b/src/vs/workbench/api/browser/mainThreadQuickDiff.ts index 48f53faea39..d5312097ee9 100644 --- a/src/vs/workbench/api/browser/mainThreadQuickDiff.ts +++ b/src/vs/workbench/api/browser/mainThreadQuickDiff.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableMap, IDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtHostContext, ExtHostQuickDiffShape, IDocumentFilterDto, MainContext, MainThreadQuickDiffShape } from 'vs/workbench/api/common/extHost.protocol'; @@ -30,7 +30,7 @@ export class MainThreadQuickDiff implements MainThreadQuickDiffShape { selector, isSCM: false, getOriginalResource: async (uri: URI) => { - return URI.revive(await this.proxy.$provideOriginalResource(handle, uri, new CancellationTokenSource().token)); + return URI.revive(await this.proxy.$provideOriginalResource(handle, uri, CancellationToken.None)); } }; const disposable = this.quickDiffService.addQuickDiffProvider(provider); diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index ccf5700c83b..7200372acca 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; @@ -149,7 +149,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe throw new Error('A tunnel provider has already been registered. Only the first tunnel provider to be registered will be used.'); } this._forwardPortProvider = async (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => { - const result = await provider.provideTunnel(tunnelOptions, tunnelCreationOptions, new CancellationTokenSource().token); + const result = await provider.provideTunnel(tunnelOptions, tunnelCreationOptions, CancellationToken.None); return result ?? undefined; }; diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 0eb478a3544..3b40a248a1d 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -767,7 +767,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { let command = element?.command; if (element && !command) { if ((element instanceof ResolvableTreeItem) && element.hasResolve) { - await element.resolve(new CancellationTokenSource().token); + await element.resolve(CancellationToken.None); command = element.command; } } diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 8b0318487b6..35ba3ee8c5d 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -44,7 +44,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { copyAddressIcon, forwardedPortWithoutProcessIcon, forwardedPortWithProcessIcon, forwardPortIcon, labelPortIcon, openBrowserIcon, openPreviewIcon, portsViewIcon, privatePortIcon, stopForwardIcon } from 'vs/workbench/contrib/remote/browser/remoteIcons'; import { IExternalUriOpenerService } from 'vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { isMacintosh } from 'vs/base/common/platform'; import { ITableColumn, ITableContextMenuEvent, ITableEvent, ITableMouseEvent, ITableRenderer, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table'; import { WorkbenchTable } from 'vs/platform/list/browser/listService'; @@ -1372,9 +1372,9 @@ export namespace OpenPortInPreviewAction { if (tunnel) { const remoteHost = tunnel.remoteHost.includes(':') ? `[${tunnel.remoteHost}]` : tunnel.remoteHost; const sourceUri = URI.parse(`http://${remoteHost}:${tunnel.remotePort}`); - const opener = await externalOpenerService.getOpener(tunnel.localUri, { sourceUri }, new CancellationTokenSource().token); + const opener = await externalOpenerService.getOpener(tunnel.localUri, { sourceUri }, CancellationToken.None); if (opener) { - return opener.openExternalUri(tunnel.localUri, { sourceUri }, new CancellationTokenSource().token); + return opener.openExternalUri(tunnel.localUri, { sourceUri }, CancellationToken.None); } return openerService.open(tunnel.localUri); } diff --git a/src/vs/workbench/services/remote/common/tunnelModel.ts b/src/vs/workbench/services/remote/common/tunnelModel.ts index 5394416d717..f0ceaf1950c 100644 --- a/src/vs/workbench/services/remote/common/tunnelModel.ts +++ b/src/vs/workbench/services/remote/common/tunnelModel.ts @@ -20,7 +20,7 @@ import { RemoteTunnel, ITunnelService, TunnelProtocol, TunnelPrivacyId, LOCALHOS 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 { CancellationToken } from 'vs/base/common/cancellation'; import { isNumber, isObject, isString } from 'vs/base/common/types'; import { deepClone } from 'vs/base/common/objects'; import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -993,7 +993,7 @@ export class TunnelModel extends Disposable { const portGroup = entry[1]; const matchingCandidate = matchingCandidates.get(portGroup[0]); return provider.providePortAttributes(portGroup, - matchingCandidate?.pid, matchingCandidate?.detail, new CancellationTokenSource().token); + matchingCandidate?.pid, matchingCandidate?.detail, CancellationToken.None); }); }))); const providedAttributes: Map = new Map(); From 90ab3213c32e5dfc9eff78af7b7b4783f78d855b Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 14:44:32 +0100 Subject: [PATCH 1589/1897] Move diff editor internal commands to their own file --- .../browser/widget/diffEditor/commands.ts | 242 ++++++++++++++++++ .../diffEditor/diffEditor.contribution.ts | 240 +---------------- 2 files changed, 245 insertions(+), 237 deletions(-) create mode 100644 src/vs/editor/browser/widget/diffEditor/commands.ts diff --git a/src/vs/editor/browser/widget/diffEditor/commands.ts b/src/vs/editor/browser/widget/diffEditor/commands.ts new file mode 100644 index 00000000000..5297e77c086 --- /dev/null +++ b/src/vs/editor/browser/widget/diffEditor/commands.ts @@ -0,0 +1,242 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getActiveElement } from 'vs/base/browser/dom'; +import { Codicon } from 'vs/base/common/codicons'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { localize2 } from 'vs/nls'; +import { ILocalizedString } from 'vs/platform/action/common/action'; +import { Action2, MenuId } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import './registrations.contribution'; + +export class ToggleCollapseUnchangedRegions extends Action2 { + constructor() { + super({ + id: 'diffEditor.toggleCollapseUnchangedRegions', + title: localize2('toggleCollapseUnchangedRegions', 'Toggle Collapse Unchanged Regions'), + icon: Codicon.map, + toggled: ContextKeyExpr.has('config.diffEditor.hideUnchangedRegions.enabled'), + precondition: ContextKeyExpr.has('isInDiffEditor'), + menu: { + when: ContextKeyExpr.has('isInDiffEditor'), + id: MenuId.EditorTitle, + order: 22, + group: 'navigation', + }, + }); + } + + run(accessor: ServicesAccessor, ...args: unknown[]): void { + const configurationService = accessor.get(IConfigurationService); + const newValue = !configurationService.getValue('diffEditor.hideUnchangedRegions.enabled'); + configurationService.updateValue('diffEditor.hideUnchangedRegions.enabled', newValue); + } +} + +export class ToggleShowMovedCodeBlocks extends Action2 { + constructor() { + super({ + id: 'diffEditor.toggleShowMovedCodeBlocks', + title: localize2('toggleShowMovedCodeBlocks', 'Toggle Show Moved Code Blocks'), + precondition: ContextKeyExpr.has('isInDiffEditor'), + }); + } + + run(accessor: ServicesAccessor, ...args: unknown[]): void { + const configurationService = accessor.get(IConfigurationService); + const newValue = !configurationService.getValue('diffEditor.experimental.showMoves'); + configurationService.updateValue('diffEditor.experimental.showMoves', newValue); + } +} + +export class ToggleUseInlineViewWhenSpaceIsLimited extends Action2 { + constructor() { + super({ + id: 'diffEditor.toggleUseInlineViewWhenSpaceIsLimited', + title: localize2('toggleUseInlineViewWhenSpaceIsLimited', 'Toggle Use Inline View When Space Is Limited'), + precondition: ContextKeyExpr.has('isInDiffEditor'), + }); + } + + run(accessor: ServicesAccessor, ...args: unknown[]): void { + const configurationService = accessor.get(IConfigurationService); + const newValue = !configurationService.getValue('diffEditor.useInlineViewWhenSpaceIsLimited'); + configurationService.updateValue('diffEditor.useInlineViewWhenSpaceIsLimited', newValue); + } +} + +const diffEditorCategory: ILocalizedString = localize2('diffEditor', "Diff Editor"); + +export class SwitchSide extends EditorAction2 { + constructor() { + super({ + id: 'diffEditor.switchSide', + title: localize2('switchSide', 'Switch Side'), + icon: Codicon.arrowSwap, + precondition: ContextKeyExpr.has('isInDiffEditor'), + f1: true, + category: diffEditorCategory, + }); + } + + runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, arg?: { dryRun: boolean }): unknown { + const diffEditor = findFocusedDiffEditor(accessor); + if (diffEditor instanceof DiffEditorWidget) { + if (arg && arg.dryRun) { + return { destinationSelection: diffEditor.mapToOtherSide().destinationSelection }; + } else { + diffEditor.switchSide(); + } + } + return undefined; + } +} +export class ExitCompareMove extends EditorAction2 { + constructor() { + super({ + id: 'diffEditor.exitCompareMove', + title: localize2('exitCompareMove', 'Exit Compare Move'), + icon: Codicon.close, + precondition: EditorContextKeys.comparingMovedCode, + f1: false, + category: diffEditorCategory, + keybinding: { + weight: 10000, + primary: KeyCode.Escape, + } + }); + } + + runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]): void { + const diffEditor = findFocusedDiffEditor(accessor); + if (diffEditor instanceof DiffEditorWidget) { + diffEditor.exitCompareMove(); + } + } +} + +export class CollapseAllUnchangedRegions extends EditorAction2 { + constructor() { + super({ + id: 'diffEditor.collapseAllUnchangedRegions', + title: localize2('collapseAllUnchangedRegions', 'Collapse All Unchanged Regions'), + icon: Codicon.fold, + precondition: ContextKeyExpr.has('isInDiffEditor'), + f1: true, + category: diffEditorCategory, + }); + } + + runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]): void { + const diffEditor = findFocusedDiffEditor(accessor); + if (diffEditor instanceof DiffEditorWidget) { + diffEditor.collapseAllUnchangedRegions(); + } + } +} + +export class ShowAllUnchangedRegions extends EditorAction2 { + constructor() { + super({ + id: 'diffEditor.showAllUnchangedRegions', + title: localize2('showAllUnchangedRegions', 'Show All Unchanged Regions'), + icon: Codicon.unfold, + precondition: ContextKeyExpr.has('isInDiffEditor'), + f1: true, + category: diffEditorCategory, + }); + } + + runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]): void { + const diffEditor = findFocusedDiffEditor(accessor); + if (diffEditor instanceof DiffEditorWidget) { + diffEditor.showAllUnchangedRegions(); + } + } +} + +const accessibleDiffViewerCategory: ILocalizedString = localize2('accessibleDiffViewer', "Accessible Diff Viewer"); + +export class AccessibleDiffViewerNext extends Action2 { + public static id = 'editor.action.accessibleDiffViewer.next'; + + constructor() { + super({ + id: AccessibleDiffViewerNext.id, + title: localize2('editor.action.accessibleDiffViewer.next', 'Go to Next Difference'), + category: accessibleDiffViewerCategory, + precondition: ContextKeyExpr.has('isInDiffEditor'), + keybinding: { + primary: KeyCode.F7, + weight: KeybindingWeight.EditorContrib + }, + f1: true, + }); + } + + public override run(accessor: ServicesAccessor): void { + const diffEditor = findFocusedDiffEditor(accessor); + diffEditor?.accessibleDiffViewerNext(); + } +} + +export class AccessibleDiffViewerPrev extends Action2 { + public static id = 'editor.action.accessibleDiffViewer.prev'; + + constructor() { + super({ + id: AccessibleDiffViewerPrev.id, + title: localize2('editor.action.accessibleDiffViewer.prev', 'Go to Previous Difference'), + category: accessibleDiffViewerCategory, + precondition: ContextKeyExpr.has('isInDiffEditor'), + keybinding: { + primary: KeyMod.Shift | KeyCode.F7, + weight: KeybindingWeight.EditorContrib + }, + f1: true, + }); + } + + public override run(accessor: ServicesAccessor): void { + const diffEditor = findFocusedDiffEditor(accessor); + diffEditor?.accessibleDiffViewerPrev(); + } +} + +export function findFocusedDiffEditor(accessor: ServicesAccessor): IDiffEditor | null { + const codeEditorService = accessor.get(ICodeEditorService); + const diffEditors = codeEditorService.listDiffEditors(); + + const activeElement = getActiveElement(); + if (activeElement) { + for (const d of diffEditors) { + const container = d.getContainerDomNode(); + if (isElementOrParentOf(container, activeElement)) { + return d; + } + } + } + + return null; +} + +function isElementOrParentOf(elementOrParent: Element, element: Element): boolean { + let e: Element | null = element; + while (e) { + if (e === elementOrParent) { + return true; + } + e = e.parentElement; + } + return false; +} diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts b/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts index 2cc7ffacdc4..bb8ab9ccca7 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts @@ -3,83 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getActiveElement } from 'vs/base/browser/dom'; import { Codicon } from 'vs/base/common/codicons'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; +import { AccessibleDiffViewerNext, AccessibleDiffViewerPrev, CollapseAllUnchangedRegions, ExitCompareMove, ShowAllUnchangedRegions, SwitchSide, ToggleCollapseUnchangedRegions, ToggleShowMovedCodeBlocks, ToggleUseInlineViewWhenSpaceIsLimited } from 'vs/editor/browser/widget/diffEditor/commands'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { localize, localize2 } from 'vs/nls'; -import { ILocalizedString } from 'vs/platform/action/common/action'; -import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; +import { localize } from 'vs/nls'; +import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import './registrations.contribution'; -export class ToggleCollapseUnchangedRegions extends Action2 { - constructor() { - super({ - id: 'diffEditor.toggleCollapseUnchangedRegions', - title: localize2('toggleCollapseUnchangedRegions', 'Toggle Collapse Unchanged Regions'), - icon: Codicon.map, - toggled: ContextKeyExpr.has('config.diffEditor.hideUnchangedRegions.enabled'), - precondition: ContextKeyExpr.has('isInDiffEditor'), - menu: { - when: ContextKeyExpr.has('isInDiffEditor'), - id: MenuId.EditorTitle, - order: 22, - group: 'navigation', - }, - }); - } - - run(accessor: ServicesAccessor, ...args: unknown[]): void { - const configurationService = accessor.get(IConfigurationService); - const newValue = !configurationService.getValue('diffEditor.hideUnchangedRegions.enabled'); - configurationService.updateValue('diffEditor.hideUnchangedRegions.enabled', newValue); - } -} - registerAction2(ToggleCollapseUnchangedRegions); - -export class ToggleShowMovedCodeBlocks extends Action2 { - constructor() { - super({ - id: 'diffEditor.toggleShowMovedCodeBlocks', - title: localize2('toggleShowMovedCodeBlocks', 'Toggle Show Moved Code Blocks'), - precondition: ContextKeyExpr.has('isInDiffEditor'), - }); - } - - run(accessor: ServicesAccessor, ...args: unknown[]): void { - const configurationService = accessor.get(IConfigurationService); - const newValue = !configurationService.getValue('diffEditor.experimental.showMoves'); - configurationService.updateValue('diffEditor.experimental.showMoves', newValue); - } -} - registerAction2(ToggleShowMovedCodeBlocks); - -export class ToggleUseInlineViewWhenSpaceIsLimited extends Action2 { - constructor() { - super({ - id: 'diffEditor.toggleUseInlineViewWhenSpaceIsLimited', - title: localize2('toggleUseInlineViewWhenSpaceIsLimited', 'Toggle Use Inline View When Space Is Limited'), - precondition: ContextKeyExpr.has('isInDiffEditor'), - }); - } - - run(accessor: ServicesAccessor, ...args: unknown[]): void { - const configurationService = accessor.get(IConfigurationService); - const newValue = !configurationService.getValue('diffEditor.useInlineViewWhenSpaceIsLimited'); - configurationService.updateValue('diffEditor.useInlineViewWhenSpaceIsLimited', newValue); - } -} - registerAction2(ToggleUseInlineViewWhenSpaceIsLimited); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { @@ -110,130 +44,12 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { when: ContextKeyExpr.has('isInDiffEditor'), }); -const diffEditorCategory: ILocalizedString = localize2('diffEditor', "Diff Editor"); - -export class SwitchSide extends EditorAction2 { - constructor() { - super({ - id: 'diffEditor.switchSide', - title: localize2('switchSide', 'Switch Side'), - icon: Codicon.arrowSwap, - precondition: ContextKeyExpr.has('isInDiffEditor'), - f1: true, - category: diffEditorCategory, - }); - } - - runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, arg?: { dryRun: boolean }): unknown { - const diffEditor = findFocusedDiffEditor(accessor); - if (diffEditor instanceof DiffEditorWidget) { - if (arg && arg.dryRun) { - return { destinationSelection: diffEditor.mapToOtherSide().destinationSelection }; - } else { - diffEditor.switchSide(); - } - } - return undefined; - } -} registerAction2(SwitchSide); - -export class ExitCompareMove extends EditorAction2 { - constructor() { - super({ - id: 'diffEditor.exitCompareMove', - title: localize2('exitCompareMove', 'Exit Compare Move'), - icon: Codicon.close, - precondition: EditorContextKeys.comparingMovedCode, - f1: false, - category: diffEditorCategory, - keybinding: { - weight: 10000, - primary: KeyCode.Escape, - } - }); - } - - runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]): void { - const diffEditor = findFocusedDiffEditor(accessor); - if (diffEditor instanceof DiffEditorWidget) { - diffEditor.exitCompareMove(); - } - } -} - registerAction2(ExitCompareMove); - -export class CollapseAllUnchangedRegions extends EditorAction2 { - constructor() { - super({ - id: 'diffEditor.collapseAllUnchangedRegions', - title: localize2('collapseAllUnchangedRegions', 'Collapse All Unchanged Regions'), - icon: Codicon.fold, - precondition: ContextKeyExpr.has('isInDiffEditor'), - f1: true, - category: diffEditorCategory, - }); - } - - runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]): void { - const diffEditor = findFocusedDiffEditor(accessor); - if (diffEditor instanceof DiffEditorWidget) { - diffEditor.collapseAllUnchangedRegions(); - } - } -} - registerAction2(CollapseAllUnchangedRegions); - -export class ShowAllUnchangedRegions extends EditorAction2 { - constructor() { - super({ - id: 'diffEditor.showAllUnchangedRegions', - title: localize2('showAllUnchangedRegions', 'Show All Unchanged Regions'), - icon: Codicon.unfold, - precondition: ContextKeyExpr.has('isInDiffEditor'), - f1: true, - category: diffEditorCategory, - }); - } - - runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]): void { - const diffEditor = findFocusedDiffEditor(accessor); - if (diffEditor instanceof DiffEditorWidget) { - diffEditor.showAllUnchangedRegions(); - } - } -} - registerAction2(ShowAllUnchangedRegions); -const accessibleDiffViewerCategory: ILocalizedString = localize2('accessibleDiffViewer', "Accessible Diff Viewer"); - -export class AccessibleDiffViewerNext extends Action2 { - public static id = 'editor.action.accessibleDiffViewer.next'; - - constructor() { - super({ - id: AccessibleDiffViewerNext.id, - title: localize2('editor.action.accessibleDiffViewer.next', 'Go to Next Difference'), - category: accessibleDiffViewerCategory, - precondition: ContextKeyExpr.has('isInDiffEditor'), - keybinding: { - primary: KeyCode.F7, - weight: KeybindingWeight.EditorContrib - }, - f1: true, - }); - } - - public override run(accessor: ServicesAccessor): void { - const diffEditor = findFocusedDiffEditor(accessor); - diffEditor?.accessibleDiffViewerNext(); - } -} - MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: AccessibleDiffViewerNext.id, @@ -248,56 +64,6 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { ), }); -export class AccessibleDiffViewerPrev extends Action2 { - public static id = 'editor.action.accessibleDiffViewer.prev'; - - constructor() { - super({ - id: AccessibleDiffViewerPrev.id, - title: localize2('editor.action.accessibleDiffViewer.prev', 'Go to Previous Difference'), - category: accessibleDiffViewerCategory, - precondition: ContextKeyExpr.has('isInDiffEditor'), - keybinding: { - primary: KeyMod.Shift | KeyCode.F7, - weight: KeybindingWeight.EditorContrib - }, - f1: true, - }); - } - - public override run(accessor: ServicesAccessor): void { - const diffEditor = findFocusedDiffEditor(accessor); - diffEditor?.accessibleDiffViewerPrev(); - } -} - -export function findFocusedDiffEditor(accessor: ServicesAccessor): IDiffEditor | null { - const codeEditorService = accessor.get(ICodeEditorService); - const diffEditors = codeEditorService.listDiffEditors(); - - const activeElement = getActiveElement(); - if (activeElement) { - for (const d of diffEditors) { - const container = d.getContainerDomNode(); - if (isElementOrParentOf(container, activeElement)) { - return d; - } - } - } - - return null; -} - -function isElementOrParentOf(elementOrParent: Element, element: Element): boolean { - let e: Element | null = element; - while (e) { - if (e === elementOrParent) { - return true; - } - e = e.parentElement; - } - return false; -} CommandsRegistry.registerCommandAlias('editor.action.diffReview.next', AccessibleDiffViewerNext.id); registerAction2(AccessibleDiffViewerNext); From f63ffa61419924642f0d5722452f16eb14984fa9 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 14:57:23 +0100 Subject: [PATCH 1590/1897] Move diff editor commands into their own files --- .../parts/editor/diffEditorCommands.ts | 231 ++++++++++++++++++ .../parts/editor/editor.contribution.ts | 14 +- .../browser/parts/editor/editorCommands.ts | 221 +---------------- 3 files changed, 241 insertions(+), 225 deletions(-) create mode 100644 src/vs/workbench/browser/parts/editor/diffEditorCommands.ts diff --git a/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts b/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts new file mode 100644 index 00000000000..550ed81628f --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts @@ -0,0 +1,231 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { localize2, localize } from 'vs/nls'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; +import { TextCompareEditorVisibleContext, TextCompareEditorActiveContext } from 'vs/workbench/common/contextkeys'; +import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +export const TOGGLE_DIFF_SIDE_BY_SIDE = 'toggle.diff.renderSideBySide'; +export const GOTO_NEXT_CHANGE = 'workbench.action.compareEditor.nextChange'; +export const GOTO_PREVIOUS_CHANGE = 'workbench.action.compareEditor.previousChange'; +export const DIFF_FOCUS_PRIMARY_SIDE = 'workbench.action.compareEditor.focusPrimarySide'; +export const DIFF_FOCUS_SECONDARY_SIDE = 'workbench.action.compareEditor.focusSecondarySide'; +export const DIFF_FOCUS_OTHER_SIDE = 'workbench.action.compareEditor.focusOtherSide'; +export const DIFF_OPEN_SIDE = 'workbench.action.compareEditor.openSide'; +export const TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE = 'toggle.diff.ignoreTrimWhitespace'; +export const DIFF_SWAP_SIDES = 'workbench.action.compareEditor.swapSides'; + +export function registerDiffEditorCommands(): void { + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: GOTO_NEXT_CHANGE, + weight: KeybindingWeight.WorkbenchContrib, + when: TextCompareEditorVisibleContext, + primary: KeyMod.Alt | KeyCode.F5, + handler: accessor => navigateInDiffEditor(accessor, true) + }); + + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: GOTO_NEXT_CHANGE, + title: localize2('compare.nextChange', 'Go to Next Change'), + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: GOTO_PREVIOUS_CHANGE, + weight: KeybindingWeight.WorkbenchContrib, + when: TextCompareEditorVisibleContext, + primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F5, + handler: accessor => navigateInDiffEditor(accessor, false) + }); + + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: GOTO_PREVIOUS_CHANGE, + title: localize2('compare.previousChange', 'Go to Previous Change'), + } + }); + + function getActiveTextDiffEditor(accessor: ServicesAccessor): TextDiffEditor | undefined { + const editorService = accessor.get(IEditorService); + + for (const editor of [editorService.activeEditorPane, ...editorService.visibleEditorPanes]) { + if (editor instanceof TextDiffEditor) { + return editor; + } + } + + return undefined; + } + + function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void { + const activeTextDiffEditor = getActiveTextDiffEditor(accessor); + + if (activeTextDiffEditor) { + activeTextDiffEditor.getControl()?.goToDiff(next ? 'next' : 'previous'); + } + } + + enum FocusTextDiffEditorMode { + Original, + Modified, + Toggle + } + + function focusInDiffEditor(accessor: ServicesAccessor, mode: FocusTextDiffEditorMode): void { + const activeTextDiffEditor = getActiveTextDiffEditor(accessor); + + if (activeTextDiffEditor) { + switch (mode) { + case FocusTextDiffEditorMode.Original: + activeTextDiffEditor.getControl()?.getOriginalEditor().focus(); + break; + case FocusTextDiffEditorMode.Modified: + activeTextDiffEditor.getControl()?.getModifiedEditor().focus(); + break; + case FocusTextDiffEditorMode.Toggle: + if (activeTextDiffEditor.getControl()?.getModifiedEditor().hasWidgetFocus()) { + return focusInDiffEditor(accessor, FocusTextDiffEditorMode.Original); + } else { + return focusInDiffEditor(accessor, FocusTextDiffEditorMode.Modified); + } + } + } + } + + function toggleDiffSideBySide(accessor: ServicesAccessor): void { + const configurationService = accessor.get(IConfigurationService); + + const newValue = !configurationService.getValue('diffEditor.renderSideBySide'); + configurationService.updateValue('diffEditor.renderSideBySide', newValue); + } + + function toggleDiffIgnoreTrimWhitespace(accessor: ServicesAccessor): void { + const configurationService = accessor.get(IConfigurationService); + + const newValue = !configurationService.getValue('diffEditor.ignoreTrimWhitespace'); + configurationService.updateValue('diffEditor.ignoreTrimWhitespace', newValue); + } + + async function swapDiffSides(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + + const diffEditor = getActiveTextDiffEditor(accessor); + const activeGroup = diffEditor?.group; + const diffInput = diffEditor?.input; + if (!diffEditor || typeof activeGroup === 'undefined' || !(diffInput instanceof DiffEditorInput) || !diffInput.modified.resource) { + return; + } + + const untypedDiffInput = diffInput.toUntyped({ preserveViewState: activeGroup.id, preserveResource: true }); + if (!untypedDiffInput) { + return; + } + + // Since we are about to replace the diff editor, make + // sure to first open the modified side if it is not + // yet opened. This ensures that the swapping is not + // bringing up a confirmation dialog to save. + if (diffInput.modified.isModified() && editorService.findEditors({ resource: diffInput.modified.resource, typeId: diffInput.modified.typeId, editorId: diffInput.modified.editorId }).length === 0) { + await editorService.openEditor({ + ...untypedDiffInput.modified, + options: { + ...untypedDiffInput.modified.options, + pinned: true, + inactive: true + } + }, activeGroup); + } + + // Replace the input with the swapped variant + await editorService.replaceEditors([ + { + editor: diffInput, + replacement: { + ...untypedDiffInput, + original: untypedDiffInput.modified, + modified: untypedDiffInput.original, + options: { + ...untypedDiffInput.options, + pinned: true + } + } + } + ], activeGroup); + } + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: TOGGLE_DIFF_SIDE_BY_SIDE, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: undefined, + handler: accessor => toggleDiffSideBySide(accessor) + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: DIFF_FOCUS_PRIMARY_SIDE, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: undefined, + handler: accessor => focusInDiffEditor(accessor, FocusTextDiffEditorMode.Modified) + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: DIFF_FOCUS_SECONDARY_SIDE, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: undefined, + handler: accessor => focusInDiffEditor(accessor, FocusTextDiffEditorMode.Original) + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: DIFF_FOCUS_OTHER_SIDE, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: undefined, + handler: accessor => focusInDiffEditor(accessor, FocusTextDiffEditorMode.Toggle) + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: undefined, + handler: accessor => toggleDiffIgnoreTrimWhitespace(accessor) + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: DIFF_SWAP_SIDES, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: undefined, + handler: accessor => swapDiffSides(accessor) + }); + + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: TOGGLE_DIFF_SIDE_BY_SIDE, + title: localize2('toggleInlineView', "Toggle Inline View"), + category: localize('compare', "Compare") + }, + when: TextCompareEditorActiveContext + }); + + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: DIFF_SWAP_SIDES, + title: localize2('swapDiffSides', "Swap Left and Right Editor Side"), + category: localize('compare', "Compare") + }, + when: TextCompareEditorActiveContext + }); +} diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index ca21ad13864..a60e8e59d62 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -47,12 +47,13 @@ import { } from 'vs/workbench/browser/parts/editor/editorActions'; import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_EDITOR_GROUP_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, - CLOSE_PINNED_EDITOR_COMMAND_ID, CLOSE_SAVED_EDITORS_COMMAND_ID, GOTO_NEXT_CHANGE, GOTO_PREVIOUS_CHANGE, KEEP_EDITOR_COMMAND_ID, PIN_EDITOR_COMMAND_ID, SHOW_EDITORS_IN_GROUP, SPLIT_EDITOR_DOWN, SPLIT_EDITOR_LEFT, - SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, TOGGLE_DIFF_SIDE_BY_SIDE, TOGGLE_KEEP_EDITORS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, setup as registerEditorCommands, REOPEN_WITH_COMMAND_ID, + CLOSE_PINNED_EDITOR_COMMAND_ID, CLOSE_SAVED_EDITORS_COMMAND_ID, KEEP_EDITOR_COMMAND_ID, PIN_EDITOR_COMMAND_ID, SHOW_EDITORS_IN_GROUP, SPLIT_EDITOR_DOWN, SPLIT_EDITOR_LEFT, + SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, TOGGLE_KEEP_EDITORS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, setup as registerEditorCommands, REOPEN_WITH_COMMAND_ID, TOGGLE_LOCK_GROUP_COMMAND_ID, UNLOCK_GROUP_COMMAND_ID, SPLIT_EDITOR_IN_GROUP, JOIN_EDITOR_IN_GROUP, FOCUS_FIRST_SIDE_EDITOR, FOCUS_SECOND_SIDE_EDITOR, TOGGLE_SPLIT_EDITOR_IN_GROUP_LAYOUT, LOCK_GROUP_COMMAND_ID, SPLIT_EDITOR, TOGGLE_MAXIMIZE_EDITOR_GROUP, MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, MOVE_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, - NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID, DIFF_SWAP_SIDES + NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { GOTO_NEXT_CHANGE, GOTO_PREVIOUS_CHANGE, TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, TOGGLE_DIFF_SIDE_BY_SIDE, DIFF_SWAP_SIDES } from './diffEditorCommands'; import { inQuickPickContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; @@ -570,11 +571,8 @@ appendEditorToolItem( CLOSE_ORDER - 1, // immediately to the left of close action ); -const previousChangeIcon = registerIcon('diff-editor-previous-change', Codicon.arrowUp, localize('previousChangeIcon', 'Icon for the previous change action in the diff editor.')); -const nextChangeIcon = registerIcon('diff-editor-next-change', Codicon.arrowDown, localize('nextChangeIcon', 'Icon for the next change action in the diff editor.')); -const toggleWhitespace = registerIcon('diff-editor-toggle-whitespace', Codicon.whitespace, localize('toggleWhitespace', 'Icon for the toggle whitespace action in the diff editor.')); - // Diff Editor Title Menu: Previous Change +const previousChangeIcon = registerIcon('diff-editor-previous-change', Codicon.arrowUp, localize('previousChangeIcon', 'Icon for the previous change action in the diff editor.')); appendEditorToolItem( { id: GOTO_PREVIOUS_CHANGE, @@ -588,6 +586,7 @@ appendEditorToolItem( ); // Diff Editor Title Menu: Next Change +const nextChangeIcon = registerIcon('diff-editor-next-change', Codicon.arrowDown, localize('nextChangeIcon', 'Icon for the next change action in the diff editor.')); appendEditorToolItem( { id: GOTO_NEXT_CHANGE, @@ -613,6 +612,7 @@ appendEditorToolItem( undefined ); +const toggleWhitespace = registerIcon('diff-editor-toggle-whitespace', Codicon.whitespace, localize('toggleWhitespace', 'Icon for the toggle whitespace action in the diff editor.')); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 68cd3a6b328..89795c71057 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -16,7 +16,7 @@ import { isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { localize, localize2 } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -30,7 +30,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; -import { ActiveEditorCanSplitInGroupContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupLockedContext, ActiveEditorStickyContext, MultipleEditorGroupsContext, SideBySideEditorActiveContext, TextCompareEditorActiveContext, TextCompareEditorVisibleContext } from 'vs/workbench/common/contextkeys'; +import { ActiveEditorCanSplitInGroupContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupLockedContext, ActiveEditorStickyContext, MultipleEditorGroupsContext, SideBySideEditorActiveContext, TextCompareEditorActiveContext } from 'vs/workbench/common/contextkeys'; import { CloseDirection, EditorInputCapabilities, EditorsOrder, IEditorCommandsContext, IEditorIdentifier, IResourceDiffEditorInput, IUntitledTextResourceEditorInput, IVisibleEditorPane, isEditorIdentifier, isEditorInputWithOptionsAndGroup } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; @@ -41,6 +41,7 @@ import { IEditorResolverService } from 'vs/workbench/services/editor/common/edit import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { DIFF_FOCUS_OTHER_SIDE, DIFF_FOCUS_PRIMARY_SIDE, DIFF_FOCUS_SECONDARY_SIDE, DIFF_OPEN_SIDE, registerDiffEditorCommands } from './diffEditorCommands'; export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors'; export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup'; @@ -65,16 +66,6 @@ export const REOPEN_WITH_COMMAND_ID = 'workbench.action.reopenWithEditor'; export const PIN_EDITOR_COMMAND_ID = 'workbench.action.pinEditor'; export const UNPIN_EDITOR_COMMAND_ID = 'workbench.action.unpinEditor'; -export const TOGGLE_DIFF_SIDE_BY_SIDE = 'toggle.diff.renderSideBySide'; -export const GOTO_NEXT_CHANGE = 'workbench.action.compareEditor.nextChange'; -export const GOTO_PREVIOUS_CHANGE = 'workbench.action.compareEditor.previousChange'; -export const DIFF_FOCUS_PRIMARY_SIDE = 'workbench.action.compareEditor.focusPrimarySide'; -export const DIFF_FOCUS_SECONDARY_SIDE = 'workbench.action.compareEditor.focusSecondarySide'; -export const DIFF_FOCUS_OTHER_SIDE = 'workbench.action.compareEditor.focusOtherSide'; -export const DIFF_OPEN_SIDE = 'workbench.action.compareEditor.openSide'; -export const TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE = 'toggle.diff.ignoreTrimWhitespace'; -export const DIFF_SWAP_SIDES = 'workbench.action.compareEditor.swapSides'; - export const SPLIT_EDITOR = 'workbench.action.splitEditor'; export const SPLIT_EDITOR_UP = 'workbench.action.splitEditorUp'; export const SPLIT_EDITOR_DOWN = 'workbench.action.splitEditorDown'; @@ -373,212 +364,6 @@ function registerEditorGroupsLayoutCommands(): void { }); } -function registerDiffEditorCommands(): void { - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: GOTO_NEXT_CHANGE, - weight: KeybindingWeight.WorkbenchContrib, - when: TextCompareEditorVisibleContext, - primary: KeyMod.Alt | KeyCode.F5, - handler: accessor => navigateInDiffEditor(accessor, true) - }); - - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: GOTO_NEXT_CHANGE, - title: localize2('compare.nextChange', 'Go to Next Change'), - } - }); - - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: GOTO_PREVIOUS_CHANGE, - weight: KeybindingWeight.WorkbenchContrib, - when: TextCompareEditorVisibleContext, - primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F5, - handler: accessor => navigateInDiffEditor(accessor, false) - }); - - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: GOTO_PREVIOUS_CHANGE, - title: localize2('compare.previousChange', 'Go to Previous Change'), - } - }); - - function getActiveTextDiffEditor(accessor: ServicesAccessor): TextDiffEditor | undefined { - const editorService = accessor.get(IEditorService); - - for (const editor of [editorService.activeEditorPane, ...editorService.visibleEditorPanes]) { - if (editor instanceof TextDiffEditor) { - return editor; - } - } - - return undefined; - } - - function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void { - const activeTextDiffEditor = getActiveTextDiffEditor(accessor); - - if (activeTextDiffEditor) { - activeTextDiffEditor.getControl()?.goToDiff(next ? 'next' : 'previous'); - } - } - - enum FocusTextDiffEditorMode { - Original, - Modified, - Toggle - } - - function focusInDiffEditor(accessor: ServicesAccessor, mode: FocusTextDiffEditorMode): void { - const activeTextDiffEditor = getActiveTextDiffEditor(accessor); - - if (activeTextDiffEditor) { - switch (mode) { - case FocusTextDiffEditorMode.Original: - activeTextDiffEditor.getControl()?.getOriginalEditor().focus(); - break; - case FocusTextDiffEditorMode.Modified: - activeTextDiffEditor.getControl()?.getModifiedEditor().focus(); - break; - case FocusTextDiffEditorMode.Toggle: - if (activeTextDiffEditor.getControl()?.getModifiedEditor().hasWidgetFocus()) { - return focusInDiffEditor(accessor, FocusTextDiffEditorMode.Original); - } else { - return focusInDiffEditor(accessor, FocusTextDiffEditorMode.Modified); - } - } - } - } - - function toggleDiffSideBySide(accessor: ServicesAccessor): void { - const configurationService = accessor.get(IConfigurationService); - - const newValue = !configurationService.getValue('diffEditor.renderSideBySide'); - configurationService.updateValue('diffEditor.renderSideBySide', newValue); - } - - function toggleDiffIgnoreTrimWhitespace(accessor: ServicesAccessor): void { - const configurationService = accessor.get(IConfigurationService); - - const newValue = !configurationService.getValue('diffEditor.ignoreTrimWhitespace'); - configurationService.updateValue('diffEditor.ignoreTrimWhitespace', newValue); - } - - async function swapDiffSides(accessor: ServicesAccessor): Promise { - const editorService = accessor.get(IEditorService); - - const diffEditor = getActiveTextDiffEditor(accessor); - const activeGroup = diffEditor?.group; - const diffInput = diffEditor?.input; - if (!diffEditor || typeof activeGroup === 'undefined' || !(diffInput instanceof DiffEditorInput) || !diffInput.modified.resource) { - return; - } - - const untypedDiffInput = diffInput.toUntyped({ preserveViewState: activeGroup.id, preserveResource: true }); - if (!untypedDiffInput) { - return; - } - - // Since we are about to replace the diff editor, make - // sure to first open the modified side if it is not - // yet opened. This ensures that the swapping is not - // bringing up a confirmation dialog to save. - if (diffInput.modified.isModified() && editorService.findEditors({ resource: diffInput.modified.resource, typeId: diffInput.modified.typeId, editorId: diffInput.modified.editorId }).length === 0) { - await editorService.openEditor({ - ...untypedDiffInput.modified, - options: { - ...untypedDiffInput.modified.options, - pinned: true, - inactive: true - } - }, activeGroup); - } - - // Replace the input with the swapped variant - await editorService.replaceEditors([ - { - editor: diffInput, - replacement: { - ...untypedDiffInput, - original: untypedDiffInput.modified, - modified: untypedDiffInput.original, - options: { - ...untypedDiffInput.options, - pinned: true - } - } - } - ], activeGroup); - } - - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: TOGGLE_DIFF_SIDE_BY_SIDE, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: undefined, - handler: accessor => toggleDiffSideBySide(accessor) - }); - - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: DIFF_FOCUS_PRIMARY_SIDE, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: undefined, - handler: accessor => focusInDiffEditor(accessor, FocusTextDiffEditorMode.Modified) - }); - - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: DIFF_FOCUS_SECONDARY_SIDE, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: undefined, - handler: accessor => focusInDiffEditor(accessor, FocusTextDiffEditorMode.Original) - }); - - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: DIFF_FOCUS_OTHER_SIDE, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: undefined, - handler: accessor => focusInDiffEditor(accessor, FocusTextDiffEditorMode.Toggle) - }); - - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: undefined, - handler: accessor => toggleDiffIgnoreTrimWhitespace(accessor) - }); - - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: DIFF_SWAP_SIDES, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: undefined, - handler: accessor => swapDiffSides(accessor) - }); - - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: TOGGLE_DIFF_SIDE_BY_SIDE, - title: localize2('toggleInlineView', "Toggle Inline View"), - category: localize('compare', "Compare") - }, - when: TextCompareEditorActiveContext - }); - - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: DIFF_SWAP_SIDES, - title: localize2('swapDiffSides', "Swap Left and Right Editor Side"), - category: localize('compare', "Compare") - }, - when: TextCompareEditorActiveContext - }); -} - function registerOpenEditorAPICommands(): void { function mixinContext(context: IOpenEvent | undefined, options: ITextEditorOptions | undefined, column: EditorGroupColumn | undefined): [ITextEditorOptions | undefined, EditorGroupColumn | undefined] { From 6176d9642094a0140f4318659a683bb3baf0915c Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 19:22:57 +0100 Subject: [PATCH 1591/1897] Fixes CI --- .../contrib/chat/browser/actions/chatAccessibilityHelp.ts | 2 +- src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 9fd67d69b88..92b430ff162 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -12,7 +12,7 @@ import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { AccessibleDiffViewerNext } from 'vs/editor/browser/widget/diffEditor/diffEditor.contribution'; +import { AccessibleDiffViewerNext } from 'vs/editor/browser/widget/diffEditor/commands'; export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'panelChat' | 'inlineChat'): string { const keybindingService = accessor.get(IKeybindingService); diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index a19d88cca33..902b335ba4f 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -8,7 +8,7 @@ import { autorunWithStore, observableFromEvent } from 'vs/base/common/observable import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { registerDiffEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { AccessibleDiffViewerNext, AccessibleDiffViewerPrev } from 'vs/editor/browser/widget/diffEditor/diffEditor.contribution'; +import { AccessibleDiffViewerNext, AccessibleDiffViewerPrev } from 'vs/editor/browser/widget/diffEditor/commands'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; import { IDiffEditorContribution } from 'vs/editor/common/editorCommon'; From 9e5982f512e32739e6ffe92e8c9b123531430b5a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 26 Feb 2024 12:51:14 +0100 Subject: [PATCH 1592/1897] Some :lipstick: around the code (#206222) --- .../browser/parts/editor/editorStatus.ts | 45 ++++++++++--------- src/vs/workbench/common/contributions.ts | 2 +- src/vs/workbench/common/editor.ts | 6 +-- .../common/editor/sideBySideEditorInput.ts | 6 +-- .../browser/multiDiffEditorInput.ts | 4 +- .../contrib/remote/browser/remoteIndicator.ts | 45 +++++++++++-------- .../editor/browser/editorResolverService.ts | 4 +- .../editor/common/editorResolverService.ts | 4 +- 8 files changed, 63 insertions(+), 53 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index e1ee2bc0d94..d34a58122cd 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -293,8 +293,6 @@ class TabFocusMode extends Disposable { const tabFocusModeConfig = configurationService.getValue('editor.tabFocusMode') === true ? true : false; TabFocus.setTabFocusMode(tabFocusModeConfig); - - this._onDidChange.fire(tabFocusModeConfig); } private registerListeners(): void { @@ -328,14 +326,16 @@ class EditorStatus extends Disposable { private readonly eolElement = this._register(new MutableDisposable()); private readonly languageElement = this._register(new MutableDisposable()); private readonly metadataElement = this._register(new MutableDisposable()); - private readonly currentProblemStatus = this._register(this.instantiationService.createInstance(ShowCurrentMarkerInStatusbarContribution)); - private readonly state = new State(); - private readonly activeEditorListeners = this._register(new DisposableStore()); - private readonly delayedRender = this._register(new MutableDisposable()); + + private readonly currentMarkerStatus = this._register(this.instantiationService.createInstance(ShowCurrentMarkerInStatusbarContribution)); private readonly tabFocusMode = this.instantiationService.createInstance(TabFocusMode); + private readonly state = new State(); private toRender: StateChange | undefined = undefined; + private readonly activeEditorListeners = this._register(new DisposableStore()); + private readonly delayedRender = this._register(new MutableDisposable()); + constructor( private readonly targetWindowId: number, @IEditorService private readonly editorService: IEditorService, @@ -634,7 +634,7 @@ class EditorStatus extends Disposable { this.onEncodingChange(activeEditorPane, activeCodeEditor); this.onIndentationChange(activeCodeEditor); this.onMetadataChange(activeEditorPane); - this.currentProblemStatus.update(activeCodeEditor); + this.currentMarkerStatus.update(activeCodeEditor); // Dispose old active editor listeners this.activeEditorListeners.clear(); @@ -662,7 +662,7 @@ class EditorStatus extends Disposable { // Hook Listener for Selection changes this.activeEditorListeners.add(Event.defer(activeCodeEditor.onDidChangeCursorPosition)(() => { this.onSelectionChange(activeCodeEditor); - this.currentProblemStatus.update(activeCodeEditor); + this.currentMarkerStatus.update(activeCodeEditor); })); // Hook Listener for language changes @@ -673,7 +673,7 @@ class EditorStatus extends Disposable { // Hook Listener for content changes this.activeEditorListeners.add(Event.accumulate(activeCodeEditor.onDidChangeModelContent)(e => { this.onEOLChange(activeCodeEditor); - this.currentProblemStatus.update(activeCodeEditor); + this.currentMarkerStatus.update(activeCodeEditor); const selections = activeCodeEditor.getSelections(); if (selections) { @@ -918,13 +918,16 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable { @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(); + this.statusBarEntryAccessor = this._register(new MutableDisposable()); + this._register(markerService.onMarkerChanged(changedResources => this.onMarkerChanged(changedResources))); this._register(Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('problems.showCurrentInStatus'))(() => this.updateStatus())); } update(editor: ICodeEditor | undefined): void { this.editor = editor; + this.updateMarkers(); this.updateStatus(); } @@ -1022,26 +1025,26 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable { resource: model.uri, severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info }); - this.markers.sort(compareMarker); + this.markers.sort(this.compareMarker); } else { this.markers = []; } this.updateStatus(); } -} -function compareMarker(a: IMarker, b: IMarker): number { - let res = compare(a.resource.toString(), b.resource.toString()); - if (res === 0) { - res = MarkerSeverity.compare(a.severity, b.severity); + private compareMarker(a: IMarker, b: IMarker): number { + let res = compare(a.resource.toString(), b.resource.toString()); + if (res === 0) { + res = MarkerSeverity.compare(a.severity, b.severity); + } + + if (res === 0) { + res = Range.compareRangesUsingStarts(a, b); + } + + return res; } - - if (res === 0) { - res = Range.compareRangesUsingStarts(a, b); - } - - return res; } export class ShowLanguageExtensionsAction extends Action { diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index b96df678196..aaf1452c25a 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -385,7 +385,7 @@ export class WorkbenchContributionsRegistry extends Disposable implements IWorkb } } - if (typeof contribution.id === 'string' || !environmentService.isBuilt /* only log out of sources where we have good ctor names (TODO@bpasero remove when adopted IDs) */) { + if (typeof contribution.id === 'string' || !environmentService.isBuilt /* only log out of sources where we have good ctor names */) { const time = Date.now() - now; if (time > (phase < LifecyclePhase.Restored ? WorkbenchContributionsRegistry.BLOCK_BEFORE_RESTORE_WARN_THRESHOLD : WorkbenchContributionsRegistry.BLOCK_AFTER_RESTORE_WARN_THRESHOLD)) { logService.warn(`Creation of workbench contribution '${contribution.id ?? contribution.ctor.name}' took ${time}ms.`); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index c6986bd0f57..9652c2dfe00 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -564,7 +564,7 @@ export function isResourceDiffEditorInput(editor: unknown): editor is IResourceD return candidate?.original !== undefined && candidate.modified !== undefined; } -export function isResourceDiffListEditorInput(editor: unknown): editor is IResourceMultiDiffEditorInput { +export function isResourceMultiDiffEditorInput(editor: unknown): editor is IResourceMultiDiffEditorInput { if (isEditorInput(editor)) { return false; // make sure to not accidentally match on typed editor inputs } @@ -1310,7 +1310,7 @@ class EditorResourceAccessorImpl { } } - if (isResourceDiffEditorInput(editor) || isResourceDiffListEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) { + if (isResourceDiffEditorInput(editor) || isResourceMultiDiffEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) { return undefined; } @@ -1379,7 +1379,7 @@ class EditorResourceAccessorImpl { } } - if (isResourceDiffEditorInput(editor) || isResourceDiffListEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) { + if (isResourceDiffEditorInput(editor) || isResourceMultiDiffEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) { return undefined; } diff --git a/src/vs/workbench/common/editor/sideBySideEditorInput.ts b/src/vs/workbench/common/editor/sideBySideEditorInput.ts index 7228b47ae71..0c6e63d43ce 100644 --- a/src/vs/workbench/common/editor/sideBySideEditorInput.ts +++ b/src/vs/workbench/common/editor/sideBySideEditorInput.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorInputCapabilities, GroupIdentifier, ISaveOptions, IRevertOptions, EditorExtensions, IEditorFactoryRegistry, IEditorSerializer, ISideBySideEditorInput, IUntypedEditorInput, isResourceSideBySideEditorInput, isDiffEditorInput, isResourceDiffEditorInput, IResourceSideBySideEditorInput, findViewStateForEditor, IMoveResult, isEditorInput, isResourceEditorInput, Verbosity, isResourceMergeEditorInput, isResourceDiffListEditorInput } from 'vs/workbench/common/editor'; +import { EditorInputCapabilities, GroupIdentifier, ISaveOptions, IRevertOptions, EditorExtensions, IEditorFactoryRegistry, IEditorSerializer, ISideBySideEditorInput, IUntypedEditorInput, isResourceSideBySideEditorInput, isDiffEditorInput, isResourceDiffEditorInput, IResourceSideBySideEditorInput, findViewStateForEditor, IMoveResult, isEditorInput, isResourceEditorInput, Verbosity, isResourceMergeEditorInput, isResourceMultiDiffEditorInput } from 'vs/workbench/common/editor'; import { EditorInput, IUntypedEditorOptions } from 'vs/workbench/common/editor/editorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -210,7 +210,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi return new SideBySideEditorInput(this.preferredName, this.preferredDescription, primarySaveResult, primarySaveResult, this.editorService); } - if (!isResourceDiffEditorInput(primarySaveResult) && !isResourceDiffListEditorInput(primarySaveResult) && !isResourceSideBySideEditorInput(primarySaveResult) && !isResourceMergeEditorInput(primarySaveResult)) { + if (!isResourceDiffEditorInput(primarySaveResult) && !isResourceMultiDiffEditorInput(primarySaveResult) && !isResourceSideBySideEditorInput(primarySaveResult) && !isResourceMergeEditorInput(primarySaveResult)) { return { primary: primarySaveResult, secondary: primarySaveResult, @@ -279,7 +279,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi if ( primaryResourceEditorInput && secondaryResourceEditorInput && !isResourceDiffEditorInput(primaryResourceEditorInput) && !isResourceDiffEditorInput(secondaryResourceEditorInput) && - !isResourceDiffListEditorInput(primaryResourceEditorInput) && !isResourceDiffListEditorInput(secondaryResourceEditorInput) && + !isResourceMultiDiffEditorInput(primaryResourceEditorInput) && !isResourceMultiDiffEditorInput(secondaryResourceEditorInput) && !isResourceSideBySideEditorInput(primaryResourceEditorInput) && !isResourceSideBySideEditorInput(secondaryResourceEditorInput) && !isResourceMergeEditorInput(primaryResourceEditorInput) && !isResourceMergeEditorInput(secondaryResourceEditorInput) ) { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 4344d015873..ab81ed790df 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -343,9 +343,9 @@ export class MultiDiffEditorResolverContribution extends Disposable { }, {}, { - createMultiDiffEditorInput: (diffListEditor: IResourceMultiDiffEditorInput): EditorInputWithOptions => { + createMultiDiffEditorInput: (multiDiffEditor: IResourceMultiDiffEditorInput): EditorInputWithOptions => { return { - editor: MultiDiffEditorInput.fromResourceMultiDiffEditorInput(diffListEditor, instantiationService), + editor: MultiDiffEditorInput.fromResourceMultiDiffEditorInput(multiDiffEditor, instantiationService), }; }, } diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index f362b61aa1b..f5d98f3fd90 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -97,7 +97,32 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr private measureNetworkConnectionLatencyScheduler: RunOnceScheduler | undefined = undefined; private loggedInvalidGroupNames: { [group: string]: boolean } = Object.create(null); - private readonly remoteExtensionMetadata: RemoteExtensionMetadata[]; + + private _remoteExtensionMetadata: RemoteExtensionMetadata[] | undefined = undefined; + private get remoteExtensionMetadata(): RemoteExtensionMetadata[] { + if (!this._remoteExtensionMetadata) { + const remoteExtensionTips = { ...this.productService.remoteExtensionTips, ...this.productService.virtualWorkspaceExtensionTips }; + this._remoteExtensionMetadata = Object.values(remoteExtensionTips).filter(value => value.startEntry !== undefined).map(value => { + return { + id: value.extensionId, + installed: false, + friendlyName: value.friendlyName, + isPlatformCompatible: false, + dependencies: [], + helpLink: value.startEntry?.helpLink ?? '', + startConnectLabel: value.startEntry?.startConnectLabel ?? '', + startCommand: value.startEntry?.startCommand ?? '', + priority: value.startEntry?.priority ?? 10, + supportedPlatforms: value.supportedPlatforms + }; + }); + + this.remoteExtensionMetadata.sort((ext1, ext2) => ext1.priority - ext2.priority); + } + + return this._remoteExtensionMetadata; + } + private remoteMetadataInitialized: boolean = false; private readonly _onDidChangeEntries = this._register(new Emitter()); private readonly onDidChangeEntries: Event = this._onDidChangeEntries.event; @@ -124,24 +149,6 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr ) { super(); - const remoteExtensionTips = { ...this.productService.remoteExtensionTips, ...this.productService.virtualWorkspaceExtensionTips }; - this.remoteExtensionMetadata = Object.values(remoteExtensionTips).filter(value => value.startEntry !== undefined).map(value => { - return { - id: value.extensionId, - installed: false, - friendlyName: value.friendlyName, - isPlatformCompatible: false, - dependencies: [], - helpLink: value.startEntry?.helpLink ?? '', - startConnectLabel: value.startEntry?.startConnectLabel ?? '', - startCommand: value.startEntry?.startCommand ?? '', - priority: value.startEntry?.priority ?? 10, - supportedPlatforms: value.supportedPlatforms - }; - }); - - this.remoteExtensionMetadata.sort((ext1, ext2) => ext1.priority - ext2.priority); - // Set initial connection state if (this.remoteAuthority) { this.connectionState = 'initializing'; diff --git a/src/vs/workbench/services/editor/browser/editorResolverService.ts b/src/vs/workbench/services/editor/browser/editorResolverService.ts index 91d1b7f6da9..d6c3375262a 100644 --- a/src/vs/workbench/services/editor/browser/editorResolverService.ts +++ b/src/vs/workbench/services/editor/browser/editorResolverService.ts @@ -10,7 +10,7 @@ import { basename, extname, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EditorActivation, EditorResolution, IEditorOptions } from 'vs/platform/editor/common/editor'; -import { DEFAULT_EDITOR_ASSOCIATION, EditorResourceAccessor, EditorInputWithOptions, IResourceSideBySideEditorInput, isEditorInputWithOptions, isEditorInputWithOptionsAndGroup, isResourceDiffEditorInput, isResourceSideBySideEditorInput, isUntitledResourceEditorInput, isResourceMergeEditorInput, IUntypedEditorInput, SideBySideEditor, isResourceDiffListEditorInput } from 'vs/workbench/common/editor'; +import { DEFAULT_EDITOR_ASSOCIATION, EditorResourceAccessor, EditorInputWithOptions, IResourceSideBySideEditorInput, isEditorInputWithOptions, isEditorInputWithOptionsAndGroup, isResourceDiffEditorInput, isResourceSideBySideEditorInput, isUntitledResourceEditorInput, isResourceMergeEditorInput, IUntypedEditorInput, SideBySideEditor, isResourceMultiDiffEditorInput } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { Schemas } from 'vs/base/common/network'; @@ -476,7 +476,7 @@ export class EditorResolverService extends Disposable implements IEditorResolver } // If it's a diff list editor we trigger the create diff list editor input - if (isResourceDiffListEditorInput(editor)) { + if (isResourceMultiDiffEditorInput(editor)) { if (!selectedEditor.editorFactoryObject.createMultiDiffEditorInput) { return; } diff --git a/src/vs/workbench/services/editor/common/editorResolverService.ts b/src/vs/workbench/services/editor/common/editorResolverService.ts index cd571b4d9e2..bc3a26d311b 100644 --- a/src/vs/workbench/services/editor/common/editorResolverService.ts +++ b/src/vs/workbench/services/editor/common/editorResolverService.ts @@ -108,7 +108,7 @@ export type UntitledEditorInputFactoryFunction = (untitledEditorInput: IUntitled export type DiffEditorInputFactoryFunction = (diffEditorInput: IResourceDiffEditorInput, group: IEditorGroup) => EditorInputFactoryResult; -export type DiffListEditorInputFactoryFunction = (diffEditorInput: IResourceMultiDiffEditorInput, group: IEditorGroup) => EditorInputFactoryResult; +export type MultiDiffEditorInputFactoryFunction = (multiDiffEditorInput: IResourceMultiDiffEditorInput, group: IEditorGroup) => EditorInputFactoryResult; export type MergeEditorInputFactoryFunction = (mergeEditorInput: IResourceMergeEditorInput, group: IEditorGroup) => EditorInputFactoryResult; @@ -116,7 +116,7 @@ type EditorInputFactories = { createEditorInput?: EditorInputFactoryFunction; createUntitledEditorInput?: UntitledEditorInputFactoryFunction; createDiffEditorInput?: DiffEditorInputFactoryFunction; - createMultiDiffEditorInput?: DiffListEditorInputFactoryFunction; + createMultiDiffEditorInput?: MultiDiffEditorInputFactoryFunction; createMergeEditorInput?: MergeEditorInputFactoryFunction; }; From 9f4bcdde684bc54e311ea49cb469771796094e7d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 26 Feb 2024 13:08:31 +0100 Subject: [PATCH 1593/1897] voice - tweak mic animation further (#206064) --- .../electron-sandbox/actions/voiceChatActions.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index b508d24ff6b..36ff7b6c4b0 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -788,7 +788,18 @@ registerThemingParticipant((theme, collector) => { .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { border-radius: 50%; outline: 2px solid ${activeRecordingColor}; - animation: pulseAnimation 1500ms ease-in-out infinite !important; + outline-offset: -1px; + animation: pulseAnimation 1500ms cubic-bezier(0.75, 0, 0.25, 1) infinite; + } + + .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, + .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { + position: absolute; + outline: 1px solid ${activeRecordingColor}; + outline-offset: 2px; + border-radius: 50%; + width: 16px; + height: 16px; } @keyframes pulseAnimation { From 10e94ec363bf41ca09098a44186bc915b710cb61 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 26 Feb 2024 14:03:25 +0100 Subject: [PATCH 1594/1897] rename suggestions: use editor#typicalHalfwidthCharacterWidth --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 6cb9f8a8c5a..3bb00ecdb05 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -435,6 +435,7 @@ class CandidatesView { private _lineHeight: number; private _availableHeight: number; private _minimumWidth: number; + private _typicalHalfwidthCharacterWidth: number; private _disposables: DisposableStore; @@ -446,6 +447,7 @@ class CandidatesView { this._minimumWidth = 0; this._lineHeight = opts.fontInfo.lineHeight; + this._typicalHalfwidthCharacterWidth = opts.fontInfo.typicalHalfwidthCharacterWidth; this._listContainer = document.createElement('div'); this._listContainer.style.fontFamily = opts.fontInfo.fontFamily; @@ -612,8 +614,7 @@ class CandidatesView { } private _pickListWidth(candidates: NewSymbolName[]): number { - const APPROXIMATE_CHAR_WIDTH = 8; // approximate # of pixes taken by a single character - const longestCandidateWidth = Math.ceil(Math.max(...candidates.map(c => c.newSymbolName.length)) * APPROXIMATE_CHAR_WIDTH); // TODO@ulugbekna: use editor#typicalCharacterWidth or something + const longestCandidateWidth = Math.ceil(Math.max(...candidates.map(c => c.newSymbolName.length)) * this._typicalHalfwidthCharacterWidth); const width = Math.max( this._minimumWidth, 4 /* padding */ + 16 /* sparkle icon */ + 5 /* margin-left */ + longestCandidateWidth + 10 /* (possibly visible) scrollbar width */ // TODO@ulugbekna: approximate calc - clean this up From 20f1afb29131d150028318dfe8afe33efc288693 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 26 Feb 2024 14:40:58 +0100 Subject: [PATCH 1595/1897] api - update todos, rename makeChatRequest (#206240) --- src/vs/workbench/api/common/extHost.api.impl.ts | 2 +- src/vscode-dts/vscode.proposed.languageModels.d.ts | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 3bf78d8ed24..65b718f3f05 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1437,7 +1437,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'languageModels'); return extHostChatProvider.onDidChangeProviders(listener, thisArgs, disposables); }, - makeChatRequest(languageModel: string, messages: vscode.LanguageModelMessage[], optionsOrToken: { [name: string]: any } | vscode.CancellationToken, token?: vscode.CancellationToken) { + chatRequest(languageModel: string, messages: vscode.LanguageModelMessage[], optionsOrToken: { [name: string]: any } | vscode.CancellationToken, token?: vscode.CancellationToken) { checkProposedApiEnabled(extension, 'languageModels'); let options: Record; if (CancellationToken.isCancellationToken(optionsOrToken)) { diff --git a/src/vscode-dts/vscode.proposed.languageModels.d.ts b/src/vscode-dts/vscode.proposed.languageModels.d.ts index bde1bd4aad4..258a5bf18e9 100644 --- a/src/vscode-dts/vscode.proposed.languageModels.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModels.d.ts @@ -190,12 +190,19 @@ declare module 'vscode' { */ // TODO@API refine doc // TODO@API define specific error types? - export function makeChatRequest(languageModel: string, messages: LanguageModelMessage[], options: { [name: string]: any }, token: CancellationToken): Thenable; + // TODO@API NAME: chatRequest + // TODO@API NAME: ChatRequestXYZMessage + // TODO@API NAME: ChatRequestResponse + export function chatRequest(languageModel: string, messages: LanguageModelMessage[], options: { [name: string]: any }, token: CancellationToken): Thenable; /** - * @see {@link makeChatRequest} + * @see {@link chatRequest} */ - export function makeChatRequest(languageModel: string, messages: LanguageModelMessage[], token: CancellationToken): Thenable; + export function chatRequest(languageModel: string, messages: LanguageModelMessage[], token: CancellationToken): Thenable; + + // TODO@API probe on having access + // TODO@API, BETTER?: ExtensionContext.permissions.languageModels: Record; + // export function canMakeChatRequest(languageModel: string): Thenable; /** * The identifiers of all language models that are currently available. From b1a49d4cdb0ae53e19a93a27cb276061e34d9d8b Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 26 Feb 2024 15:19:48 +0100 Subject: [PATCH 1596/1897] Fix hover pointer and override options (#206246) fix hover pointer and override options --- src/vs/platform/hover/browser/hover.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index fea45187b43..0a593c04ec8 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -281,14 +281,17 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate return this.hoverService.showHover({ ...options, + ...overrideOptions, persistence: { hideOnHover: true, hideOnKeyDown: true, + ...overrideOptions.persistence }, appearance: { + ...options.appearance, compact: true, - }, - ...overrideOptions + ...overrideOptions.appearance + } }, focus); } From 845c8ed65c5daecf5395d6216db63cf2bb0eaa9b Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 26 Feb 2024 15:35:06 +0100 Subject: [PATCH 1597/1897] Indentation doesn't work in .yml files (#205979) --- extensions/docker/language-configuration.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/extensions/docker/language-configuration.json b/extensions/docker/language-configuration.json index cab4f6602ff..08a483ad5ce 100644 --- a/extensions/docker/language-configuration.json +++ b/extensions/docker/language-configuration.json @@ -20,5 +20,9 @@ ["(", ")"], ["\"", "\""], ["'", "'"] - ] -} \ No newline at end of file + ], + "indentationRules": { + "increaseIndentPattern": "^\\s*.*(:|-) ?(&\\w+)?(\\{[^}\"']*|\\([^)\"']*)?$", + "decreaseIndentPattern": "^\\s+\\}$" + } +} From 69db64290797e02b48dc481e4be0928b5cf02d39 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 26 Feb 2024 07:06:57 -0800 Subject: [PATCH 1598/1897] Add warning to suggestEnabled via deprecation message I get the occasional report of people bricking their terminal because of this setting. I'd prefer not to remove it, the deprecation message gives the best of both worlds (still in code, loud warning when used). Fixes #206172 --- .../workbench/contrib/terminal/common/terminalConfiguration.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 0df9dc1171c..3b3cafe11ab 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -601,7 +601,8 @@ const terminalConfiguration: IConfigurationNode = { restricted: true, markdownDescription: localize('terminal.integrated.shellIntegration.suggestEnabled', "Enables experimental terminal Intellisense suggestions for supported shells when {0} is set to {1}. If shell integration is installed manually, {2} needs to be set to {3} before calling the script.", '`#terminal.integrated.shellIntegration.enabled#`', '`true`', '`VSCODE_SUGGEST`', '`1`'), type: 'boolean', - default: false + default: false, + markdownDeprecationMessage: localize('suggestEnabled.deprecated', 'This is an experimental setting and may break the terminal! Use at your own risk.') }, [TerminalSettingId.SmoothScrolling]: { markdownDescription: localize('terminal.integrated.smoothScrolling', "Controls whether the terminal will scroll using an animation."), From 5443fa0bd8d08629f69e3ed075cb358537867bb7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 26 Feb 2024 07:55:29 -0800 Subject: [PATCH 1599/1897] De-dupe links and add URI-based presentation Fixes #206260 Fixes #206124 --- .../links/browser/terminalLinkManager.ts | 2 +- .../links/browser/terminalLinkQuickpick.ts | 55 +++++++++++++++++-- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts index 8d981fb62d2..5d8f803de51 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts @@ -441,6 +441,6 @@ export interface ILineColumnInfo { export interface IDetectedLinks { wordLinks?: ILink[]; webLinks?: ILink[]; - fileLinks?: ILink[]; + fileLinks?: (ILink | TerminalLink)[]; folderLinks?: ILink[]; } diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts index 0cea7b3f514..2c042949a9d 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts @@ -22,6 +22,8 @@ import { getLinkSuffix } from 'vs/workbench/contrib/terminalContrib/links/browse import { TerminalBuiltinLinkType } from 'vs/workbench/contrib/terminalContrib/links/browser/links'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import type { IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { basenameOrAuthority, dirname } from 'vs/base/common/resources'; export class TerminalLinkQuickpick extends DisposableStore { @@ -35,6 +37,7 @@ export class TerminalLinkQuickpick extends DisposableStore { @IConfigurationService private readonly _configurationService: IConfigurationService, @IEditorService private readonly _editorService: IEditorService, @IHistoryService private readonly _historyService: IHistoryService, + @ILabelService private readonly _labelService: ILabelService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService ) { @@ -148,17 +151,57 @@ export class TerminalLinkQuickpick extends DisposableStore { /** * @param ignoreLinks Links with labels to not include in the picks. */ - private async _generatePicks(links: ILink[], ignoreLinks?: ILink[]): Promise { + private async _generatePicks(links: (ILink | TerminalLink)[], ignoreLinks?: ILink[]): Promise { if (!links) { return; } - const linkKeys: Set = new Set(); + const linkTextKeys: Set = new Set(); + const linkUriKeys: Set = new Set(); const picks: ITerminalLinkQuickPickItem[] = []; for (const link of links) { - const label = link.text; - if (!linkKeys.has(label) && (!ignoreLinks || !ignoreLinks.some(e => e.text === label))) { - linkKeys.add(label); - picks.push({ label, link }); + let label = link.text; + if (!linkTextKeys.has(label) && (!ignoreLinks || !ignoreLinks.some(e => e.text === label))) { + linkTextKeys.add(label); + + // Add a consistently formatted resolved URI label to the description if applicable + let description: string | undefined; + if ('uri' in link && link.uri) { + // For local files and folders, mimic the presentation of go to file + if ( + link.type === TerminalBuiltinLinkType.LocalFile || + link.type === TerminalBuiltinLinkType.LocalFolderInWorkspace || + link.type === TerminalBuiltinLinkType.LocalFolderOutsideWorkspace + ) { + label = basenameOrAuthority(link.uri); + description = this._labelService.getUriLabel(dirname(link.uri), { relative: true }); + } + + // Add line and column numbers to the label if applicable + if (link.type === TerminalBuiltinLinkType.LocalFile) { + if (link.parsedLink?.suffix?.row !== undefined) { + label += `:${link.parsedLink.suffix.row}`; + if (link.parsedLink?.suffix?.rowEnd !== undefined) { + label += `-${link.parsedLink.suffix.rowEnd}`; + } + if (link.parsedLink?.suffix?.col !== undefined) { + label += `:${link.parsedLink.suffix.col}`; + if (link.parsedLink?.suffix?.colEnd !== undefined) { + label += `-${link.parsedLink.suffix.colEnd}`; + } + } + } + } + + // Skip the link if it's a duplicate URI + line/col + if (description) { + if (linkUriKeys.has(label + '|' + description)) { + continue; + } + linkUriKeys.add(label + '|' + description); + } + } + + picks.push({ label, link, description }); } } return picks.length > 0 ? picks : undefined; From a984153d6a98fba04f0a39a5ab9918c7b6e47511 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:02:42 +0100 Subject: [PATCH 1600/1897] Hover debt. Mainly import changes (#206247) * hover debt * hover import * less hover imports --- .../browser/ui/actionbar/actionViewItems.ts | 13 ++- src/vs/base/browser/ui/actionbar/actionbar.ts | 6 +- src/vs/base/browser/ui/button/button.ts | 10 +-- src/vs/base/browser/ui/dropdown/dropdown.ts | 8 +- .../ui/dropdown/dropdownActionViewItem.ts | 4 +- src/vs/base/browser/ui/findinput/findInput.ts | 4 +- .../browser/ui/findinput/findInputToggles.ts | 4 +- .../base/browser/ui/findinput/replaceInput.ts | 2 +- src/vs/base/browser/ui/hover/hoverDelegate.ts | 85 +++++++++++++------ .../browser/ui/hover/hoverDelegateFactory.ts | 35 ++++++++ .../ui/hover/{hover.css => hoverWidget.css} | 0 src/vs/base/browser/ui/hover/hoverWidget.ts | 2 +- .../updatableHoverWidget.ts} | 5 +- .../browser/ui/iconLabel/iconHoverDelegate.ts | 72 ---------------- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 6 +- .../browser/ui/selectBox/selectBoxCustom.ts | 24 ++++-- src/vs/base/browser/ui/table/tableWidget.ts | 9 +- src/vs/base/browser/ui/toggle/toggle.ts | 6 +- src/vs/base/browser/ui/toolbar/toolbar.ts | 6 +- src/vs/base/browser/ui/tree/abstractTree.ts | 6 +- .../services/hoverService/hoverService.ts | 2 +- .../services/hoverService/hoverWidget.ts | 2 +- .../contrib/find/browser/findOptionsWidget.ts | 4 +- .../editor/contrib/find/browser/findWidget.ts | 10 +-- .../browser/standaloneCodeEditor.ts | 2 +- src/vs/platform/actions/browser/buttonbar.ts | 6 +- .../dropdownWithPrimaryActionViewItem.ts | 2 +- .../browser/menuEntryActionViewItem.ts | 2 +- src/vs/platform/hover/browser/hover.ts | 3 +- .../platform/quickinput/browser/quickInput.ts | 2 +- .../quickinput/browser/quickInputList.ts | 4 +- .../browser/parts/compositeBarActions.ts | 2 +- .../workbench/browser/parts/compositePart.ts | 8 +- .../parts/editor/breadcrumbsControl.ts | 4 +- .../browser/parts/editor/editorTabsControl.ts | 4 +- .../notifications/notificationsViewer.ts | 4 +- .../browser/parts/statusbar/statusbarItem.ts | 4 +- .../parts/titlebar/commandCenterControl.ts | 6 +- .../browser/parts/titlebar/titlebarPart.ts | 6 +- .../workbench/browser/parts/views/checkbox.ts | 4 +- .../workbench/browser/parts/views/treeView.ts | 4 +- .../workbench/browser/parts/views/viewPane.ts | 4 +- src/vs/workbench/browser/workbench.ts | 2 +- .../contrib/comments/browser/commentReply.ts | 4 +- .../comments/browser/commentsTreeViewer.ts | 4 +- .../contrib/comments/browser/timestamp.ts | 4 +- .../contrib/debug/browser/baseDebugView.ts | 4 +- .../contrib/debug/browser/breakpointsView.ts | 4 +- .../contrib/debug/browser/callStackView.ts | 8 +- .../debug/browser/debugActionViewItems.ts | 4 +- .../contrib/debug/browser/replViewer.ts | 4 +- .../abstractRuntimeExtensionsEditor.ts | 4 +- .../extensions/browser/extensionEditor.ts | 4 +- .../extensions/browser/extensionsWidgets.ts | 4 +- .../files/browser/views/explorerViewer.ts | 3 +- .../inlineChat/browser/inlineChatWidget.ts | 4 +- .../browser/view/cellParts/cellActionView.ts | 4 +- .../browser/view/cellParts/cellStatusPart.ts | 4 +- .../browser/view/cellParts/cellToolbars.ts | 6 +- .../settingsEditorSettingIndicators.ts | 2 +- .../contrib/remote/browser/tunnelView.ts | 4 +- .../search/browser/patternInputWidget.ts | 2 +- .../search/browser/searchResultsView.ts | 4 +- .../contrib/search/browser/searchView.ts | 4 +- .../contrib/search/browser/searchWidget.ts | 2 +- .../searchEditor/browser/searchEditor.ts | 4 +- .../contrib/terminal/browser/terminalView.ts | 2 +- .../testing/browser/testCoverageBars.ts | 4 +- .../testing/browser/testingExplorerView.ts | 4 +- .../contrib/timeline/browser/timelinePane.ts | 4 +- .../userDataProfileImportExportService.ts | 2 +- 71 files changed, 257 insertions(+), 248 deletions(-) create mode 100644 src/vs/base/browser/ui/hover/hoverDelegateFactory.ts rename src/vs/base/browser/ui/hover/{hover.css => hoverWidget.css} (100%) rename src/vs/base/browser/ui/{iconLabel/iconLabelHover.ts => hover/updatableHoverWidget.ts} (98%) delete mode 100644 src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index fd7424b0f72..698d711f4dc 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -9,9 +9,9 @@ import { addDisposableListener, EventHelper, EventLike, EventType } from 'vs/bas import { EventType as TouchEventType, Gesture } from 'vs/base/browser/touch'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { ISelectBoxOptions, ISelectBoxStyles, ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; import { IToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; import { Action, ActionRunner, IAction, IActionChangeEvent, IActionRunner, Separator } from 'vs/base/common/actions'; @@ -230,11 +230,10 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { /* While custom hover is not supported with context view */ this.element.title = title; } else { - if (!this.customHover) { + if (!this.customHover && title !== '') { const hoverDelegate = this.options.hoverDelegate ?? getDefaultHoverDelegate('element'); - this.customHover = setupCustomHover(hoverDelegate, this.element, title); - this._store.add(this.customHover); - } else { + this.customHover = this._store.add(setupCustomHover(hoverDelegate, this.element, title)); + } else if (this.customHover) { this.customHover.update(title); } } diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 5f5ad352842..05505b768a5 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -6,8 +6,8 @@ import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { ActionRunner, IAction, IActionRunner, IRunEvent, Separator } from 'vs/base/common/actions'; import { Emitter } from 'vs/base/common/event'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -118,7 +118,7 @@ export class ActionBar extends Disposable implements IActionRunner { keys: this.options.triggerKeys?.keys ?? [KeyCode.Enter, KeyCode.Space] }; - this._hoverDelegate = options.hoverDelegate ?? this._register(getDefaultHoverDelegate('element', true)); + this._hoverDelegate = options.hoverDelegate ?? this._register(createInstantHoverDelegate()); if (this.options.actionRunner) { this._actionRunner = this.options.actionRunner; diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index e7ca41dc8b3..1d2a3364d0b 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -9,9 +9,9 @@ import { sanitize } from 'vs/base/browser/dompurify/dompurify'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { renderMarkdown, renderStringAsPlaintext } from 'vs/base/browser/markdownRenderer'; import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { Action, IAction, IActionRunner } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; @@ -303,9 +303,9 @@ export class Button extends Disposable implements IButton { } private setTitle(title: string) { - if (!this._hover) { + if (!this._hover && title !== '') { this._hover = this._register(setupCustomHover(this.options.hoverDelegate ?? getDefaultHoverDelegate('mouse'), this._element, title)); - } else { + } else if (this._hover) { this._hover.update(title); } } diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index 88dfaf2c5b1..dfc2329510f 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -8,8 +8,8 @@ import { $, addDisposableListener, append, EventHelper, EventType, isMouseEvent import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventType as GestureEventType, Gesture } from 'vs/base/browser/touch'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { IMenuOptions } from 'vs/base/browser/ui/menu/menu'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { Emitter } from 'vs/base/common/event'; @@ -105,9 +105,9 @@ class BaseDropdown extends ActionRunner { set tooltip(tooltip: string) { if (this._label) { - if (!this.hover) { + if (!this.hover && tooltip !== '') { this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this._label, tooltip)); - } else { + } else if (this.hover) { this.hover.update(tooltip); } } diff --git a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index 995b8f40817..75333985372 100644 --- a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -19,8 +19,8 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { IDisposable } from 'vs/base/common/lifecycle'; import 'vs/css!./dropdown'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export interface IKeybindingProvider { (action: IAction): ResolvedKeybinding | undefined; diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index c119ad43779..9ecfcbce827 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -16,7 +16,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import 'vs/css!./findInput'; import * as nls from 'vs/nls'; import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export interface IFindInputOptions { @@ -114,7 +114,7 @@ export class FindInput extends Widget { inputBoxStyles: options.inputBoxStyles, })); - const hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + const hoverDelegate = this._register(createInstantHoverDelegate()); if (this.showCommonFindToggles) { this.regex = this._register(new RegexToggle({ diff --git a/src/vs/base/browser/ui/findinput/findInputToggles.ts b/src/vs/base/browser/ui/findinput/findInputToggles.ts index 8b3ea12580f..adce009430b 100644 --- a/src/vs/base/browser/ui/findinput/findInputToggles.ts +++ b/src/vs/base/browser/ui/findinput/findInputToggles.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Codicon } from 'vs/base/common/codicons'; import * as nls from 'vs/nls'; diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts index 8e20025d757..4dfdf549a3b 100644 --- a/src/vs/base/browser/ui/findinput/replaceInput.ts +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -16,7 +16,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import 'vs/css!./findInput'; import * as nls from 'vs/nls'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export interface IReplaceInputOptions { diff --git a/src/vs/base/browser/ui/hover/hoverDelegate.ts b/src/vs/base/browser/ui/hover/hoverDelegate.ts index 6d2dfef371a..57f6962ac0e 100644 --- a/src/vs/base/browser/ui/hover/hoverDelegate.ts +++ b/src/vs/base/browser/ui/hover/hoverDelegate.ts @@ -3,33 +3,66 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IHoverDelegate, IScopedHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { Lazy } from 'vs/base/common/lazy'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; +import { IHoverWidget, IUpdatableHoverOptions } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { IDisposable } from 'vs/base/common/lifecycle'; -const nullHoverDelegateFactory = () => ({ - get delay(): number { return -1; }, - dispose: () => { }, - showHover: () => { return undefined; }, -}); - -let hoverDelegateFactory: (placement: 'mouse' | 'element', enableInstantHover: boolean) => IScopedHoverDelegate = nullHoverDelegateFactory; -const defaultHoverDelegateMouse = new Lazy(() => hoverDelegateFactory('mouse', false)); -const defaultHoverDelegateElement = new Lazy(() => hoverDelegateFactory('element', false)); - -export function setHoverDelegateFactory(hoverDelegateProvider: ((placement: 'mouse' | 'element', enableInstantHover: boolean) => IScopedHoverDelegate)): void { - hoverDelegateFactory = hoverDelegateProvider; +export interface IHoverDelegateTarget extends IDisposable { + readonly targetElements: readonly HTMLElement[]; + x?: number; } -export function getDefaultHoverDelegate(placement: 'mouse' | 'element'): IHoverDelegate; -export function getDefaultHoverDelegate(placement: 'element', enableInstantHover: true): IScopedHoverDelegate; -export function getDefaultHoverDelegate(placement: 'mouse' | 'element', enableInstantHover?: boolean): IHoverDelegate | IScopedHoverDelegate { - if (enableInstantHover) { - // If instant hover is enabled, the consumer is responsible for disposing the hover delegate - return hoverDelegateFactory(placement, true); - } - - if (placement === 'element') { - return defaultHoverDelegateElement.value; - } - return defaultHoverDelegateMouse.value; +export interface IHoverDelegateOptions extends IUpdatableHoverOptions { + /** + * The content to display in the primary section of the hover. The type of text determines the + * default `hideOnHover` behavior. + */ + content: IMarkdownString | string | HTMLElement; + /** + * The target for the hover. This determines the position of the hover and it will only be + * hidden when the mouse leaves both the hover and the target. A HTMLElement can be used for + * simple cases and a IHoverDelegateTarget for more complex cases where multiple elements and/or a + * dispose method is required. + */ + target: IHoverDelegateTarget | HTMLElement; + /** + * The container to pass to {@link IContextViewProvider.showContextView} which renders the hover + * in. This is particularly useful for more natural tab focusing behavior, where the hover is + * created as the next tab index after the element being hovered and/or to workaround the + * element's container hiding on `focusout`. + */ + container?: HTMLElement; + /** + * Options that defines where the hover is positioned. + */ + position?: { + /** + * Position of the hover. The default is to show above the target. This option will be ignored + * if there is not enough room to layout the hover in the specified position, unless the + * forcePosition option is set. + */ + hoverPosition?: HoverPosition; + }; + appearance?: { + /** + * Whether to show the hover pointer + */ + showPointer?: boolean; + /** + * Whether to skip the fade in animation, this should be used when hovering from one hover to + * another in the same group so it looks like the hover is moving from one element to the other. + */ + skipFadeInAnimation?: boolean; + }; } + +export interface IHoverDelegate { + showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined; + onDidHideHover?: () => void; + delay: number; + placement?: 'mouse' | 'element'; + showNativeHover?: boolean; // TODO@benibenj remove this, only temp fix for contextviews +} + +export interface IScopedHoverDelegate extends IHoverDelegate, IDisposable { } diff --git a/src/vs/base/browser/ui/hover/hoverDelegateFactory.ts b/src/vs/base/browser/ui/hover/hoverDelegateFactory.ts new file mode 100644 index 00000000000..44628261333 --- /dev/null +++ b/src/vs/base/browser/ui/hover/hoverDelegateFactory.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IHoverDelegate, IScopedHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { Lazy } from 'vs/base/common/lazy'; + +const nullHoverDelegateFactory = () => ({ + get delay(): number { return -1; }, + dispose: () => { }, + showHover: () => { return undefined; }, +}); + +let hoverDelegateFactory: (placement: 'mouse' | 'element', enableInstantHover: boolean) => IScopedHoverDelegate = nullHoverDelegateFactory; +const defaultHoverDelegateMouse = new Lazy(() => hoverDelegateFactory('mouse', false)); +const defaultHoverDelegateElement = new Lazy(() => hoverDelegateFactory('element', false)); + +export function setHoverDelegateFactory(hoverDelegateProvider: ((placement: 'mouse' | 'element', enableInstantHover: boolean) => IScopedHoverDelegate)): void { + hoverDelegateFactory = hoverDelegateProvider; +} + +export function getDefaultHoverDelegate(placement: 'mouse' | 'element'): IHoverDelegate { + if (placement === 'element') { + return defaultHoverDelegateElement.value; + } + return defaultHoverDelegateMouse.value; +} + +export function createInstantHoverDelegate(): IScopedHoverDelegate { + // Creates a hover delegate with instant hover enabled. + // This hover belongs to the consumer and requires the them to dispose it. + // Instant hover only makes sense for 'element' placement. + return hoverDelegateFactory('element', true); +} diff --git a/src/vs/base/browser/ui/hover/hover.css b/src/vs/base/browser/ui/hover/hoverWidget.css similarity index 100% rename from src/vs/base/browser/ui/hover/hover.css rename to src/vs/base/browser/ui/hover/hoverWidget.css diff --git a/src/vs/base/browser/ui/hover/hoverWidget.ts b/src/vs/base/browser/ui/hover/hoverWidget.ts index dc0af66ff5a..bff397303be 100644 --- a/src/vs/base/browser/ui/hover/hoverWidget.ts +++ b/src/vs/base/browser/ui/hover/hoverWidget.ts @@ -8,7 +8,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; -import 'vs/css!./hover'; +import 'vs/css!./hoverWidget'; import { localize } from 'vs/nls'; const $ = dom.$; diff --git a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts b/src/vs/base/browser/ui/hover/updatableHoverWidget.ts similarity index 98% rename from src/vs/base/browser/ui/iconLabel/iconLabelHover.ts rename to src/vs/base/browser/ui/hover/updatableHoverWidget.ts index bdcdfa7c7da..c3c505cef39 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts +++ b/src/vs/base/browser/ui/hover/updatableHoverWidget.ts @@ -5,7 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; -import { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from 'vs/base/browser/ui/hover/hoverDelegate'; import { TimeoutTimer } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IMarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; @@ -68,6 +68,9 @@ export interface ICustomHover extends IDisposable { update(tooltip: IHoverContent, options?: IUpdatableHoverOptions): void; } +export interface IHoverWidget extends IDisposable { + readonly isDisposed: boolean; +} class UpdatableHoverWidget implements IDisposable { diff --git a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts deleted file mode 100644 index f0209856dc3..00000000000 --- a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts +++ /dev/null @@ -1,72 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; -import { IUpdatableHoverOptions } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { IDisposable } from 'vs/base/common/lifecycle'; - -export interface IHoverDelegateTarget extends IDisposable { - readonly targetElements: readonly HTMLElement[]; - x?: number; -} - -export interface IHoverDelegateOptions extends IUpdatableHoverOptions { - /** - * The content to display in the primary section of the hover. The type of text determines the - * default `hideOnHover` behavior. - */ - content: IMarkdownString | string | HTMLElement; - /** - * The target for the hover. This determines the position of the hover and it will only be - * hidden when the mouse leaves both the hover and the target. A HTMLElement can be used for - * simple cases and a IHoverDelegateTarget for more complex cases where multiple elements and/or a - * dispose method is required. - */ - target: IHoverDelegateTarget | HTMLElement; - /** - * The container to pass to {@link IContextViewProvider.showContextView} which renders the hover - * in. This is particularly useful for more natural tab focusing behavior, where the hover is - * created as the next tab index after the element being hovered and/or to workaround the - * element's container hiding on `focusout`. - */ - container?: HTMLElement; - /** - * Options that defines where the hover is positioned. - */ - position?: { - /** - * Position of the hover. The default is to show above the target. This option will be ignored - * if there is not enough room to layout the hover in the specified position, unless the - * forcePosition option is set. - */ - hoverPosition?: HoverPosition; - }; - appearance?: { - /** - * Whether to show the hover pointer - */ - showPointer?: boolean; - /** - * Whether to skip the fade in animation, this should be used when hovering from one hover to - * another in the same group so it looks like the hover is moving from one element to the other. - */ - skipFadeInAnimation?: boolean; - }; -} - -export interface IHoverDelegate { - showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined; - onDidHideHover?: () => void; - delay: number; - placement?: 'mouse' | 'element'; - showNativeHover?: boolean; // TODO@benibenj remove this, only temp fix for contextviews -} - -export interface IScopedHoverDelegate extends IHoverDelegate, IDisposable { } - -export interface IHoverWidget extends IDisposable { - readonly isDisposed: boolean; -} diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index c9d62a9bc4e..5bf35b8ffc2 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -6,13 +6,13 @@ import 'vs/css!./iconlabel'; import * as dom from 'vs/base/browser/dom'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { ITooltipMarkdownString, setupCustomHover, setupNativeHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ITooltipMarkdownString, setupCustomHover, setupNativeHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { IMatch } from 'vs/base/common/filters'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { equals } from 'vs/base/common/objects'; import { Range } from 'vs/base/common/range'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export interface IIconLabelCreationOptions { readonly supportHighlights?: boolean; diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index ca6aaa33505..c813532f496 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -9,8 +9,8 @@ import { IContentActionHandler } from 'vs/base/browser/formattedTextRenderer'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { AnchorPosition, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { IListEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { ISelectBoxDelegate, ISelectBoxOptions, ISelectBoxStyles, ISelectData, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; @@ -103,7 +103,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi private selectionDetailsPane!: HTMLElement; private _skipLayout: boolean = false; private _cachedMaxDetailsHeight?: number; - private _hover: ICustomHover; + private _hover?: ICustomHover; private _sticky: boolean = false; // for dev purposes only @@ -134,8 +134,6 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.selectElement.setAttribute('aria-description', this.selectBoxOptions.ariaDescription); } - this._hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.selectElement, '')); - this._onDidSelect = new Emitter(); this._register(this._onDidSelect); @@ -152,6 +150,14 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi } + private setTitle(title: string): void { + if (!this._hover && title) { + this._hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.selectElement, title)); + } else if (this._hover) { + this._hover.update(title); + } + } + // IDelegate - List renderer getHeight(): number { @@ -204,7 +210,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi selected: e.target.value }); if (!!this.options[this.selected] && !!this.options[this.selected].text) { - this._hover.update(this.options[this.selected].text); + this.setTitle(this.options[this.selected].text); } })); @@ -314,7 +320,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.selectElement.selectedIndex = this.selected; if (!!this.options[this.selected] && !!this.options[this.selected].text) { - this._hover.update(this.options[this.selected].text); + this.setTitle(this.options[this.selected].text); } } @@ -842,7 +848,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi }); if (!!this.options[this.selected] && !!this.options[this.selected].text) { - this._hover.update(this.options[this.selected].text); + this.setTitle(this.options[this.selected].text); } } @@ -941,7 +947,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi selected: this.options[this.selected].text }); if (!!this.options[this.selected] && !!this.options[this.selected].text) { - this._hover.update(this.options[this.selected].text); + this.setTitle(this.options[this.selected].text); } } diff --git a/src/vs/base/browser/ui/table/tableWidget.ts b/src/vs/base/browser/ui/table/tableWidget.ts index 536fb25608e..38192d39413 100644 --- a/src/vs/base/browser/ui/table/tableWidget.ts +++ b/src/vs/base/browser/ui/table/tableWidget.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { $, append, clearNode, createStyleSheet, getContentHeight, getContentWidth } from 'vs/base/browser/dom'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListOptions, IListOptionsUpdate, IListStyles, List, unthemedListStyles } from 'vs/base/browser/ui/list/listWidget'; import { ISplitViewDescriptor, IView, Orientation, SplitView } from 'vs/base/browser/ui/splitview/splitview'; @@ -132,7 +132,10 @@ class ColumnHeader extends Disposable implements IView { super(); this.element = $('.monaco-table-th', { 'data-col-index': index }, column.label); - this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.element, column.tooltip)); + + if (column.tooltip) { + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.element, column.tooltip)); + } } layout(size: number): void { diff --git a/src/vs/base/browser/ui/toggle/toggle.ts b/src/vs/base/browser/ui/toggle/toggle.ts index 540bb17db2f..53e0e2b9a30 100644 --- a/src/vs/base/browser/ui/toggle/toggle.ts +++ b/src/vs/base/browser/ui/toggle/toggle.ts @@ -13,9 +13,9 @@ import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import 'vs/css!./toggle'; import { isActiveElement, $, addDisposableListener, EventType } from 'vs/base/browser/dom'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export interface IToggleOpts extends IToggleStyles { readonly actionClassName?: string; diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index f92369cddfb..ebb6bc264d1 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -15,8 +15,8 @@ import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./toolbar'; import * as nls from 'vs/nls'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; @@ -60,7 +60,7 @@ export class ToolBar extends Disposable { constructor(container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) { super(); - options.hoverDelegate = options.hoverDelegate ?? this._register(getDefaultHoverDelegate('element', true)); + options.hoverDelegate = options.hoverDelegate ?? this._register(createInstantHoverDelegate()); this.options = options; this.lookupKeybindings = typeof this.options.getKeyBinding === 'function'; diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index c42cd0ba336..87e8f52fbc6 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -33,8 +33,8 @@ import { ISpliceable } from 'vs/base/common/sequence'; import { isNumber } from 'vs/base/common/types'; import 'vs/css!./media/tree'; import { localize } from 'vs/nls'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { createInstantHoverDelegate, getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; class TreeElementsDragAndDropData extends ElementsDragAndDropData { @@ -807,7 +807,7 @@ class FindWidget extends Disposable { this.elements.root.style.boxShadow = `0 0 8px 2px ${styles.listFilterWidgetShadow}`; } - const toggleHoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + const toggleHoverDelegate = this._register(createInstantHoverDelegate()); this.modeToggle = this._register(new ModeToggle({ ...styles.toggleStyles, isChecked: mode === TreeFindMode.Filter, hoverDelegate: toggleHoverDelegate })); this.matchTypeToggle = this._register(new FuzzyToggle({ ...styles.toggleStyles, isChecked: matchType === TreeFindMatchType.Fuzzy, hoverDelegate: toggleHoverDelegate })); this.onDidChangeMode = Event.map(this.modeToggle.onChange, () => this.modeToggle.checked ? TreeFindMode.Filter : TreeFindMode.Highlight, this._store); diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 47fddf11c86..f3d6b5c9f16 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -19,7 +19,7 @@ import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { mainWindow } from 'vs/base/browser/window'; -import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverWidget } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { ContextViewHandler } from 'vs/platform/contextview/browser/contextViewService'; export class HoverService extends Disposable implements IHoverService { diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index 8b4814a92c9..4ec59ed09bd 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -23,7 +23,7 @@ import { localize } from 'vs/nls'; import { isMacintosh } from 'vs/base/common/platform'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { status } from 'vs/base/browser/ui/aria/aria'; -import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverWidget } from 'vs/base/browser/ui/hover/updatableHoverWidget'; const $ = dom.$; type TargetRect = { diff --git a/src/vs/editor/contrib/find/browser/findOptionsWidget.ts b/src/vs/editor/contrib/find/browser/findOptionsWidget.ts index 7b693a1f28c..007723f6986 100644 --- a/src/vs/editor/contrib/find/browser/findOptionsWidget.ts +++ b/src/vs/editor/contrib/find/browser/findOptionsWidget.ts @@ -13,7 +13,7 @@ import { FIND_IDS } from 'vs/editor/contrib/find/browser/findModel'; import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { asCssVariable, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export class FindOptionsWidget extends Widget implements IOverlayWidget { @@ -53,7 +53,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground), }; - const hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + const hoverDelegate = this._register(createInstantHoverDelegate()); this.caseSensitive = this._register(new CaseSensitiveToggle({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand), diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index 76189e64741..424375e8cae 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -43,9 +43,9 @@ import { isHighContrast } from 'vs/platform/theme/common/theme'; import { assertIsDefined } from 'vs/base/common/types'; import { defaultInputBoxStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Selection } from 'vs/editor/common/core/selection'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { createInstantHoverDelegate, getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; const findSelectionIcon = registerIcon('find-selection', Codicon.selection, nls.localize('findSelectionIcon', 'Icon for \'Find in Selection\' in the editor find widget.')); const findCollapsedIcon = registerIcon('find-collapsed', Codicon.chevronRight, nls.localize('findCollapsedIcon', 'Icon to indicate that the editor find widget is collapsed.')); @@ -1014,7 +1014,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._updateMatchesCount(); // Create a scoped hover delegate for all find related buttons - const hoverDelegate = getDefaultHoverDelegate('element', true); + const hoverDelegate = this._register(createInstantHoverDelegate()); // Previous button this._prevBtn = this._register(new SimpleButton({ @@ -1148,7 +1148,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL })); // Create scoped hover delegate for replace actions - const replaceHoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + const replaceHoverDelegate = this._register(createInstantHoverDelegate()); // Replace one button this._replaceBtn = this._register(new SimpleButton({ diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 3e903d5bab9..05305694e57 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -39,7 +39,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { mainWindow } from 'vs/base/browser/window'; -import { setHoverDelegateFactory } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setHoverDelegateFactory } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover'; /** diff --git a/src/vs/platform/actions/browser/buttonbar.ts b/src/vs/platform/actions/browser/buttonbar.ts index 94720d5b557..79cbe6faa37 100644 --- a/src/vs/platform/actions/browser/buttonbar.ts +++ b/src/vs/platform/actions/browser/buttonbar.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { ButtonBar, IButton } from 'vs/base/browser/ui/button/button'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { ActionRunner, IAction, IActionRunner, SubmenuAction, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -73,7 +73,7 @@ export class WorkbenchButtonBar extends ButtonBar { this.clear(); // Support instamt hover between buttons - const hoverDelegate = this._updateStore.add(getDefaultHoverDelegate('element', true)); + const hoverDelegate = this._updateStore.add(createInstantHoverDelegate()); for (let i = 0; i < actions.length; i++) { diff --git a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts index cfcc0e2c73b..97aac13854a 100644 --- a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts +++ b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts @@ -19,7 +19,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export interface IDropdownWithPrimaryActionViewItemOptions { actionRunner?: IActionRunner; diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index c1edd287bea..da596a8fc6c 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -26,7 +26,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ThemeIcon } from 'vs/base/common/themables'; import { isDark } from 'vs/platform/theme/common/theme'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { assertType } from 'vs/base/common/types'; import { asCssVariable, selectBorder } from 'vs/platform/theme/common/colorRegistry'; import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 0a593c04ec8..3a44ead957b 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -7,10 +7,11 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; -import { IHoverDelegate, IHoverDelegateOptions, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/hover/hoverDelegate'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { addStandardDisposableListener } from 'vs/base/browser/dom'; import { KeyCode } from 'vs/base/common/keyCodes'; +import { IHoverWidget } from 'vs/base/browser/ui/hover/updatableHoverWidget'; export const IHoverService = createDecorator('hoverService'); diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 96dfeee7ea8..73c3a23a305 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -8,7 +8,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button, IButtonStyles } from 'vs/base/browser/ui/button/button'; import { CountBadge, ICountBadgeStyles } from 'vs/base/browser/ui/countBadge/countBadge'; -import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/hover/hoverDelegate'; import { IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; import { IKeybindingLabelStyles } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; diff --git a/src/vs/platform/quickinput/browser/quickInputList.ts b/src/vs/platform/quickinput/browser/quickInputList.ts index de219a800bb..26e32890647 100644 --- a/src/vs/platform/quickinput/browser/quickInputList.ts +++ b/src/vs/platform/quickinput/browser/quickInputList.ts @@ -8,7 +8,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { AriaRole } from 'vs/base/browser/ui/aria/aria'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; -import { IHoverDelegate, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; @@ -35,7 +35,7 @@ import { Lazy } from 'vs/base/common/lazy'; import { URI } from 'vs/base/common/uri'; import { isDark } from 'vs/platform/theme/common/theme'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ITooltipMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { IHoverWidget, ITooltipMarkdownString } from 'vs/base/browser/ui/hover/updatableHoverWidget'; const $ = dom.$; diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index cfa2d348aa0..6ae11c51361 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -26,7 +26,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { URI } from 'vs/base/common/uri'; import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverWidget } from 'vs/base/browser/ui/hover/updatableHoverWidget'; export interface ICompositeBar { diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 81a034ffa91..f4a0bb4fe28 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -33,9 +33,9 @@ import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { createInstantHoverDelegate, getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; export interface ICompositeTitleLabel { @@ -97,7 +97,7 @@ export abstract class CompositePart extends Part { super(id, options, themeService, storageService, layoutService); this.lastActiveCompositeId = storageService.get(activeCompositeSettingsKey, StorageScope.WORKSPACE, this.defaultCompositeId); - this.toolbarHoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + this.toolbarHoverDelegate = this._register(createInstantHoverDelegate()); } protected openComposite(id: string, focus?: boolean): Composite | undefined { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index bf46167248f..00e7153c46f 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -40,8 +40,8 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { Codicon } from 'vs/base/common/codicons'; import { defaultBreadcrumbsWidgetStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Emitter } from 'vs/base/common/event'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; class OutlineItem extends BreadcrumbsItem { diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index 218c8808205..d7a82ed5c16 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -44,8 +44,8 @@ import { IAuxiliaryEditorPart, MergeGroupMode } from 'vs/workbench/services/edit import { isMacintosh } from 'vs/base/common/platform'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class EditorCommandsContextActionRunner extends ActionRunner { diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index e66dc53ad61..246532ddd02 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -29,8 +29,8 @@ import { Event } from 'vs/base/common/event'; import { defaultButtonStyles, defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export class NotificationsListDelegate implements IListVirtualDelegate { diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts index e18622523c5..13b5fde792b 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts @@ -21,9 +21,9 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { renderIcon, renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { spinningLoading, syncing } from 'vs/platform/theme/common/iconRegistry'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { isMarkdownString, markdownStringEqual } from 'vs/base/common/htmlContent'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch'; export class StatusbarEntryItem extends Disposable { diff --git a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts index 72eeea0b590..54eded7aab3 100644 --- a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts @@ -5,9 +5,9 @@ import { isActiveDocument, reset } from 'vs/base/browser/dom'; import { BaseActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IAction, SubmenuAction } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 176ac265172..548f4d36caf 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -52,9 +52,9 @@ import { IEditorCommandsContext, IEditorPartOptionsChangeEvent, IToolbarActions import { mainWindow } from 'vs/base/browser/window'; import { ACCOUNTS_ACTIVITY_TILE_ACTION, GLOBAL_ACTIVITY_TITLE_ACTION } from 'vs/workbench/browser/parts/titlebar/titlebarActions'; import { IView } from 'vs/base/browser/ui/grid/grid'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export interface ITitleVariable { readonly name: string; @@ -282,7 +282,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.windowTitle = this._register(instantiationService.createInstance(WindowTitle, targetWindow, editorGroupsContainer)); - this.hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + this.hoverDelegate = this._register(createInstantHoverDelegate()); this.registerListeners(getWindowId(targetWindow)); } diff --git a/src/vs/workbench/browser/parts/views/checkbox.ts b/src/vs/workbench/browser/parts/views/checkbox.ts index 849966bbe33..d79f2ac7930 100644 --- a/src/vs/workbench/browser/parts/views/checkbox.ts +++ b/src/vs/workbench/browser/parts/views/checkbox.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 3b40a248a1d..2af2a8be2e5 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -8,8 +8,8 @@ import * as DOM from 'vs/base/browser/dom'; import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { ITooltipMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ITooltipMarkdownString } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ElementsDragAndDropData, ListViewTargetSector } from 'vs/base/browser/ui/list/listView'; import { IAsyncDataSource, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, ITreeNode, ITreeRenderer, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree'; diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index 9cc1b35c354..06b591f477a 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -47,8 +47,8 @@ import { FilterWidget, IFilterWidgetOptions } from 'vs/workbench/browser/parts/v import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { defaultButtonStyles, defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export enum ViewPaneShowActions { /** Show the actions when the view is hovered. This is the default behavior. */ diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 8a311d4bb0e..0315e95a2d4 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -44,7 +44,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { mainWindow } from 'vs/base/browser/window'; import { PixelRatio } from 'vs/base/browser/pixelRatio'; import { WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover'; -import { setHoverDelegateFactory } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setHoverDelegateFactory } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export interface IWorkbenchOptions { diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts index 4d2a9e4b887..55ea0d4e8f9 100644 --- a/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -30,8 +30,8 @@ import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/comme import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { LayoutableEditor, MIN_EDITOR_HEIGHT, SimpleCommentEditor, calculateEditorHeight } from './simpleCommentEditor'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; const COMMENT_SCHEME = 'comment'; let INMEM_MODEL_ID = 0; diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 585d253442c..451bc210789 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -32,8 +32,8 @@ import { IStyleOverride } from 'vs/platform/theme/browser/defaultStyles'; import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { CommentsModel } from 'vs/workbench/contrib/comments/browser/commentsModel'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export const COMMENTS_VIEW_ID = 'workbench.panel.comments'; export const COMMENTS_VIEW_STORAGE_ID = 'Comments'; diff --git a/src/vs/workbench/contrib/comments/browser/timestamp.ts b/src/vs/workbench/contrib/comments/browser/timestamp.ts index 2d1fcf15b48..16b3969d2c3 100644 --- a/src/vs/workbench/contrib/comments/browser/timestamp.ts +++ b/src/vs/workbench/contrib/comments/browser/timestamp.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { fromNow } from 'vs/base/common/date'; import { Disposable } from 'vs/base/common/lifecycle'; import { language } from 'vs/base/common/platform'; diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 90aba19a7f2..5a365a2cb9f 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -7,8 +7,8 @@ import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { IAsyncDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Codicon } from 'vs/base/common/codicons'; diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 5de52259df4..13b72018035 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -8,9 +8,9 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Gesture } from 'vs/base/browser/touch'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { AriaRole } from 'vs/base/browser/ui/aria/aria'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { IListContextMenuEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 43b69137af3..139928ec9fe 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -48,8 +48,8 @@ import { createDisconnectMenuItemAction } from 'vs/workbench/contrib/debug/brows import { CALLSTACK_VIEW_ID, CONTEXT_CALLSTACK_ITEM_STOPPED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD, CONTEXT_CALLSTACK_SESSION_IS_ATTACH, CONTEXT_DEBUG_STATE, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, getStateLabel, IDebugModel, IDebugService, IDebugSession, IRawStoppedDetails, IStackFrame, IThread, State } from 'vs/workbench/contrib/debug/common/debug'; import { StackFrame, Thread, ThreadAndSessionIds } from 'vs/workbench/contrib/debug/common/debugModel'; import { isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; const $ = dom.$; @@ -556,9 +556,9 @@ class SessionsRenderer implements ICompressibleTreeRenderer { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 084e20c5898..04d8fe1ff4f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -66,7 +66,7 @@ import { ExpansionState, HunkData, HunkInformation, Session } from 'vs/workbench import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_VISIBLE, IInlineChatFollowup, IInlineChatSlashCommand, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_FEEDBACK, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -375,7 +375,7 @@ export class InlineChatWidget { this._store.add(this._slashCommandContentWidget); // Share hover delegates between toolbars to support instant hover between both - const hoverDelegate = this._store.add(getDefaultHoverDelegate('element', true)); + const hoverDelegate = this._store.add(createInstantHoverDelegate()); // toolbars diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts index 35007bedeb8..bcfd1900226 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts @@ -13,8 +13,8 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ThemeIcon } from 'vs/base/common/themables'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export class CodiconActionViewItem extends MenuEntryActionViewItem { diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts index 52e87ad8086..b5c306d0b41 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts @@ -27,8 +27,8 @@ import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cell import { ClickTargetType, IClickTarget } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellWidgets'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { CellStatusbarAlignment, INotebookCellStatusBarItem } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { ITooltipMarkdownString, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { ITooltipMarkdownString, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/hover/hoverDelegate'; import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts index f10530616f1..250cb9824ec 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts @@ -22,8 +22,8 @@ import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/vie import { CellOverlayPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; import { registerCellToolbarStickyScroll } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbarStickyScroll'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export class BetweenCellToolbar extends CellOverlayPart { private _betweenCellToolbar: ToolBar | undefined; @@ -167,7 +167,7 @@ export class CellTitleToolbarPart extends CellOverlayPart { if (this._view) { return this._view; } - const hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + const hoverDelegate = this._register(createInstantHoverDelegate()); const toolbar = this._register(this.instantiationService.createInstance(WorkbenchToolBar, this.toolbarContainer, { actionViewItemProvider: (action, options) => { return createActionViewItem(this.instantiationService, action, options); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index 2f32b9c5932..cf98a39a26e 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -22,7 +22,7 @@ import { SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/bro import { POLICY_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IHoverOptions, IHoverService } from 'vs/platform/hover/browser/hover'; -import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverWidget } from 'vs/base/browser/ui/hover/updatableHoverWidget'; const $ = DOM.$; diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 35ba3ee8c5d..49a702fbbfe 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -51,12 +51,12 @@ import { WorkbenchTable } from 'vs/platform/list/browser/listService'; import { Button } from 'vs/base/browser/ui/button/button'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { STATUS_BAR_REMOTE_ITEM_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, forwardedPortsViewEnabled, makeAddress, mapHasAddressLocalhostOrAllInterfaces, parseAddress } from 'vs/workbench/services/remote/common/tunnelModel'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export const openPreviewEnabledContext = new RawContextKey('openPreviewEnabled', false); diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index c7dfc7eae60..d12c8269212 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -19,7 +19,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export interface IOptions { placeholder?: string; diff --git a/src/vs/workbench/contrib/search/browser/searchResultsView.ts b/src/vs/workbench/contrib/search/browser/searchResultsView.ts index 38a4f536403..e22115c2a8f 100644 --- a/src/vs/workbench/contrib/search/browser/searchResultsView.ts +++ b/src/vs/workbench/contrib/search/browser/searchResultsView.ts @@ -30,8 +30,8 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; import { SearchContext } from 'vs/workbench/contrib/search/common/constants'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; interface IFolderMatchTemplate { label: IResourceLabel; diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 4a0a156fcf6..6ea0cb4542e 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -81,8 +81,8 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { ILogService } from 'vs/platform/log/common/log'; import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; const $ = dom.$; diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 731653a4828..332c290246a 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -42,7 +42,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { GroupModelChangeKind } from 'vs/workbench/common/editor'; import { SearchFindInput } from 'vs/workbench/contrib/search/browser/searchFindInput'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; /** Specified in searchview.css */ const SingleLineInputHeight = 26; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 02b833d6737..449b0d60787 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -62,8 +62,8 @@ import { UnusualLineTerminatorsDetector } from 'vs/editor/contrib/unusualLineTer import { defaultToggleStyles, getInputBoxStyle } from 'vs/platform/theme/browser/defaultStyles'; import { ILogService } from 'vs/platform/log/common/log'; import { SearchContext } from 'vs/workbench/contrib/search/common/constants'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; const RESULT_LINE_REGEX = /^(\s+)(\d+)(: | )(\s*)(.*)$/; const FILE_LINE_REGEX = /^(\S.*):$/; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index 31ed968488e..7bf4f427bd0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -44,7 +44,7 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Event } from 'vs/base/common/event'; -import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/hover/hoverDelegate'; import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { InstanceContext, TerminalContextActionRunner } from 'vs/workbench/contrib/terminal/browser/terminalContextMenu'; diff --git a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts index 7261e5e1021..56d486699a6 100644 --- a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts +++ b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { h } from 'vs/base/browser/dom'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { ICustomHover, ITooltipMarkdownString, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { ICustomHover, ITooltipMarkdownString, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { assertNever } from 'vs/base/common/assert'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Lazy } from 'vs/base/common/lazy'; diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index 8eb58e118a3..e5e8e34abfa 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -8,8 +8,8 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { ActionBar, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button } from 'vs/base/browser/ui/button/button'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { DefaultKeyboardNavigationDelegate, IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 06476f564c8..87503bb990c 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -49,13 +49,13 @@ import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from 'vs/ import { MarshalledId } from 'vs/base/common/marshallingIds'; import { isString } from 'vs/base/common/types'; import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { AriaRole } from 'vs/base/browser/ui/aria/aria'; import { ILocalizedString } from 'vs/platform/action/common/action'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; const ItemHeight = 22; diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index 45ea476ec9a..987d9f119b9 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -77,7 +77,7 @@ import { IHoverService } from 'vs/platform/hover/browser/hover'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { DEFAULT_ICON, ICONS } from 'vs/workbench/services/userDataProfile/common/userDataProfileIcons'; import { WorkbenchIconSelectBox } from 'vs/workbench/services/userDataProfile/browser/iconSelectBox'; -import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverWidget } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; From 0bdc2f09bb775f72e72f5ca178a9f918e9dc575a Mon Sep 17 00:00:00 2001 From: Mahmoud Salah Date: Mon, 26 Feb 2024 18:09:44 +0200 Subject: [PATCH 1601/1897] =?UTF-8?q?for=20diff=20editors,=20resolve=20the?= =?UTF-8?q?=20modified=20editor=20to=20allow=20run=20tests=20in=20c?= =?UTF-8?q?=E2=80=A6=20(#206026)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * for diff editors, resolve the modified editor to allow run tests in current file or run test at cursor to work * Check for active code editor when finding tests. * Handle compilation issues with possible null. * update import path. * Missing null check. * Remove cast. --------- Co-authored-by: Mahmoud Khalil --- .../testing/browser/testExplorerActions.ts | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index f6f451bcd0d..103efb04a9c 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -9,7 +9,8 @@ import { Iterable } from 'vs/base/common/iterator'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { isDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -981,15 +982,20 @@ abstract class ExecuteTestAtCursor extends Action2 { * @override */ public async run(accessor: ServicesAccessor) { + const codeEditorService = accessor.get(ICodeEditorService); const editorService = accessor.get(IEditorService); const activeEditorPane = editorService.activeEditorPane; - const activeControl = editorService.activeTextEditorControl; - if (!activeEditorPane || !activeControl) { + let editor = codeEditorService.getActiveCodeEditor(); + if (!activeEditorPane || !editor) { return; } - const position = activeControl?.getPosition(); - const model = activeControl?.getModel(); + if (editor instanceof EmbeddedCodeEditorWidget) { + editor = editor.getParentEditor(); + } + + const position = editor?.getPosition(); + const model = editor?.getModel(); if (!position || !model || !('uri' in model)) { return; } @@ -1053,8 +1059,8 @@ abstract class ExecuteTestAtCursor extends Action2 { group: this.group, tests: bestNodes.length ? bestNodes : bestNodesBefore, }); - } else if (isCodeEditor(activeControl)) { - MessageController.get(activeControl)?.showMessage(localize('noTestsAtCursor', "No tests found here"), position); + } else if (editor) { + MessageController.get(editor)?.showMessage(localize('noTestsAtCursor', "No tests found here"), position); } } } @@ -1186,9 +1192,15 @@ abstract class ExecuteTestsInCurrentFile extends Action2 { * @override */ public run(accessor: ServicesAccessor) { - const control = accessor.get(IEditorService).activeTextEditorControl; - const position = control?.getPosition(); - const model = control?.getModel(); + let editor = accessor.get(ICodeEditorService).getActiveCodeEditor(); + if (!editor) { + return; + } + if (editor instanceof EmbeddedCodeEditorWidget) { + editor = editor.getParentEditor(); + } + const position = editor?.getPosition(); + const model = editor?.getModel(); if (!position || !model || !('uri' in model)) { return; } @@ -1218,8 +1230,8 @@ abstract class ExecuteTestsInCurrentFile extends Action2 { }); } - if (isCodeEditor(control)) { - MessageController.get(control)?.showMessage(localize('noTestsInFile', "No tests found in this file"), position); + if (editor) { + MessageController.get(editor)?.showMessage(localize('noTestsInFile', "No tests found in this file"), position); } return undefined; From 2ba3fae68d15d0a83a00cc62e2d84962180c359a Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 26 Feb 2024 08:45:12 -0800 Subject: [PATCH 1602/1897] Pick up TS 5.4 rc for bundling (#206263) --- extensions/package.json | 2 +- extensions/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/package.json b/extensions/package.json index 4365c20acc1..7066412c3f8 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -4,7 +4,7 @@ "license": "MIT", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "5.3.2" + "typescript": "5.4.1-rc" }, "scripts": { "postinstall": "node ./postinstall.mjs" diff --git a/extensions/yarn.lock b/extensions/yarn.lock index f4543f6a1c9..4b22ef50a82 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -234,10 +234,10 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -typescript@5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" - integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== +typescript@5.4.1-rc: + version "5.4.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.1-rc.tgz#1ecdd897df1d9ef5bd1f844bad64691ecc23314d" + integrity sha512-gInURzaO0bbfzfQAc3mfcHxh8qev+No4QOFUZHajo9vBgOLaljELJ3wuzyoGo/zHIzMSezdhtrsRdqL6E9SvNA== vscode-grammar-updater@^1.1.0: version "1.1.0" From 3d880720c822671cd4aa38fa9529567388b3b191 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 26 Feb 2024 09:01:43 -0800 Subject: [PATCH 1603/1897] fix #205066 --- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 50a7fc56a29..9e6c751c89c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -1654,7 +1654,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.StartVoice, - title: localize2('workbench.action.terminal.startVoice', "Start Terminal Voice"), + title: localize2('workbench.action.terminal.startDictation', "Start Dictation in Terminal"), precondition: ContextKeyExpr.and(HasSpeechProvider, sharedWhenClause.terminalAvailable), f1: true, run: (activeInstance, c, accessor) => { @@ -1665,7 +1665,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.StopVoice, - title: localize2('workbench.action.terminal.stopVoice', "Stop Terminal Voice"), + title: localize2('workbench.action.terminal.stopDictation', "Stop Dictation in Terminal"), precondition: ContextKeyExpr.and(HasSpeechProvider, sharedWhenClause.terminalAvailable), f1: true, run: (activeInstance, c, accessor) => { From 0a0f196666c687e0dd0642411a70ba0e7f9cb1c1 Mon Sep 17 00:00:00 2001 From: Luis Sousa Date: Mon, 26 Feb 2024 14:19:56 -0300 Subject: [PATCH 1604/1897] Feat: Add PascalCase to CaseActions (#206259) feat: Add PascalCase to CaseActions --- .../browser/linesOperations.ts | 32 +++++++++ .../test/browser/linesOperations.test.ts | 70 ++++++++++++++++++- 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index 74d7849587e..dd6b3158491 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -1187,6 +1187,35 @@ export class CamelCaseAction extends AbstractCaseAction { } } +export class PascalCaseAction extends AbstractCaseAction { + public static wordBoundary = new BackwardsCompatibleRegExp('[_\\s-]', 'gm'); + public static wordBoundaryToMaintain = new BackwardsCompatibleRegExp('(?<=\\.)', 'gm'); + + constructor() { + super({ + id: 'editor.action.transformToPascalcase', + label: nls.localize('editor.transformToPascalcase', "Transform to Pascal Case"), + alias: 'Transform to Pascal Case', + precondition: EditorContextKeys.writable + }); + } + + protected _modifyText(text: string, wordSeparators: string): string { + const wordBoundary = PascalCaseAction.wordBoundary.get(); + const wordBoundaryToMaintain = PascalCaseAction.wordBoundaryToMaintain.get(); + + if (!wordBoundary || !wordBoundaryToMaintain) { + // cannot support this + return text; + } + + const wordsWithMaintainBoundaries = text.split(wordBoundaryToMaintain); + const words = wordsWithMaintainBoundaries.map((word: string) => word.split(wordBoundary)).flat(); + return words.map((word: string) => word.substring(0, 1).toLocaleUpperCase() + word.substring(1)) + .join(''); + } +} + export class KebabCaseAction extends AbstractCaseAction { public static isSupported(): boolean { @@ -1257,6 +1286,9 @@ if (SnakeCaseAction.caseBoundary.isSupported() && SnakeCaseAction.singleLetters. if (CamelCaseAction.wordBoundary.isSupported()) { registerEditorAction(CamelCaseAction); } +if (PascalCaseAction.wordBoundary.isSupported()) { + registerEditorAction(PascalCaseAction); +} if (TitleCaseAction.titleBoundary.isSupported()) { registerEditorAction(TitleCaseAction); } diff --git a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts index 3df2a1f682c..795bf69bec5 100644 --- a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts @@ -12,7 +12,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { Handler } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; -import { CamelCaseAction, DeleteAllLeftAction, DeleteAllRightAction, DeleteDuplicateLinesAction, DeleteLinesAction, IndentLinesAction, InsertLineAfterAction, InsertLineBeforeAction, JoinLinesAction, KebabCaseAction, LowerCaseAction, SnakeCaseAction, SortLinesAscendingAction, SortLinesDescendingAction, TitleCaseAction, TransposeAction, UpperCaseAction } from 'vs/editor/contrib/linesOperations/browser/linesOperations'; +import { CamelCaseAction, PascalCaseAction, DeleteAllLeftAction, DeleteAllRightAction, DeleteDuplicateLinesAction, DeleteLinesAction, IndentLinesAction, InsertLineAfterAction, InsertLineBeforeAction, JoinLinesAction, KebabCaseAction, LowerCaseAction, SnakeCaseAction, SortLinesAscendingAction, SortLinesDescendingAction, TitleCaseAction, TransposeAction, UpperCaseAction } from 'vs/editor/contrib/linesOperations/browser/linesOperations'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; @@ -935,6 +935,74 @@ suite('Editor Contrib - Line Operations', () => { assertSelection(editor, new Selection(11, 1, 11, 11)); } ); + + withTestCodeEditor( + [ + 'hello world', + 'öçşğü', + 'parseHTMLString', + 'getElementById', + 'PascalCase', + 'öçşÖÇŞğüĞÜ', + 'audioConverter.convertM4AToMP3();', + 'Capital_Snake_Case', + 'parseHTML4String', + 'Kebab-Case', + ], {}, (editor) => { + const model = editor.getModel()!; + const pascalCaseAction = new PascalCaseAction(); + + editor.setSelection(new Selection(1, 1, 1, 12)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(1), 'HelloWorld'); + assertSelection(editor, new Selection(1, 1, 1, 11)); + + editor.setSelection(new Selection(2, 1, 2, 6)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(2), 'Öçşğü'); + assertSelection(editor, new Selection(2, 1, 2, 6)); + + editor.setSelection(new Selection(3, 1, 3, 16)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(3), 'ParseHTMLString'); + assertSelection(editor, new Selection(3, 1, 3, 16)); + + editor.setSelection(new Selection(4, 1, 4, 15)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(4), 'GetElementById'); + assertSelection(editor, new Selection(4, 1, 4, 15)); + + editor.setSelection(new Selection(5, 1, 5, 11)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(5), 'PascalCase'); + assertSelection(editor, new Selection(5, 1, 5, 11)); + + editor.setSelection(new Selection(6, 1, 6, 11)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(6), 'ÖçşÖÇŞğüĞÜ'); + assertSelection(editor, new Selection(6, 1, 6, 11)); + + editor.setSelection(new Selection(7, 1, 7, 34)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(7), 'AudioConverter.ConvertM4AToMP3();'); + assertSelection(editor, new Selection(7, 1, 7, 34)); + + editor.setSelection(new Selection(8, 1, 8, 19)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(8), 'CapitalSnakeCase'); + assertSelection(editor, new Selection(8, 1, 8, 17)); + + editor.setSelection(new Selection(9, 1, 9, 17)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(9), 'ParseHTML4String'); + assertSelection(editor, new Selection(9, 1, 9, 17)); + + editor.setSelection(new Selection(10, 1, 10, 11)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(10), 'KebabCase'); + assertSelection(editor, new Selection(10, 1, 10, 10)); + } + ); }); suite('DeleteAllRightAction', () => { From 6350f21dfe5614d722def7e259103e846ef5bf2c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 26 Feb 2024 10:07:28 -0800 Subject: [PATCH 1605/1897] Reveal link source while navigating detected links Part of #206127 --- .../terminal/browser/media/terminal.css | 5 + .../contrib/terminal/browser/terminal.ts | 6 +- .../browser/xterm/markNavigationAddon.ts | 97 +++++++++++++++++-- .../browser/terminal.links.contribution.ts | 2 +- .../links/browser/terminalLinkQuickpick.ts | 46 ++++++++- 5 files changed, 145 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 488815cf258..e30f5dd92d9 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -474,6 +474,11 @@ pointer-events: none; } +.terminal-range-highlight { + outline: 1px solid var(--vscode-focusBorder); + pointer-events: none; +} + .terminal-command-guide { left: 0; border: 1.5px solid #ffffff; diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 9a1bd1ab1b7..d8d7ebeb111 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -23,7 +23,7 @@ import { ITerminalStatusList } from 'vs/workbench/contrib/terminal/browser/termi import { XtermTerminal } from 'vs/workbench/contrib/terminal/browser/xterm/xtermTerminal'; import { IRegisterContributedProfileArgs, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfiguration, ITerminalFont, ITerminalProcessExtHostProxy, ITerminalProcessInfo } from 'vs/workbench/contrib/terminal/common/terminal'; import { ISimpleSelectedSuggestion } from 'vs/workbench/services/suggest/browser/simpleSuggestWidget'; -import type { IMarker, ITheme, Terminal as RawXtermTerminal } from '@xterm/xterm'; +import type { IMarker, ITheme, Terminal as RawXtermTerminal, IBufferRange } from '@xterm/xterm'; import { ScrollPosition } from 'vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { GroupIdentifier } from 'vs/workbench/common/editor'; @@ -118,8 +118,12 @@ export interface IMarkTracker { scrollToLine(line: number, position: ScrollPosition): void; revealCommand(command: ITerminalCommand, position?: ScrollPosition): void; + revealRange(range: IBufferRange): void; registerTemporaryDecoration(marker: IMarker, endMarker: IMarker | undefined, showOutline: boolean): void; showCommandGuide(command: ITerminalCommand | undefined): void; + + saveScrollState(): void; + restoreScrollState(): void; } export interface ITerminalGroup { diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts index 499e1d2f57d..bb6907c4e78 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts @@ -7,7 +7,7 @@ import { coalesce } from 'vs/base/common/arrays'; import { Disposable, DisposableStore, MutableDisposable, dispose } from 'vs/base/common/lifecycle'; import { IMarkTracker } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ITerminalCapabilityStore, ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; -import type { Terminal, IMarker, ITerminalAddon, IDecoration } from '@xterm/xterm'; +import type { Terminal, IMarker, ITerminalAddon, IDecoration, IBufferRange } from '@xterm/xterm'; import { timeout } from 'vs/base/common/async'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TERMINAL_OVERVIEW_RULER_CURSOR_FOREGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; @@ -24,6 +24,11 @@ export const enum ScrollPosition { Middle } +interface IScrollToMarkerOptions { + hideDecoration?: boolean; + bufferRange?: IBufferRange; +} + export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITerminalAddon { private _currentMarker: IMarker | Boundary = Boundary.Bottom; private _selectionStart: IMarker | Boundary | null = null; @@ -219,7 +224,7 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe } } - private _scrollToMarker(start: IMarker | number, position: ScrollPosition, end?: IMarker | number, hideDecoration?: boolean): void { + private _scrollToMarker(start: IMarker | number, position: ScrollPosition, end?: IMarker | number, options?: IScrollToMarkerOptions): void { if (!this._terminal) { return; } @@ -227,8 +232,12 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe const line = this.getTargetScrollLine(toLineIndex(start), position); this._terminal.scrollToLine(line); } - if (!hideDecoration) { - this.registerTemporaryDecoration(start, end, true); + if (!options?.hideDecoration) { + if (options?.bufferRange) { + this._highlightBufferRange(options.bufferRange); + } else { + this.registerTemporaryDecoration(start, end, true); + } } } @@ -260,6 +269,16 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe ); } + revealRange(range: IBufferRange): void { + // TODO: Allow room for sticky scroll + this._scrollToMarker( + range.start.y - 1, + ScrollPosition.Middle, + range.end.y - 1, + { bufferRange: range } + ); + } + showCommandGuide(command: ITerminalCommand | undefined): void { if (!this._terminal) { return; @@ -314,6 +333,72 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe } } + + private _scrollState: { viewportY: number } | undefined; + + saveScrollState(): void { + this._scrollState = { viewportY: this._terminal?.buffer.active.viewportY ?? 0 }; + } + + restoreScrollState(): void { + if (this._scrollState && this._terminal) { + this._terminal.scrollToLine(this._scrollState.viewportY); + this._scrollState = undefined; + } + } + + private _highlightBufferRange(range: IBufferRange): void { + if (!this._terminal) { + return; + } + + // TODO: Save original scroll point + + this._resetNavigationDecorations(); + const startLine = range.start.y; + const decorationCount = range.end.y - range.start.y + 1; + for (let i = 0; i < decorationCount; i++) { + const decoration = this._terminal.registerDecoration({ + marker: this._createMarkerForOffset(startLine - 1, i), + x: range.start.x - 1, + width: (range.end.x - 1) - (range.start.x - 1) + 1, + overviewRulerOptions: undefined + }); + if (decoration) { + this._navigationDecorations?.push(decoration); + let renderedElement: HTMLElement | undefined; + + decoration.onRender(element => { + if (!renderedElement) { + renderedElement = element; + // if (i === 0) { + // element.classList.add('top'); + // } + // if (i === decorationCount - 1) { + // element.classList.add('bottom'); + // } + element.classList.add('terminal-range-highlight'); + } + if (this._terminal?.element) { + // element.style.marginLeft = `-${getWindow(this._terminal.element).getComputedStyle(this._terminal.element).paddingLeft}`; + } + }); + // TODO: Scroll may be under sticky scroll + + // TODO: This is not efficient for a large decorationCount + decoration.onDispose(() => { this._navigationDecorations = this._navigationDecorations?.filter(d => d !== decoration); }); + // Number picked to align with symbol highlight in the editor + // if (showOutline) { + // timeout(350).then(() => { + // if (renderedElement) { + // renderedElement.classList.remove('terminal-scroll-highlight-outline'); + // } + // }); + // } + } + } + } + registerTemporaryDecoration(marker: IMarker | number, endMarker: IMarker | number | undefined, showOutline: boolean): void { if (!this._terminal) { return; @@ -373,7 +458,7 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe } getTargetScrollLine(line: number, position: ScrollPosition): number { - // Middle is treated at 1/4 of the viewport's size because context below is almost always + // Middle is treated as 1/4 of the viewport's size because context below is almost always // more important than context above in the terminal. if (this._terminal && position === ScrollPosition.Middle) { return Math.max(line - Math.floor(this._terminal.rows / 4), 0); @@ -397,7 +482,7 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe return; } const endMarker = endMarkerId ? detectionCapability.getMark(endMarkerId) : startMarker; - this._scrollToMarker(startMarker, ScrollPosition.Top, endMarker, !highlight); + this._scrollToMarker(startMarker, ScrollPosition.Top, endMarker, { hideDecoration: !highlight }); } selectToPreviousMark(): void { diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts index 0327aa48168..2a0269486a3 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts @@ -83,7 +83,7 @@ class TerminalLinkContribution extends DisposableStore implements ITerminalContr }); } const links = await this._getLinks(); - return await this._terminalLinkQuickpick.show(links); + return await this._terminalLinkQuickpick.show(this._instance, links); } private async _getLinks(): Promise<{ viewport: IDetectedLinks; all: Promise }> { diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts index 2c042949a9d..f496f89cbfc 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts @@ -8,7 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { QuickPickItem, IQuickInputService, IQuickPickItem, QuickInputHideReason } from 'vs/platform/quickinput/common/quickInput'; import { IDetectedLinks } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager'; -import { TerminalLinkQuickPickEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalLinkQuickPickEvent, type IDetachedTerminalInstance, type ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import type { ILink } from '@xterm/xterm'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; @@ -30,6 +30,8 @@ export class TerminalLinkQuickpick extends DisposableStore { private readonly _editorSequencer = new Sequencer(); private readonly _editorViewState: EditorViewState; + private _instance: ITerminalInstance | IDetachedTerminalInstance | undefined; + private readonly _onDidRequestMoreLinks = this.add(new Emitter()); readonly onDidRequestMoreLinks = this._onDidRequestMoreLinks.event; @@ -45,7 +47,9 @@ export class TerminalLinkQuickpick extends DisposableStore { this._editorViewState = new EditorViewState(_editorService); } - async show(links: { viewport: IDetectedLinks; all: Promise }): Promise { + async show(instance: ITerminalInstance | IDetachedTerminalInstance, links: { viewport: IDetectedLinks; all: Promise }): Promise { + this._instance = instance; + // Get raw link picks const wordPicks = links.viewport.wordLinks ? await this._generatePicks(links.viewport.wordLinks) : undefined; const filePicks = links.viewport.fileLinks ? await this._generatePicks(links.viewport.fileLinks) : undefined; @@ -122,6 +126,18 @@ export class TerminalLinkQuickpick extends DisposableStore { return new Promise(r => { disposables.add(pick.onDidHide(({ reason }) => { + + // Restore terminal scroll state + if (this._terminalScrollStateSaved) { + const markTracker = this._instance?.xterm?.markTracker; + if (markTracker) { + markTracker.restoreScrollState(); + // TODO: This name isn't great + markTracker.clearMarker(); + this._terminalScrollStateSaved = false; + } + } + // Restore view state upon cancellation if we changed it // but only when the picker was closed via explicit user // gesture and not e.g. when focus was lost because that @@ -208,11 +224,18 @@ export class TerminalLinkQuickpick extends DisposableStore { } private _previewItem(item: ITerminalLinkQuickPickItem | IQuickPickItem) { - if (!item || !('link' in item) || !item.link || !('uri' in item.link) || !item.link.uri) { + if (!item || !('link' in item) || !item.link) { return; } + // Any link can be previewed in the termninal const link = item.link; + this._previewItemInTerminal(link); + + if (!('uri' in link) || !link.uri) { + return; + } + if (link.type !== TerminalBuiltinLinkType.LocalFile) { return; } @@ -223,6 +246,10 @@ export class TerminalLinkQuickpick extends DisposableStore { return; } + this._previewItemInEditor(link); + } + + private _previewItemInEditor(link: TerminalLink) { const linkSuffix = link.parsedLink ? link.parsedLink.suffix : getLinkSuffix(link.text); const selection = linkSuffix?.row === undefined ? undefined : { startLineNumber: linkSuffix.row ?? 1, @@ -245,6 +272,19 @@ export class TerminalLinkQuickpick extends DisposableStore { } }); } + + private _terminalScrollStateSaved: boolean = false; + private _previewItemInTerminal(link: ILink) { + const xterm = this._instance?.xterm; + if (!xterm) { + return; + } + if (!this._terminalScrollStateSaved) { + xterm.markTracker.saveScrollState(); + this._terminalScrollStateSaved = true; + } + xterm.markTracker.revealRange(link.range); + } } export interface ITerminalLinkQuickPickItem extends IQuickPickItem { From 920a3a701eeb5830d94f00813d444873599e44ea Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 26 Feb 2024 10:56:20 -0800 Subject: [PATCH 1606/1897] Pick up latest TS for building VS Code (#206264) --- build/azure-pipelines/common/publish.js | 2 +- build/lib/compilation.js | 2 +- package.json | 2 +- yarn.lock | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build/azure-pipelines/common/publish.js b/build/azure-pipelines/common/publish.js index e6b24921ac1..b690ae5c792 100644 --- a/build/azure-pipelines/common/publish.js +++ b/build/azure-pipelines/common/publish.js @@ -595,7 +595,7 @@ async function main() { operations.push({ name: artifact.name, operation }); resultPromise = Promise.allSettled(operations.map(o => o.operation)); } - await new Promise(c => setTimeout(c, 10000)); + await new Promise(c => setTimeout(c, 10_000)); } console.log(`Found all ${done.size + processing.size} artifacts, waiting for ${processing.size} artifacts to finish publishing...`); const artifactsInProgress = operations.filter(o => processing.has(o.name)); diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 35bc464d34a..e7a460de7d0 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -99,7 +99,7 @@ function transpileTask(src, out, swc) { exports.transpileTask = transpileTask; function compileTask(src, out, build, options = {}) { const task = () => { - if (os.totalmem() < 4000000000) { + if (os.totalmem() < 4_000_000_000) { throw new Error('compilation requires 4GB of RAM'); } const compile = createCompile(src, build, true, false); diff --git a/package.json b/package.json index 2f127741d55..30ed28ac516 100644 --- a/package.json +++ b/package.json @@ -209,7 +209,7 @@ "ts-loader": "^9.4.2", "ts-node": "^10.9.1", "tsec": "0.2.7", - "typescript": "^5.4.0-dev.20240206", + "typescript": "^5.5.0-dev.20240226", "typescript-formatter": "7.1.0", "util": "^0.12.4", "vscode-nls-dev": "^3.3.1", diff --git a/yarn.lock b/yarn.lock index 98368474549..056c1e1bce0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9636,10 +9636,10 @@ typescript@^4.7.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== -typescript@^5.4.0-dev.20240206: - version "5.4.0-dev.20240206" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.0-dev.20240206.tgz#75755acb115e1176958d511d11eb018694e74987" - integrity sha512-8P1XYxDbG/AyGE5tB8+JpeiQfS5ye1BTvIVDZaHhoK9nJuCn4nkB0L66lvfwYB+46hA4rLo3vE3WkIToSYtqQA== +typescript@^5.5.0-dev.20240226: + version "5.5.0-dev.20240226" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.0-dev.20240226.tgz#b571688666f07e4d7db4c9863f3ee1401e161a7a" + integrity sha512-mLY9/pjzSCr7JLkMKHS3KQUKX+LPO9WWjiR+mRcWKcskSdMBZ0j1TPhk/zUyuBklOf3YX4orkvamNiZWZEK0CQ== typical@^4.0.0: version "4.0.0" From a2030c81feeec0d803dfe60e5b4a113c3c7b47e1 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 26 Feb 2024 11:10:01 -0800 Subject: [PATCH 1607/1897] Update issue notebook milestones (#206278) --- .vscode/notebooks/api.github-issues | 2 +- .vscode/notebooks/endgame.github-issues | 2 +- .vscode/notebooks/my-endgame.github-issues | 2 +- .vscode/notebooks/my-work.github-issues | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index 2a6f3ec1bc5..6b8a385ec42 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"February 2024\"" + "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"March 2024\"" }, { "kind": 1, diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index ee1084be56d..750e53e4b26 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"February 2024\"" + "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"March 2024\"" }, { "kind": 1, diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index a286082c738..ab59f23283f 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"February 2024\"\n\n$MINE=assignee:@me" + "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"March 2024\"\n\n$MINE=assignee:@me" }, { "kind": 1, diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index 2a3f9159703..b23dacf87e4 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n// current milestone name\n$MILESTONE=milestone:\"February 2024\"\n" + "value": "// list of repos we work in\n$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n// current milestone name\n$MILESTONE=milestone:\"March 2024\"\n" }, { "kind": 1, From ead787f6f01630a1261fe80abe87242c9170ebc6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 26 Feb 2024 11:13:02 -0800 Subject: [PATCH 1608/1897] fix issue --- .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 180d51c1549..aa43972ef64 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -9,7 +9,7 @@ import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/terminalChatWidget'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; From c9e61431bae75f54303d2c00b83c6ed6b8ff2f3c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:17:28 -0800 Subject: [PATCH 1609/1897] Show all links if resolved within 500ms Fixes #206280 --- .../links/browser/terminalLinkQuickpick.ts | 76 ++++++++++--------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts index f496f89cbfc..544612bcb53 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts @@ -14,7 +14,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import type { TerminalLink } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLink'; -import { Sequencer } from 'vs/base/common/async'; +import { Sequencer, timeout } from 'vs/base/common/async'; import { EditorViewState } from 'vs/workbench/browser/quickaccess'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; @@ -50,11 +50,17 @@ export class TerminalLinkQuickpick extends DisposableStore { async show(instance: ITerminalInstance | IDetachedTerminalInstance, links: { viewport: IDetectedLinks; all: Promise }): Promise { this._instance = instance; + // Allow all links a small amount of time to elapse to finish, if this is not done in this + // time they will be loaded upon the first filter. + const result = await Promise.race([links.all, timeout(500)]); + const usingAllLinks = typeof result === 'object'; + const resolvedLinks = usingAllLinks ? result : links.viewport; + // Get raw link picks - const wordPicks = links.viewport.wordLinks ? await this._generatePicks(links.viewport.wordLinks) : undefined; - const filePicks = links.viewport.fileLinks ? await this._generatePicks(links.viewport.fileLinks) : undefined; - const folderPicks = links.viewport.folderLinks ? await this._generatePicks(links.viewport.folderLinks) : undefined; - const webPicks = links.viewport.webLinks ? await this._generatePicks(links.viewport.webLinks) : undefined; + const wordPicks = resolvedLinks.wordLinks ? await this._generatePicks(resolvedLinks.wordLinks) : undefined; + const filePicks = resolvedLinks.fileLinks ? await this._generatePicks(resolvedLinks.fileLinks) : undefined; + const folderPicks = resolvedLinks.folderLinks ? await this._generatePicks(resolvedLinks.folderLinks) : undefined; + const webPicks = resolvedLinks.webLinks ? await this._generatePicks(resolvedLinks.webLinks) : undefined; const picks: LinkQuickPickItem[] = []; if (webPicks) { @@ -88,36 +94,38 @@ export class TerminalLinkQuickpick extends DisposableStore { // ASAP with only the viewport entries. let accepted = false; const disposables = new DisposableStore(); - disposables.add(Event.once(pick.onDidChangeValue)(async () => { - const allLinks = await links.all; - if (accepted) { - return; - } - const wordIgnoreLinks = [...(allLinks.fileLinks ?? []), ...(allLinks.folderLinks ?? []), ...(allLinks.webLinks ?? [])]; + if (!usingAllLinks) { + disposables.add(Event.once(pick.onDidChangeValue)(async () => { + const allLinks = await links.all; + if (accepted) { + return; + } + const wordIgnoreLinks = [...(allLinks.fileLinks ?? []), ...(allLinks.folderLinks ?? []), ...(allLinks.webLinks ?? [])]; - const wordPicks = allLinks.wordLinks ? await this._generatePicks(allLinks.wordLinks, wordIgnoreLinks) : undefined; - const filePicks = allLinks.fileLinks ? await this._generatePicks(allLinks.fileLinks) : undefined; - const folderPicks = allLinks.folderLinks ? await this._generatePicks(allLinks.folderLinks) : undefined; - const webPicks = allLinks.webLinks ? await this._generatePicks(allLinks.webLinks) : undefined; - const picks: LinkQuickPickItem[] = []; - if (webPicks) { - picks.push({ type: 'separator', label: localize('terminal.integrated.urlLinks', "Url") }); - picks.push(...webPicks); - } - if (filePicks) { - picks.push({ type: 'separator', label: localize('terminal.integrated.localFileLinks', "File") }); - picks.push(...filePicks); - } - if (folderPicks) { - picks.push({ type: 'separator', label: localize('terminal.integrated.localFolderLinks', "Folder") }); - picks.push(...folderPicks); - } - if (wordPicks) { - picks.push({ type: 'separator', label: localize('terminal.integrated.searchLinks', "Workspace Search") }); - picks.push(...wordPicks); - } - pick.items = picks; - })); + const wordPicks = allLinks.wordLinks ? await this._generatePicks(allLinks.wordLinks, wordIgnoreLinks) : undefined; + const filePicks = allLinks.fileLinks ? await this._generatePicks(allLinks.fileLinks) : undefined; + const folderPicks = allLinks.folderLinks ? await this._generatePicks(allLinks.folderLinks) : undefined; + const webPicks = allLinks.webLinks ? await this._generatePicks(allLinks.webLinks) : undefined; + const picks: LinkQuickPickItem[] = []; + if (webPicks) { + picks.push({ type: 'separator', label: localize('terminal.integrated.urlLinks', "Url") }); + picks.push(...webPicks); + } + if (filePicks) { + picks.push({ type: 'separator', label: localize('terminal.integrated.localFileLinks', "File") }); + picks.push(...filePicks); + } + if (folderPicks) { + picks.push({ type: 'separator', label: localize('terminal.integrated.localFolderLinks', "Folder") }); + picks.push(...folderPicks); + } + if (wordPicks) { + picks.push({ type: 'separator', label: localize('terminal.integrated.searchLinks', "Workspace Search") }); + picks.push(...wordPicks); + } + pick.items = picks; + })); + } disposables.add(pick.onDidChangeActive(async () => { const [item] = pick.activeItems; From 754dc0c68a7f1137695f1774425ed8bcb873c2b8 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Mon, 26 Feb 2024 11:25:01 -0800 Subject: [PATCH 1610/1897] Run in Section for notebook sticky scroll context menu (#205307) * breadcrumbs in nb sticky context menu * run section nb sticky scroll context menu * implement actionRunner for run in section ctx menu * use context for run in section args * nit + toggle verbage fix --- .../parts/editor/breadcrumbsControl.ts | 5 +- .../viewParts/notebookEditorStickyScroll.ts | 74 ++++++++++++++++--- 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 00e7153c46f..b97793d6ddd 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -612,13 +612,14 @@ registerAction2(class ToggleBreadcrumb extends Action2 { toggled: { condition: ContextKeyExpr.equals('config.breadcrumbs.enabled', true), title: localize('cmd.toggle2', "Breadcrumbs"), - mnemonicTitle: localize({ key: 'miBreadcrumbs2', comment: ['&& denotes a mnemonic'] }, "&&Breadcrumbs") + mnemonicTitle: localize({ key: 'miBreadcrumbs2', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breadcrumbs") }, menu: [ { id: MenuId.CommandPalette }, { id: MenuId.MenubarAppearanceMenu, group: '4_editor', order: 2 }, { id: MenuId.NotebookToolbar, group: 'notebookLayout', order: 2 }, - { id: MenuId.StickyScrollContext } + { id: MenuId.StickyScrollContext }, + { id: MenuId.NotebookStickyScrollContext, group: 'notebookView', order: 2 } ] }); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts index a02e1bb2010..1fb8e60d3db 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts @@ -34,17 +34,21 @@ export class ToggleNotebookStickyScroll extends Action2 { id: 'notebook.action.toggleNotebookStickyScroll', title: { ...localize2('toggleStickyScroll', "Toggle Notebook Sticky Scroll"), - mnemonicTitle: localize({ key: 'mitoggleStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Toggle Notebook Sticky Scroll"), + mnemonicTitle: localize({ key: 'mitoggleNotebookStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Toggle Notebook Sticky Scroll"), }, category: Categories.View, toggled: { condition: ContextKeyExpr.equals('config.notebook.stickyScroll.enabled', true), title: localize('notebookStickyScroll', "Notebook Sticky Scroll"), - mnemonicTitle: localize({ key: 'miNotebookStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Notebook Sticky Scroll"), + mnemonicTitle: localize({ key: 'mitoggleNotebookStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Toggle Notebook Sticky Scroll"), }, menu: [ { id: MenuId.CommandPalette }, - { id: MenuId.NotebookStickyScrollContext } + { + id: MenuId.NotebookStickyScrollContext, + group: 'notebookView', + order: 2 + } ] }); } @@ -56,6 +60,51 @@ export class ToggleNotebookStickyScroll extends Action2 { } } +type RunInSectionContext = { + target: HTMLElement; + currentStickyLines: Map; + notebookEditor: INotebookEditor; +}; + +export class RunInSectionStickyScroll extends Action2 { + constructor() { + super({ + id: 'notebook.action.runInSection', + title: { + ...localize2('runInSectionStickyScroll', "Run Section"), + mnemonicTitle: localize({ key: 'mirunInSectionStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Run Section"), + }, + menu: [ + { + id: MenuId.NotebookStickyScrollContext, + group: 'notebookExecution', + order: 1 + } + ] + }); + } + + override async run(accessor: ServicesAccessor, context: RunInSectionContext, ...args: any[]): Promise { + const selectedElement = context.target.parentElement; + const stickyLines: Map = context.currentStickyLines; + + const selectedOutlineEntry = Array.from(stickyLines.values()).find(entry => entry.line.element.contains(selectedElement))?.line.entry; + if (!selectedOutlineEntry) { + return; + } + + const flatList: OutlineEntry[] = []; + selectedOutlineEntry.asFlatList(flatList); + + const cellViewModels = flatList.map(entry => entry.cell); + const notebookEditor: INotebookEditor = context.notebookEditor; + notebookEditor.executeNotebookCells(cellViewModels); + } +} + export class NotebookStickyLine extends Disposable { constructor( public readonly element: HTMLElement, @@ -78,14 +127,6 @@ export class NotebookStickyLine extends Disposable { } })); - // folding icon hovers - // this._register(DOM.addDisposableListener(this.element, DOM.EventType.MOUSE_OVER, () => { - // this.foldingIcon.setVisible(true); - // })); - // this._register(DOM.addDisposableListener(this.element, DOM.EventType.MOUSE_OUT, () => { - // this.foldingIcon.setVisible(false); - // })); - } private toggleFoldRange(currentState: CellFoldingState) { @@ -145,7 +186,6 @@ export class NotebookStickyScroll extends Disposable { private readonly _onDidChangeNotebookStickyScroll = this._register(new Emitter()); readonly onDidChangeNotebookStickyScroll: Event = this._onDidChangeNotebookStickyScroll.event; - getDomNode(): HTMLElement { return this.domNode; } @@ -205,9 +245,17 @@ export class NotebookStickyScroll extends Disposable { private onContextMenu(e: MouseEvent) { const event = new StandardMouseEvent(DOM.getWindow(this.domNode), e); + + const context: RunInSectionContext = { + target: event.target, + currentStickyLines: this.currentStickyLines, + notebookEditor: this.notebookEditor, + }; + this._contextMenuService.showContextMenu({ menuId: MenuId.NotebookStickyScrollContext, getAnchor: () => event, + menuActionOptions: { shouldForwardArgs: true, arg: context }, }); } @@ -384,6 +432,7 @@ export class NotebookStickyScroll extends Disposable { stickyHeader.innerText = entry.label; stickyElement.append(stickyFoldingIcon.domNode, stickyHeader); + return new NotebookStickyLine(stickyElement, stickyFoldingIcon, stickyHeader, entry, notebookEditor); } @@ -490,3 +539,4 @@ export function computeContent(notebookEditor: INotebookEditor, notebookCellList } registerAction2(ToggleNotebookStickyScroll); +registerAction2(RunInSectionStickyScroll); From d4b102e34470574cf8c6eedb7bb6895268674ba5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:28:42 -0800 Subject: [PATCH 1611/1897] Always scroll when sticky scroll is enabled --- .../browser/xterm/markNavigationAddon.ts | 36 ++++++------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts index bb6907c4e78..aae4ac6f275 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts @@ -13,6 +13,8 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TERMINAL_OVERVIEW_RULER_CURSOR_FOREGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { getWindow } from 'vs/base/browser/dom'; import { ICurrentPartialCommand } from 'vs/platform/terminal/common/capabilities/commandDetection/terminalCommand'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; enum Boundary { Top, @@ -26,6 +28,8 @@ export const enum ScrollPosition { interface IScrollToMarkerOptions { hideDecoration?: boolean; + /** Scroll even if the line is within the viewport */ + forceScroll?: boolean; bufferRange?: IBufferRange; } @@ -48,6 +52,7 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe constructor( private readonly _capabilities: ITerminalCapabilityStore, + @IConfigurationService private readonly _configurationService: IConfigurationService, @IThemeService private readonly _themeService: IThemeService ) { super(); @@ -228,7 +233,7 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe if (!this._terminal) { return; } - if (!this._isMarkerInViewport(this._terminal, start)) { + if (!this._isMarkerInViewport(this._terminal, start) || options?.forceScroll) { const line = this.getTargetScrollLine(toLineIndex(start), position); this._terminal.scrollToLine(line); } @@ -270,12 +275,15 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe } revealRange(range: IBufferRange): void { - // TODO: Allow room for sticky scroll this._scrollToMarker( range.start.y - 1, ScrollPosition.Middle, range.end.y - 1, - { bufferRange: range } + { + bufferRange: range, + // Ensure scroll shows the line when sticky scroll is enabled + forceScroll: !!this._configurationService.getValue(TerminalSettingId.StickyScrollEnabled) + } ); } @@ -352,8 +360,6 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe return; } - // TODO: Save original scroll point - this._resetNavigationDecorations(); const startLine = range.start.y; const decorationCount = range.end.y - range.start.y + 1; @@ -371,30 +377,10 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe decoration.onRender(element => { if (!renderedElement) { renderedElement = element; - // if (i === 0) { - // element.classList.add('top'); - // } - // if (i === decorationCount - 1) { - // element.classList.add('bottom'); - // } element.classList.add('terminal-range-highlight'); } - if (this._terminal?.element) { - // element.style.marginLeft = `-${getWindow(this._terminal.element).getComputedStyle(this._terminal.element).paddingLeft}`; - } }); - // TODO: Scroll may be under sticky scroll - - // TODO: This is not efficient for a large decorationCount decoration.onDispose(() => { this._navigationDecorations = this._navigationDecorations?.filter(d => d !== decoration); }); - // Number picked to align with symbol highlight in the editor - // if (showOutline) { - // timeout(350).then(() => { - // if (renderedElement) { - // renderedElement.classList.remove('terminal-scroll-highlight-outline'); - // } - // }); - // } } } } From d1c62c90be88d326d1b8ea73c7bb7a9b6fb550ad Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 26 Feb 2024 11:29:19 -0800 Subject: [PATCH 1612/1897] Add inline code for a few special character in docs (#206277) --- src/vscode-dts/vscode.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index a8fef891e89..614dcc68cf1 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -1356,7 +1356,7 @@ declare module 'vscode' { export interface TextEditorEdit { /** * Replace a certain text region with a new value. - * You can use \r\n or \n in `value` and they will be normalized to the current {@link TextDocument document}. + * You can use `\r\n` or `\n` in `value` and they will be normalized to the current {@link TextDocument document}. * * @param location The range this operation should remove. * @param value The new text this operation should insert after removing `location`. @@ -1365,7 +1365,7 @@ declare module 'vscode' { /** * Insert text at a location. - * You can use \r\n or \n in `value` and they will be normalized to the current {@link TextDocument document}. + * You can use `\r\n` or `\n` in `value` and they will be normalized to the current {@link TextDocument document}. * Although the equivalent text edit can be made with {@link TextEditorEdit.replace replace}, `insert` will produce a different resulting selection (it will get moved). * * @param location The position where the new text should be inserted. @@ -7335,7 +7335,7 @@ declare module 'vscode' { * * @param text The text to send. * @param shouldExecute Indicates that the text being sent should be executed rather than just inserted in the terminal. - * The character(s) added are \n or \r\n, depending on the platform. This defaults to `true`. + * The character(s) added are `\n` or `\r\n`, depending on the platform. This defaults to `true`. */ sendText(text: string, shouldExecute?: boolean): void; From de7da9f4af0c7c440bddd46a6fb0a47781fa6371 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 26 Feb 2024 11:29:37 -0800 Subject: [PATCH 1613/1897] Improve creation of text models for chat code blocks (#205943) * Improve creation of text models for chat code blocks Refactors the chat code block logic to better support cross code blocks IntelliSense. Previously we only created text models for the visible editors in chat. With this new approach, we instead create a unique text model for each code block in the conversation. This allows us our IntelliSense features to work even if a code block is not visible in chat Also uses this as a change to remove some duplicate I introduced to support local file editors in chat Still a draft as the text model creation should be moved out of the chat list renderer * Move model updating logic into view model * Small cleanup --- .../contrib/chat/browser/chatListRenderer.ts | 115 +++--- .../contrib/chat/browser/chatWidget.ts | 65 +++- .../contrib/chat/browser/codeBlockPart.ts | 337 +++++------------- .../contrib/chat/common/chatViewModel.ts | 99 ++++- .../chat/common/codeBlockModelCollection.ts | 53 +++ .../inlineChat/browser/inlineChatWidget.ts | 7 +- 6 files changed, 360 insertions(+), 316 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index c0f1827014a..93b651276e5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -21,7 +21,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { FuzzyScore } from 'vs/base/common/filters'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, IReference, toDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; import { marked } from 'vs/base/common/marked/marked'; import { FileAccess, Schemas, matchesSomeScheme } from 'vs/base/common/network'; @@ -30,10 +30,9 @@ import { basename } from 'vs/base/common/path'; import { equalsIgnoreCase } from 'vs/base/common/strings'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { Range } from 'vs/editor/common/core/range'; +import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { localize } from 'vs/nls'; import { IMenuEntryActionViewItemOptions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; @@ -41,7 +40,6 @@ import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { FileKind, FileType } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -56,9 +54,9 @@ import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibil import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; -import { ChatMarkdownDecorationsRenderer, annotateSpecialMarkdownContent, extractVulnerabilitiesFromText } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; +import { ChatMarkdownDecorationsRenderer, IMarkdownVulnerability, annotateSpecialMarkdownContent, extractVulnerabilitiesFromText } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; -import { ChatCodeBlockContentProvider, ICodeBlockData, ICodeBlockPart, LocalFileCodeBlockPart, SimpleCodeBlockPart, localFileLanguageId, parseLocalFileData } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; +import { ChatCodeBlockContentProvider, CodeBlockPart, ICodeBlockData, ICodeBlockPart, localFileLanguageId, parseLocalFileData } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; @@ -68,6 +66,7 @@ import { IChatProgressMessageRenderData, IChatRenderData, IChatResponseMarkdownR import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView'; import { IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; +import { CodeBlockModelCollection } from '../common/codeBlockModelCollection'; const $ = dom.$; @@ -133,47 +132,30 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer => { - if (input.resource.scheme !== Schemas.vscodeChatCodeBlock) { - return null; - } - const block = this._editorPool.find(input.resource); - if (!block) { - return null; - } - if (input.options?.selection) { - block.editor.setSelection({ - startLineNumber: input.options.selection.startLineNumber, - startColumn: input.options.selection.startColumn, - endLineNumber: input.options.selection.startLineNumber ?? input.options.selection.endLineNumber, - endColumn: input.options.selection.startColumn ?? input.options.selection.endColumn - }); - } - return block.editor; - })); - this._usedReferencesEnabled = configService.getValue('chat.experimental.usedReferences') ?? true; this._register(configService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('chat.experimental.usedReferences')) { @@ -186,6 +168,10 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - let data: ICodeBlockData; + const index = codeBlockIndex++; + let textModel: Promise>; + let range: Range | undefined; + let vulns: readonly IMarkdownVulnerability[] | undefined; if (equalsIgnoreCase(languageId, localFileLanguageId)) { try { const parsedBody = parseLocalFileData(text); - data = { type: 'localFile', uri: parsedBody.uri, range: parsedBody.range && Range.lift(parsedBody.range), codeBlockIndex: codeBlockIndex++, element, hideToolbar: false, parentContextKeyService: templateData.contextKeyService }; + range = parsedBody.range && Range.lift(parsedBody.range); + textModel = this.textModelService.createModelReference(parsedBody.uri); } catch (e) { - console.error(e); return $('div'); } } else { - const vulns = extractVulnerabilitiesFromText(text); - const hideToolbar = isResponseVM(element) && element.errorDetails?.responseIsFiltered; - data = { type: 'code', languageId, text: vulns.newText, codeBlockIndex: codeBlockIndex++, element, hideToolbar, parentContextKeyService: templateData.contextKeyService, vulns: vulns.vulnerabilities }; + const blockModel = this.codeBlockModelCollection.get(element.id, index); + if (!blockModel) { + console.error('Trying to render code block without model', element.id, index); + return $('div'); + } + + textModel = blockModel; + const extractedVulns = extractVulnerabilitiesFromText(text); + vulns = extractedVulns.vulnerabilities; + textModel.then(ref => ref.object.textEditorModel.setValue(extractedVulns.newText)); } - const ref = this.renderCodeBlock(data); + const hideToolbar = isResponseVM(element) && element.errorDetails?.responseIsFiltered; + const ref = this.renderCodeBlock({ languageId, textModel, codeBlockIndex: index, element, range, hideToolbar, parentContextKeyService: templateData.contextKeyService, vulns }); // Attach this after updating text/layout of the editor, so it should only be fired when the size updates later (horizontal scrollbar, wrapping) // not during a renderElement OR a progressive render (when we will be firing this event anyway at the end of the render) @@ -899,15 +896,18 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.codeBlocksByEditorUri.delete(ref.object.uri))); + if (ref.object.uri) { + const uri = ref.object.uri; + this.codeBlocksByEditorUri.set(uri, info); + disposables.add(toDisposable(() => this.codeBlocksByEditorUri.delete(uri))); + } } orderedDisposablesList.push(ref); return ref.object.element; @@ -933,7 +933,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - const ref = this._editorPool.get(data); + const ref = this._editorPool.get(); const editorInfo = ref.object; editorInfo.render(data, this._currentLayoutWidth); @@ -1068,41 +1068,28 @@ interface IDisposableReference extends IDisposable { isStale: () => boolean; } -class EditorPool extends Disposable { +export class EditorPool extends Disposable { - private readonly _simpleEditorPool: ResourcePool; - private readonly _localFileEditorPool: ResourcePool; + private readonly _pool: ResourcePool; - public *inUse(): Iterable { - yield* this._simpleEditorPool.inUse; - yield* this._localFileEditorPool.inUse; + public inUse(): Iterable { + return this._pool.inUse; } constructor( - private readonly options: ChatEditorOptions, + options: ChatEditorOptions, delegate: IChatRendererDelegate, overflowWidgetsDomNode: HTMLElement | undefined, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, ) { super(); - this._simpleEditorPool = this._register(new ResourcePool(() => { - return this.instantiationService.createInstance(SimpleCodeBlockPart, this.options, MenuId.ChatCodeBlock, delegate, overflowWidgetsDomNode); - })); - this._localFileEditorPool = this._register(new ResourcePool(() => { - return this.instantiationService.createInstance(LocalFileCodeBlockPart, this.options, MenuId.ChatCodeBlock, delegate, overflowWidgetsDomNode); + this._pool = this._register(new ResourcePool(() => { + return instantiationService.createInstance(CodeBlockPart, options, MenuId.ChatCodeBlock, delegate, overflowWidgetsDomNode); })); } - get(data: ICodeBlockData): IDisposableReference { - return this.getFromPool(data.type === 'localFile' ? this._localFileEditorPool : this._simpleEditorPool); - } - - find(resource: URI): SimpleCodeBlockPart | undefined { - return Array.from(this._simpleEditorPool.inUse).find(part => part.uri?.toString() === resource.toString()); - } - - private getFromPool(pool: ResourcePool): IDisposableReference { - const codeBlock = pool.get(); + get(): IDisposableReference { + const codeBlock = this._pool.get(); let stale = false; return { object: codeBlock, @@ -1110,7 +1097,7 @@ class EditorPool extends Disposable { dispose: () => { codeBlock.reset(); stale = true; - pool.release(codeBlock); + this._pool.release(codeBlock); } }; } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index ab61b6bde4f..b2e1680a28c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -5,37 +5,41 @@ import * as dom from 'vs/base/browser/dom'; import { ITreeContextMenuEvent, ITreeElement } from 'vs/base/browser/ui/tree/tree'; -import { disposableTimeout } from 'vs/base/common/async'; +import { disposableTimeout, timeout } from 'vs/base/common/async'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, MutableDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; import { isDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/chat'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; 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'; +import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { ILogService } from 'vs/platform/log/common/log'; -import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ChatTreeItem, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatWidget, IChatWidgetService, IChatWidgetViewContext, IChatWidgetViewOptions } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { ChatAccessibilityProvider, ChatListDelegate, ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; +import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION, CONTEXT_RESPONSE_FILTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IParsedChatRequest, chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { CodeBlockModelCollection } from 'vs/workbench/contrib/chat/common/codeBlockModelCollection'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; const $ = dom.$; @@ -98,6 +102,7 @@ export class ChatWidget extends Disposable implements IChatWidget { private tree!: WorkbenchObjectTree; private renderer!: ChatListItemRenderer; + private readonly _codeBlockModelCollection: CodeBlockModelCollection; private inputPart!: ChatInputPart; private editorOptions!: ChatEditorOptions; @@ -149,6 +154,7 @@ export class ChatWidget extends Disposable implements IChatWidget { readonly viewContext: IChatWidgetViewContext, private readonly viewOptions: IChatWidgetViewOptions, private readonly styles: IChatWidgetStyles, + @ICodeEditorService codeEditorService: ICodeEditorService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IChatService private readonly chatService: IChatService, @@ -158,13 +164,51 @@ export class ChatWidget extends Disposable implements IChatWidget { @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILogService private readonly _logService: ILogService, - @IThemeService private readonly _themeService: IThemeService + @IThemeService private readonly _themeService: IThemeService, ) { super(); CONTEXT_IN_CHAT_SESSION.bindTo(contextKeyService).set(true); this.requestInProgress = CONTEXT_CHAT_REQUEST_IN_PROGRESS.bindTo(contextKeyService); this._register((chatWidgetService as ChatWidgetService).register(this)); + + this._codeBlockModelCollection = this._register(instantiationService.createInstance(CodeBlockModelCollection)); + + this._register(codeEditorService.registerCodeEditorOpenHandler(async (input: ITextResourceEditorInput, _source: ICodeEditor | null, _sideBySide?: boolean): Promise => { + if (input.resource.scheme !== Schemas.vscodeChatCodeBlock) { + return null; + } + + const responseId = input.resource.path.split('/').at(1); + if (!responseId) { + return null; + } + + const item = this.viewModel?.getItems().find(item => item.id === responseId); + if (!item) { + return null; + } + + this.reveal(item); + + await timeout(0); // wait for list to actually render + + for (const editor of this.renderer.editorsInUse() ?? []) { + if (editor.uri?.toString() === input.resource.toString()) { + const inner = editor.editor; + if (input.options?.selection) { + inner.setSelection({ + startLineNumber: input.options.selection.startLineNumber, + startColumn: input.options.selection.startColumn, + endLineNumber: input.options.selection.startLineNumber ?? input.options.selection.endLineNumber, + endColumn: input.options.selection.startColumn ?? input.options.selection.endColumn + }); + } + return inner; + } + } + return null; + })); } get supportsFileReferences(): boolean { @@ -340,6 +384,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.editorOptions, options, rendererDelegate, + this._codeBlockModelCollection, overflowWidgetsContainer, )); this._register(this.renderer.onDidClickFollowup(item => { @@ -490,8 +535,10 @@ export class ChatWidget extends Disposable implements IChatWidget { throw new Error('Call render() before setModel()'); } + this._codeBlockModelCollection.clear(); + this.container.setAttribute('data-session-id', model.sessionId); - this.viewModel = this.instantiationService.createInstance(ChatViewModel, model); + this.viewModel = this.instantiationService.createInstance(ChatViewModel, model, this._codeBlockModelCollection); this.viewModelDisposables.add(this.viewModel.onDidChange(e => { this.requestInProgress.set(this.viewModel!.requestInProgress); this.onDidChangeItems(); @@ -757,6 +804,8 @@ export class ChatWidget extends Disposable implements IChatWidget { this.inputPart.saveState(); return { inputValue: this.getInput(), inputState: this.collectInputState() }; } + + } export class ChatWidgetService implements IChatWidgetService { diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 8669cadcce7..b6d4500e448 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -9,18 +9,15 @@ import * as dom from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IReference, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IReference } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { generateUuid } from 'vs/base/common/uuid'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { EDITOR_FONT_DEFAULTS, EditorOption, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { ILanguageService } from 'vs/editor/common/languages/language'; -import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -50,27 +47,19 @@ import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/ const $ = dom.$; -interface ICodeBlockDataCommon { - codeBlockIndex: number; - element: unknown; - parentContextKeyService?: IContextKeyService; - hideToolbar?: boolean; -} +export interface ICodeBlockData { + readonly codeBlockIndex: number; + readonly element: unknown; -export interface ISimpleCodeBlockData extends ICodeBlockDataCommon { - type: 'code'; - text: string; - languageId: string; - vulns?: IMarkdownVulnerability[]; -} + readonly textModel: Promise>; + readonly languageId: string; -export interface ILocalFileCodeBlockData extends ICodeBlockDataCommon { - type: 'localFile'; - uri: URI; - range?: Range; -} + readonly vulns?: readonly IMarkdownVulnerability[]; + readonly range?: Range; -export type ICodeBlockData = ISimpleCodeBlockData | ILocalFileCodeBlockData; + readonly parentContextKeyService?: IContextKeyService; + readonly hideToolbar?: boolean; +} /** * Special markdown code block language id used to render a local file. @@ -118,19 +107,20 @@ export interface ICodeBlockActionContext { } -export interface ICodeBlockPart { +export interface ICodeBlockPart { + readonly editor: CodeEditorWidget; readonly onDidChangeContentHeight: Event; readonly element: HTMLElement; - readonly uri: URI; + readonly uri: URI | undefined; layout(width: number): void; - render(data: Data, width: number): Promise; + render(data: ICodeBlockData, width: number): Promise; focus(): void; reset(): unknown; dispose(): void; } const defaultCodeblockPadding = 10; -abstract class BaseCodeBlockPart extends Disposable implements ICodeBlockPart { +export class CodeBlockPart extends Disposable implements ICodeBlockPart { protected readonly _onDidChangeContentHeight = this._register(new Emitter()); public readonly onDidChangeContentHeight = this._onDidChangeContentHeight.event; @@ -138,9 +128,12 @@ abstract class BaseCodeBlockPart extends Disposable protected readonly toolbar: MenuWorkbenchToolBar; private readonly contextKeyService: IContextKeyService; - abstract readonly uri: URI; public readonly element: HTMLElement; + private readonly vulnsButton: Button; + private readonly vulnsListElement: HTMLElement; + + private currentCodeBlockData: ICodeBlockData | undefined; private currentScrollWidth = 0; constructor( @@ -152,7 +145,7 @@ abstract class BaseCodeBlockPart extends Disposable @IContextKeyService contextKeyService: IContextKeyService, @IModelService protected readonly modelService: IModelService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IAccessibilityService private readonly accessibilityService: IAccessibilityService + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, ) { super(); this.element = $('.interactive-result-code-block'); @@ -173,6 +166,13 @@ abstract class BaseCodeBlockPart extends Disposable scrollbar: { alwaysConsumeMouseWheel: false }, + definitionLinkOpensInPeek: false, + gotoLocation: { + multiple: 'goto', + multipleDeclarations: 'goto', + multipleDefinitions: 'goto', + multipleImplementations: 'goto', + }, ariaLabel: localize('chat.codeBlockHelp', 'Code block'), overflowWidgetsDomNode, ...this.getEditorOptionsFromConfig(), @@ -187,6 +187,31 @@ abstract class BaseCodeBlockPart extends Disposable } })); + const vulnsContainer = dom.append(this.element, $('.interactive-result-vulns')); + const vulnsHeaderElement = dom.append(vulnsContainer, $('.interactive-result-vulns-header', undefined)); + this.vulnsButton = new Button(vulnsHeaderElement, { + buttonBackground: undefined, + buttonBorder: undefined, + buttonForeground: undefined, + buttonHoverBackground: undefined, + buttonSecondaryBackground: undefined, + buttonSecondaryForeground: undefined, + buttonSecondaryHoverBackground: undefined, + buttonSeparator: undefined, + supportIcons: true + }); + + this.vulnsListElement = dom.append(vulnsContainer, $('ul.interactive-result-vulns-list')); + + this.vulnsButton.onDidClick(() => { + const element = this.currentCodeBlockData!.element as IChatResponseViewModel; + element.vulnerabilitiesListExpanded = !element.vulnerabilitiesListExpanded; + this.vulnsButton.label = this.getVulnerabilitiesLabel(); + this.element.classList.toggle('chat-vulnerabilities-collapsed', !element.vulnerabilitiesListExpanded); + this._onDidChangeContentHeight.fire(); + // this.updateAriaLabel(collapseButton.element, referencesLabel, element.usedReferencesExpanded); + }); + this._register(this.toolbar.onDidChangeDropdownVisibility(e => { toolbarElement.classList.toggle('force-visibility', e); })); @@ -229,7 +254,27 @@ abstract class BaseCodeBlockPart extends Disposable } } - protected abstract createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): CodeEditorWidget; + get uri(): URI | undefined { + return this.editor.getModel()?.uri; + } + + private createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): CodeEditorWidget { + return this._register(instantiationService.createInstance(CodeEditorWidget, parent, options, { + isSimpleWidget: false, + contributions: EditorExtensionsRegistry.getSomeEditorContributions([ + MenuPreventer.ID, + SelectionClipboardContributionID, + ContextMenuController.ID, + + WordHighlighterContribution.ID, + ViewportSemanticTokensContribution.ID, + BracketMatchingController.ID, + SmartSelectController.ID, + HoverController.ID, + GotoDefinitionAtPositionEditorContribution.ID, + ]) + })); + } focus(): void { this.editor.focus(); @@ -277,17 +322,22 @@ abstract class BaseCodeBlockPart extends Disposable this.updatePaddingForLayout(); } - protected getContentHeight() { + private getContentHeight() { + if (this.currentCodeBlockData?.range) { + const lineCount = this.currentCodeBlockData.range.endLineNumber - this.currentCodeBlockData.range.startLineNumber + 1; + const lineHeight = this.editor.getOption(EditorOption.lineHeight); + return lineCount * lineHeight; + } return this.editor.getContentHeight(); } - async render(data: Data, width: number) { + async render(data: ICodeBlockData, width: number) { if (data.parentContextKeyService) { this.contextKeyService.updateParent(data.parentContextKeyService); } if (this.options.configuration.resultEditor.wordWrap === 'on') { - // Intialize the editor with the new proper width so that getContentHeight + // Initialize the editor with the new proper width so that getContentHeight // will be computed correctly in the next call to layout() this.layout(width); } @@ -302,102 +352,6 @@ abstract class BaseCodeBlockPart extends Disposable } else { dom.show(this.toolbar.getElement()); } - } - - protected abstract updateEditor(data: Data): void | Promise; - - reset() { - this.clearWidgets(); - } - - private clearWidgets() { - HoverController.get(this.editor)?.hideContentHover(); - } -} - - -export class SimpleCodeBlockPart extends BaseCodeBlockPart { - - private readonly vulnsButton: Button; - private readonly vulnsListElement: HTMLElement; - - private currentCodeBlockData: ISimpleCodeBlockData | undefined; - - private readonly textModel: Promise; - - private readonly _uri: URI; - - constructor( - options: ChatEditorOptions, - menuId: MenuId, - delegate: IChatRendererDelegate, - overflowWidgetsDomNode: HTMLElement | undefined, - @IInstantiationService instantiationService: IInstantiationService, - @IContextKeyService contextKeyService: IContextKeyService, - @IModelService modelService: IModelService, - @ITextModelService textModelService: ITextModelService, - @IConfigurationService configurationService: IConfigurationService, - @IAccessibilityService accessibilityService: IAccessibilityService, - @ILanguageService private readonly languageService: ILanguageService, - ) { - super(options, menuId, delegate, overflowWidgetsDomNode, instantiationService, contextKeyService, modelService, configurationService, accessibilityService); - - const vulnsContainer = dom.append(this.element, $('.interactive-result-vulns')); - const vulnsHeaderElement = dom.append(vulnsContainer, $('.interactive-result-vulns-header', undefined)); - this.vulnsButton = new Button(vulnsHeaderElement, { - buttonBackground: undefined, - buttonBorder: undefined, - buttonForeground: undefined, - buttonHoverBackground: undefined, - buttonSecondaryBackground: undefined, - buttonSecondaryForeground: undefined, - buttonSecondaryHoverBackground: undefined, - buttonSeparator: undefined, - supportIcons: true - }); - this._uri = URI.from({ scheme: Schemas.vscodeChatCodeBlock, path: generateUuid() }); - this.textModel = textModelService.createModelReference(this._uri).then(ref => { - this.editor.setModel(ref.object.textEditorModel); - this._register(ref); - return ref.object.textEditorModel; - }); - - this.vulnsListElement = dom.append(vulnsContainer, $('ul.interactive-result-vulns-list')); - - this.vulnsButton.onDidClick(() => { - const element = this.currentCodeBlockData!.element as IChatResponseViewModel; - element.vulnerabilitiesListExpanded = !element.vulnerabilitiesListExpanded; - this.vulnsButton.label = this.getVulnerabilitiesLabel(); - this.element.classList.toggle('chat-vulnerabilities-collapsed', !element.vulnerabilitiesListExpanded); - this._onDidChangeContentHeight.fire(); - // this.updateAriaLabel(collapseButton.element, referencesLabel, element.usedReferencesExpanded); - }); - } - - get uri(): URI { - return this._uri; - } - - protected override createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): CodeEditorWidget { - return this._register(instantiationService.createInstance(CodeEditorWidget, parent, options, { - isSimpleWidget: false, - contributions: EditorExtensionsRegistry.getSomeEditorContributions([ - MenuPreventer.ID, - SelectionClipboardContributionID, - ContextMenuController.ID, - - WordHighlighterContribution.ID, - ViewportSemanticTokensContribution.ID, - BracketMatchingController.ID, - SmartSelectController.ID, - HoverController.ID, - GotoDefinitionAtPositionEditorContribution.ID, - ]) - })); - } - - override async render(data: ISimpleCodeBlockData, width: number): Promise { - await super.render(data, width); if (data.vulns?.length && isResponseVM(data.element)) { dom.clearNode(this.vulnsListElement); @@ -410,20 +364,27 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart } } - protected override async updateEditor(data: ISimpleCodeBlockData): Promise { - this.editor.setModel(await this.textModel); - const text = this.fixCodeText(data.text, data.languageId); - this.setText(text); + reset() { + this.clearWidgets(); + } - const vscodeLanguageId = this.languageService.getLanguageIdByLanguageName(data.languageId) ?? undefined; - this.setLanguage(vscodeLanguageId); - data.languageId = vscodeLanguageId ?? 'plaintext'; + private clearWidgets() { + HoverController.get(this.editor)?.hideContentHover(); + } + + private async updateEditor(data: ICodeBlockData): Promise { + const textModel = (await data.textModel).object.textEditorModel; + this.editor.setModel(textModel); + if (data.range) { + this.editor.setSelection(data.range); + this.editor.revealRangeInCenter(data.range, ScrollType.Immediate); + } this.toolbar.context = { - code: data.text, + code: textModel.getTextBuffer().getValueInRange(data.range ?? textModel.getFullModelRange(), EndOfLinePreference.TextDefined), codeBlockIndex: data.codeBlockIndex, element: data.element, - languageId: data.languageId + languageId: textModel.getLanguageId() } satisfies ICodeBlockActionContext; } @@ -438,110 +399,8 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart const icon = (element: IChatResponseViewModel) => element.vulnerabilitiesListExpanded ? Codicon.chevronDown : Codicon.chevronRight; return `${referencesLabel} $(${icon(this.currentCodeBlockData.element as IChatResponseViewModel).id})`; } - - private fixCodeText(text: string, languageId: string): string { - if (languageId === 'php') { - if (!text.trim().startsWith('<')) { - return ``; - } - } - - return text; - } - - private async setText(newText: string): Promise { - const model = await this.textModel; - const currentText = model.getValue(EndOfLinePreference.LF); - if (newText === currentText) { - return; - } - - if (newText.startsWith(currentText)) { - const text = newText.slice(currentText.length); - const lastLine = model.getLineCount(); - const lastCol = model.getLineMaxColumn(lastLine); - model.applyEdits([{ range: new Range(lastLine, lastCol, lastLine, lastCol), text }]); - } else { - // console.log(`Failed to optimize setText`); - model.setValue(newText); - } - } - - private async setLanguage(vscodeLanguageId: string | undefined): Promise { - (await this.textModel).setLanguage(vscodeLanguageId ?? PLAINTEXT_LANGUAGE_ID); - } } -export class LocalFileCodeBlockPart extends BaseCodeBlockPart { - - private readonly textModelReference = this._register(new MutableDisposable>()); - private currentCodeBlockData?: ILocalFileCodeBlockData; - - constructor( - options: ChatEditorOptions, - menuId: MenuId, - delegate: IChatRendererDelegate, - overflowWidgetsDomNode: HTMLElement | undefined, - @IInstantiationService instantiationService: IInstantiationService, - @IContextKeyService contextKeyService: IContextKeyService, - @IModelService modelService: IModelService, - @ITextModelService private readonly textModelService: ITextModelService, - @IConfigurationService configurationService: IConfigurationService, - @IAccessibilityService accessibilityService: IAccessibilityService - ) { - super(options, menuId, delegate, overflowWidgetsDomNode, instantiationService, contextKeyService, modelService, configurationService, accessibilityService); - } - - get uri(): URI { - return this.currentCodeBlockData!.uri; - } - - protected override getContentHeight() { - if (this.currentCodeBlockData?.range) { - const lineCount = this.currentCodeBlockData.range.endLineNumber - this.currentCodeBlockData.range.startLineNumber + 1; - const lineHeight = this.editor.getOption(EditorOption.lineHeight); - return lineCount * lineHeight; - } - return super.getContentHeight(); - } - - protected override createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): CodeEditorWidget { - return this._register(instantiationService.createInstance(CodeEditorWidget, parent, { - ...options, - }, { - // TODO: be more selective about contributions - })); - } - - protected override async updateEditor(data: ILocalFileCodeBlockData): Promise { - let model: ITextModel; - if (this.currentCodeBlockData?.uri.toString() === data.uri.toString()) { - this.currentCodeBlockData = data; - model = this.editor.getModel()!; - } else { - this.currentCodeBlockData = data; - const result = await this.textModelService.createModelReference(data.uri); - model = result.object.textEditorModel; - this.textModelReference.value = result; - this.editor.setModel(model); - } - - - if (data.range) { - this.editor.setSelection(data.range); - this.editor.revealRangeInCenter(data.range, ScrollType.Immediate); - } - - this.toolbar.context = { - code: model.getTextBuffer().getValueInRange(data.range ?? model.getFullModelRange(), EndOfLinePreference.TextDefined), - codeBlockIndex: data.codeBlockIndex, - element: data.element, - languageId: model.getLanguageId() - } satisfies ICodeBlockActionContext; - } -} - - export class ChatCodeBlockContentProvider extends Disposable implements ITextModelContentProvider { constructor( diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index a91512a6840..fc1328ca803 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -5,14 +5,19 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { marked } from 'vs/base/common/marked/marked'; import { URI } from 'vs/base/common/uri'; +import { Range } from 'vs/editor/common/core/range'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { EndOfLinePreference } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { IChatAgentCommand, IChatAgentData, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModelInitState, IChatModel, IChatRequestModel, IChatResponseModel, IChatWelcomeMessageContent, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatContentReference, IChatProgressMessage, IChatFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection, IChatCommandButton } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatCommandButton, IChatContentReference, IChatFollowup, IChatProgressMessage, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; +import { CodeBlockModelCollection } from './codeBlockModelCollection'; export function isRequestVM(item: unknown): item is IChatRequestViewModel { return !!item && typeof item === 'object' && 'message' in item; @@ -134,6 +139,7 @@ export interface IChatResponseViewModel { } export class ChatViewModel extends Disposable implements IChatViewModel { + private readonly _onDidDisposeModel = this._register(new Emitter()); readonly onDidDisposeModel = this._onDidDisposeModel.event; @@ -179,12 +185,17 @@ export class ChatViewModel extends Disposable implements IChatViewModel { constructor( private readonly _model: IChatModel, + public readonly codeBlockModelCollection: CodeBlockModelCollection, @IInstantiationService private readonly instantiationService: IInstantiationService, + @ILanguageService private readonly languageService: ILanguageService, ) { super(); _model.getRequests().forEach((request, i) => { - this._items.push(new ChatRequestViewModel(request)); + const requestModel = new ChatRequestViewModel(request); + this._items.push(requestModel); + this.updateCodeBlockTextModels(requestModel); + if (request.response) { this.onAddResponse(request.response); } @@ -193,7 +204,10 @@ export class ChatViewModel extends Disposable implements IChatViewModel { this._register(_model.onDidDispose(() => this._onDidDisposeModel.fire())); this._register(_model.onDidChange(e => { if (e.kind === 'addRequest') { - this._items.push(new ChatRequestViewModel(e.request)); + const requestModel = new ChatRequestViewModel(e.request); + this._items.push(requestModel); + this.updateCodeBlockTextModels(requestModel); + if (e.request.response) { this.onAddResponse(e.request.response); } @@ -224,8 +238,12 @@ export class ChatViewModel extends Disposable implements IChatViewModel { private onAddResponse(responseModel: IChatResponseModel) { const response = this.instantiationService.createInstance(ChatResponseViewModel, responseModel); - this._register(response.onDidChange(() => this._onDidChange.fire(null))); + this._register(response.onDidChange(() => { + this.updateCodeBlockTextModels(response); + return this._onDidChange.fire(null); + })); this._items.push(response); + this.updateCodeBlockTextModels(response); } getItems(): (IChatRequestViewModel | IChatResponseViewModel | IChatWelcomeMessageViewModel)[] { @@ -238,6 +256,79 @@ export class ChatViewModel extends Disposable implements IChatViewModel { .filter((item): item is ChatResponseViewModel => item instanceof ChatResponseViewModel) .forEach((item: ChatResponseViewModel) => item.dispose()); } + + private updateCodeBlockTextModels(model: IChatRequestViewModel | IChatResponseViewModel) { + const content = isRequestVM(model) ? model.messageText : model.response.asString(); + const renderer = new marked.Renderer(); + + let codeBlockIndex = 0; + renderer.code = (value, languageId) => { + languageId ??= ''; + const newText = this.fixCodeText(value, languageId); + const textModel = this.codeBlockModelCollection.getOrCreate(model.id, codeBlockIndex++); + textModel.then(ref => { + const model = ref.object.textEditorModel; + if (languageId) { + const vscodeLanguageId = this.languageService.getLanguageIdByLanguageName(languageId); + if (vscodeLanguageId && vscodeLanguageId !== ref.object.textEditorModel.getLanguageId()) { + ref.object.textEditorModel.setLanguage(vscodeLanguageId); + } + } + + const currentText = ref.object.textEditorModel.getValue(EndOfLinePreference.LF); + if (newText === currentText) { + return; + } + + if (newText.startsWith(currentText)) { + const text = newText.slice(currentText.length); + const lastLine = model.getLineCount(); + const lastCol = model.getLineMaxColumn(lastLine); + model.applyEdits([{ range: new Range(lastLine, lastCol, lastLine, lastCol), text }]); + } else { + // console.log(`Failed to optimize setText`); + model.setValue(newText); + } + }); + return ''; + }; + + marked.parse(this.ensureFencedCodeBlocksTerminated(content), { renderer }); + } + + private fixCodeText(text: string, languageId: string): string { + if (languageId === 'php') { + if (!text.trim().startsWith('<')) { + return ``; + } + } + + return text; + } + + /** + * Marked doesn't consistently render fenced code blocks that aren't terminated. + * + * Try to close them ourselves to workaround this. + */ + private ensureFencedCodeBlocksTerminated(content: string): string { + const lines = content.split('\n'); + let inCodeBlock = false; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (line.startsWith('```')) { + inCodeBlock = !inCodeBlock; + } + } + + // If we're still in a code block at the end of the content, add a closing fence + if (inCodeBlock) { + lines.push('```'); + } + + return lines.join('\n'); + } } export class ChatRequestViewModel implements IChatRequestViewModel { diff --git a/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts b/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts new file mode 100644 index 00000000000..edf5b4ae445 --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.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 { Disposable, IReference } from 'vs/base/common/lifecycle'; +import { ResourceMap } from 'vs/base/common/map'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; + + +export class CodeBlockModelCollection extends Disposable { + + private readonly _models = new ResourceMap>>(); + + constructor( + @ITextModelService private readonly textModelService: ITextModelService + ) { + super(); + } + + public override dispose(): void { + super.dispose(); + this.clear(); + } + + get(responseId: string, codeBlockIndex: number): Promise> | undefined { + const uri = this.getUri(responseId, codeBlockIndex); + return this._models.get(uri); + } + + getOrCreate(responseId: string, codeBlockIndex: number): Promise> { + const existing = this.get(responseId, codeBlockIndex); + if (existing) { + return existing; + } + + const uri = this.getUri(responseId, codeBlockIndex); + const ref = this.textModelService.createModelReference(uri); + this._models.set(uri, ref); + return ref; + } + + clear(): void { + this._models.forEach(async (model) => (await model).dispose()); + this._models.clear(); + } + + private getUri(responseId: string, index: number): URI { + return URI.from({ scheme: Schemas.vscodeChatCodeBlock, path: `/${responseId}/${index}` }); + } +} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 04d8fe1ff4f..5af8f2acc53 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -62,6 +62,7 @@ import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/cha import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { CodeBlockModelCollection } from 'vs/workbench/contrib/chat/common/codeBlockModelCollection'; import { ExpansionState, HunkData, HunkInformation, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_VISIBLE, IInlineChatFollowup, IInlineChatSlashCommand, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_FEEDBACK, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -240,6 +241,7 @@ export class InlineChatWidget { private _slashCommandUsedDisposables = this._store.add(new DisposableStore()); private _chatMessage: MarkdownString | undefined; + private readonly _codeBlockModelCollection: CodeBlockModelCollection; constructor( private readonly parentEditor: ICodeEditor, @@ -451,6 +453,9 @@ export class InlineChatWidget { this._elements.followUps.ariaLabel = this._accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.InlineChat); } })); + + // Code block rendering + this._codeBlockModelCollection = this._store.add(this._instantiationService.createInstance(CodeBlockModelCollection)); } @@ -615,7 +620,7 @@ export class InlineChatWidget { const viewModel = this._chatMessageDisposables.add(new ChatResponseViewModel(responseModel, this._logService)); const renderOptions: IChatListItemRendererOptions = { renderStyle: 'compact', noHeader: true, noPadding: true }; const chatRendererDelegate: IChatRendererDelegate = { getListLength() { return 1; } }; - const renderer = this._chatMessageDisposables.add(this._instantiationService.createInstance(ChatListItemRenderer, this._editorOptions, renderOptions, chatRendererDelegate, undefined)); + const renderer = this._chatMessageDisposables.add(this._instantiationService.createInstance(ChatListItemRenderer, this._editorOptions, renderOptions, chatRendererDelegate, this._codeBlockModelCollection, undefined)); renderer.layout(this._elements.chatMessageContent.clientWidth - 4); // 2 for the padding used for the tab index border this._chatMessageDisposables.add(this._onDidChangeLayout.event(() => { renderer.layout(this._elements.chatMessageContent.clientWidth - 4); From f9377b87afb68830547acf13a81119b6e07f1b5b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:29:51 -0800 Subject: [PATCH 1614/1897] clearMarker -> clear --- src/vs/workbench/contrib/terminal/browser/terminal.ts | 2 +- .../contrib/terminal/browser/xterm/markNavigationAddon.ts | 2 +- .../terminalContrib/links/browser/terminalLinkQuickpick.ts | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index d8d7ebeb111..db2791f9bda 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -113,7 +113,7 @@ export interface IMarkTracker { selectToNextMark(): void; selectToPreviousLine(): void; selectToNextLine(): void; - clearMarker(): void; + clear(): void; scrollToClosestMarker(startMarkerId: string, endMarkerId?: string, highlight?: boolean | undefined): void; scrollToLine(line: number, position: ScrollPosition): void; diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts index aae4ac6f275..9a2fbf8e6d7 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts @@ -101,7 +101,7 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe return undefined; } - clearMarker(): void { + clear(): void { // Clear the current marker so successive focus/selection actions are performed from the // bottom of the buffer this._currentMarker = Boundary.Bottom; diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts index 544612bcb53..e3bd74aec3b 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts @@ -140,8 +140,7 @@ export class TerminalLinkQuickpick extends DisposableStore { const markTracker = this._instance?.xterm?.markTracker; if (markTracker) { markTracker.restoreScrollState(); - // TODO: This name isn't great - markTracker.clearMarker(); + markTracker.clear(); this._terminalScrollStateSaved = false; } } From 12633b44242d02ff0079ddf5c12272669d1c19db Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:58:57 -0800 Subject: [PATCH 1615/1897] Ignore links with text that equals the empty string fixes #206258 --- .../terminalContrib/links/browser/terminalLinkParsing.ts | 5 +++++ .../links/test/browser/terminalLinkParsing.test.ts | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts index 8328315b957..94dbbd2f5cd 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts @@ -277,6 +277,11 @@ function detectLinksViaSuffix(line: string): IParsedLink[] { }; path = path.substring(prefix.text.length); + // Don't allow suffix links to be returned when the link itself is the empty string + if (path.trim().length === 0) { + continue; + } + // If there are multiple characters in the prefix, trim the prefix if the _first_ // suffix character is the same as the last prefix character. For example, for the // text `echo "'foo' on line 1"`: diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts index 71d641d0739..04e4aeb5e9b 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts @@ -706,5 +706,11 @@ suite('TerminalLinkParsing', () => { }); } }); + suite('should ignore links with suffixes when the path itself is the empty string', () => { + deepStrictEqual( + detectLinks('""",1', OperatingSystem.Linux), + [] as IParsedLink[] + ); + }); }); }); From 9b3f22b333b4bb4d065abb787411411494f7c45b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 26 Feb 2024 12:03:16 -0800 Subject: [PATCH 1616/1897] only allow starting chat when terminal agent has been registered --- .../contrib/terminalContrib/chat/browser/terminalChatActions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 04ff3b3a472..878246a2e26 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -28,6 +28,7 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + TerminalChatContextKeys.agentRegistered ), run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { From 51209b3a1448ff6df84c973ca1ab786fa3a99d72 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 26 Feb 2024 12:26:30 -0800 Subject: [PATCH 1617/1897] on hide, reset input value --- .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index aa43972ef64..74bb3b4ee9d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -132,6 +132,7 @@ export class TerminalChatWidget extends Disposable { this._inlineChatWidget.updateToolbar(false); this._focusedContextKey.set(false); this._visibleContextKey.set(false); + this._inlineChatWidget.value = ''; this._instance.focus(); } focus(): void { From a1070cb7f172f4930edfe3aa38f7b473c85169f4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 26 Feb 2024 12:40:34 -0800 Subject: [PATCH 1618/1897] set vertical position to below cursor line --- .../terminalContrib/chat/browser/terminalChatWidget.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 74bb3b4ee9d..26efec69639 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -112,9 +112,9 @@ export class TerminalChatWidget extends Disposable { if (!font?.charHeight) { return; } - const cursorY = this._instance.xterm?.raw.buffer.active.cursorY ?? 0; + const cursorY = (this._instance.xterm?.raw.buffer.active.cursorY ?? 0) + 1; const height = font.charHeight * font.lineHeight; - const top = cursorY * height + 10; + const top = cursorY * height + 12; this._container.style.top = `${top}px`; const terminalHeight = this._instance.domElement.clientHeight; if (terminalHeight && top > terminalHeight - this._inlineChatWidget.getHeight()) { From 6b6482cf322cc31960b4c8007d77b31e120167c6 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 26 Feb 2024 16:12:49 -0800 Subject: [PATCH 1619/1897] fix: command quoting for wt.exe (#206305) Fixes #204039 --- .../externalTerminal/node/externalTerminalService.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/externalTerminal/node/externalTerminalService.ts b/src/vs/platform/externalTerminal/node/externalTerminalService.ts index a8df823266a..5086c95a802 100644 --- a/src/vs/platform/externalTerminal/node/externalTerminalService.ts +++ b/src/vs/platform/externalTerminal/node/externalTerminalService.ts @@ -80,8 +80,7 @@ export class WindowsExternalTerminalService extends ExternalTerminalService impl return new Promise((resolve, reject) => { const title = `"${dir} - ${TERMINAL_TITLE}"`; - const command = `""${args.join('" "')}" & pause"`; // use '|' to only pause on non-zero exit code - + const command = `"${args.join('" "')}" & pause`; // use '|' to only pause on non-zero exit code // merge environment variables into a copy of the process.env const env = Object.assign({}, getSanitizedEnvironment(process), envVars); @@ -110,7 +109,7 @@ export class WindowsExternalTerminalService extends ExternalTerminalService impl cmdArgs = ['-d', '.', exec, '/c', command]; } else { spawnExec = WindowsExternalTerminalService.CMD; - cmdArgs = ['/c', 'start', title, '/wait', exec, '/c', command]; + cmdArgs = ['/c', 'start', title, '/wait', exec, '/c', `"${command}"`]; } const cmd = cp.spawn(spawnExec, cmdArgs, options); From 150d9e622b41ac36e186a6d7196dbaa36ccd5254 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 26 Feb 2024 18:39:38 -0800 Subject: [PATCH 1620/1897] debug: cleanup cancellation tokens in inline value preview (#206307) Also fixes a race where we could have outdated decorations show Refs #205966 --- .../test/browser/mainThreadWorkspace.test.ts | 12 ++++----- .../test/browser/bulkCellEdits.test.ts | 4 +-- .../debug/browser/debugEditorContribution.ts | 27 +++++++++++-------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts b/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts index 2cc5a637a6a..5234d7abb34 100644 --- a/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -41,7 +41,7 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch(null, { maxResults: 10, includePattern: 'foo', disregardSearchExcludeSettings: true }, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: 'foo', disregardSearchExcludeSettings: true }, CancellationToken.None); }); test('exclude defaults', () => { @@ -63,7 +63,7 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardSearchExcludeSettings: true }, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardSearchExcludeSettings: true }, CancellationToken.None); }); test('disregard excludes', () => { @@ -84,7 +84,7 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardSearchExcludeSettings: true, disregardExcludeSettings: true }, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardSearchExcludeSettings: true, disregardExcludeSettings: true }, CancellationToken.None); }); test('do not disregard anything if disregardExcludeSettings is true', () => { @@ -106,7 +106,7 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardExcludeSettings: true, disregardSearchExcludeSettings: false }, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardExcludeSettings: true, disregardSearchExcludeSettings: false }, CancellationToken.None); }); test('exclude string', () => { @@ -120,6 +120,6 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', excludePattern: 'exclude/**', disregardSearchExcludeSettings: true }, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', excludePattern: 'exclude/**', disregardSearchExcludeSettings: true }, CancellationToken.None); }); }); diff --git a/src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts b/src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts index 76497826d74..22c507ca0b3 100644 --- a/src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts +++ b/src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; import { mockObject } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; @@ -35,7 +35,7 @@ suite('BulkCellEdits', function () { const edits = [ new ResourceNotebookCellEdit(inputUri, { index: 0, count: 1, editType: CellEditType.Replace, cells: [] }) ]; - const bce = new BulkCellEdits(new UndoRedoGroup(), new UndoRedoSource(), progress, new CancellationTokenSource().token, edits, editorService, notebookService as any); + const bce = new BulkCellEdits(new UndoRedoGroup(), new UndoRedoSource(), progress, CancellationToken.None, edits, editorService, notebookService as any); await bce.apply(); const resolveArgs = notebookService.resolve.args[0]; diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index a800e241511..30789475e42 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -6,7 +6,7 @@ import { addDisposableListener, isKeyboardEvent } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { distinct, flatten } from 'vs/base/common/arrays'; +import { distinct } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { memoize } from 'vs/base/common/decorators'; @@ -15,7 +15,7 @@ import { Event } from 'vs/base/common/event'; import { visit } from 'vs/base/common/json'; import { setProperty } from 'vs/base/common/jsonEdit'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { IDisposable, MutableDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, MutableDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { clamp } from 'vs/base/common/numbers'; import { basename } from 'vs/base/common/path'; import * as env from 'vs/base/common/platform'; @@ -217,6 +217,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { private altListener = new MutableDisposable(); private altPressed = false; private oldDecorations = this.editor.createDecorationsCollection(); + private displayedStore = new DisposableStore(); private editorHoverOptions: IEditorHoverOptions | undefined; private readonly debounceInfo: IFeatureDebounceInformation; @@ -237,7 +238,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { ) { this.debounceInfo = featureDebounceService.for(languageFeaturesService.inlineValuesProvider, 'InlineValues', { min: DEAFULT_INLINE_DEBOUNCE_DELAY }); this.hoverWidget = this.instantiationService.createInstance(DebugHoverWidget, this.editor); - this.toDispose = [this.defaultHoverLockout, this.altListener]; + this.toDispose = [this.defaultHoverLockout, this.altListener, this.displayedStore]; this.registerListeners(); this.exceptionWidgetVisible = CONTEXT_EXCEPTION_WIDGET_VISIBLE.bindTo(contextKeyService); this.toggleExceptionWidget(); @@ -639,7 +640,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { private get removeInlineValuesScheduler(): RunOnceScheduler { return new RunOnceScheduler( () => { - this.oldDecorations.clear(); + this.displayedStore.clear(); }, 100 ); @@ -670,10 +671,14 @@ export class DebugEditorContribution implements IDebugEditorContribution { } this.removeInlineValuesScheduler.cancel(); + this.displayedStore.clear(); const viewRanges = this.editor.getVisibleRangesPlusViewportAboveBelow(); let allDecorations: IModelDeltaDecoration[]; + const cts = new CancellationTokenSource(); + this.displayedStore.add(toDisposable(() => cts.dispose(true))); + if (this.languageFeaturesService.inlineValuesProvider.has(model)) { const findVariable = async (_key: string, caseSensitiveLookup: boolean): Promise => { @@ -693,14 +698,13 @@ export class DebugEditorContribution implements IDebugEditorContribution { frameId: stackFrame.frameId, stoppedLocation: new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn + 1, stackFrame.range.endLineNumber, stackFrame.range.endColumn + 1) }; - const token = new CancellationTokenSource().token; const providers = this.languageFeaturesService.inlineValuesProvider.ordered(model).reverse(); allDecorations = []; const lineDecorations = new Map(); - const promises = flatten(providers.map(provider => viewRanges.map(range => Promise.resolve(provider.provideInlineValues(model, range, ctx, token)).then(async (result) => { + const promises = providers.flatMap(provider => viewRanges.map(range => Promise.resolve(provider.provideInlineValues(model, range, ctx, cts.token)).then(async (result) => { if (result) { for (const iv of result) { @@ -753,7 +757,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { } }, err => { onUnexpectedExternalError(err); - })))); + }))); const startTime = Date.now(); @@ -794,12 +798,15 @@ export class DebugEditorContribution implements IDebugEditorContribution { return createInlineValueDecorationsInsideRange(variables, ownRanges, model, this._wordToLineNumbersMap.value); })); - allDecorations = distinct(decorationsPerScope.reduce((previous, current) => previous.concat(current), []), + allDecorations = distinct(decorationsPerScope.flat(), // Deduplicate decorations since same variable can appear in multiple scopes, leading to duplicated decorations #129770 decoration => `${decoration.range.startLineNumber}:${decoration?.options.after?.content}`); } - this.oldDecorations.set(allDecorations); + if (!cts.token.isCancellationRequested) { + this.oldDecorations.set(allDecorations); + this.displayedStore.add(toDisposable(() => this.oldDecorations.clear())); + } } dispose(): void { @@ -810,8 +817,6 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.configurationWidget.dispose(); } this.toDispose = dispose(this.toDispose); - - this.oldDecorations.clear(); } } From 6e1561e0e58a44c2e37293532ad50ac327d9a1b6 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 27 Feb 2024 07:27:54 +0100 Subject: [PATCH 1621/1897] editors - introduce `transient` editor state (#205530) --- src/vs/platform/editor/common/editor.ts | 10 ++ .../api/browser/mainThreadEditorTabs.ts | 3 + .../browser/parts/editor/editorGroupView.ts | 14 ++- src/vs/workbench/common/editor.ts | 1 + .../common/editor/editorGroupModel.ts | 75 +++++++++++- .../common/editor/filteredEditorGroupModel.ts | 1 + .../quickTextSearch/textSearchQuickAccess.ts | 18 +-- .../links/browser/terminalLinkQuickpick.ts | 25 +--- .../editor/common/editorGroupsService.ts | 18 +++ .../test/browser/editorGroupsService.test.ts | 66 +++++++++++ .../history/browser/historyService.ts | 32 ++--- .../services/history/common/history.ts | 6 - .../test/browser/historyService.test.ts | 109 ++---------------- .../editor/filteredEditorGroupModel.test.ts | 22 ++++ .../test/browser/workbenchTestServices.ts | 2 + .../test/common/workbenchTestServices.ts | 1 - 16 files changed, 235 insertions(+), 168 deletions(-) diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index 24e2e4c5506..51060787d4a 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -288,6 +288,16 @@ export interface IEditorOptions { * applied when opening the editor. */ viewState?: object; + + /** + * A transient editor will attempt to appear as preview and certain components + * (such as history tracking) may decide to ignore the editor when it becomes + * active. + * This option is meant to be used only when the editor is used for a short + * period of time, for example when opening a preview of the editor from a + * picker control in the background while navigating through results of the picker. + */ + transient?: boolean; } export interface ITextEditorSelection { diff --git a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts index d23f8e91fd5..41ed2e74e2c 100644 --- a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts +++ b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts @@ -553,6 +553,9 @@ export class MainThreadEditorTabs implements MainThreadEditorTabsShape { this._onDidTabPreviewChange(groupId, event.editorIndex, event.editor); break; } + case GroupModelChangeKind.EDITOR_TRANSIENT: + // Currently not exposed in the API + break; case GroupModelChangeKind.EDITOR_MOVE: if (isGroupEditorMoveEvent(event) && event.editor && event.editorIndex !== undefined && event.oldEditorIndex !== undefined) { this._onDidTabMove(groupId, event.editorIndex, event.oldEditorIndex, event.editor); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index f0bdfdd5bc9..e0761b1f1db 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -886,6 +886,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this.model.isSticky(editorOrIndex); } + isTransient(editorOrIndex: EditorInput | number): boolean { + return this.model.isTransient(editorOrIndex); + } + isActive(editor: EditorInput | IUntypedEditorInput): boolean { return this.model.isActive(editor); } @@ -1004,6 +1008,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } } + setTransient(candidate: EditorInput | undefined, transient: boolean): void { + const editor = candidate ?? this.activeEditor; + if (editor) { + this.model.setTransient(editor, transient); + } + } + //#endregion //#region openEditor() @@ -1033,7 +1044,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Determine options const pinned = options?.sticky - || !this.groupsView.partOptions.enablePreview + || (!this.groupsView.partOptions.enablePreview && !options?.transient) || editor.isDirty() || (options?.pinned ?? typeof options?.index === 'number' /* unless specified, prefer to pin when opening with index */) || (typeof options?.index === 'number' && this.model.isSticky(options.index)) @@ -1042,6 +1053,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { index: options ? options.index : undefined, pinned, sticky: options?.sticky || (typeof options?.index === 'number' && this.model.isSticky(options.index)), + transient: !!options?.transient, active: this.count === 0 || !options || !options.inactive, supportSideBySide: internalOptions?.supportSideBySide }; diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 9652c2dfe00..c27b43d382c 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1133,6 +1133,7 @@ export const enum GroupModelChangeKind { EDITOR_LABEL, EDITOR_CAPABILITIES, EDITOR_PIN, + EDITOR_TRANSIENT, EDITOR_STICKY, EDITOR_DIRTY, EDITOR_WILL_DISPOSE diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 8a018234256..17c5ab59c26 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -22,7 +22,8 @@ const EditorOpenPositioning = { export interface IEditorOpenOptions { readonly pinned?: boolean; - sticky?: boolean; + readonly sticky?: boolean; + readonly transient?: boolean; active?: boolean; readonly index?: number; readonly supportSideBySide?: SideBySideEditor.ANY | SideBySideEditor.BOTH; @@ -180,6 +181,7 @@ export interface IReadonlyEditorGroupModel { isActive(editor: EditorInput | IUntypedEditorInput): boolean; isPinned(editorOrIndex: EditorInput | number): boolean; isSticky(editorOrIndex: EditorInput | number): boolean; + isTransient(editorOrIndex: EditorInput | number): boolean; isFirst(editor: EditorInput, editors?: EditorInput[]): boolean; isLast(editor: EditorInput, editors?: EditorInput[]): boolean; findEditor(editor: EditorInput | null, options?: IMatchEditorOptions): [EditorInput, number /* index */] | undefined; @@ -217,6 +219,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { private preview: EditorInput | null = null; // editor in preview state private active: EditorInput | null = null; // editor in active state private sticky = -1; // index of first editor in sticky state + private transient = new Set(); // editors in transient state private editorOpenPositioning: ('left' | 'right' | 'first' | 'last') | undefined; private focusRecentEditorAfterClose: boolean | undefined; @@ -295,6 +298,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { openEditor(candidate: EditorInput, options?: IEditorOpenOptions): IEditorOpenResult { const makeSticky = options?.sticky || (typeof options?.index === 'number' && this.isSticky(options.index)); const makePinned = options?.pinned || options?.sticky; + const makeTransient = !!options?.transient; const makeActive = options?.active || !this.activeEditor || (!makePinned && this.matches(this.preview, this.activeEditor)); const existingEditorAndIndex = this.findEditor(candidate, options); @@ -381,6 +385,11 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { this.preview = newEditor; } + // Handle transient + if (makeTransient) { + this.doSetTransient(newEditor, targetIndex, true); + } + // Listeners this.registerEditorListeners(newEditor); @@ -412,6 +421,9 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { this.doPin(existingEditor, existingEditorIndex); } + // Update transient + this.doSetTransient(existingEditor, existingEditorIndex, makeTransient); + // Activate it if (makeActive) { this.doSetActive(existingEditor, existingEditorIndex); @@ -563,6 +575,9 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { this.preview = null; } + // Remove from transient + this.transient.delete(editor); + // Remove from arrays this.splice(index, true); @@ -860,6 +875,62 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { return index <= this.sticky; } + setTransient(candidate: EditorInput, transient: boolean): EditorInput | undefined { + if (!transient && this.transient.size === 0) { + return; // no transient editor + } + + const res = this.findEditor(candidate); + if (!res) { + return; // not found + } + + const [editor, editorIndex] = res; + + this.doSetTransient(editor, editorIndex, transient); + + return editor; + } + + private doSetTransient(editor: EditorInput, editorIndex: number, transient: boolean): void { + if (transient) { + if (this.transient.has(editor)) { + return; + } + + this.transient.add(editor); + } else { + if (!this.transient.has(editor)) { + return; + } + + this.transient.delete(editor); + } + + // Event + const event: IGroupEditorChangeEvent = { + kind: GroupModelChangeKind.EDITOR_TRANSIENT, + editor, + editorIndex + }; + this._onDidModelChange.fire(event); + } + + isTransient(editorOrIndex: EditorInput | number): boolean { + if (this.transient.size === 0) { + return false; // no transient editor + } + + let editor: EditorInput | undefined; + if (typeof editorOrIndex === 'number') { + editor = this.editors[editorOrIndex]; + } else { + editor = this.findEditor(editorOrIndex)?.[0]; + } + + return !!editor && this.transient.has(editor); + } + private splice(index: number, del: boolean, editor?: EditorInput): void { const editorToDeleteOrReplace = this.editors[index]; @@ -1124,6 +1195,8 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { dispose(Array.from(this.editorListeners)); this.editorListeners.clear(); + this.transient.clear(); + super.dispose(); } } diff --git a/src/vs/workbench/common/editor/filteredEditorGroupModel.ts b/src/vs/workbench/common/editor/filteredEditorGroupModel.ts index 7b427fe5ded..390b19874c8 100644 --- a/src/vs/workbench/common/editor/filteredEditorGroupModel.ts +++ b/src/vs/workbench/common/editor/filteredEditorGroupModel.ts @@ -38,6 +38,7 @@ abstract class FilteredEditorGroupModel extends Disposable implements IReadonlyE get previewEditor(): EditorInput | null { return this.model.previewEditor && this.filter(this.model.previewEditor) ? this.model.previewEditor : null; } isPinned(editorOrIndex: EditorInput | number): boolean { return this.model.isPinned(editorOrIndex); } + isTransient(editorOrIndex: EditorInput | number): boolean { return this.model.isTransient(editorOrIndex); } isSticky(editorOrIndex: EditorInput | number): boolean { return this.model.isSticky(editorOrIndex); } isActive(editor: EditorInput | IUntypedEditorInput): boolean { return this.model.isActive(editor); } diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index 3691e98ec1d..c4e31c3c903 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -30,7 +30,6 @@ import { IPatternInfo, ISearchComplete, ITextQuery, VIEW_ID } from 'vs/workbench import { Event } from 'vs/base/common/event'; import { EditorViewState } from 'vs/workbench/browser/quickaccess'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { Sequencer } from 'vs/base/common/async'; export const TEXT_SEARCH_QUICK_ACCESS_PREFIX = '%'; @@ -84,8 +83,7 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { - // disable and re-enable history service so that we can ignore this history entry - const disposable = this._historyService.suspendTracking(); - try { - await this._editorService.openEditor({ - resource: itemMatch.parent().resource, - options: { preserveFocus: true, revealIfOpened: true, ignoreError: true, selection: itemMatch.range() } - }); - } finally { - disposable.dispose(); - } + await this._editorService.openEditor({ + resource: itemMatch.parent().resource, + options: { transient: true, preserveFocus: true, revealIfOpened: true, ignoreError: true, selection: itemMatch.range() } + }); }); } })); diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts index e3bd74aec3b..bfa7bbfb687 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts @@ -17,11 +17,8 @@ import type { TerminalLink } from 'vs/workbench/contrib/terminalContrib/links/br import { Sequencer, timeout } from 'vs/base/common/async'; import { EditorViewState } from 'vs/workbench/browser/quickaccess'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { getLinkSuffix } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing'; import { TerminalBuiltinLinkType } from 'vs/workbench/contrib/terminalContrib/links/browser/links'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import type { IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; import { ILabelService } from 'vs/platform/label/common/label'; import { basenameOrAuthority, dirname } from 'vs/base/common/resources'; @@ -36,9 +33,7 @@ export class TerminalLinkQuickpick extends DisposableStore { readonly onDidRequestMoreLinks = this._onDidRequestMoreLinks.event; constructor( - @IConfigurationService private readonly _configurationService: IConfigurationService, @IEditorService private readonly _editorService: IEditorService, - @IHistoryService private readonly _historyService: IHistoryService, @ILabelService private readonly _labelService: ILabelService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService @@ -247,12 +242,6 @@ export class TerminalLinkQuickpick extends DisposableStore { return; } - // Don't open if preview editors are disabled as it may open many editor - const config = this._configurationService.getValue(); - if (!config.workbench?.editor?.enablePreview) { - return; - } - this._previewItemInEditor(link); } @@ -267,16 +256,10 @@ export class TerminalLinkQuickpick extends DisposableStore { this._editorViewState.set(); this._editorSequencer.queue(async () => { - // disable and re-enable history service so that we can ignore this history entry - const disposable = this._historyService.suspendTracking(); - try { - await this._editorService.openEditor({ - resource: link.uri, - options: { preserveFocus: true, revealIfOpened: true, ignoreError: true, selection, } - }); - } finally { - disposable.dispose(); - } + await this._editorService.openEditor({ + resource: link.uri, + options: { transient: true, preserveFocus: true, revealIfOpened: true, ignoreError: true, selection, } + }); }); } diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 650df06217d..fd2f81f70e9 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -727,6 +727,11 @@ export interface IEditorGroup { */ isSticky(editorOrIndex: EditorInput | number): boolean; + /** + * Find out if the provided editor or index of editor is transient in the group. + */ + isTransient(editorOrIndex: EditorInput | number): boolean; + /** * Find out if the provided editor is active in the group. */ @@ -832,6 +837,19 @@ export interface IEditorGroup { */ unstickEditor(editor?: EditorInput): void; + /** + * A transient editor will attempt to appear as preview and certain components + * (such as history tracking) may decide to ignore the editor when it becomes + * active. + * This option is meant to be used only when the editor is used for a short + * period of time, for example when opening a preview of the editor from a + * picker control in the background while navigating through results of the picker. + * + * @param editor the editor to update transient state, or the currently active editor + * if unspecified. + */ + setTransient(editor: EditorInput | undefined, transient: boolean): void; + /** * Whether this editor group should be locked or not. * diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index d87cda2d39c..e42c6116bc7 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -477,6 +477,7 @@ suite('EditorGroupsService', () => { const editorCloseEvents: IGroupModelChangeEvent[] = []; let editorPinCounter = 0; let editorStickyCounter = 0; + let editorTransientCounter = 0; let editorCapabilitiesCounter = 0; const editorGroupModelChangeListener = group.onDidModelChange(e => { if (e.kind === GroupModelChangeKind.EDITOR_OPEN) { @@ -489,6 +490,9 @@ suite('EditorGroupsService', () => { } else if (e.kind === GroupModelChangeKind.EDITOR_STICKY) { assert.ok(e.editor); editorStickyCounter++; + } else if (e.kind === GroupModelChangeKind.EDITOR_TRANSIENT) { + assert.ok(e.editor); + editorTransientCounter++; } else if (e.kind === GroupModelChangeKind.EDITOR_CAPABILITIES) { assert.ok(e.editor); editorCapabilitiesCounter++; @@ -593,6 +597,15 @@ suite('EditorGroupsService', () => { group.unstickEditor(input); assert.strictEqual(editorStickyCounter, 2); + assert.strictEqual(group.isTransient(input), false); + assert.strictEqual(editorTransientCounter, 0); + group.setTransient(input, true); + assert.strictEqual(group.isTransient(input), true); + assert.strictEqual(editorTransientCounter, 1); + group.setTransient(input, false); + assert.strictEqual(group.isTransient(input), false); + assert.strictEqual(editorTransientCounter, 2); + editorCloseListener.dispose(); editorWillCloseListener.dispose(); editorDidCloseListener.dispose(); @@ -1817,5 +1830,58 @@ suite('EditorGroupsService', () => { maxiizeGroupEventDisposable.dispose(); }); + test('transient editors - basics', async () => { + const [part] = await createPart(); + const group = part.activeGroup; + + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = createTestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); + + await group.openEditor(input, { pinned: true }); + await group.openEditor(inputInactive, { inactive: true }); + + assert.strictEqual(group.isTransient(input), false); + assert.strictEqual(group.isTransient(inputInactive), false); + + group.setTransient(input, true); + + assert.strictEqual(group.isTransient(input), true); + assert.strictEqual(group.isTransient(inputInactive), false); + + group.setTransient(input, false); + + assert.strictEqual(group.isTransient(input), false); + assert.strictEqual(group.isTransient(inputInactive), false); + + const inputTransient = createTestFileEditorInput(URI.file('foo/bar/transient'), TEST_EDITOR_INPUT_ID); + + await group.openEditor(inputTransient, { transient: true }); + assert.strictEqual(group.isTransient(inputTransient), true); + + await group.openEditor(inputTransient, {}); + assert.strictEqual(group.isTransient(inputTransient), false); + }); + + test('transient editors - overrides enablePreview setting', async function () { + const instantiationService = workbenchInstantiationService(undefined, disposables); + const configurationService = new TestConfigurationService(); + await configurationService.setUserConfiguration('workbench', { 'editor': { 'enablePreview': false } }); + instantiationService.stub(IConfigurationService, configurationService); + + const [part] = await createPart(instantiationService); + + const group = part.activeGroup; + assert.strictEqual(group.isEmpty, true); + + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + + await group.openEditor(input, { pinned: false }); + assert.strictEqual(group.isPinned(input), true); + + await group.openEditor(input2, { transient: true }); + assert.strictEqual(group.isPinned(input2), false); + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts index 108685831ad..3cef7fa9416 100644 --- a/src/vs/workbench/services/history/browser/historyService.ts +++ b/src/vs/workbench/services/history/browser/historyService.ts @@ -12,7 +12,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { GoFilter, GoScope, IHistoryService } from 'vs/workbench/services/history/common/history'; import { FileChangesEvent, IFileService, FileChangeType, FILES_EXCLUDE_CONFIG, FileOperationEvent, FileOperation } from 'vs/platform/files/common/files'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { dispose, Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { dispose, Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Emitter, Event } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -73,31 +73,16 @@ export class HistoryService extends Disposable implements IHistoryService { } } - private trackingSuspended = false; - suspendTracking(): IDisposable { - this.trackingSuspended = true; - - return toDisposable(() => this.trackingSuspended = false); - } - private registerListeners(): void { // Mouse back/forward support this.registerMouseNavigationListener(); // Editor changes - this._register(this.editorService.onDidActiveEditorChange((e) => { - if (!this.trackingSuspended) { - this.onDidActiveEditorChange(); - } - })); + this._register(this.editorService.onDidActiveEditorChange(() => this.onDidActiveEditorChange())); this._register(this.editorService.onDidOpenEditorFail(event => this.remove(event.editor))); this._register(this.editorService.onDidCloseEditor(event => this.onDidCloseEditor(event))); - this._register(this.editorService.onDidMostRecentlyActiveEditorsChange(() => { - if (!this.trackingSuspended) { - this.handleEditorEventInRecentEditorsStack(); - } - })); + this._register(this.editorService.onDidMostRecentlyActiveEditorsChange(() => this.handleEditorEventInRecentEditorsStack())); // Editor group changes this._register(this.editorGroupService.onDidRemoveGroup(e => this.onDidRemoveGroup(e))); @@ -188,14 +173,15 @@ export class HistoryService extends Disposable implements IHistoryService { // Dispose old listeners this.activeEditorListeners.clear(); - // Handle editor change - this.handleActiveEditorChange(activeEditorGroup, activeEditorPane); + // Handle editor change unless the editor is transient + if (!activeEditorPane?.group.isTransient(activeEditorPane.input)) { + this.handleActiveEditorChange(activeEditorGroup, activeEditorPane); + } - // Listen to selection changes if the editor pane - // is having a selection concept. + // Listen to selection changes unless the editor is transient if (isEditorPaneWithSelection(activeEditorPane)) { this.activeEditorListeners.add(activeEditorPane.onDidChangeSelection(e => { - if (!this.trackingSuspended) { + if (!activeEditorPane.group.isTransient(activeEditorPane.input)) { this.handleActiveEditorSelectionChangeEvent(activeEditorGroup, activeEditorPane, e); } })); diff --git a/src/vs/workbench/services/history/common/history.ts b/src/vs/workbench/services/history/common/history.ts index 3d135ec2a70..b5abcd2dad3 100644 --- a/src/vs/workbench/services/history/common/history.ts +++ b/src/vs/workbench/services/history/common/history.ts @@ -8,7 +8,6 @@ import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { GroupIdentifier } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { URI } from 'vs/base/common/uri'; -import { IDisposable } from 'vs/base/common/lifecycle'; export const IHistoryService = createDecorator('historyService'); @@ -138,9 +137,4 @@ export interface IHistoryService { * Clear list of recently opened editors. */ clearRecentlyOpened(): void; - - /** - * Temporarily suspend tracking of editor events for the history. - */ - suspendTracking(): IDisposable; } diff --git a/src/vs/workbench/services/history/test/browser/historyService.test.ts b/src/vs/workbench/services/history/test/browser/historyService.test.ts index 43838aad189..28f269d3bee 100644 --- a/src/vs/workbench/services/history/test/browser/historyService.test.ts +++ b/src/vs/workbench/services/history/test/browser/historyService.test.ts @@ -807,7 +807,7 @@ suite('HistoryService', function () { return workbenchTeardown(instantiationService); }); - test('suspend should suspend editor changes- skip two editors and continue (single group)', async () => { + test('transient editors suspends editor change tracking', async () => { const [part, historyService, editorService, , instantiationService] = await createServices(); const input1 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID)); @@ -821,79 +821,24 @@ suite('HistoryService', function () { assert.strictEqual(part.activeGroup.activeEditor, input1); await editorChangePromise; - const disposable = historyService.suspendTracking(); - - // wait on two editor changes before disposing - editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange) - .then(() => Event.toPromise(editorService.onDidActiveEditorChange)); - - await part.activeGroup.openEditor(input2, { pinned: true }); + await part.activeGroup.openEditor(input2, { transient: true }); assert.strictEqual(part.activeGroup.activeEditor, input2); - await part.activeGroup.openEditor(input3, { pinned: true }); + await part.activeGroup.openEditor(input3, { transient: true }); assert.strictEqual(part.activeGroup.activeEditor, input3); - await editorChangePromise; - disposable.dispose(); - - await part.activeGroup.openEditor(input4, { pinned: true }); - assert.strictEqual(part.activeGroup.activeEditor, input4); - await part.activeGroup.openEditor(input5, { pinned: true }); - assert.strictEqual(part.activeGroup.activeEditor, input5); - - // stack should be [input1, input4, input5] - await historyService.goBack(); - assert.strictEqual(part.activeGroup.activeEditor, input4); - await historyService.goBack(); - assert.strictEqual(part.activeGroup.activeEditor, input1); - await historyService.goBack(); - assert.strictEqual(part.activeGroup.activeEditor, input1); - - await historyService.goForward(); - assert.strictEqual(part.activeGroup.activeEditor, input4); - await historyService.goForward(); - assert.strictEqual(part.activeGroup.activeEditor, input5); - - return workbenchTeardown(instantiationService); - }); - - test('suspend should suspend editor changes- skip two editors and continue (multi group)', async () => { - const [part, historyService, editorService, , instantiationService] = await createServices(); - const rootGroup = part.activeGroup; - - const input1 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID)); - const input2 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID)); - const input3 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID)); - const input4 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID)); - const input5 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar5'), TEST_EDITOR_INPUT_ID)); - - const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - - let editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); - await rootGroup.openEditor(input1, { pinned: true }); - await editorChangePromise; - - const disposable = historyService.suspendTracking(); editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange) .then(() => Event.toPromise(editorService.onDidActiveEditorChange)); - await sideGroup.openEditor(input2, { pinned: true }); - await rootGroup.openEditor(input3, { pinned: true }); - await editorChangePromise; - disposable.dispose(); - await sideGroup.openEditor(input4, { pinned: true }); - await rootGroup.openEditor(input5, { pinned: true }); + await part.activeGroup.openEditor(input4, { pinned: true }); + assert.strictEqual(part.activeGroup.activeEditor, input4); + await part.activeGroup.openEditor(input5, { pinned: true }); + assert.strictEqual(part.activeGroup.activeEditor, input5); // stack should be [input1, input4, input5] await historyService.goBack(); assert.strictEqual(part.activeGroup.activeEditor, input4); - assert.strictEqual(part.activeGroup, sideGroup); - assert.strictEqual(rootGroup.activeEditor, input5); - await historyService.goBack(); assert.strictEqual(part.activeGroup.activeEditor, input1); - assert.strictEqual(part.activeGroup, rootGroup); - assert.strictEqual(sideGroup.activeEditor, input4); - await historyService.goBack(); assert.strictEqual(part.activeGroup.activeEditor, input1); @@ -905,45 +850,5 @@ suite('HistoryService', function () { return workbenchTeardown(instantiationService); }); - test('suspend should suspend editor changes - interleaved skips', async () => { - const [part, historyService, editorService, , instantiationService] = await createServices(); - - const input1 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID)); - const input2 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID)); - const input3 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID)); - const input4 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID)); - const input5 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar5'), TEST_EDITOR_INPUT_ID)); - - let editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); - await part.activeGroup.openEditor(input1, { pinned: true }); - await editorChangePromise; - - let disposable = historyService.suspendTracking(); - editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); - await part.activeGroup.openEditor(input2, { pinned: true }); - await editorChangePromise; - disposable.dispose(); - - await part.activeGroup.openEditor(input3, { pinned: true }); - - disposable = historyService.suspendTracking(); - editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); - await part.activeGroup.openEditor(input4, { pinned: true }); - await editorChangePromise; - disposable.dispose(); - - await part.activeGroup.openEditor(input5, { pinned: true }); - - // stack should be [input1, input3, input5] - await historyService.goBack(); - assert.strictEqual(part.activeGroup.activeEditor, input3); - await historyService.goBack(); - assert.strictEqual(part.activeGroup.activeEditor, input1); - await historyService.goBack(); - assert.strictEqual(part.activeGroup.activeEditor, input1); - - return workbenchTeardown(instantiationService); - }); - ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/test/browser/parts/editor/filteredEditorGroupModel.test.ts b/src/vs/workbench/test/browser/parts/editor/filteredEditorGroupModel.test.ts index e2b8639f9cc..80765957797 100644 --- a/src/vs/workbench/test/browser/parts/editor/filteredEditorGroupModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/filteredEditorGroupModel.test.ts @@ -789,5 +789,27 @@ suite('FilteredEditorGroupModel', () => { assert.strictEqual(label1ChangeCounterUnsticky, 1); }); + test('Sticky/Unsticky isTransient()', async () => { + const model = createEditorGroupModel(); + + const stickyFilteredEditorGroup = disposables.add(new StickyEditorGroupModel(model)); + const unstickyFilteredEditorGroup = disposables.add(new UnstickyEditorGroupModel(model)); + + const input1 = input(); + const input2 = input(); + const input3 = input(); + const input4 = input(); + + model.openEditor(input1, { pinned: true, transient: false }); + model.openEditor(input2, { pinned: true }); + model.openEditor(input3, { pinned: true, transient: true }); + model.openEditor(input4, { pinned: false, transient: true }); + + assert.strictEqual(stickyFilteredEditorGroup.isTransient(input1), false); + assert.strictEqual(unstickyFilteredEditorGroup.isTransient(input2), false); + assert.strictEqual(stickyFilteredEditorGroup.isTransient(input3), true); + assert.strictEqual(unstickyFilteredEditorGroup.isTransient(input4), true); + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 6db63c1cc2e..4f297a34c83 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -904,6 +904,7 @@ export class TestEditorGroupView implements IEditorGroupView { openEditors(_editors: EditorInputWithOptions[]): Promise { throw new Error('not implemented'); } isPinned(_editor: EditorInput): boolean { return false; } isSticky(_editor: EditorInput): boolean { return false; } + isTransient(_editor: EditorInput): boolean { return false; } isActive(_editor: EditorInput | IUntypedEditorInput): boolean { return false; } contains(candidate: EditorInput | IUntypedEditorInput): boolean { return false; } moveEditor(_editor: EditorInput, _target: IEditorGroup, _options?: IEditorOptions): void { } @@ -917,6 +918,7 @@ export class TestEditorGroupView implements IEditorGroupView { pinEditor(_editor?: EditorInput): void { } stickEditor(editor?: EditorInput | undefined): void { } unstickEditor(editor?: EditorInput | undefined): void { } + setTransient(editor: EditorInput | undefined, transient: boolean): void { } lock(locked: boolean): void { } focus(): void { } get scopedContextKeyService(): IContextKeyService { throw new Error('not implemented'); } diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index 1d09528dcf6..1a938d7dbd6 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -165,7 +165,6 @@ export class TestHistoryService implements IHistoryService { async openPreviouslyUsedEditor(group?: GroupIdentifier): Promise { } getLastActiveWorkspaceRoot(_schemeFilter: string): URI | undefined { return this.root; } getLastActiveFile(_schemeFilter: string): URI | undefined { return undefined; } - suspendTracking() { return Disposable.None; } } export class TestWorkingCopy extends Disposable implements IWorkingCopy { From 24d41e4a2da7b689a76166eb029ddc9740b3c005 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 27 Feb 2024 08:44:50 +0100 Subject: [PATCH 1622/1897] editor - more `transient` fixes (#206320) * editors - clear preview flag when tranient move leaves and preview is disabled * history - log transient state * editors - update accordingly --- .../browser/parts/editor/editorGroupView.ts | 29 ++++++++++++++ .../test/browser/editorGroupsService.test.ts | 3 ++ .../history/browser/historyService.ts | 39 ++++++++++--------- 3 files changed, 53 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index e0761b1f1db..faddd4e7b06 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -544,6 +544,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Visibility this._register(this.groupsView.onDidVisibilityChange(e => this.onDidVisibilityChange(e))); + + // Focus + this._register(this.onDidFocus(() => this.onDidGainFocus())); } private onDidGroupModelChange(e: IGroupModelChangeEvent): void { @@ -578,6 +581,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { case GroupModelChangeKind.EDITOR_DIRTY: this.onDidChangeEditorDirty(e.editor); break; + case GroupModelChangeKind.EDITOR_TRANSIENT: + this.onDidChangeEditorTransient(e.editor); + break; case GroupModelChangeKind.EDITOR_LABEL: this.onDidChangeEditorLabel(e.editor); break; @@ -762,6 +768,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.titleControl.updateEditorDirty(editor); } + private onDidChangeEditorTransient(editor: EditorInput): void { + const transient = this.model.isTransient(editor); + + // Transient state overrides the `enablePreview` setting, + // so when an editor leaves the transient state, we have + // to ensure its preview state is also cleared. + if (!transient && !this.groupsView.partOptions.enablePreview) { + this.pinEditor(editor); + } + } + private onDidChangeEditorLabel(editor: EditorInput): void { // Forward to title control @@ -774,6 +791,18 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.editorPane.setVisible(visible); } + private onDidGainFocus(): void { + if (this.activeEditor) { + + // We aggressively clear the transient state of editors + // as soon as the group gains focus. This is to ensure + // that the transient state is not staying around when + // the user interacts with the editor. + + this.setTransient(this.activeEditor, false); + } + } + //#endregion //#region IEditorGroupView diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index e42c6116bc7..f1e25f4a355 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -1881,6 +1881,9 @@ suite('EditorGroupsService', () => { await group.openEditor(input2, { transient: true }); assert.strictEqual(group.isPinned(input2), false); + + group.setTransient(input2, false); + assert.strictEqual(group.isPinned(input2), true); }); ensureNoDisposablesAreLeakedInTestSuite(); diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts index 3cef7fa9416..efaf3103c6c 100644 --- a/src/vs/workbench/services/history/browser/historyService.ts +++ b/src/vs/workbench/services/history/browser/historyService.ts @@ -35,6 +35,21 @@ import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecyc import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { mainWindow } from 'vs/base/browser/window'; +interface ISerializedEditorHistoryEntry { + readonly editor: Omit & { resource: string }; +} + +interface IRecentlyClosedEditor { + readonly editorId: string | undefined; + readonly editor: IUntypedEditorInput; + + readonly resource: URI | undefined; + readonly associatedResources: URI[]; + + readonly index: number; + readonly sticky: boolean; +} + export class HistoryService extends Disposable implements IHistoryService { declare readonly _serviceBrand: undefined; @@ -47,8 +62,6 @@ export class HistoryService extends Disposable implements IHistoryService { private readonly editorHelper = this.instantiationService.createInstance(EditorHelper); - - constructor( @IEditorService private readonly editorService: EditorServiceImpl, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @@ -59,7 +72,8 @@ export class HistoryService extends Disposable implements IHistoryService { @IWorkspacesService private readonly workspacesService: IWorkspacesService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IContextKeyService private readonly contextKeyService: IContextKeyService + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @ILogService private readonly logService: ILogService ) { super(); @@ -176,6 +190,8 @@ export class HistoryService extends Disposable implements IHistoryService { // Handle editor change unless the editor is transient if (!activeEditorPane?.group.isTransient(activeEditorPane.input)) { this.handleActiveEditorChange(activeEditorGroup, activeEditorPane); + } else { + this.logService.trace(`[History]: ignoring transient editor change (editor: ${activeEditorPane.input?.resource?.toString()}})`); } // Listen to selection changes unless the editor is transient @@ -183,6 +199,8 @@ export class HistoryService extends Disposable implements IHistoryService { this.activeEditorListeners.add(activeEditorPane.onDidChangeSelection(e => { if (!activeEditorPane.group.isTransient(activeEditorPane.input)) { this.handleActiveEditorSelectionChangeEvent(activeEditorGroup, activeEditorPane, e); + } else { + this.logService.trace(`[History]: ignoring transient editor selection change (editor: ${activeEditorPane.input?.resource?.toString()}})`); } })); } @@ -2077,18 +2095,3 @@ class EditorHelper { } } } - -interface ISerializedEditorHistoryEntry { - editor: Omit & { resource: string }; -} - -interface IRecentlyClosedEditor { - editorId: string | undefined; - editor: IUntypedEditorInput; - - resource: URI | undefined; - associatedResources: URI[]; - - index: number; - sticky: boolean; -} From f79ac8713f8d9a355a948ab026b2227f29820780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chanchevrier?= Date: Tue, 27 Feb 2024 09:34:31 +0100 Subject: [PATCH 1623/1897] fix: account for sidebar position when resizing terminal --- .../contrib/terminal/browser/terminalGroup.ts | 74 +++++++++++-------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index 30a95315f33..171c69d6ed3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -7,7 +7,7 @@ import { TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal' import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, Disposable, DisposableStore, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { SplitView, Orientation, IView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; -import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITerminalInstance, Direction, ITerminalGroup, ITerminalService, ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; @@ -42,7 +42,6 @@ class SplitPaneContainer extends Disposable { constructor( private _container: HTMLElement, public orientation: Orientation, - @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService ) { super(); this._width = this._container.offsetWidth; @@ -61,25 +60,7 @@ class SplitPaneContainer extends Disposable { this._addChild(instance, index); } - resizePane(index: number, direction: Direction, amount: number, part: Parts): void { - const isHorizontal = (direction === Direction.Left) || (direction === Direction.Right); - - if ((isHorizontal && this.orientation !== Orientation.HORIZONTAL) || - (!isHorizontal && this.orientation !== Orientation.VERTICAL)) { - // Resize the entire pane as a whole - if ( - (this.orientation === Orientation.HORIZONTAL && direction === Direction.Down) || - (this.orientation === Orientation.VERTICAL && direction === Direction.Right) || - (part === Parts.AUXILIARYBAR_PART && direction === Direction.Right) - ) { - amount *= -1; - } - - this._layoutService.resizePart(part, amount, amount); - return; - } - - // Resize left/right in horizontal or up/down in vertical + resizePane(index: number, direction: Direction, amount: number): void { // Only resize when there is more than one pane if (this._children.length <= 1) { return; @@ -570,28 +551,57 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { this.setActiveInstanceByIndex(newIndex); } + private _getPosition(): Position { + switch (this._terminalLocation) { + case ViewContainerLocation.Panel: + return this._panelPosition; + case ViewContainerLocation.Sidebar: + return this._layoutService.getSideBarPosition(); + case ViewContainerLocation.AuxiliaryBar: + return this._layoutService.getSideBarPosition() === Position.LEFT ? Position.RIGHT : Position.LEFT; + } + } + + private _getOrientation(): Orientation { + return this._getPosition() === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL; + } + resizePane(direction: Direction): void { if (!this._splitPaneContainer) { return; } - const isHorizontal = (direction === Direction.Left || direction === Direction.Right); + const isHorizontalResize = (direction === Direction.Left || direction === Direction.Right); - const part = getPartByLocation(this._terminalLocation); + const groupOrientation = this._getOrientation(); - const isTerminalLeft = this._panelPosition === Position.LEFT || part === Parts.SIDEBAR_PART; - - // Left-positionned panels have inverted controls - // see https://github.com/microsoft/vscode/issues/140873 - const shouldInvertHorizontalResize = (isHorizontal && isTerminalLeft); - - const resizeDirection = shouldInvertHorizontalResize ? direction === Direction.Left ? Direction.Right : Direction.Left : direction; + const shouldResizePart = + (isHorizontalResize && groupOrientation === Orientation.VERTICAL) || + (!isHorizontalResize && groupOrientation === Orientation.HORIZONTAL); const font = this._terminalService.configHelper.getFont(getWindow(this._groupElement)); // TODO: Support letter spacing and line height - const charSize = (isHorizontal ? font.charWidth : font.charHeight); + const charSize = (isHorizontalResize ? font.charWidth : font.charHeight); + if (charSize) { - this._splitPaneContainer.resizePane(this._activeInstanceIndex, resizeDirection, charSize * Constants.ResizePartCellCount, part); + let resizeAmount = charSize * Constants.ResizePartCellCount; + + if (shouldResizePart) { + + const shouldShrink = + (this._getPosition() === Position.LEFT && direction === Direction.Left) || + (this._getPosition() === Position.RIGHT && direction === Direction.Right) || + (this._getPosition() === Position.BOTTOM && direction === Direction.Down); + + if (shouldShrink) { + resizeAmount *= -1; + } + + this._layoutService.resizePart(getPartByLocation(this._terminalLocation), resizeAmount, resizeAmount); + } else { + this._splitPaneContainer.resizePane(this._activeInstanceIndex, direction, resizeAmount); + } + } } From 3a1f27a6e657002489f8034b1f1f1afebd46ca7e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 27 Feb 2024 09:40:43 +0100 Subject: [PATCH 1624/1897] Speech: allow `auto` setting for language to derive from display language (fix #206321) (#206322) --- .../browser/accessibilityConfiguration.ts | 89 ++------------- .../contrib/speech/browser/speechService.ts | 4 +- .../contrib/speech/common/speechService.ts | 102 ++++++++++++++++++ .../speech/test/common/speechService.test.ts | 27 +++++ 4 files changed, 138 insertions(+), 84 deletions(-) create mode 100644 src/vs/workbench/contrib/speech/test/common/speechService.test.ts diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 9a42093991c..33a1379d3bd 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -9,7 +9,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { workbenchConfigurationNodeBase, Extensions as WorkbenchExtensions, IConfigurationMigrationRegistry, ConfigurationKeyValuePairs } from 'vs/workbench/common/configuration'; import { AccessibilityAlertSettingId, AccessibilitySignal } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; -import { ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; +import { ISpeechService, SPEECH_LANGUAGES, SPEECH_LANGUAGE_CONFIG } from 'vs/workbench/contrib/speech/common/speechService'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Event } from 'vs/base/common/event'; @@ -664,10 +664,9 @@ export function registerAccessibilityConfiguration() { export const enum AccessibilityVoiceSettingId { SpeechTimeout = 'accessibility.voice.speechTimeout', - SpeechLanguage = 'accessibility.voice.speechLanguage' + SpeechLanguage = SPEECH_LANGUAGE_CONFIG } export const SpeechTimeoutDefault = 1200; -const SpeechLanguageDefault = 'en-US'; export class DynamicSpeechAccessibilityConfiguration extends Disposable implements IWorkbenchContribution { @@ -703,10 +702,10 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen 'tags': ['accessibility'] }, [AccessibilityVoiceSettingId.SpeechLanguage]: { - 'markdownDescription': localize('voice.speechLanguage', "The language that voice speech recognition should recognize."), + 'markdownDescription': localize('voice.speechLanguage', "The language that voice speech recognition should recognize. Select `auto` to use the configured display language if possible. Note that not all display languages maybe supported by speech recognition"), 'type': 'string', 'enum': languagesSorted, - 'default': SpeechLanguageDefault, + 'default': 'auto', 'tags': ['accessibility'], 'enumDescriptions': languagesSorted.map(key => languages[key].name), 'enumItemLabels': languagesSorted.map(key => languages[key].name) @@ -717,84 +716,10 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen private getLanguages(): { [locale: string]: { name: string } } { return { - ['da-DK']: { - name: localize('speechLanguage.da-DK', "Danish (Denmark)") + ['auto']: { + name: localize('speechLanguage.auto', "Auto (Use Display Language)") }, - ['de-DE']: { - name: localize('speechLanguage.de-DE', "German (Germany)") - }, - ['en-AU']: { - name: localize('speechLanguage.en-AU', "English (Australia)") - }, - ['en-CA']: { - name: localize('speechLanguage.en-CA', "English (Canada)") - }, - ['en-GB']: { - name: localize('speechLanguage.en-GB', "English (United Kingdom)") - }, - ['en-IE']: { - name: localize('speechLanguage.en-IE', "English (Ireland)") - }, - ['en-IN']: { - name: localize('speechLanguage.en-IN', "English (India)") - }, - ['en-NZ']: { - name: localize('speechLanguage.en-NZ', "English (New Zealand)") - }, - [SpeechLanguageDefault]: { - name: localize('speechLanguage.en-US', "English (United States)") - }, - ['es-ES']: { - name: localize('speechLanguage.es-ES', "Spanish (Spain)") - }, - ['es-MX']: { - name: localize('speechLanguage.es-MX', "Spanish (Mexico)") - }, - ['fr-CA']: { - name: localize('speechLanguage.fr-CA', "French (Canada)") - }, - ['fr-FR']: { - name: localize('speechLanguage.fr-FR', "French (France)") - }, - ['hi-IN']: { - name: localize('speechLanguage.hi-IN', "Hindi (India)") - }, - ['it-IT']: { - name: localize('speechLanguage.it-IT', "Italian (Italy)") - }, - ['ja-JP']: { - name: localize('speechLanguage.ja-JP', "Japanese (Japan)") - }, - ['ko-KR']: { - name: localize('speechLanguage.ko-KR', "Korean (South Korea)") - }, - ['nl-NL']: { - name: localize('speechLanguage.nl-NL', "Dutch (Netherlands)") - }, - ['pt-PT']: { - name: localize('speechLanguage.pt-PT', "Portuguese (Portugal)") - }, - ['pt-BR']: { - name: localize('speechLanguage.pt-BR', "Portuguese (Brazil)") - }, - ['ru-RU']: { - name: localize('speechLanguage.ru-RU', "Russian (Russia)") - }, - ['sv-SE']: { - name: localize('speechLanguage.sv-SE', "Swedish (Sweden)") - }, - ['tr-TR']: { - name: localize('speechLanguage.tr-TR', "Turkish (Turkey)") - }, - ['zh-CN']: { - name: localize('speechLanguage.zh-CN', "Chinese (Simplified, China)") - }, - ['zh-HK']: { - name: localize('speechLanguage.zh-HK', "Chinese (Traditional, Hong Kong)") - }, - ['zh-TW']: { - name: localize('speechLanguage.zh-TW', "Chinese (Traditional, Taiwan)") - } + ...SPEECH_LANGUAGES }; } } diff --git a/src/vs/workbench/contrib/speech/browser/speechService.ts b/src/vs/workbench/contrib/speech/browser/speechService.ts index 00943f3262b..d0255933209 100644 --- a/src/vs/workbench/contrib/speech/browser/speechService.ts +++ b/src/vs/workbench/contrib/speech/browser/speechService.ts @@ -11,7 +11,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ILogService } from 'vs/platform/log/common/log'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { DeferredPromise } from 'vs/base/common/async'; -import { ISpeechService, ISpeechProvider, HasSpeechProvider, ISpeechToTextSession, SpeechToTextInProgress, IKeywordRecognitionSession, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { ISpeechService, ISpeechProvider, HasSpeechProvider, ISpeechToTextSession, SpeechToTextInProgress, IKeywordRecognitionSession, KeywordRecognitionStatus, SpeechToTextStatus, speechLanguageConfigToLanguage, SPEECH_LANGUAGE_CONFIG } from 'vs/workbench/contrib/speech/common/speechService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -80,7 +80,7 @@ export class SpeechService extends Disposable implements ISpeechService { this.logService.warn(`Multiple speech providers registered. Picking first one: ${provider.metadata.displayName}`); } - const language = this.configurationService.getValue('accessibility.voice.speechLanguage'); + const language = speechLanguageConfigToLanguage(this.configurationService.getValue(SPEECH_LANGUAGE_CONFIG)); const session = this._activeSpeechToTextSession = provider.createSpeechToTextSession(token, typeof language === 'string' ? { language } : undefined); const sessionStart = Date.now(); diff --git a/src/vs/workbench/contrib/speech/common/speechService.ts b/src/vs/workbench/contrib/speech/common/speechService.ts index a594b4f3acf..e1bbf0f4f8e 100644 --- a/src/vs/workbench/contrib/speech/common/speechService.ts +++ b/src/vs/workbench/contrib/speech/common/speechService.ts @@ -10,6 +10,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { language } from 'vs/base/common/platform'; export const ISpeechService = createDecorator('speechService'); @@ -97,3 +98,104 @@ export interface ISpeechService { */ recognizeKeyword(token: CancellationToken): Promise; } + +export const SPEECH_LANGUAGE_CONFIG = 'accessibility.voice.speechLanguage'; + +export const SPEECH_LANGUAGES = { + ['da-DK']: { + name: localize('speechLanguage.da-DK', "Danish (Denmark)") + }, + ['de-DE']: { + name: localize('speechLanguage.de-DE', "German (Germany)") + }, + ['en-AU']: { + name: localize('speechLanguage.en-AU', "English (Australia)") + }, + ['en-CA']: { + name: localize('speechLanguage.en-CA', "English (Canada)") + }, + ['en-GB']: { + name: localize('speechLanguage.en-GB', "English (United Kingdom)") + }, + ['en-IE']: { + name: localize('speechLanguage.en-IE', "English (Ireland)") + }, + ['en-IN']: { + name: localize('speechLanguage.en-IN', "English (India)") + }, + ['en-NZ']: { + name: localize('speechLanguage.en-NZ', "English (New Zealand)") + }, + ['en-US']: { + name: localize('speechLanguage.en-US', "English (United States)") + }, + ['es-ES']: { + name: localize('speechLanguage.es-ES', "Spanish (Spain)") + }, + ['es-MX']: { + name: localize('speechLanguage.es-MX', "Spanish (Mexico)") + }, + ['fr-CA']: { + name: localize('speechLanguage.fr-CA', "French (Canada)") + }, + ['fr-FR']: { + name: localize('speechLanguage.fr-FR', "French (France)") + }, + ['hi-IN']: { + name: localize('speechLanguage.hi-IN', "Hindi (India)") + }, + ['it-IT']: { + name: localize('speechLanguage.it-IT', "Italian (Italy)") + }, + ['ja-JP']: { + name: localize('speechLanguage.ja-JP', "Japanese (Japan)") + }, + ['ko-KR']: { + name: localize('speechLanguage.ko-KR', "Korean (South Korea)") + }, + ['nl-NL']: { + name: localize('speechLanguage.nl-NL', "Dutch (Netherlands)") + }, + ['pt-PT']: { + name: localize('speechLanguage.pt-PT', "Portuguese (Portugal)") + }, + ['pt-BR']: { + name: localize('speechLanguage.pt-BR', "Portuguese (Brazil)") + }, + ['ru-RU']: { + name: localize('speechLanguage.ru-RU', "Russian (Russia)") + }, + ['sv-SE']: { + name: localize('speechLanguage.sv-SE', "Swedish (Sweden)") + }, + ['tr-TR']: { + name: localize('speechLanguage.tr-TR', "Turkish (Turkey)") + }, + ['zh-CN']: { + name: localize('speechLanguage.zh-CN', "Chinese (Simplified, China)") + }, + ['zh-HK']: { + name: localize('speechLanguage.zh-HK', "Chinese (Traditional, Hong Kong)") + }, + ['zh-TW']: { + name: localize('speechLanguage.zh-TW', "Chinese (Traditional, Taiwan)") + } +}; + +export function speechLanguageConfigToLanguage(config: unknown, lang = language): string { + if (typeof config === 'string') { + if (config === 'auto') { + if (lang !== 'en') { + const langParts = lang.split('-'); + + return speechLanguageConfigToLanguage(`${langParts[0]}-${(langParts[1] ?? langParts[0]).toUpperCase()}`); + } + } else { + if (SPEECH_LANGUAGES[config as keyof typeof SPEECH_LANGUAGES]) { + return config; + } + } + } + + return 'en-US'; +} diff --git a/src/vs/workbench/contrib/speech/test/common/speechService.test.ts b/src/vs/workbench/contrib/speech/test/common/speechService.test.ts new file mode 100644 index 00000000000..d757eace7e0 --- /dev/null +++ b/src/vs/workbench/contrib/speech/test/common/speechService.test.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { speechLanguageConfigToLanguage } from 'vs/workbench/contrib/speech/common/speechService'; + +suite('SpeechService', () => { + + test('resolve language', async () => { + assert.strictEqual(speechLanguageConfigToLanguage(undefined), 'en-US'); + assert.strictEqual(speechLanguageConfigToLanguage(3), 'en-US'); + assert.strictEqual(speechLanguageConfigToLanguage('foo'), 'en-US'); + assert.strictEqual(speechLanguageConfigToLanguage('foo-bar'), 'en-US'); + + assert.strictEqual(speechLanguageConfigToLanguage('tr-TR'), 'tr-TR'); + assert.strictEqual(speechLanguageConfigToLanguage('zh-TW'), 'zh-TW'); + + assert.strictEqual(speechLanguageConfigToLanguage('auto', 'en'), 'en-US'); + assert.strictEqual(speechLanguageConfigToLanguage('auto', 'tr'), 'tr-TR'); + assert.strictEqual(speechLanguageConfigToLanguage('auto', 'zh-tw'), 'zh-TW'); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); +}); From c036c4dadf9d26ea3a6dffdc73ca3f1d04506b6b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 27 Feb 2024 09:56:36 +0100 Subject: [PATCH 1625/1897] Support local install for more error cases (#206323) --- .../electron-sandbox/remoteExtensionManagementService.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts index 476712eaf1f..68181b0b830 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts @@ -65,7 +65,10 @@ export class NativeRemoteExtensionManagementService extends RemoteExtensionManag } catch (error) { switch (error.name) { case ExtensionManagementErrorCode.Download: + case ExtensionManagementErrorCode.DownloadSignature: + case ExtensionManagementErrorCode.Gallery: case ExtensionManagementErrorCode.Internal: + case ExtensionManagementErrorCode.Unknown: try { this.logService.error(`Error while installing '${extension.identifier.id}' extension in the remote server.`, toErrorMessage(error)); return await this.downloadAndInstall(extension, installOptions || {}); From 82bba2e78bbac1a35809e74cfc878df8a6f193ef Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 27 Feb 2024 11:06:22 +0100 Subject: [PATCH 1626/1897] fix #206249 (#206328) --- src/vs/workbench/api/browser/mainThreadLanguageModels.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts index 6e33d7da99d..f8167d4dc52 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts @@ -64,7 +64,7 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { } dipsosables.add(Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: `lm-${identifier}`, - label: localize('languageModels', "Language Model ({0})", `${identifier}-${metadata.model}`), + label: localize('languageModels', "Language Model ({0})", `${identifier}`), access: { canToggle: false, }, From e9a8b6add5329f8124f23ff7143d95c22ce39f76 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 27 Feb 2024 12:14:40 +0100 Subject: [PATCH 1627/1897] Update grammars (#206330) --- extensions/dart/cgmanifest.json | 2 +- extensions/dart/syntaxes/dart.tmLanguage.json | 10 +- extensions/go/cgmanifest.json | 4 +- extensions/go/syntaxes/go.tmLanguage.json | 140 ++++++++++++------ extensions/latex/cgmanifest.json | 4 +- extensions/razor/cgmanifest.json | 2 +- .../razor/syntaxes/cshtml.tmLanguage.json | 80 +++++++++- extensions/scss/cgmanifest.json | 4 +- 8 files changed, 188 insertions(+), 58 deletions(-) diff --git a/extensions/dart/cgmanifest.json b/extensions/dart/cgmanifest.json index 0086a5158e5..9c90588adf1 100644 --- a/extensions/dart/cgmanifest.json +++ b/extensions/dart/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "dart-lang/dart-syntax-highlight", "repositoryUrl": "https://github.com/dart-lang/dart-syntax-highlight", - "commitHash": "0a6648177bdbb91a4e1a38c16e57ede0ccba4f18" + "commitHash": "272e2f89f85073c04b7e15b582257f76d2489970" } }, "licenseDetail": [ diff --git a/extensions/dart/syntaxes/dart.tmLanguage.json b/extensions/dart/syntaxes/dart.tmLanguage.json index ae4db9698e9..cc9dee8d275 100644 --- a/extensions/dart/syntaxes/dart.tmLanguage.json +++ b/extensions/dart/syntaxes/dart.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/dart-lang/dart-syntax-highlight/commit/0a6648177bdbb91a4e1a38c16e57ede0ccba4f18", + "version": "https://github.com/dart-lang/dart-syntax-highlight/commit/272e2f89f85073c04b7e15b582257f76d2489970", "name": "Dart", "scopeName": "source.dart", "patterns": [ @@ -308,7 +308,7 @@ }, { "name": "keyword.control.dart", - "match": "(?\\-]+(?:\\s*)(?:\\/(?:\\/|\\*).*)?)$)", + "match": "(?:(?<=\\))(?:\\s*)((?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?[\\w\\*\\.\\[\\]\\<\\>\\-]+(?:\\s*)(?:\\/(?:\\/|\\*).*)?)$)", "captures": { "1": { "patterns": [ @@ -1272,7 +1267,7 @@ "include": "#parameter-variable-types" }, { - "match": "(?:\\w+)", + "match": "\\w+", "name": "entity.name.type.go" } ] @@ -1513,7 +1508,7 @@ }, "functions_inline": { "comment": "functions in-line with multi return types", - "match": "(?:(\\bfunc\\b)((?:\\((?:[^/]*)\\))(?:\\s+)(?:\\((?:[^/]*)\\)))(?:\\s+)(?=\\{))", + "match": "(?:(\\bfunc\\b)((?:\\((?:[^/]*?)\\))(?:\\s+)(?:\\((?:[^/]*?)\\)))(?:\\s+)(?=\\{))", "captures": { "1": { "name": "keyword.function.go" @@ -1571,7 +1566,7 @@ }, "support_functions": { "comment": "Support Functions", - "match": "(?:(?:((?<=\\.)\\w+)|(\\w+))(\\[(?:(?:[\\w\\.\\*\\[\\]\\{\\}\"\\']+)(?:(?:\\,\\s*(?:[\\w\\.\\*\\[\\]\\{\\}]+))*))?\\])?(?=\\())", + "match": "(?:(?:((?<=\\.)\\b\\w+)|(\\b\\w+))(\\[(?:(?:[\\w\\.\\*\\[\\]\\{\\}\"\\']+)(?:(?:\\,\\s*(?:[\\w\\.\\*\\[\\]\\{\\}]+))*))?\\])?(?=\\())", "captures": { "1": { "name": "entity.name.function.support.go" @@ -1867,11 +1862,18 @@ "patterns": [ { "comment": "Struct variable for struct in struct types", - "begin": "(?:\\s*)?([\\s\\,\\w]+)(?:\\s+)(?:(?:[\\[\\]\\*])+)?(\\bstruct\\b)\\s*(\\{)", + "begin": "(?:(\\w+(?:\\,\\s*\\w+)*)(?:\\s+)(?:(?:[\\[\\]\\*])+)?(\\bstruct\\b)(?:\\s*)(\\{))", "beginCaptures": { "1": { - "match": "(?:\\w+)", - "name": "variable.other.property.go" + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "variable.other.property.go" + } + ] }, "2": { "name": "keyword.struct.go" @@ -1911,6 +1913,42 @@ } }, "patterns": [ + { + "include": "#support_functions" + }, + { + "include": "#type-declarations-without-brackets" + }, + { + "begin": "(?:([\\w\\.\\*]+)?(\\[))", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + }, + "2": { + "name": "punctuation.definition.begin.bracket.square.go" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.square.go" + } + }, + "patterns": [ + { + "include": "#generic_param_types" + } + ] + }, { "begin": "\\(", "beginCaptures": { @@ -1927,18 +1965,12 @@ "patterns": [ { "include": "#function_param_types" - }, - { - "include": "$self" } ] }, { - "include": "#support_functions" - }, - { - "comment": "single declaration | with or declarations", - "match": "((?:\\s+\\|)?(?:[\\w\\.\\[\\]\\*]+)(?:\\s+\\|)?)", + "comment": "other types", + "match": "([\\w\\.]+)", "captures": { "1": { "patterns": [ @@ -1946,10 +1978,7 @@ "include": "#type-declarations" }, { - "include": "#generic_types" - }, - { - "match": "(?:\\w+)", + "match": "\\w+", "name": "entity.name.type.go" } ] @@ -2145,7 +2174,7 @@ }, "after_control_variables": { "comment": "After control variables, to not highlight as a struct/interface (before formatting with gofmt)", - "match": "(?:(?<=\\brange\\b|\\bswitch\\b|\\;|\\bif\\b|\\bfor\\b|\\<|\\>|\\<\\=|\\>\\=|\\=\\=|\\!\\=|\\w(?:\\+|/|\\-|\\*|\\%)|\\w(?:\\+|/|\\-|\\*|\\%)\\=|\\|\\||\\&\\&)(?:\\s*)([[:alnum:]\\-\\_\\!\\.\\[\\]\\<\\>\\=\\*/\\+\\%\\:]+)(?:\\s*)(?=\\{))", + "match": "(?:(?<=\\brange\\b|\\bswitch\\b|\\;|\\bif\\b|\\bfor\\b|\\<|\\>|\\<\\=|\\>\\=|\\=\\=|\\!\\=|\\w(?:\\+|/|\\-|\\*|\\%)|\\w(?:\\+|/|\\-|\\*|\\%)\\=|\\|\\||\\&\\&)(?:\\s*)((?![\\[\\]]+)[[:alnum:]\\-\\_\\!\\.\\[\\]\\<\\>\\=\\*/\\+\\%\\:]+)(?:\\s*)(?=\\{))", "captures": { "1": { "patterns": [ @@ -2234,7 +2263,7 @@ }, { "comment": "make keyword", - "match": "(?:(\\bmake\\b)(?:(\\()((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+(?:\\([^\\)]+\\))?)?(?:[\\w\\.\\*\\[\\]\\{\\}]+)?(?:\\[(?:[^\\]]+)?\\])?(?:[\\w\\.\\*\\[\\]\\{\\}]+)?)?((?:\\,\\s*(?:[\\w\\.\\(\\)]+)?)+)?(\\))))", + "match": "(?:(\\bmake\\b)(?:(\\()((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+(?:\\([^\\)]+\\))?)?(?:[\\w\\.\\*\\[\\]\\{\\}]+)?(?:\\[(?:[^\\]]+)?\\])?(?:[\\w\\.\\*\\[\\]\\{\\}]+)?)?((?:\\,\\s*(?:[\\w\\.\\(\\)/\\+\\-\\<\\>\\&\\|\\%\\*]+)?)+)?(\\))))", "captures": { "1": { "name": "entity.name.function.support.builtin.go" @@ -2291,6 +2320,7 @@ } }, "switch_types": { + "comment": "switch type assertions, only highlights types after case keyword", "begin": "(?<=\\bswitch\\b)(?:\\s*)(?:(\\w+\\s*\\:\\=)?\\s*([\\w\\.\\*\\(\\)\\[\\]]+))(\\.\\(\\btype\\b\\)\\s*)(\\{)", "beginCaptures": { "1": { @@ -2299,7 +2329,7 @@ "include": "#operators" }, { - "match": "(?:\\w+)", + "match": "\\w+", "name": "variable.other.assignment.go" } ] @@ -2313,7 +2343,7 @@ "include": "#type-declarations" }, { - "match": "(?:\\w+)", + "match": "\\w+", "name": "variable.other.go" } ] @@ -2344,9 +2374,7 @@ }, "patterns": [ { - "include": "#type-declarations" - }, - { + "comment": "types after case keyword with single line", "match": "(?:^\\s*(\\bcase\\b))(?:\\s+)([\\w\\.\\,\\*\\=\\<\\>\\!\\s]+)(:)(\\s*/(?:/|\\*)\\s*.*)?$", "captures": { "1": { @@ -2375,6 +2403,30 @@ } } }, + { + "comment": "types after case keyword with multi lines", + "begin": "\\bcase\\b", + "beginCaptures": { + "0": { + "name": "keyword.control.go" + } + }, + "end": "\\:", + "endCaptures": { + "0": { + "name": "punctuation.other.colon.go" + } + }, + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "entity.name.type.go" + } + ] + }, { "include": "$self" } @@ -2573,7 +2625,7 @@ } }, "switch_select_case_variables": { - "comment": "variables after case control keyword in switch/select expression", + "comment": "variables after case control keyword in switch/select expression, to not scope them as property variables", "match": "(?:(?:^\\s*(\\bcase\\b))(?:\\s+)([\\s\\S]+(?:\\:)\\s*(?:/(?:/|\\*).*)?)$)", "captures": { "1": { @@ -2587,6 +2639,9 @@ { "include": "#support_functions" }, + { + "include": "#variable_assignment" + }, { "match": "\\w+", "name": "variable.other.go" @@ -2710,7 +2765,7 @@ }, "double_parentheses_types": { "comment": "double parentheses types", - "match": "(?:(\\((?:[\\w\\.\\[\\]\\*\\&]+)\\))(?=\\())", + "match": "(?:(? Date: Tue, 27 Feb 2024 12:15:12 +0100 Subject: [PATCH 1628/1897] Remove unused code-feature in release notes (#206331) --- src/vs/base/common/network.ts | 5 - .../browser/markdownSettingRenderer.ts | 44 +------- .../update/browser/releaseNotesEditor.ts | 104 +----------------- 3 files changed, 7 insertions(+), 146 deletions(-) diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 5cbdad174b7..cfc5b173887 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -117,11 +117,6 @@ export namespace Schemas { * Scheme used for special rendering of settings in the release notes */ export const codeSetting = 'code-setting'; - - /** - * Scheme used for special rendering of features in the release notes - */ - export const codeFeature = 'code-feature'; } export function matchesScheme(target: URI | string, scheme: string): boolean { diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index c4cf0aa94fe..095a2978564 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -17,7 +17,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; const codeSettingRegex = /^/; -const codeFeatureRegex = /^/; export class SimpleSettingRenderer { private _defaultSettings: DefaultSettings; @@ -45,10 +44,10 @@ export class SimpleSettingRenderer { getHtmlRenderer(): (html: string) => string { return (html): string => { - const match = codeSettingRegex.exec(html) ?? codeFeatureRegex.exec(html); + const match = codeSettingRegex.exec(html); if (match && match.length === 4) { const settingId = match[2]; - const rendered = this.render(settingId, match[3], match[1] === 'codefeature'); + const rendered = this.render(settingId, match[3]); if (rendered) { html = html.replace(codeSettingRegex, rendered); } @@ -61,10 +60,6 @@ export class SimpleSettingRenderer { return `${Schemas.codeSetting}://${settingId}${value ? `/${value}` : ''}`; } - featureToUriString(settingId: string, value?: any): string { - return `${Schemas.codeFeature}://${settingId}${value ? `/${value}` : ''}`; - } - private settingsGroups: ISettingsGroup[] | undefined = undefined; private getSetting(settingId: string): ISetting | undefined { if (!this.settingsGroups) { @@ -106,16 +101,13 @@ export class SimpleSettingRenderer { } } - private render(settingId: string, newValue: string, asFeature: boolean): string | undefined { + private render(settingId: string, newValue: string): string | undefined { const setting = this.getSetting(settingId); if (!setting) { return ''; } - if (asFeature) { - return this.renderFeature(setting, newValue); - } else { - return this.renderSetting(setting, newValue); - } + + return this.renderSetting(setting, newValue); } private viewInSettingsMessage(settingId: string, alreadyDisplayed: boolean) { @@ -176,15 +168,6 @@ export class SimpleSettingRenderer { `; } - private renderFeature(setting: ISetting, newValue: string): string | undefined { - const href = this.featureToUriString(setting.key, newValue); - const parsedValue = this.parseValue(setting.key, newValue); - const isChecked = this._configurationService.getValue(setting.key) === parsedValue; - this._featuredSettings.set(setting.key, parsedValue); - const title = nls.localize('changeFeatureTitle', "Toggle feature with setting {0}", setting.key); - return `